Browse Source

Additional framework stuff - block verification, playback &c.

cl-refactor
Gav Wood 11 years ago
parent
commit
720df0509e
  1. 1
      eth/CMakeLists.txt
  2. 57
      libethereum/BlockChain.cpp
  3. 25
      libethereum/BlockChain.h
  4. 38
      libethereum/BlockInfo.cpp
  5. 9
      libethereum/BlockInfo.h
  6. 1
      libethereum/CMakeLists.txt
  7. 3
      libethereum/Common.h
  8. 9
      libethereum/Dagger.cpp
  9. 2
      libethereum/Dagger.h
  10. 4
      libethereum/Exceptions.h
  11. 55
      libethereum/State.cpp
  12. 9
      libethereum/State.h
  13. 2
      libethereum/Transaction.cpp
  14. 3
      libethereum/Transaction.h
  15. 6
      libethereum/TransactionQueue.cpp
  16. 4
      libethereum/TransactionQueue.h
  17. 9
      libethereum/vector_ref.h

1
eth/CMakeLists.txt

@ -14,6 +14,7 @@ link_directories(../libethereum)
add_executable(eth ${SRC_LIST})
target_link_libraries(eth ethereum)
target_link_libraries(eth leveldb)
target_link_libraries(eth secp256k1)
target_link_libraries(eth cryptopp)
target_link_libraries(eth gmp)

57
libethereum/BlockChain.cpp

