Cutting blockchain tail with NeoGo

State components and options

The state stored in the DB can be roughly divided into several main categories:

  • contract storage
  • block headers
  • block transactions
  • MPT data
  • transfer data
  • auxiliary data (depends on implementation, but negligible in volume)

Contract storage

Contract storage is the essence of the system. While it grows over time, this growth reflects the use of the system and can’t be avoided. Its growth at the same time is quite modest and mostly associated with new accounts/contracts because shuffling the existing assets between old participants only changes the records in the DB but does not create them.

Block headers

Block headers are always stored in the DB, that’s where the block chain comes from, so they’re crucial. In Neo N3 a header is roughly 700 bytes, so for some 1–2M of blocks it’s not a lot (700–1400 MB), but of course, the number inevitably grows over the years of blockchain life.

Block transactions

What’s more interesting is transactions that are a part of a complete block. Neo N3 has two important protocol parameters related to them: MaxValidUntilBlockIncrement and MaxTraceableBlocks. MaxValidUntilBlockIncrement is tightly related to transaction’s ValidUntilBlock field that makes transaction incorrect after the height specified there; it specifies the maximum ValidUntilBlock value difference from the current blockchain height for a transaction to be considered correct. This is important for transaction validity checks, any new incoming transaction technically should be checked for duplicates only against the set of transactions from the previous MaxValidUntilBlockIncrement number of blocks, older ones just can’t have this transaction. On current Neo public networks (mainnet/testnet) this parameter is set to 5760, which is one day of 15-seconds blocks.

MPT data

Another and in fact very important source of data is Merkle-Patricia Trie that in some ways duplicates contract storage because it stores the same key-value set but in a radically different way. It allows computing a single hash value known as “state root” corresponding to a block. C# node implementation doesn’t have it by default, it’s implemented by the StateService plugin that needs to be installed, but NeoGo team believes that this data is so important (allowing to detect any state inconsistencies between nodes quickly) that MPT is always calculated and stored there without an option to turn it off (yes, it works even when we’re setting new performance records).

Transfer data

Tracking asset transfers is also one of the key things expected from most nodes. C# node provides that with TokensTracker plugin, which tends to be used a lot, while NeoGo has that built-in and always enabled. The log of transfers grows as transactions get accepted and processed, so we can’t ignore it too.

The evolution of RemoveUntraceableBlocks

The main option (not counting MPT-specific KeepOnlyLatest) to deal with old data was introduced in version 0.92.0 of NeoGo. It was named RemoveUntraceableBlocks, and it does exactly what you might expect of it — removes block data that is unreachable (as of MaxTraceableBlocks setting) from the current transactions.


What to expect from these options on public networks? That’s what we wanted to find out, so a test was set up using NeoGo 0.98.3 on two machines using block dumps from mainnet (1508926 blocks) and testnet (1599728 blocks). The test was performing offline synchronization from these dumps using the `db restore` command.

  • Core i7–8565U with 16 GB of memory
  • Ryzen 9 5950X with 64 GB of memory
  • default one (keeps everything)
  • KeepOnlyLatest enabled (only one MPT stored, everything else untouched)
  • RemoveUntraceableBlocks enabled (and default 2M+ MaxTraceableBlocks, for both networks this means that nothing is actually removed, but the DB is prepared for removal at 2M+ heights)
  • MaxTraceableBlocks set to 100K blocks (and RemoveUntraceableBlocks enabled)
  • MaxTraceableBlocks set to 10K blocks (and RemoveUntraceableBlocks enabled)

Mainnet results

  • KeepOnlyLatest (KOL)
  • RemoveUntraceable (RUB)
  • MaxTraceableBlocks (MTB)

LevelDB/Core i7–8565U

BoltDB/Core i7–8565U

LevelDB/Ryzen 9 5950X

BoltDB/Ryzen 9 5950X

Testnet results

LevelDB/Core i7–8565U

BoltDB/Core i7–8565U

LevelDB/Ryzen 9 5950X

BoltDB/Ryzen 9 5950X


First of all, we can clearly see that all options work as expected with respect to the database size, removing all MPTs but one or keeping less and less of a tail makes the DB much more compact. The difference will only grow with time because the chain dumps we used don’t have even one full year of blockchain life.


As of today, RemoveUntraceableBlocks does not change much for public networks. This will definitely change as these chains grow, but private ones with more aggressive MaxTraceableBlocks settings already can win a lot by using it. And KeepOnlyLatest is still there and still is very effective (especially if used with BoltDB). So, all the options are there to slim down your node and they will be more and more relevant in future.



Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store