I successfully installed and instantiated chaincode on my chain. I'm able to enroll the admin and register a user trough nodejs. If I query the chaincode it only returns a correct response around 3 out of 5 times. The rest throws errors that the chaincode can't be found.
The chaincode installed is the basic example from the fabric samples.
My js file to query the chaincode (based on the fabcar example):
/*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const { FileSystemWallet, Gateway } = require('fabric-network');
const path = require('path');
const ccpPath = path.resolve(__dirname, 'connection-org1.json');
async function main() {
try {
// Create a new file system based wallet for managing identities.
const walletPath = path.join(process.cwd(), 'wallet');
const wallet = new FileSystemWallet(walletPath);
console.log(`Wallet path: ${walletPath}`);
// Check to see if we've already enrolled the user.
const userExists = await wallet.exists('user1');
if (!userExists) {
console.log('An identity for the user "user1" does not exist in the wallet');
console.log('Run the registerUser.js application before retrying');
return;
}
// Create a new gateway for connecting to our peer node.
const gateway = new Gateway();
await gateway.connect(ccpPath, { wallet, identity: 'user1', discovery: { enabled: true, asLocalhost: true } });
// 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('mycc');
const result = await contract.evaluateTransaction('query', 'a');
console.log(`Transaction has been evaluated, result is: ${result}`);
} catch (error) {
console.error(`Failed to evaluate transaction: ${error}`);
process.exit(1);
}
}
main();
The successful queries with the errors, nothing has been changed between those executions and they have been called with around 5 seconds beetween them.
root#devserver ~/fabric-samples/bla/first-network # node index.js
Transaction has been evaluated, resultb is: 210
root#devserver ~/fabric-samples/bla/first-network # node index.js
Transaction has been evaluated, resultb is: 210
root#devserver ~/fabric-samples/bla/first-network # node index.js
Transaction has been evaluated, resultb is: 210
root#devserver ~/fabric-samples/bla/first-network # node index.js
Transaction has been evaluated, resultb is: 210
root#devserver ~/fabric-samples/bla/first-network # node index.js
2019-09-09T18:53:24.646Z - warn: [Query]: evaluate: Query ID "[object Object]" of peer "peer1.PharmaProducer.bigpharma.com:8051" failed: message=cannot retrieve package for chaincode mycc/1.0, error open /var/hyperledger/production/chaincodes/mycc.1.0: no such file or directory, stack=Error: cannot retrieve package for chaincode mycc/1.0, error open /var/hyperledger/production/chaincodes/mycc.1.0: no such file or directory
at self._endorserClient.processProposal (/root/fabric-samples/bla/first-network/node_modules/fabric-network/node_modules/fabric-client/lib/Peer.js:140:36)
at Object.onReceiveStatus (/root/fabric-samples/bla/first-network/node_modules/grpc/src/client_interceptors.js:1207:9)
at InterceptingListener._callNext (/root/fabric-samples/bla/first-network/node_modules/grpc/src/client_interceptors.js:568:42)
at InterceptingListener.onReceiveStatus (/root/fabric-samples/bla/first-network/node_modules/grpc/src/client_interceptors.js:618:8)
at callback (/root/fabric-samples/bla/first-network/node_modules/grpc/src/client_interceptors.js:845:24), status=500, , url=grpcs://localhost:8051, name=peer1.PharmaProducer.bigpharma.com:8051, grpc.max_receive_message_length=-1, grpc.max_send_message_length=-1, grpc.keepalive_time_ms=120000, grpc.http2.min_time_between_pings_ms=120000, grpc.keepalive_timeout_ms=20000, grpc.http2.max_pings_without_data=0, grpc.keepalive_permit_without_calls=1, name=peer1.PharmaProducer.bigpharma.com:8051, grpc.ssl_target_name_override=peer1.PharmaProducer.bigpharma.com, grpc.default_authority=peer1.PharmaProducer.bigpharma.com, isProposalResponse=true
Failed to evaluate transaction: Error: cannot retrieve package for chaincode mycc/1.0, error open /var/hyperledger/production/chaincodes/mycc.1.0: no such file or directory
I expect the code to successfuly return a correct result every time and not randomly show an error that the code doesn't exist.
Any insight on how this can happen is appreciated.
Looking at the logs I can see why sometimes you get peer0 and occasionally you get peer1, and that is because the discovery results and processing result in it not being possible to get the list of peers in any particular order so with 2 peers in your org and the fact that this is not a long running app but a short running invocation sometimes peer0 will be first in the list of peers and sometimes peer1. Because you have only installed the chaincode on peer0, peer1 can't honour the evaluate request and return an error.
The node-sdk should detect this and then try peer0 but either you are using an older version of the node-sdk which definitely had an issue where it did not try a different peer or the node-sdk is thinking this is (incorrectly, but may not be able to distinguish) a chaincode response and passes that back to the caller.
The solution to avoid the error is to install the chaincode on all the peers.
Related
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.
My problem is that i get an Error while trying to use the chaincode for the test-network but running my application instead of the application-javascript provided at fabric-samples/asset-transfer-basic.
The steps that I follow are:
1. ./network.sh up createChannel -c mychannel -ca
2. ./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-javascript/ -ccl javascript
3. run my application thats is exaclty same with the one provided at docs (fabric-samples/asset-transfer-basic) to InitLedger and they query or update it.
The error I get is the following:
--> Submit Transaction: InitLedger, function creates the initial set of assets on the ledger
2021-11-09T15:54:16.248Z - error: [Transaction]: Error: No valid responses from any peers. Errors:
******** FAILED to run the application: Error: No valid responses from any peers. Errors:
The code in my application is exactly the same as the one at docs:
const ccp = buildCCPOrg1();
const caClient = buildCAClient(FabricCAServices, ccp, 'ca.org1.example.com');
const wallet = await buildWallet(Wallets, walletPath);
await enrollAdmin(caClient, wallet, mspOrg1);
await registerAndEnrollUser(caClient, wallet, mspOrg1, org1UserId, 'org1.department1');
const gateway = new Gateway();
await gateway.connect(ccp, {
wallet,
identity: org1UserId,
discovery: { enabled: false, asLocalhost: true } // using asLocalhost as this gateway is using a fabric network deployed locally
});
const network = await gateway.getNetwork(channelName);
const contract = network.getContract(chaincodeName);
await contract.submitTransaction('InitLedger');
You have peer discovery disabled in your code, usually, you do it when you specify peers you want to connect to but since you don't do it - the SDK client just didn't know to what peers it should connect so it connected to none of them, got 0 responses and threw an error. So I changed this line from your example:
discovery: { enabled: false, asLocalhost: true }
to:
discovery: { enabled: true, asLocalhost: true }
And it worked.
I have a network on hyperledger fabric 1.4.4 with 5 organizations A , B, C , D , E. I created a channel with these 5 orgs and installed my chaincode on org A and org B because only they are apart of the endorsement policy.
This is the endorsement policy :
{"identities":[{"role":{"name":"member","mspId":"AMSP"}},{"role":{"name":"member","mspId":"BMSP"}},{"role":{"name":"member","mspId":"CMSP"}},{"role":{"name":"member","mspId":"DMSP"}},{"role":{"name":"member","mspId":"EMSP"}}],"policy":{"2-of":[{"signed-by":0},{"signed-by":1}]}}
I am using a gateway with the below configuration to invoke the chain code
const walletPath = path.join('wallet' );
const wallet = new FileSystemWallet(walletPath);
let connectionOptions = {
identity: userName,
wallet: wallet,
discovery: { enabled:true, asLocalhost: true },
eventHandlerOptions: {
commitTimeout: 100,
strategy: DefaultEventHandlerStrategies.NETWORK_SCOPE_ALLFORTX
}
};
logger.debug('Connecting to Fabric gateway');
await gateway.connect(clientConnectionProfileJson, connectionOptions);
const network = await gateway.getNetwork(channelName);
const contract = await network.getContract(chaincodeName , contractName);
const transaction = contract.createTransaction(functionName);
await transaction.submit(<arguments>);
This is the error which I a getting , at the client level
2021-02-17T05:28:13.063Z - warn: [TransactionEventHandler]: _strategyFail: strategy fail for transaction "9be4da8b1d52ddde804d6c7c08d134ef4b6ac2043cbe0258b5b4c921424c9f04": TransactionError: Peer a-org-peer1.a-org.com:7051 has rejected transaction "9be4da8b1d52ddde804d6c7c08d134ef4b6ac2043cbe0258b5b4c921424c9f04" with code "ENDORSEMENT_POLICY_FAILURE"
This is what I see in all the peer logs
2021-02-17 05:28:12.313 UTC [vscc] Validate -> ERRO 0db VSCC error: stateBasedValidator.Validate failed, err validation of endorsement policy for chaincode {chaincodeName} in tx 26:0 failed: signature set did not satisfy policy
After some research , I found that this is a failure that is occurring when the org peer is trying to commit the transaction to the ledger , and finds that the signature set did not satisfy the policy.
I have gone ahead and looked at the transaction object using the getTransactionByID method. I see that there are two endorsers MSP with the correct sign certificates , these certificates belong to the one of the peers of A and B orgs. So the discovery service correctly identified the peers and even the peers have endorsed the transaction , but not sure why the transaction is not getting committed.
What am I missing here ?
How can I verify if the signatures are correct ?
To explicitly say to the gateway that the request should go to specific endorsing peers , I have used the below code.
const walletPath = path.join('wallet' );
const wallet = new FileSystemWallet(walletPath);
let connectionOptions = {
identity: userName,
wallet: wallet,
discovery: { enabled: true , asLocalhost: true },
eventHandlerOptions: {
commitTimeout: 100,
strategy: DefaultEventHandlerStrategies.NETWORK_SCOPE_ALLFORTX
}
};
logger.debug('Connecting to Fabric gateway');
await gateway.connect(clientConnectionProfileJson, connectionOptions);
const network = await gateway.getNetwork(channelName);
const channel = network.getChannel();
let endorsingPeers = [];
endorsingPeers.push(channel.getChannelPeer('a-org-peer1.a-org.com'));
endorsingPeers.push(channel.getChannelPeer('b-org-peer1.b-org.com'));
// Get addressability to org.cargoesnetwork.ebilloflading contract
// Use chaincodeName that is used for installing
const contract = await network.getContract(chaincodeName , contractName);
const transaction = contract.createTransaction(functionName).setEndorsingPeers(endorsingPeers);
await transaction.submit(<arguments>);
No luck , the transaction still fails with the same endorsement policy failure. I verified the transaction object if the endorser sign certs are correctly present. They are present , but still got the same error.
Out of curiosity , I changed the endorsement policy to only one org from two orgs , every thing worked as expected. The issue exists only when the policy contains more than one endorsing organisations.
Please help in debugging this issue.
An ENDORSEMENT_POLICY_FAILURE can occur for a number of reasons. The first being you don't have enough signatures which is what you have said you have already checked. Another reason is that not all the signatures match to the proposal that was sent.
In the 1.4 gateway apis the proposals are received and not compared to see if they all match before a proposal is sent to the orderer. The SDK will send all the signatures and one of the proposals that was received back. The signatures are created over the each peer's individual proposal response.
If those proposals don't match (which would mean that your chaincode is not deterministic) then one of those signatures will be ok, but the other one won't because it won't match the proposal that was sent to the orderer.
I would check that your chaincode is deterministic because it's possible that each peer is generating different responses. An example of non-deterministic chaincode for example is where it creates a new date and stores that in the world state. Each peer would create a slightly different date value resulting in differing responses.
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);
};
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.