Skip to content
This repository has been archived by the owner on Nov 25, 2020. It is now read-only.

Writing chaincode

Jonas Snellinckx edited this page Jul 17, 2018 · 5 revisions

Writing chaincode

An example implementation of using this tool to write chaincode is located at wearetheledger/fabric-network-boilerplate.

Chaincode View definition

The Chaincode class is a base class containing handlers for the Invoke() and Init() function which are required by fabric-shim. The Init() function can be overwritten by just implementing it in your MyChaincode class, this function should be called init and will also be injected with the same arguments as your custom functions.

export class MyChaincode extends Chaincode {

    async init(stubHelper: StubHelper, args: string[]) {
      return 'this will override the init method in Chaincode';
    }
    
}

The Chaincode base class also implements the Invoke() method, it will search in your class for existing chaincode methods with the function name you sent. It will also automatically wrap and serialize the reponse with shim.success() and shim.error(). You can just return the javascript object and it will do the rest, BUT returning a Buffer is still supported aswell. So for example, if we invoke our chaincode with function queryCar, the function below will be executed.

export class MyChaincode extends Chaincode {

    async queryCar(stubHelper: StubHelper, args: string[]): Promise<any> {

        const verifiedArgs = await Helpers.checkArgs<{ key: string }>(args[0], Yup.object()
            .shape({
                key: Yup.string().required(),
            }));

        const car = await stubHelper.getStateAsObject(verifiedArgs.key); //get the car from chaincode state

        if (!car) {
            throw new ChaincodeError('Car does not exist');
        }

        return car;
    }
    
}

StubHelper View definition

The StubHelper is a wrapper around the fabric-shim Stub. Its a helper to automatically serialize and deserialize data being saved/retreived.

Get an object by key

This is the same as the getState function, but it will deserialize the Buffer to an object.

stubHelper.getStateAsObject(verifiedArgs.key);

Put an object by key

This is the same as the putState function, but it will serialize the object to a Buffer for you.

stubHelper.putState(verifiedArgs.key, data);

Get a date by key

This is the same as the getState function, but it will deserialize the Buffer to a Date.

stubHelper.getStateAsDate(verifiedArgs.key);

Get a string by key

This is the same as the getState function, but it will deserialize the Buffer to a String.

stubHelper.getStateAsString(verifiedArgs.key);

Get an Array from rich query

This is the same as the getQueryResult function, but it will deserialize and convert the iterator to an Array for you. Passing the keyValue is optional and will return an array with key values.

stubHelper.getQueryResultAsList(queryString, keyValue);

Get an Array from range query

This is the same as the getStateByRange function, but it will deserialize and convert the iterator to an Array for you. Passing the keyValue is optional and will return an array with key values.

stubHelper.getStateByRangeAsList(startKey, endKey);

Get an Array from history by key

This is the same as the getHistoryByKey function, but it will deserialize and convert the iterator to an Array for you.

stubHelper.getHistoryForKeyAsList(key);

Get the original stub

This will expose the stub which is returned by fabric-shim.

stubHelper.getStub();

Get ClientIdentity

This will expose the ClientIdentity which is returned by fabric-shim.

stubHelper.getClientIdentity();

Get ChaincodeCrypto

This will expose the ShimCrypto which is returned by fabric-shim-crypto required for encryption, decryption, signing and verification.

stubHelper.getChaincodeCrypto();

Generating deterministic uuids

It is important for a blockchain applications to be deterministic. This means you can't use randoms and current dates. This is why we added a way to generate deterministic uuids based on a given key.

stubHelper.generateUUID("CAR")

Examples

Query by key

Returns an item matching the key

async queryCar(stubHelper: StubHelper, args: string[]): Promise<any> {

        const verifiedArgs = await Helpers.checkArgs<{ key: string }>(args[0], Yup.object()
            .shape({
                key: Yup.string().required(),
            }));

        const car = await stubHelper.getStateAsObject(verifiedArgs.key); //get the car from chaincode state

        if (!car) {
            throw new ChaincodeError('Car does not exist');
        }

        return car;
}

Perform a rich query

Returns an array of items matching the rich query. Notice that we add the property 'docType' in the create method.

async queryAllCars(stubHelper: StubHelper, args: string[]): Promise<any> {

        return stubHelper.getQueryResultAsList(
            {selector:{ docType: 'car'}}
        ); 
}

Query by range

async queryAllCars(stubHelper: StubHelper, args: string[]): Promise<any> {

        const startKey = 'CAR0';
        const endKey = 'CAR999';

        return stubHelper.getStateByRangeAsList(startKey, endKey);
}

Creating

async createCar(stubHelper: StubHelper, args: string[]) {
        const verifiedArgs = await Helpers.checkArgs<any>(args[0], Yup.object()
            .shape({
                key: Yup.string().required(),
                make: Yup.string().required(),
                model: Yup.string().required(),
                color: Yup.string().required(),
                owner: Yup.string().required(),
            }));

        let car = {
            docType: 'car',
            make: verifiedArgs.make,
            model: verifiedArgs.model,
            color: verifiedArgs.color,
            owner: verifiedArgs.owner
        };

        await stubHelper.putState(verifiedArgs.key, car);
}

Updating object

async changeCarOwner(stubHelper: StubHelper, args: string[]) {

        const verifiedArgs = await Helpers.checkArgs<{ key: string; owner: string }>(args[0], Yup.object()
            .shape({
                key: Yup.string().required(),
                owner: Yup.string().required(),
            }));

        let car = await <any>stubHelper.getStateAsObject(verifiedArgs.key);

        car.owner = verifiedArgs.owner;

        await stubHelper.putState(verifiedArgs.key, car);
}

Using private data

Collections can be passed as options in the following functions as the last argument: getQueryResultAsList, getStateByRangeAsList, deleteAllByQuery, putState, getStateAsObject, getStateAsString, getStateAsDate,

async createPrivateCar(stubHelper: StubHelper, args: string[]) {
       
       const verifiedArgs = await Helpers.checkArgs<any>(args[0], Yup.object()
            .shape({
                key: Yup.string().required(),
                make: Yup.string().required(),
                model: Yup.string().required(),
                color: Yup.string().required(),
                owner: Yup.string().required(),
            }));

        let car = {
            docType: 'car',
            make: verifiedArgs.make,
            model: verifiedArgs.model,
            color: verifiedArgs.color,
            owner: verifiedArgs.owner
        };

        await stubHelper.putState(verifiedArgs.key, car, {privateCollection: "testCollection"});
}
async getPrivateCar(stubHelper: StubHelper, args: string[]) {
       
       const verifiedArgs = await Helpers.checkArgs<any>(args[0], Yup.object()
            .shape({
                key: Yup.string().required(),
            }));
        const car = await stubHelper.getStateAsObject(verifiedArgs.key, {privateCollection: "testCollection"});

        if (!car) {
            throw new ChaincodeError('Car does not exist');
        }

        return car;
}

Transform View definition

The Transform class is a helper to provide data transformation to and from the formats required by hyperledger fabric.