Allow scheduler's creation of transactions to be async and error

I don't love this, but it's the only way to select decoys without using a local
database. While the prior commit added such a databse, the performance of it
presumably wasn't viable, and while TODOs marked the needed improvements, it
was still messy with an immense scope re: any auditing.

The relevant scheduler functions now take `&self` (intentional, as all
mutations should be via the `&mut impl DbTxn` passed). The calls to `&self` are
expected to be completely deterministic (as usual).
This commit is contained in:
Luke Parker
2024-09-14 01:09:35 -04:00
parent 2edc2f3612
commit e1ad897f7e
11 changed files with 723 additions and 854 deletions

View File

@@ -22,7 +22,7 @@ use crate::key_gen::KeyGenParams;
mod rpc;
use rpc::Rpc;
mod scheduler;
use scheduler::Scheduler;
use scheduler::{Planner, Scheduler};
// Our custom code for Bitcoin
mod db;
@@ -57,7 +57,7 @@ async fn main() {
tokio::spawn(TxIndexTask(feed.clone()).continually_run(index_task, vec![]));
core::mem::forget(index_handle);
bin::main_loop::<_, KeyGenParams, Scheduler<_>, Rpc<bin::Db>>(db, feed.clone(), feed).await;
bin::main_loop::<_, KeyGenParams, _>(db, feed.clone(), Scheduler::new(Planner), feed).await;
}
/*

View File

@@ -1,3 +1,5 @@
use core::future::Future;
use ciphersuite::{Ciphersuite, Secp256k1};
use bitcoin_serai::{
@@ -89,8 +91,10 @@ fn signable_transaction<D: Db>(
.map(|bst| (SignableTransaction { inputs, payments, change, fee_per_vbyte }, bst))
}
#[derive(Clone)]
pub(crate) struct Planner;
impl<D: Db> TransactionPlanner<Rpc<D>, EffectedReceivedOutputs<Rpc<D>>> for Planner {
type EphemeralError = ();
type FeeRate = u64;
type SignableTransaction = SignableTransaction;
@@ -153,50 +157,59 @@ impl<D: Db> TransactionPlanner<Rpc<D>, EffectedReceivedOutputs<Rpc<D>>> for Plan
}
fn plan(
&self,
fee_rate: Self::FeeRate,
inputs: Vec<OutputFor<Rpc<D>>>,
payments: Vec<Payment<AddressFor<Rpc<D>>>>,
change: Option<KeyFor<Rpc<D>>>,
) -> PlannedTransaction<Rpc<D>, Self::SignableTransaction, EffectedReceivedOutputs<Rpc<D>>> {
let key = inputs.first().unwrap().key();
for input in &inputs {
assert_eq!(key, input.key());
}
) -> impl Send
+ Future<
Output = Result<
PlannedTransaction<Rpc<D>, Self::SignableTransaction, EffectedReceivedOutputs<Rpc<D>>>,
Self::EphemeralError,
>,
> {
async move {
let key = inputs.first().unwrap().key();
for input in &inputs {
assert_eq!(key, input.key());
}
let singular_spent_output = (inputs.len() == 1).then(|| inputs[0].id());
match signable_transaction::<D>(fee_rate, inputs.clone(), payments, change) {
Ok(tx) => PlannedTransaction {
signable: tx.0,
eventuality: Eventuality { txid: tx.1.txid(), singular_spent_output },
auxilliary: EffectedReceivedOutputs({
let tx = tx.1.transaction();
let scanner = scanner(key);
let singular_spent_output = (inputs.len() == 1).then(|| inputs[0].id());
match signable_transaction::<D>(fee_rate, inputs.clone(), payments, change) {
Ok(tx) => Ok(PlannedTransaction {
signable: tx.0,
eventuality: Eventuality { txid: tx.1.txid(), singular_spent_output },
auxilliary: EffectedReceivedOutputs({
let tx = tx.1.transaction();
let scanner = scanner(key);
let mut res = vec![];
for output in scanner.scan_transaction(tx) {
res.push(Output::new_with_presumed_origin(
key,
tx,
// It shouldn't matter if this is wrong as we should never try to return these
// We still provide an accurate value to ensure a lack of discrepancies
Some(Address::new(inputs[0].output.output().script_pubkey.clone()).unwrap()),
output,
));
}
res
let mut res = vec![];
for output in scanner.scan_transaction(tx) {
res.push(Output::new_with_presumed_origin(
key,
tx,
// It shouldn't matter if this is wrong as we should never try to return these
// We still provide an accurate value to ensure a lack of discrepancies
Some(Address::new(inputs[0].output.output().script_pubkey.clone()).unwrap()),
output,
));
}
res
}),
}),
},
Err(
TransactionError::NoInputs | TransactionError::NoOutputs | TransactionError::DustPayment,
) => panic!("malformed arguments to plan"),
// No data, we have a minimum fee rate, we checked the amount of inputs/outputs
Err(
TransactionError::TooMuchData |
TransactionError::TooLowFee |
TransactionError::TooLargeTransaction,
) => unreachable!(),
Err(TransactionError::NotEnoughFunds { .. }) => {
panic!("plan called for a transaction without enough funds")
Err(
TransactionError::NoInputs | TransactionError::NoOutputs | TransactionError::DustPayment,
) => panic!("malformed arguments to plan"),
// No data, we have a minimum fee rate, we checked the amount of inputs/outputs
Err(
TransactionError::TooMuchData |
TransactionError::TooLowFee |
TransactionError::TooLargeTransaction,
) => unreachable!(),
Err(TransactionError::NotEnoughFunds { .. }) => {
panic!("plan called for a transaction without enough funds")
}
}
}
}