Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
462 changes: 96 additions & 366 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[workspace]
resolver = "2"
members = ["bdk_electrum_streaming"]
members = ["bdk_electrum_streaming", "trusted-headers-gen"]
10 changes: 5 additions & 5 deletions bdk_electrum_streaming/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ version = "0.7.0"
description = "An async/blocking Electrum client for BDK, built as an explicit streaming state machine."
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.70"
rust-version = "1.85"
repository = "https://github.com/evanlinjin/experiments"
documentation = "https://docs.rs/bdk_electrum_streaming"
readme = "README.md"
Expand All @@ -13,16 +13,16 @@ readme = "README.md"
futures = "0.3"
futures-timer = "3"
anyhow = "1"
bdk_core = { version = "0.6", features = ["serde"] }
miniscript = { version = "12.0.0" }
bdk_core = { git = "https://github.com/bitcoindevkit/bdk.git", rev = "456f9b7bbf510eefdf3e7a164a5d6bc4cd668adb", features = ["serde"] }
bdk_chain = { git = "https://github.com/bitcoindevkit/bdk.git", rev = "456f9b7bbf510eefdf3e7a164a5d6bc4cd668adb" }
miniscript = { version = "13.0.0" }
electrum_streaming_client = { version = "0.4" }
serde = { version = "1", features = ["derive", "rc"] }
serde_json = "1"
tracing = "0.1"

[dev-dependencies]
bdk_testenv = "0.13.0"
bdk_chain = "0.23.0"
bdk_testenv = { git = "https://github.com/bitcoindevkit/bdk.git", rev = "456f9b7bbf510eefdf3e7a164a5d6bc4cd668adb" }
tokio = { version = "1", features = ["time", "net", "rt", "macros"]}
tokio-util = { version = "0.7.15", features = ["compat"] }
tracing-subscriber = "0.3"
14 changes: 14 additions & 0 deletions bdk_electrum_streaming/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,17 @@ state machine on top of [`electrum_streaming_client`](https://docs.rs/electrum_s
Tracks a set of descriptors, subscribes to their script hashes, and streams `FullScanResponse`
updates as history, transactions and anchors resolve, instead of blocking until an entire scan
completes. Handles reorgs by refetching whichever anchors they affect.

The server is not trusted for chain data. You hand [`HeaderChain`] a set of trusted block headers;
everything above the highest one is downloaded and checked for linkage, proof-of-work, and the
difficulty consensus requires before it becomes part of the chain. A reorg is only accepted if it
brings more work than the blocks it replaces, and never if it would drop a trusted block.
Transactions are merkle-proved against those headers, and the proof travels with the anchor
([`ProvenAnchor`]).

The highest trusted block must sit on a difficulty-adjustment boundary (`height % 2016 == 0`) on
networks where difficulty moves, so every retarget above it can be recomputed rather than taken on
faith.

A transaction confirmed below the sync start triggers a backfill: headers are fetched from just
above the highest trusted block below it, up to where the chain already begins.
33 changes: 33 additions & 0 deletions bdk_electrum_streaming/src/anchor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
use bdk_chain::Anchor;
use bdk_core::BlockId;
use electrum_streaming_client::DoubleSHA;

/// Anchors a transaction to a block, recording the merkle proof that put it there.
///
/// A [`ProvenAnchor`] only exists if [`merkle`](Self::merkle) was checked against the `merkle_root`
/// of the block's header, and that header is part of the verified
/// [`HeaderChain`](crate::HeaderChain).
///
/// There is no block time here: the header the proof was checked against travels with every
/// [`Update`](crate::Update) as part of `chain_update`, so times can be read from there.
#[derive(
Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, serde::Serialize, serde::Deserialize,
)]
pub struct ProvenAnchor {
/// The block the transaction is proven to be in.
pub block_id: BlockId,
/// Position of the transaction within the block's merkle tree.
pub pos: usize,
/// Merkle branch connecting the transaction to the block's merkle root.
pub merkle: Vec<DoubleSHA>,
}

impl Anchor for ProvenAnchor {
fn anchor_block(&self) -> BlockId {
self.block_id
}

fn confirmation_height_upper_bound(&self) -> u32 {
self.block_id.height
}
}
Loading