Bitcoin full node and query server

November 2024

Archive of the libbitcoin Slack #general channel.

Friday, November 1, 2024

evoskuil04:59
Running the big windows box validating sync again. 45% complete and on a constant per tx pace to finish in 5.25 hours. With high thread priority validation and a normal priority download with window (50k blocks), validation keeps pace with download. RAM is also well managed, staying below 50%. SSD only gets moving for periodic flush, since validation is still just reading from and writing to RAM.
image.pngimage.png
evoskuil05:17
With more than half the chain downloaded and validated, only 136 GB RAM being retained with periodic flush. This implies that the validation reads (block and prevouts) are primarily hitting close to the downloads (proximal) in RAM. Example of a flush resulting from a map expansion Disk 1 (M:)...
image.png
And a larger one, which drains a large chunk of RAM:
image.png
evoskuil06:30
image.png
evoskuil07:15
Memory finally became fully occupied by block 800k, with SSD no longer idle.
image.png
And with download completion preceding validation by about 50k blocks, more CPU time available for last window, causing a drop in validation cost (also probably from lack of taproot). Aside from that drop, the entire run was almost perfectly linear in per tx rate.
image.png
evoskuil07:26
4.9 hours, including store build, full download with concurrent validation from block 1 to 850,000, final snapshot, and shutdown. No shani, no avx512, 3 year old secp256k1, windows, on a 2Gbps dn, 40Mbps up cable internet. I also was working on the same PC the whole time, which remained sufficiently responsive.

Saturday, November 2, 2024

evoskuil01:48
I completed the confirmation stage, following the end of the validation stage, and overlaid the graphs. Total confirmation time was 1.3 hours (this still requires optimization). So the total time is download/validation (concurrent) of 4.9 hours plus confirmation of 1.3 hours, so 6.2 hours total download/validation/confirmation from block 1 through block 850,000, with full indexing (including transactions and all spends). Because there was a restart for configuration to trigger the confirmation stage, the confirmation run was cold, which added some extra cost. I'd say on the big machine our full run should be about 6 hours when the configuration is fully automated - before any further optimizations.
(edited)
image.png
A
Antoine Poinsot02:29
Hi, reading the scrollback tonight, i'm confused. All this time when you were talking about sync you only meant essentially "block download"?
So in your original tweet you meant that BN downloaded the blocks in 61 minutes? I had interpreted it as performing both download and confirmation steps since you compared it to Bitcoin Core.
No validation because assumevalid , but still confirmation. Otherwise you are not comparing apple to apple with Core.
evoskuil03:29
You understood correctly originally.
As there is no need for validation, there are no confirmability checks. During download and basic checks and storage of a milestone chain, all necessary confirmation records are also created.
A
Antoine Poinsot03:35
So the node is directly usable, for instance in your original 61min benchmark it would be able to process a 840,001th block and fully download+validate+confirm it?
2 replies · evoskuil · last Nov 02, 2024
evoskuil04:23
One of the optimizations we implemented for checkpoint and milestone runs (slightly differently in each case) was to store strong_tx records in the thread where downloads are stored. This allows that to be performed concurrently, which is a material savings depending on platform. For checkpoints these records are logically irreversible. Milestone block validity is logically irreversible, but a milestone chain (or part of it) may not be a strong chain. So the reorganization of a milestone block reverses the strong_tx association. Since milestone block validity is irreversible, the status as a milestone is stored with the block header. Checkpoints don't require this storage, since they are identifiable by height alone (once headers have been validated, which precedes any storage). Consequently it is the case that a milestone block may end up in a branch that does not actually contain the configured milestone (though must have at one point). In other words, a block that is valid under milestone necessarily remains valid in any other context. This observation simplifies the reorg of a milestone branch, since blocks below the fork point are unaffected by the reorg (i.e. do not have to be validated).
evoskuil05:09
The final step is confirmation (as opposed to confirmability checks). strong_tx is associated for a block before the confirmability checks for that block occur. This allows the block to be identified as part of the candidate chain under check without identifying the candidate blocks above it as so. But confirmation is the process of reorganizing a confirmable candidate chain into the confirmed height index. Not until that point is any block in the candidate chain considered confirmed (despite being and "strong" and "confirmable"). Confirmable (unlike the strong association) is a terminal state. So a confirmable block never needs to be revalidated (i.e. for reorganization). The final step of confirmation is not necessary (or even used) for validating subsequent blocks, as that is all performed in the candidate chain, and organizing an entire 850k blocks as confirmed (push_confirmed) under milestone takes only a couple seconds.
evoskuil03:36
yes
But when there is no milestone, all consensus checks must occur, and we split that into validation and confirmation - the two stages I reported above.
A
Antoine Poinsot03:38
Ok, thanks.
👍🏻 1
evoskuil03:56
I just fired up BC v28 on the same machine, for a full validation run. In order to get as close as possible I configured the following. In my BN validation run I disabled all checkpoints (though I don't think this is really appropriate for any benchmarking, because they are consensus). I don't see them as configurable in BC (which is fine, because it doesn't make forks configurable like BN). So I just left that and set assumevalid to genesis. I set dbcache to the max value documented in the config file (this is a 256GB RAM machine). BN was configured to maintain 200 connections, but I don't think that setting that above the default of 125 for BN would be helpful for it, possibly detrimental - so left that. txindex is on and listening, txs, wallet are all off (same as BN). There's no way to get BN's level of indexation out of BC (I don't think "coins" is comparable, have to look into that). Also on this machine the hardware is not shani and is limited to av2, so that results in the same capability for both BN and BC.
#genesis
assumevalid=000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f
blocksonly=1
dbcache=16384
txindex=1
listen=0
#maxconnections=125 (default)
disablewallet=1
1 reply · evoskuil · last Nov 02, 2024
evoskuil04:04
default of 125 for BN
for BC
evoskuil04:02
Resource utilization after 35 minutes is very low, and it's passing block 170k (still not reached the top checkpoint despite no validation being required).
image.png
evoskuil04:23
replied to a thread: view
One of the optimizations we implemented for checkpoint and milestone runs (slightly differently in each case) was to store strong_tx records in the thread where downloads are stored. This allows that to be performed concurrently, which is a material savings depending on platform. For checkpoints these records are logically irreversible. Milestone block validity is logically irreversible, but a milestone chain (or part of it) may not be a strong chain. So the reorganization of a milestone block reverses the strong_tx association. Since milestone block validity is irreversible, the status as a milestone is stored with the block header. Checkpoints don't require this storage, since they are identifiable by height alone (once headers have been validated, which precedes any storage). Consequently it is the case that a milestone block may end up in a branch that does not actually contain the configured milestone (though must have at one point). In other words, a block that is valid under milestone necessarily remains valid in any other context. This observation simplifies the reorg of a milestone branch, since blocks below the fork point are unaffected by the reorg (i.e. do not have to be validated).
evoskuil05:09
replied to a thread: view
The final step is confirmation (as opposed to confirmability checks). strong_tx is associated for a block before the confirmability checks for that block occur. This allows the block to be identified as part of the candidate chain under check without identifying the candidate blocks above it as so. But confirmation is the process of reorganizing a confirmable candidate chain into the confirmed height index. Not until that point is any block in the candidate chain considered confirmed (despite being and "strong" and "confirmable"). Confirmable (unlike the strong association) is a terminal state. So a confirmable block never needs to be revalidated (i.e. for reorganization). The final step of confirmation is not necessary (or even used) for validating subsequent blocks, as that is all performed in the candidate chain, and organizing an entire 850k blocks as confirmed (push_confirmed) under milestone takes only a couple seconds.
evoskuil05:29
BC in a full validation run (with checkpoints) passed the top checkpoint (block 295,000) at 1.2 hours. No block validation is required until the following block. At 2 hours almost at block 400,000 and reporting 10 hour to complete, still with very low resource utilization. It appears (at least up until this point), that the download is slow enough that adding validation to the unused CPU is of no consequence to total time.
(edited)
evoskuil11:34
BC still reporting 10 hours to go, after 7 hours. Almost no resource utilization. I expected to start seeing more CPU utilization, but the bandwidth utilization is so low and the hardware thread count (64) is so high that it barely registers.
image.pngimage.png
evoskuil11:40
I was concerned that it was using default config, so verified that it is reporting my assumevalid configuration, and it's not. Then I located this line in the debug.log:
2024-11-02T03:22:55Z Config file: M:\Core\Data\bitcoin.conf (not found, skipping)
So this is a default config assumevalid run. I didn't move what is apparently the sample (not active) bitcoin.conf in the installation directory into the Data directory.
(edited)
image.png
evoskuil11:49
I'll let this finish because I don't have a BC baseline for the big box. Then run it again with full configuration. It's actually running materially slower than the mini, either based on individual CPU speed (despite having 8 times as many dual cores) or lower RAM/SSD speed (PCIe3 vs. PCIe4). Certainly not RAM amount (256GB vs 32GB).
evoskuil18:16
55% complete after 13 hours, showing 15 hours to go (assumevalid). I suspect the lack of concurrency combined with the lack of shani is what's making this materially worse than the $350 mini (also Windows).
1 reply · evoskuil · last Nov 02, 2024
evoskuil20:42
Really this just amounts to lack of shani, which effectively amounts to individual CPU speed.
evoskuil20:29
70% complete after 17 hours, showing 13 hours to go.
😰 1
evoskuil20:45
It's jumping around a bit, though bandwidth is solid. My money is on 24 hours.
evoskuil22:02
It's sped up a bit, maybe reduced segwit hashing. 18.7 hours and 5-6 to go.
evoskuil22:09
image.png

Sunday, November 3, 2024

evoskuil03:20
24 hours past now, and reporting 1-2 more to go.
evoskuil12:44
Bitcoin Core took 24.6 hours to sync to block 850,000 with default configuration (assumevalid for all), on:
Dell, Windows 10
32 Dual 2.1GHz cores
256GB 2400MHz RAM
4TB 7,450 MB/s SSD
PCIe3
AVX2, no SHANI
2.3Gps down / 40Mbps up Internet (measured)
On the same machine libbitcoin's Bitcoin Node (BN) completes to the same point and result in 57-61 minutes (with both full tx and spender indexes). BN storage is about 25 GB less than BC with txindex enabled, and 25 GB higher without it. BN with database.minimize enabled is another 50GB smaller (through input point consolidation), though neither were enabled in these runs.
(edited)
👀 1💥 2
On another Windows (11) machine with PCIe4 and SHANI, BN completes slightly faster and BC completes in 14 hours. It appears that lack of SHANI adds the 10.6 hours to the Windows 10 box run. This emphasizes how dependent BC is on single thread performance, as BN does not have SHANI implemented, but it's of little to no consequence when the run is not CPU bound.
✔️ 1
evoskuil13:04
Just started a full validation run on the monster Windows 10 box described above. Verified the configuration early this time 🙂. BN completes the equivalent run (but with also full spender indexing and full validation of all checkpointed blocks) in 6 hours on the same box. Of course I don't expect this to complete in under 24 hours, but it will be interesting to see if it takes any longer than the assumevalid run, given that there are at least 63 free threads to validate. My theory is that it won't take much longer.
2024-11-03T12:57:37Z Using data directory M:\Core\Validate
2024-11-03T12:57:37Z Config file: M:\Core\Validate\bitcoin.conf
2024-11-03T12:57:37Z Config file arg: assumevalid="000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
2024-11-03T12:57:37Z Config file arg: blocksonly="1"
2024-11-03T12:57:37Z Config file arg: dbcache="16384"
2024-11-03T12:57:37Z Config file arg: disablewallet="1"
2024-11-03T12:57:37Z Config file arg: listen="0"
2024-11-03T12:57:37Z Config file arg: txindex="1"
2024-11-03T12:57:37Z Using at most 125 automatic connections (2048 file descriptors available)
2024-11-03T12:57:37Z Wallet disabled!
(edited)
evoskuil21:49
Well, my guess was way off. After 6 hours BC has downloaded/validated/confirmed 500,000 blocks, whereas it previously downloaded (under assumevalid) 500,000 blocks in 4.5 hours. At that rate it would complete at a total of 32.7 hours. Even with full blocks it's only using ~15% CPU. So I'm going to stop this run, reset the automatically-determined validation thread count from 16 (15 additional) to 64 (63 additional), and restart.
2 replies · evoskuil · last Nov 04, 2024
evoskuil05:04
So, according to its config, I can't give BC more threads. I can't imagine why, but it says this in the config file ("up to 15"):
# Set the number of script verification threads (0 = auto, up to 15, <0 =
# leave that many cores free, default: 0)
#par=<n>
evoskuil12:51
Hmm, maybe if set to -1 I can at least get it to set 63 threads (or 31 cores for 62 threads if it really means “cores”.)
evoskuil22:08
Interestingly, Core took forever to shut down. over 5 minutes. This was a problem in BN in previous prototyping, like 5-7 years ago. In the current design, based on that experience of protracted shutdowns, we made a significant effort to ensure that nothing apart from the necessary mmap flush would delay stop. mmap flush on this machine can be significant, as there is 256GB of RAM that may need to be flushed. But with the current SSD I've never seen it go much past 1 minute, and usually much less (and it makes no difference what mode we are in, just the amount of unflushed RAM). It appeared that BC validation continued after stop was signaled, maybe not wanting to discard the downloaded but not validated blocks. But the long indicated that the last block was updated before the shutdown was received and logged (matching the time that I noted it was signaled (minute 57)...
2024-11-03T21:57:03Z UpdateTip: new best=00000000000000000071ef0363647bbfb591b04ee268642060596ca21b7671d7 height=500298 version=0x20000000 log2_work=87.697860 tx=284383466 date='2017-12-20T18:03:47Z' progress=0.257010 cache=7979.4MiB(62457317txo)
2024-11-03T21:57:03Z opencon thread exit
2024-11-03T21:57:03Z Shutdown: In progress...
2024-11-03T21:57:03Z addcon thread exit
2024-11-03T21:57:03Z msghand thread exit
2024-11-03T21:57:03Z net thread exit
2024-11-03T21:57:03Z DumpAnchors: Flush 2 outbound block-relay-only peer addresses to anchors.dat started
2024-11-03T21:57:03Z DumpAnchors: Flush 2 outbound block-relay-only peer addresses to anchors.dat completed (0.00s)
2024-11-03T21:57:04Z scheduler thread exit
2024-11-03T21:57:04Z Writing 0 mempool transactions to file...
2024-11-03T21:57:04Z Writing 0 unbroadcast transactions to file.
2024-11-03T21:57:04Z Dumped mempool: 0.000s to copy, 0.005s to dump, 27 bytes dumped to file
2024-11-03T22:03:27Z Shutdown: done
Notice the last two lines. 6 minutes and 23 seconds with nothing logged. This implies it was flushing the store. So mmap close is in this case about 6 times faster than leveldb.
(edited)
Even a BN snapshot before shutdown is much faster than this, taking about 1 minute. And the snapshot completes the entire flush, so in that case the shutdown is much less than 1 minute.

Monday, November 4, 2024

evoskuil00:44
Thinking about this it's possible that BC was flushing it's unspent output object (C++) cache. Without using a custom memory allocator, each one of these objects has to be unwound (explicitly deallocated). Especially these objects are spanning threads, that gets really costly.
evoskuil00:53
Similarly deallocation at runtime is also costly, but it goes unnoticed outside of performance tools because it's incorporated into the over cost of the run.
(edited)
evoskuil02:45
Spending some time today optimizing confirmation queries. They aren't too bad on the big box, but thrashing is a problem on the mini. There are several ways the queries can be vectorized within a block. I worked up several options in the spring but never got them fully evaluated. ASIO fan-out within a block looks like it will be suboptimal and is also much more complex. c++17 std::transform(par_unseq, ...) in up to three subqueries is working well.
evoskuil05:04
replied to a thread: view
So, according to its config, I can't give BC more threads. I can't imagine why, but it says this in the config file ("up to 15"):
# Set the number of script verification threads (0 = auto, up to 15, <0 =
# leave that many cores free, default: 0)
#par=<n>
evoskuil05:23
Unlike a full milestone run (mostly download), which is under an hour on the mini, and a full validation run (which I still need to get a consistent total for) which is strictly CPU limited on the mini, confirmation on the mini thrashes. This is the outstanding design issue for full and performant operation for the minimum platform. This isn't something that can be improved through increase parallelization, since it is already strongly parallel and even if an increase was possible it would only increase thrashing.
4 replies · evoskuil · last Nov 04, 2024
evoskuil15:37
I think this will be resolved through locality. During initial and catch-up sync, organization starts once there is a stronger candidate chain, and that evaluation (download/validation/confirmability) starts once there is a current header chain. This results in a cascade of single block organizations, since the next block in order is always a stronger chain than that previously confirmed. Consequently with concurrent validation/confirmation the same inputs/outputs/prevouts and indexes will already be paged in as a consequence of full block and prevout population. This makes navigating the links for confirmability almost free and should preclude paging. The only additional searches, reads and writes are to the strong_tx table, however this is a small table (tx scale with little data) that should not cause thrashing. So instead of running confirmation in series it makes a lot more sense to run it concurrently, and instead to run download and validation/confirmability in series.
evoskuil15:41
As a consequence of concurrent block validation (partial order), to preserve locality this requires that confirmability follow closely from validation. However since confirmation is ordered and validation is partially ordered, this imposes order back onto validation. Yet as long as confirmability is significantly faster (which it is, absent thrashing, by about 4x on big box) then it will not significantly delay validation. This is a direct analog to the download concurrency window and the validator closely chasing downloads via higher thread priority. This can be most simply achieved by raising confirmability thread priority above validator.
evoskuil15:45
But we really only want normal/high priorities in use. This works out since we have two scenarios. Either full concurrency with validate and confirm higher than download (where confirm may significantly lag validator) and download followed by validation/confirm where va look d is normal and confirm is high. This keeps confirm close to valid, and both independent of download.
evoskuil15:47
That’s a reasonable bifurcation between the maximal and minimal hardware scenarios. Full concurrency on big RAM is probably around 6 hours, and sequentiality on the mini is prob 10-12 hours, with current optimization levels. In comparison the BC full validation time on the big Win is over 24 hours (probably even more on the monster Mac)… I haven’t tried it yet on the minis, but just download is 14 on win mini and 9.5 on Linux mini.
(edited)
evoskuil12:51
replied to a thread: view
Hmm, maybe if set to -1 I can at least get it to set 63 threads (or 31 cores for 62 threads if it really means “cores”.)
evoskuil14:23
Nope, it's just a hardwired thread limit of 16:
2024-11-04T14:22:07Z Config file: M:\Core\Validate\bitcoin.conf
2024-11-04T14:22:07Z Config file arg: assumevalid="000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"
2024-11-04T14:22:07Z Config file arg: blocksonly="1"
2024-11-04T14:22:07Z Config file arg: dbcache="16384"
2024-11-04T14:22:07Z Config file arg: disablewallet="1"
2024-11-04T14:22:07Z Config file arg: listen="0"
2024-11-04T14:22:07Z Config file arg: par="-1" <<<<<<==========
2024-11-04T14:22:07Z Config file arg: txindex="1"
2024-11-04T14:22:07Z Using at most 125 automatic connections (2048 file descriptors available)
2024-11-04T14:22:07Z Wallet disabled!
2024-11-04T14:22:07Z scheduler thread start
2024-11-04T14:22:07Z GUI: "registerShutdownBlockReason: Successfully registered: Bitcoin Core didn't yet exit safely…"
2024-11-04T14:22:07Z Using /16 prefix for IP bucketing
2024-11-04T14:22:07Z init message: Loading P2P addresses…
2024-11-04T14:22:07Z Creating peers.dat because the file was not found ("M:\Core\Validate\peers.dat")
2024-11-04T14:22:07Z init message: Loading banlist…
2024-11-04T14:22:07Z Recreating the banlist database
2024-11-04T14:22:07Z SetNetworkActive: true
2024-11-04T14:22:07Z Script verification uses 15 additional threads  <<<<<<==========
Just to be sure it's actually reading and interpreting the values:
2024-11-04T14:25:24Z Config file arg: par="7"  <<<<<<==========
2024-11-04T14:25:24Z Config file arg: txindex="1"
2024-11-04T14:25:24Z Using at most 125 automatic connections (2048 file descriptors available)
2024-11-04T14:25:24Z Wallet disabled!
2024-11-04T14:25:24Z scheduler thread start
2024-11-04T14:25:24Z Using /16 prefix for IP bucketing
2024-11-04T14:25:24Z init message: Loading P2P addresses…
2024-11-04T14:25:24Z GUI: "registerShutdownBlockReason: Successfully registered: Bitcoin Core didn't yet exit safely…"
2024-11-04T14:25:24Z Creating peers.dat because the file was not found ("M:\Core\Validate\peers.dat")
2024-11-04T14:25:24Z init message: Loading banlist…
2024-11-04T14:25:24Z Recreating the banlist database
2024-11-04T14:25:24Z SetNetworkActive: true
2024-11-04T14:25:24Z Script verification uses 6 additional threads  <<<<<<==========
2 replies · evoskuil · last Nov 04, 2024
evoskuil16:29
What's even more interesting about this is that on the mini the validation thread count is 16 (so all hardware threads). So it's not about preserving responsiveness of the machine (though this should certainly be up to the user in any case).
evoskuil21:41
The win mini full validation run for BC is tracking at a 15 hour rate. This is consistent with my previous guess about assumevalid. Validating signatures on a sufficiently fast CPU (the mini has 16 threads and SHANI) will not slow down the run much, making it bound under the same conditions as assumevalid.
evoskuil15:37
replied to a thread: view
I think this will be resolved through locality. During initial and catch-up sync, organization starts once there is a stronger candidate chain, and that evaluation (download/validation/confirmability) starts once there is a current header chain. This results in a cascade of single block organizations, since the next block in order is always a stronger chain than that previously confirmed. Consequently with concurrent validation/confirmation the same inputs/outputs/prevouts and indexes will already be paged in as a consequence of full block and prevout population. This makes navigating the links for confirmability almost free and should preclude paging. The only additional searches, reads and writes are to the strong_tx table, however this is a small table (tx scale with little data) that should not cause thrashing. So instead of running confirmation in series it makes a lot more sense to run it concurrently, and instead to run download and validation/confirmability in series.
evoskuil15:41
replied to a thread: view
As a consequence of concurrent block validation (partial order), to preserve locality this requires that confirmability follow closely from validation. However since confirmation is ordered and validation is partially ordered, this imposes order back onto validation. Yet as long as confirmability is significantly faster (which it is, absent thrashing, by about 4x on big box) then it will not significantly delay validation. This is a direct analog to the download concurrency window and the validator closely chasing downloads via higher thread priority. This can be most simply achieved by raising confirmability thread priority above validator.
evoskuil15:45
replied to a thread: view
But we really only want normal/high priorities in use. This works out since we have two scenarios. Either full concurrency with validate and confirm higher than download (where confirm may significantly lag validator) and download followed by validation/confirm where va look d is normal and confirm is high. This keeps confirm close to valid, and both independent of download.
evoskuil15:47
replied to a thread: view
That’s a reasonable bifurcation between the maximal and minimal hardware scenarios. Full concurrency on big RAM is probably around 6 hours, and sequentiality on the mini is prob 10-12 hours, with current optimization levels. In comparison the BC full validation time on the big Win is over 24 hours (probably even more on the monster Mac)… I haven’t tried it yet on the minis, but just download is 14 on win mini and 9.5 on Linux mini.
(edited)
evoskuil16:29
replied to a thread: view
What's even more interesting about this is that on the mini the validation thread count is 16 (so all hardware threads). So it's not about preserving responsiveness of the machine (though this should certainly be up to the user in any case).
evoskuil17:28
I updated the confirmation chaser to use a single thread high priority pool to chase validations and dispatch confirmability checks. [This caused me to notice that the validation chaser should also use an independent thread (not just independent strand on the network threadpool) for monitor and dispatch of validation jobs, either in high or normal priority as configured.] Confirmability still uses c++17 std::transform in the database query, which will project the three subqueries onto all available threads, on current thread priority. This will cause confirmation to stay close with validation (configured presently for normal thread priority). It will fall behind as it waits on validation gaps, which could be as high as the download window size (currently 50k blocks) but will catch up as gaps are filled. And of course excess CPU capacity will continue to fill in from the validator and downloader. Ideally we would want highest priority on confirm, lower on validate, and lower on download... but we really don't want to be below normal thread priority for any.
evoskuil19:34
I just checked up on the Delving thread. I didn't realize there was an actual discussion going on there. I assume some of those folks are skimming posts here - which is great. I try to write up what's going on not only to get feedback but to help people understand what we are doing and how we are doing it. A node is a complex undertaking, and libbitcoin's a totally different architecture. So it's not at all surprising that most of the assumptions being made in that discussion are incorrect. I've avoided getting involved with it because there will be no end to the questions and discussion. I'll write the book after we ship :).
(edited)
I'll summarize some of that here just for our own context however, and if anyone else is interested...
evoskuil19:59
For one, since there is no utxo set, prevouts are verified unspent by looking up the outpoint’s spending transaction and verifying that it does not exist. This to me would seem slower than looking up the prevout from a utxo set.
Yes, verifying prior existence and unspentness in the strong chain is more expensive for a number of reasons, though the size of the index isn't really one of them. Indexation performance is almost entirely a function of rate of hash collisions, and that is controllable via config. It is also affected by the amount of RAM, and with sufficient RAM the entire store and all indexes are memory resident. One of the reasons it's slower is that the data model is relational, so there are a large number of queries involved in comping to resolution of this question. Another is that we store conflicting txs and unconfirmed txs in the same tables. However these are tradeoffs. Rapid response to these two questions is not the measure of performance during catchup, compact notification, or otherwise. The time where it would be considered most critical might be during compact block notification for a miner. We operate under the assumption (as does the usefulness of the protocol itself) that in this scenario we have already seen most if not all txs in the block, apart from the coinbase. In that ideal case libbitcoin has already validated, stored, and indexed all but one tx, which itself does not spend. These txs are not revalidated (unless there is a context incompatibility from the time of pooling, which would be very rare), moved or modified in any way upon possible confirmation. We also don't construct or serialize a block. We store/index only the header and coinbase, under PoW protection, and then ensure that the block is valid and confirmable via navigation of the tx graph. Unless there is a miss in the pool (which may require a store lookup or peer query) the question of validity can be answered without querying the store. Similarly block templates can be generated from the pool without hitting the store. The graph does not store txs, and for a miner it would be entirely reasonable to have every confirmable (including conflicts) tx ever seen represented in the graph in RAM.
evoskuil20:16
Based on insights from a private conversation, Libbitcoin will also skip checking confirmability if below the milestone. This means that transactions are written in order in the confirmability thread, but their inputs are assumed to exist and be yet unspent. Skipping this check is where the real speedup lies I believe, and was not clear to me on first read. With that in mind, this approach seems more like a combination of assumevalid and assumeutxo, since the signature checks are skipped and the utxo set is assumed to be valid based on the block height.
Transactions are not written in order. Inputs are never assumed to exist or to be unspent (adherence to the milestone proves that prevouts exist and are unspent). Under milestone, the performance advantage comes from extreme concurrency. We can and do download the entire chain concurrently, though we generally configure to break it up into 50k block chunks which complete before moving to the next. This chunking improves data locality (query perf) and ensures we don't get block 1 last (which significantly delays validation in the concurrent validation scenario). Block height is not the criteria for a milestone (he may just be speaking loosely). The "utxo set" is proven to be valid under the assumption of the milestone (i.e. that the milestone represents a valid branch). So there is no difference whatsoever in the security model of a milestone and assume valid. In fact I formerly assumed theirs worked the way ours does. But now I can see that the architecture doesn't really lend itself to these optimizations. Skipping block validation checks doesn't amount for any of the performance difference, which is visible from the fact that we are unaffected by the lack of SHANI until we become CPU bound. Skipping the confirmability checks during download (and validation) is a significant performance advantage, as it is what allows parallelism. However, we can fully download, validate, check confirmability, and confirm the entire chain in 6.2 hours on a machine that takes BC over 24 hours (see above). So it is really not the case that skipping these checks during a milestone run is the basis of the performance advantage. We just don't perform unnecessary checks. The true performance advantage comes from massive parallelsism.
evoskuil20:23
My intuition is that the speedups are coming from the more aggressive peer utilisation during download, and the clear separation of unordered vs ordered checks to take advantage of parallelism.
This is a good intuition.
I do also think this is what drives most of the IBD speedup there. And it is directly related to their architecture, doing it for Bitcoin Core wouldn’t bring much benefit since you still have to update the UTxO set
Another good observation.
Yes although in the context of this specific benchmark it’s more about being able to skip a whole class of checks than parallelizing them, i think.
Not the case, see above.
evoskuil20:28
  1. Non-overlapping ranges of blocks (e.g., 1000 blocks at a time) are downloaded and and unordered checks are done in parallel across multiple ranges
  2. The first range can also start confirmability checks (genesis to block 999), after which transactions from this range are committed to the Transactions table and the ConfirmationIndex is updated
  3. Now that the first range has finished with it’s confirmability checks, the second range of blocks can start its confirmability checks, and so on
  1. The entire chain can be downloaded concurrently. Ranges are not necessary and can often make milestone runs slower. Using the configurable window improves data locality and prevents long-running gaps. But it's purely statistical.
  2. Validation checks can progress as soon as there are no download gaps in the candidate chain, and continue to the next gap. Once the validated chain is stronger than the confirmed chain (typically 1 block at a time during catch-up), confirmability checks are performed in block order (heavily concurrently within each block).
  3. The only delays for validation and confirmation are gaps and resource availability.
11 replies · josie, evoskuil · last Nov 05, 2024
josie18:35
yeah, what you've explained here, especially windows vs ranges is what i assumed was going on, but i wanted to keep the delving post simple, hence the example with ranges. i need to reread and digest the rest of your answers, but so far i think the only misconception i had was assuming that confirmability was also happening under the milestone. regarding threat model, i agree there is no material difference (that i can see) and the primary reason we dont skip this in core is because we have to delete the spent outputs to maintain a UTXO set
josie18:36
as a side note, not that im married to the analogy, but i think the windows actually makes this closer to a mapreduce problem, where confirmability is the "shuffle" phase (but i agree it doesnt perfectly map and dont want to try to jam a square peg in a round hole)
evoskuil18:37
Yeah, BC does it because it needs to populate the utxo store in order, not because there is some difference in security. BN populates outputs to the store and confirmedness associations during download when under milestone. So we do the same we just benefit from parallelism.
(edited)
💯 1
evoskuil18:40
I don't think that BC can't take advantage of this type of parallelism while retaining the utxo store, as the utxo store requires block-ordered population.
(edited)
👍 1
evoskuil18:55
I've run tests with the download window reduced to 1024 blocks (like BC) to see if this type of benefit can be achieved in such a small window, allowing blocks to be memory buffered. But in this configuration BN slows dramatically to essentially BC speed.
evoskuil18:58
So we must impose this order in a chaser that follows validation or download, both of which require re-reading through large portions of the chain.
evoskuil19:03
BC could do that as well, chasing validation and creating the utxo store. But this means populating a large and redundant table. We just populate the associations that provide the essential information provided by the utxo store. This is a costly model in terms of proving confirmability, but it should not be any cheaper to navigate the same associations to build the redundant store. This is also more costly for us because we store unconfirmed/conflicts in the same space, but that also provides other very significant advantages, such as an append only store and having everything already validated and written when a compact block arrives.
josie19:17
agree with everything here. if we forget BN/BC for a second, my intuition is it will be hard to have a universal model that is performant for IBD (a data processing problem), vs updating chaintip/serving blocks to peers which is more of a high performance/realtime service problem
evoskuil19:24
I saw that earlier, and made some comments on it here. The first observation is that serving up blocks to peers is not a performance critical operation, either for the node or its peers. Secondly, BN reads, parses, and serializes the entire chain (and does everything else for a milestone run, which is a lot, including all the hashing - not required on read) in about 50 minutes, and would be closer to 40 minutes with sufficient RAM, on a network connection that produces a theoretical limit of 38 minutes to transfer the entire chain as a single file. Reading should therefore be much faster than the network can handle. Thirdly, there are a number of significant advantages in getting to completion with a compact block. There's no reason to assume that the means of verifying confirmability will have a greater cost than a utxo model, or that the other material advantages won't be dominant (for example, BN does not have to create or serialize a block).
(edited)
josie20:36
The first observation is that serving up blocks to peers is not a performance critical operation, either for the node or its peers.
my instinct is to disagree here, but ill need to give it some more thought.
Thirdly, there are a number of significant advantages in getting to completion with a compact block. There's no reason to assume that the means of verifying confirmability will have a greater cost than a utxo model
strongly agree here! i dont want to assume anything. im mostly going on what im familiar with while also trying to upskill on understanding the architecture you are building here, hence trying to be clear these are intuitions
👍🏻 1
josie20:38
(intuitions informed by more familiarity with BC's architecture, admittedly)
evoskuil20:31
Conceptually, this seems close to a MapReduce algorithm, where unordered checks are mapped on to each range, and then the Reduce step requires sequential ordering of each range of blocks for the confirmability checks.
It's an interesting theory, but not really the case. The process much is less discrete, not constrained by "ranges".
Clear separation between un-ordered and fully ordered checks is what makes this possible, from what I can tell. The partially ordered checks are not done under the -assumevalid / milestone model.
This is true, nor are the fully-ordered checks (confirmability). These are entirely unnecessary as the milestone is of course considered a valid block, and the rest is proven by the minimal checks that are performed.
evoskuil20:40
When a Libbitcoin node serves a block to a peer, it must reconstruct the block into its serialised representation, which will require some compute for each block request. Bitcoin Core, however, stores the blocks on disk in their serialised format and will simply read the block from disk and send it to the peer
This is true, however block responsiveness to peer is not a priority (and such thread priority can be lower than incoming information for example). Download by peers (catch up sync) is a function of concurrency and peer management, not individual node query response. However, given that all recently-accessed portions of the store are memory-resident, and in fact the entire store can be memory resident, this cost can be largely eliminated. On a $350 32GB machine libbitcoin can store and index 850,000 blocks in 50 minutes (network limited). This includes tx hashing (twice for witness txs) and Merkle root (twice for witness blocks) for the entire chain, without even having AVX512 or SHANI. When blocks are deserialized there is no hashing at all. So store query performance doesn't appear to be a material issue.
evoskuil20:49
My personal view on the role of a Bitcoin node is that its primary purpose is to validate and propagate new blocks as quickly as possible, such that all nodes and miners on the network can quickly come to agreement on what the longest/heaviest PoW chain is. This is why I favour the Block based data model.
I would say we optimize for a single miner to obtain and process new blocks as fast as possible in order to be most profitable via efficient regeneration and optimization of block templates. I don't think it will be the case that Satoshi's architecture will be shown to be more optimal at doing that, but we have a long way to go. Certainly BN is not just designed just to get started quickly, though we see that as critical to people actually using Bitcoin. Sync times are getting to the absurd, and despite linear chain growth are not improving with exponential hardware and bandwidth growth. But certainly for the miner side of the economy, rapid and optimal block template generation and dissemination is the priority.
1 reply · josie · last Nov 05, 2024
josie18:39
I would say we optimize for a single miner to obtain and process new blocks as fast as possible in order to be most profitable via efficient regeneration and optimization of block templates. I don't think it will be the case that Satoshi's architecture will be shown to be more optimal at doing that, but we have a long way to go.
i havent had time to fully flesh out my thinking on this, but i do think there is a strong argument for a "blocks" based architecture vs a transaction based architecture. but, as i mentioned in the delving post, i think they serve very different use cases. ideally, some architecture will emerge that can strike some happy middle ground between the two
evoskuil20:51
This isn’t so much block storage which makes a difference but how their content is indexed. In this case it would be clearer to make a distinction between a historical transactions index vs an unspent outputs index.
Moving data between two stores as this context changes is a very high cost. BN is an append only store and doesn't redundantly store data. Indexation is constant time regardless of store size, which can be seen in our various performance graphs.
Ok, now back to optimizing confirmability for small machines.
evoskuil21:41
replied to a thread: view
The win mini full validation run for BC is tracking at a 15 hour rate. This is consistent with my previous guess about assumevalid. Validating signatures on a sufficiently fast CPU (the mini has 16 threads and SHANI) will not slow down the run much, making it bound under the same conditions as assumevalid.
evoskuil23:14
Running now the updated BN with: 1. downloader running at normal thread priority 2. validator running at high priority 3. confirmation running at highest priority And using a 50k download concurrency window. The expectation is that the window is small enough that blocks are downloaded and stored/indexed such that there are periodic flushes of mostly old block data, and new block data is written into RAM by the downloader, read in full with prevouts by the validator, and then confirmation queries are executed over the same space (blocks and prevouts) such that most of the pages are never read from disk.
2 replies · evoskuil · last Nov 05, 2024
evoskuil14:17
This was a resounding success, not only in the material reduction to the big box run (.5-1.0 hrs out of 6) but also in the mini run. The mini currently has a validation completion rate of almost 12 hours (plus 1 hours download). This is significantly increased by lack of SHANI, since validation is entirely CPU bound. On the same box, with SHANI implemented, BC takes 15 hours. On a much bigger box without SHANI, Core takes 24 hours - almost doubling the cost. We also have the old libsecp256k1 affecting us here. And our AVX512 implementation doesn't help us because the box doesn't have it. In other words, despite all of that libbitcoin is still about an hour faster than BC if you don't consider confirmation. We should expect BN validation time on mini to be dramatically reduced, possibly in half, to around 6 hours, in an apples-to-apples to BC. On the big box (which is apples-to-apples) BC takes 24.5 hours and BN takes 6.2 (both full validation/confirmation). But confirmation has been the big issue on the mini, due to thrashing from restricted RAM. However, by running confirmation concurrently with and in proximity to validation, the paging cost is dramatically reduced, basically everything is disk-read once instead of twice. [The big box benefits from larger RAM where data read from the network is rarely if ever disk-read during concurrent download/validation/confirmation.] On the mini the independent confirmation completion rate was originally 35 hours (i.e. in addition to the 12 for validation and 1 for download). So end-to-end validation and confirmation was a 47 hour rate. But with concurrent validation/confirmation the combined rate was reduced to under 16.8 hours (17.8 with download). So taking advantage of mmap page locality reduced a full run from ~47 to 18 hours. Considering the lack of SHANI alone, and with no other improvements, this will already outperform BC on the mini. Just applying the BC validation efficiency gained from SNAHI to BN, that would put us at 10.5 hours vs. their 15.
(edited)
evoskuil14:26
There is an assumption from what I've seen on Delving that we are just speeding up sync using concurrency, but due to the lack of a utxo store we will be slower at validation. The numbers we are seeing so far paint a very different picture. We are 4 times faster on a big box fully-validating run in an apples-to-apples comparison, and may be almost twice as fast on a mini run. That certainly takes advantage of concurrency, but validation is clearly very performant. The big cost for us is paging off the disk, but with single block-at-a-time compact block validations the relevant data will be mostly if not entirely already in RAM - and we don't have to construct a block, validate txs, or store any utxos. I suspect that, especially for miners with big boxes, we will be much faster there as well.
(edited)
evoskuil23:30
The operating principle of load balancing between the phases is that the downloader would race ahead of the validator if the validator was not higher priority. But as such the validator takes over cycles from the downloader until the validator is starved for work. The confirmer is much faster than the validator, but must wait on validator completions and is totally block ordered. So the confirmer is given a higher priority than the validator. This ensures that confirmations will not get too far behind the validator. Typically (but not strongly) the difference between downloader and confirmer will be no more than the arbitrary download concurrency window (default 50k blocks). This has the very significant advantage of maximizing data locality, as download->validate->confirm are all operating over the same data. When the required data is is RAM it's practically free in relation to SSD retrieval. This is in large part due to mmap paging and the possibility of getting into a thrashing scenario - where read or write and flush are taking up most of the cycles instead of a tiny portion.
evoskuil23:41
And what I'm seeing in this big box run is exactly what I want to see. Disk (M:) activity is practically zero, memory and CPU 100%.
image.png
evoskuil23:46
Ideally this implies that as long as download rate is significantly faster than validation rate, download will not materially contribute to total time, and as long as confirmation rate is significantly faster than validation, it will not contribute either. While validation is the long pole, the use of CPU for the other modes reduces availability for the validator, so at that point all CPU optimizations become worthwhile.
(edited)

Tuesday, November 5, 2024

evoskuil00:48
Stopped this concurrent validate/confirm run at ~50% of the chain by txs, 630k blocks validated and 570k confirmed, at 2.2 hours. So if download is completed first (60 mins), this run is projecting a ~5.4 hour completion. I suspect this will break 5 hours with concurrent download, will tray that a little later. First gonna graph this out and also fire it up on the mini to see the impact on thrashing.
Shutdown time is 61 seconds (from 256GB RAM in use), about 6 times faster than BC when validating with max cache.
evoskuil01:51
This is big box, post-download, ~50% of chain.
(edited)
image.png
josie08:35
👋 thanks for the invite @evoskuil
josie08:57
looks like weekly dev meetings are thursdays @ 20:00 UTC? would love to join the next one
👍🏻 1
1 reply · pmienk · last Nov 05, 2024
pmienk17:23
1:00pm PST, which i believe is 19:00 UTC. Wouldnt want you to miss it because of a mixup.
evoskuil13:21
Check back here at that time for a huddle message.
👍 1
evoskuil14:17
replied to a thread: view
This was a resounding success, not only in the material reduction to the big box run (.5-1.0 hrs out of 6) but also in the mini run. The mini currently has a validation completion rate of almost 12 hours (plus 1 hours download). This is significantly increased by lack of SHANI, since validation is entirely CPU bound. On the same box, with SHANI implemented, BC takes 15 hours. On a much bigger box without SHANI, Core takes 24 hours - almost doubling the cost. We also have the old libsecp256k1 affecting us here. And our AVX512 implementation doesn't help us because the box doesn't have it. In other words, despite all of that libbitcoin is still about an hour faster than BC if you don't consider confirmation. We should expect BN validation time on mini to be dramatically reduced, possibly in half, to around 6 hours, in an apples-to-apples to BC. On the big box (which is apples-to-apples) BC takes 24.5 hours and BN takes 6.2 (both full validation/confirmation). But confirmation has been the big issue on the mini, due to thrashing from restricted RAM. However, by running confirmation concurrently with and in proximity to validation, the paging cost is dramatically reduced, basically everything is disk-read once instead of twice. [The big box benefits from larger RAM where data read from the network is rarely if ever disk-read during concurrent download/validation/confirmation.] On the mini the independent confirmation completion rate was originally 35 hours (i.e. in addition to the 12 for validation and 1 for download). So end-to-end validation and confirmation was a 47 hour rate. But with concurrent validation/confirmation the combined rate was reduced to under 16.8 hours (17.8 with download). So taking advantage of mmap page locality reduced a full run from ~47 to 18 hours. Considering the lack of SHANI alone, and with no other improvements, this will already outperform BC on the mini. Just applying the BC validation efficiency gained from SNAHI to BN, that would put us at 10.5 hours vs. their 15.
(edited)
evoskuil14:26
replied to a thread: view
There is an assumption from what I've seen on Delving that we are just speeding up sync using concurrency, but due to the lack of a utxo store we will be slower at validation. The numbers we are seeing so far paint a very different picture. We are 4 times faster on a big box fully-validating run in an apples-to-apples comparison, and may be almost twice as fast on a mini run. That certainly takes advantage of concurrency, but validation is clearly very performant. The big cost for us is paging off the disk, but with single block-at-a-time compact block validations the relevant data will be mostly if not entirely already in RAM - and we don't have to construct a block, validate txs, or store any utxos. I suspect that, especially for miners with big boxes, we will be much faster there as well.
(edited)
evoskuil15:21
So we’re at the point where implementing shani and upgrading the nuget secp256k1 build are no longer premature optimizations.
evoskuil17:05
Looks like I misinterpreted part of the mini run. I didn’t notice that confirmation is significantly lagging validation (despite being faster and having higher thread priority). That’s most likely because confirmation concurrency is limited to block internals. So while the validation aspect will complete in a total of 14 hours (faster than the 17.8 projected, probably due to lacking taproot), the confirmation aspect will take much longer.
evoskuil18:28
replied to a thread: view
No worries, it's not simple stuff, and so very different than what people are familiar with!
😅 1
josie18:43
somewhat unrelated, im very curious what insights you guys have regarding mmap'd db architectures. ive been exploring mdbx as a replacement for leveldb in core as sort of a moonshot side experiment, and mdbx (fork of lmdb) is essentially a mmap'd shadow paged btree. we saw some really promising benchmarks, but also a ton of write amplification. curious if you guys have any insights, since it seems you've rolled your own mmaped db architecture?
14 replies · jungly, evoskuil, josie · last Nov 05, 2024
jungly19:26
The database community is very split on mmap. Andy Pavlo is fighting a big battle to get DBMSes to not use mmap for a generic database. https://db.cs.cmu.edu/mmap-cidr2022/ The story about mangodb is illuminating. On the other hand, Eric has shown if you know the precise queries and you know the schema intimately, you can make it work. I would stay away from a generic mmap database, or at least let's discuss the issues listed in the paper below.
evoskuil19:28
I would absolutely not recommend it for use in any general purpose database. mmap must page to get/set any amount of data. Data locality and access patterns become a significant consideration, pretty much dominating everything else. These are not generally encountered under you start operating under heavy stress, so it can look great until you get into thrashing and then it goes parabolic.
(edited)
💯 1
evoskuil19:32
We have almost a decade of experience with this. Another major issue is fault tolerance and reliability. If you don't have an append only store, you can forget about fault tolerance, and reliability will be limited to detecting that the store is corrupt due to an incomplete flush on shutdown, but you will not be able to fix it (resync).
evoskuil19:36
We tossed out the previous iteration and started over due to these issues. It cost a couple years at least. But the learning helped us understand how to achieve fault tolerance and reliability (with much stronger guarantees than BC), as well as achieving hot file system backup - which were all original goals of Amir's when he started with mmap. Finally, we have learned how to manage thrashing, but it's a minefield sometimes.
(edited)
evoskuil19:38
Thrashing is a complete non-issue with sufficient RAM. And ultimately the objective is to map nothing to SSD but to just maintain the store in RAM. I have a 256GB PC and a 768GB Mac so that I can test these. The 256GB was enough to hold the full chain when I started.
evoskuil19:49
Then there's the issues of achieving (1) performant write/write thread safety (as well as read/write and read/read of course), (2) cross-file transactional commit for snapshotting and disk full scenarios (we handle disk full without the need for snapshot/recovery or restart), and (3) global read/write pointer invalidation when any given table requires a remap for expansion.
evoskuil19:51
I do not think a general purpose database can come anywhere close the query performance we obtain, however solving these problems is some non-trivial specialized work.
evoskuil20:01
I realize it may just be a curiosity for you, but BC doesn't have a store performance problem. That would be premature optimization. It most critically has an ordering problem.
josie20:44
thanks, helpful insights. and not really a curiosity, more was looking into leveldb replacements for a different problem i want to solve. after experimenting with mdbx a bit, im arriving at some of the same conclusions re: mmap for general purpose database.
josie20:49
ordering problem
im not entirely sure what you mean by this, assuming you mean ordering problem in the sense that blocks must be processed sequentially, effectively going through each state transition of the UTXO set to arrive at the chaintip ?
evoskuil20:52
Yes. Concurrency is really about finding the places where unnecessary order is imposed on an algorithm. Sometimes this is low level, as in the way critical sections are imposed via mutex locking. Other times it's much higher level, like imposing a partial order on downloading and a total order on validation where neither are logically required. The result is the same however, underutilized resources.
(edited)
👍 1
evoskuil20:57
There are of course tradeoffs. Concurrency imposes synchronization cost, which has to be measured against the benefit. For example, it is not required that txs be validated with their block, and it is not required that all previous blocks (or txs) be downloaded in order to validate a given block's txs. However tracking and notifying a tx (or block) that all of its required previous outputs have been downloaded is more costly than the partial ordering by block constraint.
(edited)
evoskuil21:00
Similarly fan-out by tx adds additional cost with no gain, if there are sufficient numbers of concurrent blocks to validate such that the CPU is fully occupied. This is almost always the case during initial and catch-up sync, but not at the very start and end of the sync. But still not worth that tiny advantage. On the other hand, it's materially faster to fan-out by tx when validating confirmability by block, given sequential execution by block.
evoskuil21:03
I'm always looking for places where we've inadvertently imposed an ordering constraint where there might be a benefit that outweighs the synchronization cost. One case is that confirmability does not require validity, so that could run completely independently of validation (presently it chases validation). Another case is that confirmability can run concurrently by block within a candidate branch - but this may not be worth the synchronization cost (data-intensive).
evoskuil21:05
I've been looking into why we're not getting the expected locality benefit from concurrent validation/confirmation on the mini. Clearly the validator is racing ahead of the confirmer, despite the latter having a higher thread priority, and having no limit of work to do, and fanning that work out vis std::transform(par_unseq, ...).
evoskuil21:12
The download window purposely does not constrain either the validator or the confirmer, and the validator does not constraint the confirmer. However with all having work, naively the highest priority threads should generally exhaust first. But this makes certain assumptions about thread scheduling. The proactor work queues are managed by BOOST ASIO. There is the network pool (download and eventing), validator queue (validation dispatch and validation), and the confirmation queue. c++ is performing the confirmability fan-out (internal to each block), and is starting from the highest priority single thread. Once that thread loops through all blocks, fanning out for each, ASIO takes over to notify it again. And there are almost always confirmation messages in this stranded (ordered) queue. But I suspect that this one highest priority thread is not able to keep pace with 64 high priority validation threads (and 16 normal priority download threads).
evoskuil21:26
So I theorized that there is some fair scheduling going on based partially on quantity, not just priority (which of course is necessary), causing the confirmer to fall behind the validator despite having less work, because there is only 1 confirmer dispatch thread vs. 64 dispatch/work threads for the validator. Otherwise I would expect the confirmer to never fall behind the maximum validation gap, which should rarely exceed the download window (concurrent downloads wait until the last gap is filled within the download window). So with a 10k block download window I expect to rarely see confirmation more than 10k blocks behind validation.
evoskuil22:02
So I matched the threadpool thread counts between the validator and the confirmer (configured to 64 on the big box), and dropped the window to 10k. After an hour. archive, validate and confirm are all remaining within (or at least very close to) the download window. The smaller window also improves data locality, but there is of course a tradeoff, since the CPU is not fully utilized. As when each download window starts to close out we always see network underutilization. The same will happen with CPU by more more closely coupling validation and confirm, but the advantage should be significantly reduced paging.
(edited)
evoskuil22:07
On the big box this is now 18% complete (by tx) and tracking to finish at 6.4 hours total (download/validate/confirm). That's about an hour longer than an optimal big box config, which is to be expected. Now it's time to try it out on the mini.
block_archived...... 460939 4581
block_validated..... 460040 4581
block_archived...... 465833 4581
block_confirmed..... 459131 4581
block_archived...... 460189 4581
Confirmed blocks are in order, others are out of order but close together. Last column is seconds from startup.

Wednesday, November 6, 2024

evoskuil04:50
Well this is pretty amazing. I'm running the maxi and mini at the same time on the same network, with fully-concurrent download, full validation and confirmation, with 50k and 10k windows respectively, and both are keeping all three phases in a tight band around the window. This has reduced disk activity on the mini from 100% to 30% and eliminated thrashing. The maxi is running at a 7.7 hour completion rate and the mini at a 12.7 hour rate (which will come down significantly with shani implemented).
(edited)
evoskuil04:58
This has been accomplished by merely setting thread priority between the phases so that they stay balanced. That keeps the data required by the later two phases in memory following it's read from the network. Avoiding the disk significantly speeds both the read block and populate queries for validation, and the expensive confirmability queries. This avoids thrashing and also frees up more CPU for validation (hashing and signature verification).
Even if the download is accomplished first, such as in a milestone run, and then validation and confirmation were to follow after download is complete, this remains a significant improvement, since there is only one disk read of all data, not two.
evoskuil05:07
The runs are currently passing blocks 500k (maxi) and 450k (mini), from starting at the same time. At that point blocks are generally full and we would expect to see the same constant rate behavior for the rest of the runs. With disk activity well below 100% on both there is clearly no thrashing. So it looks as if we may have finally resolve the last major design problem. I'll update the numbers tomorrow.
(edited)
evoskuil12:38
The maxi completed in 6.5 hours, with a bigger (than 50k window) gap opening up between the three phases over time. The mini is running at a 24 hour rate, also with a bigger gap opening up (which will cause it to do twice as much disk reading, though it doesn't appear to be thrashing). We might have to be more restrictive in coupling the phases on the smaller machines. I'll graph these out later.
evoskuil17:26
image.png
evoskuil17:36
On this machine the attempt at coupling increases overall time because there is sufficient RAM to overcome thrashing, even with 64 threads. The plot shows jags at 50k intervals (the window size), which also increases total time on this machine. Validation holds closely to archive (download) constrained only by higher thread priority. Confirmation drifts away, fairly continuously, despite the highest thread priority and equal number of pooled threads (the the validator). I just realized that it might have made more sense to use a number of threads equal to the validator plus downloader. The plot also shows the effect of one stage completing (archive first, causing a slight acceleration for validation, and then validation, causing a significant acceleration for confirmation. The confirmation priority drift is the cause of the most important observation, of the inflection point in confirmation around block 550k. What we would expect on a 1TB RAM machine is that this line stays fairly straight once blocks are full. But it is decelerating until the point where validation ends (without validation this line is straight).... https://libbitcoin.slack.com/files/U6N3S6GPQ/F0803PWNVH6/image.png
(edited)
That implies it's actually starting to thrash, and that is caused by it drifting too far from validation and download. As it drifts outside the window, it requires more data to be paged even with the very large amount of RAM, and that accelerates as it drifts further. So what we want is to keep all three lines parallel once blocks are full. (Smaller blocks have a lot more overhead, so it doesn't straighten out fully when plotted by transaction either). This should certainly be possible.
(edited)
evoskuil20:13
This is the same run on the mini. The window interval is 10k here vs. 5ok, so the steps are more pronounced. Validation tracks tightly with archive (download) all the say to completion. This is also very linear (constant per block) despite the significant cost of concurrent confirmation. As described above, once confirmation drifts away from the window (about 500k) it causes a lot more paging, no longer tracking tightly with validation and archive. Once archive and validation complete, confirmation should accelerate, which is right about where the graph ends (due to an unrelated issue). Which would look similar to the complete previous (maxi) graph above. So the next effort is to try a couple ways to more closely bind confirmation to archive/validation (without tightly coupling them). Since the same result is shown on both, I'll test on the maxi which is much faster. First I'll try adjusting the confirmation chaser thread count to match that of the validator and downloader combined.

Thursday, November 7, 2024

evoskuil01:34
image.png
This is a new maxi box run in progress with the aforementioned thread priority balancing implemented. Huge difference in terms of tracking.
Clearly there's still some drift, and the overall run cost will be higher (as expected), but this is getting close to a linear run (constant average cost per block once full).
Validation time is running very constant, with expected jogs at the 50k window points. Archive is clearly being offset by confirmation, which is ideal. We slow the downloads (which would race ahead and complete in 1 hour), to prioritize confirmation, until there is no more at which point archive kicks back in. CPU is running 100% with periodic gaps, which imply the increased overall run time.
And disk activity is well below thrashing (not close to 100%). Interestingly RAM is not yet full despite having archived over 800k blocks, validated 780k blocks, and confirmed 683k blocks (drift is at about 53k blocks at this point, in contrast to 200K in the previous run).
The run is projecting confirmation completion around 7 hours (rate will increase as validation and archive complete), which is a small price to pay to achieve linearity. On this machine BC takes 24.5 hours to complete.
evoskuil02:54
With archive and validation complete, this run is on track to finish in 25 minutes at a total time of 6.2 hours. The minimal time on the maxi has been 5.2 hours IIRC, so not bad. There's still a good amount of drift beyond the window, which started about 100k blocks later than the previous run, and this run will complete about 15 minutes sooner than that one. So the closer coupling actually reduces cost, and the only way that is possible (because the coupling has a higher cost generally) is that unnecessary paging is being reduced. The smaller the window, the less paging there will be, but the higher the coupling cost. So that will end up being the tuning point based on hardware.
evoskuil03:33
image.pngimage.png
On the left is the previous run and on the right is the completed next run, where the thread count of the confirmation pool has been increased to match the total competing thread count. The highlighted region below shows the same non-linear trend as the previous run, but much later/shorter. The area before it is starting to drift, but still nearly constant, and the area after it is constant and no longer competing with (out-of-window) download and/or validation.
image.png
That highlighted areas is where confirmation has moved far enough outside of the window to cause additional paging, making the cumulative time non-constant.
A smaller window and tighter coupling will make it look like this (on the small box), except with the green line shifted to align with the others, and the three lines straight (after full blocks) and steeper than the orange/yellow but shallower than the green.
(edited)
image.png
1 reply · evoskuil · last Nov 07, 2024
evoskuil10:09
*orange/blue
Huddle with pmienk · 29 min20:56

Saturday, November 9, 2024

evoskuil00:45
Hey, sorry I forgot to chime in on the meet. I was on the road for two days.
1 reply · pmienk · last Nov 09, 2024
pmienk08:51
No worries.

Sunday, November 10, 2024

evoskuil01:12
This is a new run in which I relaxed threading a bit and simplified the confirmation algorithm. So validation and confirmation are still loosely coupled to archive, and the thread priority is a weaker control, but the simpler algorithm keeps them tracking much more closely. But again, once they deviate too far, confirmation thrashing kicks in (at about 680k), until archive and validation complete.
(edited)
image.png
Archive tracks very consistently, and so isn't the cause of the drift, but once that's caused by confirmation, the performance impact hits both archive and validation.
evoskuil12:05
This finally adds the tight coupling between confirmation and archive (download), so that archive is blocked until confirmation for that chunk completes.
image.png
evoskuil12:16
Here we achieve linearity at a 10% cost in total time. Archive remains very consistent within each chunk because it has no ordering constraint. Validation is also very consistent as it is partially ordered, such that all previous blocks must be archived before a block is queued for validation. Finally confirmability is least consistent, as it is totally ordered by block, as each previous block must be confirmed before the next is started. This last constraint causes delays (vertical on the green line) while waiting for a given block in order to be downloaded and/or validated. This is most obvious in the middle of chunks 450k and 500k.
(edited)
Validation and confirmation are running at equal thread priority, so it is also the case that confirmation may be fully delayed by validation just due to thread priority. Since archive is lower priority, vertical validation lines are just validation waiting on a gap in archived blocks.

Monday, November 11, 2024

evoskuil00:35
The above image shows the last attempt on the mini to bind confirmation and validation closely to download using higher thread priority. This is intended to prevent thrashing by keeping the reads (validate/confirm) close to each other and to the writes. On the mini a smaller concurrency window is used (10k blocks, vs. 50k on the maxi), which is visible in the steps. This technique didn't work, as confirmation departed from the window, which resulted in thrashing and a nonlinear confirmation trajectory.
(edited)
evoskuil00:42
image.png
evoskuil01:02
This is the new tightly coupled run on the mini. It's still in progress, but shown here through block 670,000. Note that in the loosely-coupled run just above, divergence started around 450k and the run was stopped prior to 600k confirmations (at almost 16 hours). In the new run we have reached that same point in less than half the time. This is not a result of increased concurrency, this is a result of shifting the load from download to validation and confirmation, so as to keep each aligned, which dramatically reduces the SSD paging requirement. So it looks like this approach is working.
(edited)
evoskuil01:18
Some caveats on the above run. (1) I stopped the run to change a configuration setting [first red box], (2) I reset the network channels while running, when I noticed that the node was fully idle - turns out there was a bug in the new code, whereby a required message was not being sent to restart idle channels when the download window was restarted following confirmation completion. So this led to some long periods of unnecessary idleness, (3) I fixed the bug and then restarted the node to patch it [second red box]. I don't have an explanation for the green box. The lines should have converged where the orange line (validation) jumps. Possibly the result of me stitching three logs together. But without these various interruptions, the progression would be even more perfectly linear.
evoskuil01:25
This approach can work just as well for a much larger concurrency window, given more RAM (primarily), as shown in the maxi run above (50k window, complete in 5 hours). Even a unlimited (single) window will work just fine and is ideal for big machines. There's plenty of tuning left to do, this just proves the design. This run was restarted with all normal priority threads, which may not be optimal from a speed standpoint. It is also configured with 32 confirmation threads in the pool, where only 1 is required (confirmation fan-out is via std::transform), though this wasted cost should be minimal. Also this is fully validating all 295,000 checkpoint blocks, which is really pointless (never necessary). That alone adds 20 minutes to the mini run.
(edited)
evoskuil01:41
Finally, as a reminder, this run is on a machine without AVX512 (which is very common for the last five years or so, and we have implemented) and BN doesn't yet apply SHANI (also common on hardware). with these applied it should significantly reduce archive and validation time, which leaves more time for confirmation. Confirmation may end up being more limited by RAM/SSD depending on the machine, but this new technique limits the importance of SSD speed and RAM volume. On the mini both will certainly help. BC does not yet have AVX512, so that's an even comparison, but it does have SHANI, which makes a major difference in its performance. The big Mac, with 768GB RAM does not have SHANI and that takes over 24 hours to validate, where the Windows 11 mini (with 32 GB RAM and SHANI) takes BC 15 hours. Finally, BN is using a 3 yo build of secp256k1, which is missing performance improvements that BC has. Given that about 75% of the archive and validation cost is secp256k1 and 15% is sha256, I'd expect to get a pretty significant boost, possibly a 1/3 time reduction on the mini, which in the apples-to-apples should put BN well ahead of BC. The maxi validation is already 5.5 hours to BC's 20.
evoskuil02:42
image.png
evoskuil02:47
Now that it hasn't been reset or restarted for a couple of hours, it's smoothed out a lot. The progression is slightly non-linear, which is a result of paging cost on the mini. Even though paging is minimized by the tight coupling, and thrashing is avoided (no longer at 100% SSD), the paging cost increases with the size of the store, as later blocks can reach back further into the history. With full RAM there is no paging and it doesn't matter how far back is the look, paging does not increase and lookup cost is constant. So with full RAM we get true constant time, and with less we get some non-linearity. Though on this run the last 2 hours have been almost perfectly linear (constant time).
(edited)
🚀 1
evoskuil02:52
It looks like this run will finish at around 20 hours. That's the same run time as BC on the maxi (which takes BN 5.5 hours). BC on the mini is 15 hours, but with the benefit of SHANI. If BN gets a 25% savings from SHANI and secp256k1 modernization it would complete at the same time as BC on the mini.
evoskuil14:16
image.png

Wednesday, November 13, 2024

evoskuil17:01
image.png
This is a clean (no restarts) full validation mini run with the concurrency window reduced to 5,000 blocks. The "bypass" data is show for checkpointed blocks (that never require full validation) - which saved about 15 minutes over the course of the previous run (and is consistent with BC full validation). Otherwise the run was modestly more efficient (and more continuous), presumably from the lack of restarts.
(edited)
evoskuil17:32
This mini run lacks SHANI, so the most comparable runs with BC would be the maxi validation run on Windows 10 (256GB RAM/64 threads, no SHANI), which completes in 24.6 hours. Also the BC macOS (768GB RAM/32 threads, no SHANI) NON-validation run, which completes in 18 hours (I would estimate this would be at least 24 hours with full validation based on lower core speed and lack of SHANI). So the milestone comparison of BN to BC on the mini is 1.0 hours to 14.1 hours (BN=7%BC), and the comparable (no-SHANI) full validation run (on the Win10 maxi) is 5.6 hours to 24.6 hours (BN=23%BC). However, it's not clear that BN can achieve comparable full validation performance on the mini due to the (straightforward) SHANI/SECP256 upgrades. It may be that BN becomes RAM/SSD bound by confirmation before those updates can provide a significant advantage (on higher RAM machines this cost goes away entirely). Basically the confirmation cost is quadratic in the size of RAM, starting from zero cost at "full" RAM and increasing quadratically as RAM decreases. With full RAM BN becomes fully CPU bound in validation/confirmation like BC. But BC doesn't scale with more RAM, so this low end machine advantage will continue to decrease as RAM cost drops. I'm not aware of anything significant that BC can do to become scalable, short of a complete rearchitecture. I'm also not aware of anything that BN can do to overcome quadratic validation cost on low RAM machines. However, with milestone sync (default) BN enjoys an order of magnitude advantage on low end machines.
evoskuil17:52
Assuming BN also achieves a 1hour time on Linux (as it should), it has a 10x advantage over BN (BC is more optimal on Linux due to BC mingw and other Windows issues). The BN maxi full (and non-) validation advantage is presumably also less on Linux than Win/Mac, but should still be significant (estimating 5.6 to 17.1 hours, just need to do a BC full validation Linux run). Once the two major optimizations are added to BN these ratios will certainly change in BN's favor. I think it's safe to say at this point that we will have a full validation disadvantage on low RAM machines, potentially as much as 30% on the mini (increasing with lower RAM), and a 10x advantage in milestone sync on the mini (15x on Windows). As RAM increases that 30% full validation disadvantage reverses to a 4x advantage at current bandwidth, and will keep increasing with hardware. And of course BN will retain a significant and growing advantage in milestone sync as RAM/CPU/bandwidth increase.
🚀 2
1 reply · evoskuil · last Nov 14, 2024
evoskuil01:30
  • 10x advantage over BC

Thursday, November 14, 2024

pmienk05:23
Can't make tomorrows meetup. Have a scheduling conflict.
evoskuil21:06
ok, I'm here if anyone wants to join.

Monday, November 18, 2024

evoskuil01:32
Last night I completed an update to the secp256k1 NuGet package. This allows us to easily run the latest update to the library in the vc++ builds. I've run a few full validation/confirmation syncs on the maxi since then. The difference is a reduction of about 10 minutes over the course of 5.5 hours. The vc++ builds pf secvp256k1 do not have the advantage of the asm optimizations, since vc++ does not implement assembly in x64 builds (optional part of the c++ standards). Also, the run is limited by confirmation, not validation. I will perform some non-confirming runs just to see what is the actual improvement.
evoskuil01:38
I've spent most of the week rebuilding a staircase in my house. Putting down new hardwood flooring. Pulled up the stair treads and found that the whole staircase was so crooked it would be easier to replace it than work around it as they did originally. The landing was unlevel because they didn't actually secure it to the house on two sides.
(edited)
IMG_7644IMG_7639IMG_7658IMG_7667IMG_7684IMG_7688IMG_7695IMG_7693IMG_7701IMG_7702
👌 3
I started thinking today that the whole process was a lot like tearing through a software project. As you peel the onion a lot of times the rot just keeps going. Sometimes it’s better to just start over.
evoskuil02:13
Now that secp256k1 is updated I think I'll take a look at implementing shani. Also it helps that I now have platforms that I can actually run it on.
(edited)
Simon Bandson18:14
Hi Everyone, I am trying to build libbitcoin shared library (libbitcoin.so or equivalent) , terminal data install status shows link : static and shared. (With enable-shared command). But no .so (shared library) file(s) are created . Any ideas ? Thank you
4 replies · pmienk, Simon Bandson · last Nov 18, 2024
pmienk18:23
Try explicitly --disable-static, though after a certain depth in the project tree only static linking is supported. On windows only static linking is supported.
Simon Bandson18:25
Thank you i will try that, im am on linux and mac os.
Simon Bandson20:04
It worked, thank you very much. Will test later and let you know.
👍🏻 1
pmienk20:07
👍

Tuesday, November 19, 2024

evoskuil05:01
Isolated (no download or confirm) validation testing with latest secp256k1 is currently running at about 83% of the time of the previous NuGet version from three years ago (old version is 1.21 x the time of the new version).
evoskuil05:37
With the new secp256k1 build, full validation without download (which takes 60 minutes) or confirmation, on the maxi (without shani) is running at about a 3.5 hours completion rate at block 700k. Note that given this is a vc++ build, this doesn't include assembly optimizations in secp256k1. shani is significant for validation, as all signatures imply a signature hash of the tx. I suspect that a comparable machine with shani will complete full validation in under 3 hours.
(edited)
evoskuil13:10
The new secp256k1 dropped validation (only) time on the maxi (no shani) from 4.0 hours to 3.4 hours (85%). Given that there is zero network variability in these runs (all blocks pre-downloaded) this is a highly reliable distinction.
(edited)
👍 2
evoskuil20:08
image.png
evoskuil20:15
This is vc++, without the asm optimizations. Good work secp256k1 team!
(edited)

Wednesday, November 20, 2024

evoskuil02:03
Validation with the new secp256k1 completed on the mini in 8.6 hours. I'm doing a validation only run with the previous version for the same comparison. So far update is at about 90% of earlier version.
evoskuil12:32
Completed in 84% of earlier version, in 8.6 hours.
evoskuil12:51
image.png

Thursday, November 21, 2024

Huddle with pmienk, evoskuil, noah.3, Marnix · 72 min20:57

Sunday, November 24, 2024

jungly17:30
https://blog.lopp.net/bitcoin-node-sync-with-utxo-snapshots/ Using torrents? Cause Core can't scale as well as existing torrent clients?
Cypherpunk Cogitations
A guide for how to significantly speed up syncing a new Bitcoin Core node using the UTXO snapshot feature.
💯 1
2 replies · evoskuil · last Nov 24, 2024
evoskuil17:33
Still slower than libbitcoin. 😂
🤣 2😂 1
evoskuil17:40
These types of trust-based hacks around their scalability issues will just dig the hole deeper by avoiding the necessary rearchitecture.
(edited)
evoskuil17:33
replied to a thread: view
Still slower than libbitcoin. 😂
🤣 2😂 1
evoskuil17:40
replied to a thread: view
These types of trust-based hacks around their scalability issues will just dig the hole deeper by avoiding the necessary rearchitecture.
(edited)
evoskuil19:17
I've been working through SHA-NI integration into the SHA algorithm.ipp template. As part of that I just completed a much-needed refactoring of that into 12 independent .ipp files, with some organization and comments. There are a couple of ways to do the integration and it's net yet clear which will be optimal, so I'm also doing some investigations on the side. I set up a third mini with my dev environment so that I can test/debug with SHA-NI available. Hopefully I'll have something running the next couple days.
👍 3
evoskuil20:32
Just looking again at download time, where all txs and blocks are hashed and Merkle roots computed (doubled for witness txs/blocks, excludes signature hashing in script validation). Out of the full run cost, during a slice around block 725,000 (segwit heavy), this is the following ash256 hash cost:
tx.hash(false)    7.4%
tx.hash(true)     4.9%
bk.merkle(false) 0.47%
bk.merkle(true)  0.48%
======================
total hashing   13.25%
Given a machine with avx512 (implemented) Merkle root calculations will be cut in half. Given shani (WIP) hash computations should be reduced to 20-25% of native (even with existing optimizations). That produces:
tx.hash(false)    1.5%
tx.hash(true)     1.0%
bk.merkle(false) 0.23%
bk.merkle(true)  0.24%
======================
total hashing       3%
However, this will likely only be noticeable when CPU bound (later part of the mini sync). 10% off the whole run is at most 6 minutes, so we should expect to only get 3 minutes or so out of this in terms of milestone sync on the mini. It should however be much more significant during validation, where then node is fully CPU bound and there is also signature hashing.

Monday, November 25, 2024

evoskuil17:20
shani message scheduling dropped right into the template and worked straight away, independently of the shani transformation, which I haven't added yet. While this method of pre-scheduling the full block requires more memory, it has a significant advantage in terms of simplicity, and in the normal form is faster. It also allows for vectorization of the message schedule, which we do.
🎉 1
evoskuil17:25
This approach also allows us to vectorize K additions, because they are applied across the fully scheduled buffer. In the typical shani algorithm (using rotating variables as opposed to a message buffer), this vectorization is limited to 4 lanes (4 concurrent additions). This is however independent of the actual shani implementation. By separating the implementation in this manner we can take advantage of 4/8/16 lane vectorization for K additions - mixing avx512 and shani.
It is also possible that our existing avx2 and/or avx512 (probably not sse41) message schedule vectorization is more optimal than the hardware-implemented 4 lane message processing of shani, at least for larger data hashing, where we must iterate over 4/8/16 hash blocks. In those cases we already vectorize the message buffer, which gives us about a 30% performance boost in that aspect. So we may end up outperforming the typical native shani implementation on machines with higher level vectorization.
(edited)

Tuesday, November 26, 2024

evoskuil01:52
The entire implementation of shani message scheduling amounts to this:
    else if constexpr (use_shani)
    {
        wbuffer[Round] = mm_sha256msg2_epu32
            (
                mm_add_epi32
                (
                    mm_alignr_epi8
                    (
                        wbuffer[Round - 1], wbuffer[Round - 2], SHA::word_bytes
                    ),
                    mm_sha256msg1_epu32
                    (
                        wbuffer[Round - 4], wbuffer[Round - 3]
                    )
                ),
                wbuffer[Round - 1]
            );
    }
evoskuil03:30
And the implementation of shani transformation:
        else if constexpr (use_shani)
        {
            state[1] = mm_sha256rnds2_epu32(state[1], state[0], wk[Round]);
            state[0] = mm_sha256rnds2_epu32(state[0], state[1],
                mm_shuffle_epi32(wk[Round], 0x0e));
        }
🚀 1

Thursday, November 28, 2024

jungly15:02
https://delvingbitcoin.org/t/can-parallel-validation-side-step-the-slow-block-issue/1288 I was trying to remember (without going to look back at the code), what happens in libbitcoin in the situation when two blocks arrive claiming to be the next tip. My guess is the first one to be validated wins, right?
evoskuil15:54
The one with the greater amount of work wins. If they are the same it doesn't matter, and the one that is already confirmed remains just out of a desire to not waste processing.