Runtime feature activation framework

This commit is contained in:
Michael Vines 2020-09-21 14:03:35 -07:00
parent d00453f747
commit 93259f0bae
6 changed files with 192 additions and 1 deletions

View File

@ -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 {

View File

@ -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

55
runtime/src/feature.rs Normal file
View File

@ -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
}
}

View File

@ -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(),
}
}
}

View File

@ -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,

View File

@ -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;