more comments

This commit is contained in:
fbock
2024-08-27 15:18:42 +02:00
parent 4f68b303eb
commit 2687a7084c
8 changed files with 53 additions and 64 deletions

View File

@ -123,23 +123,7 @@ async fn submit_escrow_psbt(
Ok(StatusCode::NOT_ACCEPTABLE.into_response()) Ok(StatusCode::NOT_ACCEPTABLE.into_response())
} }
_ => Ok(StatusCode::INTERNAL_SERVER_ERROR.into_response()), _ => Ok(StatusCode::INTERNAL_SERVER_ERROR.into_response()),
// Err(RequestError::NotFound) => {
// info!("Offer for escrow psbt not found");
// Ok(StatusCode::NOT_FOUND.into_response())
// }
// Err(RequestError::NotConfirmed) => {
// info!("Offer for escrow psbt not confirmed");
// Ok(StatusCode::NOT_ACCEPTABLE.into_response())
// }
// Err(RequestError::Database(e)) => {
// error!("Database error fetching escrow psbt: {e}");
// Ok(StatusCode::INTERNAL_SERVER_ERROR.into_response())
// }
} }
// check if psbt is correct, valid and signed
// publish psbt if it is correct
// return 200 if everything is correct
// return 400 if something is wrong
} }
/// Will get polled by the traders once they submitted their PSBT part. The coorinator will return status code 200 once he received both PSBTs and they got mined, /// Will get polled by the traders once they submitted their PSBT part. The coorinator will return status code 200 once he received both PSBTs and they got mined,
@ -163,6 +147,7 @@ async fn poll_escrow_confirmation(
} }
} }
/// gets called if the trader is happy and does not want to initiate escrow
async fn submit_obligation_confirmation( async fn submit_obligation_confirmation(
Extension(coordinator): Extension<Arc<Coordinator>>, Extension(coordinator): Extension<Arc<Coordinator>>,
Json(payload): Json<OfferTakenRequest>, Json(payload): Json<OfferTakenRequest>,
@ -190,8 +175,8 @@ async fn submit_obligation_confirmation(
// or // or
// gets called if one of the traders wants to initiate escrow (e.g. claiming they didn't receive the fiat) /// gets called if one of the traders wants to initiate escrow (e.g. claiming they didn't receive the fiat)
// before timeout ends, just sets the maker unhappy and escrow onging flag in the db /// before timeout ends, just sets the maker unhappy and escrow onging flag in the db
async fn request_escrow( async fn request_escrow(
Extension(coordinator): Extension<Arc<Coordinator>>, Extension(coordinator): Extension<Arc<Coordinator>>,
Json(payload): Json<TradeObligationsUnsatisfied>, Json(payload): Json<TradeObligationsUnsatisfied>,
@ -255,6 +240,7 @@ async fn poll_final_payout(
} }
} }
/// recieves the partial signature for the keyspend payout transaction
async fn submit_payout_signature( async fn submit_payout_signature(
Extension(coordinator): Extension<Arc<Coordinator>>, Extension(coordinator): Extension<Arc<Coordinator>>,
Json(payload): Json<PayoutSignatureRequest>, Json(payload): Json<PayoutSignatureRequest>,
@ -266,18 +252,6 @@ async fn submit_payout_signature(
// this was the first signature // this was the first signature
Ok(false) => Ok(StatusCode::ACCEPTED.into_response()), Ok(false) => Ok(StatusCode::ACCEPTED.into_response()),
// Err(RequestError::NotConfirmed) => {
// info!("Offer tx for final payout not confirmed");
// Ok(StatusCode::NOT_ACCEPTABLE.into_response())
// }
// Err(RequestError::NotFound) => {
// info!("Offer for final payout not found");
// Ok(StatusCode::NOT_FOUND.into_response())
// }
// Err(RequestError::Database(e)) => {
// error!("Database error fetching final payout: {e}");
// Ok(StatusCode::INTERNAL_SERVER_ERROR.into_response())
// }
e => { e => {
error!("Unknown error handling submit_payout_signature(): {:?}", e); error!("Unknown error handling submit_payout_signature(): {:?}", e);
Ok(StatusCode::INTERNAL_SERVER_ERROR.into_response()) Ok(StatusCode::INTERNAL_SERVER_ERROR.into_response())
@ -285,6 +259,7 @@ async fn submit_payout_signature(
} }
} }
/// testing endpoint
async fn test_api() -> &'static str { async fn test_api() -> &'static str {
"Hello, World!" "Hello, World!"
} }
@ -312,7 +287,7 @@ pub async fn api_server(coordinator: Arc<Coordinator>) -> Result<()> {
let port: u16 = env::var("PORT") let port: u16 = env::var("PORT")
.unwrap_or_else(|_| "9999".to_string()) .unwrap_or_else(|_| "9999".to_string())
.parse()?; .parse()?;
info!("Listening on {}", port); info!("Coordinator is listening on port {}", port);
let addr = SocketAddr::from(([127, 0, 0, 1], port)); let addr = SocketAddr::from(([127, 0, 0, 1], port));
let tcp = TcpListener::bind(&addr).await.unwrap(); let tcp = TcpListener::bind(&addr).await.unwrap();
axum::serve(tcp, app).await?; axum::serve(tcp, app).await?;

View File

@ -19,6 +19,8 @@ pub struct PayoutData {
pub aggregated_musig_pubkey_ctx_hex: String, pub aggregated_musig_pubkey_ctx_hex: String,
} }
/// KeyspendContext contains all data neccessary to create the
/// signed keyspend payout transaction
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct KeyspendContext { pub struct KeyspendContext {
pub agg_sig: LiftedSignature, pub agg_sig: LiftedSignature,
@ -27,7 +29,10 @@ pub struct KeyspendContext {
pub keyspend_psbt: PartiallySignedTransaction, pub keyspend_psbt: PartiallySignedTransaction,
} }
/// takes two hex encoded pub musig nonces (serialized according to musig2 crate) and
/// aggregates them into a single MusigAggNonce
pub fn agg_hex_musig_nonces(maker_nonce: &str, taker_nonce: &str) -> Result<MusigAggNonce> { pub fn agg_hex_musig_nonces(maker_nonce: &str, taker_nonce: &str) -> Result<MusigAggNonce> {
// decode the hex strings into MusigPubNonces
let musig_pub_nonce_maker = match MusigPubNonce::from_hex(maker_nonce) { let musig_pub_nonce_maker = match MusigPubNonce::from_hex(maker_nonce) {
Ok(musig_pub_nonce_maker) => musig_pub_nonce_maker, Ok(musig_pub_nonce_maker) => musig_pub_nonce_maker,
Err(e) => { Err(e) => {
@ -47,12 +52,14 @@ pub fn agg_hex_musig_nonces(maker_nonce: &str, taker_nonce: &str) -> Result<Musi
} }
}; };
// aggregate the two pub nonces
let agg_nonce = musig2::AggNonce::sum([musig_pub_nonce_maker, musig_pub_nonce_taker]); let agg_nonce = musig2::AggNonce::sum([musig_pub_nonce_maker, musig_pub_nonce_taker]);
Ok(agg_nonce) Ok(agg_nonce)
} }
impl KeyspendContext { impl KeyspendContext {
/// Create a new KeyspendContext from hex encoded strings recieved from the maker and taker
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn from_hex_str( pub fn from_hex_str(
maker_sig: &str, maker_sig: &str,
@ -64,21 +71,26 @@ impl KeyspendContext {
keyspend_psbt: &str, keyspend_psbt: &str,
descriptor: &str, descriptor: &str,
) -> anyhow::Result<Self> { ) -> anyhow::Result<Self> {
// obtain the tweak scalar from the escrow output descriptor
let tweak = get_keyspend_tweak_scalar(descriptor)?; let tweak = get_keyspend_tweak_scalar(descriptor)?;
// aggregate the maker and taker musig pubkeys with the tweak
let agg_keyspend_pk: musig2::KeyAggContext = let agg_keyspend_pk: musig2::KeyAggContext =
aggregate_musig_pubkeys_with_tweak(maker_pk, taker_pk, tweak)?; aggregate_musig_pubkeys_with_tweak(maker_pk, taker_pk, tweak)?;
// aggregate the public nonces
let agg_nonce: MusigAggNonce = let agg_nonce: MusigAggNonce =
coordinator_utils::agg_hex_musig_nonces(maker_nonce, taker_nonce)?; coordinator_utils::agg_hex_musig_nonces(maker_nonce, taker_nonce)?;
// deserialize the keyspend psbt
let keyspend_psbt = PartiallySignedTransaction::deserialize(&hex::decode(keyspend_psbt)?)?; let keyspend_psbt = PartiallySignedTransaction::deserialize(&hex::decode(keyspend_psbt)?)?;
let partial_maker_sig = PartialSignature::from_hex(maker_sig)?; let partial_maker_sig = PartialSignature::from_hex(maker_sig)?;
let partial_taker_sig = PartialSignature::from_hex(taker_sig)?; let partial_taker_sig = PartialSignature::from_hex(taker_sig)?;
let partial_signatures = vec![partial_maker_sig, partial_taker_sig]; let partial_signatures = vec![partial_maker_sig, partial_taker_sig];
// let msg = keyspend_psbt. // obtain the message to sign (taproot key spend signature hash)
let msg = { let msg = {
let mut sig_hash_cache = SighashCache::new(keyspend_psbt.unsigned_tx.clone()); let mut sig_hash_cache = SighashCache::new(keyspend_psbt.unsigned_tx.clone());
// it should only contain one utxo, the escrow locking UTXO
let utxo = keyspend_psbt let utxo = keyspend_psbt
.iter_funding_utxos() .iter_funding_utxos()
.next() .next()
@ -110,6 +122,7 @@ impl KeyspendContext {
} }
} }
/// get the scalar used to tweak the keyspend output key from the escrow output descriptor
fn get_keyspend_tweak_scalar(descriptor: &str) -> Result<bdk::bitcoin::secp256k1::Scalar> { fn get_keyspend_tweak_scalar(descriptor: &str) -> Result<bdk::bitcoin::secp256k1::Scalar> {
let tr_descriptor: Descriptor<XOnlyPublicKey> = let tr_descriptor: Descriptor<XOnlyPublicKey> =
bdk::descriptor::Descriptor::from_str(descriptor)?; bdk::descriptor::Descriptor::from_str(descriptor)?;
@ -130,6 +143,7 @@ fn get_keyspend_tweak_scalar(descriptor: &str) -> Result<bdk::bitcoin::secp256k1
Ok(tweak.to_scalar()) Ok(tweak.to_scalar())
} }
/// Aggregate two hex encoded musig pubkeys with a tweak scalar
pub fn aggregate_musig_pubkeys_with_tweak( pub fn aggregate_musig_pubkeys_with_tweak(
maker_musig_pubkey: &str, maker_musig_pubkey: &str,
taker_musig_pubkey: &str, taker_musig_pubkey: &str,
@ -152,6 +166,8 @@ pub fn aggregate_musig_pubkeys_with_tweak(
} }
impl PayoutData { impl PayoutData {
/// assembles the information retrieved from the database into a PayoutData struct which is used for
/// constructing the keyspend payout transaction passed on to the traders for signing
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
pub fn new_from_strings( pub fn new_from_strings(
escrow_output_descriptor: &str, escrow_output_descriptor: &str,
@ -189,17 +205,20 @@ impl PayoutData {
} }
} }
pub fn generate_random_order_id(len: usize) -> String { /// generates a random order id of size `size` bytes and returns it as hex encoded string
pub fn generate_random_order_id(size: usize) -> String {
// Generate `len` random bytes // Generate `len` random bytes
let bytes: Vec<u8> = rand::thread_rng() let bytes: Vec<u8> = rand::thread_rng()
.sample_iter(&rand::distributions::Standard) .sample_iter(&rand::distributions::Standard)
.take(len) .take(size)
.collect(); .collect();
// Convert bytes to hex string // Convert bytes to hex string
hex::encode(bytes) hex::encode(bytes)
} }
/// does check both that a robhash and offerID are actually existing in the database
/// and that the escrow locking transaction is confirmed
pub async fn check_offer_and_confirmation( pub async fn check_offer_and_confirmation(
offer_id_hex: &str, offer_id_hex: &str,
robohash_hex: &str, robohash_hex: &str,

View File

@ -87,6 +87,8 @@ pub struct MempoolHandler {
} }
impl MempoolHandler { impl MempoolHandler {
/// creates a new mempool handler connected to a json rpc client which spawns
/// a new tokio thread which keeps track of the mempool state
pub async fn new(json_rpc_client: Arc<Client>) -> Self { pub async fn new(json_rpc_client: Arc<Client>) -> Self {
let mempool = Arc::new(Mempool::new(json_rpc_client)); let mempool = Arc::new(Mempool::new(json_rpc_client));
let mempool_clone = Arc::clone(&mempool); let mempool_clone = Arc::clone(&mempool);
@ -101,6 +103,7 @@ impl MempoolHandler {
} }
} }
/// called to look for UTXOs in the mempool
pub async fn lookup_mempool_inputs( pub async fn lookup_mempool_inputs(
&self, &self,
bonds: &Vec<MonitoringBond>, bonds: &Vec<MonitoringBond>,
@ -125,6 +128,7 @@ impl MempoolHandler {
Ok(bonds_to_punish) Ok(bonds_to_punish)
} }
/// kills the mempool thread, needed to prevent long running when ending the program, especially in tests
pub async fn shutdown(&self) { pub async fn shutdown(&self) {
if let Some(sender) = self.shutdown_sender.lock().await.take() { if let Some(sender) = self.shutdown_sender.lock().await.take() {
let _ = sender.send(()); // Ignore the result, as the receiver might have been dropped let _ = sender.send(()); // Ignore the result, as the receiver might have been dropped

View File

@ -1,5 +1,6 @@
use super::*; use super::*;
/// fetches confirmations of the txids in unconfirmed_txids using core rpc
fn get_confirmations( fn get_confirmations(
unconfirmed_txids: Vec<String>, unconfirmed_txids: Vec<String>,
coordinator: Arc<Coordinator>, coordinator: Arc<Coordinator>,
@ -58,6 +59,9 @@ fn get_confirmations(
Ok(now_confirmed_txs) Ok(now_confirmed_txs)
} }
/// pulls txids of unconfirmed escrow transactions from the database, checks
/// for confirmations using the bitcoin core rpc api and updates the database
/// entries with the confirmation
pub async fn update_transaction_confirmations(coordinator: Arc<Coordinator>) { pub async fn update_transaction_confirmations(coordinator: Arc<Coordinator>) {
loop { loop {
tokio::time::sleep(std::time::Duration::from_secs(30)).await; tokio::time::sleep(std::time::Duration::from_secs(30)).await;
@ -78,6 +82,7 @@ pub async fn update_transaction_confirmations(coordinator: Arc<Coordinator>) {
continue; continue;
} }
let coordinator_clone = Arc::clone(&coordinator); let coordinator_clone = Arc::clone(&coordinator);
// spawn blocking because the core rpc call is blocking
let newly_confirmed_txids = match tokio::task::spawn_blocking(move || { let newly_confirmed_txids = match tokio::task::spawn_blocking(move || {
get_confirmations(unconfirmed_transactions, coordinator_clone) get_confirmations(unconfirmed_transactions, coordinator_clone)
}) })

View File

@ -110,7 +110,7 @@ async fn main() -> Result<()> {
coordinator_wallet: Arc::new(init_coordinator_wallet().await?), coordinator_wallet: Arc::new(init_coordinator_wallet().await?),
}); });
// begin monitoring bonds as separate tokio taks which runs concurrently // begin monitoring bonds as separate tokio task which runs concurrently
let coordinator_ref = Arc::clone(&coordinator); let coordinator_ref = Arc::clone(&coordinator);
tokio::spawn(async move { tokio::spawn(async move {
loop { loop {

View File

@ -12,6 +12,7 @@ pub struct EscrowPsbtConstructionData {
} }
impl EscrowPsbtConstructionData { impl EscrowPsbtConstructionData {
/// get the sum of the input utxos
pub fn input_sum(&self) -> Result<u64> { pub fn input_sum(&self) -> Result<u64> {
let mut input_sum = 0; let mut input_sum = 0;
for input in &self.escrow_input_utxos { for input in &self.escrow_input_utxos {
@ -26,6 +27,7 @@ impl EscrowPsbtConstructionData {
} }
} }
/// aggregate hex encoded musig pubkeys to a KeyAggContext without tweaking
pub fn aggregate_musig_pubkeys( pub fn aggregate_musig_pubkeys(
maker_musig_pubkey: &str, maker_musig_pubkey: &str,
taker_musig_pubkey: &str, taker_musig_pubkey: &str,
@ -43,7 +45,7 @@ pub fn aggregate_musig_pubkeys(
Ok(key_agg_ctx) Ok(key_agg_ctx)
} }
/// this function builds the escrow output with all possible spending conditions /// this function builds the escrow output descriptor with all possible spending conditions
pub fn build_escrow_transaction_output_descriptor( pub fn build_escrow_transaction_output_descriptor(
maker_escrow_data: &EscrowPsbtConstructionData, maker_escrow_data: &EscrowPsbtConstructionData,
taker_escrow_data: &EscrowPsbtConstructionData, taker_escrow_data: &EscrowPsbtConstructionData,
@ -113,8 +115,8 @@ pub fn build_escrow_transaction_output_descriptor(
Ok(descriptor) // then spend to descriptor.address(Network::Regtest) Ok(descriptor) // then spend to descriptor.address(Network::Regtest)
} }
// pub fn assemble_escrow_psbts(
impl<D: bdk::database::BatchDatabase> CoordinatorWallet<D> { impl<D: bdk::database::BatchDatabase> CoordinatorWallet<D> {
/// assemble the escrow locking transaction as psbt and return it with relevant associated data
pub async fn create_escrow_psbt( pub async fn create_escrow_psbt(
&self, &self,
db: &Arc<CoordinatorDB>, db: &Arc<CoordinatorDB>,
@ -149,32 +151,9 @@ impl<D: bdk::database::BatchDatabase> CoordinatorWallet<D> {
.await?; .await?;
let (escrow_psbt, details) = { let (escrow_psbt, details) = {
// maybe we can generate a address/taproot pk directly from the descriptor without a new wallet? // get address for escrow output from the descriptor
// let temp_wallet = Wallet::new(
// &escrow_output_descriptor,
// None,
// bitcoin::Network::Regtest,
// MemoryDatabase::new(),
// )?;
let escrow_address = let escrow_address =
escrow_output_descriptor.address(bdk::bitcoin::Network::Regtest)?; escrow_output_descriptor.address(bdk::bitcoin::Network::Regtest)?;
// dbg!("building untweaked address now");
// let agg_untweaked: musig2::secp256k1::PublicKey = aggregate_musig_pubkeys(
// &maker_psbt_input_data.musig_pubkey_compressed_hex,
// &taker_psbt_input_data.musig_pubkey_compressed_hex,
// )?
// .aggregated_pubkey_untweaked();
// let xonly_hex = agg_untweaked.x_only_public_key().0.to_string();
// let bdk_xonly = bdk::bitcoin::key::XOnlyPublicKey::from_str(&xonly_hex)?;
// let output_key = TweakedPublicKey::dangerous_assume_tweaked(bdk_xonly);
// let escrow_address = bdk::bitcoin::Address::p2tr_tweaked(output_key, Network::Regtest);
// escrow_output_descriptor = Descriptor::<XOnlyPublicKey>::new_tr(bdk_xonly, None)
// .context("Error assembling escrow output descriptor")?;
// dummy escrow address for testing the psbt signing flow
// let escrow_address =
// Address::from_str(self.get_new_address().await?.as_str())?.assume_checked();
// using absolute fee for now, in production we should come up with a way to determine the tx weight // using absolute fee for now, in production we should come up with a way to determine the tx weight
// upfront and substract the fee from the change outputs (10k == ~30/sat vbyte) // upfront and substract the fee from the change outputs (10k == ~30/sat vbyte)

View File

@ -37,6 +37,7 @@ pub struct BondRequirements {
pub min_input_sum_sat: u64, pub min_input_sum_sat: u64,
} }
/// sets up the coordinator bdk wallet from the env variables
pub async fn init_coordinator_wallet() -> Result<CoordinatorWallet<MemoryDatabase>> { pub async fn init_coordinator_wallet() -> Result<CoordinatorWallet<MemoryDatabase>> {
let wallet_xprv = ExtendedPrivKey::from_str( let wallet_xprv = ExtendedPrivKey::from_str(
&env::var("WALLET_XPRV").context("loading WALLET_XPRV from .env failed")?, &env::var("WALLET_XPRV").context("loading WALLET_XPRV from .env failed")?,
@ -49,7 +50,7 @@ pub async fn init_coordinator_wallet() -> Result<CoordinatorWallet<MemoryDatabas
password: env::var("BITCOIN_RPC_PASSWORD")?, password: env::var("BITCOIN_RPC_PASSWORD")?,
}, },
network: Network::Regtest, network: Network::Regtest,
// wallet_name: env::var("BITCOIN_RPC_WALLET_NAME")?, // derives wallet name from xprv/wallet
wallet_name: bdk::wallet::wallet_name_from_descriptor( wallet_name: bdk::wallet::wallet_name_from_descriptor(
Bip86(wallet_xprv, KeychainKind::External), Bip86(wallet_xprv, KeychainKind::External),
Some(Bip86(wallet_xprv, KeychainKind::Internal)), Some(Bip86(wallet_xprv, KeychainKind::Internal)),
@ -63,10 +64,9 @@ pub async fn init_coordinator_wallet() -> Result<CoordinatorWallet<MemoryDatabas
rpc_config.auth.clone().into(), rpc_config.auth.clone().into(),
)?); )?);
let json_rpc_client_clone = Arc::clone(&json_rpc_client); let json_rpc_client_clone = Arc::clone(&json_rpc_client);
// start new mempool instance
let mempool = MempoolHandler::new(json_rpc_client_clone).await; let mempool = MempoolHandler::new(json_rpc_client_clone).await;
let backend = RpcBlockchain::from_config(&rpc_config)?; let backend = RpcBlockchain::from_config(&rpc_config)?;
// let backend = EsploraBlockchain::new(&env::var("ESPLORA_BACKEND")?, 1000);
// let sled_db = sled::open(env::var("BDK_DB_PATH")?)?.open_tree("default_wallet")?;
let wallet = Wallet::new( let wallet = Wallet::new(
Bip86(wallet_xprv, KeychainKind::External), Bip86(wallet_xprv, KeychainKind::External),
Some(Bip86(wallet_xprv, KeychainKind::Internal)), Some(Bip86(wallet_xprv, KeychainKind::Internal)),
@ -88,17 +88,21 @@ pub async fn init_coordinator_wallet() -> Result<CoordinatorWallet<MemoryDatabas
} }
impl<D: bdk::database::BatchDatabase> CoordinatorWallet<D> { impl<D: bdk::database::BatchDatabase> CoordinatorWallet<D> {
/// shutdown function to end the mempool task
pub async fn shutdown(&self) { pub async fn shutdown(&self) {
debug!("Shutting down wallet"); debug!("Shutting down wallet");
self.mempool.shutdown().await; self.mempool.shutdown().await;
} }
/// get a new address of the coordinator wallet
pub async fn get_new_address(&self) -> Result<String> { pub async fn get_new_address(&self) -> Result<String> {
let wallet = self.wallet.lock().await; let wallet = self.wallet.lock().await;
let address = wallet.get_address(bdk::wallet::AddressIndex::New)?; let address = wallet.get_address(bdk::wallet::AddressIndex::New)?;
Ok(address.address.to_string()) Ok(address.address.to_string())
} }
/// used to validate submitted bond transactions using the same logic as in the continoous monitoring
/// puts the bond in a dummy MonitoringBond struct use the existing logic
pub async fn validate_bond_tx_hex( pub async fn validate_bond_tx_hex(
&self, &self,
bond_tx_hex: &str, bond_tx_hex: &str,
@ -263,6 +267,7 @@ fn test_mempool_accept_bonds(
) -> Result<HashMap<Vec<u8>, (MonitoringBond, anyhow::Error)>> { ) -> Result<HashMap<Vec<u8>, (MonitoringBond, anyhow::Error)>> {
let mut invalid_bonds: HashMap<Vec<u8>, (MonitoringBond, anyhow::Error)> = HashMap::new(); let mut invalid_bonds: HashMap<Vec<u8>, (MonitoringBond, anyhow::Error)> = HashMap::new();
// split bonds into chunks of 25 to avoid hitting the maxmimum allowed size of the rpc call
let raw_bonds: Vec<Vec<String>> = bonds let raw_bonds: Vec<Vec<String>> = bonds
.iter() .iter()
.map(|bond| bond.bond_tx_hex.clone().raw_hex()) .map(|bond| bond.bond_tx_hex.clone().raw_hex())

View File

@ -5,6 +5,7 @@ pub struct PsbtInput {
pub utxo: bdk::bitcoin::OutPoint, pub utxo: bdk::bitcoin::OutPoint,
} }
/// implements functions required for bond transactions on the bdk::bitcoin::Transaction struct
pub trait BondTx { pub trait BondTx {
fn input_sum<D: Database, B: GetTx>(&self, blockchain: &B, db: &D) -> Result<u64>; fn input_sum<D: Database, B: GetTx>(&self, blockchain: &B, db: &D) -> Result<u64>;
fn bond_output_sum(&self, bond_address: &str) -> Result<u64>; fn bond_output_sum(&self, bond_address: &str) -> Result<u64>;
@ -57,6 +58,7 @@ impl BondTx for Transaction {
} }
} }
/// converts a csv string of bincode binary serialized, hex encoded bdk psbt inputs to a vector of PsbtInput
pub fn csv_hex_to_bdk_input(inputs_csv_hex: &str) -> Result<Vec<PsbtInput>> { pub fn csv_hex_to_bdk_input(inputs_csv_hex: &str) -> Result<Vec<PsbtInput>> {
let mut inputs: Vec<PsbtInput> = Vec::new(); let mut inputs: Vec<PsbtInput> = Vec::new();
for input_hex in inputs_csv_hex.split(',') { for input_hex in inputs_csv_hex.split(',') {