1
1.1 writeKnownBlock
func (bc *BlockChain) writeKnownBlock(tx kv.RwTx, block block2.IBlock) error {
writeKnownBlock使用已知块更新头块标志并在必要时引入链式重组。
- 定义非外部事务标记notExternalTx
- 如果事务tx为nil,就创建一个事务
bc.ChainDB.BeginRw(bc.ctx)
,有错就返回err。使用延迟回滚,并将notExternalTx标记为true - 获得当前区块
current := bc.CurrentBlock()
- 判断传入的区块对象的父哈希值是否与当前区块的哈希值相同。如果不同,则调用
bc.reorg(tx, current, block)
(参考1.2)进行重组织操作。如果重组织失败,则返回错误信息 - 如果相同,就不需要reorg。调用
bc.writeHeadBlock(tx, block)
将区块写入区块链头部。如果写入失败,则返回错误信息 - 最后,如果是内部事务(即notExternalTx为true),则提交事务
tx.Commit()
。如果提交失败,则返回错误信息。
整个函数执行完毕后,返回nil表示成功
1.2 reorg
func (bc *BlockChain) reorg(tx kv.RwTx, oldBlock, newBlock block2.IBlock) error {
reorg获取两个块,一个旧链和一个新链,并将重构这些块并将它们插入到新规范链中,积累潜在的丢失事务并发布有关它们的事件。
注意,这里不会处理新的头块,调用者需要从外部处理它。
当两个区块链之间的差异较大时,这个函数会被调用来合并它们。
- 将较长的链减少到与较短的链相同的数量,所以要确定新链与旧链中更长的那一条链。将较长的链中的数据记录为已删除, 将较短的链存储起来以便后续插入
- 如果旧链较长
if oldBlock.Number64().Uint64() > newBlock.Number64().Uint64()
,将所有事务和日志收集为已删除的事务和日志,不断向前遍历(在不与新链相同且不为nil的情况下),将区块加入lodchain中,并且将每一个区块中的交易都记录为deleted:deletedTxs = append(deletedTxs, hash)
- 如果新链较长,将所有区块都藏起来以备后续插入,也就是不断向前遍历(在不与旧链相同且不为nil的情况下),将区块加入newchain中
- 如果有一个区块为Nil,就会输出对应的错误信息
- 在事务上:如果事务为nil,就通过
bc.ChainDB.BeginRw(bc.ctx)
开启一个事务,有错就返回err,使用延迟回滚。同时将非外部事务标志useExternalTx
标记为false - 找到两个链的共同祖先,并减少两个链,直到找到公共祖先:
- 如果新旧块的hash一样,就代表找到了共同祖先commonblock,就跳出for循环
- 否则就一直执行,移除一个旧区块
deletedTxs = append(deletedTxs, h)
同时记录一个新区块newChain = append(newChain, newBlock)
- 双链都向后退,使用
rawdb.ReadBlock
- 会输出reorg的提示信息
- 如果找到了公共祖先,函数会将新的链(除了头块)插入到区块链中
bc.writeHeadBlock(tx, newChain[i])
,并收集新添加的交易addedTxs = append(addedTxs, h)
,注意添加顺序i := len(newChain) - 1
- 删除无用的索引,包括非规范事务索引和高于头块的规范链索引
rawdb.DeleteTxLookupEntry(tx, t)
- 删除所有不是新规范链一部分的哈希标记,其中通过
rawdb.ReadCanonicalHash
获取hash,通过rawdb.TruncateCanonicalHash
进行删除 - 如果没有使用外部事务,函数会提交事务
tx.Commit()
。如果在提交过程中发生错误,函数会返回错误。否则,它会返回nil表示成功完成重组织操作
1.3 Close
func (bc *BlockChain) Close() error {
bc.Quit()
return nil
}
调用Quit
来进行退出(参考1.4)
1.4 Quit
func (bc *BlockChain) Quit() <-chan struct{} {
return bc.ctx.Done()
}
该方法返回一个类型为chan struct{}
的通道,该通道通过调用bc.ctx.Done()
方法来获取。
这个方法的作用是提供一个退出信号给BlockChain结构体的上下文(ctx),以便在需要时停止执行相关操作。当调用此方法时,它会返回一个通道,可以通过该通道向上下文发送一个信号,表示需要退出。
1.5 DB
func (bc *BlockChain) DB() kv.RwDB {
return bc.ChainDB
}
用该函数来返回一个数据库,用于执行数据库的操作
1.6 GetDepositInfo
func (bc *BlockChain) GetDepositInfo(address types.Address) (*uint256.Int, *uint256.Int) {
var info *deposit.Info
bc.ChainDB.View(bc.ctx, func(tx kv.Tx) error {
info = deposit.GetDepositInfo(tx, address)
return nil
})
if nil == info {
return nil, nil
}
return info.RewardPerBlock, info.MaxRewardPerEpoch
}
- 声明了一个名为info的指针变量
- 使用
bc.ChainDB.View
方法来访问区块链数据库,并在回调函数中获取指定地址的存款信息。回调函数通过调用deposit.GetDepositInfo
方法来获取存款信息,并将结果赋值给info变量 - 如果info为nil,则返回两个nil值
- 否则,返回存款信息中的奖励每块和每个周期的最大奖励。
1.7 GetAccountRewardUnpaid
func (bc *BlockChain) GetAccountRewardUnpaid(account types.Address) (*uint256.Int, error) {
var value *uint256.Int
var err error
bc.ChainDB.View(bc.ctx, func(tx kv.Tx) error {
value, err = rawdb.GetAccountReward(tx, account)
return nil
})
return value, err
}
该函数的作用是获取指定账户未支付的奖励金额。
- 函数内部首先声明了一个变量
value
,用于存储未支付的奖励金额,以及一个变量err
,用于存储错误信息。 - 通过调用
bc.ChainDB.View
方法来访问区块链数据库。在回调函数中,使用rawdb包中的rawdb.GetAccountReward
方法从数据库中获取指定账户的未支付奖励金额,并将其赋值给value
变量。同时,将错误信息赋值给err
变量 - 函数返回
value
和err
作为结果。
2、验证
type BlockValidator struct {
bc *BlockChain // Canonical block chain
engine consensus.Engine // Consensus engine used for validating
config *params.ChainConfig
}
这是一个名为BlockValidator的结构体,它包含以下字段:
bc
:指向BlockChain类型的指针,表示规范的区块链。engine
:consensus.Engine类型的变量,表示用于验证共识引擎。config
:指向params.ChainConfig类型的指针,表示链的配置参数。
2.1 NewBlockValidator
func NewBlockValidator(config *params.ChainConfig, blockchain *BlockChain, engine consensus.Engine) *BlockValidator {
validator := &BlockValidator{
engine: engine,
bc: blockchain,
config: config,
}
return validator
}
该函数用于初始化一个验证器
2.2 ValidateBody
func (v *BlockValidator) ValidateBody(b block.IBlock) error {
ValidateBody验证给定块的叔叔,并验证块头的事务和叔叔根。假设此时已经对标头进行了验证。
- 检查区块的签名是否有效:
- 首先获取区块的验证者列表
vfs := b.Body().Verifier()
- 遍历每个验证者,
bls.PublicKeyFromBytes
创建一个公钥,PublicKeyFromBytes从BigEndian字节切片创建BLS公钥。并将其存储在ss数组中ss[i] = blsP
- 如果配置为
v.config.IsBeijing(b.Number64().Uint64())
,(IsBeijing返回num是否等于IsBeijingfork块或更大)则使用bls.SignatureFromBytes
方法从从LittleEndian字节切片创建BLS签名,并使用FastAggregateVerify
方法验证签名是否有效。如果验证失败,记录日志并返回错误。
- 检查区块是否已知
bc.HasBlockAndState
。如果区块链中已经存在该区块及其状态,则返回ErrKnownBlock
错误。 - 检查交易根哈希是否匹配。计算区块中所有交易的SHA-256哈希值,并与区块头中的交易根哈希进行比较
if hash := DeriveSha
。如果不匹配,则返回错误 - 通过
bc.HasBlockAndState
检查父区块是否存在
- 如果区块链中不存在父区块及其状态,则根据父区块是否在区块链中
v.bc.HasBlock
,返回不同的错误 - 如果父区块存在但已被修剪(即被删除),则返回
ErrPrunedAncestor
错误;否则返回ErrUnknownAncestor
错误。
如果以上所有检查都通过,则返回nil表示区块有效。
2.3 ValidateState
func (v *BlockValidator) ValidateState(iBlock block.IBlock, statedb *state.IntraBlockState, receipts block.Receipts, usedGas uint64) error {
ValidateState验证状态转换后发生的各种变化,如gasused、接收根和状态根本身。如果验证成功,ValidateState将返回数据库批处理,否则为零并返回错误。
ValidateState用于验证区块的状态。它接收四个参数:iBlock
(待验证的区块)、statedb
(状态数据库)、receipts
(交易收据)和usedGas
(已使用的gas)。
- 检查区块头中的
GasUsed
字段是否与传入的usedGas
相等。如果不相等,返回一个错误信息,表示远程和本地的gas使用量不一致。 - 创建一个布隆过滤器
block.CreateBloom(receipts)
,并将其与区块头中的Bloom
字段进行比较。如果两者不相等if rbloom != header.Bloom
,返回一个错误信息,表示远程和本地的布隆过滤器不一致。 - 计算交易收据的哈希值
receiptSha := DeriveSha(receipts)
,并将其与区块头中的ReceiptHash
字段进行比较。如果不相等,遍历区块中的所有交易,并记录相关信息,最后返回一个错误信息,表示远程和本地的交易收据根哈希不一致 - 验证状态根
receiptSha := DeriveSha(receipts)
是否与接收到的状态根相匹配。如果不匹配,返回一个错误信息,表示远程和本地的默克尔根不一致 - 如果所有验证都通过,方法返回
nil
3、blockchain_insert.go
type insertIterator struct {
chain []block2.IBlock // Chain of blocks being iterated over
results <-chan error // Verification result sink from the consensus engine
errors []error // Header verification errors for the blocks
index int // Current offset of the iterator
validator Validator // Validator to run if verification succeeds
}
这是一个名为insertIterator
的结构体,它包含以下字段:
chain []block2.IBlock
:一个block2.IBlock
类型的切片,表示正在迭代的区块链。results <-chan error
:一个错误类型的通道,用于从共识引擎接收验证结果。errors []error
:一个错误类型的切片,用于存储区块头验证错误。index int
:一个整数,表示迭代器的当前偏移量,索引值validator Validator
:一个Validator
类型的变量,如果验证成功,将运行此验证器。
3.1 newInsertIterator
func newInsertIterator(chain []block2.IBlock, results <-chan error, validator Validator) *insertIterator {
return &insertIterator{
chain: chain,
results: results,
errors: make([]error, 0, len(chain)),
index: -1,
validator: validator,
}
}
该函数用于创建一个迭代器,基于给定的块创建一个新的迭代器。
3.2 next
func (it *insertIterator) next() (block2.IBlock, error) {
next返回迭代器中的下一个块,以及该块的任何潜在验证错误。当到达终点时,它将返回(nil,nil)。在循环迭代器中有很大的用处
该方法的作用是获取下一个区块并进行验证。
- 检查是否已经到达了区块链的末尾
if it.index+1 >= len(it.chain)
,如果是,则将索引设置为链的长度并返回空值和nil错误 - 否则,索引递增,并检查当前索引是否有错误
- 如果存在错误
if it.errors[it.index] != nil
,则直接返回该区块和错误信息 - 如果没有错误,则调用
it.validator.ValidateBody
方法对区块的主体进行验证,并返回验证后的区块和验证结果。
3.3 peek
func (it *insertIterator) peek() (block2.IBlock, error) {
peek返回迭代器中的下一个块,以及该块的任何潜在验证错误,但不会推进迭代器。
头和正文验证错误(也为nil)都缓存到迭代器中,以避免重复以下next()调用的工作。该方法的作用是预览下一个区块(block)以及可能的错误信息。
方法的实现逻辑如下:
- 检查是否已经到达了区块链的末尾
if it.index+1 >= len(it.chain)
,如果是,则返回空nil,表示没有下一个区块可以预览。 - 如果当前索引还没有到达链表的末尾,那么需要等待验证结果
- 等待结果,将验证结果添加到错误列表中
it.errors = append(it.errors, <-it.results)
- 如果当前索引对应的错误信息是否不为nil,说明验证失败,直接返回该索引对应的区块和错误信息
- 如果当前索引对应的错误信息为nil,说明验证成功,忽略主体验证(因为我们没有父区块),直接返回该索引对应的区块和nil错误
return it.chain[it.index+1], nil
3.4 previous
func (it *insertIterator) previous() block2.IHeader {
if it.index < 1 {
return nil
}
return it.chain[it.index-1].Header()
}
previor返回正在处理的前一个标头return it.chain[it.index-1].Header()
,或nil(如果当前Index<1)
3.5 current
func (it *insertIterator) current() block2.IHeader {
if it.index == -1 || it.index >= len(it.chain) {
return nil
}
return it.chain[it.index].Header()
}
current返回正在处理的当前标头it.chain[it.index].Header()
,或nil(如果当前Index=-1
太靠前,或者index已经超过链的长度了it.index >= len(it.chain)
)
3.6 first
func (it *insertIterator) first() block2.IBlock {
return it.chain[0]
}
该函数返回第一个区块return it.chain[0]
3.7 remaining
func (it *insertIterator) remaining() int {
return len(it.chain) - it.index
}
remaining返回剩余块的数量。通过总长度减去当前索引值得到剩余的区块数量
3.8 processed
func (it *insertIterator) processed() int {
return it.index + 1
}
该函数返回已经处理过的区块的数量,return it.index + 1
为当前Index+1