链码开发基础(Node.js)

发布于:2024-05-20 ⋅ 阅读:(115) ⋅ 点赞:(0)

链码Node.js工程介绍

在Chaincode代码中主要包括package.json、index.js、lib目录以及test目录

链码Node.js工程package.json

  • 1. 主要依赖:fabric-contract-api、fabric-shim
  • 2. 测试主要依赖:mochai、chai、sinon
  • 3. 测试方法:npm run test
  • 4. 运行方法:npm run start

chaincode开发指南

1. 核心功能fabric-contract-api、fabric-shim

2. 代码测试不需要连接区块链 测试过程使用sinon.js模拟

Chaincode对应文件创建

在lib目录创建assetTransfer.js

初始化配置:

定义Contract类

定义AssetTransfer类

使用module.exports将AssetTransfer设置成对外访问模块

定义InitLedger功能

功能:初始化链码中数据,使用async将方法定义为异步执行。首先定义assets变量,再通过for循环加 载至区块链中

1.定义assets变量在其中加入模拟资产数据

2.使用for循环加载进区块链

  • (1)使用ctx.stub.putState(使用await获取异步执行结果) 添加至区块链,主键为ID
  • (2)加载进区块链时数据需要转化为Buffer类型
  • (3)使用'json-stringify-deterministic' 和'sort-keys
定义CreateAsset功能

功能:创建新数据到区块链,使用async将方法定义为异步执行。首先判断数据ID是否在链中存在,接下 来添加数据,最后通过json返回数据

  • 1. 使用ctx.stub.putState(使用await获取异步执行结果)添加至区块链,主键为ID
  • 2. 加载进区块链时数据需要转化为Buffer类型
  • 3. 使用'json-stringify-deterministic' 和'sort-keys-recursive'两个功能保证数据顺序
定义ReadAsset功能

功能:通过Asset的ID查询资产数据,以JSON格式返回

1. 通过ctx.stub.getState获取数据,查询参数为Asset的ID

2. 数据通过JSON返回

定义AssetExists功能

功能:通过Asset的ID判断资产是否存在

1. 通过ctx.stub.getState获取数据,查询参数为Asset的ID 2. 数据通过JSON返回

定义DeleteAsset功能

功能:通过Asset的ID删除数据。删除前需要判断资产是否存在

1. 通过ctx.stub.getState获取数据,查询参数为Asset的ID 2. 数据通过JSON返回

创建index.js并添加代码

/*
 * Copyright IBM Corp. All Rights Reserved.
 *
 * SPDX-License-Identifier: Apache-2.0
 */

'use strict';
const assetTransfer = require('./lib/assetTransfer')

module.exports.AssetTransfer = assetTransfer;
module.exports.contracts = [assetTransfer];

安装fabric依赖

npm设置国内源:npm config set registry https://registry.npmmirror.com

运行以下代码

npm install fabric-contract-api@2.4.2 fabric-shim@2.4.2

验证fabric依赖安装情况

查看package.json,将有如下变化:

安装测试框架依赖

npm install -D eslint mocha sinon chai sinon-chai

添加测试文件assetTransfer.test.js

'use strict';
 
const sinon = require('sinon');
const chai = require('chai');
const sinonChai = require('sinon-chai');
const  expect  = chai.expect;
const { Context } = require('fabric-contract-api');
const { ChaincodeStub } = require('fabric-shim');

const AssetTransfer = require('../lib/assetTransfer.js'); 
let assert = sinon.assert;
chai.use(sinonChai);  

describe('Asset Transfer Basic Tests', () => {
    let transactionContext, chaincodeStub, asset;

    beforeEach(() => {
        transactionContext = new Context();
        chaincodeStub = sinon.createStubInstance(ChaincodeStub);
        transactionContext.setChaincodeStub(chaincodeStub);

        chaincodeStub.putState.callsFake((key, value) => {
            if (!chaincodeStub.states) {
                chaincodeStub.states = {};
            }
            chaincodeStub.states[key] = value;
        });

        chaincodeStub.getState.callsFake(async (key) => {
            let ret;
            if (chaincodeStub.states) {
                ret = chaincodeStub.states[key];
            }
            return Promise.resolve(ret);
        });

        chaincodeStub.deleteState.callsFake(async (key) => {
            if (chaincodeStub.states) {
                delete chaincodeStub.states[key];
            }
            return Promise.resolve(key);
        });

        chaincodeStub.getStateByRange.callsFake(async () => {
            function* internalGetStateByRange() {
                if (chaincodeStub.states) {
                    const copied = Object.assign({}, chaincodeStub.states);
                    for (let key in copied) {
                        yield { value: copied[key] };
                    }
                }
            }

            return Promise.resolve(internalGetStateByRange());
            });
            asset = {
                ID: "asset1",
                Color: "blue",
                Size: 5,
                Owner: "Tomoko",
                ApprasisedValue: 300, 
        };
    });
    describe('Test InitLedger', () => {
        it('should return success on InitLedger', async () => {
            let assetTransfer = new AssetTransfer();
            await assetTransfer.InitLedger(transactionContext);  

            let ret = JSON.parse((await chaincodeStub.getState('asset1')).toString()); 
            expect(ret).to.eql(Object.assign({ docType: 'asset' }, asset));
        });
    });
});

运行npm命令获取测试结果 

征信积分链码开发(一) (Nodejs版)

背景介绍

在市场行为中存在企业吸引融资,银行贷款等金融行为。政府部门包括法院、税务局、市场监督管 理局等机构为了保证市场良好运行,需要对企业、银行等机构进行监督。故而在可以构建法院、税 务局、市场监督管理局为基础的信用积分规则,用于准确评定企业机构信用情况。为了能够对信用 情况进行标准化,采用积分的形式,对企业的信用进行打分。分数越高表示信用情况越好。反之, 信用分数越低则代表信用越差。他们制定的信用积分规则如下。

构建区块链目标

构建联盟链分别包括法院、税务局、市场监督管理局等机构,通过积分上链形式用于评估对应机构 的信用情况

开发链码征信数据 管理功能

在windows环境中创建credit_chaincode目录,使用VSCode打开credit_chaincode并初始化

npm init -y

准备package.json

添加.eslintrc.js

'use strict';

module.exports = {
    env: {
        node: true,
        mocha: true,
        es6: true
    },
    "extends": "eslint:recommended",
    "overrides": [
        {
            "env": {
                "node": true
            },
            "files": [
                ".eslintrc.{js,cjs}"
            ],
            "parserOptions": {
                "sourceType": "script"
            }
        }
    ],
    "parserOptions": {
        "ecmaVersion": "latest"
    },
    "rules": {
    }
}

编写项目入口index.js

'use strict';
const creditContract = require('./lib/creditChaincode');

module.exports.CreditContract = creditContract;
module.exports.contracts = [creditContract];

征信数据上链和查询功能

在VSCode对应项目中创建lib目录,并在其中加入creditChaincode.js文件,在文件中加入以下代码用于 实现数据的上传与查询

'use strict';
const { Contract } = require('fabric-contract-api');
class CreditContract extends Contract {
    //新建征信主体
    async createCreditSubject(ctx, key, name, type) {
        console.info('=== START : 创建征信主体 ===');
        const subject = {
            key: key, name: name,
            type: type, score: 0
        };
        await ctx.stub.putState(key, Buffer.from(JSON.stringify(subject)));
        console.info('=== END : 创建征信主体 ===');
        return subject;
    }
    // 查询征信主体
    async queryCreditSubject(ctx, subjectKey) {
        console.info('=== START : 查询征信主体 ===');
        const bytes = await ctx.stub.getState(subjectKey);
        if (!bytes || bytes.length === 0) {
            const msg = `${subjectKey} 征信主体不存在`;
            console.warn(msg);
            throw new Error(msg);
        }
        const subject = JSON.parse(bytes.toString());
        console.info('=== END : 查询征信主体 ===');
        return subject;
    }
    // 创建组合键
    async _createCompositeKey(ctx, indexName, key) {
        if (!key || key === "") {
            throw new Error(`Key 不能为空`);
        }
        if (indexName === "") {
            return key;
        }
        return ctx.stub.createCompositeKey(indexName, [key]);
    }
    // 创建征信主体
    async createCreditSubjectEx(ctx, key, name, type) {
        console.info('============= START : 创建征信主体=========== ');
        const subject = {
            key: key,
            name: name,
            type: type,
            score: 0
        };
        let indexName = 'name~type';
        let compositeKey = this._createCompositeKey(ctx, indexName, [name, type]);
        await ctx.stub.putState(compositeKey, Buffer.from(JSON.stringify(subject)));
        console.info('============= END : 创建征信主体=========== ');
        return subject;
    }
    // 按照name+type查询征信主体
    async queryCreditByNameType(ctx, name, type) {
        console.info('============= START : 查询征信主体===========');
        const CompositeKey = this._createCompositeKey(ctx, 'name~type', [
            name,
            type,
        ]);
        const bytes = await ctx.stub.getState(CompositeKey);
        if (!bytes || bytes.length === 0) {
            const msg = `${name} ${type} 征信主体不存在`;
            console.warn(msg);
            throw new Error(msg);
        }
        const subject = JSON.parse(bytes.toString());
        console.info('============= END : 查询征信主体===========');
        return subject;
    }
    //将状态的iterator转换成JSON
    async _Iterator2Json(iterator) {
        let allResults = [];
        let res = await iterator.next();
        while (!res.done) {
            if (res.value && res.value.value.toString()) {
                let jsonRes = {};
                console.log(res.value.value.toString('utf8'));
                jsonRes.Key = res.value.key;
                try {
                    jsonRes.Record = JSON.parse(res.value.value.toString('utf8'));
                } catch (err) {
                    console.log(err);
                    jsonRes.Record = res.value.value.toString('utf8');
                }
                allResults.push(jsonRes);
            }
            res = await iterator.next();
        }
        iterator.close();
        return allResults;
    }
    // 查询征信主体
    async queryCreditByRange(ctx, startKey, endKey) {
        let resultsIterator = await ctx.stub.getStateByRange(startKey, endKey);
        let results = await this._Iterator2Json(resultsIterator, false);
        return JSON.stringify(results);
    }
}
module.exports = CreditContract;

安装单元测试包

使用如下具体命令安装:

npm install -D eslint mocha sinon chai sinon-chai

安装fabric依赖的npm包

使用如下具体命令安装:

npm install fabric-contract-api@2.4.2 fabric-shim@2.4.2

编写测试用例

'use strict';
const sinon = require('sinon');
const chai = require('chai');
const sinonChai = require('sinon-chai');
const expect = chai.expect;
chai.use(sinonChai);
let assert = sinon.assert;
const { Context } = require('fabric-contract-api');
const { ChaincodeStub, ClientIdentity } = require('fabric-shim');
const CreditContract = require('../lib/creditChaincode.js');
describe('Credit Chaincode Test', () => {
    let stub, ctx, ClientId;

    beforeEach(() => {
        ctx = new Context();
        stub = sinon.createStubInstance(ChaincodeStub);
        stub.getMspID.returns('Org1');
        ctx.setChaincodeStub(stub);

        ClientId = sinon.createStubInstance(ClientIdentity);

        stub.putState.callsFake((key, value) => {
            if (!stub.states) {
                stub.states = {};
            }
            stub.states[key] = value;
        });
        stub.getState.callsFake(async (key) => {
            let ret;
            if (stub.states) {
                ret = stub.states[key];
            }
            return Promise.resolve(ret);
        });
        stub.deleteState.callsFake(async (key) => {
            if (stub.states) {
                delete stub.states[key];
            }
        });
    });
    describe('Test CreditSubject function', () => {
        it('should return success on createCreditSubject', async () => {
            let creditContract = new CreditContract();
            let creditSubject = await creditContract.createCreditSubject(ctx, "A001", "My Company", "Company");
            let scroe = creditSubject.score;
            expect(scroe).to.equals(0);
        });
        it('should return success on queryCreditSubject', async () => {
            let creditContract = new CreditContract();
            await creditContract.createCreditSubject(ctx, "A001", "My Company", "Company");
            let creditSubject = await creditContract.queryCreditSubject(ctx, "A001");
            let name = creditSubject.name;
            expect(name).to.equals("My Company");
        });
        it('should return success on createCreditSubjectEx', async () => {
            let creditContract = new CreditContract();
            let creditSubject = await creditContract.createCreditSubjectEx(ctx, "A001", "My Company", "Company");
            let scroe = creditSubject.score;
            expect(scroe).to.equals(0);
        });
        it('should return success on queryCreditByNameType', async () => {
            let creditContract = new CreditContract();
            await creditContract.createCreditSubjectEx(ctx, "A001", "My Company", "Company");
            let creditSubject = await creditContract.queryCreditByNameType(ctx, "My Company", "Company");
            let name = creditSubject.name;
            expect(name).to.equals("My Company");
        })
    });
})

