[Hyperledger] Composer Transaction Processor 함수(1)

Hyperledger Composer Transaction Processor Function(1)

어떻게 하다 보니 여기까지 오긴 왔다. 이 링크 를 참고하여 트랜잭션을 관리(?)하는 함수를 작성해 보도록 하자! 예! 앞의 두 포스트는 이 포스트를 적기 위한 과정이었을 뿐. ㅠㅠ 험난했다.

Hyperledger Composer 비즈니스 네트워크는 모델 파일과 스크립트가 set로 구성된다. 스크립트에는 모델 파일에 정의 된 트랜잭션을 구현하는 트랜잭션 프로세서 기능을 포함할 수 있다.

Transaction Processor 함수는 BusinessNetworkConnection API를 사용하여 트랜잭션을 날릴 때 런타임에 의해 자동으로 호출된다.

문서 내의 decorator는 런타임 처리에 필요한 메타 데이터를 주석으로 단다.

각 트랜잭션에는 트랜잭션을 저장하는 레지스트리가 있다.


트랜잭션 프로세서의 구조

트랜잭션 프로세서 함수에는 Decorator, Metadata, JS 함수가 포함된다. 이 함수가 동작하기 위해서는 @param@transaction 두 부분이 모두 필요하다.

주석의 첫 번째 줄에는 트랜잭션 처리 함수가 수행하는 기능에 대한 설명이 들어 있다. 두 번째 줄에는 @param매개 변수 정의를 나타내는 태그가 있어야 한다 . @param태그 다음에는 트랜잭션 프로세서 기능을 트리거하는 트랜잭션의 리소스 이름이 온다. 이 태그는 비즈니스 네트워크의 네임 스페이스 형식과 트랜잭션 이름 뒤에 위치한다. 리소스 이름 뒤에, 리소스를 참조 할 매개 변수 이름이 있으면 이 매개 변수를 JavaScript 함수에 인수로 제공해야 한다. 세 번째 줄에는 @transaction태그가 있어야 한다. 이 태그는 코드를 트랜잭션 프로세서 기능으로 식별하므로 꼭 필요하다.

1
2
3
4
5
/**
* A transaction processor function description
* @param {org.example.basic.SampleTransaction} parameter-name A human description of the parameter
* @transaction
*/

주석은 트랜잭션에 권한을 부여하는 JavaScript 함수이다. 이 함수는 임의의 이름을 가질 수 있지만 주석에 정의 된 매개 변수 이름(parameter-name)을 인수로 포함해야합니다.

1
2
3
function transactionProcessor(parameter-name) {
// 이곳에 함수 처리 과정을 담는다
}

두 형식을 모두 적용한 코드는 다음과 같다.

1
2
3
4
5
6
7
8
/**
* A transaction processor function description
* @param {org.example.basic.SampleTransaction} parameter-name A human description of the parameter
* @transaction
*/
function transactionProcessor(parameter-name) {
//Do some things.
}

함수 작성하기

트랜잭션 프로세서 함수는 모델 파일에 정의 된 트랜잭션의 논리적 연산이다. 예를 들어, Trade 트랜잭션의 트랜잭션 프로세서는 owner 의 값을 한 참여자에서 다른 참여자로 변경할 수 있다.(owner가 a였는데 b가 되는 등의 변경)

다음 basic-sample-network 예제의 SampleAssetString으로 정의된 value 속성이 있다. SampleTransaction 에 변경될 값인 newValuevalue 속성으로 전달해야 한다.

1
2
3
4
5
6
7
8
9
10
asset SampleAsset identified by assetId {
o String assetId
--> SampleParticipant owner
o String value
}

transaction SampleTransaction {
--> SampleAsset asset
o String newValue
}

SampleTransaction은 다음의 코드처럼 asset 및 asset이 저장된 레지스트리 모두를 변경한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/**
* Sample transaction processor function.
* @param {org.example.basic.SampleTransaction} tx The sample transaction instance.
* @transaction
*/
async function sampleTransaction(tx) {

// asset의 이전 값을 저장한다.
let oldValue = tx.asset.value;

// asset의 값을 새 값으로 저장한다.
tx.asset.value = tx.newValue;

// registry에 있는 값을 불러온다.
let assetRegistry = getAssetRegistry('org.example.basic.SampleAsset');

// registry에 있는 값을 업데이트한다.
await assetRegistry.update(tx.asset);

// 수정된 asset을 이벤트로 전달한다.
let event = getFactory().newEvent('org.example.basic', 'SampleEvent');
event.asset = tx.asset;
event.oldValue = oldValue;
event.newValue = tx.newValue;
emit(event);
}

에러 처리하기

트랜잭션은 딱 두 가지로 나뉜다. 성공해서 변경 사항이 적용되거나 실패하고 변경 사항이 적용되지 않거나(롤백).

1
2
3
4
5
6
7
8
9
10
11
/**
* Sample transaction processor function.
* @param {org.example.basic.SampleTransaction} tx The sample transaction instance.
* @transaction
*/
async function sampleTransaction(tx) {
// 함수 내용을 적어 주세요
throw new Error('example error');
// 여기에서 에러를 처리함.
// 함수 이전의 내용으로 롤백되거나, 변경 사항이 없으면 commit된다.
}

트랜잭션에서 모델의 관계 사용하기

다음의 코드와 같이, 모델의 관계가 연결(?)되는 경우가 있을 것이다. transaction -> asset -> participant 순서로 타고 들어가는 접근이 가능하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
namespace org.example.basic

