Add Timestamp newtype
Bitcoin block headers have a timestamp. Currently we are using a `u32`. while this functions correctly it gives the compiler no chance to enforce type safety. Add a `Timestamp` newtype that is a thin wrapper around a `u32`. Document it and test the API surface in `api.rs`.
This commit is contained in:
parent
5a0d5d7b79
commit
b3f122b399
|
@ -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,
|
||||
};
|
||||
|
|
|
@ -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<W: Write + ?Sized>(&self, w: &mut W) -> Result<usize, io::Error> {
|
||||
self.to_u32().consensus_encode(w)
|
||||
}
|
||||
}
|
||||
|
||||
impl Decodable for Timestamp {
|
||||
fn consensus_decode<R: BufRead + ?Sized>(r: &mut R) -> Result<Self, encode::Error> {
|
||||
Decodable::consensus_decode(r).map(Timestamp::from_u32)
|
||||
}
|
||||
}
|
||||
|
||||
/// Extension functionality for the [`Block<Unchecked>`] 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);
|
||||
|
|
|
@ -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<Params>) -> Block<Checked> {
|
|||
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<Params>) -> Block<Checked> {
|
|||
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<Params>) -> Block<Checked> {
|
|||
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<Params>) -> Block<Checked> {
|
|||
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<Params>) -> Block<Checked> {
|
|||
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!(
|
||||
|
|
|
@ -135,6 +135,7 @@ pub use units::{
|
|||
amount::{Amount, Denomination, SignedAmount},
|
||||
block::{BlockHeight, BlockInterval},
|
||||
fee_rate::FeeRate,
|
||||
timestamp::{self, Timestamp},
|
||||
weight::Weight,
|
||||
};
|
||||
|
||||
|
|
|
@ -420,9 +420,9 @@ define_extension_trait! {
|
|||
current: Header,
|
||||
params: impl AsRef<Params>,
|
||||
) -> 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,
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -59,6 +59,7 @@ pub use units::{
|
|||
amount::{self, Amount, SignedAmount},
|
||||
block::{BlockHeight, BlockInterval},
|
||||
fee_rate::{self, FeeRate},
|
||||
timestamp::{self, Timestamp},
|
||||
weight::{self, Weight},
|
||||
};
|
||||
|
||||
|
|
|
@ -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
|
||||
};
|
||||
|
|
|
@ -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: <https://en.bitcoin.it/wiki/Block_timestamp>
|
||||
#[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<u32> for Timestamp {
|
||||
#[inline]
|
||||
fn from(t: u32) -> Self { Self::from_u32(t) }
|
||||
}
|
||||
|
||||
impl From<Timestamp> 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<Self> {
|
||||
let t: u32 = u.arbitrary()?;
|
||||
Ok(Timestamp::from(t))
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue