目录
一 . 发起交易
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的方法发送交易,在该方法内部根据发送的地址获取当前用户的钱包信息,然后对生成交易对象,利用私钥对交易签名,最后提交交易。下面是对该方法的具体介绍
参数
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.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)
}
交易的结构体
/**
以太坊交易的结构体
*/
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)
}
对交易签名分为利用硬件钱包签名和keystore签名,在本地我们讲的是利用keystore进行签名。代码路径:go-ethereum\accounts\keystore\keystore.go
主要步骤有:
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)
}
主要操作:
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)
}
主要步骤:
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
}
主要操作:
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
}
主要步骤:
/**
提交当前的交易
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
}
以太坊的将新的交易添加到交易池时分两种情况:
主要步骤:
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
}
主要步骤如下:
// 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
}
主要操作:
// 将一个新的交易插入到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
}
如果新的交易比老的交易的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
}
调用promoteExecutables把已经变得可以执行交易的从queue 列表插入到pending 列表流程如下:
(1) 粉色部分主要是为了把queue中的交易“提”到pending中。在此过程中要做如下检查
(2) 紫色部分主要是为了清理pending列表,使其满足GlobalSlots和AccountSlots的限制条件,需要从以下几个角度进行处理:
(3) 绿色部分主要是为了清理queue列表,使其满足GlobalQueue和AccountQueue的限制条件,需要从以下几个角度进行处理:
// 通过这个处理过程,所有的无效的交易(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)
}
}
}
}
主要步骤如下:
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})
}