Skip to main content

Timestamp

Validator Timestamp Oracle

在validator vote的时候可选择加入timestamp

https://github.com/anza-xyz/agave/blob/b2c4742af2631a03042e36292cfee58a9ac53612/programs/vote/src/vote_state/mod.rs#L750-L753

    if let Some(timestamp) = timestamp {
let last_slot = new_state.back().unwrap().slot();
vote_state.process_timestamp(last_slot, timestamp)?;
}

要求timestamp和slot都是严格递增的

https://github.com/anza-xyz/agave/blob/e32e1266a2a7dc9c49cde71f84d16a7d57df9e5a/sdk/program/src/vote/state/mod.rs#L939-L953

    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。