Skip to content

Hierarchical state diffs in hot DB #6750

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 61 commits into
base: unstable
Choose a base branch
from
Open

Conversation

dapplion
Copy link
Collaborator

@dapplion dapplion commented Jan 2, 2025

Proposed Changes

This PR implements #5978 (tree-states) but on the hot DB. It allows Lighthouse to massively reduce its disk footprint during non-finality and overall I/O in all cases.

Closes #6580

Conga into #6744

TODOs

  • Fix OOM in CI Fix test OOM issues on tree-states-hot #7176
  • optimise store_hot_state to avoid storing a duplicate state if the summary already exists (should be safe from races now that pruning is cleaner)
  • mispelled: get_ancenstor_state_root
  • get_ancestor_state_root should use state summaries
  • Prevent split from changing during ancestor calc
  • Use same hierarchy for hot and cold

TODO Good optimization for future PRs

  • On the migration, if the latest hot snapshot is aligned with the cold snapshot migrate the diffs instead of the full states.
align slot  time
10485760    Nov-26-2024
12582912    Sep-14-2025
14680064    Jul-02-2026

TODO Maybe things good to have

NOTTODO

  • Use fork-choice and a new method descendants_of_checkpoint to filter only the state summaries that descend of finalized checkpoint]

Additional Info

Original WIP PR with lots of context and discussion

Some resources to get up to speed with HDiff concepts:

@dapplion dapplion added work-in-progress PR is a work-in-progress tree-states Ongoing state and database overhaul labels Jan 2, 2025
@dapplion dapplion force-pushed the tree-states-hot-rebase branch from d16f311 to 168d9f0 Compare January 2, 2025 10:01
@chong-he
Copy link
Member

chong-he commented Feb 3, 2025

I have done some testing on this on the Holesky testnet as mentioned in #6775. Regular checkpoint sync works, so does syncing from a checkpoint >24 hours ago. Some findings:

  1. Seeing the following logs when syncing an archive node:
Jan 26 08:04:01.957 ERRO Database write failed, action: reverting blob DB changes, error: MissingHotStateSummary(0x554bd524427dfc0a0efcabbdc65f087dcd053385fd378cc83aa78a5d9b9db167), service: freezer_db, module: store::hot_cold_store:1390
Jan 26 08:04:01.957 ERRO Database write failed!, error: MissingHotStateSummary(0x554bd524427dfc0a0efcabbdc65f087dcd053385fd378cc83aa78a5d9b9db167), msg: Restoring fork choice from disk, service: beacon, module: beacon_chain::beacon_chain:3957

(The first line of this is until slot 3498015, I truncated it)
Shortly after this, the following appears:

Jan 26 08:04:03.946 CRIT Beacon block processing error, error: DBError(MissingHotStateSummary(0x554bd524427dfc0a0efcabbdc65f087dcd053385fd378cc83aa78a5d9b9db167)), service: beacon, module: beacon_chain::beacon_chain:3377
Jan 26 08:05:49.160 CRIT Beacon block processing error, error: MissingBeaconBlock(0x831d25d70b5bdfe7544c0cac196cb781fcea3e9c7c5d6e902ad82059d27fc8ed), service: beacon, module: beacon_chain::beacon_chain:3377
Jan 26 08:12:17.256 WARN BlockProcessingFailure, outcome: MissingBeaconBlock(0x831d25d70b5bdfe7544c0cac196cb781fcea3e9c7c5d6e902ad82059d27fc8ed), msg: unexpected condition in processing block., module: network::network_beacon_processor::sync_methods:747

The beacon node wouldn't be able to start upon a restart:

Jan 27 05:57:58.731 CRIT Failed to start beacon node, reason: Failed to build beacon chain: Head block not found in store, module: lighthouse:721

Another point worth noting is that the logs seem to be in a loop. Similar logs appear repeatedly in the log file, to the extent that it would only take a few seconds/minutes to fill up a 200MB log file. These are the repeated logs:

