修改的文件说明(按文件顺序) - Agzs/geth-pbft-study GitHub Wiki
本文主要从文件的角度,进行修改说明,区别于修改文件汇总(push)的时间角度。
注释采用//=> --Agzs,所以在vscode中搜索Agzs或//=>可以查看我的修改, //=>TODO可搜索待完成操作。
1、cmd/geth
1.1 accountcmd.go、chaincmd.go、consolecmd.go、main.go
修改import导入包,将import "github.com/ethereum/go-ethereum/console"修改为import "github.com/yeongchingtarn/geth-pbft/console",解决pbft的API相关问题
1.2 main.go
添加viper初始化代码,解决rocksDB指定路径问题,用于rocksDB的启动和关闭,rocksDB在发生viewchange时存储其相关信息
后期修改viper中获取core.yaml的路径:(注释部分为$HOME/.geth-pbft路径,未注释部分为$GOPATH/src/github.com/yeongchingtarn/geth-pbft/node路径),用于shell脚本的执行。
1.3 usage.go
添加PBFTIdFlag标识,用于命令行指定pbft的ID
2、cmd/puppeth
2.1 wizard_genesis.go
添加PBFT分支,为puppeth配置genesis信息时提供PBFT选择,并预设PBFT的signer(VP)的信息
2.2 wizard_node.go
添加PBFT分支,用于读取预设的signer及其密钥,并进行账户解锁
3、cmd/utils
3.1 flag.go
仿照NetWorkIdFlag,添加PBFTIdFlag,用于命令行指定PBFT的ID,以确定主节点。
4、consensus/pbft
4.1 helper.go
该文件为新创建的文件,主要设置了Helper结构体,帮助pbft实现消息、数据库、区块链的操作。
-
manager,和pbft.manager以及protocolManager.pbftManager完全相同,用于实现viewchange过程中消息的发送 -
databaseHelper,接口类型,定义在persist.go中,用于实现viewchange过程中pset、qset等变量向rocksDB数据库的存储和读取。 -
自定义一些函数:
InvalidateState()、ValidateState()、skipTo()、getValidatorHandle()、getValidatorHandles()、UpdateState()、getState(),这些函数涉及pbftCore的成员变量,原先通过instance.consumer.XXX()调用,目前暂定义为helper的方法
4.2 pbft-core.go
该文件为pbft算法的核心文件
-
在
pbftCore结构体中添加PBFT中的signer、signerFn、lock三个变量,用于保存签名函数(用于viewChange)。 -
在
pbftCore中添加helper成员变量,类型为Helper,定义在新添加的helper.go中,处理database、blockchain、manager,该成员变量的初始化比较特殊:其成员变量manager在pbft.go的New()中初始化,helper.manager == pbft.manager == protocolManager.pbftManager,blockchainHelper在eth/backend.go的New()中初始化以获得当前blockchain -
参照
fabric中的概率模拟byzantine部分的代码,改写了innerBroadcast()函数,在fabric中,通过调用instance.consumer.unicast(msgRaw, uint64(i))模拟恶意节点发送消息,通过调用instance.consumer.broadcast(msgRaw)模拟正常节点广播消息。
将instance.consumer.broadcast(msgRaw)改为instance.commChan <- msg,利用channel机制,其中msg是函数的参数。
-
注释掉
pbftMessage结构体,统一使用types.PbftMessage结构体,并在processEvent()中注释掉case *pbftMessage和case pbftMessageEvent,修改为case *types.PbftMessage;修改该文件中用到type.PbftMessage结构体定义,涉及prePrepare、prepare、commit消息。 -
修改hash显示问题
%x输出,用于logger.Debugf()、fmt.sprintf()...- 转换为common.hash类型,直接输出,用于
log.Info()...
主要涉及:common.StringToHash(digest)和common.BytesToHash(digest)
4.3 pbft-persist.go
沿用fabric中的函数,将其中proto的Marshal和Unmarshal全部替换为json的Marshal和Unmarshal,部分函数进行了重写,将该文件中间接调用的函数定义在persist.go文件中,通过helper调用。
4.4 pbft.go
- 改写
sigHash()函数,添加types.ViewChange参数,使其可以根据nil同时处理types.Header和types.ViewChange
大体思路:参照clique中的sign和verify函数进行仿写,由于clique中的sign只有授权节点可调用,并且作为clique的成员变量signerFn保存起来,而在pbft中,viewchange的涉及这两个函数的调用发生在pbftCore的方法中,所以目前将pbft和pbftCore中都增加signer和signerFn成员变量;由于后期需要验证签名,clique是通过恢复signer,验证signer来验证签名的,但是涉及到snapshot,目前验证签名的方法是:在viewchange中增加signer成员变量,在signer签名之前将signer保存到viewchange中,将其作为一部分进行签名,后期通过具体的签名恢复signer,比较这两个signer是否相同来验证签名。注:具体的签名恢复signer算法直接调用了clique中的方法。
注意:sigHash()中要注意传递的类型,比如参数为viewchange时,获取签名时用到该函数,传递的参数是signature为空的viewchange,然后再将签名赋值给viewchange.Signature;验证签名时也用到了该函数,传递的参数是signature不为空的viewchange,所以恢复出来的signer前后不一样;解决方法,在sigHash()中将viewchange.Signature注释掉。
-
在
Authorize()函数中,添加对pbftCore.signer和pbftCore.signerFn的赋值,同样用于后期viewChange.go中sign()函数对于viewChange签名。 -
增加
ecrecoverFromSignature(viewchange)函数,用于从viewchange.Signature中恢复signer -
增加
pbftCore方法verifyViewChangeSig(viewchange),用于验证viewchange的签名,比较viewchange.Signer和调用ecrecoverFromSignature(viewchange)获取的signer是否相同。 -
将pbft的
New()函数的参数从四个改为三个,删除的configV从loadConfig()中加载,pbftCore的newPbftCore保持不变。 -
主节点账户签名问题,使用判断语句,不是主节点无法进行
Sign()。 -
将PBFT的函数入口改为
recvRequestBlock(),在该函数中先进行一系列检查,合法后调用sendPrePrepare() -
新建一些函数,用于修改pbft的成员变量,以解决
protocolManager初始化。 -
注释掉recent相关检验授权部分,解决循环挖矿问题,因为在pbft中不需要每挖一个块,就轮换一次signer,所以recent在pbft中不起作用。 同时,注释掉
Signed recently, must wait for others"设置的时间机制;另外,后期发现未授权错误也和该部分有关。 -
将
c.pbft.lastExec = newBlock.Header().Number.Uint64()移到finishedChan<-struct{}{}后,并修改为commint.SeqNo,表示当前view中最后成功执行的消息的标号。 -
修改viper中获取config.yaml的路径:(注释部分为
$HOME/.geth-pbft路径,未注释部分为$GOPATH/src/github.com/yeongchingtarn/geth-pbft/consensus/pbft路径),用于后期shell脚本的安装执行。
4.5 persist.go
该文件为新添加文件,继续沿用fabric的gorocksdb,并将fabric下的db.go文件复制到get-pbft,将该文件中的Helper更改为databaseHelper,作为helper.go中Helper的成员变量,该过程会遇到rocksDB问题。
4.6 snapshot.go
注释掉recent相关检验授权部分,修改tally投票统计,修改提案通过的条件。
4.7 viewchange.go
-
改写所有的
instance.sign(vc)和instance.verify(vc)的调用,可参考代码中的注释 -
恢复
viewchange中所涉及的其他函数,若其原先为instance.consumer.XXX(),现以全部更换为instance.helper.XXX(),这些函数都定义在helper.go中 -
添加
ConvertMapToStruct()函数,将map类型转换为struct数组类型,主要用于NewView结构体的Xset成员变量,由原先的msgList改为XSet结构体,并修改相关部分,以解决rlp编码中没有map类型的decoder的问题, -
修改该文件中用到
type.PbftMessage结构体定义,涉及ViewChange和NewView
5 console
该目录从ethereum/go-ethereum目录下拷贝而来,主要解决pbft的API问题
将在geth-pbft目录中所有涉及web3ext的文件中的包引用替换为"github.com/yeongchingtarn/geth-pbft/internal/web3ext"
此外,在geth-pbft目录中的所有文件,若存在和ethereum/go-ethereum相同的包(不单单是包名,还包括内部文件),必须替换为geth-pbft路径的包名。
注意:geth-pbft/console/bridge.go中需要引用accounts/usbwallet,虽然geth-pbft中含有该包名,但是内部文件和ethereum/go-ethereum中的文件不同,如果进行替换的话会造成一些变量未定义,所以该处不进行替换。
6 core/types
6.1 blockchain.go
在该文件中自定义了两个函数GetBlockchainInfo()、getBlockchainInfoForBlock(),将blockchain中的currentBlock看作lastBlock,以获取BlockchainInfo类型数据。
方法的调用通过之前blockchainHelper进行调用
6.2 pbft_messages.go
- 该文件起初从
fabric.pb.go中复制而来,注意,一定要注释其中的所有和block相关的,否则会和ethereum中的block发生冲突,该文件的使用需要后期进一步研究,只是在恢复viewchange的过程中,需要用到该文件中部分定义。
在ViewChange结构体中添加Signer成员变量和其相应的方法,保存viewchange的签名者,用于后期验证签名时进行比较。
payload接口转换为其他结构体
-
后期由于p2p传输中的编解码问题,重新编写该文件,废除原先的
protobuf。主要涉及PrePrepare、Prepare、Commit、Checkpoint、ViewChange、NewView,以解决rlp编解码问题。 -
仿
block,编写pbftMessage的DecodeRLP()和EncodeRLP()方法 -
修改消息结构体成员变量的tag
7 eth
7.1 backend.go
-
在
StratMining()中仿照clique添加PBFT分支操作 -
eth.engine由CreateConsensusEngine()初始化,原先该函数需要protocolManager这个参数,而protocolManager需要在初始化engine之后才能被初始化,两者相互矛盾。
解决方法:保持CreateConsensusEngine()参数不变,将pbft中commnChan和manager赋值给protocolManager的操作放到NewProtocolManager()中; 在NewProtocolManager()中,通过判断engine是否为PBFT进行赋值.
此外,blockchainHelper的初始化操作也发生在backend.go的New()函数中.
- 调整rocksDB中db.Start()和db.Stop()的方式,二者仅在
geth-pbft版本下的signer情形下启动,即含有PBFT配置文件的pbft实例下才会启动rocksDB。
7.2 download/downloader.go
若提示错误如下:
file: 'file:///home/zhiguo/go/src/github.com/yeongchingtarn/geth-pbft/eth/downloader/downloader.go'
severity: 'Error'
message: 'cannot use headerInMeter (type "github.com/ethereum/go-ethereum/vendor/github.com/rcrowley/go-metrics".Meter) as type "github.com/rcrowley/go-metrics".Meter in argument to d.deliver:
"github.com/ethereum/go-ethereum/vendor/github.com/rcrowley/go-metrics".Meter does not implement "github.com/rcrowley/go-metrics".Meter (wrong type for Snapshot method)
have Snapshot() "github.com/ethereum/go-ethereum/vendor/github.com/rcrowley/go-metrics".Meter
want Snapshot() "github.com/rcrowley/go-metrics".Meter'
at: '1471,2'
source: ''
解决方法:找到github.com/ethereum/go-ethereum/vendor,删除vendor文件即可
7.3 handler.go
-
将
pm.peers更改为pm.peerSet.peers,因为在pm中存在peers成员变量,而pm.peers中也存在peers成员变量,若=>p表示包含关系,则peer=>peers=>peerSet=>pm,SendMsg(msg)是peer的方法,不是peerSet的方法。 -
修改
handleMsg()中的分支,涉及pbftMessage、AddPeerMsg和RomovePeerMsg,这些分支用于读取p2p广播的标识的消息,进行相应的处理操作。 -
修改
BroadcastMsg(),添加pbftMessage、AddPeerMsg和RomovePeerMsg的发送消息识别,即PeersWithoutMsg()函数,只向未发送过消息的peer发送消息。相应的,需要编写MarkMsg()和func (ps *peerSet) PeersWithoutMsg(hash common.Hash) []*peer{} -
修改
SendMsg(),针对pbftMessage将之前的consensusMsg分解出来,单独发送相应的消息,以解决rlp中对接口类型成员变量无法解码的问题。各消息在读取后重新组装成types.PbftMessage,交PBFT.processEvent()进一步处理;针对peerMsg,发送AddPeerMsg和RomovePeerMsg标识的enode消息。
7.4 metrics.go
仿block和Tx,在该文件中添加propXXXInPacketsMeter和propXXXInTrafficMeter变量,并添加相关的case分支操作,暂时没起作用。
7.5 peer.go
修改peer结构体,仿block和transaction,在peer结构体中添加knownMsg成员变量,用于识别消息是否已发送。
7.5 protocol.go
-
注释掉
ConsensusMsg标识,添加其他六个标识,修改ProtocolLengths,解决rlp编解码;针对peer连接问题,添加AddPeerMsg和RemovePeerMsg标识,,修改ProtocolLengths。 -
定义各消息类型的结构体组装,用于从p2p中读取到的msg恢复数据。
8 internal
8.1 web3ext.go
在Modules中仿照clique添加pbft键值对,相应地,添加了PBFT_JS常量类型,仍然仿照Clique_JS,后期针对API中的具体指令再进行相应的修改。
9 node
9.1 api.go
在该文件中进行修改enode相关操作。
-
添加一个全局变量
AddPeerUrlArray数组,用于保存和当前peer相连通的所有enode。该变量在func (api *PrivateAdminAPI) AddPeer(url string)函数中,每次成功执行addPeer(peer_url)操作后,都会将peer_url添加到AddPeerUrlArray数组。 -
为
AddPeerUrlArray变量添加下列函数:
findItemInArray()函数用于判断url(也就是enode)是否在当前的enode列表中,主要用于addPeer或removePeer时,urlArray是否已保存peer的url,防止重复执行addPeer或removePeer`操作。
deleteItemInArray()函数用于在enode列表中删除指定的url
-
定义了一个全局变量
AddPeerComm,channel类型,用于触发p2p中的广播函数。该变量在func (api *PrivateAdminAPI) AddPeer(url string)函数中,每次成功执行addPeer(peer_url)操作后,会将peer_url添加到AddPeerUrlArray数组,同时将url发送到AddPeerComm中,而AddPeerComm的遍历在handler.go中的processPeerMsg()函数中,类似于processConsensusMsg()中pbftMessage的commChan。 -
定义一个全局变量的
privateAdminAPI指针,用于备份初始化时创建的api
9.2 core.yaml
-
参考fabric/peer/core.yaml文件,添加.yaml文件,由于无法在
computer目录下创建其他文件夹,所以采取在home/ethtest目录下保存rocksdb文件,fileSystemPath需根据不同的系统账户进行设置,我的账户为ethtest。 -
在
cmd/geth/main.go中修改viper中获取core.yaml的路径:(注释部分为$HOME/.geth-pbft路径,未注释部分为$GOPATH/src/github.com/yeongchingtarn/geth-pbft/node路径),用于shell脚本的执行。 -
此外,修改
core.yaml文件中的fileSystemPath为/usr/local/geth-pbft/hyperledgerRocksDB/signer,这样在ubuntu的任何账户下该路径不用重新修改。
10 params
10.1 config.go
在RinkebyChainConfig的赋值中,添加ChainConfig成员变量PBFT的赋值,该变量为PBFTConfig类型。
11 其他问题
-
修改包的导入,尤其注意geth-pbft和go-ethereum相重叠的目录,一些问题都出现在这种情况下。
-
import cycle not allowed报错,两个目录之间不能相互导入自己目录下包 -
空指针错误,赋值的先后问题
-
通过shell脚本,配置.yaml文件
大体思路:将.yaml文件和可执行程序(geth、puppeth...)放在同一个文件夹,在该文件夹内创建名为install.sh的shell脚本,通过运行该脚本,将.yaml文件放到指定目录($HOME/.geth-pbft),将可执行程序安装到指定目录(/usr/local/geth-pbft),并将可执行程序的安装目录添加到环境变量PATH中。