Multinode / Multiwallet Bitcoin SV regtest network
This repository allows you run a full bitcoin network in an isolated environment. It uses bitcoin SV's regtest capability to setup an isolated bitcoin network, and then uses docker to setup a network with 2 nodes.
This is useful because normally in regtest mode you would generate all coins in the same wallet as where you'd send the coins. With this setup, you can use one node to generate the coins and then send it to one of the other nodes, which can then again send it to another node to simulate more real-life bitcoin usage.
Usage
Simple run
docker-compose up
to start all the containers. This will start the bitcoin nodes, and expose RPC on all of them. The nodes will run on the following ports:
Node | P2P port * | RPC port * | RPC Username | RPC Password |
---|---|---|---|---|
miner1 | 18500 | 18400 | bitcoin | bitcoin |
node1 | 18501 | 18401 | bitcoin | bitcoin |
node2 | 18502 | 18402 | bitcoin | bitcoin |
* Port as exposed on the host running docker.
Samples
Note these samples use curl
to exercise the API, but this would usually be bitcoin-cli
. We're using curl
so we don't have a dependency on bitcoin in the host.
You can also use the b.sh
script passing any parameters. b.sh
makes a call to the bitcoin-cli
on bitcoin-sv-regtest_node1_1
eg ./b.sh getinfo
Initial block count
Checks that the initial block count is 0.
# docker-compose up -d
Creating bitcoindocker_miner_1 ... done
Creating bitcoindocker_node1_1 ... done
Creating bitcoindocker_node2_1 ... done
# curl -d '{"jsonrpc":"2.0","id":"1","method":"getblockcount"}' -u bitcoin:bitcoin localhost:18400
{"result":0,"error":null,"id":"1"}
Check connected nodes
Check the "miner" node is connected to other nodes.
# curl -d '{"jsonrpc":"2.0","id":"1","method":"getpeerinfo","params":[]}' -u bitcoin:bitcoin -s localhost:18400 | jq '.result[] | {addr, inbound} '
{
"addr": "node1:18444",
"inbound": false
}
{
"addr": "172.18.0.3:34908",
"inbound": true
}
{
"addr": "node2:18444",
"inbound": false
}
Mine some blocks and see other nodes are updating their block count
# mine the blocks
# curl -d '{"jsonrpc":"2.0","id":"1","method":"generate","params":[101]}' -u bitcoin:bitcoin -s localhost:18400
{"result":["229e6fc0599e198f6f9bdc04cb9a0d7f494f2c8b36f3cec354c85c2507ae21d2", ...
<snip>
...,"39b792af704cad1f26d2d2e34d91d784b88217b663a6d4eff5512fb6f588d4d5"],"error":null,"id":"1"}
# check on node1
# curl -d '{"jsonrpc":"2.0","id":"1","method":"getblockcount","params":[]}' -u bitcoin:bitcoin -s localhost:18401
{"result":101,"error":null,"id":"1"}
# check on node2
# curl -d '{"jsonrpc":"2.0","id":"1","method":"getblockcount","params":[]}' -u bitcoin:bitcoin -s localhost:18402
{"result":101,"error":null,"id":"1"}
Send bitcoin from miner to another node
Now we're going to generate an address in another node. Note that we use port 18401 (node1) instead of 18400:
# Mine blocks first
# curl -d '{"jsonrpc":"2.0","id":"1","method":"generate","params":[101]}' -u bitcoin:bitcoin -s localhost:18400
{"result":["2929287f219fda71445219269081364373b4bdec73187da92126150feac39cd6",
... <snip> ...,
"2b1ffc0528fac9e759097a9213198b1c8540545b67ea1dc4c0ca68b76f02f8f1"],"error":null,"id":"1"}
# Check balance
# curl -d '{"jsonrpc":"2.0","id":"1","method":"getbalance","params":[]}' -u bitcoin:bitcoin -s localhost:18400
{"result":50.00000000,"error":null,"id":"1"}
# Generate address on node1
# curl -d '{"jsonrpc":"2.0","id":"1","method":"getnewaddress","params":[]}' -u bitcoin:bitcoin -s localhost:18401
{"result":"2N444M93zqwyhhFSDJxgzPL5h9XMdwLUibz","error":null,"id":"1"}
# Send from miner to node1
# curl -d '{"jsonrpc":"2.0","id":"1","method":"sendtoaddress","params":["2N444M93zqwyhhFSDJxgzPL5h9XMdwLUibz", "3.14"]}' -u bitcoin:bitcoin -s localhost:18400
{"result":"008f138f10e80aaae2a5211bf2891ad522a1dd7b85d3f26cbbdabfa63c60ced0","error":null,"id":"1"}
# Now, since the block was not yet mined, we usually don't see the balance yet, unless we specify 0 confirmations.
# First with the default (1) confirmation:
# curl -d '{"jsonrpc":"2.0","id":"1","method":"getbalance","params":[]}' -u bitcoin:bitcoin -s localhost:18401
{"result":0.00000000,"error":null,"id":"1"}
# No coins, so let's try 0 confirmations (the first parameter "" means default account)
# curl -d '{"jsonrpc":"2.0","id":"1","method":"getbalance","params":["", 0]}' -u bitcoin:bitcoin -s localhost:18401
{"result":3.14000000,"error":null,"id":"1"}
# This also means that node1 has it in the mempool, which shows there is exactly one transaction in it
# curl -d '{"jsonrpc":"2.0","id":"1","method":"getmempoolinfo","params":[]}' -u bitcoin:bitcoin -s localhost:18401 | jq .
{
"result": {
"size": 1,
"bytes": 187,
"usage": 1024,
"maxmempool": 300000000,
"mempoolminfee": 1e-05,
"minrelaytxfee": 1e-05
},
"error": null,
"id": "1"
}
# Finally, let's mine the block and see that getbalance will show the balance by default.
# node1:
# curl -d '{"jsonrpc":"2.0","id":"1","method":"getbalance","params":[]}' -u bitcoin:bitcoin -s localhost:18401
{"result":3.14000000,"error":null,"id":"1"}
# miner
# curl -d '{"jsonrpc":"2.0","id":"1","method":"getbalance","params":[]}' -u bitcoin:bitcoin -s localhost:18400
{"result":96.85996240,"error":null,"id":"1"}
# Some extras
# List wallet affecting transactions:
# miner
# curl -d '{"jsonrpc":"2.0","id":"1","method":"listtransactions","params":["", 150]}' -u bitcoin:bitcoin -s localhost:18400 | jq '.result [] | {amount, confirmations, txid, category}'
{
"amount": 50,
"confirmations": 102,
"txid": "7bd5b1c805dc1ec6eab533242f56c0685c713c709e7f3e7cec858773163c7d48",
"category": "generate"
}
{
"amount": 50,
"confirmations": 101,
"txid": "d32be1fce3dd33cb46701cfcaf9de1ddfde9e18dcbe35984799b6341e38a9c53",
"category": "generate"
}
<snip>
{
"amount": 50,
"confirmations": 3,
"txid": "2e3922018911136f8474f741956e45f24fd1d100908bee059aacaaf5d815d4b6",
"category": "immature"
}
{
"amount": 50,
"confirmations": 2,
"txid": "e328e32106d04121ab32300c5e35b3880a21ab3ebecd6c7af34417278bf2da49",
"category": "immature"
}
{
"amount": -3.14,
"confirmations": 1,
"txid": "008f138f10e80aaae2a5211bf2891ad522a1dd7b85d3f26cbbdabfa63c60ced0",
"category": "send"
}
{
"amount": 50.0000376,
"confirmations": 1,
"txid": "dafe13b91d80fd899e627fb481c22bdd10f9d2da13c55ec0b7af7f30175ce96c",
"category": "immature"
}
# node1
# curl -d '{"jsonrpc":"2.0","id":"1","method":"listtransactions","params":["", 150]}' -u bitcoin:bitcoin -s localhost:18401 | jq '.result [] | {amount, confirmations, txid, category}'
{
"amount": 3.14,
"confirmations": 1,
"txid": "008f138f10e80aaae2a5211bf2891ad522a1dd7b85d3f26cbbdabfa63c60ced0",
"category": "receive"
}
# node2
# curl -d '{"jsonrpc":"2.0","id":"1","method":"listtransactions","params":["", 150]}' -u bitcoin:bitcoin -s localhost:18402
{"result":[],"error":null,"id":"1"}
# Try getting the transaction
# node1:
# curl -d '{"jsonrpc":"2.0","id":"1","method":"gettransaction","params":["008f138f10e80aaae2a5211bf2891ad522a1dd7b85d3f26cbbdabfa63c60ced0"]}' -u bitcoin:bitcoin -s localhost:18401 | jq .
{
"result": {
"amount": 3.14,
"confirmations": 1,
"blockhash": "00f3b3923a14a69a78a419660e20d63a4f91370d6099c3bbbdc5daad953e2985",
"blockindex": 1,
"blocktime": 1523421980,
"txid": "008f138f10e80aaae2a5211bf2891ad522a1dd7b85d3f26cbbdabfa63c60ced0",
"walletconflicts": [],
"time": 1523421666,
"timereceived": 1523421666,
"bip125-replaceable": "no",
"details": [
{
"account": "",
"address": "2N444M93zqwyhhFSDJxgzPL5h9XMdwLUibz",
"category": "receive",
"amount": 3.14,
"label": "",
"vout": 0
}
],
"hex": "0200000001487d3c16738785ec7c3e7f9e703c715c68c0562f2433b5eac61edc05c8b1d57b0000000048473044022065b2d5c649bb6882f73654398ebaa9bf29281088493ad56f1ee0400bfcbeb8b30220402fea8148de73c14ba332693f7d5c9e2f2bf4332299a2df87115fdabaf1607b01feffffff028042b7120000000017a914768cc2b85515737b398e2613fd7fe9c3d44f73f187d0a04e170100000017a9146a82eef49043551afd83cb286d801c0725ba24318765000000"
},
"error": null,
"id": "1"
}
# node2, here it fails because that transaction is not in the wallet.
# curl -d '{"jsonrpc":"2.0","id":"1","method":"gettransaction","params":["008f138f10e80aaae2a5211bf2891ad522a1dd7b85d3f26cbbdabfa63c60ced0"]}' -u bitcoin:bitcoin -s localhost:18402 | jq .
{
"result": null,
"error": {
"code": -5,
"message": "Invalid or non-wallet transaction id"
},
"id": "1"
}
# however, using `getrawtransaction` on node2 does actually return it
# curl -d '{"jsonrpc":"2.0","id":"1","method":"getrawtransaction","params":["0c0bd722bc5534ec715e31c70e913e887dcdf6cf15438ed890c1a5b56a631d65", true]}' -u bitcoin:bitcoin -s localhost:18402
{"result":{"txid":"0c0bd722bc5534ec715e31c70e913e887dcdf6cf15438ed890c1a5b56a631d65","hash":"0c0bd722bc5534ec715e31c70e913e887dcdf6cf15438ed890c1a5b56a631d65","version":2,"size":188,"vsize":188,"locktime":101,"vin":[{"txid":"75aa43e3c1f2e4094190339b1d7aa605e06cd5a6fe68db1fcc23ff2f1b54d57d","vout":0,"scriptSig":{"asm":"304502210093f7c7b47eff76fc0e4926bb10942bfb90c8d9f5dc430dd0cb0a191908191cb10220522bcc869bd8fa44e5f8d865c8988da53ae875a05ae3fa3cabe2cf5fe1aa0310[ALL]","hex":"48304502210093f7c7b47eff76fc0e4926bb10942bfb90c8d9f5dc430dd0cb0a191908191cb10220522bcc869bd8fa44e5f8d865c8988da53ae875a05ae3fa3cabe2cf5fe1aa031001"},"sequence":4294967294}],"vout":[{"value":3.14000000,"n":0,"scriptPubKey":{"asm":"OP_HASH160 d5044b6ca6c83a0d0f182dc1f939285d7650bae4 OP_EQUAL","hex":"a914d5044b6ca6c83a0d0f182dc1f939285d7650bae487","reqSigs":1,"type":"scripthash","addresses":["2NCfZ4YFssjjGp5xQjnDMKLFRy8XP4W4TYo"]}},{"value":46.85996240,"n":1,"scriptPubKey":{"asm":"OP_HASH160 b11e110167018be58797913289c0747e1b916879 OP_EQUAL","hex":"a914b11e110167018be58797913289c0747e1b91687987","reqSigs":1,"type":"scripthash","addresses":["2N9PjcHmezjNzi4KAuj1ajKVACYmM7CWw3m"]}}],"hex":"02000000017dd5541b2fff23cc1fdb68fea6d56ce005a67a1d9b33904109e4f2c1e343aa75000000004948304502210093f7c7b47eff76fc0e4926bb10942bfb90c8d9f5dc430dd0cb0a191908191cb10220522bcc869bd8fa44e5f8d865c8988da53ae875a05ae3fa3cabe2cf5fe1aa031001feffffff028042b7120000000017a914d5044b6ca6c83a0d0f182dc1f939285d7650bae487d0a04e170100000017a914b11e110167018be58797913289c0747e1b9168798765000000","blockhash":"1d5df158d1aecf18b893a0f5cd91fe782a0f8b53b028fff3b5892ff9beaf7134","confirmations":101,"time":1523422840,"blocktime":1523422840},"error":null,"id":"1"}
Requirements
To run this you need both docker
and docker-compose
. It was tested on a clean Ubuntu 16.04 with the docker-ce package from Docker.