sui_types/
messages_consensus.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use crate::base_types::{AuthorityName, ConsensusObjectSequenceKey, ObjectRef, TransactionDigest};
5use crate::base_types::{ConciseableName, ObjectID, SequenceNumber};
6use crate::committee::EpochId;
7use crate::digests::{AdditionalConsensusStateDigest, ConsensusCommitDigest};
8use crate::error::{SuiError, SuiErrorKind};
9use crate::execution::ExecutionTimeObservationKey;
10use crate::messages_checkpoint::{
11    CheckpointDigest, CheckpointSequenceNumber, CheckpointSignatureMessage,
12};
13use crate::supported_protocol_versions::{
14    Chain, SupportedProtocolVersions, SupportedProtocolVersionsWithHashes,
15};
16use crate::transaction::{CertifiedTransaction, Transaction};
17use byteorder::{BigEndian, ReadBytesExt};
18use bytes::Bytes;
19use consensus_types::block::{BlockRef, PING_TRANSACTION_INDEX, TransactionIndex};
20use fastcrypto::error::FastCryptoResult;
21use fastcrypto::groups::bls12381;
22use fastcrypto_tbls::dkg_v1;
23use fastcrypto_zkp::bn254::zk_login::{JWK, JwkId};
24use schemars::JsonSchema;
25use serde::{Deserialize, Serialize};
26use std::collections::hash_map::DefaultHasher;
27use std::fmt::{Debug, Formatter};
28use std::hash::{Hash, Hasher};
29use std::sync::Arc;
30use std::time::{Duration, SystemTime, UNIX_EPOCH};
31
32/// The index of an authority in the consensus committee.
33/// The value should be the same in Sui committee.
34pub type AuthorityIndex = u32;
35
36// TODO: Switch to using consensus_types::block::Round?
37/// Consensus round number in u64 instead of u32 for compatibility with Narwhal.
38pub type Round = u64;
39
40// TODO: Switch to using consensus_types::block::BlockTimestampMs?
41/// Non-decreasing timestamp produced by consensus in ms.
42pub type TimestampMs = u64;
43
44/// The position of a transaction in consensus.
45#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
46pub struct ConsensusPosition {
47    // Epoch of the consensus instance.
48    pub epoch: EpochId,
49    // Block containing a transaction.
50    pub block: BlockRef,
51    // Index of the transaction in the block.
52    pub index: TransactionIndex,
53}
54
55impl ConsensusPosition {
56    pub fn into_raw(self) -> Result<Bytes, SuiError> {
57        bcs::to_bytes(&self)
58            .map_err(|e| {
59                SuiErrorKind::GrpcMessageSerializeError {
60                    type_info: "ConsensusPosition".to_string(),
61                    error: e.to_string(),
62                }
63                .into()
64            })
65            .map(Bytes::from)
66    }
67
68    // We reserve the max index for the "ping" transaction. This transaction is not included in the block, but we are
69    // simulating by assuming its position in the block as the max index.
70    pub fn ping(epoch: EpochId, block: BlockRef) -> Self {
71        Self {
72            epoch,
73            block,
74            index: PING_TRANSACTION_INDEX,
75        }
76    }
77}
78
79impl TryFrom<&[u8]> for ConsensusPosition {
80    type Error = SuiError;
81
82    fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
83        bcs::from_bytes(bytes).map_err(|e| {
84            SuiErrorKind::GrpcMessageDeserializeError {
85                type_info: "ConsensusPosition".to_string(),
86                error: e.to_string(),
87            }
88            .into()
89        })
90    }
91}
92
93impl std::fmt::Display for ConsensusPosition {
94    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
95        write!(f, "P(E{}, {}, {})", self.epoch, self.block, self.index)
96    }
97}
98
99impl std::fmt::Debug for ConsensusPosition {
100    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
101        write!(f, "P(E{}, {:?}, {})", self.epoch, self.block, self.index)
102    }
103}
104
105/// Only commit_timestamp_ms is passed to the move call currently.
106/// However we include epoch and round to make sure each ConsensusCommitPrologue has a unique tx digest.
107#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
108pub struct ConsensusCommitPrologue {
109    /// Epoch of the commit prologue transaction
110    pub epoch: u64,
111    /// Consensus round of the commit. Using u64 for compatibility.
112    pub round: u64,
113    /// Unix timestamp from consensus commit.
114    pub commit_timestamp_ms: TimestampMs,
115}
116
117#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
118pub struct ConsensusCommitPrologueV2 {
119    /// Epoch of the commit prologue transaction
120    pub epoch: u64,
121    /// Consensus round of the commit
122    pub round: u64,
123    /// Unix timestamp from consensus commit.
124    pub commit_timestamp_ms: TimestampMs,
125    /// Digest of consensus output
126    pub consensus_commit_digest: ConsensusCommitDigest,
127}
128
129/// Uses an enum to allow for future expansion of the ConsensusDeterminedVersionAssignments.
130#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize, JsonSchema)]
131pub enum ConsensusDeterminedVersionAssignments {
132    // Cancelled transaction version assignment.
133    CancelledTransactions(Vec<(TransactionDigest, Vec<(ObjectID, SequenceNumber)>)>),
134    CancelledTransactionsV2(
135        Vec<(
136            TransactionDigest,
137            Vec<(ConsensusObjectSequenceKey, SequenceNumber)>,
138        )>,
139    ),
140}
141
142impl ConsensusDeterminedVersionAssignments {
143    pub fn empty_for_testing() -> Self {
144        Self::CancelledTransactions(Vec::new())
145    }
146}
147
148#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
149pub struct ConsensusCommitPrologueV3 {
150    /// Epoch of the commit prologue transaction
151    pub epoch: u64,
152    /// Consensus round of the commit
153    pub round: u64,
154    /// The sub DAG index of the consensus commit. This field will be populated if there
155    /// are multiple consensus commits per round.
156    pub sub_dag_index: Option<u64>,
157    /// Unix timestamp from consensus commit.
158    pub commit_timestamp_ms: TimestampMs,
159    /// Digest of consensus output
160    pub consensus_commit_digest: ConsensusCommitDigest,
161    /// Stores consensus handler determined shared object version assignments.
162    pub consensus_determined_version_assignments: ConsensusDeterminedVersionAssignments,
163}
164
165#[derive(Debug, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
166pub struct ConsensusCommitPrologueV4 {
167    /// Epoch of the commit prologue transaction
168    pub epoch: u64,
169    /// Consensus round of the commit
170    pub round: u64,
171    /// The sub DAG index of the consensus commit. This field will be populated if there
172    /// are multiple consensus commits per round.
173    pub sub_dag_index: Option<u64>,
174    /// Unix timestamp from consensus commit.
175    pub commit_timestamp_ms: TimestampMs,
176    /// Digest of consensus output
177    pub consensus_commit_digest: ConsensusCommitDigest,
178    /// Stores consensus handler determined shared object version assignments.
179    pub consensus_determined_version_assignments: ConsensusDeterminedVersionAssignments,
180    /// Digest of any additional state computed by the consensus handler.
181    /// Used to detect forking bugs as early as possible.
182    pub additional_state_digest: AdditionalConsensusStateDigest,
183}
184
185// In practice, JWKs are about 500 bytes of json each, plus a bit more for the ID.
186// 4096 should give us plenty of space for any imaginable JWK while preventing DoSes.
187static MAX_TOTAL_JWK_SIZE: usize = 4096;
188
189pub fn check_total_jwk_size(id: &JwkId, jwk: &JWK) -> bool {
190    id.iss.len() + id.kid.len() + jwk.kty.len() + jwk.alg.len() + jwk.e.len() + jwk.n.len()
191        <= MAX_TOTAL_JWK_SIZE
192}
193
194#[derive(Serialize, Deserialize, Clone, Debug)]
195pub struct ConsensusTransaction {
196    /// Encodes an u64 unique tracking id to allow us trace a message between Sui and consensus.
197    /// Use an byte array instead of u64 to ensure stable serialization.
198    pub tracking_id: [u8; 8],
199    pub kind: ConsensusTransactionKind,
200}
201
202impl ConsensusTransaction {
203    /// Displays a ConsensusTransaction created locally by the validator, for example during submission to consensus.
204    pub fn local_display(&self) -> String {
205        match &self.kind {
206            ConsensusTransactionKind::CertifiedTransaction(cert) => {
207                format!("Certified({})", cert.digest())
208            }
209            ConsensusTransactionKind::CheckpointSignature(data) => {
210                format!(
211                    "CkptSig({}, {})",
212                    data.summary.sequence_number,
213                    data.summary.digest()
214                )
215            }
216            ConsensusTransactionKind::CheckpointSignatureV2(data) => {
217                format!(
218                    "CkptSigV2({}, {})",
219                    data.summary.sequence_number,
220                    data.summary.digest()
221                )
222            }
223            ConsensusTransactionKind::EndOfPublish(..) => "EOP".to_string(),
224            ConsensusTransactionKind::CapabilityNotification(..) => "Cap".to_string(),
225            ConsensusTransactionKind::CapabilityNotificationV2(..) => "CapV2".to_string(),
226            ConsensusTransactionKind::NewJWKFetched(..) => "NewJWKFetched".to_string(),
227            ConsensusTransactionKind::RandomnessStateUpdate(..) => "RandStateUpdate".to_string(),
228            ConsensusTransactionKind::RandomnessDkgMessage(..) => "RandDkg".to_string(),
229            ConsensusTransactionKind::RandomnessDkgConfirmation(..) => "RandDkgConf".to_string(),
230            ConsensusTransactionKind::ExecutionTimeObservation(..) => "ExecTimeOb".to_string(),
231            ConsensusTransactionKind::UserTransaction(tx) => {
232                format!("User({})", tx.digest())
233            }
234        }
235    }
236}
237
238// Serialized ordinally - always append to end of enum
239#[derive(Serialize, Deserialize, Clone, Hash, PartialEq, Eq, Ord, PartialOrd)]
240pub enum ConsensusTransactionKey {
241    Certificate(TransactionDigest),
242    // V1: dedup by authority + sequence only (no digest)
243    CheckpointSignature(AuthorityName, CheckpointSequenceNumber),
244    EndOfPublish(AuthorityName),
245    CapabilityNotification(AuthorityName, u64 /* generation */),
246    // Key must include both id and jwk, because honest validators could be given multiple jwks for
247    // the same id by malfunctioning providers.
248    NewJWKFetched(Box<(AuthorityName, JwkId, JWK)>),
249    RandomnessDkgMessage(AuthorityName),
250    RandomnessDkgConfirmation(AuthorityName),
251    ExecutionTimeObservation(AuthorityName, u64 /* generation */),
252    // V2: dedup by authority + sequence + digest
253    CheckpointSignatureV2(AuthorityName, CheckpointSequenceNumber, CheckpointDigest),
254}
255
256impl Debug for ConsensusTransactionKey {
257    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
258        match self {
259            Self::Certificate(digest) => write!(f, "Certificate({:?})", digest),
260            Self::CheckpointSignature(name, seq) => {
261                write!(f, "CheckpointSignature({:?}, {:?})", name.concise(), seq)
262            }
263            Self::CheckpointSignatureV2(name, seq, digest) => write!(
264                f,
265                "CheckpointSignatureV2({:?}, {:?}, {:?})",
266                name.concise(),
267                seq,
268                digest
269            ),
270            Self::EndOfPublish(name) => write!(f, "EndOfPublish({:?})", name.concise()),
271            Self::CapabilityNotification(name, generation) => write!(
272                f,
273                "CapabilityNotification({:?}, {:?})",
274                name.concise(),
275                generation
276            ),
277            Self::NewJWKFetched(key) => {
278                let (authority, id, jwk) = &**key;
279                write!(
280                    f,
281                    "NewJWKFetched({:?}, {:?}, {:?})",
282                    authority.concise(),
283                    id,
284                    jwk
285                )
286            }
287            Self::RandomnessDkgMessage(name) => {
288                write!(f, "RandomnessDkgMessage({:?})", name.concise())
289            }
290            Self::RandomnessDkgConfirmation(name) => {
291                write!(f, "RandomnessDkgConfirmation({:?})", name.concise())
292            }
293            Self::ExecutionTimeObservation(name, generation) => {
294                write!(
295                    f,
296                    "ExecutionTimeObservation({:?}, {generation:?})",
297                    name.concise()
298                )
299            }
300        }
301    }
302}
303
304/// Used to advertise capabilities of each authority via consensus. This allows validators to
305/// negotiate the creation of the ChangeEpoch transaction.
306#[derive(Serialize, Deserialize, Clone, Hash)]
307pub struct AuthorityCapabilitiesV1 {
308    /// Originating authority - must match consensus transaction source.
309    pub authority: AuthorityName,
310    /// Generation number set by sending authority. Used to determine which of multiple
311    /// AuthorityCapabilities messages from the same authority is the most recent.
312    ///
313    /// (Currently, we just set this to the current time in milliseconds since the epoch, but this
314    /// should not be interpreted as a timestamp.)
315    pub generation: u64,
316
317    /// ProtocolVersions that the authority supports.
318    pub supported_protocol_versions: SupportedProtocolVersions,
319
320    /// The ObjectRefs of all versions of system packages that the validator possesses.
321    /// Used to determine whether to do a framework/movestdlib upgrade.
322    pub available_system_packages: Vec<ObjectRef>,
323}
324
325impl Debug for AuthorityCapabilitiesV1 {
326    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
327        f.debug_struct("AuthorityCapabilities")
328            .field("authority", &self.authority.concise())
329            .field("generation", &self.generation)
330            .field(
331                "supported_protocol_versions",
332                &self.supported_protocol_versions,
333            )
334            .field("available_system_packages", &self.available_system_packages)
335            .finish()
336    }
337}
338
339impl AuthorityCapabilitiesV1 {
340    pub fn new(
341        authority: AuthorityName,
342        supported_protocol_versions: SupportedProtocolVersions,
343        available_system_packages: Vec<ObjectRef>,
344    ) -> Self {
345        let generation = SystemTime::now()
346            .duration_since(UNIX_EPOCH)
347            .expect("Sui did not exist prior to 1970")
348            .as_millis()
349            .try_into()
350            .expect("This build of sui is not supported in the year 500,000,000");
351        Self {
352            authority,
353            generation,
354            supported_protocol_versions,
355            available_system_packages,
356        }
357    }
358}
359
360/// Used to advertise capabilities of each authority via consensus. This allows validators to
361/// negotiate the creation of the ChangeEpoch transaction.
362#[derive(Serialize, Deserialize, Clone, Hash)]
363pub struct AuthorityCapabilitiesV2 {
364    /// Originating authority - must match transaction source authority from consensus.
365    pub authority: AuthorityName,
366    /// Generation number set by sending authority. Used to determine which of multiple
367    /// AuthorityCapabilities messages from the same authority is the most recent.
368    ///
369    /// (Currently, we just set this to the current time in milliseconds since the epoch, but this
370    /// should not be interpreted as a timestamp.)
371    pub generation: u64,
372
373    /// ProtocolVersions that the authority supports.
374    pub supported_protocol_versions: SupportedProtocolVersionsWithHashes,
375
376    /// The ObjectRefs of all versions of system packages that the validator possesses.
377    /// Used to determine whether to do a framework/movestdlib upgrade.
378    pub available_system_packages: Vec<ObjectRef>,
379}
380
381impl Debug for AuthorityCapabilitiesV2 {
382    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
383        f.debug_struct("AuthorityCapabilities")
384            .field("authority", &self.authority.concise())
385            .field("generation", &self.generation)
386            .field(
387                "supported_protocol_versions",
388                &self.supported_protocol_versions,
389            )
390            .field("available_system_packages", &self.available_system_packages)
391            .finish()
392    }
393}
394
395impl AuthorityCapabilitiesV2 {
396    pub fn new(
397        authority: AuthorityName,
398        chain: Chain,
399        supported_protocol_versions: SupportedProtocolVersions,
400        available_system_packages: Vec<ObjectRef>,
401    ) -> Self {
402        let generation = SystemTime::now()
403            .duration_since(UNIX_EPOCH)
404            .expect("Sui did not exist prior to 1970")
405            .as_millis()
406            .try_into()
407            .expect("This build of sui is not supported in the year 500,000,000");
408        Self {
409            authority,
410            generation,
411            supported_protocol_versions:
412                SupportedProtocolVersionsWithHashes::from_supported_versions(
413                    supported_protocol_versions,
414                    chain,
415                ),
416            available_system_packages,
417        }
418    }
419}
420
421/// Used to share estimates of transaction execution times with other validators for
422/// congestion control.
423#[derive(Debug, Default, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
424pub struct ExecutionTimeObservation {
425    /// Originating authority - must match transaction source authority from consensus.
426    pub authority: AuthorityName,
427    /// Generation number set by sending authority. Used to determine which of multiple
428    /// ExecutionTimeObservation messages from the same authority is the most recent.
429    pub generation: u64,
430
431    /// Estimated execution durations by key.
432    pub estimates: Vec<(ExecutionTimeObservationKey, Duration)>,
433}
434
435impl ExecutionTimeObservation {
436    pub fn new(
437        authority: AuthorityName,
438        generation: u64,
439        estimates: Vec<(ExecutionTimeObservationKey, Duration)>,
440    ) -> Self {
441        Self {
442            authority,
443            generation,
444            estimates,
445        }
446    }
447}
448
449#[derive(Serialize, Deserialize, Clone, Debug)]
450pub enum ConsensusTransactionKind {
451    CertifiedTransaction(Box<CertifiedTransaction>),
452    // V1: dedup by authority + sequence only
453    CheckpointSignature(Box<CheckpointSignatureMessage>),
454    EndOfPublish(AuthorityName),
455
456    CapabilityNotification(AuthorityCapabilitiesV1),
457
458    NewJWKFetched(AuthorityName, JwkId, JWK),
459    RandomnessStateUpdate(u64, Vec<u8>), // deprecated
460    // DKG is used to generate keys for use in the random beacon protocol.
461    // `RandomnessDkgMessage` is sent out at start-of-epoch to initiate the process.
462    // Contents are a serialized `fastcrypto_tbls::dkg::Message`.
463    RandomnessDkgMessage(AuthorityName, Vec<u8>),
464    // `RandomnessDkgConfirmation` is the second DKG message, sent as soon as a threshold amount of
465    // `RandomnessDkgMessages` have been received locally, to complete the key generation process.
466    // Contents are a serialized `fastcrypto_tbls::dkg::Confirmation`.
467    RandomnessDkgConfirmation(AuthorityName, Vec<u8>),
468
469    CapabilityNotificationV2(AuthorityCapabilitiesV2),
470
471    UserTransaction(Box<Transaction>),
472
473    ExecutionTimeObservation(ExecutionTimeObservation),
474    // V2: dedup by authority + sequence + digest
475    CheckpointSignatureV2(Box<CheckpointSignatureMessage>),
476}
477
478impl ConsensusTransactionKind {}
479
480#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
481#[allow(clippy::large_enum_variant)]
482pub enum VersionedDkgMessage {
483    V0(), // deprecated
484    V1(dkg_v1::Message<bls12381::G2Element, bls12381::G2Element>),
485}
486
487#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
488pub enum VersionedDkgConfirmation {
489    V0(), // deprecated
490    V1(dkg_v1::Confirmation<bls12381::G2Element>),
491}
492
493impl Debug for VersionedDkgMessage {
494    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
495        match self {
496            VersionedDkgMessage::V0() => write!(f, "Deprecated VersionedDkgMessage version 0"),
497            VersionedDkgMessage::V1(msg) => write!(
498                f,
499                "DKG V1 Message with sender={}, vss_pk.degree={}, encrypted_shares.len()={}",
500                msg.sender,
501                msg.vss_pk.degree(),
502                msg.encrypted_shares.len(),
503            ),
504        }
505    }
506}
507
508impl VersionedDkgMessage {
509    pub fn sender(&self) -> u16 {
510        match self {
511            VersionedDkgMessage::V0() => panic!("BUG: invalid VersionedDkgMessage version"),
512            VersionedDkgMessage::V1(msg) => msg.sender,
513        }
514    }
515
516    pub fn create(
517        dkg_version: u64,
518        party: Arc<dkg_v1::Party<bls12381::G2Element, bls12381::G2Element>>,
519    ) -> FastCryptoResult<VersionedDkgMessage> {
520        assert_eq!(dkg_version, 1, "BUG: invalid DKG version");
521        let msg = party.create_message(&mut rand::thread_rng())?;
522        Ok(VersionedDkgMessage::V1(msg))
523    }
524
525    pub fn unwrap_v1(self) -> dkg_v1::Message<bls12381::G2Element, bls12381::G2Element> {
526        match self {
527            VersionedDkgMessage::V1(msg) => msg,
528            _ => panic!("BUG: expected V1 message"),
529        }
530    }
531
532    pub fn is_valid_version(&self, dkg_version: u64) -> bool {
533        matches!((self, dkg_version), (VersionedDkgMessage::V1(_), 1))
534    }
535}
536
537impl VersionedDkgConfirmation {
538    pub fn sender(&self) -> u16 {
539        match self {
540            VersionedDkgConfirmation::V0() => {
541                panic!("BUG: invalid VersionedDkgConfirmation version")
542            }
543            VersionedDkgConfirmation::V1(msg) => msg.sender,
544        }
545    }
546
547    pub fn num_of_complaints(&self) -> usize {
548        match self {
549            VersionedDkgConfirmation::V0() => {
550                panic!("BUG: invalid VersionedDkgConfirmation version")
551            }
552            VersionedDkgConfirmation::V1(msg) => msg.complaints.len(),
553        }
554    }
555
556    pub fn unwrap_v1(&self) -> &dkg_v1::Confirmation<bls12381::G2Element> {
557        match self {
558            VersionedDkgConfirmation::V1(msg) => msg,
559            _ => panic!("BUG: expected V1 confirmation"),
560        }
561    }
562
563    pub fn is_valid_version(&self, dkg_version: u64) -> bool {
564        matches!((self, dkg_version), (VersionedDkgConfirmation::V1(_), 1))
565    }
566}
567
568impl ConsensusTransaction {
569    pub fn new_certificate_message(
570        authority: &AuthorityName,
571        certificate: CertifiedTransaction,
572    ) -> Self {
573        let mut hasher = DefaultHasher::new();
574        let tx_digest = certificate.digest();
575        tx_digest.hash(&mut hasher);
576        authority.hash(&mut hasher);
577        let tracking_id = hasher.finish().to_le_bytes();
578        Self {
579            tracking_id,
580            kind: ConsensusTransactionKind::CertifiedTransaction(Box::new(certificate)),
581        }
582    }
583
584    pub fn new_user_transaction_message(authority: &AuthorityName, tx: Transaction) -> Self {
585        let mut hasher = DefaultHasher::new();
586        let tx_digest = tx.digest();
587        tx_digest.hash(&mut hasher);
588        authority.hash(&mut hasher);
589        let tracking_id = hasher.finish().to_le_bytes();
590        Self {
591            tracking_id,
592            kind: ConsensusTransactionKind::UserTransaction(Box::new(tx)),
593        }
594    }
595
596    pub fn new_checkpoint_signature_message(data: CheckpointSignatureMessage) -> Self {
597        let mut hasher = DefaultHasher::new();
598        data.summary.auth_sig().signature.hash(&mut hasher);
599        let tracking_id = hasher.finish().to_le_bytes();
600        Self {
601            tracking_id,
602            kind: ConsensusTransactionKind::CheckpointSignature(Box::new(data)),
603        }
604    }
605
606    pub fn new_checkpoint_signature_message_v2(data: CheckpointSignatureMessage) -> Self {
607        let mut hasher = DefaultHasher::new();
608        data.summary.auth_sig().signature.hash(&mut hasher);
609        let tracking_id = hasher.finish().to_le_bytes();
610        Self {
611            tracking_id,
612            kind: ConsensusTransactionKind::CheckpointSignatureV2(Box::new(data)),
613        }
614    }
615
616    pub fn new_end_of_publish(authority: AuthorityName) -> Self {
617        let mut hasher = DefaultHasher::new();
618        authority.hash(&mut hasher);
619        let tracking_id = hasher.finish().to_le_bytes();
620        Self {
621            tracking_id,
622            kind: ConsensusTransactionKind::EndOfPublish(authority),
623        }
624    }
625
626    pub fn new_capability_notification(capabilities: AuthorityCapabilitiesV1) -> Self {
627        let mut hasher = DefaultHasher::new();
628        capabilities.hash(&mut hasher);
629        let tracking_id = hasher.finish().to_le_bytes();
630        Self {
631            tracking_id,
632            kind: ConsensusTransactionKind::CapabilityNotification(capabilities),
633        }
634    }
635
636    pub fn new_capability_notification_v2(capabilities: AuthorityCapabilitiesV2) -> Self {
637        let mut hasher = DefaultHasher::new();
638        capabilities.hash(&mut hasher);
639        let tracking_id = hasher.finish().to_le_bytes();
640        Self {
641            tracking_id,
642            kind: ConsensusTransactionKind::CapabilityNotificationV2(capabilities),
643        }
644    }
645
646    pub fn new_mysticeti_certificate(
647        round: u64,
648        offset: u64,
649        certificate: CertifiedTransaction,
650    ) -> Self {
651        let mut hasher = DefaultHasher::new();
652        let tx_digest = certificate.digest();
653        tx_digest.hash(&mut hasher);
654        round.hash(&mut hasher);
655        offset.hash(&mut hasher);
656        let tracking_id = hasher.finish().to_le_bytes();
657        Self {
658            tracking_id,
659            kind: ConsensusTransactionKind::CertifiedTransaction(Box::new(certificate)),
660        }
661    }
662
663    pub fn new_jwk_fetched(authority: AuthorityName, id: JwkId, jwk: JWK) -> Self {
664        let mut hasher = DefaultHasher::new();
665        id.hash(&mut hasher);
666        let tracking_id = hasher.finish().to_le_bytes();
667        Self {
668            tracking_id,
669            kind: ConsensusTransactionKind::NewJWKFetched(authority, id, jwk),
670        }
671    }
672
673    pub fn new_randomness_dkg_message(
674        authority: AuthorityName,
675        versioned_message: &VersionedDkgMessage,
676    ) -> Self {
677        let message =
678            bcs::to_bytes(versioned_message).expect("message serialization should not fail");
679        let mut hasher = DefaultHasher::new();
680        message.hash(&mut hasher);
681        let tracking_id = hasher.finish().to_le_bytes();
682        Self {
683            tracking_id,
684            kind: ConsensusTransactionKind::RandomnessDkgMessage(authority, message),
685        }
686    }
687    pub fn new_randomness_dkg_confirmation(
688        authority: AuthorityName,
689        versioned_confirmation: &VersionedDkgConfirmation,
690    ) -> Self {
691        let confirmation =
692            bcs::to_bytes(versioned_confirmation).expect("message serialization should not fail");
693        let mut hasher = DefaultHasher::new();
694        confirmation.hash(&mut hasher);
695        let tracking_id = hasher.finish().to_le_bytes();
696        Self {
697            tracking_id,
698            kind: ConsensusTransactionKind::RandomnessDkgConfirmation(authority, confirmation),
699        }
700    }
701
702    pub fn new_execution_time_observation(observation: ExecutionTimeObservation) -> Self {
703        let mut hasher = DefaultHasher::new();
704        observation.hash(&mut hasher);
705        let tracking_id = hasher.finish().to_le_bytes();
706        Self {
707            tracking_id,
708            kind: ConsensusTransactionKind::ExecutionTimeObservation(observation),
709        }
710    }
711
712    pub fn get_tracking_id(&self) -> u64 {
713        (&self.tracking_id[..])
714            .read_u64::<BigEndian>()
715            .unwrap_or_default()
716    }
717
718    pub fn key(&self) -> ConsensusTransactionKey {
719        match &self.kind {
720            ConsensusTransactionKind::CertifiedTransaction(cert) => {
721                ConsensusTransactionKey::Certificate(*cert.digest())
722            }
723            ConsensusTransactionKind::CheckpointSignature(data) => {
724                ConsensusTransactionKey::CheckpointSignature(
725                    data.summary.auth_sig().authority,
726                    data.summary.sequence_number,
727                )
728            }
729            ConsensusTransactionKind::CheckpointSignatureV2(data) => {
730                ConsensusTransactionKey::CheckpointSignatureV2(
731                    data.summary.auth_sig().authority,
732                    data.summary.sequence_number,
733                    *data.summary.digest(),
734                )
735            }
736            ConsensusTransactionKind::EndOfPublish(authority) => {
737                ConsensusTransactionKey::EndOfPublish(*authority)
738            }
739            ConsensusTransactionKind::CapabilityNotification(cap) => {
740                ConsensusTransactionKey::CapabilityNotification(cap.authority, cap.generation)
741            }
742            ConsensusTransactionKind::CapabilityNotificationV2(cap) => {
743                ConsensusTransactionKey::CapabilityNotification(cap.authority, cap.generation)
744            }
745            ConsensusTransactionKind::NewJWKFetched(authority, id, key) => {
746                ConsensusTransactionKey::NewJWKFetched(Box::new((
747                    *authority,
748                    id.clone(),
749                    key.clone(),
750                )))
751            }
752            ConsensusTransactionKind::RandomnessStateUpdate(_, _) => {
753                unreachable!(
754                    "there should never be a RandomnessStateUpdate with SequencedConsensusTransactionKind::External"
755                )
756            }
757            ConsensusTransactionKind::RandomnessDkgMessage(authority, _) => {
758                ConsensusTransactionKey::RandomnessDkgMessage(*authority)
759            }
760            ConsensusTransactionKind::RandomnessDkgConfirmation(authority, _) => {
761                ConsensusTransactionKey::RandomnessDkgConfirmation(*authority)
762            }
763            ConsensusTransactionKind::UserTransaction(tx) => {
764                // Use the same key format as ConsensusTransactionKind::CertifiedTransaction,
765                // because existing usages of ConsensusTransactionKey should not differentiate
766                // between CertifiedTransaction and UserTransaction.
767                ConsensusTransactionKey::Certificate(*tx.digest())
768            }
769            ConsensusTransactionKind::ExecutionTimeObservation(msg) => {
770                ConsensusTransactionKey::ExecutionTimeObservation(msg.authority, msg.generation)
771            }
772        }
773    }
774
775    pub fn is_dkg(&self) -> bool {
776        matches!(
777            self.kind,
778            ConsensusTransactionKind::RandomnessDkgMessage(_, _)
779                | ConsensusTransactionKind::RandomnessDkgConfirmation(_, _)
780        )
781    }
782
783    pub fn is_mfp_transaction(&self) -> bool {
784        matches!(self.kind, ConsensusTransactionKind::UserTransaction(_))
785    }
786
787    pub fn is_user_transaction(&self) -> bool {
788        matches!(
789            self.kind,
790            ConsensusTransactionKind::UserTransaction(_)
791                | ConsensusTransactionKind::CertifiedTransaction(_)
792        )
793    }
794
795    pub fn is_end_of_publish(&self) -> bool {
796        matches!(self.kind, ConsensusTransactionKind::EndOfPublish(_))
797    }
798}
799
800#[test]
801fn test_jwk_compatibility() {
802    // Ensure that the JWK and JwkId structs in fastcrypto do not change formats.
803    // If this test breaks DO NOT JUST UPDATE THE EXPECTED BYTES. Instead, add a local JWK or
804    // JwkId struct that mirrors the fastcrypto struct, use it in AuthenticatorStateUpdate, and
805    // add Into/From as necessary.
806    let jwk = JWK {
807        kty: "a".to_string(),
808        e: "b".to_string(),
809        n: "c".to_string(),
810        alg: "d".to_string(),
811    };
812
813    let expected_jwk_bytes = vec![1, 97, 1, 98, 1, 99, 1, 100];
814    let jwk_bcs = bcs::to_bytes(&jwk).unwrap();
815    assert_eq!(jwk_bcs, expected_jwk_bytes);
816
817    let id = JwkId {
818        iss: "abc".to_string(),
819        kid: "def".to_string(),
820    };
821
822    let expected_id_bytes = vec![3, 97, 98, 99, 3, 100, 101, 102];
823    let id_bcs = bcs::to_bytes(&id).unwrap();
824    assert_eq!(id_bcs, expected_id_bytes);
825}