分类 开发文档 下的文章 - 六币之门
首页
视频教程
网站导航
活动日历
关于我们
用户投稿
推荐
新闻动态
搜 索
1
融资周报 | 公开融资事件11起;加密技术公司Toposware完成500万美元融资,Polygon联创参投
107 阅读
2
六币日报 | 九只比特币ETF在6天内积累了9.5万枚BTC;贝莱德决定停止推出XRP现货ETF计划
73 阅读
3
六币日报 | 美国SEC再次推迟对灰度以太坊期货ETF做出决定;Do Kwon已出黑山监狱等待引渡
68 阅读
4
融资周报 | 公开融资事件27起;L1区块链Monad Labs完成2.25亿美元融资,Paradigm领投
67 阅读
5
【ETH钱包开发06】查询某个地址的交易记录
43 阅读
新闻动态
每日快报
一周精选
融资情况
项目投研
自治组织
数字藏品
去中心化应用
去中心化游戏
去中心化社交
去中心化金融
区块链交易所
科普知识
小白入门
用户手册
开发文档
行业报告
技术前沿
登录
搜 索
标签搜索
新闻
日报
元歌Eden
累计撰写
1,087
篇文章
累计收到
0
条评论
首页
栏目
新闻动态
每日快报
一周精选
融资情况
项目投研
自治组织
数字藏品
去中心化应用
去中心化游戏
去中心化社交
去中心化金融
区块链交易所
科普知识
小白入门
用户手册
开发文档
行业报告
技术前沿
页面
视频教程
网站导航
活动日历
关于我们
用户投稿
推荐
新闻动态
用户登录
登录
找到
27
篇与
开发文档
相关的结果
2023-03-03
Go-ethereum 源码解析之 go-ethereum/consensus/errors.go
Go-ethereum 源码解析之 go-ethereum/consensus/errors.goSource code// Copyright 2017 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // The go-ethereum library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. package consensus import "errors" var ( // ErrUnknownAncestor is returned when validating a block requires an ancestor // that is unknown. ErrUnknownAncestor = errors.New("unknown ancestor") // ErrPrunedAncestor is returned when validating a block requires an ancestor // that is known, but the state of which is not available. ErrPrunedAncestor = errors.New("pruned ancestor") // ErrFutureBlock is returned when a block's timestamp is in the future according // to the current node. ErrFutureBlock = errors.New("block in the future") // ErrInvalidNumber is returned if a block's number doesn't equal it's parent's // plus one. ErrInvalidNumber = errors.New("invalid block number") ) Appendix A. 总体批注本文件中定义了包 consensus 中的通用错误。对于特定的共识引擎 Clique 和 Ethash,其子包中将定义特定共识引擎内部使用的错误。Appendix B. 详细批注1. varErrUnknownAncestor = errors.New("unknown ancestor"):当验证区块需要的一个祖先区块未知时返回 ErrUnknownAncestor。 ErrPrunedAncestor = errors.New("pruned ancestor"):当验证区块需要的一个祖先区块已知但状态不可用时返回 ErrPrunedAncestor。 ErrFutureBlock = errors.New("block in the future"):对于本地节点,如果区块的时间戳在当前时间戳之后,将返回 ErrFutureBlock。 ErrInvalidNumber = errors.New("invalid block number"):如果区块的编号不等于它的父区块的编号加 1,则返回 ErrInvalidNumber。 Referencehttps://github.com/ethereum/go-ethereum/blob/master/consensus/errors.go ContributorWindstamp, https://github.com/windstamp
2023年03月03日
5 阅读
0 评论
0 点赞
2023-03-03
Go-ethereum 源码解析之 go-ethereum/core/types.go
Go-ethereum 源码解析之 go-ethereum/core/types.goSource code// Copyright 2017 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // The go-ethereum library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. package core import ( "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/core/vm" ) // Validator is an interface which defines the standard for block validation. It // is only responsible for validating block contents, as the header validation is // done by the specific consensus engines. // type Validator interface { // ValidateBody validates the given block's content. ValidateBody(block *types.Block) error // ValidateState validates the given statedb and optionally the receipts and // gas used. ValidateState(block, parent *types.Block, state *state.StateDB, receipts types.Receipts, usedGas uint64) error } // Processor is an interface for processing blocks using a given initial state. // // Process takes the block to be processed and the statedb upon which the // initial state is based. It should return the receipts generated, amount // of gas used in the process and return an error if any of the internal rules // failed. type Processor interface { Process(block *types.Block, statedb *state.StateDB, cfg vm.Config) (types.Receipts, []*types.Log, uint64, error) } Appendix A. 总体批注本文件定义了两个接口。一个是用于验证区块的接口 Validator。另一个是用于处理区块的接口 Processor。需要注意,这些操作都需要用到状态数据库。Appendix B. 详细批注1. type Validator interface接口 Validator 定义了区块验证的标准接口。它只负责验证区块内容,因为区块头验证由特定的共识引擎完成。(1) ValidateBody(block *types.Block) error方法 ValidateBody() 验证给定区块的内容。参数:block *types.Block: 给定区块 返回值:error: 错误消息或 nil (2) ValidateState(block, parent *types.Block, state *state.StateDB, receipts types.Receipts, usedGas uint64) error方法 ValidateState() 验证给定的状态数据库以及可选的交易回执列表和使用的 Gas。参数:block *types.Block: 区块 parent *types.Block: 父区块 state *state.StateDB: 状态数据库 receipts types.Receipts: 交易回执列表 usedGas uint64: 已使用的 Gas 返回值:error: 错误消息或 nil 2. type Processor interface接口 Processor 使用一个给定的初始状态数据库处理区块。(1) Process(block *types.Block, statedb state.StateDB, cfg vm.Config) (types.Receipts, []types.Log, uint64, error)方法 Process() 基于给定的初始状态数据库处理给定的区块。它应该返回生成的交易回执列表,消耗的 Gas,如果任一内部规则失败返回错误。参数:block *types.Block: 区块 statedb *state.StateDB: 状态数据库 cfg vm.Config: EVM 配置信息 返回值:types.Receipts: 交易回执列表 []*types.Log: 日志列表 uint64: 消耗的 Gas error: 错误消息或 nil Referencehttps://github.com/ethereum/go-ethereum/blob/master/core/types.go ContributorWindstamp, https://github.com/windstamp
2023年03月03日
6 阅读
0 评论
0 点赞
2023-03-03
Go-ethereum 源码解析之 go-ethereum/core/events.go
Go-ethereum 源码解析之 go-ethereum/core/events.go// Copyright 2017 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // The go-ethereum library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. package core import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/types" ) // NewTxsEvent is posted when a batch of transactions enter the transaction pool. type NewTxsEvent struct // PendingLogsEvent is posted pre mining and notifies of pending logs. type PendingLogsEvent struct { Logs []*types.Log } // NewMinedBlockEvent is posted when a block has been imported. type NewMinedBlockEvent struct // RemovedLogsEvent is posted when a reorg happens type RemovedLogsEvent struct type ChainEvent struct { Block *types.Block Hash common.Hash Logs []*types.Log } type ChainSideEvent struct { Block *types.Block } type ChainHeadEvent struct Appendix A. 总体批注文件 core/events.go 定义了打包新区块时需要关注的各种事件。这些事件有些是由网络中其它节点向本地节点广播的,有些是由本地节点向网络中其它节点广播的。网络节点向本地节点广播的事件NewTxsEvent 本地节点向网络节点广播的事件ChainHeadEvent ChainSideEvent NewMinedBlockEvent NewTxsEvent Appendix B. 详细批注1. type NewTxsEvent struct事件 NewTxsEvent 在一组交易进入交易池时被发布。Txs []*types.Transaction: 交易列表 2. type PendingLogsEvent struct事件 PendingLogsEvent 在挖矿前发布,并通知待处理日志。3. type NewMinedBlockEvent struct事件 NewMinedBlockEvent 在一个新区块被导入时发布。本地节点在接收到网络中其它节点打包出的新区块时,本地节点触发此事件。4. type RemovedLogsEvent struct事件 RemovedLogsEvent 在重新组织时被发布。5. type ChainEvent struct事件 ChainEvent 表示本地节点挖出了新区块。Block *types.Block: 区块 Hash common.Hash: 区块哈希 Logs []*types.Log: 日志列表 6. type ChainSideEvent struct事件 ChainSideEvent 表示本地节点挖出了叔区块。Block *types.Block: 叔区块 7. type ChainHeadEvent struct事件 ChainHeadEvent 表示本地节点挖出了新区块。Block *types.Block: 区块 Referencehttps://github.com/ethereum/go-ethereum/blob/master/core/events.go ContributorWindstamp, https://github.com/windstamp
2023年03月03日
8 阅读
0 评论
0 点赞
2023-03-03
Go-ethereum 源码解析之 go-ethereum/ethdb/interface.go
Go-ethereum 源码解析之 go-ethereum/ethdb/interface.goSource code// Copyright 2017 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // The go-ethereum library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. package ethdb // Code using batches should try to add this much data to the batch. // The value was determined empirically. const IdealBatchSize = 100 * 1024 // Putter wraps the database write operation supported by both batches and regular databases. type Putter interface { Put(key []byte, value []byte) error } // Deleter wraps the database delete operation supported by both batches and regular databases. type Deleter interface { Delete(key []byte) error } // Database wraps all database operations. All methods are safe for concurrent use. type Database interface { Putter Deleter Get(key []byte) ([]byte, error) Has(key []byte) (bool, error) Close() NewBatch() Batch } // Batch is a write-only database that commits changes to its host database // when Write is called. Batch cannot be used concurrently. type Batch interface { Putter Deleter ValueSize() int // amount of data in the batch Write() error // Reset resets the batch for reuse Reset() } Appendix A. 总体批注此文件描述了包 ethdb 提供的基本接口。Appendix B. 详细批注1. const IdealBatchSize = 100 * 1024使用批处理的代码应该尝试将这么多数据添加到批处理中。该值是根据经验确定的。2. type Putter interface接口 Putter 包装了批处理和常规数据库支持的数据库写入操作。2.1 Put(key []byte, value []byte) error方法 Put() 存储给定的 key 和 value 到数据库。参数:key []byte: key value []byte: value 3. type Deleter interface接口 Deleter 包装批处理和常规数据库支持的数据库删除操作。3.1 Delete(key []byte) error方法 Delete() 从数据库中删除给定的 key。4. type Database interface接口 Database 包装所有数据库操作。所有方法都能够安全地支持并发。4.1 Putter继承了接口 Putter。提供了方法 Put()。4.2 Deleter继承了接口 Deleter。提供了方法 Delete()。4.3 Get(key []byte) ([]byte, error)方法 Get() 从数据库中获取 key 对应的 value。参数:key []byte: key 返回值[]byte: value error: 错误消息或 nil 4.4 Has(key []byte) (bool, error)方法 Has() 查询给定 key 是否存在于数据库中。参数:key []byte: key 返回值bool: 存在为 true,不存在则为 false error: 错误消息或 nil 5. type Batch interface接口 Batch 是一个只写数据库,在调用方法 Write() 时会将更改提交到其主机数据库。批处理不支付并发。5.1 Putter继承了接口 Putter。提供了方法 Put()。5.2 Deleter继承了接口 Deleter。提供了方法 Delete()。5.3 ValueSize() int方法 ValueSize() 返回批处理中的数据量。5.4 Write() error方法 Write() 将更改提交到其主机数据库。5.5 Reset()方法 Reset() 重置批处理以便重复使用。Referencehttps://github.com/ethereum/go-ethereum/blob/master/ethdb/interface.go ContributorWindstamp, https://github.com/windstamp
2023年03月03日
7 阅读
0 评论
0 点赞
2023-03-03
Go-ethereum 源码解析之 go-ethereum/ethdb/memory_database.go
Go-ethereum 源码解析之 go-ethereum/ethdb/memory_database.goSource code// Copyright 2017 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // The go-ethereum library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. package ethdb import ( "errors" "sync" "github.com/ethereum/go-ethereum/common" ) /* * This is a test memory database. Do not use for any production it does not get persisted */ type MemDatabase struct { db map[string][]byte lock sync.RWMutex } func NewMemDatabase() *MemDatabase { return &MemDatabase{ db: make(map[string][]byte), } } func NewMemDatabaseWithCap(size int) *MemDatabase { return &MemDatabase{ db: make(map[string][]byte, size), } } func (db *MemDatabase) Put(key []byte, value []byte) error { db.lock.Lock() defer db.lock.Unlock() db.db[string(key)] = common.CopyBytes(value) return nil } func (db *MemDatabase) Has(key []byte) (bool, error) { db.lock.RLock() defer db.lock.RUnlock() _, ok := db.db[string(key)] return ok, nil } func (db *MemDatabase) Get(key []byte) ([]byte, error) { db.lock.RLock() defer db.lock.RUnlock() if entry, ok := db.db[string(key)]; ok { return common.CopyBytes(entry), nil } return nil, errors.New("not found") } func (db *MemDatabase) Keys() [][]byte { db.lock.RLock() defer db.lock.RUnlock() keys := [][]byte for key := range db.db { keys = append(keys, []byte(key)) } return keys } func (db *MemDatabase) Delete(key []byte) error { db.lock.Lock() defer db.lock.Unlock() delete(db.db, string(key)) return nil } func (db *MemDatabase) Close() func (db *MemDatabase) NewBatch() Batch { return &memBatch } func (db *MemDatabase) Len() int type kv struct { k, v []byte del bool } type memBatch struct { db *MemDatabase writes []kv size int } func (b *memBatch) Put(key, value []byte) error { b.writes = append(b.writes, kv) b.size += len(value) return nil } func (b *memBatch) Delete(key []byte) error { b.writes = append(b.writes, kv) b.size += 1 return nil } func (b *memBatch) Write() error { b.db.lock.Lock() defer b.db.lock.Unlock() for _, kv := range b.writes { if kv.del { delete(b.db.db, string(kv.k)) continue } b.db.db[string(kv.k)] = kv.v } return nil } func (b *memBatch) ValueSize() int { return b.size } func (b *memBatch) Reset() { b.writes = b.writes[:0] b.size = 0 } Appendix A. 总体批注实现了一个内存数据库 MemDatabase 用于测试环境,但不能将其用于生产环境。ethdb.MemDatabase 实现了接口 ethdb.Database。ethdb.memBatch 在 ethdb.MemDatabase 的基础上提供了批处理能力。这里将基于接口编程的思想展现的淋漓尽致。Appendix B. 详细批注1. type MemDatabase struct数据结构 MemDatabase 是一个测试内存数据库。不要将其用于任何生产环境,因为它不会被持久化。db map[string][]byte: key-value 对? lock sync.RWMutex: 锁 1.1 func NewMemDatabase() *MemDatabase构造函数 NewMemDatabase() 创建对象 MemDatabase,并使用默认值初始化。1.2 func NewMemDatabaseWithCap(size int) *MemDatabase构造函数 NewMemDatabaseWithCap() 创建对象 MemDatabase,并设定 db 的大小。1.3 func (db *MemDatabase) Put(key []byte, value []byte) error方法 Put() 实现了接口 ethdb.Putter 和接口 ethdb.Database。参数:key []byte: key value []byte: value 返回值:出错返回错误消息 error,否则返回 nil 主要实现:加锁。代码为: db.lock.Lock() defer 解锁。代码为:defer db.lock.Unlock() 将 (key, value) 对存储数据库 db。db.db[string(key)] = common.CopyBytes(value) 1.4 func (db *MemDatabase) Has(key []byte) (bool, error)方法 Has() 实现了接口 ethdb.Database。参数:key []byte: key 返回值:存在返回 true,否则返回 false 出错返回错误消息 error,否则返回 nil 主要实现:加锁。代码为: db.lock.RLock() defer 解锁。代码为:defer db.lock.RUnlock() 是否存在。_, ok := db.db[string(key)] 1.5 func (db *MemDatabase) Get(key []byte) ([]byte, error)方法 Get() 实现了接口 ethdb.Database。参数:key []byte: key 返回值:存在返回 key 对应的 value 出错返回错误消息 error,否则返回 nil 主要实现:加锁。代码为:db.lock.RLock() defer 解锁。代码为:defer db.lock.RUnlock() 获取 key 对应的值 entry。代码为:entry, ok := db.db[string(key)] 将 entry 的副本返回。代码为:return common.CopyBytes(entry) 1.6 func (db *MemDatabase) Keys() [][]byte方法 Keys() 返回数据库中的所有 key。返回值:所有的 key 构成的列表 主要实现:加锁。代码为:db.lock.RLock() defer 解锁。代码为:defer db.lock.RUnlock() 定义所有 key 的列表 keys 遍历数据库 db.db 中的所有 key 将 key 添加到 keys 1.7 func (db *MemDatabase) Delete(key []byte) error方法 Put() 实现了接口 ethdb.Deleter 和接口 ethdb.Database。参数:key []byte: key 返回值:出错返回错误消息 error,否则返回 nil 主要实现:加锁。代码为:db.lock.Lock() defer 解锁。代码为:defer db.lock.Unlock() 通过 Go 内置函数 delete() 从数据库 db.db 中删除对应的 key。代码为:delete(db.db, string(key)) 1.8 func (db *MemDatabase) Close() 方法 Close() 实现了接口 ethdb.Database。主要实现:空实现。 1.9 func (db *MemDatabase) NewBatch() Batch方法 NewBatch() 实现了接口 ethdb.Database。主要实现:return &memBatch 1.10 func (db *MemDatabase) Len() int方法 Len() 返回数据库包含的数据量。返回值:数据量 主要实现:return len(db.db) 2. type kv struct数据结构 kv 用于描述批处理的值 k, v 和操作类型是 add 还是 del。k, v []byte: Key & Value del bool: 操作类型是插入还是删除 3. type memBatch struct数据结构 memBatch 是具有批处理能力的内存数据库。db *MemDatabase: 内存数据库 writes []kv: 批处理数据 size int: 批处理的字节数 3.1 func (b *memBatch) Put(key, value []byte) error方法 Put() 实现了接口 ethdb.Putter,用于将给定的 key & value 插入数据库。参数:key []byte: key value []byte: value 返回值:出错返回错误消息 error,否则返回 nil 主要实现:将 key & value & false 构建的 kv 插入批处理数据 writes b.writes = append(b.writes, kv) 增加批处理字节数 size b.size += len(value) 3.2 func (b *memBatch) Delete(key []byte) error方法 Delete() 实现了接口 ethdb.Deleter,用于从数据库中删除给定的 key。参数:key []byte: key 返回值:出错返回错误消息 error,否则返回 nil 主要实现:将 key & nil & true 构建的 kv 插入批处理数据 writes b.writes = append(b.writes, kv) 更新批处理字节数 size b.size += 1 3.3 func (b *memBatch) Write() error方法 Write() 一次性将批处理数据更新到数据库。返回值:出错返回错误消息 error,否则返回 nil 主要实现:加锁。代码为:db.lock.Lock() defer 解锁。代码为:defer db.lock.Unlock() 遍历批处理数据 b.writes 的每个 kv 如果 kv.del 从数据库中删除 kv.k delete(b.db.db, string(kv.k)) 退出本轮迭代 否则,将 kv.k & kv.v 插入数据库 b.db.db[string(kv.k)] = kv.v 3.4 func (b *memBatch) ValueSize() int方法 ValueSize() 返回批处理字节数。返回值:批处理字节数。 主要实现:return b.size 3.5 func (b *memBatch) Reset()方法 Reset() 重置批处理操作。主要实现:清空批处理操作 b.writes = b.writes[:0] b.size = 0 Referencehttps://github.com/ethereum/go-ethereum/blob/master/ethdb/memory_database.go ContributorWindstamp, https://github.com/windstamp
2023年03月03日
6 阅读
0 评论
0 点赞
2023-03-03
Go-ethereum 源码解析之 go-ethereum/ethdb/database.go
Go-ethereum 源码解析之 go-ethereum/ethdb/database.goSource code// Copyright 2017 The go-ethereum Authors // This file is part of the go-ethereum library. // // The go-ethereum library is free software: you can redistribute it and/or modify // it under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // The go-ethereum library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public License // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. Appendix A. 总体批注实现了底层数据库 LevelDB 的抽象层 LDBDatabase,用于生产环境。ethdb.LDBDatabase 实现了接口 ethdb.Database,并且将实际的操作转发给 LevelDB 的接口。同时,基于 metrics.Meter 测试各操作的性能。ethdb.ldbBatch 在 ethdb.LDBDatabase 的基础上提供了批处理能力。暂时不深入与性能相关的实现 metrics.Meter。Appendix B. 详细批注1. constwritePauseWarningThrottler = 1 * time.Minute: ??? 性能相关指标 2. varvar OpenFileLimit = 64: ??? LevelDB 一次能打开的文件数量上限? 3. type LDBDatabase struct数据结构 LDBDatabase 实现了接口 ethdb.Database,并且将实际的操作转发给 LevelDB 的接口。同时,基于 metrics.Meter 测试各操作的性能。fn string: 文件名。??? 干什么的文件名呢? db *leveldb.DB: LevelDB 实例 compTimeMeter metrics.Meter // Meter for measuring the total time spent in database compaction compReadMeter metrics.Meter // Meter for measuring the data read during compaction compWriteMeter metrics.Meter // Meter for measuring the data written during compaction writeDelayNMeter metrics.Meter // Meter for measuring the write delay number due to database compaction writeDelayMeter metrics.Meter // Meter for measuring the write delay duration due to database compaction diskReadMeter metrics.Meter // Meter for measuring the effective amount of data read diskWriteMeter metrics.Meter // Meter for measuring the effective amount of data written quitLock sync.Mutex // Mutex protecting the quit channel access quitChan chan chan error // Quit channel to stop the metrics collection before closing the database log log.Logger // Contextual logger tracking the database path 3.1 func NewLDBDatabase(file string, cache int, handles int) (*LDBDatabase, error)构造函数 NewLDBDatabase() 创建 LDBDatabase 的一个实例,该实例是 LevelDB 的包装器。参数:file string: 文件名 cache int: ??? 缓存? handles int: ??? 处理器? 返回值:LDBDatabase 实例 出错返回错误消息 error,否则返回 nil 主要实现:构建日志器 logger logger := log.New("database", file) 调整 cache,确保其最小值为 16 调整 handles,确保其最小值为 16 输出日志信息:logger.Info("Allocated cache and file handles", "cache", cache, "handles", handles) 打开 LevelDB,并从可能的错误恢复 db, err := leveldb.OpenFile(file, ...) corrupted := err.(*errors.ErrCorrupted) db, err = leveldb.RecoverFile(file, nil) 如果 err 不为 nil,则直接退出 return nil, err 构建 LDBDatabase 的实例并返回 return &LDBDatabase, nil 注意,这里并没有初始化任何性能计时器。3.2 func (db *LDBDatabase) Path() string方法 Path() 返回数据库目录的路径。3.3 func (db *LDBDatabase) Put(key []byte, value []byte) error方法 Put() 实现了接口 ethdb.Putter 和接口 ethdb.Database,将 key & value 写入数据库。参数:key []byte: key value []byte: value 返回值:出错返回错误消息 error,否则返回 nil 主要实现:转发给 LevelDB 的方法 Put() return db.db.Put(key, value, nil) 3.4 func (db *LDBDatabase) Has(key []byte) (bool, error)方法 Has() 实现了接口 ethdb.Database,查询给定的 key 是否存在于数据库。参数:key []byte: key 返回值:存在返回 true,否则返回 false 出错返回错误消息 error,否则返回 nil 主要实现:转发给 LevelDB 的方法 Has() return db.db.Has(key, nil) 3.5 func (db *LDBDatabase) Get(key []byte) ([]byte, error)方法 Get() 实现了接口 ethdb.Database,从数据库中获取给定 key 对应的 value。参数:key []byte: key 返回值:存在返回 key 对应的 value 出错返回错误消息 error,否则返回 nil 主要实现:转发给 LevelDB 的方法 Get() dat, err := db.db.Get(key, nil) 3.6 func (db *LDBDatabase) Delete(key []byte) error方法 Delete() 实现了接口 ethdb.Database,从数据库中删除指定的 key。参数:key []byte: key 返回值:出错返回错误消息 error,否则返回 nil 主要实现:转发给 LevelDB 的方法 Delete() return db.db.Delete(key, nil) 3.7 func (db *LDBDatabase) NewIterator() iterator.Iterator方法 NewIterator() 返回 LevelDB 的迭代器 iterator.Iterator。返回值:LevelDB 的迭代器 iterator.Iterator 主要实现:return db.db.NewIterator(nil, nil) 3.8 func (db *LDBDatabase) NewIteratorWithPrefix(prefix []byte) iterator.Iterator方法 NewIteratorWithPrefix() 返回 LevelDB 的迭代器 iterator.Iterator,这个迭代器指向具有指定前缀的数据库子集。参数:prefix []byte: 前缀 返回值:LevelDB 的迭代器 iterator.Iterator 主要实现:return db.db.NewIterator(util.BytesPrefix(prefix), nil) 3.9 func (db *LDBDatabase) Close()方法 Close() 实现了接口 ethdb.Database。主要实现:??? 停止性能指标器 将实际的关闭操作转发给 LevelDB 的方法 Close() err := db.db.Close() 如果 err == nil db.log.Info("Database closed") 否则 db.log.Error("Failed to close database", "err", err) 3.10 func (db *LDBDatabase) LDB() *leveldb.DB方法 LDB() 返回 LDBDatabase 中的底层 LevelDB 数据库。返回值:LDBDatabase 中的底层 LevelDB 数据库。 主要实现:return db.db 3.11 func (db *LDBDatabase) Meter(prefix string)性能指标相关内容,暂时不关注。3.12 func (db *LDBDatabase) meter(refresh time.Duration)性能指标相关内容,暂时不关注。3.13 func (db *LDBDatabase) NewBatch() Batch方法 NewBatch() 返回批处理器。返回值:批处理器 ldbBatch 主要实现:return &ldbBatch 4. type ldbBatch struct数据结构 ldbBatch 在 LevelDB 的基础上提供批处理能力。db *leveldb.DB: 底层的 LevelDB b *leveldb.Batch: LevelDB 的批处理器 size int: 字节数 4.1 func (b *ldbBatch) Put(key, value []byte) error方法 Put() 实现了接口 ethdb.Putter 和接口 ethdb.Batch,将 key & value 写入数据库。参数:key []byte: key value []byte: value 返回值:出错返回错误消息 error,否则返回 nil 主要实现:转发给 leveldb.Batch 的方法 Put() b.b.Put(key, value) 更新字节数 b.size += len(value) 4.2 func (b *ldbBatch) Delete(key []byte) error方法 Delete() 实现了接口 ethdb.Deleter 和接口 ethdb.Batch,从数据库中删除指定的 key。参数:key []byte: key 返回值:出错返回错误消息 error,否则返回 nil 主要实现:转发给 leveldb.Batch 的方法 Delete() b.b.Delete(key) 更新字节数 b.size += 1 4.3 func (b *ldbBatch) Write() error方法 Write() 实现了接口 ethdb.Batch,将批量数据一次性写入数据库。返回值:出错返回错误消息 error,否则返回 nil 主要实现:return b.db.Write(b.b, nil) 4.4 func (b *ldbBatch) ValueSize() int方法 ValueSize() 实现了接口 ethdb.Batch,返回批量字节数。返回值:批量字节数 主要实现:return b.size 4.5 func (b *ldbBatch) Reset()方法 Reset() 实现了接口 ethdb.Batch,重置数据库。主要实现:转发给 leveldb.Batch 的方法 Reset() b.b.Reset() 更新字节数 b.size = 0 5. type table struct数据结构 table 封装了数据库 Database,描述 Database 中的 key 具有相同的前缀 prefix。db Database: 数据库 prefix string: 前缀 5.1 func NewTable(db Database, prefix string) Database构造函数 NewTable() 创建数据库,同时数据库中的 key 具有相同的前缀 prefix。参数:db Database: 数据库 prefix string: 前缀 返回值:数据库 主要实现:return &table 5.2 func (dt *table) Put(key []byte, value []byte) error方法 Put() 实现了接口 ethdb.Putter 和接口 ethdb.Database。参数:key []byte: key value []byte: value 返回值:出错返回错误消息 error,否则返回 nil 主要实现:给 key 加上前缀 prefix,同时转发给 Database 的方法 Put() dt.db.Put(append([]byte(dt.prefix), key...), value) 5.3 func (dt *table) Has(key []byte) (bool, error)方法 Has() 实现了接口 ethdb.Database。参数:key []byte: key 返回值:存在返回 true,否则返回 false 出错返回错误消息 error,否则返回 nil 主要实现:给 key 加上前缀 prefix,同时转发给 Database 的方法 Has() dt.db.Has(append([]byte(dt.prefix), key...)) 5.4 func (dt *table) Get(key []byte) ([]byte, error)方法 Get() 实现了接口 ethdb.Database。参数:key []byte: key 返回值:存在返回 key 对应的 value 出错返回错误消息 error,否则返回 nil 主要实现:给 key 加上前缀 prefix,同时转发给 Database 的方法 Get() dt.db.Get(append([]byte(dt.prefix), key...)) 5.5 func (dt *table) Delete(key []byte) error方法 Delete() 实现了接口 ethdb.Deleter 和接口 ethdb.Database。参数:key []byte: key 返回值:出错返回错误消息 error,否则返回 nil 主要实现:给 key 加上前缀 prefix,同时转发给 Database 的方法 Delete() dt.db.Delete(append([]byte(dt.prefix), key...)) 5.6 func (dt *table) Close()方法 Close() 不执行任何操作。注意,这里并不会关闭底层数据库。5.7 func (dt *table) NewBatch() Batch方法 NewBatch() 返回具有批处理能力的 ethdb.table。返回值:具有批处理能力的 ethdb.tableBatch 主要实现:return &tableBatch 6. type tableBatch struct数据结构 tableBatch 封装了数据库 Database,描述 Database 中的 key 具有相同的前缀 prefix。同时,提供批处理能力。batch Batch: 批处理 prefix string: 前缀 6.1 func NewTableBatch(db Database, prefix string) Batch构造函数 NewTableBatch() 创建具有批处理能力的数据库,同时数据库中的 key 具有相同的前缀 prefix。参数:db Database: 数据库 prefix string: 前缀 返回值:具有批处理能力数据库 主要实现:return &tableBatch 6.2 func (tb *tableBatch) Put(key, value []byte) error方法 Put() 实现了接口 ethdb.Putter 和接口 ethdb.Batch,将 key & value 插入数据库。参数:key []byte: key value []byte: value 返回值:出错返回错误消息 error,否则返回 nil 主要实现:给 key 加上前缀 prefix,同时转发给 ethdb.Batch 的方法 Put() return tb.batch.Put(append([]byte(tb.prefix), key...), value) 6.3 func (tb *tableBatch) Delete(key []byte) error方法 Delete() 实现了接口 ethdb.Deleter 和接口 ethdb.Batch,从数据库中删除给定的 key。参数:key []byte: key 返回值:出错返回错误消息 error,否则返回 nil 主要实现:给 key 加上前缀 prefix,同时转发给 ethdb.Batch 的方法 Delete() return tb.batch.Delete(append([]byte(tb.prefix), key...)) 6.4 func (tb *tableBatch) Write() error方法 Write() 实现了接口 ethdb.Batch,将批处理数据一次性写入数据库。返回值:出错返回错误消息 error,否则返回 nil 主要实现:转发给 ethdb.Batch 的方法 Write() return tb.batch.Write() 6.5 func (tb *tableBatch) ValueSize() int方法 ValueSize() 实现了接口 ethdb.Batch,返回批处理数据的字节数。返回值:批处理数据字节数 主要实现:转发给 ethdb.Batch 的方法 ValueSize() return tb.batch.ValueSize() 6.6 func (tb *tableBatch) Reset()方法 Reset() 实现了接口 ethdb.Batch,清空批处理数据。主要实现:转发给 ethdb.Batch 的方法 Reset() tb.batch.Reset() Referencehttps://github.com/ethereum/go-ethereum/blob/master/ethdb/database.go ContributorWindstamp, https://github.com/windstamp
2023年03月03日
19 阅读
0 评论
0 点赞
2023-03-03
Go-ethereum 源码解析之 miner/worker.go (下)
Go-ethereum 源码解析之 miner/worker.go (下)Appendix D. 详细批注1. constresultQueueSize: 指用于监听验证结果的通道(worker.resultCh)的缓存大小。这里的验证结果是已经被签名了的区块。 txChanSize: 指用于监听事件 core.NewTxsEvent 的通道(worker.txsCh)的缓存大小。这里的缓存大小引用自事务池的大小。其中,事件 core.NewTxsEvent 是事务列表( []types.Transaction)的封装器。 chainHeadChanSize: 指用于监听事件 core.ChainHeadEvent 的通道(worker.chainHeadCh)的缓存大小。事件 core.ChainHeadEvent 是区块(types.Block)的封装器。 chainSideChanSize: 指用于监听事件 core.ChainSideEvent 的通道(worker.chainSideCh)的缓存大小。事件 core.ChainSideEvent 是区块(types.Block)的封装器。 resubmitAdjustChanSize: 指用于重新提交间隔调整的通道(worker.resubmitAdjustCh)的缓存大小。 缓存的消息结构为 intervalAdjust,用于描述下一次提交间隔的调整因数。 miningLogAtDepth: 指记录成功挖矿时需要达到的确认数。是 miner.unconfirmedBlocks 的深度 。即本地节点挖出的最新区块如果需要得到整个网络的确认,需要整个网络再挖出 miningLogAtDepth 个区块。举个例子:本地节点挖出了编号为 1 的区块,需要等到整个网络中某个节点(也可以是本地节点)挖出编号为 8 的区块(8 = 1 + miningLogAtDepth, miningLogAtDepth = 7)之后,则编号为 1 的区块就成为了经典链的一部分。 minRecommitInterval: 指使用任何新到达的事务重新创建挖矿区块的最小时间间隔。当用户设定的重新提交间隔太小时进行修正。 maxRecommitInterval: 指使用任何新到达的事务重新创建挖矿区块的最大时间间隔。当用户设定的重新提交间隔太大时进行修正。 intervalAdjustRatio: 指单个间隔调整对验证工作重新提交间隔的影响因子。与参数 intervalAdjustBias 一起决定下一次提交间隔。 intervalAdjustBias: 指在新的重新提交间隔计算期间应用intervalAdjustBias,有利于增加上限或减少下限,以便可以访问限制。与参数 intervalAdjustRatio 一起决定下一次提交间隔。 staleThreshold: 指可接受的旧区块的最大深度。注意,目前,这个值与 miningLogAtDepth 都是 7,且表达的意思也基本差不多,是不是有一定的内存联系。 2. type environment struct数据结构 environment 描述了 worker 的当前环境,并且包含所有的当前状态信息。最主要的状态信息有:签名者(即本地节点的矿工)、状态树(主要是记录账户余额等状态?)、缓存的祖先区块、缓存的叔区块、当前周期内的事务数量、当前打包中区块的区块头、事务列表(用于构建当前打包中区块)、收据列表(用于和事务列表一一对应,构建当前打包中区块)。signer types.Signer: 签名者,即本地节点的矿工,用于对区块进行签名。 state *state.StateDB: 状态树,用于描述账户相关的状态改变,merkle trie 数据结构。可以在此修改本节节点的状态信息。 ancestors mapset.Set: ??? ancestors 区块集合(用于检查叔区块的有效性)。缓存。缓存数据结构中往往存的是区块的哈希。可以简单地认为区块、区块头、区块哈希、区块头哈希能够等价地描述区块,其中的任何一种方式都能惟一标识同一个区块。甚至可以放宽到区块编号。 family mapset.Set: ??? family 区块集合(用于验证无效叔区块)。family 区块集合比 ancestors 区块集合多了各祖先区块的叔区块。ancestors 区块集合是区块的直接父区块一级一级连接起来的。 uncles mapset.Set: 叔区块集合,即当前区块的叔区块集合,或者说当前正在挖的区块的叔区块集合。 tcount int: 一个周期里面的事务数量 gasPool *core.GasPool: 用于打包事务的可用 gas header *types.Header: 区块头。区块头需要满足通用的以太坊协议共识,还需要满足特定的 PoA 共识协议。与 PoA 共识协议相关的区块头 types.Header 字段用 Clique.Prepare() 方法进行主要的设置,Clique.Finalize() 方法进行最终的补充设置。那么以太坊协议共识相关的字段在哪里设置?或者说在 worker 的哪个方法中设置。 txs []*types.Transaction: 事务(types.Transaction)列表。当前需要打包的事务列表(或者备选事务列表),可不可以理解为事务池。 receipts []*types.Receipt: 收据(types.Receipt)列表。Receipt 表示 Transaction 一一对应的结果。 3. type task struct数据结构 task 包含共识引擎签名和签名之后的结果提交的所有信息。签名即对已经组装好的区块添加最后的签名信息。添加了签名的区块即为最终的结果区块,即签名区块或待确认区块。数据结构 task 和数据结构 environment 的区别:数据结构 environment 用于 worker 的所有操作 数据结构 task 仅用于 worker 的签名相关操作 receipts []*types.Receipt: 收据(types.Receipt)列表 state *state.StateDB: 状态树,用于描述账户相关的状态改变,merkle trie 数据结构。可以在此修改本节节点的状态信息。 block *types.Block: 待签名的区块。此时,区块已经全部组装好了,包信了事务列表、叔区块列表。同时,区块头中的字段已经全部组装好了,就差最后的签名。签名后的区块是在此原有区块上新创建的区块,并被发送到结果通道,用于驱动本地节点已经挖出新区块之后的流程。 createdAt time.Time: task 的创建时间 数据结构 task 也是通道 worker.taskCh 发送或接收的消息。4. constcommitInterruptNone 无效的中断值 commitInterruptNewHead 用于描述新区块头到达的中断值,当 worker 启动或重新启动时也是这个中断值。 commitInterruptResubmit 用于描述 worker 根据接收到的新事务,中止之前挖矿,并重新开始挖矿的中断值。 5. type newWorkReq struct数据结构 newWorkReq 表示使用相应的中断值通知程序提交新签名工作的请求。数据结构 newWorkReq 也是通道 worker.newWorkCh 发送或接收的消息。interrupt *int32: 具体的中断值,为 commitInterruptNewHead 或 commitInterruptResubmit 之一。 noempty bool: ??? 表示创建的区块是否包含事务? timestamp int64: ??? 表示区块开始组装的时间? 6. type intervalAdjust struct数据结构 intervalAdjust 表示重新提交间隔调整。ratio float64: 间隔调整的比例 inc bool: 是上调还是下调 在当前区块时计算下一区块的出块大致时间,在基本的时间间隔之上进行一定的微调,微调的参数就是用数据结构 intervalAdjust 描述的,并发送给对应的通道 resubmitAdjustCh。下一个区块在打包时从通道 resubmitAdjustCh 中获取其对应的微调参数 intervalAdjust 实行微调。7. type worker structworker 是负责向共识引擎提交新工作并且收集签名结果的主要对象。共识引擎会做哪些工作呢?通过方法 Clique.Prepare() 设置区块头中关于 PoA 共识的相关字段。 通过方法 Clique.Finalize() 组装可以被签名的区块。 通过方法 Clieque.Seal() 对区块进行签名,并发送给结果通道 worker.resultsCh。 通过方法 Clique.snapshot() 处理两种快照:检查点快照和投票快照。 那么共识引擎需要哪些输入呢?区块头 事务列表 收据列表 状态树 叔区块列表(PoA 共识协议中肯定为 nil) 区块,是个抽象概念,主要包含:区块头、事务列表、叔区块列表,但是并不包含收据列表。 那么共识引擎会产生哪些输出呢?方法 Clieque.Seal() 会将最终签名后的区块发送给结果通道 worker.resultsCh。 config *params.ChainConfig: 区块链的链配置信息,包含链 ID,是 ethash 还是 clique 共识协议等 engine consensus.Engine: 共识引擎接口 eth Backend: 后端,包含区块链和事务池,提供挖矿所需的所有方法 chain *core.BlockChain: 表示整个区块链。这不和 eth 中的区块链是同一个? gasFloor uint64: 最低 gas gasCeil uint64: 最高 gas // 订阅mux *event.TypeMux: 可以简单地理解为事件的订阅管理器,即注册事件的响应函数,和驱动事件的响应函数。 txsCh chan core.NewTxsEvent: 用于在不同协程之间交互事件 core.NewTxsEvent 的通道。事件 core.NewTxsEvent 是事务列表 []*types.Transaction 的封装器,即通道 txsCh 用于在不同协程之间交互事务列表。命名协程 worker.mainLoop() 从通道 txsCh 接收事件 core.NewTxsEvent,即事务列表。使用通道 txsCh 作为只接收消息的通道向 core.TxPool 订阅事件 core.NewTxsEvent,那么应该是从 core.TxPool 发送事件 core.NewTxsEvent 到通道 txsCh。 txsSub event.Subscription: 向事务池(core.TxPool)订阅事件 core.NewTxsEvent,并使用通道 txsCh 作为此次订阅接收消息的通道。代码为 worker.txsSub = eth.TxPool().SubscribeNewTxsEvent(worker.txsCh)。 chainHeadCh chan core.ChainHeadEvent: 用于在不同协程之间交互事件 core.ChainHeadEvent 的通道。事件 core.ChainHeadEvent 是区块 types.Block 的封装器,即通道 chainHeadCh 用于不同协程之间交互新挖出的区块头。命名协程 worker.newWorkLoop() 从通道 chainHeadCh 接收事件 core.ChainHeadEvent,即新的区块头。使用通道 chainHeadCh 作为只接收消息的通道向 core.BlockChain 订阅事件 core.ChainHeadEvent,那么应该是从 core.BlockChain 发送事件 core.ChainHeadEvent 到通道 chainHeadCh。 chainHeadSub event.Subscription: 向区块链(core.BlockChain)订阅事件 core.ChainHeadEvent,并使用通道 chainHeadCh 作为此次订阅接收消息的通道。代码为 worker.chainHeadSub = eth.BlockChain().SubscribeChainHeadEvent(worker.chainHeadCh) chainSideCh chan core.ChainSideEvent: 用于在不同协程之间交互事件 core.ChainSideEvent 的通道。事件 core.ChainSideEvent 是区块 types.Block 的封装器,即通道 chainSideCh 用于不同协程之间交互新挖出的区块头。命名协程 worker.mainLoop() 从通道 chainSideCh 接收事件 core.ChainSideEvent,即新的叔区块头(但 PoA 不是不存在叔区块?)。使用通道 chainSideCh 作为只接收消息的通道向 core.BlockChain 订阅事件 core.ChainSideEvent,那么应该是从 core.BlockChain 发送事件 core.ChainSideEvent 到通道 chainSideCh。 chainSideSub event.Subscription: 向区块链(core.BlockChain)订阅事件 core.ChainSideEvent,并使用通道 chainSideCh 作为此次订阅接收消息的通道。代码为 worker.chainSideSub = eth.BlockChain().SubscribeChainSideEvent(worker.chainSideCh) // 通道newWorkCh chan *newWorkReq: 通道 newWorkCh 用于在不同协程之间交互消息 newWorkReq 的通道。命名协程 worker.newWorkLoop() 将消息 newWorkReq 发送给通道 newWorkCh。命名协程 worker.mainLoop() 从通道 newWorkCh 中接收消息 newWorkReq。 taskCh chan *task: 通道 taskCh 用于在不同协程之间交互消息 task 的通道。(1)命名协程 worker.taskLoop() 从通道 taskCh 中接收消息 task。对接收到的消息 task 先存入待处理 map 中,其中 Key 为 task 中的区块签名哈希,Value 为 task。同时,将 task 中的区块传递给共识引擎的签名方法 w.engine.Seal() 进行签名,同时将结果通道 w.resultCh 和退出通道 stopCh 也传递给共识引擎的签名方法,以便从中接收签名之后的区块或者接收中止消息。(2)命名协程 worker.mainLoop() 中的方法 worker.commit() 将消息 task 发送给通道 taskCh。此方法先将当前环境中的区块头(w.current.header)、事务列表(w.current.txs)、收据列表(w.current.receipts)作为参数传递给共识引擎的方法 Finalize() 组装出待签名的区块,代码为 block = w.engine.Finalize(w.chain, w.current.header, s, w.current.txs, uncles, w.current.receipts)。需要注意的是,区块 types.Block 中只包含区块头 types.Header、事务列表 []types.Transaction、叔区块列表 []types.Header,并不包含收据列表 []types.Receipt,但是区块头 types.Header 中的字段 ReceiptHash 是收据列表树的根哈希,所以也需要收据列表参数。将组装后的待签名区块 types.Block,及前面解释过的收据列表 []types.Receipt 等其它参数一起构建出新的任务 task 发送给通道 taskCh,同时输出一条重要的日志信息:log.Info("Commit new mining work", "number", block.Number(), "sealhash", w.engine.SealHash(block.Header()), "uncles", len(uncles), "txs", w.current.tcount, "gas", block.GasUsed(), "fees", feesEth, "elapsed", common.PrettyDuration(time.Since(start)))。到方法 commit() 这一步,已经组装出了新的任务 task,并将此新任务 task 通过通道 taskCh 发送给命名协程 worker.taskLoop()。 resultCh chan *types.Block: 通道 resultCh 用于在不同协程之间交互消息 types.Block。(1)命名协程 worker.resultLoop() 从通道 resultCh 中接收消息 types.Block,且此区块是被签名过的。对于新接收到签名区块,首先判断这个签名区块是否为重复的;其次,需要从待处理任务映射 w.pendingTasks 中获得对应区块签名哈希的任务 task,如果没找到则输出一条重要的日志信息:log.Error("Block found but no relative pending task", "number", block.Number(), "sealhash", sealhash, "hash", hash)。并从 task 中恢复 receipts 和 logs。第三,将签名区块及其对应的收据列表和状态树等信息写入数据库。如果写入失败,则输出一条重要的日志信息:log.Error("Failed writing block to chain", "err", err),否则输出一条重要的日志信息:log.Info("Successfully sealed new block", "number", block.Number(), "sealhash", sealhash, "hash", hash, "elapsed", common.PrettyDuration(time.Since(task.createdAt)))。第四,通过新挖出的签名区块构建事件 core.NewMinedBlockEvent,并通过事件订阅管理器中的方法 w.mux.Post() 将本地节点最新签名的区块向网络中其它节点进行广播,这是基于 p2p 模块完成的。第五,同时构建事件 core.ChainEvent 和事件 core.ChainHeadEvent,或者构建事件 core.ChainSideEvent,并通过区块链中的方法 w.chain.PostChainEvents() 进行广播。需要注意的时,此广播只是针对向本地节点进行了事件注册的客户端,且是通过 JSON-RPC 完成,和第四步中的向网络中其它节点通过 p2p 进行广播是完全不同的。这一部的广播即使没有事件接收方也没有问题,因为这是业务逻辑层面的,而第四步中的广播则是必须有接收方的,否则就会破坏以太坊协议本身。比如:我们可以注册一个事件,用于监控是否有最新的区块被挖出来,然后在此基础上,查询指定账户的最新余额。第六步,将新挖出来的签名区块,添加进待确认队列中,代码为:w.unconfirmed.Insert(block.NumberU64(), block.Hash())。(2)共识引擎中的签名方法 Clique.Seal() 通过匿名协程将签名后的签名区块 types.Block 发送到通道 resultCh。 startCh chan struct: 通道 startCh 用于在不同协程之间交互消息 struct。可以发现,消息 struct 没有包含任何有意义的信息,这在 Go 中是一类特别重要的写法,用于由某个协程向另一个协程发送开始或中止消息。(1)函数 newWorker() 向通道 startCh 发送消息 struct,其中函数 newWorker() 应该是运行在主协程中或由其它某个包中的协程启动。代码为:worker.startCh <- struct。(2)方法 worker.start() 向通道 startCh 发送消息 struct,其它同(1)。(3)命名协程 worker.newWorkLoop() 从通道 startCh 中接收消息 struct。需要注意的是,(1)和(2)都可以向通道 startCh 发送消息 struct 驱动命名协程 worker.newWorkLoop() 中逻辑。方法 worker.start() 表明 worker 是可以先停止的,而不关闭,之后可以重新启动。 exitCh chan struct: 通道 exitCh 用于在不同协程之间交互消息 struct。可以参考通道 startCh 中的注释。(1)函数 worker.close() 通过调用函数 close(w.exitCh) 整个关闭通道 exitCh。(2)命名协程 worker.newWorkLoop() 从通道 exitCh 中接收消息,从而结束整个协程。(3)命名协程 worker.mainLoop() 从通道 exitCh 中接收消息,从而结束整个协程。(4)命名协程 worker.taskLoop() 从通道 exitCh 中接收消息,从而结束整个协程。(5)命名协程 worker.resultLoop() 从通道 exitCh 中接收消息,从而结束整个协程。(6)命名协程 worker.mainLoop() 调用的方法 worker.commit() 从通道 exitCh 中接收消息,从而放弃后续的工作。 resubmitIntervalCh chan time.Duration: 通道 resubmitIntervalCh 用于在不同的协程之间交互消息 time.Duration。time.Duration 是 Go 语言标准库中的类型,在这里通道 resubmitIntervalCh 起到一个定时器的作用,这也是 Go 语言中关于定时器的标准实现方式。(1)方法 worker.setRecommitInterval() 向通道 resubmitIntervalCh 发送消息 time.Duration,即设置定时器下一次触发的时间。方法 worker.setRecommitInterval() 在方法 Miner.SetRecommitInterval() 中被调用,方法 Miner.SetRecommitInterval() 又在方法 PrivateMinerAPI.SetRecommitInterval() 中调用,这应该是从外部通过 JSON-RPC 接口驱动的。(2)命名协程 worker.newWorkLoop() 从通道 resubmitIntervalCh 中接收消息 time.Duration,即获得希望定时器下一次触发的时间,并根据需要对这个时间进行一定的修正。 resubmitAdjustCh chan *intervalAdjust: 通道 resubmitAdjustCh 用于在不同的协程之间交互消息 intervalAdjust。(1)命名协程 worker.newWorkLoop() 从通道 resubmitAdjustCh 中接收消息 intervalAdjust。(2)方法 worker.commitTransactions() 向通道 resubmitAdjustCh 中发送消息 intervalAdjust。通道 resubmitAdjustCh 与通道 resubmitIntervalCh 的作用类似,都是修改下一个区块的出块时间。只不过通道 resubmitAdjustCh 中交互的消息 time.Duration 是由外部通过 JSON-RPC 接口来设定的,而通道 resubmitIntervalCh 中交互的消息 intervalAdjust 是矿工根据上一个区块的出块时间基于算法自定调整的。 current *environment: 描述了 worker 的当前环境和状态信息。具体的请参考对数据结构 environment 的注释。 possibleUncles map[common.Hash]*types.Block: 可能的叔区块集合。Key 为区块哈希 common.Hash,Value 为区块 types.Block。 unconfirmed *unconfirmedBlocks: 本地节点最近新挖出的区块集合,用于等待网络中其它节点的确认,从而成为经典链的一部分。具体的可以参考对数据结构 unconfirmedBlocks 的注释。 mu sync.RWMutex: 锁,用于保护字段 coinbase 和 extra。 coinbase common.Address: 矿工地址。 extra []byte: 分为三段:前 32 字节矿工可随意填写,最后 65 字节为对区块头的签名,中间的字节为授权签名者列表的有序列连接,且字节数为 20 的倍数。 pendingMu sync.RWMutex: 锁,用于保护字段 pendingTasks。 pendingTasks map[common.Hash]*task: 待处理的任务映射,其中:Key 为 task 中包含的区块的哈希值,Value 为 task。 snapshotMu sync.RWMutex: 锁,用于保护字段 snapshotBlock 和 snapshotState。 snapshotBlock *types.Block: 区块的快照。 snapshotState *state.StateDB: 状态的快照。 // 原子状态的计数器running int32: 用于表示共识引擎是否正在运行。 newTxs int32: 自从上次签名工作提交之后新到达的事务数量。上次签名工作即指 worker 中已经通过调用共识引擎的 Finalize() 方法组装好了待签名的区块,然后通过调用共识引擎的签名方法 Clique.Seal() 对待签名区块进行签名。即在上一个区块被本地节点挖出之后,新来的事务数量。 // Test hooksnewTaskHook func(*task): 接收到新签名任务时调用此方法。 skipSealHook func(*task) bool: 判定是否跳过签名时调用 此方法。 fullTaskHook func(): 在推送完整签名任务之前调用此方法。 resubmitHook func(time.Duration, time.Duration): 更新重新提交间隔时调用此方法。 (1) func newWorker(config *params.ChainConfig, engine consensus.Engine, eth Backend, mux *event.TypeMux, recommit time.Duration, gasFloor, gasCeil uint64) *worker构造函数 newWorker() 用于根据给定参数构建 worker。主要参数:config *params.ChainConfig: 链的配置信息 engine consensus.Engine: 共识引擎 eth Backend: 以太坊本地节点的后端 mux *event.TypeMux: 事件订阅管理器 recommit time.Duration: 下一次任务的基础时间间隔 gasFloor, gasCeil uint64: Gas 的下限 gasFloor 和上限 gasCeil。 主要实现:首先构建对象 worker,并设定大部分字段的初始值。 向事务池 core.TxPool 订阅事件 core.NewTxsEvent,并通过通道 worker.txsCh 接收事件 core.NewTxsEvent。 worker.txsSub = eth.TxPool().SubscribeNewTxsEvent(worker.txsCh) 向区块链 core.BlockChain 订阅事件 core.ChainHeadEvent,并通过通道 worker.chainHeadCh 接收事件 core.ChainHeadEvent。 worker.chainHeadSub = eth.BlockChain().SubscribeChainHeadEvent(worker.chainHeadCh) 向区块链 core.BlockChain 订阅事件 core.ChainSideEvent,并通过通道 worker.chainSideCh 接收事件 worker.ChainSideEvent。 worker.chainSideSub = eth.BlockChain().SubscribeChainSideEvent(worker.chainSideCh) 如果用户设定的重新提交间隔 recommit 太短,则重新设定 recommit = minRecommitInterval。同时,输出日志信息:log.Warn("Sanitizing miner recommit interval", "provided", recommit, "updated", minRecommitInterval) 启动新的独立协程运行方法 worker.mainLoop()。 启动新的独立协程运行方法 worker.newWorkLoop(recommit)。 启动新的独立协程运行方法 worker.resultLoop()。 启动新的独立协程运行方法 worker.taskLoop()。 提交第一个工作以初始化待处理状态。即给通道 startCh 发送消息。 worker.startCh <- struct (2) func (w *worker) setEtherbase(addr common.Address)方法 setEtherbase() 设置用于初始化区块 coinbase 字段的 etherbase。参数:addr common.Address: 地址 主要实现:加锁和解锁 w.coinbase = addr (3) func (w *worker) setExtra(extra []byte)方法 setExtra() 设置用于初始化区块额外字段的内容。参数:extra []byte: 应该是用于区块头 types.Header 中的字段 Extra 的前 32 字节。这 32 字节是以太坊协议规定在区块中用于存储矿工相关的一些额外信息。上层调用方法 miner.Miner.SetExtra(),继续上层调用方法为 eth.Ethereum 的构造函数 eth.New() 中的代码 eth.miner.SetExtra(makeExtraData(config.MinerExtraData))。这个参数最终是通过 geth 的 MINER OPTIONS 命令行参数 --extradata,或者 ETHEREUM OPTIONS 的命令行参数 --config,这是一个 TOML 配置文件。 (4) func (w *worker) setRecommitInterval(interval time.Duration)方法 setRecommitInterval() 更新矿工签名工作重新提交的间隔。参数:interval time.Duration: 重新提交的时间间隔。 主要实现:将重新提交的间隔 interval 发送到通道 worker.resubmitIntervalCh,代码为:w.resubmitIntervalCh <- interval。命名协程 worker.newWorkLoop() 会从通道 worker.resubmitIntervalCh 中接收此消息。 (5) func (w worker) pending() (types.Block, *state.StateDB)方法 pending() 返回待处理的状态和相应的区块。主要实现:加锁、解锁 snapshotMu。 返回字段 snapshotBlock 和字段 snapshotState 的副本。 (6) func (w *worker) pendingBlock() *types.Block方法 pendingBlock() 返回待处理的区块。主要实现:加锁、解锁 snapshotMu。 返回字段 snapshotBlock。 (7) func (w *worker) start()方法 start() 采用原子操作将 running 字段置为 1,并触发新工作的提交。主要实现:atomic.StoreInt32(&w.running, 1) w.startCh <- struct (8) func (w *worker) stop()方法 stop() 采用原子操作将 running 字段置为 0。主要实现:atomic.StoreInt32(&w.running, 0) (9) func (w *worker) isRunning() bool方法 isRunning() 返回 worker 是否正在运行的指示符。主要实现:return atomic.LoadInt32(&w.running) == 1 (10) func (w *worker) close()方法 close() 终止由 worker 维护的所有后台线程。注意 worker 不支持被关闭多次,这是由 Go 语言不允许多次关闭同一个通道决定的。主要实现close(w.exitCh) (11) func (w *worker) newWorkLoop(recommit time.Duration)方法 newWorkLoop() 是一个独立的协程,基于接收到的事件提交新的挖矿工作。不妨将此协程称作命名协程 worker.newWorkLoop()。参数:recommit time.Duration: 下一次提交间隔。 主要实现:定义了三个变量: interrupt *int32: 中断信号 minRecommit = recommit: 用户指定的最小重新提交间隔 timestamp int64: 每轮挖矿的时间戳 定义一个定时器,并丢弃初始的 tick timer := time.NewTimer(0) <-timer.C 定义内部提交函数 commit() 提交函数 commit() 使用给定信号中止正在进行的交易执行,并重新提交新信号。 构建新工作请求 newWorkReq,并发送给通道 newWorkCh 来驱动命名协程 worker.mainLoop() 来重新提交任务。 设置定时器 timer 的下一次时间。代码为:timer.Reset(recommit) 重置交易计数器。代码为:atomic.StoreInt32(&w.newTxs, 0) 定义内部函数 recalcRecommit() 根据一套规则来计算重新提交间隔 recommit。 具体规则后续补充注释。 定义内部函数 clearPending() 此函数用于清除过期的待处理任务。 参数 number uint64: 区块编号 加锁 w.pendingMu.Lock() 循环迭代 w.pendingTasks 区块签名哈希 h 任务 t 如果 t 中的区块编号比 number 要早 staleThreshold 个区块,则将其从 w.pendingTasks 中删除。 解锁 w.pendingMu.Unlock() 在 for 循环中持续从通道 startCh、timer.C、resubmitIntervalCh、resubmitAdjustCh 和 exitCh 中接收消息,并执行相应的逻辑。 startCh: 调用内部函数 clearPending() 清除链上当前区块之前的过期待处理任务。 调用内部函数 commit(false, commitInterruptNewHead) 提交新的 newWorkReq。 chainHeadCh: 从通道 chainHeadCh 接收消息 head(事件 core.ChainHeadEvent) 调用内部函数 clearPending() 清除 core.ChainHeadEvent 中区块之前的过期待处理任务。 调用内部函数 commit(false, commitInterruptNewHead) 提交新的 newWorkReq。 timer.C 如果挖矿正在进行中,则定期重新提交新的工作周期以提取更高价格的交易。禁用待处理区块的此开销。 如果交易计数器 w.newTxs 为 0 重置定时器。代码为:timer.Reset(recommit) 退出本轮迭代。 调用内部函数 commit(false, commitInterruptResubmit) 提交新的 newWorkReq。 timer.C: 如果挖矿正在进行中,则定期重新提交新的工作周期以便更新到价格较高的交易。对于待处理中的区块禁用此操作开销。 对于 poa 共识引擎,需要其配置的 Clique.Period > 0。!!!等于这里对于共识算法有个特殊处理。 调用内部函数 commit(true, commitInterruptResubmit) 提交新的 newWorkReq。 【批注 1】,这里用到了 time.Timer 将定时器,时间间隔为 recommit。 【批注 2】,通道主要的作用是用于协程之间交互消息,那么实际上影响到的就是工作流程。这个定时器应该主要就是挖矿有周期性的概念,比如 15 秒产生一个块。存在两个定时间隔,一个是静态配置的,另一个是由挖矿动态决定的。当挖矿的实际时间长于静态设定的,那么可能需要做一些操作,比如重新挖矿等等吧。当挖矿的实际时间适于静态设定的,可能不需要做什么操作。 resubmitIntervalCh: 支持由用户来重新设定重新提交的间隔。 用户设定的值不能小于 minRecommitInterval。 如果回调函数 resubmitHook 不空,则调用。 resubmitAdjustCh: 根据挖矿的反馈来动态地调整重新提交的间隔。 如果回调函数 resubmitHook 不空,则调用。 exitCh: 接收到退出消息,退出整个协程。 命名协程 worker.mainLoop() 用于根据接收到的事件生成签名任务,命名协程 worker.taskLoop() 用于接收上述验证任务并提交给共识引擎,命名协程 worker.resultLoop() 用于处理签名结果的提交并更新相关数据到数据库中。(12) func (w *worker) mainLoop()方法 mainLoop() 是一个独立的协程,用于根据接收到的事件重新生成签名任务。不妨将此协程称作命名协程 worker.mainLoop()。主要实现:在整个协程退出时,取消 txsSub、chainHeadSub、chainSideSub 这三个订阅。 defer w.txsSub.Unsubscribe() defer w.chainHeadSub.Unsubscribe() defer w.chainSideSub.Unsubscribe() 在 for 循环中持续从通道 newWorkCh、chainSideCh、txCh 和 exitCh 中接收消息,并执行相应的逻辑。 newWorkCh: 根据新接收到的消息 req(数据结构为 newWorkReq),调用函数 commitNewWork() 提交新的任务。代码为:w.commitNewWork(req.interrupt, req.noempty, req.timestamp)。需要说明的,虽然方法 commitNewWork() 中的参数没有包含任何区块、交易等信息,但这些信息都包含在当前环境 w.current 或 w 中。同时,任务最终通过通道 worker.taskCh 提交给命名协程 worker.taskLoop()。 chainSideCh: 接收到新的消息 ev(事件 ChainSideEvent) 如果 ev 中携带的区块已经在 possibleUncles 中,则退出本轮迭代。 把 ev 携带的区块添加到 possibleUncles中。代码为:w.possibleUncles[ev.Block.Hash()] = ev.Block。 如果正在挖矿中的区块所包含的叔区块少于 2 个,且 ev 中携带的新叔区块有效,则重新生成挖矿中的任务。见代码:if w.isRunning() && w.current != nil && w.current.uncles.Cardinality() < 2 获取任务开始时间 start。代码为:start := time.Now() 通过方法 commitUncle() 将 ev 中携带的区块添加到 current.uncles 中。如果成功 定义新任务中所需要的区块头列表 uncles。代码为:var uncles []*types.Header 遍历 w.current.uncles 中的每个 uncle hash 从 possibleUncles 中找到 uncle hash 对应的区块头,并添加到 uncles 中。代码为:uncles = append(uncles, uncle.Header()) 并根据最终获得的所有叔区块头列表 uncles 来调用方法 commit() 提交最终区块。代码为:w.commit(uncles, nil, true, start) 【批注 1】:possibleUncles 用于包含可能的叔区块,起到一个缓冲的作用。 current.uncles 是当前要打包的区块中已经被确认的叔区块。 【批注 2】:possibleUncles 是<区块头哈希>区块构成的 map,current.uncles 则仅包含了区块头哈希。 txsCh:根据新接收到的消息 ev(事件 core.NewTxsEvent) 如果不在挖矿状态,则将交易置于待处理状态。 注意,收到的所有交易可能与已包含在当前挖矿区块中的交易不连续。这些交易将自动消除。 if !w.isRunning() && w.current != nil 加锁、解锁的方式获取矿工地址 coinbase。代码为:coinbase := w.coinbase 定义变量 txs。代码为:txs := make(map[common.Address]types.Transactions) 遍历消息 ev 中携带的交易列表,对于每个交易 tx 还原出每个交易 tx 的发送者地址 acc 更新映射 txs。代码为:txs[acc] = append(txs[acc], tx) 将 txs 转换为 txset(数据结构为 types.TransactionsByPriceAndNonce),代码为:txset := types.NewTransactionsByPriceAndNonce(w.current.signer, txs) 提交交易列表 txset。代码为:w.commitTransactions(txset, coinbase, nil) 更新快照。代码为:w.updateSnapshot() else 如果我们正在挖矿中,但没有正在处理任何事情,请在新交易中醒来 if w.config.Clique != nil && w.config.Clique.Period == 0 w.commitNewWork(nil, false, time.Now().Unix()) 采用原子操作将 w.newTxs 的数量增加新接收到的事务数量。代码为:atomic.AddInt32(&w.newTxs, int32(len(ev.Txs))) w.exitCh 当从退出通道接收到消息时,结束整个协程。 w.txsSub.Err() 当从交易订阅通道接收到错误消息时,结束整个协程。 w.chainHeadSub.Err() 当从区块头订阅通道接收到错误消息时,结束整个协程。 w.chainSideSub.Err() 当从侧链区块头订阅通道接收到错误消息时,结束整个协程。 (13) func (w *worker) taskLoop()方法 taskLoop() 是一个独立的协程,用于从生成器中获取待签名任务,并将它们提交给共识引擎。不妨将此协程称作命名协程 worker.taskLoop()。主要实现:定义两个变量:退出通道 stopCh 和上一个区块哈希 prev stopCh chan struct prev common.Hash 定义局部中断函数 interrupt(),用于关闭退出通道 stopCh,结束所有从退出通道 stopCh 接收消息的协程,这里共识引擎方法 Seal() 中用于签名的独立匿名协程,退出通道 stopCh 是作为参数传递过去的。 close(stopCh) 局部通道 stopCh 和内部函数 interrupt() 用于组合终止进行中的签名任务(in-flight sealing task)。 在 for 循环中持续从通道 taskCh 和 exitCh 中接收消息,并执行相应的逻辑。 taskCh: 接收新任务 task 如果回调 w.newTaskHook != nil,则调用回调函数 w.newTaskHook(task) 获取任务 task 中包含区块的区块签名哈希 sealHash 如果 sealHash == prev,则退出本轮迭代。 过滤掉因重复提交产生的重复的签名任务 调用中断函数 interrupt() 中止共识引擎方法 Seal() 中正在签名的独立匿名协程。这里是通过关闭退出通道 stopCh 实现的。 给退出通道 stopCh 分配空间,并设置上一个区块哈希 prev。 stopCh, prev = make(chan struct), sealHash 如果回调函数 w.skipSealHook() 不为 nil 和 w.skipSealHook(task) 返回 true,则退出本轮迭代。 通过对锁 w.pendingMu 执行加锁、解锁,将任务 task 添加到 w.pendingTasks 中,为之后命名协程 worker.resultLoop() 中接收到已签名区块,查找包含该区块的任务 task 而用。 将任务 task 中包含的区块提交给共识引擎进行签名。代码为:w.engine.Seal(w.chain, task.block, w.resultCh, stopCh) 需要特别注意传递的两个通道参数 w.resutlCh, stopCh 通道 w.resultCh 用于从共识引擎的签名方法 Seal() 中接收已签名区块。 通道 stopCh 用于发送中止信号给共识引擎的签名方法 Seal(),从而中止共识引擎正在进行的签名操作。 如果签名失败,则输出日志信息:log.Warn("Block sealing failed", "err", err) exitCh: 当接收到退出消息时 通过调用内部中断函数 interrupt() 关闭中止通道 stopCh,从而使得共识引擎的签名方法 Seal() 放弃本次签名。 退出整个协程。 (14) func (w *worker) resultLoop()方法 resultLoop() 是一个独立的协程,用于处理签名区块的提交和广播,以及更新相关数据到数据库。不妨将此协程称作命名协程 worker.resultLoop()。主要实现:在 for 循环中持续从通道 resultCh 和 exitCh 中接收消息,并执行相应的逻辑。 resultCh: 接收已签名区块 block。 如果 block == nil,则进入下一轮迭代。 如果区块 block 已经存在于经典链中,则进入下一轮迭代。 定义两个变量: 区块签名哈希 sealhash,代码为:sealhash = w.engine.SealHash(block.Header()) 区块哈希 hash,代码为:hash = block.Hash() 分别计算区块头的验证哈希 sealHash(不包括 extraData 中的最后 65 个字节的签名信息),区块的哈希 hash (即区块头的哈希,而且包含整个 extraData)。 通过对锁 w.pendingMu 进行加锁和解锁的方式从 w.pendingTasks 中找到 sealHash 对应的 task。这是找出已签名区块对应的任务 task,从中获取需要的交易列表、交易回执列表等相关数据。 如果 task 不存在,则输出日志信息:log.Error("Block found but no relative pending task", "number", block.Number(), "sealhash", sealhash, "hash", hash) 同时,退出本次迭代。 定义两个变量,交易回执列表 receipts,交易回执中包含的日志列表 logs。 receipts = make([]*types.Receipt, len(task.receipts)) logs []*types.Log 这是因为不同的区块可能会共享相同的区块签名哈希,建立这些副本是为了防止写写冲突。 更新所有日志中的区块哈希。这是因为对于这些日志来说,直到现在才知道对应的区块哈希,而在创建单个交易的交易回执的接收日志时,并不知道对应的区块哈希。 更新 task.receipts 中各 receipt.Logs 的 BlockHash 值为 hash。 通过方法 w.chain.WriteBlockWithState() 将区块 block,交易回执列表 receipts,状态数据库 task.state 写入数据库,并返回写入状态 stat。stat 的取值:NonStatTy (0)、CanonStatTy (1)、SideStatTy(2)。 如果写入失败,则输出日志信息:log.Error("Failed writing block to chain", "err", err)。同时,退出本轮迭代。 至此,成功的验证了新的区块。输出日志信息:log.Info("Successfully sealed new block", "number", block.Number(), "sealhash", sealhash, "hash", hash, "elapsed", common.PrettyDuration(time.Since(task.createdAt))) 将新产生的新区块 block 广播到网络中的其他节点。这是通过构建事件 core.NewMinedBlockEvent 进调用 w.mux.Post() 实现的。代码为:w.mux.Post(core.NewMinedBlockEvent) 定义变量事件列表 events 根据写入数据库返回的状态 stat 的值: case core.CanonStatTy:在事件列表 events 中添加新的事件 core.ChainEvent、core.ChainHeadEvent case core.SideStatTy:在事件列表 events 中添加新的事件 core.ChainSideEvent。 通过方法 w.chain.PostChainEvents() 广播事件。代码为: w.chain.PostChainEvents(events, logs) 将已签名区块插入待确认区块列表中。代码为:w.unconfirmed.Insert(block.NumberU64(), block.Hash()) exitCh: 接收到退出消息则中止整个协程。 命名协程 worker.resultLoop() 从通道 resultCh 中接收消息 types.Block,且此区块是被签名过的。对于新接收到签名区块,首先判断这个签名区块是否为重复的;其次,需要从待处理任务映射 w.pendingTasks 中获得对应区块签名哈希的任务 task,如果没找到则输出一条重要的日志信息:log.Error("Block found but no relative pending task", "number", block.Number(), "sealhash", sealhash, "hash", hash)。并从 task 中恢复 receipts 和 logs。第三,将签名区块及其对应的收据列表和状态树等信息写入数据库。如果写入失败,则输出一条重要的日志信息:log.Error("Failed writing block to chain", "err", err),否则输出一条重要的日志信息:log.Info("Successfully sealed new block", "number", block.Number(), "sealhash", sealhash, "hash", hash, "elapsed", common.PrettyDuration(time.Since(task.createdAt)))。第四,通过新挖出的签名区块构建事件 core.NewMinedBlockEvent,并通过事件订阅管理器中的方法 w.mux.Post() 将本地节点最新签名的区块向网络中其它节点进行广播,这是基于 p2p 模块完成的。第五,同时构建事件 core.ChainEvent 和事件 core.ChainHeadEvent,或者构建事件 core.ChainSideEvent,并通过区块链中的方法 w.chain.PostChainEvents() 进行广播。需要注意的时,此广播只是针对向本地节点进行了事件注册的客户端,且是通过 JSON-RPC 完成,和第四步中的向网络中其它节点通过 p2p 进行广播是完全不同的。这一部的广播即使没有事件接收方也没有问题,因为这是业务逻辑层面的,而第四步中的广播则是必须有接收方的,否则就会破坏以太坊协议本身。比如:我们可以注册一个事件,用于监控是否有最新的区块被挖出来,然后在此基础上,查询指定账户的最新余额。第六步,将新挖出来的签名区块,添加进待确认队列中,代码为:w.unconfirmed.Insert(block.NumberU64(), block.Hash())。(15) func (w *worker) makeCurrent(parent *types.Block, header *types.Header) error方法 makeCurrent() 为当前周期创建新的环境 environment。参数:parent *types.Block: 父区块 header *types.Header: 当前区块头 主要实现:先通过父区块状态树的根哈希从区块链中获取状态信息 state (state.StateDB),如果失败,直接返回错误 构建当前环境 environment 的对象 env 设定字段 signer 为 types.EIP155Signer 设定字段 state 为前面获取的 state 设定字段 header 为参数 header 默认初始化其它字段 从区块链中获取父区块之前的 7 个高度的所有区块,包含叔区块 所有的直系父区块添加到字段 ancestors 所有的直系父区块和叔区块添加到字段 family 将字段 tcount 设为 0 将环境 env 赋值给字段 worker.current (16) func (w *worker) commitUncle(env *environment, uncle *types.Header) error方法 commitUncle() 将给定的区块添加至叔区块集合中,如果添加失败则返回错误。参数:env *environment: 当前环境,里面组织了本次周期里需要的所有信息 uncle *types.Header: 叔区块的区块头 主要实现:获取叔区块 hash。见代码:hash := uncle.Hash()。 判定叔区块是否惟一。见代码:if env.uncles.Contains(hash) 判定叔区块是否为兄弟区块。见代码:if env.header.ParentHash == uncle.ParentHash 判定叔区块的父区块是否存在于链上。见代码:if !env.ancestors.Contains(uncle.ParentHash) 判定叔区块是否已经存在于链上。见代码:if env.family.Contains(hash) 上述四个判定都通过,则添加到当前区块的叔区块列表中。见代码:env.uncles.Add(uncle.Hash()) (17) func (w *worker) updateSnapshot()方法 updateSnapshot() 更新待处理区块和状态的快照。注意,此函数确保当前变量是线程安全的。主要实现:- 加锁、解锁 w.snapshotMu- 定义叔区块头列表 uncles- 对于 w.current.uncles 中的每个叔区块头 uncle,如果存在于w.possibleUncles 中,则将其没回到 uncles 中。- 由 w.current.header, w.current.txs, uncles, w.current.receipts 构建出快照区块 w.snapshotBlock。- 由 w.current.state 的副本构建出快照状态 w.snapshotState。(18) func (w *worker) commitTransaction(tx types.Transaction, coinbase common.Address) ([]types.Log, error):方法 commitTransaction() 提交交易 tx,并附上交易的发起者地址。此方法会生成交易的交易回执。参数:tx *types.Transaction: 具体的一次交易信息。 coinbase common.Address: 交易的发起方地址,可以明确指定。如果为空,则为区块签名者的地址。 返回值:[]*types.Log: 交易回执中的日志信息。 主要实现:先对状态树进行备份 snap,代码为:snap := w.current.state.Snapshot() 通过对交易 tx 及交易发起者 coinbase 调用方法 core.ApplyTransaction() 获得交易回执 receipt。 如果失败,则将状态树恢复到之前的状态 snap,并直接返回。 更新交易列表。代码为 w.current.txs = append(w.current.txs, tx) 更新交易回执列表。代码为 w.current.receipts = append(w.current.receipts, receipt) (19) func (w *worker) commitTransactions(txs *types.TransactionsByPriceAndNonce, coinbase common.Address, interrupt *int32) bool:方法 commitTransactions() 提交交易列表 txs,并附上交易的发起者地址。根据整个交易列表 txs 是否都被有效提交,返回 true 或 false。参数:txs *types.TransactionsByPriceAndNonce: 交易列表的管理器,同时根据价格和随机数值进行排序,每次输出一个排序最靠前的交易。具体的注释,参考 types.TransactionsByPriceAndNonce。 coinbase common.Address: 交易的发起方地址,可以明确指定。如果为空,则为区块签名者的地址。 interrupt *int32: 中断信号值。需要特别说明,这是个指针类型的值,意味着后续的每轮迭代都能读取外部对于参数 interrupt 的更新。同时,此方法还能将内部对于参数 interrupt 的修改反馈给外部调用者。 返回值:整个交易列表是否都被正确处理。 主要实现:如果 w.current 为空,直接返回。 如果 w.current.gasPool 为空,则初始化为 w.current.header.GasLimit 汇总的事件日志,代码为:var coalescedLogs []*types.Log 循环处理交易列表 txs: 在以下三种情况下,我们将中断交易的执行。对于前两种情况,半成品将被丢弃。对于第三种情况,半成品将被提交给共识引擎。需要特别说明的是,这一步会根据 w.current.header.GasLimit 和 w.current.gasPool.Gas() 计算事件 intervalAdjust 的字段 ratio,并将字段 inc 设为 true,然后将事件 intervalAdjust 发送给通道 w.resubmitAdjustCh,从而驱动命名协程 worker.newWorkLoop() 的工作流程。具备的可以参考代码。 (1)新的区块头块事件到达,中断信号为1。 (2)对象 worker 启动或重启,中断信号为1。 (3)对象 worker 用任何新到达的交易重新创建挖掘区块,中断信号为2。 直接返回,退出整个循环和此方法。见代码:return atomic.LoadInt32(interrupt) == commitInterruptNewHead 如果没有足够的 Gas 进行任何进一步的交易,那么就退出循环。见代码:if w.current.gasPool.Gas() < params.TxGas 输出一条重要的日志信息:log.Trace("Not enough gas for further transactions", "have", w.current.gasPool, "want", params.TxGas) 需要说明的,已经提交并得到正常处理的交易仍然不变。 获取下一个交易 tx,如果为空则退出整个循环。 获取交易的发起者 from。见代码:from, _ := types.Sender(w.current.signer, tx) 这里可能会忽略错误。交易在被加入交易池时已经得到了检查。 无论当前的 hf 如何,我们都使用 eip155 签名者。 检查交易 tx 是否重播受保护。如果我们不在 EIP155 hf 阶段,请在我们开始之前开始忽略发送方。 即过滤掉此交易。当然,仍然要从 txs 中剔除。见代码:txs.Pop(); continue 开始执行交易: 更新状态树。需要说明的是,这一步会记录交易在区块中的索引。见代码:w.current.state.Prepare(tx.Hash(), common.Hash, w.current.tcount) 通过方法 worker.commitTransaction() 提交交易。见代码:logs, err := w.commitTransaction(tx, coinbase)。根据返回值 err 决定后面的操作: case core.ErrGasLimitReached 弹出当前超出 Gas 的交易,而不从账户中转移下一个交易。这是因为,该账户已经支付不起 Gas 了,所以不需要再处理该账户的其它交易。这个实现有点漂亮!!! 输出重要的日志信息:log.Trace("Gas limit exceeded for current block", "sender", from) txs.Pop() case core.ErrNonceTooLow 交易池和矿工之间的新区块头通知数据竞争,转移该账户下一个交易。 输出重要的日志信息:log.Trace("Skipping transaction with low nonce", "sender", from, "nonce", tx.Nonce()) txs.Shift() case core.ErrNonceTooHigh 事务池和矿工之间的重组通知数据竞争,跳过 account 的所有交易 输出重要的日志信息:log.Trace("Skipping account with hight nonce", "sender", from, "nonce", tx.Nonce()) txs.Pop() case nil 一切正常,收集日志并从同一帐户转移下一个交易 coalescedLogs = append(coalescedLogs, logs...) w.current.tcount++,需要增加当前区块的交易索引。 txs.Shift() default: 奇怪的错误,丢弃事务并获得下一个(注意,nonce-too-high子句将阻止我们徒劳地执行)。 输出重要的日志信息:log.Debug("Transaction failed, account skipped", "hash", tx.Hash(), "err", err) txs.Shift() 我们在挖掘时不会推送pendingLogsEvent。原因是当我们开采时,工人将每3秒钟再生一次采矿区。为了避免推送重复的pendingLog,我们禁用挂起的日志推送。 构建日志集合 coalescedLogs 的副本 cpy,避免同步问题 启动一个独立的匿名协程,将日志集合的副本 cpy 通过方法 TypeMux.Post() 发送出去。 如果当前间隔大于用户指定的间隔,则通知重新提交循环以减少重新提交间隔。代码为:w.resubmitAdjustCh <- &intervalAdjust。即将事件 intervalAdjust 发送到通道 w.resubmitAdjustCh,从而驱动命名协和 worker.newWorkLoop() 的后续逻辑。 (20) func (w *worker) commitNewWork(interrupt *int32, noempty bool, timestamp int64):方法 commitNewWork() 基于父区块生成几个新的签名任务。参数:interrupt *int32: 中断信号,值为:commitInterruptNone (0)、commitInterruptNewHead (1)、commitInterruptResubmit (2) 之一。 noempty bool: ??? timestamp int64: ??? 区块时间? 主要实现:加锁、解锁 w.mu。说明对整个方法进行了加锁处理。 获取当前时间 tstart,代码为:tstart := time.Now() 获取父区块 parent,即区块链上的当前区块。代码为:parent := w.chain.CurrentBlock() 根据父区块的时间,调整下一个区块的时间。 如果挖矿太超前,计算超前时间 wait,并睡眠 wait 时间。同时,输出日志:log.Info("Mining too far in the future", "wait", common.PrettyDuration(wait)) 获取父区块编号 num,代码为:num := parent.Number() 构建打包中的区块头 header,代码为:header := &types.Header 只有在共识引擎正在运行中,才设置 coinbase(避免虚假区块奖励) 如果 w.coinbase == (common.Address),则输出日志信息:log.Error("Refusing to mine without etherbase")。同时,退出整个方法。 header.Coinbase = w.coinbase 调用共识引擎的方法 Prepare() 设置区块头 header 中的共识字段。如果失败,则输出日志信息:log.Error("Failed to prepare header for mining", "err", err)。同时,退出整个方法。 处理 DAO 硬分叉相关内容,暂时忽略。 构建挖矿的当前环境,代码为:w.makeCurrent(parent, header)。如果失败,输出日志:log.Error("Failed to create mining context", "err", err)。同时,退出整个方法。 env := w.current 对 env 应用 DAO 相关操作。 删除 w.possibleUncles 中相对于当前区块太旧的叔区块 遍历 w.possibleUncles 累计当前区块的叔区块列表 uncles,最多支持 2 个叔区块。 下一个可能的叔区块(hash 和 uncle) 如果叔区块列表 uncles 的长度已经达到 2,则退出遍历操作。 通过 w.commitUncle() 提交叔区块 uncle 如果失败,输出日志:log.Trace("Possible uncle rejected", "hash", hash, "reason", err) 如果成功,输出日志:log.Debug("Committing new uncle to block", "hash", hash)。同时,uncles = append(uncles, uncle.Header()) if !noempty 基于临时复制状态创建空区块以提前进行签名,而无需等待区块执行完成。 w.commit(uncles, nil, false, tstart) 使用所有可用的待处理交易填充区块。代码为:pending, err := w.eth.TxPool().Pending()。如果失败,则输出日志:log.Error("Failed to fetch pending transactions", "err", err)。同时,退出整个方法。需要说明的是,从交易池中获取所有待处理的交易列表,pending 的数据结构为:map[common.Address]types.Transactions。 如果没有待处理的交易列表 更新快照。代码为:w.updateSnapshot() 退出整个方法。 将交易池中的交易 pending 划分为本地交易列表 localTxs 和远程交易列表 remoteTxs。本地交易即提交者为 w.coinbase。 具体方法为将事务池中地址为 w.coinbase 的放入本地事务列表,否则放入远程事务列表。 如果本地交易列表 localTxs 的长度大于 0 将 localTxs 封装为数据结构 types.NewTransactionsByPriceAndNonce。代码为:txs := types.NewTransactionsByPriceAndNonce(w.current.signer, localTxs) 提交交易列表。代码为:w.commitTransactions(txs, w.coinbase, interrupt)。如果失败,退出整个方法。 如果本地交易列表 remoteTxs 的长度大于 0 将 remoteTxs 封装为数据结构 types.NewTransactionsByPriceAndNonce。代码为:txs := types.NewTransactionsByPriceAndNonce(w.current.signer, remoteTxs) 提交交易列表。代码为:w.commitTransactions(txs, w.coinbase, interrupt)。如果失败,退出整个方法。 调用方法 w.commit() 组装出最终的任务 task。 (21) func (w worker) commit(uncles []types.Header, interval func(), update bool, start time.Time) error方法 commit() 运行任何交易的后续状态修改,组装最终区块,并在共识引擎运行时提交新工作。参数:uncles []*types.Header: 叔区块列表 interval func(): 中断函数 update bool: 是否更新快照 start time.Time: 方法被调用的时间 返回值:如果出错则返回出错消息,否则返回 nil。 主要实现:为了避免在不同任务之间的交互,通过深度拷贝构建 current.receipts 的副本 receipts。 构建状态数据库 w.current.state 的副本 s。 调用共识引擎的方法 Finalize() 构建出最终待签名的区块 block。需要特别说明的是:对于待组装的区块来说,除了叔区块列表 uncles 是作为参数传入之外,其它的关键信息,如:区块头、交易列表、交易回执列表都是在当前环境 w.current 中获取的。 如果对象 worker 正在运行中: 如果中断函数 interval 非空,则调用函数 interval()。 构建任务 task,并将其发送到通道 taskCh,从而驱动命名协程 worker.taskLoop() 的工作流程。 删除待确认区块列表中的过期区块,代码为:w.unconfirmed.Shift(block.NumberU64() - 1) 累计区块 block 中所有交易消耗 Gas 的总和 feesWei。第 i 个交易 tx 消耗的 Gas 计算方式: receipts[i].GasUsed * tx.GasPrice() 将 feesWei 转换成 feesEth,即消耗的总以太币。 至此,已经打包好了最终的待签名区块。输出一条重要的日志信息:log.Info("Commit new mining work", "number", block.Number(), "sealhash", w.engine.SealHash(block.Header()), "uncles", len(uncles), "txs", w.current.tcount, "gas", block.GasUsed(), "fees", feesEth, "elapsed", common.PrettyDuration(time.Since(start))) 持续监听通道 worker.exitCh,如果接收到中止消息则输出日志:log.Info("Worker has exited") 如果 update 为 true,则更新快照: 调用 w.updateSnapshot() 更新待处理的快照和状态。 方法 worker.commit() (由命名协程 worker.mainLoop() 调用)将消息 task 发送给通道 taskCh。此方法先将当前环境中的区块头(w.current.header)、事务列表(w.current.txs)、收据列表(w.current.receipts)作为参数传递给共识引擎的方法 Finalize() 组装出待签名的区块,代码为 block = w.engine.Finalize(w.chain, w.current.header, s, w.current.txs, uncles, w.current.receipts)。需要注意的是,区块 types.Block 中只包含区块头 types.Header、事务列表 []types.Transaction、叔区块列表 []types.Header,并不包含收据列表 []types.Receipt,但是区块头 types.Header 中的字段 ReceiptHash 是收据列表树的根哈希,所以也需要收据列表参数。将组装后的待签名区块 types.Block,及前面解释过的收据列表 []types.Receipt 等其它参数一起构建出新的任务 task 发送给通道 taskCh,同时输出一条重要的日志信息:log.Info("Commit new mining work", "number", block.Number(), "sealhash", w.engine.SealHash(block.Header()), "uncles", len(uncles), "txs", w.current.tcount, "gas", block.GasUsed(), "fees", feesEth, "elapsed", common.PrettyDuration(time.Since(start)))。到方法 commit() 这一步,已经组装出了新的任务 task,并将此新任务 task 通过通道 taskCh 发送给命名协程 worker.taskLoop()。Referencehttps://github.com/ethereum/go-ethereum/blob/master/miner/worker.go ContributorWindstamp, https://github.com/windstamp
2023年03月03日
5 阅读
0 评论
0 点赞
1
2
3