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
7 changes: 7 additions & 0 deletions ergo-chain-generation/src/chain_generation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,14 @@ mod tests {
suffix_head: proof.suffix_head.clone(),
suffix_tail: proof.suffix_tail.clone(),
};
assert_eq!(
disconnected_proof.validate(),
Err(ergo_nipopow::NipopowValidationError::InvalidProofStructure(
"connections"
))
);
assert!(proof.is_better_than(&disconnected_proof).unwrap());
assert!(!disconnected_proof.is_better_than(&proof).unwrap());
}

#[test]
Expand Down
202 changes: 177 additions & 25 deletions ergo-chain-generation/src/fake_pow_scheme.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ mod tests {
use rand::{thread_rng, Rng};

use crate::{default_miner_secret, ErgoFullBlock, ExtensionCandidate};
use ergo_merkle_tree::{MerkleNode, MerkleTree};
use ergo_merkle_tree::{BatchMerkleProof, MerkleNode, MerkleTree};

fn generate_popowheader_chain(len: usize, start: Option<PoPowHeader>) -> Vec<PoPowHeader> {
block_stream(start.map(|p| ErgoFullBlock {
Expand Down Expand Up @@ -202,15 +202,37 @@ mod tests {
BlockId(Digest32::from(bytes))
}

fn set_first_prefix_interlinks_with_valid_proof(
fn packed_interlink_field_value(count: u8, block_id: BlockId) -> Vec<u8> {
let mut value = vec![count];
let block_id_bytes: Vec<u8> = block_id.0.into();
value.extend(block_id_bytes);
value
}

fn set_suffix_head_interlinks_with_valid_proof(
proof: &mut NipopowProof,
interlinks: Vec<BlockId>,
) {
let extension =
ExtensionCandidate::new(NipopowAlgos::pack_interlinks(interlinks.clone())).unwrap();
let interlinks_proof = NipopowAlgos::proof_for_interlink_vector(&extension).unwrap();
proof.prefix[0].interlinks = interlinks;
proof.prefix[0].interlinks_proof = interlinks_proof;
let extension_root = MerkleTree::new(
extension
.fields()
.iter()
.map(|(key, value)| {
std::iter::once(2u8)
.chain(key.iter().copied())
.chain(value.iter().copied())
.collect::<Vec<_>>()
})
.map(MerkleNode::from_bytes)
.collect::<Vec<_>>(),
)
.root_hash();
proof.suffix_head.interlinks = interlinks;
proof.suffix_head.interlinks_proof = interlinks_proof;
proof.suffix_head.header.extension_root = extension_root;
}

#[test]
Expand All @@ -221,6 +243,23 @@ mod tests {
assert_eq!(verifier.best_proof(), Some(proof));
}

#[test]
fn test_generated_interlinks_have_canonical_run_structure() {
for popow_header in generate_popowheader_chain(100, None) {
assert_eq!(
popow_header.interlinks.is_empty(),
popow_header.header.height == 1
);
let mut run_ids = Vec::new();
for interlink in popow_header.interlinks {
if run_ids.last() != Some(&interlink) {
assert!(!run_ids.contains(&interlink));
run_ids.push(interlink);
}
}
}
}

#[test]
fn test_nipopow_verifier_ignores_wrong_genesis() {
let (_, proof) = generated_nipopow_proof();
Expand Down Expand Up @@ -270,70 +309,171 @@ mod tests {
assert_initial_proof_ignored(genesis_id, proof);
}

#[test]
fn test_nipopow_verifier_rejects_empty_non_genesis_interlinks() {
let (genesis_id, mut proof) = generated_nipopow_proof();
assert!(proof.suffix_head.header.height > 1);
proof.suffix_head.interlinks.clear();
proof.suffix_head.interlinks_proof = BatchMerkleProof::new(vec![], vec![]);
assert!(!proof.suffix_head.check_interlinks_proof());
assert_initial_proof_ignored(genesis_id, proof);
}

#[test]
fn test_nipopow_verifier_rejects_nonempty_genesis_interlinks() {
let (genesis_id, mut proof) = generated_nipopow_proof();
assert_eq!(proof.prefix[0].header.height, 1);
let interlinks = vec![genesis_id];
let extension =
ExtensionCandidate::new(NipopowAlgos::pack_interlinks(interlinks.clone())).unwrap();
proof.prefix[0].interlinks = interlinks;
proof.prefix[0].interlinks_proof =
NipopowAlgos::proof_for_interlink_vector(&extension).unwrap();
assert!(!proof.prefix[0].check_interlinks_proof());
assert_initial_proof_ignored(genesis_id, proof);
}

#[test]
fn test_nipopow_verifier_rejects_nonempty_genesis_interlinks_with_empty_proof() {
let (genesis_id, mut proof) = generated_nipopow_proof();
assert_eq!(proof.prefix[0].header.height, 1);
assert!(proof.prefix[0].interlinks.is_empty());
assert!(proof.prefix[0].interlinks_proof.get_indices().is_empty());
assert!(proof.prefix[0].interlinks_proof.get_proofs().is_empty());
proof.prefix[0].interlinks = vec![genesis_id];
assert!(proof.has_valid_connections());
assert!(!proof.prefix[0].check_interlinks_proof());
assert_initial_proof_ignored(genesis_id, proof);
}

#[test]
fn test_nipopow_verifier_rejects_reopened_interlink_run() {
let (genesis_id, mut proof) = generated_nipopow_proof();
assert!(proof.suffix_head.header.height > 1);
let mut interlinks = proof.suffix_head.interlinks.clone();
let reopened_id = interlinks[0];
assert_ne!(interlinks.last(), Some(&reopened_id));
interlinks.push(reopened_id);
set_suffix_head_interlinks_with_valid_proof(&mut proof, interlinks);
assert!(proof.has_valid_connections());
assert!(proof
.suffix_head
.interlinks_proof
.valid(proof.suffix_head.header.extension_root.as_ref()));
assert!(!proof.suffix_head.check_interlinks_proof());
assert_initial_proof_ignored(genesis_id, proof);
}

#[test]
fn test_nipopow_verifier_rejects_reopened_intermediate_interlink_run() {
let (genesis_id, mut proof) = generated_nipopow_proof();
assert!(proof.suffix_head.header.height > 1);
let mut interlinks = proof.suffix_head.interlinks.clone();
let reopened_id = indexed_block_id(1);
let separating_id = indexed_block_id(2);
assert!(!interlinks.contains(&reopened_id));
assert!(!interlinks.contains(&separating_id));
assert_ne!(reopened_id, separating_id);
interlinks.extend([reopened_id, separating_id, reopened_id]);
set_suffix_head_interlinks_with_valid_proof(&mut proof, interlinks);
assert!(proof.has_valid_connections());
assert!(proof
.suffix_head
.interlinks_proof
.valid(proof.suffix_head.header.extension_root.as_ref()));
assert!(!proof.suffix_head.check_interlinks_proof());
assert_initial_proof_ignored(genesis_id, proof);
}

#[test]
fn test_nipopow_verifier_rejects_interlink_run_overflow() {
let (genesis_id, mut proof) = generated_nipopow_proof();
proof.prefix[0].interlinks = vec![genesis_id; usize::from(u8::MAX) + 1];
assert!(proof.suffix_head.header.height > 1);
let repeated_id = indexed_block_id(1);
assert_ne!(genesis_id, repeated_id);
let interlinks = std::iter::once(genesis_id)
.chain(std::iter::repeat_n(repeated_id, usize::from(u8::MAX) + 1))
.collect();
// This is the exact extension that unchecked release-mode u8
// arithmetic would produce for the overflowing run: 256 wraps to 0.
// A proof over these bytes ensures the structural guard, rather than a
// stale Merkle proof, is what makes the verifier fail closed.
let extension = ExtensionCandidate::new(vec![
(
[INTERLINK_VECTOR_PREFIX, 0],
packed_interlink_field_value(1, genesis_id),
),
(
[INTERLINK_VECTOR_PREFIX, 1],
packed_interlink_field_value(0, repeated_id),
),
])
.unwrap();
proof.suffix_head.interlinks = interlinks;
proof.suffix_head.interlinks_proof =
NipopowAlgos::proof_for_interlink_vector(&extension).unwrap();
assert!(proof.has_valid_connections());
assert!(!proof.suffix_head.check_interlinks_proof());
assert_initial_proof_ignored(genesis_id, proof);
}

#[test]
fn test_nipopow_verifier_rejects_interlink_key_overflow() {
let (genesis_id, mut proof) = generated_nipopow_proof();
proof.prefix[0].interlinks = (0..(usize::from(u8::MAX) + 2))
assert!(proof.suffix_head.header.height > 1);
proof.suffix_head.interlinks = (0..(usize::from(u8::MAX) + 2))
.map(indexed_block_id)
.collect();
assert!(proof.has_valid_connections());
assert!(!proof.suffix_head.check_interlinks_proof());
assert_initial_proof_ignored(genesis_id, proof);
}

#[test]
fn test_nipopow_verifier_rejects_interlink_key_position_overflow() {
let (genesis_id, mut proof) = generated_nipopow_proof();
assert!(proof.suffix_head.header.height > 1);
let second_run_id = indexed_block_id(1);
let third_run_id = indexed_block_id(2);
assert_ne!(genesis_id, second_run_id);
assert_ne!(genesis_id, third_run_id);
assert_ne!(second_run_id, third_run_id);
let mut interlinks = vec![genesis_id; usize::from(u8::MAX)];
interlinks.extend([second_run_id, third_run_id]);
let interlink_field_value = |count: u8, block_id: BlockId| {
let mut value = vec![count];
let block_id_bytes: Vec<u8> = block_id.0.into();
value.extend(block_id_bytes);
value
};
let extension = ExtensionCandidate::new(vec![
(
[INTERLINK_VECTOR_PREFIX, 0],
interlink_field_value(u8::MAX, genesis_id),
packed_interlink_field_value(u8::MAX, genesis_id),
),
(
[INTERLINK_VECTOR_PREFIX, 1],
interlink_field_value(1, second_run_id),
packed_interlink_field_value(1, second_run_id),
),
(
[INTERLINK_VECTOR_PREFIX, 2],
interlink_field_value(1, third_run_id),
packed_interlink_field_value(1, third_run_id),
),
])
.unwrap();
proof.prefix[0].interlinks = interlinks;
proof.prefix[0].interlinks_proof =
proof.suffix_head.interlinks = interlinks;
proof.suffix_head.interlinks_proof =
NipopowAlgos::proof_for_interlink_vector(&extension).unwrap();
assert!(proof.has_valid_connections());
assert!(!proof.suffix_head.check_interlinks_proof());
assert_initial_proof_ignored(genesis_id, proof);
}

#[test]
fn test_nipopow_verifier_accepts_maximum_interlink_run() {
let (genesis_id, mut proof) = generated_nipopow_proof();
set_first_prefix_interlinks_with_valid_proof(
&mut proof,
vec![genesis_id; usize::from(u8::MAX)],
);
assert!(proof.prefix[0].check_interlinks_proof());
assert!(proof.suffix_head.header.height > 1);
let repeated_id = indexed_block_id(1);
assert_ne!(genesis_id, repeated_id);
let interlinks = std::iter::once(genesis_id)
.chain(std::iter::repeat_n(repeated_id, usize::from(u8::MAX)))
.collect();
set_suffix_head_interlinks_with_valid_proof(&mut proof, interlinks);
assert!(proof.suffix_head.check_interlinks_proof());
let mut verifier = NipopowVerifier::new(genesis_id);
assert!(verifier.process(proof.clone()).is_ok());
assert_eq!(verifier.best_proof(), Some(proof));
Expand All @@ -342,9 +482,14 @@ mod tests {
#[test]
fn test_nipopow_verifier_accepts_maximum_interlink_key_count() {
let (genesis_id, mut proof) = generated_nipopow_proof();
let interlinks = (0..=usize::from(u8::MAX)).map(indexed_block_id).collect();
set_first_prefix_interlinks_with_valid_proof(&mut proof, interlinks);
assert!(proof.prefix[0].check_interlinks_proof());
assert!(proof.suffix_head.header.height > 1);
let interlinks: Vec<_> = std::iter::once(genesis_id)
.chain((1..=usize::from(u8::MAX)).map(indexed_block_id))
.collect();
assert_eq!(interlinks.len(), usize::from(u8::MAX) + 1);
assert!(!interlinks[1..].contains(&genesis_id));
set_suffix_head_interlinks_with_valid_proof(&mut proof, interlinks);
assert!(proof.suffix_head.check_interlinks_proof());
let mut verifier = NipopowVerifier::new(genesis_id);
assert!(verifier.process(proof.clone()).is_ok());
assert_eq!(verifier.best_proof(), Some(proof));
Expand Down Expand Up @@ -474,7 +619,14 @@ mod tests {
suffix_head: proof.suffix_head.clone(),
suffix_tail: proof.suffix_tail.clone(),
};
assert_eq!(
disconnected_proof.validate(),
Err(ergo_nipopow::NipopowValidationError::InvalidProofStructure(
"connections"
))
);
assert!(proof.is_better_than(&disconnected_proof).unwrap());
assert!(!disconnected_proof.is_better_than(&proof).unwrap());
}

#[test]
Expand Down
Loading
Loading