Skip to content

Commit 157c15d

Browse files
authored
Merge pull request #142 from ChrisSon15/connect-wallet-protocol
move MemWallet to crate wallet and let it implement TradeWallet and ProtocolWalletApi traits
2 parents e9cd079 + aafcf7d commit 157c15d

17 files changed

Lines changed: 336 additions & 187 deletions

Cargo.lock

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

protocol/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ rand_chacha = { workspace = true }
1616
testenv = { path = "../testenv" }
1717
thiserror = { workspace = true }
1818
tracing = { workspace = true }
19+
wallet = { path = "../wallet" }
1920

2021
[dev-dependencies]
2122
bdk_wallet = { workspace = true, features = ["test-utils"] }

protocol/src/protocol_musig_adaptor.rs

Lines changed: 9 additions & 139 deletions
Original file line numberDiff line numberDiff line change
@@ -1,62 +1,20 @@
1-
use std::io::Write as _;
21
use std::str::FromStr as _;
32

4-
use bdk_electrum::BdkElectrumClient;
5-
use bdk_electrum::electrum_client::Client;
6-
use bdk_wallet::bitcoin::bip32::Xpriv;
73
use bdk_wallet::bitcoin::{
84
Address, Amount, FeeRate, Network, OutPoint, Psbt, Script, ScriptBuf, Transaction, TxOut, Txid,
95
relative,
106
};
11-
use bdk_wallet::template::{Bip86, DescriptorTemplate as _};
12-
use bdk_wallet::{AddressInfo, KeychainKind, SignOptions, Wallet};
137
use musig2::secp::{MaybeScalar, Point};
148
use musig2::{PartialSignature, PubNonce};
15-
use rand::RngCore as _;
16-
use testenv::TestEnv;
179

10+
use wallet::protocol_wallet_api::MemWallet;
1811
use crate::multisig::{KeyCtx, SigCtx};
1912
use crate::receiver::{Receiver, ReceiverList};
2013
use crate::transaction::{
2114
DepositTxBuilder, ForwardingTxBuilder, RedirectTxBuilder, WarningTxBuilder, WithWitnesses as _,
2215
};
2316
use crate::wallet_service::WalletService;
2417

25-
pub struct MemWallet {
26-
wallet: Wallet,
27-
client: BdkElectrumClient<Client>,
28-
}
29-
30-
impl MemWallet {
31-
pub fn transaction_broadcast(&self, tx: &Transaction) -> anyhow::Result<Txid> {
32-
let result = self.client.transaction_broadcast(tx);
33-
34-
if let Err(e) = result {
35-
if e.to_string().contains("Transaction already in block chain") {
36-
return Ok(tx.compute_txid());
37-
}
38-
return Err(e.into());
39-
}
40-
41-
Ok(result?)
42-
}
43-
44-
pub fn funded_wallet(env: &TestEnv) -> Self {
45-
// TODO move this line to TestEnv
46-
let client =
47-
BdkElectrumClient::new(Client::new(&env.electrum_url()).unwrap());
48-
let mut wallet = Self::new(client).unwrap();
49-
let address = wallet.next_unused_address();
50-
let txid = env
51-
.fund_address(&address, Amount::from_btc(10f64).unwrap())
52-
.unwrap();
53-
env.mine_block().unwrap();
54-
env.wait_for_tx(txid).unwrap();
55-
wallet.sync().unwrap();
56-
wallet
57-
}
58-
}
59-
6018
#[derive(PartialEq, Eq, Clone, Copy, Debug)]
6119
#[expect(clippy::exhaustive_enums)]
6220
pub enum ProtocolRole {
@@ -73,94 +31,6 @@ impl ProtocolRole {
7331
}
7432
}
7533

