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, TransactionDigest, crypto::IntentScope,
18};
19use once_cell::sync::OnceCell;
20use serde::{Deserialize, Serialize};
21use tracing::warn;
22
23use crate::{
24 base_types::{AuthorityName, ConciseableName},
25 crypto::{AuthoritySignature, DefaultHash, default_hash},
26 deny_rule_governance::DenyRuleSet,
27 message_envelope::{Envelope, Message, VerifiedEnvelope},
28 messages_checkpoint::{CheckpointSequenceNumber, CheckpointSignatureMessage},
29 supported_protocol_versions::{
30 Chain, SupportedProtocolVersions, SupportedProtocolVersionsWithHashes,
31 },
32 transaction::{CertifiedTransaction, SenderSignedData, Transaction},
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<Transaction>),
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 Transaction) -> 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_data(&self) -> Option<&SenderSignedData> {
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<&Transaction> {
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}
436
437impl VersionedMisbehaviorReport {
438 pub fn new_v1(
439 authority: AuthorityName,
440 generation: u64,
441 observations: MisbehaviorObservationsV1,
442 ) -> Self {
443 Self {
444 authority,
445 payload: MisbehaviorObservations::V1(observations),
446 generation,
447 digest: OnceCell::new(),
448 }
449 }
450
451 pub fn digest(&self) -> &MisbehaviorReportDigest {
454 self.digest
455 .get_or_init(|| MisbehaviorReportDigest::new(default_hash(self)))
456 }
457
458 pub fn summary(&self) -> u64 {
461 let summary = match &self.payload {
462 MisbehaviorObservations::V1(report) => [
463 &report.faulty_blocks_provable,
464 &report.faulty_blocks_unprovable,
465 &report.missing_proposals,
466 &report.equivocations,
467 ]
468 .into_iter()
469 .flatten()
470 .fold(0u64, |acc, metric| acc.saturating_add(*metric)),
471 };
472 if summary == u64::MAX {
473 warn!("MisbehaviorReport summary reached its maximum value.");
474 }
475 summary
476 }
477}
478
479#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
485pub struct MisbehaviorObservationsV1 {
486 pub faulty_blocks_provable: Vec<u64>,
487 pub faulty_blocks_unprovable: Vec<u64>,
488 pub missing_proposals: Vec<u64>,
489 pub equivocations: Vec<u64>,
490}
491
492impl MisbehaviorObservationsV1 {
493 pub fn verify(&self, committee_size: usize) -> bool {
494 if (self.faulty_blocks_provable.len() != committee_size)
502 || (self.faulty_blocks_unprovable.len() != committee_size)
503 || (self.equivocations.len() != committee_size)
504 || (self.missing_proposals.len() != committee_size)
505 {
506 return false;
507 }
508 true
509 }
510}
511
512#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
513pub enum VersionedDkgMessage {
514 V1(dkg_v1::Message<bls12381::G2Element, bls12381::G2Element>),
515}
516
517#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
518pub enum VersionedDkgConfirmation {
519 V1(dkg_v1::Confirmation<bls12381::G2Element>),
520}
521
522impl Debug for VersionedDkgMessage {
523 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
524 match self {
525 VersionedDkgMessage::V1(msg) => write!(
526 f,
527 "DKG V1 Message with sender={}, vss_pk.degree={}, encrypted_shares.len()={}",
528 msg.sender,
529 msg.vss_pk.degree(),
530 msg.encrypted_shares.len(),
531 ),
532 }
533 }
534}
535
536impl VersionedDkgMessage {
537 pub fn sender(&self) -> u16 {
538 match self {
539 VersionedDkgMessage::V1(msg) => msg.sender,
540 }
541 }
542
543 pub fn create(
544 dkg_version: u64,
545 party: Arc<dkg_v1::Party<bls12381::G2Element, bls12381::G2Element>>,
546 ) -> FastCryptoResult<VersionedDkgMessage> {
547 assert_eq!(dkg_version, 1, "BUG: invalid DKG version");
548 let msg = party.create_message(&mut rand::thread_rng())?;
549 Ok(VersionedDkgMessage::V1(msg))
550 }
551
552 pub fn unwrap_v1(self) -> dkg_v1::Message<bls12381::G2Element, bls12381::G2Element> {
553 match self {
554 VersionedDkgMessage::V1(msg) => msg,
555 }
556 }
557
558 pub fn is_valid_version(&self, dkg_version: u64) -> bool {
559 matches!((self, dkg_version), (VersionedDkgMessage::V1(_), 1))
560 }
561}
562
563impl VersionedDkgConfirmation {
564 pub fn sender(&self) -> u16 {
565 match self {
566 VersionedDkgConfirmation::V1(msg) => msg.sender,
567 }
568 }
569
570 pub fn num_of_complaints(&self) -> usize {
571 match self {
572 VersionedDkgConfirmation::V1(msg) => msg.complaints.len(),
573 }
574 }
575
576 pub fn unwrap_v1(&self) -> &dkg_v1::Confirmation<bls12381::G2Element> {
577 match self {
578 VersionedDkgConfirmation::V1(msg) => msg,
579 }
580 }
581
582 pub fn is_valid_version(&self, dkg_version: u64) -> bool {
583 matches!((self, dkg_version), (VersionedDkgConfirmation::V1(_), 1))
584 }
585}
586
587impl ConsensusTransaction {
588 pub fn new_certificate_message(
589 authority: &AuthorityName,
590 certificate: CertifiedTransaction,
591 ) -> Self {
592 let mut hasher = DefaultHasher::new();
593 let tx_digest = certificate.digest();
594 tx_digest.hash(&mut hasher);
595 authority.hash(&mut hasher);
596 let tracking_id = hasher.finish().to_le_bytes();
597 Self {
598 tracking_id,
599 kind: ConsensusTransactionKind::CertifiedTransaction(Box::new(certificate)),
600 }
601 }
602
603 pub fn new_checkpoint_signature_message(data: CheckpointSignatureMessage) -> Self {
604 let mut hasher = DefaultHasher::new();
605 data.summary.auth_sig().signature.hash(&mut hasher);
606 let tracking_id = hasher.finish().to_le_bytes();
607 Self {
608 tracking_id,
609 kind: ConsensusTransactionKind::CheckpointSignature(Box::new(data)),
610 }
611 }
612
613 pub fn new_end_of_publish(authority: AuthorityName) -> Self {
614 let mut hasher = DefaultHasher::new();
615 authority.hash(&mut hasher);
616 let tracking_id = hasher.finish().to_le_bytes();
617 Self {
618 tracking_id,
619 kind: ConsensusTransactionKind::EndOfPublish(authority),
620 }
621 }
622
623 pub fn new_capability_notification_v1(capabilities: AuthorityCapabilitiesV1) -> Self {
624 let mut hasher = DefaultHasher::new();
625 capabilities.hash(&mut hasher);
626 let tracking_id = hasher.finish().to_le_bytes();
627 Self {
628 tracking_id,
629 kind: ConsensusTransactionKind::CapabilityNotificationV1(capabilities),
630 }
631 }
632
633 pub fn new_signed_capability_notification_v1(
634 signed_capabilities: SignedAuthorityCapabilitiesV1,
635 ) -> Self {
636 let mut hasher = DefaultHasher::new();
637 signed_capabilities.data().hash(&mut hasher);
638 signed_capabilities.auth_sig().hash(&mut hasher);
639 let tracking_id = hasher.finish().to_le_bytes();
640 Self {
641 tracking_id,
642 kind: ConsensusTransactionKind::SignedCapabilityNotificationV1(signed_capabilities),
643 }
644 }
645
646 pub fn new_randomness_dkg_message(
647 authority: AuthorityName,
648 versioned_message: &VersionedDkgMessage,
649 ) -> Self {
650 let message =
651 bcs::to_bytes(versioned_message).expect("message serialization should not fail");
652 let mut hasher = DefaultHasher::new();
653 message.hash(&mut hasher);
654 let tracking_id = hasher.finish().to_le_bytes();
655 Self {
656 tracking_id,
657 kind: ConsensusTransactionKind::RandomnessDkgMessage(authority, message),
658 }
659 }
660 pub fn new_randomness_dkg_confirmation(
661 authority: AuthorityName,
662 versioned_confirmation: &VersionedDkgConfirmation,
663 ) -> Self {
664 let confirmation =
665 bcs::to_bytes(versioned_confirmation).expect("message serialization should not fail");
666 let mut hasher = DefaultHasher::new();
667 confirmation.hash(&mut hasher);
668 let tracking_id = hasher.finish().to_le_bytes();
669 Self {
670 tracking_id,
671 kind: ConsensusTransactionKind::RandomnessDkgConfirmation(authority, confirmation),
672 }
673 }
674
675 pub fn new_misbehavior_report(report: VersionedMisbehaviorReport) -> Self {
676 let serialized_report =
677 bcs::to_bytes(&report).expect("report serialization should not fail");
678 let mut hasher = DefaultHasher::new();
679 serialized_report.hash(&mut hasher);
680 let tracking_id = hasher.finish().to_le_bytes();
681 Self {
682 tracking_id,
683 kind: ConsensusTransactionKind::MisbehaviorReport(report),
684 }
685 }
686
687 pub fn new_user_transaction(transaction: Transaction) -> Self {
688 let mut hasher = DefaultHasher::new();
689 let tx_digest = transaction.digest();
690 tx_digest.hash(&mut hasher);
691 let tracking_id = hasher.finish().to_le_bytes();
692 Self {
693 tracking_id,
694 kind: ConsensusTransactionKind::UserTransactionV1(Box::new(transaction)),
695 }
696 }
697
698 pub fn new_overload_notification_v1(
699 authority: AuthorityName,
700 load_shedding_percentage: u8,
701 ) -> Self {
702 let generation: u64 = SystemTime::now()
709 .duration_since(UNIX_EPOCH)
710 .expect("IOTA did not exist prior to 1970")
711 .as_millis()
712 .try_into()
713 .expect("This build of iota is not supported in the year 500,000,000");
714 let mut hasher = DefaultHasher::new();
715 authority.hash(&mut hasher);
716 generation.hash(&mut hasher);
717 load_shedding_percentage.hash(&mut hasher);
718 let tracking_id = hasher.finish().to_le_bytes();
719 Self {
720 tracking_id,
721 kind: ConsensusTransactionKind::OverloadNotificationV1(
722 authority,
723 generation,
724 load_shedding_percentage,
725 ),
726 }
727 }
728
729 pub fn new_transaction_deny_rule_proposal(proposal: TransactionDenyRuleProposal) -> Self {
730 let mut hasher = DefaultHasher::new();
731 proposal.hash(&mut hasher);
732 let tracking_id = hasher.finish().to_le_bytes();
733 Self {
734 tracking_id,
735 kind: ConsensusTransactionKind::TransactionDenyRuleProposal(proposal),
736 }
737 }
738
739 pub fn get_tracking_id(&self) -> u64 {
740 (&self.tracking_id[..])
741 .read_u64::<BigEndian>()
742 .unwrap_or_default()
743 }
744
745 pub fn key(&self) -> ConsensusTransactionKey {
746 match &self.kind {
747 ConsensusTransactionKind::CertifiedTransaction(cert) => {
748 ConsensusTransactionKey::Certificate(*cert.digest())
749 }
750 ConsensusTransactionKind::CheckpointSignature(data) => {
751 ConsensusTransactionKey::CheckpointSignature(
752 data.summary.auth_sig().authority,
753 data.summary.sequence_number,
754 )
755 }
756 ConsensusTransactionKind::EndOfPublish(authority) => {
757 ConsensusTransactionKey::EndOfPublish(*authority)
758 }
759 ConsensusTransactionKind::CapabilityNotificationV1(cap) => {
760 ConsensusTransactionKey::CapabilityNotification(cap.authority, cap.generation)
761 }
762 ConsensusTransactionKind::SignedCapabilityNotificationV1(signed_cap) => {
763 ConsensusTransactionKey::CapabilityNotification(
764 signed_cap.authority,
765 signed_cap.generation,
766 )
767 }
768
769 #[allow(deprecated)]
770 ConsensusTransactionKind::NewJWKFetchedDeprecated => {
771 ConsensusTransactionKey::NewJWKFetchedDeprecated
772 }
773 ConsensusTransactionKind::RandomnessDkgMessage(authority, _) => {
774 ConsensusTransactionKey::RandomnessDkgMessage(*authority)
775 }
776 ConsensusTransactionKind::RandomnessDkgConfirmation(authority, _) => {
777 ConsensusTransactionKey::RandomnessDkgConfirmation(*authority)
778 }
779 ConsensusTransactionKind::MisbehaviorReport(report) => {
780 ConsensusTransactionKey::MisbehaviorReport(
781 report.authority,
782 *report.digest(),
783 report.generation,
784 )
785 }
786 ConsensusTransactionKind::UserTransactionV1(tx) => {
787 ConsensusTransactionKey::UserTransaction(*tx.digest())
788 }
789 ConsensusTransactionKind::OverloadNotificationV1(authority, generation, _) => {
790 ConsensusTransactionKey::OverloadNotificationV1(*authority, *generation)
791 }
792 ConsensusTransactionKind::TransactionDenyRuleProposal(proposal) => {
793 ConsensusTransactionKey::TransactionDenyRuleProposal(
794 proposal.authority,
795 proposal.generation,
796 )
797 }
798 }
799 }
800
801 pub fn is_user_certificate(&self) -> bool {
802 matches!(self.kind, ConsensusTransactionKind::CertifiedTransaction(_))
803 }
804
805 pub fn is_end_of_publish(&self) -> bool {
806 matches!(self.kind, ConsensusTransactionKind::EndOfPublish(_))
807 }
808}
809
810#[cfg(test)]
811mod tests {
812 use super::*;
813
814 #[derive(Serialize)]
818 struct LegacyVersionedMisbehaviorReport<'a> {
819 payload: &'a MisbehaviorObservations,
820 }
821
822 fn sample_payload() -> MisbehaviorObservations {
823 MisbehaviorObservations::V1(MisbehaviorObservationsV1 {
824 faulty_blocks_provable: vec![1, 2, 3],
825 faulty_blocks_unprovable: vec![4, 5, 6],
826 missing_proposals: vec![7, 8, 9],
827 equivocations: vec![10, 11, 12],
828 })
829 }
830
831 #[test]
839 fn misbehavior_report_wire_format_unchanged() {
840 let authority = AuthorityName::default();
841 let generation: u64 = 42;
842 let payload = sample_payload();
843
844 let legacy_bytes = bcs::to_bytes(&(
845 authority,
846 LegacyVersionedMisbehaviorReport { payload: &payload },
847 generation,
848 ))
849 .unwrap();
850
851 let new = VersionedMisbehaviorReport {
852 authority,
853 payload,
854 generation,
855 digest: OnceCell::new(),
856 };
857 let new_bytes = bcs::to_bytes(&new).unwrap();
858
859 assert_eq!(
860 legacy_bytes, new_bytes,
861 "VersionedMisbehaviorReport wire format must not change — testnet is live"
862 );
863 }
864
865 #[test]
872 fn misbehavior_report_consensus_kind_wire_format_unchanged() {
873 let authority = AuthorityName::default();
874 let generation: u64 = 7;
875 let payload = sample_payload();
876
877 let new_kind = ConsensusTransactionKind::MisbehaviorReport(VersionedMisbehaviorReport {
878 authority,
879 payload: payload.clone(),
880 generation,
881 digest: OnceCell::new(),
882 });
883 let new_bytes = bcs::to_bytes(&new_kind).unwrap();
884
885 let mut legacy_bytes = vec![8u8];
888 legacy_bytes.extend(
889 bcs::to_bytes(&(
890 authority,
891 LegacyVersionedMisbehaviorReport { payload: &payload },
892 generation,
893 ))
894 .unwrap(),
895 );
896
897 assert_eq!(
898 legacy_bytes, new_bytes,
899 "ConsensusTransactionKind::MisbehaviorReport wire format must not change — testnet is live"
900 );
901 }
902
903 #[test]
908 fn deny_rule_proposal_consensus_kind_wire_format_unchanged() {
909 use std::collections::BTreeSet;
910
911 use iota_sdk_types::{Address, ObjectId};
912
913 use crate::deny_rule_governance::DenyRuleSet;
914
915 let authority = AuthorityName::default();
916 let address = Address::new([7u8; 32]);
917 let object = ObjectId::new([8u8; 32]);
918 let package = ObjectId::new([9u8; 32]);
919
920 for hot in 0..6usize {
924 let switch = |i: usize| i == hot;
925 let proposal = TransactionDenyRuleProposal {
926 authority,
927 generation: 42,
928 proposed_rules: DenyRuleSet {
929 denied_addresses: [address].into(),
930 denied_objects: [object].into(),
931 denied_packages: [package].into(),
932 package_publish_disabled: switch(0),
933 package_upgrade_disabled: switch(1),
934 shared_object_disabled: switch(2),
935 user_transaction_disabled: switch(3),
936 receiving_objects_disabled: switch(4),
937 move_authenticator_disabled: switch(5),
938 },
939 };
940 let new_bytes = bcs::to_bytes(&ConsensusTransactionKind::TransactionDenyRuleProposal(
941 proposal,
942 ))
943 .unwrap();
944
945 let mut legacy_bytes = vec![11u8];
946 legacy_bytes.extend(
947 bcs::to_bytes(&(
948 authority,
949 42u64,
950 (
951 BTreeSet::from([address]),
952 BTreeSet::from([object]),
953 BTreeSet::from([package]),
954 switch(0), switch(1), switch(2), switch(3), switch(4), switch(5), ),
961 ))
962 .unwrap(),
963 );
964
965 assert_eq!(
966 legacy_bytes, new_bytes,
967 "ConsensusTransactionKind::TransactionDenyRuleProposal wire format must not \
968 change (switch {hot})"
969 );
970 }
971 }
972
973 #[test]
975 fn deny_rule_proposal_bcs_round_trip() {
976 use iota_sdk_types::{Address, ObjectId};
977
978 use crate::deny_rule_governance::DenyRuleSet;
979
980 let proposal = TransactionDenyRuleProposal {
981 authority: AuthorityName::default(),
982 generation: 42,
983 proposed_rules: DenyRuleSet {
984 denied_addresses: [Address::new([1u8; 32])].into(),
985 denied_objects: [ObjectId::new([2u8; 32])].into(),
986 denied_packages: [ObjectId::new([3u8; 32])].into(),
987 package_publish_disabled: true,
988 shared_object_disabled: true,
989 receiving_objects_disabled: true,
990 ..Default::default()
991 },
992 };
993 let bytes = bcs::to_bytes(&proposal).unwrap();
994 assert_eq!(proposal, bcs::from_bytes(&bytes).unwrap());
995 }
996
997 #[test]
1000 fn proposal_generation_supersedes_last_generation() {
1001 use crate::deny_rule_governance::DenyRuleSet;
1002
1003 let authority = AuthorityName::default();
1004 let fresh = TransactionDenyRuleProposal::new(authority, DenyRuleSet::default(), None);
1005 assert!(fresh.generation > 0);
1006
1007 let far_future = fresh.generation + 1_000_000_000;
1008 let successor =
1009 TransactionDenyRuleProposal::new(authority, DenyRuleSet::default(), Some(far_future));
1010 assert_eq!(successor.generation, far_future + 1);
1011 }
1012}