Skip to content

Commit dca26b6

Browse files
authored
feat(rpc): update to Starknet JSON-RPC spec v0.10.0 (#385)
Update RPC types to Starknet JSON-RPC specification v0.10.0: - Add `migrated_compiled_classes` to state diff - Make `old_root` optional in pre-confirmed state updates - Add `transaction_index` and `event_index` to emitted events Starknet JSON-RPC v0.10.0 specifications: https://github.com/starkware-libs/starknet-specs/blob/v0.10.0/api/starknet_api_openrpc.json ## Backward compatibility ### RPC The new fields are made optional to allow for temporary backward-compatibility - it only matters if Katana is forking using a RPC provider that is not on v0.10. Will change once the whole ecosystem has defaulted to the new spec. ## Database This only affect the RPC types. The internal changes are made in [#379](#379) and is backward compatible.
1 parent bba5909 commit dca26b6

File tree

11 files changed

+62
-44
lines changed

11 files changed

+62
-44
lines changed

crates/gateway/gateway-types/src/conversion.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ use crate::{
1212
impl From<katana_rpc_types::StateUpdate> for StateUpdate {
1313
fn from(value: katana_rpc_types::StateUpdate) -> Self {
1414
match value {
15-
katana_rpc_types::StateUpdate::Update(update) => StateUpdate::Confirmed(update.into()),
15+
katana_rpc_types::StateUpdate::Confirmed(update) => {
16+
StateUpdate::Confirmed(update.into())
17+
}
1618
katana_rpc_types::StateUpdate::PreConfirmed(pre_confirmed) => {
1719
StateUpdate::PreConfirmed(pre_confirmed.into())
1820
}

crates/gateway/gateway-types/src/state_update.rs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ pub enum StateUpdate {
2828
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
2929
pub struct PreConfirmedStateUpdate {
3030
/// The previous global state root
31-
pub old_root: Felt,
31+
pub old_root: Option<Felt>,
3232
/// State diff
3333
pub state_diff: StateDiff,
3434
}
@@ -272,20 +272,21 @@ impl<'de> Deserialize<'de> for StateUpdate {
272272
}
273273
}
274274

275-
let old_root =
276-
old_root.ok_or_else(|| serde::de::Error::missing_field("old_root"))?;
277275
let state_diff =
278276
state_diff.ok_or_else(|| serde::de::Error::missing_field("state_diff"))?;
279277

280278
// If block_hash and new_root are not present, deserialize as
281279
// PreConfirmedStateUpdate
282280
match (block_hash, new_root) {
283-
(None, None) => Ok(StateUpdate::PreConfirmed(PreConfirmedStateUpdate {
281+
(None, None, ..) => Ok(StateUpdate::PreConfirmed(PreConfirmedStateUpdate {
284282
old_root,
285283
state_diff,
286284
})),
287285

288286
(Some(block_hash), Some(new_root)) => {
287+
let old_root =
288+
old_root.ok_or_else(|| serde::de::Error::missing_field("old_root"))?;
289+
289290
Ok(StateUpdate::Confirmed(ConfirmedStateUpdate {
290291
block_hash,
291292
new_root,

crates/node/src/full/pending/provider.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ use katana_gateway_types::TxTryFromError;
22
use katana_primitives::block::FinalityStatus;
33
use katana_primitives::fee::PriceUnit;
44
use katana_primitives::transaction::{TxHash, TxNumber, TxType, TxWithHash};
5-
use katana_primitives::Felt;
65
use katana_provider::api::state::StateProvider;
76
use katana_rpc_server::starknet::{PendingBlockProvider, StarknetApiResult};
87
use katana_rpc_types::{
@@ -151,10 +150,7 @@ impl PendingBlockProvider for PreconfStateFactory {
151150
&self,
152151
) -> StarknetApiResult<Option<katana_rpc_types::PreConfirmedStateUpdate>> {
153152
if let Some(state_diff) = self.state_updates() {
154-
Ok(Some(PreConfirmedStateUpdate {
155-
old_root: Felt::ZERO,
156-
state_diff: state_diff.into(),
157-
}))
153+
Ok(Some(PreConfirmedStateUpdate { old_root: None, state_diff: state_diff.into() }))
158154
} else {
159155
Ok(None)
160156
}

crates/rpc/rpc-server/src/starknet/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -771,7 +771,7 @@ where
771771
let state_update =
772772
katana_rpc_types_builder::StateUpdateBuilder::new(block_id, provider)
773773
.build()?
774-
.map(StateUpdate::Update);
774+
.map(StateUpdate::Confirmed);
775775

776776
StarknetApiResult::Ok(state_update)
777777
})

crates/rpc/rpc-server/src/utils/events.rs

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -97,15 +97,15 @@ pub fn fetch_pending_events(
9797

9898
// process individual transactions in the block.
9999
// the iterator will start with txn index == cursor.txn.idx
100-
for (tx_idx, (tx_hash, events)) in pending_block
100+
for (tx_idx, (tx_hash, tx_events)) in pending_block
101101
.transactions
102102
.iter()
103103
.map(|receipt| (receipt.receipt.transaction_hash, receipt.receipt.receipt.events()))
104104
.enumerate()
105105
.skip(cursor.txn.idx)
106106
{
107107
if tx_idx == cursor.txn.idx {
108-
match events.len().cmp(&cursor.txn.event) {
108+
match tx_events.len().cmp(&cursor.txn.event) {
109109
Ordering::Equal | Ordering::Greater => {}
110110
Ordering::Less => continue,
111111
}
@@ -119,7 +119,7 @@ pub fn fetch_pending_events(
119119
None,
120120
tx_idx,
121121
tx_hash,
122-
events,
122+
tx_events,
123123
filter,
124124
chunk_size as usize,
125125
buffer,
@@ -222,24 +222,24 @@ pub fn fetch_events_at_blocks(
222222
Ok(None)
223223
}
224224

225-
/// An iterator that yields events that match the given filters.
225+
/// An iterator that yields events (with their original indices) that match the given filters.
226226
#[derive(Debug)]
227-
struct FilteredEvents<'a, I: Iterator<Item = &'a Event>> {
227+
struct FilteredEvents<'a, I: Iterator<Item = (usize, &'a Event)>> {
228228
iter: I,
229229
filter: &'a Filter,
230230
}
231231

232-
impl<'a, I: Iterator<Item = &'a Event>> FilteredEvents<'a, I> {
232+
impl<'a, I: Iterator<Item = (usize, &'a Event)>> FilteredEvents<'a, I> {
233233
fn new(iter: I, filter: &'a Filter) -> Self {
234234
Self { iter, filter }
235235
}
236236
}
237237

238-
impl<'a, I: Iterator<Item = &'a Event>> Iterator for FilteredEvents<'a, I> {
239-
type Item = &'a Event;
238+
impl<'a, I: Iterator<Item = (usize, &'a Event)>> Iterator for FilteredEvents<'a, I> {
239+
type Item = (usize, &'a Event);
240240

241241
fn next(&mut self) -> Option<Self::Item> {
242-
for event in self.iter.by_ref() {
242+
for (idx, event) in self.iter.by_ref() {
243243
// Skip this event if there is an address filter but doesn't match the address of the
244244
// event.
245245
if self.filter.address.is_some_and(|addr| addr != event.from_address) {
@@ -271,7 +271,7 @@ impl<'a, I: Iterator<Item = &'a Event>> Iterator for FilteredEvents<'a, I> {
271271
};
272272

273273
if is_matched {
274-
return Some(event);
274+
return Some((idx, event));
275275
}
276276
}
277277

@@ -303,7 +303,7 @@ fn fetch_tx_events(
303303
block_hash: Option<BlockHash>,
304304
tx_idx: usize,
305305
tx_hash: TxHash,
306-
events: &[Event],
306+
tx_events: &[Event],
307307
filter: &Filter,
308308
chunk_size: usize,
309309
buffer: &mut Vec<EmittedEvent>,
@@ -312,18 +312,24 @@ fn fetch_tx_events(
312312
// number of events we have taken.
313313
let total_can_take = chunk_size.saturating_sub(buffer.len());
314314

315+
// Enumerate events first to preserve original indices, then filter.
315316
// skip events according to the continuation token.
316-
let filtered = FilteredEvents::new(events.iter(), filter)
317-
.map(|e| EmittedEvent {
318-
block_hash,
319-
block_number,
320-
keys: e.keys.clone(),
321-
data: e.data.clone(),
322-
transaction_hash: tx_hash,
323-
from_address: e.from_address,
317+
let filtered = FilteredEvents::new(tx_events.iter().enumerate(), filter)
318+
.map(|(event_idx, e)| {
319+
(
320+
event_idx,
321+
EmittedEvent {
322+
block_hash,
323+
block_number,
324+
keys: e.keys.clone(),
325+
data: e.data.clone(),
326+
transaction_hash: tx_hash,
327+
from_address: e.from_address,
328+
transaction_index: Some(tx_idx as u64),
329+
event_index: Some(event_idx as u64),
330+
},
331+
)
324332
})
325-
// enumerate so that we can keep track of the event's index in the transaction
326-
.enumerate()
327333
.skip(next_event_idx)
328334
.take(total_can_take)
329335
.collect::<Vec<_>>();
@@ -349,7 +355,7 @@ fn fetch_tx_events(
349355
};
350356

351357
// if there are still more events that we haven't fetched yet for this tx.
352-
if new_last_event < events.len() {
358+
if new_last_event < tx_events.len() {
353359
return Ok(Some(PartialCursor { idx: tx_idx, event: new_last_event }));
354360
}
355361
}

crates/rpc/rpc-server/tests/forking.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ use url::Url;
2020

2121
mod common;
2222

23-
const SEPOLIA_URL: &str = "https://api.cartridge.gg/x/starknet/sepolia";
23+
const SEPOLIA_URL: &str = "https://api.cartridge.gg/x/starknet/sepolia/rpc/v0_10";
2424
const FORK_BLOCK_NUMBER: BlockNumber = 268_471;
2525
const FORK_BLOCK_HASH: BlockHash =
2626
felt!("0x208950cfcbba73ecbda1c14e4d58d66a8d60655ea1b9dcf07c16014ae8a93cd");

crates/rpc/rpc-server/tests/starknet.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ async fn declare_and_deploy_contract() {
6262
// check state update includes class in declared_classes
6363
let state_update = provider.get_state_update(BlockIdOrTag::Latest).await.unwrap();
6464
match state_update {
65-
StateUpdate::Update(update) => {
65+
StateUpdate::Confirmed(update) => {
6666
similar_asserts::assert_eq!(
6767
update.state_diff.declared_classes,
6868
BTreeMap::from_iter([(class_hash, compiled_class_hash)])

crates/rpc/rpc-types/src/event.rs

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ pub struct EventFilter {
2727
#[serde(skip_serializing_if = "Option::is_none")]
2828
pub address: Option<ContractAddress>,
2929
/// The keys to filter over
30+
///
31+
/// Per key (by position), designate the possible values to be matched for events to be
32+
/// returned. Empty array designates 'any' value
3033
#[serde(skip_serializing_if = "Option::is_none")]
3134
pub keys: Option<Vec<Vec<Felt>>>,
3235
}
@@ -63,12 +66,22 @@ pub struct GetEventsResponse {
6366
/// of transaction execution.
6467
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
6568
pub struct EmittedEvent {
66-
pub from_address: ContractAddress,
67-
pub keys: Vec<Felt>,
68-
pub data: Vec<Felt>,
69+
/// The hash of the block in which the event was emitted.
6970
#[serde(skip_serializing_if = "Option::is_none")]
7071
pub block_hash: Option<BlockHash>,
72+
/// The number of the block in which the event was emitted.
7173
#[serde(skip_serializing_if = "Option::is_none")]
7274
pub block_number: Option<BlockNumber>,
75+
/// The hash of the transaction where the event was emitted.
7376
pub transaction_hash: TxHash,
77+
/// The index of the transaction in the block.
78+
#[serde(default, skip_serializing_if = "Option::is_none")]
79+
pub transaction_index: Option<u64>,
80+
/// The index of the event within the transaction.
81+
#[serde(default, skip_serializing_if = "Option::is_none")]
82+
pub event_index: Option<u64>,
83+
/// The address of the contract that emitted the event.
84+
pub from_address: ContractAddress,
85+
pub keys: Vec<Felt>,
86+
pub data: Vec<Felt>,
7487
}

crates/rpc/rpc-types/src/state_update.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,15 +9,15 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer};
99
#[derive(Debug, Clone, Serialize, Deserialize)]
1010
#[serde(untagged)]
1111
pub enum StateUpdate {
12-
Update(ConfirmedStateUpdate),
12+
Confirmed(ConfirmedStateUpdate),
1313
PreConfirmed(PreConfirmedStateUpdate),
1414
}
1515

1616
/// State update of a pre-confirmed block.
1717
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
1818
pub struct PreConfirmedStateUpdate {
1919
/// The previous global state root
20-
pub old_root: Felt,
20+
pub old_root: Option<Felt>,
2121
/// State diff
2222
pub state_diff: StateDiff,
2323
}

crates/rpc/rpc-types/tests/state_update.rs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ fn preconfirmed_state_update() {
3535
let PreConfirmedStateUpdate { old_root, ref state_diff } = state_update;
3636
assert_eq!(
3737
old_root,
38-
felt!("0x6a59de5353d4050a800fd240020d014653d950df357ffa14319ee809a65427a")
38+
Some(felt!("0x6a59de5353d4050a800fd240020d014653d950df357ffa14319ee809a65427a"))
3939
);
4040
assert_eq!(state_diff.deprecated_declared_classes, BTreeSet::new());
4141
assert_eq!(state_diff.replaced_classes, map!());
@@ -75,7 +75,7 @@ fn confirmed_state_update() {
7575

7676
let state_update: ConfirmedStateUpdate = serde_json::from_value(json.clone()).unwrap();
7777
let as_enum: StateUpdate = serde_json::from_value(json.clone()).unwrap();
78-
assert_matches!(as_enum, StateUpdate::Update(as_enum_update) => {
78+
assert_matches!(as_enum, StateUpdate::Confirmed(as_enum_update) => {
7979
similar_asserts::assert_eq!(as_enum_update, state_update);
8080
});
8181

@@ -143,7 +143,7 @@ fn v0_10_0_confirmed_state_update() {
143143

144144
let state_update: ConfirmedStateUpdate = serde_json::from_value(json.clone()).unwrap();
145145
let as_enum: StateUpdate = serde_json::from_value(json.clone()).unwrap();
146-
assert_matches!(as_enum, StateUpdate::Update(as_enum_update) => {
146+
assert_matches!(as_enum, StateUpdate::Confirmed(as_enum_update) => {
147147
similar_asserts::assert_eq!(as_enum_update, state_update);
148148
});
149149

0 commit comments

Comments
 (0)