@ -20,6 +20,9 @@
*/
#include "Common.h"
#include "RLP.h"
#include "Exceptions.h"
#include "Dagger.h"
#include "BlockInfo.h"
#include "BlockChain.h"
using namespace std;
@ -27,6 +30,9 @@ using namespace eth;
BlockChain::BlockChain()
{
ldb::Options o;
auto s = ldb::DB::Open(o, "blockchain", &m_db);
// Initialise with the genesis as the last block on the longest chain.
m_lastBlockHash = m_genesisHash = BlockInfo::genesis().hash;
m_genesisBlock = BlockInfo::createGenesisBlock();
@ -36,11 +42,13 @@ BlockChain::~BlockChain()
{
}
std::vector<u256> blockChain() const
u256s BlockChain::blockChain() const
{
// TODO: return the current valid block chain from most recent to genesis.
// TODO: arguments for specifying a set of early-ends
return std::vector<u256>();
u256s ret;
return ret;
}
void BlockChain::import(bytes const& _block)
@ -65,17 +73,35 @@ void BlockChain::import(bytes const& _block)
return;
bi.number = it->second.first + 1;
// CHECK ANCESTRY:
// TODO: check it hashes according to proof of work.
// TODO: check timestamp is after previous timestamp.
// Check Ancestry:
// Check timestamp is after previous timestamp.
if (bi.timestamp <= BlockInfo(block(bi.parentHash)).timestamp)
throw InvalidTimestamp();
// TODO: check difficulty is correct given the two timestamps.
// if (bi.timestamp )
// TODO: check transactions are valid and that they result in a state equivalent to our state_root.
// this saves us from an embarrassing exit later.
// Check uncles.
for (auto const& i: RLP(_block)[2])
{
auto it = m_numberAndParent.find(i.toInt<u256>());
if (it == m_numberAndParent.end())
return; // Don't (yet) have the uncle in our list.
if (it->second.second != bi.parentHash)
throw InvalidUncle();
}
// Insert into DB
m_numberAndParent[newHash] = make_pair(bi.number, bi.parentHash);
m_children.insert(make_pair(bi.parentHash, newHash));
// TODO: put _block onto disk and load into cache.
ldb::WriteOptions o;
m_db->Put(o, ldb::Slice(toBigEndianString(newHash)), (ldb::Slice)ref(_block));
// This might be the new last block; count back through ancestors to common shared ancestor and compare to current.
// TODO: Use GHOST algorithm.
}
catch (...)
{
@ -86,22 +112,9 @@ void BlockChain::import(bytes const& _block)
bytesConstRef BlockChain::block(u256 _hash) const
{
auto it = m_cache.find(_hash);
if (it == m_cache.end())
{
// Load block from disk.
pair<u256, std::shared_ptr<MappedBlock>> loaded;
it = m_cache.insert(loaded).first;
}
return it->second->data();
}
bytesConstRef BlockChain::lastBlock() const
{
if (m_lastBlockHash == m_genesisHash)
return bytesConstRef((bytes*)&m_genesisBlock);
return block(m_lastBlockHash);
if (_hash == m_genesisHash)
return &m_genesisBlock;
return &m_genesisBlock;
}
u256 BlockChain::lastBlockNumber() const

25
libethereum/BlockChain.h

@ -22,23 +22,12 @@
#pragma once
#include "Common.h"
#include <leveldb/db.h>
namespace ldb = leveldb;
namespace eth
{
class MappedBlock
{
public:
MappedBlock() {}
MappedBlock(u256 _hash) {} // TODO: map memory from disk.
~MappedBlock() {} // TODO: unmap memory from disk
bytesConstRef data() const { return bytesConstRef(); }
private:
// TODO: memory mapping.
};
/**
* @brief Implements the blockchain database. All data this gives is disk-backed.
*/
@ -53,15 +42,16 @@ public:
void process();
/// Attempt to import the given block.
bool attemptImport(bytes const& _block) { try { import(_bytes); return true; } catch (...) { return false; } }
bool attemptImport(bytes const& _block) { try { import(_block); return true; } catch (...) { return false; } }
/// Import block into disk-backed DB
void import(bytes const& _block);
/// Get the last block of the longest chain.
bytesConstRef lastBlock() const; // TODO: switch to return MappedBlock or add the lock into vector_ref
bytesConstRef lastBlock() const { return block(m_lastBlockHash); }
std::vector<u256> blockChain()
/// Get the full block chain, according to the GHOST algo and the blocks available in the db.
u256s blockChain() const;
/// Get the number of the last block of the longest chain.
u256 lastBlockNumber() const;
@ -73,8 +63,7 @@ private:
mutable std::map<u256, std::pair<u256, u256>> m_numberAndParent;
mutable std::multimap<u256, u256> m_children;
/// Gets populated on demand. Inactive nodes are pruned after a while.
mutable std::map<u256, std::shared_ptr<MappedBlock>> m_cache;
ldb::DB* m_db;
/// Hash of the last (valid) block on the longest chain.
u256 m_lastBlockHash;

38
libethereum/BlockInfo.cpp

@ -20,6 +20,7 @@
*/
#include "Common.h"
#include "Dagger.h"
#include "Exceptions.h"
#include "RLP.h"
#include "BlockInfo.h"
@ -33,16 +34,26 @@ BlockInfo::BlockInfo()
number = Invalid256;
}
BlockInfo::BlockInfo(bytesConstRef _block, u256 _number)
{
populate(_block, _number);
}
bytes BlockInfo::createGenesisBlock()
{
RLPStream block(3);
auto sha256EmptyList = sha3(RLPEmptyList);
block.appendList(7) << (uint)0 << sha256EmptyList << (uint)0 << sha256EmptyList << ((uint)1 << 36) << (uint)0 << (uint)0;
auto sha3EmptyList = sha3(RLPEmptyList);
block.appendList(8) << (uint)0 << sha3EmptyList << (uint)0 << sha3(RLPNull) << sha3EmptyList << ((uint)1 << 36) << (uint)0 << (uint)0;
block.appendRaw(RLPEmptyList);
block.appendRaw(RLPEmptyList);
return block.out();
}
u256 BlockInfo::headerHashWithoutNonce() const
{
return sha3((RLPStream(7) << toBigEndianString(parentHash) << toBigEndianString(sha3Uncles) << coinbaseAddress << toBigEndianString(stateRoot) << toBigEndianString(sha3Transactions) << difficulty << timestamp).out());
}
void BlockInfo::populateGenesis()
{
bytes genesisBlock = createGenesisBlock();
@ -59,12 +70,13 @@ void BlockInfo::populate(bytesConstRef _block, u256 _number)
RLP header = root[0];
hash = eth::sha3(_block);
parentHash = header[0].toInt<u256>();
sha256Uncles = header[1].toInt<u256>();
sha3Uncles = header[1].toInt<u256>();
coinbaseAddress = header[2].toInt<u160>();
sha256Transactions = header[3].toInt<u256>();
difficulty = header[4].toInt<uint>();
timestamp = header[5].toInt<u256>();
nonce = header[6].toInt<u256>();
stateRoot = header[3].toInt<u256>();
sha3Transactions = header[4].toInt<u256>();
difficulty = header[5].toInt<u256>();
timestamp = header[6].toInt<u256>();
nonce = header[7].toInt<u256>();
}
catch (RLP::BadCast)
{
@ -76,14 +88,14 @@ void BlockInfo::verifyInternals(bytesConstRef _block)
{
RLP root(_block);
if (sha256Transactions != sha3(root[1].data()))
if (sha3Transactions != sha3(root[1].data()))
throw InvalidTransactionsHash();
if (sha256Uncles != sha3(root[2].data()))
if (sha3Uncles != sha3(root[2].data()))
throw InvalidUnclesHash();
// TODO: check difficulty against timestamp.
// TODO: check proof of work.
// TODO: check each transaction - allow coinbaseAddress for the miner fees, but everything else must be exactly how we would do it.
// check it hashes according to proof of work.
Dagger d(headerHashWithoutNonce());
if (d.eval(nonce) >= difficulty)
throw InvalidNonce();
}

9
libethereum/BlockInfo.h

@ -31,15 +31,17 @@ struct BlockInfo
public:
u256 hash;
u256 parentHash;
u256 sha256Uncles;
u256 sha3Uncles;
u256 coinbaseAddress;
u256 sha256Transactions;
u256 stateRoot;
u256 sha3Transactions;
u256 difficulty;
u256 timestamp;
u256 nonce;
u256 number;
BlockInfo();
explicit BlockInfo(bytesConstRef _block, u256 _number = 0);
explicit operator bool() { return number != Invalid256; }
@ -49,6 +51,9 @@ public:
void populate(bytesConstRef _block, u256 _number = 0);
void verifyInternals(bytesConstRef _block);
/// No-nonce sha3 of the header only.
u256 headerHashWithoutNonce() const;
static bytes createGenesisBlock();
private:

1
libethereum/CMakeLists.txt

@ -10,5 +10,6 @@ aux_source_directory(. SRC_LIST)
add_library(ethereum ${SRC_LIST})
target_link_libraries(ethereum secp256k1)
target_link_libraries(ethereum leveldb)
target_link_libraries(ethereum cryptopp)
target_link_libraries(ethereum gmp)

3
libethereum/Common.h

@ -24,6 +24,7 @@
#pragma once
#include <map>
#include <set>
#include <string>
#include <cassert>
#include <sstream>
@ -51,6 +52,8 @@ using uint = uint64_t;
using sint = int64_t;
using u256s = std::vector<u256>;
using u160s = std::vector<u160>;
using u256Set = std::set<u256>;
using u160Set = std::set<u160>;
// Map types.
using StringMap = std::map<std::string, std::string>;

9
libethereum/Dagger.cpp

@ -17,16 +17,21 @@ Dagger::~Dagger()
{
}
u256 Dagger::bound(u256 _diff)
{
return (u256)((bigint(1) << 256) / _diff);
}
u256 Dagger::search(uint _msTimeout, u256 _diff)
{
static mt19937_64 s_engine((std::random_device())());
u256 bound = (u256)((bigint(1) << 256) / _diff);
u256 b = bound(_diff);
auto start = steady_clock::now();
while (steady_clock::now() - start < milliseconds(_msTimeout))
for (uint sp = std::uniform_int_distribution<uint>()(s_engine), j = 0; j < 1000; ++j, ++sp)
if (eval(sp) < bound)
if (eval(sp) < b)
return sp;
return 0;
}

2
libethereum/Dagger.h

@ -16,6 +16,8 @@ public:
u256 eval(u256 _N);
u256 search(uint _msTimeout, u256 _diff);
static u256 bound(u256 _diff);
private:
u256 m_hash;
u256 m_xn;

4
libethereum/Exceptions.h

@ -19,11 +19,13 @@ class InvalidSignature: public std::exception {};
class InvalidTransactionFormat: public std::exception {};
class InvalidBlockFormat: public std::exception {};
class InvalidUnclesHash: public std::exception {};
class InvalidUncle: public std::exception {};
class InvalidStateRoot: public std::exception {};
class InvalidTransactionsHash: public std::exception {};
class InvalidTransaction: public std::exception {};
class InvalidDifficulty: public std::exception {};
class InvalidTimestamp: public std::exception {};
class InvalidNonce: public std::exception { public: InvalidNonce(u256 _required, u256 _candidate): required(_required), candidate(_candidate) {} u256 required; u256 candidate; };
class InvalidNonce: public std::exception { public: InvalidNonce(u256 _required = 0, u256 _candidate = 0): required(_required), candidate(_candidate) {} u256 required; u256 candidate; };
class InvalidParentHash: public std::exception {};
}

55
libethereum/State.cpp

@ -87,7 +87,6 @@ void State::sync(BlockChain const& _bc, TransactionQueue& _tq)
if (l.back() == BlockInfo::genesis().hash)
{
// Reset to genesis block.
m_current.clear();
m_previousBlock = BlockInfo::genesis();
}
else
@ -100,6 +99,9 @@ void State::sync(BlockChain const& _bc, TransactionQueue& _tq)
playback(_bc.block(*it));
m_transactions.clear();
m_current.clear();
m_currentBlock = BlockInfo();
m_currentBlock.number = m_previousBlock.number + 1;
}
@ -127,18 +129,23 @@ void State::sync(BlockChain const& _bc, TransactionQueue& _tq)
void State::playback(bytesConstRef _block)
{
BlockInfo bi;
try
{
bi.populate(_block, m_previousBlock.number + 1);
bi.verifyInternals(_block);
if (bi.parentHash != m_previousBlock.hash)
m_currentBlock.populate(_block, m_previousBlock.number + 1);
m_currentBlock.verifyInternals(_block);
if (m_currentBlock.parentHash != m_previousBlock.hash)
throw InvalidParentHash();
// All ok with the block generally. Play back the transactions now...
RLP txs = _block[1];
RLP txs = RLP(_block)[1];
for (auto const& i: txs)
execute(i.data());
// Hash the state trie and check against the state_root hash in m_currentBlock.
if (m_currentBlock.stateRoot != currentHash())
throw InvalidStateRoot();
m_previousBlock = m_currentBlock;
}
catch (...)
{
@ -148,6 +155,12 @@ void State::playback(bytesConstRef _block)
}
}
u256 State::currentHash() const
{
// TODO!
return 0;
}
bool State::mine(uint _msTimeout) const
{
// TODO: update timestamp according to clock.
@ -207,21 +220,41 @@ u256 State::contractMemory(Address _contract, u256 _memory) const
return i == m->second.memory().end() ? 0 : i->second;
}
bool State::execute(bytesConstRef _rlp)
{
// Entry point for a user-executed transaction.
try
{
Transaction t(_rlp);
execute(t, t.sender());
// Add to the user-originated transactions that we've executed.
// NOTE: Here, contract-originated transactions will not get added to the transaction list.
// If this is wrong, move this line into execute(Transaction const& _t, Address _sender) and
// don't forget to allow unsigned transactions in the tx list if they concur with the script execution.
m_transactions.insert(make_pair(t.sha3(), t));
return true;
}
catch (...)
{
return false;
}
}
void State::execute(Transaction const& _t, Address _sender)
{
// Entry point for a contract-originated transaction.
// Ignore invalid transactions.
if (_t.nonce != transactionsFrom(_sender))
throw InvalidNonce();
auto nonceReq = transactionsFrom(_sender);
if (_t.nonce != nonceReq)
throw InvalidNonce(nonceReq, _t.nonce);
// Not considered invalid - just pointless.
if (balance(_sender) < _t.value + _t.fee)
throw NotEnoughCash();
// Add to the transactions in
m_transactions.push_back(_t.sha3(), _t);
if (_t.receiveAddress)
{
subBalance(_sender, _t.value + _t.fee);

9
libethereum/State.h

@ -59,11 +59,12 @@ public:
/// Sync our state with the block chain.
/// This basically involves wiping ourselves if we've been superceded and rebuilding from the transaction queue.
void sync(BlockChain const& _bc, TransactionQueue const& _tq);
/// We also sync our transactions, killing those from the queue that we have and assimilating those that we don't.
void sync(BlockChain const& _bc, TransactionQueue& _tq);
/// Execute a given transaction.
bool execute(bytes const& _rlp) { try { Transaction t(_rlp); execute(t, t.sender()); } catch (...) { return false; } }
bool execute(bytes const& _rlp) { return execute(&_rlp); }
bool execute(bytesConstRef _rlp);
/// Check if the address is a valid normal (non-contract) account address.
bool isNormalAddress(Address _address) const;
@ -111,6 +112,8 @@ private:
/// Execute all transactions within a given block.
void playback(bytesConstRef _block);
u256 currentHash() const;
// TODO: std::hash<Address> and then move to unordered_map.
// Will need to sort on hash construction.
std::map<Address, AddressState> m_current; ///< The current state. We work with a C++ hash map rather than a Trie.

2
libethereum/Transaction.cpp

@ -25,7 +25,7 @@
using namespace std;
using namespace eth;
Transaction::Transaction(bytes const& _rlpData)
Transaction::Transaction(bytesConstRef _rlpData)
{
RLP rlp(_rlpData);
nonce = rlp[0].toInt<u256>(RLP::StrictlyInt);

3
libethereum/Transaction.h

@ -41,7 +41,8 @@ struct Signature
struct Transaction
{
Transaction() {}
Transaction(bytes const& _rlp);
Transaction(bytesConstRef _rlp);
Transaction(bytes const& _rlp): Transaction(&_rlp) {}
u256 nonce;
Address receiveAddress;

6
libethereum/TransactionQueue.cpp

@ -28,14 +28,14 @@ void TransactionQueue::import(bytes const& _block)
{
// Check if we already know this transaction.
u256 h = sha3(_block);
if (m_hashes.count(h))
if (m_data.count(h))
return;
// Check validity of _block as a transaction. To do this we just deserialise and attempt to determine the sender. If it doesn't work, the signature is bad.
// The transaction's nonce may yet be invalid (or, it could be "valid" but we may be missing a marginally older transaction).
Transaction t(_block);
u160 sender = t.sender();
t.sender();
// If valid, append to blocks.
m_data[h] = m_data;
m_data[h] = _block;
}

4
libethereum/TransactionQueue.h

@ -34,13 +34,13 @@ class BlockChain;
class TransactionQueue
{
public:
bool attemptImport(bytes const& _block) { try { import(_bytes); return true; } catch (...) { return false; } }
bool attemptImport(bytes const& _block) { try { import(_block); return true; } catch (...) { return false; } }
void import(bytes const& _block);
void drop(u256 _txHash) { m_data.erase(_txHash); }
std::map<u256, bytes> const& transactions() const;
std::map<u256, bytes> const& transactions() const { return m_data; }
private:
std::map<u256, bytes> m_data; ///< the queue.

9
libethereum/vector_ref.h

@ -4,6 +4,7 @@
#include <cassert>
#include <vector>
#include <string>
#include <leveldb/db.h>
namespace eth
{
@ -21,6 +22,7 @@ public:
vector_ref(std::string* _data): m_data((_T*)_data->data()), m_count(_data->size() / sizeof(_T)) {}
vector_ref(std::vector<typename std::enable_if<std::is_const<_T>::value, typename std::remove_const<_T>::type>::type> const* _data): m_data(_data->data()), m_count(_data->size()) {}
vector_ref(std::enable_if<std::is_const<_T>::value, std::string const&> _data): m_data((_T*)_data->data()), m_count(_data->size() / sizeof(_T)) {}
vector_ref(leveldb::Slice const& _s): m_data(_s.data()), m_count(_s.size() / sizeof(_T)) {}
explicit operator bool() const { return m_data && m_count; }
@ -48,6 +50,8 @@ public:
bool operator==(vector_ref<_T> const& _cmp) const { return m_data == _cmp.m_data && m_count == _cmp.m_count; }
bool operator!=(vector_ref<_T> const& _cmp) const { return !operator==(); }
operator leveldb::Slice() const { return leveldb::Slice((char const*)m_data, m_count * sizeof(_T)); }
void reset() { m_data = nullptr; m_count = 0; }
private:
@ -55,4 +59,9 @@ private:
unsigned m_count;
};
template<class _T> vector_ref<_T const> ref(_T const& _t) { return vector_ref<_T const>(&_t, 1); }
template<class _T> vector_ref<_T> ref(_T& _t) { return vector_ref<_T>(&_t, 1); }
template<class _T> vector_ref<_T const> ref(std::vector<_T> const& _t) { return vector_ref<_T const>(&_t); }
template<class _T> vector_ref<_T> ref(std::vector<_T>& _t) { return vector_ref<_T>(&_t); }
}

Loading…
Cancel
Save