Skip to content

fix(node): confirm submit_participant_info via advancing attestation expiry - #3736

Merged
barakeinav1 merged 11 commits into
mainfrom
fix/3686-attestation-submit-confirmation
Jul 10, 2026
Merged

fix(node): confirm submit_participant_info via advancing attestation expiry#3736
barakeinav1 merged 11 commits into
mainfrom
fix/3686-attestation-submit-confirmation

Conversation

@barakeinav1

@barakeinav1 barakeinav1 commented Jul 5, 2026

Copy link
Copy Markdown
Contributor

Closes #3686

The node confirmed submit_participant_info by reconstructing the attestation's creation time as stored_expiry − DEFAULT_EXPIRATION_DURATION_SECONDS. That only works when node and contract share the constant; under version skew (contract 3.12 = 7d, node 3.13 = 1d) the reconstructed age is ~6 days, always exceeding the 120s freshness bound, so every submission is treated as NotExecuted and retried indefinitely.

Instead, confirm the submission by checking that the stored attestation expiry advanced past the value observed before submitting.

  • Read the stored expiry before submitting; confirm it increased afterward
  • Drop the expiry − constant age reconstruction, MAX_ATTESTATION_AGE, and the shared-constant import
  • Extract the pure decision into attestation_expiry_advanced + unit tests

Expiry only ever advances via the node's own submit for its key, so this never yields a false positive. It also holds under the verifier-rotation expiry cap in #3734 (where stored expiry ≠ creation + constant), which lists #3686 as a prerequisite.

Design discussion

…expiry

The post-submit confirmation reconstructed the attestation's creation time as
`stored_expiry - DEFAULT_EXPIRATION_DURATION_SECONDS`, which breaks when the node
and contract compile different values for that constant (version skew): the
reconstructed age far exceeds the freshness bound, so every submission is treated
as NotExecuted and retried indefinitely.

Confirm instead that the stored expiry advanced past the value observed before
submitting: a successful submit re-stamps expiry forward, a failed one leaves it
unchanged. This drops the shared-constant dependency and holds under the planned
verifier-rotation expiry cap (#3734), where stored expiry is no longer
`creation + constant`.

- Capture the pre-submit expiry baseline (read_pre_submit_attestation_expiry)
- Decide via submitted_attestation_landed (Dstack: expiry advanced; Mock: equality)
- Log the check result with both expiry values, and the not-stored path
- Point the certificate-timestamp follow-up at the correct issue (#1639)
- Unit-test the Dstack and Mock decision paths
@barakeinav1
barakeinav1 force-pushed the fix/3686-attestation-submit-confirmation branch from 62c307f to 98d9bc5 Compare July 5, 2026 13:31
@barakeinav1
barakeinav1 marked this pull request as ready for review July 5, 2026 13:32
Copilot AI review requested due to automatic review settings July 5, 2026 13:32
@claude

This comment was marked as outdated.

This comment was marked as outdated.

The prior wording implied None means nothing was stored, but None also
arises from a stored Mock; state the pure invariant instead.
@barakeinav1

This comment was marked as outdated.

@kevindeforth kevindeforth left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, requesting changes for now:

  • querying the tee expiration status for all transactions is unnecessary overhead. I also think it's the wrong place, that information should be retrieved once, possibly before we call submit_remote_attestation.
  • code comments are not very useful, please take a look at our engineering standards.

Comment thread crates/node/src/indexer/tx_sender.rs Outdated
Comment thread crates/node/src/indexer/tx_sender.rs Outdated
Comment thread crates/node/src/indexer/tx_sender.rs Outdated
@pbeza
pbeza self-requested a review July 6, 2026 11:24

@pbeza pbeza left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There’s a corner case we don’t handle properly, and I don’t think we document it anywhere: if we shrink the contract’s attestation expiry window (DEFAULT_EXPIRATION_DURATION_SECONDS), this check breaks:

Some(expiry_before_submit) => stored_expiry > expiry_before_submit,

Right now, to confirm that a submit landed, we check that the stored expiry is later than it was before the submit. That works because every successful submit sets expiry = block_now + DEFAULT_EXPIRATION_DURATION_SECONDS, so a fresh submit should normally push the expiry forward, assuming the duration stays the same.

But if a contract upgrade makes DEFAULT_EXPIRATION_DURATION_SECONDS smaller, for example 7 days → 1 day, the next submit can set an expiry that is earlier than the old one already stored on-chain. So the submit actually succeeds, but the expiry goes down instead of up, and our check thinks it failed and keeps retrying forever.

So basically the same retry storm as #3686, but triggered by shrinking the expiry window instead of a node/contract version mismatch.

Comment thread crates/node/src/indexer/tx_sender.rs Outdated
Comment thread crates/node/src/indexer/tx_sender.rs Outdated
Comment thread crates/node/src/indexer/tx_sender.rs
@barakeinav1

barakeinav1 commented Jul 7, 2026

Copy link
Copy Markdown
Contributor Author

But if a contract upgrade makes DEFAULT_EXPIRATION_DURATION_SECONDS smaller, for example 7 days → 1 day, the next submit can set an expiry that is earlier than the old one already stored on-chain. So the submit actually succeeds, but the expiry goes down instead of up, and our check thinks it failed and keeps retrying forever.

In this case, the first submission would succeed and shorten the expiry, but the node will think it failed and re-try.
If we re-read the submission (the new one that landed) and re-try, then we will succeed and the node will also also think he succeeded.

so we will get only one false negative. which is fine by me.
but this does mean we need to re-read the submission before each re-try,

another option, is to still read once, but then ask the operators to just re-start the node.
but I don't like this, since, we have a future PR (3734 ) that will cause the above scenario to have once each verifier upgrade

Address review feedback on the attestation submit-confirmation:

- Move the confirmation into a dedicated confirm_participant_info_submission
  fn; observe_tx_result no longer takes a pre-submit baseline, keeping it off
  the generic signature.
- Read the pre-submit baseline only for submit_participant_info.
- Confirm via expiry *changed* (!=) rather than *advanced* (>), so a landed
  submit is detected even if a lowered expiry constant sets an earlier expiry;
  correctness no longer depends on re-reading per attempt.
- Rewrite comments to explain why (invariant, Mock-vs-Dstack asymmetry).
- TODO(#3746): note the retry loop reuses a stale attestation and stops on
  timeout; to be split into inner+outer loops.
@barakeinav1
barakeinav1 requested review from kevindeforth and pbeza July 7, 2026 06:37
Comment thread crates/node/src/indexer/tx_sender.rs Outdated
Keep ensure_send_transaction / observe_tx_result generic (no per-transaction
logic), per review. The pre-submit attestation expiry is now read once per
round by the caller and carried on the request variant:

- SubmitParticipantInfo becomes a struct variant { args, pre_submit_expiry };
  #[serde(flatten)] + #[serde(skip)] keep the on-chain args byte-identical.
- Add a ReadAttestationExpiry trait (real impl over the view client, fake stub);
  periodic_attestation_submission / monitor_attestation_removal read the
  baseline once and pass it down. Read error skips the round; no stored
  attestation (e.g. first submit) is a normal Ok(None) that proceeds.
- observe_tx_result reads the baseline from the request; ensure_send_transaction
  has no submit-specific code. Confirmation still uses expiry-changed (!=).
Tighten the doc comments the review flagged as verbose/distracting.
Per review: attestation_expiry_changed is a plain comparison, so drop the
system-behavior doc from it and state the assumption once at the decision site
(submitted_attestation_landed), linking DEFAULT_EXPIRATION_DURATION_SECONDS so
the cross-crate reference is checked. Trim remaining where-used / paraphrasing
comments.
@barakeinav1
barakeinav1 requested a review from kevindeforth July 8, 2026 14:47
kevindeforth
kevindeforth previously approved these changes Jul 9, 2026

@kevindeforth kevindeforth left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you! Just a nit and a comment regarding control flow

Comment thread crates/node/src/tee/remote_attestation.rs Outdated
Comment thread crates/node/src/tee/remote_attestation.rs
Comment thread crates/node/src/tee/remote_attestation.rs Outdated
… task args

Address review on the attestation-submission tasks:
- On a pre-submit baseline read error, submit anyway (baseline = None) instead
  of skipping the round: a broken read must not block attestation submission,
  which would let the entry lapse and evict the node. Confirmation just runs
  without a baseline that round.
- Bundle the shared task inputs into an AttestationSubmitter struct, dropping
  the too_many_arguments on periodic_attestation_submission /
  monitor_attestation_removal.
Assert the request serializes to the bare on-chain args with pre_submit_expiry
omitted, protecting the #[serde(flatten)]/#[serde(skip)] invariant.
Covers the read-error path: a failing pre-submit baseline read must not block
attestation submission.
@barakeinav1
barakeinav1 requested a review from kevindeforth July 9, 2026 05:49

@kevindeforth kevindeforth left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, this is very nice!

@kevindeforth

Copy link
Copy Markdown
Contributor

I took the liberty to resolve the merge conflict. Would be nice to get this merged today.

@kevindeforth kevindeforth left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you!

@barakeinav1
barakeinav1 added this pull request to the merge queue Jul 10, 2026
Merged via the queue into main with commit 1abc356 Jul 10, 2026
15 checks passed
@barakeinav1
barakeinav1 deleted the fix/3686-attestation-submit-confirmation branch July 10, 2026 09:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Attestation freshness check breaks under node/contract version skew, causing a submit_participant_info retry storm

4 participants