1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281
// Copyright (c) 2021-2024 Espresso Systems (espressosys.com)
// This file is part of the HotShot repository.
// You should have received a copy of the MIT License
// along with the HotShot repository. If not, see <https://mit-license.org/>.
//! Utility functions, type aliases, helper structs and enum definitions.
use std::{
hash::{Hash, Hasher},
ops::Deref,
sync::Arc,
};
use ark_serialize::{CanonicalDeserialize, CanonicalSerialize};
use bincode::{
config::{
FixintEncoding, LittleEndian, RejectTrailing, WithOtherEndian, WithOtherIntEncoding,
WithOtherLimit, WithOtherTrailing,
},
DefaultOptions, Options,
};
use committable::Commitment;
use digest::OutputSizeUser;
use serde::{Deserialize, Serialize};
use sha2::Digest;
use tagged_base64::tagged;
use typenum::Unsigned;
use crate::{
data::Leaf2,
traits::{node_implementation::NodeType, ValidatedState},
vid::VidCommitment,
};
/// A view's state
#[derive(Debug, Deserialize, Serialize, PartialEq, Eq)]
#[serde(bound = "")]
pub enum ViewInner<TYPES: NodeType> {
/// A pending view with an available block but not leaf proposal yet.
///
/// Storing this state allows us to garbage collect blocks for views where a proposal is never
/// made. This saves memory when a leader fails and subverts a DoS attack where malicious
/// leaders repeatedly request availability for blocks that they never propose.
Da {
/// Payload commitment to the available block.
payload_commitment: VidCommitment,
/// An epoch to which the data belongs to. Relevant for validating against the correct stake table
epoch: TYPES::Epoch,
},
/// Undecided view
Leaf {
/// Proposed leaf
leaf: LeafCommitment<TYPES>,
/// Validated state.
state: Arc<TYPES::ValidatedState>,
/// Optional state delta.
delta: Option<Arc<<TYPES::ValidatedState as ValidatedState<TYPES>>::Delta>>,
/// An epoch to which the data belongs to. Relevant for validating against the correct stake table
epoch: TYPES::Epoch,
},
/// Leaf has failed
Failed,
}
impl<TYPES: NodeType> Clone for ViewInner<TYPES> {
fn clone(&self) -> Self {
match self {
Self::Da {
payload_commitment,
epoch,
} => Self::Da {
payload_commitment: *payload_commitment,
epoch: *epoch,
},
Self::Leaf {
leaf,
state,
delta,
epoch,
} => Self::Leaf {
leaf: *leaf,
state: Arc::clone(state),
delta: delta.clone(),
epoch: *epoch,
},
Self::Failed => Self::Failed,
}
}
}
/// The hash of a leaf.
pub type LeafCommitment<TYPES> = Commitment<Leaf2<TYPES>>;
/// Optional validated state and state delta.
pub type StateAndDelta<TYPES> = (
Option<Arc<<TYPES as NodeType>::ValidatedState>>,
Option<Arc<<<TYPES as NodeType>::ValidatedState as ValidatedState<TYPES>>::Delta>>,
);
impl<TYPES: NodeType> ViewInner<TYPES> {
/// Return the underlying undecide leaf commitment and validated state if they exist.
#[must_use]
pub fn leaf_and_state(&self) -> Option<(LeafCommitment<TYPES>, &Arc<TYPES::ValidatedState>)> {
if let Self::Leaf { leaf, state, .. } = self {
Some((*leaf, state))
} else {
None
}
}
/// return the underlying leaf hash if it exists
#[must_use]
pub fn leaf_commitment(&self) -> Option<LeafCommitment<TYPES>> {
if let Self::Leaf { leaf, .. } = self {
Some(*leaf)
} else {
None
}
}
/// return the underlying validated state if it exists
#[must_use]
pub fn state(&self) -> Option<&Arc<TYPES::ValidatedState>> {
if let Self::Leaf { state, .. } = self {
Some(state)
} else {
None
}
}
/// Return the underlying validated state and state delta if they exist.
#[must_use]
pub fn state_and_delta(&self) -> StateAndDelta<TYPES> {
if let Self::Leaf { state, delta, .. } = self {
(Some(Arc::clone(state)), delta.clone())
} else {
(None, None)
}
}
/// return the underlying block paylod commitment if it exists
#[must_use]
pub fn payload_commitment(&self) -> Option<VidCommitment> {
if let Self::Da {
payload_commitment, ..
} = self
{
Some(*payload_commitment)
} else {
None
}
}
/// Returns `Epoch` if possible
pub fn epoch(&self) -> Option<TYPES::Epoch> {
match self {
Self::Da { epoch, .. } | Self::Leaf { epoch, .. } => Some(*epoch),
Self::Failed => None,
}
}
}
impl<TYPES: NodeType> Deref for View<TYPES> {
type Target = ViewInner<TYPES>;
fn deref(&self) -> &Self::Target {
&self.view_inner
}
}
/// This exists so we can perform state transitions mutably
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Eq)]
#[serde(bound = "")]
pub struct View<TYPES: NodeType> {
/// The view data. Wrapped in a struct so we can mutate
pub view_inner: ViewInner<TYPES>,
}
/// A struct containing information about a finished round.
#[derive(Debug, Clone)]
pub struct RoundFinishedEvent<TYPES: NodeType> {
/// The round that finished
pub view_number: TYPES::View,
}
/// Whether or not to stop inclusively or exclusively when walking
#[derive(Copy, Clone, Debug)]
pub enum Terminator<T> {
/// Stop right before this view number
Exclusive(T),
/// Stop including this view number
Inclusive(T),
}
/// Type alias for byte array of SHA256 digest length
type Sha256Digest = [u8; <sha2::Sha256 as OutputSizeUser>::OutputSize::USIZE];
#[tagged("BUILDER_COMMITMENT")]
#[derive(Clone, Debug, Hash, PartialEq, Eq, CanonicalSerialize, CanonicalDeserialize)]
/// Commitment that builders use to sign block options.
/// A thin wrapper around a Sha256 digest.
pub struct BuilderCommitment(Sha256Digest);
impl BuilderCommitment {
/// Create new commitment for `data`
pub fn from_bytes(data: impl AsRef<[u8]>) -> Self {
Self(sha2::Sha256::digest(data.as_ref()).into())
}
/// Create a new commitment from a raw Sha256 digest
pub fn from_raw_digest(digest: impl Into<Sha256Digest>) -> Self {
Self(digest.into())
}
}
impl AsRef<Sha256Digest> for BuilderCommitment {
fn as_ref(&self) -> &Sha256Digest {
&self.0
}
}
/// For the wire format, we use bincode with the following options:
/// - No upper size limit
/// - Little endian encoding
/// - Varint encoding
/// - Reject trailing bytes
#[allow(clippy::type_complexity)]
#[must_use]
#[allow(clippy::type_complexity)]
pub fn bincode_opts() -> WithOtherTrailing<
WithOtherIntEncoding<
WithOtherEndian<WithOtherLimit<DefaultOptions, bincode::config::Infinite>, LittleEndian>,
FixintEncoding,
>,
RejectTrailing,
> {
bincode::DefaultOptions::new()
.with_no_limit()
.with_little_endian()
.with_fixint_encoding()
.reject_trailing_bytes()
}
/// Returns an epoch number given a block number and an epoch height
#[must_use]
pub fn epoch_from_block_number(block_number: u64, epoch_height: u64) -> u64 {
if epoch_height == 0 {
0
} else if block_number % epoch_height == 0 {
block_number / epoch_height
} else {
block_number / epoch_height + 1
}
}
/// A function for generating a cute little user mnemonic from a hash
#[must_use]
pub fn mnemonic<H: Hash>(bytes: H) -> String {
let mut state = std::collections::hash_map::DefaultHasher::new();
bytes.hash(&mut state);
mnemonic::to_string(state.finish().to_le_bytes())
}
/// A helper enum to indicate whether a node is in the epoch transition
/// A node is in epoch transition when its high QC is for the last block in an epoch
#[derive(Debug, Clone)]
pub enum EpochTransitionIndicator {
/// A node is currently in the epoch transition
InTransition,
/// A node is not in the epoch transition
NotInTransition,
}
/// Returns true if the given block number is the last in the epoch based on the given epoch height.
#[must_use]
pub fn is_last_block_in_epoch(block_number: u64, epoch_height: u64) -> bool {
if block_number == 0 || epoch_height == 0 {
false
} else {
block_number % epoch_height == 0
}
}