Reputation: 5
I want to do inheritance in Hyperledger Fabric Chaincode using NodeJs.
I have created two classes CommonContract
and AdminContract
. CommonContract
is the base class and AdminContract child class. But I got error when I invoke getLastPatiendId
function from AdminContract
. Error is as follows.
Failed to evaluate transaction: Error: You've asked to invoke a function that does not exist: getLastPatientId
[nodemon] app crashed - waiting for file changes before starting...
Even though getLastPatientId
function is written in contract it is giving error function does not exists.
Below are the code of AdminContract, CommonContract, index.js file for chaincode and the server API which invoke transaction
This is CommonContract smart contract which is a base class.
'use strict';
const { Contract } = require('fabric-contract-api');
class CommonContract extends Contract {
async initLedger(ctx) {
console.info('============= START : Initialize Ledger ===========');
const initData = [
{
"firstName": "ABC",
"middleName": "D",
"lastName": "BCA",
"password": "d74ff0ee8da3b9806b18c877dbf29bbde50b5bd8e4dad7a3a725000feb82e8f1",
"age": "16",
"phoneNumber": "1234567890",
"address": "India",
"bloodGroup": "O+ve",
"updatedBy": "initLedger",
"symptoms": "fever",
"diagnosis": "Covid",
"treatment": "dolo 2 times",
"other": "no",
},
{
"firstName": "Five",
"middleName": ".H.",
"lastName": "CDA",
"password": "d74ff0ee8da3b9806b18c877dbf29bbde50b5bd8e4dad7a3a725000feb82e8f1",
"age": "16",
"phoneNumber": "1234567890",
"address": "India",
"bloodGroup": "O+ve",
"updatedBy": "initLedger",
"symptoms": "fever",
"diagnosis": "Covid",
"treatment": "dolo 2 times",
"other": "no",
}
]
for (let i = 0; i < initData.length; i++) {
initData[i].docType = 'patient';
await ctx.stub.putState('PID' + i, Buffer.from(JSON.stringify(initData[i])));
console.log('Data Added:---', initData[i]);
}
}
async getPatient(ctx, patientId){
const patient = await ctx.stub.getState(patientId);
if(patient.length || patient.length > 0)
console.log(patient);
let data = JSON.parse(patient.toString());
return data;
}
async getAllPatient(ctx){
const startKey = '';
const endKey = '';
const allResults = [];
for await (const {key, value} of ctx.stub.getStateByRange(startKey, endKey)){
const strValue = Buffer.from(value).toString('utf8');
let record;
try {
record = JSON.parse(strValue);
} catch (err) {
console.log(err);
record = strValue;
}
allResults.push({ Key: key, Record: record });
}
console.info(allResults);
return JSON.stringify(allResults);
}
}
module.exports = CommonContract;
This is an AdminContract smart contract that inherits CommonContract and has a single function getLastPatientId()
it generally returns the patient id of the last patient created in the ledger.
'use strict';
let Patient = require('./PatientAssets.js');
const CommonContract = require('./CommonContract.js');
class AdminContract extends CommonContract {
async getLastPatientId(ctx) {
let result = await getAllPatient(ctx);
console.log(result);
return result[result.length - 1].patiendId;
}
}
module.exports = AdminContract;
This file is main entry point all smart contract.
'use strict';
const CommonContract = require('./lib/CommonContract.js');
const AdminContract = require('./lib/AdminContract.js');
module.exports.contracts = [CommonContract, AdminContract];
This is server side route file which invoke the getLastPatientId
function which is in AdminContract.js smart contract file.
router.get('/getAllPatient', async (req, res) => {
try {
// load the network configuration
const ccpPath = path.resolve(__dirname, '..', '..', 'network-config', 'organizations', 'peerOrganizations', 'doctor.hospital_network.com', 'connection-doctor.json');
const ccp = JSON.parse(fs.readFileSync(ccpPath, 'utf8'));
// Create a new file system based wallet for managing identities.
const walletPath = path.join(process.cwd(), 'wallet');
const wallet = await Wallets.newFileSystemWallet(walletPath);
console.log(`Wallet path: ${walletPath}`);
// Check to see if we've already enrolled the user.
const identity = await wallet.get('appUser1');
if (!identity) {
console.log('An identity for the user "appUser" 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(ccp, { wallet, identity: 'appUser1', discovery: { enabled: true, asLocalhost: true } });
// Get the network (channel) our contract is deployed to.
const network = await gateway.getNetwork('hospital');
// Get the contract from the network.
const contract = network.getContract('basic');
const result = await contract.evaluateTransaction('getLastPatientId');
const data = JSON.parse(result);
console.log(`Transaction has been evaluated, result is: ${result}`);
// Disconnect from the gateway.
await gateway.disconnect();
return data;
} catch (error) {
console.error(`Failed to evaluate transaction: ${error}`);
process.exit(1);
}
});
I found out that evalutateTransaction()
invokes the function in CommonContract but was not able to invoke AdminContract functions. Please help me out. What is the error in my code ?
Upvotes: 0
Views: 1091
Reputation: 797
This is because you need to specify the contract name when calling transactions, except for the first contract which is treated as a default. For example, you should be able to successfully call initLedger
, CommonContract:initLedger
, and AdminContract:getLastPatientId
but getLastPatientId
will fail because there is no such transaction on the default contract.
You can see what transactions are available, and which contract is the default, by getting the metadata for the contract. You can get the metadata using the org.hyperledger.fabric:GetMetadata
transaction, where org.hyperledger.fabric
is the system contract and GetMetadata
is the transaction. The ccmetadata utility will call that get metadata transaction if that helps.
You can also customise the contract names using a constructor. For example, to call an Admin:getLastPatientId
transaction, add the following constructor:
class AdminContract extends Contract {
constructor() {
super('Admin');
}
//...
}
Note: I don't think it's related to your current problem but I'm not sure why you want to use inheritance in this case. It might cause other problems so I would stick to extending Contract
as shown above.
Upvotes: 2
Reputation: 5
First I was implementing multiple contracts.
By getting the metadata for the contracts there is one default contract.
{
CommonContract: {
name: 'CommonContract',
contractInstance: { name: 'CommonContract', default: true },
transactions: [ [Object], [Object], [Object], [Object], [Object] ],
info: { title: '', version: '' }
},
AdminContract: {
name: 'AdminContract',
contractInstance: { name: 'AdminContract' },
transactions: [ [Object], [Object], [Object], [Object], [Object] ],
info: { title: '', version: '' }
},
DoctorContract: {
name: 'DoctorContract',
contractInstance: { name: 'DoctorContract' },
transactions: [ [Object], [Object], [Object] ],
info: { title: '', version: '' }
},
PatientContract: {
name: 'PatientContract',
contractInstance: { name: 'PatientContract' },
transactions: [ [Object], [Object] ],
info: { title: '', version: '' }
},
'org.hyperledger.fabric': {
name: 'org.hyperledger.fabric',
contractInstance: { name: 'org.hyperledger.fabric' },
transactions: [ [Object] ],
info: { title: '', version: '' }
}
}
Now in the admin.js
file, looking at the line where we getting the contract
const contract = network.getContract('basic');
Here this statement was using default contract i.e. CommonContract
, But I was implementing multiple contracts so I have to give the reference of that contract, so I make this change.
const contract = network.getContract('basic', 'AdminContract');
and now when I invoke the contract
const result = await contract.evaluateTransaction('getLastPatientId');
or
const contract = network.getContract('basic');
const result = await contract.evaluateTransaction('AdminContract:getLastPatientId');
It works...
Upvotes: 0