Coinstack Smart Contracts

1. 개요

스마트 컨트랙트라는 개념은 1990년대까지 거슬러 올라갑니다. 컴퓨터 과학자이자, 법학자이자 암호 전문가인 Nick Szabo는 당시 디지털 계약, 디지털 통화에 관한 연구 및 기고를 꾸준히 하고 있었는데, 그가 제시했던 Bit Gold 체계를 살펴 보면 상당 부분 비트코인과 유사하여 그의 탁월한 선견지명을 보여 줍니다. 이 때문에 사토시 나카모토의 정체가 그가 아닐까 하는 의심도 있었습니다.

Nick Szabo는 국제적 규모의 비지니스 환경에서 비용의 혁명이 지속적으로 발생했다고 이야기합니다. 처음에는 교통, 다음에는 제조, 그리고 최근에는 통신 비용이 극적으로 줄어들었음을 제시하며 다음 단계는 '관계' 비용의 차례라고 주장합니다. 지역별로 상이한 사법적 관할, 보안, 신용 문제를 해결하기 위해 대부분의 초국가적 기업들은 많은 비용을 들이고 있습니다. 이 비용을 재정의하면 '관계'를 설정하고, 유지하며, 그 영향력을 보장하는 데 드는 비용이라고 할 수 있습니다.
인터넷과 컴퓨터 네트워크가 지배하는 현재에도 우리는 이 관계를 관리하는 데 여전히 종이의 세상에 머물고 있습니다. 종이에 씌어진 계약서, 법 조항으로 우리의 관계를 설정하며, 이를 유지하기 위해 비싸면서도 비효율적인 법조계 시스템에 의존해야 하고, 약속의 이행을 위한 소송을 통해 공권력의 투입을 요청해야 할 때도 있습니다.

기업 환경에서의 이러한 관계를 표현하는 가장 전통적이고 형식적인 개념은 바로 '계약'입니다. Nick Szabo는 컴퓨터의 기능에 주목하여 이 계약을 알고리즘적으로 표현할 수 있으리라 보았습니다. 그렇게 된다면 서로 다른 언어의 미묘한 뉘앙스 차이 또는 조항 자체의 애매모호함이 야기하는 분쟁의 요소를 획기적으로 줄일 수 있고, 계약의 강제적 이행도 보장될 수 있을 것입니다. 하지만 이 아이디어가 실현 가능하려면 이를 기록하고 실행하고 공유할 신뢰할 수 있는 '분산 컴퓨팅 환경' 및 알고리즘으로 표현된 계약의 강제적 이행을 위해 실질적 가치를 내포하는 '디지털 자산'이 필요하다고 보았습니다.

비트코인은 블록체인을 통해 디지털 자산으로서의 가치를 증명해 보였으나, 제한된 표현력으로 인해 일반화된 분산 컴퓨팅에는 활용할 수 없었습니다. 이에 이더리움의 창시자 Vitalik Buterin은 이를 확장하여 디지털 자산이라는 속성에 추가로 스마트 컨트랙트라는 명확한 프로그램 코드로 표현되는 계약 수행 방안을 제안함으로써 일반화된 분산 어플리케이션 플랫폼으로써의 가능성을 보여주었습니다.

블로코에서도 자체적으로 비트코인 프로토콜을 확장하여 스마트 컨트랙트 플랫폼을 구축, 제공하고 있으며 이를 통해 다양한 비지니스 로직을 블록체인 상에서 구현할 수 있도록 지원하고 있습니다.

코인스택은 스마트 컨트랙트의 프로그래밍 언어로 스크립트 언어인 Lua를 사용하고 있습니다. 스마트 컨트랙트는 컨트랙트 별로 키-밸류 쌍의 상태 정보를 저장하고 있고 모든 노드는 블록체인을 통해 동일한 컨트랙트를 단계적으로 수행함으로써 항상 동일한 상태 정보를 유지 합니다. 스마트 컨트랙트는 정의, 수행, 조회의 세단계로 나뉩니다. 정의 단계에는 Lua언어로 스마트 컨트랙트 코드를 작성, 블록체인 상에 배포하는 단계이고, 수행 단계는 앞서 정의한 코드 내의 함수를 실제로 인자값을 넣어 호출하여 상태값을 변경하는 단계입니다. 마지막으로 조회 단계는 수행 결과가 반영된 현재 상태값을 읽어오는 단계입니다.

