Workchain Manipulator ⚡️ – Bitcoin-only software development

Feb 9, 2022 • Bitcoin Core

Simulate the Bitcoin Network with Core and Docker

For a long time has Bitcoin Core had the ability to run in regtest mode. While the name indicates that this feature is intended for regression testing, it actually serves as a way to create simulated networks – what btcd node runners would know as a simnet.

Test versus simulated networks

In a test network participants not known to each other connect to maintain a pretend blockchain made to resemble the real Bitcoin network in every way possible. These networks are seeded by Core developers and are usually kept alive for a couple of years while users perform their dry runs before moving on to the main chain. There's usually one such network at a time which at the time of writing is called testnet3.

Simulated networks on the other hand are isolated experiments with known participants. Being able to fully control the variables affecting the environment is key for developers who seek to gain insight into the Bitcoin protocol in order to produce robust well-integrated solutions.

Configuring a simulated network

Launching a simulated network can be as simple as appending the -regtest flag to our daemon invocation:

bitcoind -regtest
# 2022-02-08T16:43:58Z Bitcoin Core version v22.0.0 (release build)
# …

Alternatively we can specify this option in the configuration file:

cat bitcoin.conf
regtest=1

We can now connect to our single regtest node and ask for information.

 bitcoin-cli -regtest -getinfo
# {
#   "blocks": 0,
#   "headers": 0,
#   "connections": {
#     "in": 0,
#     "out": 0,
#     "total": 0
#   },
#   "chain": "regtest",
# }

Since we can't have a network with only one node let's spin off another participant. For this we will need to create a new data directory which we will name hal. We will also need to provide alternative ports to avoid collisions with our initial instance.

mkdir hal
bitcoind -datadir=$PWD/hal -regtest -rpcport=18453 -bind=127.0.0.1:18455
# 2022-02-08T16:43:58Z Bitcoin Core version v22.0.0 (release build)

We can now connect our nodes with each other.

bitcoin-cli -datadir=$PWD/hal -regtest -rpcport=18453 addnode 127.0.0.1 onetry
bitcoin-cli -regtest addnode 127.0.0.1:18455 onetry
bitcoin-cli -regtest getnetworkinfo
# {
#   …
#   "connections": 2,
#   "connections_in": 1,
#   "connections_out": 1,
#   …
# }

The connection was made successfully but before we continue – we can already see how with all peers being identified by same loopback address can be confusing. Having to specify alternative ports for each of the nodes' interfaces can also get very messy, very quickly. It would be great if we were able to isolate every instance and run them in an IP network of their own where they each get a unique address with default TCP bindings.

Dockerized bitcoind

For a dockerized version of Bitcoin Core we will build our own images using this Dockerfile definition. Note that the process might take a few minutes as we are compiling the binaries directly from source code – as one should.

git clone https://github.com/craigwrong/docker-bitcoin-core
cd docker-bitcoin-core
docker build --target daemon-wallet -t bitcoind .
docker build --target cli -t bitcoin-cli .

We now have an image for the daemon complete with wallet support as well as one for bitcoin-cli. We can now proceed with creating a network called bitcoin-regtest which will contain all of our Core instances.

docker network create --subnet=10.0.0.0/16 bitcoin-regtest

Before we launch our first instance let's create a simple configuration file in what will be this node's data folder.

mkdir satoshi
cat << EOF >> satoshi/bitcoin.conf
regtest=1
txindex=1
rpcallowip=10.0.0.0/16
[regtest]
addnode=10.0.0.3
rpcbind=0.0.0.0
EOF

Notice that we will attempt to connect to a peer with IP 10.0.0.3. Let's launch this daemon first with IP 10.0.0.2.

docker run --network bitcoin-regtest --ip 10.0.0.2 --name bitcoind-satoshi --rm -t -v $PWD/satoshi:/root/.bitcoin bitcoind

We will now do the analogous thing for Hal's node. The only change is that we will connect it to the previously launched node.

cat << EOF >> hal/bitcoin.conf
regtest=1
txindex=1
rpcallowip=10.0.0.0/16
[regtest]
addnode=10.0.0.2
rpcbind=0.0.0.0
EOF

docker run --network bitcoin-regtest --ip 10.0.0.3 --name bitcoind-hal --rm -t -v $PWD/hal:/root/.bitcoin bitcoind

We should see in the respective consoles whether the peers successfully connected with each other.

Querying nodes

To keep things clean, we want to query our nodes using the CLI from within the virtual network we created using Docker. To do this we will launch an ephemeral container using the bitcoin-cli image we built before. We can grab the cookie file from the corresponding data directory for authentication.

docker run --rm --network bitcoin-regtest -v $PWD/satoshi/regtest/.cookie:/root/.cookie bitcoin-cli -regtest -rpccookiefile=/root/.cookie -rpcconnect=10.0.0.2 -getinfo

This should give us confirmation again that the nodes are talking to each other.

For convenience we can define the aliases bitcoin-satoshi and bitcoin-hal to make it easier to issue further calls.

alias bitcoin-satoshi="docker run --rm --network bitcoin-regtest -v $PWD/satoshi/regtest/.cookie:/root/.cookie bitcoin-cli -regtest -rpccookiefile=/root/.cookie -rpcconnect=10.0.0.2"
bitcoin-satoshi getpeerinfo
# …

We can now play around with mining as well as transacting with real regtest coin! Reach out to me on Twitter if you have questions. I hope this guide can help other devs run their own experiments with the Bitcoin protocol.