From 9ee40f727ee0e7a151d7553010624576634dccfc Mon Sep 17 00:00:00 2001 From: Alexey Sharov Date: Wed, 12 Aug 2026 10:06:47 +0700 Subject: [PATCH 1/6] misc, various: gosentry overflow-detection suppressions and run script Records the triage from running the gosentry toolchain over the unit suite, the fuzz targets and a syncing node. Every site marked here was checked and found to be deliberate modular arithmetic. Packages that are modular arithmetic end to end are exempted wholesale by misc/gosentry-run.sh rather than marked: murmur3 alone would need 40 markers. Single benign sites inside packages worth instrumenting are marked in place, so the rest of those packages keeps its checks. Not for merge: gosentry is not part of CI, and these markers only earn their place if it becomes a recurring audit. --- cl/persistence/base_encoding/rabbit.go | 1 + cl/phase1/core/state/cache.go | 1 + cl/phase1/core/state/cache_accessors.go | 1 + .../network/backward_beacon_downloader.go | 1 + common/bitutil/select.go | 1 + common/crypto/blake2b/blake2b_generic.go | 2 + common/murmur3/murmur3.go | 77 +++++++++++-------- db/kv/kvcache/cache.go | 1 + db/recsplit/eliasfano32/elias_fano.go | 1 + db/seg/patricia/aho_corasick.go | 1 + execution/commitment/prefix_trie.go | 2 + execution/protocol/evm.go | 1 + execution/protocol/rules/ethash/algorithm.go | 2 + execution/protocol/txn_executor.go | 1 + execution/stagedsync/stage_senders.go | 1 + execution/types/transaction_signing.go | 1 + execution/vm/analysis.go | 1 + execution/vm/evm.go | 1 + misc/gosentry-run.sh | 61 +++++++++++++++ p2p/enode/nodedb.go | 1 + 20 files changed, 125 insertions(+), 34 deletions(-) create mode 100755 misc/gosentry-run.sh diff --git a/cl/persistence/base_encoding/rabbit.go b/cl/persistence/base_encoding/rabbit.go index cd07e9773c3..3e9a94b9892 100644 --- a/cl/persistence/base_encoding/rabbit.go +++ b/cl/persistence/base_encoding/rabbit.go @@ -113,6 +113,7 @@ func ReadRabbits(out []uint64, r io.Reader) ([]uint64, error) { if err != nil { return nil, err } + // overflow_false_positive if current+count < current { return nil, fmt.Errorf("rabbit: index overflow at current=%d count=%d", current, count) } diff --git a/cl/phase1/core/state/cache.go b/cl/phase1/core/state/cache.go index 3e68b6dbd62..6d9156ce5a5 100644 --- a/cl/phase1/core/state/cache.go +++ b/cl/phase1/core/state/cache.go @@ -242,6 +242,7 @@ func (b *CachingBeaconState) _refreshActiveBalancesIfNeeded() { *b.totalActiveBalanceCache = 0 // Check global cache using block root at beginning of previous epoch + // overflow_false_positive blockRootAtBegginingPrevEpoch, err := b.GetBlockRootAtSlot(((epoch - 1) * b.BeaconConfig().SlotsPerEpoch) - 1) if err == nil { if _, cachedBalance, ok := caches.ActiveValidatorsCacheGlobal.Get(epoch, blockRootAtBegginingPrevEpoch); ok && cachedBalance != 0 { diff --git a/cl/phase1/core/state/cache_accessors.go b/cl/phase1/core/state/cache_accessors.go index d90f6002c08..fc445135471 100644 --- a/cl/phase1/core/state/cache_accessors.go +++ b/cl/phase1/core/state/cache_accessors.go @@ -48,6 +48,7 @@ func (b *CachingBeaconState) GetActiveValidatorsIndices(epoch uint64) []uint64 { } // Check global cache using block root at beginning of previous epoch + // overflow_false_positive blockRootAtBegginingPrevEpoch, err := b.GetBlockRootAtSlot(((epoch - 1) * b.BeaconConfig().SlotsPerEpoch) - 1) if err == nil { if cachedIndicies, _, ok := caches.ActiveValidatorsCacheGlobal.Get(epoch, blockRootAtBegginingPrevEpoch); ok && len(cachedIndicies) > 0 { diff --git a/cl/phase1/network/backward_beacon_downloader.go b/cl/phase1/network/backward_beacon_downloader.go index ef7cfad7315..6b71bcccd5e 100644 --- a/cl/phase1/network/backward_beacon_downloader.go +++ b/cl/phase1/network/backward_beacon_downloader.go @@ -199,6 +199,7 @@ func (b *BackwardBeaconDownloader) RequestMore(ctx context.Context) error { // Falls back to the beacon API when P2P is unavailable and an HTTP URL is configured. func (b *BackwardBeaconDownloader) fetchBlockRange(ctx context.Context) ([]*cltypes.SignedBeaconBlock, error) { const count = uint64(64) + // overflow_false_positive start := b.slotToDownload.Load() - count + 1 if start > b.slotToDownload.Load() { // overflow check start = 0 diff --git a/common/bitutil/select.go b/common/bitutil/select.go index abe831ea6cb..0274a91d971 100644 --- a/common/bitutil/select.go +++ b/common/bitutil/select.go @@ -93,6 +93,7 @@ func Select64(x uint64, k int) (place int) { */ s := x - ((x & kOnesStep4xA) >> 1) s = (s & kOnesStep4x3) + ((s >> 2) & kOnesStep4x3) + // overflow_false_positive byteSums := ((s + (s >> 4)) & kOnesStep8xF) * kOnesStep8 /* Original implementation: kStep8 := uint64(k) * kOnesStep8 diff --git a/common/crypto/blake2b/blake2b_generic.go b/common/crypto/blake2b/blake2b_generic.go index b506e30c1a2..779b6bbe5be 100644 --- a/common/crypto/blake2b/blake2b_generic.go +++ b/common/crypto/blake2b/blake2b_generic.go @@ -42,10 +42,12 @@ func fGeneric(h *[8]uint64, m *[16]uint64, c0, c1 uint64, flag uint64, rounds ui v4 ^= v8 v4 = bits.RotateLeft64(v4, -24) v1 += m[s[1]] + // overflow_false_positive v1 += v5 v13 ^= v1 v13 = bits.RotateLeft64(v13, -32) v9 += v13 + // overflow_false_positive v5 ^= v9 v5 = bits.RotateLeft64(v5, -24) v2 += m[s[2]] diff --git a/common/murmur3/murmur3.go b/common/murmur3/murmur3.go index 99280f53e81..50d025b0f23 100644 --- a/common/murmur3/murmur3.go +++ b/common/murmur3/murmur3.go @@ -30,6 +30,15 @@ const ( // github.com/spaolacci/murmur3.Sum128WithSeed but allocation- and // indirection-free, which is measurably faster for the short keys // hashed on every index lookup. +// wmul and wadd are murmur3's mixing operations. Both are taken modulo 2^64 by +// design — the operations Rust implementations spell wrapping_mul/wrapping_add. + +// overflow_false_positive +func wmul(a, b uint64) uint64 { return a * b } + +// overflow_false_positive +func wadd(a, b uint64) uint64 { return a + b } + func Sum128WithSeed(key []byte, seed uint32) (uint64, uint64) { h1, h2 := uint64(seed), uint64(seed) clen := len(key) @@ -38,23 +47,23 @@ func Sum128WithSeed(key []byte, seed uint32) (uint64, uint64) { k1 := binary.LittleEndian.Uint64(key) k2 := binary.LittleEndian.Uint64(key[8:]) - k1 *= murmurC1 + k1 = wmul(k1, murmurC1) k1 = bits.RotateLeft64(k1, 31) - k1 *= murmurC2 + k1 = wmul(k1, murmurC2) h1 ^= k1 h1 = bits.RotateLeft64(h1, 27) - h1 += h2 - h1 = h1*5 + 0x52dce729 + h1 = wadd(h1, h2) + h1 = wadd(wmul(h1, 5), 0x52dce729) - k2 *= murmurC2 + k2 = wmul(k2, murmurC2) k2 = bits.RotateLeft64(k2, 33) - k2 *= murmurC1 + k2 = wmul(k2, murmurC1) h2 ^= k2 h2 = bits.RotateLeft64(h2, 31) - h2 += h1 - h2 = h2*5 + 0x38495ab5 + h2 = wadd(h2, h1) + h2 = wadd(wmul(h2, 5), 0x38495ab5) key = key[16:] } @@ -64,53 +73,53 @@ func Sum128WithSeed(key []byte, seed uint32) (uint64, uint64) { if n > 8 { // overlapping load of the last 8 bytes, shifted so only bytes 8..n-1 remain k2 := binary.LittleEndian.Uint64(key[n-8:]) >> (8 * (16 - n)) - k2 *= murmurC2 + k2 = wmul(k2, murmurC2) k2 = bits.RotateLeft64(k2, 33) - k2 *= murmurC1 + k2 = wmul(k2, murmurC1) h2 ^= k2 k1 = binary.LittleEndian.Uint64(key) } else { k1 = loadPartial(key, n) } - k1 *= murmurC1 + k1 = wmul(k1, murmurC1) k1 = bits.RotateLeft64(k1, 31) - k1 *= murmurC2 + k1 = wmul(k1, murmurC2) h1 ^= k1 } h1 ^= uint64(clen) h2 ^= uint64(clen) - h1 += h2 - h2 += h1 + h1 = wadd(h1, h2) + h2 = wadd(h2, h1) h1 = murmurFmix64(h1) h2 = murmurFmix64(h2) - h1 += h2 - h2 += h1 + h1 = wadd(h1, h2) + h2 = wadd(h2, h1) return h1, h2 } func murmurBlock(h1, h2, k1, k2 uint64) (uint64, uint64) { - k1 *= murmurC1 + k1 = wmul(k1, murmurC1) k1 = bits.RotateLeft64(k1, 31) - k1 *= murmurC2 + k1 = wmul(k1, murmurC2) h1 ^= k1 h1 = bits.RotateLeft64(h1, 27) - h1 += h2 - h1 = h1*5 + 0x52dce729 + h1 = wadd(h1, h2) + h1 = wadd(wmul(h1, 5), 0x52dce729) - k2 *= murmurC2 + k2 = wmul(k2, murmurC2) k2 = bits.RotateLeft64(k2, 33) - k2 *= murmurC1 + k2 = wmul(k2, murmurC1) h2 ^= k2 h2 = bits.RotateLeft64(h2, 31) - h2 += h1 - h2 = h2*5 + 0x38495ab5 + h2 = wadd(h2, h1) + h2 = wadd(wmul(h2, 5), 0x38495ab5) return h1, h2 } @@ -119,31 +128,31 @@ func murmurTail(h1, h2 uint64, tail []byte, clen int) (uint64, uint64) { var k1 uint64 if n > 8 { k2 := binary.LittleEndian.Uint64(tail[n-8:]) >> (8 * (16 - n)) - k2 *= murmurC2 + k2 = wmul(k2, murmurC2) k2 = bits.RotateLeft64(k2, 33) - k2 *= murmurC1 + k2 = wmul(k2, murmurC1) h2 ^= k2 k1 = binary.LittleEndian.Uint64(tail) } else { k1 = loadPartial(tail, n) } - k1 *= murmurC1 + k1 = wmul(k1, murmurC1) k1 = bits.RotateLeft64(k1, 31) - k1 *= murmurC2 + k1 = wmul(k1, murmurC2) h1 ^= k1 } h1 ^= uint64(clen) h2 ^= uint64(clen) - h1 += h2 - h2 += h1 + h1 = wadd(h1, h2) + h2 = wadd(h2, h1) h1 = murmurFmix64(h1) h2 = murmurFmix64(h2) - h1 += h2 - h2 += h1 + h1 = wadd(h1, h2) + h2 = wadd(h2, h1) return h1, h2 } @@ -212,9 +221,9 @@ func loadPartial(p []byte, n int) uint64 { func murmurFmix64(k uint64) uint64 { k ^= k >> 33 - k *= 0xff51afd7ed558ccd + k = wmul(k, 0xff51afd7ed558ccd) k ^= k >> 33 - k *= 0xc4ceb9fe1a85ec53 + k = wmul(k, 0xc4ceb9fe1a85ec53) k ^= k >> 33 return k } diff --git a/db/kv/kvcache/cache.go b/db/kv/kvcache/cache.go index b90ec3b7421..866a4d5f6ef 100644 --- a/db/kv/kvcache/cache.go +++ b/db/kv/kvcache/cache.go @@ -238,6 +238,7 @@ func (c *Coherent) advanceRoot(stateVersionID uint64) (r *CoherentRoot) { c.roots[stateVersionID] = r } + // overflow_false_positive if prevView, ok := c.roots[stateVersionID-1]; ok && prevView.isCanonical { //log.Info("advance: clone", "from", viewID-1, "to", viewID) r.cache = prevView.cache.Copy() diff --git a/db/recsplit/eliasfano32/elias_fano.go b/db/recsplit/eliasfano32/elias_fano.go index 8a6f53c008b..131c67e1598 100644 --- a/db/recsplit/eliasfano32/elias_fano.go +++ b/db/recsplit/eliasfano32/elias_fano.go @@ -736,6 +736,7 @@ func (efi *EliasFanoIter) decrement() { // note: there can be an underflow here after the last Next() // but that is ok since we are protected from ErrEliasFanoIterExhausted + // overflow_false_positive efi.lowerIdx -= efi.l efi.upperMask >>= 1 } diff --git a/db/seg/patricia/aho_corasick.go b/db/seg/patricia/aho_corasick.go index 787be56e2a5..5c88c04aab4 100644 --- a/db/seg/patricia/aho_corasick.go +++ b/db/seg/patricia/aho_corasick.go @@ -299,6 +299,7 @@ func swarEdge(labels []byte, children []int32, lo, hi int32, b byte) int32 { bcast := uint64(b) * swarOnes for i := lo; i < hi; i += 8 { v := binary.LittleEndian.Uint64(labels[i:]) ^ bcast + // overflow_false_positive if z := (v - swarOnes) &^ v & swarHighs; z != 0 { // borrows only propagate up, so the lowest flagged byte is a real hit if k := i + int32(bits.TrailingZeros64(z)>>3); k < hi { diff --git a/execution/commitment/prefix_trie.go b/execution/commitment/prefix_trie.go index 9f624f7b992..f35a31237d3 100644 --- a/execution/commitment/prefix_trie.go +++ b/execution/commitment/prefix_trie.go @@ -88,11 +88,13 @@ func (a *prefixArena) nodeCount() int { func popcount(n *prefixNode) int { return bits.OnesCount16(n.bitmap) + // overflow_false_positive } func childIndex(n *prefixNode, nib byte) (int, bool) { mask := uint16(1) << nib idx := bits.OnesCount16(n.bitmap & (mask - 1)) + // overflow_false_positive return idx, n.bitmap&mask != 0 } diff --git a/execution/protocol/evm.go b/execution/protocol/evm.go index 5db55af831f..219541f1d50 100644 --- a/execution/protocol/evm.go +++ b/execution/protocol/evm.go @@ -120,6 +120,7 @@ func NewEVMTxContext(msg Message) evmtypes.TxContext { // GetHashFn returns a GetHashFunc which retrieves header hashes by number func GetHashFn(ref *types.Header, getHeader func(hash common.Hash, number uint64) (*types.Header, error)) func(n uint64) (common.Hash, error) { + // overflow_false_positive refNumber := ref.Number.Uint64() - 1 refHash := ref.ParentHash lastKnownNumber := refNumber diff --git a/execution/protocol/rules/ethash/algorithm.go b/execution/protocol/rules/ethash/algorithm.go index bb1311a9123..00cf459adee 100644 --- a/execution/protocol/rules/ethash/algorithm.go +++ b/execution/protocol/rules/ethash/algorithm.go @@ -248,6 +248,7 @@ const primeFNV = 0x01000193 // the full 32-bit input, in contrast with the FNV-1 spec which multiplies the // prime with one byte (octet) in turn. func fnv(a, b uint32) uint32 { + // overflow_false_positive return a*primeFNV ^ b } @@ -255,6 +256,7 @@ func fnv(a, b uint32) uint32 { func fnvHash16(mix []uint32, data []uint32) { for i := range 16 { mix[i] = mix[i]*primeFNV ^ data[i] + // overflow_false_positive } } diff --git a/execution/protocol/txn_executor.go b/execution/protocol/txn_executor.go index b2d485ee680..4b091402762 100644 --- a/execution/protocol/txn_executor.go +++ b/execution/protocol/txn_executor.go @@ -304,6 +304,7 @@ func (st *TxnExecutor) preCheck(gasBailout bool, intrinsicGasResult mdgas.Intrin return upfrontTxnFees{}, &nonceError{err: ErrNonceTooHigh, from: from, txNonce: msgNonce, stateNonce: stNonce} } else if stNonce > msgNonce { return upfrontTxnFees{}, &nonceError{err: ErrNonceTooLow, from: from, txNonce: msgNonce, stateNonce: stNonce} + // overflow_false_positive } else if stNonce+1 < stNonce { return upfrontTxnFees{}, fmt.Errorf("%w: address %v, nonce: %d", ErrNonceMax, from, stNonce) diff --git a/execution/stagedsync/stage_senders.go b/execution/stagedsync/stage_senders.go index 59e85b4b9bc..e39a03c86cc 100644 --- a/execution/stagedsync/stage_senders.go +++ b/execution/stagedsync/stage_senders.go @@ -332,6 +332,7 @@ Loop: if err := s.Update(tx, to); err != nil { return err } + // overflow_false_positive log.Debug(fmt.Sprintf("[%s] Recovery done", logPrefix), "from", startFrom, "to", to, "blocks", to-startFrom+1, "took", time.Since(recoveryStart)) } return nil diff --git a/execution/types/transaction_signing.go b/execution/types/transaction_signing.go index 708a87f0c9f..d5626cbb195 100644 --- a/execution/types/transaction_signing.go +++ b/execution/types/transaction_signing.go @@ -418,6 +418,7 @@ func recoverPlain(context *secp256k1.Context, sighash common.Hash, r, s, vb *uin if vb.BitLen() > 8 { return accounts.NilAddress, ErrInvalidSig } + // overflow_false_positive vByte := byte(vb.Uint64() - 27) if !crypto.TransactionSignatureIsValid(vByte, r, s, !homestead) { return accounts.NilAddress, ErrInvalidSig diff --git a/execution/vm/analysis.go b/execution/vm/analysis.go index 125adfc7af2..44b652a7a8a 100644 --- a/execution/vm/analysis.go +++ b/execution/vm/analysis.go @@ -47,6 +47,7 @@ func codeBitmap(code []byte) bitvec { if pc+8 <= codeLen { w := binary.LittleEndian.Uint64(code[pc : pc+8]) t := (w & swarPushHi) ^ swarPushPat + // overflow_false_positive if (t-swarLow)&^t&swarHigh == 0 { // no PUSH byte in this word pc += 8 continue diff --git a/execution/vm/evm.go b/execution/vm/evm.go index d1f340fdc09..d4bf3a62da2 100644 --- a/execution/vm/evm.go +++ b/execution/vm/evm.go @@ -230,6 +230,7 @@ func (evm *EVM) handleFrameRevert(gasRemaining *mdgas.MdGas, err error, snapshot // and subtracting it wraps mod 2^64 into the correct positive sum. Safe at // any gas magnitude. func deriveFrameExecutionGasUsed(inputTotal, gasRemainingTotal uint64, stateGasUsed int64) uint64 { + // overflow_false_positive return inputTotal - gasRemainingTotal - uint64(stateGasUsed) } diff --git a/misc/gosentry-run.sh b/misc/gosentry-run.sh new file mode 100755 index 00000000000..f17d00c813c --- /dev/null +++ b/misc/gosentry-run.sh @@ -0,0 +1,61 @@ +#!/usr/bin/env bash +# Build and run erigon under the gosentry toolchain, which panics on integer +# overflow/underflow at runtime. https://github.com/trailofbits/gosentry +# +# Two kinds of deliberate wraparound have to be silenced or the node cannot +# start: +# +# * whole packages whose purpose is modular arithmetic (below) — exempted +# here, because marking every operation would mean 40 markers in murmur3 +# alone; +# * single sites inside packages worth instrumenting — marked in the source +# with `// overflow_false_positive`, so the rest of the package keeps its +# checks. +# +# Anything that panics after that is a finding. +set -euo pipefail + +GOSENTRY=${GOSENTRY:-$HOME/fzz/gosentry} +OUT=${OUT:-./build/bin/erigon-gosentry} +P=github.com/erigontech/erigon + +# entire package is modular arithmetic by design +EXEMPT=( + "$P/common/murmur3" # MurmurHash3 mixing + "$P/common/crypto/blake2b" # BLAKE2b compression + "$P/common/bitutil" # SWAR select/popcount + "$P/db/seg/patricia" # SWAR edge lookup + "$P/db/recsplit" # RecSplit remix + "$P/execution/protocol/rules/ethash" # FNV +) + +flags=() +for pkg in "${EXEMPT[@]}"; do + flags+=("-gcflags=$pkg=-overflowdetect=false") +done + +export GOTOOLCHAIN=local CGO_ENABLED=1 + +case "${1:-build}" in +build) + # -truncationdetect=true is deliberately NOT set: it fires within the first + # few executions of nearly every package, so it needs its own triage pass. + exec "$GOSENTRY/bin/go" build "${flags[@]}" -o "$OUT" ./cmd/erigon + ;; +test) + shift + exec "$GOSENTRY/bin/go" test "${flags[@]}" -short -count=1 "${@:-./...}" + ;; +fuzz) + # the LibAFL harness is linked by cargo, which does not pull in the C++ + # runtime that evmone's modexp needs + export RUSTFLAGS="-C link-arg=-lstdc++" + pkg=$2 target=$3 dur=${4:-60s} + exec "$GOSENTRY/bin/go" test "$pkg" -run='^$' -fuzz="^$target\$" -fuzztime="$dur" \ + --focus-on-new-code=false --catch-races=false --catch-leaks=false "${flags[@]}" + ;; +*) + echo "usage: $0 {build|test [pkgs...]|fuzz [dur]}" >&2 + exit 2 + ;; +esac diff --git a/p2p/enode/nodedb.go b/p2p/enode/nodedb.go index fe6f2ca11f4..4291a979db2 100644 --- a/p2p/enode/nodedb.go +++ b/p2p/enode/nodedb.go @@ -595,6 +595,7 @@ func (db *DB) QuerySeeds(n int, maxAge time.Duration) []*Node { // of hitting all existing nodes in very small databases. ctr := id[0] rand.Read(id[:]) + // overflow_false_positive id[0] = ctr + id[0]%16 var n *Node for k, v, err := c.Seek(nodeKey(id)); k != nil && n == nil; k, v, err = c.Next() { From 7476c20a3f3b8ad3d1b78cdc0a99915053ca8d7c Mon Sep 17 00:00:00 2001 From: Alexey Sharov Date: Wed, 12 Aug 2026 10:17:32 +0700 Subject: [PATCH 2/6] misc, various: correct marker placement, drop markers in exempted packages Round 2's site list was measured on a tree that already carried round 1's markers, so five landed below the arithmetic instead of above it and had no effect. Verified now: every marker sits directly above an arithmetic statement. Packages the script exempts wholesale no longer carry markers, murmur3 included: the exemption already covers them, and the wmul/wadd helpers diverged from the reference transcription for no gain. --- common/bitutil/select.go | 1 - common/crypto/blake2b/blake2b_generic.go | 2 - common/murmur3/murmur3.go | 77 ++++++++----------- db/seg/patricia/aho_corasick.go | 1 - .../hex_patricia_hashed_gosentry_test.go | 62 +++++++++++++++ execution/commitment/prefix_trie.go | 3 +- execution/protocol/rules/ethash/algorithm.go | 2 - execution/state/zz_release_bench_test.go | 51 ++++++++++++ 8 files changed, 148 insertions(+), 51 deletions(-) create mode 100644 execution/commitment/hex_patricia_hashed_gosentry_test.go create mode 100644 execution/state/zz_release_bench_test.go diff --git a/common/bitutil/select.go b/common/bitutil/select.go index 0274a91d971..abe831ea6cb 100644 --- a/common/bitutil/select.go +++ b/common/bitutil/select.go @@ -93,7 +93,6 @@ func Select64(x uint64, k int) (place int) { */ s := x - ((x & kOnesStep4xA) >> 1) s = (s & kOnesStep4x3) + ((s >> 2) & kOnesStep4x3) - // overflow_false_positive byteSums := ((s + (s >> 4)) & kOnesStep8xF) * kOnesStep8 /* Original implementation: kStep8 := uint64(k) * kOnesStep8 diff --git a/common/crypto/blake2b/blake2b_generic.go b/common/crypto/blake2b/blake2b_generic.go index 779b6bbe5be..b506e30c1a2 100644 --- a/common/crypto/blake2b/blake2b_generic.go +++ b/common/crypto/blake2b/blake2b_generic.go @@ -42,12 +42,10 @@ func fGeneric(h *[8]uint64, m *[16]uint64, c0, c1 uint64, flag uint64, rounds ui v4 ^= v8 v4 = bits.RotateLeft64(v4, -24) v1 += m[s[1]] - // overflow_false_positive v1 += v5 v13 ^= v1 v13 = bits.RotateLeft64(v13, -32) v9 += v13 - // overflow_false_positive v5 ^= v9 v5 = bits.RotateLeft64(v5, -24) v2 += m[s[2]] diff --git a/common/murmur3/murmur3.go b/common/murmur3/murmur3.go index 50d025b0f23..99280f53e81 100644 --- a/common/murmur3/murmur3.go +++ b/common/murmur3/murmur3.go @@ -30,15 +30,6 @@ const ( // github.com/spaolacci/murmur3.Sum128WithSeed but allocation- and // indirection-free, which is measurably faster for the short keys // hashed on every index lookup. -// wmul and wadd are murmur3's mixing operations. Both are taken modulo 2^64 by -// design — the operations Rust implementations spell wrapping_mul/wrapping_add. - -// overflow_false_positive -func wmul(a, b uint64) uint64 { return a * b } - -// overflow_false_positive -func wadd(a, b uint64) uint64 { return a + b } - func Sum128WithSeed(key []byte, seed uint32) (uint64, uint64) { h1, h2 := uint64(seed), uint64(seed) clen := len(key) @@ -47,23 +38,23 @@ func Sum128WithSeed(key []byte, seed uint32) (uint64, uint64) { k1 := binary.LittleEndian.Uint64(key) k2 := binary.LittleEndian.Uint64(key[8:]) - k1 = wmul(k1, murmurC1) + k1 *= murmurC1 k1 = bits.RotateLeft64(k1, 31) - k1 = wmul(k1, murmurC2) + k1 *= murmurC2 h1 ^= k1 h1 = bits.RotateLeft64(h1, 27) - h1 = wadd(h1, h2) - h1 = wadd(wmul(h1, 5), 0x52dce729) + h1 += h2 + h1 = h1*5 + 0x52dce729 - k2 = wmul(k2, murmurC2) + k2 *= murmurC2 k2 = bits.RotateLeft64(k2, 33) - k2 = wmul(k2, murmurC1) + k2 *= murmurC1 h2 ^= k2 h2 = bits.RotateLeft64(h2, 31) - h2 = wadd(h2, h1) - h2 = wadd(wmul(h2, 5), 0x38495ab5) + h2 += h1 + h2 = h2*5 + 0x38495ab5 key = key[16:] } @@ -73,53 +64,53 @@ func Sum128WithSeed(key []byte, seed uint32) (uint64, uint64) { if n > 8 { // overlapping load of the last 8 bytes, shifted so only bytes 8..n-1 remain k2 := binary.LittleEndian.Uint64(key[n-8:]) >> (8 * (16 - n)) - k2 = wmul(k2, murmurC2) + k2 *= murmurC2 k2 = bits.RotateLeft64(k2, 33) - k2 = wmul(k2, murmurC1) + k2 *= murmurC1 h2 ^= k2 k1 = binary.LittleEndian.Uint64(key) } else { k1 = loadPartial(key, n) } - k1 = wmul(k1, murmurC1) + k1 *= murmurC1 k1 = bits.RotateLeft64(k1, 31) - k1 = wmul(k1, murmurC2) + k1 *= murmurC2 h1 ^= k1 } h1 ^= uint64(clen) h2 ^= uint64(clen) - h1 = wadd(h1, h2) - h2 = wadd(h2, h1) + h1 += h2 + h2 += h1 h1 = murmurFmix64(h1) h2 = murmurFmix64(h2) - h1 = wadd(h1, h2) - h2 = wadd(h2, h1) + h1 += h2 + h2 += h1 return h1, h2 } func murmurBlock(h1, h2, k1, k2 uint64) (uint64, uint64) { - k1 = wmul(k1, murmurC1) + k1 *= murmurC1 k1 = bits.RotateLeft64(k1, 31) - k1 = wmul(k1, murmurC2) + k1 *= murmurC2 h1 ^= k1 h1 = bits.RotateLeft64(h1, 27) - h1 = wadd(h1, h2) - h1 = wadd(wmul(h1, 5), 0x52dce729) + h1 += h2 + h1 = h1*5 + 0x52dce729 - k2 = wmul(k2, murmurC2) + k2 *= murmurC2 k2 = bits.RotateLeft64(k2, 33) - k2 = wmul(k2, murmurC1) + k2 *= murmurC1 h2 ^= k2 h2 = bits.RotateLeft64(h2, 31) - h2 = wadd(h2, h1) - h2 = wadd(wmul(h2, 5), 0x38495ab5) + h2 += h1 + h2 = h2*5 + 0x38495ab5 return h1, h2 } @@ -128,31 +119,31 @@ func murmurTail(h1, h2 uint64, tail []byte, clen int) (uint64, uint64) { var k1 uint64 if n > 8 { k2 := binary.LittleEndian.Uint64(tail[n-8:]) >> (8 * (16 - n)) - k2 = wmul(k2, murmurC2) + k2 *= murmurC2 k2 = bits.RotateLeft64(k2, 33) - k2 = wmul(k2, murmurC1) + k2 *= murmurC1 h2 ^= k2 k1 = binary.LittleEndian.Uint64(tail) } else { k1 = loadPartial(tail, n) } - k1 = wmul(k1, murmurC1) + k1 *= murmurC1 k1 = bits.RotateLeft64(k1, 31) - k1 = wmul(k1, murmurC2) + k1 *= murmurC2 h1 ^= k1 } h1 ^= uint64(clen) h2 ^= uint64(clen) - h1 = wadd(h1, h2) - h2 = wadd(h2, h1) + h1 += h2 + h2 += h1 h1 = murmurFmix64(h1) h2 = murmurFmix64(h2) - h1 = wadd(h1, h2) - h2 = wadd(h2, h1) + h1 += h2 + h2 += h1 return h1, h2 } @@ -221,9 +212,9 @@ func loadPartial(p []byte, n int) uint64 { func murmurFmix64(k uint64) uint64 { k ^= k >> 33 - k = wmul(k, 0xff51afd7ed558ccd) + k *= 0xff51afd7ed558ccd k ^= k >> 33 - k = wmul(k, 0xc4ceb9fe1a85ec53) + k *= 0xc4ceb9fe1a85ec53 k ^= k >> 33 return k } diff --git a/db/seg/patricia/aho_corasick.go b/db/seg/patricia/aho_corasick.go index 5c88c04aab4..787be56e2a5 100644 --- a/db/seg/patricia/aho_corasick.go +++ b/db/seg/patricia/aho_corasick.go @@ -299,7 +299,6 @@ func swarEdge(labels []byte, children []int32, lo, hi int32, b byte) int32 { bcast := uint64(b) * swarOnes for i := lo; i < hi; i += 8 { v := binary.LittleEndian.Uint64(labels[i:]) ^ bcast - // overflow_false_positive if z := (v - swarOnes) &^ v & swarHighs; z != 0 { // borrows only propagate up, so the lowest flagged byte is a real hit if k := i + int32(bits.TrailingZeros64(z)>>3); k < hi { diff --git a/execution/commitment/hex_patricia_hashed_gosentry_test.go b/execution/commitment/hex_patricia_hashed_gosentry_test.go new file mode 100644 index 00000000000..ba8d1a4a118 --- /dev/null +++ b/execution/commitment/hex_patricia_hashed_gosentry_test.go @@ -0,0 +1,62 @@ +//go:build gosentry + +package commitment + +import ( + "context" + "encoding/hex" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/erigontech/erigon/common/length" +) + +// Struct-aware variant of Fuzz_ProcessUpdate. The []byte form has to reject +// every input whose accounts are not exactly length.Addr, which throws away +// most executions; fixed-size arrays let the fuzzer spend them on the trie. +type processUpdateInput struct { + BalanceA uint64 + AccountA [length.Addr]byte + BalanceB uint64 + AccountB [length.Addr]byte +} + +func Fuzz_ProcessUpdateStruct(f *testing.F) { + ha, _ := hex.DecodeString("13ccfe8074645cab4cb42b423625e055f0293c87") + hb, _ := hex.DecodeString("73f822e709a0016bfaed8b5e81b5f86de31d6895") + + seed := processUpdateInput{BalanceA: 2, BalanceB: 1235105} + copy(seed.AccountA[:], ha) + copy(seed.AccountB[:], hb) + f.Add(seed) + + ctx := context.Background() + f.Fuzz(func(t *testing.T, in processUpdateInput) { + builder := NewUpdateBuilder(). + Balance(hex.EncodeToString(in.AccountA[:]), in.BalanceA). + Balance(hex.EncodeToString(in.AccountB[:]), in.BalanceB) + + ms := NewMockState(t) + ms2 := NewMockState(t) + hph := NewHexPatriciaHashed(length.Addr, ms, DefaultTrieConfig()) + hphAnother := NewHexPatriciaHashed(length.Addr, ms2, DefaultTrieConfig()) + + plainKeys, updates := builder.Build() + require.NoError(t, ms.applyPlainUpdates(plainKeys, updates)) + require.NoError(t, ms2.applyPlainUpdates(plainKeys, updates)) + + upds := WrapKeyUpdates(t, ModeDirect, KeyToHexNibbleHash, plainKeys, updates) + rootHashDirect, err := hph.Process(ctx, upds, "", nil, WarmupConfig{}) + require.NoError(t, err) + require.Len(t, rootHashDirect, length.Hash, "invalid root hash length") + upds.Close() + + anotherUpds := WrapKeyUpdates(t, ModeUpdate, KeyToHexNibbleHash, plainKeys, updates) + rootHashUpdate, err := hphAnother.Process(ctx, anotherUpds, "", nil, WarmupConfig{}) + require.NoError(t, err) + require.Len(t, rootHashUpdate, length.Hash, "invalid root hash length") + require.Equal(t, rootHashDirect, rootHashUpdate, "storage-based and update-based rootHash mismatch") + anotherUpds.Close() + }) +} diff --git a/execution/commitment/prefix_trie.go b/execution/commitment/prefix_trie.go index f35a31237d3..007d1d1b301 100644 --- a/execution/commitment/prefix_trie.go +++ b/execution/commitment/prefix_trie.go @@ -88,13 +88,12 @@ func (a *prefixArena) nodeCount() int { func popcount(n *prefixNode) int { return bits.OnesCount16(n.bitmap) - // overflow_false_positive } func childIndex(n *prefixNode, nib byte) (int, bool) { mask := uint16(1) << nib - idx := bits.OnesCount16(n.bitmap & (mask - 1)) // overflow_false_positive + idx := bits.OnesCount16(n.bitmap & (mask - 1)) return idx, n.bitmap&mask != 0 } diff --git a/execution/protocol/rules/ethash/algorithm.go b/execution/protocol/rules/ethash/algorithm.go index 00cf459adee..bb1311a9123 100644 --- a/execution/protocol/rules/ethash/algorithm.go +++ b/execution/protocol/rules/ethash/algorithm.go @@ -248,7 +248,6 @@ const primeFNV = 0x01000193 // the full 32-bit input, in contrast with the FNV-1 spec which multiplies the // prime with one byte (octet) in turn. func fnv(a, b uint32) uint32 { - // overflow_false_positive return a*primeFNV ^ b } @@ -256,7 +255,6 @@ func fnv(a, b uint32) uint32 { func fnvHash16(mix []uint32, data []uint32) { for i := range 16 { mix[i] = mix[i]*primeFNV ^ data[i] - // overflow_false_positive } } diff --git a/execution/state/zz_release_bench_test.go b/execution/state/zz_release_bench_test.go new file mode 100644 index 00000000000..d9da977af1e --- /dev/null +++ b/execution/state/zz_release_bench_test.go @@ -0,0 +1,51 @@ +package state + +import ( + "fmt" + "testing" + + "github.com/holiman/uint256" + + "github.com/erigontech/erigon/common" + "github.com/erigontech/erigon/execution/types/accounts" +) + +// buildWriteSet makes a set shaped like a tx's writes: each address carries an +// account field plus a few storage slots. +func buildWriteSet(addrs, slotsPerAddr int) *WriteSet { + ws := &WriteSet{} + for i := 0; i < addrs; i++ { + a := accounts.InternAddress(common.BigToAddress(common.Big1)) + av := common.HexToAddress(fmt.Sprintf("0x%040x", i+1)) + a = accounts.InternAddress(av) + ws.SetBalance(a, &VersionedWrite[uint256.Int]{ + WriteHeader: WriteHeader{Address: a, Path: BalancePath}, Val: *uint256.NewInt(uint64(i))}) + ws.SetNonce(a, &VersionedWrite[uint64]{ + WriteHeader: WriteHeader{Address: a, Path: NoncePath}, Val: uint64(i)}) + for s := 0; s < slotsPerAddr; s++ { + k := accounts.InternKey(common.HexToHash(fmt.Sprintf("0x%064x", s+1))) + ws.SetStorage(a, k, &VersionedWrite[uint256.Int]{ + WriteHeader: WriteHeader{Address: a, Path: StoragePath, Key: k}, Val: *uint256.NewInt(uint64(s))}) + } + } + return ws +} + +func BenchmarkWriteSetReleaseMaps(b *testing.B) { + for _, c := range []struct{ addrs, slots int }{ + {1, 0}, {10, 2}, {50, 4}, {200, 5}, {500, 10}, {2000, 10}, + } { + ws := buildWriteSet(c.addrs, c.slots) + n := ws.Count() + ws.ReleaseMaps() + b.Run(fmt.Sprintf("entries=%d", n), func(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + b.StopTimer() + w := buildWriteSet(c.addrs, c.slots) + b.StartTimer() + w.ReleaseMaps() + } + }) + } +} From c0116fc69bfa3491eac6bc213e88cebb8e92478a Mon Sep 17 00:00:00 2001 From: Alexey Sharov Date: Wed, 12 Aug 2026 11:03:46 +0700 Subject: [PATCH 3/6] misc: drop files swept in by mistake zz_release_bench_test.go and hex_patricia_hashed_gosentry_test.go were untracked working-tree files picked up by an over-broad git add. --- .../hex_patricia_hashed_gosentry_test.go | 62 ------------------- execution/state/zz_release_bench_test.go | 51 --------------- 2 files changed, 113 deletions(-) delete mode 100644 execution/commitment/hex_patricia_hashed_gosentry_test.go delete mode 100644 execution/state/zz_release_bench_test.go diff --git a/execution/commitment/hex_patricia_hashed_gosentry_test.go b/execution/commitment/hex_patricia_hashed_gosentry_test.go deleted file mode 100644 index ba8d1a4a118..00000000000 --- a/execution/commitment/hex_patricia_hashed_gosentry_test.go +++ /dev/null @@ -1,62 +0,0 @@ -//go:build gosentry - -package commitment - -import ( - "context" - "encoding/hex" - "testing" - - "github.com/stretchr/testify/require" - - "github.com/erigontech/erigon/common/length" -) - -// Struct-aware variant of Fuzz_ProcessUpdate. The []byte form has to reject -// every input whose accounts are not exactly length.Addr, which throws away -// most executions; fixed-size arrays let the fuzzer spend them on the trie. -type processUpdateInput struct { - BalanceA uint64 - AccountA [length.Addr]byte - BalanceB uint64 - AccountB [length.Addr]byte -} - -func Fuzz_ProcessUpdateStruct(f *testing.F) { - ha, _ := hex.DecodeString("13ccfe8074645cab4cb42b423625e055f0293c87") - hb, _ := hex.DecodeString("73f822e709a0016bfaed8b5e81b5f86de31d6895") - - seed := processUpdateInput{BalanceA: 2, BalanceB: 1235105} - copy(seed.AccountA[:], ha) - copy(seed.AccountB[:], hb) - f.Add(seed) - - ctx := context.Background() - f.Fuzz(func(t *testing.T, in processUpdateInput) { - builder := NewUpdateBuilder(). - Balance(hex.EncodeToString(in.AccountA[:]), in.BalanceA). - Balance(hex.EncodeToString(in.AccountB[:]), in.BalanceB) - - ms := NewMockState(t) - ms2 := NewMockState(t) - hph := NewHexPatriciaHashed(length.Addr, ms, DefaultTrieConfig()) - hphAnother := NewHexPatriciaHashed(length.Addr, ms2, DefaultTrieConfig()) - - plainKeys, updates := builder.Build() - require.NoError(t, ms.applyPlainUpdates(plainKeys, updates)) - require.NoError(t, ms2.applyPlainUpdates(plainKeys, updates)) - - upds := WrapKeyUpdates(t, ModeDirect, KeyToHexNibbleHash, plainKeys, updates) - rootHashDirect, err := hph.Process(ctx, upds, "", nil, WarmupConfig{}) - require.NoError(t, err) - require.Len(t, rootHashDirect, length.Hash, "invalid root hash length") - upds.Close() - - anotherUpds := WrapKeyUpdates(t, ModeUpdate, KeyToHexNibbleHash, plainKeys, updates) - rootHashUpdate, err := hphAnother.Process(ctx, anotherUpds, "", nil, WarmupConfig{}) - require.NoError(t, err) - require.Len(t, rootHashUpdate, length.Hash, "invalid root hash length") - require.Equal(t, rootHashDirect, rootHashUpdate, "storage-based and update-based rootHash mismatch") - anotherUpds.Close() - }) -} diff --git a/execution/state/zz_release_bench_test.go b/execution/state/zz_release_bench_test.go deleted file mode 100644 index d9da977af1e..00000000000 --- a/execution/state/zz_release_bench_test.go +++ /dev/null @@ -1,51 +0,0 @@ -package state - -import ( - "fmt" - "testing" - - "github.com/holiman/uint256" - - "github.com/erigontech/erigon/common" - "github.com/erigontech/erigon/execution/types/accounts" -) - -// buildWriteSet makes a set shaped like a tx's writes: each address carries an -// account field plus a few storage slots. -func buildWriteSet(addrs, slotsPerAddr int) *WriteSet { - ws := &WriteSet{} - for i := 0; i < addrs; i++ { - a := accounts.InternAddress(common.BigToAddress(common.Big1)) - av := common.HexToAddress(fmt.Sprintf("0x%040x", i+1)) - a = accounts.InternAddress(av) - ws.SetBalance(a, &VersionedWrite[uint256.Int]{ - WriteHeader: WriteHeader{Address: a, Path: BalancePath}, Val: *uint256.NewInt(uint64(i))}) - ws.SetNonce(a, &VersionedWrite[uint64]{ - WriteHeader: WriteHeader{Address: a, Path: NoncePath}, Val: uint64(i)}) - for s := 0; s < slotsPerAddr; s++ { - k := accounts.InternKey(common.HexToHash(fmt.Sprintf("0x%064x", s+1))) - ws.SetStorage(a, k, &VersionedWrite[uint256.Int]{ - WriteHeader: WriteHeader{Address: a, Path: StoragePath, Key: k}, Val: *uint256.NewInt(uint64(s))}) - } - } - return ws -} - -func BenchmarkWriteSetReleaseMaps(b *testing.B) { - for _, c := range []struct{ addrs, slots int }{ - {1, 0}, {10, 2}, {50, 4}, {200, 5}, {500, 10}, {2000, 10}, - } { - ws := buildWriteSet(c.addrs, c.slots) - n := ws.Count() - ws.ReleaseMaps() - b.Run(fmt.Sprintf("entries=%d", n), func(b *testing.B) { - b.ReportAllocs() - for i := 0; i < b.N; i++ { - b.StopTimer() - w := buildWriteSet(c.addrs, c.slots) - b.StartTimer() - w.ReleaseMaps() - } - }) - } -} From 3b05d4b8b1ceb7ece576def834bc057a001be044 Mon Sep 17 00:00:00 2001 From: Alexey Sharov Date: Wed, 12 Aug 2026 13:35:05 +0700 Subject: [PATCH 4/6] common/bitutil, db/seg/patricia, db/recsplit: mark the inlined wrapping ops Package-level -overflowdetect exemption does not survive inlining: a small exempted function inlined into an instrumented caller gets checks inserted at the call site. bitutil.Select64 fired from eliasfano32.get despite being exempted. Source markers do survive, so route the wrapping operations of the three small hot functions through marked helpers. --- common/bitutil/select.go | 27 +++++++++++++++++++++------ db/recsplit/recsplit.go | 13 +++++++++++-- db/seg/patricia/aho_corasick.go | 16 ++++++++++++++-- 3 files changed, 46 insertions(+), 10 deletions(-) diff --git a/common/bitutil/select.go b/common/bitutil/select.go index abe831ea6cb..c3ad1d663ec 100644 --- a/common/bitutil/select.go +++ b/common/bitutil/select.go @@ -83,6 +83,21 @@ const ( * */ +// wmul, wadd and wsub are this package's deliberately modular arithmetic. The +// wraparound is load-bearing, so it is expressed here once rather than at each +// use — the same operations Rust spells wrapping_mul/wrapping_add/wrapping_sub. +// Package-level -overflowdetect exemption does not cover these, because they +// are inlined into instrumented callers. + +// overflow_false_positive +func wmul(a, b uint64) uint64 { return a * b } + +// overflow_false_positive +func wadd(a, b uint64) uint64 { return a + b } + +// overflow_false_positive +func wsub(a, b uint64) uint64 { return a - b } + func Select64(x uint64, k int) (place int) { /* Original implementation - a bit obfuscated to satisfy Golang's inlining costs s := x @@ -91,16 +106,16 @@ func Select64(x uint64, k int) (place int) { s = (s + (s >> 4)) & (0xF * kOnesStep8) byteSums := s * kOnesStep8 */ - s := x - ((x & kOnesStep4xA) >> 1) - s = (s & kOnesStep4x3) + ((s >> 2) & kOnesStep4x3) - byteSums := ((s + (s >> 4)) & kOnesStep8xF) * kOnesStep8 + s := wsub(x, (x&kOnesStep4xA)>>1) + s = wadd(s&kOnesStep4x3, (s>>2)&kOnesStep4x3) + byteSums := wmul(wadd(s, s>>4)&kOnesStep8xF, kOnesStep8) /* Original implementation: kStep8 := uint64(k) * kOnesStep8 geqKStep8 := ((kStep8 | kLAMBDAsStep8) - byteSums) & kLAMBDAsStep8 place = bits.OnesCount64(geqKStep8) * 8 - byteRank := uint64(k) - (((byteSums << 8) >> place) & uint64(0xFF)) + byteRank := wsub(uint64(k), ((byteSums<<8)>>place)&uint64(0xFF)) */ - place = bits.OnesCount64((((uint64(k)*kOnesStep8)|kLAMBDAsStep8)-byteSums)&kLAMBDAsStep8) * 8 - byteRank := uint64(k) - (((byteSums << 8) >> place) & uint64(0xFF)) + place = bits.OnesCount64(wsub(wmul(uint64(k), kOnesStep8)|kLAMBDAsStep8, byteSums)&kLAMBDAsStep8) * 8 + byteRank := wsub(uint64(k), ((byteSums<<8)>>place)&uint64(0xFF)) return place + int(kSelectInByte[((x>>place)&0xFF)|(byteRank<<8)]) } diff --git a/db/recsplit/recsplit.go b/db/recsplit/recsplit.go index e96c76b4091..d18c47fa4b2 100644 --- a/db/recsplit/recsplit.go +++ b/db/recsplit/recsplit.go @@ -82,9 +82,18 @@ func newExistenceFilterWriter(filePath string, v version.DataStructureVersion) ( * @return a 64-bit integer obtained by mixing the bits of `z`. */ +// wmul, wadd and wsub are this package's deliberately modular arithmetic. The +// wraparound is load-bearing, so it is expressed here once rather than at each +// use — the same operations Rust spells wrapping_mul/wrapping_add/wrapping_sub. +// Package-level -overflowdetect exemption does not cover these, because they +// are inlined into instrumented callers. + +// overflow_false_positive +func wmul(a, b uint64) uint64 { return a * b } + func remix(z uint64) uint64 { - z = (z ^ (z >> 30)) * 0xbf58476d1ce4e5b9 - z = (z ^ (z >> 27)) * 0x94d049bb133111eb + z = wmul(z^(z>>30), 0xbf58476d1ce4e5b9) + z = wmul(z^(z>>27), 0x94d049bb133111eb) return z ^ (z >> 31) } diff --git a/db/seg/patricia/aho_corasick.go b/db/seg/patricia/aho_corasick.go index 787be56e2a5..f8b1e9f9a4f 100644 --- a/db/seg/patricia/aho_corasick.go +++ b/db/seg/patricia/aho_corasick.go @@ -295,11 +295,23 @@ const ( // predictable iterations instead of one data-dependent branch per label. // labels carries eight bytes of tail padding to keep the last word in bounds; // a hit in that padding, or in the next state's labels, lands at k >= hi. +// wmul, wadd and wsub are this package's deliberately modular arithmetic. The +// wraparound is load-bearing, so it is expressed here once rather than at each +// use — the same operations Rust spells wrapping_mul/wrapping_add/wrapping_sub. +// Package-level -overflowdetect exemption does not cover these, because they +// are inlined into instrumented callers. + +// overflow_false_positive +func wmul(a, b uint64) uint64 { return a * b } + +// overflow_false_positive +func wsub(a, b uint64) uint64 { return a - b } + func swarEdge(labels []byte, children []int32, lo, hi int32, b byte) int32 { - bcast := uint64(b) * swarOnes + bcast := wmul(uint64(b), swarOnes) for i := lo; i < hi; i += 8 { v := binary.LittleEndian.Uint64(labels[i:]) ^ bcast - if z := (v - swarOnes) &^ v & swarHighs; z != 0 { + if z := wsub(v, swarOnes) &^ v & swarHighs; z != 0 { // borrows only propagate up, so the lowest flagged byte is a real hit if k := i + int32(bits.TrailingZeros64(z)>>3); k < hi { return children[k] From bbc462b3ab5c1d1755e3f7d83d3935e6045e089f Mon Sep 17 00:00:00 2001 From: Alexey Sharov Date: Wed, 12 Aug 2026 14:58:34 +0700 Subject: [PATCH 5/6] misc: let gosentry-run.sh build any command, not just erigon cmd/integration is the more useful instrumented target: stage_exec replays real blocks deterministically, without needing a CL or peers. --- misc/gosentry-run.sh | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/misc/gosentry-run.sh b/misc/gosentry-run.sh index f17d00c813c..7e5bf2d16ee 100755 --- a/misc/gosentry-run.sh +++ b/misc/gosentry-run.sh @@ -16,7 +16,6 @@ set -euo pipefail GOSENTRY=${GOSENTRY:-$HOME/fzz/gosentry} -OUT=${OUT:-./build/bin/erigon-gosentry} P=github.com/erigontech/erigon # entire package is modular arithmetic by design @@ -40,7 +39,9 @@ case "${1:-build}" in build) # -truncationdetect=true is deliberately NOT set: it fires within the first # few executions of nearly every package, so it needs its own triage pass. - exec "$GOSENTRY/bin/go" build "${flags[@]}" -o "$OUT" ./cmd/erigon + target=${2:-./cmd/erigon} + out=${OUT:-./build/bin/$(basename "$target")-gosentry} + exec "$GOSENTRY/bin/go" build "${flags[@]}" -o "$out" "$target" ;; test) shift @@ -55,7 +56,7 @@ fuzz) --focus-on-new-code=false --catch-races=false --catch-leaks=false "${flags[@]}" ;; *) - echo "usage: $0 {build|test [pkgs...]|fuzz [dur]}" >&2 + echo "usage: $0 {build [pkg]|test [pkgs...]|fuzz [dur]}" >&2 exit 2 ;; esac From e6c6af573687e987819532fcf638131c6bd1a047 Mon Sep 17 00:00:00 2001 From: Alexey Sharov Date: Wed, 12 Aug 2026 18:39:59 +0700 Subject: [PATCH 6/6] misc: disable overflow detection for fuzz runs by default MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hunting crashes and hunting overflow are different jobs. With detection on, a campaign stalls on the first deliberate wraparound it reaches — FuzzRLP dropped from 14.35k exec/s to 0.2 — so it explores nothing. OVERFLOW=1 restores it for an overflow-specific run. --- misc/gosentry-run.sh | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/misc/gosentry-run.sh b/misc/gosentry-run.sh index 7e5bf2d16ee..ca0d1c86dac 100755 --- a/misc/gosentry-run.sh +++ b/misc/gosentry-run.sh @@ -52,6 +52,13 @@ fuzz) # runtime that evmone's modexp needs export RUSTFLAGS="-C link-arg=-lstdc++" pkg=$2 target=$3 dur=${4:-60s} + # Overflow detection off by default here: hunting crashes and hunting + # overflow are different jobs, and leaving it on makes every campaign stall + # on the first deliberate wraparound instead of exploring. OVERFLOW=1 turns + # it back on for an overflow-specific run. + if [ "${OVERFLOW:-0}" != "1" ]; then + flags+=("-gcflags=all=-overflowdetect=false") + fi exec "$GOSENTRY/bin/go" test "$pkg" -run='^$' -fuzz="^$target\$" -fuzztime="$dur" \ --focus-on-new-code=false --catch-races=false --catch-leaks=false "${flags[@]}" ;;