初鏈主網上線技術解讀之——交易流程解析
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中(會先對交易進行簽名驗證)
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 { Fromcommon.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"` GasLimituint64`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 { configTxPoolConfig chainconfig*params.ChainConfig chainblockChain gasPrice*big.Int txFeedevent.Feed scopeevent.SubscriptionScope chainHeadChchan ChainHeadEvent chainHeadSub event.Subscription signertypes.Signer musync.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 queuemap[common.Address]*txList// Queued but non-processable transactions beatsmap[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