mirror of
https://github.com/serai-dex/serai.git
synced 2024-12-23 03:59:22 +00:00
Implement a basic TX IO selector algorithm
This commit is contained in:
parent
9b52cf4d20
commit
b83ca7d666
3 changed files with 110 additions and 23 deletions
|
@ -1,3 +1,5 @@
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use rand_core::{RngCore, CryptoRng};
|
use rand_core::{RngCore, CryptoRng};
|
||||||
|
|
||||||
|
@ -16,6 +18,7 @@ use monero_serai::{
|
||||||
|
|
||||||
use crate::{Output as OutputTrait, CoinError, Coin, view_key};
|
use crate::{Output as OutputTrait, CoinError, Coin, view_key};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct Output(SpendableOutput);
|
pub struct Output(SpendableOutput);
|
||||||
impl OutputTrait for Output {
|
impl OutputTrait for Output {
|
||||||
// While we could use (tx, o), using the key ensures we won't be susceptible to the burning bug.
|
// While we could use (tx, o), using the key ensures we won't be susceptible to the burning bug.
|
||||||
|
@ -104,9 +107,9 @@ impl Coin for Monero {
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn prepare_send<R: RngCore + CryptoRng>(
|
async fn prepare_send(
|
||||||
&self,
|
&self,
|
||||||
_keys: MultisigKeys<Ed25519>,
|
_keys: Arc<MultisigKeys<Ed25519>>,
|
||||||
_label: Vec<u8>,
|
_label: Vec<u8>,
|
||||||
_height: usize,
|
_height: usize,
|
||||||
_inputs: Vec<Output>,
|
_inputs: Vec<Output>,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::marker::Send;
|
use std::{marker::Send, sync::Arc};
|
||||||
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
@ -14,7 +14,7 @@ mod wallet;
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests;
|
mod tests;
|
||||||
|
|
||||||
pub trait Output: Sized {
|
pub trait Output: Sized + Clone {
|
||||||
type Id;
|
type Id;
|
||||||
|
|
||||||
fn id(&self) -> Self::Id;
|
fn id(&self) -> Self::Id;
|
||||||
|
@ -53,9 +53,9 @@ pub trait Coin {
|
||||||
key: <Self::Curve as Curve>::G
|
key: <Self::Curve as Curve>::G
|
||||||
) -> Vec<Self::Output>;
|
) -> Vec<Self::Output>;
|
||||||
|
|
||||||
async fn prepare_send<R: RngCore + CryptoRng>(
|
async fn prepare_send(
|
||||||
&self,
|
&self,
|
||||||
keys: MultisigKeys<Self::Curve>,
|
keys: Arc<MultisigKeys<Self::Curve>>,
|
||||||
label: Vec<u8>,
|
label: Vec<u8>,
|
||||||
height: usize,
|
height: usize,
|
||||||
inputs: Vec<Self::Output>,
|
inputs: Vec<Self::Output>,
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
use std::collections::HashMap;
|
use std::{sync::Arc, collections::HashMap};
|
||||||
|
|
||||||
use transcript::{Transcript, DigestTranscript};
|
use transcript::{Transcript, DigestTranscript};
|
||||||
use frost::{Curve, MultisigKeys};
|
use frost::{Curve, MultisigKeys};
|
||||||
|
|
||||||
use crate::{CoinError, Coin};
|
use crate::{CoinError, Output, Coin};
|
||||||
|
|
||||||
pub struct WalletKeys<C: Curve> {
|
pub struct WalletKeys<C: Curve> {
|
||||||
keys: MultisigKeys<C>,
|
keys: MultisigKeys<C>,
|
||||||
|
@ -43,9 +43,8 @@ pub struct CoinDb {
|
||||||
pub struct Wallet<C: Coin> {
|
pub struct Wallet<C: Coin> {
|
||||||
db: CoinDb,
|
db: CoinDb,
|
||||||
coin: C,
|
coin: C,
|
||||||
keys: Vec<MultisigKeys<C::Curve>>,
|
keys: Vec<(Arc<MultisigKeys<C::Curve>>, Vec<C::Output>)>,
|
||||||
pending: Vec<(usize, MultisigKeys<C::Curve>)>,
|
pending: Vec<(usize, MultisigKeys<C::Curve>)>
|
||||||
outputs: Vec<C::Output>
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<C: Coin> Wallet<C> {
|
impl<C: Coin> Wallet<C> {
|
||||||
|
@ -59,8 +58,7 @@ impl<C: Coin> Wallet<C> {
|
||||||
coin,
|
coin,
|
||||||
|
|
||||||
keys: vec![],
|
keys: vec![],
|
||||||
pending: vec![],
|
pending: vec![]
|
||||||
outputs: vec![]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,21 +78,107 @@ impl<C: Coin> Wallet<C> {
|
||||||
|
|
||||||
pub async fn poll(&mut self) -> Result<(), CoinError> {
|
pub async fn poll(&mut self) -> Result<(), CoinError> {
|
||||||
let confirmed_height = self.coin.get_height().await? - C::confirmations();
|
let confirmed_height = self.coin.get_height().await? - C::confirmations();
|
||||||
for h in self.scanned_height() .. confirmed_height {
|
for height in self.scanned_height() .. confirmed_height {
|
||||||
|
// If any keys activated at this height, shift them over
|
||||||
|
{
|
||||||
let mut k = 0;
|
let mut k = 0;
|
||||||
while k < self.pending.len() {
|
while k < self.pending.len() {
|
||||||
if h == self.pending[k].0 {
|
if height >= self.pending[k].0 {
|
||||||
self.keys.push(self.pending.swap_remove(k).1);
|
self.keys.push((Arc::new(self.pending.swap_remove(k).1), vec![]));
|
||||||
} else {
|
} else {
|
||||||
k += 1;
|
k += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let block = self.coin.get_block(h).await?;
|
let block = self.coin.get_block(height).await?;
|
||||||
for keys in &self.keys {
|
for (keys, outputs) in self.keys.iter_mut() {
|
||||||
let outputs = self.coin.get_outputs(&block, keys.group_key());
|
outputs.extend(
|
||||||
|
self.coin.get_outputs(&block, keys.group_key()).await.iter().cloned().filter(
|
||||||
|
|_output| true // !self.db.handled.contains_key(output.id()) // TODO
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn prepare_sends(
|
||||||
|
&mut self,
|
||||||
|
canonical: usize,
|
||||||
|
payments: Vec<(C::Address, u64)>
|
||||||
|
) -> Result<Vec<C::SignableTransaction>, CoinError> {
|
||||||
|
if payments.len() == 0 {
|
||||||
|
return Ok(vec![]);
|
||||||
|
}
|
||||||
|
|
||||||
|
let acknowledged_height = self.acknowledged_height(canonical);
|
||||||
|
|
||||||
|
// TODO: Log schedule outputs when max_outputs is low
|
||||||
|
// 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
|
||||||
|
// let (mut payments, schedule) = payments;
|
||||||
|
let mut payments = payments;
|
||||||
|
payments.sort_by(|a, b| a.1.cmp(&b.1).reverse());
|
||||||
|
|
||||||
|
let mut txs = vec![];
|
||||||
|
for (keys, outputs) in self.keys.iter_mut() {
|
||||||
|
// Select the highest value outputs to minimize the amount of inputs needed
|
||||||
|
outputs.sort_by(|a, b| a.amount().cmp(&b.amount()).reverse());
|
||||||
|
|
||||||
|
while outputs.len() != 0 {
|
||||||
|
// Select the maximum amount of outputs possible
|
||||||
|
let mut inputs = &outputs[0 .. C::max_inputs().min(outputs.len())];
|
||||||
|
|
||||||
|
// Calculate their sum value, minus the fee needed to spend them
|
||||||
|
let mut sum = inputs.iter().map(|input| input.amount()).sum::<u64>();
|
||||||
|
// sum -= C::MAX_FEE; // TODO
|
||||||
|
|
||||||
|
// Grab the payments this will successfully fund
|
||||||
|
let mut these_payments = vec![];
|
||||||
|
for payment in &payments {
|
||||||
|
if sum > payment.1 {
|
||||||
|
these_payments.push(payment);
|
||||||
|
sum -= payment.1;
|
||||||
|
}
|
||||||
|
// Doesn't break in this else case as a smaller payment may still fit
|
||||||
|
}
|
||||||
|
|
||||||
|
// Move to the next set of keys if none of these outputs remain significant
|
||||||
|
if these_payments.len() == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Drop any uneeded outputs
|
||||||
|
while sum > inputs[inputs.len() - 1].amount() {
|
||||||
|
sum -= inputs[inputs.len() - 1].amount();
|
||||||
|
inputs = &inputs[.. (inputs.len() - 1)];
|
||||||
|
}
|
||||||
|
|
||||||
|
// We now have a minimal effective outputs/payments set
|
||||||
|
// Take ownership while removing these candidates from the provided list
|
||||||
|
let inputs = outputs.drain(.. inputs.len()).collect();
|
||||||
|
let payments = payments.drain(.. these_payments.len()).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let tx = self.coin.prepare_send(
|
||||||
|
keys.clone(),
|
||||||
|
format!(
|
||||||
|
"Serai Processor Wallet Send (height {}, index {})",
|
||||||
|
canonical,
|
||||||
|
txs.len()
|
||||||
|
).as_bytes().to_vec(),
|
||||||
|
acknowledged_height,
|
||||||
|
inputs,
|
||||||
|
&payments
|
||||||
|
).await?;
|
||||||
|
// self.db.save_tx(tx) // TODO
|
||||||
|
txs.push(tx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Remaining payments?
|
||||||
|
|
||||||
|
Ok(txs)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue