内容简介: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
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 初链主网上线解读之——抗ASIC
- 从零开始上线网站的日常(二)— 第一次上线
- Windows Server 2019下半年上线:首个预览版Build 17623上线
- Alice 上线小记
- 谈项目上线(9.21)
- 再谈系统割接上线(10.1)
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
决战618:探秘京东技术取胜之道
京东集团618作战指挥中心 / 电子工业出版社 / 2017-11 / 99
《决战618:探秘京东技术取胜之道》以京东技术团队备战618为主线,集合京东数百位技术专家,对京东所有和618相关的关键技术系统进行了一次全面的梳理和总结,是京东技术体系的智慧结晶。 《决战618:探秘京东技术取胜之道》从前端的网站、移动入口到后端的结算、履约、物流、供应链等体系,系统展示了京东最新的技术成就。同时,也涵盖了京东正在充分运用大数据、人工智能等先进技术对所有技术体系架构进行整体......一起来看看 《决战618:探秘京东技术取胜之道》 这本书的介绍吧!