5.5 以太坊原始碼詳解5
交易步驟
發起交易:制定目標地址和交易金額,以及gas和gaslimit
交易簽名:使用賬戶的私鑰對交易進行簽名
提交交易:把交易新增到交易緩衝池中(會先對簽名進行驗證)
廣播交易:通知EVM執行,同時把交易廣播到其他節點
具體分析
1、發起交易
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) // 提交交易(到記憶體池中) }
2、建立交易
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) }
3、傳送的交易結構
type SendTxArgs struct { Fromcommon.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"` }
4、新建交易的內部實現
func newTransaction(nonce uint64, to *common.Address, amount *big.Int, gasLimit uint64, gasPrice *big.Int, data []byte) *Transaction { if len(data) > 0 { data = common.CopyBytes(data) } // 交易結構 d := txdata{ AccountNonce: nonce, Recipient:to, Payload:data, Amount:new(big.Int), GasLimit:gasLimit, Price:new(big.Int), V:new(big.Int), R:new(big.Int), S:new(big.Int), } if amount != nil { d.Amount.Set(amount) } if gasPrice != nil { d.Price.Set(gasPrice) } return &Transaction{data: d} }
5、交易簽名
func (s *PublicTransactionPoolAPI) SignTransaction(ctx context.Context, args SendTxArgs) (*SignTransactionResult, error) { if args.Gas == nil { return nil, fmt.Errorf("gas not specified") } if args.GasPrice == nil { return nil, fmt.Errorf("gasPrice not specified") } if args.Nonce == nil { return nil, fmt.Errorf("nonce not specified") } if err := args.setDefaults(ctx, s.b); err != nil { return nil, err } tx, err := s.sign(args.From, args.toTransaction()) if err != nil { return nil, err } data, err := rlp.EncodeToBytes(tx) if err != nil { return nil, err } return &SignTransactionResult{data, tx}, nil } // sign is a helper function that signs a transaction with the private key of the given address. func (s *PublicTransactionPoolAPI) sign(addr common.Address, tx *types.Transaction) (*types.Transaction, error) { // Look up the wallet containing the requested signer account := accounts.Account{Address: addr} wallet, err := s.b.AccountManager().Find(account) if err != nil { return nil, err } // Request the wallet to sign the transaction var chainID *big.Int if config := s.b.ChainConfig(); config.IsEIP155(s.b.CurrentBlock().Number()) { chainID = config.ChainID } return wallet.SignTx(account, tx, chainID) }
6、最終完成交易簽名
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) }
7、具體步驟
生成雜湊
根據雜湊值和私鑰生成簽名
把簽名資料填充到交易例項中
8、提交交易到記憶體池中
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 { 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 //返回交易的雜湊 }
9、交易快取池的結構體
type TxPool struct { configTxPoolConfig chainconfig*params.ChainConfig chainblockChain gasPrice*big.Int txFeedevent.Feed scopeevent.SubscriptionScope chainHeadChchan ChainHeadEvent chainHeadSub event.Subscription signertypes.Signer musync.RWMutex currentState*state.StateDB// Current state in the blockchain head pendingState*state.ManagedState // Pending state tracking virtual nonces currentMaxGas uint64// Current gas limit for transaction caps locals*accountSet // Set of local transaction to exempt from eviction rules journal *txJournal// Journal of local transaction to back up to disk pending map[common.Address]*txList// All currently processable transactions 當前等待處理的交易 queuemap[common.Address]*txList// Queued but non-processable transactions beatsmap[common.Address]time.Time // Last heartbeat from each known account all*txLookup// All transactions to allow lookups 所有的交易列表 priced*txPricedList// All transactions sorted by price // 通過gas價格進行排序的交易列表,如果交易的gas price一樣則按照nonce值從小到大排列 wg sync.WaitGroup // for shutdown sync homestead bool }
10、預設的交易池配置
var DefaultTxPoolConfig = TxPoolConfig{ Journal:"transactions.rlp", Rejournal: time.Hour, PriceLimit: 1, PriceBump:10, AccountSlots: 16, pending中每個賬戶儲存的交易閾值 GlobalSlots:4096, // 列表的最大長度 AccountQueue: 64, // queue中 每個賬戶允許儲存的最大交易樹 GlobalQueue:1024, Lifetime: 3 * time.Hour, }
11、提交交易的內部呼叫
func (b *EthAPIBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error { return b.eth.txPool.AddLocal(signedTx) }
12、新增到交易池中
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}) // 選區一部分交易、放到pending中去 } return nil }
13、add 內部實現
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 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 { pool.all.Remove(old.Hash()) pool.priced.Removed() pendingReplaceCounter.Inc(1) } pool.all.Add(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(NewTxsEvent{types.Transactions{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 }
14、交易有效性檢查
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 }
15、具體步驟:
1:資料量必須小於32KB
2:交易金額必須非負
3:交易gas limit必須低於block 的gas limit
4:簽名必須有效,能夠解析出傳送者的地址
5:交易的gas price 必須高於最低的gas price
6:賬戶餘額必須大於交易金額加上交易燃料費
7:gas limit必須大於所需的gas 水平
8:交易的nonce值必須高於鏈上的nonce值,如果低於則說明該交易已經被打包過了
-
學院Go語言視訊主頁
ofollow,noindex" target="_blank">https://edu.csdn.net/lecturer/1928 -
掃碼獲取海量視訊及原始碼 QQ群:721929980