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:
parent
2758588ddd
commit
da6753b8c0
|
@ -2,9 +2,7 @@ use crossbeam_channel::{Receiver, RecvTimeoutError, Sender};
|
|||
use solana_ledger::blockstore::Blockstore;
|
||||
use solana_measure::measure::Measure;
|
||||
use solana_runtime::bank::Bank;
|
||||
use solana_sdk::{feature_set, timing::slot_duration_from_slots_per_year};
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
sync::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
Arc,
|
||||
|
@ -60,25 +58,9 @@ impl CacheBlockTimeService {
|
|||
}
|
||||
|
||||
fn cache_block_time(bank: Arc<Bank>, blockstore: &Arc<Blockstore>) {
|
||||
if bank
|
||||
.feature_set
|
||||
.is_active(&feature_set::timestamp_correction::id())
|
||||
{
|
||||
if let Err(e) = blockstore.cache_block_time(bank.slot(), bank.clock().unix_timestamp) {
|
||||
error!("cache_block_time failed: slot {:?} {:?}", bank.slot(), e);
|
||||
}
|
||||
} else {
|
||||
let slot_duration = slot_duration_from_slots_per_year(bank.slots_per_year());
|
||||
let epoch = bank.epoch_schedule().get_epoch(bank.slot());
|
||||
let stakes = HashMap::new();
|
||||
let stakes = bank.epoch_vote_accounts(epoch).unwrap_or(&stakes);
|
||||
|
||||
if let Err(e) =
|
||||
blockstore.cache_block_time_from_slot_entries(bank.slot(), slot_duration, stakes)
|
||||
{
|
||||
error!("cache_block_time failed: slot {:?} {:?}", bank.slot(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn join(self) -> thread::Result<()> {
|
||||
|
|
|
@ -387,11 +387,6 @@ intermittently adding a timestamp to a Vote for a particular block. A requested
|
|||
block's time is calculated from the stake-weighted mean of the Vote timestamps
|
||||
in a set of recent blocks recorded on the ledger.
|
||||
|
||||
Nodes that are booting from snapshot or limiting ledger size (by purging old
|
||||
slots) will return null timestamps for blocks below their lowest root +
|
||||
`TIMESTAMP_SLOT_RANGE`. Users interested in having this historical data must
|
||||
query a node that is built from genesis and retains the entire ledger.
|
||||
|
||||
#### Parameters:
|
||||
|
||||
- `<u64>` - block, identified by Slot
|
||||
|
|
|
@ -24,20 +24,13 @@ use rocksdb::DBRawIterator;
|
|||
use solana_measure::measure::Measure;
|
||||
use solana_metrics::{datapoint_debug, datapoint_error};
|
||||
use solana_rayon_threadlimit::get_thread_count;
|
||||
use solana_runtime::{
|
||||
hardened_unpack::{unpack_genesis_archive, MAX_GENESIS_ARCHIVE_UNPACKED_SIZE},
|
||||
vote_account::ArcVoteAccount,
|
||||
};
|
||||
use solana_runtime::hardened_unpack::{unpack_genesis_archive, MAX_GENESIS_ARCHIVE_UNPACKED_SIZE};
|
||||
use solana_sdk::{
|
||||
clock::{Slot, UnixTimestamp, DEFAULT_TICKS_PER_SECOND, MS_PER_TICK},
|
||||
genesis_config::GenesisConfig,
|
||||
hash::Hash,
|
||||
program_utils::limited_deserialize,
|
||||
pubkey::Pubkey,
|
||||
signature::{Keypair, Signature, Signer},
|
||||
stake_weighted_timestamp::{
|
||||
calculate_stake_weighted_timestamp, EstimateType, TIMESTAMP_SLOT_RANGE,
|
||||
},
|
||||
timing::timestamp,
|
||||
transaction::Transaction,
|
||||
};
|
||||
|
@ -46,7 +39,6 @@ use solana_transaction_status::{
|
|||
ConfirmedBlock, ConfirmedTransaction, ConfirmedTransactionStatusWithSignature, Rewards,
|
||||
TransactionStatusMeta, TransactionWithStatusMeta,
|
||||
};
|
||||
use solana_vote_program::vote_instruction::VoteInstruction;
|
||||
use std::{
|
||||
cell::RefCell,
|
||||
cmp,
|
||||
|
@ -59,7 +51,6 @@ use std::{
|
|||
mpsc::{sync_channel, Receiver, SyncSender, TrySendError},
|
||||
Arc, Mutex, RwLock,
|
||||
},
|
||||
time::Duration,
|
||||
};
|
||||
use thiserror::Error;
|
||||
use trees::{Tree, TreeWalk};
|
||||
|
@ -1682,29 +1673,6 @@ impl Blockstore {
|
|||
self.blocktime_cf.get(slot)
|
||||
}
|
||||
|
||||
fn get_timestamp_slots(&self, slot: Slot, timestamp_sample_range: usize) -> Vec<Slot> {
|
||||
let root_iterator = self
|
||||
.db
|
||||
.iter::<cf::Root>(IteratorMode::From(slot, IteratorDirection::Reverse));
|
||||
if !self.is_root(slot) || root_iterator.is_err() {
|
||||
return vec![];
|
||||
}
|
||||
let mut get_slots = Measure::start("get_slots");
|
||||
let mut timestamp_slots: Vec<Slot> = root_iterator
|
||||
.unwrap()
|
||||
.map(|(iter_slot, _)| iter_slot)
|
||||
.take(timestamp_sample_range)
|
||||
.collect();
|
||||
timestamp_slots.sort_unstable();
|
||||
get_slots.stop();
|
||||
datapoint_info!(
|
||||
"blockstore-get-timestamp-slots",
|
||||
("slot", slot as i64, i64),
|
||||
("get_slots_us", get_slots.as_us() as i64, i64)
|
||||
);
|
||||
timestamp_slots
|
||||
}
|
||||
|
||||
pub fn cache_block_time(&self, slot: Slot, timestamp: UnixTimestamp) -> Result<()> {
|
||||
if !self.is_root(slot) {
|
||||
return Err(BlockstoreError::SlotNotRooted);
|
||||
|
@ -1712,55 +1680,6 @@ impl Blockstore {
|
|||
self.blocktime_cf.put(slot, ×tamp)
|
||||
}
|
||||
|
||||
// DEPRECATED as of feature_set::timestamp_correction
|
||||
pub fn cache_block_time_from_slot_entries(
|
||||
&self,
|
||||
slot: Slot,
|
||||
slot_duration: Duration,
|
||||
stakes: &HashMap<Pubkey, (u64, ArcVoteAccount)>,
|
||||
) -> Result<()> {
|
||||
if !self.is_root(slot) {
|
||||
return Err(BlockstoreError::SlotNotRooted);
|
||||
}
|
||||
let mut get_unique_timestamps = Measure::start("get_unique_timestamps");
|
||||
let unique_timestamps: HashMap<Pubkey, (Slot, UnixTimestamp)> = self
|
||||
.get_timestamp_slots(slot, TIMESTAMP_SLOT_RANGE)
|
||||
.into_iter()
|
||||
.flat_map(|query_slot| self.get_block_timestamps(query_slot).unwrap_or_default())
|
||||
.collect();
|
||||
get_unique_timestamps.stop();
|
||||
if unique_timestamps.is_empty() {
|
||||
return Err(BlockstoreError::NoVoteTimestampsInRange);
|
||||
}
|
||||
|
||||
let mut calculate_timestamp = Measure::start("calculate_timestamp");
|
||||
let stake_weighted_timestamp = calculate_stake_weighted_timestamp(
|
||||
&unique_timestamps,
|
||||
stakes,
|
||||
slot,
|
||||
slot_duration,
|
||||
EstimateType::Unbounded,
|
||||
None,
|
||||
)
|
||||
.ok_or(BlockstoreError::EmptyEpochStakes)?;
|
||||
calculate_timestamp.stop();
|
||||
datapoint_info!(
|
||||
"blockstore-get-block-time",
|
||||
("slot", slot as i64, i64),
|
||||
(
|
||||
"get_unique_timestamps_us",
|
||||
get_unique_timestamps.as_us() as i64,
|
||||
i64
|
||||
),
|
||||
(
|
||||
"calculate_stake_weighted_timestamp_us",
|
||||
calculate_timestamp.as_us() as i64,
|
||||
i64
|
||||
)
|
||||
);
|
||||
self.cache_block_time(slot, stake_weighted_timestamp)
|
||||
}
|
||||
|
||||
pub fn get_first_available_block(&self) -> Result<Slot> {
|
||||
let mut root_iterator = self.rooted_slot_iterator(self.lowest_slot())?;
|
||||
Ok(root_iterator.next().unwrap_or_default())
|
||||
|
@ -2385,36 +2304,6 @@ impl Blockstore {
|
|||
self.rewards_cf.put_protobuf(index, &rewards)
|
||||
}
|
||||
|
||||
fn get_block_timestamps(&self, slot: Slot) -> Result<Vec<(Pubkey, (Slot, UnixTimestamp))>> {
|
||||
let slot_entries = self.get_slot_entries(slot, 0)?;
|
||||
Ok(slot_entries
|
||||
.iter()
|
||||
.cloned()
|
||||
.flat_map(|entry| entry.transactions)
|
||||
.flat_map(|transaction| {
|
||||
let mut timestamps: Vec<(Pubkey, (Slot, UnixTimestamp))> = Vec::new();
|
||||
for instruction in transaction.message.instructions {
|
||||
let program_id = instruction.program_id(&transaction.message.account_keys);
|
||||
if program_id == &solana_vote_program::id() {
|
||||
if let Ok(VoteInstruction::Vote(vote)) =
|
||||
limited_deserialize(&instruction.data)
|
||||
{
|
||||
if let Some(timestamp) = vote.timestamp {
|
||||
let timestamp_slot = vote.slots.iter().max();
|
||||
if let Some(timestamp_slot) = timestamp_slot {
|
||||
let vote_pubkey = transaction.message.account_keys
|
||||
[instruction.accounts[0] as usize];
|
||||
timestamps.push((vote_pubkey, (*timestamp_slot, timestamp)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
timestamps
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
pub fn get_recent_perf_samples(&self, num: usize) -> Result<Vec<(Slot, PerfSample)>> {
|
||||
Ok(self
|
||||
.db
|
||||
|
@ -3595,7 +3484,6 @@ fn adjust_ulimit_nofile(enforce_ulimit_nofile: bool) -> Result<()> {
|
|||
pub mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
blockstore_processor::fill_blockstore_slot_with_ticks,
|
||||
entry::{next_entry, next_entry_mut},
|
||||
genesis_utils::{create_genesis_config, GenesisConfigInfo},
|
||||
leader_schedule::{FixedSchedule, LeaderSchedule},
|
||||
|
@ -3609,7 +3497,6 @@ pub mod tests {
|
|||
use solana_sdk::{
|
||||
hash::{self, hash, Hash},
|
||||
instruction::CompiledInstruction,
|
||||
message::Message,
|
||||
packet::PACKET_DATA_SIZE,
|
||||
pubkey::Pubkey,
|
||||
signature::Signature,
|
||||
|
@ -3617,7 +3504,6 @@ pub mod tests {
|
|||
};
|
||||
use solana_storage_proto::convert::generated;
|
||||
use solana_transaction_status::{InnerInstructions, Reward, Rewards};
|
||||
use solana_vote_program::{vote_instruction, vote_state::Vote};
|
||||
use std::time::Duration;
|
||||
|
||||
// used for tests only
|
||||
|
@ -5748,92 +5634,6 @@ pub mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_timestamp_slots() {
|
||||
let timestamp_sample_range = 5;
|
||||
let ticks_per_slot = 5;
|
||||
/*
|
||||
Build a blockstore with < TIMESTAMP_SLOT_RANGE roots
|
||||
*/
|
||||
let blockstore_path = get_tmp_ledger_path!();
|
||||
let blockstore = Blockstore::open(&blockstore_path).unwrap();
|
||||
blockstore.set_roots(&[0]).unwrap();
|
||||
let mut last_entry_hash = Hash::default();
|
||||
for slot in 0..=3 {
|
||||
let parent = {
|
||||
if slot == 0 {
|
||||
0
|
||||
} else {
|
||||
slot - 1
|
||||
}
|
||||
};
|
||||
last_entry_hash = fill_blockstore_slot_with_ticks(
|
||||
&blockstore,
|
||||
ticks_per_slot,
|
||||
slot,
|
||||
parent,
|
||||
last_entry_hash,
|
||||
);
|
||||
}
|
||||
blockstore.set_roots(&[1, 2, 3]).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
blockstore.get_timestamp_slots(2, timestamp_sample_range),
|
||||
vec![0, 1, 2]
|
||||
);
|
||||
assert_eq!(
|
||||
blockstore.get_timestamp_slots(3, timestamp_sample_range),
|
||||
vec![0, 1, 2, 3]
|
||||
);
|
||||
|
||||
drop(blockstore);
|
||||
Blockstore::destroy(&blockstore_path).expect("Expected successful database destruction");
|
||||
|
||||
/*
|
||||
Build a blockstore in the ledger with gaps in rooted slot sequence
|
||||
|
||||
*/
|
||||
let blockstore_path = get_tmp_ledger_path!();
|
||||
let blockstore = Blockstore::open(&blockstore_path).unwrap();
|
||||
blockstore.set_roots(&[0]).unwrap();
|
||||
let desired_roots = vec![1, 2, 3, 5, 6, 8, 11];
|
||||
let mut last_entry_hash = Hash::default();
|
||||
for (i, slot) in desired_roots.iter().enumerate() {
|
||||
let parent = {
|
||||
if i == 0 {
|
||||
0
|
||||
} else {
|
||||
desired_roots[i - 1]
|
||||
}
|
||||
};
|
||||
last_entry_hash = fill_blockstore_slot_with_ticks(
|
||||
&blockstore,
|
||||
ticks_per_slot,
|
||||
*slot,
|
||||
parent,
|
||||
last_entry_hash,
|
||||
);
|
||||
}
|
||||
blockstore.set_roots(&desired_roots).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
blockstore.get_timestamp_slots(2, timestamp_sample_range),
|
||||
vec![0, 1, 2]
|
||||
);
|
||||
assert_eq!(
|
||||
blockstore.get_timestamp_slots(6, timestamp_sample_range),
|
||||
vec![1, 2, 3, 5, 6]
|
||||
);
|
||||
assert_eq!(
|
||||
blockstore.get_timestamp_slots(8, timestamp_sample_range),
|
||||
vec![2, 3, 5, 6, 8]
|
||||
);
|
||||
assert_eq!(
|
||||
blockstore.get_timestamp_slots(11, timestamp_sample_range),
|
||||
vec![3, 5, 6, 8, 11]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_confirmed_block() {
|
||||
let slot = 10;
|
||||
|
@ -5963,130 +5763,6 @@ pub mod tests {
|
|||
Blockstore::destroy(&ledger_path).expect("Expected successful database destruction");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_block_timestamps() {
|
||||
let vote_keypairs: Vec<Keypair> = (0..6).map(|_| Keypair::new()).collect();
|
||||
let base_timestamp = 1_576_183_541;
|
||||
let mut expected_timestamps: Vec<(Pubkey, (Slot, UnixTimestamp))> = Vec::new();
|
||||
|
||||
// Populate slot 1 with vote transactions, some of which have timestamps
|
||||
let mut vote_entries: Vec<Entry> = Vec::new();
|
||||
for (i, keypair) in vote_keypairs.iter().enumerate() {
|
||||
let timestamp = if i % 2 == 0 {
|
||||
let unique_timestamp = base_timestamp + i as i64;
|
||||
expected_timestamps.push((keypair.pubkey(), (1, unique_timestamp)));
|
||||
Some(unique_timestamp)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let vote = Vote {
|
||||
slots: vec![1],
|
||||
hash: Hash::default(),
|
||||
timestamp,
|
||||
};
|
||||
let vote_ix = vote_instruction::vote(&keypair.pubkey(), &keypair.pubkey(), vote);
|
||||
let vote_msg = Message::new(&[vote_ix], Some(&keypair.pubkey()));
|
||||
let vote_tx = Transaction::new(&[keypair], vote_msg, Hash::default());
|
||||
|
||||
vote_entries.push(next_entry_mut(&mut Hash::default(), 0, vec![vote_tx]));
|
||||
let mut tick = create_ticks(1, 0, hash(&serialize(&i).unwrap()));
|
||||
vote_entries.append(&mut tick);
|
||||
}
|
||||
let shreds = entries_to_test_shreds(vote_entries, 1, 0, true, 0);
|
||||
let ledger_path = get_tmp_ledger_path!();
|
||||
let blockstore = Blockstore::open(&ledger_path).unwrap();
|
||||
blockstore.insert_shreds(shreds, None, false).unwrap();
|
||||
// Populate slot 2 with ticks only
|
||||
fill_blockstore_slot_with_ticks(&blockstore, 6, 2, 1, Hash::default());
|
||||
blockstore.set_roots(&[0, 1, 2]).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
blockstore.get_block_timestamps(1).unwrap(),
|
||||
expected_timestamps
|
||||
);
|
||||
assert_eq!(blockstore.get_block_timestamps(2).unwrap(), vec![]);
|
||||
|
||||
blockstore.set_roots(&[3, 8]).unwrap();
|
||||
let mut stakes = HashMap::new();
|
||||
let slot_duration = Duration::from_millis(400);
|
||||
for slot in &[1, 2, 3, 8] {
|
||||
assert!(blockstore
|
||||
.cache_block_time_from_slot_entries(*slot, slot_duration, &stakes)
|
||||
.is_err());
|
||||
}
|
||||
|
||||
// Build epoch vote_accounts HashMap to test stake-weighted block time
|
||||
for (i, keypair) in vote_keypairs.iter().enumerate() {
|
||||
stakes.insert(keypair.pubkey(), (1 + i as u64, ArcVoteAccount::default()));
|
||||
}
|
||||
for slot in &[1, 2, 3, 8] {
|
||||
blockstore
|
||||
.cache_block_time_from_slot_entries(*slot, slot_duration, &stakes)
|
||||
.unwrap();
|
||||
}
|
||||
let block_time_slot_3 = blockstore.get_block_time(3);
|
||||
|
||||
let mut total_stake = 0;
|
||||
let mut expected_time: u64 = (0..6)
|
||||
.map(|x| {
|
||||
if x % 2 == 0 {
|
||||
total_stake += 1 + x;
|
||||
(base_timestamp as u64 + x) * (1 + x)
|
||||
} else {
|
||||
0
|
||||
}
|
||||
})
|
||||
.sum();
|
||||
expected_time /= total_stake;
|
||||
assert_eq!(block_time_slot_3.unwrap().unwrap() as u64, expected_time);
|
||||
assert_eq!(
|
||||
blockstore.get_block_time(8).unwrap().unwrap() as u64,
|
||||
expected_time + 2 // At 400ms block duration, 5 slots == 2sec
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_block_time_no_timestamps() {
|
||||
let vote_keypairs: Vec<Keypair> = (0..6).map(|_| Keypair::new()).collect();
|
||||
|
||||
// Populate slot 1 with vote transactions, none of which have timestamps
|
||||
let mut vote_entries: Vec<Entry> = Vec::new();
|
||||
for (i, keypair) in vote_keypairs.iter().enumerate() {
|
||||
let vote = Vote {
|
||||
slots: vec![1],
|
||||
hash: Hash::default(),
|
||||
timestamp: None,
|
||||
};
|
||||
let vote_ix = vote_instruction::vote(&keypair.pubkey(), &keypair.pubkey(), vote);
|
||||
let vote_msg = Message::new(&[vote_ix], Some(&keypair.pubkey()));
|
||||
let vote_tx = Transaction::new(&[keypair], vote_msg, Hash::default());
|
||||
|
||||
vote_entries.push(next_entry_mut(&mut Hash::default(), 0, vec![vote_tx]));
|
||||
let mut tick = create_ticks(1, 0, hash(&serialize(&i).unwrap()));
|
||||
vote_entries.append(&mut tick);
|
||||
}
|
||||
let shreds = entries_to_test_shreds(vote_entries, 1, 0, true, 0);
|
||||
let ledger_path = get_tmp_ledger_path!();
|
||||
let blockstore = Blockstore::open(&ledger_path).unwrap();
|
||||
blockstore.insert_shreds(shreds, None, false).unwrap();
|
||||
// Populate slot 2 with ticks only
|
||||
fill_blockstore_slot_with_ticks(&blockstore, 6, 2, 1, Hash::default());
|
||||
blockstore.set_roots(&[0, 1, 2]).unwrap();
|
||||
|
||||
// Build epoch vote_accounts HashMap to test stake-weighted block time
|
||||
let mut stakes = HashMap::new();
|
||||
for (i, keypair) in vote_keypairs.iter().enumerate() {
|
||||
stakes.insert(keypair.pubkey(), (1 + i as u64, ArcVoteAccount::default()));
|
||||
}
|
||||
let slot_duration = Duration::from_millis(400);
|
||||
for slot in &[1, 2, 3, 8] {
|
||||
assert!(blockstore
|
||||
.cache_block_time_from_slot_entries(*slot, slot_duration, &stakes)
|
||||
.is_err());
|
||||
assert_eq!(blockstore.get_block_time(*slot).unwrap(), None);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_persist_transaction_status() {
|
||||
let blockstore_path = get_tmp_ledger_path!();
|
||||
|
|
|
@ -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,25 +1354,11 @@ 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())
|
||||
{
|
||||
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()) {
|
||||
.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 {
|
||||
|
@ -1381,39 +1366,30 @@ impl Bank {
|
|||
} else {
|
||||
self.epoch()
|
||||
};
|
||||
let first_slot_in_epoch =
|
||||
self.epoch_schedule.get_first_slot_in_epoch(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())
|
||||
.is_active(&feature_set::warp_timestamp_again::id())
|
||||
{
|
||||
MAX_ALLOWABLE_DRIFT_PERCENTAGE
|
||||
MaxAllowableDrift {
|
||||
fast: MAX_ALLOWABLE_DRIFT_PERCENTAGE_FAST,
|
||||
slow: MAX_ALLOWABLE_DRIFT_PERCENTAGE_SLOW,
|
||||
}
|
||||
} else {
|
||||
DEPRECATED_MAX_ALLOWABLE_DRIFT_PERCENTAGE
|
||||
};
|
||||
(
|
||||
EstimateType::Bounded(max_allowable_drift),
|
||||
epoch_start_timestamp,
|
||||
)
|
||||
} else {
|
||||
(EstimateType::Unbounded, None)
|
||||
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(estimate_type, epoch_start_timestamp)
|
||||
self.get_timestamp_estimate(max_allowable_drift, epoch_start_timestamp)
|
||||
{
|
||||
unix_timestamp = timestamp_estimate;
|
||||
if self
|
||||
.feature_set
|
||||
.is_active(&feature_set::timestamp_bounding::id())
|
||||
&& timestamp_estimate < ancestor_timestamp
|
||||
{
|
||||
if timestamp_estimate < ancestor_timestamp {
|
||||
unix_timestamp = ancestor_timestamp;
|
||||
}
|
||||
}
|
||||
|
@ -1424,19 +1400,12 @@ impl Bank {
|
|||
("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())
|
||||
{
|
||||
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();
|
||||
|
@ -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);
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -190,10 +190,6 @@ pub mod max_program_call_depth_64 {
|
|||
solana_sdk::declare_id!("YCKSgA6XmjtkQrHBQjpyNrX6EMhJPcYcLWMVgWn36iv");
|
||||
}
|
||||
|
||||
pub mod timestamp_correction {
|
||||
solana_sdk::declare_id!("3zydSLUwuqqsV3wL5wBsaVgyvMox3XTHx7zLEuQf1U2Z");
|
||||
}
|
||||
|
||||
pub mod cumulative_rent_related_fixes {
|
||||
solana_sdk::declare_id!("FtjnuAtJTWwX3Kx9m24LduNEhzaGuuPfDW6e14SX2Fy5");
|
||||
}
|
||||
|
@ -210,10 +206,6 @@ pub mod pull_request_ping_pong_check {
|
|||
solana_sdk::declare_id!("5RzEHTnf6D7JPZCvwEzjM19kzBsyjSU3HoMfXaQmVgnZ");
|
||||
}
|
||||
|
||||
pub mod timestamp_bounding {
|
||||
solana_sdk::declare_id!("2cGj3HJYPhBrtQizd7YbBxEsifFs5qhzabyFjUAp6dBa");
|
||||
}
|
||||
|
||||
pub mod stake_program_v2 {
|
||||
solana_sdk::declare_id!("Gvd9gGJZDHGMNf1b3jkxrfBQSR5etrfTQSBNKCvLSFJN");
|
||||
}
|
||||
|
@ -238,10 +230,6 @@ pub mod try_find_program_address_syscall_enabled {
|
|||
solana_sdk::declare_id!("EMsMNadQNhCYDyGpYH5Tx6dGHxiUqKHk782PU5XaWfmi");
|
||||
}
|
||||
|
||||
pub mod warp_timestamp {
|
||||
solana_sdk::declare_id!("Bfqm7fGk5MBptqa2WHXWFLH7uJvq8hkJcAQPipy2bAMk");
|
||||
}
|
||||
|
||||
pub mod stake_program_v3 {
|
||||
solana_sdk::declare_id!("Ego6nTu7WsBcZBvVqJQKp6Yku2N3mrfG8oYCfaLZkAeK");
|
||||
}
|
||||
|
@ -290,6 +278,10 @@ pub mod matching_buffer_upgrade_authorities {
|
|||
solana_sdk::declare_id!("B5PSjDEJvKJEUQSL7q94N7XCEoWJCYum8XfUg7yuugUU");
|
||||
}
|
||||
|
||||
pub mod warp_timestamp_again {
|
||||
solana_sdk::declare_id!("GvDsGDkH5gyzwpDhxNixx8vtx1kwYHH13RiNAPw27zXb");
|
||||
}
|
||||
|
||||
lazy_static! {
|
||||
/// Map of feature identifiers to user-visible description
|
||||
pub static ref FEATURE_NAMES: HashMap<Pubkey, &'static str> = [
|
||||
|
@ -307,19 +299,16 @@ lazy_static! {
|
|||
(ristretto_mul_syscall_enabled::id(), "ristretto multiply syscall"),
|
||||
(max_invoke_depth_4::id(), "max invoke call depth 4"),
|
||||
(max_program_call_depth_64::id(), "max program call depth 64"),
|
||||
(timestamp_correction::id(), "correct bank timestamps"),
|
||||
(cumulative_rent_related_fixes::id(), "rent fixes (#10206, #10468, #11342)"),
|
||||
(sol_log_compute_units_syscall::id(), "sol_log_compute_units syscall (#13243)"),
|
||||
(pubkey_log_syscall_enabled::id(), "pubkey log syscall"),
|
||||
(pull_request_ping_pong_check::id(), "ping-pong packet check #12794"),
|
||||
(timestamp_bounding::id(), "add timestamp-correction bounding #13120"),
|
||||
(stake_program_v2::id(), "solana_stake_program v2"),
|
||||
(rewrite_stake::id(), "rewrite stake"),
|
||||
(filter_stake_delegation_accounts::id(), "filter stake_delegation_accounts #14062"),
|
||||
(simple_capitalization::id(), "simple capitalization"),
|
||||
(bpf_loader_upgradeable_program::id(), "upgradeable bpf loader"),
|
||||
(try_find_program_address_syscall_enabled::id(), "add try_find_program_address syscall"),
|
||||
(warp_timestamp::id(), "warp timestamp to current, adjust bounding to 50% #14210 & #14531"),
|
||||
(stake_program_v3::id(), "solana_stake_program v3"),
|
||||
(max_cpi_instruction_size_ipv6_mtu::id(), "max cross-program invocation size 1280"),
|
||||
(limit_cpi_loader_invoke::id(), "loader not authorized via CPI"),
|
||||
|
@ -358,6 +347,7 @@ lazy_static! {
|
|||
(full_inflation::stakeconomy::vote::id(), "Community vote allowing Stakeconomy.com to enable full inflation"),
|
||||
(full_inflation::w3m::vote::id(), "Community vote allowing w3m to enable full inflation"),
|
||||
(full_inflation::w3m::enable::id(), "Full inflation enabled by w3m"),
|
||||
(warp_timestamp_again::id(), "warp timestamp again, adjust bounding to 25% fast 80% slow #15204"),
|
||||
/*************** ADD NEW FEATURES HERE ***************/
|
||||
]
|
||||
.iter()
|
||||
|
|
|
@ -10,87 +10,29 @@ use std::{
|
|||
time::Duration,
|
||||
};
|
||||
|
||||
pub const TIMESTAMP_SLOT_RANGE: usize = 32;
|
||||
pub const DEPRECATED_TIMESTAMP_SLOT_RANGE: usize = 16; // Deprecated. Remove in the Solana v1.6.0 timeframe
|
||||
pub const DEPRECATED_MAX_ALLOWABLE_DRIFT_PERCENTAGE: u32 = 25;
|
||||
pub const MAX_ALLOWABLE_DRIFT_PERCENTAGE: u32 = 50;
|
||||
pub const MAX_ALLOWABLE_DRIFT_PERCENTAGE_FAST: u32 = 25;
|
||||
pub const MAX_ALLOWABLE_DRIFT_PERCENTAGE_SLOW: u32 = 80;
|
||||
|
||||
pub enum EstimateType {
|
||||
Bounded(u32), // Value represents max allowable drift percentage
|
||||
Bounded(MaxAllowableDrift), // Value represents max allowable drift percentage
|
||||
Unbounded, // Deprecated. Remove in the Solana v1.6.0 timeframe
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct MaxAllowableDrift {
|
||||
pub fast: u32, // Max allowable drift percentage faster than poh estimate
|
||||
pub slow: u32, // Max allowable drift percentage slower than poh estimate
|
||||
}
|
||||
|
||||
pub fn calculate_stake_weighted_timestamp<I, K, V, T>(
|
||||
unique_timestamps: I,
|
||||
stakes: &HashMap<Pubkey, (u64, T /*Account|ArcVoteAccount*/)>,
|
||||
slot: Slot,
|
||||
slot_duration: Duration,
|
||||
estimate_type: EstimateType,
|
||||
epoch_start_timestamp: Option<(Slot, UnixTimestamp)>,
|
||||
) -> Option<UnixTimestamp>
|
||||
where
|
||||
I: IntoIterator<Item = (K, V)>,
|
||||
K: Borrow<Pubkey>,
|
||||
V: Borrow<(Slot, UnixTimestamp)>,
|
||||
{
|
||||
match estimate_type {
|
||||
EstimateType::Bounded(max_allowable_drift) => calculate_bounded_stake_weighted_timestamp(
|
||||
unique_timestamps,
|
||||
stakes,
|
||||
slot,
|
||||
slot_duration,
|
||||
epoch_start_timestamp,
|
||||
max_allowable_drift,
|
||||
),
|
||||
EstimateType::Unbounded => calculate_unbounded_stake_weighted_timestamp(
|
||||
unique_timestamps,
|
||||
stakes,
|
||||
slot,
|
||||
slot_duration,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
fn calculate_unbounded_stake_weighted_timestamp<I, K, V, T>(
|
||||
unique_timestamps: I,
|
||||
stakes: &HashMap<Pubkey, (u64, T /*Account|ArcVoteAccount*/)>,
|
||||
slot: Slot,
|
||||
slot_duration: Duration,
|
||||
) -> Option<UnixTimestamp>
|
||||
where
|
||||
I: IntoIterator<Item = (K, V)>,
|
||||
K: Borrow<Pubkey>,
|
||||
V: Borrow<(Slot, UnixTimestamp)>,
|
||||
{
|
||||
let (stake_weighted_timestamps_sum, total_stake) = unique_timestamps
|
||||
.into_iter()
|
||||
.filter_map(|(vote_pubkey, slot_timestamp)| {
|
||||
let (timestamp_slot, timestamp) = slot_timestamp.borrow();
|
||||
let offset = (slot - timestamp_slot) as u32 * slot_duration;
|
||||
stakes.get(vote_pubkey.borrow()).map(|(stake, _account)| {
|
||||
(
|
||||
(*timestamp as u128 + offset.as_secs() as u128) * *stake as u128,
|
||||
stake,
|
||||
)
|
||||
})
|
||||
})
|
||||
.fold((0, 0), |(timestamps, stakes), (timestamp, stake)| {
|
||||
(timestamps + timestamp, stakes + *stake as u128)
|
||||
});
|
||||
if total_stake > 0 {
|
||||
Some((stake_weighted_timestamps_sum / total_stake) as i64)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn calculate_bounded_stake_weighted_timestamp<I, K, V, T>(
|
||||
unique_timestamps: I,
|
||||
stakes: &HashMap<Pubkey, (u64, T /*Account|ArcVoteAccount*/)>,
|
||||
slot: Slot,
|
||||
slot_duration: Duration,
|
||||
epoch_start_timestamp: Option<(Slot, UnixTimestamp)>,
|
||||
max_allowable_drift_percentage: u32,
|
||||
max_allowable_drift: MaxAllowableDrift,
|
||||
fix_estimate_into_u64: bool,
|
||||
) -> Option<UnixTimestamp>
|
||||
where
|
||||
I: IntoIterator<Item = (K, V)>,
|
||||
|
@ -126,27 +68,31 @@ where
|
|||
break;
|
||||
}
|
||||
}
|
||||
// Bound estimate by `MAX_ALLOWABLE_DRIFT_PERCENTAGE` since the start of the epoch
|
||||
// Bound estimate by `max_allowable_drift` since the start of the epoch
|
||||
if let Some((epoch_start_slot, epoch_start_timestamp)) = epoch_start_timestamp {
|
||||
let poh_estimate_offset = slot.saturating_sub(epoch_start_slot) as u32 * slot_duration;
|
||||
let estimate_offset =
|
||||
Duration::from_secs(estimate.saturating_sub(epoch_start_timestamp) as u64);
|
||||
let max_allowable_drift = poh_estimate_offset * max_allowable_drift_percentage / 100;
|
||||
let estimate_offset = Duration::from_secs(if fix_estimate_into_u64 {
|
||||
(estimate as u64).saturating_sub(epoch_start_timestamp as u64)
|
||||
} else {
|
||||
estimate.saturating_sub(epoch_start_timestamp) as u64
|
||||
});
|
||||
let max_allowable_drift_fast = poh_estimate_offset * max_allowable_drift.fast / 100;
|
||||
let max_allowable_drift_slow = poh_estimate_offset * max_allowable_drift.slow / 100;
|
||||
if estimate_offset > poh_estimate_offset
|
||||
&& estimate_offset - poh_estimate_offset > max_allowable_drift
|
||||
&& estimate_offset - poh_estimate_offset > max_allowable_drift_slow
|
||||
{
|
||||
// estimate offset since the start of the epoch is higher than
|
||||
// `MAX_ALLOWABLE_DRIFT_PERCENTAGE`
|
||||
// `MAX_ALLOWABLE_DRIFT_PERCENTAGE_SLOW`
|
||||
estimate = epoch_start_timestamp
|
||||
+ poh_estimate_offset.as_secs() as i64
|
||||
+ max_allowable_drift.as_secs() as i64;
|
||||
+ max_allowable_drift_slow.as_secs() as i64;
|
||||
} else if estimate_offset < poh_estimate_offset
|
||||
&& poh_estimate_offset - estimate_offset > max_allowable_drift
|
||||
&& poh_estimate_offset - estimate_offset > max_allowable_drift_fast
|
||||
{
|
||||
// estimate offset since the start of the epoch is lower than
|
||||
// `MAX_ALLOWABLE_DRIFT_PERCENTAGE`
|
||||
// `MAX_ALLOWABLE_DRIFT_PERCENTAGE_FAST`
|
||||
estimate = epoch_start_timestamp + poh_estimate_offset.as_secs() as i64
|
||||
- max_allowable_drift.as_secs() as i64;
|
||||
- max_allowable_drift_fast.as_secs() as i64;
|
||||
}
|
||||
}
|
||||
Some(estimate)
|
||||
|
@ -158,114 +104,7 @@ pub mod tests {
|
|||
use solana_sdk::{account::Account, native_token::sol_to_lamports};
|
||||
|
||||
#[test]
|
||||
fn test_calculate_stake_weighted_timestamp() {
|
||||
let recent_timestamp: UnixTimestamp = 1_578_909_061;
|
||||
let slot = 5;
|
||||
let slot_duration = Duration::from_millis(400);
|
||||
let expected_offset = (slot * slot_duration).as_secs();
|
||||
let pubkey0 = solana_sdk::pubkey::new_rand();
|
||||
let pubkey1 = solana_sdk::pubkey::new_rand();
|
||||
let pubkey2 = solana_sdk::pubkey::new_rand();
|
||||
let pubkey3 = solana_sdk::pubkey::new_rand();
|
||||
let unique_timestamps: HashMap<Pubkey, (Slot, UnixTimestamp)> = [
|
||||
(pubkey0, (0, recent_timestamp)),
|
||||
(pubkey1, (0, recent_timestamp)),
|
||||
(pubkey2, (0, recent_timestamp)),
|
||||
(pubkey3, (0, recent_timestamp)),
|
||||
]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
let stakes: HashMap<Pubkey, (u64, Account)> = [
|
||||
(
|
||||
pubkey0,
|
||||
(
|
||||
sol_to_lamports(4_500_000_000.0),
|
||||
Account::new(1, 0, &Pubkey::default()),
|
||||
),
|
||||
),
|
||||
(
|
||||
pubkey1,
|
||||
(
|
||||
sol_to_lamports(4_500_000_000.0),
|
||||
Account::new(1, 0, &Pubkey::default()),
|
||||
),
|
||||
),
|
||||
(
|
||||
pubkey2,
|
||||
(
|
||||
sol_to_lamports(4_500_000_000.0),
|
||||
Account::new(1, 0, &Pubkey::default()),
|
||||
),
|
||||
),
|
||||
(
|
||||
pubkey3,
|
||||
(
|
||||
sol_to_lamports(4_500_000_000.0),
|
||||
Account::new(1, 0, &Pubkey::default()),
|
||||
),
|
||||
),
|
||||
]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
assert_eq!(
|
||||
calculate_unbounded_stake_weighted_timestamp(
|
||||
&unique_timestamps,
|
||||
&stakes,
|
||||
slot as Slot,
|
||||
slot_duration
|
||||
),
|
||||
Some(recent_timestamp + expected_offset as i64)
|
||||
);
|
||||
|
||||
let stakes: HashMap<Pubkey, (u64, Account)> = [
|
||||
(
|
||||
pubkey0,
|
||||
(
|
||||
sol_to_lamports(15_000_000_000.0),
|
||||
Account::new(1, 0, &Pubkey::default()),
|
||||
),
|
||||
),
|
||||
(
|
||||
pubkey1,
|
||||
(
|
||||
sol_to_lamports(1_000_000_000.0),
|
||||
Account::new(1, 0, &Pubkey::default()),
|
||||
),
|
||||
),
|
||||
(
|
||||
pubkey2,
|
||||
(
|
||||
sol_to_lamports(1_000_000_000.0),
|
||||
Account::new(1, 0, &Pubkey::default()),
|
||||
),
|
||||
),
|
||||
(
|
||||
pubkey3,
|
||||
(
|
||||
sol_to_lamports(1_000_000_000.0),
|
||||
Account::new(1, 0, &Pubkey::default()),
|
||||
),
|
||||
),
|
||||
]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
assert_eq!(
|
||||
calculate_unbounded_stake_weighted_timestamp(
|
||||
&unique_timestamps,
|
||||
&stakes,
|
||||
slot as Slot,
|
||||
slot_duration
|
||||
),
|
||||
Some(recent_timestamp + expected_offset as i64)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_calculate_bounded_stake_weighted_timestamp_uses_median() {
|
||||
fn test_calculate_stake_weighted_timestamp_uses_median() {
|
||||
let recent_timestamp: UnixTimestamp = 1_578_909_061;
|
||||
let slot = 5;
|
||||
let slot_duration = Duration::from_millis(400);
|
||||
|
@ -274,7 +113,7 @@ pub mod tests {
|
|||
let pubkey2 = solana_sdk::pubkey::new_rand();
|
||||
let pubkey3 = solana_sdk::pubkey::new_rand();
|
||||
let pubkey4 = solana_sdk::pubkey::new_rand();
|
||||
let max_allowable_drift = 25;
|
||||
let max_allowable_drift = MaxAllowableDrift { fast: 25, slow: 25 };
|
||||
|
||||
// Test low-staked outlier(s)
|
||||
let stakes: HashMap<Pubkey, (u64, Account)> = [
|
||||
|
@ -323,24 +162,17 @@ pub mod tests {
|
|||
.cloned()
|
||||
.collect();
|
||||
|
||||
let unbounded = calculate_unbounded_stake_weighted_timestamp(
|
||||
&unique_timestamps,
|
||||
&stakes,
|
||||
slot as Slot,
|
||||
slot_duration,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let bounded = calculate_bounded_stake_weighted_timestamp(
|
||||
let bounded = calculate_stake_weighted_timestamp(
|
||||
&unique_timestamps,
|
||||
&stakes,
|
||||
slot as Slot,
|
||||
slot_duration,
|
||||
None,
|
||||
max_allowable_drift,
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(bounded - unbounded, 527); // timestamp w/ 0.00003% of the stake can shift the timestamp backward 8min
|
||||
// With no bounding, timestamp w/ 0.00003% of the stake can shift the timestamp backward 8min
|
||||
assert_eq!(bounded, recent_timestamp); // low-staked outlier cannot affect bounded timestamp
|
||||
|
||||
let unique_timestamps: HashMap<Pubkey, (Slot, UnixTimestamp)> = [
|
||||
|
@ -354,24 +186,17 @@ pub mod tests {
|
|||
.cloned()
|
||||
.collect();
|
||||
|
||||
let unbounded = calculate_unbounded_stake_weighted_timestamp(
|
||||
&unique_timestamps,
|
||||
&stakes,
|
||||
slot as Slot,
|
||||
slot_duration,
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let bounded = calculate_bounded_stake_weighted_timestamp(
|
||||
let bounded = calculate_stake_weighted_timestamp(
|
||||
&unique_timestamps,
|
||||
&stakes,
|
||||
slot as Slot,
|
||||
slot_duration,
|
||||
None,
|
||||
max_allowable_drift,
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(unbounded - bounded, 3074455295455); // timestamp w/ 0.00003% of the stake can shift the timestamp forward 97k years!
|
||||
// With no bounding, timestamp w/ 0.00003% of the stake can shift the timestamp forward 97k years!
|
||||
assert_eq!(bounded, recent_timestamp); // low-staked outlier cannot affect bounded timestamp
|
||||
|
||||
let unique_timestamps: HashMap<Pubkey, (Slot, UnixTimestamp)> = [
|
||||
|
@ -385,13 +210,14 @@ pub mod tests {
|
|||
.cloned()
|
||||
.collect();
|
||||
|
||||
let bounded = calculate_bounded_stake_weighted_timestamp(
|
||||
let bounded = calculate_stake_weighted_timestamp(
|
||||
&unique_timestamps,
|
||||
&stakes,
|
||||
slot as Slot,
|
||||
slot_duration,
|
||||
None,
|
||||
max_allowable_drift,
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(bounded, recent_timestamp); // multiple low-staked outliers cannot affect bounded timestamp if they don't shift the median
|
||||
|
@ -433,13 +259,14 @@ pub mod tests {
|
|||
.cloned()
|
||||
.collect();
|
||||
|
||||
let bounded = calculate_bounded_stake_weighted_timestamp(
|
||||
let bounded = calculate_stake_weighted_timestamp(
|
||||
&unique_timestamps,
|
||||
&stakes,
|
||||
slot as Slot,
|
||||
slot_duration,
|
||||
None,
|
||||
max_allowable_drift,
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(bounded, recent_timestamp); // outlier(s) cannot affect bounded timestamp if they don't shift the median
|
||||
|
@ -470,26 +297,31 @@ pub mod tests {
|
|||
.cloned()
|
||||
.collect();
|
||||
|
||||
let bounded = calculate_bounded_stake_weighted_timestamp(
|
||||
let bounded = calculate_stake_weighted_timestamp(
|
||||
&unique_timestamps,
|
||||
&stakes,
|
||||
slot as Slot,
|
||||
slot_duration,
|
||||
None,
|
||||
max_allowable_drift,
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(recent_timestamp - bounded, 1578909061); // outliers > 1/2 of available stake can affect timestamp
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_calculate_bounded_stake_weighted_timestamp_poh() {
|
||||
fn test_calculate_stake_weighted_timestamp_poh() {
|
||||
let epoch_start_timestamp: UnixTimestamp = 1_578_909_061;
|
||||
let slot = 20;
|
||||
let slot_duration = Duration::from_millis(400);
|
||||
let poh_offset = (slot * slot_duration).as_secs();
|
||||
let max_allowable_drift = 25;
|
||||
let acceptable_delta = (max_allowable_drift * poh_offset as u32 / 100) as i64;
|
||||
let max_allowable_drift_percentage = 25;
|
||||
let max_allowable_drift = MaxAllowableDrift {
|
||||
fast: max_allowable_drift_percentage,
|
||||
slow: max_allowable_drift_percentage,
|
||||
};
|
||||
let acceptable_delta = (max_allowable_drift_percentage * poh_offset as u32 / 100) as i64;
|
||||
let poh_estimate = epoch_start_timestamp + poh_offset as i64;
|
||||
let pubkey0 = solana_sdk::pubkey::new_rand();
|
||||
let pubkey1 = solana_sdk::pubkey::new_rand();
|
||||
|
@ -532,13 +364,14 @@ pub mod tests {
|
|||
.cloned()
|
||||
.collect();
|
||||
|
||||
let bounded = calculate_bounded_stake_weighted_timestamp(
|
||||
let bounded = calculate_stake_weighted_timestamp(
|
||||
&unique_timestamps,
|
||||
&stakes,
|
||||
slot as Slot,
|
||||
slot_duration,
|
||||
Some((0, epoch_start_timestamp)),
|
||||
max_allowable_drift,
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(bounded, poh_estimate + acceptable_delta);
|
||||
|
@ -553,13 +386,14 @@ pub mod tests {
|
|||
.cloned()
|
||||
.collect();
|
||||
|
||||
let bounded = calculate_bounded_stake_weighted_timestamp(
|
||||
let bounded = calculate_stake_weighted_timestamp(
|
||||
&unique_timestamps,
|
||||
&stakes,
|
||||
slot as Slot,
|
||||
slot_duration,
|
||||
Some((0, epoch_start_timestamp)),
|
||||
max_allowable_drift,
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(bounded, poh_estimate - acceptable_delta);
|
||||
|
@ -574,13 +408,14 @@ pub mod tests {
|
|||
.cloned()
|
||||
.collect();
|
||||
|
||||
let bounded = calculate_bounded_stake_weighted_timestamp(
|
||||
let bounded = calculate_stake_weighted_timestamp(
|
||||
&unique_timestamps,
|
||||
&stakes,
|
||||
slot as Slot,
|
||||
slot_duration,
|
||||
Some((0, epoch_start_timestamp)),
|
||||
max_allowable_drift,
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(bounded, poh_estimate + acceptable_delta);
|
||||
|
@ -594,28 +429,39 @@ pub mod tests {
|
|||
.cloned()
|
||||
.collect();
|
||||
|
||||
let bounded = calculate_bounded_stake_weighted_timestamp(
|
||||
let bounded = calculate_stake_weighted_timestamp(
|
||||
&unique_timestamps,
|
||||
&stakes,
|
||||
slot as Slot,
|
||||
slot_duration,
|
||||
Some((0, epoch_start_timestamp)),
|
||||
max_allowable_drift,
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(bounded, poh_estimate - acceptable_delta);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_calculate_bounded_stake_weighted_timestamp_levels() {
|
||||
fn test_calculate_stake_weighted_timestamp_levels() {
|
||||
let epoch_start_timestamp: UnixTimestamp = 1_578_909_061;
|
||||
let slot = 20;
|
||||
let slot_duration = Duration::from_millis(400);
|
||||
let poh_offset = (slot * slot_duration).as_secs();
|
||||
let allowable_drift_25 = 25;
|
||||
let allowable_drift_50 = 50;
|
||||
let acceptable_delta_25 = (allowable_drift_25 * poh_offset as u32 / 100) as i64;
|
||||
let acceptable_delta_50 = (allowable_drift_50 * poh_offset as u32 / 100) as i64;
|
||||
let max_allowable_drift_percentage_25 = 25;
|
||||
let allowable_drift_25 = MaxAllowableDrift {
|
||||
fast: max_allowable_drift_percentage_25,
|
||||
slow: max_allowable_drift_percentage_25,
|
||||
};
|
||||
let max_allowable_drift_percentage_50 = 50;
|
||||
let allowable_drift_50 = MaxAllowableDrift {
|
||||
fast: max_allowable_drift_percentage_50,
|
||||
slow: max_allowable_drift_percentage_50,
|
||||
};
|
||||
let acceptable_delta_25 =
|
||||
(max_allowable_drift_percentage_25 * poh_offset as u32 / 100) as i64;
|
||||
let acceptable_delta_50 =
|
||||
(max_allowable_drift_percentage_50 * poh_offset as u32 / 100) as i64;
|
||||
assert!(acceptable_delta_50 > acceptable_delta_25 + 1);
|
||||
let poh_estimate = epoch_start_timestamp + poh_offset as i64;
|
||||
let pubkey0 = solana_sdk::pubkey::new_rand();
|
||||
|
@ -668,24 +514,26 @@ pub mod tests {
|
|||
.cloned()
|
||||
.collect();
|
||||
|
||||
let bounded = calculate_bounded_stake_weighted_timestamp(
|
||||
let bounded = calculate_stake_weighted_timestamp(
|
||||
&unique_timestamps,
|
||||
&stakes,
|
||||
slot as Slot,
|
||||
slot_duration,
|
||||
Some((0, epoch_start_timestamp)),
|
||||
allowable_drift_25,
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(bounded, poh_estimate + acceptable_delta_25);
|
||||
|
||||
let bounded = calculate_bounded_stake_weighted_timestamp(
|
||||
let bounded = calculate_stake_weighted_timestamp(
|
||||
&unique_timestamps,
|
||||
&stakes,
|
||||
slot as Slot,
|
||||
slot_duration,
|
||||
Some((0, epoch_start_timestamp)),
|
||||
allowable_drift_50,
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(bounded, poh_estimate + acceptable_delta_25 + 1);
|
||||
|
@ -709,26 +557,252 @@ pub mod tests {
|
|||
.cloned()
|
||||
.collect();
|
||||
|
||||
let bounded = calculate_bounded_stake_weighted_timestamp(
|
||||
let bounded = calculate_stake_weighted_timestamp(
|
||||
&unique_timestamps,
|
||||
&stakes,
|
||||
slot as Slot,
|
||||
slot_duration,
|
||||
Some((0, epoch_start_timestamp)),
|
||||
allowable_drift_25,
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(bounded, poh_estimate + acceptable_delta_25);
|
||||
|
||||
let bounded = calculate_bounded_stake_weighted_timestamp(
|
||||
let bounded = calculate_stake_weighted_timestamp(
|
||||
&unique_timestamps,
|
||||
&stakes,
|
||||
slot as Slot,
|
||||
slot_duration,
|
||||
Some((0, epoch_start_timestamp)),
|
||||
allowable_drift_50,
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(bounded, poh_estimate + acceptable_delta_50);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_calculate_stake_weighted_timestamp_fast_slow() {
|
||||
let epoch_start_timestamp: UnixTimestamp = 1_578_909_061;
|
||||
let slot = 20;
|
||||
let slot_duration = Duration::from_millis(400);
|
||||
let poh_offset = (slot * slot_duration).as_secs();
|
||||
let max_allowable_drift_percentage_25 = 25;
|
||||
let max_allowable_drift_percentage_50 = 50;
|
||||
let max_allowable_drift = MaxAllowableDrift {
|
||||
fast: max_allowable_drift_percentage_25,
|
||||
slow: max_allowable_drift_percentage_50,
|
||||
};
|
||||
let acceptable_delta_fast =
|
||||
(max_allowable_drift_percentage_25 * poh_offset as u32 / 100) as i64;
|
||||
let acceptable_delta_slow =
|
||||
(max_allowable_drift_percentage_50 * poh_offset as u32 / 100) as i64;
|
||||
assert!(acceptable_delta_slow > acceptable_delta_fast + 1);
|
||||
let poh_estimate = epoch_start_timestamp + poh_offset as i64;
|
||||
let pubkey0 = solana_sdk::pubkey::new_rand();
|
||||
let pubkey1 = solana_sdk::pubkey::new_rand();
|
||||
let pubkey2 = solana_sdk::pubkey::new_rand();
|
||||
|
||||
let stakes: HashMap<Pubkey, (u64, Account)> = [
|
||||
(
|
||||
pubkey0,
|
||||
(
|
||||
sol_to_lamports(1_000_000.0),
|
||||
Account::new(1, 0, &Pubkey::default()),
|
||||
),
|
||||
),
|
||||
(
|
||||
pubkey1,
|
||||
(
|
||||
sol_to_lamports(1_000_000.0),
|
||||
Account::new(1, 0, &Pubkey::default()),
|
||||
),
|
||||
),
|
||||
(
|
||||
pubkey2,
|
||||
(
|
||||
sol_to_lamports(1_000_000.0),
|
||||
Account::new(1, 0, &Pubkey::default()),
|
||||
),
|
||||
),
|
||||
]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
// Test when stake-weighted median is more than 25% fast
|
||||
let unique_timestamps: HashMap<Pubkey, (Slot, UnixTimestamp)> = [
|
||||
(
|
||||
pubkey0,
|
||||
(slot as u64, poh_estimate - acceptable_delta_fast - 1),
|
||||
),
|
||||
(
|
||||
pubkey1,
|
||||
(slot as u64, poh_estimate - acceptable_delta_fast - 1),
|
||||
),
|
||||
(
|
||||
pubkey2,
|
||||
(slot as u64, poh_estimate - acceptable_delta_fast - 1),
|
||||
),
|
||||
]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
let bounded = calculate_stake_weighted_timestamp(
|
||||
&unique_timestamps,
|
||||
&stakes,
|
||||
slot as Slot,
|
||||
slot_duration,
|
||||
Some((0, epoch_start_timestamp)),
|
||||
max_allowable_drift,
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(bounded, poh_estimate - acceptable_delta_fast);
|
||||
|
||||
// Test when stake-weighted median is more than 25% but less than 50% slow
|
||||
let unique_timestamps: HashMap<Pubkey, (Slot, UnixTimestamp)> = [
|
||||
(
|
||||
pubkey0,
|
||||
(slot as u64, poh_estimate + acceptable_delta_fast + 1),
|
||||
),
|
||||
(
|
||||
pubkey1,
|
||||
(slot as u64, poh_estimate + acceptable_delta_fast + 1),
|
||||
),
|
||||
(
|
||||
pubkey2,
|
||||
(slot as u64, poh_estimate + acceptable_delta_fast + 1),
|
||||
),
|
||||
]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
let bounded = calculate_stake_weighted_timestamp(
|
||||
&unique_timestamps,
|
||||
&stakes,
|
||||
slot as Slot,
|
||||
slot_duration,
|
||||
Some((0, epoch_start_timestamp)),
|
||||
max_allowable_drift,
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(bounded, poh_estimate + acceptable_delta_fast + 1);
|
||||
|
||||
// Test when stake-weighted median is more than 50% slow
|
||||
let unique_timestamps: HashMap<Pubkey, (Slot, UnixTimestamp)> = [
|
||||
(
|
||||
pubkey0,
|
||||
(slot as u64, poh_estimate + acceptable_delta_slow + 1),
|
||||
),
|
||||
(
|
||||
pubkey1,
|
||||
(slot as u64, poh_estimate + acceptable_delta_slow + 1),
|
||||
),
|
||||
(
|
||||
pubkey2,
|
||||
(slot as u64, poh_estimate + acceptable_delta_slow + 1),
|
||||
),
|
||||
]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
let bounded = calculate_stake_weighted_timestamp(
|
||||
&unique_timestamps,
|
||||
&stakes,
|
||||
slot as Slot,
|
||||
slot_duration,
|
||||
Some((0, epoch_start_timestamp)),
|
||||
max_allowable_drift,
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(bounded, poh_estimate + acceptable_delta_slow);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_calculate_stake_weighted_timestamp_early() {
|
||||
let epoch_start_timestamp: UnixTimestamp = 1_578_909_061;
|
||||
let slot = 20;
|
||||
let slot_duration = Duration::from_millis(400);
|
||||
let poh_offset = (slot * slot_duration).as_secs();
|
||||
let max_allowable_drift_percentage = 50;
|
||||
let max_allowable_drift = MaxAllowableDrift {
|
||||
fast: max_allowable_drift_percentage,
|
||||
slow: max_allowable_drift_percentage,
|
||||
};
|
||||
let acceptable_delta = (max_allowable_drift_percentage * poh_offset as u32 / 100) as i64;
|
||||
let poh_estimate = epoch_start_timestamp + poh_offset as i64;
|
||||
let pubkey0 = solana_sdk::pubkey::new_rand();
|
||||
let pubkey1 = solana_sdk::pubkey::new_rand();
|
||||
let pubkey2 = solana_sdk::pubkey::new_rand();
|
||||
|
||||
let stakes: HashMap<Pubkey, (u64, Account)> = [
|
||||
(
|
||||
pubkey0,
|
||||
(
|
||||
sol_to_lamports(1_000_000.0),
|
||||
Account::new(1, 0, &Pubkey::default()),
|
||||
),
|
||||
),
|
||||
(
|
||||
pubkey1,
|
||||
(
|
||||
sol_to_lamports(1_000_000.0),
|
||||
Account::new(1, 0, &Pubkey::default()),
|
||||
),
|
||||
),
|
||||
(
|
||||
pubkey2,
|
||||
(
|
||||
sol_to_lamports(1_000_000.0),
|
||||
Account::new(1, 0, &Pubkey::default()),
|
||||
),
|
||||
),
|
||||
]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
// Test when stake-weighted median is before epoch_start_timestamp
|
||||
let unique_timestamps: HashMap<Pubkey, (Slot, UnixTimestamp)> = [
|
||||
(pubkey0, (slot as u64, poh_estimate - acceptable_delta - 20)),
|
||||
(pubkey1, (slot as u64, poh_estimate - acceptable_delta - 20)),
|
||||
(pubkey2, (slot as u64, poh_estimate - acceptable_delta - 20)),
|
||||
]
|
||||
.iter()
|
||||
.cloned()
|
||||
.collect();
|
||||
|
||||
// Without fix, median timestamps before epoch_start_timestamp actually increase the time
|
||||
// estimate due to incorrect casting.
|
||||
let bounded = calculate_stake_weighted_timestamp(
|
||||
&unique_timestamps,
|
||||
&stakes,
|
||||
slot as Slot,
|
||||
slot_duration,
|
||||
Some((0, epoch_start_timestamp)),
|
||||
max_allowable_drift,
|
||||
false,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(bounded, poh_estimate + acceptable_delta);
|
||||
|
||||
let bounded = calculate_stake_weighted_timestamp(
|
||||
&unique_timestamps,
|
||||
&stakes,
|
||||
slot as Slot,
|
||||
slot_duration,
|
||||
Some((0, epoch_start_timestamp)),
|
||||
max_allowable_drift,
|
||||
true,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(bounded, poh_estimate - acceptable_delta);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue