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, TransactionWithAliases};
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/// Used to advertise capabilities of each authority via consensus. This allows validators to
307/// negotiate the creation of the ChangeEpoch transaction.
308#[derive(Serialize, Deserialize, Clone, Hash)]
309pub struct AuthorityCapabilitiesV1 {
310    /// Originating authority - must match consensus transaction source.
311    pub authority: AuthorityName,
312    /// Generation number set by sending authority. Used to determine which of multiple
313    /// AuthorityCapabilities messages from the same authority is the most recent.
314    ///
315    /// (Currently, we just set this to the current time in milliseconds since the epoch, but this
316    /// should not be interpreted as a timestamp.)
317    pub generation: u64,
318
319    /// ProtocolVersions that the authority supports.
320    pub supported_protocol_versions: SupportedProtocolVersions,
321
322    /// The ObjectRefs of all versions of system packages that the validator possesses.
323    /// Used to determine whether to do a framework/movestdlib upgrade.
324    pub available_system_packages: Vec<ObjectRef>,
325}
326
327impl Debug for AuthorityCapabilitiesV1 {
328    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
329        f.debug_struct("AuthorityCapabilities")
330            .field("authority", &self.authority.concise())
331            .field("generation", &self.generation)
332            .field(
333                "supported_protocol_versions",
334                &self.supported_protocol_versions,
335            )
336            .field("available_system_packages", &self.available_system_packages)
337            .finish()
338    }
339}
340
341impl AuthorityCapabilitiesV1 {
342    pub fn new(
343        authority: AuthorityName,
344        supported_protocol_versions: SupportedProtocolVersions,
345        available_system_packages: Vec<ObjectRef>,
346    ) -> Self {
347        let generation = SystemTime::now()
348            .duration_since(UNIX_EPOCH)
349            .expect("Sui did not exist prior to 1970")
350            .as_millis()
351            .try_into()
352            .expect("This build of sui is not supported in the year 500,000,000");
353        Self {
354            authority,
355            generation,
356            supported_protocol_versions,
357            available_system_packages,
358        }
359    }
360}
361
362/// Used to advertise capabilities of each authority via consensus. This allows validators to
363/// negotiate the creation of the ChangeEpoch transaction.
364#[derive(Serialize, Deserialize, Clone, Hash)]
365pub struct AuthorityCapabilitiesV2 {
366    /// Originating authority - must match transaction source authority from consensus.
367    pub authority: AuthorityName,
368    /// Generation number set by sending authority. Used to determine which of multiple
369    /// AuthorityCapabilities messages from the same authority is the most recent.
370    ///
371    /// (Currently, we just set this to the current time in milliseconds since the epoch, but this
372    /// should not be interpreted as a timestamp.)
373    pub generation: u64,
374
375    /// ProtocolVersions that the authority supports.
376    pub supported_protocol_versions: SupportedProtocolVersionsWithHashes,
377
378    /// The ObjectRefs of all versions of system packages that the validator possesses.
379    /// Used to determine whether to do a framework/movestdlib upgrade.
380    pub available_system_packages: Vec<ObjectRef>,
381}
382
383impl Debug for AuthorityCapabilitiesV2 {
384    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
385        f.debug_struct("AuthorityCapabilities")
386            .field("authority", &self.authority.concise())
387            .field("generation", &self.generation)
388            .field(
389                "supported_protocol_versions",
390                &self.supported_protocol_versions,
391            )
392            .field("available_system_packages", &self.available_system_packages)
393            .finish()
394    }
395}
396
397impl AuthorityCapabilitiesV2 {
398    pub fn new(
399        authority: AuthorityName,
400        chain: Chain,
401        supported_protocol_versions: SupportedProtocolVersions,
402        available_system_packages: Vec<ObjectRef>,
403    ) -> Self {
404        let generation = SystemTime::now()
405            .duration_since(UNIX_EPOCH)
406            .expect("Sui did not exist prior to 1970")
407            .as_millis()
408            .try_into()
409            .expect("This build of sui is not supported in the year 500,000,000");
410        Self {
411            authority,
412            generation,
413            supported_protocol_versions:
414                SupportedProtocolVersionsWithHashes::from_supported_versions(
415                    supported_protocol_versions,
416                    chain,
417                ),
418            available_system_packages,
419        }
420    }
421}
422
423/// Used to share estimates of transaction execution times with other validators for
424/// congestion control.
425#[derive(Debug, Default, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
426pub struct ExecutionTimeObservation {
427    /// Originating authority - must match transaction source authority from consensus.
428    pub authority: AuthorityName,
429    /// Generation number set by sending authority. Used to determine which of multiple
430    /// ExecutionTimeObservation messages from the same authority is the most recent.
431    pub generation: u64,
432
433    /// Estimated execution durations by key.
434    pub estimates: Vec<(ExecutionTimeObservationKey, Duration)>,
435}
436
437impl ExecutionTimeObservation {
438    pub fn new(
439        authority: AuthorityName,
440        generation: u64,
441        estimates: Vec<(ExecutionTimeObservationKey, Duration)>,
442    ) -> Self {
443        Self {
444            authority,
445            generation,
446            estimates,
447        }
448    }
449}
450
451#[derive(Serialize, Deserialize, Clone, Debug)]
452pub enum ConsensusTransactionKind {
453    CertifiedTransaction(Box<CertifiedTransaction>),
454    // V1: dedup by authority + sequence only
455    CheckpointSignature(Box<CheckpointSignatureMessage>),
456    EndOfPublish(AuthorityName),
457
458    CapabilityNotification(AuthorityCapabilitiesV1),
459
460    NewJWKFetched(AuthorityName, JwkId, JWK),
461    RandomnessStateUpdate(u64, Vec<u8>), // deprecated
462    // DKG is used to generate keys for use in the random beacon protocol.
463    // `RandomnessDkgMessage` is sent out at start-of-epoch to initiate the process.
464    // Contents are a serialized `fastcrypto_tbls::dkg::Message`.
465    RandomnessDkgMessage(AuthorityName, Vec<u8>),
466    // `RandomnessDkgConfirmation` is the second DKG message, sent as soon as a threshold amount of
467    // `RandomnessDkgMessages` have been received locally, to complete the key generation process.
468    // Contents are a serialized `fastcrypto_tbls::dkg::Confirmation`.
469    RandomnessDkgConfirmation(AuthorityName, Vec<u8>),
470
471    CapabilityNotificationV2(AuthorityCapabilitiesV2),
472
473    UserTransaction(Box<Transaction>),
474
475    ExecutionTimeObservation(ExecutionTimeObservation),
476    // V2: dedup by authority + sequence + digest
477    CheckpointSignatureV2(Box<CheckpointSignatureMessage>),
478
479    // UserTransactionV2 commits to specific AddressAliases object versions that were used
480    // to verify the transaction.
481    UserTransactionV2(Box<TransactionWithAliases>),
482}
483
484impl ConsensusTransactionKind {
485    pub fn as_user_transaction(&self) -> Option<&Transaction> {
486        match self {
487            ConsensusTransactionKind::UserTransaction(tx) => Some(tx),
488            ConsensusTransactionKind::UserTransactionV2(tx) => Some(tx.tx()),
489            _ => None,
490        }
491    }
492
493    pub fn into_user_transaction(self) -> Option<Transaction> {
494        match self {
495            ConsensusTransactionKind::UserTransaction(tx) => Some(*tx),
496            ConsensusTransactionKind::UserTransactionV2(tx) => Some(tx.into_tx()),
497            _ => None,
498        }
499    }
500}
501
502#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
503#[allow(clippy::large_enum_variant)]
504pub enum VersionedDkgMessage {
505    V0(), // deprecated
506    V1(dkg_v1::Message<bls12381::G2Element, bls12381::G2Element>),
507}
508
509#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
510pub enum VersionedDkgConfirmation {
511    V0(), // deprecated
512    V1(dkg_v1::Confirmation<bls12381::G2Element>),
513}
514
515impl Debug for VersionedDkgMessage {
516    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
517        match self {
518            VersionedDkgMessage::V0() => write!(f, "Deprecated VersionedDkgMessage version 0"),
519            VersionedDkgMessage::V1(msg) => write!(
520                f,
521                "DKG V1 Message with sender={}, vss_pk.degree={}, encrypted_shares.len()={}",
522                msg.sender,
523                msg.vss_pk.degree(),
524                msg.encrypted_shares.len(),
525            ),
526        }
527    }
528}
529
530impl VersionedDkgMessage {
531    pub fn sender(&self) -> u16 {
532        match self {
533            VersionedDkgMessage::V0() => panic!("BUG: invalid VersionedDkgMessage version"),
534            VersionedDkgMessage::V1(msg) => msg.sender,
535        }
536    }
537
538    pub fn create(
539        dkg_version: u64,
540        party: Arc<dkg_v1::Party<bls12381::G2Element, bls12381::G2Element>>,
541    ) -> FastCryptoResult<VersionedDkgMessage> {
542        assert_eq!(dkg_version, 1, "BUG: invalid DKG version");
543        let msg = party.create_message(&mut rand::thread_rng())?;
544        Ok(VersionedDkgMessage::V1(msg))
545    }
546
547    pub fn unwrap_v1(self) -> dkg_v1::Message<bls12381::G2Element, bls12381::G2Element> {
548        match self {
549            VersionedDkgMessage::V1(msg) => msg,
550            _ => panic!("BUG: expected V1 message"),
551        }
552    }
553
554    pub fn is_valid_version(&self, dkg_version: u64) -> bool {
555        matches!((self, dkg_version), (VersionedDkgMessage::V1(_), 1))
556    }
557}
558
559impl VersionedDkgConfirmation {
560    pub fn sender(&self) -> u16 {
561        match self {
562            VersionedDkgConfirmation::V0() => {
563                panic!("BUG: invalid VersionedDkgConfirmation version")
564            }
565            VersionedDkgConfirmation::V1(msg) => msg.sender,
566        }
567    }
568
569    pub fn num_of_complaints(&self) -> usize {
570        match self {
571            VersionedDkgConfirmation::V0() => {
572                panic!("BUG: invalid VersionedDkgConfirmation version")
573            }
574            VersionedDkgConfirmation::V1(msg) => msg.complaints.len(),
575        }
576    }
577
578    pub fn unwrap_v1(&self) -> &dkg_v1::Confirmation<bls12381::G2Element> {
579        match self {
580            VersionedDkgConfirmation::V1(msg) => msg,
581            _ => panic!("BUG: expected V1 confirmation"),
582        }
583    }
584
585    pub fn is_valid_version(&self, dkg_version: u64) -> bool {
586        matches!((self, dkg_version), (VersionedDkgConfirmation::V1(_), 1))
587    }
588}
589
590impl ConsensusTransaction {
591    pub fn new_certificate_message(
592        authority: &AuthorityName,
593        certificate: CertifiedTransaction,
594    ) -> Self {
595        let mut hasher = DefaultHasher::new();
596        let tx_digest = certificate.digest();
597        tx_digest.hash(&mut hasher);
598        authority.hash(&mut hasher);
599        let tracking_id = hasher.finish().to_le_bytes();
600        Self {
601            tracking_id,
602            kind: ConsensusTransactionKind::CertifiedTransaction(Box::new(certificate)),
603        }
604    }
605
606    pub fn new_user_transaction_message(authority: &AuthorityName, tx: Transaction) -> Self {
607        let mut hasher = DefaultHasher::new();
608        let tx_digest = tx.digest();
609        tx_digest.hash(&mut hasher);
610        authority.hash(&mut hasher);
611        let tracking_id = hasher.finish().to_le_bytes();
612        Self {
613            tracking_id,
614            kind: ConsensusTransactionKind::UserTransaction(Box::new(tx)),
615        }
616    }
617
618    pub fn new_user_transaction_v2_message(
619        authority: &AuthorityName,
620        tx: TransactionWithAliases,
621    ) -> Self {
622        let mut hasher = DefaultHasher::new();
623        let tx_digest = tx.tx().digest();
624        tx_digest.hash(&mut hasher);
625        authority.hash(&mut hasher);
626        let tracking_id = hasher.finish().to_le_bytes();
627        Self {
628            tracking_id,
629            kind: ConsensusTransactionKind::UserTransactionV2(Box::new(tx)),
630        }
631    }
632
633    pub fn new_checkpoint_signature_message(data: CheckpointSignatureMessage) -> Self {
634        let mut hasher = DefaultHasher::new();
635        data.summary.auth_sig().signature.hash(&mut hasher);
636        let tracking_id = hasher.finish().to_le_bytes();
637        Self {
638            tracking_id,
639            kind: ConsensusTransactionKind::CheckpointSignature(Box::new(data)),
640        }
641    }
642
643    pub fn new_checkpoint_signature_message_v2(data: CheckpointSignatureMessage) -> Self {
644        let mut hasher = DefaultHasher::new();
645        data.summary.auth_sig().signature.hash(&mut hasher);
646        let tracking_id = hasher.finish().to_le_bytes();
647        Self {
648            tracking_id,
649            kind: ConsensusTransactionKind::CheckpointSignatureV2(Box::new(data)),
650        }
651    }
652
653    pub fn new_end_of_publish(authority: AuthorityName) -> Self {
654        let mut hasher = DefaultHasher::new();
655        authority.hash(&mut hasher);
656        let tracking_id = hasher.finish().to_le_bytes();
657        Self {
658            tracking_id,
659            kind: ConsensusTransactionKind::EndOfPublish(authority),
660        }
661    }
662
663    pub fn new_capability_notification(capabilities: AuthorityCapabilitiesV1) -> Self {
664        let mut hasher = DefaultHasher::new();
665        capabilities.hash(&mut hasher);
666        let tracking_id = hasher.finish().to_le_bytes();
667        Self {
668            tracking_id,
669            kind: ConsensusTransactionKind::CapabilityNotification(capabilities),
670        }
671    }
672
673    pub fn new_capability_notification_v2(capabilities: AuthorityCapabilitiesV2) -> Self {
674        let mut hasher = DefaultHasher::new();
675        capabilities.hash(&mut hasher);
676        let tracking_id = hasher.finish().to_le_bytes();
677        Self {
678            tracking_id,
679            kind: ConsensusTransactionKind::CapabilityNotificationV2(capabilities),
680        }
681    }
682
683    pub fn new_mysticeti_certificate(
684        round: u64,
685        offset: u64,
686        certificate: CertifiedTransaction,
687    ) -> Self {
688        let mut hasher = DefaultHasher::new();
689        let tx_digest = certificate.digest();
690        tx_digest.hash(&mut hasher);
691        round.hash(&mut hasher);
692        offset.hash(&mut hasher);
693        let tracking_id = hasher.finish().to_le_bytes();
694        Self {
695            tracking_id,
696            kind: ConsensusTransactionKind::CertifiedTransaction(Box::new(certificate)),
697        }
698    }
699
700    pub fn new_jwk_fetched(authority: AuthorityName, id: JwkId, jwk: JWK) -> Self {
701        let mut hasher = DefaultHasher::new();
702        id.hash(&mut hasher);
703        let tracking_id = hasher.finish().to_le_bytes();
704        Self {
705            tracking_id,
706            kind: ConsensusTransactionKind::NewJWKFetched(authority, id, jwk),
707        }
708    }
709
710    pub fn new_randomness_dkg_message(
711        authority: AuthorityName,
712        versioned_message: &VersionedDkgMessage,
713    ) -> Self {
714        let message =
715            bcs::to_bytes(versioned_message).expect("message serialization should not fail");
716        let mut hasher = DefaultHasher::new();
717        message.hash(&mut hasher);
718        let tracking_id = hasher.finish().to_le_bytes();
719        Self {
720            tracking_id,
721            kind: ConsensusTransactionKind::RandomnessDkgMessage(authority, message),
722        }
723    }
724    pub fn new_randomness_dkg_confirmation(
725        authority: AuthorityName,
726        versioned_confirmation: &VersionedDkgConfirmation,
727    ) -> Self {
728        let confirmation =
729            bcs::to_bytes(versioned_confirmation).expect("message serialization should not fail");
730        let mut hasher = DefaultHasher::new();
731        confirmation.hash(&mut hasher);
732        let tracking_id = hasher.finish().to_le_bytes();
733        Self {
734            tracking_id,
735            kind: ConsensusTransactionKind::RandomnessDkgConfirmation(authority, confirmation),
736        }
737    }
738
739    pub fn new_execution_time_observation(observation: ExecutionTimeObservation) -> Self {
740        let mut hasher = DefaultHasher::new();
741        observation.hash(&mut hasher);
742        let tracking_id = hasher.finish().to_le_bytes();
743        Self {
744            tracking_id,
745            kind: ConsensusTransactionKind::ExecutionTimeObservation(observation),
746        }
747    }
748
749    pub fn get_tracking_id(&self) -> u64 {
750        (&self.tracking_id[..])
751            .read_u64::<BigEndian>()
752            .unwrap_or_default()
753    }
754
755    pub fn key(&self) -> ConsensusTransactionKey {
756        match &self.kind {
757            ConsensusTransactionKind::CertifiedTransaction(cert) => {
758                ConsensusTransactionKey::Certificate(*cert.digest())
759            }
760            ConsensusTransactionKind::CheckpointSignature(data) => {
761                ConsensusTransactionKey::CheckpointSignature(
762                    data.summary.auth_sig().authority,
763                    data.summary.sequence_number,
764                )
765            }
766            ConsensusTransactionKind::CheckpointSignatureV2(data) => {
767                ConsensusTransactionKey::CheckpointSignatureV2(
768                    data.summary.auth_sig().authority,
769                    data.summary.sequence_number,
770                    *data.summary.digest(),
771                )
772            }
773            ConsensusTransactionKind::EndOfPublish(authority) => {
774                ConsensusTransactionKey::EndOfPublish(*authority)
775            }
776            ConsensusTransactionKind::CapabilityNotification(cap) => {
777                ConsensusTransactionKey::CapabilityNotification(cap.authority, cap.generation)
778            }
779            ConsensusTransactionKind::CapabilityNotificationV2(cap) => {
780                ConsensusTransactionKey::CapabilityNotification(cap.authority, cap.generation)
781            }
782            ConsensusTransactionKind::NewJWKFetched(authority, id, key) => {
783                ConsensusTransactionKey::NewJWKFetched(Box::new((
784                    *authority,
785                    id.clone(),
786                    key.clone(),
787                )))
788            }
789            ConsensusTransactionKind::RandomnessStateUpdate(_, _) => {
790                unreachable!(
791                    "there should never be a RandomnessStateUpdate with SequencedConsensusTransactionKind::External"
792                )
793            }
794            ConsensusTransactionKind::RandomnessDkgMessage(authority, _) => {
795                ConsensusTransactionKey::RandomnessDkgMessage(*authority)
796            }
797            ConsensusTransactionKind::RandomnessDkgConfirmation(authority, _) => {
798                ConsensusTransactionKey::RandomnessDkgConfirmation(*authority)
799            }
800            ConsensusTransactionKind::UserTransaction(tx) => {
801                // Use the same key format as ConsensusTransactionKind::CertifiedTransaction,
802                // because existing usages of ConsensusTransactionKey should not differentiate
803                // between CertifiedTransaction and UserTransaction.
804                ConsensusTransactionKey::Certificate(*tx.digest())
805            }
806            ConsensusTransactionKind::UserTransactionV2(tx) => {
807                // Use the same key format as ConsensusTransactionKind::CertifiedTransaction,
808                // because existing usages of ConsensusTransactionKey should not differentiate
809                // between CertifiedTransaction and UserTransactionV2.
810                ConsensusTransactionKey::Certificate(*tx.tx().digest())
811            }
812            ConsensusTransactionKind::ExecutionTimeObservation(msg) => {
813                ConsensusTransactionKey::ExecutionTimeObservation(msg.authority, msg.generation)
814            }
815        }
816    }
817
818    pub fn is_dkg(&self) -> bool {
819        matches!(
820            self.kind,
821            ConsensusTransactionKind::RandomnessDkgMessage(_, _)
822                | ConsensusTransactionKind::RandomnessDkgConfirmation(_, _)
823        )
824    }
825
826    pub fn is_mfp_transaction(&self) -> bool {
827        matches!(
828            self.kind,
829            ConsensusTransactionKind::UserTransaction(_)
830                | ConsensusTransactionKind::UserTransactionV2(_)
831        )
832    }
833
834    pub fn is_user_transaction(&self) -> bool {
835        matches!(
836            self.kind,
837            ConsensusTransactionKind::UserTransaction(_)
838                | ConsensusTransactionKind::UserTransactionV2(_)
839                | ConsensusTransactionKind::CertifiedTransaction(_)
840        )
841    }
842
843    pub fn is_end_of_publish(&self) -> bool {
844        matches!(self.kind, ConsensusTransactionKind::EndOfPublish(_))
845    }
846}
847
848#[test]
849fn test_jwk_compatibility() {
850    // Ensure that the JWK and JwkId structs in fastcrypto do not change formats.
851    // If this test breaks DO NOT JUST UPDATE THE EXPECTED BYTES. Instead, add a local JWK or
852    // JwkId struct that mirrors the fastcrypto struct, use it in AuthenticatorStateUpdate, and
853    // add Into/From as necessary.
854    let jwk = JWK {
855        kty: "a".to_string(),
856        e: "b".to_string(),
857        n: "c".to_string(),
858        alg: "d".to_string(),
859    };
860
861    let expected_jwk_bytes = vec![1, 97, 1, 98, 1, 99, 1, 100];
862    let jwk_bcs = bcs::to_bytes(&jwk).unwrap();
863    assert_eq!(jwk_bcs, expected_jwk_bytes);
864
865    let id = JwkId {
866        iss: "abc".to_string(),
867        kid: "def".to_string(),
868    };
869
870    let expected_id_bytes = vec![3, 97, 98, 99, 3, 100, 101, 102];
871    let id_bcs = bcs::to_bytes(&id).unwrap();
872    assert_eq!(id_bcs, expected_id_bytes);
873}