-
Notifications
You must be signed in to change notification settings - Fork 147
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Accept genesis state and parameter overrides #123
Conversation
7663be7
to
7bfe2a3
Compare
be40735
to
54036f3
Compare
Awesome, and done! Thanks @pipermerriam |
eth_tester/backends/mock/factory.py
Outdated
@@ -259,6 +259,15 @@ def make_genesis_block(): | |||
"uncles": [], | |||
} | |||
|
|||
if overrides is not None: | |||
if not (all(bool(override in genesis_block) for override in overrides)): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It takes a litlte extra work but I think we can improve the error message below and the readability of this code.
allowed_fields = set(genesis_block.keys())
override_fields = set(overrides.keys())
unexpected_fields = tuple(sorted(override_fields.difference(allowed_fields)))
if unexpected_fields:
raise ValueError(
"The following invalid fields were supplied to override the genesis parameters: {0}.".format(unexpected_fields)
)
eth_tester/backends/mock/factory.py
Outdated
def make_genesis_block(): | ||
return { | ||
def make_genesis_block(overrides=None): | ||
genesis_block = { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd suggest using the name default_genesis_block
. This will work well in conjunction with removing the mutation via update
later in this function.
eth_tester/backends/mock/factory.py
Outdated
fields = ', '.join(genesis_block) | ||
error = "Invalid genesis overrides; Valid parameters are {}".format(fields) | ||
raise ValueError(error) | ||
genesis_block.update(overrides) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very minor but we prefer non-mutative approaches. You can use cytoolz.merge
as follows.
from cytoolz import merge
genesis_block = merge(default_genesis_block, overrides)
eth_tester/main.py
Outdated
@@ -505,8 +505,9 @@ def revert_to_snapshot(self, snapshot_id): | |||
for log_filter in self._log_filters.values(): | |||
self._revert_log_filter(log_filter) | |||
|
|||
def reset_to_genesis(self): | |||
self.backend.reset_to_genesis() | |||
def reset_to_genesis(self, genesis_parameter_overrides=None, genesis_state_overrides=None): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm thinking that we should not expose this API at the ETHTester
level and only support it at the backend level. It seems reasonable that some backends might not support custom genesis params and I think it is reasonable to require manual instantiation of the backend when you wish to override genesis parameters.
eth_tester/backends/pyevm/main.py
Outdated
@@ -278,7 +288,7 @@ class PyEVMBackend(object): | |||
chain = None | |||
fork_config = None | |||
|
|||
def __init__(self): | |||
def __init__(self, genesis_parameter_overrides=None): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it would be better to just require providing the fully merged genesis parameters. I believe that will simplify a lot of these apis, removing the concept of merging the overrides into the base parameters. We should however provide an API for generating the merged parameters to make this easy for users.
Maybe a classmethod
or staticmethod
on the main PyEVMBackend
class?
class PyEVMBackend:
@staticmethod
def generate_genesis_params(overrides=None):
...
@staticmethod
def generate_genesis_state(overrides=None):
...
Then the user workflow would be something like:
from eth_tester import EthereumTester, PyEVMBackend
state = PyEVMBackend.generate_genesis_state(state_overrides)
genesis_params = PyEVMBackend.generate_genesis_params(genesis_overrides)
tester = EthereumTester(backend=PyEVMBackend(genesis_params, state)
it's a bit verbose, but not terrible and it should simplify the other APIs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍 - I 100% agree.
Thanks for the review - excellent feedback / ideas @pipermerriam - Ill make these changes |
dc39f4b
to
05deb23
Compare
OK - Ready for another review 🤠 I kept the changeset mostly focused on genesis parameters. I included both |
eth_tester/main.py
Outdated
@@ -605,7 +605,7 @@ def create_log_filter(self, from_block=None, to_block=None, address=None, topics | |||
|
|||
if is_integer(raw_from_block): | |||
if is_integer(raw_to_block): | |||
upper_bound = raw_to_block | |||
upper_bound = raw_to_block + 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought I had fixed this in #121.
How did this re-appear?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
does this PR need a rebase?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hello!
I reset
this module to the wrong ref when this PR was a WIP. I rebased a fix.
Good catch, and Thanks for the review. 🤠
…enesis methods across the codebase
29b4d70
to
ef4bb2f
Compare
Checking In - Does this PR seem like a reasonable approach, or do I need make additional modifications? |
@carver @pipermerriam Can you please review this? This is a very useful feature |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cool, looks like it's nearly there!
eth_tester/backends/pyevm/main.py
Outdated
@@ -119,14 +120,14 @@ def get_default_account_keys(): | |||
|
|||
|
|||
@to_dict | |||
def generate_genesis_state(account_keys): | |||
def generate_genesis_state(account_keys, overrides=None): # TODO: Merge genesis state overrides |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you don't intend to do the genesis state changes in this PR, can you:
- remove the
TODO
- open an issue (suggesting this approach)
- remove this
overrides
keyword arg, which implies that the feature works
eth_tester/backends/pyevm/main.py
Outdated
return get_default_genesis_params(overrides=overrides) | ||
|
||
@staticmethod | ||
def generate_genesis_state(overrides=None): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another cleanup: delete overrides
eth_tester/backends/pyevm/main.py
Outdated
@staticmethod | ||
def generate_genesis_state(overrides=None): | ||
account_keys = get_default_account_keys() | ||
return generate_genesis_state(account_keys=account_keys, overrides=overrides) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
delete overrides
eth_tester/backends/pyevm/main.py
Outdated
return generate_genesis_state(account_keys=account_keys, overrides=overrides) | ||
|
||
@classmethod | ||
def from_genesis_overrides(cls, parameter_overrides=None, state_overrides=None): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
delete state_overrides
eth_tester/backends/pyevm/main.py
Outdated
@classmethod | ||
def from_genesis_overrides(cls, parameter_overrides=None, state_overrides=None): | ||
params = cls.generate_genesis_params(overrides=parameter_overrides) | ||
state = cls.generate_genesis_state(overrides=state_overrides) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
delete state_overrides
tests/backends/test_pyevm.py
Outdated
pass | ||
|
||
def test_override_genesis_parameters(self, eth_tester): | ||
super().test_reset_to_genesis(eth_tester) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It strikes me as odd to run a test directly, especially inside another test. Maybe make a helper function that does a genesis reset, which both tests call.
tests/backends/test_pyevm.py
Outdated
|
||
# Or use the alternate constructor | ||
pyevm_backend = PyEVMBackend.from_genesis_overrides(parameter_overrides=param_overrides) | ||
assert pyevm_backend.chain.header._gas_limit == 4745362 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These two lines should probably be pulled out into a separate test, so that pyevm_backend
from line 43 gets tested in the EthereumTester integration below.
tests/backends/test_pyevm.py
Outdated
|
||
# Initialize pyevm backend with genesis params | ||
pyevm_backend = PyEVMBackend(genesis_params) | ||
assert pyevm_backend.chain.header._gas_limit == 4745362 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about using a public API instead, in case this private variable gets refactored? Something like:
genesis_block = pyevm_backend.get_block_by_number(0)
assert genesis_block.header.gas_limit == 4750000
2e4aa24
to
1e67fd2
Compare
…account states at PyEVM init-time
@carver - Thanks for the review - I'd like to finish the state overrides also, which I just pushed. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sweet. Any thoughts on how important it is to be able to have different account state in two different accounts in the same genesis?
Like account A has balance 1, and B has balance 2. If that's likely to be important, let's figure out a good API for it now.
cc @voith
eth_tester/backends/pyevm/main.py
Outdated
genesis_params = get_default_genesis_params() | ||
|
||
if genesis_state: | ||
accounts = len(genesis_state) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you like, num_accounts
could be a little clearer as a name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✔️
eth_tester/backends/pyevm/main.py
Outdated
return get_default_genesis_params(overrides=overrides) | ||
|
||
@staticmethod | ||
def generate_genesis_state(overrides=None, accounts=None): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It seems likely to cause confusion to have two methods of the same name (generate_genesis_state
), especially in the same file. Maybe the other method could be called generate_genesis_state_for_keys
.
Also, num_accounts
here would be clearer as a variable name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✔️
eth_tester/backends/pyevm/main.py
Outdated
return generate_genesis_state(account_keys=account_keys, overrides=overrides) | ||
|
||
@classmethod | ||
def from_genesis_overrides(cls, parameter_overrides=None, state_overrides=None, accounts=None): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I count three new APIs that are different ways of accomplishing similar things, maybe four if you count the generate_genesis_state_for_keys
method. That means a lot of new documentation and examples and helping people figure out which one they should use in which situation. Any way we can tighten this up a bit? Ideally there would be one preferred way of setting genesis info.
For example, maybe this method shouldn't exist, and if someone wants to override both parameters and state, then the preferred way would be explicitly, like:
params = PyEVMBackend.generate_genesis_params(overrides=parameter_overrides)
state = PyEVMBackend.generate_genesis_state(overrides=state_overrides, accounts=accounts)
backend = PyEVMBackend(genesis_parameters=params, genesis_state=state)
That would be one less method to document, and one less question for where a new user should start when they want to override genesis info. Sure, it's three lines instead of one, but they decompose nicely, into understanding the other public APIs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Personally, I like the simplicity of a one-liner in this situation - It even reduces confusion in some cases, IMO. I also understand that this does add an opportunity for confusion, and additional maintenance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✔️
Having multiple states specified may indeed be useful. Perhaps if the |
aaadd8f
to
e76eb55
Compare
Cool, how about something like this API... This creates three account duplicate states, with a balance of 10**20: state = PyEVMBackend.generate_genesis_state_duplicates(overrides=dict(balance=10**20), num_accounts=3) This creates 4 accounts, the first with balance 10**20, the second with nonce 2, and two more default accounts: state = PyEVMBackend.generate_genesis_state(override_accounts=(
dict(balance=10**20),
dict(nonce=2),
{},
{},
)) |
tests/backends/test_pyevm.py
Outdated
|
||
from eth_tester import ( | ||
EthereumTester, | ||
PyEVMBackend, | ||
) | ||
from eth_tester.backends.pyevm.main import generate_genesis_state_for_keys, get_default_account_keys, get_default_genesis_params |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can you wrap this line?
too bad that eth-tester
doesn't have isort!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✔️
I can't think of an immediate use case where I'd need different account states. However, having functionality that allows replicating an actual genesis block would be awesome! Although what I would really love to see happen is that there should be an optional way to just provide an account address instead of the backend generating one for you. This helps when you're trying to sign a transaction and you have the private key of any genesis account. Currently, I do this in a very hacky way i.e I keep a mapping of privatekeys and default genessis accounts. |
overall great work @KPrasch! I am really looking forward to use this feature. Currently I have to monkeypatch some eth-tester code, specifically the |
You mean pass in a json-encoded string (the genesis file) to some classmethod, and you get a PyEVMBackend instance with that genesis block? I like it. Maybe that should even be our primary public API, and these PR's methods are the tools we use to implement it. It seems like that API requires the least custom knowledge about eth-tester, since the genesis file format is a (de facto) global standard. |
Yeah, I got this idea from This will solve my account handling problem too. |
Cool, so I'm imagining an API like backend = PyEVMBackend.from_genesis({'gas_limit': ... })
# also accepts the equivalent json-encoded string:
backend = PyEVMBackend.from_genesis('{"gas_limit": ... }') The biggest problem I see with the approach is the way it handles hosted keys. Basically, if you specify a genesis file, then there's no way to also have PyEVMBackend host your keys for you. You would always have to pre-sign the transactions. Some options:
Example of option 2: backend = PyEVMBackend.from_genesis({'gas_limit': ... }, num_hosted_accounts=10)
I'm on the fence about these two. I suppose option 1 is a slight favorite until we have a good reason. It's much easier to expand an API then to go through a deprecation/major-version cycle to remove it. :) |
I'm inclined towards option 1 aswell. I had always imagined this feature this way, but I only read the code in this PR yesterday. Although, this takes away alot of the convenience from the user, @KPrasch If you make this change then I can promise to write the docs. |
This has turned into quite an endeavour! I don't have any specific opinions on the underlying API change beyond simple hope and support, but I do want to echo @voith in pointing out that @KPrasch (a close friend and colleague of mine) has done some nice work here that improves the python ethereum ecosystem in a small but very meaningful way. And also, thanks @voith for committing to writing the docs. Whichever option is selected, the remaining work is non-trivial, so here's hoping for clear thoughts and an easy merge for everyone involved. |
Awesome discussion and support, Thanks All! :-) 🛩️ With regard to creating genesis state mapping for accounts:
I like the Ideas presented above; Here is another Idea of how to handle injecting account states while avoiding usage of an empty dictionary to represent the default state:
This is very sensible, IMO:
Although, I can also imagine an even more dynamic usage of With regard to
|
Great, thanks @voith !
Yes, thanks for the callout. Snapshot & Restore belongs in a new issue, as Piper mentioned. (All of us can aim for more front-loaded discussion about the API this time :)) -- I just made #130 for it.
Totally agreed @jMyles !
Yup, I'm 99% of the way with you. The 1% -- I don't want to rush into a new public API (which has lasting maintenance effects) just because we want to merge in this contribution. @KPrasch How about prefixing the
|
No Rush, although I do think this may be well suited as multiple PRs!
Yes - mostly easy, except for the call in |
9b801dc
to
482fa29
Compare
Thank You! |
@KPrasch @jMyles Sorry for the silence. This got lost in the countless emails I get daily.
Agreed! I didn't have anything against this PR. Its just that while discussing, we came up with a different solution which solve a lot of use cases. Just didn't want the underlying API to be changed later, which would mean backwards incompatgibilty in the future. And great that @carver merged this PR because I'm not sure how soon any of us will implement the newly discussed API. I'll just wait for ethereum/py-evm#1299 to land. |
What was wrong?
The genesis parameters, including the block gas limit is hardcoded, indicated by Issue #88.
How was it fixed?
Allow an optional dictionary of genesis parameter overrides to be passed into the backend's
__init__
.Only existing default genesis parameters can be overridden, or a
ValueError
will be raised.Cute Animal Picture