eth.sendTransaction()操作解析 - Agzs/geth-pbft-study GitHub Wiki

在执行personal.unlockAccount("0x....")后,解锁账户

然后执行eth.sendTransaction({from:"0x..",to:"0x..",value:"...",data:"0x..."})就创建了一笔交易,具体交易是怎么广播出去?怎么传递的?账户是怎么实现资金流转的?

注:纯函数先后调用关系,请看交易纯函数调用

下面从源代码入手解析这一流程:

1、交易产生 -> 提交交易

eth.sendTransaction({from:"0x..",to:"0x..",value:"...",data:"0x..."})
       ↓↓↓
func (s *PublicTransactionPoolAPI) SendTransaction(ctx context.Context, args SendTxArgs)
       ↓↓↓
submitTransaction(ctx, s.b, signed)
       ↓↓↓
func submitTransaction(ctx context.Context, b Backend, tx *types.Transaction)
       ↓↓↓
    b.SendTx(ctx, tx) >> func (b *EthApiBackend) SendTx((ctx, tx) >> 
b.eth.txPool.AddLocal(signedTx) >> pool.addTx(tx, !pool.config.NoLocals) >> pool.add(tx, local)

而执行personal.sendTransaction({from:"0x..",to:"0x..",value:"...",data:"0x..."})可以不用解锁账户

personal.sendTransaction({from:"0x..",to:"0x..",value:"...",data:"0x..."})
       ↓↓↓
func (s *PrivateAccountAPI) SendTransaction(ctx context.Context, args SendTxArgs, passwd string)
       ↓↓↓
submitTransaction(ctx, s.b, signed)

从这可以看出,产生交易,并且进行签名后,都会调用submitTransaction(ctx, s.b, signed)

2、提交交易 -> 广播交易

接着上面的提交交易的函数

func submitTransaction(ctx context.Context, b Backend, tx *types.Transaction) >> b.SendTx(ctx, tx)
       ↓↓↓
func (b *EthApiBackend) SendTx(ctx context.Context, signedTx *types.Transaction)
       ↓↓↓
b.eth.txPool.AddLocal(signedTx)
       ↓↓↓
pool.addTx(tx, !pool.config.NoLocals)
      ↓↓↓
func (pool *TxPool) addTx(tx *types.Transaction, local bool)
       ↓↓↓
pool.promoteExecutables(state, []common.Address{from})
       ↓↓↓
func (pool *TxPool) promoteExecutables(state *state.StateDB, accounts []common.Address)
       ↓↓↓
pool.promoteTx(addr, hash, tx)
       ↓↓↓
go pool.eventMux.Post(TxPreEvent{tx})
       ↓↓↓
sub.deliver(event) 将event发送到postC中:s.postC <- event,而 postC 又和 readC 等价
       ↓↓↓
func (s *TypeMuxSubscription) Chan() <-chan *TypeMuxEvent { return s.readC }

Chan()方法在txBroadcastLoop()中被调用,进一步触发BroadcastTx(),然后将TxMsg广播出去

理一下TxMsg消息的广播

ProtocolManager.Start()
       ↓↓↓
go pm.txBroadcastLoop()
       ↓↓↓
self.BroadcastTx(event.Tx.Hash(), event.Tx)             
       ↓↓↓
peer.SendTransactions(types.Transactions{tx})
       ↓↓↓
p2p.Send(p.rw, TxMsg, txs)

3、收到交易

其他节点会收到交易产生者产生的交易,并广播给与其相连接的peer

func (pm *ProtocolManager) handleMsg(p *peer)的case msg.Code == TxMsg分支,执行pm.txpool.AddRemotes(txs)
       ↓↓↓
func (pool *TxPool) AddRemotes(txs []*types.Transaction)
       ↓↓↓
pool.addTxs(txs, false)

func (pool *TxPool) addTxs(txs []*types.Transaction, local bool)
       ↓↓↓
pool.promoteExecutables(state, addrs)
      ↓↓↓
func (pool *TxPool) promoteExecutables(state *state.StateDB, accounts []common.Address)
       ↓↓↓
pool.promoteTx(addr, hash, tx)
       ↓↓↓
go pool.eventMux.Post(TxPreEvent{tx})
       ↓↓↓
sub.deliver(event) >> s.postC <- event, postC <=> readC
       ↓↓↓
func (s *TypeMuxSubscription) Chan() <-chan *TypeMuxEvent { return s.readC }

Chan()方法在txBroadcastLoop()中被调用,进一步触发BroadcastTx(),然后将TxMsg广播出去

另外,Chan()方法在worker.update()中被调用,
在TxPreEvent分支,进一步触发"apply transaction to the pending state if we're not mining"相关操作,
其中包含self.current.commitTransactions(self.mux, txset, self.chain, self.coinbase)

Chan()方法在EventSystem.eventLoop()中被调用,进一步触发es.broadcast(index, ev)

4、处理交易

该过程分析采用倒序方式描述

首先,数据库中账户余额操作由ApplyTransaction()操作层层调用实现

func ApplyTransaction(config *params.ChainConfig, bc *BlockChain, author *common.Address, gp *GasPool, statedb *state.StateDB, header *types.Header, tx *types.Transaction, usedGas *big.Int, cfg vm.Config)
      ↓↓↓
ApplyMessage(vmenv, msg, gp)
      ↓↓↓
st.TransitionDb()
      ↓↓↓
evm.Call(sender, st.to().Address(), st.data, st.gas, st.value)
      ↓↓↓
evm.Transfer(evm.StateDB, caller.Address(), to.Address(), value)
      ↓↓↓
// Transfer subtracts amount from sender and adds amount to recipient using the given Db
func Transfer(db vm.StateDB, sender, recipient common.Address, amount *big.Int) {
	db.SubBalance(sender, amount)
	db.AddBalance(recipient, amount)
}

=> db.SubBalance(sender, amount) >> StateDb.SubBalance(sender, amount) >> stateObject.SubBalance(amount) >> c.SetBalance(new(big.Int).Sub(c.Balance(), amount)) >> stateObject.SetBalance(amount) >> stateObject.setBalance(amount)

=> db.AddBalance(recipient, amount) >> StateDB.AddBalance(recipient, amount) >> stateObject.AddBalance(amount) >> c.SetBalance(new(big.Int).Add(c.Balance(), amount)) >> stateObject.SetBalance(amount) >> stateObject.setBalance(amount)

ApplyTransaction()的调用有以下几条路径:

4.1、InsertChain()切入

func (bc *BlockChain) InsertChain(chain types.Blocks) 
      ↓↓↓
bc.processor.Process(block, state, bc.vmConfig)
      ↓↓↓
func (p *StateProcessor) Process(block *types.Block, statedb *state.StateDB, cfg vm.Config)
      ↓↓↓
ApplyTransaction(p.config, p.bc, nil, gp, statedb, header, tx, totalUsedGas, cfg)

4.2、commitTransactions()切入

func (env *Work) commitTransactions(mux *event.TypeMux, txs *types.TransactionsByPriceAndNonce, bc *core.BlockChain, coinbase common.Address)
      ↓↓↓
env.commitTransaction(tx, bc, coinbase, gp)
      ↓↓↓
ApplyTransaction(env.config, bc, &coinbase, gp, env.state, env.header, tx, env.header.GasUsed, vm.Config{})

commitTransactions()在以下几处中被调用,同时在3中Chan()方法的第二种调用中就包含该方法的调用

4.2.1、fullNode切入
fullNode, err := eth.New(ctx, cfg)
      ↓↓↓
func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error)
      ↓↓↓
