본문 바로가기

옥탑방주인/Hyperledger

Hyperledger Sawtooth -Building and Submitting Transactions (Sawtooth v1.0.2)



Building and Submitting Transactions


분산 장부(distributed ledger)에 제출(submit)할 정보를 인코딩하는 프로세스는 중요하다.


신원(identity) 및 데이터 유효성(data validity)을 확인하기 위해 일련의 암호 기법(cryptographic) 보호 장치가 사용된다. Hyperledger Sawtooth도 다르지 않지만, Python 3 SDK는 이러한 세부 사항 대부분을 추상화(abstract)하는 클라이언트 기능을 제공하며, 블록 체인을 변경하는 프로세스를 크게 단순화 하는 기능을 제공한다.



Creating a Private Key and Signer


신원(identity)을 확인하고 validator에 보내는 정보에 서명(sign)하려면, 256-bit key가 필요하다. Sawtooth는 서명을 위해 secp256k1 ECDSA 표준을 사용한다. 즉 거의 모든 32바이트의 셋트가 유효한 키(valid key)이다. SDK의 서명 모듈을 사용하여 유효한 키를 생성하는 것은 매우 간단하다.


서명자는 개인 키를 랩핑하는것, 개인키와 관련된 공개키를 가져오는것과 바이트에 서명(signing bytes)하기 위한 다수의 편리한 메소드를 제공한다.


from sawtooth_signing import create_context

from sawtooth_signing import CryptoFactory


context = create_context('secp256k1') 

private_key = context.new_random_private_key()

signer = CryptoFactory(context).new_signer(private_key)


노트 

이 키는 오직 블록체인에서 신원을 증명하는 역활을 한다. 당신의 key를 소유하고 있는 사람은 당신의 id를 사용해서 transaction에 서명할 수 있으며, 이것을 잃어버린 경우 복구가 불가능하다.



Encoding Your Payload


Transaction Payloads는 validator에서 알수없는 2바이트 encoding data로 구성되어 있다. 이들을 인코딩(encoding)하고 디코딩(decoding)하는 로직은 특정 transaction processor 자체 내에서 전적으로 수행된다. 결과적으로, 많은 가능한 형식(possible format)이 있으며 해당 정보에 대해서는 트랜잭션 프로세서 자체의 정의를 살펴 봐야합니다. 한 예를 들면, IntegerKey Transaction Processor는 CBOR에서 인코딩된 3개의 Key/Value 쌍을 사용한다. 아래는 cbor 하나를 생성하는 예제이다.


import cbor


payload = {

    'Verb': 'set',

    'Name': 'foo',

    'Value': 42}


payload_bytes = cbor.dumps(payload)



Building the Transaction


Transaction은 Sawtooth block chain에 대한 개별(individual) 상태 변경의 기본이다. Transaction들은 binary payload와 binary로 인코딩된 TransactionHeader로 구성되어 있고, TransactionHeader에는 header의 서명과 이것이 어떻게 제어되는지에 관한 몇몇의 cryptographic safeguards 와 metadata가 존재한다. Transaction 과 Batches에 대한 정보, 특히 TransactionHeaders의 정의를 잘 알고 사용하는게 좋다. 



1. Create the Transaction Header


TransactionHeader는 transaction이 옳바른 transaction processor로 라우팅하기 위한 정보, 입력 및 출력(input and output) state 주소(state addresses)가 관련된 항목, 의존하는 이전 transaction에 대한 참조 및 해당 서명과 관련된 공개 키를 포함합니다. 헤더(Header)는 payload bytes의 SHA-512 해쉬를 통해 payload를 참조한다.


from hashlib import sha512

from sawtooth_sdk.protobuf.transaction_pb2 import TransactionHeader


txn_header_bytes = TransactionHeader(

    family_name='intkey',

    family_version='1.0',

    inputs=['1cf1266e282c41be5e4254d8820772c5518a2c5a8c0c7f7eda19594a7eb539453e1ed7'],

    outputs=['1cf1266e282c41be5e4254d8820772c5518a2c5a8c0c7f7eda19594a7eb539453e1ed7']

    signer_public_key=signer.get_public_key().as_hex(),

    # 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.

    batcher_public_key=signer.get_public_key().as_hex(),

    # 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=[],

    payload_sha512=sha512(payload_bytes).hexdigest()

).SerializeToString() 


노트 

batcher public_key는 나중에 Transaction's Batch 서명에 사용될 private key와 매칭되는 16진수 공개 키이다. 의존성(dependencies)는 transaction 전에 커밋해야하는 transaction의 헤더 서명이다. (TransactionHeader에 관해 자세한 정보를 알고싶다면 여기 를 클릭해라)


노트 

입력과 출력(input and output)은 transaction이 읽거나 쓸 수 있는 state 주소(addresses) 이다. 위의 트랜잭션을 사용하여 'foo'값이 저장된 특정 주소(specific address)를 참조했다. 가능한 매번, 특정 주소들은 사용되어야 한다, 특정 주소들은 validator가 transaction processing을 보다 효율적으로 스케쥴링하도록 허용한다.


주소(addresses)에 assigning과 validating 하기 위한 메소드는 전적으로 Transaction Processor에 달려있다. IntegerKey의 경우에는 유효한 주소를 생성하는 특정한 규칙이 있고, 이러한 규칙들은 반드시 준수해야 한다. 그렇지 않으면 transaction이 거부된다. Transaction family와 작동하는것에 대한 address rules을 따라야 한다.



2. Create the Transaction


TransactionHeader가 생성이되면, bytes는 서명(signature)를 생성하는데 사용된다. 이 헤더 서명(header signature)는 마찬가지로 transaction의 ID처럼 작동한다. header bytes, header signature, payload bytes는 Transaction 구성하는데 모두 사용된다.


