1use alloy::{
2 eips::eip2718::Encodable2718,
3 network::{EthereumWallet, Network, TransactionBuilder},
4 primitives::{keccak256, FixedBytes, U256},
5 providers::{
6 fillers::{FillerControlFlow, TxFiller},
7 Provider, SendableTx,
8 },
9 rpc::types::{TransactionInput, TransactionRequest},
10 transports::TransportResult,
11 uint,
12};
13use eyre::Result;
14use futures::{future, future::select_all};
15use std::thread::available_parallelism;
16use tokio::{select, sync::broadcast};
17use tracing::{error, info, subscriber::set_global_default};
18use tracing_subscriber::EnvFilter;
19
20pub static ONE_ETHER: U256 = uint!(1_000_000_000_000_000_000_U256);
21pub static GWEI: U256 = uint!(1_000_000_000_U256);
22pub static GWEI_I: u128 = 1_000_000_000;
23
24#[derive(Clone, Debug)]
25pub struct DeadbeefFiller {
26 wallet: EthereumWallet,
27 prefix: String,
28 iteration_mode: IterationMode,
29}
30
31#[derive(Clone, Debug)]
32pub enum IterationMode {
33 Value,
34 Gas,
35}
36
37impl DeadbeefFiller {
38 pub fn new(prefix: String, wallet: EthereumWallet) -> Result<Self, &'static str> {
39 if prefix.is_empty() {
40 return Err("Prefix cannot be empty");
41 }
42
43 if !prefix.chars().all(|c| c.is_ascii_hexdigit()) {
44 return Err("Prefix contains non-hexadecimal characters");
45 }
46 let iteration_mode = if prefix.len() <= 4 {
47 IterationMode::Gas
48 } else {
49 IterationMode::Value
50 };
51
52 Ok(Self {
53 wallet,
54 prefix,
55 iteration_mode,
56 })
57 }
58}
59
60#[derive(Debug)]
61pub enum TxFillable {
62 Value { value: U256 },
63 Gas { gas: u64 },
64}
65
66impl<N: Network> TxFiller<N> for DeadbeefFiller {
67 type Fillable = TxFillable;
68
69 fn status(&self, _tx: &<N as Network>::TransactionRequest) -> FillerControlFlow {
70 FillerControlFlow::Ready
71 }
72 fn fill_sync(&self, _tx: &mut SendableTx<N>) {}
73
74 async fn fill(
75 &self,
76 fillable: Self::Fillable,
77 mut tx: SendableTx<N>,
78 ) -> TransportResult<SendableTx<N>> {
79 if let Some(builder) = tx.as_mut_builder() {
80 match fillable {
81 TxFillable::Value { value } => builder.set_value(value),
82 TxFillable::Gas { gas } => builder.set_gas_limit(gas),
83 }
84 }
85
86 Ok(tx)
87 }
88
89 async fn prepare<P>(
90 &self,
91 _provider: &P,
92 tx: &<N as Network>::TransactionRequest,
93 ) -> TransportResult<Self::Fillable>
94 where
95 P: Provider<N>,
96 {
97 let input = TransactionInput::new(tx.input().unwrap_or_default().clone());
98 let rpc_tx = TransactionRequest {
99 from: tx.from(),
100 to: Some(tx.to().into()),
101 value: tx.value(),
102 chain_id: tx.chain_id(),
103 input,
104 nonce: tx.nonce(),
105 max_fee_per_gas: tx.max_fee_per_gas(),
106 max_priority_fee_per_gas: tx.max_priority_fee_per_gas(),
107 gas: tx.gas_limit(),
108 access_list: tx.access_list().cloned(),
109 gas_price: tx.gas_price(),
110 ..Default::default()
111 };
112
113 let fillable = self.prefixed_tx_fillable(rpc_tx).await;
114
115 Ok(fillable.unwrap())
116 }
117}
118
119impl DeadbeefFiller {
120 pub fn set_iteration_mode(&mut self, mode: IterationMode) {
121 self.iteration_mode = mode;
122 }
123
124 pub async fn prefixed_tx(&self, tx: TransactionRequest) -> Result<TransactionRequest> {
125 let mut src_tx = tx.clone();
126
127 let fillable = self.prefixed_tx_fillable(tx).await?;
128
129 match fillable {
130 TxFillable::Value { value } => {
131 src_tx.value = Some(value);
132 }
133 TxFillable::Gas { gas } => {
134 src_tx.gas = Some(gas);
135 }
136 }
137
138 Ok(src_tx)
139 }
140
141 async fn prefixed_tx_fillable(&self, tx: TransactionRequest) -> Result<TxFillable> {
142 init_logs();
143 let max_cores = available_parallelism().unwrap().get() as u128;
144 let field = match self.iteration_mode {
145 IterationMode::Value => "value",
146 IterationMode::Gas => "gas",
147 };
148 info!(
149 "Looking for '0x{}' tx hash prefix using {max_cores} CPU cores, iterating on '{field}'",
150 self.prefix
151 );
152 let max_value = max_iterations_for_prefix(self.prefix.len() as u32) * 2; let mut handles = vec![];
154 let (done, _) = broadcast::channel(1);
155 for i in 0..max_cores {
156 let tx = tx.clone();
157 let prefix = self.prefix.clone();
158 let wallet = self.wallet.clone();
159 let mut done = done.subscribe();
160
161 let max_iters = max_value / max_cores;
162 let iteration_mode = self.iteration_mode.clone();
163
164 let handle = tokio::spawn(async move {
165 match iteration_mode {
166 IterationMode::Value => {
167 let value = value_for_prefix(
168 tx,
169 wallet,
170 &mut done,
171 i * max_iters,
172 prefix,
173 max_cores,
174 )
175 .await;
176 match value {
177 Ok(Some(value)) => Some(TxFillable::Value { value }),
178 Err(e) => {
179 error!("Error: {:?}", e);
180 None
181 }
182 _ => None,
183 }
184 }
185 IterationMode::Gas => {
186 let gas = gas_for_prefix(
187 tx,
188 wallet,
189 &mut done,
190 (i * max_iters) as u64,
191 prefix,
192 max_cores,
193 )
194 .await;
195 match gas {
196 Ok(Some(gas)) => Some(TxFillable::Gas { gas }),
197 Err(e) => {
198 error!("Error: {:?}", e);
199 None
200 }
201 _ => None,
202 }
203 }
204 }
205 });
206 handles.push(handle);
207 }
208
209 let (fillable, _index, _remaining) = select_all(handles).await;
210
211 let _ = done.send(());
212 Ok(fillable.unwrap().unwrap())
213 }
214}
215
216fn init_logs() {
217 let subscriber = tracing_subscriber::fmt()
218 .with_env_filter(EnvFilter::from_default_env())
219 .finish();
220 _ = set_global_default(subscriber);
221}
222
223async fn value_for_prefix(
224 tx: TransactionRequest,
225 wallet: EthereumWallet,
226 done: &mut tokio::sync::broadcast::Receiver<()>,
227 starting_input: u128,
228 prefix: String,
229 max_cores: u128,
230) -> Result<Option<U256>> {
231 let mut value = starting_input;
232 let mut buf = Vec::with_capacity(200);
233
234 let result: Option<U256> = loop {
235 select! {
236 biased;
237 _ = done.recv() => {
238 break None;
239 }
240 _ = future::ready(()) => {
241 let tx = tx.clone();
242 let next_value = tx.value.unwrap_or_default() + U256::from(value);
243 let tx_hash = tx_hash_for_value(tx, &wallet, next_value, &mut buf).await?;
244 value += 1;
245
246 let hash_str = format!("{:x}", &tx_hash);
247 if hash_str.starts_with(&prefix) {
248 let iters = value - starting_input;
249 let total_iters = max_cores * iters;
250 info!("Found matching tx hash: {tx_hash} after ~{total_iters} iterations");
251 break Some(next_value);
252 }
253 }
254 }
255 };
256
257 Ok(result)
258}
259
260async fn gas_for_prefix(
261 tx: TransactionRequest,
262 wallet: EthereumWallet,
263 done: &mut tokio::sync::broadcast::Receiver<()>,
264 starting_input: u64,
265 prefix: String,
266 max_cores: u128,
267) -> Result<Option<u64>> {
268 let mut value = starting_input;
269 let mut buf = Vec::with_capacity(200);
270
271 let result: Option<u64> = loop {
272 select! {
273 biased;
274 _ = done.recv() => {
275 break None;
276 }
277 _ = futures::future::ready(()) => {
278 let tx = tx.clone();
279 let next_value = tx.gas.unwrap_or_default() + value;
280 let tx_hash = tx_hash_for_gas(tx, &wallet, next_value, &mut buf).await?;
281
282 let hash_str = format!("{:x}", &tx_hash);
283
284 value += 1;
285
286 if hash_str.starts_with(&prefix) {
287 let iters = (value - starting_input) as u128;
288 let total_iters = max_cores * iters;
289 info!("Found matching tx hash: {tx_hash} after ~{total_iters} iterations");
290 break Some(next_value);
291 }
292 }
293 }
294 };
295
296 Ok(result)
297}
298
299async fn tx_hash_for_value(
300 tx: TransactionRequest,
301 wallet: &EthereumWallet,
302 value: U256,
303 buf: &mut Vec<u8>,
304) -> Result<FixedBytes<32>> {
305 let mut tx = tx;
306 buf.clear();
307 tx.value = Some(value);
308 let tx_envelope = tx.build(&wallet).await?;
309 tx_envelope.encode_2718(buf);
310 let tx_hash = keccak256(&buf);
311 Ok(tx_hash)
312}
313
314async fn tx_hash_for_gas(
315 tx: TransactionRequest,
316 wallet: &EthereumWallet,
317 gas: u64,
318 buf: &mut Vec<u8>,
319) -> Result<FixedBytes<32>> {
320 let mut tx = tx;
321 buf.clear();
322 tx.gas = Some(gas);
323 let tx_envelope = tx.build(&wallet).await?;
324 tx_envelope.encode_2718(buf);
325 let tx_hash = keccak256(&buf);
326 Ok(tx_hash)
327}
328
329fn max_iterations_for_prefix(prefix_length: u32) -> u128 {
330 let q = 1.0 / 16f64.powi(prefix_length as i32);
331 let max_iterations = (4.605 / q).ceil();
332 max_iterations as u128
333}
334
335#[cfg(test)]
336mod tests {
337 use super::*;
338 use alloy::{
339 network::EthereumWallet,
340 node_bindings::Anvil,
341 primitives::{Bytes, U256},
342 providers::{Provider, ProviderBuilder},
343 rpc::types::TransactionRequest,
344 signers::local::PrivateKeySigner,
345 };
346
347 const TX_PREFIX: &str = "de";
348
349 #[tokio::test(flavor = "multi_thread")]
350 async fn test_prefixed_tx_by_value() -> Result<()> {
351 let anvil = Anvil::new().spawn();
352 let account = anvil.addresses()[0];
353 let private_key = anvil.keys()[0].clone();
354 let wallet = EthereumWallet::from(PrivateKeySigner::from(private_key));
355
356 let mut deadbeef = DeadbeefFiller::new(TX_PREFIX.to_string(), wallet.clone()).unwrap();
357 deadbeef.set_iteration_mode(IterationMode::Value);
358
359 let anvil_provider = ProviderBuilder::new()
360 .filler(deadbeef)
361 .wallet(wallet.clone())
362 .on_http(anvil.endpoint().parse()?);
363
364 let chain_id = anvil_provider.get_chain_id().await?;
365 let nonce = anvil_provider.get_transaction_count(account).await?;
366 let gas_price = anvil_provider.get_gas_price().await?;
367
368 let tx = TransactionRequest {
369 from: Some(account),
370 to: Some(account.into()),
371 value: Some(U256::ZERO),
372 chain_id: Some(chain_id),
373 nonce: Some(nonce),
374 max_fee_per_gas: Some(gas_price * 110 / 100),
375 max_priority_fee_per_gas: Some(GWEI_I),
376 gas: Some(210000),
377 ..Default::default()
378 };
379
380 let res = anvil_provider
381 .send_transaction(tx)
382 .await?
383 .get_receipt()
384 .await?;
385
386 let tx_hash = res.transaction_hash;
387 let tx_hash = format!("{:x}", &tx_hash);
388 let tx_prefix = &tx_hash[..TX_PREFIX.len()];
389
390 assert_eq!(tx_prefix.as_bytes(), TX_PREFIX.as_bytes());
391
392 Ok(())
393 }
394
395 #[tokio::test(flavor = "multi_thread")]
396 async fn test_prefixed_tx_by_gas() -> Result<()> {
397 let anvil = Anvil::new().spawn();
398 let account = anvil.addresses()[0];
399 let private_key = anvil.keys()[0].clone();
400 let wallet = EthereumWallet::from(PrivateKeySigner::from(private_key));
401
402 let mut deadbeef = DeadbeefFiller::new(TX_PREFIX.to_string(), wallet.clone()).unwrap();
403 deadbeef.set_iteration_mode(IterationMode::Gas);
404
405 let anvil_provider = ProviderBuilder::new()
406 .filler(deadbeef)
407 .wallet(wallet.clone())
408 .on_http(anvil.endpoint().parse()?);
409
410 let chain_id = anvil_provider.get_chain_id().await?;
411 let nonce = anvil_provider.get_transaction_count(account).await?;
412 let gas_price = anvil_provider.get_gas_price().await?;
413
414 let tx = TransactionRequest {
415 from: Some(account),
416 to: Some(account.into()),
417 value: Some(U256::ZERO),
418 chain_id: Some(chain_id),
419 input: TransactionInput::new(Bytes::from("hellothere")),
420 nonce: Some(nonce),
421 gas_price: Some(gas_price * 110 / 100),
422 gas: Some(210000),
423 ..Default::default()
424 };
425
426 let res = anvil_provider
427 .send_transaction(tx)
428 .await?
429 .get_receipt()
430 .await?;
431
432 let tx_hash = res.transaction_hash;
433 let tx_hash = format!("{:x}", &tx_hash);
434 let tx_prefix = &tx_hash[..TX_PREFIX.len()];
435
436 assert_eq!(tx_prefix.as_bytes(), TX_PREFIX.as_bytes());
437
438 Ok(())
439 }
440}