use std::{
fmt::{Debug, Display, Formatter},
slice::Iter,
time::{Duration, SystemTime, UNIX_EPOCH},
};
use anyhow::Result;
use fastcrypto::hash::MultisetHash;
use iota_protocol_config::ProtocolConfig;
use once_cell::sync::OnceCell;
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
use serde_with::serde_as;
use shared_crypto::intent::{Intent, IntentScope};
use tap::TapFallible;
use tracing::warn;
pub use crate::digests::{CheckpointContentsDigest, CheckpointDigest};
use crate::{
accumulator::Accumulator,
base_types::{
AuthorityName, ExecutionData, ExecutionDigests, VerifiedExecutionData, random_object_ref,
},
committee::{Committee, EpochId, ProtocolVersion, StakeUnit},
crypto::{
AccountKeyPair, AggregateAuthoritySignature, AuthoritySignInfo, AuthoritySignInfoTrait,
AuthorityStrongQuorumSignInfo, RandomnessRound, default_hash, get_key_pair,
},
digests::Digest,
effects::{TestEffectsBuilder, TransactionEffectsAPI},
error::{IotaError, IotaResult},
gas::GasCostSummary,
iota_serde::{AsProtocolVersion, BigInt, Readable},
message_envelope::{Envelope, Message, TrustedEnvelope, VerifiedEnvelope},
signature::GenericSignature,
storage::ReadStore,
transaction::{Transaction, TransactionData},
};
pub type CheckpointSequenceNumber = u64;
pub type CheckpointTimestamp = u64;
use iota_metrics::histogram::Histogram;
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CheckpointRequest {
pub sequence_number: Option<CheckpointSequenceNumber>,
pub request_content: bool,
pub certified: bool,
}
#[allow(clippy::large_enum_variant)]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub enum CheckpointSummaryResponse {
Certified(CertifiedCheckpointSummary),
Pending(CheckpointSummary),
}
impl CheckpointSummaryResponse {
pub fn content_digest(&self) -> CheckpointContentsDigest {
match self {
Self::Certified(s) => s.content_digest,
Self::Pending(s) => s.content_digest,
}
}
}
#[allow(clippy::large_enum_variant)]
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CheckpointResponse {
pub checkpoint: Option<CheckpointSummaryResponse>,
pub contents: Option<CheckpointContents>,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
pub struct ECMHLiveObjectSetDigest {
#[schemars(with = "[u8; 32]")]
pub digest: Digest,
}
impl From<fastcrypto::hash::Digest<32>> for ECMHLiveObjectSetDigest {
fn from(digest: fastcrypto::hash::Digest<32>) -> Self {
Self {
digest: Digest::new(digest.digest),
}
}
}
impl Default for ECMHLiveObjectSetDigest {
fn default() -> Self {
Accumulator::default().digest().into()
}
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
pub enum CheckpointCommitment {
ECMHLiveObjectSetDigest(ECMHLiveObjectSetDigest),
}
impl From<ECMHLiveObjectSetDigest> for CheckpointCommitment {
fn from(d: ECMHLiveObjectSetDigest) -> Self {
Self::ECMHLiveObjectSetDigest(d)
}
}
#[serde_as]
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq, JsonSchema)]
#[serde(rename_all = "camelCase")]
pub struct EndOfEpochData {
#[schemars(with = "Vec<(AuthorityName, BigInt<u64>)>")]
#[serde_as(as = "Vec<(_, Readable<BigInt<u64>, _>)>")]
pub next_epoch_committee: Vec<(AuthorityName, StakeUnit)>,
#[schemars(with = "AsProtocolVersion")]
#[serde_as(as = "Readable<AsProtocolVersion, _>")]
pub next_epoch_protocol_version: ProtocolVersion,
pub epoch_commitments: Vec<CheckpointCommitment>,
pub epoch_supply_change: i64,
}
#[derive(Clone, Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct CheckpointSummary {
pub epoch: EpochId,
pub sequence_number: CheckpointSequenceNumber,
pub network_total_transactions: u64,
pub content_digest: CheckpointContentsDigest,
pub previous_digest: Option<CheckpointDigest>,
pub epoch_rolling_gas_cost_summary: GasCostSummary,
pub timestamp_ms: CheckpointTimestamp,
pub checkpoint_commitments: Vec<CheckpointCommitment>,
pub end_of_epoch_data: Option<EndOfEpochData>,
pub version_specific_data: Vec<u8>,
}
impl Message for CheckpointSummary {
type DigestType = CheckpointDigest;
const SCOPE: IntentScope = IntentScope::CheckpointSummary;
fn digest(&self) -> Self::DigestType {
CheckpointDigest::new(default_hash(self))
}
}
impl CheckpointSummary {
pub fn new(
protocol_config: &ProtocolConfig,
epoch: EpochId,
sequence_number: CheckpointSequenceNumber,
network_total_transactions: u64,
transactions: &CheckpointContents,
previous_digest: Option<CheckpointDigest>,
epoch_rolling_gas_cost_summary: GasCostSummary,
end_of_epoch_data: Option<EndOfEpochData>,
timestamp_ms: CheckpointTimestamp,
randomness_rounds: Vec<RandomnessRound>,
) -> CheckpointSummary {
let content_digest = *transactions.digest();
let version_specific_data = match protocol_config
.checkpoint_summary_version_specific_data_as_option()
{
None | Some(0) => Vec::new(),
Some(1) => bcs::to_bytes(&CheckpointVersionSpecificData::V1(
CheckpointVersionSpecificDataV1 { randomness_rounds },
))
.expect("version specific data should serialize"),
_ => unimplemented!("unrecognized version_specific_data version for CheckpointSummary"),
};
Self {
epoch,
sequence_number,
network_total_transactions,
content_digest,
previous_digest,
epoch_rolling_gas_cost_summary,
end_of_epoch_data,
timestamp_ms,
version_specific_data,
checkpoint_commitments: Default::default(),
}
}
pub fn verify_epoch(&self, epoch: EpochId) -> IotaResult {
fp_ensure!(self.epoch == epoch, IotaError::WrongEpoch {
expected_epoch: epoch,
actual_epoch: self.epoch,
});
Ok(())
}
pub fn sequence_number(&self) -> &CheckpointSequenceNumber {
&self.sequence_number
}
pub fn timestamp(&self) -> SystemTime {
UNIX_EPOCH + Duration::from_millis(self.timestamp_ms)
}
pub fn next_epoch_committee(&self) -> Option<&[(AuthorityName, StakeUnit)]> {
self.end_of_epoch_data
.as_ref()
.map(|e| e.next_epoch_committee.as_slice())
}
pub fn report_checkpoint_age_ms(&self, metrics: &Histogram) {
SystemTime::now()
.duration_since(self.timestamp())
.map(|latency| metrics.report(latency.as_millis() as u64))
.tap_err(|err| {
warn!(
checkpoint_seq = self.sequence_number,
"unable to compute checkpoint age: {}", err
)
})
.ok();
}
pub fn is_last_checkpoint_of_epoch(&self) -> bool {
self.end_of_epoch_data.is_some()
}
pub fn version_specific_data(
&self,
config: &ProtocolConfig,
) -> Result<Option<CheckpointVersionSpecificData>> {
match config.checkpoint_summary_version_specific_data_as_option() {
None | Some(0) => Ok(None),
Some(1) => Ok(Some(bcs::from_bytes(&self.version_specific_data)?)),
_ => unimplemented!("unrecognized version_specific_data version in CheckpointSummary"),
}
}
}
impl Display for CheckpointSummary {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
write!(
f,
"CheckpointSummary {{ epoch: {:?}, seq: {:?}, content_digest: {},
epoch_rolling_gas_cost_summary: {:?}}}",
self.epoch,
self.sequence_number,
self.content_digest,
self.epoch_rolling_gas_cost_summary,
)
}
}
pub type CheckpointSummaryEnvelope<S> = Envelope<CheckpointSummary, S>;
pub type CertifiedCheckpointSummary = CheckpointSummaryEnvelope<AuthorityStrongQuorumSignInfo>;
pub type SignedCheckpointSummary = CheckpointSummaryEnvelope<AuthoritySignInfo>;
pub type VerifiedCheckpoint = VerifiedEnvelope<CheckpointSummary, AuthorityStrongQuorumSignInfo>;
pub type TrustedCheckpoint = TrustedEnvelope<CheckpointSummary, AuthorityStrongQuorumSignInfo>;
impl CertifiedCheckpointSummary {
pub fn verify_authority_signatures(&self, committee: &Committee) -> IotaResult {
self.data().verify_epoch(self.auth_sig().epoch)?;
self.auth_sig().verify_secure(
self.data(),
Intent::iota_app(IntentScope::CheckpointSummary),
committee,
)
}
pub fn try_into_verified(self, committee: &Committee) -> IotaResult<VerifiedCheckpoint> {
self.verify_authority_signatures(committee)?;
Ok(VerifiedCheckpoint::new_from_verified(self))
}
pub fn verify_with_contents(
&self,
committee: &Committee,
contents: Option<&CheckpointContents>,
) -> IotaResult {
self.verify_authority_signatures(committee)?;
if let Some(contents) = contents {
let content_digest = *contents.digest();
fp_ensure!(
content_digest == self.data().content_digest,
IotaError::GenericAuthority {
error: format!(
"Checkpoint contents digest mismatch: summary={:?}, received content digest {:?}, received {} transactions",
self.data(),
content_digest,
contents.size()
)
}
);
}
Ok(())
}
pub fn into_summary_and_sequence(self) -> (CheckpointSequenceNumber, CheckpointSummary) {
let summary = self.into_data();
(summary.sequence_number, summary)
}
pub fn get_validator_signature(self) -> AggregateAuthoritySignature {
self.auth_sig().signature.clone()
}
}
impl SignedCheckpointSummary {
pub fn verify_authority_signatures(&self, committee: &Committee) -> IotaResult {
self.data().verify_epoch(self.auth_sig().epoch)?;
self.auth_sig().verify_secure(
self.data(),
Intent::iota_app(IntentScope::CheckpointSummary),
committee,
)
}
pub fn try_into_verified(
self,
committee: &Committee,
) -> IotaResult<VerifiedEnvelope<CheckpointSummary, AuthoritySignInfo>> {
self.verify_authority_signatures(committee)?;
Ok(VerifiedEnvelope::<CheckpointSummary, AuthoritySignInfo>::new_from_verified(self))
}
}
impl VerifiedCheckpoint {
pub fn into_summary_and_sequence(self) -> (CheckpointSequenceNumber, CheckpointSummary) {
self.into_inner().into_summary_and_sequence()
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct CheckpointSignatureMessage {
pub summary: SignedCheckpointSummary,
}
impl CheckpointSignatureMessage {
pub fn verify(&self, committee: &Committee) -> IotaResult {
self.summary.verify_authority_signatures(committee)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub enum CheckpointContents {
V1(CheckpointContentsV1),
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize, JsonSchema)]
pub struct CheckpointContentsV1 {
#[serde(skip)]
digest: OnceCell<CheckpointContentsDigest>,
transactions: Vec<ExecutionDigests>,
user_signatures: Vec<Vec<GenericSignature>>,
}
impl CheckpointContents {
pub fn new_with_digests_and_signatures(
contents: impl IntoIterator<Item = ExecutionDigests>,
user_signatures: Vec<Vec<GenericSignature>>,
) -> Self {
let transactions: Vec<_> = contents.into_iter().collect();
assert_eq!(transactions.len(), user_signatures.len());
Self::V1(CheckpointContentsV1 {
digest: Default::default(),
transactions,
user_signatures,
})
}
pub fn new_with_causally_ordered_execution_data<'a>(
contents: impl IntoIterator<Item = &'a VerifiedExecutionData>,
) -> Self {
let (transactions, user_signatures): (Vec<_>, Vec<_>) = contents
.into_iter()
.map(|data| {
(
data.digests(),
data.transaction.inner().data().tx_signatures().to_owned(),
)
})
.unzip();
assert_eq!(transactions.len(), user_signatures.len());
Self::V1(CheckpointContentsV1 {
digest: Default::default(),
transactions,
user_signatures,
})
}
#[cfg(any(test, feature = "test-utils"))]
pub fn new_with_digests_only_for_tests(
contents: impl IntoIterator<Item = ExecutionDigests>,
) -> Self {
let transactions: Vec<_> = contents.into_iter().collect();
let user_signatures = transactions.iter().map(|_| vec![]).collect();
Self::V1(CheckpointContentsV1 {
digest: Default::default(),
transactions,
user_signatures,
})
}
fn as_v1(&self) -> &CheckpointContentsV1 {
match self {
Self::V1(v) => v,
}
}
fn into_v1(self) -> CheckpointContentsV1 {
match self {
Self::V1(v) => v,
}
}
pub fn iter(&self) -> Iter<'_, ExecutionDigests> {
self.as_v1().transactions.iter()
}
pub fn into_iter_with_signatures(
self,
) -> impl Iterator<Item = (ExecutionDigests, Vec<GenericSignature>)> {
let CheckpointContentsV1 {
transactions,
user_signatures,
..
} = self.into_v1();
transactions.into_iter().zip(user_signatures)
}
pub fn enumerate_transactions(
&self,
ckpt: &CheckpointSummary,
) -> impl Iterator<Item = (u64, &ExecutionDigests)> {
let start = ckpt.network_total_transactions - self.size() as u64;
(0u64..)
.zip(self.iter())
.map(move |(i, digests)| (i + start, digests))
}
pub fn into_inner(self) -> Vec<ExecutionDigests> {
self.into_v1().transactions
}
pub fn inner(&self) -> &[ExecutionDigests] {
&self.as_v1().transactions
}
pub fn size(&self) -> usize {
self.as_v1().transactions.len()
}
pub fn digest(&self) -> &CheckpointContentsDigest {
self.as_v1()
.digest
.get_or_init(|| CheckpointContentsDigest::new(default_hash(self)))
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct FullCheckpointContents {
transactions: Vec<ExecutionData>,
user_signatures: Vec<Vec<GenericSignature>>,
}
impl FullCheckpointContents {
pub fn new_with_causally_ordered_transactions<T>(contents: T) -> Self
where
T: IntoIterator<Item = ExecutionData>,
{
let (transactions, user_signatures): (Vec<_>, Vec<_>) = contents
.into_iter()
.map(|data| {
let sig = data.transaction.data().tx_signatures().to_owned();
(data, sig)
})
.unzip();
assert_eq!(transactions.len(), user_signatures.len());
Self {
transactions,
user_signatures,
}
}
pub fn from_contents_and_execution_data(
contents: CheckpointContents,
execution_data: impl Iterator<Item = ExecutionData>,
) -> Self {
let transactions: Vec<_> = execution_data.collect();
Self {
transactions,
user_signatures: contents.into_v1().user_signatures,
}
}
pub fn from_checkpoint_contents<S>(
store: S,
contents: CheckpointContents,
) -> Result<Option<Self>, crate::storage::error::Error>
where
S: ReadStore,
{
let mut transactions = Vec::with_capacity(contents.size());
for tx in contents.iter() {
if let (Some(t), Some(e)) = (
store.get_transaction(&tx.transaction)?,
store.get_transaction_effects(&tx.transaction)?,
) {
transactions.push(ExecutionData::new((*t).clone().into_inner(), e))
} else {
return Ok(None);
}
}
Ok(Some(Self {
transactions,
user_signatures: contents.into_v1().user_signatures,
}))
}
pub fn iter(&self) -> Iter<'_, ExecutionData> {
self.transactions.iter()
}
pub fn verify_digests(&self, digest: CheckpointContentsDigest) -> Result<()> {
let self_digest = *self.checkpoint_contents().digest();
fp_ensure!(
digest == self_digest,
anyhow::anyhow!(
"checkpoint contents digest {self_digest} does not match expected digest {digest}"
)
);
for tx in self.iter() {
let transaction_digest = tx.transaction.digest();
fp_ensure!(
tx.effects.transaction_digest() == transaction_digest,
anyhow::anyhow!(
"transaction digest {transaction_digest} does not match expected digest {}",
tx.effects.transaction_digest()
)
);
}
Ok(())
}
pub fn checkpoint_contents(&self) -> CheckpointContents {
CheckpointContents::V1(CheckpointContentsV1 {
digest: Default::default(),
transactions: self.transactions.iter().map(|tx| tx.digests()).collect(),
user_signatures: self.user_signatures.clone(),
})
}
pub fn into_checkpoint_contents(self) -> CheckpointContents {
CheckpointContents::V1(CheckpointContentsV1 {
digest: Default::default(),
transactions: self
.transactions
.into_iter()
.map(|tx| tx.digests())
.collect(),
user_signatures: self.user_signatures,
})
}
pub fn size(&self) -> usize {
self.transactions.len()
}
pub fn random_for_testing() -> Self {
let (a, key): (_, AccountKeyPair) = get_key_pair();
let transaction = Transaction::from_data_and_signer(
TransactionData::new_transfer(
a,
random_object_ref(),
a,
random_object_ref(),
100000000000,
100,
),
vec![&key],
);
let effects = TestEffectsBuilder::new(transaction.data()).build();
let exe_data = ExecutionData {
transaction,
effects,
};
FullCheckpointContents::new_with_causally_ordered_transactions(vec![exe_data])
}
}
impl IntoIterator for FullCheckpointContents {
type Item = ExecutionData;
type IntoIter = std::vec::IntoIter<Self::Item>;
fn into_iter(self) -> Self::IntoIter {
self.transactions.into_iter()
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct VerifiedCheckpointContents {
transactions: Vec<VerifiedExecutionData>,
user_signatures: Vec<Vec<GenericSignature>>,
}
impl VerifiedCheckpointContents {
pub fn new_unchecked(contents: FullCheckpointContents) -> Self {
Self {
transactions: contents
.transactions
.into_iter()
.map(VerifiedExecutionData::new_unchecked)
.collect(),
user_signatures: contents.user_signatures,
}
}
pub fn iter(&self) -> Iter<'_, VerifiedExecutionData> {
self.transactions.iter()
}
pub fn transactions(&self) -> &[VerifiedExecutionData] {
&self.transactions
}
pub fn into_inner(self) -> FullCheckpointContents {
FullCheckpointContents {
transactions: self
.transactions
.into_iter()
.map(|tx| tx.into_inner())
.collect(),
user_signatures: self.user_signatures,
}
}
pub fn into_checkpoint_contents(self) -> CheckpointContents {
self.into_inner().into_checkpoint_contents()
}
pub fn into_checkpoint_contents_digest(self) -> CheckpointContentsDigest {
*self.into_inner().into_checkpoint_contents().digest()
}
pub fn num_of_transactions(&self) -> usize {
self.transactions.len()
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum CheckpointVersionSpecificData {
V1(CheckpointVersionSpecificDataV1),
}
impl CheckpointVersionSpecificData {
pub fn as_v1(&self) -> &CheckpointVersionSpecificDataV1 {
match self {
Self::V1(v) => v,
}
}
pub fn into_v1(self) -> CheckpointVersionSpecificDataV1 {
match self {
Self::V1(v) => v,
}
}
pub fn empty_for_tests() -> CheckpointVersionSpecificData {
CheckpointVersionSpecificData::V1(CheckpointVersionSpecificDataV1 {
randomness_rounds: Vec::new(),
})
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct CheckpointVersionSpecificDataV1 {
pub randomness_rounds: Vec<RandomnessRound>,
}
#[cfg(test)]
#[cfg(feature = "test-utils")]
mod tests {
use fastcrypto::traits::KeyPair;
use rand::{SeedableRng, prelude::StdRng};
use super::*;
use crate::{
digests::{ConsensusCommitDigest, TransactionDigest, TransactionEffectsDigest},
transaction::VerifiedTransaction,
utils::make_committee_key,
};
const RNG_SEED: [u8; 32] = [
21, 23, 199, 200, 234, 250, 252, 178, 94, 15, 202, 178, 62, 186, 88, 137, 233, 192, 130,
157, 179, 179, 65, 9, 31, 249, 221, 123, 225, 112, 199, 247,
];
#[test]
fn test_signed_checkpoint() {
let mut rng = StdRng::from_seed(RNG_SEED);
let (keys, committee) = make_committee_key(&mut rng);
let (_, committee2) = make_committee_key(&mut rng);
let set = CheckpointContents::new_with_digests_only_for_tests([ExecutionDigests::random()]);
let signed_checkpoints: Vec<_> = keys
.iter()
.map(|k| {
let name = k.public().into();
SignedCheckpointSummary::new(
committee.epoch,
CheckpointSummary::new(
&ProtocolConfig::get_for_max_version_UNSAFE(),
committee.epoch,
1,
0,
&set,
None,
GasCostSummary::default(),
None,
0,
Vec::new(),
),
k,
name,
)
})
.collect();
signed_checkpoints.iter().for_each(|c| {
c.verify_authority_signatures(&committee)
.expect("signature ok")
});
signed_checkpoints
.iter()
.for_each(|c| assert!(c.verify_authority_signatures(&committee2).is_err()));
}
#[test]
fn test_certified_checkpoint() {
let mut rng = StdRng::from_seed(RNG_SEED);
let (keys, committee) = make_committee_key(&mut rng);
let set = CheckpointContents::new_with_digests_only_for_tests([ExecutionDigests::random()]);
let summary = CheckpointSummary::new(
&ProtocolConfig::get_for_max_version_UNSAFE(),
committee.epoch,
1,
0,
&set,
None,
GasCostSummary::default(),
None,
0,
Vec::new(),
);
let sign_infos: Vec<_> = keys
.iter()
.map(|k| {
let name = k.public().into();
SignedCheckpointSummary::sign(committee.epoch, &summary, k, name)
})
.collect();
let checkpoint_cert =
CertifiedCheckpointSummary::new(summary, sign_infos, &committee).expect("Cert is OK");
assert!(
checkpoint_cert
.verify_with_contents(&committee, Some(&set))
.is_ok()
);
let signed_checkpoints: Vec<_> = keys
.iter()
.map(|k| {
let name = k.public().into();
let set = CheckpointContents::new_with_digests_only_for_tests([
ExecutionDigests::random(),
]);
SignedCheckpointSummary::new(
committee.epoch,
CheckpointSummary::new(
&ProtocolConfig::get_for_max_version_UNSAFE(),
committee.epoch,
1,
0,
&set,
None,
GasCostSummary::default(),
None,
0,
Vec::new(),
),
k,
name,
)
})
.collect();
let summary = signed_checkpoints[0].data().clone();
let sign_infos = signed_checkpoints
.into_iter()
.map(|v| v.into_sig())
.collect();
assert!(
CertifiedCheckpointSummary::new(summary, sign_infos, &committee)
.unwrap()
.verify_authority_signatures(&committee)
.is_err()
)
}
fn generate_test_checkpoint_summary_from_digest(
digest: TransactionDigest,
) -> CheckpointSummary {
CheckpointSummary::new(
&ProtocolConfig::get_for_max_version_UNSAFE(),
1,
2,
10,
&CheckpointContents::new_with_digests_only_for_tests([ExecutionDigests::new(
digest,
TransactionEffectsDigest::ZERO,
)]),
None,
GasCostSummary::default(),
None,
100,
Vec::new(),
)
}
#[test]
fn test_checkpoint_summary_with_different_consensus_digest() {
{
let t1 = VerifiedTransaction::new_consensus_commit_prologue_v1(
1,
2,
100,
ConsensusCommitDigest::default(),
Vec::new(),
);
let t2 = VerifiedTransaction::new_consensus_commit_prologue_v1(
1,
2,
100,
ConsensusCommitDigest::default(),
Vec::new(),
);
let c1 = generate_test_checkpoint_summary_from_digest(*t1.digest());
let c2 = generate_test_checkpoint_summary_from_digest(*t2.digest());
assert_eq!(c1.digest(), c2.digest());
}
{
let t1 = VerifiedTransaction::new_consensus_commit_prologue_v1(
1,
2,
100,
ConsensusCommitDigest::default(),
Vec::new(),
);
let t2 = VerifiedTransaction::new_consensus_commit_prologue_v1(
1,
2,
100,
ConsensusCommitDigest::random(),
Vec::new(),
);
let c1 = generate_test_checkpoint_summary_from_digest(*t1.digest());
let c2 = generate_test_checkpoint_summary_from_digest(*t2.digest());
assert_ne!(c1.digest(), c2.digest());
}
}
}