Skip to content

Commit

Permalink
Stablize FixStakingThreshold (#12509)
Browse files Browse the repository at this point in the history
  • Loading branch information
eagr authored Nov 29, 2024
1 parent 551865a commit 2986951
Show file tree
Hide file tree
Showing 11 changed files with 145 additions and 39 deletions.
4 changes: 2 additions & 2 deletions chain/chain/src/tests/simple_chain.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ fn build_chain() {
if cfg!(feature = "nightly") {
insta::assert_snapshot!(hash, @"Ch6xEoeJ1MNWLR2em48f6mKFko7niVoMyUazeUzFY8b4");
} else {
insta::assert_snapshot!(hash, @"GHZFAFiMdGzAfnWTcS9u9wqFvxMrgFpyEr6Use7jk2Lo");
insta::assert_snapshot!(hash, @"5LkmueLrB2cc3vURr6VKvT9acRTuNzWGTvzLGFJkRD9c");
}

for i in 1..5 {
Expand All @@ -53,7 +53,7 @@ fn build_chain() {
if cfg!(feature = "nightly") {
insta::assert_snapshot!(hash, @"A1uyFaVoJ2spKmGfjDRgnFXRx4aP21yQxArA7muN23bb");
} else {
insta::assert_snapshot!(hash, @"3Pdm44L71Bk8EokPHF1pxakHojsriNadBdZZSpcoDv9q");
insta::assert_snapshot!(hash, @"5txsrLCmQp9kn3jYRp1VHrCDt7oBTnhyi71rEPZmm8Ce");
}
}

Expand Down
4 changes: 0 additions & 4 deletions chain/epoch-manager/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,12 @@ near-schema-checker-lib.workspace = true
[features]
default = ["near-primitives/rand"]
expensive_tests = []
protocol_feature_fix_staking_threshold = [
"near-primitives/protocol_feature_fix_staking_threshold",
]
nightly = [
"near-chain-configs/nightly",
"near-o11y/nightly",
"near-primitives/nightly",
"near-store/nightly",
"nightly_protocol",
"protocol_feature_fix_staking_threshold",
]
nightly_protocol = [
"near-chain-configs/nightly_protocol",
Expand Down
21 changes: 6 additions & 15 deletions chain/epoch-manager/src/validator_selection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -408,29 +408,24 @@ fn select_validators(
break;
}
}
if validators.len() == max_number_selected {
let threshold = if validators.len() == max_number_selected {
// all slots were filled, so the threshold stake is 1 more than the current
// smallest stake
let threshold = validators.last().unwrap().stake() + 1;
(validators, proposals, threshold)
validators.last().unwrap().stake() + 1
} else {
// the stake ratio condition prevented all slots from being filled,
// or there were fewer proposals than available slots,
// so the threshold stake is whatever amount pass the stake ratio condition
let threshold = if checked_feature!(
"protocol_feature_fix_staking_threshold",
FixStakingThreshold,
protocol_version
) {
if checked_feature!("stable", FixStakingThreshold, protocol_version) {
(min_stake_ratio * Ratio::from_integer(total_stake)
/ (Ratio::from_integer(1u128) - min_stake_ratio))
.ceil()
.to_integer()
} else {
(min_stake_ratio * Ratio::new(total_stake, 1)).ceil().to_integer()
};
(validators, proposals, threshold)
}
}
};
(validators, proposals, threshold)
}

/// We store stakes in max heap and want to order them such that the validator
Expand Down Expand Up @@ -1139,10 +1134,7 @@ mod tests {
// too low stakes are kicked out
let kickout = epoch_info.validator_kickout();
assert_eq!(kickout.len(), 2);
#[cfg(feature = "protocol_feature_fix_staking_threshold")]
let expected_threshold = 334;
#[cfg(not(feature = "protocol_feature_fix_staking_threshold"))]
let expected_threshold = 300;
assert_eq!(
kickout.get(AccountIdRef::new_or_panic("test5")).unwrap(),
&ValidatorKickoutReason::NotEnoughStake { stake: 100, threshold: expected_threshold },
Expand Down Expand Up @@ -1175,7 +1167,6 @@ mod tests {
false,
)
.unwrap();
#[cfg(feature = "protocol_feature_fix_staking_threshold")]
assert_eq!(num_validators + 1, epoch_info.validators_iter().len());

let proposals = create_proposals(&[
Expand Down
2 changes: 1 addition & 1 deletion chain/jsonrpc/jsonrpc-tests/res/genesis_config.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"protocol_version": 73,
"protocol_version": 74,
"genesis_time": "1970-01-01T00:00:00.000000000Z",
"chain_id": "sample",
"genesis_height": 0,
Expand Down
2 changes: 0 additions & 2 deletions core/primitives-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ expect-test.workspace = true

[features]
default = []
protocol_feature_fix_staking_threshold = []
protocol_feature_fix_contract_loading_cost = []
protocol_feature_reject_blocks_with_outdated_protocol_version = []
protocol_feature_nonrefundable_transfer_nep491 = []
Expand All @@ -43,7 +42,6 @@ protocol_feature_relaxed_chunk_validation = []
nightly = [
"nightly_protocol",
"protocol_feature_fix_contract_loading_cost",
"protocol_feature_fix_staking_threshold",
"protocol_feature_nonrefundable_transfer_nep491",
"protocol_feature_reject_blocks_with_outdated_protocol_version",
"protocol_feature_relaxed_chunk_validation",
Expand Down
6 changes: 2 additions & 4 deletions core/primitives-core/src/version.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,6 @@ pub enum ProtocolFeature {
/// In case not all validator seats are occupied our algorithm provide incorrect minimal seat
/// price - it reports as alpha * sum_stake instead of alpha * sum_stake / (1 - alpha), where
/// alpha is min stake ratio
#[cfg(feature = "protocol_feature_fix_staking_threshold")]
FixStakingThreshold,
/// Charge for contract loading before it happens.
#[cfg(feature = "protocol_feature_fix_contract_loading_cost")]
Expand Down Expand Up @@ -251,15 +250,14 @@ impl ProtocolFeature {
| ProtocolFeature::ChunkEndorsementsInBlockHeader
| ProtocolFeature::StateStoredReceipt => 72,
ProtocolFeature::ExcludeContractCodeFromStateWitness => 73,
ProtocolFeature::FixStakingThreshold => 74,

// This protocol version is reserved for use in resharding tests. An extra resharding
// is simulated on top of the latest shard layout in production. Note that later
// protocol versions will still have the production layout.
ProtocolFeature::SimpleNightshadeTestonly => 100,

// Nightly features:
#[cfg(feature = "protocol_feature_fix_staking_threshold")]
ProtocolFeature::FixStakingThreshold => 126,
#[cfg(feature = "protocol_feature_fix_contract_loading_cost")]
ProtocolFeature::FixContractLoadingCost => 129,
#[cfg(feature = "protocol_feature_reject_blocks_with_outdated_protocol_version")]
Expand All @@ -285,7 +283,7 @@ impl ProtocolFeature {
}

/// Current protocol version used on the mainnet with all stable features.
const STABLE_PROTOCOL_VERSION: ProtocolVersion = 73;
const STABLE_PROTOCOL_VERSION: ProtocolVersion = 74;

// On nightly, pick big enough version to support all features.
const NIGHTLY_PROTOCOL_VERSION: ProtocolVersion = 148;
Expand Down
4 changes: 0 additions & 4 deletions core/primitives/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,6 @@ test_features = []
solomon = ["reed-solomon-erasure", "itertools"]
rand = ["dep:rand", "rand_chacha", "near-crypto/rand", "itertools"]
clock = ["near-time/clock", "near-time/serde"]
protocol_feature_fix_staking_threshold = [
"near-primitives-core/protocol_feature_fix_staking_threshold",
]
protocol_feature_fix_contract_loading_cost = [
"near-primitives-core/protocol_feature_fix_contract_loading_cost",
]
Expand All @@ -78,7 +75,6 @@ nightly = [
"near-primitives/nightly",
"nightly_protocol",
"protocol_feature_fix_contract_loading_cost",
"protocol_feature_fix_staking_threshold",
"protocol_feature_nonrefundable_transfer_nep491",
"protocol_feature_reject_blocks_with_outdated_protocol_version",
"protocol_feature_relaxed_chunk_validation",
Expand Down
133 changes: 133 additions & 0 deletions integration-tests/src/test_loop/tests/fix_stake_threshold.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
use crate::test_loop::builder::TestLoopBuilder;
use crate::test_loop::env::TestLoopEnv;
use crate::test_loop::utils::validators::get_epoch_all_validators;
use crate::test_loop::utils::ONE_NEAR;
use itertools::Itertools;
use near_async::test_loop::data::TestLoopData;
use near_async::time::Duration;
use near_chain_configs::test_genesis::TestGenesisBuilder;
use near_o11y::testonly::init_test_logger;
use near_primitives::epoch_manager::EpochConfigStore;
use near_primitives::num_rational::Rational32;
use near_primitives::test_utils::create_test_signer;
use near_primitives::types::AccountId;
use near_primitives::types::AccountInfo;
use near_primitives_core::version::ProtocolFeature;

#[test]
fn slow_test_fix_validator_stake_threshold() {
init_test_logger();

let protocol_version = ProtocolFeature::FixStakingThreshold.protocol_version() - 1;
let test_loop_builder = TestLoopBuilder::new();
let epoch_config_store = EpochConfigStore::for_chain_id("mainnet", None).unwrap();
let epoch_length = 10;

let accounts =
(0..6).map(|i| format!("account{}", i).parse().unwrap()).collect::<Vec<AccountId>>();
let clients = accounts.iter().cloned().collect_vec();
let validators = vec![
AccountInfo {
account_id: accounts[0].clone(),
public_key: create_test_signer(accounts[0].as_str()).public_key(),
amount: 300_000 * 62_500 * ONE_NEAR,
},
AccountInfo {
account_id: accounts[1].clone(),
public_key: create_test_signer(accounts[1].as_str()).public_key(),
amount: 300_000 * 62_500 * ONE_NEAR,
},
AccountInfo {
account_id: accounts[2].clone(),
public_key: create_test_signer(accounts[2].as_str()).public_key(),
amount: 100_000 * ONE_NEAR,
},
];

let mut genesis_builder = TestGenesisBuilder::new();
genesis_builder
.protocol_version(protocol_version)
.genesis_time_from_clock(&test_loop_builder.clock())
.shard_layout(epoch_config_store.get_config(protocol_version).shard_layout.clone())
.epoch_length(epoch_length)
.validators_raw(validators, 3, 3, 3)
.max_inflation_rate(Rational32::new(0, 1));
let initial_balance = 1_000_000 * ONE_NEAR;
for account in &accounts {
genesis_builder.add_user_account_simple(account.clone(), initial_balance);
}
let (genesis, _) = genesis_builder.build();

let TestLoopEnv { mut test_loop, datas: node_data, tempdir } = test_loop_builder
.genesis(genesis)
.epoch_config_store(epoch_config_store.clone())
.clients(clients)
.build();

let sender = node_data[0].client_sender.clone();
let handle = sender.actor_handle();
let client = &test_loop.data.get(&handle).client;

let epoch_id = client
.epoch_manager
.get_epoch_id_from_prev_block(&client.chain.head().unwrap().last_block_hash)
.unwrap();
let epoch_info = client.epoch_manager.get_epoch_info(&epoch_id).unwrap();
let protocol_version = client.epoch_manager.get_epoch_protocol_version(&epoch_id).unwrap();
let validators = get_epoch_all_validators(client);
let total_stake = validators
.iter()
.map(|v| {
let account_id = &v.parse().unwrap();
epoch_info.get_validator_stake(account_id).unwrap()
})
.sum::<u128>();
let num_shards = epoch_config_store.get_config(protocol_version).shard_layout.num_shards();

assert!(protocol_version < ProtocolFeature::FixStakingThreshold.protocol_version());
assert_eq!(validators.len(), 2, "proposal with stake at threshold should not be approved");
assert_eq!(total_stake / ONE_NEAR, 37_500_000_000);
// prior to threshold fix
// threshold = min_stake_ratio * total_stake
// = (1 / 62_500) * total_stake
// TODO Chunk producer stake threshold is dependent on the number of shards, which should no
// longer be the case. Get rid of num_shards once protocol is updated to correct the ratio.
assert_eq!(epoch_info.seat_price() * num_shards as u128 / ONE_NEAR, 600_000);

test_loop.run_until(
|test_loop_data: &mut TestLoopData| {
let client = &test_loop_data.get(&handle).client;
let head = client.chain.head().unwrap();
let epoch_height = client
.epoch_manager
.get_epoch_height_from_prev_block(&head.prev_block_hash)
.unwrap();
// ensure loop is exited because condition is met instead of timeout
assert!(epoch_height < 3);

let epoch_id = client
.epoch_manager
.get_epoch_id_from_prev_block(&client.chain.head().unwrap().last_block_hash)
.unwrap();
// chain will advance to the latest protocol version
let protocol_version =
client.epoch_manager.get_epoch_protocol_version(&epoch_id).unwrap();
if protocol_version >= ProtocolFeature::FixStakingThreshold.protocol_version() {
let epoch_info = client.epoch_manager.get_epoch_info(&epoch_id).unwrap();
let num_shards =
epoch_config_store.get_config(protocol_version).shard_layout.num_shards();
// after threshold fix
// threshold = min_stake_ratio * total_stake / (1 - min_stake_ratio)
// = (1 / 62_500) * total_stake / (62_499 / 62_500)
assert_eq!(epoch_info.seat_price() * num_shards as u128 / ONE_NEAR, 600_001);
true
} else {
false
}
},
Duration::seconds(4 * epoch_length as i64),
);

TestLoopEnv { test_loop, datas: node_data, tempdir }
.shutdown_and_drain_remaining_events(Duration::seconds(20));
}
1 change: 1 addition & 0 deletions integration-tests/src/test_loop/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mod contract_distribution_simple;
mod create_delete_account;
mod epoch_sync;
mod fix_min_stake_ratio;
mod fix_stake_threshold;
mod in_memory_tries;
mod max_receipt_size;
mod multinode_stateless_validators;
Expand Down
5 changes: 0 additions & 5 deletions nearcore/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,6 @@ expensive_tests = [
]
rosetta_rpc = ["near-rosetta-rpc"]
json_rpc = ["near-jsonrpc", "near-jsonrpc-primitives"]
protocol_feature_fix_staking_threshold = [
"near-primitives/protocol_feature_fix_staking_threshold",
"near-epoch-manager/protocol_feature_fix_staking_threshold",
]
protocol_feature_fix_contract_loading_cost = [
"near-vm-runner/protocol_feature_fix_contract_loading_cost",
]
Expand Down Expand Up @@ -142,7 +138,6 @@ nightly = [
"nightly_protocol",
"node-runtime/nightly",
"protocol_feature_fix_contract_loading_cost",
"protocol_feature_fix_staking_threshold",
"protocol_feature_nonrefundable_transfer_nep491",
"testlib/nightly",
]
Expand Down
2 changes: 0 additions & 2 deletions neard/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,6 @@ test_features = ["nearcore/test_features"]
expensive_tests = ["nearcore/expensive_tests"]
rosetta_rpc = ["nearcore/rosetta_rpc"]
json_rpc = ["nearcore/json_rpc"]
protocol_feature_fix_staking_threshold = ["nearcore/protocol_feature_fix_staking_threshold"]
protocol_feature_nonrefundable_transfer_nep491 = ["near-state-viewer/protocol_feature_nonrefundable_transfer_nep491"]

nightly = [
Expand All @@ -91,7 +90,6 @@ nightly = [
"near-undo-block/nightly",
"nearcore/nightly",
"nightly_protocol",
"protocol_feature_fix_staking_threshold",
"protocol_feature_nonrefundable_transfer_nep491",
]
nightly_protocol = [
Expand Down

0 comments on commit 2986951

Please sign in to comment.