본문 바로가기

Hyperledger Fabric/Chaincode for nodejs

체인코드 예제

체인코드 작성 환경구성

package.json 파일 생성

 

cd /opt/gopath/src/github.com/hyperledger/fabric-samples/chaincode/
mkdir example
cd example
npm init

 

 

package.json 파일 복사-붙여넣기

 

{
	"name": "example",
	"version": "1.0.0",
	"description": "example chaincode implemented in node.js",
	"engines": {
		"node": ">=8.4.0",
		"npm": ">=5.3.0"
	},
	"scripts": { "start" : "node example.js" },
	"engine-strict": true,
	"license": "Apache-2.0",
	"dependencies": {
		"fabric-shim": "1.2.0"
	}
}

 

구분 설명
name 패키지의 고유성 판별할 이름
version 패키지의 버전
description 패키지의 설명
main 프로그램의 시작점이 되는 모듈 ID
scripts script 명령
author 사용자 정보
license 라이센스
engines 동작 가능한 node 의 버전을 지정
engines-strict 사용하지 않으면 engines의 값을 단지조언용으로만 사용
dependencies 의존성을 규정

 

체인코드 인터페이스 구현

Base code

 

'use strict';

const shim = require('fabric-shim');

let exampleChaincode = class {

    async Init(stub) {
        console.info('=========== Instantiated Chaincode ===========');
        console.info('Transaction ID: ' + stub.getTxID());
        return shim.success();
    }

    async Invoke(stub) {
        console.info('=========== Invoke/Query Chaincode ===========');
        let ret = stub.getFunctionAndParameters(); // 호출 할 함수이름과 대상함수에 전달할 인수 배열을 포함하는 객체를 반환
        console.info(ret);

        let method = this[ret.fcn]; // 호출할 함수이름 선언
        console.info(method);

        try {
            let payload = await method(stub, ret.params, this); // 함수 호출
            return shim.success(payload);

        } catch (err) {
            console.log(err);
            return shim.error(err);

        }
    }

    /**
     * 
     * 사용자 정보 등록
     * 
     * 사용자ID     userId
     * 이름         name
     * 주소         address
     * 연락처       contact
     * 
     */

    async registerUserInfo(stub, args) {
    }
    
    /**
     * 
     * 자산 등록
     * 
     * 자산코드     assetCd
     * 자산내용     content
     * 등록일       regYmd
     * 소유자       owner
     * 
     */

    async registerAsset(stub, args) {
    }

    /**
     * 자산 이동
     * 
     * 자산코드    assetCd
     * 소유주      owner
     * 
     */

    async moveAsset(stub, args) {
    }


    /**
     * 자산/사용자 정보 조회
     * 
     * 자산코드/유저아이디    key
     * 
     */


    async query(stub, args) {
    }



};

shim.start(new exampleChaincode());

 

체인코드 인터페이스 설명

 

 

① shim.success()

: 상태코드(200) 및 payload(선택) 를 표준 응답 객체로 반환

 

 

① stub.getFunctionAndParameter()

: 호출 할 함수 이름과 대상 함수에 전달할 인수 배열을 포함하는 객체를 반환

Ex) {fcn:’registerFarm’, params: [‘farm01’ ,’2’ ,’3’ ,’4’ ,’5’]}

② this[ret.fcn]

: 호출 할 함수 이름을 method 변수에 할당

③ let payload = await method(stub, ret.params, this)

: 앞에서 할당 받은 함수 호출 후 리턴 값을 shim.success()의 파라미터로 넘겨줌

④ shim.error(msg)

: 에러 메시지 반환

실습내용

 

사용자 정보 등록 함수

 

 

① key 값 정의

② key 값이 빈 값(empty string) 으로 getState 혹은 putState 를 하게 되면 peer container 중지 되기 때문에 에러 처리

③ 매개변수의 개수가 맞지 않으면 에러 처리

 

 

⑤ 블록체인에 저장할 데이터 정의

⑥ 원장에 데이터를 저장할 수 있는 putState 함수 사용

https://fabric-shim.github.io/release-1.2/index.html

⑦ 성공 응답 메시지 정의

