signet_bundle/send/
bundle.rs1use alloy::{
3 consensus::TxEnvelope,
4 eips::Decodable2718,
5 primitives::{Bytes, B256},
6 rlp::Buf,
7 rpc::types::mev::EthSendBundle,
8};
9use serde::{Deserialize, Serialize};
10use trevm::{
11 inspectors::{Layered, TimeLimit},
12 revm::{inspector::NoOpInspector, Database},
13 BundleError,
14};
15
16pub type BundleInspector<I = NoOpInspector> = Layered<TimeLimit, I>;
18
19#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
30#[serde(rename_all = "camelCase")]
31pub struct SignetEthBundle {
32 #[serde(flatten)]
34 pub bundle: EthSendBundle,
35
36 #[serde(default, skip_serializing_if = "Vec::is_empty")]
38 pub host_txs: Vec<Bytes>,
39}
40
41impl SignetEthBundle {
42 #[allow(clippy::missing_const_for_fn)] pub fn txs(&self) -> &[Bytes] {
45 &self.bundle.txs
46 }
47
48 pub const fn block_number(&self) -> u64 {
50 self.bundle.block_number
51 }
52
53 pub const fn min_timestamp(&self) -> Option<u64> {
55 self.bundle.min_timestamp
56 }
57
58 pub const fn max_timestamp(&self) -> Option<u64> {
60 self.bundle.max_timestamp
61 }
62
63 pub fn reverting_tx_hashes(&self) -> &[B256] {
65 self.bundle.reverting_tx_hashes.as_slice()
66 }
67
68 pub fn replacement_uuid(&self) -> Option<&str> {
70 self.bundle.replacement_uuid.as_deref()
71 }
72
73 pub fn is_valid_at_timestamp(&self, timestamp: u64) -> bool {
75 let min_timestamp = self.bundle.min_timestamp.unwrap_or(0);
76 let max_timestamp = self.bundle.max_timestamp.unwrap_or(u64::MAX);
77 timestamp >= min_timestamp && timestamp <= max_timestamp
78 }
79
80 pub const fn is_valid_at_block_number(&self, block_number: u64) -> bool {
82 self.bundle.block_number == block_number
83 }
84
85 pub fn decode_and_validate_txs<Db: Database>(
87 &self,
88 ) -> Result<Vec<TxEnvelope>, BundleError<Db>> {
89 let txs = self
91 .txs()
92 .iter()
93 .map(|tx| TxEnvelope::decode_2718(&mut tx.chunk()))
94 .collect::<Result<Vec<_>, _>>()
95 .map_err(|err| BundleError::TransactionDecodingError(err))?;
96
97 if txs.iter().any(|tx| tx.is_eip4844()) {
98 return Err(BundleError::UnsupportedTransactionType);
99 }
100
101 Ok(txs)
102 }
103
104 pub fn decode_and_validate_host_txs<Db: Database>(
106 &self,
107 ) -> Result<Vec<TxEnvelope>, BundleError<Db>> {
108 let txs = self
110 .host_txs
111 .iter()
112 .map(|tx| TxEnvelope::decode_2718(&mut tx.chunk()))
113 .collect::<Result<Vec<_>, _>>()
114 .map_err(|err| BundleError::TransactionDecodingError(err))?;
115
116 Ok(txs)
117 }
118}
119
120#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
126#[serde(rename_all = "camelCase")]
127pub struct SignetEthBundleResponse {
128 pub bundle_hash: B256,
133}
134
135#[cfg(test)]
136mod test {
137 use super::*;
138
139 #[test]
140 fn send_bundle_ser_roundtrip() {
141 let bundle = SignetEthBundle {
142 bundle: EthSendBundle {
143 txs: vec![b"tx1".into(), b"tx2".into()],
144 block_number: 1,
145 min_timestamp: Some(2),
146 max_timestamp: Some(3),
147 reverting_tx_hashes: vec![B256::repeat_byte(4), B256::repeat_byte(5)],
148 replacement_uuid: Some("uuid".to_owned()),
149 ..Default::default()
150 },
151 host_txs: vec![b"host_tx1".into(), b"host_tx2".into()],
152 };
153
154 let serialized = serde_json::to_string(&bundle).unwrap();
155 let deserialized: SignetEthBundle = serde_json::from_str(&serialized).unwrap();
156
157 assert_eq!(bundle, deserialized);
158 }
159
160 #[test]
161 fn send_bundle_ser_roundtrip_no_host_no_fills() {
162 let bundle = SignetEthBundle {
163 bundle: EthSendBundle {
164 txs: vec![b"tx1".into(), b"tx2".into()],
165 block_number: 1,
166 min_timestamp: Some(2),
167 max_timestamp: Some(3),
168 reverting_tx_hashes: vec![B256::repeat_byte(4), B256::repeat_byte(5)],
169 replacement_uuid: Some("uuid".to_owned()),
170 ..Default::default()
171 },
172 host_txs: vec![],
173 };
174
175 let serialized = serde_json::to_string(&bundle).unwrap();
176 let deserialized: SignetEthBundle = serde_json::from_str(&serialized).unwrap();
177
178 assert_eq!(bundle, deserialized);
179 }
180
181 #[test]
182 fn test_deser_bundle_no_host_no_fills() {
183 let json = r#"
184 {"txs":["0x747831","0x747832"],"blockNumber":"0x1","minTimestamp":2,"maxTimestamp":3,"revertingTxHashes":["0x0404040404040404040404040404040404040404040404040404040404040404","0x0505050505050505050505050505050505050505050505050505050505050505"],"replacementUuid":"uuid"}"#;
185
186 let deserialized: SignetEthBundle = serde_json::from_str(json).unwrap();
187
188 assert!(deserialized.host_txs.is_empty());
189 }
190
191 #[test]
192 fn send_bundle_resp_ser_roundtrip() {
193 let resp = SignetEthBundleResponse { bundle_hash: B256::repeat_byte(1) };
194
195 let serialized = serde_json::to_string(&resp).unwrap();
196 let deserialized: SignetEthBundleResponse = serde_json::from_str(&serialized).unwrap();
197
198 assert_eq!(resp, deserialized);
199 }
200}