Building and Syncing Bitcoin 0.5.0 in 2022

Building and Syncing Bitcoin 0.5.0 in 2022

A couple of weeks ago, Jameson Lopp came to me with a question: Could I figure out how to build old versions of Bitcoin and give him instructions for doing so? This intrigued me, and so I obliged. Thankfully, the task was not particularly difficult as the Gitian build system was in use, and it seemed to work. So I built down to 0.3.20, put the instructions into a repository on GitHub, and called it a day.

A week later, Jameson put out a blog post detailing his attempts to sync these old versions to the main chain. But he noted that we was unable to sync and had gotten stuck at various blocks with all 4 major versions he attempted to sync. This of course, nerd sniped me once again as the failures all looked like the BDB locks problem that had a workaround published in 2013. But Jameson had tried that, and it didn’t work.

First Sync Attempt

So of course the first thing that I had to do was to try syncing an old node myself. I chose to use 0.5.0 as this was the oldest version I could easily build (I didn’t want to deal with the extra steps 0.4 required). As with Jameson, my attempt at syncing got stuck at block 258,354. That confirms that this is a persistent issue with 0.5, not a spurious failure.

The first thought is the BDB locks problem. But instead of jumping to using that solution, I wanted to dig a bit more to figure out what actually happened. Conveniently, in additon to the debug.log file, we also have db.log file which outputs messages from BDB itself. Usually this second log file is empty, but this time it was not:

Lock table is out of available lock entries

This led me to reading BDB’s documentation on the locking subsystem, which led me to the page on configuring sizes where we see the parameter the workaround mentions: set_lk_max_locks. Seeing that, I figured the correct course of action is to do as the workaround suggests and so I created a DB_CONFIG file in the datadir with the line set_lk_max_locks 1000000. I use 1000000 as I figured that a number larger than the suggestion in the workaround will be needed to sync the rest of the blockchain.

Second Attempt

With the parameter set, I restarted bitcoind and found that it still wasn’t syncing! Looking at the debug.log, I kept seeing a few lines get spammed over and over:

socket closed
disconnecting node 127.0.0.1:8333
trying connection 127.0.0.1:8333 lastseen=-429948.4hrs lasttry=-457726.1hrs
connected 127.0.0.1:8333
version message: version 70016, blocks=728263

That’s quite odd. The node is configured to only connect to a local node on this machine. It worked fine earlier, so why is getting disconnected now? I restarted my modern node but turned on network debug logging with -debug=net, where I saw the problem:

2022-03-20T21:50:33Z Header error: Size too large (headers, 5134836 bytes), peer=0
2022-03-20T21:50:33Z disconnecting peer=0

It seems that 0.5.0 pre-dates the implementation of a maximum size, currently 4 MiB, for p2p protocol messages. This is a headers message responding to a getheaders message from the modern node. Due to a bug in how 0.5.0 responds to getheaders messages, it ends up trying to respond with a significant portion of the blockchain that it knows about, and this exceeds the size limit. Resolving this is simple - remove the protocol message limit on my modern node.

On my modern node, I applied the following diff:

diff --git a/src/net.cpp b/src/net.cpp
index 955eec46e3..001283dc71 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -749,7 +749,7 @@ int V1TransportDeserializer::readHeader(Span<const uint8_t> msg_bytes)
     }
 
     // reject messages larger than MAX_SIZE or MAX_PROTOCOL_MESSAGE_LENGTH
-    if (hdr.nMessageSize > MAX_SIZE || hdr.nMessageSize > MAX_PROTOCOL_MESSAGE_LENGTH) {
+    if (hdr.nMessageSize > MAX_SIZE) {
         LogPrint(BCLog::NET, "Header error: Size too large (%s, %u bytes), peer=%d\n", SanitizeString(hdr.GetCommand()), hdr.nMessageSize, m_node_id);
         return -1;
     }

And now I could continue syncing without having to resync blocks.

Third Attempt

With the p2p problem out of the way, I figured the sync would continue as I have also increased the BDB locks. But to my surprise, it did not. This time though, the db.log had something different to say:

Lock table is out of available object entries

Looking back at the BDB documentation, there’s another option that mentions “objects” - set_lk_max_objects. I added set_lk_max_objects 1000000 to my DB_CONFIG and restarted.

Fourth Attempt

Finally I am able to get past block 258,354. In fact, this attempt was able to sync the entire blockchain! I just had to wait about two weeks.

Conclusion

0.5.0 was released in November 2011. It’s remarkable that Bitcoin has actually maintained backwards compatibility over the past 10 years. Furthermore, this is proof that Bitcoin has indeed only had soft forks since 2011.

Overall, this took two weeks in order to sync the blockchain. While 0.5.0 did not end up requiring multiple minutes to validate a single block, around block 395,000, each block started to take multiple seconds. This slowdown persisted through to the chain tip and ended up making the sync extraordinarily slow. In the end, I was observing four seconds per block.

Besides the long time it took to sync the blockchain, 0.5.0 also ended up temporarily having a much larger datadir than a modern Bitcoin node. I observed that, while it was running, it consumed 800 GB of disk space. It likely would have consumed more if I had not had to restart it because my computer ran out of disk space. But it turns out that a lot of that disk space is freed when the node shuts down. This is because the database uses journaling and those journal files take up 400+ GB. On shutdown, those journal files are compacted into the main database and cleaned up, freeing a ton of space.

The 0.5.0 datadir ended up being 400 GB after it was shut down when syncing completed. 301 GB of that are the block data, stored in blk*.dat files. Interestingly, this is less than the 372 GB of block data for my modern node. This discrepancy is of course due to witness data which 0.5.0 does not download. The modern node also stores “undo data” in the rev*.dat files, which conssume another 52 GB. However 0.5.0’s databases are far larger, at 99 GB, whereas the modern node only requires 4.6 GB for the chainstate, and 35 GB for the transaction index (which is optional but I have enabled anyways). So while modern nodes require a little bit more storage than 0.5.0, they still have more compact databases (which also do not grow to insane sizes while running) and sync far faster.

0.5.0 is certainly not the oldest Bitcoin software, it was just the most convenient old version for me to test. This experiment should be repeated in the future with even older versions to test how compatible Bitcoin really is.