以太坊源码分析-交易源码分析(一)

目录

一 . 发起交易

1. 发送交易时参数的机构体对象

2. SendTransaction()方法介绍

2.1. toTransaction()方法

2.2. 调用wallet.SignTx(account, tx, chainID)对当前交易进行签名。

2.2.1 调用types.SignTx方法进行签名

2.2.2 调用WithSignature为交易设置签名

2.2.3 调用SignatureValues方法获取r,s,v字段

2.3 调用submitTransaction提交交易

二. 节点接收交易

1. 执行add方法将交易添加到交易池中

2. 调用validateTx验证交易

3. 调用pool.enqueueTx(hash, tx),将交易添加到待执行的交易队列中

3.1 调用queue的add方法替换交易

4. 调用promoteExecutables方法将交易插入到pending执行队列中

4.1 调用promoteTx将交易广播到其他节点


以太坊的交易的生命周期主要包括以下几部分:

1. 用户发送交易

2. 节点接收交易

3. 执行交易

4. 打包区块

5. 矿工节点挖矿

6. 区块广播,交易保存到链上

在当前博文里面主要包含,用户如何发起交易、以及交易的签名、交易发送到交易池中、交易池广播交易等

一 . 发起交易

       用户通过grpc接口调用go-ethereum\internal\ethapi 里面 PublicTransactionPoolAPI结构体里面的SendTransaction的方法发送交易,在该方法内部根据发送的地址获取当前用户的钱包信息,然后对生成交易对象,利用私钥对交易签名,最后提交交易。下面是对该方法的具体介绍

1. 发送交易时参数的机构体对象

 参数
           ctx:上下文信息
          args:发送交易时的参数包含值(from,To,Gas,Gasprice,Value,Nonce,Data,Input)

//发送交易信息的结构体
type SendTxArgs struct {
	From     common.Address  `json:"from"`     //发送人
	To       *common.Address `json:"to"`       //接收地址
	Gas      *hexutil.Uint64 `json:"gas"`      //gas
	GasPrice *hexutil.Big    `json:"gasPrice"` //gasprice
	Value    *hexutil.Big    `json:"value"`    //交易的值
	Nonce    *hexutil.Uint64 `json:"nonce"`    //交易的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"`
}

2. SendTransaction()方法介绍

执行步骤
          2.1 根据from参数获取账号
          2.2 调用args.toTranscation方法生产交易tx对象
          2.3 调用wallet.SignTx方法对当前交易进行签名
          2.4 调用submitTransaction(ctx,s.b,signed)函数提交当前的交易

func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args SendTxArgs) (common.Hash, error) {
	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)
	}

	if err := args.setDefaults(ctx, s.b); err != nil {
		return common.Hash{}, err
	}
	//将当前的交易信息生成交易信息
	tx := args.toTransaction()
	var chainID *big.Int //获取当前区块链的ID
	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)
}

2.1. toTransaction()方法

交易的结构体

/**
以太坊交易的结构体
*/
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"` //发送者愿意支付执行交易所需的每个gas的Wei数量
	GasLimit     uint64          `json:"gas"      gencodec:"required"` //发送者愿意为执行交易支付gas数量的最大值。这个数量被设置之后在任何计算完成之前就会被提前扣掉
	Recipient    *common.Address `json:"to"       rlp:"nil"`           // nil means contract creation 所使用的接收人的地址,如果为空的话,这说明是使用合约地址
	Amount       *big.Int        `json:"value"    gencodec:"required"` // 从发送者转移到接收者的Wei数量。在合约创建交易中,value作为新建合约账户的开始余额
	Payload      []byte          `json:"input"    gencodec:"required"` //在调用合约函数时input中保存的是合约函数的名称及参数,在部署合约时input中保持的是合约的abi,data及合约构造函数的参数

	// 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:"-"` //当前交易的hash
}

根据输入的参数生成Transaction对象,根据输入参数里面的to字段判端,如果to字段为空的话,则为合约交易,否则为正常交易

/**
利用发送的信息生成新的交易
*/
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)
}

2.2. 调用wallet.SignTx(account, tx, chainID)对当前交易进行签名。

     对交易签名分为利用硬件钱包签名和keystore签名,在本地我们讲的是利用keystore进行签名。代码路径:go-ethereum\accounts\keystore\keystore.go

主要步骤有:

  • 根据钱包地址,查看当前钱包是否已解锁
  • 如果已解锁的话,调用types.SignTx对交易进行签名
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)
}

2.2.1 调用types.SignTx方法进行签名

主要操作:

  •      将当前交易进行hash
  •      利用私钥对交易的hash值进行签名
  •      调用tx.WithSignature(s, sig)方法,将交易的签名分别写到交易结构体里面 v,r,s字段
