Conversation
- Cache BLS public key byte[] in ValidatorInfo to eliminate heap allocations in the consensus vote verification loop - Replace O(n) List.Exists() vote deduplication with O(1) HashSet via VoteSignatureSet with FNV-1a ByteArrayKey - Add per-address storage slot index in FlatStateDb for O(1) account deletion instead of scanning the entire storage cache - Replace deep-copy Fork() with shallow reference sharing (COW-safe since SetStorage always replaces the byte[] reference) - Skip Ed25519 signature re-verification in BlockBuilder for transactions already validated at mempool admission - Replace O(n*m) mempool sender selection with O(n log m) SortedSet priority queue - Avoid zeroing ArrayPool buffers on return in MessageCodec
…on allocs - Cache compliance proofs hash in Transaction to avoid recomputing on every WriteSigningPayload call - Replace .Select().ToArray() with direct array construction in PipelinedConsensus GetAggregateSignature - Reuse key buffer across iterations in ReceiptStore.PutReceipts - Pre-allocate account/storage key and state encoding buffers in RocksDbFlatStatePersistence.Flush instead of per-iteration allocs - Apply skipSignature optimization to BuildBlockWithDex path
…on, cache DEX event sigs - Replace .ToArray().Where().ToList() with direct iteration in NodeCoordinator proposal evidence cleanup (2 call sites) - Use stackalloc for UInt256 stake-to-weight conversion in WeightedLeaderSelector instead of heap byte[32] - Cache BLAKE3 hashes of DEX event signatures in static dictionary to avoid per-operation string concat + hash computation
…ath optimizations Trie layer: - Replace MemoryStream in TrieNode.Encode() with pre-computed exact size and direct byte[] write — eliminates ~3 allocs per trie node - Accept ReadOnlySpan<byte> in NibblePath.FromCompactEncoding() to avoid .ToArray() copies during TrieNode.Decode() Execution layer: - Replace hex string selector comparison with uint32 binary compare in ManagedContractRuntime — eliminates 2 string allocs per call - Cache Address.Zero.ToArray() as static field in TransactionExecutor - Add lazy-cached CallerBytes/ContractAddressBytes on VmExecutionContext so ContractBridge.Setup() avoids .ToArray() per contract call - Replace .Select().ToList() with direct array construction for Merkle root computation in BlockBuilder - Replace LINQ .Take().Select().ToList() with direct loop in Mempool fast path (no-stateDb case) Consensus layer: - Use PopCount + pre-sized array in ValidatorSet.GetValidatorsFromBitmap instead of dynamically-resized List Network layer: - Replace LINQ chains (Select→Where→OrderBy→FirstOrDefault) with direct min/max loops in EpisubService.RebalanceTiers()
- Replace .Where().ToList() with in-place RemoveAll() in BatchAuctionSolver deadline filtering to avoid List allocation - Replace .Select().ToList() with direct loop for order extraction - Cache relayer list in MultisigRelayer, invalidate on add/remove - Eliminate intermediate hashes array in BridgeProofVerifier by computing leaf hashes directly into the padded buffer
There was a problem hiding this comment.
Pull request overview
Performance-focused refactor across consensus, execution, storage/trie, DEX, bridge, and network code paths to reduce allocations and improve asymptotic behavior in several hot loops.
Changes:
- Reworked several serialization/encoding paths to write directly into exact-sized buffers (trie nodes, network message buffers, Merkle roots).
- Added caching and indexing structures to avoid repeated allocations/scans (BLS pubkey bytes, compliance proofs hash, per-address storage slot index, event signature hashes).
- Improved selection/dedup algorithms in mempool/consensus/network services (SortedSet-based sender interleaving, O(1) vote signature dedup, loop-based tier rebalance).
Reviewed changes
Copilot reviewed 25 out of 25 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| src/storage/Basalt.Storage/Trie/TrieNode.cs | Replaces MemoryStream-based node encoding with exact-size buffer writes; span-based compact path decode. |
| src/storage/Basalt.Storage/Trie/NibblePath.cs | Changes compact path decode API to accept ReadOnlySpan to avoid intermediate allocations. |
| src/storage/Basalt.Storage/RocksDb/RocksDbFlatStatePersistence.cs | Reuses key/value buffers during flush to reduce per-iteration allocations. |
| src/storage/Basalt.Storage/RocksDb/ReceiptStore.cs | Reuses receipt key buffer inside batched writes. |
| src/storage/Basalt.Storage/FlatStateDb.cs | Adds per-address storage-slot index; switches Fork() to shallow-copy storage values (COW). |
| src/node/Basalt.Node/NodeCoordinator.cs | Removes LINQ materialization when pruning old proposal keys. |
| src/network/Basalt.Network/MessageCodec.cs | Returns pooled buffers without clearing to reduce overhead. |
| src/network/Basalt.Network/Gossip/EpisubService.cs | Replaces LINQ sorting with single-pass best/worst selection loops. |
| src/execution/Basalt.Execution/VM/ManagedContractRuntime.cs | Switches selector dispatch from hex strings to uint32 comparisons (zero-alloc fast path). |
| src/execution/Basalt.Execution/VM/ExecutionContext.cs | Adds cached Caller/ContractAddress byte[] accessors to avoid repeated ToArray(). |
| src/execution/Basalt.Execution/VM/ContractBridge.cs | Uses cached address bytes from VmExecutionContext to reduce allocations. |
| src/execution/Basalt.Execution/TransactionValidator.cs | Adds skipSignature option to validation for performance-sensitive paths. |
| src/execution/Basalt.Execution/TransactionExecutor.cs | Caches Address.Zero bytes to avoid repeated allocations. |
| src/execution/Basalt.Execution/Transaction.cs | Caches computed compliance proofs hash for reuse in signing payload. |
| src/execution/Basalt.Execution/Mempool.cs | Avoids LINQ allocations; implements SortedSet “priority queue” sender interleaving. |
| src/execution/Basalt.Execution/Dex/DexEngine.cs | Adds static cache for event signature hashes to avoid recomputation. |
| src/execution/Basalt.Execution/Dex/BatchAuctionSolver.cs | Uses in-place filtering and loop-based list extraction to reduce allocations. |
| src/execution/Basalt.Execution/BlockBuilder.cs | Skips signature re-verification in block building; avoids LINQ for Merkle leaf collection. |
| src/consensus/Basalt.Consensus/WeightedLeaderSelector.cs | Uses stackalloc for stake-to-weight conversion buffer. |
| src/consensus/Basalt.Consensus/ValidatorSet.cs | Adds cached BLS pubkey bytes; changes bitmap extraction to pre-sized array. |
| src/consensus/Basalt.Consensus/PipelinedConsensus.cs | Uses cached BLS pubkey bytes and avoids LINQ allocations for signature aggregation. |
| src/consensus/Basalt.Consensus/BasaltBft.cs | Replaces linear vote signature dedup with VoteSignatureSet and cached BLS pubkey bytes usage. |
| src/bridge/Basalt.Bridge/MultisigRelayer.cs | Adds cached relayer list to avoid rebuilding list repeatedly. |
| src/bridge/Basalt.Bridge/BridgeProofVerifier.cs | Removes intermediate hashes array and refactors Merkle proof building allocations; updates Caddyfile CORS note. |
| deploy/testnet/Caddyfile | Removes duplicated CORS headers (comment-only guidance). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+789
to
+797
| private static Hash256 GetEventSignature(string eventName) | ||
| { | ||
| if (!EventSigCache.TryGetValue(eventName, out var sig)) | ||
| { | ||
| sig = Blake3Hasher.Hash(System.Text.Encoding.UTF8.GetBytes("Dex." + eventName)); | ||
| EventSigCache[eventName] = sig; | ||
| } | ||
| return sig; | ||
| } |
src/storage/Basalt.Storage/RocksDb/RocksDbFlatStatePersistence.cs
Outdated
Show resolved
Hide resolved
Consensus and sync could both apply the same block concurrently, causing "Block parent hash does not match" errors that tripped the circuit breaker and stalled RPC nodes permanently. - NodeCoordinator: skip re-execution when sync already applied the finalized block (prevents state corruption and circuit breaker trips) - BlockApplier: tolerate already-applied blocks in ApplyBatch so the sync loop reports progress instead of stalling - BlockSyncService: add fork detection and chain rollback so RPC nodes auto-recover from chain divergence instead of retrying forever
…ation TWAP per-block snapshots created unique storage keys per (pool, block) that accumulated forever in FlatStateDb._storageCache. After ~24h of operation, Fork() deep-copied 400K+ entries including per-address HashSet clones, eventually exceeding the block interval and triggering a cascading sync failure that pinned CPU at 100% permanently. - Skip _storageSlotsIndex deep-copy in Fork() (O(1) instead of O(n)); DeleteAccount on forks falls back to key scan (rare operation) - Prune TWAP snapshots older than 3600 blocks (~2h) each block - Move EpisubService seen/cache cleanup to background with 10s cooldown - Replace ChainManager O(n) block eviction with watermark-based O(1) - Restore .ToArray() snapshot for _proposalsByView enumeration safety
Previous fix still copied _storageCache (O(400K+) with accumulated TWAP snapshots). Since FlatStateDb always writes through to the trie, forks now start with empty caches — reads fall through to the forked trie on first access. Fork() is now O(1) regardless of cache size. Also fix TWAP pruning: previous window was too narrow (poolCount*2 blocks per call) to clear historical backlog. Now uses a persisted watermark and prunes up to 200 blocks per call, progressively catching up. Prevents _storageCache on the canonical state from growing unboundedly.
Reduce MaxPrunePerCall from 200 to 50 blocks to avoid overwhelming RocksDB with tombstones during backlog cleanup (was generating 1000 deletes/block × 200 blocks = burst of writes triggering compaction). Restore _accountCache copy in Fork() — only ~50 entries but eliminates hundreds of RocksDB trie reads per block during sync execution. Storage cache remains zero-copy (the large one with TWAP snapshots).
0xZunia
added a commit
that referenced
this pull request
Mar 22, 2026
- FlatStateDb: populate _storageSlotsIndex in GetStorage and LoadFromPersistence so DeleteAccount finds all cached entries - ValidatorSet: mask bitmap to valid indices in GetValidatorsFromBitmap to prevent null entries from stale bits beyond validator count - DexEngine: change EventSigCache to ConcurrentDictionary with GetOrAdd to fix unsynchronized static Dictionary writes - MultisigRelayer: return ReadOnlyCollection from GetRelayers to prevent callers from mutating the cached list - TrieNode: advance pos after BranchValue.CopyTo for consistent writer state - RocksDbFlatStatePersistence/ReceiptStore: allocate fresh key arrays per WriteBatch.Put to avoid potential reference-sharing corruption - BridgeProofVerifier: fix misleading comment about alternating buffers
- FlatStateDb: populate _storageSlotsIndex in GetStorage and LoadFromPersistence so DeleteAccount finds all cached entries - ValidatorSet: mask bitmap to valid indices in GetValidatorsFromBitmap to prevent null entries from stale bits beyond validator count - DexEngine: change EventSigCache to ConcurrentDictionary with GetOrAdd to fix unsynchronized static Dictionary writes - MultisigRelayer: return ReadOnlyCollection from GetRelayers to prevent callers from mutating the cached list - TrieNode: advance pos after BranchValue.CopyTo for consistent writer state - RocksDbFlatStatePersistence/ReceiptStore: allocate fresh key arrays per WriteBatch.Put to avoid potential reference-sharing corruption - BridgeProofVerifier: fix misleading comment about alternating buffers
56b6820 to
bc141c6
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Comprehensive performance optimization pass across 24 files, targeting allocation reduction in consensus, storage, execution, trie, DEX, bridge, and network layers.
Changes (5 commits)
Commit 1 — Core hot paths (8 files):
Commit 2 — Execution caching (4 files):
Commit 3 — Node/consensus/DEX (3 files):
Commit 4 — Trie/execution/network (10 files):
Commit 5 — DEX/bridge (3 files):
Estimated impact
Test plan
dotnet build— 0 warnings, 0 errorsdotnet test— all 2,905 tests pass, 0 failures