Note: This blog post was originally written on April 2018 and depends on sawtooth-core v1.1.1. The instructions mentioned may not work for newer versions of Sawtooth.
In this blog post, I will try to quickly cover Hyperledger Sawtooth basics and explain how to get to a productive development setup starting from the Sawtooth core codebase.
Hyperledger is an open-source collaborative effort to create industry standard blockchain technologies. It is backed by the Linux Foundation. You can read more about the effort here – https://www.hyperledger.org/about.
This edX course would be a good starting place for anyone interested in Hyperledger – https://www.edx.org/course/blockchain-business-introduction-linuxfoundationx-lfs171x.
Sawtooth is one of the mature projects (1.0 released in Jan 2018) within Hyperledger, initially contributed by Intel. It is designed with Enterprise use-cases in mind. Among other things, Sawtooth introduces a novel consensus algorithm called Proof of Elapsed Time (PoET), has support for permissioned private blockchains, parallel transaction execution, dynamic switching of consensus algorithms, compatibility with Ethereum (via Seth transaction family) etc. It also has development support for a broad language base including Python, Go, Java etc.
Why this post?
Sawtooth is one of the most well documented piece of open-source software that I’ve come across. I would recommend anyone interested in learning Sawtooth to go through it – link.
Having said that, I feel that the level of information in the docs can be a bit daunting, especially for someone new to blockchain. Also, in my case, the fact that I wasn’t very comfortable with docker added an additional layer of indirection/complexity in the learning process. I need to be able to see how something starts up beginning from the code to fully understand how it works.
I couldn’t find any tutorials on Sawtooth outside the documentation as of today. I reached out to the friendly experts hanging out in the Sawtooth Rocket chat channel to clear many issues I came across while trying to play with Sawtooth. This post is my effort to give back to the community, in the form of a basic intro to setting up sawtooth from scratch.
Components

