diff --git a/libdevcore/Exceptions.h b/libdevcore/Exceptions.h index ec7e3f2206c..c5f9942405a 100644 --- a/libdevcore/Exceptions.h +++ b/libdevcore/Exceptions.h @@ -69,6 +69,8 @@ DEV_SIMPLE_EXCEPTION(Overflow); DEV_SIMPLE_EXCEPTION(FailedInvariant); DEV_SIMPLE_EXCEPTION(ValueTooLarge); DEV_SIMPLE_EXCEPTION(UnknownField); +DEV_SIMPLE_EXCEPTION(MissingField); +DEV_SIMPLE_EXCEPTION(WrongFieldType); DEV_SIMPLE_EXCEPTION(InterfaceNotSupported); DEV_SIMPLE_EXCEPTION(ExternalFunctionFailure); diff --git a/libdevcore/JsonUtils.cpp b/libdevcore/JsonUtils.cpp index e89648e67de..64056f98f1c 100644 --- a/libdevcore/JsonUtils.cpp +++ b/libdevcore/JsonUtils.cpp @@ -31,3 +31,77 @@ void dev::validateFieldNames(json_spirit::mObject const& _obj, std::set const& _validationMap, + std::set const& _ignoreFields) +{ + // check for unexpected fiedls + for (auto const& field : _o) + { + if (!_validationMap.count(field.first) && !_ignoreFields.count(field.first)) + { + std::string const comment = + "Unexpected field '" + field.first + "' in config: " + _config; + std::cerr << comment << "\n" + << json_spirit::write_string((json_spirit::mValue)_o, true) << "\n"; + BOOST_THROW_EXCEPTION(UnknownField() << errinfo_comment(comment)); + } + } + + // check field types with validation map + for (auto const vmap : _validationMap) + { + if (!_o.count(vmap.first)) + { + std::string const comment = + "Expected field '" + vmap.first + "' not found in config: " + _config; + std::cerr << comment << "\n" + << json_spirit::write_string((json_spirit::mValue)_o, true) << "\n"; + BOOST_THROW_EXCEPTION(MissingField() << errinfo_comment(comment)); + } + + bool matched = false; + std::string sTypes; + for (auto const& type : vmap.second) + { + if (sTypes.size()) + sTypes += ", or "; + sTypes += jsonTypeAsString(type); + if (_o.at(vmap.first).type() == type) + matched = true; + } + if (matched == false) + { + std::string const comment = + "Field '" + vmap.first + "' expected to be " + sTypes + ", but set to " + + jsonTypeAsString(_o.at(vmap.first).type()) + " in " + _config; + std::cerr << comment << "\n" + << json_spirit::write_string((json_spirit::mValue)_o, true) << "\n"; + BOOST_THROW_EXCEPTION(WrongFieldType() << errinfo_comment(comment)); + } + } +} diff --git a/libdevcore/JsonUtils.h b/libdevcore/JsonUtils.h index c22b44a9e14..bcc45136b3b 100644 --- a/libdevcore/JsonUtils.h +++ b/libdevcore/JsonUtils.h @@ -24,4 +24,14 @@ namespace dev { // Throws UnknownField() if _obj contains field names not listed in _allowedFields. void validateFieldNames(json_spirit::mObject const& _obj, std::set const& _allowedFields); + +// Converts json value type to string +std::string jsonTypeAsString(json_spirit::Value_type _type); + +// Check json _o with validation map that reuires certain field of certain type to be present in +// json +typedef std::set possibleJsonType; +void requireJsonFields(json_spirit::mObject const& _o, std::string const& _config, + std::map const& _validationMap, + std::set const& _ignoreFields = {}); } diff --git a/libethereum/ChainParams.cpp b/libethereum/ChainParams.cpp index e68e57e7983..c12c728b2a0 100644 --- a/libethereum/ChainParams.cpp +++ b/libethereum/ChainParams.cpp @@ -90,6 +90,36 @@ set const c_knownParamNames = {c_minGasLimit, c_maxGasLimit, c_gasLimitB c_durationLimit, c_chainID, c_networkID, c_allowFutureBlocks}; } // anonymous namespace +void validateConfigJson(js::mObject const& _obj) +{ + requireJsonFields(_obj, "ChainParams::loadConfig", + {{"sealEngine", {json_spirit::str_type}}, {"params", {json_spirit::obj_type}}, + {"genesis", {json_spirit::obj_type}}, {"accounts", {json_spirit::obj_type}}}); + + requireJsonFields(_obj.at("genesis").get_obj(), "ChainParams::loadConfig", + {{"author", {json_spirit::str_type}}, {"nonce", {json_spirit::str_type}}, + {"author", {json_spirit::str_type}}, {"gasLimit", {json_spirit::str_type}}, + {"timestamp", {json_spirit::str_type}}, {"difficulty", {json_spirit::str_type}}, + {"extraData", {json_spirit::str_type}}}, + {"mixHash", "parentHash"}); + + js::mObject const& accounts = _obj.at("accounts").get_obj(); + for (auto const& acc : accounts) + { + js::mObject const& account = acc.second.get_obj(); + if (account.count("precompiled")) + { + requireJsonFields(account, "ChainParams::loadConfig", + {{"precompiled", {json_spirit::obj_type}}}, {"wei"}); + } + else + { + requireJsonFields(account, "ChainParams::loadConfig", + {{"balance", {json_spirit::str_type}}}, {"code", "nonce", "storage"}); + } + } +} + ChainParams ChainParams::loadConfig( string const& _json, h256 const& _stateRoot, const boost::filesystem::path& _configPath) const { @@ -98,7 +128,8 @@ ChainParams ChainParams::loadConfig( json_spirit::read_string_or_throw(_json, val); js::mObject obj = val.get_obj(); - validateFieldNames(obj, c_knownChainConfigFields); + validateConfigJson(obj); + validateFieldNames(obj, c_knownChainConfigFields); cp.sealEngineName = obj[c_sealEngine].get_str(); // params diff --git a/test/tools/libtesteth/TestHelper.cpp b/test/tools/libtesteth/TestHelper.cpp index 8257f9d6917..8f0d8ef5e8f 100644 --- a/test/tools/libtesteth/TestHelper.cpp +++ b/test/tools/libtesteth/TestHelper.cpp @@ -23,6 +23,7 @@ #include "TestOutputHelper.h" #include "wast2wasm.h" +#include #include #include @@ -581,29 +582,6 @@ void checkCallCreates( } } -string jsonTypeAsString(json_spirit::Value_type _type) -{ - switch (_type) - { - case json_spirit::obj_type: - return "json Object"; - case json_spirit::array_type: - return "json Array"; - case json_spirit::str_type: - return "json String"; - case json_spirit::bool_type: - return "json Bool"; - case json_spirit::int_type: - return "json Int"; - case json_spirit::real_type: - return "json Real"; - case json_spirit::null_type: - return "json Null"; - default: - return "json n/a"; - } -} - void requireJsonFields(json_spirit::mObject const& _o, string const& _section, map const& _validationMap) {