diff --git a/core/src/consensus/stake/actions.rs b/core/src/consensus/stake/actions.rs index a0f63c4dbf..912461813b 100644 --- a/core/src/consensus/stake/actions.rs +++ b/core/src/consensus/stake/actions.rs @@ -105,15 +105,7 @@ impl Action { params, signatures, } => { - let current_network_id = current_params.network_id(); - let transaction_network_id = params.network_id(); - if current_network_id != transaction_network_id { - return Err(SyntaxError::InvalidCustomAction(format!( - "The current network id is {} but the transaction tries to change the network id to {}", - current_network_id, transaction_network_id - ))) - } - params.verify().map_err(SyntaxError::InvalidCustomAction)?; + params.verify_change(current_params).map_err(SyntaxError::InvalidCustomAction)?; let action = Action::ChangeParams { metadata_seq: *metadata_seq, params: params.clone(), diff --git a/json/src/scheme/params.rs b/json/src/scheme/params.rs index 2244961556..500b2eb26a 100644 --- a/json/src/scheme/params.rs +++ b/json/src/scheme/params.rs @@ -66,6 +66,10 @@ pub struct Params { pub delegation_threshold: Option, pub min_deposit: Option, pub max_candidate_metadata_size: Option, + + /// A monotonically increasing number to denote the consensus version. + /// It is increased when we fork. + pub era: Option, } #[cfg(test)] @@ -277,4 +281,79 @@ mod tests { assert_eq!(deserialized.min_deposit, Some(32.into())); assert_eq!(deserialized.max_candidate_metadata_size, Some(33.into())); } + + #[test] + #[allow(clippy::cognitive_complexity)] + fn params_deserialization_with_era() { + let s = r#"{ + "maxExtraDataSize": "0x20", + "maxAssetSchemeMetadataSize": "0x0400", + "maxTransferMetadataSize": "0x0100", + "maxTextContentSize": "0x0200", + "networkID" : "tc", + "minPayCost" : 10, + "minSetRegularKeyCost" : 11, + "minCreateShardCost" : 12, + "minSetShardOwnersCost" : 13, + "minSetShardUsersCost" : 14, + "minWrapCccCost" : 15, + "minCustomCost" : 16, + "minStoreCost" : 17, + "minRemoveCost" : 18, + "minMintAssetCost" : 19, + "minTransferAssetCost" : 20, + "minChangeAssetSchemeCost" : 21, + "minComposeAssetCost" : 22, + "minDecomposeAssetCost" : 23, + "minUnwrapCccCost" : 24, + "minIncreaseAssetSupplyCost": 25, + "maxBodySize" : 4194304, + "snapshotPeriod": 16384, + "termSeconds": 3600, + "nominationExpiration": 26, + "custodyPeriod": 27, + "releasePeriod": 28, + "maxNumOfValidators": 29, + "minNumOfValidators": 30, + "delegationThreshold": 31, + "minDeposit": 32, + "maxCandidateMetadataSize": 33, + "era": 34 + }"#; + + let deserialized: Params = serde_json::from_str(s).unwrap(); + assert_eq!(deserialized.max_extra_data_size, 0x20.into()); + assert_eq!(deserialized.max_asset_scheme_metadata_size, 0x0400.into()); + assert_eq!(deserialized.max_transfer_metadata_size, 0x0100.into()); + assert_eq!(deserialized.max_text_content_size, 0x0200.into()); + assert_eq!(deserialized.network_id, "tc".into()); + assert_eq!(deserialized.min_pay_cost, 10.into()); + assert_eq!(deserialized.min_set_regular_key_cost, 11.into()); + assert_eq!(deserialized.min_create_shard_cost, 12.into()); + assert_eq!(deserialized.min_set_shard_owners_cost, 13.into()); + assert_eq!(deserialized.min_set_shard_users_cost, 14.into()); + assert_eq!(deserialized.min_wrap_ccc_cost, 15.into()); + assert_eq!(deserialized.min_custom_cost, 16.into()); + assert_eq!(deserialized.min_store_cost, 17.into()); + assert_eq!(deserialized.min_remove_cost, 18.into()); + assert_eq!(deserialized.min_mint_asset_cost, 19.into()); + assert_eq!(deserialized.min_transfer_asset_cost, 20.into()); + assert_eq!(deserialized.min_change_asset_scheme_cost, 21.into()); + assert_eq!(deserialized.min_compose_asset_cost, 22.into()); + assert_eq!(deserialized.min_decompose_asset_cost, 23.into()); + assert_eq!(deserialized.min_unwrap_ccc_cost, 24.into()); + assert_eq!(deserialized.min_increase_asset_supply_cost, 25.into()); + assert_eq!(deserialized.max_body_size, 4_194_304.into()); + assert_eq!(deserialized.snapshot_period, 16_384.into()); + assert_eq!(deserialized.term_seconds, Some(3600.into())); + assert_eq!(deserialized.nomination_expiration, Some(26.into())); + assert_eq!(deserialized.custody_period, Some(27.into())); + assert_eq!(deserialized.release_period, Some(28.into())); + assert_eq!(deserialized.max_num_of_validators, Some(29.into())); + assert_eq!(deserialized.min_num_of_validators, Some(30.into())); + assert_eq!(deserialized.delegation_threshold, Some(31.into())); + assert_eq!(deserialized.min_deposit, Some(32.into())); + assert_eq!(deserialized.max_candidate_metadata_size, Some(33.into())); + assert_eq!(deserialized.era, Some(34.into())); + } } diff --git a/types/src/common_params.rs b/types/src/common_params.rs index f81e690f7d..e59c1eab9a 100644 --- a/types/src/common_params.rs +++ b/types/src/common_params.rs @@ -64,6 +64,8 @@ pub struct CommonParams { delegation_threshold: u64, min_deposit: u64, max_candidate_metadata_size: usize, + + era: u64, } impl CommonParams { @@ -170,6 +172,10 @@ impl CommonParams { self.max_candidate_metadata_size } + pub fn era(&self) -> u64 { + self.era + } + pub fn verify(&self) -> Result<(), String> { if self.term_seconds != 0 { if self.nomination_expiration == 0 { @@ -214,17 +220,41 @@ impl CommonParams { } Ok(()) } + + pub fn verify_change(&self, current_params: &Self) -> Result<(), String> { + self.verify()?; + let current_network_id = current_params.network_id(); + let transaction_network_id = self.network_id(); + if current_network_id != transaction_network_id { + return Err(format!( + "The current network id is {} but the transaction tries to change the network id to {}", + current_network_id, transaction_network_id + )) + } + if self.era < current_params.era { + return Err(format!("The era({}) shouldn't be less than the current era({})", self.era, current_params.era)) + } + Ok(()) + } } const DEFAULT_PARAMS_SIZE: usize = 23; const NUMBER_OF_STAKE_PARAMS: usize = 9; +const NUMBER_OF_ERA_PARAMS: usize = 1; +const STAKE_PARAM_SIZE: usize = DEFAULT_PARAMS_SIZE + NUMBER_OF_STAKE_PARAMS; +const ERA_PARAM_SIZE: usize = STAKE_PARAM_SIZE + NUMBER_OF_ERA_PARAMS; + +const VALID_SIZE: &[usize] = &[DEFAULT_PARAMS_SIZE, STAKE_PARAM_SIZE, ERA_PARAM_SIZE]; impl From for CommonParams { fn from(p: Params) -> Self { - let mut size = DEFAULT_PARAMS_SIZE; - if p.term_seconds.is_some() { - size += NUMBER_OF_STAKE_PARAMS; - } + let size = if p.era.is_some() { + ERA_PARAM_SIZE + } else if p.term_seconds.is_some() { + STAKE_PARAM_SIZE + } else { + DEFAULT_PARAMS_SIZE + }; Self { size, max_extra_data_size: p.max_extra_data_size.into(), @@ -259,6 +289,7 @@ impl From for CommonParams { delegation_threshold: p.delegation_threshold.map(From::from).unwrap_or_default(), min_deposit: p.min_deposit.map(From::from).unwrap_or_default(), max_candidate_metadata_size: p.max_candidate_metadata_size.map(From::from).unwrap_or_default(), + era: p.era.map(From::from).unwrap_or_default(), } } } @@ -292,7 +323,7 @@ impl From for Params { snapshot_period: p.snapshot_period().into(), ..Default::default() }; - if p.size == DEFAULT_PARAMS_SIZE + NUMBER_OF_STAKE_PARAMS { + if p.size >= STAKE_PARAM_SIZE { result.term_seconds = Some(p.term_seconds().into()); result.nomination_expiration = Some(p.nomination_expiration().into()); result.custody_period = Some(p.custody_period().into()); @@ -303,13 +334,15 @@ impl From for Params { result.min_deposit = Some(p.min_deposit().into()); result.max_candidate_metadata_size = Some(p.max_candidate_metadata_size().into()); } + if p.size >= ERA_PARAM_SIZE { + result.era = Some(p.era().into()); + } result } } impl Encodable for CommonParams { fn rlp_append(&self, s: &mut RlpStream) { - const VALID_SIZE: &[usize] = &[DEFAULT_PARAMS_SIZE, DEFAULT_PARAMS_SIZE + NUMBER_OF_STAKE_PARAMS]; assert!(VALID_SIZE.contains(&self.size), "{} must be in {:?}", self.size, VALID_SIZE); s.begin_list(self.size) .append(&self.max_extra_data_size) @@ -335,7 +368,7 @@ impl Encodable for CommonParams { .append(&self.min_asset_unwrap_ccc_cost) .append(&self.max_body_size) .append(&self.snapshot_period); - if self.size == DEFAULT_PARAMS_SIZE + NUMBER_OF_STAKE_PARAMS { + if self.size >= STAKE_PARAM_SIZE { s.append(&self.term_seconds) .append(&self.nomination_expiration) .append(&self.custody_period) @@ -346,13 +379,15 @@ impl Encodable for CommonParams { .append(&self.min_deposit) .append(&self.max_candidate_metadata_size); } + if self.size >= ERA_PARAM_SIZE { + s.append(&self.era); + } } } impl Decodable for CommonParams { fn decode(rlp: &Rlp) -> Result { let size = rlp.item_count()?; - const VALID_SIZE: &[usize] = &[DEFAULT_PARAMS_SIZE, DEFAULT_PARAMS_SIZE + NUMBER_OF_STAKE_PARAMS]; if !VALID_SIZE.contains(&size) { return Err(DecoderError::RlpIncorrectListLen { expected: DEFAULT_PARAMS_SIZE, @@ -394,7 +429,7 @@ impl Decodable for CommonParams { delegation_threshold, min_deposit, max_candidate_metadata_size, - ) = if size >= DEFAULT_PARAMS_SIZE + NUMBER_OF_STAKE_PARAMS { + ) = if size >= STAKE_PARAM_SIZE { ( rlp.val_at(23)?, rlp.val_at(24)?, @@ -409,6 +444,13 @@ impl Decodable for CommonParams { } else { Default::default() }; + + let era = if size >= ERA_PARAM_SIZE { + rlp.val_at(32)? + } else { + Default::default() + }; + Ok(Self { size, max_extra_data_size, @@ -443,6 +485,7 @@ impl Decodable for CommonParams { delegation_threshold, min_deposit, max_candidate_metadata_size, + era, }) } } @@ -514,7 +557,7 @@ mod tests { #[test] fn rlp_with_extra_fields() { let mut params = CommonParams::default_for_test(); - params.size = DEFAULT_PARAMS_SIZE + NUMBER_OF_STAKE_PARAMS; + params.size = ERA_PARAM_SIZE; params.term_seconds = 100; params.min_deposit = 123; rlp_encode_and_decode_test!(params); @@ -524,7 +567,7 @@ mod tests { fn rlp_encoding_are_different_if_the_size_are_different() { let origin = CommonParams::default_for_test(); let mut params = origin; - params.size = DEFAULT_PARAMS_SIZE + NUMBER_OF_STAKE_PARAMS; + params.size = ERA_PARAM_SIZE; assert_ne!(rlp::encode(&origin), rlp::encode(¶ms)); } @@ -591,6 +634,7 @@ mod tests { assert_eq!(deserialized.delegation_threshold, 0); assert_eq!(deserialized.min_deposit, 0); assert_eq!(deserialized.max_candidate_metadata_size, 0); + assert_eq!(deserialized.era, 0); assert_eq!(params, deserialized.into()); } @@ -627,6 +671,7 @@ mod tests { let params = serde_json::from_str::(s).unwrap(); let deserialized = CommonParams::from(params.clone()); + assert_eq!(deserialized.size, STAKE_PARAM_SIZE); assert_eq!(deserialized.max_extra_data_size, 0x20); assert_eq!(deserialized.max_asset_scheme_metadata_size, 0x0400); assert_eq!(deserialized.max_transfer_metadata_size, 0x0100); @@ -659,6 +704,7 @@ mod tests { assert_eq!(deserialized.delegation_threshold, 0); assert_eq!(deserialized.min_deposit, 0); assert_eq!(deserialized.max_candidate_metadata_size, 0); + assert_eq!(deserialized.era, 0); assert_eq!( Params { @@ -670,6 +716,7 @@ mod tests { delegation_threshold: Some(0.into()), min_deposit: Some(0.into()), max_candidate_metadata_size: Some(0.into()), + era: None, ..params }, deserialized.into(), @@ -716,6 +763,85 @@ mod tests { }"#; let params = serde_json::from_str::(s).unwrap(); let deserialized = CommonParams::from(params.clone()); + assert_eq!(deserialized.size, STAKE_PARAM_SIZE); + assert_eq!(deserialized.max_extra_data_size, 0x20); + assert_eq!(deserialized.max_asset_scheme_metadata_size, 0x0400); + assert_eq!(deserialized.max_transfer_metadata_size, 0x0100); + assert_eq!(deserialized.max_text_content_size, 0x0200); + assert_eq!(deserialized.network_id, "tc".into()); + assert_eq!(deserialized.min_pay_transaction_cost, 10); + assert_eq!(deserialized.min_set_regular_key_transaction_cost, 11); + assert_eq!(deserialized.min_create_shard_transaction_cost, 12); + assert_eq!(deserialized.min_set_shard_owners_transaction_cost, 13); + assert_eq!(deserialized.min_set_shard_users_transaction_cost, 14); + assert_eq!(deserialized.min_wrap_ccc_transaction_cost, 15); + assert_eq!(deserialized.min_custom_transaction_cost, 16); + assert_eq!(deserialized.min_store_transaction_cost, 17); + assert_eq!(deserialized.min_remove_transaction_cost, 18); + assert_eq!(deserialized.min_asset_mint_cost, 19); + assert_eq!(deserialized.min_asset_transfer_cost, 20); + assert_eq!(deserialized.min_asset_scheme_change_cost, 21); + assert_eq!(deserialized.min_asset_compose_cost, 22); + assert_eq!(deserialized.min_asset_decompose_cost, 23); + assert_eq!(deserialized.min_asset_unwrap_ccc_cost, 24); + assert_eq!(deserialized.min_asset_supply_increase_cost, 25); + assert_eq!(deserialized.max_body_size, 4_194_304); + assert_eq!(deserialized.snapshot_period, 16_384); + assert_eq!(deserialized.term_seconds, 3600); + assert_eq!(deserialized.nomination_expiration, 26); + assert_eq!(deserialized.custody_period, 27); + assert_eq!(deserialized.release_period, 28); + assert_eq!(deserialized.max_num_of_validators, 29); + assert_eq!(deserialized.min_num_of_validators, 30); + assert_eq!(deserialized.delegation_threshold, 31); + assert_eq!(deserialized.min_deposit, 32); + assert_eq!(deserialized.max_candidate_metadata_size, 33); + assert_eq!(deserialized.era, 0); + + assert_eq!(params, deserialized.into()); + } + + #[test] + #[allow(clippy::cognitive_complexity)] + fn params_from_json_with_era() { + let s = r#"{ + "maxExtraDataSize": "0x20", + "maxAssetSchemeMetadataSize": "0x0400", + "maxTransferMetadataSize": "0x0100", + "maxTextContentSize": "0x0200", + "networkID" : "tc", + "minPayCost" : 10, + "minSetRegularKeyCost" : 11, + "minCreateShardCost" : 12, + "minSetShardOwnersCost" : 13, + "minSetShardUsersCost" : 14, + "minWrapCccCost" : 15, + "minCustomCost" : 16, + "minStoreCost" : 17, + "minRemoveCost" : 18, + "minMintAssetCost" : 19, + "minTransferAssetCost" : 20, + "minChangeAssetSchemeCost" : 21, + "minComposeAssetCost" : 22, + "minDecomposeAssetCost" : 23, + "minUnwrapCccCost" : 24, + "minIncreaseAssetSupplyCost": 25, + "maxBodySize" : 4194304, + "snapshotPeriod": 16384, + "termSeconds": 3600, + "nominationExpiration": 26, + "custodyPeriod": 27, + "releasePeriod": 28, + "maxNumOfValidators": 29, + "minNumOfValidators": 30, + "delegationThreshold": 31, + "minDeposit": 32, + "maxCandidateMetadataSize": 33, + "era": 34 + }"#; + let params = serde_json::from_str::(s).unwrap(); + let deserialized = CommonParams::from(params.clone()); + assert_eq!(deserialized.size, ERA_PARAM_SIZE); assert_eq!(deserialized.max_extra_data_size, 0x20); assert_eq!(deserialized.max_asset_scheme_metadata_size, 0x0400); assert_eq!(deserialized.max_transfer_metadata_size, 0x0100); @@ -748,6 +874,7 @@ mod tests { assert_eq!(deserialized.delegation_threshold, 31); assert_eq!(deserialized.min_deposit, 32); assert_eq!(deserialized.max_candidate_metadata_size, 33); + assert_eq!(deserialized.era, 34); assert_eq!(params, deserialized.into()); }