Stub out Scheduler in the Monero processor

This commit is contained in:
Luke Parker 2024-09-14 01:38:31 -04:00
parent e1ad897f7e
commit a2d9aeaed7
6 changed files with 178 additions and 72 deletions

View file

@ -130,8 +130,6 @@ impl Network for Monero {
const ESTIMATED_BLOCK_TIME_IN_SECONDS: usize = 120; const ESTIMATED_BLOCK_TIME_IN_SECONDS: usize = 120;
const CONFIRMATIONS: usize = 10; const CONFIRMATIONS: usize = 10;
const MAX_OUTPUTS: usize = 16;
// TODO // TODO
const COST_TO_AGGREGATE: u64 = 0; const COST_TO_AGGREGATE: u64 = 0;
@ -318,12 +316,4 @@ impl Network for Monero {
self.get_block(block).await.unwrap() 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;
}
*/ */

View file

@ -6,7 +6,7 @@
static ALLOCATOR: zalloc::ZeroizingAlloc<std::alloc::System> = static ALLOCATOR: zalloc::ZeroizingAlloc<std::alloc::System> =
zalloc::ZeroizingAlloc(std::alloc::System); zalloc::ZeroizingAlloc(std::alloc::System);
use monero_wallet::rpc::Rpc as MRpc; use monero_simple_request_rpc::SimpleRequestRpc;
mod primitives; mod primitives;
pub(crate) use crate::primitives::*; pub(crate) use crate::primitives::*;
@ -15,18 +15,15 @@ mod key_gen;
use crate::key_gen::KeyGenParams; use crate::key_gen::KeyGenParams;
mod rpc; mod rpc;
use rpc::Rpc; use rpc::Rpc;
/*
mod scheduler; mod scheduler;
use scheduler::Scheduler; use scheduler::{Planner, Scheduler};
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
let db = bin::init(); let db = bin::init();
let feed = Rpc { let feed = Rpc {
db: db.clone(),
rpc: loop { rpc: loop {
match MRpc::new(bin::url()).await { match SimpleRequestRpc::new(bin::url()).await {
Ok(rpc) => break rpc, Ok(rpc) => break rpc,
Err(e) => { Err(e) => {
log::error!("couldn't connect to the Monero node: {e:?}"); log::error!("couldn't connect to the Monero node: {e:?}");
@ -36,9 +33,11 @@ async fn main() {
}, },
}; };
bin::main_loop::<_, KeyGenParams, Scheduler<_>, Rpc<bin::Db>>(db, feed.clone(), feed).await; bin::main_loop::<_, KeyGenParams, _>(
db,
feed.clone(),
Scheduler::new(Planner(feed.clone())),
feed,
)
.await;
} }
*/
#[tokio::main]
async fn main() {}

View file

@ -1,21 +1,17 @@
use std::collections::HashMap; use std::collections::HashMap;
use zeroize::Zeroizing;
use ciphersuite::{Ciphersuite, Ed25519}; use ciphersuite::{Ciphersuite, Ed25519};
use monero_wallet::{ use monero_wallet::{
block::Block as MBlock, rpc::ScannableBlock as MScannableBlock, ViewPairError, block::Block as MBlock, rpc::ScannableBlock as MScannableBlock, ScanError, GuaranteedScanner,
GuaranteedViewPair, ScanError, GuaranteedScanner,
}; };
use serai_client::networks::monero::Address; use serai_client::networks::monero::Address;
use primitives::{ReceivedOutput, EventualityTracker}; use primitives::{ReceivedOutput, EventualityTracker};
use view_keys::view_key;
use crate::{ use crate::{
EXTERNAL_SUBADDRESS, BRANCH_SUBADDRESS, CHANGE_SUBADDRESS, FORWARDED_SUBADDRESS, output::Output, EXTERNAL_SUBADDRESS, BRANCH_SUBADDRESS, CHANGE_SUBADDRESS, FORWARDED_SUBADDRESS, view_pair,
transaction::Eventuality, output::Output, transaction::Eventuality,
}; };
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
@ -45,17 +41,11 @@ impl primitives::Block for Block {
} }
fn scan_for_outputs_unordered(&self, key: Self::Key) -> Vec<Self::Output> { fn scan_for_outputs_unordered(&self, key: Self::Key) -> Vec<Self::Output> {
let view_pair = match GuaranteedViewPair::new(key.0, Zeroizing::new(*view_key::<Ed25519>(0))) { let mut scanner = GuaranteedScanner::new(view_pair(key));
Ok(view_pair) => view_pair, scanner.register_subaddress(EXTERNAL_SUBADDRESS);
Err(ViewPairError::TorsionedSpendKey) => { scanner.register_subaddress(BRANCH_SUBADDRESS);
unreachable!("dalek_ff_group::EdwardsPoint had torsion") scanner.register_subaddress(CHANGE_SUBADDRESS);
} scanner.register_subaddress(FORWARDED_SUBADDRESS);
};
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());
match scanner.scan(self.0.clone()) { match scanner.scan(self.0.clone()) {
Ok(outputs) => outputs.not_additionally_locked().into_iter().map(Output).collect(), Ok(outputs) => outputs.not_additionally_locked().into_iter().map(Output).collect(),
Err(ScanError::UnsupportedProtocol(version)) => { Err(ScanError::UnsupportedProtocol(version)) => {

View file

@ -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 output;
pub(crate) mod transaction; pub(crate) mod transaction;
pub(crate) mod block; pub(crate) mod block;
pub(crate) const EXTERNAL_SUBADDRESS: Option<SubaddressIndex> = SubaddressIndex::new(1, 0); pub(crate) const EXTERNAL_SUBADDRESS: SubaddressIndex = match SubaddressIndex::new(1, 0) {
pub(crate) const BRANCH_SUBADDRESS: Option<SubaddressIndex> = SubaddressIndex::new(2, 0); Some(index) => index,
pub(crate) const CHANGE_SUBADDRESS: Option<SubaddressIndex> = SubaddressIndex::new(2, 1); None => panic!("SubaddressIndex for EXTERNAL_SUBADDRESS was None"),
pub(crate) const FORWARDED_SUBADDRESS: Option<SubaddressIndex> = SubaddressIndex::new(2, 2); };
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: <Ed25519 as Ciphersuite>::G) -> GuaranteedViewPair {
match GuaranteedViewPair::new(key.0, Zeroizing::new(*view_key::<Ed25519>(0))) {
Ok(view_pair) => view_pair,
Err(ViewPairError::TorsionedSpendKey) => {
unreachable!("dalek_ff_group::EdwardsPoint had torsion")
}
}
}

View file

@ -46,16 +46,17 @@ impl ReceivedOutput<<Ed25519 as Ciphersuite>::G, Address> for Output {
type TransactionId = [u8; 32]; type TransactionId = [u8; 32];
fn kind(&self) -> OutputType { fn kind(&self) -> OutputType {
if self.0.subaddress() == EXTERNAL_SUBADDRESS { let subaddress = self.0.subaddress().unwrap();
if subaddress == EXTERNAL_SUBADDRESS {
return OutputType::External; return OutputType::External;
} }
if self.0.subaddress() == BRANCH_SUBADDRESS { if subaddress == BRANCH_SUBADDRESS {
return OutputType::Branch; return OutputType::Branch;
} }
if self.0.subaddress() == CHANGE_SUBADDRESS { if subaddress == CHANGE_SUBADDRESS {
return OutputType::Change; return OutputType::Change;
} }
if self.0.subaddress() == FORWARDED_SUBADDRESS { if subaddress == FORWARDED_SUBADDRESS {
return OutputType::Forwarded; return OutputType::Forwarded;
} }
unreachable!("scanned output to unknown subaddress"); unreachable!("scanned output to unknown subaddress");

View file

@ -1,3 +1,4 @@
/*
async fn make_signable_transaction( async fn make_signable_transaction(
block_number: usize, block_number: usize,
plan_id: &[u8; 32], 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: <Ed25519 as Ciphersuite>::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<Rpc, ()> 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<Rpc>, coin: Coin) -> Self::FeeRate {
assert_eq!(coin, Coin::Monero);
// TODO
todo!("TODO")
}
fn branch_address(key: KeyFor<Rpc>) -> AddressFor<Rpc> {
address_from_serai_key(key, OutputType::Branch)
}
fn change_address(key: KeyFor<Rpc>) -> AddressFor<Rpc> {
address_from_serai_key(key, OutputType::Change)
}
fn forwarding_address(key: KeyFor<Rpc>) -> AddressFor<Rpc> {
address_from_serai_key(key, OutputType::Forwarded)
}
fn calculate_fee(
fee_rate: Self::FeeRate,
inputs: Vec<OutputFor<Rpc>>,
payments: Vec<Payment<AddressFor<Rpc>>>,
change: Option<KeyFor<Rpc>>,
) -> Amount {
todo!("TODO")
}
fn plan(
&self,
fee_rate: Self::FeeRate,
inputs: Vec<OutputFor<Rpc>>,
payments: Vec<Payment<AddressFor<Rpc>>>,
change: Option<KeyFor<Rpc>>,
) -> impl Send
+ Future<Output = Result<PlannedTransaction<Rpc, Self::SignableTransaction, ()>, RpcError>>
{
async move { todo!("TODO") }
}
}
pub(crate) type Scheduler = utxo_standard_scheduler::Scheduler<Rpc, Planner>;
/* /*
use ciphersuite::{Ciphersuite, Secp256k1}; use ciphersuite::{Ciphersuite, Ed25519};
use bitcoin_serai::{ use bitcoin_serai::{
bitcoin::ScriptBuf, bitcoin::ScriptBuf,
@ -163,8 +260,8 @@ use crate::{
rpc::Rpc, rpc::Rpc,
}; };
fn address_from_serai_key(key: <Secp256k1 as Ciphersuite>::G, kind: OutputType) -> Address { fn address_from_serai_key(key: <Ed25519 as Ciphersuite>::G, kind: OutputType) -> Address {
let offset = <Secp256k1 as Ciphersuite>::G::GENERATOR * offsets_for_key(key)[&kind]; let offset = <Ed25519 as Ciphersuite>::G::GENERATOR * offsets_for_key(key)[&kind];
Address::new( Address::new(
p2tr_script_buf(key + offset) p2tr_script_buf(key + offset)
.expect("creating address from Serai key which wasn't properly tweaked"), .expect("creating address from Serai key which wasn't properly tweaked"),
@ -174,17 +271,17 @@ fn address_from_serai_key(key: <Secp256k1 as Ciphersuite>::G, kind: OutputType)
fn signable_transaction<D: Db>( fn signable_transaction<D: Db>(
fee_per_vbyte: u64, fee_per_vbyte: u64,
inputs: Vec<OutputFor<Rpc<D>>>, inputs: Vec<OutputFor<Rpc>>,
payments: Vec<Payment<AddressFor<Rpc<D>>>>, payments: Vec<Payment<AddressFor<Rpc>>>,
change: Option<KeyFor<Rpc<D>>>, change: Option<KeyFor<Rpc>>,
) -> Result<(SignableTransaction, BSignableTransaction), TransactionError> { ) -> Result<(SignableTransaction, BSignableTransaction), TransactionError> {
assert!( assert!(
inputs.len() < inputs.len() <
<Planner as TransactionPlanner<Rpc<D>, ()>>::MAX_INPUTS <Planner as TransactionPlanner<Rpc, ()>>::MAX_INPUTS
); );
assert!( assert!(
(payments.len() + usize::from(u8::from(change.is_some()))) < (payments.len() + usize::from(u8::from(change.is_some()))) <
<Planner as TransactionPlanner<Rpc<D>, ()>>::MAX_OUTPUTS <Planner as TransactionPlanner<Rpc, ()>>::MAX_OUTPUTS
); );
let inputs = inputs.into_iter().map(|input| input.output).collect::<Vec<_>>(); let inputs = inputs.into_iter().map(|input| input.output).collect::<Vec<_>>();
@ -194,7 +291,7 @@ fn signable_transaction<D: Db>(
.map(|payment| { .map(|payment| {
(payment.address().clone(), { (payment.address().clone(), {
let balance = payment.balance(); let balance = payment.balance();
assert_eq!(balance.coin, Coin::Bitcoin); assert_eq!(balance.coin, Coin::Monero);
balance.amount.0 balance.amount.0
}) })
}) })
@ -206,14 +303,14 @@ fn signable_transaction<D: Db>(
*/ */
payments.push(( payments.push((
// The generator is even so this is valid // The generator is even so this is valid
Address::new(p2tr_script_buf(<Secp256k1 as Ciphersuite>::G::GENERATOR).unwrap()).unwrap(), Address::new(p2tr_script_buf(<Ed25519 as Ciphersuite>::G::GENERATOR).unwrap()).unwrap(),
// This uses the minimum output value allowed, as defined as a constant in bitcoin-serai // 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` // TODO: Add a test for this comparing to bitcoin's `minimal_non_dust`
bitcoin_serai::wallet::DUST, bitcoin_serai::wallet::DUST,
)); ));
let change = change let change = change
.map(<Planner as TransactionPlanner<Rpc<D>, ()>>::change_address); .map(<Planner as TransactionPlanner<Rpc, ()>>::change_address);
BSignableTransaction::new( BSignableTransaction::new(
inputs.clone(), inputs.clone(),
@ -231,12 +328,14 @@ fn signable_transaction<D: Db>(
pub(crate) struct Planner; pub(crate) struct Planner;
impl TransactionPlanner<Rpc, ()> for Planner { impl TransactionPlanner<Rpc, ()> for Planner {
type EphemeralError = RpcError;
type FeeRate = u64; type FeeRate = u64;
type SignableTransaction = SignableTransaction; 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 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 our inputs are entirely SegWit, such fine tuning is not necessary and could create issues in
@ -255,27 +354,27 @@ impl TransactionPlanner<Rpc, ()> for Planner {
// to unstick any transactions which had too low of a fee. // to unstick any transactions which had too low of a fee.
const MAX_OUTPUTS: usize = 519; const MAX_OUTPUTS: usize = 519;
fn fee_rate(block: &BlockFor<Rpc<D>>, coin: Coin) -> Self::FeeRate { fn fee_rate(block: &BlockFor<Rpc>, coin: Coin) -> Self::FeeRate {
assert_eq!(coin, Coin::Bitcoin); assert_eq!(coin, Coin::Monero);
// TODO // TODO
1 1
} }
fn branch_address(key: KeyFor<Rpc<D>>) -> AddressFor<Rpc<D>> { fn branch_address(key: KeyFor<Rpc>) -> AddressFor<Rpc> {
address_from_serai_key(key, OutputType::Branch) address_from_serai_key(key, OutputType::Branch)
} }
fn change_address(key: KeyFor<Rpc<D>>) -> AddressFor<Rpc<D>> { fn change_address(key: KeyFor<Rpc>) -> AddressFor<Rpc> {
address_from_serai_key(key, OutputType::Change) address_from_serai_key(key, OutputType::Change)
} }
fn forwarding_address(key: KeyFor<Rpc<D>>) -> AddressFor<Rpc<D>> { fn forwarding_address(key: KeyFor<Rpc>) -> AddressFor<Rpc> {
address_from_serai_key(key, OutputType::Forwarded) address_from_serai_key(key, OutputType::Forwarded)
} }
fn calculate_fee( fn calculate_fee(
fee_rate: Self::FeeRate, fee_rate: Self::FeeRate,
inputs: Vec<OutputFor<Rpc<D>>>, inputs: Vec<OutputFor<Rpc>>,
payments: Vec<Payment<AddressFor<Rpc<D>>>>, payments: Vec<Payment<AddressFor<Rpc>>>,
change: Option<KeyFor<Rpc<D>>>, change: Option<KeyFor<Rpc>>,
) -> Amount { ) -> Amount {
match signable_transaction::<D>(fee_rate, inputs, payments, change) { match signable_transaction::<D>(fee_rate, inputs, payments, change) {
Ok(tx) => Amount(tx.1.needed_fee()), Ok(tx) => Amount(tx.1.needed_fee()),
@ -294,10 +393,10 @@ impl TransactionPlanner<Rpc, ()> for Planner {
fn plan( fn plan(
fee_rate: Self::FeeRate, fee_rate: Self::FeeRate,
inputs: Vec<OutputFor<Rpc<D>>>, inputs: Vec<OutputFor<Rpc>>,
payments: Vec<Payment<AddressFor<Rpc<D>>>>, payments: Vec<Payment<AddressFor<Rpc>>>,
change: Option<KeyFor<Rpc<D>>>, change: Option<KeyFor<Rpc>>,
) -> PlannedTransaction<Rpc<D>, Self::SignableTransaction, ()> { ) -> PlannedTransaction<Rpc, Self::SignableTransaction, ()> {
let key = inputs.first().unwrap().key(); let key = inputs.first().unwrap().key();
for input in &inputs { for input in &inputs {
assert_eq!(key, input.key()); assert_eq!(key, input.key());