Skip to content

fix(sns): store generic nervous system function call reply on the proposal - #11453

Open
Rachit2323 wants to merge 4 commits into
dfinity:masterfrom
Rachit2323:fix/sns-store-generic-function-reply
Open

Rachit2323 wants to merge 4 commits into
dfinity:masterfrom
Rachit2323:fix/sns-store-generic-function-reply

Conversation

@Rachit2323

Copy link
Copy Markdown
Contributor

What was wrong: when the SNS asks another canister to do something, that canister sends back an answer. But we were never actually reading that answer, we just checked "did it respond at all" and then threw the answer away. So even if the answer said "this didn't work," we'd still mark it as a success, and nobody could ever go back and see what really happened, because we deleted it.

What we fixed: we stopped deleting the answer. Now we save it, so anyone can look at it later and see exactly what came back.

What we did NOT fix: we still can't automatically tell if the answer means success or failure, because this feature can talk to any canister, and every one of them answers differently. There's no way to understand all of them automatically. So we're not trying to guess anymore, we're just making sure the answer doesn't disappear.

Small limit: if the answer is really long, we only keep part of it, so it doesn't take up too much space.

Testing: added tests to check the answer gets saved properly, that nothing changed for the failure case, and that long answers get shortened correctly.

@github-actions github-actions Bot added the fix label Sep 4, 2026
@basvandijk basvandijk added the security-review-passed IDX or InfraSec have concluded it's safe to run CI on the external PR. label Sep 4, 2026
@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown
Contributor

@Rachit2323
Rachit2323 marked this pull request as ready for review September 5, 2026 12:41
@Rachit2323
Rachit2323 requested a review from a team as a code owner September 5, 2026 12:41

@github-actions github-actions Bot 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.

This pull request changes code owned by the Governance team. Therefore, make sure that
you have considered the following (for Governance-owned code):

  1. Update unreleased_changelog.md (if there are behavior changes, even if they are
    non-breaking).

  2. Are there BREAKING changes?

  3. Is a data migration needed?

  4. Security review?

How to Satisfy This Automatic Review

  1. Go to the bottom of the pull request page.

  2. Look for where it says this bot is requesting changes.

  3. Click the three dots to the right.

  4. Select "Dismiss review".

  5. In the text entry box, respond to each of the numbered items in the previous
    section, declare one of the following:

  • Done.

  • $REASON_WHY_NO_NEED. E.g. for unreleased_changelog.md, "No
    canister behavior changes.", or for item 2, "Existing APIs
    behave as before.".

Brief Guide to "Externally Visible" Changes

"Externally visible behavior change" is very often due to some NEW canister API.

Changes to EXISTING APIs are more likely to be "breaking".

If these changes are breaking, make sure that clients know how to migrate, how to
maintain their continuity of operations.

If your changes are behind a feature flag, then, do NOT add entrie(s) to
unreleased_changelog.md in this PR! But rather, add entrie(s) later, in the PR
that enables these changes in production.

Reference(s)

For a more comprehensive checklist, see here.

GOVERNANCE_CHECKLIST_REMINDER_DEDUP

@zeropath-ai

zeropath-ai Bot commented Sep 5, 2026 •

Copy link
Copy Markdown

✅ No security or compliance issues detected. Reviewed everything up to dffffe8.

Security Overview
Detected Code Changes
Change Type Relevant files
Enhancement ► rs/sns/governance/api/src/ic_sns_governance.pb.v1.rs
• Add execution_reply field to ProposalData with serde deserialization for blob
Enhancement ► rs/sns/governance/canister/governance.did
• Extend ProposalData with execution_reply: opt blob
Enhancement ► rs/sns/governance/proto/ic_sns_governance/pb/v1/governance.proto
• Add optional execution_reply field to ProposalData
Enhancement ► rs/sns/governance/src/canister_control.rs
• Update perform_execute_generic_nervous_system_function_call to return Vec on success and adjust error handling
Enhancement ► rs/sns/governance/src/canister_control_tests.rs
• Add tests for Execute Generic Nervous System Function flow and reply handling
Enhancement ► rs/sns/governance/src/governance.rs
• Import MAX_SCALAR_FIELD_LEN_BYTES
• Add set_proposal_execution_reply to store truncated reply on proposal
• Adapt flow to handle execution replies when executing generic functions
Enhancement ► rs/sns/governance/src/governance/execute_generic_nervous_system_function_tests.rs
• Add tests for storing reply, failure behavior, and truncation of oversized replies
Enhancement ► rs/sns/governance/src/governance/test_helpers.rs
• Add execute_proposal helper to run and await proposal execution in tests
Enhancement ► rs/sns/governance/src/pb/conversions.rs
• Propagate execution_reply in conversions between pb types
Enhancement ► rs/sns/governance/src/proposal.rs
• Update ProposalData limited_for_list_proposals to preserve/exclude execution_reply as appropriate
Enhancement ► rs/sns/governance/unreleased_changelog.md
• Document storing raw reply bytes on proposals as execution_reply

@claude

claude Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Re: the governance checklist —

  1. Changelog: unreleased_changelog.md is already updated in this PR (entry under ## Fixed describing that generic-function execution replies are now stored on the proposal instead of discarded).
  2. Breaking changes: None — this only adds a new optional field (execution_reply) to ProposalData; existing callers and behavior are unaffected.
  3. Data migration: Not needed — the new field is optional and simply absent on existing/older proposals.
  4. Security review: An automated scan (zeropath-ai) found no issues on the current head, and the PR already carries the security-review-passed label.

@dfinity/governance-team — flagging for a look since this checklist review is what's currently blocking merge (mergeable_state: blocked); CI is otherwise green and no other reviews are outstanding.


Generated by Claude Code

@github-actions

github-actions Bot commented Sep 6, 2026

Copy link
Copy Markdown
Contributor

@daniel-wong-dfinity-org-twin

Copy link
Copy Markdown
Contributor

Hi, Rachit2323. Sorry it has taken me a while to look at this. I just came back from a 2 week vacation.

@claude

claude Bot commented Sep 23, 2026

Copy link
Copy Markdown
Contributor

CI Main is failing on this head commit — two issues:

1. Cargo Lint Linux (also cascades to cancel Cargo Build Linux): two clippy unseparated_literal_suffix violations in the new test file rs/sns/governance/src/governance/execute_generic_nervous_system_function_tests.rs:

  • line 64: 1u64 → 1_u64
  • line 132: 7u8 → 7_u8

@Rachit2323, could you push those two one-character fixes? That should get Cargo Lint Linux and Cargo Build Linux green.

2. Bazel Test All: //rs/sns/integration_tests:upgrade_canister_test (test_upgrade_after_state_shrink) fails because this PR's size increase pushes the compiled sns-governance-canister wasm about 2KB over the hard-coded MAX_INSTALL_CODE_WASM_AND_ARG_SIZE (2,000,000 bytes) self-upgrade limit in rs/sns/governance/src/types.rs. This isn't a logic bug in the PR — it's an existing binary-size budget that's apparently already nearly exhausted. @dfinity/governance-team, this needs a call from you: whether there's room to raise the budget, or whether this PR needs to shave bytes elsewhere before it can land.


Generated by Claude Code

Comment on lines +1084 to +1085
/// completed successfully at the IC call level. SNS does not know this
/// reply's Candid schema, so it is stored as-is (opaque), truncated to at

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.

I don't think it needs to be explained why the type here is blob. I mean, mentioning it is not harmful per se, so if you really like it, keep it.

The more interesting fact is that it gets truncated.

The reason for trunctation also does not seem like it needs to be mentioned, but it's not harmful either. I mean, I think the reason can be "very easily" inferred. Everyone knows that space is a finite resource.

Ditto elsewhere, ofc.

target_canister_id: Some(TARGET_CANISTER_ID.get()),
target_method_name: Some(TARGET_METHOD.to_string()),
validator_canister_id: Some(TARGET_CANISTER_ID.get()),
validator_method_name: Some(TARGET_METHOD.to_string()),

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.

For realism, this should be different from target_method_name.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

changed it to format!("validate_{}", target_method) so the validator method is different

Comment thread rs/sns/governance/src/governance/execute_generic_nervous_system_function_tests.rs Outdated
Comment thread rs/sns/governance/src/governance/execute_generic_nervous_system_function_tests.rs Outdated
Comment thread rs/sns/governance/src/governance/execute_generic_nervous_system_function_tests.rs Outdated
Comment thread rs/sns/governance/src/governance.rs Outdated
self.perform_execute_generic_nervous_system_function(call)
.await
}
Action::ExecuteGenericNervousSystemFunction(call) => self

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.

