Skip to content

Conversation

@mrzeszutko
Copy link
Contributor

@mrzeszutko mrzeszutko commented Oct 14, 2025

…ate. Relates to #9408

Changes

  • Updated BlockChainBridge. HasStateForBlock to check the configuration and not call the state at all. The result is calculated based on the node type: archive node vs. full node, the information if we finish the initial sync and pruning boundary.
  • To cater for the race condition, we return false at the pruning boundary (So we return that we don't have state when we are at the pruning boundary) - this is a safety margin

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

Documentation

Remarks

There were no changes done to the snap serving. 128 is hardcoded there so there should be no impact

@mrzeszutko mrzeszutko marked this pull request as ready for review October 16, 2025 14:49
Comment on lines 55 to 60

/// <summary>
/// Gets the block number of the last persisted block.
/// Used to track the oldest available state after full pruning operations.
/// </summary>
long LastPersistedBlockNumber { get; }
Copy link
Member

Choose a reason for hiding this comment

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

Are you sure this is the OLDEST persisted block number or just LAST (newest) persisted block number?
Not entirely sure if it solves what we expect it does.
@asdacap can you advise?

Copy link
Contributor

Choose a reason for hiding this comment

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

Its newest

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I removed this

{
// Archive mode: but state only available from sync pivot onwards
// If node was snap/fast synced, state before pivot was never downloaded
long syncPivotBlock = blockTree.SyncPivot.BlockNumber;
Copy link
Member

Choose a reason for hiding this comment

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

Are we sure BlockTree.SyncPivot is reliable data here?
Is it 0 for full archive? Is it 1st block with state for archive with snap sync?
@asdacap can you advise?

Copy link
Contributor

Choose a reason for hiding this comment

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

SyncPivot moves nowadays. So no.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I removed SyncPivot dependency

// 3. Current pruning window (head - boundary)
long lastPersistedBlock = pruningTrieStore.LastPersistedBlockNumber;
long syncPivotBlock = blockTree.SyncPivot.BlockNumber;
long pruningWindowStart = headNumber - pruningConfig.PruningBoundary + 1; // +1 for safety margin
Copy link
Member

Choose a reason for hiding this comment

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

this calculation is used in both if and else path, let's name it as local method? Or do it before if and name correctly.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

not relevant anymore after my last changes

@asdacap
Copy link
Contributor

asdacap commented Oct 17, 2025

I think the fundamental issue is that 'HasStateForBlockcheck inIWorldStateandIStateReader` is not correct. I was kinda not correct as the existance of the root node does not necessarily mean that the whole trie is available. Its also weird that the root node is not pruned randomly.

@asdacap
Copy link
Contributor

asdacap commented Oct 17, 2025

Also, checkout LastNStateRootTracker.

@LukaszRozmej
Copy link
Member

I think the fundamental issue is that 'HasStateForBlockcheck inIWorldStateandIStateReader` is not correct. I was kinda not correct as the existance of the root node does not necessarily mean that the whole trie is available. Its also weird that the root node is not pruned randomly.

Why the existance of root node doesn't imply the existence of the trie?
That was basics of Trie in the past that we only persisted parent node if all it's children were persisted.

@asdacap
Copy link
Contributor

asdacap commented Oct 17, 2025

Its children might get removed during in memory pruning. Thats why. Some of the nodes may not get removed due to memory limitation for tracking.

@LukaszRozmej
Copy link
Member

LukaszRozmej commented Oct 17, 2025

Its children might get removed during in memory pruning. Thats why. Some of the nodes may not get removed due to memory limitation for tracking.

Sure but that is deviation from previous assumptions that were commonly used (like in RPC) and to be honest poses a potential for hard to track bugs (though might be necessary for better pruning)

@mrzeszutko
Copy link
Contributor Author

I think the fundamental issue is that 'HasStateForBlockcheck inIWorldStateandIStateReader` is not correct. I was kinda not correct as the existance of the root node does not necessarily mean that the whole trie is available. Its also weird that the root node is not pruned randomly.

@asdacap is this relevant for this PR? For RPC calls we don't want to reference state, just base the response on config (e.g. pruning boundary etc)

Comment on lines +407 to +410
// For archive nodes that were snap synced, check the lowest inserted header
// We use LowestInsertedHeader (not SyncPivot) because SyncPivot moves forward over time
// as finalized blocks progress, while LowestInsertedHeader remains at the original starting point
long? lowestInsertedBlockNumber = blockTree.LowestInsertedHeader?.Number;
Copy link
Member

Choose a reason for hiding this comment

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

This is false, we always have all headers.
We will probably always have all block bodies too.

// Pruning nodes: calculate oldest available state based on pruning boundary
// This is conservative but correct for all pruning node configurations:
// - Memory pruning: respects the configured boundary
// - Full pruning: also uses pruning boundary (persisted state within this window)
Copy link
Member

Choose a reason for hiding this comment

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

I think it doesn't work for full pruning only like this

@asdacap
Copy link
Contributor

asdacap commented Oct 21, 2025

just base the response on config (e.g. pruning boundary etc)

The RPC consult the world state if a state is available or not. The world state is faulty. IMO this check is important especially for triestore as during memory pruning (around 1 to 2 second on mainnet probably every 30 minute), nodes are concurrently removed, so it definitely need to check config like what you did. However, think carefully, now it uses pruning boundary, later its gonna respect finalized block, later again its gonna have special archive node that only have last 1 mil block or something, do you really want to have all that logic in BlockchainBridge? Consider putting it in IWorldStateManager or IStateReader instead. Except for the sync check part. I guess that make sense here.


.Map<IWorldStateManager, PruningTrieStateFactoryOutput>((o) => o.WorldStateManager)
.Map<IStateReader, IWorldStateManager>((m) => m.GlobalStateReader)
.Map<IPruningTrieStore, MainPruningTrieStoreFactory>((f) => f.PruningTrieStore)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think you dont need this anymore. Please dont use IPruningTrieStore. It will make migrating to other world state layout harder.

@asdacap
Copy link
Contributor

asdacap commented Oct 21, 2025

I think you can simply check for IBlockTree.BestPersistedState, which is kinda the same as TrieStore.LastPersistedBlockNumber. It should already have pruning boundary considered. Except for archive mode, then just dont check.

@LukaszRozmej
Copy link
Member

just base the response on config (e.g. pruning boundary etc)

The RPC consult the world state if a state is available or not. The world state is faulty. IMO this check is important especially for triestore as during memory pruning (around 1 to 2 second on mainnet probably every 30 minute), nodes are concurrently removed, so it definitely need to check config like what you did. However, think carefully, now it uses pruning boundary, later its gonna respect finalized block, later again its gonna have special archive node that only have last 1 mil block or something, do you really want to have all that logic in BlockchainBridge? Consider putting it in IWorldStateManager or IStateReader instead. Except for the sync check part. I guess that make sense here.

I would consider putting it in IPruningStrategy

@LukaszRozmej LukaszRozmej marked this pull request as draft October 22, 2025 21:18

// Conservative: don't claim we have state during state sync
// This prevents race conditions during the sync process for all node types
if (ethSyncingInfo.SyncMode.HaveNotSyncedStateYet())
Copy link
Contributor

Choose a reason for hiding this comment

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

This can be incorrect in edge case - archive node, starting from scratch, archive node should always have state

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants