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