Wallet logic

This commit is contained in:
Alekos Filini
2020-02-07 23:22:28 +01:00
parent d01e4369df
commit 1a4e1bd96c
15 changed files with 2057 additions and 32 deletions

View File

@@ -235,7 +235,7 @@ impl BatchOperations for Batch {
}
impl Database for Tree {
fn iter_script_pubkeys(&self, script_type: Option<ScriptType>) -> Vec<Result<Script, Error>> {
fn iter_script_pubkeys(&self, script_type: Option<ScriptType>) -> Result<Vec<Script>, Error> {
let key = SledKey::Path((script_type, None)).as_sled_key();
self.scan_prefix(key)
.map(|x| -> Result<_, Error> {
@@ -245,7 +245,7 @@ impl Database for Tree {
.collect()
}
fn iter_utxos(&self) -> Vec<Result<UTXO, Error>> {
fn iter_utxos(&self) -> Result<Vec<UTXO>, Error> {
let key = SledKey::UTXO(None).as_sled_key();
self.scan_prefix(key)
.map(|x| -> Result<_, Error> {
@@ -257,7 +257,7 @@ impl Database for Tree {
.collect()
}
fn iter_raw_txs(&self) -> Vec<Result<Transaction, Error>> {
fn iter_raw_txs(&self) -> Result<Vec<Transaction>, Error> {
let key = SledKey::RawTx(None).as_sled_key();
self.scan_prefix(key)
.map(|x| -> Result<_, Error> {
@@ -267,8 +267,8 @@ impl Database for Tree {
.collect()
}
fn iter_txs(&self, include_raw: bool) -> Vec<Result<TransactionDetails, Error>> {
let key = SledKey::RawTx(None).as_sled_key();
fn iter_txs(&self, include_raw: bool) -> Result<Vec<TransactionDetails>, Error> {
let key = SledKey::Transaction(None).as_sled_key();
self.scan_prefix(key)
.map(|x| -> Result<_, Error> {
let (k, v) = x?;
@@ -516,7 +516,7 @@ mod test {
tree.set_script_pubkey(&script, script_type, &path).unwrap();
assert_eq!(tree.iter_script_pubkeys(None).len(), 1);
assert_eq!(tree.iter_script_pubkeys(None).unwrap().len(), 1);
}
#[test]
@@ -530,11 +530,11 @@ mod test {
let script_type = ScriptType::External;
tree.set_script_pubkey(&script, script_type, &path).unwrap();
assert_eq!(tree.iter_script_pubkeys(None).len(), 1);
assert_eq!(tree.iter_script_pubkeys(None).unwrap().len(), 1);
tree.del_script_pubkey_from_path(script_type, &path)
.unwrap();
assert_eq!(tree.iter_script_pubkeys(None).len(), 0);
assert_eq!(tree.iter_script_pubkeys(None).unwrap().len(), 0);
}
#[test]

View File

@@ -40,10 +40,10 @@ pub trait BatchOperations {
}
pub trait Database: BatchOperations {
fn iter_script_pubkeys(&self, script_type: Option<ScriptType>) -> Vec<Result<Script, Error>>;
fn iter_utxos(&self) -> Vec<Result<UTXO, Error>>;
fn iter_raw_txs(&self) -> Vec<Result<Transaction, Error>>;
fn iter_txs(&self, include_raw: bool) -> Vec<Result<TransactionDetails, Error>>;
fn iter_script_pubkeys(&self, script_type: Option<ScriptType>) -> Result<Vec<Script>, Error>;
fn iter_utxos(&self) -> Result<Vec<UTXO>, Error>;
fn iter_raw_txs(&self) -> Result<Vec<Transaction>, Error>;
fn iter_txs(&self, include_raw: bool) -> Result<Vec<TransactionDetails>, Error>;
fn get_script_pubkey_from_path<P: AsRef<[ChildNumber]>>(
&self,

View File

@@ -16,9 +16,15 @@ use serde::{Deserialize, Serialize};
pub mod error;
pub mod extended_key;
pub mod policy;
pub use self::error::Error;
pub use self::extended_key::{DerivationIndex, DescriptorExtendedKey};
pub use self::policy::{ExtractPolicy, Policy};
trait MiniscriptExtractPolicy {
fn extract_policy(&self, lookup_map: &BTreeMap<String, Box<dyn Key>>) -> Option<Policy>;
}
#[derive(Debug, Clone, Hash, PartialEq, PartialOrd, Eq, Ord, Default)]
struct DummyKey();
@@ -86,6 +92,7 @@ where
fn psbt_witness_script(&self) -> Option<Script> {
match self {
Descriptor::Wsh(ref script) => Some(script.encode()),
Descriptor::ShWsh(ref script) => Some(script.encode()),
_ => None,
}
}
@@ -261,20 +268,18 @@ impl ExtendedDescriptor {
Ok(self.internal.translate_pk(translatefpk, translatefpkh)?)
}
pub fn get_xprv(&self) -> Vec<ExtendedPrivKey> {
pub fn get_xprv(&self) -> impl IntoIterator<Item = ExtendedPrivKey> + '_ {
self.keys
.iter()
.filter(|(_, v)| v.xprv().is_some())
.map(|(_, v)| v.xprv().unwrap())
.collect()
}
pub fn get_secret_keys(&self) -> Vec<PrivateKey> {
pub fn get_secret_keys(&self) -> impl IntoIterator<Item = PrivateKey> + '_ {
self.keys
.iter()
.filter(|(_, v)| v.as_secret_key().is_some())
.map(|(_, v)| v.as_secret_key().unwrap())
.collect()
}
pub fn get_hd_keypaths(
@@ -317,6 +322,12 @@ impl ExtendedDescriptor {
}
}
impl ExtractPolicy for ExtendedDescriptor {
fn extract_policy(&self) -> Option<Policy> {
self.internal.extract_policy(&self.keys)
}
}
impl TryFrom<&str> for ExtendedDescriptor {
type Error = Error;

399
src/descriptor/policy.rs Normal file
View File

@@ -0,0 +1,399 @@
use std::collections::BTreeMap;
use serde::Serialize;
use bitcoin::hashes::*;
use bitcoin::secp256k1::Secp256k1;
use bitcoin::util::bip32::Fingerprint;
use bitcoin::PublicKey;
use miniscript::{Descriptor, Miniscript, Terminal};
use descriptor::{Key, MiniscriptExtractPolicy};
#[derive(Debug, Serialize)]
pub struct PKOrF {
#[serde(skip_serializing_if = "Option::is_none")]
pubkey: Option<PublicKey>,
#[serde(skip_serializing_if = "Option::is_none")]
fingerprint: Option<Fingerprint>,
}
impl PKOrF {
fn from_key(k: &Box<dyn Key>) -> Self {
let secp = Secp256k1::gen_new();
if let Some(fing) = k.fingerprint(&secp) {
PKOrF {
fingerprint: Some(fing),
pubkey: None,
}
} else {
PKOrF {
fingerprint: None,
pubkey: Some(k.as_public_key(&secp, None).unwrap()),
}
}
}
}
#[derive(Debug, Serialize)]
#[serde(tag = "type", rename_all = "UPPERCASE")]
pub enum SatisfiableItem {
// Leaves
Signature(PKOrF),
SignatureKey {
#[serde(skip_serializing_if = "Option::is_none")]
fingerprint: Option<Fingerprint>,
#[serde(skip_serializing_if = "Option::is_none")]
pubkey_hash: Option<hash160::Hash>,
},
SHA256Preimage {
hash: sha256::Hash,
},
HASH256Preimage {
hash: sha256d::Hash,
},
RIPEMD160Preimage {
hash: ripemd160::Hash,
},
HASH160Preimage {
hash: hash160::Hash,
},
AbsoluteTimelock {
height: u32,
},
RelativeTimelock {
blocks: u32,
},
// Complex item
Thresh {
items: Vec<Policy>,
threshold: usize,
},
Multisig {
keys: Vec<PKOrF>,
threshold: usize,
},
}
impl SatisfiableItem {
pub fn is_leaf(&self) -> bool {
match self {
SatisfiableItem::Thresh {
items: _,
threshold: _,
} => false,
_ => true,
}
}
}
#[derive(Debug, Serialize)]
pub enum ItemSatisfier {
Us,
Other(Option<Fingerprint>),
Timelock(Option<u32>), // remaining blocks. TODO: time-based timelocks
}
#[derive(Debug, Serialize)]
pub struct Policy {
#[serde(flatten)]
item: SatisfiableItem,
#[serde(skip_serializing_if = "Option::is_none")]
satisfier: Option<ItemSatisfier>,
}
#[derive(Debug, Default)]
pub struct PathRequirements {
pub csv: Option<u32>,
pub timelock: Option<u32>,
}
impl PathRequirements {
pub fn merge(&mut self, other: &Self) -> Result<(), PolicyError> {
if other.is_null() {
return Ok(());
}
match (self.csv, other.csv) {
(Some(old), Some(new)) if old != new => Err(PolicyError::DifferentCSV(old, new)),
_ => {
self.csv = self.csv.or(other.csv);
Ok(())
}
}?;
match (self.timelock, other.timelock) {
(Some(old), Some(new)) if old != new => Err(PolicyError::DifferentTimelock(old, new)),
_ => {
self.timelock = self.timelock.or(other.timelock);
Ok(())
}
}?;
Ok(())
}
pub fn is_null(&self) -> bool {
self.csv.is_none() && self.timelock.is_none()
}
}
#[derive(Debug)]
pub enum PolicyError {
NotEnoughItemsSelected(usize),
TooManyItemsSelected(usize),
IndexOutOfRange(usize, usize),
DifferentCSV(u32, u32),
DifferentTimelock(u32, u32),
}
impl Policy {
pub fn new(item: SatisfiableItem) -> Self {
Policy {
item,
satisfier: None,
}
}
pub fn make_and(a: Option<Policy>, b: Option<Policy>) -> Option<Policy> {
match (a, b) {
(None, None) => None,
(Some(x), None) | (None, Some(x)) => Some(x),
(Some(a), Some(b)) => Some(
SatisfiableItem::Thresh {
items: vec![a, b],
threshold: 2,
}
.into(),
),
}
}
pub fn make_or(a: Option<Policy>, b: Option<Policy>) -> Option<Policy> {
match (a, b) {
(None, None) => None,
(Some(x), None) | (None, Some(x)) => Some(x),
(Some(a), Some(b)) => Some(
SatisfiableItem::Thresh {
items: vec![a, b],
threshold: 1,
}
.into(),
),
}
}
pub fn make_thresh(items: Vec<Policy>, mut threshold: usize) -> Option<Policy> {
if threshold == 0 {
return None;
}
if threshold > items.len() {
threshold = items.len();
}
Some(SatisfiableItem::Thresh { items, threshold }.into())
}
fn make_multisig(pubkeys: Vec<Option<&Box<dyn Key>>>, threshold: usize) -> Option<Policy> {
let keys = pubkeys
.into_iter()
.map(|k| PKOrF::from_key(k.unwrap()))
.collect();
Some(SatisfiableItem::Multisig { keys, threshold }.into())
}
pub fn requires_path(&self) -> bool {
self.get_requirements(&vec![]).is_err()
}
pub fn get_requirements(
&self,
path: &Vec<Vec<usize>>,
) -> Result<PathRequirements, PolicyError> {
self.recursive_get_requirements(path, 0)
}
fn recursive_get_requirements(
&self,
path: &Vec<Vec<usize>>,
index: usize,
) -> Result<PathRequirements, PolicyError> {
// if items.len() == threshold, selected can be omitted and we take all of them by default
let default = match &self.item {
SatisfiableItem::Thresh { items, threshold } if items.len() == *threshold => {
(0..*threshold).into_iter().collect()
}
_ => vec![],
};
let selected = match path.get(index) {
_ if !default.is_empty() => &default,
Some(arr) => arr,
_ => &default,
};
match &self.item {
SatisfiableItem::Thresh { items, threshold } => {
let mapped_req = items
.iter()
.map(|i| i.recursive_get_requirements(path, index + 1))
.collect::<Result<Vec<_>, _>>()?;
// if all the requirements are null we don't care about `selected` because there
// are no requirements
if mapped_req.iter().all(PathRequirements::is_null) {
return Ok(PathRequirements::default());
}
// if we have something, make sure we have enough items. note that the user can set
// an empty value for this step in case of n-of-n, because `selected` is set to all
// the elements above
if selected.len() < *threshold {
return Err(PolicyError::NotEnoughItemsSelected(index));
}
// check the selected items, see if there are conflicting requirements
let mut requirements = PathRequirements::default();
for item_index in selected {
requirements.merge(
mapped_req
.get(*item_index)
.ok_or(PolicyError::IndexOutOfRange(*item_index, index))?,
)?;
}
Ok(requirements)
}
_ if !selected.is_empty() => Err(PolicyError::TooManyItemsSelected(index)),
SatisfiableItem::AbsoluteTimelock { height } => Ok(PathRequirements {
csv: None,
timelock: Some(*height),
}),
SatisfiableItem::RelativeTimelock { blocks } => Ok(PathRequirements {
csv: Some(*blocks),
timelock: None,
}),
_ => Ok(PathRequirements::default()),
}
}
}
impl From<SatisfiableItem> for Policy {
fn from(other: SatisfiableItem) -> Self {
Self::new(other)
}
}
pub trait ExtractPolicy {
fn extract_policy(&self) -> Option<Policy>;
}
fn signature_from_string(key: Option<&Box<dyn Key>>) -> Option<Policy> {
key.map(|k| SatisfiableItem::Signature(PKOrF::from_key(k)).into())
}
fn signature_key_from_string(key: Option<&Box<dyn Key>>) -> Option<Policy> {
let secp = Secp256k1::gen_new();
key.map(|k| {
if let Some(fing) = k.fingerprint(&secp) {
SatisfiableItem::SignatureKey {
fingerprint: Some(fing),
pubkey_hash: None,
}
} else {
SatisfiableItem::SignatureKey {
fingerprint: None,
pubkey_hash: Some(hash160::Hash::hash(
&k.as_public_key(&secp, None).unwrap().to_bytes(),
)),
}
}
.into()
})
}
impl MiniscriptExtractPolicy for Miniscript<String> {
fn extract_policy(&self, lookup_map: &BTreeMap<String, Box<dyn Key>>) -> Option<Policy> {
match &self.node {
// Leaves
Terminal::True | Terminal::False => None,
Terminal::Pk(pubkey) => signature_from_string(lookup_map.get(pubkey)),
Terminal::PkH(pubkey_hash) => signature_key_from_string(lookup_map.get(pubkey_hash)),
Terminal::After(height) => {
Some(SatisfiableItem::AbsoluteTimelock { height: *height }.into())
}
Terminal::Older(blocks) => {
Some(SatisfiableItem::RelativeTimelock { blocks: *blocks }.into())
}
Terminal::Sha256(hash) => Some(SatisfiableItem::SHA256Preimage { hash: *hash }.into()),
Terminal::Hash256(hash) => {
Some(SatisfiableItem::HASH256Preimage { hash: *hash }.into())
}
Terminal::Ripemd160(hash) => {
Some(SatisfiableItem::RIPEMD160Preimage { hash: *hash }.into())
}
Terminal::Hash160(hash) => {
Some(SatisfiableItem::HASH160Preimage { hash: *hash }.into())
}
// Identities
Terminal::Alt(inner)
| Terminal::Swap(inner)
| Terminal::Check(inner)
| Terminal::DupIf(inner)
| Terminal::Verify(inner)
| Terminal::NonZero(inner)
| Terminal::ZeroNotEqual(inner) => inner.extract_policy(lookup_map),
// Complex policies
Terminal::AndV(a, b) | Terminal::AndB(a, b) => {
Policy::make_and(a.extract_policy(lookup_map), b.extract_policy(lookup_map))
}
Terminal::AndOr(x, y, z) => Policy::make_or(
Policy::make_and(x.extract_policy(lookup_map), y.extract_policy(lookup_map)),
z.extract_policy(lookup_map),
),
Terminal::OrB(a, b)
| Terminal::OrD(a, b)
| Terminal::OrC(a, b)
| Terminal::OrI(a, b) => {
Policy::make_or(a.extract_policy(lookup_map), b.extract_policy(lookup_map))
}
Terminal::Thresh(k, nodes) => {
let mut threshold = *k;
let mapped: Vec<_> = nodes
.iter()
.filter_map(|n| n.extract_policy(lookup_map))
.collect();
if mapped.len() < nodes.len() {
threshold = match threshold.checked_sub(nodes.len() - mapped.len()) {
None => return None,
Some(x) => x,
};
}
Policy::make_thresh(mapped, threshold)
}
Terminal::ThreshM(k, pks) => {
Policy::make_multisig(pks.iter().map(|s| lookup_map.get(s)).collect(), *k)
}
}
}
}
impl MiniscriptExtractPolicy for Descriptor<String> {
fn extract_policy(&self, lookup_map: &BTreeMap<String, Box<dyn Key>>) -> Option<Policy> {
match self {
Descriptor::Pk(pubkey)
| Descriptor::Pkh(pubkey)
| Descriptor::Wpkh(pubkey)
| Descriptor::ShWpkh(pubkey) => signature_from_string(lookup_map.get(pubkey)),
Descriptor::Bare(inner)
| Descriptor::Sh(inner)
| Descriptor::Wsh(inner)
| Descriptor::ShWsh(inner) => inner.extract_policy(lookup_map),
}
}
}

View File

@@ -1,15 +1,40 @@
use bitcoin::{OutPoint, Script, Txid};
#[derive(Debug)]
pub enum Error {
KeyMismatch(bitcoin::secp256k1::PublicKey, bitcoin::secp256k1::PublicKey),
MissingInputUTXO(usize),
InvalidU32Bytes(Vec<u8>),
Generic(String),
ScriptDoesntHaveAddressForm,
SendAllMultipleOutputs,
OutputBelowDustLimit(usize),
InsufficientFunds,
UnknownUTXO,
DifferentTransactions,
SpendingPolicyRequired,
InvalidPolicyPathError(crate::descriptor::policy::PolicyError),
// Signing errors (expected, received)
InputTxidMismatch((Txid, OutPoint)),
InputRedeemScriptMismatch((Script, Script)), // scriptPubKey, redeemScript
InputWitnessScriptMismatch((Script, Script)), // scriptPubKey, redeemScript
InputUnknownSegwitScript(Script),
InputMissingWitnessScript(usize),
MissingUTXO,
Descriptor(crate::descriptor::error::Error),
Encode(bitcoin::consensus::encode::Error),
BIP32(bitcoin::util::bip32::Error),
Secp256k1(bitcoin::secp256k1::Error),
JSON(serde_json::Error),
Hex(bitcoin::hashes::hex::Error),
PSBT(bitcoin::util::psbt::Error),
#[cfg(any(feature = "electrum", feature = "default"))]
Electrum(electrum_client::Error),
#[cfg(any(feature = "key-value-db", feature = "default"))]
Sled(sled::Error),
}
@@ -24,10 +49,20 @@ macro_rules! impl_error {
};
}
impl_error!(crate::descriptor::error::Error, Descriptor);
impl_error!(
crate::descriptor::policy::PolicyError,
InvalidPolicyPathError
);
impl_error!(bitcoin::consensus::encode::Error, Encode);
impl_error!(bitcoin::util::bip32::Error, BIP32);
impl_error!(bitcoin::secp256k1::Error, Secp256k1);
impl_error!(serde_json::Error, JSON);
impl_error!(bitcoin::hashes::hex::Error, Hex);
impl_error!(bitcoin::util::psbt::Error, PSBT);
#[cfg(any(feature = "electrum", feature = "default"))]
impl_error!(electrum_client::Error, Electrum);
#[cfg(any(feature = "key-value-db", feature = "default"))]
impl_error!(sled::Error, Sled);

View File

@@ -9,8 +9,12 @@ extern crate serde_json;
#[macro_use]
extern crate lazy_static;
#[cfg(any(feature = "electrum", feature = "default"))]
pub extern crate electrum_client;
#[cfg(any(feature = "electrum", feature = "default"))]
pub use electrum_client::client::Client;
#[cfg(any(feature = "key-value-db", feature = "default"))]
extern crate sled;
pub extern crate sled;
#[macro_use]
pub mod error;
@@ -19,3 +23,7 @@ pub mod descriptor;
pub mod psbt;
pub mod signer;
pub mod types;
pub mod wallet;
pub use descriptor::ExtendedDescriptor;
pub use wallet::Wallet;

View File

@@ -1,6 +1,6 @@
use std::collections::BTreeMap;
use bitcoin::hashes::Hash;
use bitcoin::hashes::{hash160, Hash};
use bitcoin::util::bip143::SighashComponents;
use bitcoin::util::bip32::{DerivationPath, ExtendedPrivKey, Fingerprint};
use bitcoin::util::psbt;
@@ -8,7 +8,10 @@ use bitcoin::{PrivateKey, PublicKey, Script, SigHashType, Transaction};
use bitcoin::secp256k1::{self, All, Message, Secp256k1};
use miniscript::{BitcoinSig, Satisfier};
#[allow(unused_imports)]
use log::{debug, error, info, trace};
use miniscript::{BitcoinSig, MiniscriptKey, Satisfier};
use crate::descriptor::ExtendedDescriptor;
use crate::error::Error;
@@ -34,29 +37,70 @@ impl<'a> PSBTSatisfier<'a> {
}
}
impl<'a> PSBTSatisfier<'a> {
fn parse_sig(rawsig: &Vec<u8>) -> Option<BitcoinSig> {
let (flag, sig) = rawsig.split_last().unwrap();
let flag = bitcoin::SigHashType::from_u32(*flag as u32);
let sig = match secp256k1::Signature::from_der(sig) {
Ok(sig) => sig,
Err(..) => return None,
};
Some((sig, flag))
}
}
// TODO: also support hash preimages through the "unknown" section of PSBT
impl<'a> Satisfier<bitcoin::PublicKey> for PSBTSatisfier<'a> {
// from https://docs.rs/miniscript/0.12.0/src/miniscript/psbt/mod.rs.html#96
fn lookup_sig(&self, pk: &bitcoin::PublicKey) -> Option<BitcoinSig> {
debug!("lookup_sig: {}", pk);
if let Some(rawsig) = self.input.partial_sigs.get(pk) {
let (flag, sig) = rawsig.split_last().unwrap();
let flag = bitcoin::SigHashType::from_u32(*flag as u32);
let sig = match secp256k1::Signature::from_der(sig) {
Ok(sig) => sig,
Err(..) => return None,
};
Some((sig, flag))
Self::parse_sig(&rawsig)
} else {
None
}
}
fn lookup_pkh_pk(&self, hash: &hash160::Hash) -> Option<bitcoin::PublicKey> {
debug!("lookup_pkh_pk: {}", hash);
for (pk, _) in &self.input.partial_sigs {
if &pk.to_pubkeyhash() == hash {
return Some(*pk);
}
}
None
}
fn lookup_pkh_sig(&self, hash: &hash160::Hash) -> Option<(bitcoin::PublicKey, BitcoinSig)> {
debug!("lookup_pkh_sig: {}", hash);
for (pk, sig) in &self.input.partial_sigs {
if &pk.to_pubkeyhash() == hash {
return match Self::parse_sig(&sig) {
Some(bitcoinsig) => Some((*pk, bitcoinsig)),
None => None,
};
}
}
None
}
fn check_older(&self, height: u32) -> bool {
// TODO: also check if `nSequence` right
debug!("check_older: {}", height);
// TODO: test >= / >
self.current_height.unwrap_or(0) >= self.create_height.unwrap_or(0) + height
}
fn check_after(&self, height: u32) -> bool {
// TODO: also check if `nLockTime` is right
debug!("check_older: {}", height);
self.current_height.unwrap_or(0) > height
}
}
@@ -94,6 +138,22 @@ impl<'a> PSBTSigner<'a> {
private_keys,
})
}
pub fn extend(&mut self, mut other: PSBTSigner) -> Result<(), Error> {
if self.tx.txid() != other.tx.txid() {
return Err(Error::DifferentTransactions);
}
self.extended_keys.append(&mut other.extended_keys);
self.private_keys.append(&mut other.private_keys);
Ok(())
}
// TODO: temporary
pub fn all_public_keys(&self) -> impl IntoIterator<Item = &PublicKey> {
self.private_keys.keys()
}
}
impl<'a> Signer for PSBTSigner<'a> {

View File

@@ -36,7 +36,7 @@ pub struct UTXO {
pub txout: TxOut,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
pub struct TransactionDetails {
pub transaction: Option<Transaction>,
pub txid: Txid,

1039
src/wallet/mod.rs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,52 @@
use std::io::{self, Error, ErrorKind, Read, Write};
#[derive(Clone, Debug)]
pub struct OfflineStream {}
impl Read for OfflineStream {
fn read(&mut self, _buf: &mut [u8]) -> io::Result<usize> {
Err(Error::new(
ErrorKind::NotConnected,
"Trying to read from an OfflineStream",
))
}
}
impl Write for OfflineStream {
fn write(&mut self, _buf: &[u8]) -> io::Result<usize> {
Err(Error::new(
ErrorKind::NotConnected,
"Trying to read from an OfflineStream",
))
}
fn flush(&mut self) -> io::Result<()> {
Err(Error::new(
ErrorKind::NotConnected,
"Trying to read from an OfflineStream",
))
}
}
// #[cfg(any(feature = "electrum", feature = "default"))]
// use electrum_client::Client;
//
// #[cfg(any(feature = "electrum", feature = "default"))]
// impl OfflineStream {
// fn new_client() -> {
// use std::io::bufreader;
//
// let stream = OfflineStream{};
// let buf_reader = BufReader::new(stream.clone());
//
// Client {
// stream,
// buf_reader,
// headers: VecDeque::new(),
// script_notifications: BTreeMap::new(),
//
// #[cfg(feature = "debug-calls")]
// calls: 0,
// }
// }
// }

48
src/wallet/utils.rs Normal file
View File

@@ -0,0 +1,48 @@
// De-facto standard "dust limit" (even though it should change based on the output type)
const DUST_LIMIT_SATOSHI: u64 = 546;
// we implement this trait to make sure we don't mess up the comparison with off-by-one like a <
// instead of a <= etc. The constant value for the dust limit is not public on purpose, to
// encourage the usage of this trait.
pub trait IsDust {
fn is_dust(&self) -> bool;
}
impl IsDust for u64 {
fn is_dust(&self) -> bool {
*self <= DUST_LIMIT_SATOSHI
}
}
pub struct ChunksIterator<I: Iterator> {
iter: I,
size: usize,
}
impl<I: Iterator> ChunksIterator<I> {
pub fn new(iter: I, size: usize) -> Self {
ChunksIterator { iter, size }
}
}
impl<I: Iterator> Iterator for ChunksIterator<I> {
type Item = Vec<<I as std::iter::Iterator>::Item>;
fn next(&mut self) -> Option<Self::Item> {
let mut v = Vec::new();
for _ in 0..self.size {
let e = self.iter.next();
match e {
None => break,
Some(val) => v.push(val),
}
}
if v.is_empty() {
return None;
}
Some(v)
}
}