체인코드 작성 환경구성
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
실습:: 소유하고 있는 자산 목록 조회하는 함수 실습
• 함수이름 : 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));
}