Hyperledger Fabric外部应用写入多隐式私有集合失败排查
外部应用可正常提交写入单个隐式私有数据集合的交易,但写入2个隐式私有集合的交易会执行失败;相同合约逻辑通过VS Code区块链扩展提交时运行完全正常。
- VS Code区块链扩展版本:v2.0.8
- 扩展自动部署的Fabric运行环境:microfab 0.0.11
- 网络拓扑:基于扩展2组织模板搭建的2组织Fabric网络
- 业务逻辑:开发的智能合约需要同时向Org1、Org2对应的两个隐式私有数据集合写入数据
智能合约(TypeScript)
@Transaction() public async createMyPrivateAsset(ctx: Context, myPrivateAssetId: string): Promise<void> { const exists: boolean = await this.myPrivateAssetExists(ctx, myPrivateAssetId); if (exists) { throw new Error(`The asset my private asset ${myPrivateAssetId} already exists`); } const privateAsset: MyPrivateAsset = new MyPrivateAsset(); const transientData: Map<string, Uint8Array> = ctx.stub.getTransient(); if (transientData.size === 0 || !transientData.has('privateValue')) { throw new Error('The privateValue key was not specified in transient data. Please try again.'); } privateAsset.privateValue = transientData.get('privateValue').toString(); const collectionName: string = await getCollectionName(ctx, ctx.clientIdentity.getMSPID()); await ctx.stub.putPrivateData(collectionName, myPrivateAssetId, Buffer.from(JSON.stringify(privateAsset))); } @Transaction() public async createMyPrivateAssetMultiple(ctx: Context, myPrivateAssetId: string): Promise<void> { const exists: boolean = await this.myPrivateAssetExists(ctx, myPrivateAssetId); if (exists) { throw new Error(`The asset my private asset ${myPrivateAssetId} already exists`); } const privateAsset: MyPrivateAsset = new MyPrivateAsset(); const transientData: Map<string, Uint8Array> = ctx.stub.getTransient(); if (transientData.size === 0 || !transientData.has('privateValue')) { throw new Error('The privateValue key was not specified in transient data. Please try again.'); } privateAsset.privateValue = transientData.get('privateValue').toString(); for (const mspid of ['Org1MSP', 'Org2MSP']) { var collectionName: string = await getCollectionName(ctx, mspid); await ctx.stub.putPrivateData(collectionName, myPrivateAssetId, Buffer.from(JSON.stringify(privateAsset))); } } @Transaction(false) @Returns('MyPrivateAsset') public async readMyPrivateAsset(ctx: Context, myPrivateAssetId: string): Promise<string> { const exists: boolean = await this.myPrivateAssetExists(ctx, myPrivateAssetId); if (!exists) { throw new Error(`The asset my private asset ${myPrivateAssetId} does not exist`); } let privateDataString: string; const collectionName: string = await getCollectionName(ctx, ctx.clientIdentity.getMSPID()); const privateData: Uint8Array = await ctx.stub.getPrivateData(collectionName, myPrivateAssetId); privateDataString = JSON.parse(privateData.toString()); return privateDataString; }
方法说明:
createMyPrivateAsset:向调用者所属组织的单个隐式集合写入数据,全场景运行正常createMyPrivateAssetMultiple:同时向Org1、Org2两个组织的隐式集合写入数据,仅在外部应用调用时失败readMyPrivateAsset:读取调用者所属组织隐式集合内的私有数据
测试结果
VS Code扩展环境测试
通过VS Code交易视图提交上述两个交易均运行正常;使用Org1网关提交createMyPrivateAssetMultiple交易后,分别通过Org1、Org2网关调用readMyPrivateAsset均可正确返回私有数据。
外部应用测试
外部应用基于fabric-network ~2.1.0版本开发,其中createMyPrivateAsset交易可正常执行,createMyPrivateAssetMultiple交易执行失败。
网络连接代码
// connection const gateway: Gateway = new Gateway(); const connectionProfilePath: string = path.resolve(__dirname, '..', connectionFile); const connectionProfile = JSON.parse(fs.readFileSync(connectionProfilePath, 'utf8')); const connectionOptions: GatewayOptions = { wallet, identity: identity, discovery: { enabled: true, asLocalhost: true } }; await gateway.connect(connectionProfile, connectionOptions); // Get the network (channel) our contract is deployed to. const network = await gateway.getNetwork('mychannel'); // Get the contract from the network. const contract = network.getContract('private-contract');
单集合写入代码(运行正常)
let transientData = { 'privateValue': Buffer.from(`Private value for asset ${assetId}`) }; const trans:Transaction = contract.createTransaction('createMyPrivateAsset'); const buffer: Buffer = await trans.setTransient(transientData).submit(assetId);
多集合写入代码(运行失败)
let transientData = { 'privateValue': Buffer.from(`Private value for asset ${assetId}`) }; const trans:Transaction = contract.createTransaction('createMyPrivateAssetMultiple'); const buffer: Buffer = await trans.setTransient(transientData).submit(assetId);
使用Org1网关提交多集合写入交易时,应用抛出ENDORSEMENT_POLICY_FAILURE错误,microfab docker容器日志显示无法从远程节点拉取_implicit_org_Org2MSP集合的私有数据,存在空成员资格告警,提交区块时缺失对应私有写集。
问题核心是两端的背书节点选择逻辑存在差异:
- VS Code区块链扩展提交交易时,默认会将交易发送给通道内所有组织的对等节点执行背书。交易写入两个隐式私有集合时,Org1、Org2的节点均在本地执行合约,各自生成本组织隐式集合的私有写集,不需要跨节点拉取私有数据,因此交易可以正常提交。
- 外部应用使用fabric-network 2.1默认配置时,SDK会通过服务发现自动选择满足链码背书策略的节点发送请求。对于默认的“多数组织背书即可”规则,SDK仅会选中满足数量要求的最少节点集合。使用Org1身份提交交易时,SDK默认只选中Org1的对等节点做背书,该节点执行合约时需要写入Org2的隐式私有集合,但节点本身不属于Org2,尝试从Org2节点拉取私有数据时,受microfab环境默认的gossip跨组织通信配置、成员资格校验配置限制拉取失败,最终导致Org2隐式集合的私有写集缺失,触发背书策略失败。
优先选择显式指定背书组织的方案,稳定性最高,不需要修改底层网络配置:
在提交多集合写入交易时,调用setEndorsingOrganizations方法显式要求交易必须发送给Org1、Org2的对等节点共同背书,修改后的代码如下:
let transientData = { 'privateValue': Buffer.from(`Private value for asset ${assetId}`) }; const trans:Transaction = contract.createTransaction('createMyPrivateAssetMultiple'); const buffer: Buffer = await trans.setTransient(transientData) .setEndorsingOrganizations('Org1MSP', 'Org2MSP') .submit(assetId);
该配置下两个组织的节点都会在本地执行合约,各自生成本组织隐式集合的私有写集,不需要跨节点拉取私有数据,即可正常完成交易提交。
如果需要使用默认服务发现自动选择节点,需要逐一修正microfab环境的gossip跨组织通信配置、TLS信任配置、成员资格配置,确保Org1节点可以正常从Org2节点拉取授权范围内的私有数据,该方案配置复杂度高,不推荐在开发测试环境使用。
内容的提问来源于stack exchange,提问作者Jacques Sauvé

