Skip to content
Draft
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
153 changes: 136 additions & 17 deletions crates/duty-tracker/src/predicates.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ use strata_bridge_tx_graph::transactions::{
use strata_l1tx::{envelope::parser::parse_envelope_payloads, filter::types::TxFilterConfig};
use strata_primitives::params::RollupParams;
use strata_state::batch::{verify_signed_checkpoint_sig, Checkpoint, SignedCheckpoint};
use tracing::warn;
use tracing::{debug, warn};

fn op_return_data(script: &Script) -> Option<&[u8]> {
let mut instructions = script.instructions();
Expand Down Expand Up @@ -51,27 +51,49 @@ pub(crate) fn deposit_request_info(
build_context: &impl BuildContext,
stake_index: u32,
) -> Option<DepositRequestData> {
let deposit_request_output = tx.output.first()?;
let Some(deposit_request_output) = tx.output.first() else {
debug!(txid=%tx.compute_txid(), "DRT missing output");
return None;
};

if deposit_request_output.value <= pegout_graph_params.deposit_amount {
debug!(txid=%tx.compute_txid(), "DRT output value too low, expected at least {} but got {}", pegout_graph_params.deposit_amount, deposit_request_output.value);
return None;
}

let ee_address_size = sidesystem_params.address_length as usize;
let tag = pegout_graph_params.tag.as_bytes();

let (recovery_x_only_pk, el_addr) = magic_tagged_data(tag, &tx.output.get(1)?.script_pubkey)
.and_then(|meta| {
if meta.len() != SCHNORR_PUBLIC_KEY_SIZE + ee_address_size {
return None;
}
let recovery_x_only_pk = meta.get(..SCHNORR_PUBLIC_KEY_SIZE)?;
// TODO: handle error variant and get rid of expect.
let recovery_x_only_pk = XOnlyPublicKey::from_slice(recovery_x_only_pk)
.expect("failed to parse XOnlyPublicKey");
let el_addr =
meta.get(SCHNORR_PUBLIC_KEY_SIZE..SCHNORR_PUBLIC_KEY_SIZE + ee_address_size)?;
Some((recovery_x_only_pk, el_addr))
})?;
let Some(metadata_output) = tx.output.get(1) else {
debug!(txid=%tx.compute_txid(), "DRT missing metadata output");
return None;
Comment on lines +67 to +69
Copy link
Member

Choose a reason for hiding this comment

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

Maybe move the 1 magic number to a const?

Copy link
Collaborator Author

@Rajil1213 Rajil1213 Jul 30, 2025

Choose a reason for hiding this comment

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

Trying to add this const made me realize that this logic does not belong in the predicates, it should be part of the tx-graph crate which contains the constructor for the deposit transaction. I'll either refactor this PR or create a new one. 😄

};

let Some(meta) = magic_tagged_data(tag, &metadata_output.script_pubkey) else {
debug!(txid=%tx.compute_txid(), "DRT missing tag");
return None;
};

if meta.len() != SCHNORR_PUBLIC_KEY_SIZE + ee_address_size {
debug!(txid=%tx.compute_txid(), "invalid DRT metadata size, expected {} but got {}", SCHNORR_PUBLIC_KEY_SIZE + ee_address_size, meta.len());
return None;
}

let Some(recovery_x_only_pk) = meta.get(..SCHNORR_PUBLIC_KEY_SIZE) else {
debug!(txid=%tx.compute_txid(), "DRT metadata missing recovery x-only public key");
return None;
};

// TODO: handle error variant and get rid of expect.
let recovery_x_only_pk =
XOnlyPublicKey::from_slice(recovery_x_only_pk).expect("failed to parse XOnlyPublicKey");

let Some(el_addr) =
meta.get(SCHNORR_PUBLIC_KEY_SIZE..SCHNORR_PUBLIC_KEY_SIZE + ee_address_size)
else {
debug!(txid=%tx.compute_txid(), "DRT metadata missing execution environment address");
return None;
};

let deposit_request_data = DepositRequestData::new(
OutPoint::new(tx.compute_txid(), 0),
Expand Down Expand Up @@ -216,17 +238,24 @@ pub(crate) fn parse_strata_checkpoint(

#[cfg(test)]
mod tests {
use std::{collections::BTreeMap, str::FromStr};

use alpen_bridge_params::prelude::PegOutGraphParams;
use bitcoin::{Amount, Block, OutPoint, ScriptBuf, TxOut};
use bitcoin::{
consensus, Amount, Block, Network, OutPoint, ScriptBuf, Transaction, TxOut, XOnlyPublicKey,
};
use bitcoin_bosd::Descriptor;
use secp256k1::Parity;
use strata_bridge_common::logging::{self, LoggerConfig};
use strata_bridge_primitives::build_context::TxBuildContext;
use strata_bridge_test_utils::prelude::{generate_txid, generate_xonly_pubkey};
use strata_bridge_tx_graph::transactions::prelude::{
WithdrawalFulfillment, WithdrawalMetadata,
};
use strata_primitives::params::RollupParams;

use super::parse_strata_checkpoint;
use crate::predicates::is_fulfillment_tx;
use crate::predicates::{deposit_request_info, is_fulfillment_tx};

#[test]
fn test_fulfillment_predicate() {
Expand Down Expand Up @@ -308,4 +337,94 @@ mod tests {
"must be able to parse valid strata checkpoint tx"
);
}

#[test]
fn test_deposit_request_predicate() {
logging::init(LoggerConfig::new(
"test_deposit_request_predicate".to_string(),
));

let bridge_keys = [
"73441f2ba801b557b23c15829f4a87c02332d59a71499da1479048e6175ff4e0",
"6bc16ede3b4b30edd4b59ab3a7209de21b468508349983e17a08910ec7a82f5f",
"2bc0e2a6dd1c80beefa363d8baf980101d0596d914d4ee2d73e2fcaff2e72dc6",
]
.iter()
.enumerate()
.map(|(idx, key)| {
(
idx as u32,
XOnlyPublicKey::from_str(key)
.unwrap()
.public_key(Parity::Even),
)
})
.collect::<BTreeMap<_, _>>();

let maybe_drt = "\
02000000000101920a2ecd7eaa0adebb4b585552d67d7d26a7ebd7f8b43203852b041d8c2a2e910100000000fdffffff03e8\
cd9a3b00000000225120219796da38be41571aff1b3ba3f950dff6d0b4666e1607821fbd1a177d57ee800000000000000000\
3a6a38616c706e3a320d77dd6835636fb8435117eea3887e152751ee5f5fc7b87f68496e5ae44ee29f15a6b0d2e9450f392f\
88293b9fc0a5c5e1a77a3d0f0000000000225120c3464fa3e6075a23102b51ba94d784371e6ff9b1d94fcf30b4e15efbaeb8\
edff0140d5b50e414b9cf2fc7115e06e2f9da88286f8e944638b640bdcae6690a2fbef905f39ac86c28ae0a6b3e6f2a296e7\
43f4a257824f81ec1bfefe827633958b523a16090000\
";
let maybe_drt: Transaction = consensus::encode::deserialize_hex(maybe_drt).unwrap();

let sidesystem_params =
std::fs::read_to_string("../../test-data/rollup_params.json").unwrap();
let sidesystem_params: RollupParams = serde_json::from_str(&sidesystem_params).unwrap();
let pegout_graph_params = PegOutGraphParams::default();
let build_context = TxBuildContext::new(Network::Signet, bridge_keys.into(), 0);
let random_stake_index = 10;

let Some(deposit_request_data) = deposit_request_info(
&maybe_drt,
&sidesystem_params,
&pegout_graph_params,
&build_context,
random_stake_index,
) else {
panic!("failed to parse deposit request data");
};

assert_eq!(
deposit_request_data.deposit_request_outpoint().txid,
maybe_drt.compute_txid(),
"deposit request txid must match"
);

assert_eq!(
deposit_request_data.stake_index(),
random_stake_index,
"stake index must match"
);

let expected_el_address = vec![
0xe2, 0x9f, 0x15, 0xa6, 0xb0, 0xd2, 0xe9, 0x45, 0xf, 0x39, 0x2f, 0x88, 0x29, 0x3b,
0x9f, 0xc0, 0xa5, 0xc5, 0xe1, 0xa7,
];
assert_eq!(
deposit_request_data.el_address(),
expected_el_address,
"execution environment address must match"
);

let expected_output_amount = Amount::from_sat(1_000_001_000);
assert_eq!(
*deposit_request_data.total_amount(),
expected_output_amount,
"deposit amount must match"
);

let expected_recovery_x_only_pk = XOnlyPublicKey::from_str(
"3a320d77dd6835636fb8435117eea3887e152751ee5f5fc7b87f68496e5ae44e",
)
.unwrap();
assert_eq!(
*deposit_request_data.x_only_public_key(),
expected_recovery_x_only_pk,
"recovery x-only public key must match"
);
}
}
Loading