Compare commits

...

30 Commits

Author SHA1 Message Date
Steve Myers
c175dd2aae Bump version to 0.14.0 2021-11-27 21:07:12 -08:00
Steve Myers
6b1cbcc4b7 Bump version to 0.14.0-rc.1 2021-11-17 11:54:09 -08:00
Steve Myers
afa1ab4ff8 Fix blockchain_tests::test_send_to_bech32m_addr
Now works with latest released versions of rust-bitcoincore-rpc and
bitcoind. Once these crates are updated to support creating descriptor
wallets and add importdescriptors and bech32m support this test will
need to be updated.
2021-11-11 13:59:11 -08:00
Sandipan Dey
632422a3ab Added wallet blockchain test to send to Bech32m address 2021-11-11 08:20:40 -08:00
Sandipan Dey
54f61d17f2 Added a wallet unit test to send to a Bech32m address 2021-11-11 08:20:38 -08:00
Alekos Filini
5830226216 [database] Wrap BlockTime in another struct to allow adding more
fields in the future
2021-11-10 12:30:42 +01:00
Alekos Filini
2c77329333 Rename ConfirmationTime to BlockTime 2021-11-10 12:30:38 +01:00
Alekos Filini
3e5bb077ac Update CHANGELOG.md 2021-11-10 12:30:33 +01:00
Alekos Filini
7c06f52a07 [wallet] Store the block height and timestamp after syncing
Closes #455
2021-11-10 12:30:02 +01:00
Alekos Filini
12e51b3c06 [wallet] Expose an immutable reference to a wallet's database 2021-11-10 12:29:58 +01:00
Alekos Filini
2892edf94b [db] Add the last_sync_time database entry
This will be used to store the height and timestamp after every sync.
2021-11-10 12:29:47 +01:00
Riccardo Casatta
aa075f0b2f fix after merge changing borrow of tx in broadcast 2021-11-09 15:37:18 +01:00
Riccardo Casatta
8010d692e9 Update CHANGELOG 2021-11-09 15:37:13 +01:00
Riccardo Casatta
b2d7412d6d add test for add_data 2021-11-09 15:36:42 +01:00
Riccardo Casatta
fd51029197 add method add_data as a shortcut to create an OP_RETURN output, fix the dust check to consider only spendable output 2021-11-09 15:36:39 +01:00
Alekos Filini
711510006b Merge commit 'refs/pull/464/head' of github.com:bitcoindevkit/bdk 2021-11-08 10:41:12 +01:00
Alekos Filini
d21b6e47ab Merge commit 'refs/pull/458/head' of github.com:bitcoindevkit/bdk 2021-11-08 10:39:50 +01:00
rajarshimaitra
5922c216a1 Update WordsCount -> WordCount 2021-11-06 20:14:03 +05:30
rajarshimaitra
9e29e2d2b1 Update changelog 2021-11-06 20:13:45 +05:30
Alekos Filini
16e832533c Merge commit 'refs/pull/462/head' of github.com:bitcoindevkit/bdk 2021-11-04 15:26:15 +00:00
Steve Myers
7f91bcdf1a Merge commit 'refs/pull/453/head' of github.com:bitcoindevkit/bdk 2021-11-03 13:51:59 -07:00
rajarshimaitra
35695d8795 remove redundant backtrace dependency 2021-11-03 11:14:14 +05:30
rajarshimaitra
756858e882 update module doc 2021-11-03 11:14:13 +05:30
rajarshimaitra
d2ce2714f2 Replace tiny-bip39 with rust-bip39
Use rust-bip39 for mnemonic derivation everywhere.

This requires our own WordCount enum as rust-bip39 doesn't have
explicit mnemonic type definition.
2021-11-03 11:14:05 +05:30
rajarshimaitra
3b2b559910 Update codecov@v2 2021-11-02 15:21:37 +05:30
rajarshimaitra
3c8416bf31 update dependency
dependency updated from tiny-bip39 to rust-bip39
2021-10-31 20:29:11 +05:30
Mariusz Klochowicz
3d8efbf8bf Borrow instead of moving transaction when broadcasting
There's no need to take ownership of the transaction for a broadcast.
2021-10-27 21:51:55 +10:30
Alekos Filini
d75d221540 Pin version of ureq to maintain our MSRV 2021-10-22 15:57:40 +02:00
Alekos Filini
548e43d928 Pin version of backtrace to maintain our MSRV 2021-10-22 15:57:36 +02:00
Alekos Filini
a348dbdcfe [ci] Update the stable version to 1.56 2021-10-22 15:57:27 +02:00
19 changed files with 444 additions and 79 deletions

View File

@@ -31,7 +31,7 @@ jobs:
uses: actions-rs/grcov@v0.1.5
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v1
uses: codecov/codecov-action@v2
with:
file: ${{ steps.coverage.outputs.report }}
directory: ./coverage/reports/

View File

@@ -6,6 +6,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased]
## [v0.14.0] - [v0.13.0]
- BIP39 implementation dependency, in `keys::bip39` changed from tiny-bip39 to rust-bip39.
- Add new method on the `TxBuilder` to embed data in the transaction via `OP_RETURN`. To allow that a fix to check the dust only on spendable output has been introduced.
- Update the `Database` trait to store the last sync timestamp and block height
- Rename `ConfirmationTime` to `BlockTime`
## [v0.13.0] - [v0.12.0]
- Exposed `get_tx()` method from `Database` to `Wallet`.
@@ -388,4 +395,5 @@ final transaction is created by calling `finish` on the builder.
[v0.10.0]: https://github.com/bitcoindevkit/bdk/compare/v0.9.0...v0.10.0
[v0.11.0]: https://github.com/bitcoindevkit/bdk/compare/v0.10.0...v0.11.0
[v0.12.0]: https://github.com/bitcoindevkit/bdk/compare/v0.11.0...v0.12.0
[v0.13.0]: https://github.com/bitcoindevkit/bdk/compare/v0.12.0...v0.13.0
[v0.13.0]: https://github.com/bitcoindevkit/bdk/compare/v0.12.0...v0.13.0
[v0.14.0]: https://github.com/bitcoindevkit/bdk/compare/v0.13.0...v0.14.0

View File

