1use crate::Context;
2use crate::consensus::doms::tx::EntryTx;
3use crate::consensus::fabric;
4use crate::node::protocol;
5use crate::node::protocol::{Handle, Typename};
6use crate::utils::bls12_381;
7use crate::utils::misc::{bin_to_bitvec, bitvec_to_bin, get_unix_millis_now};
8use crate::utils::{Hash, PublicKey, Signature};
9use crate::utils::{archiver, blake3};
10use amadeus_utils::constants::DST_VRF;
12use amadeus_utils::vecpak::{Term, VecpakExt, decode, encode};
13use bitvec::prelude::*;
14use amadeus_utils::vecpak;
16use std::fmt;
17use std::net::Ipv4Addr;
18
19#[derive(Debug, thiserror::Error)]
20pub enum Error {
21 #[error(transparent)]
22 Io(#[from] std::io::Error),
23 #[error(transparent)]
24 EtfDecode(#[from] eetf::DecodeError),
25 #[error(transparent)]
26 EtfEncode(#[from] eetf::EncodeError),
27 #[error(transparent)]
28 BinDecode(#[from] bincode::error::DecodeError),
29 #[error(transparent)]
30 BinEncode(#[from] bincode::error::EncodeError),
31 #[error("bad format: {0}")]
32 BadFormat(&'static str),
33 #[error(transparent)]
34 Tx(#[from] super::tx::Error),
35 #[error(transparent)]
36 Bls(#[from] bls12_381::Error),
37 #[error(transparent)]
38 Fabric(#[from] fabric::Error),
39 #[error(transparent)]
40 Archiver(#[from] archiver::Error),
41 #[error(transparent)]
42 RocksDb(#[from] crate::utils::rocksdb::Error),
43}
44
45#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
47pub struct EntrySummary {
48 pub header: EntryHeader,
49 pub signature: Signature,
50 #[serde(default, skip_serializing_if = "Option::is_none", with = "mask_serde")]
51 pub mask: Option<BitVec<u8, Msb0>>,
52}
53
54impl From<Entry> for EntrySummary {
55 fn from(entry: Entry) -> Self {
56 Self { header: entry.header, signature: entry.signature, mask: entry.mask }
57 }
58}
59
60impl EntrySummary {
61 pub fn from_vecpak_map(map: &amadeus_utils::vecpak::PropListMap) -> Result<Self, Error> {
63 let hmap = map
64 .get_by_key(b"header")
65 .ok_or(Error::BadFormat("entry.header"))?
66 .get_proplist_map()
67 .ok_or(Error::BadFormat("entry.header"))?;
68
69 let header = EntryHeader {
70 height: hmap.get_integer(b"height").ok_or(Error::BadFormat("entry.header.height"))?,
71 slot: hmap.get_integer(b"slot").ok_or(Error::BadFormat("entry.header.slot"))?,
72 prev_slot: hmap.get_integer(b"prev_slot").ok_or(Error::BadFormat("entry.header.prev_slot"))?,
73 prev_hash: hmap.get_binary(b"prev_hash").ok_or(Error::BadFormat("entry.header.prev_hash"))?,
74 dr: hmap.get_binary(b"dr").ok_or(Error::BadFormat("entry.header.dr"))?,
75 vr: hmap.get_binary(b"vr").ok_or(Error::BadFormat("entry.header.vr"))?,
76 signer: hmap.get_binary(b"signer").ok_or(Error::BadFormat("entry.header.signer"))?,
77 root_tx: hmap.get_binary(b"root_tx").ok_or(Error::BadFormat("entry.header.root_tx"))?,
78 root_validator: hmap
79 .get_binary(b"root_validator")
80 .ok_or(Error::BadFormat("entry.header.root_validator"))?,
81 };
82
83 let mask = map.get_binary::<Vec<u8>>(b"mask").map(bin_to_bitvec);
84 let signature: Signature = map.get_binary(b"signature").ok_or(Error::BadFormat("entry.signature"))?;
85
86 Ok(Self { header, signature, mask })
87 }
88
89 pub fn to_vecpak_term(&self) -> Term {
90 let mut props = vec![
91 (Term::Binary(b"header".to_vec()), self.header.to_vecpak_term()),
92 (Term::Binary(b"signature".to_vec()), Term::Binary(self.signature.to_vec())),
93 ];
94 if let Some(mask) = &self.mask {
95 props.push((Term::Binary(b"mask".to_vec()), Term::Binary(bitvec_to_bin(mask))));
96 }
97 Term::PropList(props)
98 }
99
100 pub fn empty() -> Self {
102 let header = EntryHeader {
103 height: 0,
104 slot: 0,
105 prev_slot: 0,
106 prev_hash: Hash::from([0u8; 32]),
107 dr: Hash::from([0u8; 32]),
108 vr: Signature::from([0u8; 96]),
109 signer: PublicKey::from([0u8; 48]),
110 root_tx: Hash::from([0u8; 32]),
111 root_validator: Hash::from([0u8; 32]),
112 };
113 Self { header, signature: Signature::from([0u8; 96]), mask: None }
114 }
115}
116
117#[derive(Clone, serde::Serialize, serde::Deserialize)]
118pub struct EntryHeader {
119 pub height: u64,
120 pub slot: u64,
121 pub prev_slot: i64, pub prev_hash: Hash,
123 pub dr: Hash, pub vr: Signature, pub signer: PublicKey,
126 #[serde(default = "zero_hash", skip_serializing_if = "is_zero_hash")]
127 pub root_tx: Hash,
128 #[serde(default = "zero_hash", skip_serializing_if = "is_zero_hash")]
129 pub root_validator: Hash,
130}
131
132fn zero_hash() -> Hash {
133 Hash::from([0u8; 32])
134}
135
136fn is_zero_hash(h: &Hash) -> bool {
137 *AsRef::<[u8; 32]>::as_ref(h) == [0u8; 32]
138}
139
140impl fmt::Debug for EntryHeader {
141 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142 f.debug_struct("EntryHeader")
143 .field("slot", &self.slot)
144 .field("dr", &bs58::encode(&self.dr).into_string())
145 .field("height", &self.height)
146 .field("prev_hash", &bs58::encode(&self.prev_hash).into_string())
147 .field("prev_slot", &self.prev_slot)
148 .field("signer", &bs58::encode(&self.signer).into_string())
149 .field("root_tx", &bs58::encode(&self.root_tx).into_string())
150 .field("root_validator", &bs58::encode(&self.root_validator).into_string())
151 .field("vr", &bs58::encode(&self.vr).into_string())
152 .finish()
153 }
154}
155
156impl EntryHeader {
157 pub fn from_vecpak_map(map: &amadeus_utils::vecpak::PropListMap) -> Result<Self, Error> {
158 Ok(EntryHeader {
159 height: map.get_integer(b"height").ok_or(Error::BadFormat("entry.header.height"))?,
160 slot: map.get_integer(b"slot").ok_or(Error::BadFormat("entry.header.slot"))?,
161 prev_slot: map.get_integer(b"prev_slot").ok_or(Error::BadFormat("entry.header.prev_slot"))?,
162 prev_hash: map.get_binary(b"prev_hash").ok_or(Error::BadFormat("entry.header.prev_hash"))?,
163 dr: map.get_binary(b"dr").ok_or(Error::BadFormat("entry.header.dr"))?,
164 vr: map.get_binary(b"vr").ok_or(Error::BadFormat("entry.header.vr"))?,
165 signer: map.get_binary(b"signer").ok_or(Error::BadFormat("entry.header.signer"))?,
166 root_tx: map.get_binary(b"root_tx").unwrap_or_else(zero_hash),
167 root_validator: map.get_binary(b"root_validator").unwrap_or_else(zero_hash),
168 })
169 }
170
171 pub fn to_vecpak_term(&self) -> Term {
172 let mut props = vec![
173 (Term::Binary(b"height".to_vec()), Term::VarInt(self.height as i128)),
174 (Term::Binary(b"slot".to_vec()), Term::VarInt(self.slot as i128)),
175 (Term::Binary(b"prev_slot".to_vec()), Term::VarInt(self.prev_slot as i128)),
176 (Term::Binary(b"prev_hash".to_vec()), Term::Binary(self.prev_hash.to_vec())),
177 (Term::Binary(b"dr".to_vec()), Term::Binary(self.dr.to_vec())),
178 (Term::Binary(b"vr".to_vec()), Term::Binary(self.vr.to_vec())),
179 (Term::Binary(b"signer".to_vec()), Term::Binary(self.signer.to_vec())),
180 ];
181 if !is_zero_hash(&self.root_tx) {
182 props.push((Term::Binary(b"root_tx".to_vec()), Term::Binary(self.root_tx.to_vec())));
183 }
184 if !is_zero_hash(&self.root_validator) {
185 props.push((Term::Binary(b"root_validator".to_vec()), Term::Binary(self.root_validator.to_vec())));
186 }
187 Term::PropList(props)
188 }
189
190 pub fn to_vecpak_bin(&self) -> Vec<u8> {
191 let term = self.to_vecpak_term();
192 encode(term)
193 }
194}
195
196mod mask_serde {
197 use super::{BitVec, Msb0, bin_to_bitvec, bitvec_to_bin};
198 use serde::{Deserialize, Deserializer, Serialize, Serializer};
199 pub fn serialize<S: Serializer>(mask: &Option<BitVec<u8, Msb0>>, ser: S) -> Result<S::Ok, S::Error> {
200 match mask {
201 Some(m) => serde_bytes::Bytes::new(&bitvec_to_bin(m)).serialize(ser),
202 None => ser.serialize_none(),
203 }
204 }
205 pub fn deserialize<'de, D: Deserializer<'de>>(de: D) -> Result<Option<BitVec<u8, Msb0>>, D::Error> {
206 let v: Option<serde_bytes::ByteBuf> = Deserialize::deserialize(de)?;
207 Ok(v.map(|b| bin_to_bitvec(b.into_vec())))
208 }
209}
210
211mod txs_serde {
213 use super::EntryTx;
214 use amadeus_utils::vecpak;
215 use serde::de::{self, SeqAccess, Visitor};
216 use serde::{Deserialize, Deserializer, Serialize, Serializer};
217 use std::fmt;
218
219 pub fn serialize<S: Serializer>(txs: &Vec<EntryTx>, ser: S) -> Result<S::Ok, S::Error> {
220 txs.serialize(ser)
221 }
222
223 struct TxItemVisitor;
225
226 impl<'de> Visitor<'de> for TxItemVisitor {
227 type Value = Option<EntryTx>;
228
229 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
230 formatter.write_str("a binary blob or structured EntryTx")
231 }
232
233 fn visit_bytes<E: de::Error>(self, v: &[u8]) -> Result<Self::Value, E> {
234 Ok(vecpak::from_slice::<EntryTx>(v).ok())
236 }
237
238 fn visit_byte_buf<E: de::Error>(self, v: Vec<u8>) -> Result<Self::Value, E> {
239 Ok(vecpak::from_slice::<EntryTx>(&v).ok())
240 }
241
242 fn visit_map<M: de::MapAccess<'de>>(self, map: M) -> Result<Self::Value, M::Error> {
243 let de = de::value::MapAccessDeserializer::new(map);
245 EntryTx::deserialize(de).map(Some)
246 }
247 }
248
249 struct TxItemDeserializer;
250
251 impl<'de> de::DeserializeSeed<'de> for TxItemDeserializer {
252 type Value = Option<EntryTx>;
253
254 fn deserialize<D: Deserializer<'de>>(self, de: D) -> Result<Self::Value, D::Error> {
255 de.deserialize_any(TxItemVisitor)
256 }
257 }
258
259 struct TxsVisitor;
260
261 impl<'de> Visitor<'de> for TxsVisitor {
262 type Value = Vec<EntryTx>;
263
264 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
265 formatter.write_str("a list of transactions")
266 }
267
268 fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
269 let mut txs = Vec::with_capacity(seq.size_hint().unwrap_or(0));
270 while let Some(maybe_tx) = seq.next_element_seed(TxItemDeserializer)? {
271 if let Some(tx) = maybe_tx {
272 txs.push(tx);
273 }
274 }
275 Ok(txs)
276 }
277 }
278
279 pub fn deserialize<'de, D: Deserializer<'de>>(de: D) -> Result<Vec<EntryTx>, D::Error> {
280 de.deserialize_seq(TxsVisitor)
281 }
282}
283
284#[derive(Clone, serde::Serialize, serde::Deserialize)]
285pub struct Entry {
286 pub header: EntryHeader,
287 #[serde(with = "txs_serde")]
288 pub txs: Vec<EntryTx>,
289 pub hash: Hash,
290 pub signature: Signature,
291 #[serde(default, skip_serializing_if = "Option::is_none", with = "mask_serde")]
292 pub mask: Option<BitVec<u8, Msb0>>,
293}
294
295impl Entry {
296 pub fn from_vecpak_bin(bin: &[u8]) -> Result<Self, Error> {
297 let map = decode(bin)
298 .map_err(|_| Error::BadFormat("entry_packed"))?
299 .get_proplist_map()
300 .ok_or(Error::BadFormat("entry_packed"))?;
301 Self::from_vecpak_map(&map)
302 }
303
304 pub fn to_vecpak_bin(&self) -> Vec<u8> {
305 let term = self.to_vecpak_term();
306 encode(term)
307 }
308
309 pub fn from_vecpak_map(map: &amadeus_utils::vecpak::PropListMap) -> Result<Self, Error> {
310 let hash: Hash = map.get_binary(b"hash").ok_or(Error::BadFormat("entry.hash"))?;
311 let signature: Signature = map.get_binary(b"signature").ok_or(Error::BadFormat("entry.signature"))?;
312
313 let hmap = map
314 .get_by_key(b"header")
315 .ok_or(Error::BadFormat("entry.header"))?
316 .get_proplist_map()
317 .ok_or(Error::BadFormat("entry.header"))?;
318 let header = EntryHeader::from_vecpak_map(&hmap)?;
319
320 let mask = map.get_binary::<Vec<u8>>(b"mask").map(bin_to_bitvec);
321
322 let txs = map
324 .get_list(b"txs")
325 .map(|list| {
326 list.iter()
327 .filter_map(|t| {
328 let term_bin = encode(t.clone());
330 vecpak::from_slice::<EntryTx>(&term_bin).ok()
331 })
332 .collect()
333 })
334 .unwrap_or_default();
335
336 Ok(Entry { hash, header, signature, mask, txs })
337 }
338
339 pub fn to_vecpak_term(&self) -> Term {
340 let txs_list = Term::List(
342 self.txs
343 .iter()
344 .filter_map(|tx| {
345 let bin = vecpak::to_vec(tx).ok()?;
346 decode(&bin).ok()
347 })
348 .collect(),
349 );
350 let mut props = vec![
351 (Term::Binary(b"header".to_vec()), self.header.to_vecpak_term()),
352 (Term::Binary(b"txs".to_vec()), txs_list),
353 (Term::Binary(b"hash".to_vec()), Term::Binary(self.hash.to_vec())),
354 (Term::Binary(b"signature".to_vec()), Term::Binary(self.signature.to_vec())),
355 ];
356 if let Some(mask) = &self.mask {
357 props.push((Term::Binary(b"mask".to_vec()), Term::Binary(bitvec_to_bin(mask))));
358 }
359 Term::PropList(props)
360 }
361}
362
363#[derive(Clone, serde::Serialize, serde::Deserialize)]
364pub struct EventEntry {
365 pub entry_packed: Entry,
366}
367
368impl EventEntry {
369 pub const TYPENAME: &'static str = "event_entry";
370}
371
372impl Typename for EventEntry {
373 fn typename(&self) -> &'static str {
374 Self::TYPENAME
375 }
376}
377
378impl fmt::Debug for EventEntry {
379 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
380 f.debug_struct("EntryProto").field("entry_packed", &self.entry_packed).finish()
381 }
382}
383
384#[async_trait::async_trait]
385impl Handle for EventEntry {
386 async fn handle(&self, ctx: &Context, src: Ipv4Addr) -> Result<Vec<protocol::Instruction>, protocol::Error> {
387 self.entry_packed.handle(ctx, src).await
388 }
389}
390
391impl crate::utils::misc::Typename for Entry {
392 fn typename(&self) -> &'static str {
393 Self::TYPENAME
394 }
395}
396
397impl Entry {
398 async fn handle(&self, ctx: &Context, _src: Ipv4Addr) -> Result<Vec<protocol::Instruction>, protocol::Error> {
399 let height = self.header.height;
400
401 let rooted_height = ctx
403 .fabric
404 .get_rooted_hash()
405 .ok()
406 .flatten()
407 .map(TryInto::try_into)
408 .and_then(|h| h.ok())
409 .and_then(|h| ctx.fabric.get_entry_by_hash(&h))
410 .map(|e| e.header.height)
411 .unwrap_or(0);
412
413 if height >= rooted_height {
414 let hash = self.hash;
415 let slot = self.header.slot;
416 let bin = self.to_vecpak_bin();
417
418 ctx.fabric.insert_entry(&hash, height, slot, &bin, get_unix_millis_now())?;
419
420 }
423
424 Ok(vec![protocol::Instruction::Noop { why: "entry handling not implemented".to_string() }])
425 }
426}
427
428impl fmt::Debug for Entry {
429 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
430 f.debug_struct("Entry")
431 .field("hash", &bs58::encode(&self.hash).into_string())
432 .field("header", &self.header)
433 .field("signature", &bs58::encode(&self.signature).into_string())
434 .field("txs", &self.txs.iter().map(|tx| bs58::encode(&tx.hash).into_string()).collect::<Vec<String>>())
435 .finish()
436 }
437}
438
439impl Entry {
440 pub const TYPENAME: &'static str = "event_entry";
441
442 pub fn build_next_header(&self, slot: u64, signer_pk: &PublicKey, signer_sk: &[u8]) -> Result<EntryHeader, Error> {
445 let dr = blake3::hash(self.header.dr.as_ref());
447 let vr = bls12_381::sign(signer_sk, self.header.vr.as_ref(), DST_VRF)?;
449
450 Ok(EntryHeader {
451 slot,
452 height: self.header.height + 1,
453 prev_slot: self.header.slot as i64,
454 prev_hash: self.hash,
455 dr: Hash::from(dr),
456 vr,
457 signer: *signer_pk,
458 root_tx: Hash::from([0u8; 32]),
459 root_validator: Hash::from([0u8; 32]),
460 })
461 }
462
463 pub fn get_epoch(&self) -> u64 {
464 self.header.height / 100_000
465 }
466
467 pub fn contains_tx(&self, tx_function: &str) -> bool {
468 self.txs.iter().any(|tx| tx.tx.action.function.as_slice() == tx_function.as_bytes())
469 }
470}
471
472pub async fn get_archived_entries() -> Result<Vec<(u64, u64, u64)>, Error> {
474 let filenames_with_sizes = archiver::get_archived_filenames().await?;
475 let mut entries = Vec::new();
476
477 for (filename, file_size) in filenames_with_sizes {
478 if let Some((epoch, height)) = parse_entry_filename(&filename) {
479 entries.push((epoch, height, file_size));
480 }
481 }
482
483 entries.sort_by(|a, b| a.0.cmp(&b.0).then(a.1.cmp(&b.1)));
485 entries.dedup(); Ok(entries)
488}
489
490fn parse_entry_filename(filename: &str) -> Option<(u64, u64)> {
493 let parts: Vec<&str> = filename.split('/').collect();
495
496 let mut epoch = None;
497 let mut height = None;
498
499 for part in &parts {
501 if let Some(epoch_str) = part.strip_prefix("epoch-") {
502 if let Ok(e) = epoch_str.parse::<u64>() {
503 epoch = Some(e);
504 }
505 }
506 }
507
508 if let Some(filename_part) = parts.last() {
510 if let Some(height_str) = filename_part.strip_prefix("entry-") {
511 if let Ok(h) = height_str.parse::<u64>() {
512 height = Some(h);
513 }
514 }
515 }
516
517 match (epoch, height) {
518 (Some(e), Some(h)) => Some((e, h)),
519 _ => None,
520 }
521}
522
523#[cfg(test)]
524mod tests {
525 use super::*;
526 use crate::consensus::doms::tx::{EntryTx, EntryTxAction, EntryTxInner};
527
528 fn make_test_tx(nonce: i128) -> EntryTx {
529 EntryTx {
530 hash: Hash::from([0xABu8; 32]),
531 signature: Signature::from([0xCDu8; 96]),
532 tx: EntryTxInner {
533 action: EntryTxAction {
534 args: vec![vec![1, 2, 3]],
535 contract: b"TestContract".to_vec(),
536 function: b"test_func".to_vec(),
537 op: "call".to_string(),
538 attached_symbol: None,
539 attached_amount: None,
540 },
541 nonce,
542 signer: PublicKey::from([0xEFu8; 48]),
543 },
544 }
545 }
546
547 #[test]
548 fn test_parse_entry_filename() {
549 assert_eq!(parse_entry_filename("epoch-0/entry-12345"), Some((0, 12345)));
551 assert_eq!(parse_entry_filename("epoch-123/entry-456"), Some((123, 456)));
552 assert_eq!(parse_entry_filename("epoch-999/subdir/entry-789"), Some((999, 789)));
553
554 assert_eq!(parse_entry_filename("not-epoch/entry-123"), None);
556 assert_eq!(parse_entry_filename("epoch-123/not-entry"), None);
557 assert_eq!(parse_entry_filename("epoch-abc/entry-123"), None);
558 assert_eq!(parse_entry_filename("epoch-123/entry-def"), None);
559 assert_eq!(parse_entry_filename("random-file.txt"), None);
560 assert_eq!(parse_entry_filename(""), None);
561 }
562
563 #[tokio::test]
564 async fn test_get_archived_entries_empty() {
565 let result = get_archived_entries().await;
568 match result {
571 Ok(entries) => {
572 for i in 1..entries.len() {
574 let prev = entries[i - 1];
575 let curr = entries[i];
576 assert!(prev.0 < curr.0 || (prev.0 == curr.0 && prev.1 <= curr.1));
577 assert!(curr.2 > 0 || curr.2 == 0); }
580 }
581 Err(_) => {
582 }
584 }
585 }
586
587 #[test]
588 fn test_entry_serde_vecpak_roundtrip() {
589 use amadeus_utils::vecpak;
590
591 let header = EntryHeader {
592 height: 12345,
593 slot: 67890,
594 prev_slot: 67889,
595 prev_hash: Hash::from([1u8; 32]),
596 dr: Hash::from([2u8; 32]),
597 vr: Signature::from([3u8; 96]),
598 signer: PublicKey::from([4u8; 48]),
599 root_tx: Hash::from([5u8; 32]),
600 root_validator: Hash::from([14u8; 32]),
601 };
602 let entry = Entry {
603 hash: Hash::from([6u8; 32]),
604 header,
605 signature: Signature::from([7u8; 96]),
606 mask: Some(bin_to_bitvec(vec![0xFF, 0x00, 0xAB])),
607 txs: vec![make_test_tx(1), make_test_tx(2)],
608 };
609
610 let vecpak_bin = entry.to_vecpak_bin();
612 let decoded: Entry = vecpak::from_slice(&vecpak_bin).expect("from_slice");
613 assert_eq!(decoded.hash, entry.hash);
614 assert_eq!(decoded.header.height, entry.header.height);
615 assert_eq!(decoded.header.slot, entry.header.slot);
616 assert_eq!(decoded.txs.len(), entry.txs.len());
617 assert_eq!(decoded.mask, entry.mask);
618
619 let serde_bin = vecpak::to_vec(&entry).expect("to_vec");
621 let decoded2 = Entry::from_vecpak_bin(&serde_bin).expect("from_vecpak_bin");
622 assert_eq!(decoded2.hash, entry.hash);
623 assert_eq!(decoded2.header.height, entry.header.height);
624 assert_eq!(decoded2.header.slot, entry.header.slot);
625 assert_eq!(decoded2.txs.len(), entry.txs.len());
626 assert_eq!(decoded2.mask, entry.mask);
627
628 assert_eq!(vecpak_bin, serde_bin);
630
631 let entry_no_mask = Entry {
633 hash: Hash::from([8u8; 32]),
634 header: EntryHeader {
635 height: 1,
636 slot: 2,
637 prev_slot: -1,
638 prev_hash: Hash::from([9u8; 32]),
639 dr: Hash::from([10u8; 32]),
640 vr: Signature::from([11u8; 96]),
641 signer: PublicKey::from([12u8; 48]),
642 root_tx: Hash::from([13u8; 32]),
643 root_validator: Hash::from([15u8; 32]),
644 },
645 signature: Signature::from([14u8; 96]),
646 mask: None,
647 txs: vec![],
648 };
649 let vecpak_bin2 = entry_no_mask.to_vecpak_bin();
650 let serde_bin2 = vecpak::to_vec(&entry_no_mask).expect("to_vec");
651 assert_eq!(vecpak_bin2, serde_bin2);
652 let decoded3: Entry = vecpak::from_slice(&vecpak_bin2).expect("from_slice");
653 assert_eq!(decoded3.mask, None);
654 assert_eq!(decoded3.header.prev_slot, -1);
655 }
656
657 #[test]
658 fn test_entry_proto_roundtrip() {
659 use amadeus_utils::vecpak;
660
661 let entry_proto = EventEntry {
663 entry_packed: Entry {
664 hash: Hash::from([0x07u8; 32]),
665 header: EntryHeader {
666 height: 41939338,
667 slot: 41939338,
668 prev_slot: 41939337,
669 prev_hash: Hash::from([0xD9u8; 32]),
670 dr: Hash::from([0x91u8; 32]),
671 vr: Signature::from([0xB3u8; 96]),
672 signer: PublicKey::from([0x95u8; 48]),
673 root_tx: Hash::from([0x3Cu8; 32]),
674 root_validator: Hash::from([0x28u8; 32]),
675 },
676 signature: Signature::from([0x90u8; 96]),
677 mask: None,
678 txs: vec![make_test_tx(1762402566835945439)],
679 },
680 };
681
682 let bin = vecpak::to_vec(&entry_proto).expect("should serialize");
684 println!("Serialized EntryProto: {} bytes", bin.len());
685 println!("Hex: {}", hex::encode(&bin));
686
687 let decoded: EventEntry = vecpak::from_slice(&bin).expect("should deserialize");
689
690 assert_eq!(decoded.entry_packed.header.height, 41939338);
692 assert_eq!(decoded.entry_packed.txs.len(), 1);
693 assert_eq!(decoded.entry_packed.txs[0].tx.action.function.as_slice(), b"test_func");
694
695 println!("Successfully roundtripped EntryProto!");
696 }
697}