Browse Source

Caching (on two levels!) and quite possibly working State transition

class.
cl-refactor
Gav Wood 11 years ago
parent
commit
231619fb11
  1. 19
      libethereum/AddressState.h
  2. 200
      libethereum/State.cpp
  3. 17
      libethereum/State.h
  4. 26
      libethereum/TrieDB.h
  5. 2
      test/state.cpp

19
libethereum/AddressState.h

@ -29,6 +29,7 @@ namespace eth
enum class AddressType
{
Dead,
Normal,
Contract
};
@ -36,22 +37,30 @@ enum class AddressType
class AddressState
{
public:
AddressState(AddressType _type = AddressType::Normal): m_type(_type), m_balance(0), m_nonce(0) {}
AddressState(): m_type(AddressType::Dead), m_balance(0), m_nonce(0) {}
AddressState(u256 _balance, u256 _nonce): m_type(AddressType::Normal), m_balance(_balance), m_nonce(_nonce) {}
AddressState(u256 _balance, u256 _nonce, h256 _contractRoot): m_type(AddressType::Contract), m_balance(_balance), m_nonce(_nonce), m_contractRoot(_contractRoot) {}
void incNonce() { m_nonce++; }
void addBalance(bigint _i) { m_balance = (u256)((bigint)m_balance + _i); }
void kill() { m_type = AddressType::Dead; m_memory.clear(); m_contractRoot = h256(); m_balance = 0; m_nonce = 0; }
AddressType type() const { return m_type; }
u256& balance() { return m_balance; }
u256 const& balance() const { return m_balance; }
u256& nonce() { return m_nonce; }
u256 const& nonce() const { return m_nonce; }
std::map<u256, u256>& memory() { assert(m_type == AddressType::Contract); return m_memory; }
std::map<u256, u256> const& memory() const { assert(m_type == AddressType::Contract); return m_memory; }
bool haveMemory() const { return m_memory.empty() && m_contractRoot != h256(); } // TODO: best to switch to m_haveMemory flag rather than try to infer.
h256 oldRoot() const { assert(!haveMemory()); return m_contractRoot; }
std::map<u256, u256>& takeMemory() { assert(m_type == AddressType::Contract && haveMemory()); m_contractRoot = h256(); return m_memory; }
std::map<u256, u256> const& memory() const { assert(m_type == AddressType::Contract && haveMemory()); return m_memory; }
private:
AddressType m_type;
u256 m_balance;
u256 m_nonce;
// TODO: std::hash<u256> and then move to unordered_map.
// Will need to sort on hash construction.
h256 m_contractRoot;
// TODO: change to unordered_map.
std::map<u256, u256> m_memory;
};

200
libethereum/State.cpp

