mirror of
https://github.com/XRPLF/rippled.git
synced 2026-09-28 15:58:07 +00:00
An immutable map is treated as persistable, so SHAMap::setImmutable() returns [[nodiscard]] bool and refuses a map that has been proven impossible. Every state change goes through trySetState(), whose compare-exchange refuses to leave Invalid however it interleaves with another thread's, so setSynching() and clearSynching() cannot launder an abandoned map back into a state that passes isValid() either. clearSynching() reports its refusal and carries on, since peer data produces that verdict and an abort there would be one a peer could ask for, while setSynching() keeps an UNREACHABLE and says why it is out of reach: it only ever runs on a map that has just been constructed. The snapshot constructor carries Invalid over rather than promoting it, since a snapshot shares the source's root, and reads the source's state once into a local so a concurrent walk cannot have it report two different things. Ledger::setImmutable() and Ledger::setAccepted() do the same one level up. Both return [[nodiscard]] bool, and setImmutable() asks mapsValid() before writing anything, so a refusal leaves the header exactly as it was rather than relabelled on its way to failing. Past that check it settles both maps through setMapsImmutable(), which is deliberately not short-circuited: each map becomes Immutable or stays Invalid on its own, and neither is left mid-sync because the other refused. immutable_ is set last, so isImmutable() never reports a ledger whose maps are not both immutable. The guard is best-effort by nature, which the comments say: setInvalid() outranks Immutable, so a walk that reaches the verdict after both maps are settled narrows the window rather than closing it. All fourteen call sites branch on the result, and the rule that a ledger built or loaded locally cannot have an invalid map is stated once, in Ledger::setImmutable()'s docstring, with each such site pointing there. The tiers differ by what the caller can do: the two genesis paths and buildLedgerImpl() call logicError(), the last of those explaining why it takes the harsher tier on the consensus hot path; loadLedgerFromFile(), getLastFullLedger() and finishLoadByIndexOrHash() return instead, and the last of those clears the pointer, since nothing gates usability on the full flag and a caller that took the ledger would abort further on. InboundLedger and TransactionAcquire recover, since for them a refusal is an outcome a peer can produce: each withdraws complete_ alongside the failure, so a guard that reads that flag before failed_ cannot go on treating the result as delivered. Six cases cover it. Five are gtest: an invalid map refuses repeatedly and is not synching either; a refusal leaves both header map hashes and the ledger hash untouched; an invalid transaction map and an invalid state map each block the enclosing ledger, covering both operands of the test in setImmutable(); and a snapshot of an invalid map is invalid and unpersistable in both flavors. The boost case drives InboundLedger::done() with a map invalidated after the have-flags were set, and checks the acquisition reports neither complete nor delivered and remembers the hash as a failure.