spec: Validate required divisor fields are not 0 (#7933)

* Add validate_non_zero function

It's used to validate that a Spec's uint field used as a divisor is not zero.

* Add deserialize_with to gas_limit_bound_divisor

Prevents panics due to divide-by-zero on the gas_limit_bound_divisor
field.

* Add deserialize_with to difficulty_bound_divisor

Prevents panics due to divide-by-zero on the difficulty_bound_divisor
field.

* Add validate_optional_non_zero function

Used to validate Option<Uint> divisor fields.

* Use deserialize_with on optional divisor fields.

* Add #[serde(default)] attribute to divisor fields

When using `#[serde(deserialize_with)]`, `#[serde(default)]` must be specified so that missing
fields can be deserialized with the deserializer for `None`.
This commit is contained in:
Lorenzo Manacorda 2018-02-19 10:36:34 +01:00 committed by Rando
parent 76a098114f
commit e630f647d1
3 changed files with 60 additions and 3 deletions

View File

@ -16,7 +16,7 @@
//! Ethash params deserialization. //! Ethash params deserialization.
use uint::Uint; use uint::{self, Uint};
use hash::Address; use hash::Address;
/// Deserializable doppelganger of EthashParams. /// Deserializable doppelganger of EthashParams.
@ -27,12 +27,15 @@ pub struct EthashParams {
pub minimum_difficulty: Uint, pub minimum_difficulty: Uint,
/// See main EthashParams docs. /// See main EthashParams docs.
#[serde(rename="difficultyBoundDivisor")] #[serde(rename="difficultyBoundDivisor")]
#[serde(deserialize_with="uint::validate_non_zero")]
pub difficulty_bound_divisor: Uint, pub difficulty_bound_divisor: Uint,
/// See main EthashParams docs. /// See main EthashParams docs.
#[serde(rename="difficultyIncrementDivisor")] #[serde(rename="difficultyIncrementDivisor")]
#[serde(default, deserialize_with="uint::validate_optional_non_zero")]
pub difficulty_increment_divisor: Option<Uint>, pub difficulty_increment_divisor: Option<Uint>,
/// See main EthashParams docs. /// See main EthashParams docs.
#[serde(rename="metropolisDifficultyIncrementDivisor")] #[serde(rename="metropolisDifficultyIncrementDivisor")]
#[serde(default, deserialize_with="uint::validate_optional_non_zero")]
pub metropolis_difficulty_increment_divisor: Option<Uint>, pub metropolis_difficulty_increment_divisor: Option<Uint>,
/// See main EthashParams docs. /// See main EthashParams docs.
#[serde(rename="durationLimit")] #[serde(rename="durationLimit")]
@ -60,6 +63,7 @@ pub struct EthashParams {
pub difficulty_hardfork_transition: Option<Uint>, pub difficulty_hardfork_transition: Option<Uint>,
/// See main EthashParams docs. /// See main EthashParams docs.
#[serde(rename="difficultyHardforkBoundDivisor")] #[serde(rename="difficultyHardforkBoundDivisor")]
#[serde(default, deserialize_with="uint::validate_optional_non_zero")]
pub difficulty_hardfork_bound_divisor: Option<Uint>, pub difficulty_hardfork_bound_divisor: Option<Uint>,
/// See main EthashParams docs. /// See main EthashParams docs.
#[serde(rename="bombDefuseTransition")] #[serde(rename="bombDefuseTransition")]
@ -302,4 +306,17 @@ mod tests {
} }
}); });
} }
#[test]
#[should_panic(expected = "a non-zero value")]
fn test_zero_value_divisor() {
let s = r#"{
"params": {
"difficultyBoundDivisor": "0x0",
"minimumDifficulty": "0x020000"
}
}"#;
let _deserialized: Ethash = serde_json::from_str(s).unwrap();
}
} }

View File

@ -16,7 +16,7 @@
//! Spec params deserialization. //! Spec params deserialization.
use uint::Uint; use uint::{self, Uint};
use hash::{H256, Address}; use hash::{H256, Address};
use bytes::Bytes; use bytes::Bytes;
@ -102,6 +102,7 @@ pub struct Params {
pub wasm: Option<bool>, pub wasm: Option<bool>,
/// See `CommonParams` docs. /// See `CommonParams` docs.
#[serde(rename="gasLimitBoundDivisor")] #[serde(rename="gasLimitBoundDivisor")]
#[serde(deserialize_with="uint::validate_non_zero")]
pub gas_limit_bound_divisor: Uint, pub gas_limit_bound_divisor: Uint,
/// See `CommonParams` docs. /// See `CommonParams` docs.
pub registrar: Option<Address>, pub registrar: Option<Address>,
@ -149,4 +150,21 @@ mod tests {
assert_eq!(deserialized.gas_limit_bound_divisor, Uint(U256::from(0x20))); assert_eq!(deserialized.gas_limit_bound_divisor, Uint(U256::from(0x20)));
assert_eq!(deserialized.max_code_size, Some(Uint(U256::from(0x1000)))); assert_eq!(deserialized.max_code_size, Some(Uint(U256::from(0x1000))));
} }
#[test]
#[should_panic(expected = "a non-zero value")]
fn test_zero_value_divisor() {
let s = r#"{
"maximumExtraDataSize": "0x20",
"networkID" : "0x1",
"chainID" : "0x15",
"subprotocolName" : "exp",
"minGasLimit": "0x1388",
"accountStartNonce": "0x01",
"gasLimitBoundDivisor": "0x0",
"maxCodeSize": "0x1000"
}"#;
let _deserialized: Params = serde_json::from_str(s).unwrap();
}
} }

View File

@ -19,7 +19,7 @@
use std::fmt; use std::fmt;
use std::str::FromStr; use std::str::FromStr;
use serde::{Deserialize, Deserializer}; use serde::{Deserialize, Deserializer};
use serde::de::{Error, Visitor}; use serde::de::{Error, Visitor, Unexpected};
use ethereum_types::U256; use ethereum_types::U256;
/// Lenient uint json deserialization for test json files. /// Lenient uint json deserialization for test json files.
@ -90,6 +90,28 @@ impl<'a> Visitor<'a> for UintVisitor {
} }
} }
pub fn validate_non_zero<'de, D>(d: D) -> Result<Uint, D::Error> where D: Deserializer<'de> {
let value = Uint::deserialize(d)?;
if value == Uint(U256::from(0)) {
return Err(Error::invalid_value(Unexpected::Unsigned(value.into()), &"a non-zero value"))
}
Ok(value)
}
pub fn validate_optional_non_zero<'de, D>(d: D) -> Result<Option<Uint>, D::Error> where D: Deserializer<'de> {
let value: Option<Uint> = Option::deserialize(d)?;
if let Some(value) = value {
if value == Uint(U256::from(0)) {
return Err(Error::invalid_value(Unexpected::Unsigned(value.into()), &"a non-zero value"))
}
}
Ok(value)
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use serde_json; use serde_json;