Skip to content

Commit

Permalink
feature(data_structures): switch block time when v2.0 activates
Browse files Browse the repository at this point in the history
  • Loading branch information
drcpu-github committed Jun 21, 2024
1 parent db15ccf commit 9ec8d09
Show file tree
Hide file tree
Showing 14 changed files with 245 additions and 56 deletions.
8 changes: 4 additions & 4 deletions config/src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -432,13 +432,13 @@ pub struct Tapi {
/// Configuration related to protocol versions.
#[derive(Clone, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
pub struct Protocol {
pub v1_7: Option<Epoch>,
pub v1_8: Option<Epoch>,
pub v2_0: Option<Epoch>,
pub v1_7: Option<(Epoch, u16)>,
pub v1_8: Option<(Epoch, u16)>,
pub v2_0: Option<(Epoch, u16)>,
}

impl Protocol {
pub fn iter(&self) -> IntoIter<(ProtocolVersion, Option<Epoch>), 3> {
pub fn iter(&self) -> IntoIter<(ProtocolVersion, Option<(Epoch, u16)>), 3> {
[
(ProtocolVersion::V1_7, self.v1_7),
(ProtocolVersion::V1_8, self.v1_8),
Expand Down
4 changes: 2 additions & 2 deletions config/src/defaults.rs
Original file line number Diff line number Diff line change
Expand Up @@ -481,8 +481,8 @@ pub trait Defaults {
100
}

fn protocol_versions(&self) -> HashMap<ProtocolVersion, Epoch> {
[(ProtocolVersion::V1_7, 0)].into_iter().collect()
fn protocol_versions(&self) -> HashMap<ProtocolVersion, (Epoch, u16)> {
[(ProtocolVersion::V1_7, (0, 45))].into_iter().collect()
}
}

Expand Down
92 changes: 74 additions & 18 deletions data_structures/src/chain/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4478,6 +4478,12 @@ pub struct EpochConstants {

/// Period between checkpoints, in seconds
pub checkpoints_period: u16,

/// Timestamp of checkpoint (in seconds) when v2 started)
pub checkpoint_zero_timestamp_v2: i64,

/// Period between checkpoints, in seconds, starting at v2
pub checkpoints_period_v2: u16,
}

// This default is only used for tests
Expand All @@ -4487,46 +4493,96 @@ impl Default for EpochConstants {
checkpoint_zero_timestamp: 0,
// This cannot be 0 because we would divide by zero
checkpoints_period: 1,
// Variables for v2
checkpoint_zero_timestamp_v2: i64::MAX,
// This cannot be 0 because we would divide by zero
checkpoints_period_v2: 1,
}
}
}

impl EpochConstants {
/// Calculate the last checkpoint (current epoch) at the supplied timestamp
pub fn epoch_at(&self, timestamp: i64) -> Result<Epoch, EpochCalculationError> {
let zero = self.checkpoint_zero_timestamp;
let period = self.checkpoints_period;
let elapsed = timestamp - zero;
if timestamp >= self.checkpoint_zero_timestamp_v2 {
let epochs_pre_v2 = match Epoch::try_from(
self.checkpoint_zero_timestamp_v2 - self.checkpoint_zero_timestamp,
) {
Ok(epoch) => epoch / Epoch::from(self.checkpoints_period),
Err(_) => {
return Err(EpochCalculationError::CheckpointZeroInTheFuture(
self.checkpoint_zero_timestamp,
));
}
};
let epochs_post_v2 =
match Epoch::try_from(timestamp - self.checkpoint_zero_timestamp_v2) {
Ok(epoch) => epoch / Epoch::from(self.checkpoints_period_v2),
Err(_) => {
return Err(EpochCalculationError::CheckpointZeroInTheFuture(
self.checkpoint_zero_timestamp,
));
}
};

Epoch::try_from(elapsed)
.map(|epoch| epoch / Epoch::from(period))
.map_err(|_| EpochCalculationError::CheckpointZeroInTheFuture(zero))
Ok(epochs_pre_v2 + epochs_post_v2)
} else {
Epoch::try_from(timestamp - self.checkpoint_zero_timestamp)
.map(|epoch| epoch / Epoch::from(self.checkpoints_period))
.map_err(|_| {
EpochCalculationError::CheckpointZeroInTheFuture(self.checkpoint_zero_timestamp)
})
}
}

/// Calculate the timestamp for a checkpoint (the start of an epoch)
pub fn epoch_timestamp(&self, epoch: Epoch) -> Result<i64, EpochCalculationError> {
let zero = self.checkpoint_zero_timestamp;
let period = self.checkpoints_period;

Epoch::from(period)
pub fn epoch_timestamp(&self, epoch: Epoch) -> Result<(i64, bool), EpochCalculationError> {
let epoch_timestamp = Epoch::from(self.checkpoints_period)
.checked_mul(epoch)
.filter(|&x| x <= Epoch::MAX as Epoch)
.map(i64::from)
.and_then(|x| x.checked_add(zero))
.ok_or(EpochCalculationError::Overflow)
.and_then(|x| x.checked_add(self.checkpoint_zero_timestamp))
.ok_or(EpochCalculationError::Overflow);

let epoch_timestamp = match epoch_timestamp {
Ok(timestamp) => timestamp,
Err(error) => {
return Err(error);
}
};

let mut in_v2 = false;
let timestamp = if epoch_timestamp >= self.checkpoint_zero_timestamp_v2 {
in_v2 = true;

let epochs_pre_v2 = ((self.checkpoint_zero_timestamp_v2
- self.checkpoint_zero_timestamp)
/ self.checkpoints_period as i64) as u32;

self.checkpoint_zero_timestamp_v2
+ i64::from((epoch - epochs_pre_v2) * Epoch::from(self.checkpoints_period_v2))
} else {
epoch_timestamp
};

Ok((timestamp, in_v2))
}

/// Calculate the timestamp for when block mining should happen.
pub fn block_mining_timestamp(&self, epoch: Epoch) -> Result<i64, EpochCalculationError> {
let start = self.epoch_timestamp(epoch)?;
let (start, in_v2) = self.epoch_timestamp(epoch)?;
// TODO: analyze when should nodes start mining a block
// Start mining at the midpoint of the epoch
let seconds_before_next_epoch = self.checkpoints_period / 2;
let checkpoints_period = if in_v2 {
self.checkpoints_period_v2
} else {
self.checkpoints_period
};

let seconds_before_next_epoch = checkpoints_period / 2;

start
.checked_add(i64::from(
self.checkpoints_period - seconds_before_next_epoch,
))
.checked_add(i64::from(checkpoints_period - seconds_before_next_epoch))
.ok_or(EpochCalculationError::Overflow)
}
}
Expand Down
31 changes: 26 additions & 5 deletions data_structures/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -139,14 +139,18 @@ pub fn get_protocol_version(epoch: Option<Epoch>) -> ProtocolVersion {
}

/// Let the protocol versions controller know about the a protocol version, and its activation epoch.
pub fn register_protocol_version(protocol_version: ProtocolVersion, epoch: Epoch) {
pub fn register_protocol_version(
protocol_version: ProtocolVersion,
epoch: Epoch,
checkpoint_period: u16,
) {
log::debug!(
"Registering protocol version {protocol_version}, which enters into force at epoch {epoch}"
);
// This unwrap is safe as long as the lock is not poisoned.
// The lock can only become poisoned when a writer panics.
let mut protocol_info = PROTOCOL.write().unwrap();
protocol_info.register(epoch, protocol_version);
protocol_info.register(epoch, protocol_version, checkpoint_period);
}

/// Set the protocol version that we are running.
Expand All @@ -163,6 +167,23 @@ pub fn refresh_protocol_version(current_epoch: Epoch) {
set_protocol_version(current_version)
}

pub fn get_protocol_version_activation_epoch(protocol_version: ProtocolVersion) -> Epoch {
// This unwrap is safe as long as the lock is not poisoned.
// The lock can only become poisoned when a writer panics.
let protocol = PROTOCOL.write().unwrap();
protocol.all_versions.get_activation_epoch(protocol_version)
}

pub fn get_protocol_version_period(protocol_version: ProtocolVersion) -> u16 {
// This unwrap is safe as long as the lock is not poisoned.
// The lock can only become poisoned when a writer panics.
let protocol = PROTOCOL.write().unwrap();
match protocol.all_checkpoints_periods.get(&protocol_version) {
Some(period) => *period,
None => u16::MAX,
}
}

#[cfg(test)]
mod tests {
use super::*;
Expand All @@ -183,9 +204,9 @@ mod tests {
assert_eq!(version, ProtocolVersion::V1_7);

// Register the different protocol versions
register_protocol_version(ProtocolVersion::V1_7, 100);
register_protocol_version(ProtocolVersion::V1_8, 200);
register_protocol_version(ProtocolVersion::V2_0, 300);
register_protocol_version(ProtocolVersion::V1_7, 100, 45);
register_protocol_version(ProtocolVersion::V1_8, 200, 45);
register_protocol_version(ProtocolVersion::V2_0, 300, 20);

// The initial protocol version should be the default one
let version = get_protocol_version(Some(0));
Expand Down
14 changes: 12 additions & 2 deletions data_structures/src/proto/versioning.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,14 @@ use crate::{
pub struct ProtocolInfo {
pub current_version: ProtocolVersion,
pub all_versions: VersionsMap,
pub all_checkpoints_periods: HashMap<ProtocolVersion, u16>,
}

impl ProtocolInfo {
pub fn register(&mut self, epoch: Epoch, version: ProtocolVersion) {
self.all_versions.register(epoch, version)
pub fn register(&mut self, epoch: Epoch, version: ProtocolVersion, checkpoint_period: u16) {
self.all_versions.register(epoch, version);
self.all_checkpoints_periods
.insert(version, checkpoint_period);
}
}

Expand All @@ -60,6 +63,13 @@ impl VersionsMap {
.copied()
.unwrap_or_default()
}

pub fn get_activation_epoch(&self, version: ProtocolVersion) -> Epoch {
match self.efv.get(&version) {
Some(epoch) => *epoch,
None => Epoch::MAX,
}
}
}

/// An enumeration of different protocol versions.
Expand Down
2 changes: 1 addition & 1 deletion data_structures/src/transaction_factory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -675,7 +675,7 @@ pub fn transaction_inputs_sum(
})?;

// Verify that commits are only accepted after the time lock expired
let epoch_timestamp = epoch_constants.epoch_timestamp(epoch)?;
let (epoch_timestamp, _) = epoch_constants.epoch_timestamp(epoch)?;
let vt_time_lock = i64::try_from(vt_output.time_lock)?;
if vt_time_lock > epoch_timestamp {
return Err(TransactionError::TimeLock {
Expand Down
6 changes: 6 additions & 0 deletions node/src/actors/chain_manager/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4072,6 +4072,8 @@ mod tests {
chain_manager.epoch_constants = Some(EpochConstants {
checkpoint_zero_timestamp: 0,
checkpoints_period: 1_000,
checkpoint_zero_timestamp_v2: i64::MAX,
checkpoints_period_v2: 1,
});
chain_manager.chain_state.chain_info = Some(ChainInfo {
environment: Environment::default(),
Expand Down Expand Up @@ -4113,10 +4115,12 @@ mod tests {
Reputation(0),
vrf_hash_1,
false,
Power::from(0 as u64),
block_2.hash(),
Reputation(0),
vrf_hash_2,
false,
Power::from(0 as u64),
&VrfSlots::new(vec![Hash::default()]),
ProtocolVersion::V1_7,
),
Expand Down Expand Up @@ -4199,6 +4203,8 @@ mod tests {
chain_manager.epoch_constants = Some(EpochConstants {
checkpoint_zero_timestamp: 0,
checkpoints_period: 1_000,
checkpoint_zero_timestamp_v2: i64::MAX,
checkpoints_period_v2: 1,
});
chain_manager.chain_state.chain_info = Some(ChainInfo {
environment: Environment::default(),
Expand Down
23 changes: 17 additions & 6 deletions node/src/actors/epoch_manager/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use rand::Rng;
use witnet_data_structures::{
chain::{Epoch, EpochConstants},
error::EpochCalculationError,
get_protocol_version_activation_epoch, get_protocol_version_period,
proto::versioning::ProtocolVersion,
};
use witnet_util::timestamp::{
duration_between_timestamps, get_timestamp, get_timestamp_nanos, update_global_timestamp,
Expand Down Expand Up @@ -85,15 +87,15 @@ impl EpochManager {
pub fn set_checkpoint_zero_and_period(
&mut self,
checkpoint_zero_timestamp: i64,
mut checkpoints_period: u16,
checkpoints_period: u16,
checkpoint_zero_timestamp_v2: i64,
checkpoints_period_v2: u16,
) {
if checkpoints_period == 0 {
log::warn!("Setting the checkpoint period to the minimum value of 1 second");
checkpoints_period = 1;
}
self.constants = Some(EpochConstants {
checkpoint_zero_timestamp,
checkpoints_period,
checkpoint_zero_timestamp_v2,
checkpoints_period_v2,
});
}
/// Calculate the last checkpoint (current epoch) at the supplied timestamp
Expand All @@ -113,7 +115,10 @@ impl EpochManager {
pub fn epoch_timestamp(&self, epoch: Epoch) -> EpochResult<i64> {
match &self.constants {
// Calculate (period * epoch + zero) with overflow checks
Some(x) => Ok(x.epoch_timestamp(epoch)?),
Some(x) => {
let (timestamp, _) = x.epoch_timestamp(epoch)?;
Ok(timestamp)
}
None => Err(EpochManagerError::UnknownEpochConstants),
}
}
Expand All @@ -122,9 +127,15 @@ impl EpochManager {
config_mngr::get()
.into_actor(self)
.and_then(|config, act, ctx| {
let checkpoint_zero_timestamp_v2 =
config.consensus_constants.checkpoint_zero_timestamp
+ get_protocol_version_activation_epoch(ProtocolVersion::V2_0) as i64
* config.consensus_constants.checkpoints_period as i64;
act.set_checkpoint_zero_and_period(
config.consensus_constants.checkpoint_zero_timestamp,
config.consensus_constants.checkpoints_period,
checkpoint_zero_timestamp_v2,
get_protocol_version_period(ProtocolVersion::V2_0),
);
log::info!(
"Checkpoint zero timestamp: {}, checkpoints period: {}",
Expand Down
17 changes: 11 additions & 6 deletions node/src/actors/sessions_manager/actor.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
use super::SessionsManager;
use crate::config_mngr;
use actix::prelude::*;
use witnet_data_structures::chain::EpochConstants;
use witnet_data_structures::{
chain::EpochConstants, get_protocol_version_activation_epoch, get_protocol_version_period,
proto::versioning::ProtocolVersion,
};

use witnet_util::timestamp::get_timestamp;

Expand Down Expand Up @@ -39,16 +42,18 @@ impl Actor for SessionsManager {
.set_range_limit(config.connections.reject_sybil_inbounds_range_limit);

// Initialized epoch from config
let mut checkpoints_period = config.consensus_constants.checkpoints_period;
let checkpoints_period = config.consensus_constants.checkpoints_period;
let checkpoint_zero_timestamp =
config.consensus_constants.checkpoint_zero_timestamp;
if checkpoints_period == 0 {
log::warn!("Setting the checkpoint period to the minimum value of 1 second");
checkpoints_period = 1;
}
let checkpoint_zero_timestamp_v2 = checkpoint_zero_timestamp
+ get_protocol_version_activation_epoch(ProtocolVersion::V2_0) as i64
* checkpoints_period as i64;
let checkpoints_period_v2 = get_protocol_version_period(ProtocolVersion::V2_0);
let epoch_constants = EpochConstants {
checkpoint_zero_timestamp,
checkpoints_period,
checkpoint_zero_timestamp_v2,
checkpoints_period_v2,
};
act.current_epoch = epoch_constants
.epoch_at(get_timestamp())
Expand Down
Loading

0 comments on commit 9ec8d09

Please sign in to comment.