Bläddra i källkod

Add num_partitions to Blockstore rewards (#1601)

* Add num_partitions field to Rewards proto definition

* Add type to hold rewards plus num_partitions

* Add Bank method to get rewards plus num_partitions for recording

* Update Blockstore::write_rewards to use num_partitions

* Update RewardsRecorderService to handle num_partitions

* Populate num_partitions in ReplayStage::record_rewards

* Write num_partitions to Bigtable

* Reword KeyedRewardsAndNumPartitions method

* Clone immediately

* Determine epoch boundary by checking parent epoch

* Rename UiConfirmedBlock field

* nit: fix comment typo

* Add test_get_rewards_and_partitions

* Add pre-activation test

* Add should_record unit test
Tyera 1 år sedan
förälder
incheckning
034cd7396a

+ 3 - 3
core/src/replay_stage.rs

@@ -4393,10 +4393,10 @@ impl ReplayStage {
 
     fn record_rewards(bank: &Bank, rewards_recorder_sender: &Option<RewardsRecorderSender>) {
         if let Some(rewards_recorder_sender) = rewards_recorder_sender {
-            let rewards = bank.rewards.read().unwrap();
-            if !rewards.is_empty() {
+            let rewards = bank.get_rewards_and_num_partitions();
+            if rewards.should_record() {
                 rewards_recorder_sender
-                    .send(RewardsMessage::Batch((bank.slot(), rewards.clone())))
+                    .send(RewardsMessage::Batch((bank.slot(), rewards)))
                     .unwrap_or_else(|err| warn!("rewards_recorder_sender failed: {:?}", err));
             }
             rewards_recorder_sender

+ 18 - 5
core/src/rewards_recorder_service.rs

@@ -1,8 +1,9 @@
 use {
     crossbeam_channel::{Receiver, RecvTimeoutError, Sender},
     solana_ledger::blockstore::Blockstore,
-    solana_sdk::{clock::Slot, pubkey::Pubkey, reward_info::RewardInfo},
-    solana_transaction_status::Reward,
+    solana_runtime::bank::KeyedRewardsAndNumPartitions,
+    solana_sdk::clock::Slot,
+    solana_transaction_status::{Reward, RewardsAndNumPartitions},
     std::{
         sync::{
             atomic::{AtomicBool, AtomicU64, Ordering},
@@ -13,7 +14,7 @@ use {
     },
 };
 
-pub type RewardsBatch = (Slot, Vec<(Pubkey, RewardInfo)>);
+pub type RewardsBatch = (Slot, KeyedRewardsAndNumPartitions);
 pub type RewardsRecorderReceiver = Receiver<RewardsMessage>;
 pub type RewardsRecorderSender = Sender<RewardsMessage>;
 
@@ -55,7 +56,13 @@ impl RewardsRecorderService {
         blockstore: &Blockstore,
     ) -> Result<(), RecvTimeoutError> {
         match rewards_receiver.recv_timeout(Duration::from_secs(1))? {
-            RewardsMessage::Batch((slot, rewards)) => {
+            RewardsMessage::Batch((
+                slot,
+                KeyedRewardsAndNumPartitions {
+                    keyed_rewards: rewards,
+                    num_partitions,
+                },
+            )) => {
                 let rpc_rewards = rewards
                     .into_iter()
                     .map(|(pubkey, reward_info)| Reward {
@@ -68,7 +75,13 @@ impl RewardsRecorderService {
                     .collect();
 
                 blockstore
-                    .write_rewards(slot, rpc_rewards)
+                    .write_rewards(
+                        slot,
+                        RewardsAndNumPartitions {
+                            rewards: rpc_rewards,
+                            num_partitions,
+                        },
+                    )
                     .expect("Expect database write to succeed");
             }
             RewardsMessage::Complete(slot) => {

+ 9 - 4
ledger/src/blockstore.rs

@@ -57,8 +57,9 @@ use {
     solana_storage_proto::{StoredExtendedRewards, StoredTransactionStatusMeta},
     solana_transaction_status::{
         ConfirmedTransactionStatusWithSignature, ConfirmedTransactionWithStatusMeta, Rewards,
-        TransactionStatusMeta, TransactionWithStatusMeta, VersionedConfirmedBlock,
-        VersionedConfirmedBlockWithEntries, VersionedTransactionWithStatusMeta,
+        RewardsAndNumPartitions, TransactionStatusMeta, TransactionWithStatusMeta,
+        VersionedConfirmedBlock, VersionedConfirmedBlockWithEntries,
+        VersionedTransactionWithStatusMeta,
     },
     std::{
         borrow::Cow,
@@ -2678,7 +2679,7 @@ impl Blockstore {
                     Hash::default()
                 };
 
-                let rewards = self
+                let (rewards, num_partitions) = self
                     .rewards_cf
                     .get_protobuf_or_bincode::<StoredExtendedRewards>(slot)?
                     .unwrap_or_default()
@@ -2699,6 +2700,7 @@ impl Blockstore {
                     transactions: self
                         .map_transactions_to_statuses(slot, slot_transaction_iterator)?,
                     rewards,
+                    num_partitions,
                     block_time,
                     block_height,
                 };
@@ -3371,7 +3373,7 @@ impl Blockstore {
             .map(|result| result.map(|option| option.into()))
     }
 
-    pub fn write_rewards(&self, index: Slot, rewards: Rewards) -> Result<()> {
+    pub fn write_rewards(&self, index: Slot, rewards: RewardsAndNumPartitions) -> Result<()> {
         let rewards = rewards.into();
         self.rewards_cf.put_protobuf(index, &rewards)
     }
@@ -8302,6 +8304,7 @@ pub mod tests {
             blockhash: blockhash.to_string(),
             previous_blockhash: Hash::default().to_string(),
             rewards: vec![],
+            num_partitions: None,
             block_time: None,
             block_height: None,
         };
@@ -8316,6 +8319,7 @@ pub mod tests {
             blockhash: blockhash.to_string(),
             previous_blockhash: blockhash.to_string(),
             rewards: vec![],
+            num_partitions: None,
             block_time: None,
             block_height: None,
         };
@@ -8333,6 +8337,7 @@ pub mod tests {
             blockhash: blockhash.to_string(),
             previous_blockhash: blockhash.to_string(),
             rewards: vec![],
+            num_partitions: None,
             block_time: None,
             block_height: None,
         };

+ 1 - 0
rpc-client/src/mock_sender.rs

@@ -406,6 +406,7 @@ impl RpcSender for MockSender {
                     version: Some(TransactionVersion::LEGACY),
                 }],
                 rewards: Rewards::new(),
+                num_partitions: None,
                 block_time: None,
                 block_height: Some(428),
             })?,

+ 3 - 1
runtime/src/bank.rs

@@ -35,7 +35,6 @@
 //! already been signed and verified.
 #[allow(deprecated)]
 use solana_sdk::recent_blockhashes_account;
-pub use solana_sdk::reward_type::RewardType;
 use {
     crate::{
         bank::{
@@ -205,6 +204,9 @@ use {
         time::{Duration, Instant},
     },
 };
+pub use {
+    partitioned_epoch_rewards::KeyedRewardsAndNumPartitions, solana_sdk::reward_type::RewardType,
+};
 #[cfg(feature = "dev-context-only-utils")]
 use {
     solana_accounts_db::accounts_db::{

+ 269 - 1
runtime/src/bank/partitioned_epoch_rewards/mod.rs

@@ -150,7 +150,36 @@ pub(super) struct CalculateRewardsAndDistributeVoteRewardsResult {
 
 pub(crate) type StakeRewards = Vec<StakeReward>;
 
+#[derive(Debug, PartialEq)]
+pub struct KeyedRewardsAndNumPartitions {
+    pub keyed_rewards: Vec<(Pubkey, RewardInfo)>,
+    pub num_partitions: Option<u64>,
+}
+
+impl KeyedRewardsAndNumPartitions {
+    pub fn should_record(&self) -> bool {
+        !self.keyed_rewards.is_empty() || self.num_partitions.is_some()
+    }
+}
+
 impl Bank {
+    pub fn get_rewards_and_num_partitions(&self) -> KeyedRewardsAndNumPartitions {
+        let keyed_rewards = self.rewards.read().unwrap().clone();
+        let epoch_rewards_sysvar = self.get_epoch_rewards_sysvar();
+        // If partitioned epoch rewards are active and this Bank is the
+        // epoch-boundary block, populate num_partitions
+        let epoch_schedule = self.epoch_schedule();
+        let parent_epoch = epoch_schedule.get_epoch(self.parent_slot());
+        let is_first_block_in_epoch = self.epoch() > parent_epoch;
+
+        let num_partitions = (epoch_rewards_sysvar.active && is_first_block_in_epoch)
+            .then_some(epoch_rewards_sysvar.num_partitions);
+        KeyedRewardsAndNumPartitions {
+            keyed_rewards,
+            num_partitions,
+        }
+    }
+
     pub(super) fn is_partitioned_rewards_feature_enabled(&self) -> bool {
         self.feature_set
             .is_active(&feature_set::enable_partitioned_epoch_reward::id())
@@ -248,6 +277,7 @@ mod tests {
             account::Account,
             epoch_schedule::EpochSchedule,
             native_token::LAMPORTS_PER_SOL,
+            reward_type::RewardType,
             signature::Signer,
             signer::keypair::Keypair,
             stake::instruction::StakeError,
@@ -684,7 +714,7 @@ mod tests {
 
     /// Test that program execution that attempts to mutate a stake account
     /// incorrectly should fail during reward period. A credit should succeed,
-    /// but a withdrawal shoudl fail.
+    /// but a withdrawal should fail.
     #[test]
     fn test_program_execution_restricted_for_stake_account_in_reward_period() {
         use solana_sdk::transaction::TransactionError::InstructionError;
@@ -800,4 +830,242 @@ mod tests {
             previous_bank = bank;
         }
     }
+
+    #[test]
+    fn test_get_rewards_and_partitions() {
+        let starting_slot = SLOTS_PER_EPOCH - 1;
+        let num_rewards = 100;
+        let stake_account_stores_per_block = 50;
+        let RewardBank { bank, .. } =
+            create_reward_bank(num_rewards, stake_account_stores_per_block, starting_slot);
+
+        assert!(bank.is_partitioned_rewards_feature_enabled());
+        // Slot before the epoch boundary contains empty rewards (since fees are
+        // off), and no partitions because not at the epoch boundary
+        assert_eq!(
+            bank.get_rewards_and_num_partitions(),
+            KeyedRewardsAndNumPartitions {
+                keyed_rewards: vec![],
+                num_partitions: None,
+            }
+        );
+
+        let epoch_boundary_bank = Arc::new(Bank::new_from_parent(
+            bank,
+            &Pubkey::default(),
+            SLOTS_PER_EPOCH,
+        ));
+        assert!(epoch_boundary_bank.is_partitioned_rewards_feature_enabled());
+        // Slot at the epoch boundary contains voting rewards only, as well as partition data
+        let KeyedRewardsAndNumPartitions {
+            keyed_rewards,
+            num_partitions,
+        } = epoch_boundary_bank.get_rewards_and_num_partitions();
+        for (_pubkey, reward) in keyed_rewards.iter() {
+            assert_eq!(reward.reward_type, RewardType::Voting);
+        }
+        assert_eq!(keyed_rewards.len(), num_rewards);
+        assert_eq!(
+            num_partitions,
+            Some(num_rewards as u64 / stake_account_stores_per_block)
+        );
+
+        let mut total_staking_rewards = 0;
+
+        let partition0_bank = Arc::new(Bank::new_from_parent(
+            epoch_boundary_bank,
+            &Pubkey::default(),
+            SLOTS_PER_EPOCH + 1,
+        ));
+        assert!(partition0_bank.is_partitioned_rewards_feature_enabled());
+        // Slot after the epoch boundary contains first partition of staking
+        // rewards, and no partitions because not at the epoch boundary
+        let KeyedRewardsAndNumPartitions {
+            keyed_rewards,
+            num_partitions,
+        } = partition0_bank.get_rewards_and_num_partitions();
+        for (_pubkey, reward) in keyed_rewards.iter() {
+            assert_eq!(reward.reward_type, RewardType::Staking);
+        }
+        total_staking_rewards += keyed_rewards.len();
+        assert_eq!(num_partitions, None);
+
+        let partition1_bank = Arc::new(Bank::new_from_parent(
+            partition0_bank,
+            &Pubkey::default(),
+            SLOTS_PER_EPOCH + 2,
+        ));
+        assert!(partition1_bank.is_partitioned_rewards_feature_enabled());
+        // Slot 2 after the epoch boundary contains second partition of staking
+        // rewards, and no partitions because not at the epoch boundary
+        let KeyedRewardsAndNumPartitions {
+            keyed_rewards,
+            num_partitions,
+        } = partition1_bank.get_rewards_and_num_partitions();
+        for (_pubkey, reward) in keyed_rewards.iter() {
+            assert_eq!(reward.reward_type, RewardType::Staking);
+        }
+        total_staking_rewards += keyed_rewards.len();
+        assert_eq!(num_partitions, None);
+
+        // All rewards are recorded
+        assert_eq!(total_staking_rewards, num_rewards);
+
+        let bank = Bank::new_from_parent(partition1_bank, &Pubkey::default(), SLOTS_PER_EPOCH + 3);
+        assert!(bank.is_partitioned_rewards_feature_enabled());
+        // Next slot contains empty rewards (since fees are off), and no
+        // partitions because not at the epoch boundary
+        assert_eq!(
+            bank.get_rewards_and_num_partitions(),
+            KeyedRewardsAndNumPartitions {
+                keyed_rewards: vec![],
+                num_partitions: None,
+            }
+        );
+    }
+
+    #[test]
+    fn test_get_rewards_and_partitions_before_feature() {
+        let starting_slot = SLOTS_PER_EPOCH - 1;
+        let num_rewards = 100;
+
+        let validator_keypairs = (0..num_rewards)
+            .map(|_| ValidatorVoteKeypairs::new_rand())
+            .collect::<Vec<_>>();
+
+        let GenesisConfigInfo {
+            mut genesis_config, ..
+        } = create_genesis_config_with_vote_accounts(
+            1_000_000_000,
+            &validator_keypairs,
+            vec![2_000_000_000; num_rewards],
+        );
+        genesis_config.epoch_schedule = EpochSchedule::new(SLOTS_PER_EPOCH);
+
+        // Set feature to inactive
+        genesis_config
+            .accounts
+            .remove(&feature_set::enable_partitioned_epoch_reward::id());
+
+        let bank = Bank::new_for_tests(&genesis_config);
+
+        for validator_vote_keypairs in &validator_keypairs {
+            let vote_id = validator_vote_keypairs.vote_keypair.pubkey();
+            let mut vote_account = bank.get_account(&vote_id).unwrap();
+            // generate some rewards
+            let mut vote_state = Some(vote_state::from(&vote_account).unwrap());
+            for i in 0..MAX_LOCKOUT_HISTORY + 42 {
+                if let Some(v) = vote_state.as_mut() {
+                    vote_state::process_slot_vote_unchecked(v, i as u64)
+                }
+                let versioned = VoteStateVersions::Current(Box::new(vote_state.take().unwrap()));
+                vote_state::to(&versioned, &mut vote_account).unwrap();
+                match versioned {
+                    VoteStateVersions::Current(v) => {
+                        vote_state = Some(*v);
+                    }
+                    _ => panic!("Has to be of type Current"),
+                };
+            }
+            bank.store_account_and_update_capitalization(&vote_id, &vote_account);
+        }
+
+        let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests();
+        let bank = new_bank_from_parent_with_bank_forks(
+            &bank_forks,
+            bank,
+            &Pubkey::default(),
+            starting_slot,
+        );
+
+        assert!(!bank.is_partitioned_rewards_feature_enabled());
+        // Slot before the epoch boundary contains empty rewards (since fees are
+        // off), and no partitions because feature is inactive
+        assert_eq!(
+            bank.get_rewards_and_num_partitions(),
+            KeyedRewardsAndNumPartitions {
+                keyed_rewards: vec![],
+                num_partitions: None,
+            }
+        );
+
+        let epoch_boundary_bank = Arc::new(Bank::new_from_parent(
+            bank,
+            &Pubkey::default(),
+            SLOTS_PER_EPOCH,
+        ));
+        assert!(!epoch_boundary_bank.is_partitioned_rewards_feature_enabled());
+        // Slot at the epoch boundary contains voting rewards and staking rewards; still no partitions
+        let KeyedRewardsAndNumPartitions {
+            keyed_rewards,
+            num_partitions,
+        } = epoch_boundary_bank.get_rewards_and_num_partitions();
+        let mut voting_rewards_count = 0;
+        let mut staking_rewards_count = 0;
+        for (_pubkey, reward) in keyed_rewards.iter() {
+            match reward.reward_type {
+                RewardType::Voting => {
+                    voting_rewards_count += 1;
+                }
+                RewardType::Staking => {
+                    staking_rewards_count += 1;
+                }
+                _ => {}
+            }
+        }
+        assert_eq!(
+            keyed_rewards.len(),
+            voting_rewards_count + staking_rewards_count
+        );
+        assert_eq!(voting_rewards_count, num_rewards);
+        assert_eq!(staking_rewards_count, num_rewards);
+        assert!(num_partitions.is_none());
+
+        let bank =
+            Bank::new_from_parent(epoch_boundary_bank, &Pubkey::default(), SLOTS_PER_EPOCH + 1);
+        assert!(!bank.is_partitioned_rewards_feature_enabled());
+        // Slot after the epoch boundary contains empty rewards (since fees are
+        // off), and no partitions because feature is inactive
+        assert_eq!(
+            bank.get_rewards_and_num_partitions(),
+            KeyedRewardsAndNumPartitions {
+                keyed_rewards: vec![],
+                num_partitions: None,
+            }
+        );
+    }
+
+    #[test]
+    fn test_rewards_and_partitions_should_record() {
+        let reward = RewardInfo {
+            reward_type: RewardType::Voting,
+            lamports: 55,
+            post_balance: 5555,
+            commission: Some(5),
+        };
+
+        let rewards_and_partitions = KeyedRewardsAndNumPartitions {
+            keyed_rewards: vec![],
+            num_partitions: None,
+        };
+        assert!(!rewards_and_partitions.should_record());
+
+        let rewards_and_partitions = KeyedRewardsAndNumPartitions {
+            keyed_rewards: vec![(Pubkey::new_unique(), reward)],
+            num_partitions: None,
+        };
+        assert!(rewards_and_partitions.should_record());
+
+        let rewards_and_partitions = KeyedRewardsAndNumPartitions {
+            keyed_rewards: vec![],
+            num_partitions: Some(42),
+        };
+        assert!(rewards_and_partitions.should_record());
+
+        let rewards_and_partitions = KeyedRewardsAndNumPartitions {
+            keyed_rewards: vec![(Pubkey::new_unique(), reward)],
+            num_partitions: Some(42),
+        };
+        assert!(rewards_and_partitions.should_record());
+    }
 }

+ 4 - 0
storage-bigtable/src/bigtable.rs

@@ -985,6 +985,7 @@ mod tests {
             parent_slot,
             transactions,
             rewards,
+            num_partitions,
             block_time,
             block_height,
         } = confirmed_block;
@@ -995,6 +996,8 @@ mod tests {
             parent_slot,
             transactions: transactions.into_iter().map(|tx| tx.into()).collect(),
             rewards: rewards.into_iter().map(|r| r.into()).collect(),
+            num_partitions: num_partitions
+                .map(|num_partitions| generated::NumPartitions { num_partitions }),
             block_time: block_time.map(|timestamp| generated::UnixTimestamp { timestamp }),
             block_height: block_height.map(|block_height| generated::BlockHeight { block_height }),
         }
@@ -1028,6 +1031,7 @@ mod tests {
             blockhash: Hash::default().to_string(),
             previous_blockhash: Hash::default().to_string(),
             rewards: vec![],
+            num_partitions: None,
             block_time: Some(1_234_567_890),
             block_height: Some(1),
         };

+ 2 - 0
storage-bigtable/src/lib.rs

@@ -141,6 +141,7 @@ impl From<ConfirmedBlock> for StoredConfirmedBlock {
             parent_slot,
             transactions,
             rewards,
+            num_partitions: _num_partitions,
             block_time,
             block_height,
         } = confirmed_block;
@@ -175,6 +176,7 @@ impl From<StoredConfirmedBlock> for ConfirmedBlock {
             parent_slot,
             transactions: transactions.into_iter().map(|tx| tx.into()).collect(),
             rewards: rewards.into_iter().map(|reward| reward.into()).collect(),
+            num_partitions: None,
             block_time,
             block_height,
         }

+ 6 - 0
storage-proto/proto/confirmed_block.proto

@@ -10,6 +10,7 @@ message ConfirmedBlock {
     repeated Reward rewards = 5;
     UnixTimestamp block_time = 6;
     BlockHeight block_height = 7;
+    NumPartitions num_partitions = 8;
 }
 
 message ConfirmedTransaction {
@@ -130,6 +131,7 @@ message Reward {
 
 message Rewards {
   repeated Reward rewards = 1;
+  NumPartitions num_partitions = 2;
 }
 
 message UnixTimestamp {
@@ -139,3 +141,7 @@ message UnixTimestamp {
 message BlockHeight {
     uint64 block_height = 1;
 }
+
+message NumPartitions {
+    uint64 num_partitions = 1;
+}

+ 36 - 2
storage-proto/src/convert.rs

@@ -16,8 +16,9 @@ use {
     },
     solana_transaction_status::{
         ConfirmedBlock, EntrySummary, InnerInstruction, InnerInstructions, Reward, RewardType,
-        TransactionByAddrInfo, TransactionStatusMeta, TransactionTokenBalance,
-        TransactionWithStatusMeta, VersionedConfirmedBlock, VersionedTransactionWithStatusMeta,
+        RewardsAndNumPartitions, TransactionByAddrInfo, TransactionStatusMeta,
+        TransactionTokenBalance, TransactionWithStatusMeta, VersionedConfirmedBlock,
+        VersionedTransactionWithStatusMeta,
     },
     std::{
         convert::{TryFrom, TryInto},
@@ -47,6 +48,16 @@ impl From<Vec<Reward>> for generated::Rewards {
     fn from(rewards: Vec<Reward>) -> Self {
         Self {
             rewards: rewards.into_iter().map(|r| r.into()).collect(),
+            num_partitions: None,
+        }
+    }
+}
+
+impl From<RewardsAndNumPartitions> for generated::Rewards {
+    fn from(input: RewardsAndNumPartitions) -> Self {
+        Self {
+            rewards: input.rewards.into_iter().map(|r| r.into()).collect(),
+            num_partitions: input.num_partitions.map(|n| n.into()),
         }
     }
 }
@@ -57,6 +68,17 @@ impl From<generated::Rewards> for Vec<Reward> {
     }
 }
 
+impl From<generated::Rewards> for (Vec<Reward>, Option<u64>) {
+    fn from(rewards: generated::Rewards) -> Self {
+        (
+            rewards.rewards.into_iter().map(|r| r.into()).collect(),
+            rewards
+                .num_partitions
+                .map(|generated::NumPartitions { num_partitions }| num_partitions),
+        )
+    }
+}
+
 impl From<StoredExtendedRewards> for generated::Rewards {
     fn from(rewards: StoredExtendedRewards) -> Self {
         Self {
@@ -67,6 +89,7 @@ impl From<StoredExtendedRewards> for generated::Rewards {
                     r.into()
                 })
                 .collect(),
+            num_partitions: None,
         }
     }
 }
@@ -121,6 +144,12 @@ impl From<generated::Reward> for Reward {
     }
 }
 
+impl From<u64> for generated::NumPartitions {
+    fn from(num_partitions: u64) -> Self {
+        Self { num_partitions }
+    }
+}
+
 impl From<VersionedConfirmedBlock> for generated::ConfirmedBlock {
     fn from(confirmed_block: VersionedConfirmedBlock) -> Self {
         let VersionedConfirmedBlock {
@@ -129,6 +158,7 @@ impl From<VersionedConfirmedBlock> for generated::ConfirmedBlock {
             parent_slot,
             transactions,
             rewards,
+            num_partitions,
             block_time,
             block_height,
         } = confirmed_block;
@@ -139,6 +169,7 @@ impl From<VersionedConfirmedBlock> for generated::ConfirmedBlock {
             parent_slot,
             transactions: transactions.into_iter().map(|tx| tx.into()).collect(),
             rewards: rewards.into_iter().map(|r| r.into()).collect(),
+            num_partitions: num_partitions.map(Into::into),
             block_time: block_time.map(|timestamp| generated::UnixTimestamp { timestamp }),
             block_height: block_height.map(|block_height| generated::BlockHeight { block_height }),
         }
@@ -156,6 +187,7 @@ impl TryFrom<generated::ConfirmedBlock> for ConfirmedBlock {
             parent_slot,
             transactions,
             rewards,
+            num_partitions,
             block_time,
             block_height,
         } = confirmed_block;
@@ -169,6 +201,8 @@ impl TryFrom<generated::ConfirmedBlock> for ConfirmedBlock {
                 .map(|tx| tx.try_into())
                 .collect::<std::result::Result<Vec<_>, Self::Error>>()?,
             rewards: rewards.into_iter().map(|r| r.into()).collect(),
+            num_partitions: num_partitions
+                .map(|generated::NumPartitions { num_partitions }| num_partitions),
             block_time: block_time.map(|generated::UnixTimestamp { timestamp }| timestamp),
             block_height: block_height.map(|generated::BlockHeight { block_height }| block_height),
         })

+ 14 - 0
transaction-status/src/lib.rs

@@ -628,6 +628,11 @@ pub struct Reward {
 
 pub type Rewards = Vec<Reward>;
 
+pub struct RewardsAndNumPartitions {
+    pub rewards: Rewards,
+    pub num_partitions: Option<u64>,
+}
+
 #[derive(Debug, Error)]
 pub enum ConvertBlockError {
     #[error("transactions missing after converted, before: {0}, after: {1}")]
@@ -641,6 +646,7 @@ pub struct ConfirmedBlock {
     pub parent_slot: Slot,
     pub transactions: Vec<TransactionWithStatusMeta>,
     pub rewards: Rewards,
+    pub num_partitions: Option<u64>,
     pub block_time: Option<UnixTimestamp>,
     pub block_height: Option<u64>,
 }
@@ -654,6 +660,7 @@ pub struct VersionedConfirmedBlock {
     pub parent_slot: Slot,
     pub transactions: Vec<VersionedTransactionWithStatusMeta>,
     pub rewards: Rewards,
+    pub num_partitions: Option<u64>,
     pub block_time: Option<UnixTimestamp>,
     pub block_height: Option<u64>,
 }
@@ -670,6 +677,7 @@ impl From<VersionedConfirmedBlock> for ConfirmedBlock {
                 .map(TransactionWithStatusMeta::Complete)
                 .collect(),
             rewards: block.rewards,
+            num_partitions: block.num_partitions,
             block_time: block.block_time,
             block_height: block.block_height,
         }
@@ -704,6 +712,7 @@ impl TryFrom<ConfirmedBlock> for VersionedConfirmedBlock {
             parent_slot: block.parent_slot,
             transactions: txs,
             rewards: block.rewards,
+            num_partitions: block.num_partitions,
             block_time: block.block_time,
             block_height: block.block_height,
         })
@@ -768,6 +777,7 @@ impl ConfirmedBlock {
             } else {
                 None
             },
+            num_reward_partitions: self.num_partitions,
             block_time: self.block_time,
             block_height: self.block_height,
         })
@@ -782,6 +792,7 @@ pub struct EncodedConfirmedBlock {
     pub parent_slot: Slot,
     pub transactions: Vec<EncodedTransactionWithStatusMeta>,
     pub rewards: Rewards,
+    pub num_partitions: Option<u64>,
     pub block_time: Option<UnixTimestamp>,
     pub block_height: Option<u64>,
 }
@@ -794,6 +805,7 @@ impl From<UiConfirmedBlock> for EncodedConfirmedBlock {
             parent_slot: block.parent_slot,
             transactions: block.transactions.unwrap_or_default(),
             rewards: block.rewards.unwrap_or_default(),
+            num_partitions: block.num_reward_partitions,
             block_time: block.block_time,
             block_height: block.block_height,
         }
@@ -812,6 +824,8 @@ pub struct UiConfirmedBlock {
     pub signatures: Option<Vec<String>>,
     #[serde(default, skip_serializing_if = "Option::is_none")]
     pub rewards: Option<Rewards>,
+    #[serde(default, skip_serializing_if = "Option::is_none")]
+    pub num_reward_partitions: Option<u64>,
     pub block_time: Option<UnixTimestamp>,
     pub block_height: Option<u64>,
 }