Skip to content

[Fix] Address feedback from program upgradability audits.#2758

Closed
d0cd wants to merge 21 commits intofeat/reject-old-execs-after-upgradefrom
fix/upgradability-audits
Closed

[Fix] Address feedback from program upgradability audits.#2758
d0cd wants to merge 21 commits intofeat/reject-old-execs-after-upgradefrom
fix/upgradability-audits

Conversation

@d0cd
Copy link
Collaborator

@d0cd d0cd commented Jun 9, 2025

This PR addresses feedback from auditors that reviewed program upgradability.
Auditors used the following commit: 962d5fd as the reference.

@d0cd d0cd requested a review from rot256 June 13, 2025 22:44
vicsn and others added 3 commits June 13, 2025 15:54
Cleanup

Fix hash computation

Fix tests

Fix tests

Fix test: correctly order public inputs

Fix test and cleanup

Use program checksum instead of call graph checksum

Cleanup and fix
@d0cd d0cd force-pushed the fix/upgradability-audits branch from d13d847 to 8c11cd8 Compare June 13, 2025 22:55
// Initialize a list for the deployed stacks.
let mut stacks = Vec::new();
// A helper function abort an atomic batch with an error message while rolling back changes to the stacks in `Process`.
let abort_with_error = |error: String| -> Result<Vec<FinalizeOperation<N>>, String> {
Copy link
Contributor

Choose a reason for hiding this comment

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

This requires us to call this function on every error, which is error-prone. I think an alternative way is to use "defer" function to automatically call the revert_stacks function when exiting the current scope. Like this:

    defer! {
        // This code runs when the scope ends
        process.revert_stacks();
    }

If the scope succeeds, we call process.commit_stacks() like the current way and the above revert_stacks will do nothing.

Copy link
Collaborator Author

@d0cd d0cd Jun 18, 2025

Choose a reason for hiding this comment

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

This is pretty neat! I've never used this macro.
Is there a crate you recommend? defer, scopeguard or defer_lite?
I'll check with the rest of the team on whether they're open to this.

Copy link
Contributor

Choose a reason for hiding this comment

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

The macro is basically defining a Struct in the current scope and execute the specified function when it is dropped (called RAII). You can implement a simple local version like this:

// Define a struct that holds a closure to be executed on drop
pub struct Defer<F: FnOnce()> {
    f: Option<F>,
}

impl<F: FnOnce()> Defer<F> {
    pub fn new(f: F) -> Self {
        Defer { f: Some(f) }
    }
}

impl<F: FnOnce()> Drop for Defer<F> {
    fn drop(&mut self) {
        if let Some(f) = self.f.take() {
            f();
        }
    }
}

// Macro to create a defer block with a unique guard variable
#[macro_export]
macro_rules! defer {
    ($($body:tt)*) => {
        let _defer_guard = $crate::Defer::new(|| { $($body)* });
    };
}

And use it:

fn main() {
    defer! {
        println!("execute defer");
    }

    println!("in main");
}
// Output:
// in main
// execute defer

Copy link
Collaborator Author

@d0cd d0cd Jun 19, 2025

Choose a reason for hiding this comment

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

First of all big fan of this, I'll be borrowing this in a lot of my other code :)
Do you see a way defer can be done conditionally? Basically, if there's an error then revert_stacks otherwise commit_stacks?

Copy link
Contributor

Choose a reason for hiding this comment

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

For this case, if you already executed commit_stacks, then the revert_stacks (or called revert_uncommitted_stack) will do nothing right?

@d0cd d0cd mentioned this pull request Jun 17, 2025
24 tasks
/// The mapping of program IDs to stacks.
stacks: Arc<RwLock<IndexMap<ProgramID<N>, Arc<Stack<N>>>>>,
/// The mapping of program IDs to old stacks.
old_stacks: Arc<RwLock<IndexMap<ProgramID<N>, Option<Arc<Stack<N>>>>>>,
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Open question on whether it's cleaner to stage stacks in Process or manage them out of band in atomic_{finalize, speculate}

@d0cd d0cd requested review from randomsleep, raychu86 and vicsn and removed request for randomsleep and rot256 June 26, 2025 17:19
// We first acquire a read lock on the process to ensure that the programs are not updated while we are verifying the transaction.
// This lock is held for the duration of the transaction verification.
// Note: The calls to `check_deployment_internal` and `check_execution_internal` will also acquire a read lock on the process.
let process = self.process.read();
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Note to reviewers. Removing this read lock held over the duration of verification improved snarkOS node performance.

Furthermore, this is safe since editions are always sequentially incremented. If an edition is updated, the cache key will simply be invalid and the full verification will run again.

@d0cd
Copy link
Collaborator Author

d0cd commented Jul 21, 2025

Closed in favor of #2807

@d0cd d0cd closed this Jul 21, 2025
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.

4 participants