Jan 26 08:02:47.632 DEBG Created block lookup, id: 1753067, awaiting_parent: none, block_root: 0x523bff6d855c77b4f14ed5d7639a3f4975cef5115e0f0631f2f730665229fbc6, peer_ids: [PeerId("16Uiu2HAm4qmfW7E7E9rkvNVAWeZNWMhZ4VefKJHhjtFDUKaJAGAb"), PeerId("16Uiu2HAkvWFRpCLuiRyxvse9JzXP5HjgTMJfwPcHUCCa5NGwaike"),  PeerId("16Uiu2HAm9pjmagyJFvMkn3bVDPWTVwgyLJ3QdsejndLJnRhz2wfZ")], service: lookup_sync, service: sync, module: network::sync::block_lookups:401
Jan 26 08:02:47.632 DEBG Dropping completed lookup, id: 1753067, block: 0x523bff6d855c77b4f14ed5d7639a3f4975cef5115e0f0631f2f730665229fbc6, service: lookup_sync, service: sync, module: network::sync::block_lookups:781
Jan 26 08:02:47.632 DEBG Continuing child lookup, block_root: 0xf8eb5af88a4ea962e91a6fa77c69e1efa17005c346e7921d4218d0e84ff5a149, id: 113701, parent_root: 0x523bff6d855c77b4f14ed5d7639a3f4975cef5115e0f0631f2f730665229fbc6, service: lookup_sync, service: sync, module: network::sync::block_lookups:733
Jan 26 08:02:47.632 DEBG Sending blobs for processing, id: 113701, block: 0xf8eb5af88a4ea962e91a6fa77c69e1efa17005c346e7921d4218d0e84ff5a149, service: sync, module: network::sync::network_context:1086
Jan 26 08:02:47.632 DEBG RPC blobs received, commitments: [0xb8b1…3151, 0xb0a9…8f7d, 0xb29d…876a], slot: 3497985, block_root: 0xf8eb5af88a4ea962e91a6fa77c69e1efa17005c346e7921d4218d0e84ff5a149, indices: [0, 1, 2], module: network::network_beacon_processor::sync_methods:267
Jan 26 08:02:47.632 WARN Error when importing rpc blobs, slot: 3497985, block_hash: 0xf8eb5af88a4ea962e91a6fa77c69e1efa17005c346e7921d4218d0e84ff5a149, error: ParentUnknown { parent_root: 0x523bff6d855c77b4f14ed5d7639a3f4975cef5115e0f0631f2f730665229fbc6 }, module: network::network_beacon_processor::sync_methods:318
Jan 26 08:02:47.632 DEBG Received lookup processing result, result: Err(ParentUnknown { parent_root: 0x523bff6d855c77b4f14ed5d7639a3f4975cef5115e0f0631f2f730665229fbc6 }), id: 113701, block_root: 0xf8eb5af88a4ea962e91a6fa77c69e1efa17005c346e7921d4218d0e84ff5a149, component: Blob, service: lookup_sync, service: sync, module: network::sync::block_lookups:552
Jan 26 08:02:47.632 DEBG Marking lookup as awaiting parent, parent_root: 0x523bff6d855c77b4f14ed5d7639a3f4975cef5115e0f0631f2f730665229fbc6, block_root: 0xf8eb5af88a4ea962e91a6fa77c69e1efa17005c346e7921d4218d0e84ff5a149, id: 113701,

(The first line of the above log is truncated)

  1. On another try of syncing an archive node:
Jan 22 01:27:34.393 ERRO Database write failed, action: reverting blob DB changes, error: MissingHotStateSummary(0x21262323cc023403605249a57849c267debad4612d7c340523fab29ded71bdb5), service: freezer_db, module: store::hot_cold_store:1390
Jan 22 01:27:34.393 ERRO Database write failed!, error: MissingHotStateSummary(0x21262323cc023403605249a57849c267debad4612d7c340523fab29ded71bdb5), msg: Restoring fork choice from disk, service: beacon, module: beacon_chain::beacon_chain:3957
Jan 22 01:28:51.854 WARN Hot DB pruning failed, error: PruningError(MissingSummaryForFinalizedCheckpoint(0xd395c3f4e6d3eafd733a5554b3191e20f670c444916d868d68157aa07f0887fb)), service: beacon, module: beacon_chain::migrate:381

The beacon node wouldn't be able to start upon a restart:

Jan 22 03:55:27.130 INFO Blob DB initialized                     oldest_data_column_slot: None, oldest_blob_slot: Some(Slot(3336224)), path: "/var/lib/lighthouse_test1/beacon/blobs_db", service: freezer_db
Jan 22 03:55:28.186 CRIT Failed to start beacon node             reason: Unable to open database: HotColdDBError(MissingSplitState(0x855420d7ff98dca1dbe028ef624b18a81a027ab0cb1f95e4c048ad9b3c4510c4, Slot(3467232)))
Jan 22 03:55:28.186 INFO Internal shutdown received              reason: Failed to start beacon node
  1. I have not successfully reconstructed all states (i.e., finished syncing an archived node) on this branch (due to the above errors). It is either stuck during historical blocks download or during state reconstruction. A similar error to no. 2 occurs regularly (during state reconstruction):
Jan 28 07:44:58.442 ERRO Database write failed                   action: reverting blob DB changes, error: Hdiff(LessThanStart(Slot(3504672), Slot(3504704))), service: freezer_db
Jan 28 07:44:58.443 WARN Hot DB pruning failed                   error: DBError(HotColdDBError(Rollback)), service: beacon

(slot 3504704 is the anchor slot from the database info)

  1. Upgrading from v6.0.1:
    Checkpoint sync using v6.0.1, stop Lighthouse once it has checkpoint synced, then change the binary file to this PR branch. Sometimes it can upgrade smoothly, but sometimes it has error:
Jan 28 08:49:27.224 INFO Blob DB initialized                     oldest_data_column_slot: None, oldest_blob_slot: Some(Slot(3497888)), path: "/home/ck/.lighthouse/holesky/beacon/blobs_db", service: freezer_db
Jan 28 08:49:27.556 CRIT Failed to start beacon node             reason: Unable to open database: MigrationError("Migrating from SchemaVersion(22) to SchemaVersion(23): MigrationError(\"error computing states summaries dag MissingStateSummary(0x9d6efd378136fd8a08574b9a7f11b3f04ff5c5a4c15c0eaa64eac0c82f2aadee)\")")
Jan 28 08:49:27.557 INFO Internal shutdown received              reason: Failed to start beacon node

