1use std::{
6 collections::hash_map::DefaultHasher,
7 fmt::{Debug, Formatter},
8 hash::{Hash, Hasher},
9 sync::Arc,
10 time::{SystemTime, UNIX_EPOCH},
11};
12
13use byteorder::{BigEndian, ReadBytesExt};
14use fastcrypto::{error::FastCryptoResult, groups::bls12381, hash::HashFunction};
15use fastcrypto_tbls::dkg_v1;
16use iota_sdk_types::{
17 DenyRuleSet, Digest, MisbehaviorReportDigest, ObjectReference, SenderSignedTransaction,
18 TransactionDigest, crypto::IntentScope,
19};
20use once_cell::sync::OnceCell;
21use serde::{Deserialize, Serialize};
22use tracing::warn;
23
24use crate::{
25 base_types::{AuthorityName, ConciseableName},
26 crypto::{AuthoritySignature, DefaultHash, default_hash},
27 message_envelope::{Envelope, Message, VerifiedEnvelope},
28 messages_checkpoint::{CheckpointSequenceNumber, CheckpointSignatureMessage},
29 supported_protocol_versions::{
30 Chain, SupportedProtocolVersions, SupportedProtocolVersionsWithHashes,
31 },
32 transaction::{CertifiedTransaction, TransactionEnvelope},
33};
34
35#[derive(Serialize, Deserialize, Clone, Debug)]
36pub struct ConsensusTransaction {
37 pub tracking_id: [u8; 8],
41 pub kind: ConsensusTransactionKind,
42}
43
44#[derive(Serialize, Deserialize, Clone, Hash, PartialEq, Eq, Ord, PartialOrd)]
45pub enum ConsensusTransactionKey {
46 Certificate(TransactionDigest),
47 CheckpointSignature(AuthorityName, CheckpointSequenceNumber),
48 EndOfPublish(AuthorityName),
49 CapabilityNotification(AuthorityName, u64 ),
50 #[deprecated(note = "Authenticator state (JWK) is deprecated and was never enabled on IOTA")]
51 NewJWKFetchedDeprecated,
52 RandomnessDkgMessage(AuthorityName),
53 RandomnessDkgConfirmation(AuthorityName),
54 MisbehaviorReport(
55 AuthorityName,
56 MisbehaviorReportDigest,
57 CheckpointSequenceNumber,
58 ),
59 UserTransaction(TransactionDigest),
61 OverloadNotificationV1(AuthorityName, u64 ),
62 TransactionDenyRuleProposal(AuthorityName, u64 ),
63 }
66
67impl Debug for ConsensusTransactionKey {
68 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
69 match self {
70 Self::Certificate(digest) => write!(f, "Certificate({digest})"),
71 Self::CheckpointSignature(name, seq) => {
72 write!(f, "CheckpointSignature({:?}, {:?})", name.concise(), seq)
73 }
74 Self::EndOfPublish(name) => write!(f, "EndOfPublish({:?})", name.concise()),
75 Self::CapabilityNotification(name, generation) => write!(
76 f,
77 "CapabilityNotification({:?}, {:?})",
78 name.concise(),
79 generation
80 ),
81 #[allow(deprecated)]
82 Self::NewJWKFetchedDeprecated => {
83 write!(
84 f,
85 "NewJWKFetched(deprecated: Authenticator state (JWK) is deprecated and was never enabled on IOTA)"
86 )
87 }
88 Self::RandomnessDkgMessage(name) => {
89 write!(f, "RandomnessDkgMessage({:?})", name.concise())
90 }
91 Self::RandomnessDkgConfirmation(name) => {
92 write!(f, "RandomnessDkgConfirmation({:?})", name.concise())
93 }
94 Self::MisbehaviorReport(name, digest, checkpoint_seq) => {
95 write!(
96 f,
97 "MisbehaviorReport({:?}, {:?}, {:?})",
98 name.concise(),
99 digest,
100 checkpoint_seq
101 )
102 }
103 Self::UserTransaction(digest) => write!(f, "UserTransaction({digest:?})"),
104 Self::OverloadNotificationV1(name, generation) => {
105 write!(
106 f,
107 "OverloadNotificationV1({:?}, gen={generation:?})",
108 name.concise()
109 )
110 }
111 Self::TransactionDenyRuleProposal(name, generation) => {
112 write!(
113 f,
114 "TransactionDenyRuleProposal({:?}, gen={generation:?})",
115 name.concise()
116 )
117 }
118 }
119 }
120}
121
122pub type SignedAuthorityCapabilitiesV1 = Envelope<AuthorityCapabilitiesV1, AuthoritySignature>;
123
124pub type VerifiedAuthorityCapabilitiesV1 =
125 VerifiedEnvelope<AuthorityCapabilitiesV1, AuthoritySignature>;
126
127#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
128pub struct AuthorityCapabilitiesDigest(Digest);
129
130impl AuthorityCapabilitiesDigest {
131 pub const fn new(digest: [u8; 32]) -> Self {
132 Self(Digest::new(digest))
133 }
134}
135
136impl Debug for AuthorityCapabilitiesDigest {
137 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
138 f.debug_tuple("AuthorityCapabilitiesDigest")
139 .field(&self.0)
140 .finish()
141 }
142}
143
144#[derive(Serialize, Deserialize, Clone, Hash)]
147pub struct AuthorityCapabilitiesV1 {
148 pub authority: AuthorityName,
151 pub generation: u64,
158
159 pub supported_protocol_versions: SupportedProtocolVersionsWithHashes,
162
163 pub available_system_packages: Vec<ObjectReference>,
167}
168
169impl Message for AuthorityCapabilitiesV1 {
170 type DigestType = AuthorityCapabilitiesDigest;
171 const SCOPE: IntentScope = IntentScope::AuthorityCapabilities;
172
173 fn digest(&self) -> Self::DigestType {
174 let mut hasher = DefaultHash::new();
176 let serialized = bcs::to_bytes(&self).expect("BCS should not fail");
177 hasher.update(&serialized);
178 AuthorityCapabilitiesDigest::new(<[u8; 32]>::from(hasher.finalize()))
179 }
180}
181
182impl Debug for AuthorityCapabilitiesV1 {
183 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
184 f.debug_struct("AuthorityCapabilities")
185 .field("authority", &self.authority.concise())
186 .field("generation", &self.generation)
187 .field(
188 "supported_protocol_versions",
189 &self.supported_protocol_versions,
190 )
191 .field("available_system_packages", &self.available_system_packages)
192 .finish()
193 }
194}
195
196impl AuthorityCapabilitiesV1 {
197 pub fn new(
198 authority: AuthorityName,
199 chain: Chain,
200 supported_protocol_versions: SupportedProtocolVersions,
201 available_system_packages: Vec<ObjectReference>,
202 ) -> Self {
203 let generation = SystemTime::now()
204 .duration_since(UNIX_EPOCH)
205 .expect("IOTA did not exist prior to 1970")
206 .as_millis()
207 .try_into()
208 .expect("This build of iota is not supported in the year 500,000,000");
209 Self {
210 authority,
211 generation,
212 supported_protocol_versions:
213 SupportedProtocolVersionsWithHashes::from_supported_versions(
214 supported_protocol_versions,
215 chain,
216 ),
217 available_system_packages,
218 }
219 }
220}
221
222impl SignedAuthorityCapabilitiesV1 {
223 pub fn cache_digest(&self, epoch: u64) -> AuthorityCapabilitiesDigest {
224 let data_with_epoch = (self.data(), epoch);
226
227 let mut hasher = DefaultHash::new();
229 let serialized = bcs::to_bytes(&data_with_epoch).expect("BCS should not fail");
230 hasher.update(&serialized);
231 AuthorityCapabilitiesDigest::new(<[u8; 32]>::from(hasher.finalize()))
232 }
233}
234
235#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
242pub struct TransactionDenyRuleProposal {
243 pub authority: AuthorityName,
245 pub generation: u64,
248 pub proposed_rules: DenyRuleSet,
250}
251
252impl TransactionDenyRuleProposal {
253 pub fn new(
258 authority: AuthorityName,
259 proposed_rules: DenyRuleSet,
260 last_generation: Option<u64>,
261 ) -> Self {
262 let now: u64 = SystemTime::now()
263 .duration_since(UNIX_EPOCH)
264 .expect("IOTA did not exist prior to 1970")
265 .as_millis()
266 .try_into()
267 .expect("This build of iota is not supported in the year 500,000,000");
268 Self {
269 authority,
270 generation: now.max(last_generation.map_or(0, |g| g.saturating_add(1))),
271 proposed_rules,
272 }
273 }
274}
275
276#[derive(Serialize, Deserialize, Clone, Debug)]
277pub enum ConsensusTransactionKind {
278 CertifiedTransaction(Box<CertifiedTransaction>),
279 CheckpointSignature(Box<CheckpointSignatureMessage>),
280 EndOfPublish(AuthorityName),
281
282 CapabilityNotificationV1(AuthorityCapabilitiesV1),
283 SignedCapabilityNotificationV1(SignedAuthorityCapabilitiesV1),
284
285 #[deprecated(note = "Authenticator state (JWK) is deprecated and was never enabled on IOTA")]
286 NewJWKFetchedDeprecated,
287
288 RandomnessDkgMessage(AuthorityName, Vec<u8>),
292 RandomnessDkgConfirmation(AuthorityName, Vec<u8>),
296 MisbehaviorReport(VersionedMisbehaviorReport),
297 UserTransactionV1(Box<TransactionEnvelope>),
301 OverloadNotificationV1(
302 AuthorityName,
303 u64, u8, ),
306 TransactionDenyRuleProposal(TransactionDenyRuleProposal),
310 }
313
314impl ConsensusTransactionKind {
315 fn map_cert_or_raw_user_tx<'a, R>(
322 &'a self,
323 certified: impl FnOnce(&'a CertifiedTransaction) -> R,
324 raw: impl FnOnce(&'a TransactionEnvelope) -> R,
325 ) -> Option<R> {
326 match self {
327 Self::CertifiedTransaction(c) => Some(certified(c)),
328 Self::UserTransactionV1(t) => Some(raw(t)),
329 Self::CheckpointSignature(_)
330 | Self::EndOfPublish(_)
331 | Self::CapabilityNotificationV1(_)
332 | Self::SignedCapabilityNotificationV1(_)
333 | Self::RandomnessDkgMessage(..)
334 | Self::RandomnessDkgConfirmation(..)
335 | Self::MisbehaviorReport(_)
336 | Self::OverloadNotificationV1(..)
337 | Self::TransactionDenyRuleProposal(_) => None,
338 #[allow(deprecated)]
339 Self::NewJWKFetchedDeprecated => None,
340 }
341 }
342
343 pub fn as_sender_signed_transaction(&self) -> Option<&SenderSignedTransaction> {
346 self.map_cert_or_raw_user_tx(|c| c.data(), |t| t.data())
347 }
348
349 pub fn transaction_digest(&self) -> Option<TransactionDigest> {
352 self.map_cert_or_raw_user_tx(|c| *c.digest(), |t| *t.digest())
353 }
354
355 pub fn as_user_transaction(&self) -> Option<&TransactionEnvelope> {
359 match self {
360 Self::UserTransactionV1(tx) => Some(tx),
361 Self::CertifiedTransaction(_)
362 | Self::CheckpointSignature(_)
363 | Self::EndOfPublish(_)
364 | Self::CapabilityNotificationV1(_)
365 | Self::SignedCapabilityNotificationV1(_)
366 | Self::RandomnessDkgMessage(..)
367 | Self::RandomnessDkgConfirmation(..)
368 | Self::MisbehaviorReport(_)
369 | Self::OverloadNotificationV1(..)
370 | Self::TransactionDenyRuleProposal(_) => None,
371 #[allow(deprecated)]
372 Self::NewJWKFetchedDeprecated => None,
373 }
374 }
375
376 pub fn is_user_transaction(&self) -> bool {
380 self.as_user_transaction().is_some()
381 }
382
383 pub fn is_dkg(&self) -> bool {
386 match self {
387 Self::RandomnessDkgMessage(_, _) | Self::RandomnessDkgConfirmation(_, _) => true,
388 Self::UserTransactionV1(_)
389 | Self::CertifiedTransaction(_)
390 | Self::CheckpointSignature(_)
391 | Self::EndOfPublish(_)
392 | Self::CapabilityNotificationV1(_)
393 | Self::SignedCapabilityNotificationV1(_)
394 | Self::MisbehaviorReport(_)
395 | Self::OverloadNotificationV1(..)
396 | Self::TransactionDenyRuleProposal(_) => false,
397 #[allow(deprecated)]
398 Self::NewJWKFetchedDeprecated => false,
399 }
400 }
401}
402
403#[derive(Debug, Clone, Serialize, Deserialize)]
413pub struct VersionedMisbehaviorReport {
414 pub authority: AuthorityName,
417 pub payload: MisbehaviorObservations,
419 pub generation: u64,
423 #[serde(skip)]
424 digest: OnceCell<MisbehaviorReportDigest>,
425}
426
427#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
433pub enum MisbehaviorObservations {
434 V1(MisbehaviorObservationsV1),
435 V2(MisbehaviorObservationsV2),
436}
437
438impl MisbehaviorObservations {
439 pub fn verify(&self, committee_size: usize) -> bool {
441 match self {
442 Self::V1(payload) => payload.verify(committee_size),
443 Self::V2(payload) => payload.verify(committee_size),
444 }
445 }
446}
447
448impl VersionedMisbehaviorReport {
449 pub fn new_v1(
450 authority: AuthorityName,
451 generation: u64,
452 observations: MisbehaviorObservationsV1,
453 ) -> Self {
454 Self {
455 authority,
456 payload: MisbehaviorObservations::V1(observations),
457 generation,
458 digest: OnceCell::new(),
459 }
460 }
461
462 pub fn new_v2(
463 authority: AuthorityName,
464 generation: u64,
465 observations: MisbehaviorObservationsV2,
466 ) -> Self {
467 Self {
468 authority,
469 payload: MisbehaviorObservations::V2(observations),
470 generation,
471 digest: OnceCell::new(),
472 }
473 }
474
475 pub fn digest(&self) -> &MisbehaviorReportDigest {
478 self.digest
479 .get_or_init(|| MisbehaviorReportDigest::new(default_hash(self)))
480 }
481
482 pub fn summary(&self) -> u64 {
485 let summary = match &self.payload {
486 MisbehaviorObservations::V1(report) => [
487 &report.faulty_blocks_provable,
488 &report.faulty_blocks_unprovable,
489 &report.missing_proposals,
490 &report.equivocations,
491 ]
492 .into_iter()
493 .flatten()
494 .fold(0u64, |acc, metric| acc.saturating_add(*metric)),
495 MisbehaviorObservations::V2(report) => [
496 &report.faulty_blocks_provable,
497 &report.faulty_blocks_unprovable,
498 &report.missing_proposals,
499 &report.equivocations,
500 &report.invalid_bundle_parts,
501 ]
502 .into_iter()
503 .flatten()
504 .fold(0u64, |acc, metric| acc.saturating_add(*metric)),
505 };
506 if summary == u64::MAX {
507 warn!("MisbehaviorReport summary reached its maximum value.");
508 }
509 summary
510 }
511}
512
513#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
519pub struct MisbehaviorObservationsV1 {
520 pub faulty_blocks_provable: Vec<u64>,
521 pub faulty_blocks_unprovable: Vec<u64>,
522 pub missing_proposals: Vec<u64>,
523 pub equivocations: Vec<u64>,
524}
525
526impl MisbehaviorObservationsV1 {
527 pub fn verify(&self, committee_size: usize) -> bool {
528 if (self.faulty_blocks_provable.len() != committee_size)
536 || (self.faulty_blocks_unprovable.len() != committee_size)
537 || (self.equivocations.len() != committee_size)
538 || (self.missing_proposals.len() != committee_size)
539 {
540 return false;
541 }
542 true
543 }
544}
545
546#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
551pub struct MisbehaviorObservationsV2 {
552 pub faulty_blocks_provable: Vec<u64>,
553 pub faulty_blocks_unprovable: Vec<u64>,
554 pub missing_proposals: Vec<u64>,
555 pub equivocations: Vec<u64>,
556 pub invalid_bundle_parts: Vec<u64>,
557}
558
559impl MisbehaviorObservationsV2 {
560 pub fn verify(&self, committee_size: usize) -> bool {
561 self.faulty_blocks_provable.len() == committee_size
562 && self.faulty_blocks_unprovable.len() == committee_size
563 && self.missing_proposals.len() == committee_size
564 && self.equivocations.len() == committee_size
565 && self.invalid_bundle_parts.len() == committee_size
566 }
567}
568
569#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
570pub enum VersionedDkgMessage {
571 V1(dkg_v1::Message<bls12381::G2Element, bls12381::G2Element>),
572}
573
574#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
575pub enum VersionedDkgConfirmation {
576 V1(dkg_v1::Confirmation<bls12381::G2Element>),
577}
578
579impl Debug for VersionedDkgMessage {
580 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
581 match self {
582 VersionedDkgMessage::V1(msg) => write!(
583 f,
584 "DKG V1 Message with sender={}, vss_pk.degree={}, encrypted_shares.len()={}",
585 msg.sender,
586 msg.vss_pk.degree(),
587 msg.encrypted_shares.len(),
588 ),
589 }
590 }
591}
592
593impl VersionedDkgMessage {
594 pub fn sender(&self) -> u16 {
595 match self {
596 VersionedDkgMessage::V1(msg) => msg.sender,
597 }
598 }
599
600 pub fn create(
601 dkg_version: u64,
602 party: Arc<dkg_v1::Party<bls12381::G2Element, bls12381::G2Element>>,
603 ) -> FastCryptoResult<VersionedDkgMessage> {
604 assert_eq!(dkg_version, 1, "BUG: invalid DKG version");
605 let msg = party.create_message(&mut rand::thread_rng())?;
606 Ok(VersionedDkgMessage::V1(msg))
607 }
608
609 pub fn unwrap_v1(self) -> dkg_v1::Message<bls12381::G2Element, bls12381::G2Element> {
610 match self {
611 VersionedDkgMessage::V1(msg) => msg,
612 }
613 }
614
615 pub fn is_valid_version(&self, dkg_version: u64) -> bool {
616 matches!((self, dkg_version), (VersionedDkgMessage::V1(_), 1))
617 }
618}
619
620impl VersionedDkgConfirmation {
621 pub fn sender(&self) -> u16 {
622 match self {
623 VersionedDkgConfirmation::V1(msg) => msg.sender,
624 }
625 }
626
627 pub fn num_of_complaints(&self) -> usize {
628 match self {
629 VersionedDkgConfirmation::V1(msg) => msg.complaints.len(),
630 }
631 }
632
633 pub fn unwrap_v1(&self) -> &dkg_v1::Confirmation<bls12381::G2Element> {
634 match self {
635 VersionedDkgConfirmation::V1(msg) => msg,
636 }
637 }
638
639 pub fn is_valid_version(&self, dkg_version: u64) -> bool {
640 matches!((self, dkg_version), (VersionedDkgConfirmation::V1(_), 1))
641 }
642}
643
644impl ConsensusTransaction {
645 pub fn new_certificate_message(
646 authority: &AuthorityName,
647 certificate: CertifiedTransaction,
648 ) -> Self {
649 let mut hasher = DefaultHasher::new();
650 let tx_digest = certificate.digest();
651 tx_digest.hash(&mut hasher);
652 authority.hash(&mut hasher);
653 let tracking_id = hasher.finish().to_le_bytes();
654 Self {
655 tracking_id,
656 kind: ConsensusTransactionKind::CertifiedTransaction(Box::new(certificate)),
657 }
658 }
659
660 pub fn new_checkpoint_signature_message(data: CheckpointSignatureMessage) -> Self {
661 let mut hasher = DefaultHasher::new();
662 data.summary.auth_sig().signature.hash(&mut hasher);
663 let tracking_id = hasher.finish().to_le_bytes();
664 Self {
665 tracking_id,
666 kind: ConsensusTransactionKind::CheckpointSignature(Box::new(data)),
667 }
668 }
669
670 pub fn new_end_of_publish(authority: AuthorityName) -> Self {
671 let mut hasher = DefaultHasher::new();
672 authority.hash(&mut hasher);
673 let tracking_id = hasher.finish().to_le_bytes();
674 Self {
675 tracking_id,
676 kind: ConsensusTransactionKind::EndOfPublish(authority),
677 }
678 }
679
680 pub fn new_capability_notification_v1(capabilities: AuthorityCapabilitiesV1) -> Self {
681 let mut hasher = DefaultHasher::new();
682 capabilities.hash(&mut hasher);
683 let tracking_id = hasher.finish().to_le_bytes();
684 Self {
685 tracking_id,
686 kind: ConsensusTransactionKind::CapabilityNotificationV1(capabilities),
687 }
688 }
689
690 pub fn new_signed_capability_notification_v1(
691 signed_capabilities: SignedAuthorityCapabilitiesV1,
692 ) -> Self {
693 let mut hasher = DefaultHasher::new();
694 signed_capabilities.data().hash(&mut hasher);
695 signed_capabilities.auth_sig().hash(&mut hasher);
696 let tracking_id = hasher.finish().to_le_bytes();
697 Self {
698 tracking_id,
699 kind: ConsensusTransactionKind::SignedCapabilityNotificationV1(signed_capabilities),
700 }
701 }
702
703 pub fn new_randomness_dkg_message(
704 authority: AuthorityName,
705 versioned_message: &VersionedDkgMessage,
706 ) -> Self {
707 let message =
708 bcs::to_bytes(versioned_message).expect("message serialization should not fail");
709 let mut hasher = DefaultHasher::new();
710 message.hash(&mut hasher);
711 let tracking_id = hasher.finish().to_le_bytes();
712 Self {
713 tracking_id,
714 kind: ConsensusTransactionKind::RandomnessDkgMessage(authority, message),
715 }
716 }
717 pub fn new_randomness_dkg_confirmation(
718 authority: AuthorityName,
719 versioned_confirmation: &VersionedDkgConfirmation,
720 ) -> Self {
721 let confirmation =
722 bcs::to_bytes(versioned_confirmation).expect("message serialization should not fail");
723 let mut hasher = DefaultHasher::new();
724 confirmation.hash(&mut hasher);
725 let tracking_id = hasher.finish().to_le_bytes();
726 Self {
727 tracking_id,
728 kind: ConsensusTransactionKind::RandomnessDkgConfirmation(authority, confirmation),
729 }
730 }
731
732 pub fn new_misbehavior_report(report: VersionedMisbehaviorReport) -> Self {
733 let serialized_report =
734 bcs::to_bytes(&report).expect("report serialization should not fail");
735 let mut hasher = DefaultHasher::new();
736 serialized_report.hash(&mut hasher);
737 let tracking_id = hasher.finish().to_le_bytes();
738 Self {
739 tracking_id,
740 kind: ConsensusTransactionKind::MisbehaviorReport(report),
741 }
742 }
743
744 pub fn new_user_transaction(transaction: TransactionEnvelope) -> Self {
745 let mut hasher = DefaultHasher::new();
746 let tx_digest = transaction.digest();
747 tx_digest.hash(&mut hasher);
748 let tracking_id = hasher.finish().to_le_bytes();
749 Self {
750 tracking_id,
751 kind: ConsensusTransactionKind::UserTransactionV1(Box::new(transaction)),
752 }
753 }
754
755 pub fn new_overload_notification_v1(
756 authority: AuthorityName,
757 load_shedding_percentage: u8,
758 ) -> Self {
759 let generation: u64 = SystemTime::now()
766 .duration_since(UNIX_EPOCH)
767 .expect("IOTA did not exist prior to 1970")
768 .as_millis()
769 .try_into()
770 .expect("This build of iota is not supported in the year 500,000,000");
771 let mut hasher = DefaultHasher::new();
772 authority.hash(&mut hasher);
773 generation.hash(&mut hasher);
774 load_shedding_percentage.hash(&mut hasher);
775 let tracking_id = hasher.finish().to_le_bytes();
776 Self {
777 tracking_id,
778 kind: ConsensusTransactionKind::OverloadNotificationV1(
779 authority,
780 generation,
781 load_shedding_percentage,
782 ),
783 }
784 }
785
786 pub fn new_transaction_deny_rule_proposal(proposal: TransactionDenyRuleProposal) -> Self {
787 let mut hasher = DefaultHasher::new();
788 proposal.hash(&mut hasher);
789 let tracking_id = hasher.finish().to_le_bytes();
790 Self {
791 tracking_id,
792 kind: ConsensusTransactionKind::TransactionDenyRuleProposal(proposal),
793 }
794 }
795
796 pub fn get_tracking_id(&self) -> u64 {
797 (&self.tracking_id[..])
798 .read_u64::<BigEndian>()
799 .unwrap_or_default()
800 }
801
802 pub fn key(&self) -> ConsensusTransactionKey {
803 match &self.kind {
804 ConsensusTransactionKind::CertifiedTransaction(cert) => {
805 ConsensusTransactionKey::Certificate(*cert.digest())
806 }
807 ConsensusTransactionKind::CheckpointSignature(data) => {
808 ConsensusTransactionKey::CheckpointSignature(
809 data.summary.auth_sig().authority,
810 data.summary.sequence_number,
811 )
812 }
813 ConsensusTransactionKind::EndOfPublish(authority) => {
814 ConsensusTransactionKey::EndOfPublish(*authority)
815 }
816 ConsensusTransactionKind::CapabilityNotificationV1(cap) => {
817 ConsensusTransactionKey::CapabilityNotification(cap.authority, cap.generation)
818 }
819 ConsensusTransactionKind::SignedCapabilityNotificationV1(signed_cap) => {
820 ConsensusTransactionKey::CapabilityNotification(
821 signed_cap.authority,
822 signed_cap.generation,
823 )
824 }
825
826 #[allow(deprecated)]
827 ConsensusTransactionKind::NewJWKFetchedDeprecated => {
828 ConsensusTransactionKey::NewJWKFetchedDeprecated
829 }
830 ConsensusTransactionKind::RandomnessDkgMessage(authority, _) => {
831 ConsensusTransactionKey::RandomnessDkgMessage(*authority)
832 }
833 ConsensusTransactionKind::RandomnessDkgConfirmation(authority, _) => {
834 ConsensusTransactionKey::RandomnessDkgConfirmation(*authority)
835 }
836 ConsensusTransactionKind::MisbehaviorReport(report) => {
837 ConsensusTransactionKey::MisbehaviorReport(
838 report.authority,
839 *report.digest(),
840 report.generation,
841 )
842 }
843 ConsensusTransactionKind::UserTransactionV1(tx) => {
844 ConsensusTransactionKey::UserTransaction(*tx.digest())
845 }
846 ConsensusTransactionKind::OverloadNotificationV1(authority, generation, _) => {
847 ConsensusTransactionKey::OverloadNotificationV1(*authority, *generation)
848 }
849 ConsensusTransactionKind::TransactionDenyRuleProposal(proposal) => {
850 ConsensusTransactionKey::TransactionDenyRuleProposal(
851 proposal.authority,
852 proposal.generation,
853 )
854 }
855 }
856 }
857
858 pub fn is_user_certificate(&self) -> bool {
859 matches!(self.kind, ConsensusTransactionKind::CertifiedTransaction(_))
860 }
861
862 pub fn is_end_of_publish(&self) -> bool {
863 matches!(self.kind, ConsensusTransactionKind::EndOfPublish(_))
864 }
865}
866
867#[cfg(test)]
868mod tests {
869 use super::*;
870
871 #[derive(Serialize)]
875 struct LegacyVersionedMisbehaviorReport<'a> {
876 payload: &'a MisbehaviorObservations,
877 }
878
879 fn sample_payload() -> MisbehaviorObservations {
880 MisbehaviorObservations::V1(MisbehaviorObservationsV1 {
881 faulty_blocks_provable: vec![1, 2, 3],
882 faulty_blocks_unprovable: vec![4, 5, 6],
883 missing_proposals: vec![7, 8, 9],
884 equivocations: vec![10, 11, 12],
885 })
886 }
887
888 #[test]
896 fn misbehavior_report_wire_format_unchanged() {
897 let authority = AuthorityName::default();
898 let generation: u64 = 42;
899 let payload = sample_payload();
900
901 let legacy_bytes = bcs::to_bytes(&(
902 authority,
903 LegacyVersionedMisbehaviorReport { payload: &payload },
904 generation,
905 ))
906 .unwrap();
907
908 let new = VersionedMisbehaviorReport {
909 authority,
910 payload,
911 generation,
912 digest: OnceCell::new(),
913 };
914 let new_bytes = bcs::to_bytes(&new).unwrap();
915
916 assert_eq!(
917 legacy_bytes, new_bytes,
918 "VersionedMisbehaviorReport wire format must not change — testnet is live"
919 );
920 }
921
922 #[test]
926 fn misbehavior_observations_v2_wire_format() {
927 let payload = MisbehaviorObservations::V2(MisbehaviorObservationsV2 {
928 faulty_blocks_provable: vec![1, 2, 3],
929 faulty_blocks_unprovable: vec![4, 5, 6],
930 missing_proposals: vec![7, 8, 9],
931 equivocations: vec![10, 11, 12],
932 invalid_bundle_parts: vec![13, 14, 15],
933 });
934
935 let mut expected = vec![1u8];
936 expected.extend(
937 bcs::to_bytes(&(
938 vec![1u64, 2, 3],
939 vec![4u64, 5, 6],
940 vec![7u64, 8, 9],
941 vec![10u64, 11, 12],
942 vec![13u64, 14, 15],
943 ))
944 .unwrap(),
945 );
946
947 assert_eq!(
948 bcs::to_bytes(&payload).unwrap(),
949 expected,
950 "MisbehaviorObservations::V2 wire format must not change"
951 );
952 }
953
954 #[test]
961 fn misbehavior_report_consensus_kind_wire_format_unchanged() {
962 let authority = AuthorityName::default();
963 let generation: u64 = 7;
964 let payload = sample_payload();
965
966 let new_kind = ConsensusTransactionKind::MisbehaviorReport(VersionedMisbehaviorReport {
967 authority,
968 payload: payload.clone(),
969 generation,
970 digest: OnceCell::new(),
971 });
972 let new_bytes = bcs::to_bytes(&new_kind).unwrap();
973
974 let mut legacy_bytes = vec![8u8];
977 legacy_bytes.extend(
978 bcs::to_bytes(&(
979 authority,
980 LegacyVersionedMisbehaviorReport { payload: &payload },
981 generation,
982 ))
983 .unwrap(),
984 );
985
986 assert_eq!(
987 legacy_bytes, new_bytes,
988 "ConsensusTransactionKind::MisbehaviorReport wire format must not change — testnet is live"
989 );
990 }
991
992 #[test]
997 fn deny_rule_proposal_consensus_kind_wire_format_unchanged() {
998 use iota_sdk_types::{Address, DenyRuleSet, ObjectId};
999
1000 let authority = AuthorityName::default();
1001 let address = Address::new([7u8; 32]);
1002 let object = ObjectId::new([8u8; 32]);
1003 let package = ObjectId::new([9u8; 32]);
1004
1005 for hot in 0..6usize {
1009 let switch = |i: usize| i == hot;
1010 let proposal = TransactionDenyRuleProposal {
1011 authority,
1012 generation: 42,
1013 proposed_rules: DenyRuleSet {
1014 denied_addresses: [address].into(),
1015 denied_objects: [object].into(),
1016 denied_packages: [package].into(),
1017 package_publish_disabled: switch(0),
1018 package_upgrade_disabled: switch(1),
1019 shared_object_disabled: switch(2),
1020 user_transaction_disabled: switch(3),
1021 receiving_objects_disabled: switch(4),
1022 move_authenticator_disabled: switch(5),
1023 },
1024 };
1025 let new_bytes = bcs::to_bytes(&ConsensusTransactionKind::TransactionDenyRuleProposal(
1026 proposal,
1027 ))
1028 .unwrap();
1029
1030 let mut legacy_bytes = vec![11u8];
1031 legacy_bytes.extend(
1032 bcs::to_bytes(&(
1033 authority,
1034 42u64,
1035 (
1036 vec![address],
1037 vec![object],
1038 vec![package],
1039 switch(0), switch(1), switch(2), switch(3), switch(4), switch(5), ),
1046 ))
1047 .unwrap(),
1048 );
1049
1050 assert_eq!(
1051 legacy_bytes, new_bytes,
1052 "ConsensusTransactionKind::TransactionDenyRuleProposal wire format must not \
1053 change (switch {hot})"
1054 );
1055 }
1056 }
1057
1058 #[test]
1060 fn deny_rule_proposal_bcs_round_trip() {
1061 use iota_sdk_types::{Address, DenyRuleSet, ObjectId};
1062
1063 let proposal = TransactionDenyRuleProposal {
1064 authority: AuthorityName::default(),
1065 generation: 42,
1066 proposed_rules: DenyRuleSet {
1067 denied_addresses: [Address::new([1u8; 32])].into(),
1068 denied_objects: [ObjectId::new([2u8; 32])].into(),
1069 denied_packages: [ObjectId::new([3u8; 32])].into(),
1070 package_publish_disabled: true,
1071 shared_object_disabled: true,
1072 receiving_objects_disabled: true,
1073 ..Default::default()
1074 },
1075 };
1076 let bytes = bcs::to_bytes(&proposal).unwrap();
1077 assert_eq!(proposal, bcs::from_bytes(&bytes).unwrap());
1078 }
1079
1080 #[test]
1083 fn proposal_generation_supersedes_last_generation() {
1084 use iota_sdk_types::DenyRuleSet;
1085
1086 let authority = AuthorityName::default();
1087 let fresh = TransactionDenyRuleProposal::new(authority, DenyRuleSet::default(), None);
1088 assert!(fresh.generation > 0);
1089
1090 let far_future = fresh.generation + 1_000_000_000;
1091 let successor =
1092 TransactionDenyRuleProposal::new(authority, DenyRuleSet::default(), Some(far_future));
1093 assert_eq!(successor.generation, far_future + 1);
1094 }
1095}