Reputation: 126
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
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
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