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

栏目: IOS · 发布时间: 5年前

内容简介:Truechain主网Beta版交易流程解析初链主网Beta版于新加坡时间2018年09月28日08:00正式上线,在此之前,07:56分PBFT委员会第一次共识出块和TrueChain fPOW创世区块被挖出今天我们主要看看初链主网Beta版的交易部分,本文主要浅谈源码,所以懂go是前提,我们先看下启动流程再看交易流程。

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.Context) error {
    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 (ks *KeyStore) SignTxWithPassphrase(a accounts.Account, passphrase string, tx *types.Transaction, chainID *big.Int) (*types.Transaction, error) {
    _, key, err := ks.getDecryptedKey(a, passphrase)
    if err != nil {
        return nil, err
    }
    defer zeroKey(key.PrivateKey)

    // Depending on the presence of the chain ID, sign with EIP155 or homestead
    if chainID != nil {
        return types.SignTx(tx, types.NewEIP155Signer(chainID), key.PrivateKey)
    }
    return types.SignTx(tx, types.HomesteadSigner{}, key.PrivateKey)
}

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

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

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

  • 生成交易的hash值

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

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

4、提交交易

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

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

    wg sync.WaitGroup // for shutdown sync

    homestead bool
}

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

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

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
}

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

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

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

此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 (pool *TxPool) promoteExecutables(accounts []common.Address) {
...
for _, tx := range list.Ready(pool.pendingState.GetNonce(addr)) {
            hash := tx.Hash()
            if pool.promoteTx(addr, hash, tx) {
                log.Trace("Promoting queued transaction", "hash", hash)
                promoted = append(promoted, tx)
            }
        }
...
// Notify subsystem for new promoted transactions.
    if len(promoted) > 0 {
        go pool.txFeed.Send(NewTxsEvent{promoted})
    }
....
}

promoteTx 代码

func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.Transaction) bool {
    // Try to insert the transaction into the pending queue
    if pool.pending[addr] == nil {
        pool.pending[addr] = newTxList(true)
    }
    list := pool.pending[addr]

    inserted, old := list.Add(tx, pool.config.PriceBump)
    if !inserted {
        // An older transaction was better, discard this
        pool.all.Remove(hash)
        pool.priced.Removed()

        pendingDiscardCounter.Inc(1)
        return false
    }
    // Otherwise discard any previous transaction and mark this
    if old != nil {
        pool.all.Remove(old.Hash())
        pool.priced.Removed()

        pendingReplaceCounter.Inc(1)
    }
    // Failsafe to work around direct pending inserts (tests)
    if pool.all.Get(hash) == nil {
        pool.all.Add(tx)
        pool.priced.Put(tx)
    }
    // Set the potentially new pending nonce and notify any subsystems of the new tx
    pool.beats[addr] = time.Now()
    pool.pendingState.SetNonce(addr, tx.Nonce()+1)

    return true
}

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

SubscribeNewTxsEvent()函数订阅该事件:

func (pool *TxPool) SubscribeNewTxsEvent(ch chan<-    core.NewTxsEvent) event.Subscription {
 return pool.scope.Track(pool.txFeed.Subscribe(ch))
}

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

func (pm *ProtocolManager) Start(maxPeers int) {
    pm.maxPeers = maxPeers

    // broadcast transactions 广播交易
    pm.txsCh = make(chan core.NewTxsEvent, txChanSize)
    pm.txsSub = pm.txpool.SubscribeNewTxsEvent(pm.txsCh)
    go pm.txBroadcastLoop()

    //broadcast fruits  广播水果
    pm.fruitsch = make(chan snailchain.NewFruitsEvent, fruitChanSize)
    pm.fruitsSub = pm.SnailPool.SubscribeNewFruitEvent(pm.fruitsch)
    go pm.fruitBroadcastLoop()
        ....

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

func (pm *ProtocolManager) BroadcastTxs(txs types.Transactions) {
    var txset = make(map[*peer]types.Transactions)

    // Broadcast transactions to a batch of peers not knowing about it
    for _, tx := range txs {
        peers := pm.peers.PeersWithoutTx(tx.Hash())
        for _, peer := range peers {
            txset[peer] = append(txset[peer], tx)
        }
        log.Trace("Broadcast transaction", "hash", tx.Hash(), "recipients", len(peers))
    }
    // FIXME include this again: peers = peers[:int(math.Sqrt(float64(len(peers))))]
    for peer, txs := range txset {
        peer.AsyncSendTransactions(txs)
    }
}

//传播水果区块
func (pm *ProtocolManager) BroadcastFruits(fruits types.Fruits) {
    var fruitset = make(map[*peer]types.Fruits)

    // Broadcast records to a batch of peers not knowing about it
    for _, fruit := range fruits {
        peers := pm.peers.PeersWithoutFruit(fruit.Hash())
        for _, peer := range peers {
            fruitset[peer] = append(fruitset[peer], fruit)
        }
        log.Trace("Broadcast fruits", "number", fruit.FastNumber(), "diff", fruit.FruitDifficulty(), "recipients", len(peers), "hash", fruit.Hash())
    }
    // FIXME include this again: peers = peers[:int(math.Sqrt(float64(len(peers))))]
    for peer, fruits := range fruitset {
        peer.AsyncSendFruits(fruits)
    }
}

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

作者:qq_22269733 

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


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Building Websites with Joomla!

Building Websites with Joomla!

H Graf / Packt Publishing / 2006-01-20 / USD 44.99

This book is a fast paced tutorial to creating a website using Joomla!. If you've never used Joomla!, or even any web content management system before, then this book will walk you through each step i......一起来看看 《Building Websites with Joomla!》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具