participant SampleParticipant identified by participantId {
o String participantId
}

asset SampleAsset identified by assetId {
o String assetId
--> SampleParticipant owner
}

transaction SampleTransaction {
--> SampleAsset asset
}

만약, transaction에서 participant로 접근하고 싶다면 함수를 아래와 같이 작성하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
/**
* Sample transaction processor function.
* @param {org.example.basic.SampleTransaction} tx The sample transaction instance.
* @transaction
*/
async function sampleTransaction(tx) {
// asset을 tx(트랜잭션)의 asset으로 지정한다.
let asset = tx.asset;
// tx의 asset은 owner(SampleParticipant 형의 owner 변수)를 갖는다.
// 고로, 이 함수에서 정의한 owner의 값을 tx.asset.owner로 지정할 수 있다.
let owner = tx.asset.owner;
}

위의 경우, onwer 는 특정 참여자(tx.asset.owner)를 가리키게 된다.


비동기 코드 및 Promise 처리

transaction 함수는 commit 전 promise 가 해결될 때까지 기다린다. promise 가 반환될 때까지 트랜잭션은 완료되지 않는다.

아래의 모델 파일을 활용한 함수를 작성해 보자.

1
2
3
4
5
namespace org.example.basic

transaction SampleTransaction {

}

node 8부터는 async/awit 구문이 지원되기 때문에 promise 를 사용하는 것보다 간결히 코드를 작성할 수 있다. 권장하는 스타일은 다음과 같다.

1
2
3
4
5
6
7
8
9
/**
* Sample transaction processor function.
* @param {org.example.basic.SampleTransaction} tx The sample transaction instance.
* @transaction
*/
async function sampleTransaction(tx) {
let assetRegistry = await getAssetRegistry(...);
await assetRegistry.update(...);
}

위 코드를 사용하면 sampleTransaction()getAssetRegistry() 가 완료되기 전에 assetRegistry 에 값을 저장하지 않고, assetRegistryupdate() 가 완료되어야만 정상적으로 종료된다.

만약 개발자가 promise 구문을 그대로 사용하고 싶다면 다음와 같이 코드를 작성하면 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* Sample transaction processor function.
* @param {org.example.basic.SampleTransaction} tx The sample transaction instance.
* @transaction
*/
function sampleTransaction(tx) {
// Transaction processor functions can return promises; Composer will wait
// for the promise to be resolved before committing the transaction.
// Do something that returns a promise.
return Promise.resolve()
.then(function () {
// Do something else that returns a promise.
return Promise.resolve();
})
.then(function () {
// Do something else that returns a promise.
// This transaction is complete only when this
// promise is resolved.
return Promise.resolve();
});
}

API 사용하기

함수에서 Hyperledger Composer API 호출하기

함수에서 적절한 인수를 사용하여 Hyperledger Composer API를 간단하게 호출할 수 있다.

model file
1
2
3
4
5
6
7
8
9
10
11
namespace org.example.basic

asset SampleAsset identified by assetId {
o String assetId
o String value
}

transaction SampleTransaction {
--> SampleAsset asset
o String newValue
}

아래의 예제 getAssetRegistry 에서 트랜잭션은 트랜잭션이 완료되기 전에 해결되는 promise 를 반환한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* Sample transaction processor function.
* @param {org.example.basic.SampleTransaction} tx The sample transaction instance.
* @transaction
*/
async function sampleTransaction(tx) {
// Update the value in the asset.
let asset = tx.asset;
asset.value = tx.newValue;
// Get the asset registry that stores the assets. Note that
// getAssetRegistry() returns a promise, so we have to await for it.
let assetRegistry = await getAssetRegistry('org.example.basic.SampleAsset');

// Update the asset in the asset registry. Again, note
// that update() returns a promise, so so we have to return
// the promise so that Composer waits for it to be resolved.
await assetRegistry.update(asset);
}

함수에서 Hyperledger Fabric API 호출하기

Fabric API를 호출하려면 함수 getNativeAPI 를 먼저 호출해야 한다. 그 다음, Fabric API를 호출하여 사용할 수 있다. Fabric API를 사용하면 Composer API에서 사용할 수 없는 기능에 접근할 수 있다.

중요: getStateputStatedeleteStategetStateByPartialCompositeKeygetQueryResult 는 Hyperledger Composer ACL을 무시한다.

getHistoryForKey 가 호출되면, 지정된 Asset의 History를 반환(return)한다. 트랜잭션 프로세서 함수는 반환 된 데이터를 배열에 저장한다.

트랜잭션 프로세서 함수에서 호출할 수 있는 Hyperledger Fabric API는 이 링크에 자세히 적혀 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
async function simpleNativeHistoryTransaction (transaction) {
const id = transaction.assetId;
const nativeSupport = transaction.nativeSupport;

const nativeKey = getNativeAPI().createCompositeKey('Asset:systest.transactions.SimpleStringAsset', [id]);
const iterator = await getNativeAPI().getHistoryForKey(nativeKey);
let results = [];
let res = {done : false};
while (!res.done) {
res = await iterator.next();

if (res && res.value && res.value.value) {
let val = res.value.value.toString('utf8');
if (val.length > 0) {
results.push(JSON.parse(val));
}
}
if (res && res.done) {
try {
iterator.close();
}
catch (err) {
}
}
}
}

Share