Minimize usage of Network in public API

A release or so ago we added `non_exhaustive` to the `Network` enum,
this turned out to make usage of the enum un-ergonomic for downstream
users. After much debate we decided that a way forward was to just
minimize the usage of the enum in the public API by instead use
`AsRef<Params>` so that downstream could define their own network enum
based on the networks they support.

Minimize usage of `Network` by using `AsRef<Params>` as a parameter type
instead. "minimize" because the `Network` still appears in some places.
This commit is contained in:
Tobin C. Harding 2024-03-14 11:26:09 +11:00
parent 3ec5eff56e
commit f6467ac98d
No known key found for this signature in database
GPG Key ID: 40BF9E4C269D6607
6 changed files with 67 additions and 12 deletions

View File

@ -43,6 +43,7 @@ use crate::blockdata::constants::{
use crate::blockdata::script::witness_program::WitnessProgram; use crate::blockdata::script::witness_program::WitnessProgram;
use crate::blockdata::script::witness_version::WitnessVersion; use crate::blockdata::script::witness_version::WitnessVersion;
use crate::blockdata::script::{self, Script, ScriptBuf, ScriptHash}; use crate::blockdata::script::{self, Script, ScriptBuf, ScriptHash};
use crate::consensus::Params;
use crate::crypto::key::{ use crate::crypto::key::{
CompressedPublicKey, PubkeyHash, PublicKey, TweakedPublicKey, UntweakedPublicKey, CompressedPublicKey, PubkeyHash, PublicKey, TweakedPublicKey, UntweakedPublicKey,
}; };
@ -505,7 +506,8 @@ impl Address {
pub fn is_spend_standard(&self) -> bool { self.address_type().is_some() } pub fn is_spend_standard(&self) -> bool { self.address_type().is_some() }
/// Constructs an [`Address`] from an output script (`scriptPubkey`). /// Constructs an [`Address`] from an output script (`scriptPubkey`).
pub fn from_script(script: &Script, network: Network) -> Result<Address, FromScriptError> { pub fn from_script(script: &Script, params: impl AsRef<Params>) -> Result<Address, FromScriptError> {
let network = params.as_ref().network;
if script.is_p2pkh() { if script.is_p2pkh() {
let bytes = script.as_bytes()[3..23].try_into().expect("statically 20B long"); let bytes = script.as_bytes()[3..23].try_into().expect("statically 20B long");
let hash = PubkeyHash::from_byte_array(bytes); let hash = PubkeyHash::from_byte_array(bytes);
@ -820,6 +822,7 @@ mod tests {
use hex_lit::hex; use hex_lit::hex;
use super::*; use super::*;
use crate::consensus::params;
use crate::network::Network::{Bitcoin, Testnet}; use crate::network::Network::{Bitcoin, Testnet};
fn roundtrips(addr: &Address, network: Network) { fn roundtrips(addr: &Address, network: Network) {
@ -1281,7 +1284,7 @@ mod tests {
assert_eq!(Address::from_script(&bad_p2wpkh, Network::Bitcoin), expected); assert_eq!(Address::from_script(&bad_p2wpkh, Network::Bitcoin), expected);
assert_eq!(Address::from_script(&bad_p2wsh, Network::Bitcoin), expected); assert_eq!(Address::from_script(&bad_p2wsh, Network::Bitcoin), expected);
assert_eq!( assert_eq!(
Address::from_script(&invalid_segwitv0_script, Network::Bitcoin), Address::from_script(&invalid_segwitv0_script, &params::MAINNET),
Err(FromScriptError::WitnessProgram(witness_program::Error::InvalidSegwitV0Length(17))) Err(FromScriptError::WitnessProgram(witness_program::Error::InvalidSegwitV0Length(17)))
); );
} }

View File

@ -17,6 +17,7 @@ use crate::blockdata::opcodes::all::*;
use crate::blockdata::script; use crate::blockdata::script;
use crate::blockdata::transaction::{self, OutPoint, Sequence, Transaction, TxIn, TxOut}; use crate::blockdata::transaction::{self, OutPoint, Sequence, Transaction, TxIn, TxOut};
use crate::blockdata::witness::Witness; use crate::blockdata::witness::Witness;
use crate::consensus::Params;
use crate::internal_macros::impl_bytes_newtype; use crate::internal_macros::impl_bytes_newtype;
use crate::network::Network; use crate::network::Network;
use crate::pow::CompactTarget; use crate::pow::CompactTarget;
@ -84,11 +85,11 @@ fn bitcoin_genesis_tx() -> Transaction {
} }
/// Constructs and returns the genesis block. /// Constructs and returns the genesis block.
pub fn genesis_block(network: Network) -> Block { pub fn genesis_block(params: impl AsRef<Params>) -> Block {
let txdata = vec![bitcoin_genesis_tx()]; let txdata = vec![bitcoin_genesis_tx()];
let hash: sha256d::Hash = txdata[0].compute_txid().into(); let hash: sha256d::Hash = txdata[0].compute_txid().into();
let merkle_root = hash.into(); let merkle_root = hash.into();
match network { match params.as_ref().network {
Network::Bitcoin => Block { Network::Bitcoin => Block {
header: block::Header { header: block::Header {
version: block::Version::ONE, version: block::Version::ONE,
@ -169,7 +170,17 @@ impl ChainHash {
/// ///
/// See [BOLT 0](https://github.com/lightning/bolts/blob/ffeece3dab1c52efdb9b53ae476539320fa44938/00-introduction.md#chain_hash) /// See [BOLT 0](https://github.com/lightning/bolts/blob/ffeece3dab1c52efdb9b53ae476539320fa44938/00-introduction.md#chain_hash)
/// for specification. /// for specification.
pub const fn using_genesis_block(network: Network) -> Self { pub fn using_genesis_block(params: impl AsRef<Params>) -> Self {
let network = params.as_ref().network;
let hashes = [Self::BITCOIN, Self::TESTNET, Self::SIGNET, Self::REGTEST];
hashes[network as usize]
}
/// Returns the hash of the `network` genesis block for use as a chain hash.
///
/// See [BOLT 0](https://github.com/lightning/bolts/blob/ffeece3dab1c52efdb9b53ae476539320fa44938/00-introduction.md#chain_hash)
/// for specification.
pub const fn using_genesis_block_const(network: Network) -> Self {
let hashes = [Self::BITCOIN, Self::TESTNET, Self::SIGNET, Self::REGTEST]; let hashes = [Self::BITCOIN, Self::TESTNET, Self::SIGNET, Self::REGTEST];
hashes[network as usize] hashes[network as usize]
} }
@ -187,6 +198,7 @@ mod test {
use hex::test_hex_unwrap as hex; use hex::test_hex_unwrap as hex;
use super::*; use super::*;
use crate::consensus::params;
use crate::consensus::encode::serialize; use crate::consensus::encode::serialize;
#[test] #[test]
@ -213,9 +225,21 @@ mod test {
); );
} }
#[test]
fn bitcoin_genesis_block_calling_convention() {
// This is the best.
let _ = genesis_block(&params::MAINNET);
// this works and is ok too.
let _ = genesis_block(&Network::Bitcoin);
let _ = genesis_block(Network::Bitcoin);
// This works too, but is suboptimal because it inlines the const.
let _ = genesis_block(Params::MAINNET);
let _ = genesis_block(&Params::MAINNET);
}
#[test] #[test]
fn bitcoin_genesis_full_block() { fn bitcoin_genesis_full_block() {
let gen = genesis_block(Network::Bitcoin); let gen = genesis_block(&params::MAINNET);
assert_eq!(gen.header.version, block::Version::ONE); assert_eq!(gen.header.version, block::Version::ONE);
assert_eq!(gen.header.prev_blockhash, Hash::all_zeros()); assert_eq!(gen.header.prev_blockhash, Hash::all_zeros());
@ -235,7 +259,7 @@ mod test {
#[test] #[test]
fn testnet_genesis_full_block() { fn testnet_genesis_full_block() {
let gen = genesis_block(Network::Testnet); let gen = genesis_block(&params::TESTNET);
assert_eq!(gen.header.version, block::Version::ONE); assert_eq!(gen.header.version, block::Version::ONE);
assert_eq!(gen.header.prev_blockhash, Hash::all_zeros()); assert_eq!(gen.header.prev_blockhash, Hash::all_zeros());
assert_eq!( assert_eq!(
@ -253,7 +277,7 @@ mod test {
#[test] #[test]
fn signet_genesis_full_block() { fn signet_genesis_full_block() {
let gen = genesis_block(Network::Signet); let gen = genesis_block(&params::SIGNET);
assert_eq!(gen.header.version, block::Version::ONE); assert_eq!(gen.header.version, block::Version::ONE);
assert_eq!(gen.header.prev_blockhash, Hash::all_zeros()); assert_eq!(gen.header.prev_blockhash, Hash::all_zeros());
assert_eq!( assert_eq!(
@ -280,7 +304,7 @@ mod test {
let hash = sha256::Hash::from_slice(genesis_hash.as_byte_array()).unwrap(); let hash = sha256::Hash::from_slice(genesis_hash.as_byte_array()).unwrap();
let want = format!("{:02x}", hash); let want = format!("{:02x}", hash);
let chain_hash = ChainHash::using_genesis_block(network); let chain_hash = ChainHash::using_genesis_block_const(network);
let got = format!("{:02x}", chain_hash); let got = format!("{:02x}", chain_hash);
// Compare strings because the spec specifically states how the chain hash must encode to hex. // Compare strings because the spec specifically states how the chain hash must encode to hex.
@ -317,7 +341,7 @@ mod test {
// Test vector taken from: https://github.com/lightning/bolts/blob/master/00-introduction.md // Test vector taken from: https://github.com/lightning/bolts/blob/master/00-introduction.md
#[test] #[test]
fn mainnet_chain_hash_test_vector() { fn mainnet_chain_hash_test_vector() {
let got = ChainHash::using_genesis_block(Network::Bitcoin).to_string(); let got = ChainHash::using_genesis_block_const(Network::Bitcoin).to_string();
let want = "6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000"; let want = "6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000";
assert_eq!(got, want); assert_eq!(got, want);
} }

View File

@ -91,10 +91,11 @@ impl OutPoint {
/// # Examples /// # Examples
/// ///
/// ```rust /// ```rust
/// use bitcoin::consensus::params;
/// use bitcoin::constants::genesis_block; /// use bitcoin::constants::genesis_block;
/// use bitcoin::Network; /// use bitcoin::Network;
/// ///
/// let block = genesis_block(Network::Bitcoin); /// let block = genesis_block(&params::MAINNET);
/// let tx = &block.txdata[0]; /// let tx = &block.txdata[0];
/// ///
/// // Coinbase transactions don't have any previous output. /// // Coinbase transactions don't have any previous output.

View File

@ -56,6 +56,21 @@ pub struct Params {
pub no_pow_retargeting: bool, pub no_pow_retargeting: bool,
} }
/// The mainnet parameters.
///
/// Use this for a static reference e.g., `&params::MAINNET`.
///
/// For more on static vs const see The Rust Reference [using-statics-or-consts] section.
///
/// [using-statics-or-consts]: <https://doc.rust-lang.org/reference/items/static-items.html#using-statics-or-consts>
pub static MAINNET: Params = Params::MAINNET;
/// The testnet parameters.
pub static TESTNET: Params = Params::TESTNET;
/// The signet parameters.
pub static SIGNET: Params = Params::SIGNET;
/// The regtest parameters.
pub static REGTEST: Params = Params::REGTEST;
impl Params { impl Params {
/// The mainnet parameters (alias for `Params::MAINNET`). /// The mainnet parameters (alias for `Params::MAINNET`).
pub const BITCOIN: Params = Params::MAINNET; pub const BITCOIN: Params = Params::MAINNET;
@ -163,3 +178,14 @@ impl From<&Network> for &'static Params {
impl AsRef<Params> for Params { impl AsRef<Params> for Params {
fn as_ref(&self) -> &Params { self } fn as_ref(&self) -> &Params { self }
} }
impl AsRef<Params> for Network {
fn as_ref(&self) -> &Params {
match *self {
Network::Bitcoin => &MAINNET,
Network::Testnet => &TESTNET,
Network::Signet => &SIGNET,
Network::Regtest => &REGTEST,
}
}
}

View File

@ -129,6 +129,7 @@ pub use crate::{
blockdata::weight::Weight, blockdata::weight::Weight,
blockdata::witness::{self, Witness}, blockdata::witness::{self, Witness},
consensus::encode::VarInt, consensus::encode::VarInt,
consensus::params,
crypto::ecdsa, crypto::ecdsa,
crypto::key::{self, PrivateKey, PubkeyHash, PublicKey, CompressedPublicKey, WPubkeyHash, XOnlyPublicKey}, crypto::key::{self, PrivateKey, PubkeyHash, PublicKey, CompressedPublicKey, WPubkeyHash, XOnlyPublicKey},
crypto::sighash::{self, LegacySighash, SegwitV0Sighash, TapSighash, TapSighashTag}, crypto::sighash::{self, LegacySighash, SegwitV0Sighash, TapSighash, TapSighashTag},

View File

@ -154,7 +154,7 @@ impl Network {
/// let network = Network::Bitcoin; /// let network = Network::Bitcoin;
/// assert_eq!(network.chain_hash(), ChainHash::BITCOIN); /// assert_eq!(network.chain_hash(), ChainHash::BITCOIN);
/// ``` /// ```
pub fn chain_hash(self) -> ChainHash { ChainHash::using_genesis_block(self) } pub fn chain_hash(self) -> ChainHash { ChainHash::using_genesis_block_const(self) }
/// Creates a `Network` from the chain hash (genesis block hash). /// Creates a `Network` from the chain hash (genesis block hash).
/// ///