Warp timestamp and extend max-allowable-drift for accommodate slow blocks (#15204)

* Remove timestamp_correction feature gating

* Remove timestamp_bounding feature gating

* Remove unused deprecated ledger code

* Remove unused deprecated unbounded-timestamp code

* Enable independent adjustment of fast/slow timestamp bounding

* Update timestamp bounds to 25% fast, 80% slow; warp timestamp

* Update bank hash test

* Add PR number to feature

Co-authored-by: Michael Vines <mvines@gmail.com>

Co-authored-by: Michael Vines <mvines@gmail.com>
This commit is contained in:
Tyera Eulberg
2021-02-09 15:49:00 -07:00
committed by GitHub
parent 2758588ddd
commit da6753b8c0
7 changed files with 516 additions and 928 deletions

View File

@@ -61,9 +61,8 @@ use solana_sdk::{
slot_hashes::SlotHashes,
slot_history::SlotHistory,
stake_weighted_timestamp::{
calculate_stake_weighted_timestamp, EstimateType,
DEPRECATED_MAX_ALLOWABLE_DRIFT_PERCENTAGE, DEPRECATED_TIMESTAMP_SLOT_RANGE,
MAX_ALLOWABLE_DRIFT_PERCENTAGE,
calculate_stake_weighted_timestamp, MaxAllowableDrift, MAX_ALLOWABLE_DRIFT_PERCENTAGE,
MAX_ALLOWABLE_DRIFT_PERCENTAGE_FAST, MAX_ALLOWABLE_DRIFT_PERCENTAGE_SLOW,
},
system_transaction,
sysvar::{self},
@@ -1355,89 +1354,59 @@ impl Bank {
}
fn update_clock(&self, parent_epoch: Option<Epoch>) {
let mut unix_timestamp = self.unix_timestamp_from_genesis();
if self
let mut unix_timestamp = self.clock().unix_timestamp;
let warp_timestamp_again = self
.feature_set
.is_active(&feature_set::timestamp_correction::id())
.activated_slot(&feature_set::warp_timestamp_again::id());
let epoch_start_timestamp = if warp_timestamp_again == Some(self.slot()) {
None
} else {
let epoch = if let Some(epoch) = parent_epoch {
epoch
} else {
self.epoch()
};
let first_slot_in_epoch = self.epoch_schedule.get_first_slot_in_epoch(epoch);
Some((first_slot_in_epoch, self.clock().epoch_start_timestamp))
};
let max_allowable_drift = if self
.feature_set
.is_active(&feature_set::warp_timestamp_again::id())
{
unix_timestamp = self.clock().unix_timestamp;
let (estimate_type, epoch_start_timestamp) =
if let Some(timestamp_bounding_activation_slot) = self
.feature_set
.activated_slot(&feature_set::timestamp_bounding::id())
{
// This check avoids a chicken-egg problem with epoch_start_timestamp, which is
// needed for timestamp bounding, but isn't yet corrected for the activation slot
let epoch_start_timestamp = if self.slot() > timestamp_bounding_activation_slot
{
let warp_timestamp = self
.feature_set
.activated_slot(&feature_set::warp_timestamp::id());
if warp_timestamp == Some(self.slot()) {
None
} else {
let epoch = if let Some(epoch) = parent_epoch {
epoch
} else {
self.epoch()
};
let first_slot_in_epoch =
self.epoch_schedule.get_first_slot_in_epoch(epoch);
Some((first_slot_in_epoch, self.clock().epoch_start_timestamp))
}
} else {
None
};
let max_allowable_drift = if self
.feature_set
.is_active(&feature_set::warp_timestamp::id())
{
MAX_ALLOWABLE_DRIFT_PERCENTAGE
} else {
DEPRECATED_MAX_ALLOWABLE_DRIFT_PERCENTAGE
};
(
EstimateType::Bounded(max_allowable_drift),
epoch_start_timestamp,
)
} else {
(EstimateType::Unbounded, None)
};
let ancestor_timestamp = self.clock().unix_timestamp;
if let Some(timestamp_estimate) =
self.get_timestamp_estimate(estimate_type, epoch_start_timestamp)
{
unix_timestamp = timestamp_estimate;
if self
.feature_set
.is_active(&feature_set::timestamp_bounding::id())
&& timestamp_estimate < ancestor_timestamp
{
unix_timestamp = ancestor_timestamp;
}
MaxAllowableDrift {
fast: MAX_ALLOWABLE_DRIFT_PERCENTAGE_FAST,
slow: MAX_ALLOWABLE_DRIFT_PERCENTAGE_SLOW,
}
datapoint_info!(
"bank-timestamp-correction",
("slot", self.slot(), i64),
("from_genesis", self.unix_timestamp_from_genesis(), i64),
("corrected", unix_timestamp, i64),
("ancestor_timestamp", ancestor_timestamp, i64),
);
}
let mut epoch_start_timestamp = if self
.feature_set
.is_active(&feature_set::timestamp_bounding::id())
} else {
MaxAllowableDrift {
fast: MAX_ALLOWABLE_DRIFT_PERCENTAGE,
slow: MAX_ALLOWABLE_DRIFT_PERCENTAGE,
}
};
let ancestor_timestamp = self.clock().unix_timestamp;
if let Some(timestamp_estimate) =
self.get_timestamp_estimate(max_allowable_drift, epoch_start_timestamp)
{
unix_timestamp = timestamp_estimate;
if timestamp_estimate < ancestor_timestamp {
unix_timestamp = ancestor_timestamp;
}
}
datapoint_info!(
"bank-timestamp-correction",
("slot", self.slot(), i64),
("from_genesis", self.unix_timestamp_from_genesis(), i64),
("corrected", unix_timestamp, i64),
("ancestor_timestamp", ancestor_timestamp, i64),
);
let mut epoch_start_timestamp =
// On epoch boundaries, update epoch_start_timestamp
if parent_epoch.is_some() && parent_epoch.unwrap() != self.epoch() {
unix_timestamp
} else {
self.clock().epoch_start_timestamp
}
} else {
Self::get_unused_from_slot(self.slot, self.unused) as i64
};
};
if self.slot == 0 {
unix_timestamp = self.unix_timestamp_from_genesis();
epoch_start_timestamp = self.unix_timestamp_from_genesis();
@@ -1925,13 +1894,10 @@ impl Bank {
fn get_timestamp_estimate(
&self,
estimate_type: EstimateType,
max_allowable_drift: MaxAllowableDrift,
epoch_start_timestamp: Option<(Slot, UnixTimestamp)>,
) -> Option<UnixTimestamp> {
let mut get_timestamp_estimate_time = Measure::start("get_timestamp_estimate");
let timestamp_bounding_enabled = self
.feature_set
.is_active(&feature_set::timestamp_bounding::id());
let slots_per_epoch = self.epoch_schedule().slots_per_epoch;
let recent_timestamps =
self.vote_accounts()
@@ -1940,9 +1906,7 @@ impl Bank {
let vote_state = account.vote_state();
let vote_state = vote_state.as_ref().ok()?;
let slot_delta = self.slot().checked_sub(vote_state.last_timestamp.slot)?;
if (timestamp_bounding_enabled && slot_delta <= slots_per_epoch)
|| slot_delta <= DEPRECATED_TIMESTAMP_SLOT_RANGE as u64
{
if slot_delta <= slots_per_epoch {
Some((
pubkey,
(
@@ -1962,8 +1926,10 @@ impl Bank {
stakes,
self.slot(),
slot_duration,
estimate_type,
epoch_start_timestamp,
max_allowable_drift,
self.feature_set
.is_active(&feature_set::warp_timestamp_again::id()),
);
get_timestamp_estimate_time.stop();
datapoint_info!(
@@ -10216,19 +10182,19 @@ pub(crate) mod tests {
if bank.slot == 32 {
assert_eq!(
bank.hash().to_string(),
"9FwpFSUvbCfzQMGXDSdvnNhNPpvHUsEJyNA9P3nqiLaJ"
"4syPxVrVFUpksTre5BB5w7qd3BxSU4WzUT6R2fjFgMJ2"
);
}
if bank.slot == 64 {
assert_eq!(
bank.hash().to_string(),
"7p6g7GmE9quceefLtPe97fr9YQeYWB562os2ttiG3Anq"
"4GKgnCxQs6AJxcqYQkxa8oF8gEp13bfRNCm2uzCceA26"
);
}
if bank.slot == 128 {
assert_eq!(
bank.hash().to_string(),
"DfeuEsVvVRUkce31sM1d4Vhhp6Si99JdVEtYddbBgLKV"
"9YwXsk2qpM7bZLnWGdtqCmDEygiu1KpEcr4zWWBTUKw6"
);
break;
}
@@ -11134,127 +11100,6 @@ pub(crate) mod tests {
bank.store_account(vote_pubkey, &vote_account);
}
#[test]
fn test_get_timestamp_estimate() {
let validator_vote_keypairs0 = ValidatorVoteKeypairs::new_rand();
let validator_vote_keypairs1 = ValidatorVoteKeypairs::new_rand();
let validator_keypairs = vec![&validator_vote_keypairs0, &validator_vote_keypairs1];
let GenesisConfigInfo {
mut genesis_config,
mint_keypair: _,
voting_keypair: _,
} = create_genesis_config_with_vote_accounts(
1_000_000_000,
&validator_keypairs,
vec![10_000; 2],
);
genesis_config
.accounts
.remove(&feature_set::timestamp_bounding::id())
.unwrap();
let mut bank = Bank::new(&genesis_config);
assert_eq!(
bank.get_timestamp_estimate(EstimateType::Unbounded, None),
Some(0)
);
let recent_timestamp: UnixTimestamp = bank.unix_timestamp_from_genesis();
update_vote_account_timestamp(
BlockTimestamp {
slot: bank.slot(),
timestamp: recent_timestamp,
},
&bank,
&validator_vote_keypairs0.vote_keypair.pubkey(),
);
let additional_secs = 2;
update_vote_account_timestamp(
BlockTimestamp {
slot: bank.slot(),
timestamp: recent_timestamp + additional_secs,
},
&bank,
&validator_vote_keypairs1.vote_keypair.pubkey(),
);
assert_eq!(
bank.get_timestamp_estimate(EstimateType::Unbounded, None),
Some(recent_timestamp + additional_secs / 2)
);
for _ in 0..10 {
bank = new_from_parent(&Arc::new(bank));
}
let adjustment = (bank.ns_per_slot as u64 * bank.slot()) / 1_000_000_000;
assert_eq!(
bank.get_timestamp_estimate(EstimateType::Unbounded, None),
Some(recent_timestamp + adjustment as i64 + additional_secs / 2)
);
for _ in 0..7 {
bank = new_from_parent(&Arc::new(bank));
}
assert_eq!(
bank.get_timestamp_estimate(EstimateType::Unbounded, None),
None
);
}
#[test]
fn test_timestamp_correction_feature() {
let leader_pubkey = solana_sdk::pubkey::new_rand();
let GenesisConfigInfo {
mut genesis_config,
voting_keypair,
..
} = create_genesis_config_with_leader(5, &leader_pubkey, 3);
genesis_config
.accounts
.remove(&feature_set::timestamp_correction::id())
.unwrap();
genesis_config
.accounts
.remove(&feature_set::timestamp_bounding::id())
.unwrap();
let bank = Bank::new(&genesis_config);
let recent_timestamp: UnixTimestamp = bank.unix_timestamp_from_genesis();
let additional_secs = 1;
update_vote_account_timestamp(
BlockTimestamp {
slot: bank.slot(),
timestamp: recent_timestamp + additional_secs,
},
&bank,
&voting_keypair.pubkey(),
);
// Bank::new_from_parent should not adjust timestamp before feature activation
let mut bank = new_from_parent(&Arc::new(bank));
assert_eq!(
bank.clock().unix_timestamp,
bank.unix_timestamp_from_genesis()
);
// Request `timestamp_correction` activation
bank.store_account(
&feature_set::timestamp_correction::id(),
&feature::create_account(
&Feature {
activated_at: Some(bank.slot),
},
42,
),
);
bank.compute_active_feature_set(true);
// Now Bank::new_from_parent should adjust timestamp
let bank = Arc::new(new_from_parent(&Arc::new(bank)));
assert_eq!(
bank.clock().unix_timestamp,
bank.unix_timestamp_from_genesis() + additional_secs
);
}
#[test]
fn test_simple_capitalization_adjustment_minimum_genesis_set() {
solana_logger::setup();
@@ -11365,129 +11210,6 @@ pub(crate) mod tests {
);
}
#[test]
fn test_timestamp_bounding_feature() {
let leader_pubkey = solana_sdk::pubkey::new_rand();
let GenesisConfigInfo {
mut genesis_config,
voting_keypair,
..
} = create_genesis_config_with_leader(5, &leader_pubkey, 3);
let slots_in_epoch = 32;
genesis_config
.accounts
.remove(&feature_set::timestamp_bounding::id())
.unwrap();
genesis_config
.accounts
.remove(&feature_set::warp_timestamp::id())
.unwrap();
genesis_config.epoch_schedule = EpochSchedule::new(slots_in_epoch);
let bank = Bank::new(&genesis_config);
let recent_timestamp: UnixTimestamp = bank.unix_timestamp_from_genesis();
let additional_secs = 1;
update_vote_account_timestamp(
BlockTimestamp {
slot: bank.slot(),
timestamp: recent_timestamp + additional_secs,
},
&bank,
&voting_keypair.pubkey(),
);
// Bank::new_from_parent should allow unbounded timestamp before activation
let mut bank = new_from_parent(&Arc::new(bank));
assert_eq!(
bank.clock().unix_timestamp,
bank.unix_timestamp_from_genesis() + additional_secs
);
// Bank::new_from_parent should not allow epoch_start_timestamp to be set before activation
bank.update_clock(Some(0));
assert_eq!(
bank.clock().epoch_start_timestamp,
Bank::get_unused_from_slot(bank.slot(), bank.unused) as i64
);
// Request `timestamp_bounding` activation
let feature = Feature { activated_at: None };
bank.store_account(
&feature_set::timestamp_bounding::id(),
&feature::create_account(&feature, 42),
);
for _ in 0..30 {
bank = new_from_parent(&Arc::new(bank));
}
// Refresh vote timestamp
let recent_timestamp: UnixTimestamp = bank.unix_timestamp_from_genesis();
let additional_secs = 1;
update_vote_account_timestamp(
BlockTimestamp {
slot: bank.slot(),
timestamp: recent_timestamp + additional_secs,
},
&bank,
&voting_keypair.pubkey(),
);
// Advance to epoch boundary to activate
bank = new_from_parent(&Arc::new(bank));
// Bank::new_from_parent is bounding, but should not use epoch_start_timestamp in activation slot
assert_eq!(
bank.clock().unix_timestamp,
bank.unix_timestamp_from_genesis() + additional_secs
);
assert_eq!(
bank.clock().epoch_start_timestamp,
bank.unix_timestamp_from_genesis() + additional_secs
);
// Past activation slot, bounding should use epoch_start_timestamp in activation slot
bank = new_from_parent(&Arc::new(bank));
assert_eq!(
bank.clock().unix_timestamp,
bank.unix_timestamp_from_genesis()
);
for _ in 0..30 {
bank = new_from_parent(&Arc::new(bank));
}
// Refresh vote timestamp
let recent_timestamp: UnixTimestamp = bank.unix_timestamp_from_genesis();
let additional_secs = 20;
update_vote_account_timestamp(
BlockTimestamp {
slot: bank.slot(),
timestamp: recent_timestamp + additional_secs,
},
&bank,
&voting_keypair.pubkey(),
);
// Advance to epoch boundary
bank = new_from_parent(&Arc::new(bank));
// Past activation slot, bounding should use previous epoch_start_timestamp on epoch boundary slots
assert_eq!(
bank.clock().unix_timestamp,
bank.unix_timestamp_from_genesis() // Plus estimated offset + 25%
+ ((slots_in_epoch as u32 * Duration::from_nanos(bank.ns_per_slot as u64))
.as_secs()
* 25
/ 100) as i64,
);
assert_eq!(
bank.clock().epoch_start_timestamp,
bank.clock().unix_timestamp
);
}
#[test]
fn test_update_clock_timestamp() {
let leader_pubkey = solana_sdk::pubkey::new_rand();
@@ -11570,6 +11292,157 @@ pub(crate) mod tests {
);
}
fn poh_estimate_offset(bank: &Bank) -> Duration {
let mut epoch_start_slot = bank.epoch_schedule.get_first_slot_in_epoch(bank.epoch());
if epoch_start_slot == bank.slot() {
epoch_start_slot = bank
.epoch_schedule
.get_first_slot_in_epoch(bank.epoch() - 1);
}
bank.slot().saturating_sub(epoch_start_slot) as u32
* Duration::from_nanos(bank.ns_per_slot as u64)
}
#[test]
fn test_warp_timestamp_again_feature_slow() {
fn max_allowable_delta_since_epoch(bank: &Bank, max_allowable_drift: u32) -> i64 {
let poh_estimate_offset = poh_estimate_offset(bank);
(poh_estimate_offset.as_secs()
+ (poh_estimate_offset * max_allowable_drift / 100).as_secs()) as i64
}
let leader_pubkey = solana_sdk::pubkey::new_rand();
let GenesisConfigInfo {
mut genesis_config,
voting_keypair,
..
} = create_genesis_config_with_leader(5, &leader_pubkey, 3);
let slots_in_epoch = 32;
genesis_config
.accounts
.remove(&feature_set::warp_timestamp_again::id())
.unwrap();
genesis_config.epoch_schedule = EpochSchedule::new(slots_in_epoch);
let mut bank = Bank::new(&genesis_config);
let recent_timestamp: UnixTimestamp = bank.unix_timestamp_from_genesis();
let additional_secs = 8; // Greater than MAX_ALLOWABLE_DRIFT_PERCENTAGE for full epoch
update_vote_account_timestamp(
BlockTimestamp {
slot: bank.slot(),
timestamp: recent_timestamp + additional_secs,
},
&bank,
&voting_keypair.pubkey(),
);
// additional_secs greater than MAX_ALLOWABLE_DRIFT_PERCENTAGE for an epoch
// timestamp bounded to 50% deviation
for _ in 0..31 {
bank = new_from_parent(&Arc::new(bank));
assert_eq!(
bank.clock().unix_timestamp,
bank.clock().epoch_start_timestamp
+ max_allowable_delta_since_epoch(&bank, MAX_ALLOWABLE_DRIFT_PERCENTAGE),
);
assert_eq!(bank.clock().epoch_start_timestamp, recent_timestamp);
}
// Request `warp_timestamp_again` activation
let feature = Feature { activated_at: None };
bank.store_account(
&feature_set::warp_timestamp_again::id(),
&feature::create_account(&feature, 42),
);
let previous_epoch_timestamp = bank.clock().epoch_start_timestamp;
let previous_timestamp = bank.clock().unix_timestamp;
// Advance to epoch boundary to activate; time is warped to estimate with no bounding
bank = new_from_parent(&Arc::new(bank));
assert_ne!(bank.clock().epoch_start_timestamp, previous_timestamp);
assert!(
bank.clock().epoch_start_timestamp
> previous_epoch_timestamp
+ max_allowable_delta_since_epoch(&bank, MAX_ALLOWABLE_DRIFT_PERCENTAGE)
);
// Refresh vote timestamp
let recent_timestamp: UnixTimestamp = bank.clock().unix_timestamp;
let additional_secs = 8;
update_vote_account_timestamp(
BlockTimestamp {
slot: bank.slot(),
timestamp: recent_timestamp + additional_secs,
},
&bank,
&voting_keypair.pubkey(),
);
// additional_secs greater than MAX_ALLOWABLE_DRIFT_PERCENTAGE for 22 slots
// timestamp bounded to 80% deviation
for _ in 0..23 {
bank = new_from_parent(&Arc::new(bank));
assert_eq!(
bank.clock().unix_timestamp,
bank.clock().epoch_start_timestamp
+ max_allowable_delta_since_epoch(&bank, MAX_ALLOWABLE_DRIFT_PERCENTAGE_SLOW),
);
assert_eq!(bank.clock().epoch_start_timestamp, recent_timestamp);
}
for _ in 0..8 {
bank = new_from_parent(&Arc::new(bank));
assert_eq!(
bank.clock().unix_timestamp,
bank.clock().epoch_start_timestamp
+ poh_estimate_offset(&bank).as_secs() as i64
+ additional_secs,
);
assert_eq!(bank.clock().epoch_start_timestamp, recent_timestamp);
}
}
#[test]
fn test_timestamp_fast() {
fn max_allowable_delta_since_epoch(bank: &Bank, max_allowable_drift: u32) -> i64 {
let poh_estimate_offset = poh_estimate_offset(bank);
(poh_estimate_offset.as_secs()
- (poh_estimate_offset * max_allowable_drift / 100).as_secs()) as i64
}
let leader_pubkey = solana_sdk::pubkey::new_rand();
let GenesisConfigInfo {
mut genesis_config,
voting_keypair,
..
} = create_genesis_config_with_leader(5, &leader_pubkey, 3);
let slots_in_epoch = 32;
genesis_config.epoch_schedule = EpochSchedule::new(slots_in_epoch);
let mut bank = Bank::new(&genesis_config);
let recent_timestamp: UnixTimestamp = bank.unix_timestamp_from_genesis();
let additional_secs = 5; // Greater than MAX_ALLOWABLE_DRIFT_PERCENTAGE_FAST for full epoch
update_vote_account_timestamp(
BlockTimestamp {
slot: bank.slot(),
timestamp: recent_timestamp - additional_secs,
},
&bank,
&voting_keypair.pubkey(),
);
// additional_secs greater than MAX_ALLOWABLE_DRIFT_PERCENTAGE_FAST for an epoch
// timestamp bounded to 25% deviation
for _ in 0..31 {
bank = new_from_parent(&Arc::new(bank));
assert_eq!(
bank.clock().unix_timestamp,
bank.clock().epoch_start_timestamp
+ max_allowable_delta_since_epoch(&bank, MAX_ALLOWABLE_DRIFT_PERCENTAGE_FAST),
);
assert_eq!(bank.clock().epoch_start_timestamp, recent_timestamp);
}
}
#[test]
fn test_program_is_native_loader() {
let (genesis_config, mint_keypair) = create_genesis_config(50000);

View File

@@ -327,7 +327,6 @@ mod tests {
clock::UnixTimestamp,
pubkey::Pubkey,
signature::{Keypair, Signer},
stake_weighted_timestamp::DEPRECATED_TIMESTAMP_SLOT_RANGE,
sysvar::epoch_schedule::EpochSchedule,
};
use solana_vote_program::vote_state::BlockTimestamp;
@@ -436,8 +435,7 @@ mod tests {
let additional_timestamp_secs = 2;
let num_slots = slots_in_epoch + 1 // Advance past first epoch boundary
+ DEPRECATED_TIMESTAMP_SLOT_RANGE as u64 + 1; // ... and past deprecated slot range
let num_slots = slots_in_epoch + 1; // Advance past first epoch boundary
for slot in 1..num_slots {
// Just after the epoch boundary, timestamp a vote that will shift
// Clock::unix_timestamp from Bank::unix_timestamp_from_genesis()