-
Notifications
You must be signed in to change notification settings - Fork 645
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
Standard user account #230
Standard user account #230
Conversation
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.
Looks good.
evm/auxiliary/user_account.py
Outdated
# | ||
|
||
@property | ||
def signature(self): |
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.
what do you think about renaming this to vrs
instead of signature
? I think that naming convention would match better with other portions of both this codebase and others (web3.py and eth-keys).
With the conventions we've been establishing, I would expect this to return a eth_keys.Signature
object.
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.
Do you prefer renaming it to vrs
or making it return a Signature
object? I have no preference in that regard.
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 vrs
is my preference since this is a low-level API
evm/auxiliary/user_account.py
Outdated
self.data = self.data[:VALUE_SLICE.start] + b + self.data[VALUE_SLICE.stop:] | ||
|
||
@property | ||
def receiver(self): |
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.
This fragments the terminology that we use for the destination of a transaction. Elsewhere we're using to
. Any thoughts on whether it'd be better to conform to this naming convention?
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.
Not sure... We used to have to
and sender
. Now we have target
, sender
and receiver
.
to
andtarget
refer to more or less the same thing I guess (the first account that is called)sender
changes meaning: it used to be the one who signed the transaction, now it's the "owner" of thetarget
account (and just happens to put a signature in thedata
field)receiver
is the second hop (the account that's called by thetarget
account)
So from a technical standpoint, I don't see a lot of overlap between original and new terms (sender
and receiver
are new concepts, and don't live at the protocol level at all). I could imagine renaming target
to to
for consistency (although I'd prefer target
in a vacuum). Renaming receiver
to to
could be misleading on the protocol level, but would make sense from a high level point of view in most cases. I don't see a good alternative to sender
.
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.
Good explanation. I think you are right to leave this as-is and not change the names from target/sender/receiver
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 the name destination
better capture the concept that receiver
currently represents?
evm/logic/context.py
Outdated
|
||
|
||
def sighash(computation): | ||
pass |
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.
thoughts on changing this to a raise NotImplementedError
?
evm/logic/system.py
Outdated
|
||
|
||
def paygas(computation): | ||
pass |
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.
thoughts on changing this to a raise NotImplementedError
?
evm/vm/forks/sharding/opcodes.py
Outdated
mnemonic=mnemonics.PAYGAS, | ||
gas_cost=0, | ||
), | ||
|
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.
whitespace
|
||
def test_valid_transfer(chain, tx1): | ||
vm = chain.get_vm() | ||
vm.apply_transaction(tx1) |
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.
Are there any assertions that we could make here?
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.
Definitely. That's one of the reasons why it's still WiP (this test doesn't even pass yet)
evm/auxiliary/user_account.py
Outdated
|
||
|
||
USER_ACCOUNT_CODE = decode_hex( | ||
"0x3f595260805903608020600052602060006080600060006001610bb8f1506001600051" |
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.
Will you add a comment referencing the source code that produced this bytecode and how it can be reproduced/verified.
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.
Yes, this is not much more than a placeholder for now. Ideally I'd like to put the source code there directly and compile it in a test for comparison, but this will probably not work because Viper needs Python 3.6.
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.
#206 has the same issue, it's better not import Viper even in Python 3.6.
A (little complicated) suggestion of verifying Viper code:
- A command for compiling Viper source code to bytecode.
- If someone want to update the code, they have to upload
source code
andbytecode
(can be in separate file or assigned to a global variable in this case) - Install Viper in CI testing.
- Write a test for auto-compiling the source code and then comparing the compiled bytecode with
evm.auxiliary.user_account.USER_ACCOUNT_CODE
.
By the way, @pipermerriam what do you think about upgrading to Python 3.6?
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.
Perhaps the cleanest solution would be to put the code plus tests in a separate repository. The repository would contain packages with the bytecode for different languages that can be imported into client implementations such as Py-EVM.
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.
By the way, @pipermerriam what do you think about upgrading to Python 3.6?
I'm in favor: ethereum/py-trie#18
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 indifferent to the actual solution for the bytecode creation as long as it's documented and independently verifiable.
I've given up on testing the contract code for now, as it's not really possible at the moment. I'll come back to it once #238 is done, but for now I'd like to put this PR on hold. |
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.
Would you mind taking a minute to flesh out the main description for this pull request.
I think that a large part of this diff will go away once this is rebased. |
Updated the description. Sorry for the current state of the PR. Now that #238 is merged I'm back at writing tests/debugging the contract. In the meantime I've made a couple of changes in the contract. I'm going to push something later today. |
91d225f
to
095b7c1
Compare
Sighash is now merged so this can be rebased (as I assume it needs sighash in it to work) |
eh, should have said sighash is ready for merge :P |
55215ce
to
f5d854a
Compare
Merged the sighash PR and rebased. This should be ready for review now. I'm not particularly happy with some names in this one, especially Test-wise I'd like to see tests that make sure that everything works as expected even if the gas runs out at different points during the execution of the contract. But this can only be done once PAYGAS is properly implemented. |
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.
Just a few suggestions for cleanup/readability/opinionated-code-style
EMPTY_DATA = b'\x00' * DESTINATION_SLICE.stop | ||
|
||
|
||
class ForwardingTransaction(ShardingTransaction): |
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.
Other name ideas
StandardAccountTransaction
ProxyTransaction
EIP86Transaction
(or whatever the appropriate EIP number is)
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.
StandardAccountTransaction
was the name before I switched to ForwardingTransaction
. I dismissed it because I'm not sure if "standard account" is a good term either. It should probably be rather called "standard user account", and even that assumes that this contract will actually become "the" standard. But StandardUserAccountTransaction
strikes me as too long.
General note on EIPs: As far as I know there are no EIPs for the sharding protocol itself. At least at the moment, I don't know if that will change.
self.sig_hash, | ||
]) | ||
|
||
def sign(self, private_key): |
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.
Was it intentional to deviate from the existing API for transaction signing which uses as_signed_transaction
which returns a new object rather than mutating the existing object?
This question is from a broader question about the use of property setters.
- if the use of property setters was to embed validation as part of setting the value, then I would suggest changing to use a single
validate()
method which is inline with the other transaction classes. - if the use of property setters was to allow mutation of the object, I would like to understand why since we've trended away from most forms of object mutation.
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 added the setters to allow mutation. The other transaction objects are technically mutable as well and I wanted to mirror that. But if they aren't mutated in practice I'll be happy to remove them because all these setters look quite ugly. I'll also try to implement the as_signed_transaction
pattern.
"code": b"", | ||
} | ||
|
||
DEFAULT_TX_PARAMS = {**DEFAULT_BASE_TX_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.
This method of dictionary merging has always bugged me, mostly because it takes advantage of the implementation rather than using formal semantics or which keys take precedence. I admit it could be argued that the way key precedence is handled in this case is part of the formal semantics for how this works, but either way, it's always felt a bit like magic.
What do you think about making use of something like the following. (If you haven't figured it out by now, I have great love for the toolz/cytoolz
library)
from cytoolz import (
merge,
dissoc,
)
DEFAUT_TX_PARAMS = merge(
dissoc(DEFAULT_BASE_TX_PARAMS, 'code'),
{...<the overrides>},
}
Zero mutation and explicit about dictionary merging.
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 don't prefer one way over the other, so I don't mind adopting this style.
(v, r, s - 1), | ||
(v, r, 0), | ||
|
||
(27 if v == 28 else 28, r, SECPK1_N - s) |
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 we parametrize these using pytest.mark.parametrize
so that they each run in isolation?
|
||
logs = computation.get_log_entries() | ||
assert len(logs) == 1 | ||
assert big_endian_to_int(logs[0][-1]) > 900 * 1000 # some gas will have been consumed earlier |
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.
A slightly easier to understand version of this assertion that I've used elsewhere is to assert that the delta is smaller than a certain value.
gas_used = big_endian_to_int(logs[0][-1])
assert transaction.gas - gas_used < 1000
block_number=10, | ||
gas_limit=3141592, | ||
timestamp=1422494849, | ||
parent_hash=decode_hex("0000000000000000000000000000000000000000000000000000000000000000"), |
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.
You can use evm.constants.ZERO_HASH32
.
gas=gas, | ||
gas_price=gas_price, | ||
access_list=access_list, | ||
code=b'', | ||
) | ||
|
||
def create_unsigned_transaction(self): |
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.
To stick with the existing transaction API this should be a classmethod
. This API exists to reduce the cumbersome import of both the signed and unsigned classes, allowing you to create unsigned transactions from the main transaction class. I'd be fine with this just being an *args, **kwargs
passthrough to reduce size since there are 10+ arguments.
s=s, | ||
) | ||
# don't detect invalidity before transaction execution | ||
transaction.validate = lambda: 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 a more appropriate way to check this would be to assemble a Message
and then to run vm.get_computation(message).apply_message()
.
This should execute the transaction below the place where the call to transaction.validate()
occurs, allowing you to directly test that the EVM code appropriately validates the signature without having to do any monkeypatching of the transaction object.
I was checking with @vbuterin about renaming some parameters and also mentioned about
|
f7897d9
to
35476e8
Compare
Addressed comments and rebased. @vbuterin Do you have time to review the contract code? https://github.com/ethereum/py-evm/pull/230/files#diff-05178e170e27782a10a1498ec2aefdef |
['if', | ||
['eq', | ||
['calldataload', CALLDATA_FUNCTION_ID], | ||
NONCE_GETTER_ID], |
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.
Shouldn't NONCE_GETTER_ID be multiplied by 2^224? It's the first four bytes that are the function sig, so getting the first 32 bytes would return that with 28 zero bytes at the end.
BTW are there tests for all of this? If so, the tests should catch this.
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.
You're right, will fix this.
Tests are here: https://github.com/jannikluhn/py-evm/blob/07587ac50734c62a7d8862c072fbcaeedaa93c97/tests/auxiliary/user-account/test_contract.py
They cheat a bit by importing the function ids, that's why they didn't catch this. Will add a test comparing against a manually generated one (using the Vyper code).
['calldataload', CALLDATA_FUNCTION_ID], | ||
VALIDATION_CODE_GETTER_ID, | ||
# return validation code address | ||
['mstore', 0, validation_code_address], |
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.
Now that I think about it, what would the validation code address be? We don't want to create two contracts per account. I would actually recommend just not bothering with this get_validation_code
feature at this point to keep it simpler.
# output data (discarded) | ||
0, | ||
0 | ||
]], |
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.
Let's not do things this way. This would require calling 2 accounts to send from this account, which would blow up witness size by a factor of 2. IMO just do ecrecover here.
['sstore', STORAGE_NONCE, ['add', ['calldataload', CALLDATA_NONCE], 1]], | ||
|
||
# Assert that we won't run out of gas from here on | ||
['assert', ['gt', 'gas', GAS_RESERVE + GAS_RESERVE_OFFSET]], |
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.
Is this line still necessary?
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.
Depends: If we run out of gas after the PAYGAS call, the transaction can be replayed infinitely and the account would get drained. This line makes sure that this can never happen. If we trust the user to never create transactions with not enough gas, the check isn't necessary. But at least I would feel much safer with it.
Anything else or can we merge? |
I'm also in favor of renaming |
f6f8429
to
2d305bf
Compare
Rebased. Is this |
VALIDATION_CODE_GAS = 3500 | ||
GAS_RESERVE = 4500 # amount of gas reserved for returning | ||
GAS_RESERVE_OFFSET = 200 | ||
NONCE_GETTER_ID = 0x141b5b48 |
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.
maybe add a comment to describe
# NONCE_GETTER_ID = big_endian_to_int(keccak(b"get_nonce()")[:4])
?
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.
Good to merge (when tests are green)
They are green now 🎉 |
The standard user account is a contract parametrized by an address that
It also has additional methods to
Once the PR is finished it should contain
References: