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 get_block_headers and check timestamps #669

Closed
wants to merge 2 commits into from
Closed
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add `Excess` enum to handle remaining amount after coin selection.
- Move change creation from `Wallet::create_tx` to `CoinSelectionAlgorithm::coin_select`.
- Change the interface of `SqliteDatabase::new` to accept any type that implement AsRef<Path>
- Change trait `GetBlockHash` to `GetBlockInfo`. A new function is added to the new trait `get_block_header` which expects a block height and returns corresponding block header. This is implemented on every blockchain backend.
- Amend the struct `After` and `Older` to accept and check against timestamps as well.

## [v0.20.0] - [v0.19.0]

Expand Down
5 changes: 4 additions & 1 deletion src/blockchain/any.rs
Original file line number Diff line number Diff line change
Expand Up @@ -121,10 +121,13 @@ impl GetTx for AnyBlockchain {
}

#[maybe_async]
impl GetBlockHash for AnyBlockchain {
impl GetBlockInfo for AnyBlockchain {
fn get_block_hash(&self, height: u64) -> Result<BlockHash, Error> {
maybe_await!(impl_inner_method!(self, get_block_hash, height))
}
fn get_block_header(&self, height: u64) -> Result<BlockHeader, Error> {
maybe_await!(impl_inner_method!(self, get_block_header, height))
}
}

#[maybe_async]
Expand Down
7 changes: 6 additions & 1 deletion src/blockchain/compact_filters/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -260,14 +260,19 @@ impl GetTx for CompactFiltersBlockchain {
}
}

impl GetBlockHash for CompactFiltersBlockchain {
impl GetBlockInfo for CompactFiltersBlockchain {
fn get_block_hash(&self, height: u64) -> Result<BlockHash, Error> {
self.headers
.get_block_hash(height as usize)?
.ok_or(Error::CompactFilters(
CompactFiltersError::BlockHashNotFound,
))
}
fn get_block_header(&self, height: u64) -> Result<BlockHeader, Error> {
self.headers
.get_block_header(height as usize)?
.ok_or(Error::CompactFilters(CompactFiltersError::InvalidHeaders))
}
}

impl WalletSync for CompactFiltersBlockchain {
Expand Down
12 changes: 11 additions & 1 deletion src/blockchain/compact_filters/store.rs
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,16 @@ impl ChainStore<Full> {
}

pub fn get_block_hash(&self, height: usize) -> Result<Option<BlockHash>, CompactFiltersError> {
match self.get_block_header(height)? {
Some(header) => Ok(Some(header.block_hash())),
None => Ok(None),
}
}

pub fn get_block_header(
&self,
height: usize,
) -> Result<Option<BlockHeader>, CompactFiltersError> {
let read_store = self.store.read().unwrap();
let cf_handle = read_store.cf_handle(&self.cf_name).unwrap();

Expand All @@ -444,7 +454,7 @@ impl ChainStore<Full> {
data.map(|data| {
let (header, _): (BlockHeader, Uint256) =
deserialize(&data).map_err(|_| CompactFiltersError::DataCorruption)?;
Ok::<_, CompactFiltersError>(header.block_hash())
Ok::<_, CompactFiltersError>(header)
})
.transpose()
}
Expand Down
6 changes: 5 additions & 1 deletion src/blockchain/electrum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -98,11 +98,15 @@ impl GetTx for ElectrumBlockchain {
}
}

impl GetBlockHash for ElectrumBlockchain {
impl GetBlockInfo for ElectrumBlockchain {
fn get_block_hash(&self, height: u64) -> Result<BlockHash, Error> {
let block_header = self.client.block_header(height as usize)?;
Ok(block_header.block_hash())
}
fn get_block_header(&self, height: u64) -> Result<BlockHeader, Error> {
let block_header = self.client.block_header(height as usize)?;
Ok(block_header)
}
}

impl WalletSync for ElectrumBlockchain {
Expand Down
6 changes: 5 additions & 1 deletion src/blockchain/esplora/reqwest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,11 +118,15 @@ impl GetTx for EsploraBlockchain {
}

#[maybe_async]
impl GetBlockHash for EsploraBlockchain {
impl GetBlockInfo for EsploraBlockchain {
fn get_block_hash(&self, height: u64) -> Result<BlockHash, Error> {
let block_header = await_or_block!(self.url_client._get_header(height as u32))?;
Ok(block_header.block_hash())
}
fn get_block_header(&self, height: u64) -> Result<BlockHeader, Error> {
let block_header = await_or_block!(self.url_client._get_header(height as u32))?;
Ok(block_header)
}
}

#[maybe_async]
Expand Down
6 changes: 5 additions & 1 deletion src/blockchain/esplora/ureq.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,11 +112,15 @@ impl GetTx for EsploraBlockchain {
}
}

impl GetBlockHash for EsploraBlockchain {
impl GetBlockInfo for EsploraBlockchain {
fn get_block_hash(&self, height: u64) -> Result<BlockHash, Error> {
let block_header = self.url_client._get_header(height as u32)?;
Ok(block_header.block_hash())
}
fn get_block_header(&self, height: u64) -> Result<BlockHeader, Error> {
let block_header = await_or_block!(self.url_client._get_header(height as u32))?;
Ok(block_header)
}
}

impl WalletSync for EsploraBlockchain {
Expand Down
13 changes: 9 additions & 4 deletions src/blockchain/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use std::ops::Deref;
use std::sync::mpsc::{channel, Receiver, Sender};
use std::sync::Arc;

use bitcoin::{BlockHash, Transaction, Txid};
use bitcoin::{BlockHash, BlockHeader, Transaction, Txid};

use crate::database::BatchDatabase;
use crate::error::Error;
Expand Down Expand Up @@ -87,7 +87,7 @@ pub enum Capability {

/// Trait that defines the actions that must be supported by a blockchain backend
#[maybe_async]
pub trait Blockchain: WalletSync + GetHeight + GetTx + GetBlockHash {
pub trait Blockchain: WalletSync + GetHeight + GetTx + GetBlockInfo {
/// Return the set of [`Capability`] supported by this backend
fn get_capabilities(&self) -> HashSet<Capability>;
/// Broadcast a transaction
Expand All @@ -112,9 +112,11 @@ pub trait GetTx {

#[maybe_async]
/// Trait for getting block hash by block height
pub trait GetBlockHash {
pub trait GetBlockInfo {
/// fetch block hash given its height
fn get_block_hash(&self, height: u64) -> Result<BlockHash, Error>;
/// fetch block header given its height
fn get_block_header(&self, height: u64) -> Result<BlockHeader, Error>;
}

/// Trait for blockchains that can sync by updating the database directly.
Expand Down Expand Up @@ -367,10 +369,13 @@ impl<T: GetHeight> GetHeight for Arc<T> {
}

#[maybe_async]
impl<T: GetBlockHash> GetBlockHash for Arc<T> {
impl<T: GetBlockInfo> GetBlockInfo for Arc<T> {
fn get_block_hash(&self, height: u64) -> Result<BlockHash, Error> {
maybe_await!(self.deref().get_block_hash(height))
}
fn get_block_header(&self, height: u64) -> Result<BlockHeader, Error> {
maybe_await!(self.deref().get_block_header(height))
}
}

#[maybe_async]
Expand Down
6 changes: 5 additions & 1 deletion src/blockchain/rpc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,10 +169,14 @@ impl GetHeight for RpcBlockchain {
}
}

impl GetBlockHash for RpcBlockchain {
impl GetBlockInfo for RpcBlockchain {
fn get_block_hash(&self, height: u64) -> Result<BlockHash, Error> {
Ok(self.client.get_block_hash(height)?)
}
fn get_block_header(&self, height: u64) -> Result<BlockHeader, Error> {
let block_hash = self.client.get_block_hash(height)?;
Ok(self.client.get_block_header(&block_hash)?)
}
}

impl WalletSync for RpcBlockchain {
Expand Down
10 changes: 8 additions & 2 deletions src/descriptor/policy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -904,7 +904,7 @@ impl<Ctx: ScriptContext + 'static> ExtractPolicy for Miniscript<DescriptorPublic
..
} = build_sat
{
let after = After::new(Some(current_height), false);
let after = After::new(Some(current_height), None, false);
let after_sat = Satisfier::<bitcoin::PublicKey>::check_after(&after, *value);
let inputs_sat = psbt_inputs_sat(psbt)
.all(|sat| Satisfier::<bitcoin::PublicKey>::check_after(&sat, *value));
Expand All @@ -929,7 +929,13 @@ impl<Ctx: ScriptContext + 'static> ExtractPolicy for Miniscript<DescriptorPublic
psbt,
} = build_sat
{
let older = Older::new(Some(current_height), Some(input_max_height), false);
let older = Older::new(
Some(current_height),
None,
Some(input_max_height),
None,
false,
);
let older_sat = Satisfier::<bitcoin::PublicKey>::check_older(&older, *value);
let inputs_sat = psbt_inputs_sat(psbt)
.all(|sat| Satisfier::<bitcoin::PublicKey>::check_older(&sat, *value));
Expand Down
2 changes: 1 addition & 1 deletion src/testutils/blockchain_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1419,7 +1419,7 @@ macro_rules! bdk_blockchain_tests {
#[test]
fn test_get_block_hash() {
use bitcoincore_rpc::{ RpcApi };
use crate::blockchain::GetBlockHash;
use crate::blockchain::GetBlockInfo;

// create wallet with init_wallet
let (_, blockchain, _descriptors, mut test_client) = init_single_sig();
Expand Down
97 changes: 93 additions & 4 deletions src/wallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
use std::cell::RefCell;
use std::collections::HashMap;
use std::collections::{BTreeMap, HashSet};
use std::convert::TryInto;
use std::fmt;
use std::ops::{Deref, DerefMut};
use std::str::FromStr;
Expand Down Expand Up @@ -1155,15 +1156,37 @@ where
.borrow()
.get_tx(&input.previous_output.txid, false)?
.map(|tx| tx.confirmation_time.map(|c| c.height).unwrap_or(u32::MAX));
let create_time = self
.database
.borrow()
.get_tx(&input.previous_output.txid, false)?
.map(|tx| {
tx.confirmation_time
.map(|c| {
c.timestamp
.try_into()
.expect("Time is greater than 0xFFFFFFFF")
})
.unwrap_or(u32::MAX)
});
let last_sync_height = self
.database()
.get_sync_time()?
.map(|sync_time| sync_time.block_time.height);
let current_height = sign_options.assume_height.or(last_sync_height);
// TODO: Change current time to median time of latest 11 blocks with Blockchain::GetBlockInfo
// according to BIP-113
let current_time = self.database().get_sync_time()?.map(|sync_time| {
sync_time
.block_time
.timestamp
.try_into()
.expect("Time is greater than 0xFFFFFFFF")
});

debug!(
"Input #{} - {}, using `create_height` = {:?}, `current_height` = {:?}",
n, input.previous_output, create_height, current_height
"Input #{} - {}, using `create_height` = {:?}, `current_height` = {:?}, `create_time` = {:?}, `current_time` = {:?}",
n, input.previous_output, create_height, current_height, create_time, current_time
);

// - Try to derive the descriptor by looking at the txout. If it's in our database, we
Expand Down Expand Up @@ -1197,8 +1220,16 @@ where
&mut tmp_input,
(
PsbtInputSatisfier::new(psbt, n),
After::new(current_height, false),
Older::new(current_height, create_height, false),
// FIXME: The satisfier doesn't call check methods of After and Older defined in wallet/utils.rs
// Instead it calls the implementations defined in miniscript
After::new(current_height, current_time, false),
Older::new(
current_height,
current_time,
create_height,
create_time,
false,
),
),
) {
Ok(_) => {
Expand Down Expand Up @@ -1962,11 +1993,21 @@ pub(crate) mod test {
"wsh(or_d(pk(cRjo6jqfVNP33HhSS76UhXETZsGTZYx8FMFvR9kpbtCSV1PmdZdu),and_v(v:pk(cMnkdebixpXMPfkcNEjjGin7s94hiehAH4mLbYkZoh9KSiNNmqC8),older(144))))"
}

pub(crate) fn get_test_single_sig_csv_with_time() -> &'static str {
// and(pk(Alice),older(4194904)) // (1 << 22) | 600 -> lock of 600 seconds with type time
"wsh(and_v(v:pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW),older(4194904)))"
}

pub(crate) fn get_test_single_sig_cltv() -> &'static str {
// and(pk(Alice),after(100000))
"wsh(and_v(v:pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW),after(100000)))"
}

pub(crate) fn get_test_single_sig_cltv_with_future_time() -> &'static str {
// and(pk(Alice),after(1893456000)) // Tue Jan 01 2030 00:00:00 GMT+0000
"wsh(and_v(v:pk(cVpPVruEDdmutPzisEsYvtST1usBR3ntr8pXSyt6D2YYqXRyPcFW),after(1893456000)))"
}

pub(crate) fn get_test_tr_single_sig() -> &'static str {
"tr(cNJmN3fH9DDbDt131fQNkVakkpzawJBSeybCUNmP1BovpmGQ45xG)"
}
Expand Down Expand Up @@ -2157,6 +2198,54 @@ pub(crate) mod test {
assert_eq!(psbt.unsigned_tx.lock_time, 100_000);
}

#[test]
fn test_create_tx_locktime_cltv_with_time() {
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_cltv_with_future_time());
let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), 25_000);
let (psbt, _) = builder.finish().unwrap();
let after = After::new(None, Some(time::get_timestamp() as u32), false);
let after_sat = miniscript::Satisfier::<bitcoin::PublicKey>::check_after(
&after,
psbt.unsigned_tx.lock_time,
);

assert!(!after_sat);

let after = After::new(None, Some(1893456000), false);
let after_sat = miniscript::Satisfier::<bitcoin::PublicKey>::check_after(
&after,
psbt.unsigned_tx.lock_time,
);
assert!(after_sat);
}

#[test]
fn test_create_tx_locktime_csv_with_time() {
let (wallet, _, _) = get_funded_wallet(get_test_single_sig_csv_with_time());
let addr = wallet.get_address(New).unwrap();
let mut builder = wallet.build_tx();
builder.add_recipient(addr.script_pubkey(), 25_000);
let (psbt, _) = builder.finish().unwrap();
let time_stamp = Some(time::get_timestamp() as u32);
let late_time_stamp = Some(time::get_timestamp() as u32 + 601);

let older = Older::new(None, time_stamp, None, time_stamp, false);
let older_sat = miniscript::Satisfier::<bitcoin::PublicKey>::check_older(
&older,
psbt.unsigned_tx.input[0].sequence,
);
assert!(!older_sat);

let older = Older::new(None, late_time_stamp, None, time_stamp, false);
let older_sat = miniscript::Satisfier::<bitcoin::PublicKey>::check_older(
&older,
psbt.unsigned_tx.input[0].sequence,
);
assert!(older_sat);
}

#[test]
fn test_create_tx_custom_locktime() {
let (wallet, _, _) = get_funded_wallet(get_test_wpkh());
Expand Down
Loading