이 장에서는 각각의 단계에서 코인스택 SDK를 사용하는 구체적인 방법을 기술하고 분산화된 어플리케이션(Distributed Application, DApp)을 구축하는 방안을 제시합니다.

2. Programming Language

코인스택은 가벼운 스크립트 언어인 Lua를 스마트 컨트랙트 언어로 사용합니다. 블로코에서는 개발의 편의를 위해 코인스택 코드라는 Lua 개발 및 컨트랙트 배포 지원도구(IDE)를 제공합니다. 코인스택 코드에 대해서는 본 문서의 범위에서 벗어나므로 별도로 문의해 주시기 바랍니다.

다음은 블록체인 상태 저장소에 키-밸류 값을 저장하고 값을 읽어오는 Lua로 작성된 간단한 코인스택 스마트 컨트랙트의 예제입니다.

local system = require("system");

-- 상태 저장소에 키-밸류 저장
function set(key, value)
    system.setItem(key, value);
end

-- 상태 저장소에서 키에 해당하는 값 반환
function get(key)
    return system.getItem(key);
end

-- 현재 블록의 해시값을 출력. 출력 결과는 서버 로그에 찍힘
function printBlock()
    system.print(system.getBlockhash());
end

블록체인 함수 내부에서 사용자의 접근을 제어하거나 블록체인 상에 데이터를 읽고 쓰기 위해서는 블로코에서 제공하는 시스템 패키지를 사용해야 합니다. 위의 예제에서는 system 패키지의 setItem, getItem 함수를 통해서 코인스택 내부의 키-밸류 저장소에 접근하여 데이터를 영속적으로 저장하는 함수를 컨트랙트 사용자에게 제공합니다. 또한 간단한 디버그 메시지를 print 함수를 통해 노드의 로그파일에 출력 할 수 있도록 하였습니다.

시스템 패키지의 기능은 아래와 같이 한정적인데 이는 허용되지 않은 기능을 사용함으로써 시스템 상에 오류 혹은 노드간 데이터 불일치(e.g. 파일 삭제, 랜덤 사용)를 사전에 방지하기 위함입니다. 같은 이유로 외부 패키지의 사용을 제한하고 있습니다.

시스템 패키지는 컨트랙트 코드 내에서 다음과 같이 require 명령어를 통해 사용 가능합니다.

local system = require("system");

시스템 패키지에서 제공하는 기본 함수 목록과 그에 대한 설명은 다음과 같습니다.

함수명 인자 반환값 기능
print (string) message 노드상에서 디버그 메시지를 출력
setItem (string) key, (any) value 값을 블록체인에 저장. Query문에는 사용 불가
getItem (string) key (any) value 저장된 값을 반환
getSender (string) address 스마트 컨트랙트를 호출한 주소 반환
getCreator (string) address 스마트 컨트랙트를 정의한 주소 반환
getBlockhash (string) hash 호출 시점 스마트 컨트랙트가 포함된 블록의 해시 반환
getBlockheight (number) height 호출 시점 스마트 컨트랙트가 포함된 블록의 높이 반환
getTimestamp (number) timestamp UNIX 타임스탬프를 반환
getContractID (string) id 스마트 컨트랙트 ID를 반환
getTxhash (string) hash 스마트 컨트랙트가 포함된 트랜잰션의 해시 반환
getNode (string) id 코인스택 노드 식별자를 반환. 설정 파일에서 nodeid 값에 해당

3. Built-in Functions

Lua는 언어 자체적으로 유용한 함수 및 기본 패키지 제공합니다. 문자열 관리 함수 등 유용한 함수를 제공하므로 이것들을 활용하여 쉽게 스마트 컨트랙트를 작성 할 수 있습니다. 이에 대한 상세한 문법, 설명, 기본 내장 함수와 패키지는 Lua Reference Manual을 참조하시기 바랍니다.

Lua Smart Contract 는 코인스택에서 수행되기 때문에 Input/Output 을 포함한 OS 에 관련된 함수는 안정성 및 보안을 위해 제공되지 않습니다.

다음은 사용할 수 없는 기본 함수 목록입니다.

print, dofile, loadfile, module

다음의 사용할 수 없는 기본 패키지 목록입니다.

coroutine, io, os, debug

string, math, table 패키지는 사용이 가능합니다. 단, math 패키지의 random, randomseed 함수는 사용할 수 없습니다.

4. Smart Contract Definition

스마트 컨트랙트 코드를 사용하기 위해서는 코인스택 코드 IDE 혹은 직접 SDK를 사용하여 블록체인 상에 작성한 스마트 컨트랙트를 등록, 배포해야 합니다. 이러한 과정은 스마트 컨트랙트 정의 (smart contract definition)라고 불립니다.

다음은 코인스택 SDK를 사용하여 블록체인에 스마트 컨트랙트를 등록하는 예제입니다.

JAVA
CoinStackClient client = new CoinStackClient("CONNECTION_INFO");
String contractAddress = "ADDRESS_FOR_SMARTCONTRACT"; //컨트랙트로 사용할 주소
String privateKey = "MY_PRIVATEKEY"; //컨트랙트로 사용할 주소의 개인키
String definition = "SMARTCONTRACT_CODE"; //앞서 정의한 예제 코드로 대체

// SmartContract Builder Instance 생성
LuaContractBuilder builder = new LuaContractBuilder(); 
// 사용할 Contract 주소 설정. Transaction을 Sign할 개인키의 주소와 동일해야 함
builder.setContractId(contractAddress); 
// 정의에 사용할 Lua Contract 소스코드 설정
builder.setDefinition(definition);
// 트랜잭션 수수료 설정, 트랜잭션의 크기에 따라 증가시켜야 함
builder.setFee(10000);
// 트랜잭션 빌드 
String rawTx = builder.buildTransaction(client, ContractPk); 
// 트랜잭션 해시값 추출
String txHash = TransactionUtil.getTransactionHash(rawTx); 
// 트랜잭션 전송
client.sendTransaction(rawTx);

// 블록에 트랜잭션이 포함 될 때까지 대기
while(true) {
  Thread.sleep(1000);
  // 블록 포함 여부 검사. 블록에 포함이 되어야만 스마트컨트랙트 수행 가능
  if (client.getTransaction(txHash).getBlockHeights().length != 0)  
    System.out.println("Smart Contract Definition is Committed: "+ txHash);
}
Node.js
var CoinStack = require('coinstack-sdk-js');
var client = new CoinStack("", "", "127.0.0.1:8080", "http");
var contractAddress = "ADDRESS_FOR_SMARTCONTRACT"; //컨트랙트로 사용할 주소
var privateKey = "MY_PRIVATEKEY"; //컨트랙트로 사용할 주소의 개인키
var definitionCode = "SMARTCONTRACT_DEFINITION_CODE"; //앞서 정의한 예제 코드로 대체

var builder = client.createLuaContractBuilder();
builder.setInput(contractAddress);
builder.setContractID(contractAddress);
builder.setFee(fee);
builder.setDefinition(definitionCode);

builder.buildTransaction(function (err, tx) {
  if (err) {
    console.log("Failed to create a transaction: ", err);
    return;
  }
  try {
    tx.sign(privatekey);
    var rawTx = tx.serialize();
    var txHash = tx.getHash();
    client.sendTransaction(rawTx, function (err) {
      if (err) {
        console.log("Failed to send transaction: ", err);
        return;
      }
    });
    client.getTransaction(txHash, function(err, result) {
      console.log(result);
    });
  } catch (e) {
    console.log(e);
  }
});