func SignTx(tx *Transaction, s Signer, prv *ecdsa.PrivateKey) (*Transaction, error) {
	h := s.Hash(tx)
	sig, err := crypto.Sign(h[:], prv)
	if err != nil {
		return nil, err
	}
	return tx.WithSignature(s, sig)
}

2.2.2 调用WithSignature为交易设置签名

主要步骤:

  • 调用签名笔的SignatureValues对象分别获取r,s,v字段
  • 将r,s,v字段设置到transation中,并返回交易
func (tx *Transaction) WithSignature(signer Signer, sig []byte) (*Transaction, error) {
	r, s, v, err := signer.SignatureValues(tx, sig)
	if err != nil {
		return nil, err
	}
	cpy := &Transaction{data: tx.data}
	cpy.data.R, cpy.data.S, cpy.data.V = r, s, v
	return cpy, nil
}

2.2.3 调用SignatureValues方法获取r,s,v字段

主要操作:

  •  获取交易签名的前32位赋值给r
  • 获取交易签名的32-64位赋值给s
  • 获取交易签名的最后一位赋值为v
  • 返回r,s,v
func (fs FrontierSigner) SignatureValues(tx *Transaction, sig []byte) (r, s, v *big.Int, err error) {
	if len(sig) != 65 {
		panic(fmt.Sprintf("wrong size for signature: got %d, want 65", len(sig)))
	}
	r = new(big.Int).SetBytes(sig[:32])
	s = new(big.Int).SetBytes(sig[32:64])
	v = new(big.Int).SetBytes([]byte{sig[64] + 27})
	return r, s, v, nil
}

2.3 调用submitTransaction提交交易

主要步骤:

  • 调用grpc SendTx发送交易到节点
  • 如果交易的to字段为空的话,根据发送者的地址和交易的nonce值,生成合约地址
  • 返回交易的hash值
/**
提交当前的交易
parama:执行交易的上下文,交易的接口,交易
return 交易的hash
*/
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
	}
	if tx.To() == nil { //如果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
}

二. 节点接收交易

以太坊的将新的交易添加到交易池时分两种情况:

  • 本地交易:如果是本地交易的话,当添加到交易池之后,交易的发送地址存会被放在locals列表里面,这个账户的关联的交易将不会因为价格的限制或者其他的一些限制被删除
  • 远程交易:远程发送来的交易为普通交易,交易可能会因为价格或者gas的限制等原因被从交易池里面的剔除

1. 执行add方法将交易添加到交易池中

主要步骤:

  • 检查当前交易是否在交易池里面已存在
  • 调用validateTx方法验证当前交易,如果验证没有通过的话直接丢弃
  • 查看当前交易池是否已满(5120),如果已经满了的话在交易池里面删除价格比较低的交易,如果当前交易的价格比较低的话直接拒绝接收
  • 查看当前交易是否已经在正在处理的交易列表里面,如果在看能否替换旧的交易,并将新的交易添加到交易池中,同时将当前交易广播给其他节点
  • 如果当前交易不能替换正在处理的交易列表(peeding),则将交易放在待处理的交易列表中(queue)
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 !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(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(), false)
		}
	}
	// 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
		// 如果交易对应的Nonce已经在pending队列了,那么看是否能够替换.
		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
	// 新交易不能替换pending里面的任意一个交易,那么把他push到futuren 队列里面.
	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)
	}
	// 如果是本地的交易,会被记录进入journalTx
	pool.journalTx(from, tx) //存到硬盘

	log.Trace("Pooled new future transaction", "hash", hash, "from", from, "to", tx.To())
	return replace, nil
}

2. 调用validateTx验证交易

主要步骤如下:

  • 检查交易的大小
  • 检查交易签名
  • 验证当前交易的gasprice是否小于交易池设置的gasPrice(本地交易不做此验证)
  • 验证交易的Nonce
  • 验证用户当前的余额是否足够
// validateTx 使用一致性规则来检查一个交易是否有效,并采用本地节点的一些启发式的限制.
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
	// 如果不是本地的交易,并且GasPrice低于我们的设置,那么也不会接收.
	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
	// 确保交易遵守了Nonce的顺序,账户的nonce
	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
	}
	// 如果交易是一个合约创建或者调用. 那么看看是否有足够的 初始Gas.
	if tx.Gas() < intrGas {
		return ErrIntrinsicGas
	}
	return nil
}

3. 调用pool.enqueueTx(hash, tx),将交易添加到待执行的交易队列中

主要操作:

  • 验证交易签名
  • 调用queue的add方法替换该账户中比较老的未执行的交易
  • 将当前交易添加到所有交易池中
