Hyperledger Fabric Smart Contract with private data works in vscode but not in external app. How can I write data to multiple implicit collections? - hyperledger-fabric

Summary: An external app can submit a transaction that writes to a single implicit private collection but fails when writing to 2 implicit collections. Everything works OK when using the vscode blockchain extension instead of the external app.
Details:
I am using the vscode blockchain extension (v2.0.8) When used, it installs microfab version 0.0.11.
I am using a 2-org network created in the vscode extension using the 2-org template.
I have a Smart Contract that writes data to 2 implicit private collections (for Org1 and Org2).
Here is the relevant portion of the smart contract (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 writes to a single implicit collection: everything OK.
createMyPrivateAssetMultiple writes to 2 implicit collections: fails in external app.
Both transactions work perfectly when I use the vscode Transaction View to submit transactions.
For createMyPrivateAssetMultiple, I submit using the Org1 gateway and then call readMyPrivateAsset using the Org1 gateway and also using the Org2 gateway and the private data is returned correctly.
Now, when I use an external app, the transaction createMyPrivateAsset works but createMyPrivateAssetMultiple does not.
Here is the relevant portion of the app (typescript):
// 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');
Here is the transaction submission for createMyPrivateAsset
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);
This works fine in the app.
Here is the code for createMyPrivateAssetMultiple
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);
For this transaction, the app throws this (using Org1 gateway):
2022-06-07T13:21:50.727Z - warn: [TransactionEventHandler]: strategyFail: commit failure for transaction "4e9921b590a361ae01bba673e1d3d204d106522780c820055cec0345e1e67e6f": TransactionError: Commit of transaction 4e9921b590a361ae01bba673e1d3d204d106522780c820055cec0345e1e67e6f failed on peer org1peer-api.127-0-0-1.nip.io:8084 with status ENDORSEMENT_POLICY_FAILURE
The microfab docker container log includes this:
> WARN 0ff Failed fetching private data from remote peers for dig2src:[map[{b16526e4cd2ac3f431103cda23a6f64adc12acab0550eff18c1f25f1cc0d8bc1 private-contract _implicit_org_Org2MSP 6 0}:[]]], err: Empty membership channel=mychannel
...
[ org2peer] 2022-06-07 13:22:50.794 UTC [gossip.privdata] RetrievePvtdata -> WARN 220 Could not fetch all 1 eligible collection private write sets for block [20] (0 from local cache, 0 from transient store, 0 from other peers). Will commit block with missing private write sets:[txID: 4e9921b590a361ae01bba673e1d3d204d106522780c820055cec0345e1e67e6f, seq: 0, namespace: private-contract, collection: _implicit_org_Org2MSP, hash: 3e0f263d2edcfaf29df346504a40fdbadce0807938f204fe3e6bf753b751d9a3
Also, package.json includes this:
"dependencies": {
"fabric-network": "~2.1.0"
},
Can anyone shed light on this problem?

You should definitely be using fabric-network#2.2.x, not 2.1.x.
I suspect what is happening is the VS Code client is not using service discovery and sending proposals for endorsement to all network peers, whereas the standalone client application is using service discovery by default and only sending proposals to orgs required by the chaincode endorsement policy. To get it to consider the collection endorsement policy you would need to add a chaincode interest to the Contract object before submitting transactions:
https://hyperledger.github.io/fabric-sdk-node/release-2.2/module-fabric-network.Contract.html#addDiscoveryInterest
There is a working example of this in the Fabric samples:
https://github.com/hyperledger/fabric-samples/blob/8ca50df4ffec311e59451c2a7ebe210d9e6f0004/asset-transfer-private-data/application-javascript/app.js#L166-L178
Alternatively you could either:
Disable service discovery in the Gateway connection options.
Explicitly set the endorsing orgs for a given transaction invocation:
https://hyperledger.github.io/fabric-sdk-node/release-2.2/module-fabric-network.Transaction.html#setEndorsingOrganizations
In general it's much better to be using service discovery so I would not recommend option 1.
The best approach would actually be to use Fabric v2.4+ and the Fabric Gateway client API:
https://hyperledger.github.io/fabric-gateway/
With this API the client (generally) does not need to worry about the organizations required for endorsement when using private data collections or state-/key-based endorsement policies. Things just work automagically.

Related

Send proposal with signature ends with `ChaincodeId is nill` error in Hyperledger Fabric 2.2 NodeJS client

This is the flow we need on the backend.
First user creates unsigned proposal and the proposal buffer is returned to him.
const proposal = new Endorsement(this.config.chaincodeId, this.channel)
const user = User.createUser(
enrollmentId,
enrollmentId,
this.config.userMspId,
certificate
)
const identityContext = new IdentityContext(user, this.channel.client)
const proposalBuffer = proposal.build(identityContext, {
fcn,
args,
})
const digest = createHash('sha256').update(proposalBuffer).digest('hex')
Then after user signs digest and creates signature our backend sends signed proposal to endorser:
const signedProposal = {
signature: Buffer.from(signature, 'base64'),
proposal_bytes: proposalBuffer,
}
const endorser = this.channel.getEndorsers(this.config.userMspId)[0]
const response = await endorser.sendProposal(
Buffer.from(JSON.stringify( signedProposal ))
)
sendProposal method throws ChaincodeId is nil error.
Anyone knows how we could implement this right?
How do we create the Buffer object for the sendProposal method parameter?
In my case I created the buffer from stringified json object, how SignedProposal is defined in the Hyperledger Fabric documentation.
I see your code is custom code to send a proposal. Why should you do this? Why not do the easy way by using fabric-network lib?
For your question, I found some code in fabric-network:
// This is the object that will centralize this endorsement activities
// with the fabric network
const endorsement = channel.newEndorsement(this.contract.chaincodeId);
const proposalBuildRequest = this.newBuildProposalRequest(args);
logger.debug('%s - build and send the endorsement', method);
// build the outbound request along with getting a new transactionId
// from the identity context
endorsement.build(this.identityContext, proposalBuildRequest);
endorsement.sign(this.identityContext);
...
...
else if (this.endorsingOrgs) {
logger.debug('%s - user has assigned endorsing orgs %s', method, this.endorsingOrgs);
const flatten = (accumulator, value) => {
accumulator.push(...value);
return accumulator;
};
proposalSendRequest.targets = this.endorsingOrgs.map((mspid) => channel.getEndorsers(mspid)).reduce(flatten, []);
}
...
...
// by now we should have targets or a discovery handler to be used
// by the send() of the proposal instance
const proposalResponse = await endorsement.send(proposalSendRequest);
...
In send method:
...
const signedEnvelope = this.getSignedProposal();
...
peer.sendProposal(signedEnvelope, requestTimeout)
...
Look inside getSignedProposal method:
...
const fabproto6 = require('fabric-protos');
...
/*
* return a signed proposal from the signature and the payload as bytes
*
* This method is not intended for use by an application. It will be used
* by the send method of the super class.
* #returns {object} An object with the signature and the payload bytes
*/
getSignedProposal() {
const method = `getSignedProposal[${this.type}:${this.name}]`;
logger.debug('%s - start', method);
this._checkPayloadAndSignature();
const signedProposal = fabproto6.protos.SignedProposal.create({
signature: this._signature,
proposal_bytes: this._payload
});
// const signedProposal = {
// signature: this._signature,
// proposalBytes: this._payload
// };
return signedProposal;
}
So, try to use fabric-protos lib to encode your proposal. Hope this help
Solved this issue by moving to #hyperledger/fabric-gateway library. It works fine and has a well documented API. Offline transactions are better supported too.

How to select endorsing peers when submitting transaction which uses private data

I have a Hyperledger Fabric network with two organisations: Org1 and Org2. Discovery is enabled. Private data collections are used to protect sensitive data. In particular there is one private data collection accessible only to members of Org2. When I try to submit transaction which requires access to Org2-only private data, I observe that it is sent to peers of Org1 for endorsement as well:
const gateway = new Gateway();
await gateway.connect(ccp, { wallet, identity: userName, discovery: { enabled: true, asLocalhost: false } });
const network = await gateway.getNetwork(channelName);
const contract = await network.getContract(contractName);
await contract.createTransaction(command).setTransient(transient).submit();
Connection profile used does not list peers from Org2, but my guess is that the Node SDK finds peers from Org1 through discovery and sends transaction proposal to them. Peer logs in Org1 show that it cannot access private data, which is expected:
2019-11-21T12:03:03.684Z ERROR [contracts-spi/chaincodefromcontract.js]
{"message":"GET_STATE failed: transaction ID: 25f22c0abd0318b2ec1da06ae28b90a8e6af55e6d0ea825461938cce8b2d0801: private data matching public hash version is not available. Public hash version = {BlockNum: 951, TxNum: 4}, Private data version = <nil>","stack":"Error: GET_STATE failed: transaction ID: 25f22c0abd0318b2ec1da06ae28b90a8e6af55e6d0ea825461938cce8b2d0801: private data matching public hash version is not available. Public hash version = {BlockNum: 951, TxNum: 4}, Private data version = <nil>\n at parseResponse (/usr/local/src/node_modules/fabric-shim/lib/handler.js:751:15)\n at MsgQueueHandler.handleMsgResponse (/usr/local/src/node_modules/fabric-shim/lib/handler.js:136:40)\n at ClientDuplexStream.<anonymous> (/usr/local/src/node_modules/fabric-shim/lib/handler.js:290:46)\n at emitOne (events.js:116:13)\n at ClientDuplexStream.emit (events.js:211:7)\n at addChunk (_stream_readable.js:263:12)\n at readableAddChunk (_stream_readable.js:250:11)\n at ClientDuplexStream.Readable.push (_stream_readable.js:208:10)\n at Object.onReceiveMessage (/usr/local/src/node_modules/grpc/src/client_interceptors.js:1292:19)\n at InterceptingListener.recvMessageWithContext (/usr/local/src/node_modules/grpc/src/client_interceptors.js:607:19)"}
2019-11-21T12:03:03.684Z ERROR [lib/handler.js] [channel123-25f22c0a]Calling chaincode Invoke() returned error response [Error: GET_STATE failed: transaction ID: 25f22c0abd0318b2ec1da06ae28b90a8e6af55e6d0ea825461938cce8b2d0801: private data matching public hash version is not available. Public hash version = {BlockNum: 951, TxNum: 4}, Private data version = <nil>]. Sending ERROR message back to peer
Similar output is also displayed at the client side. Only in client application, it is a Warning instead of an Error.
2019-11-21T15:15:53.165Z - warn: [DiscoveryEndorsementHandler]: _build_endorse_group_member >> G2:0 - endorsement failed - Error: transaction returned with failure: Error: GET_STATE failed: transaction ID: 0b3e90c745535af7520ffab7b82b041394d409850cb5efff96071c24f5f75817: private data matching public hash version is not available. Public hash version = {BlockNum: 957, TxNum: 0}, Private data version = <nil>
Despite the above errors/warnings, the transaction is successful. Expected updates to the ledger and private data collections are performed.
The question is: is it possible to control from the client side, which peers are used for endorsement of particular transaction, taking into account private data?
I found Channel.getEndorsementPlan(endorsement_hint) which correctly recognises which peers have access to specific chaincode and private data collections. Is it possible to use output from this function to control the behaviour of Transaction.submit()?
The code to allow you to target specific peers is in the gateway (high-level api) code base but unfortunately it is not in the current release of the node sdk which currently is 1.4.4. Hopefully at some point a 1.4.5 version will be released that will have this in. There are probably newer snapshot releases of the fabric-node-sdk on npm that you could try out for now. For reference if you look at the reference documentation here https://fabric-sdk-node.github.io/release-1.4/module-fabric-network.Transaction.html you should see a method called setEndorsingPeers. This should allow you to perform peer targeting for your transaction. An example used in the tests can be found here https://github.com/hyperledger/fabric-sdk-node/blob/bf8c663fbbb9adeeb872b27eb8ccec60c03af6de/test/typescript/integration/network-e2e/invoke.ts#L954
The node-sdk low-level api (Client/Channel interface) does have the capability to discover and determine collections, but that is not available through the gateway/network/contract interface (and no code for it exists at the moment). Here is a reference to a how to use it https://fabric-sdk-node.github.io/release-1.4/tutorial-discovery.html but the Client/Channel apis don't provide support for wallets or handle events for you so you need to do identity handling and event handling yourself.
I Think that the better way to do it is creating a specif queryhandler (FABRIC 2):
import { QueryHandler, QueryHandlerFactory, Query, QueryResults } from 'fabric-network';
import { Endorser } from 'fabric-common';
import * as util from 'util';
/**
* Query handler implementation
*/
class SameOrgQueryHandler implements QueryHandler {
private readonly peers: Endorser[];
constructor(peers: Endorser[]) {
this.peers = peers;
}
public async evaluate(query: Query): Promise<Buffer> {
const errorMessages: string[] = [];
for (const peer of this.peers) {
const results: QueryResults = await query.evaluate([peer]);
const result = results[peer.name];
if (result instanceof Error) {
errorMessages.push(result.toString());
} else {
if (result.isEndorsed) {
console.log(`QueryHandler: ${result.payload}`);
return result.payload;
}
throw new Error(result.message);
}
}
const message = util.format('Query failed. Errors: %j', errorMessages);
throw new Error(message);
}
}
/**
* Factory function for creating sample query handlers.
* #param {Network} network The network where transactions are to be evaluated.
* #returns {QueryHandler} A query handler implementation.
*/
export const createQueryHandler: QueryHandlerFactory = network => {
const mspId = network.getGateway().getIdentity().mspId;
//const mspIdOrg2 = 'Org2MSP';
const channel = network.getChannel();
const orgPeers = channel.getEndorsers(mspId);
const otherPeers = channel.getEndorsers().filter(peer => !orgPeers.includes(peer));
const allPeers = orgPeers.concat(otherPeers);
return new SameOrgQueryHandler(allPeers);
};

Hyperledger Fabric 2.0, Can't Access Fabtokens of Users Using Node.js SDK

I'm trying to issue some Fabtokens to users and then use them in various scenarios such as transferring, redeeming, etc. I follow the Node SDK documentation here: https://fabric-sdk-node.github.io/master/tutorial-fabtoken.html
This is how they do the Fabtoken operations:
// create a TokenClient instance from client
const tokenclient = client.newTokenClient(mychannel);
// create a transaction ID for "issuer"
const txId = client.newTransactionID();
// create two parameters for issue, one for user1 and one for user2
const param1 = {
owner: user1.getIdentity().serialize(),
type: 'USD',
quantity: '500',
};
const param2 = {
owner: user2.getIdentity().serialize(),
type: 'EURO',
quantity: '300',
};
// create the token request for issue
const issueRequest = {
params: [param1, param2],
txId: txId,
};
// issuer calls issue method to issue tokens to user1 and user2
const result = await tokenClient.issue(issueRequest);
And then use a different tokenClient to list the tokens of user 1:
const user1Tokenclient = client1.newTokenClient(mychannel);
// user1 lists tokens
const mytokens = await user1TokenClient.list();
// iterate the tokens to get token id, type, and quantity for each token
for (const token of tokens) {
// get token.id, token.type, and token.quantity
// token.id will be used for transfer and redeem
}
It's mentioned on the Node SDK's Client class page here: https://fabric-sdk-node.github.io/master/Client.html that switching userContexts with the same client instance is an anti-pattern and not recommended since client instances are stateful.
As they suggest, I create my client instances with different user contexts. This is how I create my clients, set their user context and create my tokenClient instances:
const adminClient = new Fabric_Client();
const admin = await adminClient.createUser(user_opts);
adminClient.setUserContext(admin, true);
let adminConfig = {
admin: admin,
adminClient: adminClient,
adminTokenClient: adminClient.newTokenClient(channel)
}
const server = await serverClient.createUser(server_opts);
serverClient.setUserContext(server, true);
let serverConfig = {
server: server,
serverClient: serverClient,
serverTokenClient: serverClient.newTokenClient(channel)
}
Later on, I'm using these config objects to issue some tokens to different users. How I issue tokens to my server account from my issuer (admin) account:
const txId = adminConfig.adminClient.newTransactionID();
let issueQuery = {
tokenClient: adminConfig.adminTokenClient,
txId: txId,
channel: channel,
params: []
}
for(let i=0; i < 3; ++i) {
let param = {
owner: serverConfig.server.getIdentity().serialize(),
type: 'test',
quantity: '1'
}
issueQuery.params.push(param);
}
let issueTx = await waitForIssue(issueQuery);
This successfully issue three tokens to the server as expected. The problem is that when I try to access to the tokens of my server like the example they provide using a similar code:
let server_tokens = await serverConfig.serverTokenClient.list();
for (let server_token of server_tokens) {
console.log(server_token.id);
}
Result is just empty and I don't get any error messages. However, when I check the transaction using queryTransaction(txId) for the token issue transaction I generate, I can see that owner of the issued tokens in that transaction is the server and that's how I can be sure that I can successfully issue the tokens to the server. Is there any other way to check the tokens of my server? Or shouldn't I use a different client and user context per each user as they suggest? Because, previously I was able to see the tokens of the server when I used a single client and single user context to issue and list tokens. But this approach caused me problems when I was trying to transfer my tokens asynchronously.
As far as I know FabTokens are removed from the Master branch of Fabric 2.0 now as described in these links:
https://gerrit.hyperledger.org/r/c/fabric/+/32979
https://lists.hyperledger.org/g/fabric/topic/fabtoken/34150195?p=,,,20,0,0,0::recentpostdate%2Fsticky,,,20,2,0,34150195
I would expect the tutorial and the information in the Fabric docs to be removed in due course.

Identity management on Hyperledger Fabric

I am using Hyperledger fabric 1.4.1 and the latest versions of fabric-contract-api for the smart contracts, fabric-client for the low level APIs to manage channel creation and chaincodes, and fabric-network for interaction with the peers. I have referred to this sample for setting up my network.
I have written a basic chaincode package in nodejs and setup a simple 1 org, 1 peer, 1 orderer network. The first step is to connect to the peer node and use the fabric-ca-client to create the admin identity. As per examples the enrollment id and secret used is admin, adminpw. This is dependent on the configuration used here.
The code I use to create and join a channel, followed by installing and instantiating a chaincode is
const CAClient = require('fabric-ca-client');
const client = require('fabric-client');
const User = client.User;
const fs = require('fs');
const path = require('path');
const basePath = path.resolve(__dirname, '../certs');
const readCryptoFile = filename => fs.readFileSync(path.resolve(basePath, filename)).toString();
const ccPath = path.resolve(__dirname, '../chaincode/src/ax-chaincode');
const url = require('url');
const http = require('http');
let myClient = new client();
const ordererConfig = {
hostname: 'orderer0',
url: 'grpc://localhost:7050',
pem: readCryptoFile('ordererOrg.pem')
};
const orderer = myClient.newOrderer(ordererConfig.url, {
pem: ordererConfig.pem,
'ssl-target-name-override': ordererConfig.hostname
});
let peerConfig = {
hostname: 'ax-peer',
url: 'grpc://localhost:7051', // change to grpcs://ax-peer:7051 in some condition (no idea?)
eventHubUrl: 'grpc://localhost:7053',
pem: readCryptoFile('axOrg.pem')
};
const defaultPeer = myClient.newPeer(peerConfig.url, {
pem: peerConfig.pem,
'ssl-target-name-override': peerConfig.hostname
});
// console.log(defaultPeer);
myClient.setStateStore(await client.newDefaultKeyValueStore({
path: './ax-peer'
}))
let url = 'http://localhost:7054'
const ca = new CAClient(url, {
verify: false
});
let enrollmentID = 'admin';
let enrollmentSecret = 'adminpw';
const enrollment = await ca.enroll({
enrollmentID: 'admin',
enrollmentSecret: 'adminpw'
});
user = new User(enrollmentID, myClient);
// console.log(enrollment);
await user.setEnrollment(enrollment.key, enrollment.certificate, 'AxOrgMSP');
The above will check if admin user is available in the state store. Some queries regarding the above process
The admin user generated here can be used to interact with any and all peers of the same org, assuming only one CA is used?
What is a practical use of this identity, since for the rest of the functions, the admin identity generated by cryptogen for each peer is used (code below)
While enrolling the admin, no attrs are passed along in ca.enroll(), so naturally when querying the identity roles field returns null. The ca-server link shared clearly assigns roles of client, user, peer, validator, auditor to it. Shouldn't that reflect here since it uses admin and adminpw for enrolling id and secret?
Continuing the code
// crypto material got from cryptogen and shifted to new folder
let adminUser = await myClient.createUser({
username: `Admin#ax-peer`,
mspid: 'AxOrgMSP',
cryptoContent: {
privateKeyPEM: readCryptoFile('Admin#ax-org-key.pem'),
signedCertPEM: readCryptoFile('Admin#ax-org-cert.pem')
}
});
let txId = myClient.newTransactionID();
let envelope_bytes = fs.readFileSync('./channel.tx');
let channelConfig = myClient.extractChannelConfig(envelope_bytes);
let signature = myClient.signChannelConfig(channelConfig);
const request = {
name: 'default',
orderer: orderer,
config: channelConfig,
signatures: [signature],
txId: txId
};
const response = await myClient.createChannel(request); // should be 200
// rest of code joins channel, installs and instantiates chaincode
// docker logs show init function being called in new cc container
The stateStore has two files in it (as expected), called admin and Admin#ax-peer. These look like
"name": "Admin#ax-peer",
"mspid": "AxOrgMSP",
"roles": null,
"affiliation": "",
"enrollmentSecret": "",
enrollment: {
"signingIdentity": "554a5f5cfc5a59231a04b7b051bcbcb4f79c4226ff336a4aa48b551de4a8428f",
"certificate": "-----BEGIN CERTIFICATE----- xyz -----END CERTIFICATE-----"
}
When this user is used from the state store by await myClient.getUserContext('admin', true);, how does the client sign the transactions? I am unable to locate the private key/ cert for any user created using the fabric-client SDK.
Now If I use fabric-network API, a FileSystemWallet() function is implemented that stores the private and public cert for each user made by it.
const enrollment = await ca.enroll({ enrollmentID: `admin`, enrollmentSecret: `adminpw` });
const identity = X509WalletMixin.createIdentity('AxOrgMSP', enrollment.certificate, enrollment.key.toBytes());
wallet.import('admin', identity);
This function serves the same purpose as the ca.enroll() but stores the private cert in a visible manner. If I want to use the users created by fabric-client in the fabric-network SDK, I would need to shift the certificates, how do I achieve this?
TLDR - Register user using fabric-client and then use Gateway() class from fabric-network to submit transactions with the same user.
Any advice, guidance is much appreciated. Thanks!
There are too many questions in this stack overflow, so all I will give is some insight into what you have posted.
The admin identity you enroll from the ca server is called admin because it is a registrar of the fabric ca server and thus can register more identities in the ca server. It is not an admin identity for anything else such as the fabric network.
You have used both the lower level api and the fabric-network mechanism for identity management. In the lower level api all that got persisted to the file system was the state store, the public cert and private key are stored in memory purely because of the way you have called everything it just happens to setup defaults in that manner, unfortunately other approaches using the low level api may require you to explicitly set the cryptosuite with a cryptoKeyStore. In the wallet implementation you probably used the file system wallet, so everything got persisted to the file system.
My recommendation is to start everything with a fabric-network gateway if you can. Then if you need to drop to the lower level api's because the fabric-network doesn't do what you need then you can call
gateway.getClient()
or
network.getChannel()
to get a client/channel that has been pre-configured with the gateway configuration and identity.
To get access to a Certificate Authority you can use
gateway.getClient().getCertificateAuthority()
All of this allows you to use the wallet implementation for identity management (and the wallet implementation provides different persistence mechanisms such as in memory, file system, couchdb or you can write your own)

How to insert documents to couchdb in bulk by chaincode

How to insert/write documents to couchdb in bulk by chaincode? It seems that the chaincode shim library(https://godoc.org/github.com/hyperledger/fabric/core/chaincode/shim) doesn't have such API.
For reading documents, it seems that there is an API named "GetQueryResult"(https://godoc.org/github.com/hyperledger/fabric/core/chaincode/shim#ChaincodeStub.GetQueryResult). In the string parameter "query", we can construct bulk get request.
But for inserting/writing documents, is there any bulk API for chaincode? Thanks in advance.
When chaincode executes, each PutState() adds a key/value to the transaction's proposed writeset. You can call PutState() multiple times in a chaincode, and the final set of keys/values will appear in the transaction's proposed writeset. Note that nothing is written to CouchDB at chaincode execution time.
Only when the transaction is submitted for ordering, will the transaction appear in a block that gets processed by all peers. Each peer validates the transactions and then applies the writesets of all valid transactions in the block to the CouchDB state database. Note that the state database commit is indeed using the CouchDB bulk update API (HTTP _bulk_docs), so you automatically get the desired bulk update performance in CouchDB.
If there are many key/value updates in the block, Fabric will actually group them into batches of 1000 (configurable using core.yaml maxBatchUpdateSize property) when committing to CouchDB to avoid any issues with excessively large payloads. Finally, to ensure all writes appear to Fabric as an atomic commit, Fabric writes a final savepoint per block to the CouchDB state database and flushes the CouchDB data to disk. This ensures any chaincode executions get a consistent view of the state data, and ensures Fabric can recover from any peer crash with full data integrity.
If you mean you want to batch multiple transactions in one batch then it is not advisable to do so because if they are updating the same value then while looking at the history, the final change can only be tracked. So, it is better to submit individual transactions.
Not sure if this answers your question but by document, lets assume you mean a PDF file.
This has two functions, read a key value pair and write one. Nothing special here.
'use strict';
const { Contract } = require('fabric-contract-api');
Class SomeName extends Contract {
async initLedger(ctx) { }
async writeDocument(ctx, documentId, documentAsString) {
await ctx.stub.putState(documentId, Buffer.from(documentAsString));
};
async getDocument(ctx, documentId) {
const docAsBytes = await ctx.stub.getState(documentId);
return carAsBytes.toString();
};
}
Now comes the server side code
const { FileSystemWallet, Gateway } = require('fabric-network');
async main() {
const walletPath = path.join(process.cwd(), 'wallet');
const wallet = new FileSystemWallet(walletPath); // can be wherever your path is
const gateway = new Gateway();
await gateway.connect(ccp, { wallet, identity: 'user1', discovery: { enabled: false } });
const network = await gateway.getNetwork('mychannel');
// Get the contract from the network.
const contract = network.getContract('SomeName');
// Now over here lets assume you get the PDF file from a POST request using Multer
// We get the PDF file as a buffer, then convert to a string and send it
let pdfFile = req.file;
await contract.submitTransaction('writeDocument', req.file.buffer.toString('utf-8);
}
and if you want to retrieve the PDF file
const getDocument = async (req, res, next) => {
const walletPath = path.join(process.cwd(), 'wallet');
const wallet = new FileSystemWallet(walletPath); // can be wherever your path is
const gateway = new Gateway();
await gateway.connect(ccp, { wallet, identity: 'user1', discovery: { enabled: false } });
const network = await gateway.getNetwork('mychannel');
// Get the contract from the network.
const contract = network.getContract('SomeName');
const pdfAsString= await contract.evaluateTransaction('getDocument', req.body.documentId);
// Now do whatever with this
};
So I hope this answers your question regarding document upload, that being said its not a good practice to upload documents on a blockchain as it can slow down the transaction speed significantly.
In that case, you can store the document on a local server such as mongoDB and then refer a sha256 hash of the document on the blockchain to cross check the authenticity of the document at a later stage.

Resources