diff --git a/tap_core/src/adapters/mock/receipt_checks_adapter_mock.rs b/tap_core/src/adapters/mock/receipt_checks_adapter_mock.rs index eaccbfa7..3dd0f4d7 100644 --- a/tap_core/src/adapters/mock/receipt_checks_adapter_mock.rs +++ b/tap_core/src/adapters/mock/receipt_checks_adapter_mock.rs @@ -67,7 +67,7 @@ impl ReceiptChecksAdapter for ReceiptChecksAdapterMock { Ok(receipt_storage .iter() .all(|(stored_receipt_id, stored_receipt)| { - (stored_receipt.signed_receipt.message != receipt.message) + (stored_receipt.signed_receipt().message != receipt.message) || *stored_receipt_id == receipt_id })) } diff --git a/tap_core/src/adapters/mock/receipt_storage_adapter_mock.rs b/tap_core/src/adapters/mock/receipt_storage_adapter_mock.rs index d4bbe6fe..af444c51 100644 --- a/tap_core/src/adapters/mock/receipt_storage_adapter_mock.rs +++ b/tap_core/src/adapters/mock/receipt_storage_adapter_mock.rs @@ -7,7 +7,9 @@ use async_trait::async_trait; use tokio::sync::RwLock; use crate::{ - adapters::receipt_storage_adapter::{safe_truncate_receipts, ReceiptRead, ReceiptStore}, + adapters::receipt_storage_adapter::{ + safe_truncate_receipts, ReceiptRead, ReceiptStore, StoredReceipt, + }, tap_receipt::ReceivedReceipt, }; @@ -44,7 +46,7 @@ impl ReceiptStorageAdapterMock { Ok(receipt_storage .iter() .filter(|(_, rx_receipt)| { - rx_receipt.signed_receipt.message.timestamp_ns == timestamp_ns + rx_receipt.signed_receipt().message.timestamp_ns == timestamp_ns }) .map(|(&id, rx_receipt)| (id, rx_receipt.clone())) .collect()) @@ -52,7 +54,7 @@ impl ReceiptStorageAdapterMock { pub async fn retrieve_receipts_upto_timestamp( &self, timestamp_ns: u64, - ) -> Result, AdapterErrorMock> { + ) -> Result, AdapterErrorMock> { self.retrieve_receipts_in_timestamp_range(..=timestamp_ns, None) .await } @@ -117,7 +119,7 @@ impl ReceiptStore for ReceiptStorageAdapterMock { ) -> Result<(), Self::AdapterError> { let mut receipt_storage = self.receipt_storage.write().await; receipt_storage.retain(|_, rx_receipt| { - !timestamp_ns.contains(&rx_receipt.signed_receipt.message.timestamp_ns) + !timestamp_ns.contains(&rx_receipt.signed_receipt().message.timestamp_ns) }); Ok(()) } @@ -130,12 +132,12 @@ impl ReceiptRead for ReceiptStorageAdapterMock { &self, timestamp_range_ns: R, limit: Option, - ) -> Result, Self::AdapterError> { + ) -> Result, Self::AdapterError> { let receipt_storage = self.receipt_storage.read().await; let mut receipts_in_range: Vec<(u64, ReceivedReceipt)> = receipt_storage .iter() .filter(|(_, rx_receipt)| { - timestamp_range_ns.contains(&rx_receipt.signed_receipt.message.timestamp_ns) + timestamp_range_ns.contains(&rx_receipt.signed_receipt().message.timestamp_ns) }) .map(|(&id, rx_receipt)| (id, rx_receipt.clone())) .collect(); @@ -143,9 +145,9 @@ impl ReceiptRead for ReceiptStorageAdapterMock { if limit.is_some_and(|limit| receipts_in_range.len() > limit as usize) { safe_truncate_receipts(&mut receipts_in_range, limit.unwrap()); - Ok(receipts_in_range) + Ok(receipts_in_range.into_iter().map(|r| r.into()).collect()) } else { - Ok(receipts_in_range) + Ok(receipts_in_range.into_iter().map(|r| r.into()).collect()) } } } diff --git a/tap_core/src/adapters/receipt_storage_adapter.rs b/tap_core/src/adapters/receipt_storage_adapter.rs index be98c215..b66715a0 100644 --- a/tap_core/src/adapters/receipt_storage_adapter.rs +++ b/tap_core/src/adapters/receipt_storage_adapter.rs @@ -115,9 +115,22 @@ pub trait ReceiptRead { &self, timestamp_range_ns: R, limit: Option, - ) -> Result, Self::AdapterError>; + ) -> Result, Self::AdapterError>; } +pub struct StoredReceipt { + pub receipt_id: u64, + pub receipt: ReceivedReceipt, +} + +impl From<(u64, ReceivedReceipt)> for StoredReceipt { + fn from((receipt_id, receipt): (u64, ReceivedReceipt)) -> Self { + Self { + receipt_id, + receipt, + } + } +} /// See [`ReceiptStorageAdapter::retrieve_receipts_in_timestamp_range()`] for details. /// /// WARNING: Will sort the receipts by timestamp using @@ -130,18 +143,19 @@ pub fn safe_truncate_receipts(receipts: &mut Vec<(u64, ReceivedReceipt)>, limit: return; } - receipts.sort_unstable_by_key(|(_, rx_receipt)| rx_receipt.signed_receipt.message.timestamp_ns); + receipts + .sort_unstable_by_key(|(_, rx_receipt)| rx_receipt.signed_receipt().message.timestamp_ns); // This one will be the last timestamp in `receipts` after naive truncation let last_timestamp = receipts[limit as usize - 1] .1 - .signed_receipt + .signed_receipt() .message .timestamp_ns; // This one is the timestamp that comes just after the one above let after_last_timestamp = receipts[limit as usize] .1 - .signed_receipt + .signed_receipt() .message .timestamp_ns; @@ -152,7 +166,7 @@ pub fn safe_truncate_receipts(receipts: &mut Vec<(u64, ReceivedReceipt)>, limit: // remove all the receipts with the same timestamp as the last one, because // otherwise we would leave behind part of the receipts for that timestamp. receipts.retain(|(_, rx_receipt)| { - rx_receipt.signed_receipt.message.timestamp_ns != last_timestamp + rx_receipt.signed_receipt().message.timestamp_ns != last_timestamp }); } } diff --git a/tap_core/src/adapters/test/receipt_checks_adapter_test.rs b/tap_core/src/adapters/test/receipt_checks_adapter_test.rs index 6fc8b26a..e6ad12d3 100644 --- a/tap_core/src/adapters/test/receipt_checks_adapter_test.rs +++ b/tap_core/src/adapters/test/receipt_checks_adapter_test.rs @@ -116,19 +116,19 @@ mod receipt_checks_adapter_unit_test { .insert(unique_receipt_id, new_receipt.1.clone()); assert!(receipt_checks_adapter - .is_unique(&new_receipt.1.signed_receipt, unique_receipt_id) + .is_unique(&new_receipt.1.signed_receipt(), unique_receipt_id) .await .unwrap()); assert!(receipt_checks_adapter - .is_valid_allocation_id(new_receipt.1.signed_receipt.message.allocation_id) + .is_valid_allocation_id(new_receipt.1.signed_receipt().message.allocation_id) .await .unwrap()); // TODO: Add check when sender_id is available from received receipt (issue: #56) // assert!(receipt_checks_adapter.is_valid_sender_id(sender_id)); assert!(receipt_checks_adapter .is_valid_value( - new_receipt.1.signed_receipt.message.value, - new_receipt.1.query_id + new_receipt.1.signed_receipt().message.value, + new_receipt.1.query_id() ) .await .unwrap()); diff --git a/tap_core/src/adapters/test/receipt_storage_adapter_test.rs b/tap_core/src/adapters/test/receipt_storage_adapter_test.rs index 42468d49..6bd14395 100644 --- a/tap_core/src/adapters/test/receipt_storage_adapter_test.rs +++ b/tap_core/src/adapters/test/receipt_storage_adapter_test.rs @@ -20,9 +20,10 @@ mod receipt_storage_adapter_unit_test { receipt_storage_adapter::ReceiptStore, receipt_storage_adapter_mock::ReceiptStorageAdapterMock, }; + use crate::tap_receipt::ReceivedReceipt; use crate::{ eip_712_signed_message::EIP712SignedMessage, tap_receipt::get_full_list_of_checks, - tap_receipt::Receipt, tap_receipt::ReceivedReceipt, + tap_receipt::Receipt, }; #[fixture] @@ -135,7 +136,7 @@ mod receipt_storage_adapter_unit_test { .await .unwrap(), ); - receipt_timestamps.push(received_receipt.signed_receipt.message.timestamp_ns) + receipt_timestamps.push(received_receipt.signed_receipt().message.timestamp_ns) } // Retreive receipts with timestamp @@ -241,12 +242,12 @@ mod receipt_storage_adapter_unit_test { for (elem_trun, expected_timestamp) in receipts_truncated.iter().zip(expected.iter()) { // Check timestamps assert_eq!( - elem_trun.1.signed_receipt.message.timestamp_ns, + elem_trun.1.signed_receipt().message.timestamp_ns, *expected_timestamp ); // Check that the IDs are fine - assert_eq!(elem_trun.0, elem_trun.1.query_id); + assert_eq!(elem_trun.0, elem_trun.1.query_id()); } } } diff --git a/tap_core/src/tap_manager/manager.rs b/tap_core/src/tap_manager/manager.rs index 2c2bb6bc..f53cd265 100644 --- a/tap_core/src/tap_manager/manager.rs +++ b/tap_core/src/tap_manager/manager.rs @@ -12,7 +12,10 @@ use crate::{ receipt_storage_adapter::{ReceiptRead, ReceiptStore}, }, receipt_aggregate_voucher::ReceiptAggregateVoucher, - tap_receipt::{ReceiptAuditor, ReceiptCheck, ReceivedReceipt}, + tap_receipt::{ + Failed, FinalizedReceipt, ReceiptAuditor, ReceiptCheck, ReceiptWithId, ReceiptWithState, + ReceivedReceipt, Reserved, ReservedReceipt, StatefulVec, + }, Error, }; @@ -122,7 +125,13 @@ where timestamp_buffer_ns: u64, min_timestamp_ns: u64, limit: Option, - ) -> Result<(Vec, Vec), Error> { + ) -> Result< + ( + Vec>, + Vec>, + ), + Error, + > { let max_timestamp_ns = crate::get_current_timestamp_u64_ns()? - timestamp_buffer_ns; if min_timestamp_ns > max_timestamp_ns { @@ -139,30 +148,43 @@ where source_error: anyhow::Error::new(err), })?; - let mut accepted_signed_receipts = Vec::::new(); - let mut failed_signed_receipts = Vec::::new(); + let StatefulVec { + checking_receipts, + mut awaiting_reserve_receipts, + mut failed_receipts, + mut reserved_receipts, + } = received_receipts.into(); - let mut received_receipts: Vec = - received_receipts.into_iter().map(|e| e.1).collect(); + for received_receipt in checking_receipts { + let ReceiptWithId { + receipt, + receipt_id, + } = received_receipt; + let receipt = receipt + .finalize_receipt_checks( + receipt_id, + &self.receipt_auditor, + ) + .await?; - for check in self.required_checks.iter() { - ReceivedReceipt::perform_check_batch( - &mut received_receipts, - check, - &self.receipt_auditor, - ) - .await?; + match receipt { + FinalizedReceipt::AwaitingReserve(checked) => { + awaiting_reserve_receipts.push(checked) + } + FinalizedReceipt::Failed(failed) => failed_receipts.push(failed), + } } - - for received_receipt in received_receipts { - if received_receipt.is_accepted() { - accepted_signed_receipts.push(received_receipt.signed_receipt); - } else { - failed_signed_receipts.push(received_receipt); + for checked in awaiting_reserve_receipts { + match checked + .check_and_reserve_escrow(&self.receipt_auditor) + .await + { + ReservedReceipt::Reserved(reserved) => reserved_receipts.push(reserved), + ReservedReceipt::Failed(failed) => failed_receipts.push(failed), } } - Ok((accepted_signed_receipts, failed_signed_receipts)) + Ok((reserved_receipts, failed_receipts)) } } @@ -203,6 +225,10 @@ where self.receipt_auditor .update_min_timestamp_ns(expected_rav.timestamp_ns) .await; + let valid_receipts = valid_receipts + .into_iter() + .map(|rx_receipt| rx_receipt.signed_receipt()) + .collect::>(); Ok(RAVRequest { valid_receipts, @@ -213,14 +239,23 @@ where } fn generate_expected_rav( - receipts: &[SignedReceipt], + receipts: &[ReceiptWithState], previous_rav: Option, ) -> Result { if receipts.is_empty() { return Err(Error::NoValidReceiptsForRAVRequest); } - let allocation_id = receipts[0].message.allocation_id; - ReceiptAggregateVoucher::aggregate_receipts(allocation_id, receipts, previous_rav) + // TODO kinda ugly + let allocation_id = receipts[0].signed_receipt().message.allocation_id; + let receipts = receipts + .iter() + .map(|rx_receipt| rx_receipt.signed_receipt()) + .collect::>(); + ReceiptAggregateVoucher::aggregate_receipts( + allocation_id, + receipts.as_slice(), + previous_rav, + ) } } @@ -291,9 +326,11 @@ where source_error: anyhow::Error::new(err), })?; - received_receipt - .perform_checks(initial_checks, receipt_id, &self.receipt_auditor) - .await?; + if let ReceivedReceipt::Checking(received_receipt) = &mut received_receipt { + received_receipt + .perform_checks(initial_checks, receipt_id, &self.receipt_auditor) + .await; + } self.receipt_storage_adapter .update_receipt_by_id(receipt_id, received_receipt) diff --git a/tap_core/src/tap_manager/rav_request.rs b/tap_core/src/tap_manager/rav_request.rs index 68a3b7b6..bcf395e4 100644 --- a/tap_core/src/tap_manager/rav_request.rs +++ b/tap_core/src/tap_manager/rav_request.rs @@ -4,13 +4,16 @@ use serde::{Deserialize, Serialize}; use super::{SignedRAV, SignedReceipt}; -use crate::{receipt_aggregate_voucher::ReceiptAggregateVoucher, tap_receipt::ReceivedReceipt}; +use crate::{ + receipt_aggregate_voucher::ReceiptAggregateVoucher, + tap_receipt::{Failed, ReceiptWithState}, +}; #[derive(Debug, Serialize, Deserialize, Clone)] pub struct RAVRequest { pub valid_receipts: Vec, pub previous_rav: Option, - pub invalid_receipts: Vec, + pub invalid_receipts: Vec>, pub expected_rav: ReceiptAggregateVoucher, } diff --git a/tap_core/src/tap_receipt/mod.rs b/tap_core/src/tap_receipt/mod.rs index 4a01e5be..696fcfc8 100644 --- a/tap_core/src/tap_receipt/mod.rs +++ b/tap_core/src/tap_receipt/mod.rs @@ -9,7 +9,11 @@ use std::collections::HashMap; use alloy_primitives::Address; pub use receipt::Receipt; pub use receipt_auditor::ReceiptAuditor; -pub use received_receipt::{RAVStatus, ReceiptState, ReceivedReceipt}; +pub use received_receipt::{ + AwaitingReserve, Checking, Failed, FinalizedReceipt, ReceiptState, ReceiptWithId, + ReceiptWithState, Reserved, ReservedReceipt, ReceivedReceipt, StatefulVec, +}; + use serde::{Deserialize, Serialize}; use strum_macros::{Display, EnumString}; use thiserror::Error; @@ -44,7 +48,6 @@ pub enum ReceiptCheck { CheckTimestamp, CheckValue, CheckSignature, - CheckAndReserveEscrow, } pub fn get_full_list_of_receipt_check_results() -> ReceiptCheckResults { @@ -54,7 +57,6 @@ pub fn get_full_list_of_receipt_check_results() -> ReceiptCheckResults { all_checks_list.insert(ReceiptCheck::CheckTimestamp, None); all_checks_list.insert(ReceiptCheck::CheckValue, None); all_checks_list.insert(ReceiptCheck::CheckSignature, None); - all_checks_list.insert(ReceiptCheck::CheckAndReserveEscrow, None); all_checks_list } @@ -66,7 +68,6 @@ pub fn get_full_list_of_checks() -> Vec { ReceiptCheck::CheckTimestamp, ReceiptCheck::CheckValue, ReceiptCheck::CheckSignature, - ReceiptCheck::CheckAndReserveEscrow, ] } diff --git a/tap_core/src/tap_receipt/receipt_auditor.rs b/tap_core/src/tap_receipt/receipt_auditor.rs index 1f910980..b1f9fa1f 100644 --- a/tap_core/src/tap_receipt/receipt_auditor.rs +++ b/tap_core/src/tap_receipt/receipt_auditor.rs @@ -15,7 +15,7 @@ use crate::{ Error, Result, }; -use super::ReceivedReceipt; +use super::{received_receipt::Checking, AwaitingReserve, ReceiptWithState}; pub struct ReceiptAuditor { domain_separator: Eip712Domain, @@ -60,15 +60,15 @@ impl ReceiptAuditor { async fn check_timestamp_batch( &self, - received_receipts: &mut [ReceivedReceipt], + received_receipts: &mut [ReceiptWithState], ) -> Vec> { let mut results = Vec::new(); for received_receipt in received_receipts .iter_mut() - .filter(|r| r.checks.contains_key(&ReceiptCheck::CheckTimestamp)) + .filter(|r| r.state.checks.contains_key(&ReceiptCheck::CheckTimestamp)) { - if received_receipt.checks[&ReceiptCheck::CheckTimestamp].is_none() { + if received_receipt.state.checks[&ReceiptCheck::CheckTimestamp].is_none() { let signed_receipt = &received_receipt.signed_receipt; results.push(self.check_timestamp(signed_receipt).await); } @@ -79,15 +79,15 @@ impl ReceiptAuditor { async fn check_uniqueness_batch( &self, - received_receipts: &mut [ReceivedReceipt], + received_receipts: &mut [ReceiptWithState], ) -> Vec> { let mut results = Vec::new(); // If at least one of the receipts in the batch hasn't been checked for uniqueness yet, check the whole batch. if received_receipts .iter() - .filter(|r| r.checks.contains_key(&ReceiptCheck::CheckUnique)) - .any(|r| r.checks[&ReceiptCheck::CheckUnique].is_none()) + .filter(|r| r.state.checks.contains_key(&ReceiptCheck::CheckUnique)) + .any(|r| r.state.checks[&ReceiptCheck::CheckUnique].is_none()) { let mut signatures: HashSet = HashSet::new(); @@ -123,16 +123,13 @@ where ReceiptCheck::CheckSignature => self.check_signature(signed_receipt).await, ReceiptCheck::CheckTimestamp => self.check_timestamp(signed_receipt).await, ReceiptCheck::CheckValue => self.check_value(signed_receipt, query_id).await, - ReceiptCheck::CheckAndReserveEscrow => { - self.check_and_reserve_escrow(signed_receipt).await - } } } - pub async fn check_batch( + pub async fn check_batch_todo( &self, receipt_check: &ReceiptCheck, - received_receipts: &mut [ReceivedReceipt], + received_receipts: &mut [ReceiptWithState], ) -> Vec> { match receipt_check { ReceiptCheck::CheckUnique => self.check_uniqueness_batch(received_receipts).await, @@ -142,9 +139,6 @@ where ReceiptCheck::CheckSignature => self.check_signature_batch(received_receipts).await, ReceiptCheck::CheckTimestamp => self.check_timestamp_batch(received_receipts).await, ReceiptCheck::CheckValue => self.check_value_batch(received_receipts).await, - ReceiptCheck::CheckAndReserveEscrow => { - self.check_and_reserve_escrow_batch(received_receipts).await - } } } } @@ -192,15 +186,16 @@ where async fn check_allocation_id_batch( &self, - received_receipts: &mut [ReceivedReceipt], + received_receipts: &mut [ReceiptWithState], ) -> Vec> { let mut results = Vec::new(); - for received_receipt in received_receipts - .iter_mut() - .filter(|r| r.checks.contains_key(&ReceiptCheck::CheckAllocationId)) - { - if received_receipt.checks[&ReceiptCheck::CheckAllocationId].is_none() { + for received_receipt in received_receipts.iter_mut().filter(|r| { + r.state + .checks + .contains_key(&ReceiptCheck::CheckAllocationId) + }) { + if received_receipt.state.checks[&ReceiptCheck::CheckAllocationId].is_none() { let signed_receipt = &received_receipt.signed_receipt; results.push(self.check_allocation_id(signed_receipt).await); } @@ -231,15 +226,15 @@ where async fn check_value_batch( &self, - received_receipts: &mut [ReceivedReceipt], + received_receipts: &mut [ReceiptWithState], ) -> Vec> { let mut results = Vec::new(); for received_receipt in received_receipts .iter_mut() - .filter(|r| r.checks.contains_key(&ReceiptCheck::CheckValue)) + .filter(|r| r.state.checks.contains_key(&ReceiptCheck::CheckValue)) { - if received_receipt.checks[&ReceiptCheck::CheckValue].is_none() { + if received_receipt.state.checks[&ReceiptCheck::CheckValue].is_none() { let signed_receipt = &received_receipt.signed_receipt; results.push( self.check_value(signed_receipt, received_receipt.query_id) @@ -280,15 +275,15 @@ where async fn check_signature_batch( &self, - received_receipts: &mut [ReceivedReceipt], + received_receipts: &mut [ReceiptWithState], ) -> Vec> { let mut results = Vec::new(); for received_receipt in received_receipts .iter_mut() - .filter(|r| r.checks.contains_key(&ReceiptCheck::CheckSignature)) + .filter(|r| r.state.checks.contains_key(&ReceiptCheck::CheckSignature)) { - if received_receipt.checks[&ReceiptCheck::CheckSignature].is_none() { + if received_receipt.state.checks[&ReceiptCheck::CheckSignature].is_none() { let signed_receipt = &received_receipt.signed_receipt; results.push(self.check_signature(signed_receipt).await); } @@ -322,10 +317,11 @@ impl ReceiptAuditor where EA: EscrowAdapter, { - async fn check_and_reserve_escrow( + pub async fn check_and_reserve_escrow( &self, - signed_receipt: &EIP712SignedMessage, + received_receipt: &ReceiptWithState, ) -> ReceiptResult<()> { + let signed_receipt = &received_receipt.signed_receipt; let receipt_signer_address = signed_receipt .recover_signer(&self.domain_separator) .map_err(|err| ReceiptError::InvalidSignature { @@ -342,20 +338,4 @@ where Ok(()) } - - async fn check_and_reserve_escrow_batch( - &self, - received_receipts: &mut [ReceivedReceipt], - ) -> Vec> { - let mut results = Vec::new(); - - for received_receipt in received_receipts.iter_mut().filter(|r| { - r.escrow_reserve_attempt_required() && !r.escrow_reserve_attempt_completed() - }) { - let signed_receipt = &received_receipt.signed_receipt; - results.push(self.check_and_reserve_escrow(signed_receipt).await); - } - - results - } } diff --git a/tap_core/src/tap_receipt/received_receipt.rs b/tap_core/src/tap_receipt/received_receipt.rs index 0b5510c0..ae6a5b2e 100644 --- a/tap_core/src/tap_receipt/received_receipt.rs +++ b/tap_core/src/tap_receipt/received_receipt.rs @@ -14,63 +14,142 @@ //! their progress through various checks and stages of inclusion in RAV requests and received RAVs. use serde::{Deserialize, Serialize}; -use strum_macros::{Display, EnumString}; -use super::{ - receipt_auditor::ReceiptAuditor, Receipt, ReceiptCheck, ReceiptCheckResults, ReceiptError, -}; +use super::{receipt_auditor::ReceiptAuditor, Receipt, ReceiptCheck, ReceiptCheckResults}; use crate::{ - adapters::{escrow_adapter::EscrowAdapter, receipt_checks_adapter::ReceiptChecksAdapter}, + adapters::{ + escrow_adapter::EscrowAdapter, receipt_checks_adapter::ReceiptChecksAdapter, + receipt_storage_adapter::StoredReceipt, + }, eip_712_signed_message::EIP712SignedMessage, Error, Result, }; -#[derive(Eq, PartialEq, Debug, Clone, EnumString, Display, Serialize, Deserialize)] -/// State of the contained receipt -pub enum ReceiptState { - /// Initial state, received with no checks started - Received, - /// Checking in progress, no errors found - Checking, - /// Checks completed with at least one check resulting in an error - Failed, - /// Checks completed with all passed, awaiting escrow check and reserve - AwaitingReserveEscrow, - /// All checks completed with no errors found, escrow is reserved if requested by user - Accepted, - /// Receipt was added to a RAV request - IncludedInRAVRequest, - /// Receipt was included in received RAV - Complete, +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Checking { + /// A list of checks to be completed for the receipt, along with their current result + pub(crate) checks: ReceiptCheckResults, +} +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Failed; +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct AwaitingReserve; + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct Reserved; + +pub trait ReceiptState {} +impl ReceiptState for Checking {} +impl ReceiptState for AwaitingReserve {} +impl ReceiptState for Reserved {} +impl ReceiptState for Failed {} + +#[derive(Clone)] +pub enum ReceivedReceipt { + AwaitingReserve(ReceiptWithState), + Checking(ReceiptWithState), + Failed(ReceiptWithState), + Reserved(ReceiptWithState), } -#[derive(Eq, PartialEq, Debug, Clone, Serialize, Deserialize)] +pub enum FinalizedReceipt { + AwaitingReserve(ReceiptWithState), + Failed(ReceiptWithState), +} -/// Status of receipt relating to RAV inclusion -pub enum RAVStatus { - /// Has not been included in a RAV request or received RAV - NotIncluded, - /// Has been added to a RAV request, but not a received RAV (awaiting a response) - IncludedInRequest, - /// A RAV has been received that included this receipt - IncludedInReceived, +pub enum ReservedReceipt { + Reserved(ReceiptWithState), + Failed(ReceiptWithState), } -#[derive(Debug, Clone, Serialize, Deserialize)] -/// Wrapper class for metadata and state of a received receipt -pub struct ReceivedReceipt { - /// An EIP712 signed receipt message - pub(crate) signed_receipt: EIP712SignedMessage, - /// A unique identifier for the query associated with the receipt - pub(crate) query_id: u64, - /// A list of checks to be completed for the receipt, along with their current result - pub(crate) checks: ReceiptCheckResults, - /// Escrow check and reserve, which is performed only after all other checks are complete. `Ok` result means escrow was reserved - pub(crate) escrow_reserved: Option>>, - /// The current RAV status of the receipt (e.g., not included, included in a request, or included in a received RAV) - pub(crate) rav_status: RAVStatus, - /// The current state of the receipt (e.g., received, checking, failed, accepted, etc.) - pub(crate) state: ReceiptState, +pub struct ReceiptWithId +where + T: ReceiptState + ?Sized, +{ + pub(crate) receipt_id: u64, + pub(crate) receipt: ReceiptWithState, +} + +impl From<(u64, ReceiptWithState)> for ReceiptWithId +where + T: ReceiptState, +{ + fn from((receipt_id, receipt): (u64, ReceiptWithState)) -> ReceiptWithId { + Self { + receipt_id, + receipt, + } + } +} + +pub struct StatefulVec { + pub(crate) awaiting_reserve_receipts: Vec>, + pub(crate) checking_receipts: Vec>, + pub(crate) failed_receipts: Vec>, + pub(crate) reserved_receipts: Vec>, +} + +impl FinalizedReceipt { + pub fn into_stateful(self) -> ReceivedReceipt { + match self { + FinalizedReceipt::AwaitingReserve(checked) => ReceivedReceipt::AwaitingReserve(checked), + FinalizedReceipt::Failed(failed) => ReceivedReceipt::Failed(failed), + } + } +} + +impl ReceiptWithState { + pub fn into_stateful(self) -> ReceivedReceipt { + ReceivedReceipt::AwaitingReserve(self) + } +} + +impl ReceiptWithState { + pub fn into_stateful(self) -> ReceivedReceipt { + ReceivedReceipt::Checking(self) + } +} + +impl ReceiptWithState { + pub fn into_stateful(self) -> ReceivedReceipt { + ReceivedReceipt::Failed(self) + } +} + +impl ReceiptWithState { + pub fn into_stateful(self) -> ReceivedReceipt { + ReceivedReceipt::Reserved(self) + } +} + +impl From> for StatefulVec { + fn from(value: Vec) -> Self { + let mut checked_receipts = Vec::new(); + let mut checking_receipts = Vec::new(); + let mut failed_receipts = Vec::new(); + let mut reserved_receipts = Vec::new(); + + for stored_receipt in value { + let StoredReceipt { + receipt_id, + receipt, + } = stored_receipt; + match receipt { + ReceivedReceipt::AwaitingReserve(checked) => checked_receipts.push(checked), + ReceivedReceipt::Checking(checking) => { + checking_receipts.push((receipt_id, checking).into()) + } + ReceivedReceipt::Failed(failed) => failed_receipts.push(failed), + ReceivedReceipt::Reserved(reserved) => reserved_receipts.push(reserved), + } + } + Self { + awaiting_reserve_receipts: checked_receipts, + checking_receipts, + failed_receipts, + reserved_receipts, + } + } } impl ReceivedReceipt { @@ -80,21 +159,67 @@ impl ReceivedReceipt { query_id: u64, required_checks: &[ReceiptCheck], ) -> Self { - let mut checks = Self::get_empty_required_checks_hashmap(required_checks); - let escrow_reserved = checks.remove(&ReceiptCheck::CheckAndReserveEscrow); + let checks = ReceiptWithState::get_empty_required_checks_hashmap(required_checks); - let mut received_receipt = Self { + let received_receipt = ReceiptWithState { signed_receipt, query_id, - checks, - escrow_reserved, - rav_status: RAVStatus::NotIncluded, - state: ReceiptState::Received, + state: Checking { checks }, }; - received_receipt.update_state(); - received_receipt + received_receipt.into_stateful() + } + pub fn signed_receipt(&self) -> EIP712SignedMessage { + match self { + ReceivedReceipt::AwaitingReserve(ReceiptWithState { signed_receipt, .. }) + | ReceivedReceipt::Checking(ReceiptWithState { signed_receipt, .. }) + | ReceivedReceipt::Failed(ReceiptWithState { signed_receipt, .. }) + | ReceivedReceipt::Reserved(ReceiptWithState { signed_receipt, .. }) => { + signed_receipt.clone() + } + } } + pub fn query_id(&self) -> u64 { + match self { + ReceivedReceipt::AwaitingReserve(ReceiptWithState { query_id, .. }) + | ReceivedReceipt::Checking(ReceiptWithState { query_id, .. }) + | ReceivedReceipt::Failed(ReceiptWithState { query_id, .. }) + | ReceivedReceipt::Reserved(ReceiptWithState { query_id, .. }) => *query_id, + } + } +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +/// Wrapper class for metadata and state of a received receipt +pub struct ReceiptWithState +where + S: ReceiptState + ?Sized, +{ + /// An EIP712 signed receipt message + pub(crate) signed_receipt: EIP712SignedMessage, + /// A unique identifier for the query associated with the receipt + pub(crate) query_id: u64, + /// The current state of the receipt (e.g., received, checking, failed, accepted, etc.) + pub(crate) state: S, +} + +impl ReceiptWithState { + pub async fn check_and_reserve_escrow( + self, + auditor: &ReceiptAuditor, + ) -> ReservedReceipt + where + EA: EscrowAdapter, + RCA: ReceiptChecksAdapter, + { + match auditor.check_and_reserve_escrow(&self).await { + Ok(_) => ReservedReceipt::Reserved(self.perform_state_changes(Reserved)), + Err(_) => ReservedReceipt::Failed(self.perform_state_changes(Failed)), + } + } +} + +impl ReceiptWithState { /// Completes a single *incomplete* check and stores the result, *if the check already has a result it is skipped.* /// /// Returns `Err` only if unable to complete the check, returns `Ok` if the check was completed (*Important:* this is not the result of the check, just the result of _completing_ the check) @@ -110,31 +235,12 @@ impl ReceivedReceipt { check: &ReceiptCheck, receipt_id: u64, receipt_auditor: &ReceiptAuditor, - ) -> crate::Result<()> { - match self.state { - ReceiptState::Checking | ReceiptState::Received => { - // Cannot do escrow check and reserve until all other checks are complete - if check == &ReceiptCheck::CheckAndReserveEscrow { - return Err(crate::Error::InvalidStateForRequestedAction { - state: self.state.to_string(), - }); - } - } - // All checks are valid in this state (although complete ones will be skipped) - ReceiptState::AwaitingReserveEscrow => {} - - // If all checks are complete then checking is skipped - ReceiptState::Accepted - | ReceiptState::Complete - | ReceiptState::IncludedInRAVRequest - | ReceiptState::Failed => return Ok(()), - } - - // All skipped checks return `Ok` - let mut result = Ok(()); + ) { // Only perform check if it is incomplete - if !self.check_is_complete(check) { - result = self.update_check( + // Don't check if already failed + if !self.check_is_complete(check) && !self.any_check_resulted_in_error() { + // TODO + let _ = self.update_check( check, Some( receipt_auditor @@ -143,8 +249,6 @@ impl ReceivedReceipt { ), ); } - self.update_state(); - result } pub async fn perform_check_batch( @@ -152,11 +256,10 @@ impl ReceivedReceipt { check: &ReceiptCheck, receipt_auditor: &ReceiptAuditor, ) -> Result<()> { - let results = receipt_auditor.check_batch(check, batch).await; + let results = receipt_auditor.check_batch_todo(check, batch).await; for (receipt, result) in batch.iter_mut().zip(results) { receipt.update_check(check, Some(result))?; - receipt.update_state(); } Ok(()) @@ -177,27 +280,11 @@ impl ReceivedReceipt { checks: &[ReceiptCheck], receipt_id: u64, receipt_auditor: &ReceiptAuditor, - ) -> Result<()> { - let mut check_and_reserve_escrow_included = false; + ) { for check in checks { - if *check == ReceiptCheck::CheckAndReserveEscrow { - // if checks include check and reserve escrow it needs to be completed last - check_and_reserve_escrow_included = true; - continue; - } self.perform_check(check, receipt_id, receipt_auditor) - .await?; + .await; } - if check_and_reserve_escrow_included && self.state != ReceiptState::Failed { - // CheckAndReserveEscrow is only performed after all other checks have passed - self.perform_check( - &ReceiptCheck::CheckAndReserveEscrow, - receipt_id, - receipt_auditor, - ) - .await?; - } - Ok(()) } /// Completes all remaining checks and stores the results @@ -205,96 +292,28 @@ impl ReceivedReceipt { /// Returns `Err` only if unable to complete a check, returns `Ok` if no check failed to complete (*Important:* this is not the result of the check, just the result of _completing_ the check) /// pub async fn finalize_receipt_checks( - &mut self, + mut self, receipt_id: u64, receipt_auditor: &ReceiptAuditor, - ) -> Result<()> { - self.perform_checks( - self.incomplete_checks().as_slice(), - receipt_id, - receipt_auditor, - ) - .await - } - - /// Update RAV status, should be called when receipt is included in RAV request and when RAV request is received - pub fn update_rav_status(&mut self, rav_status: RAVStatus) { - self.rav_status = rav_status; - self.update_state(); - } - - pub(crate) fn update_check( - &mut self, - check: &ReceiptCheck, - result: Option>, - ) -> Result<()> { - if check == &ReceiptCheck::CheckAndReserveEscrow { - return self.update_escrow_reserved_check(check, result); - } - - if !self.checks.contains_key(check) { - return Err(Error::InvalidCheckError { - check_string: check.to_string(), - }); - } + ) -> Result { + let incomplete_checks = self.incomplete_checks(); - self.checks.insert(check.clone(), result); - Ok(()) - } + self.perform_checks(incomplete_checks.as_slice(), receipt_id, receipt_auditor) + .await; - fn update_escrow_reserved_check( - &mut self, - check: &ReceiptCheck, - result: Option>, - ) -> Result<()> { - if !(self.state == ReceiptState::AwaitingReserveEscrow) { - return Err(Error::InvalidStateForRequestedAction { - state: self.state.to_string(), - }); - } - - if let Some(ref mut escrow_reserved_check) = self.escrow_reserved { - *escrow_reserved_check = result; + if self.any_check_resulted_in_error() { + let failed = self.perform_state_changes(Failed); + Ok(FinalizedReceipt::Failed(failed)) } else { - return Err(crate::Error::InvalidCheckError { - check_string: check.to_string(), - }); + let checked = self.perform_state_changes(AwaitingReserve); + Ok(FinalizedReceipt::AwaitingReserve(checked)) } - - self.update_state(); - Ok(()) - } - - pub fn signed_receipt(&self) -> EIP712SignedMessage { - self.signed_receipt.clone() - } - - pub fn query_id(&self) -> u64 { - self.query_id - } - - /// Returns all checks that have not been completed - pub fn incomplete_checks(&self) -> Vec { - let mut incomplete_checks: Vec = self - .checks - .iter() - .filter_map(|(check, result)| { - if result.is_none() { - Some((*check).clone()) - } else { - None - } - }) - .collect(); - if self.escrow_reserve_attempt_required() && !self.escrow_reserve_attempt_completed() { - incomplete_checks.push(ReceiptCheck::CheckAndReserveEscrow); - } - incomplete_checks } /// Returns all checks that completed with errors pub fn completed_checks_with_errors(&self) -> ReceiptCheckResults { - self.checks + self.state + .checks .iter() .filter_map(|(check, result)| { if let Some(unwrapped_result) = result { @@ -307,124 +326,82 @@ impl ReceivedReceipt { .collect() } - /// Updates receieved receipt state based on internal values, should be called anytime internal state changes - pub(crate) fn update_state(&mut self) { - let mut next_state = self.state.clone(); - match self.state { - ReceiptState::Received => { - if self.checking_is_started() { - next_state = self.get_state_of_checks(); + /// Returns all checks that have not been completed + pub fn incomplete_checks(&self) -> Vec { + let incomplete_checks: Vec = self + .state + .checks + .iter() + .filter_map(|(check, result)| { + if result.is_none() { + Some((*check).clone()) } else { - next_state = ReceiptState::Received; - } - } - ReceiptState::Checking => { - next_state = self.get_state_of_checks(); - } - ReceiptState::AwaitingReserveEscrow => { - next_state = self.get_state_of_escrow_reserve(); - } - ReceiptState::Failed => {} // currently no next state from Failed - ReceiptState::Accepted => { - if self.rav_status == RAVStatus::IncludedInRequest { - next_state = ReceiptState::IncludedInRAVRequest; - } - } - ReceiptState::IncludedInRAVRequest => { - if self.rav_status == RAVStatus::IncludedInReceived { - next_state = ReceiptState::Complete; - } - } - ReceiptState::Complete => {} // currently no next state from complete - } - self.state = next_state; - } - - fn get_state_of_checks(&self) -> ReceiptState { - if self.checking_is_completed() && self.any_check_resulted_in_error() { - return ReceiptState::Failed; - } - if self.all_checks_passed() { - return self.get_state_of_escrow_reserve(); - } - if self.checking_is_in_progress() { - return ReceiptState::Checking; - } - // Incase the function got called when checking was not started we can return to received state - ReceiptState::Received - } - - fn get_state_of_escrow_reserve(&self) -> ReceiptState { - if !self.escrow_reserve_attempt_required() { - return ReceiptState::Accepted; - } - if self.escrow_reserve_attempt_completed() { - if let Some(Some(escrow_reserve_attempt_result)) = &self.escrow_reserved { - if escrow_reserve_attempt_result.is_err() { - return ReceiptState::Failed; - } - if escrow_reserve_attempt_result.is_ok() { - return ReceiptState::Accepted; + None } - } - } - - ReceiptState::AwaitingReserveEscrow + }) + .collect(); + incomplete_checks } - pub(crate) fn escrow_reserve_attempt_completed(&self) -> bool { - if let Some(escrow_reserve_attempt) = &self.escrow_reserved { - return escrow_reserve_attempt.is_some(); + pub(crate) fn update_check( + &mut self, + check: &ReceiptCheck, + result: Option>, + ) -> Result<()> { + if !self.state.checks.contains_key(check) { + return Err(Error::InvalidCheckError { + check_string: check.to_string(), + }); } - false - } - - pub(crate) fn escrow_reserve_attempt_required(&self) -> bool { - self.escrow_reserved.is_some() - } - fn checking_is_in_progress(&self) -> bool { - self.checking_is_started() && !self.checking_is_completed() - } - - fn checking_is_started(&self) -> bool { - self.checks.iter().any(|(_, status)| status.is_some()) + self.state.checks.insert(check.clone(), result); + Ok(()) } - fn checking_is_completed(&self) -> bool { - !self.checks.iter().any(|(_, status)| status.is_none()) + /// returns true `check` has a result, otherwise false + pub(crate) fn check_is_complete(&self, check: &ReceiptCheck) -> bool { + matches!(self.state.checks.get(check), Some(Some(_))) } fn any_check_resulted_in_error(&self) -> bool { - self.checks.iter().any(|(_, status)| match &status { + self.state.checks.iter().any(|(_, status)| match &status { Some(result) => result.is_err(), None => false, }) } - fn all_checks_passed(&self) -> bool { - self.checking_is_completed() && !self.any_check_resulted_in_error() + pub fn checking_is_complete(&self) -> bool { + self.state.checks.iter().all(|(_, status)| status.is_some()) } - /// returns true `check` has a result, otherwise false - fn check_is_complete(&self, check: &ReceiptCheck) -> bool { - matches!(self.checks.get(check), Some(Some(_))) + fn get_empty_required_checks_hashmap(required_checks: &[ReceiptCheck]) -> ReceiptCheckResults { + required_checks + .iter() + .map(|check| (check.clone(), None)) + .collect() } +} - /// Returns true if all checks are complete and at least one failed - pub fn is_failed(&self) -> bool { - self.state == ReceiptState::Failed +impl ReceiptWithState +where + S: ReceiptState, +{ + fn perform_state_changes(self, new_state: T) -> ReceiptWithState + where + T: ReceiptState, + { + ReceiptWithState { + signed_receipt: self.signed_receipt, + query_id: self.query_id, + state: new_state, + } } - /// Returns true if all checks are complete and all checks passed - pub fn is_accepted(&self) -> bool { - self.state == ReceiptState::Accepted + pub fn signed_receipt(&self) -> EIP712SignedMessage { + self.signed_receipt.clone() } - fn get_empty_required_checks_hashmap(required_checks: &[ReceiptCheck]) -> ReceiptCheckResults { - required_checks - .iter() - .map(|check| (check.clone(), None)) - .collect() + pub fn query_id(&self) -> u64 { + self.query_id } } diff --git a/tap_core/src/tap_receipt/tests/received_receipt_tests.rs b/tap_core/src/tap_receipt/tests/received_receipt_tests.rs index 456dd0f0..3ff62e46 100644 --- a/tap_core/src/tap_receipt/tests/received_receipt_tests.rs +++ b/tap_core/src/tap_receipt/tests/received_receipt_tests.rs @@ -24,9 +24,7 @@ mod received_receipt_unit_test { eip_712_signed_message::EIP712SignedMessage, get_current_timestamp_u64_ns, tap_receipt::{ - get_full_list_of_checks, - received_receipt::{RAVStatus, ReceiptState}, - Receipt, ReceiptAuditor, ReceiptCheck, ReceivedReceipt, + get_full_list_of_checks, Receipt, ReceiptAuditor, ReceiptCheck, ReceivedReceipt, }, }; @@ -126,10 +124,13 @@ mod received_receipt_unit_test { let received_receipt = ReceivedReceipt::new(signed_receipt, query_id, &checks); + let received_receipt = match received_receipt { + ReceivedReceipt::Checking(checking) => checking, + _ => panic!("ReceivedReceipt should be in Checking state"), + }; + assert!(received_receipt.completed_checks_with_errors().is_empty()); assert!(received_receipt.incomplete_checks().len() == checks.len()); - assert_eq!(received_receipt.state, ReceiptState::Received); - assert_eq!(received_receipt.rav_status, RAVStatus::NotIncluded); } #[rstest] @@ -181,26 +182,25 @@ mod received_receipt_unit_test { .insert(query_id, query_value); let checks = get_full_list_of_checks(); - let mut received_receipt = ReceivedReceipt::new(signed_receipt, query_id, &checks); + let received_receipt = ReceivedReceipt::new(signed_receipt, query_id, &checks); let receipt_id = 0u64; - assert_eq!(received_receipt.state, ReceiptState::Received); - assert_eq!(received_receipt.rav_status, RAVStatus::NotIncluded); + let mut received_receipt = match received_receipt { + ReceivedReceipt::Checking(checking) => checking, + _ => panic!("ReceivedReceipt should be in Checking state"), + }; // perform single arbitrary check let arbitrary_check_to_perform = ReceiptCheck::CheckUnique; - assert!(received_receipt + received_receipt .perform_check(&arbitrary_check_to_perform, receipt_id, &receipt_auditor) - .await - .is_ok()); + .await; + assert!(received_receipt.check_is_complete(&arbitrary_check_to_perform)); - assert!(received_receipt + received_receipt .perform_checks(&checks, receipt_id, &receipt_auditor) - .await - .is_ok()); - - assert_eq!(received_receipt.state, ReceiptState::Accepted); - assert_eq!(received_receipt.rav_status, RAVStatus::NotIncluded); + .await; + assert!(received_receipt.checking_is_complete()); } #[rstest] @@ -252,36 +252,26 @@ mod received_receipt_unit_test { .insert(query_id, query_value); let checks = get_full_list_of_checks(); - let mut received_receipt = ReceivedReceipt::new(signed_receipt, query_id, &checks); + let received_receipt = ReceivedReceipt::new(signed_receipt, query_id, &checks); let receipt_id = 0u64; - assert_eq!(received_receipt.state, ReceiptState::Received); - assert_eq!(received_receipt.rav_status, RAVStatus::NotIncluded); + let mut received_receipt = match received_receipt { + ReceivedReceipt::Checking(checking) => checking, + _ => panic!("ReceivedReceipt should be in Checking state"), + }; // perform single arbitrary check let arbitrary_check_to_perform = ReceiptCheck::CheckUnique; - assert!(received_receipt + + received_receipt .perform_check(&arbitrary_check_to_perform, receipt_id, &receipt_auditor) - .await - .is_ok()); + .await; + assert!(received_receipt.check_is_complete(&arbitrary_check_to_perform)); assert!(received_receipt .finalize_receipt_checks(receipt_id, &receipt_auditor) .await .is_ok()); - - assert_eq!(received_receipt.state, ReceiptState::Accepted); - assert_eq!(received_receipt.rav_status, RAVStatus::NotIncluded); - - received_receipt.update_rav_status(RAVStatus::IncludedInRequest); - - assert_eq!(received_receipt.state, ReceiptState::IncludedInRAVRequest); - assert_eq!(received_receipt.rav_status, RAVStatus::IncludedInRequest); - - received_receipt.update_rav_status(RAVStatus::IncludedInReceived); - - assert_eq!(received_receipt.state, ReceiptState::Complete); - assert_eq!(received_receipt.rav_status, RAVStatus::IncludedInReceived); } #[rstest] @@ -333,28 +323,17 @@ mod received_receipt_unit_test { .insert(query_id, query_value); let checks = get_full_list_of_checks(); - let mut received_receipt = ReceivedReceipt::new(signed_receipt, query_id, &checks); + let received_receipt = ReceivedReceipt::new(signed_receipt, query_id, &checks); let receipt_id = 0u64; - assert_eq!(received_receipt.state, ReceiptState::Received); - assert_eq!(received_receipt.rav_status, RAVStatus::NotIncluded); + let received_receipt = match received_receipt { + ReceivedReceipt::Checking(checking) => checking, + _ => panic!("ReceivedReceipt should be in Checking state"), + }; assert!(received_receipt .finalize_receipt_checks(receipt_id, &receipt_auditor) .await .is_ok()); - - assert_eq!(received_receipt.state, ReceiptState::Accepted); - assert_eq!(received_receipt.rav_status, RAVStatus::NotIncluded); - - received_receipt.update_rav_status(RAVStatus::IncludedInRequest); - - assert_eq!(received_receipt.state, ReceiptState::IncludedInRAVRequest); - assert_eq!(received_receipt.rav_status, RAVStatus::IncludedInRequest); - - received_receipt.update_rav_status(RAVStatus::IncludedInReceived); - - assert_eq!(received_receipt.state, ReceiptState::Complete); - assert_eq!(received_receipt.rav_status, RAVStatus::IncludedInReceived); } } diff --git a/tap_integration_tests/tests/showcase.rs b/tap_integration_tests/tests/showcase.rs index 6a08d279..4b958dfb 100644 --- a/tap_integration_tests/tests/showcase.rs +++ b/tap_integration_tests/tests/showcase.rs @@ -209,7 +209,6 @@ fn required_checks() -> Vec { ReceiptCheck::CheckTimestamp, ReceiptCheck::CheckUnique, ReceiptCheck::CheckValue, - ReceiptCheck::CheckAndReserveEscrow, ] } @@ -223,7 +222,6 @@ fn initial_checks() -> Vec { ReceiptCheck::CheckTimestamp, ReceiptCheck::CheckUnique, ReceiptCheck::CheckValue, - ReceiptCheck::CheckAndReserveEscrow, ] }