以太坊源码分析之六交易


以太坊的交易才是真正为“币圈”重视的,代码啥的,人家根本不感兴趣,交易也算是“币圈”和“链圈”的结合点吧。
一、 交易的流程
以太坊的交易大致分以下几步:
1、 发起交易:指定目标地址和交易金额以及相关的gas/gaslimit发起相关交易,如果目标地址为空,则表示其为一个智能合约的交易。
2、 交易签名:使用私钥对交易进行签名。这涉及到上一篇中帐户的私钥和公钥的产生机制。
3、 提交交易:把交易添加到交易池中,类似于比特币。签名验证后,通过一定的规则对池内的交易进行排序(如交易的gas)。
4、 广播交易:EVM执行交易,然后广播到其他节点。
5、 交易打包:POW工作量证明,然后形成新的区块打包交易。
6、 广播区块事件:即提交最终的区块。
通过以上六个步骤,把一个完整的交易过程展现出来,严格意义上来说,5和6要归到区块链的模块中去,但是一个完整的交易是需要有这个过程才能证明是完成的。所以在说明时,重点说明前四条。
二、 交易的步骤
1、 发起交易
交易的发起是通过RPC来进行,而以太坊的js命令操作最终都会落在internel这个包(应该还记得在node启动时startHTTP时注册API,如果不清楚,安装好环境,下断点调试一下startRPC函数)文件下的api.go中。而交易最终也是落到了:
// SendTransaction creates a transaction for the given argument, sign it and submit it to the
// transaction pool.
func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args SendTxArgs) (common.Hash, error) {


// Look up the wallet containing the requested signer查找相关钱包
account := accounts.Account{Address: args.From}


wallet, err := s.b.AccountManager().Find(account)
if err != nil {
return common.Hash{}, err
}


if args.Nonce == nil {
// Hold the addresse's mutex around signing to prevent concurrent assignment of
// the same nonce to multiple accounts.前面提出的问题,防止双花
s.nonceLock.LockAddr(args.From)
defer s.nonceLock.UnlockAddr(args.From)
}


// Set some sanity defaults and terminate on failure
if err := args.setDefaults(ctx, s.b); err != nil {
return common.Hash{}, err
}
// Assemble the transaction and sign with the wallet处理交易并签名
tx := args.toTransaction()


var chainID *big.Int
if config := s.b.ChainConfig(); config.IsEIP155(s.b.CurrentBlock().Number()) {
chainID = config.ChainId
}
signed, err := wallet.SignTx(account, tx, chainID)
if err != nil {
return common.Hash{}, err
}
    //提交交易
return submitTransaction(ctx, s.b, signed)
}
需要说明的是,在这个文件中还有一个同名的函数,但是属于PrivateAccountAPI  这个是管理员使用的,不要混淆。


2、 创建交易并签名
创建交易,使用SendTxArgs类:
func (args *SendTxArgs) toTransaction() *types.Transaction {
var input []byte
if args.Data != nil {
input = *args.Data
} else if args.Input != nil {
input = *args.Input
}
if args.To == nil {
return types.NewContractCreation(uint64(*args.Nonce), (*big.Int)(args.Value), uint64(*args.Gas), (*big.Int)(args.GasPrice), input)
}
return types.NewTransaction(uint64(*args.Nonce), *args.To, (*big.Int)(args.Value), uint64(*args.Gas), (*big.Int)(args.GasPrice), input)
}


