Runtime feature activation framework
This commit is contained in:
parent
d00453f747
commit
93259f0bae
|
@ -526,6 +526,7 @@ fn main() -> Result<(), Box<dyn error::Error>> {
|
||||||
}
|
}
|
||||||
|
|
||||||
solana_stake_program::add_genesis_accounts(&mut genesis_config);
|
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") {
|
if let Some(files) = matches.values_of("primordial_accounts_file") {
|
||||||
for file in files {
|
for file in files {
|
||||||
|
|
|
@ -13,6 +13,8 @@ use crate::{
|
||||||
builtins::get_builtins,
|
builtins::get_builtins,
|
||||||
epoch_stakes::{EpochStakes, NodeVoteAccounts},
|
epoch_stakes::{EpochStakes, NodeVoteAccounts},
|
||||||
instruction_recorder::InstructionRecorder,
|
instruction_recorder::InstructionRecorder,
|
||||||
|
feature::Feature,
|
||||||
|
feature_set::{FeatureSet},
|
||||||
log_collector::LogCollector,
|
log_collector::LogCollector,
|
||||||
message_processor::{Executors, MessageProcessor},
|
message_processor::{Executors, MessageProcessor},
|
||||||
nonce_utils,
|
nonce_utils,
|
||||||
|
@ -526,6 +528,8 @@ pub struct Bank {
|
||||||
cached_executors: Arc<RwLock<CachedExecutors>>,
|
cached_executors: Arc<RwLock<CachedExecutors>>,
|
||||||
|
|
||||||
transaction_debug_keys: Option<Arc<HashSet<Pubkey>>>,
|
transaction_debug_keys: Option<Arc<HashSet<Pubkey>>>,
|
||||||
|
|
||||||
|
pub feature_set: Arc<FeatureSet>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for BlockhashQueue {
|
impl Default for BlockhashQueue {
|
||||||
|
@ -649,6 +653,7 @@ impl Bank {
|
||||||
rewards_pool_pubkeys: parent.rewards_pool_pubkeys.clone(),
|
rewards_pool_pubkeys: parent.rewards_pool_pubkeys.clone(),
|
||||||
cached_executors: parent.cached_executors.clone(),
|
cached_executors: parent.cached_executors.clone(),
|
||||||
transaction_debug_keys: parent.transaction_debug_keys.clone(),
|
transaction_debug_keys: parent.transaction_debug_keys.clone(),
|
||||||
|
feature_set: parent.feature_set.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
datapoint_info!(
|
datapoint_info!(
|
||||||
|
@ -753,6 +758,7 @@ impl Bank {
|
||||||
rewards_pool_pubkeys: new(),
|
rewards_pool_pubkeys: new(),
|
||||||
cached_executors: Arc::new(RwLock::new(CachedExecutors::new(MAX_CACHED_EXECUTORS))),
|
cached_executors: Arc::new(RwLock::new(CachedExecutors::new(MAX_CACHED_EXECUTORS))),
|
||||||
transaction_debug_keys: debug_keys,
|
transaction_debug_keys: debug_keys,
|
||||||
|
feature_set: new(),
|
||||||
};
|
};
|
||||||
bank.finish_init(genesis_config);
|
bank.finish_init(genesis_config);
|
||||||
|
|
||||||
|
@ -3458,6 +3464,11 @@ impl Bank {
|
||||||
// This is called from snapshot restore AND for each epoch boundary
|
// This is called from snapshot restore AND for each epoch boundary
|
||||||
// The entire code path herein must be idempotent
|
// The entire code path herein must be idempotent
|
||||||
fn apply_feature_activations(&mut self, init_finish_or_warp: bool, initiate_callback: bool) {
|
fn apply_feature_activations(&mut self, init_finish_or_warp: bool, initiate_callback: bool) {
|
||||||
|
let new_feature_activations = self.compute_active_feature_set();
|
||||||
|
for feature_id in new_feature_activations {
|
||||||
|
info!("New feature activated: {}", feature_id);
|
||||||
|
}
|
||||||
|
|
||||||
self.ensure_builtins(init_finish_or_warp);
|
self.ensure_builtins(init_finish_or_warp);
|
||||||
self.reinvoke_entered_epoch_callback(initiate_callback);
|
self.reinvoke_entered_epoch_callback(initiate_callback);
|
||||||
self.recheck_cross_program_support();
|
self.recheck_cross_program_support();
|
||||||
|
@ -3466,6 +3477,48 @@ impl Bank {
|
||||||
self.ensure_no_storage_rewards_pool();
|
self.ensure_no_storage_rewards_pool();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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) -> 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 {
|
||||||
|
if let Some(mut account) = self.get_account(feature_id) {
|
||||||
|
if let Some(mut feature) = Feature::from_account(&account) {
|
||||||
|
match feature.activated_at {
|
||||||
|
None => {
|
||||||
|
// 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);
|
||||||
|
active.insert(*feature_id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Some(activation_slot) => {
|
||||||
|
if slot >= activation_slot {
|
||||||
|
// Feature is already active
|
||||||
|
active.insert(*feature_id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inactive.insert(*feature_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.feature_set = Arc::new(FeatureSet {
|
||||||
|
id: self.feature_set.id,
|
||||||
|
active,
|
||||||
|
inactive,
|
||||||
|
});
|
||||||
|
newly_activated
|
||||||
|
}
|
||||||
|
|
||||||
fn ensure_builtins(&mut self, init_or_warp: bool) {
|
fn ensure_builtins(&mut self, init_or_warp: bool) {
|
||||||
for (program, start_epoch) in get_builtins(self.cluster_type()) {
|
for (program, start_epoch) in get_builtins(self.cluster_type()) {
|
||||||
let should_populate = init_or_warp && self.epoch() >= start_epoch
|
let should_populate = init_or_warp && self.epoch() >= start_epoch
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
use solana_sdk::{
|
||||||
|
account::{Account, KeyedAccount},
|
||||||
|
account_info::AccountInfo,
|
||||||
|
clock::Slot,
|
||||||
|
instruction::InstructionError,
|
||||||
|
program_error::ProgramError,
|
||||||
|
};
|
||||||
|
|
||||||
|
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, Serialize, Deserialize)]
|
||||||
|
pub struct Feature {
|
||||||
|
pub activated_at: Option<Slot>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Feature {
|
||||||
|
pub fn size_of() -> usize {
|
||||||
|
bincode::serialized_size(&Self::default()).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 from_account_info(account_info: &AccountInfo) -> Result<Self, ProgramError> {
|
||||||
|
bincode::deserialize(&account_info.data.borrow()).map_err(|_| ProgramError::InvalidArgument)
|
||||||
|
}
|
||||||
|
pub fn to_account_info(&self, account_info: &mut AccountInfo) -> Option<()> {
|
||||||
|
bincode::serialize_into(&mut account_info.data.borrow_mut()[..], self).ok()
|
||||||
|
}
|
||||||
|
pub fn from_keyed_account(keyed_account: &KeyedAccount) -> Result<Self, InstructionError> {
|
||||||
|
Self::from_account(&*keyed_account.try_account_ref()?)
|
||||||
|
.ok_or(InstructionError::InvalidArgument)
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
use solana_sdk::{
|
||||||
|
hash::{Hash, Hasher},
|
||||||
|
pubkey::Pubkey,
|
||||||
|
};
|
||||||
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
/// The `FeatureSet` struct tracks the set of available and currently active runtime features
|
||||||
|
#[derive(AbiExample)]
|
||||||
|
pub struct FeatureSet {
|
||||||
|
/// Unique identifier of this feature set
|
||||||
|
pub id: Hash,
|
||||||
|
|
||||||
|
/// Features that are currently active
|
||||||
|
pub active: HashSet<Pubkey>,
|
||||||
|
|
||||||
|
/// Features that are currently inactive
|
||||||
|
pub inactive: HashSet<Pubkey>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FeatureSet {
|
||||||
|
pub fn active(&self, feature_id: &Pubkey) -> bool {
|
||||||
|
self.active.contains(feature_id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for FeatureSet {
|
||||||
|
// By default all features are disabled
|
||||||
|
fn default() -> Self {
|
||||||
|
let features: [Pubkey; 0] = [];
|
||||||
|
|
||||||
|
Self {
|
||||||
|
id: {
|
||||||
|
let mut hasher = Hasher::default();
|
||||||
|
for feature in features.iter() {
|
||||||
|
hasher.hash(feature.as_ref());
|
||||||
|
}
|
||||||
|
hasher.result()
|
||||||
|
},
|
||||||
|
active: HashSet::new(),
|
||||||
|
inactive: features.iter().cloned().collect(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FeatureSet {
|
||||||
|
// New `FeatureSet` with all features enabled
|
||||||
|
pub fn new_enabled() -> Self {
|
||||||
|
let default = Self::default();
|
||||||
|
|
||||||
|
Self {
|
||||||
|
id: default.id,
|
||||||
|
active: default
|
||||||
|
.active
|
||||||
|
.intersection(&default.inactive)
|
||||||
|
.cloned()
|
||||||
|
.collect::<HashSet<_>>(),
|
||||||
|
inactive: HashSet::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,7 +1,8 @@
|
||||||
|
use crate::{feature::Feature, feature_set::FeatureSet};
|
||||||
use solana_sdk::{
|
use solana_sdk::{
|
||||||
account::Account,
|
account::Account,
|
||||||
fee_calculator::FeeRateGovernor,
|
fee_calculator::FeeRateGovernor,
|
||||||
genesis_config::GenesisConfig,
|
genesis_config::{ClusterType, GenesisConfig},
|
||||||
pubkey::Pubkey,
|
pubkey::Pubkey,
|
||||||
rent::Rent,
|
rent::Rent,
|
||||||
signature::{Keypair, Signer},
|
signature::{Keypair, Signer},
|
||||||
|
@ -107,6 +108,24 @@ pub fn create_genesis_config_with_leader(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn add_feature_accounts(genesis_config: &mut GenesisConfig) {
|
||||||
|
// Activate all features at genesis in development mode
|
||||||
|
if genesis_config.cluster_type == ClusterType::Development {
|
||||||
|
let feature_set = FeatureSet::new_enabled();
|
||||||
|
|
||||||
|
for feature_id in feature_set.active {
|
||||||
|
let feature = Feature {
|
||||||
|
activated_at: Some(0),
|
||||||
|
};
|
||||||
|
|
||||||
|
genesis_config.accounts.insert(
|
||||||
|
feature_id,
|
||||||
|
feature.create_account(genesis_config.rent.minimum_balance(Feature::size_of())),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn create_genesis_config_with_leader_ex(
|
pub fn create_genesis_config_with_leader_ex(
|
||||||
mint_lamports: u64,
|
mint_lamports: u64,
|
||||||
bootstrap_validator_pubkey: &Pubkey,
|
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);
|
solana_stake_program::add_genesis_accounts(&mut genesis_config);
|
||||||
|
add_feature_accounts(&mut genesis_config);
|
||||||
|
|
||||||
GenesisConfigInfo {
|
GenesisConfigInfo {
|
||||||
genesis_config,
|
genesis_config,
|
||||||
|
|
|
@ -12,6 +12,8 @@ pub mod bloom;
|
||||||
pub mod builtins;
|
pub mod builtins;
|
||||||
pub mod commitment;
|
pub mod commitment;
|
||||||
pub mod epoch_stakes;
|
pub mod epoch_stakes;
|
||||||
|
pub mod feature;
|
||||||
|
pub mod feature_set;
|
||||||
pub mod genesis_utils;
|
pub mod genesis_utils;
|
||||||
pub mod hardened_unpack;
|
pub mod hardened_unpack;
|
||||||
pub mod instruction_recorder;
|
pub mod instruction_recorder;
|
||||||
|
|
Loading…
Reference in New Issue