[Fix] Address feedback from program upgradability audits.#2758
[Fix] Address feedback from program upgradability audits.#2758d0cd wants to merge 21 commits intofeat/reject-old-execs-after-upgradefrom
Conversation
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
d13d847 to
8c11cd8
Compare
synthesizer/src/vm/finalize.rs
Outdated
| // 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> { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 deferThere was a problem hiding this comment.
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?
There was a problem hiding this comment.
For this case, if you already executed commit_stacks, then the revert_stacks (or called revert_uncommitted_stack) will do nothing right?
| /// 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>>>>>>, |
There was a problem hiding this comment.
Open question on whether it's cleaner to stage stacks in Process or manage them out of band in atomic_{finalize, speculate}
| // 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(); |
There was a problem hiding this comment.
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.
|
Closed in favor of #2807 |
This PR addresses feedback from auditors that reviewed program upgradability.
Auditors used the following commit: 962d5fd as the reference.