Skip to content

Commit ffb966f

Browse files
authored
feat(backrun): set max backrun bundle tx limit and gas limit (#122)
1 parent 230544c commit ffb966f

File tree

2 files changed

+75
-11
lines changed

2 files changed

+75
-11
lines changed

crates/ingress-rpc/src/lib.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,14 @@ pub struct Config {
193193
#[arg(long, env = "TIPS_INGRESS_BACKRUN_ENABLED", default_value = "false")]
194194
pub backrun_enabled: bool,
195195

196+
/// Maximum number of transactions allowed in a backrun bundle (including target tx)
197+
#[arg(long, env = "MAX_BACKRUN_TXS", default_value = "5")]
198+
pub max_backrun_txs: usize,
199+
200+
/// Maximum total gas limit for all transactions in a backrun bundle
201+
#[arg(long, env = "MAX_BACKRUN_GAS_LIMIT", default_value = "5000000")]
202+
pub max_backrun_gas_limit: u64,
203+
196204
/// URL of third-party RPC endpoint to forward raw transactions to (enables forwarding if set)
197205
#[arg(long, env = "TIPS_INGRESS_RAW_TX_FORWARD_RPC")]
198206
pub raw_tx_forward_rpc: Option<Url>,

crates/ingress-rpc/src/service.rs

Lines changed: 67 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,8 @@ pub struct IngressService<Q: MessageQueue, M: Mempool> {
8282
builder_tx: broadcast::Sender<MeterBundleResponse>,
8383
backrun_enabled: bool,
8484
builder_backrun_tx: broadcast::Sender<AcceptedBundle>,
85+
max_backrun_txs: usize,
86+
max_backrun_gas_limit: u64,
8587
}
8688

8789
impl<Q: MessageQueue, M: Mempool> IngressService<Q, M> {
@@ -127,18 +129,40 @@ impl<Q: MessageQueue, M: Mempool> IngressService<Q, M> {
127129
builder_tx,
128130
backrun_enabled: config.backrun_enabled,
129131
builder_backrun_tx,
132+
max_backrun_txs: config.max_backrun_txs,
133+
max_backrun_gas_limit: config.max_backrun_gas_limit,
130134
}
131135
}
132136
}
133137

138+
fn validate_backrun_bundle_limits(
139+
txs_count: usize,
140+
total_gas_limit: u64,
141+
max_backrun_txs: usize,
142+
max_backrun_gas_limit: u64,
143+
) -> Result<(), String> {
144+
if txs_count < 2 {
145+
return Err(
146+
"Backrun bundle must have at least 2 transactions (target + backrun)".to_string(),
147+
);
148+
}
149+
if txs_count > max_backrun_txs {
150+
return Err(format!(
151+
"Backrun bundle exceeds max transaction count: {txs_count} > {max_backrun_txs}",
152+
));
153+
}
154+
if total_gas_limit > max_backrun_gas_limit {
155+
return Err(format!(
156+
"Backrun bundle exceeds max gas limit: {total_gas_limit} > {max_backrun_gas_limit}",
157+
));
158+
}
159+
Ok(())
160+
}
161+
134162
#[async_trait]
135163
impl<Q: MessageQueue + 'static, M: Mempool + 'static> IngressApiServer for IngressService<Q, M> {
136164
async fn send_backrun_bundle(&self, bundle: Bundle) -> RpcResult<BundleHash> {
137165
if !self.backrun_enabled {
138-
info!(
139-
message = "Backrun bundle submission is disabled",
140-
backrun_enabled = self.backrun_enabled
141-
);
142166
return Err(
143167
EthApiError::InvalidParams("Backrun bundle submission is disabled".into())
144168
.into_rpc_err(),
@@ -149,15 +173,23 @@ impl<Q: MessageQueue + 'static, M: Mempool + 'static> IngressApiServer for Ingre
149173
let (accepted_bundle, bundle_hash) =
150174
self.validate_parse_and_meter_bundle(&bundle, false).await?;
151175

176+
let total_gas_limit: u64 = accepted_bundle.txs.iter().map(|tx| tx.gas_limit()).sum();
177+
validate_backrun_bundle_limits(
178+
accepted_bundle.txs.len(),
179+
total_gas_limit,
180+
self.max_backrun_txs,
181+
self.max_backrun_gas_limit,
182+
)
183+
.map_err(|e| EthApiError::InvalidParams(e).into_rpc_err())?;
184+
152185
self.metrics.backrun_bundles_received_total.increment(1);
153186

154-
if let Err(e) = self.builder_backrun_tx.send(accepted_bundle.clone()) {
155-
warn!(
156-
message = "Failed to send backrun bundle to builders",
157-
bundle_hash = %bundle_hash,
158-
error = %e
159-
);
160-
}
187+
self.builder_backrun_tx
188+
.send(accepted_bundle.clone())
189+
.map_err(|e| {
190+
EthApiError::InvalidParams(format!("Failed to send backrun bundle: {e}"))
191+
.into_rpc_err()
192+
})?;
161193

162194
self.send_audit_event(&accepted_bundle, bundle_hash);
163195

@@ -578,6 +610,8 @@ mod tests {
578610
raw_tx_forward_rpc: None,
579611
chain_id: 11,
580612
user_operation_topic: String::new(),
613+
max_backrun_txs: 5,
614+
max_backrun_gas_limit: 5000000,
581615
}
582616
}
583617

@@ -854,4 +888,26 @@ mod tests {
854888

855889
assert!(wrong_user_op_result.is_err());
856890
}
891+
892+
#[test]
893+
fn test_validate_backrun_bundle_rejects_invalid() {
894+
// Too few transactions (need at least 2: target + backrun)
895+
let result = validate_backrun_bundle_limits(1, 21000, 5, 5000000);
896+
assert!(result.is_err());
897+
assert!(result.unwrap_err().contains("at least 2 transactions"));
898+
899+
// Exceeds max tx count
900+
let result = validate_backrun_bundle_limits(6, 21000, 5, 5000000);
901+
assert!(result.is_err());
902+
assert!(
903+
result
904+
.unwrap_err()
905+
.contains("exceeds max transaction count")
906+
);
907+
908+
// Exceeds max gas limit
909+
let result = validate_backrun_bundle_limits(2, 6000000, 5, 5000000);
910+
assert!(result.is_err());
911+
assert!(result.unwrap_err().contains("exceeds max gas limit"));
912+
}
857913
}

0 commit comments

Comments
 (0)