Skip to content
This repository has been archived by the owner on Oct 28, 2021. It is now read-only.

explicit chainParams check #5030

Merged
merged 5 commits into from
Jun 15, 2018
Merged

explicit chainParams check #5030

merged 5 commits into from
Jun 15, 2018

Conversation

winsvega
Copy link
Contributor

Instead of validating fields names with c_fieldname constant
I moved requireJson function to libdevcode
so you could specify how json object should look like, and what fields it must have and of what time those fields could be.

example code in validating the chainParams. requires review.
this is a step toward understanding what chain params are required and what could be set optional.

@winsvega winsvega requested a review from pirapira May 16, 2018 16:04
@winsvega winsvega force-pushed the smallrpc branch 2 times, most recently from 98a1af7 to 9b9d269 Compare May 16, 2018 17:20
@codecov-io
Copy link

codecov-io commented May 16, 2018

Codecov Report

Merging #5030 into develop will decrease coverage by 0.33%.
The diff coverage is 81.6%.

Impacted file tree graph

@@             Coverage Diff             @@
##           develop    #5030      +/-   ##
===========================================
- Coverage    58.71%   58.37%   -0.34%     
===========================================
  Files          334      346      +12     
  Lines        26424    26888     +464     
  Branches      3133     3228      +95     
===========================================
+ Hits         15514    15696     +182     
- Misses        9909    10180     +271     
- Partials      1001     1012      +11


// Check json _o with validation map that reuires certain field of certain type to be present in
// json
typedef std::set<json_spirit::Value_type> possibleJsonType;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For me jsonTypeSet sounds better.

First I thought "possibly it is a json type, but possibly it is not a json type".

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, JsonTypeSet would be better.
Also better use using instead of typedef.
And better place type definitions at the top, before function definitions.

