mirror of
https://github.com/serai-dex/serai.git
synced 2024-12-23 03:59:22 +00:00
Add a builder API to the Monero library
Enables more composable construction flows.
This commit is contained in:
parent
3f503d92fb
commit
f0957c8d52
4 changed files with 163 additions and 3 deletions
|
@ -24,7 +24,7 @@ pub(crate) mod decoys;
|
||||||
pub(crate) use decoys::Decoys;
|
pub(crate) use decoys::Decoys;
|
||||||
|
|
||||||
mod send;
|
mod send;
|
||||||
pub use send::{Fee, TransactionError, SignableTransaction};
|
pub use send::{Fee, TransactionError, SignableTransaction, SignableTransactionBuilder};
|
||||||
#[cfg(feature = "multisig")]
|
#[cfg(feature = "multisig")]
|
||||||
pub use send::TransactionMachine;
|
pub use send::TransactionMachine;
|
||||||
|
|
||||||
|
|
119
coins/monero/src/wallet/send/builder.rs
Normal file
119
coins/monero/src/wallet/send/builder.rs
Normal file
|
@ -0,0 +1,119 @@
|
||||||
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
|
use zeroize::{Zeroize, ZeroizeOnDrop};
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
Protocol,
|
||||||
|
wallet::{address::MoneroAddress, Fee, SpendableOutput, SignableTransaction, TransactionError},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone, PartialEq, Eq, Debug, Zeroize, ZeroizeOnDrop)]
|
||||||
|
struct SignableTransactionBuilderInternal {
|
||||||
|
protocol: Protocol,
|
||||||
|
fee: Fee,
|
||||||
|
|
||||||
|
inputs: Vec<SpendableOutput>,
|
||||||
|
payments: Vec<(MoneroAddress, u64)>,
|
||||||
|
change_address: Option<MoneroAddress>,
|
||||||
|
data: Option<Vec<u8>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SignableTransactionBuilderInternal {
|
||||||
|
// Takes in the change address so users don't miss that they have to manually set one
|
||||||
|
// If they don't, all leftover funds will become part of the fee
|
||||||
|
fn new(protocol: Protocol, fee: Fee, change_address: Option<MoneroAddress>) -> Self {
|
||||||
|
Self { protocol, fee, inputs: vec![], payments: vec![], change_address, data: None }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_input(&mut self, input: SpendableOutput) {
|
||||||
|
self.inputs.push(input);
|
||||||
|
}
|
||||||
|
fn add_inputs(&mut self, inputs: &[SpendableOutput]) {
|
||||||
|
self.inputs.extend(inputs.iter().cloned());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_payment(&mut self, dest: MoneroAddress, amount: u64) {
|
||||||
|
self.payments.push((dest, amount));
|
||||||
|
}
|
||||||
|
fn add_payments(&mut self, payments: &[(MoneroAddress, u64)]) {
|
||||||
|
self.payments.extend(payments);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_data(&mut self, data: Vec<u8>) {
|
||||||
|
self.data = Some(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A Transaction Builder for Monero transactions.
|
||||||
|
/// All methods provided will modify self while also returning a shallow copy, enabling efficient
|
||||||
|
/// chaining with a clean API.
|
||||||
|
/// In order to fork the builder at some point, clone will still return a deep copy.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct SignableTransactionBuilder(Arc<RwLock<SignableTransactionBuilderInternal>>);
|
||||||
|
impl Clone for SignableTransactionBuilder {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
Self(Arc::new(RwLock::new((*self.0.read().unwrap()).clone())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialEq for SignableTransactionBuilder {
|
||||||
|
fn eq(&self, other: &Self) -> bool {
|
||||||
|
*self.0.read().unwrap() == *other.0.read().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Eq for SignableTransactionBuilder {}
|
||||||
|
|
||||||
|
impl Zeroize for SignableTransactionBuilder {
|
||||||
|
fn zeroize(&mut self) {
|
||||||
|
self.0.write().unwrap().zeroize()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SignableTransactionBuilder {
|
||||||
|
fn shallow_copy(&self) -> Self {
|
||||||
|
Self(self.0.clone())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(protocol: Protocol, fee: Fee, change_address: Option<MoneroAddress>) -> Self {
|
||||||
|
Self(Arc::new(RwLock::new(SignableTransactionBuilderInternal::new(
|
||||||
|
protocol,
|
||||||
|
fee,
|
||||||
|
change_address,
|
||||||
|
))))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_input(&mut self, input: SpendableOutput) -> Self {
|
||||||
|
self.0.write().unwrap().add_input(input);
|
||||||
|
self.shallow_copy()
|
||||||
|
}
|
||||||
|
pub fn add_inputs(&mut self, inputs: &[SpendableOutput]) -> Self {
|
||||||
|
self.0.write().unwrap().add_inputs(inputs);
|
||||||
|
self.shallow_copy()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn add_payment(&mut self, dest: MoneroAddress, amount: u64) -> Self {
|
||||||
|
self.0.write().unwrap().add_payment(dest, amount);
|
||||||
|
self.shallow_copy()
|
||||||
|
}
|
||||||
|
pub fn add_payments(&mut self, payments: &[(MoneroAddress, u64)]) -> Self {
|
||||||
|
self.0.write().unwrap().add_payments(payments);
|
||||||
|
self.shallow_copy()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_data(&mut self, data: Vec<u8>) -> Self {
|
||||||
|
self.0.write().unwrap().set_data(data);
|
||||||
|
self.shallow_copy()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(self) -> Result<SignableTransaction, TransactionError> {
|
||||||
|
let read = self.0.read().unwrap();
|
||||||
|
SignableTransaction::new(
|
||||||
|
read.protocol,
|
||||||
|
read.inputs.clone(),
|
||||||
|
read.payments.clone(),
|
||||||
|
read.change_address,
|
||||||
|
read.data.clone(),
|
||||||
|
read.fee,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -28,6 +28,9 @@ use crate::{
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
mod builder;
|
||||||
|
pub use builder::SignableTransactionBuilder;
|
||||||
|
|
||||||
#[cfg(feature = "multisig")]
|
#[cfg(feature = "multisig")]
|
||||||
mod multisig;
|
mod multisig;
|
||||||
#[cfg(feature = "multisig")]
|
#[cfg(feature = "multisig")]
|
||||||
|
@ -156,7 +159,7 @@ async fn prepare_inputs<R: RngCore + CryptoRng>(
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Fee struct, defined as a per-unit cost and a mask for rounding purposes.
|
/// Fee struct, defined as a per-unit cost and a mask for rounding purposes.
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Zeroize)]
|
||||||
pub struct Fee {
|
pub struct Fee {
|
||||||
pub per_weight: u64,
|
pub per_weight: u64,
|
||||||
pub mask: u64,
|
pub mask: u64,
|
||||||
|
|
|
@ -24,7 +24,10 @@ use frost::{
|
||||||
|
|
||||||
use monero_serai::{
|
use monero_serai::{
|
||||||
random_scalar,
|
random_scalar,
|
||||||
wallet::{address::Network, ViewPair, Scanner, SpendableOutput, SignableTransaction},
|
wallet::{
|
||||||
|
address::Network, ViewPair, Scanner, SpendableOutput, SignableTransaction,
|
||||||
|
SignableTransactionBuilder,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
mod rpc;
|
mod rpc;
|
||||||
|
@ -192,3 +195,38 @@ async_sequential! {
|
||||||
send_core(1, true).await;
|
send_core(1, true).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async_sequential! {
|
||||||
|
async fn builder() {
|
||||||
|
let rpc = rpc().await;
|
||||||
|
|
||||||
|
// Generate an address
|
||||||
|
let spend = Zeroizing::new(random_scalar(&mut OsRng));
|
||||||
|
let view = random_scalar(&mut OsRng);
|
||||||
|
let spend_pub = spend.deref() * &ED25519_BASEPOINT_TABLE;
|
||||||
|
|
||||||
|
let view_pair = ViewPair::new(spend_pub, view);
|
||||||
|
let mut scanner = Scanner::from_view(view_pair, Network::Mainnet, Some(HashSet::new()));
|
||||||
|
let addr = scanner.address();
|
||||||
|
|
||||||
|
let fee = rpc.get_fee().await.unwrap();
|
||||||
|
|
||||||
|
let start = rpc.get_height().await.unwrap();
|
||||||
|
for _ in 0 .. 7 {
|
||||||
|
mine_block(&rpc, &addr.to_string()).await.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
let coinbase = rpc.get_block_transactions(start).await.unwrap().swap_remove(0);
|
||||||
|
let output = scanner.scan_transaction(&coinbase).ignore_timelock().swap_remove(0);
|
||||||
|
rpc.publish_transaction(
|
||||||
|
&SignableTransactionBuilder::new(rpc.get_protocol().await.unwrap(), fee, Some(addr))
|
||||||
|
.add_input(SpendableOutput::from(&rpc, output).await.unwrap())
|
||||||
|
.add_payment(addr, 0)
|
||||||
|
.build()
|
||||||
|
.unwrap()
|
||||||
|
.sign(&mut OsRng, &rpc, &spend)
|
||||||
|
.await
|
||||||
|
.unwrap()
|
||||||
|
).await.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue