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
2 changes: 2 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions bin/asm-runner/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ strata-asm-proof-db.workspace = true
strata-asm-proof-impl.workspace = true
strata-asm-proof-types.workspace = true
strata-asm-proto-bridge-v1.workspace = true
strata-asm-spec.workspace = true
strata-asm-txs-bridge-v1.workspace = true
strata-asm-worker.workspace = true
strata-btc-types.workspace = true
Expand Down
2 changes: 2 additions & 0 deletions bin/asm-runner/src/bootstrap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use anyhow::Result;
use bitcoind_async_client::{Auth, Client};
use strata_asm_params::AsmParams;
use strata_asm_proof_db::SledProofDb;
use strata_asm_spec::StrataAsmSpec;
use strata_asm_worker::AsmWorkerBuilder;
use strata_tasks::TaskExecutor;
use tokio::{
Expand Down Expand Up @@ -44,6 +45,7 @@ pub(crate) async fn bootstrap(
let asm_worker = AsmWorkerBuilder::new()
.with_context(worker_context)
.with_asm_params(Arc::new(params.clone()))
.with_asm_spec(StrataAsmSpec)
.launch(&executor)?;

// 5. Compute the starting height for the block watcher.
Expand Down
28 changes: 23 additions & 5 deletions crates/worker/src/builder.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::sync::Arc;

use strata_asm_common::AsmSpec;
use strata_asm_params::AsmParams;
use strata_service::ServiceBuilder;
use strata_tasks::TaskExecutor;
Expand All @@ -15,18 +16,24 @@ use crate::{
/// launch an ASM worker using the service framework, preventing impl details
/// from leaking into the caller. The builder launches the service and returns
/// a handle to it.
///
/// Generic over the worker context `W` and the ASM spec `S`, so callers can
/// inject alternative specs (e.g. a debug-wrapped spec for testing) without
/// forking the worker.
#[derive(Debug)]
pub struct AsmWorkerBuilder<W> {
pub struct AsmWorkerBuilder<W, S> {
context: Option<W>,
asm_params: Option<Arc<AsmParams>>,
spec: Option<S>,
}

impl<W> AsmWorkerBuilder<W> {
impl<W, S> AsmWorkerBuilder<W, S> {
/// Create a new builder instance.
pub fn new() -> Self {
Self {
context: None,
asm_params: None,
spec: None,
}
}

Expand All @@ -41,6 +48,15 @@ impl<W> AsmWorkerBuilder<W> {
self
}

/// Set the ASM spec driving the subprotocol pipeline.
///
/// Production deployments pass [`strata_asm_spec::StrataAsmSpec`]; tests
/// can pass a wrapped debug spec to inject extra subprotocols.
pub fn with_asm_spec(mut self, spec: S) -> Self {
self.spec = Some(spec);
self
}

/// Launch the ASM worker service and return a handle to it.
///
/// This method validates all required dependencies, creates the service state,
Expand All @@ -49,20 +65,22 @@ impl<W> AsmWorkerBuilder<W> {
pub fn launch(self, executor: &TaskExecutor) -> anyhow::Result<AsmWorkerHandle>
where
W: WorkerContext + Send + Sync + 'static,
S: AsmSpec + Send + Sync + 'static,
{
let context = self
.context
.ok_or(WorkerError::MissingDependency("context"))?;
let asm_params = self
.asm_params
.ok_or(WorkerError::MissingDependency("asm_params"))?;
let spec = self.spec.ok_or(WorkerError::MissingDependency("spec"))?;

// Create the service state.
let service_state = AsmWorkerServiceState::new(context, asm_params);
let service_state = AsmWorkerServiceState::new(context, asm_params, spec);

// Create the service builder and get command handle.
let mut service_builder =
ServiceBuilder::<AsmWorkerService<W>, _>::new().with_state(service_state);
ServiceBuilder::<AsmWorkerService<W, S>, _>::new().with_state(service_state);

// Create the command handle before launching.
let command_handle = service_builder.create_command_handle(64);
Expand All @@ -77,7 +95,7 @@ impl<W> AsmWorkerBuilder<W> {
}
}

impl<W> Default for AsmWorkerBuilder<W> {
impl<W, S> Default for AsmWorkerBuilder<W, S> {
fn default() -> Self {
Self::new()
}
Expand Down
33 changes: 23 additions & 10 deletions crates/worker/src/service.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ use std::{marker, thread::sleep, time::Duration};

use bitcoin::hashes::Hash;
use serde::{Deserialize, Serialize};
use strata_asm_common::AsmSpec;
use strata_btc_types::BlockHashExt;
use strata_identifiers::{Buf32, L1BlockCommitment, L1BlockId};
use strata_service::{Response, Service, SyncService};
Expand All @@ -16,12 +17,16 @@ use crate::{

/// ASM service implementation using the service framework.
#[derive(Debug)]
pub struct AsmWorkerService<W> {
_phantom: marker::PhantomData<W>,
pub struct AsmWorkerService<W, S> {
_phantom: marker::PhantomData<(W, S)>,
}

impl<W: WorkerContext + Send + Sync + 'static> Service for AsmWorkerService<W> {
type State = AsmWorkerServiceState<W>;
impl<W, S> Service for AsmWorkerService<W, S>
where
W: WorkerContext + Send + Sync + 'static,
S: AsmSpec + Send + Sync + 'static,
{
type State = AsmWorkerServiceState<W, S>;
type Msg = AsmWorkerMessage;
type Status = AsmWorkerStatus;

Expand All @@ -34,14 +39,18 @@ impl<W: WorkerContext + Send + Sync + 'static> Service for AsmWorkerService<W> {
}
}

impl<W: WorkerContext + Send + Sync + 'static> SyncService for AsmWorkerService<W> {
fn on_launch(state: &mut AsmWorkerServiceState<W>) -> anyhow::Result<()> {
impl<W, S> SyncService for AsmWorkerService<W, S>
where
W: WorkerContext + Send + Sync + 'static,
S: AsmSpec + Send + Sync + 'static,
{
fn on_launch(state: &mut AsmWorkerServiceState<W, S>) -> anyhow::Result<()> {
Ok(state.load_latest_or_create_genesis()?)
}

// TODO(STR-1928): add tests.
fn process_input(
state: &mut AsmWorkerServiceState<W>,
state: &mut AsmWorkerServiceState<W, S>,
input: AsmWorkerMessage,
) -> anyhow::Result<Response> {
match input {
Expand All @@ -59,10 +68,14 @@ impl<W: WorkerContext + Send + Sync + 'static> SyncService for AsmWorkerService<
}

/// Processes an L1 block through the ASM state transition.
fn process_block<W: WorkerContext + Send + Sync + 'static>(
state: &mut AsmWorkerServiceState<W>,
fn process_block<W, S>(
state: &mut AsmWorkerServiceState<W, S>,
incoming_block: &L1BlockCommitment,
) -> crate::WorkerResult<()> {
) -> crate::WorkerResult<()>
where
W: WorkerContext + Send + Sync + 'static,
S: AsmSpec + Send + Sync + 'static,
{
let ctx = &state.context;

// Handle pre-genesis: if the block is before genesis we don't care about it.
Expand Down
38 changes: 28 additions & 10 deletions crates/worker/src/state.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use std::sync::Arc;

use bitcoin::Block;
use strata_asm_common::AuxData;
use strata_asm_common::{AsmSpec, AuxData};
use strata_asm_params::AsmParams;
use strata_asm_spec::{StrataAsmSpec, construct_genesis_state};
use strata_asm_spec::construct_genesis_state;
use strata_asm_stf::AsmStfOutput;
use strata_btc_verification::TxidInclusionProof;
use strata_identifiers::L1BlockCommitment;
Expand All @@ -15,14 +15,21 @@ use crate::{
};

/// Service state for the ASM worker.
///
/// Generic over the worker context `W` and the ASM spec `S`, so callers can
/// inject alternative specs (e.g. `DebugAsmSpec` wrapping `StrataAsmSpec` for
/// testing) without forking the worker.
#[derive(Debug)]
pub struct AsmWorkerServiceState<W> {
pub struct AsmWorkerServiceState<W, S> {
/// Params.
pub(crate) asm_params: Arc<AsmParams>,

/// Context for the state to interact with outer world.
pub(crate) context: W,

/// ASM spec driving the subprotocol pipeline.
pub(crate) spec: S,

/// Whether the service is initialized.
pub initialized: bool,

Expand All @@ -33,12 +40,17 @@ pub struct AsmWorkerServiceState<W> {
pub blkid: Option<L1BlockCommitment>,
}

impl<W: WorkerContext + Send + Sync + 'static> AsmWorkerServiceState<W> {
impl<W, S> AsmWorkerServiceState<W, S>
where
W: WorkerContext + Send + Sync + 'static,
S: AsmSpec + Send + Sync + 'static,
{
/// A new (uninitialized) instance of the service state.
pub fn new(context: W, asm_params: Arc<AsmParams>) -> Self {
pub fn new(context: W, asm_params: Arc<AsmParams>, spec: S) -> Self {
Self {
asm_params,
context,
spec,
anchor: None,
blkid: None,
initialized: false,
Expand Down Expand Up @@ -80,7 +92,7 @@ impl<W: WorkerContext + Send + Sync + 'static> AsmWorkerServiceState<W> {
let span = tracing::debug_span!("asm.stf.pre_process", protocol_txs = Empty);
let _guard = span.enter();

let result = strata_asm_stf::pre_process_asm(&StrataAsmSpec, cur_state.state(), block)
let result = strata_asm_stf::pre_process_asm(&self.spec, cur_state.state(), block)
.map_err(WorkerError::AsmError)?;

span.record("protocol_txs", result.txs.len());
Expand Down Expand Up @@ -109,7 +121,7 @@ impl<W: WorkerContext + Send + Sync + 'static> AsmWorkerServiceState<W> {
let coinbase_inclusion_proof = TxidInclusionProof::generate(&block.txdata, 0);

strata_asm_stf::compute_asm_transition(
&StrataAsmSpec,
&self.spec,
cur_state.state(),
block,
&aux_data,
Expand All @@ -127,7 +139,11 @@ impl<W: WorkerContext + Send + Sync + 'static> AsmWorkerServiceState<W> {
}
}

impl<W: WorkerContext + Send + Sync + 'static> ServiceState for AsmWorkerServiceState<W> {
impl<W, S> ServiceState for AsmWorkerServiceState<W, S>
where
W: WorkerContext + Send + Sync + 'static,
S: AsmSpec + Send + Sync + 'static,
{
fn name(&self) -> &str {
constants::SERVICE_NAME
}
Expand All @@ -145,6 +161,7 @@ mod tests {
};
use corepc_node::Node;
use strata_asm_common::AsmManifest;
use strata_asm_spec::StrataAsmSpec;
use strata_btc_types::{BitcoinTxid, BlockHashExt, RawBitcoinTx};
use strata_btc_verification::L1Anchor;
use strata_identifiers::{Hash, L1BlockId};
Expand All @@ -156,7 +173,7 @@ mod tests {
struct TestEnv {
pub _node: Node, // Keep node alive
pub client: Arc<Client>,
pub service_state: AsmWorkerServiceState<MockWorkerContext>,
pub service_state: AsmWorkerServiceState<MockWorkerContext, StrataAsmSpec>,
}

async fn setup_env() -> TestEnv {
Expand All @@ -183,7 +200,8 @@ mod tests {

// 3. Set worker context and initialize service state
let context = MockWorkerContext::new();
let mut service_state = AsmWorkerServiceState::new(context.clone(), asm_params);
let mut service_state =
AsmWorkerServiceState::new(context.clone(), asm_params, StrataAsmSpec);

// Initialize: this should create genesis state based on our `genesis_l1_view`
service_state
Expand Down
1 change: 1 addition & 0 deletions tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ strata-asm-params = { workspace = true, features = ["arbitrary"] }
strata-asm-proto-administration.workspace = true
strata-asm-proto-bridge-v1.workspace = true
strata-asm-proto-checkpoint.workspace = true
strata-asm-spec.workspace = true
strata-asm-txs-admin = { workspace = true, features = ["test-utils"] }
strata-asm-txs-bridge-v1 = { workspace = true, features = ["test-utils"] }
strata-asm-worker.workspace = true
Expand Down
2 changes: 2 additions & 0 deletions tests/harness/test_harness.rs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ use strata_asm_params::{
AdministrationInitConfig, AsmParams, BridgeV1InitConfig, CheckpointInitConfig,
SubprotocolInstance,
};
use strata_asm_spec::StrataAsmSpec;
use strata_asm_worker::{AsmState, AsmWorkerBuilder, AsmWorkerHandle, WorkerContext};
use strata_btc_types::BlockHashExt;
use strata_identifiers::{Buf32, L1BlockCommitment};
Expand Down Expand Up @@ -588,6 +589,7 @@ impl AsmTestHarnessBuilder {
let asm_handle = AsmWorkerBuilder::new()
.with_context(context.clone())
.with_asm_params(asm_params.clone())
.with_asm_spec(StrataAsmSpec)
.launch(&executor)?;

let harness = AsmTestHarness {
Expand Down
Loading