76-
// TODO think about stop_gap and batch_size
77-
const STOP_GAP: usize = 50;
78-
const BATCH_SIZE: usize = 5;
79-
80-
impl MemWallet {
81-
pub fn new(client: BdkElectrumClient<Client>) -> anyhow::Result<Self> {
82-
let mut seed: [u8; 32] = [0u8; 32];
83-
rand::rng().fill_bytes(&mut seed);
84-
85-
let network: Network = Network::Regtest;
86-
let xprv: Xpriv = Xpriv::new_master(network, &seed)?;
87-
tracing::info!(
88-
"Generated Master Private Key:\n{xprv}\nWarning: be very careful with private \
89-
keys when using MainNet! We are logging these values for convenience only because this \
90-
is an example on RegTest.\n"
91-
);
92-
93-
let (descriptor, external_map, _) = Bip86(xprv, KeychainKind::External)
94-
.build(network)
95-
.expect("Failed to build external descriptor");
96-
97-
let (change_descriptor, internal_map, _) = Bip86(xprv, KeychainKind::Internal)
98-
.build(network)
99-
.expect("Failed to build internal descriptor");
100-
101-
let wallet = Wallet::create(descriptor, change_descriptor)
102-
.network(network)
103-
.keymap(KeychainKind::External, external_map)
104-
.keymap(KeychainKind::Internal, internal_map)
105-
.create_wallet_no_persist()?;
106-
107-
Ok(Self { wallet, client })
108-
}
109-
110-
pub fn sync(&mut self) -> anyhow::Result<()> {
111-
// Populate the electrum client's transaction cache so it doesn't re-download transaction we
112-
// already have.
113-
self.client
114-
.populate_tx_cache(self.wallet.tx_graph().full_txs().map(|tx_node| tx_node.tx));
115-
116-
let request = self.wallet.start_full_scan().inspect({
117-
let mut stdout = std::io::stdout();
118-
// let mut once = HashSet::<KeychainKind>::new();
119-
move |_k, _spk_i, _| {
120-
// if once.insert(k) {
121-
// print!("\nScanning keychain [{:?}]", k);
122-
// }
123-
// print!(" {:<3}", spk_i);
124-
stdout.flush().expect("must flush");
125-
}
126-
});
127-
tracing::info!("requesting update...");
128-
let update = self
129-
.client
130-
.full_scan(request, STOP_GAP, BATCH_SIZE, false)?;
131-
self.wallet.apply_update(update)?;
132-
Ok(())
133-
}
134-
135-
pub fn balance(&self) -> Amount {
136-
self.wallet.balance().trusted_spendable()
137-
}
138-
139-
pub fn next_unused_address(&mut self) -> AddressInfo {
140-
// FIXME: `next_unused_address` just returns the same unused address over and over. It has
141-
// to either be marked as used (which change isn't staged and therefore presumably never
142-
// persisted) or a fresh address requested with `reveal_next_address`.
143-
self.wallet.next_unused_address(KeychainKind::External)
144-
}
145-
146-
fn _transfer_to_address(
147-
&mut self,
148-
address: &AddressInfo,
149-
amount: Amount,
150-
) -> anyhow::Result<Txid> {
151-
let mut tx_builder = self.wallet.build_tx();
152-
tx_builder.add_recipient(address.script_pubkey(), amount);
153-
154-
let mut psbt = tx_builder.finish()?;
155-
let finalized = self.wallet.sign(&mut psbt, SignOptions::default())?;
156-
assert!(finalized);
157-
158-
let tx = psbt.extract_tx()?;
159-
self.client.transaction_broadcast(&tx)?;
160-
Ok(tx.compute_txid())
161-
}
162-
}
163-
16434
#[derive(Debug)]
16535
pub struct Round1Parameter {
16636
// DepositTx --------
@@ -497,7 +367,7 @@ impl RedirectTx {
497367
}
498368

499369
pub fn broadcast(&self, me: &BMPContext) -> anyhow::Result<Txid> {
500-
Ok(me.funds.client.transaction_broadcast(self.builder.signed_tx()?)?)
370+
me.funds.transaction_broadcast(self.builder.signed_tx()?)
501371
}
502372

503373
/// sum of all f64 must be 1
@@ -559,7 +429,7 @@ impl ClaimTx {
559429
}
560430

561431
pub fn broadcast(&self, me: &BMPContext) -> anyhow::Result<Txid> {
562-
Ok(me.funds.client.transaction_broadcast(self.signed_tx()?)?)
432+
me.funds.transaction_broadcast(self.signed_tx()?)
563433
}
564434
}
565435

@@ -651,7 +521,7 @@ impl WarningTx {
651521
}
652522

653523
pub fn broadcast(&self, me: &BMPContext) -> anyhow::Result<Txid> {
654-
Ok(me.funds.client.transaction_broadcast(self.signed_tx()?)?)
524+
me.funds.transaction_broadcast(self.signed_tx()?)
655525
}
656526
}
657527

