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