blockchain核心 | Word Count: 4.1k | Reading Time: 16mins | Post Views:
交易的签名 理解收据receipt 理解区块 理解交易 blockchain核心 布隆过滤器原理 forkId 解读 oracle 原理和实现 交易池分析 TxList 解读 MPT树 区块同步 geth源码学习——介绍 How Geth starts its server
参考: 以太坊 blockchain 源码分析 - mindcarver - 博客园 (cnblogs.com)
主要部分转载:以太坊 blockchain 源码分析
blockchain 关键元素
db:持久化到底层数据储存,即 leveldb(注意不是 MySQL)(源码core/rawdb
);(参考文章:)Leveldb 基本介绍和使用指南 - 知乎 (zhihu.com) 以及对应的百科 [LevelDB_百度百科 (baidu.com )](https://baike.baidu.com/item/LevelDB/6416354#:~:text= Leveldb 是一个 google 实现的非常高效的 kv 数据库,目前的版本 1.2 能够支持 billion 级别的数据量了。 在这个数量级别下还有着非常高的性能,主要归功于它的良好的设计。,特别是 LSM 算法。 [1] LevelDB 是单进程的服务,性能非常之高,在一台 4 核 Q6600 的 CPU 机器上,每秒钟写数据超过 40w,而随机读的性能每秒钟超过 10w。)
genesisBlock:创始区块
currentBlock:当前区块,blockchain 中并不是储存链所有的 block,而是通过 currentBlock 向前回溯直到 genesisBlock,这样就构成了区块链
bodyCache、bodyRLPCache、blockCache、futureBlocks:区块链中的缓存结构,用于加快区块链的读取和构建;
hc:headerchain 区块头链,由 blockchain 额外维护的另一条链,由于 Header 和 Block 的储存空间是有很大差别的,但同时 Block 的 Hash 值就是 Header(RLP)的 Hash 值,所以维护一个 headerchain 可以用于快速延长链,验证通过后再下载 blockchain,或者可以与 blockchain 进行相互验证;
processor:执行区块链交易的接口,收到一个新的区块时,要对区块中的所有交易执行一遍,一方面是验证,一方面是更新世界状态;
validator:验证数据有效性的接口
futureBlocks:收到的区块时间大于当前头区块时间 15s 而小于 30s 的区块,可作为当前节点待处理的区块。
函数介绍
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 func (bc *BlockChain) BadBlocks() []*types.Block {}func (bc *BlockChain) addBadBlock(block *types.Block) {}func (bc *BlockChain) CurrentBlock() *types.Block {}func (bc *BlockChain) CurrentHeader() *types.Header{}func (bc *BlockChain) CurrentFastBlock() *types.Block {}func (bc *BlockChain) Export(w io.Writer) error {}func (bc *BlockChain) ExportN(w io.Writer, first uint64 , last uint64 ) error {}func (bc *BlockChain) FastSyncCommitHead(hash common.Hash) error {}func (bc *BlockChain) GasLimit() uint64 {}func (bc *BlockChain) Genesis() *types.Block {}func (bc *BlockChain) GetBody(hash common.Hash) *types.Body {}func (bc *BlockChain) GetBodyRLP(hash common.Hash) rlp.RawValue {}func (bc *BlockChain) GetBlock(hash common.Hash, number uint64 ) *types.Block {}func (bc *BlockChain) GetBlockByHash(hash common.Hash) *types.Block {}func (bc *BlockChain) GetBlockByNumber(number uint64 ) *types.Block {}func (bc *BlockChain) GetHeader(hash common.Hash, number uint64 ) *types.Header{}func (bc *BlockChain) GetHeaderByHash(hash common.Hash) *types.Header{}func (bc *BlockChain) GetHeaderByNumber(number uint64 ) *types.Header{}func (bc *BlockChain) HasBlock(hash common.Hash, number uint64 ) bool {}func (bc *BlockChain) HasHeader(hash common.Hash, number uint64 ) bool {}func (bc *BlockChain) HasState(hash common.Hash) bool {}func (bc *BlockChain) HasBlockAndState(hash common.Hash, number uint64 ) bool {}func (bc *BlockChain) GetTd(hash common.Hash, number uint64 ) *big.Int{}func (bc *BlockChain) GetBlockHashesFromHash(hash common.Hash, max uint64 ) []common.Hash{}func (bc *BlockChain) GetReceiptsByHash(hash common.Hash) types.Receipts {}func (bc *BlockChain) GetBlocksFromHash(hash common.Hash, n int ) (blocks []*types.Block) {}func (bc *BlockChain) GetUnclesInChain(block *types.Block, length int ) []*types.Header {}func (bc *BlockChain) insert(block *types.Block) {}func (bc *BlockChain) InsertChain(chain types.Blocks) (int , error ){}func (bc *BlockChain) insertChain(chain types.Blocks) (int , []interface {}, []*types.Log, error ){}func (bc *BlockChain) InsertHeaderChain(chain []*types.Header, checkFreq int ) (int , error ){}func (bc *BlockChain) InsertReceiptChain(blockChain types.Blocks, receiptChain []types.Receipts) (int , error ) {}func (bc *BlockChain) loadLastState() error {}func (bc *BlockChain) Processor() Processor {}func (bc *BlockChain) Reset() error {}func (bc *BlockChain) ResetWithGenesisBlock(genesis *types.Block) error {}func (bc *BlockChain) repair(head **types.Block) error {}func (bc *BlockChain) reorg(oldBlock, newBlock *types.Block) error {}func (bc *BlockChain) Rollback(chain []common.Hash) {}func SetReceiptsData (config *params.ChainConfig, block *types.Block, receipts types.Receipts) error {}func (bc *BlockChain) SetHead(head uint64 ) error {}func (bc *BlockChain) SetProcessor(processor Processor) {}func (bc *BlockChain) SetValidator(validator Validator) {}func (bc *BlockChain) State() (*state.StateDB, error ) {}func (bc *BlockChain) StateAt(root common.Hash) (*state.StateDB, error ) {}func (bc *BlockChain) Stop() {}func (bc *BlockChain) TrieNode(hash common.Hash) ([]byte , error ) {}func (bc *BlockChain) Validator() Validator {}func (bc *BlockChain) WriteBlockWithoutState(block *types.Block, td *big.Int) (err error ){}func (bc *BlockChain) WriteBlockWithState(block *types.Block, receipts []*types.Receipt, state *state.StateDB) {}func (bc *BlockChain) writeHeader(header *types.Header) error {}func (bc *BlockChain) update() {}
blockchain 初始化(NewBlockChain)
主要步骤:
①:创建一个新的 headerChain 结构
1 bc.hc, err = NewHeaderChain(db, chainConfig, engine, bc.getProcInterrupt)
根据number(0)获取 genesisHeader
从rawdb 中读取 HeadBlock 并存储在 currentHeade r 中
②:获取 genesisBlock
1 bc.genesisBlock = bc.GetBlockByNumber(0 )
③:如果链不为空,则用老的链数据初始化链
1 2 3 if bc.empty() { rawdb.InitDatabaseFromFreezer(bc.db) }
④:加载最新的状态数据
1 2 3 if err := bc.loadLastState(); err != nil { return nil , err }
⑤:检查区块哈希的当前状态,并确保链中没有任何坏块
1 2 3 4 5 6 7 8 9 10 for hash := range BadHashes { if header := bc.GetHeaderByHash(hash); header != nil { headerByNumber := bc.GetHeaderByNumber(header.Number.Uint64()) if headerByNumber != nil && headerByNumber.Hash() == header.Hash() { log.Error("Found bad hash, rewinding chain" , "number" , header.Number, "hash" , header.ParentHash) bc.SetHead(header.Number.Uint64() - 1 ) log.Error("Chain rewind was successful, resuming normal operation" ) } } }
⑥:定时处理 future block
1 2 3 go bc.update() ->procFutureBlocks ->InsertChain
总的来说做了以下几件事:
配置 cacheConfig,创建各种 lru 缓存
初始化 triegc
初始化 stateDb:state.NewDatabase(db)
初始化区块和状态验证:NewBlockValidator()
初始化状态处理器:NewStateProcessor()
初始化区块头部链:NewHeaderChain()
查找创世区块:bc.genesisBlock = bc.GetBlockByNumber(0)
加载最新的状态数据:bc.loadLastState()
检查区块哈希的当前状态,并确保链中没有任何坏块
go bc.futureBlocksLoop ()定时处理 future block
加载区块链状态(locadLastState)
1:从 rawdb 数据库中恢复最新的 headblock,如果 rawdb 数据库空的话也就是没有读出来头部区块的话,触发 reset chain
1 2 3 4 5 6 head := rawdb.ReadHeadBlockHash(bc.db) if head == (common.Hash{}) { log.Warn("Empty database, resetting chain" ) return bc.Reset() }
2:通过头部 hash 获取头部区块,确保整个 head block 是可以获取的,若为空,则触发 reset chain
1 2 3 4 5 6 7 currentBlock := bc.GetBlockByHash(head) if currentBlock == nil { log.Warn("Head block missing, resetting chain" , "hash" , head) return bc.Reset() }
其中 GetBlockByHash(head)的方法是这样的:
1 2 3 4 5 6 7 8 9 func (bc *BlockChain) GetBlockByHash(hash common.Hash) *types.Block { number := bc.hc.GetBlockNumber(hash) if number == nil { return nil } return bc.GetBlock(hash, *number) }
3:存储当前的 headblock 和设置当前的 headHeader 以及头部 fast 块
1 2 3 4 5 bc.currentBlock.Store(currentBlock) .... bc.hc.SetCurrentHeader(currentHeader) ... bc.currentFastBlock.Store(currentBlock)
具体的store
函数的使用我写了一段代码作为示例帮助理解:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 package mainimport ( "fmt" "sync/atomic" ) func main () { var res atomic.Value res.Store("test1" ) fmt.Println(res) res.Store("test2" ) fmt.Println(res) }
证明里面存放的数据会进行覆盖,所以类似于这样的代码就好理解了:
1 2 3 4 5 6 7 8 9 10 11 12 bc.currentFastBlock.Store(currentBlock) headFastBlockGauge.Update(int64 (currentBlock.NumberU64())) if head := rawdb.ReadHeadFastBlockHash(bc.db); head != (common.Hash{}) { if block := bc.GetBlockByHash(head); block != nil { bc.currentFastBlock.Store(block) headFastBlockGauge.Update(int64 (block.NumberU64())) } }
获取难度值,具体难度值的计算请参考该篇文章:以太坊挖矿难度调整算法详解
1 2 3 4 headerTd := bc.GetTd(currentHeader.Hash(), currentHeader.Number.Uint64()) blockTd := bc.GetTd(currentBlock.Hash(), currentBlock.NumberU64()) fastTd := bc.GetTd(currentFastBlock.Hash(), currentFastBlock.NumberU64())
插入数据到 blockchain 中
①:如果链正在中断,直接返回
②:开启并行的签名恢复
③:校验 header
1 abort, results := bc.engine.VerifyHeaders(bc, headers, seals)
校验header
是共识引擎所要做的事情
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 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 } 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
1 2 3 block, err := it.next() -> ValidateBody -> VerifyUncles
包括以下错误:
block 已知
uncle 太多
重复的 uncle
uncle 是祖先块
uncle 哈希不匹配
交易哈希不匹配
未知祖先
祖先块的状态无法获取
如果 block 存在,且是已知块,则写入已知块。
如果是祖先块的状态无法获取的错误,则作为侧链插入:
1 bc.insertSideChain(block, it)
如果是未来块或者未知祖先,则添加未来块:
1 bc.addFutureBlock(block);
注意这里的添加 futureBlock,会被扔进 futureBlocks 里面去,在 NewBlockChain 的时候会开启新的 goroutine:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 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 秒就会去执行插入这些未来的块。
如果是其他错误,直接中断,并且报告坏块。
1 2 3 bc.futureBlocks.Remove(block.Hash()) ... bc.reportBlock(block, nil , err)
⑤:没有校验错误
如果是坏块,则报告;
1 2 3 4 5 6 if BadHashes[block.Hash()] { bc.reportBlock(block, nil , ErrBlacklistedHash) return it.index, ErrBlacklistedHash } ... }
如果是未知块,则写入未知块;
1 2 3 4 5 6 7 8 9 10 11 12 13 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 }
根据给定 trie,创建 state;
1 2 3 4 5 parent := it.previous() if parent == nil { parent = bc.GetHeader(block.ParentHash(), block.NumberU64()-1 ) } statedb, err := state.New(parent.Root, bc.stateCache)
执行块中的交易:
1 receipts, logs, usedGas, err := bc.processor.Process(block, statedb, bc.vmConfig)
1 bc.validator.ValidateState(block, statedb, receipts, usedGas);
1 status, err := bc.writeBlockWithState(block, receipts, logs, statedb, false )
⑥:校验写入区块的状态
CanonStatTy : 插入成功新的 block
SideStatTy:插入成功新的分叉区块
Default:插入未知状态的 block
⑦:如果还有块,并且是未来块的话,那么将块添加到未来块的缓存中去
1 bc.addFutureBlock(block)
至此 insertChain 大概介绍清楚。
将块和关联状态写入到数据库
函数:WriteBlockWithState
①:计算父块的 total td
1 ptd := bc.GetTd(block.ParentHash(), block.NumberU64()-1 )
②:添加待插入块本身的 td ,并将此时最新的 total td 存储到数据库中。
1 bc.hc.WriteTd(block.Hash(), block.NumberU64(), externTd)
③:将块的 header 和 body 分别序列化到数据库
1 2 3 rawdb.WriteBlock(bc.db, block) ->WriteBody(db, block.Hash(), block.NumberU64(), block.Body()) ->WriteHeader(db, block.Header())
④:将状态写入底层内存 Trie 数据库
1 state.Commit(bc.chainConfig.IsEIP158(block.Number()))
⑤:存储一个块的所有交易数据
1 rawdb.WriteReceipts(batch, block.Hash(), block.NumberU64(), receipts)
⑥:将新的 head 块注入到当前链中
1 2 3 if status == CanonStatTy { bc.insert(block) }
存储分配给规范块的哈希
存储头块的哈希
存储最新的快
更新 currentFastBlock
到此 writeBlockWithState 结束,从上面可以知道,insertChain 的最终还是调用了 writeBlockWithState 的 insert 方法完成了最终的插入动作。