@ -60,13 +60,68 @@ State::State(Address _coinbaseAddress): m_state(&m_db), m_ourAddress(_coinbaseAd
ldb::Options o;
ldb::DB* db = nullptr;
ldb::DB::Open(o, "state", &db);
ldb::DB::Open(o, string(getenv("HOME")) + "/.ethereum++/state", &db);
m_db.setDB(db);
m_state.init();
m_state.setRoot(m_currentBlock.stateRoot);
}
void State::ensureCached(Address _a, bool _requireMemory) const
{
auto it = m_cache.find(_a);
if (it == m_cache.end())
{
// populate basic info.
string stateBack = m_state.at(_a);
RLP state(stateBack);
AddressState s;
if (state.itemCount() == 2)
s = AddressState(state[0].toInt<u256>(), state[1].toInt<u256>());
else
s = AddressState(state[0].toInt<u256>(), state[1].toInt<u256>(), state[2].toHash<h256>());
bool ok;
tie(it, ok) = m_cache.insert(make_pair(_a, s));
}
if (_requireMemory && !it->second.haveMemory())
{
// Populate memory.
assert(it->second.type() == AddressType::Contract);
TrieDB<u256, Overlay> memdb(const_cast<Overlay*>(&m_db), it->second.oldRoot()); // promise we won't alter the overlay! :)
map<u256, u256>& mem = it->second.takeMemory();
for (auto const& i: memdb)
mem[i.first] = RLP(i.second).toInt<u256>();
}
}
void State::commit()
{
for (auto const& i: m_cache)
if (i.second.type() == AddressType::Dead)
m_state.remove(i.first);
else
{
RLPStream s;
s << i.second.balance() << i.second.nonce();
if (i.second.type() == AddressType::Contract)
{
if (i.second.haveMemory())
{
TrieDB<u256, Overlay> memdb(&m_db);
memdb.init();
for (auto const& j: i.second.memory())
if (j.second)
memdb.insert(j.first, rlp(j.second)); // TODO: CHECK: check this isn't RLP or compact
s << memdb.root();
}
else
s << i.second.oldRoot();
}
m_state.insert(i.first, &s.out());
}
m_cache.clear();
}
void State::sync(BlockChain const& _bc)
{
sync(_bc, _bc.currentHash());
@ -131,6 +186,7 @@ void State::sync(BlockChain const& _bc, h256 _block)
void State::resetCurrent()
{
m_transactions.clear();
m_cache.clear();
m_currentBlock = BlockInfo();
m_currentBlock.coinbaseAddress = m_ourAddress;
m_currentBlock.stateRoot = m_previousBlock.stateRoot;
@ -214,9 +270,19 @@ u256 State::playback(bytesConstRef _block, BlockInfo const& _grandParent)
}
applyRewards(rewarded);
// Commit all cached state changes to the state trie.
commit();
// Hash the state trie and check against the state_root hash in m_currentBlock.
if (m_currentBlock.stateRoot != rootHash())
{
// Rollback the trie.
m_db.rollback();
throw InvalidStateRoot();
}
// Commit the new trie to disk.
m_db.commit();
m_previousBlock = m_currentBlock;
resetCurrent();
@ -249,6 +315,10 @@ void State::prepareToMine(BlockChain const& _bc)
m_currentBlock.sha3Transactions = sha3(m_currentTxs);
m_currentBlock.sha3Uncles = sha3(m_currentUncles);
// Commit any and all changes to the trie that are in the cache, then update the state root accordingly.
commit();
m_currentBlock.stateRoot = m_state.root();
}
bool State::mine(uint _msTimeout)
@ -277,95 +347,87 @@ bool State::mine(uint _msTimeout)
bool State::isNormalAddress(Address _id) const
{
return RLP(m_state[_id]).itemCount() == 2;
ensureCached(_id);
auto it = m_cache.find(_id);
if (it == m_cache.end())
return false;
return it->second.type() == AddressType::Normal;
}
bool State::isContractAddress(Address _id) const
{
return RLP(m_state[_id]).itemCount() == 3;
ensureCached(_id);
auto it = m_cache.find(_id);
if (it == m_cache.end())
return false;
return it->second.type() == AddressType::Contract;
}
u256 State::balance(Address _id) const
{
RLP rlp(m_state[_id]);
if (rlp.isList())
return rlp[0].toInt<u256>();
else
ensureCached(_id);
auto it = m_cache.find(_id);
if (it == m_cache.end())
return 0;
return it->second.balance();
}
void State::noteSending(Address _id)
{
RLP rlp(m_state[_id]);
if (rlp.isList())
if (rlp.itemCount() == 2)
m_state.insert(_id, rlpList(rlp[0], rlp[1].toInt<u256>() + 1));
else
m_state.insert(_id, rlpList(rlp[0], rlp[1].toInt<u256>() + 1, rlp[2]));
ensureCached(_id);
auto it = m_cache.find(_id);
if (it == m_cache.end())
m_cache[_id] = AddressState(0, 1);
else
m_state.insert(_id, rlpList(0, 1));
it->second.incNonce();
}
void State::addBalance(Address _id, u256 _amount)
{
RLP rlp(m_state[_id]);
if (rlp.isList())
if (rlp.itemCount() == 2)
m_state.insert(_id, rlpList(rlp[0].toInt<u256>() + _amount, rlp[1]));
else
m_state.insert(_id, rlpList(rlp[0].toInt<u256>() + _amount, rlp[1], rlp[2]));
ensureCached(_id);
auto it = m_cache.find(_id);
if (it == m_cache.end())
m_cache[_id] = AddressState(_amount, 0);
else
m_state.insert(_id, rlpList(_amount, 0));
it->second.addBalance(_amount);
}
void State::subBalance(Address _id, bigint _amount)
{
RLP rlp(m_state[_id]);
if (rlp.isList())
{
bigint bal = rlp[0].toInt<u256>();
if (bal < _amount)
throw NotEnoughCash();
bal -= _amount;
if (rlp.itemCount() == 2)
m_state.insert(_id, rlpList(bal, rlp[1]));
else
m_state.insert(_id, rlpList(bal, rlp[1], rlp[2]));
}
else
ensureCached(_id);
auto it = m_cache.find(_id);
if (it == m_cache.end() || (bigint)it->second.balance() < _amount)
throw NotEnoughCash();
else
it->second.addBalance(-_amount);
}
u256 State::transactionsFrom(Address _id) const
{
RLP rlp(m_state[_id]);
if (rlp.isList())
return rlp[0].toInt<u256>(RLP::LaisezFaire);
else
ensureCached(_id);
auto it = m_cache.find(_id);
if (it == m_cache.end())
return 0;
else
return it->second.nonce();
}
u256 State::contractMemory(Address _id, u256 _memory) const
{
RLP rlp(m_state[_id]);
if (rlp.itemCount() != 3)
throw InvalidContractAddress();
return fromBigEndian<u256>(TrieDB<h256, Overlay>(const_cast<Overlay*>(&m_db), rlp[2].toHash<h256>())[_memory]);
}
void State::setContractMemory(Address _contract, u256 _memory, u256 _value)
{
RLP rlp(m_state[_contract]);
TrieDB<h256, Overlay> c(&m_db);
std::string s = toBigEndianString(_value);
if (rlp.itemCount() == 3)
ensureCached(_id);
auto it = m_cache.find(_id);
if (it == m_cache.end() || it->second.type() != AddressType::Contract)
return 0;
else if (it->second.haveMemory())
{
c.setRoot(rlp[2].toHash<h256>());
c.insert(_memory, bytesConstRef(s));
m_state.insert(_contract, rlpList(rlp[0], rlp[1], c.root()));
auto mit = it->second.memory().find(_memory);
if (mit == it->second.memory().end())
return 0;
return mit->second;
}
else
throw InvalidContractAddress();
// Memory not cached - just grab one item from the DB rather than cache the lot.
TrieDB<u256, Overlay> memdb(const_cast<Overlay*>(&m_db), it->second.oldRoot()); // promise we won't change the overlay! :)
return RLP(memdb.at(_memory)).toInt<u256>(); // TODO: CHECK: check if this is actually an RLP decode
}
bool State::execute(bytesConstRef _rlp)
@ -431,16 +493,20 @@ void State::execute(Transaction const& _t, Address _sender)
}
else
{
// Try to make a new contract
if (_t.fee < _t.data.size() * c_memoryFee + c_newContractFee)
throw FeeTooSmall();
Address newAddress = low160(_t.sha3());
if (isContractAddress(newAddress))
if (isContractAddress(newAddress) || isNormalAddress(newAddress))
throw ContractAddressCollision();
// All OK - set it up.
m_cache[newAddress] = AddressState(0, 0, sha3(RLPNull));
auto& mem = m_cache[newAddress].takeMemory();
for (uint i = 0; i < _t.data.size(); ++i)
setContractMemory(newAddress, i, _t.data[i]);
mem[i] = _t.data[i];
subBalance(_sender, _t.value + _t.fee);
addBalance(newAddress, _t.value);
addBalance(m_currentBlock.coinbaseAddress, _t.fee);
@ -449,7 +515,7 @@ void State::execute(Transaction const& _t, Address _sender)
// Convert from a 256-bit integer stack/memory entry into a 160-bit Address hash.
// Currently we just pull out the left (high-order in BE) 160-bits.
// TODO: check that this is correct.
// TODO: CHECK: check that this is correct.
inline Address asAddress(u256 _item)
{
return left160(h256(_item));
@ -465,19 +531,20 @@ void State::execute(Address _myAddress, Address _txSender, u256 _txValue, u256 _
if (stack.size() < _n)
throw StackTooSmall(_n, stack.size());
};
ensureCached(_myAddress, true);
auto& myMemory = m_cache[_myAddress].takeMemory();
auto mem = [&](u256 _n) -> u256
{
return contractMemory(_myAddress, _n);
// auto i = myMemory.find(_n);
// return i == myMemory.end() ? 0 : i->second;
auto i = myMemory.find(_n);
return i == myMemory.end() ? 0 : i->second;
};
auto setMem = [&](u256 _n, u256 _v)
{
setContractMemory(_myAddress, _n, _v);
/* if (_v)
if (_v)
myMemory[_n] = _v;
else
myMemory.erase(_n);*/
myMemory.erase(_n);
};
u256 curPC = 0;
@ -942,10 +1009,9 @@ void State::execute(Address _myAddress, Address _txSender, u256 _txValue, u256 _
{
require(1);
Address dest = asAddress(stack.back());
// TODO: easy once we have the local cache of memory in place.
u256 minusVoidFee = 0;//m_current[_myAddress].memory().size() * c_memoryFee;
u256 minusVoidFee = myMemory.size() * c_memoryFee;
addBalance(dest, balance(_myAddress) + minusVoidFee);
m_state.remove(_myAddress);
m_cache[_myAddress].kill();
// ...follow through to...
}
case Instruction::STOP:

17
libethereum/State.h

@ -103,12 +103,6 @@ public:
/// @returns 0 if no contract exists at that address.
u256 contractMemory(Address _contract, u256 _memory) const;
/// Set the value of a memory position of a contract.
void setContractMemory(Address _contract, u256 _memory, u256 _value);
/// Get the full memory of a contract.
// std::map<u256, u256> contractMemory(Address _contract) const;
/// Note that the given address is sending a transaction and thus increment the associated ticker.
void noteSending(Address _id);
@ -137,6 +131,15 @@ private:
u256 fee;
};
/// Retrieve all information about a given address into the cache.
void ensureCached(Address _a, bool _requireMemory = false) const;
/// Commit all changes waiting in the address cache.
void commit();
/// Commit all changes waiting in the address cache.
void rollback() { m_cache.clear(); }
/// Execute the given block on our previous block. This will set up m_currentBlock first, then call the other playback().
/// Any failure will be critical.
u256 playback(bytesConstRef _block);
@ -159,6 +162,8 @@ private:
TrieDB<Address, Overlay> m_state; ///< Our state tree, as an Overlay DB.
std::map<h256, Transaction> m_transactions; ///< The current list of transactions that we've included in the state.
mutable std::map<Address, AddressState> m_cache; ///< Our address cache. This stores the states of each address that has (or at least might have) been changed.
BlockInfo m_previousBlock; ///< The previous block's information.
BlockInfo m_currentBlock; ///< The current block's information.
bytes m_currentBytes; ///< The current block.

26
libethereum/TrieDB.h

@ -348,6 +348,32 @@ public:
void insert(KeyType _k, bytesConstRef _value) { GenericTrieDB<DB>::insert(bytesConstRef((byte const*)&_k, sizeof(KeyType)), _value); }
void insert(KeyType _k, bytes const& _value) { insert(_k, bytesConstRef(&_value)); }
void remove(KeyType _k) { GenericTrieDB<DB>::remove(bytesConstRef((byte const*)&_k, sizeof(KeyType))); }
class iterator: public GenericTrieDB<DB>::iterator
{
public:
using Super = typename GenericTrieDB<DB>::iterator;
using value_type = std::pair<KeyType, bytesConstRef>;
iterator() {}
iterator(TrieDB const* _db): Super(_db) {}
value_type operator*() const { return at(); }
value_type operator->() const { return at(); }
value_type at() const
{
auto p = Super::at();
value_type ret;
assert(p.first.size() == sizeof(ret));
memcpy(&ret.first, p.first.data(), sizeof(ret));
ret.second = p.second;
return ret;
}
};
iterator begin() const { return this; }
iterator end() const { return iterator(); }
};
template <class KeyType, class DB>

2
test/state.cpp

@ -26,6 +26,8 @@ using namespace eth;
int stateTest()
{
State s(toPublic(sha3("123")));
return 0;
}

Loading…
Cancel
Save