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
Merged
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
- Added: [#5588](https://github.com/ethereum/aleth/pull/5588) Testeth prints similar test suite name suggestions, when the name passed in `-t` argument is not found.
- Added: [#5593](https://github.com/ethereum/aleth/pull/5593) Dynamically updating host ENR.
- Added: [#5624](https://github.com/ethereum/aleth/pull/5624) Remove useless peers from peer list.
- Added: [#5634](https://github.com/ethereum/aleth/pull/5634) Bootnodes for Rinkeby and Goerli
- Added: [#5634](https://github.com/ethereum/aleth/pull/5634) Bootnodes for Rinkeby and Goerli.
- Added: [#5640](https://github.com/ethereum/aleth/pull/5640) Istanbul support: EIP-1702 Generalized Account Versioning Scheme.
- Changed: [#5532](https://github.com/ethereum/aleth/pull/5532) The leveldb is upgraded to 1.22. This is breaking change on Windows and the old databases are not compatible.
- Changed: [#5559](https://github.com/ethereum/aleth/pull/5559) Update peer validation error messages.
- Changed: [#5568](https://github.com/ethereum/aleth/pull/5568) Improve rlpx handshake log messages and create new rlpx log channel.
Expand Down
10 changes: 6 additions & 4 deletions aleth-vm/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -259,13 +259,18 @@ int main(int argc, char** argv)
} // Ignore decoding errors.
}

unique_ptr<SealEngineFace> se(ChainParams(genesisInfo(networkName)).createSealEngine());
LastBlockHashes lastBlockHashes;
EnvInfo const envInfo(blockHeader, lastBlockHashes, 0);

Transaction t;
Address contractDestination("1122334455667788991011121314151617181920");
if (!code.empty())
{
// Deploy the code on some fake account to be called later.
Account account(0, 0);
account.setCode(bytes{code});
auto const latestVersion = se->evmSchedule(envInfo.number()).accountVersion;
account.setCode(bytes{code}, latestVersion);
std::unordered_map<Address, Account> map;
map[contractDestination] = account;
state.populateFrom(map);
Expand All @@ -278,9 +283,6 @@ int main(int argc, char** argv)

state.addBalance(sender, value);

unique_ptr<SealEngineFace> se(ChainParams(genesisInfo(networkName)).createSealEngine());
LastBlockHashes lastBlockHashes;
EnvInfo const envInfo(blockHeader, lastBlockHashes, 0);
Executive executive(state, envInfo, *se);
ExecutionResult res;
executive.setResultRecipient(res);
Expand Down
2 changes: 2 additions & 0 deletions libethcore/ChainOperationParams.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ EVMSchedule const& ChainOperationParams::scheduleForBlockNumber(u256 const& _blo
{
if (_blockNumber >= experimentalForkBlock)
return ExperimentalSchedule;
else if (_blockNumber >= istanbulForkBlock)
return IstanbulSchedule;
else if (_blockNumber >= constantinopleFixForkBlock)
return ConstantinopleFixSchedule;
else if (_blockNumber >= constantinopleForkBlock)
Expand Down
24 changes: 23 additions & 1 deletion libethcore/EVMSchedule.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 accountVersion = 0;
bool exceptionalFailedCodeDeposit = true;
bool haveDelegateCall = true;
bool eip150Mode = false;
Expand Down Expand Up @@ -146,10 +147,31 @@ static const EVMSchedule ConstantinopleFixSchedule = [] {
return schedule;
}();

static const EVMSchedule IstanbulSchedule = [] {
EVMSchedule schedule = ConstantinopleFixSchedule;
schedule.accountVersion = 1;
return schedule;
}();

static const EVMSchedule ExperimentalSchedule = [] {
EVMSchedule schedule = ConstantinopleSchedule;
EVMSchedule schedule = IstanbulSchedule;
schedule.blockhashGas = 800;
return schedule;
}();

inline EVMSchedule const& latestScheduleForAccountVersion(u256 const& _version)
{
if (_version == 0)
return ConstantinopleFixSchedule;
else if (_version == IstanbulSchedule.accountVersion)
return IstanbulSchedule;
else
{
// This should not happen, as all existing accounts
// are created either with version 0 or with one of fork's versions
assert(false);
return DefaultSchedule;
chfast marked this conversation as resolved.
Show resolved Hide resolved
}
}
}
}
9 changes: 6 additions & 3 deletions libethereum/Account.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ 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.

{
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

Expand All @@ -43,13 +43,16 @@ void Account::setCode(bytes&& _code)
m_hasNewCode = true;
m_codeHash = newHash;
}
m_version = _version;
}

void Account::resetCode()
{
m_codeCache.clear();
m_hasNewCode = false;
m_codeHash = EmptySHA3;
// Reset the version, as it was set together with code
m_version = 0;
chfast marked this conversation as resolved.
Show resolved Hide resolved
}

u256 Account::originalStorageValue(u256 const& _key, OverlayDB const& _db) const
Expand Down Expand Up @@ -166,7 +169,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.

}
else
cerr << "Error importing code of account " << a
Expand All @@ -186,7 +189,7 @@ AccountMap dev::eth::jsonToAccountMap(std::string const& _json, u256 const& _def
if (code.empty())
cerr << "Error importing code of account " << a << "! Code file "
<< codePath << " empty or does not exist.\n";
ret[a].setCode(std::move(code));
ret[a].setCode(std::move(code), 0);
}
else
cerr << "Error importing code of account " << a
Expand Down
26 changes: 21 additions & 5 deletions libethereum/Account.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,23 @@ class Account
/// Construct a dead Account.
Account() {}

/// Construct an alive Account, with given endowment, for either a normal (non-contract) account or for a
/// contract account in the
/// conception phase, where the code is not yet known.
/// Construct an alive Account, with given endowment, for either a normal (non-contract) account
/// or for a contract account in the conception phase, where the code is not yet known.
Account(u256 _nonce, u256 _balance, Changedness _c = Changed): m_isAlive(true), m_isUnchanged(_c == Unchanged), m_nonce(_nonce), m_balance(_balance) {}

/// Explicit constructor for wierd cases of construction or a contract account.
Account(u256 _nonce, u256 _balance, h256 _contractRoot, h256 _codeHash, Changedness _c): m_isAlive(true), m_isUnchanged(_c == Unchanged), m_nonce(_nonce), m_balance(_balance), m_storageRoot(_contractRoot), m_codeHash(_codeHash) { assert(_contractRoot); }
Account(u256 const& _nonce, u256 const& _balance, h256 const& _contractRoot,
h256 const& _codeHash, u256 const& _version, Changedness _c)
: m_isAlive(true),
m_isUnchanged(_c == Unchanged),
m_nonce(_nonce),
m_balance(_balance),
m_storageRoot(_contractRoot),
m_codeHash(_codeHash),
m_version(_version)
{
assert(_contractRoot);
}


/// Kill this account. Useful for the suicide opcode. Following this call, isAlive() returns
Expand All @@ -88,6 +98,7 @@ class Account
m_storageRoot = EmptyTrie;
m_balance = 0;
m_nonce = 0;
m_version = 0;
changed();
}

Expand Down Expand Up @@ -171,7 +182,7 @@ class Account
bool hasNewCode() const { return m_hasNewCode; }

/// Sets the code of the account. Used by "create" messages.
void setCode(bytes&& _code);
void setCode(bytes&& _code, u256 const& _version);

/// Reset the code set by previous setCode
void resetCode();
Expand All @@ -183,6 +194,8 @@ class Account
/// @returns the account's code.
bytes const& code() const { return m_codeCache; }

u256 version() const { return m_version; }

private:
/// Note that we've altered the account.
void changed() { m_isUnchanged = false; }
Expand Down Expand Up @@ -214,6 +227,9 @@ class Account
*/
h256 m_codeHash = EmptySHA3;

/// Account's version
u256 m_version = 0;

/// The map with is overlaid onto whatever storage is implied by the m_storageRoot in the trie.
mutable std::unordered_map<u256, u256> m_storageOverlay;

Expand Down
3 changes: 2 additions & 1 deletion libethereum/Block.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -683,7 +683,8 @@ void Block::updateBlockhashContract()
if (blockNumber == forkBlock)
{
m_state.createContract(c_blockhashContractAddress);
m_state.setCode(c_blockhashContractAddress, bytes(c_blockhashContractCode));
m_state.setCode(c_blockhashContractAddress, bytes(c_blockhashContractCode),
m_sealEngine->evmSchedule(blockNumber).accountVersion);
m_state.commit(State::CommitBehaviour::KeepEmptyAccounts);
}

Expand Down
36 changes: 28 additions & 8 deletions libethereum/Executive.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -342,9 +342,11 @@ bool Executive::call(CallParameters const& _p, u256 const& _gasPrice, Address co
{
bytes const& c = m_s.code(_p.codeAddress);
h256 codeHash = m_s.codeHash(_p.codeAddress);
// Contract will be executed with the version stored in account
auto const version = m_s.version(_p.codeAddress);
m_ext = make_shared<ExtVM>(m_s, m_envInfo, m_sealEngine, _p.receiveAddress,
_p.senderAddress, _origin, _p.apparentValue, _gasPrice, _p.data, &c, codeHash,
m_depth, false, _p.staticCall);
version, m_depth, false, _p.staticCall);
}
}

Expand All @@ -355,24 +357,38 @@ bool Executive::call(CallParameters const& _p, u256 const& _gasPrice, Address co

bool Executive::create(Address const& _txSender, u256 const& _endowment, u256 const& _gasPrice, u256 const& _gas, bytesConstRef _init, Address const& _origin)
{
// Contract creation by an external account is the same as CREATE opcode
return createOpcode(_txSender, _endowment, _gasPrice, _gas, _init, _origin);
// Contract will be created with the version corresponding to latest hard fork
auto const latestVersion = m_sealEngine.evmSchedule(m_envInfo.number()).accountVersion;
return createWithAddressFromNonceAndSender(
_txSender, _endowment, _gasPrice, _gas, _init, _origin, latestVersion);
}

bool Executive::createOpcode(Address const& _sender, u256 const& _endowment, u256 const& _gasPrice, u256 const& _gas, bytesConstRef _init, Address const& _origin)
{
// Contract will be created with the version equal to parent's version
return createWithAddressFromNonceAndSender(
_sender, _endowment, _gasPrice, _gas, _init, _origin, m_s.version(_sender));
}

bool Executive::createWithAddressFromNonceAndSender(Address const& _sender, u256 const& _endowment,
u256 const& _gasPrice, u256 const& _gas, bytesConstRef _init, Address const& _origin,
u256 const& _version)
{
u256 nonce = m_s.getNonce(_sender);
m_newAddress = right160(sha3(rlpList(_sender, nonce)));
return executeCreate(_sender, _endowment, _gasPrice, _gas, _init, _origin);
return executeCreate(_sender, _endowment, _gasPrice, _gas, _init, _origin, _version);
}

bool Executive::create2Opcode(Address const& _sender, u256 const& _endowment, u256 const& _gasPrice, u256 const& _gas, bytesConstRef _init, Address const& _origin, u256 const& _salt)
{
m_newAddress = right160(sha3(bytes{0xff} +_sender.asBytes() + toBigEndian(_salt) + sha3(_init)));
return executeCreate(_sender, _endowment, _gasPrice, _gas, _init, _origin);
// Contract will be created with the version equal to parent's version
return executeCreate(
_sender, _endowment, _gasPrice, _gas, _init, _origin, m_s.version(_sender));
}

bool Executive::executeCreate(Address const& _sender, u256 const& _endowment, u256 const& _gasPrice, u256 const& _gas, bytesConstRef _init, Address const& _origin)
bool Executive::executeCreate(Address const& _sender, u256 const& _endowment, u256 const& _gasPrice,
u256 const& _gas, bytesConstRef _init, Address const& _origin, u256 const& _version)
{
if (_sender != MaxAddress ||
m_envInfo.number() < m_sealEngine.chainParams().experimentalForkBlock) // EIP86
Expand Down Expand Up @@ -411,7 +427,11 @@ bool Executive::executeCreate(Address const& _sender, u256 const& _endowment, u2
// Schedule _init execution if not empty.
if (!_init.empty())
m_ext = make_shared<ExtVM>(m_s, m_envInfo, m_sealEngine, m_newAddress, _sender, _origin,
_endowment, _gasPrice, bytesConstRef(), _init, sha3(_init), m_depth, true, false);
_endowment, _gasPrice, bytesConstRef(), _init, sha3(_init), _version, m_depth, true,
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)


return !m_ext;
}
Expand Down Expand Up @@ -476,7 +496,7 @@ bool Executive::go(OnOpFunc const& _onOp)
}
if (m_res)
m_res->output = out.toVector(); // copy output to execution result
m_s.setCode(m_ext->myAddress, out.toVector());
m_s.setCode(m_ext->myAddress, out.toVector(), m_ext->version);
}
else
m_output = vm->exec(m_gas, *m_ext, _onOp);
Expand Down
10 changes: 9 additions & 1 deletion libethereum/Executive.h
Original file line number Diff line number Diff line change
Expand Up @@ -186,9 +186,17 @@ class Executive
/// Revert all changes made to the state by this execution.
void revert();

/// Used only in tests
ExtVM const& extVM() const { return *m_ext; }

private:
/// @returns false iff go() must be called (and thus a VM execution in required).
bool executeCreate(Address const& _txSender, u256 const& _endowment, u256 const& _gasPrice, u256 const& _gas, bytesConstRef _code, Address const& _originAddress);
bool createWithAddressFromNonceAndSender(Address const& _sender, u256 const& _endowment,
u256 const& _gasPrice, u256 const& _gas, bytesConstRef _init, Address const& _origin,
u256 const& _version);
/// @returns false iff go() must be called (and thus a VM execution in required).
bool executeCreate(Address const& _txSender, u256 const& _endowment, u256 const& _gasPrice,
u256 const& _gas, bytesConstRef _code, Address const& _originAddress, u256 const& _version);

State& m_s; ///< The state to which this operation/transaction is applied.
// TODO: consider changign to EnvInfo const& to avoid LastHashes copy at every CALL/CREATE
Expand Down
27 changes: 19 additions & 8 deletions libethereum/ExtVM.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,13 @@ class ExtVM : public ExtVMFace
/// Full constructor.
ExtVM(State& _s, EnvInfo const& _envInfo, SealEngineFace const& _sealEngine, Address _myAddress,
Address _caller, Address _origin, u256 _value, u256 _gasPrice, bytesConstRef _data,
bytesConstRef _code, h256 const& _codeHash, unsigned _depth, bool _isCreate,
bool _staticCall)
bytesConstRef _code, h256 const& _codeHash, u256 const& _version, unsigned _depth,
bool _isCreate, bool _staticCall)
: ExtVMFace(_envInfo, _myAddress, _caller, _origin, _value, _gasPrice, _data, _code.toBytes(),
_codeHash, _depth, _isCreate, _staticCall),
_codeHash, _version, _depth, _isCreate, _staticCall),
m_s(_s),
m_sealEngine(_sealEngine)
m_sealEngine(_sealEngine),
m_evmSchedule(initEvmSchedule(envInfo().number(), _version))
{
// Contract: processing account must exist. In case of CALL, the ExtVM
// is created only if an account has code (so exist). In case of CREATE
Expand Down Expand Up @@ -97,19 +98,29 @@ class ExtVM : public ExtVMFace
void suicide(Address _a) final;

/// Return the EVM gas-price schedule for this execution context.
EVMSchedule const& evmSchedule() const final
{
return m_sealEngine.evmSchedule(envInfo().number());
}
EVMSchedule const& evmSchedule() const final { return m_evmSchedule; }

State const& state() const { return m_s; }

/// Hash of a block if within the last 256 blocks, or h256() otherwise.
h256 blockHash(u256 _number) final;

private:
EVMSchedule const& initEvmSchedule(int64_t _blockNumber, u256 const& _version) const
{
// If _version is latest for the block, select corresponding latest schedule.
// Otherwise run with the latest schedule known to correspond to the _version.
EVMSchedule const& currentBlockSchedule = m_sealEngine.evmSchedule(_blockNumber);
if (currentBlockSchedule.accountVersion == _version)
return currentBlockSchedule;
else
return latestScheduleForAccountVersion(_version);
}


State& m_s; ///< A reference to the base state.
SealEngineFace const& m_sealEngine;
EVMSchedule const& m_evmSchedule;
};

}
Expand Down
Loading