While reviewing #5423, a more general upgrade invariant surfaced that seems worth making explicit Core-wide.
For valid histories H1, H2, if protocol version v maps them to the same canonical Core state:
C_v(H1) == C_v(H2)
then every later protocol rule must treat them identically unless an earlier upgrade canonically preserved the missing distinction.
Otherwise restart/catchup/replay cannot derive the new semantics from authoritative state alone.
Concrete #5423 example: AccountEntryExtensionV3.seqTime stores whole seconds. Origins 10.001s and 10.999s therefore collapse to the same persisted account state today, while a future millisecond minSeqAge could require opposite answers. A migration added later cannot reconstruct which origin happened.
Safe rollout shape:
current semantics -> preserve richer state with no behavior change -> later version may consume it
For already-coarse legacy state, either keep legacy interpretation or replace it through a new authoritative write that records the richer state.
This looks reusable beyond time: XDR refinements, protocol-version transitions, replay/catchup requirements, randomness locks, and any future feature that wants to add meaning to information older Core intentionally discarded.
Potential regression pattern: construct pairs of valid pre-upgrade histories that serialize to identical canonical state; any post-upgrade observable must remain identical unless the distinguishing information was canonically preserved before activation.
Would this be useful as a standard protocol-upgrade test/invariant?
While reviewing #5423, a more general upgrade invariant surfaced that seems worth making explicit Core-wide.
For valid histories
H1,H2, if protocol versionvmaps them to the same canonical Core state:C_v(H1) == C_v(H2)then every later protocol rule must treat them identically unless an earlier upgrade canonically preserved the missing distinction.
Otherwise restart/catchup/replay cannot derive the new semantics from authoritative state alone.
Concrete #5423 example:
AccountEntryExtensionV3.seqTimestores whole seconds. Origins10.001sand10.999stherefore collapse to the same persisted account state today, while a future millisecondminSeqAgecould require opposite answers. A migration added later cannot reconstruct which origin happened.Safe rollout shape:
current semantics -> preserve richer state with no behavior change -> later version may consume itFor already-coarse legacy state, either keep legacy interpretation or replace it through a new authoritative write that records the richer state.
This looks reusable beyond time: XDR refinements, protocol-version transitions, replay/catchup requirements, randomness locks, and any future feature that wants to add meaning to information older Core intentionally discarded.
Potential regression pattern: construct pairs of valid pre-upgrade histories that serialize to identical canonical state; any post-upgrade observable must remain identical unless the distinguishing information was canonically preserved before activation.
Would this be useful as a standard protocol-upgrade test/invariant?