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
8 changes: 8 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 crates/subprotocols/admin/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ workspace = true
[dependencies]
strata-asm-checkpoint-msgs.workspace = true
strata-asm-common.workspace = true
strata-asm-logs.workspace = true
strata-asm-params.workspace = true
strata-asm-txs-admin.workspace = true
strata-crypto.workspace = true
Expand Down
44 changes: 42 additions & 2 deletions crates/subprotocols/admin/src/handler.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use strata_asm_checkpoint_msgs::CheckpointIncomingMsg;
use strata_asm_common::{
MsgRelayer,
AsmLogEntry, MsgRelayer,
logging::{error, info},
};
use strata_asm_logs::AsmStfUpdate;
use strata_asm_txs_admin::{
actions::{MultisigAction, UpdateAction, updates::predicate::ProofType},
parser::SignedPayload,
Expand Down Expand Up @@ -56,7 +57,13 @@ pub(crate) fn handle_pending_updates(
let (key, kind) = update.into_inner();
match kind {
ProofType::Asm => {
// TODO(STR-1721): Emit ASM Log
let log_entry = AsmLogEntry::from_log(&AsmStfUpdate::new(key))
.expect("AsmStfUpdate encoding is infallible");
relayer.emit_log(log_entry);
info!(
%update_id,
"Emitted ASM STF verifying key update log",
);
}
ProofType::OLStf => {
relay_checkpoint_predicate(relayer, key);
Expand Down Expand Up @@ -183,6 +190,7 @@ mod tests {
use rand::{rngs::OsRng, seq::SliceRandom, thread_rng};
use strata_asm_checkpoint_msgs::CheckpointIncomingMsg;
use strata_asm_common::{AsmLogEntry, InterprotoMsg, MsgRelayer};
use strata_asm_logs::AsmStfUpdate;
use strata_asm_params::{AdministrationInitConfig, Role};
use strata_asm_txs_admin::{
actions::{
Expand Down Expand Up @@ -502,6 +510,38 @@ mod tests {
}
}

#[test]
fn test_asm_verifying_key_update_emits_log() {
let (params, _, _) = create_test_params();
let mut state = AdministrationSubprotoState::new(&params);
let mut relayer = MockRelayer::<CheckpointIncomingMsg>::new();

let predicate = PredicateKey::always_accept();

let update = PredicateUpdate::new(predicate.clone(), ProofType::Asm);
let update_id = state.next_update_id();
let activation_height = 42;
state.enqueue(QueuedUpdate::new(
update_id,
update.into(),
activation_height,
));

handle_pending_updates(&mut state, &mut relayer, activation_height);

assert!(state.queued().is_empty());
// No inter-protocol messages should be sent for ASM updates
assert!(relayer.messages().is_empty());
// Exactly one log should be emitted
assert_eq!(relayer.logs.len(), 1);

let log_entry = &relayer.logs[0];
let asm_update = log_entry
.try_into_log::<AsmStfUpdate>()
.expect("log should deserialize as AsmStfUpdate");
assert_eq!(asm_update.new_predicate(), &predicate);
}

/// Test that cancel actions properly remove queued updates:
/// - First queue 5 update actions.
/// - Then cancel each one individually.
Expand Down
1 change: 1 addition & 0 deletions crates/subprotocols/admin/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ mod queued_update;
mod state;
mod subprotocol;

pub use queued_update::QueuedUpdate;
pub use state::AdministrationSubprotoState;
pub use subprotocol::AdministrationSubprotocol;
1 change: 1 addition & 0 deletions guest-builder/sp1/guest-asm/Cargo.lock

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

11 changes: 11 additions & 0 deletions tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,19 @@ version = "0.1.0"
workspace = true

[dependencies]
moho-runtime-impl.workspace = true
moho-runtime-interface.workspace = true
moho-types.workspace = true
strata-asm-common.workspace = true
strata-asm-logs.workspace = true
strata-asm-manifest-types.workspace = true
strata-asm-params = { workspace = true, features = ["arbitrary"] }
strata-asm-proof-impl = { workspace = true, features = ["test-utils"] }
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-stf.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 All @@ -31,6 +37,7 @@ strata-merkle.workspace = true
strata-predicate.workspace = true
strata-tasks.workspace = true
strata-test-utils-arb.workspace = true
strata-test-utils-btc.workspace = true
strata-test-utils-btcio.workspace = true
strata-test-utils-checkpoint.workspace = true

Expand Down Expand Up @@ -74,3 +81,7 @@ path = "asm/checkpoint.rs"
[[test]]
name = "asm_bridge_to_checkpoint"
path = "asm/bridge_to_checkpoint.rs"

[[test]]
name = "asm_admin_to_stf"
path = "asm/admin_to_stf.rs"
192 changes: 192 additions & 0 deletions tests/asm/admin_to_stf.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,192 @@
//! Admin → ASM STF interaction tests
//!
//! Tests the propagation of ASM verifying key updates as `AsmStfUpdate` logs
//! in the manifest, which the `MohoProgram` uses to set the next predicate key.

#![allow(
unused_crate_dependencies,
reason = "test dependencies shared across test suite"
)]

use harness::{
admin::{create_test_admin_setup, predicate_update, AdminExt},
test_harness::AsmTestHarnessBuilder,
};
use integration_tests::harness;
use moho_runtime_impl::RuntimeInput;
use ssz::Encode;
use strata_asm_common::AuxData;
use strata_asm_logs::AsmStfUpdate;
use strata_asm_proof_impl::{
moho_program::input::AsmStepInput, program::AsmStfProofProgram, test_utils::create_moho_state,
};
use strata_asm_spec::StrataAsmSpec;
use strata_asm_stf::compute_asm_transition;
use strata_asm_txs_admin::actions::updates::predicate::ProofType;
use strata_btc_verification::TxidInclusionProof;
use strata_predicate::PredicateKey;

/// Verifies ASM predicate updates emit an `AsmStfUpdate` log in the manifest after activation.
///
/// Flow:
/// 1. Submit predicate update with `ProofType::Asm` (gets queued)
/// 2. Mine blocks to trigger activation (confirmation_depth=2)
/// 3. Verify the manifest contains an `AsmStfUpdate` log with the correct predicate
#[tokio::test(flavor = "multi_thread")]
async fn test_asm_predicate_update_emits_log() {
let (admin_config, mut ctx) = create_test_admin_setup(2);
let harness = AsmTestHarnessBuilder::default()
.with_admin_config(admin_config)
.build()
.await
.unwrap();

// Initialize subprotocols (genesis state has no sections)
harness.mine_block(None).await.unwrap();

// Submit an ASM predicate update (gets queued for StrataAdministrator role)
let new_predicate = PredicateKey::always_accept();
harness
.submit_admin_action(
&mut ctx,
predicate_update(new_predicate.clone(), ProofType::Asm),
)
.await
.unwrap();

// Verify it's queued, not applied yet
let state = harness.admin_state().unwrap();
assert_eq!(state.queued().len(), 1, "Predicate update should be queued");

// Mine blocks to trigger activation (confirmation_depth=2)
harness.mine_block(None).await.unwrap();
harness.mine_block(None).await.unwrap();

// Admin queue should be empty
let final_state = harness.admin_state().unwrap();
assert_eq!(
final_state.queued().len(),
0,
"Queue should be empty after activation"
);

// Find the AsmStfUpdate log in the stored manifests
let manifests = harness.get_stored_manifests();
let asm_stf_update = manifests
.iter()
.flat_map(|m| &m.logs)
.find_map(|log| log.try_into_log::<AsmStfUpdate>().ok())
.expect("expected an AsmStfUpdate log in manifests");

assert_eq!(
asm_stf_update.new_predicate(),
&new_predicate,
"AsmStfUpdate log should contain the new predicate"
);
}

/// Verifies that `AsmStfProofProgram::execute()` produces a `MohoAttestation` whose post-state
/// commitment reflects the updated predicate key.
///
/// Uses the full test harness (bitcoind regtest) to naturally submit an admin predicate update,
/// mine blocks for activation, and then replays the activation block through
/// `AsmStfProofProgram::execute()` to verify the proof output.
///
/// Flow:
/// 1. Set up harness with `confirmation_depth=2`, submit predicate update (always_accept →
/// never_accept)
/// 2. Mine blocks to trigger activation, capturing the pre-state and activation block
/// 3. Build `RuntimeInput` from the captured state/block and run `AsmStfProofProgram::execute()`
/// 4. Verify the output attestation's post-state commitment reflects the new predicate
#[tokio::test(flavor = "multi_thread")]
async fn test_proof_program_reflects_predicate_update() {
let (admin_config, mut ctx) = create_test_admin_setup(2);
let harness = AsmTestHarnessBuilder::default()
.with_admin_config(admin_config)
.build()
.await
.unwrap();

// Initialize subprotocols (genesis state has no sections yet).
harness.mine_block(None).await.unwrap();

// Submit an ASM predicate update (gets queued for StrataAdministrator role).
let new_predicate = PredicateKey::never_accept();
harness
.submit_admin_action(
&mut ctx,
predicate_update(new_predicate.clone(), ProofType::Asm),
)
.await
.unwrap();

// Verify it's queued.
let state = harness.admin_state().unwrap();
assert_eq!(state.queued().len(), 1, "Predicate update should be queued");

// Mine first confirmation block.
harness.mine_block(None).await.unwrap();

// Capture the pre-state before the activation block.
let (_, pre_asm_state) = harness
.get_latest_asm_state()
.unwrap()
.expect("ASM state must exist before activation block");
let pre_anchor_state = pre_asm_state.state().clone();

// Mine the activation block (confirmation_depth=2 reached).
let activation_block_hash = harness.mine_block(None).await.unwrap();

// Admin queue should be empty after activation.
let final_state = harness.admin_state().unwrap();
assert_eq!(
final_state.queued().len(),
0,
"Queue should be empty after activation"
);

// Fetch the activation block.
let activation_block = harness.get_block(activation_block_hash).await.unwrap();
let coinbase_inclusion_proof = TxidInclusionProof::generate(&activation_block.txdata, 0);

// Build AsmStepInput from the real activation block.
let step_input = AsmStepInput::new(
activation_block.clone(),
AuxData::default(),
Some(coinbase_inclusion_proof.clone()),
);

// Build MohoState pre-state with always_accept (the initial predicate).
let initial_predicate = PredicateKey::always_accept();
let moho_pre_state = create_moho_state(&pre_anchor_state, initial_predicate);

// Construct RuntimeInput and execute the proof program.
let runtime_input = RuntimeInput::new(
moho_pre_state,
pre_anchor_state.as_ssz_bytes(),
step_input.as_ssz_bytes(),
);
let attestation =
AsmStfProofProgram::execute(&runtime_input).expect("AsmStfProofProgram::execute failed");

// Independently compute the expected post-state.
let stf_output = compute_asm_transition(
&StrataAsmSpec,
&pre_anchor_state,
&activation_block,
step_input.aux_data(),
Some(&coinbase_inclusion_proof),
)
.expect("compute_asm_transition failed");

// The post MohoState should carry `never_accept` as the next predicate,
// because the queued AsmStfUpdate log was emitted during the transition.
let expected_post_moho = create_moho_state(&stf_output.state, new_predicate);

// The proven commitment in the attestation must match.
assert_eq!(
attestation.proven().commitment(),
&expected_post_moho.compute_commitment(),
"post-state commitment should reflect the updated predicate (never_accept)"
);
}
8 changes: 8 additions & 0 deletions tests/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,19 @@ use bitcoin_bosd as _;
use bitcoind_async_client as _;
use borsh as _;
use corepc_node as _;
use moho_runtime_impl as _;
use moho_runtime_interface as _;
use moho_types as _;
use rand as _;
use rand_chacha as _;
use ssz as _;
use strata_asm_common as _;
use strata_asm_logs as _;
use strata_asm_manifest_types as _;
use strata_asm_proof_impl as _;
use strata_asm_proto_administration as _;
use strata_asm_spec as _;
use strata_asm_stf as _;
use strata_asm_txs_admin as _;
use strata_asm_worker as _;
use strata_bridge_types as _;
Expand All @@ -29,5 +36,6 @@ use strata_l1_txfmt as _;
use strata_merkle as _;
use strata_predicate as _;
use strata_tasks as _;
use strata_test_utils_btc as _;
use strata_test_utils_btcio as _;
use strata_test_utils_checkpoint as _;
Loading