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>,
526}
527
528impl MisbehaviorObservationsV1 {
529 pub fn verify(&self, committee_size: usize) -> bool {
530 if (self.faulty_blocks_provable.len() != committee_size)
538 || (self.faulty_blocks_unprovable.len() != committee_size)
539 || (self.equivocations.len() != committee_size)
540 || (self.missing_proposals.len() != committee_size)
541 {
542 return false;
543 }
544 true
545 }
546}
547
548#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
553pub struct MisbehaviorObservationsV2 {
554 pub faulty_blocks_provable: Vec<u64>,
555 pub faulty_blocks_unprovable: Vec<u64>,
556 pub missing_proposals: Vec<u64>,
557 pub equivocations: Vec<u64>,
560 pub invalid_bundle_parts: Vec<u64>,
561}
562
563impl MisbehaviorObservationsV2 {
564 pub fn verify(&self, committee_size: usize) -> bool {
565 self.faulty_blocks_provable.len() == committee_size
566 && self.faulty_blocks_unprovable.len() == committee_size
567 && self.missing_proposals.len() == committee_size
568 && self.equivocations.len() == committee_size
569 && self.invalid_bundle_parts.len() == committee_size
570 }
571}
572
573#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
574pub enum VersionedDkgMessage {
575 V1(dkg_v1::Message<bls12381::G2Element, bls12381::G2Element>),
576}
577
578#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
579pub enum VersionedDkgConfirmation {
580 V1(dkg_v1::Confirmation<bls12381::G2Element>),
581}
582
583impl Debug for VersionedDkgMessage {
584 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
585 match self {
586 VersionedDkgMessage::V1(msg) => write!(
587 f,
588 "DKG V1 Message with sender={}, vss_pk.degree={}, encrypted_shares.len()={}",
589 msg.sender,
590 msg.vss_pk.degree(),
591 msg.encrypted_shares.len(),
592 ),
593 }
594 }
595}
596
597impl VersionedDkgMessage {
598 pub fn sender(&self) -> u16 {
599 match self {
600 VersionedDkgMessage::V1(msg) => msg.sender,
601 }
602 }
603
604 pub fn create(
605 dkg_version: u64,
606 party: Arc<dkg_v1::Party<bls12381::G2Element, bls12381::G2Element>>,
607 ) -> FastCryptoResult<VersionedDkgMessage> {
608 assert_eq!(dkg_version, 1, "BUG: invalid DKG version");
609 let msg = party.create_message(&mut rand::thread_rng())?;
610 Ok(VersionedDkgMessage::V1(msg))
611 }
612
613 pub fn unwrap_v1(self) -> dkg_v1::Message<bls12381::G2Element, bls12381::G2Element> {
614 match self {
615 VersionedDkgMessage::V1(msg) => msg,
616 }
617 }
618
619 pub fn is_valid_version(&self, dkg_version: u64) -> bool {
620 matches!((self, dkg_version), (VersionedDkgMessage::V1(_), 1))
621 }
622}
623
624impl VersionedDkgConfirmation {
625 pub fn sender(&self) -> u16 {
626 match self {
627 VersionedDkgConfirmation::V1(msg) => msg.sender,
628 }
629 }
630
631 pub fn num_of_complaints(&self) -> usize {
632 match self {
633 VersionedDkgConfirmation::V1(msg) => msg.complaints.len(),
634 }
635 }
636
637 pub fn unwrap_v1(&self) -> &dkg_v1::Confirmation<bls12381::G2Element> {
638 match self {
639 VersionedDkgConfirmation::V1(msg) => msg,
640 }
641 }
642
643 pub fn is_valid_version(&self, dkg_version: u64) -> bool {
644 matches!((self, dkg_version), (VersionedDkgConfirmation::V1(_), 1))
645 }
646}
647
648impl ConsensusTransaction {
649 pub fn new_certificate_message(
650 authority: &AuthorityName,
651 certificate: CertifiedTransaction,
652 ) -> Self {
653 let mut hasher = DefaultHasher::new();
654 let tx_digest = certificate.digest();
655 tx_digest.hash(&mut hasher);
656 authority.hash(&mut hasher);
657 let tracking_id = hasher.finish().to_le_bytes();
658 Self {
659 tracking_id,
660 kind: ConsensusTransactionKind::CertifiedTransaction(Box::new(certificate)),
661 }
662 }
663
664 pub fn new_checkpoint_signature_message(data: CheckpointSignatureMessage) -> Self {
665 let mut hasher = DefaultHasher::new();
666 data.summary.auth_sig().signature.hash(&mut hasher);
667 let tracking_id = hasher.finish().to_le_bytes();
668 Self {
669 tracking_id,
670 kind: ConsensusTransactionKind::CheckpointSignature(Box::new(data)),
671 }
672 }
673
674 pub fn new_end_of_publish(authority: AuthorityName) -> Self {
675 let mut hasher = DefaultHasher::new();
676 authority.hash(&mut hasher);
677 let tracking_id = hasher.finish().to_le_bytes();
678 Self {
679 tracking_id,
680 kind: ConsensusTransactionKind::EndOfPublish(authority),
681 }
682 }
683
684 pub fn new_capability_notification_v1(capabilities: AuthorityCapabilitiesV1) -> Self {
685 let mut hasher = DefaultHasher::new();
686 capabilities.hash(&mut hasher);
687 let tracking_id = hasher.finish().to_le_bytes();
688 Self {
689 tracking_id,
690 kind: ConsensusTransactionKind::CapabilityNotificationV1(capabilities),
691 }
692 }
693
694 pub fn new_signed_capability_notification_v1(
695 signed_capabilities: SignedAuthorityCapabilitiesV1,
696 ) -> Self {
697 let mut hasher = DefaultHasher::new();
698 signed_capabilities.data().hash(&mut hasher);
699 signed_capabilities.auth_sig().hash(&mut hasher);
700 let tracking_id = hasher.finish().to_le_bytes();
701 Self {
702 tracking_id,
703 kind: ConsensusTransactionKind::SignedCapabilityNotificationV1(signed_capabilities),
704 }
705 }
706
707 pub fn new_randomness_dkg_message(
708 authority: AuthorityName,
709 versioned_message: &VersionedDkgMessage,
710 ) -> Self {
711 let message =
712 bcs::to_bytes(versioned_message).expect("message serialization should not fail");
713 let mut hasher = DefaultHasher::new();
714 message.hash(&mut hasher);
715 let tracking_id = hasher.finish().to_le_bytes();
716 Self {
717 tracking_id,
718 kind: ConsensusTransactionKind::RandomnessDkgMessage(authority, message),
719 }
720 }
721 pub fn new_randomness_dkg_confirmation(
722 authority: AuthorityName,
723 versioned_confirmation: &VersionedDkgConfirmation,
724 ) -> Self {
725 let confirmation =
726 bcs::to_bytes(versioned_confirmation).expect("message serialization should not fail");
727 let mut hasher = DefaultHasher::new();
728 confirmation.hash(&mut hasher);
729 let tracking_id = hasher.finish().to_le_bytes();
730 Self {
731 tracking_id,
732 kind: ConsensusTransactionKind::RandomnessDkgConfirmation(authority, confirmation),
733 }
734 }
735
736 pub fn new_misbehavior_report(report: VersionedMisbehaviorReport) -> Self {
737 let serialized_report =
738 bcs::to_bytes(&report).expect("report serialization should not fail");
739 let mut hasher = DefaultHasher::new();
740 serialized_report.hash(&mut hasher);
741 let tracking_id = hasher.finish().to_le_bytes();
742 Self {
743 tracking_id,
744 kind: ConsensusTransactionKind::MisbehaviorReport(report),
745 }
746 }
747
748 pub fn new_user_transaction(transaction: TransactionEnvelope) -> Self {
749 let mut hasher = DefaultHasher::new();
750 let tx_digest = transaction.digest();
751 tx_digest.hash(&mut hasher);
752 let tracking_id = hasher.finish().to_le_bytes();
753 Self {
754 tracking_id,
755 kind: ConsensusTransactionKind::UserTransactionV1(Box::new(transaction)),
756 }
757 }
758
759 pub fn new_overload_notification_v1(
760 authority: AuthorityName,
761 load_shedding_percentage: u8,
762 ) -> Self {
763 let generation: u64 = SystemTime::now()
770 .duration_since(UNIX_EPOCH)
771 .expect("IOTA did not exist prior to 1970")
772 .as_millis()
773 .try_into()
774 .expect("This build of iota is not supported in the year 500,000,000");
775 let mut hasher = DefaultHasher::new();
776 authority.hash(&mut hasher);
777 generation.hash(&mut hasher);
778 load_shedding_percentage.hash(&mut hasher);
779 let tracking_id = hasher.finish().to_le_bytes();
780 Self {
781 tracking_id,
782 kind: ConsensusTransactionKind::OverloadNotificationV1(
783 authority,
784 generation,
785 load_shedding_percentage,
786 ),
787 }
788 }
789
790 pub fn new_transaction_deny_rule_proposal(proposal: TransactionDenyRuleProposal) -> Self {
791 let mut hasher = DefaultHasher::new();
792 proposal.hash(&mut hasher);
793 let tracking_id = hasher.finish().to_le_bytes();
794 Self {
795 tracking_id,
796 kind: ConsensusTransactionKind::TransactionDenyRuleProposal(proposal),
797 }
798 }
799
800 pub fn get_tracking_id(&self) -> u64 {
801 (&self.tracking_id[..])
802 .read_u64::<BigEndian>()
803 .unwrap_or_default()
804 }
805
806 pub fn key(&self) -> ConsensusTransactionKey {
807 match &self.kind {
808 ConsensusTransactionKind::CertifiedTransaction(cert) => {
809 ConsensusTransactionKey::Certificate(*cert.digest())
810 }
811 ConsensusTransactionKind::CheckpointSignature(data) => {
812 ConsensusTransactionKey::CheckpointSignature(
813 data.summary.auth_sig().authority,
814 data.summary.sequence_number,
815 )
816 }
817 ConsensusTransactionKind::EndOfPublish(authority) => {
818 ConsensusTransactionKey::EndOfPublish(*authority)
819 }
820 ConsensusTransactionKind::CapabilityNotificationV1(cap) => {
821 ConsensusTransactionKey::CapabilityNotification(cap.authority, cap.generation)
822 }
823 ConsensusTransactionKind::SignedCapabilityNotificationV1(signed_cap) => {
824 ConsensusTransactionKey::CapabilityNotification(
825 signed_cap.authority,
826 signed_cap.generation,
827 )
828 }
829
830 #[allow(deprecated)]
831 ConsensusTransactionKind::NewJWKFetchedDeprecated => {
832 ConsensusTransactionKey::NewJWKFetchedDeprecated
833 }
834 ConsensusTransactionKind::RandomnessDkgMessage(authority, _) => {
835 ConsensusTransactionKey::RandomnessDkgMessage(*authority)
836 }
837 ConsensusTransactionKind::RandomnessDkgConfirmation(authority, _) => {
838 ConsensusTransactionKey::RandomnessDkgConfirmation(*authority)
839 }
840 ConsensusTransactionKind::MisbehaviorReport(report) => {
841 ConsensusTransactionKey::MisbehaviorReport(
842 report.authority,
843 *report.digest(),
844 report.generation,
845 )
846 }
847 ConsensusTransactionKind::UserTransactionV1(tx) => {
848 ConsensusTransactionKey::UserTransaction(*tx.digest())
849 }
850 ConsensusTransactionKind::OverloadNotificationV1(authority, generation, _) => {
851 ConsensusTransactionKey::OverloadNotificationV1(*authority, *generation)
852 }
853 ConsensusTransactionKind::TransactionDenyRuleProposal(proposal) => {
854 ConsensusTransactionKey::TransactionDenyRuleProposal(
855 proposal.authority,
856 proposal.generation,
857 )
858 }
859 }
860 }
861
862 pub fn is_user_certificate(&self) -> bool {
863 matches!(self.kind, ConsensusTransactionKind::CertifiedTransaction(_))
864 }
865
866 pub fn is_end_of_publish(&self) -> bool {
867 matches!(self.kind, ConsensusTransactionKind::EndOfPublish(_))
868 }
869}
870
871#[cfg(test)]
872mod tests {
873 use super::*;
874
875 #[derive(Serialize)]
879 struct LegacyVersionedMisbehaviorReport<'a> {
880 payload: &'a MisbehaviorObservations,
881 }
882
883 fn sample_payload() -> MisbehaviorObservations {
884 MisbehaviorObservations::V1(MisbehaviorObservationsV1 {
885 faulty_blocks_provable: vec![1, 2, 3],
886 faulty_blocks_unprovable: vec![4, 5, 6],
887 missing_proposals: vec![7, 8, 9],
888 equivocations: vec![10, 11, 12],
889 })
890 }
891
892 #[test]
900 fn misbehavior_report_wire_format_unchanged() {
901 let authority = AuthorityName::default();
902 let generation: u64 = 42;
903 let payload = sample_payload();
904
905 let legacy_bytes = bcs::to_bytes(&(
906 authority,
907 LegacyVersionedMisbehaviorReport { payload: &payload },
908 generation,
909 ))
910 .unwrap();
911
912 let new = VersionedMisbehaviorReport {
913 authority,
914 payload,
915 generation,
916 digest: OnceCell::new(),
917 };
918 let new_bytes = bcs::to_bytes(&new).unwrap();
919
920 assert_eq!(
921 legacy_bytes, new_bytes,
922 "VersionedMisbehaviorReport wire format must not change — testnet is live"
923 );
924 }
925
926 #[test]
930 fn misbehavior_observations_v2_wire_format() {
931 let payload = MisbehaviorObservations::V2(MisbehaviorObservationsV2 {
932 faulty_blocks_provable: vec![1, 2, 3],
933 faulty_blocks_unprovable: vec![4, 5, 6],
934 missing_proposals: vec![7, 8, 9],
935 equivocations: vec![10, 11, 12],
936 invalid_bundle_parts: vec![13, 14, 15],
937 });
938
939 let mut expected = vec![1u8];
940 expected.extend(
941 bcs::to_bytes(&(
942 vec![1u64, 2, 3],
943 vec![4u64, 5, 6],
944 vec![7u64, 8, 9],
945 vec![10u64, 11, 12],
946 vec![13u64, 14, 15],
947 ))
948 .unwrap(),
949 );
950
951 assert_eq!(
952 bcs::to_bytes(&payload).unwrap(),
953 expected,
954 "MisbehaviorObservations::V2 wire format must not change"
955 );
956 }
957
958 #[test]
965 fn misbehavior_report_consensus_kind_wire_format_unchanged() {
966 let authority = AuthorityName::default();
967 let generation: u64 = 7;
968 let payload = sample_payload();
969
970 let new_kind = ConsensusTransactionKind::MisbehaviorReport(VersionedMisbehaviorReport {
971 authority,
972 payload: payload.clone(),
973 generation,
974 digest: OnceCell::new(),
975 });
976 let new_bytes = bcs::to_bytes(&new_kind).unwrap();
977
978 let mut legacy_bytes = vec![8u8];
981 legacy_bytes.extend(
982 bcs::to_bytes(&(
983 authority,
984 LegacyVersionedMisbehaviorReport { payload: &payload },
985 generation,
986 ))
987 .unwrap(),
988 );
989
990 assert_eq!(
991 legacy_bytes, new_bytes,
992 "ConsensusTransactionKind::MisbehaviorReport wire format must not change — testnet is live"
993 );
994 }
995
996 #[test]
1001 fn deny_rule_proposal_consensus_kind_wire_format_unchanged() {
1002 use iota_sdk_types::{Address, DenyRuleSet, ObjectId};
1003
1004 let authority = AuthorityName::default();
1005 let address = Address::new([7u8; 32]);
1006 let object = ObjectId::new([8u8; 32]);
1007 let package = ObjectId::new([9u8; 32]);
1008
1009 for hot in 0..6usize {
1013 let switch = |i: usize| i == hot;
1014 let proposal = TransactionDenyRuleProposal {
1015 authority,
1016 generation: 42,
1017 proposed_rules: DenyRuleSet {
1018 denied_addresses: [address].into(),
1019 denied_objects: [object].into(),
1020 denied_packages: [package].into(),
1021 package_publish_disabled: switch(0),
1022 package_upgrade_disabled: switch(1),
1023 shared_object_disabled: switch(2),
1024 user_transaction_disabled: switch(3),
1025 receiving_objects_disabled: switch(4),
1026 move_authenticator_disabled: switch(5),
1027 },
1028 };
1029 let new_bytes = bcs::to_bytes(&ConsensusTransactionKind::TransactionDenyRuleProposal(
1030 proposal,
1031 ))
1032 .unwrap();
1033
1034 let mut legacy_bytes = vec![11u8];
1035 legacy_bytes.extend(
1036 bcs::to_bytes(&(
1037 authority,
1038 42u64,
1039 (
1040 vec![address],
1041 vec![object],
1042 vec![package],
1043 switch(0), switch(1), switch(2), switch(3), switch(4), switch(5), ),
1050 ))
1051 .unwrap(),
1052 );
1053
1054 assert_eq!(
1055 legacy_bytes, new_bytes,
1056 "ConsensusTransactionKind::TransactionDenyRuleProposal wire format must not \
1057 change (switch {hot})"
1058 );
1059 }
1060 }
1061
1062 #[test]
1064 fn deny_rule_proposal_bcs_round_trip() {
1065 use iota_sdk_types::{Address, DenyRuleSet, ObjectId};
1066
1067 let proposal = TransactionDenyRuleProposal {
1068 authority: AuthorityName::default(),
1069 generation: 42,
1070 proposed_rules: DenyRuleSet {
1071 denied_addresses: [Address::new([1u8; 32])].into(),
1072 denied_objects: [ObjectId::new([2u8; 32])].into(),
1073 denied_packages: [ObjectId::new([3u8; 32])].into(),
1074 package_publish_disabled: true,
1075 shared_object_disabled: true,
1076 receiving_objects_disabled: true,
1077 ..Default::default()
1078 },
1079 };
1080 let bytes = bcs::to_bytes(&proposal).unwrap();
1081 assert_eq!(proposal, bcs::from_bytes(&bytes).unwrap());
1082 }
1083
1084 #[test]
1087 fn proposal_generation_supersedes_last_generation() {
1088 use iota_sdk_types::DenyRuleSet;
1089
1090 let authority = AuthorityName::default();
1091 let fresh = TransactionDenyRuleProposal::new(authority, DenyRuleSet::default(), None);
1092 assert!(fresh.generation > 0);
1093
1094 let far_future = fresh.generation + 1_000_000_000;
1095 let successor =
1096 TransactionDenyRuleProposal::new(authority, DenyRuleSet::default(), Some(far_future));
1097 assert_eq!(successor.generation, far_future + 1);
1098 }
1099}