diff --git a/core/src/genesis.rs b/core/src/genesis.rs index 1a788c94e3..ea1001a00b 100644 --- a/core/src/genesis.rs +++ b/core/src/genesis.rs @@ -72,16 +72,17 @@ pub fn genesis_test() -> core::Block { total_difficulty: Difficulty::from_num(10_u64.pow(5)), secondary_scaling: 1856, nonce: 23, - proof: Proof { - nonces: vec![ + proof: { + let mut proof = Proof::new(vec![ 16994232_, 22975978_, 32664019_, 44016212_, 50238216_, 57272481_, 85779161_, 124272202, 125203242, 133907662, 140522149, 145870823, 147481297, 164952795, 177186722, 183382201, 197418356, 211393794, 239282197, 239323031, 250757611, 281414565, 305112109, 308151499, 357235186, 374041407, 389924708, 390768911, 401322239, 401886855, 406986280, 416797005, 418935317, 429007407, 439527429, 484809502, 486257104, 495589543, 495892390, 525019296, 529899691, 531685572, - ], - edge_bits: 29, + ]); + proof.edge_bits = 29; + proof }, }, ..Default::default() @@ -184,16 +185,17 @@ pub fn genesis_main() -> core::Block { total_difficulty: Difficulty::from_num(2_u64.pow(34)), secondary_scaling: 1856, nonce: 41, - proof: Proof { - nonces: vec![ + proof: { + let mut proof = Proof::new(vec![ 4391451__, 36730677_, 38198400_, 38797304_, 60700446_, 72910191_, 73050441_, 110099816, 140885802, 145512513, 149311222, 149994636, 157557529, 160778700, 162870981, 179649435, 194194460, 227378628, 230933064, 252046196, 272053956, 277878683, 288331253, 290266880, 293973036, 305315023, 321927758, 353841539, 356489212, 373843111, 381697287, 389274717, 403108317, 409994705, 411629694, 431823422, 441976653, 521469643, 521868369, 523044572, 524964447, 530250249, - ], - edge_bits: 29, + ]); + proof.edge_bits = 29; + proof }, }, ..Default::default() diff --git a/core/src/pow/types.rs b/core/src/pow/types.rs index d284d91f37..1761ee259b 100644 --- a/core/src/pow/types.rs +++ b/core/src/pow/types.rs @@ -330,12 +330,23 @@ impl ProofOfWork { /// (i+1) * edge_bits - 1, padding it with up to 7 0-bits to a multiple of 8 bits, /// writing as a little endian byte array, and hashing with blake2b using 256 bit digest. -#[derive(Clone, PartialOrd, PartialEq, Serialize)] +#[derive(Clone, Serialize)] pub struct Proof { /// Power of 2 used for the size of the cuckoo graph pub edge_bits: u8, /// The nonces pub nonces: Vec, + /// Cached packed nonces used for Hash (and full) serialization. + /// + /// Populated when the proof is deserialized from the on-wire / on-disk bit + /// packing, so subsequent `hash()` / `write()` calls do not re-run the + /// relatively expensive bit-packing step. Not populated for locally + /// constructed proofs (mining / tests) unless filled explicitly. + /// + /// **Important:** if you mutate `edge_bits` or `nonces` after construction, + /// call [`Proof::clear_packed_cache`] so a stale cache cannot poison hashes. + #[serde(skip)] + packed_nonces: Option>, } impl DefaultHashable for Proof {} @@ -353,8 +364,28 @@ impl fmt::Debug for Proof { } } +impl PartialEq for Proof { + fn eq(&self, other: &Proof) -> bool { + self.edge_bits == other.edge_bits && self.nonces == other.nonces + } +} + impl Eq for Proof {} +impl PartialOrd for Proof { + fn partial_cmp(&self, other: &Proof) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Proof { + fn cmp(&self, other: &Proof) -> std::cmp::Ordering { + self.edge_bits + .cmp(&other.edge_bits) + .then_with(|| self.nonces.cmp(&other.nonces)) + } +} + impl Proof { /// Builds a proof with provided nonces at default edge_bits pub fn new(mut in_nonces: Vec) -> Proof { @@ -362,6 +393,7 @@ impl Proof { Proof { edge_bits: global::min_edge_bits(), nonces: in_nonces, + packed_nonces: None, } } @@ -370,6 +402,7 @@ impl Proof { Proof { edge_bits: global::min_edge_bits(), nonces: vec![0; proof_size], + packed_nonces: None, } } @@ -394,6 +427,7 @@ impl Proof { Proof { edge_bits: global::min_edge_bits(), nonces: v, + packed_nonces: None, } } @@ -402,8 +436,27 @@ impl Proof { self.nonces.len() } - /// Pack the nonces of the proof to their exact bit size as described above + /// Drop any cached packed nonces. Call this after mutating `edge_bits` or `nonces`. + pub fn clear_packed_cache(&mut self) { + self.packed_nonces = None; + } + + /// Whether this proof currently holds a packed-nonces cache. + #[cfg(test)] + pub fn has_packed_cache(&self) -> bool { + self.packed_nonces.is_some() + } + + /// Pack the nonces of the proof to their exact bit size as described above. + /// Uses the deserialized cache when available. pub fn pack_nonces(&self) -> Vec { + if let Some(ref packed) = self.packed_nonces { + return packed.clone(); + } + self.compute_packed_nonces() + } + + fn compute_packed_nonces(&self) -> Vec { let mut compressed = vec![0u8; Proof::pack_len(self.edge_bits)]; pack_bits( self.edge_bits, @@ -510,11 +563,17 @@ impl Readable for Proof { if read_number(&bits, end_of_data, bytes_len * 8 - end_of_data) != 0 { return Err(ser::Error::CorruptedData); } - Ok(Proof { edge_bits, nonces }) + // Cache the on-wire packed form so later hash()/write() skip re-packing. + Ok(Proof { + edge_bits, + nonces, + packed_nonces: Some(bits), + }) } else { Ok(Proof { edge_bits, nonces: vec![], + packed_nonces: None, }) } } @@ -525,7 +584,12 @@ impl Writeable for Proof { if writer.serialization_mode() != ser::SerializationMode::Hash { writer.write_u8(self.edge_bits)?; } - writer.write_fixed_bytes(&self.pack_nonces()) + // Prefer the deserialized packed form (hash-hot path); otherwise pack on demand. + if let Some(ref packed) = self.packed_nonces { + writer.write_fixed_bytes(packed) + } else { + writer.write_fixed_bytes(&self.compute_packed_nonces()) + } } } @@ -542,6 +606,7 @@ mod tests { for edge_bits in 10..63 { let mut proof = Proof::new(gen_proof(edge_bits as u32)); proof.edge_bits = edge_bits; + proof.clear_packed_cache(); let mut buf = Cursor::new(Vec::new()); let mut w = BinWriter::new(&mut buf, ProtocolVersion::local()); if let Err(e) = proof.write(&mut w) { @@ -555,11 +620,43 @@ mod tests { ); match Proof::read(&mut r) { Err(e) => panic!("failed to read proof: {:?}", e), - Ok(p) => assert_eq!(p, proof), + Ok(p) => { + assert_eq!(p, proof); + assert!( + p.has_packed_cache(), + "deserialized proof should cache packed nonces" + ); + } } } } + #[test] + fn packed_cache_matches_recomputed_and_hash() { + global::set_local_chain_type(global::ChainTypes::Mainnet); + let mut proof = Proof::new(gen_proof(29)); + proof.edge_bits = 29; + let hash_before = proof.hash(); + let packed = proof.compute_packed_nonces(); + + // Simulate a deserialized proof: same nonces, cache filled. + let cached = Proof { + edge_bits: proof.edge_bits, + nonces: proof.nonces.clone(), + packed_nonces: Some(packed.clone()), + }; + assert!(cached.has_packed_cache()); + assert_eq!(cached.pack_nonces(), packed); + assert_eq!(cached.hash(), hash_before); + + // Mutating nonces without clearing would be wrong; clear keeps hash correct. + let mut mutated = cached.clone(); + mutated.nonces[0] = mutated.nonces[0].wrapping_add(1); + mutated.clear_packed_cache(); + assert!(!mutated.has_packed_cache()); + assert_ne!(mutated.hash(), hash_before); + } + fn gen_proof(bits: u32) -> Vec { let mut rng = rand::thread_rng(); let mut v = Vec::with_capacity(42); diff --git a/servers/src/mining/stratumserver.rs b/servers/src/mining/stratumserver.rs index d39312a560..b246d6efbd 100644 --- a/servers/src/mining/stratumserver.rs +++ b/servers/src/mining/stratumserver.rs @@ -388,6 +388,8 @@ impl Handler { b.header.pow.proof.edge_bits = params.edge_bits as u8; b.header.pow.nonce = params.nonce; b.header.pow.proof.nonces = params.pow; + // Mutating edge_bits/nonces must drop any packed-nonces cache. + b.header.pow.proof.clear_packed_cache(); if !b.header.pow.is_primary() && !b.header.pow.is_secondary() { // Return error status