前言

本文参考官方技术文档的教程,说明了Fabric-samples的示例链码的使用,包括以下五部分:
1、CouchDB(asset-transfer-ledger-queries链码)
2、私有数据(asset-transfer-private-data链码)
3、基于状态的背书(asset-transfer-sbe链码)
4、基于属性的访问控制(asset-transfer-abac链码)
5、安全资产转移(secured-asset-transfer链码)
每一个示例链码都提供了对Fabric的某些功能/特性的实现。


一、使用CouchDB

1.1. 用例说明

CouchDB是Fabric的一种可选状态数据库,允许将账本上的数据存储为JSON格式,并针对数据的值而不是键进行富查询。CouchDB支持使用链码部署索引,以提高查询效率,并能够查询大型数据集。

asset-transfer-ledger-queries链码中定义的资产数据结构和一组实例如下所示:

在这里插入图片描述
在这里插入图片描述

其中,DocType用来区分数据库中的不同数据类型。对于上述实例,DocType用于标识这些数据表示资产asset。

1.2. 索引

索引是一个JSON格式的文件, 它允许查询数据库而不必每次查询都检查每一行,从而使数据库运行得更快、更高效。通常,索引是为频繁出现的查询条件构建的,这样可以更有效地查询数据。在CouchDB中进行富查询并非一定要索引, 但强烈建议使用索引以提高性能。此外,如果查询中需要排序,CouchDB需要一个包含已排序字段的索引。

没有索引的JSON查询可以工作,但会在对等体的日志中发出未找到索引的警告。但是,如果富查询包含排序规范,则索引中需要包含查询的字段,否则查询将失败并引发错误。

索引必须位于链码项目的META-INF/statedb/cochdb/indexes路径下,并与链码一同打包和安装,以便通过将其放置在适当的元数据文件夹中进行部署。

要定义一个索引,需要以下信息:

  • fields:要查询的字段
  • name:索引的名称
  • type:始终为“json”

可选地,可以在索引中包含属性ddoc(设计文档,design document)。设计文档是一种为包含索引而设计的CouchDB结构。属性ddoc的值代表设计文档的名称。

asset-transfer-ledger-queries链码中的索引如下所示:

在这里插入图片描述

在该示例中,如果设计文档indexOwnerDoc还不存在,则在部署索引时会自动创建它。

可以使用fields列表中指定的一个或多个属性来构造索引,并且可以指定属性的任何组合。一个属性可以存在于同一文档类型的多个索引中。

1.3. 准备工作

首先启动Fabric测试网络:

cd hyfa/fabric-samples/test-network
./network.sh down
./network.sh up createChannel -s couchdb

然后使用测试网络来部署Go版本的asset-transfer-ledger-queries链码:

./network.sh deployCC -ccn ledger -ccp ../asset-transfer-ledger-queries/chaincode-go/ -ccl go -ccep "OR('Org1MSP.peer','Org2MSP.peer')"

注:这里使用-ccep标志来部署背书策略为"OR(‘Org1MSP.peer’,‘Org2MSP.peer’)"的智能合约,这允许任何一个组织在没有收到另一个组织背书的情况下创建资产。
ccep标志实际上是在下述命令额外使用–signature-policy参数:
peer lifecycle chaincode approveformyorg
peer lifecycle chaincode checkcommitreadiness
peer lifecycle chaincode commit
具体为--signature-policy 'OR('\''Org1MSP.peer'\'','\''Org2MSP.peer'\'')'

可以通过检查对等体的容器日志来验证CouchDB索引是否已成功创建:

docker logs peer0.org1.example.com  2>&1 | grep "CouchDB index"

配置以下环境变量:

export PATH=$PATH:${PWD}/../bin
export FABRIC_CFG_PATH=${PWD}/../config/
export CORE_PEER_TLS_ENABLED=true
export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
export CORE_PEER_ADDRESS=localhost:7051

以Org1的身份创建“tom”拥有的资产:

peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n ledger -c '{"Args":["CreateAsset","asset1","blue","5","tom","35"]}'

注:因为部署链码时已经使用-ccep标志来将背书策略改为"OR(‘Org1MSP.peer’,‘Org2MSP.peer’)",所以本章中所有的peer chaincode invoke命令都无需指定明确的背书节点,默认使用CLI当前指向的对等体即可。

1.4. CouchDB Fauxton接口

Fauxton接口是一个用于CouchDB的web UI。如果已经使用CouchDB部署了测试网络,则可以通过打开浏览器来使用它。

如果要访问Org1对等体的CouchDB状态数据库,导航到:

http://localhost:5984/_utils

如果要访问Org2对等体的CouchDB状态数据库,导航到:

http://localhost:7984/_utils

账号和密码都是admin和adminpw。

后续可以在web UI中实时观察数据库中数据的变化情况。也可以测试各种索引对链码查询的支持情况而无需更改链码,因为链码的任何更改都需要重新部署。

1.5. 运行用例

1.5.1. 选择器

asset-transfer-ledger-queries链码提供了一个JSON查询的方法QueryAssets(),可以传递一个字符串作为选择器到函数。

下面使用peer CLI来测试该方法以查询tom拥有的所有资产:

peer chaincode query -C mychannel -n ledger -c '{"Args":["QueryAssets", "{\"selector\":{\"docType\":\"asset\",\"owner\":\"tom\"}, \"use_index\":[\"_design/indexOwnerDoc\", \"indexOwner\"]}"]}'

1.5.2. 分页

当CouchDB查询返回大型结果集时,可以使用一组API对结果列表进行分页。分页提供了一种机制,通过指定页面大小pagesize和书签bookmark(用于指示结果集起点)来对结果集进行分区。客户端应用程序反复调用执行查询的链码,直到不再返回结果为止。

asset-transfer-ledger-queries链码提供了一个结合分页的JSON查询的方法QueryAssetsWithPagination()。

首先再创建四个由“tom”拥有的资产:

peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile  "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n ledger -c '{"Args":["CreateAsset","asset2","yellow","5","tom","35"]}'
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile  "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n ledger -c '{"Args":["CreateAsset","asset3","green","6","tom","20"]}'
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile  "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n ledger -c '{"Args":["CreateAsset","asset4","purple","7","tom","20"]}'
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile  "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n ledger -c '{"Args":["CreateAsset","asset5","blue","8","tom","40"]}'

