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::crypto::IntentScope;
17pub use iota_sdk_types::{
18 CancelledTransaction, CheckpointTimestamp as TimestampMs, ConsensusCommitPrologueV1,
19 ConsensusDeterminedVersionAssignments, VersionAssignment,
20};
21use once_cell::sync::OnceCell;
22use serde::{Deserialize, Serialize};
23use tracing::warn;
24
25use crate::{
26 base_types::{AuthorityName, ConciseableName, ObjectRef, TransactionDigest},
27 crypto::{AuthoritySignature, DefaultHash, default_hash},
28 digests::{Digest, MisbehaviorReportDigest},
29 message_envelope::{Envelope, Message, VerifiedEnvelope},
30 messages_checkpoint::{CheckpointSequenceNumber, CheckpointSignatureMessage},
31 supported_protocol_versions::{
32 Chain, SupportedProtocolVersions, SupportedProtocolVersionsWithHashes,
33 },
34 transaction::CertifiedTransaction,
35};
36
37#[derive(Serialize, Deserialize, Clone, Debug)]
38pub struct ConsensusTransaction {
39 pub tracking_id: [u8; 8],
43 pub kind: ConsensusTransactionKind,
44}
45
46#[derive(Serialize, Deserialize, Clone, Hash, PartialEq, Eq, Ord, PartialOrd)]
47pub enum ConsensusTransactionKey {
48 Certificate(TransactionDigest),
49 CheckpointSignature(AuthorityName, CheckpointSequenceNumber),
50 EndOfPublish(AuthorityName),
51 CapabilityNotification(AuthorityName, u64 ),
52 #[deprecated(note = "Authenticator state (JWK) is deprecated and was never enabled on IOTA")]
53 NewJWKFetchedDeprecated,
54 RandomnessDkgMessage(AuthorityName),
55 RandomnessDkgConfirmation(AuthorityName),
56 MisbehaviorReport(
57 AuthorityName,
58 MisbehaviorReportDigest,
59 CheckpointSequenceNumber,
60 ),
61 }
64
65impl Debug for ConsensusTransactionKey {
66 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
67 match self {
68 Self::Certificate(digest) => write!(f, "Certificate({digest:?})"),
69 Self::CheckpointSignature(name, seq) => {
70 write!(f, "CheckpointSignature({:?}, {:?})", name.concise(), seq)
71 }
72 Self::EndOfPublish(name) => write!(f, "EndOfPublish({:?})", name.concise()),
73 Self::CapabilityNotification(name, generation) => write!(
74 f,
75 "CapabilityNotification({:?}, {:?})",
76 name.concise(),
77 generation
78 ),
79 #[allow(deprecated)]
80 Self::NewJWKFetchedDeprecated => {
81 write!(
82 f,
83 "NewJWKFetched(deprecated: Authenticator state (JWK) is deprecated and was never enabled on IOTA)"
84 )
85 }
86 Self::RandomnessDkgMessage(name) => {
87 write!(f, "RandomnessDkgMessage({:?})", name.concise())
88 }
89 Self::RandomnessDkgConfirmation(name) => {
90 write!(f, "RandomnessDkgConfirmation({:?})", name.concise())
91 }
92 Self::MisbehaviorReport(name, digest, checkpoint_seq) => {
93 write!(
94 f,
95 "MisbehaviorReport({:?}, {:?}, {:?})",
96 name.concise(),
97 digest,
98 checkpoint_seq
99 )
100 }
101 }
102 }
103}
104
105pub type SignedAuthorityCapabilitiesV1 = Envelope<AuthorityCapabilitiesV1, AuthoritySignature>;
106
107pub type VerifiedAuthorityCapabilitiesV1 =
108 VerifiedEnvelope<AuthorityCapabilitiesV1, AuthoritySignature>;
109
110#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
111pub struct AuthorityCapabilitiesDigest(Digest);
112
113impl AuthorityCapabilitiesDigest {
114 pub const fn new(digest: [u8; 32]) -> Self {
115 Self(Digest::new(digest))
116 }
117}
118
119impl Debug for AuthorityCapabilitiesDigest {
120 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
121 f.debug_tuple("AuthorityCapabilitiesDigest")
122 .field(&self.0)
123 .finish()
124 }
125}
126
127#[derive(Serialize, Deserialize, Clone, Hash)]
130pub struct AuthorityCapabilitiesV1 {
131 pub authority: AuthorityName,
134 pub generation: u64,
141
142 pub supported_protocol_versions: SupportedProtocolVersionsWithHashes,
145
146 pub available_system_packages: Vec<ObjectRef>,
150}
151
152impl Message for AuthorityCapabilitiesV1 {
153 type DigestType = AuthorityCapabilitiesDigest;
154 const SCOPE: IntentScope = IntentScope::AuthorityCapabilities;
155
156 fn digest(&self) -> Self::DigestType {
157 let mut hasher = DefaultHash::new();
159 let serialized = bcs::to_bytes(&self).expect("BCS should not fail");
160 hasher.update(&serialized);
161 AuthorityCapabilitiesDigest::new(<[u8; 32]>::from(hasher.finalize()))
162 }
163}
164
165impl Debug for AuthorityCapabilitiesV1 {
166 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
167 f.debug_struct("AuthorityCapabilities")
168 .field("authority", &self.authority.concise())
169 .field("generation", &self.generation)
170 .field(
171 "supported_protocol_versions",
172 &self.supported_protocol_versions,
173 )
174 .field("available_system_packages", &self.available_system_packages)
175 .finish()
176 }
177}
178
179impl AuthorityCapabilitiesV1 {
180 pub fn new(
181 authority: AuthorityName,
182 chain: Chain,
183 supported_protocol_versions: SupportedProtocolVersions,
184 available_system_packages: Vec<ObjectRef>,
185 ) -> Self {
186 let generation = SystemTime::now()
187 .duration_since(UNIX_EPOCH)
188 .expect("IOTA did not exist prior to 1970")
189 .as_millis()
190 .try_into()
191 .expect("This build of iota is not supported in the year 500,000,000");
192 Self {
193 authority,
194 generation,
195 supported_protocol_versions:
196 SupportedProtocolVersionsWithHashes::from_supported_versions(
197 supported_protocol_versions,
198 chain,
199 ),
200 available_system_packages,
201 }
202 }
203}
204
205impl SignedAuthorityCapabilitiesV1 {
206 pub fn cache_digest(&self, epoch: u64) -> AuthorityCapabilitiesDigest {
207 let data_with_epoch = (self.data(), epoch);
209
210 let mut hasher = DefaultHash::new();
212 let serialized = bcs::to_bytes(&data_with_epoch).expect("BCS should not fail");
213 hasher.update(&serialized);
214 AuthorityCapabilitiesDigest::new(<[u8; 32]>::from(hasher.finalize()))
215 }
216}
217
218#[derive(Serialize, Deserialize, Clone, Debug)]
219pub enum ConsensusTransactionKind {
220 CertifiedTransaction(Box<CertifiedTransaction>),
221 CheckpointSignature(Box<CheckpointSignatureMessage>),
222 EndOfPublish(AuthorityName),
223
224 CapabilityNotificationV1(AuthorityCapabilitiesV1),
225 SignedCapabilityNotificationV1(SignedAuthorityCapabilitiesV1),
226
227 #[deprecated(note = "Authenticator state (JWK) is deprecated and was never enabled on IOTA")]
228 NewJWKFetchedDeprecated,
229
230 RandomnessDkgMessage(AuthorityName, Vec<u8>),
234 RandomnessDkgConfirmation(AuthorityName, Vec<u8>),
238 MisbehaviorReport(VersionedMisbehaviorReport),
239 }
242
243impl ConsensusTransactionKind {
244 pub fn is_dkg(&self) -> bool {
245 matches!(
246 self,
247 ConsensusTransactionKind::RandomnessDkgMessage(_, _)
248 | ConsensusTransactionKind::RandomnessDkgConfirmation(_, _)
249 )
250 }
251}
252
253#[derive(Debug, Clone, Serialize, Deserialize)]
263pub struct VersionedMisbehaviorReport {
264 pub authority: AuthorityName,
267 pub payload: MisbehaviorObservations,
269 pub generation: u64,
273 #[serde(skip)]
274 digest: OnceCell<MisbehaviorReportDigest>,
275}
276
277#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
283pub enum MisbehaviorObservations {
284 V1(MisbehaviorObservationsV1),
285}
286
287impl VersionedMisbehaviorReport {
288 pub fn new_v1(
289 authority: AuthorityName,
290 generation: u64,
291 observations: MisbehaviorObservationsV1,
292 ) -> Self {
293 Self {
294 authority,
295 payload: MisbehaviorObservations::V1(observations),
296 generation,
297 digest: OnceCell::new(),
298 }
299 }
300
301 pub fn digest(&self) -> &MisbehaviorReportDigest {
304 self.digest
305 .get_or_init(|| MisbehaviorReportDigest::new(default_hash(self)))
306 }
307
308 pub fn summary(&self) -> u64 {
311 let summary = match &self.payload {
312 MisbehaviorObservations::V1(report) => [
313 &report.faulty_blocks_provable,
314 &report.faulty_blocks_unprovable,
315 &report.missing_proposals,
316 &report.equivocations,
317 ]
318 .into_iter()
319 .flatten()
320 .fold(0u64, |acc, metric| acc.saturating_add(*metric)),
321 };
322 if summary == u64::MAX {
323 warn!("MisbehaviorReport summary reached its maximum value.");
324 }
325 summary
326 }
327}
328
329#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
335pub struct MisbehaviorObservationsV1 {
336 pub faulty_blocks_provable: Vec<u64>,
337 pub faulty_blocks_unprovable: Vec<u64>,
338 pub missing_proposals: Vec<u64>,
339 pub equivocations: Vec<u64>,
340}
341
342impl MisbehaviorObservationsV1 {
343 pub fn verify(&self, committee_size: usize) -> bool {
344 if (self.faulty_blocks_provable.len() != committee_size)
352 || (self.faulty_blocks_unprovable.len() != committee_size)
353 || (self.equivocations.len() != committee_size)
354 || (self.missing_proposals.len() != committee_size)
355 {
356 return false;
357 }
358 true
359 }
360}
361
362#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
363pub enum VersionedDkgMessage {
364 V1(dkg_v1::Message<bls12381::G2Element, bls12381::G2Element>),
365}
366
367#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
368pub enum VersionedDkgConfirmation {
369 V1(dkg_v1::Confirmation<bls12381::G2Element>),
370}
371
372impl Debug for VersionedDkgMessage {
373 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
374 match self {
375 VersionedDkgMessage::V1(msg) => write!(
376 f,
377 "DKG V1 Message with sender={}, vss_pk.degree={}, encrypted_shares.len()={}",
378 msg.sender,
379 msg.vss_pk.degree(),
380 msg.encrypted_shares.len(),
381 ),
382 }
383 }
384}
385
386impl VersionedDkgMessage {
387 pub fn sender(&self) -> u16 {
388 match self {
389 VersionedDkgMessage::V1(msg) => msg.sender,
390 }
391 }
392
393 pub fn create(
394 dkg_version: u64,
395 party: Arc<dkg_v1::Party<bls12381::G2Element, bls12381::G2Element>>,
396 ) -> FastCryptoResult<VersionedDkgMessage> {
397 assert_eq!(dkg_version, 1, "BUG: invalid DKG version");
398 let msg = party.create_message(&mut rand::thread_rng())?;
399 Ok(VersionedDkgMessage::V1(msg))
400 }
401
402 pub fn unwrap_v1(self) -> dkg_v1::Message<bls12381::G2Element, bls12381::G2Element> {
403 match self {
404 VersionedDkgMessage::V1(msg) => msg,
405 }
406 }
407
408 pub fn is_valid_version(&self, dkg_version: u64) -> bool {
409 matches!((self, dkg_version), (VersionedDkgMessage::V1(_), 1))
410 }
411}
412
413impl VersionedDkgConfirmation {
414 pub fn sender(&self) -> u16 {
415 match self {
416 VersionedDkgConfirmation::V1(msg) => msg.sender,
417 }
418 }
419
420 pub fn num_of_complaints(&self) -> usize {
421 match self {
422 VersionedDkgConfirmation::V1(msg) => msg.complaints.len(),
423 }
424 }
425
426 pub fn unwrap_v1(&self) -> &dkg_v1::Confirmation<bls12381::G2Element> {
427 match self {
428 VersionedDkgConfirmation::V1(msg) => msg,
429 }
430 }
431
432 pub fn is_valid_version(&self, dkg_version: u64) -> bool {
433 matches!((self, dkg_version), (VersionedDkgConfirmation::V1(_), 1))
434 }
435}
436
437impl ConsensusTransaction {
438 pub fn new_certificate_message(
439 authority: &AuthorityName,
440 certificate: CertifiedTransaction,
441 ) -> Self {
442 let mut hasher = DefaultHasher::new();
443 let tx_digest = certificate.digest();
444 tx_digest.hash(&mut hasher);
445 authority.hash(&mut hasher);
446 let tracking_id = hasher.finish().to_le_bytes();
447 Self {
448 tracking_id,
449 kind: ConsensusTransactionKind::CertifiedTransaction(Box::new(certificate)),
450 }
451 }
452
453 pub fn new_checkpoint_signature_message(data: CheckpointSignatureMessage) -> Self {
454 let mut hasher = DefaultHasher::new();
455 data.summary.auth_sig().signature.hash(&mut hasher);
456 let tracking_id = hasher.finish().to_le_bytes();
457 Self {
458 tracking_id,
459 kind: ConsensusTransactionKind::CheckpointSignature(Box::new(data)),
460 }
461 }
462
463 pub fn new_end_of_publish(authority: AuthorityName) -> Self {
464 let mut hasher = DefaultHasher::new();
465 authority.hash(&mut hasher);
466 let tracking_id = hasher.finish().to_le_bytes();
467 Self {
468 tracking_id,
469 kind: ConsensusTransactionKind::EndOfPublish(authority),
470 }
471 }
472
473 pub fn new_capability_notification_v1(capabilities: AuthorityCapabilitiesV1) -> Self {
474 let mut hasher = DefaultHasher::new();
475 capabilities.hash(&mut hasher);
476 let tracking_id = hasher.finish().to_le_bytes();
477 Self {
478 tracking_id,
479 kind: ConsensusTransactionKind::CapabilityNotificationV1(capabilities),
480 }
481 }
482
483 pub fn new_signed_capability_notification_v1(
484 signed_capabilities: SignedAuthorityCapabilitiesV1,
485 ) -> Self {
486 let mut hasher = DefaultHasher::new();
487 signed_capabilities.data().hash(&mut hasher);
488 signed_capabilities.auth_sig().hash(&mut hasher);
489 let tracking_id = hasher.finish().to_le_bytes();
490 Self {
491 tracking_id,
492 kind: ConsensusTransactionKind::SignedCapabilityNotificationV1(signed_capabilities),
493 }
494 }
495
496 pub fn new_randomness_dkg_message(
497 authority: AuthorityName,
498 versioned_message: &VersionedDkgMessage,
499 ) -> Self {
500 let message =
501 bcs::to_bytes(versioned_message).expect("message serialization should not fail");
502 let mut hasher = DefaultHasher::new();
503 message.hash(&mut hasher);
504 let tracking_id = hasher.finish().to_le_bytes();
505 Self {
506 tracking_id,
507 kind: ConsensusTransactionKind::RandomnessDkgMessage(authority, message),
508 }
509 }
510 pub fn new_randomness_dkg_confirmation(
511 authority: AuthorityName,
512 versioned_confirmation: &VersionedDkgConfirmation,
513 ) -> Self {
514 let confirmation =
515 bcs::to_bytes(versioned_confirmation).expect("message serialization should not fail");
516 let mut hasher = DefaultHasher::new();
517 confirmation.hash(&mut hasher);
518 let tracking_id = hasher.finish().to_le_bytes();
519 Self {
520 tracking_id,
521 kind: ConsensusTransactionKind::RandomnessDkgConfirmation(authority, confirmation),
522 }
523 }
524
525 pub fn new_misbehavior_report(report: VersionedMisbehaviorReport) -> Self {
526 let serialized_report =
527 bcs::to_bytes(&report).expect("report serialization should not fail");
528 let mut hasher = DefaultHasher::new();
529 serialized_report.hash(&mut hasher);
530 let tracking_id = hasher.finish().to_le_bytes();
531 Self {
532 tracking_id,
533 kind: ConsensusTransactionKind::MisbehaviorReport(report),
534 }
535 }
536
537 pub fn get_tracking_id(&self) -> u64 {
538 (&self.tracking_id[..])
539 .read_u64::<BigEndian>()
540 .unwrap_or_default()
541 }
542
543 pub fn key(&self) -> ConsensusTransactionKey {
544 match &self.kind {
545 ConsensusTransactionKind::CertifiedTransaction(cert) => {
546 ConsensusTransactionKey::Certificate(*cert.digest())
547 }
548 ConsensusTransactionKind::CheckpointSignature(data) => {
549 ConsensusTransactionKey::CheckpointSignature(
550 data.summary.auth_sig().authority,
551 data.summary.sequence_number,
552 )
553 }
554 ConsensusTransactionKind::EndOfPublish(authority) => {
555 ConsensusTransactionKey::EndOfPublish(*authority)
556 }
557 ConsensusTransactionKind::CapabilityNotificationV1(cap) => {
558 ConsensusTransactionKey::CapabilityNotification(cap.authority, cap.generation)
559 }
560 ConsensusTransactionKind::SignedCapabilityNotificationV1(signed_cap) => {
561 ConsensusTransactionKey::CapabilityNotification(
562 signed_cap.authority,
563 signed_cap.generation,
564 )
565 }
566
567 #[allow(deprecated)]
568 ConsensusTransactionKind::NewJWKFetchedDeprecated => {
569 ConsensusTransactionKey::NewJWKFetchedDeprecated
570 }
571 ConsensusTransactionKind::RandomnessDkgMessage(authority, _) => {
572 ConsensusTransactionKey::RandomnessDkgMessage(*authority)
573 }
574 ConsensusTransactionKind::RandomnessDkgConfirmation(authority, _) => {
575 ConsensusTransactionKey::RandomnessDkgConfirmation(*authority)
576 }
577 ConsensusTransactionKind::MisbehaviorReport(report) => {
578 ConsensusTransactionKey::MisbehaviorReport(
579 report.authority,
580 *report.digest(),
581 report.generation,
582 )
583 }
584 }
585 }
586
587 pub fn is_user_certificate(&self) -> bool {
588 matches!(self.kind, ConsensusTransactionKind::CertifiedTransaction(_))
589 }
590
591 pub fn is_end_of_publish(&self) -> bool {
592 matches!(self.kind, ConsensusTransactionKind::EndOfPublish(_))
593 }
594}
595
596#[cfg(test)]
597mod tests {
598 use super::*;
599
600 #[derive(Serialize)]
604 struct LegacyVersionedMisbehaviorReport<'a> {
605 payload: &'a MisbehaviorObservations,
606 }
607
608 fn sample_payload() -> MisbehaviorObservations {
609 MisbehaviorObservations::V1(MisbehaviorObservationsV1 {
610 faulty_blocks_provable: vec![1, 2, 3],
611 faulty_blocks_unprovable: vec![4, 5, 6],
612 missing_proposals: vec![7, 8, 9],
613 equivocations: vec![10, 11, 12],
614 })
615 }
616
617 #[test]
625 fn misbehavior_report_wire_format_unchanged() {
626 let authority = AuthorityName::default();
627 let generation: u64 = 42;
628 let payload = sample_payload();
629
630 let legacy_bytes = bcs::to_bytes(&(
631 authority,
632 LegacyVersionedMisbehaviorReport { payload: &payload },
633 generation,
634 ))
635 .unwrap();
636
637 let new = VersionedMisbehaviorReport {
638 authority,
639 payload,
640 generation,
641 digest: OnceCell::new(),
642 };
643 let new_bytes = bcs::to_bytes(&new).unwrap();
644
645 assert_eq!(
646 legacy_bytes, new_bytes,
647 "VersionedMisbehaviorReport wire format must not change — testnet is live"
648 );
649 }
650
651 #[test]
658 fn misbehavior_report_consensus_kind_wire_format_unchanged() {
659 let authority = AuthorityName::default();
660 let generation: u64 = 7;
661 let payload = sample_payload();
662
663 let new_kind = ConsensusTransactionKind::MisbehaviorReport(VersionedMisbehaviorReport {
664 authority,
665 payload: payload.clone(),
666 generation,
667 digest: OnceCell::new(),
668 });
669 let new_bytes = bcs::to_bytes(&new_kind).unwrap();
670
671 let mut legacy_bytes = vec![8u8];
674 legacy_bytes.extend(
675 bcs::to_bytes(&(
676 authority,
677 LegacyVersionedMisbehaviorReport { payload: &payload },
678 generation,
679 ))
680 .unwrap(),
681 );
682
683 assert_eq!(
684 legacy_bytes, new_bytes,
685 "ConsensusTransactionKind::MisbehaviorReport wire format must not change — testnet is live"
686 );
687 }
688}