一、流程简介
二、源码分析
以太坊系列(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() } } } }