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

feat: Set transaction expiry block delta #897

Merged
merged 12 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## 0.6.0 (TBD)

- Implemented kernel procedure to set transaction expiration block delta (#897).
- Created a proving service that receives `TransactionWitness` and returns the proof using gRPC (#881).
- Made note scripts public (#880).
- Implemented serialization for `TransactionWitness`, `ChainMmr`, `TransactionInputs` and `TransactionArgs` (#888).
Expand Down
26 changes: 26 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,32 @@ export.end_foreign_context
dropw
end

#! Updates the transaction expiration time delta.
#! Once set, the delta can be 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: [...]
#!
#! Where:
#! - block_height_delta is the desired expiration time delta (1 to 0xFFFF).
export.update_expiration_block_num
exec.tx::update_expiration_block_num
end

#! Gets the transaction expiration block number.
bobbinth marked this conversation as resolved.
Show resolved Hide resolved
#!
#! Inputs: [...]
#! Output: [block_height_delta, ...]
#!
#! Where:
#! - block_height_delta is the stored expiration time delta (1 to 0xFFFF).
export.get_expiration_delta
exec.tx::get_expiration_delta
end

#! Executes a kernel procedure specified by its offset.
#!
#! Inputs: [procedure_offset, <procedure_inputs>, <pad>]
Expand Down
7 changes: 6 additions & 1 deletion 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 @@ -224,7 +225,7 @@ end
#! - asserts that the input and output vault roots are equal
#!
#! Stack: []
#! Output: [OUTPUT_NOTES_COMMITMENT, FINAL_ACCOUNT_HASH]
#! Output: [OUTPUT_NOTES_COMMITMENT, FINAL_ACCOUNT_HASH, tx_expiration_block_num]
#!
#! - OUTPUT_NOTES_COMMITMENT is the commitment of the output notes
#! - FINAL_ACCOUNT_HASH is the final account hash
Expand Down Expand Up @@ -308,4 +309,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]

swapdw
exec.memory::get_expiration_block_num
swap drop swapdw
end
19 changes: 19 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,9 @@ 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

# The memory address at which the absolute expiration block number is stored.
const.TX_EXPIRATION_BLOCK_NUM_PTR=7

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

Expand Down Expand Up @@ -829,6 +832,22 @@ export.set_new_acct_code_commitment
mem_storew
end

#! Sets the transaction expiration block number.
#!
#! Inputs: [tx_expiration_block_num, ...]
#! Output: [...]
export.set_expiration_block_num
push.TX_EXPIRATION_BLOCK_NUM_PTR mem_store
end

#! Gets the transaction expiration block number.
#!
#! Inputs: []
#! Output: [tx_expiration_block_num]
export.get_expiration_block_num
push.TX_EXPIRATION_BLOCK_NUM_PTR mem_load
end

#! Returns the number of procedures contained in the account code.
#!
#! Stack: []
Expand Down
8 changes: 8 additions & 0 deletions miden-lib/asm/kernels/transaction/lib/prologue.masm
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@ use.kernel::constants
use.kernel::memory
use.kernel::utils

# CONSTS
# =================================================================================================

# Max U32 value, used for initializing the expiration block number
const.MAX_BLOCK_NUM=0xFFFFFFFF

# ERRORS
# =================================================================================================

Expand Down Expand Up @@ -1233,4 +1239,6 @@ export.prepare_transaction
exec.process_input_notes_data
exec.process_tx_script_root
# => []

push.MAX_BLOCK_NUM exec.memory::set_expiration_block_num
end
56 changes: 55 additions & 1 deletion miden-lib/asm/kernels/transaction/lib/tx.masm
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,9 @@ const.ENCRYPTED_NOTE=3 # 0b11
# Two raised to the power of 38 (2^38), used for shifting the note type value
const.TWO_POW_38=274877906944

# Max value for U16, used as the upper limit for expiration block delta
const.EXPIRY_UPPER_LIMIT=0xFFFF+1

# ERRORS
# =================================================================================================

Expand Down Expand Up @@ -61,6 +64,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 0x1 and 0xFFFF.
const.ERR_INVALID_TX_EXPIRATION_DELTA=0x00020055

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

Expand All @@ -73,7 +79,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 +184,54 @@ proc.add_non_fungible_asset_to_note
# => [note_ptr, note_idx]
end

#! Updates the transaction expiration block number.
#!
#! The input block_height_delta is added to the block reference number in order to output an upper
#! limit at which the transaction will be considered valid (not expired).
#! This value can be later decreased, but not increased.
#!
#! Inputs: [block_height_delta, ...]
#! Output: [...]
#!
#! Where:
#! - block_height_delta is the desired expiration time delta (1 to 0xFFFF).
#! - tx_block_height_delta is the updated (or unchanged) transaction expiration time delta.
bobbinth marked this conversation as resolved.
Show resolved Hide resolved
export.update_expiration_block_num
# Ensure block_height_delta is between 1 and 0xFFFF (inclusive)
dup neq.0 assert.err=ERR_INVALID_TX_EXPIRATION_DELTA
dup push.EXPIRY_UPPER_LIMIT lt assert.err=ERR_INVALID_TX_EXPIRATION_DELTA
# => [block_height_delta]

exec.get_block_number add
# => [absolute_expiration_num]

# Load the current stored delta from memory
dup exec.memory::get_expiration_block_num
# => [block_height_delta, absolute_expiration_num, absolute_expiration_num]
bobbinth marked this conversation as resolved.
Show resolved Hide resolved

# Check if block_height_delta is greater
lt
bobbinth marked this conversation as resolved.
Show resolved Hide resolved
if.true
# Set new expiration delta
exec.memory::set_expiration_block_num
else
drop
end
end

#! Gets the transaction expiration block number.
#!
#! Inputs: [...]
#! Output: [block_height_delta, ...]
#!
#! Where:
#! - block_height_delta is the stored expiration time delta (1 to 0xFFFF).
export.get_expiration_delta
bobbinth marked this conversation as resolved.
Show resolved Hide resolved
exec.memory::get_expiration_block_num exec.get_block_number
sub
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
28 changes: 27 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,8 @@ 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.UPDATE_EXPIRATION_BLOCK_NUM_OFFSET=30
const.GET_EXPIRATION_DELTA_OFFSET=31

# ACCESSORS
# -------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -105,7 +107,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 +260,30 @@ export.get_block_number_offset
push.GET_BLOCK_NUMBER_OFFSET
end

#! Returns an offset of the `update_expiration_block_num` kernel procedure.
#!
#! Stack: []
#! Output: [proc_offset]
#!
#! Where:
#! - proc_offset is the offset of the `set_tx_expiration_delta` kernel procedure required to get the
#! address where this procedure is stored.
export.update_expiration_block_num_offset
push.UPDATE_EXPIRATION_BLOCK_NUM_OFFSET
end

#! Returns an offset of the `get_expiration_delta` kernel procedure.
#!
#! Stack: []
#! Output: [proc_offset]
#!
#! Where:
#! - proc_offset is the offset of the `set_tx_expiration_delta` kernel procedure required to get the
#! address where this procedure is stored.
export.get_expiration_block_delta_offset
push.GET_EXPIRATION_DELTA_OFFSET
end

#! Returns an offset of the `get_block_hash` kernel procedure.
#!
#! Stack: []
Expand Down
5 changes: 4 additions & 1 deletion miden-lib/src/transaction/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ pub type StorageSlot = u8;
//
// | Section | Start address | End address |
// | ------------- | :------------:| :-----------:|
// | Bookkeeping | 0 | 6 |
// | Bookkeeping | 0 | 7 |
// | Global inputs | 100 | 105 |
// | Block header | 200 | 208 |
// | Chain MMR | 300 | 332? |
Expand Down Expand Up @@ -73,6 +73,9 @@ pub const CURRENT_ACCOUNT_DATA_PTR: MemoryAddress = 5;
/// The memory address at which the native account's new code commitment is stored.
pub const NEW_CODE_ROOT_PTR: MemoryAddress = 6;

/// The memory address at which the transaction expiration block number is stored.
pub const TX_EXPIRATION_BLOCK_NUM_PTR: MemoryAddress = 7;

// GLOBAL INPUTS
// ------------------------------------------------------------------------------------------------

Expand Down
41 changes: 29 additions & 12 deletions miden-lib/src/transaction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use miden_objects::{
Digest, Felt, TransactionOutputError, Word, EMPTY_WORD,
};
use miden_stdlib::StdLibrary;
use outputs::EXPIRATION_BLOCK_ELEMENT_IDX;

use super::MidenLib;

Expand Down Expand Up @@ -174,35 +175,43 @@ impl TransactionKernel {
///
/// The data on the stack is expected to be arranged as follows:
///
/// Stack: [CNC, FAH]
/// Stack: [CNC, FAH, tx_expiration_block_num]
///
/// 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.
/// - tx_expiration_block_num 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, u32), TransactionOutputError> {
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 = stack
.get_stack_item(EXPIRATION_BLOCK_ELEMENT_IDX)
.expect("element on index 8 missing");

let expiration_block_num = u32::try_from(expiration_block_num.as_int()).map_err(|_| {
TransactionOutputError::OutputStackInvalid(
"Expiration block number should be lower than u32::MAX".into(),
igamigo marked this conversation as resolved.
Show resolved Hide resolved
)
})?;

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 +223,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 +233,15 @@ impl TransactionKernel {
///
/// The output stack is expected to be arrange as follows:
///
/// Stack: [CNC, FAH]
/// Stack: [CNC, FAH, tx_expiration_block_num]
///
/// 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.
/// - tx_expiration_block_num 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 +250,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 +271,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
Loading
Loading