-
Notifications
You must be signed in to change notification settings - Fork 2.2k
Implement EIP-1702 Generalized Account Versioning Scheme #5640
Conversation
Codecov Report
@@ Coverage Diff @@
## master #5640 +/- ##
=========================================
+ Coverage 62.79% 63% +0.21%
=========================================
Files 349 350 +1
Lines 29721 29906 +185
Branches 3346 3350 +4
=========================================
+ Hits 18663 18843 +180
- Misses 9845 9846 +1
- Partials 1213 1217 +4 |
libethereum/State.h
Outdated
@@ -136,8 +136,8 @@ struct Change | |||
{} | |||
|
|||
/// Helper constructor especially for new code change log. | |||
Change(Address const& _addr, bytes const& _oldCode): | |||
kind(Code), address(_addr), oldCode(_oldCode) | |||
Change(Address const& _addr, bytes const& _oldCode, u256 const& _oldVersion) |
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 forgot what's the exact reason to save the old code, I'll try to remember, but if it's needed, probably we need to save the version, too, and restore during rollback.
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.
Apparently this is not needed anymore, I think I'll remove saving the old code in #5641, then will rebase this.
@@ -155,7 +156,7 @@ AccountMap dev::eth::jsonToAccountMap(std::string const& _json, u256 const& _def | |||
cerr << "Error importing code of account " << a | |||
<< "! Code needs to be hex bytecode prefixed by \"0x\"."; | |||
else | |||
ret[a].setCode(fromHex(codeStr)); | |||
ret[a].setCode(fromHex(codeStr), 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.
I'll create an issue to handle this properly. We should have the ability to set contract's version in config file.
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.
aeae419
to
23b85b5
Compare
m_hasNewCode = true; | ||
m_codeHash = sha3(m_codeCache); | ||
auto const newHash = sha3(_code); | ||
if (newHash != m_codeHash) |
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 we should have done this check before, too, because without it in case init code returns empty code, setCode({})
raises m_hasNewCode
flag, and this makes commit
function to save into database the pair EmptySHA3 => ""
. This should better not happen.
Here it also helps with the same problem in case of empty init code. In this case setCode
is still called to set the version.
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.
Separate PR?
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.
Yeah can be separate
libethcore/EVMSchedule.h
Outdated
@@ -146,10 +147,26 @@ static const EVMSchedule ConstantinopleFixSchedule = [] { | |||
return schedule; | |||
}(); | |||
|
|||
static const EVMSchedule IstanbulSchedule = [] { | |||
EVMSchedule schedule = ConstantinopleFixSchedule; | |||
schedule.version = 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.
This value is not finalized yet, see ethereum/EIPs#2156
I think this is ready for review. I've also added some relevant unit tests for |
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 add a changelog entry?
@@ -0,0 +1,66 @@ | |||
/* |
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 we use the new license message?
libethcore/EVMSchedule.h
Outdated
@@ -29,6 +29,7 @@ struct EVMSchedule | |||
{ | |||
EVMSchedule(): tierStepGas(std::array<unsigned, 8>{{0, 2, 3, 5, 8, 10, 20, 0}}) {} | |||
EVMSchedule(bool _efcd, bool _hdc, unsigned const& _txCreateGas): exceptionalFailedCodeDeposit(_efcd), haveDelegateCall(_hdc), tierStepGas(std::array<unsigned, 8>{{0, 2, 3, 5, 8, 10, 20, 0}}), txCreateGas(_txCreateGas) {} | |||
int version = 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.
Should this be unsigned since values are only >= 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.
Well in general it's not a good idea to try to enforce non-negative values with unsigned
(because it doesn't really enforce it)
https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines.html#es106-dont-try-to-avoid-negative-values-by-using-unsigned
But now I think that I'll change it since other values in this struct are unsigned
(gas prices), and in other parts of the code I made it u256
Maybe makes sense to make it u256
here as well, but for now unsigned
definitely will work, too.
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 was thinking less of enforcing non-negative values and more of code clarity, i.e. someone reading the code will be able to tell that version should be non-negative.
@@ -34,18 +34,24 @@ using namespace dev::eth::validation; | |||
|
|||
namespace fs = boost::filesystem; | |||
|
|||
void Account::setCode(bytes&& _code) | |||
void Account::setCode(bytes&& _code, u256 const& _version) |
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 related to your changes, but why do we have a setCode function rather than only allowing code to be set in a ctor since from what I understand smart contracts can't be redeployed at the same 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.
I think because contract creation has init code execution phase. First init code (provided in the contract deployment transaction or in CREATE/CREATE2 params) is executed, and it returns in the end another code, which becomes the code of new contract, saved into the state (with setCode
).
So init code execution is performed in the context of the newly created contract, and so this new contract should already exist, but its code is empty until init code is finished.
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.
@gumb0 Ah okay, thanks for the clarification! Still not clear on this:
and it returns in the end another code, which becomes the code of new contract, saved into the state (with setCode).
What is this other code that is returned by init code execution, how does it differ from the code supplied in the contract creation tx?
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.
The code supplied in the contract creation tx (or in CREATE
/CREATE2
instructions) can return any data (via RETURN
instruction). The rules are that the data returned from this code becomes the code of the newly created account.
In other words the code supplied in the contract creation tx is only constructor code (in solidity terms) and it returns the code that becomes the actual contract code.
Should we also reset the version in Lines 92 to 102 in 23b85b5
|
Where do we retrieve the code version during syncing pass it to the VM? [Edit] Never mind, I see it in |
@halfalicious Good catch about |
|
||
#include "../../GenesisInfo.h" | ||
|
||
static std::string const c_genesisInfoIstanbulTransitionTest = std::string() + |
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 add Istanbul configs being a copy of Petersburg in separate PR?
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
libethcore/EVMSchedule.h
Outdated
@@ -29,6 +29,7 @@ struct EVMSchedule | |||
{ | |||
EVMSchedule(): tierStepGas(std::array<unsigned, 8>{{0, 2, 3, 5, 8, 10, 20, 0}}) {} | |||
EVMSchedule(bool _efcd, bool _hdc, unsigned const& _txCreateGas): exceptionalFailedCodeDeposit(_efcd), haveDelegateCall(_hdc), tierStepGas(std::array<unsigned, 8>{{0, 2, 3, 5, 8, 10, 20, 0}}), txCreateGas(_txCreateGas) {} | |||
unsigned version = 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.
Maybe it should be named accountVersion
for precision?
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.
Ok
m_hasNewCode = true; | ||
m_codeHash = sha3(m_codeCache); | ||
auto const newHash = sha3(_code); | ||
if (newHash != m_codeHash) |
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.
Separate PR?
libethereum/ExtVM.h
Outdated
if (_blockNumber < m_sealEngine.chainParams().istanbulForkBlock) | ||
return m_sealEngine.evmSchedule(_blockNumber); | ||
else | ||
return evmScheduleForAccountVersion(_version); |
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.
Wouldn't this work all the time because we have version 0 before Istanbul?
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 really, EvmSchedule
that we need changes each fork before Istanbul, so this function to work correctly would need to return different schedules for version 0 based on block number.
If I understood correctly your question.
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, you are right.
But I think the more future proof strategy is to select schedule group by version number and then select the schedule within the group by the block number. As I understand the EIP-1702 and as I want to be, we will not bump the version on each HF but only if some breaking changes are introduced.
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.
@chfast I see, it makes sense, but also makes the logic of choosing schedule more complicated, as there's no one-to-one relation between schedules and versions.
I changed this just slightly now. It's not exactly as you suggest, but I think more in line with this strategy.
So basically we care about choosing by block number only within the latest version, while all past versions choose the fixed schedule "just before the version bump".
What do you think (see code)?
libethashseal/GenesisInfo.h
Outdated
@@ -67,6 +67,8 @@ enum class Network | |||
ConstantinopleNoProofTest = 82, | |||
/// Byzantium + Constantinople + ConstantinopleFix active from block 0 | |||
ConstantinopleFixTest = 83, | |||
/// ConstantinopleFixTest + Istanbul active from block 2 |
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.
Why is this active from block 2 rather than block 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.
This is a special kind of config for the tests to be able to check the behavior before Istanbul (in block 1) as well as after Istanbul. We have similar configs for other forks, too.
libethashseal/GenesisInfo.cpp
Outdated
@@ -77,6 +78,8 @@ std::string const& dev::eth::genesisInfo(Network _n) | |||
return c_genesisInfoExperimentalTransitionTest; | |||
case Network::ConstantinopleFixTest: | |||
return c_genesisInfoConstantinopleFixTest; | |||
case Network::IstanbulTransitionTest: |
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 we need to add genesis info for Instabul tests as a part of this PR?
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'll split it into separate PR. (note that this is a config only for tests, though)
libethcore/EVMSchedule.h
Outdated
@@ -29,6 +29,7 @@ struct EVMSchedule | |||
{ | |||
EVMSchedule(): tierStepGas(std::array<unsigned, 8>{{0, 2, 3, 5, 8, 10, 20, 0}}) {} | |||
EVMSchedule(bool _efcd, bool _hdc, unsigned const& _txCreateGas): exceptionalFailedCodeDeposit(_efcd), haveDelegateCall(_hdc), tierStepGas(std::array<unsigned, 8>{{0, 2, 3, 5, 8, 10, 20, 0}}), txCreateGas(_txCreateGas) {} | |||
unsigned version = 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.
Is this the best place to store the version? EVMSchedule appears to only contain opcode / gas cost info, is there another place where we store other VM configuration information?
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.
There's also at least block reward value, and some other things. I think it's an ok place, I don't think there is any other currently.
Rebased and issues addressed. |
@gumb0 Feel free to check in once Pawel signs off, I don't know enough about the related areas to be able to sign off on this PR with a sufficient level of confidence |
false); | ||
else | ||
// code stays empty, but we set the version | ||
m_s.setCode(m_newAddress, {}, _version); |
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's the point of setting the version if there's no code?
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 question might be delegated to the EIP.
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 discussed this with EIP author here sorpaas/EIPs#2 (comment)
libethereum/State.cpp
Outdated
// version is 0 if absent from RLP | ||
auto const version = state[4] ? state[4].toInt<u256>() : 0; | ||
|
||
auto i = m_cache.emplace(std::piecewise_construct, std::forward_as_tuple(_addr), |
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.
Don't need std::
prefix since there's using namespace std
libethereum/State.cpp
Outdated
{ | ||
// rollback assumes that overwriting of the code never happens | ||
// (not allowed in contract creation logic in Executive) | ||
assert(!addressHasCode(_address)); | ||
m_changeLog.emplace_back(Change::Code, _address); | ||
m_cache[_address].setCode(std::move(_code)); | ||
m_cache[_address].setCode(std::move(_code), _version); |
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.
Don't need std::
prefix
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.
Ok to me. Check @halfalicious comments.
false); | ||
else | ||
// code stays empty, but we set the version | ||
m_s.setCode(m_newAddress, {}, _version); |
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 question might be delegated to the EIP.
@halfalicious Please approve or dismiss your review |
@gumb0 : Done! |
and some other minor issues raised in review.
…ules corresponding to a single version in the future.
Rebased. |
Closes #5638
https://eips.ethereum.org/EIPS/eip-1702