Skip to content
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

l1-filter: Add TxFilterConfig #458

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
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
12 changes: 3 additions & 9 deletions crates/btcio/src/reader/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ use strata_state::l1::{
};
use strata_status::StatusTx;
use strata_tx_parser::{
filter::{derive_tx_filter_rules, filter_relevant_txs, TxFilterRule},
filter::{filter_protocol_op_tx_refs, TxFilterConfig},
messages::{BlockData, L1Event},
};
use tokio::sync::mpsc;
Expand Down Expand Up @@ -70,13 +70,9 @@ async fn do_reader_task(
let cur_best_height = state.best_block_idx();
let poll_span = debug_span!("l1poll", %cur_best_height);

// Maybe this should be called outside loop?
let filters = derive_tx_filter_rules(config.params.rollup())?;

if let Err(err) = poll_for_new_blocks(
client,
event_tx,
&filters,
&mut state,
&mut status_updates,
config.params.as_ref(),
Expand Down Expand Up @@ -148,7 +144,6 @@ async fn init_reader_state(
async fn poll_for_new_blocks(
client: &impl Reader,
event_tx: &mpsc::Sender<L1Event>,
filters: &[TxFilterRule],
state: &mut ReaderState,
status_updates: &mut Vec<L1StatusUpdate>,
params: &Params,
Expand Down Expand Up @@ -193,7 +188,6 @@ async fn poll_for_new_blocks(
event_tx,
state,
status_updates,
filters,
params,
)
.await
Expand Down Expand Up @@ -238,13 +232,13 @@ async fn fetch_and_process_block(
event_tx: &mpsc::Sender<L1Event>,
state: &mut ReaderState,
status_updates: &mut Vec<L1StatusUpdate>,
filters: &[TxFilterRule],
params: &Params,
) -> anyhow::Result<BlockHash> {
let block = client.get_block_at(height).await?;
let txs = block.txdata.len();

let filtered_txs = filter_relevant_txs(&block, filters);
let filter_config = TxFilterConfig::from_rollup_params(params.rollup())?;
let filtered_txs = filter_protocol_op_tx_refs(&block, filter_config);
let block_data = BlockData::new(height, block, filtered_txs);
let l1blkid = block_data.block().block_hash();
trace!(%height, %l1blkid, %txs, "fetched block from client");
Expand Down
7 changes: 4 additions & 3 deletions crates/proof-impl/btc-blockspace/src/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,19 @@ use strata_state::{
batch::BatchCheckpoint,
tx::{DepositInfo, ProtocolOperation},
};
use strata_tx_parser::filter::{derive_tx_filter_rules, filter_relevant_txs};
use strata_tx_parser::filter::{filter_protocol_op_tx_refs, TxFilterConfig};

pub fn extract_relevant_info(
block: &Block,
rollup_params: &RollupParams,
) -> (Vec<DepositInfo>, Option<BatchCheckpoint>) {
let filters = derive_tx_filter_rules(rollup_params).expect("derive tx-filter rules");
let filter_config =
TxFilterConfig::from_rollup_params(rollup_params).expect("derive tx-filter config");

let mut deposits = Vec::new();
let mut prev_checkpoint = None;

let relevant_txs = filter_relevant_txs(block, &filters);
let relevant_txs = filter_protocol_op_tx_refs(block, filter_config);

for tx in relevant_txs {
match tx.proto_op() {
Expand Down
131 changes: 101 additions & 30 deletions crates/tx-parser/src/filter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
use borsh::{BorshDeserialize, BorshSerialize};
use strata_primitives::{
buf::Buf32,
l1::BitcoinAddress,
params::{OperatorConfig, RollupParams},
prelude::DepositTxParams,
};
Expand All @@ -24,6 +25,77 @@
DepositRequest(DepositTxParams),
/// Deposit transaction with deposit config and address
Deposit(DepositTxParams),
/// Addresses that are spent to
SpentToAddrs(Vec<BitcoinAddress>),
/// Blob ids that are expected
BlobIds(Vec<Buf32>),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

These vecs can be sorted and then we can do binary search.
Having Vec instead of Individual items lets us group the items, sort and do binary search as we want.

Copy link
Contributor

Choose a reason for hiding this comment

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

It doesn't make sense to have a list of rules where there there's multiple entries of the same variant. And in most cases we expect that all variants will end up being present exactly once. The ticket specified using a struct for this instead of a list of enum instances, and we'd do the filtering based on that directly.

/// Outpoints
Outpoints(Vec<(Buf32, u32)>),
}

#[derive(Clone, Debug)]
pub struct TxFilterConfig {
/// For checkpoint update inscriptions.
rollup_name: RollupName,

/// For addresses that we expect spends to.
expected_addrs: Vec<BitcoinAddress>,

/// For blobs we expect to be written.
expected_blobs: Vec<Buf32>,

/// For deposits that might be spent from.
expected_outpoints: Vec<(Buf32, u32)>,
Comment on lines +47 to +48
Copy link
Contributor

Choose a reason for hiding this comment

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

Should use a dedicated type for this.

// eventually, in future version
// For bridge V1 deposits that do bitmap flagging for the multisig addr.
// operator_key_tbl: PublickeyTable,
/// EE addr length
ee_addr_len: u8,
Copy link
Contributor

Choose a reason for hiding this comment

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

Should be max length.


/// Deposit denomination
deposit_denomination: u64, // sats
Comment on lines +55 to +56
Copy link
Contributor

Choose a reason for hiding this comment

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

Don't bake this in as a single value, it should be capable of being extended to support multiple denominations easily even if we don't support this today.


/// Operators addr
operator_addr: BitcoinAddress,
Comment on lines +58 to +59
Copy link
Contributor

Choose a reason for hiding this comment

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

Possibly redundant, this would be part of expected_addrs.

}

impl TxFilterConfig {
pub fn from_rollup_params(rollup_params: &RollupParams) -> anyhow::Result<Self> {
let operator_wallet_pks = get_operator_wallet_pks(rollup_params);
let address = generate_taproot_address(&operator_wallet_pks, rollup_params.network)?;

Comment on lines +63 to +66
Copy link
Contributor

Choose a reason for hiding this comment

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

We will be be computing the rules based on the last checkpoint's bridge state and the rollup params. I'm not sure this function should be on this type.

let rollup_name = rollup_params.rollup_name.clone();
let expected_blobs = Vec::new(); // TODO: this should come from chainstate
let expected_addrs = vec![address.clone()];
let expected_outpoints = Vec::new();

Ok(Self {
rollup_name,
expected_blobs,
expected_addrs,
expected_outpoints,
ee_addr_len: rollup_params.address_length,
deposit_denomination: rollup_params.deposit_amount,
operator_addr: address,
})
}

pub fn into_rules(self) -> Vec<TxFilterRule> {
let deposit_params = DepositTxParams {
magic_bytes: self.rollup_name.clone().into_bytes().to_vec(),
address_length: self.ee_addr_len,
deposit_amount: self.deposit_denomination,
address: self.operator_addr,
};
vec![
TxFilterRule::RollupInscription(self.rollup_name.clone()),
TxFilterRule::SpentToAddrs(self.expected_addrs),
TxFilterRule::BlobIds(self.expected_blobs),
TxFilterRule::Outpoints(self.expected_outpoints),
TxFilterRule::Deposit(deposit_params.clone()),
TxFilterRule::DepositRequest(deposit_params),
]
}
}

type RollupName = String;
Expand All @@ -47,46 +119,45 @@
TxFilterRule::Deposit(deposit_provider),
])
}
/// Filter all the relevant [`Transaction`]s in a block based on given [`TxFilterRule`]s
pub fn filter_relevant_txs(block: &Block, filters: &[TxFilterRule]) -> Vec<ProtocolOpTxRef> {

/// Filter protocol operatios as refs from relevant [`Transaction`]s in a block based on given

Check failure on line 123 in crates/tx-parser/src/filter.rs

View workflow job for this annotation

GitHub Actions / Check code spellings

operatios ==> operations
/// [`TxFilterRule`]s
pub fn filter_protocol_op_tx_refs(
block: &Block,
filter_config: TxFilterConfig,
) -> Vec<ProtocolOpTxRef> {
let filter_rules = filter_config.into_rules();
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The config is converted to existing rules with added variants. We can add complex logic here if needed. But this should probably suffice.

block
.txdata
.iter()
.enumerate()
.filter_map(|(i, tx)| {
check_and_extract_relevant_info(tx, filters)
extract_protocol_op(tx, &filter_rules)
.map(|relevant_tx| ProtocolOpTxRef::new(i as u32, relevant_tx))
})
.collect()
}

/// if a [`Transaction`] is relevant based on given [`RelevantTxType`]s then we extract relevant
/// info
fn check_and_extract_relevant_info(
tx: &Transaction,
filters: &[TxFilterRule],
) -> Option<ProtocolOperation> {
fn extract_protocol_op(tx: &Transaction, filters: &[TxFilterRule]) -> Option<ProtocolOperation> {
Copy link
Contributor

Choose a reason for hiding this comment

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

Rework this to operate with the config directly.

filters.iter().find_map(|rel_type| match rel_type {
TxFilterRule::RollupInscription(name) => {
if !tx.input.is_empty() {
if let Some(scr) = tx.input[0].witness.tapscript() {
if let Ok(inscription_data) = parse_inscription_data(&scr.into(), name) {
if let Ok(signed_batch) = borsh::from_slice::<SignedBatchCheckpoint>(
inscription_data.batch_data(),
) {
return Some(ProtocolOperation::RollupInscription(signed_batch));
}
}
}
}
None
}
TxFilterRule::RollupInscription(name) => tx.input.first().and_then(|inp| {
inp.witness
.tapscript()
.and_then(|scr| parse_inscription_data(&scr.into(), name).ok())
.and_then(|data| borsh::from_slice::<SignedBatchCheckpoint>(data.batch_data()).ok())
.map(ProtocolOperation::RollupInscription)
}),

TxFilterRule::DepositRequest(config) => extract_deposit_request_info(tx, config)
.map(|deposit_req_info| Some(ProtocolOperation::DepositRequest(deposit_req_info)))?,

TxFilterRule::Deposit(config) => extract_deposit_info(tx, config)
.map(|deposit_info| Some(ProtocolOperation::Deposit(deposit_info)))?,

// TODO: add others
_ => None,
})
}

Expand Down Expand Up @@ -121,7 +192,7 @@
build_test_deposit_request_script, build_test_deposit_script,
create_transaction_two_outpoints, get_deposit_tx_config, test_taproot_addr,
},
filter::{filter_relevant_txs, TxFilterRule},
filter::{filter_protocol_op_tx_refs, TxFilterRule},
};

const OTHER_ADDR: &str = "bcrt1q6u6qyya3sryhh42lahtnz2m7zuufe7dlt8j0j5";
Expand Down Expand Up @@ -206,7 +277,7 @@
let block = create_test_block(vec![tx]);

let txids: Vec<u32> =
filter_relevant_txs(&block, &[TxFilterRule::RollupInscription(rollup_name)])
filter_protocol_op_tx_refs(&block, &[TxFilterRule::RollupInscription(rollup_name)])
.iter()
.map(|op_refs| op_refs.index())
.collect();
Expand All @@ -217,7 +288,7 @@
let rollup_name = "TestRollup".to_string();
let tx = create_inscription_tx(rollup_name.clone());
let block = create_test_block(vec![tx]);
let result = filter_relevant_txs(
let result = filter_protocol_op_tx_refs(
&block,
&[TxFilterRule::RollupInscription("invalid_name".to_string())],
);
Expand All @@ -232,7 +303,7 @@
let rollup_name = "alpenstrata".to_string();

let txids: Vec<u32> =
filter_relevant_txs(&block, &[TxFilterRule::RollupInscription(rollup_name)])
filter_protocol_op_tx_refs(&block, &[TxFilterRule::RollupInscription(rollup_name)])
.iter()
.map(|op_refs| op_refs.index())
.collect();
Expand All @@ -248,7 +319,7 @@
let block = create_test_block(vec![tx1, tx2, tx3]);

let txids: Vec<u32> =
filter_relevant_txs(&block, &[TxFilterRule::RollupInscription(rollup_name)])
filter_protocol_op_tx_refs(&block, &[TxFilterRule::RollupInscription(rollup_name)])
.iter()
.map(|op_refs| op_refs.index())
.collect();
Expand All @@ -272,7 +343,7 @@
let block = create_test_block(vec![tx]);

let filters = vec![TxFilterRule::Deposit(config.clone())];
let result = filter_relevant_txs(&block, &filters);
let result = filter_protocol_op_tx_refs(&block, &filters);

assert_eq!(result.len(), 1, "Should find one relevant transaction");
assert_eq!(
Expand Down Expand Up @@ -315,7 +386,7 @@
let block = create_test_block(vec![tx]);

let relevant_types = vec![TxFilterRule::DepositRequest(config.clone())];
let result = filter_relevant_txs(&block, &relevant_types);
let result = filter_protocol_op_tx_refs(&block, &relevant_types);

assert_eq!(result.len(), 1, "Should find one relevant transaction");
assert_eq!(
Expand Down Expand Up @@ -350,7 +421,7 @@
let block = create_test_block(vec![irrelevant_tx]);

let relevant_types = vec![TxFilterRule::Deposit(config)];
let result = filter_relevant_txs(&block, &relevant_types);
let result = filter_protocol_op_tx_refs(&block, &relevant_types);

assert!(
result.is_empty(),
Expand Down Expand Up @@ -383,7 +454,7 @@
let block = create_test_block(vec![tx1, tx2]);

let relevant_types = vec![TxFilterRule::Deposit(config.clone())];
let result = filter_relevant_txs(&block, &relevant_types);
let result = filter_protocol_op_tx_refs(&block, &relevant_types);

assert_eq!(result.len(), 2, "Should find two relevant transactions");
assert_eq!(
Expand Down
Loading