-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add
MultiUpdateSubscriber
to receive multiple updates from a …
…single node
- Loading branch information
1 parent
2d5f3c5
commit 98e7ece
Showing
5 changed files
with
343 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -26,3 +26,6 @@ tracing-subscriber = { version = "0.3" } | |
|
||
[[example]] | ||
name = "example" | ||
|
||
[[example]] | ||
name = "multi" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
use std::net::IpAddr; | ||
use std::net::Ipv4Addr; | ||
|
||
use bdk_kyoto::builder::LightClientBuilder; | ||
use bdk_kyoto::MultiLightClient; | ||
use bdk_wallet::Wallet; | ||
|
||
use kyoto::Network; | ||
|
||
use bdk_kyoto::multi::MultiSyncRequest; | ||
use bdk_kyoto::ScanType; | ||
|
||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)] | ||
struct WalletId(u8); | ||
|
||
const WALLET_ID_ONE: WalletId = WalletId(1); | ||
const WALLET_ID_TWO: WalletId = WalletId(2); | ||
|
||
const PRIV_RECV: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/0/*)"; | ||
const PRIV_CHANGE: &str = "wpkh(tprv8ZgxMBicQKsPdy6LMhUtFHAgpocR8GC6QmwMSFpZs7h6Eziw3SpThFfczTDh5rW2krkqffa11UpX3XkeTTB2FvzZKWXqPY54Y6Rq4AQ5R8L/84'/1'/0'/1/*)"; | ||
const PUB_RECV: &str = "tr([7d94197e/86'/1'/0']tpubDCyQVJj8KzjiQsFjmb3KwECVXPvMwvAxxZGCP9XmWSopmjW3bCV3wD7TgxrUhiGSueDS1MU5X1Vb1YjYcp8jitXc5fXfdC1z68hDDEyKRNr/0/*)"; | ||
const PUB_CHANGE: &str = "tr([7d94197e/86'/1'/0']tpubDCyQVJj8KzjiQsFjmb3KwECVXPvMwvAxxZGCP9XmWSopmjW3bCV3wD7TgxrUhiGSueDS1MU5X1Vb1YjYcp8jitXc5fXfdC1z68hDDEyKRNr/1/*)"; | ||
|
||
const NETWORK: Network = Network::Signet; | ||
|
||
const PEER: IpAddr = IpAddr::V4(Ipv4Addr::new(23, 137, 57, 100)); | ||
const NUM_PEERS: u8 = 1; | ||
|
||
const RECOVERY_HEIGHT: u32 = 170_000; | ||
|
||
#[tokio::main] | ||
async fn main() -> anyhow::Result<()> { | ||
let subscriber = tracing_subscriber::FmtSubscriber::new(); | ||
tracing::subscriber::set_global_default(subscriber)?; | ||
|
||
let mut wallet_one = Wallet::create(PUB_RECV, PUB_CHANGE) | ||
.network(NETWORK) | ||
.lookahead(30) | ||
.create_wallet_no_persist()?; | ||
|
||
let mut wallet_two = Wallet::create(PRIV_RECV, PRIV_CHANGE) | ||
.network(NETWORK) | ||
.lookahead(30) | ||
.create_wallet_no_persist()?; | ||
|
||
let request_one = MultiSyncRequest { | ||
index: WALLET_ID_ONE, | ||
scan_type: ScanType::New, | ||
wallet: &wallet_one, | ||
}; | ||
let request_two = MultiSyncRequest { | ||
index: WALLET_ID_TWO, | ||
scan_type: ScanType::Recovery { | ||
from_height: RECOVERY_HEIGHT, | ||
}, | ||
wallet: &wallet_two, | ||
}; | ||
let requests = vec![request_one, request_two]; | ||
|
||
let MultiLightClient { | ||
requester: _, | ||
mut log_subscriber, | ||
mut warning_subscriber, | ||
mut update_subscriber, | ||
node, | ||
} = LightClientBuilder::new() | ||
.connections(NUM_PEERS) | ||
.peers(vec![PEER.into()]) | ||
.build_multi(requests)?; | ||
|
||
tokio::task::spawn(async move { node.run().await }); | ||
|
||
loop { | ||
tokio::select! { | ||
updates = update_subscriber.sync() => { | ||
for (index, update) in updates { | ||
tracing::info!("Got update for wallet {}", index.0); | ||
if index == WALLET_ID_ONE { | ||
wallet_one.apply_update(update)?; | ||
tracing::info!("Wallet one balance {}", wallet_one.balance().total()) | ||
} else if index == WALLET_ID_TWO { | ||
wallet_two.apply_update(update)?; | ||
tracing::info!("Wallet two balance {}", wallet_two.balance().total()) | ||
} | ||
} | ||
}, | ||
log = log_subscriber.next_log() => { | ||
tracing::info!("{log}") | ||
} | ||
warn = warning_subscriber.next_warning() => { | ||
tracing::warn!("{warn}") | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
//! Types for requesting updates for multiple wallets. | ||
use std::{ | ||
collections::{BTreeMap, HashMap}, | ||
hash::Hash, | ||
}; | ||
|
||
use bdk_wallet::{ | ||
chain::{ | ||
keychain_txout::KeychainTxOutIndex, local_chain, local_chain::LocalChain, | ||
ConfirmationBlockTime, IndexedTxGraph, TxUpdate, | ||
}, | ||
KeychainKind, Update, Wallet, | ||
}; | ||
use kyoto::{BlockHash, Event, SyncUpdate, UnboundedReceiver}; | ||
|
||
use crate::ScanType; | ||
|
||
/// A request when building a node for multiple wallets. | ||
pub struct MultiSyncRequest<'a, H: Hash + Eq + Clone + Copy> { | ||
/// The unique identifier for a wallet. Typically a simple index like an integer. | ||
pub index: H, | ||
/// The scanning policy for this wallet. | ||
pub scan_type: ScanType, | ||
/// The wallet to fetch an update for. | ||
pub wallet: &'a Wallet, | ||
} | ||
|
||
/// Construct updates for multiple wallets. | ||
pub struct MultiUpdateSubscriber<H: Hash + Eq + Clone + Copy> { | ||
pub(crate) receiver: UnboundedReceiver<Event>, | ||
pub(crate) wallet_map: HashMap< | ||
H, | ||
( | ||
LocalChain, | ||
IndexedTxGraph<ConfirmationBlockTime, KeychainTxOutIndex<KeychainKind>>, | ||
), | ||
>, | ||
pub(crate) chain_changeset: BTreeMap<u32, Option<BlockHash>>, | ||
} | ||
|
||
impl<H> MultiUpdateSubscriber<H> | ||
where | ||
H: Hash + Eq + Clone + Copy, | ||
{ | ||
/// Return updates for all registered wallets for every time the light client | ||
/// syncs to connected peers. | ||
pub async fn sync(&mut self) -> impl Iterator<Item = (H, Update)> { | ||
while let Some(event) = self.receiver.recv().await { | ||
match event { | ||
Event::Block(indexed_block) => { | ||
let hash = indexed_block.block.block_hash(); | ||
self.chain_changeset | ||
.insert(indexed_block.height, Some(hash)); | ||
for (_, graph) in self.wallet_map.values_mut() { | ||
let _ = | ||
graph.apply_block_relevant(&indexed_block.block, indexed_block.height); | ||
} | ||
} | ||
Event::BlocksDisconnected(disconnected) => { | ||
for header in disconnected { | ||
let height = header.height; | ||
self.chain_changeset.insert(height, None); | ||
} | ||
} | ||
Event::Synced(SyncUpdate { | ||
tip: _, | ||
recent_history, | ||
}) => { | ||
recent_history.into_iter().for_each(|(height, header)| { | ||
self.chain_changeset | ||
.insert(height, Some(header.block_hash())); | ||
}); | ||
break; | ||
} | ||
} | ||
} | ||
let mut responses = Vec::new(); | ||
for (index, (local_chain, graph)) in &mut self.wallet_map { | ||
let tx_update = TxUpdate::from(graph.graph().clone()); | ||
let last_active_indices = graph.index.last_used_indices(); | ||
local_chain | ||
.apply_changeset(&local_chain::ChangeSet::from(self.chain_changeset.clone())) | ||
.expect("chain was initialized with genesis"); | ||
let update = Update { | ||
tx_update, | ||
last_active_indices, | ||
chain: Some(local_chain.tip()), | ||
}; | ||
responses.push((*index, update)); | ||
} | ||
self.chain_changeset = BTreeMap::new(); | ||
responses.into_iter() | ||
} | ||
} |