diff --git a/processor/monero/src/lib.rs b/processor/monero/src/lib.rs index 52ebb6cb..0848e08a 100644 --- a/processor/monero/src/lib.rs +++ b/processor/monero/src/lib.rs @@ -130,8 +130,6 @@ impl Network for Monero { const ESTIMATED_BLOCK_TIME_IN_SECONDS: usize = 120; const CONFIRMATIONS: usize = 10; - const MAX_OUTPUTS: usize = 16; - // TODO const COST_TO_AGGREGATE: u64 = 0; @@ -318,12 +316,4 @@ impl Network for Monero { self.get_block(block).await.unwrap() } } - -impl UtxoNetwork for Monero { - // wallet2 will not create a transaction larger than 100kb, and Monero won't relay a transaction - // larger than 150kb. This fits within the 100kb mark - // Technically, it can be ~124, yet a small bit of buffer is appreciated - // TODO: Test creating a TX this big - const MAX_INPUTS: usize = 120; -} */ diff --git a/processor/monero/src/main.rs b/processor/monero/src/main.rs index 344b6c48..daba3255 100644 --- a/processor/monero/src/main.rs +++ b/processor/monero/src/main.rs @@ -6,7 +6,7 @@ static ALLOCATOR: zalloc::ZeroizingAlloc = zalloc::ZeroizingAlloc(std::alloc::System); -use monero_wallet::rpc::Rpc as MRpc; +use monero_simple_request_rpc::SimpleRequestRpc; mod primitives; pub(crate) use crate::primitives::*; @@ -15,18 +15,15 @@ mod key_gen; use crate::key_gen::KeyGenParams; mod rpc; use rpc::Rpc; - -/* mod scheduler; -use scheduler::Scheduler; +use scheduler::{Planner, Scheduler}; #[tokio::main] async fn main() { let db = bin::init(); let feed = Rpc { - db: db.clone(), rpc: loop { - match MRpc::new(bin::url()).await { + match SimpleRequestRpc::new(bin::url()).await { Ok(rpc) => break rpc, Err(e) => { log::error!("couldn't connect to the Monero node: {e:?}"); @@ -36,9 +33,11 @@ async fn main() { }, }; - bin::main_loop::<_, KeyGenParams, Scheduler<_>, Rpc>(db, feed.clone(), feed).await; + bin::main_loop::<_, KeyGenParams, _>( + db, + feed.clone(), + Scheduler::new(Planner(feed.clone())), + feed, + ) + .await; } -*/ - -#[tokio::main] -async fn main() {} diff --git a/processor/monero/src/primitives/block.rs b/processor/monero/src/primitives/block.rs index 130e5ac8..70a559c1 100644 --- a/processor/monero/src/primitives/block.rs +++ b/processor/monero/src/primitives/block.rs @@ -1,21 +1,17 @@ use std::collections::HashMap; -use zeroize::Zeroizing; - use ciphersuite::{Ciphersuite, Ed25519}; use monero_wallet::{ - block::Block as MBlock, rpc::ScannableBlock as MScannableBlock, ViewPairError, - GuaranteedViewPair, ScanError, GuaranteedScanner, + block::Block as MBlock, rpc::ScannableBlock as MScannableBlock, ScanError, GuaranteedScanner, }; use serai_client::networks::monero::Address; use primitives::{ReceivedOutput, EventualityTracker}; -use view_keys::view_key; use crate::{ - EXTERNAL_SUBADDRESS, BRANCH_SUBADDRESS, CHANGE_SUBADDRESS, FORWARDED_SUBADDRESS, output::Output, - transaction::Eventuality, + EXTERNAL_SUBADDRESS, BRANCH_SUBADDRESS, CHANGE_SUBADDRESS, FORWARDED_SUBADDRESS, view_pair, + output::Output, transaction::Eventuality, }; #[derive(Clone, Debug)] @@ -45,17 +41,11 @@ impl primitives::Block for Block { } fn scan_for_outputs_unordered(&self, key: Self::Key) -> Vec { - let view_pair = match GuaranteedViewPair::new(key.0, Zeroizing::new(*view_key::(0))) { - Ok(view_pair) => view_pair, - Err(ViewPairError::TorsionedSpendKey) => { - unreachable!("dalek_ff_group::EdwardsPoint had torsion") - } - }; - let mut scanner = GuaranteedScanner::new(view_pair); - scanner.register_subaddress(EXTERNAL_SUBADDRESS.unwrap()); - scanner.register_subaddress(BRANCH_SUBADDRESS.unwrap()); - scanner.register_subaddress(CHANGE_SUBADDRESS.unwrap()); - scanner.register_subaddress(FORWARDED_SUBADDRESS.unwrap()); + let mut scanner = GuaranteedScanner::new(view_pair(key)); + scanner.register_subaddress(EXTERNAL_SUBADDRESS); + scanner.register_subaddress(BRANCH_SUBADDRESS); + scanner.register_subaddress(CHANGE_SUBADDRESS); + scanner.register_subaddress(FORWARDED_SUBADDRESS); match scanner.scan(self.0.clone()) { Ok(outputs) => outputs.not_additionally_locked().into_iter().map(Output).collect(), Err(ScanError::UnsupportedProtocol(version)) => { diff --git a/processor/monero/src/primitives/mod.rs b/processor/monero/src/primitives/mod.rs index de057399..317cae28 100644 --- a/processor/monero/src/primitives/mod.rs +++ b/processor/monero/src/primitives/mod.rs @@ -1,10 +1,37 @@ -use monero_wallet::address::SubaddressIndex; +use zeroize::Zeroizing; + +use ciphersuite::{Ciphersuite, Ed25519}; + +use monero_wallet::{address::SubaddressIndex, ViewPairError, GuaranteedViewPair}; + +use view_keys::view_key; pub(crate) mod output; pub(crate) mod transaction; pub(crate) mod block; -pub(crate) const EXTERNAL_SUBADDRESS: Option = SubaddressIndex::new(1, 0); -pub(crate) const BRANCH_SUBADDRESS: Option = SubaddressIndex::new(2, 0); -pub(crate) const CHANGE_SUBADDRESS: Option = SubaddressIndex::new(2, 1); -pub(crate) const FORWARDED_SUBADDRESS: Option = SubaddressIndex::new(2, 2); +pub(crate) const EXTERNAL_SUBADDRESS: SubaddressIndex = match SubaddressIndex::new(1, 0) { + Some(index) => index, + None => panic!("SubaddressIndex for EXTERNAL_SUBADDRESS was None"), +}; +pub(crate) const BRANCH_SUBADDRESS: SubaddressIndex = match SubaddressIndex::new(2, 0) { + Some(index) => index, + None => panic!("SubaddressIndex for BRANCH_SUBADDRESS was None"), +}; +pub(crate) const CHANGE_SUBADDRESS: SubaddressIndex = match SubaddressIndex::new(2, 1) { + Some(index) => index, + None => panic!("SubaddressIndex for CHANGE_SUBADDRESS was None"), +}; +pub(crate) const FORWARDED_SUBADDRESS: SubaddressIndex = match SubaddressIndex::new(2, 2) { + Some(index) => index, + None => panic!("SubaddressIndex for FORWARDED_SUBADDRESS was None"), +}; + +pub(crate) fn view_pair(key: ::G) -> GuaranteedViewPair { + match GuaranteedViewPair::new(key.0, Zeroizing::new(*view_key::(0))) { + Ok(view_pair) => view_pair, + Err(ViewPairError::TorsionedSpendKey) => { + unreachable!("dalek_ff_group::EdwardsPoint had torsion") + } + } +} diff --git a/processor/monero/src/primitives/output.rs b/processor/monero/src/primitives/output.rs index d66fd983..fea042c8 100644 --- a/processor/monero/src/primitives/output.rs +++ b/processor/monero/src/primitives/output.rs @@ -46,16 +46,17 @@ impl ReceivedOutput<::G, Address> for Output { type TransactionId = [u8; 32]; fn kind(&self) -> OutputType { - if self.0.subaddress() == EXTERNAL_SUBADDRESS { + let subaddress = self.0.subaddress().unwrap(); + if subaddress == EXTERNAL_SUBADDRESS { return OutputType::External; } - if self.0.subaddress() == BRANCH_SUBADDRESS { + if subaddress == BRANCH_SUBADDRESS { return OutputType::Branch; } - if self.0.subaddress() == CHANGE_SUBADDRESS { + if subaddress == CHANGE_SUBADDRESS { return OutputType::Change; } - if self.0.subaddress() == FORWARDED_SUBADDRESS { + if subaddress == FORWARDED_SUBADDRESS { return OutputType::Forwarded; } unreachable!("scanned output to unknown subaddress"); diff --git a/processor/monero/src/scheduler.rs b/processor/monero/src/scheduler.rs index 7666ec4f..ef52c413 100644 --- a/processor/monero/src/scheduler.rs +++ b/processor/monero/src/scheduler.rs @@ -1,3 +1,4 @@ +/* async fn make_signable_transaction( block_number: usize, plan_id: &[u8; 32], @@ -136,10 +137,106 @@ match MSignableTransaction::new( }, } } +*/ +use core::future::Future; + +use ciphersuite::{Ciphersuite, Ed25519}; + +use monero_wallet::rpc::{FeeRate, RpcError}; + +use serai_client::{ + primitives::{Coin, Amount}, + networks::monero::Address, +}; + +use primitives::{OutputType, ReceivedOutput, Payment}; +use scanner::{KeyFor, AddressFor, OutputFor, BlockFor}; +use utxo_scheduler::{PlannedTransaction, TransactionPlanner}; + +use monero_wallet::address::Network; + +use crate::{ + EXTERNAL_SUBADDRESS, BRANCH_SUBADDRESS, CHANGE_SUBADDRESS, FORWARDED_SUBADDRESS, view_pair, + output::Output, + transaction::{SignableTransaction, Eventuality}, + rpc::Rpc, +}; + +fn address_from_serai_key(key: ::G, kind: OutputType) -> Address { + view_pair(key) + .address( + Network::Mainnet, + Some(match kind { + OutputType::External => EXTERNAL_SUBADDRESS, + OutputType::Branch => BRANCH_SUBADDRESS, + OutputType::Change => CHANGE_SUBADDRESS, + OutputType::Forwarded => FORWARDED_SUBADDRESS, + }), + None, + ) + .try_into() + .expect("created address which wasn't representable") +} + +#[derive(Clone)] +pub(crate) struct Planner(pub(crate) Rpc); +impl TransactionPlanner for Planner { + type EphemeralError = RpcError; + + type FeeRate = FeeRate; + + type SignableTransaction = SignableTransaction; + + // wallet2 will not create a transaction larger than 100 KB, and Monero won't relay a transaction + // larger than 150 KB. This fits within the 100 KB mark to fit in and not poke the bear. + // Technically, it can be ~124, yet a small bit of buffer is appreciated + // TODO: Test creating a TX this big + const MAX_INPUTS: usize = 120; + const MAX_OUTPUTS: usize = 16; + + fn fee_rate(block: &BlockFor, coin: Coin) -> Self::FeeRate { + assert_eq!(coin, Coin::Monero); + // TODO + todo!("TODO") + } + + fn branch_address(key: KeyFor) -> AddressFor { + address_from_serai_key(key, OutputType::Branch) + } + fn change_address(key: KeyFor) -> AddressFor { + address_from_serai_key(key, OutputType::Change) + } + fn forwarding_address(key: KeyFor) -> AddressFor { + address_from_serai_key(key, OutputType::Forwarded) + } + + fn calculate_fee( + fee_rate: Self::FeeRate, + inputs: Vec>, + payments: Vec>>, + change: Option>, + ) -> Amount { + todo!("TODO") + } + + fn plan( + &self, + fee_rate: Self::FeeRate, + inputs: Vec>, + payments: Vec>>, + change: Option>, + ) -> impl Send + + Future, RpcError>> + { + async move { todo!("TODO") } + } +} + +pub(crate) type Scheduler = utxo_standard_scheduler::Scheduler; /* -use ciphersuite::{Ciphersuite, Secp256k1}; +use ciphersuite::{Ciphersuite, Ed25519}; use bitcoin_serai::{ bitcoin::ScriptBuf, @@ -163,8 +260,8 @@ use crate::{ rpc::Rpc, }; -fn address_from_serai_key(key: ::G, kind: OutputType) -> Address { - let offset = ::G::GENERATOR * offsets_for_key(key)[&kind]; +fn address_from_serai_key(key: ::G, kind: OutputType) -> Address { + let offset = ::G::GENERATOR * offsets_for_key(key)[&kind]; Address::new( p2tr_script_buf(key + offset) .expect("creating address from Serai key which wasn't properly tweaked"), @@ -174,17 +271,17 @@ fn address_from_serai_key(key: ::G, kind: OutputType) fn signable_transaction( fee_per_vbyte: u64, - inputs: Vec>>, - payments: Vec>>>, - change: Option>>, + inputs: Vec>, + payments: Vec>>, + change: Option>, ) -> Result<(SignableTransaction, BSignableTransaction), TransactionError> { assert!( inputs.len() < - , ()>>::MAX_INPUTS + >::MAX_INPUTS ); assert!( (payments.len() + usize::from(u8::from(change.is_some()))) < - , ()>>::MAX_OUTPUTS + >::MAX_OUTPUTS ); let inputs = inputs.into_iter().map(|input| input.output).collect::>(); @@ -194,7 +291,7 @@ fn signable_transaction( .map(|payment| { (payment.address().clone(), { let balance = payment.balance(); - assert_eq!(balance.coin, Coin::Bitcoin); + assert_eq!(balance.coin, Coin::Monero); balance.amount.0 }) }) @@ -206,14 +303,14 @@ fn signable_transaction( */ payments.push(( // The generator is even so this is valid - Address::new(p2tr_script_buf(::G::GENERATOR).unwrap()).unwrap(), + Address::new(p2tr_script_buf(::G::GENERATOR).unwrap()).unwrap(), // This uses the minimum output value allowed, as defined as a constant in bitcoin-serai // TODO: Add a test for this comparing to bitcoin's `minimal_non_dust` bitcoin_serai::wallet::DUST, )); let change = change - .map(, ()>>::change_address); + .map(>::change_address); BSignableTransaction::new( inputs.clone(), @@ -231,12 +328,14 @@ fn signable_transaction( pub(crate) struct Planner; impl TransactionPlanner for Planner { + type EphemeralError = RpcError; + type FeeRate = u64; type SignableTransaction = SignableTransaction; /* - Bitcoin has a max weight of 400,000 (MAX_STANDARD_TX_WEIGHT). + Monero has a max weight of 400,000 (MAX_STANDARD_TX_WEIGHT). A non-SegWit TX will have 4 weight units per byte, leaving a max size of 100,000 bytes. While our inputs are entirely SegWit, such fine tuning is not necessary and could create issues in @@ -255,27 +354,27 @@ impl TransactionPlanner for Planner { // to unstick any transactions which had too low of a fee. const MAX_OUTPUTS: usize = 519; - fn fee_rate(block: &BlockFor>, coin: Coin) -> Self::FeeRate { - assert_eq!(coin, Coin::Bitcoin); + fn fee_rate(block: &BlockFor, coin: Coin) -> Self::FeeRate { + assert_eq!(coin, Coin::Monero); // TODO 1 } - fn branch_address(key: KeyFor>) -> AddressFor> { + fn branch_address(key: KeyFor) -> AddressFor { address_from_serai_key(key, OutputType::Branch) } - fn change_address(key: KeyFor>) -> AddressFor> { + fn change_address(key: KeyFor) -> AddressFor { address_from_serai_key(key, OutputType::Change) } - fn forwarding_address(key: KeyFor>) -> AddressFor> { + fn forwarding_address(key: KeyFor) -> AddressFor { address_from_serai_key(key, OutputType::Forwarded) } fn calculate_fee( fee_rate: Self::FeeRate, - inputs: Vec>>, - payments: Vec>>>, - change: Option>>, + inputs: Vec>, + payments: Vec>>, + change: Option>, ) -> Amount { match signable_transaction::(fee_rate, inputs, payments, change) { Ok(tx) => Amount(tx.1.needed_fee()), @@ -294,10 +393,10 @@ impl TransactionPlanner for Planner { fn plan( fee_rate: Self::FeeRate, - inputs: Vec>>, - payments: Vec>>>, - change: Option>>, - ) -> PlannedTransaction, Self::SignableTransaction, ()> { + inputs: Vec>, + payments: Vec>>, + change: Option>, + ) -> PlannedTransaction { let key = inputs.first().unwrap().key(); for input in &inputs { assert_eq!(key, input.key());