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
15 changes: 10 additions & 5 deletions backend-blindbit-v1/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,19 @@ repository.workspace = true
[lib]
crate-type = ["lib", "staticlib", "cdylib"]

[features]
default = ["async"]
async = ["spdk-core/async", "dep:async-trait", "dep:futures", "dep:reqwest"]
sync = ["spdk-core/sync", "dep:ureq"]

[dependencies]
spdk-core.workspace = true
spdk-core = { workspace = true, default-features = false }

anyhow.workspace = true
async-trait.workspace = true
bitcoin.workspace = true
futures.workspace = true
async-trait = { workspace = true, optional = true }
futures = { workspace = true, optional = true }
serde.workspace = true
serde_json.workspace = true
reqwest.workspace = true
reqwest = { workspace = true, optional = true }
ureq = { version = "2", features = ["json", "gzip", "tls"], optional = true }
hex.workspace = true
23 changes: 23 additions & 0 deletions backend-blindbit-v1/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,29 @@
#[cfg(all(feature = "async", feature = "sync"))]
compile_error!(
"Features `async` and `sync` are mutually exclusive. Use `--no-default-features --features sync` for a sync build."
);

#[cfg(not(any(feature = "async", feature = "sync")))]
compile_error!("Either feature `async` or `sync` must be enabled.");

pub mod api_structs;

#[cfg(feature = "async")]
mod backend;
#[cfg(feature = "async")]
mod client;

#[cfg(feature = "sync")]
mod sync_backend;
#[cfg(feature = "sync")]
mod sync_client;

#[cfg(feature = "async")]
pub use backend::BlindbitBackend;
#[cfg(feature = "async")]
pub use client::BlindbitClient;

#[cfg(feature = "sync")]
pub use sync_backend::SyncBlindbitBackend;
#[cfg(feature = "sync")]
pub use sync_client::SyncBlindbitClient;
73 changes: 73 additions & 0 deletions backend-blindbit-v1/src/sync_backend.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use std::ops::RangeInclusive;

use spdk_core::bitcoin::{Amount, absolute::Height};

use anyhow::Result;
use spdk_core::{BlockData, SpentIndexData, SyncChainBackend, UtxoData};

use crate::SyncBlindbitClient;

#[derive(Debug)]
pub struct SyncBlindbitBackend {
client: SyncBlindbitClient,
}

impl SyncBlindbitBackend {
pub fn new(blindbit_url: String) -> Result<Self> {
Ok(Self {
client: SyncBlindbitClient::new(blindbit_url)?,
})
}
}

impl SyncChainBackend for SyncBlindbitBackend {
/// High-level function to get block data for a range of blocks.
/// Block data includes all the information needed to determine if a block is relevant for scanning,
/// but does not include utxos, or spent index.
/// These need to be fetched separately afterwards, if it is determined this block is relevant.
fn get_block_data_for_range(
&self,
range: RangeInclusive<u32>,
dust_limit: Amount,
with_cutthrough: bool,
) -> Box<dyn Iterator<Item = Result<BlockData>> + Send> {
let client = self.client.clone();

let iter = range.map(move |n| {
let blkheight = Height::from_consensus(n)?;
let tweaks = match with_cutthrough {
true => client.tweaks(blkheight, dust_limit)?,
false => client.tweak_index(blkheight, dust_limit)?,
};
let new_utxo_filter = client.filter_new_utxos(blkheight)?;
let spent_filter = client.filter_spent(blkheight)?;
let blkhash = new_utxo_filter.block_hash;
Ok(BlockData {
blkheight,
blkhash,
tweaks,
new_utxo_filter: new_utxo_filter.into(),
spent_filter: spent_filter.into(),
})
});

Box::new(iter)
}

fn spent_index(&self, block_height: Height) -> Result<SpentIndexData> {
self.client.spent_index(block_height).map(Into::into)
}

fn utxos(&self, block_height: Height) -> Result<Vec<UtxoData>> {
Ok(self
.client
.utxos(block_height)?
.into_iter()
.map(Into::into)
.collect())
}

fn block_height(&self) -> Result<Height> {
self.client.block_height()
}
}
105 changes: 105 additions & 0 deletions backend-blindbit-v1/src/sync_client.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
use std::time::Duration;

use spdk_core::bitcoin::{Amount, Txid, absolute::Height, secp256k1::PublicKey};
use ureq::Agent;

use anyhow::Result;

use super::api_structs::{
BlockHeightResponse, FilterResponse, ForwardTxRequest, InfoResponse, SpentIndexResponse,
UtxoResponse,
};

#[derive(Clone, Debug)]
pub struct SyncBlindbitClient {
agent: Agent,
host_url: String,
}

