Skip to main content

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 crate::transaction_deny_rules::TransactionDenyRules;
18use byteorder::{BigEndian, ReadBytesExt};
19use bytes::Bytes;
20use consensus_types::block::{BlockRef, PING_TRANSACTION_INDEX, TransactionIndex};
21use fastcrypto::error::FastCryptoResult;
22use fastcrypto::groups::bls12381;
23use fastcrypto_tbls::dkg_v1;
24use fastcrypto_zkp::bn254::zk_login::{JWK, JwkId};
25use mysten_common::debug_fatal;
26use schemars::JsonSchema;
27use serde::{Deserialize, Serialize};
28use std::collections::hash_map::DefaultHasher;
29use std::fmt::{Debug, Formatter};
30use std::hash::{Hash, Hasher};
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            ConsensusTransactionKind::UpdateTransactionDenyConfig(msg) => {
238                format!(
239                    "UpdateDenyConfig({}, gen={})",
240                    msg.authority().concise(),
241                    msg.generation()
242                )
243            }
244        }
245    }
246}
247
248// Serialized ordinally - always append to end of enum
249#[derive(Serialize, Deserialize, Clone, Hash, PartialEq, Eq, Ord, PartialOrd)]
250pub enum ConsensusTransactionKey {
251    Certificate(TransactionDigest),
252    // V1: dedup by authority + sequence only (no digest)
253    CheckpointSignature(AuthorityName, CheckpointSequenceNumber),
254    EndOfPublish(AuthorityName),
255    CapabilityNotification(AuthorityName, u64 /* generation */),
256    // Key must include both id and jwk, because honest validators could be given multiple jwks for
257    // the same id by malfunctioning providers.
258    NewJWKFetched(Box<(AuthorityName, JwkId, JWK)>),
259    RandomnessDkgMessage(AuthorityName),
260    RandomnessDkgConfirmation(AuthorityName),
261    ExecutionTimeObservation(AuthorityName, u64 /* generation */),
262    // V2: dedup by authority + sequence + digest
263    CheckpointSignatureV2(AuthorityName, CheckpointSequenceNumber, CheckpointDigest),
264    // Deprecated.
265    RandomnessStateUpdate,
266    /// `(authority, generation)` — supersede by strictly-increasing generation.
267    UpdateTransactionDenyConfig(AuthorityName, u64),
268}
269
270impl Debug for ConsensusTransactionKey {
271    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
272        match self {
273            Self::Certificate(digest) => write!(f, "Certificate({:?})", digest),
274            Self::CheckpointSignature(name, seq) => {
275                write!(f, "CheckpointSignature({:?}, {:?})", name.concise(), seq)
276            }
277            Self::CheckpointSignatureV2(name, seq, digest) => write!(
278                f,
279                "CheckpointSignatureV2({:?}, {:?}, {:?})",
280                name.concise(),
281                seq,
282                digest
283            ),
284            Self::EndOfPublish(name) => write!(f, "EndOfPublish({:?})", name.concise()),
285            Self::CapabilityNotification(name, generation) => write!(
286                f,
287                "CapabilityNotification({:?}, {:?})",
288                name.concise(),
289                generation
290            ),
291            Self::NewJWKFetched(key) => {
292                let (authority, id, jwk) = &**key;
293                write!(
294                    f,
295                    "NewJWKFetched({:?}, {:?}, {:?})",
296                    authority.concise(),
297                    id,
298                    jwk
299                )
300            }
301            Self::RandomnessDkgMessage(name) => {
302                write!(f, "RandomnessDkgMessage({:?})", name.concise())
303            }
304            Self::RandomnessDkgConfirmation(name) => {
305                write!(f, "RandomnessDkgConfirmation({:?})", name.concise())
306            }
307            Self::ExecutionTimeObservation(name, generation) => {
308                write!(
309                    f,
310                    "ExecutionTimeObservation({:?}, {generation:?})",
311                    name.concise()
312                )
313            }
314            Self::RandomnessStateUpdate => {
315                write!(f, "RandomnessStateUpdate")
316            }
317            Self::UpdateTransactionDenyConfig(name, generation) => write!(
318                f,
319                "UpdateTransactionDenyConfig({:?}, {generation:?})",
320                name.concise()
321            ),
322        }
323    }
324}
325
326#[derive(Serialize, Deserialize, Clone, Hash, Debug, PartialEq, Eq)]
327pub enum SharedTransactionDenyConfig {
328    V1(SharedTransactionDenyConfigV1),
329}
330
331impl SharedTransactionDenyConfig {
332    /// Upper bound on how far (in ms) a generation may run ahead of a receiving
333    /// validator's wall clock before the update is ignored rather than applied.
334    pub const MAX_GENERATION_FUTURE_DRIFT_MS: u64 = 60_000;
335
336    pub fn authority(&self) -> AuthorityName {
337        match self {
338            Self::V1(inner) => inner.authority,
339        }
340    }
341
342    pub fn generation(&self) -> u64 {
343        match self {
344            Self::V1(inner) => inner.generation,
345        }
346    }
347
348    pub fn rules(&self) -> Option<&TransactionDenyRules> {
349        match self {
350            Self::V1(inner) => inner.rules.as_ref(),
351        }
352    }
353}
354
355#[derive(Serialize, Deserialize, Clone, Hash, Debug, PartialEq, Eq)]
356pub struct SharedTransactionDenyConfigV1 {
357    /// Originating authority — must match consensus transaction source.
358    pub authority: AuthorityName,
359    /// Generation: ms since epoch (matches `CapabilityNotificationV2`). The receiver
360    /// only accepts updates with strictly greater generation than the last accepted
361    /// for that authority.
362    pub generation: u64,
363    /// `Some(rules)` = recommendation; `None` = withdraw a previous recommendation.
364    pub rules: Option<TransactionDenyRules>,
365}
366
367/// Deprecated in favor of AuthorityCapabilitiesV2
368/// Used to advertise capabilities of each authority via consensus. This allows validators to
369/// negotiate the creation of the ChangeEpoch transaction.
370#[derive(Serialize, Deserialize, Clone, Hash)]
371pub struct AuthorityCapabilitiesV1 {
372    /// Originating authority - must match consensus transaction source.
373    pub authority: AuthorityName,
374    /// Generation number set by sending authority. Used to determine which of multiple
375    /// AuthorityCapabilities messages from the same authority is the most recent.
376    ///
377    /// (Currently, we just set this to the current time in milliseconds since the epoch, but this
378    /// should not be interpreted as a timestamp.)
379    pub generation: u64,
380
381    /// ProtocolVersions that the authority supports.
382    pub supported_protocol_versions: SupportedProtocolVersions,
383
384    /// The ObjectRefs of all versions of system packages that the validator possesses.
385    /// Used to determine whether to do a framework/movestdlib upgrade.
386    pub available_system_packages: Vec<ObjectRef>,
387}
388
389impl Debug for AuthorityCapabilitiesV1 {
390    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
391        f.debug_struct("AuthorityCapabilities")
392            .field("authority", &self.authority.concise())
393            .field("generation", &self.generation)
394            .field(
395                "supported_protocol_versions",
396                &self.supported_protocol_versions,
397            )
398            .field("available_system_packages", &self.available_system_packages)
399            .finish()
400    }
401}
402
403/// Used to advertise capabilities of each authority via consensus. This allows validators to
404/// negotiate the creation of the ChangeEpoch transaction.
405#[derive(Serialize, Deserialize, Clone, Hash)]
406pub struct AuthorityCapabilitiesV2 {
407    /// Originating authority - must match transaction source authority from consensus.
408    pub authority: AuthorityName,
409    /// Generation number set by sending authority. Used to determine which of multiple
410    /// AuthorityCapabilities messages from the same authority is the most recent.
411    ///
412    /// (Currently, we just set this to the current time in milliseconds since the epoch, but this
413    /// should not be interpreted as a timestamp.)
414    pub generation: u64,
415
416    /// ProtocolVersions that the authority supports.
417    pub supported_protocol_versions: SupportedProtocolVersionsWithHashes,
418
419    /// The ObjectRefs of all versions of system packages that the validator possesses.
420    /// Used to determine whether to do a framework/movestdlib upgrade.
421    pub available_system_packages: Vec<ObjectRef>,
422}
423
424impl Debug for AuthorityCapabilitiesV2 {
425    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
426        f.debug_struct("AuthorityCapabilities")
427            .field("authority", &self.authority.concise())
428            .field("generation", &self.generation)
429            .field(
430                "supported_protocol_versions",
431                &self.supported_protocol_versions,
432            )
433            .field("available_system_packages", &self.available_system_packages)
434            .finish()
435    }
436}
437
438impl AuthorityCapabilitiesV2 {
439    pub fn new(
440        authority: AuthorityName,
441        chain: Chain,
442        supported_protocol_versions: SupportedProtocolVersions,
443        available_system_packages: Vec<ObjectRef>,
444    ) -> Self {
445        let generation = SystemTime::now()
446            .duration_since(UNIX_EPOCH)
447            .expect("Sui did not exist prior to 1970")
448            .as_millis()
449            .try_into()
450            .expect("This build of sui is not supported in the year 500,000,000");
451        Self {
452            authority,
453            generation,
454            supported_protocol_versions:
455                SupportedProtocolVersionsWithHashes::from_supported_versions(
456                    supported_protocol_versions,
457                    chain,
458                ),
459            available_system_packages,
460        }
461    }
462}
463
464/// Used to share estimates of transaction execution times with other validators for
465/// congestion control.
466#[derive(Debug, Default, PartialEq, Eq, Hash, Clone, Serialize, Deserialize)]
467pub struct ExecutionTimeObservation {
468    /// Originating authority - must match transaction source authority from consensus.
469    pub authority: AuthorityName,
470    /// Generation number set by sending authority. Used to determine which of multiple
471    /// ExecutionTimeObservation messages from the same authority is the most recent.
472    pub generation: u64,
473
474    /// Estimated execution durations by key.
475    pub estimates: Vec<(ExecutionTimeObservationKey, Duration)>,
476}
477
478impl ExecutionTimeObservation {
479    pub fn new(
480        authority: AuthorityName,
481        generation: u64,
482        estimates: Vec<(ExecutionTimeObservationKey, Duration)>,
483    ) -> Self {
484        Self {
485            authority,
486            generation,
487            estimates,
488        }
489    }
490}
491
492#[derive(Serialize, Deserialize, Clone, Debug)]
493pub enum ConsensusTransactionKind {
494    CertifiedTransaction(Box<CertifiedTransaction>),
495    CheckpointSignature(Box<CheckpointSignatureMessage>), // deprecated, use CheckpointSignatureV2
496    EndOfPublish(AuthorityName),
497
498    CapabilityNotification(AuthorityCapabilitiesV1), // deprecated, use CapabilityNotificationV2
499
500    NewJWKFetched(AuthorityName, JwkId, JWK),
501    RandomnessStateUpdate(u64, Vec<u8>), // deprecated
502    // DKG is used to generate keys for use in the random beacon protocol.
503    // `RandomnessDkgMessage` is sent out at start-of-epoch to initiate the process.
504    // Contents are a serialized `fastcrypto_tbls::dkg::Message`.
505    RandomnessDkgMessage(AuthorityName, Vec<u8>),
506    // `RandomnessDkgConfirmation` is the second DKG message, sent as soon as a threshold amount of
507    // `RandomnessDkgMessages` have been received locally, to complete the key generation process.
508    // Contents are a serialized `fastcrypto_tbls::dkg::Confirmation`.
509    RandomnessDkgConfirmation(AuthorityName, Vec<u8>),
510
511    CapabilityNotificationV2(AuthorityCapabilitiesV2),
512
513    UserTransaction(Box<Transaction>),
514
515    ExecutionTimeObservation(ExecutionTimeObservation),
516    // V2: dedup by authority + sequence + digest
517    CheckpointSignatureV2(Box<CheckpointSignatureMessage>),
518
519    // UserTransactionV2 commits to verified claims about the transaction:
520    // - AddressAliases: specific object versions used for signature verification
521    // - ImmutableInputObjects: object IDs that are immutable (to avoid locking them)
522    UserTransactionV2(Box<PlainTransactionWithClaims>),
523
524    /// Recommended `TransactionDenyConfig` settings broadcast by an authority for peers
525    /// to use. Application of recommended rules by receiving validators is opt-in.
526    UpdateTransactionDenyConfig(Box<SharedTransactionDenyConfig>),
527}
528
529impl ConsensusTransactionKind {
530    pub fn as_user_transaction(&self) -> Option<&Transaction> {
531        match self {
532            ConsensusTransactionKind::UserTransactionV2(tx) => Some(tx.tx()),
533            _ => None,
534        }
535    }
536
537    pub fn into_user_transaction(self) -> Option<Transaction> {
538        match self {
539            ConsensusTransactionKind::UserTransactionV2(tx) => Some(tx.into_tx()),
540            _ => None,
541        }
542    }
543}
544
545#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
546#[allow(clippy::large_enum_variant)]
547pub enum VersionedDkgMessage {
548    V0(), // deprecated
549    V1(dkg_v1::Message<bls12381::G2Element, bls12381::G2Element>),
550}
551
552#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
553pub enum VersionedDkgConfirmation {
554    V0(), // deprecated
555    V1(dkg_v1::Confirmation<bls12381::G2Element>),
556}
557
558impl Debug for VersionedDkgMessage {
559    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
560        match self {
561            VersionedDkgMessage::V0() => write!(f, "Deprecated VersionedDkgMessage version 0"),
562            VersionedDkgMessage::V1(msg) => write!(
563                f,
564                "DKG V1 Message with sender={}, vss_pk.degree={}, encrypted_shares.len()={}",
565                msg.sender,
566                msg.vss_pk.degree(),
567                msg.encrypted_shares.len(),
568            ),
569        }
570    }
571}
572
573impl VersionedDkgMessage {
574    pub fn sender(&self) -> u16 {
575        match self {
576            VersionedDkgMessage::V0() => panic!("BUG: invalid VersionedDkgMessage version"),
577            VersionedDkgMessage::V1(msg) => msg.sender,
578        }
579    }
580
581    pub fn create(
582        dkg_version: u64,
583        party: &dkg_v1::Party<bls12381::G2Element, bls12381::G2Element>,
584    ) -> FastCryptoResult<VersionedDkgMessage> {
585        assert_eq!(dkg_version, 1, "BUG: invalid DKG version");
586        let msg = party.create_message(&mut rand::thread_rng())?;
587        Ok(VersionedDkgMessage::V1(msg))
588    }
589
590    pub fn unwrap_v1(self) -> dkg_v1::Message<bls12381::G2Element, bls12381::G2Element> {
591        match self {
592            VersionedDkgMessage::V1(msg) => msg,
593            _ => panic!("BUG: expected V1 message"),
594        }
595    }
596
597    pub fn is_valid_version(&self, dkg_version: u64) -> bool {
598        matches!((self, dkg_version), (VersionedDkgMessage::V1(_), 1))
599    }
600}
601
602impl VersionedDkgConfirmation {
603    pub fn sender(&self) -> u16 {
604        match self {
605            VersionedDkgConfirmation::V0() => {
606                panic!("BUG: invalid VersionedDkgConfirmation version")
607            }
608            VersionedDkgConfirmation::V1(msg) => msg.sender,
609        }
610    }
611
612    pub fn num_of_complaints(&self) -> usize {
613        match self {
614            VersionedDkgConfirmation::V0() => {
615                panic!("BUG: invalid VersionedDkgConfirmation version")
616            }
617            VersionedDkgConfirmation::V1(msg) => msg.complaints.len(),
618        }
619    }
620
621    pub fn as_v1(&self) -> Option<&dkg_v1::Confirmation<bls12381::G2Element>> {
622        match self {
623            VersionedDkgConfirmation::V1(msg) => Some(msg),
624            _ => None,
625        }
626    }
627
628    pub fn is_valid_version(&self, dkg_version: u64) -> bool {
629        matches!((self, dkg_version), (VersionedDkgConfirmation::V1(_), 1))
630    }
631}
632
633impl ConsensusTransaction {
634    pub fn new_user_transaction_v2_message(
635        authority: &AuthorityName,
636        tx: PlainTransactionWithClaims,
637    ) -> Self {
638        let mut hasher = DefaultHasher::new();
639        let tx_digest = tx.tx().digest();
640        tx_digest.hash(&mut hasher);
641        authority.hash(&mut hasher);
642        let tracking_id = hasher.finish().to_le_bytes();
643        Self {
644            tracking_id,
645            kind: ConsensusTransactionKind::UserTransactionV2(Box::new(tx)),
646        }
647    }
648
649    pub fn new_checkpoint_signature_message_v2(data: CheckpointSignatureMessage) -> Self {
650        let mut hasher = DefaultHasher::new();
651        data.summary.auth_sig().signature.hash(&mut hasher);
652        let tracking_id = hasher.finish().to_le_bytes();
653        Self {
654            tracking_id,
655            kind: ConsensusTransactionKind::CheckpointSignatureV2(Box::new(data)),
656        }
657    }
658
659    pub fn new_end_of_publish(authority: AuthorityName) -> Self {
660        let mut hasher = DefaultHasher::new();
661        authority.hash(&mut hasher);
662        let tracking_id = hasher.finish().to_le_bytes();
663        Self {
664            tracking_id,
665            kind: ConsensusTransactionKind::EndOfPublish(authority),
666        }
667    }
668
669    pub fn new_capability_notification_v2(capabilities: AuthorityCapabilitiesV2) -> Self {
670        let mut hasher = DefaultHasher::new();
671        capabilities.hash(&mut hasher);
672        let tracking_id = hasher.finish().to_le_bytes();
673        Self {
674            tracking_id,
675            kind: ConsensusTransactionKind::CapabilityNotificationV2(capabilities),
676        }
677    }
678
679    pub fn new_jwk_fetched(authority: AuthorityName, id: JwkId, jwk: JWK) -> Self {
680        let mut hasher = DefaultHasher::new();
681        id.hash(&mut hasher);
682        let tracking_id = hasher.finish().to_le_bytes();
683        Self {
684            tracking_id,
685            kind: ConsensusTransactionKind::NewJWKFetched(authority, id, jwk),
686        }
687    }
688
689    pub fn new_randomness_dkg_message(
690        authority: AuthorityName,
691        versioned_message: &VersionedDkgMessage,
692    ) -> Self {
693        let message =
694            bcs::to_bytes(versioned_message).expect("message serialization should not fail");
695        let mut hasher = DefaultHasher::new();
696        message.hash(&mut hasher);
697        let tracking_id = hasher.finish().to_le_bytes();
698        Self {
699            tracking_id,
700            kind: ConsensusTransactionKind::RandomnessDkgMessage(authority, message),
701        }
702    }
703    pub fn new_randomness_dkg_confirmation(
704        authority: AuthorityName,
705        versioned_confirmation: &VersionedDkgConfirmation,
706    ) -> Self {
707        let confirmation =
708            bcs::to_bytes(versioned_confirmation).expect("message serialization should not fail");
709        let mut hasher = DefaultHasher::new();
710        confirmation.hash(&mut hasher);
711        let tracking_id = hasher.finish().to_le_bytes();
712        Self {
713            tracking_id,
714            kind: ConsensusTransactionKind::RandomnessDkgConfirmation(authority, confirmation),
715        }
716    }
717
718    pub fn new_execution_time_observation(observation: ExecutionTimeObservation) -> Self {
719        let mut hasher = DefaultHasher::new();
720        observation.hash(&mut hasher);
721        let tracking_id = hasher.finish().to_le_bytes();
722        Self {
723            tracking_id,
724            kind: ConsensusTransactionKind::ExecutionTimeObservation(observation),
725        }
726    }
727
728    pub fn new_update_transaction_deny_config(msg: SharedTransactionDenyConfig) -> Self {
729        let mut hasher = DefaultHasher::new();
730        msg.hash(&mut hasher);
731        let tracking_id = hasher.finish().to_le_bytes();
732        Self {
733            tracking_id,
734            kind: ConsensusTransactionKind::UpdateTransactionDenyConfig(Box::new(msg)),
735        }
736    }
737
738    pub fn get_tracking_id(&self) -> u64 {
739        (&self.tracking_id[..])
740            .read_u64::<BigEndian>()
741            .unwrap_or_default()
742    }
743
744    pub fn key(&self) -> ConsensusTransactionKey {
745        match &self.kind {
746            ConsensusTransactionKind::CertifiedTransaction(cert) => {
747                ConsensusTransactionKey::Certificate(*cert.digest())
748            }
749            ConsensusTransactionKind::CheckpointSignature(data) => {
750                ConsensusTransactionKey::CheckpointSignature(
751                    data.summary.auth_sig().authority,
752                    data.summary.sequence_number,
753                )
754            }
755            ConsensusTransactionKind::CheckpointSignatureV2(data) => {
756                ConsensusTransactionKey::CheckpointSignatureV2(
757                    data.summary.auth_sig().authority,
758                    data.summary.sequence_number,
759                    *data.summary.digest(),
760                )
761            }
762            ConsensusTransactionKind::EndOfPublish(authority) => {
763                ConsensusTransactionKey::EndOfPublish(*authority)
764            }
765            ConsensusTransactionKind::CapabilityNotification(cap) => {
766                ConsensusTransactionKey::CapabilityNotification(cap.authority, cap.generation)
767            }
768            ConsensusTransactionKind::CapabilityNotificationV2(cap) => {
769                ConsensusTransactionKey::CapabilityNotification(cap.authority, cap.generation)
770            }
771            ConsensusTransactionKind::NewJWKFetched(authority, id, key) => {
772                ConsensusTransactionKey::NewJWKFetched(Box::new((
773                    *authority,
774                    id.clone(),
775                    key.clone(),
776                )))
777            }
778            ConsensusTransactionKind::RandomnessStateUpdate(_, _) => {
779                debug_fatal!(
780                    "there should never be a RandomnessStateUpdate with SequencedConsensusTransactionKind::External"
781                );
782                ConsensusTransactionKey::RandomnessStateUpdate
783            }
784            ConsensusTransactionKind::RandomnessDkgMessage(authority, _) => {
785                ConsensusTransactionKey::RandomnessDkgMessage(*authority)
786            }
787            ConsensusTransactionKind::RandomnessDkgConfirmation(authority, _) => {
788                ConsensusTransactionKey::RandomnessDkgConfirmation(*authority)
789            }
790            ConsensusTransactionKind::UserTransaction(tx) => {
791                // Use the same key format as ConsensusTransactionKind::CertifiedTransaction,
792                // because existing usages of ConsensusTransactionKey should not differentiate
793                // between CertifiedTransaction and UserTransaction.
794                ConsensusTransactionKey::Certificate(*tx.digest())
795            }
796            ConsensusTransactionKind::UserTransactionV2(tx) => {
797                // Use the same key format as ConsensusTransactionKind::CertifiedTransaction,
798                // because existing usages of ConsensusTransactionKey should not differentiate
799                // between CertifiedTransaction and UserTransactionV2.
800                ConsensusTransactionKey::Certificate(*tx.tx().digest())
801            }
802            ConsensusTransactionKind::ExecutionTimeObservation(msg) => {
803                ConsensusTransactionKey::ExecutionTimeObservation(msg.authority, msg.generation)
804            }
805            ConsensusTransactionKind::UpdateTransactionDenyConfig(msg) => {
806                ConsensusTransactionKey::UpdateTransactionDenyConfig(
807                    msg.authority(),
808                    msg.generation(),
809                )
810            }
811        }
812    }
813
814    pub fn is_dkg(&self) -> bool {
815        matches!(
816            self.kind,
817            ConsensusTransactionKind::RandomnessDkgMessage(_, _)
818                | ConsensusTransactionKind::RandomnessDkgConfirmation(_, _)
819        )
820    }
821
822    pub fn is_user_transaction(&self) -> bool {
823        // CertifiedTransaction and UserTransaction are unused and not accepted now.
824        matches!(self.kind, ConsensusTransactionKind::UserTransactionV2(_))
825    }
826
827    pub fn is_end_of_publish(&self) -> bool {
828        matches!(self.kind, ConsensusTransactionKind::EndOfPublish(_))
829    }
830}
831
832#[test]
833fn test_shared_transaction_deny_config_bcs_roundtrip() {
834    use crate::base_types::{ObjectID, SuiAddress};
835    use crate::transaction_deny_rules::TransactionDenyRules;
836    use std::collections::BTreeSet;
837
838    let authority = AuthorityName::new([7u8; 96]);
839    let mut rules = TransactionDenyRules::default();
840    rules.object_deny_list.insert(ObjectID::new([1u8; 32]));
841    rules.object_deny_list.insert(ObjectID::new([2u8; 32]));
842    rules
843        .address_deny_list
844        .insert(SuiAddress::from_bytes([3u8; 32]).unwrap());
845    rules.user_transaction_disabled = true;
846    let mut providers: BTreeSet<String> = BTreeSet::new();
847    providers.insert("Google".to_string());
848    rules.zklogin_disabled_providers = providers;
849
850    let msg = SharedTransactionDenyConfig::V1(SharedTransactionDenyConfigV1 {
851        authority,
852        generation: 12345,
853        rules: Some(rules),
854    });
855    let consensus_tx = ConsensusTransaction::new_update_transaction_deny_config(msg.clone());
856
857    // Round-trip the full ConsensusTransaction wire format. This is exactly what
858    // mysticeti_adapter sends and consensus_validator parses on receive.
859    let bytes = bcs::to_bytes(&consensus_tx).unwrap();
860    let decoded: ConsensusTransaction = bcs::from_bytes(&bytes).unwrap();
861    assert_eq!(decoded.tracking_id, consensus_tx.tracking_id);
862    let decoded_msg = match decoded.kind {
863        ConsensusTransactionKind::UpdateTransactionDenyConfig(m) => *m,
864        other => panic!("unexpected kind: {other:?}"),
865    };
866    assert_eq!(decoded_msg, msg);
867
868    // The dedup key must be (authority, generation), letting receivers ignore
869    // out-of-order replays of older generations from the same authority.
870    let key = consensus_tx.key();
871    match key {
872        ConsensusTransactionKey::UpdateTransactionDenyConfig(name, generation) => {
873            assert_eq!(name, authority);
874            assert_eq!(generation, 12345);
875        }
876        other => panic!("unexpected key: {other:?}"),
877    }
878}
879
880#[test]
881fn test_shared_transaction_deny_config_withdrawal_bcs_roundtrip() {
882    let authority = AuthorityName::new([9u8; 96]);
883    let msg = SharedTransactionDenyConfig::V1(SharedTransactionDenyConfigV1 {
884        authority,
885        generation: 99,
886        rules: None,
887    });
888    let consensus_tx = ConsensusTransaction::new_update_transaction_deny_config(msg.clone());
889    let bytes = bcs::to_bytes(&consensus_tx).unwrap();
890    let decoded: ConsensusTransaction = bcs::from_bytes(&bytes).unwrap();
891    let decoded_msg = match decoded.kind {
892        ConsensusTransactionKind::UpdateTransactionDenyConfig(m) => *m,
893        other => panic!("unexpected kind: {other:?}"),
894    };
895    assert_eq!(decoded_msg, msg);
896    assert!(decoded_msg.rules().is_none());
897}
898
899#[test]
900fn test_jwk_compatibility() {
901    // Ensure that the JWK and JwkId structs in fastcrypto do not change formats.
902    // If this test breaks DO NOT JUST UPDATE THE EXPECTED BYTES. Instead, add a local JWK or
903    // JwkId struct that mirrors the fastcrypto struct, use it in AuthenticatorStateUpdate, and
904    // add Into/From as necessary.
905    let jwk = JWK {
906        kty: "a".to_string(),
907        e: "b".to_string(),
908        n: "c".to_string(),
909        alg: "d".to_string(),
910    };
911
912    let expected_jwk_bytes = vec![1, 97, 1, 98, 1, 99, 1, 100];
913    let jwk_bcs = bcs::to_bytes(&jwk).unwrap();
914    assert_eq!(jwk_bcs, expected_jwk_bytes);
915
916    let id = JwkId {
917        iss: "abc".to_string(),
918        kid: "def".to_string(),
919    };
920
921    let expected_id_bytes = vec![3, 97, 98, 99, 3, 100, 101, 102];
922    let id_bcs = bcs::to_bytes(&id).unwrap();
923    assert_eq!(id_bcs, expected_id_bytes);
924}