|
| 1 | +//! Admin → ASM STF interaction tests |
| 2 | +//! |
| 3 | +//! Tests the propagation of ASM verifying key updates as `AsmStfUpdate` logs |
| 4 | +//! in the manifest, which the `MohoProgram` uses to set the next predicate key. |
| 5 | +
|
| 6 | +#![allow( |
| 7 | + unused_crate_dependencies, |
| 8 | + reason = "test dependencies shared across test suite" |
| 9 | +)] |
| 10 | + |
| 11 | +use harness::{ |
| 12 | + admin::{create_test_admin_setup, predicate_update, AdminExt}, |
| 13 | + test_harness::AsmTestHarnessBuilder, |
| 14 | +}; |
| 15 | +use integration_tests::harness; |
| 16 | +use moho_runtime_impl::RuntimeInput; |
| 17 | +use ssz::Encode; |
| 18 | +use strata_asm_common::AuxData; |
| 19 | +use strata_asm_logs::AsmStfUpdate; |
| 20 | +use strata_asm_proof_impl::{ |
| 21 | + moho_program::input::AsmStepInput, program::AsmStfProofProgram, test_utils::create_moho_state, |
| 22 | +}; |
| 23 | +use strata_asm_spec::StrataAsmSpec; |
| 24 | +use strata_asm_stf::compute_asm_transition; |
| 25 | +use strata_asm_txs_admin::actions::updates::predicate::ProofType; |
| 26 | +use strata_btc_verification::TxidInclusionProof; |
| 27 | +use strata_predicate::PredicateKey; |
| 28 | + |
| 29 | +/// Verifies ASM predicate updates emit an `AsmStfUpdate` log in the manifest after activation. |
| 30 | +/// |
| 31 | +/// Flow: |
| 32 | +/// 1. Submit predicate update with `ProofType::Asm` (gets queued) |
| 33 | +/// 2. Mine blocks to trigger activation (confirmation_depth=2) |
| 34 | +/// 3. Verify the manifest contains an `AsmStfUpdate` log with the correct predicate |
| 35 | +#[tokio::test(flavor = "multi_thread")] |
| 36 | +async fn test_asm_predicate_update_emits_log() { |
| 37 | + let (admin_config, mut ctx) = create_test_admin_setup(2); |
| 38 | + let harness = AsmTestHarnessBuilder::default() |
| 39 | + .with_admin_config(admin_config) |
| 40 | + .build() |
| 41 | + .await |
| 42 | + .unwrap(); |
| 43 | + |
| 44 | + // Initialize subprotocols (genesis state has no sections) |
| 45 | + harness.mine_block(None).await.unwrap(); |
| 46 | + |
| 47 | + // Submit an ASM predicate update (gets queued for StrataAdministrator role) |
| 48 | + let new_predicate = PredicateKey::always_accept(); |
| 49 | + harness |
| 50 | + .submit_admin_action( |
| 51 | + &mut ctx, |
| 52 | + predicate_update(new_predicate.clone(), ProofType::Asm), |
| 53 | + ) |
| 54 | + .await |
| 55 | + .unwrap(); |
| 56 | + |
| 57 | + // Verify it's queued, not applied yet |
| 58 | + let state = harness.admin_state().unwrap(); |
| 59 | + assert_eq!(state.queued().len(), 1, "Predicate update should be queued"); |
| 60 | + |
| 61 | + // Mine blocks to trigger activation (confirmation_depth=2) |
| 62 | + harness.mine_block(None).await.unwrap(); |
| 63 | + harness.mine_block(None).await.unwrap(); |
| 64 | + |
| 65 | + // Admin queue should be empty |
| 66 | + let final_state = harness.admin_state().unwrap(); |
| 67 | + assert_eq!( |
| 68 | + final_state.queued().len(), |
| 69 | + 0, |
| 70 | + "Queue should be empty after activation" |
| 71 | + ); |
| 72 | + |
| 73 | + // Find the AsmStfUpdate log in the stored manifests |
| 74 | + let manifests = harness.get_stored_manifests(); |
| 75 | + let asm_stf_update = manifests |
| 76 | + .iter() |
| 77 | + .flat_map(|m| &m.logs) |
| 78 | + .find_map(|log| log.try_into_log::<AsmStfUpdate>().ok()) |
| 79 | + .expect("expected an AsmStfUpdate log in manifests"); |
| 80 | + |
| 81 | + assert_eq!( |
| 82 | + asm_stf_update.new_predicate(), |
| 83 | + &new_predicate, |
| 84 | + "AsmStfUpdate log should contain the new predicate" |
| 85 | + ); |
| 86 | +} |
| 87 | + |
| 88 | +/// Verifies that `AsmStfProofProgram::execute()` produces a `MohoAttestation` whose post-state |
| 89 | +/// commitment reflects the updated predicate key. |
| 90 | +/// |
| 91 | +/// Uses the full test harness (bitcoind regtest) to naturally submit an admin predicate update, |
| 92 | +/// mine blocks for activation, and then replays the activation block through |
| 93 | +/// `AsmStfProofProgram::execute()` to verify the proof output. |
| 94 | +/// |
| 95 | +/// Flow: |
| 96 | +/// 1. Set up harness with `confirmation_depth=2`, submit predicate update (always_accept → |
| 97 | +/// never_accept) |
| 98 | +/// 2. Mine blocks to trigger activation, capturing the pre-state and activation block |
| 99 | +/// 3. Build `RuntimeInput` from the captured state/block and run `AsmStfProofProgram::execute()` |
| 100 | +/// 4. Verify the output attestation's post-state commitment reflects the new predicate |
| 101 | +#[tokio::test(flavor = "multi_thread")] |
| 102 | +async fn test_proof_program_reflects_predicate_update() { |
| 103 | + let (admin_config, mut ctx) = create_test_admin_setup(2); |
| 104 | + let harness = AsmTestHarnessBuilder::default() |
| 105 | + .with_admin_config(admin_config) |
| 106 | + .build() |
| 107 | + .await |
| 108 | + .unwrap(); |
| 109 | + |
| 110 | + // Initialize subprotocols (genesis state has no sections yet). |
| 111 | + harness.mine_block(None).await.unwrap(); |
| 112 | + |
| 113 | + // Submit an ASM predicate update (gets queued for StrataAdministrator role). |
| 114 | + let new_predicate = PredicateKey::never_accept(); |
| 115 | + harness |
| 116 | + .submit_admin_action( |
| 117 | + &mut ctx, |
| 118 | + predicate_update(new_predicate.clone(), ProofType::Asm), |
| 119 | + ) |
| 120 | + .await |
| 121 | + .unwrap(); |
| 122 | + |
| 123 | + // Verify it's queued. |
| 124 | + let state = harness.admin_state().unwrap(); |
| 125 | + assert_eq!(state.queued().len(), 1, "Predicate update should be queued"); |
| 126 | + |
| 127 | + // Mine first confirmation block. |
| 128 | + harness.mine_block(None).await.unwrap(); |
| 129 | + |
| 130 | + // Capture the pre-state before the activation block. |
| 131 | + let (_, pre_asm_state) = harness |
| 132 | + .get_latest_asm_state() |
| 133 | + .unwrap() |
| 134 | + .expect("ASM state must exist before activation block"); |
| 135 | + let pre_anchor_state = pre_asm_state.state().clone(); |
| 136 | + |
| 137 | + // Mine the activation block (confirmation_depth=2 reached). |
| 138 | + let activation_block_hash = harness.mine_block(None).await.unwrap(); |
| 139 | + |
| 140 | + // Admin queue should be empty after activation. |
| 141 | + let final_state = harness.admin_state().unwrap(); |
| 142 | + assert_eq!( |
| 143 | + final_state.queued().len(), |
| 144 | + 0, |
| 145 | + "Queue should be empty after activation" |
| 146 | + ); |
| 147 | + |
| 148 | + // Fetch the activation block. |
| 149 | + let activation_block = harness.get_block(activation_block_hash).await.unwrap(); |
| 150 | + let coinbase_inclusion_proof = TxidInclusionProof::generate(&activation_block.txdata, 0); |
| 151 | + |
| 152 | + // Build AsmStepInput from the real activation block. |
| 153 | + let step_input = AsmStepInput::new( |
| 154 | + activation_block.clone(), |
| 155 | + AuxData::default(), |
| 156 | + Some(coinbase_inclusion_proof.clone()), |
| 157 | + ); |
| 158 | + |
| 159 | + // Build MohoState pre-state with always_accept (the initial predicate). |
| 160 | + let initial_predicate = PredicateKey::always_accept(); |
| 161 | + let moho_pre_state = create_moho_state(&pre_anchor_state, initial_predicate); |
| 162 | + |
| 163 | + // Construct RuntimeInput and execute the proof program. |
| 164 | + let runtime_input = RuntimeInput::new( |
| 165 | + moho_pre_state, |
| 166 | + pre_anchor_state.as_ssz_bytes(), |
| 167 | + step_input.as_ssz_bytes(), |
| 168 | + ); |
| 169 | + let attestation = |
| 170 | + AsmStfProofProgram::execute(&runtime_input).expect("AsmStfProofProgram::execute failed"); |
| 171 | + |
| 172 | + // Independently compute the expected post-state. |
| 173 | + let stf_output = compute_asm_transition( |
| 174 | + &StrataAsmSpec, |
| 175 | + &pre_anchor_state, |
| 176 | + &activation_block, |
| 177 | + step_input.aux_data(), |
| 178 | + Some(&coinbase_inclusion_proof), |
| 179 | + ) |
| 180 | + .expect("compute_asm_transition failed"); |
| 181 | + |
| 182 | + // The post MohoState should carry `never_accept` as the next predicate, |
| 183 | + // because the queued AsmStfUpdate log was emitted during the transition. |
| 184 | + let expected_post_moho = create_moho_state(&stf_output.state, new_predicate); |
| 185 | + |
| 186 | + // The proven commitment in the attestation must match. |
| 187 | + assert_eq!( |
| 188 | + attestation.proven().commitment(), |
| 189 | + &expected_post_moho.compute_commitment(), |
| 190 | + "post-state commitment should reflect the updated predicate (never_accept)" |
| 191 | + ); |
| 192 | +} |
0 commit comments