Skip to content

Commit

Permalink
NEP205: Delay voting for new version until a certain date and time (#…
Browse files Browse the repository at this point in the history
…6309)

See near/NEPs#205
This PR enables clients to extend the protocol upgrade window, but indirectly. This PR lets clients upgrade early but not announce the fact of the upgrade until a certain date in the future.

Normal upgrades:
* Clients will delay voting until a certain date.

Emergency upgrades:
* Voting for the new version starts immediately and a protocol upgrade becomes effective in 1-2 epochs.
  • Loading branch information
nikurt committed Mar 4, 2022
1 parent 78c4b0c commit cfba9e9
Show file tree
Hide file tree
Showing 6 changed files with 264 additions and 14 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

13 changes: 7 additions & 6 deletions core/primitives/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,18 @@ This crate provides the base set of primitives used by other nearcore crates
byteorder = "1.3"
bytesize = "1.1"
chrono = { version = "0.4.4", features = ["serde"] }
deepsize = { version = "0.2.0", features = ["chrono"], optional=true }
derive_more = "0.99.3"
easy-ext = "0.2"
serde = { version = "1", features = ["derive", "rc"] }
serde_json = "1"
smart-default = "0.6"
rand = "0.7"
reed-solomon-erasure = "4"
hex = "0.4"
num-rational = { version = "0.3", features = ["serde"] }
once_cell = "1.5.2"
primitive-types = "0.10"
deepsize = { version = "0.2.0", features = ["chrono"], optional=true }
rand = "0.7"
reed-solomon-erasure = "4"
serde = { version = "1", features = ["derive", "rc"] }
serde_json = "1"
smart-default = "0.6"

borsh = { version = "0.9", features = ["rc"] }

Expand Down
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
225 changes: 225 additions & 0 deletions core/primitives/src/upgrade_schedule.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
use chrono::{DateTime, NaiveDateTime, ParseError, Utc};
use near_primitives_core::types::ProtocolVersion;
use std::collections::HashMap;
use std::str::FromStr;

/// Defines the point in time after which validators are expected to vote on the new protocol version.
pub(crate) struct ProtocolUpgradeVotingSchedule {
timestamp: chrono::DateTime<Utc>,
}

impl FromStr for ProtocolUpgradeVotingSchedule {
type Err = ParseError;

fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(Self {
timestamp: DateTime::<Utc>::from_utc(
NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S")?,
Utc,
),
})
}
}

impl ProtocolUpgradeVotingSchedule {
pub fn is_in_future(&self) -> bool {
chrono::Utc::now() < self.timestamp
}
}

pub(crate) fn get_protocol_version_internal(
// Protocol version that will be used in the next epoch.
next_epoch_protocol_version: ProtocolVersion,
// Latest protocol version supported by this client.
client_protocol_version: ProtocolVersion,
// Map of protocol versions to points in time when voting for that protocol version is expected to start.
// If None or missing, the client votes for the latest protocol version immediately.
schedule: &HashMap<ProtocolVersion, Option<ProtocolUpgradeVotingSchedule>>,
) -> ProtocolVersion {
if next_epoch_protocol_version >= client_protocol_version {
return client_protocol_version;
}
match schedule.get(&client_protocol_version) {
Some(Some(voting_start)) => {
if voting_start.is_in_future() {
// 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
}
}
_ => 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,
&HashMap::new(),
)
);
assert_eq!(
client_protocol_version,
get_protocol_version_internal(
client_protocol_version,
client_protocol_version,
&HashMap::new()
)
);
assert_eq!(
client_protocol_version,
get_protocol_version_internal(
client_protocol_version + 2,
client_protocol_version,
&HashMap::new(),
)
);
}

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

let client_protocol_version = 100;
let mut schedule = HashMap::new();
schedule.insert(client_protocol_version, None);

assert_eq!(
client_protocol_version,
get_protocol_version_internal(
client_protocol_version - 2,
client_protocol_version,
&schedule,
)
);
assert_eq!(
client_protocol_version,
get_protocol_version_internal(
client_protocol_version,
client_protocol_version,
&schedule
)
);
assert_eq!(
client_protocol_version,
get_protocol_version_internal(
client_protocol_version + 2,
client_protocol_version,
&schedule
)
);
}

#[test]
fn test_before_scheduled_time() {
let client_protocol_version = 100;
let mut schedule = HashMap::new();
schedule.insert(
client_protocol_version,
Some(ProtocolUpgradeVotingSchedule::from_str("2050-01-01 00:00:00").unwrap()),
);

// 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,
&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,
&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,
&schedule,
)
);
}

#[test]
fn test_after_scheduled_time() {
let client_protocol_version = 100;
let mut schedule = HashMap::new();
schedule.insert(
client_protocol_version,
Some(ProtocolUpgradeVotingSchedule::from_str("1900-01-01 00:00:00").unwrap()),
);

// 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,
&schedule,
)
);
assert_eq!(
client_protocol_version,
get_protocol_version_internal(
client_protocol_version,
client_protocol_version,
&schedule,
)
);
assert_eq!(
client_protocol_version,
get_protocol_version_internal(
client_protocol_version + 2,
client_protocol_version,
&schedule,
)
);
}

#[test]
fn test_parse() {
assert!(ProtocolUpgradeVotingSchedule::from_str("2001-02-03 23:59:59").is_ok());
assert!(ProtocolUpgradeVotingSchedule::from_str("123").is_err());
}

#[test]
fn test_is_in_future() {
assert!(ProtocolUpgradeVotingSchedule::from_str("2999-02-03 23:59:59")
.unwrap()
.is_in_future());
assert!(!ProtocolUpgradeVotingSchedule::from_str("1999-02-03 23:59:59")
.unwrap()
.is_in_future());
}
}
34 changes: 28 additions & 6 deletions core/primitives/src/version.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use serde::{Deserialize, Serialize};

use crate::types::Balance;
use once_cell::sync::Lazy;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;

/// Data structure for semver version and github tag or commit.
#[cfg_attr(feature = "deepsize_feature", derive(deepsize::DeepSizeOf))]
Expand All @@ -18,6 +19,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 +154,40 @@ 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 points in time after which the voting for the protocol version should start.
#[allow(dead_code)]
const PROTOCOL_UPGRADE_SCHEDULE: Lazy<
HashMap<ProtocolVersion, Option<ProtocolUpgradeVotingSchedule>>,
> = Lazy::new(|| {
let mut schedule = HashMap::new();
schedule.insert(52, None);
schedule
});

/// 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_SCHEDULE,
)
}

impl ProtocolFeature {
pub const fn protocol_version(self) -> ProtocolVersion {
match self {
Expand Down

0 comments on commit cfba9e9

Please sign in to comment.