geth源码阅读——交易流程

一、流程简介

  • 发起交易:指定目标地址和交易金额,以及需要的gas/gaslimit
  • 交易签名:使用账户私钥对交易进行签名
  • 提交交易:把交易加入到交易缓冲池txpool中(会先对交易签名进行验证)
  • 广播交易:通知EVM执行,同时把交易信息广播给其他结点

geth源码阅读——交易流程_第1张图片

二、源码分析

以太坊系列(ETH&ETC)在发送交易有三个对应的RPC接口,分别是eth_sendTransaction、eth_sendRawTransaction和personal_sendTransaction。personal_sendTransaction很少听说,暂时不管。eth_sendRawTransaction接口必须附上已经计算好的签名,eth_sendTransaction不需要提前计算好签名,用法:

eth.sendTransaction({from:acc0,to:acc1,value: web3.toWei(1)})          //用于本地的两个账户

eth.sendTransaction({from:"0x51c641e9b2e9693d0f3c94e4cd804ae4eb9c8900",to: "0x573dc3f3bdd9b9b579ee507412483cf43d9e7b08", value: amount})

2.1 发起交易

//internal/ethapi/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,封装一个transaction对象
   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)             //提交Transaction对象到txpool
}
 
  
// SendRawTransaction will add the signed transaction to the transaction pool.
// The sender is responsible for signing the transaction and using the correct nonce.
func (s *PublicTransactionPoolAPI) SendRawTransaction(ctx context.Context, encodedTx hexutil.Bytes) (common.Hash, error) {
   tx := new(types.Transaction)     //封装一个Transaction对象
   if err := rlp.DecodeBytes(encodedTx, tx); err != nil {       //对tx做rlpI编码
      return common.Hash{}, err
   }
   return submitTransaction(ctx, s.b, tx)              //提交交易到txpool
}
-------------------------------------------------------------------
 
  
// 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) {
   
//发送交易,注意:Backend使用了多态,sendTx是txPool的接口,txPool根据light和full两种启动模式有两种类型,本文研究full模式的交易
if err := b. SendTx(ctx, tx); err != nil { return common.Hash{}, err } if tx.To() == nil { //to是空的,说明是部署合约 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)
}
-------------------------------------------------------------------------
 
  
// AddLocal enqueues a single transaction into the pool if it is valid, marking the sender as a local one in the mean time,
// ensuring it goes around the local pricing constraint
// 开始入池
func (pool *TxPool) AddLocal(tx *types.Transaction) error {
   return pool.addTx(tx, !pool.config.NoLocals)
}
---------------------------------------------------------------------------
 
  
// 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,如果是新交易,直接提交到可执行状态
   if !replace {
      from, _ := types.Sender(pool.signer, tx) // already validated
      pool.promoteExecutables([]common.Address{from})              //提交到可执行队列
   }
   return nil
}
-----------------------------------------------------------------------------
 
  
// add validates a transaction and inserts it into the non-executable queue for  add验证交易合法性并插入到非稳定队列
// later pending promotion and execution. If the transaction is a replacement for 如果是一个覆盖交易,
// an already pending or queued one, it overwrites the previous and returns this  那么覆盖之前的交易
// so outer code doesn't uselessly call promote.
//
// If a newly added transaction is marked as local, its sending account will be  如果是新交易则标记为local
// whitelisted, preventing any associated transaction from being dropped out of  则将发送账户列入白名单,
// the pool due to pricing constraints.                                          防止任何关联的交易由于价格约束从池中退出
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.Get(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(pool.all.Count()) >= pool.config.GlobalSlots+pool.config.GlobalQueue {
      // If the new transaction is underpriced, don't accept it
      if !local && 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(pool.all.Count()-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(), false)
      }
   }
   // If the transaction is replacing an already pending one, do directly, 交易是用来替换旧交易的,覆盖它
   from, _ := types.Sender(pool.signer, tx) // already validated ,根据签名获取from地址
   //pendding队列不为空,并且当前交易已经存在(需要覆盖)
   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)              //添加到pengding队列
      if !inserted {
         pendingDiscardCounter.Inc(1)
         return false, ErrReplaceUnderpriced
      }
      // New transaction is better, replace old one
      if old != nil {
         pool.all.Remove(old.Hash())     //从all队列移除旧的
         pool.priced.Removed()
         pendingReplaceCounter.Inc(1)
      }
      pool.all.Add(tx)                   //添加到all队列
      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,由于注入了一个replacement交易,通知所有子系统
      go pool.txFeed.Send(NewTxsEvent{types.Transactions{tx}})

      return old != nil, nil
   }
   // New transaction isn't replacing a pending one, push into queue,插入新交易到pool
   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)       //插入到local队列
   }
   pool.journalTx(from, tx)       //将交易记录到pool的Jorunal中,Jorunal用来把本地交易写到磁盘来备份

   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() {   //交易的最大汽油限制(GasLimit)不能超过池中汽油的最大值
      return ErrGasLimit
   }
   // Make sure the transaction is signed properly
   from, err := types.Sender(pool.signer, tx)    //根据签名获取from的地址
   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 {    //交易的汽油价格不能小于pool指定的汽油价格
      return ErrUnderpriced
   }
   // Ensure the transaction adheres to nonce ordering
   if pool.currentState.GetNonce(from) > tx.Nonce() {      //nonce不能小于当前的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 {   //汽油需要的量(amount+gasPrice*gasLimit)不能大于当前的余额
      return ErrInsufficientFunds
   }
   //gasLimit一定要大于intrinsic gas, intrinsic gas 包括交易预定义费用 + 发送数据费用 + 创建合约费用
   intrGas, err := IntrinsicGas(tx.Data(), tx.To() == nil, pool.homestead)
   if err != nil {
      return err
   }
   if tx.Gas() < intrGas {
      return ErrIntrinsicGas
   }
   return nil
}
-----------------------------------------------------------------------
promoteExecutables方法把 已经变得可以执行的交易从future queue 插入到pending queue。通过这个处理过程,所有的无效的交易(nonce太低,余额不足)会被删除。
    
    // promoteExecutables moves transactions that have become processable from the
    // future queue to the set of pending transactions. During this process, all
    // invalidated transactions (low nonce, low balance) are deleted.
    func (pool *TxPool) promoteExecutables(accounts []common.Address) {
        // Gather all the accounts potentially needing updates
        // accounts存储了所有潜在需要更新的账户。 如果账户传入为nil,代表所有已知的账户。
        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)
            // 删除所有的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
            // 得到所有的可以执行的交易,并promoteTx加入pending
            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())
        }
        // 如果pending的总数超过系统的配置。 
        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
                // 首先把所有大于AccountSlots最小值的账户记录下来, 会从这些账户里面剔除一些交易。
                // 注意spammers是一个优先级队列,也就是说是按照交易的多少从大到小排序的。
                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 { // 第一次进入这个循环的时候, offenders队列里面有交易数量最大的两个账户
                    // 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
                    // 遍历直到pending有效,或者是倒数第二个的交易数量等于最后一个的交易数量
                    for pending > pool.config.GlobalSlots && pool.pending[offenders[len(offenders)-2]].Len() > threshold {
                        // 遍历除了最后一个账户以外的所有账户, 把他们的交易数量减去1.
                        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
            // 经过上面的循环,所有的超过AccountSlots的账户的交易数量都变成了之前的最小值。
            // 如果还是超过阈值,那么在继续从offenders里面每次删除一个。
            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))
        }  //end if pending > pool.config.GlobalSlots {
        // If we've queued more transactions than the hard limit, drop oldest ones
        // 我们处理了pending的限制, 下面需要处理future queue的限制了。
        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)
                }
            }
        }
    }
