【原文】在geth控制台使用如下命令来发起转账交易:
-
personal.unlockAccount(eth.accounts[0])
-
eth.sendTransaction({from:eth.accounts[0],to:eth.accounts[1],value:web3.toWei(200,"ether")})
将执行到txpool.go的validateTx()函数进行交易验证,验证项目包括交易大小、交易Gas、账户余额检查等。
-
func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
-
// Heuristic limit, reject transactions over 32KB to prevent DOS attacks
-
if tx.Size() > 320*1024 {//lzj change from 32 to 320
-
return ErrOversizedData
-
}
-
// Transactions can't be negative. This may never happen using RLP decoded
-
// transactions but may occur if you create a transaction using the RPC.
-
if tx.Value().Sign() < 0 {
-
return ErrNegativeValue
-
}
-
// Ensure the transaction doesn't exceed the current block limit gas.
-
if pool.currentMaxGas < tx.Gas() {
-
return ErrGasLimit
-
}
-
// Make sure the transaction is signed properly
-
from, err := types.Sender(pool.signer, tx)
-
if err != nil {
-
return ErrInvalidSender
-
}
-
// Drop non-local transactions under our own minimal accepted gas price
-
local = local || pool.locals.contains(from) // account may be local even if the transaction arrived from the network
-
if !local && pool.gasPrice.Cmp(tx.GasPrice()) > 0 {
-
return ErrUnderpriced
-
}
-
// Ensure the transaction adheres to nonce ordering
-
if pool.currentState.GetNonce(from) > tx.Nonce() {
-
return ErrNonceTooLow
-
}
-
// Transactor should have enough funds to cover the costs
-
// cost == V + GP * GL
-
if pool.currentState.GetBalance(from).Cmp(tx.Cost()) < 0 {
-
return ErrInsufficientFunds
-
}
-
intrGas, err := IntrinsicGas(tx.Data(), tx.To() == nil, pool.homestead)
-
if err != nil {
-
return err
-
}
-
if tx.Gas() < intrGas {
-
return ErrIntrinsicGas
-
}
-
return nil
-
}
然后在tx_pool.go中的promoteExecutables()函数中进行进一步检查:
-
func (pool *TxPool) promoteExecutables(accounts []common.Address) {
-
// Gather all the accounts potentially needing updates
-
if accounts == nil {
-
accounts = make([]common.Address, 0, len(pool.queue))
-
for addr := range pool.queue {
-
accounts = append(accounts, addr)
-
}
-
}
-
// Iterate over all accounts and promote any executable transactions
-
for _, addr := range accounts {
-
list := pool.queue[addr]
-
if list == nil {
-
continue // Just in case someone calls with a non existing account
-
}
-
// Drop all transactions that are deemed too old (low nonce)
-
for _, tx := range list.Forward(pool.currentState.GetNonce(addr)) {
-
hash := tx.Hash()
-
log.Trace("Removed old queued transaction", "hash", hash)
-
delete(pool.all, hash)
-
pool.priced.Removed()
-
}
-
// Drop all transactions that are too costly (low balance or out of gas)
-
drops, _ := list.Filter(pool.currentState.GetBalance(addr), pool.currentMaxGas)
-
for _, tx := range drops {
-
hash := tx.Hash()
-
log.Trace("Removed unpayable queued transaction", "hash", hash)
-
delete(pool.all, hash)
-
pool.priced.Removed()
-
queuedNofundsCounter.Inc(1)
-
}
-
// Gather all executable transactions and promote them
-
for _, tx := range list.Ready(pool.pendingState.GetNonce(addr)) {
-
hash := tx.Hash()
-
log.Trace("Promoting queued transaction", "hash", hash)
-
pool.promoteTx(addr, hash, tx)
-
}
-
// Drop all transactions over the allowed limit
-
if !pool.locals.contains(addr) {
-
for _, tx := range list.Cap(int(pool.config.AccountQueue)) {
-
hash := tx.Hash()
-
delete(pool.all, hash)
-
pool.priced.Removed()
-
queuedRateLimitCounter.Inc(1)
-
log.Trace("Removed cap-exceeding queued transaction", "hash", hash)
-
}
-
}.....
其中重要的是pool.promoteTx(addr,hash,tx),从这里进入promoteTx函数:
-
func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.Transaction) {
-
// 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
-
delete(pool.all, hash)
-
pool.priced.Removed()
-
pendingDiscardCounter.Inc(1)
-
return
-
}
-
// Otherwise discard any previous transaction and mark this
-
if old != nil {
-
delete(pool.all, old.Hash())
-
pool.priced.Removed()
-
pendingReplaceCounter.Inc(1)
-
}
-
// Failsafe to work around direct pending inserts (tests)
-
if pool.all[hash] == nil {
-
pool.all[hash] = 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)
-
go pool.txFeed.Send(TxPreEvent{tx})
-
}
promoteTx函数最后用
go pool.txFeed.Send(TxPreEvent{tx})
将交易发布发布到TxPreEvent类型的监听通道上去。接受该交易的通道定义在miner/worker.go中:
-
func newWorker(config *params.ChainConfig, engine consensus.Engine, coinbase common.Address, eth Backend, mux *event.TypeMux) *worker {
-
worker := &worker{
-
config: config,
-
engine: engine,
-
eth: eth,
-
mux: mux,
-
txCh: make(chan core.TxPreEvent, txChanSize),
-
chainHeadCh: make(chan core.ChainHeadEvent, chainHeadChanSize),
-
chainSideCh: make(chan core.ChainSideEvent, chainSideChanSize),
-
chainDb: eth.ChainDb(),
-
recv: make(chan *Result, resultQueueSize),
-
chain: eth.BlockChain(),
-
proc: eth.BlockChain().Validator(),
-
possibleUncles: make(map[common.Hash]*types.Block),
-
coinbase: coinbase,
-
agents: make(map[Agent]struct{}),
-
unconfirmed: newUnconfirmedBlocks(eth.BlockChain(), miningLogAtDepth),
-
}
-
// Subscribe TxPreEvent for tx pool
-
worker.txSub = eth.TxPool().SubscribeTxPreEvent(worker.txCh)
-
// Subscribe events for blockchain
-
worker.chainHeadSub = eth.BlockChain().SubscribeChainHeadEvent(worker.chainHeadCh)
-
worker.chainSideSub = eth.BlockChain().SubscribeChainSideEvent(worker.chainSideCh)
-
go worker.update()
-
go worker.wait()
-
worker.commitNewWork()
-
return worker
-
}
接受交易的通道是worker.txCh,通过
-
// Subscribe TxPreEvent for tx pool
-
worker.txSub = eth.TxPool().SubscribeTxPreEvent(worker.txCh)
来注册对TxPreEvent事件的监听。go-ethereum中的事件监听机制见这篇文章。txCh通道的监听执行函数在worker.go中的update()函数:
-
func (self *worker) update() {
-
defer self.txSub.Unsubscribe()
-
defer self.chainHeadSub.Unsubscribe()
-
defer self.chainSideSub.Unsubscribe()
-
for {
-
// A real event arrived, process interesting content
-
select {
-
// Handle ChainHeadEvent
-
case <-self.chainHeadCh:
-
self.commitNewWork()
-
// Handle ChainSideEvent
-
case ev := <-self.chainSideCh:
-
self.uncleMu.Lock()
-
self.possibleUncles[ev.Block.Hash()] = ev.Block
-
self.uncleMu.Unlock()
-
// Handle TxPreEvent
-
case ev := <-self.txCh:
-
log.Info("worker self.txch now")
-
// Apply transaction to the pending state if we're not mining
-
if atomic.LoadInt32(&self.mining) == 0 {
-
self.currentMu.Lock()
-
acc, _ := types.Sender(self.current.signer, ev.Tx)
-
txs := map[common.Address]types.Transactions{acc: {ev.Tx}}
-
txset := types.NewTransactionsByPriceAndNonce(self.current.signer, txs)
-
self.current.commitTransactions(self.mux, txset, self.chain, self.coinbase)
-
self.updateSnapshot()
-
self.currentMu.Unlock()
-
} else {
-
// If we're mining, but nothing is being processed, wake on new transactions
-
if self.config.Clique != nil && self.config.Clique.Period == 0 {
-
self.commitNewWork()
-
}
-
}
-
// System stopped
-
case <-self.txSub.Err():
-
return
-
case <-self.chainHeadSub.Err():
-
return
-
case <-self.chainSideSub.Err():
-
return
-
}
-
}
-
}
在通过commitTransactions()函数将交易提交给区块链执行。commitTransactions()函数中遍历每一笔交易,然后通过
commitTransaction()函数提交并执行交易:
-
func (env *Work) commitTransaction(tx *types.Transaction, bc *core.BlockChain, coinbase common.Address, gp *core.GasPool) (error, []*types.Log) {
-
snap := env.state.Snapshot()
-
receipt, _, err := core.ApplyTransaction(env.config, bc, &coinbase, gp, env.state, env.header, tx, &env.header.GasUsed, vm.Config{})
-
if err != nil {
-
log.Info("worker commitTransaction err","err",err)
-
env.state.RevertToSnapshot(snap)
-
return err, nil
-
}
-
env.txs = append(env.txs, tx)
-
env.receipts = append(env.receipts, receipt)
-
return nil, receipt.Logs
-
}
commitTransaction()函数先创建区块链状态的快照,然后通过core.ApplyTransaction()函数来执行交易,如果执行出错,就又回退交易到快照状态。ApplyTransaction()的核心函数是ApplyMessage()。基本调用流程是:
state_processor.go的ApplyTransaction()-->state_transition.go中的ApplyMessage()-->TransitionDb()-->vm/evm.go中的Call()函数。