Skip to content

Commit

Permalink
fix: re-add subscribe_deposit function
Browse files Browse the repository at this point in the history
  • Loading branch information
elsirion committed Aug 25, 2024
1 parent ceeec00 commit 2002349
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 9 deletions.
1 change: 1 addition & 0 deletions modules/fedimint-wallet-client/src/client_db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 140,7 @@ impl_db_record!(
key = ClaimedPegInKey,
value = ClaimedPegInData,
db_prefix = DbKeyPrefix::ClaimedPegIn,
notify_on_modify = true,
);
impl_db_lookup!(key = ClaimedPegInKey, query_prefix = ClaimedPegInPrefix);

Expand Down
100 changes: 96 additions & 4 deletions modules/fedimint-wallet-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 53,8 @@ use fedimint_core::module::{
ApiVersion, CommonModuleInit, ModuleCommon, ModuleInit, MultiApiVersion,
};
use fedimint_core::task::{MaybeSend, MaybeSync, TaskGroup};
use fedimint_core::util::backoff_util;
use fedimint_core::util::backoff_util::background_backoff;
use fedimint_core::util::{backoff_util, retry};
use fedimint_core::{
apply, async_trait_maybe_send, push_db_pair_items, runtime, Amount, OutPoint, TransactionId,
};
Expand Down Expand Up @@ -92,9 93,21 @@ pub struct BitcoinTransactionData {
#[derive(Debug, Clone, Serialize, Deserialize, Eq, PartialEq)]
pub enum DepositState {
WaitingForTransaction,
WaitingForConfirmation(BitcoinTransactionData),
Confirmed(BitcoinTransactionData),
Claimed(BitcoinTransactionData),
WaitingForConfirmation {
// Caution: previously this variant contained `BitcoinTransactionData`, it should not have
// been persisted by the fedimint client though
btc_out_point: bitcoin::OutPoint,
},
Confirmed {
// Caution: previously this variant contained `BitcoinTransactionData`, it should not have
// been persisted by the fedimint client though
},
Claimed {
// Caution: previously this variant contained `BitcoinTransactionData`, which may still be
// present in client DBs. If you want to re-use the btc_transaction and out_idx key make
// sure to remove old data cached in the operation log first.
btc_out_point: bitcoin::OutPoint,
},
Failed(String),
}

Expand Down Expand Up @@ -656,6 669,85 @@ impl WalletClientModule {
Ok((operation_id, address, tweak_idx))
}

/// Returns a stream of updates about an ongoing deposit operation created
/// with [`allocate_deposit_address_expert_only`]. Returns an error for old
/// deposit operations created prior to the 0.4 release and not driven to
/// completion yet. This should be rare enough that an indeterminate state
/// is ok here.
pub async fn subscribe_deposit(
&self,
operation_id: OperationId,
) -> anyhow::Result<UpdateStreamOrOutcome<DepositState>> {
let operation = self
.client_ctx
.get_operation(operation_id)
.await
.with_context(|| anyhow!("Operation not found: {}", operation_id.fmt_short()))?;

if operation.operation_module_kind() != WalletCommonInit::KIND.as_str() {
bail!("Operation is not a wallet operation");
}

let operation_meta = operation.meta::<WalletOperationMeta>();

let WalletOperationMetaVariant::Deposit {
address, tweak_idx, ..
} = operation_meta.variant
else {
bail!("Operation is not a deposit operation");
};

let Some(tweak_idx) = tweak_idx else {
bail!("Old pending deposit, can't subscribe to updates");
};

Ok(operation.outcome_or_updates(&self.client_ctx.global_db(), operation_id, || {
let stream_rpc = self.rpc.clone();
let stream_cient_ctx = self.client_ctx.clone();
let stream_script_pub_key = address.assume_checked().script_pubkey();

stream! {
yield DepositState::WaitingForTransaction;

retry(
"subscribe script history",
background_backoff(),
|| stream_rpc.watch_script_history(&stream_script_pub_key)
).await.expect("Will never give up");
let btc_out_point = retry(
"fetch history",
background_backoff(),
|| async {
let history = stream_rpc.get_script_history(&stream_script_pub_key).await?;
history.first().and_then(|tx| {
let out_idx = tx.output
.iter()
.enumerate()
.find_map(|(idx, output)| (output.script_pubkey == stream_script_pub_key).then_some(idx))?;
let txid = tx.txid();

Some(bitcoin::OutPoint {
txid,
vout: out_idx as u32,
})
}).context("No deposit transaction found")
}
).await.expect("Will never give up");

yield DepositState::WaitingForConfirmation { btc_out_point };

stream_cient_ctx.module_db().wait_key_exists(&ClaimedPegInKey {
peg_in_index: tweak_idx,
btc_out_point,
}).await;

yield DepositState::Claimed {
btc_out_point,
};
}
}))
}

pub async fn find_tweak_idx_by_operation_id(
&self,
operation_id: OperationId,
Expand Down
32 changes: 27 additions & 5 deletions modules/fedimint-wallet-tests/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 19,7 @@ use fedimint_dummy_server::DummyInit;
use fedimint_testing::btc::BitcoinTest;
use fedimint_testing::fixtures::Fixtures;
use fedimint_wallet_client::api::WalletFederationApi;
use fedimint_wallet_client::{WalletClientInit, WalletClientModule, WithdrawState};
use fedimint_wallet_client::{DepositState, WalletClientInit, WalletClientModule, WithdrawState};
use fedimint_wallet_common::config::{WalletConfig, WalletGenParams};
use fedimint_wallet_common::tweakable::Tweakable;
use fedimint_wallet_common::txoproof::PegInProof;
Expand Down Expand Up @@ -147,6 147,7 @@ async fn on_chain_peg_in_and_peg_out_happy_case() -> anyhow::Result<()> {
let fixtures = fixtures();
let fed = fixtures.new_default_fed().await;
let client = fed.new_client().await;
let wallet_module = client.get_first_module::<WalletClientModule>();
let bitcoin = fixtures.bitcoin();
let bitcoin = bitcoin.lock_exclusive().await;
info!("Starting test on_chain_peg_in_and_peg_out_happy_case");
Expand All @@ -157,16 158,19 @@ async fn on_chain_peg_in_and_peg_out_happy_case() -> anyhow::Result<()> {

let mut balance_sub = initial_peg_in(&client, bitcoin.as_ref(), finality_delay).await?;

// Test operation is created
let operations = client.operation_log().list_operations(10, None).await;
assert_eq!(operations.len(), 1, "Expecting only the peg-in operation");
let deposit_operation_id = operations[0].0.operation_id;
let deposit_operation = &operations[0].1;

assert_eq!(
operations[0].1.operation_module_kind(),
deposit_operation.operation_module_kind(),
"wallet",
"Peg-in operation should ke of kind wallet"
);
assert!(
operations[0]
.1
deposit_operation
.meta::<serde_json::Value>()
.get("variant")
.and_then(|v| v.get("deposit"))
Expand All @@ -175,11 179,29 @@ async fn on_chain_peg_in_and_peg_out_happy_case() -> anyhow::Result<()> {
"Peg-in operation meta data should contain address"
);

// Test update stream returns expected updates
let mut deposit_updates = wallet_module
.subscribe_deposit(deposit_operation_id)
.await?
.into_stream();
assert_eq!(
deposit_updates.next().await.unwrap(),
DepositState::WaitingForTransaction
);
assert!(matches!(
deposit_updates.next().await.unwrap(),
DepositState::WaitingForConfirmation { .. }
));
assert!(matches!(
deposit_updates.next().await.unwrap(),
DepositState::Claimed { .. }
));
assert_eq!(deposit_updates.next().await, None);

info!("Peg-in finished for test on_chain_peg_in_and_peg_out_happy_case");
// Peg-out test, requires block to recognize change UTXOs
let address = checked_address_to_unchecked_address(&bitcoin.get_new_address().await);
let peg_out = bsats(PEG_OUT_AMOUNT_SATS);
let wallet_module = client.get_first_module::<WalletClientModule>();
let fees = wallet_module
.get_withdraw_fees(address.clone(), peg_out)
.await?;
Expand Down

0 comments on commit 2002349

Please sign in to comment.