Skip to content
Draft
Show file tree
Hide file tree
Changes from 2 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
87 changes: 87 additions & 0 deletions ledger/src/get.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@

use super::*;

use crate::store::TransactionType;

impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
/// Returns the committee for the given `block height`.
pub fn get_committee(&self, block_height: u32) -> Result<Option<Committee<N>>> {
Expand Down Expand Up @@ -378,4 +380,89 @@ impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
_ => bail!("Invalid bond_state in finalize storage."),
}
}

/// Returns a tuple containing the number of all the input records and all the output records.
pub fn get_record_count(&self) -> (usize, usize) {
Copy link
Collaborator

@vicsn vicsn Dec 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: a struct with named fields will save developers of dependents a lot of reasoning work - I still regret our *_cost* functions returning tuples.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, though the place for these structs would be snarkOS, no? Or is it ok if we include "target" objects in this module?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well we define the functions here... So we need it here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

let transition_store = self.vm.block_store().transition_store();
let num_input_records = transition_store.input_store().record_map().len_confirmed();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These seem quite expensive, as it's a O(N) scan for each len_confirmed call.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd call it somewhat expensive - in all likelihood, it's the other call that's the more expensive one due to the number of queries involved; obtaining the number of records for a given map is O(N), but it's an optimized operation.

let num_output_records = transition_store.output_store().record_map().len_confirmed();
(num_input_records, num_output_records)
}

