diff --git a/libethereum/AddressState.h b/libethereum/AddressState.h index bcd644319..8b063c3ac 100644 --- a/libethereum/AddressState.h +++ b/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& memory() { assert(m_type == AddressType::Contract); return m_memory; } - std::map 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& takeMemory() { assert(m_type == AddressType::Contract && haveMemory()); m_contractRoot = h256(); return m_memory; } + std::map 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 and then move to unordered_map. - // Will need to sort on hash construction. + h256 m_contractRoot; + // TODO: change to unordered_map. std::map m_memory; }; diff --git a/libethereum/State.cpp b/libethereum/State.cpp index 1a35bf5c7..d3d01deec 100644 --- a/libethereum/State.cpp +++ b/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(), state[1].toInt()); + else + s = AddressState(state[0].toInt(), state[1].toInt(), state[2].toHash()); + 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 memdb(const_cast(&m_db), it->second.oldRoot()); // promise we won't alter the overlay! :) + map& mem = it->second.takeMemory(); + for (auto const& i: memdb) + mem[i.first] = RLP(i.second).toInt(); + } +} + +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 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(); - 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() + 1)); - else - m_state.insert(_id, rlpList(rlp[0], rlp[1].toInt() + 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() + _amount, rlp[1])); - else - m_state.insert(_id, rlpList(rlp[0].toInt() + _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(); - 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(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(TrieDB(const_cast(&m_db), rlp[2].toHash())[_memory]); -} - -void State::setContractMemory(Address _contract, u256 _memory, u256 _value) -{ - RLP rlp(m_state[_contract]); - TrieDB 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()); - 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 memdb(const_cast(&m_db), it->second.oldRoot()); // promise we won't change the overlay! :) + return RLP(memdb.at(_memory)).toInt(); // 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: diff --git a/libethereum/State.h b/libethereum/State.h index 310c63fef..43ce33825 100644 --- a/libethereum/State.h +++ b/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 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 m_state; ///< Our state tree, as an Overlay DB. std::map m_transactions; ///< The current list of transactions that we've included in the state. + mutable std::map 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. diff --git a/libethereum/TrieDB.h b/libethereum/TrieDB.h index 7f5df7de8..cf333d17c 100644 --- a/libethereum/TrieDB.h +++ b/libethereum/TrieDB.h @@ -348,6 +348,32 @@ public: void insert(KeyType _k, bytesConstRef _value) { GenericTrieDB::insert(bytesConstRef((byte const*)&_k, sizeof(KeyType)), _value); } void insert(KeyType _k, bytes const& _value) { insert(_k, bytesConstRef(&_value)); } void remove(KeyType _k) { GenericTrieDB::remove(bytesConstRef((byte const*)&_k, sizeof(KeyType))); } + + class iterator: public GenericTrieDB::iterator + { + public: + using Super = typename GenericTrieDB::iterator; + using value_type = std::pair; + + 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 diff --git a/test/state.cpp b/test/state.cpp index 744daf0d6..0e2967d77 100644 --- a/test/state.cpp +++ b/test/state.cpp @@ -26,6 +26,8 @@ using namespace eth; int stateTest() { + State s(toPublic(sha3("123"))); + return 0; }