Use bounded timestamp-correction when feature enabled
This commit is contained in:
committed by
Michael Vines
parent
80db6c0980
commit
90778615f6
@ -2703,6 +2703,7 @@ pub mod tests {
|
|||||||
message::Message,
|
message::Message,
|
||||||
nonce, rpc_port,
|
nonce, rpc_port,
|
||||||
signature::{Keypair, Signer},
|
signature::{Keypair, Signer},
|
||||||
|
stake_weighted_timestamp::EstimateType,
|
||||||
system_program, system_transaction,
|
system_program, system_transaction,
|
||||||
timing::slot_duration_from_slots_per_year,
|
timing::slot_duration_from_slots_per_year,
|
||||||
transaction::{self, TransactionError},
|
transaction::{self, TransactionError},
|
||||||
|
@ -1081,8 +1081,32 @@ impl Bank {
|
|||||||
.feature_set
|
.feature_set
|
||||||
.is_active(&feature_set::timestamp_correction::id())
|
.is_active(&feature_set::timestamp_correction::id())
|
||||||
{
|
{
|
||||||
|
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 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
|
||||||
|
};
|
||||||
|
(EstimateType::Bounded, epoch_start_timestamp)
|
||||||
|
} else {
|
||||||
|
(EstimateType::Unbounded, None)
|
||||||
|
};
|
||||||
if let Some(timestamp_estimate) =
|
if let Some(timestamp_estimate) =
|
||||||
self.get_timestamp_estimate(EstimateType::Unbounded, None)
|
self.get_timestamp_estimate(estimate_type, epoch_start_timestamp)
|
||||||
{
|
{
|
||||||
if timestamp_estimate > unix_timestamp {
|
if timestamp_estimate > unix_timestamp {
|
||||||
datapoint_info!(
|
datapoint_info!(
|
||||||
@ -9729,6 +9753,10 @@ mod tests {
|
|||||||
.accounts
|
.accounts
|
||||||
.remove(&feature_set::timestamp_correction::id())
|
.remove(&feature_set::timestamp_correction::id())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
genesis_config
|
||||||
|
.accounts
|
||||||
|
.remove(&feature_set::timestamp_bounding::id())
|
||||||
|
.unwrap();
|
||||||
let bank = Bank::new(&genesis_config);
|
let bank = Bank::new(&genesis_config);
|
||||||
|
|
||||||
let recent_timestamp: UnixTimestamp = bank.unix_timestamp_from_genesis();
|
let recent_timestamp: UnixTimestamp = bank.unix_timestamp_from_genesis();
|
||||||
@ -9744,10 +9772,10 @@ mod tests {
|
|||||||
|
|
||||||
// Bank::new_from_parent should not adjust timestamp before feature activation
|
// Bank::new_from_parent should not adjust timestamp before feature activation
|
||||||
let mut bank = new_from_parent(&Arc::new(bank));
|
let mut bank = new_from_parent(&Arc::new(bank));
|
||||||
let clock =
|
assert_eq!(
|
||||||
from_account::<sysvar::clock::Clock>(&bank.get_account(&sysvar::clock::id()).unwrap())
|
bank.clock().unix_timestamp,
|
||||||
.unwrap();
|
bank.unix_timestamp_from_genesis()
|
||||||
assert_eq!(clock.unix_timestamp, bank.unix_timestamp_from_genesis());
|
);
|
||||||
|
|
||||||
// Request `timestamp_correction` activation
|
// Request `timestamp_correction` activation
|
||||||
bank.store_account(
|
bank.store_account(
|
||||||
@ -9763,15 +9791,131 @@ mod tests {
|
|||||||
|
|
||||||
// Now Bank::new_from_parent should adjust timestamp
|
// Now Bank::new_from_parent should adjust timestamp
|
||||||
let bank = Arc::new(new_from_parent(&Arc::new(bank)));
|
let bank = Arc::new(new_from_parent(&Arc::new(bank)));
|
||||||
let clock =
|
|
||||||
from_account::<sysvar::clock::Clock>(&bank.get_account(&sysvar::clock::id()).unwrap())
|
|
||||||
.unwrap();
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
clock.unix_timestamp,
|
bank.clock().unix_timestamp,
|
||||||
bank.unix_timestamp_from_genesis() + additional_secs
|
bank.unix_timestamp_from_genesis() + additional_secs
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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.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(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]
|
#[test]
|
||||||
fn test_update_clock_timestamp() {
|
fn test_update_clock_timestamp() {
|
||||||
let leader_pubkey = solana_sdk::pubkey::new_rand();
|
let leader_pubkey = solana_sdk::pubkey::new_rand();
|
||||||
|
@ -110,13 +110,22 @@ fn calculate_bounded_stake_weighted_timestamp(
|
|||||||
let poh_estimate_offset = slot.saturating_sub(epoch_start_slot) as u32 * slot_duration;
|
let poh_estimate_offset = slot.saturating_sub(epoch_start_slot) as u32 * slot_duration;
|
||||||
let estimate_offset =
|
let estimate_offset =
|
||||||
Duration::from_secs(estimate.saturating_sub(epoch_start_timestamp) as u64);
|
Duration::from_secs(estimate.saturating_sub(epoch_start_timestamp) as u64);
|
||||||
let delta = if estimate_offset > poh_estimate_offset {
|
let max_allowable_drift = poh_estimate_offset * MAX_ALLOWABLE_DRIFT_PERCENTAGE / 100;
|
||||||
estimate_offset - poh_estimate_offset
|
if estimate_offset > poh_estimate_offset
|
||||||
} else {
|
&& estimate_offset - poh_estimate_offset > max_allowable_drift
|
||||||
poh_estimate_offset - estimate_offset
|
{
|
||||||
};
|
// estimate offset since the start of the epoch is higher than
|
||||||
if delta > poh_estimate_offset * MAX_ALLOWABLE_DRIFT_PERCENTAGE / 100 {
|
// `MAX_ALLOWABLE_DRIFT_PERCENTAGE`
|
||||||
estimate = epoch_start_timestamp + poh_estimate_offset.as_secs() as i64;
|
estimate = epoch_start_timestamp
|
||||||
|
+ poh_estimate_offset.as_secs() as i64
|
||||||
|
+ max_allowable_drift.as_secs() as i64;
|
||||||
|
} else if estimate_offset < poh_estimate_offset
|
||||||
|
&& poh_estimate_offset - estimate_offset > max_allowable_drift
|
||||||
|
{
|
||||||
|
// estimate offset since the start of the epoch is lower than
|
||||||
|
// `MAX_ALLOWABLE_DRIFT_PERCENTAGE`
|
||||||
|
estimate = epoch_start_timestamp + poh_estimate_offset.as_secs() as i64
|
||||||
|
- max_allowable_drift.as_secs() as i64;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Some(estimate)
|
Some(estimate)
|
||||||
@ -503,7 +512,7 @@ pub mod tests {
|
|||||||
Some((0, epoch_start_timestamp)),
|
Some((0, epoch_start_timestamp)),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(bounded, poh_estimate);
|
assert_eq!(bounded, poh_estimate + acceptable_delta);
|
||||||
|
|
||||||
// Test when stake-weighted median is too low
|
// Test when stake-weighted median is too low
|
||||||
let unique_timestamps: HashMap<Pubkey, (Slot, UnixTimestamp)> = [
|
let unique_timestamps: HashMap<Pubkey, (Slot, UnixTimestamp)> = [
|
||||||
@ -523,7 +532,7 @@ pub mod tests {
|
|||||||
Some((0, epoch_start_timestamp)),
|
Some((0, epoch_start_timestamp)),
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert_eq!(bounded, poh_estimate);
|
assert_eq!(bounded, poh_estimate - acceptable_delta);
|
||||||
|
|
||||||
// Test stake-weighted median within bounds
|
// Test stake-weighted median within bounds
|
||||||
let unique_timestamps: HashMap<Pubkey, (Slot, UnixTimestamp)> = [
|
let unique_timestamps: HashMap<Pubkey, (Slot, UnixTimestamp)> = [
|
||||||
|
Reference in New Issue
Block a user