Merge rust-bitcoin/rust-bitcoin#4630: units: Remove serde derive feature

d8377d90dd units: Remove serde derive feature (Tobin C. Harding)
1031851da4 units: Manually implement serde traits for block types (Tobin C. Harding)

Pull request description:

  Currently we only need the `derive` feature of `serde` in test code.
  
  Observe:
  
  - We do not need the error testing logic because `ParseAmountError` is already exhaustively tested.
  - The rest of the `serde` test logic in `amount` can be done using the public API so it can be moved to the integration test directory.
  
  Move the unit test code to `tests/` excluding the error testing logic. Remove the `derive` feature from the `serde` dependency. Add a `dev-dependency` on `serde` that enables the `derive` feature.


ACKs for top commit:
  apoelstra:
    ACK d8377d90dd8d6bd066f51f45c23ffdfe2d0ab694; successfully ran local tests; nice!


Tree-SHA512: 03eb24ae1917e838a2e20c3c62ef9381e2a1eaccdb6474f60a2db59af98d9533054227af4c404013ea8deb4cfe4d57075ae4890857f8af283ebb4338ddb4ed3f
This commit is contained in:
Andrew Poelstra 2025-06-21 18:20:38 +00:00
commit 51f78bbb4c
No known key found for this signature in database
GPG Key ID: C588D63CE41B97C1
26 changed files with 826 additions and 729 deletions

View File

@ -77,6 +77,7 @@ name = "bitcoin-fuzz"
version = "0.0.1"
dependencies = [
"bitcoin",
"bitcoin-p2p-messages",
"honggfuzz",
"serde",
"serde_json",
@ -109,6 +110,15 @@ dependencies = [
[[package]]
name = "bitcoin-p2p-messages"
version = "0.1.0"
dependencies = [
"bitcoin",
"bitcoin-internals",
"bitcoin-io 0.2.0",
"bitcoin-units",
"bitcoin_hashes 0.16.0",
"hex-conservative 0.3.0",
"hex_lit",
]
[[package]]
name = "bitcoin-primitives"

View File

@ -76,6 +76,7 @@ name = "bitcoin-fuzz"
version = "0.0.1"
dependencies = [
"bitcoin",
"bitcoin-p2p-messages",
"honggfuzz",
"serde",
"serde_json",
@ -108,6 +109,15 @@ dependencies = [
[[package]]
name = "bitcoin-p2p-messages"
version = "0.1.0"
dependencies = [
"bitcoin",
"bitcoin-internals",
"bitcoin-io 0.2.0",
"bitcoin-units",
"bitcoin_hashes 0.16.0",
"hex-conservative 0.3.0",
"hex_lit",
]
[[package]]
name = "bitcoin-primitives"

View File

@ -56,10 +56,6 @@ rustdoc-args = ["--cfg", "docsrs"]
[[example]]
name = "bip32"
[[example]]
name = "handshake"
required-features = ["rand-std"]
[[example]]
name = "ecdsa-psbt"
required-features = ["std", "bitcoinconsensus"]

View File

@ -94,7 +94,6 @@ extern crate serde;
mod internal_macros;
#[macro_use]
pub mod p2p;
pub mod address;
pub mod bip152;
pub mod bip158;

View File

@ -1,42 +0,0 @@
macro_rules! impl_vec_wrapper {
($wrapper: ident, $type: ty) => {
impl crate::consensus::encode::Encodable for $wrapper {
#[inline]
fn consensus_encode<W: io::Write + ?Sized>(
&self,
w: &mut W,
) -> core::result::Result<usize, io::Error> {
let mut len = 0;
len += w.emit_compact_size(self.0.len())?;
for c in self.0.iter() {
len += c.consensus_encode(w)?;
}
Ok(len)
}
}
impl crate::consensus::encode::Decodable for $wrapper {
#[inline]
fn consensus_decode_from_finite_reader<R: io::BufRead + ?Sized>(
r: &mut R,
) -> core::result::Result<$wrapper, crate::consensus::encode::Error> {
let len = r.read_compact_size()?;
// Do not allocate upfront more items than if the sequence of type
// occupied roughly quarter a block. This should never be the case
// for normal data, but even if that's not true - `push` will just
// reallocate.
// Note: OOM protection relies on reader eventually running out of
// data to feed us.
let max_capacity =
crate::consensus::encode::MAX_VEC_SIZE / 4 / core::mem::size_of::<$type>();
let mut ret = Vec::with_capacity(core::cmp::min(len as usize, max_capacity));
for _ in 0..len {
ret.push(Decodable::consensus_decode_from_finite_reader(r)?);
}
Ok($wrapper(ret))
}
}
};
}
pub(crate) use impl_vec_wrapper;

View File

@ -1,495 +0,0 @@
// SPDX-License-Identifier: CC0-1.0
//! Bitcoin p2p network types.
//!
//! This module defines support for (de)serialization and network transport
//! of Bitcoin data and Bitcoin p2p network messages.
#[cfg(feature = "std")]
pub mod address;
#[cfg(feature = "std")]
mod deser;
#[cfg(feature = "std")]
pub mod message;
#[cfg(feature = "std")]
pub mod message_blockdata;
#[cfg(feature = "std")]
pub mod message_bloom;
#[cfg(feature = "std")]
pub mod message_compact_blocks;
#[cfg(feature = "std")]
pub mod message_filter;
#[cfg(feature = "std")]
pub mod message_network;
use core::str::FromStr;
use core::{fmt, ops};
use hex::FromHex;
use internals::{impl_to_hex_from_lower_hex, write_err};
use io::{BufRead, Write};
use crate::consensus::encode::{self, Decodable, Encodable};
use crate::network::{Network, Params, TestnetVersion};
use crate::prelude::{Borrow, BorrowMut, String, ToOwned};
#[rustfmt::skip]
#[doc(inline)]
#[cfg(feature = "std")]
pub use self::address::Address;
/// Version of the protocol as appearing in network message headers.
///
/// This constant is used to signal to other peers which features you support. Increasing it implies
/// that your software also supports every feature prior to this version. Doing so without support
/// may lead to you incorrectly banning other peers or other peers banning you.
///
/// These are the features required for each version:
/// 70016 - Support receiving `wtxidrelay` message between `version` and `verack` message
/// 70015 - Support receiving invalid compact blocks from a peer without banning them
/// 70014 - Support compact block messages `sendcmpct`, `cmpctblock`, `getblocktxn` and `blocktxn`
/// 70013 - Support `feefilter` message
/// 70012 - Support `sendheaders` message and announce new blocks via headers rather than inv
/// 70011 - Support NODE_BLOOM service flag and don't support bloom filter messages if it is not set
/// 70002 - Support `reject` message
/// 70001 - Support bloom filter messages `filterload`, `filterclear` `filteradd`, `merkleblock` and FILTERED_BLOCK inventory type
/// 60002 - Support `mempool` message
/// 60001 - Support `pong` message and nonce in `ping` message
pub const PROTOCOL_VERSION: u32 = 70001;
/// Flags to indicate which network services a node supports.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ServiceFlags(u64);
impl ServiceFlags {
/// NONE means no services supported.
pub const NONE: ServiceFlags = ServiceFlags(0);
/// NETWORK means that the node is capable of serving the complete block chain. It is currently
/// set by all Bitcoin Core non pruned nodes, and is unset by SPV clients or other light
/// clients.
pub const NETWORK: ServiceFlags = ServiceFlags(1 << 0);
/// GETUTXO means the node is capable of responding to the getutxo protocol request. Bitcoin
/// Core does not support this but a patch set called Bitcoin XT does.
/// See BIP 64 for details on how this is implemented.
pub const GETUTXO: ServiceFlags = ServiceFlags(1 << 1);
/// BLOOM means the node is capable and willing to handle bloom-filtered connections. Bitcoin
/// Core nodes used to support this by default, without advertising this bit, but no longer do
/// as of protocol version 70011 (= NO_BLOOM_VERSION)
pub const BLOOM: ServiceFlags = ServiceFlags(1 << 2);
/// WITNESS indicates that a node can be asked for blocks and transactions including witness
/// data.
pub const WITNESS: ServiceFlags = ServiceFlags(1 << 3);
/// COMPACT_FILTERS means the node will service basic block filter requests.
/// See BIP157 and BIP158 for details on how this is implemented.
pub const COMPACT_FILTERS: ServiceFlags = ServiceFlags(1 << 6);
/// NETWORK_LIMITED means the same as NODE_NETWORK with the limitation of only serving the last
/// 288 (2 day) blocks.
/// See BIP159 for details on how this is implemented.
pub const NETWORK_LIMITED: ServiceFlags = ServiceFlags(1 << 10);
/// P2P_V2 indicates that the node supports the P2P v2 encrypted transport protocol.
/// See BIP324 for details on how this is implemented.
pub const P2P_V2: ServiceFlags = ServiceFlags(1 << 11);
// NOTE: When adding new flags, remember to update the Display impl accordingly.
/// Add [ServiceFlags] together.
///
/// Returns itself.
pub fn add(&mut self, other: ServiceFlags) -> ServiceFlags {
self.0 |= other.0;
*self
}
/// Remove [ServiceFlags] from this.
///
/// Returns itself.
pub fn remove(&mut self, other: ServiceFlags) -> ServiceFlags {
self.0 &= !other.0;
*self
}
/// Check whether [ServiceFlags] are included in this one.
pub fn has(self, flags: ServiceFlags) -> bool { (self.0 | flags.0) == self.0 }
/// Gets the integer representation of this [`ServiceFlags`].
pub fn to_u64(self) -> u64 { self.0 }
}
impl fmt::LowerHex for ServiceFlags {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(&self.0, f) }
}
impl_to_hex_from_lower_hex!(ServiceFlags, |service_flags: &ServiceFlags| 16
- service_flags.0.leading_zeros() as usize / 4);
impl fmt::UpperHex for ServiceFlags {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::UpperHex::fmt(&self.0, f) }
}
impl fmt::Display for ServiceFlags {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut flags = *self;
if flags == ServiceFlags::NONE {
return write!(f, "ServiceFlags(NONE)");
}
let mut first = true;
macro_rules! write_flag {
($f:ident) => {
if flags.has(ServiceFlags::$f) {
if !first {
write!(f, "|")?;
}
first = false;
write!(f, stringify!($f))?;
flags.remove(ServiceFlags::$f);
}
};
}
write!(f, "ServiceFlags(")?;
write_flag!(NETWORK);
write_flag!(GETUTXO);
write_flag!(BLOOM);
write_flag!(WITNESS);
write_flag!(COMPACT_FILTERS);
write_flag!(NETWORK_LIMITED);
write_flag!(P2P_V2);
// If there are unknown flags left, we append them in hex.
if flags != ServiceFlags::NONE {
if !first {
write!(f, "|")?;
}
write!(f, "0x{:x}", flags)?;
}
write!(f, ")")
}
}
impl From<u64> for ServiceFlags {
fn from(f: u64) -> Self { ServiceFlags(f) }
}
impl From<ServiceFlags> for u64 {
fn from(flags: ServiceFlags) -> Self { flags.0 }
}
impl ops::BitOr for ServiceFlags {
type Output = Self;
fn bitor(mut self, rhs: Self) -> Self { self.add(rhs) }
}
impl ops::BitOrAssign for ServiceFlags {
fn bitor_assign(&mut self, rhs: Self) { self.add(rhs); }
}
impl ops::BitXor for ServiceFlags {
type Output = Self;
fn bitxor(mut self, rhs: Self) -> Self { self.remove(rhs) }
}
impl ops::BitXorAssign for ServiceFlags {
fn bitxor_assign(&mut self, rhs: Self) { self.remove(rhs); }
}
impl Encodable for ServiceFlags {
#[inline]
fn consensus_encode<W: Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
self.0.consensus_encode(w)
}
}
impl Decodable for ServiceFlags {
#[inline]
fn consensus_decode<R: BufRead + ?Sized>(r: &mut R) -> Result<Self, encode::Error> {
Ok(ServiceFlags(Decodable::consensus_decode(r)?))
}
}
/// Network magic bytes to identify the cryptocurrency network the message was intended for.
#[derive(Copy, PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
pub struct Magic([u8; 4]);
impl Magic {
/// Bitcoin mainnet network magic bytes.
pub const BITCOIN: Self = Self([0xF9, 0xBE, 0xB4, 0xD9]);
/// Bitcoin testnet3 network magic bytes.
#[deprecated(since = "0.33.0", note = "use `TESTNET3` instead")]
pub const TESTNET: Self = Self([0x0B, 0x11, 0x09, 0x07]);
/// Bitcoin testnet3 network magic bytes.
pub const TESTNET3: Self = Self([0x0B, 0x11, 0x09, 0x07]);
/// Bitcoin testnet4 network magic bytes.
pub const TESTNET4: Self = Self([0x1c, 0x16, 0x3f, 0x28]);
/// Bitcoin signet network magic bytes.
pub const SIGNET: Self = Self([0x0A, 0x03, 0xCF, 0x40]);
/// Bitcoin regtest network magic bytes.
pub const REGTEST: Self = Self([0xFA, 0xBF, 0xB5, 0xDA]);
/// Construct a new network magic from bytes.
pub const fn from_bytes(bytes: [u8; 4]) -> Magic { Magic(bytes) }
/// Get network magic bytes.
pub fn to_bytes(self) -> [u8; 4] { self.0 }
/// Returns the magic bytes for the network defined by `params`.
pub fn from_params(params: impl AsRef<Params>) -> Self { params.as_ref().network.into() }
}
impl FromStr for Magic {
type Err = ParseMagicError;
fn from_str(s: &str) -> Result<Magic, Self::Err> {
match <[u8; 4]>::from_hex(s) {
Ok(magic) => Ok(Magic::from_bytes(magic)),
Err(e) => Err(ParseMagicError { error: e, magic: s.to_owned() }),
}
}
}
impl From<Network> for Magic {
fn from(network: Network) -> Self {
match network {
Network::Bitcoin => Magic::BITCOIN,
Network::Testnet(TestnetVersion::V3) => Magic::TESTNET3,
Network::Testnet(TestnetVersion::V4) => Magic::TESTNET4,
Network::Signet => Magic::SIGNET,
Network::Regtest => Magic::REGTEST,
// Remember to add the `TryFrom<Magic>` for new networks
}
}
}
impl TryFrom<Magic> for Network {
type Error = UnknownMagicError;
fn try_from(magic: Magic) -> Result<Self, Self::Error> {
match magic {
Magic::BITCOIN => Ok(Network::Bitcoin),
Magic::TESTNET3 => Ok(Network::Testnet(TestnetVersion::V3)),
Magic::TESTNET4 => Ok(Network::Testnet(TestnetVersion::V4)),
Magic::SIGNET => Ok(Network::Signet),
Magic::REGTEST => Ok(Network::Regtest),
_ => Err(UnknownMagicError(magic)),
}
}
}
impl fmt::Display for Magic {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
hex::fmt_hex_exact!(f, 4, &self.0, hex::Case::Lower)?;
Ok(())
}
}
impl fmt::Debug for Magic {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { fmt::Display::fmt(self, f) }
}
impl fmt::LowerHex for Magic {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
hex::fmt_hex_exact!(f, 4, &self.0, hex::Case::Lower)?;
Ok(())
}
}
impl_to_hex_from_lower_hex!(Magic, |_| 8);
impl fmt::UpperHex for Magic {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
hex::fmt_hex_exact!(f, 4, &self.0, hex::Case::Upper)?;
Ok(())
}
}
impl Encodable for Magic {
fn consensus_encode<W: Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
self.0.consensus_encode(writer)
}
}
impl Decodable for Magic {
fn consensus_decode<R: BufRead + ?Sized>(reader: &mut R) -> Result<Self, encode::Error> {
Ok(Magic(Decodable::consensus_decode(reader)?))
}
}
impl AsRef<[u8]> for Magic {
fn as_ref(&self) -> &[u8] { &self.0 }
}
impl AsRef<[u8; 4]> for Magic {
fn as_ref(&self) -> &[u8; 4] { &self.0 }
}
impl AsMut<[u8]> for Magic {
fn as_mut(&mut self) -> &mut [u8] { &mut self.0 }
}
impl AsMut<[u8; 4]> for Magic {
fn as_mut(&mut self) -> &mut [u8; 4] { &mut self.0 }
}
impl Borrow<[u8]> for Magic {
fn borrow(&self) -> &[u8] { &self.0 }
}
impl Borrow<[u8; 4]> for Magic {
fn borrow(&self) -> &[u8; 4] { &self.0 }
}
impl BorrowMut<[u8]> for Magic {
fn borrow_mut(&mut self) -> &mut [u8] { &mut self.0 }
}
impl BorrowMut<[u8; 4]> for Magic {
fn borrow_mut(&mut self) -> &mut [u8; 4] { &mut self.0 }
}
/// An error in parsing magic bytes.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct ParseMagicError {
/// The error that occurred when parsing the string.
error: hex::HexToArrayError,
/// The byte string that failed to parse.
magic: String,
}
impl fmt::Display for ParseMagicError {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write_err!(f, "failed to parse {} as network magic", self.magic; self.error)
}
}
#[cfg(feature = "std")]
impl std::error::Error for ParseMagicError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { Some(&self.error) }
}
/// Error in creating a Network from Magic bytes.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct UnknownMagicError(Magic);
impl fmt::Display for UnknownMagicError {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "unknown network magic {}", self.0)
}
}
#[cfg(feature = "std")]
impl std::error::Error for UnknownMagicError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
}
#[cfg(test)]
mod tests {
use super::*;
use crate::consensus::encode::{deserialize, serialize};
#[test]
fn serialize_deserialize() {
assert_eq!(serialize(&Magic::BITCOIN), &[0xf9, 0xbe, 0xb4, 0xd9]);
let magic: Magic = Network::Bitcoin.into();
assert_eq!(serialize(&magic), &[0xf9, 0xbe, 0xb4, 0xd9]);
assert_eq!(serialize(&Magic::TESTNET3), &[0x0b, 0x11, 0x09, 0x07]);
let magic: Magic = Network::Testnet(TestnetVersion::V3).into();
assert_eq!(serialize(&magic), &[0x0b, 0x11, 0x09, 0x07]);
assert_eq!(serialize(&Magic::TESTNET4), &[0x1c, 0x16, 0x3f, 0x28]);
let magic: Magic = Network::Testnet(TestnetVersion::V4).into();
assert_eq!(serialize(&magic), &[0x1c, 0x16, 0x3f, 0x28]);
assert_eq!(serialize(&Magic::SIGNET), &[0x0a, 0x03, 0xcf, 0x40]);
let magic: Magic = Network::Signet.into();
assert_eq!(serialize(&magic), &[0x0a, 0x03, 0xcf, 0x40]);
assert_eq!(serialize(&Magic::REGTEST), &[0xfa, 0xbf, 0xb5, 0xda]);
let magic: Magic = Network::Regtest.into();
assert_eq!(serialize(&magic), &[0xfa, 0xbf, 0xb5, 0xda]);
assert_eq!(
deserialize::<Magic>(&[0xf9, 0xbe, 0xb4, 0xd9]).ok(),
Some(Network::Bitcoin.into())
);
assert_eq!(
deserialize::<Magic>(&[0x0b, 0x11, 0x09, 0x07]).ok(),
Some(Network::Testnet(TestnetVersion::V3).into())
);
assert_eq!(
deserialize::<Magic>(&[0x1c, 0x16, 0x3f, 0x28]).ok(),
Some(Network::Testnet(TestnetVersion::V4).into())
);
assert_eq!(
deserialize::<Magic>(&[0x0a, 0x03, 0xcf, 0x40]).ok(),
Some(Network::Signet.into())
);
assert_eq!(
deserialize::<Magic>(&[0xfa, 0xbf, 0xb5, 0xda]).ok(),
Some(Network::Regtest.into())
);
}
#[test]
fn service_flags_test() {
let all = [
ServiceFlags::NETWORK,
ServiceFlags::GETUTXO,
ServiceFlags::BLOOM,
ServiceFlags::WITNESS,
ServiceFlags::COMPACT_FILTERS,
ServiceFlags::NETWORK_LIMITED,
ServiceFlags::P2P_V2,
];
let mut flags = ServiceFlags::NONE;
for f in all.iter() {
assert!(!flags.has(*f));
}
flags |= ServiceFlags::WITNESS;
assert_eq!(flags, ServiceFlags::WITNESS);
let mut flags2 = flags | ServiceFlags::GETUTXO;
for f in all.iter() {
assert_eq!(flags2.has(*f), *f == ServiceFlags::WITNESS || *f == ServiceFlags::GETUTXO);
}
flags2 ^= ServiceFlags::WITNESS;
assert_eq!(flags2, ServiceFlags::GETUTXO);
flags2 |= ServiceFlags::COMPACT_FILTERS;
flags2 ^= ServiceFlags::GETUTXO;
assert_eq!(flags2, ServiceFlags::COMPACT_FILTERS);
// Test formatting.
assert_eq!("ServiceFlags(NONE)", ServiceFlags::NONE.to_string());
assert_eq!("ServiceFlags(WITNESS)", ServiceFlags::WITNESS.to_string());
assert_eq!("ServiceFlags(P2P_V2)", ServiceFlags::P2P_V2.to_string());
let flag = ServiceFlags::WITNESS
| ServiceFlags::BLOOM
| ServiceFlags::NETWORK
| ServiceFlags::P2P_V2;
assert_eq!("ServiceFlags(NETWORK|BLOOM|WITNESS|P2P_V2)", flag.to_string());
let flag = ServiceFlags::WITNESS | 0xf0.into();
assert_eq!("ServiceFlags(WITNESS|COMPACT_FILTERS|0xb0)", flag.to_string());
}
#[test]
fn magic_from_str() {
let known_network_magic_strs = [
("f9beb4d9", Network::Bitcoin),
("0b110907", Network::Testnet(TestnetVersion::V3)),
("1c163f28", Network::Testnet(TestnetVersion::V4)),
("fabfb5da", Network::Regtest),
("0a03cf40", Network::Signet),
];
for (magic_str, network) in &known_network_magic_strs {
let magic: Magic = magic_str.parse::<Magic>().unwrap();
assert_eq!(Network::try_from(magic).unwrap(), *network);
assert_eq!(&magic.to_string(), magic_str);
}
}
}

View File

@ -11,6 +11,7 @@ cargo-fuzz = true
[dependencies]
honggfuzz = { version = "0.5.56", default-features = false }
bitcoin = { path = "../bitcoin", features = [ "serde" ] }
p2p = { path = "../p2p", package = "bitcoin-p2p-messages" }
serde = { version = "1.0.103", features = [ "derive" ] }
serde_json = "1.0"

View File

@ -1,7 +1,7 @@
use honggfuzz::fuzz;
fn do_test(data: &[u8]) {
let _: Result<bitcoin::p2p::message::RawNetworkMessage, _> =
let _: Result<p2p::message::RawNetworkMessage, _> =
bitcoin::consensus::encode::deserialize(data);
}

View File

@ -2,7 +2,7 @@ use std::convert::TryFrom;
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
use bitcoin::consensus::Decodable;
use bitcoin::p2p::address::AddrV2;
use p2p::address::AddrV2;
use honggfuzz::fuzz;
fn do_test(data: &[u8]) {

View File

@ -13,8 +13,18 @@ rust-version = "1.63.0"
exclude = ["tests", "contrib"]
[dependencies]
bitcoin = { path = "../bitcoin/" }
hashes = { package = "bitcoin_hashes", path = "../hashes", default-features = false, features = ["std"] }
hex = { package = "hex-conservative", version = "0.3.0", default-features = false, features = ["std"] }
internals = { package = "bitcoin-internals", path = "../internals", features = ["std"] }
io = { package = "bitcoin-io", path = "../io", default-features = false, features = ["std"] }
units = { package = "bitcoin-units", path = "../units", default-features = false, features = ["std"] }
[dev-dependencies]
hex_lit = "0.1.1"
[[example]]
name = "handshake"
[package.metadata.docs.rs]
all-features = true

View File

@ -4,8 +4,7 @@ use std::time::{SystemTime, UNIX_EPOCH};
use std::{env, process};
use bitcoin::consensus::{encode, Decodable};
use bitcoin::p2p::{self, address, message, message_network, Magic};
use bitcoin::secp256k1::rand::Rng;
use bitcoin_p2p_messages::{self, address, message, message_network, Magic, ServiceFlags};
fn main() {
// This example establishes a connection to a Bitcoin node, sends the initial
@ -71,19 +70,20 @@ fn build_version_message(address: SocketAddr) -> message::NetworkMessage {
let my_address = SocketAddr::new(IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), 0);
// "bitfield of features to be enabled for this connection"
let services = p2p::ServiceFlags::NONE;
let services = ServiceFlags::NONE;
// "standard UNIX timestamp in seconds"
let timestamp = SystemTime::now().duration_since(UNIX_EPOCH).expect("Time error").as_secs();
// "The network address of the node receiving this message"
let addr_recv = address::Address::new(&address, p2p::ServiceFlags::NONE);
let addr_recv = address::Address::new(&address, ServiceFlags::NONE);
// "The network address of the node emitting this message"
let addr_from = address::Address::new(&my_address, p2p::ServiceFlags::NONE);
let addr_from = address::Address::new(&my_address, ServiceFlags::NONE);
// "Node random nonce, randomly generated every time a version packet is sent. This nonce is used to detect connections to self."
let nonce: u64 = secp256k1::rand::thread_rng().gen();
// Because this crate does not include the `rand` dependency, this is a fixed value.
let nonce: u64 = 42;
// "User Agent (0x00 if string is 0 bytes long)"
let user_agent = String::from("rust-example");

View File

@ -10,9 +10,8 @@ use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV
use io::{BufRead, Read, Write};
use crate::consensus;
use crate::consensus::encode::{self, Decodable, Encodable, ReadExt, WriteExt};
use crate::p2p::ServiceFlags;
use bitcoin::consensus::encode::{self, Decodable, Encodable, ReadExt, WriteExt};
use crate::ServiceFlags;
/// A message which can be sent on the Bitcoin network
#[derive(Clone, PartialEq, Eq, Hash)]
@ -207,7 +206,7 @@ impl Encodable for AddrV2 {
network: u8,
bytes: &[u8],
) -> Result<usize, io::Error> {
Ok(network.consensus_encode(w)? + encode::consensus_encode_with_size(bytes, w)?)
Ok(network.consensus_encode(w)? + crate::consensus::consensus_encode_with_size(bytes, w)?)
}
Ok(match *self {
AddrV2::Ipv4(ref addr) => encode_addr(w, 1, &addr.octets())?,
@ -225,28 +224,28 @@ impl Decodable for AddrV2 {
let network_id = u8::consensus_decode(r)?;
let len = r.read_compact_size()?;
if len > 512 {
return Err(consensus::parse_failed_error("IP must be <= 512 bytes"));
return Err(crate::consensus::parse_failed_error("IP must be <= 512 bytes"));
}
Ok(match network_id {
1 => {
if len != 4 {
return Err(consensus::parse_failed_error("invalid IPv4 address"));
return Err(crate::consensus::parse_failed_error("invalid IPv4 address"));
}
let addr: [u8; 4] = Decodable::consensus_decode(r)?;
AddrV2::Ipv4(Ipv4Addr::new(addr[0], addr[1], addr[2], addr[3]))
}
2 => {
if len != 16 {
return Err(consensus::parse_failed_error("invalid IPv6 address"));
return Err(crate::consensus::parse_failed_error("invalid IPv6 address"));
}
let addr: [u16; 8] = read_be_address(r)?;
if addr[0..3] == ONION {
return Err(consensus::parse_failed_error(
return Err(crate::consensus::parse_failed_error(
"OnionCat address sent with IPv6 network id",
));
}
if addr[0..6] == [0x0000, 0x0000, 0x0000, 0x0000, 0x0000, 0xFFFF] {
return Err(consensus::parse_failed_error(
return Err(crate::consensus::parse_failed_error(
"IPV4 wrapped address sent with IPv6 network id",
));
}
@ -257,26 +256,26 @@ impl Decodable for AddrV2 {
4 => {
if len != 32 {
return Err(consensus::parse_failed_error("invalid TorV3 address"));
return Err(crate::consensus::parse_failed_error("invalid TorV3 address"));
}
let pubkey = Decodable::consensus_decode(r)?;
AddrV2::TorV3(pubkey)
}
5 => {
if len != 32 {
return Err(consensus::parse_failed_error("invalid I2P address"));
return Err(crate::consensus::parse_failed_error("invalid I2P address"));
}
let hash = Decodable::consensus_decode(r)?;
AddrV2::I2p(hash)
}
6 => {
if len != 16 {
return Err(consensus::parse_failed_error("invalid CJDNS address"));
return Err(crate::consensus::parse_failed_error("invalid CJDNS address"));
}
let addr: [u16; 8] = read_be_address(r)?;
// check the first byte for the CJDNS marker
if addr[0] >> 8 != 0xFC {
return Err(consensus::parse_failed_error("invalid CJDNS address"));
return Err(crate::consensus::parse_failed_error("invalid CJDNS address"));
}
AddrV2::Cjdns(Ipv6Addr::new(
addr[0], addr[1], addr[2], addr[3], addr[4], addr[5], addr[6], addr[7],
@ -438,12 +437,12 @@ impl std::error::Error for AddrV2ToIpv6AddrError {}
mod test {
use std::net::IpAddr;
use bitcoin::consensus::encode::{deserialize, serialize};
use hex::FromHex;
use hex_lit::hex;
use super::*;
use crate::consensus::encode::{deserialize, serialize};
use crate::p2p::message::AddrV2Payload;
use crate::message::AddrV2Payload;
#[test]
fn serialize_address() {

95
p2p/src/consensus.rs Normal file
View File

@ -0,0 +1,95 @@
use bitcoin::consensus::encode::WriteExt;
use io::Write;
pub(crate) fn consensus_encode_with_size<W: Write + ?Sized>(
data: &[u8],
w: &mut W,
) -> Result<usize, io::Error> {
Ok(w.emit_compact_size(data.len())? + w.emit_slice(data)?)
}
pub(crate) fn parse_failed_error(msg: &'static str) -> bitcoin::consensus::encode::Error {
bitcoin::consensus::encode::Error::Parse(bitcoin::consensus::encode::ParseError::ParseFailed(msg))
}
macro_rules! impl_consensus_encoding {
($thing:ident, $($field:ident),+) => (
impl bitcoin::consensus::Encodable for $thing {
#[inline]
fn consensus_encode<W: io::Write + ?Sized>(
&self,
w: &mut W,
) -> core::result::Result<usize, io::Error> {
let mut len = 0;
$(len += self.$field.consensus_encode(w)?;)+
Ok(len)
}
}
impl bitcoin::consensus::Decodable for $thing {
#[inline]
fn consensus_decode_from_finite_reader<R: io::BufRead + ?Sized>(
r: &mut R,
) -> core::result::Result<$thing, bitcoin::consensus::encode::Error> {
Ok($thing {
$($field: bitcoin::consensus::Decodable::consensus_decode_from_finite_reader(r)?),+
})
}
#[inline]
fn consensus_decode<R: io::BufRead + ?Sized>(
r: &mut R,
) -> core::result::Result<$thing, bitcoin::consensus::encode::Error> {
let mut r = r.take(internals::ToU64::to_u64(bitcoin::consensus::encode::MAX_VEC_SIZE));
Ok($thing {
$($field: bitcoin::consensus::Decodable::consensus_decode(&mut r)?),+
})
}
}
);
}
pub(crate) use impl_consensus_encoding;
macro_rules! impl_vec_wrapper {
($wrapper: ident, $type: ty) => {
impl bitcoin::consensus::encode::Encodable for $wrapper {
#[inline]
fn consensus_encode<W: io::Write + ?Sized>(
&self,
w: &mut W,
) -> core::result::Result<usize, io::Error> {
let mut len = 0;
len += w.emit_compact_size(self.0.len())?;
for c in self.0.iter() {
len += c.consensus_encode(w)?;
}
Ok(len)
}
}
impl bitcoin::consensus::encode::Decodable for $wrapper {
#[inline]
fn consensus_decode_from_finite_reader<R: io::BufRead + ?Sized>(
r: &mut R,
) -> core::result::Result<$wrapper, bitcoin::consensus::encode::Error> {
let len = r.read_compact_size()?;
// Do not allocate upfront more items than if the sequence of type
// occupied roughly quarter a block. This should never be the case
// for normal data, but even if that's not true - `push` will just
// reallocate.
// Note: OOM protection relies on reader eventually running out of
// data to feed us.
let max_capacity =
bitcoin::consensus::encode::MAX_VEC_SIZE / 4 / core::mem::size_of::<$type>();
let mut ret = Vec::with_capacity(core::cmp::min(len as usize, max_capacity));
for _ in 0..len {
ret.push(Decodable::consensus_decode_from_finite_reader(r)?);
}
Ok($wrapper(ret))
}
}
};
}
pub(crate) use impl_vec_wrapper;

View File

@ -14,3 +14,495 @@
#![allow(clippy::needless_question_mark)] // https://github.com/rust-bitcoin/rust-bitcoin/pull/2134
#![allow(clippy::manual_range_contains)] // More readable than clippy's format.
#![allow(clippy::uninlined_format_args)] // Allow `format!("{}", x)`instead of enforcing `format!("{x}")`
pub mod address;
pub mod message;
pub mod message_blockdata;
pub mod message_bloom;
pub mod message_compact_blocks;
pub mod message_filter;
pub mod message_network;
mod consensus;
extern crate alloc;
use core::str::FromStr;
use core::{fmt, ops};
use std::borrow::{Borrow, BorrowMut, ToOwned};
use hex::FromHex;
use internals::impl_to_hex_from_lower_hex;
use io::{BufRead, Write};
use bitcoin::consensus::encode::{self, Decodable, Encodable};
use bitcoin::network::{Network, Params, TestnetVersion};
#[rustfmt::skip]
#[doc(inline)]
pub use self::address::Address;
/// Version of the protocol as appearing in network message headers.
///
/// This constant is used to signal to other peers which features you support. Increasing it implies
/// that your software also supports every feature prior to this version. Doing so without support
/// may lead to you incorrectly banning other peers or other peers banning you.
///
/// These are the features required for each version:
/// 70016 - Support receiving `wtxidrelay` message between `version` and `verack` message
/// 70015 - Support receiving invalid compact blocks from a peer without banning them
/// 70014 - Support compact block messages `sendcmpct`, `cmpctblock`, `getblocktxn` and `blocktxn`
/// 70013 - Support `feefilter` message
/// 70012 - Support `sendheaders` message and announce new blocks via headers rather than inv
/// 70011 - Support NODE_BLOOM service flag and don't support bloom filter messages if it is not set
/// 70002 - Support `reject` message
/// 70001 - Support bloom filter messages `filterload`, `filterclear` `filteradd`, `merkleblock` and FILTERED_BLOCK inventory type
/// 60002 - Support `mempool` message
/// 60001 - Support `pong` message and nonce in `ping` message
pub const PROTOCOL_VERSION: u32 = 70001;
/// Flags to indicate which network services a node supports.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct ServiceFlags(u64);
impl ServiceFlags {
/// NONE means no services supported.
pub const NONE: ServiceFlags = ServiceFlags(0);
/// NETWORK means that the node is capable of serving the complete block chain. It is currently
/// set by all Bitcoin Core non pruned nodes, and is unset by SPV clients or other light
/// clients.
pub const NETWORK: ServiceFlags = ServiceFlags(1 << 0);
/// GETUTXO means the node is capable of responding to the getutxo protocol request. Bitcoin
/// Core does not support this but a patch set called Bitcoin XT does.
/// See BIP 64 for details on how this is implemented.
pub const GETUTXO: ServiceFlags = ServiceFlags(1 << 1);
/// BLOOM means the node is capable and willing to handle bloom-filtered connections. Bitcoin
/// Core nodes used to support this by default, without advertising this bit, but no longer do
/// as of protocol version 70011 (= NO_BLOOM_VERSION)
pub const BLOOM: ServiceFlags = ServiceFlags(1 << 2);
/// WITNESS indicates that a node can be asked for blocks and transactions including witness
/// data.
pub const WITNESS: ServiceFlags = ServiceFlags(1 << 3);
/// COMPACT_FILTERS means the node will service basic block filter requests.
/// See BIP157 and BIP158 for details on how this is implemented.
pub const COMPACT_FILTERS: ServiceFlags = ServiceFlags(1 << 6);
/// NETWORK_LIMITED means the same as NODE_NETWORK with the limitation of only serving the last
/// 288 (2 day) blocks.
/// See BIP159 for details on how this is implemented.
pub const NETWORK_LIMITED: ServiceFlags = ServiceFlags(1 << 10);
/// P2P_V2 indicates that the node supports the P2P v2 encrypted transport protocol.
/// See BIP324 for details on how this is implemented.
pub const P2P_V2: ServiceFlags = ServiceFlags(1 << 11);
// NOTE: When adding new flags, remember to update the Display impl accordingly.
/// Add [ServiceFlags] together.
///
/// Returns itself.
#[must_use]
pub fn add(&mut self, other: ServiceFlags) -> ServiceFlags {
self.0 |= other.0;
*self
}
/// Remove [ServiceFlags] from this.
///
/// Returns itself.
#[must_use]
pub fn remove(&mut self, other: ServiceFlags) -> ServiceFlags {
self.0 &= !other.0;
*self
}
/// Check whether [ServiceFlags] are included in this one.
pub fn has(self, flags: ServiceFlags) -> bool { (self.0 | flags.0) == self.0 }
/// Gets the integer representation of this [`ServiceFlags`].
pub fn to_u64(self) -> u64 { self.0 }
}
impl fmt::LowerHex for ServiceFlags {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::LowerHex::fmt(&self.0, f) }
}
impl_to_hex_from_lower_hex!(ServiceFlags, |service_flags: &ServiceFlags| 16
- service_flags.0.leading_zeros() as usize / 4);
impl fmt::UpperHex for ServiceFlags {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::UpperHex::fmt(&self.0, f) }
}
impl fmt::Display for ServiceFlags {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut flags = *self;
if flags == ServiceFlags::NONE {
return write!(f, "ServiceFlags(NONE)");
}
let mut first = true;
macro_rules! write_flag {
($f:ident) => {
if flags.has(ServiceFlags::$f) {
if !first {
write!(f, "|")?;
}
first = false;
write!(f, stringify!($f))?;
let _ = flags.remove(ServiceFlags::$f);
}
};
}
write!(f, "ServiceFlags(")?;
write_flag!(NETWORK);
write_flag!(GETUTXO);
write_flag!(BLOOM);
write_flag!(WITNESS);
write_flag!(COMPACT_FILTERS);
write_flag!(NETWORK_LIMITED);
write_flag!(P2P_V2);
// If there are unknown flags left, we append them in hex.
if flags != ServiceFlags::NONE {
if !first {
write!(f, "|")?;
}
write!(f, "0x{:x}", flags)?;
}
write!(f, ")")
}
}
impl From<u64> for ServiceFlags {
fn from(f: u64) -> Self { ServiceFlags(f) }
}
impl From<ServiceFlags> for u64 {
fn from(flags: ServiceFlags) -> Self { flags.0 }
}
impl ops::BitOr for ServiceFlags {
type Output = Self;
fn bitor(mut self, rhs: Self) -> Self { self.add(rhs) }
}
impl ops::BitOrAssign for ServiceFlags {
fn bitor_assign(&mut self, rhs: Self) { let _ = self.add(rhs); }
}
impl ops::BitXor for ServiceFlags {
type Output = Self;
fn bitxor(mut self, rhs: Self) -> Self { self.remove(rhs) }
}
impl ops::BitXorAssign for ServiceFlags {
fn bitxor_assign(&mut self, rhs: Self) { let _ = self.remove(rhs); }
}
impl Encodable for ServiceFlags {
#[inline]
fn consensus_encode<W: Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
self.0.consensus_encode(w)
}
}
impl Decodable for ServiceFlags {
#[inline]
fn consensus_decode<R: BufRead + ?Sized>(r: &mut R) -> Result<Self, encode::Error> {
Ok(ServiceFlags(Decodable::consensus_decode(r)?))
}
}
/// Network magic bytes to identify the cryptocurrency network the message was intended for.
#[derive(Copy, PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
pub struct Magic([u8; 4]);
impl Magic {
/// Bitcoin mainnet network magic bytes.
pub const BITCOIN: Self = Self([0xF9, 0xBE, 0xB4, 0xD9]);
/// Bitcoin testnet3 network magic bytes.
#[deprecated(since = "0.33.0", note = "use `TESTNET3` instead")]
pub const TESTNET: Self = Self([0x0B, 0x11, 0x09, 0x07]);
/// Bitcoin testnet3 network magic bytes.
pub const TESTNET3: Self = Self([0x0B, 0x11, 0x09, 0x07]);
/// Bitcoin testnet4 network magic bytes.
pub const TESTNET4: Self = Self([0x1c, 0x16, 0x3f, 0x28]);
/// Bitcoin signet network magic bytes.
pub const SIGNET: Self = Self([0x0A, 0x03, 0xCF, 0x40]);
/// Bitcoin regtest network magic bytes.
pub const REGTEST: Self = Self([0xFA, 0xBF, 0xB5, 0xDA]);
/// Construct a new network magic from bytes.
pub const fn from_bytes(bytes: [u8; 4]) -> Magic { Magic(bytes) }
/// Get network magic bytes.
pub fn to_bytes(self) -> [u8; 4] { self.0 }
/// Returns the magic bytes for the network defined by `params`.
pub fn from_params(params: impl AsRef<Params>) -> Option<Self> { params.as_ref().network.try_into().ok() }
}
impl FromStr for Magic {
type Err = ParseMagicError;
fn from_str(s: &str) -> Result<Magic, Self::Err> {
match <[u8; 4]>::from_hex(s) {
Ok(magic) => Ok(Magic::from_bytes(magic)),
Err(e) => Err(ParseMagicError { error: e, magic: s.to_owned() }),
}
}
}
impl TryFrom<Network> for Magic {
type Error = UnknownNetworkError;
fn try_from(network: Network) -> Result<Self, Self::Error> {
match network {
Network::Bitcoin => Ok(Magic::BITCOIN),
Network::Testnet(TestnetVersion::V3) => Ok(Magic::TESTNET3),
Network::Testnet(TestnetVersion::V4) => Ok(Magic::TESTNET4),
Network::Signet => Ok(Magic::SIGNET),
Network::Regtest => Ok(Magic::REGTEST),
_ => Err(UnknownNetworkError(network)),
}
}
}
impl TryFrom<Magic> for Network {
type Error = UnknownMagicError;
fn try_from(magic: Magic) -> Result<Self, Self::Error> {
match magic {
Magic::BITCOIN => Ok(Network::Bitcoin),
Magic::TESTNET3 => Ok(Network::Testnet(TestnetVersion::V3)),
Magic::TESTNET4 => Ok(Network::Testnet(TestnetVersion::V4)),
Magic::SIGNET => Ok(Network::Signet),
Magic::REGTEST => Ok(Network::Regtest),
_ => Err(UnknownMagicError(magic)),
}
}
}
impl fmt::Display for Magic {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
hex::fmt_hex_exact!(f, 4, &self.0, hex::Case::Lower)?;
Ok(())
}
}
impl fmt::Debug for Magic {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { fmt::Display::fmt(self, f) }
}
impl fmt::LowerHex for Magic {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
hex::fmt_hex_exact!(f, 4, &self.0, hex::Case::Lower)?;
Ok(())
}
}
impl_to_hex_from_lower_hex!(Magic, |_| 8);
impl fmt::UpperHex for Magic {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
hex::fmt_hex_exact!(f, 4, &self.0, hex::Case::Upper)?;
Ok(())
}
}
impl Encodable for Magic {
fn consensus_encode<W: Write + ?Sized>(&self, writer: &mut W) -> Result<usize, io::Error> {
self.0.consensus_encode(writer)
}
}
impl Decodable for Magic {
fn consensus_decode<R: BufRead + ?Sized>(reader: &mut R) -> Result<Self, encode::Error> {
Ok(Magic(Decodable::consensus_decode(reader)?))
}
}
impl AsRef<[u8]> for Magic {
fn as_ref(&self) -> &[u8] { &self.0 }
}
impl AsRef<[u8; 4]> for Magic {
fn as_ref(&self) -> &[u8; 4] { &self.0 }
}
impl AsMut<[u8]> for Magic {
fn as_mut(&mut self) -> &mut [u8] { &mut self.0 }
}
impl AsMut<[u8; 4]> for Magic {
fn as_mut(&mut self) -> &mut [u8; 4] { &mut self.0 }
}
impl Borrow<[u8]> for Magic {
fn borrow(&self) -> &[u8] { &self.0 }
}
impl Borrow<[u8; 4]> for Magic {
fn borrow(&self) -> &[u8; 4] { &self.0 }
}
impl BorrowMut<[u8]> for Magic {
fn borrow_mut(&mut self) -> &mut [u8] { &mut self.0 }
}
impl BorrowMut<[u8; 4]> for Magic {
fn borrow_mut(&mut self) -> &mut [u8; 4] { &mut self.0 }
}
/// An error in parsing magic bytes.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct ParseMagicError {
/// The error that occurred when parsing the string.
error: hex::HexToArrayError,
/// The byte string that failed to parse.
magic: String,
}
impl fmt::Display for ParseMagicError {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "failed to parse {} as network magic", self.magic)
}
}
impl std::error::Error for ParseMagicError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { Some(&self.error) }
}
/// Error in creating a Network from Magic bytes.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct UnknownMagicError(Magic);
impl fmt::Display for UnknownMagicError {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "unknown network magic {}", self.0)
}
}
impl std::error::Error for UnknownMagicError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
}
/// Error in creating a Magic from a Network.
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct UnknownNetworkError(Network);
impl fmt::Display for UnknownNetworkError {
fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(f, "unknown network {}", self.0)
}
}
impl std::error::Error for UnknownNetworkError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
}
#[cfg(test)]
mod tests {
use super::*;
use bitcoin::consensus::encode::{deserialize, serialize};
#[test]
fn serialize_deserialize() {
assert_eq!(serialize(&Magic::BITCOIN), &[0xf9, 0xbe, 0xb4, 0xd9]);
let magic: Magic = Network::Bitcoin.try_into().unwrap();
assert_eq!(serialize(&magic), &[0xf9, 0xbe, 0xb4, 0xd9]);
assert_eq!(serialize(&Magic::TESTNET3), &[0x0b, 0x11, 0x09, 0x07]);
let magic: Magic = Network::Testnet(TestnetVersion::V3).try_into().unwrap();
assert_eq!(serialize(&magic), &[0x0b, 0x11, 0x09, 0x07]);
assert_eq!(serialize(&Magic::TESTNET4), &[0x1c, 0x16, 0x3f, 0x28]);
let magic: Magic = Network::Testnet(TestnetVersion::V4).try_into().unwrap();
assert_eq!(serialize(&magic), &[0x1c, 0x16, 0x3f, 0x28]);
assert_eq!(serialize(&Magic::SIGNET), &[0x0a, 0x03, 0xcf, 0x40]);
let magic: Magic = Network::Signet.try_into().unwrap();
assert_eq!(serialize(&magic), &[0x0a, 0x03, 0xcf, 0x40]);
assert_eq!(serialize(&Magic::REGTEST), &[0xfa, 0xbf, 0xb5, 0xda]);
let magic: Magic = Network::Regtest.try_into().unwrap();
assert_eq!(serialize(&magic), &[0xfa, 0xbf, 0xb5, 0xda]);
assert_eq!(deserialize::<Magic>(&[0xf9, 0xbe, 0xb4, 0xd9]).ok(), Network::Bitcoin.try_into().ok());
assert_eq!(
deserialize::<Magic>(&[0x0b, 0x11, 0x09, 0x07]).ok(),
Network::Testnet(TestnetVersion::V3).try_into().ok()
);
assert_eq!(
deserialize::<Magic>(&[0x1c, 0x16, 0x3f, 0x28]).ok(),
Network::Testnet(TestnetVersion::V4).try_into().ok()
);
assert_eq!(deserialize::<Magic>(&[0x0a, 0x03, 0xcf, 0x40]).ok(), Network::Signet.try_into().ok());
assert_eq!(deserialize::<Magic>(&[0xfa, 0xbf, 0xb5, 0xda]).ok(), Network::Regtest.try_into().ok());
}
#[test]
fn service_flags_test() {
let all = [
ServiceFlags::NETWORK,
ServiceFlags::GETUTXO,
ServiceFlags::BLOOM,
ServiceFlags::WITNESS,
ServiceFlags::COMPACT_FILTERS,
ServiceFlags::NETWORK_LIMITED,
ServiceFlags::P2P_V2,
];
let mut flags = ServiceFlags::NONE;
for f in all.iter() {
assert!(!flags.has(*f));
}
flags |= ServiceFlags::WITNESS;
assert_eq!(flags, ServiceFlags::WITNESS);
let mut flags2 = flags | ServiceFlags::GETUTXO;
for f in all.iter() {
assert_eq!(flags2.has(*f), *f == ServiceFlags::WITNESS || *f == ServiceFlags::GETUTXO);
}
flags2 ^= ServiceFlags::WITNESS;
assert_eq!(flags2, ServiceFlags::GETUTXO);
flags2 |= ServiceFlags::COMPACT_FILTERS;
flags2 ^= ServiceFlags::GETUTXO;
assert_eq!(flags2, ServiceFlags::COMPACT_FILTERS);
// Test formatting.
assert_eq!("ServiceFlags(NONE)", ServiceFlags::NONE.to_string());
assert_eq!("ServiceFlags(WITNESS)", ServiceFlags::WITNESS.to_string());
assert_eq!("ServiceFlags(P2P_V2)", ServiceFlags::P2P_V2.to_string());
let flag = ServiceFlags::WITNESS
| ServiceFlags::BLOOM
| ServiceFlags::NETWORK
| ServiceFlags::P2P_V2;
assert_eq!("ServiceFlags(NETWORK|BLOOM|WITNESS|P2P_V2)", flag.to_string());
let flag = ServiceFlags::WITNESS | 0xf0.into();
assert_eq!("ServiceFlags(WITNESS|COMPACT_FILTERS|0xb0)", flag.to_string());
}
#[test]
fn magic_from_str() {
let known_network_magic_strs = [
("f9beb4d9", Network::Bitcoin),
("0b110907", Network::Testnet(TestnetVersion::V3)),
("1c163f28", Network::Testnet(TestnetVersion::V4)),
("fabfb5da", Network::Regtest),
("0a03cf40", Network::Signet),
];
for (magic_str, network) in &known_network_magic_strs {
let magic: Magic = magic_str.parse::<Magic>().unwrap();
assert_eq!(Network::try_from(magic).unwrap(), *network);
assert_eq!(&magic.to_string(), magic_str);
}
}
}

View File

@ -6,21 +6,23 @@
//! are used for (de)serializing Bitcoin objects for transmission on the network.
use core::{fmt, iter};
use std::borrow::Cow;
use std::boxed::Box;
use std::borrow::ToOwned;
use hashes::sha256d;
use internals::ToU64 as _;
use io::{BufRead, Write};
use crate::consensus::encode::{self, CheckedData, Decodable, Encodable, ReadExt, WriteExt};
use crate::merkle_tree::MerkleBlock;
use crate::p2p::address::{AddrV2Message, Address};
use crate::p2p::deser::impl_vec_wrapper;
use crate::p2p::{
use bitcoin::consensus::encode::{self, CheckedData, Decodable, Encodable, ReadExt, WriteExt};
use bitcoin::merkle_tree::MerkleBlock;
use crate::address::{AddrV2Message, Address};
use crate::consensus::impl_vec_wrapper;
use crate::{
message_blockdata, message_bloom, message_compact_blocks, message_filter, message_network,
Magic,
};
use crate::prelude::{Box, Cow, String, ToOwned, Vec};
use crate::{block, consensus, transaction};
use bitcoin::{block, transaction};
/// The maximum number of [super::message_blockdata::Inventory] items in an `inv` message.
///
@ -144,7 +146,6 @@ impl fmt::Display for CommandStringError {
}
}
#[cfg(feature = "std")]
impl std::error::Error for CommandStringError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None }
}
@ -518,7 +519,7 @@ impl Decodable for HeaderDeserializationWrapper {
for _ in 0..len {
ret.push(Decodable::consensus_decode(r)?);
if u8::consensus_decode(r)? != 0u8 {
return Err(consensus::parse_failed_error(
return Err(crate::consensus::parse_failed_error(
"Headers message should not contain transactions",
));
}
@ -718,21 +719,21 @@ mod test {
use units::BlockHeight;
use super::*;
use crate::bip152::BlockTransactionsRequest;
use crate::bip158::{FilterHash, FilterHeader};
use crate::block::{Block, BlockHash};
use crate::consensus::encode::{deserialize, deserialize_partial, serialize};
use crate::p2p::address::AddrV2;
use crate::p2p::message_blockdata::{GetBlocksMessage, GetHeadersMessage, Inventory};
use crate::p2p::message_bloom::{BloomFlags, FilterAdd, FilterLoad};
use crate::p2p::message_compact_blocks::{GetBlockTxn, SendCmpct};
use crate::p2p::message_filter::{
use bitcoin::bip152::BlockTransactionsRequest;
use bitcoin::bip158::{FilterHash, FilterHeader};
use bitcoin::block::{Block, BlockHash};
use bitcoin::consensus::encode::{deserialize, deserialize_partial, serialize};
use bitcoin::script::ScriptBuf;
use bitcoin::transaction::{Transaction, Txid};
use crate::address::AddrV2;
use crate::message_blockdata::{GetBlocksMessage, GetHeadersMessage, Inventory};
use crate::message_bloom::{BloomFlags, FilterAdd, FilterLoad};
use crate::message_compact_blocks::{GetBlockTxn, SendCmpct};
use crate::message_filter::{
CFCheckpt, CFHeaders, CFilter, GetCFCheckpt, GetCFHeaders, GetCFilters,
};
use crate::p2p::message_network::{Reject, RejectReason, VersionMessage};
use crate::p2p::ServiceFlags;
use crate::script::ScriptBuf;
use crate::transaction::{Transaction, Txid};
use crate::message_network::{Reject, RejectReason, VersionMessage};
use crate::ServiceFlags;
fn hash(array: [u8; 32]) -> sha256d::Hash { sha256d::Hash::from_byte_array(array) }
@ -740,7 +741,7 @@ mod test {
fn full_round_ser_der_raw_network_message() {
let version_msg: VersionMessage = deserialize(&hex!("721101000100000000000000e6e0845300000000010000000000000000000000000000000000ffff0000000000000100000000000000fd87d87eeb4364f22cf54dca59412db7208d47d920cffce83ee8102f5361746f7368693a302e392e39392f2c9f040001")).unwrap();
let tx: Transaction = deserialize(&hex!("0100000001a15d57094aa7a21a28cb20b59aab8fc7d1149a3bdbcddba9c622e4f5f6a99ece010000006c493046022100f93bb0e7d8db7bd46e40132d1f8242026e045f03a0efe71bbb8e3f475e970d790221009337cd7f1f929f00cc6ff01f03729b069a7c21b59b1736ddfee5db5946c5da8c0121033b9b137ee87d5a812d6f506efdd37f0affa7ffc310711c06c7f3e097c9447c52ffffffff0100e1f505000000001976a9140389035a9225b3839e2bbf32d826a1e222031fd888ac00000000")).unwrap();
let block: Block = deserialize(&include_bytes!("../../tests/data/testnet_block_000000000000045e0b1660b6445b5e5c5ab63c9a4f956be7e1e69be04fa4497b.raw")[..]).unwrap();
let block: Block = deserialize(&hex!("00608e2e094d41aecfbcbf8fe70cb60be57516b07db1bafee4c4de5dad760000000000004aec16eab3be95abe9c54e01cf850c14b8c5cad1bc6b2e73e811db5d5998ada404503e66fcff031b4ebd99d701010000000001010000000000000000000000000000000000000000000000000000000000000000ffffffff3402983a000404503e6604f1f617271083bc3d6600000000000000000007bb1b0a636b706f6f6c0d506f72746c616e642e484f444cffffffff0200f2052a010000001976a9142ce72b25fe97b52638c199acfaa5e3891ddfed5b88ac0000000000000000266a24aa21a9ede2f61c3f71d1defd3fa999dfa36953755c690689799962b48bebd836974e8cf90120000000000000000000000000000000000000000000000000000000000000000000000000")).unwrap();
let header: block::Header = deserialize(&hex!("010000004ddccd549d28f385ab457e98d1b11ce80bfea2c5ab93015ade4973e400000000bf4473e53794beae34e64fccc471dace6ae544180816f89591894e0f417a914cd74d6e49ffff001d323b3a7b")).unwrap();
let script: ScriptBuf =
deserialize(&hex!("1976a91431a420903c05a0a7de2de40c9f02ebedbacdc17288ac")).unwrap();

View File

@ -7,11 +7,10 @@
use io::{BufRead, Write};
use crate::block::BlockHash;
use crate::consensus::encode::{self, Decodable, Encodable};
use crate::internal_macros::impl_consensus_encoding;
use crate::p2p;
use crate::transaction::{Txid, Wtxid};
use bitcoin::block::BlockHash;
use bitcoin::consensus::encode::{self, Decodable, Encodable};
use bitcoin::transaction::{Txid, Wtxid};
use crate::consensus::impl_consensus_encoding;
/// An inventory item.
#[derive(PartialEq, Eq, Clone, Debug, Copy, Hash, PartialOrd, Ord)]
@ -127,7 +126,7 @@ pub struct GetHeadersMessage {
impl GetBlocksMessage {
/// Construct a new `getblocks` message
pub fn new(locator_hashes: Vec<BlockHash>, stop_hash: BlockHash) -> GetBlocksMessage {
GetBlocksMessage { version: p2p::PROTOCOL_VERSION, locator_hashes, stop_hash }
GetBlocksMessage { version: crate::PROTOCOL_VERSION, locator_hashes, stop_hash }
}
}
@ -136,7 +135,7 @@ impl_consensus_encoding!(GetBlocksMessage, version, locator_hashes, stop_hash);
impl GetHeadersMessage {
/// Construct a new `getheaders` message
pub fn new(locator_hashes: Vec<BlockHash>, stop_hash: BlockHash) -> GetHeadersMessage {
GetHeadersMessage { version: p2p::PROTOCOL_VERSION, locator_hashes, stop_hash }
GetHeadersMessage { version: crate::PROTOCOL_VERSION, locator_hashes, stop_hash }
}
}
@ -147,7 +146,7 @@ mod tests {
use hex_lit::hex;
use super::*;
use crate::consensus::encode::{deserialize, serialize};
use bitcoin::consensus::encode::{deserialize, serialize};
#[test]
fn getblocks_message() {

View File

@ -6,8 +6,8 @@
use io::{BufRead, Write};
use crate::consensus::{self, encode, Decodable, Encodable, ReadExt};
use crate::internal_macros::impl_consensus_encoding;
use bitcoin::consensus::{encode, Decodable, Encodable, ReadExt};
use crate::consensus::impl_consensus_encoding;
/// `filterload` message sets the current bloom filter
#[derive(Clone, PartialEq, Eq, Debug)]
@ -52,7 +52,7 @@ impl Decodable for BloomFlags {
0 => BloomFlags::None,
1 => BloomFlags::All,
2 => BloomFlags::PubkeyOnly,
_ => return Err(consensus::parse_failed_error("unknown bloom flag")),
_ => return Err(crate::consensus::parse_failed_error("unknown bloom flag")),
})
}
}

View File

@ -3,8 +3,8 @@
//!
//! BIP152 Compact Blocks network messages
use crate::bip152;
use crate::internal_macros::impl_consensus_encoding;
use bitcoin::bip152;
use crate::consensus::impl_consensus_encoding;
/// sendcmpct message
#[derive(PartialEq, Eq, Clone, Debug, Copy, PartialOrd, Ord, Hash)]

View File

@ -6,9 +6,9 @@
use units::BlockHeight;
use crate::bip158::{FilterHash, FilterHeader};
use crate::block::BlockHash;
use crate::internal_macros::impl_consensus_encoding;
use bitcoin::bip158::{FilterHash, FilterHeader};
use bitcoin::block::BlockHash;
use crate::consensus::impl_consensus_encoding;
/// getcfilters message
#[derive(PartialEq, Eq, Clone, Debug)]

View File

@ -4,16 +4,15 @@
//!
//! This module defines network messages which describe peers and their
//! capabilities.
use std::borrow::Cow;
use hashes::sha256d;
use io::{BufRead, Write};
use crate::consensus::{self, encode, Decodable, Encodable, ReadExt};
use crate::internal_macros::impl_consensus_encoding;
use crate::p2p;
use crate::p2p::address::Address;
use crate::p2p::ServiceFlags;
use crate::prelude::{Cow, String};
use bitcoin::consensus::{encode, Decodable, Encodable, ReadExt};
use crate::address::Address;
use crate::consensus::impl_consensus_encoding;
use crate::ServiceFlags;
// Some simple messages
@ -61,7 +60,7 @@ impl VersionMessage {
start_height: i32,
) -> VersionMessage {
VersionMessage {
version: p2p::PROTOCOL_VERSION,
version: crate::PROTOCOL_VERSION,
services,
timestamp,
receiver,
@ -126,7 +125,7 @@ impl Decodable for RejectReason {
0x41 => RejectReason::Dust,
0x42 => RejectReason::Fee,
0x43 => RejectReason::Checkpoint,
_ => return Err(consensus::parse_failed_error("unknown reject code")),
_ => return Err(crate::consensus::parse_failed_error("unknown reject code")),
})
}
}
@ -151,7 +150,7 @@ mod tests {
use hex_lit::hex;
use super::*;
use crate::consensus::encode::{deserialize, serialize};
use bitcoin::consensus::encode::{deserialize, serialize};
#[test]
fn version_message_test() {

View File

@ -20,7 +20,7 @@ use core::fmt;
use arbitrary::{Arbitrary, Unstructured};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use units::locktime::relative::TimeOverflowError;
use units::locktime::relative::{NumberOf512Seconds, TimeOverflowError};
use units::parse::{self, PrefixedHexError, UnprefixedHexError};
use crate::locktime::relative;
@ -156,11 +156,8 @@ impl Sequence {
/// Will return an error if the input cannot be encoded in 16 bits.
#[inline]
pub fn from_seconds_floor(seconds: u32) -> Result<Self, TimeOverflowError> {
if let Ok(interval) = u16::try_from(seconds / 512) {
Ok(Sequence::from_512_second_intervals(interval))
} else {
Err(TimeOverflowError::new(seconds))
}
let intervals = NumberOf512Seconds::from_seconds_floor(seconds)?;
Ok(Sequence::from_512_second_intervals(intervals.to_512_second_intervals()))
}
/// Constructs a new relative lock-time from seconds, converting the seconds into 512 second
@ -169,11 +166,8 @@ impl Sequence {
/// Will return an error if the input cannot be encoded in 16 bits.
#[inline]
pub fn from_seconds_ceil(seconds: u32) -> Result<Self, TimeOverflowError> {
if let Ok(interval) = u16::try_from((seconds + 511) / 512) {
Ok(Sequence::from_512_second_intervals(interval))
} else {
Err(TimeOverflowError::new(seconds))
}
let intervals = NumberOf512Seconds::from_seconds_ceil(seconds)?;
Ok(Sequence::from_512_second_intervals(intervals.to_512_second_intervals()))
}
/// Constructs a new sequence from a u32 value.

View File

@ -6,7 +6,7 @@
//!
//! These are general types for abstracting over block heights, they are not designed to use with
//! lock times. If you are creating lock times you should be using the
//! [`locktime::absolute::Height`] and [`locktime::relative::Height`] types.
//! [`locktime::absolute::Height`] and [`locktime::relative::NumberOfBlocks`] types.
//!
//! The difference between these types and the locktime types is that these types are thin wrappers
//! whereas the locktime types contain more complex locktime specific abstractions.

View File

@ -211,18 +211,6 @@ pub struct TimeOverflowError {
pub(crate) seconds: u32,
}
impl TimeOverflowError {
/// Constructs a new `TimeOverflowError` using `seconds`.
///
/// # Panics
///
/// If `seconds` would not actually overflow a `u16`.
pub fn new(seconds: u32) -> Self {
assert!(u16::try_from((seconds + 511) / 512).is_err());
Self { seconds }
}
}
impl fmt::Display for TimeOverflowError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(

View File

@ -5,6 +5,8 @@
use core::convert::Infallible;
use core::fmt;
#[cfg(feature = "arbitrary")]
use arbitrary::{Arbitrary, Unstructured};
use NumOpResult as R;
use crate::{Amount, FeeRate, SignedAmount, Weight};
@ -320,6 +322,32 @@ impl fmt::Display for MathOp {
}
}
#[cfg(feature = "arbitrary")]
impl<'a, T: Arbitrary<'a>> Arbitrary<'a> for NumOpResult<T> {
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
let choice = u.int_in_range(0..=1)?;
match choice {
0 => Ok(NumOpResult::Valid(T::arbitrary(u)?)),
_ => Ok(NumOpResult::Error(NumOpError(MathOp::arbitrary(u)?))),
}
}
}
#[cfg(feature = "arbitrary")]
impl<'a> Arbitrary<'a> for MathOp {
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
let choice = u.int_in_range(0..=5)?;
match choice {
0 => Ok(MathOp::Add),
1 => Ok(MathOp::Sub),
2 => Ok(MathOp::Mul),
3 => Ok(MathOp::Div),
4 => Ok(MathOp::Rem),
_ => Ok(MathOp::Neg),
}
}
}
#[cfg(test)]
mod tests {
use crate::MathOp;

View File

@ -12,39 +12,49 @@
#[cfg(feature = "arbitrary")]
use arbitrary::{Arbitrary, Unstructured};
// These imports test "typical" usage by user code.
use bitcoin_units::locktime::{absolute, relative}; // Typical usage is `absolute::Height`.
use bitcoin_units::locktime::{absolute, relative}; // Typical usage is `absolute::LockTime`.
use bitcoin_units::{
amount, block, fee_rate, locktime, parse, weight, Amount, BlockHeight, BlockInterval, BlockMtp,
BlockMtpInterval, BlockTime, CheckedSum, FeeRate, SignedAmount, Weight,
amount, block, fee_rate, locktime, parse, time, weight, Amount, BlockHeight,
BlockHeightInterval, BlockMtp, BlockMtpInterval, BlockTime, CheckedSum, FeeRate, MathOp,
NumOpResult, SignedAmount, Weight,
};
/// A struct that includes all public non-error enums.
#[derive(Debug)] // All public types implement Debug (C-DEBUG).
struct Enums {
a: amount::Denomination,
b: NumOpResult<Amount>,
c: MathOp,
}
impl Enums {
fn new() -> Self { Self { a: amount::Denomination::Bitcoin } }
fn new() -> Self {
Self {
a: amount::Denomination::Bitcoin,
b: NumOpResult::Valid(Amount::MAX),
c: MathOp::Add,
}
}
}
/// A struct that includes all public non-error structs.
#[derive(Debug)] // All public types implement Debug (C-DEBUG).
struct Structs {
a: Amount,
// Full path to show alphabetic sort order.
a: amount::Amount,
b: amount::Display,
c: SignedAmount,
d: BlockHeight,
e: BlockInterval,
f: FeeRate,
g: absolute::Height,
h: absolute::MedianTimePast,
i: relative::Height,
j: relative::Time,
k: Weight,
l: BlockTime,
m: BlockMtp,
n: BlockMtpInterval,
c: amount::SignedAmount,
d: block::BlockHeight,
e: block::BlockHeightInterval,
f: block::BlockMtp,
g: block::BlockMtpInterval,
h: fee_rate::FeeRate,
i: locktime::absolute::Height,
j: locktime::absolute::MedianTimePast,
k: locktime::relative::NumberOf512Seconds,
l: locktime::relative::NumberOfBlocks,
m: time::BlockTime,
n: weight::Weight,
}
impl Structs {
@ -54,16 +64,16 @@ impl Structs {
b: Amount::MAX.display_in(amount::Denomination::Bitcoin),
c: SignedAmount::MAX,
d: BlockHeight::MAX,
e: BlockInterval::MAX,
f: FeeRate::MAX,
g: absolute::Height::MAX,
h: absolute::MedianTimePast::MAX,
i: relative::Height::MAX,
j: relative::Time::MAX,
k: Weight::MAX,
l: BlockTime::from_u32(u32::MAX),
m: BlockMtp::MAX,
n: BlockMtpInterval::MAX,
e: BlockHeightInterval::MAX,
f: BlockMtp::MAX,
g: BlockMtpInterval::MAX,
h: FeeRate::MAX,
i: absolute::Height::MAX,
j: absolute::MedianTimePast::MAX,
k: relative::NumberOf512Seconds::MAX,
l: relative::NumberOfBlocks::MAX,
m: BlockTime::from_u32(u32::MAX),
n: Weight::MAX,
}
}
}
@ -83,19 +93,21 @@ impl Types {
// C-COMMON-TRAITS excluding `Default` and `Display`. `Display` is done in `./str.rs`.
#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct CommonTraits {
a: Amount,
c: SignedAmount,
d: BlockHeight,
e: BlockInterval,
f: FeeRate,
g: absolute::Height,
h: absolute::MedianTimePast,
i: relative::Height,
j: relative::Time,
k: Weight,
l: BlockTime,
m: BlockMtp,
n: BlockMtpInterval,
// Full path to show alphabetic sort order.
a: amount::Amount,
// b: amount::Display,
c: amount::SignedAmount,
d: block::BlockHeight,
e: block::BlockHeightInterval,
f: block::BlockMtp,
g: block::BlockMtpInterval,
h: fee_rate::FeeRate,
i: locktime::absolute::Height,
j: locktime::absolute::MedianTimePast,
k: locktime::relative::NumberOf512Seconds,
l: locktime::relative::NumberOfBlocks,
m: time::BlockTime,
n: weight::Weight,
}
/// A struct that includes all types that implement `Default`.
@ -103,10 +115,10 @@ struct CommonTraits {
struct Default {
a: Amount,
b: SignedAmount,
c: BlockInterval,
d: relative::Height,
e: relative::Time,
f: BlockMtpInterval,
c: BlockHeightInterval,
d: BlockMtpInterval,
e: relative::NumberOf512Seconds,
f: relative::NumberOfBlocks,
}
/// A struct that includes all public error types.
@ -124,28 +136,18 @@ struct Errors {
i: amount::PossiblyConfusingDenominationError,
j: amount::TooPreciseError,
k: amount::UnknownDenominationError,
l: amount::InputTooLargeError,
m: amount::InvalidCharacterError,
n: amount::MissingDenominationError,
o: amount::MissingDigitsError,
p: amount::OutOfRangeError,
q: amount::ParseAmountError,
r: amount::ParseDenominationError,
s: amount::ParseError,
t: amount::PossiblyConfusingDenominationError,
u: amount::TooPreciseError,
v: amount::UnknownDenominationError,
w: block::TooBigForRelativeHeightError,
x: locktime::absolute::ConversionError,
y: locktime::absolute::Height,
z: locktime::absolute::ParseHeightError,
aa: locktime::absolute::ParseTimeError,
ab: locktime::relative::TimeOverflowError,
ac: locktime::relative::InvalidHeightError,
ad: locktime::relative::InvalidTimeError,
ae: parse::ParseIntError,
af: parse::PrefixedHexError,
ag: parse::UnprefixedHexError,
l: block::TooBigForRelativeHeightError,
#[cfg(feature = "serde")]
m: fee_rate::serde::OverflowError,
n: locktime::absolute::ConversionError,
o: locktime::absolute::ParseHeightError,
p: locktime::absolute::ParseTimeError,
q: locktime::relative::InvalidHeightError,
r: locktime::relative::InvalidTimeError,
s: locktime::relative::TimeOverflowError,
t: parse::ParseIntError,
u: parse::PrefixedHexError,
v: parse::UnprefixedHexError,
}
#[test]
@ -156,8 +158,8 @@ fn api_can_use_modules_from_crate_root() {
#[test]
fn api_can_use_types_from_crate_root() {
use bitcoin_units::{
Amount, BlockHeight, BlockInterval, BlockMtp, BlockMtpInterval, BlockTime, FeeRate,
SignedAmount, Weight,
Amount, BlockHeight, BlockHeightInterval, BlockInterval, BlockMtp, BlockMtpInterval,
BlockTime, FeeRate, MathOp, NumOpError, NumOpResult, SignedAmount, Weight,
};
}
@ -173,11 +175,15 @@ fn api_can_use_all_types_from_module_amount() {
#[test]
fn api_can_use_all_types_from_module_block() {
use bitcoin_units::block::{BlockHeight, BlockHeightInterval, TooBigForRelativeHeightError};
use bitcoin_units::block::{
BlockHeight, BlockHeightInterval, BlockMtp, BlockMtpInterval, TooBigForRelativeHeightError,
};
}
#[test]
fn api_can_use_all_types_from_module_fee_rate() {
#[cfg(feature = "serde")]
use bitcoin_units::fee_rate::serde::OverflowError;
use bitcoin_units::fee_rate::FeeRate;
}
@ -190,7 +196,10 @@ fn api_can_use_all_types_from_module_locktime_absolute() {
#[test]
fn api_can_use_all_types_from_module_locktime_relative() {
use bitcoin_units::locktime::relative::{Height, Time, TimeOverflowError};
use bitcoin_units::locktime::relative::{
Height, InvalidHeightError, InvalidTimeError, NumberOf512Seconds, NumberOfBlocks, Time,
TimeOverflowError,
};
}
#[test]
@ -262,10 +271,10 @@ fn regression_default() {
let want = Default {
a: Amount::ZERO,
b: SignedAmount::ZERO,
c: BlockInterval::ZERO,
d: relative::Height::ZERO,
e: relative::Time::ZERO,
f: BlockMtpInterval::ZERO,
c: BlockHeightInterval::ZERO,
d: BlockMtpInterval::ZERO,
e: relative::NumberOf512Seconds::ZERO,
f: relative::NumberOfBlocks::ZERO,
};
assert_eq!(got, want);
}
@ -279,7 +288,7 @@ fn dyn_compatible() {
// These traits are explicitly not dyn compatible.
// b: Box<dyn amount::serde::SerdeAmount>,
// c: Box<dyn amount::serde::SerdeAmountForOpt>,
// d: Box<dyn parse::Integer>,
// d: Box<dyn parse::Integer>, // Because of core::num::ParseIntError
}
}
@ -300,16 +309,16 @@ impl<'a> Arbitrary<'a> for Structs {
b: Amount::MAX.display_in(amount::Denomination::Bitcoin),
c: SignedAmount::arbitrary(u)?,
d: BlockHeight::arbitrary(u)?,
e: BlockInterval::arbitrary(u)?,
f: FeeRate::arbitrary(u)?,
g: absolute::Height::arbitrary(u)?,
h: absolute::MedianTimePast::arbitrary(u)?,
i: relative::Height::arbitrary(u)?,
j: relative::Time::arbitrary(u)?,
k: Weight::arbitrary(u)?,
l: BlockTime::arbitrary(u)?,
m: BlockMtp::arbitrary(u)?,
n: BlockMtpInterval::arbitrary(u)?,
e: BlockHeightInterval::arbitrary(u)?,
f: BlockMtp::arbitrary(u)?,
g: BlockMtpInterval::arbitrary(u)?,
h: FeeRate::arbitrary(u)?,
i: absolute::Height::arbitrary(u)?,
j: absolute::MedianTimePast::arbitrary(u)?,
k: relative::NumberOf512Seconds::arbitrary(u)?,
l: relative::NumberOfBlocks::arbitrary(u)?,
m: BlockTime::arbitrary(u)?,
n: Weight::arbitrary(u)?,
};
Ok(a)
}
@ -318,7 +327,11 @@ impl<'a> Arbitrary<'a> for Structs {
#[cfg(feature = "arbitrary")]
impl<'a> Arbitrary<'a> for Enums {
fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result<Self> {
let a = Enums { a: amount::Denomination::arbitrary(u)? };
let a = Enums {
a: amount::Denomination::arbitrary(u)?,
b: NumOpResult::<Amount>::arbitrary(u)?,
c: MathOp::arbitrary(u)?,
};
Ok(a)
}
}

View File

@ -40,14 +40,14 @@ check! {
lock_by_height_absolute_min, absolute::Height, absolute::Height::MIN, "0";
lock_by_height_absolute_max, absolute::Height, absolute::Height::MAX, "499999999";
lock_by_height_relative_min, relative::Height, relative::Height::MIN, "0";
lock_by_height_relative_max, relative::Height, relative::Height::MAX, "65535";
lock_by_height_relative_min, relative::NumberOfBlocks, relative::NumberOfBlocks::MIN, "0";
lock_by_height_relative_max, relative::NumberOfBlocks, relative::NumberOfBlocks::MAX, "65535";
lock_by_time_absolute_min, absolute::Time, absolute::Time::MIN, "500000000";
lock_by_time_absolute_max, absolute::Time, absolute::Time::MAX, "4294967295";
lock_by_time_absolute_min, absolute::MedianTimePast, absolute::MedianTimePast::MIN, "500000000";
lock_by_time_absolute_max, absolute::MedianTimePast, absolute::MedianTimePast::MAX, "4294967295";
lock_by_time_relative_min, relative::Time, relative::Time::MIN, "0";
lock_by_time_relative_max, relative::Time, relative::Time::MAX, "65535";
lock_by_time_relative_min, relative::NumberOf512Seconds, relative::NumberOf512Seconds::MIN, "0";
lock_by_time_relative_max, relative::NumberOf512Seconds, relative::NumberOf512Seconds::MAX, "65535";
weight_min, Weight, Weight::MIN, "0";
weight_max, Weight, Weight::MAX, "18446744073709551615";