// 将一个新的交易插入到future queue
func (pool *TxPool) enqueueTx(hash common.Hash, tx *types.Transaction) (bool, error) {
	// Try to insert the transaction into the future queue
	from, _ := types.Sender(pool.signer, tx) // already validated 验证签名
	if pool.queue[from] == nil {
		pool.queue[from] = newTxList(false) //如果pool.queue不存在当前地址
	}
	inserted, old := pool.queue[from].Add(tx, pool.config.PriceBump)
	if !inserted {
		// An older transaction was better, discard this
		queuedDiscardCounter.Inc(1)
		return false, ErrReplaceUnderpriced
	}
	// Discard any previous transaction and mark this
	if old != nil {
		delete(pool.all, old.Hash())
		pool.priced.Removed()
		queuedReplaceCounter.Inc(1)
	}
	if pool.all[hash] == nil {
		pool.all[hash] = tx
		pool.priced.Put(tx)
	}
	return old != nil, nil
}

3.1 调用queue的add方法替换交易

如果新的交易比老的交易的GasPrice值要高出一定的比值priceBump,那么会替换老的交易。
 Add 尝试插入一个新的交易,返回交易是否被接收,如果被接收,那么任意之前的交易会被替换。
如果新的交易被接收,那么总的cost和gas限制会被更新。
func (l *txList) Add(tx *types.Transaction, priceBump uint64) (bool, *types.Transaction) {
	// If there's an older better transaction, abort
	// 如果存在老的交易。 而且新的交易的价格比老的高出一定的数量。那么替换。
	old := l.txs.Get(tx.Nonce())
	if old != nil {
		threshold := new(big.Int).Div(new(big.Int).Mul(old.GasPrice(), big.NewInt(100+int64(priceBump))), big.NewInt(100))
		// Have to ensure that the new gas price is higher than the old gas
		// price as well as checking the percentage threshold to ensure that
		// this is accurate for low (Wei-level) gas price replacements
		if old.GasPrice().Cmp(tx.GasPrice()) >= 0 || threshold.Cmp(tx.GasPrice()) > 0 {
			return false, nil
		}
	}
	// Otherwise overwrite the old transaction with the current one
	l.txs.Put(tx)
	if cost := tx.Cost(); l.costcap.Cmp(cost) < 0 {
		l.costcap = cost
	}
	if gas := tx.Gas(); l.gascap < gas {
		l.gascap = gas
	}
	return true, old
}

4. 调用promoteExecutables方法将交易插入到pending执行队列中

调用promoteExecutables把已经变得可以执行交易的从queue 列表插入到pending 列表流程如下:

以太坊源码分析-交易源码分析(一)_第1张图片


(1)   粉色部分主要是为了把queue中的交易“提”到pending中。在此过程中要做如下检查

  •     丢弃nonce < 账户当前nonce的交易,也就是已经被打包过的交易
  •     丢弃转账金额 + gas消耗 > 账户余额的交易,也就是会out-of-gas的交易
  •     丢弃gas limit > block gas limit的交易,这部分交易可能会导致区块生成失败
  •     得到所有的可以执行的交易,并promoteTx加入pending并且广播交易

(2)  紫色部分主要是为了清理pending列表,使其满足GlobalSlots和AccountSlots的限制条件,需要从以下几个角度进行处理:

  •     如果有些账户的交易数超过了AccountSlots,则先按交易数最少的账户进行均衡。举例来说,如果有10个账户交易数超过了AccountSlots(默认16),其中交易数最少的账户包含20笔交易,那么先把其他9个账户的交易数量削减到20。
  •    如果经过上面的步骤,pending的长度还是超过了GlobalSlots,那就严格按照AccountSlots进行均衡,也就是把上面的10个账户的交易数进一步削减到16

(3)   绿色部分主要是为了清理queue列表,使其满足GlobalQueue和AccountQueue的限制条件,需要从以下几个角度进行处理:

  •     如果每个账户的交易数超过了AccountQueue,丢弃多余交易
  •     如果queue的长度超过了GlobalQueue,则把账户按最后一次心跳时间排序,然后依次去除账户中的交易,直到满足限制条件位置
// 通过这个处理过程,所有的无效的交易(nonce太低,余额不足)会被删除。
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
				// 把最后加入的账户的交易数量当成本次的阈值t
				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
		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(), true)
				}
				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(), true)
				drop--
				queuedRateLimitCounter.Inc(1)
			}
		}
	}
}

4.1 调用promoteTx将交易广播到其他节点

主要步骤如下:

  • 获取正在执行队列中该地址的所有交易
  • 用当前交易替换已存在的比较老的交易
  • 如果交易池中不存在当前交易,将该交易添加到all队列中
  • 设置更新交易的nonce
  • 最后调用grpc接口,将交易广播到其他节点
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})
}

 

你可能感兴趣的:(以太坊)