feat: inspect spks on request types
This commit is contained in:
parent
1f7c782d49
commit
89f803a753
@ -176,6 +176,11 @@ interface PsbtParseError {
|
|||||||
Base64Encoding(string error_message);
|
Base64Encoding(string error_message);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
[Error]
|
||||||
|
interface InspectError {
|
||||||
|
RequestAlreadyConsumed();
|
||||||
|
};
|
||||||
|
|
||||||
[Error]
|
[Error]
|
||||||
interface SignerError {
|
interface SignerError {
|
||||||
MissingKey();
|
MissingKey();
|
||||||
@ -274,9 +279,23 @@ dictionary CanonicalTx {
|
|||||||
ChainPosition chain_position;
|
ChainPosition chain_position;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface FullScanRequest {};
|
interface FullScanRequest {
|
||||||
|
[Throws=InspectError]
|
||||||
|
FullScanRequest inspect_spks_for_all_keychains(FullScanScriptInspector inspector);
|
||||||
|
};
|
||||||
|
|
||||||
interface SyncRequest {};
|
interface SyncRequest {
|
||||||
|
[Throws=InspectError]
|
||||||
|
SyncRequest inspect_spks(SyncScriptInspector inspector);
|
||||||
|
};
|
||||||
|
|
||||||
|
callback interface SyncScriptInspector {
|
||||||
|
void inspect(Script script, u64 total);
|
||||||
|
};
|
||||||
|
|
||||||
|
callback interface FullScanScriptInspector {
|
||||||
|
void inspect(KeychainKind keychain, u32 index, Script script);
|
||||||
|
};
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// bdk crate - wallet module
|
// bdk crate - wallet module
|
||||||
|
@ -375,6 +375,12 @@ pub enum FeeRateError {
|
|||||||
ArithmeticOverflow,
|
ArithmeticOverflow,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, thiserror::Error)]
|
||||||
|
pub enum InspectError {
|
||||||
|
#[error("the request has already been consumed")]
|
||||||
|
RequestAlreadyConsumed,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum ParseAmountError {
|
pub enum ParseAmountError {
|
||||||
#[error("amount is negative")]
|
#[error("amount is negative")]
|
||||||
@ -1080,8 +1086,8 @@ mod test {
|
|||||||
use crate::error::{
|
use crate::error::{
|
||||||
AddressError, Bip32Error, Bip39Error, CannotConnectError, CreateTxError, DescriptorError,
|
AddressError, Bip32Error, Bip39Error, CannotConnectError, CreateTxError, DescriptorError,
|
||||||
DescriptorKeyError, ElectrumError, EsploraError, ExtractTxError, FeeRateError,
|
DescriptorKeyError, ElectrumError, EsploraError, ExtractTxError, FeeRateError,
|
||||||
ParseAmountError, PersistenceError, PsbtParseError, TransactionError, TxidParseError,
|
InspectError, ParseAmountError, PersistenceError, PsbtParseError, TransactionError,
|
||||||
WalletCreationError,
|
TxidParseError, WalletCreationError,
|
||||||
};
|
};
|
||||||
use crate::CalculateFeeError;
|
use crate::CalculateFeeError;
|
||||||
use crate::OutPoint;
|
use crate::OutPoint;
|
||||||
@ -1671,6 +1677,18 @@ mod test {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_error_inspect() {
|
||||||
|
let cases = vec![(
|
||||||
|
InspectError::RequestAlreadyConsumed,
|
||||||
|
"the request has already been consumed",
|
||||||
|
)];
|
||||||
|
|
||||||
|
for (error, expected_message) in cases {
|
||||||
|
assert_eq!(error.to_string(), expected_message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_error_parse_amount() {
|
fn test_error_parse_amount() {
|
||||||
let cases = vec![
|
let cases = vec![
|
||||||
|
@ -30,6 +30,7 @@ use crate::error::ElectrumError;
|
|||||||
use crate::error::EsploraError;
|
use crate::error::EsploraError;
|
||||||
use crate::error::ExtractTxError;
|
use crate::error::ExtractTxError;
|
||||||
use crate::error::FeeRateError;
|
use crate::error::FeeRateError;
|
||||||
|
use crate::error::InspectError;
|
||||||
use crate::error::ParseAmountError;
|
use crate::error::ParseAmountError;
|
||||||
use crate::error::PersistenceError;
|
use crate::error::PersistenceError;
|
||||||
use crate::error::PsbtParseError;
|
use crate::error::PsbtParseError;
|
||||||
@ -47,9 +48,11 @@ use crate::types::Balance;
|
|||||||
use crate::types::CanonicalTx;
|
use crate::types::CanonicalTx;
|
||||||
use crate::types::ChainPosition;
|
use crate::types::ChainPosition;
|
||||||
use crate::types::FullScanRequest;
|
use crate::types::FullScanRequest;
|
||||||
|
use crate::types::FullScanScriptInspector;
|
||||||
use crate::types::LocalOutput;
|
use crate::types::LocalOutput;
|
||||||
use crate::types::ScriptAmount;
|
use crate::types::ScriptAmount;
|
||||||
use crate::types::SyncRequest;
|
use crate::types::SyncRequest;
|
||||||
|
use crate::types::SyncScriptInspector;
|
||||||
use crate::wallet::BumpFeeTxBuilder;
|
use crate::wallet::BumpFeeTxBuilder;
|
||||||
use crate::wallet::SentAndReceivedValues;
|
use crate::wallet::SentAndReceivedValues;
|
||||||
use crate::wallet::TxBuilder;
|
use crate::wallet::TxBuilder;
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
use crate::bitcoin::{Address, OutPoint, Script, Transaction, TxOut};
|
use crate::bitcoin::{Address, OutPoint, Script, Transaction, TxOut};
|
||||||
|
|
||||||
|
use bdk::bitcoin::ScriptBuf as BdkScriptBuf;
|
||||||
use bdk::chain::spk_client::FullScanRequest as BdkFullScanRequest;
|
use bdk::chain::spk_client::FullScanRequest as BdkFullScanRequest;
|
||||||
use bdk::chain::spk_client::SyncRequest as BdkSyncRequest;
|
use bdk::chain::spk_client::SyncRequest as BdkSyncRequest;
|
||||||
use bdk::chain::tx_graph::CanonicalTx as BdkCanonicalTx;
|
use bdk::chain::tx_graph::CanonicalTx as BdkCanonicalTx;
|
||||||
@ -12,6 +13,7 @@ use bdk::LocalOutput as BdkLocalOutput;
|
|||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
use crate::bitcoin::Amount;
|
use crate::bitcoin::Amount;
|
||||||
|
use crate::error::InspectError;
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
pub enum ChainPosition {
|
pub enum ChainPosition {
|
||||||
@ -112,6 +114,55 @@ impl From<BdkLocalOutput> for LocalOutput {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Callback for the FullScanRequest
|
||||||
|
pub trait FullScanScriptInspector: Sync + Send {
|
||||||
|
fn inspect(&self, keychain: KeychainKind, index: u32, script: Arc<Script>);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Callback for the SyncRequest
|
||||||
|
pub trait SyncScriptInspector: Sync + Send {
|
||||||
|
fn inspect(&self, script: Arc<Script>, total: u64);
|
||||||
|
}
|
||||||
|
|
||||||
pub struct FullScanRequest(pub(crate) Mutex<Option<BdkFullScanRequest<KeychainKind>>>);
|
pub struct FullScanRequest(pub(crate) Mutex<Option<BdkFullScanRequest<KeychainKind>>>);
|
||||||
|
|
||||||
pub struct SyncRequest(pub(crate) Mutex<Option<BdkSyncRequest>>);
|
pub struct SyncRequest(pub(crate) Mutex<Option<BdkSyncRequest>>);
|
||||||
|
|
||||||
|
impl SyncRequest {
|
||||||
|
pub fn inspect_spks(
|
||||||
|
&self,
|
||||||
|
inspector: Box<dyn SyncScriptInspector>,
|
||||||
|
) -> Result<Arc<Self>, InspectError> {
|
||||||
|
let mut guard = self.0.lock().unwrap();
|
||||||
|
if let Some(sync_request) = guard.take() {
|
||||||
|
let total = sync_request.spks.len() as u64;
|
||||||
|
let sync_request = sync_request.inspect_spks(move |spk| {
|
||||||
|
inspector.inspect(Arc::new(BdkScriptBuf::from(spk).into()), total)
|
||||||
|
});
|
||||||
|
Ok(Arc::new(SyncRequest(Mutex::new(Some(sync_request)))))
|
||||||
|
} else {
|
||||||
|
Err(InspectError::RequestAlreadyConsumed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FullScanRequest {
|
||||||
|
pub fn inspect_spks_for_all_keychains(
|
||||||
|
&self,
|
||||||
|
inspector: Box<dyn FullScanScriptInspector>,
|
||||||
|
) -> Result<Arc<Self>, InspectError> {
|
||||||
|
let mut guard = self.0.lock().unwrap();
|
||||||
|
if let Some(full_scan_request) = guard.take() {
|
||||||
|
let inspector = Arc::new(inspector);
|
||||||
|
let full_scan_request =
|
||||||
|
full_scan_request.inspect_spks_for_all_keychains(move |k, spk_i, script| {
|
||||||
|
inspector.inspect(k, spk_i, Arc::new(BdkScriptBuf::from(script).into()))
|
||||||
|
});
|
||||||
|
Ok(Arc::new(FullScanRequest(Mutex::new(Some(
|
||||||
|
full_scan_request,
|
||||||
|
)))))
|
||||||
|
} else {
|
||||||
|
Err(InspectError::RequestAlreadyConsumed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -6,6 +6,7 @@ private const val SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net"
|
|||||||
private const val TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
|
private const val TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
|
||||||
|
|
||||||
class LiveMemoryWalletTest {
|
class LiveMemoryWalletTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun testSyncedBalance() {
|
fun testSyncedBalance() {
|
||||||
val descriptor: Descriptor = Descriptor(
|
val descriptor: Descriptor = Descriptor(
|
||||||
@ -33,4 +34,38 @@ class LiveMemoryWalletTest {
|
|||||||
println("Received ${sentAndReceived.received.toSat()}")
|
println("Received ${sentAndReceived.received.toSat()}")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun testScriptInspector() {
|
||||||
|
val descriptor: Descriptor = Descriptor(
|
||||||
|
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
||||||
|
Network.SIGNET
|
||||||
|
)
|
||||||
|
val changeDescriptor: Descriptor = Descriptor(
|
||||||
|
"wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)",
|
||||||
|
Network.SIGNET
|
||||||
|
)
|
||||||
|
val wallet: Wallet = Wallet.newNoPersist(descriptor, changeDescriptor, Network.SIGNET)
|
||||||
|
val esploraClient: EsploraClient = EsploraClient(SIGNET_ESPLORA_URL)
|
||||||
|
|
||||||
|
val scriptInspector: FullScriptInspector = FullScriptInspector()
|
||||||
|
val fullScanRequest: FullScanRequest = wallet.startFullScan().inspectSpksForAllKeychains(scriptInspector)
|
||||||
|
val update = esploraClient.fullScan(fullScanRequest, 21uL, 1uL)
|
||||||
|
|
||||||
|
wallet.applyUpdate(update)
|
||||||
|
wallet.commit()
|
||||||
|
println("Balance: ${wallet.getBalance().total.toSat()}")
|
||||||
|
|
||||||
|
assert(wallet.getBalance().total.toSat() > 0uL) {
|
||||||
|
"Wallet balance must be greater than 0! Please send funds to ${wallet.revealNextAddress(KeychainKind.EXTERNAL).address.asString()} and try again."
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class FullScriptInspector: FullScanScriptInspector {
|
||||||
|
override fun inspect(keychain: KeychainKind, index: UInt, script: Script){
|
||||||
|
println("Inspecting index $index for keychain $keychain")
|
||||||
|
}
|
||||||
}
|
}
|
@ -5,6 +5,7 @@ private let SIGNET_ESPLORA_URL = "http://signet.bitcoindevkit.net"
|
|||||||
private let TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
|
private let TESTNET_ESPLORA_URL = "https://esplora.testnet.kuutamo.cloud"
|
||||||
|
|
||||||
final class LiveMemoryWalletTests: XCTestCase {
|
final class LiveMemoryWalletTests: XCTestCase {
|
||||||
|
|
||||||
func testSyncedBalance() throws {
|
func testSyncedBalance() throws {
|
||||||
let descriptor = try Descriptor(
|
let descriptor = try Descriptor(
|
||||||
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
||||||
@ -41,4 +42,45 @@ final class LiveMemoryWalletTests: XCTestCase {
|
|||||||
print("Received \(sentAndReceived.received.toSat())")
|
print("Received \(sentAndReceived.received.toSat())")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func testScriptInspector() throws {
|
||||||
|
let descriptor = try Descriptor(
|
||||||
|
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/0/*)",
|
||||||
|
network: Network.signet
|
||||||
|
)
|
||||||
|
let changeDescriptor = try Descriptor(
|
||||||
|
descriptor: "wpkh(tprv8ZgxMBicQKsPf2qfrEygW6fdYseJDDrVnDv26PH5BHdvSuG6ecCbHqLVof9yZcMoM31z9ur3tTYbSnr1WBqbGX97CbXcmp5H6qeMpyvx35B/84h/1h/0h/1/*)",
|
||||||
|
network: Network.signet
|
||||||
|
)
|
||||||
|
let wallet = try Wallet.newNoPersist(
|
||||||
|
descriptor: descriptor,
|
||||||
|
changeDescriptor: changeDescriptor,
|
||||||
|
network: .signet
|
||||||
|
)
|
||||||
|
let esploraClient = EsploraClient(url: SIGNET_ESPLORA_URL)
|
||||||
|
let scriptInspector = FullScriptInspector()
|
||||||
|
let fullScanRequest = try wallet.startFullScan().inspectSpksForAllKeychains(inspector: scriptInspector)
|
||||||
|
|
||||||
|
let update = try esploraClient.fullScan(
|
||||||
|
fullScanRequest: fullScanRequest,
|
||||||
|
stopGap: 21,
|
||||||
|
parallelRequests: 1
|
||||||
|
)
|
||||||
|
try wallet.applyUpdate(update: update)
|
||||||
|
let _ = try wallet.commit()
|
||||||
|
let address = try wallet.revealNextAddress(keychain: KeychainKind.external).address.asString()
|
||||||
|
|
||||||
|
XCTAssertGreaterThan(
|
||||||
|
wallet.getBalance().total.toSat(),
|
||||||
|
UInt64(0),
|
||||||
|
"Wallet must have positive balance, please send funds to \(address)"
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class FullScriptInspector: FullScanScriptInspector {
|
||||||
|
func inspect(keychain: KeychainKind, index: UInt32, script: Script) {
|
||||||
|
print("Inspecting index \(index) for keychain \(keychain)")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user