【源码阅读】blockchainⅠ

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的结构体,它包含了区块链的各种属性和方法。以下是对各个属性的简要说明:

  1. chainConfig:链配置
  2. ctx:上下文
  3. cancel:取消函数
  4. genesisBlock:创世区块
  5. blocks:区块列表
  6. headers:区块头列表
  7. currentBlock:当前区块
  8. ChainDB:链数据库
  9. engine:共识引擎,是consensus模块中的接口,用来验证block的接口
  10. insertLock:插入锁
  11. latestBlockCh:最新区块通道
  12. lock:互斥锁
  13. peers:节点映射
  14. chBlocks:区块通道
  15. p2p:P2P协议实例
  16. errorCh:错误通道
  17. `process``:处理器,执行区块链交易的接口,收到一个新的区块时,要对区块中的所有交易执行一遍,一方面是验证,一方面是更新worldState
  18. wg:同步等待组,类型为sync.WaitGroup。
  19. procInterrupt:进程中断标志
  20. futureBlocks:未来区块缓存,收到的区块时间大于当前头区块时间15s而小于30s的区块,可作为当前节点待处理的区块
  21. receiptCache:收据缓存
  22. blockCache:区块缓存
  23. headerCache:区块头缓存
  24. numberCache:区块号缓存
  25. tdCache:总难度缓存
  26. forker:分叉选择器
  27. 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) {

这个函数用于初始化:

  1. c, cancel := context.WithCancel(ctx)使用Go语言中的context包创建一个带有取消功能的上下文。其中,传入的参数ctx是一个已经存在的上下文对象,cancel是一个用于取消上下文的函数。通过调用context.WithCancel(ctx)方法,可以创建一个新的上下文对象c,并返回一个与c关联的取消函数cancel。当需要取消上下文时,只需调用cancel()函数即可。
  2. db.View来读取数据库中的信息,如果读取到的当前区块信息为nil,就创建一个创世区块
  3. 初始化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
}

该方法的作用是启动区块链的运行

  1. 使用bc.wg.Add(3)向一个工作组(workgroup)中添加了三个任务
  2. 调用go bc.runLoop()启动了一个名为runLoop的协程(参考1.9),该协程负责处理区块链的运行逻辑
  3. 通过调用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

用于添加节点

  1. 检查传入的hash值与创世区块中定义的hash值是否一样,如果不一样就输出错误信息
  2. 检查节点是否以及存在,通过peers的映射进行查看_, ok := bc.peers[peerID],如果已经存在,就输出错误信息
  3. 打印日志信息

1.7 GetReceipts/GetLogs

func (bc *BlockChain) GetReceipts(blockHash types.Hash) (block2.Receipts, error)
func (bc *BlockChain) GetLogs(blockHash types.Hash) ([][]*block2.Log, error)

获取收据和日志函数。

  1. 其中rtx, err := bc.ChainDB.BeginRo(bc.ctx)bc.ChainDB中开始一个只读事务。rtx是一个指向事务的指针,err是一个错误变量,用于存储可能发生的错误。
  2. 在代码块执行完毕后,使用defer关键字调用rtx.Rollback()方法来回滚事务。
  3. 调用rawdb.ReadReceiptsByHash(rtx, blockHash)来通过hash获取收据消息

1.8 LatestBlockCh

func (bc *BlockChain) LatestBlockCh() (block2.IBlock, error) {

函数从通道中获取信息,对应不同的操作从而得到最新区块

  1. case <-bc.ctx.Done():表示上下文已经完成之后,就会返回nil和主链已经关闭的信息提示
  2. case block, ok := <-bc.latestBlockCh:读取最新区块通道的信息来判断是否出错

1.9 runLoop

func (bc *BlockChain) runLoop() {
  1. 延迟执行函数
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() {
  1. 定时器设置
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中所有存在的值
  1. sort.Slice对一个名为blocks的切片进行排序。排序依据是Number64()方法(区块编号),按照从小到大的顺序排列
  2. 判断排序后的第一个区块的编号是否大于当前区块编号加1。如果是,则跳过此次循环,继续下一次迭代
  3. 如果第一个区块的编号小于等于当前区块编号加1,则尝试将blocks中的区块插入到区块链中bc.InsertChain(blocks)
  4. 如果插入成功,则从bc.futureBlocks中移除这些已插入的区块bc.futureBlocks.Remove(k),并记录日志信息
  5. 如果在插入过程中发生错误,则记录警告日志
  6. 当上下文取消事件发生时,函数返回,结束循环case <-bc.ctx.Done():

1.11 runNewBlockMessage

func (bc *BlockChain) runNewBlockMessage() {
  1. 创建newblock通道
  2. 使用v2包订阅事件
  3. 使用延迟defer sub.Unsubscribe()当函数执行完成之后取消订阅
  4. 将数据库对象赋值给db
  5. 执行无限循环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

  1. 先从header缓存headercache中进行get,如果能找到(ok),就返回header
  2. 否则就从数据库中查找
  3. 如果由报错err,就返回nil
  4. 否则就使用rawdb的rawdb.ReadHeader函数进行获取
  5. 如果获取到的为nil就返回nil
  6. 从数据库中查找的时候也使用到bc.ChainDB.BeginRo(bc.ctx)和延迟回滚defer tx.Rollback()

2.2 GetHeaderByNumber

func (bc *BlockChain) GetHeaderByNumber(number *uint256.Int) block2.IHeader {

函数通过number获取到区块的header

  1. 使用数据库bc.ChainDB.BeginRo(bc.ctx)
  2. 如果有错误,就输出错误信息并返回nil
  3. 使用rawdb.ReadCanonicalHash函数根据number获得hash值,并在错误情况下输出错误信息,返回nil
  4. 如果获取到的hash为默认types.Hash{},就返回bil表示不存在
  5. 后续流程与2.1的GetHeader一致
  6. 延迟回滚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
}
  1. 使用bc.GetBlockNumber(h)通过hash获取到number的值(参考2.5),如果number不存在,就返回nil
  2. 否则使用GetHeader(参考2.1)来获取
  3. 同时使用到延迟回滚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。

  1. 使用bc.ChainDB.BeginRo(bc.ctx)从数据库中开始读
  2. 如果有错,就返回默认的hash
  3. 否则使用rawdb.ReadCanonicalHash从数据库中获取到hash数据,有错就返回默认hashtypes.Hash{}没有错误就返回获取到的hash
  4. 使用到延迟回滚defer tx.Rollback()

2.5 GetBlockNumber

func (bc *BlockChain) GetBlockNumber(hash types.Hash) *uint64 {

该函数通过给定的hash获取对应的区块号number

  1. 先从numbercache缓存中进行查看,如果有,就返回
  2. 否则尝试从数据库中查找
  • 使用bc.ChainDB.BeginRo(bc.ctx)开启,有错误信息就返回nil
  • 没有错误就从数据库中使用number := rawdb.ReadHeaderNumber(tx, hash)来读取
  • 如果查找到number,就使用bc.numberCache.Add(hash, *number)来添加一条数据到cache中,方便下次查询
  1. 使用到延迟回滚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

  1. 使用number := bc.GetBlockNumber(h)获取对应的区块号number(参考2.5)
  2. 如果获取的number为nil,就返回nil和区块不存在的错误信息
  3. 否则就使用bc.GetBlock(h, *number)获取区块并返回(参考2.9)

2.7 GetBlockByNumber

func (bc *BlockChain) GetBlockByNumber(number *uint256.Int) (block2.IBlock, error) {
}

该函数用过给定的number来获取对应的block

  1. 获取对应的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个区块中对应的区块小链

  1. 通过bc.numberCache.Get(hash)来获取对应的区块号number,并在获取到之后将num赋值给number
  2. 如果没有从numbercache中获取到,就从数据库中进行查找
  • 如果查到的number为nil,就返回nil
  • 否则就得到number并添加进numbercache中bc.numberCache.Add(hash, *number)
  1. 使用for循环
  • 调用block := bc.GetBlock(hash, *number)获取该hash对应的区块block(参考2.9)
  • 如果block为nil,就break跳出循环
  • 否则将该block加入blocks中blocks = append(blocks, block)
  • 并将hash往前变成其父节点的hashhash = block.ParentHash()
  • 且区块号减一*number--
  • 进行下一轮循环
  1. 返回整个blocks

2.9 GetBlock

func (bc *BlockChain) GetBlock(hash types.Hash, number uint64) block2.IBlock {

该函数根据给定的hash和number来获取区块Block

  1. 如果hash是默认的types.Hash{}就返回nil,代表不存在
  2. 先从blockcache缓存中根据Hash进行查询,如果查到就返回block,否则就从数据库中查找
  3. 从数据库中查找
  4. 使用bc.ChainDB.BeginRo(bc.ctx)开启,有错就返回nil
  5. 否则使用rawdb.ReadBlock从数据库中读取block
  6. 如果读取到的block为nil,就返回nil表示block不存在
  7. 如果能读取到block,就添加到blockcache中,方便下一次查找bc.blockCache.Add(hash, block)
  8. 返回block
  9. 同时使用到延迟回滚defer tx.Rollback()

2.10 GetTd

func (bc *BlockChain) GetTd(hash types.Hash, number *uint256.Int) *uint256.Int {

该函数通过给定的hash和number来获取对应的总难度td

  1. 先从tdcache缓存中读取td, ok := bc.tdCache.Get(hash),如果有就直接返回td,否则就从数据库中读取
  2. 使用rawdb.ReadTd从数据库中进行读取,如果有错误err,就返回nil
  3. 否则将读取到的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
})

你可能感兴趣的:(区块链,区块链)