以太坊提交交易流程分析

说明

代码基于go-ethereum,版本v1.8.10。

RPC代码入口

SendTransaction

// SendTransaction will create a transaction from the given arguments and
// tries to sign it with the key associated with args.To. If the given passwd isn't
// able to decrypt the key it fails.
func (s *PrivateAccountAPI) SendTransaction(ctx context.Context, args SendTxArgs, passwd string) (common.Hash, error) {
    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)
    }
    signed, err := s.signTransaction(ctx, args, passwd)
    if err != nil {
        return common.Hash{}, err
    }
    return submitTransaction(ctx, s.b, signed)
}

SendTxArgs的数据结构如下:

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"`
}

新增加Input字段。用于替换原来Data字段,为了兼容保留Data字段。

查找钱包

根据from地址信息查找钱包。

    // Look up the wallet containing the requested signer
    account := accounts.Account{Address: args.From}
    wallet, err := s.am.Find(account)
    if err != nil {
        return nil, err
    }

设置参数

// setDefaults is a helper function that fills in default values for unspecified tx fields.
func (args *SendTxArgs) setDefaults(ctx context.Context, b Backend) error {
    if args.Gas == nil {
        args.Gas = new(hexutil.Uint64)
        *(*uint64)(args.Gas) = 90000
    }
    if args.GasPrice == nil {
        price, err := b.SuggestPrice(ctx)
        if err != nil {
            return err
        }
        args.GasPrice = (*hexutil.Big)(price)
    }
    if args.Value == nil {
        args.Value = new(hexutil.Big)
    }
    if args.Nonce == nil {
        nonce, err := b.GetPoolNonce(ctx, args.From)
        if err != nil {
            return err
        }
        args.Nonce = (*hexutil.Uint64)(&nonce)
    }
    if args.Data != nil && args.Input != nil && !bytes.Equal(*args.Data, *args.Input) {
        return errors.New(`Both "data" and "input" are set and not equal. Please use "input" to pass transaction call data.`)
    }
    if args.To == nil {
        // Contract creation
        var input []byte
        if args.Data != nil {
            input = *args.Data
        } else if args.Input != nil {
            input = *args.Input
        }
        if len(input) == 0 {
            return errors.New(`contract creation without any data provided`)
        }
    }
    return nil
}

创建交易

根据交易参数创建交易。

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)
}

相关数据结构如下:

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"`
    GasLimit     uint64          `json:"gas"      gencodec:"required"`
    Recipient    *common.Address `json:"to"       rlp:"nil"` // nil means contract creation
    Amount       *big.Int        `json:"value"    gencodec:"required"`
    Payload      []byte          `json:"input"    gencodec:"required"`

    // 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:"-"`
}

获取ChainId

为了防止重放攻击,获得了chainID(EIP155之后才支持),用于签名。

    var chainID *big.Int
    if config := s.b.ChainConfig(); config.IsEIP155(s.b.CurrentBlock().Number()) {
        chainID = config.ChainId
    }

chainID是如何使用的?简单说就是:以一系列数学运算将chainID保存到签名信息中。

func NewEIP155Signer(chainId *big.Int) EIP155Signer {
    if chainId == nil {
        chainId = new(big.Int)
    }
    return EIP155Signer{
        chainId:    chainId,
        chainIdMul: new(big.Int).Mul(chainId, big.NewInt(2)),
    }
}
// WithSignature returns a new transaction with the given signature. This signature
// needs to be in the [R || S || V] format where V is 0 or 1.
func (s EIP155Signer) SignatureValues(tx *Transaction, sig []byte) (R, S, V *big.Int, err error) {
    R, S, V, err = HomesteadSigner{}.SignatureValues(tx, sig)
    if err != nil {
        return nil, nil, nil, err
    }
    if s.chainId.Sign() != 0 {
        V = big.NewInt(int64(sig[64] + 35))
        V.Add(V, s.chainIdMul)
    }
    return R, S, V, nil
}

签名

从keystore获取私钥用于签名交易。

// SignHashWithPassphrase signs hash if the private key matching the given address
// can be decrypted with the given passphrase. The produced signature is in the
// [R || S || V] format where V is 0 or 1.
func (ks *KeyStore) SignHashWithPassphrase(a accounts.Account, passphrase string, hash []byte) (signature []byte, err error) {
    _, key, err := ks.getDecryptedKey(a, passphrase)
    if err != nil {
        return nil, err
    }
    defer zeroKey(key.PrivateKey)
    return crypto.Sign(hash, key.PrivateKey)
}

提交交易

// submitTransaction is a helper function that submits tx to txPool and logs a message.
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
}

添加交易到本地交易池。

func (b *EthAPIBackend) SendTx(ctx context.Context, signedTx *types.Transaction) error {
    return b.eth.txPool.AddLocal(signedTx)
}

小结

到此为止,交易已经提交到交易池。这才是整个交易完成过程中第一步,后面还有广播、检查、打包、确认等流程。

这是节点钱包提交交易的过程。轻钱包提交交易与此有一些不同,是通过rpc调用提交到钱包服务的节点钱包。

欢迎关注

欢迎关注微信公众帐号:沉风网事(savewind)

沉风网事

Share Comments
comments powered by Disqus