fabric可以跨链吗?

前言

今天公司让我整理一个基于fabric的跨链的方案,之前没怎么接触过跨链,在这里记录下自己的思路吧。

首先,先明白几个概念。什么是跨链?我的理解是跨链是跨channel。下面详细说下我的理由:

  1. 回顾下fabric的启动过程:创建证书,生成创世区块,通道配置交易块,创建通道,节点加入通道,安装链码,实例化链码,链码的调用。这个是完整的生命周期。
  2. 一个节点上可以安装多个chaincode,且每个chaincode是一个账本。
  3. 同一个通道中,所有的节点安装的是相同的chaincode,所以每个节点都有完整的数据,不存在跨链之说。
  4. 综上,跨链是指跨channel,因为不同的channel拥有不同的账本,跨链的本质是把一个链上的数据转移到另外一条链上

跨链我们既可以在上层来做,也可以在chaincode层来做。经过查找我发现了一个InvokeChaincode方法,看着不错,看上去是用来调用其他的chaincode的(不要像我学习,望文生义,如果仔细读完API文档,就没有后面的故事)。

所以我设计如下的跨链方案:

简单描述下:OrgA中的peer1和peer2 和ORGC中的peer5加入channelA,并且安装ChaincodeA,OrgB中的peer3和peer4 和ORGC中的peer5加入channelB,并且安装ChaincodeB。
peer5这个节点是可以跨链的关键所在,因为该节点同时拥有两个通道的数据。

事情到这里,并没有完,上面的操作不是一个原子操作,所以我们必须要考虑事务性,如果中间步骤出错,我们要将整个过程进行回滚,并且这是在分布式的环境下完成的,哎,真的让人头大。

1 生成证书

在开始之前,我们需要相应的搭建相应的开发环境,我是在fabric的源码基础上进行做的。基于 fabric v1.3.0
我的环境规划是:Org1有两个peer节点,Org2有两个peer节点,Org3有1个节点,其中Org1和Org3加入channel1,安装chaincode1,Org2和Org3加入channel2,安装chaincode2。

另外,Org3的节点要单独安装chaincode3,用于实现跨链。

下面我所改动的文件的详细内容请参考:
https://github.com/Anapodoton/CrossChain

证书的生成我们需要修改如下配置文件:
crypto-config.yaml
docker-compose-e2e-template.yaml
docker-compose-base.yam
generateArtifacts.sh

我们需要添加第三个组织的相关信息,修改相应的端口号。

改动完成之后,我们可以使用cryptogen工具生成相应的证书文件,我们使用tree命令进行查看。

# 2 生成创世区块,应用通道配置交易文件和锚节点配置更新交易文件
我们需要修改configtx.yaml文件和generateArtifacts.sh文件。

我们使用的主要工具是configtxgen工具。目的是生成系统通道的创世区块,两个应用通道channel1和channel2的配置交易文件,每个channel的每个组织都要生成锚节点配置更新交易文件。生成后的文件如下所示:

3 启动相应的容器

我们首先可以使用docker-comppose-e2e来测试下网络的联通是否正常。

docker-compose -f docker-compose-e2e.yaml 看看网络是否是正常的 ,不正常的要及时调整。

接下来,我们修改docker-compose-cli.yaml,我们使用fabric提供的fabric-tools镜像来创建cli容器来代替SDK。

4 创建网络

这里主要使用的是script.sh来创建网络,启动orderer节点和peer节点。

我们创建channel1,channel2,把各个节点分别加入channel,更新4个锚节点,安装链码,实例化链码。

上面的操作全部没有错误后,我们就搭建好了跨链的环境了,这里在逼逼一句,我们创建了两个通道,每个通道两个组织,其中Org3是其交集。下面可以正式的进行跨链了。

其实在前面的操作中,并不是一帆风顺的,大家可以看到,需要修改的文件其实还是蛮多的,有一个地方出错,网络就启动不了,建议大家分步进行运行,一步一步的解决问题,比如说,我在configtx.yaml文件中,ORG3的MSPTYPE指定成了idemix类型的,导致后面无论如何也验证不过,通道无法创建成功。

简单说下idemix,这个玩意是fabric v1.3 引入的一个新的特性,是用来用户做隐私保护的,基于零知识证明的知识,这里不在详述,感兴趣的可以参考:
fabric关于idemix的描述

5. 发现错误

在实例化chaincode3的时候,我晕了,chaincode3实例化在两个通道的话,本质上是会产生两个账本啊,即L31和L32,怎么在L31和L32之间转移数据呢?
绕了这么一大圈,竟然回去了。3天的功夫都白费掉了,哎,真的是蠢。。。

6 阅读API

找到fabric提供了这么一个函数的文档,我们先来看看。

invokechaincode

// InvokeChaincode documentation can be found in 
interfaces.gofunc (stub *ChaincodeStub) InvokeChaincode(chaincodeName string, args [][]byte, channel string) pb.Response {     
// Internally we handle chaincode name as a composite name     
    if channel != "" {          
    chaincodeName = chaincodeName + "/" + channel     
    }    
    return stub.handler.handleInvokeChaincode(chaincodeName, args, stub.ChannelId, stub.TxID)}

下面是官方的文档说明:

// InvokeChaincode locally calls the specified chaincode `Invoke` using the
// same transaction context; that is, chaincode calling chaincode doesn't
// create a new transaction message.
// If the called chaincode is on the same channel, it simply adds the called
// chaincode read set and write set to the calling transaction.
// If the called chaincode is on a different channel,
// only the Response is returned to the calling chaincode; any PutState calls
// from the called chaincode will not have any effect on the ledger; that is,
// the called chaincode on a different channel will not have its read set
// and write set applied to the transaction. Only the calling chaincode's
// read set and write set will be applied to the transaction. Effectively
// the called chaincode on a different channel is a `Query`, which does not
// participate in state validation checks in subsequent commit phase.
// If `channel` is empty, the caller's channel is assumed.
InvokeChaincode(chaincodeName string, args [][]byte, channel string) pb.Response

上面的意思是说:
InvokeChaincode并不会创建一条新的交易,使用的是之前的transactionID。
如果调用的是相同通道的chaincode,返回的是调用者的chaincode的响应。仅仅会把被调用的chaincode的读写集添加到调用的transaction中。
如果被调用的chaincode在不同的通道中,任何PutState的调用都不会影响被调用chaincode的账本。

哎,这里已经说的很明白了,跨channel是不能读写的,相同通道才可以的。哎,想想也是啊,channel本来就是用来隔离数据使用的,如果可以通过这个方法访问其他通道的数据,那么fabric的数据隔离机制就崩掉了。

7. 验证

下面我简单搭建一个测试网络来进行验证,还是两个channel,channel2中的chaincode通过invokeChaincode方法尝试调用chaincode1中的方法,我们来看看效果。

其中chaincode1是fabric/examples/chaincode/go/example02,chaincode2基于chaincode1进行修改,增加了一个queryByInvoke方法。

直接贴出queryByInvoke核心代码,代码中有注释

A = args[0]//query的参数
chaincode1:=args[1]//要调用的链码
invokeArgs := toChaincodeArgs("query", A)//把参数和要调用的函数转化成符合InvokeChaincode的要求
response := stub.InvokeChaincode(chaincode1, invokeArgs, "channel1")//调用channel1中的chaincode1的query方法
if response.Status != shim.OK { //是不是调用成功    
    errStr := fmt.Sprintf("Cross chain error.Failed to invoke chaincode.")     
    fmt.Printf(errStr)   
return shim.Error(errStr)}

我们分别执行如下两次查询:
第一次:
  peer chaincode query -C "channel1" -n mycc1 -c '{"Args":["query","a"]}'

结果如下:可以查到正确的结果。

我们再次查询,在channel2上通过chaincode2中的queryByInvoke方法调用channel1的chaincode1中的query方法:

 peer chaincode query -C "channel2" -n mycc2 -c '{"Args":["queryByInvoke","a","mycc1"]}'

结果如下所示:

invokeChaincode方法只能用在相同的channel中,在不同的channel中,不能读也不能写。

8. 总结

在这次方案的研究中,还是踩了很多的坑的,现总结如下:

  1. 对待一个陌生的东西,一定要先看官方文档,然后写个简单的demo进行验证。不要急着先干活。根据验证的结果在决定下面怎么办?
  2. 要学会思考,就比如说这次,其实很简单的道理,channel是为了保护数据的,不需要被调用 方做任何验证的情况下,怎么可能获取到数据呢?

跨链在实际的业务中还是需要的,虽然无法通过chaincode来实现,但是还是要想其他办法的。

猜你喜欢

转载自www.cnblogs.com/anapodoton/p/11084535.html