diff --git a/manta-accounting/src/asset.rs b/manta-accounting/src/asset.rs index de31c893c..8d22faeab 100644 --- a/manta-accounting/src/asset.rs +++ b/manta-accounting/src/asset.rs @@ -859,22 +859,39 @@ macro_rules! impl_asset_map_for_maps_body { #[inline] fn select(&self, asset: Asset) -> Selection { - // TODO: Use a smarter coin-selection algorithm (max-heap?). if asset.is_zero() { return Selection::default(); } let mut sum = Asset::zero(asset.id); let mut values = Vec::new(); - for (key, assets) in self { - for item in assets { - if item.value != AssetValue(0) && sum.try_add_assign(*item) { - values.push((key.clone(), item.value)); - if sum.value >= asset.value { - break; - } + let mut min_max_asset: Option<($k, AssetValue)> = None; + let map = self + .iter() + .map(|(key, assets)| assets.iter().map(move |asset| (key, asset))) + .flatten() + .filter_map(|(key, item)| { + if !item.is_zero() && item.id == asset.id { + Some((key, item.value)) + } else { + None } + }); + for (key, value) in map { + if value > asset.value { + min_max_asset = Some(match min_max_asset.take() { + Some(best) if value >= best.1 => best, + _ => (key.clone(), value), + }); + } else if value == asset.value { + return Selection::new(Default::default(), vec![(key.clone(), value)]); + } else { + sum.add_assign(value); + values.push((key.clone(), value)); } } + if let Some((best_key, best_value)) = min_max_asset { + return Selection::new(best_value - asset.value, vec![(best_key, best_value)]); + } if sum.value < asset.value { Selection::default() } else { diff --git a/manta-accounting/src/wallet/ledger.rs b/manta-accounting/src/wallet/ledger.rs index 8cd64e885..6416323ed 100644 --- a/manta-accounting/src/wallet/ledger.rs +++ b/manta-accounting/src/wallet/ledger.rs @@ -16,136 +16,110 @@ //! Ledger Connection -use crate::transfer::{Configuration, EncryptedNote, TransferPost, Utxo, VoidNumber}; -use alloc::vec::Vec; use core::{fmt::Debug, hash::Hash}; use manta_util::future::LocalBoxFutureResult; #[cfg(feature = "serde")] use manta_util::serde::{Deserialize, Serialize}; +/// Ledger Connection +/// +/// This is the base `trait` for defining a connection with a ledger. To communicate with the +/// ledger, you can establish such a connection first and then interact via the [`Read`] and +/// [`Write`] traits which send messages along the connection. +pub trait Connection { + /// Error Type + /// + /// This error type corresponds to the communication channel setup by the [`Connection`] rather + /// than any errors introduced by [`read`] or [`write`] methods. Instead those methods should + /// return errors in their `Response` types. + /// + /// [`read`]: Read::read + /// [`write`]: Write::write + type Error; +} + /// Ledger Checkpoint /// /// The checkpoint type is responsible for keeping the ledger, signer, and wallet in sync with each /// other making sure that they all have the same view of the ledger state. Checkpoints should -/// be orderable with a bottom element returned by [`Default::default`]. -pub trait Checkpoint: Default + PartialOrd { - /// Returns the index into the receiver set for the ledger. - fn receiver_index(&self) -> usize; +/// be orderable with a bottom element returned by [`Default::default`]. Types implementing this +/// `trait` must also implement [`Clone`] as it must be safe (but not necessarily efficient) to +/// copy a checkpoint value. +pub trait Checkpoint: Clone + Default + PartialOrd {} - /// Returns the index into the sender set for the ledger. - fn sender_index(&self) -> usize; -} - -/// Ledger Pull Configuration -pub trait PullConfiguration +/// Ledger Data Pruning +pub trait Prune where - C: Configuration, + T: Checkpoint, { - /// Ledger State Checkpoint Type - type Checkpoint: Checkpoint; - - /// Receiver Chunk Iterator Type - type ReceiverChunk: IntoIterator, EncryptedNote)>; - - /// Sender Chunk Iterator Type - type SenderChunk: IntoIterator>; + /// Prunes the data in `self`, which was retrieved at `origin`, so that it meets the current + /// `checkpoint`, dropping data that is older than the given `checkpoint`. This method should + /// return `true` if it dropped data from `self`. + fn prune(&mut self, origin: &T, checkpoint: &T) -> bool; } -/// Ledger Source Connection -pub trait Connection: PullConfiguration -where - C: Configuration, -{ - /// Push Response Type - /// - /// This is the return type of the [`push`](Self::push) method. Use this type to customize the - /// ledger's response to posting a set of transactions, valid or otherwise. In most cases `bool` - /// or some result type like `Result<(), Error>` is sufficient. In other cases where the ledger - /// cannot respond immediately to the [`push`](Self::push) command, a subscription token can be - /// returned instead which can be used to listen to the result later on. - type PushResponse; - - /// Error Type - /// - /// This error type corresponds to the communication channel itself setup by the [`Connection`] - /// rather than any errors introduced by the [`pull`](Self::pull) or [`push`](Self::push) - /// methods themselves which would correspond to an empty [`PullResponse`] or whatever error - /// variants are stored in [`PushResponse`](Self::PushResponse). - type Error; +/// Ledger Connection Reading +pub trait Read: Connection { + /// Checkpoint Type + type Checkpoint: Checkpoint; - /// Pulls receiver data from the ledger starting from `checkpoint`, returning the current - /// [`Checkpoint`](PullConfiguration::Checkpoint). - fn pull<'s>( + /// Gets data from the ledger starting from `checkpoint`, returning the current + /// [`Checkpoint`](Self::Checkpoint). + fn read<'s>( &'s mut self, checkpoint: &'s Self::Checkpoint, - ) -> LocalBoxFutureResult<'s, PullResponse, Self::Error>; - - /// Sends `posts` to the ledger, returning `true` or `false` depending on whether the entire - /// batch succeeded or not. - fn push( - &mut self, - posts: Vec>, - ) -> LocalBoxFutureResult; + ) -> LocalBoxFutureResult<'s, ReadResponse, Self::Error>; } -/// Ledger Source Pull Response +/// Ledger Connection Read Response /// -/// This `struct` is created by the [`pull`](Connection::pull) method on [`Connection`]. +/// This `struct` is created by the [`read`](Read::read) method on [`Read`]. /// See its documentation for more. #[cfg_attr( feature = "serde", derive(Deserialize, Serialize), - serde( - bound( - deserialize = r" - L::Checkpoint: Deserialize<'de>, - L::ReceiverChunk: Deserialize<'de>, - L::SenderChunk: Deserialize<'de> - ", - serialize = r" - L::Checkpoint: Serialize, - L::ReceiverChunk: Serialize, - L::SenderChunk: Serialize - ", - ), - crate = "manta_util::serde", - deny_unknown_fields - ) -)] -#[derive(derivative::Derivative)] -#[derivative( - Clone(bound = "L::Checkpoint: Clone, L::ReceiverChunk: Clone, L::SenderChunk: Clone"), - Copy(bound = "L::Checkpoint: Copy, L::ReceiverChunk: Copy, L::SenderChunk: Copy"), - Debug(bound = "L::Checkpoint: Debug, L::ReceiverChunk: Debug, L::SenderChunk: Debug"), - Default(bound = "L::Checkpoint: Default, L::ReceiverChunk: Default, L::SenderChunk: Default"), - Eq(bound = "L::Checkpoint: Eq, L::ReceiverChunk: Eq, L::SenderChunk: Eq"), - Hash(bound = "L::Checkpoint: Hash, L::ReceiverChunk: Hash, L::SenderChunk: Hash"), - PartialEq( - bound = "L::Checkpoint: PartialEq, L::ReceiverChunk: PartialEq, L::SenderChunk: PartialEq" - ) + serde(crate = "manta_util::serde", deny_unknown_fields) )] -pub struct PullResponse +#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)] +pub struct ReadResponse where - C: Configuration, - L: PullConfiguration + ?Sized, + T: Checkpoint, { - /// Pull Continuation Flag + /// Read Continuation Flag /// /// The `should_continue` flag is set to `true` if the client should request more data from the - /// ledger to finish the pull. + /// ledger to finish the requested [`read`](Read::read). pub should_continue: bool, - /// Ledger Checkpoint + /// Next Ledger Checkpoint + /// + /// This checkpoint represents the new checkpoint that the client should be synchronized to when + /// incorporating the [`data`] returned by the [`read`] request. To continue the request the + /// client should send this checkpoint in their next call to [`read`]. /// - /// If the `should_continue` flag is set to `true` then `checkpoint` is the next - /// [`Checkpoint`](PullConfiguration::Checkpoint) to request data from the ledger. Otherwise, it - /// represents the current ledger state. - pub checkpoint: L::Checkpoint, + /// [`data`]: Self::data + /// [`read`]: Read::read + pub next_checkpoint: T, - /// Ledger Receiver Chunk - pub receivers: L::ReceiverChunk, + /// Data Payload + /// + /// This is the data payload that was returned by the ledger corresponding to the + /// [`read`](Read::read) request. + pub data: D, +} - /// Ledger Sender Chunk - pub senders: L::SenderChunk, +/// Ledger Connection Writing +pub trait Write: Connection { + /// Ledger Response Type + /// + /// This is the return type of the [`write`] method. Use this type to customize the ledger's + /// response to performing a [`write`] call, valid or otherwise. In most cases `bool` or some + /// result type like `Result<(), Error>` is sufficient. In other cases where the ledger cannot + /// respond immediately to the [`write`] command, a subscription token can be returned instead + /// which can be used to listen to the result later on. + type Response; + + /// Sends the `request` to the ledger, returning its [`Response`](Self::Response). + fn write(&mut self, request: R) -> LocalBoxFutureResult; } diff --git a/manta-accounting/src/wallet/mod.rs b/manta-accounting/src/wallet/mod.rs index 42d071be3..a40c72dbe 100644 --- a/manta-accounting/src/wallet/mod.rs +++ b/manta-accounting/src/wallet/mod.rs @@ -32,14 +32,14 @@ use crate::{ asset::{Asset, AssetId, AssetList, AssetMetadata, AssetValue}, transfer::{ canonical::{Transaction, TransactionKind}, - Configuration, ReceivingKey, + Configuration, ReceivingKey, TransferPost, }, wallet::{ balance::{BTreeMapBalanceState, BalanceState}, - ledger::{Checkpoint, PullResponse}, + ledger::ReadResponse, signer::{ - ReceivingKeyRequest, SignError, SignRequest, SignResponse, SyncError, SyncRequest, - SyncResponse, + ReceivingKeyRequest, SignError, SignRequest, SignResponse, SyncData, SyncError, + SyncRequest, SyncResponse, }, }, }; @@ -62,7 +62,7 @@ pub mod test; pub struct Wallet, B = BTreeMapBalanceState> where C: Configuration, - L: ledger::Connection, + L: ledger::Connection, S: signer::Connection, B: BalanceState, { @@ -70,7 +70,7 @@ where ledger: L, /// Ledger Checkpoint - checkpoint: L::Checkpoint, + checkpoint: S::Checkpoint, /// Signer Connection signer: S, @@ -85,14 +85,14 @@ where impl Wallet where C: Configuration, - L: ledger::Connection, + L: ledger::Connection, S: signer::Connection, B: BalanceState, { /// Builds a new [`Wallet`] without checking if `ledger`, `checkpoint`, `signer`, and `assets` /// are properly synchronized. #[inline] - fn new_unchecked(ledger: L, checkpoint: L::Checkpoint, signer: S, assets: B) -> Self { + fn new_unchecked(ledger: L, checkpoint: S::Checkpoint, signer: S, assets: B) -> Self { Self { ledger, checkpoint, @@ -154,10 +154,10 @@ where &self.ledger } - /// Returns the [`Checkpoint`](ledger::PullConfiguration::Checkpoint) representing the current - /// state of this wallet. + /// Returns the [`Checkpoint`](ledger::Checkpoint) representing the current state of this + /// wallet. #[inline] - pub fn checkpoint(&self) -> &L::Checkpoint { + pub fn checkpoint(&self) -> &S::Checkpoint { &self.checkpoint } @@ -183,7 +183,10 @@ where /// [`InconsistencyError`] type for more information on the kinds of errors that can occur and /// how to resolve them. #[inline] - pub async fn recover(&mut self) -> Result<(), Error> { + pub async fn recover(&mut self) -> Result<(), Error> + where + L: ledger::Read, Checkpoint = S::Checkpoint>, + { self.reset(); while self.sync_with(true).await?.is_continue() {} Ok(()) @@ -200,7 +203,10 @@ where /// [`InconsistencyError`] type for more information on the kinds of errors that can occur and /// how to resolve them. #[inline] - pub async fn sync(&mut self) -> Result<(), Error> { + pub async fn sync(&mut self) -> Result<(), Error> + where + L: ledger::Read, Checkpoint = S::Checkpoint>, + { while self.sync_partial().await?.is_continue() {} Ok(()) } @@ -216,33 +222,37 @@ where /// [`InconsistencyError`] type for more information on the kinds of errors that can occur and /// how to resolve them. #[inline] - pub async fn sync_partial(&mut self) -> Result> { + pub async fn sync_partial(&mut self) -> Result> + where + L: ledger::Read, Checkpoint = S::Checkpoint>, + { self.sync_with(false).await } /// Pulls data from the ledger, synchronizing the wallet and balance state. #[inline] - async fn sync_with(&mut self, with_recovery: bool) -> Result> { - let PullResponse { + async fn sync_with(&mut self, with_recovery: bool) -> Result> + where + L: ledger::Read, Checkpoint = S::Checkpoint>, + { + let ReadResponse { should_continue, - checkpoint, - receivers, - senders, + next_checkpoint, + data, } = self .ledger - .pull(&self.checkpoint) + .read(&self.checkpoint) .await .map_err(Error::LedgerConnectionError)?; - if checkpoint < self.checkpoint { + if next_checkpoint < self.checkpoint { return Err(Error::Inconsistency(InconsistencyError::LedgerCheckpoint)); } match self .signer .sync(SyncRequest { with_recovery, - starting_index: self.checkpoint.receiver_index(), - inserts: receivers.into_iter().collect(), - removes: senders.into_iter().collect(), + origin_checkpoint: self.checkpoint.clone(), + data, }) .await .map_err(Error::SignerConnectionError)? @@ -257,24 +267,16 @@ where self.assets.clear(); self.assets.deposit_all(assets); } - Err(SyncError::InconsistentSynchronization { starting_index }) => { - // FIXME: What should be done when we receive an `InconsistentSynchronization` error - // from the signer? - // - One option is to do some sort of (exponential) backoff algorithm to - // find the point at which the signer and the wallet are able to - // synchronize again. The correct algorithm may be simply to exchange - // some checkpoints between the signer and the wallet until they can - // agree on a minimal one. - // - In the worst case we would have to recover the wallet (not necessarily - // the signer), which is what the docs currently recommend. - // - let _ = starting_index; + Err(SyncError::InconsistentSynchronization { checkpoint }) => { + if checkpoint < self.checkpoint { + self.checkpoint = checkpoint; + } return Err(Error::Inconsistency( InconsistencyError::SignerSynchronization, )); } } - self.checkpoint = checkpoint; + self.checkpoint = next_checkpoint; Ok(ControlFlow::should_continue(should_continue)) } @@ -291,42 +293,60 @@ where transaction.check(move |a| self.contains(a)) } - /// Posts a transaction to the ledger, returning `true` if the `transaction` was successfully - /// saved onto the ledger. This method automatically synchronizes with the ledger before - /// posting, _but not after_. To amortize the cost of future calls to [`post`](Self::post), the - /// [`sync`](Self::sync) method can be used to synchronize with the ledger. + /// Signs the `transaction` using the signer connection, sending `metadata` for context. This + /// method _does not_ automatically sychronize with the ledger. To do this, call the + /// [`sync`](Self::sync) method separately. + #[inline] + pub async fn sign( + &mut self, + transaction: Transaction, + metadata: Option, + ) -> Result, Error> { + self.check(&transaction) + .map_err(Error::InsufficientBalance)?; + self.signer + .sign(SignRequest { + transaction, + metadata, + }) + .await + .map_err(Error::SignerConnectionError)? + .map_err(Error::SignError) + } + + /// Posts a transaction to the ledger, returning a success [`Response`] if the `transaction` + /// was successfully posted to the ledger. This method automatically synchronizes with the + /// ledger before posting, _but not after_. To amortize the cost of future calls to [`post`], + /// the [`sync`] method can be used to synchronize with the ledger. /// /// # Failure Conditions /// - /// This method returns `false` when there were no errors in producing transfer data and + /// This method returns a [`Response`] when there were no errors in producing transfer data and /// sending and receiving from the ledger, but instead the ledger just did not accept the - /// transaction as is. This could be caused by an external update to the ledger while the - /// signer was building the transaction that caused the wallet and the ledger to get out of - /// sync. In this case, [`post`](Self::post) can safely be called again, to retry the - /// transaction. + /// transaction as is. This could be caused by an external update to the ledger while the signer + /// was building the transaction that caused the wallet and the ledger to get out of sync. In + /// this case, [`post`] can safely be called again, to retry the transaction. /// /// This method returns an error in any other case. The internal state of the wallet is kept /// consistent between calls and recoverable errors are returned for the caller to handle. + /// + /// [`Response`]: ledger::Write::Response + /// [`post`]: Self::post + /// [`sync`]: Self::sync #[inline] pub async fn post( &mut self, transaction: Transaction, metadata: Option, - ) -> Result> { + ) -> Result> + where + L: ledger::Read, Checkpoint = S::Checkpoint> + + ledger::Write>>, + { self.sync().await?; - self.check(&transaction) - .map_err(Error::InsufficientBalance)?; - let SignResponse { posts } = self - .signer - .sign(SignRequest { - transaction, - metadata, - }) - .await - .map_err(Error::SignerConnectionError)? - .map_err(Error::SignError)?; + let SignResponse { posts } = self.sign(transaction, metadata).await?; self.ledger - .push(posts) + .write(posts) .await .map_err(Error::LedgerConnectionError) } @@ -421,7 +441,7 @@ pub enum InconsistencyError { pub enum Error where C: Configuration, - L: ledger::Connection, + L: ledger::Connection, S: signer::Connection, { /// Insufficient Balance @@ -441,3 +461,15 @@ where /// Ledger Connection Error LedgerConnectionError(L::Error), } + +impl From for Error +where + C: Configuration, + L: ledger::Connection, + S: signer::Connection, +{ + #[inline] + fn from(err: InconsistencyError) -> Self { + Self::Inconsistency(err) + } +} diff --git a/manta-accounting/src/wallet/signer.rs b/manta-accounting/src/wallet/signer.rs index fbaf4956c..b69b47f18 100644 --- a/manta-accounting/src/wallet/signer.rs +++ b/manta-accounting/src/wallet/signer.rs @@ -40,6 +40,7 @@ use crate::{ ProvingContext, Receiver, ReceivingKey, SecretKey, Sender, SpendingKey, Transfer, TransferPost, Utxo, VoidNumber, }, + wallet::ledger::{Checkpoint, Prune}, }; use alloc::{boxed::Box, vec, vec::Vec}; use core::{convert::Infallible, fmt::Debug, hash::Hash}; @@ -49,11 +50,7 @@ use manta_crypto::{ rand::{CryptoRng, FromEntropy, Rand, RngCore}, }; use manta_util::{ - array_map, - cache::{CachedResource, CachedResourceError}, - future::LocalBoxFutureResult, - into_array_unchecked, - iter::IteratorExt, + array_map, future::LocalBoxFutureResult, into_array_unchecked, iter::IteratorExt, persistence::Rollback, }; @@ -65,6 +62,11 @@ pub trait Connection where C: transfer::Configuration, { + /// Checkpoint Type + /// + /// This checkpoint is used by the signer to stay synchronized with wallet and the ledger. + type Checkpoint: Checkpoint; + /// Error Type /// /// This is the error type for the connection itself, not for an error produced during one of @@ -75,8 +77,8 @@ where /// returning an updated asset distribution. fn sync( &mut self, - request: SyncRequest, - ) -> LocalBoxFutureResult, Self::Error>; + request: SyncRequest, + ) -> LocalBoxFutureResult>, Self::Error>; /// Signs a transaction and returns the ledger transfer posts if successful. fn sign( @@ -91,7 +93,7 @@ where ) -> LocalBoxFutureResult>, Self::Error>; } -/// Signer Synchronization Request +/// Signer Synchronization Data #[cfg_attr( feature = "serde", derive(Deserialize, Serialize), @@ -121,9 +123,53 @@ where Hash(bound = "Utxo: Hash, EncryptedNote: Hash, VoidNumber: Hash"), PartialEq(bound = "Utxo: PartialEq, EncryptedNote: PartialEq, VoidNumber: PartialEq") )] -pub struct SyncRequest +pub struct SyncData +where + C: transfer::Configuration + ?Sized, +{ + /// Receiver Data + pub receivers: Vec<(Utxo, EncryptedNote)>, + + /// Sender Data + pub senders: Vec>, +} + +impl Prune for SyncData +where + C: Configuration, +{ + #[inline] + fn prune(&mut self, origin: &C::Checkpoint, checkpoint: &C::Checkpoint) -> bool { + C::prune_sync_data(self, origin, checkpoint) + } +} + +/// Signer Synchronization Request +#[cfg_attr( + feature = "serde", + derive(Deserialize, Serialize), + serde( + bound( + deserialize = "T: Deserialize<'de>, SyncData: Deserialize<'de>", + serialize = "T: Serialize, SyncData: Serialize", + ), + crate = "manta_util::serde", + deny_unknown_fields + ) +)] +#[derive(derivative::Derivative)] +#[derivative( + Clone(bound = "T: Clone, SyncData: Clone"), + Debug(bound = "T: Debug, SyncData: Debug"), + Default(bound = "T: Default, SyncData: Default"), + Eq(bound = "T: Eq, SyncData: Eq"), + Hash(bound = "T: Hash, SyncData: Hash"), + PartialEq(bound = "T: PartialEq, SyncData: PartialEq") +)] +pub struct SyncRequest where C: transfer::Configuration, + T: Checkpoint, { /// Recovery Flag /// @@ -134,21 +180,33 @@ where /// [`GAP_LIMIT`]: HierarchicalKeyDerivationScheme::GAP_LIMIT pub with_recovery: bool, - /// Starting Index + /// Origin Checkpoint /// - /// This index is the starting point for insertions and indicates how far into the - /// [`UtxoAccumulator`] the insertions received are starting from. The signer may be ahead of - /// this index and so can skip those UTXOs which are in between the `starting_index` and its own - /// internal index. - /// - /// [`UtxoAccumulator`]: Configuration::UtxoAccumulator - pub starting_index: usize, + /// This checkpoint was the one that was used to retrieve the [`data`](Self::data) from the + /// ledger. + pub origin_checkpoint: T, - /// Balance Insertions - pub inserts: Vec<(Utxo, EncryptedNote)>, + /// Ledger Synchronization Data + pub data: SyncData, +} - /// Balance Removals - pub removes: Vec>, +impl SyncRequest +where + C: transfer::Configuration, + T: Checkpoint, +{ + /// Prunes the [`data`] in `self` according to the target `checkpoint` given that + /// [`origin_checkpoint`] was the origin of the data. + /// + /// [`data`]: Self::data + /// [`origin_checkpoint`]: Self::origin_checkpoint + #[inline] + pub fn prune(&mut self, checkpoint: &T) -> bool + where + SyncData: Prune, + { + self.data.prune(&self.origin_checkpoint, checkpoint) + } } /// Signer Synchronization Response @@ -164,8 +222,8 @@ where pub enum SyncResponse { /// Partial Update /// - /// This is the typical response from the [`Signer`]. In rare cases, we may need to perform a - /// [`Full`](Self::Full) update. + /// This is the typical response from the [`Signer`]. In rare de-synchronization cases, we may + /// need to perform a [`Full`](Self::Full) update. Partial { /// Assets Deposited in the Last Update deposit: Vec, @@ -195,11 +253,19 @@ pub enum SyncResponse { serde(crate = "manta_util::serde", deny_unknown_fields) )] #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -pub enum SyncError { +pub enum SyncError +where + T: Checkpoint, +{ /// Inconsistent Synchronization + /// + /// This error occurs whenever the signer checkpoint gets behind the wallet checkpoint and + /// cannot safely process the incoming data. The data is dropped and the signer checkpoint is + /// sent back up to the wallet. If the wallet determines that it can safely re-synchronize with + /// this older checkpoint then it will try again and fetch older data from the ledger. InconsistentSynchronization { - /// Desired starting index to fix synchronization - starting_index: usize, + /// Signer Checkpoint + checkpoint: T, }, } @@ -310,9 +376,6 @@ pub enum SignError where C: transfer::Configuration, { - /// Proving Context Cache Error - ProvingContextCacheError, - /// Insufficient Balance InsufficientBalance(Asset), @@ -356,6 +419,9 @@ pub enum ReceivingKeyRequest { /// Signer Configuration pub trait Configuration: transfer::Configuration { + /// Checkpoint Type + type Checkpoint: Checkpoint; + /// Hierarchical Key Derivation Scheme type HierarchicalKeyDerivationScheme: HierarchicalKeyDerivationScheme< SecretKey = SecretKey, @@ -370,11 +436,22 @@ pub trait Configuration: transfer::Configuration { /// Asset Map Type type AssetMap: AssetMap>; - /// Proving Context Cache - type ProvingContextCache: CachedResource>; - /// Random Number Generator Type type Rng: CryptoRng + FromEntropy + RngCore; + + /// Updates the given `checkpoint` to match the state of the `utxo_accumulator`. + fn update_checkpoint( + checkpoint: &Self::Checkpoint, + utxo_accumulator: &Self::UtxoAccumulator, + ) -> Self::Checkpoint; + + /// Prunes the `data` required for a [`sync`](Connection::sync) call against `origin` and + /// `checkpoint`. + fn prune_sync_data( + data: &mut SyncData, + origin: &Self::Checkpoint, + checkpoint: &Self::Checkpoint, + ) -> bool; } /// Account Table Type @@ -383,18 +460,14 @@ pub type AccountTable = key::AccountTable<::HierarchicalK /// Asset Map Key Type pub type AssetMapKey = (KeyIndex, SecretKey); -/// Proving Context Cache Error Type -pub type ProvingContextCacheError = - CachedResourceError, ::ProvingContextCache>; - /// Signer Parameters #[derive(derivative::Derivative)] #[derivative( - Clone(bound = "Parameters: Clone, C::ProvingContextCache: Clone"), - Debug(bound = "Parameters: Debug, C::ProvingContextCache: Debug"), - Eq(bound = "Parameters: Eq, C::ProvingContextCache: Eq"), - Hash(bound = "Parameters: Hash, C::ProvingContextCache: Hash"), - PartialEq(bound = "Parameters: PartialEq, C::ProvingContextCache: PartialEq") + Clone(bound = "Parameters: Clone, MultiProvingContext: Clone"), + Debug(bound = "Parameters: Debug, MultiProvingContext: Debug"), + Eq(bound = "Parameters: Eq, MultiProvingContext: Eq"), + Hash(bound = "Parameters: Hash, MultiProvingContext: Hash"), + PartialEq(bound = "Parameters: PartialEq, MultiProvingContext: PartialEq") )] pub struct SignerParameters where @@ -404,7 +477,7 @@ where pub parameters: Parameters, /// Proving Context - pub proving_context: C::ProvingContextCache, + pub proving_context: MultiProvingContext, } impl SignerParameters @@ -413,22 +486,13 @@ where { /// Builds a new [`SignerParameters`] from `parameters` and `proving_context`. #[inline] - pub fn new(parameters: Parameters, proving_context: C::ProvingContextCache) -> Self { + pub fn new(parameters: Parameters, proving_context: MultiProvingContext) -> Self { Self { parameters, proving_context, } } - /// Returns the public parameters by reading from the proving context cache. - #[inline] - pub fn get( - &mut self, - ) -> Result<(&Parameters, &MultiProvingContext), ProvingContextCacheError> { - let reading_key = self.proving_context.aquire()?; - Ok((&self.parameters, self.proving_context.read(reading_key))) - } - /// Converts `keypair` into a [`ReceivingKey`] by using the key-agreement scheme to derive the /// public keys associated to `keypair`. #[inline] @@ -528,7 +592,7 @@ where encrypted_note: EncryptedNote, void_numbers: &mut Vec>, deposit: &mut Vec, - ) -> Result<(), SyncError> { + ) -> Result<(), SyncError> { let mut finder = DecryptedMessage::find(encrypted_note); if let Some(ViewKeySelection { index, @@ -603,7 +667,7 @@ where inserts: I, mut void_numbers: Vec>, is_partial: bool, - ) -> Result + ) -> Result> where I: Iterator, EncryptedNote)>, { @@ -953,7 +1017,7 @@ where #[inline] fn new_inner( accounts: AccountTable, - proving_context: C::ProvingContextCache, + proving_context: MultiProvingContext, parameters: Parameters, utxo_accumulator: C::UtxoAccumulator, assets: C::AssetMap, @@ -982,7 +1046,7 @@ where #[inline] pub fn new( accounts: AccountTable, - proving_context: C::ProvingContextCache, + proving_context: MultiProvingContext, parameters: Parameters, utxo_accumulator: C::UtxoAccumulator, rng: C::Rng, @@ -1011,29 +1075,31 @@ where /// Updates the internal ledger state, returning the new asset distribution. #[inline] - pub fn sync(&mut self, request: SyncRequest) -> Result { + pub fn sync( + &mut self, + mut request: SyncRequest, + ) -> Result> { // TODO: Do a capacity check on the current UTXO accumulator? // // if self.utxo_accumulator.capacity() < starting_index { // panic!("full capacity") // } - // - let utxo_accumulator_len = self.state.utxo_accumulator.len(); - match utxo_accumulator_len.checked_sub(request.starting_index) { - Some(diff) => { - let result = self.state.sync_with( - &self.parameters.parameters, - request.with_recovery, - request.inserts.into_iter().skip(diff), - request.removes, - diff == 0, - ); - self.state.utxo_accumulator.commit(); - result - } - _ => Err(SyncError::InconsistentSynchronization { - starting_index: utxo_accumulator_len, - }), + let checkpoint = + C::update_checkpoint(&request.origin_checkpoint, &self.state.utxo_accumulator); + if checkpoint < request.origin_checkpoint { + Err(SyncError::InconsistentSynchronization { checkpoint }) + } else { + let has_pruned = request.prune(&checkpoint); + let SyncData { receivers, senders } = request.data; + let result = self.state.sync_with( + &self.parameters.parameters, + request.with_recovery, + receivers.into_iter(), + senders, + !has_pruned, + ); + self.state.utxo_accumulator.commit(); + result } } @@ -1048,30 +1114,28 @@ where let change = self .state .build_receiver(&self.parameters.parameters, asset.id.with(selection.change))?; - let (parameters, proving_context) = self - .parameters - .get() - .map_err(|_| SignError::ProvingContextCacheError)?; let mut posts = Vec::new(); let senders = self.state.compute_batched_transactions( - parameters, - proving_context, + &self.parameters.parameters, + &self.parameters.proving_context, asset.id, selection.pre_senders, &mut posts, )?; let final_post = match receiver { Some(receiver) => { - let receiver = self.state.prepare_receiver(parameters, asset, receiver); + let receiver = + self.state + .prepare_receiver(&self.parameters.parameters, asset, receiver); self.state.private_transfer_post( - parameters, - &proving_context.private_transfer, + &self.parameters.parameters, + &self.parameters.proving_context.private_transfer, PrivateTransfer::build(senders, [change, receiver]), )? } _ => self.state.reclaim_post( - parameters, - &proving_context.reclaim, + &self.parameters.parameters, + &self.parameters.proving_context.reclaim, Reclaim::build(senders, [change], asset), )?, }; @@ -1090,13 +1154,9 @@ where let receiver = self .state .build_receiver(&self.parameters.parameters, asset)?; - let (parameters, proving_context) = self - .parameters - .get() - .map_err(|_| SignError::ProvingContextCacheError)?; Ok(SignResponse::new(vec![self.state.mint_post( - parameters, - &proving_context.mint, + &self.parameters.parameters, + &self.parameters.proving_context.mint, Mint::build(asset, receiver), )?])) } @@ -1110,11 +1170,8 @@ where /// Signs the `transaction`, generating transfer posts. #[inline] pub fn sign(&mut self, transaction: Transaction) -> Result, SignError> { - // TODO: Should we do a time-based release mechanism to amortize the cost of reading - // from the proving context cache? let result = self.sign_internal(transaction); self.state.utxo_accumulator.rollback(); - self.parameters.proving_context.release(); result } @@ -1152,13 +1209,14 @@ impl Connection for Signer where C: Configuration, { + type Checkpoint = C::Checkpoint; type Error = Infallible; #[inline] fn sync( &mut self, - request: SyncRequest, - ) -> LocalBoxFutureResult, Self::Error> { + request: SyncRequest, + ) -> LocalBoxFutureResult>, Self::Error> { Box::pin(async move { Ok(self.sync(request)) }) } diff --git a/manta-accounting/src/wallet/test/mod.rs b/manta-accounting/src/wallet/test/mod.rs index 8bd23961d..d84291530 100644 --- a/manta-accounting/src/wallet/test/mod.rs +++ b/manta-accounting/src/wallet/test/mod.rs @@ -21,20 +21,20 @@ use crate::{ asset::{Asset, AssetList}, - transfer::{self, canonical::Transaction, PublicKey, ReceivingKey}, + transfer::{self, canonical::Transaction, PublicKey, ReceivingKey, TransferPost}, wallet::{ ledger, - signer::{self, ReceivingKeyRequest}, + signer::{self, ReceivingKeyRequest, SyncData}, BalanceState, Error, Wallet, }, }; -use alloc::{boxed::Box, sync::Arc}; +use alloc::{boxed::Box, sync::Arc, vec::Vec}; use core::{fmt::Debug, future::Future, hash::Hash, marker::PhantomData}; use futures::StreamExt; use indexmap::IndexSet; use manta_crypto::rand::{CryptoRng, RngCore, Sample}; use manta_util::future::LocalBoxFuture; -use parking_lot::RwLock; +use parking_lot::Mutex; use rand::{distributions::Distribution, Rng}; use statrs::{distribution::Categorical, StatsError}; @@ -106,12 +106,12 @@ impl Default for ActionDistributionPMF { #[inline] fn default() -> Self { Self { - skip: 0, - mint: 4, - private_transfer: 8, - reclaim: 2, - generate_public_key: 2, - recover: 3, + skip: 2, + mint: 5, + private_transfer: 9, + reclaim: 3, + generate_public_key: 3, + recover: 4, } } } @@ -183,12 +183,30 @@ pub trait PublicBalanceOracle { fn public_balances(&self) -> LocalBoxFuture>; } +/// Ledger Alias Trait +/// +/// This `trait` is used as an alias for the [`Read`](ledger::Read) and [`Write`](ledger::Write) +/// requirements for the simulation ledger. +pub trait Ledger: + ledger::Read> + ledger::Write>, Response = bool> +where + C: transfer::Configuration, +{ +} + +impl Ledger for L +where + C: transfer::Configuration, + L: ledger::Read> + ledger::Write>, Response = bool>, +{ +} + /// Actor pub struct Actor where C: transfer::Configuration, - L: ledger::Connection, - S: signer::Connection, + L: Ledger, + S: signer::Connection, { /// Wallet pub wallet: Wallet, @@ -203,8 +221,8 @@ where impl Actor where C: transfer::Configuration, - L: ledger::Connection, - S: signer::Connection, + L: Ledger, + S: signer::Connection, { /// Builds a new [`Actor`] with `wallet`, `distribution`, and `lifetime`. #[inline] @@ -276,25 +294,25 @@ where /// Simulation Event #[derive(derivative::Derivative)] -#[derivative(Debug(bound = "L::PushResponse: Debug, Error: Debug"))] +#[derivative(Debug(bound = "L::Response: Debug, Error: Debug"))] pub struct Event where C: transfer::Configuration, - L: ledger::Connection, - S: signer::Connection, + L: Ledger, + S: signer::Connection, { /// Action Type pub action: ActionType, /// Action Result - pub result: Result>, + pub result: Result>, } /// Public Key Database pub type PublicKeyDatabase = IndexSet>; /// Shared Public Key Database -pub type SharedPublicKeyDatabase = Arc>>; +pub type SharedPublicKeyDatabase = Arc>>; /// Simulation #[derive(derivative::Derivative)] @@ -302,8 +320,8 @@ pub type SharedPublicKeyDatabase = Arc>>; pub struct Simulation where C: transfer::Configuration, - L: ledger::Connection, - S: signer::Connection, + L: Ledger, + S: signer::Connection, PublicKey: Eq + Hash, { /// Public Key Database @@ -316,15 +334,15 @@ where impl Simulation where C: transfer::Configuration, - L: ledger::Connection, - S: signer::Connection, + L: Ledger, + S: signer::Connection, PublicKey: Eq + Hash, { /// Builds a new [`Simulation`] with a starting set of public `keys`. #[inline] pub fn new(keys: [ReceivingKey; N]) -> Self { Self { - public_keys: Arc::new(RwLock::new(keys.into_iter().collect())), + public_keys: Arc::new(Mutex::new(keys.into_iter().collect())), __: PhantomData, } } @@ -333,8 +351,8 @@ where impl sim::ActionSimulation for Simulation where C: transfer::Configuration, - L: ledger::Connection + PublicBalanceOracle, - S: signer::Connection, + L: Ledger + PublicBalanceOracle, + S: signer::Connection, PublicKey: Eq + Hash, { type Actor = Actor; @@ -361,7 +379,7 @@ where }, ActionType::PrivateTransfer => match actor.sample_withdraw(rng).await { Some(asset) => { - let public_keys = self.public_keys.read(); + let public_keys = self.public_keys.lock(); let len = public_keys.len(); if len == 0 { Action::GeneratePublicKey @@ -432,7 +450,7 @@ where { Ok(keys) => { for key in keys { - self.public_keys.write().insert(key); + self.public_keys.lock().insert(key); } Ok(true) } @@ -453,8 +471,8 @@ where pub async fn measure_balances<'w, C, L, S, I>(wallets: I) -> Result> where C: 'w + transfer::Configuration, - L: 'w + ledger::Connection + PublicBalanceOracle, - S: 'w + signer::Connection, + L: 'w + Ledger + PublicBalanceOracle, + S: 'w + signer::Connection, I: IntoIterator>, { let mut balances = AssetList::new(); @@ -497,13 +515,13 @@ impl Config { ) -> Result> where C: transfer::Configuration, - L: ledger::Connection + PublicBalanceOracle, - S: signer::Connection, + L: Ledger + PublicBalanceOracle, + S: signer::Connection, R: CryptoRng + RngCore, GL: FnMut(usize) -> L, GS: FnMut(usize) -> S, F: FnMut() -> R, - ES: FnMut(&sim::Event>>) -> ESFut, + ES: Copy + FnMut(&sim::Event>>) -> ESFut, ESFut: Future, Error: Debug, PublicKey: Eq + Hash, @@ -522,14 +540,12 @@ impl Config { let mut simulator = sim::Simulator::new(sim::ActionSim(Simulation::default()), actors); let initial_balances = measure_balances(simulator.actors.iter_mut().map(|actor| &mut actor.wallet)).await?; - let mut events = simulator.run(rng); - while let Some(event) = events.next().await { - event_subscriber(&event).await; - if let Err(err) = event.event.result { - return Err(err); - } - } - drop(events); + simulator + .run(rng) + .for_each_concurrent(None, move |event| async move { + event_subscriber(&event).await; + }) + .await; let final_balances = measure_balances(simulator.actors.iter_mut().map(|actor| &mut actor.wallet)).await?; Ok(initial_balances == final_balances) diff --git a/manta-crypto/src/rand.rs b/manta-crypto/src/rand.rs index dcc5f7517..9fa6d0017 100644 --- a/manta-crypto/src/rand.rs +++ b/manta-crypto/src/rand.rs @@ -218,11 +218,10 @@ macro_rules! impl_sample_from_u32 { $( impl Sample for $type { #[inline] - fn sample(distribution: (), rng: &mut R) -> Self + fn sample(_: (), rng: &mut R) -> Self where R: RngCore + ?Sized, { - let _ = distribution; rng.next_u32() as Self } } diff --git a/manta-pay/Cargo.toml b/manta-pay/Cargo.toml index a72fc731c..26133ae01 100644 --- a/manta-pay/Cargo.toml +++ b/manta-pay/Cargo.toml @@ -117,7 +117,7 @@ ark-std = { version = "0.3.0", optional = true, default-features = false } bip32 = { version = "0.3.0", optional = true, default-features = false, features = ["bip39", "secp256k1"] } blake2 = { version = "0.10.4", default-features = false } bs58 = { version = "0.4.0", optional = true, default-features = false, features = ["alloc"] } -clap = { version = "3.1.15", optional = true, default-features = false, features = ["color", "derive", "std", "suggestions", "unicode", "wrap_help"] } +clap = { version = "3.1.18", optional = true, default-features = false, features = ["color", "derive", "std", "suggestions", "unicode", "wrap_help"] } derivative = { version = "2.2.0", default-features = false, features = ["use_core"] } futures = { version = "0.3.21", optional = true, default-features = false } indexmap = { version = "1.8.0", optional = true, default-features = false } @@ -132,7 +132,7 @@ scale-codec = { package = "parity-scale-codec", version = "2.3.1", optional = tr scale-info = { version = "1.0.0", optional = true, default-features = false, features = ["derive"] } serde_json = { version = "1.0.79", optional = true, default-features = false, features = ["alloc"] } tide = { version = "0.16.0", optional = true, default-features = false, features = ["h1-server"] } -tokio = { version = "1.17.0", optional = true, default-features = false } +tokio = { version = "1.18.2", optional = true, default-features = false } tokio-tungstenite = { version = "0.17.1", optional = true, default-features = false, features = ["native-tls"] } ws_stream_wasm = { version = "0.7.3", optional = true, default-features = false } # TODO: zk-garage-plonk = { package = "plonk", git = "https://github.com/zk-garage/plonk", optional = true, default-features = false } diff --git a/manta-pay/src/signer/base.rs b/manta-pay/src/signer/base.rs index b3ef3aed7..a050c7d91 100644 --- a/manta-pay/src/signer/base.rs +++ b/manta-pay/src/signer/base.rs @@ -17,19 +17,24 @@ //! Manta Pay Signer Configuration use crate::{ - config::{Bls12_381_Edwards, Config, MerkleTreeConfiguration, MultiProvingContext, SecretKey}, + config::{Bls12_381_Edwards, Config, MerkleTreeConfiguration, SecretKey}, crypto::constraint::arkworks::Fp, key::{CoinType, KeySecret, Testnet, TestnetKeySecret}, + signer::Checkpoint, }; +use alloc::collections::BTreeMap; use ark_ec::ProjectiveCurve; use ark_ff::PrimeField; -use core::marker::PhantomData; +use core::{marker::PhantomData, mem}; use manta_accounting::{ asset::HashAssetMap, key::{self, HierarchicalKeyDerivationScheme}, - wallet::{self, signer::AssetMapKey}, + wallet::{ + self, + signer::{AssetMapKey, SyncData}, + }, }; -use manta_crypto::{key::KeyDerivationFunction, merkle_tree}; +use manta_crypto::{key::KeyDerivationFunction, merkle_tree, merkle_tree::forest::Configuration}; #[cfg(feature = "serde")] use manta_util::serde::{Deserialize, Serialize}; @@ -75,165 +80,83 @@ pub type UtxoAccumulator = merkle_tree::forest::TreeArrayMerkleForest< { MerkleTreeConfiguration::FOREST_WIDTH }, >; -/// Proving Context Cache -pub mod cache { - use super::*; - use crate::config::ProvingContext; - use core::marker::PhantomData; - use manta_util::{ - cache::CachedResource, - codec::{Decode, Encode, IoReader, IoWriter}, - }; - use std::{ - fs::{File, OpenOptions}, - io, - path::{Path, PathBuf}, - }; - - /// Caching Error - #[derive(Debug)] - pub enum Error { - /// Encoding Error - Encode, - - /// Decoding Error - Decode, +impl wallet::signer::Configuration for Config { + type Checkpoint = Checkpoint; + type HierarchicalKeyDerivationScheme = + key::Map; + type UtxoAccumulator = UtxoAccumulator; + type AssetMap = HashAssetMap>; + type Rng = rand_chacha::ChaCha20Rng; - /// I/O Error - Io(io::Error), + #[inline] + fn update_checkpoint( + checkpoint: &Self::Checkpoint, + utxo_accumulator: &Self::UtxoAccumulator, + ) -> Self::Checkpoint { + Checkpoint::new( + utxo_accumulator + .forest + .as_ref() + .iter() + .map(move |t| t.len()) + .collect(), + checkpoint.sender_index, + ) } - impl From for Error { - #[inline] - fn from(err: io::Error) -> Self { - Self::Io(err) + #[inline] + fn prune_sync_data( + data: &mut SyncData, + origin: &Self::Checkpoint, + checkpoint: &Self::Checkpoint, + ) -> bool { + const PRUNE_PANIC_MESSAGE: &str = "ERROR: Invalid pruning conditions"; + if checkpoint < origin { + return false; } - } - - /// Cache Reading Key - pub struct ReadingKey(PhantomData<()>); - - impl ReadingKey { - #[inline] - fn new() -> Self { - Self(PhantomData) + match checkpoint.sender_index.checked_sub(origin.sender_index) { + Some(diff) => drop(data.senders.drain(0..diff)), + _ => panic!( + "{}: Sender Pruning: {:?} {:?} {:?}", + PRUNE_PANIC_MESSAGE, data, origin, checkpoint + ), } - } - - /// On-Disk Multi-Proving Context - pub struct OnDiskMultiProvingContext { - /// Source Directory - directory: PathBuf, - - /// Current Cached Context - context: Option, - } - - impl OnDiskMultiProvingContext { - /// Builds a new [`OnDiskMultiProvingContext`] setting the source directory to `directory`. - /// - /// To save the cache data to disk, use [`save`](Self::save). - #[inline] - pub fn new

(directory: P) -> Self - where - P: AsRef, - { - Self { - directory: directory.as_ref().to_owned(), - context: None, + let mut data_map = BTreeMap::<_, Vec<_>>::new(); + for receiver in mem::take(&mut data.receivers) { + let key = MerkleTreeConfiguration::tree_index(&receiver.0); + match data_map.get_mut(&key) { + Some(entry) => entry.push(receiver), + _ => { + data_map.insert(key, vec![receiver]); + } } } - - /// Returns the directory where `self` stores the [`MultiProvingContext`]. - #[inline] - pub fn directory(&self) -> &Path { - &self.directory - } - - /// Reads a single [`ProvingContext`] from `path`. - #[inline] - fn read_context

(path: P) -> Result - where - P: AsRef, + let mut has_pruned = false; + for (i, (origin_index, index)) in origin + .receiver_index + .into_iter() + .zip(checkpoint.receiver_index) + .enumerate() { - File::open(path.as_ref()) - .map_err(Error::Io) - .and_then(move |f| ProvingContext::decode(IoReader(f)).map_err(|_| Error::Decode)) - } - - /// Writes `context` to `path`. - #[inline] - fn write_context

(path: P, context: ProvingContext) -> Result<(), Error> - where - P: AsRef, - { - OpenOptions::new() - .write(true) - .create(true) - .open(path.as_ref()) - .map_err(Error::Io) - .and_then(move |f| context.encode(IoWriter(f)).map_err(|_| Error::Encode)) - } - - /// Saves the `context` to the on-disk directory. This method _does not_ write `context` into - /// the cache. - #[inline] - pub fn save(&self, context: MultiProvingContext) -> Result<(), Error> { - Self::write_context(self.directory.join("mint.pk"), context.mint)?; - Self::write_context( - self.directory.join("private-transfer.pk"), - context.private_transfer, - )?; - Self::write_context(self.directory.join("reclaim.pk"), context.reclaim)?; - Ok(()) - } - } - - impl CachedResource for OnDiskMultiProvingContext { - type ReadingKey = ReadingKey; - type Error = Error; - - #[inline] - fn aquire(&mut self) -> Result { - self.context = Some(MultiProvingContext { - mint: Self::read_context(self.directory.join("mint.pk"))?, - private_transfer: Self::read_context(self.directory.join("private-transfer.pk"))?, - reclaim: Self::read_context(self.directory.join("reclaim.pk"))?, - }); - Ok(ReadingKey::new()) - } - - #[inline] - fn read(&self, reading_key: Self::ReadingKey) -> &MultiProvingContext { - // SAFETY: Since `reading_key` is only given out when we know that `context` is `Some`, - // we can safely `unwrap` here. - let _ = reading_key; - self.context.as_ref().unwrap() - } - - #[inline] - fn release(&mut self) { - self.context.take(); - } - } - - impl Clone for OnDiskMultiProvingContext { - #[inline] - fn clone(&self) -> Self { - Self::new(&self.directory) + match index.checked_sub(origin_index) { + Some(diff) => { + if let Some(entries) = data_map.remove(&(i as u8)) { + data.receivers.extend(entries.into_iter().skip(diff)); + if diff > 0 { + has_pruned = true; + } + } + } + _ => panic!( + "{}: Receiver Pruning: {:?} {:?} {:?}", + PRUNE_PANIC_MESSAGE, data, origin, checkpoint + ), + } } + has_pruned } } -impl wallet::signer::Configuration for Config { - type HierarchicalKeyDerivationScheme = - key::Map; - type UtxoAccumulator = UtxoAccumulator; - type AssetMap = HashAssetMap>; - type ProvingContextCache = MultiProvingContext; - type Rng = rand_chacha::ChaCha20Rng; -} - /// Signer Parameters Type pub type SignerParameters = wallet::signer::SignerParameters; diff --git a/manta-pay/src/signer/client/http.rs b/manta-pay/src/signer/client/http.rs index e2498082d..0c1fd3eb2 100644 --- a/manta-pay/src/signer/client/http.rs +++ b/manta-pay/src/signer/client/http.rs @@ -19,8 +19,8 @@ use crate::{ config::{Config, ReceivingKey}, signer::{ - ReceivingKeyRequest, SignError, SignRequest, SignResponse, SyncError, SyncRequest, - SyncResponse, + Checkpoint, ReceivingKeyRequest, SignError, SignRequest, SignResponse, SyncError, + SyncRequest, SyncResponse, }, util::http::{self, IntoUrl}, }; @@ -49,6 +49,7 @@ impl Client { } impl signer::Connection for Client { + type Checkpoint = Checkpoint; type Error = Error; #[inline] diff --git a/manta-pay/src/signer/client/websocket.rs b/manta-pay/src/signer/client/websocket.rs index 401476b63..3d44c4c91 100644 --- a/manta-pay/src/signer/client/websocket.rs +++ b/manta-pay/src/signer/client/websocket.rs @@ -21,8 +21,8 @@ use crate::{ config::{Config, ReceivingKey}, signer::{ - ReceivingKeyRequest, SignError, SignRequest, SignResponse, SyncError, SyncRequest, - SyncResponse, + Checkpoint, ReceivingKeyRequest, SignError, SignRequest, SignResponse, SyncError, + SyncRequest, SyncResponse, }, }; use alloc::{boxed::Box, vec::Vec}; @@ -121,6 +121,7 @@ impl Client { } impl signer::Connection for Client { + type Checkpoint = Checkpoint; type Error = Error; #[inline] diff --git a/manta-pay/src/signer/mod.rs b/manta-pay/src/signer/mod.rs index ed72b6088..8813af873 100644 --- a/manta-pay/src/signer/mod.rs +++ b/manta-pay/src/signer/mod.rs @@ -30,13 +30,13 @@ pub mod client; pub mod base; /// Synchronization Request -pub type SyncRequest = signer::SyncRequest; +pub type SyncRequest = signer::SyncRequest; /// Synchronization Response pub type SyncResponse = signer::SyncResponse; /// Synchronization Error -pub type SyncError = signer::SyncError; +pub type SyncError = signer::SyncError; /// Sign Request pub type SignRequest = signer::SignRequest; @@ -96,17 +96,7 @@ impl From for Checkpoint { } } -impl ledger::Checkpoint for Checkpoint { - #[inline] - fn receiver_index(&self) -> usize { - self.receiver_index.iter().sum() - } - - #[inline] - fn sender_index(&self) -> usize { - self.sender_index - } -} +impl ledger::Checkpoint for Checkpoint {} #[cfg(feature = "scale")] #[cfg_attr(doc_cfg, doc(cfg(feature = "scale")))] diff --git a/manta-pay/src/simulation/ledger/http/client.rs b/manta-pay/src/simulation/ledger/http/client.rs index 3ea6ff06b..59f0e4cb8 100644 --- a/manta-pay/src/simulation/ledger/http/client.rs +++ b/manta-pay/src/simulation/ledger/http/client.rs @@ -17,14 +17,15 @@ //! Ledger Simulation Client use crate::{ - config::{Config, EncryptedNote, TransferPost, Utxo, VoidNumber}, + config::{Config, TransferPost}, simulation::ledger::{http::Request, AccountId, Checkpoint}, util::http::{self, Error, IntoUrl}, }; use manta_accounting::{ asset::AssetList, wallet::{ - ledger::{self, PullResponse}, + ledger::{self, ReadResponse}, + signer::SyncData, test::PublicBalanceOracle, }, }; @@ -53,21 +54,19 @@ impl Client { } } -impl ledger::PullConfiguration for Client { - type Checkpoint = Checkpoint; - type ReceiverChunk = Vec<(Utxo, EncryptedNote)>; - type SenderChunk = Vec; +impl ledger::Connection for Client { + type Error = Error; } -impl ledger::Connection for Client { - type PushResponse = bool; - type Error = Error; +impl ledger::Read> for Client { + type Checkpoint = Checkpoint; #[inline] - fn pull<'s>( + fn read<'s>( &'s mut self, checkpoint: &'s Self::Checkpoint, - ) -> LocalBoxFutureResult<'s, PullResponse, Self::Error> { + ) -> LocalBoxFutureResult<'s, ReadResponse>, Self::Error> + { Box::pin(async move { self.client .post( @@ -80,12 +79,16 @@ impl ledger::Connection for Client { .await }) } +} + +impl ledger::Write> for Client { + type Response = bool; #[inline] - fn push( + fn write( &mut self, posts: Vec, - ) -> LocalBoxFutureResult { + ) -> LocalBoxFutureResult { Box::pin(async move { self.client .post( diff --git a/manta-pay/src/simulation/ledger/http/server.rs b/manta-pay/src/simulation/ledger/http/server.rs index 31dc6d829..082e9046e 100644 --- a/manta-pay/src/simulation/ledger/http/server.rs +++ b/manta-pay/src/simulation/ledger/http/server.rs @@ -18,13 +18,14 @@ use crate::{ config::{Config, TransferPost}, - simulation::ledger::{ - http::Request, AccountId, Checkpoint, Ledger, LedgerConnection, SharedLedger, - }, + simulation::ledger::{http::Request, AccountId, Checkpoint, Ledger, SharedLedger}, }; use alloc::sync::Arc; use core::future::Future; -use manta_accounting::{asset::AssetList, wallet::ledger::PullResponse}; +use manta_accounting::{ + asset::AssetList, + wallet::{ledger::ReadResponse, signer::SyncData}, +}; use manta_util::serde::{de::DeserializeOwned, Serialize}; use tide::{listener::ToListener, Body, Response}; use tokio::{io, sync::RwLock}; @@ -46,7 +47,7 @@ impl State { self, account: AccountId, checkpoint: Checkpoint, - ) -> PullResponse { + ) -> ReadResponse> { let _ = account; self.0.read().await.pull(&checkpoint) } diff --git a/manta-pay/src/simulation/ledger/mod.rs b/manta-pay/src/simulation/ledger/mod.rs index e73367db5..bf439578a 100644 --- a/manta-pay/src/simulation/ledger/mod.rs +++ b/manta-pay/src/simulation/ledger/mod.rs @@ -37,7 +37,8 @@ use manta_accounting::{ TransferLedger, TransferLedgerSuperPostingKey, TransferPostingKey, UtxoAccumulatorOutput, }, wallet::{ - ledger::{self, PullResponse}, + ledger::{self, ReadResponse}, + signer::SyncData, test::PublicBalanceOracle, }, }; @@ -49,10 +50,7 @@ use manta_crypto::{ Tree, }, }; -use manta_util::{ - future::{LocalBoxFuture, LocalBoxFutureResult}, - Array, -}; +use manta_util::future::{LocalBoxFuture, LocalBoxFutureResult}; use std::collections::{HashMap, HashSet}; use tokio::sync::RwLock; @@ -165,7 +163,7 @@ impl Ledger { /// Pulls the data from the ledger later than the given `checkpoint`. #[inline] - pub fn pull(&self, checkpoint: &Checkpoint) -> PullResponse { + pub fn pull(&self, checkpoint: &Checkpoint) -> ReadResponse> { let mut receivers = Vec::new(); for (i, mut index) in checkpoint.receiver_index.iter().copied().enumerate() { let shard = &self.shards[&MerkleForestIndex::from_index(i)]; @@ -180,21 +178,18 @@ impl Ledger { .skip(checkpoint.sender_index) .copied() .collect(); - PullResponse { + ReadResponse { should_continue: false, - checkpoint: Checkpoint::new( - Array::from_unchecked( - self.utxo_forest - .forest - .as_ref() - .iter() - .map(move |t| t.len()) - .collect::>(), - ), + next_checkpoint: Checkpoint::new( + self.utxo_forest + .forest + .as_ref() + .iter() + .map(move |t| t.len()) + .collect(), self.void_numbers.len(), ), - receivers, - senders, + data: SyncData { receivers, senders }, } } @@ -439,29 +434,31 @@ impl LedgerConnection { } } -impl ledger::PullConfiguration for LedgerConnection { - type Checkpoint = Checkpoint; - type ReceiverChunk = Vec<(Utxo, EncryptedNote)>; - type SenderChunk = Vec; +impl ledger::Connection for LedgerConnection { + type Error = Infallible; } -impl ledger::Connection for LedgerConnection { - type PushResponse = bool; - type Error = Infallible; +impl ledger::Read> for LedgerConnection { + type Checkpoint = Checkpoint; #[inline] - fn pull<'s>( + fn read<'s>( &'s mut self, checkpoint: &'s Self::Checkpoint, - ) -> LocalBoxFutureResult<'s, PullResponse, Self::Error> { + ) -> LocalBoxFutureResult<'s, ReadResponse>, Self::Error> + { Box::pin(async move { Ok(self.ledger.read().await.pull(checkpoint)) }) } +} + +impl ledger::Write> for LedgerConnection { + type Response = bool; #[inline] - fn push( + fn write( &mut self, posts: Vec, - ) -> LocalBoxFutureResult { + ) -> LocalBoxFutureResult { Box::pin(async move { Ok(self.ledger.write().await.push(self.account, posts)) }) } } diff --git a/manta-pay/src/simulation/mod.rs b/manta-pay/src/simulation/mod.rs index a845a8dc9..07b4a969e 100644 --- a/manta-pay/src/simulation/mod.rs +++ b/manta-pay/src/simulation/mod.rs @@ -135,8 +135,8 @@ impl Simulation { #[inline] pub async fn run_with(&self, ledger: GL, signer: GS) where - L: wallet::ledger::Connection + PublicBalanceOracle, - S: wallet::signer::Connection, + L: wallet::test::Ledger + PublicBalanceOracle, + S: wallet::signer::Connection, GL: FnMut(usize) -> L, GS: FnMut(usize) -> S, Error: Debug, @@ -150,8 +150,8 @@ impl Simulation { } }) .await - .expect("Error during simulation."), - "Simulation balance mismatch!" + .expect("An error occured during the simulation."), + "ERROR: Simulation balance mismatch. Funds before and after the simulation do not match." ); } } diff --git a/manta-util/Cargo.toml b/manta-util/Cargo.toml index 4b3f032e1..221d87232 100644 --- a/manta-util/Cargo.toml +++ b/manta-util/Cargo.toml @@ -40,6 +40,6 @@ std = ["alloc"] [dependencies] crossbeam-channel = { version = "0.5.4", optional = true, default-features = false } rayon = { version = "1.5.1", optional = true, default-features = false } -serde = { version = "1.0.136", optional = true, default-features = false, features = ["derive"] } -serde_with = { version = "1.12.0", optional = true, default-features = false, features = ["macros"] } +serde = { version = "1.0.137", optional = true, default-features = false, features = ["derive"] } +serde_with = { version = "1.13.0", optional = true, default-features = false, features = ["macros"] } diff --git a/manta-util/src/array.rs b/manta-util/src/array.rs index f8936ffa5..7ae72a768 100644 --- a/manta-util/src/array.rs +++ b/manta-util/src/array.rs @@ -17,9 +17,11 @@ //! Array Utilities use core::{ + array::IntoIter, borrow::{Borrow, BorrowMut}, convert::TryInto, ops::{Deref, DerefMut}, + slice::Iter, }; #[cfg(feature = "alloc")] @@ -190,6 +192,38 @@ macro_rules! impl_array_traits { Self(array.into()) } } + + #[cfg(feature = "alloc")] + #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] + impl FromIterator for $type { + #[inline] + fn from_iter(iter: I) -> Self + where + I: IntoIterator, + { + Self::from_vec(iter.into_iter().collect::>()) + } + } + + impl IntoIterator for $type { + type Item = T; + type IntoIter = IntoIter; + + #[inline] + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } + } + + impl<'t, T, const N: usize> IntoIterator for &'t $type { + type Item = &'t T; + type IntoIter = Iter<'t, T>; + + #[inline] + fn into_iter(self) -> Self::IntoIter { + self.0.iter() + } + } }; } @@ -229,6 +263,15 @@ impl Array { Self(into_array_unchecked(v)) } + /// Performs the [`TryInto`] conversion from `vec` into an array without checking if the + /// conversion succeeded. See [`into_array_unchecked`] for more. + #[cfg(feature = "alloc")] + #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] + #[inline] + pub fn from_vec(vec: Vec) -> Self { + Self::from_unchecked(vec) + } + /// Maps `f` over `self` using allocation. #[cfg(feature = "alloc")] #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] @@ -333,6 +376,15 @@ impl BoxArray { { Self(into_boxed_array_unchecked(v)) } + + /// Performs the [`TryInto`] conversion from `vec` into a boxed array without checking if the + /// conversion succeeded. See [`into_boxed_array_unchecked`] for more. + #[cfg(feature = "alloc")] + #[cfg_attr(doc_cfg, doc(cfg(feature = "alloc")))] + #[inline] + pub fn from_vec(vec: Vec) -> Self { + Self::from_unchecked(vec.into_boxed_slice()) + } } #[cfg(feature = "alloc")] diff --git a/manta-util/src/cache.rs b/manta-util/src/cache.rs deleted file mode 100644 index 5450440ff..000000000 --- a/manta-util/src/cache.rs +++ /dev/null @@ -1,74 +0,0 @@ -// Copyright 2019-2022 Manta Network. -// This file is part of manta-rs. -// -// manta-rs is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// manta-rs is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with manta-rs. If not, see . - -//! Caching Utilities - -use core::{borrow::Borrow, convert::Infallible}; - -/// Cached Resource -pub trait CachedResource { - /// Reading Key Type - type ReadingKey; - - /// Aquisition Error Type - type Error; - - /// Tries to aquire the resource with `self`, returning a reading key if successful, storing - /// the aquired resource in the cache. - /// - /// # Contract - /// - /// This method should be idempotent unless calls to [`aquire`](Self::aquire) are interleaved - /// with calls to [`release`](Self::release). - fn aquire(&mut self) -> Result; - - /// Reads the resource, spending the `reading_key`. The reference can be held on to until - /// [`release`](Self::release) is called or the reference falls out of scope. - fn read(&self, reading_key: Self::ReadingKey) -> &T; - - /// Releases the resource with `self`, clearing the cache. - /// - /// # Contract - /// - /// This method should be idempotent unless calls to [`release`](Self::release) are interleaved - /// with calls to [`aquire`](Self::aquire). This method can be a no-op if the resource was not - /// aquired. - fn release(&mut self); -} - -/// Cached Resource Error Type -pub type CachedResourceError = >::Error; - -impl CachedResource for B -where - B: Borrow, -{ - type ReadingKey = (); - type Error = Infallible; - - #[inline] - fn aquire(&mut self) -> Result { - Ok(()) - } - - #[inline] - fn read(&self, _: Self::ReadingKey) -> &T { - self.borrow() - } - - #[inline] - fn release(&mut self) {} -} diff --git a/manta-util/src/lib.rs b/manta-util/src/lib.rs index 60e5e45bb..4a10604db 100644 --- a/manta-util/src/lib.rs +++ b/manta-util/src/lib.rs @@ -28,7 +28,6 @@ mod array; mod bytes; mod sealed; -pub mod cache; pub mod codec; pub mod convert; pub mod future;