死磕以太坊源码分析之区块上链入库

这篇文章主要介绍了死磕以太坊源码分析之区块上链入库 ,文中通过代码以及文档配合进行讲解,很详细,它对在座的每个人的研究和工作具有很经典的参考价值。 如果需要,让我们与区块链资料网一起学习。

https://www.interchains.cc/23126.html

死磕以太坊源码分析之区块上链入库是很好的区块链资料,他说明了区块链当中的经典原理,可以给我们提供资料,死磕以太坊源码分析之区块上链入库学习起来其实是很简单的,

不多的几个较为抽象的概念也很容易理解,之所以很多人感觉死磕以太坊源码分析之区块上链入库比较复杂,一方面是因为大多数的文档没有做到由浅入深地讲解,概念上没有注意先后顺序,给读者的理解带来困难

死磕以太坊eth源码分析之区块上链入库

死磕以太坊eth源码分析之区块上链入库

死磕以太坊eth源码分析之区块上链入库

配合以下代码进行阅读:https://github.com/blockchainGuide/

写文不易,给个小关注,有什么问题可以指出,便于大家交流学习。

引言

不管是矿工挖矿还是Fetcher同步,Downloader同步,或者是导入本地文件等等,最中都是将区块上链入库。接下来我们就详细分析这部分的动作。

几处可能调用的地方

①:在Downloader同步最后会将区块插入到区块链blockchain

func (d *Downloader) importBlockResults(results []*fetchResult) error {   ...   if index, err := d.blockchain.InsertChain(blocks); err != nil {     ....   } }

②:创建一个新的以太坊eth协议管理器,也会将区块插入到链中

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) {}

获取区块链blockchain所有相关文章以及资料,请参阅: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进行更深的校验,也是最核心的校验,大概做了以下几件事:

  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

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 &lt;-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 将块写入到区块链blockchain中并获取状态: (稍后会在下节对此进行详细分析)

status, err := bc.writeBlockWithState(block, receipts, logs, statedb, false)

⑥:校验写入区块的状态

  • CanonStatTy : 插入成功新的block
  • SideStatTy:插入成功新的分叉区块
  • 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 &lt; 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 &lt; 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)

③:将块的headerbody分别序列化到数据库

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 事件:

  1. 在tx_pool.go中,收到此事件会进行换head的操作

    pool.chainHeadSub = pool.chain.SubscribeChainHeadEvent(pool.chainHeadCh)
  2. 在worker.go中,其他节点的矿工收到此事件就会停止当前的挖矿,继续下一个挖矿任务

    worker.chainHeadSub = eth.BlockChain().SubscribeChainHeadEvent(worker.chainHeadCh)

到此整个区块上链入库就完成了,最后再送上一张总结的图:

死磕<a href=以太坊eth源码分析之区块上链入库” />


参考

公号:区块链blockchain技术栈 (推荐)

https://github.com/blockchainGuide (推荐)

部分转自网络,侵权联系删除www.interchains.cchttps://www.interchains.cc/23126.html

区块链毕设网(www.interchains.cc)全网最靠谱的原创区块链毕设代做网站 部分资料来自网络,侵权联系删除! 最全最大的区块链源码站 ! QQ3039046426
区块链知识分享网, 以太坊dapp资源网, 区块链教程, fabric教程下载, 区块链书籍下载, 区块链资料下载, 区块链视频教程下载, 区块链基础教程, 区块链入门教程, 区块链资源 » 死磕以太坊源码分析之区块上链入库

提供最优质的资源集合

立即查看 了解详情