Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature(data_structures): switch block time when v2.0 activates #2473

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
101 changes: 83 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 @@

/// 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,48 +4493,107 @@
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

Check failure on line 4558 in data_structures/src/chain/mod.rs

View workflow job for this annotation

GitHub Actions / build_ubuntu

casting `i64` to `u32` may truncate the value

Check failure on line 4558 in data_structures/src/chain/mod.rs

View workflow job for this annotation

GitHub Actions / build_ubuntu

casting `i64` to `u32` may lose the sign of the value

Check failure on line 4558 in data_structures/src/chain/mod.rs

View workflow job for this annotation

GitHub Actions / build_ubuntu

casting `i64` to `u32` may truncate the value

Check failure on line 4558 in data_structures/src/chain/mod.rs

View workflow job for this annotation

GitHub Actions / build_ubuntu

casting `i64` to `u32` may lose the sign of the value
- self.checkpoint_zero_timestamp)
/ self.checkpoints_period as i64) as u32;

Check failure on line 4560 in data_structures/src/chain/mod.rs

View workflow job for this annotation

GitHub Actions / build_ubuntu

casting `u16` to `i64` may become silently lossy if you later change the type

Check failure on line 4560 in data_structures/src/chain/mod.rs

View workflow job for this annotation

GitHub Actions / build_ubuntu

casting `u16` to `i64` may become silently lossy if you later change the type

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)
}

pub fn get_epoch_period(&self, epoch: Epoch) -> Result<u16, EpochCalculationError> {
let (_, in_v2) = self.epoch_timestamp(epoch)?;
if in_v2 {
Ok(self.checkpoints_period_v2)
} else {
Ok(self.checkpoints_period)
}
}
}

#[derive(Debug, PartialEq, Eq)]
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: 5 additions & 1 deletion node/src/actors/chain_manager/mining.rs
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,11 @@ impl ChainManager {
let (collateral_age, checkpoint_period) = match &self.chain_state.chain_info {
Some(x) => (
x.consensus_constants.collateral_age,
x.consensus_constants.checkpoints_period,
// Unwraps should be safe if we have a chain_info object
self.epoch_constants
.unwrap()
.get_epoch_period(current_epoch)
.unwrap(),
),
None => {
log::error!("ChainInfo is None");
Expand Down
15 changes: 13 additions & 2 deletions node/src/actors/chain_manager/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1277,13 +1277,18 @@ impl ChainManager {
})
.into_actor(act)
})
.map_ok(|res, act, ctx| {
.map_ok(move |res, act, ctx| {
// Broadcast vote between one and ("superblock_period" - 5) epoch checkpoints later.
// This is used to prevent the race condition described in issue #1573
// It is also used to spread the CPU load by checking superblock votes along
// the superblock period with a safe margin
let mut rng = rand::thread_rng();
let checkpoints_period = act.consensus_constants().checkpoints_period;
// Should be safe here to just call unwraps
let checkpoints_period = act
.epoch_constants
.unwrap()
.get_epoch_period(current_epoch)
.unwrap();
let superblock_period = act.consensus_constants().superblock_period;
let end_range = if superblock_period > 5 {
(superblock_period - 5) * checkpoints_period
Expand Down Expand Up @@ -4072,6 +4077,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 +4120,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 +4208,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
Loading
Loading