3.9.2 Application Architecture

The application architecture is defined by the basic application functionality that is necessary to form and host transactions. The general logic can be refined considering the additional functionality of the application. REST-API of the platform will return data on the execution of transactions in JSON (raw) form or specific data for a given family of transactions. Writing to the ledger requires a digital signature and must use the requirements for a common transaction structure.

The structure of the general transaction (steps to create it) defines the main components of the application:

  • Creating a key pair (private and public). Regardless of any additional identity components (DID), the DGT platform works using asymmetric cryptography. In the simplest case, 256-bit private keys are used, which can be generated offline, for example, using the secp256k1 library. Since DGT can work with various cryptographies, any application implementation should support the same cryptography (type, curve) as the entire platform. The public key can be derived from the private key. Example of a Python code:

import secp256k1
key_handler = secp256k1.PrivateKey()
private_key_bytes = key_handler.private_key
public_key_bytes = key_handler.public_key.serialize()
public_key_hex = public_key_bytes.hex()
  • Creating the transaction payload (body). The body of the transaction is the functionality for which the transaction is launched. The body of the transaction is encoded into a set of bytes (binary-encoded), for example, using Concise Binary Object Representation (CBOR) serialization. Example:

import cbor

payload = {‘Verb’: ‘set’,
           ‘Key’: ‘THENAME’,
           ‘Value’: 256}
  • Creating a transaction header requires additional steps to generate the corresponding hash, as well as the content of the header. Header encoding is done through the Google Protocol Buffer (Protobuff). The header of the transaction may also contain certain inputs and outputs that allow for the control of processing. An example using Sawtooth SDK:

from random import randint
from sawtooth_sdk.protobuf.transaction_pb2 import TransactionHeader

txn_header = TransactionHeader(
    batcher_public_key=public_key_hex,
# If we had any dependencies, this is what it might look like:
#dependencies=[
'540a6803971d1880ec73a96cb97815a95d374cbad5d865925e5aa0432fcf1931539afe10310c1    
 22c5eaae15df61236079abbf4f258889359c4d175516934484a'
],
 family_name='intkey',
 family_version='1.0',
 inputs=[
 '1cf1266e282c41be5e4254d8820772c5518a2c5a8c0c7f7eda19594a7eb539453e1ed7'],
    nonce=str(randint(0, 1000000000)),
    outputs=[
'1cf1266e282c41be5e4254d8820772c5518a2c5a8c0c7f7eda19594a7eb539453e1ed7'],
    payload_sha512=payload_sha512,
    signer_public_key=public_key_hex)

txn_header_bytes = txn_header.SerializeToString()
  • Signing the transaction header using cryptography, e.g. ECDSA (such as secp256k1 elliptic curve, SHA-256 hash)

key_handler = secp256k1.PrivateKey(private_key_bytes)

# ecdsa_sign automatically generates a SHA-256 hash of the header bytes
txn_signature = key_handler.ecdsa_sign(txn_header_bytes)
txn_signature_bytes = key_handler.ecdsa_serialize_compact(txn_signature)
txn_signature_hex = txn_signature_bytes.hex()
  • Creating a transaction requires joining the transaction header, signature, and payload (body):

from sawtooth_sdk.protobuf.transaction_pb2 import Transaction
txn = Transaction(
    header=txn_header_bytes,
    header_signature=txn_signature_hex,
    payload=payload_bytes)
  • The transaction may be additionally decoded in case of external processing (forming a TransactionList):

txnList = TransactionList()
txnList.ParseFromString(txnBytes)

txn = txnList.transactions[0]
  • Forming and signing the transaction package. To optimize network processing, transactions are transmitted and processed in batches. At a minimum, batches of transactions and the transactions themselves coincide.

batch_signature = key_handler.ecdsa_sign(batch_header_bytes)

batch_signature_bytes = key_handler.ecdsa_serialize_compact(batch_signature)

batch_signature_hex = batch_signature_bytes.hex()

from sawtooth_sdk.protobuf.batch_pb2 import Batch

batch = Batch(
    header=batch_header_bytes,
    header_signature=batch_signature_hex,
    transactions=[txn])
  • Encoding the transaction batches (serializing). Transaction batches passed to the node core (Validator) must be serialized into a BatchList structure:

from sawtooth_sdk.protobuf.batch_pb2 import BatchList

batch_list = BatchList(batches=[batch])
batch_bytes = batch_list.SerializeToString()
  • Transferring transactions to the validator is essentially calling to the REST-API:

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

Last updated