fix(l1): scope TIP-403 policy-set membership to the queried block#651
Open
MattewGraham wants to merge 2 commits into
Open
fix(l1): scope TIP-403 policy-set membership to the queried block#651MattewGraham wants to merge 2 commits into
MattewGraham wants to merge 2 commits into
Conversation
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.
Scope TIP-403 policy-set membership to the queried block
What breaks
A blacklisted account can transact on a zone, and a whitelisted account can be
wrongly rejected, whenever the L1 subscriber has observed a later policy-set
change for that account than the block the engine is currently authorizing.
Invariant at risk:
TEMPO-ZONE-TIP403-INHERITANCE(🔴) — "Zone token transfer,mint, and withdrawal paths enforce the TIP-403 policy inherited from the current
finalized Tempo view."
Root cause
PolicySetanswers membership per block viacontains(user, block_number), but thegate that decides whether that answer is trustworthy was block-agnostic:
observedwas populated the moment any event for the user was applied, at anyheight. The subscriber runs ahead of the engine (the cache advances only after the
engine finishes a block), so the set routinely holds a user's future delta while the
engine queries an earlier block.
is_knownthen reportedtrue,check_policy/check_simpletrustedcontains, and the RPC fallback that exists precisely for"we don't actually know" was skipped.
Worked example (blacklist bypass)
Thas a BLACKLIST policy. AccountBis blacklisted as of L1 block 1 —before the subscriber started, so no add event is ever seen and the baseline is
empty for
B.Bis removed from the blacklist(
MembershipChanged{in_set: false}), recorded as a pending delta and markingBobserved.
is_known(B)istrue(observed),contains(B, 120)finds no delta ≤ 120 and no baseline entry →false, soBLACKLIST yields
Some(!false) = Some(true)—Bis authorized at a blockwhere it is blacklisted. No RPC fallback fires.
The whitelist case is the mirror image: a genuinely-authorized user is rejected.
Fix
Membership from an event is only meaningful at and after the block the event was
observed on. Replace the block-agnostic
observedset withfirst_seen: HashMap<Address, u64>— the earliest block at which we recorded any event for theaddress — and make known-ness block-scoped:
For a query strictly earlier than the first event we hold for a user (the case
above), and for users we've never seen,
is_knownnow returnsfalse, socheck_policy/check_simplereturnNoneand the provider falls back toauthoritative L1 RPC.
first_seensurvivesadvance()(folding deltas into thebaseline keeps the earliest-seen block) and is cleared by
clear(), mirroring theprevious
observedlifecycle.The four production call sites in
cache.rsalready haveblock_numberin scope;they now pass it through.
Tests
policy_set_future_delta_does_not_authorize_earlier_queryreproduces theahead-of-engine scenario and asserts the earlier block is reported unknown.
PolicySettests updated to pass the query block; the "observed survivesadvance" guarantee is retained (renamed to
first_seen).cargo test -p zone-l1 --lib policy_set(rustc 1.95.0).