09.Building and Submitting Transactions with js

이 문서는 hyperledger sawtooth 1.0.4을 docker for ubuntu(18.03.01-ce)에서 다루며 os는 ubuntu-18.04 LTS임

1. Overview

이번 문서에서는 자바스크립트 코드로 트랜잭션을 만들고, validator에 제출해보도록 하겠습니다.

node.js사용

2. Prerequisites

이전 문서에서 했던 과정을 그대로 따라해주시고 docker-compose커맨드로 sawtooth를 실행시켜주시기 바랍니다.

같은 폴더 안에 있는 package.json과 compile_protobuf.js파일, protobuf폴더와 protos폴더를 클론받아 프로젝트에 추가시켜주세요.

3. Install Package

cmd창에서 다음 커맨드를 통해 패키지를 설치합니다.

npm install

패키지를 설치하고 나면 프로젝트 구조는 다음과 같이 구성됩니다.

Alt text
node_moudules에는 package.json에 기재되어있던 의존모듈이 추가되고 scriptsprepublish를 통해 compile_protobuf.js가 컴파일되고 payload를 인코딩할 수 있는 protobuf_bundle.json이 추가됩니다.

4. protobuf_bundle.json

protobuf_bundle.json파일은 compile_protobuf.js파일을 컴파일함으로써 protos폴더 안의 proto파일을 json형식으로 나타낸 것입니다. 이를 통해 Payload가 어떤식으로 구성되어야하는지를 알 수 있게됩니다.

proto 파일에 대한 정보는 여기

5. Payload 구성

Payload를 구성함에 있어 가장 주의깊게 봐야할 부분은 Encoding하는 부분입니다. 모든 트랜잭션 패밀리에서 payload를 parsing하는 방법이 다르기 때문에 보내는 방법도 각양각색입니다.
반드시 참고해야할 트랜잭션 패밀리의 핸들러 소스코드와 proto파일을 읽어보셔야합니다.

5.1 Intkey

intkey 트랜잭션 패밀리는 payload를 CBOR로 encoding하고 json형식으로 작성해야합니다.

const cbor = require('cbor')
const payload = {
    Verb: 'set',
    Name: 'foo',
    Value: 42
}
const payloadBytes = cbor.encode(payload)

5.2 Xo

Xo 트랜잭션 패밀리는 payload를 작성하는 방법이 Intkey와는 사뭇 다릅니다.

class XoPayload:

    def __init__(self, payload):
        try:
            # The payload is csv utf-8 encoded string
            name, action, space = payload.decode().split(",")
        except ValueError:
            raise InvalidTransaction("Invalid payload serialization")
    ...

xo트랜잭션의 payload를 핸들링하는 소스코드의 일부입니다. try문 안을 보면 name, action, space의 순서대로 “,“를 기준으로 parsing하고 있습니다.

xo 트랜잭션의 payload를 구성할 때는 json형식으로 구성한다면 InvalidTransaction 에러가 뜰 수 밖에 없습니다.

    const payload = "game1,create,";
    const payloadBytes = cbor.encode(payload);

위와 같이 페이로드를 구성하면 됩니다.

5.3 Identity

세 번째 예시는 Identity 트랜잭션 패밀리입니다. 제가 예시를 들어볼 트랜잭션은 policy를 생성하는 트랜잭션입니다.

Identity 트랜잭션을 만드려면 npm install하기전에 protos 폴더에 identities.proto 파일이 존재해야합니다.
sawtooth-core의 protos폴더에는 identities.proto파일이 존재하지 않고, 여기에 있으니 참고하시기 바랍니다. 현재 문서의 protos 폴더에는 identities.proto를 추가시켜놨습니다.

단순히 require(“protobuf”)를 하게 되면, node_modules를 참조하게 됩니다. 하지만 우리는 identities.proto를 임의로 추가시켜 protobuf_bundle파일을 생성했으니 node_modules가 아닌 로컬의 protobuf파일을 참조하게 해야합니다.

따라서 이와같이 작성하여야 합니다.

const {Policy,IdentityPayload,TransactionHeader,Transaction,BatchHeader,Batch,BatchList} = require("../protobuf");

protobuf_bundle.json 파일로 가서 payload를 만들기 위한 양식을 찾아봅시다. Policy나 Role을 세팅하기 위한 json 포맷은 다음과 같습니다.

    "IdentityPayload": {
      "fields": {
        "type": {
          "type": "IdentityType",
          "id": 1
        },
        "data": {
          "type": "bytes",
          "id": 2
        }
      },
      "nested": {
        "IdentityType": {
          "values": {
            "IDENTITY_TYPE_UNSET": 0,
            "POLICY": 1,
            "ROLE": 2
          }
        }
      }
    },

