-
Notifications
You must be signed in to change notification settings - Fork 20
feat(btcio): sps-50 compatibility in btcio #1039
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Commit: 009c59a SP1 Execution Results
|
b30f972
to
13a4dd2
Compare
// TODO: Update this crate so the module works with the ASM + SPS-50 tag flow, allowing it to drive | ||
// ASM-focused tests. |
There was a problem hiding this comment.
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.
There was a problem hiding this 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.
/// 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") | ||
} |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
agree here.
|
||
/// 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>, |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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]; |
There was a problem hiding this comment.
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.
#[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>, | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Few points on this:
- It makes sense to put the
sequencer_pubkey
androllup_verifying_key
in the state as we want to support updating that even in v1. (is fine for v0). - I think we can also make the
rollup_verifying_key
non-optional. - Also related with (2). We can also remove
skip_proof_verification
. We already have Native in theRollupVerifyingKey
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.
// 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 |
There was a problem hiding this comment.
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.
Amazing work. waiting for it to be merged soon! |
f0688ed
to
b1536de
Compare
/// 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 |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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)?
pub fn can_accept_epoch(&self, epoch: u64) -> bool { | ||
if self.last_checkpoint.is_none() { | ||
epoch == 0 | ||
} else { | ||
epoch == self.current_verified_epoch + 1 | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
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?
a96db18
to
8e396c3
Compare
There was a problem hiding this 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? |
There was a problem hiding this comment.
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?
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()); | ||
} | ||
|
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
/// 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`. |
let op_return_push = PushBytesBuf::try_from(envelope_tag.to_vec()) | ||
.map_err(|_| EnvelopeError::InvalidEnvelopeTag)?; |
There was a problem hiding this comment.
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()); |
There was a problem hiding this comment.
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
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) |
There was a problem hiding this comment.
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:
alpen/crates/util/python-utils/src/bridge/types.rs
Lines 41 to 50 in 8e396c3
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), | |
} | |
} | |
} |
8e396c3
to
a2011c2
Compare
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 thebtcio
reader, and implementscheckpointing-v0
.Type of Change
Notes to Reviewers
Checklist
in the body of this PR.
Related Issues
STR-1724