mirror of
https://github.com/logos-blockchain/lssa.git
synced 2026-08-01 20:43:14 +00:00
615 lines
22 KiB
Rust
615 lines
22 KiB
Rust
#![expect(
|
|
clippy::tests_outside_test_module,
|
|
reason = "We don't care about these in tests"
|
|
)]
|
|
|
|
use std::time::Duration;
|
|
|
|
use anyhow::Context as _;
|
|
use common::transaction::LeeTransaction;
|
|
use integration_tests::{
|
|
TIME_TO_WAIT_FOR_BLOCK_SECONDS, TestContext, account_balance, get_account,
|
|
};
|
|
use lee::{
|
|
execute_and_prove, privacy_preserving_transaction, program::Program, public_transaction,
|
|
};
|
|
use lee_core::{InputAccountIdentity, account::AccountWithMetadata};
|
|
use sequencer_service_rpc::RpcClient as _;
|
|
use tokio::test;
|
|
|
|
// const TIME_TO_FINALIZE_DEPOSIT_EVENT_ON_BEDROCK: Duration = Duration::from_mins(2);
|
|
|
|
#[test]
|
|
async fn public_bridge_deposit_invocation_is_dropped() -> anyhow::Result<()> {
|
|
let ctx = TestContext::new().await?;
|
|
|
|
let recipient_id = ctx.existing_public_accounts()[0];
|
|
let bridge_account_id = system_accounts::bridge_account_id();
|
|
let vault_program_id = programs::vault().id();
|
|
let recipient_vault_id = vault_core::compute_vault_account_id(vault_program_id, recipient_id);
|
|
let receipt_id = bridge_core::deposit_receipt_account_id(programs::bridge().id(), [0_u8; 32]);
|
|
|
|
let message = public_transaction::Message::try_new(
|
|
programs::bridge().id(),
|
|
vec![bridge_account_id, recipient_vault_id, receipt_id],
|
|
vec![],
|
|
bridge_core::Instruction::Deposit {
|
|
l1_deposit_op_id: [0_u8; 32],
|
|
vault_program_id,
|
|
recipient_id,
|
|
amount: 1,
|
|
},
|
|
)
|
|
.context("Failed to build public bridge deposit transaction")?;
|
|
|
|
let attack_tx = LeeTransaction::Public(lee::PublicTransaction::new(
|
|
message,
|
|
lee::public_transaction::WitnessSet::from_raw_parts(vec![]),
|
|
));
|
|
|
|
let bridge_balance_before = account_balance(&ctx, bridge_account_id).await?;
|
|
let vault_balance_before = account_balance(&ctx, recipient_vault_id).await?;
|
|
|
|
let tx_hash = ctx.sequencer_client().send_transaction(attack_tx).await?;
|
|
|
|
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
|
|
|
let bridge_balance_after = account_balance(&ctx, bridge_account_id).await?;
|
|
let vault_balance_after = account_balance(&ctx, recipient_vault_id).await?;
|
|
let tx_on_chain = ctx.sequencer_client().get_transaction(tx_hash).await?;
|
|
|
|
assert_eq!(bridge_balance_after, bridge_balance_before);
|
|
assert_eq!(vault_balance_after, vault_balance_before);
|
|
assert!(
|
|
tx_on_chain.is_none(),
|
|
"Direct public bridge::Deposit invocation should be rejected"
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
async fn public_bridge_deposit_with_zero_amount_is_rejected() -> anyhow::Result<()> {
|
|
let ctx = TestContext::new().await?;
|
|
|
|
let recipient_id = ctx.existing_public_accounts()[0];
|
|
let bridge_account_id = system_accounts::bridge_account_id();
|
|
let vault_program_id = programs::vault().id();
|
|
let recipient_vault_id = vault_core::compute_vault_account_id(vault_program_id, recipient_id);
|
|
let receipt_id = bridge_core::deposit_receipt_account_id(programs::bridge().id(), [0_u8; 32]);
|
|
|
|
let message = public_transaction::Message::try_new(
|
|
programs::bridge().id(),
|
|
vec![bridge_account_id, recipient_vault_id, receipt_id],
|
|
vec![],
|
|
bridge_core::Instruction::Deposit {
|
|
l1_deposit_op_id: [0_u8; 32],
|
|
vault_program_id,
|
|
recipient_id,
|
|
amount: 0,
|
|
},
|
|
)
|
|
.context("Failed to build zero-amount public bridge deposit transaction")?;
|
|
|
|
let attack_tx = LeeTransaction::Public(lee::PublicTransaction::new(
|
|
message,
|
|
lee::public_transaction::WitnessSet::from_raw_parts(vec![]),
|
|
));
|
|
|
|
let bridge_balance_before = ctx
|
|
.sequencer_client()
|
|
.get_account_balance(bridge_account_id)
|
|
.await?;
|
|
let vault_balance_before = ctx
|
|
.sequencer_client()
|
|
.get_account_balance(recipient_vault_id)
|
|
.await?;
|
|
|
|
let tx_hash = ctx.sequencer_client().send_transaction(attack_tx).await?;
|
|
|
|
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
|
|
|
let bridge_balance_after = ctx
|
|
.sequencer_client()
|
|
.get_account_balance(bridge_account_id)
|
|
.await?;
|
|
let vault_balance_after = ctx
|
|
.sequencer_client()
|
|
.get_account_balance(recipient_vault_id)
|
|
.await?;
|
|
let tx_on_chain = ctx.sequencer_client().get_transaction(tx_hash).await?;
|
|
|
|
assert_eq!(bridge_balance_after, bridge_balance_before);
|
|
assert_eq!(vault_balance_after, vault_balance_before);
|
|
assert!(
|
|
tx_on_chain.is_none(),
|
|
"Public bridge::Deposit with zero amount should be rejected"
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
async fn private_bridge_deposit_invocation_is_dropped() -> anyhow::Result<()> {
|
|
let ctx = TestContext::new().await?;
|
|
|
|
let recipient_id = ctx.existing_public_accounts()[0];
|
|
let bridge_account_id = system_accounts::bridge_account_id();
|
|
let vault_program_id = programs::vault().id();
|
|
let recipient_vault_id = vault_core::compute_vault_account_id(vault_program_id, recipient_id);
|
|
let receipt_id = bridge_core::deposit_receipt_account_id(programs::bridge().id(), [0_u8; 32]);
|
|
|
|
// Get pre-state of bridge and vault accounts; the receipt is unminted (a
|
|
// default account), so the program would create it on a first mint.
|
|
let bridge_pre = AccountWithMetadata::new(
|
|
get_account(&ctx, bridge_account_id).await?,
|
|
false,
|
|
bridge_account_id,
|
|
);
|
|
let vault_pre = AccountWithMetadata::new(
|
|
get_account(&ctx, recipient_vault_id).await?,
|
|
false,
|
|
recipient_vault_id,
|
|
);
|
|
let receipt_pre =
|
|
AccountWithMetadata::new(lee_core::account::Account::default(), false, receipt_id);
|
|
|
|
// Create program with dependencies
|
|
let program_with_deps =
|
|
lee::privacy_preserving_transaction::circuit::ProgramWithDependencies::new(
|
|
programs::bridge(),
|
|
[
|
|
(vault_program_id, programs::vault()),
|
|
(
|
|
programs::authenticated_transfer().id(),
|
|
programs::authenticated_transfer(),
|
|
),
|
|
]
|
|
.into(),
|
|
);
|
|
|
|
// Serialize the bridge deposit instruction
|
|
let instruction = Program::serialize_instruction(bridge_core::Instruction::Deposit {
|
|
l1_deposit_op_id: [0_u8; 32],
|
|
vault_program_id,
|
|
recipient_id,
|
|
amount: 1,
|
|
})
|
|
.context("Failed to serialize bridge deposit instruction")?;
|
|
|
|
// Execute and prove the bridge deposit
|
|
let (output, proof) = execute_and_prove(
|
|
vec![bridge_pre.clone(), vault_pre.clone(), receipt_pre.clone()],
|
|
instruction,
|
|
vec![
|
|
InputAccountIdentity::Public,
|
|
InputAccountIdentity::Public,
|
|
InputAccountIdentity::Public,
|
|
],
|
|
&program_with_deps,
|
|
)
|
|
.context("Failed to execute/prove bridge deposit")?;
|
|
|
|
// Create privacy-preserving transaction from circuit output
|
|
let message = privacy_preserving_transaction::Message::try_from_circuit_output(
|
|
vec![bridge_account_id, recipient_vault_id, receipt_id],
|
|
vec![
|
|
bridge_pre.account.nonce,
|
|
vault_pre.account.nonce,
|
|
receipt_pre.account.nonce,
|
|
],
|
|
output,
|
|
)
|
|
.context("Failed to build privacy-preserving bridge deposit message")?;
|
|
|
|
let witness_set = privacy_preserving_transaction::WitnessSet::for_message(&message, proof, &[]);
|
|
let attack_tx = LeeTransaction::PrivacyPreserving(lee::PrivacyPreservingTransaction::new(
|
|
message,
|
|
witness_set,
|
|
));
|
|
|
|
let bridge_balance_before = account_balance(&ctx, bridge_account_id).await?;
|
|
let vault_balance_before = account_balance(&ctx, recipient_vault_id).await?;
|
|
|
|
let tx_hash = ctx.sequencer_client().send_transaction(attack_tx).await?;
|
|
|
|
tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
|
|
|
let bridge_balance_after = account_balance(&ctx, bridge_account_id).await?;
|
|
let vault_balance_after = account_balance(&ctx, recipient_vault_id).await?;
|
|
let tx_on_chain = ctx.sequencer_client().get_transaction(tx_hash).await?;
|
|
|
|
assert_eq!(bridge_balance_after, bridge_balance_before);
|
|
assert_eq!(vault_balance_after, vault_balance_before);
|
|
assert!(
|
|
tx_on_chain.is_none(),
|
|
"Privacy-preserving bridge::Deposit invocation should be rejected"
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
// async fn submit_bedrock_deposit(
|
|
// bedrock_addr: std::net::SocketAddr,
|
|
// bedrock_account_pk: &str,
|
|
// recipient_id: AccountId,
|
|
// amount: u64,
|
|
// ) -> anyhow::Result<()> {
|
|
// #[derive(BorshSerialize)]
|
|
// struct DepositMetadata {
|
|
// recipient_id: AccountId,
|
|
// }
|
|
|
|
// // Encode deposit metadata
|
|
// let metadata = borsh::to_vec(&DepositMetadata { recipient_id })
|
|
// .context("Failed to encode deposit metadata")?
|
|
// .try_into()
|
|
// .context("Encoded metadata is too big")?;
|
|
|
|
// let channel_id = integration_tests::config::bedrock_channel_id();
|
|
// let client = reqwest::Client::new();
|
|
|
|
// let mut balance = bedrock_wallet_balance(bedrock_addr, bedrock_account_pk).await?;
|
|
|
|
// info!(
|
|
// "Queried Bedrock balance for key {bedrock_account_pk}: {:?}",
|
|
// balance.balance
|
|
// );
|
|
|
|
// if balance.balance < amount {
|
|
// anyhow::bail!(
|
|
// "Bedrock wallet with key {bedrock_account_pk} has insufficient balance {:?} for
|
|
// deposit amount {:?}", balance.balance,
|
|
// amount
|
|
// );
|
|
// }
|
|
|
|
// let mut selected_note_id = balance
|
|
// .notes
|
|
// .iter()
|
|
// .find_map(|(note_id, value)| (*value == amount).then_some(*note_id));
|
|
|
|
// if selected_note_id.is_none() {
|
|
// let transfer_body = WalletTransferFundsRequestBody {
|
|
// tip: None,
|
|
// change_public_key: balance.address,
|
|
// funding_public_keys: vec![balance.address],
|
|
// recipient_public_key: balance.address,
|
|
// amount,
|
|
// };
|
|
|
|
// let transfer_response = client
|
|
// .post(format!(
|
|
// "http://{bedrock_addr}/wallet/transactions/transfer-funds"
|
|
// ))
|
|
// .json(&transfer_body)
|
|
// .send()
|
|
// .await
|
|
// .context("Failed to submit Bedrock transfer-funds request")?;
|
|
// let transfer_response = check_response_success(transfer_response).await?;
|
|
|
|
// let transfer: WalletTransferFundsResponseBody = transfer_response
|
|
// .json()
|
|
// .await
|
|
// .context("Failed to decode Bedrock transfer-funds response")?;
|
|
|
|
// info!(
|
|
// "Submitted transfer-funds to create exact deposit note, tx hash {:?}",
|
|
// transfer.hash
|
|
// );
|
|
|
|
// let mut found_note = None;
|
|
// for _ in 0..20 {
|
|
// tokio::time::sleep(Duration::from_millis(500)).await;
|
|
// balance = bedrock_wallet_balance(bedrock_addr, bedrock_account_pk).await?;
|
|
// found_note = balance
|
|
// .notes
|
|
// .iter()
|
|
// .find_map(|(note_id, value)| (*value == amount).then_some(*note_id));
|
|
// if found_note.is_some() {
|
|
// break;
|
|
// }
|
|
// }
|
|
|
|
// selected_note_id = found_note;
|
|
// }
|
|
|
|
// let Some(selected_note_id) = selected_note_id else {
|
|
// anyhow::bail!(
|
|
// "Failed to locate exact-value note {amount:?} for Bedrock deposit; available notes:
|
|
// {:?}", balance.notes,
|
|
// );
|
|
// };
|
|
|
|
// let body = ChannelDepositRequestBody {
|
|
// tip: None,
|
|
// deposit: DepositOp {
|
|
// channel_id,
|
|
// inputs: Inputs::new(selected_note_id),
|
|
// metadata,
|
|
// },
|
|
// change_public_key: balance.address,
|
|
// funding_public_keys: vec![balance.address],
|
|
// max_tx_fee: u64::MAX.into(),
|
|
// };
|
|
|
|
// let response = client
|
|
// .post(format!("http://{bedrock_addr}/channel/deposit"))
|
|
// .json(&body)
|
|
// .send()
|
|
// .await
|
|
// .context("Failed to submit Bedrock deposit request")?;
|
|
// let response = check_response_success(response).await?;
|
|
|
|
// let body_text = response
|
|
// .text()
|
|
// .await
|
|
// .unwrap_or_else(|_| "<failed to decode>".to_owned());
|
|
// info!(
|
|
// "Successfully submitted Bedrock deposit request for recipient {recipient_id} and amount
|
|
// {amount}, response body: {body_text}", );
|
|
|
|
// Ok(())
|
|
// }
|
|
|
|
// /// The Bedrock wallet state of `bedrock_account_pk`: its total balance and the
|
|
// /// notes it owns, keyed by note id.
|
|
// async fn bedrock_wallet_balance(
|
|
// bedrock_addr: std::net::SocketAddr,
|
|
// bedrock_account_pk: &str,
|
|
// ) -> anyhow::Result<WalletBalanceResponseBody> {
|
|
// let response = reqwest::Client::new()
|
|
// .get(format!(
|
|
// "http://{bedrock_addr}/wallet/{bedrock_account_pk}/balance"
|
|
// ))
|
|
// .send()
|
|
// .await
|
|
// .context("Failed to query Bedrock wallet balance")?;
|
|
|
|
// check_response_success(response)
|
|
// .await?
|
|
// .json::<WalletBalanceResponseBody>()
|
|
// .await
|
|
// .context("Failed to decode Bedrock balance response")
|
|
// }
|
|
|
|
// async fn check_response_success(response: reqwest::Response) -> anyhow::Result<reqwest::Response>
|
|
// { if response.status().is_success() {
|
|
// Ok(response)
|
|
// } else {
|
|
// let status = response.status();
|
|
// let body_text = response.text().await.unwrap_or_default();
|
|
// anyhow::bail!("Request failed with status {status} and body {body_text}");
|
|
// }
|
|
// }
|
|
|
|
// async fn wait_for_vault_balance(
|
|
// ctx: &TestContext,
|
|
// vault_id: AccountId,
|
|
// expected_balance: u128,
|
|
// ) -> anyhow::Result<()> {
|
|
// let timeout = TIME_TO_FINALIZE_DEPOSIT_EVENT_ON_BEDROCK
|
|
// + Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS);
|
|
// tokio::time::timeout(timeout, async {
|
|
// loop {
|
|
// let balance = account_balance(ctx, vault_id).await?;
|
|
// if balance == expected_balance {
|
|
// return Ok(());
|
|
// }
|
|
// tokio::time::sleep(Duration::from_millis(500)).await;
|
|
// }
|
|
// })
|
|
// .await
|
|
// .with_context(|| {
|
|
// format!("Timed out waiting for vault {vault_id} balance to reach {expected_balance}")
|
|
// })?
|
|
// }
|
|
|
|
// /// Test deposit and withdraw round trip.
|
|
// ///
|
|
// /// Implemented as one test instead of two separate tests for deposit and withdraw, because the
|
|
// /// withdraw test depends on the deposit to set up the necessary state (funds in vault) for
|
|
// testing /// withdraw functionality.
|
|
// #[test]
|
|
// async fn bedrock_deposit_claim_and_withdraw_round_trip_succeeds() -> anyhow::Result<()> {
|
|
// let mut ctx = TestContext::new().await?;
|
|
|
|
// let bedrock_account_pk = "2e03b2eff5a45478e7e79668d2a146cf2c5c7925bce927f2b1c67f2ab4fc0d26";
|
|
// let recipient_id = ctx.existing_public_accounts()[0];
|
|
// let amount = 1_u64;
|
|
// let vault_program_id = programs::vault().id();
|
|
// let recipient_vault_id = vault_core::compute_vault_account_id(vault_program_id,
|
|
// recipient_id);
|
|
|
|
// let vault_balance_before = account_balance(&ctx, recipient_vault_id).await?;
|
|
// let recipient_balance_before = account_balance(&ctx, recipient_id).await?;
|
|
|
|
// // Submit deposit to Bedrock
|
|
// submit_bedrock_deposit(ctx.bedrock_addr(), bedrock_account_pk, recipient_id, amount)
|
|
// .await
|
|
// .context("Failed to submit Bedrock deposit for round-trip setup")?;
|
|
|
|
// // Wait for vault to receive the deposit (minted from bridge to vault)
|
|
// wait_for_vault_balance(
|
|
// &ctx,
|
|
// recipient_vault_id,
|
|
// vault_balance_before + u128::from(amount),
|
|
// )
|
|
// .await?;
|
|
|
|
// // Now claim funds from vault back to recipient
|
|
// let nonces = ctx
|
|
// .wallet()
|
|
// .get_accounts_nonces(&[recipient_id])
|
|
// .await
|
|
// .context("Failed to get nonce for vault claim")?;
|
|
|
|
// let signing_key = ctx
|
|
// .wallet()
|
|
// .storage()
|
|
// .key_chain()
|
|
// .pub_account_signing_key(recipient_id)
|
|
// .with_context(|| format!("Missing signing key for account {recipient_id}"))?;
|
|
|
|
// let claim_message = public_transaction::Message::try_new(
|
|
// vault_program_id,
|
|
// vec![recipient_id, recipient_vault_id],
|
|
// nonces,
|
|
// vault_core::Instruction::Claim {
|
|
// amount: u128::from(amount),
|
|
// },
|
|
// )
|
|
// .context("Failed to build vault claim message")?;
|
|
|
|
// let claim_witness_set =
|
|
// public_transaction::WitnessSet::for_message(&claim_message, &[signing_key]);
|
|
// let claim_tx = LeeTransaction::Public(lee::PublicTransaction::new(
|
|
// claim_message,
|
|
// claim_witness_set,
|
|
// ));
|
|
|
|
// let claim_hash = ctx.sequencer_client().send_transaction(claim_tx).await?;
|
|
|
|
// tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await;
|
|
|
|
// let claim_on_chain = ctx.sequencer_client().get_transaction(claim_hash).await?;
|
|
// let vault_balance_after_claim = account_balance(&ctx, recipient_vault_id).await?;
|
|
// let recipient_balance_after_claim = account_balance(&ctx, recipient_id).await?;
|
|
|
|
// assert!(
|
|
// claim_on_chain.is_some(),
|
|
// "Vault claim transaction must be included on-chain"
|
|
// );
|
|
// assert_eq!(
|
|
// vault_balance_after_claim, vault_balance_before,
|
|
// "Vault balance should return to initial state after claim"
|
|
// );
|
|
// assert_eq!(
|
|
// recipient_balance_after_claim,
|
|
// recipient_balance_before + u128::from(amount),
|
|
// "Recipient balance should increase by claimed amount"
|
|
// );
|
|
|
|
// // The indexer must replay the deposit and claim blocks and reach the same
|
|
// // state as the sequencer — including the bridge system account the deposit
|
|
// // modifies, which is the case the hot fix unblocks.
|
|
// wait_for_indexer_to_catch_up(&ctx).await?;
|
|
// let bridge_account_id = system_accounts::bridge_account_id();
|
|
// for account_id in [recipient_id, recipient_vault_id, bridge_account_id] {
|
|
// let indexer_account = indexer_service_rpc::RpcClient::get_account(
|
|
// // `deref` is needed for correct trait resolution
|
|
// // of the async `get_account` method on `RpcClient`
|
|
// ctx.indexer_client().deref(),
|
|
// account_id.into(),
|
|
// )
|
|
// .await?;
|
|
// let sequencer_account = get_account(&ctx, account_id).await?;
|
|
// assert_eq!(
|
|
// indexer_account,
|
|
// sequencer_account.into(),
|
|
// "Indexer and sequencer diverged for account {account_id} after deposit"
|
|
// );
|
|
// }
|
|
|
|
// // Withdraw back to Bedrock and wait for finalized withdraw event.
|
|
// let sender_id = recipient_id;
|
|
|
|
// let observer = create_zone_indexer_observer(ctx.bedrock_addr())?;
|
|
// let observe_fut =
|
|
// wait_for_finalized_withdraw_op(&observer, ctx.bedrock_addr(), amount,
|
|
// bedrock_account_pk);
|
|
|
|
// let withdraw_fut = execute_subcommand(
|
|
// ctx.wallet_mut(),
|
|
// Command::Bridge(BridgeSubcommand::Withdraw {
|
|
// from: public_mention(sender_id),
|
|
// amount,
|
|
// bedrock_account_pk: bedrock_account_pk.to_owned(),
|
|
// }),
|
|
// );
|
|
|
|
// let (observe_result, withdraw_result) = tokio::join!(observe_fut, withdraw_fut);
|
|
|
|
// withdraw_result.context("Failed to execute wallet bridge withdraw command")?;
|
|
|
|
// observe_result
|
|
// .context("Failed while waiting for finalized withdraw event from zone indexer")?;
|
|
|
|
// // Sleep to observe sequencer log about validated withdraw event
|
|
// tokio::time::sleep(Duration::from_secs(1)).await;
|
|
|
|
// Ok(())
|
|
// }
|
|
|
|
// fn create_zone_indexer_observer(
|
|
// bedrock_addr: std::net::SocketAddr,
|
|
// ) -> anyhow::Result<ZoneIndexer<NodeHttpClient>> {
|
|
// let bedrock_url = integration_tests::config::addr_to_url(
|
|
// integration_tests::config::UrlProtocol::Http,
|
|
// bedrock_addr,
|
|
// )
|
|
// .context("Failed to convert Bedrock addr to URL for zone indexer observer")?;
|
|
|
|
// let node = NodeHttpClient::new(CommonHttpClient::new(None), bedrock_url);
|
|
|
|
// Ok(ZoneIndexer::new(
|
|
// integration_tests::config::bedrock_channel_id(),
|
|
// node,
|
|
// ))
|
|
// }
|
|
|
|
// /// Waits for a finalized withdraw that pays `expected_amount` to `receiver_pk`.
|
|
// ///
|
|
// /// A withdraw op releases channel-owned notes and carries nothing but their
|
|
// /// ids — the value and recipient live in the note itself. A released note keeps
|
|
// /// its id, value and public key, so the pairing is checked on the receiver's
|
|
// /// Bedrock wallet: one of the released notes must land there with the expected
|
|
// /// value.
|
|
// async fn wait_for_finalized_withdraw_op(
|
|
// observer: &ZoneIndexer<NodeHttpClient>,
|
|
// bedrock_addr: std::net::SocketAddr,
|
|
// expected_amount: u64,
|
|
// receiver_pk: &str,
|
|
// ) -> anyhow::Result<()> {
|
|
// let timeout = TIME_TO_FINALIZE_DEPOSIT_EVENT_ON_BEDROCK
|
|
// + Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS);
|
|
|
|
// tokio::time::timeout(timeout, async {
|
|
// // The wallet can trail the channel event, so released notes accumulate
|
|
// // across polls instead of being checked once when first observed.
|
|
// let mut released_notes = HashSet::new();
|
|
|
|
// loop {
|
|
// let stream = observer
|
|
// .follow()
|
|
// .await
|
|
// .context("Failed to read zone indexer message batch")?;
|
|
// let mut stream = std::pin::pin!(stream);
|
|
|
|
// while let Some(message) = stream.next().await {
|
|
// info!("Observed zone message {message:?}");
|
|
|
|
// if let ZoneMessage::Withdraw(withdraw) = message {
|
|
// released_notes.extend(withdraw.inputs.iter().copied());
|
|
// }
|
|
// }
|
|
|
|
// if !released_notes.is_empty() {
|
|
// let balance = bedrock_wallet_balance(bedrock_addr, receiver_pk).await?;
|
|
// if released_notes
|
|
// .iter()
|
|
// .any(|note_id| balance.notes.get(note_id) == Some(&expected_amount))
|
|
// {
|
|
// return Ok(());
|
|
// }
|
|
// }
|
|
|
|
// tokio::time::sleep(Duration::from_millis(500)).await;
|
|
// }
|
|
// })
|
|
// .await
|
|
// .with_context(|| {
|
|
// format!("Timed out waiting for finalized withdraw message with amount {expected_amount}")
|
|
// })?
|
|
// }
|