Abhi
Abhi

Reputation: 126

Sawtooth validator is not registering a custom transaction processor. Logs 'No Handler for that type' message

I am trying to build a custom transaction processor for sawtooth but I have run into a wall and since then have been stuck there. I created a sawtooth test network using docker on my local machine (using this guide). To test my custom transaction processor I modified the docker compose file to publish ports of the validator-0 and rest-api-0 to the host machine. I have tried accessing the rest api from the browser and it works fine. However the problem occurs when I try to run my custom transaction processor. The logs on the validator terminal shows a message as follows:

sawtooth-validator-default-0 | [2022-03-23 13:17:21.492 INFO     dispatch] received a message of type TP_REGISTER_REQUEST from d9de3f928215c306cf719fba501bae475c209cb41cad6efd55b410d7918dfe657f0f9211580c8b3249baec3aaf87d0fc84b58aee1c1501b8c65a7c8fc1ad14f8 but have no handler for that type

The modified docker compose file is below

# Copyright 2019 Cargill Incorporated
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

version: '3.6'

volumes:
  pbft-shared:

services:

# -------------=== rest api ===-------------

  rest-api-0:
    image: hyperledger/sawtooth-rest-api:nightly
    container_name: sawtooth-rest-api-default-0
    # expose:
    #   - 8008
    ports:
        - 8008:8008
    command: |
      bash -c "
        sawtooth-rest-api \
          --connect tcp://validator-0:4004 \
          --bind rest-api-0:8008
      "
    stop_signal: SIGKILL

  rest-api-1:
    image: hyperledger/sawtooth-rest-api:nightly
    container_name: sawtooth-rest-api-default-1
    expose:
      - 8008
    command: |
      bash -c "
        sawtooth-rest-api \
          --connect tcp://validator-1:4004 \
          --bind rest-api-1:8008
      "
    stop_signal: SIGKILL

  rest-api-2:
    image: hyperledger/sawtooth-rest-api:nightly
    container_name: sawtooth-rest-api-default-2
    expose:
      - 8008
    command: |
      bash -c "
        sawtooth-rest-api \
          --connect tcp://validator-2:4004 \
          --bind rest-api-2:8008
      "
    stop_signal: SIGKILL

  rest-api-3:
    image: hyperledger/sawtooth-rest-api:nightly
    container_name: sawtooth-rest-api-default-3
    expose:
      - 8008
    command: |
      bash -c "
        sawtooth-rest-api \
          --connect tcp://validator-3:4004 \
          --bind rest-api-3:8008
      "
    stop_signal: SIGKILL

  rest-api-4:
    image: hyperledger/sawtooth-rest-api:nightly
    container_name: sawtooth-rest-api-default-4
    expose:
      - 8008
    command: |
      bash -c "
        sawtooth-rest-api \
          --connect tcp://validator-4:4004 \
          --bind rest-api-4:8008
      "
    stop_signal: SIGKILL

# -------------=== shell ===-------------

  shell:
    image: hyperledger/sawtooth-shell:nightly
    container_name: sawtooth-shell-default
    volumes:
      - pbft-shared:/pbft-shared
    command: |
      bash -c "
        sawtooth keygen
        tail -f /dev/null
      "
    stop_signal: SIGKILL

