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#[cfg(test)]
121mod test {
122 use super::*;
123
124 #[test]
125 fn send_bundle_ser_roundtrip() {
126 let bundle = SignetEthBundle {
127 bundle: EthSendBundle {
128 txs: vec![b"tx1".into(), b"tx2".into()],
129 block_number: 1,
130 min_timestamp: Some(2),
131 max_timestamp: Some(3),
132 reverting_tx_hashes: vec![B256::repeat_byte(4), B256::repeat_byte(5)],
133 replacement_uuid: Some("uuid".to_owned()),
134 ..Default::default()
135 },
136 host_txs: vec![b"host_tx1".into(), b"host_tx2".into()],
137 };
138
139 let serialized = serde_json::to_string(&bundle).unwrap();
140 let deserialized: SignetEthBundle = serde_json::from_str(&serialized).unwrap();
141
142 assert_eq!(bundle, deserialized);
143 }
144
145 #[test]
146 fn send_bundle_ser_roundtrip_no_host_no_fills() {
147 let bundle = SignetEthBundle {
148 bundle: EthSendBundle {
149 txs: vec![b"tx1".into(), b"tx2".into()],
150 block_number: 1,
151 min_timestamp: Some(2),
152 max_timestamp: Some(3),
153 reverting_tx_hashes: vec![B256::repeat_byte(4), B256::repeat_byte(5)],
154 replacement_uuid: Some("uuid".to_owned()),
155 ..Default::default()
156 },
157 host_txs: vec![],
158 };
159
160 let serialized = serde_json::to_string(&bundle).unwrap();
161 let deserialized: SignetEthBundle = serde_json::from_str(&serialized).unwrap();
162
163 assert_eq!(bundle, deserialized);
164 }
165
166 #[test]
167 fn test_deser_bundle_no_host_no_fills() {
168 let json = r#"
169 {"txs":["0x747831","0x747832"],"blockNumber":"0x1","minTimestamp":2,"maxTimestamp":3,"revertingTxHashes":["0x0404040404040404040404040404040404040404040404040404040404040404","0x0505050505050505050505050505050505050505050505050505050505050505"],"replacementUuid":"uuid"}"#;
170
171 let deserialized: SignetEthBundle = serde_json::from_str(json).unwrap();
172
173 assert!(deserialized.host_txs.is_empty());
174 }
175}