Skip to content

Commit c3bd8c6

Browse files
authored
Merge pull request #3117 from ProvableHQ/feat/try-get-transaction
[Feature] Add `Ledger::try_get_*` for transmissions
2 parents b0dd5c5 + 595e7d0 commit c3bd8c6

5 files changed

Lines changed: 142 additions & 50 deletions

File tree

ledger/query/src/query.rs

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -201,10 +201,9 @@ impl<N: Network, B: BlockStorage<N>> Query<N, B> {
201201
/// Returns the transaction for the given transaction ID.
202202
pub fn get_transaction(&self, transaction_id: &N::TransactionID) -> Result<Transaction<N>> {
203203
match self {
204-
Self::VM(block_store) => {
205-
let txn = block_store.get_transaction(transaction_id)?;
206-
txn.ok_or_else(|| anyhow!("Transaction {transaction_id} not in local storage"))
207-
}
204+
Self::VM(block_store) => block_store
205+
.get_transaction(transaction_id)?
206+
.ok_or_else(|| anyhow!("Missing transaction '{transaction_id}' in block storage")),
208207
Self::REST(query) => query.get_transaction(transaction_id),
209208
Self::STATIC(_query) => bail!("get_transaction is not supported by StaticQuery"),
210209
}
@@ -214,10 +213,9 @@ impl<N: Network, B: BlockStorage<N>> Query<N, B> {
214213
#[cfg(feature = "async")]
215214
pub async fn get_transaction_async(&self, transaction_id: &N::TransactionID) -> Result<Transaction<N>> {
216215
match self {
217-
Self::VM(block_store) => {
218-
let txn = block_store.get_transaction(transaction_id)?;
219-
txn.ok_or_else(|| anyhow!("Transaction {transaction_id} not in local storage"))
220-
}
216+
Self::VM(block_store) => block_store
217+
.get_transaction(transaction_id)?
218+
.ok_or_else(|| anyhow!("Missing transaction '{transaction_id}' in block storage")),
221219
Self::REST(query) => query.get_transaction_async(transaction_id).await,
222220
Self::STATIC(_query) => bail!("get_transaction is not supported by StaticQuery"),
223221
}

ledger/src/get.rs

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
use super::*;
1717

18+
// Getters for `Ledger`.
1819
impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
1920
/// Returns the committee for the given `block height`.
2021
pub fn get_committee(&self, block_height: u32) -> Result<Option<Committee<N>>> {
@@ -223,31 +224,46 @@ impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
223224

224225
/// Returns the transaction for the given transaction ID.
225226
pub fn get_transaction(&self, transaction_id: N::TransactionID) -> Result<Transaction<N>> {
226-
// Retrieve the transaction.
227227
match self.vm.block_store().get_transaction(&transaction_id)? {
228228
Some(transaction) => Ok(transaction),
229-
None => bail!("Missing transaction for ID {transaction_id}"),
229+
None => bail!("Missing transaction '{transaction_id}' in block storage"),
230230
}
231231
}
232232

233+
/// Returns the transaction for the given transaction ID, or `None` if no transaction of this ID exists.
234+
pub fn try_get_transaction(&self, transaction_id: &N::TransactionID) -> Result<Option<Transaction<N>>> {
235+
self.vm.block_store().get_transaction(transaction_id)
236+
}
237+
233238
/// Returns the confirmed transaction for the given transaction ID.
234239
pub fn get_confirmed_transaction(&self, transaction_id: N::TransactionID) -> Result<ConfirmedTransaction<N>> {
235-
// Retrieve the confirmed transaction.
236-
match self.vm.block_store().get_confirmed_transaction(&transaction_id)? {
240+
match self.try_get_confirmed_transaction(&transaction_id)? {
237241
Some(confirmed_transaction) => Ok(confirmed_transaction),
238242
None => bail!("Missing confirmed transaction for ID {transaction_id}"),
239243
}
240244
}
241245

246+
/// Returns the confirmed transaction for the given transaction ID, or `None` if no confirmed transaction of this ID exists.
247+
pub fn try_get_confirmed_transaction(
248+
&self,
249+
transaction_id: &N::TransactionID,
250+
) -> Result<Option<ConfirmedTransaction<N>>> {
251+
self.vm.block_store().get_confirmed_transaction(transaction_id)
252+
}
253+
242254
/// Returns the unconfirmed transaction for the given `transaction ID`.
243255
pub fn get_unconfirmed_transaction(&self, transaction_id: &N::TransactionID) -> Result<Transaction<N>> {
244-
// Retrieve the unconfirmed transaction.
245-
match self.vm.block_store().get_unconfirmed_transaction(transaction_id)? {
256+
match self.try_get_unconfirmed_transaction(transaction_id)? {
246257
Some(unconfirmed_transaction) => Ok(unconfirmed_transaction),
247258
None => bail!("Missing unconfirmed transaction for ID {transaction_id}"),
248259
}
249260
}
250261

262+
/// Returns the unconfirmed transaction for the given transaction ID, or `None` if no unconfirmed transaction of this ID exists.
263+
pub fn try_get_unconfirmed_transaction(&self, transaction_id: &N::TransactionID) -> Result<Option<Transaction<N>>> {
264+
self.vm.block_store().get_unconfirmed_transaction(transaction_id)
265+
}
266+
251267
/// Returns the latest edition for the given `program ID`.
252268
pub fn get_latest_edition_for_program(&self, program_id: &ProgramID<N>) -> Result<u16> {
253269
match self.vm.block_store().get_latest_edition_for_program(program_id)? {
@@ -295,6 +311,14 @@ impl<N: Network, C: ConsensusStorage<N>> Ledger<N, C> {
295311

296312
/// Returns the solution for the given solution ID.
297313
pub fn get_solution(&self, solution_id: &SolutionID<N>) -> Result<Solution<N>> {
314+
match self.try_get_solution(solution_id)? {
315+
Some(solution) => Ok(solution),
316+
None => bail!("Missing solution for ID {solution_id}"),
317+
}
318+
}
319+
320+
/// Returns the solution for the given solution ID, or `None` if no solution of this ID exists.
321+
pub fn try_get_solution(&self, solution_id: &SolutionID<N>) -> Result<Option<Solution<N>>> {
298322
self.vm.block_store().get_solution(solution_id)
299323
}
300324

ledger/src/tests.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1461,6 +1461,36 @@ function create_duplicate_record:
14611461
assert!(!partially_verified_transaction.contains(&deployment_3_cache_key));
14621462
}
14631463

1464+
// Tests that `try_get_*' returns `None` if the tranmissions does not exist.
1465+
#[test]
1466+
fn test_get_transaction() {
1467+
let rng = &mut TestRng::default();
1468+
let ledger = crate::test_helpers::sample_test_env(rng).ledger;
1469+
1470+
// Generate a random transaction ID.
1471+
let transaction = crate::test_helpers::sample_deployment_transaction(1, 0, true, rng);
1472+
let transaction_id = transaction.id();
1473+
1474+
assert_eq!(ledger.try_get_transaction(&transaction_id).unwrap(), None);
1475+
assert_eq!(ledger.try_get_confirmed_transaction(&transaction_id).unwrap(), None);
1476+
assert_eq!(ledger.try_get_unconfirmed_transaction(&transaction_id).unwrap(), None);
1477+
1478+
assert!(ledger.get_transaction(transaction_id).is_err());
1479+
assert!(ledger.get_confirmed_transaction(transaction_id).is_err());
1480+
assert!(ledger.get_unconfirmed_transaction(&transaction_id).is_err());
1481+
1482+
// Insert the transaction as unconfirmed into the ledger.
1483+
ledger.vm().transaction_store().insert(&transaction).unwrap();
1484+
1485+
assert!(ledger.try_get_transaction(&transaction_id).unwrap().is_some());
1486+
assert_eq!(ledger.try_get_confirmed_transaction(&transaction_id).unwrap(), None);
1487+
assert!(ledger.try_get_unconfirmed_transaction(&transaction_id).unwrap().is_some());
1488+
1489+
assert!(ledger.get_transaction(transaction_id).is_ok());
1490+
assert!(ledger.get_confirmed_transaction(transaction_id).is_err());
1491+
assert!(ledger.get_unconfirmed_transaction(&transaction_id).is_ok());
1492+
}
1493+
14641494
#[test]
14651495
fn test_execute_duplicate_transition_ids() {
14661496
let rng = &mut TestRng::default();

ledger/store/src/block/mod.rs

Lines changed: 73 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -809,26 +809,35 @@ pub trait BlockStorage<N: Network>: 'static + Clone + Send + Sync {
809809
Ok(solutions.into_owned())
810810
}
811811

812-
/// Returns the prover solution for the given solution ID.
813-
fn get_solution(&self, solution_id: &SolutionID<N>) -> Result<Solution<N>> {
812+
/// Returns the prover solution for the given solution ID, or `None` if no reference to this solution
813+
/// exists in the ledger.
814+
fn get_solution(&self, solution_id: &SolutionID<N>) -> Result<Option<Solution<N>>> {
814815
// Retrieve the block height for the solution ID.
815816
let Some(block_height) = self.find_block_height_from_solution_id(solution_id)? else {
816-
bail!("The block height for solution ID '{solution_id}' is missing in block storage")
817+
// In this case, the solution is not yet known to the ledger.
818+
return Ok(None);
817819
};
818-
// Retrieve the block hash.
820+
821+
// Errors below are more severe, as it measn there is a reference to solution, but
822+
// the solution itself is missing.
823+
824+
// Get the block hash for the given height.
819825
let Some(block_hash) = self.get_block_hash(block_height)? else {
820826
bail!("The block hash for block '{block_height}' is missing in block storage")
821827
};
822-
// Retrieve the solutions.
828+
829+
// Get the solutions for the block.
823830
let Some(solutions) = self.solutions_map().get_confirmed(&block_hash)? else {
824831
bail!("The solutions for block '{block_height}' are missing in block storage")
825832
};
833+
826834
// Retrieve the prover solution.
827-
match solutions.deref().deref() {
828-
Some(solutions) => solutions.get(solution_id).cloned().ok_or_else(|| {
829-
anyhow!("The prover solution for solution ID '{solution_id}' is missing in block storage")
830-
}),
831-
_ => bail!("The prover solution for solution ID '{solution_id}' is missing in block storage"),
835+
if let Some(solutions) = solutions.deref().deref()
836+
&& let Some(solution) = solutions.get(solution_id).cloned()
837+
{
838+
Ok(Some(solution))
839+
} else {
840+
bail!("The prover solution for solution ID '{solution_id}' is missing in block storage");
832841
}
833842
}
834843

@@ -846,7 +855,7 @@ pub trait BlockStorage<N: Network>: 'static + Clone + Send + Sync {
846855
// Retrieve the transactions.
847856
transaction_ids
848857
.iter()
849-
.map(|transaction_id| self.get_confirmed_transaction(*transaction_id))
858+
.map(|transaction_id| self.get_confirmed_transaction(transaction_id))
850859
.collect::<Result<Option<Transactions<_>>>>()
851860
}
852861

@@ -855,7 +864,7 @@ pub trait BlockStorage<N: Network>: 'static + Clone + Send + Sync {
855864
Ok(self.aborted_transaction_ids_map().get_confirmed(block_hash)?.map(|x| x.into_owned()))
856865
}
857866

858-
/// Returns the transaction for the given `TransactionID`.
867+
/// Returns the transaction for the given `TransactionID`, or `None` if no transaction of this ID exists.
859868
fn get_transaction(&self, transaction_id: &N::TransactionID) -> Result<Option<Transaction<N>>> {
860869
// Check if the transaction was rejected or aborted.
861870
// Note: We can only retrieve accepted or rejected transactions. We cannot retrieve aborted transactions.
@@ -868,37 +877,45 @@ pub trait BlockStorage<N: Network>: 'static + Clone + Send + Sync {
868877

869878
let Some(confirmed) = transactions.find_confirmed_transaction_for_unconfirmed_transaction_id(transaction_id)
870879
else {
871-
if let Some(aborted_ids) = self.get_block_aborted_transaction_ids(&block_hash)? {
872-
if aborted_ids.contains(transaction_id) {
873-
bail!("Transaction '{transaction_id}' was aborted in block '{block_hash}'");
874-
}
880+
if let Some(aborted_ids) = self.get_block_aborted_transaction_ids(&block_hash)?
881+
&& aborted_ids.contains(transaction_id)
882+
{
883+
bail!("Transaction '{transaction_id}' was aborted in block '{block_hash}'");
884+
} else {
885+
return Ok(None);
875886
}
876-
bail!("Missing transaction '{transaction_id}' in block storage");
877887
};
878888
Ok(Some(confirmed.transaction().clone()))
879889
}
880890

881-
/// Returns the confirmed transaction for the given `transaction ID`.
882-
fn get_confirmed_transaction(&self, transaction_id: N::TransactionID) -> Result<Option<ConfirmedTransaction<N>>> {
891+
/// Returns the confirmed transaction for the given `transaction ID`, or `None` if no confirmed transaction of this ID exists.
892+
fn get_confirmed_transaction(&self, transaction_id: &N::TransactionID) -> Result<Option<ConfirmedTransaction<N>>> {
883893
// Retrieve the transaction.
884-
let Some(transaction) = self.get_transaction(&transaction_id)? else {
885-
bail!("Missing transaction '{transaction_id}' in block storage");
894+
let Some(transaction) = self.get_transaction(transaction_id)? else {
895+
return Ok(None);
886896
};
897+
887898
// Retrieve the confirmed attributes.
888899
let Some((_, confirmed_type, finalize_operations)) =
889900
self.confirmed_transactions_map().get_confirmed(&transaction.id())?.map(|x| x.into_owned())
890901
else {
891-
bail!("Missing confirmed transaction '{transaction_id}' in block storage")
902+
return Ok(None);
892903
};
904+
893905
// Construct the confirmed transaction.
894906
to_confirmed_transaction(confirmed_type, transaction, finalize_operations).map(Some)
895907
}
896908

897-
/// Get the unconfirmed transaction for the given `TransactionID`.
909+
/// Retrieve an unconfirmed transaction using its ID.
898910
///
899911
/// For unconfirmed and accepted transactions, this will return original transaction issued by the client.
900912
/// This function also returns the original execution/deployment for a rejected transaction,
901913
/// even when the given `TransactionID` is of a fee transaction.
914+
///
915+
/// # Returns
916+
/// - `Ok(txn)` if the transaction exists and is not confirmed
917+
/// - `Ok(None)` if no such unconfirmed transaction exist
918+
/// - `Err(_)` if any other error occured (most likely a storage corruption)
902919
fn get_unconfirmed_transaction(&self, transaction_id: &N::TransactionID) -> Result<Option<Transaction<N>>> {
903920
// Check if the transaction was rejected or aborted.
904921
// Note: We can only retrieve accepted or rejected transactions. We cannot retrieve aborted transactions.
@@ -907,10 +924,13 @@ pub trait BlockStorage<N: Network>: 'static + Clone + Send + Sync {
907924
Some(transactions) => {
908925
match transactions.find_confirmed_transaction_for_unconfirmed_transaction_id(transaction_id) {
909926
Some(confirmed) => Ok(Some(confirmed.to_unconfirmed_transaction()?)),
910-
None => bail!("Missing transaction '{transaction_id}' in block storage"),
927+
None => Ok(None),
911928
}
912929
}
913-
None => bail!("Missing transactions for block '{block_hash}' in block storage"),
930+
// This is an error, because there must always be a transactions entry for a known block hash.
931+
None => bail!(
932+
"Transaction '{transaction_id}' is associated with a block '{block_hash}', but no transactions entry exists for it"
933+
),
914934
},
915935
None => {
916936
let Some(txn) = self.transaction_store().get_transaction(transaction_id)? else {
@@ -921,12 +941,15 @@ pub trait BlockStorage<N: Network>: 'static + Clone + Send + Sync {
921941
if let Transaction::Fee(_, fee) = txn {
922942
// Look up the original transaction in its block.
923943
let Some(block_hash) = self.find_block_hash(transaction_id)? else {
924-
bail!("Missing fee transaction '{transaction_id}' in block storage");
944+
// This is an error, because a fee transaction must always have an original transaction associated with it.
945+
bail!("Transaction {transaction_id} is a fee transaction with no associated block");
925946
};
926947

927948
match self.get_block_transactions(&block_hash)? {
928949
Some(transactions) => transactions.find_unconfirmed_transaction_for_transition_id(fee.id()),
929-
None => bail!("Missing transactions for block '{block_hash}' in block storage"),
950+
None => bail!(
951+
"Transaction {transaction_id} is associated with block '{block_hash}' but no transacitons entry exists for it"
952+
),
930953
}
931954
} else {
932955
Ok(Some(txn))
@@ -1365,7 +1388,7 @@ impl<N: Network, B: BlockStorage<N>> BlockStore<N, B> {
13651388
}
13661389

13671390
/// Returns the prover solution for the given solution ID.
1368-
pub fn get_solution(&self, solution_id: &SolutionID<N>) -> Result<Solution<N>> {
1391+
pub fn get_solution(&self, solution_id: &SolutionID<N>) -> Result<Option<Solution<N>>> {
13691392
self.storage.get_solution(solution_id)
13701393
}
13711394

@@ -1382,24 +1405,41 @@ impl<N: Network, B: BlockStorage<N>> BlockStore<N, B> {
13821405
self.storage.get_block_aborted_transaction_ids(block_hash)
13831406
}
13841407

1385-
/// Returns the transaction for the given `transaction ID`.
1408+
/// Retrieve a transaction using its ID.
13861409
///
13871410
/// For a rejected transaction, this returns the fee transaction, not the original/unconfirmed one.
1411+
///
1412+
/// # Returns
1413+
/// - `Ok(txn)` if the transaction exists
1414+
/// - `Ok(None)` if no such transaction exist
1415+
/// - `Err(_)` if any other error occured
1416+
///
13881417
pub fn get_transaction(&self, transaction_id: &N::TransactionID) -> Result<Option<Transaction<N>>> {
13891418
self.storage.get_transaction(transaction_id)
13901419
}
13911420

1392-
/// Returns the confirmed transaction for the given `transaction ID`.
1421+
/// Retreive a confirmed transation using its ID.
1422+
///
1423+
/// # Returns
1424+
/// - `Ok(txn)` if the transaction exists
1425+
/// - `Ok(None)` if no such confirmed transaction exist
1426+
/// - `Err(_)` if no such transaction exist or any other error occured
13931427
pub fn get_confirmed_transaction(
13941428
&self,
13951429
transaction_id: &N::TransactionID,
13961430
) -> Result<Option<ConfirmedTransaction<N>>> {
1397-
self.storage.get_confirmed_transaction(*transaction_id)
1431+
self.storage.get_confirmed_transaction(transaction_id)
13981432
}
13991433

1400-
/// Returns the unconfirmed transaction for the given `transaction ID`.
1401-
///
1434+
/// Retrieve an unconfirmed transaction using its ID.
1435+
///
14021436
/// For a rejected transaction, this returns the origin transaction issued by the user, not the fee transaction.
1437+
///
1438+
/// # Returns
1439+
/// - `Ok(txn)` if the transaction exists and is not confirmed
1440+
/// - `Ok(None)` if no such unconfirmed transaction exist
1441+
/// - `Err(_)` if any other error occured
1442+
///
14031443
pub fn get_unconfirmed_transaction(&self, transaction_id: &N::TransactionID) -> Result<Option<Transaction<N>>> {
14041444
self.storage.get_unconfirmed_transaction(transaction_id)
14051445
}

synthesizer/src/vm/verify.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -745,10 +745,10 @@ impl<N: Network, C: ConsensusStorage<N>> VM<N, C> {
745745
let result = match verification {
746746
Ok(()) => match self.block_store().contains_state_root(&fee.global_state_root()) {
747747
Ok(true) => Ok(()),
748-
Ok(false) => bail!("Fee verification failed: global state root not found"),
749-
Err(error) => bail!("Fee verification failed: {error}"),
748+
Ok(false) => bail!("Fee verification failed - State root {} not found", fee.global_state_root()),
749+
Err(error) => bail!("Fee verification failed - Storage error - {error}"),
750750
},
751-
Err(error) => bail!("Fee verification failed: {error}"),
751+
Err(error) => bail!("Fee verification failed - {error}"),
752752
};
753753
finish!(timer, "Check the global state root");
754754
result

0 commit comments

Comments
 (0)