Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 10 additions & 8 deletions core/src/genesis.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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()
Expand Down
107 changes: 102 additions & 5 deletions core/src/pow/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u64>,
/// 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<Vec<u8>>,
}

impl DefaultHashable for Proof {}
Expand All @@ -353,15 +364,36 @@ 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<std::cmp::Ordering> {
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<u64>) -> Proof {
in_nonces.sort_unstable();
Proof {
edge_bits: global::min_edge_bits(),
nonces: in_nonces,
packed_nonces: None,
}
}

Expand All @@ -370,6 +402,7 @@ impl Proof {
Proof {
edge_bits: global::min_edge_bits(),
nonces: vec![0; proof_size],
packed_nonces: None,
}
}

Expand All @@ -394,6 +427,7 @@ impl Proof {
Proof {
edge_bits: global::min_edge_bits(),
nonces: v,
packed_nonces: None,
}
}

Expand All @@ -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<u8> {
if let Some(ref packed) = self.packed_nonces {
return packed.clone();
}
self.compute_packed_nonces()
}

fn compute_packed_nonces(&self) -> Vec<u8> {
let mut compressed = vec![0u8; Proof::pack_len(self.edge_bits)];
pack_bits(
self.edge_bits,
Expand Down Expand Up @@ -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,
})
}
}
Expand All @@ -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())
}
}
}

Expand All @@ -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) {
Expand All @@ -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<u64> {
let mut rng = rand::thread_rng();
let mut v = Vec::with_capacity(42);
Expand Down
2 changes: 2 additions & 0 deletions servers/src/mining/stratumserver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading