Skip to content

refactor: Improve assumeutxo state representation - #30214

Merged
fanquake merged 17 commits into
bitcoin:masterfrom
ryanofsky:pr/cstate
Dec 16, 2025
Merged

refactor: Improve assumeutxo state representation#30214
fanquake merged 17 commits into
bitcoin:masterfrom
ryanofsky:pr/cstate

Conversation

@ryanofsky

Copy link
Copy Markdown
Contributor

This PR contains the first part of #28608, which tries to make assumeutxo code more maintainable, and improve it by not locking cs_main for a long time when the snapshot block is connected, and by deleting the snapshot validation chainstate when it is no longer used, instead of waiting until the next restart.

The changes in this PR are just refactoring. They make Chainstate objects self-contained, so for example, it is possible to determine what blocks to connect to a chainstate without querying ChainstateManager, and to determine whether a Chainstate is validated without basing it on inferences like &cs != &ActiveChainstate() or GetAll().size() == 1.

The PR also tries to make assumeutxo terminology less confusing, using "current chainstate" to refer to the chainstate targeting the current network tip, and "historical chainstate" to refer to the chainstate downloading old blocks and validating the assumeutxo snapshot. It removes uses of the terms "active chainstate," "usable chainstate," "disabled chainstate," "ibd chainstate," and "snapshot chainstate" which are confusing for various reasons.

@DrahtBot

DrahtBot commented May 31, 2024

Copy link
Copy Markdown
Contributor

The following sections might be updated with supplementary metadata relevant to reviewers and maintainers.

Code Coverage & Benchmarks

For details see: https://corecheck.dev/bitcoin/bitcoin/pulls/30214.

Reviews

See the guideline for information on the review process.

Type Reviewers
ACK maflcko, fjahr, sedited
Concept ACK mzumsande

If your review is incorrectly listed, please copy-paste <!--meta-tag:bot-skip--> into the comment that the bot should ignore.

Conflicts

Reviewers, this pull request conflicts with the following ones:

  • #34079 (kernel: Remove non-kernel module includes by sedited)
  • #34075 (fees: Introduce Mempool Based Fee Estimation to reduce overestimation by ismaelsadeeq)
  • #34054 (net processing: Add ibd check before processing block for txdownloadman by sedited)
  • #34004 (Implementation of SwiftSync by rustaceanrob)
  • #33854 (fix assumevalid is ignored during reindex by Eunovo)
  • #33817 (validation: reduce persisted UTXO set size by prioritizing positive lookups (RFC) by l0rinc)
  • #33779 (ci, iwyu: Fix warnings in src/kernel and treat them as errors by hebasto)
  • #33728 (test: Add bitcoin-chainstate test for assumeutxo functionality by stringintech)
  • #33680 (validation: do not wipe utxo cache for stats/scans/snapshots by l0rinc)
  • #33604 (p2p: Allow block downloads from peers without snapshot block after assumeutxo validation by stringintech)
  • #33259 (rpc, logging: add backgroundvalidation to getblockchaininfo by polespinasa)
  • #32966 (Silent Payments: Receiving by Eunovo)
  • #32950 (validation: remove BLOCK_FAILED_CHILD by stratospher)
  • #32843 (doc: invalid block handling followups by mzumsande)
  • #31533 (fuzz: Add fuzz target for block index tree and related validation events by mzumsande)
  • #31382 (kernel: Flush in ChainstateManager destructor by sedited)
  • #30342 (kernel, logging: Pass Logger instances to kernel objects by ryanofsky)
  • #29700 (kernel, refactor: return error status on all fatal errors by ryanofsky)
  • #26022 (Add util::ResultPtr class by ryanofsky)
  • #25722 (refactor: Use util::Result class for wallet loading by ryanofsky)
  • #25665 (refactor: Add util::Result failure types and ability to merge result values by ryanofsky)

If you consider this pull request important, please also help to review the conflicting pull requests. Ideally, start with the one that should be merged first.

@DrahtBot

Copy link
Copy Markdown
Contributor

🚧 At least one of the CI tasks failed. Make sure to run all tests locally, according to the
documentation.

Possibly this is due to a silent merge conflict (the changes in this pull request being
incompatible with the current code in the target branch). If so, make sure to rebase on the latest
commit of the target branch.