签名:
// SignTx signs the given transaction with the requested account.
func (ks *KeyStore) SignTx(a accounts.Account, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
// Look up the key to sign with and abort if it cannot be found
ks.mu.RLock()
defer ks.mu.RUnlock()


unlockedKey, found := ks.unlocked[a.Address]
if !found {
return nil, ErrLocked
}
// Depending on the presence of the chain ID, sign with EIP155 or homestead
if chainID != nil {
return types.SignTx(tx, types.NewEIP155Signer(chainID), unlockedKey.PrivateKey)
}
return types.SignTx(tx, types.HomesteadSigner{}, unlockedKey.PrivateKey)
}
最终其实还是调用了交易签名tx.WithSignature
3、 交易提交
看一下提交:
// submitTransaction is a helper function that submits tx to txPool and logs a message.
func submitTransaction(ctx context.Context, b Backend, tx *types.Transaction) (common.Hash, error) {
    //提交到本地交易池
if err := b.SendTx(ctx, tx); err != nil {
return common.Hash{}, err
}
    //如果TO为空,则为智能合约交易,前面提到过
if tx.To() == nil {
signer := types.MakeSigner(b.ChainConfig(), b.CurrentBlock().Number())
from, err := types.Sender(signer, tx)
if err != nil {
return common.Hash{}, err
}
addr := crypto.CreateAddress(from, tx.Nonce())
log.Info("Submitted contract creation", "fullhash", tx.Hash().Hex(), "contract", addr.Hex())
} else {
log.Info("Submitted transaction", "fullhash", tx.Hash().Hex(), "recipient", tx.To())
}
return tx.Hash(), nil
}
它的注释上说,这个函数是一个帮助函数,它的作用是提交并记录日志。
func (b *EthApiBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error {
return b.eth.txPool.AddLocal(signedTx)
}
最终会调用:
// addTx enqueues a single transaction into the pool if it is valid.
func (pool *TxPool) addTx(tx *types.Transaction, local bool) error {
pool.mu.Lock()
defer pool.mu.Unlock()


// Try to inject the transaction and update any state这个是关键
replace, err := pool.add(tx, local)
if err != nil {
return err
}
// If we added a new transaction, run promotion checks and return
    //预执行一下,主要是防止出现各种问题,不过要是GAS不够,就悲剧
if !replace {
from, _ := types.Sender(pool.signer, tx) // already validated
pool.promoteExecutables([]common.Address{from})
}
return nil
}
重中之重来了,进了交易池基本这事就OK了
func (pool *TxPool) add(tx *types.Transaction, local bool) (bool, error) {
// If the transaction is already known, discard it
    //如果交易已经存在,丢弃当前交易
hash := tx.Hash()
if pool.all[hash] != nil {
log.Trace("Discarding already known transaction", "hash", hash)
return false, fmt.Errorf("known transaction: %x", hash)
}
// If the transaction fails basic validation, discard it
    //交易的基本验证(包含交易的大小,签名等),看后面代码分析
if err := pool.validateTx(tx, local); err != nil {
log.Trace("Discarding invalid transaction", "hash", hash, "err", err)
invalidTxCounter.Inc(1)
return false, err
}
// If the transaction pool is full, discard underpriced transactions
    //搞掉不喜欢的低价交易,当交易队列满的时候
if uint64(len(pool.all)) >= pool.config.GlobalSlots+pool.config.GlobalQueue {
// If the new transaction is underpriced, don't accept it
if pool.priced.Underpriced(tx, pool.locals) {
log.Trace("Discarding underpriced transaction", "hash", hash, "price", tx.GasPrice())
underpricedTxCounter.Inc(1)
return false, ErrUnderpriced
}
// New transaction is better than our worse ones, make room for it
drop := pool.priced.Discard(len(pool.all)-int(pool.config.GlobalSlots+pool.config.GlobalQueue-1), pool.locals)
for _, tx := range drop {
log.Trace("Discarding freshly underpriced transaction", "hash", tx.Hash(), "price", tx.GasPrice())
underpricedTxCounter.Inc(1)
pool.removeTx(tx.Hash())
}
}
// If the transaction is replacing an already pending one, do directly
from, _ := types.Sender(pool.signer, tx) // already validated
if list := pool.pending[from]; list != nil && list.Overlaps(tx) {
// Nonce already pending, check if required price bump is met
inserted, old := list.Add(tx, pool.config.PriceBump)
if !inserted {
pendingDiscardCounter.Inc(1)
return false, ErrReplaceUnderpriced
}
// New transaction is better, replace old one
if old != nil {
delete(pool.all, old.Hash())
pool.priced.Removed()
pendingReplaceCounter.Inc(1)
}
pool.all[tx.Hash()] = tx
pool.priced.Put(tx)
pool.journalTx(from, tx)


log.Trace("Pooled new executable transaction", "hash", hash, "from", from, "to", tx.To())


// We've directly injected a replacement transaction, notify subsystems
go pool.txFeed.Send(TxPreEvent{tx})


return old != nil, nil
}
// New transaction isn't replacing a pending one, push into queue
replace, err := pool.enqueueTx(hash, tx)
if err != nil {
return false, err
}
// Mark local addresses and journal local transactions
if local {
pool.locals.add(from)
}
pool.journalTx(from, tx)


log.Trace("Pooled new future transaction", "hash", hash, "from", from, "to", tx.To())
return replace, nil
}
交易的基本验证:
// validateTx checks whether a transaction is valid according to the consensus
// rules and adheres to some heuristic limits of the local node (price and size).
func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
// Heuristic limit, reject transactions over 32KB to prevent DOS attacks
if tx.Size() > 32*1024 {
return ErrOversizedData
}
// Transactions can't be negative. This may never happen using RLP decoded
// transactions but may occur if you create a transaction using the RPC.
if tx.Value().Sign() < 0 {
return ErrNegativeValue
}
// Ensure the transaction doesn't exceed the current block limit gas.
if pool.currentMaxGas < tx.Gas() {
return ErrGasLimit
}
// Make sure the transaction is signed properly
from, err := types.Sender(pool.signer, tx)
if err != nil {
return ErrInvalidSender
}
// Drop non-local transactions under our own minimal accepted gas price
local = local || pool.locals.contains(from) // account may be local even if the transaction arrived from the network
if !local && pool.gasPrice.Cmp(tx.GasPrice()) > 0 {
return ErrUnderpriced
}
// Ensure the transaction adheres to nonce ordering
if pool.currentState.GetNonce(from) > tx.Nonce() {
return ErrNonceTooLow
}
// Transactor should have enough funds to cover the costs
// cost == V + GP * GL
if pool.currentState.GetBalance(from).Cmp(tx.Cost()) < 0 {
return ErrInsufficientFunds
}
intrGas, err := IntrinsicGas(tx.Data(), tx.To() == nil, pool.homestead)
if err != nil {
return err
}
if tx.Gas() < intrGas {
return ErrIntrinsicGas
}
return nil
}
主要验证:


    1)交易大小必须<32KB
    2)交易金额>=0
    3)交易的gas limit<block的gas limit
    4)签名有效
    5)交易的gas price>最低gas price(非本地交易)
    6)交易的nonce值必须大于当前nonce值
    7)当前账户余额判断必须满足交易