코인스택 스마트 컨트랙트는 기존의 비트코인 표준 프로토콜을 확장한 자체적인 프로토콜을 가지므로 이를 따르는 트랜잭션 생성을 지원하기 위해 LuaContractBuilder라는 클래스를 제공합니다. LuaContractBuilder는 스마트 컨트랙트의 정의 및 수행시 사용하며 이를 위해 Transaction을 만들어 보내야 하므로 큰 틀에서 TransactionBuilder와 형태가 유사합니다.

먼저 생성자를 통해 인스턴스를 생성하고 난 뒤에 setContractId(String contractID)라는 함수로 스마트 컨트랙트로 사용할 주소를 지정합니다. 이 주소는 고유한 스마트 컨트랙트의 ID로 사용되어, 이후 다른 사용자들도 이 주소를 대상으로 스마트 컨트랙트 트랜잭션을 수행하거나 상태값을 조회해 볼 수 있습니다. 따라서 기존에 다른 응용에서 사용하던 주소 혹은 마이닝 보상을 받는데 사용하던 주소를 사용하기 보다는 새로운 주소를 만들고 컨트랙트 정의 트랜잭션을 수행할 정도로 소량의 잔고를 충전하여 사용하시기를 권고합니다.
타 사용자가 컨트랙트를 재정의 하는 것을 방지하기 위해 특정 주소로의 스마트 컨트랙트 정의(define)는 해당 주소의 개인키 소유자만 가능합니다.

컨트랙트 정의를 위한 트랜잭션을 생성할 때는 setDefinition(String contractCode)에 스마트 컨트랙트 코드를 문자열로 할당합니다. 그리고 최종적으로 트랜잭션을 빌드하고 보내게 되면, 해당 코드는 트랜잭션의 op_return nullData 영역에 스마트 컨트랙트 프로토콜에 맞추어 담겨 블록체인을 통해 모든 노드에 전파되게 됩니다.

만약에 코드의 크기가 커서 Serialize한 트랜잭션의 크기가 1kb를 넘는다면 수수료를 기본 트랜잭션 수수료인 10000 사토시에 더해 트랜잭션 사이즈에 따라 (기본값은 10000 사토시/1kb)더 넣어야 합니다.

스마트 컨트랙트 정의 트랜잭션은 트랜잭션의 수행순서를 보장하기 위해 맴풀이 아닌 블록에 담겨져 있는 상태여야만 작동합니다. 트랜잭션이 실제 블록에 포함되어있는지의 여부는 위의 예제와 같이 getTransaction(String transactionHash)로 트랜잭션을 읽어 온 후, int[] getBlockHeights() 함수를 호출하여 최소 한개 이상의 값이 존재하는지 확인하면 됩니다. getBlockHeights()의 결과값이 배열인 이유는 트랜잭션이 브랜치가 생겨 여러 블록에 포함되는 경우, 여러 높이가 존재할 수 있기 때문입니다.

5. Smart Contract Execution

정의 후 블록에 포함된 스마트 컨트랙트는 수행 코드를 통해서 호출하고 그 결과를 블록체인에 반영할 수 있습니다. 한번 블록체인에 반영된 수행 결과는 모든 노드가 동일하며 지속적으로 유지됩니다. 스마트 컨트랙트를 수행하기 위해서는 정의한 컨트랙트 내의 함수를 호출하는 수행 코드를 작성해야 합니다. 다음은 앞서 정의한 코드를 수행하여 사용자가 설정한 키에 특정 값을 저장하는 예제 코드입니다.

call("set", "Key1", 365);

스마트 컨트랙트 수행 코드를 작성한 뒤에는 코인스택 코드를 통해서 실행하거나, SDK를 통해서 직접 수행 트랜잭션을 생성, 전송하여 실행할 수 있습니다. 다음은 SDK를 이용해 스마트 컨트랙트를 수행하는 예제 코드입니다.

