Skip to content

Conversation

irnb
Copy link
Contributor

@irnb irnb commented Sep 18, 2025

Description

Crates:

  • crates/asm/subprotocols/checkpointing-v0
  • crates/asm/txs/checkpointing
  • crates/btcio/

This PR introduces an SPS-50–compatible btcio writer, removes the old parsing logic from the btcio reader, and implements checkpointing-v0.

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature/Enhancement (non-breaking change which adds functionality or enhances an existing one)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Refactor
  • New or updated tests
  • Dependency Update

Notes to Reviewers

Checklist

  • I have performed a self-review of my code.
  • I have commented my code where necessary.
  • I have updated the documentation if needed.
  • My changes do not introduce new warnings.
  • I have added (where necessary) tests that prove my changes are effective or that my feature works.
  • New and existing tests pass with my changes.
  • I have disclosed my use of AI
    in the body of this PR.

Related Issues

STR-1724

@irnb irnb added the DO NOT MERGE This PR should not be merged as long as this tag is present. label Sep 18, 2025
Copy link
Contributor

github-actions bot commented Sep 18, 2025

Commit: 009c59a

SP1 Execution Results

program cycles success
EVM EE STF 1,173,084
CL STF 332,134
Checkpoint 2,422

Copy link

codecov bot commented Sep 18, 2025

Codecov Report

❌ Patch coverage is 0% with 215 lines in your changes missing coverage. Please review.
✅ Project coverage is 72.52%. Comparing base (5177fc3) to head (5a0144e).

Files with missing lines Patch % Lines
.../subprotocols/checkpointing-v0/src/verification.rs 0.00% 74 Missing ⚠️
...s/asm/subprotocols/checkpointing-v0/src/parsing.rs 0.00% 56 Missing ⚠️
...m/subprotocols/checkpointing-v0/src/subprotocol.rs 0.00% 56 Missing ⚠️
...tes/asm/subprotocols/checkpointing-v0/src/types.rs 0.00% 20 Missing ⚠️
crates/asm/spec/src/lib.rs 0.00% 4 Missing ⚠️
crates/state/src/chain_state.rs 0.00% 3 Missing ⚠️
crates/asm/spec-debug/src/lib.rs 0.00% 2 Missing ⚠️
@@            Coverage Diff             @@
##             main    #1039      +/-   ##
==========================================
- Coverage   72.98%   72.52%   -0.46%     
==========================================
  Files         450      454       +4     
  Lines       37452    37661     +209     
==========================================
- Hits        27334    27315      -19     
- Misses      10118    10346     +228     
Files with missing lines Coverage Δ
crates/asm/spec-debug/src/lib.rs 0.00% <0.00%> (ø)
crates/state/src/chain_state.rs 82.47% <0.00%> (-2.64%) ⬇️
crates/asm/spec/src/lib.rs 0.00% <0.00%> (ø)
...tes/asm/subprotocols/checkpointing-v0/src/types.rs 0.00% <0.00%> (ø)
...s/asm/subprotocols/checkpointing-v0/src/parsing.rs 0.00% <0.00%> (ø)
...m/subprotocols/checkpointing-v0/src/subprotocol.rs 0.00% <0.00%> (ø)
.../subprotocols/checkpointing-v0/src/verification.rs 0.00% <0.00%> (ø)

... and 12 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@irnb irnb force-pushed the STR-1706-checkpointing-v0-skeleton branch from b30f972 to 13a4dd2 Compare September 23, 2025 05:53
Comment on lines +3 to +4
// TODO: Update this crate so the module works with the ASM + SPS-50 tag flow, allowing it to drive
// ASM-focused tests.
Copy link
Contributor

Choose a reason for hiding this comment

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

For the TODO, let's create a ticket and add the link here.

Copy link
Contributor

@prajwolrg prajwolrg left a comment

Choose a reason for hiding this comment

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

Great work. I think even for feature parity, we can make the checkpoint verification flow better. Also for larger TODO, we can maybe create a ticket and link it in the code.

Comment on lines 5 to 22
/// Construct the SPS-50 tag for a checkpoint transaction.
///
/// Layout: `[magic][subprotocol_id][tx_type][aux]`. The auxiliary portion is currently empty for
/// checkpointing v0.
pub fn encode_checkpoint_tag(magic: MagicBytes) -> Vec<u8> {
let config = ParseConfig::new(magic);
let tag = TagDataRef::new(
CHECKPOINTING_V0_SUBPROTOCOL_ID,
OL_STF_CHECKPOINT_TX_TYPE,
&[],
)
.expect("checkpoint tag encoding");

config
.encode_tag_buf(&tag)
.expect("checkpoint tag encoding")
}
Copy link
Contributor

Choose a reason for hiding this comment

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

This should be outside of here. Maybe this can be moved into strata-l1tx instead. It can take that as the argument, or it might also makes sense in the strata-common repo.

Copy link
Contributor

Choose a reason for hiding this comment

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

agree here.

Comment on lines 26 to +27

/// Transactions in the block that contain protocol operations
relevant_txs: Vec<RelevantTxEntry>,
/// Transaction indexes in the block that contain SPS-50 tags
tagged_tx_indices: Vec<u32>,
Copy link
Contributor

Choose a reason for hiding this comment

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

This feels weird. We need to handle this in a better way. I imagine eventually the BlockData will be SPS-50 parsed transactions.

Since this is meant to be stepping stone, I think it's fine, but we should add a TODO with a new ticket.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think it's necessary as a field (BlockData is not persisted, unlike L1TxManifest).
tagged_tx_indices can be computed in-place (in generate_l1txs), generate_l1txs can be extented with ctx: &ReaderContext<R> (to fetch the magic bytes and construct the parser in place).

.unwrap(); // should be 33 bytes

let inp_txn = get_txn_from_utxo(utxo, &ctx.sequencer_address);
let header = vec![0xAAu8, 0xBB, 0xCC];
Copy link
Contributor

Choose a reason for hiding this comment

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

header is confusing. I think this should be envelope tag.

Comment on lines 38 to 53
#[derive(Clone, Debug)]
pub struct CheckpointV0VerificationParams {
/// Sequencer public key for signature verification
pub sequencer_pubkey: Buf32,
/// Whether to skip proof verification for testing (current system compatibility)
pub skip_proof_verification: bool,
/// Genesis L1 block commitment
pub genesis_l1_block: L1BlockCommitment,
/// Rollup verifying key for proof verification
/// Optional to support testing environments without proof verification
pub rollup_verifying_key: Option<strata_primitives::proof::RollupVerifyingKey>,
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Few points on this:

  1. It makes sense to put the sequencer_pubkey and rollup_verifying_key in the state as we want to support updating that even in v1. (is fine for v0).
  2. I think we can also make the rollup_verifying_key non-optional.
  3. Also related with (2). We can also remove skip_proof_verification. We already have Native in the RollupVerifyingKey enum where the proof verification is already skipped. We will most likely be replacing the verifying key with the predicate, so it should be fine to skip that as well.

Comment on lines 80 to 85
// TODO: Implement actual signature verification
// For now, accept all signatures for feature parity testing
// In real implementation, this would:
// 1. Extract signature and pubkey from signed checkpoint
// 2. Verify signature against checkpoint data
// 3. Check pubkey matches expected sequencer key
Copy link
Contributor

Choose a reason for hiding this comment

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

Even for feature parity, I think we are already checking the signature verification.

@evgenyzdanovich
Copy link
Contributor

Amazing work. waiting for it to be merged soon!

@prajwolrg prajwolrg force-pushed the STR-1706-checkpointing-v0-skeleton branch from f0688ed to b1536de Compare September 25, 2025 03:46
/// 3. Delegate to checkpoint verification logic
/// 4. Extract and forward withdrawal messages if checkpoint is accepted
///
/// NOTE: This bridges current checkpoint format with SPS-50 envelope parsing
Copy link
Contributor

Choose a reason for hiding this comment

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

What exactly do you mean by that? Does the current checkpoint structure follow SPS-50?

return Err(CheckpointV0Error::InvalidProof);
}

let is_empty_proof = proof_receipt.proof().is_empty();
Copy link
Contributor

Choose a reason for hiding this comment

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

I know this code will be changed, but still, we should make sure this code will not end up in production, so let's add a big TODO to remove this.

let withdrawal_intents = extract_withdrawal_messages(signed_checkpoint.checkpoint())
.map_err(|e| CheckpointV0Error::StateTransitionError(e.to_string()))?;

// Forward each withdrawal message to the bridge subprotocol
Copy link
Contributor

Choose a reason for hiding this comment

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

This is interesting! The OL already "knows" about this withdrawal intent, right? Is this needed then only for the bridge node itself (that eventually will also run ASM)?

Comment on lines 111 to 112
pub fn can_accept_epoch(&self, epoch: u64) -> bool {
if self.last_checkpoint.is_none() {
epoch == 0
} else {
epoch == self.current_verified_epoch + 1
}
}
}
Copy link
Contributor

@barakshani barakshani Sep 28, 2025

Choose a reason for hiding this comment

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

Can't this be replaced with checking if epoch == expected_next_epoch() you've just defined?

@irnb irnb changed the title feat(asm): checkpointing-v0 subprotocol Feat(btcio): SPS-50 compatibility in btcio Sep 29, 2025
@irnb irnb changed the title Feat(btcio): SPS-50 compatibility in btcio feat(btcio): sps-50 compatibility in btcio Sep 29, 2025
@irnb irnb force-pushed the STR-1706-checkpointing-v0-skeleton branch from a96db18 to 8e396c3 Compare October 4, 2025 12:49
Copy link
Member

@storopoli storopoli left a comment

Choose a reason for hiding this comment

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

Some code quality suggestions.

block_num: u64,

/// Raw block data.
// TODO remove?
Copy link
Member

Choose a reason for hiding this comment

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

Maybe remove this TODO comment? Since we're not storing the RelevantTxEntry anymore?

Comment on lines 93 to +111
payloads: &[L1Payload],
ctx: &WriterContext<R>,
) -> anyhow::Result<(Transaction, Transaction)> {
// SPS-50 envelopes currently carry a single protocol transaction, so btcio enforces
// exactly one payload until multi-payload envelopes are supported.
if payloads.len() != 1 {
return Err(EnvelopeError::InvalidPayloadCount(payloads.len()).into());
}

Copy link
Member

Choose a reason for hiding this comment

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

Why not take a stack allocated array then?
s/payloads: &[L1Payload]/payload: [L1Payload; 1]/
Or even a single &L1Payload?

use strata_primitives::{l1::payload::L1Payload, params::Params};
use strata_status::StatusChannel;

/// Callable that produces SPS-50 tag bytes for the reveal transaction OP_RETURN.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
/// Callable that produces SPS-50 tag bytes for the reveal transaction OP_RETURN.
/// Callable that produces SPS-50 tag bytes for the reveal transaction `OP_RETURN`.

Comment on lines +157 to +158
let op_return_push = PushBytesBuf::try_from(envelope_tag.to_vec())
.map_err(|_| EnvelopeError::InvalidEnvelopeTag)?;
Copy link
Member

Choose a reason for hiding this comment

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

I don't think you need to heap-allocate this into Vec.
PushBytes in rust-bitcoin has a TryFrom<&'a [u8]>


let op_return_push = PushBytesBuf::try_from(envelope_tag.to_vec())
.map_err(|_| EnvelopeError::InvalidEnvelopeTag)?;
let op_return_script = ScriptBuf::new_op_return(op_return_push.clone());
Copy link
Member

Choose a reason for hiding this comment

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

Also here I don't think you need to clone.
The new_op_return function signature is:

pub fn new_op_return<T: AsRef<PushBytes>>(data: T) -> Self

Comment on lines +417 to +421
let mut tag = Vec::with_capacity(6);
tag.extend_from_slice(&magic_bytes);
tag.push(CHECKPOINT_V0_SUBPROTOCOL_ID);
tag.push(OL_STF_CHECKPOINT_TX_TYPE);
Ok(tag)
Copy link
Member

Choose a reason for hiding this comment

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

Don't allocate.
We have make_buf! that is a macro with compile-time stack allocations instead. See as an example:

pub(crate) fn op_return_data(&self) -> [u8; 44] {
let deposit_txid_data = consensus::encode::serialize(&self.deposit_txid);
make_buf! {
(&self.tag, 4),
(&self.operator_idx.to_be_bytes(), 4),
(&self.deposit_idx.to_be_bytes(), 4),
(&deposit_txid_data, 32),
}
}
}

@prajwolrg prajwolrg force-pushed the STR-1706-checkpointing-v0-skeleton branch from 8e396c3 to a2011c2 Compare October 7, 2025 11:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
DO NOT MERGE This PR should not be merged as long as this tag is present.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants