Browse Source

State updater tested and working pretty well.

cl-refactor
Gav Wood 11 years ago
parent
commit
8fb99152ee
  1. 19
      eth/main.cpp
  2. 103
      libethereum/BlockChain.cpp
  3. 42
      libethereum/BlockChain.h
  4. 15
      libethereum/BlockInfo.h
  5. 11
      libethereum/Dagger.cpp
  6. 4
      libethereum/Dagger.h
  7. 4
      libethereum/RLP.h
  8. 92
      libethereum/State.cpp
  9. 45
      libethereum/State.h
  10. 7
      libethereum/TrieDB.h
  11. 8
      test/main.cpp
  12. 41
      test/state.cpp

19
eth/main.cpp

@ -32,9 +32,10 @@ int main()
h256 privkey = sha3("123");
Address us = toAddress(privkey); // TODO: should be loaded from config file/set at command-line.
BlockChain bc; // Maintains block database.
TransactionQueue tq; // Maintains list of incoming transactions not yet on the block chain.
State s(us);
BlockChain bc; // Maintains block database.
TransactionQueue tq; // Maintains list of incoming transactions not yet on the block chain.
Overlay stateDB = State::openDB(); // Acts as the central point for the state database, so multiple States can share it.
State s(us, stateDB);
// Synchronise the state according to the block chain - i.e. replay all transactions in block chain, in order.
// In practise this won't need to be done since the State DB will contain the keys for the tries for most recent (and many old) blocks.
@ -42,7 +43,7 @@ int main()
s.sync(bc);
s.sync(tq);
PeerNetwork net; // TODO: Implement - should run in background and send us events when blocks found and allow us to send blocks as required.
PeerNetwork net; // TODO: Implement - should run in background and send us events when blocks found and allow us to send blocks as required.
while (true)
{
// Process network events.
@ -62,14 +63,10 @@ int main()
s.sync(tq);
// Mine for a while.
if (s.mine(100))
{
// Mined block
bytes b = s.blockData();
bytes b = s.mine(100);
if (b.size())
// Import block.
bc.import(b);
}
bc.attemptImport(b, stateDB);
}
return 0;

103
libethereum/BlockChain.cpp

