use super::Digest;
use super::GasCostSummary;
use super::Object;
use super::SignedTransaction;
use super::TransactionEffects;
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 = "serde",
derive(serde_derive::Serialize, serde_derive::Deserialize)
)]
#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
#[non_exhaustive]
pub enum CheckpointCommitment {
EcmhLiveObjectSet { digest: Digest },
CheckpointArtifacts { digest: Digest },
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(
feature = "serde",
derive(serde_derive::Serialize, serde_derive::Deserialize)
)]
#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
pub struct EndOfEpochData {
#[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=1).lift()))]
pub next_epoch_committee: Vec<ValidatorCommitteeMember>,
pub next_epoch_protocol_version: ProtocolVersion,
#[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=1).lift()))]
pub epoch_commitments: Vec<CheckpointCommitment>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(
feature = "serde",
derive(serde_derive::Serialize, serde_derive::Deserialize)
)]
#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
pub struct CheckpointSummary {
pub epoch: EpochId,
pub sequence_number: CheckpointSequenceNumber,
pub network_total_transactions: u64,
pub content_digest: Digest,
pub previous_digest: Option<Digest>,
pub epoch_rolling_gas_cost_summary: GasCostSummary,
pub timestamp_ms: CheckpointTimestamp,
#[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=1).lift()))]
pub checkpoint_commitments: Vec<CheckpointCommitment>,
pub end_of_epoch_data: Option<EndOfEpochData>,
pub version_specific_data: Vec<u8>,
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(
feature = "serde",
derive(serde_derive::Serialize, serde_derive::Deserialize)
)]
#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
pub struct SignedCheckpointSummary {
pub checkpoint: CheckpointSummary,
pub signature: ValidatorAggregatedSignature,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CheckpointContents {
version: usize,
transactions: Vec<CheckpointTransactionInfo>,
}
impl CheckpointContents {
pub fn new_v1(mut transactions: Vec<CheckpointTransactionInfo>) -> Self {
transactions
.iter_mut()
.flat_map(|t| t.signatures.iter_mut())
.for_each(|(_, v)| *v = None);
Self {
version: 1,
transactions,
}
}
pub fn new_v2(transactions: Vec<CheckpointTransactionInfo>) -> Self {
Self {
version: 2,
transactions,
}
}
pub fn transactions(&self) -> &[CheckpointTransactionInfo] {
&self.transactions
}
pub fn version(&self) -> usize {
self.version
}
}
#[cfg(feature = "proptest")]
impl proptest::arbitrary::Arbitrary for CheckpointContents {
type Parameters = ();
type Strategy = proptest::strategy::BoxedStrategy<Self>;
fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy {
use proptest::collection::vec;
use proptest::prelude::*;
use proptest::strategy::Strategy;
(
any::<bool>(),
vec(any::<CheckpointTransactionInfo>(), 0..=2),
)
.prop_map(|(version, txns)| match version {
false => Self::new_v1(txns),
true => Self::new_v2(txns),
})
.boxed()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
pub struct CheckpointTransactionInfo {
transaction: Digest,
effects: Digest,
#[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=2).lift()))]
signatures: Vec<(UserSignature, Option<u64>)>,
}
impl CheckpointTransactionInfo {
pub fn new(transaction: Digest, effects: Digest, signatures: Vec<UserSignature>) -> Self {
Self {
transaction,
effects,
signatures: signatures.into_iter().map(|s| (s, None)).collect(),
}
}
pub fn new_with_address_aliases_versions(
transaction: Digest,
effects: Digest,
signatures: Vec<(UserSignature, Option<u64>)>,
) -> Self {
Self {
transaction,
effects,
signatures,
}
}
pub fn transaction(&self) -> &Digest {
&self.transaction
}
pub fn effects(&self) -> &Digest {
&self.effects
}
pub fn signatures(&self) -> impl Iterator<Item = &UserSignature> {
self.signatures.iter().map(|(s, _)| s)
}
pub fn signatures_with_address_aliases_versions(
&self,
) -> impl Iterator<Item = (&UserSignature, Option<u64>)> {
self.signatures.iter().map(|(s, v)| (s, *v))
}
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(
feature = "serde",
derive(serde_derive::Serialize, serde_derive::Deserialize)
)]
#[cfg_attr(feature = "proptest", derive(test_strategy::Arbitrary))]
pub struct CheckpointData {
pub checkpoint_summary: SignedCheckpointSummary,
pub checkpoint_contents: CheckpointContents,
#[cfg_attr(feature = "proptest", 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 = "proptest", derive(test_strategy::Arbitrary))]
pub struct CheckpointTransaction {
#[cfg_attr(
feature = "serde",
serde(with = "::serde_with::As::<crate::_serde::SignedTransactionWithIntentMessage>")
)]
pub transaction: SignedTransaction,
pub effects: TransactionEffects,
pub events: Option<TransactionEvents>,
#[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=1).lift()))]
pub input_objects: Vec<Object>,
#[cfg_attr(feature = "proptest", any(proptest::collection::size_range(0..=1).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;
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;
#[derive(serde_derive::Serialize)]
struct Digests<'a> {
transaction: &'a Digest,
effects: &'a Digest,
}
match self.version() {
1 => {
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.transactions.len()))?;
for txn in &self.0.transactions {
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.transactions.len()))?;
for txn in &self.0.transactions {
let sigs: Vec<&UserSignature> = txn.signatures().collect();
seq.serialize_element(&sigs)?;
}
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()
}
2 => {
#[derive(serde_derive::Serialize)]
struct CheckpointTransactionInfoV2<'a> {
digests: Digests<'a>,
signatures: &'a [(UserSignature, Option<u64>)],
}
struct V2<'a>(&'a CheckpointContents);
impl Serialize for V2<'_> {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut seq =
serializer.serialize_seq(Some(self.0.transactions.len()))?;
for txn in &self.0.transactions {
let txn = CheckpointTransactionInfoV2 {
digests: Digests {
transaction: &txn.transaction,
effects: &txn.effects,
},
signatures: &txn.signatures,
};
seq.serialize_element(&txn)?;
}
seq.end()
}
}
let mut s =
serializer.serialize_tuple_variant("CheckpointContents", 1, "V2", 1)?;
s.serialize_field(&V2(self))?;
s.end()
}
_ => {
unreachable!("invalid checkpoint contents version");
}
}
}
}
#[derive(serde_derive::Deserialize)]
struct ExecutionDigests {
transaction: Digest,
effects: Digest,
}
#[derive(serde_derive::Deserialize)]
struct BinaryContentsV1 {
digests: Vec<ExecutionDigests>,
signatures: Vec<Vec<UserSignature>>,
}
#[derive(serde_derive::Deserialize)]
struct BinaryContentsV2 {
transactions: Vec<CheckpointTransactionContents>,
}
#[derive(serde_derive::Deserialize)]
struct CheckpointTransactionContents {
digests: ExecutionDigests,
signatures: Vec<(UserSignature, Option<u64>)>,
}
#[derive(serde_derive::Deserialize)]
enum BinaryContents {
V1(BinaryContentsV1),
V2(BinaryContentsV2),
}
impl<'de> Deserialize<'de> for CheckpointContents {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
match Deserialize::deserialize(deserializer)? {
BinaryContents::V1(BinaryContentsV1 {
digests,
signatures,
}) => {
if digests.len() != signatures.len() {
return Err(serde::de::Error::custom(
"must have same number of signatures as transactions",
));
}
Ok(Self::new_v1(
digests
.into_iter()
.zip(signatures)
.map(
|(
ExecutionDigests {
transaction,
effects,
},
signatures,
)| {
CheckpointTransactionInfo::new(transaction, effects, signatures)
},
)
.collect(),
))
}
BinaryContents::V2(v2) => Ok(Self::new_v2(
v2.transactions
.into_iter()
.map(|info| {
CheckpointTransactionInfo::new_with_address_aliases_versions(
info.digests.transaction,
info.digests.effects,
info.signatures,
)
})
.collect(),
)),
}
}
}
#[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",
"AAAAAAAAAAACAAAAAAAAAAgAAAAAAAAAIJBUX7gl7mh+M/NoHcFa3oR3I+5BFublxXc33/GPUZ79ASAyWbpVsiA3AaeLJkcLPhQy4QKHM66TkJNFPJLaVqfoJQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAjf/gOJgBAAABASDxpIv3q2qAdsaAF16ZXzTSU8tRSJ5ylwIRyOzYsdeZigACAAAAAAAAAAAAALkSpPtV6n0lfTq6upYfSk7ZWw8avL3vaG/tU6s2ELoUKK3ucADvyjsDGNVKkhhGkhI6MAAAAQAAAAAAAAAQAAAAAAA=",
];
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}");
}
}
}