From 41dc392dad3dcb4e5568040054f060cb1fb41922 Mon Sep 17 00:00:00 2001 From: Dimitry Date: Mon, 28 May 2018 22:51:41 +0300 Subject: [PATCH] add validation schemes --- libdevcore/JsonUtils.cpp | 35 ++++-- libdevcore/JsonUtils.h | 23 +++- libethereum/Account.cpp | 31 +---- libethereum/ChainParams.cpp | 110 ++--------------- libethereum/ValidationSchemes.cpp | 139 ++++++++++++++++++++++ libethereum/ValidationSchemes.h | 82 +++++++++++++ test/unittests/libethereum/ClientTest.cpp | 4 +- 7 files changed, 278 insertions(+), 146 deletions(-) create mode 100644 libethereum/ValidationSchemes.cpp create mode 100644 libethereum/ValidationSchemes.h diff --git a/libdevcore/JsonUtils.cpp b/libdevcore/JsonUtils.cpp index 64056f98f1c..0ff9e8707eb 100644 --- a/libdevcore/JsonUtils.cpp +++ b/libdevcore/JsonUtils.cpp @@ -56,13 +56,12 @@ std::string dev::jsonTypeAsString(json_spirit::Value_type _type) } void dev::requireJsonFields(json_spirit::mObject const& _o, std::string const& _config, - std::map const& _validationMap, - std::set const& _ignoreFields) + std::map const& _validationMap) { // check for unexpected fiedls for (auto const& field : _o) { - if (!_validationMap.count(field.first) && !_ignoreFields.count(field.first)) + if (!_validationMap.count(field.first)) { std::string const comment = "Unexpected field '" + field.first + "' in config: " + _config; @@ -75,27 +74,37 @@ void dev::requireJsonFields(json_spirit::mObject const& _o, std::string const& _ // check field types with validation map for (auto const vmap : _validationMap) { + // check that all required fields are in the object 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)); + if (vmap.second.second == jsonField::Required) + { + 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)); + } + else if (vmap.second.second == jsonField::Optional) + continue; } + // check that field type is one of allowed field types bool matched = false; - std::string sTypes; - for (auto const& type : vmap.second) + for (auto const& type : vmap.second.first) { - if (sTypes.size()) - sTypes += ", or "; - sTypes += jsonTypeAsString(type); if (_o.at(vmap.first).type() == type) matched = true; } if (matched == false) { + std::string sTypes; + for (auto const& type : vmap.second.first) + { + if (sTypes.size()) + sTypes += ", or "; + sTypes += jsonTypeAsString(type); + } std::string const comment = "Field '" + vmap.first + "' expected to be " + sTypes + ", but set to " + jsonTypeAsString(_o.at(vmap.first).type()) + " in " + _config; diff --git a/libdevcore/JsonUtils.h b/libdevcore/JsonUtils.h index bcc45136b3b..9f2db0b6359 100644 --- a/libdevcore/JsonUtils.h +++ b/libdevcore/JsonUtils.h @@ -28,10 +28,21 @@ void validateFieldNames(json_spirit::mObject const& _obj, std::set // 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 = {}); +enum jsonField +{ + Required, + Optional +}; +using jsonTypeSet = std::set; +using jsonType = std::pair; +//! Check the json object with validation map that reuires certain field of certain type to be +//! present in json +/*! + \param _o a json object to check + \param _configName a string with json object name. Will apper in error message. + \param _validationMap a map with json objects that would be checked. "objName" -> {js::str_type, + jsonField::Required} +*/ +void requireJsonFields(json_spirit::mObject const& _o, std::string const& _configName, + std::map const& _validationMap); } diff --git a/libethereum/Account.cpp b/libethereum/Account.cpp index c3c67a5407c..4b80144370d 100644 --- a/libethereum/Account.cpp +++ b/libethereum/Account.cpp @@ -20,6 +20,7 @@ */ #include "Account.h" +#include "ValidationSchemes.h" #include #include #include @@ -27,6 +28,7 @@ using namespace std; using namespace dev; using namespace dev::eth; +using namespace dev::eth::validation; namespace fs = boost::filesystem; @@ -81,29 +83,8 @@ PrecompiledContract createPrecompiledContract(js::mObject& _precompiled) throw; } } - -} -namespace -{ - string const c_wei = "wei"; - string const c_finney = "finney"; - string const c_balance = "balance"; - string const c_nonce = "nonce"; - string const c_code = "code"; - string const c_codeFromFile = "codeFromFile"; ///< A file containg a code as bytes. - string const c_storage = "storage"; - string const c_shouldnotexist = "shouldnotexist"; - string const c_precompiled = "precompiled"; - std::set const c_knownAccountFields = { - c_wei, c_finney, c_balance, c_nonce, c_code, c_codeFromFile, c_storage, c_shouldnotexist, - c_code, c_precompiled - }; - void validateAccountMapObj(js::mObject const& _o) - { - for (auto const& field: _o) - validateFieldNames(field.second.get_obj(), c_knownAccountFields); - } } + AccountMap dev::eth::jsonToAccountMap(std::string const& _json, u256 const& _defaultNonce, AccountMaskMap* o_mask, PrecompiledContractMap* o_precompiled, const fs::path& _configPath) { @@ -119,12 +100,12 @@ AccountMap dev::eth::jsonToAccountMap(std::string const& _json, u256 const& _def js::mValue val; json_spirit::read_string_or_throw(_json, val); js::mObject o = val.get_obj(); - validateAccountMapObj(o); - for (auto const& account: o) + for (auto const& account: o) { - Address a(fromHex(account.first)); + Address a(fromHex(account.first)); // FIXME: Do not copy every account object. auto o = account.second.get_obj(); + validateAccountMapObj(o); bool haveBalance = (o.count(c_wei) || o.count(c_finney) || o.count(c_balance)); bool haveNonce = o.count(c_nonce); diff --git a/libethereum/ChainParams.cpp b/libethereum/ChainParams.cpp index fec8909b17c..2e6580d1dcf 100644 --- a/libethereum/ChainParams.cpp +++ b/libethereum/ChainParams.cpp @@ -27,15 +27,16 @@ #include #include #include +#include "ValidationSchemes.h" #include "GenesisInfo.h" #include "State.h" #include "Account.h" using namespace std; using namespace dev; using namespace eth; +using namespace eth::validation; namespace js = json_spirit; - ChainParams::ChainParams() { for (unsigned i = 1; i <= 4; ++i) @@ -52,79 +53,6 @@ ChainParams::ChainParams(string const& _json, h256 const& _stateRoot) *this = loadConfig(_json, _stateRoot); } -namespace -{ -string const c_sealEngine = "sealEngine"; -string const c_params = "params"; -string const c_genesis = "genesis"; -string const c_accounts = "accounts"; - -set const c_knownChainConfigFields = - {c_sealEngine, c_params, c_genesis, c_accounts}; - -string const c_minGasLimit = "minGasLimit"; -string const c_maxGasLimit = "maxGasLimit"; -string const c_gasLimitBoundDivisor = "gasLimitBoundDivisor"; -string const c_homesteadForkBlock = "homesteadForkBlock"; -string const c_daoHardforkBlock = "daoHardforkBlock"; -string const c_EIP150ForkBlock = "EIP150ForkBlock"; -string const c_EIP158ForkBlock = "EIP158ForkBlock"; -string const c_byzantiumForkBlock = "byzantiumForkBlock"; -string const c_eWASMForkBlock = "eWASMForkBlock"; -string const c_constantinopleForkBlock = "constantinopleForkBlock"; -string const c_accountStartNonce = "accountStartNonce"; -string const c_maximumExtraDataSize = "maximumExtraDataSize"; -string const c_tieBreakingGas = "tieBreakingGas"; -string const c_blockReward = "blockReward"; -string const c_difficultyBoundDivisor = "difficultyBoundDivisor"; -string const c_minimumDifficulty = "minimumDifficulty"; -string const c_durationLimit = "durationLimit"; -string const c_chainID = "chainID"; -string const c_networkID = "networkID"; -string const c_allowFutureBlocks = "allowFutureBlocks"; - -set const c_knownParamNames = {c_minGasLimit, c_maxGasLimit, c_gasLimitBoundDivisor, - c_homesteadForkBlock, c_EIP150ForkBlock, c_EIP158ForkBlock, c_accountStartNonce, - c_maximumExtraDataSize, c_tieBreakingGas, c_blockReward, c_byzantiumForkBlock, c_eWASMForkBlock, - c_constantinopleForkBlock, c_daoHardforkBlock, c_minimumDifficulty, c_difficultyBoundDivisor, - 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 - { - if (account.count("wei")) - requireJsonFields(account, "ChainParams::loadConfig", {{"wei", {json_spirit::str_type}}}); - 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 { @@ -134,13 +62,10 @@ ChainParams ChainParams::loadConfig( js::mObject obj = val.get_obj(); validateConfigJson(obj); - validateFieldNames(obj, c_knownChainConfigFields); - cp.sealEngineName = obj[c_sealEngine].get_str(); // params js::mObject params = obj[c_params].get_obj(); - validateFieldNames(params, c_knownParamNames); - cp.accountStartNonce = u256(fromBigEndian(fromHex(params[c_accountStartNonce].get_str()))); + cp.accountStartNonce = u256(fromBigEndian(fromHex(params[c_accountStartNonce].get_str()))); cp.maximumExtraDataSize = u256(fromBigEndian(fromHex(params[c_maximumExtraDataSize].get_str()))); cp.tieBreakingGas = params.count(c_tieBreakingGas) ? params[c_tieBreakingGas].get_bool() : true; cp.setBlockReward(u256(fromBigEndian(fromHex(params[c_blockReward].get_str())))); @@ -178,28 +103,15 @@ ChainParams ChainParams::loadConfig( cp.genesisState = jsonToAccountMap( genesisStateStr, cp.accountStartNonce, nullptr, &cp.precompiled, _configPath); - cp.stateRoot = _stateRoot ? _stateRoot : cp.calculateStateRoot(true); - return cp; -} + // Strict account check + json_spirit::read_string_or_throw(genesisStateStr, val); + for (auto const& account: val.get_obj()) + validateAccountObj(account.second.get_obj()); -namespace -{ -string const c_parentHash = "parentHash"; -string const c_coinbase = "coinbase"; -string const c_author = "author"; -string const c_difficulty = "difficulty"; -string const c_gasLimit = "gasLimit"; -string const c_gasUsed = "gasUsed"; -string const c_timestamp = "timestamp"; -string const c_extraData = "extraData"; -string const c_mixHash = "mixHash"; -string const c_nonce = "nonce"; + cp.stateRoot = _stateRoot ? _stateRoot : cp.calculateStateRoot(true); -set const c_knownGenesisFields = { - c_parentHash, c_coinbase, c_author, c_difficulty, c_gasLimit, c_gasUsed, c_timestamp, - c_extraData, c_mixHash, c_nonce -}; + return cp; } ChainParams ChainParams::loadGenesis(string const& _json, h256 const& _stateRoot) const @@ -210,9 +122,7 @@ ChainParams ChainParams::loadGenesis(string const& _json, h256 const& _stateRoot json_spirit::read_string(_json, val); js::mObject genesis = val.get_obj(); - validateFieldNames(genesis, c_knownGenesisFields); - - cp.parentHash = h256(genesis[c_parentHash].get_str()); + cp.parentHash = h256(0); // required by the YP cp.author = genesis.count(c_coinbase) ? h160(genesis[c_coinbase].get_str()) : h160(genesis[c_author].get_str()); cp.difficulty = genesis.count(c_difficulty) ? u256(fromBigEndian(fromHex(genesis[c_difficulty].get_str()))) : 0; cp.gasLimit = u256(fromBigEndian(fromHex(genesis[c_gasLimit].get_str()))); diff --git a/libethereum/ValidationSchemes.cpp b/libethereum/ValidationSchemes.cpp new file mode 100644 index 00000000000..1ede262e0a9 --- /dev/null +++ b/libethereum/ValidationSchemes.cpp @@ -0,0 +1,139 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +#include +#include +#include "ValidationSchemes.h" + +using namespace std; +using namespace dev; +namespace js = json_spirit; + +namespace dev { +namespace eth { +namespace validation { + +string const c_sealEngine = "sealEngine"; +string const c_params = "params"; +string const c_genesis = "genesis"; +string const c_accounts = "accounts"; +string const c_balance = "balance"; +string const c_wei = "wei"; +string const c_finney = "finney"; +string const c_author = "author"; +string const c_coinbase = "coinbase"; +string const c_nonce = "nonce"; +string const c_gasLimit = "gasLimit"; +string const c_timestamp = "timestamp"; +string const c_difficulty = "difficulty"; +string const c_extraData = "extraData"; +string const c_mixHash = "mixHash"; +string const c_parentHash = "parentHash"; +string const c_precompiled = "precompiled"; +string const c_code = "code"; +string const c_storage = "storage"; +string const c_gasUsed = "gasUsed"; +string const c_codeFromFile = "codeFromFile"; ///< A file containg a code as bytes. +string const c_shouldnotexist = "shouldnotexist"; + +string const c_minGasLimit = "minGasLimit"; +string const c_maxGasLimit = "maxGasLimit"; +string const c_gasLimitBoundDivisor = "gasLimitBoundDivisor"; +string const c_homesteadForkBlock = "homesteadForkBlock"; +string const c_daoHardforkBlock = "daoHardforkBlock"; +string const c_EIP150ForkBlock = "EIP150ForkBlock"; +string const c_EIP158ForkBlock = "EIP158ForkBlock"; +string const c_byzantiumForkBlock = "byzantiumForkBlock"; +string const c_eWASMForkBlock = "eWASMForkBlock"; +string const c_constantinopleForkBlock = "constantinopleForkBlock"; +string const c_accountStartNonce = "accountStartNonce"; +string const c_maximumExtraDataSize = "maximumExtraDataSize"; +string const c_tieBreakingGas = "tieBreakingGas"; +string const c_blockReward = "blockReward"; +string const c_difficultyBoundDivisor = "difficultyBoundDivisor"; +string const c_minimumDifficulty = "minimumDifficulty"; +string const c_durationLimit = "durationLimit"; +string const c_chainID = "chainID"; +string const c_networkID = "networkID"; +string const c_allowFutureBlocks = "allowFutureBlocks"; + +void validateConfigJson(js::mObject const& _obj) +{ + requireJsonFields(_obj, "ChainParams::loadConfig", + {{c_sealEngine, {{json_spirit::str_type}, jsonField::Required}}, + {c_params, {{json_spirit::obj_type}, jsonField::Required}}, + {c_genesis, {{json_spirit::obj_type}, jsonField::Required}}, + {c_accounts, {{json_spirit::obj_type}, jsonField::Required}}}); + + requireJsonFields(_obj.at(c_genesis).get_obj(), "ChainParams::loadConfig::genesis", + {{c_author, {{json_spirit::str_type}, jsonField::Required}}, + {c_nonce, {{json_spirit::str_type}, jsonField::Required}}, + {c_gasLimit, {{json_spirit::str_type}, jsonField::Required}}, + {c_timestamp, {{json_spirit::str_type}, jsonField::Required}}, + {c_difficulty, {{json_spirit::str_type}, jsonField::Required}}, + {c_extraData, {{json_spirit::str_type}, jsonField::Required}}, + {c_mixHash, {{json_spirit::str_type}, jsonField::Required}}, + {c_parentHash, {{json_spirit::str_type}, jsonField::Optional}}}); + + js::mObject const& accounts = _obj.at(c_accounts).get_obj(); + for (auto const& acc : accounts) + validateAccountObj(acc.second.get_obj()); +} + +void validateAccountMapObj(js::mObject const& _obj) +{ + // A map object with possibly defined fields + requireJsonFields(_obj, "validateAccountMapObj", + {{c_storage, {{json_spirit::obj_type}, jsonField::Optional}}, + {c_balance, {{json_spirit::str_type}, jsonField::Optional}}, + {c_nonce, {{json_spirit::str_type}, jsonField::Optional}}, + {c_code, {{json_spirit::str_type}, jsonField::Optional}}, + {c_precompiled, {{json_spirit::obj_type}, jsonField::Optional}}, + {c_shouldnotexist, {{json_spirit::str_type}, jsonField::Optional}}, + {c_wei, {{json_spirit::str_type}, jsonField::Optional}}}); +} + +void validateAccountObj(js::mObject const& _obj) +{ + if (_obj.count(c_precompiled)) + { + // A precompiled contract + requireJsonFields(_obj, "validateAccountObj", + {{c_precompiled, {{json_spirit::obj_type}, jsonField::Required}}, + {c_wei, {{json_spirit::str_type}, jsonField::Optional}}}); + } + else if (_obj.size() == 1) + { + // A genesis account with only balance set + if (_obj.count(c_balance)) + requireJsonFields(_obj, "validateAccountObj", + {{c_balance, {{json_spirit::str_type}, jsonField::Required}}}); + else + requireJsonFields(_obj, "validateAccountObj", + {{c_wei, {{json_spirit::str_type}, jsonField::Required}}}); + } + else + { + // A standart account with all fields + requireJsonFields(_obj, "validateAccountObj", + {{c_code, {{json_spirit::str_type}, jsonField::Required}}, + {c_nonce, {{json_spirit::str_type}, jsonField::Required}}, + {c_storage, {{json_spirit::obj_type}, jsonField::Required}}, + {c_balance, {{json_spirit::str_type}, jsonField::Required}}}); + } +} + +}}} diff --git a/libethereum/ValidationSchemes.h b/libethereum/ValidationSchemes.h new file mode 100644 index 00000000000..02d26bd3559 --- /dev/null +++ b/libethereum/ValidationSchemes.h @@ -0,0 +1,82 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file ValidationSchemes.h + * @date 2018 + */ + +#include +#include + +namespace dev { +namespace eth { +namespace validation { + +extern std::string const c_sealEngine; +extern std::string const c_params; +extern std::string const c_genesis; +extern std::string const c_accounts; +extern std::string const c_balance; +extern std::string const c_wei; +extern std::string const c_finney; +extern std::string const c_author; +extern std::string const c_coinbase; +extern std::string const c_nonce; +extern std::string const c_gasLimit; +extern std::string const c_timestamp; +extern std::string const c_difficulty; +extern std::string const c_extraData; +extern std::string const c_mixHash; +extern std::string const c_parentHash; +extern std::string const c_precompiled; +extern std::string const c_storage; +extern std::string const c_code; +extern std::string const c_gasUsed; +extern std::string const c_codeFromFile; +extern std::string const c_shouldnotexist; + +extern std::string const c_minGasLimit; +extern std::string const c_maxGasLimit; +extern std::string const c_gasLimitBoundDivisor; +extern std::string const c_homesteadForkBlock; +extern std::string const c_daoHardforkBlock; +extern std::string const c_EIP150ForkBlock; +extern std::string const c_EIP158ForkBlock; +extern std::string const c_byzantiumForkBlock; +extern std::string const c_eWASMForkBlock; +extern std::string const c_constantinopleForkBlock; +extern std::string const c_accountStartNonce; +extern std::string const c_maximumExtraDataSize; +extern std::string const c_tieBreakingGas; +extern std::string const c_blockReward; +extern std::string const c_difficultyBoundDivisor; +extern std::string const c_minimumDifficulty; +extern std::string const c_durationLimit; +extern std::string const c_chainID; +extern std::string const c_networkID; +extern std::string const c_allowFutureBlocks; + +// Validate config.json that contains chain params and genesis state +void validateConfigJson(json_spirit::mObject const& _obj); + +// Validate account json object +void validateAccountObj(json_spirit::mObject const& _o); + +// Validate account Map json object. Map indicates which fields are set +void validateAccountMapObj(json_spirit::mObject const& _o); + + +}}} diff --git a/test/unittests/libethereum/ClientTest.cpp b/test/unittests/libethereum/ClientTest.cpp index af80508b405..e36fc7a0f40 100644 --- a/test/unittests/libethereum/ClientTest.cpp +++ b/test/unittests/libethereum/ClientTest.cpp @@ -77,10 +77,10 @@ static std::string const c_configString = R"( "nonce": "0x0000000000000042", "author": "0000000000000010000000000000000000000000", "timestamp": "0x00", - "parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000", "extraData": "0x", "gasLimit": "0x1000000000000", - "difficulty": "0x020000" + "difficulty": "0x020000", + "mixHash": "0x0000000000000000000000000000000000000000000000000000000000000000" }, "accounts": { "0000000000000000000000000000000000000001": { "wei": "1", "precompiled": { "name": "ecrecover", "linear": { "base": 3000, "word": 0 } } },