⑧ 성공 메시지 반환

 

    /**
     * 
     * 사용자 정보 등록
     * 
     * 사용자ID     userId
     * 이름         name
     * 주소         address
     * 연락처       contact
     * 
     */

    async registerUserInfo(stub, args) {
        console.info('============= END : registerUserInfo() ===========');
        
        // key 정의
        let key = args[0];

        // key 값이 없으면 에러 처리
        if(!key){
            var err = {};
            err.status = 14010;
            err.message = 'Key value doesn\'t exist';
            throw new Error("#" + JSON.stringify(err));
        }

        // 매개변수 개수 체크
        if (args.length != 4) {
            var err = {};
            err.status = 14011;
            err.message = 'Incorrect number of arguments';
            throw new Error("#" + JSON.stringify(err));
        }

        
        // data 정의
        var user = {
            docType: 'registerUserInfo',
            userId: args[0],                // 사용자ID
            name: args[1],                  // 이름
            address: args[2],               // 주소
            contact: args[3],               // 연락처
        };

        try{
            // 원장에 데이터 저장
            await stub.putState(key, Buffer.from(JSON.stringify(user)));

            // 성공 응답 메시지 정의
            var response = {};
            response.status = 14001;
            response.message = 'Success Message';

            console.info('============= END : registerUserInfo() ===========');
            
            // payload 리턴 
            return Buffer.from(JSON.stringify(response));

        }catch(e){
            console.log(e);
            throw new Error(e);
        }        
    }

유저 및 자산 정보 쿼리 함수

① key 값 정의

② key 값이 빈값(null, empty string) 으로 getState 혹은 putState 를 하게 되면 peer container 중지 되기 때문에 에러 처리

③ 매개변수의 개수가 맞지 않으면 에러 처리

 

⑤ 원장에 데이터를 조회할 수 있는 getState 함수 사용

https://fabric-shim.github.io/release-1.2/index.html

⑥ 데이터 없을 때 빈 오브젝트 반환

⑦ 데이터 반환

 

    async query(stub, args) {
        // key 정의
        let key = args[0];

        // key 값이 없으면 에러 처리
        if(!key) {
            var err = {};
            err.status = 14010;
            err.message = 'Key value doesn\'t exist';
            throw new Error("#" + JSON.stringify(err));
        }

        // 매개변수 개수 체크
        if (args.length != 1) {
            var err = {};
            err.status = 14013;
            err.message = 'Incorrect number of arguments.';
            throw new Error("#" + JSON.stringify(err));
        }
        
        // 원장 데이터 조회
        let valueBytes = await stub.getState(key);

        // 데이터 없으면 빈 값 반환
        if (!valueBytes || valueBytes.toString().length <= 0) {
            return Buffer.from(JSON.stringify({}));
        }

        console.log(valueBytes.toString());

        // 데이터 값 반환
        return valueBytes;
    }

체인코드 install/instantiate/invoke/query

도커 CLI 컨테이너 접속

 

docker container exec -it cli bash

 

chaincode install

 

peer chaincode install -n excc -v 1.0 -p /opt/gopath/src/github.com/chaincode/example/ -l node

 

chaincode instantiate

 

peer chaincode instantiate -o orderer.example.com:7050 --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n excc -v 1.0 -c '{"Args":[""]}' -P 'OR ('\''Org1MSP.member'\'','\''Org2MSP.member'\'')'

 

도커 chaincode 컨테이너 로그 확인

 

docker container logs dev-peer0.org1.example.com-excc-1.0 -f

Invoke

CLI

 

peer chaincode invoke -o orderer.example.com:7050 --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n excc -c '{"Args":["registerUserInfo","user001","sohee","msh227@nongshim.co.kr","01011112222"]}'

query

CLI

 

peer chaincode query -C mychannel -n excc -c '{"Args":["query","user001"]}'

실습:: 자산 정보등록 함수

• 함수이름 : registerAsset

• 파라미터 정의

 

필드 설명 비고
assetCd 자산 코드 Key 값
content 자산 내용  
regYmd 등록일  
Owner 소유자  
doctype 구분자 값을 registerAsset 로 고정

 

