From 79028ecb6468c77659e218031ae32e3ff4e35f69 Mon Sep 17 00:00:00 2001 From: Tyera Date: Wed, 28 Aug 2024 10:23:49 -0600 Subject: [PATCH] RPC: rewards, return error if epoch_boundary_block is a lie (#2758) * Return error if epoch_boundary_block is not actually the epoch boundary block * Update rpc-client-api/src/custom_error.rs Co-authored-by: Trent Nelson <490004+t-nelson@users.noreply.github.com> --------- Co-authored-by: Trent Nelson <490004+t-nelson@users.noreply.github.com> (cherry picked from commit 9a4b094ded997ccc5f97fe00646912a09314930e) # Conflicts: # rpc-client-api/src/custom_error.rs # rpc/src/rpc.rs --- rpc-client-api/src/custom_error.rs | 35 ++++++++++++++++++++++++++++++ rpc/src/rpc.rs | 32 +++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/rpc-client-api/src/custom_error.rs b/rpc-client-api/src/custom_error.rs index b6175a9230bdcc..f3e3c04f5a3809 100644 --- a/rpc-client-api/src/custom_error.rs +++ b/rpc-client-api/src/custom_error.rs @@ -24,6 +24,11 @@ pub const JSON_RPC_SERVER_ERROR_TRANSACTION_SIGNATURE_LEN_MISMATCH: i64 = -32013 pub const JSON_RPC_SERVER_ERROR_BLOCK_STATUS_NOT_AVAILABLE_YET: i64 = -32014; pub const JSON_RPC_SERVER_ERROR_UNSUPPORTED_TRANSACTION_VERSION: i64 = -32015; pub const JSON_RPC_SERVER_ERROR_MIN_CONTEXT_SLOT_NOT_REACHED: i64 = -32016; +<<<<<<< HEAD +======= +pub const JSON_RPC_SERVER_ERROR_EPOCH_REWARDS_PERIOD_ACTIVE: i64 = -32017; +pub const JSON_RPC_SERVER_ERROR_SLOT_NOT_EPOCH_BOUNDARY: i64 = -32018; +>>>>>>> 9a4b094ded (RPC: rewards, return error if epoch_boundary_block is a lie (#2758)) #[derive(Error, Debug)] pub enum RpcCustomError { @@ -65,6 +70,17 @@ pub enum RpcCustomError { UnsupportedTransactionVersion(u8), #[error("MinContextSlotNotReached")] MinContextSlotNotReached { context_slot: Slot }, +<<<<<<< HEAD +======= + #[error("EpochRewardsPeriodActive")] + EpochRewardsPeriodActive { + slot: Slot, + current_block_height: u64, + rewards_complete_block_height: u64, + }, + #[error("SlotNotEpochBoundary")] + SlotNotEpochBoundary { slot: Slot }, +>>>>>>> 9a4b094ded (RPC: rewards, return error if epoch_boundary_block is a lie (#2758)) } #[derive(Debug, Serialize, Deserialize)] @@ -206,6 +222,25 @@ impl From for Error { context_slot, })), }, +<<<<<<< HEAD +======= + RpcCustomError::EpochRewardsPeriodActive { slot, current_block_height, rewards_complete_block_height } => Self { + code: ErrorCode::ServerError(JSON_RPC_SERVER_ERROR_EPOCH_REWARDS_PERIOD_ACTIVE), + message: format!("Epoch rewards period still active at slot {slot}"), + data: Some(serde_json::json!(EpochRewardsPeriodActiveErrorData { + current_block_height, + rewards_complete_block_height, + })), + }, + RpcCustomError::SlotNotEpochBoundary { slot } => Self { + code: ErrorCode::ServerError(JSON_RPC_SERVER_ERROR_SLOT_NOT_EPOCH_BOUNDARY), + message: format!( + "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, + }, +>>>>>>> 9a4b094ded (RPC: rewards, return error if epoch_boundary_block is a lie (#2758)) } } } diff --git a/rpc/src/rpc.rs b/rpc/src/rpc.rs index b449851b5ea00c..23985c0b278d5e 100644 --- a/rpc/src/rpc.rs +++ b/rpc/src/rpc.rs @@ -575,10 +575,42 @@ impl JsonRpcRequestProcessor { .into()); }; +<<<<<<< HEAD let addresses: Vec = addresses .into_iter() .map(|pubkey| pubkey.to_string()) .collect(); +======= + // If there is a gap in blockstore or long-term historical storage that + // includes the epoch boundary, the `get_blocks_with_limit()` call above + // will return the slot of the block at the end of that gap, not a + // legitimate epoch-boundary block. Therefore, verify that the parent of + // `epoch_boundary_block` occurred before the `first_slot_in_epoch`. If + // it didn't, return an error; it will be impossible to locate + // rewards properly. + if epoch_boundary_block.parent_slot >= first_slot_in_epoch { + return Err(RpcCustomError::SlotNotEpochBoundary { + slot: first_confirmed_block_in_epoch, + } + .into()); + } + + // Collect rewards from first block in the epoch if partitioned epoch + // rewards not enabled, or address is a vote account + let mut reward_map: HashMap = { + let addresses: Vec = + addresses.iter().map(|pubkey| pubkey.to_string()).collect(); + Self::filter_map_rewards( + &epoch_boundary_block.rewards, + first_confirmed_block_in_epoch, + &addresses, + &|reward_type| -> bool { + reward_type == RewardType::Voting + || (!partitioned_epoch_reward_enabled && reward_type == RewardType::Staking) + }, + ) + }; +>>>>>>> 9a4b094ded (RPC: rewards, return error if epoch_boundary_block is a lie (#2758)) let reward_hash: HashMap = first_confirmed_block .rewards