eth.miner = miner.New(eth, eth.chainConfig, eth.EventMux(), eth.engine)
      ↓↓↓
newWorker(config, engine, common.Address{}, eth, mux)
      ↓↓↓
go worker.update()
      ↓↓↓
self.current.commitTransactions(self.mux, txset, self.chain, self.coinbase)

4.2.2、commitNewWork()切入
func (self *worker) commitNewWork()
      ↓↓↓
work.commitTransactions(self.mux, txs, self.chain, self.coinbase)

commitNewWork()的被调用又有好多处:

4.2.2.1、fullNode切入
fullNode, err := eth.New(ctx, cfg)
      ↓↓↓
func New(ctx *node.ServiceContext, config *Config) (*Ethereum, error)
      ↓↓↓
eth.miner = miner.New(eth, eth.chainConfig, eth.EventMux(), eth.engine)
      ↓↓↓
newWorker(config, engine, common.Address{}, eth, mux)
      ↓↓↓
go worker.update() + go worker.wait() + worker.commitNewWork(),顺序执行操作
      ↓↓↓
self.commitNewWork()
4.2.2.2、miner.start()切入
func (api *PrivateMinerAPI) Start(threads *int)
      ↓↓↓
api.e.StartMining(true)
      ↓↓↓
func (s *Ethereum) StartMining(local bool)
      ↓↓↓
go s.miner.Start(eb)
      ↓↓↓
func (self *Miner) Start(coinbase common.Address)
      ↓↓↓
self.worker.commitNewWork()

纯函数先后调用关系,请看交易纯函数调用