1#![forbid(unsafe_code)]
17#![allow(clippy::too_many_arguments)]
18#![cfg_attr(test, allow(clippy::single_element_loop))]
20
21pub mod header;
22pub use header::*;
23
24mod helpers;
25pub use helpers::*;
26
27pub mod ratifications;
28pub use ratifications::*;
29
30pub mod ratify;
31pub use ratify::*;
32
33pub mod solutions;
34pub use solutions::*;
35
36pub mod transaction;
37pub use transaction::*;
38
39pub mod transactions;
40pub use transactions::*;
41
42pub mod transition;
43pub use transition::*;
44
45mod bytes;
46mod genesis;
47mod serialize;
48mod string;
49mod verify;
50
51use console::{
52 account::PrivateKey,
53 network::prelude::*,
54 program::{Ciphertext, Record},
55 types::{Field, Group, U64},
56};
57use ledger_authority::Authority;
58use ledger_committee::Committee;
59use ledger_narwhal_data::Data;
60use ledger_narwhal_subdag::Subdag;
61use ledger_narwhal_transmission_id::TransmissionID;
62use ledger_puzzle::{PuzzleSolutions, Solution, SolutionID};
63
64#[derive(Clone, PartialEq, Eq)]
65pub struct Block<N: Network> {
66 block_hash: N::BlockHash,
68 previous_hash: N::BlockHash,
70 header: Header<N>,
72 authority: Authority<N>,
74 ratifications: Ratifications<N>,
76 solutions: Solutions<N>,
78 aborted_solution_ids: Vec<SolutionID<N>>,
80 transactions: Transactions<N>,
82 aborted_transaction_ids: Vec<N::TransactionID>,
84}
85
86impl<N: Network> Block<N> {
87 pub fn new_beacon<R: Rng + CryptoRng>(
90 private_key: &PrivateKey<N>,
91 previous_hash: N::BlockHash,
92 header: Header<N>,
93 ratifications: Ratifications<N>,
94 solutions: Solutions<N>,
95 aborted_solution_ids: Vec<SolutionID<N>>,
96 transactions: Transactions<N>,
97 aborted_transaction_ids: Vec<N::TransactionID>,
98 rng: &mut R,
99 ) -> Result<Self> {
100 let block_hash = N::hash_bhp1024(&to_bits_le![previous_hash, header.to_root()?])?;
102 let authority = Authority::new_beacon(private_key, block_hash, rng)?;
104 Self::from(
106 previous_hash,
107 header,
108 authority,
109 ratifications,
110 solutions,
111 aborted_solution_ids,
112 transactions,
113 aborted_transaction_ids,
114 )
115 }
116
117 pub fn new_quorum(
120 previous_hash: N::BlockHash,
121 header: Header<N>,
122 subdag: Subdag<N>,
123 ratifications: Ratifications<N>,
124 solutions: Solutions<N>,
125 aborted_solution_ids: Vec<SolutionID<N>>,
126 transactions: Transactions<N>,
127 aborted_transaction_ids: Vec<N::TransactionID>,
128 ) -> Result<Self> {
129 let authority = Authority::new_quorum(subdag);
131 Self::from(
133 previous_hash,
134 header,
135 authority,
136 ratifications,
137 solutions,
138 aborted_solution_ids,
139 transactions,
140 aborted_transaction_ids,
141 )
142 }
143
144 pub fn from(
147 previous_hash: N::BlockHash,
148 header: Header<N>,
149 authority: Authority<N>,
150 ratifications: Ratifications<N>,
151 solutions: Solutions<N>,
152 aborted_solution_ids: Vec<SolutionID<N>>,
153 transactions: Transactions<N>,
154 aborted_transaction_ids: Vec<N::TransactionID>,
155 ) -> Result<Self> {
156 if aborted_solution_ids.len() > Solutions::<N>::max_aborted_solutions()? {
158 bail!("Cannot initialize a block with {} aborted solutions IDs", aborted_solution_ids.len());
159 }
160
161 if transactions.len() > Transactions::<N>::MAX_TRANSACTIONS {
163 bail!(
164 "Cannot initialize a block with more than {} confirmed transactions",
165 Transactions::<N>::MAX_TRANSACTIONS
166 );
167 }
168
169 if aborted_transaction_ids.len() > Transactions::<N>::max_aborted_transactions()? {
171 bail!(
172 "Cannot initialize a block with more than {} aborted transaction IDs",
173 Transactions::<N>::max_aborted_transactions()?
174 );
175 }
176
177 let block_hash = N::hash_bhp1024(&to_bits_le![previous_hash, header.to_root()?])?;
179
180 match &authority {
182 Authority::Beacon(signature) => {
183 let address = signature.to_address();
185 ensure!(signature.verify(&address, &[block_hash]), "Invalid signature for block {}", header.height());
187 }
188 Authority::Quorum(subdag) => {
189 Self::check_subdag_transmissions(
191 subdag,
192 &solutions,
193 &aborted_solution_ids,
194 &transactions,
195 &aborted_transaction_ids,
196 )?;
197 }
198 }
199
200 if header.solutions_root() != solutions.to_solutions_root()? {
202 bail!("The solutions root in the block does not correspond to the solutions");
203 }
204
205 let subdag_root = match &authority {
207 Authority::Beacon(_) => Field::<N>::zero(),
208 Authority::Quorum(subdag) => subdag.to_subdag_root()?,
209 };
210 if header.subdag_root() != subdag_root {
211 bail!("The subdag root in the block does not correspond to the authority");
212 }
213
214 Self::from_unchecked(
216 block_hash.into(),
217 previous_hash,
218 header,
219 authority,
220 ratifications,
221 solutions,
222 aborted_solution_ids,
223 transactions,
224 aborted_transaction_ids,
225 )
226 }
227
228 pub fn from_unchecked(
231 block_hash: N::BlockHash,
232 previous_hash: N::BlockHash,
233 header: Header<N>,
234 authority: Authority<N>,
235 ratifications: Ratifications<N>,
236 solutions: Solutions<N>,
237 aborted_solution_ids: Vec<SolutionID<N>>,
238 transactions: Transactions<N>,
239 aborted_transaction_ids: Vec<N::TransactionID>,
240 ) -> Result<Self> {
241 Ok(Self {
243 block_hash,
244 previous_hash,
245 header,
246 authority,
247 ratifications,
248 solutions,
249 aborted_solution_ids,
250 transactions,
251 aborted_transaction_ids,
252 })
253 }
254}
255
256impl<N: Network> Block<N> {
257 pub const fn hash(&self) -> N::BlockHash {
259 self.block_hash
260 }
261
262 pub const fn previous_hash(&self) -> N::BlockHash {
264 self.previous_hash
265 }
266
267 pub const fn authority(&self) -> &Authority<N> {
269 &self.authority
270 }
271
272 pub const fn ratifications(&self) -> &Ratifications<N> {
274 &self.ratifications
275 }
276
277 pub const fn solutions(&self) -> &Solutions<N> {
279 &self.solutions
280 }
281
282 pub const fn aborted_solution_ids(&self) -> &Vec<SolutionID<N>> {
284 &self.aborted_solution_ids
285 }
286
287 pub const fn transactions(&self) -> &Transactions<N> {
289 &self.transactions
290 }
291
292 pub const fn aborted_transaction_ids(&self) -> &Vec<N::TransactionID> {
294 &self.aborted_transaction_ids
295 }
296}
297
298impl<N: Network> Block<N> {
299 pub const fn header(&self) -> &Header<N> {
301 &self.header
302 }
303
304 pub const fn previous_state_root(&self) -> N::StateRoot {
306 self.header.previous_state_root()
307 }
308
309 pub const fn transactions_root(&self) -> Field<N> {
311 self.header.transactions_root()
312 }
313
314 pub const fn finalize_root(&self) -> Field<N> {
316 self.header.finalize_root()
317 }
318
319 pub const fn ratifications_root(&self) -> Field<N> {
321 self.header.ratifications_root()
322 }
323
324 pub const fn solutions_root(&self) -> Field<N> {
326 self.header.solutions_root()
327 }
328
329 pub const fn metadata(&self) -> &Metadata<N> {
331 self.header.metadata()
332 }
333
334 pub const fn network(&self) -> u16 {
336 self.header.network()
337 }
338
339 pub const fn height(&self) -> u32 {
341 self.header.height()
342 }
343
344 pub const fn round(&self) -> u64 {
346 self.header.round()
347 }
348
349 pub const fn epoch_number(&self) -> u32 {
351 self.height() / N::NUM_BLOCKS_PER_EPOCH
352 }
353
354 pub const fn cumulative_weight(&self) -> u128 {
356 self.header.cumulative_weight()
357 }
358
359 pub const fn cumulative_proof_target(&self) -> u128 {
361 self.header.cumulative_proof_target()
362 }
363
364 pub const fn coinbase_target(&self) -> u64 {
366 self.header.coinbase_target()
367 }
368
369 pub const fn proof_target(&self) -> u64 {
371 self.header.proof_target()
372 }
373
374 pub const fn last_coinbase_target(&self) -> u64 {
376 self.header.last_coinbase_target()
377 }
378
379 pub const fn last_coinbase_timestamp(&self) -> i64 {
381 self.header.last_coinbase_timestamp()
382 }
383
384 pub const fn timestamp(&self) -> i64 {
386 self.header.timestamp()
387 }
388}
389
390impl<N: Network> Block<N> {
391 pub fn contains_transition(&self, transition_id: &N::TransitionID) -> bool {
393 self.transactions.contains_transition(transition_id)
394 }
395
396 pub fn contains_serial_number(&self, serial_number: &Field<N>) -> bool {
398 self.transactions.contains_serial_number(serial_number)
399 }
400
401 pub fn contains_commitment(&self, commitment: &Field<N>) -> bool {
403 self.transactions.contains_commitment(commitment)
404 }
405}
406
407impl<N: Network> Block<N> {
408 pub fn get_solution(&self, solution_id: &SolutionID<N>) -> Option<&Solution<N>> {
410 self.solutions.as_ref().and_then(|solution| solution.get_solution(solution_id))
411 }
412
413 pub fn get_transaction(&self, transaction_id: &N::TransactionID) -> Option<&Transaction<N>> {
415 self.transactions.get(transaction_id).map(|t| t.deref())
416 }
417
418 pub fn get_confirmed_transaction(&self, transaction_id: &N::TransactionID) -> Option<&ConfirmedTransaction<N>> {
420 self.transactions.get(transaction_id)
421 }
422}
423
424impl<N: Network> Block<N> {
425 pub fn find_transaction_for_transition_id(&self, transition_id: &N::TransitionID) -> Option<&Transaction<N>> {
427 self.transactions.find_transaction_for_transition_id(transition_id)
428 }
429
430 pub fn find_transaction_for_serial_number(&self, serial_number: &Field<N>) -> Option<&Transaction<N>> {
432 self.transactions.find_transaction_for_serial_number(serial_number)
433 }
434
435 pub fn find_transaction_for_commitment(&self, commitment: &Field<N>) -> Option<&Transaction<N>> {
437 self.transactions.find_transaction_for_commitment(commitment)
438 }
439
440 pub fn find_transition(&self, transition_id: &N::TransitionID) -> Option<&Transition<N>> {
442 self.transactions.find_transition(transition_id)
443 }
444
445 pub fn find_transition_for_serial_number(&self, serial_number: &Field<N>) -> Option<&Transition<N>> {
447 self.transactions.find_transition_for_serial_number(serial_number)
448 }
449
450 pub fn find_transition_for_commitment(&self, commitment: &Field<N>) -> Option<&Transition<N>> {
452 self.transactions.find_transition_for_commitment(commitment)
453 }
454
455 pub fn find_record(&self, commitment: &Field<N>) -> Option<&Record<N, Ciphertext<N>>> {
457 self.transactions.find_record(commitment)
458 }
459}
460
461impl<N: Network> Block<N> {
462 pub fn solution_ids(&self) -> Option<impl '_ + Iterator<Item = &SolutionID<N>>> {
464 self.solutions.as_ref().map(|solution| solution.solution_ids())
465 }
466
467 pub fn transaction_ids(&self) -> impl '_ + Iterator<Item = &N::TransactionID> {
469 self.transactions.transaction_ids()
470 }
471
472 pub fn deployments(&self) -> impl '_ + Iterator<Item = &ConfirmedTransaction<N>> {
474 self.transactions.deployments()
475 }
476
477 pub fn executions(&self) -> impl '_ + Iterator<Item = &ConfirmedTransaction<N>> {
479 self.transactions.executions()
480 }
481
482 pub fn transitions(&self) -> impl '_ + Iterator<Item = &Transition<N>> {
484 self.transactions.transitions()
485 }
486
487 pub fn transition_ids(&self) -> impl '_ + Iterator<Item = &N::TransitionID> {
489 self.transactions.transition_ids()
490 }
491
492 pub fn transition_public_keys(&self) -> impl '_ + Iterator<Item = &Group<N>> {
494 self.transactions.transition_public_keys()
495 }
496
497 pub fn transition_commitments(&self) -> impl '_ + Iterator<Item = &Field<N>> {
499 self.transactions.transition_commitments()
500 }
501
502 pub fn tags(&self) -> impl '_ + Iterator<Item = &Field<N>> {
504 self.transactions.tags()
505 }
506
507 pub fn input_ids(&self) -> impl '_ + Iterator<Item = &Field<N>> {
509 self.transactions.input_ids()
510 }
511
512 pub fn serial_numbers(&self) -> impl '_ + Iterator<Item = &Field<N>> {
514 self.transactions.serial_numbers()
515 }
516
517 pub fn output_ids(&self) -> impl '_ + Iterator<Item = &Field<N>> {
519 self.transactions.output_ids()
520 }
521
522 pub fn commitments(&self) -> impl '_ + Iterator<Item = &Field<N>> {
524 self.transactions.commitments()
525 }
526
527 pub fn records(&self) -> impl '_ + Iterator<Item = (&Field<N>, &Record<N, Ciphertext<N>>)> {
529 self.transactions.records()
530 }
531
532 pub fn nonces(&self) -> impl '_ + Iterator<Item = &Group<N>> {
534 self.transactions.nonces()
535 }
536
537 pub fn transaction_fee_amounts(&self) -> impl '_ + Iterator<Item = Result<U64<N>>> {
539 self.transactions.transaction_fee_amounts()
540 }
541}
542
543impl<N: Network> Block<N> {
544 pub fn into_transaction_ids(self) -> impl Iterator<Item = N::TransactionID> {
546 self.transactions.into_transaction_ids()
547 }
548
549 pub fn into_deployments(self) -> impl Iterator<Item = ConfirmedTransaction<N>> {
551 self.transactions.into_deployments()
552 }
553
554 pub fn into_executions(self) -> impl Iterator<Item = ConfirmedTransaction<N>> {
556 self.transactions.into_executions()
557 }
558
559 pub fn into_transitions(self) -> impl Iterator<Item = Transition<N>> {
561 self.transactions.into_transitions()
562 }
563
564 pub fn into_transition_ids(self) -> impl Iterator<Item = N::TransitionID> {
566 self.transactions.into_transition_ids()
567 }
568
569 pub fn into_transition_public_keys(self) -> impl Iterator<Item = Group<N>> {
571 self.transactions.into_transition_public_keys()
572 }
573
574 pub fn into_tags(self) -> impl Iterator<Item = Field<N>> {
576 self.transactions.into_tags()
577 }
578
579 pub fn into_serial_numbers(self) -> impl Iterator<Item = Field<N>> {
581 self.transactions.into_serial_numbers()
582 }
583
584 pub fn into_commitments(self) -> impl Iterator<Item = Field<N>> {
586 self.transactions.into_commitments()
587 }
588
589 pub fn into_records(self) -> impl Iterator<Item = (Field<N>, Record<N, Ciphertext<N>>)> {
591 self.transactions.into_records()
592 }
593
594 pub fn into_nonces(self) -> impl Iterator<Item = Group<N>> {
596 self.transactions.into_nonces()
597 }
598}
599
600#[cfg(test)]
601pub mod test_helpers {
602 use super::*;
603 use algorithms::snark::varuna::VarunaVersion;
604 use console::account::{Address, PrivateKey};
605 use ledger_query::Query;
606 use ledger_store::{BlockStore, helpers::memory::BlockMemory};
607 use synthesizer_process::Process;
608
609 use once_cell::sync::OnceCell;
610
611 type CurrentNetwork = console::network::MainnetV0;
612 type CurrentAleo = circuit::network::AleoV0;
613
614 pub(crate) fn sample_genesis_block(rng: &mut TestRng) -> Block<CurrentNetwork> {
616 let (block, _, _) = sample_genesis_block_and_components(rng);
618 block
620 }
621
622 pub(crate) fn sample_genesis_block_and_transaction(
624 rng: &mut TestRng,
625 ) -> (Block<CurrentNetwork>, Transaction<CurrentNetwork>) {
626 let (block, transaction, _) = sample_genesis_block_and_components(rng);
628 (block, transaction)
630 }
631
632 pub(crate) fn sample_genesis_block_and_components(
634 rng: &mut TestRng,
635 ) -> (Block<CurrentNetwork>, Transaction<CurrentNetwork>, PrivateKey<CurrentNetwork>) {
636 static INSTANCE: OnceCell<(Block<CurrentNetwork>, Transaction<CurrentNetwork>, PrivateKey<CurrentNetwork>)> =
637 OnceCell::new();
638 INSTANCE.get_or_init(|| sample_genesis_block_and_components_raw(rng)).clone()
639 }
640
641 fn sample_genesis_block_and_components_raw(
643 rng: &mut TestRng,
644 ) -> (Block<CurrentNetwork>, Transaction<CurrentNetwork>, PrivateKey<CurrentNetwork>) {
645 let private_key = PrivateKey::new(rng).unwrap();
647 let address = Address::<CurrentNetwork>::try_from(private_key).unwrap();
648
649 let locator = ("credits.aleo", "transfer_public_to_private");
651 let amount = 100_000_000u64;
653 let inputs = [address.to_string(), format!("{amount}_u64")];
655
656 let process = Process::load().unwrap();
658 let authorization =
660 process.authorize::<CurrentAleo, _>(&private_key, locator.0, locator.1, inputs.iter(), rng).unwrap();
661 let (_, mut trace) = process.execute::<CurrentAleo, _>(authorization, rng).unwrap();
663
664 let block_store = BlockStore::<CurrentNetwork, BlockMemory<_>>::open(None).unwrap();
666
667 trace.prepare(Query::from(block_store)).unwrap();
669 let execution = trace.prove_execution::<CurrentAleo, _>(locator.0, VarunaVersion::V1, rng).unwrap();
671 let execution = Execution::from_str(&execution.to_string()).unwrap();
674
675 let transaction = Transaction::from_execution(execution, None).unwrap();
677 let confirmed = ConfirmedTransaction::accepted_execute(0, transaction.clone(), vec![]).unwrap();
679 let transactions = Transactions::from_iter([confirmed]);
681
682 let ratifications = Ratifications::try_from(vec![]).unwrap();
684
685 let header = Header::genesis(&ratifications, &transactions, vec![]).unwrap();
687 let previous_hash = <CurrentNetwork as Network>::BlockHash::default();
689
690 let block = Block::new_beacon(
692 &private_key,
693 previous_hash,
694 header,
695 ratifications,
696 None.into(),
697 vec![],
698 transactions,
699 vec![],
700 rng,
701 )
702 .unwrap();
703 assert!(block.header().is_genesis(), "Failed to initialize a genesis block");
704 (block, transaction, private_key)
706 }
707
708 pub(crate) fn sample_metadata() -> Metadata<CurrentNetwork> {
709 let network = CurrentNetwork::ID;
710 let round = u64::MAX;
711 let height = u32::MAX;
712 let cumulative_weight = u128::MAX - 1;
713 let cumulative_proof_target = u128::MAX - 1;
714 let coinbase_target = u64::MAX;
715 let proof_target = u64::MAX - 1;
716 let last_coinbase_target = u64::MAX;
717 let timestamp = i64::MAX - 1;
718 let last_coinbase_timestamp = timestamp - 1;
719 Metadata::new(
720 network,
721 round,
722 height,
723 cumulative_weight,
724 cumulative_proof_target,
725 coinbase_target,
726 proof_target,
727 last_coinbase_target,
728 last_coinbase_timestamp,
729 timestamp,
730 )
731 .unwrap()
732 }
733}
734
735#[cfg(test)]
736mod tests {
737 use super::*;
738
739 use console::network::MainnetV0;
740 use indexmap::IndexMap;
741 type CurrentNetwork = MainnetV0;
742
743 #[test]
744 fn test_find_transaction_for_transition_id() {
745 let rng = &mut TestRng::default();
746
747 let (block, transaction) = crate::test_helpers::sample_genesis_block_and_transaction(rng);
748 let transactions = block.transactions();
749
750 let transitions = transaction.transitions().collect::<Vec<_>>();
752 assert!(!transitions.is_empty());
753
754 for transition in transitions {
756 assert_eq!(block.find_transaction_for_transition_id(transition.id()), Some(&transaction));
757 assert_eq!(transactions.find_transaction_for_transition_id(transition.id()), Some(&transaction));
758 }
759
760 for _ in 0..10 {
762 let transition_id = &rng.gen();
763 assert_eq!(block.find_transaction_for_transition_id(transition_id), None);
764 assert_eq!(transactions.find_transaction_for_transition_id(transition_id), None);
765 }
766 }
767
768 #[test]
769 fn test_find_transaction_for_commitment() {
770 let rng = &mut TestRng::default();
771
772 let (block, transaction) = crate::test_helpers::sample_genesis_block_and_transaction(rng);
773 let transactions = block.transactions();
774
775 let commitments = transaction.commitments().collect::<Vec<_>>();
777 assert!(!commitments.is_empty());
778
779 for commitment in commitments {
781 assert_eq!(block.find_transaction_for_commitment(commitment), Some(&transaction));
782 assert_eq!(transactions.find_transaction_for_commitment(commitment), Some(&transaction));
783 }
784
785 for _ in 0..10 {
787 let commitment = &rng.gen();
788 assert_eq!(block.find_transaction_for_commitment(commitment), None);
789 assert_eq!(transactions.find_transaction_for_commitment(commitment), None);
790 }
791 }
792
793 #[test]
794 fn test_find_transition() {
795 let rng = &mut TestRng::default();
796
797 let (block, transaction) = crate::test_helpers::sample_genesis_block_and_transaction(rng);
798 let transactions = block.transactions();
799
800 let transitions = transaction.transitions().collect::<Vec<_>>();
802 assert!(!transitions.is_empty());
803
804 for transition in transitions {
806 assert_eq!(block.find_transition(transition.id()), Some(transition));
807 assert_eq!(transactions.find_transition(transition.id()), Some(transition));
808 assert_eq!(transaction.find_transition(transition.id()), Some(transition));
809 }
810
811 for _ in 0..10 {
813 let transition_id = &rng.gen();
814 assert_eq!(block.find_transition(transition_id), None);
815 assert_eq!(transactions.find_transition(transition_id), None);
816 assert_eq!(transaction.find_transition(transition_id), None);
817 }
818 }
819
820 #[test]
821 fn test_find_transition_for_commitment() {
822 let rng = &mut TestRng::default();
823
824 let (block, transaction) = crate::test_helpers::sample_genesis_block_and_transaction(rng);
825 let transactions = block.transactions();
826
827 let transitions = transaction.transitions().collect::<Vec<_>>();
829 assert!(!transitions.is_empty());
830
831 for transition in transitions {
832 let commitments = transition.commitments().collect::<Vec<_>>();
834 assert!(!commitments.is_empty());
835
836 for commitment in commitments {
838 assert_eq!(block.find_transition_for_commitment(commitment), Some(transition));
839 assert_eq!(transactions.find_transition_for_commitment(commitment), Some(transition));
840 assert_eq!(transaction.find_transition_for_commitment(commitment), Some(transition));
841 }
842 }
843
844 for _ in 0..10 {
846 let commitment = &rng.gen();
847 assert_eq!(block.find_transition_for_commitment(commitment), None);
848 assert_eq!(transactions.find_transition_for_commitment(commitment), None);
849 assert_eq!(transaction.find_transition_for_commitment(commitment), None);
850 }
851 }
852
853 #[test]
854 fn test_find_record() {
855 let rng = &mut TestRng::default();
856
857 let (block, transaction) = crate::test_helpers::sample_genesis_block_and_transaction(rng);
858 let transactions = block.transactions();
859
860 let records = transaction.records().collect::<IndexMap<_, _>>();
862 assert!(!records.is_empty());
863
864 for (commitment, record) in records {
866 assert_eq!(block.find_record(commitment), Some(record));
867 assert_eq!(transactions.find_record(commitment), Some(record));
868 assert_eq!(transaction.find_record(commitment), Some(record));
869 }
870
871 for _ in 0..10 {
873 let commitment = &rng.gen();
874 assert_eq!(block.find_record(commitment), None);
875 assert_eq!(transactions.find_record(commitment), None);
876 assert_eq!(transaction.find_record(commitment), None);
877 }
878 }
879
880 #[test]
881 fn test_serde_metadata() {
882 let metadata = crate::test_helpers::sample_metadata();
883 let json_metadata = serde_json::to_string(&metadata).unwrap();
884 let deserialized_metadata: Metadata<CurrentNetwork> = serde_json::from_str(&json_metadata).unwrap();
885 assert_eq!(metadata, deserialized_metadata);
886 }
887}