Skip to content

Commit

Permalink
runtime: use leader schedule epoch to serve sol_get_epoch_stake (#3279
Browse files Browse the repository at this point in the history
)
  • Loading branch information
buffalojoec committed Oct 25, 2024
1 parent 2788c95 commit 15ff731
Show file tree
Hide file tree
Showing 7 changed files with 435 additions and 2 deletions.
9 changes: 9 additions & 0 deletions programs/sbf/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

45 changes: 45 additions & 0 deletions programs/sbf/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ rustversion = "1.0.14"
serde = "1.0.112" # must match the serde_derive version, see https://github.com/serde-rs/serde/issues/2584#issuecomment-1685252251
serde_derive = "1.0.112" # must match the serde version, see https://github.com/serde-rs/serde/issues/2584#issuecomment-1685252251
serde_json = "1.0.56"
<<<<<<< HEAD
solana-account-decoder = { path = "../../account-decoder", version = "=2.0.15" }
solana-accounts-db = { path = "../../accounts-db", version = "=2.0.15" }
solana-bpf-loader-program = { path = "../bpf_loader", version = "=2.0.15" }
Expand Down Expand Up @@ -54,6 +55,47 @@ solana-type-overrides = { path = "../../type-overrides", version = "=2.0.15" }
agave-validator = { path = "../../validator", version = "=2.0.15" }
solana-zk-token-sdk = { path = "../../zk-token-sdk", version = "=2.0.15" }
solana_rbpf = "=0.8.4"
=======
solana-account-decoder = { path = "../../account-decoder", version = "=2.1.0" }
solana-accounts-db = { path = "../../accounts-db", version = "=2.1.0" }
solana-bn254 = { path = "../../curves/bn254", version = "=2.1.0" }
solana-bpf-loader-program = { path = "../bpf_loader", version = "=2.1.0" }
solana-cli-output = { path = "../../cli-output", version = "=2.1.0" }
solana-compute-budget = { path = "../../compute-budget", version = "=2.1.0" }
solana-curve25519 = { path = "../../curves/curve25519", version = "=2.1.0" }
solana-decode-error = { path = "../../sdk/decode-error", version = "=2.1.0" }
solana-feature-set = { path = "../../sdk/feature-set", version = "=2.1.0" }
solana-fee = { path = "../../fee", version = "=2.1.0" }
solana-ledger = { path = "../../ledger", version = "=2.1.0" }
solana-log-collector = { path = "../../log-collector", version = "=2.1.0" }
solana-logger = { path = "../../logger", version = "=2.1.0" }
solana-measure = { path = "../../measure", version = "=2.1.0" }
solana-poseidon = { path = "../../poseidon/", version = "=2.1.0" }
solana-program = { path = "../../sdk/program", version = "=2.1.0" }
solana-program-runtime = { path = "../../program-runtime", version = "=2.1.0" }
solana-runtime = { path = "../../runtime", version = "=2.1.0" }
solana-runtime-transaction = { path = "../../runtime-transaction", version = "=2.1.0" }
solana-sbf-rust-128bit-dep = { path = "rust/128bit_dep", version = "=2.1.0" }
solana-sbf-rust-invoke-dep = { path = "rust/invoke_dep", version = "=2.1.0" }
solana-sbf-rust-invoked-dep = { path = "rust/invoked_dep", version = "=2.1.0" }
solana-sbf-rust-many-args-dep = { path = "rust/many_args_dep", version = "=2.1.0" }
solana-sbf-rust-mem-dep = { path = "rust/mem_dep", version = "=2.1.0" }
solana-sbf-rust-param-passing-dep = { path = "rust/param_passing_dep", version = "=2.1.0" }
solana-sbf-rust-realloc-dep = { path = "rust/realloc_dep", version = "=2.1.0" }
solana-sbf-rust-realloc-invoke-dep = { path = "rust/realloc_invoke_dep", version = "=2.1.0" }
solana-sdk = { path = "../../sdk", version = "=2.1.0" }
solana-secp256k1-recover = { path = "../../curves/secp256k1-recover", version = "=2.1.0" }
solana-svm = { path = "../../svm", version = "=2.1.0" }
solana-svm-transaction = { path = "../../svm-transaction", version = "=2.1.0" }
solana-timings = { path = "../../timings", version = "=2.1.0" }
solana-transaction-status = { path = "../../transaction-status", version = "=2.1.0" }
solana-type-overrides = { path = "../../type-overrides", version = "=2.1.0" }
solana-vote = { path = "../../vote", version = "=2.1.0" }
solana-vote-program = { path = "../../programs/vote", version = "=2.1.0" }
agave-validator = { path = "../../validator", version = "=2.1.0" }
solana-zk-token-sdk = { path = "../../zk-token-sdk", version = "=2.1.0" }
solana_rbpf = "=0.8.5"
>>>>>>> aa84789bf1 (runtime: use leader schedule epoch to serve `sol_get_epoch_stake` (#3279))
thiserror = "1.0"

[package]
Expand Down Expand Up @@ -106,6 +148,8 @@ solana-sdk = { workspace = true, features = ["dev-context-only-utils"] }
solana-svm = { workspace = true }
solana-transaction-status = { workspace = true }
solana-type-overrides = { workspace = true }
solana-vote = { workspace = true }
solana-vote-program = { workspace = true }
solana_rbpf = { workspace = true }

[[bench]]
Expand Down Expand Up @@ -163,6 +207,7 @@ members = [
"rust/simulation",
"rust/spoof1",
"rust/spoof1_system",
"rust/syscall-get-epoch-stake",
"rust/sysvar",
"rust/upgradeable",
"rust/upgraded",
Expand Down
15 changes: 15 additions & 0 deletions programs/sbf/rust/syscall-get-epoch-stake/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[package]
name = "solana-sbf-syscall-get-epoch-stake"
version = { workspace = true }
description = { workspace = true }
authors = { workspace = true }
repository = { workspace = true }
homepage = { workspace = true }
license = { workspace = true }
edition = { workspace = true }

[dependencies]
solana-program = { workspace = true }

[lib]
crate-type = ["cdylib"]
39 changes: 39 additions & 0 deletions programs/sbf/rust/syscall-get-epoch-stake/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
//! Example Rust-based SBF program that tests the `sol_get_epoch_stake`
//! syscall.

extern crate solana_program;
use solana_program::{
account_info::AccountInfo,
entrypoint::ProgramResult,
epoch_stake::{get_epoch_stake_for_vote_account, get_epoch_total_stake},
msg,
program::set_return_data,
pubkey::Pubkey,
};

solana_program::entrypoint_no_alloc!(process_instruction);
pub fn process_instruction(
_program_id: &Pubkey,
accounts: &[AccountInfo],
_instruction_data: &[u8],
) -> ProgramResult {
// Total stake.
let total_stake = get_epoch_total_stake();
assert_ne!(total_stake, 0);
msg!("Total Stake: {}", total_stake);

// Vote accounts.
let check_vote_account_stake = |i: usize| {
let vote_address = accounts[i].key;
let vote_stake = get_epoch_stake_for_vote_account(vote_address);
assert_ne!(vote_stake, 0);
msg!("Vote Stake for account {}: {}", i, vote_stake);
};
check_vote_account_stake(0);
check_vote_account_stake(1);

// For good measure, set the return data to total stake.
set_return_data(&total_stake.to_le_bytes());

Ok(())
}
102 changes: 102 additions & 0 deletions programs/sbf/tests/syscall_get_epoch_stake.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
#![cfg(feature = "sbf_rust")]

use {
solana_runtime::{
bank::Bank,
bank_client::BankClient,
epoch_stakes::EpochStakes,
genesis_utils::{
create_genesis_config_with_vote_accounts, GenesisConfigInfo, ValidatorVoteKeypairs,
},
loader_utils::load_upgradeable_program_and_advance_slot,
},
solana_sdk::{
instruction::{AccountMeta, Instruction},
message::Message,
signature::{Keypair, Signer},
transaction::{SanitizedTransaction, Transaction},
},
solana_vote::vote_account::VoteAccount,
solana_vote_program::vote_state::create_account_with_authorized,
std::collections::HashMap,
};

#[test]
fn test_syscall_get_epoch_stake() {
solana_logger::setup();

// Two vote accounts with stake.
let stakes = vec![100_000_000, 500_000_000];
let voting_keypairs = vec![
ValidatorVoteKeypairs::new_rand(),
ValidatorVoteKeypairs::new_rand(),
];
let total_stake: u64 = stakes.iter().sum();

let GenesisConfigInfo {
genesis_config,
mint_keypair,
..
} = create_genesis_config_with_vote_accounts(1_000_000_000, &voting_keypairs, stakes.clone());

let mut bank = Bank::new_for_tests(&genesis_config);

// Intentionally overwrite the bank epoch with no stake, to ensure the
// syscall gets the _current_ epoch stake based on the leader schedule
// (N + 1).
let epoch_stakes_epoch_0 = EpochStakes::new_for_tests(
voting_keypairs
.iter()
.map(|keypair| {
let node_id = keypair.node_keypair.pubkey();
let authorized_voter = keypair.vote_keypair.pubkey();
let vote_account = VoteAccount::try_from(create_account_with_authorized(
&node_id,
&authorized_voter,
&node_id,
0,
100,
))
.unwrap();
(authorized_voter, (0, vote_account)) // No stake.
})
.collect::<HashMap<_, _>>(),
0, // Leader schedule epoch 0
);
bank.set_epoch_stakes_for_test(0, epoch_stakes_epoch_0);

let (bank, bank_forks) = bank.wrap_with_bank_forks_for_tests();
let mut bank_client = BankClient::new_shared(bank);

let authority_keypair = Keypair::new();
let (bank, program_id) = load_upgradeable_program_and_advance_slot(
&mut bank_client,
bank_forks.as_ref(),
&mint_keypair,
&authority_keypair,
"solana_sbf_syscall_get_epoch_stake",
);
bank.freeze();

let instruction = Instruction::new_with_bytes(
program_id,
&[],
vec![
AccountMeta::new_readonly(voting_keypairs[0].vote_keypair.pubkey(), false),
AccountMeta::new_readonly(voting_keypairs[1].vote_keypair.pubkey(), false),
],
);

let blockhash = bank.last_blockhash();
let message = Message::new(&[instruction], Some(&mint_keypair.pubkey()));
let transaction = Transaction::new(&[&mint_keypair], message, blockhash);
let sanitized_tx = SanitizedTransaction::from_transaction_for_tests(transaction);

let result = bank.simulate_transaction(&sanitized_tx, false);

assert!(result.result.is_ok());

let return_data_le_bytes: [u8; 8] = result.return_data.unwrap().data[0..8].try_into().unwrap();
let total_stake_from_return_data = u64::from_le_bytes(return_data_le_bytes);
assert_eq!(total_stake_from_return_data, total_stake);
}
18 changes: 16 additions & 2 deletions runtime/src/bank.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3677,8 +3677,8 @@ impl Bank {
let (blockhash, lamports_per_signature) = self.last_blockhash_and_lamports_per_signature();
let processing_environment = TransactionProcessingEnvironment {
blockhash,
epoch_total_stake: self.epoch_total_stake(self.epoch()),
epoch_vote_accounts: self.epoch_vote_accounts(self.epoch()),
epoch_total_stake: Some(self.get_current_epoch_total_stake()),
epoch_vote_accounts: Some(self.get_current_epoch_vote_accounts()),
feature_set: Arc::clone(&self.feature_set),
fee_structure: Some(&self.fee_structure),
lamports_per_signature,
Expand Down Expand Up @@ -6142,13 +6142,27 @@ impl Bank {
.map(|epoch_stakes| epoch_stakes.total_stake())
}

/// Get the total epoch stake for the current Bank::epoch
pub fn get_current_epoch_total_stake(&self) -> u64 {
self.current_epoch_stakes().total_stake()
}

/// vote accounts for the specific epoch along with the stake
/// attributed to each account
pub fn epoch_vote_accounts(&self, epoch: Epoch) -> Option<&VoteAccountsHashMap> {
let epoch_stakes = self.epoch_stakes.get(&epoch)?.stakes();
Some(epoch_stakes.vote_accounts().as_ref())
}

/// Get the vote accounts along with the stake attributed to each account
/// for the current Bank::epoch
pub fn get_current_epoch_vote_accounts(&self) -> &VoteAccountsHashMap {
self.current_epoch_stakes()
.stakes()
.vote_accounts()
.as_ref()
}

/// Get the fixed authorized voter for the given vote account for the
/// current epoch
pub fn epoch_authorized_voter(&self, vote_account: &Pubkey) -> Option<&Pubkey> {
Expand Down
Loading

0 comments on commit 15ff731

Please sign in to comment.