Skip to content
Closed
5 changes: 5 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions bin/asm-runner/src/bootstrap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ pub(crate) async fn bootstrap(
aux_db,
manifest_db,
mmr_db,
fork_activation_db,
export_entries_db,
} = create_storage(&config.database)?;

Expand Down Expand Up @@ -65,6 +66,7 @@ pub(crate) async fn bootstrap(
aux_db.clone(),
manifest_db.clone(),
mmr_db.clone(),
fork_activation_db,
);

// 5. Launch ASM worker.
Expand Down
6 changes: 5 additions & 1 deletion bin/asm-runner/src/storage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
use std::sync::Arc;

use anyhow::Result;
use asm_storage::{SledAsmAuxDataDb, SledAsmManifestDb, SledAsmManifestMmrDb, SledAsmStateDb};
use asm_storage::{
SledAsmAuxDataDb, SledAsmManifestDb, SledAsmManifestMmrDb, SledAsmStateDb, SledForkActivationDb,
};
use strata_asm_moho_storage::SledExportEntriesDb;

use crate::config::DatabaseConfig;
Expand All @@ -14,6 +16,7 @@ pub(crate) struct Storage {
pub aux_db: Arc<SledAsmAuxDataDb>,
pub manifest_db: Arc<SledAsmManifestDb>,
pub mmr_db: Arc<SledAsmManifestMmrDb>,
pub fork_activation_db: Arc<SledForkActivationDb>,
pub export_entries_db: SledExportEntriesDb,
}

Expand All @@ -25,6 +28,7 @@ pub(crate) fn create_storage(config: &DatabaseConfig) -> Result<Storage> {
aux_db: Arc::new(SledAsmAuxDataDb::open(&db)?),
manifest_db: Arc::new(SledAsmManifestDb::open(&db)?),
mmr_db: Arc::new(SledAsmManifestMmrDb::open(&db)?),
fork_activation_db: Arc::new(SledForkActivationDb::open(&db)?),
export_entries_db: SledExportEntriesDb::open(&db)?,
})
}
36 changes: 33 additions & 3 deletions bin/asm-runner/src/worker_context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@

use std::sync::Arc;

use asm_storage::{SledAsmAuxDataDb, SledAsmManifestDb, SledAsmManifestMmrDb, SledAsmStateDb};
use asm_storage::{
SledAsmAuxDataDb, SledAsmManifestDb, SledAsmManifestMmrDb, SledAsmStateDb, SledForkActivationDb,
};
use bitcoin::{Block, BlockHash, Network, block::Header};
use bitcoind_async_client::{Client, error::ClientError, traits::Reader};
use strata_asm_common::{AnchorState, AsmManifest, AsmManifestHash, AuxData};
use strata_asm_common::{AnchorState, AsmManifest, AsmManifestHash, AuxData, ForkActivation};
use strata_asm_worker::{
AnchorStateStore, AuxDataStore, L1DataProvider, ManifestMmrStore, WorkerError, WorkerResult,
AnchorStateStore, AuxDataStore, ForkActivationStore, L1DataProvider, ManifestMmrStore,
WorkerError, WorkerResult,
};
use strata_btc_types::{BitcoinTxid, L1BlockIdBitcoinExt, RawBitcoinTx};
use strata_identifiers::{L1BlockCommitment, L1BlockId};
Expand All @@ -36,9 +39,14 @@ pub(crate) struct AsmWorkerContext {
aux_db: Arc<SledAsmAuxDataDb>,
manifest_db: Arc<SledAsmManifestDb>,
mmr_db: Arc<SledAsmManifestMmrDb>,
fork_activation_db: Arc<SledForkActivationDb>,
}

impl AsmWorkerContext {
#[expect(
clippy::too_many_arguments,
reason = "one argument per storage concern"
)]
pub(crate) fn new(
runtime_handle: Handle,
bitcoin_client: Arc<Client>,
Expand All @@ -47,6 +55,7 @@ impl AsmWorkerContext {
aux_db: Arc<SledAsmAuxDataDb>,
manifest_db: Arc<SledAsmManifestDb>,
mmr_db: Arc<SledAsmManifestMmrDb>,
fork_activation_db: Arc<SledForkActivationDb>,
) -> Self {
Self {
runtime_handle,
Expand All @@ -57,6 +66,7 @@ impl AsmWorkerContext {
aux_db,
manifest_db,
mmr_db,
fork_activation_db,
}
}
}
Expand Down Expand Up @@ -233,6 +243,26 @@ impl ManifestMmrStore for AsmWorkerContext {
}
}

impl ForkActivationStore for AsmWorkerContext {
fn record_fork_activation(&self, activation: ForkActivation) -> WorkerResult<()> {
self.fork_activation_db
.put(activation)
.map_err(|_| WorkerError::DbError)
}

fn list_fork_activations(&self) -> WorkerResult<Vec<ForkActivation>> {
self.fork_activation_db
.list()
.map_err(|_| WorkerError::DbError)
}

fn prune_fork_activations_after(&self, after_height: u32) -> WorkerResult<()> {
self.fork_activation_db
.prune_after(after_height)
.map_err(|_| WorkerError::DbError)
}
}

impl AuxDataStore for AsmWorkerContext {
fn store_aux_data(&self, blockid: &L1BlockCommitment, data: &AuxData) -> WorkerResult<()> {
self.aux_db
Expand Down
1 change: 1 addition & 0 deletions crates/common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ strata-identifiers.workspace = true
strata-l1-txfmt.workspace = true
strata-merkle = { workspace = true, features = ["ssz"] }
strata-msg-fmt.workspace = true
strata-predicate.workspace = true

bitcoin.workspace = true
borsh.workspace = true
Expand Down
55 changes: 55 additions & 0 deletions crates/common/src/fork.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
//! outcome at every height it executes (see `StfParams`).

use serde::{Deserialize, Serialize};
use strata_predicate::PredicateKey;

/// Identifies a named fork.
///
Expand Down Expand Up @@ -60,6 +61,35 @@ impl TryFrom<u16> for ForkId {
}
}

/// A discovered fork activation.
///
/// Records that the block at `enacting_height` enacted an ASM VK upgrade
/// which activates `fork` from the next block onward, switching the ASM STF
/// predicate to `new_predicate`.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ForkActivation {
/// Height of the L1 block whose ASM VK upgrade enactment triggered the
/// activation.
pub enacting_height: u32,

/// The activated fork.
pub fork: ForkId,

/// The ASM STF predicate the upgrade enacted. Enactment removes the
/// update from the admin queue and only surfaces it in the emitted log,
/// so this record is the worker's durable copy of the VK the boundary
/// switched to.
pub new_predicate: PredicateKey,
}

impl ForkActivation {
/// Height from which the fork's rules apply: the block after the
/// enacting one.
pub fn activation_height(&self) -> u64 {
self.enacting_height as u64 + 1
}
}

/// Activation heights for every named fork.
///
/// A fork is active at L1 height `h` iff `h >= activation_height`. `0` means
Expand All @@ -79,6 +109,11 @@ impl ForkSchedule {
Self { fork1: u64::MAX }
}

/// Schedule with every fork active since genesis (activation at `0`).
pub const fn all_enabled() -> Self {
Self { fork1: 0 }
}

