Part of the reason I started the bchd project (a Bitcoin Cash full node written in Go) is because I’ve had lots of experience using indexing blockchain servers and have intimate knowledge of just how awful most of them are. I wanted to create a server that I wanted use. Hence, bchd was born.

Recently I’ve had the displeasure of rewriting some of the wallet code for my day job and this created an excellent opportunity for a compare and contrast with bchd.

The codebase in question is designed to handle wallets for multiple coins. Hence we needed a blockchain server that could handle multiple coins. We ultimately ended up settling on Trezor’s Blockbook server. It’s more or less a Go version of the Insight server with a mostly compatible Insight API. Though it’s not as crappy as Insight (but not substantially better either, unfortunately).

But while I have no choice but to use Blockbook for BTC, LTC, ZEC, DASH etc, for BCH I’m using the bchd gRPC API which I wrote. Why not right?

In what follows I hope you’ll get the picture of just how much better bchd is than everything else. It’s almost as if the current offerings were written by people who never wrote anything actually using a blockchain server.

GetBlockchainInfo

OK time for API call number one. GetBlockchainInfo is a basic API call which queries the server and tries to get back:

1. The best block hash

2. The best block height

3. The timestamp of the best block

4. The hash of the previous block

In bchd this was dirt simple and took only about 30 seconds. Not only is the bchrpc.proto file the canonical API documentation, but simply importing the client libraries into my project allows my IDE to show me exactly what API calls are available and what data they take in and return.

With Blockbook I spent about 15 minutes hunting down API documentation which is notoriously poor for Insight. Eventually I gave up and just decided to look directly at the Blockbook server code to figure out what API calls are available and how to craft the request. All told it took around 40 minutes to write the following function:

Notice not only is this more lines of code, but because the API call to get the previous block’s hash only accepts a height and not a hash, there is a non-zero probability that it will return the wrong previous hash. If a reorg happens between my API calls, the second API call may return the previous hash for a different block. This is rather unlikely but still non-zero and my code has no way of handling it or even knowing this happened. Again, very poor API design.

IsBlockInMainChain

I have the wallet(s) persist the height and hash of the tip of the chain to disk. On start up it loads the previously known best hash and checks to see if that block is still in the main chain. If it isn’t it assumes a reorg happened while the app was shut down and does a more thorough rescan of its transactions. This is supposed to be a bandwidth saving measure. If no reorg has happened we only need to download transactions that happened since our last known block. But as we will see, due to poor API design, it doesn’t end up saving us any bandwidth at all.

Once again, it took only about 30 seconds to write the bchd function.

But with Blockbook I spent about another half hour looking for an API call similar to bchd’s GetBlockInfo . This should just return some block metadata and that’s it.

But guess what? No such endpoint exists for Blockbook! The closest thing is GetBlock but it returns a full “page” worth of transactions of transactions in addition to the metadata with no option to disable sending transactions. This could be up to 1000 JSON transactions! Did the authors not think a block metadata endpoint would ever be needed?

Here’s the corresponding Blockbook function:

So my bandwidth saving method ends up taking 4.5kB on each start up!

Ultimately I was sure there had to be some kind of workaround for this and I spent another hour pouring over the Blockbook code and found a hacky way to accomplish my goal without using 4.5kB of bandwidth.

But still, great dev experience!

SubscribeTransactions/Blocks

With these API calls I need the server to stream blocks and transactions to me as they come in. Again, bchd handles this functionality really well:

Blockbook, no so much. Blockbook/Insight use socket.io, a protocol layer on top of websockets, for pushing data to subscribers.

The documentation on how to use this is even more poor than the standard REST API. You should be prepared to block out an entire day or longer to figure out how to use this.

In Go it turns out that the main socket.io library on github only implements a socket.io server and not client functionality. For client functionality I had to use a lesser used, unmaintained library. And even here it took me ungodly long to figure out that the the library doesn’t even support formatting data as an array. So guess what? I had to fork the library to put in my patches and now I’m the maintainer of a socket.io client library. Just what I always wanted!

Further, I had to write a custom transport implementation for this library to allow me to proxy socket connections over Tor as that behavior was not part of the library.

So yeah, be prepared to spend days trying to implement streaming blocks and transactions.

So this is just a taste of what cryptocurrency developers have to deal with when using the current blockchain server offerings. They all appear to be written by people who have never actually had to use a blockchain server before.

Thankfully the bchd gRPC API is written by people who have had to go through all of this and it will be a major time saver if you’re developing any Bitcoin Cash applications.