diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ee7be277..a29326653 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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). diff --git a/miden-lib/asm/kernels/transaction/api.masm b/miden-lib/asm/kernels/transaction/api.masm index 26327d0c2..b0db97f24 100644 --- a/miden-lib/asm/kernels/transaction/api.masm +++ b/miden-lib/asm/kernels/transaction/api.masm @@ -893,6 +893,22 @@ export.end_foreign_context dropw end +#! Sets the transaction expiration time delta, in blocks. +#! Once set, the delta can be later 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_block_num +end + #! Executes a kernel procedure specified by its offset. #! #! Inputs: [procedure_offset, , ] diff --git a/miden-lib/asm/kernels/transaction/lib/epilogue.masm b/miden-lib/asm/kernels/transaction/lib/epilogue.masm index 5db8f4681..2f578e623 100644 --- a/miden-lib/asm/kernels/transaction/lib/epilogue.masm +++ b/miden-lib/asm/kernels/transaction/lib/epilogue.masm @@ -3,9 +3,16 @@ use.kernel::asset_vault use.kernel::constants use.kernel::memory use.kernel::note +use.kernel::tx use.std::crypto::hashes::native +# CONSTS +# ================================================================================================= + +# Max U32 value. This is the default value for transaction expiration delta +const.U32_MAX=0xFFFFFFFF + # ERRORS # ================================================================================================= @@ -224,7 +231,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 @@ -301,11 +308,11 @@ export.finalize_transaction exec.copy_output_notes_to_advice_map # => [OUTPUT_NOTES_COMMITMENT, FINAL_ACCOUNT_HASH] - # truncate stack - swapdw dropw dropw - # => [OUTPUT_NOTES_COMMITMENT, FINAL_ACCOUNT_HASH] - # 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 diff --git a/miden-lib/asm/kernels/transaction/lib/memory.masm b/miden-lib/asm/kernels/transaction/lib/memory.masm index 5abfcc947..b20345512 100644 --- a/miden-lib/asm/kernels/transaction/lib/memory.masm +++ b/miden-lib/asm/kernels/transaction/lib/memory.masm @@ -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 # ------------------------------------------------------------------------------------------------- @@ -829,6 +832,23 @@ 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: [] diff --git a/miden-lib/asm/kernels/transaction/lib/prologue.masm b/miden-lib/asm/kernels/transaction/lib/prologue.masm index dd13f42cc..bc9072918 100644 --- a/miden-lib/asm/kernels/transaction/lib/prologue.masm +++ b/miden-lib/asm/kernels/transaction/lib/prologue.masm @@ -9,6 +9,12 @@ use.kernel::constants use.kernel::memory use.kernel::utils +# CONSTS +# ================================================================================================= + +# Max U32 value, used for initializing the expiration block delta +const.U32_MAX=0xFFFFFFFF + # ERRORS # ================================================================================================= @@ -1233,4 +1239,6 @@ export.prepare_transaction exec.process_input_notes_data exec.process_tx_script_root # => [] + + push.U32_MAX exec.memory::set_expiration_block_num end diff --git a/miden-lib/asm/kernels/transaction/lib/tx.masm b/miden-lib/asm/kernels/transaction/lib/tx.masm index 53799a10d..e67219de1 100644 --- a/miden-lib/asm/kernels/transaction/lib/tx.masm +++ b/miden-lib/asm/kernels/transaction/lib/tx.masm @@ -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 # ================================================================================================= @@ -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 1 and 255. +const.ERR_INVALID_TX_EXPIRATION_DELTA=0x00020055 + # EVENTS # ================================================================================================= @@ -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: [] @@ -178,6 +184,41 @@ 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_block_num + # Ensure block_height_delta is between 1 and 255 (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] + + # Check if block_height_delta is greater + lt + if.true + # Set new expiration delta + exec.memory::set_expiration_block_num + else + drop + 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 diff --git a/miden-lib/asm/miden/kernel_proc_offsets.masm b/miden-lib/asm/miden/kernel_proc_offsets.masm index 2f48ec6f2..da2053fdd 100644 --- a/miden-lib/asm/miden/kernel_proc_offsets.masm +++ b/miden-lib/asm/miden/kernel_proc_offsets.masm @@ -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 # ------------------------------------------------------------------------------------------------- @@ -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 @@ -258,6 +259,18 @@ export.get_block_number_offset push.GET_BLOCK_NUMBER_OFFSET end +#! Returns an offset of the `set_tx_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.set_tx_expiration_delta + push.SET_TX_EXPIRATION_DELTA_OFFSET +end + #! Returns an offset of the `get_block_hash` kernel procedure. #! #! Stack: [] diff --git a/miden-lib/src/transaction/memory.rs b/miden-lib/src/transaction/memory.rs index b5b33cce5..f8ee290de 100644 --- a/miden-lib/src/transaction/memory.rs +++ b/miden-lib/src/transaction/memory.rs @@ -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? | @@ -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 // ------------------------------------------------------------------------------------------------ diff --git a/miden-lib/src/transaction/mod.rs b/miden-lib/src/transaction/mod.rs index 623d6858d..7be9025e8 100644 --- a/miden-lib/src/transaction/mod.rs +++ b/miden-lib/src/transaction/mod.rs @@ -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; @@ -174,12 +175,15 @@ 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: @@ -187,22 +191,27 @@ impl TransactionKernel { /// - 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(), + ) + })?; + 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(), @@ -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 @@ -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. @@ -238,7 +250,8 @@ impl TransactionKernel { adv_map: &AdviceMap, output_notes: Vec, ) -> Result { - 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( @@ -258,7 +271,11 @@ impl TransactionKernel { )); } - Ok(TransactionOutputs { account, output_notes }) + Ok(TransactionOutputs { + account, + output_notes, + expiration_block_num, + }) } } diff --git a/miden-lib/src/transaction/outputs.rs b/miden-lib/src/transaction/outputs.rs index 3cf55a071..da3cfba80 100644 --- a/miden-lib/src/transaction/outputs.rs +++ b/miden-lib/src/transaction/outputs.rs @@ -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 // ================================================================================================ diff --git a/miden-lib/src/transaction/procedures/kernel_v0.rs b/miden-lib/src/transaction/procedures/kernel_v0.rs index f318b41e5..391cd8e5f 100644 --- a/miden-lib/src/transaction/procedures/kernel_v0.rs +++ b/miden-lib/src/transaction/procedures/kernel_v0.rs @@ -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 @@ -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!(0xc9582b7ff8edbd9f, 0xfa71377979938e5, 0x28d7c073f4cb7693, 0xa23523e23145949e), ]; diff --git a/miden-tx/src/errors/tx_kernel_errors.rs b/miden-tx/src/errors/tx_kernel_errors.rs index 96e831117..5e46aa7e9 100644 --- a/miden-tx/src/errors/tx_kernel_errors.rs +++ b/miden-tx/src/errors/tx_kernel_errors.rs @@ -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"), @@ -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."), ]; diff --git a/miden-tx/src/prover/mod.rs b/miden-tx/src/prover/mod.rs index 3452895d0..098ff3999 100644 --- a/miden-tx/src/prover/mod.rs +++ b/miden-tx/src/prover/mod.rs @@ -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) diff --git a/miden-tx/src/tests/kernel_tests/test_epilogue.rs b/miden-tx/src/tests/kernel_tests/test_epilogue.rs index 10256ed21..78e57af7c 100644 --- a/miden-tx/src/tests/kernel_tests/test_epilogue.rs +++ b/miden-tx/src/tests/kernel_tests/test_epilogue.rs @@ -1,4 +1,4 @@ -use alloc::vec::Vec; +use alloc::{string::ToString, vec::Vec}; use miden_lib::transaction::{ memory::{NOTE_MEM_SIZE, OUTPUT_NOTE_ASSET_HASH_OFFSET, OUTPUT_NOTE_SECTION_OFFSET}, @@ -8,12 +8,15 @@ use miden_objects::{ accounts::Account, transaction::{OutputNote, OutputNotes}, }; -use vm_processor::ONE; +use vm_processor::{ProcessState, ONE}; use super::{output_notes_data_procedure, ZERO}; use crate::{ assert_execution_error, - errors::tx_kernel_errors::{ERR_EPILOGUE_ASSETS_DONT_ADD_UP, ERR_NONCE_DID_NOT_INCREASE}, + errors::tx_kernel_errors::{ + ERR_EPILOGUE_ASSETS_DONT_ADD_UP, ERR_INVALID_TX_EXPIRATION_DELTA, + ERR_NONCE_DID_NOT_INCREASE, + }, testing::TransactionContextBuilder, tests::kernel_tests::read_root_mem_value, }; @@ -191,6 +194,80 @@ fn test_epilogue_asset_preservation_violation_too_many_fungible_input() { assert_execution_error!(process, ERR_EPILOGUE_ASSETS_DONT_ADD_UP); } +#[test] +fn test_block_expiration_height_monotonically_decreases() { + let tx_context = TransactionContextBuilder::with_standard_account(ONE).build(); + + let test_pairs: [(u64, u64); 3] = [(9, 12), (18, 3), (20, 20)]; + let code_template = " + use.kernel::prologue + use.kernel::tx + use.kernel::epilogue + + begin + exec.prologue::prepare_transaction + push.{value_1} + exec.tx::set_expiration_block_num + push.{value_2} + exec.tx::set_expiration_block_num + exec.epilogue::finalize_transaction + end + "; + + for (v1, v2) in test_pairs { + let code = &code_template + .replace("{value_1}", &v1.to_string()) + .replace("{value_2}", &v2.to_string()); + + let process = tx_context.execute_code(code).unwrap(); + + // 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; + assert_eq!(process.get_stack_item(8).as_int(), expected_expiry); + } +} + +#[test] +fn test_invalid_expiration_deltas() { + let tx_context = TransactionContextBuilder::with_standard_account(ONE).build(); + + let test_values = [0u64, u16::MAX as u64 + 1, u32::MAX as u64]; + let code_template = " + use.kernel::tx + + begin + push.{value_1} + exec.tx::set_expiration_block_num + end + "; + + for value in test_values { + let code = &code_template.replace("{value_1}", &value.to_string()); + let process = tx_context.execute_code(code); + + assert_execution_error!(process, ERR_INVALID_TX_EXPIRATION_DELTA); + } +} + +#[test] +fn test_no_expiration_delta_set() { + let tx_context = TransactionContextBuilder::with_standard_account(ONE).build(); + + let code_template = " + use.kernel::prologue + use.kernel::epilogue + + begin + exec.prologue::prepare_transaction + exec.epilogue::finalize_transaction + end + "; + let process = tx_context.execute_code(code_template).unwrap(); + // Default value should be equal to u32::max, set in the prologue + assert_eq!(process.get_stack_item(8).as_int() as u32, u32::MAX); +} + #[test] fn test_epilogue_increment_nonce_success() { let tx_context = TransactionContextBuilder::with_standard_account(ONE) diff --git a/objects/src/transaction/outputs.rs b/objects/src/transaction/outputs.rs index 3d38a3ae8..8de8e2db1 100644 --- a/objects/src/transaction/outputs.rs +++ b/objects/src/transaction/outputs.rs @@ -15,8 +15,12 @@ use crate::{ /// Describes the result of executing a transaction. #[derive(Debug, Clone, PartialEq, Eq)] pub struct TransactionOutputs { + /// Information related to the account's final state. pub account: AccountHeader, + /// Set of output notes created by the transaction. pub output_notes: OutputNotes, + /// Defines up to which block the transaction is considered valid. + pub expiration_block_num: u32, } // OUTPUT NOTES diff --git a/objects/src/transaction/proven_tx.rs b/objects/src/transaction/proven_tx.rs index 1ba8faa19..6646487ac 100644 --- a/objects/src/transaction/proven_tx.rs +++ b/objects/src/transaction/proven_tx.rs @@ -36,6 +36,9 @@ pub struct ProvenTransaction { /// The block hash of the last known block at the time the transaction was executed. block_ref: Digest, + /// The block number by which the transaction will expire, as defined by the executed scripts. + expiration_block_num: u32, + /// A STARK proof that attests to the correct execution of the transaction. proof: ExecutionProof, } @@ -145,6 +148,7 @@ impl Serializable for ProvenTransaction { self.input_notes.write_into(target); self.output_notes.write_into(target); self.block_ref.write_into(target); + self.expiration_block_num.write_into(target); self.proof.write_into(target); } } @@ -157,6 +161,7 @@ impl Deserializable for ProvenTransaction { let output_notes = OutputNotes::read_from(source)?; let block_ref = Digest::read_from(source)?; + let expiration_block_num = u32::read_from(source)?; let proof = ExecutionProof::read_from(source)?; let id = TransactionId::new( @@ -172,6 +177,7 @@ impl Deserializable for ProvenTransaction { input_notes, output_notes, block_ref, + expiration_block_num, proof, }; @@ -208,6 +214,9 @@ pub struct ProvenTransactionBuilder { /// Block [Digest] of the transaction's reference block. block_ref: Digest, + /// The block number by which the transaction will expire, as defined by the executed scripts. + expiration_block_num: u32, + /// A STARK proof that attests to the correct execution of the transaction. proof: ExecutionProof, } @@ -222,6 +231,7 @@ impl ProvenTransactionBuilder { initial_account_hash: Digest, final_account_hash: Digest, block_ref: Digest, + expiration_block_num: u32, proof: ExecutionProof, ) -> Self { Self { @@ -232,6 +242,7 @@ impl ProvenTransactionBuilder { input_notes: Vec::new(), output_notes: Vec::new(), block_ref, + expiration_block_num, proof, } } @@ -294,6 +305,7 @@ impl ProvenTransactionBuilder { input_notes, output_notes, block_ref: self.block_ref, + expiration_block_num: self.expiration_block_num, proof: self.proof, };