Bitcoin full node and query server

Jan 3, 2025

View in context in the libbitcoin Slack #general mirror.

evoskuil19:16
Our existing tables consist of a head and body, where the head stores the body size and an array of hashmap buckets. For non-hashmap tables (array or blob) the head is just a size. However this index is not a hashmap, so there's some generalization necessary. hashmap heads are implemented using the array template. This is the same template used for array bodies. Each bucket is an offset pointer into the body, identifying the first element of the hashmap conflict list. We can generalize this array-indexed array as a hashmap with no conflicts. That requires that each valid search key has exactly one bucket. This is easy since the search key is the block height. So 850k buckets have 850k pointers into the body, and there is never a need to walk to a second element in the conflict list. The hash function for this would be a pass-through (no-hash) of the block height. This is also something we already do, with the exception that the hash is key modulo bucket count. But in this case we don't want to modulo, we want to increase the bucket count. Bucket count is a runtime (non-template) parameter. It's defined in config and verified at startup against head size. But since it's not a template argument it's an easy change. On verify we allow an increase (decrease still implies a fault) by just updating the bucket count member. This makes the verification a little weaker, since it trusts the increased size, but it's already weak. The check is more of a smoke check, not a true guard. So that's the first change. On write we allow expansion of the head. Heads and bodies are implemented over the same basic mmap wrapper, which we use for this type of expansion in the bodies. However since it never happens in the heads (until now) the remap shared pointer is not necessary, as there is no reason to block global access during a remap. So in this case the remap guards must be in place, especially since writes will be concurrent (coming from the validation stage) as will reads (coming non-concurrently but concurrent with writes). So that's the second change. When we remap we always over allocate, because remap is expensive. Heads are small, and this is with small growth, but still do not want to grow as slow as one word at a time (default behavior would be more random than that due to out of order writes at different heights). So we will have to truncate to logical size on close, which we also do for bodies but may not for heads - not sure. So that's a third thing. snapshot/restore/disk-full are not affected. start/stop are not affected.
(edited)