执行测试

执行以下命令,进行测试:

npm run test

部署管理征信链码

链码操作准备

拷贝credit_chaincode到chaincode目录下,前提需要把项目中的配置文件删掉

打包链码

启动容器

docker-compose -f fabric-compose.yaml start

 查看

 进入上级test目录运行以下命令打包:

export FABRIC_CFG_PATH=${PWD}/config

peer lifecycle chaincode package ./chaincode/credit_chaincode.tar.gz --path ./chaincode/credit_chaincode --lang node --label credit_chaincode_1.1

检查打包结果

安装链码

运行以下进入fabric-cli容器:

docker exec -it fabric-cli bash

1. 在org1中安装 运行以下链码安装:

. scripts/set-env.sh 1 0 7051
peer lifecycle chaincode install chaincode/credit_chaincode.tar.gz

2. 在org2中安装 运行以下链码安装:

. scripts/set-env.sh 2 0 9051
peer lifecycle chaincode install chaincode/credit_chaincode.tar.gz

3. 查看安装情况

peer lifecycle chaincode queryinstalled

批准链码

org1批准链码

1. 设置链码环境变量(ID为上面安装的链码)

export CC_PACKAGE_ID=credit_chaincode_1.1:2c3741f2faf6862eecd3f3c0f7296bbba1d93cf4302cca64e79a7689fba47a23

 2. 设置Org1环境变量

. scripts/set-env.sh 1 0 7051

3.批准链码

peer lifecycle chaincode approveformyorg -o orderer.example.com:7050 --ordererTLSHostnameOverride orderer.example.com --channelID $CHANNEL_NAME --name credit_chaincode --version 1.1 --package-id $CC_PACKAGE_ID --sequence 1 --tls --cafile $ORDERER_CA

org2批准链码

1. 设置Org2环境变量
 

. scripts/set-env.sh 2 0 9051

2.批准链码

peer lifecycle chaincode approveformyorg -o orderer.example.com:7050 --ordererTLSHostnameOverride orderer.example.com --channelID $CHANNEL_NAME --name credit_chaincode --version 1.1 --package-id $CC_PACKAGE_ID --sequence 1 --tls --cafile $ORDERER_CA

检查提交准备

peer lifecycle chaincode checkcommitreadiness --channelID $CHANNEL_NAME --name credit_chaincode --version 1.1 --sequence 1 --tls --cafile $ORDERER_CA --output json

提交链码

提交链码是特殊交易,需要背书节点背书,因此需要指定2个背书节点。具体操作如下:

peer lifecycle chaincode commit -o orderer.example.com:7050 --ordererTLSHostnameOverride orderer.example.com --channelID $CHANNEL_NAME --name credit_chaincode --version 1.1 --sequence 1 --tls --cafile $ORDERER_CA --peerAddresses peer0.org1.example.com:7051 --tlsRootCertFiles $PEER0_ORG1_CA --peerAddresses peer0.org2.example.com:9051 --tlsRootCertFiles $PEER0_ORG2_CA

查询提交的链码

peer lifecycle chaincode querycommitted --channelID $CHANNEL_NAME --name credit_chaincode --tls --cafile $ORDERER_CA

使用docker查看链码安装情况

docker ps

查看运行镜像形成容器情况

docker logs -f b1ddd21303a5

测试验证

调用createCreditSubject功能

peer chaincode invoke -o orderer.example.com:7050 --tls --cafile $ORDERER_CA --channelID $CHANNEL_NAME --name credit_chaincode --peerAddresses peer0.org1.example.com:7051 --tlsRootCertFiles $PEER0_ORG1_CA --peerAddresses peer0.org2.example.com:9051 --tlsRootCertFiles $PEER0_ORG2_CA -c '{"function":"createCreditSubject", "Args":["A001","My Company","Company"]}'

 调用queryCreditSubject功能

peer chaincode query -C $CHANNEL_NAME --name credit_chaincode -c '{"function":"queryCreditSubject","Args":["A001"]}'

观察正在运行的链码容器运行情况