How to insert documents to couchdb in bulk by chaincode - hyperledger-fabric

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.

Related

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

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.

NodeJS - Which layer should I roll back the transaction with multiple inserts?

I'm using Controller Layer/Service Layer/Repository Layer in my API with Postgres database (I'm using node-postgres).
In a given service, before inserting certain information A, I need to insert other information in other tables in the database. However, if there is a problem with one of the inserts, I would like to roll back the transaction. In node-postgres, rollbacks are done as follows:
const { Pool } = require('pg')
const pool = new Pool()
;(async () => {
// note: we don't try/catch this because if connecting throws an exception
// we don't need to dispose of the client (it will be undefined)
const client = await pool.connect()
try {
await client.query('BEGIN')
const queryText = 'INSERT INTO users(name) VALUES($1) RETURNING id'
const res = await client.query(queryText, ['brianc'])
const insertPhotoText = 'INSERT INTO photos(user_id, photo_url) VALUES ($1, $2)'
const insertPhotoValues = [res.rows[0].id, 's3.bucket.foo']
await client.query(insertPhotoText, insertPhotoValues)
await client.query('COMMIT')
} catch (e) {
await client.query('ROLLBACK')
throw e
} finally {
client.release()
}
})().catch(e => console.error(e.stack))
The database connection is called on the Repository Layer. However, the rollback situation will only happen on the Service Layer. How do I solve this situation, since for architectural reasons, I can't call the database connection directly in the Service Layer? Is there a problem with my architecture?
The easiest way to accomplish this is to put all the related transactions into a single method in your repository layer. This is generally "OK" because they are all fundamentally a single transaction.
If you need to support distributed transactions, you are better off using a unit of work pattern to implement holding onto the various transactions, and rolling back on the whole unit of work.

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.

How to get Smart Contract address when it is deployed with web3.js

I have tried to deploy a SmartContract from web3.js node library, I am getting a transaction hash from it but how would I get the contract address after It's been mined by a miner?
finally i got the answer
var Tx=require('ethereumjs-tx')
const Web3=require('web3')
const web3 = new Web3('https://rinkeby.infura.io/xxxxxxxxxxxxxxxxxx')
const account1='0xf2b6xxxxxxxxxxxxxxxxxxx83e9d52d934e5c'
const privateKey1=Buffer.from('xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx','hex')
web3.eth.getTransactionCount(account1,(err,txCount)=>{
//smart contract data
const data = 'your data here'
//create transaction object
const txObject={
nonce:web3.utils.toHex(txCount),
gasLimit:web3.utils.toHex(1000000),
gasPrice:web3.utils.toHex(web3.utils.toWei('10','gwei')),
data: data
}
//sign the transaction
const tx = new Tx(txObject)
tx.sign(privateKey1)
const serializedTx = tx.serialize()
const raw='0x'+serializedTx.toString('hex')
//broadcast the transaction
web3.eth.sendSignedTransaction(raw,(err,txHash)=>{
console.log('err : ',err,'txHash : ',txHash)
//use this hash to find smartcontract on etherscan
}).on('receipt', console.log,);
})
.on() method wait till the end of block mining and returns the address of transaction(here contract address). This method is applicable if you don't want to use metamask to sign your transaction and broadcast to the network.
This returns the contract address...
MyContract is the .json file in the build/contracts folder that is created by migration.
const netId = await web3.eth.net.getId();
const deployedNetwork = MyContract.networks[netId];
const contract = new web3.eth.Contract(
MyContract.abi,
deployedNetwork.address
);
Add .address after the object.
var contact = web3.eth.contract.new(abi,{from: web3.eth.accounts[0], data: bc});
console.log(contract.address); // Prints address
If you just want to get the smart contract by transaction hash which deployed the smart contract, you can use the the web3.eth.getTransactionReceipt fetches the receipt for a transaction hash. The receipt has a contactAddress field filled in if the transaction was a deployment.
Check this out: https://github.com/ChainSafe/web3.js/issues/3515

Web3 - can ETH transaction be cancelled?

I am trying to create a website where ethereum transaction can be made.
If I make an eth transaction using eth.sendTransaction({from:sender, to:receiver, value: amount}) can this transaction be cancelled?
I am asking this because I see that it doesn't take any promise or callback parameters, meaning I would have no idea whether the transaction has been made successfully or not?
Is it possible for web3 transactions to be cancelled? and if it is, how do I make sure that I get notified whether transaction has been made or not? (preferrably using promises or callback rather than having to check wallet every time)
It’s technically possible to cancel a transaction, but highly unlikely. Essentially, the only way to do so would be to try to send another transaction using the same account/nonce combination and hope it gets mined before the transaction you want to cancel is mined. It’s merely a side effect of how nonce is used to guarantee transaction order rather than a cancellation feature.
For your other question, web3.eth.sendTransaction does take a callback. It’s an optional second parameter after the options object and uses the error/result callback style. From the Web3js API:
web3.eth.sendTransaction(transactionObject [, callback])
Function - (optional) If you pass a callback the HTTP request is made asynchronous.
Using callbacks
If you want to make an asynchronous request, you can pass an optional callback as the last parameter to most functions. All callbacks are using an error first callback style
To cancel some ethereum pending transaction you need to:
Create a new transaction where you will send 0 ETH to yourselves.
You should increase the value of gas fees
You need to use the same nonce of the pending transaction.
Using web3js you can do something like this:
async function main() {
require('dotenv').config();
const { API_URL, PRIVATE_KEY } = process.env;
const { createAlchemyWeb3 } = require("#alch/alchemy-web3");
const web3 = createAlchemyWeb3(API_URL);
const myAddress = //put your address
const nonce = //put the nonce of pending tx
const maxFeePerGas = //increase the gas fee value comparated to the pending tx
const transaction = {
'to': myAddress,
'value': 0,
'gas': 21000,
'maxFeePerGas': maxFeePerGas,
'nonce': nonce
};
const signedTx = await web3.eth.accounts.signTransaction(transaction, PRIVATE_KEY);
web3.eth.sendSignedTransaction(signedTx.rawTransaction, function(error, hash) {
if (!error) {
console.log("The hash of your transaction is: ", hash);
} else {
console.log(error)
}
});
}
main();

Resources