Skip to content

Commit

Permalink
feat: Set transaction recency conditions
Browse files Browse the repository at this point in the history
  • Loading branch information
igamigo committed Sep 25, 2024
1 parent b8d9541 commit ce820fe
Show file tree
Hide file tree
Showing 14 changed files with 245 additions and 22 deletions.
16 changes: 16 additions & 0 deletions miden-lib/asm/kernels/transaction/api.masm
Original file line number Diff line number Diff line change
Expand Up @@ -893,6 +893,22 @@ export.end_foreign_context
dropw
end

#! Sets the transaction expiration time delta, in blocks.
#! Once set, the delta can be leter decreased but not increased.
#!
#! The input block height delta is added to the reference block in order to output an upper limit
#! up until which the transaction will be considered valid (not expired).
#!
#! Inputs: [block_height_delta, ...]
#! Output: [tx_block_height_delta, ...]
#!
#! Where:
#! - block_height_delta is the desired expiration time delta (1 to 256).
#! - tx_block_height_delta is the updated (or unchanged) transaction expiration time delta.
export.set_tx_expiration_delta
exec.tx::set_expiration_delta
end

#! Executes a kernel procedure specified by its offset.
#!
#! Inputs: [procedure_offset, <procedure_inputs>, <pad>]
Expand Down
30 changes: 30 additions & 0 deletions miden-lib/asm/kernels/transaction/lib/epilogue.masm
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use.kernel::asset_vault
use.kernel::constants
use.kernel::memory
use.kernel::note
use.kernel::tx

use.std::crypto::hashes::native

Expand Down Expand Up @@ -213,6 +214,31 @@ proc.update_account_storage_commitment
end
end

# EXPIRATION BLOCK HEIGHT
# =================================================================================================

#! Calculates the block number by which the transaction will not be valid anymore, given by the
#! addition of the reference block number and the transaction expiration delta.
#!
#! Stack: []
#! Output: [expiration_block_num]
#!
#! Where:
#! - expiration_block_num: If the expiration block delta was set, it will be added to the block
#! ref number. Otherwise, it will be equal to 0.
proc.get_absolute_expiration_block
exec.memory::get_expiration_delta
# => [expiration_block_delta]

dup neq.0
if.true
# get block number to add to transaction expiration delta
exec.tx::get_block_number add
# => [expiration_block_num]
end
end


# TRANSACTION EPILOGUE PROCEDURE
# =================================================================================================

Expand Down Expand Up @@ -308,4 +334,8 @@ export.finalize_transaction
# assert no net creation or destruction of assets over the transaction
exec.memory::get_input_vault_root exec.memory::get_output_vault_root assert_eqw.err=ERR_EPILOGUE_ASSETS_DONT_ADD_UP
# => [OUTPUT_NOTES_COMMITMENT, FINAL_ACCOUNT_HASH]

exec.get_absolute_expiration_block movdn.8

exec.::std::sys::truncate_stack
end
22 changes: 22 additions & 0 deletions miden-lib/asm/kernels/transaction/lib/memory.masm
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@ const.CURRENT_ACCOUNT_DATA_PTR=5
# The memory address at which the native account's new code commitment is stored.
const.NEW_CODE_ROOT_PTR=6

# Memory address at which the transaction expiration delta is stored.
# The maximum block number for which the transaction is valid will be given by the transaction's
# block number, plus the expiration height delta.
const.TX_EXPIRATION_DELTA_PTR=7

# GLOBAL INPUTS
# -------------------------------------------------------------------------------------------------

Expand Down Expand Up @@ -829,6 +834,23 @@ export.set_new_acct_code_commitment
mem_storew
end

#! Sets the transaction expiration time delta, in blocks.
#!
#! Inputs: [block_height_delta, ...]
#! Output: [...]
export.set_expiration_delta
push.TX_EXPIRATION_DELTA_PTR mem_store
end

#! Gets the transaction expiration time delta, in blocks.
#!
#! Inputs: []
#! Output: [block_height_delta]
export.get_expiration_delta
push.TX_EXPIRATION_DELTA_PTR mem_load
end


#! Returns the number of procedures contained in the account code.
#!
#! Stack: []
Expand Down
47 changes: 46 additions & 1 deletion miden-lib/asm/kernels/transaction/lib/tx.masm
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ const.ERR_NON_FUNGIBLE_ASSET_ALREADY_EXISTS=0x00020051
# Note idx must be within [0, num_of_notes]
const.ERR_INVALID_NOTE_IDX=0x00020052

# Input transaction expiration block delta is not within 1 and 255.
const.ERR_INVALID_TX_EXPIRATION_DELTA=0x00020055

# EVENTS
# =================================================================================================

Expand All @@ -73,7 +76,7 @@ const.NOTE_AFTER_CREATED_EVENT=131084
const.NOTE_BEFORE_ADD_ASSET_EVENT=131085
# Event emitted after an ASSET is added to a note
const.NOTE_AFTER_ADD_ASSET_EVENT=131086

#! Returns the block hash of the reference block to memory.
#!
#! Stack: []
Expand Down Expand Up @@ -178,6 +181,48 @@ proc.add_non_fungible_asset_to_note
# => [note_ptr, note_idx]
end

#! Sets the transaction expiration delta, in blocks.
#! Once set, the delta can be later decreased but not increased.
#!
#! The input block height delta added to the reference block in order to output an upper limit
#! at which the transaction will be considered valid (not expired).
#!
#! Inputs: [block_height_delta, ...]
#! Output: [...]
#!
#! Where:
#! - block_height_delta is the desired expiration time delta (1 to 256).
#! - tx_block_height_delta is the updated (or unchanged) transaction expiration time delta.
export.set_expiration_delta
# Ensure block_height_delta is between 1 and 255 (inclusive)
dup neq.0 assert.err=ERR_INVALID_TX_EXPIRATION_DELTA
dup push.256 lt assert.err=ERR_INVALID_TX_EXPIRATION_DELTA
# => [block_height_delta]

# Load the current stored delta from memory
exec.memory::get_expiration_delta dup
# => [stored_delta, stored_delta, block_height_delta]

# if stored_delta is 0, it was not yet set
eq.0
if.true
drop
# => [block_height_delta]

exec.memory::set_expiration_delta
else
# Expiration delta had been set, check if new one should be stored
dup.1
# => [block_height_delta, stored_delta, block_height_delta]
gt
if.true
exec.memory::set_expiration_delta
else
drop
end
end
end

#! Adds a fungible asset to a note. If the note already holds an asset issued by the
#! same faucet id the two quantities are summed up and the new quantity is stored at the
#! old position in the note. In the other case, the asset is stored at the next available
Expand Down
7 changes: 6 additions & 1 deletion miden-lib/asm/miden/kernel_proc_offsets.masm
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ const.GET_BLOCK_HASH_OFFSET=26
const.GET_BLOCK_NUMBER_OFFSET=27
const.START_FOREIGN_CONTEXT_OFFSET=28
const.END_FOREIGN_CONTEXT_OFFSET=29
const.SET_TX_EXPIRATION_DELTA_OFFSET=30

# ACCESSORS
# -------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -105,7 +106,7 @@ end
#! Returns an offset of the `get_account_item` kernel procedure.
#!
#! Stack: []
#! Output: [proc_offset]
#! Output: [proc_offset]
#!
#! Where:
#! - proc_offset is the offset of the `get_account_item` kernel procedure required to get the
Expand Down Expand Up @@ -258,6 +259,10 @@ export.get_block_number_offset
push.GET_BLOCK_NUMBER_OFFSET
end

export.set_tx_expiration_delta
push.SET_TX_EXPIRATION_DELTA_OFFSET
end

#! Returns an offset of the `get_block_hash` kernel procedure.
#!
#! Stack: []
Expand Down
2 changes: 1 addition & 1 deletion miden-lib/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ fn generate_kernel_proc_hash_file(kernel: KernelLibrary) -> Result<()> {
let proc_count = generated_procs.len();
let generated_procs: String = generated_procs.into_iter().enumerate().map(|(index, (offset, txt))| {
if index != offset {
panic!("Offset constants in the file `{offsets_filename:?}` are not contiguous (missing offset: {index})");
panic!("Offset constants in the file `{offsets_filename:?}` are not contiguous (missing offset: {index} - {txt})");
}

txt
Expand Down
44 changes: 30 additions & 14 deletions miden-lib/src/transaction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ use miden_objects::{
},
utils::{group_slice_elements, serde::Deserializable},
vm::{AdviceInputs, AdviceMap, Program, ProgramInfo, StackInputs, StackOutputs},
Digest, Felt, TransactionOutputError, Word, EMPTY_WORD,
Digest, Felt, FieldElement, TransactionOutputError, Word, EMPTY_WORD,
};
use miden_stdlib::StdLibrary;
use outputs::EXPIRATION_BLOCK_ELEMENT_IDX;

use super::MidenLib;

Expand Down Expand Up @@ -174,35 +175,42 @@ impl TransactionKernel {
///
/// The data on the stack is expected to be arranged as follows:
///
/// Stack: [CNC, FAH]
/// Stack: [CNC, FAH, block_expiration_height]
///
/// Where:
/// - CNC is the commitment to the notes created by the transaction.
/// - FAH is the final account hash of the account that the transaction is being executed
/// against.
///
/// - block_expiration_height is the block height at which the transaction will become expired,
/// defined by the sum of the execution block ref and the transaction's block expiration
/// delta (if set during transaction execution).
///
/// # Errors
/// Returns an error if:
/// - Words 3 and 4 on the stack are not 0.
/// - Overflow addresses are not empty.
pub fn parse_output_stack(
stack: &StackOutputs,
) -> Result<(Digest, Digest), TransactionOutputError> {
) -> Result<(Digest, Digest, Option<u32>), TransactionOutputError> {
// # => [OUTPUT_NOTES_COMMITMENT, FINAL_ACCOUNT_HASH]
let output_notes_hash = stack
.get_stack_word(OUTPUT_NOTES_COMMITMENT_WORD_IDX * 4)
.expect("first word missing")
.into();

let final_account_hash = stack
.get_stack_word(FINAL_ACCOUNT_HASH_WORD_IDX * 4)
.expect("second word missing")
.into();

// make sure that the stack has been properly cleaned
if stack.get_stack_word(8).expect("third word missing") != EMPTY_WORD {
return Err(TransactionOutputError::OutputStackInvalid(
"Third word on output stack should consist only of ZEROs".into(),
));
}
let expiration_block_num: Felt = stack
.get_stack_item(EXPIRATION_BLOCK_ELEMENT_IDX)
.expect("element on index 8 missing")
.into();

let expiration_block_num =
(expiration_block_num != Felt::ZERO).then_some(expiration_block_num.as_int() as u32);

if stack.get_stack_word(12).expect("fourth word missing") != EMPTY_WORD {
return Err(TransactionOutputError::OutputStackInvalid(
"Fourth word on output stack should consist only of ZEROs".into(),
Expand All @@ -214,7 +222,7 @@ impl TransactionKernel {
));
}

Ok((final_account_hash, output_notes_hash))
Ok((final_account_hash, output_notes_hash, expiration_block_num))
}

// TRANSACTION OUTPUT PARSER
Expand All @@ -224,12 +232,15 @@ impl TransactionKernel {
///
/// The output stack is expected to be arrange as follows:
///
/// Stack: [CNC, FAH]
/// Stack: [CNC, FAH, block_expiration_height]
///
/// Where:
/// - CNC is the commitment to the notes created by the transaction.
/// - FAH is the final account hash of the account that the transaction is being executed
/// against.
/// - block_expiration_height is the block height at which the transaction will become expired,
/// defined by the sum of the execution block ref and the transaction's block expiration
/// delta (if set during transaction execution).
///
/// The actual data describing the new account state and output notes is expected to be located
/// in the provided advice map under keys CNC and FAH.
Expand All @@ -238,7 +249,8 @@ impl TransactionKernel {
adv_map: &AdviceMap,
output_notes: Vec<OutputNote>,
) -> Result<TransactionOutputs, TransactionOutputError> {
let (final_acct_hash, output_notes_hash) = Self::parse_output_stack(stack)?;
let (final_acct_hash, output_notes_hash, expiration_block_num) =
Self::parse_output_stack(stack)?;

// parse final account state
let final_account_data: &[Word] = group_slice_elements(
Expand All @@ -258,7 +270,11 @@ impl TransactionKernel {
));
}

Ok(TransactionOutputs { account, output_notes })
Ok(TransactionOutputs {
account,
output_notes,
expiration_block_num,
})
}
}

Expand Down
3 changes: 3 additions & 0 deletions miden-lib/src/transaction/outputs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ pub const OUTPUT_NOTES_COMMITMENT_WORD_IDX: usize = 0;
/// The index of the word at which the final account hash is stored on the output stack.
pub const FINAL_ACCOUNT_HASH_WORD_IDX: usize = 1;

/// The index of the item at which the expiration block height is stored on the output stack.
pub const EXPIRATION_BLOCK_ELEMENT_IDX: usize = 8;

// ACCOUNT HEADER EXTRACTOR
// ================================================================================================

Expand Down
4 changes: 3 additions & 1 deletion miden-lib/src/transaction/procedures/kernel_v0.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use miden_objects::{digest, Digest, Felt};
// ================================================================================================

/// Hashes of all dynamically executed procedures from the kernel 0.
pub const KERNEL0_PROCEDURES: [Digest; 30] = [
pub const KERNEL0_PROCEDURES: [Digest; 31] = [
// account_vault_add_asset
digest!(0xb8815bfacbdcb4c2, 0x6c7e694cf4f6a517, 0xf6233da2865ca264, 0xe51463cd0df6e896),
// account_vault_get_balance
Expand Down Expand Up @@ -67,4 +67,6 @@ pub const KERNEL0_PROCEDURES: [Digest; 30] = [
digest!(0x9d231f21bd27ff27, 0x5cc4476fad12b66d, 0x82f40fd18e7abb0a, 0xc09c240f2a1d82af),
// end_foreign_context
digest!(0x3770db711ce9aaf1, 0xb6f3c929151a5d52, 0x3ed145ec5dbee85f, 0xf979d975d7951bf6),
// set_tx_expiration_delta
digest!(0x56505ecb517b3a7f, 0x8c531dc547913e86, 0x20ef1cdcb370f308, 0xad7f1097f1b8038d),
];
4 changes: 3 additions & 1 deletion miden-tx/src/errors/tx_kernel_errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,8 +86,9 @@ pub const ERR_NON_FUNGIBLE_ASSET_ALREADY_EXISTS: u32 = 131153;
pub const ERR_INVALID_NOTE_IDX: u32 = 131154;
pub const ERR_KERNEL_PROCEDURE_OFFSET_OUT_OF_BOUNDS: u32 = 131155;
pub const ERR_CURRENT_ACCOUNT_IS_NOT_NATIVE: u32 = 131156;
pub const ERR_INVALID_TX_EXPIRATION_DELTA: u32 = 131157;

pub const KERNEL_ERRORS: [(u32, &str); 85] = [
pub const KERNEL_ERRORS: [(u32, &str); 86] = [
(ERR_FAUCET_RESERVED_DATA_SLOT, "For faucets, storage slot 254 is reserved and can not be used with set_account_item procedure"),
(ERR_ACCT_MUST_BE_A_FAUCET, "Procedure can only be called from faucet accounts"),
(ERR_P2ID_WRONG_NUMBER_OF_INPUTS, "P2ID scripts expect exactly 1 note input"),
Expand Down Expand Up @@ -173,4 +174,5 @@ pub const KERNEL_ERRORS: [(u32, &str); 85] = [
(ERR_INVALID_FAUCET_STORAGE_OFFSET, "Storage offset is invalid for a faucet account (0 is prohibited being the reserved faucet data slot)"),
(ERR_KERNEL_PROCEDURE_OFFSET_OUT_OF_BOUNDS, "Provided kernel procedure offset is out of bounds"),
(ERR_CURRENT_ACCOUNT_IS_NOT_NATIVE, "Procedure can be called only for the native account"),
(ERR_INVALID_TX_EXPIRATION_DELTA, "Invalid transaction expiration block delta was set."),
];
1 change: 1 addition & 0 deletions miden-tx/src/prover/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ impl TransactionProver for LocalTransactionProver {
account.init_hash(),
tx_outputs.account.hash(),
block_hash,
tx_outputs.expiration_block_num,
proof,
)
.add_input_notes(input_notes)
Expand Down
Loading

0 comments on commit ce820fe

Please sign in to comment.