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