use std::{
collections::BTreeMap,
fmt::{Debug, Display},
hash::Hash,
marker::PhantomData,
sync::Arc,
};
use anyhow::{ensure, Result};
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
use async_lock::RwLock;
#[cfg(async_executor_impl = "async-std")]
use async_std::task::spawn_blocking;
use bincode::Options;
use committable::{Commitment, CommitmentBoundsArkless, Committable, RawCommitmentBuilder};
use derivative::Derivative;
use jf_vid::{precomputable::Precomputable, VidDisperse as JfVidDisperse, VidScheme};
use rand::Rng;
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[cfg(async_executor_impl = "tokio")]
use tokio::task::spawn_blocking;
use tracing::error;
use vec1::Vec1;
use crate::{
message::{Proposal, UpgradeLock},
simple_certificate::{
QuorumCertificate, TimeoutCertificate, UpgradeCertificate, ViewSyncFinalizeCertificate2,
},
simple_vote::{QuorumData, UpgradeProposalData, VersionedVoteData},
traits::{
block_contents::{
vid_commitment, BlockHeader, BuilderFee, EncodeBytes, TestableBlock,
GENESIS_VID_NUM_STORAGE_NODES,
},
election::Membership,
node_implementation::{ConsensusTime, NodeType, Versions},
signature_key::SignatureKey,
states::TestableState,
BlockPayload,
},
utils::bincode_opts,
vid::{vid_scheme, VidCommitment, VidCommon, VidPrecomputeData, VidSchemeType, VidShare},
vote::{Certificate, HasViewNumber},
};
#[derive(
Copy,
Clone,
Debug,
PartialEq,
Eq,
PartialOrd,
Ord,
Hash,
Serialize,
Deserialize,
CanonicalSerialize,
CanonicalDeserialize,
)]
pub struct ViewNumber(u64);
impl ConsensusTime for ViewNumber {
fn genesis() -> Self {
Self(0)
}
fn new(n: u64) -> Self {
Self(n)
}
fn u64(&self) -> u64 {
self.0
}
}
impl Display for ViewNumber {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl Committable for ViewNumber {
fn commit(&self) -> Commitment<Self> {
let builder = RawCommitmentBuilder::new("View Number Commitment");
builder.u64(self.0).finalize()
}
}
impl std::ops::Add<u64> for ViewNumber {
type Output = ViewNumber;
fn add(self, rhs: u64) -> Self::Output {
Self(self.0 + rhs)
}
}
impl std::ops::AddAssign<u64> for ViewNumber {
fn add_assign(&mut self, rhs: u64) {
self.0 += rhs;
}
}
impl std::ops::Deref for ViewNumber {
type Target = u64;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl std::ops::Sub<u64> for ViewNumber {
type Output = ViewNumber;
fn sub(self, rhs: u64) -> Self::Output {
Self(self.0 - rhs)
}
}
#[derive(custom_debug::Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)]
#[serde(bound = "TYPES: NodeType")]
pub struct DaProposal<TYPES: NodeType> {
pub encoded_transactions: Arc<[u8]>,
pub metadata: <TYPES::BlockPayload as BlockPayload<TYPES>>::Metadata,
pub view_number: TYPES::Time,
}
#[derive(custom_debug::Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)]
#[serde(bound = "TYPES: NodeType")]
pub struct UpgradeProposal<TYPES>
where
TYPES: NodeType,
{
pub upgrade_proposal: UpgradeProposalData<TYPES>,
pub view_number: TYPES::Time,
}
#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)]
pub struct VidDisperse<TYPES: NodeType> {
pub view_number: TYPES::Time,
pub payload_commitment: VidCommitment,
pub shares: BTreeMap<TYPES::SignatureKey, VidShare>,
pub common: VidCommon,
}
impl<TYPES: NodeType> VidDisperse<TYPES> {
pub fn from_membership(
view_number: TYPES::Time,
mut vid_disperse: JfVidDisperse<VidSchemeType>,
membership: &TYPES::Membership,
) -> Self {
let shares = membership
.committee_members(view_number)
.iter()
.map(|node| (node.clone(), vid_disperse.shares.remove(0)))
.collect();
Self {
view_number,
shares,
common: vid_disperse.common,
payload_commitment: vid_disperse.commit,
}
}
#[allow(clippy::panic)]
pub async fn calculate_vid_disperse(
txns: Arc<[u8]>,
membership: &Arc<TYPES::Membership>,
view: TYPES::Time,
precompute_data: Option<VidPrecomputeData>,
) -> Self {
let num_nodes = membership.total_nodes();
let vid_disperse = spawn_blocking(move || {
precompute_data
.map_or_else(
|| vid_scheme(num_nodes).disperse(Arc::clone(&txns)),
|data| vid_scheme(num_nodes).disperse_precompute(Arc::clone(&txns), &data)
)
.unwrap_or_else(|err| panic!("VID disperse failure:(num_storage nodes,payload_byte_len)=({num_nodes},{}) error: {err}", txns.len()))
}).await;
#[cfg(async_executor_impl = "tokio")]
let vid_disperse = vid_disperse.unwrap();
Self::from_membership(view, vid_disperse, membership.as_ref())
}
}
#[derive(custom_debug::Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)]
#[serde(bound(deserialize = ""))]
pub enum ViewChangeEvidence<TYPES: NodeType> {
Timeout(TimeoutCertificate<TYPES>),
ViewSync(ViewSyncFinalizeCertificate2<TYPES>),
}
impl<TYPES: NodeType> ViewChangeEvidence<TYPES> {
pub fn is_valid_for_view(&self, view: &TYPES::Time) -> bool {
match self {
ViewChangeEvidence::Timeout(timeout_cert) => timeout_cert.date().view == *view - 1,
ViewChangeEvidence::ViewSync(view_sync_cert) => view_sync_cert.view_number == *view,
}
}
}
#[derive(Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)]
pub struct VidDisperseShare<TYPES: NodeType> {
pub view_number: TYPES::Time,
pub payload_commitment: VidCommitment,
pub share: VidShare,
pub common: VidCommon,
pub recipient_key: TYPES::SignatureKey,
}
impl<TYPES: NodeType> VidDisperseShare<TYPES> {
pub fn from_vid_disperse(vid_disperse: VidDisperse<TYPES>) -> Vec<Self> {
vid_disperse
.shares
.into_iter()
.map(|(recipient_key, share)| VidDisperseShare {
share,
recipient_key,
view_number: vid_disperse.view_number,
common: vid_disperse.common.clone(),
payload_commitment: vid_disperse.payload_commitment,
})
.collect()
}
pub fn to_proposal(
self,
private_key: &<TYPES::SignatureKey as SignatureKey>::PrivateKey,
) -> Option<Proposal<TYPES, Self>> {
let Ok(signature) =
TYPES::SignatureKey::sign(private_key, self.payload_commitment.as_ref())
else {
error!("VID: failed to sign dispersal share payload");
return None;
};
Some(Proposal {
signature,
_pd: PhantomData,
data: self,
})
}
pub fn to_vid_disperse<'a, I>(mut it: I) -> Option<VidDisperse<TYPES>>
where
I: Iterator<Item = &'a VidDisperseShare<TYPES>>,
{
let first_vid_disperse_share = it.next()?.clone();
let mut share_map = BTreeMap::new();
share_map.insert(
first_vid_disperse_share.recipient_key,
first_vid_disperse_share.share,
);
let mut vid_disperse = VidDisperse {
view_number: first_vid_disperse_share.view_number,
payload_commitment: first_vid_disperse_share.payload_commitment,
common: first_vid_disperse_share.common,
shares: share_map,
};
let _ = it.map(|vid_disperse_share| {
vid_disperse.shares.insert(
vid_disperse_share.recipient_key.clone(),
vid_disperse_share.share.clone(),
)
});
Some(vid_disperse)
}
pub fn to_vid_share_proposals(
vid_disperse_proposal: Proposal<TYPES, VidDisperse<TYPES>>,
) -> Vec<Proposal<TYPES, VidDisperseShare<TYPES>>> {
vid_disperse_proposal
.data
.shares
.into_iter()
.map(|(recipient_key, share)| Proposal {
data: VidDisperseShare {
share,
recipient_key,
view_number: vid_disperse_proposal.data.view_number,
common: vid_disperse_proposal.data.common.clone(),
payload_commitment: vid_disperse_proposal.data.payload_commitment,
},
signature: vid_disperse_proposal.signature.clone(),
_pd: vid_disperse_proposal._pd,
})
.collect()
}
}
#[derive(custom_debug::Debug, Serialize, Deserialize, Clone, Eq, PartialEq, Hash)]
#[serde(bound(deserialize = ""))]
pub struct QuorumProposal<TYPES: NodeType> {
pub block_header: TYPES::BlockHeader,
pub view_number: TYPES::Time,
pub justify_qc: QuorumCertificate<TYPES>,
pub upgrade_certificate: Option<UpgradeCertificate<TYPES>>,
pub proposal_certificate: Option<ViewChangeEvidence<TYPES>>,
}
impl<TYPES: NodeType> HasViewNumber<TYPES> for DaProposal<TYPES> {
fn view_number(&self) -> TYPES::Time {
self.view_number
}
}
impl<TYPES: NodeType> HasViewNumber<TYPES> for VidDisperse<TYPES> {
fn view_number(&self) -> TYPES::Time {
self.view_number
}
}
impl<TYPES: NodeType> HasViewNumber<TYPES> for VidDisperseShare<TYPES> {
fn view_number(&self) -> TYPES::Time {
self.view_number
}
}
impl<TYPES: NodeType> HasViewNumber<TYPES> for QuorumProposal<TYPES> {
fn view_number(&self) -> TYPES::Time {
self.view_number
}
}
impl<TYPES: NodeType> HasViewNumber<TYPES> for UpgradeProposal<TYPES> {
fn view_number(&self) -> TYPES::Time {
self.view_number
}
}
#[derive(Error, Debug, Serialize, Deserialize)]
pub enum BlockError {
#[error("Invalid block header: {0}")]
InvalidBlockHeader(String),
#[error("Inconsistent payload commitment")]
InconsistentPayloadCommitment,
}
pub trait TestableLeaf {
type NodeType: NodeType;
fn create_random_transaction(
&self,
rng: &mut dyn rand::RngCore,
padding: u64,
) -> <<Self::NodeType as NodeType>::BlockPayload as BlockPayload<Self::NodeType>>::Transaction;
}
#[derive(Serialize, Deserialize, Clone, Debug, Derivative, Eq)]
#[serde(bound(deserialize = ""))]
pub struct Leaf<TYPES: NodeType> {
view_number: TYPES::Time,
justify_qc: QuorumCertificate<TYPES>,
parent_commitment: Commitment<Self>,
block_header: TYPES::BlockHeader,
upgrade_certificate: Option<UpgradeCertificate<TYPES>>,
block_payload: Option<TYPES::BlockPayload>,
}
impl<TYPES: NodeType> Leaf<TYPES> {
#[allow(clippy::unused_async)]
pub async fn commit<V: Versions>(
&self,
_upgrade_lock: &UpgradeLock<TYPES, V>,
) -> Commitment<Self> {
<Self as Committable>::commit(self)
}
}
impl<TYPES: NodeType> PartialEq for Leaf<TYPES> {
fn eq(&self, other: &Self) -> bool {
self.view_number == other.view_number
&& self.justify_qc == other.justify_qc
&& self.parent_commitment == other.parent_commitment
&& self.block_header == other.block_header
}
}
impl<TYPES: NodeType> Hash for Leaf<TYPES> {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.view_number.hash(state);
self.justify_qc.hash(state);
self.parent_commitment.hash(state);
self.block_header.hash(state);
}
}
impl<TYPES: NodeType> Display for Leaf<TYPES> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"view: {:?}, height: {:?}, justify: {}",
self.view_number,
self.height(),
self.justify_qc
)
}
}
impl<TYPES: NodeType> QuorumCertificate<TYPES> {
#[must_use]
pub async fn genesis<V: Versions>(
validated_state: &TYPES::ValidatedState,
instance_state: &TYPES::InstanceState,
) -> Self {
let upgrade_lock = UpgradeLock::<TYPES, V>::new();
let genesis_view = <TYPES::Time as ConsensusTime>::genesis();
let data = QuorumData {
leaf_commit: Leaf::genesis(validated_state, instance_state)
.await
.commit(&upgrade_lock)
.await,
};
let versioned_data =
VersionedVoteData::<_, _, V>::new_infallible(data.clone(), genesis_view, &upgrade_lock)
.await;
let bytes: [u8; 32] = versioned_data.commit().into();
Self::new(
data,
Commitment::from_raw(bytes),
genesis_view,
None,
PhantomData,
)
}
}
impl<TYPES: NodeType> Leaf<TYPES> {
#[must_use]
pub async fn genesis(
validated_state: &TYPES::ValidatedState,
instance_state: &TYPES::InstanceState,
) -> Self {
let (payload, metadata) =
TYPES::BlockPayload::from_transactions([], validated_state, instance_state)
.await
.unwrap();
let builder_commitment = payload.builder_commitment(&metadata);
let payload_bytes = payload.encode();
let payload_commitment = vid_commitment(&payload_bytes, GENESIS_VID_NUM_STORAGE_NODES);
let block_header = TYPES::BlockHeader::genesis(
instance_state,
payload_commitment,
builder_commitment,
metadata,
);
let null_quorum_data = QuorumData {
leaf_commit: Commitment::<Leaf<TYPES>>::default_commitment_no_preimage(),
};
let justify_qc = QuorumCertificate::new(
null_quorum_data.clone(),
null_quorum_data.commit(),
<TYPES::Time as ConsensusTime>::genesis(),
None,
PhantomData,
);
Self {
view_number: TYPES::Time::genesis(),
justify_qc,
parent_commitment: null_quorum_data.leaf_commit,
upgrade_certificate: None,
block_header: block_header.clone(),
block_payload: Some(payload),
}
}
pub fn view_number(&self) -> TYPES::Time {
self.view_number
}
pub fn height(&self) -> u64 {
self.block_header.block_number()
}
pub fn justify_qc(&self) -> QuorumCertificate<TYPES> {
self.justify_qc.clone()
}
pub fn upgrade_certificate(&self) -> Option<UpgradeCertificate<TYPES>> {
self.upgrade_certificate.clone()
}
pub fn parent_commitment(&self) -> Commitment<Self> {
self.parent_commitment
}
pub fn block_header(&self) -> &<TYPES as NodeType>::BlockHeader {
&self.block_header
}
pub fn block_header_mut(&mut self) -> &mut <TYPES as NodeType>::BlockHeader {
&mut self.block_header
}
pub fn fill_block_payload(
&mut self,
block_payload: TYPES::BlockPayload,
num_storage_nodes: usize,
) -> Result<(), BlockError> {
let encoded_txns = block_payload.encode();
let commitment = vid_commitment(&encoded_txns, num_storage_nodes);
if commitment != self.block_header.payload_commitment() {
return Err(BlockError::InconsistentPayloadCommitment);
}
self.block_payload = Some(block_payload);
Ok(())
}
pub fn fill_block_payload_unchecked(&mut self, block_payload: TYPES::BlockPayload) {
self.block_payload = Some(block_payload);
}
pub fn block_payload(&self) -> Option<TYPES::BlockPayload> {
self.block_payload.clone()
}
pub fn payload_commitment(&self) -> VidCommitment {
self.block_header().payload_commitment()
}
pub async fn extends_upgrade(
&self,
parent: &Self,
decided_upgrade_certificate: &Arc<RwLock<Option<UpgradeCertificate<TYPES>>>>,
) -> Result<()> {
match (self.upgrade_certificate(), parent.upgrade_certificate()) {
(None | Some(_), None) => {}
(None, Some(parent_cert)) => {
let decided_upgrade_certificate_read = decided_upgrade_certificate.read().await;
ensure!(self.view_number() > parent_cert.data.new_version_first_view
|| (self.view_number() > parent_cert.data.decide_by && decided_upgrade_certificate_read.is_none()),
"The new leaf is missing an upgrade certificate that was present in its parent, and should still be live."
);
}
(Some(cert), Some(parent_cert)) => {
ensure!(cert == parent_cert, "The new leaf does not extend the parent leaf, because it has attached a different upgrade certificate.");
}
}
Ok(())
}
}
impl<TYPES: NodeType> TestableLeaf for Leaf<TYPES>
where
TYPES::ValidatedState: TestableState<TYPES>,
TYPES::BlockPayload: TestableBlock<TYPES>,
{
type NodeType = TYPES;
fn create_random_transaction(
&self,
rng: &mut dyn rand::RngCore,
padding: u64,
) -> <<Self::NodeType as NodeType>::BlockPayload as BlockPayload<Self::NodeType>>::Transaction
{
TYPES::ValidatedState::create_random_transaction(None, rng, padding)
}
}
#[must_use]
pub fn fake_commitment<S: Committable>() -> Commitment<S> {
RawCommitmentBuilder::new("Dummy commitment for arbitrary genesis").finalize()
}
#[must_use]
pub fn random_commitment<S: Committable>(rng: &mut dyn rand::RngCore) -> Commitment<S> {
let random_array: Vec<u8> = (0u8..100u8).map(|_| rng.gen_range(0..255)).collect();
RawCommitmentBuilder::new("Random Commitment")
.constant_str("Random Field")
.var_size_bytes(&random_array)
.finalize()
}
pub fn serialize_signature2<TYPES: NodeType>(
signatures: &<TYPES::SignatureKey as SignatureKey>::QcType,
) -> Vec<u8> {
let mut signatures_bytes = vec![];
signatures_bytes.extend("Yes".as_bytes());
let (sig, proof) = TYPES::SignatureKey::sig_proof(signatures);
let proof_bytes = bincode_opts()
.serialize(&proof.as_bitslice())
.expect("This serialization shouldn't be able to fail");
signatures_bytes.extend("bitvec proof".as_bytes());
signatures_bytes.extend(proof_bytes.as_slice());
let sig_bytes = bincode_opts()
.serialize(&sig)
.expect("This serialization shouldn't be able to fail");
signatures_bytes.extend("aggregated signature".as_bytes());
signatures_bytes.extend(sig_bytes.as_slice());
signatures_bytes
}
impl<TYPES: NodeType> Committable for Leaf<TYPES> {
fn commit(&self) -> committable::Commitment<Self> {
RawCommitmentBuilder::new("leaf commitment")
.u64_field("view number", *self.view_number)
.field("parent leaf commitment", self.parent_commitment)
.field("block header", self.block_header.commit())
.field("justify qc", self.justify_qc.commit())
.optional("upgrade certificate", &self.upgrade_certificate)
.finalize()
}
}
impl<TYPES: NodeType> Leaf<TYPES> {
pub fn from_quorum_proposal(quorum_proposal: &QuorumProposal<TYPES>) -> Self {
let QuorumProposal {
view_number,
justify_qc,
block_header,
upgrade_certificate,
proposal_certificate: _,
} = quorum_proposal;
Leaf {
view_number: *view_number,
justify_qc: justify_qc.clone(),
parent_commitment: justify_qc.date().leaf_commit,
block_header: block_header.clone(),
upgrade_certificate: upgrade_certificate.clone(),
block_payload: None,
}
}
}
pub mod null_block {
#![allow(missing_docs)]
use jf_vid::VidScheme;
use memoize::memoize;
use vbs::version::StaticVersionType;
use crate::{
traits::{
block_contents::BuilderFee,
node_implementation::{NodeType, Versions},
signature_key::BuilderSignatureKey,
BlockPayload,
},
vid::{vid_scheme, VidCommitment},
};
#[memoize(SharedCache, Capacity: 10)]
#[must_use]
pub fn commitment(num_storage_nodes: usize) -> Option<VidCommitment> {
let vid_result = vid_scheme(num_storage_nodes).commit_only(&Vec::new());
match vid_result {
Ok(r) => Some(r),
Err(_) => None,
}
}
#[must_use]
pub fn builder_fee<TYPES: NodeType, V: Versions>(
num_storage_nodes: usize,
version: vbs::version::Version,
) -> Option<BuilderFee<TYPES>> {
const FEE_AMOUNT: u64 = 0;
let (pub_key, priv_key) =
<TYPES::BuilderSignatureKey as BuilderSignatureKey>::generated_from_seed_indexed(
[0_u8; 32], 0,
);
if version >= V::Marketplace::VERSION {
match TYPES::BuilderSignatureKey::sign_sequencing_fee_marketplace(&priv_key, FEE_AMOUNT)
{
Ok(sig) => Some(BuilderFee {
fee_amount: FEE_AMOUNT,
fee_account: pub_key,
fee_signature: sig,
}),
Err(_) => None,
}
} else {
let (_null_block, null_block_metadata) =
<TYPES::BlockPayload as BlockPayload<TYPES>>::empty();
match TYPES::BuilderSignatureKey::sign_fee(
&priv_key,
FEE_AMOUNT,
&null_block_metadata,
&commitment(num_storage_nodes)?,
) {
Ok(sig) => Some(BuilderFee {
fee_amount: FEE_AMOUNT,
fee_account: pub_key,
fee_signature: sig,
}),
Err(_) => None,
}
}
}
}
#[derive(Debug, Eq, PartialEq, Clone)]
pub struct PackedBundle<TYPES: NodeType> {
pub encoded_transactions: Arc<[u8]>,
pub metadata: <TYPES::BlockPayload as BlockPayload<TYPES>>::Metadata,
pub view_number: TYPES::Time,
pub sequencing_fees: Vec1<BuilderFee<TYPES>>,
pub vid_precompute: Option<VidPrecomputeData>,
pub auction_result: Option<TYPES::AuctionResult>,
}
impl<TYPES: NodeType> PackedBundle<TYPES> {
pub fn new(
encoded_transactions: Arc<[u8]>,
metadata: <TYPES::BlockPayload as BlockPayload<TYPES>>::Metadata,
view_number: TYPES::Time,
sequencing_fees: Vec1<BuilderFee<TYPES>>,
vid_precompute: Option<VidPrecomputeData>,
auction_result: Option<TYPES::AuctionResult>,
) -> Self {
Self {
encoded_transactions,
metadata,
view_number,
sequencing_fees,
vid_precompute,
auction_result,
}
}
}