8)交易的gas limit判断


前面提到过预执行函数promoteExecutables,它的目的是”去芜存菁“,说白了就是嫌贫爱富。
func (pool *TxPool) promoteExecutables(accounts []common.Address) {
// Gather all the accounts potentially needing updates
if accounts == nil {
accounts = make([]common.Address, 0, len(pool.queue))
for addr := range pool.queue {
accounts = append(accounts, addr)
}
}
// Iterate over all accounts and promote any executable transactions
for _, addr := range accounts {
list := pool.queue[addr]
if list == nil {
continue // Just in case someone calls with a non existing account
}
// Drop all transactions that are deemed too old (low nonce)
for _, tx := range list.Forward(pool.currentState.GetNonce(addr)) {
hash := tx.Hash()
log.Trace("Removed old queued transaction", "hash", hash)
delete(pool.all, hash)
pool.priced.Removed()
}
// Drop all transactions that are too costly (low balance or out of gas)
drops, _ := list.Filter(pool.currentState.GetBalance(addr), pool.currentMaxGas)
for _, tx := range drops {
hash := tx.Hash()
log.Trace("Removed unpayable queued transaction", "hash", hash)
delete(pool.all, hash)
pool.priced.Removed()
queuedNofundsCounter.Inc(1)
}
// Gather all executable transactions and promote them
for _, tx := range list.Ready(pool.pendingState.GetNonce(addr)) {
hash := tx.Hash()
log.Trace("Promoting queued transaction", "hash", hash)
            //处理并传播交易,见下面的广播交易
pool.promoteTx(addr, hash, tx)
}
// Drop all transactions over the allowed limit
if !pool.locals.contains(addr) {
for _, tx := range list.Cap(int(pool.config.AccountQueue)) {
hash := tx.Hash()
delete(pool.all, hash)
pool.priced.Removed()
queuedRateLimitCounter.Inc(1)
log.Trace("Removed cap-exceeding queued transaction", "hash", hash)
}
}
// Delete the entire queue entry if it became empty.
if list.Empty() {
delete(pool.queue, addr)
}
}
// If the pending limit is overflown, start equalizing allowances
pending := uint64(0)
for _, list := range pool.pending {
pending += uint64(list.Len())
}
if pending > pool.config.GlobalSlots {
pendingBeforeCap := pending
// Assemble a spam order to penalize large transactors first
spammers := prque.New()
for addr, list := range pool.pending {
// Only evict transactions from high rollers
if !pool.locals.contains(addr) && uint64(list.Len()) > pool.config.AccountSlots {
spammers.Push(addr, float32(list.Len()))
}
}
// Gradually drop transactions from offenders
offenders := []common.Address{}
for pending > pool.config.GlobalSlots && !spammers.Empty() {
// Retrieve the next offender if not local address
offender, _ := spammers.Pop()
offenders = append(offenders, offender.(common.Address))


// Equalize balances until all the same or below threshold
if len(offenders) > 1 {
// Calculate the equalization threshold for all current offenders
threshold := pool.pending[offender.(common.Address)].Len()


// Iteratively reduce all offenders until below limit or threshold reached
for pending > pool.config.GlobalSlots && pool.pending[offenders[len(offenders)-2]].Len() > threshold {
for i := 0; i < len(offenders)-1; i++ {
list := pool.pending[offenders[i]]
for _, tx := range list.Cap(list.Len() - 1) {
// Drop the transaction from the global pools too
hash := tx.Hash()
delete(pool.all, hash)
pool.priced.Removed()


// Update the account nonce to the dropped transaction
if nonce := tx.Nonce(); pool.pendingState.GetNonce(offenders[i]) > nonce {
pool.pendingState.SetNonce(offenders[i], nonce)
}
log.Trace("Removed fairness-exceeding pending transaction", "hash", hash)
}
pending--
}
}
}
}
// If still above threshold, reduce to limit or min allowance
if pending > pool.config.GlobalSlots && len(offenders) > 0 {
for pending > pool.config.GlobalSlots && uint64(pool.pending[offenders[len(offenders)-1]].Len()) > pool.config.AccountSlots {
for _, addr := range offenders {
list := pool.pending[addr]
for _, tx := range list.Cap(list.Len() - 1) {
// Drop the transaction from the global pools too
hash := tx.Hash()
delete(pool.all, hash)
pool.priced.Removed()


// Update the account nonce to the dropped transaction
if nonce := tx.Nonce(); pool.pendingState.GetNonce(addr) > nonce {
pool.pendingState.SetNonce(addr, nonce)
}
log.Trace("Removed fairness-exceeding pending transaction", "hash", hash)
}
pending--
}
}
}
pendingRateLimitCounter.Inc(int64(pendingBeforeCap - pending))
}
// If we've queued more transactions than the hard limit, drop oldest ones
queued := uint64(0)
for _, list := range pool.queue {
queued += uint64(list.Len())
}
if queued > pool.config.GlobalQueue {
// Sort all accounts with queued transactions by heartbeat
addresses := make(addresssByHeartbeat, 0, len(pool.queue))
for addr := range pool.queue {
if !pool.locals.contains(addr) { // don't drop locals
addresses = append(addresses, addressByHeartbeat{addr, pool.beats[addr]})
}
}
sort.Sort(addresses)


// Drop transactions until the total is below the limit or only locals remain
for drop := queued - pool.config.GlobalQueue; drop > 0 && len(addresses) > 0; {
addr := addresses[len(addresses)-1]
list := pool.queue[addr.address]


addresses = addresses[:len(addresses)-1]


// Drop all transactions if they are less than the overflow
if size := uint64(list.Len()); size <= drop {
for _, tx := range list.Flatten() {
pool.removeTx(tx.Hash())
}
drop -= size
queuedRateLimitCounter.Inc(int64(size))
continue
}
// Otherwise drop only last few transactions
txs := list.Flatten()
for i := len(txs) - 1; i >= 0 && drop > 0; i-- {
pool.removeTx(txs[i].Hash())
drop--
queuedRateLimitCounter.Inc(1)
}
}
}
}
英文注释中也写得清楚,移除低GAS,低优先级的。别看上面代码多,其实主要分为三部分:
首先把队列中的交易进入pending状态:
    1)判断nonce ,清理重复交易
    2)判断是否余额不足即gas+tx > balance
