* Runtime feature activation framework (cherry picked from commit93259f0bae
) # Conflicts: # runtime/src/bank.rs * Add feature set identifier to gossiped version information (cherry picked from commit35f5f9fc7b
) # Conflicts: # Cargo.lock # version/Cargo.toml * Port instructions sysvar and secp256k1 program activation to FeatureSet (cherry picked from commitc10da16d7b
) # Conflicts: # runtime/src/bank.rs # runtime/src/message_processor.rs * Add feature management commands (cherry picked from commit93ed0ab2bb
) # Conflicts: # Cargo.lock # cli/Cargo.toml * Make test_process_rest_api less fragile (cherry picked from commit7526bb96f3
) * Remove id field (cherry picked from commitcc6ba1e131
) * FeatureSet test (cherry picked from commit92406cf9a0
) * cargo fmt (cherry picked from commit199940d683
) * cli review feedback (cherry picked from commit3a2b8c5e5b
) * Rename active() to is_active() (cherry picked from commite39fac9f01
) * Resolve merge conflicts * Remove continues from compute_active_feature_set() Co-authored-by: Michael Vines <mvines@gmail.com>
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -3572,6 +3572,7 @@ dependencies = [
|
||||
"solana-logger 1.3.14",
|
||||
"solana-net-utils",
|
||||
"solana-remote-wallet",
|
||||
"solana-runtime",
|
||||
"solana-sdk 1.3.14",
|
||||
"solana-stake-program",
|
||||
"solana-transaction-status",
|
||||
@ -4711,6 +4712,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_derive",
|
||||
"solana-logger 1.3.14",
|
||||
"solana-runtime",
|
||||
"solana-sdk 1.3.14",
|
||||
"solana-sdk-macro-frozen-abi 1.3.14",
|
||||
]
|
||||
|
@ -38,6 +38,7 @@ solana-faucet = { path = "../faucet", version = "1.3.14" }
|
||||
solana-logger = { path = "../logger", version = "1.3.14" }
|
||||
solana-net-utils = { path = "../net-utils", version = "1.3.14" }
|
||||
solana-remote-wallet = { path = "../remote-wallet", version = "1.3.14" }
|
||||
solana-runtime = { path = "../runtime", version = "1.3.14" }
|
||||
solana-sdk = { path = "../sdk", version = "1.3.14" }
|
||||
solana-stake-program = { path = "../programs/stake", version = "1.3.14" }
|
||||
solana-transaction-status = { path = "../transaction-status", version = "1.3.14" }
|
||||
|
@ -67,7 +67,7 @@ pub fn check_account_for_multiple_fees_with_commitment(
|
||||
pub fn calculate_fee(fee_calculator: &FeeCalculator, messages: &[&Message]) -> u64 {
|
||||
messages
|
||||
.iter()
|
||||
.map(|message| fee_calculator.calculate_fee(message, None))
|
||||
.map(|message| fee_calculator.calculate_fee(message))
|
||||
.sum()
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
use crate::{
|
||||
checks::*, cluster_query::*, nonce::*, spend_utils::*, stake::*, validator_info::*, vote::*,
|
||||
checks::*, cluster_query::*, feature::*, nonce::*, spend_utils::*, stake::*, validator_info::*,
|
||||
vote::*,
|
||||
};
|
||||
use chrono::prelude::*;
|
||||
use clap::{value_t_or_exit, App, AppSettings, Arg, ArgMatches, SubCommand};
|
||||
@ -107,6 +108,7 @@ pub enum CliCommand {
|
||||
seed: String,
|
||||
program_id: Pubkey,
|
||||
},
|
||||
Feature(FeatureCliCommand),
|
||||
Fees,
|
||||
FirstAvailableBlock,
|
||||
GetBlock {
|
||||
@ -554,6 +556,9 @@ pub fn parse_command(
|
||||
("create-address-with-seed", Some(matches)) => {
|
||||
parse_create_address_with_seed(matches, default_signer, wallet_manager)
|
||||
}
|
||||
("feature", Some(matches)) => {
|
||||
parse_feature_subcommand(matches, default_signer, wallet_manager)
|
||||
}
|
||||
("fees", Some(_matches)) => Ok(CliCommandInfo {
|
||||
command: CliCommand::Fees,
|
||||
signers: vec![],
|
||||
@ -1709,6 +1714,9 @@ pub fn process_command(config: &CliConfig) -> ProcessResult {
|
||||
program_id,
|
||||
} => process_create_address_with_seed(config, from_pubkey.as_ref(), &seed, &program_id),
|
||||
CliCommand::Fees => process_fees(&rpc_client, config),
|
||||
CliCommand::Feature(feature_subcommand) => {
|
||||
process_feature_subcommand(&rpc_client, config, feature_subcommand)
|
||||
}
|
||||
CliCommand::FirstAvailableBlock => process_first_available_block(&rpc_client),
|
||||
CliCommand::GetBlock { slot } => process_get_block(&rpc_client, config, *slot),
|
||||
CliCommand::GetBlockTime { slot } => process_get_block_time(&rpc_client, config, *slot),
|
||||
@ -2357,6 +2365,7 @@ pub fn app<'ab, 'v>(name: &str, about: &'ab str, version: &'v str) -> App<'ab, '
|
||||
),
|
||||
)
|
||||
.cluster_query_subcommands()
|
||||
.feature_subcommands()
|
||||
.nonce_subcommands()
|
||||
.stake_subcommands()
|
||||
.subcommand(
|
||||
|
@ -17,7 +17,7 @@ use solana_client::{
|
||||
pubsub_client::PubsubClient,
|
||||
rpc_client::{GetConfirmedSignaturesForAddress2Config, RpcClient},
|
||||
rpc_config::{RpcLargestAccountsConfig, RpcLargestAccountsFilter},
|
||||
rpc_response::{RpcVersionInfo, SlotInfo},
|
||||
rpc_response::SlotInfo,
|
||||
};
|
||||
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
|
||||
use solana_sdk::{
|
||||
@ -1314,12 +1314,9 @@ pub fn process_show_validators(
|
||||
for contact_info in rpc_client.get_cluster_nodes()? {
|
||||
node_version.insert(
|
||||
contact_info.pubkey,
|
||||
RpcVersionInfo {
|
||||
solana_core: contact_info
|
||||
.version
|
||||
.unwrap_or_else(|| unknown_version.clone()),
|
||||
}
|
||||
.to_string(),
|
||||
contact_info
|
||||
.version
|
||||
.unwrap_or_else(|| unknown_version.clone()),
|
||||
);
|
||||
}
|
||||
|
||||
|
273
cli/src/feature.rs
Normal file
273
cli/src/feature.rs
Normal file
@ -0,0 +1,273 @@
|
||||
use crate::{
|
||||
cli::{CliCommand, CliCommandInfo, CliConfig, CliError, ProcessResult},
|
||||
spend_utils::{resolve_spend_tx_and_check_account_balance, SpendAmount},
|
||||
};
|
||||
use clap::{App, AppSettings, Arg, ArgMatches, SubCommand};
|
||||
use console::style;
|
||||
use solana_clap_utils::{input_parsers::*, input_validators::*, keypair::*};
|
||||
use solana_client::{client_error::ClientError, rpc_client::RpcClient};
|
||||
use solana_remote_wallet::remote_wallet::RemoteWalletManager;
|
||||
use solana_runtime::{
|
||||
feature::{self, Feature},
|
||||
feature_set::FEATURE_NAMES,
|
||||
};
|
||||
use solana_sdk::{message::Message, pubkey::Pubkey, system_instruction, transaction::Transaction};
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
pub enum FeatureCliCommand {
|
||||
Status { features: Vec<Pubkey> },
|
||||
Activate { feature: Pubkey },
|
||||
}
|
||||
|
||||
pub trait FeatureSubCommands {
|
||||
fn feature_subcommands(self) -> Self;
|
||||
}
|
||||
|
||||
impl FeatureSubCommands for App<'_, '_> {
|
||||
fn feature_subcommands(self) -> Self {
|
||||
self.subcommand(
|
||||
SubCommand::with_name("feature")
|
||||
.about("Runtime feature management")
|
||||
.setting(AppSettings::SubcommandRequiredElseHelp)
|
||||
.subcommand(
|
||||
SubCommand::with_name("status")
|
||||
.about("Query runtime feature status")
|
||||
.arg(
|
||||
Arg::with_name("features")
|
||||
.value_name("ADDRESS")
|
||||
.validator(is_valid_pubkey)
|
||||
.index(1)
|
||||
.multiple(true)
|
||||
.help("Feature status to query [default: all known features]"),
|
||||
),
|
||||
)
|
||||
.subcommand(
|
||||
SubCommand::with_name("activate")
|
||||
.about("Activate a runtime feature")
|
||||
.arg(
|
||||
Arg::with_name("feature")
|
||||
.value_name("FEATURE_KEYPAIR")
|
||||
.validator(is_valid_signer)
|
||||
.index(1)
|
||||
.required(true)
|
||||
.help("The signer for the feature to activate"),
|
||||
),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn known_feature(feature: &Pubkey) -> Result<(), CliError> {
|
||||
if FEATURE_NAMES.contains_key(feature) {
|
||||
Ok(())
|
||||
} else {
|
||||
Err(CliError::BadParameter(format!(
|
||||
"Unknown feature: {}",
|
||||
feature
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_feature_subcommand(
|
||||
matches: &ArgMatches<'_>,
|
||||
default_signer: &DefaultSigner,
|
||||
wallet_manager: &mut Option<Arc<RemoteWalletManager>>,
|
||||
) -> Result<CliCommandInfo, CliError> {
|
||||
let response = match matches.subcommand() {
|
||||
("activate", Some(matches)) => {
|
||||
let (feature_signer, feature) = signer_of(matches, "feature", wallet_manager)?;
|
||||
let mut signers = vec![default_signer.signer_from_path(matches, wallet_manager)?];
|
||||
signers.push(feature_signer.unwrap());
|
||||
let feature = feature.unwrap();
|
||||
|
||||
known_feature(&feature)?;
|
||||
|
||||
CliCommandInfo {
|
||||
command: CliCommand::Feature(FeatureCliCommand::Activate { feature }),
|
||||
signers,
|
||||
}
|
||||
}
|
||||
("status", Some(matches)) => {
|
||||
let mut features = if let Some(features) = pubkeys_of(matches, "features") {
|
||||
for feature in &features {
|
||||
known_feature(feature)?;
|
||||
}
|
||||
features
|
||||
} else {
|
||||
FEATURE_NAMES.keys().cloned().collect()
|
||||
};
|
||||
features.sort();
|
||||
CliCommandInfo {
|
||||
command: CliCommand::Feature(FeatureCliCommand::Status { features }),
|
||||
signers: vec![],
|
||||
}
|
||||
}
|
||||
_ => unreachable!(),
|
||||
};
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
pub fn process_feature_subcommand(
|
||||
rpc_client: &RpcClient,
|
||||
config: &CliConfig,
|
||||
feature_subcommand: &FeatureCliCommand,
|
||||
) -> ProcessResult {
|
||||
match feature_subcommand {
|
||||
FeatureCliCommand::Status { features } => process_status(rpc_client, features),
|
||||
FeatureCliCommand::Activate { feature } => process_activate(rpc_client, config, *feature),
|
||||
}
|
||||
}
|
||||
|
||||
// Feature activation is only allowed when 95% of the active stake is on the current feature set
|
||||
fn feature_activation_allowed(rpc_client: &RpcClient) -> Result<bool, ClientError> {
|
||||
let my_feature_set = solana_version::Version::default().feature_set;
|
||||
|
||||
let feature_set_map = rpc_client
|
||||
.get_cluster_nodes()?
|
||||
.into_iter()
|
||||
.map(|contact_info| (contact_info.pubkey, contact_info.feature_set))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
let vote_accounts = rpc_client.get_vote_accounts()?;
|
||||
|
||||
let total_active_stake: u64 = vote_accounts
|
||||
.current
|
||||
.iter()
|
||||
.chain(vote_accounts.delinquent.iter())
|
||||
.map(|vote_account| vote_account.activated_stake)
|
||||
.sum();
|
||||
|
||||
let total_compatible_stake: u64 = vote_accounts
|
||||
.current
|
||||
.iter()
|
||||
.map(|vote_account| {
|
||||
if Some(&Some(my_feature_set)) == feature_set_map.get(&vote_account.node_pubkey) {
|
||||
vote_account.activated_stake
|
||||
} else {
|
||||
0
|
||||
}
|
||||
})
|
||||
.sum();
|
||||
|
||||
Ok(total_compatible_stake * 100 / total_active_stake >= 95)
|
||||
}
|
||||
|
||||
fn process_status(rpc_client: &RpcClient, feature_ids: &[Pubkey]) -> ProcessResult {
|
||||
if feature_ids.len() > 1 {
|
||||
println!(
|
||||
"{}",
|
||||
style(format!(
|
||||
"{:<44} {:<40} {}",
|
||||
"Feature", "Description", "Status"
|
||||
))
|
||||
.bold()
|
||||
);
|
||||
}
|
||||
|
||||
let mut inactive = false;
|
||||
for (i, account) in rpc_client
|
||||
.get_multiple_accounts(feature_ids)?
|
||||
.into_iter()
|
||||
.enumerate()
|
||||
{
|
||||
let feature_id = &feature_ids[i];
|
||||
let feature_name = FEATURE_NAMES.get(feature_id).unwrap();
|
||||
if let Some(account) = account {
|
||||
if let Some(feature) = Feature::from_account(&account) {
|
||||
match feature.activated_at {
|
||||
None => println!(
|
||||
"{:<44} {:<40} {}",
|
||||
feature_id,
|
||||
feature_name,
|
||||
style("activation pending").yellow()
|
||||
),
|
||||
Some(activation_slot) => {
|
||||
println!(
|
||||
"{:<44} {:<40} {}",
|
||||
feature_id,
|
||||
feature_name,
|
||||
style(format!("active since slot {}", activation_slot)).green()
|
||||
);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
inactive = true;
|
||||
println!(
|
||||
"{:<44} {:<40} {}",
|
||||
feature_id,
|
||||
feature_name,
|
||||
style("inactive").red()
|
||||
);
|
||||
}
|
||||
|
||||
if inactive && !feature_activation_allowed(rpc_client)? {
|
||||
println!(
|
||||
"{}",
|
||||
style("\nFeature activation is not allowed at this time")
|
||||
.bold()
|
||||
.red()
|
||||
);
|
||||
}
|
||||
Ok("".to_string())
|
||||
}
|
||||
|
||||
fn process_activate(
|
||||
rpc_client: &RpcClient,
|
||||
config: &CliConfig,
|
||||
feature_id: Pubkey,
|
||||
) -> ProcessResult {
|
||||
let account = rpc_client
|
||||
.get_multiple_accounts(&[feature_id])?
|
||||
.into_iter()
|
||||
.next()
|
||||
.unwrap();
|
||||
if let Some(account) = account {
|
||||
if Feature::from_account(&account).is_some() {
|
||||
return Err(format!("{} has already been activated", feature_id).into());
|
||||
}
|
||||
}
|
||||
|
||||
if !feature_activation_allowed(rpc_client)? {
|
||||
return Err("Feature activation is not allowed at this time".into());
|
||||
}
|
||||
|
||||
let rent = rpc_client.get_minimum_balance_for_rent_exemption(Feature::size_of())?;
|
||||
|
||||
let (blockhash, fee_calculator) = rpc_client.get_recent_blockhash()?;
|
||||
let (message, _) = resolve_spend_tx_and_check_account_balance(
|
||||
rpc_client,
|
||||
false,
|
||||
SpendAmount::Some(rent),
|
||||
&fee_calculator,
|
||||
&config.signers[0].pubkey(),
|
||||
|lamports| {
|
||||
Message::new(
|
||||
&[
|
||||
system_instruction::transfer(
|
||||
&config.signers[0].pubkey(),
|
||||
&feature_id,
|
||||
lamports,
|
||||
),
|
||||
system_instruction::allocate(&feature_id, Feature::size_of() as u64),
|
||||
system_instruction::assign(&feature_id, &feature::id()),
|
||||
],
|
||||
Some(&config.signers[0].pubkey()),
|
||||
)
|
||||
},
|
||||
config.commitment,
|
||||
)?;
|
||||
let mut transaction = Transaction::new_unsigned(message);
|
||||
transaction.try_sign(&config.signers, blockhash)?;
|
||||
|
||||
println!(
|
||||
"Activating {} ({})",
|
||||
FEATURE_NAMES.get(&feature_id).unwrap(),
|
||||
feature_id
|
||||
);
|
||||
rpc_client.send_and_confirm_transaction_with_spinner(&transaction)?;
|
||||
Ok("".to_string())
|
||||
}
|
@ -23,6 +23,7 @@ extern crate serde_derive;
|
||||
pub mod checks;
|
||||
pub mod cli;
|
||||
pub mod cluster_query;
|
||||
pub mod feature;
|
||||
pub mod nonce;
|
||||
pub mod spend_utils;
|
||||
pub mod stake;
|
||||
|
@ -121,6 +121,7 @@ pub enum ReceivedSignatureResult {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RpcContactInfo {
|
||||
/// Pubkey of the node as a base-58 string
|
||||
pub pubkey: String,
|
||||
@ -132,6 +133,8 @@ pub struct RpcContactInfo {
|
||||
pub rpc: Option<SocketAddr>,
|
||||
/// Software version
|
||||
pub version: Option<String>,
|
||||
/// First 4 bytes of the FeatureSet identifier
|
||||
pub feature_set: Option<u32>,
|
||||
}
|
||||
|
||||
/// Map of leader base58 identity pubkeys to the slot indices relative to the first epoch slot
|
||||
@ -142,6 +145,8 @@ pub type RpcLeaderSchedule = HashMap<String, Vec<usize>>;
|
||||
pub struct RpcVersionInfo {
|
||||
/// The current version of solana-core
|
||||
pub solana_core: String,
|
||||
/// first 4 bytes of the FeatureSet identifier
|
||||
pub feature_set: Option<u32>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for RpcVersionInfo {
|
||||
|
@ -30,10 +30,9 @@ use solana_runtime::{
|
||||
};
|
||||
use solana_sdk::{
|
||||
clock::{
|
||||
Epoch, Slot, DEFAULT_TICKS_PER_SLOT, MAX_PROCESSING_AGE, MAX_TRANSACTION_FORWARDING_DELAY,
|
||||
Slot, DEFAULT_TICKS_PER_SLOT, MAX_PROCESSING_AGE, MAX_TRANSACTION_FORWARDING_DELAY,
|
||||
MAX_TRANSACTION_FORWARDING_DELAY_GPU,
|
||||
},
|
||||
genesis_config::ClusterType,
|
||||
poh_config::PohConfig,
|
||||
pubkey::Pubkey,
|
||||
timing::{duration_as_ms, timestamp},
|
||||
@ -737,8 +736,7 @@ impl BankingStage {
|
||||
fn transactions_from_packets(
|
||||
msgs: &Packets,
|
||||
transaction_indexes: &[usize],
|
||||
cluster_type: ClusterType,
|
||||
epoch: Epoch,
|
||||
secp256k1_program_enabled: bool,
|
||||
) -> (Vec<Transaction>, Vec<usize>) {
|
||||
let packets = Packets::new(
|
||||
transaction_indexes
|
||||
@ -748,25 +746,24 @@ impl BankingStage {
|
||||
);
|
||||
|
||||
let transactions = Self::deserialize_transactions(&packets);
|
||||
let maybe_secp_verified_transactions: Vec<_> =
|
||||
if solana_sdk::secp256k1::is_enabled(cluster_type, epoch) {
|
||||
transactions
|
||||
.into_iter()
|
||||
.map(|tx| {
|
||||
if let Some(tx) = tx {
|
||||
if tx.verify_precompiles().is_ok() {
|
||||
Some(tx)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
let maybe_secp_verified_transactions: Vec<_> = if secp256k1_program_enabled {
|
||||
transactions
|
||||
.into_iter()
|
||||
.map(|tx| {
|
||||
if let Some(tx) = tx {
|
||||
if tx.verify_precompiles().is_ok() {
|
||||
Some(tx)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
transactions
|
||||
};
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
transactions
|
||||
};
|
||||
|
||||
Self::filter_transaction_indexes(maybe_secp_verified_transactions, &transaction_indexes)
|
||||
}
|
||||
@ -820,8 +817,7 @@ impl BankingStage {
|
||||
let (transactions, transaction_to_packet_indexes) = Self::transactions_from_packets(
|
||||
msgs,
|
||||
&packet_indexes,
|
||||
bank.cluster_type(),
|
||||
bank.epoch(),
|
||||
bank.secp256k1_program_enabled(),
|
||||
);
|
||||
debug!(
|
||||
"bank: {} filtered transactions {}",
|
||||
@ -874,8 +870,7 @@ impl BankingStage {
|
||||
let (transactions, transaction_to_packet_indexes) = Self::transactions_from_packets(
|
||||
msgs,
|
||||
&transaction_indexes,
|
||||
bank.cluster_type(),
|
||||
bank.epoch(),
|
||||
bank.secp256k1_program_enabled(),
|
||||
);
|
||||
|
||||
let tx_count = transaction_to_packet_indexes.len();
|
||||
|
@ -359,7 +359,7 @@ pub fn make_accounts_hashes_message(
|
||||
}
|
||||
|
||||
// TODO These messages should go through the gpu pipeline for spam filtering
|
||||
#[frozen_abi(digest = "CnN1gW2K2TRydGc84eYnQJwdTADPjQf6LJLZ4RP1QeoH")]
|
||||
#[frozen_abi(digest = "3ZHQscZ9SgxKh45idzHv3hiagyyPRtDgeySmJn171PTi")]
|
||||
#[derive(Serialize, Deserialize, Debug, AbiEnumVisitor, AbiExample)]
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
enum Protocol {
|
||||
@ -573,7 +573,7 @@ impl ClusterInfo {
|
||||
}
|
||||
let ip_addr = node.gossip.ip();
|
||||
Some(format!(
|
||||
"{:15} {:2}| {:5} | {:44} |{:^15}| {:5}| {:5}| {:5}| {:5}| {:5}| {:5}| {:5}| {:5}| {:5}| {:5}| {}\n",
|
||||
"{:15} {:2}| {:5} | {:44} |{:^9}| {:5}| {:5}| {:5}| {:5}| {:5}| {:5}| {:5}| {:5}| {:5}| {:5}| {}\n",
|
||||
if ContactInfo::is_valid_address(&node.gossip) {
|
||||
ip_addr.to_string()
|
||||
} else {
|
||||
@ -605,8 +605,8 @@ impl ClusterInfo {
|
||||
|
||||
format!(
|
||||
"IP Address |Age(ms)| Node identifier \
|
||||
| Version |Gossip| TPU |TPUfwd| TVU |TVUfwd|Repair|ServeR| RPC |PubSub|ShredVer\n\
|
||||
------------------+-------+----------------------------------------------+---------------+\
|
||||
| Version |Gossip| TPU |TPUfwd| TVU |TVUfwd|Repair|ServeR| RPC |PubSub|ShredVer\n\
|
||||
------------------+-------+----------------------------------------------+---------+\
|
||||
------+------+------+------+------+------+------+------+------+--------\n\
|
||||
{}\
|
||||
Nodes: {}{}{}",
|
||||
@ -894,7 +894,8 @@ impl ClusterInfo {
|
||||
}
|
||||
|
||||
pub fn get_node_version(&self, pubkey: &Pubkey) -> Option<solana_version::Version> {
|
||||
self.gossip
|
||||
let version = self
|
||||
.gossip
|
||||
.read()
|
||||
.unwrap()
|
||||
.crds
|
||||
@ -902,7 +903,21 @@ impl ClusterInfo {
|
||||
.get(&CrdsValueLabel::Version(*pubkey))
|
||||
.map(|x| x.value.version())
|
||||
.flatten()
|
||||
.map(|version| version.version.clone())
|
||||
.map(|version| version.version.clone());
|
||||
|
||||
if version.is_none() {
|
||||
self.gossip
|
||||
.read()
|
||||
.unwrap()
|
||||
.crds
|
||||
.table
|
||||
.get(&CrdsValueLabel::LegacyVersion(*pubkey))
|
||||
.map(|x| x.value.legacy_version())
|
||||
.flatten()
|
||||
.map(|version| version.version.clone().into())
|
||||
} else {
|
||||
version
|
||||
}
|
||||
}
|
||||
|
||||
/// all validators that have a valid rpc port regardless of `shred_version`.
|
||||
|
@ -75,6 +75,7 @@ pub enum CrdsData {
|
||||
SnapshotHashes(SnapshotHash),
|
||||
AccountsHashes(SnapshotHash),
|
||||
EpochSlots(EpochSlotsIndex, EpochSlots),
|
||||
LegacyVersion(LegacyVersion),
|
||||
Version(Version),
|
||||
}
|
||||
|
||||
@ -102,6 +103,7 @@ impl Sanitize for CrdsData {
|
||||
}
|
||||
val.sanitize()
|
||||
}
|
||||
CrdsData::LegacyVersion(version) => version.sanitize(),
|
||||
CrdsData::Version(version) => version.sanitize(),
|
||||
}
|
||||
}
|
||||
@ -208,6 +210,23 @@ impl Vote {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, AbiExample)]
|
||||
pub struct LegacyVersion {
|
||||
pub from: Pubkey,
|
||||
pub wallclock: u64,
|
||||
pub version: solana_version::LegacyVersion,
|
||||
}
|
||||
|
||||
impl Sanitize for LegacyVersion {
|
||||
fn sanitize(&self) -> Result<(), SanitizeError> {
|
||||
if self.wallclock >= MAX_WALLCLOCK {
|
||||
return Err(SanitizeError::ValueOutOfBounds);
|
||||
}
|
||||
self.from.sanitize()?;
|
||||
self.version.sanitize()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, AbiExample)]
|
||||
pub struct Version {
|
||||
pub from: Pubkey,
|
||||
@ -245,6 +264,7 @@ pub enum CrdsValueLabel {
|
||||
SnapshotHashes(Pubkey),
|
||||
EpochSlots(EpochSlotsIndex, Pubkey),
|
||||
AccountsHashes(Pubkey),
|
||||
LegacyVersion(Pubkey),
|
||||
Version(Pubkey),
|
||||
}
|
||||
|
||||
@ -257,6 +277,7 @@ impl fmt::Display for CrdsValueLabel {
|
||||
CrdsValueLabel::SnapshotHashes(_) => write!(f, "SnapshotHash({})", self.pubkey()),
|
||||
CrdsValueLabel::EpochSlots(ix, _) => write!(f, "EpochSlots({}, {})", ix, self.pubkey()),
|
||||
CrdsValueLabel::AccountsHashes(_) => write!(f, "AccountsHashes({})", self.pubkey()),
|
||||
CrdsValueLabel::LegacyVersion(_) => write!(f, "LegacyVersion({})", self.pubkey()),
|
||||
CrdsValueLabel::Version(_) => write!(f, "Version({})", self.pubkey()),
|
||||
}
|
||||
}
|
||||
@ -271,6 +292,7 @@ impl CrdsValueLabel {
|
||||
CrdsValueLabel::SnapshotHashes(p) => *p,
|
||||
CrdsValueLabel::EpochSlots(_, p) => *p,
|
||||
CrdsValueLabel::AccountsHashes(p) => *p,
|
||||
CrdsValueLabel::LegacyVersion(p) => *p,
|
||||
CrdsValueLabel::Version(p) => *p,
|
||||
}
|
||||
}
|
||||
@ -300,6 +322,7 @@ impl CrdsValue {
|
||||
CrdsData::SnapshotHashes(hash) => hash.wallclock,
|
||||
CrdsData::AccountsHashes(hash) => hash.wallclock,
|
||||
CrdsData::EpochSlots(_, p) => p.wallclock,
|
||||
CrdsData::LegacyVersion(version) => version.wallclock,
|
||||
CrdsData::Version(version) => version.wallclock,
|
||||
}
|
||||
}
|
||||
@ -311,6 +334,7 @@ impl CrdsValue {
|
||||
CrdsData::SnapshotHashes(hash) => hash.from,
|
||||
CrdsData::AccountsHashes(hash) => hash.from,
|
||||
CrdsData::EpochSlots(_, p) => p.from,
|
||||
CrdsData::LegacyVersion(version) => version.from,
|
||||
CrdsData::Version(version) => version.from,
|
||||
}
|
||||
}
|
||||
@ -322,6 +346,7 @@ impl CrdsValue {
|
||||
CrdsData::SnapshotHashes(_) => CrdsValueLabel::SnapshotHashes(self.pubkey()),
|
||||
CrdsData::AccountsHashes(_) => CrdsValueLabel::AccountsHashes(self.pubkey()),
|
||||
CrdsData::EpochSlots(ix, _) => CrdsValueLabel::EpochSlots(*ix, self.pubkey()),
|
||||
CrdsData::LegacyVersion(_) => CrdsValueLabel::LegacyVersion(self.pubkey()),
|
||||
CrdsData::Version(_) => CrdsValueLabel::Version(self.pubkey()),
|
||||
}
|
||||
}
|
||||
@ -373,6 +398,13 @@ impl CrdsValue {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn legacy_version(&self) -> Option<&LegacyVersion> {
|
||||
match &self.data {
|
||||
CrdsData::LegacyVersion(legacy_version) => Some(legacy_version),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn version(&self) -> Option<&Version> {
|
||||
match &self.data {
|
||||
CrdsData::Version(version) => Some(version),
|
||||
@ -387,6 +419,7 @@ impl CrdsValue {
|
||||
CrdsValueLabel::LowestSlot(*key),
|
||||
CrdsValueLabel::SnapshotHashes(*key),
|
||||
CrdsValueLabel::AccountsHashes(*key),
|
||||
CrdsValueLabel::LegacyVersion(*key),
|
||||
CrdsValueLabel::Version(*key),
|
||||
];
|
||||
labels.extend((0..MAX_VOTES).map(|ix| CrdsValueLabel::Vote(ix, *key)));
|
||||
@ -438,7 +471,7 @@ mod test {
|
||||
|
||||
#[test]
|
||||
fn test_labels() {
|
||||
let mut hits = [false; 5 + MAX_VOTES as usize + MAX_EPOCH_SLOTS as usize];
|
||||
let mut hits = [false; 6 + MAX_VOTES as usize + MAX_EPOCH_SLOTS as usize];
|
||||
// this method should cover all the possible labels
|
||||
for v in &CrdsValue::record_labels(&Pubkey::default()) {
|
||||
match v {
|
||||
@ -446,10 +479,11 @@ mod test {
|
||||
CrdsValueLabel::LowestSlot(_) => hits[1] = true,
|
||||
CrdsValueLabel::SnapshotHashes(_) => hits[2] = true,
|
||||
CrdsValueLabel::AccountsHashes(_) => hits[3] = true,
|
||||
CrdsValueLabel::Version(_) => hits[4] = true,
|
||||
CrdsValueLabel::Vote(ix, _) => hits[*ix as usize + 5] = true,
|
||||
CrdsValueLabel::LegacyVersion(_) => hits[4] = true,
|
||||
CrdsValueLabel::Version(_) => hits[5] = true,
|
||||
CrdsValueLabel::Vote(ix, _) => hits[*ix as usize + 6] = true,
|
||||
CrdsValueLabel::EpochSlots(ix, _) => {
|
||||
hits[*ix as usize + MAX_VOTES as usize + 5] = true
|
||||
hits[*ix as usize + MAX_VOTES as usize + 6] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1950,14 +1950,19 @@ impl RpcSol for RpcSolImpl {
|
||||
if my_shred_version == contact_info.shred_version
|
||||
&& ContactInfo::is_valid_address(&contact_info.gossip)
|
||||
{
|
||||
let (version, feature_set) =
|
||||
if let Some(version) = cluster_info.get_node_version(&contact_info.id) {
|
||||
(Some(version.to_string()), Some(version.feature_set))
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
Some(RpcContactInfo {
|
||||
pubkey: contact_info.id.to_string(),
|
||||
gossip: Some(contact_info.gossip),
|
||||
tpu: valid_address_or_none(&contact_info.tpu),
|
||||
rpc: valid_address_or_none(&contact_info.rpc),
|
||||
version: cluster_info
|
||||
.get_node_version(&contact_info.id)
|
||||
.map(|v| v.to_string()),
|
||||
version,
|
||||
feature_set,
|
||||
})
|
||||
} else {
|
||||
None // Exclude spy nodes
|
||||
@ -2304,8 +2309,10 @@ impl RpcSol for RpcSolImpl {
|
||||
|
||||
fn get_version(&self, _: Self::Metadata) -> Result<RpcVersionInfo> {
|
||||
debug!("get_version rpc request received");
|
||||
let version = solana_version::Version::default();
|
||||
Ok(RpcVersionInfo {
|
||||
solana_core: solana_version::Version::default().to_string(),
|
||||
solana_core: version.to_string(),
|
||||
feature_set: Some(version.feature_set),
|
||||
})
|
||||
}
|
||||
|
||||
@ -2853,7 +2860,7 @@ pub mod tests {
|
||||
.expect("actual response deserialization");
|
||||
|
||||
let expected = format!(
|
||||
r#"{{"jsonrpc":"2.0","result":[{{"pubkey": "{}", "gossip": "127.0.0.1:1235", "tpu": "127.0.0.1:1234", "rpc": "127.0.0.1:{}", "version": null}}],"id":1}}"#,
|
||||
r#"{{"jsonrpc":"2.0","result":[{{"pubkey": "{}", "gossip": "127.0.0.1:1235", "tpu": "127.0.0.1:1234", "rpc": "127.0.0.1:{}", "version": null, "featureSet": null}}],"id":1}}"#,
|
||||
leader_pubkey,
|
||||
rpc_port::DEFAULT_RPC_PORT
|
||||
);
|
||||
@ -4404,10 +4411,12 @@ pub mod tests {
|
||||
|
||||
let req = r#"{"jsonrpc":"2.0","id":1,"method":"getVersion"}"#;
|
||||
let res = io.handle_request_sync(&req, meta);
|
||||
let version = solana_version::Version::default();
|
||||
let expected = json!({
|
||||
"jsonrpc": "2.0",
|
||||
"result": {
|
||||
"solana-core": solana_version::version!().to_string()
|
||||
"solana-core": version.to_string(),
|
||||
"feature-set": version.feature_set,
|
||||
},
|
||||
"id": 1
|
||||
});
|
||||
|
@ -480,11 +480,7 @@ mod tests {
|
||||
|
||||
assert_eq!(None, process_rest(&bank_forks, "not-a-supported-rest-api"));
|
||||
assert_eq!(
|
||||
Some("0.000010127".to_string()),
|
||||
process_rest(&bank_forks, "/v0/circulating-supply")
|
||||
);
|
||||
assert_eq!(
|
||||
Some("0.000010127".to_string()),
|
||||
process_rest(&bank_forks, "/v0/circulating-supply"),
|
||||
process_rest(&bank_forks, "/v0/total-supply")
|
||||
);
|
||||
}
|
||||
|
@ -80,10 +80,7 @@ impl TransactionStatusService {
|
||||
_ => bank.get_fee_calculator(&transaction.message().recent_blockhash),
|
||||
}
|
||||
.expect("FeeCalculator must exist");
|
||||
let fee = fee_calculator.calculate_fee(
|
||||
transaction.message(),
|
||||
solana_sdk::secp256k1::get_fee_config(bank.cluster_type(), bank.epoch()),
|
||||
);
|
||||
let fee = fee_calculator.calculate_fee(transaction.message());
|
||||
let (writable_keys, readonly_keys) =
|
||||
transaction.message.get_account_keys_by_lock_type();
|
||||
|
||||
|
@ -526,6 +526,7 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
||||
}
|
||||
|
||||
solana_stake_program::add_genesis_accounts(&mut genesis_config);
|
||||
solana_runtime::genesis_utils::add_feature_accounts(&mut genesis_config);
|
||||
|
||||
if let Some(files) = matches.values_of("primordial_accounts_file") {
|
||||
for file in files {
|
||||
|
@ -681,8 +681,7 @@ pub fn confirm_slot(
|
||||
let entry_state = entries.start_verify(
|
||||
&progress.last_entry,
|
||||
recyclers.clone(),
|
||||
bank.cluster_type(),
|
||||
bank.epoch(),
|
||||
bank.secp256k1_program_enabled(),
|
||||
);
|
||||
if entry_state.status() == EntryVerificationStatus::Failure {
|
||||
warn!("Ledger proof of history failed at slot: {}", slot);
|
||||
|
@ -17,8 +17,6 @@ use solana_perf::cuda_runtime::PinnedVec;
|
||||
use solana_perf::perf_libs;
|
||||
use solana_perf::recycler::Recycler;
|
||||
use solana_rayon_threadlimit::get_thread_count;
|
||||
use solana_sdk::clock::Epoch;
|
||||
use solana_sdk::genesis_config::ClusterType;
|
||||
use solana_sdk::hash::Hash;
|
||||
use solana_sdk::timing;
|
||||
use solana_sdk::transaction::Transaction;
|
||||
@ -329,8 +327,7 @@ pub trait EntrySlice {
|
||||
&self,
|
||||
start_hash: &Hash,
|
||||
recyclers: VerifyRecyclers,
|
||||
cluster_type: ClusterType,
|
||||
epoch: Epoch,
|
||||
secp256k1_program_enabled: bool,
|
||||
) -> EntryVerificationState;
|
||||
fn verify(&self, start_hash: &Hash) -> bool;
|
||||
/// Checks that each entry tick has the correct number of hashes. Entry slices do not
|
||||
@ -339,18 +336,13 @@ pub trait EntrySlice {
|
||||
fn verify_tick_hash_count(&self, tick_hash_count: &mut u64, hashes_per_tick: u64) -> bool;
|
||||
/// Counts tick entries
|
||||
fn tick_count(&self) -> u64;
|
||||
fn verify_transaction_signatures(&self, cluster_type: ClusterType, epoch: Epoch) -> bool;
|
||||
fn verify_transaction_signatures(&self, secp256k1_program_enabled: bool) -> bool;
|
||||
}
|
||||
|
||||
impl EntrySlice for [Entry] {
|
||||
fn verify(&self, start_hash: &Hash) -> bool {
|
||||
self.start_verify(
|
||||
start_hash,
|
||||
VerifyRecyclers::default(),
|
||||
ClusterType::Development,
|
||||
0,
|
||||
)
|
||||
.finish_verify(self)
|
||||
self.start_verify(start_hash, VerifyRecyclers::default(), true)
|
||||
.finish_verify(self)
|
||||
}
|
||||
|
||||
fn verify_cpu_generic(&self, start_hash: &Hash) -> EntryVerificationState {
|
||||
@ -497,14 +489,14 @@ impl EntrySlice for [Entry] {
|
||||
}
|
||||
}
|
||||
|
||||
fn verify_transaction_signatures(&self, cluster_type: ClusterType, epoch: Epoch) -> bool {
|
||||
fn verify_transaction_signatures(&self, secp256k1_program_enabled: bool) -> bool {
|
||||
PAR_THREAD_POOL.with(|thread_pool| {
|
||||
thread_pool.borrow().install(|| {
|
||||
self.par_iter().all(|e| {
|
||||
e.transactions.par_iter().all(|transaction| {
|
||||
let sig_verify = transaction.verify().is_ok();
|
||||
if sig_verify
|
||||
&& solana_sdk::secp256k1::is_enabled(cluster_type, epoch)
|
||||
&& secp256k1_program_enabled
|
||||
&& transaction.verify_precompiles().is_err()
|
||||
{
|
||||
return false;
|
||||
@ -520,11 +512,10 @@ impl EntrySlice for [Entry] {
|
||||
&self,
|
||||
start_hash: &Hash,
|
||||
recyclers: VerifyRecyclers,
|
||||
cluster_type: ClusterType,
|
||||
epoch: Epoch,
|
||||
secp256k1_program_enabled: bool,
|
||||
) -> EntryVerificationState {
|
||||
let start = Instant::now();
|
||||
let res = self.verify_transaction_signatures(cluster_type, epoch);
|
||||
let res = self.verify_transaction_signatures(secp256k1_program_enabled);
|
||||
let transaction_duration_us = timing::duration_as_us(&start.elapsed());
|
||||
if !res {
|
||||
return EntryVerificationState {
|
||||
|
@ -2,7 +2,7 @@ use clap::{crate_description, crate_name, value_t, App, Arg};
|
||||
use solana_ledger::entry::{self, create_ticks, init_poh, EntrySlice, VerifyRecyclers};
|
||||
use solana_measure::measure::Measure;
|
||||
use solana_perf::perf_libs;
|
||||
use solana_sdk::{genesis_config::ClusterType, hash::hash};
|
||||
use solana_sdk::hash::hash;
|
||||
|
||||
fn main() {
|
||||
solana_logger::setup();
|
||||
@ -118,7 +118,7 @@ fn main() {
|
||||
let recyclers = VerifyRecyclers::default();
|
||||
for _ in 0..iterations {
|
||||
assert!(ticks[..num_entries]
|
||||
.start_verify(&start_hash, recyclers.clone(), ClusterType::Development, 0)
|
||||
.start_verify(&start_hash, recyclers.clone(), true)
|
||||
.finish_verify(&ticks[..num_entries]));
|
||||
}
|
||||
time.stop();
|
||||
|
@ -6,6 +6,7 @@ use crate::{
|
||||
append_vec::StoredAccount,
|
||||
bank::{HashAgeKind, TransactionProcessResult},
|
||||
blockhash_queue::BlockhashQueue,
|
||||
feature_set::{self, FeatureSet},
|
||||
nonce_utils,
|
||||
rent_collector::RentCollector,
|
||||
system_instruction_processor::{get_system_account_kind, SystemAccountKind},
|
||||
@ -17,7 +18,7 @@ use rayon::slice::ParallelSliceMut;
|
||||
use solana_sdk::{
|
||||
account::Account,
|
||||
clock::{Epoch, Slot},
|
||||
fee_calculator::FeeCalculator,
|
||||
fee_calculator::{FeeCalculator, FeeConfig},
|
||||
genesis_config::ClusterType,
|
||||
hash::Hash,
|
||||
message::Message,
|
||||
@ -72,11 +73,10 @@ pub enum AccountAddressFilter {
|
||||
impl Accounts {
|
||||
pub fn new(paths: Vec<PathBuf>, cluster_type: &ClusterType) -> Self {
|
||||
Self {
|
||||
slot: 0,
|
||||
epoch: 0,
|
||||
accounts_db: Arc::new(AccountsDB::new(paths, cluster_type)),
|
||||
account_locks: Mutex::new(HashSet::new()),
|
||||
readonly_locks: Arc::new(RwLock::new(Some(HashMap::new()))),
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
@ -94,11 +94,10 @@ impl Accounts {
|
||||
|
||||
pub(crate) fn new_empty(accounts_db: AccountsDB) -> Self {
|
||||
Self {
|
||||
slot: 0,
|
||||
epoch: 0,
|
||||
accounts_db: Arc::new(accounts_db),
|
||||
account_locks: Mutex::new(HashSet::new()),
|
||||
readonly_locks: Arc::new(RwLock::new(Some(HashMap::new()))),
|
||||
..Self::default()
|
||||
}
|
||||
}
|
||||
|
||||
@ -133,6 +132,7 @@ impl Accounts {
|
||||
fee: u64,
|
||||
error_counters: &mut ErrorCounters,
|
||||
rent_collector: &RentCollector,
|
||||
feature_set: &FeatureSet,
|
||||
) -> Result<(TransactionAccounts, TransactionRent)> {
|
||||
// Copy all the accounts
|
||||
let message = tx.message();
|
||||
@ -150,10 +150,8 @@ impl Accounts {
|
||||
payer_index = Some(i);
|
||||
}
|
||||
|
||||
if solana_sdk::sysvar::instructions::is_enabled(
|
||||
self.epoch,
|
||||
self.accounts_db.cluster_type.unwrap(),
|
||||
) && solana_sdk::sysvar::instructions::check_id(key)
|
||||
if solana_sdk::sysvar::instructions::check_id(key)
|
||||
&& feature_set.is_active(&feature_set::instructions_sysvar_enabled::id())
|
||||
{
|
||||
if message.is_writable(i) {
|
||||
return Err(TransactionError::InvalidAccountIndex);
|
||||
@ -300,11 +298,17 @@ impl Accounts {
|
||||
hash_queue: &BlockhashQueue,
|
||||
error_counters: &mut ErrorCounters,
|
||||
rent_collector: &RentCollector,
|
||||
feature_set: &FeatureSet,
|
||||
) -> Vec<(Result<TransactionLoadResult>, Option<HashAgeKind>)> {
|
||||
//PERF: hold the lock to scan for the references, but not to clone the accounts
|
||||
//TODO: two locks usually leads to deadlocks, should this be one structure?
|
||||
let accounts_index = self.accounts_db.accounts_index.read().unwrap();
|
||||
let storage = self.accounts_db.storage.read().unwrap();
|
||||
|
||||
let fee_config = FeeConfig {
|
||||
secp256k1_program_enabled: feature_set
|
||||
.is_active(&feature_set::secp256k1_program_enabled::id()),
|
||||
};
|
||||
OrderedIterator::new(txs, txs_iteration_order)
|
||||
.zip(lock_results.into_iter())
|
||||
.map(|etx| match etx {
|
||||
@ -318,13 +322,7 @@ impl Accounts {
|
||||
.cloned(),
|
||||
};
|
||||
let fee = if let Some(fee_calculator) = fee_calculator {
|
||||
fee_calculator.calculate_fee(
|
||||
tx.message(),
|
||||
solana_sdk::secp256k1::get_fee_config(
|
||||
self.accounts_db.cluster_type.unwrap(),
|
||||
self.epoch,
|
||||
),
|
||||
)
|
||||
fee_calculator.calculate_fee_with_config(tx.message(), &fee_config)
|
||||
} else {
|
||||
return (Err(TransactionError::BlockhashNotFound), hash_age_kind);
|
||||
};
|
||||
@ -337,6 +335,7 @@ impl Accounts {
|
||||
fee,
|
||||
error_counters,
|
||||
rent_collector,
|
||||
feature_set,
|
||||
);
|
||||
let (accounts, rents) = match load_res {
|
||||
Ok((a, r)) => (a, r),
|
||||
@ -888,6 +887,7 @@ mod tests {
|
||||
&hash_queue,
|
||||
error_counters,
|
||||
rent_collector,
|
||||
&FeatureSet::default(),
|
||||
)
|
||||
}
|
||||
|
||||
@ -1024,7 +1024,7 @@ mod tests {
|
||||
);
|
||||
|
||||
let fee_calculator = FeeCalculator::new(10);
|
||||
assert_eq!(fee_calculator.calculate_fee(tx.message(), None), 10);
|
||||
assert_eq!(fee_calculator.calculate_fee(tx.message()), 10);
|
||||
|
||||
let loaded_accounts =
|
||||
load_accounts_with_fee(tx, &accounts, &fee_calculator, &mut error_counters);
|
||||
@ -1832,6 +1832,7 @@ mod tests {
|
||||
&hash_queue,
|
||||
&mut error_counters,
|
||||
&rent_collector,
|
||||
&FeatureSet::default(),
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -10,8 +10,10 @@ use crate::{
|
||||
accounts_db::{ErrorCounters, SnapshotStorages},
|
||||
accounts_index::Ancestors,
|
||||
blockhash_queue::BlockhashQueue,
|
||||
builtins::get_builtins,
|
||||
builtins::*,
|
||||
epoch_stakes::{EpochStakes, NodeVoteAccounts},
|
||||
feature::Feature,
|
||||
feature_set::{self, FeatureSet},
|
||||
instruction_recorder::InstructionRecorder,
|
||||
log_collector::LogCollector,
|
||||
message_processor::{Executors, MessageProcessor},
|
||||
@ -41,7 +43,7 @@ use solana_sdk::{
|
||||
},
|
||||
epoch_info::EpochInfo,
|
||||
epoch_schedule::EpochSchedule,
|
||||
fee_calculator::{FeeCalculator, FeeRateGovernor},
|
||||
fee_calculator::{FeeCalculator, FeeConfig, FeeRateGovernor},
|
||||
genesis_config::{ClusterType, GenesisConfig},
|
||||
hard_forks::HardForks,
|
||||
hash::{extend_and_hash, hashv, Hash},
|
||||
@ -526,6 +528,8 @@ pub struct Bank {
|
||||
cached_executors: Arc<RwLock<CachedExecutors>>,
|
||||
|
||||
transaction_debug_keys: Option<Arc<HashSet<Pubkey>>>,
|
||||
|
||||
pub feature_set: Arc<FeatureSet>,
|
||||
}
|
||||
|
||||
impl Default for BlockhashQueue {
|
||||
@ -651,6 +655,7 @@ impl Bank {
|
||||
rewards_pool_pubkeys: parent.rewards_pool_pubkeys.clone(),
|
||||
cached_executors: parent.cached_executors.clone(),
|
||||
transaction_debug_keys: parent.transaction_debug_keys.clone(),
|
||||
feature_set: parent.feature_set.clone(),
|
||||
};
|
||||
|
||||
datapoint_info!(
|
||||
@ -756,6 +761,7 @@ impl Bank {
|
||||
rewards_pool_pubkeys: new(),
|
||||
cached_executors: Arc::new(RwLock::new(CachedExecutors::new(MAX_CACHED_EXECUTORS))),
|
||||
transaction_debug_keys: debug_keys,
|
||||
feature_set: new(),
|
||||
};
|
||||
bank.finish_init(genesis_config);
|
||||
|
||||
@ -1607,6 +1613,7 @@ impl Bank {
|
||||
&self.blockhash_queue.read().unwrap(),
|
||||
error_counters,
|
||||
&self.rent_collector,
|
||||
&self.feature_set,
|
||||
)
|
||||
}
|
||||
fn check_age(
|
||||
@ -2045,8 +2052,7 @@ impl Bank {
|
||||
log_collector.clone(),
|
||||
executors.clone(),
|
||||
instruction_recorders.as_deref(),
|
||||
self.cluster_type(),
|
||||
self.epoch(),
|
||||
&self.feature_set,
|
||||
);
|
||||
|
||||
Self::compile_recorded_instructions(
|
||||
@ -2127,6 +2133,11 @@ impl Bank {
|
||||
) -> Vec<Result<()>> {
|
||||
let hash_queue = self.blockhash_queue.read().unwrap();
|
||||
let mut fees = 0;
|
||||
|
||||
let fee_config = FeeConfig {
|
||||
secp256k1_program_enabled: self.secp256k1_program_enabled(),
|
||||
};
|
||||
|
||||
let results = OrderedIterator::new(txs, iteration_order)
|
||||
.zip(executed.iter())
|
||||
.map(|((_, tx), (res, hash_age_kind))| {
|
||||
@ -2143,10 +2154,7 @@ impl Bank {
|
||||
};
|
||||
let fee_calculator = fee_calculator.ok_or(TransactionError::BlockhashNotFound)?;
|
||||
|
||||
let fee = fee_calculator.calculate_fee(
|
||||
tx.message(),
|
||||
solana_sdk::secp256k1::get_fee_config(self.cluster_type(), self.epoch()),
|
||||
);
|
||||
let fee = fee_calculator.calculate_fee_with_config(tx.message(), &fee_config);
|
||||
|
||||
let message = tx.message();
|
||||
match *res {
|
||||
@ -3486,10 +3494,16 @@ impl Bank {
|
||||
consumed_budget.saturating_sub(budget_recovery_delta)
|
||||
}
|
||||
|
||||
pub fn secp256k1_program_enabled(&self) -> bool {
|
||||
self.feature_set
|
||||
.is_active(&feature_set::secp256k1_program_enabled::id())
|
||||
}
|
||||
|
||||
// This is called from snapshot restore AND for each epoch boundary
|
||||
// The entire code path herein must be idempotent
|
||||
fn apply_feature_activations(&mut self, init_finish_or_warp: bool, initiate_callback: bool) {
|
||||
self.ensure_builtins(init_finish_or_warp);
|
||||
let new_feature_activations = self.compute_active_feature_set(!init_finish_or_warp);
|
||||
self.ensure_builtins(init_finish_or_warp, &new_feature_activations);
|
||||
self.reinvoke_entered_epoch_callback(initiate_callback);
|
||||
self.recheck_cross_program_support();
|
||||
self.recheck_compute_budget();
|
||||
@ -3497,14 +3511,66 @@ impl Bank {
|
||||
self.ensure_no_storage_rewards_pool();
|
||||
}
|
||||
|
||||
fn ensure_builtins(&mut self, init_or_warp: bool) {
|
||||
for (program, start_epoch) in get_builtins(self.cluster_type()) {
|
||||
// Compute the active feature set based on the current bank state, and return the set of newly activated features
|
||||
fn compute_active_feature_set(&mut self, allow_new_activations: bool) -> HashSet<Pubkey> {
|
||||
let mut active = self.feature_set.active.clone();
|
||||
let mut inactive = HashSet::new();
|
||||
let mut newly_activated = HashSet::new();
|
||||
let slot = self.slot();
|
||||
|
||||
for feature_id in &self.feature_set.inactive {
|
||||
let mut activated = false;
|
||||
if let Some(mut account) = self.get_account(feature_id) {
|
||||
if let Some(mut feature) = Feature::from_account(&account) {
|
||||
match feature.activated_at {
|
||||
None => {
|
||||
if allow_new_activations {
|
||||
// Feature has been requested, activate it now
|
||||
feature.activated_at = Some(slot);
|
||||
if feature.to_account(&mut account).is_some() {
|
||||
self.store_account(feature_id, &account);
|
||||
}
|
||||
newly_activated.insert(*feature_id);
|
||||
activated = true;
|
||||
info!("Feature {} activated at slot {}", feature_id, slot);
|
||||
}
|
||||
}
|
||||
Some(activation_slot) => {
|
||||
if slot >= activation_slot {
|
||||
// Feature is already active
|
||||
activated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if activated {
|
||||
active.insert(*feature_id);
|
||||
} else {
|
||||
inactive.insert(*feature_id);
|
||||
}
|
||||
}
|
||||
|
||||
self.feature_set = Arc::new(FeatureSet { active, inactive });
|
||||
newly_activated
|
||||
}
|
||||
|
||||
fn ensure_builtins(&mut self, init_or_warp: bool, new_feature_activations: &HashSet<Pubkey>) {
|
||||
for (program, start_epoch) in get_cluster_builtins(self.cluster_type()) {
|
||||
let should_populate = init_or_warp && self.epoch() >= start_epoch
|
||||
|| !init_or_warp && self.epoch() == start_epoch;
|
||||
if should_populate {
|
||||
self.add_builtin(&program.name, program.id, program.entrypoint);
|
||||
}
|
||||
}
|
||||
|
||||
for (program, feature) in get_feature_builtins() {
|
||||
let should_populate = init_or_warp && self.feature_set.is_active(&feature)
|
||||
|| !init_or_warp && new_feature_activations.contains(&feature);
|
||||
if should_populate {
|
||||
self.add_builtin(&program.name, program.id, program.entrypoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn reinvoke_entered_epoch_callback(&mut self, initiate: bool) {
|
||||
@ -8508,7 +8574,7 @@ mod tests {
|
||||
.collect::<Vec<_>>();
|
||||
consumed_budgets.sort();
|
||||
// consumed_budgets represents the count of alive accounts in the three slots 0,1,2
|
||||
assert_eq!(consumed_budgets, vec![0, 1, 10]);
|
||||
assert_eq!(consumed_budgets, vec![0, 1, 9]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -8973,4 +9039,59 @@ mod tests {
|
||||
assert!(executors.borrow().executors.contains_key(&key3));
|
||||
assert!(executors.borrow().executors.contains_key(&key4));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_compute_active_feature_set() {
|
||||
let (genesis_config, _mint_keypair) = create_genesis_config(100_000);
|
||||
let bank0 = Arc::new(Bank::new(&genesis_config));
|
||||
let mut bank = Bank::new_from_parent(&bank0, &Pubkey::default(), 1);
|
||||
|
||||
let test_feature = "TestFeature11111111111111111111111111111111"
|
||||
.parse::<Pubkey>()
|
||||
.unwrap();
|
||||
let mut feature_set = FeatureSet::default();
|
||||
feature_set.inactive.insert(test_feature);
|
||||
bank.feature_set = Arc::new(feature_set.clone());
|
||||
|
||||
let new_activations = bank.compute_active_feature_set(true);
|
||||
assert!(new_activations.is_empty());
|
||||
assert!(!bank.feature_set.is_active(&test_feature));
|
||||
|
||||
// Depositing into the `test_feature` account should do nothing
|
||||
bank.deposit(&test_feature, 42);
|
||||
let new_activations = bank.compute_active_feature_set(true);
|
||||
assert!(new_activations.is_empty());
|
||||
assert!(!bank.feature_set.is_active(&test_feature));
|
||||
|
||||
// Request `test_feature` activation
|
||||
let feature = Feature::default();
|
||||
assert_eq!(feature.activated_at, None);
|
||||
bank.store_account(&test_feature, &feature.create_account(42));
|
||||
|
||||
// Run `compute_active_feature_set` disallowing new activations
|
||||
let new_activations = bank.compute_active_feature_set(false);
|
||||
assert!(new_activations.is_empty());
|
||||
assert!(!bank.feature_set.is_active(&test_feature));
|
||||
let feature = Feature::from_account(&bank.get_account(&test_feature).expect("get_account"))
|
||||
.expect("from_account");
|
||||
assert_eq!(feature.activated_at, None);
|
||||
|
||||
// Run `compute_active_feature_set` allowing new activations
|
||||
let new_activations = bank.compute_active_feature_set(true);
|
||||
assert_eq!(new_activations.len(), 1);
|
||||
assert!(bank.feature_set.is_active(&test_feature));
|
||||
let feature = Feature::from_account(&bank.get_account(&test_feature).expect("get_account"))
|
||||
.expect("from_account");
|
||||
assert_eq!(feature.activated_at, Some(1));
|
||||
|
||||
// Reset the bank's feature set
|
||||
bank.feature_set = Arc::new(feature_set);
|
||||
assert!(!bank.feature_set.is_active(&test_feature));
|
||||
|
||||
// Running `compute_active_feature_set` will not cause new activations, but
|
||||
// `test_feature` is now be active
|
||||
let new_activations = bank.compute_active_feature_set(true);
|
||||
assert!(new_activations.is_empty());
|
||||
assert!(bank.feature_set.is_active(&test_feature));
|
||||
}
|
||||
}
|
||||
|
@ -1,18 +1,21 @@
|
||||
use crate::{
|
||||
bank::{Builtin, Entrypoint},
|
||||
system_instruction_processor,
|
||||
feature_set, system_instruction_processor,
|
||||
};
|
||||
use solana_sdk::{
|
||||
clock::{Epoch, GENESIS_EPOCH},
|
||||
genesis_config::ClusterType,
|
||||
pubkey::Pubkey,
|
||||
system_program,
|
||||
};
|
||||
|
||||
use log::*;
|
||||
|
||||
/// The entire set of available builtin programs that should be active at the given cluster_type
|
||||
pub fn get_builtins(cluster_type: ClusterType) -> Vec<(Builtin, Epoch)> {
|
||||
trace!("get_builtins: {:?}", cluster_type);
|
||||
/// Builtin programs that should be active for the given cluster_type
|
||||
///
|
||||
/// Old style. Use `get_feature_builtins()` instead
|
||||
pub fn get_cluster_builtins(cluster_type: ClusterType) -> Vec<(Builtin, Epoch)> {
|
||||
trace!("get_cluster_builtins: {:?}", cluster_type);
|
||||
let mut builtins = vec![];
|
||||
|
||||
builtins.extend(
|
||||
@ -46,8 +49,8 @@ pub fn get_builtins(cluster_type: ClusterType) -> Vec<(Builtin, Epoch)> {
|
||||
// repurpose Testnet for test_get_builtins because the Development is overloaded...
|
||||
#[cfg(test)]
|
||||
if cluster_type == ClusterType::Testnet {
|
||||
use solana_sdk::account::KeyedAccount;
|
||||
use solana_sdk::instruction::InstructionError;
|
||||
use solana_sdk::{account::KeyedAccount, pubkey::Pubkey};
|
||||
use std::str::FromStr;
|
||||
fn mock_ix_processor(
|
||||
_pubkey: &Pubkey,
|
||||
@ -57,35 +60,33 @@ pub fn get_builtins(cluster_type: ClusterType) -> Vec<(Builtin, Epoch)> {
|
||||
Err(InstructionError::Custom(42))
|
||||
}
|
||||
let program_id = Pubkey::from_str("7saCc6X5a2syoYANA5oUUnPZLcLMfKoSjiDhFU5fbpoK").unwrap();
|
||||
builtins.extend(vec![(
|
||||
builtins.push((
|
||||
Builtin::new("mock", program_id, Entrypoint::Program(mock_ix_processor)),
|
||||
2,
|
||||
)]);
|
||||
));
|
||||
}
|
||||
|
||||
let secp256k1_builtin = Builtin::new(
|
||||
"secp256k1_program",
|
||||
solana_sdk::secp256k1_program::id(),
|
||||
Entrypoint::Program(solana_secp256k1_program::process_instruction),
|
||||
);
|
||||
let secp_epoch = solana_sdk::secp256k1::is_enabled_epoch(cluster_type);
|
||||
builtins.push((secp256k1_builtin, secp_epoch));
|
||||
|
||||
builtins
|
||||
}
|
||||
|
||||
/// Builtin programs that are activated dynamically by feature
|
||||
pub fn get_feature_builtins() -> Vec<(Builtin, Pubkey)> {
|
||||
vec![(
|
||||
Builtin::new(
|
||||
"secp256k1_program",
|
||||
solana_sdk::secp256k1_program::id(),
|
||||
Entrypoint::Program(solana_secp256k1_program::process_instruction),
|
||||
),
|
||||
feature_set::secp256k1_program_enabled::id(),
|
||||
)]
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::bank::Bank;
|
||||
use solana_sdk::{
|
||||
genesis_config::{create_genesis_config, ClusterType},
|
||||
pubkey::Pubkey,
|
||||
};
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::str::FromStr;
|
||||
use std::sync::Arc;
|
||||
use solana_sdk::genesis_config::create_genesis_config;
|
||||
use std::{collections::HashSet, str::FromStr, sync::Arc};
|
||||
|
||||
fn do_test_uniqueness(builtins: Vec<(Builtin, Epoch)>) {
|
||||
let mut unique_ids = HashSet::new();
|
||||
@ -101,10 +102,10 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_uniqueness() {
|
||||
do_test_uniqueness(get_builtins(ClusterType::Development));
|
||||
do_test_uniqueness(get_builtins(ClusterType::Devnet));
|
||||
do_test_uniqueness(get_builtins(ClusterType::Testnet));
|
||||
do_test_uniqueness(get_builtins(ClusterType::MainnetBeta));
|
||||
do_test_uniqueness(get_cluster_builtins(ClusterType::Development));
|
||||
do_test_uniqueness(get_cluster_builtins(ClusterType::Devnet));
|
||||
do_test_uniqueness(get_cluster_builtins(ClusterType::Testnet));
|
||||
do_test_uniqueness(get_cluster_builtins(ClusterType::MainnetBeta));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
42
runtime/src/feature.rs
Normal file
42
runtime/src/feature.rs
Normal file
@ -0,0 +1,42 @@
|
||||
use solana_sdk::{account::Account, clock::Slot};
|
||||
|
||||
solana_sdk::declare_id!("Feature111111111111111111111111111111111111");
|
||||
|
||||
/// The `Feature` struct is the on-chain representation of a runtime feature.
|
||||
///
|
||||
/// Feature activation is accomplished by:
|
||||
/// 1. Activation is requested by the feature authority, who issues a transaction to create the
|
||||
/// feature account. The newly created feature account will have the value of
|
||||
/// `Feature::default()`
|
||||
/// 2. When the next epoch is entered the runtime will check for new activation requests and
|
||||
/// active them. When this occurs, the activation slot is recorded in the feature account
|
||||
///
|
||||
#[derive(Default, Debug, Serialize, Deserialize)]
|
||||
pub struct Feature {
|
||||
pub activated_at: Option<Slot>,
|
||||
}
|
||||
|
||||
impl Feature {
|
||||
pub fn size_of() -> usize {
|
||||
bincode::serialized_size(&Self {
|
||||
activated_at: Some(Slot::MAX),
|
||||
})
|
||||
.unwrap() as usize
|
||||
}
|
||||
pub fn from_account(account: &Account) -> Option<Self> {
|
||||
if account.owner != id() {
|
||||
None
|
||||
} else {
|
||||
bincode::deserialize(&account.data).ok()
|
||||
}
|
||||
}
|
||||
pub fn to_account(&self, account: &mut Account) -> Option<()> {
|
||||
bincode::serialize_into(&mut account.data[..], self).ok()
|
||||
}
|
||||
pub fn create_account(&self, lamports: u64) -> Account {
|
||||
let data_len = Self::size_of().max(bincode::serialized_size(self).unwrap() as usize);
|
||||
let mut account = Account::new(lamports, data_len, &id());
|
||||
self.to_account(&mut account).unwrap();
|
||||
account
|
||||
}
|
||||
}
|
69
runtime/src/feature_set.rs
Normal file
69
runtime/src/feature_set.rs
Normal file
@ -0,0 +1,69 @@
|
||||
use lazy_static::lazy_static;
|
||||
use solana_sdk::{
|
||||
hash::{Hash, Hasher},
|
||||
pubkey::Pubkey,
|
||||
};
|
||||
use std::collections::{HashMap, HashSet};
|
||||
|
||||
pub mod instructions_sysvar_enabled {
|
||||
solana_sdk::declare_id!("EnvhHCLvg55P7PDtbvR1NwuTuAeodqpusV3MR5QEK8gs");
|
||||
}
|
||||
|
||||
pub mod secp256k1_program_enabled {
|
||||
solana_sdk::declare_id!("E3PHP7w8kB7np3CTQ1qQ2tW3KCtjRSXBQgW9vM2mWv2Y");
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
/// Map of feature identifiers to user-visible description
|
||||
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [
|
||||
(instructions_sysvar_enabled::id(), "instructions sysvar"),
|
||||
(secp256k1_program_enabled::id(), "secp256k1 program")
|
||||
/*************** ADD NEW FEATURES HERE ***************/
|
||||
]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
/// Unique identifier of the current software's feature set
|
||||
pub static ref ID: Hash = {
|
||||
let mut hasher = Hasher::default();
|
||||
let mut feature_ids = FEATURE_NAMES.keys().collect::<Vec<_>>();
|
||||
feature_ids.sort();
|
||||
for feature in feature_ids {
|
||||
hasher.hash(feature.as_ref());
|
||||
}
|
||||
hasher.result()
|
||||
};
|
||||
}
|
||||
|
||||
/// `FeatureSet` holds the set of currently active/inactive runtime features
|
||||
#[derive(AbiExample, Clone)]
|
||||
pub struct FeatureSet {
|
||||
pub active: HashSet<Pubkey>,
|
||||
pub inactive: HashSet<Pubkey>,
|
||||
}
|
||||
|
||||
impl FeatureSet {
|
||||
pub fn is_active(&self, feature_id: &Pubkey) -> bool {
|
||||
self.active.contains(feature_id)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FeatureSet {
|
||||
fn default() -> Self {
|
||||
// All features disabled
|
||||
Self {
|
||||
active: HashSet::new(),
|
||||
inactive: FEATURE_NAMES.keys().cloned().collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FeatureSet {
|
||||
pub fn enabled() -> Self {
|
||||
Self {
|
||||
active: FEATURE_NAMES.keys().cloned().collect(),
|
||||
inactive: HashSet::new(),
|
||||
}
|
||||
}
|
||||
}
|
@ -1,7 +1,8 @@
|
||||
use crate::{feature::Feature, feature_set::FeatureSet};
|
||||
use solana_sdk::{
|
||||
account::Account,
|
||||
fee_calculator::FeeRateGovernor,
|
||||
genesis_config::GenesisConfig,
|
||||
genesis_config::{ClusterType, GenesisConfig},
|
||||
pubkey::Pubkey,
|
||||
rent::Rent,
|
||||
signature::{Keypair, Signer},
|
||||
@ -107,6 +108,24 @@ pub fn create_genesis_config_with_leader(
|
||||
)
|
||||
}
|
||||
|
||||
pub fn add_feature_accounts(genesis_config: &mut GenesisConfig) {
|
||||
if genesis_config.cluster_type == ClusterType::Development {
|
||||
// Activate all features at genesis in development mode
|
||||
for feature_id in FeatureSet::default().inactive {
|
||||
let feature = Feature {
|
||||
activated_at: Some(0),
|
||||
};
|
||||
genesis_config.accounts.insert(
|
||||
feature_id,
|
||||
feature.create_account(std::cmp::max(
|
||||
genesis_config.rent.minimum_balance(Feature::size_of()),
|
||||
1,
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_genesis_config_with_leader_ex(
|
||||
mint_lamports: u64,
|
||||
bootstrap_validator_pubkey: &Pubkey,
|
||||
@ -164,6 +183,7 @@ pub fn create_genesis_config_with_leader_ex(
|
||||
};
|
||||
|
||||
solana_stake_program::add_genesis_accounts(&mut genesis_config);
|
||||
add_feature_accounts(&mut genesis_config);
|
||||
|
||||
GenesisConfigInfo {
|
||||
genesis_config,
|
||||
|
@ -12,6 +12,8 @@ pub mod bloom;
|
||||
pub mod builtins;
|
||||
pub mod commitment;
|
||||
pub mod epoch_stakes;
|
||||
pub mod feature;
|
||||
pub mod feature_set;
|
||||
pub mod genesis_utils;
|
||||
pub mod hardened_unpack;
|
||||
pub mod instruction_recorder;
|
||||
|
@ -1,6 +1,9 @@
|
||||
use crate::{
|
||||
instruction_recorder::InstructionRecorder, log_collector::LogCollector,
|
||||
native_loader::NativeLoader, rent_collector::RentCollector,
|
||||
feature_set::{self, FeatureSet},
|
||||
instruction_recorder::InstructionRecorder,
|
||||
log_collector::LogCollector,
|
||||
native_loader::NativeLoader,
|
||||
rent_collector::RentCollector,
|
||||
};
|
||||
use log::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
@ -11,7 +14,6 @@ use solana_sdk::{
|
||||
ComputeBudget, ComputeMeter, ErasedProcessInstruction, ErasedProcessInstructionWithContext,
|
||||
Executor, InvokeContext, Logger, ProcessInstruction, ProcessInstructionWithContext,
|
||||
},
|
||||
genesis_config::ClusterType,
|
||||
instruction::{CompiledInstruction, Instruction, InstructionError},
|
||||
message::Message,
|
||||
native_loader,
|
||||
@ -679,12 +681,11 @@ impl MessageProcessor {
|
||||
executors: Rc<RefCell<Executors>>,
|
||||
instruction_recorder: Option<InstructionRecorder>,
|
||||
instruction_index: usize,
|
||||
cluster_type: ClusterType,
|
||||
epoch: Epoch,
|
||||
feature_set: &FeatureSet,
|
||||
) -> Result<(), InstructionError> {
|
||||
// Fixup the special instructions key if present
|
||||
// before the account pre-values are taken care of
|
||||
if solana_sdk::sysvar::instructions::is_enabled(epoch, cluster_type) {
|
||||
if feature_set.is_active(&feature_set::instructions_sysvar_enabled::id()) {
|
||||
for (i, key) in message.account_keys.iter().enumerate() {
|
||||
if solana_sdk::sysvar::instructions::check_id(key) {
|
||||
let mut mut_account_ref = accounts[i].borrow_mut();
|
||||
@ -736,8 +737,7 @@ impl MessageProcessor {
|
||||
log_collector: Option<Rc<LogCollector>>,
|
||||
executors: Rc<RefCell<Executors>>,
|
||||
instruction_recorders: Option<&[InstructionRecorder]>,
|
||||
cluster_type: ClusterType,
|
||||
epoch: Epoch,
|
||||
feature_set: &FeatureSet,
|
||||
) -> Result<(), TransactionError> {
|
||||
for (instruction_index, instruction) in message.instructions.iter().enumerate() {
|
||||
let instruction_recorder = instruction_recorders
|
||||
@ -753,8 +753,7 @@ impl MessageProcessor {
|
||||
executors.clone(),
|
||||
instruction_recorder,
|
||||
instruction_index,
|
||||
cluster_type,
|
||||
epoch,
|
||||
feature_set,
|
||||
)
|
||||
.map_err(|err| TransactionError::InstructionError(instruction_index as u8, err))?;
|
||||
}
|
||||
@ -1349,8 +1348,7 @@ mod tests {
|
||||
None,
|
||||
executors.clone(),
|
||||
None,
|
||||
ClusterType::Development,
|
||||
0,
|
||||
&FeatureSet::default(),
|
||||
);
|
||||
assert_eq!(result, Ok(()));
|
||||
assert_eq!(accounts[0].borrow().lamports, 100);
|
||||
@ -1373,8 +1371,7 @@ mod tests {
|
||||
None,
|
||||
executors.clone(),
|
||||
None,
|
||||
ClusterType::Development,
|
||||
0,
|
||||
&FeatureSet::default(),
|
||||
);
|
||||
assert_eq!(
|
||||
result,
|
||||
@ -1401,8 +1398,7 @@ mod tests {
|
||||
None,
|
||||
executors,
|
||||
None,
|
||||
ClusterType::Development,
|
||||
0,
|
||||
&FeatureSet::default(),
|
||||
);
|
||||
assert_eq!(
|
||||
result,
|
||||
@ -1512,8 +1508,7 @@ mod tests {
|
||||
None,
|
||||
executors.clone(),
|
||||
None,
|
||||
ClusterType::Development,
|
||||
0,
|
||||
&FeatureSet::default(),
|
||||
);
|
||||
assert_eq!(
|
||||
result,
|
||||
@ -1540,8 +1535,7 @@ mod tests {
|
||||
None,
|
||||
executors.clone(),
|
||||
None,
|
||||
ClusterType::Development,
|
||||
0,
|
||||
&FeatureSet::default(),
|
||||
);
|
||||
assert_eq!(result, Ok(()));
|
||||
|
||||
@ -1565,8 +1559,7 @@ mod tests {
|
||||
None,
|
||||
executors,
|
||||
None,
|
||||
ClusterType::Development,
|
||||
0,
|
||||
&FeatureSet::default(),
|
||||
);
|
||||
assert_eq!(result, Ok(()));
|
||||
assert_eq!(accounts[0].borrow().lamports, 80);
|
||||
|
@ -19,9 +19,16 @@ impl Default for FeeCalculator {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct FeeConfig {
|
||||
pub is_secp256k1_enabled: bool,
|
||||
pub secp256k1_program_enabled: bool,
|
||||
}
|
||||
|
||||
impl Default for FeeConfig {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
secp256k1_program_enabled: true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FeeCalculator {
|
||||
@ -31,27 +38,27 @@ impl FeeCalculator {
|
||||
}
|
||||
}
|
||||
|
||||
// extra_config: None == everything enabled
|
||||
pub fn calculate_fee(&self, message: &Message, extra_config: Option<FeeConfig>) -> u64 {
|
||||
let is_secp256k1_enabled = match extra_config {
|
||||
Some(config) => config.is_secp256k1_enabled,
|
||||
None => true,
|
||||
};
|
||||
let mut num_secp_signatures: u64 = 0;
|
||||
if is_secp256k1_enabled {
|
||||
pub fn calculate_fee(&self, message: &Message) -> u64 {
|
||||
self.calculate_fee_with_config(message, &FeeConfig::default())
|
||||
}
|
||||
|
||||
pub fn calculate_fee_with_config(&self, message: &Message, fee_config: &FeeConfig) -> u64 {
|
||||
let mut num_secp256k1_signatures: u64 = 0;
|
||||
if fee_config.secp256k1_program_enabled {
|
||||
for instruction in &message.instructions {
|
||||
let program_index = instruction.program_id_index as usize;
|
||||
// Transaction may not be sanitized here
|
||||
if program_index < message.account_keys.len() {
|
||||
let id = message.account_keys[program_index];
|
||||
if secp256k1_program::check_id(&id) && !instruction.data.is_empty() {
|
||||
num_secp_signatures += instruction.data[0] as u64;
|
||||
num_secp256k1_signatures += instruction.data[0] as u64;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.lamports_per_signature
|
||||
* (u64::from(message.header.num_required_signatures) + num_secp_signatures)
|
||||
* (u64::from(message.header.num_required_signatures) + num_secp256k1_signatures)
|
||||
}
|
||||
}
|
||||
|
||||
@ -182,9 +189,7 @@ impl FeeRateGovernor {
|
||||
|
||||
/// create a FeeCalculator based on current cluster signature throughput
|
||||
pub fn create_fee_calculator(&self) -> FeeCalculator {
|
||||
FeeCalculator {
|
||||
lamports_per_signature: self.lamports_per_signature,
|
||||
}
|
||||
FeeCalculator::new(self.lamports_per_signature)
|
||||
}
|
||||
}
|
||||
|
||||
@ -207,34 +212,25 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_fee_calculator_calculate_fee() {
|
||||
let fee_config = Some(FeeConfig {
|
||||
is_secp256k1_enabled: true,
|
||||
});
|
||||
// Default: no fee.
|
||||
let message = Message::default();
|
||||
assert_eq!(
|
||||
FeeCalculator::default().calculate_fee(&message, fee_config.clone()),
|
||||
0
|
||||
);
|
||||
assert_eq!(FeeCalculator::default().calculate_fee(&message), 0);
|
||||
|
||||
// No signature, no fee.
|
||||
assert_eq!(FeeCalculator::new(1).calculate_fee(&message, fee_config), 0);
|
||||
assert_eq!(FeeCalculator::new(1).calculate_fee(&message), 0);
|
||||
|
||||
let fee_config = Some(FeeConfig {
|
||||
is_secp256k1_enabled: false,
|
||||
});
|
||||
// One signature, a fee.
|
||||
let pubkey0 = Pubkey::new(&[0; 32]);
|
||||
let pubkey1 = Pubkey::new(&[1; 32]);
|
||||
let ix0 = system_instruction::transfer(&pubkey0, &pubkey1, 1);
|
||||
let message = Message::new(&[ix0], Some(&pubkey0));
|
||||
assert_eq!(FeeCalculator::new(2).calculate_fee(&message, fee_config), 2);
|
||||
assert_eq!(FeeCalculator::new(2).calculate_fee(&message), 2);
|
||||
|
||||
// Two signatures, double the fee.
|
||||
let ix0 = system_instruction::transfer(&pubkey0, &pubkey1, 1);
|
||||
let ix1 = system_instruction::transfer(&pubkey1, &pubkey0, 1);
|
||||
let message = Message::new(&[ix0, ix1], Some(&pubkey0));
|
||||
assert_eq!(FeeCalculator::new(2).calculate_fee(&message, None), 4);
|
||||
assert_eq!(FeeCalculator::new(2).calculate_fee(&message), 4);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -262,21 +258,21 @@ mod tests {
|
||||
],
|
||||
Some(&pubkey0),
|
||||
);
|
||||
let fee_config = Some(FeeConfig {
|
||||
is_secp256k1_enabled: true,
|
||||
});
|
||||
assert_eq!(FeeCalculator::new(1).calculate_fee(&message), 2);
|
||||
assert_eq!(
|
||||
FeeCalculator::new(1).calculate_fee(&message, fee_config.clone()),
|
||||
2
|
||||
FeeCalculator::new(1).calculate_fee_with_config(
|
||||
&message,
|
||||
&FeeConfig {
|
||||
secp256k1_program_enabled: false
|
||||
}
|
||||
),
|
||||
1
|
||||
);
|
||||
|
||||
secp_instruction.data = vec![0];
|
||||
secp_instruction2.data = vec![10];
|
||||
let message = Message::new(&[ix0, secp_instruction, secp_instruction2], Some(&pubkey0));
|
||||
assert_eq!(
|
||||
FeeCalculator::new(1).calculate_fee(&message, fee_config),
|
||||
11
|
||||
);
|
||||
assert_eq!(FeeCalculator::new(1).calculate_fee(&message), 11);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -1,28 +1,6 @@
|
||||
use crate::clock::{Epoch, GENESIS_EPOCH};
|
||||
use crate::fee_calculator::FeeConfig;
|
||||
use crate::genesis_config::ClusterType;
|
||||
use digest::Digest;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
pub fn get_fee_config(cluster_type: ClusterType, epoch: Epoch) -> Option<FeeConfig> {
|
||||
Some(FeeConfig {
|
||||
is_secp256k1_enabled: is_enabled(cluster_type, epoch),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn is_enabled_epoch(cluster_type: ClusterType) -> Epoch {
|
||||
match cluster_type {
|
||||
ClusterType::Development => GENESIS_EPOCH,
|
||||
ClusterType::Testnet => u64::MAX,
|
||||
ClusterType::MainnetBeta => u64::MAX,
|
||||
ClusterType::Devnet => u64::MAX,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_enabled(cluster_type: ClusterType, epoch: Epoch) -> bool {
|
||||
epoch >= is_enabled_epoch(cluster_type)
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum Secp256k1Error {
|
||||
InvalidSignature,
|
||||
|
@ -11,16 +11,6 @@ crate::declare_sysvar_id!("Sysvar1nstructions1111111111111111111111111", Instruc
|
||||
|
||||
impl Sysvar for Instructions {}
|
||||
|
||||
#[cfg(not(feature = "program"))]
|
||||
use crate::clock::Epoch;
|
||||
#[cfg(not(feature = "program"))]
|
||||
use crate::genesis_config::ClusterType;
|
||||
|
||||
#[cfg(not(feature = "program"))]
|
||||
pub fn is_enabled(_epoch: Epoch, cluster_type: ClusterType) -> bool {
|
||||
cluster_type == ClusterType::Development
|
||||
}
|
||||
|
||||
pub fn load_current_index(data: &[u8]) -> u16 {
|
||||
let mut instr_fixed_data = [0u8; 2];
|
||||
let len = data.len();
|
||||
|
@ -430,7 +430,7 @@ fn transact(
|
||||
info!("{} transactions to send", transactions.len());
|
||||
|
||||
let required_fee = transactions.iter().fold(0, |fee, (transaction, _)| {
|
||||
fee + fee_calculator.calculate_fee(&transaction.message, None)
|
||||
fee + fee_calculator.calculate_fee(&transaction.message)
|
||||
});
|
||||
info!("Required fee: {} SOL", lamports_to_sol(required_fee));
|
||||
if required_fee > authorized_staker_balance {
|
||||
|
@ -14,6 +14,7 @@ serde = "1.0.112"
|
||||
serde_derive = "1.0.103"
|
||||
solana-logger = { path = "../logger", version = "1.3.14" }
|
||||
solana-sdk = { path = "../sdk", version = "1.3.14" }
|
||||
solana-runtime = { path = "../runtime", version = "1.3.14" }
|
||||
solana-sdk-macro-frozen-abi = { path = "../sdk/macro-frozen-abi", version = "1.3.14" }
|
||||
|
||||
[lib]
|
||||
|
@ -3,19 +3,42 @@
|
||||
extern crate serde_derive;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use solana_sdk::sanitize::Sanitize;
|
||||
use std::fmt;
|
||||
|
||||
use std::{convert::TryInto, fmt};
|
||||
#[macro_use]
|
||||
extern crate solana_sdk_macro_frozen_abi;
|
||||
|
||||
// Older version structure used earlier 1.3.x releases
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, AbiExample)]
|
||||
pub struct Version {
|
||||
pub struct LegacyVersion {
|
||||
major: u16,
|
||||
minor: u16,
|
||||
patch: u16,
|
||||
commit: Option<u32>, // first 4 bytes of the sha1 commit hash
|
||||
}
|
||||
|
||||
impl Sanitize for LegacyVersion {}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, PartialEq, AbiExample)]
|
||||
pub struct Version {
|
||||
pub major: u16,
|
||||
pub minor: u16,
|
||||
pub patch: u16,
|
||||
pub commit: Option<u32>, // first 4 bytes of the sha1 commit hash
|
||||
pub feature_set: u32, // first 4 bytes of the FeatureSet identifier
|
||||
}
|
||||
|
||||
impl From<LegacyVersion> for Version {
|
||||
fn from(legacy_version: LegacyVersion) -> Self {
|
||||
Self {
|
||||
major: legacy_version.major,
|
||||
minor: legacy_version.minor,
|
||||
patch: legacy_version.patch,
|
||||
commit: legacy_version.commit,
|
||||
feature_set: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_commit(sha1: Option<&'static str>) -> Option<u32> {
|
||||
let sha1 = sha1?;
|
||||
if sha1.len() < 8 {
|
||||
@ -27,27 +50,40 @@ fn compute_commit(sha1: Option<&'static str>) -> Option<u32> {
|
||||
|
||||
impl Default for Version {
|
||||
fn default() -> Self {
|
||||
let feature_set = u32::from_le_bytes(
|
||||
solana_runtime::feature_set::ID.as_ref()[..4]
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
);
|
||||
Self {
|
||||
major: env!("CARGO_PKG_VERSION_MAJOR").parse().unwrap(),
|
||||
minor: env!("CARGO_PKG_VERSION_MINOR").parse().unwrap(),
|
||||
patch: env!("CARGO_PKG_VERSION_PATCH").parse().unwrap(),
|
||||
commit: compute_commit(option_env!("CI_COMMIT")),
|
||||
feature_set,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Version {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "{}.{}.{}", self.major, self.minor, self.patch,)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Version {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}.{}.{} {}",
|
||||
"{}.{}.{} (src:{}; feat:{})",
|
||||
self.major,
|
||||
self.minor,
|
||||
self.patch,
|
||||
match self.commit {
|
||||
None => "devbuild".to_string(),
|
||||
Some(commit) => format!("{:08x}", commit),
|
||||
}
|
||||
},
|
||||
self.feature_set,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user