Chaincode (smart contract) là thành phần thực thi logic nghiêp vụ trên blockchain. Giống như code application bình thường được setup trên server, thực thi logic, đọc ghi update db, thì chaincode cũng vậy, đọc ghi và update state (trạng thái) vào sổ cái.

Ngôn ngữ lập trình hỗ trợ: js, java, và golang

Chaincode được triển khai trong container docker để đảm bảo tính an toàn và độc lập.

Các bước triển khai: Code → Install các node trong mạng → gọi (invoke hoặc query)

Ví dụ các bước code 1 chaincode hoàn chỉnh bằng ngôn ngữ javascript (nodejs), tham khảo sample https://github.com/hyperledger/fabric-samples/blob/v2.2.15/asset-transfer-events/chaincode-javascript/package.json

  1. File package.json
{
	"name": "chaincode-sample",
	"version": "0.0.1",
	"description": "chaincode sample",
	"main": "index.js",
	"engines": {
		"node": ">=12",
		"npm": ">=5"
	},
	"scripts": {
		"start": "fabric-chaincode-node start",
	},
	"author": "Hyperledger",
	"license": "Apache-2.0",
	"dependencies": {
		"fabric-contract-api": "2.5.4",
		"fabric-shim": "2.5.4",
		"dotenv": "^16.4.5"
	}
}

Lưu ý, version của fabric-contract-api và fabric-shim fix cố định, tránh để update version gây lỗi. dotenv là option, nếu trong code có cần đọc thông tin của file .env thì mới cần package này

  1. File index.js
'use strict';

const sampleContract = require('./lib/sampleContract.js');

module.exports.SampleContract = sampleContract;
module.exports.contracts = [sampleContract];
  1. File samleContract.js
'use strict';

const { Contract } = require('fabric-contract-api');

function authorization(ctx) {
  const clientMSPID = ctx.clientIdentity.getMSPID();
  const peerOrg = ctx.stub.getMspID();
  if (clientMSPID !== peerOrg) {
      throw new Error('Client is not authorized to mint new tokens');
  }
}

/*
  * Cause by clientIdentity.getId() returned format "x509::/OU=org1/OU=client/OU=department1/CN=9::/C=US/ST=North Carolina/O=Hyperledger/OU=Fabric/CN=fabric-ca-server"
  * /CN={value}: value is enrollmentId that mean cnId
  * This function is used to get CNId = address wallet
  */
async function getCNId(ctx) {
  const id = await ctx.clientIdentity.getID();
  const begin = id.indexOf("/CN=");
  const end = id.lastIndexOf("::/C=");

  return id.substring(begin + 4, end);
}

/**
 * composite key
 */
function createCompositeKey(ctx, keyMain, ...keyOther) {
  return ctx.stub.createCompositeKey(keyMain, keyOther);
}

async function saveData(ctx, key, value) {
  const data = Buffer.from(value.toString());
  await ctx.stub.putState(key, data);

  return data;
}

async function deleteData(ctx, key) {
  return await ctx.stub.deleteState(key);
}

async function readState(ctx, key) {
  const assetBuffer = await ctx.stub.getState(key);

  if (!assetBuffer || assetBuffer.length === 0) {
    throw new Error(`The asset ${key} does not exist`);
  }

  const assetString = assetBuffer.toString();
  const asset = JSON.parse(assetString);

  return asset;
}

class AssetTransferEvents extends Contract {
  async CreateAsset(ctx, id, attrs) {
    authorization(ctx);
    const sender = getCNId(ctx);
    const data = await saveData(ctx, createCompositeKey(ctx, id, sender), attrs);
    ctx.stub.setEvent('CreateAsset', data);
  }

  async TransferAsset(ctx, id, amount, newOwner) {
    authorization(ctx);
    const sender = getCNId(ctx);
    const keyAssetSender = createCompositeKey(ctx, id, sender);
    const asset = await readState(ctx, keyAssetSender);
    asset.amount = asset.amount - amount;
    const newAsset = {...asset, amount};

    const data = saveData(ctx, keyAssetSender, JSON.stringify(asset));
    saveData(ctx, createCompositeKey(ctx, id, newOwner), JSON.stringify(newAsset));

    ctx.stub.setEvent('TransferAsset', data);
  }

  async UpdateAsset(ctx, id, attrs) {
    authorization(ctx);
    const sender = getCNId(ctx);
    const keySender = createCompositeKey(ctx, id, sender);
    const asset = await readState(ctx, keySender);
    asset = {...asset, ...attrs};
    const data = await saveData(ctx, keySender, asset);

    ctx.stub.setEvent('UpdateAsset', data);
  }

  async DeleteAsset(ctx, id) {
    authorization(ctx);
    const sender = getCNId(ctx);
    const keySender = createCompositeKey(ctx, id, sender);
    const asset = await readState(ctx, keySender);
    const assetBuffer = Buffer.from(JSON.stringify(asset));
    deleteData(ctx, keySender);

    ctx.stub.setEvent('DeleteAsset', assetBuffer);
  }

  async ReadAsset(ctx, id) {
    authorization(ctx);
    const sender = getCNId(ctx);
    return await readState(ctx, createCompositeKey(ctx, id, sender));
  }
}

module.exports = AssetTransferEvents;