from sawtooth_sdk.protobuf.transaction_pb2 import Transaction


signature = signer.sign(txn_header_bytes)


txn = Transaction(

    header=txn_header_bytes,

    header_signature=signature,

    payload: payload_bytes

)



3. (optional) Encode the Transaction(s)


같은 머신이 transaction 및 batch를 생성하는 경우 transaction instance를 인코딩 할 필요가 없다. 그러나 Transaction이 외부에서 일괄 처리되는 사용 사례(use case)에서는 bathcer로 전송되기 전에 serialized 되어야 한다. Python 3 SDK는 이러한 방법을 위해 2가지 옵션을 제공한다. Transaction은 TransactionList 메소드에서 serialized 또는 single Transaction에서 serialized것을 결합한다.


from sawtooth_sdk.protobuf import TransactionList


txn_list_bytes = TransactionList(

    transactions=[txn1, txn2]

).SerializeToString()


txn_bytes = txn.SerializeToString()



Building the Batch


하나 이상의 Transaction instance가 준비되면, 반드시 Batch로 wrapped 되어야 한다. Batches는 Sawtooth's state에서 원자 단위(atomic unit)의 변경이다.(아주 작은 단위의 변경을 말하는 것 같다) Batch가 validator에 제출(submitted)되었을 때, 각각의 Transaction안에서 순서대로 적용되거나, Transaction이 적용되지 않는다. Transaction이 다른 Transaction에 의존하지 않더라도, validator에 직접 제출(submitted)할 수 없습니다. Transaction은 반드시 모두 Batch에 wrapped 되어야 한다.



1. Create the BatchHeader


TransactionHeader와 비슷하다. 이것들은 각각의 Batch를 위한 BatchHeader이다. Batch는 Transaction보단 매우 심플하다. BatchHeader는 Batch에 나열된 것과 동일한 순서로 서명자(signer)의 공개 키(public key), Transaction ID의 리스트만 필요하다.


from sawtooth_sdk.protobuf.batch_pb2 import BatchHeader


txns = [txn]


batch_header_bytes = BatchHeader(

    signer_public_key=signer.get_public_key().as_hex(),

    transaction_ids=[txn.header_signature for txn in txns],

).SerializeToString()



2. Create the Batch


SDK를 사용하면, Batch를 생성하는것은 transaction을 생성하는것과 비슷할 수 있다. header는 서명되있거나 서명(signature)의 결과는 Batch의 ID처럼 작동할 수 있다. Batch는 header bytes, header signature 및 batch를 구성하는 transaction으로 구성된다.(코드상으로는 맞는 해석인 것 같지만, 정확한 해석이 맞는지 모르겠어서 다시 확인하고 수정하겠습니다.)


from sawtooth_sdk.protobuf.batch_pb2 import Batch


signature = signer.sign(batch_header_bytes)


batch = Batch(

    header=batch_header_bytes,

    header_signature=signature,

    transactions=txns

)



3. Encode the Batch(es) in a BatchList


validator에 Batch를 전송하려면, 반드시 BatchList에 수집되어야 한다. 다수의 Batch는 하나의 BatchList에 제출할 수 있지만, batch 자체는 반드시 서로 의존할 필요는 없다. Batch와는 다르게, BatchList는 atomic이 아니다. 다른 클라이언트의 batch가 당신의 batch에 동시에 액세스 할 수 있다.


from sawtooth_sdk.protobuf.batch_pb2 import BatchList


batch_list_bytes = BatchList(batches=[batch]).SerializeToString()


노트 

transaction 생성자가 bathcer에서 다른 private key를 사용한다면, bathcer public_key는 반드시 모든 Transaction과 서명된 Batch에 사용될 private key로부터 생성된것을 위해 명시해줘야 한다.




Submitting Batches to the Validator


validator로 batch를 제출(submit)하는 규정된 방법은 REST API를 사용하는 것이다. 이것은 클라이언트가 HTTP/JSON 표준(Standard)을 사용하여 통신 할 수 있게하는 validator와 함께 실행되는 독립적인(independent) 프로세스이다. 간단하게, POST요청은 "application/octet-stream"의 header가 "Content-Type"이고, BatchList로 serialized된 body와 함께 batches의 종점(endpoint)에 보내는 것 이다.


HTTP request를 보내는 것에는 많은 방법이 있고, 여기서는 submission process가 매우 간단하다. 아래의 예제에서는 BatchList를 준비하는 동일한 Python3 프로세스에서 요청을 보내는 경우의 코드이다(계속해서 HTTP 요청을 보낸다는 의미인 것 같다.).


import urllib.request

from urllib.error import HTTPError


try:

    request = urllib.request.Request(

        'http://rest.api.domain/batches',

        batch_list_bytes,

        method='POST',

        headers={'Content-Type': 'application/octet-stream'})

    response = urllib.request.urlopen(request)


except HTTPError as e:

    response = e.file


 curl명령어를 사용하여 보낸 후 그리고 binary를 파일에 저장 하고 싶다면 아래와 같은 명령어를 입력해라.


output = open('intekey.batches', 'wb'

output.write(batch_list_bytes) 


% curl --request POST \

    --header "Content-Type: application/octet-stream" \

    --data-binary @intkey.batches \

    "http://rest.api.domain/batches" 





이번 섹션은 되게 저한테 사소한 문장들이 많아서 발 번역이 많은 것 같네요;; 시간될 때 마다 보면서 수정하겠습니다. 혹은 댓글로 지적해주시면 확인 후 의견을 반영하겠습니다.