fix: Stop a ledger built from a header from claiming to be immutable - #8089
Conversation
There was a problem hiding this comment.
Pull request overview
This PR corrects the semantics of Ledger(LedgerHeader const&, Rules, Family&) so a ledger created from an externally supplied header (acquisition/replay paths) does not report isImmutable() until setImmutable() has successfully settled both maps, and so settling does not overwrite the header’s map hashes that the maps are intended to sync against.
Changes:
- Initialize header-constructed ledgers with
immutable_ = falseand record that the header’s map hashes are externally supplied (mapHashesFromHeader_ = true). - Update
Ledger::setImmutable()to skip derivingtxHash/accountHashfrom maps when those hashes came from the header. - Add gtests covering (1) immutability stays false until settling succeeds and (2) header-provided hashes are preserved across settling.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.
| File | Description |
|---|---|
| src/libxrpl/ledger/Ledger.cpp | Makes header-based ledger construction start mutable and prevents setImmutable() from rewriting externally supplied map hashes. |
| include/xrpl/ledger/Ledger.h | Documents the updated setImmutable() behavior and adds the mapHashesFromHeader_ flag. |
| src/tests/libxrpl/shamap/SHAMapSync.cpp | Adds regression tests for immutability gating and preserving header map hashes when settling. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
f887be7 to
6b4b9cd
Compare
6b4b9cd to
619b615
Compare
619b615 to
e5e1236
Compare
e5e1236 to
dd8bd69
Compare
dd8bd69 to
edd160f
Compare
|
This PR has conflicts, please resolve them in order for the PR to be reviewed. |
1 similar comment
|
This PR has conflicts, please resolve them in order for the PR to be reviewed. |
edd160f to
3fa57e2
Compare
|
All conflicts have been resolved. Assigned reviewers can now start or resume their review. |
3fa57e2 to
db1bc1d
Compare
db1bc1d to
14d643a
Compare
Ledger(LedgerHeader const&, Rules, Family&) now starts at immutable_ false: its maps are constructed Synching and filled in afterward by an acquisition or a replay, so the ledger is only settled by setImmutable() once both maps are sound. mapHashesFromHeader_ records that this constructor's hashes are input rather than derived, so setImmutable() leaves them alone - deriving them from the maps would relabel a map that fell short of its target instead of refusing it.
14d643a to
720b42e
Compare
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
Part 10/16 of a stack. Base: part 9 (
bthomee/shamap-invalid-09-settle-before-complete).High Level Overview of Change
The
Ledgerconstructor built from aLedgerHeader(used by acquisition and replay) now startsimmutable_false and only becomes immutable oncesetImmutable()confirms both maps are sound, insteadof claiming to be immutable from construction while its maps are still mid-sync.
Context of Change
Ledger(LedgerHeader const&, Rules, Family&)starts atimmutable_false. Its maps are constructedSynchingand filled in afterwards, by an acquisition syncing against the hashes the header carries orby a replay that only reads the header, so the ledger is settled by
setImmutable()once both maps aresound rather than at construction. Every consumer of
isImmutable()therefore sees an in-flightacquisition for what it is, which is what
LedgerHistory::insert()andLedgerReplayMsgHandlergate on.InboundLedger::done()(part 9) already publishes completeness only after settling, so no readerobserves the flag before the ledger it describes is immutable.
mapHashesFromHeader_records that this constructor's transaction and account hashes are input ratherthan derived, and
setImmutable()therefore leaves them alone. Deriving them from the maps would turn aledger verified against a hash we asked for into one that is merely self-consistent: a map that fell
short of its target would be relabelled instead of refused. It is fixed at construction, unlike
immutable_, and set by this constructor alone, so every other constructor still derives its maphashes.
Two gtest cases cover it: the first builds a ledger from a header naming a chain root, checks both maps
report as syncing and the ledger does not report as immutable, then abandons the transaction map and
checks settling refuses; the second leaves the transaction map empty while the header names that root,
so settling succeeds and both the named root and the ledger hash it was verified against are still what
the header carried.
API Impact
libxrplchange (any change that may affectlibxrplor dependents oflibxrpl)