Running Your Own Node
When building an application, the primary way of interacting with the Bitcoin network will be through a node that acts as an “edge router”. This “edge router” speaks the wire-level protocol of the Bitcoin network on one end, and exposes application-friendly APIs on the other. While it might be the case that for most uses the running of one’s own “edge router” or “listening node” is not desirable or required, there do exist integration scenarios where this would be the preferred way of interacting with the network. The decision will be left up to the individual developer.
The Bitcoin Client Node can act as an “edge router” for your application.
Architecture
Our Dart application architecture for directly connecting to a Bitcoin Node has hit a snag.
There are unfortunately, at the time of writing, no Dart bindings for ZeroMQ available. The Dart FFI (Foreign Function Interface) is in Beta as of Dart V2.7.x. It is hoped that in future a FFI C-language binding to libzmq will emerge for us to use. In the event that one would like to listen to and respond to ZeroMQ notifications, and alternative solution to a Dart server-side application should be considered. We will make up for this shortcoming with different architectures in subsequent chapters.
While we will discuss the proper configuration and setup of a Bitcoin Node for zeroMQ, we will be unable to build a Dart code example in this chapter.
Setup / Installation
You should be familiar by now with operating a local running instance of the Bitcoin Client Node, including how to configure it. Refer to the section on Getting Started for a refresher.
Connect
RPC Interface
Configuration
In general, you should already have the JSON-RPC configuration setup, since that is the interface that the bitcoin-cli program uses to interact with the Bitcoin Client Node. The specific sections of the configuration file that deal with the RPC interface are:
# Server setting to turn on RPC (required for bitcoin-cli to work)
server=1
rpcbind=127.0.0.1
rpcport=18332
whitelist=127.0.0.1
rpcworkqueue=128
rpcthreads=128
rpctimeout=220
JSON-RPC Config Settings
This is the list of settings that control the RPC server. These settings should be added/modified inside the bitcoind.conf configuration file.
server
Accept command line and JSON-RPC commands
rest
Accept public REST requests (default: 0)
rpcbind=<addr>
Bind to given address to listen for JSON-RPC connections. Use
[host]:port notation for IPv6. This option can be specified
multiple times (default: bind to all interfaces)
rpccookiefile=<loc>
Location of the auth cookie (default: data dir)
rpcuser=<user>
Username for JSON-RPC connections
rpcpassword=<pw>
Password for JSON-RPC connections
rpcauth=<userpw>
Username and hashed password for JSON-RPC connections. The field
<userpw> comes in the format: <USERNAME>:<SALT>$<HASH>. A
canonical python script is included in share/rpcuser. The client
then connects normally using the
rpcuser=<USERNAME>/rpcpassword=<PASSWORD> pair of arguments. This
option can be specified multiple times
rpcport=<port>
Listen for JSON-RPC connections on <port> (default: 8332 or testnet:
18332)
rpcallowip=<ip>
Allow JSON-RPC connections from specified source. Valid for <ip> are a
single IP (e.g. 1.2.3.4), a network/netmask (e.g.
1.2.3.4/255.255.255.0) or a network/CIDR (e.g. 1.2.3.4/24). This
option can be specified multiple times
JSON-RPC via the CLI
The RPC cookie file contains a username and password combination that is formatted as : “[username]:[password]”. In REGTEST mode you can find the cookie file in $BITCOIN_HOME/data/regtest/.cookie.
cat $BITCOIN_HOME/data/regtest/.cookie
__cookie__:9612263a3c3e8dbe0fedc143baa7717ca56f2888cea9c8c24bc1dfa956bfbf63
There are quite a lot of RPC commands. The best current way of getting to know these would be to interrogate the bitcoin-cli program directly. We can start by asking for the list of RPC commands that are supported. Please substitute user and password as appropriate for your own installation.
bitcoin-cli -conf=data/bitcoind.conf -rpcuser=<user> -rpcpassword=<rpcpassword> -rpcport=18332 help
To get information on a specific command, including a helpful curl example
bitcoin-cli -conf=data/bitcoind.conf -rpcuser=<user> -rpcpassword=<rpcpassword> -rpcport=18332 getwalletinfo help
.
.
.
Examples:
> bitcoin-cli getwalletinfo
> curl --user myusername --data-binary '{"jsonrpc": "1.0", "id":"curltest", "method": "getwalletinfo", "params": [] }' -H 'content-type: text/plain;' http://127.0.0.1:8332/
Substituting for our actual credentials from the .cookie file, and piping through the jq command to render our json nicely we get
curl --user __cookie__:9612263a3c3e8dbe0fedc143baa7717ca56f2888cea9c8c24bc1dfa956bfbf63 --data-binary '{"jsonrpc": "1.0", "id":"curltest", "method": "getwalletinfo", "params": [] }' -H 'content-type: text/plain;' http://127.0.0.1:18332/ | jq
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 422 100 345 100 77 26538 5923 --:--:-- --:--:-- --:--:-- 30142
{
"result": {
"walletname": "wallet.dat",
"walletversion": 160300,
"balance": 0,
"unconfirmed_balance": 0,
"immature_balance": 0,
"txcount": 0,
"keypoololdest": 1581753212,
"keypoolsize": 1000,
"keypoolsize_hd_internal": 1000,
"paytxfee": 0,
"hdmasterkeyid": "c6c336722ac7101991837673cd94068a40cee46c"
},
"error": null,
"id": "curltest"
}
Notifications
ZeroMQ is not enabled within the node by default. It has to be explicitly enabled through configuration parameters in data/bitcoind.conf. The following configuration settings apply to zeroMQ:
# The address bitcoind will listen for new ZeroMQ connection requests.
zmqpubrawtx=tcp://127.0.0.1:28332
zmqpubrawblock=tcp://127.0.0.1:28332
zmqpubhashtx=tcp://127.0.0.1:28332
zmqpubhashblock=tcp://127.0.0.1:28332
There is unfortunately no commandline utility that allows us to receive ZeroMQ notifications. Consider this information a placeholder for now. We will make use of it in a later chapter.
Code Example
JSON-RPC
Making a JSON-RPC call is rather simple. We can simply translate the curl example we are given by the bitcoin-cli command’s help system into the appropriate HTTP client code within Dart. Note that you will need to adapt the following code to account for your own node’s username and password as found in the .cookie file.
/*
Use JSON-RPC to retrieve local test node's wallet information
*/
import 'dart:convert';
import 'package:dartsv/dartsv.dart';
import 'package:http/http.dart' as http;
Future<String> getWalletInfo() async{
var url = 'http://localhost:18332';
var client = http.Client();
var username = '__cookie__';
var password = '9612263a3c3e8dbe0fedc143baa7717ca56f2888cea9c8c24bc1dfa956bfbf63';
var authHeader = base64.encode(utf8.encode('${username}:${password}'));
var bodyParams = jsonEncode({
"jsonrpc": "1.0",
"id":"twostack",
"method": "getwalletinfo",
"params": []
});
var response = await client.post(url, headers: {
'Authorization' : 'Basic ${authHeader}'
}, body: bodyParams);
print(response.statusCode);
print(response.body.toString());
return response.body.toString();
}
Let’s do something a little more ambitious. Let’s reuse our code from when we created a P2PKH transaction earlier. If you recall, we had to copy-paste the raw transaction into our code. Let’s update our code, so that instead of taking a raw transaction, it will instead use a Transaction ID.
- Generate a coinbase output with some fake “mining”, and send some of those coins to our local node’s bitcoin wallet. When we run the sendtoaddress command a Transaction ID will be printed on the commandline. We’ll grab that.
bitcoin-cli -conf=data/bitcoind.conf -rpcuser=__cookie__ -rpcpassword=9612263a3c3e8dbe0fedc143baa7717ca56f2888cea9c8c24bc1dfa956bfbf63 -rpcport=18332 generate 101
[
.
.
"7ed1eedfbc33c4defcd3a62646510b037b83de529a57be96083f968b4ad34058"
]
bitcoin-cli -datadir=bin/bitcoin-sv-1.0.0/data sendtoaddress mz7ZQ1XWBc65GrgpsG3djwd3cJFyaksKdz 100
564499f42ba76bb2b19f12c1e4292bc0eebd1e024ab5653c9889c59b07fa2286 <--- Transaction ID to Grab
- Now we want to get the appropriate curl command that corresponds to the “getrawtransaction” JSON-RPC call.
----
Let's get the appropriate curl command to
bitcoin-cli -conf=data/bitcoind.conf -rpcuser=__cookie__ -rpcpassword=9612263a3c3e8dbe0fedc143baa7717ca56f2888cea9c8c24bc1dfa956bfbf63 -rpcport=18332 getrawtransaction
.
.
.
Examples:
> bitcoin-cli getrawtransaction "mytxid"
> bitcoin-cli getrawtransaction "mytxid" true
> curl --user myusername --data-binary '{"jsonrpc": "1.0", "id":"curltest", "method": "getrawtransaction", "params": ["mytxid", true] }' -H 'content-type: text/plain;' http://127.0.0.1:8332/
And as a last step we combine the two above bits of information, and write the code to create a spending transaction just like we did before.
/*
Use JSON-RPC to retrieve local test node's wallet information
*/
import 'dart:convert';
import 'package:dartsv/dartsv.dart';
import 'package:http/http.dart' as http;
Future<String> getRawTransaction(String transactionId) async{
var url = 'http://localhost:18332';
var client = http.Client();
var username = '__cookie__';
var password = '9612263a3c3e8dbe0fedc143baa7717ca56f2888cea9c8c24bc1dfa956bfbf63';
var authHeader = base64.encode(utf8.encode('${username}:${password}'));
var bodyParams = jsonEncode({
"jsonrpc": "1.0",
"id":"twostack",
"method": "getrawtransaction",
"params": [transactionId, true]
});
var response = await client.post(url, headers: {
'Authorization' : 'Basic ${authHeader}'
}, body: bodyParams);
print(response.statusCode);
print(response.body.toString());
var jsonResponse = response.body.toString();
var resultMap = jsonDecode(jsonResponse) as Map<String, dynamic>;
//Error handling is left as an exercise to the reader ( *evil grin* )
return resultMap["result"]["hex"];
}
Future<String> jsonRPCWithP2PKH( ) async {
//Let's reconstruct our Private Key
var privateKey = SVPrivateKey.fromWIF("cVVvUsNHhbrgd7aW3gnuGo2qJM45LhHhTCVXrDSJDDcNGE6qmyCs");
//Creates Address : "mu4DpTaD75nheE4z5CQazqm1ivej1vzL4L"
var recipientAddress = privateKey.toAddress(); // my address
var changeAddress = Address("n3aZKucfWmXeXhX13MREQQnqNfbrWiYKtg"); //bitcoin-cli wallet address
//Create a Transaction instance from the RAW transaction data created by bitcoin-cli.
//Remember that this transaction contains the UTXO we are interested in
var rawTx = await getRawTransaction('564499f42ba76bb2b19f12c1e4292bc0eebd1e024ab5653c9889c59b07fa2286');
var txWithUTXO = Transaction.fromHex(rawTx);
//Let's create the set of Spending Transaction Inputs. These Transaction Inputs need to refer to the Outputs in
//the Transaction we are spending from.
var utxo = txWithUTXO.outputs[0]; //looking at the decoded JSON we can see that our UTXO is at vout[0]
//1. where is the money coming from - (spendFromOutput())
//2. where is the money going to - (spendTo())
//3. where should we send what's left over as difference between (1) and (2) - (sendChangeTo())
//4. how much are we willing to pay the miners - (withFeePerKb())
var unlockBuilder = P2PKHUnlockBuilder(privateKey.publicKey);
var transaction = new Transaction()
.spendFromOutput(utxo, Transaction.NLOCKTIME_MAX_VALUE, scriptBuilder: unlockBuilder) //implicitly creates correct Inputs in spending transaction
.spendTo(recipientAddress, BigInt.from(50000000), scriptBuilder: P2PKHLockBuilder(recipientAddress)) //spend half a bitcoin == 50 million satoshis (creates Outputs in spending transaction)
.sendChangeTo(changeAddress, scriptBuilder: P2PKHLockBuilder(changeAddress)) // spend change to myself
.withFeePerKb(1000); //how much we are willing to pay the miners
//Sign the Transaction Input
transaction.signInput(0, privateKey, sighashType: SighashType.SIGHASH_ALL | SighashType.SIGHASH_FORKID);
print(transaction.serialize());
}
Send it !
The JSON-RPC interface also contains a sendrawtransaction method. As an exercise, use the methods learnt above to send the transaction directly to the local node, rather than using bitcoin-cli.