@ -30,44 +30,72 @@
using namespace std;
using namespace eth;
std::string Defaults::s_dbPath = string(getenv("HOME")) + "/.ethereum";
namespace eth
{
std::ostream& operator<<(std::ostream& _out, BlockChain const& _bc)
{
string cmp = toBigEndianString(_bc.m_lastBlockHash);
auto it = _bc.m_detailsDB->NewIterator(_bc.m_readOptions);
for (it->SeekToFirst(); it->Valid(); it->Next())
if (it->key().ToString() != "best")
{
BlockDetails d(RLP(it->value().ToString()));
_out << asHex(it->key().ToString()) << ": " << d.number << " @ " << d.parent << (cmp == it->key().ToString() ? " BEST" : "") << std::endl;
}
delete it;
return _out;
}
}
BlockDetails::BlockDetails(RLP const& _r)
{
number = _r[0].toInt<uint>();
totalDifficulty = _r[1].toInt<u256>();
parent = _r[2].toHash<h256>();
children = _r[3].toVector<h256>();
}
bytes BlockDetails::rlp() const
{
return rlpList(number, totalDifficulty, parent, children);
}
BlockChain::BlockChain(std::string _path, bool _killExisting)
{
if (_path.empty())
_path = string(getenv("HOME")) + "/.ethereum";
_path = Defaults::s_dbPath;
boost::filesystem::create_directory(_path);
if (_killExisting)
{
boost::filesystem::remove_all(_path + "/blocks");
boost::filesystem::remove_all(_path + "/details");
}
ldb::Options o;
o.create_if_missing = true;
auto s = ldb::DB::Open(o, _path + "/blocks", &m_db);
s = ldb::DB::Open(o, _path + "/details", &m_detailsDB);
// Initialise with the genesis as the last block on the longest chain.
m_lastBlockHash = m_genesisHash = BlockInfo::genesis().hash;
m_genesisHash = BlockInfo::genesis().hash;
m_genesisBlock = BlockInfo::createGenesisBlock();
// Insert details of genesis block.
m_details[m_genesisHash] = { 0, (u256)1 << 36, h256(), {} };
}
m_details[m_genesisHash] = BlockDetails(0, (u256)1 << 36, h256(), {});
BlockChain::~BlockChain()
{
// TODO: Implement ability to rebuild details map from DB.
std::string l;
m_detailsDB->Get(m_readOptions, ldb::Slice("best"), &l);
m_lastBlockHash = l.empty() ? m_genesisHash : *(h256*)l.data();
}
h256s BlockChain::blockChain(h256Set const& _earlyExit) const
BlockChain::~BlockChain()
{
// Return the current valid block chain from most recent to genesis.
// Arguments for specifying a set of early-ends
h256s ret;
ret.reserve(m_details[m_lastBlockHash].number + 1);
auto i = m_lastBlockHash;
for (; i != m_genesisHash && !_earlyExit.count(i); i = m_details[i].parent)
ret.push_back(i);
ret.push_back(i);
return ret;
}
void BlockChain::import(bytes const& _block)
void BlockChain::import(bytes const& _block, Overlay const& _db)
{
try
{
@ -78,12 +106,12 @@ void BlockChain::import(bytes const& _block)
auto newHash = eth::sha3(_block);
// Check block doesn't already exist first!
if (m_details.count(newHash))
if (details(newHash))
return;
// Work out its number as the parent's number + 1
auto it = m_details.find(bi.parentHash);
if (it == m_details.end())
auto pd = details(bi.parentHash);
if (!pd)
// We don't know the parent (yet) - discard for now. It'll get resent to us if we find out about its ancestry later on.
return;
@ -92,23 +120,35 @@ void BlockChain::import(bytes const& _block)
bi.verifyParent(biParent);
// Check transactions are valid and that they result in a state equivalent to our state_root.
State s(bi.coinbaseAddress);
State s(bi.coinbaseAddress, _db);
s.sync(*this, bi.parentHash);
// Get total difficulty increase and update state, checking it.
BlockInfo biGrandParent;
if (it->second.number)
biGrandParent.populate(block(it->second.parent));
u256 td = it->second.totalDifficulty + s.playback(&_block, bi, biParent, biGrandParent, true);
if (pd.number)
biGrandParent.populate(block(pd.parent));
auto tdIncrease = s.playback(&_block, bi, biParent, biGrandParent, true);
u256 td = pd.totalDifficulty + tdIncrease;
// All ok - insert into DB
m_details[newHash] = BlockDetails{(uint)it->second.number + 1, bi.parentHash, td};
m_details[newHash] = BlockDetails((uint)pd.number + 1, td, bi.parentHash, {});
m_detailsDB->Put(m_writeOptions, ldb::Slice((char const*)&newHash, 32), (ldb::Slice)eth::ref(m_details[newHash].rlp()));
m_details[bi.parentHash].children.push_back(newHash);
m_db->Put(m_writeOptions, ldb::Slice(toBigEndianString(newHash)), (ldb::Slice)ref(_block));
m_detailsDB->Put(m_writeOptions, ldb::Slice((char const*)&bi.parentHash, 32), (ldb::Slice)eth::ref(m_details[bi.parentHash].rlp()));
m_db->Put(m_writeOptions, ldb::Slice((char const*)&newHash, 32), (ldb::Slice)ref(_block));
// This might be the new last block...
if (td > m_details[m_lastBlockHash].totalDifficulty)
{
m_lastBlockHash = newHash;
m_detailsDB->Put(m_writeOptions, ldb::Slice("best"), ldb::Slice((char const*)&newHash, 32));
}
else
{
cerr << "*** WARNING: Imported block not newest (otd=" << m_details[m_lastBlockHash].totalDifficulty << ", td=" << td << ")" << endl;
}
}
catch (...)
{
@ -122,7 +162,7 @@ bytesConstRef BlockChain::block(h256 _hash) const
if (_hash == m_genesisHash)
return &m_genesisBlock;
m_db->Get(m_readOptions, ldb::Slice(toBigEndianString(_hash)), &m_cache[_hash]);
m_db->Get(m_readOptions, ldb::Slice((char const*)&_hash, 32), &m_cache[_hash]);
return bytesConstRef(&m_cache[_hash]);
}
@ -130,6 +170,13 @@ BlockDetails const& BlockChain::details(h256 _h) const
{
auto it = m_details.find(_h);
if (it == m_details.end())
return NullBlockDetails;
{
std::string s;
m_detailsDB->Get(m_readOptions, ldb::Slice((char const*)&_h, 32), &s);
if (s.empty())
return NullBlockDetails;
bool ok;
tie(it, ok) = m_details.insert(make_pair(_h, BlockDetails(RLP(s))));
}
return it->second;
}

42
libethereum/BlockChain.h

@ -27,24 +27,49 @@ namespace ldb = leveldb;
namespace eth
{
struct Defaults
{
friend class BlockChain;
friend class State;
public:
static void setDBPath(std::string _dbPath) { s_dbPath = _dbPath; }
private:
static std::string s_dbPath;
};
class RLP;
class RLPStream;
struct BlockDetails
{
BlockDetails(): number(0), totalDifficulty(0) {}
BlockDetails(uint _n, u256 _tD, h256 _p, h256s _c): number(_n), totalDifficulty(_tD), parent(_p), children(_c) {}
BlockDetails(RLP const& _r);
bytes rlp() const;
bool isNull() const { return !totalDifficulty; }
explicit operator bool() const { return !isNull(); }
uint number;
u256 totalDifficulty;
h256 parent;
h256s children;
};
static const BlockDetails NullBlockDetails({ 0, 0, h256(), {} });
static const BlockDetails NullBlockDetails;
static const h256s NullH256s;
class Overlay;
/**
* @brief Implements the blockchain database. All data this gives is disk-backed.
*/
class BlockChain
{
public:
BlockChain(std::string _path = std::string(), bool _killExisting = false);
BlockChain(bool _killExisting = false): BlockChain(std::string(), _killExisting) {}
BlockChain(std::string _path, bool _killExisting = false);
~BlockChain();
/// (Potentially) renders invalid existing bytesConstRef returned by lastBlock.
@ -52,13 +77,10 @@ public:
void process();
/// Attempt to import the given block.
bool attemptImport(bytes const& _block) { try { import(_block); return true; } catch (...) { return false; } }
bool attemptImport(bytes const& _block, Overlay const& _stateDB) { try { import(_block, _stateDB); return true; } catch (...) { return false; } }
/// Import block into disk-backed DB
void import(bytes const& _block);
/// Get the full block chain, according to the GHOST algo and the blocks available in the db.
h256s blockChain(h256Set const& _earlyExit) const;
void import(bytes const& _block, Overlay const& _stateDB);
/// Get the number of the last block of the longest chain.
BlockDetails const& details(h256 _hash) const;
@ -75,10 +97,10 @@ public:
private:
/// Get fully populated from disk DB.
mutable std::map<h256, BlockDetails> m_details;
mutable std::map<h256, std::string> m_cache;
ldb::DB* m_db;
ldb::DB* m_detailsDB;
/// Hash of the last (valid) block on the longest chain.
h256 m_lastBlockHash;
@ -87,6 +109,10 @@ private:
ldb::ReadOptions m_readOptions;
ldb::WriteOptions m_writeOptions;
friend std::ostream& operator<<(std::ostream& _out, BlockChain const& _bc);
};
std::ostream& operator<<(std::ostream& _out, BlockChain const& _bc);
}

15
libethereum/BlockInfo.h

@ -30,7 +30,7 @@ namespace eth
struct BlockInfo
{
public:
h256 hash;
h256 hash; ///< SHA3 hash of the entire block!
h256 parentHash;
h256 sha3Uncles;
Address coinbaseAddress;
@ -46,7 +46,18 @@ public:
explicit operator bool() const { return timestamp != Invalid256; }
bool operator==(BlockInfo const& _cmp) const { return parentHash == _cmp.parentHash && nonce == _cmp.nonce; }
bool operator==(BlockInfo const& _cmp) const
{
return parentHash == _cmp.parentHash &&
sha3Uncles == _cmp.sha3Uncles &&
coinbaseAddress == _cmp.coinbaseAddress &&
stateRoot == _cmp.stateRoot &&
sha3Transactions == _cmp.sha3Transactions &&
difficulty == _cmp.difficulty &&
timestamp == _cmp.timestamp &&
extraData == _cmp.extraData &&
nonce == _cmp.nonce;
}
bool operator!=(BlockInfo const& _cmp) const { return !operator==(_cmp); }
static BlockInfo const& genesis() { if (!s_genesis) (s_genesis = new BlockInfo)->populateGenesis(); return *s_genesis; }

11
libethereum/Dagger.cpp

@ -22,11 +22,12 @@ namespace eth
#if FAKE_DAGGER
bool Dagger::mine(u256& o_solution, h256 const& _root, u256 const& _difficulty, uint _msTimeout)
bool Dagger::mine(u256& o_solution, h256 const& _root, u256 const& _difficulty, uint _msTimeout, bool const& _continue)
{
o_solution = 0;
static std::mt19937_64 s_eng((time(0)));
o_solution = std::uniform_int_distribution<uint>(0, ~(uint)0)(s_eng);
// evaluate until we run out of time
for (auto startTime = steady_clock::now(); (steady_clock::now() - startTime) < milliseconds(_msTimeout); o_solution += 1)
for (auto startTime = steady_clock::now(); (steady_clock::now() - startTime) < milliseconds(_msTimeout) && _continue; o_solution += 1)
if (verify(_root, o_solution, _difficulty))
return true;
return false;
@ -52,7 +53,7 @@ bool Dagger::verify(h256 const& _root, u256 const& _nonce, u256 const& _difficul
return eval(_root, _nonce) < bound(_difficulty);
}
bool Dagger::mine(u256& o_solution, h256 const& _root, u256 const& _difficulty, uint _msTimeout)
bool Dagger::mine(u256& o_solution, h256 const& _root, u256 const& _difficulty, uint _msTimeout, bool const& _continue)
{
// restart search if root has changed
if (m_root != _root)
@ -65,7 +66,7 @@ bool Dagger::mine(u256& o_solution, h256 const& _root, u256 const& _difficulty,
u256 const b = bound(_difficulty);
// evaluate until we run out of time
for (auto startTime = steady_clock::now(); (steady_clock::now() - startTime) < milliseconds(_msTimeout); m_nonce += 1)
for (auto startTime = steady_clock::now(); (steady_clock::now() - startTime) < milliseconds(_msTimeout) && _continue; m_nonce += 1)
{
if (eval(_root, m_nonce) < b)
{

4
libethereum/Dagger.h

@ -15,7 +15,7 @@ public:
static h256 eval(h256 const& _root, u256 const& _nonce) { h256 b = (h256)((u256)_root ^ _nonce); return sha3(bytesConstRef((byte const*)&b, 32)); }
static bool verify(h256 const& _root, u256 const& _nonce, u256 const& _difficulty) { return (u256)eval(_root, _nonce) > _difficulty; }
bool mine(u256& o_solution, h256 const& _root, u256 const& _difficulty, uint _msTimeout = 100);
bool mine(u256& o_solution, h256 const& _root, u256 const& _difficulty, uint _msTimeout = 100, bool const& _continue = bool(true));
};
#else
@ -31,7 +31,7 @@ public:
static h256 eval(h256 const& _root, u256 const& _nonce);
static bool verify(h256 const& _root, u256 const& _nonce, u256 const& _difficulty);
bool mine(u256& o_solution, h256 const& _root, u256 const& _difficulty, uint _msTimeout = 100);
bool mine(u256& o_solution, h256 const& _root, u256 const& _difficulty, uint _msTimeout = 100, bool const& _continue = bool(true));
private:

4
libethereum/RLP.h

@ -161,7 +161,7 @@ public:
explicit operator uint() const { return toSlimInt(); }
explicit operator u256() const { return toFatInt(); }
explicit operator bigint() const { return toBigInt(); }
template <unsigned _N> explicit operator FixedHash<_N>() const { return toHash<_N>(); }
template <unsigned _N> explicit operator FixedHash<_N>() const { return toHash<FixedHash<_N>>(); }
/// Converts to bytearray. @returns the empty byte array if not a string.
bytes toBytes() const { if (!isString()) return bytes(); return bytes(payload().data(), payload().data() + items()); }
@ -172,6 +172,8 @@ public:
/// Converts to string. @throws BadCast if not a string.
std::string toStringStrict() const { if (!isString()) throw BadCast(); return payload().cropped(0, items()).toString(); }
template <class T> std::vector<T> toVector() const { std::vector<T> ret; if (isList()) { ret.reserve(itemCount()); for (auto const& i: *this) ret.push_back((T)i); } return ret; }
/// Int conversion flags
enum
{

92
libethereum/State.cpp

@ -53,12 +53,10 @@ u256 const State::c_newContractFee = 60000;
u256 const State::c_txFee = 0;
u256 const State::c_blockReward = 1000000000;
State::State(Address _coinbaseAddress, std::string _path, bool _killExisting): m_state(&m_db), m_ourAddress(_coinbaseAddress)
Overlay State::openDB(std::string _path, bool _killExisting)
{
secp256k1_start();
if (_path.empty())
_path = string(getenv("HOME")) + "/.ethereum";
_path = Defaults::s_dbPath;
boost::filesystem::create_directory(_path);
if (_killExisting)
boost::filesystem::remove_all(_path + "/state");
@ -67,21 +65,26 @@ State::State(Address _coinbaseAddress, std::string _path, bool _killExisting): m
o.create_if_missing = true;
ldb::DB* db = nullptr;
ldb::DB::Open(o, _path + "/state", &db);
return Overlay(db);
}
m_db.setDB(db);
State::State(Address _coinbaseAddress, Overlay const& _db): m_db(_db), m_state(&m_db), m_ourAddress(_coinbaseAddress)
{
secp256k1_start();
m_state.init();
m_previousBlock = BlockInfo::genesis();
resetCurrent();
}
void State::ensureCached(Address _a, bool _requireMemory) const
void State::ensureCached(Address _a, bool _requireMemory, bool _forceCreate) const
{
auto it = m_cache.find(_a);
if (it == m_cache.end())
{
// populate basic info.
string stateBack = m_state.at(_a);
if (stateBack.empty() && !_forceCreate)
return;
RLP state(stateBack);
AddressState s;
if (state.isNull())
@ -176,23 +179,22 @@ void State::sync(BlockChain const& _bc, h256 _block)
// New blocks available, or we've switched to a different branch. All change.
// Find most recent state dump and replay what's left.
// (Most recent state dump might end up being genesis.)
std::vector<h256> l = _bc.blockChain(h256Set());
if (l.back() == BlockInfo::genesis().hash)
{
// Reset to genesis block.
m_previousBlock = BlockInfo::genesis();
}
else
std::vector<h256> chain;
while (bi.stateRoot != BlockInfo::genesis().hash && m_db.lookup(bi.stateRoot).empty()) // while we don't have the state root of the latest block...
{
// TODO: Begin at a restore point.
chain.push_back(bi.hash); // push back for later replay.
bi.populate(_bc.block(bi.parentHash)); // move to parent.
}
m_previousBlock = bi;
resetCurrent();
// Iterate through in reverse, playing back each of the blocks.
for (auto it = next(l.cbegin()); it != l.cend(); ++it)
for (auto it = chain.rbegin(); it != chain.rend(); ++it)
playback(_bc.block(*it), true);
m_currentNumber = _bc.details(_bc.currentHash()).number + 1;
m_currentNumber = _bc.details(_block).number + 1;
resetCurrent();
}
}
@ -289,6 +291,8 @@ u256 State::playback(bytesConstRef _block, BlockInfo const& _grandParent, bool _
// Commit all cached state changes to the state trie.
commit();
// cout << m_state << endl << TrieDB<Address, Overlay>(&m_db, m_currentBlock.stateRoot);
// Hash the state trie and check against the state_root hash in m_currentBlock.
if (m_currentBlock.stateRoot != rootHash())
{
@ -325,13 +329,15 @@ void State::commitToMine(BlockChain const& _bc)
{
// Find uncles if we're not a direct child of the genesis.
auto us = _bc.details(m_previousBlock.parentHash).children;
uncles.appendList(us.size());
assert(us.size() >= 1); // must be at least 1 child of our grandparent - it's our own parent!
uncles.appendList(us.size() - 1); // one fewer - uncles precludes our parent from the list of grandparent's children.
for (auto const& u: us)
{
BlockInfo ubi(_bc.block(u));
ubi.fillStream(uncles, true);
uncleAddresses.push_back(ubi.coinbaseAddress);
}
if (u != m_previousBlock.hash) // ignore our own parent - it's not an uncle.
{
BlockInfo ubi(_bc.block(u));
ubi.fillStream(uncles, true);
uncleAddresses.push_back(ubi.coinbaseAddress);
}
}
else
uncles.appendList(0);
@ -354,7 +360,7 @@ void State::commitToMine(BlockChain const& _bc)
m_currentBlock.parentHash = m_previousBlock.hash;
}
bool State::mine(uint _msTimeout)
bytes const& State::mine(uint _msTimeout)
{
// Update timestamp according to clock.
m_currentBlock.timestamp = time(0);
@ -365,27 +371,29 @@ bool State::mine(uint _msTimeout)
// TODO: Miner class that keeps dagger between mine calls (or just non-polling mining).
if (m_dagger.mine(/*out*/m_currentBlock.nonce, m_currentBlock.headerHashWithoutNonce(), m_currentBlock.difficulty, _msTimeout))
{
// Got it! Compile block:
// Got it!
// Commit our database to disk or nothing other than this state will understand, which would make verifying the state_root rather difficult no?
m_db.commit();
// Compile block:
RLPStream ret;
ret.appendList(3);
{
RLPStream s;
m_currentBlock.fillStream(s, true);
m_currentBlock.hash = sha3(s.out());
ret.appendRaw(s.out());
}
m_currentBlock.fillStream(ret, true);
ret.appendRaw(m_currentTxs);
ret.appendRaw(m_currentUncles);
ret.swapOut(m_currentBytes);
return true;
m_currentBlock.hash = sha3(m_currentBytes);
}
else
m_currentBytes.clear();
return false;
return m_currentBytes;
}
bool State::isNormalAddress(Address _id) const
{
ensureCached(_id);
ensureCached(_id, false, false);
auto it = m_cache.find(_id);
if (it == m_cache.end())
return false;
@ -394,7 +402,7 @@ bool State::isNormalAddress(Address _id) const
bool State::isContractAddress(Address _id) const
{
ensureCached(_id);
ensureCached(_id, false, false);
auto it = m_cache.find(_id);
if (it == m_cache.end())
return false;
@ -403,7 +411,7 @@ bool State::isContractAddress(Address _id) const
u256 State::balance(Address _id) const
{
ensureCached(_id);
ensureCached(_id, false, false);
auto it = m_cache.find(_id);
if (it == m_cache.end())
return 0;
@ -412,7 +420,7 @@ u256 State::balance(Address _id) const
void State::noteSending(Address _id)
{
ensureCached(_id);
ensureCached(_id, false, false);
auto it = m_cache.find(_id);
if (it == m_cache.end())
m_cache[_id] = AddressState(0, 1);
@ -422,7 +430,7 @@ void State::noteSending(Address _id)
void State::addBalance(Address _id, u256 _amount)
{
ensureCached(_id);
ensureCached(_id, false, false);
auto it = m_cache.find(_id);
if (it == m_cache.end())
m_cache[_id] = AddressState(_amount, 0);
@ -432,7 +440,7 @@ void State::addBalance(Address _id, u256 _amount)
void State::subBalance(Address _id, bigint _amount)
{
ensureCached(_id);
ensureCached(_id, false, false);
auto it = m_cache.find(_id);
if (it == m_cache.end() || (bigint)it->second.balance() < _amount)
throw NotEnoughCash();
@ -442,7 +450,7 @@ void State::subBalance(Address _id, bigint _amount)
u256 State::transactionsFrom(Address _id) const
{
ensureCached(_id);
ensureCached(_id, false, false);
auto it = m_cache.find(_id);
if (it == m_cache.end())
return 0;
@ -452,7 +460,7 @@ u256 State::transactionsFrom(Address _id) const
u256 State::contractMemory(Address _id, u256 _memory) const
{
ensureCached(_id);
ensureCached(_id, false, false);
auto it = m_cache.find(_id);
if (it == m_cache.end() || it->second.type() != AddressType::Contract)
return 0;
@ -571,7 +579,7 @@ void State::execute(Address _myAddress, Address _txSender, u256 _txValue, u256 _
if (stack.size() < _n)
throw StackTooSmall(_n, stack.size());
};
ensureCached(_myAddress, true);
ensureCached(_myAddress, true, true);
auto& myMemory = m_cache[_myAddress].takeMemory();
auto mem = [&](u256 _n) -> u256

45
libethereum/State.h

@ -43,13 +43,16 @@ class BlockChain;
* @brief Model of the current state of the ledger.
* Maintains current ledger (m_current) as a fast hash-map. This is hashed only when required (i.e. to create or verify a block).
* Should maintain ledger as of last N blocks, also, in case we end up on the wrong branch.
* TODO: Block database I/O class.
*/
class State
{
public:
/// Construct state object.
explicit State(Address _coinbaseAddress, std::string _path = std::string(), bool _killExisting = false);
State(Address _coinbaseAddress, Overlay const& _db);
/// Open a DB - useful for passing into the constructor & keeping for other states that are necessary.
static Overlay openDB(std::string _path, bool _killExisting = false);
static Overlay openDB(bool _killExisting = false) { return openDB(std::string(), _killExisting); }
/// Cancels transactions and rolls back the state to the end of the previous block.
/// @warning This will only work for on any transactions after you called the last commitToMine().
@ -65,9 +68,9 @@ public:
/// Attempt to find valid nonce for block that this state represents.
/// @param _msTimeout Timeout before return in milliseconds.
/// @returns true if it got lucky. In this case, call blockData() to get the block for
/// spreading far and wide.
bool mine(uint _msTimeout = 1000);
/// @returns a non-empty byte array containing the block if it got lucky. In this case, call blockData()
/// to get the block if you need it later.
bytes const& mine(uint _msTimeout = 1000);
/// Get the complete current block, including valid nonce.
/// Only valid after mine() returns true.
@ -140,7 +143,10 @@ private:
};
/// Retrieve all information about a given address into the cache.
void ensureCached(Address _a, bool _requireMemory = false) const;
/// If _requireMemory is true, grab the full memory should it be a contract item.
/// If _forceCreate is true, then insert a default item into the cache, in the case it doesn't
/// exist in the DB.
void ensureCached(Address _a, bool _requireMemory, bool _forceCreate) const;
/// Commit all changes waiting in the address cache.
void commit();
@ -190,8 +196,35 @@ private:
static const u256 c_newContractFee;
static const u256 c_txFee;
static const u256 c_blockReward;
static std::string c_defaultPath;
friend std::ostream& operator<<(std::ostream& _out, State const& _s);
};
inline std::ostream& operator<<(std::ostream& _out, State const& _s)
{
_out << "--- " << _s.rootHash() << std::endl;
std::set<Address> d;
for (auto const& i: TrieDB<Address, Overlay>(const_cast<Overlay*>(&_s.m_db), _s.m_currentBlock.stateRoot))
{
auto it = _s.m_cache.find(i.first);
if (it == _s.m_cache.end())
{
RLP r(i.second);
_out << "[ " << (r.itemCount() == 3 ? "CONTRACT] " : " NORMAL] ") << i.first << ": " << std::dec << r[1].toInt<u256>() << "@" << r[0].toInt<u256>() << std::endl;
}
else
d.insert(i.first);
}
for (auto i: _s.m_cache)
if (i.second.type() == AddressType::Dead)
_out << "[XXX " << i.first << std::endl;
else
_out << (d.count(i.first) ? "[ ! " : "[ * ") << (i.second.type() == AddressType::Contract ? "CONTRACT] " : " NORMAL] ") << i.first << ": " << std::dec << i.second.nonce() << "@" << i.second.balance() << std::endl;
return _out;
}
}

7
libethereum/TrieDB.h

@ -22,6 +22,7 @@
#pragma once
#include <map>
#include <memory>
#include <leveldb/db.h>
#include "TrieCommon.h"
namespace ldb = leveldb;
@ -71,8 +72,8 @@ class Overlay: public BasicMap
public:
Overlay(ldb::DB* _db = nullptr): m_db(_db) {}
ldb::DB* db() const { return m_db; }
void setDB(ldb::DB* _db, bool _clearOverlay = true) { m_db = _db; if (_clearOverlay) m_over.clear(); }
ldb::DB* db() const { return m_db.get(); }
void setDB(ldb::DB* _db, bool _clearOverlay = true) { m_db = std::shared_ptr<ldb::DB>(_db); if (_clearOverlay) m_over.clear(); }
void commit() { for (auto const& i: m_over) m_db->Put(m_writeOptions, ldb::Slice((char const*)i.first.data(), i.first.size), ldb::Slice(i.second.data(), i.second.size())); m_over.clear(); m_refCount.clear(); }
void rollback() { m_over.clear(); m_refCount.clear(); }
@ -82,7 +83,7 @@ public:
private:
using BasicMap::clear;
ldb::DB* m_db = nullptr;
std::shared_ptr<ldb::DB> m_db;
ldb::ReadOptions m_readOptions;
ldb::WriteOptions m_writeOptions;

8
test/main.cpp

@ -31,11 +31,11 @@ int hexPrefixTest();
int main()
{
hexPrefixTest();
rlpTest();
trieTest();
// hexPrefixTest();
// rlpTest();
// trieTest();
// daggerTest();
cryptoTest();
// cryptoTest();
stateTest();
return 0;
}

41
test/state.cpp

@ -32,21 +32,31 @@ int stateTest()
KeyPair myMiner = sha3("Gav's Miner");
// KeyPair you = sha3("123");
BlockChain bc("/tmp");
State s(myMiner.address(), "/tmp");
Defaults::setDBPath("/tmp");
cout << dec << "me: " << s.balance(me.address()) << endl;
cout << "myMiner: " << s.balance(myMiner.address()) << endl;
Overlay stateDB = State::openDB();
BlockChain bc;
State s(myMiner.address(), stateDB);
cout << bc;
// Sync up - this won't do much until we use the last state.
s.sync(bc);
cout << s;
// Mine to get some ether!
s.commitToMine(bc);
while (!s.mine(100)) {}
bc.attemptImport(s.blockData());
while (s.mine(100).empty()) {}
bc.attemptImport(s.blockData(), stateDB);
cout << bc;
s.sync(bc);
cout << "me: " << s.balance(me.address()) << endl;
cout << "myMiner: " << s.balance(myMiner.address()) << endl;
cout << s;
// Inject a transaction to transfer funds from miner to me.
bytes tx;
{
Transaction t;
@ -60,17 +70,18 @@ int stateTest()
}
s.execute(tx);
cout << "me: " << s.balance(me.address()) << endl;
cout << "myMiner: " << s.balance(myMiner.address()) << endl;
cout << s;
// Mine to get some ether and set in stone.
s.commitToMine(bc);
while (!s.mine(100)) {}
bc.attemptImport(s.blockData());
while (s.mine(100).empty()) {}
bc.attemptImport(s.blockData(), stateDB);
cout << bc;
s.sync(bc);
cout << "me: " << s.balance(me.address()) << endl;
cout << "myMiner: " << s.balance(myMiner.address()) << endl;
// s.dumpAccounts();
cout << s;
return 0;
}

Loading…
Cancel
Save