Bitcoin is a popular cryptocurrency, which is a form of electronic cash, in which a ledger of all transactions is stored as a blockchain. In this article we will show how to compile your own local copy of the Bitcoin client, run it, and add custom commands in order to inspect various attributes of the underlying blockchain.
Compile
First, you will want to clone the Bitcoin source code repository from Github: https://github.com/bitcoin/bitcoin
Then, based on your operating system, you will want to follow the instructions found in the “doc” folder of the Bitcoin repository, where the file starts with “build-” followed by the operating system.
For Mac OS, installing the dependent libraries is very easy using the “homebrew” tool mentioned in the build instructions. Note that after installing libraries with “homebrew” there may be instructions given in the result for more commands to run – make sure you run those.
Building Bitcoin Core on Windows is slightly more complicated since Bitcoin Core is mostly developed on Linux-based or other Unix-like operating systems. Below is a summary of build-windows.md to build Bitcoin Core on Windows 64-bit
- Enable Windows Subsystem for Linux (WSL)
- Install a Linux distribution (e.g. Ubuntu)
- Install dependencies
sudo apt update sudo apt upgrade sudo apt install build-essential libtool autotools-dev automake pkg-config bsdmainutils curl git sudo apt install g++-mingw-w64-x86-64
- Configure for cross-compilation
sudo update-alternatives --config x86_64-w64-mingw32-g++
- Fetch the project with Git
git clone https://github.com/bitcoin/bitcoin.git
- Build the project (this can take a long time, maybe a half an hour or more)
PATH=$(echo "$PATH" | sed -e 's/:\/mnt.*//g') cd depends make HOST=x86_64-w64-mingw32 cd .. ./autogen.sh CONFIG_SITE=$PWD/depends/x86_64-w64-mingw32/share/config.site ./configure --prefix=/ make
- Install the built project
make install DESTDIR=/mnt/c/workspace/bitcoin
Instead of installing you can also go to the build directory directly. This will be in a location like:
`C:\Users\User\AppData\Local\Packages\CanonicalGroupLimited.Ubuntu18.04onWindows_79rhkp1fndgsc\LocalState\rootfs\home\user\bitcoin`
Run
Once the code is built using autogen.sh, configure, and make, you can start the Bitcoin Core with the “./src/bitcoind” command. Bitcoin Core will attempt to download the entire blockchain, which could take a long time and hard drive space. If you are interested in only inspecting a portion of the blockchain, or want to keep the blockchain at a certain point without updating it, you can exit out of the Bitcoin Core while it is downloading, then run “./src/bitcoind -noconnect” to prevent connection to any nodes.
In addition to the `-noconnect` option described above it is also possible to test using two smaller blockchains, testnet and regtest (short for 'regression test'). You can start the client with one of these chains with `bitcoind -testnet` or `bitcoind -regtest` respectively. Both chains are used exclusively for testing, so coins on them have no monetary value. The testnet chain is still pretty big (about 23 GB as of Feb. 2019), but behaves much more closely to the real Bitcoin network. The regtest network is a private chain used in Bitcoin's functional regression tests (written in Python). There are RPC commands available only for regtest, `setgenerate` and `generate` that allow you to quickly generate coins.
Customize
Now that we have a runnable Bitcoin Core and some (or all) of a blockchain downloaded, we can see how to run some commands to inspect the blockchain. If you run “./src/bitcoin-cli help” while the Bitcoin Core is running, you can see a list of available commands. To get the hash of a certain block in the chain, you can run “./src/bitcoin-cli getblockhash <height>” where <height> is the ordinal number of the location of the block in the chain. Then to get detailed data on the block, you can run “./src/bitcoin-cli getblock <hash> <verbosity>” where <hash> is the block hash returned from the previous command and <verbosity> is the amount of detail you want returned (use 2 for the most detail).
The available commands that can be used to inspect the blockchain are nice, but let’s see how we can implement our own commands. The relevant command code can be found in the source file “/src/rpc/blockchain.cpp”. To see the total value of all transactions contained in a specific block in the block chain, we could add the following code:
static UniValue getblocktotalvalue(const JSONRPCRequest& request)
{
// Here we provide a “help” response for usage of the command
if (request.fHelp || request.params.size() != 1) {
throw std::runtime_error(
RPCHelpMan{"getblocktotalvalue",
"\nReturns an Object with information on the total value of transactions in block <hash>.\n",
{
{"blockhash", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "The block hash"},
}}
.ToString() +
"\nResult:\n"
"{\n"
" \"hash\" : \"hash\", (string) the block hash (same as provided).\n"
" \"nTx\" : n, (numeric) The number of transactions in the block.\n"
" \"totalValue\" : x.xxxxxxxx, (numeric) The total value of all the transactions in the block.\n"
"}\n"
"\nExamples:\n"
+ HelpExampleCli("getblocktotalvalue", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\"")
+ HelpExampleRpc("getblocktotalvalue", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\"")
);
}
// The following code was copied from the getblock function as we are performing a similar
// operation
LOCK(cs_main);
uint256 hash(ParseHashV(request.params[0], "blockhash"));
const CBlockIndex* pblockindex = LookupBlockIndex(hash);
if (!pblockindex) {
throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found");
}
const CBlock block = GetBlockChecked(pblockindex);
AssertLockHeld(cs_main);
UniValue result(UniValue::VOBJ);
// Here we declare a variable to hold the total value
double totalValue = 0;
// Now loop through the block’s transactions and each transactions “out” and add each
// amount to the total
for(const auto& tx : block.vtx)
{
for (unsigned int i = 0; i < tx->vout.size(); i++) {
totalValue += tx->vout[i].nValue;
}
}
// Add the total number of transactions and the total value to our result.
result.pushKV("numTxs", (uint64_t)block.vtx.size());
result.pushKV("totalValue", ValueFromAmount(totalValue));
return result;
}
In order to run this command, we just need to add it to the commands[] array at the bottom of the file:
{ "blockchain", "getblocktotalvalue", &getblocktotalvalue, {"blockhash"} },
Once the updates above are made to the source file, go ahead and run the “make” command again to rebuild (it won’t take as long this time around since only one file was modified).
Now let’s try running our new command. First, obtain a block with multiple transactions (this example is on the main blockchain – you will need to make sure you download at least up to block 110000 for this command to return successfully):
“./src/bitcoin-cli getblockhash 110000”
Then run our new command with the resulting hash value:
“./src/bitcoin-cli getblocktotalvalue <hash>”
This returns the total number of transactions in the block (12) and the total value of transactions in the block (754.57000000).
If you would like to verify that the new command is working properly, you can run the following to see each transaction and individual value for the block:
“./src/bitcoin-cli getblock <hash> 2
Now that we have implemented a custom command to derive some information from the blockchain, try implementing your own custom commands to derive any additional data that you might want to from the blockchain.
Below is information about some of the important Bitcoin data types that you might encounter while developing a custom command:
- uint256: Stores 256-bit hashes generated by Bitcoin's hashing algorithm, SHA-256
- CBlockIndex: Used to locate a block within the block chain. Includes the block's hash, a pointer to the block before it, and other fields.
- CBlock: Represents a block itself, including the header and its transactions (in the form of a vector of CTransaction smart pointers)
- CTransaction: A transaction in a block, including inputs, outputs, and hashes
- CTxIn: A single input to a transaction. This will correspond to the output of an earlier transaction.
- CTxMemPool: Stores transactions that are waiting to be added to a mined block.
- Coin: Represents Bitcoin that has not been spent yet
- CAmount: An integer representing a number of satoshis (the smallest unit of Bitcoin, 10^-8)
- UniValue: Represents a JSON value, such as in an RPC response