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_runtime::genesis_utils::add_feature_accounts(&mut genesis_config);
|
||||
|
||||
if let Some(files) = matches.values_of("primordial_accounts_file") {
|
||||
for file in files {
|
||||
|
|
|
@ -13,6 +13,8 @@ use crate::{
|
|||
builtins::get_builtins,
|
||||
epoch_stakes::{EpochStakes, NodeVoteAccounts},
|
||||
instruction_recorder::InstructionRecorder,
|
||||
feature::Feature,
|
||||
feature_set::{FeatureSet},
|
||||
log_collector::LogCollector,
|
||||
message_processor::{Executors, MessageProcessor},
|
||||
nonce_utils,
|
||||
|
@ -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 {
|
||||
|
@ -649,6 +653,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!(
|
||||
|
@ -753,6 +758,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);
|
||||
|
||||
|
@ -3458,6 +3464,11 @@ impl Bank {
|
|||
// 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) {
|
||||
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.reinvoke_entered_epoch_callback(initiate_callback);
|
||||
self.recheck_cross_program_support();
|
||||
|
@ -3466,6 +3477,48 @@ impl Bank {
|
|||
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) {
|
||||
for (program, start_epoch) in get_builtins(self.cluster_type()) {
|
||||
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::{
|
||||
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) {
|
||||
// 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(
|
||||
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;
|
||||
|
|
Loading…
Reference in New Issue