Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fix] Selection of transition ID in finalize. #2596

Merged
merged 17 commits into from
Jan 17, 2025
Merged
2 changes: 1 addition & 1 deletion .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -702,7 +702,7 @@ jobs:
resource_class: << pipeline.parameters.twoxlarge >>
steps:
- run_serial:
flags: --test '*' -- --test-threads=8
flags: --test '*' --features test -- --test-threads=8
workspace_member: synthesizer
cache_key: v1.0.0-rust-1.81.0-snarkvm-synthesizer-integration-cache

Expand Down
2 changes: 1 addition & 1 deletion synthesizer/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ serial = [
"synthesizer-snark/serial"
]
setup = [ ]
test = [ "console/test" ]
test = [ "console/test", "ledger-block/test", "ledger-store/test" ]
timer = [ "aleo-std/timer" ]
wasm = [
"process",
Expand Down
56 changes: 40 additions & 16 deletions synthesizer/process/src/finalize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,11 @@ impl<N: Network> Process<N> {
lap!(timer, "Verify the number of transitions");

// Construct the call graph.
let call_graph = self.construct_call_graph(execution)?;
// If the height is greater than or equal to `CONSENSUS_V3_HEIGHT`, then provide an empty call graph, as it is no longer used during finalization.
let call_graph = match state.block_height() < N::CONSENSUS_V3_HEIGHT {
true => self.construct_call_graph(execution)?,
false => HashMap::new(),
};

atomic_batch_scope!(store, {
// Finalize the root transition.
Expand Down Expand Up @@ -160,9 +164,11 @@ fn finalize_fee_transition<N: Network, P: FinalizeStorage<N>>(
fee: &Fee<N>,
) -> Result<Vec<FinalizeOperation<N>>> {
// Construct the call graph.
let mut call_graph = HashMap::new();
// Insert the fee transition.
call_graph.insert(*fee.transition_id(), Vec::new());
// If the height is greater than or equal to `CONSENSUS_V3_HEIGHT`, then provide an empty call graph, as it is no longer used during finalization.
let call_graph = match state.block_height() < N::CONSENSUS_V3_HEIGHT {
true => HashMap::from([(*fee.transition_id(), Vec::new())]),
false => HashMap::new(),
};

// Finalize the transition.
match finalize_transition(state, store, stack, fee, call_graph) {
Expand Down Expand Up @@ -208,8 +214,12 @@ fn finalize_transition<N: Network, P: FinalizeStorage<N>>(
// Initialize a stack of active finalize states.
let mut states = Vec::new();

// Initialize a nonce for the finalize registers.
// Note that this nonce must be unique for each sub-transition being finalized.
let mut nonce = 0;

// Initialize the top-level finalize state.
states.push(initialize_finalize_state(state, future, stack, *transition.id())?);
states.push(initialize_finalize_state(state, future, stack, *transition.id(), nonce)?);

// While there are active finalize states, finalize them.
'outer: while let Some(FinalizeState {
Expand Down Expand Up @@ -263,20 +273,31 @@ fn finalize_transition<N: Network, P: FinalizeStorage<N>>(
await_.register()
);

// Get the current transition ID.
let transition_id = registers.transition_id();
// Get the child transition ID.
let child_transition_id = match call_graph.get(transition_id) {
Some(transitions) => match transitions.get(call_counter) {
Some(transition_id) => *transition_id,
None => bail!("Child transition ID not found."),
},
None => bail!("Transition ID '{transition_id}' not found in call graph"),
// Get the transition ID used to initialize the finalize registers.
// If the block height is greater than or equal to `CONSENSUS_V3_HEIGHT`, then use the top-level transition ID.
// Otherwise, query the call graph for the child transition ID corresponding to the future that is being awaited.
let transition_id = match state.block_height() < N::CONSENSUS_V3_HEIGHT {
true => {
// Get the current transition ID.
let transition_id = registers.transition_id();
// Get the child transition ID.
match call_graph.get(transition_id) {
Some(transitions) => match transitions.get(call_counter) {
Some(transition_id) => *transition_id,
None => bail!("Child transition ID not found."),
},
None => bail!("Transition ID '{transition_id}' not found in call graph"),
}
}
false => *transition.id(),
};

// Increment the nonce.
nonce += 1;

// Set up the finalize state for the await.
let callee_state =
match try_vm_runtime!(|| setup_await(state, await_, stack, &registers, child_transition_id)) {
match try_vm_runtime!(|| setup_await(state, await_, stack, &registers, transition_id, nonce)) {
Ok(Ok(callee_state)) => callee_state,
// If the evaluation fails, bail and return the error.
Ok(Err(error)) => bail!("'finalize' failed to evaluate command ({command}): {error}"),
Expand Down Expand Up @@ -357,6 +378,7 @@ fn initialize_finalize_state<'a, N: Network>(
future: &Future<N>,
stack: &'a Stack<N>,
transition_id: N::TransitionID,
nonce: u64,
) -> Result<FinalizeState<'a, N>> {
// Get the finalize logic and the stack.
let (finalize, stack) = match stack.program_id() == future.program_id() {
Expand All @@ -381,6 +403,7 @@ fn initialize_finalize_state<'a, N: Network>(
transition_id,
*future.function_name(),
stack.get_finalize_types(future.function_name())?.clone(),
nonce,
);

// Store the inputs.
Expand All @@ -402,14 +425,15 @@ fn setup_await<'a, N: Network>(
stack: &'a Stack<N>,
registers: &FinalizeRegisters<N>,
transition_id: N::TransitionID,
nonce: u64,
) -> Result<FinalizeState<'a, N>> {
// Retrieve the input as a future.
let future = match registers.load(stack, &Operand::Register(await_.register().clone()))? {
Value::Future(future) => future,
_ => bail!("The input to 'await' is not a future"),
};
// Initialize the state.
initialize_finalize_state(state, &future, stack, transition_id)
initialize_finalize_state(state, &future, stack, transition_id, nonce)
}

// A helper function that returns the index to branch to.
Expand Down
19 changes: 18 additions & 1 deletion synthesizer/process/src/stack/finalize_registers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ pub struct FinalizeRegisters<N: Network> {
finalize_types: FinalizeTypes<N>,
/// The mapping of assigned registers to their values.
registers: IndexMap<u64, Value<N>>,
/// A nonce for finalize registers.
nonce: u64,
/// The tracker for the last register locator.
last_register: Option<u64>,
}
Expand All @@ -58,8 +60,17 @@ impl<N: Network> FinalizeRegisters<N> {
transition_id: N::TransitionID,
function_name: Identifier<N>,
finalize_types: FinalizeTypes<N>,
nonce: u64,
) -> Self {
Self { state, transition_id, finalize_types, function_name, registers: IndexMap::new(), last_register: None }
Self {
state,
transition_id,
finalize_types,
function_name,
registers: IndexMap::new(),
nonce,
last_register: None,
}
}
}

Expand All @@ -81,4 +92,10 @@ impl<N: Network> FinalizeRegistersState<N> for FinalizeRegisters<N> {
fn function_name(&self) -> &Identifier<N> {
&self.function_name
}

/// Returns the nonce for the finalize registers.
#[inline]
fn nonce(&self) -> u64 {
self.nonce
}
}
2 changes: 1 addition & 1 deletion synthesizer/process/src/stack/register_types/initialize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ impl<N: Network> RegisterTypes<N> {
}

/* Additional checks. */
// - All futures produces before the `async` call must be consumed by the `async` call.
// - All futures produced before the `async` call must be consumed by the `async` call.

// Get all registers containing futures.
let mut future_registers: IndexSet<(Register<N>, Locator<N>)> = register_types
Expand Down
32 changes: 23 additions & 9 deletions synthesizer/program/src/logic/command/rand_chacha.rs
Original file line number Diff line number Diff line change
Expand Up @@ -89,15 +89,29 @@ impl<N: Network> RandChaCha<N> {
let seeds: Vec<_> = self.operands.iter().map(|operand| registers.load(stack, operand)).try_collect()?;

// Construct the random seed.
let preimage = to_bits_le![
registers.state().random_seed(),
**registers.transition_id(),
stack.program_id(),
registers.function_name(),
self.destination.locator(),
self.destination_type.type_id(),
seeds
];
// If the height is greater than or equal to `CONSENSUS_V3_HEIGHT`, then use the new preimage definition.
// The difference is that a nonce is also included in the new definition.
let preimage = match registers.state().block_height() < N::CONSENSUS_V3_HEIGHT {
true => to_bits_le![
registers.state().random_seed(),
**registers.transition_id(),
stack.program_id(),
registers.function_name(),
self.destination.locator(),
self.destination_type.type_id(),
seeds
],
false => to_bits_le![
registers.state().random_seed(),
**registers.transition_id(),
stack.program_id(),
registers.function_name(),
registers.nonce(),
self.destination.locator(),
self.destination_type.type_id(),
seeds
],
};

// Hash the preimage.
let digest = N::hash_bhp1024(&preimage)?.to_bytes_le()?;
Expand Down
3 changes: 3 additions & 0 deletions synthesizer/program/src/traits/stack_and_registers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,9 @@ pub trait FinalizeRegistersState<N: Network> {

/// Returns the function name for the finalize scope.
fn function_name(&self) -> &Identifier<N>;

/// Returns the nonce for the finalize registers.
fn nonce(&self) -> u64;
}

pub trait RegistersSigner<N: Network> {
Expand Down
1 change: 1 addition & 0 deletions synthesizer/program/tests/helpers/sample.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ pub fn sample_finalize_registers(
<CurrentNetwork as Network>::TransitionID::default(),
*function_name,
stack.get_finalize_types(function_name)?.clone(),
0u64,
);

// For each literal,
Expand Down
Loading