reth_optimism_flashblocks/
sequence.rs1use crate::{ExecutionPayloadBaseV1, FlashBlock};
2use alloy_eips::eip2718::WithEncoded;
3use reth_primitives_traits::{Recovered, SignedTransaction};
4use std::collections::BTreeMap;
5use tracing::trace;
6
7#[derive(Debug)]
9pub(crate) struct FlashBlockSequence<T> {
10 inner: BTreeMap<u64, PreparedFlashBlock<T>>,
15}
16
17impl<T> FlashBlockSequence<T>
18where
19 T: SignedTransaction,
20{
21 pub(crate) const fn new() -> Self {
22 Self { inner: BTreeMap::new() }
23 }
24
25 pub(crate) fn insert(&mut self, flashblock: FlashBlock) -> eyre::Result<()> {
29 if flashblock.index == 0 {
30 trace!(number=%flashblock.block_number(), "Tracking new flashblock sequence");
31 self.clear();
33 self.inner.insert(flashblock.index, PreparedFlashBlock::new(flashblock)?);
34 return Ok(())
35 }
36
37 if self.block_number() == Some(flashblock.metadata.block_number) {
39 trace!(number=%flashblock.block_number(), index = %flashblock.index, block_count = self.inner.len() ,"Received followup flashblock");
40 self.inner.insert(flashblock.index, PreparedFlashBlock::new(flashblock)?);
41 } else {
42 trace!(number=%flashblock.block_number(), index = %flashblock.index, current=?self.block_number() ,"Ignoring untracked flashblock following");
43 }
44
45 Ok(())
46 }
47
48 pub(crate) fn block_number(&self) -> Option<u64> {
50 Some(self.inner.values().next()?.block().metadata.block_number)
51 }
52
53 pub(crate) fn payload_base(&self) -> Option<ExecutionPayloadBaseV1> {
55 self.inner.values().next()?.block().base.clone()
56 }
57
58 pub(crate) fn ready_transactions(
65 &self,
66 ) -> impl Iterator<Item = WithEncoded<Recovered<T>>> + '_ {
67 self.inner
68 .values()
69 .enumerate()
70 .take_while(|(idx, block)| {
71 block.block().index == *idx as u64
73 })
74 .flat_map(|(_, block)| block.txs.clone())
75 }
76
77 pub(crate) fn count(&self) -> usize {
79 self.inner.len()
80 }
81
82 fn clear(&mut self) {
83 self.inner.clear();
84 }
85}
86
87#[derive(Debug)]
88struct PreparedFlashBlock<T> {
89 txs: Vec<WithEncoded<Recovered<T>>>,
91 block: FlashBlock,
93}
94
95impl<T> PreparedFlashBlock<T> {
96 const fn block(&self) -> &FlashBlock {
97 &self.block
98 }
99}
100
101impl<T> PreparedFlashBlock<T>
102where
103 T: SignedTransaction,
104{
105 fn new(block: FlashBlock) -> eyre::Result<Self> {
109 let mut txs = Vec::with_capacity(block.diff.transactions.len());
110 for encoded in block.diff.transactions.iter().cloned() {
111 let tx = T::decode_2718_exact(encoded.as_ref())?;
112 let signer = tx.try_recover()?;
113 let tx = WithEncoded::new(encoded, tx.with_signer(signer));
114 txs.push(tx);
115 }
116
117 Ok(Self { txs, block })
118 }
119}
120
121#[cfg(test)]
122mod tests {
123 use super::*;
124 use crate::ExecutionPayloadFlashblockDeltaV1;
125 use alloy_consensus::{
126 transaction::SignerRecoverable, EthereumTxEnvelope, EthereumTypedTransaction, TxEip1559,
127 };
128 use alloy_eips::Encodable2718;
129 use alloy_primitives::{hex, Signature, TxKind, U256};
130
131 #[test]
132 fn test_sequence_stops_before_gap() {
133 let mut sequence = FlashBlockSequence::new();
134 let tx = EthereumTxEnvelope::new_unhashed(
135 EthereumTypedTransaction::<TxEip1559>::Eip1559(TxEip1559 {
136 chain_id: 4,
137 nonce: 26u64,
138 max_priority_fee_per_gas: 1500000000,
139 max_fee_per_gas: 1500000013,
140 gas_limit: 21_000u64,
141 to: TxKind::Call(hex!("61815774383099e24810ab832a5b2a5425c154d5").into()),
142 value: U256::from(3000000000000000000u64),
143 input: Default::default(),
144 access_list: Default::default(),
145 }),
146 Signature::new(
147 U256::from_be_bytes(hex!(
148 "59e6b67f48fb32e7e570dfb11e042b5ad2e55e3ce3ce9cd989c7e06e07feeafd"
149 )),
150 U256::from_be_bytes(hex!(
151 "016b83f4f980694ed2eee4d10667242b1f40dc406901b34125b008d334d47469"
152 )),
153 true,
154 ),
155 );
156 let tx = Recovered::new_unchecked(tx.clone(), tx.recover_signer_unchecked().unwrap());
157
158 sequence
159 .insert(FlashBlock {
160 payload_id: Default::default(),
161 index: 0,
162 base: None,
163 diff: ExecutionPayloadFlashblockDeltaV1 {
164 transactions: vec![tx.encoded_2718().into()],
165 ..Default::default()
166 },
167 metadata: Default::default(),
168 })
169 .unwrap();
170
171 sequence
172 .insert(FlashBlock {
173 payload_id: Default::default(),
174 index: 2,
175 base: None,
176 diff: Default::default(),
177 metadata: Default::default(),
178 })
179 .unwrap();
180
181 let actual_txs: Vec<_> = sequence.ready_transactions().collect();
182 let expected_txs = vec![WithEncoded::new(tx.encoded_2718().into(), tx)];
183
184 assert_eq!(actual_txs, expected_txs);
185 }
186}