JAVA
CoinStackClient client = new CoinStackClient("CONNECTION_INFO");
String contractAddress = "ADDRESS_FOR_SMARTCONTRACT"; //수행할 대상 스마트 컨트랙트 주소
String privateKey = "MY_PRIVATEKEY"; //나의 개인키
String executionCode = "SMARTCONTRACT_EXECUTION_CODE"; //앞서 정의한 예제 코드로 대체

LuaContractBuilder builder = new LuaContractBuilder();
 // 이전에 정의한 Smart Contract Address 할당
builder.setContractId(contractAddress);
// 수행할 함수 및 인자의 코드를 빌더에 설정
builder.setExecution(executionCode);
builder.setFee(10000); 
String rawTx = builder.buildTransaction(client, privateKey);
client.sendTransaction(rawTx);
Node.js
var CoinStack = require('coinstack-sdk-js');
var client = new CoinStack("", "", "127.0.0.1:8080", "http");
var contractAddress = "SMARTCONTRACT_ADDRESS";
var executionCode = "SMARTCONTRACT_EXECUTION_CODE";

var builder = client.createLuaContractBuilder();
builder.setInput(contractAddress);
builder.setContractID(contractAddress);
builder.setFee(10000);
builder.setExecution(executionCode);
builder.buildTransaction(function (err, tx) {
  if (err) {
    console.log("Failed to create a transaction -", err);
    return;
  }

  try {
    tx.sign(privatekey);
    var rawTx = tx.serialize();
    var hash = tx.getHash();
    client.sendTransaction(rawTx, function (err) {
      if (err) {
        console.log("Failed to send transaction:", hash, "(", err, ")")
        return;
      }
      console.log("Sent transaction: ", hash);
    });
  } catch (e) {
    console.log(e);
  }
});

앞서 스마트 컨트랙트 정의와 유사하게 생성자를 통해 LuaContractBuilder 인스턴스를 생성하고 난 뒤에 setContractId(String contractID)라는 함수로 대상 스마트 컨트랙트 주소를 지정합니다. 여기에서는 앞서 스마트 컨트랙트를 정의하는데 사용한 주소를 동일하게 입력하여 앞서 정의한 컨트랙트를 수행하도록 합니다.

컨트랙트 수행을 위한 트랜잭션을 생성할 때는 setExecution(String contractCode) 함수에 수행할 스마트 컨트랙트 코드를 문자열로 할당합니다. (해당 코드는 트랜잭션의 op_return nullData 영역에 스마트 컨트랙트 프로토콜에 맞추어 적재됩니다.) 그리고 스마트 컨트랙트 주소에 대응하는 개인키가 아닌 스마트 컨트랙트를 수행할 각 사용자 개개인의 개인키로 트랜잭션을 빌드합니다. 최종적으로 트랜잭션을 전송하면 블록체인을 통해 모든 노드에 수행 코드가 전파됩니다. 그리고 사전에 정의된 스마트 컨트랙트에 따라 코드가 수행되어 블록체인 상태를 변화시키게 됩니다. 참고로 스마트 컨트랙트 별로 키-밸류 상태값은 구분되어 집니다. 즉 A 스마트 컨트랙트의 Key1을 변경해도 B 스마트 컨트랙트의 Key1에는 영향이 없습니다.

위의 예제에서는 1번째 인자에 'Key1'을 넣고 2번째 인자에 '365'를 넣어 기 정의한 set 함수를 호출하는 코드를 수행하였습니다. 그러면 set함수 내에서 system.setItem이 불리우고 contractAddress에 해당하는 상태 저장소의 Key1에 365라는 값이 저장되게 됩니다.

6. Smart Contract Query

스마트 컨트랙트의 수행은 트랜잭션 형태로 블록체인에 전달되어 블록에 포함된 후 수행 결과가 임의의 시간 후에 반영되므로, 클라이언트에 결과를 바로 반환할 수 없습니다. 따라서 상태값을 확인하기 위해서는 먼저 수행 트랜잭션이 블록에 포함되어 있는지의 여부를 확인하고, 이후 조회 기능을 사용하여 상태값을 읽어와야 합니다. 같은 이유로 컨트랙트 수행중에 에러가 나도 바로 확인이 불가능합니다. 따라서 코인스택 코드등을 통해 먼저 충분한 테스트를 거쳐 코드를 배포하기를 권하며, 필요시 print 기능을 사용해 서버에 디버그 메시지 남겨 버그를 확인하시기를 권합니다.

