Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 21 additions & 15 deletions crates/node/src/node/block_importer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,20 +169,30 @@ impl<S: KvStore + 'static> Node<S> {
// A valid proof means the block producer correctly accumulated all
// tx signature entries; this is belt-and-suspenders verification on top
// of the existing individual sig checks below.
// Commitment-only payloads (no proof_bytes) are accepted as-is; full
// STARK verification happens when a ProofAmendment is gossiped.
if let Some(proof_bytes) = &block.header.sig_aggregate_proof {
match shell_stark_prover::proof::SigBatchProof::from_json(proof_bytes.as_ref()) {
Ok(sig_proof) => {
if let Err(e) = verify_sig_batch(&sig_proof) {
return Err(NodeError::Startup(format!(
"block {} STARK aggregate proof verification failed: {e}",
block.number()
)));
if sig_proof.has_proof() {
if let Err(e) = verify_sig_batch(&sig_proof) {
return Err(NodeError::Startup(format!(
"block {} STARK aggregate proof verification failed: {e}",
block.number()
)));
}
debug!(
block = block.number(),
n_sigs = sig_proof.n_sigs,
"C3: STARK aggregate proof verified"
);
} else {
debug!(
block = block.number(),
n_sigs = sig_proof.n_sigs,
"C3: commitment-only sig_aggregate_proof accepted; full proof pending ProofAmendment"
);
}
debug!(
block = block.number(),
n_sigs = sig_proof.n_sigs,
"C3: STARK aggregate proof verified"
);
}
Err(e) => {
return Err(NodeError::Startup(format!(
Expand Down Expand Up @@ -426,11 +436,7 @@ impl<S: KvStore + 'static> Node<S> {
&result.system_contract_effects,
)?;
} else {
commit_pqvm_state(
&result,
evm.state_db_mut().world_state_mut(),
&self.chain_store,
)?;
commit_pqvm_state(&result, evm.state_db_mut())?;
}
total_effective_fees = total_effective_fees.saturating_add(
U256::from(result.gas_used).saturating_mul(U256::from(price)),
Expand Down
28 changes: 20 additions & 8 deletions crates/node/src/node/block_producer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,16 +128,15 @@ impl<S: KvStore + 'static> Node<S> {
)?;
} else {
// Normal PQVM tx: commit the revm state changeset.
commit_pqvm_state(
&result,
evm.state_db_mut().world_state_mut(),
&self.chain_store,
)?;
// Snapshot the address registry before the first commit clears it,
// so the persistent WorldState can resolve PQ addresses too.
let registry = evm.state_db().address_registry_snapshot();
commit_pqvm_state(&result, evm.state_db_mut())?;

// Commit to the node's persistent WorldState.
{
let mut ws = self.world_state.write();
commit_pqvm_state(&result, &mut ws, &self.chain_store)?;
commit_pqvm_state_raw(&result, &mut *ws, &self.chain_store, &registry)?;
}
}
total_effective_fees = total_effective_fees.saturating_add(
Expand Down Expand Up @@ -300,14 +299,27 @@ impl<S: KvStore + 'static> Node<S> {
proposer_seal: None,
};

// C3: If STARK aggregation is enabled, collect sig batch entries now.
// G4: ProofTask pushed to backlog AFTER signing so we have the real block hash.
// C3: If STARK aggregation is enabled, collect sig batch entries and
// compute the 32-byte commitment synchronously so it can be embedded
// in the header (and thus covered by the block hash / proposer seal).
// The full STARK proof is generated asynchronously; nodes that receive
// a commitment-only header will skip full STARK verification until a
// ProofAmendment arrives.
let stark_entries: Option<Vec<SigBatchEntry>> = if self.stark_aggregation {
Some(stark_sources::entries_from_txs(&included_txs))
} else {
None
};

// Embed the batch-root commitment in the header before signing.
if let Some(entries) = stark_entries.as_ref() {
if !entries.is_empty() {
let batch_root = compute_batch_root(entries);
let commitment = SigBatchProof::commitment_only(batch_root, entries.len());
block.header.sig_aggregate_proof = commitment.to_json().ok().map(Into::into);
}
}

// Sign the block with the proposer's key.
consensus.sign_block(&mut block, signer)?;

Expand Down
31 changes: 21 additions & 10 deletions crates/node/src/node/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,9 @@ pub(crate) use shell_crypto::{
};
pub(crate) use shell_mempool::TxPool;
pub(crate) use shell_network::{NetworkMessage, NetworkService};
pub(crate) use shell_pqvm::{commit_pqvm_state, validate_tx_for_import, ShellPqvm, ShellStateDb};
pub(crate) use shell_pqvm::{
commit_pqvm_state, commit_pqvm_state_raw, validate_tx_for_import, ShellPqvm, ShellStateDb,
};
pub(crate) use shell_primitives::{Address, Bytes, ShellHash, U256};
pub(crate) use shell_rpc::DevRpcControl;
pub(crate) use shell_storage::{
Expand All @@ -56,7 +58,8 @@ pub(crate) use challenge_lifecycle::{
pub(crate) use readiness::{ProductionReadiness, ProductionReadinessState};

pub(crate) use shell_stark_prover::{
prover::{verify_sig_batch, SigBatchEntry},
proof::SigBatchProof,
prover::{compute_batch_root, verify_sig_batch, SigBatchEntry},
AggregationConfig, AggregationScheduler, AggregationTrigger, ProofAmendment, ProofBacklog,
ProofTask, SettledL1Input, DEFAULT_MAX_L1_RANGE_SOURCES, MIN_L1_STARK_TXS,
};
Expand Down Expand Up @@ -1533,7 +1536,7 @@ mod tests {
start_block: Some(6),
proof: shell_stark_prover::proof::SigBatchProof {
version: shell_stark_prover::proof::SIG_BATCH_PROOF_VERSION,
batch_root_bytes: [0x22; 16],
batch_root_bytes: [0x22; 32],
n_sigs: if layer == 1 { MIN_L1_STARK_TXS } else { 2 },
proof_bytes: vec![0x33; compressed_size as usize],
},
Expand Down Expand Up @@ -1600,7 +1603,7 @@ mod tests {
.and_then(|end_plus_one| end_plus_one.checked_sub(source_hashes.len() as u64)),
proof: shell_stark_prover::proof::SigBatchProof {
version: shell_stark_prover::proof::SIG_BATCH_PROOF_VERSION,
batch_root_bytes: [0x22; 16],
batch_root_bytes: [0x22; 32],
n_sigs: if layer == 1 {
MIN_L1_STARK_TXS
} else {
Expand Down Expand Up @@ -1820,7 +1823,7 @@ mod tests {
.insert((1, l1_src.block_hash));

// Build an L2 amendment with correct n_sigs and aggregate root.
let root = u128::from_le_bytes(l1_src.proof.batch_root_bytes);
let root = u128::from_le_bytes(l1_src.proof.batch_root_bytes[0..16].try_into().unwrap());
let agg_root = compute_aggregate_root(&[root]);
let l2 = ProofAmendment {
version: shell_stark_prover::amendment::PROOF_AMENDMENT_VERSION,
Expand All @@ -1829,7 +1832,11 @@ mod tests {
start_block: Some(1),
proof: shell_stark_prover::proof::SigBatchProof {
version: shell_stark_prover::proof::SIG_BATCH_PROOF_VERSION,
batch_root_bytes: agg_root.to_le_bytes(),
batch_root_bytes: {
let mut b = [0u8; 32];
b[0..16].copy_from_slice(&agg_root.to_le_bytes());
b
},
n_sigs: 1,
proof_bytes: vec![0x33; 128],
},
Expand Down Expand Up @@ -1868,7 +1875,7 @@ mod tests {
.lock()
.insert((1, l1_src.block_hash));

let root = u128::from_le_bytes(l1_src.proof.batch_root_bytes);
let root = u128::from_le_bytes(l1_src.proof.batch_root_bytes[0..16].try_into().unwrap());
let agg_root = compute_aggregate_root(&[root]);
let l2 = ProofAmendment {
version: shell_stark_prover::amendment::PROOF_AMENDMENT_VERSION,
Expand All @@ -1877,7 +1884,11 @@ mod tests {
start_block: Some(1),
proof: shell_stark_prover::proof::SigBatchProof {
version: shell_stark_prover::proof::SIG_BATCH_PROOF_VERSION,
batch_root_bytes: agg_root.to_le_bytes(),
batch_root_bytes: {
let mut b = [0u8; 32];
b[0..16].copy_from_slice(&agg_root.to_le_bytes());
b
},
n_sigs: 1,
// H-1: These garbage bytes cannot be decoded as a RecursiveProof.
proof_bytes: vec![0x33; 128],
Expand Down Expand Up @@ -2288,7 +2299,7 @@ mod tests {
start_block: Some(0),
proof: shell_stark_prover::proof::SigBatchProof {
version: shell_stark_prover::proof::SIG_BATCH_PROOF_VERSION,
batch_root_bytes: [0x22; 16],
batch_root_bytes: [0x22; 32],
n_sigs: MIN_L1_STARK_TXS,
proof_bytes: vec![0x33; 128],
},
Expand Down Expand Up @@ -2531,7 +2542,7 @@ mod tests {
start_block: Some(0),
proof: shell_stark_prover::proof::SigBatchProof {
version: shell_stark_prover::proof::SIG_BATCH_PROOF_VERSION,
batch_root_bytes: [0u8; 16],
batch_root_bytes: [0u8; 32],
n_sigs: 999, // wrong — actual canonical entry count is 0
proof_bytes: vec![0x33; 128],
},
Expand Down
13 changes: 9 additions & 4 deletions crates/node/src/node/system_rewards.rs
Original file line number Diff line number Diff line change
Expand Up @@ -336,9 +336,10 @@ impl<S: KvStore + 'static> Node<S> {
)));
}

// Extract the L1 batch root as a u128 for aggregate computation.
// Extract the L1 batch root lo-half as a u128 for L2 aggregate computation.
// batch_root_bytes is [lo:16 ‖ hi:16]; the L2 recursive circuit operates on u128.
let root_bytes = source_amendment.proof.batch_root_bytes;
let root = u128::from_le_bytes(root_bytes);
let root = u128::from_le_bytes(root_bytes[0..16].try_into().unwrap());
l1_roots.push(root);
}

Expand All @@ -355,7 +356,9 @@ impl<S: KvStore + 'static> Node<S> {
// Check 2: aggregate root must match compute_aggregate_root(l1_roots).
let expected_agg_root =
shell_stark_prover::recursive_air::compute_aggregate_root(&l1_roots);
let declared_agg_root = u128::from_le_bytes(amendment.proof.batch_root_bytes);
// batch_root_bytes is [lo:16 ‖ hi:16]; L2 aggregate root lives in the lo half.
let declared_agg_root =
u128::from_le_bytes(amendment.proof.batch_root_bytes[0..16].try_into().unwrap());
if expected_agg_root != declared_agg_root {
self.metrics.stark_settlements_rejected.inc();
return Err(NodeError::Startup(format!(
Expand Down Expand Up @@ -510,7 +513,9 @@ impl<S: KvStore + 'static> Node<S> {
let start_block = amendment
.range_start_block()
.unwrap_or(amendment.block_number);
let batch_root = u128::from_le_bytes(amendment.proof.batch_root_bytes);
// batch_root_bytes is [lo:16 ‖ hi:16]; SettledL1Input uses the lo half (u128).
let batch_root =
u128::from_le_bytes(amendment.proof.batch_root_bytes[0..16].try_into().unwrap());
let input = SettledL1Input {
start_block,
end_block: amendment.block_number,
Expand Down
Loading