Transaction Processor
The transaction processor maintains all domain specific business logic for a transaction family (similar to smart contracts) such as:
- Serializing/Deserializing the state stored in the ledger to domain objects.
- Providing the business logic for validating transactions
Validator
The validator handles consensus, all blockchain read/writes, peer interaction etc. It is agnostic of the business logic. In fact, one validator process running in a host can be connected to multiple transaction processor processes.
Client
Clients (CLI, web services etc.) interact with a transaction processor via a REST API (a separate process).
If you are interested in playing around with Sawtooth using pre-built images, the installation section of Sawtooth would be helpful – link. You can use docker images, Ubuntu packages or install Sawtooth from the AWS marketplace.
Getting your hands dirty
In this section, I will try to get a working instance of Intkey transaction processor (TP) running locally in Python. Intkey is a sample TP for setting, incrementing and decrementing values in a dictionary. First of all, checkout the sawtooth-core code-base
git clone https://github.com/hyperledger/sawtooth-core
We will still use a provided docker container for running all our processes to save us the trouble of getting the right version of all the dependencies required. In our case, we will use the sawtooth-dev-python image. Also, we will build and use the validator and REST API directly (not from code) as these don’t need to be changed by application developers.
Setup the validator
Build everything (install docker if you haven’t):
$ cd sawtooth-core/docker $ docker build . -f sawtooth-dev-python -t sawtooth-dev-python
Now if you list your docker images, you should see the newly created one:
$ docker images REPOSITORY TAG IMAGE ID CREATED SIZE sawtooth-dev-python latest ecf3ae086754 2 days ago 817MB
Next, let’s start a container and get inside it:
$ cd sawtooth-core
$ docker run -v $(pwd):/project/sawtooth-core -it sawtooth-dev-python bash
This should start a container using the sawtooth-dev-python image and get you to a root prompt prompt within the container with our present working directory (sawtooth-core) mounted as /project/sawtooth-core.
root@43edef3881be:/project/sawtooth-core# ls
Before we start up the validator, we need to do some setup here. Basically create the genesis block and required keys.
# sawtooth keygen # sawset genesis # sawadm genesis config-genesis.batch
Next, let’s start up the validator:
# sawadm keygen # cd bin/ # sawtooth-validator -vv
You would want to keep the validator running for the rest of the steps to work. For that, you will need to connect to the container from a different terminal. For that, you need to find the container id for the container that you just created:
$ docker container ls CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 43edef3881be sawtooth-dev-python "bash" 8 days ago Up 8 days 4004/tcp, 8008/tcp keen_keller
Now, to get into the container from this new terminal, you just need to do the following:
$ docker exec -it 43edef3881be bash
Now, we need to start up the settings transaction processor which is required for the validator to function correctly:
# cd bin/ # settings-tp -vv
You should see some logging in the validator terminal tab/window along these lines now:
[2018-04-12 07:25:27.657 INFO processor_handlers] registered transaction processor: connection_id=f519f4ff0bd1e26968d5bcd76cd71eed88d097c5a4846798c35f5d9c5efeb8845ea65fdf2172c12c7b4a226fc4214c18f6989c4048e0012c2fb5378252d67a08, family=sawtooth_settings, version=1.0, namespaces=['000000'], max_occupancy=10 [2018-04-12 07:25:27.705 INFO genesis] Genesis block created: 69f85216c3137fdc0ee31f8d754dfd134198b6d97f31c45e52ccbb3af356bbbe443aa38ba6a3380a7aab734b939881dbb198367311423b0b8d9aa39569d186eb (block_num:0, state:2cd74660fc59b472699aad3f6da884ae636191673e6773e6d81b9c8987065e9f, previous_block_id:0000000000000000) [2018-04-12 07:25:27.709 INFO interconnect] Listening on tcp://127.0.0.1:8800 [2018-04-12 07:25:27.712 INFO chain] Chain controller initialized with chain head: 69f85216c3137fdc0ee31f8d754dfd134198b6d97f31c45e52ccbb3af356bbbe443aa38ba6a3380a7aab734b939881dbb198367311423b0b8d9aa39569d186eb (block_num:0, state:2cd74660fc59b472699aad3f6da884ae636191673e6773e6d81b9c8987065e9f, previous_block_id:0000000000000000)
There is one more piece required before we can get to our Intkey transaction processor – the REST API. Again, from a new terminal, get into the container and run:
# cd bin/ # sawtooth-rest-api -vv
At this point, you have a functional sawtooth validator. You can play around with it using the sawtooth command. Some examples:
# sawtooth state list ADDRESS SIZE DATA 000000a87cb5eafdcca6a8cde0fb0dec1400c5ab274474a6aa82c12840f169a04216b7 110 b'\n... HEAD BLOCK: "69f85216c3137fdc0ee31f8d754dfd134198b6d97f31c45e52ccbb3af356bbbe443aa38ba6a3380a7aab734b939881dbb198367311423b0b8d9aa39569d186eb"
# sawtooth transaction list TRANSACTION_ID FAMILY VERS SIZE PAYLOAD 5964642adb957ca994f5789e9a5f9930853c20359a8adbc5c18ecf4b338fc9a00f544f42cf17d7cdc79531727fbdb307f4143c8104ec9ed0e5c3557a476cccdb sawtooth_settings 1.0 131 b'\x08\...
You can also directly query the REST API as follows:
# curl http://localhost:8008/blocks { "data": [ { "batches": [ { "header": { "signer_public_key": "0317f01f8958bc6d404d1ffe88770fe927fef63022216f24484906760873501d7f", "transaction_ids": [ "5964642adb957ca994f5789e9a5f9930853c20359a8adbc5c18ecf4b338fc9a00f544f42cf17d7cdc79531727fbdb307f4143c8104ec9ed0e5c3557a476cccdb" ] }.. // trimmed to keep it short
Intkey
The Transaction Processor
You’ll notice that pre-build Intkey transaction processor binaries are also available in the bin folder. However let’s try to run it from directly from the code. The main.py (code link) in the following directory actually starts the example Intkey processor:
/project/sawtooth-core/sdk/examples/intkey_python/sawtooth_intkey/processor/main.py
We need python3 for some of the dependencies. But if you directly try to run main.py from python, you will run into couple of issues. You’ll notice it depends on the sawtooth_sdk module, which in turn depends on sawtooth_signing module. Lets build them first.
// First, lets install sawtooth_signing # cd /project/sawtooth-core/signing/ # python3 setup.py install // Next, lets install sawtooth_sdk # cd /project/sawtooth-core/sdk/python/sawtooth_sdk # python3 setup.py install
Now, we need to tweak the main.py file a bit to work in run-from-source approach.
It tries to import from sawtooth_intkey.processor.handler module, which it will fail to find. The handler.py is however right besides the main.py in the same directory. So lets modify main.py to use that:
- from sawtooth_intkey.processor.handler import IntkeyTransactionHandler + from handler import IntkeyTransactionHandler
At this point, we should be able to run main.py without any errors. But it will exit without doing anything. Thats because no-one is calling the main method. Let’s add a bit of code at the end of the file to do that:
if __name__ == "__main__": main()
Notice that you can modify the file in your host machine itself i.e outside the container. Since the sawtooth-core folder is shared with the container, it will be automatically up-to date with the latest code.
Now, your Intkey transaction processor is ready to roll! You can try some of the commands as follows:
# python3 main.py -V sawtooth-intkey (Hyperledger Sawtooth) version UNKNOWN
Here is how you really start it:
# python3 main.py -vv [2018-04-12 08:09:31.254 INFO core] register attempt: OK
You should see the validator also logging in its terminal the fact that intkey TP connected successfully. Something like:
[2018-04-12 08:09:31.248 INFO processor_handlers] registered transaction processor: connection_id=acf1ed62db9a2e18c88af958532ac7c9857d6db50d951348eabda8ba5e0913e9a4deeb3fa607cbf6897efdc0585559ea38470cfbbe96141668369db16d3f45a5, family=intkey, version=1.0, namespaces=['1cf126'], max_occupancy=10
Intkey CLI
Let’s try running the CLI to interact with the Intkey transaction processor.
You will notice the provided intkey_cli.py file references other files in its directory using sawtooth_intkey.client_cli.xyz path. For it to be able to find these files correctly, lets copy it to the appropriate location:
# cd /project/sawtooth-core/sdk/examples/intkey_python/ # cp sawtooth_intkey/client_cli/intkey_cli.py .
Next, similar to the earlier main.py, we need to add an entry point for the intkey_cli.py. Add the following snippet at the end of the file:
if __name__ == '__main__': main_wrapper()
This completes our setup! Let’s play around with it:
# python3 intkey_cli.py --help usage: intkey_cli.py [-h] [-v] [-V] {set,inc,dec,show,list,generate,load,populate,create_batch,workload} ... optional arguments: -h, --help show this help message and exit -v, --verbose enable more verbose output -V, --version display version information subcommands: {set,inc,dec,show,list,generate,load,populate,create_batch,workload} set Sets an intkey value inc Increments an intkey value dec Decrements an intkey value show Displays the specified intkey value list Displays all intkey values
If you list all the entries in the blockchain now, it should be empty (as we did not insert any):
# python3 intkey_cli.py list
Now, sets try setting and getting a key-value pair:
// Set key 'a' to value 10 # python3 intkey_cli.py set a 10 { "link": "http://127.0.0.1:8008/batch_statuses?id=46f82450d39c6c59968efc83a15669477804ccb9ad569a7b61bebecf6cf55f931e0bd19c4de42926b7ce90b320b3ffe50e411130957b1812f8e4f2a45865c8ed" } //Let's try listing again # python3 intkey_cli.py list a: 10 // Let's try reading the key # python3 intkey_cli.py show a a: 10 // Let's try incrementing 'a' by 2 # python3 intkey_cli.py inc a 2 { "link": "http://127.0.0.1:8008/batch_statuses?id=8d2f5e0ef324ba15a5cd95392cd65caf6df7124f7c06dc0531feb77cfda49f047c765cb96557a3de0f91902e7b2b3212111b997fe663c7b9bb610bd7a7ad6759" } // Show again # python3 intkey_cli.py show a a: 12
You can also do batch import of random keys (for testing, measuring throughput etc.) as follows:
// Create 1 batch file with operations on 5 keys # python3 intkey_cli.py create_batch -K 5 -c 1 Writing to batches.intkey... // Load the batch file # python3 intkey_cli.py load batches: 2 batch/sec: 72.39234705765597 // List the added keys # python3 intkey_cli.py list a: 12 ZmyetF: 30086 ReocMV: 59247 CINQSf: 57819 BWADZo: 39267 RoDdEV: 47475
You should be able to see the corresponding logging for each of these commands in the REST API, transaction processor and validator terminal consoles.
In summary, we started by checking out the sawtooth-core codebase and ended with a docker container running the following:
- Validator
- Settings Transaction Processor
- REST API
- Intkey Transaction Processor (from source)
- Intkey CLI (from source)
This concludes my hands-on overview of working with Hyperledger Sawtooth.
I hope this will be helpful as a good starting place for developers trying to create their own custom transaction families in Sawtooth. In case you run into any issues while following these instructions or have any questions, please let me know. I’ll try to help you out. As I mentioned earlier, the Sawtooth Rocket chat is a good place to get help from experts in case you are stuck.
The sawtooth-dev-python dockerfile is no longer on https://github.com/hyperledger/sawtooth-core. The sawtooth-debug-python file which is there, fails to build, with error message
E: The repository ‘http://repo.sawtooth.me/ubuntu/debug bionic Release’ does not have a Release file.
I tried to use an older release of sawtooth-core, v.1.0.2 (released two weeks before this article was written). It (sawtooth-dev-python) builds fine, but when running the first interesting command (sawtooth keygen), I get the error message
ImportError: No module named ‘sawtooth_cli.protobuf’
Any suggestions?
Hi Chris,
Sorry for the late response. The version of Sawtooth core that was used in this post is v1.1.1.
I have not had the opportunity to work on Sawtooth since I wrote this post. I am not sure what has changed since then because of which you are getting this error.
Sorry for the trouble. I will add a disclaimer to the post mentioning that these instructions might not work for newer versions of Sawtooth.
Thanks,
Rahul