Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add ElectrumClient::server_features method #641

Merged
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
63 changes: 63 additions & 0 deletions bdk-ffi/src/bdk.udl
Original file line number Diff line number Diff line change
Expand Up @@ -1054,18 +1054,81 @@ interface EsploraClient {
// bdk_electrum crate
// ------------------------------------------------------------------------

/// Wrapper around an electrum_client::ElectrumApi which includes an internal in-memory transaction
/// cache to avoid re-fetching already downloaded transactions.
interface ElectrumClient {
/// Creates a new bdk client from a electrum_client::ElectrumApi
[Throws=ElectrumError]
constructor(string url);

/// Full scan the keychain scripts specified with the blockchain (via an Electrum client) and
/// returns updates for bdk_chain data structures.
///
/// - `full_scan_request`: struct with data required to perform a spk-based blockchain client
/// full scan, see `FullScanRequest`.
/// - `stop_gap`: the full scan for each keychain stops after a gap of script pubkeys with no
/// associated transactions.
/// - `batch_size`: specifies the max number of script pubkeys to request for in a single batch
/// request.
/// - `fetch_prev_txouts`: specifies whether we want previous `TxOuts` for fee calculation. Note
/// that this requires additional calls to the Electrum server, but is necessary for
/// calculating the fee on a transaction if your wallet does not own the inputs. Methods like
/// `Wallet.calculate_fee` and `Wallet.calculate_fee_rate` will return a
/// `CalculateFeeError::MissingTxOut` error if those TxOuts are not present in the transaction
/// graph.
[Throws=ElectrumError]
Update full_scan(FullScanRequest full_scan_request, u64 stop_gap, u64 batch_size, boolean fetch_prev_txouts);

/// Sync a set of scripts with the blockchain (via an Electrum client) for the data specified and returns updates for bdk_chain data structures.
///
/// - `sync_request`: struct with data required to perform a spk-based blockchain client
/// sync, see `SyncRequest`.
/// - `batch_size`: specifies the max number of script pubkeys to request for in a single batch
/// request.
/// - `fetch_prev_txouts`: specifies whether we want previous `TxOuts` for fee calculation. Note
/// that this requires additional calls to the Electrum server, but is necessary for
/// calculating the fee on a transaction if your wallet does not own the inputs. Methods like
/// `Wallet.calculate_fee` and `Wallet.calculate_fee_rate` will return a
/// `CalculateFeeError::MissingTxOut` error if those TxOuts are not present in the transaction
/// graph.
///
/// If the scripts to sync are unknown, such as when restoring or importing a keychain that may
/// include scripts that have been used, use full_scan with the keychain.
[Throws=ElectrumError]
Update sync(SyncRequest sync_request, u64 batch_size, boolean fetch_prev_txouts);

/// Broadcasts a transaction to the network.
[Throws=ElectrumError]
string broadcast([ByRef] Transaction transaction);

/// Returns the capabilities of the server.
[Throws=ElectrumError]
ServerFeaturesRes server_features();

/// Estimates the fee required in bitcoin per kilobyte to confirm a transaction in `number` blocks.
[Throws=ElectrumError]
f64 estimate_fee(u64 number);
};

/// Response to an ElectrumClient.server_features request.
dictionary ServerFeaturesRes {
/// Server version reported.
string server_version;

/// Hash of the genesis block.
string genesis_hash;

/// Minimum supported version of the protocol.
string protocol_min;

/// Maximum supported version of the protocol.
string protocol_max;

/// Hash function used to create the `ScriptHash`.
string? hash_function;

/// Pruned height of the server.
i64? pruning;
};

// ------------------------------------------------------------------------
Expand Down
40 changes: 40 additions & 0 deletions bdk-ffi/src/electrum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@ use bdk_core::spk_client::FullScanRequest as BdkFullScanRequest;
use bdk_core::spk_client::FullScanResponse as BdkFullScanResponse;
use bdk_core::spk_client::SyncRequest as BdkSyncRequest;
use bdk_core::spk_client::SyncResponse as BdkSyncResponse;
use bdk_electrum::electrum_client::ServerFeaturesRes as BdkServerFeaturesRes;
use bdk_electrum::BdkElectrumClient as BdkBdkElectrumClient;
use bdk_wallet::bitcoin::Transaction as BdkTransaction;
use bdk_wallet::KeychainKind;
use bdk_wallet::Update as BdkUpdate;

use bdk_core::bitcoin::hex::{Case, DisplayHex};
use bdk_electrum::electrum_client::ElectrumApi;
use std::collections::BTreeMap;
use std::sync::Arc;

Expand Down Expand Up @@ -93,4 +96,41 @@ impl ElectrumClient {
.map_err(ElectrumError::from)
.map(|txid| txid.to_string())
}

pub fn server_features(&self) -> Result<ServerFeaturesRes, ElectrumError> {
self.0
.inner
.server_features()
.map_err(ElectrumError::from)
.map(ServerFeaturesRes::from)
}

pub fn estimate_fee(&self, number: u64) -> Result<f64, ElectrumError> {
self.0
.inner
.estimate_fee(number as usize)
.map_err(ElectrumError::from)
}
}

pub struct ServerFeaturesRes {
pub server_version: String,
pub genesis_hash: String,
pub protocol_min: String,
pub protocol_max: String,
pub hash_function: Option<String>,
pub pruning: Option<i64>,
}

impl From<BdkServerFeaturesRes> for ServerFeaturesRes {
fn from(value: BdkServerFeaturesRes) -> ServerFeaturesRes {
ServerFeaturesRes {
server_version: value.server_version,
genesis_hash: value.genesis_hash.to_hex_string(Case::Lower),
protocol_min: value.protocol_min,
protocol_max: value.protocol_max,
hash_function: value.hash_function,
pruning: value.pruning,
}
}
}
1 change: 1 addition & 0 deletions bdk-ffi/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use crate::bitcoin::TxIn;
use crate::bitcoin::TxOut;
use crate::descriptor::Descriptor;
use crate::electrum::ElectrumClient;
use crate::electrum::ServerFeaturesRes;
use crate::error::AddressParseError;
use crate::error::Bip32Error;
use crate::error::Bip39Error;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package org.bitcoindevkit

import kotlin.test.Test
import kotlin.test.assertEquals
import org.rustbitcoin.bitcoin.Network
import org.bitcoindevkit.ServerFeaturesRes

private const val SIGNET_ELECTRUM_URL = "ssl://mempool.space:60602"

Expand Down Expand Up @@ -38,4 +40,16 @@ class LiveElectrumClientTest {
println("Received ${sentAndReceived.received.toSat()}")
}
}
}

@Test
fun testServerFeatures() {
val electrumClient: ElectrumClient = ElectrumClient("ssl://electrum.blockstream.info:60002")
val features: ServerFeaturesRes = electrumClient.serverFeatures()
println("Server Features:\n$features")

assertEquals(
expected = "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943",
actual = features.genesisHash
)
}
}
11 changes: 11 additions & 0 deletions bdk-swift/Tests/BitcoinDevKitTests/LiveElectrumClientTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -47,4 +47,15 @@ final class LiveElectrumClientTests: XCTestCase {
print("Received \(sentAndReceived.received.toSat())")
}
}

func testServerFeatures() throws {
let electrumClient: ElectrumClient = try ElectrumClient(url: "ssl://electrum.blockstream.info:60002")
let features: ServerFeaturesRes = try electrumClient.serverFeatures()
print("Server Features:\n\(features)")

XCTAssertEqual(
features.genesisHash,
"000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943"
)
}
}
Loading