diff --git a/CHANGELOG.md b/CHANGELOG.md index cffe0b8056b29f..9562034a89822d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,10 @@ Release channels have their own copy of this changelog: ### RPC +#### Breaking +* Added a `slot` property to `EpochRewardsPeriodActiveErrorData` +* Added error data containing a `slot` property to `RpcCustomError::SlotNotEpochBoundary` + #### Changes * The subscription server now prioritizes processing received messages before sending out responses. This ensures that new subscription requests and time-sensitive messages like `PING` opcodes take priority over notifications. diff --git a/Cargo.lock b/Cargo.lock index d84bffa971e238..0a9e3717ec44dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -10055,6 +10055,7 @@ dependencies = [ "solana-signer", "solana-transaction-error", "solana-transaction-status-client-types", + "test-case", "thiserror 2.0.12", ] diff --git a/rpc-client-api/Cargo.toml b/rpc-client-api/Cargo.toml index 0ba5f76a88aac7..b1e5692555e256 100644 --- a/rpc-client-api/Cargo.toml +++ b/rpc-client-api/Cargo.toml @@ -27,3 +27,6 @@ solana-signer = { workspace = true } solana-transaction-error = { workspace = true } solana-transaction-status-client-types = { workspace = true } thiserror = { workspace = true } + +[dev-dependencies] +test-case = { workspace = true } diff --git a/rpc-client-api/src/custom_error.rs b/rpc-client-api/src/custom_error.rs index 57381f2676fea3..cd1449ae6c8664 100644 --- a/rpc-client-api/src/custom_error.rs +++ b/rpc-client-api/src/custom_error.rs @@ -92,11 +92,13 @@ pub struct MinContextSlotNotReachedErrorData { pub context_slot: Slot, } +#[cfg_attr(test, derive(PartialEq))] #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] pub struct EpochRewardsPeriodActiveErrorData { pub current_block_height: u64, pub rewards_complete_block_height: u64, + pub slot: Option, } impl From for RpcCustomError { @@ -237,6 +239,7 @@ impl From for Error { data: Some(serde_json::json!(EpochRewardsPeriodActiveErrorData { current_block_height, rewards_complete_block_height, + slot: Some(slot), })), }, RpcCustomError::SlotNotEpochBoundary { slot } => Self { @@ -245,7 +248,9 @@ impl From for Error { "Rewards cannot be found because slot {slot} is not the epoch boundary. This \ may be due to gap in the queried node's local ledger or long-term storage" ), - data: None, + data: Some(serde_json::json!({ + "slot": slot, + })), }, RpcCustomError::LongTermStorageUnreachable => Self { code: ErrorCode::ServerError(JSON_RPC_SERVER_ERROR_LONG_TERM_STORAGE_UNREACHABLE), @@ -255,3 +260,42 @@ impl From for Error { } } } + +#[cfg(test)] +mod tests { + use { + crate::custom_error::EpochRewardsPeriodActiveErrorData, serde_json::Value, + test_case::test_case, + }; + + #[test_case(serde_json::json!({ + "currentBlockHeight": 123, + "rewardsCompleteBlockHeight": 456 + }); "Pre-3.0 schema")] + #[test_case(serde_json::json!({ + "currentBlockHeight": 123, + "rewardsCompleteBlockHeight": 456, + "slot": 789 + }); "3.0+ schema")] + fn test_deseriailze_epoch_rewards_period_active_error_data(serialized_data: Value) { + let expected_current_block_height = serialized_data + .get("currentBlockHeight") + .map(|v| v.as_u64().unwrap()) + .unwrap(); + let expected_rewards_complete_block_height = serialized_data + .get("rewardsCompleteBlockHeight") + .map(|v| v.as_u64().unwrap()) + .unwrap(); + let expected_slot: Option = serialized_data.get("slot").map(|v| v.as_u64().unwrap()); + let actual: EpochRewardsPeriodActiveErrorData = + serde_json::from_value(serialized_data).expect("Failed to deserialize test fixture"); + assert_eq!( + actual, + EpochRewardsPeriodActiveErrorData { + current_block_height: expected_current_block_height, + rewards_complete_block_height: expected_rewards_complete_block_height, + slot: expected_slot, + } + ); + } +}