commonware_broadcast/linked/signer/
tip_manager.rs1use crate::linked::parsed;
2use commonware_cryptography::Scheme;
3use commonware_utils::Array;
4use std::collections::{hash_map::Entry, HashMap};
5
6#[derive(Default, Debug)]
8pub struct TipManager<C: Scheme, D: Array> {
9 tips: HashMap<C::PublicKey, parsed::Node<C, D>>,
15}
16
17impl<C: Scheme, D: Array> TipManager<C, D> {
18 pub fn new() -> Self {
20 Self {
21 tips: HashMap::new(),
22 }
23 }
24
25 pub fn put(&mut self, node: &parsed::Node<C, D>) -> bool {
28 match self.tips.entry(node.chunk.sequencer.clone()) {
29 Entry::Vacant(e) => {
30 e.insert(node.clone());
31 true
32 }
33 Entry::Occupied(mut e) => {
34 let old = e.get();
35 if old.chunk.height > node.chunk.height {
36 panic!("Attempted to insert a lower-height tip");
37 }
38 if old.chunk.height == node.chunk.height {
39 assert!(
40 old.chunk.payload == node.chunk.payload,
41 "New tip has the same height but a different payload"
42 );
43 return false;
44 }
45 e.insert(node.clone());
46 true
47 }
48 }
49 }
50
51 pub fn get(&self, sequencer: &C::PublicKey) -> Option<parsed::Node<C, D>> {
53 self.tips.get(sequencer).cloned()
54 }
55
56 pub fn len(&self) -> usize {
58 self.tips.len()
59 }
60
61 pub fn is_empty(&self) -> bool {
63 self.tips.is_empty()
64 }
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70 use crate::linked::parsed;
71 use bytes::Bytes;
72 use commonware_cryptography::{
73 ed25519::{self, Ed25519, PublicKey, Signature},
74 sha256::{self, Digest},
75 };
76 use commonware_utils::Array;
77 use commonware_utils::SizedSerialize;
78 use rand::SeedableRng;
79
80 mod helpers {
82 use super::*;
83
84 pub fn create_dummy_node(
86 sequencer: PublicKey,
87 height: u64,
88 payload: &str,
89 ) -> parsed::Node<Ed25519, Digest> {
90 let signature = {
91 let mut data = Bytes::from(vec![3u8; Signature::SERIALIZED_LEN]);
92 Signature::read_from(&mut data).unwrap()
93 };
94 parsed::Node::<Ed25519, Digest> {
95 chunk: parsed::Chunk {
96 sequencer,
97 height,
98 payload: sha256::hash(payload.as_bytes()),
99 },
100 signature,
101 parent: None,
102 }
103 }
104
105 pub fn deterministic_public_key(seed: u64) -> ed25519::PublicKey {
107 let mut rng = rand::rngs::StdRng::seed_from_u64(seed);
108 ed25519::Ed25519::new(&mut rng).public_key()
109 }
110
111 pub fn insert_tip(
113 manager: &mut TipManager<Ed25519, Digest>,
114 key: ed25519::PublicKey,
115 height: u64,
116 payload: &str,
117 ) -> parsed::Node<Ed25519, Digest> {
118 let node = create_dummy_node(key.clone(), height, payload);
119 manager.put(&node);
120 node
121 }
122 }
123
124 #[test]
126 fn test_put_new_tip() {
127 let mut manager = TipManager::<Ed25519, Digest>::new();
128 let key = helpers::deterministic_public_key(1);
129 let node = helpers::create_dummy_node(key.clone(), 1, "payload");
130 assert!(manager.put(&node));
131 let got = manager.get(&key).unwrap();
132 assert_eq!(got.chunk, node.chunk);
133 assert_eq!(got.signature, node.signature);
134 assert_eq!(got.parent, node.parent);
135 }
136
137 #[test]
139 fn test_put_same_height_same_payload() {
140 let mut manager = TipManager::<Ed25519, Digest>::new();
141 let key = helpers::deterministic_public_key(2);
142 let node = helpers::create_dummy_node(key.clone(), 1, "payload");
143 assert!(manager.put(&node));
144 assert!(!manager.put(&node));
145 let got = manager.get(&key).unwrap();
146 assert_eq!(got.chunk, node.chunk);
147 assert_eq!(got.signature, node.signature);
148 assert_eq!(got.parent, node.parent);
149 }
150
151 #[test]
153 fn test_put_higher_tip() {
154 let mut manager = TipManager::<Ed25519, Digest>::new();
155 let key = helpers::deterministic_public_key(3);
156 let node1 = helpers::create_dummy_node(key.clone(), 1, "payload1");
157 assert!(manager.put(&node1));
158 let node2 = helpers::create_dummy_node(key.clone(), 2, "payload2");
159 assert!(manager.put(&node2));
160 let got = manager.get(&key).unwrap();
161 assert_eq!(got.chunk, node2.chunk);
162 assert_eq!(got.signature, node2.signature);
163 assert_eq!(got.parent, node2.parent);
164 }
165
166 #[test]
168 #[should_panic(expected = "Attempted to insert a lower-height tip")]
169 fn test_put_lower_tip_panics() {
170 let mut manager = TipManager::<Ed25519, Digest>::new();
171 let key = helpers::deterministic_public_key(4);
172 let node1 = helpers::create_dummy_node(key.clone(), 2, "payload");
173 assert!(manager.put(&node1));
174 let node2 = helpers::create_dummy_node(key.clone(), 1, "payload");
175 manager.put(&node2);
176 }
177
178 #[test]
180 #[should_panic]
181 fn test_put_same_height_different_payload_panics() {
182 let mut manager = TipManager::<Ed25519, Digest>::new();
183 let key = helpers::deterministic_public_key(5);
184 let node1 = helpers::create_dummy_node(key.clone(), 1, "payload1");
185 assert!(manager.put(&node1));
186 let node2 = helpers::create_dummy_node(key.clone(), 1, "payload2");
187 manager.put(&node2);
188 }
189
190 #[test]
192 fn test_get_nonexistent() {
193 let manager = TipManager::<Ed25519, Digest>::new();
194 let key = helpers::deterministic_public_key(6);
195 assert!(manager.get(&key).is_none());
196 }
197
198 #[test]
200 fn test_multiple_sequencers() {
201 let mut manager = TipManager::<Ed25519, Digest>::new();
202 let key1 = helpers::deterministic_public_key(10);
203 let key2 = helpers::deterministic_public_key(20);
204 let node1 = helpers::insert_tip(&mut manager, key1.clone(), 1, "payload1");
205 let node2 = helpers::insert_tip(&mut manager, key2.clone(), 2, "payload2");
206
207 let got1 = manager.get(&key1).unwrap();
208 let got2 = manager.get(&key2).unwrap();
209 assert_eq!(got1.chunk, node1.chunk);
210 assert_eq!(got2.chunk, node2.chunk);
211 }
212
213 #[test]
215 fn test_put_multiple_updates() {
216 let mut manager = TipManager::<Ed25519, Digest>::new();
217 let key = helpers::deterministic_public_key(7);
218
219 let node1 = helpers::insert_tip(&mut manager, key.clone(), 1, "payload1");
221 let got1 = manager.get(&key).unwrap();
222 assert_eq!(got1.chunk.height, 1);
223 assert_eq!(got1.chunk.payload, node1.chunk.payload);
224
225 let node2 = helpers::insert_tip(&mut manager, key.clone(), 2, "payload2");
227 let got2 = manager.get(&key).unwrap();
228 assert_eq!(got2.chunk.height, 2);
229 assert_eq!(got2.chunk.payload, node2.chunk.payload);
230
231 let node3 = helpers::insert_tip(&mut manager, key.clone(), 3, "payload3");
233 let got3 = manager.get(&key).unwrap();
234 assert_eq!(got3.chunk.height, 3);
235 assert_eq!(got3.chunk.payload, node3.chunk.payload);
236
237 assert!(!manager.put(&node3));
239
240 let node4 = helpers::insert_tip(&mut manager, key.clone(), 4, "payload4");
242 let got4 = manager.get(&key).unwrap();
243 assert_eq!(got4.chunk.height, 4);
244 assert_eq!(got4.chunk.payload, node4.chunk.payload);
245 }
246}