Skip to content

Unify types with Geth#11937

Draft
Dyslex7c wants to merge 136 commits into
NethermindEth:masterfrom
Dyslex7c:unify-geth-types
Draft

Unify types with Geth#11937
Dyslex7c wants to merge 136 commits into
NethermindEth:masterfrom
Dyslex7c:unify-geth-types

Conversation

@Dyslex7c

@Dyslex7c Dyslex7c commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Closes #9685

Changes

  • Unified block number, gas-related, and other numeric fields with Geth by replacing signed integer types with ulong.
  • Removed unnecessary casts and adjusted logic to work with the updated types.

Types of changes

What types of changes does your code introduce?

  • Bugfix (a non-breaking change that fixes an issue)
  • New feature (a non-breaking change that adds functionality)
  • Breaking change (a change that causes existing functionality not to work as expected)
  • Optimization
  • Refactoring
  • Documentation update
  • Build-related changes
  • Other: Description

Testing

Requires testing

  • Yes
  • No

If yes, did you write tests?

  • Yes
  • No

- ChainSpecBasedSpecProvider: drop verbose BlockOf comment block
- HistoryPruner: drop misplaced field-level sentinel comment
- Merkleizer: (ulong)1 << k -> 1UL << k
- Test files: drop (ulong) casts where suffix or local const suffices
errorMargin / BasisPointsDivisor + 1 collapsed to integer 1 after the
ulong swap, losing the basis-point safety head-room and pushing
eth_estimateGas to exact minimal gas.
Math.Clamp throws when min > max, which can occur on a pruned node with
an ancient receipts barrier higher than BestKnown - MaxReorgDepth. The
master form silently returned max in that window.
Encoder still writes Value/GasPrice/MaxFeePerGas at full UInt256 width,
so DecodeULong() would fail to round-trip any blob tx with these fields
>= 2^64 wei from the persistent blob pool.
Encoder writes Value at full UInt256 width; DecodeULong() would fail to
round-trip OP deposit txs with value >= 2^64 wei. Mirrors the adjacent
Mint field which correctly stays DecodeUInt256().
The PR changed the 1559 branch from MaxFeePerGas to GasPrice, but for
type-2 txs GasPrice is MaxPriorityFeePerGas. A tx with positive fee cap
and zero priority would now be wrongly classified as zero-gas.
- FeeHistoryOracle percentile threshold: restore explicit (ulong) floor
  so reward selection matches Geth instead of advancing one extra step
  at fractional thresholds (RPC-visible).
- FeeHistoryOracle ShouldCache, HeadersSyncFeed prioritization, and
  VirtualMachine CREATE-revert reportedGas: use SaturatingSub for ulong
  subtractions that could underflow when the left operand is smaller.
Interlocked.CompareExchange(ref f, f, f) reads f twice; a concurrent
mutation between the two reads makes the CAS write the stale earlier
value back, rolling the monotonic round guard back. Interlocked.Read
has had a ulong overload since .NET 5.
(ulong) cast on a UInt256 estimate then assigning back into UInt256
fields truncates mod 2^64. The estimate stays UInt256 end-to-end.
(decimal)(CurrentValue - StartValue) does the subtraction in ulong
*before* the cast (precedence), so non-monotonic progress (deep-reorg
during full sync, BlockDownloader steps back) wraps to ~1.8e19 instead
of going negative as long did.
_minBlock starts at ulong.MaxValue; _minBlock + MaxDepth * 4 wraps to a
small value, so ShouldPrune returns true for any block past 1023 even
when the cache is empty. Also migrate Clear()'s leftover int.MaxValue
reset to ulong.MaxValue.
The prev-block sentinel migrated long.MinValue -> 0 collided with
genesis: a genuine previous-block-0 was treated as 'no previous' and
its LatestKey-indexed entry was not removed.
The PR inlined the refund-cap math (in long to preserve a possibly-
negative totalToRefund) so the virtual is no longer called and has no
overrides. Delete the orphaned hook.
The 'checked' block would throw a bare OverflowException on a hint
below 32 MB; restore the targeted 'memory hint is not enough' message
that long-arithmetic emitted via AssignPeersMemory.
TrieStore.pruningBoundaryBlock and EthRpcModule.eth_getBlockAccessListByNumber
checked '>= 0' on ulong values; the else branches are unreachable.
The PR re-expressed the cap inside ValidateGasLimitRange, which is
skipped for genesis, orphaned headers, AuRa contract-gas-limit blocks,
XDC block 1, and OP/Taiko subclass overrides — a regression vs the
master ValidateFieldLimit that ran on every header. Extract the cap
into ValidateAbsoluteGasLimit and call it on the unconditional path.
Keep the user-facing validation for eth_call/eth_simulateV1
blockOverrides.gasLimit; matches HeaderValidator's 2^63-1 protocol cap.
inMemoryStateDepth - _compactSize underflows shortly after a persist
(depth < compactSize) and bypasses the keep-in-memory early-return.
Pre-PR signed math went negative and triggered the return; use
SaturatingSub to restore that semantic.
# Conflicts:
#	src/Nethermind/Nethermind.Trie/Pruning/CompositePersistenceStrategy.cs
@LukaszRozmej