# -------------=== validators ===-------------

  validator-0:
    image: hyperledger/sawtooth-validator:nightly
    container_name: sawtooth-validator-default-0
    # expose:
    #   - 4004
    #   - 5050
    #   - 8800
    ports:
        - "4004:4004"
        - "5050:5050"
        - "8800:8800"
    volumes:
      - pbft-shared:/pbft-shared
    command: |
      bash -c "
        if [ -e /pbft-shared/validators/validator-0.priv ]; then
          cp /pbft-shared/validators/validator-0.pub /etc/sawtooth/keys/validator.pub
          cp /pbft-shared/validators/validator-0.priv /etc/sawtooth/keys/validator.priv
        fi &&
        if [ ! -e /etc/sawtooth/keys/validator.priv ]; then
          sawadm keygen
          mkdir -p /pbft-shared/validators || true
          cp /etc/sawtooth/keys/validator.pub /pbft-shared/validators/validator-0.pub
          cp /etc/sawtooth/keys/validator.priv /pbft-shared/validators/validator-0.priv
        fi &&
        if [ ! -e config-genesis.batch ]; then
          sawset genesis -k /etc/sawtooth/keys/validator.priv -o config-genesis.batch
        fi &&
        while [[ ! -f /pbft-shared/validators/validator-1.pub || \
                 ! -f /pbft-shared/validators/validator-2.pub || \
                 ! -f /pbft-shared/validators/validator-3.pub || \
                 ! -f /pbft-shared/validators/validator-4.pub ]];
        do sleep 1; done
        echo sawtooth.consensus.pbft.members=\\['\"'$$(cat /pbft-shared/validators/validator-0.pub)'\"','\"'$$(cat /pbft-shared/validators/validator-1.pub)'\"','\"'$$(cat /pbft-shared/validators/validator-2.pub)'\"','\"'$$(cat /pbft-shared/validators/validator-3.pub)'\"','\"'$$(cat /pbft-shared/validators/validator-4.pub)'\"'\\] &&
        if [ ! -e config.batch ]; then
         sawset proposal create \
            -k /etc/sawtooth/keys/validator.priv \
            sawtooth.consensus.algorithm.name=pbft \
            sawtooth.consensus.algorithm.version=1.0 \
            sawtooth.consensus.pbft.members=\\['\"'$$(cat /pbft-shared/validators/validator-0.pub)'\"','\"'$$(cat /pbft-shared/validators/validator-1.pub)'\"','\"'$$(cat /pbft-shared/validators/validator-2.pub)'\"','\"'$$(cat /pbft-shared/validators/validator-3.pub)'\"','\"'$$(cat /pbft-shared/validators/validator-4.pub)'\"'\\] \
            sawtooth.publisher.max_batches_per_block=1200 \
            -o config.batch
        fi &&
        if [ ! -e /var/lib/sawtooth/genesis.batch ]; then
          sawadm genesis config-genesis.batch config.batch
        fi &&
        if [ ! -e /root/.sawtooth/keys/my_key.priv ]; then
          sawtooth keygen my_key
        fi &&
        sawtooth-validator -vv \
          --endpoint tcp://validator-0:8800 \
          --bind component:tcp://eth0:4004 \
          --bind consensus:tcp://eth0:5050 \
          --bind network:tcp://eth0:8800 \
          --scheduler parallel \
          --peering static \
          --maximum-peer-connectivity 10000
      "

  validator-1:
    image: hyperledger/sawtooth-validator:nightly
    container_name: sawtooth-validator-default-1
    expose:
      - 4004
      - 5050
      - 8800
    volumes:
      - pbft-shared:/pbft-shared
    command: |
      bash -c "
        if [ -e /pbft-shared/validators/validator-1.priv ]; then
          cp /pbft-shared/validators/validator-1.pub /etc/sawtooth/keys/validator.pub
          cp /pbft-shared/validators/validator-1.priv /etc/sawtooth/keys/validator.priv
        fi &&
        if [ ! -e /etc/sawtooth/keys/validator.priv ]; then
          sawadm keygen
          mkdir -p /pbft-shared/validators || true
          cp /etc/sawtooth/keys/validator.pub /pbft-shared/validators/validator-1.pub
          cp /etc/sawtooth/keys/validator.priv /pbft-shared/validators/validator-1.priv
        fi &&
        sawtooth keygen my_key &&
        sawtooth-validator -vv \
          --endpoint tcp://validator-1:8800 \
          --bind component:tcp://eth0:4004 \
          --bind consensus:tcp://eth0:5050 \
          --bind network:tcp://eth0:8800 \
          --scheduler parallel \
          --peering static \
          --maximum-peer-connectivity 10000 \
          --peers tcp://validator-0:8800
      "

  validator-2:
    image: hyperledger/sawtooth-validator:nightly
    container_name: sawtooth-validator-default-2
    expose:
      - 4004
      - 5050
      - 8800
    volumes:
      - pbft-shared:/pbft-shared
    command: |
      bash -c "
        if [ -e /pbft-shared/validators/validator-2.priv ]; then
          cp /pbft-shared/validators/validator-2.pub /etc/sawtooth/keys/validator.pub
          cp /pbft-shared/validators/validator-2.priv /etc/sawtooth/keys/validator.priv
        fi &&
        if [ ! -e /etc/sawtooth/keys/validator.priv ]; then
          sawadm keygen
          mkdir -p /pbft-shared/validators || true
          cp /etc/sawtooth/keys/validator.pub /pbft-shared/validators/validator-2.pub
          cp /etc/sawtooth/keys/validator.priv /pbft-shared/validators/validator-2.priv
        fi &&
        sawtooth keygen my_key &&
        sawtooth-validator -vv \
          --endpoint tcp://validator-2:8800 \
          --bind component:tcp://eth0:4004 \
          --bind consensus:tcp://eth0:5050 \
          --bind network:tcp://eth0:8800 \
          --scheduler parallel \
          --peering static \
          --maximum-peer-connectivity 10000 \
          --peers tcp://validator-0:8800 \
          --peers tcp://validator-1:8800
      "

  validator-3:
    image: hyperledger/sawtooth-validator:nightly
    container_name: sawtooth-validator-default-3
    expose:
      - 4004
      - 5050
      - 8800
    volumes:
      - pbft-shared:/pbft-shared
    command: |
      bash -c "
        if [ -e /pbft-shared/validators/validator-3.priv ]; then
         cp /pbft-shared/validators/validator-3.pub /etc/sawtooth/keys/validator.pub
         cp /pbft-shared/validators/validator-3.priv /etc/sawtooth/keys/validator.priv
        fi &&
        if [ ! -e /etc/sawtooth/keys/validator.priv ]; then
         sawadm keygen
         mkdir -p /pbft-shared/validators || true
         cp /etc/sawtooth/keys/validator.pub /pbft-shared/validators/validator-3.pub
         cp /etc/sawtooth/keys/validator.priv /pbft-shared/validators/validator-3.priv
        fi &&
        sawtooth keygen my_key &&
        sawtooth-validator -vv \
          --endpoint tcp://validator-3:8800 \
          --bind component:tcp://eth0:4004 \
          --bind consensus:tcp://eth0:5050 \
          --bind network:tcp://eth0:8800 \
          --scheduler parallel \
          --peering static \
          --maximum-peer-connectivity 10000 \
          --peers tcp://validator-0:8800 \
          --peers tcp://validator-1:8800 \
          --peers tcp://validator-2:8800
      "

  validator-4:
    image: hyperledger/sawtooth-validator:nightly
    container_name: sawtooth-validator-default-4
    expose:
      - 4004
      - 5050
      - 8800
    volumes:
      - pbft-shared:/pbft-shared
    command: |
      bash -c "
        if [ -e /pbft-shared/validators/validator-4.priv ]; then
          cp /pbft-shared/validators/validator-4.pub /etc/sawtooth/keys/validator.pub
          cp /pbft-shared/validators/validator-4.priv /etc/sawtooth/keys/validator.priv
        fi &&
        if [ ! -e /etc/sawtooth/keys/validator.priv ]; then
          sawadm keygen
          mkdir -p /pbft-shared/validators || true
          cp /etc/sawtooth/keys/validator.pub /pbft-shared/validators/validator-4.pub
          cp /etc/sawtooth/keys/validator.priv /pbft-shared/validators/validator-4.priv
        fi &&
        sawtooth keygen my_key &&
        sawtooth-validator -vv \
          --endpoint tcp://validator-4:8800 \
          --bind component:tcp://eth0:4004 \
          --bind consensus:tcp://eth0:5050 \
          --bind network:tcp://eth0:8800 \
          --scheduler parallel \
          --peering static \
          --maximum-peer-connectivity 10000 \
          --peers tcp://validator-0:8800 \
          --peers tcp://validator-1:8800 \
          --peers tcp://validator-2:8800 \
          --peers tcp://validator-3:8800
      "

# -------------=== pbft engines ===-------------

  pbft-0:
    image: hyperledger/sawtooth-pbft-engine:nightly
    container_name: sawtooth-pbft-engine-default-0
    command: pbft-engine -vv --connect tcp://validator-0:5050
    stop_signal: SIGKILL

  pbft-1:
    image: hyperledger/sawtooth-pbft-engine:nightly
    container_name: sawtooth-pbft-engine-default-1
    command: pbft-engine -vv --connect tcp://validator-1:5050
    stop_signal: SIGKILL

  pbft-2:
    image: hyperledger/sawtooth-pbft-engine:nightly
    container_name: sawtooth-pbft-engine-default-2
    command: pbft-engine -vv --connect tcp://validator-2:5050
    stop_signal: SIGKILL

  pbft-3:
    image: hyperledger/sawtooth-pbft-engine:nightly
    container_name: sawtooth-pbft-engine-default-3
    command: pbft-engine -vv --connect tcp://validator-3:5050
    stop_signal: SIGKILL

  pbft-4:
    image: hyperledger/sawtooth-pbft-engine:nightly
    container_name: sawtooth-pbft-engine-default-4
    command: pbft-engine -vv --connect tcp://validator-4:5050
    stop_signal: SIGKILL

The code for the transaction processor is below

import sys

from sawtooth_sdk.processor.core import TransactionProcessor
from sawtooth_sdk.processor.handler import TransactionHandler

from transaction_family import TransactionPayload, State, InvalidAction, \
    NAMESPACE, NAME, VERSION

import logging

logger = logging.getLogger(__name__)


class CustomTransactionHandler(TransactionHandler):

    @property
    def family_name(self):
        return NAME

    @property
    def family_versions(self):
        return [VERSION]

    @property
    def namespaces(self):
        return [NAMESPACE]

    # The argument transaction is an instance of the class Transaction that
    # is created from the protobuf definition. Also, context is an instance of
    # the class Context from the python SDK.
    def apply(self, transaction, context):
        logger.error("inside apply")
        header = transaction.header
        signer = header.signer_public_key

        print(transaction.payload)

        transaction = TransactionPayload.from_bytes(transaction.payload)
        state = State(context)

        if transaction.action == 'insert':
            state.insert(transaction.key, transaction.value)
            pass
        elif transaction.action == 'delete':
            state.delete(transaction.key)
            pass
        else:
            raise InvalidAction(transaction.action)


def main():
    processor = TransactionProcessor(url="tcp://localhost:4004")
    processor.add_handler(CustomTransactionHandler())
    processor.start()


if __name__ == '__main__':
    logging.basicConfig(filename='example.log',
                        level=logging.DEBUG)
    logger.setLevel(logging.INFO)
    logger.info("hello")
    main()

Transaction Family module is below:

import hashlib
import sys

import cbor2

NAME = 'custom'
NAMESPACE = hashlib.sha512(NAME.encode('utf-8')).hexdigest()[:6]
VERSION = '1.0'


def generate_address(key):
    return NAMESPACE + hashlib.sha512(str(key).encode('utf-8')).hexdigest()[
                       -64:]


class TransactionPayload:

    def __init__(self, payload):
        action, key, value = cbor2.loads(payload)
        self._action = action
        self._key = int(key)
        self._value = int(value)

    @property
    def action(self):
        return self._action

    @property
    def key(self):
        return self._key

    @property
    def value(self):
        return self._value

    @classmethod
    def from_bytes(cls, payload):
        return cls(payload)


class State:

    def __init__(self, context):
        self._context = context

    def insert(self, key, value):
        address = generate_address(key)
        n_req_bytes = (value.bit_length() + 7) // 8
        self._context.set_state({address: value.to_bytes(n_req_bytes,
                                                         sys.byteorder)})

    def delete(self, key):
        address = generate_address(key)
        self._context.delete_state([address])


class InvalidAction(Exception):
    def __init__(self, msg):
        self._msg = msg

    def __str__(self):
        return self._msg

Finally, the client through which I am trying to access the rest api is below:

import random
from hashlib import sha512
import requests
import cbor2
from sawtooth_sdk.protobuf.batch_pb2 import BatchHeader, Batch, BatchList
from sawtooth_sdk.protobuf.transaction_pb2 import TransactionHeader, Transaction
from sawtooth_signing import create_context
from sawtooth_signing import CryptoFactory
from transaction_family import generate_address, NAME, VERSION
import secrets

context = create_context('secp256k1')
private_key = context.new_random_private_key()
print(private_key)
signer = CryptoFactory(context).new_signer(private_key)


def insert(key, val):
    payload_bytes = cbor2.dumps(['insert', key, val])
    address = generate_address(key)

    txn_header_bytes = TransactionHeader(
        family_name=NAME,
        family_version=VERSION,
        inputs=[address],
        outputs=[address],
        signer_public_key=signer.get_public_key().as_hex(),
        batcher_public_key=signer.get_public_key().as_hex(),
        dependencies=[],
        payload_sha512=sha512(payload_bytes).hexdigest(),
        nonce=secrets.token_hex(16),
        # nonce=hex(random.randint(0, 2**64))
    ).SerializeToString()
    signature = signer.sign(txn_header_bytes)
    txn = Transaction(header=txn_header_bytes,
                      header_signature=signature,
                      payload=payload_bytes)

    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()
    signature = signer.sign(batch_header_bytes)
    batch = Batch(
        header=batch_header_bytes,
        header_signature=signature,
        transactions=txns,
        trace=True
    )

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

    print(batch_list_bytes)

    # send request
    resp = requests.post(
        'http://localhost:8008/batches',
        headers={'Content-Type': 'application/octet-stream'},
        data=batch_list_bytes)

    print(resp)
    print(resp.json())


def main():
    insert(1, 2)


if __name__ == '__main__':
    main()

Upvotes: 1

Views: 321

Answers (2)

Abhi
Abhi

Reputation: 126

I figured out the problem. It was a mistake on my part. I was trying to run only one instance of the transaction processor. However, each node requires an instance of the transaction processor. Once I modified my docker-compose file to include the transaction processor for all the five nodes, it worked as expected.

Posting this answer if anyone else faces a similar issue.

Upvotes: 0

Lampadaire
Lampadaire

Reputation: 21

I see that you use the nightly version for the docker images.

The nightly version is a version that is updated by developers more commonly, it is not necessarily a stable version.

Instead, I suggest you replace nightly with latest, which is a more stable version. I've already had the problem and this is the only way I've found to get your processor to register with validators.

You can find the versions from the docker website: https://hub.docker.com/u/hyperledger

Upvotes: 2

Related Questions