调用QueryAssetsWithPagination()方法,指定pageSize为3且不指定书签:

peer chaincode query -C mychannel -n ledger -c '{"Args":["QueryAssetsWithPagination", "{\"selector\":{\"docType\":\"asset\",\"owner\":\"tom\"}, \"use_index\":[\"_design/indexOwnerDoc\", \"indexOwner\"]}","3",""]}'

再一次调用QueryAssetsWithPagination()方法,指定pageSize为3且使用前一次调用返回的"bookmark"的值指定书签:

peer chaincode query -C mychannel -n ledger -c '{"Args":["QueryAssetsWithPagination", "{\"selector\":{\"docType\":\"asset\",\"owner\":\"tom\"}, \"use_index\":[\"_design/indexOwnerDoc\", \"indexOwner\"]}","3","g1AAAABJeJzLYWBgYMpgSmHgKy5JLCrJTq2MT8lPzkzJBYqzJRYXp5YYg2Q5YLI5IPUgSVawJIjFXJKfm5UFANozE8s"]}'

1.5.3. 历史记录

asset-transfer-ledger-queries链码提供了一个查询历史记录的方法GetAssetHistory()。

首先删除资产asset1:

peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n ledger -c '{"Args":["DeleteAsset","asset1"]}'

接下来查询资产asset1的历史记录:

peer chaincode query -C mychannel -n ledger -c '{"Args":["GetAssetHistory", "asset1"]}'

二、使用私有数据

2.1. 用例说明

Fabric的私有数据特性使用私有数据集合(PDC)来为组织的授权对等体在区块链网络上提供私有数据的存储和检索。从分布式数据库的角度来看,PDC就是若干仅存在于部分数据库中的私有表,这有别于在全部数据库中都存在的那些公共表。

测试网络部署的asset-transfer-private-data链码使用三个私有数据集合assetCollection、Org1MSPPrivateCollection和Org2MSPPrivateCollections在Org 1和Org 2之间转移资产:

  • Org1的成员(此后称为所有者owner)创建一个新资产。资产的公共详细信息,包括所有者的身份,存储在名为assetCollection的私有数据集合中。资产还包括所有者提供的评估价值,由每个参与者用于同意资产转让,并且仅存储在所有者组织的集合Org1MSPPrivateCollection中。
  • Org2的成员(此后称为购买者buyer)购买该资产。购买时需要同意与资产所有者相同的评估价值。具体的,购买者使用智能合约功能"AgreeToTransfer"创建交易并同意评估价值,此值存储在Org2MSPPrivateCollection集合中。随后,资产所有者使用智能合约功能"TransferAsset"将资产转移给购买者。"TransferAsset"功能使用通道账本上的哈希来确认所有者和购买者在转移资产之前已同意相同的评估价值。

使用PDC时,只有存储到公共表中的私有数据散列通过排序者,而存储在私有表中的私有数据本身对排序者保密,这带来了额外的数据隐私优势。

asset-transfer-private-data链码中定义的资产数据结构如下所示:

在这里插入图片描述

2.2. 私有数据集合

2.2.1. 集合定义文件

在一组组织可以使用私有数据进行交易之前,需要构建一个集合定义文件,该文件描述了与每个链码关联的私有数据集合以及组织可以从链码中读取和写入的所有私有数据集合。存储在私有数据集合中的数据仅分发给某些组织的对等体,而不是通道的所有成员。所有使用链码的组织都需要部署相同的集合定义文件,即使该组织不属于任何集合。

asset-transfer-private-data链码的集合定义文件collections_config.json如下图所示:

在这里插入图片描述

每个集合包含以下属性:

  • name:集合的名称。
  • policy:定义允许持久保存集合数据的组织对等体。
  • requiredPeerCount:定义对私有数据进行背书所需的对等体的最小数量。
  • maxPeerCount:出于数据冗余的目的,当前背书对等体将尝试将数据分发到的其它对等体的数量。背书对等体出现故障时,如果有请求提取私有数据,则这些其它对等体可用。
  • blockToLive:定义私有数据在私有数据库中的寿命。私有数据库只保留指定数量的区块,之后将被清除,从而使这些数据从网络中过时。若要无限期保留私有数据,即永远不清除私有数据,将blockToLive属性设置为0。
  • memberOnlyRead:值true表示对等体自动强制只有属于某个集合成员组织的客户端才允许对私有数据进行读访问。
  • memberOnlyWrite:值true表示对等体自动强制只有属于某个集合成员组织的客户端才允许对私有数据进行写访问。
  • endorsementPolicy:定义为了写入私有数据集合而需要满足的背书策略。集合级别的背书策略重写为链码级别的策略。

2.2.2. 私有数据的索引

asset-transfer-private-data链码的索引同样位于项目的META-INF/statedb/cochdb/indexes路径下。当–collections-config参数指向集合JSON文件的位置时,关联的索引将在通道上的链码实例化时自动部署。

2.3. 准备工作

首先启动Fabric测试网络:

cd hyfa/fabric-samples/test-network
./network.sh down
./network.sh up createChannel -ca -s couchdb

注1:Fabric-CA组件在这里是必须的,因为后续会使用它生成一些用于示例的身份。

注2:LevelDB或CouchDB都可以与集合一起使用。但是CouchDB在这里是必须的,因为源代码中包含了索引和JSON查询的内容。

然后在测试网络上部署Go版本的asset-transfer-private-data链码:

./network.sh deployCC -ccn private -ccp ../asset-transfer-private-data/chaincode-go/ -ccl go -ccep "OR('Org1MSP.peer','Org2MSP.peer')" -cccg ../asset-transfer-private-data/chaincode-go/collections_config.json

注3:这里同样使用-ccep标志来部署背书策略为"OR(‘Org1MSP.peer’,‘Org2MSP.peer’)"的智能合约,这允许任何一个组织在没有收到另一个组织背书的情况下创建资产。

注4:这里使用-cccg标志来将私有数据集合定义文件的路径传递给命令。作为将链码部署到通道的一部分,通道上的两个组织必须传递相同的私有数据集合定义作为链码生命周期的一部分。
-cccg标志实际上是在下述命令额外使用–collections-config参数:
peer lifecycle chaincode approveformyorg
peer lifecycle chaincode checkcommitreadiness
peer lifecycle chaincode commit
具体为--collections-config ../asset-transfer-private-data/chaincode-go/collections_config.json

