alloy_deadbeef/
lib.rs

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; // multiply by 2 to avoid searach space overlap in case 99% certainty fails
153        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}