diff --git a/src/api/utils.rs b/src/api/utils.rs index 821775c..f99257b 100644 --- a/src/api/utils.rs +++ b/src/api/utils.rs @@ -152,7 +152,8 @@ pub async fn handle_rejection(err: Rejection) -> Result { // This should not happen! All errors should be handled error!("Unhandled API rejection: {:?}", err); error.code = StatusCode::INTERNAL_SERVER_ERROR; - error.message = ApiErrorType::Generic(format!("Unhandled rejection: {err:?}")); + error.message = + ApiErrorType::Generic(format!("Unhandled rejection. Internal Server Error")); } Ok(common_error_reply( diff --git a/src/asert.rs b/src/asert.rs index da435ae..1b0b8e7 100644 --- a/src/asert.rs +++ b/src/asert.rs @@ -1,9 +1,6 @@ use { crate::constants::{ASERT_HALF_LIFE, ASERT_TARGET_HASHES_PER_BLOCK}, - rug::{ - integer::ParseIntegerError, - Integer, - }, + rug::{integer::ParseIntegerError, Integer}, std::{ cmp::Ordering, fmt, @@ -12,10 +9,7 @@ use { str::FromStr, time::Duration, }, - tw_chain::{ - crypto::sha3_256, - primitives::block::BlockHeader, - }, + tw_chain::{crypto::sha3_256, primitives::block::BlockHeader}, }; /// Maps AIBlock PoW parameters to ASERT inputs then computes and returns the @@ -24,18 +18,18 @@ pub fn calculate_asert_target( activation_height: u64, current_height: u64, winning_hashes_since_activation: u64, -) --> CompactTarget -{ - assert!(current_height > activation_height, "ASERT has not activated yet"); +) -> CompactTarget { + assert!( + current_height > activation_height, + "ASERT has not activated yet" + ); map_asert_inputs( - activation_height, - current_height, - winning_hashes_since_activation, - ) - .calculate_target() - + activation_height, + current_height, + winning_hashes_since_activation, + ) + .calculate_target() } // this exists so that the map_asert_inputs function and the calculation are @@ -48,13 +42,9 @@ struct MappedAsertInputs { } impl MappedAsertInputs { - pub fn calculate_target(&self) -> CompactTarget { self.context - .calculate_target( - self.block_height, - self.block_timestamp, - ) + .calculate_target(self.block_height, self.block_timestamp) } } @@ -62,10 +52,11 @@ fn map_asert_inputs( activation_height: u64, current_height: u64, winning_hashes_since_activation: u64, -) --> MappedAsertInputs -{ - assert!(current_height > activation_height, "ASERT has not activated yet"); +) -> MappedAsertInputs { + assert!( + current_height > activation_height, + "ASERT has not activated yet" + ); // perform some input manipulation to convert hashes-per-block to timestamps. // in classic ASERT, there is a fixed number of winning hashes per block (i.e. 1), @@ -73,7 +64,7 @@ fn map_asert_inputs( // and a variable number of hashes per block. // the target returned by ASERT tries to bound the variance of the time, // whereas we want to bound the number of winning hashes, and time is fixed. - + // the mapping function needs to convert number of hashes to block time // when the difficulty is too low: // - classic block time will be earlier than target @@ -83,18 +74,16 @@ fn map_asert_inputs( // - classic block time will be later than target // - our number of hashes will be too low // - map a shortfall of hashes to a later block time - + // so first we need to invert the hash count difference difference, and second we // need some function to convert "n hashes too few/many" into "n seconds too long/short". // for simplicity we assume the anchor block "timestamp" is zero, and the // adjusted "timestamp" should increase by 11 per block. - + let expected_hashes = (current_height - activation_height) * ASERT_TARGET_HASHES_PER_BLOCK; let adjusted_asert_timestamp = match expected_hashes.cmp(&winning_hashes_since_activation) { - Ordering::Less => { - // we have received more hashes than we would like. // difficulty is too easy. // adjust the "timestamp" so that it appears "early". @@ -107,15 +96,13 @@ fn map_asert_inputs( // need a fairly aggressive adjustment to the timestamp. .unwrap_or(current_height) } - + Ordering::Equal => { - // we have exactly as many hashes as we expect expected_hashes } - - Ordering::Greater => { + Ordering::Greater => { // we have received fewer hashes than we would like. // difficulty is too hard. // adjust the "timestamp" so that it appears "late". @@ -132,18 +119,10 @@ fn map_asert_inputs( let anchor_target = "0x1f00ffff".parse().unwrap(); - let context = Asert::with_parameters( - TARGET_BLOCK_TIME_D, - HALF_LIFE_D, - ) - .with_anchor( - anchor_block_height, - anchor_target, - ) - .having_parent( - Timestamp::ZERO, - ); - + let context = Asert::with_parameters(TARGET_BLOCK_TIME_D, HALF_LIFE_D) + .with_anchor(anchor_block_height, anchor_target) + .having_parent(Timestamp::ZERO); + MappedAsertInputs { context, block_height: current_block_height, @@ -156,11 +135,9 @@ fn map_asert_inputs( pub struct BlockHeight(u64); impl BlockHeight { - pub const ZERO: BlockHeight = BlockHeight(0); pub fn diff(&self, other: &Self) -> (Ordering, u64) { - use std::cmp::Ordering::*; let ord = self.cmp(other); let diff = match ord { @@ -225,11 +202,9 @@ impl Sub for BlockHeight { pub struct Timestamp(u32); impl Timestamp { - pub const ZERO: Timestamp = Timestamp(0); pub fn diff(&self, other: &Self) -> (Ordering, Duration) { - use std::cmp::Ordering::*; let ord = self.cmp(other); let diff = match ord { @@ -271,9 +246,7 @@ impl Add for Timestamp { type Output = Self; fn add(self, rhs: Duration) -> Self::Output { - Self( - self.0.saturating_add(rhs.as_secs() as u32) - ) + Self(self.0.saturating_add(rhs.as_secs() as u32)) } } @@ -299,7 +272,6 @@ impl Sub for Timestamp { type Output = Integer; fn sub(self, rhs: Self) -> Self::Output { - let lhs = Integer::from(self.0); let rhs = Integer::from(rhs.0); lhs - rhs @@ -308,29 +280,26 @@ impl Sub for Timestamp { /// A 32-bit approximation of a 256-bit number that represents the target /// a block hash must be lower than in order to meet PoW requirements. -/// +/// /// This enconding is made up of an 8-bit exponent and a 24-bit mantissa. -/// +/// /// Bitcoin calls this representation `nBits` in block headers. -/// +/// /// This encoding originates from OpenSSL. #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct CompactTarget(u32); impl CompactTarget { - /// This is defined ... somewhere. pub const MAX: CompactTarget = CompactTarget(u32::from_be_bytes([0x1d, 0x00, 0xff, 0xff])); pub fn expand(&self) -> Target { - let byte_len = self.0 >> 24; let value = self.0 & 0x007fffff; let target = if byte_len <= 3 { Integer::from(value) >> 8 * (3 - byte_len) - } - else { + } else { Integer::from(value) << 8 * (byte_len - 3) }; @@ -348,13 +317,12 @@ impl CompactTarget { } pub fn try_from_slice(slice: &[u8]) -> Option { - if slice.len() < 4 { return None; } let mut array = [0u8; 4]; - array.copy_from_slice(&slice[slice.len() - 4 ..]); + array.copy_from_slice(&slice[slice.len() - 4..]); Some(Self::from_array(array)) } } @@ -363,12 +331,11 @@ impl FromStr for CompactTarget { type Err = ParseIntError; fn from_str(mut s: &str) -> Result { - // we accept strings starting with a leading '0x' if s.starts_with("0x") { s = &s[2..]; } - + u32::from_str_radix(s, 16).map(Self) } } @@ -386,38 +353,30 @@ impl fmt::Display for CompactTarget { } impl CompactTarget { - pub fn _expand(&self) -> Target { - let e0 = self.0 >> 24; let m0 = self.0 & 0xffffff; let (m, e) = if e0 <= 3 { - let m = m0 >> (8 * 3u32 - e0); (m, 0) - } - else { - + } else { let e = 8 * (e0 - 3); (m0, e) }; let expanded = if m > 0x7fffff { Integer::ZERO - } - else { + } else { Integer::from(m) << e }; Target(expanded) - } } pub struct HeaderHash(sha3_256::Output); impl HeaderHash { - pub fn try_calculate(header: &BlockHeader) -> Option { let serialized = bincode::serialize(header).ok()?; let hashed = sha3_256::digest(&serialized); @@ -441,7 +400,7 @@ impl HeaderHash { /// The expanded integer from of a `CompactTarget` that represents the target /// a block hash must be lower than in order to meet PoW requirements. -/// +/// /// Bitcoin refers to this as the _target_. #[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct Target(Integer); @@ -456,7 +415,6 @@ impl FromStr for Target { type Err = ParseIntegerError; fn from_str(mut s: &str) -> Result { - // we accept strings starting with a leading '0x' if s.starts_with("0x") { s = &s[2..]; @@ -479,39 +437,34 @@ impl fmt::Display for Target { } impl Target { - /// Returns the expansion of `CompactTarget::MAX`. pub fn max() -> Self { CompactTarget::MAX.expand() } /// Returns the compacted form of `self`. - /// + /// /// Note that this is usually lossy as `Target` represents /// values with much greater precision than `CompactTarget`. pub fn compact(&self) -> CompactTarget { - let mut e = (self.0.signed_bits() + 7) / 8; // we need E bytes to represent the number // we have 3 bytes to put the most significant bits into // we also have to do something about negative numbers. - + let mut m = if e <= 3 { - - let m0 = self.0 + let m0 = self + .0 .clone() .clamp(&0u32, &u32::MAX) .to_u64() // unwrap is safe because we've just clamped it .unwrap(); (m0 << (8 * (3 - e))) as u32 - } else { - let m0 = self.0.clone() >> (8 * (e - 3)); - m0 - .clamp(&0u32, &u32::MAX) + m0.clamp(&0u32, &u32::MAX) .to_u32() // unwrap is safe because we've just clamped it .unwrap() @@ -526,18 +479,18 @@ impl Target { } let compact = m | (e << 24); - + CompactTarget(compact) } } /// ASERT3-2D calculation, as implemented around 2020 in Bitcoin ABC node. -/// +/// /// * [Spec on GitHub](https://github.com/bitcoincashorg/bitcoincash.org/blob/master/spec/2020-11-15-asert.md). /// * [Java implementation](https://github.com/pokkst/asert-java/blob/master/src/main/java/xyz/pokkst/asert/Asert.java). /// * [Bitcoin ABC implementation](https://reviews.bitcoinabc.org/rABC5b4c8ede6a9bff82ff9dc2e54b62aa4b7ee4d67e). /// * [Bitcoin Cash Node implementation](https://gitlab.com/bitcoin-cash-node/bitcoin-cash-node/-/blob/master/src/pow.cpp). -/// +/// /// This implementation is based on the C++ implementation found in Bitcoin ABC and Bitcoin Cash Node, as this /// is the implementation that has been used in production for several years, and is therefore the most /// "battle tested" implementation. @@ -550,66 +503,66 @@ fn calculate_next_target( target_block_time: Duration, halflife: Duration, ) -> CompactTarget { - assert!(!halflife.is_zero(), "half-life cannot be zero"); // spec doesn't make a comment on time travelling, but a test vector relies on it let time_diff = i64::from(current_time.0) - i64::from(anchor_parent_time.0); // bch node enforces this ordering, whereas spec says negative diffs are fine - let height_diff = current_height.0.checked_sub(anchor_height.0).expect("anchor after current"); + let height_diff = current_height + .0 + .checked_sub(anchor_height.0) + .expect("anchor after current"); // todo: check pow limit on anchor block - let exponent = ((i64::from(time_diff) - (target_block_time.as_secs() as i64) * ((height_diff + 1) as i64)) * 65536) / (halflife.as_secs() as i64); + let exponent = ((i64::from(time_diff) + - (target_block_time.as_secs() as i64) * ((height_diff + 1) as i64)) + * 65536) + / (halflife.as_secs() as i64); assert_eq!(-1_i64 >> 1, -1_i64, "ASERT2 needs arithmetic shift support"); let mut shifts = (exponent >> 16) as i64; let frac = exponent as u16; - assert_eq!(((shifts as i64) * 65536_i64) + i64::from(frac), exponent, "fracwang"); + assert_eq!( + ((shifts as i64) * 65536_i64) + i64::from(frac), + exponent, + "fracwang" + ); let frac = u64::from(frac); - let factor = 65536u64 + (( - 195766423245049_u64 * frac + - 971821376_u64 * frac * frac + - 5127_u64 * frac * frac * frac + - (1_u64 << 47)) - >> 48); + let factor = 65536u64 + + ((195766423245049_u64 * frac + + 971821376_u64 * frac * frac + + 5127_u64 * frac * frac * frac + + (1_u64 << 47)) + >> 48); let mut next_target = anchor_target.expand().0 * factor; shifts -= 16; if shifts < 0 { next_target >>= shifts.abs() as usize; - } - else { - + } else { let shifted_target = next_target.clone() << shifts as usize; if (shifted_target.clone() >> (shifts as usize)) != next_target { return CompactTarget::MAX; - } - else { + } else { next_target = shifted_target; } } if next_target.is_zero() { - Target(Integer::from(1)).compact() - } - else if next_target > Target::max().0 { - + } else if next_target > Target::max().0 { CompactTarget::MAX - } - else { - + } else { Target(next_target).compact() } } - #[doc(hidden)] pub struct AsertParameters { target_block_time: Duration, @@ -657,22 +610,22 @@ impl RequiresParent { } /// A parameterised instance of the ASERTI3-2D algorithm. -/// +/// /// # Usage -/// +/// /// To use the ASERT algorithm to calculate a block's target: -/// +/// /// 1. Create an ASERT context /// 2. Supply static parameters /// 3. Configure an anchor /// 4. Calculate the target -/// +/// /// Note that the context returned after (3) can be kept and /// re-used; it is not necessary to construct a context for /// every calculation. -/// +/// /// ## Creating An ASERT Context -/// +/// /// The ASERT algorithm requires three parts: /// * Static parameters /// * An anchor, representing the point in time at which @@ -685,131 +638,131 @@ impl RequiresParent { /// * A block for which the target is to be calculated /// * Block Height /// * Timestamp -/// +/// /// The `Asert` struct provides a builder-like interface for constructing /// a calculation context. Once constructed, only the third part above /// must be specified on a per-call basis. -/// +/// /// ### Static parameters -/// +/// /// The following static parameters are required: -/// +/// /// * Block Interval, target number of seconds between blocks /// * Half-Life, the period at which, given a doubling of the /// number of blocks produced, the difficulty doubles (i.e. /// the target halves) -/// +/// /// #### BCH Static Parameters -/// +/// /// These are the default parameters as ASERT is deployed on the BCH network. -/// +/// /// This implementation contains default parameters: /// * Target Block Interval of 30 seconds /// * Half-Life of 2 days -/// +/// /// ``` /// use aserti3_2d::*; -/// +/// /// let asert = Asert::with_bch_parameters() /// // configure anchor etc /// # ; /// ``` -/// +/// /// #### Supplying alternate static parameters -/// +/// /// To use alternative parameters, use the following function: -/// +/// /// ``` /// use aserti3_2d::*; /// use std::time::Duration; -/// +/// /// let target_block_time = Duration::from_secs(30); /// let halflife = Duration::from_secs(2 * 24 * 60 * 60); -/// +/// /// let asert = Asert::with_parameters(target_block_time, halflife) /// // configure anchor etc /// # ; /// ``` -/// +/// /// ### Specifying An Anchor -/// +/// /// The algorithm works by comparing some fixed point in the past, represented /// by an anchor block, to some block after that fixed point in time. -/// +/// /// The anchor may either be the genesis block, or some later block in the chain. -/// +/// /// * For anchoring at the genesis block: /// * the height, target, and timestamp of the genesis block /// * For a non-genesis anchor: /// * The height and target of the anchor block /// * The timestamp of the block prior to the anchor block -/// +/// /// #### Anchoring to the genesis block -/// +/// /// According to the ASERT specification, when anchoring to genesis, /// the timestamp of the genesis block is used rather than the timestamp /// of the block prior to the anchor. This is because there is no block /// prior to the genesis block. -/// +/// /// ``` /// use aserti3_2d::*; -/// +/// /// // let genesis_target = genesis.target; /// // let genesis_timestamp = genesis.timestamp; /// # let genesis_target = CompactTarget::MAX; /// # let genesis_timestamp = Timestamp::UNIX_EPOCH; -/// +/// /// let asert = Asert::with_bch_parameters() /// .with_genesis_anchor(genesis_target, genesis_timestamp); /// ``` -/// +/// /// #### Anchoring to a non-genesis block -/// +/// /// When anchoring to a non-genesis block, the anchor block's /// height and target are required, along with the timestamp of /// the anchor block's parent. -/// +/// /// ``` /// use aserti3_2d::*; -/// +/// /// // let height = anchor.height; /// // let target = anchor.target; /// // let parent_timestamp = anchor.parent.timestamp; /// # let height = BlockHeight::ZERO; /// # let target = CompactTarget::MAX; /// # let parent_timestamp = Timestamp::UNIX_EPOCH; -/// +/// /// let asert = Asert::with_bch_parameters() /// .with_anchor(height, target) /// .having_parent(parent_timestamp); /// ``` -/// +/// /// ## Calculating A Block Target -/// +/// /// Once an ASERT context is established, the target for a given /// block may be calculated by calling `Asert::calculate_target`, /// passing the current block's height and timestamp. -/// +/// /// ``` /// use aserti3_2d::*; -/// +/// /// // create a context as previously demonstrated /// // let asert = Asert::... /// # let genesis_target = CompactTarget::MAX; /// # let genesis_timestamp = Timestamp::UNIX_EPOCH; /// # let asert = Asert::with_bch_parameters() /// # .with_genesis_anchor(genesis_target, genesis_timestamp); -/// +/// /// // get block parameters /// // let height = current_block.height; /// // let timestamp = current_block.timestamp; /// # let height = BlockHeight::from(10); /// # let timestamp = Timestamp::from(1000); -/// +/// /// let target = asert.calculate_target(height, timestamp); /// println!("target for block {height} with timestamp {timestamp}: {target}"); /// ``` -/// +/// #[derive(Debug)] pub struct Asert { target_block_time: Duration, @@ -820,9 +773,8 @@ pub struct Asert { } impl Asert { - /// Begins context creation with default static parameters. - /// + /// /// See `Asert` struct documentation for details. pub fn with_bch_parameters() -> AsertParameters { AsertParameters { @@ -831,9 +783,8 @@ impl Asert { } } - /// Begins context creation with custom static parameters. - /// + /// /// See `Asert` struct documentation for details. pub fn with_parameters(target_block_time: Duration, halflife: Duration) -> AsertParameters { AsertParameters { @@ -843,7 +794,7 @@ impl Asert { } /// Calculates and returns the target for a block with given height and timestamp. - /// + /// /// See `Asert` struct documentation for details. pub fn calculate_target(&self, height: BlockHeight, timestamp: Timestamp) -> CompactTarget { calculate_next_target( @@ -865,15 +816,14 @@ mod tests { mod target_conversions { use super::*; - fn test_target_expansion(compact_s: &'static str, expanded_s: &'static str, compacted_s: &'static str) { - - let target: Target = expanded_s - .parse() - .expect("setup: parse expanded target"); + fn test_target_expansion( + compact_s: &'static str, + expanded_s: &'static str, + compacted_s: &'static str, + ) { + let target: Target = expanded_s.parse().expect("setup: parse expanded target"); - let compact: CompactTarget = compact_s - .parse() - .expect("setup: parse compact target"); + let compact: CompactTarget = compact_s.parse().expect("setup: parse compact target"); let roundtrip: CompactTarget = compacted_s .parse() @@ -891,21 +841,33 @@ mod tests { #[test] fn target_expansion_0x17073039() { // test case from https://bitcoin.stackexchange.com/a/117269 - test_target_expansion("0x17073039", "0x730390000000000000000000000000000000000000000", "0x17073039") + test_target_expansion( + "0x17073039", + "0x730390000000000000000000000000000000000000000", + "0x17073039", + ) } #[test] fn target_expansion_0x1b0404cb() { // test case from https://en.bitcoin.it/wiki/Difficulty // happens to be bitcoin block 100,800 - test_target_expansion("0x1b0404cb", "0x00000000000404CB000000000000000000000000000000000000000000000000", "0x1b0404cb") + test_target_expansion( + "0x1b0404cb", + "0x00000000000404CB000000000000000000000000000000000000000000000000", + "0x1b0404cb", + ) } #[test] fn target_expansion_0x1d00ffff() { // max possible value, according to bitcoin wiki // this is what was used in the bitcoin genesis block - test_target_expansion("0x1d00ffff", "0x00000000FFFF0000000000000000000000000000000000000000000000000000", "0x1d00ffff") + test_target_expansion( + "0x1d00ffff", + "0x00000000FFFF0000000000000000000000000000000000000000000000000000", + "0x1d00ffff", + ) } // bitcoin block test cases extracted from https://learnmeabitcoin.com/technical/block/bits/ @@ -913,19 +875,31 @@ mod tests { #[test] fn target_expansion_0x170355f0() { // test case: bitcoin block 845,098 - test_target_expansion("170355f0", "0000000000000000000355f00000000000000000000000000000000000000000", "170355f0") + test_target_expansion( + "170355f0", + "0000000000000000000355f00000000000000000000000000000000000000000", + "170355f0", + ) } #[test] fn target_expansion_0x1824dbe9() { // test case: bitcoin block 320,544 - test_target_expansion("1824dbe9", "000000000000000024dbe9000000000000000000000000000000000000000000", "1824dbe9") + test_target_expansion( + "1824dbe9", + "000000000000000024dbe9000000000000000000000000000000000000000000", + "1824dbe9", + ) } #[test] fn target_expansion_0x1b0e7256() { // test case: bitcoin block 90,720 - test_target_expansion("1b0e7256", "00000000000e7256000000000000000000000000000000000000000000000000", "1b0e7256") + test_target_expansion( + "1b0e7256", + "00000000000e7256000000000000000000000000000000000000000000000000", + "1b0e7256", + ) } // bitcoin core test cases @@ -961,7 +935,6 @@ mod tests { fn target_expansion_0x04123456() { test_target_expansion("0x04123456", "0x12345600", "0x04123456") } - } #[test] @@ -972,7 +945,6 @@ mod tests { ); } - mod aserti3_2d_bch_test_cases { use super::*; @@ -995,7 +967,6 @@ mod tests { } fn parse(vector: &'static str) -> TestVector { - #[derive(Default)] struct VectorBuilder { description: Option<&'static str>, @@ -1009,13 +980,13 @@ mod tests { } impl VectorBuilder { - fn build(self) -> TestVector { - let v = TestVector { description: self.description.expect("missing description"), anchor_height: self.anchor_height.expect("missing anchor_height"), - anchor_ancestor_time: self.anchor_ancestor_time.expect("missing anchor_ancestor_time"), + anchor_ancestor_time: self + .anchor_ancestor_time + .expect("missing anchor_ancestor_time"), anchor_target: self.anchor_target.expect("missing anchor_target"), start_height: self.start_height.expect("missing start_height"), start_time: self.start_time.expect("missing start_time"), @@ -1028,23 +999,11 @@ mod tests { // parser errors that would affect the correctness // of the tests. - assert_eq!( - v.iterations, - v.items.len(), - "wrong number of iterations", - ); + assert_eq!(v.iterations, v.items.len(), "wrong number of iterations",); if let Some(first) = v.items.iter().nth(0) { - assert_eq!( - v.start_height, - first.height, - "wrong start height" - ); - assert_eq!( - v.start_time, - first.time, - "wrong start time" - ); + assert_eq!(v.start_height, first.height, "wrong start height"); + assert_eq!(v.start_time, first.time, "wrong start time"); } v @@ -1060,7 +1019,6 @@ mod tests { } impl ItemBuilder { - fn build(self) -> TestVectorItem { TestVectorItem { iteration: self.iteration.expect("missing iteration"), @@ -1073,39 +1031,41 @@ mod tests { let mut builder = VectorBuilder::default(); for line in vector.lines() { - if line.starts_with("##") { - let line = &line[2..]; let mut parts = line.splitn(2, ':'); - let Some(k) = parts.next() else { continue; }; - let Some(v) = parts.next() else { continue; }; + let Some(k) = parts.next() else { + continue; + }; + let Some(v) = parts.next() else { + continue; + }; let k = k.trim(); let v = v.trim(); match k { - "description" => builder.description = Some(v.trim()), "anchor height" => builder.anchor_height = v.parse().ok(), - "anchor ancestor time" | "anchor parent time" => builder.anchor_ancestor_time = v.parse().ok(), + "anchor ancestor time" | "anchor parent time" => { + builder.anchor_ancestor_time = v.parse().ok() + } "anchor nBits" => builder.anchor_target = v.parse().ok(), "start height" => builder.start_height = v.parse().ok(), "start time" => builder.start_time = v.parse().ok(), "iterations" => builder.iterations = v.parse().ok(), other => panic!("unexpected key: {}", other), } - } - else if line.starts_with("#") { - + } else if line.starts_with("#") { assert_eq!( "# iteration,height,time,target", line.trim(), "unexpected field layout" ); - } - else { - if line.trim().is_empty() { continue; } + } else { + if line.trim().is_empty() { + continue; + } let mut parts = line.split_whitespace(); let mut item = ItemBuilder::default(); item.iteration = parts.next().map(|n| n.parse().ok()).flatten(); @@ -1120,13 +1080,11 @@ mod tests { } fn execute(vector: TestVector, name: &'static str) { - // bch selected constants const TARGET_BLOCK_TIME: Duration = Duration::from_secs(10 * 60); const HALFLIFE: Duration = Duration::from_secs(2 * 24 * 60 * 60); for item in vector.items.into_iter() { - let actual = calculate_next_target( vector.anchor_height, vector.anchor_ancestor_time, @@ -1134,101 +1092,88 @@ mod tests { item.height, item.time, TARGET_BLOCK_TIME, - HALFLIFE + HALFLIFE, ); // temporary: rule out compact conversion as the cause of test failures assert_eq!( - item.target, - actual, - "{name} iteration {} ({})", item.iteration, vector.description, + item.target, actual, + "{name} iteration {} ({})", + item.iteration, vector.description, ) } } #[test] fn run01() { - let vector = parse(include_str!("test_data/asert/run01")); execute(vector, "run01"); } #[test] fn run02() { - let vector = parse(include_str!("test_data/asert/run02")); execute(vector, "run02"); } #[test] fn run03() { - let vector = parse(include_str!("test_data/asert/run03")); execute(vector, "run03"); } #[test] fn run04() { - let vector = parse(include_str!("test_data/asert/run04")); execute(vector, "run04"); } #[test] fn run05() { - let vector = parse(include_str!("test_data/asert/run05")); execute(vector, "run05"); } #[test] fn run06() { - let vector = parse(include_str!("test_data/asert/run06")); execute(vector, "run06"); } #[test] fn run07() { - let vector = parse(include_str!("test_data/asert/run07")); execute(vector, "run07"); } #[test] fn run08() { - let vector = parse(include_str!("test_data/asert/run08")); execute(vector, "run08"); } #[test] fn run09() { - let vector = parse(include_str!("test_data/asert/run09")); execute(vector, "run09"); } #[test] fn run10() { - let vector = parse(include_str!("test_data/asert/run10")); execute(vector, "run10"); } #[test] fn run11() { - let vector = parse(include_str!("test_data/asert/run11")); execute(vector, "run11"); } #[test] fn run12() { - let vector = parse(include_str!("test_data/asert/run12")); execute(vector, "run12"); } } - } diff --git a/src/bin/node/miner.rs b/src/bin/node/miner.rs index f5df21b..99c1a41 100644 --- a/src/bin/node/miner.rs +++ b/src/bin/node/miner.rs @@ -560,7 +560,7 @@ fn configuration( ) -> (MinerNodeConfig, Option) { ( settings.0.try_into().unwrap(), - settings.1.map(|v| v.try_into().unwrap()) + settings.1.map(|v| v.try_into().unwrap()), ) } diff --git a/src/block_pipeline.rs b/src/block_pipeline.rs index 3aadc48..49668d9 100644 --- a/src/block_pipeline.rs +++ b/src/block_pipeline.rs @@ -41,7 +41,6 @@ pub enum MiningPipelineItem { ResetPipeline, } - /// Participants collection (unsorted: given order, and lookup collection) #[derive(Default, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)] pub struct Participants { @@ -126,11 +125,11 @@ pub struct MiningPipelineInfo { current_reward: TokenAmount, /// Proposed keys for current mining pipeline cycle proposed_keys: BTreeSet, - + /// [AM] The total number of hashes since ASERT activation #[serde(default)] asert_winning_hashes_count: u64, - + /// [AM] the block height at which ASERT activates // // note: this data structure has a subtly complex interaction between: @@ -576,7 +575,6 @@ impl MiningPipelineInfo { /// Selects a winning miner from the list via UNICORN and move to halted state pub fn start_winning_pow_halted(&mut self) { - let all_winning_pow = std::mem::take(&mut self.all_winning_pow); let _timeouts = std::mem::take(&mut self.current_phase_timeout_peer_ids); @@ -589,10 +587,10 @@ impl MiningPipelineInfo { // the first block to use ASERT is the one that immediately follows the anchor block. // therefore, we collect metrics from the anchor block. if b_num >= self.activation_height_asert { - self.asert_winning_hashes_count += u64::try_from(all_winning_pow.len()).unwrap_or(crate::constants::ASERT_TARGET_HASHES_PER_BLOCK); + self.asert_winning_hashes_count += u64::try_from(all_winning_pow.len()) + .unwrap_or(crate::constants::ASERT_TARGET_HASHES_PER_BLOCK); } - } - else { + } else { panic!("[AM] we've hooked the wrong place; we need the block number and the number of winning hashes in the same place at the same time"); } @@ -700,7 +698,7 @@ impl MiningPipelineInfo { current_block_num, current_block, activation_height_asert, - asert_winning_hashes_count + asert_winning_hashes_count, } = value; Self { @@ -720,7 +718,7 @@ impl MiningPipelineInfo { current_block_num: self.current_block_num, current_block: self.current_block, activation_height_asert: self.activation_height_asert, - asert_winning_hashes_count: self.asert_winning_hashes_count + asert_winning_hashes_count: self.asert_winning_hashes_count, } } } diff --git a/src/constants.rs b/src/constants.rs index 85c9265..094d590 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -113,11 +113,12 @@ pub const ASERT_TARGET_HASHES_PER_BLOCK: u64 = 11; const BCH_SECONDS_PER_BLOCK: u64 = 10 * 60; const BCH_HALF_LIFE: u64 = 2 * 24 * 60 * 60; /// ASERT algorithm constant for smoothing difficulty target adjustments over a number of blocks -/// +/// /// Our mapping function projects number of hashes to seconds, so we feed ASERT_TARGET_HASHES_PER_BLOCK /// to asert as the target block time. This constant adjusts the half life constant such that ASERT's /// smoothing/spreading/smearing acts over the same number of blocks as it would in BCH. -pub const ASERT_HALF_LIFE: u64 = BCH_HALF_LIFE * ASERT_TARGET_HASHES_PER_BLOCK / BCH_SECONDS_PER_BLOCK; +pub const ASERT_HALF_LIFE: u64 = + BCH_HALF_LIFE * ASERT_TARGET_HASHES_PER_BLOCK / BCH_SECONDS_PER_BLOCK; /*------- TESTS -------*/ diff --git a/src/mempool_raft.rs b/src/mempool_raft.rs index 2a368a8..d9d17c6 100644 --- a/src/mempool_raft.rs +++ b/src/mempool_raft.rs @@ -1,8 +1,9 @@ use crate::active_raft::ActiveRaft; use crate::asert::calculate_asert_target; use crate::block_pipeline::{ - MiningPipelineInfo, MiningPipelineInfoImport, MiningPipelineItem, MiningPipelinePhaseChange, - MiningPipelineStatus, Participants, PipelineEventInfo, MiningPipelineInfoPreDifficulty, + MiningPipelineInfo, MiningPipelineInfoImport, MiningPipelineInfoPreDifficulty, + MiningPipelineItem, MiningPipelinePhaseChange, MiningPipelineStatus, Participants, + PipelineEventInfo, }; use crate::configurations::{MempoolNodeConfig, UnicornFixedInfo}; use crate::constants::{BLOCK_SIZE_IN_TX, COINBASE_MATURITY, DB_PATH, TX_POOL_LIMIT}; @@ -13,7 +14,9 @@ use crate::raft_util::{RaftContextKey, RaftInFlightProposals}; use crate::tracked_utxo::TrackedUtxoSet; use crate::unicorn::{UnicornFixedParam, UnicornInfo}; use crate::utils::{ - calculate_reward, construct_coinbase_tx, create_socket_addr_for_list, get_timestamp_now, get_total_coinbase_tokens, make_utxo_set_from_seed, try_deserialize, BackupCheck, UtxoReAlignCheck + calculate_reward, construct_coinbase_tx, create_socket_addr_for_list, get_timestamp_now, + get_total_coinbase_tokens, make_utxo_set_from_seed, try_deserialize, BackupCheck, + UtxoReAlignCheck, }; use bincode::serialize; use serde::{Deserialize, Serialize}; @@ -138,7 +141,7 @@ pub struct MempoolConsensusedRuntimeData { pub mining_api_keys: BTreeMap, } -/// This is a dirty patch to enable the import of consensus snapshots +/// This is a dirty patch to enable the import of consensus snapshots /// from before the introduction of the difficulty function for mining #[derive(Clone, Debug, Default, Serialize, Deserialize)] pub struct MempoolConsensusedPreDifficulty { @@ -249,7 +252,7 @@ pub struct MempoolConsensusedImport { pub miner_whitelist: MinerWhitelist, pub init_issuances: Vec, pub activation_height_asert: u64, - pub asert_winning_hashes_count: u64 + pub asert_winning_hashes_count: u64, } /// Consensused Mempool fields and consensus management. @@ -565,7 +568,7 @@ impl MempoolRaft { warn!("apply_snapshot called self.consensused updated"); let consensus_check: Result = try_deserialize(&consensused_ser); - + // Handle the case where the snapshot is from a previous version self.consensused = match consensus_check { Ok(consensused) => consensused, @@ -573,7 +576,8 @@ impl MempoolRaft { warn!("Deserialization of consensus snapshot failed: {:?}", e); warn!("Attempting to deserialize as a previous version"); - let consensus_prediff: Result = try_deserialize(&consensused_ser); + let consensus_prediff: Result = + try_deserialize(&consensused_ser); match consensus_prediff { Ok(consensused) => consensused.into(), Err(e) => { @@ -1234,7 +1238,7 @@ impl From for MempoolConsensused { miner_whitelist, timestamp, init_issuances, - block_pipeline + block_pipeline, } = consensused; let post_diff_block_pipeline: MiningPipelineInfo = block_pipeline.into(); @@ -1331,9 +1335,7 @@ impl MempoolConsensused { /// Specify the height at which the ASERT algorithm activates pub fn with_activation_height_asert(mut self, height: u64) -> Self { - self.block_pipeline = self - .block_pipeline - .with_activation_height_asert(height); + self.block_pipeline = self.block_pipeline.with_activation_height_asert(height); self } @@ -1373,7 +1375,7 @@ impl MempoolConsensused { current_block_num: tx_current_block_num, current_block, activation_height_asert, - asert_winning_hashes_count + asert_winning_hashes_count, }; let timestamp = get_timestamp_now(); @@ -1623,13 +1625,12 @@ impl MempoolConsensused { // use ASERT from the block AFTER the block that started // the counter. if self.block_pipeline.get_activation_height_asert() < b_num { - let target = calculate_asert_target( self.block_pipeline.get_activation_height_asert(), b_num, self.block_pipeline.get_asert_winning_hashes_count(), ); - + block.header.difficulty = target.into_array().to_vec(); } diff --git a/src/storage.rs b/src/storage.rs index 5aea7c2..3ebef41 100644 --- a/src/storage.rs +++ b/src/storage.rs @@ -965,7 +965,7 @@ impl StorageNode { self.resend_trigger_message().await; return None; }; - + if let Err(e) = construct_valid_block_pow_hash(&common.block) { debug!("Block received not added. PoW invalid: {}", e); return Some(Response { diff --git a/src/utils.rs b/src/utils.rs index 80cc1bc..6a8272c 100644 --- a/src/utils.rs +++ b/src/utils.rs @@ -10,9 +10,11 @@ use crate::interfaces::{ use crate::wallet::WalletDb; use crate::Rs2JsMsg; use bincode::serialize; +use bincode::{self, Error as BincodeError}; use chrono::Utc; use futures::future::join_all; use rand::{self, Rng}; +use serde::Deserialize; use std::collections::BTreeMap; use std::error::Error; use std::fmt; @@ -29,8 +31,6 @@ use tokio::time::Instant; use tracing::{info, trace, warn}; use trust_dns_resolver::TokioAsyncResolver; use tw_chain::constants::TOTAL_TOKENS; -use serde::Deserialize; -use bincode::{self, Error as BincodeError}; use tw_chain::crypto::sha3_256; use tw_chain::crypto::sign_ed25519::{self as sign, PublicKey, SecretKey, Signature}; use tw_chain::primitives::transaction::GenesisTxHashSpec; @@ -587,9 +587,9 @@ pub fn validate_pow_for_address(pow: &ProofOfWork, rand_num: &Option<&Vec>) } /// Will attempt deserialization of a given byte array using bincode -/// +/// /// ### Arguments -/// +/// /// * `data` - Byte array to attempt deserialization on pub fn try_deserialize<'a, T: Deserialize<'a>>(data: &'a [u8]) -> Result { bincode::deserialize(data) @@ -650,7 +650,6 @@ pub fn validate_pow_block(header: &BlockHeader) -> bool { /// /// * `header` - The header for PoW fn validate_pow_block_hash(header: &BlockHeader) -> Option> { - // [AM] even though we've got explicit activation height in configuration // and a hard-coded fallback elsewhere in the code, here // we're basically sniffing at the difficulty field in the @@ -665,12 +664,9 @@ fn validate_pow_block_hash(header: &BlockHeader) -> Option> { // access configuration. if header.difficulty.is_empty() { - let pow = serialize(header).unwrap(); validate_pow_leading_zeroes(&pow) - } - else { - + } else { use crate::asert::{CompactTarget, HeaderHash}; let target = CompactTarget::try_from_slice(&header.difficulty)?; @@ -678,8 +674,7 @@ fn validate_pow_block_hash(header: &BlockHeader) -> Option> { if header_hash.is_below_compact_target(&target) { Some(header_hash.into_vec()) - } - else { + } else { None } }