Conversation
A pointer's owner key cannot change, but what its address resolves to can be handed over for good: the owner signs one last state, at counter u64::MAX, pointing at a pointer the new owner holds the key to. Readers of the address are redirected there, and the address never changes. That only works if the last state stays last. Under the previous order a state at u64::MAX was still displaced by another at the same counter with smaller target bytes, so a former owner could grind a target and take the address back. replaces() now has a rule ahead of the other two: nothing replaces a final state, not even another final one. Below the final counter the order is unchanged. The price is that two different final states are unordered, so each node keeps whichever it took first. Only the owner can make that fork, only by racing two final states to different nodes, and only while no node holds a final state yet: once one does, no node that holds it takes another. Which side a reader believes is decided by how many of the close group hold each, on the client. Adds FINAL_COUNTER, Pointer::finalize, Pointer::transfer_to and transferred_to on both the record and its parsed state. finalize refuses to sign past a record that is already final, since that is how a fork is made.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Linear issue
Closes V2-1354
Risk tier
This changes the pointer merge rule that nodes and clients both apply. The change is one comparison, at the final counter.
What
A pointer's owner key cannot change. The address can still be handed over for good: the owner signs one last state at
counter == u64::MAX, pointing at a pointer the new owner holds the key to. Every reader of the address is then redirected to the new owner's pointer, and the address stays the same.Under ADR-0016's rule that does not stick. A state at
u64::MAXis still displaced by another state at the same counter with smaller target bytes, so a former owner can grind a target in about two tries and take the address back. This PR adds one rule ahead of the other two:Below the final counter nothing changes. At the final counter, two different final states are unordered, so a node keeps whichever it took first. Only the owner can create that fork, by racing two final states to different nodes before any node holds one. Once a node holds a final state, no arrival moves it off. The node PR also has a node look at its close group before taking a final state, and the client PR reads the side a majority holds and reports forks.
replacesstays a strict partial order: it is never true both ways round, it is transitive, and it is total except between two final states.Also adds
FINAL_COUNTER,Pointer::finalize(refuses to sign past a record that is already final, since a second final state is how a fork is made),Pointer::transfer_to, andtransferred_toon both the record andPointerState.PointerError::CounterExhaustednow says the pointer is final, instead of advising a migration before the last counter.Companion PRs: WithAutonomi/ant-node#239 (ADR-0018, and the node's look before a final state) and WithAutonomi/ant-client#210 (transfer, finality check, majority reads). Both pin this branch's head by rev.
Compatibility
u64::MAX.FINAL_COUNTER,Pointer::finalize,Pointer::transfer_to,Pointer::transferred_to,PointerState::is_terminal,PointerState::transferred_to. One behaviour change:PointerState::replaces/Pointer::replacesreturnfalsewhen the held state is final. Nodes and clients must agree on it. A node still on the old rule lets a smaller-target final state displace the first; see ADR-0018's mixed-fleet note.Semver impact
Pointers are not in a published release yet (3.0.0 has none), so the rule change lands on an unreleased surface.
Test evidence
cargo test --lib: 108 passed, 21 of them inpointer. New tests:transfer_tosigns at the final counter to the recipient and refuses to sign past a final record.cargo clippy --all-targets --all-features -- -D warnings,cargo fmt --all -- --check,RUSTDOCFLAGS="-D warnings" cargo doc --all-features --no-depsandcargo build --no-default-features: all clean.New dependency
None.
ADR
https://github.com/WithAutonomi/ant-node/blob/feat/pointer-ownership-transfer/docs/adr/ADR-0018-pointer-transfer-by-final-redirection.md:
ADR-0018, added by WithAutonomi/ant-node#239. It amends ADR-0016's merge rule at the final counter.Mitigation / rollback
Revert the rule-0 line in
PointerState::replaces. Nodes and clients pin this crate by rev, so a rollback is a re-pin, and no stored data changes shape either way.