2.4. 注册身份

在这个场景中,资产的所有者是Org1的成员,而购买者是Org2的成员。示例将使用Org1和Org2证书颁发机构(CA)注册两个新身份,并生成每个身份的加密材料。asset-transfer-private-data链码支持这种属于网络的个人身份的所有权。

首先配置环境变量:

export PATH=${PWD}/../bin:${PWD}:$PATH
export FABRIC_CFG_PATH=$PWD/../config/

将FABRIC_CA_CLIENT_HOME指向Org1 CA admin的MSP,代表使用Org1 CA来创建资产所有者的身份:

export FABRIC_CA_CLIENT_HOME=${PWD}/organizations/peerOrganizations/org1.example.com/

使用fabric-ca-client工具注册新的所有者客户端身份:

fabric-ca-client register --caname ca-org1 --id.name owner --id.secret ownerpw --id.type client --tls.certfiles "${PWD}/organizations/fabric-ca/org1/tls-cert.pem"

使用fabric-ca-client工具生成新身份的加密材料:

fabric-ca-client enroll -u https://owner:ownerpw@localhost:7054 --caname ca-org1 -M "${PWD}/organizations/peerOrganizations/org1.example.com/users/owner@org1.example.com/msp" --tls.certfiles "${PWD}/organizations/fabric-ca/org1/tls-cert.pem"

将Node OU配置文件复制到所有者的MSP中:

cp "${PWD}/organizations/peerOrganizations/org1.example.com/msp/config.yaml" "${PWD}/organizations/peerOrganizations/org1.example.com/users/owner@org1.example.com/msp/config.yaml"

类似的,使用Org2 CA来创建购买者的身份:

export FABRIC_CA_CLIENT_HOME=${PWD}/organizations/peerOrganizations/org2.example.com/
fabric-ca-client register --caname ca-org2 --id.name buyer --id.secret buyerpw --id.type client --tls.certfiles "${PWD}/organizations/fabric-ca/org2/tls-cert.pem"
fabric-ca-client enroll -u https://buyer:buyerpw@localhost:8054 --caname ca-org2 -M "${PWD}/organizations/peerOrganizations/org2.example.com/users/buyer@org2.example.com/msp" --tls.certfiles "${PWD}/organizations/fabric-ca/org2/tls-cert.pem"
cp "${PWD}/organizations/peerOrganizations/org2.example.com/msp/config.yaml" "${PWD}/organizations/peerOrganizations/org2.example.com/users/buyer@org2.example.com/msp/config.yaml"

2.5. 运行用例

2.5.1. 创建私有数据

在创建资产之前,访问Org1对等体的CouchDB状态数据库:

在这里插入图片描述

此时只有mychannel_private$$passet$collection这一张相关的表,其中存储了链码实例化时部署在通道上的索引:

在这里插入图片描述

设置以下环境变量,以Org1 owner用户操作peer CLI:

export CORE_PEER_TLS_ENABLED=true
export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/owner@org1.example.com/msp
export CORE_PEER_ADDRESS=localhost:7051

创建新资产:

export ASSET_PROPERTIES=$(echo -n "{\"objectType\":\"asset\",\"assetID\":\"asset1\",\"color\":\"green\",\"size\":20,\"appraisedValue\":100}" | base64 | tr -d \\n)
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n private -c '{"function":"CreateAsset","Args":[]}' --transient "{\"asset_properties\":\"$ASSET_PROPERTIES\"}"

注意,私有数据是使用–transient标志传递的。作为瞬态数据传递的输入将不会在交易中持久化,以保持数据的私有性。瞬态数据作为二进制数据传递,因此在使用终端时,必须对其进行base64编码。这里使用一个环境变量来捕获base64编码的值,并使用tr命令来去除linux base64命令添加的有问题的换行符。

再一次访问Org1对等体的CouchDB状态数据库:

在这里插入图片描述

此时有关的表有四张:

  1. mychannel_private$$p$org1$m$s$p$private$collection:保存只有Org1可见的集合Org1MSPPrivateCollection,包含资产的appraisedValue。
    在这里插入图片描述

  2. mychannel_private$$passet$collection:保存对全部组织可见的集合assetCollection,包含索引和资产的其它信息。
    在这里插入图片描述

  3. mychannel_private$$h$org1$m$s$p$private$collection:保存集合Org1MSPPrivateCollection的散列值。

  4. mychannel_private$$hasset$collection:保存集合assetCollection的散列值。

访问Org2对等体的CouchDB状态数据库:

在这里插入图片描述

可以看到,只有表mychannel_private$$p$org1$m$s$p$private$collection(即只对Org1可见的集合Org1MSPPrivateCollection)不存在。

2.5.2. 查询私有数据

2.5.2.1. 以授权身份查询

下面使用Org1中的对等体查询这两组私有数据:

peer chaincode query -C mychannel -n private -c '{"function":"ReadAsset","Args":["asset1"]}'
peer chaincode query -C mychannel -n private -c '{"function":"ReadAssetPrivateDetails","Args":["Org1MSPPrivateCollection","asset1"]}'
2.5.2.2. 以非授权身份查询

设置以下环境变量,以Org2 buyer用户操作peer CLI:

export CORE_PEER_LOCALMSPID="Org2MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org2.example.com/users/buyer@org2.example.com/msp
export CORE_PEER_ADDRESS=localhost:9051

Org2中的对等体在其数据库中拥有第一组私有数据(assetID、color、size和owner):

peer chaincode query -C mychannel -n private -c '{"function":"ReadAsset","Args":["asset1"]}'

对于第二组私有数据,Org2中的对等体在其数据库中无法查到:

peer chaincode query -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n private -c '{"function":"ReadAssetPrivateDetails","Args":["Org2MSPPrivateCollection","asset1"]}'

如果试图使用Org2的对等体查询Org1的私有数据集合,则会被拒绝:

peer chaincode query -C mychannel -n private -c '{"function":"ReadAssetPrivateDetails","Args":["Org1MSPPrivateCollection","asset1"]}'

2.5.3. 转移资产