• 사용메소드 : stub.putState(key, value)

• 응답 코드

 

에러 코드 메시지
14010 Key value doesn't exist
14011 Incorrect number of arguments
14013 There is no Asset Information

 

응답 코드 메시지
14001 Success Transaction
 
    /**
     * 
     * 자산 등록
     * 
     * 자산코드     assetCd
     * 자산내용     content
     * 등록일       regYmd
     * 소유자       owner
     * 
     */

    async registerAsset(stub, args) {
        console.info('============= END : registerAsset() ===========');
        
        // key 정의

        // key 값이 없으면 에러 처리

        // 매개변수 개수 체크
        
        // data 정의

        try{
            // 원장에 데이터 저장

            // 성공 응답 메시지 정의

            console.info('============= END : registerAsset() ===========');

            // payload 리턴 

        }catch(e){
            console.log(e);
            throw new Error(e);

        }        
    }

 

실습:: 자산 이동 함수

• 함수이름 : moveAsset

• 파라미터 정의

 

필드 설명 비고
assetCd 자산 코드 Key 로 지정
owner 소유자  

• 사용메소드 : stub.putState(key, value), stub.getState(key)

• 응답 코드

 

에러 코드 메시지
14010 Key value doesn't exist
14011 Incorrect number of arguments
14013 There is no Asset Information

 

응답 코드 메시지
14001 Success Transaction
 
    /**
     * 자산 이동
     * 
     * 자산코드    assetCd
     * 소유주      owner
     * 
     */

    async moveAsset(stub, args) {
        console.info('=================== START : moveAsset() ===================');
        
        // key 정의
        let key = args[0];

        // key 값이 없으면 에러 처리

        // 매개변수 개수 체크
        if (args.length != 2) {
            var err = {};
            err.status = 14010;
            err.message = 'Incorrect number of arguments';
            throw new Error("#" + JSON.stringify(err));
        }

        // 데이터 가져오기

        // 가져온 데이터 값이 없을 경우 에러정의
        if (!assetBytes.toString()) {
            var err = {};
            err.status = 14013;
            err.message = 'There is no Asset Information';
            throw new Error("#" + JSON.stringify(err));
        }

        // JSON 형식으로 변환
        var assetInfo = JSON.parse(assetBytes);

        // owner 필드 값 변경

        try{
            // 원장에 데이터 저장

            // 성공 응답 메시지 정의
            var response = {};
            response.status = 14001;
            response.message = 'Success Message';

            console.info('============= END : moveAsset() ===========');

            // payload 리턴 
            return Buffer.from(JSON.stringify(response));

        }catch(e){
            console.log(e);
            throw new Error(e);

        }        
    }

체인코드 upgrade

 

docker container exec -it cli bash

chaincode install

 

peer chaincode install -n excc -v 2.0 -p /opt/gopath/src/github.com/chaincode/example/ -l node

 

chaincode upgrade

 

peer chaincode upgrade -o orderer.example.com:7050 --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n excc -v 2.0 -c '{"Args":[""]}' -P 'OR ('\''Org1MSP.member'\'','\''Org2MSP.member'\'')'

Invoke

registerAseet

 

["asset001","certification","2019-01-17","user001"]

 

moveAsset

 

["user002","asset001"]

 

query

 

["asset001"]

CouchDB 쿼리 함수

 

① queryString 값 정의

② 쿼리 값 담을 allResult 변수 초기화

③ 매개변수의 개수가 맞지 않으면 에러 처리

④ 매개변수 개수 체크

 

 

⑤ getQueryResult 를 사용하여 couchDB 를 통해 쿼리

⑥ 리턴 값이 iterator 이기 때문에 반복 수행, 객체를 순서대로 순회해야 하기 때문에 next 사용

⑦ 객체에 값이 있으면

⑧ 그 값을 읽을 수 있는 값으로 가공 하여 결과값을 담을 allResult 변수에 넣기

 

 

⑨ 반복이 끝나면 객체에서 done 이 true 를 반환 하면서 while 이 종료되고 allResults를 리턴

 

    async queryCouchDB(stub, args) {

        // queryString 정의
        let queryString = args[0];

        // 리턴 값 초기화
        let allResults = [];

        // queryString 값이 없으면 에러 처리
        if (!queryString) {
            var err = {};
            err.status = 14010;
            err.message = 'Key value doesn\'t exist';
            throw new Error("#" + JSON.stringify(err));
        }

        // 매개변수 개수 체크
        if (args.length < 1) {
            var err = {};
            err.status = 14011;
            err.message = 'Incorrect number of arguments.';
            throw new Error("#" + JSON.stringify(err));        
        }


        // state database 에서 쿼리 수행
        let resultsIterator = await stub.getQueryResult(queryString);

        // iterator
        while (true) {
            // 모든 Iterator 객체는 결과 객체를 반환하는 next() 메서드
            let res = await resultsIterator.next();            

            // 값이 있으면
            if (res.value && res.value.value.toString()) {

                let jsonRes = {};
                jsonRes.Key = res.value.key;
                jsonRes.Record = JSON.parse(res.value.value.toString('utf8'));

                allResults.push(jsonRes);
            }

            // 반복이 끝나면 iterator.done 이 true 를 반환
            if (res.done) {
                // 사용 후 닫기
                await resultsIterator.close();
                break;
            }

        }
        return Buffer.from(JSON.stringify(allResults));

    }

 

체인코드 적용

 

docker container exec -it cli bash

 

chaincode install

 

peer chaincode install -n excc -v 2.0 -p /opt/gopath/src/github.com/chaincode/example/ -l node

 

chaincode upgrade

 

peer chaincode upgrade -o orderer.example.com:7050 --tls true --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n excc -v 2.0 -c '{"Args":[""]}' -P 'OR ('\''Org1MSP.member'\'','\''Org2MSP.member'\'')'

 

query

 

["{\"selector\":{\"docType\":\"registerAsset\"}}"]

 

https://docs.couchdb.org/en/2.2.0/api/database/find.html

 

10.3.6. /db/_find — Apache CouchDB 2.2 Documentation

POST /{db}/_find Find documents using a declarative JSON querying syntax. Queries can use the built-in _all_docs index or custom indexes, specified using the _index endpoint. Parameters: Request Headers:   Request JSON Object:   selector (json) – JSON obje

docs.couchdb.org

실습:: 소유하고 있는 자산 목록 조회하는 함수 실습

• 함수이름 : queryAllAsset

• 입력 파라미터 정의

필드 설명
owner 소유자

• 사용메소드 :

stub.putState(key, value)

stub.getState(key)

stub.getQueryResult(queryString)

• 응답 코드

에러 코드 메시지
14010 Key value doesn't exist
14011 Incorrect number of arguments
14013 There is no Asset Information
응답 코드 메시지
14001 Success Transaction

• 예시

 

["user001"]

 

Result:

 

{
"userId":"user001",
"name":"sophia",
"address":"example@example.com",
"contact":"01099996666",
"asset":["a1","a3","a4","a5"]
}

Example code

실습:: 자산 정보등록 함수

 

    /**
     * 
     * 자산 등록
     * 
     * 자산코드     assetCd
     * 자산내용     content
     * 등록일       regYmd
     * 소유자       owner
     * 
     */

    async registerAsset(stub, args) {
        console.info('============= END : registerAsset() ===========');
        
        // key 정의
        let key = args[0];

        // key 값이 없으면 에러 처리
        if(!key) {
            var err = {};
            err.status = 14010;
            err.message = 'Key value doesn\'t exist';
            throw new Error("#" + JSON.stringify(err));
        }

        // 매개변수 개수 체크
        if (args.length != 4) {
            var err = {};
            err.status = 14011;
            err.message = 'Incorrect number of arguments';
            throw new Error("#" + JSON.stringify(err));
        }
        
        // data 정의
        var asset = {
            docType: 'registerAsset',
            assetCd: args[0],           // 자산코드
            content: args[1],           // 자산내용
            regYmd: args[2],            // 등록일
            owner: args[3],             // 소유자
        };

        try{
            // 원장에 데이터 저장
            await stub.putState(key, Buffer.from(JSON.stringify(asset)));

            // 성공 응답 메시지 정의
            var response = {};
            response.status = 14001;
            response.message = 'Success Message';

            console.info('============= END : registerAsset() ===========');
            
            // payload 리턴 
            return Buffer.from(JSON.stringify(response));

        }catch(e){
            console.log(e);
            throw new Error(e);

        }        
    }

 

실습:: 자산 이동 함수

 

    /**
     * 자산 이동
     * 
     * 자산코드    assetCd
     * 소유주      owner
     * 
     */

    async moveAsset(stub, args) {
        console.info('============= END : moveAsset() ===========');
        
        // key 정의
        let key = args[0];

        // key 값이 없으면 에러 처리
        if(!key) {
            var err = {};
            err.status = 14010;
            err.message = 'Key value doesn\'t exist';
            throw new Error("#" + JSON.stringify(err));
        }

        // 매개변수 개수 체크
        if (args.length != 2) {
            var err = {};
            err.status = 14010;
            err.message = 'Incorrect number of arguments';
            throw new Error("#" + JSON.stringify(err));
        }

        // 변경할 데이터 가져오기
        let assetBytes = await stub.getState(key);

        // 가져온 데이터 값이 없을 경우 에러정의
        if (!assetBytes.toString()) {
            var err = {};
            err.status = 14013;
            err.message = 'There is no Asset Information';
            throw new Error("#" + JSON.stringify(err));
        }

        // JSON 형식으로 변환
        var assetInfo = JSON.parse(assetBytes);

        // owner 필드 값 변경
        assetInfo.owner = args[1];


        try{

            // 원장에 데이터 저장
            await stub.putState(key, Buffer.from(JSON.stringify(assetInfo)));

            // 성공 응답 메시지 정의
            var response = {};
            response.status = 14001;
            response.message = 'Success Message';

            console.info('============= END : moveAsset() ===========');

            // payload 리턴 
            return Buffer.from(JSON.stringify(response));

        }catch(e){
            console.log(e);
            throw new Error(e);

        }        
    }

 

실습:: 소유하고 있는 자산 목록 조회하는 함수 실습 

 

    async queryAllAsset(stub, args) {

        // key 정의
        let key = args[0];

        // 리턴 값 초기화
        let allResults = [];

        // queryString 값이 없으면 에러 처리
        if (!key) {
            var err = {};
            err.status = 14010;
            err.message = 'Key value doesn\'t exist';
            throw new Error("#" + JSON.stringify(err));
        }

        // 매개변수 개수 체크
        if (args.length < 1) {
            var err = {};
            err.status = 14011;
            err.message = 'Incorrect number of arguments.';
            throw new Error("#" + JSON.stringify(err));        
        }

                // 변경할 데이터 가져오기
        let userBytes = await stub.getState(key);

        // 가져온 데이터 값이 없을 경우 에러정의
        if (!userBytes.toString()) {
            var err = {};
            err.status = 14013;
            err.message = 'There is no Asset Information';
            throw new Error("#" + JSON.stringify(err));
        }

        // JSON 형식으로 변환
        var userInfo = JSON.parse(userBytes);

        var queryString = "{\"selector\":{\"docType\":\"registerAsset\",\"owner\":\""+key+"\"}}";

        // state database 에서 쿼리 수행
        let resultsIterator = await stub.getQueryResult(queryString);

        // iterator
        while (true) {
            // 모든 Iterator 객체는 결과 객체를 반환하는 next() 메서드
            let res = await resultsIterator.next();            

            // 값이 있으면
            if (res.value && res.value.value.toString()) {

                // let jsonRes = {};
                // jsonRes.Key = res.value.key;
                // jsonRes.Record = JSON.parse(res.value.value.toString('utf8'));

                allResults.push(res.value.key);
            }

            // 반복이 끝나면 iterator.done 이 true 를 반환
            if (res.done) {
                // 사용 후 닫기
                await resultsIterator.close();
                break;
            }

        }
        userInfo.asset = allResults;
        
        return Buffer.from(JSON.stringify(userInfo));

    }