@@ -759,7 +629,7 @@ impl SwapTx {
759629
}
760630

761631
pub fn broadcast(&self, me: &BMPContext) -> anyhow::Result<Txid> {
762-
Ok(me.funds.client.transaction_broadcast(self.builder.signed_tx()?)?)
632+
me.funds.transaction_broadcast(self.builder.signed_tx()?)
763633
}
764634
}
765635

@@ -785,11 +655,11 @@ impl DepositTx {
785655

786656
let psbt = if ctx.am_buyer() {
787657
self.builder
788-
.init_buyers_half_psbt(&mut ctx.funds.wallet, &mut rand::rng())?
658+
.init_buyers_half_psbt(&mut ctx.funds, &mut rand::rng())?
789659
.buyers_half_psbt()?
790660
} else {
791661
self.builder
792-
.init_sellers_half_psbt(&mut ctx.funds.wallet, &mut rand::rng())?
662+
.init_sellers_half_psbt(&mut ctx.funds, &mut rand::rng())?
793663
.sellers_half_psbt()?
794664
};
795665
Ok(psbt.clone())
@@ -810,9 +680,9 @@ impl DepositTx {
810680

811681
fn sign(&mut self, ctx: &mut BMPContext) -> anyhow::Result<()> {
812682
if ctx.am_buyer() {
813-
self.builder.sign_buyer_inputs(&ctx.funds.wallet)?;
683+
self.builder.sign_buyer_inputs(&ctx.funds)?;
814684
} else {
815-
self.builder.sign_seller_inputs(&ctx.funds.wallet)?;
685+
self.builder.sign_seller_inputs(&ctx.funds)?;
816686
}
817687
Ok(())
818688
}

protocol/src/psbt.rs

Lines changed: 64 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ use bdk_wallet::miniscript::{Descriptor, ToPublicKey as _};
1414
use bdk_wallet::{KeychainKind, SignOptions, TxOrdering, Wallet};
1515
use rand::{RngCore, SeedableRng as _};
1616
use rand_chacha::ChaCha20Rng;
17+
use wallet::protocol_wallet_api::MemWallet;
1718

1819
use crate::receiver::Receiver;
1920
use crate::swap::Swap as _;
@@ -55,7 +56,9 @@ struct MockTradeWallet<Cs: Iterator<Item=TxOutput>, As: Iterator<Item=Address>>
5556
internal_key: Option<XOnlyPublicKey>,
5657
}
5758

58-
impl<Cs: Iterator<Item=TxOutput>, As: Iterator<Item=Address>> TradeWallet for MockTradeWallet<Cs, As> {
59+
impl<Cs: Iterator<Item = TxOutput>, As: Iterator<Item = Address>> TradeWallet
60+
for MockTradeWallet<Cs, As>
61+
{
5962
fn network(&self) -> Network { Network::Regtest }
6063

6164
fn new_address(&mut self) -> Result<Address> {
@@ -235,19 +238,19 @@ impl TradeWallet for Wallet {
235238
) -> Result<Psbt> {
236239
let mut builder = self.build_tx();
237240
builder
238-
.ordering(TxOrdering::Untouched)
239-
.nlocktime(absolute::LockTime::ZERO)
240-
.fee_rate(fee_rate)
241-
.add_recipient(half_deposit_placeholder_spk(rng), deposit_amount);
241+
.ordering(TxOrdering::Untouched)
242+
.nlocktime(absolute::LockTime::ZERO)
243+
.fee_rate(fee_rate)
244+
.add_recipient(half_deposit_placeholder_spk(rng), deposit_amount);
242245
for receiver in trade_fee_receivers {
243246
builder.add_recipient(receiver.address.script_pubkey(), receiver.amount);
244247
}
245248
let mut psbt = builder.finish()?;
246249
// Calculate tx fee overpay unconditionally, as this performs additional checks on the PSBT:
247250
let overpay_msat: u64 = half_psbt_fee_overpay_msat(&psbt, fee_rate)
248-
.ok_or(TransactionErrorKind::Overflow)?
249-
.try_into()
250-
.map_err(|_| TransactionErrorKind::InvalidPsbt)?;
251+
.ok_or(TransactionErrorKind::Overflow)?
252+
.try_into()
253+
.map_err(|_| TransactionErrorKind::InvalidPsbt)?;
251254
let change_output_index = 1 + trade_fee_receivers.len();
252255
if psbt.unsigned_tx.output.len() > change_output_index {
253256
// Correct any tx fee overpay due to overly conservative input witness size estimation
@@ -277,6 +280,59 @@ impl TradeWallet for Wallet {
277280
}
278281
}
279282

283+
impl TradeWallet for MemWallet {
284+
fn network(&self) -> Network { self.network() }
285+
286+
fn new_address(&mut self) -> Result<Address> {
287+
// For privacy, always get fresh addresses for the trade protocol.
288+
// FIXME: Need to find a way to prevent gaps of unused addresses from growing too large.
289+
Ok(self.reveal_next_address())
290+
}
291+
292+
fn new_internal_key(&mut self) -> Result<XOnlyPublicKey> {
293+
Self::new_internal_key(self).map_err(TransactionErrorKind::from)
294+
}
295+
296+
fn create_half_deposit_psbt(
297+
&mut self,
298+
deposit_amount: Amount,
299+
fee_rate: FeeRate,
300+
trade_fee_receivers: &[Receiver],
301+
rng: &mut dyn RngCore,
302+
) -> Result<Psbt> {
303+
let mut res: Vec<(ScriptBuf, Amount)> = trade_fee_receivers
304+
.iter()
305+
.map(|receiver| (receiver.address.script_pubkey(), deposit_amount))
306+
.collect();
307+
res.push((half_deposit_placeholder_spk(rng), deposit_amount));
308+
let mut psbt = self.create_psbt(res, fee_rate)?;
309+
// Calculate tx fee overpay unconditionally, as this performs additional checks on the PSBT:
310+
let overpay_msat: u64 = half_psbt_fee_overpay_msat(&psbt, fee_rate)
311+
.ok_or(TransactionErrorKind::Overflow)?
312+
.try_into()
313+
.map_err(|_| TransactionErrorKind::InvalidPsbt)?;
314+
let change_output_index = 1 + trade_fee_receivers.len();
315+
if psbt.unsigned_tx.output.len() > change_output_index {
316+
// Correct any tx fee overpay due to overly conservative input witness size estimation
317+
// by BDK (as each witness is assumed to potentially have a non-default sighash type in
318+
// the case of p2tr and not grind for low R in the case of p2wpkh), and also due to the
319+
// fact that each would-be-signed half-deposit PSBT is 1 wu bigger than ideal.
320+
let overpay = Amount::from_sat(overpay_msat / 1000);
321+
psbt.unsigned_tx.output[change_output_index].value += overpay;
322+
}
323+
psbt.redact_sensitive_fields();
324+
Ok(psbt)
325+
}
326+
327+
fn sign_selected_inputs(&self, psbt: &mut Psbt, is_selected: &dyn Fn(&OutPoint) -> bool) -> Result<()> {
328+
if !is_well_formed(psbt) {
329+
return Err(TransactionErrorKind::InvalidPsbt);
330+
}
331+
self.sign_the_selected_inputs(psbt, is_selected)?;
332+
Ok(())
333+
}
334+
}
335+
280336
trait Redact {
281337
fn redact_sensitive_fields(&mut self);
282338
}

protocol/src/transaction.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -568,6 +568,8 @@ pub enum TransactionErrorKind {
568568
CreateTx(#[from] bdk_wallet::error::CreateTxError),
569569
Signer(#[from] bdk_wallet::signer::SignerError),
570570
Conversion(#[from] bdk_wallet::miniscript::descriptor::ConversionError),
571+
Wallet(#[from] wallet::protocol_wallet_api::WalletErrorKind),
572+
Anyhow(#[from] anyhow::Error),
571573
}
572574

573575
impl From<ExtractTxError> for TransactionErrorKind {

protocol/src/wallet_service.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ This represents a service to get access to the wallet
33
as of now its a fake implementation, you need to pass in the wallet which you
44
want to retrieve later. In the real version this should discover and actually load the user's wallet.
55
*/
6-
use crate::protocol_musig_adaptor::MemWallet;
6+
use wallet::protocol_wallet_api::MemWallet;
77

88
#[derive(Default)]
99
pub struct WalletService {

0 commit comments

Comments
 (0)