要转移资产,购买者需要通过调用链码函数AgreetToTransfer同意与资产所有者相同的评估价值:

export ASSET_VALUE=$(echo -n "{\"assetID\":\"asset1\",\"appraisedValue\":100}" | base64 | tr -d \\n)
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n private -c '{"function":"AgreeToTransfer","Args":[]}' --transient "{\"asset_value\":\"$ASSET_VALUE\"}"

该值将存储在Org2的对等体上的Org2MSPDetailsCollection集合中。此外,函数AgreetToTransfer还会在assetCollection集合中存储一条额外的数据(转移协议),表明购买者想要购买的资产以及购买者的身份。

因为购买者同意了以评估价值购买资产,现在所有者可以将资产转让给Org2。资产需要通过拥有该资产的身份进行转移,所以设置以下环境变量,以Org1 owner用户操作peer CLI:

export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/owner@org1.example.com/msp
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export CORE_PEER_ADDRESS=localhost:7051

Org1的所有者可以读取AgreeToTransfer交易添加的转移协议,以查看购买者的身份:

peer chaincode query -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n private -c '{"function":"ReadTransferAgreement","Args":["asset1"]}'

接下来调用TransferAsset功能转移资产。该功能使用GetPrivateDataHash()函数检查Org1MSPPrivateCollection中的资产评估价值的散列是否与Org2MSPPrivateCollection中的评估价值的散列匹配。如果散列相同,则确认所有者和感兴趣的购买者同意相同的评估价值。如果条件满足,转移功能将从转移协议中获取购买者的客户ID,并使购买者成为资产的新所有者。转移功能还将从前所有者的集合中删除资产评估价值,并从资产集合中删除转移协议。

export ASSET_OWNER=$(echo -n "{\"assetID\":\"asset1\",\"buyerMSP\":\"Org2MSP\"}" | base64 | tr -d \\n)
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n private -c '{"function":"TransferAsset","Args":[]}' --transient "{\"asset_owner\":\"$ASSET_OWNER\"}" --peerAddresses localhost:7051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt"

转移后查询asset1以查看结果,并确认已从Org1的Org1MSPPrivateCollection集合中删除了资产评估价值:

peer chaincode query -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n private -c '{"function":"ReadAsset","Args":["asset1"]}'
peer chaincode query -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n private -c '{"function":"ReadAssetPrivateDetails","Args":["Org1MSPPrivateCollection","asset1"]}'

2.5.4. 清除私有数据

对于私有数据只需要保存很短时间的用例,可以在一定数量的区块之后“清除”数据,只留下一个数据哈希,作为交易的不可变证据。

在这个示例中,组织可能希望在一段时间后使appraisedValue过期。这可以使用集合定义中的blockToLive属性在区块链上对指定数量的区块保持不变后进行清除来实现。

Org2MSPPrivateCollection定义的blockToLive属性值为3,这意味着此数据将在数据库中存在三个区块,然后将被清除。

以Org2 buyer用户操作peer CLI:

export CORE_PEER_LOCALMSPID="Org2MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org2.example.com/users/buyer@org2.example.com/msp
export CORE_PEER_ADDRESS=localhost:9051

查询Org2MSPPrivateCollection中的评估价值:

peer chaincode query -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n private -c '{"function":"ReadAssetPrivateDetails","Args":["Org2MSPPrivateCollection","asset1"]}'

打开一个新的终端窗口,运行以下命令来查看Org2对等体的私有数据日志:

docker logs peer0.org1.example.com 2>&1 | grep -i -a -E 'private|pvt|privdata'

此时的最高区块号为8。

返回原来的终端窗口,运行以下命令来创建三个新资产:

export ASSET_PROPERTIES=$(echo -n "{\"objectType\":\"asset\",\"assetID\":\"asset2\",\"color\":\"blue\",\"size\":30,\"appraisedValue\":100}" | base64 | tr -d \\n)
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n private -c '{"function":"CreateAsset","Args":[]}' --transient "{\"asset_properties\":\"$ASSET_PROPERTIES\"}"
export ASSET_PROPERTIES=$(echo -n "{\"objectType\":\"asset\",\"assetID\":\"asset3\",\"color\":\"red\",\"size\":25,\"appraisedValue\":100}" | base64 | tr -d \\n)
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n private -c '{"function":"CreateAsset","Args":[]}' --transient "{\"asset_properties\":\"$ASSET_PROPERTIES\"}"
export ASSET_PROPERTIES=$(echo -n "{\"objectType\":\"asset\",\"assetID\":\"asset4\",\"color\":\"orange\",\"size\":15,\"appraisedValue\":100}" | base64 | tr -d \\n)
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n private -c '{"function":"CreateAsset","Args":[]}' --transient "{\"asset_properties\":\"$ASSET_PROPERTIES\"}"

每个命令都将创建一个新块。

再一次在新的终端窗口运行以下命令来查看Org2对等体的私有数据日志:

docker logs peer0.org1.example.com 2>&1 | grep -i -a -E 'private|pvt|privdata'

此时的最高区块号为11。

现在已从Org2MSPDetailsCollection集合中清除了assessedValue。从原来的终端窗口再次发出查询,响应将为空。

peer chaincode query -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n private -c '{"function":"ReadAssetPrivateDetails","Args":["Org2MSPPrivateCollection","asset1"]}'

在数据库中也可以看到asset1的数据已经没有了:

在这里插入图片描述


三、基于状态的背书

3.1. 用例说明

提交到Hyperledger Fabric网络的交易需要由加入通道的对等体背书,然后才能添加到账本中。背书策略指定了一组组织,这些组织的对等体需要在将交易添加到账本之前对其进行背书。

部署到通道的每个链码都有一个链码级别的背书策略。但是,也可以使用基于状态的背书策略来覆盖链码级别的背书策略,为通道的公共账本或私有数据集合中特定的键创建背书策略。基于状态的背书策略也称为键级别(key-level)的背书策略,允许通道成员对同一链码管理的数据使用不同的背书策略。

asset-transfer-sbe链码演示了如何使用键级别的背书策略来确保资产仅由资产所有者背书。场景如下:

  • 将asset-transfer-sbe链码部署到测试网络的通道上。通道有两个成员,Org1和Org2,每个组织都有一个加入通道的对等体。
  • 使用默认的链码级别背书策略创建资产,要求通道上的大多数组织对交易进行背书,这意味着创建资产的交易需要由属于Org1和Org2的对等体背书。创建资产时,智能合约会创建一个基于状态的背书策略,该策略指定只有拥有该资产的组织才能背书更新交易。由于资产归Org1所有,因此未来对资产的任何更新都需要得到Org1对等体的认可。
  • 仅通过Org1背书来更新资产。
  • 将资产转移到Org2。在执行转移交易期间,链码将创建一个新的基于状态的背书策略,该策略反映资产的新所有者。
  • 再次仅通过Org2来更新资产。

3.2. 准备工作

首先启动Fabric测试网络:

cd hyfa/fabric-samples/test-network
./network.sh down
./network.sh up createChannel

使用默认的链码级别背书策略将asset-transfer-sbe链码部署到通道上:

./network.sh deployCC -ccn sbe -ccp ../asset-transfer-sbe/chaincode-java -ccl java

注:如果遇到问题,请使用最新版本。

这需要多数通道成员的背书。在示例中,这将要求Org1和Org2都认可一项交易。

将二进制文件和配置文件的路径加入环境变量:

export PATH=${PWD}/../bin:${PWD}:$PATH
export FABRIC_CFG_PATH=$PWD/../config/

3.3. 运行用例

设置以下环境变量,以Org1 User1用户操作peer CLI:

export CORE_PEER_TLS_ENABLED=true
export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export CORE_PEER_ADDRESS=localhost:7051

创建一个新资产:

peer chaincode invoke -o localhost:7050 --waitForEvent --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n sbe --peerAddresses localhost:7051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt" --peerAddresses localhost:9051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt" -c '{"function":"CreateAsset","Args":["asset1","100","Org1User1"]}'

查询资产:

peer chaincode query -C mychannel -n sbe -c '{"Args":["ReadAsset","asset1"]}'

创建资产时,CreateAsset函数还为资产设置了基于状态的背书策略,只有资产所有者的对等体才能成功背书资产的更新。

尝试从Org2的对等体更新资产:

peer chaincode invoke -o localhost:7050 --waitForEvent --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n sbe --peerAddresses localhost:9051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt" -c '{"function":"UpdateAsset","Args":["asset1","200"]}'

尝试从Org1的对等体更新资产:

peer chaincode invoke -o localhost:7050 --waitForEvent --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n sbe --peerAddresses localhost:7051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt" -c '{"function":"UpdateAsset","Args":["asset1","200"]}'

查询更新后的资产:

peer chaincode query -C mychannel -n sbe -c '{"Args":["ReadAsset","asset1"]}'

运行以下命令将资产从Org1转移到Org2:

peer chaincode invoke -o localhost:7050 --waitForEvent --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n sbe --peerAddresses localhost:7051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt" -c '{"function":"TransferAsset","Args":["asset1","Org2User1","Org2MSP"]}'

TransferAsset功能使用Org2 MSP ID作为交易输入更新背书策略,以指定只有新所有者的对等体才能更新资产。注意,此命令只能由Org1的对等体发起。

查询转移后的资产:

peer chaincode query -C mychannel -n sbe -c '{"Args":["ReadAsset","asset1"]}'

尝试从Org1的对等体更新资产:

peer chaincode invoke -o localhost:7050 --waitForEvent --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n sbe --peerAddresses localhost:7051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt" -c '{"function":"UpdateAsset","Args":["asset1","300"]}'

尝试从Org2的对等体更新资产:

peer chaincode invoke -o localhost:7050 --waitForEvent --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n sbe --peerAddresses localhost:9051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt" -c '{"function":"UpdateAsset","Args":["asset1","300"]}'

查询更新后的资产:

peer chaincode query -C mychannel -n sbe -c '{"Args":["ReadAsset","asset1"]}'

四、基于属性的访问控制

4.1. 用例说明

基于属性的访问控制(ABAC)是指根据用户证书中的属性限制对智能合约中某些功能的访问的能力。

asset-transfer-abac链码创建资产所有者可以更新、转移或删除的资产。但是,创建资产的能力仅限于具有abac.creator=true属性的身份。

4.2. 准备工作

首先启动Fabric测试网络:

cd hyfa/fabric-samples/test-network
./network.sh down
./network.sh up createChannel -ca

将asset-transfer-sbe链码部署到通道上:

./network.sh deployCC -ccn abac -ccp ../asset-transfer-abac/chaincode-go -ccl go

将二进制文件和配置文件的路径加入环境变量:

export PATH=${PWD}/../bin:${PWD}:$PATH
export FABRIC_CFG_PATH=$PWD/../config/

4.3. 注册带属性的身份

将FABRIC_CA_CLIENT_HOME指向Org1 CA admin的MSP,代表使用Org1 CA来创建资产所有者的身份:

export FABRIC_CA_CLIENT_HOME=${PWD}/organizations/peerOrganizations/org1.example.com/

有两种方法可以生成添加了属性的证书。

第一种方法是在注册新身份时默认将属性添加到证书中。

使用fabric-ca-client工具注册一个名为creator1的新身份,属性为abac.creator=true且带ecert的后缀:

fabric-ca-client register --id.name creator1 --id.secret creator1pw --id.type client --id.affiliation org1 --id.attrs 'abac.creator=true:ecert' --tls.certfiles "${PWD}/organizations/fabric-ca/org1/tls-cert.pem"

使用fabric-ca-client工具生成新身份的加密材料:

fabric-ca-client enroll -u https://creator1:creator1pw@localhost:7054 --caname ca-org1 -M "${PWD}/organizations/peerOrganizations/org1.example.com/users/creator1@org1.example.com/msp" --tls.certfiles "${PWD}/organizations/fabric-ca/org1/tls-cert.pem"

将Node OU配置文件复制到所有者的MSP中:

cp "${PWD}/organizations/peerOrganizations/org1.example.com/msp/config.yaml" ${PWD}/organizations/peerOrganizations/org1.example.com/users/creator1@org1.example.com/msp/config.yaml

第二种方法是在生成新身份的加密材料时添加该属性。

使用fabric-ca-client工具注册一个名为creator2的新身份,属性同样为abac.creator=true但是不带ecert的后缀:

fabric-ca-client register --id.name creator2 --id.secret creator2pw --id.type client --id.affiliation org1 --id.attrs 'abac.creator=true:' --tls.certfiles "${PWD}/organizations/fabric-ca/org1/tls-cert.pem"

使用fabric-ca-client工具生成新身份的加密材料,这时将属性加入到证书中:

fabric-ca-client enroll -u https://creator2:creator2pw@localhost:7054 --caname ca-org1 --enrollment.attrs "abac.creator" -M "${PWD}/organizations/peerOrganizations/org1.example.com/users/creator2@org1.example.com/msp" --tls.certfiles "${PWD}/organizations/fabric-ca/org1/tls-cert.pem"

将Node OU配置文件复制到所有者的MSP中:

cp "${PWD}/organizations/peerOrganizations/org1.example.com/msp/config.yaml" "${PWD}/organizations/peerOrganizations/org1.example.com/users/creator2@org1.example.com/msp/config.yaml"

4.4. 运行用例

4.4.1. 创建资产

设置以下环境变量,以Org1 creator1用户操作peer CLI:

export CORE_PEER_TLS_ENABLED=true
export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/creator1@org1.example.com/msp
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export CORE_PEER_ADDRESS=localhost:7051

以带有abac.creator=true属性的身份creator1创建资产Asset1:

export TARGET_TLS_OPTIONS=(-o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" --peerAddresses localhost:7051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt" --peerAddresses localhost:9051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt")
peer chaincode invoke "${TARGET_TLS_OPTIONS[@]}" -C mychannel -n abac -c '{"function":"CreateAsset","Args":["Asset1","blue","20","100"]}'

查询资产:

peer chaincode query -C mychannel -n abac -c '{"function":"ReadAsset","Args":["Asset1"]}'

以不带abac.creator=true属性的身份user1创建资产Asset2:

export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp
peer chaincode invoke "${TARGET_TLS_OPTIONS[@]}" -C mychannel -n abac -c '{"function":"CreateAsset","Args":["Asset2","red","20","100"]}'

这将会报错,提示身份user1没有被授权创建资产,因为它的证书里没有abac.creator 属性(500 message:“submitting client not authorized to create asset, does not have abac.creator role”)。

4.4.2. 转移资产

作为Asset1的所有者,creator 1可以将资产转移给另一个所有者。为了转移资产,creator 1需要向TransferAsset功能提供新所有者的name和issue。下面将Asset1从creator 1所有转移到测试网络启动时创建的Org1的User1所有:

export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/creator1@org1.example.com/msp
export RECIPIENT="x509::CN=user1,OU=client,O=Hyperledger,ST=North Carolina,C=US::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US"
peer chaincode invoke "${TARGET_TLS_OPTIONS[@]}" -C mychannel -n abac -c '{"function":"TransferAsset","Args":["Asset1","'"$RECIPIENT"'"]}'

查询转移后的资产:

peer chaincode query -C mychannel -n abac -c '{"function":"ReadAsset","Args":["Asset1"]}'

4.4.3. 更新资产

现在资产已经转移,新所有者可以更新资产属性。

先尝试使用creator1身份更新资产:

peer chaincode invoke "${TARGET_TLS_OPTIONS[@]}" -C mychannel -n abac -c '{"function":"UpdateAsset","Args":["Asset1","green","20","100"]}'

即使Asset1是creator1创建的,更新操作还是会失败,因为交易不是由拥有资产的身份user1提交的。

再尝试使用user1身份更新资产:

export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp
peer chaincode invoke "${TARGET_TLS_OPTIONS[@]}" -C mychannel -n abac -c '{"function":"UpdateAsset","Args":["Asset1","green","20","100"]}'

查询更新后的资产:

peer chaincode query -C mychannel -n abac -c '{"function":"ReadAsset","Args":["Asset1"]}'

4.4.4. 删除资产

所有者还可以删除资产。

先尝试使用creator1身份删除资产:

export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/creator1@org1.example.com/msp
peer chaincode invoke "${TARGET_TLS_OPTIONS[@]}" -C mychannel -n abac -c '{"function":"DeleteAsset","Args":["Asset1"]}'

即使Asset1是creator1创建的,删除操作还是会失败,因为交易不是由拥有资产的身份user1提交的。

再尝试使用user1身份删除资产:

export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp
peer chaincode invoke "${TARGET_TLS_OPTIONS[@]}" -C mychannel -n abac -c '{"function":"DeleteAsset","Args":["Asset1"]}'

查询删除后的资产:

peer chaincode query -C mychannel -n abac -c '{"function":"ReadAsset","Args":["Asset1"]}'

五、安全资产转移

5.1. 用例说明

本节将在测试网络中使用secured-asset-transfer链码。该链码结合多种Fabric功能(基于状态的背书+私有数据+基于属性的访问控制)提供私有和可验证的安全交易,以在不公开地共享数据的情况下在两个组织之间转移私人资产。每个链上资产都是一个不可替代的代币(NFT),代表具有特定不可变属性(如大小和颜色)的特定资产,拥有唯一的所有者。当所有者想要出售资产时,双方需要在资产转让前就相同的价格达成一致。智能合约强制要求只有资产所有者才能转移资产。

场景受以下要求约束:

  • 资产可以由第一所有者的组织发行(在现实世界中,发行可能仅限于某个机构)。
  • 所有权在组织级别进行管理(尽管Fabric同样支持组织内个人身份级别的所有权)。
  • 资产ID是资产不可变属性的散列,与当前所有者一起存储为公共通道数据,供所有通道成员查看。
  • 资产不可变属性是只有资产所有者(和先前所有者)知道的私有信息。
  • 感兴趣的购买者会在购买前根据资产ID验证资产的不可变属性。这证实了购买者拥有正确的资产描述。
  • 感兴趣的购买者会核实资产的起源和保管链。这证实了该资产自发行以来没有发生变化。
  • 要转让资产,购买者和所有者必须首先就资产的属性和销售价格达成一致。
  • 只有当前所有者才能将其资产转移到其他组织。
  • 实际的资产转让必须核实资产的属性和价格是否已达成一致。买方和卖方都必须对转让进行背书。

secured-asset-transfer链码使用以下技术来确保资产属性保持私有:

  • 通道上的每个组织都有自己组织可以使用的专用数据集合。资产属性仅存储在当前拥有组织的对等体上的隐式私有数据集合中。此集合不需要在链码中显式定义。
  • 尽管私有属性的哈希会自动存储在链上,供所有通道成员查看,但私有属性中会包含随机salt,因此其它通道成员无法通过字典攻击猜测私有数据预映像。
  • 智能合约请求利用私有数据的瞬态字段,这样私有数据就不会被包括在最终的链上交易中。
  • 私有数据查询必须来自组织ID与对等体的组织ID匹配的客户端,该客户端必须与资产所有者的组织ID相同。

场景的交易流程如下:

(1)创建资产

任何组织都可以创建自己拥有的资产,而不需要其它通道成员的认可。资产的创建是唯一使用链码级别背书策略的交易。更新或转移现有资产的交易使用基于状态的背书策略或私人数据集合的背书策略。

注1:在其他情况下,签发机构也可以为创建交易背书。

智能合约使用以下结构功能来确保资产只能由拥有资产的组织更新或转移:

  • 创建资产时,智能合约会获取提交请求的组织的MSP ID,并将MSP ID作为所有者存储在公共链代码世界状态下的资产密钥/值中。随后更新或转移资产的智能合约请求将使用访问控制逻辑来验证请求客户端是否来自同一组织。

注2:在其他场景中,所有权可以基于组织内的特定客户端身份,而不是组织本身。

  • 创建资产时,智能合约会为资产的键设置基于状态的背书策略。该策略指定拥有资产的组织的对等体必须认可更新或转移资产的后续请求。这可以防止任何其他组织使用在其对等体上被恶意更改的智能合约进行资产转移。

注3:为了进一步确保资产转移的安全,可以考虑将可信的第三方纳入资产的基于状态的背书政策。

(2)同意转移

创建资产后,资产所有者可以更改公共所有权记录中的描述,例如宣传资产待售。智能合约访问控制强制要求此更改需要由资产所有者组织的成员提交。基于状态的背书策略强制要求此更改必须由所有者组织的对等体认可。

资产所有者和资产购买者同意以一定价格转让资产:

  • 购买者和所有者同意的价格和私有资产属性存储在每个组织的隐式私有数据集合中。私有数据集合对通道的其他成员保密商定的价格和资产属性。隐式私有数据集合的背书策略确保相应组织的对等体对价格协议进行背书,而智能合约访问控制逻辑确保价格协议由关联组织的客户端提交。
  • 当使用私有数据集合时,每个价格协议和资产属性的哈希会自动存储在账本上。只有当两个组织同意相同的价格并且两个资产描述对应时,这些哈希才会匹配。这允许组织在执行和批准转让交易时,验证他们是否已就转让细节达成一致。价格协议中添加了一个随机的交易ID作为salt,以确保其它通道成员不能使用账本上的哈希来猜测价格。

(3)转移资产

在两个组织同意相同的价格和资产属性后,资产所有者可以调用转移函数将资产转移给购买者:

  • 智能合约访问控制确保转移必须由拥有资产的组织成员发起。
  • 转移函数通过比较其哈希值来验证所有者私有集合中存在的链上资产属性与购买者私有集合中的资产属性是否对应,以确保资产所有者出售的资产与宣称的资产相同。
  • 转移函数使用账本上价格协议的散列,以确保两个组织都同意相同的价格。
  • 如果满足转移条件,转移函数将从购买者的集合中删除资产,并更新公共记录中的所有者。
  • 价格协议也从所有者和购买者隐含的私有数据集合中删除,并且在每个私有数据集合中将创建一个销售收据,以记录交易价格和时间戳。
  • 由于转账交易会更新所有者和购买者隐式数据集合中的数据,因此转账必须得到所有者和购买者对等体的认可。
  • 更新公共资产记录的基于状态的背书策略,以便只有资产的新所有者的对等体才能进行其新资产的后续转移。

5.2. 准备工作

首先启动Fabric测试网络:

cd hyfa/fabric-samples/test-network
./network.sh down
./network.sh up createChannel

将asset-transfer-sbe链码部署到通道上:

./network.sh deployCC -ccn secured -ccp ../asset-transfer-secured-agreement/chaincode-go/ -ccl go -ccep "OR('Org1MSP.peer','Org2MSP.peer')"

在运行此示例的过程中,需要同时作为Org1和Org2与网络进行交互。后续将为每个组织使用单独的终端。

在当前终端中设置以下环境变量:

export PATH=$PATH:${PWD}/../bin
export FABRIC_CFG_PATH=${PWD}/../config/
export CORE_PEER_TLS_ENABLED=true
export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
export CORE_PEER_ADDRESS=localhost:7051

打开一个新的终端,进入测试网络目录中:

cd hyfa/fabric-samples/test-network

设置以下环境变量:

export PATH=${PWD}/../bin:${PWD}:$PATH
export FABRIC_CFG_PATH=$PWD/../config/
export CORE_PEER_TLS_ENABLED=true
export CORE_PEER_LOCALMSPID="Org2MSP"
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
export CORE_PEER_ADDRESS=localhost:9051

5.3. 运行用例

5.3.1. 创建资产

5.3.1.1. 在Org1终端中操作

在创建资产之前,需要指定资产的详细信息。首先创建一个描述资产的JSON,其中“salt”参数是一个随机字符串,用于防止字典攻击。此字符串以Base64格式编码,可以将其作为瞬态数据传递给创建交易。

export ASSET_PROPERTIES=$(echo -n "{\"object_type\":\"asset_properties\",\"color\":\"blue\",\"size\":35,\"salt\":\"a94a8fe5ccb19ba61c4c0873d391e987982fbbd3\"}" | base64 | tr -d \\n)

创建属于Org1的资产:

peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n secured -c '{"function":"CreateAsset","Args":["A new asset for Org1MSP"]}' --transient "{\"asset_properties\":\"$ASSET_PROPERTIES\"}"

资产属性的散列将成为资产ID,将其设置为环境变量:

export ASSET_ID=d9923f21b770adbc79cbcc47a3aeecc81dc7f030bd129155301ce3932be7fbcc

使用ASSET_ID查询Org1隐式数据集合以查看创建的资产:

peer chaincode query -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n secured -c "{\"function\":\"GetAssetPrivateProperties\",\"Args\":[\"$ASSET_ID\"]}"

使用ASSET_ID查询公共账本上的所有权记录:

peer chaincode query -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n secured -c "{\"function\":\"ReadAsset\",\"Args\":[\"$ASSET_ID\"]}"

作为资产所有者,Org1可以更新公开描述,以宣传资产正在出售:

peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n secured -c "{\"function\":\"ChangePublicDescription\",\"Args\":[\"$ASSET_ID\",\"This asset is for sale\"]}"

再次使用ASSET_ID查询公共账本以查看更新的描述:

peer chaincode query -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n secured -c "{\"function\":\"ReadAsset\",\"Args\":[\"$ASSET_ID\"]}"
5.3.1.2. 在Org2终端中操作

查询资产:

export ASSET_ID=d9923f21b770adbc79cbcc47a3aeecc81dc7f030bd129155301ce3932be7fbcc
peer chaincode query -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n secured -c "{\"function\":\"ReadAsset\",\"Args\":[\"$ASSET_ID\"]}"

尝试更新公开描述:

peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n secured -c "{\"function\":\"ChangePublicDescription\",\"Args\":[\"$ASSET_ID\",\"the worst asset\"]}"

5.3.2. 同意出售资产

5.3.2.1. 作为Org1同意出售

从Org1终端进行操作,将资产价格设定为110美元。此外,所有者和购买者之间还需要通过电子邮件或其它通信方式将该价值与资产属性一起在带外传递。

export ASSET_PRICE=$(echo -n "{\"asset_id\":\"$ASSET_ID\",\"trade_id\":\"109f4b3c50d7b0df729d299bc6f8e9ef9066971f\",\"price\":110}" | base64 | tr -d \\n)
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n secured -c "{\"function\":\"AgreeToSell\",\"Args\":[\"$ASSET_ID\"]}" --transient "{\"asset_price\":\"$ASSET_PRICE\"}"

查询Org1私有数据集合以读取设定的售价:

peer chaincode query -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n secured -c "{\"function\":\"GetAssetSalesPrice\",\"Args\":[\"$ASSET_ID\"]}"
5.3.2.2. 1作为Org2同意购买

从Org2终端进行操作,验证资产属性。该属性是将通过电子邮件或其它通信方式在带外传递的。

export ASSET_PROPERTIES=$(echo -n "{\"object_type\":\"asset_properties\",\"color\":\"blue\",\"size\":35,\"salt\":\"a94a8fe5ccb19ba61c4c0873d391e987982fbbd3\"}" | base64 | tr -d \\n)
peer chaincode query -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n secured -c "{\"function\":\"VerifyAssetProperties\",\"Args\":[\"$ASSET_ID\"]}" --transient "{\"asset_properties\":\"$ASSET_PROPERTIES\"}"

为asset1报价100美元:

export ASSET_PRICE=$(echo -n "{\"asset_id\":\"$ASSET_ID\",\"trade_id\":\"109f4b3c50d7b0df729d299bc6f8e9ef9066971f\",\"price\":100}" | base64 | tr -d \\n)
export ASSET_PROPERTIES=$(echo -n "{\"object_type\":\"asset_properties\",\"color\":\"blue\",\"size\":35,\"salt\":\"a94a8fe5ccb19ba61c4c0873d391e987982fbbd3\"}" | base64 | tr -d \\n)
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n secured -c "{\"function\":\"AgreeToBuy\",\"Args\":[\"$ASSET_ID\"]}" --transient "{\"asset_price\":\"$ASSET_PRICE\", \"asset_properties\":\"$ASSET_PROPERTIES\"}"

查询Org2私有数据集合以读取报价:

peer chaincode query -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n secured -c "{\"function\":\"GetAssetBidPrice\",\"Args\":[\"$ASSET_ID\"]}"

5.3.3. 在Org1和Org2之间转移资产

5.3.3.1. 作为Org1转移资产

从Org1终端进行操作,发起转移。使用–peerAddresses标志指定Org1和Org2的对等体。这两个组织都需要批准转让。价格在传输请求中作为瞬态属性传递。

peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n secured -c "{\"function\":\"TransferAsset\",\"Args\":[\"$ASSET_ID\",\"Org2MSP\"]}" --transient "{\"asset_price\":\"$ASSET_PRICE\"}" --peerAddresses localhost:7051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt" --peerAddresses localhost:9051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt"

由于两个组织没有就相同的价格达成一致,因此无法完成转让。

Org1和Org2就购买资产的价格达成了新的协议。Org1将资产价格降至100:

export ASSET_PRICE=$(echo -n "{\"asset_id\":\"$ASSET_ID\",\"trade_id\":\"109f4b3c50d7b0df729d299bc6f8e9ef9066971f\",\"price\":100}" | base64 | tr -d \\n)
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n secured -c "{\"function\":\"AgreeToSell\",\"Args\":[\"$ASSET_ID\",\"Org2MSP\"]}" --transient "{\"asset_price\":\"$ASSET_PRICE\"}"

再次发起转移:

peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n secured -c "{\"function\":\"TransferAsset\",\"Args\":[\"$ASSET_ID\",\"Org2MSP\"]}" --transient "{\"asset_price\":\"$ASSET_PRICE\"}" --peerAddresses localhost:7051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt" --peerAddresses localhost:9051 --tlsRootCertFiles "${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt"

查询资产所有权记录以验证转移是否成功:

peer chaincode query -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n secured -c "{\"function\":\"ReadAsset\",\"Args\":[\"$ASSET_ID\"]}"
5.3.3.2. 作为Org2更新资产描述

从Org2终端进行操作,从Org2隐式数据集合中读取资产详细信息:

peer chaincode query -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n secured -c "{\"function\":\"GetAssetPrivateProperties\",\"Args\":[\"$ASSET_ID\"]}"

更新资产公共描述:

peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n secured -c "{\"function\":\"ChangePublicDescription\",\"Args\":[\"$ASSET_ID\",\"This asset is not for sale\"]}"

查询账本以验证资产是否不再出售:

peer chaincode query -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile "${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem" -C mychannel -n secured -c "{\"function\":\"ReadAsset\",\"Args\":[\"$ASSET_ID\"]}"

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