源码

首页 » 归档 » 源码 » 初链主网上线技术解读之——交易流程解析

初链主网上线技术解读之——交易流程解析

Truechain主网Beta版交易流程解析

初链主网Beta版于新加坡时间2018年09月28日08:00正式上线,在此之前,07:56分PBFT委员会第一次共识出块和TrueChain fPOW创世区块被挖出

今天我们主要看看初链主网Beta版的交易部分,本文主要浅谈源码,所以懂go是前提,我们先看下启动流程再看交易流程。

启动的流程

当我们使用命令./build/bin/getrue --datadir ./data --cache 4096 --rpc --rpcport 33333 --rpcaddr 0.0.0.0 开启节点时的流程:

首先整个true项目的主函数在cmd/getrue/main.go中,这个文件中有一个main() 和init() 函数,先执行init() 初始化配置一个解析命令的库。其中app.Action = getrue 则说明如果用户在没有输入其他的子命令的情况下会调用这个字段指向的函数app.Action = getrue,即main.go中的func getrue(ctx *cli.Context) error函数。

func init() {
    // Initialize the CLI app and start Getrue 初始化CLI APP库
    app.Action = getrue 
    app.HideVersion = true // we have a command to print the version
    app.Copyright = "Copyright 2013-2018 The getrue Authors"
    app.Commands = []cli.Command{
        // See chaincmd.go:
        initCommand,
        importCommand,

然后再调用主函数main(),app 是一个第三方包gopkg.in/urfave/cli.v1的实列,这个第三方包的大用法大致就是首先构造这个app对象,通过代码配置app对象的行为,提供一些回调函数。然后运行的时候直接在main函数里运行app.Run(os.Args)就ok.

func main() {
    if err := app.Run(os.Args); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

如果没有指定特殊的子命令,那么getrue 是系统的主要入口,它会根据提供的参数创建一个默认的节点。并且以阻塞的模式运行这个节点,并且等待着节点被终止

func getrue(ctx *cli.Contexterror {
    node := makeFullNode(ctx)
    startNode(ctx, node)
    node.Wait()
    return nil
}

我们可以看看makeFullNode函数,在cmd/getrue/config.go 中

func makeFullNode(ctx *cli.Context) *node.Node {
      根据命令行参数和一些特殊配置来创建一个node
    stack, cfg := makeConfigNode(ctx)
       把etrue 的服务注册到这个节点上面。
    utils.RegisterEthService(stack, &cfg.Etrue)

    if ctx.GlobalBool(utils.DashboardEnabledFlag.Name) {
        utils.RegisterDashboardService(stack, &cfg.Dashboard, gitCommit)
    }
    // Whisper must be explicitly enabled by specifying at least 1 whisper flag or in dev mode
.....

交易流程

true 基本交易流程

大致流程分为以下几个步骤:

  • 发起交易:指定目标地址和交易金额,以及需要的gas/gaslimit

  • 交易签名:使用账户私钥队对交易进行签名

  • 提交交易:把交易加入到交易缓冲池txpool中(会先对交易进行签名验证)

  • 广播交易:通知EVM(true目前还是以太坊的EVM)执行,同时

1、发起交易

用户通过JONS RPC 发起 etrue.sendTransacton 交易請求,最終會調用PublicTransactionPoolAPI的SendTransaction 实现

首先会根据from地址查找对应的wallet,检查一下参数值,然后通过SendTxArgs.toTransaction()创建交易,通过Wallet.SignTx()对交易进行签名。 通过submitTransaction()提交交易

我们先看看SendTransaction的源码,代码位于internal/trueapi/api.go

func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args SendTxArgs) (common.Hash, error) {

    // Look up the wallet containing the requested signe
  解锁发起交易的账户
    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、创建交易

tx := args.toTransaction() 创建交易代码

我们先看SendTxArgs类型的定义 。代码位于internal/trueapi/api.go

// SendTxArgs represents the arguments to sumbit a new transaction into the transaction pool.
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"`
}

可以看到的是和JSON字段对应的,包括地址、gas、金额这些交易信息,nonce值是一个随账户交易次数递增的数字,一般会自动填充,交易还可以携带一些额外数据,存放在data或者input字段中.

我们看下toTransaction()函数:

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

可以看到,如果目标地址为空的话,表示这是一个创建智能合约的交易,调用NewContractCreation(),否则说明这是一个普通的交易,调用NewTransaction()方法,不管调用哪个都会生成一个Transaction实列,我们先看看这个Transaction类型的定义:源码位于core/types/transaction.go

type Transaction struct {
    data txdata
    // 缓存
    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"`

    // 签名数据
    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:"-"`
}

3、签名交易

签名交易的源码位于internal/trueapi/api.go

func (s *PrivateAccountAPI) signTransaction(ctx context.Context, args SendTxArgs, passwd string) (*types.Transaction, error) {
    // 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
    }
    // Set some sanity defaults and terminate on failure
    if err := args.setDefaults(ctx, s.b); err != nil {
        return nil, 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
    }
    return wallet.SignTxWithPassphrase(account, passwd, tx, chainID)
}

我们可以看到最后一句代码就是签名方法,传递账户和密码,以及交易和链的id,我们来看看SignTxWithPassphrase这个方法,这个方法的代码位于 accounts/keystore/keystore_wallet.go

func (w *keystoreWallet) SignTxWithPassphrase(account accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
    // Make sure the requested account is contained within
    if account.Address != w.account.Address {
        return nil, accounts.ErrUnknownAccount
    }
    if account.URL != (accounts.URL{}) && account.URL != w.account.URL {
        return nil, accounts.ErrUnknownAccount
    }
    // Account seems valid, request the keystore to sign
    return w.keystore.SignTxWithPassphrase(account, passphrase, tx, chainID)
}

w.keystore.SignTxWithPassphrase(account, passphrase, tx, chainID)的代码位于 accounts/keystore/keystore.go 主要就是通过SignTx进行签名

func main() {
    if err := app.Run(os.Args); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

0

这里会首先判断账户是否已经解锁,如果已经解锁的话就可以获取它的私钥,然后创建签名器,如果要符合EIP155规范的话就需要把chainId传进去也就是我们的--networkid命令行的参数,最后调用一个全局函数SignTx()完成签名,SignTx()这个方法位于core/types/transaction_signing.go

func main() {
    if err := app.Run(os.Args); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

1

SignTx()方法主要分为3个步骤,并且不继续展开讲解了,

  • 生成交易的hash值

  • 根据hash值和私钥生成签名

  • 把签名数据填充到Transaction实列中

4、提交交易

签名完成后就需要调用submitTransaction()函数提交到Txpool缓冲池中,我们先看下TxPool中的字段,源码位于core/tx_pool.go

func main() {
    if err := app.Run(os.Args); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

2

pending字段中包含了当前所有可被处理的交易列表,而queue字段包含了所有不可以被处理,也就是新加入进来的交易。

然后我们再看看submitTransaction()函数,源码位于internal/trueapi/api.go

func main() {
    if err := app.Run(os.Args); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

3

可以看到submitTransaction函数里先调用了SendTx()函数提交交易,然后如果发现目标地址为空,表明这是一个创建智能合约的交易,会创建合约地址。

提交交易到txpool, 源码位于etrue/api_backend.go

func main() {
    if err := app.Run(os.Args); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

4

此txPool.AddLocl()函数中有2个主要的函数add函数,和promoteExecuteables(),源码位于core/tx_pool.go自行去看,add()会判断是否应该把当前交易加入到queue列表中,promoteExecuteables()则会从queue中选取一些交易放入pending列表中等待执行。这里就不展开那2个函数了。

5、广播交易

交易提交到txpool中后,还需要广播出去,一方面通知EVM执行该交易,另外就是要把信息广播给其他的节点,具体调用再promoteExecutables中的promoteTx()函数中,源码位于core/tx_pool.go

func main() {
    if err := app.Run(os.Args); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

5

promoteTx 代码

func main() {
    if err := app.Run(os.Args); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

6

先更新了最后一次心跳时间,然后更新账户的nonce值。pool.txFeed.Send 发送一个TxPreEvent事件,外部可以通过

SubscribeNewTxsEvent()函数订阅该事件:

func main() {
    if err := app.Run(os.Args); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

7

我们只要全局搜索SubscribeNewTxsEvent这个函数,就知道有哪些组件订阅了该事件,其中一个订阅的地方在etrue/handler.go

func main() {
    if err := app.Run(os.Args); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

8

启动了一个goroutine来接TxPreEvent事件, txBroadcastLoop()函数里调用了BroadcastTxs()函数

func main() {
    if err := app.Run(os.Args); err != nil {
        fmt.Fprintln(os.Stderr, err)
        os.Exit(1)
    }
}

9

上面的代码可以看出这里会通过P2P向所有没有该交易的节点发送该交易

作者:qq_22269733 

原文:https://blog.csdn.net/qq_22269733/article/details/83025225 

(0)

本文由 投稿者 创作,文章地址:https://blog.isoyu.com/archives/chulianzhuwangshangxianjishujieduzhijiaoyiliuchengjiexi.html
采用知识共享署名4.0 国际许可协议进行许可。除注明转载/出处外,均为本站原创或翻译,转载前请务必署名。最后编辑时间为:10 月 30, 2018 at 04:52 上午

热评文章

发表回复

[必填]

我是人?

提交后请等待三秒以免造成未提交成功和重复