Nonce使用规则

为了防止交易重播,ETH(ETC)节点要求每笔交易必须有一个nonce数值。
每一个账户从同一个节点发起交易时,这个nonce值从0开始计数,发送一笔nonce对应加1。
当前面的nonce处理完成之后才会处理后面的nonce。

注意这里的前提条件是相同的地址在相同的节点发送交易。

1.当nonce太小(小于之前已经有交易使用的nonce值),交易会被直接拒绝。
2.当nonce太大,交易会一直处于队列之中,这也就是导致我们上面描述的问题的原因;
3.当发送一个比较大的nonce值,然后补齐开始nonce到那个值之间的nonce,那么交易依旧可以被执行。
4.当交易处于queue中时停止geth客户端,那么交易queue中的交易会被清除掉。
txPool 在创建的时候,开启了一个阻塞式线程,用于响应外部区块链事件,各种报告,或者交易回收
// loop is the transaction pool's main event loop, waiting for and reacting to
// outside blockchain events as well as for various reporting and transaction
// eviction events.
func (pool *TxPool) loop() {
   defer pool.wg.Done()

   // Start the stats reporting and transaction eviction tickers
   var prevPending, prevQueued, prevStales int

   report := time.NewTicker(statsReportInterval)
   defer report.Stop()

   evict := time.NewTicker(evictionInterval)
   defer evict.Stop()

   journal := time.NewTicker(pool.config.Rejournal)
   defer journal.Stop()

   // Track the previous head headers for transaction reorgs
   head := pool.chain.CurrentBlock()

   // Keep waiting for and reacting to the various events
   for {
      select {
      // Handle ChainHeadEvent 区块链 head 事件 handle
      case ev := <-pool.chainHeadCh:
         if ev.Block != nil {
            pool.mu.Lock()
            if pool.chainconfig.IsHomestead(ev.Block.Number()) {
               pool.homestead = true
            }
            pool.reset(head.Header(), ev.Block.Header())
            head = ev.Block

            pool.mu.Unlock()
         }
      // Be unsubscribed due to system stopped,系统停止导致的注销
      case <-pool.chainHeadSub.Err():
         return

      // Handle stats reporting ticks,统计报告时钟的handle
      case <-report.C:
         pool.mu.RLock()
         pending, queued := pool.stats()
         stales := pool.priced.stales
         pool.mu.RUnlock()

         if pending != prevPending || queued != prevQueued || stales != prevStales {
            log.Debug("Transaction pool status report", "executable", pending, "queued", queued, "stales", stales)
            prevPending, prevQueued, prevStales = pending, queued, stales
         }

      // Handle inactive account transaction eviction,不活跃的账户的交易回收handle
      case <-evict.C:
         pool.mu.Lock()
         for addr := range pool.queue {
            // Skip local transactions from the eviction mechanism
            if pool.locals.contains(addr) {
               continue
            }
            // Any non-locals old enough should be removed
            if time.Since(pool.beats[addr]) > pool.config.Lifetime {
               for _, tx := range pool.queue[addr].Flatten() {
                  pool.removeTx(tx.Hash(), true)
               }
            }
         }
         pool.mu.Unlock()

      // Handle local transaction journal rotation,本地交易journal轮询handle
      case <-journal.C:
         if pool.journal != nil {
            pool.mu.Lock()
            if err := pool.journal.rotate(pool.local()); err != nil {
               log.Warn("Failed to rotate local tx journal", "err", err)
            }
            pool.mu.Unlock()
         }
      }
   }
}

 
 

你可能感兴趣的:(geth源码阅读——交易流程)