@@ -1,6 +1,6 @@
[package]
name = "bdk"
version = "0.13.1-dev"
version = "0.14.0"
edition = "2018"
authors = ["Alekos Filini <alekos.filini@gmail.com>", "Riccardo Casatta <riccardo@casatta.it>"]
homepage = "https://bitcoindevkit.org"
@@ -34,14 +34,7 @@ cc = { version = ">=1.0.64", optional = true }
socks = { version = "0.3", optional = true }
lazy_static = { version = "1.4", optional = true }
# the latest 0.8 version of tiny-bip39 depends on zeroize_derive 1.2 which has MSRV 1.51 and our
# MSRV is 1.46, to fix this until we update our MSRV or replace the tiny-bip39
# dependency https://github.com/bitcoindevkit/bdk/issues/399 we can only use an older version
tiny-bip39 = { version = "< 0.8", optional = true }
# backtrace > 0.3.61 includes object v0.27 which doesn't compile on 1.46. this is used by
# tiny-bip39
backtrace = { version = "=0.3.61", optional = true }
bip39 = { version = "1.0.1", optional = true }
bitcoinconsensus = { version = "0.19.0-3", optional = true }
# Needed by bdk_blockchain_tests macro
@@ -65,7 +58,7 @@ sqlite = ["rusqlite", "ahash"]
compact_filters = ["rocksdb", "socks", "lazy_static", "cc"]
key-value-db = ["sled"]
all-keys = ["keys-bip39"]
keys-bip39 = ["tiny-bip39", "backtrace"]
keys-bip39 = ["bip39"]
rpc = ["bitcoincore-rpc"]
# We currently provide mulitple implementations of `Blockchain`, all are
@@ -101,7 +94,7 @@ test-md-docs = ["electrum"]
lazy_static = "1.4"
env_logger = "0.7"
clap = "2.33"
electrsd = { version= "0.12", features = ["trigger", "bitcoind_0_21_1"] }
electrsd = { version= "0.12", features = ["trigger", "bitcoind_22_0"] }
[[example]]
name = "address_validator"

View File

@@ -71,7 +71,7 @@ use super::{Blockchain, Capability, ConfigurableBlockchain, Progress};
use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils};
use crate::error::Error;
use crate::types::{KeychainKind, LocalUtxo, TransactionDetails};
use crate::{ConfirmationTime, FeeRate};
use crate::{BlockTime, FeeRate};
use peer::*;
use store::*;
@@ -206,7 +206,7 @@ impl CompactFiltersBlockchain {
transaction: Some(tx.clone()),
received: incoming,
sent: outgoing,
confirmation_time: ConfirmationTime::new(height, timestamp),
confirmation_time: BlockTime::new(height, timestamp),
verified: height.is_some(),
fee: Some(inputs_sum.saturating_sub(outputs_sum)),
};

View File

@@ -37,7 +37,7 @@ use crate::blockchain::{Blockchain, Capability, ConfigurableBlockchain, Progress
use crate::database::{BatchDatabase, DatabaseUtils};
use crate::descriptor::{get_checksum, IntoWalletDescriptor};
use crate::wallet::utils::SecpCtx;
use crate::{ConfirmationTime, Error, FeeRate, KeychainKind, LocalUtxo, TransactionDetails};
use crate::{BlockTime, Error, FeeRate, KeychainKind, LocalUtxo, TransactionDetails};
use bitcoincore_rpc::json::{
GetAddressInfoResultLabel, ImportMultiOptions, ImportMultiRequest,
ImportMultiRequestScriptPubkey, ImportMultiRescanSince,
@@ -230,7 +230,7 @@ impl Blockchain for RpcBlockchain {
list_txs_ids.insert(txid);
if let Some(mut known_tx) = known_txs.get_mut(&txid) {
let confirmation_time =
ConfirmationTime::new(tx_result.info.blockheight, tx_result.info.blocktime);
BlockTime::new(tx_result.info.blockheight, tx_result.info.blocktime);
if confirmation_time != known_tx.confirmation_time {
// reorg may change tx height
debug!(
@@ -266,7 +266,7 @@ impl Blockchain for RpcBlockchain {
let td = TransactionDetails {
transaction: Some(tx),
txid: tx_result.info.txid,
confirmation_time: ConfirmationTime::new(
confirmation_time: BlockTime::new(
tx_result.info.blockheight,
tx_result.info.blocktime,
),

View File

@@ -21,7 +21,7 @@ use bitcoin::{BlockHeader, OutPoint, Script, Transaction, Txid};
use super::*;
use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils};
use crate::error::Error;
use crate::types::{ConfirmationTime, KeychainKind, LocalUtxo, TransactionDetails};
use crate::types::{BlockTime, KeychainKind, LocalUtxo, TransactionDetails};
use crate::wallet::time::Instant;
use crate::wallet::utils::ChunksIterator;
@@ -151,7 +151,7 @@ pub trait ElectrumLikeSync {
// check if tx height matches, otherwise updates it. timestamp is not in the if clause
// because we are not asking headers for confirmed tx we know about
if tx_details.confirmation_time.as_ref().map(|c| c.height) != height {
let confirmation_time = ConfirmationTime::new(height, timestamp);
let confirmation_time = BlockTime::new(height, timestamp);
let mut new_tx_details = tx_details.clone();
new_tx_details.confirmation_time = confirmation_time;
batch.set_tx(&new_tx_details)?;
@@ -359,7 +359,7 @@ fn save_transaction_details_and_utxos<D: BatchDatabase>(
transaction: Some(tx),
received: incoming,
sent: outgoing,
confirmation_time: ConfirmationTime::new(height, timestamp),
confirmation_time: BlockTime::new(height, timestamp),
fee: Some(inputs_sum.saturating_sub(outputs_sum)), /* if the tx is a coinbase, fees would be negative */
verified: height.is_some(),
};

View File

@@ -144,6 +144,9 @@ impl BatchOperations for AnyDatabase {
fn set_last_index(&mut self, keychain: KeychainKind, value: u32) -> Result<(), Error> {
impl_inner_method!(AnyDatabase, self, set_last_index, keychain, value)
}
fn set_sync_time(&mut self, sync_time: SyncTime) -> Result<(), Error> {
impl_inner_method!(AnyDatabase, self, set_sync_time, sync_time)
}
fn del_script_pubkey_from_path(
&mut self,
@@ -180,6 +183,9 @@ impl BatchOperations for AnyDatabase {
fn del_last_index(&mut self, keychain: KeychainKind) -> Result<Option<u32>, Error> {
impl_inner_method!(AnyDatabase, self, del_last_index, keychain)
}
fn del_sync_time(&mut self) -> Result<Option<SyncTime>, Error> {
impl_inner_method!(AnyDatabase, self, del_sync_time)
}
}
impl Database for AnyDatabase {
@@ -241,6 +247,9 @@ impl Database for AnyDatabase {
fn get_last_index(&self, keychain: KeychainKind) -> Result<Option<u32>, Error> {
impl_inner_method!(AnyDatabase, self, get_last_index, keychain)
}
fn get_sync_time(&self) -> Result<Option<SyncTime>, Error> {
impl_inner_method!(AnyDatabase, self, get_sync_time)
}
fn increment_last_index(&mut self, keychain: KeychainKind) -> Result<u32, Error> {
impl_inner_method!(AnyDatabase, self, increment_last_index, keychain)
@@ -272,6 +281,9 @@ impl BatchOperations for AnyBatch {
fn set_last_index(&mut self, keychain: KeychainKind, value: u32) -> Result<(), Error> {
impl_inner_method!(AnyBatch, self, set_last_index, keychain, value)
}
fn set_sync_time(&mut self, sync_time: SyncTime) -> Result<(), Error> {
impl_inner_method!(AnyBatch, self, set_sync_time, sync_time)
}
fn del_script_pubkey_from_path(
&mut self,
@@ -302,6 +314,9 @@ impl BatchOperations for AnyBatch {
fn del_last_index(&mut self, keychain: KeychainKind) -> Result<Option<u32>, Error> {
impl_inner_method!(AnyBatch, self, del_last_index, keychain)
}
fn del_sync_time(&mut self) -> Result<Option<SyncTime>, Error> {
impl_inner_method!(AnyBatch, self, del_sync_time)
}
}
impl BatchDatabase for AnyDatabase {

View File

@@ -18,7 +18,7 @@ use bitcoin::hash_types::Txid;
use bitcoin::{OutPoint, Script, Transaction};
use crate::database::memory::MapKey;
use crate::database::{BatchDatabase, BatchOperations, Database};
use crate::database::{BatchDatabase, BatchOperations, Database, SyncTime};
use crate::error::Error;
use crate::types::*;
@@ -82,6 +82,13 @@ macro_rules! impl_batch_operations {
Ok(())
}
fn set_sync_time(&mut self, data: SyncTime) -> Result<(), Error> {
let key = MapKey::SyncTime.as_map_key();
self.insert(key, serde_json::to_vec(&data)?)$($after_insert)*;
Ok(())
}
fn del_script_pubkey_from_path(&mut self, keychain: KeychainKind, path: u32) -> Result<Option<Script>, Error> {
let key = MapKey::Path((Some(keychain), Some(path))).as_map_key();
let res = self.remove(key);
@@ -168,6 +175,14 @@ macro_rules! impl_batch_operations {
}
}
}
fn del_sync_time(&mut self) -> Result<Option<SyncTime>, Error> {
let key = MapKey::SyncTime.as_map_key();
let res = self.remove(key);
let res = $process_delete!(res);
Ok(res.map(|b| serde_json::from_slice(&b)).transpose()?)
}
}
}
@@ -342,6 +357,14 @@ impl Database for Tree {
.transpose()
}
fn get_sync_time(&self) -> Result<Option<SyncTime>, Error> {
let key = MapKey::SyncTime.as_map_key();
Ok(self
.get(key)?
.map(|b| serde_json::from_slice(&b))
.transpose()?)
}
// inserts 0 if not present
fn increment_last_index(&mut self, keychain: KeychainKind) -> Result<u32, Error> {
let key = MapKey::LastIndex(keychain).as_map_key();
@@ -470,4 +493,9 @@ mod test {
fn test_last_index() {
crate::database::test::test_last_index(get_tree());
}
#[test]
fn test_sync_time() {
crate::database::test::test_sync_time(get_tree());
}
}

View File

@@ -22,7 +22,7 @@ use bitcoin::consensus::encode::{deserialize, serialize};
use bitcoin::hash_types::Txid;
use bitcoin::{OutPoint, Script, Transaction};
use crate::database::{BatchDatabase, BatchOperations, ConfigurableDatabase, Database};
use crate::database::{BatchDatabase, BatchOperations, ConfigurableDatabase, Database, SyncTime};
use crate::error::Error;
use crate::types::*;
@@ -33,6 +33,7 @@ use crate::types::*;
// transactions t<txid> -> tx details
// deriv indexes c{i,e} -> u32
// descriptor checksum d{i,e} -> vec<u8>
// last sync time l -> { height, timestamp }
pub(crate) enum MapKey<'a> {
Path((Option<KeychainKind>, Option<u32>)),
@@ -41,6 +42,7 @@ pub(crate) enum MapKey<'a> {
RawTx(Option<&'a Txid>),
Transaction(Option<&'a Txid>),
LastIndex(KeychainKind),
SyncTime,
DescriptorChecksum(KeychainKind),
}
@@ -59,6 +61,7 @@ impl MapKey<'_> {
MapKey::RawTx(_) => b"r".to_vec(),
MapKey::Transaction(_) => b"t".to_vec(),
MapKey::LastIndex(st) => [b"c", st.as_ref()].concat(),
MapKey::SyncTime => b"l".to_vec(),
MapKey::DescriptorChecksum(st) => [b"d", st.as_ref()].concat(),
}
}
@@ -180,6 +183,12 @@ impl BatchOperations for MemoryDatabase {
Ok(())
}
fn set_sync_time(&mut self, data: SyncTime) -> Result<(), Error> {
let key = MapKey::SyncTime.as_map_key();
self.map.insert(key, Box::new(data));
Ok(())
}
fn del_script_pubkey_from_path(
&mut self,
@@ -270,6 +279,13 @@ impl BatchOperations for MemoryDatabase {
Some(b) => Ok(Some(*b.downcast_ref().unwrap())),
}
}
fn del_sync_time(&mut self) -> Result<Option<SyncTime>, Error> {
let key = MapKey::SyncTime.as_map_key();
let res = self.map.remove(&key);
self.deleted_keys.push(key);
Ok(res.map(|b| b.downcast_ref().cloned().unwrap()))
}
}
impl Database for MemoryDatabase {
@@ -407,6 +423,14 @@ impl Database for MemoryDatabase {
Ok(self.map.get(&key).map(|b| *b.downcast_ref().unwrap()))
}
fn get_sync_time(&self) -> Result<Option<SyncTime>, Error> {
let key = MapKey::SyncTime.as_map_key();
Ok(self
.map
.get(&key)
.map(|b| b.downcast_ref().cloned().unwrap()))
}
// inserts 0 if not present
fn increment_last_index(&mut self, keychain: KeychainKind) -> Result<u32, Error> {
let key = MapKey::LastIndex(keychain).as_map_key();
@@ -479,12 +503,10 @@ macro_rules! populate_test_db {
};
let txid = tx.txid();
let confirmation_time = tx_meta
.min_confirmations
.map(|conf| $crate::ConfirmationTime {
height: current_height.unwrap().checked_sub(conf as u32).unwrap(),
timestamp: 0,
});
let confirmation_time = tx_meta.min_confirmations.map(|conf| $crate::BlockTime {
height: current_height.unwrap().checked_sub(conf as u32).unwrap(),
timestamp: 0,
});
let tx_details = $crate::TransactionDetails {
transaction: Some(tx.clone()),
@@ -590,4 +612,9 @@ mod test {
fn test_last_index() {
crate::database::test::test_last_index(get_tree());
}
#[test]
fn test_sync_time() {
crate::database::test::test_sync_time(get_tree());
}
}

View File

@@ -24,6 +24,8 @@
//!
//! [`Wallet`]: crate::wallet::Wallet
use serde::{Deserialize, Serialize};
use bitcoin::hash_types::Txid;
use bitcoin::{OutPoint, Script, Transaction, TxOut};
@@ -44,6 +46,15 @@ pub use sqlite::SqliteDatabase;
pub mod memory;
pub use memory::MemoryDatabase;
/// Blockchain state at the time of syncing
///
/// Contains only the block time and height at the moment
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct SyncTime {
/// Block timestamp and height at the time of sync
pub block_time: BlockTime,
}
/// Trait for operations that can be batched
///
/// This trait defines the list of operations that must be implemented on the [`Database`] type and
@@ -64,6 +75,8 @@ pub trait BatchOperations {
fn set_tx(&mut self, transaction: &TransactionDetails) -> Result<(), Error>;
/// Store the last derivation index for a given keychain.
fn set_last_index(&mut self, keychain: KeychainKind, value: u32) -> Result<(), Error>;
/// Store the sync time
fn set_sync_time(&mut self, sync_time: SyncTime) -> Result<(), Error>;
/// Delete a script_pubkey given the keychain and its child number.
fn del_script_pubkey_from_path(
@@ -89,6 +102,10 @@ pub trait BatchOperations {
) -> Result<Option<TransactionDetails>, Error>;
/// Delete the last derivation index for a keychain.
fn del_last_index(&mut self, keychain: KeychainKind) -> Result<Option<u32>, Error>;
/// Reset the sync time to `None`
///
/// Returns the removed value
fn del_sync_time(&mut self) -> Result<Option<SyncTime>, Error>;
}
/// Trait for reading data from a database
@@ -134,6 +151,8 @@ pub trait Database: BatchOperations {
fn get_tx(&self, txid: &Txid, include_raw: bool) -> Result<Option<TransactionDetails>, Error>;
/// Return the last defivation index for a keychain.
fn get_last_index(&self, keychain: KeychainKind) -> Result<Option<u32>, Error>;
/// Return the sync time, if present
fn get_sync_time(&self) -> Result<Option<SyncTime>, Error>;
/// Increment the last derivation index for a keychain and return it
///
@@ -325,7 +344,7 @@ pub mod test {
received: 1337,
sent: 420420,
fee: Some(140),
confirmation_time: Some(ConfirmationTime {
confirmation_time: Some(BlockTime {
timestamp: 123456,
height: 1000,
}),
@@ -377,5 +396,25 @@ pub mod test {
);
}
pub fn test_sync_time<D: Database>(mut tree: D) {
assert!(tree.get_sync_time().unwrap().is_none());
tree.set_sync_time(SyncTime {
block_time: BlockTime {
height: 100,
timestamp: 1000,
},
})
.unwrap();
let extracted = tree.get_sync_time().unwrap();
assert!(extracted.is_some());
assert_eq!(extracted.as_ref().unwrap().block_time.height, 100);
assert_eq!(extracted.as_ref().unwrap().block_time.timestamp, 1000);
tree.del_sync_time().unwrap();
assert!(tree.get_sync_time().unwrap().is_none());
}
// TODO: more tests...
}

View File

@@ -13,7 +13,7 @@ use bitcoin::consensus::encode::{deserialize, serialize};
use bitcoin::hash_types::Txid;
use bitcoin::{OutPoint, Script, Transaction, TxOut};
use crate::database::{BatchDatabase, BatchOperations, Database};
use crate::database::{BatchDatabase, BatchOperations, Database, SyncTime};
use crate::error::Error;
use crate::types::*;
@@ -35,6 +35,7 @@ static MIGRATIONS: &[&str] = &[
"CREATE UNIQUE INDEX idx_indices_keychain ON last_derivation_indices(keychain);",
"CREATE TABLE checksums (keychain TEXT, checksum BLOB);",
"CREATE INDEX idx_checksums_keychain ON checksums(keychain);",
"CREATE TABLE sync_time (id INTEGER PRIMARY KEY, height INTEGER, timestamp INTEGER);"
];
/// Sqlite database stored on filesystem
@@ -205,6 +206,19 @@ impl SqliteDatabase {
Ok(())
}
fn update_sync_time(&self, data: SyncTime) -> Result<i64, Error> {
let mut statement = self.connection.prepare_cached(
"INSERT INTO sync_time (id, height, timestamp) VALUES (0, :height, :timestamp) ON CONFLICT(id) DO UPDATE SET height=:height, timestamp=:timestamp WHERE id = 0",
)?;
statement.execute(named_params! {
":height": data.block_time.height,
":timestamp": data.block_time.timestamp,
})?;
Ok(self.connection.last_insert_rowid())
}
fn select_script_pubkeys(&self) -> Result<Vec<Script>, Error> {
let mut statement = self
.connection
@@ -375,7 +389,7 @@ impl SqliteDatabase {
};
let confirmation_time = match (height, timestamp) {
(Some(height), Some(timestamp)) => Some(ConfirmationTime { height, timestamp }),
(Some(height), Some(timestamp)) => Some(BlockTime { height, timestamp }),
_ => None,
};
@@ -409,7 +423,7 @@ impl SqliteDatabase {
let verified: bool = row.get(6)?;
let confirmation_time = match (height, timestamp) {
(Some(height), Some(timestamp)) => Some(ConfirmationTime { height, timestamp }),
(Some(height), Some(timestamp)) => Some(BlockTime { height, timestamp }),
_ => None,
};
@@ -452,7 +466,7 @@ impl SqliteDatabase {
};
let confirmation_time = match (height, timestamp) {
(Some(height), Some(timestamp)) => Some(ConfirmationTime { height, timestamp }),
(Some(height), Some(timestamp)) => Some(BlockTime { height, timestamp }),
_ => None,
};
@@ -487,6 +501,24 @@ impl SqliteDatabase {
}
}
fn select_sync_time(&self) -> Result<Option<SyncTime>, Error> {
let mut statement = self
.connection
.prepare_cached("SELECT height, timestamp FROM sync_time WHERE id = 0")?;
let mut rows = statement.query([])?;
if let Some(row) = rows.next()? {
Ok(Some(SyncTime {
block_time: BlockTime {
height: row.get(0)?,
timestamp: row.get(1)?,
},
}))
} else {
Ok(None)
}
}
fn select_checksum_by_keychain(&self, keychain: String) -> Result<Option<Vec<u8>>, Error> {
let mut statement = self
.connection
@@ -563,6 +595,14 @@ impl SqliteDatabase {
Ok(())
}
fn delete_sync_time(&self) -> Result<(), Error> {
let mut statement = self
.connection
.prepare_cached("DELETE FROM sync_time WHERE id = 0")?;
statement.execute([])?;
Ok(())
}
}
impl BatchOperations for SqliteDatabase {
@@ -622,6 +662,11 @@ impl BatchOperations for SqliteDatabase {
Ok(())
}
fn set_sync_time(&mut self, ct: SyncTime) -> Result<(), Error> {
self.update_sync_time(ct)?;
Ok(())
}
fn del_script_pubkey_from_path(
&mut self,
keychain: KeychainKind,
@@ -707,6 +752,17 @@ impl BatchOperations for SqliteDatabase {
None => Ok(None),
}
}
fn del_sync_time(&mut self) -> Result<Option<SyncTime>, Error> {
match self.select_sync_time()? {
Some(value) => {
self.delete_sync_time()?;
Ok(Some(value))
}
None => Ok(None),
}
}
}
impl Database for SqliteDatabase {
@@ -818,6 +874,10 @@ impl Database for SqliteDatabase {
Ok(value)
}
fn get_sync_time(&self) -> Result<Option<SyncTime>, Error> {
self.select_sync_time()
}
fn increment_last_index(&mut self, keychain: KeychainKind) -> Result<u32, Error> {
let keychain_string = serde_json::to_string(&keychain)?;
match self.get_last_index(keychain)? {
@@ -965,4 +1025,9 @@ pub mod test {
fn test_last_index() {
crate::database::test::test_last_index(get_database());
}
#[test]
fn test_sync_time() {
crate::database::test::test_sync_time(get_database());
}
}

View File

@@ -19,7 +19,23 @@ use bitcoin::Network;
use miniscript::ScriptContext;
pub use bip39::{Language, Mnemonic, MnemonicType, Seed};
pub use bip39::{Language, Mnemonic};
type Seed = [u8; 64];
/// Type describing entropy length (aka word count) in the mnemonic
pub enum WordCount {
/// 12 words mnemonic (128 bits entropy)
Words12 = 128,
/// 15 words mnemonic (160 bits entropy)
Words15 = 160,
/// 18 words mnemonic (192 bits entropy)
Words18 = 192,
/// 21 words mnemonic (224 bits entropy)
Words21 = 224,
/// 24 words mnemonic (256 bits entropy)
Words24 = 256,
}
use super::{
any_network, DerivableKey, DescriptorKey, ExtendedKey, GeneratableKey, GeneratedKey, KeyError,
@@ -40,7 +56,7 @@ pub type MnemonicWithPassphrase = (Mnemonic, Option<String>);
#[cfg_attr(docsrs, doc(cfg(feature = "keys-bip39")))]
impl<Ctx: ScriptContext> DerivableKey<Ctx> for Seed {
fn into_extended_key(self) -> Result<ExtendedKey<Ctx>, KeyError> {
Ok(bip32::ExtendedPrivKey::new_master(Network::Bitcoin, self.as_bytes())?.into())
Ok(bip32::ExtendedPrivKey::new_master(Network::Bitcoin, &self[..])?.into())
}
fn into_descriptor_key(
@@ -60,7 +76,7 @@ impl<Ctx: ScriptContext> DerivableKey<Ctx> for Seed {
impl<Ctx: ScriptContext> DerivableKey<Ctx> for MnemonicWithPassphrase {
fn into_extended_key(self) -> Result<ExtendedKey<Ctx>, KeyError> {
let (mnemonic, passphrase) = self;
let seed = Seed::new(&mnemonic, passphrase.as_deref().unwrap_or(""));
let seed: Seed = mnemonic.to_seed(passphrase.as_deref().unwrap_or(""));
seed.into_extended_key()
}
@@ -101,15 +117,15 @@ impl<Ctx: ScriptContext> DerivableKey<Ctx> for Mnemonic {
impl<Ctx: ScriptContext> GeneratableKey<Ctx> for Mnemonic {
type Entropy = [u8; 32];
type Options = (MnemonicType, Language);
type Error = Option<bip39::ErrorKind>;
type Options = (WordCount, Language);
type Error = Option<bip39::Error>;
fn generate_with_entropy(
(mnemonic_type, language): Self::Options,
(word_count, language): Self::Options,
entropy: Self::Entropy,
) -> Result<GeneratedKey<Self, Ctx>, Self::Error> {
let entropy = &entropy.as_ref()[..(mnemonic_type.entropy_bits() / 8)];
let mnemonic = Mnemonic::from_entropy(entropy, language).map_err(|e| e.downcast().ok())?;
let entropy = &entropy.as_ref()[..(word_count as usize / 8)];
let mnemonic = Mnemonic::from_entropy_in(language, entropy)?;
Ok(GeneratedKey::new(mnemonic, any_network()))
}
@@ -121,15 +137,17 @@ mod test {
use bitcoin::util::bip32;
use bip39::{Language, Mnemonic, MnemonicType};
use bip39::{Language, Mnemonic};
use crate::keys::{any_network, GeneratableKey, GeneratedKey};
use super::WordCount;
#[test]
fn test_keys_bip39_mnemonic() {
let mnemonic =
"aim bunker wash balance finish force paper analyst cabin spoon stable organ";
let mnemonic = Mnemonic::from_phrase(mnemonic, Language::English).unwrap();
let mnemonic = Mnemonic::parse_in(Language::English, mnemonic).unwrap();
let path = bip32::DerivationPath::from_str("m/44'/0'/0'/0").unwrap();
let key = (mnemonic, path);
@@ -143,7 +161,7 @@ mod test {
fn test_keys_bip39_mnemonic_passphrase() {
let mnemonic =
"aim bunker wash balance finish force paper analyst cabin spoon stable organ";
let mnemonic = Mnemonic::from_phrase(mnemonic, Language::English).unwrap();
let mnemonic = Mnemonic::parse_in(Language::English, mnemonic).unwrap();
let path = bip32::DerivationPath::from_str("m/44'/0'/0'/0").unwrap();
let key = ((mnemonic, Some("passphrase".into())), path);
@@ -157,7 +175,7 @@ mod test {
fn test_keys_generate_bip39() {
let generated_mnemonic: GeneratedKey<_, miniscript::Segwitv0> =
Mnemonic::generate_with_entropy(
(MnemonicType::Words12, Language::English),
(WordCount::Words12, Language::English),
crate::keys::test::TEST_ENTROPY,
)
.unwrap();
@@ -169,7 +187,7 @@ mod test {
let generated_mnemonic: GeneratedKey<_, miniscript::Segwitv0> =
Mnemonic::generate_with_entropy(
(MnemonicType::Words24, Language::English),
(WordCount::Words24, Language::English),
crate::keys::test::TEST_ENTROPY,
)
.unwrap();
@@ -180,11 +198,11 @@ mod test {
#[test]
fn test_keys_generate_bip39_random() {
let generated_mnemonic: GeneratedKey<_, miniscript::Segwitv0> =
Mnemonic::generate((MnemonicType::Words12, Language::English)).unwrap();
Mnemonic::generate((WordCount::Words12, Language::English)).unwrap();
assert_eq!(generated_mnemonic.valid_networks, any_network());
let generated_mnemonic: GeneratedKey<_, miniscript::Segwitv0> =
Mnemonic::generate((MnemonicType::Words24, Language::English)).unwrap();
Mnemonic::generate((WordCount::Words24, Language::English)).unwrap();
assert_eq!(generated_mnemonic.valid_networks, any_network());
}
}

View File

@@ -460,9 +460,9 @@ use bdk::keys::bip39::{Mnemonic, Language};
# fn main() -> Result<(), Box<dyn std::error::Error>> {
let xkey: ExtendedKey =
Mnemonic::from_phrase(
Mnemonic::parse_in(
Language::English,
"jelly crash boy whisper mouse ecology tuna soccer memory million news short",
Language::English
)?
.into_extended_key()?;
let xprv = xkey.into_xprv(Network::Bitcoin).unwrap();

View File

@@ -40,7 +40,7 @@
//! interact with the bitcoin P2P network.
//!
//! ```toml
//! bdk = "0.13.0"
//! bdk = "0.14.0"
//! ```
#![cfg_attr(
feature = "electrum",

View File

@@ -145,9 +145,7 @@ impl TestClient {
let bumped: serde_json::Value = self.call("bumpfee", &[txid.to_string().into()]).unwrap();
let new_txid = Txid::from_str(&bumped["txid"].as_str().unwrap().to_string()).unwrap();
let monitor_script =
tx.vout[0].script_pub_key.addresses.as_ref().unwrap()[0].script_pubkey();
let monitor_script = Script::from_hex(&mut tx.vout[0].script_pub_key.hex.to_hex()).unwrap();
self.wait_for_tx(new_txid, &monitor_script);
debug!("Bumped {}, new txid {}", txid, new_txid);
@@ -394,6 +392,9 @@ macro_rules! bdk_blockchain_tests {
#[test]
fn test_sync_simple() {
use std::ops::Deref;
use crate::database::Database;
let (wallet, descriptors, mut test_client) = init_single_sig();
let tx = testutils! {
@@ -402,7 +403,13 @@ macro_rules! bdk_blockchain_tests {
println!("{:?}", tx);
let txid = test_client.receive(tx);
// the RPC blockchain needs to call `sync()` during initialization to import the
// addresses (see `init_single_sig()`), so we skip this assertion
#[cfg(not(feature = "test-rpc"))]
assert!(wallet.database().deref().get_sync_time().unwrap().is_none(), "initial sync_time not none");
wallet.sync(noop_progress(), None).unwrap();
assert!(wallet.database().deref().get_sync_time().unwrap().is_some(), "sync_time hasn't been updated");
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
assert_eq!(wallet.list_unspent().unwrap()[0].keychain, KeychainKind::External, "incorrect keychain kind");
@@ -594,7 +601,7 @@ macro_rules! bdk_blockchain_tests {
assert!(finalized, "Cannot finalize transaction");
let tx = psbt.extract_tx();
println!("{}", bitcoin::consensus::encode::serialize_hex(&tx));
wallet.broadcast(tx).unwrap();
wallet.broadcast(&tx).unwrap();
wallet.sync(noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect balance after send");
@@ -645,7 +652,7 @@ macro_rules! bdk_blockchain_tests {
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction");
let sent_txid = wallet.broadcast(psbt.extract_tx()).unwrap();
let sent_txid = wallet.broadcast(&psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect balance after receive");
@@ -688,7 +695,7 @@ macro_rules! bdk_blockchain_tests {
let (mut psbt, details) = builder.finish().unwrap();
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(psbt.extract_tx()).unwrap();
wallet.broadcast(&psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap();
@@ -727,7 +734,7 @@ macro_rules! bdk_blockchain_tests {
let (mut psbt, details) = builder.finish().unwrap();
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(psbt.extract_tx()).unwrap();
wallet.broadcast(&psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000 - details.fee.unwrap_or(0) - 5_000, "incorrect balance from fees");
assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect balance from received");
@@ -737,7 +744,7 @@ macro_rules! bdk_blockchain_tests {
let (mut new_psbt, new_details) = builder.finish().unwrap();
let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(new_psbt.extract_tx()).unwrap();
wallet.broadcast(&new_psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000 - new_details.fee.unwrap_or(0) - 5_000, "incorrect balance from fees after bump");
assert_eq!(wallet.get_balance().unwrap(), new_details.received, "incorrect balance from received after bump");
@@ -762,7 +769,7 @@ macro_rules! bdk_blockchain_tests {
let (mut psbt, details) = builder.finish().unwrap();
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(psbt.extract_tx()).unwrap();
wallet.broadcast(&psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 1_000 - details.fee.unwrap_or(0), "incorrect balance after send");
assert_eq!(wallet.get_balance().unwrap(), details.received, "incorrect received after send");
@@ -772,7 +779,7 @@ macro_rules! bdk_blockchain_tests {
let (mut new_psbt, new_details) = builder.finish().unwrap();
let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(new_psbt.extract_tx()).unwrap();
wallet.broadcast(&new_psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 0, "incorrect balance after change removal");
assert_eq!(new_details.received, 0, "incorrect received after change removal");
@@ -797,7 +804,7 @@ macro_rules! bdk_blockchain_tests {
let (mut psbt, details) = builder.finish().unwrap();
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(psbt.extract_tx()).unwrap();
wallet.broadcast(&psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 26_000 - details.fee.unwrap_or(0), "incorrect balance after send");
assert_eq!(details.received, 1_000 - details.fee.unwrap_or(0), "incorrect received after send");
@@ -807,7 +814,7 @@ macro_rules! bdk_blockchain_tests {
let (mut new_psbt, new_details) = builder.finish().unwrap();
let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(new_psbt.extract_tx()).unwrap();
wallet.broadcast(&new_psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap();
assert_eq!(new_details.sent, 75_000, "incorrect sent");
assert_eq!(wallet.get_balance().unwrap(), new_details.received, "incorrect balance after add input");
@@ -830,7 +837,7 @@ macro_rules! bdk_blockchain_tests {
let (mut psbt, details) = builder.finish().unwrap();
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(psbt.extract_tx()).unwrap();
wallet.broadcast(&psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 26_000 - details.fee.unwrap_or(0), "incorrect balance after send");
assert_eq!(details.received, 1_000 - details.fee.unwrap_or(0), "incorrect received after send");
@@ -842,13 +849,44 @@ macro_rules! bdk_blockchain_tests {
let finalized = wallet.sign(&mut new_psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction");
wallet.broadcast(new_psbt.extract_tx()).unwrap();
wallet.broadcast(&new_psbt.extract_tx()).unwrap();
wallet.sync(noop_progress(), None).unwrap();
assert_eq!(new_details.sent, 75_000, "incorrect sent");
assert_eq!(wallet.get_balance().unwrap(), 0, "incorrect balance after add input");
assert_eq!(new_details.received, 0, "incorrect received after add input");
}
#[test]
fn test_add_data() {
let (wallet, descriptors, mut test_client) = init_single_sig();
let node_addr = test_client.get_node_address(None);
let _ = test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000 )
});
wallet.sync(noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000, "incorrect balance");
let mut builder = wallet.build_tx();
let data = [42u8;80];
builder.add_data(&data);
let (mut psbt, details) = builder.finish().unwrap();
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized, "Cannot finalize transaction");
let tx = psbt.extract_tx();
let serialized_tx = bitcoin::consensus::encode::serialize(&tx);
assert!(serialized_tx.windows(data.len()).any(|e| e==data), "cannot find op_return data in transaction");
let sent_txid = wallet.broadcast(&tx).unwrap();
test_client.generate(1, Some(node_addr));
wallet.sync(noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000 - details.fee.unwrap_or(0), "incorrect balance after send");
let tx_map = wallet.list_transactions(false).unwrap().into_iter().map(|tx| (tx.txid, tx)).collect::<std::collections::HashMap<_, _>>();
let _ = tx_map.get(&sent_txid).unwrap();
}
#[test]
fn test_sync_receive_coinbase() {
let (wallet, _, mut test_client) = init_single_sig();
@@ -871,6 +909,102 @@ macro_rules! bdk_blockchain_tests {
wallet.sync(noop_progress(), None).unwrap();
assert!(wallet.get_balance().unwrap() > 0, "incorrect balance after receiving coinbase");
}
#[test]
fn test_send_to_bech32m_addr() {
use std::str::FromStr;
use serde;
use serde_json;
use serde::Serialize;
use bitcoincore_rpc::jsonrpc::serde_json::Value;
use bitcoincore_rpc::{Auth, Client, RpcApi};
let (wallet, descriptors, mut test_client) = init_single_sig();
// TODO remove once rust-bitcoincore-rpc with PR 199 released
// https://github.com/rust-bitcoin/rust-bitcoincore-rpc/pull/199
/// Import Descriptor Request
#[derive(Serialize, Clone, PartialEq, Eq, Debug)]
pub struct ImportDescriptorRequest {
pub active: bool,
#[serde(rename = "desc")]
pub descriptor: String,
pub range: [i64; 2],
pub next_index: i64,
pub timestamp: String,
pub internal: bool,
}
// TODO remove once rust-bitcoincore-rpc with PR 199 released
impl ImportDescriptorRequest {
/// Create a new Import Descriptor request providing just the descriptor and internal flags
pub fn new(descriptor: &str, internal: bool) -> Self {
ImportDescriptorRequest {
descriptor: descriptor.to_string(),
internal,
active: true,
range: [0, 100],
next_index: 0,
timestamp: "now".to_string(),
}
}
}
// 1. Create and add descriptors to a test bitcoind node taproot wallet
// TODO replace once rust-bitcoincore-rpc with PR 174 released
// https://github.com/rust-bitcoin/rust-bitcoincore-rpc/pull/174
let _createwallet_result: Value = test_client.bitcoind.client.call("createwallet", &["taproot_wallet".into(),false.into(),true.into(),serde_json::to_value("").unwrap(), false.into(), true.into()]).unwrap();
// TODO replace once bitcoind released with support for rust-bitcoincore-rpc PR 174
let taproot_wallet_client = Client::new(&test_client.bitcoind.rpc_url_with_wallet("taproot_wallet"), Auth::CookieFile(test_client.bitcoind.params.cookie_file.clone())).unwrap();
let wallet_descriptor = "tr(tprv8ZgxMBicQKsPdBtxmEMPnNq58KGusNAimQirKFHqX2yk2D8q1v6pNLiKYVAdzDHy2w3vF4chuGfMvNtzsbTTLVXBcdkCA1rje1JG6oksWv8/86h/1h/0h/0/*)#y283ssmn";
let change_descriptor = "tr(tprv8ZgxMBicQKsPdBtxmEMPnNq58KGusNAimQirKFHqX2yk2D8q1v6pNLiKYVAdzDHy2w3vF4chuGfMvNtzsbTTLVXBcdkCA1rje1JG6oksWv8/86h/1h/0h/1/*)#47zsd9tt";
let tr_descriptors = vec![
ImportDescriptorRequest::new(wallet_descriptor, false),
ImportDescriptorRequest::new(change_descriptor, false),
];
// TODO replace once rust-bitcoincore-rpc with PR 199 released
let _import_result: Value = taproot_wallet_client.call("importdescriptors", &[serde_json::to_value(tr_descriptors).unwrap()]).unwrap();
// 2. Get a new bech32m address from test bitcoind node taproot wallet
// TODO replace once rust-bitcoincore-rpc with PR 199 released
let node_addr: bitcoin::Address = taproot_wallet_client.call("getnewaddress", &["test address".into(), "bech32m".into()]).unwrap();
assert_eq!(node_addr, bitcoin::Address::from_str("bcrt1pj5y3f0fu4y7g98k4v63j9n0xvj3lmln0cpwhsjzknm6nt0hr0q7qnzwsy9").unwrap());
// 3. Send 50_000 sats from test bitcoind node to test BDK wallet
test_client.receive(testutils! {
@tx ( (@external descriptors, 0) => 50_000 )
});
wallet.sync(noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), 50_000, "wallet has incorrect balance");
// 4. Send 25_000 sats from test BDK wallet to test bitcoind node taproot wallet
let mut builder = wallet.build_tx();
builder.add_recipient(node_addr.script_pubkey(), 25_000);
let (mut psbt, details) = builder.finish().unwrap();
let finalized = wallet.sign(&mut psbt, Default::default()).unwrap();
assert!(finalized, "wallet cannot finalize transaction");
let tx = psbt.extract_tx();
wallet.broadcast(&tx).unwrap();
wallet.sync(noop_progress(), None).unwrap();
assert_eq!(wallet.get_balance().unwrap(), details.received, "wallet has incorrect balance after send");
assert_eq!(wallet.list_transactions(false).unwrap().len(), 2, "wallet has incorrect number of txs");
assert_eq!(wallet.list_unspent().unwrap().len(), 1, "wallet has incorrect number of unspents");
test_client.generate(1, None);
// 5. Verify 25_000 sats are received by test bitcoind node taproot wallet
let taproot_balance = taproot_wallet_client.get_balance(None, None).unwrap();
assert_eq!(taproot_balance.as_sat(), 25_000, "node has incorrect taproot wallet balance");
}
}
};

View File

@@ -210,7 +210,7 @@ pub struct TransactionDetails {
pub fee: Option<u64>,
/// If the transaction is confirmed, contains height and timestamp of the block containing the
/// transaction, unconfirmed transaction contains `None`.
pub confirmation_time: Option<ConfirmationTime>,
pub confirmation_time: Option<BlockTime>,
/// Whether the tx has been verified against the consensus rules
///
/// Confirmed txs are considered "verified" by default, while unconfirmed txs are checked to
@@ -222,20 +222,26 @@ pub struct TransactionDetails {
pub verified: bool,
}
/// Block height and timestamp of the block containing the confirmed transaction
/// Block height and timestamp of a block
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Default)]
pub struct ConfirmationTime {
pub struct BlockTime {
/// confirmation block height
pub height: u32,
/// confirmation block timestamp
pub timestamp: u64,
}
impl ConfirmationTime {
/// Returns `Some` `ConfirmationTime` if both `height` and `timestamp` are `Some`
/// **DEPRECATED**: Confirmation time of a transaction
///
/// The structure has been renamed to `BlockTime`
#[deprecated(note = "This structure has been renamed to `BlockTime`")]
pub type ConfirmationTime = BlockTime;
impl BlockTime {
/// Returns `Some` `BlockTime` if both `height` and `timestamp` are `Some`
pub fn new(height: Option<u32>, timestamp: Option<u64>) -> Option<Self> {
match (height, timestamp) {
(Some(height), Some(timestamp)) => Some(ConfirmationTime { height, timestamp }),
(Some(height), Some(timestamp)) => Some(BlockTime { height, timestamp }),
_ => None,
}
}

View File

@@ -212,7 +212,7 @@ mod test {
use crate::database::{memory::MemoryDatabase, BatchOperations};
use crate::types::TransactionDetails;
use crate::wallet::Wallet;
use crate::ConfirmationTime;
use crate::BlockTime;
fn get_test_db() -> MemoryDatabase {
let mut db = MemoryDatabase::new();
@@ -226,7 +226,7 @@ mod test {
received: 100_000,
sent: 0,
fee: Some(500),
confirmation_time: Some(ConfirmationTime {
confirmation_time: Some(BlockTime {
timestamp: 12345678,
height: 5000,
}),

View File

@@ -57,7 +57,7 @@ use utils::{check_nlocktime, check_nsequence_rbf, After, Older, SecpCtx, DUST_LI
use crate::blockchain::{Blockchain, Progress};
use crate::database::memory::MemoryDatabase;
use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils};
use crate::database::{BatchDatabase, BatchOperations, DatabaseUtils, SyncTime};
use crate::descriptor::derived::AsDerived;
use crate::descriptor::policy::BuildSatisfaction;
use crate::descriptor::{
@@ -601,7 +601,7 @@ where
let recipients = params.recipients.iter().map(|(r, v)| (r, *v));
for (index, (script_pubkey, value)) in recipients.enumerate() {
if value.is_dust() {
if value.is_dust() && !script_pubkey.is_provably_unspendable() {
return Err(Error::OutputBelowDustLimit(index));
}
@@ -1447,6 +1447,11 @@ where
Ok(())
}
/// Return an immutable reference to the internal database
pub fn database(&self) -> impl std::ops::Deref<Target = D> + '_ {
self.database.borrow()
}
}
impl<B, D> Wallet<B, D>
@@ -1549,6 +1554,15 @@ where
}
}
let sync_time = SyncTime {
block_time: BlockTime {
height: maybe_await!(self.client.get_height())?,
timestamp: time::get_timestamp(),
},
};
debug!("Saving `sync_time` = {:?}", sync_time);
self.database.borrow_mut().set_sync_time(sync_time)?;
Ok(())
}
@@ -1559,8 +1573,8 @@ where
/// Broadcast a transaction to the network
#[maybe_async]
pub fn broadcast(&self, tx: Transaction) -> Result<Txid, Error> {
maybe_await!(self.client.broadcast(&tx))?;
pub fn broadcast(&self, tx: &Transaction) -> Result<Txid, Error> {
maybe_await!(self.client.broadcast(tx))?;
Ok(tx.txid())
}
@@ -2778,7 +2792,7 @@ pub(crate) mod test {
let txid = tx.txid();
// skip saving the utxos, we know they can't be used anyways
details.transaction = Some(tx);
details.confirmation_time = Some(ConfirmationTime {
details.confirmation_time = Some(BlockTime {
timestamp: 12345678,
height: 42,
});
@@ -3980,4 +3994,15 @@ pub(crate) mod test {
}
);
}
#[test]
fn test_sending_to_bip350_bech32m_address() {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
let addr =
Address::from_str("tb1pqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesf3hn0c")
.unwrap();
let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), 45_000);
builder.finish().unwrap();
}
}

View File

@@ -560,6 +560,13 @@ impl<'a, B, D: BatchDatabase, Cs: CoinSelectionAlgorithm<D>> TxBuilder<'a, B, D,
self
}
/// Add data as an output, using OP_RETURN
pub fn add_data(&mut self, data: &[u8]) -> &mut Self {
let script = Script::new_op_return(data);
self.add_recipient(script, 0u64);
self
}
/// Sets the address to *drain* excess coins to.
///
/// Usually, when there are excess coins they are sent to a change address generated by the