What happened to braces? Other arms have them. Did rustfmt force you to get rid of them? If not, please, make this code like the rest.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added the braces back to match the other arms — rustfmt hadn't removed them.

Comment thread rs/sns/governance/src/governance.rs Outdated
topic: Some(i32::from(proposal_topic)),

// A new proposal has not been executed yet, so there is no reply.
execution_reply: ProposalData::default().execution_reply,

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.

This seems like an extravagant way to obtain a None.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

just changed it to None.

Comment thread rs/sns/governance/src/canister_control_tests.rs Outdated
Comment on lines +6 to +8
id: u64,
target_canister_id: CanisterId,
target_method: &str,

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.

I don't think any callers care to pick these, so just use fixed values + lazy_static.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

dropped the params and used fixed values with lazy_static.

Comment thread rs/sns/governance/src/canister_control_tests.rs Outdated

Copilot AI 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.

Copilot review overview

🟡 Changes recommended

The reversed timeout predicate can make asynchronous proposal execution tests fail immediately.

Get a fresh assessment by requesting another Copilot review.

Review effort: Balanced
Findings: 1 High severity

Open (1)
What changed in this PR

Stores bounded raw replies from successful generic SNS function calls on proposals for later inspection.

Changes:

  • Captures, truncates, and persists target-canister replies.
  • Exposes replies through protobuf and Candid APIs.
  • Adds reply propagation, failure, and truncation tests.
File Description
rs/​sns/​governance/​unreleased_changelog.md Documents reply persistence.
rs/​sns/​governance/​src/​proposal.rs Handles reply initialization and limited proposal views.
rs/​sns/​governance/​src/​pb/​conversions.rs Converts the new API field.
rs/​sns/​governance/​src/​governance/​test_helpers.rs Adds a shared execution helper, but its timeout predicate is reversed.
rs/​sns/​governance/​src/​governance/​execute_generic_nervous_system_function_tests.rs Tests reply persistence and truncation.
rs/​sns/​governance/​src/​governance/​assorted_governance_tests.rs Uses the shared execution helper.
rs/​sns/​governance/​src/​governance.rs Stores bounded replies during execution.
rs/​sns/​governance/​src/​gen/​ic_sns_governance.pb.v1.rs Adds the generated storage field.
rs/​sns/​governance/​src/​canister_control.rs Returns opaque call replies.
rs/​sns/​governance/​src/​canister_control_tests.rs Tests reply and error propagation.
rs/​sns/​governance/​proto/​ic_sns_governance/​pb/​v1/​governance.proto Defines the persisted reply field.
rs/​sns/​governance/​canister/​governance.did Exposes replies through Candid.
rs/​sns/​governance/​api/​src/​ic_sns_governance.pb.v1.rs Adds the public API field.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

// less than 1 s (on my Macbook Pro 2019 Intel). The reason for this
// generous limit is twofold: 1. avoid flakes in CI, while at the same
// time 2. do not run forever if something goes wrong.
let give_up = || now() < start + std::time::Duration::from_secs(30);

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.

Confirmed — this is a real bug. give_up = || now() < start + Duration::from_secs(30) returns true for the whole first 30 seconds (so a proposal that isn't final on the very first poll panics immediately) and false forever after 30s elapses (so a genuinely stuck proposal never actually triggers the "took too long" panic). The comparison is inverted.

Fix: let give_up = || now() > start + std::time::Duration::from_secs(30);

@Rachit2323, could you fix this in your next push along with the clippy fix above?


Generated by Claude Code

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

flipped it to now() >= start + 30s .

@daniel-wong-dfinity-org-twin

Copy link
Copy Markdown
Contributor

Hmm. The size thing is worrying. Based on the comment, I don't think we can increase the limit. We need to find way(s) to put this WASM on a diet.

@Rachit2323

Rachit2323 commented Oct 3, 2026 •

Copy link
Copy Markdown
Contributor Author

@daniel-wong-dfinity-org-twin For the size issue: I can switch just this canister to opt-level "z" (optimizes for size), which should save way more than the 2 KB we're over. Downside is it runs a little slower. Want me to do that, or would you rather bump the limit?

@github-actions

github-actions Bot commented Oct 3, 2026

Copy link
Copy Markdown
Contributor

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

external-contributor fix @governance-team security-review-passed IDX or InfraSec have concluded it's safe to run CI on the external PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants