5.5 以太坊原始碼詳解5

尹成發表於2018-11-09

交易步驟 
發起交易:制定目標地址和交易金額,以及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值,如果低於則說明該交易已經被打包過了

 

相關文章