use super::types::Node; use commonware_cryptography::{certificate::Scheme, Digest, PublicKey}; use std::collections::{hash_map::Entry, HashMap}; /// Manages the highest-height chunk for each sequencer. #[derive(Default, Debug)] pub struct TipManager { // The highest-height chunk for each sequencer. // The chunk must have the certificate of its parent. // Existence of the chunk implies: // - The existence of the sequencer's entire chunk chain (from height zero) // - That the chunk has been acked by this validator. tips: HashMap>, } impl TipManager { /// Creates a new `TipManager`. pub fn new() -> Self { Self { tips: HashMap::new(), } } /// Inserts a new tip. Returns true if the tip is new. /// Panics if the new tip is lower-height than the existing tip. pub fn put(&mut self, node: &Node) -> bool { match self.tips.entry(node.chunk.sequencer.clone()) { Entry::Vacant(e) => { e.insert(node.clone()); true } Entry::Occupied(mut e) => { let old = e.get(); if old.chunk.height > node.chunk.height { panic!("Attempted to insert a lower-height tip"); } if old.chunk.height == node.chunk.height { assert!( old.chunk.payload == node.chunk.payload, "New tip has the same height but a different payload" ); return false; } e.insert(node.clone()); true } } } /// Returns the tip for the given sequencer. pub fn get(&self, sequencer: &C) -> Option> { self.tips.get(sequencer).cloned() } } #[cfg(test)] mod tests { use super::*; use crate::{ ordered_broadcast::{ scheme::{bls12381_multisig, bls12381_threshold, ed25519, secp256r1, Scheme}, types::{Chunk, ChunkSigner}, }, types::Height, }; use commonware_cryptography::{ bls12381::primitives::variant::{MinPk, MinSig}, certificate::mocks::Fixture, ed25519::PublicKey, sha256::{Digest as Sha256Digest, Sha256}, Hasher as _, Signer as _, }; use commonware_math::algebra::Random; use commonware_utils::test_rng; use rand::{rngs::StdRng, SeedableRng}; use std::panic::catch_unwind; const NAMESPACE: &[u8] = b"tip_manager_test"; /// Creates a node for testing with a given scheme. fn create_node>( fixture: &Fixture, sequencer_idx: usize, height: Height, payload: &str, ) -> Node { let sequencer = fixture.participants[sequencer_idx].clone(); let digest = Sha256::hash(payload.as_bytes()); let chunk = Chunk::new(sequencer, height, digest); // Sign the chunk using a deterministic ed25519 key (since Node.signature is P::Signature, // which is ed25519::Signature for our PublicKey type) let mut rng = StdRng::seed_from_u64(sequencer_idx as u64); let private_key = commonware_cryptography::ed25519::PrivateKey::random(&mut rng); let mut signer = ChunkSigner::new(b"test", private_key); let signature = signer.sign(&chunk); Node::new(chunk, signature, None) } /// Generates a deterministic public key for testing using the provided seed. fn deterministic_public_key(seed: u64) -> PublicKey { let mut rng = StdRng::seed_from_u64(seed); commonware_cryptography::ed25519::PrivateKey::random(&mut rng).public_key() } fn put_new_tip(fixture: F) where S: Scheme, F: FnOnce(&mut StdRng, &[u8], u32) -> Fixture, { let fixture = fixture(&mut test_rng(), NAMESPACE, 4); let mut manager = TipManager::::new(); let node = create_node(&fixture, 0, Height::new(1), "payload"); let key = node.chunk.sequencer.clone(); assert!(manager.put(&node)); let got = manager.get(&key).unwrap(); assert_eq!(got.chunk, node.chunk); assert_eq!(got.signature, node.signature); assert_eq!(got.parent, node.parent); } #[test] fn test_put_new_tip() { put_new_tip(ed25519::fixture); put_new_tip(secp256r1::fixture); put_new_tip(bls12381_multisig::fixture::); put_new_tip(bls12381_multisig::fixture::); put_new_tip(bls12381_threshold::fixture::); put_new_tip(bls12381_threshold::fixture::); } fn put_same_height_same_payload(fixture: F) where S: Scheme, F: FnOnce(&mut StdRng, &[u8], u32) -> Fixture, { let fixture = fixture(&mut test_rng(), NAMESPACE, 4); let mut manager = TipManager::::new(); let node = create_node(&fixture, 0, Height::new(1), "payload"); let key = node.chunk.sequencer.clone(); assert!(manager.put(&node)); assert!(!manager.put(&node)); let got = manager.get(&key).unwrap(); assert_eq!(got.chunk, node.chunk); assert_eq!(got.signature, node.signature); assert_eq!(got.parent, node.parent); } #[test] fn test_put_same_height_same_payload() { put_same_height_same_payload(ed25519::fixture); put_same_height_same_payload(secp256r1::fixture); put_same_height_same_payload(bls12381_multisig::fixture::); put_same_height_same_payload(bls12381_multisig::fixture::); put_same_height_same_payload(bls12381_threshold::fixture::); put_same_height_same_payload(bls12381_threshold::fixture::); } fn put_higher_tip(fixture: F) where S: Scheme, F: FnOnce(&mut StdRng, &[u8], u32) -> Fixture, { let fixture = fixture(&mut test_rng(), NAMESPACE, 4); let mut manager = TipManager::::new(); let node1 = create_node(&fixture, 0, Height::new(1), "payload1"); let key = node1.chunk.sequencer.clone(); assert!(manager.put(&node1)); let node2 = create_node(&fixture, 0, Height::new(2), "payload2"); assert!(manager.put(&node2)); let got = manager.get(&key).unwrap(); assert_eq!(got.chunk, node2.chunk); assert_eq!(got.signature, node2.signature); assert_eq!(got.parent, node2.parent); } #[test] fn test_put_higher_tip() { put_higher_tip(ed25519::fixture); put_higher_tip(secp256r1::fixture); put_higher_tip(bls12381_multisig::fixture::); put_higher_tip(bls12381_multisig::fixture::); put_higher_tip(bls12381_threshold::fixture::); put_higher_tip(bls12381_threshold::fixture::); } fn put_lower_tip_panics(fixture: F) where S: Scheme, F: FnOnce(&mut StdRng, &[u8], u32) -> Fixture, { let fixture = fixture(&mut test_rng(), NAMESPACE, 4); let mut manager = TipManager::::new(); let node1 = create_node(&fixture, 0, Height::new(2), "payload"); assert!(manager.put(&node1)); let node2 = create_node(&fixture, 0, Height::new(1), "payload"); manager.put(&node2); // Should panic } #[test] fn test_put_lower_tip_panics() { assert!(catch_unwind(|| put_lower_tip_panics(ed25519::fixture)).is_err()); assert!(catch_unwind(|| put_lower_tip_panics(secp256r1::fixture)).is_err()); assert!( catch_unwind(|| put_lower_tip_panics(bls12381_multisig::fixture::)).is_err() ); assert!( catch_unwind(|| put_lower_tip_panics(bls12381_multisig::fixture::)).is_err() ); assert!( catch_unwind(|| put_lower_tip_panics(bls12381_threshold::fixture::)).is_err() ); assert!( catch_unwind(|| put_lower_tip_panics(bls12381_threshold::fixture::)) .is_err() ); } fn put_same_height_different_payload_panics(fixture: F) where S: Scheme, F: FnOnce(&mut StdRng, &[u8], u32) -> Fixture, { let fixture = fixture(&mut test_rng(), NAMESPACE, 4); let mut manager = TipManager::::new(); let node1 = create_node(&fixture, 0, Height::new(1), "payload1"); assert!(manager.put(&node1)); let node2 = create_node(&fixture, 0, Height::new(1), "payload2"); manager.put(&node2); // Should panic } #[test] fn test_put_same_height_different_payload_panics() { assert!( catch_unwind(|| put_same_height_different_payload_panics(ed25519::fixture)).is_err() ); assert!( catch_unwind(|| put_same_height_different_payload_panics(secp256r1::fixture)).is_err() ); assert!(catch_unwind(|| put_same_height_different_payload_panics( bls12381_multisig::fixture:: )) .is_err()); assert!(catch_unwind(|| put_same_height_different_payload_panics( bls12381_multisig::fixture:: )) .is_err()); assert!(catch_unwind(|| put_same_height_different_payload_panics( bls12381_threshold::fixture:: )) .is_err()); assert!(catch_unwind(|| put_same_height_different_payload_panics( bls12381_threshold::fixture:: )) .is_err()); } fn get_nonexistent() where S: Scheme, { let manager = TipManager::::new(); let key = deterministic_public_key(6); assert!(manager.get(&key).is_none()); } #[test] fn test_get_nonexistent() { get_nonexistent::(); get_nonexistent::>(); get_nonexistent::>(); get_nonexistent::>(); get_nonexistent::>(); get_nonexistent::>(); } fn multiple_sequencers(fixture: F) where S: Scheme, F: FnOnce(&mut StdRng, &[u8], u32) -> Fixture, { let fixture = fixture(&mut test_rng(), NAMESPACE, 4); let mut manager = TipManager::::new(); let node1 = create_node(&fixture, 0, Height::new(1), "payload1"); let node2 = create_node(&fixture, 1, Height::new(2), "payload2"); let key1 = node1.chunk.sequencer.clone(); let key2 = node2.chunk.sequencer.clone(); manager.put(&node1); manager.put(&node2); let got1 = manager.get(&key1).unwrap(); let got2 = manager.get(&key2).unwrap(); assert_eq!(got1.chunk, node1.chunk); assert_eq!(got2.chunk, node2.chunk); } #[test] fn test_multiple_sequencers() { multiple_sequencers(ed25519::fixture); multiple_sequencers(secp256r1::fixture); multiple_sequencers(bls12381_multisig::fixture::); multiple_sequencers(bls12381_multisig::fixture::); multiple_sequencers(bls12381_threshold::fixture::); multiple_sequencers(bls12381_threshold::fixture::); } fn put_multiple_updates(fixture: F) where S: Scheme, F: FnOnce(&mut StdRng, &[u8], u32) -> Fixture, { let fixture = fixture(&mut test_rng(), NAMESPACE, 4); let mut manager = TipManager::::new(); // Insert tip with height 1. let node1 = create_node(&fixture, 0, Height::new(1), "payload1"); let key = node1.chunk.sequencer.clone(); manager.put(&node1); let got1 = manager.get(&key).unwrap(); assert_eq!(got1.chunk.height, Height::new(1)); assert_eq!(got1.chunk.payload, node1.chunk.payload); // Insert tip with height 2. let node2 = create_node(&fixture, 0, Height::new(2), "payload2"); manager.put(&node2); let got2 = manager.get(&key).unwrap(); assert_eq!(got2.chunk.height, Height::new(2)); assert_eq!(got2.chunk.payload, node2.chunk.payload); // Insert tip with height 3. let node3 = create_node(&fixture, 0, Height::new(3), "payload3"); manager.put(&node3); let got3 = manager.get(&key).unwrap(); assert_eq!(got3.chunk.height, Height::new(3)); assert_eq!(got3.chunk.payload, node3.chunk.payload); // Re-inserting the same tip should return false. assert!(!manager.put(&node3)); // Insert tip with height 4. let node4 = create_node(&fixture, 0, Height::new(4), "payload4"); manager.put(&node4); let got4 = manager.get(&key).unwrap(); assert_eq!(got4.chunk.height, Height::new(4)); assert_eq!(got4.chunk.payload, node4.chunk.payload); } #[test] fn test_put_multiple_updates() { put_multiple_updates(ed25519::fixture); put_multiple_updates(secp256r1::fixture); put_multiple_updates(bls12381_multisig::fixture::); put_multiple_updates(bls12381_multisig::fixture::); put_multiple_updates(bls12381_threshold::fixture::); put_multiple_updates(bls12381_threshold::fixture::); } }