impl SyncBlindbitClient {
pub fn new(host_url: String) -> Result<Self> {
let agent = ureq::AgentBuilder::new()
.timeout_connect(Duration::from_secs(5))
.timeout_read(Duration::from_secs(30))
.build();

// we need a trailing slash, if not present we append it
let host_url = if host_url.ends_with('/') {
host_url
} else {
format!("{}/", host_url)
};

Ok(SyncBlindbitClient { agent, host_url })
}

pub fn block_height(&self) -> Result<Height> {
let url = format!("{}block-height", self.host_url);
let body = self.agent.get(&url).call()?.into_string()?;
let blkheight: BlockHeightResponse = serde_json::from_str(&body)?;
Ok(blkheight.block_height)
}

pub fn tweaks(&self, block_height: Height, dust_limit: Amount) -> Result<Vec<PublicKey>> {
let url = format!("{}tweaks/{}", self.host_url, block_height);
let body = self
.agent
.get(&url)
.query("dustLimit", &dust_limit.to_sat().to_string())
.call()?
.into_string()?;
Ok(serde_json::from_str(&body)?)
}

pub fn tweak_index(&self, block_height: Height, dust_limit: Amount) -> Result<Vec<PublicKey>> {
let url = format!("{}tweak-index/{}", self.host_url, block_height);
let body = self
.agent
.get(&url)
.query("dustLimit", &dust_limit.to_sat().to_string())
.call()?
.into_string()?;
Ok(serde_json::from_str(&body)?)
}

pub fn utxos(&self, block_height: Height) -> Result<Vec<UtxoResponse>> {
let url = format!("{}utxos/{}", self.host_url, block_height);
let body = self.agent.get(&url).call()?.into_string()?;
Ok(serde_json::from_str(&body)?)
}

pub fn spent_index(&self, block_height: Height) -> Result<SpentIndexResponse> {
let url = format!("{}spent-index/{}", self.host_url, block_height);
let body = self.agent.get(&url).call()?.into_string()?;
Ok(serde_json::from_str(&body)?)
}

pub fn filter_new_utxos(&self, block_height: Height) -> Result<FilterResponse> {
let url = format!("{}filter/new-utxos/{}", self.host_url, block_height);
let body = self.agent.get(&url).call()?.into_string()?;
Ok(serde_json::from_str(&body)?)
}

pub fn filter_spent(&self, block_height: Height) -> Result<FilterResponse> {
let url = format!("{}filter/spent/{}", self.host_url, block_height);
let body = self.agent.get(&url).call()?.into_string()?;
Ok(serde_json::from_str(&body)?)
}

pub fn forward_tx(&self, tx_hex: String) -> Result<Txid> {
let url = format!("{}forward-tx", self.host_url);
let body = ForwardTxRequest::new(tx_hex);
let resp = self
.agent
.post(&url)
.send_json(serde_json::to_value(&body)?)?
.into_string()?;
Ok(serde_json::from_str(&resp)?)
}

pub fn info(&self) -> Result<InfoResponse> {
let url = format!("{}info", self.host_url);
let body = self.agent.get(&url).call()?.into_string()?;
Ok(serde_json::from_str(&body)?)
}
}
9 changes: 7 additions & 2 deletions spdk-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,15 @@ repository.workspace = true
[lib]
crate-type = ["lib", "staticlib", "cdylib"]

[features]
default = ["async"]
async = ["dep:async-trait", "dep:futures"]
sync = []

[dependencies]
anyhow.workspace = true
async-trait.workspace = true
async-trait = { workspace = true, optional = true }
bitcoin.workspace = true
futures.workspace = true
futures = { workspace = true, optional = true }
serde.workspace = true
silentpayments.workspace = true
7 changes: 7 additions & 0 deletions spdk-core/src/chain/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
mod structs;

#[cfg(feature = "sync")]
mod sync_trait;
#[cfg(feature = "async")]
mod r#trait;

#[cfg(feature = "async")]
pub use r#trait::ChainBackend;
pub use structs::*;
#[cfg(feature = "sync")]
pub use sync_trait::SyncChainBackend;
21 changes: 21 additions & 0 deletions spdk-core/src/chain/sync_trait.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
use std::ops::RangeInclusive;

use anyhow::Result;
use bitcoin::{absolute::Height, Amount};

use super::structs::{BlockData, SpentIndexData, UtxoData};

pub trait SyncChainBackend {
fn get_block_data_for_range(
&self,
range: RangeInclusive<u32>,
dust_limit: Amount,
with_cutthrough: bool,
) -> Box<dyn Iterator<Item = Result<BlockData>> + Send>;

fn spent_index(&self, block_height: Height) -> Result<SpentIndexData>;

fn utxos(&self, block_height: Height) -> Result<Vec<UtxoData>>;

fn block_height(&self) -> Result<Height>;
}
6 changes: 6 additions & 0 deletions spdk-core/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
#[cfg(all(feature = "async", feature = "sync"))]
compile_error!("Features `async` and `sync` are mutually exclusive. Use `--no-default-features --features sync` for a sync build.");

#[cfg(not(any(feature = "async", feature = "sync")))]
compile_error!("Either feature `async` or `sync` must be enabled.");

pub mod chain;
pub mod constants;
pub mod updater;
Loading
Loading