다음은 스마트 컨트랙트의 조회 관련 함수를 호출하여 상태값을 조회하는 Lua 코드의 예입니다.

res, ok = call("get", "Key1"); 
assert(ok); 

return res;

내부적으로 getItem 함수를 감싸고 있는 get 함수를 조회할 키값(Key1)을 인자로 호출해 해당 키에 저장된 값을 읽어옵니다.
위와 같은 코드를 작성한 뒤에는 코인스택 코드를 통해서 조회 해 보거나, SDK를 사용해 조회할 수 있습니다.

다음은 SDK를 활용한 상태값 조회 예제입니다.

JAVA
String lookupCode = "SMARTCONTRACT_LOOKUP_CODE";
ContractResult res = client.queryContract(ContractAddress, ContractBody.TYPE_LSC, lookupCode); 
if (res == null) {
  System.out.println("Result is null"); } 
else {
  System.out.println(res.asJson());
}
Node.js
var client = new CoinStack("", "", "127.0.0.1:8080", "http");

client.queryContract(contract, "LSC", query, function (err, res) {
  console.log(err);
  console.log(res);
});

이전의 컨트랙트 정의, 수행 시와 다른점은 조회는 트랜잭션을 생성해서 쓰기 요청을 보내지 않고 client를 통해 읽기 요청을 보내는 것이라는 점 입니다. 조회 시에는 내부적으로 함수에 setItem 함수를 호출해도 블록체인 저장소에 반영되지 않습니다.

Json으로 변환한 결과값은 다음과 같이 출력됩니다.

{"result":"365", "success":true}

결과는 json 포맷으로 반환되며, result를 dApp의 용도에 맞게 파싱하여 사용하면 됩니다.

7. 확장 기능

기본 system 패키지 외에 개발의 편의성을 위해 추가적으로 다음과 같은 기능들을 제공합니다.

7.1. Json

입출력 시의 사용자 편의성을 위해 Json 패키지를 제공합니다. 이 패키지로 Json 형식의 문자열과 Lua Table 구조체 간의 자동 변환이 가능합니다.

함수명 인자 반환값 기능
encode (any) (string) JSON 형식 문자열 인자로 주어진 루아값을 JSON 형식의 문자열로 변환한다.
decode (string) JSON 형식 문자열 (any) JSON 형식의 문자열을 대응되는 Lua 구조로 변환한다.

다음은 실제로 문자열을 decode 한 예제입니다. (Lua 테이블로 표현되는 일반 Array 는 1 부터 시작되니 사용시에 주의가 필요합니다.)

local json = require("json")

arrayVar = json.decode("[V1, V2, V3]")
system.print(arrayVar[1])  -- 1번째 배열 값 출력
system.print(arrayVar[2])  -- 2번째 배열 값 출력
system.print(arrayVar[3])  -- 3번째 배열 값 출력

수행결과는 다음과 같습니다.

V1
V2
V3

다음은 Lua 테이블을 문자열로 encode한 예제입니다.

local json = require("json")

jsonVar = {V1, V2, V3; name="john"}
jsonStr = json.encode(jsonVar)
system.print(jsonStr)
jsonVar = {{V1, V2, V3}, name="john"}
jsonStr = json.encode(jsonVar)
system.print(jsonStr)

수행결과는 다음과 같습니다.

{ "1":V1, "2":V2, "3":V3, "name":"john"}
{ "1":[V1, V2, V3], "name":"john"}

위와 같이 encode 시에는 필드 명이 명시 된 경우 각 값에 필드 명을, 필드 명이 없는 경우는 자동으로 필드 명을 추가하여 문자열로 변환합니다. 만약에 값을 배열로 저장하고 싶다면, 두번째 결과와 같이 필드 명을 붙이지 않고 "{}"로 배열로 저장할 값을 묶어서 변환하면 됩니다.

7.2. Execution Permission

스마트 컨트랙트의 정의를 위해서는 배포할 주소의 개인키가 필요하지만, 수행은 기본적으로 모든 주소에서 해당 스마트 컨트랙트를 대상으로 가능합니다. 코인스택 스마트 컨트랙트에서는 이를 제한하여 권한이 있는 사용자만 함수를 수행할 수 있도록 하는 기능을 제공합니다. 이를 위해서는 grant 함수로 수행 권한을 특정 주소에 부여하고, 각 함수 내에서 명시적으로 system.hasPermission 함수로 권한이 있는지를 확인하여 권한이 있는 사용자만 해당 함수를 수행하도록 할 수 있습니다.

다음은 수행 권한 설정을 위해 제공되는system 패키지 함수 설명입니다.

함수명 인자 반환값 기능
grant (string)address, (string)tokenName 입력한 주소에 tokenName 대한 권한을 부여
revoke (string)address, (string)tokenName 입력한 주소에 tokenName 대한 권한을 제거
hasPermission (string)tokenName (bool) 스마트 컨트랙트를 호출한 주소에 tokenName 대한 권한이 여부 반환

수행 권한을 설정하려면 다음과 같은 방법을 따릅니다.

  1. 수행 권한을 설정 하고자 하는 스마트 컨트랙트 함수에 hasPermission 를 이용한 권한 여부 체크 로직을 추가합니다.
  2. 스마트 컨트랙트 배포자는 호출 권한을 특정 사용자에게 grant하는 스마트 컨트랙트를 수행 합니다.

다음은 example() 함수에 권한 체크 로직을 추가한 예입니다.

local system = require("system")
function example()
  -- example이라는 토큰을 가진 sender만 이 블록안의 코드를 수행 가능 
  if system.hasPermission("example") {
  -- 실제 수행 로직 작성
  }
end

권한을 부여하기 위해서는 다음과 같은 내장 grant 함수를 호출하는 Lua 스마트 컨트랙트 트랜잭션을 수행하면 됩니다.

  -- CallerAddress에 example 이라는 토큰을 할당 함
  grant("CallerAddress", "example")

반대로 기 부여한 권한을 삭제하려면 revoke 함수를 호출하는 Lua 스마트 컨트랙트 트랜잭션을 수행하면 됩니다.

  -- CallerAddress에 기 부여되어 있던 example 토큰 권한을 제거
  revoke("CallerAddress", "example")

SDK에서는 스마트 컨트랙트 권한 부여, 제거 트랜잭션의 손쉬운 생성을 위해 유틸리티 함수를 제공합니다. 아래는 SDK를 이용한 권한 부여의 예입니다.

CoinStackClient client = new CoinStackClient("CONNECTION_INFO");
String contractAddress = "ADDRESS_FOR_SMARTCONTRACT"; // 수행할 대상 스마트 컨트랙트 주소
String privateKey = "SMARTCONTRACT_PRIVATEKEY"; //대상 스마트 컨트랙트 개인키
String granteeAddress = "ADDRESS_TO_GIVE_PERMISSION"; // 권한을 부여할 주소

LuaContractBuilder builder = new LuaContractBuilder();
 // 이전에 정의한 Smart Contract Address 할당
builder.setContractId(contractAddress);
// 빌더에 권한을 부여할 주소, 권한 또는 함수명, 부여 여부를 설정
// 부여 여부는 true 부여, false면 기존의 권한 제거
builder.setPermission(granteeAddress, "example", true);

String rawTx = builder.buildTransaction(client, privateKey);
client.sendTransaction(rawTx);

7.3. Event Notification

코인스택은 스마트 컨트랙트 수행중에 외부로 데이터를 전송하는 기능(Event Notification)을 제공합니다. 데이터는 전송 상황에 대한 식별자(이벤트 식별자)와 부가적인 정보로 구성됩니다. 데이터는 JSON 이며 형식은 아래와 같습니다.

{"UDEvent":eventname,"data":사용자지정값}
  • eventname 은 전송 상황에 대한 식별자 입니다. pushEventeventname 인자값과 동일합니다.
  • 사용자지정값 은 임의의 Lua 값입니다. pushEventdata 인자값의 JSON 표현입니다.

데이터를 전송 받고자하는 외부 객체를 endpoint 로 부릅니다. 코인스택은 endpoint 로 HTTP PUSH request 를 보냅니다. 따라서, Endpoint 는 HTTP PUSH Method 를 구현하고 JSON 데이터를 처리하는 HTTP 서버의 URL 이어야 합니다.

다음은 Event Notification 을 위해 제공되는 system 패키지 함수 설명입니다.

함수명 인자 반환값 기능
registEvent (string) eventname
(string) endpoint
(string) nodeid
eventname 으로 이벤트를 등록합니다. endpoint 는 HTTP PUSH Method 를 처리하는 서버의 url 입니다. 동일 eventname 으로 여러 개의 endpoint 를 등록할 수 있습니다. 등록시 동일한 endpoint 가 존재하면 업데이트 됩니다. nodeid 를 사용해서 특정 노드에만 등록할 수 있습니다. 명시하지 않으면 모든 노드에 등록됩니다.
unregistEvent (string) eventname
(string) endpoint
eventname, endpoint 로 등록된 이벤트를 제거합니다.
pushEvent (string) eventname
(any) data
eventname 에 해당하는 endpoint로 이벤트를 발생시킵니다. data는 임의의 사용자 지정 값입니다.
delEvent (string) eventname eventname 으로 등록된 전체 이벤트를 제거합니다.

아래는 Event Notification API 를 이용하여 입출금 상황을 통보하는 예제입니다.

모든 노드에서 이벤트 통보를 보내면 혼란스럽기 때문에 발생 노드를 지정합니다. 설정 파일의 nodeid 를 아래와 같이 수정합니다.

nodeid=alpha

스마트 컨트랙트 구현을 등록합니다.

event_def.lua

local system = require("system")

function addEventListener(eventName, endPoint)
    system.registEvent(eventName, endPoint, "alpha")
end

function deposit(name, amount)
    changeBalance(name, amount)
    system.pushEvent("balance", {op = "deposit", name = name, amount = amount})
end

function withdraw(name, amount)
    changeBalance(name, -amount)
    system.pushEvent("balance", {op = "withdraw", name = name, amount = amount})
end

function balance(name)
    accounts = system.getItem("accounts")
    if accounts == nil then
        accounts = {}
        accounts[name] = 0
    end
    return accounts[name]
end

function changeBalance(name, amount)
    accounts = system.getItem("accounts")
    if accounts == nil then
        accounts = {}
        accounts[name] = 0
    end
    balance = accounts[name] + amount
    accounts[name] = balance
    system.setItem("accounts", accounts)
end

이벤트 등록을 위한 스마트 컨트랙트를 호출합니다. (예제의 HTTP Server 는 JSON 데이터를 단순 출력하도록 구현했으며 상세 내용은 생략합니다.)

call("addEventListener", "balance", "http://postserver:3001")

addEventListener 함수는 system.registEvent 함수를 이용해서 설정 파일에 명시한 "alpha" 노드에 "balance" 이벤트를 등록합니다.

입출금 스마트 컨트랙트를 수행합니다.

call("deposit", "ktlee", 100)
call("deposit", "ktlee", 200)
call("withdraw", "ktlee", 50)

deposit/withdraw 함수는 입출금 처리 후에 pushEvent 함수를 통해 입출금 내역을 통보하게 됩니다.

http://postserver:3001 는 아래와 같이 출력합니다.

{"UDEvent":"balance","data":{"amount":100,"name":"ktlee","op":"deposit"}}
{"UDEvent":"balance","data":{"amount":200,"name":"ktlee","op":"deposit"}}
{"UDEvent":"balance","data":{"amount":50,"name":"ktlee","op":"withdraw"}}

results matching ""

    No results matching ""