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