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

Implement EIP-1702 Generalized Account Versioning Scheme #5640

Merged
merged 14 commits into from
Jul 10, 2019

Conversation

gumb0
Copy link
Member

@gumb0 gumb0 commented Jun 26, 2019

@gumb0 gumb0 added this to the Istanbul milestone Jun 26, 2019
@gumb0 gumb0 changed the title Implement EIP-1702 Generalized Account Versioning Scheme in LegacyVM Implement EIP-1702 Generalized Account Versioning Scheme Jun 26, 2019
@codecov-io
Copy link

codecov-io commented Jun 26, 2019

Codecov Report

Merging #5640 into master will increase coverage by 0.21%.
The diff coverage is 95.43%.

Impacted file tree graph

@@            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

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

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.

Copy link
Member Author

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

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.

Copy link
Member Author

Choose a reason for hiding this comment

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

m_hasNewCode = true;
m_codeHash = sha3(m_codeCache);
auto const newHash = sha3(_code);
if (newHash != m_codeHash)
Copy link
Member Author

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.

Copy link
Member

Choose a reason for hiding this comment

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

Separate PR?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah can be separate

@@ -146,10 +147,26 @@ static const EVMSchedule ConstantinopleFixSchedule = [] {
return schedule;
}();

static const EVMSchedule IstanbulSchedule = [] {
EVMSchedule schedule = ConstantinopleFixSchedule;
schedule.version = 1;
Copy link
Member Author

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

@gumb0
Copy link
Member Author

gumb0 commented Jul 2, 2019

I think this is ready for review. I've also added some relevant unit tests for Executive, which we never had before.

@gumb0 gumb0 removed the in progress label Jul 2, 2019
@gumb0 gumb0 requested review from chfast and halfalicious July 2, 2019 14:41
Copy link
Contributor

@halfalicious halfalicious left a 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 @@
/*
Copy link
Contributor

@halfalicious halfalicious Jul 4, 2019

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?

@@ -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;
Copy link
Contributor

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?

Copy link
Member Author

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.

Copy link
Contributor

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)
Copy link
Contributor

@halfalicious halfalicious Jul 4, 2019

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?

Copy link
Member Author

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.

Copy link
Contributor

@halfalicious halfalicious Jul 5, 2019

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?

Copy link
Member Author

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.

@halfalicious
Copy link
Contributor

halfalicious commented Jul 4, 2019

Should we also reset the version in Account::kill:

void kill()
{
m_isAlive = false;
m_storageOverlay.clear();
m_storageOriginal.clear();
m_codeHash = EmptySHA3;
m_storageRoot = EmptyTrie;
m_balance = 0;
m_nonce = 0;
changed();
}

@halfalicious
Copy link
Contributor

halfalicious commented Jul 4, 2019

Where do we retrieve the code version during syncing pass it to the VM?

[Edit] Never mind, I see it in Executive::call

@gumb0
Copy link
Member Author

gumb0 commented Jul 4, 2019

@halfalicious Good catch about Account::kill. Doesn't look like it would affect consensus, because killed accounts are not executed / are removed from DB anyway, but it's better to set version to 0 there, too, I think.

aleth-vm/main.cpp Outdated Show resolved Hide resolved

#include "../../GenesisInfo.h"

static std::string const c_genesisInfoIstanbulTransitionTest = std::string() +
Copy link
Member

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?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes

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

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?

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok

libethcore/EVMSchedule.h Show resolved Hide resolved
m_hasNewCode = true;
m_codeHash = sha3(m_codeCache);
auto const newHash = sha3(_code);
if (newHash != m_codeHash)
Copy link
Member

Choose a reason for hiding this comment

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

Separate PR?

libethereum/Account.cpp Show resolved Hide resolved
if (_blockNumber < m_sealEngine.chainParams().istanbulForkBlock)
return m_sealEngine.evmSchedule(_blockNumber);
else
return evmScheduleForAccountVersion(_version);
Copy link
Member

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?

Copy link
Member Author

@gumb0 gumb0 Jul 5, 2019

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.

Copy link
Member

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.

Copy link
Member Author

@gumb0 gumb0 Jul 8, 2019

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)?

@@ -67,6 +67,8 @@ enum class Network
ConstantinopleNoProofTest = 82,
/// Byzantium + Constantinople + ConstantinopleFix active from block 0
ConstantinopleFixTest = 83,
/// ConstantinopleFixTest + Istanbul active from block 2
Copy link
Contributor

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?

Copy link
Member Author

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.

@@ -77,6 +78,8 @@ std::string const& dev::eth::genesisInfo(Network _n)
return c_genesisInfoExperimentalTransitionTest;
case Network::ConstantinopleFixTest:
return c_genesisInfoConstantinopleFixTest;
case Network::IstanbulTransitionTest:
Copy link
Contributor

@halfalicious halfalicious Jul 5, 2019

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?

Copy link
Member Author

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)

@@ -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;
Copy link
Contributor

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?

Copy link
Member Author

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.

@gumb0 gumb0 removed the in progress label Jul 8, 2019
@gumb0
Copy link
Member Author

gumb0 commented Jul 8, 2019

Rebased and issues addressed.

@halfalicious
Copy link
Contributor

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

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?

Copy link
Member

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.

Copy link
Member Author

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)

// 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),
Copy link
Contributor

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

{
// 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);
Copy link
Contributor

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

Copy link
Member

@chfast chfast left a 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);
Copy link
Member

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.

@gumb0
Copy link
Member Author

gumb0 commented Jul 9, 2019

@halfalicious Please approve or dismiss your review

@halfalicious halfalicious self-requested a review July 9, 2019 16:50
@halfalicious halfalicious dismissed their stale review July 9, 2019 16:51

Not familiar enough with the area

@halfalicious
Copy link
Contributor

@halfalicious Please approve or dismiss your review

@gumb0 : Done!

@gumb0
Copy link
Member Author

gumb0 commented Jul 9, 2019

Rebased.

@gumb0 gumb0 merged commit d169557 into master Jul 10, 2019
@gumb0 gumb0 deleted the account-versioning branch July 10, 2019 09:16
@gumb0 gumb0 mentioned this pull request Aug 7, 2019
18 tasks
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.

Implement Generalized Account Versioning
4 participants