precalculate SipHash constant salt XORs #30442

pull l0rinc wants to merge 6 commits into bitcoin:master from l0rinc:paplorinc/siphash changing 9 files +112 −115
  1. l0rinc commented at 3:01 pm on July 12, 2024: contributor

    This change is part of [IBD] - Tracking PR for speeding up Initial Block Download

    Summary

    The in-memory representation of the UTXO set uses (salted) SipHash to avoid key collision attacks.

    Hashing uint256 keys is performed frequently throughout the codebase. Previously, specialized optimizations existed as standalone functions (SipHashUint256 and SipHashUint256Extra), but the constant salting operations (C0-C3 XOR with keys) were recomputed on every call.

    This PR introduces PresaltedSipHasher, a class that caches the initial SipHash state (v0-v3 after XORing constants with keys), eliminating redundant constant computations when hashing multiple values with the same keys. The optimization is applied uniformly across:

    • All Salted*Hasher classes (SaltedUint256Hasher, SaltedTxidHasher, SaltedWtxidHasher, SaltedOutpointHasher)
    • CBlockHeaderAndShortTxIDs for compact block short ID computation

    Details

    The change replaces the standalone SipHashUint256 and SipHashUint256Extra functions with PresaltedSipHasher class methods that cache the constant-salted state. This is particularly beneficial for hash map operations where the same salt is used repeatedly (as suggested by Sipa in #30442 (comment)).

    CSipHasher behavior remains unchanged; only the specialized uint256 paths and callers now reuse the cached state instead of recomputing it.

    Measurements

    Benchmarks were run using local SaltedOutpointHasherBench_* microbenchmarks (not included in this PR) that exercise SaltedOutpointHasher in realistic std::unordered_set scenarios.

      0diff --git a/src/bench/crypto_hash.cpp b/src/bench/crypto_hash.cpp
      1--- a/src/bench/crypto_hash.cpp	(revision 9b1a7c3e8dd78d97fbf47c2d056d043b05969176)
      2+++ b/src/bench/crypto_hash.cpp	(revision e1b4f056b3097e7e34b0eda31f57826d81c9d810)
      3@@ -2,7 +2,6 @@
      4 // Distributed under the MIT software license, see the accompanying
      5 // file COPYING or http://www.opensource.org/licenses/mit-license.php.
      6
      7-
      8 #include <bench/bench.h>
      9 #include <crypto/muhash.h>
     10 #include <crypto/ripemd160.h>
     11@@ -12,9 +11,11 @@
     12 #include <crypto/sha512.h>
     13 #include <crypto/siphash.h>
     14 #include <random.h>
     15-#include <span.h>
     16 #include <tinyformat.h>
     17 #include <uint256.h>
     18+#include <primitives/transaction.h>
     19+#include <util/hasher.h>
     20+#include <unordered_set>
     21
     22 #include <cstdint>
     23 #include <vector>
     24@@ -205,6 +206,98 @@
     25     });
     26 }
     27
     28+static void SaltedOutpointHasherBench_hash(benchmark::Bench& bench)
     29+{
     30+    FastRandomContext rng{/*fDeterministic=*/true};
     31+    constexpr size_t size{1000};
     32+
     33+    std::vector<COutPoint> outpoints(size);
     34+    for (auto& outpoint : outpoints) {
     35+        outpoint = {Txid::FromUint256(rng.rand256()), rng.rand32()};
     36+    }
     37+
     38+    const SaltedOutpointHasher hasher;
     39+    bench.batch(size).run([&] {
     40+        size_t result{0};
     41+        for (const auto& outpoint : outpoints) {
     42+            result ^= hasher(outpoint);
     43+        }
     44+        ankerl::nanobench::doNotOptimizeAway(result);
     45+    });
     46+}
     47+
     48+static void SaltedOutpointHasherBench_match(benchmark::Bench& bench)
     49+{
     50+    FastRandomContext rng{/*fDeterministic=*/true};
     51+    constexpr size_t size{1000};
     52+
     53+    std::unordered_set<COutPoint, SaltedOutpointHasher> values;
     54+    std::vector<COutPoint> value_vector;
     55+    values.reserve(size);
     56+    value_vector.reserve(size);
     57+
     58+    for (size_t i{0}; i < size; ++i) {
     59+        COutPoint outpoint{Txid::FromUint256(rng.rand256()), rng.rand32()};
     60+        values.emplace(outpoint);
     61+        value_vector.push_back(outpoint);
     62+        assert(values.contains(outpoint));
     63+    }
     64+
     65+    bench.batch(size).run([&] {
     66+        bool result{true};
     67+        for (const auto& outpoint : value_vector) {
     68+            result ^= values.contains(outpoint);
     69+        }
     70+        ankerl::nanobench::doNotOptimizeAway(result);
     71+    });
     72+}
     73+
     74+static void SaltedOutpointHasherBench_mismatch(benchmark::Bench& bench)
     75+{
     76+    FastRandomContext rng{/*fDeterministic=*/true};
     77+    constexpr size_t size{1000};
     78+
     79+    std::unordered_set<COutPoint, SaltedOutpointHasher> values;
     80+    std::vector<COutPoint> missing_value_vector;
     81+    values.reserve(size);
     82+    missing_value_vector.reserve(size);
     83+
     84+    for (size_t i{0}; i < size; ++i) {
     85+        values.emplace(Txid::FromUint256(rng.rand256()), rng.rand32());
     86+        COutPoint missing_outpoint{Txid::FromUint256(rng.rand256()), rng.rand32()};
     87+        missing_value_vector.push_back(missing_outpoint);
     88+        assert(!values.contains(missing_outpoint));
     89+    }
     90+
     91+    bench.batch(size).run([&] {
     92+        bool result{false};
     93+        for (const auto& outpoint : missing_value_vector) {
     94+            result ^= values.contains(outpoint);
     95+        }
     96+        ankerl::nanobench::doNotOptimizeAway(result);
     97+    });
     98+}
     99+
    100+static void SaltedOutpointHasherBench_create_set(benchmark::Bench& bench)
    101+{
    102+    FastRandomContext rng{/*fDeterministic=*/true};
    103+    constexpr size_t size{1000};
    104+
    105+    std::vector<COutPoint> outpoints(size);
    106+    for (auto& outpoint : outpoints) {
    107+        outpoint = {Txid::FromUint256(rng.rand256()), rng.rand32()};
    108+    }
    109+
    110+    bench.batch(size).run([&] {
    111+        std::unordered_set<COutPoint, SaltedOutpointHasher> set;
    112+        set.reserve(size);
    113+        for (const auto& outpoint : outpoints) {
    114+            set.emplace(outpoint);
    115+        }
    116+        ankerl::nanobench::doNotOptimizeAway(set.size());
    117+    });
    118+}
    119+
    120 static void MuHash(benchmark::Bench& bench)
    121 {
    122     MuHash3072 acc;
    123@@ -276,6 +369,10 @@
    124 BENCHMARK(SHA256_32b_AVX2, benchmark::PriorityLevel::HIGH);
    125 BENCHMARK(SHA256_32b_SHANI, benchmark::PriorityLevel::HIGH);
    126 BENCHMARK(SipHash_32b, benchmark::PriorityLevel::HIGH);
    127+BENCHMARK(SaltedOutpointHasherBench_hash, benchmark::PriorityLevel::HIGH);
    128+BENCHMARK(SaltedOutpointHasherBench_match, benchmark::PriorityLevel::HIGH);
    129+BENCHMARK(SaltedOutpointHasherBench_mismatch, benchmark::PriorityLevel::HIGH);
    130+BENCHMARK(SaltedOutpointHasherBench_create_set, benchmark::PriorityLevel::HIGH);
    131 BENCHMARK(SHA256D64_1024_STANDARD, benchmark::PriorityLevel::HIGH);
    132 BENCHMARK(SHA256D64_1024_SSE4, benchmark::PriorityLevel::HIGH);
    133 BENCHMARK(SHA256D64_1024_AVX2, benchmark::PriorityLevel::HIGH);
    

    cmake -B build -DBUILD_BENCH=ON -DCMAKE_BUILD_TYPE=Release && cmake –build build -j$(nproc) && build/bin/bench_bitcoin -filter=‘SaltedOutpointHasherBench’ -min-time=10000

    Before:

    ns/op op/s err% total benchmark
    58.60 17,065,922.04 0.3% 11.02 SaltedOutpointHasherBench_create_set
    11.97 83,576,684.83 0.1% 11.01 SaltedOutpointHasherBench_hash
    14.50 68,985,850.12 0.3% 10.96 SaltedOutpointHasherBench_match
    13.90 71,942,033.47 0.4% 11.03 SaltedOutpointHasherBench_mismatch

    After:

    ns/op op/s err% total benchmark
    57.27 17,462,299.19 0.1% 11.02 SaltedOutpointHasherBench_create_set
    11.24 88,997,888.48 0.3% 11.04 SaltedOutpointHasherBench_hash
    13.91 71,902,014.20 0.2% 11.01 SaltedOutpointHasherBench_match
    13.29 75,230,390.31 0.1% 11.00 SaltedOutpointHasherBench_mismatch

    compared to master:

    0create_set - 17,462,299.19 / 17,065,922.04 - 2.3% faster
    1hash       - 88,997,888.48 / 83,576,684.83 - 6.4% faster
    2match      - 71,902,014.20 / 68,985,850.12 - 4.2% faster
    3mismatch   - 75,230,390.31 / 71,942,033.47 - 4.5% faster
    

    C++ compiler …………………….. GNU 13.3.0

    Before:

    ns/op op/s err% ins/op cyc/op IPC bra/op miss% total benchmark
    136.76 7,312,133.16 0.0% 1,086.67 491.12 2.213 119.54 1.1% 11.01 SaltedOutpointHasherBench_create_set
    23.82 41,978,882.62 0.0% 252.01 85.57 2.945 4.00 0.0% 11.00 SaltedOutpointHasherBench_hash
    60.42 16,549,695.42 0.1% 460.51 217.04 2.122 21.00 1.4% 10.99 SaltedOutpointHasherBench_match
    78.66 12,713,595.35 0.1% 555.59 282.52 1.967 20.19 2.2% 10.74 SaltedOutpointHasherBench_mismatch

    After:

    ns/op op/s err% ins/op cyc/op IPC bra/op miss% total benchmark
    135.38 7,386,349.49 0.0% 1,078.19 486.16 2.218 119.56 1.1% 11.00 SaltedOutpointHasherBench_create_set
    23.67 42,254,558.08 0.0% 247.01 85.01 2.906 4.00 0.0% 11.00 SaltedOutpointHasherBench_hash
    58.95 16,962,220.14 0.1% 446.55 211.74 2.109 20.86 1.4% 11.01 SaltedOutpointHasherBench_match
    76.98 12,991,047.69 0.1% 548.93 276.50 1.985 20.25 2.3% 10.72 SaltedOutpointHasherBench_mismatch
    0compared to master:
    1create_set -  7,386,349.49 / 7,312,133.16  - 1.0% faster
    2hash       - 42,254,558.08 / 41,978,882.62 - 0.6% faster
    3match      - 16,962,220.14 / 16,549,695.42 - 2.4% faster
    4mismatch   - 12,991,047.69 / 12,713,595.35 - 2.1% faster
    
  2. DrahtBot commented at 3:01 pm on July 12, 2024: contributor

    The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

    Code Coverage & Benchmarks

    For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/30442.

    Reviews

    See the guideline for information on the review process.

    Type Reviewers
    ACK laanwj
    Concept ACK jonatack
    Approach ACK Raimo33, vasild
    Stale ACK ismaelsadeeq, sipa, ryanofsky, achow101

    If your review is incorrectly listed, please copy-paste <!–meta-tag:bot-skip–> into the comment that the bot should ignore.

    Conflicts

    No conflicts as of last run.

  3. l0rinc force-pushed on Jul 12, 2024
  4. l0rinc renamed this:
    Precalculate SipHash constant XOR with k0 and k1 in SaltedOutpointHasher
    optimization: Precalculate SipHash constant XOR with k0 and k1 in SaltedOutpointHasher
    on Jul 12, 2024
  5. l0rinc marked this as ready for review on Jul 13, 2024
  6. l0rinc commented at 12:40 pm on July 14, 2024: contributor
    @andrewtoth, this is another tiny addition to the coincache speedup, your review would be welcome.
  7. andrewtoth commented at 3:36 pm on July 14, 2024: contributor

    I did not see any improvement in the benchmark with this change. Running ./src/bench/bench_bitcoin -filter=.*Out[pP]oint.*

    commit 3b7b82b4b0c39a38538aae2cba10bec3907c5cbf

    0|               ns/op |                op/s |    err% |          ins/op |          cyc/op |         bra/op |   miss% |     total | benchmark
    1|--------------------:|--------------------:|--------:|----------------:|----------------:|---------------:|--------:|----------:|:----------
    2|                1.31 |      766,241,272.84 |    0.2% |            0.00 |            0.00 |           0.00 |    0.0% |      0.01 | `COutPoint_equality_match`
    3|                0.64 |    1,561,309,649.35 |    0.0% |            0.00 |            0.00 |           0.00 |    0.0% |      0.01 | `COutPoint_equality_mismatch`
    4|              107.62 |        9,292,298.54 |    1.7% |            0.00 |            0.00 |           0.00 |    0.0% |      0.10 | `SaltedOutpointHasherBenchmark_create_set`
    5|               23.36 |       42,799,791.14 |    0.0% |            0.00 |            0.00 |           0.00 |    0.0% |      0.01 | `SaltedOutpointHasherBenchmark_hash`
    6|               48.78 |       20,502,210.98 |    0.1% |            0.00 |            0.00 |           0.00 |    0.0% |      0.01 | `SaltedOutpointHasherBenchmark_match`
    7|               69.31 |       14,426,956.77 |    0.3% |            0.00 |            0.00 |           0.00 |    0.0% |      0.01 | `SaltedOutpointHasherBenchmark_mismatch`
    

    commit 7ac4e3aaf345a06fdde463100c45c4295d730820

    0|               ns/op |                op/s |    err% |          ins/op |          cyc/op |         bra/op |   miss% |     total | benchmark
    1|--------------------:|--------------------:|--------:|----------------:|----------------:|---------------:|--------:|----------:|:----------
    2|                1.30 |      766,440,414.89 |    0.0% |            0.00 |            0.00 |           0.00 |    0.0% |      0.01 | `COutPoint_equality_match`
    3|                1.02 |      982,018,502.07 |    0.2% |            0.00 |            0.00 |           0.00 |    0.0% |      0.01 | `COutPoint_equality_mismatch`
    4|              108.92 |        9,180,628.87 |    0.9% |            0.00 |            0.00 |           0.00 |    0.0% |      0.10 | `SaltedOutpointHasherBenchmark_create_set`
    5|               23.69 |       42,213,365.83 |    0.1% |            0.00 |            0.00 |           0.00 |    0.0% |      0.01 | `SaltedOutpointHasherBenchmark_hash`
    6|               47.90 |       20,876,369.10 |    0.1% |            0.00 |            0.00 |           0.00 |    0.0% |      0.01 | `SaltedOutpointHasherBenchmark_match`
    7|               70.10 |       14,265,655.03 |    0.1% |            0.00 |            0.00 |           0.00 |    0.0% |      0.01 | `SaltedOutpointHasherBenchmark_mismatch`
    
  8. l0rinc commented at 5:58 pm on July 14, 2024: contributor
    That’s disappointing, thanks for checking, something’s still off with my compiler, it seems. After I finish benching #28280 (comment), I’ll try this again on the dedicates server instead. I’m also a bit surprised that the ratios are completely different compared to my measurements, e.g. SaltedOutpointHasherBenchmark_match and _mismatch are 40% off, while in my case they’re basically equal. Are compilers this different? Could you please send me the exact command and compiler versions you’ve used? Thanks!
  9. l0rinc marked this as a draft on Jul 15, 2024
  10. andrewtoth commented at 3:55 pm on July 15, 2024: contributor

    Could you please send me the exact command and compiler versions you’ve used?

    gcc version 11.4.0 (Ubuntu 11.4.0-1ubuntu1~22.04)

    I ran ./configure --enable-bench make ./src/bench/bench_bitcoin -filter=.*Out[pP]oint.*

  11. l0rinc commented at 10:15 am on July 16, 2024: contributor

    Now that the other benchmark finished after a few days, I ran this on a gcc (Debian 12.2.0-14) 12.2.0.

    After the first run I got a warning of:

    Warning, results might be unstable:

    • CPU frequency scaling enabled: CPU 0 between 800.0 and 4,200.0 MHz
    • CPU governor is ‘powersave’ but should be ‘performance’
    • Turbo is enabled, CPU frequency will fluctuate

    which I’ve fixed and also ran pyperf system tune. Additionally I set a --min-time=10000, otherwise the results weren’t consistent between runs. I ran each change twice to make sure they’re stable.

    before:

    0./configure --enable-bench
    1git checkout 7ac4e3aaf345a06fdde463100c45c4295d730820 && git reset --hard
    2make -j$(nproc) && ./src/bench/bench_bitcoin -filter='.*Out[pP]oint.*' --min-time=10000
    
    ns/op op/s err% ins/op cyc/op IPC bra/op miss% total benchmark
    135.01 7,406,978.51 0.1% 1,060.42 485.49 2.184 120.72 1.1% 11.05 SaltedOutpointHasherBenchmark_create_set
    23.89 41,860,205.94 0.0% 252.01 85.94 2.932 4.00 0.0% 11.00 SaltedOutpointHasherBenchmark_hash
    59.81 16,719,701.99 0.1% 451.14 215.16 2.097 20.83 1.5% 11.00 SaltedOutpointHasherBenchmark_match
    79.21 12,624,726.37 0.0% 566.84 284.97 1.989 20.50 2.3% 10.74 SaltedOutpointHasherBenchmark_mismatch
    ns/op op/s err% ins/op cyc/op IPC bra/op miss% total benchmark
    133.71 7,479,023.41 0.0% 1,060.38 480.97 2.205 120.72 1.1% 11.03 SaltedOutpointHasherBenchmark_create_set
    23.87 41,894,947.89 0.0% 252.01 85.88 2.935 4.00 0.0% 11.00 SaltedOutpointHasherBenchmark_hash
    60.05 16,652,312.90 0.1% 452.20 216.05 2.093 20.86 1.3% 11.00 SaltedOutpointHasherBenchmark_match
    79.90 12,515,491.22 0.1% 566.85 287.46 1.972 20.50 2.3% 10.74

    after:

    0git checkout 3b7b82b4b0c39a38538aae2cba10bec3907c5cbf && git reset --hard
    1make -j$(nproc) && ./src/bench/bench_bitcoin -filter='.*Out[pP]oint.*' --min-time=10000
    
    ns/op op/s err% ins/op cyc/op IPC bra/op miss% total benchmark
    132.60 7,541,596.87 0.0% 1,045.84 476.97 2.193 120.72 1.1% 11.03 SaltedOutpointHasherBenchmark_create_set
    23.43 42,674,820.33 0.0% 246.01 84.29 2.918 4.00 0.0% 11.00 SaltedOutpointHasherBenchmark_hash
    58.90 16,977,444.58 0.0% 444.45 211.91 2.097 20.92 1.3% 11.00 SaltedOutpointHasherBenchmark_match
    78.29 12,773,764.99 0.0% 544.01 281.65 1.932 20.29 2.6% 10.73 SaltedOutpointHasherBenchmark_mismatch
    ns/op op/s err% ins/op cyc/op IPC bra/op miss% total benchmark
    132.75 7,533,102.65 0.0% 1,045.87 477.53 2.190 120.72 1.1% 11.02 SaltedOutpointHasherBenchmark_create_set
    23.44 42,660,977.02 0.0% 246.01 84.33 2.917 4.00 0.0% 11.00 SaltedOutpointHasherBenchmark_hash
    59.05 16,934,798.45 0.0% 444.96 212.44 2.094 20.93 1.3% 11.01 SaltedOutpointHasherBenchmark_match
    76.32 13,103,542.96 0.1% 531.55 274.56 1.936 19.99 2.4% 10.71 SaltedOutpointHasherBenchmark_mismatch

    Resulting in:

    SaltedOutpointHasherBenchmark_create_set: Before Avg = 7443000.96, After Avg = 7537349.76, Increase = 1.27%

    SaltedOutpointHasherBenchmark_hash: Before Avg = 41877576.91, After Avg = 42667898.67, Increase = 1.89%

    SaltedOutpointHasherBenchmark_match: Before Avg = 16686007.45, After Avg = 16956121.52, Increase = 1.62%

    SaltedOutpointHasherBenchmark_mismatch: Before Avg = 12570108.79, After Avg = 12938653.98, Increase = 2.93% @andrewtoth, what do you think, can you try reproducing these results?

  12. l0rinc marked this as ready for review on Jul 18, 2024
  13. andrewtoth commented at 3:24 am on July 25, 2024: contributor
    Based on your benchmark results, I don’t think this can be called an optimization. It seems to be worse for one benchmark, better in another, and roughly the same for the rest. The description also notes that this will not be noticeable by users. In light of that, does the motivation for this PR still hold?
  14. l0rinc closed this on Jul 25, 2024

  15. l0rinc commented at 6:11 pm on July 26, 2024: contributor
    I’ve removed the COutPoint_equality changes and benchmarks (updated the comments to avoid confusion), the SaltedOutpointHasher speed gains remain. I’ll let you decide if it’s worth doing the change or not.
  16. l0rinc reopened this on Jul 26, 2024

  17. l0rinc force-pushed on Jul 26, 2024
  18. l0rinc force-pushed on Jul 26, 2024
  19. DrahtBot commented at 4:18 pm on August 30, 2024: contributor

    🚧 At least one of the CI tasks failed. Debug: https://github.com/bitcoin/bitcoin/runs/27977216903

    Make sure to run all tests locally, according to the documentation.

    The failure may happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

  20. DrahtBot added the label CI failed on Aug 30, 2024
  21. l0rinc force-pushed on Aug 31, 2024
  22. l0rinc renamed this:
    optimization: Precalculate SipHash constant XOR with k0 and k1 in SaltedOutpointHasher
    optimization: precalculate SipHash constant XOR with k0 and k1 in SaltedOutpointHasher
    on Aug 31, 2024
  23. DrahtBot removed the label CI failed on Aug 31, 2024
  24. DrahtBot added the label CI failed on Oct 13, 2024
  25. l0rinc force-pushed on Oct 15, 2024
  26. achow101 requested review from josibake on Oct 15, 2024
  27. l0rinc force-pushed on Oct 16, 2024
  28. l0rinc commented at 1:15 pm on October 16, 2024: contributor
    @laanwj This is the optimization that relies on #30349, would really appreciate you input on it.
  29. DrahtBot removed the label CI failed on Oct 16, 2024
  30. laanwj requested review from laanwj on Oct 17, 2024
  31. laanwj added the label Utils/log/libs on Oct 17, 2024
  32. l0rinc closed this on Jan 19, 2025

  33. laanwj commented at 9:50 am on January 20, 2025: member

    Sorry for taking so long.

    FWIW, Code review ACK bc959f5ba9a62b41b36cc662aa176b03969be176. Moving the magic numbers to constants seems like a good idea to me. It’s good to add the benchmarks. And although the speed change is neglilible (as would be expected from factoring out ~four xor instructions), it doesn’t complicate the code much either.

  34. l0rinc reopened this on Jan 20, 2025

  35. in src/bench/crypto_hash.cpp:212 in 6ce13a33c6 outdated
    199@@ -199,6 +200,98 @@ static void SipHash_32b(benchmark::Bench& bench)
    200     });
    201 }
    202 
    203+static void SaltedOutpointHasherBenchmark_hash(benchmark::Bench& bench)
    204+{
    205+    FastRandomContext rng(true);
    206+    constexpr size_t size = 1000;
    


    ismaelsadeeq commented at 9:33 pm on January 23, 2025:

    In “bench: Add COutPoint and SaltedOutpointHasher benchmarks” 6ce13a33c6901d820467b9c24a03ca3861d91640

    You could factor out size as a static global variable and reuse it in the benchmarks to avoid repetition!


    l0rinc commented at 10:01 pm on January 23, 2025:
    I thought of that but they each have different difficulties, so they should be tuned separately (i.e. if something takes 2x as much time per round, we likely want fewer repetitions)
  36. in src/bench/crypto_hash.cpp:240 in 6ce13a33c6 outdated
    229+    std::vector<COutPoint> value_vector;
    230+    values.reserve(size);
    231+    value_vector.reserve(size);
    232+
    233+    for (size_t i = 0; i < size; ++i) {
    234+        COutPoint outpoint{Txid::FromUint256(rng.rand256()), rng.rand32()};
    


    ismaelsadeeq commented at 9:34 pm on January 23, 2025:

    In “bench: Add COutPoint and SaltedOutpointHasher benchmarks” 6ce13a33c6901d820467b9c24a03ca3861d91640

    You could create a helper for inserting random outpoints to reduce duplication.


    l0rinc commented at 10:04 pm on January 23, 2025:
    If I need to edit again, I’ll consider it, thanks
  37. in src/bench/crypto_hash.cpp:223 in 6ce13a33c6 outdated
    212+
    213+    const SaltedOutpointHasher hasher;
    214+    bench.batch(size).run([&] {
    215+        size_t result = 0;
    216+        for (const auto& outpoint : outpoints) {
    217+            result ^= hasher(outpoint);
    


    ismaelsadeeq commented at 9:35 pm on January 23, 2025:

    In “bench: Add COutPoint and SaltedOutpointHasher benchmarks” 6ce13a33c6901d820467b9c24a03ca3861d91640

    Why are you xoring the hashes?


    l0rinc commented at 10:00 pm on January 23, 2025:
    Just to make sure they’re not optimized away - ankerl::nanobench::doNotOptimizeAway is likely more costly than a simple xor. Do you have a better idea?
  38. in src/bench/crypto_hash.cpp:241 in 6ce13a33c6 outdated
    230+    values.reserve(size);
    231+    value_vector.reserve(size);
    232+
    233+    for (size_t i = 0; i < size; ++i) {
    234+        COutPoint outpoint{Txid::FromUint256(rng.rand256()), rng.rand32()};
    235+        values.emplace(outpoint);
    


    ismaelsadeeq commented at 9:38 pm on January 23, 2025:

    In “bench: Add COutPoint and SaltedOutpointHasher benchmarks” 6ce13a33c6901d820467b9c24a03ca3861d91640

    Why aren’t you benching insertion to the unordered map?


    l0rinc commented at 10:03 pm on January 23, 2025:
    I’d be measuring the map’s behavior in that case, not the hashing

    l0rinc commented at 10:04 pm on January 23, 2025:
    But there is a SaltedOutpointHasherBenchmark_create_set which does something similar
  39. ismaelsadeeq commented at 9:44 pm on January 23, 2025: member

    I’ve thoroughly reviewed 6ce13a33c6901d820467b9c24a03ca3861d91640 and lightly reviewed bc959f5ba9a62b41b36cc662aa176b03969be176.

    I’ve noticed a slight increase in performance.

    Moving the magic numbers to constants seems like a good idea to me. It’s good to add the benchmarks.

    Same!

    ACK bc959f5ba9a62b41b36cc662aa176b03969be176

  40. DrahtBot requested review from ismaelsadeeq on Jan 23, 2025
  41. sipa commented at 3:24 pm on February 1, 2025: member

    I’m not convinced about the approach here: it’s leaking a SipHash implementation detail (the way the initial v0..v3 values are computed) into a higher layer (util/hasher.h).

    I would suggest to have a Uint256ExtraSipHasher class instead of the SipHashUint256Extra function, with a constructor that takes k0, k1 like the function current before, and computes the v0..v3 values, and then an uint64_t Uint256ExtraSipHasher::operator()(const uint256& val, uint32_t extra) const noexcept which contains the meat of the computation.

  42. l0rinc force-pushed on Feb 1, 2025
  43. l0rinc force-pushed on Feb 1, 2025
  44. l0rinc commented at 7:42 pm on February 1, 2025: contributor

    Thanks a lot for the review @sipa. The SipHash leaking into the hasher also bothered me a lot (it’s also why I closed it a few weeks ago before @laanwj revived it), but I like the Uint256ExtraSipHasher wrapper suggestion (basically the same as CSipHasher now, except for the extra tmp and count fields: both constructors cache the constant parts now.

    I’m not sure why the suggested name was Uint256ExtraSipHasher and not SipHasherUint256Extra (to be more similar to SipHashUint256), but I don’t really mind either way.

    I pushed a separate rebase (without conflicts), to simplify review, and the requested changes (and a few more related cleanups) in https://github.com/bitcoin/bitcoin/compare/2885e7785417b57a3f3cad420196837965572258..d848da97f2e4fd0997507b2d1c2a1f4a2325193d

    I’d like to clean up SipHash after this change (get rid of templates, reduce duplication) in a separate PR - if this gets merged.

  45. l0rinc renamed this:
    optimization: precalculate SipHash constant XOR with k0 and k1 in SaltedOutpointHasher
    [IBD] Precalculate some SipHash constant calculations
    on Mar 12, 2025
  46. l0rinc renamed this:
    [IBD] Precalculate some SipHash constant calculations
    [IBD] precalculate some SipHash constant calculations
    on Mar 12, 2025
  47. l0rinc renamed this:
    [IBD] precalculate some SipHash constant calculations
    [IBD] precalculate SipHash constant salt calculations
    on Mar 12, 2025
  48. l0rinc force-pushed on Mar 12, 2025
  49. l0rinc force-pushed on Apr 17, 2025
  50. l0rinc commented at 6:48 pm on May 16, 2025: contributor
    @laanwj, @sipa, I’ve addressed the concerns you’ve raised, I’d appreciate a re-review here
  51. DrahtBot added the label Needs rebase on Aug 13, 2025
  52. l0rinc commented at 10:46 pm on August 13, 2025: contributor

    Rebased to fix a conflict with #33116.

    Also dropped the benchmarking commit to make the change less scary - but kept the patch in the PRs description for anyone interested.

    Change is ready for review again.

  53. l0rinc force-pushed on Aug 13, 2025
  54. jonatack commented at 10:52 pm on August 13, 2025: member
    Concept ACK to be pinged by the bot, plan to review in a couple of weeks.
  55. DrahtBot removed the label Needs rebase on Aug 13, 2025
  56. l0rinc renamed this:
    [IBD] precalculate SipHash constant salt calculations
    [IBD] precalculate SipHash constant salt XORs
    on Aug 13, 2025
  57. Raimo33 commented at 0:35 am on August 30, 2025: contributor

    Concept ACK Approach ACK ACK 585fa7e1e3352ad5c2293ade2dbd1185f6979ac4

    I haven’t tested the code but I have reviewed it and it looks reasonable; i think the approach is sound and the two class design better addresses the matter.

  58. sipa commented at 9:23 pm on September 30, 2025: member
    Code review ACK 656da514c5a3ee4d376b9b60f57451d3e4b6aec7
  59. DrahtBot requested review from jonatack on Sep 30, 2025
  60. achow101 commented at 9:49 pm on September 30, 2025: member
    Can the same optimization be applied to SipHashUint256 as well? Those are used by the other Salted*Hashers.
  61. l0rinc commented at 9:51 pm on September 30, 2025: contributor
    Yes, will do it in the next push!
  62. l0rinc commented at 2:39 am on October 1, 2025: contributor

    Thanks @sipa for the review, @achow101 for the comment.

    I have pushed an updated version, extended the optimization from just SipHashUint256Extra to cover all SipHash operations on uint256:

    • Renamed and unified: Uint256ExtraSipHasherPresaltedSipHasher
    • All Salted*Hasher classes now use PresaltedSipHasher internally, so the compact block short ID computations should also be slightly faster after this.
  63. l0rinc force-pushed on Oct 1, 2025
  64. in src/blockencodings.h:91 in d9f3ebfa62 outdated
    87@@ -87,8 +88,7 @@ typedef enum ReadStatus_t
    88 } ReadStatus;
    89 
    90 class CBlockHeaderAndShortTxIDs {
    91-private:
    92-    mutable uint64_t shorttxidk0, shorttxidk1;
    93+    mutable std::optional<PresaltedSipHasher> m_hasher;
    


    sipa commented at 1:28 pm on October 1, 2025:

    In commit “optimization: Cache PresaltedSipHasher in CBlockHeaderAndShortTxIDs”

    Why is this optional? It looks like it’s always initialized in the constructor.


    l0rinc commented at 3:27 pm on October 1, 2025:
    This is meant to avoid a partial initialization, .value() throws if we forget to call FillShortTxIDSelector (while previously we would have just salted with 0)

    sipa commented at 3:28 pm on October 1, 2025:

    I was wrong; it’s initialized in CBlockHeaderAndShortTxIDs::FillShortTxIDSelector.

    If we want runtime checks, I’d rather have an Assert() or Assume() than relying on the throwing behavior of std::optional<T>::value(), but that’s just a nit.


    l0rinc commented at 4:31 pm on October 1, 2025:

    You mean

    0return (*Assert(m_hasher))(wtxid.ToUint256()) & 0xffffffffffffL;
    

    would be more explicit than

    0return m_hasher.value()(wtxid.ToUint256()) & 0xffffffffffffL;
    

    Pushed, let me know what you think.

  65. l0rinc force-pushed on Oct 1, 2025
  66. laanwj approved
  67. laanwj commented at 4:36 pm on October 21, 2025: member
    Code review ACK 89bbbbd257063118e6968c409e52632835b76ce8
  68. DrahtBot requested review from sipa on Oct 21, 2025
  69. l0rinc renamed this:
    [IBD] precalculate SipHash constant salt XORs
    precalculate SipHash constant salt XORs
    on Oct 28, 2025
  70. in src/crypto/siphash.cpp:134 in 8568340be2 outdated
    132@@ -133,17 +133,12 @@ uint64_t SipHashUint256(uint64_t k0, uint64_t k1, const uint256& val)
    133     return v0 ^ v1 ^ v2 ^ v3;
    134 }
    135 
    136-uint64_t SipHashUint256Extra(uint64_t k0, uint64_t k1, const uint256& val, uint32_t extra)
    137+/* Specialized implementation for efficiency */
    138+uint64_t PresaltedSipHasher::operator()(const uint256& val, uint32_t extra) const noexcept
    139 {
    140-    /* Specialized implementation for efficiency */
    141+    uint64_t v0 = v[0], v1 = v[1], v2 = v[2], v3 = v[3];
    


    ryanofsky commented at 3:00 pm on November 3, 2025:

    In commit “optimization: Introduce PresaltedSipHasher for repeated hashing” (8568340be2c679713703edc3d418272ac78d1147)

    This code would feel safer if v vector was declared const, to provide some guarantee that v[0] will actually equal C0 ^ k0 (and so on) at the time this is called.

    EDIT: Implemented this const suggestion in diff below


    l0rinc commented at 11:41 pm on November 20, 2025:
    Done
  71. in src/crypto/siphash.h:53 in 8568340be2 outdated
    47@@ -48,6 +48,20 @@ class CSipHasher
    48  *      .Finalize()
    49  */
    50 uint64_t SipHashUint256(uint64_t k0, uint64_t k1, const uint256& val);
    51-uint64_t SipHashUint256Extra(uint64_t k0, uint64_t k1, const uint256& val, uint32_t extra);
    52+
    53+class PresaltedSipHasher
    


    ryanofsky commented at 3:03 pm on November 3, 2025:

    In commit “optimization: Introduce PresaltedSipHasher for repeated hashing” (8568340be2c679713703edc3d418272ac78d1147)

    Can the description of this class from commit description be added here so this has some documentation?

    Also it seems like distinction between the CSipHasher and PresaltedSipHasher classes is not really that one is presalted and the other isn’t. They seem both seem presalted and have the same members and do the same work in the constructor.

    Would seem more accurate to call PresaltedSipHasher something like ReusableSipHasher or ImmutableSipHasher since the key difference from CSipHasher seems to be that it can be reused any number of times because of its constant state.


    l0rinc commented at 11:41 pm on November 20, 2025:
    Added some comments, please check them out
  72. in src/crypto/siphash.h:65 in 8568340be2 outdated
    60+        v[1] = CSipHasher::C1 ^ k1;
    61+        v[2] = CSipHasher::C2 ^ k0;
    62+        v[3] = CSipHasher::C3 ^ k1;
    63+    }
    64+
    65+    uint64_t operator()(const uint256& val, uint32_t extra) const noexcept;
    


    ryanofsky commented at 3:28 pm on November 3, 2025:

    In commit “optimization: Introduce PresaltedSipHasher for repeated hashing” (8568340be2c679713703edc3d418272ac78d1147)

    Would be good to document that calling this is equivalent to calling SipHasher(k0, k1).Write(val).Write(extra).Finalize()


    l0rinc commented at 11:41 pm on November 20, 2025:
    Added something like this, thanks
  73. in src/crypto/siphash.h:51 in 8568340be2 outdated
    58+    explicit PresaltedSipHasher(uint64_t k0, uint64_t k1) noexcept {
    59+        v[0] = CSipHasher::C0 ^ k0;
    60+        v[1] = CSipHasher::C1 ^ k1;
    61+        v[2] = CSipHasher::C2 ^ k0;
    62+        v[3] = CSipHasher::C3 ^ k1;
    63+    }
    


    ryanofsky commented at 3:57 pm on November 3, 2025:

    In commit “optimization: Introduce PresaltedSipHasher for repeated hashing” (8568340be2c679713703edc3d418272ac78d1147)

    I can see this code is just being moved, not duplicated again, but since this does duplicate the CSipHasher constructor code, would seem good to just move and define once in a single place.

    Would suggest a change like the following (this change also makes PresaltedSipHasher state constant as suggested earlier):

     0--- a/src/crypto/siphash.cpp
     1+++ b/src/crypto/siphash.cpp
     2@@ -15,12 +15,8 @@
     3     v2 = std::rotl(v2, 32); \
     4 } while (0)
     5 
     6-CSipHasher::CSipHasher(uint64_t k0, uint64_t k1)
     7+CSipHasher::CSipHasher(uint64_t k0, uint64_t k1) : SipHashState{k0, k1}
     8 {
     9-    v[0] = C0 ^ k0;
    10-    v[1] = C1 ^ k1;
    11-    v[2] = C2 ^ k0;
    12-    v[3] = C3 ^ k1;
    13     count = 0;
    14     tmp = 0;
    15 }
    16@@ -136,7 +132,7 @@ uint64_t SipHashUint256(uint64_t k0, uint64_t k1, const uint256& val)
    17 /* Specialized implementation for efficiency */
    18 uint64_t PresaltedSipHasher::operator()(const uint256& val, uint32_t extra) const noexcept
    19 {
    20-    uint64_t v0 = v[0], v1 = v[1], v2 = v[2], v3 = v[3];
    21+    uint64_t v0 = m_state.v[0], v1 = m_state.v[1], v2 = m_state.v[2], v3 = m_state.v[3];
    22     uint64_t d = val.GetUint64(0);
    23     v3 ^= d;
    24     SIPROUND;
    25--- a/src/crypto/siphash.h
    26+++ b/src/crypto/siphash.h
    27@@ -10,19 +10,27 @@
    28 #include <span.h>
    29 #include <uint256.h>
    30 
    31-/** SipHash-2-4 */
    32-class CSipHasher
    33+class SipHashState
    34 {
    35-private:
    36-    uint64_t v[4];
    37-    uint64_t tmp;
    38-    uint8_t count; // Only the low 8 bits of the input size matter.
    39-
    40 public:
    41+    SipHashState(uint64_t k0, uint64_t k1)  noexcept : v{C0 ^ k0, C1 ^ k1, C2 ^ k0, C3 ^ k1} {}
    42+
    43+    uint64_t v[4];
    44+
    45     static constexpr uint64_t C0{0x736f6d6570736575ULL};
    46     static constexpr uint64_t C1{0x646f72616e646f6dULL};
    47     static constexpr uint64_t C2{0x6c7967656e657261ULL};
    48     static constexpr uint64_t C3{0x7465646279746573ULL};
    49+};
    50+
    51+/** SipHash-2-4 */
    52+class CSipHasher : public SipHashState
    53+{
    54+private:
    55+    uint64_t tmp;
    56+    uint8_t count; // Only the low 8 bits of the input size matter.
    57+
    58+public:
    59 
    60     /** Construct a SipHash calculator initialized with 128-bit key (k0, k1) */
    61     CSipHasher(uint64_t k0, uint64_t k1);
    62@@ -51,15 +59,10 @@ uint64_t SipHashUint256(uint64_t k0, uint64_t k1, const uint256& val);
    63 
    64 class PresaltedSipHasher
    65 {
    66-    uint64_t v[4];
    67+    const SipHashState m_state;
    68 
    69 public:
    70-    explicit PresaltedSipHasher(uint64_t k0, uint64_t k1) noexcept {
    71-        v[0] = CSipHasher::C0 ^ k0;
    72-        v[1] = CSipHasher::C1 ^ k1;
    73-        v[2] = CSipHasher::C2 ^ k0;
    74-        v[3] = CSipHasher::C3 ^ k1;
    75-    }
    76+    explicit PresaltedSipHasher(uint64_t k0, uint64_t k1) noexcept : m_state{k0, k1} {}
    77 
    78     uint64_t operator()(const uint256& val, uint32_t extra) const noexcept;
    79 };
    

    l0rinc commented at 11:41 pm on November 20, 2025:
    Did something similar in a new commit, what do you think?
  74. ryanofsky approved
  75. ryanofsky commented at 4:19 pm on November 3, 2025: contributor
    Code review ACK 89bbbbd257063118e6968c409e52632835b76ce8. Makes sense to replace the optimized SipHashUint256 and SipHashUint256Extra functions with something more generic and avoid the need for them to repeat the same preprocessing when called multiple times. I left various suggestions below but they are not important. This already seems like a clear improvement in its current form.
  76. l0rinc commented at 3:57 pm on November 4, 2025: contributor
    Thanks @ryanofsky, given that this is in review for more than 1 year, I will address your concerns in a follow-up. rfm?
  77. achow101 commented at 1:47 am on November 18, 2025: member
    ACK 89bbbbd257063118e6968c409e52632835b76ce8
  78. achow101 commented at 2:01 am on November 18, 2025: member

    Seems to be a compile failure with clang:

     0/usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/15.2.1/../../../../include/c++/15.2.1/bits/hashtable_policy.h:1056:70: error: chosen constructor is explicit in copy-initialization
     1 1056 |       [[__no_unique_address__]] _Hashtable_ebo_helper<_Hash> _M_hash{};
     2      |                                                                      ^
     3/usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/15.2.1/../../../../include/c++/15.2.1/bits/hashtable_policy.h:1062:7: note: in instantiation of default member initializer 'std::__detail::_Hash_code_base<COutPoint, std::pair<const COutPoint, wallet::WalletTXO>, std::__detail::_Select1st, SaltedOutpointHasher, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, false>::_M_hash' requested here
     4 1062 |       _Hash_code_base() = default;
     5      |       ^
     6/usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/15.2.1/../../../../include/c++/15.2.1/bits/hashtable_policy.h:1373:14: note: in evaluation of exception specification for 'std::__detail::_Hash_code_base<COutPoint, std::pair<const COutPoint, wallet::WalletTXO>, std::__detail::_Select1st, SaltedOutpointHasher, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, false>::_Hash_code_base' needed here
     7 1373 |     : public _Hash_code_base<_Key, _Value, _ExtractKey, _Hash, _RangeHash,
     8      |              ^
     9/usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/15.2.1/../../../../include/c++/15.2.1/bits/hashtable.h:190:14: note: in evaluation of exception specification for 'std::__detail::_Hashtable_base<COutPoint, std::pair<const COutPoint, wallet::WalletTXO>, std::__detail::_Select1st, std::equal_to<COutPoint>, SaltedOutpointHasher, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Hashtable_traits<false, false, true>>::_Hashtable_base' needed here
    10  190 |     : public __detail::_Hashtable_base<_Key, _Value, _ExtractKey, _Equal,
    11      |              ^
    12/usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/15.2.1/../../../../include/c++/15.2.1/bits/unordered_map.h:115:18: note: in evaluation of exception specification for 'std::_Hashtable<COutPoint, std::pair<const COutPoint, wallet::WalletTXO>, std::allocator<std::pair<const COutPoint, wallet::WalletTXO>>, std::__detail::_Select1st, std::equal_to<COutPoint>, SaltedOutpointHasher, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true>>::_Hashtable' needed here
    13  115 |       _Hashtable _M_h;
    14      |                  ^
    15/home/ava/bitcoin/bitcoin/30442/src/wallet/wallet.h:478:5: note: in evaluation of exception specification for 'std::unordered_map<COutPoint, wallet::WalletTXO, SaltedOutpointHasher>::unordered_map' needed here
    16  478 |     CWallet(interfaces::Chain* chain, const std::string& name, std::unique_ptr<WalletDatabase> database)
    17      |     ^
    18/home/ava/bitcoin/bitcoin/30442/src/util/hasher.h:59:14: note: explicit constructor declared here
    19   59 |     explicit SaltedOutpointHasher(bool deterministic = false);
    20      |              ^
    21/usr/bin/../lib64/gcc/x86_64-pc-linux-gnu/15.2.1/../../../../include/c++/15.2.1/bits/hashtable_policy.h:1024:11: note: in implicit initialization of field '_M_obj' with omitted initializer
    22 1024 |       _Tp _M_obj;
    23      |           ^
    
    0$ clang --version
    1clang version 21.1.5
    2Target: x86_64-pc-linux-gnu
    3Thread model: posix
    4InstalledDir: /usr/bin
    
  79. maflcko closed this on Nov 18, 2025

  80. maflcko reopened this on Nov 18, 2025

  81. DrahtBot added the label CI failed on Nov 18, 2025
  82. DrahtBot removed the label CI failed on Nov 18, 2025
  83. l0rinc commented at 1:26 pm on November 18, 2025: contributor

    Thanks for restarting the build @maflcko. @achow101, I can reproduce the same locally, even on Clang 22 using libstdc++ 15.

     0[183/612] Building CXX object src/CMakeFiles/bitcoin_node.dir/node/txorphanage.cpp.o                                                                                                                                                                                    10:44:13 [1552/3036]
     1FAILED: src/CMakeFiles/bitcoin_node.dir/node/txorphanage.cpp.o                                                                                                                                                                                                                              
     2/usr/bin/ccache /usr/bin/clang++ -DBOOST_MULTI_INDEX_DISABLE_SERIALIZATION -DBOOST_NO_CXX98_FUNCTION_BASE -I/mnt/my_storage/bitcoin/build/src -I/mnt/my_storage/bitcoin/src -I/mnt/my_storage/bitcoin/src/leveldb/include -I/mnt/my_storage/bitcoin/src/minisketch/include -I/mnt/my_storage
     3/bitcoin/src/univalue/include -O2 -g -std=c++20 -fPIC -fdebug-prefix-map=/mnt/my_storage/bitcoin/src=. -fmacro-prefix-map=/mnt/my_storage/bitcoin/src=. -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=3 -Wstack-protector -fstack-protector-all -fstack-clash-protection -mbranch-protection=standard 
     4-Wall -Wextra -Wgnu -Wformat -Wformat-security -Wvla -Wshadow-field -Wthread-safety -Wthread-safety-pointer -Wloop-analysis -Wredundant-decls -Wunused-member-function -Wdate-time -Wconditional-uninitialized -Woverloaded-virtual -Wsuggest-override -Wimplicit-fallthrough -Wunreachable-
     5code -Wdocumentation -Wself-assign -Wundef -Wno-unused-parameter -MD -MT src/CMakeFiles/bitcoin_node.dir/node/txorphanage.cpp.o -MF src/CMakeFiles/bitcoin_node.dir/node/txorphanage.cpp.o.d -o src/CMakeFiles/bitcoin_node.dir/node/txorphanage.cpp.o -c /mnt/my_storage/bitcoin/src/node/t
     6xorphanage.cpp                                                                                                                                                                                                                                                                              
     7In file included from /mnt/my_storage/bitcoin/src/node/txorphanage.cpp:5:                                                                                                                                                                                                                   
     8In file included from /mnt/my_storage/bitcoin/src/node/txorphanage.h:9:                                                                                                                                                                                                                     
     9In file included from /mnt/my_storage/bitcoin/src/net.h:9:                                                                                                                                                                                                                                  
    10In file included from /mnt/my_storage/bitcoin/src/bip324.h:14:                                                                                                                                                                                                                              
    11In file included from /mnt/my_storage/bitcoin/src/key.h:13:                                                                                                                                                                                                                                 
    12In file included from /mnt/my_storage/bitcoin/src/support/allocators/secure.h:9:                                                                                                                                                                                                            
    13In file included from /mnt/my_storage/bitcoin/src/support/lockedpool.h:13:                                                                                                                                                                                                                  
    14In file included from /usr/lib/gcc/aarch64-linux-gnu/15/../../../../include/c++/15/unordered_map:43:                                                                                                                                                                                        
    15In file included from /usr/lib/gcc/aarch64-linux-gnu/15/../../../../include/c++/15/bits/unordered_map.h:33:                                                                                                                                                                                 
    16In file included from /usr/lib/gcc/aarch64-linux-gnu/15/../../../../include/c++/15/bits/hashtable.h:37:                                                                                                                                                                                     
    17/usr/lib/gcc/aarch64-linux-gnu/15/../../../../include/c++/15/bits/hashtable_policy.h:1056:70: error: chosen constructor is explicit in copy-initialization                                                                                                                                  
    18 1056 |       [[__no_unique_address__]] _Hashtable_ebo_helper<_Hash> _M_hash{};                                                                                                                                                                                                             
    19      |                                                                      ^                                                                                                                                                                                                              
    20/usr/lib/gcc/aarch64-linux-gnu/15/../../../../include/c++/15/bits/hashtable_policy.h:1062:7: note: in instantiation of default member initializer 'std::__detail::_Hash_code_base<COutPoint, std::pair<const COutPoint, std::set<transaction_identifier<true>>>, std::__detail::_Select1st, 
    21SaltedOutpointHasher, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, false>::_M_hash' requested here                                                                                                                                                               
    22 1062 |       _Hash_code_base() = default;                                                                                                                                                                                                                                                  
    23      |       ^                                                                                                                                                                                                                                                                             
    24/usr/lib/gcc/aarch64-linux-gnu/15/../../../../include/c++/15/bits/hashtable_policy.h:1373:14: note: in evaluation of exception specification for 'std::__detail::_Hash_code_base<COutPoint, std::pair<const COutPoint, std::set<transaction_identifier<true>>>, std::__detail::_Select1st, S
    25altedOutpointHasher, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, false>::_Hash_code_base' needed here                                                                                                                                                           
    26 1373 |     : public _Hash_code_base<_Key, _Value, _ExtractKey, _Hash, _RangeHash,                                                                                        
    27      |              ^                                                                                                                                                    
    28/usr/lib/gcc/aarch64-linux-gnu/15/../../../../include/c++/15/bits/hashtable.h:190:14: note: in evaluation of exception specification for 'std::__detail::_Hashtable_base<COutPoint, std::pair<const COutPoint, std::set<transaction_identifier<true>>>, std::__detail::_Select1st, std::equa
    29l_to<COutPoint>, SaltedOutpointHasher, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Hashtable_traits<false, false, true>>::_Hashtable_base' needed here                                                 
    30  190 |     : public __detail::_Hashtable_base<_Key, _Value, _ExtractKey, _Equal,                                                                                                            
    31      |              ^                                                                                                                                                    
    32/usr/lib/gcc/aarch64-linux-gnu/15/../../../../include/c++/15/bits/unordered_map.h:115:18: note: in evaluation of exception specification for 'std::_Hashtable<COutPoint, std::pair<const COutPoint, std::set<transaction_identifier<true>>>, std::allocator<std::pair<const COutPoint, std::
    33set<transaction_identifier<true>>>>, std::__detail::_Select1st, std::equal_to<COutPoint>, SaltedOutpointHasher, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true>>::_Hashtab
    34le' needed here                                                                                                                                                                                                      
    35  115 |       _Hashtable _M_h;                                                                                                                                                                                       
    36      |                  ^                                                                                                                                                                   
    37/mnt/my_storage/bitcoin/src/node/txorphanage.cpp:192:5: note: in evaluation of exception specification for 'std::unordered_map<COutPoint, std::set<transaction_identifier<true>>, SaltedOutpointHasher>::unordered_map' needed here
    38  192 |     TxOrphanageImpl(Count max_global_latency_score, Usage reserved_peer_usage) :                                                                                                                             
    39      |     ^                                                                                                                                                             
    40/mnt/my_storage/bitcoin/src/util/hasher.h:59:14: note: explicit constructor declared here                                                                                                    
    41   59 |     explicit SaltedOutpointHasher(bool deterministic = false);                                                                                                    
    42      |              ^                                                                                                                                                    
    43/usr/lib/gcc/aarch64-linux-gnu/15/../../../../include/c++/15/bits/hashtable_policy.h:1024:11: note: in implicit initialization of field '_M_obj' with omitted initializer 
    44 1024 |       _Tp _M_obj;                                                                                                                                                 
    45      |           ^                                                                                                                                                       
    46/usr/lib/gcc/aarch64-linux-gnu/15/../../../../include/c++/15/bits/hashtable_policy.h:1100:22: error: no matching member function for call to '_M_hash_code'               
    47 1100 |         return _RangeHash{}(_M_hash_code(_ExtractKey{}(__n._M_v())),                                                                                              
    48      |                             ^~~~~~~~~~~~                                                                                                                          
    49/usr/lib/gcc/aarch64-linux-gnu/15/../../../../include/c++/15/bits/hashtable.h:807:34: note: in instantiation of member function 'std::__detail::_Hash_code_base<COutPoint, std::pair<const COutPoint, std::set<transaction_identifier<true>>>, std::__detail::_Select1st, SaltedOutpointHash
    50er, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, false>::_M_bucket_index' requested here                                                       
    51  807 |       { return __hash_code_base::_M_bucket_index(__n, _M_bucket_count); }                                                                                                                                    
    52      |                                  ^                                                                                                                                                                           
    53/usr/lib/gcc/aarch64-linux-gnu/15/../../../../include/c++/15/bits/hashtable.h:2215:42: note: in instantiation of member function 'std::_Hashtable<COutPoint, std::pair<const COutPoint, std::set<transaction_identifier<true>>>, std::allocator<std::pair<const COutPoint, std::set<transact
    54ion_identifier<true>>>>, std::__detail::_Select1st, std::equal_to<COutPoint>, SaltedOutpointHasher, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true>>::_M_bucket_index' req
    55uested here                                                                                                                                                                                                          
    56 2215 |           if (__builtin_expect (!__p->_M_nxt || _M_bucket_index(*__p->_M_next()) != __bkt, 0))                                                                                                               
    57      |                                                 ^                                                                                                                                                            
    58/usr/lib/gcc/aarch64-linux-gnu/15/../../../../include/c++/15/bits/hashtable.h:2283:20: note: in instantiation of member function 'std::_Hashtable<COutPoint, std::pair<const COutPoint, std::set<transaction_identifier<true>>>, std::allocator<std::pair<const COutPoint, std::set<transact
    59ion_identifier<true>>>>, std::__detail::_Select1st, std::equal_to<COutPoint>, SaltedOutpointHasher, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true>>::_M_find_before_node'
    60 requested here                                                                                                                                                                                                      
    61 2283 |         __loc._M_before = _M_find_before_node(__loc._M_bucket_index, __k,                                                                                                                                    
    62      |                           ^                                                                                                                                                                                  
    63/usr/lib/gcc/aarch64-linux-gnu/15/../../../../include/c++/15/bits/hashtable.h:1088:21: note: in instantiation of member function 'std::_Hashtable<COutPoint, std::pair<const COutPoint, std::set<transaction_identifier<true>>>, std::allocator<std::pair<const COutPoint, std::set<transact
    64ion_identifier<true>>>>, std::__detail::_Select1st, std::equal_to<COutPoint>, SaltedOutpointHasher, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true>>::_M_locate' requested
    65 here                                                                                                                                                                                                                
    66 1088 |           if (auto __loc = _M_locate(__k))                                                                                                                                                                   
    67      |                            ^                                                                                                                                                                                 
    68/usr/lib/gcc/aarch64-linux-gnu/15/../../../../include/c++/15/bits/unordered_map.h:527:16: note: in instantiation of function template specialization 'std::_Hashtable<COutPoint, std::pair<const COutPoint, std::set<transaction_identifier<true>>>, std::allocator<std::pair<const COutPoin
    69t, std::set<transaction_identifier<true>>>>, std::__detail::_Select1st, std::equal_to<COutPoint>, SaltedOutpointHasher, std::__detail::_Mod_range_hashing, std::__detail::_Default_ranged_hash, std::__detail::_Prime_rehash_policy, std::__detail::_Hashtable_traits<false, false, true>>::
    70try_emplace<const COutPoint &>' requested here                                                                                                                                                                       
    71  527 |           return _M_h.try_emplace(cend(), __k, std::forward<_Args>(__args)...);                                                                                                                              
    72      |                       ^                                                                                                                                                                                      
    73/mnt/my_storage/bitcoin/src/node/txorphanage.cpp:332:68: note: in instantiation of function template specialization 'std::unordered_map<COutPoint, std::set<transaction_identifier<true>>, SaltedOutpointHasher>::try_emplace<>' requested here
    74  332 |             auto& wtxids_for_prevout = m_outpoint_to_orphan_wtxids.try_emplace(input.prevout).first->second;                                                      
    75      |                                                                    ^                                                                                                                                         
    76/usr/lib/gcc/aarch64-linux-gnu/15/../../../../include/c++/15/bits/hashtable_policy.h:1084:7: note: candidate function not viable: no known conversion from 'typename __1st_type<const std::pair<const COutPoint, std::set<transaction_identifier<true>, std::less<transaction_identifier<tru
    77e>>, std::allocator<transaction_identifier<true>>>>>::type' (aka 'const COutPoint') to 'const _Hash_node_value<std::pair<const COutPoint, std::set<transaction_identifier<true>, std::less<transaction_identifier<true>>, std::allocator<transaction_identifier<true>>>>, false>' for 1st ar
    78gument                                                                                                                                                                                       
    79 1084 |       _M_hash_code(const _Hash_node_value<_Value, false>& __n) const                                                                                                                                         
    80      |       ^            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~                                                                                                                                                
    81/usr/lib/gcc/aarch64-linux-gnu/15/../../../../include/c++/15/bits/hashtable_policy.h:1088:7: note: candidate function not viable: no known conversion from 'typename __1st_type<const std::pair<const COutPoint, std::set<transaction_identifier<true>, std::less<transaction_identifier<tru
    82e>>, std::allocator<transaction_identifier<true>>>>>::type' (aka 'const COutPoint') to 'const _Hash_node_value<std::pair<const COutPoint, std::set<transaction_identifier<true>, std::less<transaction_identifier<true>>, std::allocator<transaction_identifier<true>>>>, true>' for 1st arg
    83ument                                                                                                                                                                     
    84 1088 |       _M_hash_code(const _Hash_node_value<_Value, true>& __n) const                                                                                                                                          
    85      |       ^            ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~                                                                                                                                                 
    862 errors generated.                                                                                                                                                                                                  
    87[184/612] Building CXX object src/CMakeFiles/bitcoin_node.dir/node/txreconciliation.cpp.o                                                                                                    
    88ninja: build stopped: subcommand failed.                                                                
    

    The problem seems to be that SaltedOutpointHasher’s only default constructor is explicit, so std::unordered_map<..., SaltedOutpointHasher> is now rejected - we can fix it by removing the explicit from the constructor: https://github.com/l0rinc/bitcoin/commit/63f518c91b9dfffa8143c11824c2947f1a65ec6b

    Given that the CI and local GCC 15 passes, it seems like a compiler version incompatibility: we can either add that in a follow-up or I could add the above commit on top of this to not completely invalidate the previous commit ACKs.

  84. achow101 commented at 7:32 pm on November 18, 2025: member

    we can either add that in a follow-up

    I’m not merging something that doesn’t compile on my machine, it will break merging of other things.

  85. l0rinc commented at 10:11 am on November 19, 2025: contributor

    I have pushed the commit on top to not invalidate previous ACKs:

    0-    explicit SaltedOutpointHasher(bool deterministic = false);
    1+    SaltedOutpointHasher(bool deterministic = false);
    

    I would appreciate a re-review @achow101, @ismaelsadeeq, @jonatack, @laanwj, @ryanofsky, @sipa.

  86. sipa commented at 1:41 pm on November 19, 2025: member

    I don’t think we should have any commits which are known to not compile on a reasonable system.

    You’ll need re-ACKs anyway for the PR, I don’t think there is a material difference for ease of reviewing for doing it in a separate commit vs. just fixing the commit that introduces the problem.

  87. l0rinc force-pushed on Nov 19, 2025
  88. l0rinc commented at 1:50 pm on November 19, 2025: contributor

    just fixing the commit that introduces the problem

    Thanks, did that, should be no difference compared to last head, just a fixup of the last commit.

  89. in src/blockencodings.cpp:44 in 9f6016c574 outdated
    42 }
    43 
    44 uint64_t CBlockHeaderAndShortTxIDs::GetShortID(const Wtxid& wtxid) const {
    45     static_assert(SHORTTXIDS_LENGTH == 6, "shorttxids calculation assumes 6-byte shorttxids");
    46-    return SipHashUint256(shorttxidk0, shorttxidk1, wtxid.ToUint256()) & 0xffffffffffffL;
    47+    return (*Assert(m_hasher))(wtxid.ToUint256()) & 0xffffffffffffL;
    


    vasild commented at 2:27 pm on November 20, 2025:

    In master, the shorttxidk0 and shorttxidk1 members are default initialized to 0 when the object is created. Then they are assigned in CBlockHeaderAndShortTxIDs::FillShortTxIDSelector() and eventually used in CBlockHeaderAndShortTxIDs::GetShortID().

    This means that (in master) a call to GetShortID() without a preceding call to FillShortTxIDSelector() is well defined and will use shorttxidk... as 0.

    Now, with these changes such an usage would trigger this newly added Assert() because the optional m_hasher will not be set.

    To preserve the same behavior as before, what about something like:

    0return m_hasher.value_or(PresaltedSipHasher{0, 0})(wtxid.ToUint256()) & 0xffffffffffffL;
    

    or, but that is different behavior, still without a crash (assert):

    0if (!m_hasher.has_value()) {
    1    FillShortTxIDSelector();
    2}
    3return m_hasher.value()(wtxid.ToUint256()) & 0xffffffffffffL;
    

    or at least document the methods of CBlockHeaderAndShortTxIDs - that they should be called in specific order.


    l0rinc commented at 8:23 pm on November 20, 2025:

    a call to GetShortID() without a preceding call to FillShortTxIDSelector() is well defined and will use shorttxidk… as 0

    As far as I can see, that is not guaranteed in C++. CBlockHeaderAndShortTxIDs has a user-provided default constructor and shorttxidk0/shorttxidk1 are without in-class initialization, so unless FillShortTxIDSelector() has run, they hold indeterminate values, not guaranteed zeros.

    Either way, based on the serialization code and the constructors, the object is never meant to be used without being initialized via FillShortTxIDSelector() first. That function effectively completes initialization.

    document the methods of CBlockHeaderAndShortTxIDs - that they should be called in specific order

    That is what the assert does now: if the object is not fully initialized, it fails fast at the call site. Previously, if we had removed the FillShortTxIDSelector() call after deserialization or construction, GetShortID() would have silently used whatever uninitialized salt values happened to be there and any resulting breakage would only show up later. So we’re not changing behavior here, if you have a better idea (I don’t, that’s why I made it optional so that at least if fails-fast when called incorrectly), we can push that in a follow-up (you’re not the first one getting confused by this).


    l0rinc commented at 11:41 pm on November 20, 2025:
    Added the explanation to the commit message as well
  90. in src/bench/crypto_hash.cpp:197 in 9f6016c574 outdated
    196@@ -197,7 +197,7 @@ static void SipHash_32b(benchmark::Bench& bench)
    197     auto val{rng.rand256()};
    


    vasild commented at 2:50 pm on November 20, 2025:

    In the commit message of a80521bce064f9a3cf6a349db4b6ee9fd50a4e2f optimization: Introduce PresaltedSipHasher for repeated hashing: build/src/bench/bench_bitcoin should be changed to build/bin/bench_bitcoin.

    The tests SaltedOutpointHasherBench* do not exist?


    l0rinc commented at 8:25 pm on November 20, 2025:

    The tests SaltedOutpointHasherBench* do not exist?

    Please see the attached benchmarks in the PR description. They were part of the PR originally, but aren’t really useful after this change, doesn’t make sense to commit them.

  91. in src/crypto/siphash.cpp:88 in 9f6016c574 outdated
    91@@ -96,15 +92,11 @@ uint64_t CSipHasher::Finalize() const
    92     return v0 ^ v1 ^ v2 ^ v3;
    


    vasild commented at 2:51 pm on November 20, 2025:
    Would be nice to wrap the commit mesage lines to 72 chars.

    l0rinc commented at 8:28 pm on November 20, 2025:
    I’m not really a fan of that, I explicitly try to make long and expressive commits - it can be soft-wrapped on the client-side by the vertically-challended IDEs :D
  92. vasild commented at 2:51 pm on November 20, 2025: contributor
    Approach ACK 9f6016c57440041945ace0639d2d97bc87ea6876
  93. test: rename k1/k2 to k0/k1 in `SipHash` consistency tests
    Aligns test variable naming with the `k0`/`k1` convention used consistently throughout the codebase for `SipHash` keys.
    Also splits the single-param `SipHash` test from the one with extra, for clarity.
    88816fc778
  94. refactor: extract `SipHash` C0-C3 constants to class scope
    Moves the `SipHash` initialization constants (C0-C3) from magic numbers to named static constexpr members of `CSipHasher`.
    f35537a331
  95. optimization: introduce `PresaltedSipHasher` for repeated hashing
    Replaces the `SipHashUint256Extra` function with the `PresaltedSipHasher` class that caches the constant-salted state (v[0-3] after XORing with keys).
    This avoids redundant XOR operations when hashing multiple values with the same keys, benefiting use cases like `SaltedOutpointHasher`.
    
    This essentially brings the precalculations in the `CSipHasher` constructor to the `uint256`-specialized SipHash implementation.
    
    > cmake -B build -DBUILD_BENCH=ON -DCMAKE_BUILD_TYPE=Release && cmake --build build -j$(nproc) && build/src/bench/bench_bitcoin -filter='SaltedOutpointHasherBench.*' -min-time=10000
    
    > C++ compiler .......................... AppleClang 16.0.0.16000026
    
    |               ns/op |                op/s |    err% |     total | benchmark
    |--------------------:|--------------------:|--------:|----------:|:----------
    |               57.27 |       17,462,299.19 |    0.1% |     11.02 | `SaltedOutpointHasherBench_create_set`
    |               11.24 |       88,997,888.48 |    0.3% |     11.04 | `SaltedOutpointHasherBench_hash`
    |               13.91 |       71,902,014.20 |    0.2% |     11.01 | `SaltedOutpointHasherBench_match`
    |               13.29 |       75,230,390.31 |    0.1% |     11.00 | `SaltedOutpointHasherBench_mismatch`
    
    compared to master:
    create_set - 17,462,299.19/17,065,922.04 - 2.3% faster
    hash       - 88,997,888.48/83,576,684.83 - 6.4% faster
    match      - 71,902,014.20/68,985,850.12 - 4.2% faster
    mismatch   - 75,230,390.31/71,942,033.47 - 4.5% faster
    
    > C++ compiler .......................... GNU 13.3.0
    
    |               ns/op |                op/s |    err% |          ins/op |          cyc/op |    IPC |         bra/op |   miss% |     total | benchmark
    |--------------------:|--------------------:|--------:|----------------:|----------------:|-------:|---------------:|--------:|----------:|:----------
    |              135.38 |        7,386,349.49 |    0.0% |        1,078.19 |          486.16 |  2.218 |         119.56 |    1.1% |     11.00 | `SaltedOutpointHasherBench_create_set`
    |               23.67 |       42,254,558.08 |    0.0% |          247.01 |           85.01 |  2.906 |           4.00 |    0.0% |     11.00 | `SaltedOutpointHasherBench_hash`
    |               58.95 |       16,962,220.14 |    0.1% |          446.55 |          211.74 |  2.109 |          20.86 |    1.4% |     11.01 | `SaltedOutpointHasherBench_match`
    |               76.98 |       12,991,047.69 |    0.1% |          548.93 |          276.50 |  1.985 |          20.25 |    2.3% |     10.72 | `SaltedOutpointHasherBench_mismatch`
    
    compared to master:
    create_set -  7,386,349.49/7,312,133.16  - 1% faster
    hash       - 42,254,558.08/41,978,882.62 - 0.6% faster
    match      - 16,962,220.14/16,549,695.42 - 2.4% faster
    mismatch   - 12,991,047.69/12,713,595.35 - 2% faster
    
    Co-authored-by: sipa <pieter@wuille.net>
    39c7475129
  96. optimization: migrate `SipHashUint256` to `PresaltedSipHasher`
    Replaces standalone `SipHashUint256` with an `operator()` overload in `PresaltedSipHasher`.
    Updates all hasher classes (`SaltedUint256Hasher`, `SaltedTxidHasher`, `SaltedWtxidHasher`) to use `PresaltedSipHasher` internally, enabling the same constant-state caching optimization while keeping behavior unchanged.
    
    Benchmark was also adjusted to cache the salting part.
    97c18fa977
  97. optimization: cache `PresaltedSipHasher` in `CBlockHeaderAndShortTxIDs`
    Replaces separate `shorttxidk0`/`shorttxidk1` members with a cached `PresaltedSipHasher`, so `GetShortID()` reuses the precomputed `SipHash` state instead of rebuilding it on every call.
    
    `CBlockHeaderAndShortTxIDs` was never intended to be used before `FillShortTxIDSelector()` runs; doing so already relied on indeterminate salt values.
    The new `Assert(m_hasher)` just makes this invariant explicit and fails fast if the object is used in an uninitialized state.
    843892d28c
  98. l0rinc force-pushed on Nov 20, 2025
  99. DrahtBot added the label CI failed on Nov 21, 2025
  100. DrahtBot commented at 2:04 am on November 21, 2025: contributor

    🚧 At least one of the CI tasks failed. Task tidy: https://github.com/bitcoin/bitcoin/actions/runs/19554905487/job/55994986573 LLM reason (✨ experimental): Clang-Tidy reported modernization errors (use default member initializer in siphash.h), causing the CI to fail.

    Try to run the tests locally, according to the documentation. However, a CI failure may still happen due to a number of reasons, for example:

    • Possibly due to a silent merge conflict (the changes in this pull request being incompatible with the current code in the target branch). If so, make sure to rebase on the latest commit of the target branch.

    • A sanitizer issue, which can only be found by compiling with the sanitizer and running the affected test.

    • An intermittent issue.

    Leave a comment here, if you need help tracking down a confusing failure.

  101. l0rinc commented at 12:04 pm on November 21, 2025: contributor

    Fixed the remaining linter warnings after the rebase.

     0diff --git a/src/crypto/siphash.h b/src/crypto/siphash.h
     1index 0027a6850e..10d2713257 100644
     2--- a/src/crypto/siphash.h
     3+++ b/src/crypto/siphash.h
     4@@ -19,15 +19,15 @@ class SipSalt
     5 public:
     6     explicit SipSalt(uint64_t k0, uint64_t k1) noexcept : v{C0 ^ k0, C1 ^ k1, C2 ^ k0, C3 ^ k1} {}
     7 
     8-    std::array<uint64_t, 4> v{};
     9+    std::array<uint64_t, 4> v;
    10 };
    11 
    12 // General SipHash-2-4 implementation.
    13 class CSipHasher
    14 {
    15     SipSalt m_salt;
    16-    uint64_t m_tmp{0};
    17-    uint8_t m_count{0}; // Only the low 8 bits of the input size matter.
    18+    uint64_t m_tmp;
    19+    uint8_t m_count; // Only the low 8 bits of the input size matter.
    20 
    21 public:
    22     /** Construct a SipHash calculator initialized with 128-bit key (k0, k1). */
    

    Remeasured the performance of the benchmarks (see the description for the code).

    0root@rpi5-16-1:/mnt/my_storage/bitcoin# for compiler in gcc clang; do   if [ "$compiler" = "gcc" ]; then CC=gcc; CXX=g++; COMP_VER=$(gcc -dumpfullversion);   else CC=clang; CXX=clang++; COMP_VER=$(clang -dumpversion); fi &&   echo "> Compiler: $compiler $COMP_VER" &&   for commit in 45311a90abe493e00e5507bab67cadb4c80b0791 ba7cf4d549aa6178cc7e0a65f816fe8c05246064 7d155a5912789ed9e6c177b57165c6e62d7818d7 5842bec58b359b1eaff1e90369e34740e8eebbeb 466be82ace43755354d8f9600812d176ab855666; do     git fetch origin $commit >/dev/null 2>&1 && git checkout $commit >/dev/null 2>&1 && echo "" && git log -1 --pretty='%h %s' &&     rm -rf build >/dev/null 2>&1 && cmake -B build -DBUILD_BENCH=ON -DENABLE_IPC=OFF -DCMAKE_BUILD_TYPE=Release -DCMAKE_C_COMPILER=$CC -DCMAKE_CXX_COMPILER=$CXX >/dev/null 2>&1 &&     cmake --build build -j$(nproc) >/dev/null 2>&1 &&     for i in 1 2 3; do       build/bin/bench_bitcoin -filter='SaltedOutpointHasherBench.*' -min-time=10000;     done;   done; done
    

    Compiler: gcc 15.0.1

    45311a90ab bench

    ns/op op/s err% ins/op cyc/op IPC bra/op miss% total benchmark
    165.01 6,060,140.06 0.0% 1,045.65 394.90 2.648 124.50 1.0% 11.06 SaltedOutpointHasherBench_create_set
    32.90 30,398,735.43 0.0% 204.01 78.75 2.590 4.00 0.0% 11.00 SaltedOutpointHasherBench_hash
    75.23 13,293,099.81 0.0% 369.66 180.08 2.053 17.85 4.0% 10.70 SaltedOutpointHasherBench_match
    91.71 10,903,992.88 0.0% 444.42 219.59 2.024 15.64 4.1% 11.00 SaltedOutpointHasherBench_mismatch
    ns/op op/s err% ins/op cyc/op IPC bra/op miss% total benchmark
    164.88 6,065,081.20 0.3% 1,045.81 394.76 2.649 124.51 1.0% 11.06 SaltedOutpointHasherBench_create_set
    32.88 30,412,929.68 0.0% 204.01 78.75 2.591 4.00 0.0% 11.00 SaltedOutpointHasherBench_hash
    78.75 12,697,941.30 0.0% 375.01 188.58 1.989 18.00 5.0% 10.73 SaltedOutpointHasherBench_match
    94.41 10,592,390.55 0.0% 451.68 226.10 1.998 15.85 4.3% 11.00 SaltedOutpointHasherBench_mismatch
    ns/op op/s err% ins/op cyc/op IPC bra/op miss% total benchmark
    164.91 6,064,060.45 0.0% 1,045.57 394.77 2.649 124.46 1.0% 11.06 SaltedOutpointHasherBench_create_set
    32.88 30,412,096.03 0.0% 204.01 78.75 2.591 4.00 0.0% 11.00 SaltedOutpointHasherBench_hash
    75.49 13,246,145.77 0.0% 371.37 180.79 2.054 17.90 4.0% 10.70 SaltedOutpointHasherBench_match
    92.69 10,788,198.10 0.0% 458.75 221.96 2.067 16.05 4.0% 11.00 SaltedOutpointHasherBench_mismatch

    ba7cf4d549 optimization: introduce PresaltedSipHasher for repeated hashing

    ns/op op/s err% ins/op cyc/op IPC bra/op miss% total benchmark
    164.77 6,069,220.21 0.1% 1,005.44 394.20 2.551 124.49 1.0% 11.05 SaltedOutpointHasherBench_create_set
    31.30 31,950,758.24 0.0% 185.01 74.93 2.469 4.00 0.0% 11.00 SaltedOutpointHasherBench_hash
    70.32 14,221,128.16 0.0% 341.34 168.34 2.028 17.80 4.0% 10.65 SaltedOutpointHasherBench_match
    91.49 10,930,561.48 0.0% 424.39 219.01 1.938 16.10 4.0% 10.99 SaltedOutpointHasherBench_mismatch
    ns/op op/s err% ins/op cyc/op IPC bra/op miss% total benchmark
    164.60 6,075,451.17 0.0% 1,005.61 394.03 2.552 124.51 1.0% 11.06 SaltedOutpointHasherBench_create_set
    31.06 32,194,971.56 0.0% 185.01 74.39 2.487 4.00 0.0% 11.00 SaltedOutpointHasherBench_hash
    73.39 13,625,191.16 0.0% 339.97 175.76 1.934 17.76 4.7% 10.68 SaltedOutpointHasherBench_match
    91.78 10,896,087.96 0.0% 419.13 219.78 1.907 15.94 3.8% 11.00 SaltedOutpointHasherBench_mismatch
    ns/op op/s err% ins/op cyc/op IPC bra/op miss% total benchmark
    164.59 6,075,665.19 0.4% 1,005.56 394.05 2.552 124.51 1.0% 11.06 SaltedOutpointHasherBench_create_set
    30.46 32,833,391.76 0.0% 185.01 72.94 2.536 4.00 0.0% 11.00 SaltedOutpointHasherBench_hash
    78.83 12,684,741.48 0.0% 358.00 188.80 1.896 18.31 5.6% 10.74 SaltedOutpointHasherBench_match
    93.35 10,712,594.38 0.0% 418.11 223.56 1.870 15.91 4.1% 11.00 SaltedOutpointHasherBench_mismatch

    7d155a5912 optimization: migrate SipHashUint256 to PresaltedSipHasher

    ns/op op/s err% ins/op cyc/op IPC bra/op miss% total benchmark
    164.01 6,097,346.34 0.0% 1,005.53 392.48 2.562 124.50 1.0% 11.05 SaltedOutpointHasherBench_create_set
    30.48 32,812,982.09 0.0% 185.01 72.96 2.536 4.00 0.0% 11.00 SaltedOutpointHasherBench_hash
    74.82 13,364,968.48 0.0% 347.42 179.10 1.940 17.98 5.2% 10.69 SaltedOutpointHasherBench_match
    89.65 11,154,079.55 0.1% 408.70 214.70 1.904 15.62 3.9% 10.84 SaltedOutpointHasherBench_mismatch
    ns/op op/s err% ins/op cyc/op IPC bra/op miss% total benchmark
    163.82 6,104,138.09 0.1% 1,005.51 392.04 2.565 124.51 1.0% 11.04 SaltedOutpointHasherBench_create_set
    30.48 32,812,308.50 0.0% 185.01 72.97 2.535 4.00 0.0% 10.99 SaltedOutpointHasherBench_hash
    73.27 13,649,074.16 0.1% 343.70 175.33 1.960 17.87 4.4% 10.67 SaltedOutpointHasherBench_match
    90.16 11,091,477.40 0.1% 409.45 215.68 1.898 15.64 4.1% 10.84 SaltedOutpointHasherBench_mismatch
    ns/op op/s err% ins/op cyc/op IPC bra/op miss% total benchmark
    163.81 6,104,465.47 0.1% 1,005.59 392.17 2.564 124.50 1.0% 11.05 SaltedOutpointHasherBench_create_set
    30.46 32,827,104.46 0.0% 185.01 72.93 2.537 4.00 0.0% 11.00 SaltedOutpointHasherBench_hash
    73.08 13,683,949.18 0.1% 337.62 174.95 1.930 17.68 4.6% 10.68 SaltedOutpointHasherBench_match
    90.74 11,021,104.82 0.1% 408.07 217.17 1.879 15.60 4.2% 10.85 SaltedOutpointHasherBench_mismatch

    5842bec58b optimization: cache PresaltedSipHasher in CBlockHeaderAndShortTxIDs

    ns/op op/s err% ins/op cyc/op IPC bra/op miss% total benchmark
    164.06 6,095,487.39 0.4% 1,005.57 392.58 2.561 124.51 1.0% 11.06 SaltedOutpointHasherBench_create_set
    30.48 32,810,711.10 0.0% 185.01 72.96 2.536 4.00 0.0% 11.00 SaltedOutpointHasherBench_hash
    69.99 14,287,302.53 0.0% 338.21 167.60 2.018 17.70 3.4% 10.65 SaltedOutpointHasherBench_match
    90.03 11,106,967.33 0.0% 415.00 215.61 1.925 15.81 4.0% 10.85 SaltedOutpointHasherBench_mismatch
    ns/op op/s err% ins/op cyc/op IPC bra/op miss% total benchmark
    164.02 6,096,948.85 0.4% 1,005.56 392.48 2.562 124.51 1.0% 11.07 SaltedOutpointHasherBench_create_set
    31.33 31,915,740.20 0.0% 185.01 75.00 2.467 4.00 0.0% 11.01 SaltedOutpointHasherBench_hash
    72.37 13,818,581.28 0.0% 347.42 173.24 2.005 17.98 4.1% 10.67 SaltedOutpointHasherBench_match
    90.45 11,055,234.02 0.0% 416.53 216.58 1.923 15.86 4.1% 10.85 SaltedOutpointHasherBench_mismatch
    ns/op op/s err% ins/op cyc/op IPC bra/op miss% total benchmark
    164.05 6,095,611.96 0.4% 1,005.38 392.58 2.561 124.46 1.0% 11.07 SaltedOutpointHasherBench_create_set
    30.46 32,829,123.03 0.0% 185.01 72.94 2.537 4.00 0.0% 11.01 SaltedOutpointHasherBench_hash
    70.85 14,113,926.73 0.0% 340.56 169.67 2.007 17.77 3.7% 10.65 SaltedOutpointHasherBench_match
    89.80 11,135,499.25 0.0% 410.88 215.07 1.910 15.69 4.1% 10.84 SaltedOutpointHasherBench_mismatch

    466be82ace refactor: extract shared SipHash state into SipSalt

    ns/op op/s err% ins/op cyc/op IPC bra/op miss% total benchmark
    163.88 6,102,194.66 0.4% 1,005.54 392.19 2.564 124.50 1.0% 11.05 SaltedOutpointHasherBench_create_set
    30.48 32,808,612.06 0.0% 185.01 72.97 2.536 4.00 0.0% 11.00 SaltedOutpointHasherBench_hash
    75.66 13,216,862.78 0.0% 350.95 181.13 1.938 18.09 5.0% 10.70 SaltedOutpointHasherBench_match
    91.56 10,921,411.83 0.0% 416.04 219.29 1.897 15.85 3.8% 11.00 SaltedOutpointHasherBench_mismatch
    ns/op op/s err% ins/op cyc/op IPC bra/op miss% total benchmark
    163.86 6,102,939.86 0.0% 1,005.63 392.25 2.564 124.51 1.0% 11.05 SaltedOutpointHasherBench_create_set
    31.05 32,201,461.31 0.0% 185.01 74.38 2.487 4.00 0.0% 11.00 SaltedOutpointHasherBench_hash
    74.13 13,489,655.36 0.0% 341.54 177.53 1.924 17.80 5.3% 10.69 SaltedOutpointHasherBench_match
    87.45 11,435,124.96 0.0% 400.64 209.42 1.913 15.38 3.9% 10.82 SaltedOutpointHasherBench_mismatch
    ns/op op/s err% ins/op cyc/op IPC bra/op miss% total benchmark
    164.00 6,097,466.44 0.3% 1,005.39 392.28 2.563 124.46 1.0% 11.06 SaltedOutpointHasherBench_create_set
    31.06 32,192,822.17 0.0% 185.01 74.39 2.487 4.00 0.0% 11.00 SaltedOutpointHasherBench_hash
    72.46 13,800,105.42 0.1% 350.56 173.52 2.020 18.08 3.9% 10.68 SaltedOutpointHasherBench_match
    88.40 11,311,769.26 0.0% 411.86 211.72 1.945 15.71 3.8% 10.83 `SaltedOutpointHasherBench_mismatc

    Compiler: clang 22.0.0

    45311a90ab bench

    ns/op op/s err% ins/op cyc/op IPC bra/op miss% total benchmark
    159.20 6,281,392.93 0.0% 1,010.03 380.98 2.651 122.14 1.0% 10.97 SaltedOutpointHasherBench_create_set
    32.35 30,916,580.86 0.0% 203.01 77.44 2.621 4.00 0.0% 11.00 SaltedOutpointHasherBench_hash
    74.05 13,504,160.32 0.0% 379.35 177.33 2.139 15.98 2.4% 10.68 SaltedOutpointHasherBench_match
    91.58 10,918,990.28 0.0% 461.68 219.30 2.105 17.72 3.0% 11.00 SaltedOutpointHasherBench_mismatch
    ns/op op/s err% ins/op cyc/op IPC bra/op miss% total benchmark
    159.41 6,273,057.46 0.2% 1,010.24 380.92 2.652 122.19 1.0% 10.98 SaltedOutpointHasherBench_create_set
    32.34 30,918,192.90 0.0% 203.01 77.44 2.622 4.00 0.0% 11.01 SaltedOutpointHasherBench_hash
    73.32 13,639,148.09 0.0% 375.83 175.55 2.141 15.89 2.3% 10.68 SaltedOutpointHasherBench_match
    92.93 10,760,375.56 0.0% 466.29 222.52 2.095 17.85 3.0% 11.00 SaltedOutpointHasherBench_mismatch
    ns/op op/s err% ins/op cyc/op IPC bra/op miss% total benchmark
    160.15 6,244,308.27 0.2% 1,010.08 383.10 2.637 122.15 1.0% 10.99 SaltedOutpointHasherBench_create_set
    32.33 30,929,222.42 0.0% 203.01 77.44 2.622 4.00 0.0% 11.00 SaltedOutpointHasherBench_hash
    73.16 13,668,456.93 0.0% 377.37 175.20 2.154 15.93 2.3% 10.68 SaltedOutpointHasherBench_match
    90.51 11,048,345.44 0.0% 458.37 216.77 2.114 17.63 3.0% 10.85 SaltedOutpointHasherBench_mismatch

    ba7cf4d549 optimization: introduce PresaltedSipHasher for repeated hashing

    ns/op op/s err% ins/op cyc/op IPC bra/op miss% total benchmark
    158.90 6,293,353.24 0.0% 961.95 380.25 2.530 118.56 1.1% 10.98 SaltedOutpointHasherBench_create_set
    29.40 34,012,945.33 0.0% 183.01 70.39 2.600 4.00 0.0% 11.00 SaltedOutpointHasherBench_hash
    70.74 14,135,540.52 0.0% 339.31 169.42 2.003 13.03 3.0% 10.65 SaltedOutpointHasherBench_match
    90.76 11,018,265.23 0.0% 425.06 217.35 1.956 15.62 3.3% 11.00 SaltedOutpointHasherBench_mismatch
    ns/op op/s err% ins/op cyc/op IPC bra/op miss% total benchmark
    158.55 6,306,991.83 0.0% 961.74 379.58 2.534 118.50 1.1% 10.97 SaltedOutpointHasherBench_create_set
    29.33 34,093,935.67 0.0% 183.01 70.25 2.605 4.00 0.0% 11.00 SaltedOutpointHasherBench_hash
    68.66 14,565,283.47 0.0% 328.46 164.41 1.998 12.70 3.0% 10.63 SaltedOutpointHasherBench_match
    89.47 11,176,838.96 0.1% 416.58 214.28 1.944 15.34 3.6% 10.84 SaltedOutpointHasherBench_mismatch
    ns/op op/s err% ins/op cyc/op IPC bra/op miss% total benchmark
    166.42 6,008,843.88 0.0% 961.75 398.48 2.414 118.50 1.7% 10.97 SaltedOutpointHasherBench_create_set
    29.34 34,086,054.52 0.0% 183.01 70.26 2.605 4.00 0.0% 11.00 SaltedOutpointHasherBench_hash
    70.40 14,204,942.90 0.0% 336.30 168.59 1.995 12.94 3.0% 10.65 SaltedOutpointHasherBench_match
    92.25 10,839,660.05 0.0% 430.53 220.94 1.949 15.80 3.2% 11.00 SaltedOutpointHasherBench_mismatch

    7d155a5912 optimization: migrate SipHashUint256 to PresaltedSipHasher

    ns/op op/s err% ins/op cyc/op IPC bra/op miss% total benchmark
    158.99 6,289,623.94 0.0% 961.91 380.61 2.527 118.54 1.1% 10.98 SaltedOutpointHasherBench_create_set
    29.41 34,000,361.88 0.0% 183.01 70.44 2.598 4.00 0.0% 11.00 SaltedOutpointHasherBench_hash
    71.77 13,934,042.90 0.0% 343.73 171.80 2.001 13.16 2.8% 10.66 SaltedOutpointHasherBench_match
    90.12 11,096,834.23 0.0% 421.09 215.81 1.951 15.52 3.2% 10.85 SaltedOutpointHasherBench_mismatch
    ns/op op/s err% ins/op cyc/op IPC bra/op miss% total benchmark
    158.87 6,294,646.14 0.1% 961.73 380.30 2.529 118.50 1.1% 10.98 SaltedOutpointHasherBench_create_set
    29.40 34,008,909.80 0.0% 183.01 70.42 2.599 4.00 0.0% 11.00 SaltedOutpointHasherBench_hash
    70.75 14,133,589.70 0.0% 339.91 169.43 2.006 13.04 2.9% 10.65 SaltedOutpointHasherBench_match
    92.12 10,855,638.01 0.0% 429.08 220.58 1.945 15.74 3.4% 11.01 SaltedOutpointHasherBench_mismatch
    ns/op op/s err% ins/op cyc/op IPC bra/op miss% total benchmark
    158.81 6,297,021.95 0.0% 961.78 380.15 2.530 118.51 1.1% 10.98 SaltedOutpointHasherBench_create_set
    29.41 34,005,598.41 0.0% 183.01 70.43 2.599 4.00 0.0% 11.00 SaltedOutpointHasherBench_hash
    70.53 14,177,384.28 0.0% 332.48 168.90 1.969 12.82 2.8% 10.65 SaltedOutpointHasherBench_match
    88.03 11,359,903.57 0.0% 410.15 210.81 1.946 15.16 3.3% 10.83 SaltedOutpointHasherBench_mismatch

    5842bec58b optimization: cache PresaltedSipHasher in CBlockHeaderAndShortTxIDs

    ns/op op/s err% ins/op cyc/op IPC bra/op miss% total benchmark
    159.26 6,279,064.01 0.1% 961.91 381.04 2.524 118.56 1.1% 10.96 SaltedOutpointHasherBench_create_set
    29.35 34,065,929.59 0.1% 183.01 70.27 2.604 4.00 0.0% 10.99 SaltedOutpointHasherBench_hash
    68.51 14,597,202.79 0.0% 327.65 163.99 1.998 12.68 2.8% 10.62 SaltedOutpointHasherBench_match
    87.40 11,441,397.56 0.1% 410.63 209.29 1.962 15.20 3.0% 10.81 SaltedOutpointHasherBench_mismatch
    ns/op op/s err% ins/op cyc/op IPC bra/op miss% total benchmark
    159.28 6,278,141.69 0.0% 961.88 381.35 2.522 118.54 1.1% 10.97 SaltedOutpointHasherBench_create_set
    29.33 34,095,536.70 0.0% 183.01 70.24 2.605 4.00 0.0% 11.00 SaltedOutpointHasherBench_hash
    71.42 14,002,462.01 0.0% 340.52 171.02 1.991 13.06 3.0% 10.66 SaltedOutpointHasherBench_match
    91.28 10,954,860.55 0.0% 424.95 218.61 1.944 15.65 3.3% 11.00 SaltedOutpointHasherBench_mismatch
    ns/op op/s err% ins/op cyc/op IPC bra/op miss% total benchmark
    159.09 6,285,866.58 0.0% 961.73 380.83 2.525 118.50 1.1% 10.96 SaltedOutpointHasherBench_create_set
    29.33 34,092,574.76 0.0% 183.01 70.25 2.605 4.00 0.0% 11.00 SaltedOutpointHasherBench_hash
    70.04 14,277,806.74 0.0% 339.11 167.71 2.022 13.02 2.9% 10.64 SaltedOutpointHasherBench_match
    88.25 11,332,071.20 0.1% 414.18 211.33 1.960 15.29 3.3% 10.82 SaltedOutpointHasherBench_mismatch

    466be82ace refactor: extract shared SipHash state into SipSalt

    ns/op op/s err% ins/op cyc/op IPC bra/op miss% total benchmark
    159.58 6,266,405.62 0.2% 961.94 381.80 2.519 118.56 1.1% 10.97 SaltedOutpointHasherBench_create_set
    29.34 34,082,515.52 0.0% 183.01 70.26 2.605 4.00 0.0% 10.99 SaltedOutpointHasherBench_hash
    70.51 14,182,079.00 0.0% 340.92 168.85 2.019 13.07 3.0% 10.65 SaltedOutpointHasherBench_match
    88.28 11,327,985.85 0.1% 416.25 211.41 1.969 15.37 3.2% 10.83 SaltedOutpointHasherBench_mismatch
    ns/op op/s err% ins/op cyc/op IPC bra/op miss% total benchmark
    159.13 6,284,068.23 0.0% 961.99 381.00 2.525 118.56 1.1% 10.97 SaltedOutpointHasherBench_create_set
    29.33 34,093,520.03 0.0% 183.01 70.25 2.605 4.00 0.0% 11.00 SaltedOutpointHasherBench_hash
    70.95 14,094,813.02 0.0% 337.10 169.91 1.984 12.96 2.9% 10.66 SaltedOutpointHasherBench_match
    89.01 11,234,682.87 0.0% 417.56 213.15 1.959 15.37 3.3% 10.83 SaltedOutpointHasherBench_mismatch
    ns/op op/s err% ins/op cyc/op IPC bra/op miss% total benchmark
    159.18 6,282,391.41 0.0% 962.01 381.05 2.525 118.55 1.1% 10.97 SaltedOutpointHasherBench_create_set
    29.33 34,094,320.06 0.0% 183.01 70.25 2.605 4.00 0.0% 11.00 SaltedOutpointHasherBench_hash
    69.59 14,369,347.25 0.0% 335.09 166.64 2.011 12.90 2.9% 10.64 SaltedOutpointHasherBench_match
    91.96 10,873,956.74 0.0% 427.87 220.21 1.943 15.70 3.7% 11.00 SaltedOutpointHasherBench_mismatch

    There’s a bit of jitter, but the trend is obvious for both compilers.

  102. l0rinc force-pushed on Nov 21, 2025
  103. refactor: extract shared `SipHash` state into `SipSalt`
    Split the repeated `SipHash` v[0..3] initialization into a small `SipSalt` helper that is used by both `CSipHasher` and `PresaltedSipHasher`.
    
    Added explanatory comments to clarify behavior, documenting the equivalence of `PresaltedSipHasher` `operator()` overloads to `CSipHasher` usage.
    
    Co-authored-by: Ryan Ofsky <ryan@ofsky.org>
    1b810390fe
  104. l0rinc force-pushed on Nov 21, 2025
  105. DrahtBot removed the label CI failed on Nov 21, 2025
  106. laanwj approved
  107. laanwj commented at 5:38 pm on November 26, 2025: member

    re-ACK 1b810390fe51b79c7063966c9722c01cb8a4f7bf

    Since last review:

    0git range-diff  9b1a7c3e8dd78d97fbf47c2d056d043b05969176..89bbbbd257063118e6968c409e52632835b76ce8   17072f70051dc086ef57880cd14e102ed346c350..1b810390fe51b79c7063966c9722c01cb8a4f7bf
    
    1. test: Rename k1/k2 to k0/k1 in SipHash consistency tests: - Reordering in testcase, no functional change
    2. refactor: Extract SipHash C0-C3 constants to class scope - No changes
    3. optimization: Introduce PresaltedSipHasher for repeated hashing - No changes
    4. optimization: Migrate SipHashUint256 to PresaltedSipHasher- Constructor of SaltedOutpointHasher is no longer explicit (because of clang compiler problem)
    5. optimization: Cache PresaltedSipHasher in CBlockHeaderAndShortTxIDs - using m_hasher.emplace instead of assignment
    6. refactor: extract shared SipHash state into SipSalt - new commit that adds SipSalt
  108. DrahtBot requested review from vasild on Nov 26, 2025
  109. DrahtBot requested review from achow101 on Nov 26, 2025
  110. DrahtBot requested review from ryanofsky on Nov 26, 2025

github-metadata-mirror

This is a metadata mirror of the GitHub repository bitcoin/bitcoin. This site is not affiliated with GitHub. Content is generated from a GitHub metadata backup.
generated: 2025-11-28 06:13 UTC

This site is hosted by @0xB10C
More mirrored repositories can be found on mirror.b10c.me