[wallet] Add AddressValidators
This commit is contained in:
63
src/wallet/address_validator.rs
Normal file
63
src/wallet/address_validator.rs
Normal file
@@ -0,0 +1,63 @@
|
||||
use bitcoin::Script;
|
||||
|
||||
use crate::descriptor::HDKeyPaths;
|
||||
use crate::types::ScriptType;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum AddressValidatorError {
|
||||
UserRejected,
|
||||
ConnectionError,
|
||||
TimeoutError,
|
||||
InvalidScript,
|
||||
}
|
||||
|
||||
pub trait AddressValidator {
|
||||
fn validate(
|
||||
&self,
|
||||
script_type: ScriptType,
|
||||
hd_keypaths: &HDKeyPaths,
|
||||
script: &Script,
|
||||
) -> Result<(), AddressValidatorError>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use std::sync::Arc;
|
||||
|
||||
use super::*;
|
||||
use crate::wallet::test::{get_funded_wallet, get_test_wpkh};
|
||||
use crate::wallet::TxBuilder;
|
||||
|
||||
struct TestValidator;
|
||||
impl AddressValidator for TestValidator {
|
||||
fn validate(
|
||||
&self,
|
||||
_script_type: ScriptType,
|
||||
_hd_keypaths: &HDKeyPaths,
|
||||
_script: &bitcoin::Script,
|
||||
) -> Result<(), AddressValidatorError> {
|
||||
Err(AddressValidatorError::InvalidScript)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "InvalidScript")]
|
||||
fn test_address_validator_external() {
|
||||
let (mut wallet, _, _) = get_funded_wallet(get_test_wpkh());
|
||||
wallet.add_address_validator(Arc::new(Box::new(TestValidator)));
|
||||
|
||||
wallet.get_new_address().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "InvalidScript")]
|
||||
fn test_address_validator_internal() {
|
||||
let (mut wallet, descriptors, _) = get_funded_wallet(get_test_wpkh());
|
||||
wallet.add_address_validator(Arc::new(Box::new(TestValidator)));
|
||||
|
||||
let addr = testutils!(@external descriptors, 10);
|
||||
wallet
|
||||
.create_tx(TxBuilder::from_addressees(vec![(addr, 25_000)]))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
@@ -14,6 +14,7 @@ use miniscript::descriptor::DescriptorPublicKey;
|
||||
#[allow(unused_imports)]
|
||||
use log::{debug, error, info, trace};
|
||||
|
||||
pub mod address_validator;
|
||||
pub mod coin_selection;
|
||||
pub mod export;
|
||||
mod rbf;
|
||||
@@ -22,7 +23,8 @@ pub mod time;
|
||||
pub mod tx_builder;
|
||||
pub mod utils;
|
||||
|
||||
use signer::{Signer, SignersContainer};
|
||||
use address_validator::AddressValidator;
|
||||
use signer::{Signer, SignerId, SignersContainer};
|
||||
use tx_builder::TxBuilder;
|
||||
use utils::{After, FeeRate, IsDust, Older};
|
||||
|
||||
@@ -33,7 +35,6 @@ use crate::descriptor::{
|
||||
};
|
||||
use crate::error::Error;
|
||||
use crate::psbt::PSBTUtils;
|
||||
// use crate::psbt::{utils::PSBTUtils, PSBTSatisfier, PSBTSigner};
|
||||
use crate::types::*;
|
||||
|
||||
const CACHE_ADDR_BATCH_SIZE: u32 = 100;
|
||||
@@ -47,6 +48,8 @@ pub struct Wallet<B: Blockchain, D: BatchDatabase> {
|
||||
signers: Arc<SignersContainer<DescriptorPublicKey>>,
|
||||
change_signers: Arc<SignersContainer<DescriptorPublicKey>>,
|
||||
|
||||
address_validators: Vec<Arc<Box<dyn AddressValidator>>>,
|
||||
|
||||
network: Network,
|
||||
|
||||
current_height: Option<u32>,
|
||||
@@ -96,6 +99,7 @@ where
|
||||
change_descriptor,
|
||||
signers,
|
||||
change_signers,
|
||||
address_validators: Vec::new(),
|
||||
|
||||
network,
|
||||
|
||||
@@ -134,6 +138,24 @@ where
|
||||
.fold(0, |sum, i| sum + i.txout.value))
|
||||
}
|
||||
|
||||
pub fn add_signer(
|
||||
&mut self,
|
||||
script_type: ScriptType,
|
||||
id: SignerId<DescriptorPublicKey>,
|
||||
signer: Arc<Box<dyn Signer>>,
|
||||
) {
|
||||
let signers = match script_type {
|
||||
ScriptType::External => Arc::make_mut(&mut self.signers),
|
||||
ScriptType::Internal => Arc::make_mut(&mut self.change_signers),
|
||||
};
|
||||
|
||||
signers.add_external(id, signer);
|
||||
}
|
||||
|
||||
pub fn add_address_validator(&mut self, validator: Arc<Box<dyn AddressValidator>>) {
|
||||
self.address_validators.push(validator);
|
||||
}
|
||||
|
||||
pub fn create_tx<Cs: coin_selection::CoinSelectionAlgorithm>(
|
||||
&self,
|
||||
builder: TxBuilder<Cs>,
|
||||
@@ -724,6 +746,14 @@ where
|
||||
self.cache_addresses(script_type, index, CACHE_ADDR_BATCH_SIZE)?;
|
||||
}
|
||||
|
||||
let hd_keypaths = descriptor.get_hd_keypaths(index)?;
|
||||
let script = descriptor
|
||||
.derive(&[ChildNumber::from_normal_idx(index).unwrap()])
|
||||
.script_pubkey();
|
||||
for validator in &self.address_validators {
|
||||
validator.validate(script_type, &hd_keypaths, &script)?;
|
||||
}
|
||||
|
||||
Ok(index)
|
||||
}
|
||||
|
||||
@@ -1103,21 +1133,21 @@ mod test {
|
||||
.is_some());
|
||||
}
|
||||
|
||||
fn get_test_wpkh() -> &'static str {
|
||||
pub(crate) fn get_test_wpkh() -> &'static str {
|
||||
"wpkh(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW)"
|
||||
}
|
||||
|
||||
fn get_test_single_sig_csv() -> &'static str {
|
||||
pub(crate) fn get_test_single_sig_csv() -> &'static str {
|
||||
// and(pk(Alice),older(6))
|
||||
"wsh(and_v(v:pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW),older(6)))"
|
||||
}
|
||||
|
||||
fn get_test_single_sig_cltv() -> &'static str {
|
||||
pub(crate) fn get_test_single_sig_cltv() -> &'static str {
|
||||
// and(pk(Alice),after(100000))
|
||||
"wsh(and_v(v:pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW),after(100000)))"
|
||||
}
|
||||
|
||||
fn get_funded_wallet(
|
||||
pub(crate) fn get_funded_wallet(
|
||||
descriptor: &str,
|
||||
) -> (
|
||||
OfflineWallet<MemoryDatabase>,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::any::Any;
|
||||
use std::collections::HashMap;
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
|
||||
use bitcoin::blockdata::opcodes;
|
||||
use bitcoin::blockdata::script::Builder as ScriptBuilder;
|
||||
@@ -17,7 +18,7 @@ use crate::descriptor::XKeyUtils;
|
||||
|
||||
/// Identifier of a signer in the `SignersContainers`. Used as a key to find the right signer among
|
||||
/// many of them
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum SignerId<Pk: MiniscriptKey> {
|
||||
PkHash(<Pk as MiniscriptKey>::Hash),
|
||||
Fingerprint(Fingerprint),
|
||||
@@ -150,8 +151,8 @@ impl Signer for PrivateKey {
|
||||
}
|
||||
|
||||
/// Container for multiple signers
|
||||
#[derive(Debug, Default)]
|
||||
pub struct SignersContainer<Pk: MiniscriptKey>(HashMap<SignerId<Pk>, Box<dyn Signer>>);
|
||||
#[derive(Debug, Default, Clone)]
|
||||
pub struct SignersContainer<Pk: MiniscriptKey>(HashMap<SignerId<Pk>, Arc<Box<dyn Signer>>>);
|
||||
|
||||
impl SignersContainer<DescriptorPublicKey> {
|
||||
pub fn as_key_map(&self) -> KeyMap {
|
||||
@@ -189,11 +190,12 @@ impl From<KeyMap> for SignersContainer<DescriptorPublicKey> {
|
||||
.public_key(&Secp256k1::signing_only())
|
||||
.to_pubkeyhash(),
|
||||
),
|
||||
Box::new(private_key),
|
||||
Arc::new(Box::new(private_key)),
|
||||
),
|
||||
DescriptorSecretKey::XPrv(xprv) => container.add_external(
|
||||
SignerId::from(xprv.root_fingerprint()),
|
||||
Arc::new(Box::new(xprv)),
|
||||
),
|
||||
DescriptorSecretKey::XPrv(xprv) => {
|
||||
container.add_external(SignerId::from(xprv.root_fingerprint()), Box::new(xprv))
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -212,13 +214,13 @@ impl<Pk: MiniscriptKey> SignersContainer<Pk> {
|
||||
pub fn add_external(
|
||||
&mut self,
|
||||
id: SignerId<Pk>,
|
||||
signer: Box<dyn Signer>,
|
||||
) -> Option<Box<dyn Signer>> {
|
||||
signer: Arc<Box<dyn Signer>>,
|
||||
) -> Option<Arc<Box<dyn Signer>>> {
|
||||
self.0.insert(id, signer)
|
||||
}
|
||||
|
||||
/// Removes a signer from the container and returns it
|
||||
pub fn remove(&mut self, id: SignerId<Pk>) -> Option<Box<dyn Signer>> {
|
||||
pub fn remove(&mut self, id: SignerId<Pk>) -> Option<Arc<Box<dyn Signer>>> {
|
||||
self.0.remove(&id)
|
||||
}
|
||||
|
||||
@@ -228,7 +230,7 @@ impl<Pk: MiniscriptKey> SignersContainer<Pk> {
|
||||
}
|
||||
|
||||
/// Finds the signer with a given id in the container
|
||||
pub fn find(&self, id: SignerId<Pk>) -> Option<&Box<dyn Signer>> {
|
||||
pub fn find(&self, id: SignerId<Pk>) -> Option<&Arc<Box<dyn Signer>>> {
|
||||
self.0.get(&id)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user