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::UserTransactionV2(tx) => Some(tx.tx()),
474            _ => None,
475        }
476    }
477
478    pub fn into_user_transaction(self) -> Option<Transaction> {
479        match self {
480            ConsensusTransactionKind::UserTransactionV2(tx) => Some(tx.into_tx()),
481            _ => None,
482        }
483    }
484}
485
486#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
487#[allow(clippy::large_enum_variant)]
488pub enum VersionedDkgMessage {
489    V0(), // deprecated
490    V1(dkg_v1::Message<bls12381::G2Element, bls12381::G2Element>),
491}
492
493#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
494pub enum VersionedDkgConfirmation {
495    V0(), // deprecated
496    V1(dkg_v1::Confirmation<bls12381::G2Element>),
497}
498
499impl Debug for VersionedDkgMessage {
500    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
501        match self {
502            VersionedDkgMessage::V0() => write!(f, "Deprecated VersionedDkgMessage version 0"),
503            VersionedDkgMessage::V1(msg) => write!(
504                f,
505                "DKG V1 Message with sender={}, vss_pk.degree={}, encrypted_shares.len()={}",
506                msg.sender,
507                msg.vss_pk.degree(),
508                msg.encrypted_shares.len(),
509            ),
510        }
511    }
512}
513
514impl VersionedDkgMessage {
515    pub fn sender(&self) -> u16 {
516        match self {
517            VersionedDkgMessage::V0() => panic!("BUG: invalid VersionedDkgMessage version"),
518            VersionedDkgMessage::V1(msg) => msg.sender,
519        }
520    }
521
522    pub fn create(
523        dkg_version: u64,
524        party: Arc<dkg_v1::Party<bls12381::G2Element, bls12381::G2Element>>,
525    ) -> FastCryptoResult<VersionedDkgMessage> {
526        assert_eq!(dkg_version, 1, "BUG: invalid DKG version");
527        let msg = party.create_message(&mut rand::thread_rng())?;
528        Ok(VersionedDkgMessage::V1(msg))
529    }
530
531    pub fn unwrap_v1(self) -> dkg_v1::Message<bls12381::G2Element, bls12381::G2Element> {
532        match self {
533            VersionedDkgMessage::V1(msg) => msg,
534            _ => panic!("BUG: expected V1 message"),
535        }
536    }
537
538    pub fn is_valid_version(&self, dkg_version: u64) -> bool {
539        matches!((self, dkg_version), (VersionedDkgMessage::V1(_), 1))
540    }
541}
542
543impl VersionedDkgConfirmation {
544    pub fn sender(&self) -> u16 {
545        match self {
546            VersionedDkgConfirmation::V0() => {
547                panic!("BUG: invalid VersionedDkgConfirmation version")
548            }
549            VersionedDkgConfirmation::V1(msg) => msg.sender,
550        }
551    }
552
553    pub fn num_of_complaints(&self) -> usize {
554        match self {
555            VersionedDkgConfirmation::V0() => {
556                panic!("BUG: invalid VersionedDkgConfirmation version")
557            }
558            VersionedDkgConfirmation::V1(msg) => msg.complaints.len(),
559        }
560    }
561
562    pub fn unwrap_v1(&self) -> &dkg_v1::Confirmation<bls12381::G2Element> {
563        match self {
564            VersionedDkgConfirmation::V1(msg) => msg,
565            _ => panic!("BUG: expected V1 confirmation"),
566        }
567    }
568
569    pub fn is_valid_version(&self, dkg_version: u64) -> bool {
570        matches!((self, dkg_version), (VersionedDkgConfirmation::V1(_), 1))
571    }
572}
573
574impl ConsensusTransaction {
575    pub fn new_user_transaction_v2_message(
576        authority: &AuthorityName,
577        tx: PlainTransactionWithClaims,
578    ) -> Self {
579        let mut hasher = DefaultHasher::new();
580        let tx_digest = tx.tx().digest();
581        tx_digest.hash(&mut hasher);
582        authority.hash(&mut hasher);
583        let tracking_id = hasher.finish().to_le_bytes();
584        Self {
585            tracking_id,
586            kind: ConsensusTransactionKind::UserTransactionV2(Box::new(tx)),
587        }
588    }
589
590    pub fn new_checkpoint_signature_message_v2(data: CheckpointSignatureMessage) -> Self {
591        let mut hasher = DefaultHasher::new();
592        data.summary.auth_sig().signature.hash(&mut hasher);
593        let tracking_id = hasher.finish().to_le_bytes();
594        Self {
595            tracking_id,
596            kind: ConsensusTransactionKind::CheckpointSignatureV2(Box::new(data)),
597        }
598    }
599
600    pub fn new_end_of_publish(authority: AuthorityName) -> Self {
601        let mut hasher = DefaultHasher::new();
602        authority.hash(&mut hasher);
603        let tracking_id = hasher.finish().to_le_bytes();
604        Self {
605            tracking_id,
606            kind: ConsensusTransactionKind::EndOfPublish(authority),
607        }
608    }
609
610    pub fn new_capability_notification_v2(capabilities: AuthorityCapabilitiesV2) -> Self {
611        let mut hasher = DefaultHasher::new();
612        capabilities.hash(&mut hasher);
613        let tracking_id = hasher.finish().to_le_bytes();
614        Self {
615            tracking_id,
616            kind: ConsensusTransactionKind::CapabilityNotificationV2(capabilities),
617        }
618    }
619
620    pub fn new_jwk_fetched(authority: AuthorityName, id: JwkId, jwk: JWK) -> Self {
621        let mut hasher = DefaultHasher::new();
622        id.hash(&mut hasher);
623        let tracking_id = hasher.finish().to_le_bytes();
624        Self {
625            tracking_id,
626            kind: ConsensusTransactionKind::NewJWKFetched(authority, id, jwk),
627        }
628    }
629
630    pub fn new_randomness_dkg_message(
631        authority: AuthorityName,
632        versioned_message: &VersionedDkgMessage,
633    ) -> Self {
634        let message =
635            bcs::to_bytes(versioned_message).expect("message serialization should not fail");
636        let mut hasher = DefaultHasher::new();
637        message.hash(&mut hasher);
638        let tracking_id = hasher.finish().to_le_bytes();
639        Self {
640            tracking_id,
641            kind: ConsensusTransactionKind::RandomnessDkgMessage(authority, message),
642        }
643    }
644    pub fn new_randomness_dkg_confirmation(
645        authority: AuthorityName,
646        versioned_confirmation: &VersionedDkgConfirmation,
647    ) -> Self {
648        let confirmation =
649            bcs::to_bytes(versioned_confirmation).expect("message serialization should not fail");
650        let mut hasher = DefaultHasher::new();
651        confirmation.hash(&mut hasher);
652        let tracking_id = hasher.finish().to_le_bytes();
653        Self {
654            tracking_id,
655            kind: ConsensusTransactionKind::RandomnessDkgConfirmation(authority, confirmation),
656        }
657    }
658
659    pub fn new_execution_time_observation(observation: ExecutionTimeObservation) -> Self {
660        let mut hasher = DefaultHasher::new();
661        observation.hash(&mut hasher);
662        let tracking_id = hasher.finish().to_le_bytes();
663        Self {
664            tracking_id,
665            kind: ConsensusTransactionKind::ExecutionTimeObservation(observation),
666        }
667    }
668
669    pub fn get_tracking_id(&self) -> u64 {
670        (&self.tracking_id[..])
671            .read_u64::<BigEndian>()
672            .unwrap_or_default()
673    }
674
675    pub fn key(&self) -> ConsensusTransactionKey {
676        match &self.kind {
677            ConsensusTransactionKind::CertifiedTransaction(cert) => {
678                ConsensusTransactionKey::Certificate(*cert.digest())
679            }
680            ConsensusTransactionKind::CheckpointSignature(data) => {
681                ConsensusTransactionKey::CheckpointSignature(
682                    data.summary.auth_sig().authority,
683                    data.summary.sequence_number,
684                )
685            }
686            ConsensusTransactionKind::CheckpointSignatureV2(data) => {
687                ConsensusTransactionKey::CheckpointSignatureV2(
688                    data.summary.auth_sig().authority,
689                    data.summary.sequence_number,
690                    *data.summary.digest(),
691                )
692            }
693            ConsensusTransactionKind::EndOfPublish(authority) => {
694                ConsensusTransactionKey::EndOfPublish(*authority)
695            }
696            ConsensusTransactionKind::CapabilityNotification(cap) => {
697                ConsensusTransactionKey::CapabilityNotification(cap.authority, cap.generation)
698            }
699            ConsensusTransactionKind::CapabilityNotificationV2(cap) => {
700                ConsensusTransactionKey::CapabilityNotification(cap.authority, cap.generation)
701            }
702            ConsensusTransactionKind::NewJWKFetched(authority, id, key) => {
703                ConsensusTransactionKey::NewJWKFetched(Box::new((
704                    *authority,
705                    id.clone(),
706                    key.clone(),
707                )))
708            }
709            ConsensusTransactionKind::RandomnessStateUpdate(_, _) => {
710                debug_fatal!(
711                    "there should never be a RandomnessStateUpdate with SequencedConsensusTransactionKind::External"
712                );
713                ConsensusTransactionKey::RandomnessStateUpdate
714            }
715            ConsensusTransactionKind::RandomnessDkgMessage(authority, _) => {
716                ConsensusTransactionKey::RandomnessDkgMessage(*authority)
717            }
718            ConsensusTransactionKind::RandomnessDkgConfirmation(authority, _) => {
719                ConsensusTransactionKey::RandomnessDkgConfirmation(*authority)
720            }
721            ConsensusTransactionKind::UserTransaction(tx) => {
722                // Use the same key format as ConsensusTransactionKind::CertifiedTransaction,
723                // because existing usages of ConsensusTransactionKey should not differentiate
724                // between CertifiedTransaction and UserTransaction.
725                ConsensusTransactionKey::Certificate(*tx.digest())
726            }
727            ConsensusTransactionKind::UserTransactionV2(tx) => {
728                // Use the same key format as ConsensusTransactionKind::CertifiedTransaction,
729                // because existing usages of ConsensusTransactionKey should not differentiate
730                // between CertifiedTransaction and UserTransactionV2.
731                ConsensusTransactionKey::Certificate(*tx.tx().digest())
732            }
733            ConsensusTransactionKind::ExecutionTimeObservation(msg) => {
734                ConsensusTransactionKey::ExecutionTimeObservation(msg.authority, msg.generation)
735            }
736        }
737    }
738
739    pub fn is_dkg(&self) -> bool {
740        matches!(
741            self.kind,
742            ConsensusTransactionKind::RandomnessDkgMessage(_, _)
743                | ConsensusTransactionKind::RandomnessDkgConfirmation(_, _)
744        )
745    }
746
747    pub fn is_user_transaction(&self) -> bool {
748        // CertifiedTransaction and UserTransaction are unused and not accepted now.
749        matches!(self.kind, ConsensusTransactionKind::UserTransactionV2(_))
750    }
751
752    pub fn is_end_of_publish(&self) -> bool {
753        matches!(self.kind, ConsensusTransactionKind::EndOfPublish(_))
754    }
755}
756
757#[test]
758fn test_jwk_compatibility() {
759    // Ensure that the JWK and JwkId structs in fastcrypto do not change formats.
760    // If this test breaks DO NOT JUST UPDATE THE EXPECTED BYTES. Instead, add a local JWK or
761    // JwkId struct that mirrors the fastcrypto struct, use it in AuthenticatorStateUpdate, and
762    // add Into/From as necessary.
763    let jwk = JWK {
764        kty: "a".to_string(),
765        e: "b".to_string(),
766        n: "c".to_string(),
767        alg: "d".to_string(),
768    };
769
770    let expected_jwk_bytes = vec![1, 97, 1, 98, 1, 99, 1, 100];
771    let jwk_bcs = bcs::to_bytes(&jwk).unwrap();
772    assert_eq!(jwk_bcs, expected_jwk_bytes);
773
774    let id = JwkId {
775        iss: "abc".to_string(),
776        kid: "def".to_string(),
777    };
778
779    let expected_id_bytes = vec![3, 97, 98, 99, 3, 100, 101, 102];
780    let id_bcs = bcs::to_bytes(&id).unwrap();
781    assert_eq!(id_bcs, expected_id_bytes);
782}