Overview

Skill Level: Intermediate

In this recipe, we will go through a step-by-step guide to implement a transaction processor or a smart contract, in our transaction family example using the Sawtooth Python SDK.

Ingredients

To follow and complete this recipe, you need to have good knowledge of blockchain, Hyperledger transactions, JavaScript as well as basic knowledge of Python and Linux.

Step-by-step

  1. Hyperledger Sawtooth Overview

    Sawtooth simplifies the development of applications by separating the core system from the application domain. Application developers only need to focus on implementing their business logic for the enterprise using the transaction processor, with the language of their choice, without needing to know the underlying details of the Sawtooth core system.
    For those who are not familiar with Hyperledger project Intro to Hyperledger Family and Hyperledger Blockchain Ecosystem and Hyperledger Design Philosophy and Framework Architecture articles are strongly recommended.
    To better follow and understand this recipe, it is advisable to read Essential Hyperledger Sawtooth Features for Enterprise Blockchain Developers, Blockchain Developer Guide- How to Install and work with Hyperledger Sawtooth , Configuring Hyperledger Sawtooth Validator and REST API as well as Designing namespace and address for Hyperledger Sawtooth transaction family articles in advance.

    In this recipe, we will go through a step-by-step guide to implement a transaction processor, a smart contract, in our transaction family example using the Sawtooth Python SDK. In doing so, we cover the following topics: i- Build Python Sawtooth SDK, ii- Registering the transaction handler to the transaction processor and iii- Implementing the transaction handler class.

  2. Build Python Sawtooth SDK

    The Python SDK is installed automatically when you install Hyperledger Sawtooth on AWS. You can import the SDK in Python to verify Python Sawtooth SDK is installed on the computer as follows:
    Hyperledger Sawtooth processor as a service and Python Egg

    The SDK is installed at /usr/lib/python3/dist-packages/sawtooth_sdk.

    Let’s go through a step-by-step guide to implement a transaction processor. We will highlight the important code segments to explain the logic for each step and the full example code implementation can be downloaded from the GitHub repository.

  3. Registering Transaction Handler to Transaction Processor

    The main module to start the transaction processor and register the transaction handler for the example application is at sawtooth_mkt/processor/main.py. The steps to do this are as follows:
    1. Instantiate the general transaction processor and set the validator connection URL:

    processor = TransactionProcessor(url=mkt_config.connect)

    2. Instantiate the transaction handler and set the namespace prefix for the transaction family:

    handler = MktTransactionHandler(namespace_prefix=mkt_prefix)

    3. Register the transaction handler to the transaction processor:
    processor.add_handler(handler)

    Start the transaction processor to connect to the validator and start the process request for the transaction family:

    processor.start()

  4. Implementing Transaction Handler Class

    The transaction handler class allows us to implement smart contract logic in its apply method. The basic steps to implement smart logic are shown in the following diagram:
    Hyperledger Sawtooth processor as a service and Python Egg SDK

     

    The transaction handler module for our marketplace application can be found in sawtooth_mkt/processor/handler.py. The apply method skeleton code, as indicated in the preceding diagram, is as follows:

    def apply(self, transaction, context):

    try:

    # 1. Decode the transaction payload

    house, action, owner, signer = _unpack_transaction(transaction)

    _display(“User {} house {} action {} owner {}”.format(signer[:6],house, action, owner))

    # 2. Get the current state from context

    dbowner, house_list = _get_state_data(context, self._namespace_prefix, house)

    _display(“dbowner {} house list {} “.format(dbowner, house_list))

    # 3. Validate the transaction

    _validate_house_data(action, owner, signer, dbowner)

    # 4. Enforce entitlement, ACL

    # 5. Apply the transaction if action == “create”:

    _display(“User {} created a house {} owner {}.”.format(signer[:6],house, owner))

    elif action == “transfer”:

    _display(“User {} transfer: {} from {} to {}\n\n”.format(signer[:6], house, dbowner,owner))

    # 6. Store new state back in context

    _store_state_data(context, house_list, self._namespace_prefix, house, owner)

    except Exception as e:

    _display(“Exception in apply {} \n\n”.format(e))

     

  5. Put Things Together

    We will illustrate each of the preceding steps using the code snippet for the marketplace sample:
    1. Decode the transaction payload: The transaction header has the signer public key, which identifies the person who submits the transaction. The payload is the encoded transaction data in the client application. For the marketplace sample, we encode the data as a comma-delimited CSV, so the payload will look like (house, action, owner) and the decode function looks as follows:

    def _unpack_transaction(transaction):

    header = transaction.header

    The transaction signer is the player signer = header.signer_public_key try:

    The payload is csv utf-8 encoded string house, action, owner =

    transaction.payload.decode().split(“,”)

    except ValueError:

    raise InvalidTransaction(“Invalid payload serialization”)

    2. Get the current state from the context: You often need to get the current state for existing assets, which is stored in the global state. You can access this based on the address through the context. The address is the 70-character address, and it includes the 6 characters for the namespace prefix; the remaining 64 characters are for the asset following the scheme that you design for the transaction family. The address for the marketplace example is as follows:

    def _make_mkt_address(namespace_prefix, house):

    return namespace_prefix + \

    hashlib.sha512(house.encode(‘utf-8’)).hexdigest()[:64]

    The current state is stored as a multi-house record, each of which is separated by |. Each house and owner is a comma-delimited string:

    def _get_state_data(context, namespace_prefix, house):

    Get data from address state_entries = \

     

    context.get_state([_make_mkt_address(namespace_prefix,house)])

    context.get_state() returns a list. If no data has been stored yet

    at the given address, it will be empty.

    if state_entries:

    try:

    state_data = state_entries[0].data

    _display(“state_data {}

    \n”.format(state_data.decode()))

    house_list = { dbhouse: (dbowner) for dbhouse, dbowner

    in

    1. dbhouseowner.split(‘,’) for dbhouseowner in state_data.decode().split(‘|’) ] }

     

    _display(“house list in db {} \n”.format(house_list))

    dbowner = house_list[house]

    _display(“db house {} db owner \n”.format(house,dbowner))

    except ValueError:

    raise InternalError(“Failed to deserialize game data.”)

    else:

    house_list = {}

    dbowner = None

    return dbowner, house_list

     

    3. Validate the transaction: Like the normal logic, before you update the current state based on the new action, the transaction data should be validated based on your business rules:

    def _validate_house_data(action, owner, signer, dbowner):

    if action == ‘create’:

    if dbowner is not None:

    raise InvalidTransaction(‘Invalid action: house already

    exists.’)

    elif action == ‘transfer’:

    if dbowner is None:

    raise InvalidTransaction(

    ‘Invalid action: transfer requires an existing house.’)

    4. Enforce resource entitlement using access control lists (ACLs): Since the signer public key is available as the header, you can enforce resource entitlement to the asset using ACLs before applying the transaction, based on who submitted the transaction. Authorization and resource access can be implemented to keep the data safe.

    5. Apply the transaction: Once both the transaction and the current state are available, you are ready to apply your business rules, based on the commands and instructions in the transaction. The rules and computation can be applied to the current state to generate a new state.

    6. Store the new state back in the context: We need to commit the new state back into the global state via the context. This is the same as getting the current state. The new state needs to be encoded to update the global state based on the address:

    def _store_state_data(context, house_list, namespace_prefix, house, owner):
    house_list[house] = (owner)

    state_data = ‘|’.join(sorted([

    ‘,’.join([house, owner]) for house, (owner) in house_list.items() ])).encode()

    addresses = context.set_state(

    {_make_mkt_address(namespace_prefix, house): state_data})

    if len(addresses) < 1:

    raise InternalError(“State Error”)

     

    Here is a link to the source files of this recipe.

    So far you learned how to configure Hyperledger Sawtooth (Validator and REST API), design namespace and address for Hyperledger Sawtooth transaction family and build transaction handler and processor for Hyperledger Sawtooth. The next and final step is to Build Transaction Processor Service and Python Egg for Hyperledger Sawtooth.
    This recipe is written in collaboration with Brian Wu who is a senior Hyperledger instructor at Coding Bootcamps school in Washington DC.

Join The Discussion