Skip to content

Commit

Permalink
chore: prepare ScheduledValueChange for mutable delays. (#6085)
Browse files Browse the repository at this point in the history
Part of #5493. This lays some of the groundwork by making
`ScheduledValueChange` not have an immutable delay. The only user of
this struct is `SharedMutable` however, which does (so far) have
immutable delays, so there's no functionality changes.
  • Loading branch information
nventuro committed Apr 30, 2024
1 parent 0032a3a commit cfa850b
Show file tree
Hide file tree
Showing 3 changed files with 135 additions and 106 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,18 @@ use dep::protocol_types::traits::{Serialize, Deserialize, FromField, ToField};

// This data structure is used by SharedMutable to represent a value that changes from `pre` to `post` at some block
// called the `block_of_change`. The value can only be made to change by scheduling a change event at some future block
// of change after some delay measured in blocks has elapsed. This means that at any given block number we know both the
// current value and the smallest block number at which the value might change - this is called the 'block horizon'.
//
// The delay being a type parameter instead of a struct field is an implementation detail, and is due to a number of
// reasons:
// - we want to serialize and deserialize this object in order to store it in public storage, but we don't want to
// include the delay there because it is immutable
// - because of how aztec-nr state variables are declared, having a type with some immutable property is better
// expressed via types, since they are always constructed with the same `::new(context, storage_slot)` function.
struct ScheduledValueChange<T, DELAY> {
// of change after some minimum delay measured in blocks has elapsed. This means that at any given block number we know
// both the current value and the smallest block number at which the value might change - this is called the
// 'block horizon'.
struct ScheduledValueChange<T> {
pre: T,
post: T,
block_of_change: u32,
// The _dummy variable forces DELAY to be interpreted as a numberic value. This is a workaround to
// https://github.com/noir-lang/noir/issues/4633. Remove once resolved.
_dummy: [Field; DELAY],
}

impl<T, DELAY> ScheduledValueChange<T, DELAY> {
impl<T> ScheduledValueChange<T> {
pub fn new(pre: T, post: T, block_of_change: u32) -> Self {
Self { pre, post, block_of_change, _dummy: [0; DELAY] }
Self { pre, post, block_of_change }
}

/// Returns the value stored in the data structure at a given block. This function can be called both in public
Expand Down Expand Up @@ -54,10 +45,12 @@ impl<T, DELAY> ScheduledValueChange<T, DELAY> {
/// Returns the largest block number at which the value returned by `get_current_at` is known to remain the current
/// value. This value is only meaningful in private when constructing a proof at some `historical_block_number`,
/// since due to its asynchronous nature private execution cannot know about any later scheduled changes.
/// The caller of this function must know how quickly the value can change due to a scheduled change in the form of
/// `minimum_delay`. If the delay itself is immutable, then this is just its duration.
/// The value returned by `get_current_at` in private when called with a historical block number is only safe to use
/// if the transaction's `max_block_number` property is set to a value lower or equal to the block horizon computed
/// using the same historical block number.
pub fn get_block_horizon(self, historical_block_number: u32) -> u32 {
pub fn get_block_horizon(self, historical_block_number: u32, minimum_delay: u32) -> u32 {
// The block horizon is the very last block in which the current value is known. Any block past the horizon
// (i.e. with a block number larger than the block horizon) may have a different current value. Reading the
// current value in private typically requires constraining the maximum valid block number to be equal to the
Expand All @@ -68,77 +61,87 @@ impl<T, DELAY> ScheduledValueChange<T, DELAY> {
// change is scheduled. This did not happen at the historical block number (or else it would not be
// greater or equal to the block of change), and therefore could only happen after the historical block
// number. The earliest would be the immediate next block, and so the smallest possible next block of change
// equals `historical_block_number + 1 + DELAY`. Our block horizon is simply the previous block to that one.
// equals `historical_block_number + 1 + minimum_delay`. Our block horizon is simply the previous block to
// that one.
//
// block of historical
// change block block horizon
// =======|=============N===================H===========>
// ^ ^
// ---------------------
// delay
// minimum delay

historical_block_number + DELAY
historical_block_number + minimum_delay
} else {
// If the block of change has not yet been mined however, then there are two possible scenarios.
// a) It could be so far into the future that the block horizon is actually determined by the delay,
// because a new change could be scheduled and take place _before_ the currently scheduled one. This is
// similar to the scenario where the block of change is in the past: the time horizon is the block
// prior to the earliest one in which a new block of change might land.
// a) It could be so far into the future that the block horizon is actually determined by the minimum
// delay, because a new change could be scheduled and take place _before_ the currently scheduled one.
// This is similar to the scenario where the block of change is in the past: the time horizon is the
// block prior to the earliest one in which a new block of change might land.
//
// historical
// block block horizon block of change
// =====N=================================H=================|=========>
// ^ ^
// | |
// -----------------------------------
// delay
// minimum delay
//
// b) It could be fewer than `delay` blocks away from the historical block number, in which case it would
// become the limiting factor for the time horizon, which would be the block right before the block of
// change (since by definition the value changes at the block of change).
// b) It could be fewer than `minimum_delay` blocks away from the historical block number, in which case
// the block of change would become the limiting factor for the time horizon, which would equal the
// block right before the block of change (since by definition the value changes at the block of
// change).
//
// historical block horizon
// block block of change if not scheduled
// =======N=============|===================H=================>
// ^ ^ ^
// | actual horizon |
// -----------------------------------
// delay
// minimum delay
//
// Note that the current implementation does not allow the caller to set the block of change to an arbitrary
// value, and therefore scenario a) is not currently possible. However implementing #5501 would allow for
// this to happen.

// Because historical_block_number < self.block_of_change, then block_of_change > 0 and we can safely
// subtract 1.
min(self.block_of_change - 1, historical_block_number + DELAY)
min(
self.block_of_change - 1,
historical_block_number + minimum_delay
)
}
}

/// Mutates a scheduled value change by scheduling a change at the current block number. This function is only
/// meaningful when called in public with the current block number.
pub fn schedule_change(&mut self, new_value: T, current_block_number: u32) {
pub fn schedule_change(
&mut self,
new_value: T,
current_block_number: u32,
minimum_delay: u32,
block_of_change: u32
) {
assert(block_of_change >= current_block_number + minimum_delay);

self.pre = self.get_current_at(current_block_number);
self.post = new_value;
// TODO: make this configurable
// https://github.com/AztecProtocol/aztec-packages/issues/5501
self.block_of_change = current_block_number + DELAY;
self.block_of_change = block_of_change;
}
}

impl<T, DELAY> Serialize<3> for ScheduledValueChange<T, DELAY> {
impl<T> Serialize<3> for ScheduledValueChange<T> {
fn serialize(self) -> [Field; 3] where T: ToField {
[self.pre.to_field(), self.post.to_field(), self.block_of_change.to_field()]
}
}

impl<T, DELAY> Deserialize<3> for ScheduledValueChange<T, DELAY> {
impl<T> Deserialize<3> for ScheduledValueChange<T> {
fn deserialize(input: [Field; 3]) -> Self where T: FromField {
Self {
pre: FromField::from_field(input[0]),
post: FromField::from_field(input[1]),
block_of_change: FromField::from_field(input[2]),
_dummy: [0; DELAY]
block_of_change: FromField::from_field(input[2]),
}
}
}
Expand All @@ -157,15 +160,15 @@ fn test_min() {
mod test {
use crate::state_vars::shared_mutable::scheduled_value_change::ScheduledValueChange;

global TEST_DELAY = 200;
global TEST_DELAY: u32 = 200;

#[test]
fn test_get_current_at() {
let pre = 1;
let post = 2;
let block_of_change = 50;

let value: ScheduledValueChange<Field, TEST_DELAY> = ScheduledValueChange::new(pre, post, block_of_change);
let value: ScheduledValueChange<Field> = ScheduledValueChange::new(pre, post, block_of_change);

assert_eq(value.get_current_at(0), pre);
assert_eq(value.get_current_at(block_of_change - 1), pre);
Expand All @@ -179,13 +182,13 @@ mod test {
let post = 2;
let block_of_change = 50;

let value: ScheduledValueChange<Field, TEST_DELAY> = ScheduledValueChange::new(pre, post, block_of_change);
let value: ScheduledValueChange<Field> = ScheduledValueChange::new(pre, post, block_of_change);

assert_eq(value.get_scheduled(), (post, block_of_change));
}

fn assert_block_horizon_invariants(
value: &mut ScheduledValueChange<Field, TEST_DELAY>,
value: &mut ScheduledValueChange<Field>,
historical_block_number: u32,
block_horizon: u32
) {
Expand All @@ -198,7 +201,12 @@ mod test {
// changing at the previously determined block_horizon.

let new = value.pre + value.post; // Make sure it's different to both pre and post
value.schedule_change(new, historical_block_number + 1);
value.schedule_change(
new,
historical_block_number + 1,
TEST_DELAY,
historical_block_number + 1 + TEST_DELAY
);

assert(value.block_of_change > block_horizon);
assert_eq(current_at_historical, value.get_current_at(block_horizon));
Expand All @@ -209,9 +217,9 @@ mod test {
let historical_block_number = 100;
let block_of_change = 50;

let mut value: ScheduledValueChange<Field, TEST_DELAY> = ScheduledValueChange::new(1, 2, block_of_change);
let mut value: ScheduledValueChange<Field> = ScheduledValueChange::new(1, 2, block_of_change);

let block_horizon = value.get_block_horizon(historical_block_number);
let block_horizon = value.get_block_horizon(historical_block_number, TEST_DELAY);
assert_eq(block_horizon, historical_block_number + TEST_DELAY);

assert_block_horizon_invariants(&mut value, historical_block_number, block_horizon);
Expand All @@ -222,9 +230,9 @@ mod test {
let historical_block_number = 100;
let block_of_change = 100;

let mut value: ScheduledValueChange<Field, TEST_DELAY> = ScheduledValueChange::new(1, 2, block_of_change);
let mut value: ScheduledValueChange<Field> = ScheduledValueChange::new(1, 2, block_of_change);

let block_horizon = value.get_block_horizon(historical_block_number);
let block_horizon = value.get_block_horizon(historical_block_number, TEST_DELAY);
assert_eq(block_horizon, historical_block_number + TEST_DELAY);

assert_block_horizon_invariants(&mut value, historical_block_number, block_horizon);
Expand All @@ -235,12 +243,12 @@ mod test {
let historical_block_number = 100;
let block_of_change = 120;

let mut value: ScheduledValueChange<Field, TEST_DELAY> = ScheduledValueChange::new(1, 2, block_of_change);
let mut value: ScheduledValueChange<Field> = ScheduledValueChange::new(1, 2, block_of_change);

// Note that this is the only scenario in which the block of change informs the block horizon.
// This may result in privacy leaks when interacting with applications that have a scheduled change
// in the near future.
let block_horizon = value.get_block_horizon(historical_block_number);
let block_horizon = value.get_block_horizon(historical_block_number, TEST_DELAY);
assert_eq(block_horizon, block_of_change - 1);

assert_block_horizon_invariants(&mut value, historical_block_number, block_horizon);
Expand All @@ -251,9 +259,9 @@ mod test {
let historical_block_number = 100;
let block_of_change = 500;

let mut value: ScheduledValueChange<Field, TEST_DELAY> = ScheduledValueChange::new(1, 2, block_of_change);
let mut value: ScheduledValueChange<Field> = ScheduledValueChange::new(1, 2, block_of_change);

let block_horizon = value.get_block_horizon(historical_block_number);
let block_horizon = value.get_block_horizon(historical_block_number, TEST_DELAY);
assert_eq(block_horizon, historical_block_number + TEST_DELAY);

assert_block_horizon_invariants(&mut value, historical_block_number, block_horizon);
Expand All @@ -265,11 +273,16 @@ mod test {
let post = 2;
let block_of_change = 500;

let mut value: ScheduledValueChange<Field, TEST_DELAY> = ScheduledValueChange::new(pre, post, block_of_change);
let mut value: ScheduledValueChange<Field> = ScheduledValueChange::new(pre, post, block_of_change);

let new = 42;
let current_block_number = block_of_change - 50;
value.schedule_change(new, current_block_number);
value.schedule_change(
new,
current_block_number,
TEST_DELAY,
current_block_number + TEST_DELAY
);

// Because we re-schedule before the last scheduled change takes effect, the old `post` value is lost.
assert_eq(value.pre, pre);
Expand All @@ -283,11 +296,16 @@ mod test {
let post = 2;
let block_of_change = 500;

let mut value: ScheduledValueChange<Field, TEST_DELAY> = ScheduledValueChange::new(pre, post, block_of_change);
let mut value: ScheduledValueChange<Field> = ScheduledValueChange::new(pre, post, block_of_change);

let new = 42;
let current_block_number = block_of_change + 50;
value.schedule_change(new, current_block_number);
value.schedule_change(
new,
current_block_number,
TEST_DELAY,
current_block_number + TEST_DELAY
);

assert_eq(value.pre, post);
assert_eq(value.post, new);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@ use crate::state_vars::{storage::Storage, shared_mutable::scheduled_value_change
struct SharedMutable<T, DELAY> {
context: Context,
storage_slot: Field,
// The _dummy variable forces DELAY to be interpreted as a numberic value. This is a workaround to
// https://github.com/noir-lang/noir/issues/4633. Remove once resolved.
_dummy: [Field; DELAY],
}

impl<T, DELAY> Storage<T> for SharedMutable<T, DELAY> {}
Expand All @@ -24,35 +27,40 @@ impl<T, DELAY> Storage<T> for SharedMutable<T, DELAY> {}
impl<T, DELAY> SharedMutable<T, DELAY> {
pub fn new(context: Context, storage_slot: Field) -> Self {
assert(storage_slot != 0, "Storage slot 0 not allowed. Storage slots must start from 1.");
Self { context, storage_slot }
Self { context, storage_slot, _dummy: [0; DELAY] }
}

pub fn schedule_value_change(self, new_value: T) {
let context = self.context.public.unwrap();
let mut scheduled_value_change: ScheduledValueChange<T, DELAY> = public_storage::read(self.get_derived_storage_slot());
let mut scheduled_value_change: ScheduledValueChange<T> = public_storage::read(self.get_derived_storage_slot());

scheduled_value_change.schedule_change(new_value, context.block_number() as u32);
let block_number = context.block_number() as u32;
// TODO: make this configurable
// https://github.com/AztecProtocol/aztec-packages/issues/5501
let block_of_change = block_number + DELAY;

scheduled_value_change.schedule_change(new_value, block_number, DELAY, block_of_change);

public_storage::write(self.get_derived_storage_slot(), scheduled_value_change);
}

pub fn get_current_value_in_public(self) -> T {
let scheduled_value_change: ScheduledValueChange<T, DELAY> = public_storage::read(self.get_derived_storage_slot());
let scheduled_value_change: ScheduledValueChange<T> = public_storage::read(self.get_derived_storage_slot());

let block_number = self.context.public.unwrap().block_number() as u32;
scheduled_value_change.get_current_at(block_number)
}

pub fn get_scheduled_value_in_public(self) -> (T, u32) {
let scheduled_value_change: ScheduledValueChange<T, DELAY> = public_storage::read(self.get_derived_storage_slot());
let scheduled_value_change: ScheduledValueChange<T> = public_storage::read(self.get_derived_storage_slot());
scheduled_value_change.get_scheduled()
}

pub fn get_current_value_in_private(self) -> T where T: FromField {
let mut context = self.context.private.unwrap();

let (scheduled_value_change, historical_block_number) = self.historical_read_from_public_storage(*context);
let block_horizon = scheduled_value_change.get_block_horizon(historical_block_number);
let block_horizon = scheduled_value_change.get_block_horizon(historical_block_number, DELAY);

// We prevent this transaction from being included in any block after the block horizon, ensuring that the
// historical public value matches the current one, since it can only change after the horizon.
Expand All @@ -63,7 +71,7 @@ impl<T, DELAY> SharedMutable<T, DELAY> {
fn historical_read_from_public_storage(
self,
context: PrivateContext
) -> (ScheduledValueChange<T, DELAY>, u32) where T: FromField {
) -> (ScheduledValueChange<T>, u32) where T: FromField {
let derived_slot = self.get_derived_storage_slot();

// Ideally the following would be simply public_storage::read_historical, but we can't implement that yet.
Expand All @@ -76,7 +84,7 @@ impl<T, DELAY> SharedMutable<T, DELAY> {
);
}

let scheduled_value: ScheduledValueChange<T, DELAY> = ScheduledValueChange::deserialize(raw_fields);
let scheduled_value: ScheduledValueChange<T> = ScheduledValueChange::deserialize(raw_fields);
let historical_block_number = context.historical_header.global_variables.block_number as u32;

(scheduled_value, historical_block_number)
Expand Down
Loading

0 comments on commit cfa850b

Please sign in to comment.