Skip to content

Commit 36d7d9b

Browse files
fix(upgradability): use last voted protocol version (#3297)
Address one of the issues mentioned in #3288: use the last voted protocol version instead of current epoch protocol version when there is no change. Test plan --------- `test_protocol_version_switch_after_switch` `test_epoch_protocol_version_change`
1 parent 79b04fa commit 36d7d9b

5 files changed

Lines changed: 167 additions & 13 deletions

File tree

chain/client/tests/process_blocks.rs

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2061,3 +2061,82 @@ fn test_catchup_gas_price_change() {
20612061
// The chunk extra of the prev block of sync block should be the same as the node that it is syncing from
20622062
assert_eq!(chunk_extra_after_sync, expected_chunk_extra);
20632063
}
2064+
2065+
#[test]
2066+
fn test_epoch_protocol_version_change() {
2067+
init_test_logger();
2068+
let epoch_length = 5;
2069+
let mut genesis = Genesis::test(vec!["test0", "test1"], 2);
2070+
genesis.config.epoch_length = epoch_length;
2071+
genesis.config.protocol_version = PROTOCOL_VERSION;
2072+
let genesis_height = genesis.config.genesis_height;
2073+
let runtimes: Vec<Arc<dyn RuntimeAdapter>> = vec![
2074+
Arc::new(neard::NightshadeRuntime::new(
2075+
Path::new("."),
2076+
create_test_store(),
2077+
Arc::new(genesis.clone()),
2078+
vec![],
2079+
vec![],
2080+
)),
2081+
Arc::new(neard::NightshadeRuntime::new(
2082+
Path::new("."),
2083+
create_test_store(),
2084+
Arc::new(genesis.clone()),
2085+
vec![],
2086+
vec![],
2087+
)),
2088+
];
2089+
let chain_genesis = ChainGenesis::from(Arc::new(genesis));
2090+
let mut env = TestEnv::new_with_runtime(chain_genesis, 2, 2, runtimes);
2091+
for i in 1..=16 {
2092+
let head = env.clients[0].chain.head().unwrap();
2093+
let epoch_id = env.clients[0]
2094+
.runtime_adapter
2095+
.get_epoch_id_from_prev_block(&head.last_block_hash)
2096+
.unwrap();
2097+
let block_producer =
2098+
env.clients[0].runtime_adapter.get_block_producer(&epoch_id, i).unwrap();
2099+
let index = if block_producer == "test0".to_string() { 0 } else { 1 };
2100+
let (encoded_chunk, merkle_paths, receipts) =
2101+
create_chunk_on_height(&mut env.clients[index], i);
2102+
2103+
for j in 0..2 {
2104+
let mut chain_store =
2105+
ChainStore::new(env.clients[j].chain.store().owned_store(), genesis_height);
2106+
env.clients[j]
2107+
.shards_mgr
2108+
.distribute_encoded_chunk(
2109+
encoded_chunk.clone(),
2110+
merkle_paths.clone(),
2111+
receipts.clone(),
2112+
&mut chain_store,
2113+
)
2114+
.unwrap();
2115+
}
2116+
2117+
let mut block = env.clients[index].produce_block(i).unwrap().unwrap();
2118+
// upgrade to new protocol version but in the second epoch one node vote for the old version.
2119+
if i != 10 {
2120+
let validator_signer = InMemoryValidatorSigner::from_seed(
2121+
&format!("test{}", index),
2122+
KeyType::ED25519,
2123+
&format!("test{}", index),
2124+
);
2125+
2126+
block.get_mut().header.get_mut().inner_rest.latest_protocol_version =
2127+
PROTOCOL_VERSION + 1;
2128+
block.mut_header().resign(&validator_signer);
2129+
}
2130+
for j in 0..2 {
2131+
let (_, res) = env.clients[j].process_block(block.clone(), Provenance::NONE);
2132+
assert!(res.is_ok());
2133+
env.clients[j].run_catchup(&vec![]).unwrap();
2134+
}
2135+
}
2136+
let last_block = env.clients[0].chain.get_block_by_height(16).unwrap().clone();
2137+
let protocol_version = env.clients[0]
2138+
.runtime_adapter
2139+
.get_epoch_protocol_version(last_block.header().epoch_id())
2140+
.unwrap();
2141+
assert_eq!(protocol_version, PROTOCOL_VERSION + 1);
2142+
}

chain/epoch_manager/src/lib.rs

Lines changed: 74 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ use near_primitives::types::{
1515
AccountId, ApprovalStake, Balance, BlockChunkValidatorStats, BlockHeight, EpochId, ShardId,
1616
ValidatorId, ValidatorKickoutReason, ValidatorStake, ValidatorStats,
1717
};
18-
use near_primitives::version::ProtocolVersion;
18+
use near_primitives::version::{ProtocolVersion, UPGRADABILITY_FIX_PROTOCOL_VERSION};
1919
use near_primitives::views::{
2020
CurrentEpochValidatorInfo, EpochValidatorInfo, NextEpochValidatorInfo, ValidatorKickoutView,
2121
};
@@ -236,6 +236,13 @@ impl EpochManager {
236236
.map(|&id| epoch_info.validators[*id as usize].stake)
237237
.sum();
238238

239+
let protocol_version = if epoch_info.protocol_version >= UPGRADABILITY_FIX_PROTOCOL_VERSION
240+
{
241+
next_epoch_info.protocol_version
242+
} else {
243+
epoch_info.protocol_version
244+
};
245+
239246
let next_version = if let Some((&version, stake)) =
240247
versions.into_iter().max_by(|left, right| left.1.cmp(&right.1))
241248
{
@@ -246,10 +253,10 @@ impl EpochManager {
246253
{
247254
version
248255
} else {
249-
epoch_info.protocol_version
256+
protocol_version
250257
}
251258
} else {
252-
epoch_info.protocol_version
259+
protocol_version
253260
};
254261

255262
// Gather slashed validators and add them to kick out first.
@@ -2981,4 +2988,68 @@ mod tests {
29812988
PROTOCOL_VERSION
29822989
);
29832990
}
2991+
2992+
#[test]
2993+
fn test_protocol_version_switch_after_switch() {
2994+
let store = create_test_store();
2995+
let config = epoch_config(2, 1, 2, 0, 90, 60, 0);
2996+
let amount_staked = 1_000_000;
2997+
let validators = vec![stake("test1", amount_staked), stake("test2", amount_staked)];
2998+
let mut epoch_manager = EpochManager::new(
2999+
store.clone(),
3000+
config.clone(),
3001+
UPGRADABILITY_FIX_PROTOCOL_VERSION,
3002+
default_reward_calculator(),
3003+
validators.clone(),
3004+
)
3005+
.unwrap();
3006+
let h = hash_range(10);
3007+
record_block(&mut epoch_manager, CryptoHash::default(), h[0], 0, vec![]);
3008+
for i in 1..5 {
3009+
let mut block_info = block_info(
3010+
i as u64,
3011+
i as u64 - 1,
3012+
h[i - 1],
3013+
h[i - 1],
3014+
h[0],
3015+
vec![],
3016+
DEFAULT_TOTAL_SUPPLY,
3017+
);
3018+
if i != 4 {
3019+
block_info.latest_protocol_version = UPGRADABILITY_FIX_PROTOCOL_VERSION + 1;
3020+
} else {
3021+
block_info.latest_protocol_version = UPGRADABILITY_FIX_PROTOCOL_VERSION;
3022+
}
3023+
epoch_manager.record_block_info(&h[i], block_info, [0; 32]).unwrap();
3024+
}
3025+
3026+
assert_eq!(
3027+
epoch_manager.get_epoch_info(&EpochId(h[2])).unwrap().protocol_version,
3028+
UPGRADABILITY_FIX_PROTOCOL_VERSION + 1
3029+
);
3030+
3031+
assert_eq!(
3032+
epoch_manager.get_epoch_info(&EpochId(h[4])).unwrap().protocol_version,
3033+
UPGRADABILITY_FIX_PROTOCOL_VERSION + 1
3034+
);
3035+
3036+
// if there are enough votes to use the old version, it should be allowed
3037+
for i in 5..7 {
3038+
let mut block_info = block_info(
3039+
i as u64,
3040+
i as u64 - 1,
3041+
h[i - 1],
3042+
h[i - 1],
3043+
h[0],
3044+
vec![],
3045+
DEFAULT_TOTAL_SUPPLY,
3046+
);
3047+
block_info.latest_protocol_version = UPGRADABILITY_FIX_PROTOCOL_VERSION;
3048+
epoch_manager.record_block_info(&h[i], block_info, [0; 32]).unwrap();
3049+
}
3050+
assert_eq!(
3051+
epoch_manager.get_epoch_info(&EpochId(h[6])).unwrap().protocol_version,
3052+
UPGRADABILITY_FIX_PROTOCOL_VERSION
3053+
);
3054+
}
29843055
}

chain/epoch_manager/src/proposals.rs

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ pub(crate) fn find_threshold(
4141
pub fn proposals_to_epoch_info(
4242
epoch_config: &EpochConfig,
4343
rng_seed: RngSeed,
44-
epoch_info: &EpochInfo,
44+
prev_epoch_info: &EpochInfo,
4545
proposals: Vec<ValidatorStake>,
4646
mut validator_kickout: HashMap<AccountId, ValidatorKickoutReason>,
4747
validator_reward: HashMap<AccountId, Balance>,
@@ -67,7 +67,7 @@ pub fn proposals_to_epoch_info(
6767
ordered_proposals.insert(p.account_id.clone(), p);
6868
}
6969
}
70-
for r in epoch_info.validators.iter() {
70+
for r in prev_epoch_info.validators.iter() {
7171
if validator_kickout.contains_key(&r.account_id) {
7272
stake_change.insert(r.account_id.clone(), 0);
7373
continue;
@@ -77,7 +77,7 @@ pub fn proposals_to_epoch_info(
7777
stake_change.insert(p.account_id.clone(), p.stake);
7878
}
7979

80-
for r in epoch_info.fishermen.iter() {
80+
for r in prev_epoch_info.fishermen.iter() {
8181
if validator_kickout.contains_key(&r.account_id) {
8282
stake_change.insert(r.account_id.clone(), 0);
8383
continue;
@@ -107,8 +107,8 @@ pub fn proposals_to_epoch_info(
107107
fishermen.push(p);
108108
} else {
109109
*stake_change.get_mut(&account_id).unwrap() = 0;
110-
if epoch_info.validator_to_index.contains_key(&account_id)
111-
|| epoch_info.fishermen_to_index.contains_key(&account_id)
110+
if prev_epoch_info.validator_to_index.contains_key(&account_id)
111+
|| prev_epoch_info.fishermen_to_index.contains_key(&account_id)
112112
{
113113
validator_kickout.insert(
114114
account_id,
@@ -156,8 +156,8 @@ pub fn proposals_to_epoch_info(
156156
fishermen.push(p);
157157
} else {
158158
stake_change.insert(p.account_id.clone(), 0);
159-
if epoch_info.validator_to_index.contains_key(&p.account_id)
160-
|| epoch_info.fishermen_to_index.contains_key(&p.account_id)
159+
if prev_epoch_info.validator_to_index.contains_key(&p.account_id)
160+
|| prev_epoch_info.fishermen_to_index.contains_key(&p.account_id)
161161
{
162162
validator_kickout.insert(p.account_id, ValidatorKickoutReason::DidNotGetASeat);
163163
}
@@ -195,7 +195,7 @@ pub fn proposals_to_epoch_info(
195195
.collect::<HashMap<_, _>>();
196196

197197
Ok(EpochInfo {
198-
epoch_height: epoch_info.epoch_height + 1,
198+
epoch_height: prev_epoch_info.epoch_height + 1,
199199
validators: final_proposals,
200200
fishermen,
201201
validator_to_index,

core/primitives/src/version.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ pub const DB_VERSION: DbVersion = 9;
1818
pub type ProtocolVersion = u32;
1919

2020
/// Current latest version of the protocol.
21-
pub const PROTOCOL_VERSION: ProtocolVersion = 36;
21+
pub const PROTOCOL_VERSION: ProtocolVersion = 37;
2222
/// TODO: Remove in next release. This is to allow nodes with initial version 34
2323
/// to be compatible with nodes at version 35
2424
pub const NETWORK_PROTOCOL_VERSION: ProtocolVersion = 34;
@@ -40,3 +40,7 @@ pub const IMPLICIT_ACCOUNT_CREATION_PROTOCOL_VERSION: ProtocolVersion = 35;
4040

4141
/// The protocol version that enables reward on mainnet.
4242
pub const ENABLE_INFLATION_PROTOCOL_VERSION: ProtocolVersion = 36;
43+
44+
/// Fix upgrade to use the latest voted protocol version instead of the current epoch protocol
45+
/// version when there is no new change in protocol version.
46+
pub const UPGRADABILITY_FIX_PROTOCOL_VERSION: ProtocolVersion = 37;

neard/res/genesis_config.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"protocol_version": 36,
2+
"protocol_version": 37,
33
"genesis_time": "1970-01-01T00:00:00.000000000Z",
44
"chain_id": "sample",
55
"genesis_height": 0,

0 commit comments

Comments
 (0)