diff --git a/TODO b/TODO index c5ebaeea2..4d0250543 100644 --- a/TODO +++ b/TODO @@ -13,6 +13,12 @@ Crypto stuff: Better handling of corrupt blocks. +Network: +- Firewall-busting PeerConnections. +- Crypto on network. +- Respect idealPeerCount. +- Peer network traversal for peer discovery. + ### GAV diff --git a/alephzero/Main.cpp b/alephzero/Main.cpp index 6408e0159..bad35c382 100644 --- a/alephzero/Main.cpp +++ b/alephzero/Main.cpp @@ -1,17 +1,111 @@ +#include +#include +#include #include "Main.h" #include "ui_Main.h" +using namespace std; +using namespace eth; Main::Main(QWidget *parent) : QDialog(parent), - ui(new Ui::Main) + ui(new Ui::Main), + m_client("AlephZero/v0.1") { setWindowFlags(Qt::Window); ui->setupUi(this); - ui->transactions->setHtml("Hello world!"); + readSettings(); + refresh(); + + m_refresh = new QTimer(this); + connect(m_refresh, SIGNAL(timeout()), SLOT(refresh())); + m_refresh->start(1000); } Main::~Main() { + writeSettings(); delete ui; } + +void Main::writeSettings() +{ + QSettings s("ethereum", "alephzero"); + QByteArray b; + b.resize(32); + memcpy(b.data(), &m_myKey, 32); + s.setValue("address", b); +} + +void Main::readSettings() +{ + QSettings s("ethereum", "alephzero"); + QByteArray b = s.value("address").toByteArray(); + if (b.isEmpty()) + m_myKey = KeyPair::create(); + else + { + h256 k; + memcpy(&k, b.data(), 32); + m_myKey = KeyPair(k); + } + m_client.setAddress(m_myKey.address()); + + /*for (uint i = 0; !s.value(QString("peer%1").arg(i)).isNull(); ++i) + { + s.value(QString("peer%1").arg(i)).toString(); + }*/ +} + +void Main::refresh() +{ + ui->balance->setText(QString::fromStdString(toString(m_client.state().balance(m_myKey.address()))) + " wei"); + ui->peerCount->setText(QString::fromStdString(toString(m_client.peerCount())) + " peer(s)"); + ui->address->setText(QString::fromStdString(asHex(m_client.state().address().asArray()))); + ui->peers->clear(); + for (PeerInfo const& i: m_client.peers()) + ui->peers->addItem(QString("%3 ms - %1:%2 - %4").arg(i.endpoint.address().to_string().c_str()).arg(i.endpoint.port()).arg(chrono::duration_cast(i.lastPing).count()).arg(i.clientVersion.c_str())); + + auto d = m_client.blockChain().details(); + auto diff = BlockInfo(m_client.blockChain().block()).difficulty; + ui->blockChain->setText(QString("%1 blocks @ (%3) - %2").arg(d.number).arg(toLog2(d.totalDifficulty)).arg(toLog2(diff))); +} + +void Main::on_net_toggled() +{ + if (ui->net->isChecked()) + m_client.startNetwork(ui->port->value()); + else + m_client.stopNetwork(); +} + +void Main::on_connect_clicked() +{ + QString s = QInputDialog::getText(this, "Enter first peer", "Enter a peer to which a connection may be made", QLineEdit::Normal, "127.0.0.1:30303"); + string host = s.section(":", 0, 0).toStdString(); + short port = s.section(":", 1).toInt(); + m_client.connect(host, port); +} + +void Main::on_mine_toggled() +{ + if (ui->mine->isChecked()) + m_client.startMining(); + else + m_client.stopMining(); +} + +void Main::on_send_clicked() +{ + Secret s = Secret(fromUserHex(ui->address->text().toStdString())); + Address r = Address(fromUserHex(ui->destination->text().toStdString())); + m_client.transact(s, r, ui->value->value(), ui->fee->value()); + refresh(); +} + +void Main::on_create_clicked() +{ + KeyPair p = KeyPair::create(); + QString s = QString::fromStdString("New key:\n" + asHex(p.secret().asArray()) + "\nAddress: " + asHex(p.address().asArray())); + QMessageBox::information(this, "New Key", s, QMessageBox::Ok); +} diff --git a/alephzero/Main.h b/alephzero/Main.h index 9ef27b3b2..0ee36c273 100644 --- a/alephzero/Main.h +++ b/alephzero/Main.h @@ -1,7 +1,9 @@ #ifndef MAIN_H #define MAIN_H +#include #include +#include namespace Ui { class Main; @@ -17,11 +19,25 @@ public: private slots: void on_connect_clicked(); + void on_mine_toggled(); + void on_send_clicked(); + void on_create_clicked(); + void on_net_toggled(); + + void refresh(); private: - Client c; + void readSettings(); + void writeSettings(); Ui::Main *ui; + + eth::Client m_client; + + eth::KeyPair m_myKey; + std::vector m_peers; + + QTimer* m_refresh; }; #endif // MAIN_H diff --git a/alephzero/Main.ui b/alephzero/Main.ui index 79231dd28..fd91b0df1 100644 --- a/alephzero/Main.ui +++ b/alephzero/Main.ui @@ -6,8 +6,8 @@ 0 0 - 787 - 555 + 978 + 556 @@ -29,6 +29,29 @@ 0 + + + + Net + + + true + + + + + + + 1024 + + + 32767 + + + 30303 + + + @@ -36,11 +59,30 @@ + + + + Qt::Horizontal + + + + 40 + 20 + + + + wei + + 430000000 + + + 1000 + @@ -51,6 +93,12 @@ (fee + + 430000000 + + + 100 + @@ -63,6 +111,13 @@ + + + + Create + + + @@ -70,12 +125,15 @@ - 205 + 40 20 + + + @@ -86,9 +144,6 @@ - - - @@ -103,16 +158,17 @@ Qt::Horizontal - - - QFrame::NoFrame - - true + + + + QFrame::NoFrame + + @@ -129,10 +185,21 @@ - + + + true + + + + + + + 1 block + + - + 0 peers diff --git a/alephzero/alephzero.pro b/alephzero/alephzero.pro index 3e8542f56..5d1f7e07d 100644 --- a/alephzero/alephzero.pro +++ b/alephzero/alephzero.pro @@ -6,12 +6,14 @@ QT += core gui widgets +QMAKE_CXXFLAGS += -std=c++11 + TARGET = alephzero TEMPLATE = app QMAKE_LIBDIR += ../../cpp-ethereum-build/libethereum ../../secp256k1 ../../cryptopp562 -LIBS += -lethereum -lsecp256k1 -lleveldb -lcryptopp -lgmp -lboost_system -lboost_filesystem +LIBS += -Wl,-rpath,../../cpp-ethereum-build/libethereum -Wl,-rpath,../../secp256k1 -Wl,-rpath,../../cryptopp562 -lethereum -lsecp256k1 -lleveldb -lcryptopp -lgmp -lboost_filesystem -lboost_system SOURCES += main.cpp\ Main.cpp @@ -20,6 +22,6 @@ HEADERS += Main.h FORMS += Main.ui -INCLUDEPATH = ../../secp256k1/include ../../cryptopp562 +INCLUDEPATH = ../../secp256k1/include ../../cryptopp562 ../../cpp-ethereum diff --git a/eth/main.cpp b/eth/main.cpp index 48068b79c..bb7afce95 100644 --- a/eth/main.cpp +++ b/eth/main.cpp @@ -20,17 +20,20 @@ * Ethereum client. */ +#include "Client.h" #include "PeerNetwork.h" #include "BlockChain.h" #include "State.h" using namespace std; using namespace eth; -int main() +int main(int argc, char** argv) { short listenPort = 30303; string remoteHost; short remotePort = 30303; + bool interactive = false; + string dbPath; // Our address. Address us; // TODO: should be loaded from config file @@ -45,47 +48,72 @@ int main() else if (arg == "-p" && i + 1 < argc) remotePort = atoi(argv[++i]); else if (arg == "-a" && i + 1 < argc) - us = h256(fromUserHex(argv[++i])); + us = h160(fromUserHex(argv[++i])); + else if (arg == "-i") + interactive = true; + else if (arg == "-d") + dbPath = arg; else remoteHost = argv[i]; } - 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. - // TODO: currently it contains keys for *all* blocks. Make it remove old ones. - s.sync(bc); - s.sync(tq); - - PeerServer net(bc, 0, 30303); // TODO: Implement - should run in background and send us events when blocks found and allow us to send blocks as required. - while (true) + Client c("Ethereum(++)/v0.1", us, dbPath); + if (interactive) { - // Process network events. - net.process(); - - // Synchronise block chain with network. - // Will broadcast any of our (new) transactions and blocks, and collect & add any of their (new) transactions and blocks. - net.sync(bc, tq); - - // Synchronise state to block chain. - // This should remove any transactions on our queue that are included within our state. - // It also guarantees that the state reflects the longest (valid!) chain on the block chain. - // This might mean reverting to an earlier state and replaying some blocks, or, (worst-case: - // if there are no checkpoints before our fork) reverting to the genesis block and replaying - // all blocks. - s.sync(bc); // Resynchronise state with block chain & trans - s.sync(tq); + cout << "Ethereum (++)" << endl; + cout << " By Gav Wood, Tim Hughes & team Ethereum, (c) 2013, 2014" << endl << endl; - // Mine for a while. - bytes b = s.mine(100); - if (b.size()) - // Import block. - bc.attemptImport(b, stateDB); + while (true) + { + cout << "> " << flush; + std::string cmd; + cin >> cmd; + if (cmd == "netstart") + { + eth::uint port; + cin >> port; + c.startNetwork(port); + } + else if (cmd == "connect") + { + string addr; + eth::uint port; + cin >> addr >> port; + c.connect(addr, port); + } + else if (cmd == "netstop") + { + c.stopNetwork(); + } + else if (cmd == "minestart") + { + c.startMining(); + } + else if (cmd == "minestop") + { + c.stopMining(); + } + else if (cmd == "transact") + { + string sechex; + string rechex; + u256 amount; + u256 fee; + cin >> sechex >> rechex >> amount >> fee; + Secret secret = h256(fromUserHex(sechex)); + Address dest = h160(fromUserHex(rechex)); + c.transact(secret, dest, amount, fee); + } + } } + else + { + c.startNetwork(listenPort, remoteHost, remotePort); + c.startMining(); + while (true) + sleep(1); + } + return 0; } diff --git a/libethereum/BlockChain.cpp b/libethereum/BlockChain.cpp index e3589964a..9ab3faa80 100644 --- a/libethereum/BlockChain.cpp +++ b/libethereum/BlockChain.cpp @@ -97,63 +97,55 @@ BlockChain::~BlockChain() void BlockChain::import(bytes const& _block, Overlay const& _db) { - try - { - // VERIFY: populates from the block and checks the block is internally coherent. - BlockInfo bi(&_block); - bi.verifyInternals(&_block); + // VERIFY: populates from the block and checks the block is internally coherent. + BlockInfo bi(&_block); + bi.verifyInternals(&_block); - auto newHash = eth::sha3(_block); + auto newHash = eth::sha3(_block); - // Check block doesn't already exist first! - if (details(newHash)) - return; + // Check block doesn't already exist first! + if (details(newHash)) + throw AlreadyHaveBlock(); - // Work out its number as the parent's number + 1 - 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; + // Work out its number as the parent's number + 1 + 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. + throw UnknownParent(); - // Check family: - BlockInfo biParent(block(bi.parentHash)); - bi.verifyParent(biParent); + // Check family: + BlockInfo biParent(block(bi.parentHash)); + bi.verifyParent(biParent); - // Check transactions are valid and that they result in a state equivalent to our state_root. - State s(bi.coinbaseAddress, _db); - s.sync(*this, bi.parentHash); + // Check transactions are valid and that they result in a state equivalent to our state_root. + State s(bi.coinbaseAddress, _db); + s.sync(*this, bi.parentHash); - // Get total difficulty increase and update state, checking it. - BlockInfo biGrandParent; - if (pd.number) - biGrandParent.populate(block(pd.parent)); - auto tdIncrease = s.playback(&_block, bi, biParent, biGrandParent, true); - u256 td = pd.totalDifficulty + tdIncrease; + // Get total difficulty increase and update state, checking it. + BlockInfo biGrandParent; + 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)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())); + // All ok - insert into DB + 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_detailsDB->Put(m_writeOptions, ldb::Slice((char const*)&bi.parentHash, 32), (ldb::Slice)eth::ref(m_details[bi.parentHash].rlp())); + m_details[bi.parentHash].children.push_back(newHash); + 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)); + 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; - } + // 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)); } - catch (...) + else { - // Exit silently on exception(?) - return; + cerr << "*** WARNING: Imported block not newest (otd=" << m_details[m_lastBlockHash].totalDifficulty << ", td=" << td << ")" << endl; } } diff --git a/libethereum/BlockChain.h b/libethereum/BlockChain.h index 08478d526..014ef16af 100644 --- a/libethereum/BlockChain.h +++ b/libethereum/BlockChain.h @@ -62,6 +62,9 @@ static const h256s NullH256s; class Overlay; +class AlreadyHaveBlock: public std::exception {}; +class UnknownParent: public std::exception {}; + /** * @brief Implements the blockchain database. All data this gives is disk-backed. */ @@ -84,15 +87,18 @@ public: /// Get the number of the last block of the longest chain. BlockDetails const& details(h256 _hash) const; + BlockDetails const& details() const { return details(currentHash()); } /// Get a given block (RLP format). bytesConstRef block(h256 _hash) const; + bytesConstRef block() const { return block(currentHash()); } /// Get a given block (RLP format). h256 currentHash() const { return m_lastBlockHash; } /// Get the coinbase address of a given block. Address coinbaseAddress(h256 _hash) const; + Address coinbaseAddress() const { return coinbaseAddress(currentHash()); } private: /// Get fully populated from disk DB. diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index 311851213..2b71aeb5c 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -24,18 +24,19 @@ using namespace std; using namespace eth; -Client::Client(std::string const& _dbPath): +Client::Client(std::string const& _clientVersion, Address _us, std::string const& _dbPath): + m_clientVersion(_clientVersion), m_bc(_dbPath), m_stateDB(State::openDB(_dbPath)), - m_s(m_stateDB) + m_s(_us, m_stateDB) { Defaults::setDBPath(_dbPath); // 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. // TODO: currently it contains keys for *all* blocks. Make it remove old ones. - s.sync(bc); - s.sync(tq); + m_s.sync(m_bc); + m_s.sync(m_tq); m_work = new thread([&](){ while (m_workState != Deleting) work(); m_workState = Deleted; }); } @@ -48,31 +49,22 @@ Client::~Client() usleep(10000); } -void Client::transact(Address _dest, u256 _amount, u256 _fee, u256s _data = u256s(), Secret _secret) -{ -} - -BlockChain const& Client::blockChain() const -{ -} - -TransactionQueue const& Client::transactionQueue() const -{ -} - -unsigned Client::peerCount() const -{ -} - -void Client::startNetwork(short _listenPort = 30303, std::string const& _seedHost, short _port = 30303) +void Client::startNetwork(short _listenPort, std::string const& _seedHost, short _port) { if (m_net) return; - m_net = new PeerServer(m_bc, 0, _listenPort); + m_net = new PeerServer(m_clientVersion, m_bc, 0, _listenPort); if (_seedHost.size()) m_net->connect(_seedHost, _port); } +void Client::connect(std::string const& _seedHost, short _port) +{ + if (!m_net) + return; + m_net->connect(_seedHost, _port); +} + void Client::stopNetwork() { delete m_net; @@ -89,16 +81,25 @@ void Client::stopMining() m_doMine = false; } -std::pair Client::miningProgress() const +void Client::transact(Secret _secret, Address _dest, u256 _amount, u256 _fee, u256s _data) { + Transaction t; + t.nonce = m_s.transactionsFrom(toAddress(_secret)); + t.receiveAddress = _dest; + t.value = _amount; + t.fee = _fee; + t.data = _data; + t.sign(_secret); + m_tq.attemptImport(t.rlp()); } -void Client::work(string const& _seedHost, short _port) +void Client::work() { // Process network events. // Synchronise block chain with network. // Will broadcast any of our (new) transactions and blocks, and collect & add any of their (new) transactions and blocks. - m_net->process(m_bc, m_tq); + if (m_net) + m_net->process(m_bc, m_tq, m_stateDB); // Synchronise state to block chain. // This should remove any transactions on our queue that are included within our state. @@ -112,10 +113,18 @@ void Client::work(string const& _seedHost, short _port) if (m_doMine) { // Mine for a while. - bytes b = s.mine(100); + MineInfo mineInfo = m_s.mine(100); + m_mineProgress.best = max(m_mineProgress.best, mineInfo.best); + m_mineProgress.current = mineInfo.best; + m_mineProgress.requirement = mineInfo.requirement; - if (b.size()) + if (mineInfo.completed()) + { // Import block. - bc.attemptImport(b, stateDB); + m_bc.attemptImport(m_s.blockData(), m_stateDB); + m_mineProgress.best = 0; + } } + else + usleep(100000); } diff --git a/libethereum/Client.h b/libethereum/Client.h index 5f08f3c07..12f341cdc 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -27,34 +27,47 @@ #include "BlockChain.h" #include "TransactionQueue.h" #include "State.h" +#include "Dagger.h" #include "PeerNetwork.h" namespace eth { +struct MineProgress +{ + uint requirement; + uint best; + uint current; +}; + class Client { public: - Client(std::string const& _dbPath); + Client(std::string const& _clientVersion, Address _us = Address(), std::string const& _dbPath = std::string()); ~Client(); - void transact(Address _dest, u256 _amount, u256 _fee, u256s _data = u256s(), Secret _secret); + void transact(Secret _secret, Address _dest, u256 _amount, u256 _fee, u256s _data = u256s()); - BlockChain const& blockChain() const; - TransactionQueue const& transactionQueue() const; + State const& state() const { return m_s; } + BlockChain const& blockChain() const { return m_bc; } + TransactionQueue const& transactionQueue() const { return m_tq; } - unsigned peerCount() const; + std::vector peers() { return m_net ? m_net->peers() : std::vector(); } + unsigned peerCount() const { return m_net ? m_net->peerCount() : 0; } - void startNetwork(short _listenPort = 30303, std::string const& _seedHost, short _port = 30303); + void startNetwork(short _listenPort = 30303, std::string const& _seedHost = std::string(), short _port = 30303); + void connect(std::string const& _seedHost, short _port = 30303); void stopNetwork(); + void setAddress(Address _us) { m_s.setAddress(_us); } void startMining(); void stopMining(); - std::pair miningProgress() const; + MineProgress miningProgress() const { return m_mineProgress; } private: void work(); + std::string m_clientVersion; ///< Our end-application client's name/version. BlockChain m_bc; ///< Maintains block database. TransactionQueue m_tq; ///< Maintains list of incoming transactions not yet on the block chain. Overlay m_stateDB; ///< Acts as the central point for the state database, so multiple States can share it. @@ -63,6 +76,7 @@ private: std::thread* m_work; ///< The work thread. enum { Active = 0, Deleting, Deleted } m_workState = Active; bool m_doMine = false; ///< Are we supposed to be mining? + MineProgress m_mineProgress; }; } diff --git a/libethereum/Common.cpp b/libethereum/Common.cpp index 10b96bedc..94ac77247 100644 --- a/libethereum/Common.cpp +++ b/libethereum/Common.cpp @@ -169,3 +169,14 @@ Address eth::toAddress(Secret _private) #endif return ret; } + +KeyPair KeyPair::create() +{ + static std::mt19937_64 s_eng(time(0)); + std::uniform_int_distribution d(0, 255); + KeyPair ret; + for (uint i = 0; i < 20; ++i) + ret.m_secret[i] = d(s_eng); + ret.m_address = toAddress(ret.m_secret); + return ret; +} diff --git a/libethereum/Common.h b/libethereum/Common.h index 6e16e407c..3c93fdf07 100644 --- a/libethereum/Common.h +++ b/libethereum/Common.h @@ -74,7 +74,7 @@ public: FixedHash() { m_data.fill(0); } FixedHash(Arith const& _arith) { toBigEndian(_arith, m_data); } - explicit FixedHash(bytes const& _b) { memcpy(m_data.data(), _b.data(), min(_b.size(), N)); } + explicit FixedHash(bytes const& _b) { memcpy(m_data.data(), _b.data(), std::min(_b.size(), N)); } operator Arith() const { return fromBigEndian(m_data); } @@ -99,6 +99,8 @@ public: byte const* data() const { return m_data.data(); } bytes asBytes() const { return bytes(data(), data() + N); } + std::array& asArray() { return m_data; } + std::array const& asArray() const { return m_data; } private: std::array m_data; @@ -335,6 +337,8 @@ public: KeyPair() {} KeyPair(Secret _k): m_secret(_k), m_address(toAddress(_k)) {} + static KeyPair create(); + Secret secret() const { return m_secret; } Address address() const { return m_address; } diff --git a/libethereum/Dagger.cpp b/libethereum/Dagger.cpp index 177ec0696..d65a8932a 100644 --- a/libethereum/Dagger.cpp +++ b/libethereum/Dagger.cpp @@ -22,15 +22,20 @@ namespace eth #if FAKE_DAGGER -bool Dagger::mine(u256& o_solution, h256 const& _root, u256 const& _difficulty, uint _msTimeout, bool const& _continue) +MineInfo Dagger::mine(u256& o_solution, h256 const& _root, u256 const& _difficulty, uint _msTimeout, bool const& _continue) { + MineInfo ret{toLog2(_difficulty), 0}; static std::mt19937_64 s_eng((time(0))); o_solution = std::uniform_int_distribution(0, ~(uint)0)(s_eng); // evaluate until we run out of time 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; + { + auto e = (u256)eval(_root, o_solution); + ret.best = max(ret.best, (uint)log2((double)e)); + if (e > _difficulty) + return ret; + } + return ret; } #else diff --git a/libethereum/Dagger.h b/libethereum/Dagger.h index 2d9e1bba4..23dc57af1 100644 --- a/libethereum/Dagger.h +++ b/libethereum/Dagger.h @@ -7,6 +7,18 @@ namespace eth { +inline uint toLog2(u256 _d) +{ + return (uint)log2((double)_d); +} + +struct MineInfo +{ + uint requirement; + uint best; + bool completed() { return best > requirement; } +}; + #if FAKE_DAGGER class Dagger @@ -15,7 +27,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 const& _continue = bool(true)); + MineInfo mine(u256& o_solution, h256 const& _root, u256 const& _difficulty, uint _msTimeout = 100, bool const& _continue = bool(true)); }; #else diff --git a/libethereum/PeerNetwork.cpp b/libethereum/PeerNetwork.cpp index a8736376c..f68e460e7 100644 --- a/libethereum/PeerNetwork.cpp +++ b/libethereum/PeerNetwork.cpp @@ -21,11 +21,15 @@ #include "Common.h" #include "BlockChain.h" +#include "TransactionQueue.h" #include "PeerNetwork.h" using namespace std; using namespace eth; -PeerSession::PeerSession(PeerServer* _s, bi::tcp::socket _socket, uint _rNId): m_server(_s), m_socket(std::move(_socket)), m_reqNetworkId(_rNId) +PeerSession::PeerSession(PeerServer* _s, bi::tcp::socket _socket, uint _rNId): + m_server(_s), + m_socket(std::move(_socket)), + m_reqNetworkId(_rNId) { } @@ -59,11 +63,12 @@ bool PeerSession::interpret(RLP const& _r) break; } case Pong: - cout << "Latency: " << chrono::duration_cast(std::chrono::steady_clock::now() - m_ping).count() << " ms" << endl; + m_lastPing = std::chrono::steady_clock::now() - m_ping; + cout << "Latency: " << chrono::duration_cast(m_lastPing).count() << " ms" << endl; break; case GetPeers: { - std::vector peers = m_server->peers(); + std::vector peers = m_server->potentialPeers(); RLPStream s; prep(s).appendList(2); s << Peers; @@ -134,29 +139,39 @@ RLPStream& PeerSession::prep(RLPStream& _s) return _s.appendRaw(bytes(8, 0)); } +void PeerSession::seal(bytes& _b) +{ + _b[0] = 0x22; + _b[1] = 0x40; + _b[2] = 0x08; + _b[3] = 0x91; + uint32_t len = _b.size() - 8; + _b[4] = len >> 24; + _b[5] = len >> 16; + _b[6] = len >> 8; + _b[7] = len; +} + void PeerSession::sealAndSend(RLPStream& _s) { bytes b; _s.swapOut(b); - b[0] = 0x22; - b[1] = 0x40; - b[2] = 0x08; - b[3] = 0x91; - uint32_t len = b.size() - 8; - b[4] = len >> 24; - b[5] = len >> 16; - b[6] = len >> 8; - b[7] = len; - send(b); + sendDestroy(b); } -void PeerSession::send(bytes& _msg) +void PeerSession::sendDestroy(bytes& _msg) { std::shared_ptr buffer = std::make_shared(); swap(*buffer, _msg); ba::async_write(m_socket, ba::buffer(*buffer), [=](boost::system::error_code ec, std::size_t length) {}); } +void PeerSession::send(bytesConstRef _msg) +{ + std::shared_ptr buffer = std::make_shared(_msg.toBytes()); + ba::async_write(m_socket, ba::buffer(*buffer), [=](boost::system::error_code ec, std::size_t length) {}); +} + void PeerSession::disconnect() { RLPStream s; @@ -172,9 +187,11 @@ void PeerSession::start() cout << "Starting session." << endl; RLPStream s; prep(s); - s.appendList(4) << (uint)Hello << (uint)0 << (uint)0 << "Ethereum++/0.1.0"; + s.appendList(4) << (uint)Hello << (uint)0 << (uint)0 << m_server->m_clientVersion; sealAndSend(s); + ping(); doRead(); + // TODO: ask for latest block chain. } void PeerSession::doRead() @@ -207,7 +224,8 @@ void PeerSession::doRead() }); } -PeerServer::PeerServer(BlockChain const& _ch, uint _networkId, short _port): +PeerServer::PeerServer(std::string const& _clientVersion, BlockChain const& _ch, uint _networkId, short _port): + m_clientVersion(_clientVersion), m_chain(&_ch), m_acceptor(m_ioService, bi::tcp::endpoint(bi::tcp::v4(), _port)), m_socket(m_ioService), @@ -216,14 +234,15 @@ PeerServer::PeerServer(BlockChain const& _ch, uint _networkId, short _port): doAccept(); } -PeerServer::PeerServer(uint _networkId): +PeerServer::PeerServer(std::string const& _clientVersion, uint _networkId): + m_clientVersion(_clientVersion), m_acceptor(m_ioService, bi::tcp::endpoint(bi::tcp::v4(), 0)), m_socket(m_ioService), m_requiredNetworkId(_networkId) { } -std::vector PeerServer::peers() +std::vector PeerServer::potentialPeers() { std::vector ret; bool haveLocal = false; @@ -275,7 +294,7 @@ bool PeerServer::connect(string const& _addr, uint _port) } } -void PeerServer::process(BlockChain& _bc, TransactionQueue const& _tq) +void PeerServer::process(BlockChain& _bc) { m_ioService.poll(); for (auto i = m_peers.begin(); i != m_peers.end();) @@ -283,16 +302,101 @@ void PeerServer::process(BlockChain& _bc, TransactionQueue const& _tq) {} else i = m_peers.erase(i); - /* - while (incomingData()) +} + +void PeerServer::process(BlockChain& _bc, TransactionQueue& _tq, Overlay& _o) +{ + if (m_latestBlockSent == h256()) + { + // First time - just initialise. + m_latestBlockSent = _bc.currentHash(); + for (auto const& i: _tq.transactions()) + m_transactionsSent.insert(i.first); + } + + process(_bc); + + for (auto it = m_incomingTransactions.begin(); it != m_incomingTransactions.end();) + if (!_tq.import(*it)) + m_transactionsSent.insert(sha3(*it)); // if we already had the transaction, then don't bother sending it on. + m_incomingTransactions.clear(); + + // Send any new transactions. + { + bytes b; + uint n = 0; + for (auto const& i: _tq.transactions()) + if (!m_transactionsSent.count(i.first)) + { + b += i.second; + ++n; + m_transactionsSent.insert(i.first); + } + if (n) { - // import new block - bytes const& data = net.incomingData(); - if (!tq.attemptImport(data) && !_bc.attemptImport(data)) - handleMessage(data); - popIncoming(); + RLPStream ts; + PeerSession::prep(ts); + ts.appendList(2) << Transactions; + ts.appendList(n).appendRaw(b).swapOut(b); + PeerSession::seal(b); + for (auto j: m_peers) + if (auto p = j.lock()) + p->send(&b); } - */ + } + + // Send any new blocks. + { + auto h = _bc.currentHash(); + if (h != m_latestBlockSent) + { + // TODO: find where they diverge and send complete new branch. + RLPStream ts; + PeerSession::prep(ts); + ts.appendList(2) << Blocks; + bytes b; + ts.appendList(1).appendRaw(_bc.block(_bc.currentHash())).swapOut(b); + PeerSession::seal(b); + for (auto j: m_peers) + if (auto p = j.lock()) + p->send(&b); + } + } + + for (bool accepted = 1; accepted;) + { + accepted = 0; + for (auto it = m_incomingBlocks.begin(); it != m_incomingBlocks.end();) + { + try + { + _bc.import(*it, _o); + it = m_incomingBlocks.erase(it); + ++accepted; + } + catch (UnknownParent) + { + // Don't (yet) know its parent. Leave it for later. + ++it; + } + catch (...) + { + // Some other error - erase it. + it = m_incomingBlocks.erase(it); + } + } + } +} + +std::vector PeerServer::peers() const +{ + const_cast(this)->pingAll(); + usleep(200000); + std::vector ret; + for (auto& i: m_peers) + if (auto j = i.lock()) + ret.push_back(PeerInfo{j->m_clientVersion, j->m_socket.remote_endpoint(), j->m_lastPing}); + return ret; } void PeerServer::pingAll() diff --git a/libethereum/PeerNetwork.h b/libethereum/PeerNetwork.h index c09930e0d..4e0014980 100644 --- a/libethereum/PeerNetwork.h +++ b/libethereum/PeerNetwork.h @@ -71,9 +71,11 @@ private: void doWrite(std::size_t length); bool interpret(RLP const& _r); - RLPStream& prep(RLPStream& _s); + static RLPStream& prep(RLPStream& _s); + static void seal(bytes& _b); void sealAndSend(RLPStream& _s); - void send(bytes& _msg); + void sendDestroy(bytes& _msg); + void send(bytesConstRef _msg); PeerServer* m_server; bi::tcp::socket m_socket; @@ -86,6 +88,14 @@ private: uint m_reqNetworkId; std::chrono::steady_clock::time_point m_ping; + std::chrono::steady_clock::duration m_lastPing; +}; + +struct PeerInfo +{ + std::string clientVersion; + bi::tcp::endpoint endpoint; + std::chrono::steady_clock::duration lastPing; }; class PeerServer @@ -94,9 +104,9 @@ class PeerServer public: /// Start server, listening for connections on the given port. - PeerServer(BlockChain const& _ch, uint _networkId, short _port); + PeerServer(std::string const& _clientVersion, BlockChain const& _ch, uint _networkId, short _port); /// Start server, but don't listen. - PeerServer(uint _networkId); + PeerServer(std::string const& _clientVersion, uint _networkId); /// Connect to a peer explicitly. bool connect(std::string const& _addr = "127.0.0.1", uint _port = 30303); @@ -104,27 +114,26 @@ public: /// Sync with the BlockChain. It might contain one of our mined blocks, we might have new candidates from the network. /// Conduct I/O, polling, syncing, whatever. /// Ideally all time-consuming I/O is done in a background thread, but you get this call every 100ms or so anyway. - void process(BlockChain& _bc, TransactionQueue const&); - - /// Get an incoming transaction from the queue. @returns bytes() if nothing waiting. - std::vector const& incomingTransactions() { return m_incomingTransactions; } - - /// Get an incoming transaction from the queue. @returns bytes() if nothing waiting. - void incomingTransactions(std::vector& o_out) { swap(o_out, m_incomingTransactions); m_incomingTransactions.clear(); } - - /// Get an incoming transaction from the queue. @returns bytes() if nothing waiting. - void incomingBlocks(std::vector& o_out) { swap(o_out, m_blocks); m_blocks.clear(); } + void process(BlockChain& _bc, TransactionQueue&, Overlay& _o); + void process(BlockChain& _bc); /// Get number of peers connected. unsigned peerCount() const { return m_peers.size(); } - /// Remove incoming transactions from the queue. - void clearIncomingTransactions() {} + /// Set ideal number of peers. + void setIdealPeerCount(uint _n) { m_idealPeerCount = _n; } + + /// Get peer information. + std::vector peers() const; + + /// Ping the peers. void pingAll(); private: void doAccept(); - std::vector peers(); + std::vector potentialPeers(); + + std::string m_clientVersion; BlockChain const* m_chain = nullptr; ba::io_service m_ioService; @@ -137,6 +146,11 @@ private: std::vector m_incomingTransactions; std::vector m_incomingBlocks; std::vector m_incomingPeers; + + h256 m_latestBlockSent; + std::set m_transactionsSent; + + unsigned m_idealPeerCount; }; diff --git a/libethereum/State.cpp b/libethereum/State.cpp index 4a7bd099d..e1f782d49 100644 --- a/libethereum/State.cpp +++ b/libethereum/State.cpp @@ -360,7 +360,7 @@ void State::commitToMine(BlockChain const& _bc) m_currentBlock.parentHash = m_previousBlock.hash; } -bytes const& State::mine(uint _msTimeout) +MineInfo State::mine(uint _msTimeout) { // Update timestamp according to clock. m_currentBlock.timestamp = time(0); @@ -369,11 +369,12 @@ bytes const& State::mine(uint _msTimeout) m_currentBlock.difficulty = m_currentBlock.calculateDifficulty(m_previousBlock); // 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)) + MineInfo ret = m_dagger.mine(/*out*/m_currentBlock.nonce, m_currentBlock.headerHashWithoutNonce(), m_currentBlock.difficulty, _msTimeout); + if (ret.completed()) { // 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? + // Commit to disk. m_db.commit(); // Compile block: @@ -388,7 +389,7 @@ bytes const& State::mine(uint _msTimeout) else m_currentBytes.clear(); - return m_currentBytes; + return ret; } bool State::isNormalAddress(Address _id) const diff --git a/libethereum/State.h b/libethereum/State.h index 8e73934a3..32e413614 100644 --- a/libethereum/State.h +++ b/libethereum/State.h @@ -50,6 +50,11 @@ public: /// Construct state object. State(Address _coinbaseAddress, Overlay const& _db); + /// Set the coinbase address for any transactions we do. + /// This causes a complete reset of current block. + void setAddress(Address _coinbaseAddress) { m_ourAddress = _coinbaseAddress; resetCurrent(); } + Address address() const { return m_ourAddress; } + /// 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); } @@ -70,7 +75,7 @@ public: /// @param _msTimeout Timeout before return in milliseconds. /// @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); + MineInfo mine(uint _msTimeout = 1000); /// Get the complete current block, including valid nonce. /// Only valid after mine() returns true. diff --git a/libethereum/TransactionQueue.cpp b/libethereum/TransactionQueue.cpp index 77abec707..e1d555a71 100644 --- a/libethereum/TransactionQueue.cpp +++ b/libethereum/TransactionQueue.cpp @@ -24,18 +24,27 @@ using namespace std; using namespace eth; -void TransactionQueue::import(bytes const& _block) +bool TransactionQueue::import(bytes const& _block) { // Check if we already know this transaction. h256 h = sha3(_block); if (m_data.count(h)) - return; + return false; - // 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); - t.sender(); + try + { + // 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); + t.sender(); - // If valid, append to blocks. - m_data[h] = _block; + // If valid, append to blocks. + m_data[h] = _block; + } + catch (...) + { + return false; + } + + return true; } diff --git a/libethereum/TransactionQueue.h b/libethereum/TransactionQueue.h index 1ab2cec41..f4aa67e28 100644 --- a/libethereum/TransactionQueue.h +++ b/libethereum/TransactionQueue.h @@ -36,7 +36,7 @@ class TransactionQueue public: bool attemptImport(bytes const& _block) { try { import(_block); return true; } catch (...) { return false; } } - void import(bytes const& _block); + bool import(bytes const& _block); void drop(h256 _txHash) { m_data.erase(_txHash); } diff --git a/test/peer.cpp b/test/peer.cpp index 14bf790e1..5a440a88f 100644 --- a/test/peer.cpp +++ b/test/peer.cpp @@ -46,7 +46,7 @@ int peerTest(int argc, char** argv) } BlockChain ch("/tmp"); - PeerServer pn(ch, 0, listenPort); + PeerServer pn("Test", ch, 0, listenPort); if (!remoteHost.empty()) pn.connect(remoteHost, remotePort); @@ -54,7 +54,7 @@ int peerTest(int argc, char** argv) for (int i = 0; ; ++i) { usleep(100000); - pn.process(); + pn.process(ch); if (!(i % 10)) pn.pingAll(); } diff --git a/test/state.cpp b/test/state.cpp index d4201c570..ed9d05aab 100644 --- a/test/state.cpp +++ b/test/state.cpp @@ -47,7 +47,7 @@ int stateTest() // Mine to get some ether! s.commitToMine(bc); - while (s.mine(100).empty()) {} + while (s.mine(100).completed()) {} bc.attemptImport(s.blockData(), stateDB); cout << bc; @@ -74,7 +74,7 @@ int stateTest() // Mine to get some ether and set in stone. s.commitToMine(bc); - while (s.mine(100).empty()) {} + while (s.mine(100).completed()) {} bc.attemptImport(s.blockData(), stateDB); cout << bc;