死磕以太坊源码分析之区块上链入库
死磕以太坊源码分析之区块上链入库
> 死磕以太坊源码分析之区块上链入库 > > 配合以下代码进行阅读:https://github.com/blockchainGuide/ > > 写文不易,给个小关注,有什么问题可以指出,便于大家交流学习。 ## 引言 不管是矿工挖矿还是`Fetcher`同步,`Downloader`同步,或者是导入本地文件等等,最中都是将区块上链入库。接下来我们就详细分析这部分的动作。 ## 几处可能调用的地方 ①:在Downloader同步最后会将区块插入到区块链中 ```go func (d *Downloader) importBlockResults(results []*fetchResult) error { ... if index, err := d.blockchain.InsertChain(blocks); err != nil { .... } } ``` ②:创建一个新的以太坊协议管理器,也会将区块插入到链中 ```go func NewProtocolManager(...) (*ProtocolManager, error) { ... n, err := manager.blockchain.InsertChain(blocks) } ``` ③:插入侧链数据 ```go func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (int, error) { ... if _, err := bc.insertChain(blocks, false); err != nil { .... } } ``` ④:从本地文件导入链 ```go func (api *PrivateAdminAPI) ImportChain(file string) (bool, error) { if _, err := api.eth.BlockChain().InsertChain(blocks); err != nil { .... } } ``` ⑤:fetcher同步导入块 ```go func (f *Fetcher) insert(peer string, block *types.Block) { ... if _, err := f.insertChain(types.Blocks{block}); err != nil { ... } } ``` 以上就是比较常见的需要将区块上链的动作。调用的核心方法就是: ```go func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, error) {} ``` ------ > 获取区块链所有相关文章以及资料,请参阅:https://github.com/blockchainGuide/ ## 插入数据到blockchain中 ①:如果链正在中断,直接返回 ```GO if atomic.LoadInt32(&bc.procInterrupt) == 1 { return 0, nil } ``` ②:开启并行的签名恢复 ```GO senderCacher.recoverFromBlocks(types.MakeSigner(bc.chainConfig, chain[0].Number()), chain) ``` ③:开启并行校验header ```go abort, results := bc.engine.VerifyHeaders(bc, headers, seals) ``` 校验`header`是共识引擎所要做的事情,我们这里只分析`ethash`它的实现。 ```go func (ethash *Ethash) VerifyHeaders(chain consensus.ChainReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) { .... errors[index] = ethash.verifyHeaderWorker(chain, headers, seals, index) } ``` ```go func (ethash *Ethash) verifyHeaderWorker(chain consensus.ChainReader, headers []*types.Header, seals []bool, index int) error { var parent *types.Header if index == 0 { parent = chain.GetHeader(headers[0].ParentHash, headers[0].Number.Uint64()-1) } else if headers[index-1].Hash() == headers[index].ParentHash { parent = headers[index-1] } if parent == nil { return consensus.ErrUnknownAncestor } if chain.GetHeader(headers[index].Hash(), headers[index].Number.Uint64()) != nil { return nil // known block } return ethash.verifyHeader(chain, headers[index], parent, false, seals[index]) } ``` 首先会调用`verifyHeaderWorker`进行校验,主要检验块的祖先是否已知以及块是否已知,接着会调用`verifyHeader`进行更深的校验,也是最核心的校验,大概做了以下几件事: 1. header.Extra*不可超过32字节* 2. header.Time*不能超过15秒,15秒以后的就被认定为未来的块* 3. *当前header的时间戳不可以等于父块的时间戳* 4. *根据难度计算算法得出的expected必须和header.Difficulty 一致。* 5. *Gas limit 要 <= 2 ^ 63-1* 6. *gasUsed<= gasLimit* 7. *Gas limit 要在允许范围内* 8. *块号必须是父块加1* 9. *根据 ethash.VerifySeal去验证块是否满足POW难度要求* 到此验证header的事情就做完了。 ④:循环校验body ```go block, err := it.next() -> ValidateBody -> VerifyUncles ``` 包括以下错误: - **block**已知 - **uncle**太多 - 重复的**uncle** - **uncle**是祖先块 - **uncle**哈希不匹配 - 交易哈希不匹配 - 未知祖先 - 祖先块的状态无法获取 4.1 如果`block`存在,且是已知块,则写入已知块。 ```go bc.writeKnownBlock(block) ``` 4.2 如果是祖先块的状态无法获取的错误,则作为侧链插入: ```go bc.insertSideChain(block, it) ``` 4.3 如果是未来块或者未知祖先,则添加未来块: ```go bc.addFutureBlock(block); ``` 注意这里的添加 futureBlock,会被扔进futureBlocks里面去,在NewBlockChain的时候会开启新的goroutine: ```go go bc.update() ``` ```go func (bc *BlockChain) update() { futureTimer := time.NewTicker(5 * time.Second) for{ select{ case <-futureTimer.C: bc.procFutureBlocks() } } } ``` ```go func (bc *BlockChain) procFutureBlocks() { ... for _, hash := range bc.futureBlocks.Keys() { if block, exist := bc.futureBlocks.Peek(hash); exist { blocks = append(blocks, block.(*types.Block)) } } ... for i := range blocks { bc.InsertChain(blocks[i : i+1]) } } } ``` 会开启一个计时器,每5秒就会去执行插入这些未来的块。 4.4 如果是其他错误,直接中断,并且报告坏块。 ```go bc.futureBlocks.Remove(block.Hash()) ... bc.reportBlock(block, nil, err) ``` ⑤:没有校验错误 5.1 如果是坏块,则报告; ```go if BadHashes[block.Hash()] { bc.reportBlock(block, nil, ErrBlacklistedHash) return it.index, ErrBlacklistedHash } ``` 5.2 如果是未知块,则写入未知块; ```go if err == ErrKnownBlock { logger := log.Debug if bc.chainConfig.Clique == nil { logger = log.Warn } ... if err := bc.writeKnownBlock(block); err != nil { return it.index, err } stats.processed++ lastCanon = block continue } ``` 5.3 根据给定trie,创建状态; ```go parent := it.previous() if parent == nil { parent = bc.GetHeader(block.ParentHash(), block.NumberU64()-1) } statedb, err := state.New(parent.Root, bc.stateCache) ``` 5.4执行块中的交易: (**稍后会在下节对此进行详细分析**) ```go receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig) ``` 5.5 使用默认的validator校验状态: ```go bc.validator.ValidateState(block, statedb, receipts, usedGas); ``` 5.6 将块写入到区块链中并获取状态: (**稍后会在下节对此进行详细分析**) ```go status, err := bc.writeBlockWithState(block, receipts, logs, statedb, false) ``` ⑥:校验写入区块的状态 - `CanonStatTy` : 插入成功新的block - `SideStatTy`:插入成功新的分叉区块 - `Default`:插入未知状态的block ⑦:如果还有块,并且是未来块的话,那么将块添加到未来块的缓存中去 ```go bc.addFutureBlock(block) ``` 至此`insertChain` 大概介绍清楚。 ----- ### 执行块中交易 在我们将区块上链,有一个关键步骤就是执行区块交易: ```go receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig) ``` 进入函数,具体分析: ①:准备要用的字段,循环执行交易 关键函数:`ApplyTransaction`,根据此函数返回收据。 1.1 将交易结构转成`Message`结构 ```go msg, err := tx.AsMessage(types.MakeSigner(config, header.Number)) ``` 1.2 创建要在EVM环境中使用的新上下文 ```go context := NewEVMContext(msg, header, bc, author) ``` 1.3 创建一个新环境,其中包含有关事务和调用机制的所有相关信息。 ```GO vmenv := vm.NewEVM(context, statedb, config, cfg) ``` 1.4 将交易应用到当前状态(包含在env中) ```go _, gas, failed, err := ApplyMessage(vmenv, msg, gp) ``` 这部分代码继续跟进: ```go func ApplyMessage(evm *vm.EVM, msg Message, gp *GasPool) ([]byte, uint64, bool, error) { return NewStateTransition(evm, msg, gp).TransitionDb() } ``` `NewStateTransition` 是一个状态转换对象,`TransitionDb()` 负责转换交易状态,继续跟进: 先进行`preCheck`,用来校验`nonce`是否正确 ```go st.preCheck() if st.msg.CheckNonce() { nonce := st.state.GetNonce(st.msg.From()) if nonce < st.msg.Nonce() { return ErrNonceTooHigh } else if nonce > st.msg.Nonce() { return ErrNonceTooLow } } ``` 计算所需`gas`: ```go gas, err := IntrinsicGas(st.data, contractCreation, homestead, istanbul) ``` 扣除`gas`: ```go if err = st.useGas(gas); err != nil { return nil, 0, false, err } ``` ```go func (st *StateTransition) useGas(amount uint64) error { if st.gas < amount { return vm.ErrOutOfGas } st.gas -= amount return nil } ``` 如果是合约交易,则新建一个合约 ```go ret, _, st.gas, vmerr = evm.Create(sender, st.data, st.gas, st.value) ``` 如果不是合约交易,则增加`nonce` ```go st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1) ret, st.gas, vmerr = evm.Call(sender, st.to(), st.data, st.gas, st.value) ``` 重点关注`evm.call`方法: *检查账户是否有足够的气体进行转账* ```go if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) { return nil, gas, ErrInsufficientBalance } ``` *如果stateDb不存在此账户,则新建账户* ```go if !evm.StateDB.Exist(addr) { evm.StateDB.CreateAccount(addr) } ``` *执行转账操作* ```go evm.Transfer(evm.StateDB, caller.Address(), to.Address(), value) ``` *创建合约* ```go contract := NewContract(caller, to, value, gas) ``` *执行合约* ```go ret, err = run(evm, contract, input, false) ``` 添加余额 ```go st.state.AddBalance(st.evm.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice)) ``` 回到`ApplyTransaction` 1.5 调用`IntermediateRoot`计算状态`trie`的当前根哈希值。 最终确定所有肮脏的存储状态,并把它们写进`trie` ```go s.Finalise(deleteEmptyObjects) ``` 将trie根设置为当前的根哈希并将给定的`object`写入到`trie`中 ```go obj.updateRoot(s.db) s.updateStateObject(obj) ``` 1.6 创建收据 ```go receipt := types.NewReceipt(root, failed, *usedGas) receipt.TxHash = tx.Hash() receipt.GasUsed = gas if msg.To() == nil { receipt.ContractAddress = crypto.CreateAddress(vmenv.Context.Origin, tx.Nonce()) } // Set the receipt logs and create a bloom for filtering receipt.Logs = statedb.GetLogs(tx.Hash()) receipt.Bloom = types.CreateBloom(types.Receipts{receipt}) receipt.BlockHash = statedb.BlockHash() receipt.BlockNumber = header.Number receipt.TransactionIndex = uint(statedb.TxIndex()) ``` ②:最后完成区块,应用任何共识引擎特定的额外功能(例如区块奖励) ```go p.engine.Finalize(p.bc, header, statedb, block.Transactions(), block.Uncles()) ``` ```go func (ethash *Ethash) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header) { // Accumulate any block and uncle rewards and commit the final state root //累积任何块和叔叔的奖励并提交最终状态树根 accumulateRewards(chain.Config(), state, header, uncles) header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number)) } ``` 到此为止`bc.processor.Process`执行完毕,返回`receipts`. ------ ### 校验状态 大致包括4部分的校验: ①:校验使用的`gas`是否相等 ```go if block.GasUsed() != usedGas { return fmt.Errorf("invalid gas used (remote: %d local: %d)", block.GasUsed(), usedGas) } ``` ②:校验bloom是否相等 ```go rbloom := types.CreateBloom(receipts) if rbloom != header.Bloom { return fmt.Errorf("invalid bloom (remote: %x local: %x)", header.Bloom, rbloom) } ``` ③:校验收据哈希是否相等 ```go receiptSha := types.DeriveSha(receipts) if receiptSha != header.ReceiptHash { return fmt.Errorf("invalid receipt root hash (remote: %x local: %x)", header.ReceiptHash, receiptSha) } ``` ④:校验merkleroot 是否相等 ```go if root := statedb.IntermediateRoot(v.config.IsEIP158(header.Number)); header.Root != root { return fmt.Errorf("invalid merkle root (remote: %x local: %x)", header.Root, root) } ``` ----- ### 将块和关联状态写入到数据库 函数:**WriteBlockWithState** ①:计算块的`total td` ```go ptd := bc.GetTd(block.ParentHash(), block.NumberU64()-1) ``` ②:添加待插入块本身的`td` ,并将此时最新的`total td` 存储到数据库中。 ```GO bc.hc.WriteTd(block.Hash(), block.NumberU64(), externTd) ``` ③:将块的`header`和`body`分别序列化到数据库 ```go rawdb.WriteBlock(bc.db, block) ->WriteBody(db, block.Hash(), block.NumberU64(), block.Body()) ->WriteHeader(db, block.Header()) ``` ④:将状态写入底层内存`Trie`数据库 ```go state.Commit(bc.chainConfig.IsEIP158(block.Number())) ``` ⑤:遍历节点数据写入到磁盘 ```go triedb.Commit(header.Root, true) ``` ⑥:存储一个块的所有交易数据 ```go rawdb.WriteReceipts(batch, block.Hash(), block.NumberU64(), receipts) ``` ⑦:将新的`head`块注入到当前链中 ```go if status == CanonStatTy { bc.insert(block) } ``` - 存储分配给规范块的哈希 - 存储头块的哈希 - 存储最新的快 - 更新`currentFastBlock` ⑧:发送`chainEvent`事件或者`ChainSideEvent`事件或者`ChainHeadEvent`事件 ```go if status == CanonStatTy { bc.chainFeed.Send(ChainEvent{Block: block, Hash: block.Hash(), Logs: logs}) if len(logs) > 0 { bc.logsFeed.Send(logs) } if emitHeadEvent { bc.chainHeadFeed.Send(ChainHeadEvent{Block: block}) } } else { bc.chainSideFeed.Send(ChainSideEvent{Block: block}) } ``` 到此writeBlockWithState 结束,从上面可以知道,insertChain的最终还是调用了`writeBlockWithState`的insert方法完成了最终的上链入库动作。 最后整个`insertChain` *函数,如果已经完成了插入,就发送`chain head`事件* ```go defer func() { if lastCanon != nil && bc.CurrentBlock().Hash() == lastCanon.Hash() { bc.chainHeadFeed.Send(ChainHeadEvent{lastCanon}) } }() ``` 比较常见的有这么几处会进行订阅`chain head` 事件: 1. 在tx_pool.go中,收到此事件会进行换head的操作 ```go pool.chainHeadSub = pool.chain.SubscribeChainHeadEvent(pool.chainHeadCh) ``` 2. 在worker.go中,其他节点的矿工收到此事件就会停止当前的挖矿,继续下一个挖矿任务 ```go worker.chainHeadSub = eth.BlockChain().SubscribeChainHeadEvent(worker.chainHeadCh) ``` 到此整个区块上链入库就完成了,最后再送上一张总结的图: ![image-20201224104046731](https://img.learnblockchain.cn/2021/03/02_/839807798.jpg) ----- ## 参考 > 公号:区块链技术栈 (推荐) > > https://github.com/blockchainGuide (推荐)
死磕以太坊源码分析之区块上链入库
配合以下代码进行阅读:https://github.com/blockchainGuide/
写文不易,给个小关注,有什么问题可以指出,便于大家交流学习。
引言
不管是矿工挖矿还是Fetcher
同步,Downloader
同步,或者是导入本地文件等等,最中都是将区块上链入库。接下来我们就详细分析这部分的动作。
几处可能调用的地方
①:在Downloader同步最后会将区块插入到区块链中
func (d *Downloader) importBlockResults(results []*fetchResult) error {
...
if index, err := d.blockchain.InsertChain(blocks); err != nil {
....
}
}
②:创建一个新的以太坊协议管理器,也会将区块插入到链中
func NewProtocolManager(...) (*ProtocolManager, error) {
...
n, err := manager.blockchain.InsertChain(blocks)
}
③:插入侧链数据
func (bc *BlockChain) insertSideChain(block *types.Block, it *insertIterator) (int, error) {
...
if _, err := bc.insertChain(blocks, false); err != nil {
....
}
}
④:从本地文件导入链
func (api *PrivateAdminAPI) ImportChain(file string) (bool, error) {
if _, err := api.eth.BlockChain().InsertChain(blocks); err != nil {
....
}
}
⑤:fetcher同步导入块
func (f *Fetcher) insert(peer string, block *types.Block) {
...
if _, err := f.insertChain(types.Blocks{block}); err != nil {
...
}
}
以上就是比较常见的需要将区块上链的动作。调用的核心方法就是:
func (bc *BlockChain) insertChain(chain types.Blocks, verifySeals bool) (int, error) {}
获取区块链所有相关文章以及资料,请参阅:https://github.com/blockchainGuide/
插入数据到blockchain中
①:如果链正在中断,直接返回
if atomic.LoadInt32(&bc.procInterrupt) == 1 {
return 0, nil
}
②:开启并行的签名恢复
senderCacher.recoverFromBlocks(types.MakeSigner(bc.chainConfig, chain[0].Number()), chain)
③:开启并行校验header
abort, results := bc.engine.VerifyHeaders(bc, headers, seals)
校验header
是共识引擎所要做的事情,我们这里只分析ethash
它的实现。
func (ethash *Ethash) VerifyHeaders(chain consensus.ChainReader, headers []*types.Header, seals []bool) (chan<- struct{}, <-chan error) {
....
errors[index] = ethash.verifyHeaderWorker(chain, headers, seals, index)
}
func (ethash *Ethash) verifyHeaderWorker(chain consensus.ChainReader, headers []*types.Header, seals []bool, index int) error {
var parent *types.Header
if index == 0 {
parent = chain.GetHeader(headers[0].ParentHash, headers[0].Number.Uint64()-1)
} else if headers[index-1].Hash() == headers[index].ParentHash {
parent = headers[index-1]
}
if parent == nil {
return consensus.ErrUnknownAncestor
}
if chain.GetHeader(headers[index].Hash(), headers[index].Number.Uint64()) != nil {
return nil // known block
}
return ethash.verifyHeader(chain, headers[index], parent, false, seals[index])
}
首先会调用verifyHeaderWorker
进行校验,主要检验块的祖先是否已知以及块是否已知,接着会调用verifyHeader
进行更深的校验,也是最核心的校验,大概做了以下几件事:
- header.Extra不可超过32字节
- header.Time不能超过15秒,15秒以后的就被认定为未来的块
- 当前header的时间戳不可以等于父块的时间戳
- 根据难度计算算法得出的expected必须和header.Difficulty 一致。
- Gas limit 要 <= 2 ^ 63-1
- gasUsed<= gasLimit
- Gas limit 要在允许范围内
- 块号必须是父块加1
- 根据 ethash.VerifySeal去验证块是否满足POW难度要求
到此验证header的事情就做完了。
④:循环校验body
block, err := it.next()
-> ValidateBody
-> VerifyUncles
包括以下错误:
- block已知
- uncle太多
- 重复的uncle
- uncle是祖先块
- uncle哈希不匹配
- 交易哈希不匹配
- 未知祖先
- 祖先块的状态无法获取
4.1 如果block
存在,且是已知块,则写入已知块。
bc.writeKnownBlock(block)
4.2 如果是祖先块的状态无法获取的错误,则作为侧链插入:
bc.insertSideChain(block, it)
4.3 如果是未来块或者未知祖先,则添加未来块:
bc.addFutureBlock(block);
注意这里的添加 futureBlock,会被扔进futureBlocks里面去,在NewBlockChain的时候会开启新的goroutine:
go bc.update()
func (bc *BlockChain) update() {
futureTimer := time.NewTicker(5 * time.Second)
for{
select{
case <-futureTimer.C:
bc.procFutureBlocks()
}
}
}
func (bc *BlockChain) procFutureBlocks() {
...
for _, hash := range bc.futureBlocks.Keys() {
if block, exist := bc.futureBlocks.Peek(hash); exist {
blocks = append(blocks, block.(*types.Block))
}
}
...
for i := range blocks {
bc.InsertChain(blocks[i : i+1])
}
}
}
会开启一个计时器,每5秒就会去执行插入这些未来的块。
4.4 如果是其他错误,直接中断,并且报告坏块。
bc.futureBlocks.Remove(block.Hash())
...
bc.reportBlock(block, nil, err)
⑤:没有校验错误
5.1 如果是坏块,则报告;
if BadHashes[block.Hash()] {
bc.reportBlock(block, nil, ErrBlacklistedHash)
return it.index, ErrBlacklistedHash
}
5.2 如果是未知块,则写入未知块;
if err == ErrKnownBlock {
logger := log.Debug
if bc.chainConfig.Clique == nil {
logger = log.Warn
}
...
if err := bc.writeKnownBlock(block); err != nil {
return it.index, err
}
stats.processed++
lastCanon = block
continue
}
5.3 根据给定trie,创建状态;
parent := it.previous()
if parent == nil {
parent = bc.GetHeader(block.ParentHash(), block.NumberU64()-1)
}
statedb, err := state.New(parent.Root, bc.stateCache)
5.4执行块中的交易: (稍后会在下节对此进行详细分析)
receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig)
5.5 使用默认的validator校验状态:
bc.validator.ValidateState(block, statedb, receipts, usedGas);
5.6 将块写入到区块链中并获取状态: (稍后会在下节对此进行详细分析)
status, err := bc.writeBlockWithState(block, receipts, logs, statedb, false)
⑥:校验写入区块的状态
CanonStatTy
: 插入成功新的blockSideStatTy
:插入成功新的分叉区块Default
:插入未知状态的block
⑦:如果还有块,并且是未来块的话,那么将块添加到未来块的缓存中去
bc.addFutureBlock(block)
至此insertChain
大概介绍清楚。
执行块中交易
在我们将区块上链,有一个关键步骤就是执行区块交易:
receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig)
进入函数,具体分析:
①:准备要用的字段,循环执行交易
关键函数:ApplyTransaction
,根据此函数返回收据。
1.1 将交易结构转成Message
结构
msg, err := tx.AsMessage(types.MakeSigner(config, header.Number))
1.2 创建要在EVM环境中使用的新上下文
context := NewEVMContext(msg, header, bc, author)
1.3 创建一个新环境,其中包含有关事务和调用机制的所有相关信息。
vmenv := vm.NewEVM(context, statedb, config, cfg)
1.4 将交易应用到当前状态(包含在env中)
_, gas, failed, err := ApplyMessage(vmenv, msg, gp)
这部分代码继续跟进:
func ApplyMessage(evm *vm.EVM, msg Message, gp *GasPool) ([]byte, uint64, bool, error) {
return NewStateTransition(evm, msg, gp).TransitionDb()
}
NewStateTransition
是一个状态转换对象,TransitionDb()
负责转换交易状态,继续跟进: 先进行preCheck
,用来校验nonce
是否正确
st.preCheck()
if st.msg.CheckNonce() {
nonce := st.state.GetNonce(st.msg.From())
if nonce < st.msg.Nonce() {
return ErrNonceTooHigh
} else if nonce > st.msg.Nonce() {
return ErrNonceTooLow
}
}
计算所需gas
:
gas, err := IntrinsicGas(st.data, contractCreation, homestead, istanbul)
扣除gas
:
if err = st.useGas(gas); err != nil {
return nil, 0, false, err
}
func (st *StateTransition) useGas(amount uint64) error {
if st.gas < amount {
return vm.ErrOutOfGas
}
st.gas -= amount
return nil
}
如果是合约交易,则新建一个合约
ret, _, st.gas, vmerr = evm.Create(sender, st.data, st.gas, st.value)
如果不是合约交易,则增加nonce
st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1)
ret, st.gas, vmerr = evm.Call(sender, st.to(), st.data, st.gas, st.value)
重点关注evm.call
方法:
检查账户是否有足够的气体进行转账
if !evm.Context.CanTransfer(evm.StateDB, caller.Address(), value) {
return nil, gas, ErrInsufficientBalance
}
如果stateDb不存在此账户,则新建账户
if !evm.StateDB.Exist(addr) {
evm.StateDB.CreateAccount(addr)
}
执行转账操作
evm.Transfer(evm.StateDB, caller.Address(), to.Address(), value)
创建合约
contract := NewContract(caller, to, value, gas)
执行合约
ret, err = run(evm, contract, input, false)
添加余额
st.state.AddBalance(st.evm.Coinbase, new(big.Int).Mul(new(big.Int).SetUint64(st.gasUsed()), st.gasPrice))
回到ApplyTransaction
1.5 调用IntermediateRoot
计算状态trie
的当前根哈希值。
最终确定所有肮脏的存储状态,并把它们写进trie
s.Finalise(deleteEmptyObjects)
将trie根设置为当前的根哈希并将给定的object
写入到trie
中
obj.updateRoot(s.db)
s.updateStateObject(obj)
1.6 创建收据
receipt := types.NewReceipt(root, failed, *usedGas)
receipt.TxHash = tx.Hash()
receipt.GasUsed = gas
if msg.To() == nil {
receipt.ContractAddress = crypto.CreateAddress(vmenv.Context.Origin, tx.Nonce())
}
// Set the receipt logs and create a bloom for filtering
receipt.Logs = statedb.GetLogs(tx.Hash())
receipt.Bloom = types.CreateBloom(types.Receipts{receipt})
receipt.BlockHash = statedb.BlockHash()
receipt.BlockNumber = header.Number
receipt.TransactionIndex = uint(statedb.TxIndex())
②:最后完成区块,应用任何共识引擎特定的额外功能(例如区块奖励)
p.engine.Finalize(p.bc, header, statedb, block.Transactions(), block.Uncles())
func (ethash *Ethash) Finalize(chain consensus.ChainReader, header *types.Header, state *state.StateDB, txs []*types.Transaction, uncles []*types.Header) {
// Accumulate any block and uncle rewards and commit the final state root
//累积任何块和叔叔的奖励并提交最终状态树根
accumulateRewards(chain.Config(), state, header, uncles)
header.Root = state.IntermediateRoot(chain.Config().IsEIP158(header.Number))
}
到此为止bc.processor.Process
执行完毕,返回receipts
.
校验状态
大致包括4部分的校验:
①:校验使用的gas
是否相等
if block.GasUsed() != usedGas {
return fmt.Errorf("invalid gas used (remote: %d local: %d)", block.GasUsed(), usedGas)
}
②:校验bloom是否相等
rbloom := types.CreateBloom(receipts)
if rbloom != header.Bloom {
return fmt.Errorf("invalid bloom (remote: %x local: %x)", header.Bloom, rbloom)
}
③:校验收据哈希是否相等
receiptSha := types.DeriveSha(receipts)
if receiptSha != header.ReceiptHash {
return fmt.Errorf("invalid receipt root hash (remote: %x local: %x)", header.ReceiptHash, receiptSha)
}
④:校验merkleroot 是否相等
if root := statedb.IntermediateRoot(v.config.IsEIP158(header.Number)); header.Root != root {
return fmt.Errorf("invalid merkle root (remote: %x local: %x)", header.Root, root)
}
将块和关联状态写入到数据库
函数:WriteBlockWithState
①:计算块的total td
ptd := bc.GetTd(block.ParentHash(), block.NumberU64()-1)
②:添加待插入块本身的td
,并将此时最新的total td
存储到数据库中。
bc.hc.WriteTd(block.Hash(), block.NumberU64(), externTd)
③:将块的header
和body
分别序列化到数据库
rawdb.WriteBlock(bc.db, block)
->WriteBody(db, block.Hash(), block.NumberU64(), block.Body())
->WriteHeader(db, block.Header())
④:将状态写入底层内存Trie
数据库
state.Commit(bc.chainConfig.IsEIP158(block.Number()))
⑤:遍历节点数据写入到磁盘
triedb.Commit(header.Root, true)
⑥:存储一个块的所有交易数据
rawdb.WriteReceipts(batch, block.Hash(), block.NumberU64(), receipts)
⑦:将新的head
块注入到当前链中
if status == CanonStatTy {
bc.insert(block)
}
- 存储分配给规范块的哈希
- 存储头块的哈希
- 存储最新的快
- 更新
currentFastBlock
⑧:发送chainEvent
事件或者ChainSideEvent
事件或者ChainHeadEvent
事件
if status == CanonStatTy {
bc.chainFeed.Send(ChainEvent{Block: block, Hash: block.Hash(), Logs: logs})
if len(logs) > 0 {
bc.logsFeed.Send(logs)
}
if emitHeadEvent {
bc.chainHeadFeed.Send(ChainHeadEvent{Block: block})
}
} else {
bc.chainSideFeed.Send(ChainSideEvent{Block: block})
}
到此writeBlockWithState 结束,从上面可以知道,insertChain的最终还是调用了writeBlockWithState
的insert方法完成了最终的上链入库动作。
最后整个insertChain
函数,如果已经完成了插入,就发送chain head
事件
defer func() {
if lastCanon != nil && bc.CurrentBlock().Hash() == lastCanon.Hash() {
bc.chainHeadFeed.Send(ChainHeadEvent{lastCanon})
}
}()
比较常见的有这么几处会进行订阅chain head
事件:
-
在tx_pool.go中,收到此事件会进行换head的操作
pool.chainHeadSub = pool.chain.SubscribeChainHeadEvent(pool.chainHeadCh)
-
在worker.go中,其他节点的矿工收到此事件就会停止当前的挖矿,继续下一个挖矿任务
worker.chainHeadSub = eth.BlockChain().SubscribeChainHeadEvent(worker.chainHeadCh)
到此整个区块上链入库就完成了,最后再送上一张总结的图:
参考
公号:区块链技术栈 (推荐)
https://github.com/blockchainGuide (推荐)
区块链技术网。
- 发表于 2021-03-02 08:50
- 阅读 ( 633 )
- 学分 ( 21 )
- 分类:以太坊
评论