3)判断gas limit > block gas limit,这种交易可能会导致区块生成失败
然后清理pending状态:
    1)对交易账户的交易数超过了AccountSlots进行按交易数均衡。举例来说,如果有10个账户交易数超过了AccountSlots(默认16),其中交易数最少的账户包含20笔交易,那么先把其他9个账户的交易数量削减到20。
    2)如果经过上面的步骤,pending的长度还是超过了GlobalSlots,那就严格按照AccountSlots进行均衡,也就是把上面的10个账户的交易数进一步削减到16。
4、 广播交易
广播交易在前面的预执行里调用:
func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.Transaction) {
// Try to insert the transaction into the pending queue
if pool.pending[addr] == nil {
pool.pending[addr] = newTxList(true)
}
list := pool.pending[addr]


inserted, old := list.Add(tx, pool.config.PriceBump)
if !inserted {
// An older transaction was better, discard this
delete(pool.all, hash)
pool.priced.Removed()


pendingDiscardCounter.Inc(1)  //增加计数器引用
return
}
// Otherwise discard any previous transaction and mark this
if old != nil {
delete(pool.all, old.Hash())
pool.priced.Removed()


pendingReplaceCounter.Inc(1)
}
// Failsafe to work around direct pending inserts (tests)
if pool.all[hash] == nil {
pool.all[hash] = tx
pool.priced.Put(tx)
}
// Set the potentially new pending nonce and notify any subsystems of the new tx
    //更新心跳和随机值
pool.beats[addr] = time.Now()
pool.pendingState.SetNonce(addr, tx.Nonce()+1)
    //发送
go pool.txFeed.Send(TxPreEvent{tx})

既然发送了一个订阅事件,那么就得查找一下哪里订阅了:
一个在前面提到的worker中:
func newWorker(config *params.ChainConfig, engine consensus.Engine, coinbase common.Address, eth Backend, mux *event.TypeMux) *worker {
……
// Subscribe TxPreEvent for tx pool
worker.txSub = eth.TxPool().SubscribeTxPreEvent(worker.txCh)
…….


return worker
}
处理的地点在update 函数中:
func (self *worker) update() {
defer self.txSub.Unsubscribe()
……


// Handle TxPreEvent
case ev := <-self.txCh:
// Apply transaction to the pending state if we're not mining
if atomic.LoadInt32(&self.mining) == 0 {
self.currentMu.Lock()
acc, _ := types.Sender(self.current.signer, ev.Tx)
txs := map[common.Address]types.Transactions{acc: {ev.Tx}}
txset := types.NewTransactionsByPriceAndNonce(self.current.signer, txs)


self.current.commitTransactions(self.mux, txset, self.chain, self.coinbase)
self.currentMu.Unlock()
} else {
// If we're mining, but nothing is being processed, wake on new transactions
if self.config.Clique != nil && self.config.Clique.Period == 0 {
self.commitNewWork()
}
}


……

}
没啥,交易提交给EVM执行,前面提到过。分两种情况,挖矿和不挖矿。说明写得很清楚。
另一个在 ProtocolManager中:
func (pm *ProtocolManager) Start(maxPeers int) {
pm.maxPeers = maxPeers


// broadcast transactions
pm.txCh = make(chan core.TxPreEvent, txChanSize)
pm.txSub = pm.txpool.SubscribeTxPreEvent(pm.txCh)
go pm.txBroadcastLoop()


// broadcast mined blocks
pm.minedBlockSub = pm.eventMux.Subscribe(core.NewMinedBlockEvent{})
go pm.minedBroadcastLoop()


……
}
func (self *ProtocolManager) txBroadcastLoop() {
for {
select {
case event := <-self.txCh:
self.BroadcastTx(event.Tx.Hash(), event.Tx)


// Err() channel will be closed when unsubscribing.
case <-self.txSub.Err():
return
}
}
}
继续查看广播调用:
// BroadcastTx will propagate a transaction to all peers which are not known to
// already have the given transaction.
func (pm *ProtocolManager) BroadcastTx(hash common.Hash, tx *types.Transaction) {
// Broadcast transaction to a batch of peers not knowing about it
peers := pm.peers.PeersWithoutTx(hash)
//FIXME include this again: peers = peers[:int(math.Sqrt(float64(len(peers))))]
for _, peer := range peers {
peer.SendTransactions(types.Transactions{tx})
}
log.Trace("Broadcast transaction", "hash", hash, "recipients", len(peers))
}
最后到P2P的Send中:
func Send(w MsgWriter, msgcode uint64, data interface{}) error {
size, r, err := rlp.EncodeToReader(data)
if err != nil {
return err
}
return w.WriteMsg(Msg{Code: msgcode, Size: uint32(size), Payload: r})
}
很明显广播到了相关的节点中去了。


三、 交易打包
1、 打包
在前边的Workers中详细分析过,commitTransactions最终会发把交易打包到块上。
2、 广播
前面同样提到过NewMinedBlockEvent事件
四、 交易的数据结构
1、 交易相关
// SendTxArgs represents the arguments to sumbit a new transaction into the transaction //pool.创建一个新交易
type SendTxArgs struct {
From     common.Address  `json:"from"`
To       *common.Address `json:"to"`
Gas      *hexutil.Uint64 `json:"gas"`
GasPrice *hexutil.Big    `json:"gasPrice"`
Value    *hexutil.Big    `json:"value"`
Nonce    *hexutil.Uint64 `json:"nonce"`
// We accept "data" and "input" for backwards-compatibility reasons. "input" is the
// newer name and should be preferred by clients.
Data  *hexutil.Bytes `json:"data"`
Input *hexutil.Bytes `json:"input"`
}
交易:
type Transaction struct {
data txdata
// caches
hash atomic.Value
size atomic.Value
from atomic.Value
}


type txdata struct {
AccountNonce uint64          `json:"nonce"    gencodec:"required"`
Price        *big.Int        `json:"gasPrice" gencodec:"required"`
GasLimit     uint64          `json:"gas"      gencodec:"required"`
Recipient    *common.Address `json:"to"       rlp:"nil"` // nil means contract creation
Amount       *big.Int        `json:"value"    gencodec:"required"`
Payload      []byte          `json:"input"    gencodec:"required"`


// Signature values
V *big.Int `json:"v" gencodec:"required"`
R *big.Int `json:"r" gencodec:"required"`
S *big.Int `json:"s" gencodec:"required"`


// This is only used when marshaling to JSON.
Hash *common.Hash `json:"hash" rlp:"-"`
}
2、 交易池
    type TxPool struct {
config       *params.ChainConfig
signer       types.Signer
quit         chan bool
txFeed       event.Feed
scope        event.SubscriptionScope
chainHeadCh  chan core.ChainHeadEvent  区块头事件
chainHeadSub event.Subscription
mu           sync.RWMutex
chain        *LightChain
odr          OdrBackend
chainDb      ethdb.Database
relay        TxRelayBackend
head         common.Hash
nonce        map[common.Address]uint64            // "pending" nonce
pending      map[common.Hash]*types.Transaction   // pending transactions by tx hash
mined        map[common.Hash][]*types.Transaction // mined transactions by block hash
clearIdx     uint64                               // earliest block nr that can contain mined tx info


homestead bool
}
// txList is a "list" of transactions belonging to an account, sorted by account
// nonce. The same type can be used both for storing contiguous transactions for
// the executable/pending queue; and for storing gapped transactions for the non-
// executable/future queue, with minor behavioral changes.
type txList struct {
strict bool         // Whether nonces are strictly continuous or not
txs    *txSortedMap // Heap indexed sorted hash map of the transactions


costcap *big.Int // Price of the highest costing transaction (reset only if exceeds balance)
gascap  uint64   // Gas limit of the highest spending transaction (reset only if exceeds block limit)
}
五、 总结
通过上面的分析,基本一个普通交易(非智能合约的交易)就完成了,还是比较复杂的,慢慢分析吧。

猜你喜欢

转载自blog.csdn.net/fpcc/article/details/81050976