Skip to content

Commit

Permalink
refactor: allow multiple inputs in psbt
Browse files Browse the repository at this point in the history
  • Loading branch information
crisdut committed Jul 13, 2023
1 parent bdca28f commit 0ec6de4
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 141 deletions.
52 changes: 24 additions & 28 deletions src/rgb.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::str::FromStr;

use ::psbt::serialize::Serialize;
use amplify::{confinement::U16, hex::ToHex};
use anyhow::{anyhow, Result};
Expand All @@ -10,7 +12,6 @@ use rgbstd::{
contract::ContractId,
persistence::{Stash, Stock},
};
use std::{collections::BTreeMap, str::FromStr};
use strict_encoding::StrictSerialize;

pub mod accept;
Expand Down Expand Up @@ -48,10 +49,10 @@ use crate::{
AcceptRequest, AcceptResponse, AssetType, ContractMetadata, ContractResponse,
ContractsResponse, ImportRequest, InterfaceDetail, InterfacesResponse, InvoiceRequest,
InvoiceResponse, IssueMetaRequest, IssueMetadata, IssueRequest, IssueResponse,
NewCollectible, NextAddressResponse, NextUtxoResponse, NextUtxosResponse, PsbtRequest,
PsbtResponse, ReIssueRequest, ReIssueResponse, RgbTransferRequest, RgbTransferResponse,
SchemaDetail, SchemasResponse, UDADetail, WatcherDetailResponse, WatcherRequest,
WatcherResponse, WatcherUtxoResponse,
NewCollectible, NextAddressResponse, NextUtxoResponse, NextUtxosResponse, PsbtFeeRequest,
PsbtRequest, PsbtResponse, ReIssueRequest, ReIssueResponse, RgbTransferRequest,
RgbTransferResponse, SchemaDetail, SchemasResponse, UDADetail, WatcherDetailResponse,
WatcherRequest, WatcherResponse, WatcherUtxoResponse,
},
};

Expand All @@ -65,7 +66,7 @@ use self::{
prefetch_resolver_utxo_status, prefetch_resolver_utxos, prefetch_resolver_waddress,
prefetch_resolver_wutxo,
},
psbt::{estimate_fee_tx, save_commit},
psbt::{fee_estimate, save_commit},
wallet::{
create_wallet, next_address, next_utxo, next_utxos, register_address, register_utxo,
sync_wallet,
Expand Down Expand Up @@ -336,53 +337,48 @@ pub async fn create_invoice(sk: &str, request: InvoiceRequest) -> Result<Invoice

pub async fn create_psbt(sk: &str, request: PsbtRequest) -> Result<PsbtResponse> {
let PsbtRequest {
descriptor_pub,
inputs,
asset_inputs,
asset_descriptor_change,
asset_terminal_change,
bitcoin_inputs,
bitcoin_changes,
change_index,
fee,
} = request;

let stock = retrieve_stock(sk, ASSETS_STOCK).await?;
let rgb_account = retrieve_wallets(sk, ASSETS_WALLETS).await?;

let mut all_inputs = asset_inputs.clone();
all_inputs.extend(bitcoin_inputs.clone());

// Prefetch
let mut resolver = ExplorerResolver {
explorer_url: BITCOIN_EXPLORER_API.read().await.to_string(),
..Default::default()
};
for asset_utxo in inputs.clone() {
prefetch_resolver_psbt(&asset_utxo.asset_utxo, &mut resolver).await;
for input_utxo in all_inputs.clone() {
prefetch_resolver_psbt(&input_utxo.utxo, &mut resolver).await;
}

let bitcoin_inputs: BTreeMap<String, String> = inputs
.clone()
.into_iter()
.map(|input| (input.asset_utxo, input.asset_utxo_terminal))
.collect();
// Retrieve transaction fee
let fee = match fee {
Some(fee) => fee,
_ => estimate_fee_tx(
&descriptor_pub,
PsbtFeeRequest::Value(fee) => fee,
PsbtFeeRequest::FeeRate(fee_rate) => fee_estimate(
asset_inputs,
asset_descriptor_change,
asset_terminal_change.clone(),
bitcoin_inputs,
bitcoin_changes.clone(),
change_index,
fee_rate,
&mut resolver,
),
};

let asset_inputs: Vec<(String, String, Option<String>)> = inputs
.into_iter()
.map(|input| (input.asset_utxo, input.asset_utxo_terminal, input.tapret))
.collect();

let wallet = rgb_account.wallets.get("default");
let (psbt_file, change_terminal) = create_rgb_psbt(
descriptor_pub.0.to_string(),
asset_inputs,
change_index,
all_inputs,
bitcoin_changes,
asset_terminal_change,
fee,
wallet.cloned(),
&resolver,
Expand Down
6 changes: 3 additions & 3 deletions src/rgb/prefetch.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ pub async fn prefetch_resolver_import_rgb(
}

#[cfg(not(target_arch = "wasm32"))]
pub async fn prefetch_resolver_psbt(asset_utxo: &str, explorer: &mut ExplorerResolver) {}
pub async fn prefetch_resolver_psbt(input_utxo: &str, explorer: &mut ExplorerResolver) {}

#[cfg(not(target_arch = "wasm32"))]
pub async fn prefetch_resolver_utxo_status(
Expand Down Expand Up @@ -233,11 +233,11 @@ pub async fn prefetch_resolver_import_rgb(
}

#[cfg(target_arch = "wasm32")]
pub async fn prefetch_resolver_psbt(asset_utxo: &str, explorer: &mut ExplorerResolver) {
pub async fn prefetch_resolver_psbt(input_utxo: &str, explorer: &mut ExplorerResolver) {
let esplora_client: EsploraBlockchain =
EsploraBlockchain::new(&explorer.explorer_url, 100).with_concurrency(6);

let outpoint: OutPoint = asset_utxo.parse().expect("invalid outpoint format");
let outpoint: OutPoint = input_utxo.parse().expect("invalid outpoint format");
let txid = outpoint.txid;
if let Some(tx) = esplora_client
.get_tx(&txid)
Expand Down
102 changes: 38 additions & 64 deletions src/rgb/psbt.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
use std::collections::BTreeMap;
use std::str::FromStr;

use amplify::hex::ToHex;
Expand Down Expand Up @@ -32,39 +31,35 @@ use wallet::{

use crate::{
rgb::{constants::RGB_PSBT_TAPRET, structs::AddressAmount},
structs::SecretString,
structs::{PsbtInputRequest, SecretString},
};

#[allow(clippy::too_many_arguments)]
pub fn create_psbt(
descriptor_pub: String,
asset_utxos: Vec<(String, String, Option<String>)>,
bitcoin_change_index: Option<u16>,
bitcoin_changes: Vec<String>,
psbt_inputs: Vec<PsbtInputRequest>,
psbt_outputs: Vec<String>,
asset_terminal_change: String,
bitcoin_fee: u64,
wallet: Option<RgbWallet>,
tx_resolver: &impl ResolveTx,
) -> Result<(Psbt, String), ProprietaryKeyError> {
let mut inputs = vec![];

// Define Descriptor
let (_, terminal, _) = asset_utxos.first().unwrap();
let terminal: DerivationSubpath<UnhardenedIndex> =
terminal.parse().expect("invalid terminal path parse");

let contract_index = terminal.first().expect("first derivation index");
let terminal_step = format!("/{contract_index}/*");

let descriptor_pub = descriptor_pub.replace(&terminal_step, "/*/*");
let descriptor_pub = psbt_inputs[0]
.descriptor
.0
.replace(&psbt_inputs[0].utxo_terminal, "/*/*");
let descriptor: &Descriptor<DerivationAccount> =
&Descriptor::from_str(&descriptor_pub).expect("invalid descriptor parse");

// Define Input Descriptors
for (asset_utxo, asset_utxo_terminal, tapret) in asset_utxos {
let outpoint: OutPoint = asset_utxo.parse().expect("invalid outpoint parse");
for psbt_input in psbt_inputs {
let outpoint: OutPoint = psbt_input.utxo.parse().expect("invalid outpoint parse");
let mut input = InputDescriptor {
outpoint,
terminal: asset_utxo_terminal
terminal: psbt_input
.utxo_terminal
.parse()
.expect("invalid terminal path parse"),
seq_no: SeqNo::default(),
Expand All @@ -73,7 +68,7 @@ pub fn create_psbt(
};

// Verify TapTweak (User Input or Watcher inspect)
if let Some(tapret) = tapret {
if let Some(tapret) = psbt_input.tapret {
input.tweak = Some((
Fingerprint::default(),
tapret.parse().expect("invalid hash"),
Expand All @@ -92,7 +87,7 @@ pub fn create_psbt(
inputs.push(input);
}

let bitcoin_addresses: Vec<AddressAmount> = bitcoin_changes
let bitcoin_addresses: Vec<AddressAmount> = psbt_outputs
.into_iter()
.map(|btc| AddressAmount::from_str(btc.as_str()).expect("invalid AddressFormat parse"))
.collect();
Expand All @@ -114,21 +109,14 @@ pub fn create_psbt(
}];

// Change Terminal Derivation
let mut change_derivation = vec![terminal[0]];
let change_index = match bitcoin_change_index {
Some(index) => {
UnhardenedIndex::from_str(&index.to_string()).expect("invalid change_index parse")
}
_ => UnhardenedIndex::default(),
};
change_derivation.insert(1, change_index);
let change_terminal = format!("/{contract_index}/{change_index}");

let change_derivation: DerivationSubpath<UnhardenedIndex> = asset_terminal_change
.parse()
.expect("invalid terminal change");
let mut psbt = Psbt::construct(
descriptor,
&inputs,
&outputs,
change_derivation,
change_derivation.to_vec(),
bitcoin_fee,
tx_resolver,
)
Expand Down Expand Up @@ -166,7 +154,7 @@ pub fn create_psbt(
}
}

Ok((psbt, change_terminal))
Ok((psbt, asset_terminal_change))
}

fn complete_input_desc(
Expand Down Expand Up @@ -298,29 +286,31 @@ pub fn save_commit(terminal: &str, commit: Vec<u8>, wallet: &mut RgbWallet) {
}

// TODO: [Experimental] Review with Diba Team
pub fn estimate_fee_tx<T>(
descriptor_pub: &SecretString,
bitcoin_inputs: BTreeMap<String, String>,
pub fn fee_estimate<T>(
assets_inputs: Vec<PsbtInputRequest>,
asset_descriptor_change: SecretString,
asset_terminal_change: String,
bitcoin_inputs: Vec<PsbtInputRequest>,
bitcoin_changes: Vec<String>,
bitcoin_change_index: Option<u16>,
fee_rate: f32,
resolver: &mut T,
) -> u64
where
T: ResolveTx + Resolver,
{
let mut vout_value = 0;
let mut bitcoin_terminal = String::new();
let fee_rate = FeeRate::from_sat_per_vb(fee_rate);

for (utxo, terminal) in bitcoin_inputs {
let outpoint = OutPoint::from_str(&utxo).expect("invalid outpoint");
// Total Stats
let mut all_inputs = assets_inputs;
all_inputs.extend(bitcoin_inputs);

for item in all_inputs {
let outpoint = OutPoint::from_str(&item.utxo).expect("invalid outpoint");
if let Ok(tx) = resolver.resolve_tx(outpoint.txid) {
if let Some(vout) = tx.output.to_vec().get(outpoint.vout as usize) {
vout_value = vout.value;
}

if bitcoin_terminal.is_empty() {
bitcoin_terminal = terminal;
}
}
}

Expand All @@ -332,19 +322,11 @@ where
total_spent += recipient.amount;
}

let bitcoin_change_index = match bitcoin_change_index {
Some(index) => index.into(),
_ => UnhardenedIndex::default(),
};

// Main Recipient
total_spent = vout_value - total_spent;
let target_script =
get_recipient_script(descriptor_pub, &bitcoin_terminal, bitcoin_change_index)
.expect("invalid derivation");
let target_script = get_recipient_script(&asset_descriptor_change, &asset_terminal_change)
.expect("invalid derivation");

// TODO: Provide way to get fee rate estimate
let fee_rate = FeeRate::from_sat_per_vb(5.0);
let excess = decide_change(total_spent, fee_rate, &target_script);
match excess {
Excess::Change { amount: _, fee } => fee,
Expand All @@ -356,29 +338,21 @@ where
}
}

fn get_recipient_script(
descriptor_pub: &SecretString,
bitcoin_terminal: &str,
change_index: UnhardenedIndex,
) -> Option<Script> {
let bitcoin_terminal: DerivationSubpath<UnhardenedIndex> = bitcoin_terminal
fn get_recipient_script(descriptor_pub: &SecretString, bitcoin_terminal: &str) -> Option<Script> {
let terminal_step: DerivationSubpath<UnhardenedIndex> = bitcoin_terminal
.parse()
.expect("invalid terminal path parse");

let contract_index = bitcoin_terminal.first().expect("first derivation index");
let terminal_step = format!("/{contract_index}/*");

let descriptor_pub = descriptor_pub.0.replace(&terminal_step, "/*/*");
let descriptor_pub = descriptor_pub.0.replace(bitcoin_terminal, "/*/*");
let descriptor: &Descriptor<DerivationAccount> =
&Descriptor::from_str(&descriptor_pub).expect("invalid descriptor parse");

let change_derivation = [*contract_index, change_index];
match descriptor {
Descriptor::Tr(_) => {
let change_descriptor = DeriveDescriptor::<XOnlyPublicKey>::derive_descriptor(
descriptor,
SECP256K1,
change_derivation,
terminal_step,
)
.expect("Derivation mismatch");
let change_descriptor = match change_descriptor {
Expand Down
Loading

0 comments on commit 0ec6de4

Please sign in to comment.