修改的文件说明(按文件顺序) - 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",解决pbftAPI相关问题

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标识,用于命令行指定pbftID

2、cmd/puppeth

2.1 wizard_genesis.go

添加PBFT分支,为puppeth配置genesis信息时提供PBFT选择,并预设PBFTsigner(VP)的信息

2.2 wizard_node.go

添加PBFT分支,用于读取预设的signer及其密钥,并进行账户解锁

3、cmd/utils

3.1 flag.go

仿照NetWorkIdFlag,添加PBFTIdFlag,用于命令行指定PBFTID,以确定主节点。

4、consensus/pbft

4.1 helper.go

该文件为新创建的文件,主要设置了Helper结构体,帮助pbft实现消息、数据库、区块链的操作。

  1. manager,和pbft.manager以及protocolManager.pbftManager完全相同,用于实现viewchange过程中消息的发送

  2. databaseHelper,接口类型,定义在persist.go中,用于实现viewchange过程中psetqset等变量向rocksDB数据库的存储和读取。

  3. 自定义一些函数:InvalidateState()ValidateState()skipTo()getValidatorHandle()getValidatorHandles() UpdateState()getState(),这些函数涉及pbftCore的成员变量,原先通过instance.consumer.XXX()调用,目前暂定义为helper的方法

4.2 pbft-core.go

该文件为pbft算法的核心文件

  1. pbftCore结构体中添加PBFT中的signersignerFnlock三个变量,用于保存签名函数(用于viewChange)。

  2. pbftCore中添加helper成员变量,类型为Helper,定义在新添加的helper.go中,处理databaseblockchainmanager,该成员变量的初始化比较特殊:其成员变量manager在pbft.go的New()中初始化, helper.manager == pbft.manager == protocolManager.pbftManagerblockchainHelper在eth/backend.go的New()中初始化以获得当前blockchain

  3. 参照fabric中的概率模拟byzantine部分的代码,改写了innerBroadcast()函数,在fabric中,通过调用instance.consumer.unicast(msgRaw, uint64(i))模拟恶意节点发送消息,通过调用instance.consumer.broadcast(msgRaw)模拟正常节点广播消息。

instance.consumer.broadcast(msgRaw)改为instance.commChan <- msg,利用channel机制,其中msg是函数的参数。

  1. 注释掉pbftMessage结构体,统一使用types.PbftMessage结构体,并在processEvent()中注释掉case *pbftMessagecase pbftMessageEvent,修改为case *types.PbftMessage;修改该文件中用到type.PbftMessage结构体定义,涉及prePrepare、prepare、commit消息。

  2. 修改hash显示问题

  • %x输出,用于logger.Debugf()fmt.sprintf()...
  • 转换为common.hash类型,直接输出,用于log.Info()...

主要涉及:common.StringToHash(digest)common.BytesToHash(digest)

4.3 pbft-persist.go

沿用fabric中的函数,将其中protoMarshalUnmarshal全部替换为jsonMarshalUnmarshal,部分函数进行了重写,将该文件中间接调用的函数定义在persist.go文件中,通过helper调用。

4.4 pbft.go

  1. 改写sigHash()函数,添加types.ViewChange参数,使其可以根据nil同时处理types.Headertypes.ViewChange

大体思路:参照clique中的signverify函数进行仿写,由于clique中的sign只有授权节点可调用,并且作为clique的成员变量signerFn保存起来,而在pbft中,viewchange的涉及这两个函数的调用发生在pbftCore的方法中,所以目前将pbftpbftCore中都增加signersignerFn成员变量;由于后期需要验证签名,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注释掉。

  1. Authorize()函数中,添加对pbftCore.signerpbftCore.signerFn的赋值,同样用于后期viewChange.gosign()函数对于viewChange签名。

  2. 增加ecrecoverFromSignature(viewchange)函数,用于从viewchange.Signature中恢复signer

  3. 增加pbftCore方法verifyViewChangeSig(viewchange),用于验证viewchange的签名,比较viewchange.Signer和调用ecrecoverFromSignature(viewchange)获取的signer是否相同。

  4. 将pbft的New()函数的参数从四个改为三个,删除的configVloadConfig()中加载,pbftCore的newPbftCore保持不变。

  5. 主节点账户签名问题,使用判断语句,不是主节点无法进行Sign()

  6. 将PBFT的函数入口改为recvRequestBlock(),在该函数中先进行一系列检查,合法后调用sendPrePrepare()

  7. 新建一些函数,用于修改pbft的成员变量,以解决protocolManager初始化。

  8. 注释掉recent相关检验授权部分,解决循环挖矿问题,因为在pbft中不需要每挖一个块,就轮换一次signer,所以recent在pbft中不起作用。 同时,注释掉Signed recently, must wait for others"设置的时间机制;另外,后期发现未授权错误也和该部分有关。

  9. c.pbft.lastExec = newBlock.Header().Number.Uint64()移到finishedChan<-struct{}{}后,并修改为commint.SeqNo,表示当前view中最后成功执行的消息的标号。

  10. 修改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.goHelper的成员变量,该过程会遇到rocksDB问题。

4.6 snapshot.go

注释掉recent相关检验授权部分,修改tally投票统计,修改提案通过的条件。

4.7 viewchange.go

  1. 改写所有的instance.sign(vc)instance.verify(vc)的调用,可参考代码中的注释

  2. 恢复viewchange中所涉及的其他函数,若其原先为instance.consumer.XXX(),现以全部更换为instance.helper.XXX(),这些函数都定义在helper.go中

  3. 添加ConvertMapToStruct()函数,将map类型转换为struct数组类型,主要用于NewView结构体的Xset成员变量,由原先的msgList改为XSet结构体,并修改相关部分,以解决rlp编码中没有map类型的decoder的问题,

  4. 修改该文件中用到type.PbftMessage结构体定义,涉及ViewChangeNewView

5 console

该目录从ethereum/go-ethereum目录下拷贝而来,主要解决pbftAPI问题

将在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

  1. 该文件起初从fabric.pb.go中复制而来,注意,一定要注释其中的所有和block相关的,否则会和ethereum中的block发生冲突,该文件的使用需要后期进一步研究,只是在恢复viewchange的过程中,需要用到该文件中部分定义。

ViewChange结构体中添加Signer成员变量和其相应的方法,保存viewchange的签名者,用于后期验证签名时进行比较。 payload接口转换为其他结构体

  1. 后期由于p2p传输中的编解码问题,重新编写该文件,废除原先的protobuf。主要涉及PrePrepare、Prepare、Commit、Checkpoint、ViewChange、NewView,以解决rlp编解码问题。

  2. 仿block,编写pbftMessageDecodeRLP()EncodeRLP()方法

  3. 修改消息结构体成员变量的tag

7 eth

7.1 backend.go

  1. StratMining()中仿照clique添加PBFT分支操作

  2. eth.engineCreateConsensusEngine()初始化,原先该函数需要protocolManager这个参数,而protocolManager需要在初始化engine之后才能被初始化,两者相互矛盾。

解决方法:保持CreateConsensusEngine()参数不变,将pbftcommnChanmanager赋值给protocolManager的操作放到NewProtocolManager()中; 在NewProtocolManager()中,通过判断engine是否为PBFT进行赋值.

此外,blockchainHelper的初始化操作也发生在backend.goNew()函数中.

  1. 调整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

  1. pm.peers更改为pm.peerSet.peers,因为在pm中存在peers成员变量,而pm.peers中也存在peers成员变量,若=>p表示包含关系,则peer=>peers=>peerSet=>pm,SendMsg(msg)peer的方法,不是peerSet的方法。

  2. 修改handleMsg()中的分支,涉及pbftMessageAddPeerMsgRomovePeerMsg,这些分支用于读取p2p广播的标识的消息,进行相应的处理操作。

  3. 修改BroadcastMsg(),添加pbftMessageAddPeerMsgRomovePeerMsg的发送消息识别,即PeersWithoutMsg()函数,只向未发送过消息的peer发送消息。相应的,需要编写MarkMsg()func (ps *peerSet) PeersWithoutMsg(hash common.Hash) []*peer{}

  4. 修改SendMsg(),针对pbftMessage将之前的consensusMsg分解出来,单独发送相应的消息,以解决rlp中对接口类型成员变量无法解码的问题。各消息在读取后重新组装成types.PbftMessage,交PBFT.processEvent()进一步处理;针对peerMsg,发送AddPeerMsgRomovePeerMsg标识的enode消息。

7.4 metrics.go

仿blockTx,在该文件中添加propXXXInPacketsMeterpropXXXInTrafficMeter变量,并添加相关的case分支操作,暂时没起作用。

7.5 peer.go

修改peer结构体,仿blocktransaction,在peer结构体中添加knownMsg成员变量,用于识别消息是否已发送。

7.5 protocol.go

  1. 注释掉ConsensusMsg标识,添加其他六个标识,修改ProtocolLengths,解决rlp编解码;针对peer连接问题,添加AddPeerMsgRemovePeerMsg标识,,修改ProtocolLengths

  2. 定义各消息类型的结构体组装,用于从p2p中读取到的msg恢复数据。

8 internal

8.1 web3ext.go

Modules中仿照clique添加pbft键值对,相应地,添加了PBFT_JS常量类型,仍然仿照Clique_JS,后期针对API中的具体指令再进行相应的修改。

9 node

9.1 api.go

在该文件中进行修改enode相关操作。

  1. 添加一个全局变量AddPeerUrlArray数组,用于保存和当前peer相连通的所有enode。该变量在func (api *PrivateAdminAPI) AddPeer(url string)函数中,每次成功执行addPeer(peer_url)操作后,都会将peer_url添加到AddPeerUrlArray数组。

  2. AddPeerUrlArray变量添加下列函数:

findItemInArray()函数用于判断url(也就是enode)是否在当前的enode列表中,主要用于addPeerremovePeer时,urlArray是否已保存peer的url,防止重复执行addPeerremovePeer`操作。

deleteItemInArray()函数用于在enode列表中删除指定的url

  1. 定义了一个全局变量AddPeerCommchannel类型,用于触发p2p中的广播函数。该变量在func (api *PrivateAdminAPI) AddPeer(url string)函数中,每次成功执行addPeer(peer_url)操作后,会将peer_url添加到AddPeerUrlArray数组,同时将url发送到AddPeerComm中,而AddPeerComm的遍历在handler.go中的processPeerMsg()函数中,类似于processConsensusMsg()pbftMessagecommChan

  2. 定义一个全局变量的privateAdminAPI指针,用于备份初始化时创建的api

9.2 core.yaml

  1. 参考fabric/peer/core.yaml文件,添加.yaml文件,由于无法在computer目录下创建其他文件夹,所以采取在home/ethtest目录下保存rocksdb文件,fileSystemPath需根据不同的系统账户进行设置,我的账户为ethtest

  2. cmd/geth/main.go中修改viper中获取core.yaml的路径:(注释部分为$HOME/.geth-pbft路径,未注释部分为$GOPATH/src/github.com/yeongchingtarn/geth-pbft/node路径),用于shell脚本的执行。

  3. 此外,修改core.yaml文件中的fileSystemPath/usr/local/geth-pbft/hyperledgerRocksDB/signer,这样在ubuntu的任何账户下该路径不用重新修改。

10 params

10.1 config.go

RinkebyChainConfig的赋值中,添加ChainConfig成员变量PBFT的赋值,该变量为PBFTConfig类型。

11 其他问题

  1. 修改包的导入,尤其注意geth-pbft和go-ethereum相重叠的目录,一些问题都出现在这种情况下。

  2. import cycle not allowed 报错,两个目录之间不能相互导入自己目录下包

  3. 空指针错误,赋值的先后问题

  4. 通过shell脚本,配置.yaml文件

大体思路:将.yaml文件和可执行程序(geth、puppeth...)放在同一个文件夹,在该文件夹内创建名为install.sh的shell脚本,通过运行该脚本,将.yaml文件放到指定目录($HOME/.geth-pbft),将可执行程序安装到指定目录(/usr/local/geth-pbft),并将可执行程序的安装目录添加到环境变量PATH中。