이 튜토리얼은 기존 패브릭 네트워크에서 캘리퍼를 사용해 스마트 컨트랙트를 테스트하는 방법을 가르쳐준다.
이 튜토리얼을 마치기 위해 NodeJS가 반드시 설치되어있어야 한다. nvm 사용을 권장한다.
이 튜토리얼은 공식 하이퍼레저 패브릭 문서에서 사용가능한 리소스에 기초한다. 두 조직과 단일 오더러, 자바스크립트 asset-transfer-basic
로 네트워크가 구성되어 있고, 이미 빌드가 끝났으며 수행 테스트 준비가 되어있음을 가정한다.
아래 커맨드 리스트는 최소한의 입력을 통해 패브릭 네트워크 생성 및 구동을 빠르게 진행하는 방식이다. 명시적 수준에서 사용가능한 하이퍼레저 패브릭 리소스를 사용한다. 테스트 네트워크 생성 동안 일어나는 일들을 이해하고 문제를 해결하기 위해서는 패브릭 문서를 참고해라!
# Hyperledger Fabric samples 에서 고정된 버전을 가져온다
git clone https://github.com/hyperledger/fabric-samples.git
cd fabric-samples
git checkout 22393b629bcac7f7807cc6998aa44e06ecc77426
# 패브릭 툴 설치 및 패스에 저장
curl -sSL https://bit.ly/2ysbOFE | bash -s -- 2.2.0 1.4.8 -s
export PATH=$PATH:$(pwd)/bin
# 네트워크 생성 및 초기화
cd test-network
./network.sh up createChannel
./network.sh deployCC -ccn basic -ccl javascript
fabric-samples
디렉토리와 같은 레벨에 caliper-workspace
폴더를 만든다. 그리고 caliper-workspace
폴더 안에서 networks
, benchmarks
, workload
폴더를 만든다.
캘리퍼 설치와 사용은 로컬 npm 설치를 따른다. caliper-workspace
디렉토리에서 아래 터미널 커맨드를 사용해 CLI를 설치한다 :
npm install --only=prod @hyperledger/caliper-cli@0.4.2
SDK를 바인딩한다 :
npx caliper bind --caliper-bind-sut fabric:2.2
캘리퍼 설치 및 바인딩에 대한 더 자세한 정보는 관련 문서 페이지에서 볼 수 있다.
캘리퍼는 두 구성 파일을 필요로 한다 :
이제 이 폴더들을 캘리퍼가 요구하는 자산들로 채울 것이다.
네트워크 구성 파일은 캘리퍼 워커가 하이퍼레저 캘리퍼 네트워크에 트랜잭션을 제출하고 평가하기 위해 필요한 파일이다.
YAML 또는 JSON 포맷일 수 있다. 이 예제에서는 YAML 포맷을 사용한다.
network 폴더 아래에 networkConfig.yaml
템플릿 파일을 만든다. 아래 내용을 포함해야 한다:
name: Caliper test
version: "2.0.0"
caliper:
blockchain: fabric
channels:
organizations:
Caliper test
다.2.0.0
은 새 패브릭 커넥터가 연결됨을 의미한다.fabric
으로 지정한다.Common Connection Profiles(CCP)는 모든 하이퍼레저 패브릭 SDK가 하이퍼레저 패브릭 네트워크에 연결하기 위해 사용할 수 있는 파일 포맷이다. 캘리퍼가 네트워크 연결을 위해 node SDK를 활용하기 때문에, 캘리퍼는 이러한 연결 프로필을 사용한다. 하이퍼레저 패브릭 네트워크를 빌드하는 모든 사람은 이 파일들을 만들어야 한다.
Common Connection Profile은 조직 특정적이다. 즉, 각 조직이 고유한 파일을 가진다. 네트워크 공급자는 각 조직을 위해 파일을 제공해야 한다.
하이퍼레저 패브릭 문서에서 이 프로필들은 static
또는 dynamic
이라는 두 가지 형태로 제공될 수 있다. 간단히 요약해서 static
커넥션 프로필은 패브릭 네트워크에 대한 모든 정보를 미리 가진다. 이것은 존재하는 거의 모든 다른 것들, 모든 피어들, 오더러들, 그리고 채널을 포함한다. dynamic
커넥션 프로필은 최소한의 정보를 갖고 있다. 대개 조직 피어를 하나 또는 둘 갖고 잇는데, 이는 SDK가 패브릭 네트워크와 상호작용하기위해 요구되는 정보를 결정할 목적으로 검색을 사용하기 위해 사용되는 정보이다.
패브릭 샘플의 test-network
는 각 조직에 대한 일반 커넥션 프로필을 제공하고 있고, 이들은 동적 커넥션 프로필이다.
테스트 네트워크 튜토리얼을 따라가면 각 조직의 아이덴티티 집합과 함께 일반 커넥션 프로필이 생성된다.
이 예시에서는 MSP ID가 Org1MSP
인 Org1을 커넥션을 위해 사용한다. 즉 테스트 네트워크의 일부인 Org2에 대한 디테일을 준비할 필요가 없다. 단일 조직만을 준비하는 것은 매우 일반적인 패턴이다.
Org1MSP
MSP ID를 가진 조직에 대한 정보를 추가할 필요가 있다. 이름, 조직과 연관된 커넥션 프로필, 하나 이상의 아이덴티티가 필요하다.
커넥션 프로필은 fabric-samples > test-network > organizations > peerOrganizations > org1.example.com
에서 찾을 수 있다. JSON과 YAML버전이 모두 있으며 여기서는 connection-org1.yaml
을 사용한다. 이 커넥션 프로필은 하이퍼레저 패브릭이 동적으로 참조하는 것으로, 검색과 함께 사용될 것으로 예상된다. 따라서 이 커넥션 파일이 검색 사용을 필요로 함을 선언해야 한다.
준비할 아이덴티티는 User1@org1.example.com
다.
개인 키는 fabric-samples > test-network > organizations > peerOrganizations > org1.example.com > users > User1 > msp > keystore > priv_sk
에서 찾을 수 있다.
공개 인증서는 fabric-samples > test-network > organizations > peerOrganizations > org1.example.com > users > User1 > msp > signedcerts > User1@org1.example.com-cert.pem
에서 찾을 수 있다.
해당 아이덴티티는 조직 내에서 유니크한 이름을 가져야 한다. test-network
가 사용한 이름(User1@org1.example.com
)과 일치할 필요는 없다. 그러므로 이름을 간단하게 User1
로 붙인다. 튜토리얼 목적에 따라 단순히 인증서 및 개인키를 가리키지만 네트워크 구성 파일에 정보를 직접 추가할 수도 있다.
아래는 설명한 디테일을 제공하기 위해 필요한 조직 세션이다.
organizations:
- mspid: Org1MSP
identities:
certificates:
- name: 'User1'
clientPrivateKey:
path: '../fabric-samples/test-network/organizations/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/keystore/priv_sk'
clientSignedCert:
path: '../fabric-samples/test-network/organizations/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/signcerts/User1@org1.example.com-cert.pem'
connectionProfile:
path: '../fabric-samples/test-network/organizations/peerOrganizations/org1.example.com/connection-org1.yaml'
discover: true
위 예시에서 mspid
와 name
앞에 있는 -
사인에 주목하자. organizations
섹션이 하나 이상의 조직을 포함할 수 있기 때문에 중요하다. certificates
또한 하나 이상의 아이덴티티를 포함할 수 있다.
또 하나 중요한 포인트는 파일에 정의된 첫 조직은 디폴트 조직이 된다는 것이다. 호출 조직을 지정하지 않은 워크로드 모듈에서는 디폴트 조직이 사용된다. 오직 한 조직만이 정의되었기 때문에 워크로드 구현에서 호출 조직에 대한 참조를 볼 수 없다.
캘리퍼를 위한 패브릭 커넥터는 패브릭 네트워크 커넥션을 생성할 때 도움이 필요하다. 상호작용할 수 있는 스마트 컨트랙트 목록인 channels
목록이 반드시 제공되어야 한다.
test-network
튜토리얼에서 mychannel
이 생성되고 basic
스마트 컨트랙트(체인코드가) 해당 채널에서 인스턴스화된다. 이것을 아래와 같이 선언할 수 있다 :
channels:
- channelName: mychannel
contracts:
- id: basic
channelName
과 id
앞에 있는 -
에 주의하자. 하나 이상의 채널이 존재할 수 있어서 channels
이 채널 목록을 정의할 수 있고, contracts
는 하나 이상의 컨트랙트 ID를 가질 수 있다.
캘리퍼 네트워크 구성 파일은 완전히 채워졌다. 인증서, 개인키, 커넥션 프로필의 경로가 올바른지 확인하자.
name: Calier test
version: "2.0.0"
caliper:
blockchain: fabric
channels:
- channelName: mychannel
contracts:
- id: basic
organizations:
- mspid: Org1MSP
identities:
certificates:
- name: 'User1'
clientPrivateKey:
path: '../fabric-samples/test-network/organizations/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/keystore/priv_sk'
clientSignedCert:
path: '../fabric-samples/test-network/organizations/peerOrganizations/org1.example.com/users/User1@org1.example.com/msp/signcerts/User1@org1.example.com-cert.pem'
connectionProfile:
path: '../fabric-samples/test-network/organizations/peerOrganizations/org1.example.com/connection-org1.yaml'
discover: true
벤치마크 라운드 동안 워크로드 모듈이 배포된 스마트 컨트랙트와 상호작용한다. 워크로드 모듈은 caliper-core
의 WorkloadModuleBase
캘리퍼 클래스를 상속한다. 워크로드 모듈은 세가지 오버라이드를 제공한다 :
더 많은 정보는 왼쪽 메뉴에서 접근가능한 Workload Configuration
문서를 확인해라.
여기서 만들 워크로드는 world state DB에 존재하는 자산에 대한 쿼리를 벤치마킹하기 위한 것이다. 결과적으로 워크로드 모듈에서 사용가능한 세가지 단계를 모두 사용할 것이다 :
submitTransaction
단계에서 쿼리될 자산을 생성한다initializeWorkloadModule
단계에서 생성된 자산에 쿼리한다.initializeWorkloadModule
단계에서 만든 자산을 삭제해 벤치마크가 반복될 수 있게 한다. workload
폴더에서 readAsset.js
파일을 생성한다:
'use strict';
const { WorkloadModuleBase } = require('@hyperledger/caliper-core');
class MyWorkload extends WorkloadModuleBase {
constructor() {
super();
}
async initializeWorkloadModule(workerIndex, totalWorkers, roundIndex, roundArguments, sutAdapter, sutContext) {
await super.initializeWorkloadModule(workerIndex, totalWorkers, roundIndex, roundArguments, sutAdapter, sutContext);
}
async submitTransaction() {
// NOOP
}
async cleanupWorkloadModule() {
// NOOP
}
}
function createWorkloadModule() {
return new MyWorkload();
}
module.exports.createWorkloadModule = createWorkloadModule;
파일을 채울 때, 배포된 assetTransfer.js
스마트 컨트랙트 파일에서 사용가능한 메서드를 참조한다. assetTransfer.js
파일은 fabric-samples > asset-transfer-basic > chaincode-javascript > lib > assetTransfer.js 에서 찾을 수 있다.
이 메서드는 벤치마킹이 완료되었을 때 기본 submitTransaction
메서드에 필요한 아이템을 준비하기 위해 사용된다.
만들어질 자산 수는 roundArguments.assets
로 주어진다. 스마트 컨트랙트를 사용해 자산을 생성한다. 트랜잭션 바디를 정의하는 인수 객체(arguments object)를 채우고, 아래 정보를 요구하는 Caliper API sendRequests
를 이용함으로써 스마트 컨트랙트를 사용한다.
sendRequests
가 요구하는 정보 :
메서드는 아래와 같이 구성되어야 한다 :
async initializeWorkloadModule(workerIndex, totalWorkers, roundIndex, roundArguments, sutAdapter, sutContext) {
await super.initializeWorkloadModule(workerIndex, totalWorkers, roundIndex, roundArguments, sutAdapter, sutContext);
for (let i=0; i<this.roundArguments.assets; i++) {
const assetID = `${this.workerIndex}_${i}`;
console.log(`Worker ${this.workerIndex}: Creating asset ${assetID}`);
const request = {
contractId: this.roundArguments.contractId,
contractFunction: 'CreateAsset',
invokerIdentity: 'User1',
contractArguments: [assetID,'blue','20','penguin','500'],
readOnly: false
};
await this.sutAdapter.sendRequests(request);
}
}
위 예시에서 같은 파라미터('blue','20','penguin','500')를 가진 서로 다른 자산이 생성된다. 위 내용과 스마트 컨트랙트 메서드를 비교하면 컨트랙트 인수들(arguments)과 메서드 파라미터 사이에 1:1 매핑이 있다.
이 메서드는 벤치마크 테스트 단계에서 반복적으로 실행된다. ReadAsset
스마트 컨트랙트를 initializeWorkloadModule
메서드에서 생성되는 자산을 쿼리함으로써 평가할 것이다.
처음으로, 쿼리를 위한 자산 문자열 아이덴티티를 생성한다. 이것은 워커 인덱스와 0에서 생성된 자산의 수 사이의 임의 정수로 형성된다.
이제 sendRequests
call이 객체를 전달하기를 기다린다(await
한다). 이 객체에는 round arguments
에서 전달된 값으로 설정된 contractId
, ReadAsset
으로 설정된 contractFunction
, User1
으로 설정된 invokerIdentity
, 이 시행에서 쿼리하기 위한 자원 배열로 설정된 chaincodeArguments
가 포함된다.
async submitTransaction() {
const randomId = Math.floor(Math.random()*this.roundArguments.assets);
const myArgs = {
contractId: this.roundArguments.contractId,
contractFunction: 'ReadAsset',
invokerIdentity: 'User1',
contractArguments: [`${this.workerIndex}_${randomId}`],
readOnly: true
};
await this.sutAdapter.sendRequests(myArgs);
}
이 함수는 스마트 컨트랙트 함수 DeleteAsset
사용을 통해 initializeWorkloadModule
함수에서 생성된 자산을 삭제하므로 테스트 이후 "청소"에 쓰인다.
initializeWorkloadModule
내부와 비슷하게 구현된다. initializeWorkloadModule
와 cleanupWorkloadModule
를 생성/삭제를 수행하는 common method 활용을 위해 리팩토링 할 수도 있다.
async cleanupWorkloadModule() {
for (let i=0; i<this.roundArguments.assets; i++) {
const assetID = `${this.workerIndex}_${i}`;
console.log(`Worker ${this.workerIndex}: Deleting asset ${assetID}`);
const request = {
contractId: this.roundArguments.contractId,
contractFunction: 'DeleteAsset',
invokerIdentity: 'User1',
contractArguments: [assetID],
readOnly: false
};
await this.sutAdapter.sendRequests(request);
}
}
테스트 콜백 파일이 채워져야 한다 :
'use strict';
const { WorkloadModuleBase } = require('@hyperledger/caliper-core');
class MyWorkload extends WorkloadModuleBase {
constructor() {
super();
}
async initializeWorkloadModule(workerIndex, totalWorkers, roundIndex, roundArguments, sutAdapter, sutContext) {
await super.initializeWorkloadModule(workerIndex, totalWorkers, roundIndex, roundArguments, sutAdapter, sutContext);
for (let i=0; i<this.roundArguments.assets; i++) {
const assetID = `${this.workerIndex}_${i}`;
console.log(`Worker ${this.workerIndex}: Creating asset ${assetID}`);
const request = {
contractId: this.roundArguments.contractId,
contractFunction: 'CreateAsset',
invokerIdentity: 'User1',
contractArguments: [assetID,'blue','20','penguin','500'],
readOnly: false
};
await this.sutAdapter.sendRequests(request);
}
}
async submitTransaction() {
const randomId = Math.floor(Math.random()*this.roundArguments.assets);
const myArgs = {
contractId: this.roundArguments.contractId,
contractFunction: 'ReadAsset',
invokerIdentity: 'User1',
contractArguments: [`${this.workerIndex}_${randomId}`],
readOnly: true
};
await this.sutAdapter.sendRequests(myArgs);
}
async cleanupWorkloadModule() {
for (let i=0; i<this.roundArguments.assets; i++) {
const assetID = `${this.workerIndex}_${i}`;
console.log(`Worker ${this.workerIndex}: Deleting asset ${assetID}`);
const request = {
contractId: this.roundArguments.contractId,
contractFunction: 'DeleteAsset',
invokerIdentity: 'User1',
contractArguments: [assetID],
readOnly: false
};
await this.sutAdapter.sendRequests(request);
}
}
}
function createWorkloadModule() {
return new MyWorkload();
}
module.exports.createWorkloadModule = createWorkloadModule;
벤치마크 구성 파일은 벤치마크 라운드를 정의하고 정의된 워크로드 모듈(들)을 참조한다. 이것은 로드 생성시 사용할 워커의 수, 테스트 라운드 수, 각 라운드의 지속시간, 각 라운드 동안 트랜잭션 로드에 적용되는 비율 제어(rate control), 모니터와 관련된 옵션을 지정한다. 이 튜토리얼은 모니터나 트랜잭션 옵저버를 사용하지 않는다. 이것에 대한 디테일은 문서를 참고하자.
벤치마크 구성 파일은 YAML 또는 JSON 포맷으로 제공된다. 여기서 쓰이는 것은 YAML 포맷이다. YAML 파일은 대소문자를 구별하고, 모든 라벨은 소문자라는 점에 주의하자.
벤치마크 구성 파일은 단일 필수 절을 가진다 :
test:
benchmarks
폴더에서 아래 내용을 포함한 myAssetBenchmark.yaml
파일을 만든다 :
test:
name: basic-contract-benchmark
description: A test benchmark
workers:
rounds:
이제 워커 수와 생성한 워커로드 모듈을 사용하는 테스트 라운드를 지정해 파일을 채우자.
두 개별 워커를 사용할 것이다. 이것은 워커 지정을 통해 달성된다:
type: local
number: 2
각 round
블록은 아래를 포함한다:
label
: 라운드에 쓰이는 유니크한 헤더 라벨description
: 실행되는 라운드 설명txDuration
: 테스트 지속기간 지정(초)rateControl
: rate control 타입과 옵션workload
: 사용할 워크로드 모듈과 모듈에 전달할 인수들. 전달된 모든 인수는 워크로드 모듈 안에서 roundArguments
로 이용가능하다.readAsset
라벨이 붙은 벤치마크 라운드를 지정할 것이다. 설명은 Read asset benchmark
이고 30s동안 실행되며 fixed-load
rate controller를 사용한다. 이 컨트롤러는 트랜잭션 부하를 2로 유지한다. 추가적으로 readAsset.js
워크로드 파일로 지정한 워크로드를 제공한다. {assets: 10, contractId: asset-transfer-basic}
를 인수로 전달해줄 것이다.
이것은 아래 라운드 지정으로 달성된다 :
- label: readAsset
description: Read asset benchmark
txDuration: 30
rateControl:
type: fixed-load
opts:
transactionLoad: 2
workload:
module: workload/readAsset.js
arguments:
assets: 10
contractId: basic
벤치마크 구성파일이 채워져야 한다 :
test:
name: basic-contract-benchmark
description: test benchmark
workers:
type: local
number: 2
rounds:
- label: readAsset
description: Read asset benchmark
txDuration: 30
rateControl:
type: fixed-load
opts:
transactionLoad: 2
workload:
module: workload/readAsset.js
arguments:
assets: 10
contractId: basic
위 구성 파일과 테스트 모듈을 사용해 성능 벤치마크를 실행할 준비가 되었다. 실행 벤치마크는 캘리퍼 CLI를 사용해 실행된다. 워크스페이스 경로 및 워크스페이스를 기준으로 한 네트워크 구성 파일과 벤치마크 구성 파일 상대경로가 필요하다. 이 정보는 각각 --caliper-workspace
, --caliper-networkconfig
, --caliper-benchconfig
플래그로 제공할 수 있다.
스마트 컨트랙트가 이미 설치되고 인스턴스화되었기 때문에 캘리퍼는 테스트 단계를 수행하기만 하면 된다. 이것은 --caliper-flow-only-test
플래그를 통해 지정된다.
2.2 SUT를 사용했으므로 gateway capability 사용을 지정해주어야 한다. 2.x 커넥터에는 게이트웨이를 사용하지 않는 버전이 없기 때문이다. --caliper-fabric-gateway-enabled
를 지정한다.
caliper-workspace
디렉토리에 있는지 확인한다.
터미널에서 아래 커맨드를 실행시킨다:
npx caliper launch manager --caliper-workspace ./ --caliper-networkconfig networks/networkConfig.yaml --caliper-benchconfig benchmarks/myAssetBenchmark.yaml --caliper-flow-only-test --caliper-fabric-gateway-enabled
결과는 각 벤치마크 라운드에 대해 아래 아이템들을 상새히 보고한다 :
성공적으로 스마트 컨트랙트 벤치마킹을 마쳤다. 다양한 벤치마크 파라미터로 테스트를 반복하거나 리소스 모니터를 추가할 수 있다. 옵션 전체 설정은 캘리퍼 문서를 참고하자.
The Caliper codebase is released under the Apache 2.0 license. Any documentation developed by the Caliper Project is licensed under the Creative Commons Attribution 4.0 International License. You may obtain a copy of the license, titled CC-BY-4.0, at http://creativecommons.org/licenses/by/4.0/.