sov_rollup_interface/node/rpc/mod.rs
1//! The rpc module defines types and traits for querying chain history
2//! via an RPC interface.
3#[cfg(feature = "native")]
4use serde::de::DeserializeOwned;
5use serde::{Deserialize, Serialize};
6#[cfg(feature = "native")]
7use tokio::sync::broadcast::Receiver;
8
9#[cfg(feature = "native")]
10use crate::stf::Event;
11use crate::stf::EventKey;
12
13/// A struct containing enough information to uniquely specify single batch.
14#[derive(Debug, PartialEq, Serialize, Deserialize)]
15pub struct SlotIdAndOffset {
16 /// The [`SlotIdentifier`] of the slot containing this batch.
17 pub slot_id: SlotIdentifier,
18 /// The offset into the slot at which this tx is located.
19 /// Index 0 is the first batch in the slot.
20 pub offset: u64,
21}
22
23/// A struct containing enough information to uniquely specify single transaction.
24#[derive(Debug, PartialEq, Serialize, Deserialize)]
25pub struct BatchIdAndOffset {
26 /// The [`BatchIdentifier`] of the batch containing this transaction.
27 pub batch_id: BatchIdentifier,
28 /// The offset into the batch at which this tx is located.
29 /// Index 0 is the first transaction in the batch.
30 pub offset: u64,
31}
32
33/// A struct containing enough information to uniquely specify single event.
34#[derive(Debug, PartialEq, Serialize, Deserialize)]
35pub struct TxIdAndOffset {
36 /// The [`TxIdentifier`] of the transaction containing this event.
37 pub tx_id: TxIdentifier,
38 /// The offset into the tx's events at which this event is located.
39 /// Index 0 is the first event from this tx.
40 pub offset: u64,
41}
42
43/// A struct containing enough information to uniquely specify single event.
44#[derive(Debug, PartialEq, Serialize, Deserialize)]
45pub struct TxIdAndKey {
46 /// The [`TxIdentifier`] of the transaction containing this event.
47 pub tx_id: TxIdentifier,
48 /// The key of the event.
49 pub key: EventKey,
50}
51
52/// An identifier that specifies a single batch
53#[derive(Debug, PartialEq, Serialize, Deserialize)]
54#[serde(untagged)]
55pub enum BatchIdentifier {
56 /// The hex-encoded hash of the batch, as computed by the DA layer.
57 Hash(#[serde(with = "utils::rpc_hex")] [u8; 32]),
58 /// An offset into a particular slot (i.e. the 3rd batch in slot 5).
59 SlotIdAndOffset(SlotIdAndOffset),
60 /// The monotonically increasing number of the batch, ordered by the DA layer For example, if the genesis slot
61 /// contains 0 batches, slot 1 contains 2 txs, and slot 3 contains 3 txs,
62 /// the last batch in block 3 would have number 5. The counter never resets.
63 Number(u64),
64}
65
66/// An identifier that specifies a single transaction.
67#[derive(Debug, PartialEq, Serialize, Deserialize)]
68#[serde(untagged)]
69pub enum TxIdentifier {
70 /// The hex encoded hash of the transaction.
71 Hash(#[serde(with = "utils::rpc_hex")] [u8; 32]),
72 /// An offset into a particular batch (i.e. the 3rd transaction in batch 5).
73 BatchIdAndOffset(BatchIdAndOffset),
74 /// The monotonically increasing number of the tx, ordered by the DA layer For example, if genesis
75 /// contains 0 txs, batch 1 contains 8 txs, and batch 3 contains 7 txs,
76 /// the last tx in batch 3 would have number 15. The counter never resets.
77 Number(u64),
78}
79
80/// An identifier that specifies a single event.
81#[derive(Debug, PartialEq, Serialize, Deserialize)]
82#[serde(untagged)]
83pub enum EventIdentifier {
84 /// An offset into a particular transaction (i.e. the 3rd event in transaction number 5).
85 TxIdAndOffset(TxIdAndOffset),
86 /// A particular event key from a particular transaction.
87 TxIdAndKey(TxIdAndKey),
88 /// The monotonically increasing number of the event, ordered by the DA layer For example, if the first tx
89 /// contains 7 events, tx 2 contains 11 events, and tx 3 contains 7 txs,
90 /// the last event in tx 3 would have number 25. The counter never resets.
91 Number(u64),
92}
93
94/// An identifier for a group of related events
95#[derive(Debug, PartialEq, Serialize, Deserialize)]
96#[serde(untagged)]
97pub enum EventGroupIdentifier {
98 /// Fetch all events from a particular transaction.
99 TxId(TxIdentifier),
100 /// Fetch all events (i.e. from all transactions) with a particular key.
101 Key(Vec<u8>),
102}
103
104/// An identifier that specifies a single slot.
105#[derive(Debug, PartialEq, Serialize, Deserialize)]
106#[serde(untagged)]
107pub enum SlotIdentifier {
108 /// The hex encoded hash of the slot (i.e. the da layer's block hash).
109 Hash(#[serde(with = "utils::rpc_hex")] [u8; 32]),
110 /// The monotonically increasing number of the slot, ordered by the DA layer but starting from 0
111 /// at the *rollup's* genesis.
112 Number(u64),
113}
114
115/// A QueryMode specifies how much information to return in response to an RPC query
116#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
117pub enum QueryMode {
118 /// Returns the parent struct but no details about its children.
119 /// For example, a `Compact` "get_slots" response would simply state the range of batch
120 /// numbers which occurred in the slot, but not the hashes of the batches themselves.
121 Compact,
122 /// Returns the parent struct and the hashes of all its children.
123 Standard,
124 /// Returns the parent struct and all its children, recursively fetching its children
125 /// in `Full` mode. For example, a `Full` "get_batch" response would include the `Full`
126 /// details of all the transactions in the batch, and those would in turn return the event bodies
127 /// which had occurred in those transactions.
128 Full,
129}
130
131impl Default for QueryMode {
132 fn default() -> Self {
133 Self::Standard
134 }
135}
136
137/// The body of a response to a JSON-RPC request for a particular slot.
138#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
139pub struct SlotResponse<B, Tx> {
140 /// The slot number.
141 pub number: u64,
142 /// The hex encoded slot hash.
143 #[serde(with = "utils::rpc_hex")]
144 pub hash: [u8; 32],
145 /// The range of batches in this slot.
146 pub batch_range: std::ops::Range<u64>,
147 /// The batches in this slot, if the [`QueryMode`] of the request is not `Compact`
148 #[serde(skip_serializing_if = "Option::is_none")]
149 pub batches: Option<Vec<ItemOrHash<BatchResponse<B, Tx>>>>,
150}
151
152/// The response to a JSON-RPC request for a particular batch.
153#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
154pub struct BatchResponse<B, Tx> {
155 /// The hex encoded batch hash.
156 #[serde(with = "utils::rpc_hex")]
157 pub hash: [u8; 32],
158 /// The range of transactions in this batch.
159 pub tx_range: std::ops::Range<u64>,
160 /// The transactions in this batch, if the [`QueryMode`] of the request is not `Compact`.
161 #[serde(skip_serializing_if = "Option::is_none")]
162 pub txs: Option<Vec<ItemOrHash<TxResponse<Tx>>>>,
163 /// The custom receipt specified by the rollup. This typically contains
164 /// information about the outcome of the batch.
165 pub custom_receipt: B,
166}
167
168/// The response to a JSON-RPC request for a particular transaction.
169#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
170pub struct TxResponse<Tx> {
171 /// The hex encoded transaction hash.
172 #[serde(with = "utils::rpc_hex")]
173 pub hash: [u8; 32],
174 /// The range of events occurring in this transaction.
175 pub event_range: std::ops::Range<u64>,
176 /// The transaction body, if stored by the rollup.
177 #[serde(skip_serializing_if = "Option::is_none")]
178 pub body: Option<Vec<u8>>,
179 /// The custom receipt specified by the rollup. This typically contains
180 /// information about the outcome of the transaction.
181 pub custom_receipt: Tx,
182}
183
184/// An RPC response which might contain a full item or just its hash.
185#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
186#[serde(untagged)]
187pub enum ItemOrHash<T> {
188 /// The hex encoded hash of the requested item.
189 Hash(#[serde(with = "utils::rpc_hex")] [u8; 32]),
190 /// The full item body.
191 Full(T),
192}
193
194/// A LedgerRpcProvider provides a way to query the ledger for information about slots, batches, transactions, and events.
195#[cfg(feature = "native")]
196pub trait LedgerRpcProvider {
197 /// Get the latest slot in the ledger.
198 fn get_head<B: DeserializeOwned + Clone, T: DeserializeOwned>(
199 &self,
200 query_mode: QueryMode,
201 ) -> Result<Option<SlotResponse<B, T>>, anyhow::Error>;
202
203 /// Get a list of slots by id. The IDs need not be ordered.
204 fn get_slots<B: DeserializeOwned, T: DeserializeOwned>(
205 &self,
206 slot_ids: &[SlotIdentifier],
207 query_mode: QueryMode,
208 ) -> Result<Vec<Option<SlotResponse<B, T>>>, anyhow::Error>;
209
210 /// Get a list of batches by id. The IDs need not be ordered.
211 fn get_batches<B: DeserializeOwned, T: DeserializeOwned>(
212 &self,
213 batch_ids: &[BatchIdentifier],
214 query_mode: QueryMode,
215 ) -> Result<Vec<Option<BatchResponse<B, T>>>, anyhow::Error>;
216
217 /// Get a list of transactions by id. The IDs need not be ordered.
218 fn get_transactions<T: DeserializeOwned>(
219 &self,
220 tx_ids: &[TxIdentifier],
221 query_mode: QueryMode,
222 ) -> Result<Vec<Option<TxResponse<T>>>, anyhow::Error>;
223
224 /// Get events by id. The IDs need not be ordered.
225 fn get_events(
226 &self,
227 event_ids: &[EventIdentifier],
228 ) -> Result<Vec<Option<Event>>, anyhow::Error>;
229
230 /// Get a single slot by hash.
231 fn get_slot_by_hash<B: DeserializeOwned, T: DeserializeOwned>(
232 &self,
233 hash: &[u8; 32],
234 query_mode: QueryMode,
235 ) -> Result<Option<SlotResponse<B, T>>, anyhow::Error>;
236
237 /// Get a single batch by hash.
238 fn get_batch_by_hash<B: DeserializeOwned, T: DeserializeOwned>(
239 &self,
240 hash: &[u8; 32],
241 query_mode: QueryMode,
242 ) -> Result<Option<BatchResponse<B, T>>, anyhow::Error>;
243
244 /// Get a single transaction by hash.
245 fn get_tx_by_hash<T: DeserializeOwned>(
246 &self,
247 hash: &[u8; 32],
248 query_mode: QueryMode,
249 ) -> Result<Option<TxResponse<T>>, anyhow::Error>;
250
251 /// Get a single slot by number.
252 fn get_slot_by_number<B: DeserializeOwned, T: DeserializeOwned>(
253 &self,
254 number: u64,
255 query_mode: QueryMode,
256 ) -> Result<Option<SlotResponse<B, T>>, anyhow::Error>;
257
258 /// Get a single batch by number.
259 fn get_batch_by_number<B: DeserializeOwned, T: DeserializeOwned>(
260 &self,
261 number: u64,
262 query_mode: QueryMode,
263 ) -> Result<Option<BatchResponse<B, T>>, anyhow::Error>;
264
265 /// Get a single event by number.
266 fn get_event_by_number(&self, number: u64) -> Result<Option<Event>, anyhow::Error>;
267
268 /// Get a single tx by number.
269 fn get_tx_by_number<T: DeserializeOwned>(
270 &self,
271 number: u64,
272 query_mode: QueryMode,
273 ) -> Result<Option<TxResponse<T>>, anyhow::Error>;
274
275 /// Get a range of slots. This query is the most efficient way to
276 /// fetch large numbers of slots, since it allows for easy batching of
277 /// db queries for adjacent items.
278 fn get_slots_range<B: DeserializeOwned, T: DeserializeOwned>(
279 &self,
280 start: u64,
281 end: u64,
282 query_mode: QueryMode,
283 ) -> Result<Vec<Option<SlotResponse<B, T>>>, anyhow::Error>;
284
285 /// Get a range of batches. This query is the most efficient way to
286 /// fetch large numbers of batches, since it allows for easy batching of
287 /// db queries for adjacent items.
288 fn get_batches_range<B: DeserializeOwned, T: DeserializeOwned>(
289 &self,
290 start: u64,
291 end: u64,
292 query_mode: QueryMode,
293 ) -> Result<Vec<Option<BatchResponse<B, T>>>, anyhow::Error>;
294
295 /// Get a range of batches. This query is the most efficient way to
296 /// fetch large numbers of transactions, since it allows for easy batching of
297 /// db queries for adjacent items.
298 fn get_transactions_range<T: DeserializeOwned>(
299 &self,
300 start: u64,
301 end: u64,
302 query_mode: QueryMode,
303 ) -> Result<Vec<Option<TxResponse<T>>>, anyhow::Error>;
304
305 /// Get a notification each time a slot is processed
306 fn subscribe_slots(&self) -> Result<Receiver<u64>, anyhow::Error>;
307}
308
309/// JSON-RPC -related utilities. Occasionally useful but unimportant for most
310/// use cases.
311pub mod utils {
312 /// Serialization and deserialization logic for `0x`-prefixed hex strings.
313 pub mod rpc_hex {
314 use core::fmt;
315 use std::marker::PhantomData;
316
317 use hex::{FromHex, ToHex};
318 use serde::de::{Error, Visitor};
319 use serde::{Deserializer, Serializer};
320
321 /// Serializes `data` as hex string using lowercase characters and prefixing with '0x'.
322 ///
323 /// Lowercase characters are used (e.g. `f9b4ca`). The resulting string's length
324 /// is always even, each byte in data is always encoded using two hex digits.
325 /// Thus, the resulting string contains exactly twice as many bytes as the input
326 /// data.
327 pub fn serialize<S, T>(data: T, serializer: S) -> Result<S::Ok, S::Error>
328 where
329 S: Serializer,
330 T: ToHex,
331 {
332 let formatted_string = format!("0x{}", data.encode_hex::<String>());
333 serializer.serialize_str(&formatted_string)
334 }
335
336 /// Deserializes a hex string into raw bytes.
337 ///
338 /// Both, upper and lower case characters are valid in the input string and can
339 /// even be mixed (e.g. `f9b4ca`, `F9B4CA` and `f9B4Ca` are all valid strings).
340 pub fn deserialize<'de, D, T>(deserializer: D) -> Result<T, D::Error>
341 where
342 D: Deserializer<'de>,
343 T: FromHex,
344 <T as FromHex>::Error: fmt::Display,
345 {
346 struct HexStrVisitor<T>(PhantomData<T>);
347
348 impl<'de, T> Visitor<'de> for HexStrVisitor<T>
349 where
350 T: FromHex,
351 <T as FromHex>::Error: fmt::Display,
352 {
353 type Value = T;
354
355 fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
356 write!(f, "a hex encoded string")
357 }
358
359 fn visit_str<E>(self, data: &str) -> Result<Self::Value, E>
360 where
361 E: Error,
362 {
363 let data = data.trim_start_matches("0x");
364 FromHex::from_hex(data).map_err(Error::custom)
365 }
366
367 fn visit_borrowed_str<E>(self, data: &'de str) -> Result<Self::Value, E>
368 where
369 E: Error,
370 {
371 let data = data.trim_start_matches("0x");
372 FromHex::from_hex(data).map_err(Error::custom)
373 }
374 }
375
376 deserializer.deserialize_str(HexStrVisitor(PhantomData))
377 }
378 }
379}
380
381#[cfg(test)]
382mod rpc_hex_tests {
383 use serde::{Deserialize, Serialize};
384
385 #[derive(Serialize, Deserialize, PartialEq, Debug)]
386 struct TestStruct {
387 #[serde(with = "super::utils::rpc_hex")]
388 data: Vec<u8>,
389 }
390
391 #[test]
392 fn test_roundtrip() {
393 let test_data = TestStruct {
394 data: vec![0x01, 0x02, 0x03, 0x04],
395 };
396
397 let serialized = serde_json::to_string(&test_data).unwrap();
398 assert!(serialized.contains("0x01020304"));
399 let deserialized: TestStruct = serde_json::from_str(&serialized).unwrap();
400 assert_eq!(deserialized, test_data)
401 }
402
403 #[test]
404 fn test_accepts_hex_without_0x_prefix() {
405 let test_data = TestStruct {
406 data: vec![0x01, 0x02, 0x03, 0x04],
407 };
408
409 let deserialized: TestStruct = serde_json::from_str(r#"{"data": "01020304"}"#).unwrap();
410 assert_eq!(deserialized, test_data)
411 }
412}