{
if (sTypes.size())
sTypes += ", or ";
sTypes += jsonTypeAsString(type);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need this concatenation only when there is an error.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so you think two loops on for (auto const& type : vmap.second) is better ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes

@winsvega
Copy link
Contributor Author

ethereum/EIPs#1085

@winsvega
Copy link
Contributor Author

@gumb0 @pirapira
Cpp client has specific declaration of a precompiled contracts in the genesis state.

My question is do we really need it? Precompiled contracts are hardcoded anyway. non other clients support this specific account desctiption anyway. retesteth and hive has to prepare a specific genesis for cpp in this case. could we move those params to hardcode of the client?

"0000000000000000000000000000000000000001": { "precompiled": { "name": "ecrecover", "linear": { "base": 3000, "word": 0 } } },
		"0000000000000000000000000000000000000002": { "precompiled": { "name": "sha256", "linear": { "base": 60, "word": 12 } } },
		"0000000000000000000000000000000000000003": { "precompiled": { "name": "ripemd160", "linear": { "base": 600, "word": 120 } } },
		"0000000000000000000000000000000000000004": { "precompiled": { "name": "identity", "linear": { "base": 15, "word": 3 } } },
		"0000000000000000000000000000000000000005": { "precompiled": { "name": "modexp" } },
		"0000000000000000000000000000000000000006": { "precompiled": { "name": "alt_bn128_G1_add", "linear": { "base": 500, "word": 0 } } },
		"0000000000000000000000000000000000000007": { "precompiled": { "name": "alt_bn128_G1_mul", "linear": { "base": 40000, "word": 0 } } },
		"0000000000000000000000000000000000000008": { "precompiled": { "name": "alt_bn128_pairing_product" } }

@gumb0 gumb0 self-requested a review May 22, 2018 10:10
@gumb0
Copy link
Member

gumb0 commented May 22, 2018

I think we should be able to get rid of them.
I actually don't like that we now need to duplicate the fork block number in startingBlock for precompiles that appear at Byzantium. And flexibility with setting up gas cost formula looks like not needed complexity.

On the other hand I think ewasm team requested or even implemented the feature with config file to refer to external files with precompiled code when they want to create wasm-precompiles. Not sure what to do with that.

cc @chfast

@@ -98,7 +133,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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean to leave both of these validations here?
So now we have two separate list of allowed fields to maintain?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the other one could be removed from this place. once we come to a standart in genesis config.
the question remains is wether we should use constants instead of strings in scheme validation functions.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should validate it two times. So let's not merge it then until we have final version

@pirapira
Copy link
Member

@winsvega @gumb0 I have no opinions around precompiles. If WASM people need a place, the format should allow that as an optional field.

@winsvega
Copy link
Contributor Author

optional fields are ok.
but in the current implementation it is required.
otherwise even if you set the fork block in configuration, the client wont initialize a precompiled for the forkRules.

@@ -98,7 +133,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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we should validate it two times. So let's not merge it then until we have final version

void validateConfigJson(js::mObject const& _obj)
{
requireJsonFields(_obj, "ChainParams::loadConfig",
{{"sealEngine", {json_spirit::str_type}}, {"params", {json_spirit::obj_type}},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So why did you choose not to use the constants for the field names here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this check is like a constant itself. if it does not match, then it throws an error.
so if you have a typo, it will break the test run.
although it is said that it is not good to use char in the code this way. maybe we need a dictionary class that would return a string by id for all text checks and messages in the client.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We shouldn't have duplicate string literals in the code here, it's maintenance pain and might waste resources. So use here c_sealEngine, c_params etc.

}
}

void dev::requireJsonFields(json_spirit::mObject const& _o, std::string const& _config,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about having it in libdevcore

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the previous checks were put into this file. @pirapira ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there is a better place, feel free to move.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so we could remove jsonUtils from libdevcore. and keep it in libethereum. but I dont think its a better choise

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If libdevcore or libethereum, I'd vote for libdevcore. This is not so particularly about Ethereum.

@winsvega
Copy link
Contributor Author

looks like client devs wont come up with the common scheme any time soon.
I suggest we figure out the minimum required config fields for cpp and create our own scheme validation.

// A precompiled contract
requireJsonFields(_obj, "validateAccountObj",
{{c_precompiled, {json_spirit::obj_type}}},
{c_wei});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't you want wei as str_type?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, it's not required?

// Check json _o with validation map that reuires certain field of certain type to be present in
// json
using jsonTypeSet = std::set<json_spirit::Value_type>;
void requireJsonFields(json_spirit::mObject const& _o, std::string const& _config,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please document this function.

@chfast
Copy link
Member

chfast commented May 29, 2018

Have you considered using JSON Schema to specify and validate JSON configs? I've found validator for JSON Schema: https://github.com/tristanpenman/valijson.

@winsvega
Copy link
Contributor Author

Hmm. That repo looks like a lot of code to add for dependencies.
Here we have one small function that does a verification

};
using jsonTypeSet = std::set<json_spirit::Value_type>;
using jsonType = std::pair<jsonTypeSet, jsonField>;
void requireJsonFields(json_spirit::mObject const& _o, std::string const& _config,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function does a little bit different job from what "require Json Fields" suggests.

Please document the function.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you clarify what "document the function" should look like ?

// Converts json value type to string
std::string jsonTypeAsString(json_spirit::Value_type _type);

enum jsonField
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

enum class is preferable to enum. Also start type names with capital letter. Also this should be better named something like JsonFieldPresence maybe

Required,
Optional
};
using jsonTypeSet = std::set<json_spirit::Value_type>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Start type name with capital letter

Optional
};
using jsonTypeSet = std::set<json_spirit::Value_type>;
using jsonType = std::pair<jsonTypeSet, jsonField>;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Start type name with capital letter, also this is something like JsonTypesAndPresence I'd say

if (matched == false)
{
std::string sTypes;
for (auto const& type : vmap.second.first)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd use boost::algorithm::join here instead of reinventing joining the strings, but it's up to you

sTypes += jsonTypeAsString(type);
}
std::string const comment =
"Field '" + vmap.first + "' expected to be " + sTypes + ", but set to " +
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is (or was) expected to be, but is (or was) set

You should have received a copy of the GNU General Public License
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file ValidationSchemes.h
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove these @file and @date
Use @file only if you provide some meaningful description of the file

// FIXME: Do not copy every account object.
auto o = account.second.get_obj();
validateAccountMapObj(o);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you do this now inside the loop? Validating the whole map object in each iteration?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

checking one account instead of a list of accounts

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah wow, don't use o for both the whole map and one account object.
Use more meaningful names than o anyway

namespace eth {
namespace validation {

extern std::string const c_sealEngine;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I liked these constants more in ChainParams.cpp / Account.cpp ... where they are closer to the actual useful work being done with them.
Are you sure with moving all of this into separate file? I'd leave the three validation functions closer to the other work with the json configs

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this constants could grow over time into a big amount of variables

void validateAccountMapObj(json_spirit::mObject const& _o);


}}}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't look like it was clang-formatted

{c_precompiled, {{json_spirit::obj_type}, JsonFieldPresence::Optional}},
{c_shouldnotexist, {{json_spirit::str_type}, JsonFieldPresence::Optional}},
{c_wei, {{json_spirit::str_type}, JsonFieldPresence::Optional}}});
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Indentation is off here. Use clang-format

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used clang-format

#include <string>

using namespace std;
using namespace dev;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't need this, everything is defined inside dev anyway.
You probably don't need using namespace std; either


using namespace std;
using namespace dev;
namespace js = json_spirit;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You don't use this below, it's json_spirit everywhere. So maybe just delete it.

}
else
{
// A standart account with all fields
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the difference between standard account and genesis account? Do these standard accounts really always have code and storage?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes. standart account required to have all 4 fields.
in genesis it is not required.
and account map could be empty

@@ -27,15 +27,16 @@
#include <libethcore/SealEngine.h>
#include <libethcore/BlockHeader.h>
#include <libethcore/Precompiled.h>
#include "ValidationSchemes.h"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use clang-format here, it should reorder includes

}
else
{
// A standart account with all fields
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In which configs are they used?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah it's allowed to have in genesis accounts either balance only or all 4 fields? ok then

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In tests also

// Validate account json object
void validateAccountObj(json_spirit::mObject const& _o);

// Validate account Map json object. Map indicates which fields are set
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Second part of comment is not needed I guess?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the confusion is because it should be account mask here

// FIXME: Do not copy every account object.
auto o = account.second.get_obj();
auto const& accountMapJson = account.second.get_obj();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe just accountJson or accountObject, because acountMap sounds like a map of accounts, but as I understand it refers to single account

Copy link
Contributor Author

@winsvega winsvega Jun 13, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Map is because it indicates which fields are set and which are not. there is a class for that: AccountMask. should be renamed to accountMask to avoid confusion

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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also this name, if it validates just one account object I would name it differently

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

accountMaskObj

// Strict account check
js::read_string_or_throw(genesisStateStr, val);
for (auto const& account: val.get_obj())
validateAccountObj(account.second.get_obj());
Copy link
Member

@gumb0 gumb0 Jun 13, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you need this here at all? You call validateConfigJson(obj); above and inside it it already goes over all accounts, no?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you right

void validateConfigJson(json_spirit::mObject const& _obj);

// Validate account json object
void validateAccountObj(json_spirit::mObject const& _o);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is now used only by validateConfigJson so make it internal to ValidationSchemes.cpp, remove from header

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think it shoud be removed. it should be used in testeth.

@gumb0
Copy link
Member

gumb0 commented Jun 14, 2018

I'm confused now about these account masks.

dev::eth::jsonToAccountMap() function parses json with accounts and returns all accounts as AccountMap. It also produces AccountMaskMap but that's a side-effect, not it's main purpose.

Now you want to validate input json to this function. Input json has account description, not "account masks". So the validation of json of each account it seems should be called something like validateAccountJsonObject() not validateAccountMaskObj()

Also: I'm looking at ChainParams::loadConfig() which is supposed to parse config including genesis header and accounts. Now it looks like it would validate

  1. first the accounts list inside validateConfigJson() (calling validateAccountObj())
  2. and then it would validate the same account list inside dev::eth::jsonToAccountMap() calling validateAccountMaskObj() for each.

So do you even need both of validateAccountObj() and validateAccountMaskObj()?
It looks to me like that they both validate the same json structure but in a different way for some weird reason.

@winsvega
Copy link
Contributor Author

Could we have a call?

@winsvega
Copy link
Contributor Author

winsvega commented Jun 14, 2018

your confusion might be because

  1. accountMask is totally a different object. it is not a json object of account.
  2. accountMask used only in the tests. but for some reason Gavin implemented it in libethereum.

double check in jsonToAccountMap is because we don't store data in objects. instead we pass arguments from function to function and this function here jsonToAccountMap could not rely on the correctness of the input data. what would be better is an abstraction layer between json data and logical objects. so once you read json data into those objects you wont need to verify every input argument of the function.

@gumb0
Copy link
Member

gumb0 commented Jun 14, 2018

Sure, ping me in discord for a call, you're offline now

@gumb0 gumb0 dismissed pirapira’s stale review June 14, 2018 15:50

Dismissing Yoichi's review as he says he is not available for doing reviews at the moment

@winsvega winsvega merged commit b859d94 into develop Jun 15, 2018
@gumb0 gumb0 deleted the smallrpc branch June 15, 2018 09:09
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants