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

feat(wallet): add check for network and genesis_hash consistency #1777

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
15 changes: 15 additions & 0 deletions crates/wallet/src/descriptor/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ pub enum Error {
Hex(bitcoin::hex::HexToBytesError),
/// The provided wallet descriptors are identical
ExternalAndInternalAreTheSame,
/// The provided parameters mismatch
Mismatch(MismatchError),
}

impl From<crate::keys::KeyError> for Error {
Expand Down Expand Up @@ -83,6 +85,7 @@ impl fmt::Display for Error {
Self::ExternalAndInternalAreTheSame => {
write!(f, "External and internal descriptors are the same")
}
Self::Mismatch(mismatch) => write!(f, "The provided parameters mismatch: {mismatch:?}"),
}
}
}
Expand Down Expand Up @@ -125,3 +128,15 @@ impl From<crate::descriptor::policy::PolicyError> for Error {
Error::Policy(err)
}
}

#[derive(Debug, PartialEq)]
/// Represents a mismatch within the parameters that passed as [`crate::wallet::CreateParams`].
pub enum MismatchError {
/// Genesis hash does not match.
Genesis {
/// The genesis hash for the given network parameter.
network: bitcoin::BlockHash,
/// The genesis hash given as parameter.
parameter: bitcoin::BlockHash,
},
}
21 changes: 20 additions & 1 deletion crates/wallet/src/wallet/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,6 @@ pub mod signer;
pub mod tx_builder;
pub(crate) mod utils;

use crate::collections::{BTreeMap, HashMap, HashSet};
use crate::descriptor::{
check_wallet_descriptor, error::Error as DescriptorError, policy::BuildSatisfaction,
DerivedDescriptor, DescriptorMeta, ExtendedDescriptor, ExtractPolicy, IntoWalletDescriptor,
Expand All @@ -74,6 +73,10 @@ use crate::wallet::{
tx_builder::{FeePolicy, TxBuilder, TxParams},
utils::{check_nsequence_rbf, After, Older, SecpCtx},
};
use crate::{
collections::{BTreeMap, HashMap, HashSet},
descriptor::error::MismatchError,
};

// re-exports
pub use bdk_chain::Balance;
Expand Down Expand Up @@ -372,6 +375,14 @@ impl Wallet {
let genesis_hash = params
.genesis_hash
.unwrap_or(genesis_block(network).block_hash());

if genesis_hash.ne(&genesis_block(network).block_hash()) {
return Err(DescriptorError::Mismatch(MismatchError::Genesis {
network: genesis_block(network).block_hash(),
parameter: genesis_hash,
}));
}

Comment on lines 375 to +385
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As mentioned by @ValuedMammal, the intention here is so the caller can provide a custom genesis hash.

However, if the intention is to enforce a 1-possible-genesis-hash-per-network-variant invariance, changing the data structure to disallow the invariance would make a lot more sense. I.e. remove params.genesis_hash.

let (chain, chain_changeset) = LocalChain::from_genesis_hash(genesis_hash);

let (descriptor, mut descriptor_keymap) = (params.descriptor)(&secp, network)?;
Expand Down Expand Up @@ -506,6 +517,14 @@ impl Wallet {
expected: exp_genesis_hash,
}));
}

let network_genesis_hash = genesis_block(network).block_hash();
if network_genesis_hash != exp_genesis_hash {
return Err(LoadError::Mismatch(LoadMismatch::Genesis {
loaded: network_genesis_hash,
expected: exp_genesis_hash,
}));
}
Comment on lines +521 to +527
Copy link
Contributor Author

@oleonardolima oleonardolima Dec 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still unsure of how to properly test this one (or if it's even needed), because I can't initially create a wallet with network and genesis_hash that are valid (consistent) on create_with_params, but it will fail with an inconsistent check_genesis_hash on load_with_params.

It'll always fail on the previous check.

}

let descriptor = changeset
Expand Down
19 changes: 19 additions & 0 deletions crates/wallet/tests/wallet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use anyhow::Context;
use assert_matches::assert_matches;
use bdk_chain::{BlockId, ChainPosition, ConfirmationBlockTime};
use bdk_wallet::coin_selection::{self, LargestFirstCoinSelection};
use bdk_wallet::descriptor::error::MismatchError;
use bdk_wallet::descriptor::{calc_checksum, DescriptorError, IntoWalletDescriptor};
use bdk_wallet::error::CreateTxError;
use bdk_wallet::psbt::PsbtUtils;
Expand Down Expand Up @@ -355,6 +356,24 @@ fn test_error_external_and_internal_are_the_same() {
);
}

#[test]
fn test_create_genesis_hash_and_network_consistency() {
let (external_desc, internal_desc) = get_test_tr_single_sig_xprv_and_change_desc();
let mainnet_genesis_hash = BlockHash::from_byte_array(ChainHash::BITCOIN.to_bytes());
let err = Wallet::create(external_desc, internal_desc)
.genesis_hash(mainnet_genesis_hash)
.network(Network::Regtest)
.create_wallet_no_persist();

assert!(
matches!(
&err,
Err(DescriptorError::Mismatch(MismatchError::Genesis { .. }))
),
"unexpected network and genesis_hash parameter check result: `genesis_hash` network (Mainnet) does not match network parameter (Regtest)",
);
}

#[test]
fn test_descriptor_checksum() {
let (wallet, _) = get_funded_wallet_wpkh();
Expand Down
Loading