Copy link
Copy Markdown
Member

@claude review

@claude

claude Bot commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Claude encountered an error after 11m 23s —— View job


PR Review: Unify types with Geth

Tasks:

  • Load tools and initialize
  • Read AGENTS.md and relevant rule files
  • Examine key changed files and prior review comments
  • Identify remaining issues post LukaszRozmej's follow-up review (2026-06-19)
  • Post inline comments for remaining findings
  • Write final summary

@@ -179,21 +179,20 @@ private void EnsureIndexLoaded()

_componentIndexTlvStart = indexEntryStart;

// Read starting block number (first field of index data)
_startBlock = ReadInt64(indexEntryStart + EntryHeaderSize);
_startBlock = ReadUInt64(indexEntryStart + EntryHeaderSize);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Medium: No validation for negative starting_number on disk

The ComponentIndex format defines starting_number as an int64 little-endian field (see the // sizeof(long) comment on IndexFieldSize). Reading it unconditionally as ulong turns a malformed/corrupted archive with a negative value (e.g. starting_number = -1) into ulong.MaxValue. Every subsequent ComponentOffset call then throws ArgumentOutOfRangeException with a confusing range message instead of EraFormatException.

Suggested change
_startBlock = ReadUInt64(indexEntryStart + EntryHeaderSize);
long startBlockSigned = ReadInt64(indexEntryStart + EntryHeaderSize);
if (startBlockSigned < 0)
throw new EraFormatException($"Invalid starting block number {startBlockSigned} in EraE ComponentIndex.");
_startBlock = (ulong)startBlockSigned;

(This was flagged by @flcl42 in the 2026-06-15 review and has not been addressed.)

@@ -66,7 +66,7 @@ public sealed class TrieStore : ITrieStore, IPruningTrieStore
private readonly bool _pastKeyTrackingEnabled = false;

private bool _lastPersistedReachedReorgBoundary;
private long _toBePersistedBlockNumber = -1;
private ulong _toBePersistedBlockNumber = ulong.MinValue;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Medium: ulong.MinValue (0) is a valid block number (genesis), not a safe sentinel

ulong.MinValue == 0, which is genesis block number. When pruning fires at a node that persisted genesis (LastPersistedBlockNumber == 0), line 614 sets _toBePersistedBlockNumber = 0. Then every concurrent BeginScope call on line 350 sees persistedBoundary == ulong.MinValue and spin-waits indefinitely — it interprets genesis as "not yet determined".

The TrieStoreDirtyNodesCache already uses ulong.MaxValue as its "not set" sentinel (NoCommitSentinel). Use the same sentinel here:

Suggested change
private ulong _toBePersistedBlockNumber = ulong.MinValue;
private ulong _toBePersistedBlockNumber = ulong.MaxValue;

And update the check on line 350 and all resets (lines 642, 684) from ulong.MinValueulong.MaxValue.

LukaszRozmej and others added 4 commits June 23, 2026 18:52
- CompositePersistenceStrategyTests: long blockNumber -> ulong to match
  the post-merge ShouldPersist(ulong) signature.
- MemoryHintManTests: drop now-unused Core.Extensions using directive
  (left behind after the (ulong)256.MB -> 256 * MB cleanup).
# Conflicts:
#	src/Nethermind/Nethermind.Xdc/SubnetPenaltyHandler.cs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unify types with Geth

5 participants