Archive of the libbitcoin Slack #general channel.
mmap refactor incoming. Our mmap wrapper (map) is now a variadic template (more compile time yay), factored from one into four source files...
github.com/libbitcoin/libbitcoin-database/pull/847/changes
The rather large interface is unchanged, not test impact. There is just a template parameter for table widths, with the default being one column of width 1 (map<1>). This reduces to the identical compiled code as the previous map class. But now we can define an map as map<4, 32, 33, 64> (our ecdsa signature table). Under the covers there are four mmaps of specified columns and an aligned number of rows. The single map has all of the same logical and physical size values and methods, aggregating the tables.
This is similar to what I did previously, but that maintained the full map object for each table. So that meant all guards applied to each table independently. All allocator locks and remaps were independent. This reduces that to the one set of controls just spread over the independent mmap files. So the type changes trivially for a SoA table, the constructor accepts the corresponding number of file system paths, and divides up the numeric config across the columns.
And there is an accessor that allows the stream read/write in logical tables to obtain the offset pointer the specific columns. So that is buried in the existing logical table wrapper object for the vertical tables, so that the columns are sub-named. If this doesn't bring the perf back into line with horizontal rows, I'll be sad, because the impl is pretty slick - and there's really nothing else to optimize.milestone run to 950,000 in 39.0 mins.What would we do with an error from a math function?
nice work regarding the vert tables
5.09 hrs its fully consistent with the previous implementation, so I'll update the vertical tables and see how that goes.
Also I integrated signature table pruning into store.prune(), so now when the store becomes current and coalesced it drops the ecdsa and schnorr table set down to their logical size (which is zero at that point), which reclaims disk space back to the configured default initial size (and zero when stopped, as before). So if you configure with default (1 byte), they grow to GB size and then shrink to 1 byte in reservation as well as on disk. Previously they would retain the largest reservation until shutdown (the expected behavior for append-only tables).2026-07-01T16:54:55Z.1 Capture ecdsa.... (40858454 / 41171928) = 99.2386% 2026-07-01T16:54:55Z.1 Capture multisig. (3659847 / 3762952) = 97.2600% 2026-07-01T16:54:55Z.1 Capture schnorr.. (3459475 / 3506957) = 98.6461% 2026-07-01T16:54:55Z.1 Capture threshold (258785 / 258785) = 100.0000%
op_checkmultisig alone is producing as many individual signatures as all of schnorr, and ecdsa is almost 12x schnorr. This is just the raw sigs encountered, the pairings that must be executing are even higher due to op_checkmultisig combinatorics.~/libbitcoin/master/source ~/libbitcoin ********************** Configuration ********************** BUILD_boost : yes BUILD_secp256k1 : BUILD_ultrafast : yes BUILD_SRC_DIR : /home/neillm/libbitcoin/master/source BUILD_OBJ_DIR : /home/neillm/libbitcoin/master/source/obj BUILD_OBJ_DIR_RELATIVE : BUILD_CONFIG : release BUILD_LINK : static BUILD_FULL_REPOSITORIES : BUILD_POST_INSTALL_CLEAN : BUILD_USE_LOCAL_SRC : yes BUILD_SKIP_TESTS : yes PARALLEL : 96 PREFIX : /home/neillm/libbitcoin/master/install DISPLAY_VERBOSE : CONFIGURE_OPTIONS : --with-ultrafast --without-tests --prefix=/home/neillm/libbitcoin/master/install --enable-ndebug ********************** Toolchain Configuration Parameters ********************** CC : gcc CFLAGS : -O3 CXX : g++ CXXFLAGS : -O3 LD_RUN_PATH : /home/neillm/libbitcoin/master/install/lib LD_LIBRARY_PATH : :/usr/local/cuda-13.3/lib64:/usr/local/cuda-13.3/lib64:/home/neillm/libbitcoin/master/install/lib PKG_CONFIG_PATH : /home/neillm/libbitcoin/master/install/lib/pkgconfig:/home/neillm/libbitcoin/master/install/lib/pkgconfig LDLIBS : BOOST_ROOT : /home/neillm/libbitcoin/master/install CMAKE_PREFIX_PATH : CMAKE_INSTALL_PREFIX : /home/neillm/libbitcoin/master/install CMAKE_INCLUDE_PATH : CMAKE_LIBRARY_PATH : ********************** Preparing to acquire boost ********************** [0;93mReusing existing 'boost' directory...[0m ********************** Preparing to build boost **********************full file
neillm@dev:~/libbitcoin$ nvcc --version nvcc: NVIDIA (R) Cuda compiler driver Copyright (c) 2005-2026 NVIDIA Corporation Built on Tue_Jun_09_02:43:40_PM_PDT_2026 Cuda compilation tools, release 13.3, V13.3.73 Build cuda_13.3.r13.3/compiler.38244171_0 neillm@dev:~/libbitcoin$ nvidia-smi Thu Jul 2 08:49:02 2026 +-----------------------------------------------------------------------------------------+ | NVIDIA-SMI 610.43.02 KMD Version: 610.43.02 CUDA UMD Version: 13.3 | +-----------------------------------------+------------------------+----------------------+ | GPU Name Persistence-M | Bus-Id Disp.A | Volatile Uncorr. ECC | | Fan Temp Perf Pwr:Usage/Cap | Memory-Usage | GPU-Util Compute M. | | | | MIG M. | |=========================================+========================+======================| | 0 Quadro RTX 6000 On | 00000000:21:00.0 Off | Off | | 33% 28C P8 9W / 260W | 1MiB / 24576MiB | 0% Default | | | | N/A | +-----------------------------------------+------------------------+----------------------+ +-----------------------------------------------------------------------------------------+ | Processes: | | GPU GI CI PID Type Process name GPU Memory | | ID ID Usage | |=========================================================================================| | No running processes found | +-----------------------------------------------------------------------------------------+
In file included from /home/neillm/libbitcoin/master/source/libbitcoin-server/builds/gnu/UltrafastSecp256k1/src/opencl/tests/test_opencl.cpp:9:
/home/neillm/libbitcoin/master/source/libbitcoin-server/builds/gnu/UltrafastSecp256k1/src/opencl/include/secp256k1_opencl.hpp:18:10: fatal error: secp256k1/types.hpp: No such file or directory
18 | #include "secp256k1/types.hpp"
| ^~~~~~~~~~~~~~~~~~~~~
compilation terminated.
gmake[2]: *** [src/opencl/CMakeFiles/opencl_test.dir/build.make:79: src/opencl/CMakeFiles/opencl_test.dir/tests/test_opencl.cpp.o] Error 1
gmake[1]: *** [CMakeFiles/Makefile2:1010: src/opencl/CMakeFiles/opencl_test.dir/all] Error 2
gmake[1]: *** Waiting for unfinished jobs....bs: /home/neillm/libbitcoin/master/install/include/bitcoin/database/tables/caches/ecdsa.hpp:272: bool libbitcoin::database::table::ecdsa_correlate::allocate::to_data(libbitcoin::database::flipper&) const: Assertion `!sink || sink.get_write_position() == minrow' failed. Aborted ~/libbitcoin/master/install/bin/bs -c bs.cfg.v4.testnet4
Here are the updated bridge-free libbitcoin integration docs and examples. The canonical path is header-only C++ via <ufsecp/libbitcoin.hpp> / ufsecp::lbtc::*, with one CMake target and no shim, no C ABI, and no bridge. GPU column acceleration is opt-in and internal to the engine; the libbitcoin caller API stays unchanged and falls back transparently to CPU.
Hi all, We finished the current libbitcoin direct-integration benchmark/example package for `shrec/UltrafastSecp256k1`. The goal of this integration path is: - Direct C++ integration via `<ufsecp/libbitcoin.hpp>` - No C ABI dependency for libbitcoin integration - No bridge layer - No shim layer - Build-flag controlled inclusion - Only the libbitcoin-relevant public-data entrypoints exposed - CPU fallback kept available, GPU hooks used when enabled/available ## Main links Integration guide: https://github.com/shrec/UltrafastSecp256k1/blob/dev/docs/LIBBITCOIN_INTEGRATION.md Public operations benchmark report: https://github.com/shrec/UltrafastSecp256k1/blob/dev/docs/LIBBITCOIN_PUBLIC_OPS_BENCHMARKS.md Public operations example: https://github.com/shrec/UltrafastSecp256k1/blob/dev/compat/libbitcoin_direct/examples/public_ops_example.cpp Public operations benchmark source: https://github.com/shrec/UltrafastSecp256k1/blob/dev/compat/libbitcoin_direct/bench/bench_public_ops.cpp Hash256 var GPU/direct report: https://github.com/shrec/UltrafastSecp256k1/blob/dev/docs/LIBBITCOIN_HASH256_VAR_GPU_REPORT.md Libbitcoin direct README: https://github.com/shrec/UltrafastSecp256k1/blob/dev/compat/libbitcoin_direct/README.md Current integration commit: https://github.com/shrec/UltrafastSecp256k1/commit/72b300d0561c3a2ccf371565e54630060b633f95 ## Covered libbitcoin-facing entrypoints The public direct C++ header now covers benchmark/example paths for: - `xonly_validate_batch` - `pubkey_validate_batch` - `taproot_commitment_verify_batch` - `tagged_hash_batch` - `tagged_hash_batch` tag-string overload - `tagged_hash_var_batch` - `hash256_batch` - `hash256_var_batch` The example target calls all of these through `<ufsecp/libbitcoin.hpp>`. ## Build flags For the direct libbitcoin package: ```bash cmake -S . -B out/libbitcoin-public-ops-bench -G Ninja \ -DCMAKE_BUILD_TYPE=Release \ -DSECP256K1_BUILD_LIBBITCOIN=ON \ -DSECP256K1_BUILD_LIBBITCOIN_TESTS=ON \ -DSECP256K1_BUILD_LIBBITCOIN_BENCH=ON \ -DSECP256K1_BUILD_LIBBITCOIN_EXAMPLES=ON
cmake --build out/libbitcoin-public-ops-bench --target \ example_lbtc_public_ops \ bench_lbtc_public_ops \ bench_lbtc_hash256_var \ bench_lbtc_direct_batch \ test_lbtc_direct_verify \ test_lbtc_direct_operations -j
ctest --test-dir out/libbitcoin-public-ops-bench -R lbtc_direct --output-on-failure
out/libbitcoin-public-ops-bench/compat/libbitcoin_direct/example_lbtc_public_ops
example_lbtc_public_ops: PASS (direct C++, no C ABI/shim/bridge)
out/libbitcoin-public-ops-bench/compat/libbitcoin_direct/bench_lbtc_public_ops \ 8192 3 80 512 80 512 \ --json out/libbitcoin-public-ops-bench/lbtc_public_ops.json
out/libbitcoin-public-ops-bench/compat/libbitcoin_direct/bench_lbtc_hash256_var \ 262144 5 512 80 512 \ --json out/libbitcoin-public-ops-bench/lbtc_hash256_var.json
bench_lbtc_public_ops, production direct path:
operationthroughputxonly_validate0.15 M rows/spubkey_validate0.29 M rows/scommitment_verify0.04 M rows/stagged_hash9.39 M rows/stagged_hash_tag_overload9.42 M rows/stagged_hash_var4.46 M rows/shash2566.33 M rows/shash256_var4.11 M rows/s
bench_lbtc_hash256_var local baseline:
modethroughputpayload bandwidthns/rowserial reference4.60 M rows/s1296.68 MiB/s217.3direct CPU forced4.67 M rows/s1316.40 MiB/s214.0direct production4.64 M rows/s1307.02 MiB/s215.5
Validation completed locally
The following passed locally:
2026-07-09T07:07:38Z.1 Batch verify rate ecdsa.... (69257 / 0) = 69257 sps 2026-07-09T07:07:38Z.1 Batch verify rate schnorr.. (2669 / 0) = 2669 sps 2026-07-09T07:07:38Z.1 Capture ecdsa.... (2653358659 / 2666696373) = 99.4998% 2026-07-09T07:07:38Z.1 Capture multisig. (840674831 / 844696245) = 99.5239% 2026-07-09T07:07:38Z.1 Capture schnorr.. (289214946 / 300248374) = 96.3252% 2026-07-09T07:07:38Z.1 Capture threshold (1613084 / 1613084) = 100.0000%
Line 3099543: ecdsa_secs.......... 0 208 Line 3111469: ecdsa_secs.......... 0 218 Line 3136271: ecdsa_secs.......... 0 227 Line 3140665: ecdsa_secs.......... 0 231 Line 3143011: ecdsa_secs.......... 0 233 Line 3145955: ecdsa_secs.......... 0 235 Line 3154158: ecdsa_secs.......... 0 243 Line 3156735: ecdsa_secs.......... 0 245 Line 3159492: ecdsa_secs.......... 0 247 Line 3161866: ecdsa_secs.......... 0 249 Line 3163448: ecdsa_secs.......... 0 250 Line 3167300: ecdsa_secs.......... 0 296 Line 3167592: ecdsa_secs.......... 0 297 Line 3170877: ecdsa_secs.......... 0 461 Line 3184876: ecdsa_secs.......... 0 463 Line 3225266: ecdsa_secs.......... 0 467 Line 3231002: ecdsa_secs.......... 0 471 Line 3238837: ecdsa_secs.......... 0 477 Line 3246859: ecdsa_secs.......... 0 483 Line 3255101: ecdsa_secs.......... 0 489 Line 3263837: ecdsa_secs.......... 0 496 Line 3271855: ecdsa_secs.......... 0 502 Line 3280828: ecdsa_secs.......... 0 509 Line 3289028: ecdsa_secs.......... 0 516 Line 3294738: ecdsa_secs.......... 0 521 Line 3296660: ecdsa_secs.......... 0 523 Line 3325749: ecdsa_secs.......... 0 549 Line 3331839: ecdsa_secs.......... 0 555 Line 3340202: ecdsa_secs.......... 0 564 Line 3346980: ecdsa_secs.......... 0 571 Line 3352350: ecdsa_secs.......... 0 575 Line 3355181: ecdsa_secs.......... 0 578 Line 3372246: ecdsa_secs.......... 0 633 Line 3408260: ecdsa_secs.......... 0 940 Line 3421641: ecdsa_secs.......... 0 947 Line 3459612: ecdsa_secs.......... 0 949 Line 3477812: ecdsa_secs.......... 0 969 Line 3481143: ecdsa_secs.......... 0 973 Line 3485790: ecdsa_secs.......... 0 979 Line 3488567: ecdsa_secs.......... 0 982 Line 3522305: ecdsa_secs.......... 0 1037 Line 3578790: ecdsa_secs.......... 0 1206 Line 3617440: ecdsa_secs.......... 0 1458 Line 3637257: ecdsa_secs.......... 0 1463 Line 3662814: ecdsa_secs.......... 0 1464 Line 3665707: ecdsa_secs.......... 0 1468 Line 3668539: ecdsa_secs.......... 0 1473 Line 3671028: ecdsa_secs.......... 0 1477 Line 3674221: ecdsa_secs.......... 0 1483 Line 3677101: ecdsa_secs.......... 0 1487 Line 3679711: ecdsa_secs.......... 0 1492 Line 3683398: ecdsa_secs.......... 0 1498 Line 3692836: ecdsa_secs.......... 0 1519 Line 3694575: ecdsa_secs.......... 0 1522 Line 3696403: ecdsa_secs.......... 0 1525 Line 3702838: ecdsa_secs.......... 0 1536 Line 3706342: ecdsa_secs.......... 0 1544 Line 3706590: ecdsa_secs.......... 0 1545 Line 3708940: ecdsa_secs.......... 0 1549 Line 3710846: ecdsa_secs.......... 0 1552 Line 3712771: ecdsa_secs.......... 0 1555 Line 3714972: ecdsa_secs.......... 0 1559 Line 3716732: ecdsa_secs.......... 0 1562 Line 3718385: ecdsa_secs.......... 0 1564 Line 3723164: ecdsa_secs.......... 0 1575 Line 3723806: ecdsa_secs.......... 0 1577 Line 3724829: ecdsa_secs.......... 0 1577 Line 3725422: ecdsa_secs.......... 0 1579 Line 3820504: ecdsa_secs.......... 0 2120 Line 3861430: ecdsa_secs.......... 0 2727 Line 3902983: ecdsa_secs.......... 0 2728 Line 3986869: ecdsa_secs.......... 0 2828 Line 3996712: ecdsa_secs.......... 0 2859 Line 4005465: ecdsa_secs.......... 0 2860 Line 4008193: ecdsa_secs.......... 0 2861 Line 4008960: ecdsa_secs.......... 0 2863 Line 4080652: ecdsa_secs.......... 0 3085 Line 4210039: ecdsa_secs.......... 0 3479 Line 4226403: ecdsa_secs.......... 0 3481 Line 4256084: ecdsa_secs.......... 0 3482 Line 4274785: ecdsa_secs.......... 0 3528 Line 4277364: ecdsa_secs.......... 0 3534 Line 4281666: ecdsa_secs.......... 0 3545 Line 4281896: ecdsa_secs.......... 0 3545 Line 4284034: ecdsa_secs.......... 0 3551 Line 4287942: ecdsa_secs.......... 0 3559 Line 4293153: ecdsa_secs.......... 0 3574 Line 4293240: ecdsa_secs.......... 0 3574 Line 4295677: ecdsa_secs.......... 0 3579 Line 4297631: ecdsa_secs.......... 0 3584 Line 4300973: ecdsa_secs.......... 0 3592 Line 4305877: ecdsa_secs.......... 0 3604 Line 4307911: ecdsa_secs.......... 0 3608 Line 4311861: ecdsa_secs.......... 0 3621 Line 4311944: ecdsa_secs.......... 0 3622 Line 4320246: ecdsa_secs.......... 0 3642 Line 4321420: ecdsa_secs.......... 0 3645 Line 4322567: ecdsa_secs.......... 0 3647 Line 4323445: ecdsa_secs.......... 0 3649 Line 4324391: ecdsa_secs.......... 0 3651 Line 4380313: ecdsa_secs.......... 0 3827 Line 4380354: ecdsa_secs.......... 0 3828 Line 4422843: ecdsa_secs.......... 0 4405 Line 4443872: ecdsa_secs.......... 0 4436 Line 4621926: ecdsa_secs.......... 0 5367 Line 4697071: ecdsa_secs.......... 0 6726 Line 4697072: schnorr_secs........ 0 6726 Line 4745191: ecdsa_secs.......... 0 6728 Line 4745192: schnorr_secs........ 0 6728 Line 4752320: ecdsa_secs.......... 0 6729 Line 4752321: schnorr_secs........ 0 6729 Line 4895217: ecdsa_secs.......... 0 9750 Line 4895222: schnorr_secs........ 0 9750 Line 4922642: ecdsa_secs.......... 0 9752 Line 4922643: schnorr_secs........ 0 9752 Line 4975868: ecdsa_secs.......... 0 9768 Line 4975869: schnorr_secs........ 0 9768 Line 5154365: ecdsa_secs.......... 0 13481 Line 5154366: schnorr_secs........ 0 13481 Line 5154797: ecdsa_secs.......... 0 13484 Line 5154798: schnorr_secs........ 0 13484 Line 5170531: ecdsa_secs.......... 0 13486 Line 5170532: schnorr_secs........ 0 13486 Line 5195071: ecdsa_secs.......... 0 13576 Line 5195072: schnorr_secs........ 0 13576 Line 5206153: ecdsa_secs.......... 0 13577 Line 5206154: schnorr_secs........ 0 13577 Line 5213469: ecdsa_secs.......... 0 13628 Line 5213470: schnorr_secs........ 0 13629 Line 5279596: ecdsa_secs.......... 0 14874 Line 5279597: schnorr_secs........ 0 14874 Line 5280768: ecdsa_secs.......... 0 14880 Line 5280769: schnorr_secs........ 0 14880 Line 5379668: ecdsa_secs.......... 0 16326 Line 5379669: schnorr_secs........ 0 16326 Line 5392669: ecdsa_secs.......... 0 16329 Line 5392670: schnorr_secs........ 0 16329 Line 5405492: ecdsa_secs.......... 0 16332 Line 5405493: schnorr_secs........ 0 16332 Line 5411567: ecdsa_secs.......... 0 16334 Line 5411568: schnorr_secs........ 0 16334 Line 5416461: ecdsa_secs.......... 0 16335 Line 5416462: schnorr_secs........ 0 16335 Line 5433876: ecdsa_secs.......... 0 16463 Line 5433877: schnorr_secs........ 0 16463 Line 5439143: ecdsa_secs.......... 0 16552 Line 5439144: schnorr_secs........ 0 16552 Line 5441714: ecdsa_secs.......... 0 16686 Line 5441715: schnorr_secs........ 0 16686 Line 5442130: ecdsa_secs.......... 0 16693 Line 5442131: schnorr_secs........ 0 16693 Line 5445356: ecdsa_secs.......... 0 16738 Line 5445357: schnorr_secs........ 0 16738 Line 5448103: ecdsa_secs.......... 0 16798 Line 5448104: schnorr_secs........ 0 16798 Line 5450293: ecdsa_secs.......... 0 16930 Line 5450294: schnorr_secs........ 0 16930 Line 5450365: ecdsa_secs.......... 0 16931 Line 5450366: schnorr_secs........ 0 16931 Line 5453790: ecdsa_secs.......... 0 16985 Line 5453791: schnorr_secs........ 0 16985 Line 5461475: ecdsa_secs.......... 0 17474 Line 5461476: schnorr_secs........ 0 17474 Line 5461545: ecdsa_secs.......... 0 17482 Line 5461546: schnorr_secs........ 0 17482 Line 5465487: ecdsa_secs.......... 0 17756 Line 5465488: schnorr_secs........ 0 17756 Line 5471006: ecdsa_secs.......... 0 17760 Line 5471007: schnorr_secs........ 0 17760 Line 5474932: ecdsa_secs.......... 0 18057 Line 5474933: schnorr_secs........ 0 18057 Line 5481748: ecdsa_secs.......... 0 18066 Line 5481749: schnorr_secs........ 0 18066 Line 5490495: ecdsa_secs.......... 0 18710 Line 5490496: schnorr_secs........ 0 18710 Line 5499164: ecdsa_secs.......... 0 18711 Line 5499165: schnorr_secs........ 0 18711
locks or copy and malloc excess) but instead by insufficient control over batch sizes.
The config sets a minimum batch size, and then the node cuts off the capture in the validation message pipeline. However because a full window worth of block validations (e.g. 50k blocks) can pass that gate almost immediately, being queued up for concurrent validate, the result is a pressure wave that eventually cuts out all batch draining until the window closes. This dramatically oversized the batch tables creating a large amount of I/O back pressure.
This was exacerbated by an oversight in the calculation of the window itself when batching, as using the prevalid state (validated and batch accumulated, but sigs not yet verified) and the valid state (post-verify) led to a doubling of the intended window size. After resolving this so that it maintains the window only on the valid state, performance was materially improved. But the major issue of excessive table sizing remained.
Prior to optimizing above, the full validation run bypassing sig verify took about 6:00. After the above changes that was down to around 4:00. Then made the change to increase control over table excesses, but moving the batch cutoff out of the ASIO message pump and into the executing validations. At the same time I removed the reader-writer mutex lock that was set up to guard the batch tables during verify, replaced with an atomic turnstile. This reduced the above total time to 3:18 as it brought batch tables into a very well-controlled range (1.0 - 1.2x of target).
However the turnstile was set up to allow blocks to continue to validate (unbatched, with inline sig verify) when the batch cutoff was signaled. This allowed about 24% of all batchable signatures to bypass capture. That's about 43 minutes of pure sig verify cost. This is far greater than optimal, so I've modified the turnstile to allow blocks to write into the batch cutoff, while still allowing them to validate inline during the batch verify. It may turn out that validating during GPU verify is also suboptimal, as pre and post-processing the GPU may be slowed by it, in which case that can be adjusted as well.3:18 to 3:00. That implies a full validation run with CPU batching should be around 5:15, noting that full unbatched validation run on maxi is 5:00.log and event capture. notifications are via ws, subscription via http or ws (same model as native). These are the subscribers, responses are all json. embedded (or file system) site pages are exposed just as with native.
/v1/log/subscribe?filter=1024 /v1/event/subscribe?filter=8
filter is a bitmask of log or event types, where it currently maps each bit to the corresponding enum ordinal position. A filter value of zero drops the subscription. The server only holds the mask and one closure for each, where the closure captures message traffic and filters by the mask, sending a single ws message for each match.950,000 in 39 mins and was emitting events and log messages over websockets almost the entire time.[admin] bind = 127.0.0.1:81 connections = 100
37.7 minutes to 950,000. Constant emission of logs and events via websockets on the previous run was just under 39 mins, within normal variance.shelly first milestone sync comes in at 34.5 mins, on the same 2.5 Gbps network as maxi but still managing to shave 3.3 mins off the record. I believe this ties the record set by @josie on his 10 Gbps network. That was with default config, so I'll vary that a bit, but time now for some batching and validation.shelly first signature batch (batched sigs unverified) sync comes in at 1.93 hrs, just a few minutes shy of @josie batch with verify. This was also my default config from maxi (batching only 1m at a time). With full verify on this machine, full validation should come in around 2.0 hrs.shelly just hit 1:40 full validation (with no batching).
That's down from 4.85 on maxi and handily beats @josie's former non-batching record (though I forget the number) and even ekes out a win over his fully-batched 1:50 former record!44.8 mins Shelly crushed the milestone with address index (Electrum) record. IIRC it was previously ~90 mins on maxi. Of course this also includes full tx and spender indexes, which are always available in libbitcoin.27.91 mins. The only change was a reduction in the network thread count from 16 to 12.28.2 mins milestone (essentially the same as 12).30 minutes on 8 fast threads and lots of RAM. But given that the run is almost entirely append, the RAM is not as significant as it might seem. The flush to disk just has to keep up with the network, which is very reasonable.30.3 mins milestone, so the bottom is somewhere between 8 and 16. But 30 minutes, on 4 threads!database::settings) and read them out at startup via dedicated query. When they differ it can issue a startup warning. Just 4 bytes added to the end of the first txs (slab hashmap) table blob (genesis tx association), next to the 1 byte for the Merkle root cache depth. github.com/libbitcoin/libbitcoin-database/pull/85929.6 mins, so it's actually faster to use the window (which is the cause of the stepping above). The above numbers of confirmation, not download. Those are loosely coupled, but because of the window closures, confirmation shares CPU times at the end of each window, and closes out conformation state. Otherwise there is a very long tail at the end of the run where all of the confirmations live, and at that point the data is no longer proximal, so a little more expensive.1.7 hrs full validation on 12 network threads, now isolating optimal node (validator) thread count.1.47 hrs with 12 network threads, but increased from 64 to 128 validation threads (matching physical).00:15 and validation at 1:15.ins and points tables into an aggregate hashmap, same idea as the batch/silent tables but for different reason. This means these settings are no longer a valid:
[database] point_buckets = point_size = point_rate =
point_buckets):
[database] ins_buckets =
ins_size changes to 34250000000, which is the combined size defaults for the former two tables, now two columns.23.5 mins. It's possible thread count can go all the way to 128 given this change, but I'll prob hit bandwidth limits before that.write contention has entirely disappeared, where before the above change it was dominant and milestone CPU has dropped to almost nothing, averaging <15%, saying the same thing (contention burns CPU). And we certainly are not writing at bus/RAM speed (no disk I/O at all given the RAM size). So this is a picture of being network limited.22.5 mins (including 30 secs for header sync, which is purposely not peer-optimized) and I am hitting 23.5 mins. So just 60 secs below the absolute limit, if all we did was transfer the full chain as a file in a single stream.unlimited on this machine by any factors other than network, until 100Gbps. And at 100Gbps (>20x my current speed), a milestone run to 950k takes 60 seconds. And at that point it's up against nothing but CPU and RAM speed. Milestone is fully scalable with hardware at this point. The only exceptions are the header sync and the confirmation chase (which can easily be made fully parallel in milestone).-30 secs) in full validation either, for the same reason. When it's downloading it's already up against the limit. But that will now compress all the way to ~zero given sufficient bandwidth.milestone record 23.0 mins.59 minutes full validation with GPU verify.14 mins milestone to 950k, owing to his 10 Gbps network. Now I've gotta optimize milestone confirmation, because it looks like that cost him an extra 5 mins which we can parallelize out.1:21.1.13 hrs where it was formerly 1.95 hrs. Write contention on the capture path is now gone, as is capture leakage. So on a lower CPU machine with a good GPU (like @josie 's) the improvement should be significant.Ultrafast as a drop-in secp256k1 replacement (no GPU linkage). The straight inline validation run came it at +1 second vs. the baseline of 1.35 hrs, so these are equivalent. The batched CPU run came in at 1.65 hrs which I assume will also match the a baseline from secp256k1 (running now).
While it would be nice if we got a CPU bump from UF, it's sufficient/excellent that we are seeing parity. This allows it to be set up as a dependency swap, as opposed to an additional dependency. And the GPU functionality can be incorporated into the statically-linked builds for use where GPU is available, with the engine dropping into CPU where not.
However since full validation CPU batching is always slower than inline, this will remain an important configuration knob. The user or outer controller will have to termine whether it's worth turning on batching. If it's turned on and the GPU is slow, unsupported, or missing - UF will drop into CPU verification. But at least (in this worst case) the difference (20%) between batched and inline CPU is not huge.set_strong(...) parallel within a block, which should speed up confirmations, especially at the end of a milestone run. Maybe get us close to 10 mins (from 14 presently).1M signatures being evaluated in a parallel loop over libsecp256k1.3,800 of those spikes to validate mainnet.1.65 hrs is going to be significantly faster than libsecp256k1 iteration. However, at least on this machine, inline CPU verification is even faster, at 1.35 hrs.R.x mod n == r check and does not provide the recovery parity needed to reconstruct R unambiguously. So any wording suggesting “Pippenger ECDSA” is misleading unless it refers to generic MSM or a recoverable/explicit-R variant.but this need much better batch management
yes much smaller but all depends hardware
vm.dirty_ratioet al., system-wide); macOS's UBC hardcodes it with no interface at all. Net result: on a 796GB machine with the RAM bought and paid for, macOS permits roughly 20GB of accumulated dirty mmap before forced writeback throttles the writers — while Windows lets the accumulate-then-flush design work exactly as intended, by default. And the whole regime is futile anyway: the overcommit it protects still breaks eventually; it just chooses when to break someone else's process.[4354091.298471] bs invoked oom-killer: gfp_mask=0x140cca(GFP_HIGHUSER_MOVABLE|__GFP_COMP), order=0, oom_score_adj=0 [4354091.298521] oom_kill_process.cold+0xa/0xb7 [4354091.298886] [ pid ] uid tgid total_vm rss rss_anon rss_file rss_shmem pgtables_bytes swapents oom_score_adj name [4354091.299165] oom-kill:constraint=CONSTRAINT_NONE,nodemask=(null),cpuset=/,mems_allowed=0-1,global_oom,task_memcg=/user.slice/user-1000.slice/session-45.scope,task=bs,pid=4257,uid=1000 [4354091.299406] Out of memory: Killed process 4257 (bs) total-vm:1772734212kB, anon-rss:39880944kB, file-rss:8kB, shmem-rss:0kB, UID:1000 pgtables:1801732kB oom_score_adj:0 [4354093.703073] oom_reaper: reaped process 4257 (bs), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB
posix_spawn (1999), Apple's own frameworks discourage fork-without-exec (Objective-C/Swift runtimes are fork-unsafe by design), and fork doesn't exist on Windows NT, nor in any modern OS research design — Fuchsia's Zircon, for instance, deliberately omitted it. The 2019 HotOS paper "A fork() in the road" (Microsoft Research/BU) makes essentially your argument academically: fork is a terrible abstraction whose costs — overcommit among them — are systemic. The technique outlived its usefulness decades ago; the memory-accounting regime it necessitated is the part that refuses to die."vm.overcommit_memory = 2 (strict, meaning correct, accounting). So when a JVM reserves a huge heap, and then forks it because muh, never heard of threads, it will get a failure if there is insufficient actual reservable virtual memory to accommodate what has been requested.vm.overcommit_memory = 2 and watch applications fail because they assume large overcommit, and watch libbitcoin keep going because it does not. All of our mmap is file-backed, and when there is insufficient space, we handle the disk full condition gracefully.is_current(...) overloads to eliminate ambiguities, and this one got the wrong name. PR welcome.Mac Pro (which now needs a name), milestone to 950,000 completed in 101 mins over my (single) 2.5 (2.3) Gbps Internet. This is the fastest Mac sync ever by a wide margin, based on a prototype mmap replacement that controls write flushing rationally. It's a 5 year old machine with 796GB RAM, 32 threads and without shani. So it should be network limited but is currently about twice that. But now that's just optimization, not the broken OS. This can also be applied to Linux with no changes, which would avoid the need for OS paging config.722,933 confirmed in ~30 minutes.56 mins, but 4 mins was spent in a shutdown/restart to force the flush at 500GB. So with managed dirty flushing the big mac will be network limited, just as maxi. And that also implies that our mac mini should also be network limited, at about 50 mins. So once the details are worked out, macOS will be a first class citizen. And instead of prompting for OS config, we can poorly-configured (default) Linux on the same path.--build-config=release) have been defaulting to !NDEBUG (debug build), which is a dramatic performance hit. This can be avoided by explicitly setting --enable-ndebug until the default is updated. CMake and MSVC unaffected.full validation record time is 26 mins, and milestone is 14 mins.
We got an offer today to perform benchmarking on a 100 Gbps network. If we remain network limited that would reduce milestone to less than 2 mins (dunno the specs available for validation).master, 2026-07-21/23: system 8f0c082, database 6cf5992, network 12ff6dc, node 5ae5d93, server 6828fdd.
Toolchain: VS 2026 (18.8.12021.73), toolset *v145*, vs2026 solutions. Dependency: *UltrafastSecp256k1-vc145 4.5.0.0*.
v145 is required, not preferred — the vc143 package references __std_rotate, which exists only in the v145 STL, so it will not link against any v143 toolset.
msbuild libbitcoin-<repo>.sln /p:Configuration=StaticRelease /p:Platform=x64 /p:PreferredToolArchitecture=x64 /p:Option-sha=true /p:Option-ultrafast=true /p:Option-cuda=true /m:2 /p:MultiProcessorCompilation=false
Option-ultrafast defines WITH_ULTRAFAST → HAVE_ULTRAFAST and sets Linkage-ultrafast=static. Option-cuda links secp256k1_gpu_host + secp256k1_cuda + cudart_static and adds /INCLUDE:secp256k1_gpu_columns_provider_anchor. *Both default to false* — build without them and it compiles clean, links no GPU, and silently runs CPU-only. Verify with a 25.6 MB .nv_fatb section in the resulting binary. (/m:2 + MultiProcessorCompilation=false only works around C1060: out of heap space on bs.vcxproj.)
*Runtime config* (non-defaults only)
ini [bitcoin] milestone = 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f:0 checkpoint = 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f:0 [node] batch_signatures = 100000 maximum_concurrency = 4000 delay_inbound = false thread_priority = true memory_priority = true
milestone is height 950,000 and bypass = is_under_checkpoint(height) || is_milestone(link), so defaults skip script/signature validation for ~99.3% of the chain. Pinned to genesis, nothing is bypassed.
batch_signatures is the only GPU-related knob in libbitcoin — upstream default is now 0 (inline CPU verify). Non-zero routes through the ufsecp bridge, which binds CUDA via UFSECP_LBTC_AUTO. No device selector, no env var. Process ran under a Job Object at 75% CPU cap.
*Results — 959,516 blocks, all signature-verified*
- 22,450 s elapsed; tip reached at ~3 h, remainder idle at tip
- signature verification: *219 s total* — ecdsa_secs 11 s over 27,835 batches, schnorr_secs 208 s over 9,951 batches
- 27,834 of 27,835 ECDSA batches completed under the log's 1 s resolution; the single 11 s outlier is first-batch CUDA context/module load
- observed per-batch rates 4–8M sigs/sec, consistent with the aggregate
*What limited it — storage, per the run's own disk sampler (33,501 samples)*
Reads climb from 27 MB/s to a 3,309 MB/s 15-minute average, peaking at 5,166 MB/s. Writes stay bursty and modest. That is prevout resolution: random lookups into the archive tables, growing with inputs-per-block and table size.
archived − validated gap: p50 1,337, p90 3,954, max *4,000* — exactly maximum_concurrency. Download was pinned at its ceiling waiting on validation to drain, so this was validation-limited, not bandwidth-limited. Raising the window would not help.[bitcoin] # only known block to this user is genesis. milestone = 000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f:0 [node] # default maximum_concurrency is 50,000, so can just leave out. maximum_concurrency = 50,000 # clean stop point for benchmark comparisons. maximum_height = 950,000 # defaults to 0 (disabled) and this is a good typical size. batch_signatures = 1,000,000 [database] # the batch tables are disabled by default, so set initial capacity. ecdsa_size = 100,000,000 schnorr_size = 10,000,000 prevalid_size = 1,000,000(edited)
[table.ecdsa] size = 100,000,000 [table.schnorr] size = 10,000,000 [table.prevalid] size = 1,000,000