/// Returns the activation height of `fork`.
pub fn activation_height(&self, fork: ForkId) -> u64 {
match fork {
Expand Down Expand Up @@ -120,6 +155,16 @@ pub struct StfParams {
pub forks: ForkSchedule,
}

impl StfParams {
/// Params with every fork active since genesis, matching current mainline
/// behavior.
pub const fn all_forks_enabled() -> Self {
Self {
forks: ForkSchedule::all_enabled(),
}
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand Down Expand Up @@ -181,4 +226,14 @@ mod tests {
assert_eq!(ForkId::try_from(0u16).unwrap(), ForkId::Fork1);
assert_eq!(ForkId::try_from(0xFFFFu16), Err(0xFFFF));
}

#[test]
fn activation_is_block_after_enactment() {
let activation = ForkActivation {
enacting_height: 41,
fork: ForkId::Fork1,
new_predicate: PredicateKey::always_accept(),
};
assert_eq!(activation.activation_height(), 42);
}
}
2 changes: 1 addition & 1 deletion crates/extensions/prover/worker/src/backend/native.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ pub(super) async fn build_native_hosts(
use zkaleido_native_adapter::NativeHost;

// Matches the schedule baked into the production ASM guest.
let stf_params = StfParams::default();
let stf_params = StfParams::all_forks_enabled();
Ok((
NativeHost::new(asm_signing_key.clone(), move |env| {
process_asm_stf(env, stf_params.clone())
Expand Down
55 changes: 43 additions & 12 deletions crates/logs/src/asm_stf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,25 @@ use strata_predicate::PredicateKey;

use crate::constants::AsmLogTypeId;

/// Details for an execution environment verification key update.
/// Details for an ASM STF verification key update.
#[derive(Debug, Clone)]
pub struct AsmStfUpdate {
/// New execution environment state transition function verification key.
/// New ASM state transition function verification key.
new_predicate: PredicateKey,

/// Raw id of the fork the new proving artifact implements, carried
/// verbatim from the enacted update. Raw so that artifacts predating the
/// fork can still emit it; the worker maps ids it knows.
fork_id: u16,
}

impl AsmStfUpdate {
/// Create a new AsmStfUpdate instance.
pub fn new(new_predicate: PredicateKey) -> Self {
Self { new_predicate }
pub fn new(new_predicate: PredicateKey, fork_id: u16) -> Self {
Self {
new_predicate,
fork_id,
}
}

pub fn new_predicate(&self) -> &PredicateKey {
Expand All @@ -26,16 +34,25 @@ impl AsmStfUpdate {
pub fn into_new_predicate(self) -> PredicateKey {
self.new_predicate
}

pub fn fork_id(&self) -> u16 {
self.fork_id
}
}

impl Codec for AsmStfUpdate {
fn decode(dec: &mut impl Decoder) -> Result<Self, CodecError> {
let new_predicate = CodecSsz::<PredicateKey>::decode(dec)?.into_inner();
Ok(Self { new_predicate })
let fork_id = CodecSsz::<u16>::decode(dec)?.into_inner();
Ok(Self {
new_predicate,
fork_id,
})
}

fn encode(&self, enc: &mut impl Encoder) -> Result<(), CodecError> {
CodecSsz::new(self.new_predicate.clone()).encode(enc)
CodecSsz::new(self.new_predicate.clone()).encode(enc)?;
CodecSsz::new(self.fork_id).encode(enc)
}
}

Expand All @@ -62,22 +79,36 @@ mod tests {
proptest! {
#[test]
fn from_log_is_infallible(key in predicate_key_strategy()) {
let log = AsmStfUpdate::new(key);
let log = AsmStfUpdate::new(key, 1);
prop_assert!(AsmLogEntry::from_log(&log).is_ok());
}
}

#[test]
fn from_log_boundary_cases() {
let cases = [
AsmStfUpdate::new(PredicateKey::new(PredicateTypeId::AlwaysAccept, vec![])),
AsmStfUpdate::new(PredicateKey::new(
PredicateTypeId::AlwaysAccept,
vec![0u8; MAX_CONDITION_LEN as usize],
)),
AsmStfUpdate::new(PredicateKey::new(PredicateTypeId::AlwaysAccept, vec![]), 1),
AsmStfUpdate::new(
PredicateKey::new(
PredicateTypeId::AlwaysAccept,
vec![0u8; MAX_CONDITION_LEN as usize],
),
1,
),
];
for log in cases {
assert!(AsmLogEntry::from_log(&log).is_ok());
}
}

#[test]
fn roundtrip_preserves_fork_id() {
let log = AsmStfUpdate::new(PredicateKey::always_accept(), 7);
let entry = AsmLogEntry::from_log(&log).expect("encoding is infallible");
let back = entry
.try_into_log::<AsmStfUpdate>()
.expect("log should decode back");
assert_eq!(back.fork_id(), 7);
assert_eq!(back.new_predicate(), log.new_predicate());
}
}
3 changes: 2 additions & 1 deletion crates/proof/statements/src/program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,8 @@ mod tests {
fn test_stf() {
let runtime_input = create_runtime_input();

let output = AsmStfProofProgram::execute(&runtime_input, StfParams::default()).unwrap();
let output =
AsmStfProofProgram::execute(&runtime_input, StfParams::all_forks_enabled()).unwrap();
dbg!(output);
}
}
1 change: 1 addition & 0 deletions crates/storage/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ strata-asm-common.workspace = true
strata-identifiers.workspace = true
strata-merkle = { workspace = true, features = ["ssz"] }
strata-merkle-node-store.workspace = true
strata-predicate = { workspace = true, features = ["borsh"] }

anyhow.workspace = true
borsh.workspace = true
Expand Down
Loading
Loading