먼저 최종적으로 IdentityPayload에 들어가야할 내용은 type과 data입니다. type은 IdentityType을 참고하고 있고, IdentityType은 하단의 nested안에서 확인할 수 있습니다.
제가 예시로 들 내용은 policy를 세팅하는 것이니 type은 1이 될 것입니다.

다음으로는 data입니다. data에는 policy에 세팅할 정보가 들어가게 될 것입니다.

    "Policy": {
      "fields": {
        "name": {
          "type": "string",
          "id": 1
        },
        "entries": {
          "rule": "repeated",
          "type": "Entry",
          "id": 2
        }
      },
      "nested": {
        "EntryType": {
          "values": {
            "ENTRY_TYPE_UNSET": 0,
            "PERMIT_KEY": 1,
            "DENY_KEY": 2
          }
        },
        "Entry": {
          "fields": {
            "type": {
              "type": "EntryType",
              "id": 1
            },
            "key": {
              "type": "string",
              "id": 2
            }
          }
        }
      }
    },

Policy는 name과 entries가 필요합니다. name은 String형, entries는 Entry형이고 반복될수있습니다.

다음과 같은 커맨드는

$ sawtooth identity policy create p1_name "PERMIT_KEY *" 

이렇게 구성될 수 있습니다.

    const policyBytes = Policy.encode({
        name: "p1_name",
        entries: [
            {
                type: 1,   //PERMIT_KEY
                key: "*"
            }
        ]
    }).finish();

    const payloadBytes = IdentityPayload.encode({
        type : 1,          //policy
        data : policyBytes
    }).finish();

CBOR을 사용하지 않고, protocol buffer의 encode를 사용하고 있습니다.

identity는 트랜잭션을 보낼 때 allowed_key로 정해진 key만 sign이 가능합니다.

6. Signer

공식 문서의 예시에서는 랜덤으로 privateKey를 생성하여 트랜잭션과 배치에 서명하게 되어있습니다.
특정 privateKey로 서명하고 싶다면 다음과 같이 하면 됩니다.

        const context = createContext("secp256k1"); //암호화 컨텍스트
        
        const privateKey = Secp256k1PrivateKey.fromHex("8a0d61af0a9518bcf4d60f013edc5da6876fcd4dad9ee0f3a832a0c67122faae");
        const signer = new CryptoFactory(context).newSigner(privateKey);

7. Transaction Header

트랜잭션의 헤더는 다음과 같이 작성할 수 있습니다.

    const transactionHeaderBytes = TransactionHeader.encode({
        familyName: "sawtooth_identity",      //보내려는 트랜잭션의 패밀리 네임
        familyVersion: "1.0",
        inputs: ["000000", "00001d"],         //input : read from
        outputs: ["00001d"],                  //output : write to
        test: signer.getPublicKey(),
        signerPublicKey: signer.getPublicKey(privateKey).asHex(),
        // In this example, we're signing the batch with the same private key,
        // but the batch can be signed by another party, in which case, the
        // public key will need to be associated with that key.
        batcherPublicKey: signer.getPublicKey(privateKey).asHex(),
        // In this example, there are no dependencies.  This list should include
        // an previous transaction header signatures that must be applied for
        // this transaction to successfully commit.
        // For example,
        // dependencies:['540a6803971d1880ec73a96cb97815a95d374cbad5d865925e5aa0432fcf1931539afe10310c122c5eaae15df61236079abbf4f258889359c4d175516934484a'],
        dependencies: [],
        payloadSha512: createHash("sha512").update(payloadBytes).digest("hex")
    }).finish();

input과 output에 관한 정보는 여기의 line 49처럼 define된 변수가 있거나 공식문서의 트랜잭션패밀리를 다룬 부분의 Addressing을 확인하면 됩니다.

Alt text
만약 input과 output을 틀리게 적었다면 위와같은 오류가 발생합니다.

이 뒤의 내용은 공식문서의 예시와 같으니 참고하시면 됩니다.

8. 마치며

수고하셧읍니다^^7 점점 새로 공부해야하는 내용이 많아지네요. 다음 문서는 뭐로 작성하면 좋을지 고민중..



댓글남기기