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 {
From common.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 {
config TxPoolConfig
chainconfig *params.ChainConfig
chain blockChain
gasPrice *big.Int
txFeed event.Feed
scope event.SubscriptionScope
chainHeadCh chan ChainHeadEvent
chainHeadSub event.Subscription
signer types.Signer
mu sync.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 當前等待處理的交易
queue map[common.Address]*txList // Queued but non-processable transactions
beats map[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語言視訊主頁
https://edu.csdn.net/lecturer/1928 -
掃碼獲取海量視訊及原始碼 QQ群:721929980
相關文章
- 5.1 以太坊原始碼詳解1原始碼
- 5.2 以太坊原始碼詳解2原始碼
- 5.3 以太坊原始碼詳解3原始碼
- 5.4 以太坊原始碼詳解4原始碼
- 5.6 以太坊原始碼詳解6原始碼
- 5.7 以太坊原始碼詳解7原始碼
- 以太坊原始碼分析(5)accounts程式碼分析原始碼
- 以太坊官方 Token 程式碼詳解
- Geth命令用法-引數詳解 and 以太坊原始碼檔案目錄原始碼
- 以太坊原始碼分析(37)eth以太坊協議分析原始碼協議
- 以太坊原始碼分析(18)以太坊交易執行分析原始碼
- 以太坊原始碼分析(52)以太坊fast sync演算法原始碼AST演算法
- 3.4 以太坊架構詳解架構
- 以太坊交易池原始碼分析原始碼
- 以太坊交易池原始碼解析原始碼
- 以太坊原始碼分析(36)ethdb原始碼分析原始碼
- 以太坊原始碼分析(38)event原始碼分析原始碼
- 以太坊原始碼分析(41)hashimoto原始碼分析原始碼
- 以太坊原始碼分析(43)node原始碼分析原始碼
- 以太坊原始碼分析(51)rpc原始碼分析原始碼RPC
- 以太坊原始碼分析(52)trie原始碼分析原始碼
- 以太坊原始碼分析(54)以太坊隨機數生成方式原始碼隨機
- 以太坊原始碼分析(3)以太坊交易手續費明細原始碼
- 以太坊原始碼解讀 BlockChain的初始化原始碼Blockchain
- 3.3 以太坊核心詞彙詳解
- 以太坊原始碼分析(13)RPC分析原始碼RPC
- 以太坊原始碼分析(15)node包建立多重協議以太坊節點原始碼協議
- 以太坊原始碼分析(35)eth-fetcher原始碼分析原始碼
- 以太坊原始碼分析(20)core-bloombits原始碼分析原始碼OOM
- 以太坊原始碼分析(24)core-state原始碼分析原始碼
- 以太坊原始碼分析(29)core-vm原始碼分析原始碼
- 以太坊原始碼分析(34)eth-downloader原始碼分析原始碼
- 以太坊原始碼分析(8)區塊分析原始碼
- 以太坊原始碼分析(9)cmd包分析原始碼
- 以太坊原始碼分析(16)挖礦分析原始碼
- 以太坊啟動過程原始碼解析原始碼
- 死磕以太坊原始碼分析之state原始碼
- 死磕以太坊原始碼分析之txpool原始碼