Skip to content
Closed
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
14 changes: 12 additions & 2 deletions bin/alpen-client/src/prover/spec_acct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -360,8 +360,18 @@ impl ProofSpec for AcctSpec {
let cur_state = ProofState::new(pre_ee_state.compute_state_root(), pre_inbox_idx);
let new_state = ProofState::new(post_state_root, new_inbox_idx);

let extra_data =
UpdateExtraData::new(new_tip_blkid, new_tip_state_root, processed_inputs, 0);
let value_sent = update_outputs.compute_total_value().ok_or_else(|| {
PaasError::PermanentFailure(format!(
"overflow computing aggregate value sent for batch {batch_id}"
))
})?;
let extra_data = UpdateExtraData::new(
new_tip_blkid,
new_tip_state_root,
processed_inputs,
0,
value_sent,
);
let extra_data_bytes = encode_to_vec(&extra_data)
.map_err(|e| PaasError::PermanentFailure(format!("encode extra data: {e}")))?;
let ledger_refs = build_ledger_refs_from_da(&da_refs, self.ol_client.as_ref())
Expand Down
8 changes: 7 additions & 1 deletion bin/prover-perf/src/programs/alpen_acct.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,13 @@ fn prepare_input() -> EeAcctProofInput {
// 4. Build pubvals matching the post-state. The witness fixture is a vanilla Ethereum block —
// it doesn't touch the EE precompiles that emit subject deposits or output messages, so all
// the aggregate fields are empty.
let extra_data = UpdateExtraData::new(chunk_transition.tip_exec_blkid(), tip_state_root, 0, 0);
let extra_data = UpdateExtraData::new(
chunk_transition.tip_exec_blkid(),
tip_state_root,
0,
0,
BitcoinAmount::ZERO,
);
let extra_data_bytes = encode_to_vec(&extra_data).expect("encode extra data");

let pub_params = UpdateProofPubParams::new(
Expand Down
5 changes: 4 additions & 1 deletion crates/alpen-ee/block-assembly/src/payload.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::num::NonZero;
use alloy_primitives::B256;
use alpen_ee_common::{DepositInfo, EnginePayload, PayloadBuildAttributes, PayloadBuilderEngine};
use alpen_reth_evm::subject_to_address_unchecked;
use strata_acct_types::Hash;
use strata_acct_types::{BitcoinAmount, Hash};
use strata_ee_acct_types::{EeAccountState, PendingInputEntry, UpdateExtraData};
use tracing::debug;

Expand Down Expand Up @@ -61,6 +61,9 @@ pub(crate) async fn build_exec_payload<E: PayloadBuilderEngine>(
new_tip_state_root,
processed_inputs,
processed_fincls,
// No outputs are emitted by this path; balance reconciliation deduction
// is zero.
BitcoinAmount::ZERO,
);

Ok((payload, update_extra_data))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,11 +108,15 @@ fn build_update_operation(
}

// 3. Build extra data
let value_sent = outputs
.compute_total_value()
.ok_or_else(|| eyre!("overflow computing aggregate value sent"))?;
let extra_data = UpdateExtraData::new(
new_tip_blkid,
new_tip_state_root,
processed_inputs as u32,
0,
value_sent,
);
let extra_data_buf = encode_to_vec(&extra_data)?;

Expand Down
7 changes: 7 additions & 0 deletions crates/ee-acct-runtime/src/ee_program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,13 @@ impl<E: ExecutionEnvironment> SnarkAccountProgram for EeSnarkAccountProgram<E> {
// mismatched `UpdateExtraData.new_tip_state_root`.
state.set_last_exec_state_root(*extra_data.new_tip_state_root());

// Decrement tracked balance by the aggregate value sent out by chunk
// outputs in this update. The verification path independently
// accumulates this value and checks it against `extra_data.value_sent`
// in `process_chunks_on_acct`, so this subtraction stays bound to what
// the chunks actually emitted.
state.try_subtract_tracked_balance(*extra_data.value_sent())?;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Keep tracked-balance subtraction consistent with stored block state

Subtracting extra_data.value_sent here changes the state root produced by update processing, but the sequencer’s block assembly path still persists ExecBlockRecord.account_state without any corresponding output deduction (it only updates tip/queues in crates/alpen-ee/block-assembly/src/block.rs). bin/alpen-client/src/prover/spec_acct.rs then uses that persisted account_state().compute_state_root() as new_state, while this program now computes a lower post-state whenever chunk outputs carry value; for batches containing withdrawals/output messages, this creates a deterministic post-state root mismatch and breaks proof/update validation.

Useful? React with 👍 / 👎.


Ok(())
}

Expand Down
24 changes: 23 additions & 1 deletion crates/ee-acct-runtime/src/update_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
//! inputs, allowing the consumer to query available inputs and accept
//! validated [`ChunkTransition`]s.

use strata_acct_types::{Hash, MessageEntry};
use strata_acct_types::{BitcoinAmount, Hash, MessageEntry};
use strata_ee_acct_types::{
EeAccountState, ExecutionEnvironment, PendingInputEntry, UpdateExtraData,
};
Expand Down Expand Up @@ -49,6 +49,11 @@ pub struct UpdateBuilder<'i, E: ExecutionEnvironment> {

/// Number of forced inclusions processed.
fincls_processed: usize,

/// Aggregate value sent out by accepted chunk outputs (transfers + message
/// payload values). Carried into `UpdateExtraData::value_sent` so the
/// finalization step can decrement `tracked_balance` accordingly.
value_sent: BitcoinAmount,
}

impl<'i, E: ExecutionEnvironment> UpdateBuilder<'i, E> {
Expand Down Expand Up @@ -85,6 +90,7 @@ impl<'i, E: ExecutionEnvironment> UpdateBuilder<'i, E> {
pending_inputs,
inputs_consumed: 0,
fincls_processed: 0,
value_sent: BitcoinAmount::ZERO,
})
}

Expand Down Expand Up @@ -197,6 +203,20 @@ impl<'i, E: ExecutionEnvironment> UpdateBuilder<'i, E> {
// 3. Merge outputs.
let outputs = transition.outputs();

// Sum the value being sent out by this chunk and accumulate into the
// running total carried in `UpdateExtraData::value_sent`.
let chunk_value_sent = outputs
.output_transfers()
.iter()
.map(|t| t.value())
.chain(outputs.output_messages().iter().map(|m| m.payload().value()))
.try_fold(BitcoinAmount::ZERO, |acc, v| acc.checked_add(v))
.ok_or(BuilderError::OutputOverflow)?;
self.value_sent = self
.value_sent
.checked_add(chunk_value_sent)
.ok_or(BuilderError::OutputOverflow)?;

self.inner
.outputs_mut()
.try_extend_transfers(
Expand Down Expand Up @@ -251,6 +271,7 @@ impl<'i, E: ExecutionEnvironment> UpdateBuilder<'i, E> {
self.cur_tip_state_root,
self.inputs_consumed as u32,
self.fincls_processed as u32,
self.value_sent,
);

let (op, coinputs) = self
Expand All @@ -271,6 +292,7 @@ impl<'i, E: ExecutionEnvironment> UpdateBuilder<'i, E> {
self.cur_tip_state_root,
self.inputs_consumed as u32,
self.fincls_processed as u32,
self.value_sent,
);

Ok(self.inner.build_private_input(extra_data)?)
Expand Down
11 changes: 11 additions & 0 deletions crates/ee-acct-runtime/src/verification_state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,11 @@ impl<'a, E: ExecutionEnvironment> EeVerificationState<'a, E> {
self.cur_balance
}

/// Returns the total value sent out by chunk outputs processed so far.
pub fn total_val_sent(&self) -> BitcoinAmount {
self.total_val_sent
}

/// Increases our verification state tracked balance.
///
/// This is intended for when we accept a message.
Expand Down Expand Up @@ -298,6 +303,12 @@ impl<'a, E: ExecutionEnvironment> EeVerificationState<'a, E> {
return Err(EnvError::InconsistentChunkIo);
}

// Bind the deduction applied to `tracked_balance` in `pre_finalize_state`
// to the aggregate value the chunk outputs actually emitted.
if self.total_val_sent != *extra_data.value_sent() {
return Err(EnvError::InconsistentChunkIo);
}

Ok(())
}

Expand Down
9 changes: 8 additions & 1 deletion crates/ee-acct-runtime/tests/invalid_updates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ fn test_output_mismatch_fails() {
initial_state.last_exec_state_root(),
0,
0,
BitcoinAmount::ZERO,
);
let extra_data_buf = encode_to_vec(&extra_data).unwrap();

Expand Down Expand Up @@ -139,7 +140,13 @@ fn test_extra_data_tip_mismatch() {

// Manually construct operation data with a wrong tip.
let wrong_tip = Hash::new([0xAA; 32]);
let extra_data = UpdateExtraData::new(wrong_tip, initial_state.last_exec_state_root(), 0, 0);
let extra_data = UpdateExtraData::new(
wrong_tip,
initial_state.last_exec_state_root(),
0,
0,
BitcoinAmount::ZERO,
);
let extra_data_buf = encode_to_vec(&extra_data).unwrap();

let operation = UpdateOperationData::new(
Expand Down
8 changes: 7 additions & 1 deletion crates/ee-acct-types/src/extra_data.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Interpretation of extra data.

use strata_acct_types::Hash;
use strata_acct_types::{BitcoinAmount, Hash};
use strata_codec::impl_type_flat_struct;
use strata_snark_acct_runtime::IExtraData;

Expand Down Expand Up @@ -31,6 +31,12 @@ impl_type_flat_struct! {

/// The total number of items to remove from the fincl queue.
processed_fincls: u32,

/// Aggregate value sent out by chunk outputs (transfers + message
/// payloads) processed in this update. Subtracted from the account's
/// tracked balance during state finalization so that the EE-tracked
/// balance stays reconciled with the OL-side ledger view.
value_sent: BitcoinAmount,
}
}

Expand Down
44 changes: 43 additions & 1 deletion crates/ee-acct-types/src/state.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ use strata_identifiers::Hash;
use strata_snark_acct_runtime::IInnerState;
use tree_hash::{Sha256Hasher, TreeHash};

use crate::ssz_generated::ssz::state::{EeAccountState, PendingFinclEntry, PendingInputEntry};
use crate::{
errors::{EnvError, EnvResult},
ssz_generated::ssz::state::{EeAccountState, PendingFinclEntry, PendingInputEntry},
};

impl EeAccountState {
pub fn new(
Expand Down Expand Up @@ -86,6 +89,19 @@ impl EeAccountState {
.expect("snarkacct: overflowing balance");
}

/// Subtracts from the tracked balance, returning [`EnvError::InsufficientFunds`]
/// if the result would underflow.
///
/// On error the balance is left unchanged.
pub fn try_subtract_tracked_balance(&mut self, amt: BitcoinAmount) -> EnvResult<()> {
let new_balance = self
.tracked_balance
.checked_sub(amt)
.ok_or(EnvError::InsufficientFunds)?;
self.tracked_balance = new_balance;
Ok(())
}

pub fn pending_inputs(&self) -> &[PendingInputEntry] {
&self.pending_inputs
}
Expand Down Expand Up @@ -225,6 +241,7 @@ mod tests {
use strata_snark_acct_runtime::IInnerState;

use super::*;
use crate::errors::EnvError;

ssz_proptest!(
EeAccountState,
Expand Down Expand Up @@ -278,5 +295,30 @@ mod tests {
assert_ne!(a.compute_state_root(), b.compute_state_root());
assert_ne!(a.last_exec_state_root(), b.last_exec_state_root());
}

#[test]
fn tracked_balance_add_then_subtract_roundtrips() {
let mut s = EeAccountState::new(
Hash::from([0u8; 32]),
Hash::from([0u8; 32]),
BitcoinAmount::from_sat(100),
Vec::new(),
Vec::new(),
);

s.add_tracked_balance(BitcoinAmount::from_sat(50));
assert_eq!(s.tracked_balance(), BitcoinAmount::from_sat(150));

s.try_subtract_tracked_balance(BitcoinAmount::from_sat(75))
.expect("subtract within balance must succeed");
assert_eq!(s.tracked_balance(), BitcoinAmount::from_sat(75));

// Underflow must error and leave the balance unchanged.
let err = s
.try_subtract_tracked_balance(BitcoinAmount::from_sat(1000))
.expect_err("subtract beyond balance must fail");
assert!(matches!(err, EnvError::InsufficientFunds));
assert_eq!(s.tracked_balance(), BitcoinAmount::from_sat(75));
}
}
}
9 changes: 7 additions & 2 deletions crates/proof-impl/alpen-acct/src/program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -145,8 +145,13 @@ mod tests {
let state_root = initial_state.compute_state_root();

// Extra data: tip stays the same, nothing processed.
let extra_data =
UpdateExtraData::new(initial_blkid, initial_state.last_exec_state_root(), 0, 0);
let extra_data = UpdateExtraData::new(
initial_blkid,
initial_state.last_exec_state_root(),
0,
0,
BitcoinAmount::ZERO,
);
let extra_data_bytes = encode_to_vec(&extra_data).expect("encode extra data");

// With zero chunks and no state change, pre == post state root.
Expand Down
Loading