/// Returns the list of input and output records applicable to the given block height.
pub fn get_num_block_records(&self, height: u32) -> Result<(usize, usize)> {
let block_store = self.vm.block_store();
let block_hash = match block_store.get_block_hash(height)? {
Some(block_hash) => block_hash,
None => bail!("Block {height} does not exist in storage"),
};

let Some(block_transaction_ids) = block_store.transactions_map().get_confirmed(&block_hash)? else {
return Ok(Default::default());
};

let transaction_store = block_store.transaction_store();
let mut transaction_ids_with_type = Vec::with_capacity(block_transaction_ids.len());
for tx_id in block_transaction_ids.iter() {
let Some(tx_ty) = transaction_store.id_map().get_confirmed(tx_id)? else {
bail!("Missing type for transaction {tx_id}");
};
transaction_ids_with_type.push((*tx_id, tx_ty));
}

let transition_store = transaction_store.transition_store();
let execution_store = transaction_store.execution_store();
let fee_store = transaction_store.fee_store();
let input_store = transition_store.input_store();
let output_store = transition_store.output_store();

let mut num_input_records = 0usize;
let mut num_output_records = 0usize;

let mut process_transition_ids = |transition_ids: &[N::TransitionID]| -> Result<()> {
for transition_id in transition_ids {
let input_ids = transition_store.get_input_ids(transition_id)?;
let output_ids = transition_store.get_output_ids(transition_id)?;

for id in input_ids {
if input_store.record_map().contains_key_confirmed(&id)? {
num_input_records += 1;
}
}
for id in output_ids {
if output_store.record_map().contains_key_confirmed(&id)? {
num_output_records += 1;
}
}
Comment on lines +427 to +436
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider unit testing or explicitly E2E testing the different cases

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be happy to do so; is there any existing test that guarantees the existence of records, which I could repurpose for record counting checks?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually ongoing work by @Antonio95 :)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @ljedrz ! For some more context, what are your testing needs?

I recently added some functionality that I wanted to specifically test (among other cases) on transactions involving transitions which consumed records. (Extra background: I went through the test cases in synthesizer/tests/test_vm_execute_and_finalize.rs and saw the only one with a chance of record consumption was synthesizer/tests/tests/vm/execute_and_finalize/mint_and_split.aleo. I modified the inputs to correctly consume one and my tested function did behave correctly; however, a new commit shortly after broke that so that my function was no longer being tested under record consumption. We suspect even if we fix the mint_and_split.aleo test case again, it will soon break.) In summary, I couldn't find any high-level tests which reliably involved record consumption.

As part of the dynamic dispatch work, we are adding many tests involving record consumption. Most test cases in the subfolders of https://github.com/ProvableHQ/snarkVM/blob/3a67e6a1f2091500d2a0a4778bfa0c51495ae1fb/synthesizer/src/vm/tests/test_v12 do (this is all happening in the feat/dynamic-dispatch-extension-case1wip branch). Cf. e. g.

fn test_execution_cost_for_authorization() {
for a specific example.

That code will likely not be merged for a few weeks, so if you want to add tests before that you can take inspiration from what I sent, or I'd also be happy to write a test case if you tell me what you need in a bit more detail :)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

}

Ok(())
};

for (tx_id, tx_ty) in transaction_ids_with_type {
match *tx_ty {
TransactionType::Deploy => {
continue;
}
TransactionType::Execute => {
let Some(transition_ids_w_fee) = execution_store.id_map().get_confirmed(&tx_id)? else {
continue;
};
let (transition_ids, _fee) = &*transition_ids_w_fee;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we count I/O records in the _fee?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fee records should be counted by the loop in L455 - the fee in this particular map is only an indicator if there was a fee associated with the related execution and its transitions.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm I'm not familiar with the database schema, but Fee transactions are different from Fee objects used in Execute or Deploy transactions.


process_transition_ids(transition_ids)?;
}
TransactionType::Fee => {
let Some(transition_id_w_root_and_proof) = fee_store.fee_map().get_confirmed(&tx_id)? else {
continue;
};
let (transition_id, _root, _proof) = &*transition_id_w_root_and_proof;

process_transition_ids(&[*transition_id])?;
}
}
}

Ok((num_input_records, num_output_records))
}
}
2 changes: 1 addition & 1 deletion ledger/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ use snarkvm_ledger_committee::Committee;
use snarkvm_ledger_narwhal::{BatchCertificate, Subdag, Transmission, TransmissionID};
use snarkvm_ledger_puzzle::{Puzzle, PuzzleSolutions, Solution, SolutionID};
use snarkvm_ledger_query::QueryTrait;
use snarkvm_ledger_store::{ConsensusStorage, ConsensusStore};
use snarkvm_ledger_store::{ConsensusStorage, ConsensusStore, helpers::MapRead};
use snarkvm_synthesizer::{
program::{FinalizeGlobalState, Program},
vm::VM,
Expand Down
5 changes: 5 additions & 0 deletions ledger/store/src/block/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1204,6 +1204,11 @@ impl<N: Network, B: BlockStorage<N>> BlockStore<N, B> {
pub fn backup_database<P: AsRef<std::path::Path>>(&self, path: P) -> Result<(), String> {
self.storage.backup_database(path)
}

/// Returns a reference to the transactions map.
pub fn transactions_map(&self) -> &B::TransactionsMap {
self.storage.transactions_map()
}
}

impl<N: Network, B: BlockStorage<N>> BlockStore<N, B> {
Expand Down
5 changes: 5 additions & 0 deletions ledger/store/src/transaction/deployment.rs
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,11 @@ impl<N: Network, D: DeploymentStorage<N>> DeploymentStore<N, D> {
pub fn storage_mode(&self) -> &StorageMode {
self.storage.storage_mode()
}

/// Returns a reference to the program map.
pub fn program_map(&self) -> &D::ProgramMap {
self.storage.program_map()
}
}

impl<N: Network, D: DeploymentStorage<N>> DeploymentStore<N, D> {
Expand Down
5 changes: 5 additions & 0 deletions ledger/store/src/transaction/execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -355,6 +355,11 @@ impl<N: Network, E: ExecutionStorage<N>> ExecutionStore<N, E> {
pub fn storage_mode(&self) -> &StorageMode {
self.storage.storage_mode()
}

/// Returns a reference to the ID map.
pub fn id_map(&self) -> &E::IDMap {
self.storage.id_map()
}
}

impl<N: Network, E: ExecutionStorage<N>> ExecutionStore<N, E> {
Expand Down
5 changes: 5 additions & 0 deletions ledger/store/src/transaction/fee.rs
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,11 @@ impl<N: Network, F: FeeStorage<N>> FeeStore<N, F> {
pub fn storage_mode(&self) -> &StorageMode {
self.storage.storage_mode()
}

/// Returns a reference to the fee map.
pub fn fee_map(&self) -> &F::FeeMap {
self.storage.fee_map()
}
}

impl<N: Network, F: FeeStorage<N>> FeeStore<N, F> {
Expand Down
15 changes: 15 additions & 0 deletions ledger/store/src/transaction/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,21 @@ impl<N: Network, T: TransactionStorage<N>> TransactionStore<N, T> {
pub fn storage_mode(&self) -> &StorageMode {
self.storage.storage_mode()
}

/// Returns a reference to the transaction ID map.
pub fn id_map(&self) -> &T::IDMap {
self.storage.id_map()
}

/// Returns a reference to the execution store.
pub fn execution_store(&self) -> &ExecutionStore<N, T::ExecutionStorage> {
self.storage.execution_store()
}

/// Returns a reference to the fee store.
pub fn fee_store(&self) -> &FeeStore<N, T::FeeStorage> {
self.storage.fee_store()
}
}

impl<N: Network, T: TransactionStorage<N>> TransactionStore<N, T> {
Expand Down
5 changes: 5 additions & 0 deletions ledger/store/src/transition/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -384,6 +384,11 @@ impl<N: Network, I: InputStorage<N>> InputStore<N, I> {
pub fn storage_mode(&self) -> &StorageMode {
self.storage.storage_mode()
}

/// Returns a reference to the map of the records.
pub fn record_map(&self) -> &I::RecordMap {
&self.record
}
}

impl<N: Network, I: InputStorage<N>> InputStore<N, I> {
Expand Down
10 changes: 10 additions & 0 deletions ledger/store/src/transition/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,16 @@ impl<N: Network, T: TransitionStorage<N>> TransitionStore<N, T> {
pub fn storage_mode(&self) -> &StorageMode {
self.storage.storage_mode()
}

/// Returns a reference to the input store.
pub fn input_store(&self) -> &InputStore<N, T::InputStorage> {
&self.inputs
}

/// Returns a reference to the output store.
pub fn output_store(&self) -> &OutputStore<N, T::OutputStorage> {
&self.outputs
}
}

impl<N: Network, T: TransitionStorage<N>> TransitionStore<N, T> {
Expand Down
5 changes: 5 additions & 0 deletions ledger/store/src/transition/output.rs
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,11 @@ impl<N: Network, O: OutputStorage<N>> OutputStore<N, O> {
pub fn storage_mode(&self) -> &StorageMode {
self.storage.storage_mode()
}

/// Returns a reference to the map of the records.
pub fn record_map(&self) -> &O::RecordMap {
&self.record
}
}

impl<N: Network, O: OutputStorage<N>> OutputStore<N, O> {
Expand Down