pub struct L2Block {
pub header: L2BlockHeader,
pub spend_bundles: Vec<SpendBundle>,
pub slash_proposal_payloads: Vec<Vec<u8>>,
pub proposer_signature: Signature,
}Expand description
Complete L2 block: header plus body (spend bundles, slash payloads) and proposer attestation.
See BLK-003 and SPEC §2.3.
Chia Streamable (wire): see L2BlockHeader — gossip uses this encoding; persistence uses bincode + zstd in dig-blockstore.
Fields§
§header: L2BlockHeaderBlock header (identity hash, Merkle roots, metadata).
spend_bundles: Vec<SpendBundle>Spend bundles included in this block (chia-protocol).
slash_proposal_payloads: Vec<Vec<u8>>Raw slash proposal payloads (count should align with header slash fields when validated).
proposer_signature: SignatureBLS signature over the block from the proposer (crate::primitives::Signature / chia-bls).
Implementations§
Source§impl L2Block
impl L2Block
Sourcepub fn new(
header: L2BlockHeader,
spend_bundles: Vec<SpendBundle>,
slash_proposal_payloads: Vec<Vec<u8>>,
proposer_signature: Signature,
) -> Self
pub fn new( header: L2BlockHeader, spend_bundles: Vec<SpendBundle>, slash_proposal_payloads: Vec<Vec<u8>>, proposer_signature: Signature, ) -> Self
Construct a block from all body fields and the header (BLK-003 new()).
Note: Callers must keep header fields (e.g. spend_bundle_count, Merkle roots) consistent with
spend_bundles / slash_proposal_payloads; structural validation is separate (ERR-* / VAL-* requirements).
Sourcepub fn hash(&self) -> Bytes32
pub fn hash(&self) -> Bytes32
Canonical block identity: SHA-256 over the header preimage only (L2BlockHeader::hash, HSH-001 / SPEC §3.1).
Delegation: identical to self.header.hash() — required by BLK-003 so light clients and
signers can treat the header hash as the block id without serializing the body.
Sourcepub fn to_bytes(&self) -> Vec<u8> ⓘ
pub fn to_bytes(&self) -> Vec<u8> ⓘ
Serialize this block (header + body) to bincode bytes (SER-002, SPEC §8.2).
Infallible: Same contract as L2BlockHeader::to_bytes — well-formed structs serialize; failures are expect panics.
Sourcepub fn from_bytes(bytes: &[u8]) -> Result<Self, BlockError>
pub fn from_bytes(bytes: &[u8]) -> Result<Self, BlockError>
Deserialize a block from bincode bytes (SER-002).
Sourcepub fn height(&self) -> u64
pub fn height(&self) -> u64
Block height from the header (L2BlockHeader::height).
Sourcepub fn epoch(&self) -> u64
pub fn epoch(&self) -> u64
Epoch from the header (L2BlockHeader::epoch).
Sourcepub fn compute_spends_root(&self) -> Bytes32
pub fn compute_spends_root(&self) -> Bytes32
Merkle root over spend-bundle leaf digests in block order; empty body → crate::EMPTY_ROOT.
Delegation: crate::compute_spends_root (HSH-003) —
each leaf is SHA-256 of serialized SpendBundle bytes; chia_sdk_types::MerkleTree applies tagged hashing
(HSH-007, SPEC §3.3 spends_root row).
Sourcepub fn compute_additions_root(&self) -> Bytes32
pub fn compute_additions_root(&self) -> Bytes32
Additions Merkle root over Self::all_additions (HSH-004).
Delegation: crate::compute_additions_root — puzzle_hash groups, [ph, hash_coin_ids(ids)] pairs in
first-seen order (indexmap::IndexMap inside that function), then [merkle_set_root] /
chia_consensus::merkle_set::compute_merkle_set_root (SPEC §3.4).
Sourcepub fn compute_removals_root(&self) -> Bytes32
pub fn compute_removals_root(&self) -> Bytes32
Removals Merkle set over all spent coin IDs (HSH-005).
Body order: IDs come from Self::all_removals (spend-bundle then coin-spend order). Root: delegates to
crate::compute_removals_root, which uses chia_consensus::merkle_set::compute_merkle_set_root — the same
multiset of IDs yields the same root regardless of slice order (SPEC §3.5).
Sourcepub fn compute_filter_hash(&self) -> Bytes32
pub fn compute_filter_hash(&self) -> Bytes32
BIP-158 compact filter hash (HSH-006, SPEC §3.6).
Delegation: crate::compute_filter_hash with body-derived Self::all_additions /
Self::all_removals slices.
BIP158 key (block_identity argument): Self::header’s L2BlockHeader::parent_hash — stable while the
filter field is being filled and matches SPEC §6.4’s filter_hash = compute_filter_hash(additions, removals) build
step (no self-referential Self::hash dependency). SipHash keys are the first 16 bytes of that parent digest
([crate::merkle_util::bip158_filter_encoded]).
Sourcepub fn compute_slash_proposals_root(&self) -> Bytes32
pub fn compute_slash_proposals_root(&self) -> Bytes32
Binary Merkle root over slash payload digests (sha256 each), in payload order.
Sourcepub fn slash_proposals_root_from(payloads: &[Vec<u8>]) -> Bytes32
pub fn slash_proposals_root_from(payloads: &[Vec<u8>]) -> Bytes32
Self::compute_slash_proposals_root for an explicit payload list (tests, pre-serialized batches).
Sourcepub fn slash_proposal_leaf_hash(payload: &[u8]) -> Bytes32
pub fn slash_proposal_leaf_hash(payload: &[u8]) -> Bytes32
Single slash payload leaf digest (building block for Self::compute_slash_proposals_root).
Sourcepub fn all_additions(&self) -> Vec<Coin>
pub fn all_additions(&self) -> Vec<Coin>
All CREATE_COIN outputs from every spend bundle (CLVM-simulated per SpendBundle::additions).
Sourcepub fn all_addition_ids(&self) -> Vec<Bytes32> ⓘ
pub fn all_addition_ids(&self) -> Vec<Bytes32> ⓘ
Coin IDs of every addition in body order (same walk as Self::all_additions).
Sourcepub fn all_removals(&self) -> Vec<Bytes32> ⓘ
pub fn all_removals(&self) -> Vec<Bytes32> ⓘ
Spent coin IDs (CoinSpend.coin) in bundle / spend order.
Sourcepub fn has_duplicate_outputs(&self) -> Option<Bytes32>
pub fn has_duplicate_outputs(&self) -> Option<Bytes32>
First duplicate output coin ID in addition set, else None (SPEC / Chia duplicate-output check).
Sourcepub fn has_double_spends(&self) -> Option<Bytes32>
pub fn has_double_spends(&self) -> Option<Bytes32>
First coin ID spent twice as a removal, else None.
Sourcepub fn compute_size(&self) -> usize
pub fn compute_size(&self) -> usize
Full bincode body size (header + spends + slash payloads + signature), per SPEC serialization rules.
Sourcepub fn validate_structure(&self) -> Result<(), BlockError>
pub fn validate_structure(&self) -> Result<(), BlockError>
Tier 1 structural validation: cheap consistency checks that need no chain state (SPEC §5.2).
SVL-005 (spec): header counters
spend_bundle_count, additions_count, removals_count, and slash_proposal_count MUST match the body
(spend_bundles, Self::all_additions, total [CoinSpend] rows, slash_proposal_payloads).
SVL-006 (spec): after counts, enforces
Merkle commitments and integrity in SPEC §5.2 order: spends_root → duplicate outputs (Chia check 13) →
double spends (check 14) → additions_root / removals_root → BIP158 filter_hash → slash count and per-payload
byte caps → slash_proposals_root → full-block bincode size vs crate::MAX_BLOCK_SIZE. All hashing reuses
Self::compute_spends_root, Self::compute_additions_root, Self::compute_removals_root,
Self::compute_filter_hash, Self::compute_slash_proposals_root so validation stays aligned with HSH-003–006
and BLK-004.
Rationale: Count checks stay first (cheap, fail-fast); Merkle and filter work only after cardinality is sane;
serialized-size is last so malicious oversized bodies still pay for earlier checks where applicable.
crate::validation::structural indexes the SVL matrix.
Sourcepub fn validate_execution(
&self,
clvm_config: &ValidationConfig,
genesis_challenge: &Bytes32,
) -> Result<ExecutionResult, BlockError>
pub fn validate_execution( &self, clvm_config: &ValidationConfig, genesis_challenge: &Bytes32, ) -> Result<ExecutionResult, BlockError>
Tier 2 — execution validation entry point (EXE-001, SPEC §7.4).
Processes each SpendBundle in block order (ephemeral-coin semantics depend on this)
and returns an aggregated crate::ExecutionResult that carries into Tier 3
(STV-001).
§Scope of this method (EXE-001 alone)
Implemented:
- API surface matching NORMATIVE:
&self,&ValidationConfig(from dig-clvm, seedocs/prompt/start.mdHard Requirement 2),&Bytes32(genesis_challengeforAGG_SIG_MEdomain separation under EXE-005). - Block-order traversal of
Self::spend_bundles. - Block-level fee consistency (EXE-006)
—
computed_total_fees == header.total_fees, elseBlockError::FeesMismatch. - Block-level cost consistency (EXE-007)
—
computed_total_cost == header.total_cost, elseBlockError::CostMismatch. - Emits a fully populated (potentially empty)
crate::ExecutionResult(EXE-008).
Deferred to later requirements (documented here for trace):
- EXE-002 —
tree_hash(puzzle_reveal) == coin.puzzle_hashper [CoinSpend] (clvm_utils::tree_hash). - EXE-003 —
dig_clvm::validate_spend_bundleper bundle (CLVM execution); note it requires adig_clvm::ValidationContextwith per-coinCoinRecords, which today lives in Tier 3 (crate::CoinLookup). Wiring this in is EXE-003’s job. - EXE-004 / EXE-009 — two-pass condition collection +
crate::PendingAssertionpopulation. - EXE-005 — BLS aggregate signature verification (inside
dig-clvm).
For this requirement alone, the method only needs to be callable with the NORMATIVE
signature and must return the empty-block identity when there is no body. That matches the
EXE-001 test plan’s empty_block case; non-empty behavior is validated once EXE-003 lands.
§Error mapping
| Trigger | Variant | Requirement |
|---|---|---|
computed_total_fees != header.total_fees | BlockError::FeesMismatch | EXE-006 |
computed_total_cost != header.total_cost | BlockError::CostMismatch | EXE-007 |
§Chia parity
The method sits at the same layer as chia-blockchain’s pre_validate_blocks + body-level
checks (block_body_validation.py Check 9 (INVALID_BLOCK_COST) + Check 19
(INVALID_BLOCK_FEE_AMOUNT)).
Sourcepub fn validate_execution_with_context(
&self,
clvm_config: &ValidationConfig,
genesis_challenge: &Bytes32,
context: &ValidationContext,
) -> Result<ExecutionResult, BlockError>
pub fn validate_execution_with_context( &self, clvm_config: &ValidationConfig, genesis_challenge: &Bytes32, context: &ValidationContext, ) -> Result<ExecutionResult, BlockError>
Tier-2 execution with an explicit dig_clvm::ValidationContext
(EXE-003, SPEC §7.4.3).
§Why this signature (not EXE-001’s tight one)
dig_clvm::validate_spend_bundle requires a dig_clvm::ValidationContext populated with
per-coin [chia_sdk_coinset::CoinRecord]s before it can run CLVM (structural coin-exists
check precedes execution). That state lives in Tier 3 (crate::CoinLookup). This method
is the integration entry point callers use when they have a context ready; the
NORMATIVE-pinned Self::validate_execution remains a thin wrapper over an empty
context and is only sound for empty bodies. When the Tier-2/Tier-3 bridge lands
(validate_full), the wrapper will build context from a provided CoinLookup.
§Pipeline
- For each
chia_protocol::SpendBundlein block order:- For each
chia_protocol::CoinSpend:crate::verify_coin_spend_puzzle_hash(EXE-002). dig_clvm::validate_spend_bundle(EXE-003) — CLVM + conditions + BLS aggregate verify + per-bundle conservation.- Fold
SpendResultinto the runningcrate::ExecutionResult.
- For each
- After all bundles:
- EXE-006 fee consistency (
computed_total_fees == header.total_fees). - EXE-007 cost consistency (
computed_total_cost == header.total_cost).
- EXE-006 fee consistency (
§Error mapping
All dig_clvm::ValidationError variants pass through crate::map_clvm_validation_error
(EXE-003 mapping table) so callers see only BlockError.
§Rationale vs delegating directly
Keeping the CLVM call gated by a puzzle-hash pre-check (EXE-002) preserves fail-fast on tampered reveals without paying CLVM cost. Every other check runs inside dig-clvm.
Sourcepub fn validate_state(
&self,
exec: &ExecutionResult,
coins: &dyn CoinLookup,
proposer_pubkey: &PublicKey,
) -> Result<Bytes32, BlockError>
pub fn validate_state( &self, exec: &ExecutionResult, coins: &dyn CoinLookup, proposer_pubkey: &PublicKey, ) -> Result<Bytes32, BlockError>
Tier 3 — state validation entry point (STV-001, SPEC §7.5).
Consumes the crate::ExecutionResult produced by Tier 2, cross-references it against
the caller’s crate::CoinLookup view of the coin set, verifies the proposer signature,
and returns the computed state-trie root for commitment.
§Sub-checks (each a follow-on STV-* requirement)
| Step | Requirement | Purpose |
|---|---|---|
| 1 | STV-002 | Every exec.removals coin exists and is unspent (or is ephemeral — present in exec.additions). |
| 2 | STV-003 | CoinState.coin.puzzle_hash cross-check vs the spent coin’s puzzle_hash. |
| 3 | STV-004 | Every exec.additions coin is not already in the coin set (ephemeral exception). |
| 4 | STV-005 | Evaluate each crate::PendingAssertion from Tier 2 against chain context. |
| 5 | STV-006 | chia_bls::verify(proposer_pubkey, header.hash(), proposer_signature). |
| 6 | STV-007 | Apply additions / removals, recompute state root, compare to header.state_root, return it. |
§Scope of this commit (STV-001 only)
Dispatcher with placeholder sub-check bodies. On empty inputs (zero additions / removals /
pending assertions) every sub-check is a no-op and the method returns
self.header.state_root directly — the boundary case needed for validate_full to
finish a genesis-style empty block end-to-end. STV-002..007 will harden each step
without changing this outer signature.
§Return value
Bytes32 — the computed state-trie root. For successful validation this equals
self.header.state_root; callers use it as the committed parent-state value for the next
block. This is why the return is not ().
Sourcepub fn validate_full(
&self,
clvm_config: &ValidationConfig,
genesis_challenge: &Bytes32,
coins: &dyn CoinLookup,
proposer_pubkey: &PublicKey,
) -> Result<Bytes32, BlockError>
pub fn validate_full( &self, clvm_config: &ValidationConfig, genesis_challenge: &Bytes32, coins: &dyn CoinLookup, proposer_pubkey: &PublicKey, ) -> Result<Bytes32, BlockError>
Convenience wrapper: Tier 1 → Tier 2 → Tier 3 (STV-001).
Short-circuits on the first failing tier. On success returns the computed state root
(same semantics as Self::validate_state). Each tier can still be called independently
for partial validation or tests.
Trait Implementations§
Source§impl<'de> Deserialize<'de> for L2Block
impl<'de> Deserialize<'de> for L2Block
Source§fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
fn deserialize<__D>(__deserializer: __D) -> Result<Self, __D::Error>where
__D: Deserializer<'de>,
Source§impl Streamable for L2Block
impl Streamable for L2Block
fn update_digest(&self, digest: &mut Sha256)
fn stream(&self, out: &mut Vec<u8>) -> Result<()>
fn parse<const TRUSTED: bool>(input: &mut Cursor<&[u8]>) -> Result<Self>
fn to_bytes(&self) -> Result<Vec<u8>, Error>
fn from_bytes(bytes: &[u8]) -> Result<Self, Error>where
Self: Sized,
fn from_bytes_unchecked(bytes: &[u8]) -> Result<Self, Error>where
Self: Sized,
fn hash(&self) -> [u8; 32]
Auto Trait Implementations§
impl Freeze for L2Block
impl RefUnwindSafe for L2Block
impl Send for L2Block
impl Sync for L2Block
impl Unpin for L2Block
impl UnsafeUnpin for L2Block
impl UnwindSafe for L2Block
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> FmtForward for T
impl<T> FmtForward for T
Source§fn fmt_binary(self) -> FmtBinary<Self>where
Self: Binary,
fn fmt_binary(self) -> FmtBinary<Self>where
Self: Binary,
self to use its Binary implementation when Debug-formatted.Source§fn fmt_display(self) -> FmtDisplay<Self>where
Self: Display,
fn fmt_display(self) -> FmtDisplay<Self>where
Self: Display,
self to use its Display implementation when
Debug-formatted.Source§fn fmt_lower_exp(self) -> FmtLowerExp<Self>where
Self: LowerExp,
fn fmt_lower_exp(self) -> FmtLowerExp<Self>where
Self: LowerExp,
self to use its LowerExp implementation when
Debug-formatted.Source§fn fmt_lower_hex(self) -> FmtLowerHex<Self>where
Self: LowerHex,
fn fmt_lower_hex(self) -> FmtLowerHex<Self>where
Self: LowerHex,
self to use its LowerHex implementation when
Debug-formatted.Source§fn fmt_octal(self) -> FmtOctal<Self>where
Self: Octal,
fn fmt_octal(self) -> FmtOctal<Self>where
Self: Octal,
self to use its Octal implementation when Debug-formatted.Source§fn fmt_pointer(self) -> FmtPointer<Self>where
Self: Pointer,
fn fmt_pointer(self) -> FmtPointer<Self>where
Self: Pointer,
self to use its Pointer implementation when
Debug-formatted.Source§fn fmt_upper_exp(self) -> FmtUpperExp<Self>where
Self: UpperExp,
fn fmt_upper_exp(self) -> FmtUpperExp<Self>where
Self: UpperExp,
self to use its UpperExp implementation when
Debug-formatted.Source§fn fmt_upper_hex(self) -> FmtUpperHex<Self>where
Self: UpperHex,
fn fmt_upper_hex(self) -> FmtUpperHex<Self>where
Self: UpperHex,
self to use its UpperHex implementation when
Debug-formatted.Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> Pipe for Twhere
T: ?Sized,
impl<T> Pipe for Twhere
T: ?Sized,
Source§fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> Rwhere
Self: Sized,
fn pipe<R>(self, func: impl FnOnce(Self) -> R) -> Rwhere
Self: Sized,
Source§fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> Rwhere
R: 'a,
fn pipe_ref<'a, R>(&'a self, func: impl FnOnce(&'a Self) -> R) -> Rwhere
R: 'a,
self and passes that borrow into the pipe function. Read moreSource§fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> Rwhere
R: 'a,
fn pipe_ref_mut<'a, R>(&'a mut self, func: impl FnOnce(&'a mut Self) -> R) -> Rwhere
R: 'a,
self and passes that borrow into the pipe function. Read moreSource§fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
fn pipe_borrow<'a, B, R>(&'a self, func: impl FnOnce(&'a B) -> R) -> R
Source§fn pipe_borrow_mut<'a, B, R>(
&'a mut self,
func: impl FnOnce(&'a mut B) -> R,
) -> R
fn pipe_borrow_mut<'a, B, R>( &'a mut self, func: impl FnOnce(&'a mut B) -> R, ) -> R
Source§fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
fn pipe_as_ref<'a, U, R>(&'a self, func: impl FnOnce(&'a U) -> R) -> R
self, then passes self.as_ref() into the pipe function.Source§fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> R
fn pipe_as_mut<'a, U, R>(&'a mut self, func: impl FnOnce(&'a mut U) -> R) -> R
self, then passes self.as_mut() into the pipe
function.Source§fn pipe_deref<'a, T, R>(&'a self, func: impl FnOnce(&'a T) -> R) -> R
fn pipe_deref<'a, T, R>(&'a self, func: impl FnOnce(&'a T) -> R) -> R
self, then passes self.deref() into the pipe function.Source§impl<T> PolicyExt for Twhere
T: ?Sized,
impl<T> PolicyExt for Twhere
T: ?Sized,
Source§impl<T> Tap for T
impl<T> Tap for T
Source§fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
fn tap_borrow<B>(self, func: impl FnOnce(&B)) -> Self
Borrow<B> of a value. Read moreSource§fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
fn tap_borrow_mut<B>(self, func: impl FnOnce(&mut B)) -> Self
BorrowMut<B> of a value. Read moreSource§fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
fn tap_ref<R>(self, func: impl FnOnce(&R)) -> Self
AsRef<R> view of a value. Read moreSource§fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
fn tap_ref_mut<R>(self, func: impl FnOnce(&mut R)) -> Self
AsMut<R> view of a value. Read moreSource§fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
fn tap_deref<T>(self, func: impl FnOnce(&T)) -> Self
Deref::Target of a value. Read moreSource§fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
fn tap_deref_mut<T>(self, func: impl FnOnce(&mut T)) -> Self
Deref::Target of a value. Read moreSource§fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self
fn tap_dbg(self, func: impl FnOnce(&Self)) -> Self
.tap() only in debug builds, and is erased in release builds.Source§fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self
fn tap_mut_dbg(self, func: impl FnOnce(&mut Self)) -> Self
.tap_mut() only in debug builds, and is erased in release
builds.Source§fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
fn tap_borrow_dbg<B>(self, func: impl FnOnce(&B)) -> Self
.tap_borrow() only in debug builds, and is erased in release
builds.Source§fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Self
fn tap_borrow_mut_dbg<B>(self, func: impl FnOnce(&mut B)) -> Self
.tap_borrow_mut() only in debug builds, and is erased in release
builds.Source§fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
fn tap_ref_dbg<R>(self, func: impl FnOnce(&R)) -> Self
.tap_ref() only in debug builds, and is erased in release
builds.Source§fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Self
fn tap_ref_mut_dbg<R>(self, func: impl FnOnce(&mut R)) -> Self
.tap_ref_mut() only in debug builds, and is erased in release
builds.Source§fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
fn tap_deref_dbg<T>(self, func: impl FnOnce(&T)) -> Self
.tap_deref() only in debug builds, and is erased in release
builds.