If this error occurs and I change back to v6.0.1 and let it syncs for a few minutes, and then change back again to this binary PR, it could work (upgrade is successful) and the database schema shows V23:

Jan 28 08:54:08.619 INFO Blob DB initialized                     oldest_data_column_slot: None, oldest_blob_slot: Some(Slot(3497504)), path: "/home/ck/.lighthouse/holesky/beacon/blobs_db", service: freezer_db
Jan 28 08:54:14.692 INFO Hot states migration in progress        summaries_written: 33, diff_written: 2
Jan 28 08:54:17.910 INFO Hot states migration complete           summaries_written: 99, diff_written: 3
Jan 28 08:54:20.345 INFO Refusing to checkpoint sync             msg: database already exists, use --purge-db to force checkpoint sync, service: beacon

However, I notice the anchor_slot is changed after upgrading to V23 (e.g., advancing by 1 epoch). Is this expected?

@dapplion dapplion added the backwards-incompat Backwards-incompatible API change label Feb 3, 2025
Copy link

mergify bot commented Feb 3, 2025

This PR is amazing @dapplion, therefore you don't have to fix the conflicts yet. Please take your time and enjoy the day 💮 ❤️

@michaelsproul
Copy link
Member

Should address outstanding comments from the OOM PR too:

Base automatically changed from drop-headtracker to unstable April 7, 2025 04:23
@michaelsproul
Copy link
Member

I've just resolved the merge conflicts after the merge of drop-headtracker. It was a bit gnarly, but I think I got it right. We still have several TODOs to solve.

Copy link

mergify bot commented Apr 10, 2025

This pull request has merge conflicts. Could you please resolve them @dapplion? 🙏

slot: summary.slot,
latest_block_root: summary.latest_block_root,
epoch_boundary_state_root: state_summaries_dag
.ancestor_state_root_at_slot(state_root, epoch_boundary_state_slot)
Copy link
Member

Choose a reason for hiding this comment

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

TODO: check that this handles the genesis state/slot

@michaelsproul
Copy link
Member

TODO: remove StateSummariesNotContiguous failure case. We should try to gracefully handle this case and delete the junk state as part of the migration.

Related to:

.and_then(|parent_block_summaries| {
parent_block_summaries.get(&previous_slot)
})
.map_or(Hash256::ZERO, |(parent_state_root, _)| **parent_state_root)
Copy link
Member

Choose a reason for hiding this comment

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

Rather than 0x0, we could use an enum here. I think we would also need this inside the HotStateSummary.

Another thing we could do would be to differentiate nodes that we don't expect to have a parent state (the split state) and nodes that should have one (everything else).

@dapplion
Copy link
Collaborator Author

dapplion commented May 1, 2025

Changed the DAGStateSummary to hold a Result in this branch / commit 0ab1c7f
. What I don't like about this approach is that after the migration the DAGStateSummary still holds a Result but it will always be Ok

pub struct DAGStateSummary {
    ...
    pub previous_state_root: Result<Hash256, String>,
}

@dapplion
Copy link
Collaborator Author

dapplion commented May 1, 2025

Idea: if migration fails because the DAG of states is broken, could we just drop all unfinalized data? Basically, drop the entire fork-choice and prune the blocks from the DB. Then we have an infallible migration. Provided we have the best possible code, it's best to require sync from a few unlucky users than to get them stuck. We have seen that the chance of the DB having a broken DAG is low and not everyone will update at the same time.

@dapplion
Copy link
Collaborator Author

dapplion commented May 1, 2025

More thoughts:

At the migration we need to copy the summaries to the new DB column with the new format. The new format requires adding:

  • previous_state_root
  • diff_base_state

We don't need to copy ALL hot summaries. Only those to preserve the invariant:

  • For each block there's a state_summary for each slot between finalized_slot and block_slot in the chain of ancestors of block
  • For each state_summary there's a way to compute the full state of that state_root

IDEA: We don't need to copy all summaries. We can ignore the summaries for advanced states, and the non-descendants of finalized block. We can also over-copy and future pruning rounds will get rid of them.

Where to get previous_state_root from?

  • From the DAG, like the current impl of the PR
  • But we could also load the state at state_root and check its state_roots vector. With caching and finality not too far away we only need to load one state per head

Filter to reduce chance of bugs

is this a correct way of detecting advanced states?

struct Block {
  slot: Slot,
  highest_child: Option<Slot>,
}

fn is_advanced_state(state_summary: StateSummary) -> bool {
  let block: Block = get_block(state_summary.latest_block_root);
  match block.highest_child {
    Some(highest_child) => state_summary.slot > highest_child,
    None => state_summary.slot > block.slot,
  }
}

@michaelsproul michaelsproul added the v7.1.0 Post-Electra release label May 15, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
backwards-incompat Backwards-incompatible API change tree-states Ongoing state and database overhaul v7.1.0 Post-Electra release work-in-progress PR is a work-in-progress
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants