关于solo模式下修改orderer地址的问题

Fabric 1.1关于solo模式下修改orderer地址的问题

修改orderer的流程

我们知道configtx可以用来修改orderer的地址,官方有标准的文档,可以查看,其主要流程是:

  1. 获取channel的最新pb格式配置块(config block)
  2. 把pb格式的config block转换成json格式
  3. 修改json文件中关于orderer地址的值,手动修改即可
  4. 把json格式的config block文件转换回pb格式
  5. diff前后两个pb格式config block,得到一个config update
  6. 对config update签名
  7. 把签名后的config update提交给orderer来更新地址
  8. orderer验证后会把update块deliver给所有的peer以完成更新

这个过程本身是没有问题的,所有的peers和orderer都能够继续无缝工作,但是问题出在要新加入的peer上。

问题:对新加入的peer

在peer加入channel的时候,需要channel的genesis block作为参数
peer channel join -b {channel-genesis-block}.pb
这个channel-genesis-block一般我们直接取channel的第一个block即可:
peer channel fetch 0 {channel-genesis-block} -c ${CHANNEL} -o ${ORDERERADDR}
问题就在这儿,序号为0的block是channel的genesis block,但是它里面记录的orderer地址是原始的地址,而不是更新后的地址。

从而在peer join的时候从genesis block中拿到的orderer地址是老地址,那么join操作肯定不会成功。

这个地方有一点需要注意:我们为什么不在peer join的命令行中指定orderer地址呢,就像peer channel fetch那样?
答案是不能的,peer channel fetch是把命令直接发给orderer,所以需要在命令行中指定orderer地址,那么我们使用新地址就能工作;而peer join的工作不是把命令发给orderer,而是把命令发给作为server的另一个peer,peer客户端把{channel-genesie-block}这个参数发给了作为server的peer,而并不把orderer地址发给作为server的peer(如果用户强行指定了-o参数,peer join命令直接把它丢弃)。这样作为server的peer收到的参数只有genesis block,然后它从genesis block里面读出orderer地址,显然是个老地址,无法工作。

我们可能会想到两个方案:

  1. 取最新的block作为genesis block,因为它的orderer地址是新的。

答案是不行的:因为peer join的时候要验证收到的block number,最新的config block的block number显然不是0,肯定是过不了的。

(我们是不是可以把这个block number也强行改成0呢,这个我没有试过,不知道行不行哎。)

  1. 从orderer拿到genesis block之后,利用configtx工具把里面的orderer地址改掉再join呢。

这个办法是不是可行呢,相当于说我们欺骗了channel拿一个修改过的genesis block冒充原装block来join channel,经过实际我发现这个方案貌似可行,channel的数据能从orderer同步过来,后续也能工作,唯一留下的缺陷是orderer的block chain和peer的block不一致了,因为peer的genesis block和orderer的genesis block不一致(orderer地址不一致)。

有文化的同学就有疑问了,block chain不一致了,这怎么行,他们不是有hash值来验证所有block的链关系吗,我只能说这个问题说明你很专业,但是看起来config block的部分内容并没有参与到block hash值得计算范围内,至少orderer地址所在的内容没有参与,我们已经明确知道orderer的genesis block和peer的genesis block内容不一致的,但是这两个块最终算出的hash值是一样的,所以整个block chain对fabric来说还是完整的。是不是很滑稽。

那么,这样是不是问题就算解决了呢?当然不是,所以我前面说貌似可行,其实还是不行的。这个方案的可行是有条件的,即整条链从没有修改过配置信息,也就是说除了第一个genesis block,在整条链中其他的全是transactions block,没有其他configure block了。

一旦链中存在另一个configure block,那么在同步这个configure block的时候必然会失败。错误栈如下:

panic: Cannot commit block to the ledger due to ConfigEnvelope LastUpdate did not produce the supplied config result
github.com/hyperledger/fabric/common/configtx.(*ValidatorImpl).Validate
        /opt/gopath/src/github.com/hyperledger/fabric/common/configtx/validator.go:193
github.com/hyperledger/fabric/core/peer.(*chainSupport).Apply
        /opt/gopath/src/github.com/hyperledger/fabric/core/peer/peer.go:93
github.com/hyperledger/fabric/core/committer/txvalidator.validateTx
        /opt/gopath/src/github.com/hyperledger/fabric/core/committer/txvalidator/validator.go:401
github.com/hyperledger/fabric/core/committer/txvalidator.(*txValidator).Validate.func1.1

原因是什么:

orderer: [genesis-block-with-old-addr] -> ... -> [config-block]
peer:    [genesis-block-with-new-addr] -> ... -> validating

在文章一开始的时候,我们提到修改config的时候,提交给orderer的是config update内容,也就是差异部分,所以在config-block里面会记录这个差异部分,然后从差异部分推导出新的完整的config内容,这个新的完整的config也记录在config block里面(所以一个config block包含两块内容:1, config update域包含修改过的项,2,config域包含完整的新的所有项)。如果一个config update没有修改orderer地址,那么config update没有orderer地址这项,从而对orderer来说最终推导出来的完整的新config block使用的是老的orderer地址,而对于peer来说最终推导出来的新的config block使用的是新的orderer地址,(因为如果orderer地址没有在config update域里面,那么他们都会使用之前的config block里面的内容),这导致经过同一个config update之后orderer和peer产出的新的config block内容不一致了,表示peer验证config update失败,从而不能胜利完成同步。

这个问题好像没有正确的解法,我能想到的一个hack的办法就是:把block chain上的所有block捋一遍,把其中所有的config block中的orderer地址强制修改掉,就像老orderer地址从来没有被使用过一样。(前面提过orderer地址的修改,并不影响config block的hash值,所以整个block chain并不会被破坏)

标准解法

前面提到的其实是很不正式的办法,这里提供一个不用hack的办法,但是是有限制的:

  1. 第一步 标准流程修改orderer地址
    把新的orderer地址也加入到channel的orderer地址列表中,这样channel就包含两个orderer地址一个是旧的,一个是就新的,当然当前使用的是旧的。
  2. 第二步 完成block chain的迁移,完成orderer真实地址的切换
    此时channel还是有两个orderer地址一个是旧的,一个是就新的,当然当前使用的是新的了。
  3. 第三步 再次标准流程修改orderer地址
    即把地址中旧的删除,只保留一个新地址即可

这个办法的限制是:

  1. 老的instance必须可用,需要修改config
  2. 新地址必须在老instance可用的时候就确定。
    例如有些场景,需要删除老instance,然后创建新instance,而又必须新instance创建出来之后才能知道新地址(动态分配)的场景就不适用了。

后续

修改orderer地址,对后加入的peer还有隐含问题,试想在一个orderer地址修改的config前后的block如何处理:要么前面的block不工作,要么后面的block不工作,因为orderer地址只有一个呢。

你可能感兴趣的:(关于solo模式下修改orderer地址的问题)