Skip to main content

near_kit/types/
rpc_extra.rs

1//! Additional RPC response types for validators, light client, and state changes.
2
3use serde::Deserialize;
4
5use super::rpc::{AccessKeyDetails, ValidatorStakeView};
6use super::{AccountId, CryptoHash, NearToken, PublicKey, Signature};
7
8// ============================================================================
9// Validators / Epoch types
10// ============================================================================
11
12/// Epoch validator info from `validators` RPC.
13#[derive(Debug, Clone, Deserialize)]
14pub struct EpochValidatorInfo {
15    /// Current epoch validators.
16    pub current_validators: Vec<CurrentEpochValidatorInfo>,
17    /// Next epoch validators.
18    pub next_validators: Vec<NextEpochValidatorInfo>,
19    /// Current fishermen (deprecated, typically empty).
20    #[serde(default)]
21    pub current_fishermen: Vec<ValidatorStakeView>,
22    /// Next fishermen (deprecated, typically empty).
23    #[serde(default)]
24    pub next_fishermen: Vec<ValidatorStakeView>,
25    /// Current proposals for next epoch.
26    #[serde(default)]
27    pub current_proposals: Vec<ValidatorStakeView>,
28    /// Validators kicked out in previous epoch.
29    #[serde(default)]
30    pub prev_epoch_kickout: Vec<ValidatorKickoutView>,
31    /// Block height when the epoch started.
32    pub epoch_start_height: u64,
33    /// Epoch height.
34    pub epoch_height: u64,
35}
36
37/// Current epoch validator information.
38#[derive(Debug, Clone, Deserialize)]
39pub struct CurrentEpochValidatorInfo {
40    /// Validator account ID.
41    pub account_id: AccountId,
42    /// Validator public key.
43    pub public_key: PublicKey,
44    /// Whether this validator has been slashed.
45    pub is_slashed: bool,
46    /// Stake amount.
47    pub stake: NearToken,
48    /// Shards produced by this validator.
49    #[serde(default)]
50    pub shards_produced: Vec<u64>,
51    /// Number of blocks produced.
52    pub num_produced_blocks: u64,
53    /// Number of blocks expected.
54    pub num_expected_blocks: u64,
55    /// Number of chunks produced.
56    #[serde(default)]
57    pub num_produced_chunks: u64,
58    /// Number of chunks expected.
59    #[serde(default)]
60    pub num_expected_chunks: u64,
61    /// Number of produced chunks per shard.
62    #[serde(default)]
63    pub num_produced_chunks_per_shard: Vec<u64>,
64    /// Number of expected chunks per shard.
65    #[serde(default)]
66    pub num_expected_chunks_per_shard: Vec<u64>,
67    /// Number of endorsements produced.
68    #[serde(default)]
69    pub num_produced_endorsements: u64,
70    /// Number of endorsements expected.
71    #[serde(default)]
72    pub num_expected_endorsements: u64,
73    /// Number of endorsements produced per shard.
74    #[serde(default)]
75    pub num_produced_endorsements_per_shard: Vec<u64>,
76    /// Number of endorsements expected per shard.
77    #[serde(default)]
78    pub num_expected_endorsements_per_shard: Vec<u64>,
79    /// Shards endorsed.
80    #[serde(default)]
81    pub shards_endorsed: Vec<u64>,
82}
83
84/// Next epoch validator information.
85#[derive(Debug, Clone, Deserialize)]
86pub struct NextEpochValidatorInfo {
87    /// Validator account ID.
88    pub account_id: AccountId,
89    /// Validator public key.
90    pub public_key: PublicKey,
91    /// Stake amount.
92    pub stake: NearToken,
93    /// Shards to be assigned.
94    #[serde(default)]
95    pub shards: Vec<u64>,
96}
97
98/// Validator kickout information.
99#[derive(Debug, Clone, Deserialize)]
100pub struct ValidatorKickoutView {
101    /// Account ID of the kicked validator.
102    pub account_id: AccountId,
103    /// Reason for kickout.
104    pub reason: ValidatorKickoutReason,
105}
106
107/// Reason a validator was kicked out.
108#[derive(Debug, Clone, Deserialize)]
109pub enum ValidatorKickoutReason {
110    /// Slashed (deprecated, unused).
111    #[serde(rename = "Slashed")]
112    Slashed,
113    /// Not enough blocks produced.
114    NotEnoughBlocks {
115        /// Blocks produced.
116        produced: u64,
117        /// Blocks expected.
118        expected: u64,
119    },
120    /// Not enough chunks produced.
121    NotEnoughChunks {
122        /// Chunks produced.
123        produced: u64,
124        /// Chunks expected.
125        expected: u64,
126    },
127    /// Validator unstaked.
128    Unstaked,
129    /// Not enough stake.
130    NotEnoughStake {
131        /// Validator's stake.
132        stake: NearToken,
133        /// Minimum threshold.
134        threshold: NearToken,
135    },
136    /// Did not get a seat.
137    DidNotGetASeat,
138    /// Not enough chunk endorsements.
139    NotEnoughChunkEndorsements {
140        /// Endorsements produced.
141        produced: u64,
142        /// Endorsements expected.
143        expected: u64,
144    },
145    /// Protocol version too old.
146    ProtocolVersionTooOld {
147        /// Validator's protocol version.
148        version: u32,
149        /// Network protocol version.
150        network_version: u32,
151    },
152}
153
154// ============================================================================
155// Light client types
156// ============================================================================
157
158/// Light client block view from `next_light_client_block` RPC.
159#[derive(Debug, Clone, Deserialize)]
160pub struct LightClientBlockView {
161    /// Previous block hash.
162    pub prev_block_hash: CryptoHash,
163    /// Next block inner hash.
164    pub next_block_inner_hash: CryptoHash,
165    /// Inner lite header.
166    pub inner_lite: BlockHeaderInnerLiteView,
167    /// Hash of inner rest fields.
168    pub inner_rest_hash: CryptoHash,
169    /// Next epoch block producers (None if unchanged).
170    #[serde(default)]
171    pub next_bps: Option<Vec<ValidatorStakeView>>,
172    /// Approvals after next block.
173    #[serde(default)]
174    pub approvals_after_next: Vec<Option<Signature>>,
175}
176
177/// Light client block lite view.
178#[derive(Debug, Clone, Deserialize)]
179pub struct LightClientBlockLiteView {
180    /// Previous block hash.
181    pub prev_block_hash: CryptoHash,
182    /// Hash of inner rest fields.
183    pub inner_rest_hash: CryptoHash,
184    /// Inner lite header.
185    pub inner_lite: BlockHeaderInnerLiteView,
186}
187
188/// Block header inner lite (for light client proofs).
189#[derive(Debug, Clone, Deserialize)]
190pub struct BlockHeaderInnerLiteView {
191    /// Block height.
192    pub height: u64,
193    /// Epoch ID.
194    pub epoch_id: CryptoHash,
195    /// Next epoch ID.
196    pub next_epoch_id: CryptoHash,
197    /// Previous state root.
198    pub prev_state_root: CryptoHash,
199    /// Outcome root.
200    pub outcome_root: CryptoHash,
201    /// Timestamp (legacy, as u64).
202    pub timestamp: u64,
203    /// Timestamp in nanoseconds.
204    pub timestamp_nanosec: String,
205    /// Next block producers hash.
206    pub next_bp_hash: CryptoHash,
207    /// Block merkle root.
208    pub block_merkle_root: CryptoHash,
209}
210
211// ============================================================================
212// State change types
213// ============================================================================
214
215/// State change with its cause (from `EXPERIMENTAL_changes` RPC).
216#[derive(Debug, Clone, Deserialize)]
217pub struct StateChangeWithCauseView {
218    /// What caused this state change.
219    pub cause: StateChangeCauseView,
220    /// The state change value.
221    pub value: StateChangeValueView,
222}
223
224/// Cause of a state change.
225#[derive(Debug, Clone, Deserialize)]
226#[serde(rename_all = "snake_case", tag = "type")]
227pub enum StateChangeCauseView {
228    /// State not writable to disk.
229    NotWritableToDisk,
230    /// Initial state.
231    InitialState,
232    /// Transaction processing.
233    TransactionProcessing {
234        /// Transaction hash.
235        tx_hash: CryptoHash,
236    },
237    /// Action receipt processing started.
238    ActionReceiptProcessingStarted {
239        /// Receipt hash.
240        receipt_hash: CryptoHash,
241    },
242    /// Action receipt gas reward.
243    ActionReceiptGasReward {
244        /// Receipt hash.
245        receipt_hash: CryptoHash,
246    },
247    /// Receipt processing.
248    ReceiptProcessing {
249        /// Receipt hash.
250        receipt_hash: CryptoHash,
251    },
252    /// Postponed receipt.
253    PostponedReceipt {
254        /// Receipt hash.
255        receipt_hash: CryptoHash,
256    },
257    /// Updated delayed receipts.
258    UpdatedDelayedReceipts,
259    /// Validator accounts update.
260    ValidatorAccountsUpdate,
261    /// State migration.
262    Migration,
263    /// Bandwidth scheduler state update.
264    BandwidthSchedulerStateUpdate,
265}
266
267/// State change value.
268#[derive(Debug, Clone, Deserialize)]
269#[serde(rename_all = "snake_case", tag = "type", content = "change")]
270pub enum StateChangeValueView {
271    /// Account updated.
272    AccountUpdate {
273        /// Account ID.
274        account_id: AccountId,
275        /// Account state fields (flattened: amount, locked, code_hash, etc.).
276        #[serde(flatten)]
277        account: serde_json::Value,
278    },
279    /// Account deleted.
280    AccountDeletion {
281        /// Account ID.
282        account_id: AccountId,
283    },
284    /// Access key updated.
285    AccessKeyUpdate {
286        /// Account ID.
287        account_id: AccountId,
288        /// Public key.
289        public_key: PublicKey,
290        /// New access key.
291        access_key: AccessKeyDetails,
292    },
293    /// Access key deleted.
294    AccessKeyDeletion {
295        /// Account ID.
296        account_id: AccountId,
297        /// Public key.
298        public_key: PublicKey,
299    },
300    /// Gas key nonce updated.
301    GasKeyNonceUpdate {
302        /// Account ID.
303        account_id: AccountId,
304        /// Public key.
305        public_key: PublicKey,
306        /// Nonce index.
307        index: u16,
308        /// Nonce value.
309        nonce: u64,
310    },
311    /// Data updated.
312    DataUpdate {
313        /// Account ID.
314        account_id: AccountId,
315        /// Key (base64-encoded).
316        #[serde(rename = "key_base64")]
317        key: String,
318        /// Value (base64-encoded).
319        #[serde(rename = "value_base64")]
320        value: String,
321    },
322    /// Data deleted.
323    DataDeletion {
324        /// Account ID.
325        account_id: AccountId,
326        /// Key (base64-encoded).
327        #[serde(rename = "key_base64")]
328        key: String,
329    },
330    /// Contract code updated.
331    ContractCodeUpdate {
332        /// Account ID.
333        account_id: AccountId,
334        /// Code (base64-encoded).
335        #[serde(rename = "code_base64")]
336        code: String,
337    },
338    /// Contract code deleted.
339    ContractCodeDeletion {
340        /// Account ID.
341        account_id: AccountId,
342    },
343}
344
345#[cfg(test)]
346mod tests {
347    use super::*;
348
349    #[test]
350    fn test_gas_key_nonce_update_deserialization() {
351        let json = serde_json::json!({
352            "type": "gas_key_nonce_update",
353            "change": {
354                "account_id": "alice.near",
355                "public_key": "ed25519:6E8sCci9badyRkXb3JoRpBj5p8C6Tw41ELDZoiihKEtp",
356                "index": 3,
357                "nonce": 42
358            }
359        });
360        let change: StateChangeValueView = serde_json::from_value(json).unwrap();
361        match change {
362            StateChangeValueView::GasKeyNonceUpdate {
363                account_id,
364                public_key,
365                index,
366                nonce,
367            } => {
368                assert_eq!(account_id.as_str(), "alice.near");
369                assert!(public_key.to_string().starts_with("ed25519:"));
370                assert_eq!(index, 3);
371                assert_eq!(nonce, 42);
372            }
373            _ => panic!("Expected GasKeyNonceUpdate"),
374        }
375    }
376
377    #[test]
378    fn test_state_change_with_cause_deserialization() {
379        let json = serde_json::json!({
380            "cause": {
381                "type": "transaction_processing",
382                "tx_hash": "9FtHUFBQsZ2MG77K3x3MJ9wjX3UT8zE1TczCrhZEcG8U"
383            },
384            "value": {
385                "type": "gas_key_nonce_update",
386                "change": {
387                    "account_id": "alice.near",
388                    "public_key": "ed25519:6E8sCci9badyRkXb3JoRpBj5p8C6Tw41ELDZoiihKEtp",
389                    "index": 0,
390                    "nonce": 100
391                }
392            }
393        });
394        let change: StateChangeWithCauseView = serde_json::from_value(json).unwrap();
395        assert!(matches!(
396            change.value,
397            StateChangeValueView::GasKeyNonceUpdate { .. }
398        ));
399    }
400
401    #[test]
402    fn test_state_change_cause_deserialization() {
403        let json = serde_json::json!({
404            "type": "transaction_processing",
405            "tx_hash": "9FtHUFBQsZ2MG77K3x3MJ9wjX3UT8zE1TczCrhZEcG8U"
406        });
407        let cause: StateChangeCauseView = serde_json::from_value(json).unwrap();
408        assert!(matches!(
409            cause,
410            StateChangeCauseView::TransactionProcessing { .. }
411        ));
412    }
413
414    #[test]
415    fn test_account_update_flattened_deserialization() {
416        let json = serde_json::json!({
417            "type": "account_update",
418            "change": {
419                "account_id": "alice.near",
420                "amount": "1000000000000000000000000",
421                "locked": "0",
422                "code_hash": "11111111111111111111111111111111",
423                "storage_usage": 100,
424                "storage_paid_at": 0
425            }
426        });
427        let change: StateChangeValueView = serde_json::from_value(json).unwrap();
428        match change {
429            StateChangeValueView::AccountUpdate {
430                account_id,
431                account,
432            } => {
433                assert_eq!(account_id.as_str(), "alice.near");
434                assert_eq!(account["amount"], "1000000000000000000000000");
435                assert_eq!(account["storage_usage"], 100);
436            }
437            _ => panic!("expected AccountUpdate"),
438        }
439    }
440
441    #[test]
442    fn test_data_update_base64_field_names() {
443        let json = serde_json::json!({
444            "type": "data_update",
445            "change": {
446                "account_id": "alice.near",
447                "key_base64": "c3RhdGU=",
448                "value_base64": "dGVzdA=="
449            }
450        });
451        let change: StateChangeValueView = serde_json::from_value(json).unwrap();
452        match change {
453            StateChangeValueView::DataUpdate {
454                account_id,
455                key,
456                value,
457            } => {
458                assert_eq!(account_id.as_str(), "alice.near");
459                assert_eq!(key, "c3RhdGU=");
460                assert_eq!(value, "dGVzdA==");
461            }
462            _ => panic!("expected DataUpdate"),
463        }
464    }
465
466    #[test]
467    fn test_data_deletion_base64_field_name() {
468        let json = serde_json::json!({
469            "type": "data_deletion",
470            "change": {
471                "account_id": "alice.near",
472                "key_base64": "c3RhdGU="
473            }
474        });
475        let change: StateChangeValueView = serde_json::from_value(json).unwrap();
476        assert!(matches!(change, StateChangeValueView::DataDeletion { .. }));
477    }
478
479    #[test]
480    fn test_contract_code_update_base64_field_name() {
481        let json = serde_json::json!({
482            "type": "contract_code_update",
483            "change": {
484                "account_id": "alice.near",
485                "code_base64": "AGFzbQEAAAA="
486            }
487        });
488        let change: StateChangeValueView = serde_json::from_value(json).unwrap();
489        match change {
490            StateChangeValueView::ContractCodeUpdate {
491                account_id, code, ..
492            } => {
493                assert_eq!(account_id.as_str(), "alice.near");
494                assert_eq!(code, "AGFzbQEAAAA=");
495            }
496            _ => panic!("expected ContractCodeUpdate"),
497        }
498    }
499}