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

NEP205: Delay voting for new version until a certain date and time #6309

Merged
merged 15 commits into from
Mar 4, 2022
Merged
Show file tree
Hide file tree
Changes from 7 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
4 changes: 2 additions & 2 deletions core/primitives/src/block_header.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ use crate::types::validator_stake::{ValidatorStake, ValidatorStakeIter, Validato
use crate::types::{AccountId, Balance, BlockHeight, EpochId, MerkleHash, NumBlocks};
use crate::utils::{from_timestamp, to_timestamp};
use crate::validator_signer::ValidatorSigner;
use crate::version::{ProtocolVersion, PROTOCOL_VERSION};
use crate::version::{get_protocol_version, ProtocolVersion, PROTOCOL_VERSION};

#[cfg_attr(feature = "deepsize_feature", derive(deepsize::DeepSizeOf))]
#[derive(BorshSerialize, BorshDeserialize, Serialize, Debug, Clone, Eq, PartialEq)]
Expand Down Expand Up @@ -463,7 +463,7 @@ impl BlockHeader {
prev_height,
epoch_sync_data_hash,
approvals,
latest_protocol_version: PROTOCOL_VERSION,
latest_protocol_version: get_protocol_version(next_epoch_protocol_version),
};
let (hash, signature) = signer.sign_block_header_parts(
prev_hash,
Expand Down
1 change: 1 addition & 0 deletions core/primitives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ pub mod time;
pub mod transaction;
pub mod trie_key;
pub mod types;
mod upgrade_schedule;
pub mod utils;
pub mod validator_signer;
pub mod version;
Expand Down
150 changes: 150 additions & 0 deletions core/primitives/src/upgrade_schedule.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
use chrono::{DateTime, NaiveDateTime, ParseResult, Utc};
use near_primitives_core::types::ProtocolVersion;

/// Defines the point in time after which validators are expected to vote on the new protocol version.
pub(crate) struct ProtocolUpgradeVotingSchedule(pub &'static str);
nikurt marked this conversation as resolved.
Show resolved Hide resolved

impl ProtocolUpgradeVotingSchedule {
nikurt marked this conversation as resolved.
Show resolved Hide resolved
#[allow(dead_code)]
pub fn is_valid(self) -> bool {
self.parse().is_ok()
}

pub fn get(self) -> DateTime<Utc> {
DateTime::<Utc>::from_utc(self.parse().unwrap(), Utc)
}

fn parse(self) -> ParseResult<NaiveDateTime> {
NaiveDateTime::parse_from_str(&self.0, "%Y-%m-%d %H:%M:%S")
}
}

pub(crate) fn get_protocol_version_internal(
nikurt marked this conversation as resolved.
Show resolved Hide resolved
next_epoch_protocol_version: ProtocolVersion,
client_protocol_version: ProtocolVersion,
schedule: Option<ProtocolUpgradeVotingSchedule>,
) -> ProtocolVersion {
if next_epoch_protocol_version >= client_protocol_version {
client_protocol_version
} else if let Some(voting_start) = schedule {
if chrono::Utc::now() < voting_start.get() {
nikurt marked this conversation as resolved.
Show resolved Hide resolved
// Don't announce support for the latest protocol version yet.
next_epoch_protocol_version
} else {
// The time has passed, announce the latest supported protocol version.
client_protocol_version
}
} else {
// No voting schedule set, always announce the latest supported protocol version.
client_protocol_version
}
}

#[cfg(test)]
mod tests {
use super::*;

// The tests call `get_protocol_version_internal()` with the following parameters:
// No schedule: (X-2,X), (X,X), (X+2,X)
// Before the scheduled upgrade: (X-2,X), (X,X), (X+2,X)
// After the scheduled upgrade: (X-2,X), (X,X), (X+2,X)

#[test]
fn test_no_upgrade_schedule() {
// As no protocol upgrade voting schedule is set, always return the version supported by the client.

let client_protocol_version = 100;
assert_eq!(
client_protocol_version,
get_protocol_version_internal(
client_protocol_version - 2,
client_protocol_version,
None
)
);
assert_eq!(
client_protocol_version,
get_protocol_version_internal(client_protocol_version, client_protocol_version, None)
);
assert_eq!(
client_protocol_version,
get_protocol_version_internal(
client_protocol_version + 2,
client_protocol_version,
None
)
);
}

#[test]
fn test_before_scheduled_time() {
let schedule = "2050-01-01 00:00:00";
let client_protocol_version = 100;

// The client supports a newer version than the version of the next epoch.
// Upgrade voting will start in the far future, therefore don't announce the newest supported version.
let next_epoch_protocol_version = client_protocol_version - 2;
assert_eq!(
next_epoch_protocol_version,
get_protocol_version_internal(
next_epoch_protocol_version,
client_protocol_version,
Some(ProtocolUpgradeVotingSchedule(schedule))
)
);

// An upgrade happened before the scheduled time.
let next_epoch_protocol_version = client_protocol_version;
assert_eq!(
next_epoch_protocol_version,
get_protocol_version_internal(
next_epoch_protocol_version,
client_protocol_version,
Some(ProtocolUpgradeVotingSchedule(schedule))
)
);

// Several upgrades happened before the scheduled time. Announce only the currently supported protocol version.
let next_epoch_protocol_version = client_protocol_version + 2;
assert_eq!(
client_protocol_version,
get_protocol_version_internal(
next_epoch_protocol_version,
client_protocol_version,
Some(ProtocolUpgradeVotingSchedule(schedule))
)
);
}

#[test]
fn test_after_scheduled_time() {
let schedule = "1999-01-01 00:00:00";
let client_protocol_version = 100;

// Regardless of the protocol version of the next epoch, return the version supported by the client.
assert_eq!(
client_protocol_version,
get_protocol_version_internal(
client_protocol_version - 2,
client_protocol_version,
Some(ProtocolUpgradeVotingSchedule(schedule))
)
);
assert_eq!(
client_protocol_version,
get_protocol_version_internal(
client_protocol_version,
client_protocol_version,
Some(ProtocolUpgradeVotingSchedule(schedule))
)
);
assert_eq!(
client_protocol_version,
get_protocol_version_internal(
client_protocol_version + 2,
client_protocol_version,
Some(ProtocolUpgradeVotingSchedule(schedule))
)
);
}
}
57 changes: 51 additions & 6 deletions core/primitives/src/version.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use serde::{Deserialize, Serialize};

use crate::types::Balance;
use serde::{Deserialize, Serialize};

/// Data structure for semver version and github tag or commit.
#[cfg_attr(feature = "deepsize_feature", derive(deepsize::DeepSizeOf))]
Expand All @@ -18,6 +17,7 @@ pub type DbVersion = u32;
/// Current version of the database.
pub const DB_VERSION: DbVersion = 31;

use crate::upgrade_schedule::{get_protocol_version_internal, ProtocolUpgradeVotingSchedule};
/// Protocol version type.
pub use near_primitives_core::types::ProtocolVersion;

Expand Down Expand Up @@ -152,20 +152,46 @@ pub enum ProtocolFeature {

/// Both, outgoing and incoming tcp connections to peers, will be rejected if `peer's`
/// protocol version is lower than this.
pub const PEER_MIN_ALLOWED_PROTOCOL_VERSION: ProtocolVersion = MAIN_NET_PROTOCOL_VERSION - 2;
pub const PEER_MIN_ALLOWED_PROTOCOL_VERSION: ProtocolVersion = STABLE_PROTOCOL_VERSION - 2;

/// Current protocol version used on the main net.
/// Current protocol version used on the mainnet.
/// Some features (e. g. FixStorageUsage) require that there is at least one epoch with exactly
/// the corresponding version
const MAIN_NET_PROTOCOL_VERSION: ProtocolVersion = 52;
const STABLE_PROTOCOL_VERSION: ProtocolVersion = 52;

/// Version used by this binary.
#[cfg(not(feature = "nightly_protocol"))]
pub const PROTOCOL_VERSION: ProtocolVersion = MAIN_NET_PROTOCOL_VERSION;
pub const PROTOCOL_VERSION: ProtocolVersion = STABLE_PROTOCOL_VERSION;
/// Current latest nightly version of the protocol.
#[cfg(feature = "nightly_protocol")]
pub const PROTOCOL_VERSION: ProtocolVersion = 126;

/// The moment in time after which the voting for the latest client version should start on the
/// mainnet version of the protocol.
#[allow(dead_code)]
const STABLE_PROTOCOL_UPGRADE_VOTING_START: Option<ProtocolUpgradeVotingSchedule> = None;
nikurt marked this conversation as resolved.
Show resolved Hide resolved
#[cfg(not(feature = "nightly_protocol"))]
const PROTOCOL_UPGRADE_VOTING_START: Option<ProtocolUpgradeVotingSchedule> =
STABLE_PROTOCOL_UPGRADE_VOTING_START;

/// The moment in time after which the voting for the latest client version should start on the
/// nightly version of the protocol.
#[allow(dead_code)]
const NIGHTLY_PROTOCOL_UPGRADE_VOTING_START: Option<ProtocolUpgradeVotingSchedule> = None;
#[cfg(feature = "nightly_protocol")]
const PROTOCOL_UPGRADE_VOTING_START: Option<ProtocolUpgradeVotingSchedule> =
NIGHTLY_PROTOCOL_UPGRADE_VOTING_START;

/// Gives new clients an option to upgrade without announcing that they support the new version.
/// This gives non-validator nodes time to upgrade. See https://github.com/near/NEPs/issues/205
pub fn get_protocol_version(next_epoch_protocol_version: ProtocolVersion) -> ProtocolVersion {
get_protocol_version_internal(
next_epoch_protocol_version,
PROTOCOL_VERSION,
PROTOCOL_UPGRADE_VOTING_START,
)
}

impl ProtocolFeature {
pub const fn protocol_version(self) -> ProtocolVersion {
match self {
Expand Down Expand Up @@ -248,3 +274,22 @@ macro_rules! checked_feature {
}
}};
}

#[cfg(test)]
mod tests {
use crate::version::{
NIGHTLY_PROTOCOL_UPGRADE_VOTING_START, STABLE_PROTOCOL_UPGRADE_VOTING_START,
};

#[test]
// This unit test ensures validity of upgrade schedule, because the validity can't be ensured
// during `const` construction.
fn test_upgrade_schedule_valid() {
if let Some(schedule) = STABLE_PROTOCOL_UPGRADE_VOTING_START {
assert!(schedule.is_valid());
}
if let Some(schedule) = NIGHTLY_PROTOCOL_UPGRADE_VOTING_START {
assert!(schedule.is_valid());
}
}
}