1、BlockChain
type BlockChain struct {
chainConfig *params.ChainConfig
ctx context.Context
cancel context.CancelFunc
genesisBlock block2.IBlock
blocks []block2.IBlock
headers []block2.IHeader
currentBlock atomic.Pointer[block2.Block]
//state *statedb.StateDB
ChainDB kv.RwDB
engine consensus.Engine
insertLock chan struct{}
latestBlockCh chan block2.IBlock
lock sync.Mutex
peers map[peer.ID]bool
chBlocks chan block2.IBlock
p2p p2p.P2P
errorCh chan error
process Processor
wg sync.WaitGroup //
procInterrupt int32 // insert chain
futureBlocks *lru.Cache[types.Hash, *block2.Block]
receiptCache *lru.Cache[types.Hash, []*block2.Receipt]
blockCache *lru.Cache[types.Hash, *block2.Block]
headerCache *lru.Cache[types.Hash, *block2.Header]
numberCache *lru.Cache[types.Hash, uint64]
tdCache *lru.Cache[types.Hash, *uint256.Int]
forker *ForkChoice
validator Validator
}
这是一个名为BlockChain的结构体,它包含了区块链的各种属性和方法。以下是对各个属性的简要说明:
chainConfig
:链配置ctx
:上下文cancel
:取消函数genesisBlock
:创世区块blocks
:区块列表headers
:区块头列表currentBlock
:当前区块ChainDB
:链数据库engine
:共识引擎,是consensus模块中的接口,用来验证block的接口insertLock
:插入锁latestBlockCh
:最新区块通道lock
:互斥锁peers
:节点映射chBlocks
:区块通道p2p
:P2P协议实例errorCh
:错误通道- `process``:处理器,执行区块链交易的接口,收到一个新的区块时,要对区块中的所有交易执行一遍,一方面是验证,一方面是更新worldState
wg
:同步等待组,类型为sync.WaitGroup。procInterrupt
:进程中断标志futureBlocks
:未来区块缓存,收到的区块时间大于当前头区块时间15s而小于30s的区块,可作为当前节点待处理的区块receiptCache
:收据缓存blockCache
:区块缓存headerCache
:区块头缓存numberCache
:区块号缓存tdCache
:总难度缓存forker
:分叉选择器validator
:验证器
1.1 NewBlockChain
func NewBlockChain(ctx context.Context, genesisBlock block2.IBlock, engine consensus.Engine, db kv.RwDB, p2p p2p.P2P, config *params.ChainConfig) (common.IBlockChain, error) {
这个函数用于初始化:
c, cancel := context.WithCancel(ctx)
使用Go语言中的context包创建一个带有取消功能的上下文。其中,传入的参数ctx
是一个已经存在的上下文对象,cancel
是一个用于取消上下文的函数。通过调用context.WithCancel(ctx)
方法,可以创建一个新的上下文对象c
,并返回一个与c
关联的取消函数cancel
。当需要取消上下文时,只需调用cancel()
函数即可。- 用
db.View
来读取数据库中的信息,如果读取到的当前区块信息为nil,就创建一个创世区块 - 初始化BlockChain对象 155-190
- 使用
bc.forker = NewForkChoice(bc, nil)
创建新的分叉选择 - 使用
NewStateProcessor
创建新的状态处理 - 使用
NewBlockValidator
创建新的区块验证
1.2 Config\CurrentBlock\GenesisBlock
func (bc *BlockChain) Config() *params.ChainConfig {
return bc.chainConfig
}
func (bc *BlockChain) CurrentBlock() block2.IBlock {
return bc.currentBlock.Load()
}
func (bc *BlockChain) Blocks() []block2.IBlock {
return bc.blocks
}
func (bc *BlockChain) GenesisBlock() block2.IBlock {
return bc.genesisBlock
}
用于返回区块链配置/当前区块/区块列表和创世区块
1.3 InsertHeader
func (bc *BlockChain) InsertHeader(headers []block2.IHeader) (int, error) {
//TODO implement me
panic("implement me")
}
panic(“implement me”)表示抛出一个带有消息"implement me"的panic异常。通常,这种异常是为了提醒开发者需要实现某个功能或者修复某个错误。
1.4 Start
func (bc *BlockChain) Start() error {
bc.wg.Add(3)
go bc.runLoop()
go bc.updateFutureBlocksLoop()
return nil
}
该方法的作用是启动区块链的运行
- 使用
bc.wg.Add(3)
向一个工作组(workgroup)中添加了三个任务 - 调用
go bc.runLoop()
启动了一个名为runLoop
的协程(参考1.9),该协程负责处理区块链的运行逻辑 - 通过调用
go bc.updateFutureBlocksLoop()
启动了一个名为updateFutureBlocksLoop
的协程,该协程负责更新未来区块的信息
整个方法最后返回nil
表示没有错误发生。
1.5 verifyBody/verifyState
func (bc *BlockChain) verifyBody(block block2.IBlock) error {
return nil
}
func (bc *BlockChain) verifyState(block block2.IBlock, state *state.IntraBlockState, receipts block2.Receipts, usedGas uint64) error {
return nil
}
用于验证区块体和状态
1.6 AddPeer
func (bc *BlockChain) AddPeer(hash string, remoteBlock uint64, peerID peer.ID) error
用于添加节点
- 检查传入的hash值与创世区块中定义的hash值是否一样,如果不一样就输出错误信息
- 检查节点是否以及存在,通过peers的映射进行查看
_, ok := bc.peers[peerID]
,如果已经存在,就输出错误信息 - 打印日志信息
1.7 GetReceipts/GetLogs
func (bc *BlockChain) GetReceipts(blockHash types.Hash) (block2.Receipts, error)
func (bc *BlockChain) GetLogs(blockHash types.Hash) ([][]*block2.Log, error)
获取收据和日志函数。
- 其中
rtx, err := bc.ChainDB.BeginRo(bc.ctx)
从bc.ChainDB
中开始一个只读事务。rtx
是一个指向事务的指针,err
是一个错误变量,用于存储可能发生的错误。 - 在代码块执行完毕后,使用
defer
关键字调用rtx.Rollback()
方法来回滚事务。 - 调用
rawdb.ReadReceiptsByHash(rtx, blockHash)
来通过hash获取收据消息
1.8 LatestBlockCh
func (bc *BlockChain) LatestBlockCh() (block2.IBlock, error) {
函数从通道中获取信息,对应不同的操作从而得到最新区块
case <-bc.ctx.Done():
表示上下文已经完成之后,就会返回nil和主链已经关闭的信息提示case block, ok := <-bc.latestBlockCh:
读取最新区块通道的信息来判断是否出错
1.9 runLoop
func (bc *BlockChain) runLoop() {
- 延迟执行函数
defer func() {
bc.wg.Done()
bc.cancel()
bc.StopInsert()
close(bc.errorCh)
bc.wg.Wait()
}()
在runLoop
函数执行完成之后,会执行上述的函数,包括同步完成函数、取消函数、停止插入函数、关闭error通道和开始同步等待
2. 对应不同的select
- 如果接收到来自
bc.ctx.Done()
的信号,表示已经完成,就会直接返回 - 如果接收到来自
case err, ok := <-bc.errorCh
通道的信号,就会判断有无错误
1.10 updateFutureBlocksLoop
func (bc *BlockChain) updateFutureBlocksLoop() {
- 定时器设置
futureTimer := time.NewTicker(2 * time.Second)
defer futureTimer.Stop()
defer bc.wg.Done()
创建了一个定时器,每隔2秒触发一次。该函数的主要功能是定期检查并更新区块链中的未来区块。然后使用defer
关键字来确保在函数执行完毕后,定时器会被停止,并且等待组bc.wg
的完成状态被设置
2. 执行一个for循环,其中使用select语句监听两个事件:定时器触发事件和上下文取消事件。
case <-futureTimer.C
语句确保在定时器触发时,才会执行,否则会一直阻塞- 当由待处理区块的时候,区块长度大于0的时候,执行if语句
- 创建blocks切片用于存放
- 使用for循环遍历
bc.futureBlocks
的所有键。
(1) 对于每个键,使用bc.futureBlocks.Get(key)
获取对应的值。如果该键存在且有对应的值,则将该值赋给变量value,并且ok为true。
(2)如果ok为true,则将value添加到blocks切片中
循环结束后,blocks切片将包含bc.futureBlocks中所有存在的值
sort.Slice
对一个名为blocks的切片进行排序。排序依据是Number64()方法(区块编号),按照从小到大的顺序排列- 判断排序后的第一个区块的编号是否大于当前区块编号加1。如果是,则跳过此次循环,继续下一次迭代
- 如果第一个区块的编号小于等于当前区块编号加1,则尝试将
blocks
中的区块插入到区块链中bc.InsertChain(blocks)
。 - 如果插入成功,则从
bc.futureBlocks
中移除这些已插入的区块bc.futureBlocks.Remove(k)
,并记录日志信息 - 如果在插入过程中发生错误,则记录警告日志
- 当上下文取消事件发生时,函数返回,结束循环
case <-bc.ctx.Done():
1.11 runNewBlockMessage
func (bc *BlockChain) runNewBlockMessage() {
- 创建newblock通道
- 使用v2包订阅事件
- 使用延迟
defer sub.Unsubscribe()
当函数执行完成之后取消订阅 - 将数据库对象赋值给db
- 执行无限循环for语句,其中使用select语句
case <-bc.ctx.Done():
就直接返回- 如果err错误通道中有信号,就输出错误信息并返回
- 如果从Block通道中获取信号
case block, ok := <-bc.chBlocks:
,当获取到区块的时候,更新数据库(写区块,写区块头,读当前区块)使用的都是rawdb中的函数 - 如果从newblock通道中获取到信号,当使用了
block.FromProtoMessage(msg.Block)
没有err信息的时候,也进行数据库的更新
1.12 NewBlockValidator
2、查询
2.1 GetHeader
func (bc *BlockChain) GetHeader(h types.Hash, number *uint256.Int) block2.IHeader {
获取给定hash和number获取区块的header
- 先从header缓存headercache中进行get,如果能找到(ok),就返回header
- 否则就从数据库中查找
- 如果由报错err,就返回nil
- 否则就使用rawdb的
rawdb.ReadHeader
函数进行获取 - 如果获取到的为nil就返回nil
- 从数据库中查找的时候也使用到
bc.ChainDB.BeginRo(bc.ctx)
和延迟回滚defer tx.Rollback()
2.2 GetHeaderByNumber
func (bc *BlockChain) GetHeaderByNumber(number *uint256.Int) block2.IHeader {
函数通过number获取到区块的header
- 使用数据库
bc.ChainDB.BeginRo(bc.ctx)
- 如果有错误,就输出错误信息并返回nil
- 使用
rawdb.ReadCanonicalHash
函数根据number获得hash值,并在错误情况下输出错误信息,返回nil - 如果获取到的hash为默认
types.Hash{}
,就返回bil表示不存在 - 后续流程与2.1的GetHeader一致
- 延迟回滚
defer tx.Rollback()
2.3 GetHeaderByHash
func (bc *BlockChain) GetHeaderByHash(h types.Hash) (block2.IHeader, error) {
number := bc.GetBlockNumber(h)
if number == nil {
return nil, nil
}
return bc.GetHeader(h, uint256.NewInt(*number)), nil
}
- 使用
bc.GetBlockNumber(h)
通过hash获取到number的值(参考2.5),如果number不存在,就返回nil - 否则使用
GetHeader
(参考2.1)来获取 - 同时使用到延迟回滚
defer tx.Rollback()
2.4 GetCanonicalHash
func (bc *BlockChain) GetCanonicalHash(number *uint256.Int) types.Hash {
tx, err := bc.ChainDB.BeginRo(bc.ctx)
if nil != err {
return types.Hash{}
}
defer tx.Rollback()
hash, err := rawdb.ReadCanonicalHash(tx, number.Uint64())
if nil != err {
return types.Hash{}
}
return hash
}
该函数为传入的number的值得到对应的hash。
- 使用
bc.ChainDB.BeginRo(bc.ctx)
从数据库中开始读 - 如果有错,就返回默认的hash
- 否则使用
rawdb.ReadCanonicalHash
从数据库中获取到hash数据,有错就返回默认hashtypes.Hash{}
没有错误就返回获取到的hash - 使用到延迟回滚
defer tx.Rollback()
2.5 GetBlockNumber
func (bc *BlockChain) GetBlockNumber(hash types.Hash) *uint64 {
该函数通过给定的hash获取对应的区块号number
- 先从numbercache缓存中进行查看,如果有,就返回
- 否则尝试从数据库中查找
- 使用
bc.ChainDB.BeginRo(bc.ctx)
开启,有错误信息就返回nil - 没有错误就从数据库中使用
number := rawdb.ReadHeaderNumber(tx, hash)
来读取 - 如果查找到number,就使用
bc.numberCache.Add(hash, *number)
来添加一条数据到cache中,方便下次查询
- 使用到延迟回滚
defer tx.Rollback()
2.6 GetBlockByHash
func (bc *BlockChain) GetBlockByHash(h types.Hash) (block2.IBlock, error) {
number := bc.GetBlockNumber(h)
if nil == number {
return nil, errBlockDoesNotExist
}
return bc.GetBlock(h, *number), nil
}
该函数用过给定的hash来获取对应的block
- 使用
number := bc.GetBlockNumber(h)
获取对应的区块号number(参考2.5) - 如果获取的number为nil,就返回nil和区块不存在的错误信息
- 否则就使用
bc.GetBlock(h, *number)
获取区块并返回(参考2.9)
2.7 GetBlockByNumber
func (bc *BlockChain) GetBlockByNumber(number *uint256.Int) (block2.IBlock, error) {
}
该函数用过给定的number来获取对应的block
- 获取对应的hash
bc.ChainDB.View(bc.ctx, func(tx kv.Tx) error {
hash, _ = rawdb.ReadCanonicalHash(tx, number.Uint64())
return nil
})
其中使用到rawdb.ReadCanonicalHash(tx, number.Uint64())
从数据库中来获取对应的hash
2. 如果获取的hash为types.Hash{}
,就返回nil
3. 否则就使用bc.GetBlock(h, *number)
获取区块并返回(参考2.9)
2.8 GetBlocksFromHash
func (bc *BlockChain) GetBlocksFromHash(hash types.Hash, n int) (blocks []block2.IBlock) {
该函数通过传入的hash来获取n个区块中对应的区块小链
- 通过
bc.numberCache.Get(hash)
来获取对应的区块号number,并在获取到之后将num赋值给number - 如果没有从numbercache中获取到,就从数据库中进行查找
- 如果查到的number为nil,就返回nil
- 否则就得到number并添加进numbercache中
bc.numberCache.Add(hash, *number)
- 使用for循环
- 调用
block := bc.GetBlock(hash, *number)
获取该hash对应的区块block(参考2.9) - 如果block为nil,就break跳出循环
- 否则将该block加入blocks中
blocks = append(blocks, block)
- 并将hash往前变成其父节点的hash
hash = block.ParentHash()
- 且区块号减一
*number--
- 进行下一轮循环
- 返回整个blocks
2.9 GetBlock
func (bc *BlockChain) GetBlock(hash types.Hash, number uint64) block2.IBlock {
该函数根据给定的hash和number来获取区块Block
- 如果hash是默认的
types.Hash{}
就返回nil,代表不存在 - 先从blockcache缓存中根据Hash进行查询,如果查到就返回block,否则就从数据库中查找
- 从数据库中查找
- 使用
bc.ChainDB.BeginRo(bc.ctx)
开启,有错就返回nil - 否则使用
rawdb.ReadBlock
从数据库中读取block - 如果读取到的block为nil,就返回nil表示block不存在
- 如果能读取到block,就添加到blockcache中,方便下一次查找
bc.blockCache.Add(hash, block)
- 返回block
- 同时使用到延迟回滚
defer tx.Rollback()
2.10 GetTd
func (bc *BlockChain) GetTd(hash types.Hash, number *uint256.Int) *uint256.Int {
该函数通过给定的hash和number来获取对应的总难度td
- 先从tdcache缓存中读取
td, ok := bc.tdCache.Get(hash)
,如果有就直接返回td,否则就从数据库中读取 - 使用
rawdb.ReadTd
从数据库中进行读取,如果有错误err,就返回nil - 否则将读取到的ptd赋值给td,并添加到tdcache缓存中
bc.tdCache.Add(hash, td)
_ = bc.ChainDB.View(bc.ctx, func(tx kv.Tx) error {
ptd, err := rawdb.ReadTd(tx, hash, number.Uint64())
if nil != err {
return err
}
td = ptd
return nil
})