use super::CheckpointContentsDigest;
use super::CheckpointDigest;
use super::Digest;
use super::GasCostSummary;
use super::Object;
use super::SignedTransaction;
use super::TransactionDigest;
use super::TransactionEffects;
use super::TransactionEffectsDigest;
use super::TransactionEvents;
use super::UserSignature;
use super::ValidatorAggregatedSignature;
use super::ValidatorCommitteeMember;
pub type CheckpointSequenceNumber = u64;
pub type CheckpointTimestamp = u64;
pub type EpochId = u64;
pub type StakeUnit = u64;
pub type ProtocolVersion = u64;
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(
feature = "schemars",
derive(schemars::JsonSchema),
schemars(tag = "type", rename_all = "snake_case")
)]
#[cfg_attr(test, derive(test_strategy::Arbitrary))]
pub enum CheckpointCommitment {
EcmhLiveObjectSet { digest: Digest },
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(
feature = "serde",
derive(serde_derive::Serialize, serde_derive::Deserialize)
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[cfg_attr(test, derive(test_strategy::Arbitrary))]
pub struct EndOfEpochData {
pub next_epoch_committee: Vec<ValidatorCommitteeMember>,
#[cfg_attr(feature = "serde", serde(with = "crate::_serde::ReadableDisplay"))]
#[cfg_attr(feature = "schemars", schemars(with = "crate::_schemars::U64"))]
pub next_epoch_protocol_version: ProtocolVersion,
pub epoch_commitments: Vec<CheckpointCommitment>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[cfg_attr(test, derive(test_strategy::Arbitrary))]
pub struct CheckpointSummary {
#[cfg_attr(feature = "schemars", schemars(with = "crate::_schemars::U64"))]
pub epoch: EpochId,
#[cfg_attr(feature = "schemars", schemars(with = "crate::_schemars::U64"))]
pub sequence_number: CheckpointSequenceNumber,
#[cfg_attr(feature = "schemars", schemars(with = "crate::_schemars::U64"))]
pub network_total_transactions: u64,
pub content_digest: CheckpointContentsDigest,
pub previous_digest: Option<CheckpointDigest>,
pub epoch_rolling_gas_cost_summary: GasCostSummary,
#[cfg_attr(feature = "schemars", schemars(with = "crate::_schemars::U64"))]
pub timestamp_ms: CheckpointTimestamp,
#[cfg_attr(
feature = "schemars",
schemars(with = "Option<Vec<CheckpointCommitment>>")
)]
pub checkpoint_commitments: Vec<CheckpointCommitment>,
pub end_of_epoch_data: Option<EndOfEpochData>,
#[cfg_attr(
feature = "schemars",
schemars(with = "Option<crate::_schemars::Base64>")
)]
pub version_specific_data: Vec<u8>,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(
feature = "serde",
derive(serde_derive::Serialize, serde_derive::Deserialize)
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[cfg_attr(test, derive(test_strategy::Arbitrary))]
pub struct SignedCheckpointSummary {
pub checkpoint: CheckpointSummary,
pub signature: ValidatorAggregatedSignature,
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[cfg_attr(test, derive(test_strategy::Arbitrary))]
pub struct CheckpointContents(
#[cfg_attr(test, any(proptest::collection::size_range(0..=2).lift()))]
Vec<CheckpointTransactionInfo>,
);
impl CheckpointContents {
pub fn new(transactions: Vec<CheckpointTransactionInfo>) -> Self {
Self(transactions)
}
pub fn transactions(&self) -> &[CheckpointTransactionInfo] {
&self.0
}
pub fn into_v1(self) -> Vec<CheckpointTransactionInfo> {
self.0
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(
feature = "serde",
derive(serde_derive::Serialize, serde_derive::Deserialize)
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[cfg_attr(test, derive(test_strategy::Arbitrary))]
pub struct CheckpointTransactionInfo {
pub transaction: TransactionDigest,
pub effects: TransactionEffectsDigest,
#[cfg_attr(test, any(proptest::collection::size_range(0..=2).lift()))]
pub signatures: Vec<UserSignature>,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(
feature = "serde",
derive(serde_derive::Serialize, serde_derive::Deserialize)
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[cfg_attr(test, derive(test_strategy::Arbitrary))]
pub struct CheckpointData {
pub checkpoint_summary: SignedCheckpointSummary,
pub checkpoint_contents: CheckpointContents,
#[cfg_attr(test, any(proptest::collection::size_range(0..=1).lift()))]
pub transactions: Vec<CheckpointTransaction>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(
feature = "serde",
derive(serde_derive::Serialize, serde_derive::Deserialize)
)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[cfg_attr(test, derive(test_strategy::Arbitrary))]
pub struct CheckpointTransaction {
#[cfg_attr(
feature = "serde",
serde(with = "::serde_with::As::<crate::_serde::SignedTransactionWithIntentMessage>")
)]
#[cfg_attr(feature = "schemars", schemars(with = "SignedTransaction"))]
pub transaction: SignedTransaction,
pub effects: TransactionEffects,
pub events: Option<TransactionEvents>,
#[cfg_attr(test, any(proptest::collection::size_range(0..=2).lift()))]
pub input_objects: Vec<Object>,
#[cfg_attr(test, any(proptest::collection::size_range(0..=2).lift()))]
pub output_objects: Vec<Object>,
}
#[cfg(feature = "serde")]
#[cfg_attr(doc_cfg, doc(cfg(feature = "serde")))]
mod serialization {
use super::*;
use serde::Deserialize;
use serde::Deserializer;
use serde::Serialize;
use serde::Serializer;
#[derive(serde_derive::Serialize)]
struct ReadableCheckpointSummaryRef<'a> {
#[serde(with = "crate::_serde::ReadableDisplay")]
epoch: &'a EpochId,
#[serde(with = "crate::_serde::ReadableDisplay")]
sequence_number: &'a CheckpointSequenceNumber,
#[serde(with = "crate::_serde::ReadableDisplay")]
network_total_transactions: &'a u64,
content_digest: &'a CheckpointContentsDigest,
#[serde(skip_serializing_if = "Option::is_none")]
previous_digest: &'a Option<CheckpointDigest>,
epoch_rolling_gas_cost_summary: &'a GasCostSummary,
#[serde(with = "crate::_serde::ReadableDisplay")]
timestamp_ms: &'a CheckpointTimestamp,
#[serde(skip_serializing_if = "Vec::is_empty")]
checkpoint_commitments: &'a Vec<CheckpointCommitment>,
#[serde(skip_serializing_if = "Option::is_none")]
end_of_epoch_data: &'a Option<EndOfEpochData>,
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(with = "::serde_with::As::<crate::_serde::Base64Encoded>")]
version_specific_data: &'a Vec<u8>,
}
#[derive(serde_derive::Deserialize)]
struct ReadableCheckpointSummary {
#[serde(with = "crate::_serde::ReadableDisplay")]
epoch: EpochId,
#[serde(with = "crate::_serde::ReadableDisplay")]
sequence_number: CheckpointSequenceNumber,
#[serde(with = "crate::_serde::ReadableDisplay")]
network_total_transactions: u64,
content_digest: CheckpointContentsDigest,
#[serde(default)]
previous_digest: Option<CheckpointDigest>,
epoch_rolling_gas_cost_summary: GasCostSummary,
#[serde(with = "crate::_serde::ReadableDisplay")]
timestamp_ms: CheckpointTimestamp,
#[serde(default)]
checkpoint_commitments: Vec<CheckpointCommitment>,
#[serde(default)]
end_of_epoch_data: Option<EndOfEpochData>,
#[serde(default)]
#[serde(with = "::serde_with::As::<crate::_serde::Base64Encoded>")]
version_specific_data: Vec<u8>,
}
#[derive(serde_derive::Serialize)]
struct BinaryCheckpointSummaryRef<'a> {
epoch: &'a EpochId,
sequence_number: &'a CheckpointSequenceNumber,
network_total_transactions: &'a u64,
content_digest: &'a CheckpointContentsDigest,
previous_digest: &'a Option<CheckpointDigest>,
epoch_rolling_gas_cost_summary: &'a GasCostSummary,
timestamp_ms: &'a CheckpointTimestamp,
checkpoint_commitments: &'a Vec<CheckpointCommitment>,
end_of_epoch_data: &'a Option<EndOfEpochData>,
version_specific_data: &'a Vec<u8>,
}
#[derive(serde_derive::Deserialize)]
struct BinaryCheckpointSummary {
epoch: EpochId,
sequence_number: CheckpointSequenceNumber,
network_total_transactions: u64,
content_digest: CheckpointContentsDigest,
previous_digest: Option<CheckpointDigest>,
epoch_rolling_gas_cost_summary: GasCostSummary,
timestamp_ms: CheckpointTimestamp,
checkpoint_commitments: Vec<CheckpointCommitment>,
end_of_epoch_data: Option<EndOfEpochData>,
version_specific_data: Vec<u8>,
}
impl Serialize for CheckpointSummary {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let Self {
epoch,
sequence_number,
network_total_transactions,
content_digest,
previous_digest,
epoch_rolling_gas_cost_summary,
timestamp_ms,
checkpoint_commitments,
end_of_epoch_data,
version_specific_data,
} = self;
if serializer.is_human_readable() {
let readable = ReadableCheckpointSummaryRef {
epoch,
sequence_number,
network_total_transactions,
content_digest,
previous_digest,
epoch_rolling_gas_cost_summary,
timestamp_ms,
checkpoint_commitments,
end_of_epoch_data,
version_specific_data,
};
readable.serialize(serializer)
} else {
let binary = BinaryCheckpointSummaryRef {
epoch,
sequence_number,
network_total_transactions,
content_digest,
previous_digest,
epoch_rolling_gas_cost_summary,
timestamp_ms,
checkpoint_commitments,
end_of_epoch_data,
version_specific_data,
};
binary.serialize(serializer)
}
}
}
impl<'de> Deserialize<'de> for CheckpointSummary {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
if deserializer.is_human_readable() {
let ReadableCheckpointSummary {
epoch,
sequence_number,
network_total_transactions,
content_digest,
previous_digest,
epoch_rolling_gas_cost_summary,
timestamp_ms,
checkpoint_commitments,
end_of_epoch_data,
version_specific_data,
} = Deserialize::deserialize(deserializer)?;
Ok(Self {
epoch,
sequence_number,
network_total_transactions,
content_digest,
previous_digest,
epoch_rolling_gas_cost_summary,
timestamp_ms,
checkpoint_commitments,
end_of_epoch_data,
version_specific_data,
})
} else {
let BinaryCheckpointSummary {
epoch,
sequence_number,
network_total_transactions,
content_digest,
previous_digest,
epoch_rolling_gas_cost_summary,
timestamp_ms,
checkpoint_commitments,
end_of_epoch_data,
version_specific_data,
} = Deserialize::deserialize(deserializer)?;
Ok(Self {
epoch,
sequence_number,
network_total_transactions,
content_digest,
previous_digest,
epoch_rolling_gas_cost_summary,
timestamp_ms,
checkpoint_commitments,
end_of_epoch_data,
version_specific_data,
})
}
}
}
impl Serialize for CheckpointContents {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
use serde::ser::SerializeSeq;
use serde::ser::SerializeTupleVariant;
if serializer.is_human_readable() {
serializer.serialize_newtype_struct("CheckpointContents", &self.0)
} else {
#[derive(serde_derive::Serialize)]
struct Digests<'a> {
transaction: &'a TransactionDigest,
effects: &'a TransactionEffectsDigest,
}
struct DigestSeq<'a>(&'a CheckpointContents);
impl Serialize for DigestSeq<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut seq = serializer.serialize_seq(Some(self.0 .0.len()))?;
for txn in &self.0 .0 {
let digests = Digests {
transaction: &txn.transaction,
effects: &txn.effects,
};
seq.serialize_element(&digests)?;
}
seq.end()
}
}
struct SignatureSeq<'a>(&'a CheckpointContents);
impl Serialize for SignatureSeq<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut seq = serializer.serialize_seq(Some(self.0 .0.len()))?;
for txn in &self.0 .0 {
seq.serialize_element(&txn.signatures)?;
}
seq.end()
}
}
let mut s = serializer.serialize_tuple_variant("CheckpointContents", 0, "V1", 2)?;
s.serialize_field(&DigestSeq(self))?;
s.serialize_field(&SignatureSeq(self))?;
s.end()
}
}
}
#[derive(serde_derive::Deserialize)]
struct ExecutionDigests {
transaction: TransactionDigest,
effects: TransactionEffectsDigest,
}
#[derive(serde_derive::Deserialize)]
struct BinaryContentsV1 {
digests: Vec<ExecutionDigests>,
signatures: Vec<Vec<UserSignature>>,
}
#[derive(serde_derive::Deserialize)]
enum BinaryContents {
V1(BinaryContentsV1),
}
impl<'de> Deserialize<'de> for CheckpointContents {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
if deserializer.is_human_readable() {
let contents: Vec<CheckpointTransactionInfo> =
Deserialize::deserialize(deserializer)?;
Ok(Self(contents))
} else {
let BinaryContents::V1(BinaryContentsV1 {
digests,
signatures,
}) = Deserialize::deserialize(deserializer)?;
if digests.len() != signatures.len() {
return Err(serde::de::Error::custom(
"must have same number of signatures as transactions",
));
}
Ok(Self(
digests
.into_iter()
.zip(signatures)
.map(
|(
ExecutionDigests {
transaction,
effects,
},
signatures,
)| CheckpointTransactionInfo {
transaction,
effects,
signatures,
},
)
.collect(),
))
}
}
}
#[derive(serde_derive::Serialize, serde_derive::Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
enum ReadableCommitment {
EcmhLiveObjectSet { digest: Digest },
}
#[derive(serde_derive::Serialize, serde_derive::Deserialize)]
enum BinaryCommitment {
EcmhLiveObjectSet { digest: Digest },
}
impl Serialize for CheckpointCommitment {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
if serializer.is_human_readable() {
let readable = match *self {
CheckpointCommitment::EcmhLiveObjectSet { digest } => {
ReadableCommitment::EcmhLiveObjectSet { digest }
}
};
readable.serialize(serializer)
} else {
let binary = match *self {
CheckpointCommitment::EcmhLiveObjectSet { digest } => {
BinaryCommitment::EcmhLiveObjectSet { digest }
}
};
binary.serialize(serializer)
}
}
}
impl<'de> Deserialize<'de> for CheckpointCommitment {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
if deserializer.is_human_readable() {
Ok(match ReadableCommitment::deserialize(deserializer)? {
ReadableCommitment::EcmhLiveObjectSet { digest } => {
Self::EcmhLiveObjectSet { digest }
}
})
} else {
Ok(match BinaryCommitment::deserialize(deserializer)? {
BinaryCommitment::EcmhLiveObjectSet { digest } => {
Self::EcmhLiveObjectSet { digest }
}
})
}
}
}
#[cfg(test)]
mod test {
use super::*;
use base64ct::Base64;
use base64ct::Encoding;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_test::wasm_bindgen_test as test;
#[test]
fn signed_checkpoint_fixture() {
const FIXTURES: &[&str] = &[
"CgAAAAAAAAAUAAAAAAAAABUAAAAAAAAAIJ6CIMG/6Un4MKNM8h+R9r8bQ6dNTk0WZxBMUQH1XFQBASCWUVucdQkje+4YbXVpvQZcg74nndL1NK7ccj1dDR04agAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACwAAAAAAAAAAAAAKAAAAAAAAAKOonlp6Vf8dJEjQYa/VyigZruaZwSwu3u/ZZVCsdrS1iaGPIAERZcNnfM75tOh10hI6MAAAAQAAAAAAAAAQAAAAAAA=",
"AgAAAAAAAAAFAAAAAAAAAAYAAAAAAAAAIINaPEm+WRQV2vGcPR9fe6fYhxl48GpqB+DqDYQqRHkuASBe+6BDLHSRCMiWqBkvVMqWXPWUsZnpc2gbOVdre3vnowAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAwAAAAAAAAAAAQFgqGJldzxWMt2CZow1QiLmDf0RdLE6udu0bVdc1xaExX37NByF27rDH5C1DF+mkpLdA6YZnXMvuUw+zoWo71qe2DTdIDU4AcNaSUE3OoEHceuT+fBa6dMib3yDkkhmOZLyECcAAAAAAAAkAAAAAAAAAAAAAgAAAAAAAACvljn+1LWFSpu3PGx4BlIlVZq7blFK+fV7SOPEU0z9nz7lgkv8a12EA9R0tGm8hEYSOjAAAAEAAAAAAAAAEAAAAAAA",
];
for fixture in FIXTURES {
let bcs = Base64::decode_vec(fixture).unwrap();
let checkpoint: SignedCheckpointSummary = bcs::from_bytes(&bcs).unwrap();
let bytes = bcs::to_bytes(&checkpoint).unwrap();
assert_eq!(bcs, bytes);
let json = serde_json::to_string_pretty(&checkpoint).unwrap();
println!("{json}");
}
}
#[test]
fn contents_fixture() {
let fixture ="AAEgp6oAB8Qadn8+FqtdqeDIp8ViQNOZpMKs44MN0N5y7zIgqn5dKR1+8poL0pLNwRo/2knMnodwMTEDhqYL03kdewQBAWEAgpORkfH6ewjfFQYZJhmjkYq0/B3Set4mLJX/G0wUPb/V4H41gJipYu4I6ToyixnEuPQWxHKLckhNn+0UmI+pAJ9GegzEh0q2HWABmFMpFoPw0229dCfzWNOhHW5bes4H";
let bcs = Base64::decode_vec(fixture).unwrap();
let contents: CheckpointContents = bcs::from_bytes(&bcs).unwrap();
let bytes = bcs::to_bytes(&contents).unwrap();
assert_eq!(bcs, bytes);
let json = serde_json::to_string_pretty(&contents).unwrap();
println!("{json}");
}
}
}