node.jshyperledger-fabricblockchainhyperledger-chaincodehyperledger-fabric-sdk-js

Failed to evaluate transaction: Error: You've asked to invoke a function that does not exist: getLastPatientId


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

CommonContract.js

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;

AdminContract.js

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;

index.js

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];

admin.js

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 ?


Solution

  • 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.