diff --git a/bitcoin/src/bip152.rs b/bitcoin/src/bip152.rs index f57fe3e1c..c7b69526b 100644 --- a/bitcoin/src/bip152.rs +++ b/bitcoin/src/bip152.rs @@ -413,8 +413,8 @@ mod test { use crate::merkle_tree::TxMerkleNode; use crate::transaction::OutPointExt; use crate::{ - transaction, Amount, BlockChecked, CompactTarget, OutPoint, ScriptBuf, Sequence, TxIn, - TxOut, Txid, Witness, + transaction, Amount, BlockChecked, CompactTarget, OutPoint, ScriptBuf, Sequence, Timestamp, + TxIn, TxOut, Txid, Witness, }; fn dummy_tx(nonce: &[u8]) -> Transaction { @@ -437,7 +437,7 @@ mod test { version: block::Version::ONE, prev_blockhash: BlockHash::from_byte_array([0x99; 32]), merkle_root: TxMerkleNode::from_byte_array([0x77; 32]), - time: 2, + time: Timestamp::from_u32(2), bits: CompactTarget::from_consensus(3), nonce: 4, }; diff --git a/bitcoin/src/blockdata/block.rs b/bitcoin/src/blockdata/block.rs index 43ac31ad7..dfd039e55 100644 --- a/bitcoin/src/blockdata/block.rs +++ b/bitcoin/src/blockdata/block.rs @@ -13,6 +13,7 @@ use core::fmt; use hashes::{sha256d, HashEngine}; use internals::{compact_size, ToU64}; use io::{BufRead, Write}; +use units::Timestamp; use super::Weight; use crate::consensus::encode::WriteExt as _; @@ -85,6 +86,18 @@ impl Decodable for Version { } } +impl Encodable for Timestamp { + fn consensus_encode(&self, w: &mut W) -> Result { + self.to_u32().consensus_encode(w) + } +} + +impl Decodable for Timestamp { + fn consensus_decode(r: &mut R) -> Result { + Decodable::consensus_decode(r).map(Timestamp::from_u32) + } +} + /// Extension functionality for the [`Block`] type. pub trait BlockUncheckedExt: sealed::Sealed { /// Validates (or checks) a block. @@ -596,7 +609,7 @@ mod tests { block::compute_merkle_root(&transactions).unwrap() ); assert_eq!(serialize(&real_decode.header().merkle_root), merkle); - assert_eq!(real_decode.header().time, 1231965655); + assert_eq!(real_decode.header().time, Timestamp::from_u32(1231965655)); assert_eq!(real_decode.header().bits, CompactTarget::from_consensus(486604799)); assert_eq!(real_decode.header().nonce, 2067413810); assert_eq!(real_decode.header().work(), work); @@ -647,7 +660,7 @@ mod tests { real_decode.header().merkle_root, block::compute_merkle_root(&transactions).unwrap() ); - assert_eq!(real_decode.header().time, 1472004949); + assert_eq!(real_decode.header().time, Timestamp::from_u32(1472004949)); assert_eq!(real_decode.header().bits, CompactTarget::from_consensus(0x1a06d450)); assert_eq!(real_decode.header().nonce, 1879759182); assert_eq!(real_decode.header().work(), work); diff --git a/bitcoin/src/blockdata/constants.rs b/bitcoin/src/blockdata/constants.rs index 4b7e51486..2c5cb4e19 100644 --- a/bitcoin/src/blockdata/constants.rs +++ b/bitcoin/src/blockdata/constants.rs @@ -16,7 +16,7 @@ use crate::opcodes::all::*; use crate::pow::CompactTarget; use crate::transaction::{self, OutPoint, Transaction, TxIn, TxOut}; use crate::witness::Witness; -use crate::{script, Amount, BlockHash, Sequence, TestnetVersion}; +use crate::{script, Amount, BlockHash, Sequence, TestnetVersion, Timestamp}; /// How many seconds between blocks we expect on average. pub const TARGET_BLOCK_SPACING: u32 = 600; @@ -132,7 +132,7 @@ pub fn genesis_block(params: impl AsRef) -> Block { version: block::Version::ONE, prev_blockhash: BlockHash::GENESIS_PREVIOUS_BLOCK_HASH, merkle_root, - time: 1231006505, + time: Timestamp::from_u32(1231006505), bits: CompactTarget::from_consensus(0x1d00ffff), nonce: 2083236893, }, @@ -144,7 +144,7 @@ pub fn genesis_block(params: impl AsRef) -> Block { version: block::Version::ONE, prev_blockhash: BlockHash::GENESIS_PREVIOUS_BLOCK_HASH, merkle_root, - time: 1296688602, + time: Timestamp::from_u32(1296688602), bits: CompactTarget::from_consensus(0x1d00ffff), nonce: 414098458, }, @@ -156,7 +156,7 @@ pub fn genesis_block(params: impl AsRef) -> Block { version: block::Version::ONE, prev_blockhash: BlockHash::GENESIS_PREVIOUS_BLOCK_HASH, merkle_root, - time: 1714777860, + time: Timestamp::from_u32(1714777860), bits: CompactTarget::from_consensus(0x1d00ffff), nonce: 393743547, }, @@ -168,7 +168,7 @@ pub fn genesis_block(params: impl AsRef) -> Block { version: block::Version::ONE, prev_blockhash: BlockHash::GENESIS_PREVIOUS_BLOCK_HASH, merkle_root, - time: 1598918400, + time: Timestamp::from_u32(1598918400), bits: CompactTarget::from_consensus(0x1e0377ae), nonce: 52613770, }, @@ -180,7 +180,7 @@ pub fn genesis_block(params: impl AsRef) -> Block { version: block::Version::ONE, prev_blockhash: BlockHash::GENESIS_PREVIOUS_BLOCK_HASH, merkle_root, - time: 1296688602, + time: Timestamp::from_u32(1296688602), bits: CompactTarget::from_consensus(0x207fffff), nonce: 2, }, @@ -320,7 +320,7 @@ mod test { "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b" ); - assert_eq!(gen.header().time, 1231006505); + assert_eq!(gen.header().time, Timestamp::from_u32(1231006505)); assert_eq!(gen.header().bits, CompactTarget::from_consensus(0x1d00ffff)); assert_eq!(gen.header().nonce, 2083236893); assert_eq!( @@ -338,7 +338,7 @@ mod test { gen.header().merkle_root.to_string(), "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b" ); - assert_eq!(gen.header().time, 1296688602); + assert_eq!(gen.header().time, Timestamp::from_u32(1296688602)); assert_eq!(gen.header().bits, CompactTarget::from_consensus(0x1d00ffff)); assert_eq!(gen.header().nonce, 414098458); assert_eq!( @@ -356,7 +356,7 @@ mod test { gen.header().merkle_root.to_string(), "4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b" ); - assert_eq!(gen.header().time, 1598918400); + assert_eq!(gen.header().time, Timestamp::from_u32(1598918400)); assert_eq!(gen.header().bits, CompactTarget::from_consensus(0x1e0377ae)); assert_eq!(gen.header().nonce, 52613770); assert_eq!( diff --git a/bitcoin/src/lib.rs b/bitcoin/src/lib.rs index af6386a40..ab1d7c9ac 100644 --- a/bitcoin/src/lib.rs +++ b/bitcoin/src/lib.rs @@ -135,6 +135,7 @@ pub use units::{ amount::{Amount, Denomination, SignedAmount}, block::{BlockHeight, BlockInterval}, fee_rate::FeeRate, + timestamp::{self, Timestamp}, weight::Weight, }; diff --git a/bitcoin/src/pow.rs b/bitcoin/src/pow.rs index e25319273..09929f64f 100644 --- a/bitcoin/src/pow.rs +++ b/bitcoin/src/pow.rs @@ -420,9 +420,9 @@ define_extension_trait! { current: Header, params: impl AsRef, ) -> CompactTarget { - let timespan = current.time - last_epoch_boundary.time; + let timespan = i64::from(current.time.to_u32()) - i64::from(last_epoch_boundary.time.to_u32()); let bits = current.bits; - CompactTarget::from_next_work_required(bits, timespan.into(), params) + CompactTarget::from_next_work_required(bits, timespan, params) } } } @@ -1078,6 +1078,7 @@ pub mod test_utils { mod tests { use super::*; use crate::pow::test_utils::{u128_to_work, u32_to_target, u64_to_target}; + use crate::Timestamp; impl U256 { fn bit_at(&self, index: usize) -> bool { @@ -1762,7 +1763,7 @@ mod tests { version: Version::ONE, prev_blockhash: BlockHash::from_byte_array([0; 32]), merkle_root: TxMerkleNode::from_byte_array([0; 32]), - time: 1599332177, + time: Timestamp::from_u32(1599332177), bits: epoch_start.bits, nonce: epoch_start.nonce, }; @@ -1784,7 +1785,7 @@ mod tests { version: Version::ONE, prev_blockhash: BlockHash::from_byte_array([0; 32]), merkle_root: TxMerkleNode::from_byte_array([0; 32]), - time: 1599332844, + time: Timestamp::from_u32(1599332844), bits: starting_bits, nonce: 0, }; @@ -1794,7 +1795,7 @@ mod tests { version: Version::ONE, prev_blockhash: BlockHash::from_byte_array([0; 32]), merkle_root: TxMerkleNode::from_byte_array([0; 32]), - time: 1600591200, + time: Timestamp::from_u32(1600591200), bits: starting_bits, nonce: 0, }; diff --git a/primitives/src/block.rs b/primitives/src/block.rs index b30a2c6c9..5de122e5e 100644 --- a/primitives/src/block.rs +++ b/primitives/src/block.rs @@ -14,6 +14,7 @@ use core::marker::PhantomData; #[cfg(feature = "arbitrary")] use arbitrary::{Arbitrary, Unstructured}; use hashes::{sha256d, HashEngine as _}; +use units::Timestamp; use crate::merkle_tree::TxMerkleNode; #[cfg(feature = "alloc")] @@ -170,7 +171,7 @@ pub struct Header { /// The root hash of the Merkle tree of transactions in the block. pub merkle_root: TxMerkleNode, /// The timestamp of the block, as claimed by the miner. - pub time: u32, + pub time: Timestamp, /// The target value below which the blockhash must lie. pub bits: CompactTarget, /// The nonce, selected to obtain a low enough blockhash. @@ -189,7 +190,7 @@ impl Header { engine.input(&self.version.to_consensus().to_le_bytes()); engine.input(self.prev_blockhash.as_byte_array()); engine.input(self.merkle_root.as_byte_array()); - engine.input(&self.time.to_le_bytes()); + engine.input(&self.time.to_u32().to_le_bytes()); engine.input(&self.bits.to_consensus().to_le_bytes()); engine.input(&self.nonce.to_le_bytes()); @@ -388,7 +389,7 @@ mod tests { version: Version::ONE, prev_blockhash: BlockHash::from_byte_array([0x99; 32]), merkle_root: TxMerkleNode::from_byte_array([0x77; 32]), - time: 2, + time: Timestamp::from(2), bits: CompactTarget::from_consensus(3), nonce: 4, }; @@ -398,7 +399,7 @@ mod tests { let header_size = header.version.to_consensus().to_le_bytes().len() + header.prev_blockhash.as_byte_array().len() + header.merkle_root.as_byte_array().len() - + header.time.to_le_bytes().len() + + header.time.to_u32().to_le_bytes().len() + header.bits.to_consensus().to_le_bytes().len() + header.nonce.to_le_bytes().len(); diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index 5cd715063..ebc44aa58 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -59,6 +59,7 @@ pub use units::{ amount::{self, Amount, SignedAmount}, block::{BlockHeight, BlockInterval}, fee_rate::{self, FeeRate}, + timestamp::{self, Timestamp}, weight::{self, Weight}, }; diff --git a/units/src/lib.rs b/units/src/lib.rs index 4d3be98ac..a5f5434a5 100644 --- a/units/src/lib.rs +++ b/units/src/lib.rs @@ -34,6 +34,7 @@ pub mod block; pub mod fee_rate; pub mod locktime; pub mod parse; +pub mod timestamp; pub mod weight; #[doc(inline)] @@ -42,5 +43,6 @@ pub use self::{ amount::{Amount, SignedAmount}, block::{BlockHeight, BlockInterval}, fee_rate::FeeRate, + timestamp::Timestamp, weight::Weight }; diff --git a/units/src/timestamp.rs b/units/src/timestamp.rs new file mode 100644 index 000000000..35e59d8be --- /dev/null +++ b/units/src/timestamp.rs @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: CC0-1.0 + +//! A UNIX timestamp used as the Bitcoin block time. +//! +//! Also known as Epoch Time - January 1, 1970. +//! +//! This differs from other UNIX timestamps in that we only use non-negative values. The Epoch +//! pre-dates Bitcoin so timestamps before this are not useful for block timestamps. + +#[cfg(feature = "arbitrary")] +use arbitrary::{Arbitrary, Unstructured}; +#[cfg(feature = "serde")] +use serde::{Deserialize, Serialize}; + +/// A Bitcoin block timestamp. +/// +/// > Each block contains a Unix time timestamp. In addition to serving as a source of variation for +/// > the block hash, they also make it more difficult for an adversary to manipulate the block chain. +/// > +/// > A timestamp is accepted as valid if it is greater than the median timestamp of previous 11 +/// > blocks, and less than the network-adjusted time + 2 hours. "Network-adjusted time" is the +/// > median of the timestamps returned by all nodes connected to you. As a result block timestamps +/// > are not exactly accurate, and they do not need to be. Block times are accurate only to within +/// > an hour or two. +/// +/// ref: +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub struct Timestamp(u32); + +impl Timestamp { + /// Constructs a new [`Timestamp`] from an unsigned 32 bit integer value. + #[inline] + pub const fn from_u32(t: u32) -> Self { Timestamp(t) } + + /// Returns the inner `u32` value. + #[inline] + pub const fn to_u32(self) -> u32 { self.0 } +} + +impl From for Timestamp { + #[inline] + fn from(t: u32) -> Self { Self::from_u32(t) } +} + +impl From for u32 { + #[inline] + fn from(t: Timestamp) -> Self { t.to_u32() } +} + +#[cfg(feature = "arbitrary")] +impl<'a> Arbitrary<'a> for Timestamp { + #[inline] + fn arbitrary(u: &mut Unstructured<'a>) -> arbitrary::Result { + let t: u32 = u.arbitrary()?; + Ok(Timestamp::from(t)) + } +} diff --git a/units/tests/api.rs b/units/tests/api.rs index 00ad9b084..97a20e2a6 100644 --- a/units/tests/api.rs +++ b/units/tests/api.rs @@ -15,7 +15,7 @@ use arbitrary::{Arbitrary, Unstructured}; use bitcoin_units::locktime::{absolute, relative}; // Typical usage is `absolute::Height`. use bitcoin_units::{ amount, block, fee_rate, locktime, parse, weight, Amount, BlockHeight, BlockInterval, FeeRate, - SignedAmount, Weight, + SignedAmount, Timestamp, Weight, }; /// A struct that includes all public non-error enums. @@ -42,6 +42,7 @@ struct Structs { i: relative::Height, j: relative::Time, k: Weight, + l: Timestamp, } impl Structs { @@ -58,6 +59,7 @@ impl Structs { i: relative::Height::MAX, j: relative::Time::MAX, k: Weight::MAX, + l: Timestamp::from_u32(u32::MAX), } } } @@ -87,6 +89,7 @@ struct CommonTraits { i: relative::Height, j: relative::Time, k: Weight, + l: Timestamp, } /// A struct that includes all types that implement `Default`. @@ -138,12 +141,14 @@ struct Errors { #[test] fn api_can_use_modules_from_crate_root() { - use bitcoin_units::{amount, block, fee_rate, locktime, parse, weight}; + use bitcoin_units::{amount, block, fee_rate, locktime, parse, timestamp, weight}; } #[test] fn api_can_use_types_from_crate_root() { - use bitcoin_units::{Amount, BlockHeight, BlockInterval, FeeRate, SignedAmount, Weight}; + use bitcoin_units::{ + Amount, BlockHeight, BlockInterval, FeeRate, SignedAmount, Timestamp, Weight, + }; } #[test] @@ -183,6 +188,11 @@ fn api_can_use_all_types_from_module_parse() { use bitcoin_units::parse::{ParseIntError, PrefixedHexError, UnprefixedHexError}; } +#[test] +fn api_can_use_all_types_from_module_timestamp() { + use bitcoin_units::timestamp::Timestamp; +} + #[test] fn api_can_use_all_types_from_module_weight() { use bitcoin_units::weight::Weight; @@ -218,6 +228,8 @@ fn api_all_non_error_types_have_non_empty_debug() { assert!(!debug.is_empty()); let debug = format!("{:?}", t.b.k); assert!(!debug.is_empty()); + let debug = format!("{:?}", t.b.l); + assert!(!debug.is_empty()); } #[test] @@ -283,6 +295,7 @@ impl<'a> Arbitrary<'a> for Structs { i: relative::Height::arbitrary(u)?, j: relative::Time::arbitrary(u)?, k: Weight::arbitrary(u)?, + l: Timestamp::arbitrary(u)?, }; Ok(a) }