Leave a comment here, if you need help tracking down a confusing failure.

Debug: https://github.com/bitcoin/bitcoin/runs/25656143496

@fjahr

fjahr commented Jun 8, 2024

Copy link
Copy Markdown
Contributor

Concept ACK

ryanofsky and others added 15 commits December 12, 2025 06:49
Get rid of m_disabled/IsUsable members. Instead of marking chains disabled for
different reasons, store chainstate assumeutxo status explicitly and use that
information to determine how chains should be treated.
Move duplicate code from ChainstateManager::ActivateSnapshot and
ChainstateManager::ActivateExistingSnapshot methods to a new
ChainstateManager::AddChainstate method.

The "AddChainstate" method name doesn't mention snapshots even though it is
only used to add snapshot chainstates now, because it becomes more generalized
in a later commit in this PR ("refactor: Add ChainstateManager::m_chainstates
member")
Remove hardcoded references to m_ibd_chainstate and m_snapshot_chainstate so
MaybeCompleteSnapshotValidation function can be simpler and focus on validating
the snapshot without dealing with internal ChainstateManager states.

This is a step towards being able to validate the snapshot outside of
ActivateBestChain loop so cs_main is not locked for minutes when the snapshot
block is connected.
Use to simplify code determining the chainstate leveldb paths. New method is
the now the only code that needs to figure out the storage path, so the path
doesn't need to be constructed multiple places and backed out of leveldb.
CurrentChainstate() is basically the same as ActiveChainstate() except it
requires cs_main to be locked when it is called, instead of locking cs_main
internally.

The name "current" should also be less confusing than "active" because multiple
chainstates can be active, and CurrentChainstate() returns the chainstate
targeting the current network tip, regardless of what chainstates are being
downloaded or how they are used.
ValidatedChainstate() accessor replaces GetChainstateForIndexing() with no
change in behavior.
Change ChainstateRole parameter passed to wallets and indexes. Wallets and
indexes need to know whether chainstate is historical and whether it is fully
validated. They should not be aware of the assumeutxo snapshot validation
process.
IsSnapshotActive() method is only called one place outside of tests and
asserts, and is confusing because it returns true even after the snapshot is
fully validated.

The documentation which said this "implies that a background validation
chainstate is also in use" is also incorrect, because after the snapshot is
validated, the background chainstate gets disabled and IsUsable() would return
false.
IsSnapshotValidated() is only called one place outside of tests, and is use
redundantly in some tests, asserting that a snapshot is not validated when a
snapshot chainstate does not even exist. Simplify by dropping the method and
checking Chainstate m_assumeutxo field directly.
SnapshotBlockhash() is only called two places outside of tests, and is used
redundantly in some tests, checking the same field as other checks. Simplify by
dropping the method and using the m_from_snapshot_blockhash field directly.
Use to replace m_active_chainstate, m_ibd_chainstate, and m_snapshot_chainstate
members. This has several benefits:

- Ensures ChainstateManager treats chainstates instances equally, making
  distinctions based on their attributes, not having special cases and making
  assumptions based on their identities.

- Normalizes ChainstateManager representation so states that should be
  impossible to reach and validation code has no handling for (like
  m_snapshot_chainstate being set and m_ibd_chainstate being unset, or both
  being set but m_active_chainstate pointing to the m_ibd_chainstate) can no
  longer be represented.

- Makes ChainstateManager more extensible so new chainstates can be added for
  different purposes, like indexing or generating and validating assumeutxo
  snapshots without interrupting regular node operations. With the
  m_chainstates member, new chainstates can be added and handled without needing
  to make changes all over validation code or to copy/paste/modify the existing
  code that's been already been written to handle m_ibd_chainstate and
  m_snapshot_chainstate.

- Avoids terms that are confusing and misleading:

  - The term "active chainstate" term is confusing because multiple chainstates
    will be active and in use at the same time. Before a snapshot is validated,
    wallet code will use the snapshot chainstate, while indexes will use the IBD
    chainstate, and netorking code will use both chainstates, downloading
    snapshot blocks at higher priority, but also IBD blocks simultaneously.

  - The term "snapshot chainstate" is ambiguous because it could refer either
    to the chainstate originally loaded from a snapshot, or to the chainstate
    being used to validate a snapshot that was loaded, or to a chainstate being
    used to produce a snapshot, but it is arbitrary used to refer the first
    thing. The terms "most-work chainstate" or "assumed-valid chainstate" should
    be less ambiguous ways to refer to chainstates loaded from snapshots.

  - The term "IBD chainstate" is not just ambiguous but actively confusing
    because technically IBD ends and the node is considered synced when the
    snapshot chainstate finishes syncing, so in practice the IBD chainstate
    will mostly by synced after IBD is complete. The term "fully-validated" is
    a better way of describing the characteristics and purpose of this
    chainstate.
Deduplicate code looping over chainstate objects and calling
ActivateBestChain() and avoid need for code outside ChainstateManager to use
the GetAll() method.
Just use m_chainstates array instead.
Move GetPruneRange from ChainstateManager to Chainstate.
@maflcko

maflcko commented Dec 12, 2025

Copy link
Copy Markdown
Member

Only change is rebase and fix of two LLM nits.

re-review ACK 82be652 🕍

Show signature

Signature:

untrusted comment: signature from minisign secret key on empty file; verify via: minisign -Vm "${path_to_any_empty_file}" -P RWTRmVTMeKV5noAMqVlsMugDDCyyTSbA3Re5AkUrhvLVln0tSaFWglOw -x "${path_to_this_whole_four_line_signature_blob}"
RUTRmVTMeKV5npGrKx1nqXCw5zeVHdtdYURB/KlyA/LMFgpNCs+SkW9a8N95d+U4AP1RJMi+krxU1A3Yux4bpwZNLvVBKy0wLgM=
trusted comment: re-review ACK 82be652e40ec7e1bea4b260ee804a92a3e05f496 🕍
DCfUEEgxlpRFVrZNQ1J8RKhoRVJ+eV5+NOwbq0XgVloeD4T3M3orc/5G1cUGNM0b0D5Qc4CexDOp8iiv9lFvCg==

@DrahtBot
DrahtBot requested a review from sedited December 12, 2025 12:42

@ryanofsky ryanofsky left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Thanks for the reviews!

Rebased 5dd7cbf -> 82be652 (pr/cstate.23 -> pr/cstate.24, compare) fixing conflict #32414 and also adding suggested arg name comments

Comment thread src/interfaces/chain.h
Comment on lines +395 to +396
//! returns true even after the snapshot is validated, until the next node
//! restart.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

re: #30214 (comment)

q in cb3e7af: I wonder if this should be fixed in a follow-up, because the comment in AttachChain says that the loop called when hasAssumedValidChain is true may be slow.

Yes it could be nice to change this and make loading old wallets on non-pruned nodes faster, even though it's a little bit of an edge case

Comment thread src/test/util/chainstate.h Outdated
Assert(chain.LoadGenesisBlock());
// These cache values will be corrected shortly in `MaybeRebalanceCaches`.
chain.InitCoinsDB(1 << 20, true, false, "");
chain.InitCoinsDB(1 << 20, true, false);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

re: #30214 (comment)

llm nit in 6c98ddc: could use named args while touching?

Makes sense, added.

Comment thread src/validation.cpp Outdated
snapshot_chainstate->InitCoinsDB(
static_cast<size_t>(current_coinsdb_cache_size * SNAPSHOT_CACHE_PERC),
in_memory, false, "chainstate");
in_memory, false);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

re: #30214 (comment)

llm nit in the same commit: name args?

Thanks also added this

Comment thread src/validation.cpp
validated_cs.m_assumeutxo != Assumeutxo::VALIDATED ||
!validated_cs.m_chain.Tip() ||
// Or the validated chainstate is not targeting the snapshot block...
!validated_cs.m_target_blockhash ||

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

re: #30214 (comment)

i'd say assumes are fine to detect logic bugs or storage corruption, but no strong opinion, if the goal is to somehow make this more flexible for future changes

Yeah I think it needs to be up to callers to check their own expected states or to check the return status of this function. Having this function make unnecessary assumptions about particular states it thinks it will be called in seems like it could be easily break if any outside code changes

@fjahr

fjahr commented Dec 12, 2025

Copy link
Copy Markdown
Contributor

re-ACK 82be652

Confirmed that changes since last review were addressing nits and rebasing.

@sedited sedited left a comment

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.

Re-ACK 82be652

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.

8 participants