Skip to content

Commit

Permalink
feat: added BlockNumber struct (#1043)
Browse files Browse the repository at this point in the history
  • Loading branch information
varun-doshi authored Jan 16, 2025
1 parent bd3a396 commit 2e4a974
Show file tree
Hide file tree
Showing 19 changed files with 215 additions and 95 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

### Changes

- Added health check endpoints to the prover service (#1006).
- Implemented serialization for `AccountHeader` (#996).
- Updated Pingora crates to 0.4 and added polling time to the configuration file (#997).
- Added support for `miden-tx-prover` proxy to update workers on a running proxy (#989).
Expand All @@ -17,11 +16,13 @@
- Added conversion from `Account` to `AccountDelta` for initial account state representation as delta (#983).
- [BREAKING] Added `miden::note::get_script_hash` procedure (#995).
- [BREAKING] Refactor error messages in `miden-lib` and `miden-tx` and use `thiserror` 2.0 (#1005).
- Added health check endpoints to the prover service (#1006).
- [BREAKING] Extend `AccountId` to two `Felt`s and require block hash in derivation (#982).
- Removed workers list from the proxy configuration file (#1018).
- Added tracing to the `miden-tx-prover` CLI (#1014).
- Added metrics to the `miden-tx-prover` proxy (#1017).
- Implemented `to_hex` for `AccountIdPrefix` and `epoch_block_num` for `BlockHeader` (#1039).
- Add `BlockNumber` struct (#1043).
- Introduce `AccountIdBuilder` to simplify `AccountId` generation in tests (#1045).
- Introduced `AccountComponentTemplate` with TOML serialization and templating (#1015, #1027).
- [BREAKING] Updated the names and values of the kernel procedure offsets and corresponding kernel procedures (#1037).
Expand Down
6 changes: 4 additions & 2 deletions miden-tx/src/executor/data_store.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
#[cfg(feature = "async")]
use alloc::boxed::Box;

use miden_objects::{accounts::AccountId, notes::NoteId, transaction::TransactionInputs};
use miden_objects::{
accounts::AccountId, block::BlockNumber, notes::NoteId, transaction::TransactionInputs,
};
use winter_maybe_async::*;

use crate::DataStoreError;
Expand Down Expand Up @@ -32,7 +34,7 @@ pub trait DataStore {
fn get_transaction_inputs(
&self,
account_id: AccountId,
block_ref: u32,
block_ref: BlockNumber,
notes: &[NoteId],
) -> Result<TransactionInputs, DataStoreError>;
}
3 changes: 2 additions & 1 deletion miden-tx/src/executor/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use miden_lib::transaction::TransactionKernel;
use miden_objects::{
accounts::{AccountCode, AccountId},
assembly::Library,
block::BlockNumber,
notes::NoteId,
transaction::{ExecutedTransaction, TransactionArgs, TransactionInputs},
vm::StackOutputs,
Expand Down Expand Up @@ -128,7 +129,7 @@ impl TransactionExecutor {
pub fn execute_transaction(
&self,
account_id: AccountId,
block_ref: u32,
block_ref: BlockNumber,
notes: &[NoteId],
tx_args: TransactionArgs,
) -> Result<ExecutedTransaction, TransactionExecutorError> {
Expand Down
18 changes: 10 additions & 8 deletions miden-tx/src/testing/mock_chain/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ use miden_objects::{
},
assets::{Asset, FungibleAsset, TokenSymbol},
block::{
block_num_from_epoch, compute_tx_hash, Block, BlockAccountUpdate, BlockNoteIndex,
BlockNoteTree, NoteBatch,
compute_tx_hash, Block, BlockAccountUpdate, BlockNoteIndex, BlockNoteTree, BlockNumber,
NoteBatch,
},
crypto::{
dsa::rpo_falcon512::SecretKey,
Expand Down Expand Up @@ -602,14 +602,14 @@ impl MockChain {
let block = self.blocks.last().unwrap();

let mut input_notes = vec![];
let mut block_headers_map: BTreeMap<u32, BlockHeader> = BTreeMap::new();
let mut block_headers_map: BTreeMap<BlockNumber, BlockHeader> = BTreeMap::new();
for note in notes {
let input_note = self.available_notes.get(note).expect("Note not found").clone();
let note_block_num = input_note.location().unwrap().block_num();
if note_block_num != block.header().block_num() {
block_headers_map.insert(
note_block_num,
self.blocks.get(note_block_num as usize).unwrap().header(),
self.blocks.get(note_block_num.as_usize()).unwrap().header(),
);
}
input_notes.push(input_note);
Expand All @@ -618,13 +618,13 @@ impl MockChain {
// If the account is new, add the anchor block's header from which the account ID is derived
// to the MMR.
if account.is_new() {
let epoch_block_num = block_num_from_epoch(account.id().anchor_epoch());
let epoch_block_num = BlockNumber::from_epoch(account.id().anchor_epoch());
// The reference block of the transaction is added to the MMR in
// prologue::process_chain_data so we can skip adding it to the block headers here.
if epoch_block_num != block.header().block_num() {
block_headers_map.insert(
epoch_block_num,
self.blocks.get(epoch_block_num as usize).unwrap().header(),
self.blocks.get(epoch_block_num.as_usize()).unwrap().header(),
);
}
}
Expand Down Expand Up @@ -653,7 +653,9 @@ impl MockChain {
/// This will also make all the objects currently pending available for use.
/// If `block_num` is `Some(number)`, blocks will be generated up to `number`.
pub fn seal_block(&mut self, block_num: Option<u32>) -> Block {
let next_block_num = self.blocks.last().map_or(0, |b| b.header().block_num() + 1);
let next_block_num =
self.blocks.last().map_or(0, |b| b.header().block_num().child().as_u32());

let target_block_num = block_num.unwrap_or(next_block_num);

if target_block_num < next_block_num {
Expand Down Expand Up @@ -711,7 +713,7 @@ impl MockChain {
let header = BlockHeader::new(
version,
prev_hash,
current_block_num,
BlockNumber::from(current_block_num),
chain_root,
account_root,
nullifier_root,
Expand Down
3 changes: 2 additions & 1 deletion miden-tx/src/testing/tx_context/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use miden_lib::transaction::TransactionKernel;
use miden_objects::{
accounts::{Account, AccountCode, AccountId},
assembly::Assembler,
block::BlockNumber,
notes::{Note, NoteId},
transaction::{ExecutedTransaction, InputNote, InputNotes, TransactionArgs, TransactionInputs},
};
Expand Down Expand Up @@ -139,7 +140,7 @@ impl DataStore for TransactionInputs {
fn get_transaction_inputs(
&self,
account_id: AccountId,
block_num: u32,
block_num: BlockNumber,
notes: &[NoteId],
) -> Result<TransactionInputs, DataStoreError> {
assert_eq!(account_id, self.account().id());
Expand Down
3 changes: 2 additions & 1 deletion miden-tx/src/tests/kernel_tests/test_epilogue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,7 +244,8 @@ fn test_block_expiration_height_monotonically_decreases() {

// Expiry block should be set to transaction's block + the stored expiration delta
// (which can only decrease, not increase)
let expected_expiry = v1.min(v2) + tx_context.tx_inputs().block_header().block_num() as u64;
let expected_expiry =
v1.min(v2) + tx_context.tx_inputs().block_header().block_num().as_u64();
assert_eq!(process.get_stack_item(8).as_int(), expected_expiry);
}
}
Expand Down
2 changes: 1 addition & 1 deletion miden-tx/src/tests/kernel_tests/test_prologue.rs
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ fn chain_mmr_memory_assertions(process: &Process<MockHost>, prepared_tx: &Transa

assert_eq!(
read_root_mem_value(process, CHAIN_MMR_NUM_LEAVES_PTR)[0],
Felt::new(chain_mmr.chain_length() as u64),
Felt::new(chain_mmr.chain_length().as_u64()),
"The number of leaves should be stored at the CHAIN_MMR_NUM_LEAVES_PTR"
);

Expand Down
14 changes: 6 additions & 8 deletions objects/src/accounts/account_id/id_anchor.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
use crate::{
block::block_epoch_from_number, errors::AccountIdError, BlockHeader, Digest, EMPTY_WORD,
};
use crate::{block::BlockNumber, errors::AccountIdError, BlockHeader, Digest, EMPTY_WORD};

// ACCOUNT ID ANCHOR
// ================================================================================================
Expand All @@ -14,8 +12,8 @@ use crate::{
/// # Constraints
///
/// This type enforces the following constraints.
/// - The `anchor_block_number` % 2^[`BlockHeader::EPOCH_LENGTH_EXPONENT`] must be zero. In other
/// words, the block number must a multiple of 2^[`BlockHeader::EPOCH_LENGTH_EXPONENT`].
/// - The `anchor_block_number` % 2^[`BlockNumber::EPOCH_LENGTH_EXPONENT`] must be zero. In other
/// words, the block number must a multiple of 2^[`BlockNumber::EPOCH_LENGTH_EXPONENT`].
/// - The epoch derived from the `anchor_block_number` must be strictly less than [`u16::MAX`].
#[derive(Debug, Clone, Copy)]
pub struct AccountIdAnchor {
Expand Down Expand Up @@ -51,14 +49,14 @@ impl AccountIdAnchor {
/// Returns an error if any of the anchor constraints are not met. See the [type
/// documentation](AccountIdAnchor) for details.
pub fn new(
anchor_block_number: u32,
anchor_block_number: BlockNumber,
anchor_block_hash: Digest,
) -> Result<Self, AccountIdError> {
if anchor_block_number & 0x0000_ffff != 0 {
if anchor_block_number.as_u32() & 0x0000_ffff != 0 {
return Err(AccountIdError::AnchorBlockMustBeEpochBlock);
}

let anchor_epoch = block_epoch_from_number(anchor_block_number);
let anchor_epoch = anchor_block_number.block_epoch();

if anchor_epoch == u16::MAX {
return Err(AccountIdError::AnchorEpochMustNotBeU16Max);
Expand Down
4 changes: 2 additions & 2 deletions objects/src/accounts/account_id/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ use crate::{errors::AccountIdError, AccountError, ACCOUNT_TREE_DEPTH};
/// as the [`NoteMetadata`](crate::notes::NoteMetadata). In such cases, it can happen that all
/// bits of the encoded suffix would be one, so having the epoch constraint is important.
/// - The ID is dependent on the hash of an epoch block. This is a block whose number is a multiple
/// of 2^[`BlockHeader::EPOCH_LENGTH_EXPONENT`][epoch_len_exp], e.g. `0`, `65536`, `131072`, ...
/// of 2^[`BlockNumber::EPOCH_LENGTH_EXPONENT`][epoch_len_exp], e.g. `0`, `65536`, `131072`, ...
/// These are the first blocks of epoch 0, 1, 2, ... We call this dependence _anchoring_ because
/// the ID is anchored to that epoch block's hash. Anchoring makes it practically impossible for
/// an attacker to construct a rainbow table of account IDs whose epoch is X, if the block for
Expand All @@ -108,7 +108,7 @@ use crate::{errors::AccountIdError, AccountError, ACCOUNT_TREE_DEPTH};
/// hashes to the user's ID can claim the assets sent to the user's ID. Adding the anchor
/// block hash to ID generation process makes this attack practically impossible.
///
/// [epoch_len_exp]: crate::block::BlockHeader::EPOCH_LENGTH_EXPONENT
/// [epoch_len_exp]: crate::block::BlockNumber::EPOCH_LENGTH_EXPONENT
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AccountId {
V0(AccountIdV0),
Expand Down
6 changes: 4 additions & 2 deletions objects/src/accounts/builder/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ mod tests {
use vm_core::FieldElement;

use super::*;
use crate::accounts::StorageSlot;
use crate::{accounts::StorageSlot, block::BlockNumber};

const CUSTOM_CODE1: &str = "
export.foo
Expand Down Expand Up @@ -317,7 +317,9 @@ mod tests {

let anchor_block_hash = Digest::new([Felt::new(42); 4]);
let anchor_block_number = 1 << 16;
let id_anchor = AccountIdAnchor::new(anchor_block_number, anchor_block_hash).unwrap();
let id_anchor =
AccountIdAnchor::new(BlockNumber::from(anchor_block_number), anchor_block_hash)
.unwrap();

let (account, seed) = Account::builder([5; 32])
.anchor(id_anchor)
Expand Down
114 changes: 114 additions & 0 deletions objects/src/block/block_number.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
use core::{fmt, ops::Add};

use super::Felt;
use crate::utils::serde::{
ByteReader, ByteWriter, Deserializable, DeserializationError, Serializable,
};

// BLOCK NUMBER
// ================================================================================================

/// A convenience wrapper around a `u32` representing the number of a block.
///
/// Each block has a unique number and block numbers increase monotonically by `1`.
#[derive(Debug, Default, Eq, PartialEq, Copy, Clone, PartialOrd, Ord, Hash)]
pub struct BlockNumber(u32);

impl BlockNumber {
/// The length of an epoch expressed as a power of two. `2^(EPOCH_LENGTH_EXPONENT)` is the
/// number of blocks in an epoch.
///
/// The epoch of a block can be obtained by shifting the block number to the right by this
/// exponent.
pub const EPOCH_LENGTH_EXPONENT: u8 = 16;

/// Genesis BlockNumber
pub const GENESIS: Self = Self(0);

/// Returns the previous block number
pub fn parent(self) -> Option<BlockNumber> {
self.checked_sub(1)
}

/// Returns the next block number
pub fn child(self) -> BlockNumber {
self + 1
}

/// Creates the [`BlockNumber`] corresponding to the epoch block for the provided `epoch`.
pub const fn from_epoch(epoch: u16) -> BlockNumber {
BlockNumber((epoch as u32) << BlockNumber::EPOCH_LENGTH_EXPONENT)
}

/// Creates a `BlockNumber` from a `usize`.
pub fn from_usize(value: usize) -> Self {
BlockNumber(value as u32)
}

/// Returns the epoch to which this block number belongs.
pub const fn block_epoch(&self) -> u16 {
(self.0 >> BlockNumber::EPOCH_LENGTH_EXPONENT) as u16
}

/// Returns the block number as a `u32`.
pub fn as_u32(&self) -> u32 {
self.0
}

/// Returns the block number as a `u64`.
pub fn as_u64(&self) -> u64 {
self.0 as u64
}

/// Returns the block number as a `usize`.
pub fn as_usize(&self) -> usize {
self.0 as usize
}

/// Checked integer subtraction. Computes `self - rhs`, returning `None` if underflow occurred.
pub fn checked_sub(&self, rhs: u32) -> Option<Self> {
self.0.checked_sub(rhs).map(Self)
}
}

impl Add<u32> for BlockNumber {
type Output = Self;

fn add(self, other: u32) -> Self::Output {
BlockNumber(self.0 + other)
}
}

impl Serializable for BlockNumber {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
target.write_u32(self.0);
}

fn get_size_hint(&self) -> usize {
core::mem::size_of::<u32>()
}
}

impl Deserializable for BlockNumber {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
source.read::<u32>().map(BlockNumber::from)
}
}

impl From<BlockNumber> for Felt {
fn from(value: BlockNumber) -> Self {
Felt::from(value.as_u32())
}
}

impl From<u32> for BlockNumber {
fn from(value: u32) -> Self {
BlockNumber(value)
}
}

impl fmt::Display for BlockNumber {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.0)
}
}
Loading

0 comments on commit 2e4a974

Please sign in to comment.