Timestamp
Validator Timestamp Oracle
在validator vote的时候可选择加入timestamp
if let Some(timestamp) = timestamp {
let last_slot = new_state.back().unwrap().slot();
vote_state.process_timestamp(last_slot, timestamp)?;
}
要求timestamp和slot都是严格递增的
pub fn process_timestamp(
&mut self,
slot: Slot,
timestamp: UnixTimestamp,
) -> Result<(), VoteError> {
if (slot < self.last_timestamp.slot || timestamp < self.last_timestamp.timestamp)
|| (slot == self.last_timestamp.slot
&& BlockTimestamp { slot, timestamp } != self.last_timestamp
&& self.last_timestamp.slot != 0)
{
return Err(VoteError::TimestampTooOld);
}
self.last_timestamp = BlockTimestamp { slot, timestamp };
Ok(())
}
每个vote account里保存的VoteState里会记录slot和timestamp,也就是同一个vote account里应该是不能回滚的
在创建新的Bank(大概是block state的概念)时,处理tx前会先更新sysvars,包括clock:https://github.com/anza-xyz/agave/blob/ccabfcf84921977202fd06d3197cbcea83742133/runtime/src/bank.rs#L1249-L1255
// Update sysvars before processing transactions
let (_, update_sysvars_time_us) = measure_us!({
new.update_slot_hashes();
new.update_stake_history(Some(parent.epoch()));
new.update_clock(Some(parent.epoch()));
new.update_last_restart_slot()
});
update_clock会把新状态写进sysvar::clock::id()对应的account:https://github.com/anza-xyz/agave/blob/ccabfcf84921977202fd06d3197cbcea83742133/runtime/src/bank.rs#L1835-L1847
let clock = sysvar::clock::Clock {
slot: self.slot,
epoch_start_timestamp,
epoch: self.epoch_schedule().get_epoch(self.slot),
leader_schedule_epoch: self.epoch_schedule().get_leader_schedule_epoch(self.slot),
unix_timestamp,
};
self.update_sysvar_account(&sysvar::clock::id(), |account| {
create_account(
&clock,
self.inherit_specially_retained_account_fields(account),
)
});
Bank Timestamp Correction
timestamp共识从加权平均值调整为加权中位数,消除权重很小的voter提交超大timestamp的风险。
在update_clock时会估计当前的timestamp,此时也会对drift有限制:https://github.com/anza-xyz/agave/blob/ccabfcf84921977202fd06d3197cbcea83742133/runtime/src/bank.rs#L1803-L1816
let max_allowable_drift = MaxAllowableDrift {
fast: MAX_ALLOWABLE_DRIFT_PERCENTAGE_FAST,
slow: MAX_ALLOWABLE_DRIFT_PERCENTAGE_SLOW_V2,
};
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;
}
}
估计timestamp的算法是,根据每一个vote account里记录的slot, timestamp,按照每个slot 0.4s,计算对应当前slot的timestamp,然后去加权中位数:https://github.com/anza-xyz/agave/blob/99cf1e280e0dc0c21b59050463cca4b319bd928f/runtime/src/stake_weighted_timestamp.rs#L26-L101
for (vote_pubkey, slot_timestamp) in unique_timestamps {
let (timestamp_slot, timestamp) = slot_timestamp.borrow();
let offset = slot_duration.saturating_mul(slot.saturating_sub(*timestamp_slot) as u32);
let estimate = timestamp.saturating_add(offset.as_secs() as i64);
let stake = stakes
.get(vote_pubkey.borrow())
.map(|(stake, _account)| stake)
.unwrap_or(&0);
stake_per_timestamp
.entry(estimate)
.and_modify(|stake_sum| *stake_sum = stake_sum.saturating_add(*stake as u128))
.or_insert(*stake as u128);
total_stake = total_stake.saturating_add(*stake as u128);
}
最后会根据max allowable drift再修正最终的estimate。