diff --git a/processor/src/coins/monero.rs b/processor/src/coins/monero.rs index c0cb416a..da5d6ac4 100644 --- a/processor/src/coins/monero.rs +++ b/processor/src/coins/monero.rs @@ -24,10 +24,10 @@ impl OutputTrait for Output { // While we could use (tx, o), using the key ensures we won't be susceptible to the burning bug. // While the Monero library offers a variant which allows senders to ensure their TXs have unique // output keys, Serai can still be targeted using the classic burning bug - type Id = CompressedEdwardsY; + type Id = [u8; 32]; fn id(&self) -> Self::Id { - self.0.key.compress() + self.0.key.compress().to_bytes() } fn amount(&self) -> u64 { diff --git a/processor/src/lib.rs b/processor/src/lib.rs index 3e01dd54..9357bd06 100644 --- a/processor/src/lib.rs +++ b/processor/src/lib.rs @@ -13,7 +13,7 @@ mod wallet; mod tests; pub trait Output: Sized + Clone { - type Id; + type Id: AsRef<[u8]>; fn id(&self) -> Self::Id; fn amount(&self) -> u64; diff --git a/processor/src/tests/mod.rs b/processor/src/tests/mod.rs index 1bddc91a..02c8180d 100644 --- a/processor/src/tests/mod.rs +++ b/processor/src/tests/mod.rs @@ -2,14 +2,14 @@ use std::rc::Rc; use rand::rngs::OsRng; -use crate::{Coin, coins::monero::Monero, wallet::{WalletKeys, Wallet}}; +use crate::{Coin, coins::monero::Monero, wallet::{WalletKeys, MemCoinDb, Wallet}}; #[tokio::test] async fn test() { let monero = Monero::new("http://127.0.0.1:18081".to_string()); println!("{}", monero.get_height().await.unwrap()); let mut keys = frost::tests::key_gen::<_, ::Curve>(&mut OsRng); - let mut wallet = Wallet::new(monero); + let mut wallet = Wallet::new(MemCoinDb::new(), monero); wallet.acknowledge_height(0, 0); wallet.add_keys(&WalletKeys::new(Rc::try_unwrap(keys.remove(&1).take().unwrap()).unwrap(), 0)); dbg!(0); diff --git a/processor/src/wallet.rs b/processor/src/wallet.rs index db05a6cd..0c5b66a9 100644 --- a/processor/src/wallet.rs +++ b/processor/src/wallet.rs @@ -33,28 +33,83 @@ impl WalletKeys { } } -pub struct CoinDb { +pub trait CoinDb { + // Set a height as scanned to + fn scanned_to_height(&mut self, height: usize); + // Acknowledge a given coin height for a canonical height + fn acknowledge_height(&mut self, canonical: usize, height: usize); + + // Adds an output to the DB. Returns false if the output was already added + fn add_output(&mut self, output: &O) -> bool; + + // Height this coin has been scanned to + fn scanned_height(&self) -> usize; + // Acknowledged height for a given canonical height + fn acknowledged_height(&self, canonical: usize) -> usize; +} + +pub struct MemCoinDb { // Height this coin has been scanned to scanned_height: usize, // Acknowledged height for a given canonical height - acknowledged_heights: HashMap + acknowledged_heights: HashMap, + outputs: HashMap, Vec> } -pub struct Wallet { - db: CoinDb, + +impl MemCoinDb { + pub fn new() -> MemCoinDb { + MemCoinDb { + scanned_height: 0, + acknowledged_heights: HashMap::new(), + outputs: HashMap::new() + } + } +} + +impl CoinDb for MemCoinDb { + fn scanned_to_height(&mut self, height: usize) { + self.scanned_height = height; + } + + fn acknowledge_height(&mut self, canonical: usize, height: usize) { + debug_assert!(!self.acknowledged_heights.contains_key(&canonical)); + self.acknowledged_heights.insert(canonical, height); + } + + fn add_output(&mut self, output: &O) -> bool { + // This would be insecure as we're indexing by ID and this will replace the output as a whole + // Multiple outputs may have the same ID in edge cases such as Monero, where outputs are ID'd + // by key image, not by hash + index + // self.outputs.insert(output.id(), output).is_some() + let id = output.id().as_ref().to_vec(); + if self.outputs.contains_key(&id) { + return false; + } + self.outputs.insert(id, output.serialize()); + true + } + + fn scanned_height(&self) -> usize { + self.scanned_height + } + + fn acknowledged_height(&self, canonical: usize) -> usize { + self.acknowledged_heights[&canonical] + } +} + +pub struct Wallet { + db: D, coin: C, keys: Vec<(Arc>, Vec)>, pending: Vec<(usize, MultisigKeys)> } -impl Wallet { - pub fn new(coin: C) -> Wallet { +impl Wallet { + pub fn new(db: D, coin: C) -> Wallet { Wallet { - db: CoinDb { - scanned_height: 0, - acknowledged_heights: HashMap::new(), - }, - + db, coin, keys: vec![], @@ -62,13 +117,12 @@ impl Wallet { } } - pub fn scanned_height(&self) -> usize { self.db.scanned_height } + pub fn scanned_height(&self) -> usize { self.db.scanned_height() } pub fn acknowledge_height(&mut self, canonical: usize, height: usize) { - debug_assert!(!self.db.acknowledged_heights.contains_key(&canonical)); - self.db.acknowledged_heights.insert(canonical, height); + self.db.acknowledge_height(canonical, height); } pub fn acknowledged_height(&self, canonical: usize) -> usize { - self.db.acknowledged_heights[&canonical] + self.db.acknowledged_height(canonical) } pub fn add_keys(&mut self, keys: &WalletKeys) { @@ -83,7 +137,10 @@ impl Wallet { { let mut k = 0; while k < self.pending.len() { - if height >= self.pending[k].0 { + // TODO + //if height < self.pending[k].0 { + //} else if height == self.pending[k].0 { + if height <= self.pending[k].0 { self.keys.push((Arc::new(self.pending.swap_remove(k).1), vec![])); } else { k += 1; @@ -95,7 +152,7 @@ impl Wallet { for (keys, outputs) in self.keys.iter_mut() { outputs.extend( self.coin.get_outputs(&block, keys.group_key()).await.iter().cloned().filter( - |_output| true // !self.db.handled.contains_key(output.id()) // TODO + |output| self.db.add_output(output) ) ); } @@ -103,18 +160,23 @@ impl Wallet { Ok(()) } + // This should be called whenever new outputs are received, meaning there was a new block + // If these outputs were received and sent to Substrate, it should be called after they're + // included in a block and we have results to act on + // If these outputs weren't sent to Substrate (change), it should be called immediately + // with all payments still queued from the last call pub async fn prepare_sends( &mut self, canonical: usize, payments: Vec<(C::Address, u64)> - ) -> Result, CoinError> { + ) -> Result<(Vec<(C::Address, u64)>, Vec), CoinError> { if payments.len() == 0 { - return Ok(vec![]); + return Ok((vec![], vec![])); } let acknowledged_height = self.acknowledged_height(canonical); - // TODO: Log schedule outputs when MAX_OUTPUTS is low + // TODO: Log schedule outputs when MAX_OUTPUTS is lower than payments.len() // Payments is the first set of TXs in the schedule // As each payment re-appears, let mut payments = schedule[payment] where the only input is // the source payment @@ -177,8 +239,6 @@ impl Wallet { } } - // TODO: Remaining payments? - - Ok(txs) + Ok((payments, txs)) } }