Seems to be stuck a few hundred blocks from 950000
batch_ecdsa_* 152 MB total (identity 7, key 37, message 36, signature 72)
batch_schnorr_* 130 MB total
~283 MB, not sparse (apparent == allocated)batch_ecdsa_signature.data at 74,573,056 bytes ÷ 64 B/sig ≈ 1.16M rows — i.e. the table settled at roughly the 1M batch target, not the 100M I configured. So ecdsa_size = 100000000 did not translate into 100M rows of resident allocation, and the memory-pressure-from-preallocation theory has no support. The RAM pressure is real (mean working set 39.9 GB, free RAM <2 GB for 48.7% of the run) but it comes from the ~890 GB archive store being mapped, not from the batch tables.
Caveat: those are post-purge sizes, so they bound the resting state, not necessarily the peak.
Re-plotting at 4× finer resolution since he flagged the sampling rate:
Correction to my own earlier figures — they were contaminated by the post-completion idle tail:
Sending the 15s plot, but the numbers matter more — my earlier CPU figures were wrong.
I was wrong about CPU, and evoskuil is closer to right
My earlier stats included 5,115 s of post-completion idle — the node hit maximum_height at 15,406 s but the sampler ran to 20,528 s. Restricted to the actual run:
contaminated run window only
bs CPU mean 35.4% 46.9%
median — 54.1%
p90 — 73.7%
max 76.2% 76.2%
at/near 75% cap 6.6% 8.7%
below 15% 37.6% 17.2%CpuPercent would buy you very little" was wrong. It is now the single most obvious thing to test.
What is left after that
stall episodes 51 median 30s p90 135s max 165s total stalled 2,520s = 16.4% of the run free RAM < 2 GB 64.6% of samples
batch_ecdsa_signature.data at ~1.16M rows, i.e. about the 1M batch target rather than the 100M configured. No 15 GB, no support for my memory-pressure theory. Worth noting his own suggested ecdsa_size = 100000000 against batch_signatures = 1000000 is 100× the target, so "size in relation to the batch target" argues for ~1M there.
Schnorr still looks anomalous. His model says schnorr is faster per signature and always the smaller batch, so it should be a rounding error. Measured: schnorr 1,740 s over 1,137 batches versus ecdsa 2 s over 3,111 batches. Smaller batches, faster per-sig algorithm, 870× the total time. If ultrafast were merely slower at 1M it should hurt ecdsa at least as much — it did not, ecdsa actually improved from 11 s to 2 s. That points at the schnorr path specifically rather than at batch size.
...ecdsa_size = 100000000 did not translate into 100M rowsAnd memory pressure is worse than I reported: nearly two-thirds of the run had under 2 GB free.
Measured: schnorr 1,740 s over 1,137 batches versus ecdsa 2 s over 3,111 batches.
During those stalls reads drop to 266 MB/s while CPU sits under 15%
[database] ecdsa_size = 100,000,000 schnorr_size = 10,000,000 prevalid_size = 1,000,000
1hr on the Big Mac is an insane win. This machine took at least 10 hours last time I checked.
950k (72min -> 63min), no tampering with any kernel parameters, no need for sudo.posix memory management replacement is now merged. It's the only option (no option) on macOS, and can be optionally compiled (as shown immediately above) on Linux. The Linux preprocessor flag that currently enables it on Linux is MANAGE_STAGING, which I do not recommend setting on Windows builds 🙂. I'll bet exposing that more cleanly and adding some config to allow tuning of the memory manager's constants.table config has been moved into individual sections per table:
[table.header] buckets = rate = size = [table.txs] buckets = rate = size = [table.tx] buckets = rate = size = [table.ins] buckets = rate = size = [table.outs] rate = size = [table.input] rate = size = [table.output] rate = size = [table.candidate] rate = size = [table.confirmed] rate = size = [table.strong_tx] rate = size = [table.duplicate] buckets = rate = size = [table.prevout] buckets = rate = size = [table.validated_bk] buckets = rate = size = [table.validated_tx] buckets = rate = size = [table.ecdsa] rate = size = [table.schnorr] rate = size = [table.silent] rate = size = [table.address] buckets = rate = size = [table.filter_tx] buckets = rate = size = [table.filter_bk] buckets = rate = size =
[bitcoind] credential = user:pass
[bitcoind] username = user password = pass
[bitcoind] credential = user1:pass1 credential = user2:pass2(edited)
6:19, which is proportional to its CPU power. Once the core problem was figured out the resolution was relatively straightforward. Basically macOS runs a linear search across all memory pages every 30 seconds, to identify evictable pages. As size grows the search cost exceeds 30 seconds, and the machine essentially locks itself up in a vain attempt to preserve memory, despite having a huge excess of memory. Easy for us to work around. Hard to take this operating system seriously.14 mins (milestone) and 26 mins (GPU validate) from @josie on his 10Gbps network.daemon and Windows service install/uninstall with automatic start controlled shutdown and are now implemented. Installation and uninstallation are both via a new command line switch (--daemon or -d). There is an optional Boolean parameter, defaults to true (false for uninstall). This works in conjunction with the existing --config (or -c) parameter, which takes the path to the config file. The process is loaded from the existing path to the binary that executed the command, and the config file parameter is resolved to an explicit path. There is also a new --user (or -u) parameter that accepts a username:password for the process.user logoff and other process termination suspension. The hard part was Windows, but that's been done for some time. And it turns out that the signal handling already in place satisfies macOS and Linux.