From 114354126e093c3b6af44c23a62b4fcd6a8e2523 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 23 Jul 2014 20:52:37 +0200 Subject: [PATCH 001/223] First set of commits. Not yet working. --- alethzero/Main.ui | 6 + alethzero/MainWin.cpp | 418 ++++++++++++++++++++++------------- alethzero/MainWin.h | 54 +++-- eth/EthStubServer.cpp | 7 +- eth/eth.js | 10 +- eth/main.cpp | 1 - libethential/FixedHash.h | 2 +- libethential/RLP.h | 10 +- libethereum/BlockChain.cpp | 38 +++- libethereum/BlockChain.h | 31 ++- libethereum/Client.cpp | 286 ++++++++++++++++-------- libethereum/Client.h | 122 ++++++---- libethereum/PeerServer.cpp | 12 +- libethereum/PeerServer.h | 2 +- libethereum/PeerSession.cpp | 6 +- libethereum/State.cpp | 19 +- libethereum/State.h | 10 +- libqethereum/QEthereum.cpp | 227 ++++++------------- libqethereum/QEthereum.h | 372 +++++-------------------------- libqethereum/QmlEthereum.cpp | 188 ++++++++++++++++ libqethereum/QmlEthereum.h | 283 ++++++++++++++++++++++++ neth/main.cpp | 1 - walleth/MainWin.cpp | 6 +- walleth/MainWin.h | 5 +- 24 files changed, 1293 insertions(+), 823 deletions(-) create mode 100644 libqethereum/QmlEthereum.cpp create mode 100644 libqethereum/QmlEthereum.h diff --git a/alethzero/Main.ui b/alethzero/Main.ui index 53466c510..282447b79 100644 --- a/alethzero/Main.ui +++ b/alethzero/Main.ui @@ -485,6 +485,7 @@ + @@ -1637,6 +1638,11 @@ font-size: 14pt &Pretty... + + + &Refresh + + diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index a892bc125..c0e59cf7c 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -90,6 +90,32 @@ static void initUnits(QComboBox* _b) _b->addItem(QString::fromStdString(units()[n].second), n); } +static QString fromRaw(eth::h256 _n, unsigned* _inc = nullptr) +{ + if (_n) + { + std::string s((char const*)_n.data(), 32); + auto l = s.find_first_of('\0'); + if (!l) + return QString(); + if (l != string::npos) + { + auto p = s.find_first_not_of('\0', l); + if (!(p == string::npos || (_inc && p == 31))) + return QString(); + if (_inc) + *_inc = (byte)s[31]; + s.resize(l); + } + for (auto i: s) + if (i < 32) + return QString(); + return QString::fromStdString(s); + } + return QString(); +} + + Address c_config = Address("661005d2720d855f1d9976f88bb10c1a3398c77f"); Main::Main(QWidget *parent) : @@ -145,24 +171,22 @@ Main::Main(QWidget *parent) : connect(ui->ourAccounts->model(), SIGNAL(rowsMoved(const QModelIndex &, int, int, const QModelIndex &, int)), SLOT(ourAccountsRowsMoved())); m_client.reset(new Client("AlethZero")); - m_client->start(); connect(ui->webView, &QWebView::loadStarted, [this]() { - QEthereum *eth = new QEthereum(this, this->m_client.get(), this->owned()); - this->m_ethereum = eth; - connect(this, SIGNAL(changed()), this->m_ethereum, SIGNAL(changed())); + // NOTE: no need to delete as QETH_INSTALL_JS_NAMESPACE adopts it. + m_ethereum = new QEthereum(this, m_client.get(), owned()); - QWebFrame* f = this->ui->webView->page()->mainFrame(); + QWebFrame* f = ui->webView->page()->mainFrame(); f->disconnect(SIGNAL(javaScriptWindowObjectCleared())); - eth->setup(f); - f->addToJavaScriptWindowObject("env", this, QWebFrame::QtOwnership); - connect(f, &QWebFrame::javaScriptWindowObjectCleared, QETH_INSTALL_JS_NAMESPACE); + m_ethereum->setup(f); + auto qeth = m_ethereum; + connect(f, &QWebFrame::javaScriptWindowObjectCleared, QETH_INSTALL_JS_NAMESPACE(f, qeth, this)); }); connect(ui->webView, &QWebView::loadFinished, [=]() { - this->changed(); + m_ethereum->poll(); }); connect(ui->webView, &QWebView::titleChanged, [=]() @@ -171,11 +195,10 @@ Main::Main(QWidget *parent) : }); readSettings(); - refresh(); - m_refresh = new QTimer(this); - connect(m_refresh, SIGNAL(timeout()), SLOT(refresh())); - m_refresh->start(100); + installWatches(); + + startTimer(100); { QSettings s("ethereum", "alethzero"); @@ -185,7 +208,6 @@ Main::Main(QWidget *parent) : s.setValue("splashMessage", false); } } - m_pcWarp.clear(); } Main::~Main() @@ -194,6 +216,106 @@ Main::~Main() writeSettings(); } +void Main::onKeysChanged() +{ + installBalancesWatch(); +} + +unsigned Main::installWatch(eth::TransactionFilter const& _tf, std::function const& _f) +{ + auto ret = m_client->installWatch(_tf); + m_handlers[ret] = _f; + return ret; +} + +unsigned Main::installWatch(eth::h256 _tf, std::function const& _f) +{ + auto ret = m_client->installWatch(_tf); + m_handlers[ret] = _f; + return ret; +} + +void Main::installWatches() +{ + installWatch(eth::TransactionFilter().altered(c_config, 0), [=](){ installNameRegWatch(); }); + installWatch(eth::TransactionFilter().altered(c_config, 1), [=](){ installCurrenciesWatch(); }); + installWatch(eth::NewPendingFilter, [=](){ onNewPending(); }); + installWatch(eth::NewBlockFilter, [=](){ onNewBlock(); }); +} + +void Main::installNameRegWatch() +{ + m_client->uninstallWatch(m_nameRegFilter); + m_nameRegFilter = installWatch(eth::TransactionFilter().altered((u160)state().storage(c_config, 0)), [=](){ onNameRegChange(); }); +} + +void Main::installCurrenciesWatch() +{ + m_client->uninstallWatch(m_currenciesFilter); + m_currenciesFilter = installWatch(eth::TransactionFilter().altered((u160)m_client->stateAt(c_config, 1)), [=](){ onCurrenciesChange(); }); +} + +void Main::installBalancesWatch() +{ + eth::TransactionFilter tf; + + vector
altCoins; + Address coinsAddr = right160(m_client->stateAt(c_config, 1)); + for (unsigned i = 0; i < m_client->stateAt(coinsAddr, 0); ++i) + altCoins.push_back(right160(m_client->stateAt(coinsAddr, i + 1))); + for (auto i: m_myKeys) + { + tf.altered(i.address()); + for (auto c: altCoins) + tf.altered(c, (u160)i.address()); + } + + m_client->uninstallWatch(m_balancesFilter); + m_balancesFilter = installWatch(tf, [=](){ onBalancesChange(); }); +} + +void Main::onNameRegChange() +{ + cdebug << "NameReg changed!"; + + // update any namereg-dependent stuff - for now force a full update. + refreshAll(); +} + +void Main::onCurrenciesChange() +{ + cdebug << "Currencies changed!"; + installBalancesWatch(); + + // TODO: update any currency-dependent stuff? +} + +void Main::onBalancesChange() +{ + cdebug << "Our balances changed!"; + + refreshBalances(); +} + +void Main::onNewBlock() +{ + cdebug << "Blockchain changed!"; + + // update blockchain dependent views. + refreshBlockCount(); + refreshBlockChain(); + refreshAccounts(); +} + +void Main::onNewPending() +{ + cdebug << "Pending transactions changed!"; + + // update any pending-transaction dependent views. + refreshPending(); + refreshAccounts(); +} + void Main::on_clearPending_triggered() { m_client->clearPending(); @@ -276,40 +398,15 @@ void Main::eval(QString const& _js) ui->jsConsole->setHtml(s); } -QString fromRaw(eth::h256 _n, unsigned* _inc = nullptr) -{ - if (_n) - { - std::string s((char const*)_n.data(), 32); - auto l = s.find_first_of('\0'); - if (!l) - return QString(); - if (l != string::npos) - { - auto p = s.find_first_not_of('\0', l); - if (!(p == string::npos || (_inc && p == 31))) - return QString(); - if (_inc) - *_inc = (byte)s[31]; - s.resize(l); - } - for (auto i: s) - if (i < 32) - return QString(); - return QString::fromStdString(s); - } - return QString(); -} - QString Main::pretty(eth::Address _a) const { h256 n; - if (h160 nameReg = (u160)state().storage(c_config, 0)) - n = state().storage(nameReg, (u160)(_a)); + if (h160 nameReg = (u160)m_client->stateAt(c_config, 0)) + n = m_client->stateAt(nameReg, (u160)(_a)); if (!n) - n = state().storage(m_nameReg, (u160)(_a)); + n = m_client->stateAt(m_nameReg, (u160)(_a)); return fromRaw(n); } @@ -470,12 +567,18 @@ void Main::on_nameReg_textChanged() if (s.size() == 40) { m_nameReg = Address(fromHex(s)); - refresh(true); + refreshAll(); } else m_nameReg = Address(); } +void Main::on_preview_triggered() +{ + m_client->setDefault(ui->preview->isChecked() ? 0 : -1); + refreshAll(); +} + void Main::refreshMining() { eth::ClientGuard g(m_client.get()); @@ -495,6 +598,33 @@ void Main::refreshMining() */ } +void Main::refreshBalances() +{ + // update all the balance-dependent stuff. + ui->ourAccounts->clear(); + u256 totalBalance = 0; + map> altCoins; + Address coinsAddr = right160(m_client->stateAt(c_config, 1)); + for (unsigned i = 0; i < m_client->stateAt(coinsAddr, 0); ++i) + altCoins[right160(m_client->stateAt(coinsAddr, m_client->stateAt(coinsAddr, i + 1)))] = make_pair(fromRaw(m_client->stateAt(coinsAddr, i + 1)), 0); + for (auto i: m_myKeys) + { + u256 b = m_client->balanceAt(i.address()); + (new QListWidgetItem(QString("%2: %1 [%3]").arg(formatBalance(b).c_str()).arg(render(i.address())).arg((unsigned)m_client->countAt(i.address())), ui->ourAccounts)) + ->setData(Qt::UserRole, QByteArray((char const*)i.address().data(), Address::size)); + totalBalance += b; + + for (auto& c: altCoins) + c.second.second += (u256)m_client->stateAt(c.first, (u160)i.address()); + } + + QString b; + for (auto const& c: altCoins) + if (c.second.second) + b += QString::fromStdString(toString(c.second.second)) + " " + c.second.first.toUpper() + " | "; + ui->balance->setText(b + QString::fromStdString(formatBalance(totalBalance))); +} + void Main::refreshNetwork() { auto ps = m_client->peers(); @@ -510,24 +640,78 @@ eth::State const& Main::state() const return ui->preview->isChecked() ? m_client->postState() : m_client->state(); } -void Main::updateBlockCount() +void Main::refreshAll() { - auto d = m_client->blockChain().details(); - auto diff = BlockInfo(m_client->blockChain().block()).difficulty; - ui->blockCount->setText(QString("#%1 @%3 T%2 N%4 D%5").arg(d.number).arg(toLog2(d.totalDifficulty)).arg(toLog2(diff)).arg(eth::c_protocolVersion).arg(eth::c_databaseVersion)); + refreshDestination(); + refreshBlockChain(); + refreshBlockCount(); + refreshPending(); + refreshAccounts(); } -void Main::on_blockChainFilter_textChanged() +void Main::refreshPending() { - static QTimer* s_delayed = nullptr; - if (!s_delayed) + cdebug << "refreshPending()"; + ui->transactionQueue->clear(); + for (Transaction const& t: m_client->pending()) { - s_delayed = new QTimer(this); - s_delayed->setSingleShot(true); - connect(s_delayed, SIGNAL(timeout()), SLOT(refreshBlockChain())); + QString s = t.receiveAddress ? + QString("%2 %5> %3: %1 [%4]") + .arg(formatBalance(t.value).c_str()) + .arg(render(t.safeSender())) + .arg(render(t.receiveAddress)) + .arg((unsigned)t.nonce) + .arg(m_client->codeAt(t.receiveAddress).size() ? '*' : '-') : + QString("%2 +> %3: %1 [%4]") + .arg(formatBalance(t.value).c_str()) + .arg(render(t.safeSender())) + .arg(render(right160(sha3(rlpList(t.safeSender(), t.nonce))))) + .arg((unsigned)t.nonce); + ui->transactionQueue->addItem(s); } - s_delayed->stop(); - s_delayed->start(200); +} + +void Main::refreshAccounts() +{ + cdebug << "refreshAccounts()"; + ui->accounts->clear(); + ui->contracts->clear(); + for (auto n = 0; n < 2; ++n) + for (auto i: m_client->addresses()) + { + auto r = render(i); + if (r.contains('(') == !n) + { + if (n == 0 || ui->showAllAccounts->isChecked()) + (new QListWidgetItem(QString("%2: %1 [%3]").arg(formatBalance(m_client->balanceAt(i)).c_str()).arg(r).arg((unsigned)m_client->countAt(i)), ui->accounts)) + ->setData(Qt::UserRole, QByteArray((char const*)i.data(), Address::size)); + if (m_client->codeAt(i).size()) + (new QListWidgetItem(QString("%2: %1 [%3]").arg(formatBalance(m_client->balanceAt(i)).c_str()).arg(r).arg((unsigned)m_client->countAt(i)), ui->contracts)) + ->setData(Qt::UserRole, QByteArray((char const*)i.data(), Address::size)); + } + } +} + +void Main::refreshDestination() +{ + cdebug << "refreshDestination()"; + QString s; + for (auto i: m_client->addresses()) + if ((s = pretty(i)).size()) + // A namereg address + if (ui->destination->findText(s, Qt::MatchExactly | Qt::MatchCaseSensitive) == -1) + ui->destination->addItem(s); + for (int i = 0; i < ui->destination->count(); ++i) + if (ui->destination->itemText(i) != "(Create Contract)" && !fromString(ui->destination->itemText(i))) + ui->destination->removeItem(i--); +} + +void Main::refreshBlockCount() +{ + cdebug << "refreshBlockCount()"; + auto d = m_client->blockChain().details(); + auto diff = BlockInfo(m_client->blockChain().block()).difficulty; + ui->blockCount->setText(QString("#%1 @%3 T%2 N%4 D%5").arg(d.number).arg(toLog2(d.totalDifficulty)).arg(toLog2(diff)).arg(eth::c_protocolVersion).arg(eth::c_databaseVersion)); } static bool blockMatch(string const& _f, eth::BlockDetails const& _b, h256 _h, BlockChain const& _bc) @@ -557,6 +741,7 @@ static bool transactionMatch(string const& _f, Transaction const& _t) void Main::refreshBlockChain() { + cdebug << "refreshBlockChain()"; eth::ClientGuard g(m_client.get()); auto const& st = state(); @@ -612,113 +797,49 @@ void Main::refreshBlockChain() ui->blocks->setCurrentRow(0); } -void Main::refresh(bool _override) +void Main::on_blockChainFilter_textChanged() +{ + static QTimer* s_delayed = nullptr; + if (!s_delayed) + { + s_delayed = new QTimer(this); + s_delayed->setSingleShot(true); + connect(s_delayed, SIGNAL(timeout()), SLOT(refreshBlockChain())); + } + s_delayed->stop(); + s_delayed->start(200); +} + +void Main::on_refresh_triggered() +{ + refreshAll(); +} + +void Main::timerEvent(QTimerEvent*) { // 7/18, Alex: aggregating timers, prelude to better threading? // Runs much faster on slower dual-core processors static int interval = 100; // refresh mining every 200ms - if(interval / 100 % 2 == 0) + if (interval / 100 % 2 == 0) refreshMining(); // refresh peer list every 1000ms, reset counter - if(interval == 1000) + if (interval == 1000) { interval = 0; refreshNetwork(); - } else + } + else interval += 100; - - eth::ClientGuard g(m_client.get()); - auto const& st = state(); - - bool c = m_client->changed(); - if (c || _override) - { - changed(); - - updateBlockCount(); - - auto acs = st.addresses(); - ui->accounts->clear(); - ui->contracts->clear(); - for (auto n = 0; n < 2; ++n) - for (auto i: acs) - { - auto r = render(i.first); - if (r.contains('(') == !n) - { - if (n == 0 || ui->showAllAccounts->isChecked()) - (new QListWidgetItem(QString("%2: %1 [%3]").arg(formatBalance(i.second).c_str()).arg(r).arg((unsigned)state().transactionsFrom(i.first)), ui->accounts)) - ->setData(Qt::UserRole, QByteArray((char const*)i.first.data(), Address::size)); - if (st.addressHasCode(i.first)) - (new QListWidgetItem(QString("%2: %1 [%3]").arg(formatBalance(i.second).c_str()).arg(r).arg((unsigned)st.transactionsFrom(i.first)), ui->contracts)) - ->setData(Qt::UserRole, QByteArray((char const*)i.first.data(), Address::size)); - - if (r.contains('(')) - { - // A namereg address - QString s = pretty(i.first); - if (ui->destination->findText(s, Qt::MatchExactly | Qt::MatchCaseSensitive) == -1) - ui->destination->addItem(s); - } - } - } - - for (int i = 0; i < ui->destination->count(); ++i) - if (ui->destination->itemText(i) != "(Create Contract)" && !fromString(ui->destination->itemText(i))) - ui->destination->removeItem(i--); - - ui->transactionQueue->clear(); - for (Transaction const& t: m_client->pending()) - { - QString s = t.receiveAddress ? - QString("%2 %5> %3: %1 [%4]") - .arg(formatBalance(t.value).c_str()) - .arg(render(t.safeSender())) - .arg(render(t.receiveAddress)) - .arg((unsigned)t.nonce) - .arg(st.addressHasCode(t.receiveAddress) ? '*' : '-') : - QString("%2 +> %3: %1 [%4]") - .arg(formatBalance(t.value).c_str()) - .arg(render(t.safeSender())) - .arg(render(right160(sha3(rlpList(t.safeSender(), t.nonce))))) - .arg((unsigned)t.nonce); - ui->transactionQueue->addItem(s); - } - - refreshBlockChain(); - } - - if (c || m_keysChanged || _override) - { - m_keysChanged = false; - ui->ourAccounts->clear(); - u256 totalBalance = 0; - map> altCoins; - Address coinsAddr = right160(st.storage(c_config, 1)); - for (unsigned i = 0; i < st.storage(coinsAddr, 0); ++i) - altCoins[right160(st.storage(coinsAddr, st.storage(coinsAddr, i + 1)))] = make_pair(fromRaw(st.storage(coinsAddr, i + 1)), 0); -// u256 totalGavCoinBalance = 0; - for (auto i: m_myKeys) - { - u256 b = st.balance(i.address()); - (new QListWidgetItem(QString("%2: %1 [%3]").arg(formatBalance(b).c_str()).arg(render(i.address())).arg((unsigned)st.transactionsFrom(i.address())), ui->ourAccounts)) - ->setData(Qt::UserRole, QByteArray((char const*)i.address().data(), Address::size)); - totalBalance += b; - - for (auto& c: altCoins) - c.second.second += (u256)st.storage(c.first, (u160)i.address()); - } + if (m_ethereum) + m_ethereum->poll(); - QString b; - for (auto const& c: altCoins) - if (c.second.second) - b += QString::fromStdString(toString(c.second.second)) + " " + c.second.first.toUpper() + " | "; - ui->balance->setText(b + QString::fromStdString(formatBalance(totalBalance))); - } + for (auto const& i: m_handlers) + if (m_client->checkWatch(i.first)) + i.second(); } string Main::renderDiff(eth::StateDiff const& _d) const @@ -846,7 +967,6 @@ void Main::on_inject_triggered() QString s = QInputDialog::getText(this, "Inject Transaction", "Enter transaction dump in hex"); bytes b = fromHex(s.toStdString()); m_client->inject(&b); - refresh(); } void Main::on_blocks_currentItemChanged() @@ -1203,7 +1323,6 @@ void Main::on_killBlockchain_triggered() ui->net->setChecked(false); m_client.reset(); m_client.reset(new Client("AlethZero", Address(), string(), true)); - m_client->start(); readSettings(); } @@ -1321,7 +1440,6 @@ void Main::on_send_clicked() m_client->transact(s, value(), m_data, ui->gas->value(), gasPrice()); else m_client->transact(s, value(), fromString(ui->destination->currentText()), m_data, ui->gas->value(), gasPrice()); - refresh(); return; } statusBar()->showMessage("Couldn't make transaction: no single account contains at least the required amount."); diff --git a/alethzero/MainWin.h b/alethzero/MainWin.h index 99b337a20..7117618ea 100644 --- a/alethzero/MainWin.h +++ b/alethzero/MainWin.h @@ -39,6 +39,7 @@ class Main; namespace eth { class Client; class State; +class TransactionFilter; } class QQuickView; @@ -78,6 +79,8 @@ public slots: void debug(QString _entry); void warn(QString _entry); + void onKeysChanged(); + private slots: void eval(QString const& _js); @@ -106,7 +109,7 @@ private slots: void on_about_triggered(); void on_paranoia_triggered(); void on_nameReg_textChanged(); - void on_preview_triggered() { refresh(true); } + void on_preview_triggered(); void on_quit_triggered() { close(); } void on_urlEdit_returnPressed(); void on_debugStep_triggered(); @@ -118,8 +121,8 @@ private slots: void on_importKey_triggered(); void on_exportKey_triggered(); void on_inject_triggered(); - void on_showAll_triggered() { refresh(true); } - void on_showAllAccounts_triggered() { refresh(true); } + void on_showAll_triggered() { refreshBlockChain(); } + void on_showAllAccounts_triggered() { refreshAccounts(); } void on_loadJS_triggered(); void on_blockChainFilter_textChanged(); void on_clearPending_triggered(); @@ -134,18 +137,12 @@ private slots: void on_debugCurrent_triggered(); void on_debugDumpState_triggered(int _add = 1); void on_debugDumpStatePre_triggered(); - - void refresh(bool _override = false); - void refreshNetwork(); - void refreshMining(); - void refreshBlockChain(); + void on_refresh_triggered(); signals: - void changed(); // TODO: manifest + void poll(); private: - void updateBlockCount(); - QString pretty(eth::Address _a) const; QString prettyU256(eth::u256 _n) const; @@ -171,13 +168,44 @@ private: eth::u256 value() const; eth::u256 gasPrice() const; + unsigned installWatch(eth::TransactionFilter const& _tf, std::function const& _f); + unsigned installWatch(eth::h256 _tf, std::function const& _f); + + void onNewPending(); + void onNewBlock(); + void onNameRegChange(); + void onCurrenciesChange(); + void onBalancesChange(); + + void installWatches(); + void installCurrenciesWatch(); + void installNameRegWatch(); + void installBalancesWatch(); + + virtual void timerEvent(QTimerEvent*); + + void refreshNetwork(); + void refreshMining(); + + void refreshAll(); + void refreshPending(); + void refreshAccounts(); + void refreshDestination(); + void refreshBlockChain(); + void refreshBlockCount(); + void refreshBalances(); + std::unique_ptr ui; std::unique_ptr m_client; + std::map> m_handlers; + unsigned m_nameRegFilter = (unsigned)-1; + unsigned m_currenciesFilter = (unsigned)-1; + unsigned m_balancesFilter = (unsigned)-1; QByteArray m_peers; QMutex m_guiLock; - QTimer* m_refresh; + QTimer* m_ticker; QTimer* m_refreshNetwork; QTimer* m_refreshMining; QStringList m_servers; @@ -202,5 +230,5 @@ private: QList> m_consoleHistory; - QEthereum* m_ethereum; + QEthereum* m_ethereum = nullptr; }; diff --git a/eth/EthStubServer.cpp b/eth/EthStubServer.cpp index 0d5182eb8..fc281a706 100644 --- a/eth/EthStubServer.cpp +++ b/eth/EthStubServer.cpp @@ -72,14 +72,15 @@ std::string EthStubServer::balanceAt(std::string const& _a) Json::Value EthStubServer::check(Json::Value const& _as) { - if (m_client.changed()) + // TODO +// if (m_client.changed()) return _as; - else +/* else { Json::Value ret; ret.resize(0); return ret; - } + }*/ } std::string EthStubServer::create(const std::string& _bCode, const std::string& _sec, const std::string& _xEndowment, const std::string& _xGas, const std::string& _xGasPrice) diff --git a/eth/eth.js b/eth/eth.js index 02c6f4b8a..733097723 100644 --- a/eth/eth.js +++ b/eth/eth.js @@ -75,19 +75,19 @@ window.eth = (function ethScope() { p[s.order[j]] = (s.order[j][0] === "b") ? a[j].unbin() : a[j] return p }; - if (m == "create" || m == "transact") - ret[m] = function() { return reqAsync(m, getParams(arguments), arguments[s.order.length]) } + if (m == "create" || m == "transact") + ret[m] = function() { return reqAsync(m, getParams(arguments), arguments[s.order.length]) } else { ret[am] = function() { return reqAsync(m, getParams(arguments), arguments[s.order.length]) } if (s.params) ret[m] = function() { return reqSync(m, getParams(arguments)) } - else + else Object.defineProperty(ret, m, { get: function() { return reqSync(m, {}); }, set: function(v) {} - }) - } + }) + } })(spec[si]); ret.check = function(force) { diff --git a/eth/main.cpp b/eth/main.cpp index 3ab4c5837..34361a7d3 100644 --- a/eth/main.cpp +++ b/eth/main.cpp @@ -292,7 +292,6 @@ int main(int argc, char** argv) if (!clientName.empty()) clientName += "/"; Client c("Ethereum(++)/" + clientName + "v" + eth::EthVersion + "/" ETH_QUOTED(ETH_BUILD_TYPE) "/" ETH_QUOTED(ETH_BUILD_PLATFORM), coinbase, dbPath); - c.start(); cout << credits(); cout << "Address: " << endl << toHex(us.address().asArray()) << endl; diff --git a/libethential/FixedHash.h b/libethential/FixedHash.h index f792e378d..07e257534 100644 --- a/libethential/FixedHash.h +++ b/libethential/FixedHash.h @@ -163,7 +163,7 @@ private: /// Fast equality operator for h256. template<> inline bool FixedHash<32>::operator==(FixedHash<32> const& _other) const { - const uint64_t* hash1 = (const uint64_t*)this->data(); + const uint64_t* hash1 = (const uint64_t*)data(); const uint64_t* hash2 = (const uint64_t*)_other.data(); return (hash1[0] == hash2[0]) && (hash1[1] == hash2[1]) && (hash1[2] == hash2[2]) && (hash1[3] == hash2[3]); } diff --git a/libethential/RLP.h b/libethential/RLP.h index a3f42a783..6d388763f 100644 --- a/libethential/RLP.h +++ b/libethential/RLP.h @@ -164,6 +164,10 @@ public: explicit operator u256() const { return toInt(); } explicit operator bigint() const { return toInt(); } template explicit operator FixedHash<_N>() const { return toHash>(); } + template explicit operator std::pair() const { return toPair(); } + template explicit operator std::vector() const { return toVector(); } + template explicit operator std::set() const { return toSet(); } + template explicit operator std::array() const { return toArray(); } /// Converts to bytearray. @returns the empty byte array if not a string. bytes toBytes() const { if (!isData()) return bytes(); return bytes(payload().data(), payload().data() + length()); } @@ -175,6 +179,8 @@ public: std::string toStringStrict() const { if (!isData()) throw BadCast(); return payload().cropped(0, length()).toString(); } template std::vector toVector() const { std::vector ret; if (isList()) { ret.reserve(itemCount()); for (auto const& i: *this) ret.push_back((T)i); } return ret; } + template std::set toSet() const { std::set ret; if (isList()) { for (auto const& i: *this) ret.insert((T)i); } return ret; } + template std::pair toPair() const { std::pair ret; if (isList()) { ret.first = (T)((*this)[0]); ret.second = (U)((*this)[1]); } return ret; } template std::array toArray() const { if (itemCount() != N || !isList()) throw BadCast(); std::array ret; for (uint i = 0; i < N; ++i) ret[i] = (T)operator[](i); return ret; } /// Int conversion flags @@ -285,8 +291,10 @@ public: /// Appends a sequence of data to the stream as a list. template RLPStream& append(std::vector<_T> const& _s) { return appendVector(_s); } - template RLPStream& append(std::array<_T, S> const& _s) { appendList(_s.size()); for (auto const& i: _s) append(i); return *this; } template RLPStream& appendVector(std::vector<_T> const& _s) { appendList(_s.size()); for (auto const& i: _s) append(i); return *this; } + template RLPStream& append(std::array<_T, S> const& _s) { appendList(_s.size()); for (auto const& i: _s) append(i); return *this; } + template RLPStream& append(std::set<_T> const& _s) { appendList(_s.size()); for (auto const& i: _s) append(i); return *this; } + template RLPStream& append(std::pair const& _s) { appendList(2); append(_s.first); append(_s.second); return *this; } /// Appends a list. RLPStream& appendList(uint _items); diff --git a/libethereum/BlockChain.cpp b/libethereum/BlockChain.cpp index 660bf3498..f2dd89b4c 100644 --- a/libethereum/BlockChain.cpp +++ b/libethereum/BlockChain.cpp @@ -196,7 +196,7 @@ inline ldb::Slice toSlice(h256 _h, unsigned _sub = 0) #endif } -void BlockChain::import(bytes const& _block, OverlayDB const& _db) +h256s BlockChain::import(bytes const& _block, OverlayDB const& _db) { // VERIFY: populates from the block and checks the block is internally coherent. BlockInfo bi; @@ -295,9 +295,11 @@ void BlockChain::import(bytes const& _block, OverlayDB const& _db) // cnote << "Parent " << bi.parentHash << " has " << details(bi.parentHash).children.size() << " children."; + h256s ret; // This might be the new best block... if (td > details(m_lastBlockHash).totalDifficulty) { + ret = treeRoute(m_lastBlockHash, newHash); m_lastBlockHash = newHash; m_extrasDB->Put(m_writeOptions, ldb::Slice("best"), ldb::Slice((char const*)&newHash, 32)); clog(BlockChainNote) << " Imported and best. Has" << (details(bi.parentHash).children.size() - 1) << "siblings."; @@ -306,6 +308,40 @@ void BlockChain::import(bytes const& _block, OverlayDB const& _db) { clog(BlockChainNote) << " Imported but not best (oTD:" << details(m_lastBlockHash).totalDifficulty << ", TD:" << td << ")"; } + return ret; +} + +h256s BlockChain::treeRoute(h256 _from, h256 _to, h256* o_common) const +{ + h256s ret; + h256s back; + unsigned fn = details(_from).number; + unsigned tn = details(_to).number; + while (fn > tn) + { + ret.push_back(_from); + _from = details(_from).parent; + fn--; + } + while (fn < tn) + { + back.push_back(_to); + _to = details(_to).parent; + tn--; + } + while (_from != _to) + { + _from = details(_from).parent; + _to = details(_to).parent; + ret.push_back(_from); + back.push_back(_to); + } + if (o_common) + *o_common = _from; + ret.reserve(ret.size() + back.size()); + for (auto it = back.cbegin(); it != back.cend(); ++it) + ret.push_back(*it); + return ret; } void BlockChain::checkConsistency() diff --git a/libethereum/BlockChain.h b/libethereum/BlockChain.h index 2eeda53c0..959ec1bdf 100644 --- a/libethereum/BlockChain.h +++ b/libethereum/BlockChain.h @@ -114,7 +114,8 @@ public: bool attemptImport(bytes const& _block, OverlayDB const& _stateDB); /// Import block into disk-backed DB - void import(bytes const& _block, OverlayDB const& _stateDB); + /// @returns the block hashes of any blocks that came into/went out of the canonical block chain. + h256s import(bytes const& _block, OverlayDB const& _stateDB); /// Get the familial details concerning a block (or the most recent mined if none given). Thread-safe. BlockDetails details(h256 _hash) const; @@ -144,13 +145,29 @@ public: /// Get the hash of a block of a given number. h256 numberHash(unsigned _n) const; - std::vector> interestQueue() { std::vector> ret; swap(ret, m_interestQueue); return ret; } - void pushInterest(Address _a) { m_interest[_a]++; } - void popInterest(Address _a) { if (m_interest[_a] > 1) m_interest[_a]--; else if (m_interest[_a]) m_interest.erase(_a); } - + /// @returns the genesis block header. static BlockInfo const& genesis() { if (!s_genesis) { auto gb = createGenesisBlock(); (s_genesis = new BlockInfo)->populate(&gb); } return *s_genesis; } + + /// @returns the genesis block as its RLP-encoded byte array. + /// @note This is slow as it's constructed anew each call. Consider genesis() instead. static bytes createGenesisBlock(); + /** @returns the hash of all blocks between @a _from and @a _to, all blocks are ordered first by a number of + * blocks that are parent-to-child, then two sibling blocks, then a number of blocks that are child-to-parent. + * + * If non-null, the h256 at @a o_common is set to the latest common ancestor of both blocks. + * + * e.g. if the block tree is 3a -> 2a -> 1a -> g and 2b -> 1b -> g (g is genesis, *a, *b are competing chains), + * then: + * @code + * treeRoute(3a, 2b) == { 3a, 2a, 1a, 1b, 2b }; // *o_common == g + * treeRoute(2a, 1a) == { 2a, 1a }; // *o_common == 1a + * treeRoute(1a, 2a) == { 1a, 2a }; // *o_common == 1a + * treeRoute(1b, 2a) == { 1b, 1a, 2a }; // *o_common == g + * @endcode + */ + h256s treeRoute(h256 _from, h256 _to, h256* o_common = nullptr) const; + private: void checkConsistency(); @@ -162,10 +179,6 @@ private: mutable std::map m_cache; mutable std::recursive_mutex m_lock; - /// The queue of transactions that have happened that we're interested in. - std::map m_interest; - std::vector> m_interestQueue; - ldb::DB* m_db; ldb::DB* m_extrasDB; diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index 3fc03b459..715c3a3cc 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -30,6 +30,18 @@ using namespace std; using namespace eth; +void TransactionFilter::fillStream(RLPStream& _s) const +{ + _s.appendList(8) << m_from << m_to << m_stateAltered << m_altered << m_earliest << m_latest << m_max << m_skip; +} + +h256 TransactionFilter::sha3() const +{ + RLPStream s; + fillStream(s); + return eth::sha3(s.out()); +} + VersionChecker::VersionChecker(string const& _dbPath): m_path(_dbPath.size() ? _dbPath : Defaults::dbPath()) { @@ -62,23 +74,25 @@ Client::Client(std::string const& _clientVersion, Address _us, std::string const if (_dbPath.size()) Defaults::setDBPath(_dbPath); m_vc.setOk(); - m_changed = true; } -void Client::start() { +void Client::ensureWorking() +{ static const char* c_threadName = "eth"; - m_work.reset(new thread([&](){ - setThreadName(c_threadName); - while (m_workState.load(std::memory_order_acquire) != Deleting) - work(); - m_workState.store(Deleted, std::memory_order_release); - - // Synchronise the state according to the head of the block chain. - // TODO: currently it contains keys for *all* blocks. Make it remove old ones. - m_preMine.sync(m_bc); - m_postMine = m_preMine; - })); + if (!m_work) + m_work.reset(new thread([&]() + { + setThreadName(c_threadName); + while (m_workState.load(std::memory_order_acquire) != Deleting) + work(); + m_workState.store(Deleted, std::memory_order_release); + + // Synchronise the state according to the head of the block chain. + // TODO: currently it contains keys for *all* blocks. Make it remove old ones. + m_preMine.sync(m_bc); + m_postMine = m_preMine; + })); } Client::~Client() @@ -90,8 +104,89 @@ Client::~Client() m_work->join(); } +void Client::flushTransactions() +{ + work(true); +} + +void Client::clearPending() +{ + ClientGuard l(this); + if (!m_postMine.pending().size()) + return; + h256Set changeds; + for (unsigned i = 0; i < m_postMine.pending().size(); ++i) + appendFromNewPending(m_postMine.bloom(i), changeds); + changeds.insert(NewPendingFilter); + m_postMine = m_preMine; + noteChanged(changeds); +} + +unsigned Client::installWatch(h256 _h) +{ + auto ret = m_watches.size() ? m_watches.rbegin()->first + 1 : 0; + m_watches[ret] = Watch(_h); + return ret; +} + +unsigned Client::installWatch(TransactionFilter const& _f) +{ + lock_guard l(m_filterLock); + + h256 h = _f.sha3(); + + if (!m_filters.count(h)) + m_filters.insert(make_pair(h, _f)); + + return installWatch(h); +} + +void Client::uninstallWatch(unsigned _i) +{ + lock_guard l(m_filterLock); + + auto it = m_watches.find(_i); + if (it == m_watches.end()) + return; + + auto fit = m_filters.find(it->second.id); + if (fit != m_filters.end()) + if (!--fit->second.refCount) + m_filters.erase(fit); + + m_watches.erase(it); +} + +void Client::appendFromNewPending(h256 _bloom, h256Set& o_changed) const +{ + lock_guard l(m_filterLock); + for (pair const& i: m_filters) + if (numberOf(i.second.filter.earliest()) == m_postMine.info().number && i.second.filter.matches(_bloom)) + o_changed.insert(i.first); +} + +void Client::appendFromNewBlock(h256 _block, h256Set& o_changed) const +{ + auto d = m_bc.details(_block); + + lock_guard l(m_filterLock); + for (pair const& i: m_filters) + if (numberOf(i.second.filter.earliest()) >= d.number && i.second.filter.matches(d.bloom)) + o_changed.insert(i.first); +} + +void Client::noteChanged(h256Set const& _filters) +{ + lock_guard l(m_filterLock); + for (auto& i: m_watches) + if (_filters.count(i.second.id)) + i.second.changes++; +} + void Client::startNetwork(unsigned short _listenPort, std::string const& _seedHost, unsigned short _port, NodeMode _mode, unsigned _peers, string const& _publicIP, bool _upnp) { + ensureWorking(); + ClientGuard l(this); if (m_net.get()) return; @@ -139,6 +234,8 @@ void Client::stopNetwork() void Client::startMining() { + ensureWorking(); + m_doMine = true; m_restartMining = true; } @@ -184,23 +281,28 @@ void Client::inject(bytesConstRef _rlp) { ClientGuard l(this); m_tq.attemptImport(_rlp); - m_changed = true; } -void Client::work() +void Client::work(bool _justQueue) { - bool changed = false; + h256Set changeds; // 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. - if (m_net) + if (m_net && !_justQueue) { ClientGuard l(this); m_net->process(); // must be in guard for now since it uses the blockchain. TODO: make BlockChain thread-safe. - if (m_net->sync(m_bc, m_tq, m_stateDB)) - changed = true; + // TODO: return h256Set as block hashes, once for each block that has come in/gone out. + h256Set newBlocks = m_net->sync(m_bc, m_tq, m_stateDB); + if (newBlocks.size()) + { + for (auto i: newBlocks) + appendFromNewBlock(i, changeds); + changeds.insert(NewBlockFilter); + } } // Synchronise state to block chain. @@ -216,74 +318,82 @@ void Client::work() { if (m_doMine) cnote << "New block on chain: Restarting mining operation."; - changed = true; m_restartMining = true; // need to re-commit to mine. m_postMine = m_preMine; } - if (m_postMine.sync(m_tq, &changed)) + + // returns h256s as blooms, once for each transaction. + h256s newPendingBlooms = m_postMine.sync(m_tq); + if (newPendingBlooms.size()) { + for (auto i: newPendingBlooms) + appendFromNewPending(i, changeds); + changeds.insert(NewPendingFilter); + if (m_doMine) cnote << "Additional transaction ready: Restarting mining operation."; m_restartMining = true; } } - if (m_doMine) + noteChanged(changeds); + + if (!_justQueue) { - if (m_restartMining) + if (m_doMine) { - m_mineProgress.best = (double)-1; - m_mineProgress.hashes = 0; - m_mineProgress.ms = 0; - ClientGuard l(this); - if (m_paranoia) + if (m_restartMining) { - if (m_postMine.amIJustParanoid(m_bc)) + m_mineProgress.best = (double)-1; + m_mineProgress.hashes = 0; + m_mineProgress.ms = 0; + ClientGuard l(this); + if (m_paranoia) { - cnote << "I'm just paranoid. Block is fine."; - m_postMine.commitToMine(m_bc); + if (m_postMine.amIJustParanoid(m_bc)) + { + cnote << "I'm just paranoid. Block is fine."; + m_postMine.commitToMine(m_bc); + } + else + { + cwarn << "I'm not just paranoid. Cannot mine. Please file a bug report."; + m_doMine = false; + } } else - { - cwarn << "I'm not just paranoid. Cannot mine. Please file a bug report."; - m_doMine = false; - } + m_postMine.commitToMine(m_bc); } - else - m_postMine.commitToMine(m_bc); } - } - if (m_doMine) - { - m_restartMining = false; + if (m_doMine) + { + m_restartMining = false; - // Mine for a while. - MineInfo mineInfo = m_postMine.mine(100); + // Mine for a while. + MineInfo mineInfo = m_postMine.mine(100); - m_mineProgress.best = min(m_mineProgress.best, mineInfo.best); - m_mineProgress.current = mineInfo.best; - m_mineProgress.requirement = mineInfo.requirement; - m_mineProgress.ms += 100; - m_mineProgress.hashes += mineInfo.hashes; - { - ClientGuard l(this); - m_mineHistory.push_back(mineInfo); - } + m_mineProgress.best = min(m_mineProgress.best, mineInfo.best); + m_mineProgress.current = mineInfo.best; + m_mineProgress.requirement = mineInfo.requirement; + m_mineProgress.ms += 100; + m_mineProgress.hashes += mineInfo.hashes; + { + ClientGuard l(this); + m_mineHistory.push_back(mineInfo); + } - if (mineInfo.completed) - { - // Import block. - ClientGuard l(this); - m_postMine.completeMine(); - m_bc.attemptImport(m_postMine.blockData(), m_stateDB); - m_changed = true; + if (mineInfo.completed) + { + // Import block. + ClientGuard l(this); + m_postMine.completeMine(); + m_bc.attemptImport(m_postMine.blockData(), m_stateDB); + } } + else + this_thread::sleep_for(chrono::milliseconds(100)); } - else - this_thread::sleep_for(chrono::milliseconds(100)); - - m_changed = m_changed || changed; } void Client::lock() const @@ -316,6 +426,15 @@ State Client::asOf(int _h) const return State(m_stateDB, m_bc, m_bc.numberHash(numberOf(_h))); } +std::vector
Client::addresses(int _block) const +{ + ClientGuard l(this); + vector
ret; + for (auto const& i: asOf(_block).addresses()) + ret.push_back(i.first); + return ret; +} + u256 Client::balanceAt(Address _a, int _block) const { ClientGuard l(this); @@ -480,28 +599,24 @@ PastMessages Client::transactions(TransactionFilter const& _f) const ret.insert(ret.begin(), pm[j].polish(h256(), ts, 0)); } } -/* for (unsigned i = m_postMine.pending().size(); i-- && ret.size() != m;) - if (_f.matches(m_postMine, i)) - { - if (s) - s--; - else - ret.insert(ret.begin(), PastMessage(m_postMine.pending()[i], h256(), i, time(0), 0)); - }*/ // Early exit here since we can't rely on begin/end, being out of the blockchain as we are. if (_f.earliest() == 0) return ret; } +#if ETH_DEBUG unsigned skipped = 0; unsigned falsePos = 0; +#endif auto cn = m_bc.number(); auto h = m_bc.numberHash(begin); unsigned n = begin; for (; ret.size() != m && n != end; n--, h = m_bc.details(h).parent) { auto d = m_bc.details(h); +#if ETH_DEBUG int total = 0; +#endif if (_f.matches(d.bloom)) { // Might have a block that contains a transaction that contains a matching message. @@ -517,7 +632,9 @@ PastMessages Client::transactions(TransactionFilter const& _f) const PastMessages pm = _f.matches(changes, i); if (pm.size()) { +#if ETH_DEBUG total += pm.size(); +#endif auto ts = BlockInfo(m_bc.block(h)).timestamp; for (unsigned j = 0; j < pm.size() && ret.size() != m; ++j) if (s) @@ -527,35 +644,20 @@ PastMessages Client::transactions(TransactionFilter const& _f) const ret.insert(ret.begin(), pm[j].polish(h, ts, cn - n + 2)); } } +#if ETH_DEBUG if (!total) falsePos++; -/* try - { - State st(m_stateDB, m_bc, h); - unsigned os = s; - for (unsigned i = st.pending().size(); i-- && ret.size() != m;) - if (_f.matches(st, i)) - { - if (s) - s--; - else - ret.insert(ret.begin(), PastMessage(st.pending()[i], h, i, BlockInfo(m_bc.block(h)).timestamp, cn - n + 2)); - } - if (os - s == st.pending().size()) - falsePos++; - } - catch (...) - { - // Gaa. bad state. not good at all. bury head in sand for now. - } -*/ } else skipped++; - +#else + } +#endif if (n == end) break; } +#if ETH_DEBUG // cdebug << (begin - n) << "searched; " << skipped << "skipped; " << falsePos << "false +ves"; +#endif return ret; } diff --git a/libethereum/Client.h b/libethereum/Client.h index 387282935..180d2d07b 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -82,7 +82,7 @@ struct PastMessage { PastMessage(Manifest const& _m, std::vector _path, Address _o): to(_m.to), from(_m.from), value(_m.value), input(_m.input), output(_m.output), path(_path), origin(_o) {} - PastMessage& polish(h256 _b, u256 _ts, int _a) { block = _b; timestamp = _ts; age = _a; return *this; } + PastMessage& polish(h256 _b, u256 _ts, unsigned _n) { block = _b; timestamp = _ts; number = _n; return *this; } Address to; ///< The receiving address of the transaction. Address() in the case of a creation. Address from; ///< The receiving address of the transaction. Address() in the case of a creation. @@ -93,8 +93,8 @@ struct PastMessage std::vector path; ///< Call path into the block transaction. size() is always > 0. First item is the transaction index in the block. Address origin; ///< Originating sender of the transaction. h256 block; ///< Block hash. - u256 timestamp; ///< Block timestamp. - int age; ///< Transaction age. + u256 timestamp; ///< Block timestamp. + unsigned number; ///< Block number. }; typedef std::vector PastMessages; @@ -104,6 +104,9 @@ class TransactionFilter public: TransactionFilter(int _earliest = GenesisBlock, int _latest = 0, unsigned _max = 10, unsigned _skip = 0): m_earliest(_earliest), m_latest(_latest), m_max(_max), m_skip(_skip) {} + void fillStream(RLPStream& _s) const; + h256 sha3() const; + int earliest() const { return m_earliest; } int latest() const { return m_latest; } unsigned max() const { return m_max; } @@ -134,6 +137,26 @@ private: unsigned m_skip; }; +struct InstalledFilter +{ + InstalledFilter(TransactionFilter const& _f): filter(_f) {} + + TransactionFilter filter; + unsigned refCount = 1; +}; + +static const h256 NewPendingFilter = u256(0); +static const h256 NewBlockFilter = u256(1); + +struct Watch +{ + Watch() {} + explicit Watch(h256 _id): id(_id) {} + + h256 id; + unsigned changes = 1; +}; + /** * @brief Main API hub for interfacing with Ethereum. */ @@ -143,9 +166,6 @@ public: /// Constructor. explicit Client(std::string const& _clientVersion, Address _us = Address(), std::string const& _dbPath = std::string(), bool _forceClean = false); - // Start client. Boost require threads are started outside constructor. - void start(); - /// Destructor. ~Client(); @@ -156,36 +176,17 @@ public: /// @returns the new contract's address (assuming it all goes through). Address transact(Secret _secret, u256 _endowment, bytes const& _init, u256 _gas = 10000, u256 _gasPrice = 10 * szabo); + /// Blocks until all pending transactions have been processed. + void flushTransactions(); + + /// Injects the RLP-encoded transaction given by the _rlp into the transaction queue directly. void inject(bytesConstRef _rlp); /// Makes the given call. Nothing is recorded into the state. TODO // bytes call(Secret _secret, u256 _amount, u256 _gasPrice, Address _dest, u256 _gas, bytes _data = bytes()); - /// Requires transactions involving this address be queued for inspection. - void setInterest(Address _dest); - - /// @returns incoming minable transactions that we wanted to be notified of. Clears the queue. - Transactions pendingQueue() { ClientGuard g(this); return m_tq.interestQueue(); } - - /// @returns alterations in state of a mined block that we wanted to be notified of. Clears the queue. - std::vector> minedQueue() { ClientGuard g(this); return m_bc.interestQueue(); } - - // Not yet - probably best as using some sort of signals implementation. - /// Calls @a _f when a valid transaction is received that involves @a _dest and once per such transaction. -// void onPending(Address _dest, function const& _f); - - /// Calls @a _f when a transaction is mined that involves @a _dest and once per change. -// void onConfirmed(Address _dest, function const& _f); - // Informational stuff - /// Determines whether at least one of the state/blockChain/transactionQueue has changed since the last call to changed(). - bool changed() const { auto ret = m_changed; m_changed = false; return ret; } - bool peekChanged() const { return m_changed; } - - /// Get a map containing each of the pending transactions. - Transactions pending() const { return m_postMine.pending(); } - // [OLD API]: /// Locks/unlocks the state/blockChain/transactionQueue for access. @@ -201,11 +202,34 @@ public: // [NEW API] - u256 balanceAt(Address _a, int _block = -1) const; - u256 countAt(Address _a, int _block = -1) const; - u256 stateAt(Address _a, u256 _l, int _block = -1) const; - bytes codeAt(Address _a, int _block = -1) const; - PastMessages transactions(TransactionFilter const& _f) const; + void setDefault(int _block) { m_default = _block; } + + u256 balanceAt(Address _a) const { return balanceAt(_a, m_default); } + u256 countAt(Address _a) const { return countAt(_a, m_default); } + u256 stateAt(Address _a, u256 _l) const { return stateAt(_a, _l, m_default); } + bytes codeAt(Address _a) const { return codeAt(_a, m_default); } + + u256 balanceAt(Address _a, int _block) const; + u256 countAt(Address _a, int _block) const; + u256 stateAt(Address _a, u256 _l, int _block) const; + bytes codeAt(Address _a, int _block) const; + PastMessages transactions(TransactionFilter const& _filter) const; + PastMessages transactions(unsigned _watchId) const { try { std::lock_guard l(m_filterLock); return transactions(m_filters.at(m_watches.at(_watchId).id).filter); } catch (...) { return PastMessages(); } } + unsigned installWatch(TransactionFilter const& _filter); + unsigned installWatch(h256 _filterId); + void uninstallWatch(unsigned _watchId); + bool peekWatch(unsigned _watchId) const { std::lock_guard l(m_filterLock); try { return m_watches.at(_watchId).changes; } catch (...) { return 0; } } + bool checkWatch(unsigned _watchId) { std::lock_guard l(m_filterLock); bool ret = false; try { ret = m_watches.at(_watchId).changes; m_watches.at(_watchId).changes = 0; } catch (...) {} return ret; } + + // [EXTRA API]: + + /// Get a map containing each of the pending transactions. + /// @TODO: Remove in favour of transactions(). + Transactions pending() const { return m_postMine.pending(); } + + /// Get a list of all active addresses. + std::vector
addresses() const { return addresses(m_default); } + std::vector
addresses(int _block) const; // Misc stuff: @@ -253,10 +277,27 @@ public: std::list miningHistory() { auto ret = m_mineHistory; m_mineHistory.clear(); return ret; } /// Clears pending transactions. Just for debug use. - void clearPending() { ClientGuard l(this); m_postMine = m_preMine; changed(); } + void clearPending(); private: - void work(); + /// Ensure the worker thread is running. Needed for networking & mining. + void ensureWorking(); + + /// Do some work. Handles networking and mining. + /// @param _justQueue If true will only processing the transaction queues. + void work(bool _justQueue = false); + + /// Collate the changed filters for the bloom filter of the given pending transaction. + /// Insert any filters that are activated into @a o_changed. + void appendFromNewPending(h256 _pendingTransactionBloom, h256Set& o_changed) const; + + /// Collate the changed filters for the hash of the given block. + /// Insert any filters that are activated into @a o_changed. + void appendFromNewBlock(h256 _blockHash, h256Set& o_changed) const; + + /// Record that the set of filters @a _filters have changed. + /// This doesn't actually make any callbacks, but incrememnts some counters in m_watches. + void noteChanged(h256Set const& _filters); /// Return the actual block number of the block with the given int-number (positive is the same, INT_MIN is genesis block, < 0 is negative age, thus -1 is most recently mined, 0 is pending. unsigned numberOf(int _b) const; @@ -268,9 +309,10 @@ private: VersionChecker m_vc; ///< Dummy object to check & update the protocol version. BlockChain m_bc; ///< Maintains block database. TransactionQueue m_tq; ///< Maintains list of incoming transactions not yet on the block chain. - OverlayDB m_stateDB; ///< Acts as the central point for the state database, so multiple States can share it. + OverlayDB m_stateDB; ///< Acts as the central point for the state database, so multiple States can share it. State m_preMine; ///< The present state of the client. State m_postMine; ///< The state of the client which we're mining (i.e. it'll have all the rewards added). + std::unique_ptr m_net; ///< Should run in background and send us events when blocks found and allow us to send blocks as required. std::unique_ptr m_work;///< The work thread. @@ -283,7 +325,11 @@ private: std::list m_mineHistory; mutable bool m_restartMining = false; - mutable bool m_changed; + mutable std::mutex m_filterLock; + std::map m_filters; + std::map m_watches; + + int m_default = -1; }; inline ClientGuard::ClientGuard(Client const* _c): m_client(_c) diff --git a/libethereum/PeerServer.cpp b/libethereum/PeerServer.cpp index 275d9ace7..2de031ac2 100644 --- a/libethereum/PeerServer.cpp +++ b/libethereum/PeerServer.cpp @@ -350,9 +350,11 @@ bool PeerServer::noteBlock(h256 _hash, bytesConstRef _data) return false; } -bool PeerServer::sync(BlockChain& _bc, TransactionQueue& _tq, OverlayDB& _o) +h256Set PeerServer::sync(BlockChain& _bc, TransactionQueue& _tq, OverlayDB& _o) { - bool ret = ensureInitialised(_bc, _tq); + h256Set ret; + + bool netChange = ensureInitialised(_bc, _tq); if (m_mode == NodeMode::Full) { @@ -421,10 +423,11 @@ bool PeerServer::sync(BlockChain& _bc, TransactionQueue& _tq, OverlayDB& _o) { try { - _bc.import(*it, _o); + for (auto h: _bc.import(*it, _o)) + ret.insert(h); it = m_incomingBlocks.erase(it); ++accepted; - ret = true; + netChange = true; } catch (UnknownParent) { @@ -506,6 +509,7 @@ bool PeerServer::sync(BlockChain& _bc, TransactionQueue& _tq, OverlayDB& _o) worst->disconnect(TooManyPeers); } + (void)netChange; return ret; } diff --git a/libethereum/PeerServer.h b/libethereum/PeerServer.h index 6e44d2ea4..0eae35a1f 100644 --- a/libethereum/PeerServer.h +++ b/libethereum/PeerServer.h @@ -58,7 +58,7 @@ public: void connect(bi::tcp::endpoint const& _ep); /// Sync with the BlockChain. It might contain one of our mined blocks, we might have new candidates from the network. - bool sync(BlockChain& _bc, TransactionQueue&, OverlayDB& _o); + h256Set sync(BlockChain& _bc, TransactionQueue&, OverlayDB& _o); /// Conduct I/O, polling, syncing, whatever. /// Ideally all time-consuming I/O is done in a background thread or otherwise asynchronously, but you get this call every 100ms or so anyway. diff --git a/libethereum/PeerSession.cpp b/libethereum/PeerSession.cpp index a80cc6b27..373673f71 100644 --- a/libethereum/PeerSession.cpp +++ b/libethereum/PeerSession.cpp @@ -463,7 +463,7 @@ void PeerSession::writeImpl(bytes& _buffer) if (m_writeq.size() > 1) return; - this->write(); + write(); } void PeerSession::write() @@ -477,12 +477,12 @@ void PeerSession::write() { // must check que, as write callback can occur following dropped() if (!m_writeq.empty()) - this->m_writeq.pop_front(); + m_writeq.pop_front(); if (ec) { cwarn << "Error sending: " << ec.message(); - this->dropped(); + dropped(); } else m_strand.post(boost::bind(&PeerSession::write, this)); })); diff --git a/libethereum/State.cpp b/libethereum/State.cpp index 66dec31e7..f7fd5049c 100644 --- a/libethereum/State.cpp +++ b/libethereum/State.cpp @@ -454,28 +454,24 @@ bool State::cull(TransactionQueue& _tq) const return ret; } -bool State::sync(TransactionQueue& _tq, bool* _changed) +h256s State::sync(TransactionQueue& _tq, bool* o_transactionQueueChanged) { // TRANSACTIONS - bool ret = false; + h256s ret; auto ts = _tq.transactions(); - vector> futures; for (int goodTxs = 1; goodTxs;) { goodTxs = 0; for (auto const& i: ts) - { if (!m_transactionSet.count(i.first)) { // don't have it yet! Execute it now. try { - ret = true; uncommitToMine(); execute(i.second); - if (_changed) - *_changed = true; + ret.push_back(m_transactions.back().changes.bloom()); _tq.noteGood(i); ++goodTxs; } @@ -485,8 +481,8 @@ bool State::sync(TransactionQueue& _tq, bool* _changed) { // too old _tq.drop(i.first); - if (_changed) - *_changed = true; + if (o_transactionQueueChanged) + *o_transactionQueueChanged = true; } else _tq.setFuture(i); @@ -495,11 +491,10 @@ bool State::sync(TransactionQueue& _tq, bool* _changed) { // Something else went wrong - drop it. _tq.drop(i.first); - if (_changed) - *_changed = true; + if (o_transactionQueueChanged) + *o_transactionQueueChanged = true; } } - } } return ret; } diff --git a/libethereum/State.h b/libethereum/State.h index 3618b9a9a..ccf6bfdd0 100644 --- a/libethereum/State.h +++ b/libethereum/State.h @@ -138,6 +138,8 @@ public: /// @returns the set containing all addresses currently in use in Ethereum. std::map addresses() const; + BlockInfo const& info() const { return m_currentBlock; } + /// @brief Checks that mining the current object will result in a valid block. /// Effectively attempts to import the serialised block. /// @returns true if all is ok. If it's false, worry. @@ -178,10 +180,10 @@ public: // TODO: Cleaner interface. /// Sync our transactions, killing those from the queue that we have and assimilating those that we don't. - /// @returns true if we uncommitted from mining during the operation. - /// @a o_changed boolean pointer, the value of which will be set to true if the state changed and the pointer - /// is non-null - bool sync(TransactionQueue& _tq, bool* o_changed = nullptr); + /// @returns a list of bloom filters one for each transaction placed from the queue into the state. + /// @a o_transactionQueueChanged boolean pointer, the value of which will be set to true if the transaction queue + /// changed and the pointer is non-null + h256s sync(TransactionQueue& _tq, bool* o_transactionQueueChanged = nullptr); /// Like sync but only operate on _tq, killing the invalid/old ones. bool cull(TransactionQueue& _tq) const; diff --git a/libqethereum/QEthereum.cpp b/libqethereum/QEthereum.cpp index 520547f64..9c9f5ce5f 100644 --- a/libqethereum/QEthereum.cpp +++ b/libqethereum/QEthereum.cpp @@ -1,6 +1,3 @@ -#if ETH_QTQML -#include -#endif #include #include #include @@ -47,143 +44,6 @@ using eth::g_logPost; using eth::g_logVerbosity; using eth::c_instructionInfo; -// Horrible global for the mainwindow. Needed for the QmlEthereums to find the Main window which acts as multiplexer for now. -// Can get rid of this once we've sorted out ITC for signalling & multiplexed querying. -eth::Client* g_qmlClient; -QObject* g_qmlMain; - -QmlAccount::QmlAccount(QObject*) -{ -} - -QmlAccount::~QmlAccount() -{ -} - -void QmlAccount::setEthereum(QmlEthereum* _eth) -{ - if (m_eth == _eth) - return; - if (m_eth) - disconnect(m_eth, SIGNAL(changed()), this, SIGNAL(changed())); - m_eth = _eth; - if (m_eth) - connect(m_eth, SIGNAL(changed()), this, SIGNAL(changed())); - ethChanged(); - changed(); -} - -eth::u256 QmlAccount::balance() const -{ - if (m_eth) - return m_eth->balanceAt(m_address); - return u256(0); -} - -double QmlAccount::txCount() const -{ - if (m_eth) - return m_eth->txCountAt(m_address); - return 0; -} - -bool QmlAccount::isContract() const -{ - if (m_eth) - return m_eth->isContractAt(m_address); - return 0; -} - -QmlEthereum::QmlEthereum(QObject* _p): QObject(_p) -{ - connect(g_qmlMain, SIGNAL(changed()), SIGNAL(changed())); -} - -QmlEthereum::~QmlEthereum() -{ -} - -Client* QmlEthereum::client() const -{ - return g_qmlClient; -} - -Address QmlEthereum::coinbase() const -{ - return client()->address(); -} - -void QmlEthereum::setCoinbase(Address _a) -{ - if (client()->address() != _a) - { - client()->setAddress(_a); - changed(); - } -} - -u256 QmlEthereum::balanceAt(Address _a) const -{ - return client()->postState().balance(_a); -} - -bool QmlEthereum::isContractAt(Address _a) const -{ - return client()->postState().addressHasCode(_a); -} - -bool QmlEthereum::isMining() const -{ - return client()->isMining(); -} - -bool QmlEthereum::isListening() const -{ - return client()->haveNetwork(); -} - -void QmlEthereum::setMining(bool _l) -{ - if (_l) - client()->startMining(); - else - client()->stopMining(); -} - -void QmlEthereum::setListening(bool _l) -{ - if (_l) - client()->startNetwork(); - else - client()->stopNetwork(); -} - -double QmlEthereum::txCountAt(Address _a) const -{ - return (double)client()->postState().transactionsFrom(_a); -} - -unsigned QmlEthereum::peerCount() const -{ - return (unsigned)client()->peerCount(); -} - -void QmlEthereum::transact(Secret _secret, u256 _amount, u256 _gasPrice, u256 _gas, QByteArray _init) -{ - client()->transact(_secret, _amount, bytes(_init.data(), _init.data() + _init.size()), _gas, _gasPrice); -} - -void QmlEthereum::transact(Secret _secret, Address _dest, u256 _amount, u256 _gasPrice, u256 _gas, QByteArray _data) -{ - client()->transact(_secret, _amount, _dest, bytes(_data.data(), _data.data() + _data.size()), _gas, _gasPrice); -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - eth::bytes toBytes(QString const& _s) { if (_s.startsWith("0x")) @@ -209,11 +69,6 @@ QString padded(QString const& _s, unsigned _l, unsigned _r) //"0xff".bin().unbin() -QString QEthereum::secretToAddress(QString _s) const -{ - return toQJS(KeyPair(toSecret(_s)).address()); -} - QString padded(QString const& _s, unsigned _l) { if (_s.startsWith("0x") || !_s.contains(QRegExp("[^0-9]"))) @@ -234,11 +89,24 @@ QString unpadded(QString _s) QEthereum::QEthereum(QObject* _p, Client* _c, QList _accounts): QObject(_p), m_client(_c), m_accounts(_accounts) { // required to prevent crash on osx when performing addto/evaluatejavascript calls - this->moveToThread(_p->thread()); + moveToThread(_p->thread()); } QEthereum::~QEthereum() { + clearWatches(); +} + +void QEthereum::clearWatches() +{ + for (auto i: m_watches) + m_client->uninstallWatch(i); + m_watches.clear(); +} + +QString QEthereum::secretToAddress(QString _s) const +{ + return toQJS(KeyPair(toSecret(_s)).address()); } void QEthereum::setup(QWebFrame*) @@ -310,7 +178,7 @@ void QEthereum::setCoinbase(QString _a) if (client()->address() != toAddress(_a)) { client()->setAddress(toAddress(_a)); - changed(); + coinbaseChanged(); } } @@ -370,11 +238,11 @@ double QEthereum::countAt(QString _a, int _block) const return (double)(uint64_t)client()->countAt(toAddress(_a), _block); } -QString QEthereum::getTransactions(QString _a) const +static eth::TransactionFilter toTransactionFilter(QString _json) { eth::TransactionFilter filter; - QJsonObject f = QJsonDocument::fromJson(_a.toUtf8()).object(); + QJsonObject f = QJsonDocument::fromJson(_json.toUtf8()).object(); if (f.contains("earliest")) filter.withEarliest(f["earliest"].toInt()); if (f.contains("latest")) @@ -413,12 +281,15 @@ QString QEthereum::getTransactions(QString _a) const else filter.altered(toAddress(f["altered"].toString())); } + return filter; +} - QJsonArray ret; - for (eth::PastMessage const& t: m_client->transactions(filter)) +static QString toJson(eth::PastMessages const& _pms) +{ + QJsonArray jsonArray; + for (eth::PastMessage const& t: _pms) { QJsonObject v; - v["data"] = ::fromBinary(t.input); v["input"] = ::fromBinary(t.input); v["output"] = ::fromBinary(t.output); v["to"] = toQJS(t.to); @@ -430,10 +301,15 @@ QString QEthereum::getTransactions(QString _a) const for (int i: t.path) path.append(i); v["path"] = path; - v["age"] = (int)t.age; - ret.append(v); + v["number"] = (int)t.number; + jsonArray.append(v); } - return QString::fromUtf8(QJsonDocument(ret).toJson()); + return QString::fromUtf8(QJsonDocument(jsonArray).toJson()); +} + +QString QEthereum::getTransactions(QString _json) const +{ + return toJson(m_client->transactions(toTransactionFilter(_json))); } bool QEthereum::isMining() const @@ -469,19 +345,48 @@ unsigned QEthereum::peerCount() const QString QEthereum::doCreate(QString _secret, QString _amount, QString _init, QString _gas, QString _gasPrice) { - client()->changed(); auto ret = toQJS(client()->transact(toSecret(_secret), toU256(_amount), toBytes(_init), toU256(_gas), toU256(_gasPrice))); - while (!client()->peekChanged()) - this_thread::sleep_for(chrono::milliseconds(10)); + client()->flushTransactions(); return ret; } void QEthereum::doTransact(QString _secret, QString _amount, QString _dest, QString _data, QString _gas, QString _gasPrice) { - client()->changed(); client()->transact(toSecret(_secret), toU256(_amount), toAddress(_dest), toBytes(_data), toU256(_gas), toU256(_gasPrice)); - while (!client()->peekChanged()) - this_thread::sleep_for(chrono::milliseconds(10)); + client()->flushTransactions(); +} + +unsigned QEthereum::newWatch(QString _json) +{ + unsigned ret; + if (_json == "chainChanged") + ret = m_client->installWatch(eth::NewBlockFilter); + else if (_json == "pendingChanged") + ret = m_client->installWatch(eth::NewPendingFilter); + else + ret = m_client->installWatch(toTransactionFilter(_json)); + m_watches.push_back(ret); + return ret; +} + +QString QEthereum::watchTransactions(unsigned _w) +{ + return toJson(m_client->transactions(_w)); +} + +void QEthereum::killWatch(unsigned _w) +{ + m_client->uninstallWatch(_w); + std::remove(m_watches.begin(), m_watches.end(), _w); +} + +void QEthereum::poll() +{ + if (!m_client) + return; + for (auto w: m_watches) + if (m_client->checkWatch(w)) + emit watchChanged(w); } // extra bits needed to link on VS diff --git a/libqethereum/QEthereum.h b/libqethereum/QEthereum.h index 3b0eb34de..bd5832304 100644 --- a/libqethereum/QEthereum.h +++ b/libqethereum/QEthereum.h @@ -1,9 +1,8 @@ #pragma once -#include -#if ETH_QTQML -#include -#endif +#include +#include +#include #include #include @@ -12,280 +11,10 @@ class Client; class State; } -class QQmlEngine; class QJSEngine; class QWebFrame; class QEthereum; -class QmlAccount; -class QmlEthereum; - -extern eth::Client* g_qmlClient; -extern QObject* g_qmlMain; - -Q_DECLARE_METATYPE(eth::u256) -Q_DECLARE_METATYPE(eth::Address) -Q_DECLARE_METATYPE(eth::Secret) -Q_DECLARE_METATYPE(eth::KeyPair) -Q_DECLARE_METATYPE(QEthereum*) -Q_DECLARE_METATYPE(QmlAccount*) -Q_DECLARE_METATYPE(QmlEthereum*) - -class QmlU256Helper: public QObject -{ - Q_OBJECT - -public: - QmlU256Helper(QObject* _p = nullptr): QObject(_p) {} - - Q_INVOKABLE eth::u256 add(eth::u256 _a, eth::u256 _b) const { return _a + _b; } - Q_INVOKABLE eth::u256 sub(eth::u256 _a, eth::u256 _b) const { return _a - _b; } - Q_INVOKABLE eth::u256 mul(eth::u256 _a, int _b) const { return _a * _b; } - Q_INVOKABLE eth::u256 mul(int _a, eth::u256 _b) const { return _a * _b; } - Q_INVOKABLE eth::u256 div(eth::u256 _a, int _b) const { return _a / _b; } - - Q_INVOKABLE eth::u256 wei(double _s) const { return (eth::u256)_s; } - Q_INVOKABLE eth::u256 szabo(double _s) const { return (eth::u256)(_s * (double)eth::szabo); } - Q_INVOKABLE eth::u256 finney(double _s) const { return (eth::u256)(_s * (double)eth::finney); } - Q_INVOKABLE eth::u256 ether(double _s) const { return (eth::u256)(_s * (double)eth::ether); } - Q_INVOKABLE eth::u256 wei(unsigned _s) const { return (eth::u256)_s; } - Q_INVOKABLE eth::u256 szabo(unsigned _s) const { return (eth::u256)(_s * eth::szabo); } - Q_INVOKABLE eth::u256 finney(unsigned _s) const { return (eth::u256)(_s * eth::finney); } - Q_INVOKABLE eth::u256 ether(unsigned _s) const { return (eth::u256)(_s * eth::ether); } - Q_INVOKABLE double toWei(eth::u256 _t) const { return (double)_t; } - Q_INVOKABLE double toSzabo(eth::u256 _t) const { return toWei(_t) / (double)eth::szabo; } - Q_INVOKABLE double toFinney(eth::u256 _t) const { return toWei(_t) / (double)eth::finney; } - Q_INVOKABLE double toEther(eth::u256 _t) const { return toWei(_t) / (double)eth::ether; } - - Q_INVOKABLE double value(eth::u256 _t) const { return (double)_t; } - - Q_INVOKABLE QString stringOf(eth::u256 _t) const { return QString::fromStdString(eth::formatBalance(_t)); } -}; - -class QmlKeyHelper: public QObject -{ - Q_OBJECT - -public: - QmlKeyHelper(QObject* _p = nullptr): QObject(_p) {} - - Q_INVOKABLE eth::KeyPair create() const { return eth::KeyPair::create(); } - Q_INVOKABLE eth::Address address(eth::KeyPair _p) const { return _p.address(); } - Q_INVOKABLE eth::Secret secret(eth::KeyPair _p) const { return _p.secret(); } - Q_INVOKABLE eth::KeyPair keypair(eth::Secret _k) const { return eth::KeyPair(_k); } - - Q_INVOKABLE bool isNull(eth::Address _a) const { return !_a; } - - Q_INVOKABLE eth::Address addressOf(QString _s) const { return eth::Address(_s.toStdString()); } - Q_INVOKABLE QString stringOf(eth::Address _a) const { return QString::fromStdString(eth::toHex(_a.asArray())); } - Q_INVOKABLE QString toAbridged(eth::Address _a) const { return QString::fromStdString(_a.abridged()); } -}; - -class QmlAccount: public QObject -{ - Q_OBJECT - -public: - QmlAccount(QObject* _p = nullptr); - virtual ~QmlAccount(); - - Q_INVOKABLE QmlEthereum* ethereum() const { return m_eth; } - Q_INVOKABLE eth::u256 balance() const; - Q_INVOKABLE double txCount() const; - Q_INVOKABLE bool isContract() const; - Q_INVOKABLE eth::Address address() const { return m_address; } - - // TODO: past transactions models. - -public slots: - void setEthereum(QmlEthereum* _eth); - void setAddress(eth::Address _a) { m_address = _a; } - -signals: - void changed(); - void ethChanged(); - -private: - QmlEthereum* m_eth = nullptr; - eth::Address m_address; - - Q_PROPERTY(eth::u256 balance READ balance NOTIFY changed STORED false) - Q_PROPERTY(double txCount READ txCount NOTIFY changed STORED false) - Q_PROPERTY(bool isContract READ isContract NOTIFY changed STORED false) - Q_PROPERTY(eth::Address address READ address WRITE setAddress NOTIFY changed) - Q_PROPERTY(QmlEthereum* ethereum READ ethereum WRITE setEthereum NOTIFY ethChanged) -}; - -class QmlEthereum: public QObject -{ - Q_OBJECT - -public: - QmlEthereum(QObject* _p = nullptr); - virtual ~QmlEthereum(); - - eth::Client* client() const; - - static QObject* constructU256Helper(QQmlEngine*, QJSEngine*) { return new QmlU256Helper; } - static QObject* constructKeyHelper(QQmlEngine*, QJSEngine*) { return new QmlKeyHelper; } - - Q_INVOKABLE eth::Address coinbase() const; - - Q_INVOKABLE bool isListening() const; - Q_INVOKABLE bool isMining() const; - - Q_INVOKABLE eth::u256 balanceAt(eth::Address _a) const; - Q_INVOKABLE double txCountAt(eth::Address _a) const; - Q_INVOKABLE bool isContractAt(eth::Address _a) const; - - Q_INVOKABLE unsigned peerCount() const; - - Q_INVOKABLE QmlEthereum* self() { return this; } - -public slots: - void transact(eth::Secret _secret, eth::Address _dest, eth::u256 _amount, eth::u256 _gasPrice, eth::u256 _gas, QByteArray _data); - void transact(eth::Secret _secret, eth::u256 _amount, eth::u256 _gasPrice, eth::u256 _gas, QByteArray _init); - void setCoinbase(eth::Address); - void setMining(bool _l); - - void setListening(bool _l); - -signals: - void changed(); -// void netChanged(); -// void miningChanged(); - -private: - Q_PROPERTY(eth::Address coinbase READ coinbase WRITE setCoinbase NOTIFY changed) - Q_PROPERTY(bool listening READ isListening WRITE setListening) - Q_PROPERTY(bool mining READ isMining WRITE setMining) -}; - -#if 0 -template T to(QVariant const& _s) { if (_s.type() != QVariant::String) return T(); auto s = _s.toString().toLatin1(); assert(s.size() == sizeof(T)); return *(T*)s.data(); } -template QVariant toQJS(T const& _s) { QLatin1String ret((char*)&_s, sizeof(T)); assert(QVariant(QString(ret)).toString().toLatin1().size() == sizeof(T)); assert(*(T*)(QVariant(QString(ret)).toString().toLatin1().data()) == _s); return QVariant(QString(ret)); } - -class U256Helper: public QObject -{ - Q_OBJECT - -public: - U256Helper(QObject* _p = nullptr): QObject(_p) {} - - static eth::u256 in(QVariant const& _s) { return to(_s); } - static QVariant out(eth::u256 const& _s) { return toQJS(_s); } - - Q_INVOKABLE QVariant add(QVariant _a, QVariant _b) const { return out(in(_a) + in(_b)); } - Q_INVOKABLE QVariant sub(QVariant _a, QVariant _b) const { return out(in(_a) - in(_b)); } - Q_INVOKABLE QVariant mul(QVariant _a, int _b) const { return out(in(_a) * in(_b)); } - Q_INVOKABLE QVariant mul(int _a, QVariant _b) const { return out(in(_a) * in(_b)); } - Q_INVOKABLE QVariant div(QVariant _a, int _b) const { return out(in(_a) / in(_b)); } - - Q_INVOKABLE QVariant wei(double _s) const { return out(eth::u256(_s)); } - Q_INVOKABLE QVariant szabo(double _s) const { return out(eth::u256(_s * (double)eth::szabo)); } - Q_INVOKABLE QVariant finney(double _s) const { return out(eth::u256(_s * (double)eth::finney)); } - Q_INVOKABLE QVariant ether(double _s) const { return out(eth::u256(_s * (double)eth::ether)); } - Q_INVOKABLE QVariant wei(unsigned _s) const { return value(_s); } - Q_INVOKABLE QVariant szabo(unsigned _s) const { return out(eth::u256(_s) * eth::szabo); } - Q_INVOKABLE QVariant finney(unsigned _s) const { return out(eth::u256(_s) * eth::finney); } - Q_INVOKABLE QVariant ether(unsigned _s) const { return out(eth::u256(_s) * eth::ether); } - Q_INVOKABLE double toWei(QVariant _t) const { return toValue(_t); } - Q_INVOKABLE double toSzabo(QVariant _t) const { return toWei(_t) / (double)eth::szabo; } - Q_INVOKABLE double toFinney(QVariant _t) const { return toWei(_t) / (double)eth::finney; } - Q_INVOKABLE double toEther(QVariant _t) const { return toWei(_t) / (double)eth::ether; } - - Q_INVOKABLE QVariant value(unsigned _s) const { return out(eth::u256(_s)); } - Q_INVOKABLE double toValue(QVariant _t) const { return (double)in(_t); } - - Q_INVOKABLE QString ethOf(QVariant _t) const { return QString::fromStdString(eth::formatBalance(in(_t))); } - Q_INVOKABLE QString stringOf(QVariant _t) const { return QString::fromStdString(eth::toString(in(_t))); } - - Q_INVOKABLE QByteArray bytesOf(QVariant _t) const { eth::h256 b = in(_t); return QByteArray((char const*)&b, sizeof(eth::h256)); } - Q_INVOKABLE QVariant fromHex(QString _s) const { return out((eth::u256)eth::h256(_s.toStdString())); } - - Q_INVOKABLE QVariant fromAddress(QVariant/*eth::Address*/ _a) const { return out((eth::u160)to(_a)); } - Q_INVOKABLE QVariant toAddress(QVariant/*eth::Address*/ _a) const { return toQJS((eth::u160)in(_a)); } - - Q_INVOKABLE bool isNull(QVariant/*eth::Address*/ _a) const { return !in(_a); } -}; - -class KeyHelper: public QObject -{ - Q_OBJECT - -public: - KeyHelper(QObject* _p = nullptr): QObject(_p) {} - - static eth::Address in(QVariant const& _s) { return to(_s); } - static QVariant out(eth::Address const& _s) { return toQJS(_s); } - - Q_INVOKABLE QVariant/*eth::KeyPair*/ create() const { return toQJS(eth::KeyPair::create()); } - Q_INVOKABLE QVariant/*eth::Address*/ address(QVariant/*eth::KeyPair*/ _p) const { return out(to(_p).address()); } - Q_INVOKABLE QVariant/*eth::Secret*/ secret(QVariant/*eth::KeyPair*/ _p) const { return toQJS(to(_p).secret()); } - Q_INVOKABLE QVariant/*eth::KeyPair*/ keypair(QVariant/*eth::Secret*/ _k) const { return toQJS(eth::KeyPair(to(_k))); } - - Q_INVOKABLE bool isNull(QVariant/*eth::Address*/ _a) const { return !in(_a); } - - Q_INVOKABLE QVariant/*eth::Address*/ addressOf(QString _s) const { return out(eth::Address(_s.toStdString())); } - Q_INVOKABLE QString stringOf(QVariant/*eth::Address*/ _a) const { return QString::fromStdString(eth::toHex(in(_a).asArray())); } - Q_INVOKABLE QString toAbridged(QVariant/*eth::Address*/ _a) const { return QString::fromStdString(in(_a).abridged()); } - -}; - -class BytesHelper: public QObject -{ - Q_OBJECT - -public: - BytesHelper(QObject* _p = nullptr): QObject(_p) {} - - Q_INVOKABLE QByteArray concat(QVariant _v, QVariant _w) const - { - QByteArray ba; - if (_v.type() == QVariant::ByteArray) - ba = _v.toByteArray(); - else - ba = _v.toString().toLatin1(); - QByteArray ba2; - if (_w.type() == QVariant::ByteArray) - ba2 = _w.toByteArray(); - else - ba2 = _w.toString().toLatin1(); - ba.append(ba2); - return QByteArray(ba); - } - Q_INVOKABLE QByteArray concat(QByteArray _v, QByteArray _w) const - { - _v.append(_w); - return _v; - } - Q_INVOKABLE QByteArray fromString(QString _s) const - { - return _s.toLatin1(); - } - Q_INVOKABLE QByteArray fromString(QString _s, unsigned _padding) const - { - QByteArray b = _s.toLatin1(); - for (unsigned i = b.size(); i < _padding; ++i) - b.append((char)0); - b.resize(_padding); - return b; - } - Q_INVOKABLE QString toString(QByteArray _b) const - { - while (_b.size() && !_b[_b.size() - 1]) - _b.resize(_b.size() - 1); - return QString::fromLatin1(_b); - } - Q_INVOKABLE QVariant u256of(QByteArray _s) const - { - while (_s.size() < 32) - _s.append((char)0); - eth::h256 ret((uint8_t const*)_s.data(), eth::h256::ConstructFromPointer); - return toQJS(ret); - } -}; -#endif inline eth::bytes asBytes(QString const& _s) { @@ -324,17 +53,7 @@ template eth::FixedHash toFixed(QString const& _s) return eth::FixedHash(asBytes(padded(_s, N))); } -template boost::multiprecision::number> toInt(QString const& _s) -{ - if (_s.startsWith("0x")) - return eth::fromBigEndian>>(eth::fromHex(_s.toStdString().substr(2))); - else if (!_s.contains(QRegExp("[^0-9]"))) - // Hex or Decimal - return boost::multiprecision::number>(_s.toStdString()); - else - // Binary - return eth::fromBigEndian>>(asBytes(padded(_s, N))); -} +template inline boost::multiprecision::number> toInt(QString const& _s); inline eth::Address toAddress(QString const& _s) { return toFixed<20>(_s); } inline eth::Secret toSecret(QString const& _s) { return toFixed<32>(_s); } @@ -376,7 +95,7 @@ public: void setup(QWebFrame* _e); void teardown(QWebFrame* _e); - void setAccounts(QList _l) { m_accounts = _l; this->changed(); } + void setAccounts(QList _l) { m_accounts = _l; keysChanged(); } Q_INVOKABLE QString ethTest() const { return "Hello world!"; } Q_INVOKABLE QEthereum* self() { return this; } @@ -403,11 +122,16 @@ public: Q_INVOKABLE double countAt(QString/*eth::Address*/ _a, int _block) const; Q_INVOKABLE QString/*eth::u256*/ stateAt(QString/*eth::Address*/ _a, QString/*eth::u256*/ _p, int _block) const; Q_INVOKABLE QString/*eth::u256*/ codeAt(QString/*eth::Address*/ _a, int _block) const; - Q_INVOKABLE QString getTransactions(QString _attribs) const; + Q_INVOKABLE QString/*json*/ getTransactions(QString _attribs/*json*/) const; Q_INVOKABLE QString doCreate(QString _secret, QString _amount, QString _init, QString _gas, QString _gasPrice); Q_INVOKABLE void doTransact(QString _secret, QString _amount, QString _dest, QString _data, QString _gas, QString _gasPrice); + Q_INVOKABLE unsigned newWatch(QString _json); + Q_INVOKABLE QString watchTransactions(unsigned _w); + Q_INVOKABLE void killWatch(unsigned _w); + void clearWatches(); + bool isListening() const; bool isMining() const; @@ -431,41 +155,61 @@ public slots: void setMining(bool _l); void setListening(bool _l); + /// Check to see if anything has changed, fire off signals if so. + /// @note Must be called in the QObject's thread. + void poll(); + signals: - void changed(); -// void netChanged(); -// void miningChanged(); + void watchChanged(unsigned _w); + void coinbaseChanged(); + void keysChanged(); + void netChanged(); + void miningChanged(); private: - Q_PROPERTY(QString number READ number NOTIFY changed) - Q_PROPERTY(QString coinbase READ coinbase WRITE setCoinbase NOTIFY changed) - Q_PROPERTY(bool listening READ isListening WRITE setListening) - Q_PROPERTY(bool mining READ isMining WRITE setMining) - Q_PROPERTY(QString gasPrice READ gasPrice NOTIFY changed) - Q_PROPERTY(QString key READ key NOTIFY changed) - Q_PROPERTY(QStringList keys READ keys NOTIFY changed) - Q_PROPERTY(unsigned peerCount READ peerCount) + Q_PROPERTY(QString number READ number NOTIFY watchChanged) + Q_PROPERTY(QString coinbase READ coinbase WRITE setCoinbase NOTIFY coinbaseChanged) + Q_PROPERTY(QString gasPrice READ gasPrice) + Q_PROPERTY(QString key READ key NOTIFY keysChanged) + Q_PROPERTY(QStringList keys READ keys NOTIFY keysChanged) + Q_PROPERTY(bool mining READ isMining WRITE setMining NOTIFY netChanged) + Q_PROPERTY(bool listening READ isListening WRITE setListening NOTIFY netChanged) + Q_PROPERTY(unsigned peerCount READ peerCount NOTIFY miningChanged) eth::Client* m_client; + std::vector m_watches; QList m_accounts; }; -#define QETH_INSTALL_JS_NAMESPACE [f, eth, this]() \ +#define QETH_INSTALL_JS_NAMESPACE(frame, eth, env) [frame, eth, env]() \ { \ - f->disconnect(); \ - f->addToJavaScriptWindowObject("env", this, QWebFrame::QtOwnership); \ - f->addToJavaScriptWindowObject("eth", eth, QWebFrame::ScriptOwnership); \ - f->evaluateJavaScript("eth.watch = function(a, s, f) { eth.changed.connect(f ? f : s) }"); \ - f->evaluateJavaScript("eth.newBlock = function(f) { eth.changed.connect(f) }"); \ - \ - f->evaluateJavaScript("eth.create = function(s, v, c, g, p, f) { var v = eth.doCreate(s, v, c, g, p); if (f) f(v) }"); \ - f->evaluateJavaScript("eth.transact = function(s, v, t, d, g, p, f) { eth.doTransact(s, v, t, d, g, p); if (f) f() }"); \ - f->evaluateJavaScript("eth.transactions = function(a) { return JSON.parse(eth.getTransactions(JSON.stringify(a))); }"); \ - f->evaluateJavaScript("String.prototype.pad = function(l, r) { return eth.pad(this, l, r) }"); \ - f->evaluateJavaScript("String.prototype.bin = function() { return eth.toBinary(this) }"); \ - f->evaluateJavaScript("String.prototype.unbin = function(l) { return eth.fromBinary(this) }"); \ - f->evaluateJavaScript("String.prototype.unpad = function(l) { return eth.unpad(this) }"); \ - f->evaluateJavaScript("String.prototype.dec = function() { return eth.toDecimal(this) }"); \ - f->evaluateJavaScript("String.prototype.sha3 = function() { return eth.sha3(this) }"); \ + frame->disconnect(); \ + frame->addToJavaScriptWindowObject("env", env, QWebFrame::QtOwnership); \ + frame->addToJavaScriptWindowObject("eth", eth, QWebFrame::ScriptOwnership); \ + frame->evaluateJavaScript("eth.makeWatch = function(a) { var ww = eth.newWatch(a); return { w: ww, uninstall: function() { eth.killWatch(w) }, changed: function(f) { eth.watchChanged.connect(function(nw) { if (nw == this.w) f() }) }, transactions: function() { return JSON.parse(eth.watchTransactions(w)) } }; }"); \ + frame->evaluateJavaScript("eth.watch = function(a) { return makeWatch(JSON.stringify(a)); }"); \ + frame->evaluateJavaScript("eth.watchChain = function() { return eth.makeWatch('chainChanged') }"); \ + frame->evaluateJavaScript("eth.watchPending = function() { return eth.makeWatch('pendingChanged') }"); \ + frame->evaluateJavaScript("eth.create = function(s, v, c, g, p, f) { var v = eth.doCreate(s, v, c, g, p); if (f) f(v) }"); \ + frame->evaluateJavaScript("eth.transact = function(s, v, t, d, g, p, f) { eth.doTransact(s, v, t, d, g, p); if (f) f() }"); \ + frame->evaluateJavaScript("eth.transactions = function(a) { return JSON.parse(eth.getTransactions(JSON.stringify(a))); }"); \ + frame->evaluateJavaScript("String.prototype.pad = function(l, r) { return eth.pad(this, l, r) }"); \ + frame->evaluateJavaScript("String.prototype.bin = function() { return eth.toBinary(this) }"); \ + frame->evaluateJavaScript("String.prototype.unbin = function(l) { return eth.fromBinary(this) }"); \ + frame->evaluateJavaScript("String.prototype.unpad = function(l) { return eth.unpad(this) }"); \ + frame->evaluateJavaScript("String.prototype.dec = function() { return eth.toDecimal(this) }"); \ + frame->evaluateJavaScript("String.prototype.sha3 = function() { return eth.sha3(this) }"); \ +} + +template inline boost::multiprecision::number> toInt(QString const& _s) +{ + if (_s.startsWith("0x")) + return eth::fromBigEndian>>(eth::fromHex(_s.toStdString().substr(2))); + else if (!_s.contains(QRegExp("[^0-9]"))) + // Hex or Decimal + return boost::multiprecision::number>(_s.toStdString()); + else + // Binary + return eth::fromBigEndian>>(asBytes(padded(_s, N))); } diff --git a/libqethereum/QmlEthereum.cpp b/libqethereum/QmlEthereum.cpp new file mode 100644 index 000000000..560d75ddc --- /dev/null +++ b/libqethereum/QmlEthereum.cpp @@ -0,0 +1,188 @@ +#if ETH_QTQML +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#include +#include "QmlEthereum.h" +using namespace std; + +// types +using eth::bytes; +using eth::bytesConstRef; +using eth::h160; +using eth::h256; +using eth::u160; +using eth::u256; +using eth::u256s; +using eth::Address; +using eth::BlockInfo; +using eth::Client; +using eth::Instruction; +using eth::KeyPair; +using eth::NodeMode; +using eth::PeerInfo; +using eth::RLP; +using eth::Secret; +using eth::Transaction; + +// functions +using eth::toHex; +using eth::disassemble; +using eth::formatBalance; +using eth::fromHex; +using eth::right160; +using eth::simpleDebugOut; +using eth::toLog2; +using eth::toString; +using eth::units; + +// vars +using eth::g_logPost; +using eth::g_logVerbosity; +using eth::c_instructionInfo; + +// Horrible global for the mainwindow. Needed for the QmlEthereums to find the Main window which acts as multiplexer for now. +// Can get rid of this once we've sorted out ITC for signalling & multiplexed querying. +eth::Client* g_qmlClient; +QObject* g_qmlMain; + +QmlAccount::QmlAccount(QObject*) +{ +} + +QmlAccount::~QmlAccount() +{ +} + +void QmlAccount::setEthereum(QmlEthereum* _eth) +{ + if (m_eth == _eth) + return; +// if (m_eth) +// disconnect(m_eth, SIGNAL(changed()), this, SIGNAL(changed())); + m_eth = _eth; +// if (m_eth) +// connect(m_eth, SIGNAL(changed()), this, SIGNAL(changed())); + ethChanged(); +// changed(); +} + +eth::u256 QmlAccount::balance() const +{ + if (m_eth) + return m_eth->balanceAt(m_address); + return u256(0); +} + +double QmlAccount::txCount() const +{ + if (m_eth) + return m_eth->txCountAt(m_address); + return 0; +} + +bool QmlAccount::isContract() const +{ + if (m_eth) + return m_eth->isContractAt(m_address); + return 0; +} + +QmlEthereum::QmlEthereum(QObject* _p): QObject(_p) +{ +// connect(g_qmlMain, SIGNAL(changed()), SIGNAL(changed())); +} + +QmlEthereum::~QmlEthereum() +{ +} + +Client* QmlEthereum::client() const +{ + return g_qmlClient; +} + +Address QmlEthereum::coinbase() const +{ + return client()->address(); +} + +void QmlEthereum::setCoinbase(Address _a) +{ + if (client()->address() != _a) + { + client()->setAddress(_a); +// changed(); + } +} + +u256 QmlEthereum::balanceAt(Address _a) const +{ + return client()->postState().balance(_a); +} + +bool QmlEthereum::isContractAt(Address _a) const +{ + return client()->postState().addressHasCode(_a); +} + +bool QmlEthereum::isMining() const +{ + return client()->isMining(); +} + +bool QmlEthereum::isListening() const +{ + return client()->haveNetwork(); +} + +void QmlEthereum::setMining(bool _l) +{ + if (_l) + client()->startMining(); + else + client()->stopMining(); +} + +void QmlEthereum::setListening(bool _l) +{ + if (_l) + client()->startNetwork(); + else + client()->stopNetwork(); +} + +double QmlEthereum::txCountAt(Address _a) const +{ + return (double)client()->postState().transactionsFrom(_a); +} + +unsigned QmlEthereum::peerCount() const +{ + return (unsigned)client()->peerCount(); +} + +void QmlEthereum::transact(Secret _secret, u256 _amount, u256 _gasPrice, u256 _gas, QByteArray _init) +{ + client()->transact(_secret, _amount, bytes(_init.data(), _init.data() + _init.size()), _gas, _gasPrice); +} + +void QmlEthereum::transact(Secret _secret, Address _dest, u256 _amount, u256 _gasPrice, u256 _gas, QByteArray _data) +{ + client()->transact(_secret, _amount, _dest, bytes(_data.data(), _data.data() + _data.size()), _gas, _gasPrice); +} + +// extra bits needed to link on VS +#ifdef _MSC_VER + +// include moc file, ofuscated to hide from automoc +#include\ +"moc_QmlEthereum.cpp" + +#endif diff --git a/libqethereum/QmlEthereum.h b/libqethereum/QmlEthereum.h new file mode 100644 index 000000000..2554dd02f --- /dev/null +++ b/libqethereum/QmlEthereum.h @@ -0,0 +1,283 @@ +#pragma once + +#include +#if ETH_QTQML +#include +#endif +#include +#include + +namespace eth { +class Client; +class State; +} + +class QQmlEngine; +class QmlAccount; +class QmlEthereum; + +extern eth::Client* g_qmlClient; +extern QObject* g_qmlMain; + +Q_DECLARE_METATYPE(eth::u256) +Q_DECLARE_METATYPE(eth::Address) +Q_DECLARE_METATYPE(eth::Secret) +Q_DECLARE_METATYPE(eth::KeyPair) +Q_DECLARE_METATYPE(QmlAccount*) +Q_DECLARE_METATYPE(QmlEthereum*) + +class QmlU256Helper: public QObject +{ + Q_OBJECT + +public: + QmlU256Helper(QObject* _p = nullptr): QObject(_p) {} + + Q_INVOKABLE eth::u256 add(eth::u256 _a, eth::u256 _b) const { return _a + _b; } + Q_INVOKABLE eth::u256 sub(eth::u256 _a, eth::u256 _b) const { return _a - _b; } + Q_INVOKABLE eth::u256 mul(eth::u256 _a, int _b) const { return _a * _b; } + Q_INVOKABLE eth::u256 mul(int _a, eth::u256 _b) const { return _a * _b; } + Q_INVOKABLE eth::u256 div(eth::u256 _a, int _b) const { return _a / _b; } + + Q_INVOKABLE eth::u256 wei(double _s) const { return (eth::u256)_s; } + Q_INVOKABLE eth::u256 szabo(double _s) const { return (eth::u256)(_s * (double)eth::szabo); } + Q_INVOKABLE eth::u256 finney(double _s) const { return (eth::u256)(_s * (double)eth::finney); } + Q_INVOKABLE eth::u256 ether(double _s) const { return (eth::u256)(_s * (double)eth::ether); } + Q_INVOKABLE eth::u256 wei(unsigned _s) const { return (eth::u256)_s; } + Q_INVOKABLE eth::u256 szabo(unsigned _s) const { return (eth::u256)(_s * eth::szabo); } + Q_INVOKABLE eth::u256 finney(unsigned _s) const { return (eth::u256)(_s * eth::finney); } + Q_INVOKABLE eth::u256 ether(unsigned _s) const { return (eth::u256)(_s * eth::ether); } + Q_INVOKABLE double toWei(eth::u256 _t) const { return (double)_t; } + Q_INVOKABLE double toSzabo(eth::u256 _t) const { return toWei(_t) / (double)eth::szabo; } + Q_INVOKABLE double toFinney(eth::u256 _t) const { return toWei(_t) / (double)eth::finney; } + Q_INVOKABLE double toEther(eth::u256 _t) const { return toWei(_t) / (double)eth::ether; } + + Q_INVOKABLE double value(eth::u256 _t) const { return (double)_t; } + + Q_INVOKABLE QString stringOf(eth::u256 _t) const { return QString::fromStdString(eth::formatBalance(_t)); } +}; + +class QmlKeyHelper: public QObject +{ + Q_OBJECT + +public: + QmlKeyHelper(QObject* _p = nullptr): QObject(_p) {} + + Q_INVOKABLE eth::KeyPair create() const { return eth::KeyPair::create(); } + Q_INVOKABLE eth::Address address(eth::KeyPair _p) const { return _p.address(); } + Q_INVOKABLE eth::Secret secret(eth::KeyPair _p) const { return _p.secret(); } + Q_INVOKABLE eth::KeyPair keypair(eth::Secret _k) const { return eth::KeyPair(_k); } + + Q_INVOKABLE bool isNull(eth::Address _a) const { return !_a; } + + Q_INVOKABLE eth::Address addressOf(QString _s) const { return eth::Address(_s.toStdString()); } + Q_INVOKABLE QString stringOf(eth::Address _a) const { return QString::fromStdString(eth::toHex(_a.asArray())); } + Q_INVOKABLE QString toAbridged(eth::Address _a) const { return QString::fromStdString(_a.abridged()); } +}; + +class QmlAccount: public QObject +{ + Q_OBJECT + +public: + QmlAccount(QObject* _p = nullptr); + virtual ~QmlAccount(); + + Q_INVOKABLE QmlEthereum* ethereum() const { return m_eth; } + Q_INVOKABLE eth::u256 balance() const; + Q_INVOKABLE double txCount() const; + Q_INVOKABLE bool isContract() const; + Q_INVOKABLE eth::Address address() const { return m_address; } + + // TODO: past transactions models. + +public slots: + void setEthereum(QmlEthereum* _eth); + void setAddress(eth::Address _a) { m_address = _a; } + +signals: + void changed(); + void ethChanged(); + +private: + QmlEthereum* m_eth = nullptr; + eth::Address m_address; + + Q_PROPERTY(eth::u256 balance READ balance NOTIFY changed STORED false) + Q_PROPERTY(double txCount READ txCount NOTIFY changed STORED false) + Q_PROPERTY(bool isContract READ isContract NOTIFY changed STORED false) + Q_PROPERTY(eth::Address address READ address WRITE setAddress NOTIFY changed) + Q_PROPERTY(QmlEthereum* ethereum READ ethereum WRITE setEthereum NOTIFY ethChanged) +}; + +class QmlEthereum: public QObject +{ + Q_OBJECT + +public: + QmlEthereum(QObject* _p = nullptr); + virtual ~QmlEthereum(); + + eth::Client* client() const; + + static QObject* constructU256Helper(QQmlEngine*, QJSEngine*) { return new QmlU256Helper; } + static QObject* constructKeyHelper(QQmlEngine*, QJSEngine*) { return new QmlKeyHelper; } + + Q_INVOKABLE eth::Address coinbase() const; + + Q_INVOKABLE bool isListening() const; + Q_INVOKABLE bool isMining() const; + + Q_INVOKABLE eth::u256 balanceAt(eth::Address _a) const; + Q_INVOKABLE double txCountAt(eth::Address _a) const; + Q_INVOKABLE bool isContractAt(eth::Address _a) const; + + Q_INVOKABLE unsigned peerCount() const; + + Q_INVOKABLE QmlEthereum* self() { return this; } + +public slots: + void transact(eth::Secret _secret, eth::Address _dest, eth::u256 _amount, eth::u256 _gasPrice, eth::u256 _gas, QByteArray _data); + void transact(eth::Secret _secret, eth::u256 _amount, eth::u256 _gasPrice, eth::u256 _gas, QByteArray _init); + void setCoinbase(eth::Address); + void setMining(bool _l); + + void setListening(bool _l); + +signals: + void coinbaseChanged(); +// void netChanged(); +// void miningChanged(); + +private: + Q_PROPERTY(eth::Address coinbase READ coinbase WRITE setCoinbase NOTIFY coinbaseChanged) + Q_PROPERTY(bool listening READ isListening WRITE setListening) + Q_PROPERTY(bool mining READ isMining WRITE setMining) +}; + +#if 0 +template T to(QVariant const& _s) { if (_s.type() != QVariant::String) return T(); auto s = _s.toString().toLatin1(); assert(s.size() == sizeof(T)); return *(T*)s.data(); } +template QVariant toQJS(T const& _s) { QLatin1String ret((char*)&_s, sizeof(T)); assert(QVariant(QString(ret)).toString().toLatin1().size() == sizeof(T)); assert(*(T*)(QVariant(QString(ret)).toString().toLatin1().data()) == _s); return QVariant(QString(ret)); } + +class U256Helper: public QObject +{ + Q_OBJECT + +public: + U256Helper(QObject* _p = nullptr): QObject(_p) {} + + static eth::u256 in(QVariant const& _s) { return to(_s); } + static QVariant out(eth::u256 const& _s) { return toQJS(_s); } + + Q_INVOKABLE QVariant add(QVariant _a, QVariant _b) const { return out(in(_a) + in(_b)); } + Q_INVOKABLE QVariant sub(QVariant _a, QVariant _b) const { return out(in(_a) - in(_b)); } + Q_INVOKABLE QVariant mul(QVariant _a, int _b) const { return out(in(_a) * in(_b)); } + Q_INVOKABLE QVariant mul(int _a, QVariant _b) const { return out(in(_a) * in(_b)); } + Q_INVOKABLE QVariant div(QVariant _a, int _b) const { return out(in(_a) / in(_b)); } + + Q_INVOKABLE QVariant wei(double _s) const { return out(eth::u256(_s)); } + Q_INVOKABLE QVariant szabo(double _s) const { return out(eth::u256(_s * (double)eth::szabo)); } + Q_INVOKABLE QVariant finney(double _s) const { return out(eth::u256(_s * (double)eth::finney)); } + Q_INVOKABLE QVariant ether(double _s) const { return out(eth::u256(_s * (double)eth::ether)); } + Q_INVOKABLE QVariant wei(unsigned _s) const { return value(_s); } + Q_INVOKABLE QVariant szabo(unsigned _s) const { return out(eth::u256(_s) * eth::szabo); } + Q_INVOKABLE QVariant finney(unsigned _s) const { return out(eth::u256(_s) * eth::finney); } + Q_INVOKABLE QVariant ether(unsigned _s) const { return out(eth::u256(_s) * eth::ether); } + Q_INVOKABLE double toWei(QVariant _t) const { return toValue(_t); } + Q_INVOKABLE double toSzabo(QVariant _t) const { return toWei(_t) / (double)eth::szabo; } + Q_INVOKABLE double toFinney(QVariant _t) const { return toWei(_t) / (double)eth::finney; } + Q_INVOKABLE double toEther(QVariant _t) const { return toWei(_t) / (double)eth::ether; } + + Q_INVOKABLE QVariant value(unsigned _s) const { return out(eth::u256(_s)); } + Q_INVOKABLE double toValue(QVariant _t) const { return (double)in(_t); } + + Q_INVOKABLE QString ethOf(QVariant _t) const { return QString::fromStdString(eth::formatBalance(in(_t))); } + Q_INVOKABLE QString stringOf(QVariant _t) const { return QString::fromStdString(eth::toString(in(_t))); } + + Q_INVOKABLE QByteArray bytesOf(QVariant _t) const { eth::h256 b = in(_t); return QByteArray((char const*)&b, sizeof(eth::h256)); } + Q_INVOKABLE QVariant fromHex(QString _s) const { return out((eth::u256)eth::h256(_s.toStdString())); } + + Q_INVOKABLE QVariant fromAddress(QVariant/*eth::Address*/ _a) const { return out((eth::u160)to(_a)); } + Q_INVOKABLE QVariant toAddress(QVariant/*eth::Address*/ _a) const { return toQJS((eth::u160)in(_a)); } + + Q_INVOKABLE bool isNull(QVariant/*eth::Address*/ _a) const { return !in(_a); } +}; + +class KeyHelper: public QObject +{ + Q_OBJECT + +public: + KeyHelper(QObject* _p = nullptr): QObject(_p) {} + + static eth::Address in(QVariant const& _s) { return to(_s); } + static QVariant out(eth::Address const& _s) { return toQJS(_s); } + + Q_INVOKABLE QVariant/*eth::KeyPair*/ create() const { return toQJS(eth::KeyPair::create()); } + Q_INVOKABLE QVariant/*eth::Address*/ address(QVariant/*eth::KeyPair*/ _p) const { return out(to(_p).address()); } + Q_INVOKABLE QVariant/*eth::Secret*/ secret(QVariant/*eth::KeyPair*/ _p) const { return toQJS(to(_p).secret()); } + Q_INVOKABLE QVariant/*eth::KeyPair*/ keypair(QVariant/*eth::Secret*/ _k) const { return toQJS(eth::KeyPair(to(_k))); } + + Q_INVOKABLE bool isNull(QVariant/*eth::Address*/ _a) const { return !in(_a); } + + Q_INVOKABLE QVariant/*eth::Address*/ addressOf(QString _s) const { return out(eth::Address(_s.toStdString())); } + Q_INVOKABLE QString stringOf(QVariant/*eth::Address*/ _a) const { return QString::fromStdString(eth::toHex(in(_a).asArray())); } + Q_INVOKABLE QString toAbridged(QVariant/*eth::Address*/ _a) const { return QString::fromStdString(in(_a).abridged()); } + +}; + +class BytesHelper: public QObject +{ + Q_OBJECT + +public: + BytesHelper(QObject* _p = nullptr): QObject(_p) {} + + Q_INVOKABLE QByteArray concat(QVariant _v, QVariant _w) const + { + QByteArray ba; + if (_v.type() == QVariant::ByteArray) + ba = _v.toByteArray(); + else + ba = _v.toString().toLatin1(); + QByteArray ba2; + if (_w.type() == QVariant::ByteArray) + ba2 = _w.toByteArray(); + else + ba2 = _w.toString().toLatin1(); + ba.append(ba2); + return QByteArray(ba); + } + Q_INVOKABLE QByteArray concat(QByteArray _v, QByteArray _w) const + { + _v.append(_w); + return _v; + } + Q_INVOKABLE QByteArray fromString(QString _s) const + { + return _s.toLatin1(); + } + Q_INVOKABLE QByteArray fromString(QString _s, unsigned _padding) const + { + QByteArray b = _s.toLatin1(); + for (unsigned i = b.size(); i < _padding; ++i) + b.append((char)0); + b.resize(_padding); + return b; + } + Q_INVOKABLE QString toString(QByteArray _b) const + { + while (_b.size() && !_b[_b.size() - 1]) + _b.resize(_b.size() - 1); + return QString::fromLatin1(_b); + } + Q_INVOKABLE QVariant u256of(QByteArray _s) const + { + while (_s.size() < 32) + _s.append((char)0); + eth::h256 ret((uint8_t const*)_s.data(), eth::h256::ConstructFromPointer); + return toQJS(ret); + } +}; +#endif diff --git a/neth/main.cpp b/neth/main.cpp index 01199b7fc..f8947fd7c 100644 --- a/neth/main.cpp +++ b/neth/main.cpp @@ -417,7 +417,6 @@ int main(int argc, char** argv) if (!clientName.empty()) clientName += "/"; Client c("NEthereum(++)/" + clientName + "v" + eth::EthVersion + "/" ETH_QUOTED(ETH_BUILD_TYPE) "/" ETH_QUOTED(ETH_BUILD_PLATFORM), coinbase, dbPath); - c.start(); cout << credits(); std::ostringstream ccout; diff --git a/walleth/MainWin.cpp b/walleth/MainWin.cpp index 2c36d94fc..7047e05b4 100644 --- a/walleth/MainWin.cpp +++ b/walleth/MainWin.cpp @@ -63,7 +63,6 @@ Main::Main(QWidget *parent) : g_qmlMain = this; m_client.reset(new Client("Walleth", Address(), eth::getDataDir() + "/Walleth")); - m_client->start(); g_qmlClient = m_client.get(); @@ -105,8 +104,6 @@ Main::Main(QWidget *parent) : connect(m_refreshNetwork, SIGNAL(timeout()), SLOT(refreshNetwork())); m_refreshNetwork->start(1000); - connect(this, SIGNAL(changed()), SLOT(refresh())); - connect(&m_webCtrl, &QNetworkAccessManager::finished, [&](QNetworkReply* _r) { m_servers = QString::fromUtf8(_r->readAll()).split("\n", QString::SkipEmptyParts); @@ -135,8 +132,7 @@ Main::~Main() void Main::timerEvent(QTimerEvent *) { - if (m_client->changed()) - changed(); + } void Main::on_about_triggered() diff --git a/walleth/MainWin.h b/walleth/MainWin.h index 61891e0a3..b4c192d01 100644 --- a/walleth/MainWin.h +++ b/walleth/MainWin.h @@ -6,7 +6,7 @@ #include #include #include -#include +#include namespace Ui { class Main; @@ -43,9 +43,6 @@ private slots: void refresh(); void refreshNetwork(); -signals: - void changed(); - protected: virtual void timerEvent(QTimerEvent *); From b1e0666ed4503bc6cd3a2885fe5e1a446b8eb6c8 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 23 Jul 2014 21:19:08 +0200 Subject: [PATCH 002/223] Various fixes. --- alethzero/MainWin.cpp | 1 + libethereum/Client.cpp | 23 +++++++++++++++++------ libethereum/State.cpp | 3 +++ 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index c0e59cf7c..c269090f5 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -647,6 +647,7 @@ void Main::refreshAll() refreshBlockCount(); refreshPending(); refreshAccounts(); + refreshBalances(); } void Main::refreshPending() diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index 715c3a3cc..f109fe513 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -69,11 +69,12 @@ Client::Client(std::string const& _clientVersion, Address _us, std::string const m_stateDB(State::openDB(_dbPath, !m_vc.ok() || _forceClean)), m_preMine(_us, m_stateDB), m_postMine(_us, m_stateDB), - m_workState(Active) + m_workState(Deleted) { if (_dbPath.size()) Defaults::setDBPath(_dbPath); m_vc.setOk(); + work(true); } void Client::ensureWorking() @@ -84,6 +85,7 @@ void Client::ensureWorking() m_work.reset(new thread([&]() { setThreadName(c_threadName); + m_workState.store(Active, std::memory_order_release); while (m_workState.load(std::memory_order_acquire) != Deleting) work(); m_workState.store(Deleted, std::memory_order_release); @@ -97,11 +99,14 @@ void Client::ensureWorking() Client::~Client() { - if (m_workState.load(std::memory_order_acquire) == Active) - m_workState.store(Deleting, std::memory_order_release); - while (m_workState.load(std::memory_order_acquire) != Deleted) - this_thread::sleep_for(chrono::milliseconds(10)); - m_work->join(); + if (m_work) + { + if (m_workState.load(std::memory_order_acquire) == Active) + m_workState.store(Deleting, std::memory_order_release); + while (m_workState.load(std::memory_order_acquire) != Deleted) + this_thread::sleep_for(chrono::milliseconds(10)); + m_work->join(); + } } void Client::flushTransactions() @@ -247,6 +252,8 @@ void Client::stopMining() void Client::transact(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice) { + ensureWorking(); + ClientGuard l(this); Transaction t; cdebug << "Nonce at " << toAddress(_secret) << " pre:" << m_preMine.transactionsFrom(toAddress(_secret)) << " post:" << m_postMine.transactionsFrom(toAddress(_secret)); @@ -263,6 +270,8 @@ void Client::transact(Secret _secret, u256 _value, Address _dest, bytes const& _ Address Client::transact(Secret _secret, u256 _endowment, bytes const& _init, u256 _gas, u256 _gasPrice) { + ensureWorking(); + ClientGuard l(this); Transaction t; t.nonce = m_postMine.transactionsFrom(toAddress(_secret)); @@ -279,6 +288,8 @@ Address Client::transact(Secret _secret, u256 _endowment, bytes const& _init, u2 void Client::inject(bytesConstRef _rlp) { + ensureWorking(); + ClientGuard l(this); m_tq.attemptImport(_rlp); } diff --git a/libethereum/State.cpp b/libethereum/State.cpp index f7fd5049c..b4a41edc5 100644 --- a/libethereum/State.cpp +++ b/libethereum/State.cpp @@ -281,7 +281,10 @@ void State::ensureCached(std::map& _cache, Address _a, bo // populate basic info. string stateBack = m_state.at(_a); if (stateBack.empty() && !_forceCreate) + { + cdebug << m_state; return; + } RLP state(stateBack); AddressState s; if (state.isNull()) From b389052eeb42422176b254c5377090f92a33e8ee Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 23 Jul 2014 22:51:14 +0200 Subject: [PATCH 003/223] Minor updates. --- libethereum/State.cpp | 3 --- liblll/Assembly.cpp | 8 ++++---- 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/libethereum/State.cpp b/libethereum/State.cpp index b4a41edc5..f7fd5049c 100644 --- a/libethereum/State.cpp +++ b/libethereum/State.cpp @@ -281,10 +281,7 @@ void State::ensureCached(std::map& _cache, Address _a, bo // populate basic info. string stateBack = m_state.at(_a); if (stateBack.empty() && !_forceCreate) - { - cdebug << m_state; return; - } RLP state(stateBack); AddressState s; if (state.isNull()) diff --git a/liblll/Assembly.cpp b/liblll/Assembly.cpp index 6f5678539..3abdf66f7 100644 --- a/liblll/Assembly.cpp +++ b/liblll/Assembly.cpp @@ -116,7 +116,7 @@ ostream& eth::operator<<(ostream& _out, AssemblyItemsConstRef _i) _out << " PUSH" << i.data(); break; case PushString: - _out << " PUSH'[" << h256(i.data()).abridged() << "]"; + _out << " PUSH'[" << hex << (unsigned)i.data() << "]"; break; case PushTag: _out << " PUSH[tag" << i.data() << "]"; @@ -125,7 +125,7 @@ ostream& eth::operator<<(ostream& _out, AssemblyItemsConstRef _i) _out << " tag" << i.data() << ":"; break; case PushData: - _out << " PUSH*[" << h256(i.data()).abridged() << "]"; + _out << " PUSH*[" << hex << (unsigned)i.data() << "]"; break; case UndefinedItem: _out << " ???"; @@ -156,7 +156,7 @@ ostream& Assembly::streamOut(ostream& _out) const _out << "tag" << i.m_data << ": " << endl; break; case PushData: - _out << " PUSH [" << h256(i.m_data).abridged() << "]" << endl; + _out << " PUSH [" << hex << (unsigned)i.m_data << "]" << endl; break; default:; } @@ -165,7 +165,7 @@ ostream& Assembly::streamOut(ostream& _out) const { _out << ".data:" << endl; for (auto const& i: m_data) - _out << " " << i.first.abridged() << ": " << toHex(i.second) << endl; + _out << " " << hex << (unsigned)(u256)i.first << ": " << toHex(i.second) << endl; } return _out; } From 2fff90ba788ff85c9135b609448c66b5bc501131 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 24 Jul 2014 03:03:13 +0200 Subject: [PATCH 004/223] Default to higher throughput. --- libethereum/PeerSession.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libethereum/PeerSession.cpp b/libethereum/PeerSession.cpp index f06b2fcbf..adf2fb1a8 100644 --- a/libethereum/PeerSession.cpp +++ b/libethereum/PeerSession.cpp @@ -31,9 +31,9 @@ using namespace eth; #define clogS(X) eth::LogOutputStream(false) << "| " << std::setw(2) << m_socket.native_handle() << "] " -static const eth::uint c_maxHashes = 32; ///< Maximum number of hashes GetChain will ever send. -static const eth::uint c_maxBlocks = 64; ///< Maximum number of blocks Blocks will ever send. BUG: if this gets too big (e.g. 2048) stuff starts going wrong. -static const eth::uint c_maxBlocksAsk = 256; ///< Maximum number of blocks we ask to receive in Blocks (when using GetChain). +static const eth::uint c_maxHashes = 4096; ///< Maximum number of hashes GetChain will ever send. +static const eth::uint c_maxBlocks = 2048; ///< Maximum number of blocks Blocks will ever send. +static const eth::uint c_maxBlocksAsk = 512; ///< Maximum number of blocks we ask to receive in Blocks (when using GetChain). PeerSession::PeerSession(PeerServer* _s, bi::tcp::socket _socket, uint _rNId, bi::address _peerAddress, unsigned short _peerPort): m_server(_s), From d53864e1628edd48b0287c8f4a1be8011f3bd338 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 24 Jul 2014 12:25:01 +0200 Subject: [PATCH 005/223] Fixes. --- alethzero/MainWin.cpp | 2 +- libethereum/Client.cpp | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index 703bd4a89..bb4ab9889 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -913,7 +913,7 @@ void Main::on_transactionQueue_currentItemChanged() stringstream s; int i = ui->transactionQueue->currentRow(); - if (i >= 0) + if (i >= 0 && i < (int)m_client->postState().pending().size()) { Transaction tx(m_client->postState().pending()[i]); auto ss = tx.safeSender(); diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index f109fe513..94a801df8 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -166,7 +166,7 @@ void Client::appendFromNewPending(h256 _bloom, h256Set& o_changed) const { lock_guard l(m_filterLock); for (pair const& i: m_filters) - if (numberOf(i.second.filter.earliest()) == m_postMine.info().number && i.second.filter.matches(_bloom)) + if (numberOf(i.second.filter.latest()) == m_postMine.info().number && i.second.filter.matches(_bloom)) o_changed.insert(i.first); } @@ -176,7 +176,7 @@ void Client::appendFromNewBlock(h256 _block, h256Set& o_changed) const lock_guard l(m_filterLock); for (pair const& i: m_filters) - if (numberOf(i.second.filter.earliest()) >= d.number && i.second.filter.matches(d.bloom)) + if (numberOf(i.second.filter.latest()) >= d.number && i.second.filter.matches(d.bloom)) o_changed.insert(i.first); } @@ -304,7 +304,7 @@ void Client::work(bool _justQueue) if (m_net && !_justQueue) { ClientGuard l(this); - m_net->process(); // must be in guard for now since it uses the blockchain. TODO: make BlockChain thread-safe. + m_net->process(); // must be in guard for now since it uses the blockchain. // TODO: return h256Set as block hashes, once for each block that has come in/gone out. h256Set newBlocks = m_net->sync(m_bc, m_tq, m_stateDB); @@ -313,6 +313,7 @@ void Client::work(bool _justQueue) for (auto i: newBlocks) appendFromNewBlock(i, changeds); changeds.insert(NewBlockFilter); + changeds.insert(NewPendingFilter); // if there's a new block, then we've probably reset the pending transactions. } } From e653d2e61e825892ae668d2400b5b4469a86fab1 Mon Sep 17 00:00:00 2001 From: caktux Date: Thu, 24 Jul 2014 10:30:52 -0400 Subject: [PATCH 006/223] update inspect cmd, whitespace cleanup --- eth/main.cpp | 41 +++++++++++++++++++++++++---------------- neth/main.cpp | 45 +++++++++++++++++++++++++++------------------ 2 files changed, 52 insertions(+), 34 deletions(-) diff --git a/eth/main.cpp b/eth/main.cpp index 3ab4c5837..ebad68709 100644 --- a/eth/main.cpp +++ b/eth/main.cpp @@ -448,12 +448,12 @@ int main(int argc, char** argv) if (size < 40) { if (size > 0) - cwarn << "Invalid address length: " << size; + cwarn << "Invalid address length:" << size; } else if (gasPrice < info.minGasPrice) - cwarn << "Minimum gas price is " << info.minGasPrice; + cwarn << "Minimum gas price is" << info.minGasPrice; else if (gas < minGas) - cwarn << "Minimum gas amount is " << minGas; + cwarn << "Minimum gas amount is" << minGas; else if (ssize < 40) { if (ssize > 0) @@ -515,7 +515,7 @@ int main(int argc, char** argv) if (size < 40) { if (size > 0) - cwarn << "Invalid address length: " << size; + cwarn << "Invalid address length:" << size; } else { @@ -550,7 +550,7 @@ int main(int argc, char** argv) bytes init; cnote << "Init:"; cnote << sinit; - cnote << "Code size: " << size; + cnote << "Code size:" << size; if (size < 1) cwarn << "No code submitted"; else @@ -567,9 +567,9 @@ int main(int argc, char** argv) if (endowment < 0) cwarn << "Invalid endowment"; else if (gasPrice < info.minGasPrice) - cwarn << "Minimum gas price is " << info.minGasPrice; + cwarn << "Minimum gas price is" << info.minGasPrice; else if (gas < minGas) - cwarn << "Minimum gas amount is " << minGas; + cwarn << "Minimum gas amount is" << minGas; else c.transact(us.secret(), endowment, init, gas, gasPrice); } @@ -588,17 +588,26 @@ int main(int argc, char** argv) ClientGuard g(&c); auto h = h160(fromHex(rechex)); stringstream s; - auto mem = c.state().storage(h); - for (auto const& i: mem) - s << "@" << showbase << hex << i.first << " " << showbase << hex << i.second << endl; - s << endl << disassemble(c.state().code(h)); + try + { + auto storage = c.state().storage(h); + for (auto const& i: storage) + s << "@" << showbase << hex << i.first << " " << showbase << hex << i.second << endl; + s << endl << disassemble(c.state().code(h)) << endl; + + string outFile = getDataDir() + "/" + rechex + ".evm"; + ofstream ofs; + ofs.open(outFile, ofstream::binary); + ofs.write(s.str().c_str(), s.str().length()); + ofs.close(); - string outFile = getDataDir() + "/" + rechex + ".evm"; - ofstream ofs; - ofs.open(outFile, ofstream::binary); - ofs.write(s.str().c_str(), s.str().length()); - ofs.close(); + cnote << "Saved" << rechex << "to" << outFile; + } + catch (eth::InvalidTrie) + { + cwarn << "Corrupted trie."; + } } } else if (cmd == "setSecret") diff --git a/neth/main.cpp b/neth/main.cpp index 01199b7fc..48c97568f 100644 --- a/neth/main.cpp +++ b/neth/main.cpp @@ -151,7 +151,7 @@ void version() exit(0); } -Address c_config = Address("9ef0f0d81e040012600b0c1abdef7c48f720f88a"); +Address c_config = Address("661005d2720d855f1d9976f88bb10c1a3398c77f"); string pretty(h160 _a, eth::State _st) { string ns; @@ -668,12 +668,12 @@ int main(int argc, char** argv) if (size < 40) { if (size > 0) - cwarn << "Invalid address length: " << size; + cwarn << "Invalid address length:" << size; } else if (gasPrice < info.minGasPrice) - cwarn << "Minimum gas price is " << info.minGasPrice; + cwarn << "Minimum gas price is" << info.minGasPrice; else if (gas < minGas) - cwarn << "Minimum gas amount is " << minGas; + cwarn << "Minimum gas amount is" << minGas; else if (ssize < 40) { if (ssize > 0) @@ -713,7 +713,7 @@ int main(int argc, char** argv) if (size < 40) { if (size > 0) - cwarn << "Invalid address length: " << size; + cwarn << "Invalid address length:" << size; } else { @@ -770,7 +770,7 @@ int main(int argc, char** argv) bytes init; cnote << "Init:"; cnote << sinit; - cnote << "Code size: " << size; + cnote << "Code size:" << size; if (size < 1) cwarn << "No code submitted"; else @@ -787,9 +787,9 @@ int main(int argc, char** argv) if (endowment < 0) cwarn << "Invalid endowment"; else if (gasPrice < info.minGasPrice) - cwarn << "Minimum gas price is " << info.minGasPrice; + cwarn << "Minimum gas price is" << info.minGasPrice; else if (gas < minGas) - cwarn << "Minimum gas amount is " << minGas; + cwarn << "Minimum gas amount is" << minGas; else { c.transact(us.secret(), endowment, init, gas, gasPrice); @@ -808,17 +808,26 @@ int main(int argc, char** argv) ClientGuard g(&c); auto h = h160(fromHex(rechex)); stringstream s; - auto mem = c.state().storage(h); - for (auto const& i: mem) - s << "@" << showbase << hex << i.first << " " << showbase << hex << i.second << endl; - s << endl << disassemble(c.state().code(h)); - - string outFile = getDataDir() + "/" + rechex + ".evm"; - ofstream ofs; - ofs.open(outFile, ofstream::binary); - ofs.write(s.str().c_str(), s.str().length()); - ofs.close(); + try + { + auto storage = c.state().storage(h); + for (auto const& i: storage) + s << "@" << showbase << hex << i.first << " " << showbase << hex << i.second << endl; + s << endl << disassemble(c.state().code(h)) << endl; + + string outFile = getDataDir() + "/" + rechex + ".evm"; + ofstream ofs; + ofs.open(outFile, ofstream::binary); + ofs.write(s.str().c_str(), s.str().length()); + ofs.close(); + + cnote << "Saved" << rechex << "to" << outFile; + } + catch (eth::InvalidTrie) + { + cwarn << "Corrupted trie."; + } } } else if (cmd == "reset") From 0d2f5439ce9e5edec95bc985ad40c82f6c4b1258 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 24 Jul 2014 18:16:36 +0200 Subject: [PATCH 007/223] Nearly complete differential changelog system. --- alethzero/MainWin.cpp | 9 ++++- libethereum/Client.cpp | 24 +++++------- libethereum/Client.h | 6 +-- libqethereum/QEthereum.cpp | 77 ++++++++++++++++++++++++++++---------- libqethereum/QEthereum.h | 7 +++- 5 files changed, 84 insertions(+), 39 deletions(-) diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index bb4ab9889..3a3982d30 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -212,6 +212,10 @@ Main::Main(QWidget *parent) : Main::~Main() { + // Must do this here since otherwise m_ethereum'll be deleted (and therefore clearWatches() called by the destructor) + // *after* the client is dead. + m_ethereum->clientDieing(); + g_logPost = simpleDebugOut; writeSettings(); } @@ -379,7 +383,8 @@ void Main::eval(QString const& _js) { if (_js.trimmed().isEmpty()) return; - QVariant ev = ui->webView->page()->currentFrame()->evaluateJavaScript(_js); + QVariant ev = ui->webView->page()->currentFrame()->evaluateJavaScript("___RET=(" + _js + ")"); + QVariant jsonEv = ui->webView->page()->currentFrame()->evaluateJavaScript("JSON.stringify(__RET)"); QString s; if (ev.isNull()) s = "null"; @@ -387,6 +392,8 @@ void Main::eval(QString const& _js) s = "\"" + ev.toString().toHtmlEscaped() + "\""; else if (ev.type() == QVariant::Int || ev.type() == QVariant::Double) s = "" + ev.toString().toHtmlEscaped() + ""; + else if (jsonEv.type() == QVariant::String) + s = "" + jsonEv.toString().toHtmlEscaped() + ""; else s = "unknown type"; m_consoleHistory.push_back(qMakePair(_js, s)); diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index 94a801df8..e059c9c7c 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -153,20 +153,20 @@ void Client::uninstallWatch(unsigned _i) auto it = m_watches.find(_i); if (it == m_watches.end()) return; + auto id = it->second.id; + m_watches.erase(it); - auto fit = m_filters.find(it->second.id); + auto fit = m_filters.find(id); if (fit != m_filters.end()) if (!--fit->second.refCount) m_filters.erase(fit); - - m_watches.erase(it); } void Client::appendFromNewPending(h256 _bloom, h256Set& o_changed) const { lock_guard l(m_filterLock); for (pair const& i: m_filters) - if (numberOf(i.second.filter.latest()) == m_postMine.info().number && i.second.filter.matches(_bloom)) + if ((unsigned)i.second.filter.latest() >= m_postMine.info().number && i.second.filter.matches(_bloom)) o_changed.insert(i.first); } @@ -176,7 +176,7 @@ void Client::appendFromNewBlock(h256 _block, h256Set& o_changed) const lock_guard l(m_filterLock); for (pair const& i: m_filters) - if (numberOf(i.second.filter.latest()) >= d.number && i.second.filter.matches(d.bloom)) + if ((unsigned)i.second.filter.latest() >= d.number && (unsigned)i.second.filter.earliest() <= d.number && i.second.filter.matches(d.bloom)) o_changed.insert(i.first); } @@ -587,13 +587,13 @@ PastMessages Client::transactions(TransactionFilter const& _f) const ClientGuard l(this); PastMessages ret; - unsigned begin = numberOf(_f.latest()); - unsigned end = min(begin, numberOf(_f.earliest())); + unsigned begin = min(m_bc.number(), (unsigned)_f.latest()); + unsigned end = min(begin, (unsigned)_f.earliest()); unsigned m = _f.max(); unsigned s = _f.skip(); // Handle pending transactions differently as they're not on the block chain. - if (_f.latest() == 0) + if (begin == m_bc.number()) { for (unsigned i = 0; i < m_postMine.pending().size(); ++i) { @@ -608,19 +608,15 @@ PastMessages Client::transactions(TransactionFilter const& _f) const s--; else // Have a transaction that contains a matching message. - ret.insert(ret.begin(), pm[j].polish(h256(), ts, 0)); + ret.insert(ret.begin(), pm[j].polish(h256(), ts, m_bc.number() + 1)); } } - // Early exit here since we can't rely on begin/end, being out of the blockchain as we are. - if (_f.earliest() == 0) - return ret; } #if ETH_DEBUG unsigned skipped = 0; unsigned falsePos = 0; #endif - auto cn = m_bc.number(); auto h = m_bc.numberHash(begin); unsigned n = begin; for (; ret.size() != m && n != end; n--, h = m_bc.details(h).parent) @@ -653,7 +649,7 @@ PastMessages Client::transactions(TransactionFilter const& _f) const s--; else // Have a transaction that contains a matching message. - ret.insert(ret.begin(), pm[j].polish(h, ts, cn - n + 2)); + ret.insert(ret.begin(), pm[j].polish(h, ts, n)); } } #if ETH_DEBUG diff --git a/libethereum/Client.h b/libethereum/Client.h index 180d2d07b..b1089741c 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -102,7 +102,7 @@ typedef std::vector PastMessages; class TransactionFilter { public: - TransactionFilter(int _earliest = GenesisBlock, int _latest = 0, unsigned _max = 10, unsigned _skip = 0): m_earliest(_earliest), m_latest(_latest), m_max(_max), m_skip(_skip) {} + TransactionFilter(int _earliest = 0, int _latest = -1, unsigned _max = 10, unsigned _skip = 0): m_earliest(_earliest), m_latest(_latest), m_max(_max), m_skip(_skip) {} void fillStream(RLPStream& _s) const; h256 sha3() const; @@ -131,8 +131,8 @@ private: std::set
m_to; std::set> m_stateAltered; std::set
m_altered; - int m_earliest; - int m_latest; + int m_earliest = 0; + int m_latest = -1; unsigned m_max; unsigned m_skip; }; diff --git a/libqethereum/QEthereum.cpp b/libqethereum/QEthereum.cpp index 9c9f5ce5f..38d7ca9da 100644 --- a/libqethereum/QEthereum.cpp +++ b/libqethereum/QEthereum.cpp @@ -97,11 +97,18 @@ QEthereum::~QEthereum() clearWatches(); } +void QEthereum::clientDieing() +{ + clearWatches(); + m_client = nullptr; +} + void QEthereum::clearWatches() { - for (auto i: m_watches) - m_client->uninstallWatch(i); - m_watches.clear(); + if (m_client) + for (auto i: m_watches) + m_client->uninstallWatch(i); + m_watches.clear(); } QString QEthereum::secretToAddress(QString _s) const @@ -135,12 +142,12 @@ QString QEthereum::sha3(QString _s) const QString QEthereum::coinbase() const { - return toQJS(client()->address()); + return m_client ? toQJS(client()->address()) : ""; } QString QEthereum::number() const { - return QString::number(client()->blockChain().number() + 1); + return m_client ? QString::number(client()->blockChain().number() + 1) : ""; } QString QEthereum::account() const @@ -175,7 +182,7 @@ QStringList QEthereum::keys() const void QEthereum::setCoinbase(QString _a) { - if (client()->address() != toAddress(_a)) + if (m_client && client()->address() != toAddress(_a)) { client()->setAddress(toAddress(_a)); coinbaseChanged(); @@ -184,58 +191,75 @@ void QEthereum::setCoinbase(QString _a) QString QEthereum::balanceAt(QString _a) const { - return toQJS(client()->postState().balance(toAddress(_a))); + return m_client ? toQJS(client()->postState().balance(toAddress(_a))) : ""; } QString QEthereum::storageAt(QString _a, QString _p) const { + if (!m_client) + return ""; eth::ClientGuard l(const_cast(m_client)); return toQJS(client()->postState().storage(toAddress(_a), toU256(_p))); } double QEthereum::txCountAt(QString _a) const { + if (!m_client) + return 0.0; + eth::ClientGuard l(const_cast(m_client)); return (double)client()->postState().transactionsFrom(toAddress(_a)); } bool QEthereum::isContractAt(QString _a) const { + if (!m_client) + return false; + eth::ClientGuard l(const_cast(m_client)); return client()->postState().addressHasCode(toAddress(_a)); } u256 QEthereum::balanceAt(Address _a) const { + if (!m_client) + return 0; + eth::ClientGuard l(const_cast(m_client)); return client()->postState().balance(_a); } double QEthereum::txCountAt(Address _a) const { + if (!m_client) + return 0.0; + eth::ClientGuard l(const_cast(m_client)); return (double)client()->postState().transactionsFrom(_a); } bool QEthereum::isContractAt(Address _a) const { + if (!m_client) + return false; + eth::ClientGuard l(const_cast(m_client)); return client()->postState().addressHasCode(_a); } QString QEthereum::balanceAt(QString _a, int _block) const { - return toQJS(client()->balanceAt(toAddress(_a), _block)); + return m_client ? toQJS(client()->balanceAt(toAddress(_a), _block)) : ""; } QString QEthereum::stateAt(QString _a, QString _p, int _block) const { - return toQJS(client()->stateAt(toAddress(_a), toU256(_p), _block)); + return m_client ? toQJS(client()->stateAt(toAddress(_a), toU256(_p), _block)) : ""; } QString QEthereum::codeAt(QString _a, int _block) const { - return ::fromBinary(client()->codeAt(toAddress(_a), _block)); + return m_client ? ::fromBinary(client()->codeAt(toAddress(_a), _block)) : ""; } double QEthereum::countAt(QString _a, int _block) const { - return (double)(uint64_t)client()->countAt(toAddress(_a), _block); + return m_client ? (double)(uint64_t)client()->countAt(toAddress(_a), _block) : 0; } static eth::TransactionFilter toTransactionFilter(QString _json) @@ -309,29 +333,34 @@ static QString toJson(eth::PastMessages const& _pms) QString QEthereum::getTransactions(QString _json) const { - return toJson(m_client->transactions(toTransactionFilter(_json))); + return m_client ? toJson(m_client->transactions(toTransactionFilter(_json))) : ""; } bool QEthereum::isMining() const { - return client()->isMining(); + return m_client ? client()->isMining() : false; } bool QEthereum::isListening() const { - return client()->haveNetwork(); + return m_client ? client()->haveNetwork() : false; } void QEthereum::setMining(bool _l) { - if (_l) - client()->startMining(); - else - client()->stopMining(); + if (m_client) + { + if (_l) + client()->startMining(); + else + client()->stopMining(); + } } void QEthereum::setListening(bool _l) { + if (!m_client) + return; if (_l) client()->startNetwork(); else @@ -340,11 +369,13 @@ void QEthereum::setListening(bool _l) unsigned QEthereum::peerCount() const { - return (unsigned)client()->peerCount(); + return m_client ? (unsigned)client()->peerCount() : 0; } QString QEthereum::doCreate(QString _secret, QString _amount, QString _init, QString _gas, QString _gasPrice) { + if (!m_client) + return ""; auto ret = toQJS(client()->transact(toSecret(_secret), toU256(_amount), toBytes(_init), toU256(_gas), toU256(_gasPrice))); client()->flushTransactions(); return ret; @@ -352,12 +383,16 @@ QString QEthereum::doCreate(QString _secret, QString _amount, QString _init, QSt void QEthereum::doTransact(QString _secret, QString _amount, QString _dest, QString _data, QString _gas, QString _gasPrice) { + if (!m_client) + return; client()->transact(toSecret(_secret), toU256(_amount), toAddress(_dest), toBytes(_data), toU256(_gas), toU256(_gasPrice)); client()->flushTransactions(); } unsigned QEthereum::newWatch(QString _json) { + if (!m_client) + return (unsigned)-1; unsigned ret; if (_json == "chainChanged") ret = m_client->installWatch(eth::NewBlockFilter); @@ -371,11 +406,15 @@ unsigned QEthereum::newWatch(QString _json) QString QEthereum::watchTransactions(unsigned _w) { + if (!m_client) + return ""; return toJson(m_client->transactions(_w)); } void QEthereum::killWatch(unsigned _w) { + if (!m_client) + return; m_client->uninstallWatch(_w); std::remove(m_watches.begin(), m_watches.end(), _w); } diff --git a/libqethereum/QEthereum.h b/libqethereum/QEthereum.h index bd5832304..eba10f5ed 100644 --- a/libqethereum/QEthereum.h +++ b/libqethereum/QEthereum.h @@ -92,6 +92,9 @@ public: eth::Client* client() const; + /// Call when the client() is going to be deleted to make this object useless but safe. + void clientDieing(); + void setup(QWebFrame* _e); void teardown(QWebFrame* _e); @@ -186,8 +189,8 @@ private: frame->disconnect(); \ frame->addToJavaScriptWindowObject("env", env, QWebFrame::QtOwnership); \ frame->addToJavaScriptWindowObject("eth", eth, QWebFrame::ScriptOwnership); \ - frame->evaluateJavaScript("eth.makeWatch = function(a) { var ww = eth.newWatch(a); return { w: ww, uninstall: function() { eth.killWatch(w) }, changed: function(f) { eth.watchChanged.connect(function(nw) { if (nw == this.w) f() }) }, transactions: function() { return JSON.parse(eth.watchTransactions(w)) } }; }"); \ - frame->evaluateJavaScript("eth.watch = function(a) { return makeWatch(JSON.stringify(a)); }"); \ + frame->evaluateJavaScript("eth.makeWatch = function(a) { var ww = eth.newWatch(a); var ret = { w: ww }; ret.uninstall = function() { eth.killWatch(w); }; ret.changed = function(f) { eth.watchChanged.connect(function(nw) { env.note('check:' + nw + ' vs ' + ww); if (nw == ww) f() }); }; ret.transactions = function() { return JSON.parse(eth.watchTransactions(this.w)) }; return ret; }"); \ + frame->evaluateJavaScript("eth.watch = function(a) { return eth.makeWatch(JSON.stringify(a)) }"); \ frame->evaluateJavaScript("eth.watchChain = function() { return eth.makeWatch('chainChanged') }"); \ frame->evaluateJavaScript("eth.watchPending = function() { return eth.makeWatch('pendingChanged') }"); \ frame->evaluateJavaScript("eth.create = function(s, v, c, g, p, f) { var v = eth.doCreate(s, v, c, g, p); if (f) f(v) }"); \ From b13979d1af12aa381e432820a5474636a845f847 Mon Sep 17 00:00:00 2001 From: caktux Date: Thu, 24 Jul 2014 12:22:15 -0400 Subject: [PATCH 008/223] neth: fix pending --- neth/main.cpp | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/neth/main.cpp b/neth/main.cpp index 48c97568f..652f0b498 100644 --- a/neth/main.cpp +++ b/neth/main.cpp @@ -890,23 +890,21 @@ int main(int argc, char** argv) // Pending y = 1; - auto aps = c.pending(); - for (auto const& t: aps) + for (Transaction const& t: c.pending()) { - if (t.receiveAddress) - auto s = boost::format("%1% %2%> %3%: %4% [%5%]") % + auto s = t.receiveAddress ? + boost::format("%1% %2%> %3%: %4% [%5%]") % toString(t.safeSender()) % (st.addressHasCode(t.receiveAddress) ? '*' : '-') % toString(t.receiveAddress) % toString(formatBalance(t.value)) % - toString((unsigned)t.nonce); - else - auto s = boost::format("%1% +> %2%: %3% [%4%]") % + toString((unsigned)t.nonce) : + boost::format("%1% +> %2%: %3% [%4%]") % toString(t.safeSender()) % toString(right160(sha3(rlpList(t.safeSender(), t.nonce)))) % toString(formatBalance(t.value)) % toString((unsigned)t.nonce); - mvwaddnstr(pendingwin, y++, x, s.c_str(), qwidth); + mvwaddnstr(pendingwin, y++, x, s.str().c_str(), qwidth); if (y > height * 1 / 5 - 4) break; } From cbcff4953f486fe89d2f00ed25a7cffc38796d47 Mon Sep 17 00:00:00 2001 From: caktux Date: Thu, 24 Jul 2014 12:57:20 -0400 Subject: [PATCH 009/223] neth: format peers --- neth/main.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/neth/main.cpp b/neth/main.cpp index 652f0b498..2f7844b12 100644 --- a/neth/main.cpp +++ b/neth/main.cpp @@ -947,14 +947,14 @@ int main(int argc, char** argv) // Peers y = 1; - string psc; - string pss; - auto cp = c.peers(); - psc = toString(cp.size()) + " peer(s)"; - for (PeerInfo const& i: cp) + for (PeerInfo const& i: c.peers()) { - pss = toString(chrono::duration_cast(i.lastPing).count()) + " ms - " + i.host + ":" + toString(i.port) + " - " + i.clientVersion; - mvwaddnstr(peerswin, y++, x, pss.c_str(), qwidth); + auto s = boost::format("%1% ms - %2%:%3% - %4%") % + toString(chrono::duration_cast(i.lastPing).count()) % + i.host % + toString(i.port) % + i.clientVersion; + mvwaddnstr(peerswin, y++, x, s.str().c_str(), qwidth); if (y > height * 2 / 5 - 4) break; } From 030745a121368cd6867f84e79dd9748e40ed5777 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 24 Jul 2014 19:48:26 +0200 Subject: [PATCH 010/223] More fixes & debuging. --- alethzero/MainWin.cpp | 4 +- libethereum/BlockChain.cpp | 11 +++-- libethereum/BlockChain.h | 2 +- libethereum/Client.cpp | 92 ++++++++++++++++++++----------------- libethereum/PeerSession.cpp | 6 +-- libqethereum/QEthereum.cpp | 11 +---- libqethereum/QEthereum.h | 4 +- 7 files changed, 66 insertions(+), 64 deletions(-) diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index 3a3982d30..f55fc4525 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -179,7 +179,6 @@ Main::Main(QWidget *parent) : QWebFrame* f = ui->webView->page()->mainFrame(); f->disconnect(SIGNAL(javaScriptWindowObjectCleared())); - m_ethereum->setup(f); auto qeth = m_ethereum; connect(f, &QWebFrame::javaScriptWindowObjectCleared, QETH_INSTALL_JS_NAMESPACE(f, qeth, this)); }); @@ -1333,7 +1332,10 @@ void Main::on_killBlockchain_triggered() ui->net->setChecked(false); m_client.reset(); m_client.reset(new Client("AlethZero", Address(), string(), true)); + m_ethereum->setClient(m_client.get()); readSettings(); + installWatches(); + refreshAll(); } bool Main::isCreation() const diff --git a/libethereum/BlockChain.cpp b/libethereum/BlockChain.cpp index f2dd89b4c..5344f5147 100644 --- a/libethereum/BlockChain.cpp +++ b/libethereum/BlockChain.cpp @@ -165,19 +165,18 @@ bool contains(T const& _t, V const& _v) return false; } -bool BlockChain::attemptImport(bytes const& _block, OverlayDB const& _stateDB) +h256s BlockChain::attemptImport(bytes const& _block, OverlayDB const& _stateDB) { #if ETH_CATCH try #endif { - import(_block, _stateDB); - return true; + return import(_block, _stateDB); } #if ETH_CATCH catch (...) { - return false; + return h256s(); } #endif } @@ -302,7 +301,9 @@ h256s BlockChain::import(bytes const& _block, OverlayDB const& _db) ret = treeRoute(m_lastBlockHash, newHash); m_lastBlockHash = newHash; m_extrasDB->Put(m_writeOptions, ldb::Slice("best"), ldb::Slice((char const*)&newHash, 32)); - clog(BlockChainNote) << " Imported and best. Has" << (details(bi.parentHash).children.size() - 1) << "siblings."; + clog(BlockChainNote) << " Imported and best. Has" << (details(bi.parentHash).children.size() - 1) << "siblings. Route:"; + for (auto r: ret) + clog(BlockChainNote) << r; } else { diff --git a/libethereum/BlockChain.h b/libethereum/BlockChain.h index 959ec1bdf..749dd87f9 100644 --- a/libethereum/BlockChain.h +++ b/libethereum/BlockChain.h @@ -111,7 +111,7 @@ public: void process(); /// Attempt to import the given block. - bool attemptImport(bytes const& _block, OverlayDB const& _stateDB); + h256s attemptImport(bytes const& _block, OverlayDB const& _stateDB); /// Import block into disk-backed DB /// @returns the block hashes of any blocks that came into/went out of the canonical block chain. diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index e059c9c7c..df5df27b5 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -131,6 +131,7 @@ unsigned Client::installWatch(h256 _h) { auto ret = m_watches.size() ? m_watches.rbegin()->first + 1 : 0; m_watches[ret] = Watch(_h); + cdebug << "Install watch" << ret << _h; return ret; } @@ -148,6 +149,8 @@ unsigned Client::installWatch(TransactionFilter const& _f) void Client::uninstallWatch(unsigned _i) { + cdebug << "Uninstall watch" << _i; + lock_guard l(m_filterLock); auto it = m_watches.find(_i); @@ -185,7 +188,10 @@ void Client::noteChanged(h256Set const& _filters) lock_guard l(m_filterLock); for (auto& i: m_watches) if (_filters.count(i.second.id)) + { + cdebug << "Watch activated" << i.first << i.second.id; i.second.changes++; + } } void Client::startNetwork(unsigned short _listenPort, std::string const& _seedHost, unsigned short _port, NodeMode _mode, unsigned _peers, string const& _publicIP, bool _upnp) @@ -313,43 +319,10 @@ void Client::work(bool _justQueue) for (auto i: newBlocks) appendFromNewBlock(i, changeds); changeds.insert(NewBlockFilter); - changeds.insert(NewPendingFilter); // if there's a new block, then we've probably reset the pending transactions. } } - // 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. - // Resynchronise state with block chain & trans - { - ClientGuard l(this); - if (m_preMine.sync(m_bc) || m_postMine.address() != m_preMine.address()) - { - if (m_doMine) - cnote << "New block on chain: Restarting mining operation."; - m_restartMining = true; // need to re-commit to mine. - m_postMine = m_preMine; - } - - // returns h256s as blooms, once for each transaction. - h256s newPendingBlooms = m_postMine.sync(m_tq); - if (newPendingBlooms.size()) - { - for (auto i: newPendingBlooms) - appendFromNewPending(i, changeds); - changeds.insert(NewPendingFilter); - - if (m_doMine) - cnote << "Additional transaction ready: Restarting mining operation."; - m_restartMining = true; - } - } - - noteChanged(changeds); - + // Do some mining. if (!_justQueue) { if (m_doMine) @@ -390,22 +363,59 @@ void Client::work(bool _justQueue) m_mineProgress.requirement = mineInfo.requirement; m_mineProgress.ms += 100; m_mineProgress.hashes += mineInfo.hashes; - { - ClientGuard l(this); - m_mineHistory.push_back(mineInfo); - } - + ClientGuard l(this); + m_mineHistory.push_back(mineInfo); if (mineInfo.completed) { // Import block. - ClientGuard l(this); m_postMine.completeMine(); - m_bc.attemptImport(m_postMine.blockData(), m_stateDB); + h256s hs = m_bc.attemptImport(m_postMine.blockData(), m_stateDB); + if (hs.size()) + { + for (auto h: hs) + appendFromNewBlock(h, changeds); + changeds.insert(NewBlockFilter); + //changeds.insert(NewPendingFilter); // if we mined the new block, then we've probably reset the pending transactions. + } } } else this_thread::sleep_for(chrono::milliseconds(100)); } + + // 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. + // Resynchronise state with block chain & trans + { + ClientGuard l(this); + if (m_preMine.sync(m_bc) || m_postMine.address() != m_preMine.address()) + { + if (m_doMine) + cnote << "New block on chain: Restarting mining operation."; + m_restartMining = true; // need to re-commit to mine. + m_postMine = m_preMine; + changeds.insert(NewPendingFilter); + } + + // returns h256s as blooms, once for each transaction. + h256s newPendingBlooms = m_postMine.sync(m_tq); + if (newPendingBlooms.size()) + { + for (auto i: newPendingBlooms) + appendFromNewPending(i, changeds); + changeds.insert(NewPendingFilter); + + if (m_doMine) + cnote << "Additional transaction ready: Restarting mining operation."; + m_restartMining = true; + } + } + + noteChanged(changeds); } void Client::lock() const diff --git a/libethereum/PeerSession.cpp b/libethereum/PeerSession.cpp index 2af61e3b6..4c403a897 100644 --- a/libethereum/PeerSession.cpp +++ b/libethereum/PeerSession.cpp @@ -31,9 +31,9 @@ using namespace eth; #define clogS(X) eth::LogOutputStream(false) << "| " << std::setw(2) << m_socket.native_handle() << "] " -static const eth::uint c_maxHashes = 4096; ///< Maximum number of hashes GetChain will ever send. -static const eth::uint c_maxBlocks = 2048; ///< Maximum number of blocks Blocks will ever send. -static const eth::uint c_maxBlocksAsk = 512; ///< Maximum number of blocks we ask to receive in Blocks (when using GetChain). +static const eth::uint c_maxHashes = 128; ///< Maximum number of hashes GetChain will ever send. +static const eth::uint c_maxBlocks = 32; ///< Maximum number of blocks Blocks will ever send. +static const eth::uint c_maxBlocksAsk = 32; ///< Maximum number of blocks we ask to receive in Blocks (when using GetChain). PeerSession::PeerSession(PeerServer* _s, bi::tcp::socket _socket, uint _rNId, bi::address _peerAddress, unsigned short _peerPort): m_server(_s), diff --git a/libqethereum/QEthereum.cpp b/libqethereum/QEthereum.cpp index 38d7ca9da..5dd367b7a 100644 --- a/libqethereum/QEthereum.cpp +++ b/libqethereum/QEthereum.cpp @@ -108,7 +108,7 @@ void QEthereum::clearWatches() if (m_client) for (auto i: m_watches) m_client->uninstallWatch(i); - m_watches.clear(); + m_watches.clear(); } QString QEthereum::secretToAddress(QString _s) const @@ -116,15 +116,6 @@ QString QEthereum::secretToAddress(QString _s) const return toQJS(KeyPair(toSecret(_s)).address()); } -void QEthereum::setup(QWebFrame*) -{ - // Alex: JS codes moved to mainwin until qtwebkit bugs are resolved (#245) -} - -void QEthereum::teardown(QWebFrame*) -{ -} - Client* QEthereum::client() const { return m_client; diff --git a/libqethereum/QEthereum.h b/libqethereum/QEthereum.h index eba10f5ed..2707ba9ab 100644 --- a/libqethereum/QEthereum.h +++ b/libqethereum/QEthereum.h @@ -91,13 +91,11 @@ public: virtual ~QEthereum(); eth::Client* client() const; + void setClient(eth::Client* _c) { m_client = _c; } /// Call when the client() is going to be deleted to make this object useless but safe. void clientDieing(); - void setup(QWebFrame* _e); - void teardown(QWebFrame* _e); - void setAccounts(QList _l) { m_accounts = _l; keysChanged(); } Q_INVOKABLE QString ethTest() const { return "Hello world!"; } From 80c4925ff0ab4b6acc3a5d4223bd47fd9e636a21 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 24 Jul 2014 23:38:56 +0200 Subject: [PATCH 011/223] Additional alterations to network & logging for ease of debugging. --- alethzero/Main.ui | 14 ++++---- alethzero/MainWin.cpp | 43 ++++++++++++++++++------- alethzero/MainWin.h | 3 ++ libethereum/Client.cpp | 26 +++++++++++---- libethereum/Client.h | 3 ++ libethereum/PeerNetwork.h | 13 ++++---- libethereum/PeerServer.cpp | 22 +++++++------ libethereum/PeerServer.h | 2 +- libethereum/PeerSession.cpp | 64 ++++++++++++++++++++++++------------- libethereum/PeerSession.h | 2 ++ libqethereum/QEthereum.h | 2 +- 11 files changed, 130 insertions(+), 64 deletions(-) diff --git a/alethzero/Main.ui b/alethzero/Main.ui index 4ccdab651..b5bb04df6 100644 --- a/alethzero/Main.ui +++ b/alethzero/Main.ui @@ -377,19 +377,21 @@ 0 - + - Monospace - 12 + Ubuntu Mono - - Qt::NoFocus - QFrame::NoFrame + + QFrame::Plain + + + 0 + diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index f55fc4525..1e0eb3c74 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -124,7 +124,15 @@ Main::Main(QWidget *parent) : { setWindowFlags(Qt::Window); ui->setupUi(this); - g_logPost = [=](std::string const& s, char const* c) { simpleDebugOut(s, c); ui->log->addItem(QString::fromStdString(s)); }; + g_logPost = [=](std::string const& s, char const* c) + { + simpleDebugOut(s, c); + m_logLock.lock(); + m_logHistory.append(QString::fromStdString(s) + "\n"); + m_logChanged = true; + m_logLock.unlock(); +// ui->log->addItem(QString::fromStdString(s)); + }; #if 0&Ð_DEBUG m_servers.append("192.168.0.10:30301"); @@ -279,7 +287,7 @@ void Main::installBalancesWatch() void Main::onNameRegChange() { - cdebug << "NameReg changed!"; + cwatch << "NameReg changed!"; // update any namereg-dependent stuff - for now force a full update. refreshAll(); @@ -287,7 +295,7 @@ void Main::onNameRegChange() void Main::onCurrenciesChange() { - cdebug << "Currencies changed!"; + cwatch << "Currencies changed!"; installBalancesWatch(); // TODO: update any currency-dependent stuff? @@ -295,14 +303,14 @@ void Main::onCurrenciesChange() void Main::onBalancesChange() { - cdebug << "Our balances changed!"; + cwatch << "Our balances changed!"; refreshBalances(); } void Main::onNewBlock() { - cdebug << "Blockchain changed!"; + cwatch << "Blockchain changed!"; // update blockchain dependent views. refreshBlockCount(); @@ -312,7 +320,7 @@ void Main::onNewBlock() void Main::onNewPending() { - cdebug << "Pending transactions changed!"; + cwatch << "Pending transactions changed!"; // update any pending-transaction dependent views. refreshPending(); @@ -608,6 +616,7 @@ void Main::refreshMining() void Main::refreshBalances() { + cwatch << "refreshBalances()"; // update all the balance-dependent stuff. ui->ourAccounts->clear(); u256 totalBalance = 0; @@ -660,7 +669,7 @@ void Main::refreshAll() void Main::refreshPending() { - cdebug << "refreshPending()"; + cwatch << "refreshPending()"; ui->transactionQueue->clear(); for (Transaction const& t: m_client->pending()) { @@ -682,7 +691,7 @@ void Main::refreshPending() void Main::refreshAccounts() { - cdebug << "refreshAccounts()"; + cwatch << "refreshAccounts()"; ui->accounts->clear(); ui->contracts->clear(); for (auto n = 0; n < 2; ++n) @@ -703,7 +712,7 @@ void Main::refreshAccounts() void Main::refreshDestination() { - cdebug << "refreshDestination()"; + cwatch << "refreshDestination()"; QString s; for (auto i: m_client->addresses()) if ((s = pretty(i)).size()) @@ -717,7 +726,7 @@ void Main::refreshDestination() void Main::refreshBlockCount() { - cdebug << "refreshBlockCount()"; + cwatch << "refreshBlockCount()"; auto d = m_client->blockChain().details(); auto diff = BlockInfo(m_client->blockChain().block()).difficulty; ui->blockCount->setText(QString("#%1 @%3 T%2 N%4 D%5").arg(d.number).arg(toLog2(d.totalDifficulty)).arg(toLog2(diff)).arg(eth::c_protocolVersion).arg(eth::c_databaseVersion)); @@ -750,7 +759,7 @@ static bool transactionMatch(string const& _f, Transaction const& _t) void Main::refreshBlockChain() { - cdebug << "refreshBlockChain()"; + cwatch << "refreshBlockChain()"; eth::ClientGuard g(m_client.get()); auto const& st = state(); @@ -834,6 +843,15 @@ void Main::timerEvent(QTimerEvent*) if (interval / 100 % 2 == 0) refreshMining(); + if (m_logChanged) + { + m_logLock.lock(); + m_logChanged = false; + ui->log->appendPlainText(m_logHistory); + m_logHistory.clear(); + m_logLock.unlock(); + } + // refresh peer list every 1000ms, reset counter if (interval == 1000) { @@ -1198,7 +1216,8 @@ void Main::on_ourAccounts_doubleClicked() void Main::on_log_doubleClicked() { - qApp->clipboard()->setText(ui->log->currentItem()->text()); + ui->log->setPlainText(""); + m_logHistory.clear(); } void Main::on_accounts_doubleClicked() diff --git a/alethzero/MainWin.h b/alethzero/MainWin.h index 7117618ea..35f8cb137 100644 --- a/alethzero/MainWin.h +++ b/alethzero/MainWin.h @@ -229,6 +229,9 @@ private: QNetworkAccessManager m_webCtrl; QList> m_consoleHistory; + QMutex m_logLock; + QString m_logHistory; + bool m_logChanged = true; QEthereum* m_ethereum = nullptr; }; diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index df5df27b5..d2905234d 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -131,7 +131,7 @@ unsigned Client::installWatch(h256 _h) { auto ret = m_watches.size() ? m_watches.rbegin()->first + 1 : 0; m_watches[ret] = Watch(_h); - cdebug << "Install watch" << ret << _h; + cwatch << "+++" << ret << _h; return ret; } @@ -149,7 +149,7 @@ unsigned Client::installWatch(TransactionFilter const& _f) void Client::uninstallWatch(unsigned _i) { - cdebug << "Uninstall watch" << _i; + cwatch << "XXX" << _i; lock_guard l(m_filterLock); @@ -189,7 +189,7 @@ void Client::noteChanged(h256Set const& _filters) for (auto& i: m_watches) if (_filters.count(i.second.id)) { - cdebug << "Watch activated" << i.first << i.second.id; + cwatch << "!!!" << i.first << i.second.id; i.second.changes++; } } @@ -262,7 +262,7 @@ void Client::transact(Secret _secret, u256 _value, Address _dest, bytes const& _ ClientGuard l(this); Transaction t; - cdebug << "Nonce at " << toAddress(_secret) << " pre:" << m_preMine.transactionsFrom(toAddress(_secret)) << " post:" << m_postMine.transactionsFrom(toAddress(_secret)); +// cdebug << "Nonce at " << toAddress(_secret) << " pre:" << m_preMine.transactionsFrom(toAddress(_secret)) << " post:" << m_postMine.transactionsFrom(toAddress(_secret)); t.nonce = m_postMine.transactionsFrom(toAddress(_secret)); t.value = _value; t.gasPrice = _gasPrice; @@ -302,6 +302,7 @@ void Client::inject(bytesConstRef _rlp) void Client::work(bool _justQueue) { + cdebug << ">>> WORK"; h256Set changeds; // Process network events. @@ -309,11 +310,14 @@ void Client::work(bool _justQueue) // Will broadcast any of our (new) transactions and blocks, and collect & add any of their (new) transactions and blocks. if (m_net && !_justQueue) { + cdebug << "--- WORK: LOCK"; ClientGuard l(this); + cdebug << "--- WORK: NETWORK"; m_net->process(); // must be in guard for now since it uses the blockchain. - // TODO: return h256Set as block hashes, once for each block that has come in/gone out. - h256Set newBlocks = m_net->sync(m_bc, m_tq, m_stateDB); + // returns h256Set as block hashes, once for each block that has come in/gone out. + cdebug << "--- WORK: TQ <== NET ==> CHAIN"; + h256Set newBlocks = m_net->sync(m_bc, m_tq, m_stateDB, 100); if (newBlocks.size()) { for (auto i: newBlocks) @@ -353,6 +357,7 @@ void Client::work(bool _justQueue) if (m_doMine) { + cdebug << "--- WORK: MINE"; m_restartMining = false; // Mine for a while. @@ -368,7 +373,9 @@ void Client::work(bool _justQueue) if (mineInfo.completed) { // Import block. + cdebug << "--- WORK: COMPLETE MINE%"; m_postMine.completeMine(); + cdebug << "--- WORK: CHAIN <== postSTATE"; h256s hs = m_bc.attemptImport(m_postMine.blockData(), m_stateDB); if (hs.size()) { @@ -380,7 +387,10 @@ void Client::work(bool _justQueue) } } else + { + cdebug << "--- WORK: SLEEP"; this_thread::sleep_for(chrono::milliseconds(100)); + } } // Synchronise state to block chain. @@ -392,6 +402,7 @@ void Client::work(bool _justQueue) // Resynchronise state with block chain & trans { ClientGuard l(this); + cdebug << "--- WORK: preSTATE <== CHAIN"; if (m_preMine.sync(m_bc) || m_postMine.address() != m_preMine.address()) { if (m_doMine) @@ -402,6 +413,7 @@ void Client::work(bool _justQueue) } // returns h256s as blooms, once for each transaction. + cdebug << "--- WORK: postSTATE <== TQ"; h256s newPendingBlooms = m_postMine.sync(m_tq); if (newPendingBlooms.size()) { @@ -415,7 +427,9 @@ void Client::work(bool _justQueue) } } + cdebug << "--- WORK: noteChanged" << changeds.size() << "items"; noteChanged(changeds); + cdebug << "<<< WORK"; } void Client::lock() const diff --git a/libethereum/Client.h b/libethereum/Client.h index b1089741c..d589d30ce 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -157,6 +157,9 @@ struct Watch unsigned changes = 1; }; +struct WatchChannel: public LogChannel { static const char* name() { return "(o)"; } static const int verbosity = 6; }; +#define cwatch eth::LogOutputStream() + /** * @brief Main API hub for interfacing with Ethereum. */ diff --git a/libethereum/PeerNetwork.h b/libethereum/PeerNetwork.h index 7f6b00b5d..22ebd98b7 100644 --- a/libethereum/PeerNetwork.h +++ b/libethereum/PeerNetwork.h @@ -46,12 +46,13 @@ class PeerSession; struct NetWarn: public LogChannel { static const char* name() { return "!N!"; } static const int verbosity = 0; }; struct NetNote: public LogChannel { static const char* name() { return "*N*"; } static const int verbosity = 1; }; struct NetMessageSummary: public LogChannel { static const char* name() { return "-N-"; } static const int verbosity = 2; }; -struct NetMessageDetail: public LogChannel { static const char* name() { return "=N="; } static const int verbosity = 3; }; -struct NetTriviaSummary: public LogChannel { static const char* name() { return "-N-"; } static const int verbosity = 4; }; -struct NetTriviaDetail: public LogChannel { static const char* name() { return "=N="; } static const int verbosity = 5; }; -struct NetAllDetail: public LogChannel { static const char* name() { return "=N="; } static const int verbosity = 6; }; -struct NetRight: public LogChannel { static const char* name() { return ">N>"; } static const int verbosity = 8; }; -struct NetLeft: public LogChannel { static const char* name() { return "N>"; } static const int verbosity = 18; }; +struct NetLeft: public LogChannel { static const char* name() { return "async_connect(_ep, [=](boost::system::error_code const& ec) { if (ec) { - clog(NetNote) << "Connection refused to " << _ep << " (" << ec.message() << ")"; + clog(NetConnect) << "Connection refused to " << _ep << " (" << ec.message() << ")"; for (auto i = m_incomingPeers.begin(); i != m_incomingPeers.end(); ++i) if (i->second.first == _ep && i->second.second < 3) { @@ -323,13 +323,13 @@ void PeerServer::connect(bi::tcp::endpoint const& _ep) goto OK; } // for-else - clog(NetNote) << "Giving up."; + clog(NetConnect) << "Giving up."; OK:; } else { auto p = make_shared(this, std::move(*s), m_networkId, _ep.address(), _ep.port()); - clog(NetNote) << "Connected to " << _ep; + clog(NetConnect) << "Connected to " << _ep; p->start(); } delete s; @@ -363,7 +363,7 @@ bool PeerServer::noteBlock(h256 _hash, bytesConstRef _data) return false; } -h256Set PeerServer::sync(BlockChain& _bc, TransactionQueue& _tq, OverlayDB& _o) +h256Set PeerServer::sync(BlockChain& _bc, TransactionQueue& _tq, OverlayDB& _o, unsigned _max) { h256Set ret; @@ -427,12 +427,13 @@ h256Set PeerServer::sync(BlockChain& _bc, TransactionQueue& _tq, OverlayDB& _o) } m_latestBlockSent = h; - for (int accepted = 1, n = 0; accepted; ++n) + unsigned totalAccepted = 0; + for (int accepted = 1, n = 0; accepted && totalAccepted < _max; ++n) { accepted = 0; lock_guard l(m_incomingLock); if (m_incomingBlocks.size()) - for (auto it = prev(m_incomingBlocks.end());; --it) + for (auto it = prev(m_incomingBlocks.end()); totalAccepted < _max; --it) { try { @@ -440,6 +441,7 @@ h256Set PeerServer::sync(BlockChain& _bc, TransactionQueue& _tq, OverlayDB& _o) ret.insert(h); it = m_incomingBlocks.erase(it); ++accepted; + ++totalAccepted; netChange = true; } catch (UnknownParent) diff --git a/libethereum/PeerServer.h b/libethereum/PeerServer.h index 0eae35a1f..465a4dc41 100644 --- a/libethereum/PeerServer.h +++ b/libethereum/PeerServer.h @@ -58,7 +58,7 @@ public: void connect(bi::tcp::endpoint const& _ep); /// Sync with the BlockChain. It might contain one of our mined blocks, we might have new candidates from the network. - h256Set sync(BlockChain& _bc, TransactionQueue&, OverlayDB& _o); + h256Set sync(BlockChain& _bc, TransactionQueue&, OverlayDB& _o, unsigned _max); /// Conduct I/O, polling, syncing, whatever. /// Ideally all time-consuming I/O is done in a background thread or otherwise asynchronously, but you get this call every 100ms or so anyway. diff --git a/libethereum/PeerSession.cpp b/libethereum/PeerSession.cpp index 4c403a897..239163e86 100644 --- a/libethereum/PeerSession.cpp +++ b/libethereum/PeerSession.cpp @@ -31,9 +31,9 @@ using namespace eth; #define clogS(X) eth::LogOutputStream(false) << "| " << std::setw(2) << m_socket.native_handle() << "] " -static const eth::uint c_maxHashes = 128; ///< Maximum number of hashes GetChain will ever send. -static const eth::uint c_maxBlocks = 32; ///< Maximum number of blocks Blocks will ever send. -static const eth::uint c_maxBlocksAsk = 32; ///< Maximum number of blocks we ask to receive in Blocks (when using GetChain). +static const eth::uint c_maxHashes = 4096; ///< Maximum number of hashes GetChain will ever send. +static const eth::uint c_maxBlocks = 2048; ///< Maximum number of blocks Blocks will ever send. +static const eth::uint c_maxBlocksAsk = 512; ///< Maximum number of blocks we ask to receive in Blocks (when using GetChain). PeerSession::PeerSession(PeerServer* _s, bi::tcp::socket _socket, uint _rNId, bi::address _peerAddress, unsigned short _peerPort): m_server(_s), @@ -111,24 +111,9 @@ bool PeerSession::interpret(RLP const& _r) m_server->m_peers[m_id] = shared_from_this(); - // Grab their block chain off them. + // Grab trsansactions off them. { - uint n = m_server->m_chain->number(m_server->m_latestBlockSent); - clogS(NetAllDetail) << "Want chain. Latest:" << m_server->m_latestBlockSent << ", number:" << n; - uint count = std::min(c_maxHashes, n + 1); RLPStream s; - prep(s).appendList(2 + count); - s << GetChainPacket; - auto h = m_server->m_latestBlockSent; - for (uint i = 0; i < count; ++i, h = m_server->m_chain->details(h).parent) - { - clogS(NetAllDetail) << " " << i << ":" << h; - s << h; - } - - s << c_maxBlocksAsk; - sealAndSend(s); - s.clear(); prep(s).appendList(1); s << GetTransactionsPacket; sealAndSend(s); @@ -240,16 +225,27 @@ bool PeerSession::interpret(RLP const& _r) } } m_rating += used; - if (g_logVerbosity >= 3) + unsigned knownParents = 0; + unsigned unknownParents = 0; + if (g_logVerbosity >= 2) + { for (unsigned i = 1; i < _r.itemCount(); ++i) { auto h = sha3(_r[i].data()); BlockInfo bi(_r[i].data()); if (!m_server->m_chain->details(bi.parentHash) && !m_knownBlocks.count(bi.parentHash)) + { + unknownParents++; clogS(NetMessageDetail) << "Unknown parent " << bi.parentHash << " of block " << h; + } else + { + knownParents++; clogS(NetMessageDetail) << "Known parent " << bi.parentHash << " of block " << h; + } } + } + clogS(NetMessageSummary) << dec << knownParents << " known parents, " << unknownParents << "unknown, " << used << "used."; if (used) // we received some - check if there's any more { RLPStream s; @@ -259,6 +255,8 @@ bool PeerSession::interpret(RLP const& _r) s << c_maxBlocksAsk; sealAndSend(s); } + else + clogS(NetMessageSummary) << "Peer sent all blocks in chain."; break; } case GetChainPacket: @@ -316,6 +314,9 @@ bool PeerSession::interpret(RLP const& _r) clogS(NetAllDetail) << " " << dec << i << " " << h; s.appendRaw(m_server->m_chain->block(h)); } + + if (!count) + clogS(NetMessageSummary) << "Sent peer all we have."; clogS(NetAllDetail) << "Parent: " << h; } else if (parent != parents.back()) @@ -487,7 +488,7 @@ void PeerSession::dropped() if (m_socket.is_open()) try { - clogS(NetNote) << "Closing " << m_socket.remote_endpoint(); + clogS(NetConnect) << "Closing " << m_socket.remote_endpoint(); m_socket.close(); } catch (...) {} @@ -503,7 +504,7 @@ void PeerSession::dropped() void PeerSession::disconnect(int _reason) { - clogS(NetNote) << "Disconnecting (reason:" << reasonOf((DisconnectReason)_reason) << ")"; + clogS(NetConnect) << "Disconnecting (reason:" << reasonOf((DisconnectReason)_reason) << ")"; if (m_socket.is_open()) { if (m_disconnect == chrono::steady_clock::time_point::max()) @@ -531,6 +532,25 @@ void PeerSession::start() doRead(); } +void PeerSession::startInitialSync() +{ + uint n = m_server->m_chain->number(m_server->m_latestBlockSent); + clogS(NetAllDetail) << "Want chain. Latest:" << m_server->m_latestBlockSent << ", number:" << n; + uint count = std::min(c_maxHashes, n + 1); + RLPStream s; + prep(s).appendList(2 + count); + s << GetChainPacket; + auto h = m_server->m_latestBlockSent; + for (uint i = 0; i < count; ++i, h = m_server->m_chain->details(h).parent) + { + clogS(NetAllDetail) << " " << i << ":" << h; + s << h; + } + + s << c_maxBlocksAsk; + sealAndSend(s); +} + void PeerSession::doRead() { // ignore packets received while waiting to disconnect diff --git a/libethereum/PeerSession.h b/libethereum/PeerSession.h index 1a28c66ec..6c7e56d7c 100644 --- a/libethereum/PeerSession.h +++ b/libethereum/PeerSession.h @@ -51,6 +51,8 @@ public: bi::tcp::endpoint endpoint() const; ///< for other peers to connect to. private: + void startInitialSync(); + void dropped(); void doRead(); void doWrite(std::size_t length); diff --git a/libqethereum/QEthereum.h b/libqethereum/QEthereum.h index 2707ba9ab..70a41c32d 100644 --- a/libqethereum/QEthereum.h +++ b/libqethereum/QEthereum.h @@ -187,7 +187,7 @@ private: frame->disconnect(); \ frame->addToJavaScriptWindowObject("env", env, QWebFrame::QtOwnership); \ frame->addToJavaScriptWindowObject("eth", eth, QWebFrame::ScriptOwnership); \ - frame->evaluateJavaScript("eth.makeWatch = function(a) { var ww = eth.newWatch(a); var ret = { w: ww }; ret.uninstall = function() { eth.killWatch(w); }; ret.changed = function(f) { eth.watchChanged.connect(function(nw) { env.note('check:' + nw + ' vs ' + ww); if (nw == ww) f() }); }; ret.transactions = function() { return JSON.parse(eth.watchTransactions(this.w)) }; return ret; }"); \ + frame->evaluateJavaScript("eth.makeWatch = function(a) { var ww = eth.newWatch(a); var ret = { w: ww }; ret.uninstall = function() { eth.killWatch(w); }; ret.changed = function(f) { eth.watchChanged.connect(function(nw) { if (nw == ww) f() }); }; ret.transactions = function() { return JSON.parse(eth.watchTransactions(this.w)) }; return ret; }"); \ frame->evaluateJavaScript("eth.watch = function(a) { return eth.makeWatch(JSON.stringify(a)) }"); \ frame->evaluateJavaScript("eth.watchChain = function() { return eth.makeWatch('chainChanged') }"); \ frame->evaluateJavaScript("eth.watchPending = function() { return eth.makeWatch('pendingChanged') }"); \ From c91c5430a8e85c36e80d16900e7b6aa8e195b1d7 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 25 Jul 2014 14:36:57 +0200 Subject: [PATCH 012/223] Thread-safe blockchain. --- libethereum/BlockChain.cpp | 138 +++++++++++-------------------------- libethereum/BlockChain.h | 63 +++++++++++++---- 2 files changed, 91 insertions(+), 110 deletions(-) diff --git a/libethereum/BlockChain.cpp b/libethereum/BlockChain.cpp index 5344f5147..21fa9f256 100644 --- a/libethereum/BlockChain.cpp +++ b/libethereum/BlockChain.cpp @@ -40,7 +40,7 @@ namespace eth std::ostream& operator<<(std::ostream& _out, BlockChain const& _bc) { - string cmp = toBigEndianString(_bc.m_lastBlockHash); + string cmp = toBigEndianString(_bc.currentHash()); auto it = _bc.m_extrasDB->NewIterator(_bc.m_readOptions); for (it->SeekToFirst(); it->Valid(); it->Next()) if (it->key().ToString() != "best") @@ -87,6 +87,21 @@ std::map const& eth::genesisState() } BlockInfo* BlockChain::s_genesis = nullptr; +boost::shared_mutex BlockChain::x_genesis; + +ldb::Slice eth::toSlice(h256 _h, unsigned _sub) +{ +#if ALL_COMPILERS_ARE_CPP11_COMPLIANT + static thread_local h256 h = _h ^ h256(u256(_sub)); + return ldb::Slice((char const*)&h, 32); +#else + static boost::thread_specific_ptr t_h; + if (!t_h.get()) + t_h.reset(new h256); + *t_h = _h ^ h256(u256(_sub)); + return ldb::Slice((char const*)t_h.get(), 32); +#endif +} bytes BlockChain::createGenesisBlock() { @@ -144,9 +159,10 @@ BlockChain::BlockChain(std::string _path, bool _killExisting) // TODO: Implement ability to rebuild details map from DB. std::string l; m_extrasDB->Get(m_readOptions, ldb::Slice("best"), &l); + m_lastBlockHash = l.empty() ? m_genesisHash : *(h256*)l.data(); - cnote << "Opened blockchain DB. Latest: " << m_lastBlockHash; + cnote << "Opened blockchain DB. Latest: " << currentHash(); } BlockChain::~BlockChain() @@ -181,20 +197,6 @@ h256s BlockChain::attemptImport(bytes const& _block, OverlayDB const& _stateDB) #endif } -inline ldb::Slice toSlice(h256 _h, unsigned _sub = 0) -{ -#if ALL_COMPILERS_ARE_CPP11_COMPLIANT - static thread_local h256 h = _h ^ h256(u256(_sub)); - return ldb::Slice((char const*)&h, 32); -#else - static boost::thread_specific_ptr t_h; - if (!t_h.get()) - t_h.reset(new h256); - *t_h = _h ^ h256(u256(_sub)); - return ldb::Slice((char const*)t_h.get(), 32); -#endif -} - h256s BlockChain::import(bytes const& _block, OverlayDB const& _db) { // VERIFY: populates from the block and checks the block is internally coherent. @@ -267,10 +269,16 @@ h256s BlockChain::import(bytes const& _block, OverlayDB const& _db) #endif // All ok - insert into DB { - lock_guard l(m_lock); + WriteGuard l(x_details); m_details[newHash] = BlockDetails((uint)pd.number + 1, td, bi.parentHash, {}, b); m_details[bi.parentHash].children.push_back(newHash); + } + { + WriteGuard l(x_blooms); m_blooms[newHash] = bb; + } + { + WriteGuard l(x_traces); m_traces[newHash] = bt; } @@ -296,10 +304,14 @@ h256s BlockChain::import(bytes const& _block, OverlayDB const& _db) h256s ret; // This might be the new best block... - if (td > details(m_lastBlockHash).totalDifficulty) + h256 last = currentHash(); + if (td > details(last).totalDifficulty) { - ret = treeRoute(m_lastBlockHash, newHash); - m_lastBlockHash = newHash; + ret = treeRoute(last, newHash); + { + WriteGuard l(x_lastBlockHash); + m_lastBlockHash = newHash; + } m_extrasDB->Put(m_writeOptions, ldb::Slice("best"), ldb::Slice((char const*)&newHash, 32)); clog(BlockChainNote) << " Imported and best. Has" << (details(bi.parentHash).children.size() - 1) << "siblings. Route:"; for (auto r: ret) @@ -307,7 +319,7 @@ h256s BlockChain::import(bytes const& _block, OverlayDB const& _db) } else { - clog(BlockChainNote) << " Imported but not best (oTD:" << details(m_lastBlockHash).totalDifficulty << ", TD:" << td << ")"; + clog(BlockChainNote) << " Imported but not best (oTD:" << details(last).totalDifficulty << ", TD:" << td << ")"; } return ret; } @@ -347,7 +359,6 @@ h256s BlockChain::treeRoute(h256 _from, h256 _to, h256* o_common) const void BlockChain::checkConsistency() { - lock_guard l(m_lock); m_details.clear(); ldb::Iterator* it = m_db->NewIterator(m_readOptions); for (it->SeekToFirst(); it->Valid(); it->Next()) @@ -371,15 +382,17 @@ bytes BlockChain::block(h256 _hash) const if (_hash == m_genesisHash) return m_genesisBlock; - lock_guard l(m_lock); - - auto it = m_cache.find(_hash); - if (it != m_cache.end()) - return it->second; + { + ReadGuard l(x_cache); + auto it = m_cache.find(_hash); + if (it != m_cache.end()) + return it->second; + } string d; m_db->Get(m_readOptions, ldb::Slice((char const*)&_hash, 32), &d); + WriteGuard l(x_cache); m_cache[_hash].resize(d.size()); memcpy(m_cache[_hash].data(), d.data(), d.size()); @@ -389,11 +402,6 @@ bytes BlockChain::block(h256 _hash) const return m_cache[_hash]; } -eth::uint BlockChain::number(h256 _hash) const -{ - return details(_hash).number; -} - h256 BlockChain::numberHash(unsigned _n) const { if (!_n) @@ -402,69 +410,3 @@ h256 BlockChain::numberHash(unsigned _n) const for (; _n < details().number; ++_n, ret = details(ret).parent) {} return ret; } - -BlockDetails BlockChain::details(h256 _h) const -{ - lock_guard l(m_lock); - - BlockDetailsHash::const_iterator it = m_details.find(_h); - if (it != m_details.end()) - return it->second; - - std::string s; - m_extrasDB->Get(m_readOptions, toSlice(_h), &s); - if (s.empty()) - { -// cout << "Not found in DB: " << _h << endl; - return NullBlockDetails; - } - { - bool ok; - tie(it, ok) = m_details.insert(std::make_pair(_h, BlockDetails(RLP(s)))); - } - return it->second; -} - -BlockBlooms BlockChain::blooms(h256 _h) const -{ - lock_guard l(m_lock); - - BlockBloomsHash::const_iterator it = m_blooms.find(_h); - if (it != m_blooms.end()) - return it->second; - - std::string s; - m_extrasDB->Get(m_readOptions, toSlice(_h, 1), &s); - if (s.empty()) - { -// cout << "Not found in DB: " << _h << endl; - return NullBlockBlooms; - } - { - bool ok; - tie(it, ok) = m_blooms.insert(std::make_pair(_h, BlockBlooms(RLP(s)))); - } - return it->second; -} - -BlockTraces BlockChain::traces(h256 _h) const -{ - lock_guard l(m_lock); - - BlockTracesHash::const_iterator it = m_traces.find(_h); - if (it != m_traces.end()) - return it->second; - - std::string s; - m_extrasDB->Get(m_readOptions, toSlice(_h, 2), &s); - if (s.empty()) - { -// cout << "Not found in DB: " << _h << endl; - return NullBlockTraces; - } - { - bool ok; - tie(it, ok) = m_traces.insert(std::make_pair(_h, BlockTraces(RLP(s)))); - } - return it->second; -} diff --git a/libethereum/BlockChain.h b/libethereum/BlockChain.h index 749dd87f9..1a05d631c 100644 --- a/libethereum/BlockChain.h +++ b/libethereum/BlockChain.h @@ -22,6 +22,7 @@ #pragma once #include +#include #include #include #include @@ -94,6 +95,13 @@ struct BlockChainNote: public LogChannel { static const char* name() { return "= // TODO: Move all this Genesis stuff into Genesis.h/.cpp std::map const& genesisState(); +using ReadGuard = boost::shared_lock; +using UpgradableGuard = boost::upgrade_lock; +using UpgradeGuard = boost::upgrade_to_unique_lock; +using WriteGuard = boost::unique_lock; + +ldb::Slice toSlice(h256 _h, unsigned _sub = 0); + /** * @brief Implements the blockchain database. All data this gives is disk-backed. * @todo Make thread-safe. @@ -118,35 +126,36 @@ public: h256s import(bytes const& _block, OverlayDB const& _stateDB); /// Get the familial details concerning a block (or the most recent mined if none given). Thread-safe. - BlockDetails details(h256 _hash) const; + BlockDetails details(h256 _hash) const { return queryExtras(_hash, m_details, x_details, NullBlockDetails); } BlockDetails details() const { return details(currentHash()); } /// Get the transactions' bloom filters of a block (or the most recent mined if none given). Thread-safe. - BlockBlooms blooms(h256 _hash) const; + BlockBlooms blooms(h256 _hash) const { return queryExtras(_hash, m_blooms, x_blooms, NullBlockBlooms); } BlockBlooms blooms() const { return blooms(currentHash()); } /// Get the transactions' trace manifests of a block (or the most recent mined if none given). Thread-safe. - BlockTraces traces(h256 _hash) const; + BlockTraces traces(h256 _hash) const { return queryExtras(_hash, m_traces, x_traces, NullBlockTraces); } BlockTraces traces() const { return traces(currentHash()); } - /// Get a given block (RLP format). Thread-safe. + /// Get a block (RLP format) for the given hash (or the most recent mined if none given). Thread-safe. bytes block(h256 _hash) const; bytes block() const { return block(currentHash()); } - uint number(h256 _hash) const; + /// Get a number for the given hash (or the most recent mined if none given). Thread-safe. + uint number(h256 _hash) const { return details(_hash).number; } uint number() const { return number(currentHash()); } /// Get a given block (RLP format). Thread-safe. - h256 currentHash() const { return m_lastBlockHash; } + h256 currentHash() const { ReadGuard l(x_lastBlockHash); return m_lastBlockHash; } - /// Get the hash of the genesis block. + /// Get the hash of the genesis block. Thread-safe. h256 genesisHash() const { return m_genesisHash; } - /// Get the hash of a block of a given number. + /// Get the hash of a block of a given number. Slow; try not to use it too much. h256 numberHash(unsigned _n) const; /// @returns the genesis block header. - static BlockInfo const& genesis() { if (!s_genesis) { auto gb = createGenesisBlock(); (s_genesis = new BlockInfo)->populate(&gb); } return *s_genesis; } + static BlockInfo const& genesis() { UpgradableGuard l(x_genesis); if (!s_genesis) { auto gb = createGenesisBlock(); UpgradeGuard ul(l); (s_genesis = new BlockInfo)->populate(&gb); } return *s_genesis; } /// @returns the genesis block as its RLP-encoded byte array. /// @note This is slow as it's constructed anew each call. Consider genesis() instead. @@ -169,21 +178,49 @@ public: h256s treeRoute(h256 _from, h256 _to, h256* o_common = nullptr) const; private: + template T queryExtras(h256 _h, std::map& _m, boost::shared_mutex& _x, T const& _n) const + { + { + ReadGuard l(_x); + auto it = _m.find(_h); + if (it != _m.end()) + return it->second; + } + + std::string s; + m_extrasDB->Get(m_readOptions, toSlice(_h, N), &s); + if (s.empty()) + { + // cout << "Not found in DB: " << _h << endl; + return _n; + } + + WriteGuard l(_x); + auto ret = _m.insert(std::make_pair(_h, T(RLP(s)))); + return ret.first->second; + } + void checkConsistency(); - /// Get fully populated from disk DB. + /// The caches of the disk DB and their locks. + mutable boost::shared_mutex x_details; mutable BlockDetailsHash m_details; + mutable boost::shared_mutex x_blooms; mutable BlockBloomsHash m_blooms; + mutable boost::shared_mutex x_traces; mutable BlockTracesHash m_traces; - + mutable boost::shared_mutex x_cache; mutable std::map m_cache; - mutable std::recursive_mutex m_lock; + /// The disk DBs. Thread-safe, so no need for locks. ldb::DB* m_db; ldb::DB* m_extrasDB; /// Hash of the last (valid) block on the longest chain. + mutable boost::shared_mutex x_lastBlockHash; h256 m_lastBlockHash; + + /// Genesis block info. h256 m_genesisHash; bytes m_genesisBlock; @@ -192,6 +229,8 @@ private: friend std::ostream& operator<<(std::ostream& _out, BlockChain const& _bc); + /// Static genesis info and its lock. + static boost::shared_mutex x_genesis; static BlockInfo* s_genesis; }; From 44748a35e8ea217708afefaebb061ae7c4bf25e8 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 25 Jul 2014 15:01:00 +0200 Subject: [PATCH 013/223] Threadsafe transaction queue. Some repotting. --- libethereum/BlockChain.cpp | 20 +-------- libethereum/BlockChain.h | 58 ++---------------------- libethereum/BlockDetails.cpp | 40 +++++++++++++++++ libethereum/BlockDetails.h | 76 ++++++++++++++++++++++++++++++++ libethereum/Guards.cpp | 29 ++++++++++++ libethereum/Guards.h | 34 ++++++++++++++ libethereum/TransactionQueue.cpp | 8 +++- libethereum/TransactionQueue.h | 29 ++++++------ 8 files changed, 204 insertions(+), 90 deletions(-) create mode 100644 libethereum/BlockDetails.cpp create mode 100644 libethereum/BlockDetails.h create mode 100644 libethereum/Guards.cpp create mode 100644 libethereum/Guards.h diff --git a/libethereum/BlockChain.cpp b/libethereum/BlockChain.cpp index 21fa9f256..742818118 100644 --- a/libethereum/BlockChain.cpp +++ b/libethereum/BlockChain.cpp @@ -35,10 +35,7 @@ using namespace eth; #define ETH_CATCH 1 -namespace eth -{ - -std::ostream& operator<<(std::ostream& _out, BlockChain const& _bc) +std::ostream& eth::operator<<(std::ostream& _out, BlockChain const& _bc) { string cmp = toBigEndianString(_bc.currentHash()); auto it = _bc.m_extrasDB->NewIterator(_bc.m_readOptions); @@ -51,21 +48,6 @@ std::ostream& operator<<(std::ostream& _out, BlockChain const& _bc) delete it; return _out; } -} - -BlockDetails::BlockDetails(RLP const& _r) -{ - number = _r[0].toInt(); - totalDifficulty = _r[1].toInt(); - parent = _r[2].toHash(); - children = _r[3].toVector(); - bloom = _r[4].toHash(); -} - -bytes BlockDetails::rlp() const -{ - return rlpList(number, totalDifficulty, parent, children, bloom); -} std::map const& eth::genesisState() { diff --git a/libethereum/BlockChain.h b/libethereum/BlockChain.h index 1a05d631c..052471537 100644 --- a/libethereum/BlockChain.h +++ b/libethereum/BlockChain.h @@ -22,64 +22,17 @@ #pragma once #include -#include #include #include #include -#include "Manifest.h" +#include "Guards.h" +#include "BlockDetails.h" #include "AddressState.h" namespace ldb = leveldb; namespace eth { -class RLP; -class RLPStream; - -struct BlockDetails -{ - BlockDetails(): number(0), totalDifficulty(0) {} - BlockDetails(uint _n, u256 _tD, h256 _p, h256s _c, h256 _bloom): number(_n), totalDifficulty(_tD), parent(_p), children(_c), bloom(_bloom) {} - BlockDetails(RLP const& _r); - bytes rlp() const; - - bool isNull() const { return !totalDifficulty; } - explicit operator bool() const { return !isNull(); } - - uint number; // TODO: remove? - u256 totalDifficulty; - h256 parent; - h256s children; - h256 bloom; -}; - -struct BlockBlooms -{ - BlockBlooms() {} - BlockBlooms(RLP const& _r) { blooms = _r.toVector(); } - bytes rlp() const { RLPStream s; s << blooms; return s.out(); } - - h256s blooms; -}; - -struct BlockTraces -{ - BlockTraces() {} - BlockTraces(RLP const& _r) { for (auto const& i: _r) traces.emplace_back(i.data()); } - bytes rlp() const { RLPStream s(traces.size()); for (auto const& i: traces) i.streamOut(s); return s.out(); } - - Manifests traces; -}; - - -typedef std::map BlockDetailsHash; -typedef std::map BlockBloomsHash; -typedef std::map BlockTracesHash; - -static const BlockDetails NullBlockDetails; -static const BlockBlooms NullBlockBlooms; -static const BlockTraces NullBlockTraces; - static const h256s NullH256s; class State; @@ -95,16 +48,11 @@ struct BlockChainNote: public LogChannel { static const char* name() { return "= // TODO: Move all this Genesis stuff into Genesis.h/.cpp std::map const& genesisState(); -using ReadGuard = boost::shared_lock; -using UpgradableGuard = boost::upgrade_lock; -using UpgradeGuard = boost::upgrade_to_unique_lock; -using WriteGuard = boost::unique_lock; - ldb::Slice toSlice(h256 _h, unsigned _sub = 0); /** * @brief Implements the blockchain database. All data this gives is disk-backed. - * @todo Make thread-safe. + * @threadsafe * @todo Make not memory hog (should actually act as a cache and deallocate old entries). */ class BlockChain diff --git a/libethereum/BlockDetails.cpp b/libethereum/BlockDetails.cpp new file mode 100644 index 000000000..ae107fa68 --- /dev/null +++ b/libethereum/BlockDetails.cpp @@ -0,0 +1,40 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file BlockDetails.cpp + * @author Gav Wood + * @date 2014 + */ + +#include "BlockDetails.h" + +#include +using namespace std; +using namespace eth; + +BlockDetails::BlockDetails(RLP const& _r) +{ + number = _r[0].toInt(); + totalDifficulty = _r[1].toInt(); + parent = _r[2].toHash(); + children = _r[3].toVector(); + bloom = _r[4].toHash(); +} + +bytes BlockDetails::rlp() const +{ + return rlpList(number, totalDifficulty, parent, children, bloom); +} diff --git a/libethereum/BlockDetails.h b/libethereum/BlockDetails.h new file mode 100644 index 000000000..6db2171ee --- /dev/null +++ b/libethereum/BlockDetails.h @@ -0,0 +1,76 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file BlockDetails.h + * @author Gav Wood + * @date 2014 + */ + +#pragma once + +#include +#include +#include "Manifest.h" +namespace ldb = leveldb; + +namespace eth +{ + +struct BlockDetails +{ + BlockDetails(): number(0), totalDifficulty(0) {} + BlockDetails(uint _n, u256 _tD, h256 _p, h256s _c, h256 _bloom): number(_n), totalDifficulty(_tD), parent(_p), children(_c), bloom(_bloom) {} + BlockDetails(RLP const& _r); + bytes rlp() const; + + bool isNull() const { return !totalDifficulty; } + explicit operator bool() const { return !isNull(); } + + uint number; // TODO: remove? + u256 totalDifficulty; + h256 parent; + h256s children; + h256 bloom; +}; + +struct BlockBlooms +{ + BlockBlooms() {} + BlockBlooms(RLP const& _r) { blooms = _r.toVector(); } + bytes rlp() const { RLPStream s; s << blooms; return s.out(); } + + h256s blooms; +}; + +struct BlockTraces +{ + BlockTraces() {} + BlockTraces(RLP const& _r) { for (auto const& i: _r) traces.emplace_back(i.data()); } + bytes rlp() const { RLPStream s(traces.size()); for (auto const& i: traces) i.streamOut(s); return s.out(); } + + Manifests traces; +}; + + +typedef std::map BlockDetailsHash; +typedef std::map BlockBloomsHash; +typedef std::map BlockTracesHash; + +static const BlockDetails NullBlockDetails; +static const BlockBlooms NullBlockBlooms; +static const BlockTraces NullBlockTraces; + +} diff --git a/libethereum/Guards.cpp b/libethereum/Guards.cpp new file mode 100644 index 000000000..b2e12f98e --- /dev/null +++ b/libethereum/Guards.cpp @@ -0,0 +1,29 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file Guards.cpp + * @author Gav Wood + * @date 2014 + */ + +#include "Guards.h" +using namespace std; +using namespace eth; + +namespace eth +{ + +} diff --git a/libethereum/Guards.h b/libethereum/Guards.h new file mode 100644 index 000000000..cf047a3b3 --- /dev/null +++ b/libethereum/Guards.h @@ -0,0 +1,34 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file Guards.h + * @author Gav Wood + * @date 2014 + */ + +#pragma once + +#include + +namespace eth +{ + +using ReadGuard = boost::shared_lock; +using UpgradableGuard = boost::upgrade_lock; +using UpgradeGuard = boost::upgrade_to_unique_lock; +using WriteGuard = boost::unique_lock; + +} diff --git a/libethereum/TransactionQueue.cpp b/libethereum/TransactionQueue.cpp index f63384926..9a8c4c20a 100644 --- a/libethereum/TransactionQueue.cpp +++ b/libethereum/TransactionQueue.cpp @@ -31,6 +31,8 @@ bool TransactionQueue::import(bytesConstRef _block) { // Check if we already know this transaction. h256 h = sha3(_block); + + UpgradableGuard l(x_data); if (m_data.count(h)) return false; @@ -40,10 +42,9 @@ bool TransactionQueue::import(bytesConstRef _block) // 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); auto s = t.sender(); - if (m_interest.count(s)) - m_interestQueue.push_back(t); // If valid, append to blocks. + UpgradeGuard ul(l); m_data[h] = _block.toBytes(); } catch (InvalidTransactionFormat const& _e) @@ -62,8 +63,10 @@ bool TransactionQueue::import(bytesConstRef _block) void TransactionQueue::setFuture(std::pair const& _t) { + UpgradableGuard l(x_data); if (m_data.count(_t.first)) { + UpgradeGuard ul(l); m_data.erase(_t.first); m_future.insert(make_pair(Transaction(_t.second).sender(), _t)); } @@ -71,6 +74,7 @@ void TransactionQueue::setFuture(std::pair const& _t) void TransactionQueue::noteGood(std::pair const& _t) { + WriteGuard l(x_data); auto r = m_future.equal_range(Transaction(_t.second).sender()); for (auto it = r.first; it != r.second; ++it) m_data.insert(_t); diff --git a/libethereum/TransactionQueue.h b/libethereum/TransactionQueue.h index 0c52370b3..d3ad354cc 100644 --- a/libethereum/TransactionQueue.h +++ b/libethereum/TransactionQueue.h @@ -21,8 +21,10 @@ #pragma once +#include #include -#include "Transaction.h" +#include "libethcore/CommonEth.h" +#include "Guards.h" namespace eth { @@ -31,28 +33,27 @@ class BlockChain; /** * @brief A queue of Transactions, each stored as RLP. + * @threadsafe */ class TransactionQueue { public: - bool attemptImport(bytesConstRef _block) { try { import(_block); return true; } catch (...) { return false; } } - bool attemptImport(bytes const& _block) { try { import(&_block); return true; } catch (...) { return false; } } - bool import(bytesConstRef _block); - void drop(h256 _txHash) { m_data.erase(_txHash); } - std::map const& transactions() const { return m_data; } + bool attemptImport(bytesConstRef _tx) { try { import(_block); return true; } catch (...) { return false; } } + bool attemptImport(bytes const& _tx) { return attemptImport(&_block); } + bool import(bytesConstRef _tx); + + void drop(h256 _txHash) { WriteGuard l(x_data); m_data.erase(_txHash); } + + std::map transactions() const { ReadGuard l(x_data); return m_data; } void setFuture(std::pair const& _t); void noteGood(std::pair const& _t); - Transactions interestQueue() { Transactions ret; swap(ret, m_interestQueue); return ret; } - void pushInterest(Address _a) { m_interest[_a]++; } - void popInterest(Address _a) { if (m_interest[_a] > 1) m_interest[_a]--; else if (m_interest[_a]) m_interest.erase(_a); } - private: - std::map m_data; ///< Map of SHA3(tx) to tx. - Transactions m_interestQueue; - std::map m_interest; - std::multimap> m_future; ///< For transactions that have a future nonce; we map their sender address to the tx stuff, and insert once the sender has a valid TX. + std::map m_data; ///< Map of SHA3(tx) to tx. + boost::shared_mutex x_data; + + std::multimap> m_future; ///< For transactions that have a future nonce; we map their sender address to the tx stuff, and insert once the sender has a valid TX. }; } From 268ca545ac51e680e0d9ecc9ad8a42c4beb15a3f Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 25 Jul 2014 20:37:55 +0200 Subject: [PATCH 014/223] Network a lot smoother. --- libethereum/BlockChain.cpp | 32 +++- libethereum/BlockChain.h | 9 +- libethereum/BlockQueue.cpp | 102 ++++++++++++ libethereum/BlockQueue.h | 66 ++++++++ libethereum/Client.cpp | 75 +++++---- libethereum/Client.h | 10 +- libethereum/Guards.h | 3 + libethereum/PeerServer.cpp | 275 +++++++++++++++++-------------- libethereum/PeerServer.h | 34 +++- libethereum/PeerSession.cpp | 36 ++-- libethereum/TransactionQueue.cpp | 35 ++-- libethereum/TransactionQueue.h | 15 +- 12 files changed, 478 insertions(+), 214 deletions(-) create mode 100644 libethereum/BlockQueue.cpp create mode 100644 libethereum/BlockQueue.h diff --git a/libethereum/BlockChain.cpp b/libethereum/BlockChain.cpp index 742818118..f26304db2 100644 --- a/libethereum/BlockChain.cpp +++ b/libethereum/BlockChain.cpp @@ -163,20 +163,40 @@ bool contains(T const& _t, V const& _v) return false; } -h256s BlockChain::attemptImport(bytes const& _block, OverlayDB const& _stateDB) +h256s BlockChain::sync(BlockQueue& _bq, OverlayDB const& _stateDB, unsigned _max) +{ + vector blocks; + _bq.drain(blocks); + + h256s ret; + for (auto const& block: blocks) + try + { + for (auto h: import(block, _stateDB)) + if (!_max--) + break; + else + ret.push_back(h); + } + catch (UnknownParent) + { + cwarn << "Unknown parent of block!!!" << eth::sha3(block).abridged(); + _bq.import(&block, *this); + } + catch (...){} + return ret; +} + +h256s BlockChain::attemptImport(bytes const& _block, OverlayDB const& _stateDB) noexcept { -#if ETH_CATCH try -#endif { return import(_block, _stateDB); } -#if ETH_CATCH catch (...) { return h256s(); } -#endif } h256s BlockChain::import(bytes const& _block, OverlayDB const& _db) @@ -220,7 +240,7 @@ h256s BlockChain::import(bytes const& _block, OverlayDB const& _db) if (bi.timestamp > (u256)time(0)) { clog(BlockChainNote) << newHash << ": Future time " << bi.timestamp << " (now at " << time(0) << ")"; - // 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. + // Block has a timestamp in the future. This is no good. throw FutureTime(); } diff --git a/libethereum/BlockChain.h b/libethereum/BlockChain.h index 052471537..1c4e5e8fd 100644 --- a/libethereum/BlockChain.h +++ b/libethereum/BlockChain.h @@ -28,6 +28,7 @@ #include "Guards.h" #include "BlockDetails.h" #include "AddressState.h" +#include "BlockQueue.h" namespace ldb = leveldb; namespace eth @@ -66,8 +67,12 @@ public: /// To be called from main loop every 100ms or so. void process(); - /// Attempt to import the given block. - h256s attemptImport(bytes const& _block, OverlayDB const& _stateDB); + /// Sync the chain with any incoming blocks. All blocks should, if processed in order + h256s sync(BlockQueue& _bq, OverlayDB const& _stateDB, unsigned _max); + + /// Attempt to import the given block directly into the BlockChain and sync with the state DB. + /// @returns the block hashes of any blocks that came into/went out of the canonical block chain. + h256s attemptImport(bytes const& _block, OverlayDB const& _stateDB) noexcept; /// Import block into disk-backed DB /// @returns the block hashes of any blocks that came into/went out of the canonical block chain. diff --git a/libethereum/BlockQueue.cpp b/libethereum/BlockQueue.cpp new file mode 100644 index 000000000..b688ae186 --- /dev/null +++ b/libethereum/BlockQueue.cpp @@ -0,0 +1,102 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file BlockQueue.cpp + * @author Gav Wood + * @date 2014 + */ + +#include "BlockQueue.h" + +#include +#include +#include +#include "BlockChain.h" +using namespace std; +using namespace eth; + +bool BlockQueue::import(bytesConstRef _block, BlockChain const& _bc) +{ + // Check if we already know this block. + h256 h = sha3(_block); + + UpgradableGuard l(m_lock); + if (m_readySet.count(h) || m_futureSet.count(h)) + // Already know about this one. + return false; + + // VERIFY: populates from the block and checks the block is internally coherent. + BlockInfo bi; + +#if ETH_CATCH + try +#endif + { + bi.populate(_block); + bi.verifyInternals(_block); + } +#if ETH_CATCH + catch (Exception const& _e) + { + cwarn << "Ignoring malformed block: " << _e.description(); + return false; + } +#endif + auto newHash = eth::sha3(_block); + + // Check block doesn't already exist first! + if (_bc.details(newHash)) + return false; + + // Check it's not crazy + if (bi.timestamp > (u256)time(0)) + return false; + + UpgradeGuard ul(l); + + // We now know it. + if (!m_readySet.count(bi.parentHash) && !_bc.details(bi.parentHash)) + { + // We don't know the parent (yet) - queue it up for later. It'll get resent to us if we find out about its ancestry later on. + m_future.insert(make_pair(bi.parentHash, make_pair(h, _block.toBytes()))); + m_futureSet.insert(h); + return true; + } + + // If valid, append to blocks. + m_ready.push_back(_block.toBytes()); + m_readySet.insert(h); + + noteReadyWithoutWriteGuard(h); + + return true; +} + +void BlockQueue::noteReadyWithoutWriteGuard(h256 _b) +{ + auto r = m_future.equal_range(_b); + h256s good; + for (auto it = r.first; it != r.second; ++it) + { + m_futureSet.erase(it->second.first); + m_ready.push_back(it->second.second); + m_readySet.erase(it->second.first); + good.push_back(it->second.first); + } + m_future.erase(r.first, r.second); + for (auto g: good) + noteReadyWithoutWriteGuard(g); +} diff --git a/libethereum/BlockQueue.h b/libethereum/BlockQueue.h new file mode 100644 index 000000000..7ec68215a --- /dev/null +++ b/libethereum/BlockQueue.h @@ -0,0 +1,66 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file BlockQueue.h + * @author Gav Wood + * @date 2014 + */ + +#pragma once + +#include +#include +#include "libethcore/CommonEth.h" +#include "Guards.h" + +namespace eth +{ + +class BlockChain; + +/** + * @brief A queue of blocks. Sits between network or other I/O and the BlockChain. + * Sorts them ready for blockchain insertion (with the BlockChain::sync() method). + * @threadsafe + */ +class BlockQueue +{ +public: + /// Import a block into the queue. + bool import(bytesConstRef _tx, BlockChain const& _bc); + + /// Grabs the blocks that are ready, giving them in the correct order for insertion into the chain. + void drain(std::vector& o_out) { WriteGuard l(m_lock); swap(o_out, m_ready); m_readySet.clear(); } + + /// Notify the queue that the chain has changed and a new block has attained 'ready' status (i.e. is in the chain). + void noteReady(h256 _b) { WriteGuard l(m_lock); noteReadyWithoutWriteGuard(_b); } + + /// Get information on the items queued. + std::pair items() const { ReadGuard l(m_lock); return std::make_pair(m_ready.size(), m_future.size()); } + +private: + void noteReadyWithoutWriteGuard(h256 _b); + + mutable boost::shared_mutex m_lock; ///< General lock. + std::set m_readySet; ///< All blocks ready for chain-import. + std::vector m_ready; ///< List of blocks, in correct order, ready for chain-import. + std::set m_futureSet; ///< Set of all blocks whose parents are not ready/in-chain. + std::multimap> m_future; ///< For transactions that have an unknown parent; we map their parent hash to the block stuff, and insert once the block appears. +}; + +} + + diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index d2905234d..2b2d1211e 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -198,40 +198,43 @@ void Client::startNetwork(unsigned short _listenPort, std::string const& _seedHo { ensureWorking(); - ClientGuard l(this); - if (m_net.get()) - return; - try - { - m_net.reset(new PeerServer(m_clientVersion, m_bc, 0, _listenPort, _mode, _publicIP, _upnp)); - } - catch (std::exception const&) { - // Probably already have the port open. - cwarn << "Could not initialize with specified/default port. Trying system-assigned port"; - m_net.reset(new PeerServer(m_clientVersion, m_bc, 0, _mode, _publicIP, _upnp)); + Guard l(x_net); + if (m_net.get()) + return; + try + { + m_net.reset(new PeerServer(m_clientVersion, m_bc, 0, _listenPort, _mode, _publicIP, _upnp)); + } + catch (std::exception const&) + { + // Probably already have the port open. + cwarn << "Could not initialize with specified/default port. Trying system-assigned port"; + m_net.reset(new PeerServer(m_clientVersion, m_bc, 0, _mode, _publicIP, _upnp)); + } + + m_net->setIdealPeerCount(_peers); } - m_net->setIdealPeerCount(_peers); if (_seedHost.size()) connect(_seedHost, _port); } std::vector Client::peers() { - ClientGuard l(this); + Guard l(x_net); return m_net ? m_net->peers() : std::vector(); } size_t Client::peerCount() const { - ClientGuard l(this); + Guard l(x_net); return m_net ? m_net->peerCount() : 0; } void Client::connect(std::string const& _seedHost, unsigned short _port) { - ClientGuard l(this); + Guard l(x_net); if (!m_net.get()) return; m_net->connect(_seedHost, _port); @@ -239,7 +242,7 @@ void Client::connect(std::string const& _seedHost, unsigned short _port) void Client::stopNetwork() { - ClientGuard l(this); + Guard l(x_net); m_net.reset(nullptr); } @@ -308,27 +311,26 @@ void Client::work(bool _justQueue) // 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. - if (m_net && !_justQueue) { - cdebug << "--- WORK: LOCK"; - ClientGuard l(this); - cdebug << "--- WORK: NETWORK"; - m_net->process(); // must be in guard for now since it uses the blockchain. - - // returns h256Set as block hashes, once for each block that has come in/gone out. - cdebug << "--- WORK: TQ <== NET ==> CHAIN"; - h256Set newBlocks = m_net->sync(m_bc, m_tq, m_stateDB, 100); - if (newBlocks.size()) + Guard l(x_net); + if (m_net && !_justQueue) { - for (auto i: newBlocks) - appendFromNewBlock(i, changeds); - changeds.insert(NewBlockFilter); + cdebug << "--- WORK: NETWORK"; + m_net->process(); // must be in guard for now since it uses the blockchain. + + // returns h256Set as block hashes, once for each block that has come in/gone out. + cdebug << "--- WORK: NET <==> TQ ; CHAIN ==> NET ==> BQ"; + m_net->sync(m_tq, m_bq); + + cdebug << "--- TQ:" << m_tq.items() << "; BQ:" << m_bq.items(); } } // Do some mining. if (!_justQueue) { + + // TODO: Separate "Miner" object. if (m_doMine) { if (m_restartMining) @@ -402,6 +404,21 @@ void Client::work(bool _justQueue) // Resynchronise state with block chain & trans { ClientGuard l(this); + + cdebug << "--- WORK: BQ ==> CHAIN ==> STATE"; + OverlayDB db = m_stateDB; + m_lock.unlock(); + h256s newBlocks = m_bc.sync(m_bq, db, 100); + if (newBlocks.size()) + { + for (auto i: newBlocks) + appendFromNewBlock(i, changeds); + changeds.insert(NewBlockFilter); + } + m_lock.lock(); + if (newBlocks.size()) + m_stateDB = db; + cdebug << "--- WORK: preSTATE <== CHAIN"; if (m_preMine.sync(m_bc) || m_postMine.address() != m_preMine.address()) { diff --git a/libethereum/Client.h b/libethereum/Client.h index d589d30ce..008cf0c52 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -252,9 +252,9 @@ public: /// Stop the network subsystem. void stopNetwork(); /// Is the network subsystem up? - bool haveNetwork() { return !!m_net; } - /// Get access to the peer server object. This will be null if the network isn't online. - PeerServer* peerServer() const { return m_net.get(); } + bool haveNetwork() { Guard l(x_net); return !!m_net; } + /// Get access to the peer server object. This will be null if the network isn't online. DANGEROUS! DO NOT USE! + PeerServer* peerServer() const { Guard l(x_net); return m_net.get(); } // Mining stuff: @@ -311,11 +311,13 @@ private: std::string m_clientVersion; ///< Our end-application client's name/version. VersionChecker m_vc; ///< Dummy object to check & update the protocol version. BlockChain m_bc; ///< Maintains block database. - TransactionQueue m_tq; ///< Maintains list of incoming transactions not yet on the block chain. + TransactionQueue m_tq; ///< Maintains a list of incoming transactions not yet in a block on the blockchain. + BlockQueue m_bq; ///< Maintains a list of incoming blocks not yet on the blockchain (to be imported). OverlayDB m_stateDB; ///< Acts as the central point for the state database, so multiple States can share it. State m_preMine; ///< The present state of the client. State m_postMine; ///< The state of the client which we're mining (i.e. it'll have all the rewards added). + mutable std::mutex x_net; ///< Lock for the network. std::unique_ptr m_net; ///< Should run in background and send us events when blocks found and allow us to send blocks as required. std::unique_ptr m_work;///< The work thread. diff --git a/libethereum/Guards.h b/libethereum/Guards.h index cf047a3b3..64a95eb78 100644 --- a/libethereum/Guards.h +++ b/libethereum/Guards.h @@ -21,11 +21,14 @@ #pragma once +#include #include namespace eth { +using Guard = std::lock_guard; +using RecursiveGuard = std::lock_guard; using ReadGuard = boost::shared_lock; using UpgradableGuard = boost::upgrade_lock; using UpgradeGuard = boost::upgrade_to_unique_lock; diff --git a/libethereum/PeerServer.cpp b/libethereum/PeerServer.cpp index 6f9e779ae..73959f8f7 100644 --- a/libethereum/PeerServer.cpp +++ b/libethereum/PeerServer.cpp @@ -39,6 +39,7 @@ #include #include "BlockChain.h" #include "TransactionQueue.h" +#include "BlockQueue.h" #include "PeerSession.h" using namespace std; using namespace eth; @@ -106,9 +107,34 @@ PeerServer::PeerServer(std::string const& _clientVersion, BlockChain const& _ch, PeerServer::~PeerServer() { - for (auto const& i: m_peers) - if (auto p = i.second.lock()) - p->disconnect(ClientQuit); + disconnectPeers(); +} + +void PeerServer::registerPeer(std::shared_ptr _s) +{ + Guard l(x_peers); + m_peers[_s->m_id] = _s; +} + +void PeerServer::disconnectPeers() +{ + for (unsigned n = 0;; n = 0) + { + { + Guard l(x_peers); + for (auto i: m_peers) + if (auto p = i.second.lock()) + { + p->disconnect(ClientQuit); + n++; + } + } + if (!n) + break; + m_ioService.poll(); + usleep(100000); + } + delete m_upnp; } @@ -252,6 +278,7 @@ std::map PeerServer::potentialPeers() std::map ret; if (!m_public.address().is_unspecified()) ret.insert(make_pair(m_key.pub(), m_public)); + Guard l(x_peers); for (auto i: m_peers) if (auto j = i.second.lock()) { @@ -288,7 +315,7 @@ void PeerServer::ensureAccepting() clog(NetWarn) << "ERROR: " << _e.what(); } m_accepting = false; - if (ec.value() != 1 && (m_mode == NodeMode::PeerServer || m_peers.size() < m_idealPeerCount * 2)) + if (ec.value() != 1 && (m_mode == NodeMode::PeerServer || peerCount() < m_idealPeerCount * 2)) ensureAccepting(); }); } @@ -336,12 +363,12 @@ void PeerServer::connect(bi::tcp::endpoint const& _ep) }); } -bool PeerServer::ensureInitialised(BlockChain& _bc, TransactionQueue& _tq) +bool PeerServer::ensureInitialised(TransactionQueue& _tq) { if (m_latestBlockSent == h256()) { // First time - just initialise. - m_latestBlockSent = _bc.currentHash(); + m_latestBlockSent = m_chain->currentHash(); clog(NetNote) << "Initialising: latest=" << m_latestBlockSent; for (auto const& i: _tq.transactions()) @@ -363,146 +390,141 @@ bool PeerServer::noteBlock(h256 _hash, bytesConstRef _data) return false; } -h256Set PeerServer::sync(BlockChain& _bc, TransactionQueue& _tq, OverlayDB& _o, unsigned _max) +bool PeerServer::sync(TransactionQueue& _tq, BlockQueue& _bq) { - h256Set ret; - - bool netChange = ensureInitialised(_bc, _tq); + bool netChange = ensureInitialised(_tq); if (m_mode == NodeMode::Full) { - for (auto it = m_incomingTransactions.begin(); it != m_incomingTransactions.end(); ++it) - if (_tq.import(&*it)) - {}//ret = true; // just putting a transaction in the queue isn't enough to change the state - it might have an invalid nonce... - else - m_transactionsSent.insert(sha3(*it)); // if we already had the transaction, then don't bother sending it on. - m_incomingTransactions.clear(); + auto h = m_chain->currentHash(); - auto h = _bc.currentHash(); - bool resendAll = (h != m_latestBlockSent); + maintainTransactions(_tq, h); + maintainBlocks(_bq, h); - // Send any new transactions. - for (auto j: m_peers) - if (auto p = j.second.lock()) - { - bytes b; - uint n = 0; - for (auto const& i: _tq.transactions()) - if ((!m_transactionsSent.count(i.first) && !p->m_knownTransactions.count(i.first)) || p->m_requireTransactions || resendAll) - { - b += i.second; - ++n; - m_transactionsSent.insert(i.first); - } - if (n) - { - RLPStream ts; - PeerSession::prep(ts); - ts.appendList(n + 1) << TransactionsPacket; - ts.appendRaw(b, n).swapOut(b); - seal(b); - p->send(&b); - } - p->m_knownTransactions.clear(); - p->m_requireTransactions = false; - } + // Connect to additional peers + growPeers(); + } - // Send any new blocks. - if (h != m_latestBlockSent) + // platform for consensus of social contract. + // restricts your freedom but does so fairly. and that's the value proposition. + // guarantees that everyone else respect the rules of the system. (i.e. obeys laws). + + prunePeers(); + + return netChange; +} + +void PeerServer::maintainTransactions(TransactionQueue& _tq, h256 _currentHash) +{ + bool resendAll = (_currentHash != m_latestBlockSent); + + for (auto it = m_incomingTransactions.begin(); it != m_incomingTransactions.end(); ++it) + if (_tq.import(&*it)) + {}//ret = true; // just putting a transaction in the queue isn't enough to change the state - it might have an invalid nonce... + else + 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. + Guard l(x_peers); + for (auto j: m_peers) + if (auto p = j.second.lock()) { - // TODO: find where they diverge and send complete new branch. - RLPStream ts; - PeerSession::prep(ts); - ts.appendList(2) << BlocksPacket; bytes b; - ts.appendRaw(_bc.block(_bc.currentHash())).swapOut(b); - seal(b); - for (auto j: m_peers) - if (auto p = j.second.lock()) + uint n = 0; + for (auto const& i: _tq.transactions()) + if ((!m_transactionsSent.count(i.first) && !p->m_knownTransactions.count(i.first)) || p->m_requireTransactions || resendAll) { - if (!p->m_knownBlocks.count(_bc.currentHash())) - p->send(&b); - p->m_knownBlocks.clear(); + b += i.second; + ++n; + m_transactionsSent.insert(i.first); } + if (n) + { + RLPStream ts; + PeerSession::prep(ts); + ts.appendList(n + 1) << TransactionsPacket; + ts.appendRaw(b, n).swapOut(b); + seal(b); + p->send(&b); + } + p->m_knownTransactions.clear(); + p->m_requireTransactions = false; } - m_latestBlockSent = h; +} - unsigned totalAccepted = 0; - for (int accepted = 1, n = 0; accepted && totalAccepted < _max; ++n) - { - accepted = 0; - lock_guard l(m_incomingLock); - if (m_incomingBlocks.size()) - for (auto it = prev(m_incomingBlocks.end()); totalAccepted < _max; --it) - { - try - { - for (auto h: _bc.import(*it, _o)) - ret.insert(h); - it = m_incomingBlocks.erase(it); - ++accepted; - ++totalAccepted; - netChange = true; - } - catch (UnknownParent) - { - // Don't (yet) know its parent. Leave it for later. - m_unknownParentBlocks.push_back(*it); - it = m_incomingBlocks.erase(it); - } - catch (...) - { - // Some other error - erase it. - it = m_incomingBlocks.erase(it); - } +void PeerServer::maintainBlocks(BlockQueue& _bq, h256 _currentHash) +{ + // Import new blocks + { + lock_guard l(m_incomingLock); + for (auto it = m_incomingBlocks.rbegin(); it != m_incomingBlocks.rend(); ++it) + if (_bq.import(&*it, *m_chain)) + {} + else{} // TODO: don't forward it. + m_incomingBlocks.clear(); + } - if (it == m_incomingBlocks.begin()) - break; - } - if (!n && accepted) + // Send any new blocks. + if (_currentHash != m_latestBlockSent) + { + // TODO: find where they diverge and send complete new branch. + RLPStream ts; + PeerSession::prep(ts); + ts.appendList(2) << BlocksPacket; + bytes b; + ts.appendRaw(m_chain->block()).swapOut(b); + seal(b); + + Guard l(x_peers); + for (auto j: m_peers) + if (auto p = j.second.lock()) { - for (auto i: m_unknownParentBlocks) - m_incomingBlocks.push_back(i); - m_unknownParentBlocks.clear(); + if (!p->m_knownBlocks.count(_currentHash)) + p->send(&b); + p->m_knownBlocks.clear(); } - } + } + m_latestBlockSent = _currentHash; +} - // Connect to additional peers - while (m_peers.size() < m_idealPeerCount) +void PeerServer::growPeers() +{ + Guard l(x_peers); + while (m_peers.size() < m_idealPeerCount) + { + if (m_freePeers.empty()) { - if (m_freePeers.empty()) + if (chrono::steady_clock::now() > m_lastPeersRequest + chrono::seconds(10)) { - if (chrono::steady_clock::now() > m_lastPeersRequest + chrono::seconds(10)) - { - RLPStream s; - bytes b; - (PeerSession::prep(s).appendList(1) << GetPeersPacket).swapOut(b); - seal(b); - for (auto const& i: m_peers) - if (auto p = i.second.lock()) - if (p->isOpen()) - p->send(&b); - m_lastPeersRequest = chrono::steady_clock::now(); - } - + RLPStream s; + bytes b; + (PeerSession::prep(s).appendList(1) << GetPeersPacket).swapOut(b); + seal(b); + for (auto const& i: m_peers) + if (auto p = i.second.lock()) + if (p->isOpen()) + p->send(&b); + m_lastPeersRequest = chrono::steady_clock::now(); + } - if (!m_accepting) - ensureAccepting(); - break; - } + if (!m_accepting) + ensureAccepting(); - auto x = time(0) % m_freePeers.size(); - m_incomingPeers[m_freePeers[x]].second++; - connect(m_incomingPeers[m_freePeers[x]].first); - m_freePeers.erase(m_freePeers.begin() + x); + break; } - } - // platform for consensus of social contract. - // restricts your freedom but does so fairly. and that's the value proposition. - // guarantees that everyone else respect the rules of the system. (i.e. obeys laws). + auto x = time(0) % m_freePeers.size(); + m_incomingPeers[m_freePeers[x]].second++; + connect(m_incomingPeers[m_freePeers[x]].first); + m_freePeers.erase(m_freePeers.begin() + x); + } +} +void PeerServer::prunePeers() +{ + Guard l(x_peers); // We'll keep at most twice as many as is ideal, halfing what counts as "too young to kill" until we get there. for (uint old = 15000; m_peers.size() > m_idealPeerCount * 2 && old > 100; old /= 2) while (m_peers.size() > m_idealPeerCount) @@ -524,12 +546,17 @@ h256Set PeerServer::sync(BlockChain& _bc, TransactionQueue& _tq, OverlayDB& _o, worst->disconnect(TooManyPeers); } - (void)netChange; - return ret; + // Remove dead peers from list. + for (auto i = m_peers.begin(); i != m_peers.end();) + if (i->second.lock().get()) + ++i; + else + i = m_peers.erase(i); } std::vector PeerServer::peers(bool _updatePing) const { + Guard l(x_peers); if (_updatePing) const_cast(this)->pingAll(); this_thread::sleep_for(chrono::milliseconds(200)); @@ -543,6 +570,7 @@ std::vector PeerServer::peers(bool _updatePing) const void PeerServer::pingAll() { + Guard l(x_peers); for (auto& i: m_peers) if (auto j = i.second.lock()) j->ping(); @@ -550,6 +578,7 @@ void PeerServer::pingAll() bytes PeerServer::savePeers() const { + Guard l(x_peers); RLPStream ret; int n = 0; for (auto& i: m_peers) diff --git a/libethereum/PeerServer.h b/libethereum/PeerServer.h index 465a4dc41..ccb73eab1 100644 --- a/libethereum/PeerServer.h +++ b/libethereum/PeerServer.h @@ -30,12 +30,20 @@ #include #include #include "PeerNetwork.h" +#include "Guards.h" namespace ba = boost::asio; namespace bi = boost::asio::ip; namespace eth { +class TransactionQueue; +class BlockQueue; + +/** + * @brief The PeerServer class + * @warning None of this is thread-safe. You have been warned. + */ class PeerServer { friend class PeerSession; @@ -48,8 +56,12 @@ public: /// Start server, but don't listen. PeerServer(std::string const& _clientVersion, BlockChain const& _ch, unsigned int _networkId, NodeMode _m = NodeMode::Full); + /// Will block on network process events. ~PeerServer(); + /// Closes all peers. + void disconnectPeers(); + static unsigned protocolVersion(); unsigned networkId() { return m_networkId; } @@ -58,13 +70,15 @@ public: void connect(bi::tcp::endpoint const& _ep); /// Sync with the BlockChain. It might contain one of our mined blocks, we might have new candidates from the network. - h256Set sync(BlockChain& _bc, TransactionQueue&, OverlayDB& _o, unsigned _max); + bool sync(TransactionQueue&, BlockQueue& _bc); /// Conduct I/O, polling, syncing, whatever. /// Ideally all time-consuming I/O is done in a background thread or otherwise asynchronously, but you get this call every 100ms or so anyway. /// This won't touch alter the blockchain. void process() { if (isInitialised()) m_ioService.poll(); } + bool havePeer(Public _id) const { Guard l(x_peers); return m_peers.count(_id); } + /// Set ideal number of peers. void setIdealPeerCount(unsigned _n) { m_idealPeerCount = _n; } @@ -74,7 +88,7 @@ public: std::vector peers(bool _updatePing = false) const; /// Get number of peers connected; equivalent to, but faster than, peers().size(). - size_t peerCount() const { return m_peers.size(); } + size_t peerCount() const { Guard l(x_peers); return m_peers.size(); } /// Ping the peers, to update the latency information. void pingAll(); @@ -85,6 +99,8 @@ public: bytes savePeers() const; void restorePeers(bytesConstRef _b); + void registerPeer(std::shared_ptr _s); + private: /// Session wants to pass us a block that we might not have. /// @returns true if we didn't have it. @@ -95,10 +111,15 @@ private: void determinePublic(std::string const& _publicAddress, bool _upnp); void ensureAccepting(); + void growPeers(); + void prunePeers(); + void maintainTransactions(TransactionQueue& _tq, h256 _currentBlock); + void maintainBlocks(BlockQueue& _bq, h256 _currentBlock); + /// Check to see if the network peer-state initialisation has happened. bool isInitialised() const { return m_latestBlockSent; } /// Initialises the network peer-state, doing the stuff that needs to be once-only. @returns true if it really was first. - bool ensureInitialised(BlockChain& _bc, TransactionQueue& _tq); + bool ensureInitialised(TransactionQueue& _tq); std::map potentialPeers(); @@ -117,14 +138,15 @@ private: KeyPair m_key; unsigned m_networkId; + + mutable std::mutex x_peers; std::map> m_peers; + mutable std::recursive_mutex m_incomingLock; std::vector m_incomingTransactions; std::vector m_incomingBlocks; - mutable std::recursive_mutex m_incomingLock; - std::vector m_unknownParentBlocks; - std::vector m_freePeers; std::map> m_incomingPeers; + std::vector m_freePeers; h256 m_latestBlockSent; std::set m_transactionsSent; diff --git a/libethereum/PeerSession.cpp b/libethereum/PeerSession.cpp index 239163e86..281e834f0 100644 --- a/libethereum/PeerSession.cpp +++ b/libethereum/PeerSession.cpp @@ -86,15 +86,13 @@ bool PeerSession::interpret(RLP const& _r) clogS(NetMessageSummary) << "Hello: " << clientVersion << "V[" << m_protocolVersion << "/" << m_networkId << "]" << m_id.abridged() << showbase << hex << m_caps << dec << m_listenPort; - if (m_server->m_peers.count(m_id)) - if (auto l = m_server->m_peers[m_id].lock()) - if (l.get() != this && l->isOpen()) - { - // Already connected. - cwarn << "Already have peer id" << m_id.abridged() << "at" << l->endpoint() << "rather than" << endpoint(); - disconnect(DuplicatePeer); - return false; - } + if (m_server->havePeer(m_id)) + { + // Already connected. + cwarn << "Already have peer id" << m_id.abridged();// << "at" << l->endpoint() << "rather than" << endpoint(); + disconnect(DuplicatePeer); + return false; + } if (m_protocolVersion != PeerServer::protocolVersion() || m_networkId != m_server->networkId() || !m_id) { @@ -109,7 +107,8 @@ bool PeerSession::interpret(RLP const& _r) return false; } - m_server->m_peers[m_id] = shared_from_this(); + m_server->registerPeer(shared_from_this()); + startInitialSync(); // Grab trsansactions off them. { @@ -173,7 +172,7 @@ bool PeerSession::interpret(RLP const& _r) clogS(NetAllDetail) << "Checking: " << ep << "(" << toHex(id.ref().cropped(0, 4)) << ")"; // check that it's not us or one we already know: - if (id && (m_server->m_key.pub() == id || m_server->m_peers.count(id) || m_server->m_incomingPeers.count(id))) + if (id && (m_server->m_key.pub() == id || m_server->havePeer(id) || m_server->m_incomingPeers.count(id))) goto CONTINUE; // check that we're not already connected to addr: @@ -182,13 +181,6 @@ bool PeerSession::interpret(RLP const& _r) for (auto i: m_server->m_addresses) if (ep.address() == i && ep.port() == m_server->listenPort()) goto CONTINUE; - for (auto i: m_server->m_peers) - if (shared_ptr p = i.second.lock()) - { - clogS(NetAllDetail) << " ...against " << p->endpoint(); - if (p->m_socket.is_open() && p->endpoint() == ep) - goto CONTINUE; - } for (auto i: m_server->m_incomingPeers) if (i.second.first == ep) goto CONTINUE; @@ -492,14 +484,6 @@ void PeerSession::dropped() m_socket.close(); } catch (...) {} - - // Remove from peer server - for (auto i = m_server->m_peers.begin(); i != m_server->m_peers.end(); ++i) - if (i->second.lock().get() == this) - { - m_server->m_peers.erase(i); - break; - } } void PeerSession::disconnect(int _reason) diff --git a/libethereum/TransactionQueue.cpp b/libethereum/TransactionQueue.cpp index 9a8c4c20a..750243de2 100644 --- a/libethereum/TransactionQueue.cpp +++ b/libethereum/TransactionQueue.cpp @@ -31,9 +31,7 @@ bool TransactionQueue::import(bytesConstRef _block) { // Check if we already know this transaction. h256 h = sha3(_block); - - UpgradableGuard l(x_data); - if (m_data.count(h)) + if (m_known.count(h)) return false; try @@ -44,8 +42,7 @@ bool TransactionQueue::import(bytesConstRef _block) auto s = t.sender(); // If valid, append to blocks. - UpgradeGuard ul(l); - m_data[h] = _block.toBytes(); + m_current[h] = _block.toBytes(); } catch (InvalidTransactionFormat const& _e) { @@ -63,20 +60,36 @@ bool TransactionQueue::import(bytesConstRef _block) void TransactionQueue::setFuture(std::pair const& _t) { - UpgradableGuard l(x_data); - if (m_data.count(_t.first)) + if (m_current.count(_t.first)) { - UpgradeGuard ul(l); - m_data.erase(_t.first); + m_current.erase(_t.first); m_future.insert(make_pair(Transaction(_t.second).sender(), _t)); } } void TransactionQueue::noteGood(std::pair const& _t) { - WriteGuard l(x_data); auto r = m_future.equal_range(Transaction(_t.second).sender()); for (auto it = r.first; it != r.second; ++it) - m_data.insert(_t); + m_current.insert(it->second); m_future.erase(r.first, r.second); } + +void TransactionQueue::drop(h256 _txHash) +{ + WriteGuard l(m_lock); + if (!m_known.erase(_txHash)) + return; + + if (m_current.count(_txHash)) + m_current.erase(_txHash); + else + { + for (auto i = m_future.begin(); i != m_future.end(); ++i) + if (i->second.first == _txHash) + { + m_future.erase(i); + break; + } + } +} diff --git a/libethereum/TransactionQueue.h b/libethereum/TransactionQueue.h index d3ad354cc..2c6556a71 100644 --- a/libethereum/TransactionQueue.h +++ b/libethereum/TransactionQueue.h @@ -38,21 +38,22 @@ class BlockChain; class TransactionQueue { public: - bool attemptImport(bytesConstRef _tx) { try { import(_block); return true; } catch (...) { return false; } } - bool attemptImport(bytes const& _tx) { return attemptImport(&_block); } + bool attemptImport(bytesConstRef _tx) { try { import(_tx); return true; } catch (...) { return false; } } + bool attemptImport(bytes const& _tx) { return attemptImport(&_tx); } bool import(bytesConstRef _tx); - void drop(h256 _txHash) { WriteGuard l(x_data); m_data.erase(_txHash); } + void drop(h256 _txHash); - std::map transactions() const { ReadGuard l(x_data); return m_data; } + std::map transactions() const { ReadGuard l(m_lock); return m_current; } + std::pair items() const { ReadGuard l(m_lock); return std::make_pair(m_current.size(), m_future.size()); } void setFuture(std::pair const& _t); void noteGood(std::pair const& _t); private: - std::map m_data; ///< Map of SHA3(tx) to tx. - boost::shared_mutex x_data; - + mutable boost::shared_mutex m_lock; ///< General lock. + std::set m_known; ///< Hashes of transactions in both sets. + std::map m_current; ///< Map of SHA3(tx) to tx. std::multimap> m_future; ///< For transactions that have a future nonce; we map their sender address to the tx stuff, and insert once the sender has a valid TX. }; From d58fd1b926f237078c360cd5989221d6f56bfe8d Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 25 Jul 2014 21:17:15 +0200 Subject: [PATCH 015/223] Minor GUI fix. --- alethzero/MainWin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index 1e0eb3c74..471d37411 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -391,7 +391,7 @@ void Main::eval(QString const& _js) if (_js.trimmed().isEmpty()) return; QVariant ev = ui->webView->page()->currentFrame()->evaluateJavaScript("___RET=(" + _js + ")"); - QVariant jsonEv = ui->webView->page()->currentFrame()->evaluateJavaScript("JSON.stringify(__RET)"); + QVariant jsonEv = ui->webView->page()->currentFrame()->evaluateJavaScript("JSON.stringify(___RET)"); QString s; if (ev.isNull()) s = "null"; From 12d34371031c5ef618000b0ab87666a69bc59b2f Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 25 Jul 2014 21:23:24 +0200 Subject: [PATCH 016/223] Version bump. --- libethential/Common.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libethential/Common.cpp b/libethential/Common.cpp index 50ce71a40..070210abd 100644 --- a/libethential/Common.cpp +++ b/libethential/Common.cpp @@ -27,6 +27,6 @@ using namespace eth; namespace eth { -char const* EthVersion = "0.6.0"; +char const* EthVersion = "0.6.1"; } From 34ab7e82315b1e9dfbbd36b8e4286762e74e7922 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 26 Jul 2014 12:31:24 +0200 Subject: [PATCH 017/223] Avoid recursion in TransactionQueue. --- libethereum/BlockQueue.cpp | 24 +++++++++++---------- libethereum/Transaction.cpp | 37 ++++++++++++++++++-------------- libethereum/Transaction.h | 11 ++++++---- libethereum/TransactionQueue.cpp | 12 +++++------ 4 files changed, 47 insertions(+), 37 deletions(-) diff --git a/libethereum/BlockQueue.cpp b/libethereum/BlockQueue.cpp index b688ae186..da21e6929 100644 --- a/libethereum/BlockQueue.cpp +++ b/libethereum/BlockQueue.cpp @@ -85,18 +85,20 @@ bool BlockQueue::import(bytesConstRef _block, BlockChain const& _bc) return true; } -void BlockQueue::noteReadyWithoutWriteGuard(h256 _b) +void BlockQueue::noteReadyWithoutWriteGuard(h256 _good) { - auto r = m_future.equal_range(_b); - h256s good; - for (auto it = r.first; it != r.second; ++it) + h256s goodQueue(1, _good); + while (goodQueue.size()) { - m_futureSet.erase(it->second.first); - m_ready.push_back(it->second.second); - m_readySet.erase(it->second.first); - good.push_back(it->second.first); + auto r = m_future.equal_range(goodQueue.back()); + goodQueue.pop_back(); + for (auto it = r.first; it != r.second; ++it) + { + m_futureSet.erase(it->second.first); + m_ready.push_back(it->second.second); + m_readySet.erase(it->second.first); + goodQueue.push_back(it->second.first); + } + m_future.erase(r.first, r.second); } - m_future.erase(r.first, r.second); - for (auto g: good) - noteReadyWithoutWriteGuard(g); } diff --git a/libethereum/Transaction.cpp b/libethereum/Transaction.cpp index b60cf115e..887458a63 100644 --- a/libethereum/Transaction.cpp +++ b/libethereum/Transaction.cpp @@ -29,7 +29,7 @@ using namespace eth; #define ETH_ADDRESS_DEBUG 0 -Transaction::Transaction(bytesConstRef _rlpData) +Transaction::Transaction(bytesConstRef _rlpData, bool _checkSender) { int field = 0; RLP rlp(_rlpData); @@ -42,6 +42,8 @@ Transaction::Transaction(bytesConstRef _rlpData) value = rlp[field = 4].toInt(); data = rlp[field = 5].toBytes(); vrs = Signature{ rlp[field = 6].toInt(), rlp[field = 7].toInt(), rlp[field = 8].toInt() }; + if (_checkSender) + m_sender = sender(); } catch (RLPException const&) { @@ -63,27 +65,30 @@ Address Transaction::safeSender() const noexcept Address Transaction::sender() const { - secp256k1_start(); + if (!m_sender) + { + secp256k1_start(); - h256 sig[2] = { vrs.r, vrs.s }; - h256 msg = sha3(false); + h256 sig[2] = { vrs.r, vrs.s }; + h256 msg = sha3(false); - byte pubkey[65]; - int pubkeylen = 65; - if (!secp256k1_ecdsa_recover_compact(msg.data(), 32, sig[0].data(), pubkey, &pubkeylen, 0, (int)vrs.v - 27)) - throw InvalidSignature(); + byte pubkey[65]; + int pubkeylen = 65; + if (!secp256k1_ecdsa_recover_compact(msg.data(), 32, sig[0].data(), pubkey, &pubkeylen, 0, (int)vrs.v - 27)) + throw InvalidSignature(); - // TODO: check right160 is correct and shouldn't be left160. - auto ret = right160(eth::sha3(bytesConstRef(&(pubkey[1]), 64))); + // TODO: check right160 is correct and shouldn't be left160. + m_sender = right160(eth::sha3(bytesConstRef(&(pubkey[1]), 64))); #if ETH_ADDRESS_DEBUG - cout << "---- RECOVER -------------------------------" << endl; - cout << "MSG: " << msg << endl; - cout << "R S V: " << sig[0] << " " << sig[1] << " " << (int)(vrs.v - 27) << "+27" << endl; - cout << "PUB: " << toHex(bytesConstRef(&(pubkey[1]), 64)) << endl; - cout << "ADR: " << ret << endl; + cout << "---- RECOVER -------------------------------" << endl; + cout << "MSG: " << msg << endl; + cout << "R S V: " << sig[0] << " " << sig[1] << " " << (int)(vrs.v - 27) << "+27" << endl; + cout << "PUB: " << toHex(bytesConstRef(&(pubkey[1]), 64)) << endl; + cout << "ADR: " << ret << endl; #endif - return ret; + } + return m_sender; } void Transaction::sign(Secret _priv) diff --git a/libethereum/Transaction.h b/libethereum/Transaction.h index e3384c540..9e0ab3009 100644 --- a/libethereum/Transaction.h +++ b/libethereum/Transaction.h @@ -38,8 +38,8 @@ struct Signature struct Transaction { Transaction() {} - Transaction(bytesConstRef _rlp); - Transaction(bytes const& _rlp): Transaction(&_rlp) {} + Transaction(bytesConstRef _rlp, bool _checkSender = false); + Transaction(bytes const& _rlp, bool _checkSender = false): Transaction(&_rlp, _checkSender) {} bool operator==(Transaction const& _c) const { return receiveAddress == _c.receiveAddress && value == _c.value && data == _c.data; } bool operator!=(Transaction const& _c) const { return !operator==(_c); } @@ -55,8 +55,8 @@ struct Transaction Signature vrs; ///< The signature of the transaction. Encodes the sender. Address safeSender() const noexcept; ///< Like sender() but will never throw. - Address sender() const; ///< Determine the sender of the transaction from the signature (and hash). - void sign(Secret _priv); ///< Sign the transaction. + Address sender() const; ///< Determine the sender of the transaction from the signature (and hash). + void sign(Secret _priv); ///< Sign the transaction. bool isCreation() const { return !receiveAddress; } @@ -67,6 +67,9 @@ struct Transaction std::string rlpString(bool _sig = true) const { return asString(rlp(_sig)); } h256 sha3(bool _sig = true) const { RLPStream s; fillStream(s, _sig); return eth::sha3(s.out()); } bytes sha3Bytes(bool _sig = true) const { RLPStream s; fillStream(s, _sig); return eth::sha3Bytes(s.out()); } + +private: + mutable Address m_sender; }; using Transactions = std::vector; diff --git a/libethereum/TransactionQueue.cpp b/libethereum/TransactionQueue.cpp index 750243de2..0181f7ba0 100644 --- a/libethereum/TransactionQueue.cpp +++ b/libethereum/TransactionQueue.cpp @@ -27,22 +27,22 @@ using namespace std; using namespace eth; -bool TransactionQueue::import(bytesConstRef _block) +bool TransactionQueue::import(bytesConstRef _transactionRLP) { // Check if we already know this transaction. - h256 h = sha3(_block); + h256 h = sha3(_transactionRLP); if (m_known.count(h)) return false; 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. + // Check validity of _transactionRLP 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); - auto s = t.sender(); + Transaction t(_transactionRLP, true); // If valid, append to blocks. - m_current[h] = _block.toBytes(); + m_current[h] = _transactionRLP.toBytes(); } catch (InvalidTransactionFormat const& _e) { From a51159ef245a09a0b0e3711ae578af334086153f Mon Sep 17 00:00:00 2001 From: Tim Hughes Date: Sat, 26 Jul 2014 14:25:31 +0100 Subject: [PATCH 018/223] VS2013 fixes. --- libethereum/BlockChain.cpp | 2 ++ libethereum/Client.h | 4 ++-- libethereum/PeerServer.cpp | 2 +- libethereum/PeerServer.h | 2 +- windows/LibEthereum.vcxproj | 6 ++++++ windows/LibEthereum.vcxproj.filters | 18 ++++++++++++++++++ windows/LibQEthereum.props | 1 + windows/LibQEthereum.vcxproj | 19 +++++++++++++++++++ windows/LibQEthereum.vcxproj.filters | 2 ++ 9 files changed, 52 insertions(+), 4 deletions(-) diff --git a/libethereum/BlockChain.cpp b/libethereum/BlockChain.cpp index f26304db2..290afc589 100644 --- a/libethereum/BlockChain.cpp +++ b/libethereum/BlockChain.cpp @@ -170,6 +170,7 @@ h256s BlockChain::sync(BlockQueue& _bq, OverlayDB const& _stateDB, unsigned _max h256s ret; for (auto const& block: blocks) + { try { for (auto h: import(block, _stateDB)) @@ -184,6 +185,7 @@ h256s BlockChain::sync(BlockQueue& _bq, OverlayDB const& _stateDB, unsigned _max _bq.import(&block, *this); } catch (...){} + } return ret; } diff --git a/libethereum/Client.h b/libethereum/Client.h index 008cf0c52..62b1b4fcf 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -221,8 +221,8 @@ public: unsigned installWatch(TransactionFilter const& _filter); unsigned installWatch(h256 _filterId); void uninstallWatch(unsigned _watchId); - bool peekWatch(unsigned _watchId) const { std::lock_guard l(m_filterLock); try { return m_watches.at(_watchId).changes; } catch (...) { return 0; } } - bool checkWatch(unsigned _watchId) { std::lock_guard l(m_filterLock); bool ret = false; try { ret = m_watches.at(_watchId).changes; m_watches.at(_watchId).changes = 0; } catch (...) {} return ret; } + bool peekWatch(unsigned _watchId) const { std::lock_guard l(m_filterLock); try { return m_watches.at(_watchId).changes != 0; } catch (...) { return false; } } + bool checkWatch(unsigned _watchId) { std::lock_guard l(m_filterLock); bool ret = false; try { ret = m_watches.at(_watchId).changes != 0; m_watches.at(_watchId).changes = 0; } catch (...) {} return ret; } // [EXTRA API]: diff --git a/libethereum/PeerServer.cpp b/libethereum/PeerServer.cpp index 73959f8f7..ccc044f93 100644 --- a/libethereum/PeerServer.cpp +++ b/libethereum/PeerServer.cpp @@ -132,7 +132,7 @@ void PeerServer::disconnectPeers() if (!n) break; m_ioService.poll(); - usleep(100000); + this_thread::sleep_for(chrono::milliseconds(100)); } delete m_upnp; diff --git a/libethereum/PeerServer.h b/libethereum/PeerServer.h index ccb73eab1..8439cce79 100644 --- a/libethereum/PeerServer.h +++ b/libethereum/PeerServer.h @@ -77,7 +77,7 @@ public: /// This won't touch alter the blockchain. void process() { if (isInitialised()) m_ioService.poll(); } - bool havePeer(Public _id) const { Guard l(x_peers); return m_peers.count(_id); } + bool havePeer(Public _id) const { Guard l(x_peers); return m_peers.count(_id) != 0; } /// Set ideal number of peers. void setIdealPeerCount(unsigned _n) { m_idealPeerCount = _n; } diff --git a/windows/LibEthereum.vcxproj b/windows/LibEthereum.vcxproj index d4904ff52..c668fcb8e 100644 --- a/windows/LibEthereum.vcxproj +++ b/windows/LibEthereum.vcxproj @@ -42,10 +42,13 @@ + + + @@ -94,10 +97,13 @@ + + + diff --git a/windows/LibEthereum.vcxproj.filters b/windows/LibEthereum.vcxproj.filters index c4478f979..8421df84a 100644 --- a/windows/LibEthereum.vcxproj.filters +++ b/windows/LibEthereum.vcxproj.filters @@ -115,6 +115,15 @@ liblll + + libethereum + + + libethereum + + + libethereum + @@ -258,6 +267,15 @@ liblll + + libethereum + + + libethereum + + + libethereum + diff --git a/windows/LibQEthereum.props b/windows/LibQEthereum.props index bdf8eadae..08ba05c69 100644 --- a/windows/LibQEthereum.props +++ b/windows/LibQEthereum.props @@ -17,6 +17,7 @@ ..;$(QtInclude);%(AdditionalIncludeDirectories) 4718;%(DisableSpecificWarnings) + ETH_QTQML=1;%(PreprocessorDefinitions) diff --git a/windows/LibQEthereum.vcxproj b/windows/LibQEthereum.vcxproj index 24b0643a6..20b29e06e 100644 --- a/windows/LibQEthereum.vcxproj +++ b/windows/LibQEthereum.vcxproj @@ -25,6 +25,7 @@ + Create Create @@ -53,6 +54,24 @@ + + $(Lua) moc.lua "$(QtBin)/moc" "$(IntDir)moc_%(FileName).cpp" "@(ClCompile->'%(AdditionalIncludeDirectories)');$(IncludePath)" "@(ClCompile->'%(PreprocessorDefinitions)');_MSC_VER=1800" "%(FullPath)" + + + $(Lua) moc.lua "$(QtBin)/moc" "$(IntDir)moc_%(FileName).cpp" "@(ClCompile->'%(AdditionalIncludeDirectories)');$(IncludePath)" "@(ClCompile->'%(PreprocessorDefinitions)');_MSC_VER=1800" "%(FullPath)" + + + $(Lua) moc.lua "$(QtBin)/moc" "$(IntDir)moc_%(FileName).cpp" "@(ClCompile->'%(AdditionalIncludeDirectories)');$(IncludePath)" "@(ClCompile->'%(PreprocessorDefinitions)');_MSC_VER=1800" "%(FullPath)" + + + $(Lua) moc.lua "$(QtBin)/moc" "$(IntDir)moc_%(FileName).cpp" "@(ClCompile->'%(AdditionalIncludeDirectories)');$(IncludePath)" "@(ClCompile->'%(PreprocessorDefinitions)');_MSC_VER=1800" "%(FullPath)" + + + $(IntDir)moc_%(FileName).cpp + $(IntDir)moc_%(FileName).cpp + $(IntDir)moc_%(FileName).cpp + $(IntDir)moc_%(FileName).cpp + diff --git a/windows/LibQEthereum.vcxproj.filters b/windows/LibQEthereum.vcxproj.filters index b3ef93314..e38730504 100644 --- a/windows/LibQEthereum.vcxproj.filters +++ b/windows/LibQEthereum.vcxproj.filters @@ -10,11 +10,13 @@ Windows + Windows + From f8edff3f54a888ed9afae6f3fc2075820058cc7e Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 27 Jul 2014 11:20:43 +0200 Subject: [PATCH 019/223] Fixes and whatnot. --- alethzero/MainWin.cpp | 26 ++++++++++++++++++++++---- libethereum/Client.cpp | 2 ++ liblll/CompilerState.cpp | 2 ++ libpyserpent/pyserpent.cpp | 6 +++--- libserpent/rewriter.cpp | 2 +- stdserv.js | 12 ++++++------ 6 files changed, 36 insertions(+), 14 deletions(-) diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index 471d37411..a929ff6d3 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -1299,8 +1299,9 @@ void Main::on_data_textChanged() QString s = ui->data->toPlainText(); while (s.size()) { - QRegExp r("(@|\\$)?\"([^\"]*)\"(.*)"); - QRegExp h("(@|\\$)?(0x)?(([a-fA-F0-9])+)(.*)"); + QRegExp r("(@|\\$)?\"([^\"]*)\"(\\s.*)?"); + QRegExp d("(@|\\$)?([0-9]+)(\\s*(ether)|(finney)|(szabo))?(\\s.*)?"); + QRegExp h("(@|\\$)?(0x)?(([a-fA-F0-9])+)(\\s.*)?"); if (r.exactMatch(s)) { for (auto i: r.cap(2)) @@ -1312,6 +1313,23 @@ void Main::on_data_textChanged() m_data.push_back(0); s = r.cap(3); } + else if (d.exactMatch(s)) + { + u256 v(d.cap(2).toStdString()); + if (d.cap(6) == "szabo") + v *= eth::szabo; + else if (d.cap(5) == "finney") + v *= eth::finney; + else if (d.cap(4) == "ether") + v *= eth::ether; + bytes bs = eth::toCompactBigEndian(v); + if (d.cap(1) != "$") + for (auto i = bs.size(); i < 32; ++i) + m_data.push_back(0); + for (auto b: bs) + m_data.push_back(b); + s = d.cap(7); + } else if (h.exactMatch(s)) { bytes bs = fromHex((((h.cap(3).size() & 1) ? "0" : "") + h.cap(3)).toStdString()); @@ -1679,9 +1697,9 @@ QString Main::prettyU256(eth::u256 _n) const QString raw; ostringstream s; if (!(_n >> 64)) - s << "0x" << (uint64_t)_n << ""; + s << "" << (uint64_t)_n << " (0x" << hex << (uint64_t)_n << ")"; else if (!~(_n >> 64)) - s << "0x" << (int64_t)_n << ""; + s << "" << (int64_t)_n << " (0x" << hex << (int64_t)_n << ")"; else if (_n >> 200 == 0) { Address a = right160(_n); diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index 2b2d1211e..37512d429 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -25,6 +25,8 @@ #include #include #include +#include +#include #include "Defaults.h" #include "PeerServer.h" using namespace std; diff --git a/liblll/CompilerState.cpp b/liblll/CompilerState.cpp index 380d5a66f..01995674f 100644 --- a/liblll/CompilerState.cpp +++ b/liblll/CompilerState.cpp @@ -62,6 +62,8 @@ void CompilerState::populateStandard() "(def 'create (value code) { [0]:(msize) (create value @0 (lll code @0)) })" "(def 'create (code) { [0]:(msize) (create 0 @0 (lll code @0)) })" "(def 'sha3 (val) { [0]:val (sha3 0 32) })" + "(def 'sha3pair (a b) { [0]:a [32]:b (sha3 0 64) })" + "(def 'sha3trip (a b c) { [0]:a [32]:b [64]:c (sha3 0 96) })" "(def 'return (val) { [0]:val (return 0 32) })" "(def 'returnlll (code) (return 0 (lll code 0)) )" "(def 'makeperm (name pos) { (def name (sload pos)) (def name (v) (sstore pos v)) } )" diff --git a/libpyserpent/pyserpent.cpp b/libpyserpent/pyserpent.cpp index 64fa82ded..ba3750b51 100644 --- a/libpyserpent/pyserpent.cpp +++ b/libpyserpent/pyserpent.cpp @@ -7,7 +7,7 @@ #include #define PYMETHOD(name, FROM, method, TO) \ - static PyObject * name(PyObject *self, PyObject *args) { \ + static PyObject * name(PyObject *, PyObject *args) { \ FROM(med) \ return TO(method(med)); \ } @@ -128,7 +128,7 @@ PYMETHOD(ps_parse_lll, FROMSTR, parseLLL, pyifyNode) static PyMethodDef PyextMethods[] = { {"compile", ps_compile, METH_VARARGS, "Compile code."}, - {"compile_to_lll", ps_parse, METH_VARARGS, + {"compile_to_lll", ps_compile_to_lll, METH_VARARGS, "Compile code to LLL."}, {"compile_lll", ps_compile_lll, METH_VARARGS, "Compile LLL to EVM."}, @@ -151,5 +151,5 @@ static PyMethodDef PyextMethods[] = { PyMODINIT_FUNC initpyext(void) { - PyObject *m = Py_InitModule( "pyext", PyextMethods ); + Py_InitModule( "pyext", PyextMethods ); } diff --git a/libserpent/rewriter.cpp b/libserpent/rewriter.cpp index 00712b871..8b50c9a28 100644 --- a/libserpent/rewriter.cpp +++ b/libserpent/rewriter.cpp @@ -411,7 +411,7 @@ Node apply_rules(Node node) { node.args[0].val = "'" + node.args[0].val; i = 1; } - for (i = i; i < node.args.size(); i++) { + for (; i < node.args.size(); i++) { node.args[i] = apply_rules(node.args[i]); } } diff --git a/stdserv.js b/stdserv.js index 92f075a1b..907455eb4 100644 --- a/stdserv.js +++ b/stdserv.js @@ -92,10 +92,10 @@ var gavCoinCode = eth.lll(" (returnlll { (when (&& (= $0 'kill) (= (caller) @@0x69)) (suicide (caller))) (when (= $0 'balance) (return @@$32)) - (when (= $0 'approved) (return @@ @(sha3 (^ (if (= (calldatasize) 64) (caller) $64) $32))) ) + (when (= $0 'approved) (return @@ (sha3pair (if (= (calldatasize) 64) (caller) $64) $32)) ) (when (= $0 'approve) { - [[@(sha3 (^ (caller) $32))]] $32 + [[(sha3pair (caller) $32)]] $32 (stop) }) @@ -103,18 +103,18 @@ var gavCoinCode = eth.lll(" (set 'fromVar (if (= (calldatasize) 96) (caller) { - (when (! @@ @(sha3 (^ $96 $32)) ) (stop)) - $96 + (when (! @@ (sha3pair (origin) (caller))) (return 0)) + (origin) } )) (def 'to $32) (def 'value $64) (def 'from (get 'fromVar)) (set 'fromBal @@from) - (when (< @fromBal value) (stop)) + (when (< @fromBal value) (return 0)) [[ from ]]: (- @fromBal value) [[ to ]]: (+ @@to value) - (stop) + (return 1) }) (set 'n @@0x42) From f550217449aed43a242173f9429406ab833330c6 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 27 Jul 2014 13:09:36 +0200 Subject: [PATCH 020/223] Updates to assembler - see the sub-codes. --- eth/main.cpp | 13 +++- libethential/FixedHash.cpp | 3 + libethential/FixedHash.h | 6 +- libethential/Log.h | 2 +- libethereum/Client.cpp | 26 ++++---- libethereum/Client.h | 6 ++ liblll/Assembly.cpp | 74 +++++++++++++++++---- liblll/Assembly.h | 12 ++-- liblll/CodeFragment.cpp | 13 +--- liblll/CodeFragment.h | 8 +-- liblll/Compiler.cpp | 9 +-- neth/main.cpp | 6 +- stdserv.js | 130 +++++++++++++++++++++++++++++++++++++ 13 files changed, 242 insertions(+), 66 deletions(-) diff --git a/eth/main.cpp b/eth/main.cpp index cc845cf3f..dbb0234ce 100644 --- a/eth/main.cpp +++ b/eth/main.cpp @@ -169,7 +169,7 @@ string pretty(h160 _a, eth::State _st) } return ns; } -bytes parse_data(string _args); +bytes parseData(string _args); int main(int argc, char** argv) { unsigned short listenPort = 30303; @@ -309,9 +309,16 @@ int main(int argc, char** argv) if (interactive) { + string logbuf; string l; while (true) { + g_logPost = [](std::string const& a, char const*) { cout << "\r \r" << a << endl << "Press Enter" << flush; }; + cout << logbuf << "Press Enter" << flush; + std::getline(cin, l); + logbuf.clear(); + g_logPost = [&](std::string const& a, char const*) { logbuf += a + "\n"; }; + #if ETH_READLINE if (l.size()) add_history(l.c_str()); @@ -434,7 +441,7 @@ int main(int argc, char** argv) cnote << "Data:"; cnote << sdata; - bytes data = parse_data(sdata); + bytes data = parseData(sdata); cnote << "Bytes:"; string sbd = asString(data); bytes bbd = asBytes(sbd); @@ -695,7 +702,7 @@ int main(int argc, char** argv) return 0; } -bytes parse_data(string _args) +bytes parseData(string _args) { bytes m_data; stringstream args(_args); diff --git a/libethential/FixedHash.cpp b/libethential/FixedHash.cpp index 9d16cacb2..885d593e5 100644 --- a/libethential/FixedHash.cpp +++ b/libethential/FixedHash.cpp @@ -19,7 +19,10 @@ * @date 2014 */ +#include #include "FixedHash.h" using namespace std; using namespace eth; + +std::mt19937_64 eth::s_fixedHashEngine(time(0)); diff --git a/libethential/FixedHash.h b/libethential/FixedHash.h index 07e257534..2e3a92ce4 100644 --- a/libethential/FixedHash.h +++ b/libethential/FixedHash.h @@ -31,6 +31,8 @@ namespace eth { +extern std::mt19937_64 s_fixedHashEngine; + /// Fixed-size raw-byte array container type, with an API optimised for storing hashes. /// Transparently converts to/from the corresponding arithmetic type; this will /// assume the data contained in the hash is big-endian. @@ -125,7 +127,7 @@ public: /// @returns a randomly-valued hash template - static FixedHash random(Engine& _eng) + static FixedHash random(Engine& _eng = s_fixedHashEngine) { FixedHash ret; for (auto& i: ret.m_data) @@ -154,12 +156,10 @@ public: return ret; } - private: std::array m_data; ///< The binary data. }; - /// Fast equality operator for h256. template<> inline bool FixedHash<32>::operator==(FixedHash<32> const& _other) const { diff --git a/libethential/Log.h b/libethential/Log.h index ea7a06233..77a84333c 100644 --- a/libethential/Log.h +++ b/libethential/Log.h @@ -39,7 +39,7 @@ public: }; /// A simple log-output function that prints log messages to stdout. -void simpleDebugOut(std::string const&, char const* ); +void simpleDebugOut(std::string const&, char const*); /// The logging system's current verbosity. extern int g_logVerbosity; diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index 37512d429..841ef00ad 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -307,7 +307,7 @@ void Client::inject(bytesConstRef _rlp) void Client::work(bool _justQueue) { - cdebug << ">>> WORK"; + cworkin << "WORK"; h256Set changeds; // Process network events. @@ -317,14 +317,14 @@ void Client::work(bool _justQueue) Guard l(x_net); if (m_net && !_justQueue) { - cdebug << "--- WORK: NETWORK"; + cwork << "NETWORK"; m_net->process(); // must be in guard for now since it uses the blockchain. // returns h256Set as block hashes, once for each block that has come in/gone out. - cdebug << "--- WORK: NET <==> TQ ; CHAIN ==> NET ==> BQ"; + cwork << "NET <==> TQ ; CHAIN ==> NET ==> BQ"; m_net->sync(m_tq, m_bq); - cdebug << "--- TQ:" << m_tq.items() << "; BQ:" << m_bq.items(); + cwork << "TQ:" << m_tq.items() << "; BQ:" << m_bq.items(); } } @@ -361,7 +361,7 @@ void Client::work(bool _justQueue) if (m_doMine) { - cdebug << "--- WORK: MINE"; + cwork << "MINE"; m_restartMining = false; // Mine for a while. @@ -377,9 +377,9 @@ void Client::work(bool _justQueue) if (mineInfo.completed) { // Import block. - cdebug << "--- WORK: COMPLETE MINE%"; + cwork << "COMPLETE MINE"; m_postMine.completeMine(); - cdebug << "--- WORK: CHAIN <== postSTATE"; + cwork << "CHAIN <== postSTATE"; h256s hs = m_bc.attemptImport(m_postMine.blockData(), m_stateDB); if (hs.size()) { @@ -392,7 +392,7 @@ void Client::work(bool _justQueue) } else { - cdebug << "--- WORK: SLEEP"; + cwork << "SLEEP"; this_thread::sleep_for(chrono::milliseconds(100)); } } @@ -407,7 +407,7 @@ void Client::work(bool _justQueue) { ClientGuard l(this); - cdebug << "--- WORK: BQ ==> CHAIN ==> STATE"; + cwork << "BQ ==> CHAIN ==> STATE"; OverlayDB db = m_stateDB; m_lock.unlock(); h256s newBlocks = m_bc.sync(m_bq, db, 100); @@ -421,7 +421,7 @@ void Client::work(bool _justQueue) if (newBlocks.size()) m_stateDB = db; - cdebug << "--- WORK: preSTATE <== CHAIN"; + cwork << "preSTATE <== CHAIN"; if (m_preMine.sync(m_bc) || m_postMine.address() != m_preMine.address()) { if (m_doMine) @@ -432,7 +432,7 @@ void Client::work(bool _justQueue) } // returns h256s as blooms, once for each transaction. - cdebug << "--- WORK: postSTATE <== TQ"; + cwork << "postSTATE <== TQ"; h256s newPendingBlooms = m_postMine.sync(m_tq); if (newPendingBlooms.size()) { @@ -446,9 +446,9 @@ void Client::work(bool _justQueue) } } - cdebug << "--- WORK: noteChanged" << changeds.size() << "items"; + cwork << "noteChanged" << changeds.size() << "items"; noteChanged(changeds); - cdebug << "<<< WORK"; + cworkout << "WORK"; } void Client::lock() const diff --git a/libethereum/Client.h b/libethereum/Client.h index 62b1b4fcf..ee0bd145a 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -159,6 +159,12 @@ struct Watch struct WatchChannel: public LogChannel { static const char* name() { return "(o)"; } static const int verbosity = 6; }; #define cwatch eth::LogOutputStream() +struct WorkInChannel: public LogChannel { static const char* name() { return ">W>"; } static const int verbosity = 5; }; +struct WorkOutChannel: public LogChannel { static const char* name() { return "() +#define cworkin eth::LogOutputStream() +#define cworkout eth::LogOutputStream() /** * @brief Main API hub for interfacing with Ethereum. diff --git a/liblll/Assembly.cpp b/liblll/Assembly.cpp index 3abdf66f7..78552f3c5 100644 --- a/liblll/Assembly.cpp +++ b/liblll/Assembly.cpp @@ -32,7 +32,7 @@ int AssemblyItem::deposit() const { case Operation: return c_instructionInfo.at((Instruction)(byte)m_data).ret - c_instructionInfo.at((Instruction)(byte)m_data).args; - case Push: case PushString: case PushTag: case PushData: + case Push: case PushString: case PushTag: case PushData: case PushSub: case PushSubSize: return 1; case Tag: return 0; @@ -61,8 +61,12 @@ unsigned Assembly::bytesRequired() const case Push: ret += 1 + max(1, eth::bytesRequired(i.m_data)); break; + case PushSubSize: + ret += 4; // worst case: a 16MB program + break; case PushTag: case PushData: + case PushSub: ret += 1 + br; case Tag:; default:; @@ -87,6 +91,8 @@ void Assembly::append(Assembly const& _a) m_data.insert(i); for (auto const& i: _a.m_strings) m_strings.insert(i); + for (auto const& i: _a.m_subs) + m_subs.insert(i); assert(!_a.m_baseDeposit); assert(!_a.m_totalDeposit); @@ -127,6 +133,12 @@ ostream& eth::operator<<(ostream& _out, AssemblyItemsConstRef _i) case PushData: _out << " PUSH*[" << hex << (unsigned)i.data() << "]"; break; + case PushSub: + _out << " PUSHs[" << hex << h256(i.data()).abridged() << "]"; + break; + case PushSubSize: + _out << " PUSHss[" << hex << h256(i.data()).abridged() << "]"; + break; case UndefinedItem: _out << " ???"; default:; @@ -134,38 +146,50 @@ ostream& eth::operator<<(ostream& _out, AssemblyItemsConstRef _i) return _out; } -ostream& Assembly::streamOut(ostream& _out) const +ostream& Assembly::streamOut(ostream& _out, string const& _prefix) const { - _out << ".code:" << endl; + _out << _prefix << ".code:" << endl; for (AssemblyItem const& i: m_items) switch (i.m_type) { case Operation: - _out << " " << c_instructionInfo.at((Instruction)(byte)i.m_data).name << endl; + _out << _prefix << " " << c_instructionInfo.at((Instruction)(byte)i.m_data).name << endl; break; case Push: - _out << " PUSH " << i.m_data << endl; + _out << _prefix << " PUSH " << i.m_data << endl; break; case PushString: - _out << " PUSH \"" << m_strings.at((h256)i.m_data) << "\"" << endl; + _out << _prefix << " PUSH \"" << m_strings.at((h256)i.m_data) << "\"" << endl; break; case PushTag: - _out << " PUSH [tag" << i.m_data << "]" << endl; + _out << _prefix << " PUSH [tag" << i.m_data << "]" << endl; + break; + case PushSub: + _out << _prefix << " PUSH [$" << h256(i.m_data).abridged() << "]" << endl; + break; + case PushSubSize: + _out << _prefix << " PUSH #[$" << h256(i.m_data).abridged() << "]" << endl; break; case Tag: - _out << "tag" << i.m_data << ": " << endl; + _out << _prefix << "tag" << i.m_data << ": " << endl; break; case PushData: - _out << " PUSH [" << hex << (unsigned)i.m_data << "]" << endl; + _out << _prefix << " PUSH [" << hex << (unsigned)i.m_data << "]" << endl; break; default:; } - if (m_data.size()) + if (m_data.size() || m_subs.size()) { - _out << ".data:" << endl; + _out << _prefix << ".data:" << endl; for (auto const& i: m_data) - _out << " " << hex << (unsigned)(u256)i.first << ": " << toHex(i.second) << endl; + if (!m_subs.count(i.first)) + _out << _prefix << " " << hex << (unsigned)(u256)i.first << ": " << toHex(i.second) << endl; + for (auto const& i: m_subs) + { + _out << _prefix << " " << hex << (unsigned)(u256)i.first << ": " << endl; + i.second.streamOut(_out, _prefix + " "); + } } return _out; } @@ -195,8 +219,10 @@ inline bool matches(AssemblyItemsConstRef _a, AssemblyItemsConstRef _b) struct OptimiserChannel: public LogChannel { static const char* name() { return "OPT"; } static const int verbosity = 12; }; #define copt eth::LogOutputStream() -void Assembly::optimise() +Assembly& Assembly::optimise(bool _enable) { + if (!_enable) + return *this; map> c_simple = { { Instruction::SUB, [](u256 a, u256 b)->u256{return a - b;} }, @@ -221,6 +247,8 @@ void Assembly::optimise() { { Push, Instruction::POP }, [](AssemblyItemsConstRef) -> AssemblyItems { return {}; } }, { { PushTag, Instruction::POP }, [](AssemblyItemsConstRef) -> AssemblyItems { return {}; } }, { { PushString, Instruction::POP }, [](AssemblyItemsConstRef) -> AssemblyItems { return {}; } }, + { { PushSub, Instruction::POP }, [](AssemblyItemsConstRef) -> AssemblyItems { return {}; } }, + { { PushSubSize, Instruction::POP }, [](AssemblyItemsConstRef) -> AssemblyItems { return {}; } }, { { Push, PushTag, Instruction::JUMPI }, [](AssemblyItemsConstRef m) -> AssemblyItems { if (m[0].data()) return { m[1], Instruction::JUMP }; else return {}; } }, { { Instruction::NOT, Instruction::NOT }, [](AssemblyItemsConstRef) -> AssemblyItems { return {}; } }, }; @@ -309,6 +337,11 @@ void Assembly::optimise() } copt << total << " optimisations done."; + + for (auto& i: m_subs) + i.second.optimise(true); + + return *this; } bytes Assembly::assemble() const @@ -323,6 +356,9 @@ bytes Assembly::assemble() const unsigned bytesPerTag = eth::bytesRequired(totalBytes); byte tagPush = (byte)Instruction::PUSH1 - 1 + bytesPerTag; + for (auto const& i: m_subs) + m_data[i.first] = i.second.assemble(); + for (AssemblyItem const& i: m_items) switch (i.m_type) { @@ -358,13 +394,23 @@ bytes Assembly::assemble() const ret.resize(ret.size() + bytesPerTag); break; } - case PushData: + case PushData: case PushSub: { ret.push_back(tagPush); dataRef.insert(make_pair((h256)i.m_data, ret.size())); ret.resize(ret.size() + bytesPerTag); break; } + case PushSubSize: + { + auto s = m_data[i.m_data].size(); + byte b = max(1, eth::bytesRequired(s)); + ret.push_back((byte)Instruction::PUSH1 - 1 + b); + ret.resize(ret.size() + b); + bytesRef byr(&ret.back() + 1 - b, b); + toBigEndian(s, byr); + break; + } case Tag: tagPos[(unsigned)i.m_data] = ret.size(); break; diff --git a/liblll/Assembly.h b/liblll/Assembly.h index 4a6d02ce0..581e16433 100644 --- a/liblll/Assembly.h +++ b/liblll/Assembly.h @@ -30,7 +30,7 @@ namespace eth { -enum AssemblyItemType { UndefinedItem, Operation, Push, PushString, PushTag, Tag, PushData }; +enum AssemblyItemType { UndefinedItem, Operation, Push, PushString, PushTag, PushSub, PushSubSize, Tag, PushData }; class Assembly; @@ -70,7 +70,9 @@ public: AssemblyItem newTag() { return AssemblyItem(Tag, m_usedTags++); } AssemblyItem newPushTag() { return AssemblyItem(PushTag, m_usedTags++); } AssemblyItem newData(bytes const& _data) { h256 h = (u256)std::hash()(asString(_data)); m_data[h] = _data; return AssemblyItem(PushData, h); } + AssemblyItem newSub(Assembly const& _sub) { h256 h = h256::random(s_fixedHashEngine); m_subs[h] = _sub; return AssemblyItem(PushSub, h); } AssemblyItem newPushString(std::string const& _data) { h256 h = (u256)std::hash()(_data); m_strings[h] = _data; return AssemblyItem(PushString, h); } + AssemblyItem newPushSubSize(h256 const& _subId) { return AssemblyItem(PushSubSize, _subId); } AssemblyItem append() { return append(newTag()); } void append(Assembly const& _a); @@ -78,6 +80,7 @@ public: AssemblyItem const& append(AssemblyItem const& _i); AssemblyItem const& append(std::string const& _data) { return append(newPushString(_data)); } AssemblyItem const& append(bytes const& _data) { return append(newData(_data)); } + AssemblyItem appendSubSize(Assembly const& _asm) { auto ret = newSub(_asm); append(newPushSubSize(ret.data())); return ret; } AssemblyItem appendJump() { auto ret = append(newPushTag()); append(Instruction::JUMP); return ret; } AssemblyItem appendJumpI() { auto ret = append(newPushTag()); append(Instruction::JUMPI); return ret; } @@ -102,8 +105,8 @@ public: std::string out() const { std::stringstream ret; streamOut(ret); return ret.str(); } int deposit() const { return m_deposit; } bytes assemble() const; - void optimise(); - std::ostream& streamOut(std::ostream& _out) const; + Assembly& optimise(bool _enable); + std::ostream& streamOut(std::ostream& _out, std::string const& _prefix = "") const; private: void donePath() { if (m_totalDeposit != INT_MAX && m_totalDeposit != m_deposit) throw InvalidDeposit(); } @@ -111,7 +114,8 @@ private: unsigned m_usedTags = 0; AssemblyItems m_items; - std::map m_data; + mutable std::map m_data; + std::map m_subs; std::map m_strings; int m_deposit = 0; diff --git a/liblll/CodeFragment.cpp b/liblll/CodeFragment.cpp index d19d75090..bc7fa6153 100644 --- a/liblll/CodeFragment.cpp +++ b/liblll/CodeFragment.cpp @@ -44,12 +44,6 @@ void CodeFragment::finalise(CompilerState const& _cs) } } -bytes CodeFragment::code(CompilerState const& _cs) -{ - finalise(_cs); - return m_asm.assemble(); -} - CodeFragment::CodeFragment(sp::utree const& _t, CompilerState& _s, bool _allowASM) { /* cdebug << "CodeFragment. Locals:"; @@ -499,10 +493,7 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s) requireMaxSize(3); requireDeposit(1, 1); - code[0].optimise(); - bytes subcode = code[0].code(ns); - - m_asm.append((u256)subcode.size()); + auto subPush = m_asm.appendSubSize(code[0].assembly(ns)); m_asm.append(Instruction::DUP); if (code.size() == 3) { @@ -513,7 +504,7 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s) m_asm.append(Instruction::MUL); m_asm.append(Instruction::DUP); } - m_asm.append(subcode); + m_asm.append(subPush); m_asm.append(code[1].m_asm, 1); m_asm.append(Instruction::CODECOPY); } diff --git a/liblll/CodeFragment.h b/liblll/CodeFragment.h index 58e409125..98a6f15c7 100644 --- a/liblll/CodeFragment.h +++ b/liblll/CodeFragment.h @@ -43,13 +43,7 @@ public: static CodeFragment compile(std::string const& _src, CompilerState& _s); /// Consolidates data and compiles code. - bytes code(CompilerState const& _cs); - - /// Consolidates data and compiles code. - std::string assembly(CompilerState const& _cs) { finalise(_cs); return m_asm.out(); } - - /// Optimise the code. Best do this just before calling code() or assembly(). - void optimise() { m_asm.optimise(); } + Assembly& assembly(CompilerState const& _cs) { finalise(_cs); return m_asm; } private: void finalise(CompilerState const& _cs); diff --git a/liblll/Compiler.cpp b/liblll/Compiler.cpp index 1621acf90..ebe2638be 100644 --- a/liblll/Compiler.cpp +++ b/liblll/Compiler.cpp @@ -34,9 +34,7 @@ bytes eth::compileLLL(string const& _src, bool _opt, vector* _errors) CompilerState cs; cs.populateStandard(); auto f = CodeFragment::compile(_src, cs); - if (_opt) - f.optimise(); - bytes ret = f.code(cs); + bytes ret = f.assembly(cs).optimise(_opt).assemble(); for (auto i: cs.treesToKill) killBigints(i); return ret; @@ -60,10 +58,7 @@ std::string eth::compileLLLToAsm(std::string const& _src, bool _opt, std::vector { CompilerState cs; cs.populateStandard(); - auto f = CodeFragment::compile(_src, cs); - if (_opt) - f.optimise(); - string ret = f.assembly(cs); + string ret = CodeFragment::compile(_src, cs).assembly(cs).optimise(_opt).out(); for (auto i: cs.treesToKill) killBigints(i); return ret; diff --git a/neth/main.cpp b/neth/main.cpp index 50c35b877..c085736b2 100644 --- a/neth/main.cpp +++ b/neth/main.cpp @@ -298,7 +298,7 @@ int nc_window_streambuf::sync() } vector form_dialog(vector _sfields, vector _lfields, vector _bfields, int _cols, int _rows, string _post_form); -bytes parse_data(string _args); +bytes parseData(string _args); int main(int argc, char** argv) @@ -655,7 +655,7 @@ int main(int argc, char** argv) string sdata = fields[5]; cnote << "Data:"; cnote << sdata; - bytes data = parse_data(sdata); + bytes data = parseData(sdata); cnote << "Bytes:"; string sbd = asString(data); bytes bbd = asBytes(sbd); @@ -1223,7 +1223,7 @@ vector form_dialog(vector _sv, vector _lv, vector= (rateof @item) @irate)) {} { + (set 'offerA (min @xoffer (wantof @item))) + (set 'wantA (/ (* @offerA (rateof @item)) (exp 2 128))) ; CHECK! + + (set 'xoffer (- @xoffer @offerA)) + (set 'xwant (- @xwant @wantA)) + + (deductwant @item @offerA) + + (xfer @offer (idof @item) @offerA) + (xfer @want (caller) @wantA) + + (unless @xoffer (stop)) + + (set 'item @@ @item) + [[ @last ]] @item + }) + + (set 'last @list) + (set 'item @@ @last) + + (set 'newpos (newitem @rate (caller) @xwant)) + + (for {} (&& @item (!= @item @newpos) (>= (rateof @item) @rate)) { (set 'last @item) (inc item) } {}) + (if (= @item @newpos) + (addwant @item @wantx) + (stitchitem @last @newpos) + ) + (stop) + }) + (when (= $0 'delete) { + (set 'offer $32) + (set 'want $64) + (set 'list (sha3pair @offer @want)) + (set 'last @list) + (set 'item @@ @last) + (for {} (&& @item (!= (idof @item) (caller))) { (set 'last @item) (inc item) } {}) + (when @item { + (set 'xoffer (/ (* (wantof @item) (rateof @item)) (exp 2 128))) + [[ @last ]] @@ @item + (xfer @offer (caller) @xoffer) + }) + (stop) + }) + (when (= $0 'price) { + (set 'offer $32) + (set 'want $96) + (set 'item (head (sha3pair @offer @want))) + (return (if @item (rateof @list) 0)) + }) +}) +} +"); + +var exchange; +env.note('Create Exchange...') +eth.create(eth.key, '0', exchangeCode, 10000, eth.gasPrice, function(a) { exchange = a; }); + +env.note('Register Exchange...') +eth.transact(eth.key, '0', config, "2".pad(32) + exchange.pad(32), 10000, eth.gasPrice); + + + + env.note('Register my name...') eth.transact(eth.key, '0', nameReg, "register".pad(32) + "Gav".pad(32), 10000, eth.gasPrice); From 9a1799481f0fafb078b78abcd50fc4043f862ff5 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 27 Jul 2014 13:26:34 +0200 Subject: [PATCH 021/223] TransactionQueue fixed. --- alethzero/Main.ui | 3 +++ libethereum/TransactionQueue.cpp | 14 ++++++++++++-- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/alethzero/Main.ui b/alethzero/Main.ui index b5bb04df6..bb9b4c5be 100644 --- a/alethzero/Main.ui +++ b/alethzero/Main.ui @@ -392,6 +392,9 @@ 0 + + true + diff --git a/libethereum/TransactionQueue.cpp b/libethereum/TransactionQueue.cpp index 0181f7ba0..c2f03ddf1 100644 --- a/libethereum/TransactionQueue.cpp +++ b/libethereum/TransactionQueue.cpp @@ -31,6 +31,8 @@ bool TransactionQueue::import(bytesConstRef _transactionRLP) { // Check if we already know this transaction. h256 h = sha3(_transactionRLP); + + UpgradableGuard l(m_lock); if (m_known.count(h)) return false; @@ -41,8 +43,10 @@ bool TransactionQueue::import(bytesConstRef _transactionRLP) // The transaction's nonce may yet be invalid (or, it could be "valid" but we may be missing a marginally older transaction). Transaction t(_transactionRLP, true); + UpgradeGuard ul(l); // If valid, append to blocks. m_current[h] = _transactionRLP.toBytes(); + m_known.insert(h); } catch (InvalidTransactionFormat const& _e) { @@ -60,6 +64,7 @@ bool TransactionQueue::import(bytesConstRef _transactionRLP) void TransactionQueue::setFuture(std::pair const& _t) { + WriteGuard l(m_lock); if (m_current.count(_t.first)) { m_current.erase(_t.first); @@ -69,6 +74,7 @@ void TransactionQueue::setFuture(std::pair const& _t) void TransactionQueue::noteGood(std::pair const& _t) { + WriteGuard l(m_lock); auto r = m_future.equal_range(Transaction(_t.second).sender()); for (auto it = r.first; it != r.second; ++it) m_current.insert(it->second); @@ -77,10 +83,14 @@ void TransactionQueue::noteGood(std::pair const& _t) void TransactionQueue::drop(h256 _txHash) { - WriteGuard l(m_lock); - if (!m_known.erase(_txHash)) + UpgradableGuard l(m_lock); + + if (!m_known.count(_txHash)) return; + UpgradeGuard ul(l); + m_known.erase(_txHash); + if (m_current.count(_txHash)) m_current.erase(_txHash); else From 2ee1cda66ef010b88a2a3e39eeca85e81235c4c0 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 27 Jul 2014 14:06:38 +0200 Subject: [PATCH 022/223] Avoid _asm from MSVC. --- libethereum/Client.cpp | 2 +- liblll/Assembly.h | 2 +- stdserv.js | 6 ++---- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index 841ef00ad..cc76e0b49 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -410,7 +410,7 @@ void Client::work(bool _justQueue) cwork << "BQ ==> CHAIN ==> STATE"; OverlayDB db = m_stateDB; m_lock.unlock(); - h256s newBlocks = m_bc.sync(m_bq, db, 100); + h256s newBlocks = m_bc.sync(m_bq, db, 100); // TODO: remove transactions from m_tq nicely rather than relying on out of date nonce later on. if (newBlocks.size()) { for (auto i: newBlocks) diff --git a/liblll/Assembly.h b/liblll/Assembly.h index 581e16433..888b373c5 100644 --- a/liblll/Assembly.h +++ b/liblll/Assembly.h @@ -80,7 +80,7 @@ public: AssemblyItem const& append(AssemblyItem const& _i); AssemblyItem const& append(std::string const& _data) { return append(newPushString(_data)); } AssemblyItem const& append(bytes const& _data) { return append(newData(_data)); } - AssemblyItem appendSubSize(Assembly const& _asm) { auto ret = newSub(_asm); append(newPushSubSize(ret.data())); return ret; } + AssemblyItem appendSubSize(Assembly const& _asmbly) { auto ret = newSub(_asmbly); append(newPushSubSize(ret.data())); return ret; } AssemblyItem appendJump() { auto ret = append(newPushTag()); append(Instruction::JUMP); return ret; } AssemblyItem appendJumpI() { auto ret = append(newPushTag()); append(Instruction::JUMPI); return ret; } diff --git a/stdserv.js b/stdserv.js index 3f83ef325..7d0a8da3c 100644 --- a/stdserv.js +++ b/stdserv.js @@ -140,8 +140,6 @@ eth.transact(eth.key, '0', config, "2".pad(32) + gavCoin.pad(32), 10000, eth.gas var exchangeCode = eth.lll(" { - -; init [0] 'register [32] 'Exchange (msg allgas 0x50441127ea5b9dfd835a9aba4e1dc9c1257b58ca 0 0 64) @@ -206,7 +204,7 @@ var exchangeCode = eth.lll(" (for {} (&& @item (>= (rateof @item) @irate)) {} { (set 'offerA (min @xoffer (wantof @item))) - (set 'wantA (/ (* @offerA (rateof @item)) (exp 2 128))) ; CHECK! + (set 'wantA (/ (* @offerA (rateof @item)) (exp 2 128))) (set 'xoffer (- @xoffer @offerA)) (set 'xwant (- @xwant @wantA)) @@ -263,7 +261,7 @@ env.note('Create Exchange...') eth.create(eth.key, '0', exchangeCode, 10000, eth.gasPrice, function(a) { exchange = a; }); env.note('Register Exchange...') -eth.transact(eth.key, '0', config, "2".pad(32) + exchange.pad(32), 10000, eth.gasPrice); +eth.transact(eth.key, '0', config, "3".pad(32) + exchange.pad(32), 10000, eth.gasPrice); From 7b5c5ac46bc1346bc6a95224f776d2f7636583dd Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 27 Jul 2014 15:21:46 +0200 Subject: [PATCH 023/223] Exchange in standard services. --- stdserv.js | 39 ++++++++++++++++++++++++++++----------- 1 file changed, 28 insertions(+), 11 deletions(-) diff --git a/stdserv.js b/stdserv.js index 7d0a8da3c..6d94c397b 100644 --- a/stdserv.js +++ b/stdserv.js @@ -146,12 +146,12 @@ var exchangeCode = eth.lll(" (def 'min (a b) (if (< a b) a b)) -(def 'head (list) @@ list) -(def 'next (item) @@ item) +(def 'head (_list) @@ _list) +(def 'next (_item) @@ _item) (def 'inc (itemref) [itemref]: (next @itemref)) -(def 'rateof (item) @@ (+ item 1)) -(def 'idof (item) @@ (+ item 2)) -(def 'wantof (item) @@ (+ item 3)) +(def 'rateof (_item) @@ (+ _item 1)) +(def 'idof (_item) @@ (+ _item 2)) +(def 'wantof (_item) @@ (+ _item 3)) (def 'newitem (rate who want) { (set 'pos (sha3pair rate who)) [[ (+ @pos 1) ]] rate @@ -163,8 +163,8 @@ var exchangeCode = eth.lll(" [[ pos ]] @@ parent [[ parent ]] pos }) -(def 'addwant (item, amount) [[ (+ item 3) ]] (+ @@ (+ item 3) amount)) -(def 'deductwant (item, amount) [[ (+ item 3) ]] (- @@ (+ item 3) amount)) +(def 'addwant (_item amount) [[ (+ _item 3) ]] (+ @@ (+ _item 3) amount)) +(def 'deductwant (_item amount) [[ (+ _item 3) ]] (- @@ (+ _item 3) amount)) (def 'xfer (contract to amount) (if contract { @@ -177,6 +177,8 @@ var exchangeCode = eth.lll(" ) ) +(def 'fpdiv (a b) (/ (+ (/ b 2) (* a (exp 2 128))) b)) +(def 'fpmul (a b) (/ (* a b) (exp 2 128)) ) (returnlll { (when (= $0 'new) { @@ -184,8 +186,8 @@ var exchangeCode = eth.lll(" (set 'xoffer (if @offer $64 (callvalue))) (set 'want $96) (set 'xwant $128) - (set 'rate (/ (* @xoffer (exp 2 128)) @xwant)) - (set 'irate (/ (* @xwant (exp 2 128)) @xoffer)) + (set 'rate (fpdiv @xoffer @xwant)) + (set 'irate (fpdiv @xwant @xoffer)) (unless (&& @rate @irate @xoffer @xwant) (stop)) @@ -204,7 +206,7 @@ var exchangeCode = eth.lll(" (for {} (&& @item (>= (rateof @item) @irate)) {} { (set 'offerA (min @xoffer (wantof @item))) - (set 'wantA (/ (* @offerA (rateof @item)) (exp 2 128))) + (set 'wantA (fpmul @offerA (rateof @item))) (set 'xoffer (- @xoffer @offerA)) (set 'xwant (- @xwant @wantA)) @@ -240,7 +242,7 @@ var exchangeCode = eth.lll(" (set 'item @@ @last) (for {} (&& @item (!= (idof @item) (caller))) { (set 'last @item) (inc item) } {}) (when @item { - (set 'xoffer (/ (* (wantof @item) (rateof @item)) (exp 2 128))) + (set 'xoffer (fpmul (wantof @item) (rateof @item))) [[ @last ]] @@ @item (xfer @offer (caller) @xoffer) }) @@ -269,6 +271,21 @@ eth.transact(eth.key, '0', config, "3".pad(32) + exchange.pad(32), 10000, eth.ga env.note('Register my name...') eth.transact(eth.key, '0', nameReg, "register".pad(32) + "Gav".pad(32), 10000, eth.gasPrice); +env.note('Dole out ETH to other address...') +eth.transact(eth.key, '100000000000000000000', eth.secretToAddress(eth.keys[1]), "", 10000, eth.gasPrice); + +env.note('Register my other name...') +eth.transact(eth.keys[1], '0', nameReg, "register".pad(32) + "Gav Would".pad(32), 10000, eth.gasPrice); + +env.note('Approve Exchange...') +eth.transact(eth.key, '0', gavCoin, "approve".pad(32) + exchange.pad(32), 10000, eth.gasPrice); + +env.note('Approve Exchange on other address...') +eth.transact(eth.keys[1], '0', gavCoin, "approve".pad(32) + exchange.pad(32), 10000, eth.gasPrice); + +env.note('Make offer 5000GAV/5ETH...') +eth.transact(eth.key, '0', exchange, "new".pad(32) + gavCoin.pad(32) + "5000".pad(32) + "0".pad(32) + "5000000000000000000".pad(32), 10000, eth.gasPrice); + env.note('All done.') // env.load('/home/gav/Eth/cpp-ethereum/stdserv.js') From 7d331e5b5b1dac8dfd7c4a000af263c079484d5d Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 27 Jul 2014 17:34:24 +0200 Subject: [PATCH 024/223] Correct order for transactions in same block. --- alethzero/MainWin.cpp | 2 +- libethereum/Client.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index a929ff6d3..f23015399 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -141,7 +141,7 @@ Main::Main(QWidget *parent) : if (pocnumber == 5) m_servers.push_back("54.72.69.180:30303"); else if (pocnumber == 6) - m_servers.push_back("54.72.69.180:30303"/*"54.72.31.55:30303"*/); + m_servers.push_back("54.76.56.74:30303"); else { connect(&m_webCtrl, &QNetworkAccessManager::finished, [&](QNetworkReply* _r) diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index cc76e0b49..6a05adc67 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -692,7 +692,7 @@ PastMessages Client::transactions(TransactionFilter const& _f) const s--; else // Have a transaction that contains a matching message. - ret.insert(ret.begin(), pm[j].polish(h, ts, n)); + ret.push_back(pm[j].polish(h, ts, n)); } } #if ETH_DEBUG From 49e42ac241a332e6eff4a9175701dc5b08c0d078 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 28 Jul 2014 01:58:34 +0200 Subject: [PATCH 025/223] Rename assembly again. --- liblll/Assembly.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/liblll/Assembly.h b/liblll/Assembly.h index 888b373c5..f0f93297f 100644 --- a/liblll/Assembly.h +++ b/liblll/Assembly.h @@ -80,7 +80,7 @@ public: AssemblyItem const& append(AssemblyItem const& _i); AssemblyItem const& append(std::string const& _data) { return append(newPushString(_data)); } AssemblyItem const& append(bytes const& _data) { return append(newData(_data)); } - AssemblyItem appendSubSize(Assembly const& _asmbly) { auto ret = newSub(_asmbly); append(newPushSubSize(ret.data())); return ret; } + AssemblyItem appendSubSize(Assembly const& _a) { auto ret = newSub(_a); append(newPushSubSize(ret.data())); return ret; } AssemblyItem appendJump() { auto ret = append(newPushTag()); append(Instruction::JUMP); return ret; } AssemblyItem appendJumpI() { auto ret = append(newPushTag()); append(Instruction::JUMPI); return ret; } From 5eeae63c36a20b9293262d7edd97bdddbd604883 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 28 Jul 2014 02:04:34 +0200 Subject: [PATCH 026/223] Include file orders altered for clang. --- libethereum/Client.cpp | 2 -- libethereum/Client.h | 1 + 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index 6a05adc67..757e2f7a0 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -24,8 +24,6 @@ #include #include #include -#include -#include #include #include "Defaults.h" #include "PeerServer.h" diff --git a/libethereum/Client.h b/libethereum/Client.h index ee0bd145a..6fca76752 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include "BlockChain.h" #include "TransactionQueue.h" From 7190a52992dde6c44ecf13b55bacce68819c226b Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 28 Jul 2014 02:06:06 +0200 Subject: [PATCH 027/223] Nicer signing for clang. --- libethential/FixedHash.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libethential/FixedHash.h b/libethential/FixedHash.h index 2e3a92ce4..4bac48743 100644 --- a/libethential/FixedHash.h +++ b/libethential/FixedHash.h @@ -85,13 +85,13 @@ public: bool operator<(FixedHash const& _c) const { return m_data < _c.m_data; } // The obvious binary operators. - FixedHash& operator^=(FixedHash const& _c) { for (auto i = 0; i < N; ++i) m_data[i] ^= _c.m_data[i]; return *this; } + FixedHash& operator^=(FixedHash const& _c) { for (unsigned i = 0; i < N; ++i) m_data[i] ^= _c.m_data[i]; return *this; } FixedHash operator^(FixedHash const& _c) const { return FixedHash(*this) ^= _c; } - FixedHash& operator|=(FixedHash const& _c) { for (auto i = 0; i < N; ++i) m_data[i] |= _c.m_data[i]; return *this; } + FixedHash& operator|=(FixedHash const& _c) { for (unsigned i = 0; i < N; ++i) m_data[i] |= _c.m_data[i]; return *this; } FixedHash operator|(FixedHash const& _c) const { return FixedHash(*this) |= _c; } - FixedHash& operator&=(FixedHash const& _c) { for (auto i = 0; i < N; ++i) m_data[i] &= _c.m_data[i]; return *this; } + FixedHash& operator&=(FixedHash const& _c) { for (unsigned i = 0; i < N; ++i) m_data[i] &= _c.m_data[i]; return *this; } FixedHash operator&(FixedHash const& _c) const { return FixedHash(*this) &= _c; } - FixedHash& operator~() { for (auto i = 0; i < N; ++i) m_data[i] = ~m_data[i]; return *this; } + FixedHash& operator~() { for (unsigned i = 0; i < N; ++i) m_data[i] = ~m_data[i]; return *this; } /// @returns true if all bytes in @a _c are set in this object. bool contains(FixedHash const& _c) const { return (*this & _c) == _c; } From 787c6305e3347ac6dda04be7f1c27e3c2f35d076 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 28 Jul 2014 02:06:54 +0200 Subject: [PATCH 028/223] Remove unused member. --- libethereum/AddressState.h | 1 - 1 file changed, 1 deletion(-) diff --git a/libethereum/AddressState.h b/libethereum/AddressState.h index bc4aa6df0..3425dc4d1 100644 --- a/libethereum/AddressState.h +++ b/libethereum/AddressState.h @@ -61,7 +61,6 @@ public: private: bool m_isAlive; - bool m_gotCode; u256 m_nonce; u256 m_balance; From 6a3429a81df7b5031debbbdeb26a2e843ae22a65 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 28 Jul 2014 02:29:18 +0200 Subject: [PATCH 029/223] Protocol version bump. --- libethcore/CommonEth.cpp | 2 +- stdserv.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libethcore/CommonEth.cpp b/libethcore/CommonEth.cpp index e88de28e1..3c1a336eb 100644 --- a/libethcore/CommonEth.cpp +++ b/libethcore/CommonEth.cpp @@ -29,7 +29,7 @@ using namespace eth; //#define ETH_ADDRESS_DEBUG 1 -const unsigned eth::c_protocolVersion = 23; +const unsigned eth::c_protocolVersion = 24; const unsigned eth::c_databaseVersion = 1; static const vector> g_units = diff --git a/stdserv.js b/stdserv.js index 6d94c397b..4d95f8a82 100644 --- a/stdserv.js +++ b/stdserv.js @@ -152,8 +152,8 @@ var exchangeCode = eth.lll(" (def 'rateof (_item) @@ (+ _item 1)) (def 'idof (_item) @@ (+ _item 2)) (def 'wantof (_item) @@ (+ _item 3)) -(def 'newitem (rate who want) { - (set 'pos (sha3pair rate who)) +(def 'newitem (rate who want list) { + (set 'pos (sha3trip rate who list)) [[ (+ @pos 1) ]] rate [[ (+ @pos 2) ]] who [[ (+ @pos 3) ]] want @@ -225,7 +225,7 @@ var exchangeCode = eth.lll(" (set 'last @list) (set 'item @@ @last) - (set 'newpos (newitem @rate (caller) @xwant)) + (set 'newpos (newitem @rate (caller) @xwant @list)) (for {} (&& @item (!= @item @newpos) (>= (rateof @item) @rate)) { (set 'last @item) (inc item) } {}) (if (= @item @newpos) From eee84e27cadc54bdd263f3fcaab6c8531ecd2841 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 28 Jul 2014 03:04:59 +0200 Subject: [PATCH 030/223] Coinbase for transactions collation. --- libethereum/Client.cpp | 10 +++++++--- libethereum/Client.h | 3 ++- libqethereum/QEthereum.cpp | 1 + 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index 757e2f7a0..2c9b4453b 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -649,7 +649,7 @@ PastMessages Client::transactions(TransactionFilter const& _f) const s--; else // Have a transaction that contains a matching message. - ret.insert(ret.begin(), pm[j].polish(h256(), ts, m_bc.number() + 1)); + ret.insert(ret.begin(), pm[j].polish(h256(), ts, m_bc.number() + 1, m_postMine.address())); } } } @@ -671,6 +671,7 @@ PastMessages Client::transactions(TransactionFilter const& _f) const // Might have a block that contains a transaction that contains a matching message. auto bs = m_bc.blooms(h).blooms; Manifests ms; + BlockInfo bi; for (unsigned i = 0; i < bs.size(); ++i) if (_f.matches(bs[i])) { @@ -684,13 +685,16 @@ PastMessages Client::transactions(TransactionFilter const& _f) const #if ETH_DEBUG total += pm.size(); #endif - auto ts = BlockInfo(m_bc.block(h)).timestamp; + if (!bi) + bi.populate(m_bc.block(h)); + auto ts = bi.timestamp; + auto cb = bi.coinbaseAddress; for (unsigned j = 0; j < pm.size() && ret.size() != m; ++j) if (s) s--; else // Have a transaction that contains a matching message. - ret.push_back(pm[j].polish(h, ts, n)); + ret.push_back(pm[j].polish(h, ts, n, cb)); } } #if ETH_DEBUG diff --git a/libethereum/Client.h b/libethereum/Client.h index 6fca76752..17d1bc149 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -83,7 +83,7 @@ struct PastMessage { PastMessage(Manifest const& _m, std::vector _path, Address _o): to(_m.to), from(_m.from), value(_m.value), input(_m.input), output(_m.output), path(_path), origin(_o) {} - PastMessage& polish(h256 _b, u256 _ts, unsigned _n) { block = _b; timestamp = _ts; number = _n; return *this; } + PastMessage& polish(h256 _b, u256 _ts, unsigned _n, Address _coinbase) { block = _b; timestamp = _ts; number = _n; coinbase = _coinbase; return *this; } Address to; ///< The receiving address of the transaction. Address() in the case of a creation. Address from; ///< The receiving address of the transaction. Address() in the case of a creation. @@ -93,6 +93,7 @@ struct PastMessage std::vector path; ///< Call path into the block transaction. size() is always > 0. First item is the transaction index in the block. Address origin; ///< Originating sender of the transaction. + Address coinbase; ///< Block coinbase. h256 block; ///< Block hash. u256 timestamp; ///< Block timestamp. unsigned number; ///< Block number. diff --git a/libqethereum/QEthereum.cpp b/libqethereum/QEthereum.cpp index 5dd367b7a..bc27c7f8a 100644 --- a/libqethereum/QEthereum.cpp +++ b/libqethereum/QEthereum.cpp @@ -311,6 +311,7 @@ static QString toJson(eth::PastMessages const& _pms) v["from"] = toQJS(t.from); v["origin"] = toQJS(t.origin); v["timestamp"] = (int)t.timestamp; + v["coinbase"] = toQJS(t.coinbase); v["block"] = toQJS(t.block); QJsonArray path; for (int i: t.path) From 33b1b6b636aeae7071ef81a30665c421f76e384d Mon Sep 17 00:00:00 2001 From: caktux Date: Mon, 28 Jul 2014 01:02:29 -0400 Subject: [PATCH 031/223] neth: new peer server, parseData without @, get gav from coins --- neth/CMakeLists.txt | 3 +++ neth/main.cpp | 45 ++++++++++++++++++++------------------------- 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/neth/CMakeLists.txt b/neth/CMakeLists.txt index 3e8dab70d..153dbea31 100644 --- a/neth/CMakeLists.txt +++ b/neth/CMakeLists.txt @@ -34,14 +34,17 @@ if ("${TARGET_PLATFORM}" STREQUAL "w64") target_link_libraries(${EXECUTABLE} cryptopp) target_link_libraries(${EXECUTABLE} ncurses) target_link_libraries(${EXECUTABLE} form) + target_link_libraries(${EXECUTABLE} boost_regex-mt-s) target_link_libraries(${EXECUTABLE} boost_system-mt-s) target_link_libraries(${EXECUTABLE} boost_filesystem-mt-s) target_link_libraries(${EXECUTABLE} boost_thread_win32-mt-s) set(CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS) elseif (UNIX) + target_link_libraries(${EXECUTABLE} boost_regex) target_link_libraries(${EXECUTABLE} ncurses) target_link_libraries(${EXECUTABLE} form) else () + target_link_libraries(${EXECUTABLE} boost_regex) target_link_libraries(${EXECUTABLE} boost_system) target_link_libraries(${EXECUTABLE} boost_filesystem) target_link_libraries(${EXECUTABLE} ncurses) diff --git a/neth/main.cpp b/neth/main.cpp index c085736b2..fdb21f611 100644 --- a/neth/main.cpp +++ b/neth/main.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #if ETH_JSONRPC @@ -133,10 +134,10 @@ string credits() vs = vs.substr(vs.find_first_of('.') + 1)[0]; int pocnumber = stoi(vs); string m_servers; - if (pocnumber == 4) - m_servers = "54.72.31.55"; - else + if (pocnumber == 5) m_servers = "54.72.69.180"; + else + m_servers = "54.76.56.74"; ccout << "Type 'netstart 30303' to start networking" << endl; ccout << "Type 'connect " << m_servers << " 30303' to connect" << endl; @@ -152,6 +153,7 @@ void version() } Address c_config = Address("661005d2720d855f1d9976f88bb10c1a3398c77f"); + string pretty(h160 _a, eth::State _st) { string ns; @@ -969,7 +971,8 @@ int main(int argc, char** argv) // Balance stringstream ssb; u256 balance = c.state().balance(us.address()); - Address gavCoin("0115554959f43bf1d04cd7e3749d00fb0623ce1f"); + Address coinsAddr = right160(c.stateAt(c_config, 1)); + Address gavCoin = right160(c.stateAt(coinsAddr, c.stateAt(coinsAddr, 1))); u256 totalGavCoinBalance = st.storage(gavCoin, (u160)us.address()); ssb << "Balance: " << formatBalance(balance) << " | " << totalGavCoinBalance << " GAV"; mvwprintw(consolewin, 0, x, ssb.str().c_str()); @@ -1228,18 +1231,13 @@ bytes parseData(string _args) bytes m_data; stringstream args(_args); string arg; - int cc = 0; + static const boost::regex r("\"([^\"]*)\"(.*)"); + static const boost::regex h("(0x)?(([a-fA-F0-9])+)(.*)"); + while (args >> arg) { - int al = arg.length(); - if (boost::starts_with(arg, "0x")) - { - bytes bs = fromHex(arg); - m_data += bs; - } - else if (arg[0] == '@') + if (boost::regex_match(arg, h)) { - arg = arg.substr(1, arg.length()); if (boost::starts_with(arg, "0x")) { cnote << "hex: " << arg; @@ -1250,16 +1248,6 @@ bytes parseData(string _args) m_data.push_back(0); m_data += bs; } - else if (boost::starts_with(arg, "\"") && boost::ends_with(arg, "\"")) - { - arg = arg.substr(1, arg.length() - 2); - cnote << "string: " << arg; - if (al < 32) - for (int i = 0; i < 32 - al; ++i) - m_data.push_back(0); - for (int i = 0; i < al; ++i) - m_data.push_back(arg[i]); - } else { cnote << "value: " << arg; @@ -1271,10 +1259,17 @@ bytes parseData(string _args) m_data += bs; } } - else + else if (boost::regex_match(arg, r)) + { + arg = arg.substr(1, arg.length() - 2); + int al = arg.length(); + cnote << "string: " << arg; for (int i = 0; i < al; ++i) m_data.push_back(arg[i]); - cc++; + if (al < 32) + for (int i = 0; i < 32 - al; ++i) + m_data.push_back(0); + } } return m_data; } From b4191025fd1fb7a3d0bd9bb7e6e518003a7f636d Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 28 Jul 2014 16:18:12 +0200 Subject: [PATCH 032/223] Network has own thread. --- libethereum/Client.cpp | 73 ++++++++++++++++++++++++++---------------- libethereum/Client.h | 41 +++++++++++++----------- 2 files changed, 68 insertions(+), 46 deletions(-) diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index 2c9b4453b..805dcac75 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -99,14 +99,7 @@ void Client::ensureWorking() Client::~Client() { - if (m_work) - { - if (m_workState.load(std::memory_order_acquire) == Active) - m_workState.store(Deleting, std::memory_order_release); - while (m_workState.load(std::memory_order_acquire) != Deleted) - this_thread::sleep_for(chrono::milliseconds(10)); - m_work->join(); - } + stopNetwork(); } void Client::flushTransactions() @@ -196,6 +189,18 @@ void Client::noteChanged(h256Set const& _filters) void Client::startNetwork(unsigned short _listenPort, std::string const& _seedHost, unsigned short _port, NodeMode _mode, unsigned _peers, string const& _publicIP, bool _upnp) { + static const char* c_threadName = "net"; + + if (!m_workNet) + m_workNet.reset(new thread([&]() + { + setThreadName(c_threadName); + m_workNetState.store(Active, std::memory_order_release); + while (m_workNetState.load(std::memory_order_acquire) != Deleting) + workNet(); + m_workNetState.store(Deleted, std::memory_order_release); + })); + ensureWorking(); { @@ -242,8 +247,19 @@ void Client::connect(std::string const& _seedHost, unsigned short _port) void Client::stopNetwork() { - Guard l(x_net); - m_net.reset(nullptr); + { + Guard l(x_net); + m_net.reset(nullptr); + } + + if (m_workNet) + { + if (m_workNetState.load(std::memory_order_acquire) == Active) + m_workNetState.store(Deleting, std::memory_order_release); + while (m_workNetState.load(std::memory_order_acquire) != Deleted) + this_thread::sleep_for(chrono::milliseconds(10)); + m_workNet->join(); + } } void Client::startMining() @@ -303,28 +319,29 @@ void Client::inject(bytesConstRef _rlp) m_tq.attemptImport(_rlp); } -void Client::work(bool _justQueue) +void Client::workNet() { - cworkin << "WORK"; - h256Set changeds; - // 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. + Guard l(x_net); + if (m_net) { - Guard l(x_net); - if (m_net && !_justQueue) - { - cwork << "NETWORK"; - m_net->process(); // must be in guard for now since it uses the blockchain. + cwork << "NETWORK"; + m_net->process(); // must be in guard for now since it uses the blockchain. - // returns h256Set as block hashes, once for each block that has come in/gone out. - cwork << "NET <==> TQ ; CHAIN ==> NET ==> BQ"; - m_net->sync(m_tq, m_bq); + // returns h256Set as block hashes, once for each block that has come in/gone out. + cwork << "NET <==> TQ ; CHAIN ==> NET ==> BQ"; + m_net->sync(m_tq, m_bq); - cwork << "TQ:" << m_tq.items() << "; BQ:" << m_bq.items(); - } + cwork << "TQ:" << m_tq.items() << "; BQ:" << m_bq.items(); } +} + +void Client::work(bool _justQueue) +{ + cworkin << "WORK"; + h256Set changeds; // Do some mining. if (!_justQueue) @@ -407,7 +424,7 @@ void Client::work(bool _justQueue) cwork << "BQ ==> CHAIN ==> STATE"; OverlayDB db = m_stateDB; - m_lock.unlock(); + x_stateDB.unlock(); h256s newBlocks = m_bc.sync(m_bq, db, 100); // TODO: remove transactions from m_tq nicely rather than relying on out of date nonce later on. if (newBlocks.size()) { @@ -415,7 +432,7 @@ void Client::work(bool _justQueue) appendFromNewBlock(i, changeds); changeds.insert(NewBlockFilter); } - m_lock.lock(); + x_stateDB.lock(); if (newBlocks.size()) m_stateDB = db; @@ -451,12 +468,12 @@ void Client::work(bool _justQueue) void Client::lock() const { - m_lock.lock(); + x_stateDB.lock(); } void Client::unlock() const { - m_lock.unlock(); + x_stateDB.unlock(); } unsigned Client::numberOf(int _n) const diff --git a/libethereum/Client.h b/libethereum/Client.h index 17d1bc149..405415d9f 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -291,13 +291,16 @@ public: void clearPending(); private: - /// Ensure the worker thread is running. Needed for networking & mining. + /// Ensure the worker thread is running. Needed for blockchain maintenance & mining. void ensureWorking(); - /// Do some work. Handles networking and mining. + /// Do some work. Handles blockchain maintenance and mining. /// @param _justQueue If true will only processing the transaction queues. void work(bool _justQueue = false); + /// Do some work on the network. + void workNet(); + /// Collate the changed filters for the bloom filter of the given pending transaction. /// Insert any filters that are activated into @a o_changed. void appendFromNewPending(h256 _pendingTransactionBloom, h256Set& o_changed) const; @@ -316,24 +319,26 @@ private: State asOf(int _h) const; State asOf(unsigned _h) const; - std::string m_clientVersion; ///< Our end-application client's name/version. - VersionChecker m_vc; ///< Dummy object to check & update the protocol version. - BlockChain m_bc; ///< Maintains block database. - TransactionQueue m_tq; ///< Maintains a list of incoming transactions not yet in a block on the blockchain. - BlockQueue m_bq; ///< Maintains a list of incoming blocks not yet on the blockchain (to be imported). - OverlayDB m_stateDB; ///< Acts as the central point for the state database, so multiple States can share it. - State m_preMine; ///< The present state of the client. - State m_postMine; ///< The state of the client which we're mining (i.e. it'll have all the rewards added). - - mutable std::mutex x_net; ///< Lock for the network. - std::unique_ptr m_net; ///< Should run in background and send us events when blocks found and allow us to send blocks as required. - - std::unique_ptr m_work;///< The work thread. - - mutable std::recursive_mutex m_lock; + std::string m_clientVersion; ///< Our end-application client's name/version. + VersionChecker m_vc; ///< Dummy object to check & update the protocol version. + BlockChain m_bc; ///< Maintains block database. + TransactionQueue m_tq; ///< Maintains a list of incoming transactions not yet in a block on the blockchain. + BlockQueue m_bq; ///< Maintains a list of incoming blocks not yet on the blockchain (to be imported). + mutable std::recursive_mutex x_stateDB; // TODO: remove in favour of copying m_stateDB as required and thread-safing/copying State. Have a good think about what state objects there should be. Probably want 4 (pre, post, mining, user-visible). + OverlayDB m_stateDB; ///< Acts as the central point for the state database, so multiple States can share it. + State m_preMine; ///< The present state of the client. + State m_postMine; ///< The state of the client which we're mining (i.e. it'll have all the rewards added). + + std::unique_ptr m_workNet; ///< The network thread. + std::atomic m_workNetState; + mutable std::mutex x_net; ///< Lock for the network. // TODO: make network thread-safe. + std::unique_ptr m_net; ///< Should run in background and send us events when blocks found and allow us to send blocks as required. + + std::unique_ptr m_work; ///< The work thread. std::atomic m_workState; + bool m_paranoia = false; - bool m_doMine = false; ///< Are we supposed to be mining? + bool m_doMine = false; ///< Are we supposed to be mining? MineProgress m_mineProgress; std::list m_mineHistory; mutable bool m_restartMining = false; From eb360674cfb2a22540c4b878cf4713b02f2f4d8d Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 28 Jul 2014 21:42:46 +0200 Subject: [PATCH 033/223] Thread-safe client and remove old State const&-based APIs. --- alethzero/MainWin.cpp | 78 ++++++++------------- alethzero/MainWin.h | 2 - eth/EthStubServer.cpp | 16 ++--- eth/main.cpp | 49 ++++---------- libethereum/Client.cpp | 134 +++++++++++++++++++++++++------------ libethereum/Client.h | 76 ++++++++++----------- libethereum/State.h | 9 --- libqethereum/QEthereum.cpp | 30 ++------- neth/main.cpp | 66 ++++++------------ walleth/MainWin.cpp | 19 ++---- 10 files changed, 204 insertions(+), 275 deletions(-) diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index f23015399..b431c2afb 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -257,7 +257,7 @@ void Main::installWatches() void Main::installNameRegWatch() { m_client->uninstallWatch(m_nameRegFilter); - m_nameRegFilter = installWatch(eth::TransactionFilter().altered((u160)state().storage(c_config, 0)), [=](){ onNameRegChange(); }); + m_nameRegFilter = installWatch(eth::TransactionFilter().altered((u160)m_client->stateAt(c_config, 0)), [=](){ onNameRegChange(); }); } void Main::installCurrenciesWatch() @@ -446,11 +446,11 @@ Address Main::fromString(QString const& _a) const memset(n.data() + sn.size(), 0, 32 - sn.size()); if (_a.size()) { - if (h160 nameReg = (u160)state().storage(c_config, 0)) - if (h256 a = state().storage(nameReg, n)) + if (h160 nameReg = (u160)m_client->stateAt(c_config, 0)) + if (h256 a = m_client->stateAt(nameReg, n)) return right160(a); - if (h256 a = state().storage(m_nameReg, n)) + if (h256 a = m_client->stateAt(m_nameReg, n)) return right160(a); } if (_a.size() == 40) @@ -493,12 +493,9 @@ void Main::writeSettings() s.setValue("port", ui->port->value()); s.setValue("url", ui->urlEdit->text()); - if (m_client->peerServer()) - { - bytes d = m_client->peerServer()->savePeers(); + bytes d = m_client->savePeers(); + if (d.size()) m_peers = QByteArray((char*)d.data(), (int)d.size()); - - } s.setValue("peers", m_peers); s.setValue("nameReg", ui->nameReg->text()); @@ -597,7 +594,6 @@ void Main::on_preview_triggered() void Main::refreshMining() { - eth::ClientGuard g(m_client.get()); eth::MineProgress p = m_client->miningProgress(); ui->mineStatus->setText(m_client->isMining() ? QString("%1s @ %2kH/s").arg(p.ms / 1000).arg(p.ms ? p.hashes / p.ms : 0) : "Not mining"); if (!ui->miningView->isVisible()) @@ -652,11 +648,6 @@ void Main::refreshNetwork() ui->peers->addItem(QString("%3 ms - %1:%2 - %4").arg(i.host.c_str()).arg(i.port).arg(chrono::duration_cast(i.lastPing).count()).arg(i.clientVersion.c_str())); } -eth::State const& Main::state() const -{ - return ui->preview->isChecked() ? m_client->postState() : m_client->state(); -} - void Main::refreshAll() { refreshDestination(); @@ -760,8 +751,6 @@ static bool transactionMatch(string const& _f, Transaction const& _t) void Main::refreshBlockChain() { cwatch << "refreshBlockChain()"; - eth::ClientGuard g(m_client.get()); - auto const& st = state(); QByteArray oldSelected = ui->blocks->count() ? ui->blocks->currentItem()->data(Qt::UserRole).toByteArray() : QByteArray(); ui->blocks->clear(); @@ -794,7 +783,7 @@ void Main::refreshBlockChain() .arg(render(t.safeSender())) .arg(render(t.receiveAddress)) .arg((unsigned)t.nonce) - .arg(st.addressHasCode(t.receiveAddress) ? '*' : '-') : + .arg(m_client->codeAt(t.receiveAddress).size() ? '*' : '-') : QString(" %2 +> %3: %1 [%4]") .arg(formatBalance(t.value).c_str()) .arg(render(t.safeSender())) @@ -933,13 +922,12 @@ string Main::renderDiff(eth::StateDiff const& _d) const void Main::on_transactionQueue_currentItemChanged() { ui->pendingInfo->clear(); - eth::ClientGuard g(m_client.get()); stringstream s; int i = ui->transactionQueue->currentRow(); - if (i >= 0 && i < (int)m_client->postState().pending().size()) + if (i >= 0 && i < (int)m_client->pending().size()) { - Transaction tx(m_client->postState().pending()[i]); + Transaction tx(m_client->pending()[i]); auto ss = tx.safeSender(); h256 th = sha3(rlpList(ss, tx.nonce)); s << "

" << th << "

"; @@ -967,7 +955,7 @@ void Main::on_transactionQueue_currentItemChanged() // s << "Pre: " << fs.rootHash() << "
"; // s << "Post: " << ts.rootHash() << ""; - s << renderDiff(m_client->postState().pendingDiff(i)); + s << renderDiff(m_client->diff(i, 0)); } ui->pendingInfo->setHtml(QString::fromStdString(s.str())); @@ -1002,7 +990,6 @@ void Main::on_blocks_currentItemChanged() ui->debugCurrent->setEnabled(false); ui->debugDumpState->setEnabled(false); ui->debugDumpStatePre->setEnabled(false); - eth::ClientGuard g(m_client.get()); if (auto item = ui->blocks->currentItem()) { auto hba = item->data(Qt::UserRole).toByteArray(); @@ -1069,10 +1056,7 @@ void Main::on_blocks_currentItemChanged() if (tx.data.size()) s << eth::memDump(tx.data, 16, true); } - auto st = eth::State(m_client->state().db(), m_client->blockChain(), h); - eth::State before = st.fromPending(txi); - eth::State after = st.fromPending(txi + 1); - s << renderDiff(before.diff(after)); + s << renderDiff(m_client->diff(txi, h)); ui->debugCurrent->setEnabled(true); ui->debugDumpState->setEnabled(true); ui->debugDumpStatePre->setEnabled(true); @@ -1084,7 +1068,6 @@ void Main::on_blocks_currentItemChanged() void Main::on_debugCurrent_triggered() { - eth::ClientGuard g(m_client.get()); if (auto item = ui->blocks->currentItem()) { auto hba = item->data(Qt::UserRole).toByteArray(); @@ -1093,11 +1076,10 @@ void Main::on_debugCurrent_triggered() if (!item->data(Qt::UserRole + 1).isNull()) { - eth::State st(m_client->state().db(), m_client->blockChain(), h); unsigned txi = item->data(Qt::UserRole + 1).toInt(); - m_executiveState = st.fromPending(txi); + m_executiveState = m_client->state(txi, h); m_currentExecution = unique_ptr(new Executive(m_executiveState)); - Transaction t = st.pending()[txi]; + Transaction t = m_client->pending()[txi]; auto r = t.rlp(); populateDebugger(&r); m_currentExecution.reset(); @@ -1107,7 +1089,6 @@ void Main::on_debugCurrent_triggered() void Main::on_debugDumpState_triggered(int _add) { - eth::ClientGuard g(m_client.get()); if (auto item = ui->blocks->currentItem()) { auto hba = item->data(Qt::UserRole).toByteArray(); @@ -1120,9 +1101,8 @@ void Main::on_debugDumpState_triggered(int _add) ofstream f(fn.toStdString()); if (f.is_open()) { - eth::State st(m_client->state().db(), m_client->blockChain(), h); unsigned txi = item->data(Qt::UserRole + 1).toInt(); - f << st.fromPending(txi + _add) << endl; + f << m_client->state(txi + _add, h) << endl; } } } @@ -1178,20 +1158,19 @@ void Main::populateDebugger(eth::bytesConstRef _r) void Main::on_contracts_currentItemChanged() { ui->contractInfo->clear(); - eth::ClientGuard l(&*m_client); if (auto item = ui->contracts->currentItem()) { auto hba = item->data(Qt::UserRole).toByteArray(); assert(hba.size() == 20); - auto h = h160((byte const*)hba.data(), h160::ConstructFromPointer); + auto address = h160((byte const*)hba.data(), h160::ConstructFromPointer); stringstream s; try { - auto storage = state().storage(h); + auto storage = m_client->storageAt(address); for (auto const& i: storage) s << "@" << showbase << hex << prettyU256(i.first).toStdString() << "    " << showbase << hex << prettyU256(i.second).toStdString() << "
"; - s << "

Body Code

" << disassemble(state().code(h)); + s << "

Body Code

" << disassemble(m_client->codeAt(address)); ui->contractInfo->appendHtml(QString::fromStdString(s.str())); } catch (eth::InvalidTrie) @@ -1203,8 +1182,7 @@ void Main::on_contracts_currentItemChanged() void Main::on_idealPeers_valueChanged() { - if (m_client->peerServer()) - m_client->peerServer()->setIdealPeerCount(ui->idealPeers->value()); + m_client->setIdealPeerCount(ui->idealPeers->value()); } void Main::on_ourAccounts_doubleClicked() @@ -1288,7 +1266,7 @@ void Main::on_data_textChanged() errs.append("
" + QString::fromStdString(i).toHtmlEscaped() + "
"); } ui->code->setHtml(errs + lll + "

Code

" + QString::fromStdString(disassemble(m_data)).toHtmlEscaped()); - ui->gas->setMinimum((qint64)state().createGas(m_data.size(), 0)); + ui->gas->setMinimum((qint64)Client::txGas(m_data.size(), 0)); if (!ui->gas->isEnabled()) ui->gas->setValue(m_backupGas); ui->gas->setEnabled(true); @@ -1344,9 +1322,9 @@ void Main::on_data_textChanged() s = s.mid(1); } ui->code->setHtml(QString::fromStdString(eth::memDump(m_data, 8, true))); - if (m_client->postState().addressHasCode(fromString(ui->destination->currentText()))) + if (m_client->codeAt(fromString(ui->destination->currentText()), 0).size()) { - ui->gas->setMinimum((qint64)state().callGas(m_data.size(), 1)); + ui->gas->setMinimum((qint64)Client::txGas(m_data.size(), 1)); if (!ui->gas->isEnabled()) ui->gas->setValue(m_backupGas); ui->gas->setEnabled(true); @@ -1355,7 +1333,7 @@ void Main::on_data_textChanged() { if (ui->gas->isEnabled()) m_backupGas = ui->gas->value(); - ui->gas->setValue((qint64)state().callGas(m_data.size())); + ui->gas->setValue((qint64)Client::txGas(m_data.size())); ui->gas->setEnabled(false); } } @@ -1412,7 +1390,7 @@ void Main::updateFee() bool ok = false; for (auto i: m_myKeys) - if (state().balance(i.address()) >= totalReq) + if (m_client->balanceAt(i.address()) >= totalReq) { ok = true; break; @@ -1436,7 +1414,7 @@ void Main::on_net_triggered() { m_client->startNetwork(ui->port->value(), string(), 0, NodeMode::Full, ui->idealPeers->value(), ui->forceAddress->text().toStdString(), ui->upnp->isChecked()); if (m_peers.size() && ui->usePast->isChecked()) - m_client->peerServer()->restorePeers(bytesConstRef((byte*)m_peers.data(), m_peers.size())); + m_client->restorePeers(bytesConstRef((byte*)m_peers.data(), m_peers.size())); } else m_client->stopNetwork(); @@ -1479,9 +1457,8 @@ void Main::on_mine_triggered() void Main::on_send_clicked() { u256 totalReq = value() + fee(); - eth::ClientGuard l(&*m_client); for (auto i: m_myKeys) - if (m_client->postState().balance(i.address()) >= totalReq) + if (m_client->balanceAt(i.address(), 0) >= totalReq) { debugFinished(); Secret s = i.secret(); @@ -1500,12 +1477,11 @@ void Main::on_debug_clicked() try { u256 totalReq = value() + fee(); - eth::ClientGuard l(&*m_client); for (auto i: m_myKeys) - if (m_client->state().balance(i.address()) >= totalReq) + if (m_client->balanceAt(i.address()) >= totalReq) { Secret s = i.secret(); - m_executiveState = state(); + m_executiveState = m_client->postState(); m_currentExecution = unique_ptr(new Executive(m_executiveState)); Transaction t; t.nonce = m_executiveState.transactionsFrom(toAddress(s)); diff --git a/alethzero/MainWin.h b/alethzero/MainWin.h index 35f8cb137..24e9c5ae1 100644 --- a/alethzero/MainWin.h +++ b/alethzero/MainWin.h @@ -156,8 +156,6 @@ private: void alterDebugStateGroup(bool _enable) const; - eth::State const& state() const; - void updateFee(); void readSettings(); void writeSettings(); diff --git a/eth/EthStubServer.cpp b/eth/EthStubServer.cpp index fc281a706..666a658f1 100644 --- a/eth/EthStubServer.cpp +++ b/eth/EthStubServer.cpp @@ -60,14 +60,12 @@ Json::Value EthStubServer::procedures() std::string EthStubServer::coinbase() { - ClientGuard g(&m_client); return toJS(m_client.address()); } std::string EthStubServer::balanceAt(std::string const& _a) { - ClientGuard g(&m_client); - return toJS(m_client.postState().balance(jsToAddress(_a))); + return toJS(m_client.balanceAt(jsToAddress(_a), 0)); } Json::Value EthStubServer::check(Json::Value const& _as) @@ -85,7 +83,6 @@ Json::Value EthStubServer::check(Json::Value const& _as) std::string EthStubServer::create(const std::string& _bCode, const std::string& _sec, const std::string& _xEndowment, const std::string& _xGas, const std::string& _xGasPrice) { - ClientGuard g(&m_client); Address ret = m_client.transact(jsToSecret(_sec), jsToU256(_xEndowment), jsToBytes(_bCode), jsToU256(_xGas), jsToU256(_xGasPrice)); return toJS(ret); } @@ -102,8 +99,7 @@ std::string EthStubServer::gasPrice() bool EthStubServer::isContractAt(const std::string& _a) { - ClientGuard g(&m_client); - return m_client.postState().addressHasCode(jsToAddress(_a)); + return m_client.codeAt(jsToAddress(_a), 0).size(); } bool EthStubServer::isListening() @@ -133,27 +129,23 @@ Json::Value EthStubServer::keys() int EthStubServer::peerCount() { - ClientGuard g(&m_client); return m_client.peerCount(); } std::string EthStubServer::storageAt(const std::string& _a, const std::string& x) { - ClientGuard g(&m_client); - return toJS(m_client.postState().storage(jsToAddress(_a), jsToU256(x))); + return toJS(m_client.stateAt(jsToAddress(_a), jsToU256(x), 0)); } Json::Value EthStubServer::transact(const std::string& _aDest, const std::string& _bData, const std::string& _sec, const std::string& _xGas, const std::string& _xGasPrice, const std::string& _xValue) { - ClientGuard g(&m_client); m_client.transact(jsToSecret(_sec), jsToU256(_xValue), jsToAddress(_aDest), jsToBytes(_bData), jsToU256(_xGas), jsToU256(_xGasPrice)); return Json::Value(); } std::string EthStubServer::txCountAt(const std::string& _a) { - ClientGuard g(&m_client); - return toJS(m_client.postState().transactionsFrom(jsToAddress(_a))); + return toJS(m_client.countAt(jsToAddress(_a), 0)); } std::string EthStubServer::secretToAddress(const std::string& _a) diff --git a/eth/main.cpp b/eth/main.cpp index dbb0234ce..d400b5c93 100644 --- a/eth/main.cpp +++ b/eth/main.cpp @@ -341,7 +341,6 @@ int main(int argc, char** argv) { eth::uint port; iss >> port; - ClientGuard g(&c); c.startNetwork((short)port); } else if (cmd == "connect") @@ -349,12 +348,10 @@ int main(int argc, char** argv) string addr; eth::uint port; iss >> addr >> port; - ClientGuard g(&c); c.connect(addr, (short)port); } else if (cmd == "netstop") { - ClientGuard g(&c); c.stopNetwork(); } else if (cmd == "minestart") @@ -405,12 +402,10 @@ int main(int argc, char** argv) } else if (cmd == "block") { - ClientGuard g(&c); cout << "Current block: " << c.blockChain().details().number; } else if (cmd == "peers") { - ClientGuard g(&c); for (auto it: c.peers()) cout << it.host << ":" << it.port << ", " << it.clientVersion << ", " << std::chrono::duration_cast(it.lastPing).count() << "ms" @@ -418,12 +413,10 @@ int main(int argc, char** argv) } else if (cmd == "balance") { - ClientGuard g(&c); - cout << "Current balance: " << formatBalance(c.postState().balance(us.address())) << " = " << c.postState().balance(us.address()) << " wei" << endl; + cout << "Current balance: " << formatBalance(c.balanceAt(us.address(), 0)) << " = " << c.balanceAt(us.address(), 0) << " wei" << endl; } else if (cmd == "transact") { - ClientGuard g(&c); auto const& bc = c.blockChain(); auto h = bc.currentHash(); auto blockData = bc.block(h); @@ -450,7 +443,7 @@ int main(int argc, char** argv) cnote << ssbd.str(); int ssize = sechex.length(); int size = hexAddr.length(); - u256 minGas = (u256)c.state().callGas(data.size(), 0); + u256 minGas = (u256)Client::txGas(data.size(), 0); if (size < 40) { if (size > 0) @@ -477,40 +470,28 @@ int main(int argc, char** argv) } else if (cmd == "listContracts") { - ClientGuard g(&c); - auto const& st = c.state(); - auto acs = st.addresses(); + auto acs = c.addresses(); string ss; for (auto const& i: acs) - { - auto r = i.first; - if (st.addressHasCode(r)) + if (c.codeAt(i, 0).size()) { - ss = toString(r) + " : " + toString(formatBalance(i.second)) + " [" + toString((unsigned)st.transactionsFrom(i.first)) + "]"; + ss = toString(i) + " : " + toString(c.balanceAt(i, 0)) + " [" + toString((unsigned)c.countAt(i)) + "]"; cout << ss << endl; } - } } else if (cmd == "listAccounts") { - ClientGuard g(&c); - auto const& st = c.state(); - auto acs = st.addresses(); + auto acs = c.addresses(); string ss; for (auto const& i: acs) - { - auto r = i.first; - if (!st.addressHasCode(r)) + if (c.codeAt(i, 0).empty()) { - ss = toString(r) + pretty(r, st) + " : " + toString(formatBalance(i.second)) + " [" + toString((unsigned)st.transactionsFrom(i.first)) + "]"; + ss = toString(i) + " : " + toString(c.balanceAt(i, 0)) + " [" + toString((unsigned)c.countAt(i)) + "]"; cout << ss << endl; } - - } } else if (cmd == "send") { - ClientGuard g(&c); if (iss.peek() != -1) { string hexAddr; @@ -529,23 +510,22 @@ int main(int argc, char** argv) auto h = bc.currentHash(); auto blockData = bc.block(h); BlockInfo info(blockData); - u256 minGas = (u256)c.state().callGas(0, 0); + u256 minGas = (u256)Client::txGas(0, 0); Address dest = h160(fromHex(hexAddr)); c.transact(us.secret(), amount, dest, bytes(), minGas, info.minGasPrice); } - } else cwarn << "Require parameters: send ADDRESS AMOUNT"; } else if (cmd == "contract") { - ClientGuard g(&c); auto const& bc = c.blockChain(); auto h = bc.currentHash(); auto blockData = bc.block(h); BlockInfo info(blockData); - if(iss.peek() != -1) { + if (iss.peek() != -1) + { u256 endowment; u256 gas; u256 gasPrice; @@ -569,7 +549,7 @@ int main(int argc, char** argv) cnote << "Init:"; cnote << ssc.str(); } - u256 minGas = (u256)c.state().createGas(init.size(), 0); + u256 minGas = (u256)Client::txGas(init.size(), 0); if (endowment < 0) cwarn << "Invalid endowment"; else if (gasPrice < info.minGasPrice) @@ -591,16 +571,15 @@ int main(int argc, char** argv) cwarn << "Invalid address length"; else { - ClientGuard g(&c); auto h = h160(fromHex(rechex)); stringstream s; try { - auto storage = c.state().storage(h); + auto storage = c.storageAt(h, 0); for (auto const& i: storage) s << "@" << showbase << hex << i.first << " " << showbase << hex << i.second << endl; - s << endl << disassemble(c.state().code(h)) << endl; + s << endl << disassemble(c.codeAt(h, 0)) << endl; string outFile = getDataDir() + "/" + rechex + ".evm"; ofstream ofs; diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index 805dcac75..bd8c0a15b 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -92,6 +92,7 @@ void Client::ensureWorking() // Synchronise the state according to the head of the block chain. // TODO: currently it contains keys for *all* blocks. Make it remove old ones. + WriteGuard l(x_stateDB); m_preMine.sync(m_bc); m_postMine = m_preMine; })); @@ -109,7 +110,7 @@ void Client::flushTransactions() void Client::clearPending() { - ClientGuard l(this); + WriteGuard l(x_stateDB); if (!m_postMine.pending().size()) return; h256Set changeds; @@ -161,8 +162,10 @@ void Client::uninstallWatch(unsigned _i) void Client::appendFromNewPending(h256 _bloom, h256Set& o_changed) const { lock_guard l(m_filterLock); + ReadGuard sl(x_stateDB); + for (pair const& i: m_filters) - if ((unsigned)i.second.filter.latest() >= m_postMine.info().number && i.second.filter.matches(_bloom)) + if ((unsigned)i.second.filter.latest() > m_bc.number() && i.second.filter.matches(_bloom)) o_changed.insert(i.first); } @@ -204,20 +207,22 @@ void Client::startNetwork(unsigned short _listenPort, std::string const& _seedHo ensureWorking(); { - Guard l(x_net); + UpgradableGuard l(x_net); if (m_net.get()) return; - try - { - m_net.reset(new PeerServer(m_clientVersion, m_bc, 0, _listenPort, _mode, _publicIP, _upnp)); - } - catch (std::exception const&) { - // Probably already have the port open. - cwarn << "Could not initialize with specified/default port. Trying system-assigned port"; - m_net.reset(new PeerServer(m_clientVersion, m_bc, 0, _mode, _publicIP, _upnp)); + UpgradeGuard ul(l); + try + { + m_net.reset(new PeerServer(m_clientVersion, m_bc, 0, _listenPort, _mode, _publicIP, _upnp)); + } + catch (std::exception const&) + { + // Probably already have the port open. + cwarn << "Could not initialize with specified/default port. Trying system-assigned port"; + m_net.reset(new PeerServer(m_clientVersion, m_bc, 0, _mode, _publicIP, _upnp)); + } } - m_net->setIdealPeerCount(_peers); } @@ -227,19 +232,41 @@ void Client::startNetwork(unsigned short _listenPort, std::string const& _seedHo std::vector Client::peers() { - Guard l(x_net); + ReadGuard l(x_net); return m_net ? m_net->peers() : std::vector(); } size_t Client::peerCount() const { - Guard l(x_net); + ReadGuard l(x_net); return m_net ? m_net->peerCount() : 0; } +void Client::setIdealPeerCount(size_t _n) const +{ + ReadGuard l(x_net); + if (m_net) + return m_net->setIdealPeerCount(_n); +} + +bytes Client::savePeers() +{ + ReadGuard l(x_net); + if (m_net) + return m_net->savePeers(); + return bytes(); +} + +void Client::restorePeers(bytesConstRef _saved) +{ + ReadGuard l(x_net); + if (m_net) + return m_net->restorePeers(_saved); +} + void Client::connect(std::string const& _seedHost, unsigned short _port) { - Guard l(x_net); + ReadGuard l(x_net); if (!m_net.get()) return; m_net->connect(_seedHost, _port); @@ -248,7 +275,7 @@ void Client::connect(std::string const& _seedHost, unsigned short _port) void Client::stopNetwork() { { - Guard l(x_net); + WriteGuard l(x_net); m_net.reset(nullptr); } @@ -279,10 +306,12 @@ void Client::transact(Secret _secret, u256 _value, Address _dest, bytes const& _ { ensureWorking(); - ClientGuard l(this); Transaction t; // cdebug << "Nonce at " << toAddress(_secret) << " pre:" << m_preMine.transactionsFrom(toAddress(_secret)) << " post:" << m_postMine.transactionsFrom(toAddress(_secret)); - t.nonce = m_postMine.transactionsFrom(toAddress(_secret)); + { + ReadGuard l(x_stateDB); + t.nonce = m_postMine.transactionsFrom(toAddress(_secret)); + } t.value = _value; t.gasPrice = _gasPrice; t.gas = _gas; @@ -297,9 +326,11 @@ Address Client::transact(Secret _secret, u256 _endowment, bytes const& _init, u2 { ensureWorking(); - ClientGuard l(this); Transaction t; - t.nonce = m_postMine.transactionsFrom(toAddress(_secret)); + { + ReadGuard l(x_stateDB); + t.nonce = m_postMine.transactionsFrom(toAddress(_secret)); + } t.value = _endowment; t.gasPrice = _gasPrice; t.gas = _gas; @@ -315,7 +346,6 @@ void Client::inject(bytesConstRef _rlp) { ensureWorking(); - ClientGuard l(this); m_tq.attemptImport(_rlp); } @@ -324,7 +354,7 @@ void Client::workNet() // 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. - Guard l(x_net); + ReadGuard l(x_net); if (m_net) { cwork << "NETWORK"; @@ -355,7 +385,7 @@ void Client::work(bool _justQueue) m_mineProgress.best = (double)-1; m_mineProgress.hashes = 0; m_mineProgress.ms = 0; - ClientGuard l(this); + WriteGuard l(x_stateDB); if (m_paranoia) { if (m_postMine.amIJustParanoid(m_bc)) @@ -387,7 +417,7 @@ void Client::work(bool _justQueue) m_mineProgress.requirement = mineInfo.requirement; m_mineProgress.ms += 100; m_mineProgress.hashes += mineInfo.hashes; - ClientGuard l(this); + WriteGuard l(x_stateDB); m_mineHistory.push_back(mineInfo); if (mineInfo.completed) { @@ -420,7 +450,7 @@ void Client::work(bool _justQueue) // all blocks. // Resynchronise state with block chain & trans { - ClientGuard l(this); + WriteGuard l(x_stateDB); cwork << "BQ ==> CHAIN ==> STATE"; OverlayDB db = m_stateDB; @@ -466,16 +496,6 @@ void Client::work(bool _justQueue) cworkout << "WORK"; } -void Client::lock() const -{ - x_stateDB.lock(); -} - -void Client::unlock() const -{ - x_stateDB.unlock(); -} - unsigned Client::numberOf(int _n) const { if (_n > 0) @@ -488,6 +508,7 @@ unsigned Client::numberOf(int _n) const State Client::asOf(int _h) const { + ReadGuard l(x_stateDB); if (_h == 0) return m_postMine; else if (_h == -1) @@ -496,9 +517,38 @@ State Client::asOf(int _h) const return State(m_stateDB, m_bc, m_bc.numberHash(numberOf(_h))); } +State Client::state(unsigned _txi, h256 _block) const +{ + ReadGuard l(x_stateDB); + return State(m_stateDB, m_bc, _block).fromPending(_txi); +} + +eth::State Client::state(h256 _block) const +{ + ReadGuard l(x_stateDB); + return State(m_stateDB, m_bc, _block); +} + +eth::State Client::state(unsigned _txi) const +{ + ReadGuard l(x_stateDB); + return m_postMine.fromPending(_txi); +} + +StateDiff Client::diff(unsigned _txi, int _block) const +{ + State st = state(_block); + return st.fromPending(_txi).diff(st.fromPending(_txi + 1)); +} + +StateDiff Client::diff(unsigned _txi, h256 _block) const +{ + State st = state(_block); + return st.fromPending(_txi).diff(st.fromPending(_txi + 1)); +} + std::vector
Client::addresses(int _block) const { - ClientGuard l(this); vector
ret; for (auto const& i: asOf(_block).addresses()) ret.push_back(i.first); @@ -507,25 +557,26 @@ std::vector
Client::addresses(int _block) const u256 Client::balanceAt(Address _a, int _block) const { - ClientGuard l(this); return asOf(_block).balance(_a); } +std::map Client::storageAt(Address _a, int _block) const +{ + return asOf(_block).storage(_a); +} + u256 Client::countAt(Address _a, int _block) const { - ClientGuard l(this); return asOf(_block).transactionsFrom(_a); } u256 Client::stateAt(Address _a, u256 _l, int _block) const { - ClientGuard l(this); return asOf(_block).storage(_a, _l); } bytes Client::codeAt(Address _a, int _block) const { - ClientGuard l(this); return asOf(_block).code(_a); } @@ -642,8 +693,6 @@ bool TransactionFilter::matches(Manifest const& _m, vector _p, Address PastMessages Client::transactions(TransactionFilter const& _f) const { - ClientGuard l(this); - PastMessages ret; unsigned begin = min(m_bc.number(), (unsigned)_f.latest()); unsigned end = min(begin, (unsigned)_f.earliest()); @@ -653,6 +702,7 @@ PastMessages Client::transactions(TransactionFilter const& _f) const // Handle pending transactions differently as they're not on the block chain. if (begin == m_bc.number()) { + ReadGuard l(x_stateDB); for (unsigned i = 0; i < m_postMine.pending().size(); ++i) { // Might have a transaction that contains a matching message. diff --git a/libethereum/Client.h b/libethereum/Client.h index 405415d9f..420fe7ad3 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -27,7 +27,9 @@ #include #include #include +#include #include +#include "Guards.h" #include "BlockChain.h" #include "TransactionQueue.h" #include "State.h" @@ -47,16 +49,6 @@ struct MineProgress class Client; -class ClientGuard -{ -public: - inline ClientGuard(Client const* _c); - inline ~ClientGuard(); - -private: - Client const* m_client; -}; - enum ClientWorkState { Active = 0, @@ -198,19 +190,6 @@ public: // Informational stuff - // [OLD API]: - - /// Locks/unlocks the state/blockChain/transactionQueue for access. - void lock() const; - void unlock() const; - - /// Get the object representing the current state of Ethereum. - State const& state() const { return m_preMine; } - /// Get the object representing the current state of Ethereum. - State const& postState() const { return m_postMine; } - /// Get the object representing the current canonical blockchain. - BlockChain const& blockChain() const { return m_bc; } - // [NEW API] void setDefault(int _block) { m_default = _block; } @@ -219,29 +198,52 @@ public: u256 countAt(Address _a) const { return countAt(_a, m_default); } u256 stateAt(Address _a, u256 _l) const { return stateAt(_a, _l, m_default); } bytes codeAt(Address _a) const { return codeAt(_a, m_default); } + std::map storageAt(Address _a) const { return storageAt(_a, m_default); } u256 balanceAt(Address _a, int _block) const; u256 countAt(Address _a, int _block) const; u256 stateAt(Address _a, u256 _l, int _block) const; bytes codeAt(Address _a, int _block) const; - PastMessages transactions(TransactionFilter const& _filter) const; - PastMessages transactions(unsigned _watchId) const { try { std::lock_guard l(m_filterLock); return transactions(m_filters.at(m_watches.at(_watchId).id).filter); } catch (...) { return PastMessages(); } } + std::map storageAt(Address _a, int _block) const; + unsigned installWatch(TransactionFilter const& _filter); unsigned installWatch(h256 _filterId); void uninstallWatch(unsigned _watchId); bool peekWatch(unsigned _watchId) const { std::lock_guard l(m_filterLock); try { return m_watches.at(_watchId).changes != 0; } catch (...) { return false; } } bool checkWatch(unsigned _watchId) { std::lock_guard l(m_filterLock); bool ret = false; try { ret = m_watches.at(_watchId).changes != 0; m_watches.at(_watchId).changes = 0; } catch (...) {} return ret; } + PastMessages transactions(unsigned _watchId) const { try { std::lock_guard l(m_filterLock); return transactions(m_filters.at(m_watches.at(_watchId).id).filter); } catch (...) { return PastMessages(); } } + PastMessages transactions(TransactionFilter const& _filter) const; + // [EXTRA API]: /// Get a map containing each of the pending transactions. /// @TODO: Remove in favour of transactions(). Transactions pending() const { return m_postMine.pending(); } + /// Differences between transactions. + StateDiff diff(unsigned _txi) const { return diff(_txi, m_default); } + StateDiff diff(unsigned _txi, h256 _block) const; + StateDiff diff(unsigned _txi, int _block) const; + /// Get a list of all active addresses. std::vector
addresses() const { return addresses(m_default); } std::vector
addresses(int _block) const; + /// Get the fee associated for a transaction with the given data. + static u256 txGas(uint _dataCount, u256 _gas = 0) { return c_txDataGas * _dataCount + c_txGas + _gas; } + + // [PRIVATE API - only relevant for base clients, not available in general] + + eth::State state(unsigned _txi, h256 _block) const; + eth::State state(h256 _block) const; + eth::State state(unsigned _txi) const; + + /// Get the object representing the current state of Ethereum. + eth::State postState() const { ReadGuard l(x_stateDB); return m_postMine; } + /// Get the object representing the current canonical blockchain. + BlockChain const& blockChain() const { return m_bc; } + // Misc stuff: void setClientVersion(std::string const& _name) { m_clientVersion = _name; } @@ -252,6 +254,8 @@ public: std::vector peers(); /// Same as peers().size(), but more efficient. size_t peerCount() const; + /// Same as peers().size(), but more efficient. + void setIdealPeerCount(size_t _n) const; /// Start the network subsystem. void startNetwork(unsigned short _listenPort = 30303, std::string const& _remoteHost = std::string(), unsigned short _remotePort = 30303, NodeMode _mode = NodeMode::Full, unsigned _peers = 5, std::string const& _publicIP = std::string(), bool _upnp = true); @@ -260,9 +264,11 @@ public: /// Stop the network subsystem. void stopNetwork(); /// Is the network subsystem up? - bool haveNetwork() { Guard l(x_net); return !!m_net; } - /// Get access to the peer server object. This will be null if the network isn't online. DANGEROUS! DO NOT USE! - PeerServer* peerServer() const { Guard l(x_net); return m_net.get(); } + bool haveNetwork() { ReadGuard l(x_net); return !!m_net; } + /// Save peers + bytes savePeers(); + /// Restore peers + void restorePeers(bytesConstRef _saved); // Mining stuff: @@ -324,14 +330,14 @@ private: BlockChain m_bc; ///< Maintains block database. TransactionQueue m_tq; ///< Maintains a list of incoming transactions not yet in a block on the blockchain. BlockQueue m_bq; ///< Maintains a list of incoming blocks not yet on the blockchain (to be imported). - mutable std::recursive_mutex x_stateDB; // TODO: remove in favour of copying m_stateDB as required and thread-safing/copying State. Have a good think about what state objects there should be. Probably want 4 (pre, post, mining, user-visible). + mutable boost::shared_mutex x_stateDB; // TODO: remove in favour of copying m_stateDB as required and thread-safing/copying State. Have a good think about what state objects there should be. Probably want 4 (pre, post, mining, user-visible). OverlayDB m_stateDB; ///< Acts as the central point for the state database, so multiple States can share it. State m_preMine; ///< The present state of the client. State m_postMine; ///< The state of the client which we're mining (i.e. it'll have all the rewards added). std::unique_ptr m_workNet; ///< The network thread. std::atomic m_workNetState; - mutable std::mutex x_net; ///< Lock for the network. // TODO: make network thread-safe. + mutable boost::shared_mutex x_net; ///< Lock for the network existance. std::unique_ptr m_net; ///< Should run in background and send us events when blocks found and allow us to send blocks as required. std::unique_ptr m_work; ///< The work thread. @@ -350,14 +356,4 @@ private: int m_default = -1; }; -inline ClientGuard::ClientGuard(Client const* _c): m_client(_c) -{ - m_client->lock(); -} - -inline ClientGuard::~ClientGuard() -{ - m_client->unlock(); -} - } diff --git a/libethereum/State.h b/libethereum/State.h index ccf6bfdd0..9f014f0ab 100644 --- a/libethereum/State.h +++ b/libethereum/State.h @@ -264,15 +264,6 @@ public: /// @return the difference between this state (origin) and @a _c (destination). StateDiff diff(State const& _c) const; - /// Get the fee associated for a transaction with the given data. - u256 txGas(uint _dataCount, u256 _gas = 0) const { return c_txDataGas * _dataCount + c_txGas + _gas; } - - /// Get the fee associated for a contract created with the given data. - u256 createGas(uint _dataCount, u256 _gas = 0) const { return txGas(_dataCount, _gas); } - - /// Get the fee associated for a normal transaction. - u256 callGas(uint _dataCount, u256 _gas = 0) const { return txGas(_dataCount, _gas); } - /// Sync our state with the block chain. /// This basically involves wiping ourselves if we've been superceded and rebuilding from the transaction queue. bool sync(BlockChain const& _bc); diff --git a/libqethereum/QEthereum.cpp b/libqethereum/QEthereum.cpp index bc27c7f8a..7300b558a 100644 --- a/libqethereum/QEthereum.cpp +++ b/libqethereum/QEthereum.cpp @@ -187,50 +187,32 @@ QString QEthereum::balanceAt(QString _a) const QString QEthereum::storageAt(QString _a, QString _p) const { - if (!m_client) - return ""; - eth::ClientGuard l(const_cast(m_client)); - return toQJS(client()->postState().storage(toAddress(_a), toU256(_p))); + return m_client ? toQJS(client()->stateAt(toAddress(_a), toU256(_p))) : ""; } double QEthereum::txCountAt(QString _a) const { - if (!m_client) - return 0.0; - eth::ClientGuard l(const_cast(m_client)); - return (double)client()->postState().transactionsFrom(toAddress(_a)); + return m_client ? (double)client()->countAt(toAddress(_a)) : 0.0; } bool QEthereum::isContractAt(QString _a) const { - if (!m_client) - return false; - eth::ClientGuard l(const_cast(m_client)); - return client()->postState().addressHasCode(toAddress(_a)); + return m_client ? client()->codeAt(toAddress(_a)).size() : false; } u256 QEthereum::balanceAt(Address _a) const { - if (!m_client) - return 0; - eth::ClientGuard l(const_cast(m_client)); - return client()->postState().balance(_a); + return m_client ? client()->balanceAt(_a) : 0; } double QEthereum::txCountAt(Address _a) const { - if (!m_client) - return 0.0; - eth::ClientGuard l(const_cast(m_client)); - return (double)client()->postState().transactionsFrom(_a); + return m_client ? (double)client()->countAt(_a) : 0.0; } bool QEthereum::isContractAt(Address _a) const { - if (!m_client) - return false; - eth::ClientGuard l(const_cast(m_client)); - return client()->postState().addressHasCode(_a); + return m_client ? client()->codeAt(_a).size() : false; } QString QEthereum::balanceAt(QString _a, int _block) const diff --git a/neth/main.cpp b/neth/main.cpp index c085736b2..3eaeec7a9 100644 --- a/neth/main.cpp +++ b/neth/main.cpp @@ -481,10 +481,7 @@ int main(int argc, char** argv) if (!remoteHost.empty()) c.startNetwork(listenPort, remoteHost, remotePort, mode, peers, publicIP, upnp); if (mining) - { - ClientGuard g(&c); c.startMining(); - } #if ETH_JSONRPC auto_ptr jsonrpcServer; @@ -531,7 +528,6 @@ int main(int argc, char** argv) { eth::uint port; iss >> port; - ClientGuard g(&c); c.startNetwork((short)port); } else if (cmd == "connect") @@ -539,22 +535,18 @@ int main(int argc, char** argv) string addr; eth::uint port; iss >> addr >> port; - ClientGuard g(&c); c.connect(addr, (short)port); } else if (cmd == "netstop") { - ClientGuard g(&c); c.stopNetwork(); } else if (cmd == "minestart") { - ClientGuard g(&c); c.startMining(); } else if (cmd == "minestop") { - ClientGuard g(&c); c.stopMining(); } #if ETH_JSONRPC @@ -604,13 +596,12 @@ int main(int argc, char** argv) } else if (cmd == "balance") { - u256 balance = c.state().balance(us.address()); + u256 balance = c.balanceAt(us.address(), 0); ccout << "Current balance:" << endl; ccout << toString(balance) << endl; } else if (cmd == "transact") { - ClientGuard g(&c); auto const& bc = c.blockChain(); auto h = bc.currentHash(); auto blockData = bc.block(h); @@ -663,7 +654,7 @@ int main(int argc, char** argv) ssbd << bbd; cnote << ssbd.str(); int ssize = fields[4].length(); - u256 minGas = (u256)c.state().callGas(data.size(), 0); + u256 minGas = (u256)Client::txGas(data.size(), 0); if (size < 40) { if (size > 0) @@ -688,7 +679,6 @@ int main(int argc, char** argv) } else if (cmd == "send") { - ClientGuard g(&c); vector s; s.push_back("Address"); vector l; @@ -720,7 +710,7 @@ int main(int argc, char** argv) auto h = bc.currentHash(); auto blockData = bc.block(h); BlockInfo info(blockData); - u256 minGas = (u256)c.state().callGas(0, 0); + u256 minGas = (u256)Client::txGas(0, 0); Address dest = h160(fromHex(fields[0])); c.transact(us.secret(), amount, dest, bytes(), minGas, info.minGasPrice); } @@ -728,7 +718,6 @@ int main(int argc, char** argv) } else if (cmd == "contract") { - ClientGuard g(&c); auto const& bc = c.blockChain(); auto h = bc.currentHash(); auto blockData = bc.block(h); @@ -782,7 +771,7 @@ int main(int argc, char** argv) cnote << "Init:"; cnote << ssc.str(); } - u256 minGas = (u256)c.state().createGas(init.size(), 0); + u256 minGas = (u256)Client::txGas(init.size(), 0); if (endowment < 0) cwarn << "Invalid endowment"; else if (gasPrice < info.minGasPrice) @@ -804,16 +793,15 @@ int main(int argc, char** argv) cwarn << "Invalid address length"; else { - ClientGuard g(&c); - auto h = h160(fromHex(rechex)); + auto address = h160(fromHex(rechex)); stringstream s; try { - auto storage = c.state().storage(h); + auto storage = c.storageAt(address); for (auto const& i: storage) s << "@" << showbase << hex << i.first << " " << showbase << hex << i.second << endl; - s << endl << disassemble(c.state().code(h)) << endl; + s << endl << disassemble(c.codeAt(address)) << endl; string outFile = getDataDir() + "/" + rechex + ".evm"; ofstream ofs; @@ -848,9 +836,6 @@ int main(int argc, char** argv) // Lock to prevent corrupt block-chain errors - ClientGuard g(&c); - - auto const& st = c.state(); auto const& bc = c.blockChain(); ccout << "Genesis hash: " << bc.genesisHash() << endl; @@ -869,7 +854,7 @@ int main(int argc, char** argv) auto s = t.receiveAddress ? boost::format(" %1% %2%> %3%: %4% [%5%]") % toString(t.safeSender()) % - (st.addressHasCode(t.receiveAddress) ? '*' : '-') % + (c.codeAt(t.receiveAddress, 0).size() ? '*' : '-') % toString(t.receiveAddress) % toString(formatBalance(t.value)) % toString((unsigned)t.nonce) : @@ -894,7 +879,7 @@ int main(int argc, char** argv) auto s = t.receiveAddress ? boost::format("%1% %2%> %3%: %4% [%5%]") % toString(t.safeSender()) % - (st.addressHasCode(t.receiveAddress) ? '*' : '-') % + (c.codeAt(t.receiveAddress, 0).size() ? '*' : '-') % toString(t.receiveAddress) % toString(formatBalance(t.value)) % toString((unsigned)t.nonce) : @@ -912,38 +897,29 @@ int main(int argc, char** argv) // Contracts and addresses y = 1; int cc = 1; - auto acs = st.addresses(); + auto acs = c.addresses(); for (auto const& i: acs) - { - auto r = i.first; - - if (st.addressHasCode(r)) + if (c.codeAt(i, 0).size()) { auto s = boost::format("%1%%2% : %3% [%4%]") % - toString(r) % - pretty(r, st) % - toString(formatBalance(i.second)) % - toString((unsigned)st.transactionsFrom(i.first)); + toString(i) % + toString(formatBalance(c.balanceAt(i, 0))) % + toString((unsigned)c.countAt(i, 0)); mvwaddnstr(contractswin, cc++, x, s.str().c_str(), qwidth); if (cc > qheight - 2) break; } - } for (auto const& i: acs) - { - auto r = i.first; - if (!st.addressHasCode(r)) { + if (c.codeAt(i, 0).empty()) + { auto s = boost::format("%1%%2% : %3% [%4%]") % - toString(r) % - pretty(r, st) % - toString(formatBalance(i.second)) % - toString((unsigned)st.transactionsFrom(i.first)); + toString(i) % + toString(formatBalance(c.balanceAt(i, 0))) % + toString((unsigned)c.countAt(i, 0)); mvwaddnstr(addswin, y++, x, s.str().c_str(), width / 2 - 4); if (y > height * 3 / 5 - 4) break; } - } - // Peers y = 1; for (PeerInfo const& i: c.peers()) @@ -968,9 +944,9 @@ int main(int argc, char** argv) // Balance stringstream ssb; - u256 balance = c.state().balance(us.address()); + u256 balance = c.balanceAt(us.address(), 0); Address gavCoin("0115554959f43bf1d04cd7e3749d00fb0623ce1f"); - u256 totalGavCoinBalance = st.storage(gavCoin, (u160)us.address()); + u256 totalGavCoinBalance = c.stateAt(gavCoin, (u160)us.address()); ssb << "Balance: " << formatBalance(balance) << " | " << totalGavCoinBalance << " GAV"; mvwprintw(consolewin, 0, x, ssb.str().c_str()); diff --git a/walleth/MainWin.cpp b/walleth/MainWin.cpp index e7a5dca5b..d8f8a28c3 100644 --- a/walleth/MainWin.cpp +++ b/walleth/MainWin.cpp @@ -158,12 +158,9 @@ void Main::writeSettings() s.setValue("idealPeers", m_idealPeers); s.setValue("port", m_port); - if (client()->peerServer()) - { - bytes d = client()->peerServer()->savePeers(); + bytes d = client()->savePeers(); + if (d.size()) m_peers = QByteArray((char*)d.data(), (int)d.size()); - - } s.setValue("peers", m_peers); s.setValue("geometry", saveGeometry()); @@ -203,16 +200,8 @@ void Main::refreshNetwork() ui->peerCount->setText(QString::fromStdString(toString(ps.size())) + " peer(s)"); } -eth::State const& Main::state() const -{ - return ui->preview->isChecked() ? client()->postState() : client()->state(); -} - void Main::refresh() { - eth::ClientGuard l(client()); - auto const& st = state(); - auto d = client()->blockChain().details(); auto diff = BlockInfo(client()->blockChain().block()).difficulty; ui->blockCount->setText(QString("#%1 @%3 T%2").arg(d.number).arg(toLog2(d.totalDifficulty)).arg(toLog2(diff))); @@ -221,7 +210,7 @@ void Main::refresh() u256 totalBalance = 0; for (auto i: m_myKeys) { - u256 b = st.balance(i.address()); + u256 b = m_client->balanceAt(i.address()); totalBalance += b; } ui->balance->setText(QString::fromStdString(formatBalance(totalBalance))); @@ -244,7 +233,7 @@ void Main::on_net_triggered(bool _auto) else client()->startNetwork(m_port, string(), 0, NodeMode::Full, m_idealPeers, "", ui->upnp->isChecked()); if (m_peers.size()) - client()->peerServer()->restorePeers(bytesConstRef((byte*)m_peers.data(), m_peers.size())); + client()->restorePeers(bytesConstRef((byte*)m_peers.data(), m_peers.size())); } else client()->stopNetwork(); From 2eb7a4981906f54f0b3b73c7c5cecc933916e75c Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 28 Jul 2014 21:59:49 +0200 Subject: [PATCH 034/223] Networking stop fix. --- libethereum/Client.cpp | 81 +++++++++++++++++++++++------------------- 1 file changed, 44 insertions(+), 37 deletions(-) diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index bd8c0a15b..904ed2040 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -194,24 +194,23 @@ void Client::startNetwork(unsigned short _listenPort, std::string const& _seedHo { static const char* c_threadName = "net"; - if (!m_workNet) - m_workNet.reset(new thread([&]() - { - setThreadName(c_threadName); - m_workNetState.store(Active, std::memory_order_release); - while (m_workNetState.load(std::memory_order_acquire) != Deleting) - workNet(); - m_workNetState.store(Deleted, std::memory_order_release); - })); - - ensureWorking(); - { UpgradableGuard l(x_net); if (m_net.get()) return; { UpgradeGuard ul(l); + + if (!m_workNet) + m_workNet.reset(new thread([&]() + { + setThreadName(c_threadName); + m_workNetState.store(Active, std::memory_order_release); + while (m_workNetState.load(std::memory_order_acquire) != Deleting) + workNet(); + m_workNetState.store(Deleted, std::memory_order_release); + })); + try { m_net.reset(new PeerServer(m_clientVersion, m_bc, 0, _listenPort, _mode, _publicIP, _upnp)); @@ -228,6 +227,28 @@ void Client::startNetwork(unsigned short _listenPort, std::string const& _seedHo if (_seedHost.size()) connect(_seedHost, _port); + + ensureWorking(); +} + +void Client::stopNetwork() +{ + UpgradableGuard l(x_net); + + if (m_workNet) + { + if (m_workNetState.load(std::memory_order_acquire) == Active) + m_workNetState.store(Deleting, std::memory_order_release); + while (m_workNetState.load(std::memory_order_acquire) != Deleted) + this_thread::sleep_for(chrono::milliseconds(10)); + m_workNet->join(); + } + if (m_net) + { + UpgradeGuard ul(l); + m_net.reset(nullptr); + m_workNet.reset(nullptr); + } } std::vector Client::peers() @@ -272,23 +293,6 @@ void Client::connect(std::string const& _seedHost, unsigned short _port) m_net->connect(_seedHost, _port); } -void Client::stopNetwork() -{ - { - WriteGuard l(x_net); - m_net.reset(nullptr); - } - - if (m_workNet) - { - if (m_workNetState.load(std::memory_order_acquire) == Active) - m_workNetState.store(Deleting, std::memory_order_release); - while (m_workNetState.load(std::memory_order_acquire) != Deleted) - this_thread::sleep_for(chrono::milliseconds(10)); - m_workNet->join(); - } -} - void Client::startMining() { ensureWorking(); @@ -354,18 +358,21 @@ void Client::workNet() // 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. - ReadGuard l(x_net); - if (m_net) { - cwork << "NETWORK"; - m_net->process(); // must be in guard for now since it uses the blockchain. + ReadGuard l(x_net); + if (m_net) + { + cwork << "NETWORK"; + m_net->process(); // must be in guard for now since it uses the blockchain. - // returns h256Set as block hashes, once for each block that has come in/gone out. - cwork << "NET <==> TQ ; CHAIN ==> NET ==> BQ"; - m_net->sync(m_tq, m_bq); + // returns h256Set as block hashes, once for each block that has come in/gone out. + cwork << "NET <==> TQ ; CHAIN ==> NET ==> BQ"; + m_net->sync(m_tq, m_bq); - cwork << "TQ:" << m_tq.items() << "; BQ:" << m_bq.items(); + cwork << "TQ:" << m_tq.items() << "; BQ:" << m_bq.items(); + } } + this_thread::sleep_for(chrono::milliseconds(1)); } void Client::work(bool _justQueue) From c5fcae90959dea91dcba971946dafe894127bc7f Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 28 Jul 2014 22:36:28 +0200 Subject: [PATCH 035/223] parseData shared. --- CMakeLists.txt | 2 +- eth/main.cpp | 66 ++---------------------------- libethereum/All.h | 1 + libethereum/CMakeLists.txt | 4 ++ libethereum/Utility.cpp | 82 ++++++++++++++++++++++++++++++++++++++ libethereum/Utility.h | 32 +++++++++++++++ neth/CMakeLists.txt | 3 -- neth/main.cpp | 59 +-------------------------- stdserv.js | 2 +- 9 files changed, 126 insertions(+), 125 deletions(-) create mode 100644 libethereum/Utility.cpp create mode 100644 libethereum/Utility.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 9325fcb01..3ce9fb6d4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -289,7 +289,7 @@ else() if (LANGUAGES) find_package(Boost 1.53 REQUIRED COMPONENTS thread date_time) else() - find_package(Boost 1.53 REQUIRED COMPONENTS thread date_time system) + find_package(Boost 1.53 REQUIRED COMPONENTS thread date_time system regex) endif() set(QTQML 1) diff --git a/eth/main.cpp b/eth/main.cpp index d400b5c93..be2f9ccde 100644 --- a/eth/main.cpp +++ b/eth/main.cpp @@ -31,12 +31,7 @@ #endif #include #include -#include -#include -#include -#include -#include -#include +#include #if ETH_READLINE #include #include @@ -169,7 +164,7 @@ string pretty(h160 _a, eth::State _st) } return ns; } -bytes parseData(string _args); + int main(int argc, char** argv) { unsigned short listenPort = 30303; @@ -434,7 +429,7 @@ int main(int argc, char** argv) cnote << "Data:"; cnote << sdata; - bytes data = parseData(sdata); + bytes data = eth::parseData(sdata); cnote << "Bytes:"; string sbd = asString(data); bytes bbd = asBytes(sbd); @@ -681,58 +676,3 @@ int main(int argc, char** argv) return 0; } -bytes parseData(string _args) -{ - bytes m_data; - stringstream args(_args); - string arg; - int cc = 0; - while (args >> arg) - { - int al = arg.length(); - if (boost::starts_with(arg, "0x")) - { - bytes bs = fromHex(arg); - m_data += bs; - } - else if (arg[0] == '@') - { - arg = arg.substr(1, arg.length()); - if (boost::starts_with(arg, "0x")) - { - cnote << "hex: " << arg; - bytes bs = fromHex(arg); - int size = bs.size(); - if (size < 32) - for (auto i = 0; i < 32 - size; ++i) - m_data.push_back(0); - m_data += bs; - } - else if (boost::starts_with(arg, "\"") && boost::ends_with(arg, "\"")) - { - arg = arg.substr(1, arg.length() - 2); - cnote << "string: " << arg; - if (al < 32) - for (int i = 0; i < 32 - al; ++i) - m_data.push_back(0); - for (int i = 0; i < al; ++i) - m_data.push_back(arg[i]); - } - else - { - cnote << "value: " << arg; - bytes bs = toBigEndian(u256(arg)); - int size = bs.size(); - if (size < 32) - for (auto i = 0; i < 32 - size; ++i) - m_data.push_back(0); - m_data += bs; - } - } - else - for (int i = 0; i < al; ++i) - m_data.push_back(arg[i]); - cc++; - } - return m_data; -} diff --git a/libethereum/All.h b/libethereum/All.h index adc7fedb4..96f23aa74 100644 --- a/libethereum/All.h +++ b/libethereum/All.h @@ -12,3 +12,4 @@ #include "State.h" #include "Transaction.h" #include "TransactionQueue.h" +#include "Utility.h" diff --git a/libethereum/CMakeLists.txt b/libethereum/CMakeLists.txt index f50e709fb..22637d301 100644 --- a/libethereum/CMakeLists.txt +++ b/libethereum/CMakeLists.txt @@ -30,6 +30,7 @@ target_link_libraries(${EXECUTABLE} gmp) if("${TARGET_PLATFORM}" STREQUAL "w64") target_link_libraries(${EXECUTABLE} boost_system-mt-s) + target_link_libraries(${EXECUTABLE} boost_regex-mt-s) target_link_libraries(${EXECUTABLE} boost_filesystem-mt-s) target_link_libraries(${EXECUTABLE} boost_thread_win32-mt-s) target_link_libraries(${EXECUTABLE} iphlpapi) @@ -39,18 +40,21 @@ if("${TARGET_PLATFORM}" STREQUAL "w64") elseif (APPLE) # Latest mavericks boost libraries only come with -mt target_link_libraries(${EXECUTABLE} boost_system-mt) + target_link_libraries(${EXECUTABLE} boost_regex-mt) target_link_libraries(${EXECUTABLE} boost_filesystem-mt) target_link_libraries(${EXECUTABLE} boost_thread-mt) find_package(Threads REQUIRED) target_link_libraries(${EXECUTABLE} ${CMAKE_THREAD_LIBS_INIT}) elseif (UNIX) target_link_libraries(${EXECUTABLE} ${Boost_SYSTEM_LIBRARY}) + target_link_libraries(${EXECUTABLE} ${Boost_REGEX_LIBRARY}) target_link_libraries(${EXECUTABLE} ${Boost_FILESYSTEM_LIBRARY}) target_link_libraries(${EXECUTABLE} ${Boost_THREAD_LIBRARY}) target_link_libraries(${EXECUTABLE} ${Boost_DATE_TIME_LIBRARY}) target_link_libraries(${EXECUTABLE} ${CMAKE_THREAD_LIBS_INIT}) else () target_link_libraries(${EXECUTABLE} boost_system) + target_link_libraries(${EXECUTABLE} boost_regex) target_link_libraries(${EXECUTABLE} boost_filesystem) target_link_libraries(${EXECUTABLE} boost_thread) find_package(Threads REQUIRED) diff --git a/libethereum/Utility.cpp b/libethereum/Utility.cpp new file mode 100644 index 000000000..894041c59 --- /dev/null +++ b/libethereum/Utility.cpp @@ -0,0 +1,82 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file Utility.cpp + * @author Gav Wood + * @date 2014 + */ + +#include "Utility.h" + +#include +#include +using namespace std; +using namespace eth; + +bytes eth::parseData(string const& _args) +{ + bytes m_data; + + boost::smatch what; + static const boost::regex r("(@|\\$)?\"([^\"]*)\"(\\s.*)?"); + static const boost::regex d("(@|\\$)?([0-9]+)(\\s*(ether)|(finney)|(szabo))?(\\s.*)?"); + static const boost::regex h("(@|\\$)?(0x)?(([a-fA-F0-9])+)(\\s.*)?"); + + string s = _args; + while (s.size()) + if (boost::regex_match(s, what, d)) + { + u256 v((string)what[2]); + if (what[6] == "szabo") + v *= eth::szabo; + else if (what[5] == "finney") + v *= eth::finney; + else if (what[4] == "ether") + v *= eth::ether; + bytes bs = eth::toCompactBigEndian(v); + if (what[1] != "$") + for (auto i = bs.size(); i < 32; ++i) + m_data.push_back(0); + for (auto b: bs) + m_data.push_back(b); + s = what[7]; + } + else if (boost::regex_match(s, what, h)) + { + bytes bs = fromHex(((what[3].length() & 1) ? "0" : "") + what[3]); + if (what[1] != "$") + for (auto i = bs.size(); i < 32; ++i) + m_data.push_back(0); + for (auto b: bs) + m_data.push_back(b); + s = what[5]; + } + else if (boost::regex_match(s, what, r)) + { + for (auto i: (string)what[2]) + m_data.push_back((byte)i); + if (what[1] != "$") + for (int i = what[2].length(); i < 32; ++i) + m_data.push_back(0); + else + m_data.push_back(0); + s = what[3]; + } + else + s = s.substr(1); + + return m_data; +} diff --git a/libethereum/Utility.h b/libethereum/Utility.h new file mode 100644 index 000000000..8a0f5217d --- /dev/null +++ b/libethereum/Utility.h @@ -0,0 +1,32 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file Utility.h + * @author Gav Wood + * @date 2014 + */ + +#pragma once + +#include +#include + +namespace eth +{ + +bytes parseData(std::string const& _args); + +} diff --git a/neth/CMakeLists.txt b/neth/CMakeLists.txt index 153dbea31..3e8dab70d 100644 --- a/neth/CMakeLists.txt +++ b/neth/CMakeLists.txt @@ -34,17 +34,14 @@ if ("${TARGET_PLATFORM}" STREQUAL "w64") target_link_libraries(${EXECUTABLE} cryptopp) target_link_libraries(${EXECUTABLE} ncurses) target_link_libraries(${EXECUTABLE} form) - target_link_libraries(${EXECUTABLE} boost_regex-mt-s) target_link_libraries(${EXECUTABLE} boost_system-mt-s) target_link_libraries(${EXECUTABLE} boost_filesystem-mt-s) target_link_libraries(${EXECUTABLE} boost_thread_win32-mt-s) set(CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS) elseif (UNIX) - target_link_libraries(${EXECUTABLE} boost_regex) target_link_libraries(${EXECUTABLE} ncurses) target_link_libraries(${EXECUTABLE} form) else () - target_link_libraries(${EXECUTABLE} boost_regex) target_link_libraries(${EXECUTABLE} boost_system) target_link_libraries(${EXECUTABLE} boost_filesystem) target_link_libraries(${EXECUTABLE} ncurses) diff --git a/neth/main.cpp b/neth/main.cpp index 4b05eb2b8..608c9a1a1 100644 --- a/neth/main.cpp +++ b/neth/main.cpp @@ -24,7 +24,6 @@ #include #include #include -#include #include #include #if ETH_JSONRPC @@ -32,11 +31,7 @@ #endif #include #include -#include -#include -#include -#include -#include +#include #if ETH_JSONRPC #include #include @@ -300,8 +295,6 @@ int nc_window_streambuf::sync() } vector form_dialog(vector _sfields, vector _lfields, vector _bfields, int _cols, int _rows, string _post_form); -bytes parseData(string _args); - int main(int argc, char** argv) { @@ -648,7 +641,7 @@ int main(int argc, char** argv) string sdata = fields[5]; cnote << "Data:"; cnote << sdata; - bytes data = parseData(sdata); + bytes data = eth::parseData(sdata); cnote << "Bytes:"; string sbd = asString(data); bytes bbd = asBytes(sbd); @@ -1201,51 +1194,3 @@ vector form_dialog(vector _sv, vector _lv, vector> arg) - { - if (boost::regex_match(arg, h)) - { - if (boost::starts_with(arg, "0x")) - { - cnote << "hex: " << arg; - bytes bs = fromHex(arg); - int size = bs.size(); - if (size < 32) - for (auto i = 0; i < 32 - size; ++i) - m_data.push_back(0); - m_data += bs; - } - else - { - cnote << "value: " << arg; - bytes bs = toBigEndian(u256(arg)); - int size = bs.size(); - if (size < 32) - for (auto i = 0; i < 32 - size; ++i) - m_data.push_back(0); - m_data += bs; - } - } - else if (boost::regex_match(arg, r)) - { - arg = arg.substr(1, arg.length() - 2); - int al = arg.length(); - cnote << "string: " << arg; - for (int i = 0; i < al; ++i) - m_data.push_back(arg[i]); - if (al < 32) - for (int i = 0; i < 32 - al; ++i) - m_data.push_back(0); - } - } - return m_data; -} diff --git a/stdserv.js b/stdserv.js index 4d95f8a82..ff0d35d97 100644 --- a/stdserv.js +++ b/stdserv.js @@ -195,7 +195,7 @@ var exchangeCode = eth.lll(" (set 'arg1 'send) (set 'arg2 (address)) (set 'arg3 @xoffer) - (set 'arg4 'origin) + (set 'arg4 (caller)) (unless (msg allgas @offer 0 arg1 128) (stop)) }) (set 'list (sha3pair @offer @want)) From 83ad719ef15ae2ff4b0d1b7b93b34d2bc88b7aef Mon Sep 17 00:00:00 2001 From: Giacomo Tazzari Date: Mon, 28 Jul 2014 23:56:58 +0200 Subject: [PATCH 036/223] Fixed an array bound check in Main::on_debugStep_triggered() --- alethzero/MainWin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index b431c2afb..5b94c8753 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -1513,7 +1513,7 @@ void Main::on_create_triggered() void Main::on_debugStep_triggered() { auto l = m_history[ui->debugTimeline->value()].levels.size(); - if (ui->debugTimeline->value() < m_history.size() && m_history[ui->debugTimeline->value() + 1].levels.size() > l) + if ((ui->debugTimeline->value() + 1) < m_history.size() && m_history[ui->debugTimeline->value() + 1].levels.size() > l) { on_debugStepInto_triggered(); if (m_history[ui->debugTimeline->value()].levels.size() > l) From 9efb073fd16c61f9770671add560aba72b109980 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 28 Jul 2014 23:57:10 +0200 Subject: [PATCH 037/223] Avoid crash on Client delete. --- libethereum/Client.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index 904ed2040..c44b0bd52 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -100,6 +100,15 @@ void Client::ensureWorking() Client::~Client() { + if (m_work) + { + if (m_workState.load(std::memory_order_acquire) == Active) + m_workState.store(Deleting, std::memory_order_release); + while (m_workState.load(std::memory_order_acquire) != Deleted) + this_thread::sleep_for(chrono::milliseconds(10)); + m_work->join(); + m_work.reset(nullptr); + } stopNetwork(); } From 2d37f0151d0837655f7a668fced7e059c8d4ab36 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 29 Jul 2014 01:25:55 +0200 Subject: [PATCH 038/223] Fixed block buffer issue. --- libethcore/CommonEth.cpp | 2 +- libethential/Common.cpp | 2 +- libethereum/BlockChain.cpp | 1 + libethereum/BlockQueue.cpp | 45 +++++++++++++++++++++----------------- libethereum/BlockQueue.h | 7 +++++- 5 files changed, 34 insertions(+), 23 deletions(-) diff --git a/libethcore/CommonEth.cpp b/libethcore/CommonEth.cpp index 3c1a336eb..e88de28e1 100644 --- a/libethcore/CommonEth.cpp +++ b/libethcore/CommonEth.cpp @@ -29,7 +29,7 @@ using namespace eth; //#define ETH_ADDRESS_DEBUG 1 -const unsigned eth::c_protocolVersion = 24; +const unsigned eth::c_protocolVersion = 23; const unsigned eth::c_databaseVersion = 1; static const vector> g_units = diff --git a/libethential/Common.cpp b/libethential/Common.cpp index 070210abd..7c3d39f93 100644 --- a/libethential/Common.cpp +++ b/libethential/Common.cpp @@ -27,6 +27,6 @@ using namespace eth; namespace eth { -char const* EthVersion = "0.6.1"; +char const* EthVersion = "0.5.18"; } diff --git a/libethereum/BlockChain.cpp b/libethereum/BlockChain.cpp index 290afc589..43ce6f150 100644 --- a/libethereum/BlockChain.cpp +++ b/libethereum/BlockChain.cpp @@ -186,6 +186,7 @@ h256s BlockChain::sync(BlockQueue& _bq, OverlayDB const& _stateDB, unsigned _max } catch (...){} } + _bq.doneDrain(); return ret; } diff --git a/libethereum/BlockQueue.cpp b/libethereum/BlockQueue.cpp index da21e6929..3f636b102 100644 --- a/libethereum/BlockQueue.cpp +++ b/libethereum/BlockQueue.cpp @@ -34,7 +34,8 @@ bool BlockQueue::import(bytesConstRef _block, BlockChain const& _bc) h256 h = sha3(_block); UpgradableGuard l(m_lock); - if (m_readySet.count(h) || m_futureSet.count(h)) + + if (m_readySet.count(h) || m_drainingSet.count(h) || m_futureSet.count(h)) // Already know about this one. return false; @@ -65,39 +66,43 @@ bool BlockQueue::import(bytesConstRef _block, BlockChain const& _bc) if (bi.timestamp > (u256)time(0)) return false; - UpgradeGuard ul(l); - - // We now know it. - if (!m_readySet.count(bi.parentHash) && !_bc.details(bi.parentHash)) { - // We don't know the parent (yet) - queue it up for later. It'll get resent to us if we find out about its ancestry later on. - m_future.insert(make_pair(bi.parentHash, make_pair(h, _block.toBytes()))); - m_futureSet.insert(h); - return true; - } + UpgradeGuard ul(l); - // If valid, append to blocks. - m_ready.push_back(_block.toBytes()); - m_readySet.insert(h); + // We now know it. + if (!m_readySet.count(bi.parentHash) && !m_drainingSet.count(bi.parentHash) && !_bc.details(bi.parentHash)) + { + // We don't know the parent (yet) - queue it up for later. It'll get resent to us if we find out about its ancestry later on. + m_future.insert(make_pair(bi.parentHash, make_pair(h, _block.toBytes()))); + m_futureSet.insert(h); + } + else + { + // If valid, append to blocks. + m_ready.push_back(_block.toBytes()); + m_readySet.insert(h); - noteReadyWithoutWriteGuard(h); + noteReadyWithoutWriteGuard(h); + } + } return true; } void BlockQueue::noteReadyWithoutWriteGuard(h256 _good) { - h256s goodQueue(1, _good); + list goodQueue(1, _good); while (goodQueue.size()) { - auto r = m_future.equal_range(goodQueue.back()); - goodQueue.pop_back(); + auto r = m_future.equal_range(goodQueue.front()); + goodQueue.pop_front(); for (auto it = r.first; it != r.second; ++it) { - m_futureSet.erase(it->second.first); m_ready.push_back(it->second.second); - m_readySet.erase(it->second.first); - goodQueue.push_back(it->second.first); + auto newReady = it->second.first; + m_futureSet.erase(newReady); + m_readySet.insert(newReady); + goodQueue.push_back(newReady); } m_future.erase(r.first, r.second); } diff --git a/libethereum/BlockQueue.h b/libethereum/BlockQueue.h index 7ec68215a..d66bfabd9 100644 --- a/libethereum/BlockQueue.h +++ b/libethereum/BlockQueue.h @@ -43,7 +43,11 @@ public: bool import(bytesConstRef _tx, BlockChain const& _bc); /// Grabs the blocks that are ready, giving them in the correct order for insertion into the chain. - void drain(std::vector& o_out) { WriteGuard l(m_lock); swap(o_out, m_ready); m_readySet.clear(); } + /// Don't forget to call doneDrain() once you're done importing. + void drain(std::vector& o_out) { WriteGuard l(m_lock); if (m_drainingSet.empty()) { swap(o_out, m_ready); swap(m_drainingSet, m_readySet); } } + + /// Must be called after a drain() call. Notes that the drained blocks have been imported into the blockchain, so we can forget about them. + void doneDrain() { WriteGuard l(m_lock); m_drainingSet.clear(); } /// Notify the queue that the chain has changed and a new block has attained 'ready' status (i.e. is in the chain). void noteReady(h256 _b) { WriteGuard l(m_lock); noteReadyWithoutWriteGuard(_b); } @@ -56,6 +60,7 @@ private: mutable boost::shared_mutex m_lock; ///< General lock. std::set m_readySet; ///< All blocks ready for chain-import. + std::set m_drainingSet; ///< All blocks being imported. std::vector m_ready; ///< List of blocks, in correct order, ready for chain-import. std::set m_futureSet; ///< Set of all blocks whose parents are not ready/in-chain. std::multimap> m_future; ///< For transactions that have an unknown parent; we map their parent hash to the block stuff, and insert once the block appears. From d18a6c4e2631b361c3453d18cff90dce406ded29 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 29 Jul 2014 01:26:28 +0200 Subject: [PATCH 039/223] Fix version regression. --- libethcore/CommonEth.cpp | 2 +- libethential/Common.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libethcore/CommonEth.cpp b/libethcore/CommonEth.cpp index e88de28e1..3c1a336eb 100644 --- a/libethcore/CommonEth.cpp +++ b/libethcore/CommonEth.cpp @@ -29,7 +29,7 @@ using namespace eth; //#define ETH_ADDRESS_DEBUG 1 -const unsigned eth::c_protocolVersion = 23; +const unsigned eth::c_protocolVersion = 24; const unsigned eth::c_databaseVersion = 1; static const vector> g_units = diff --git a/libethential/Common.cpp b/libethential/Common.cpp index 7c3d39f93..2cc5c2e1c 100644 --- a/libethential/Common.cpp +++ b/libethential/Common.cpp @@ -27,6 +27,6 @@ using namespace eth; namespace eth { -char const* EthVersion = "0.5.18"; +char const* EthVersion = "0.6.2"; } From aba6bd8d424c485c9b2f5a2439164ecb76e7c756 Mon Sep 17 00:00:00 2001 From: caktux Date: Mon, 28 Jul 2014 22:12:36 -0400 Subject: [PATCH 040/223] neth: fix NameReg, pass the right number of arguments to boost::format --- neth/main.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/neth/main.cpp b/neth/main.cpp index 608c9a1a1..8d36390ca 100644 --- a/neth/main.cpp +++ b/neth/main.cpp @@ -898,6 +898,7 @@ int main(int argc, char** argv) { auto s = boost::format("%1%%2% : %3% [%4%]") % toString(i) % + pretty(i, c.postState()) % toString(formatBalance(c.balanceAt(i, 0))) % toString((unsigned)c.countAt(i, 0)); mvwaddnstr(contractswin, cc++, x, s.str().c_str(), qwidth); @@ -909,6 +910,7 @@ int main(int argc, char** argv) { auto s = boost::format("%1%%2% : %3% [%4%]") % toString(i) % + pretty(i, c.postState()) % toString(formatBalance(c.balanceAt(i, 0))) % toString((unsigned)c.countAt(i, 0)); mvwaddnstr(addswin, y++, x, s.str().c_str(), width / 2 - 4); From d49ce8c93b298e4024542725372f67b47482c43a Mon Sep 17 00:00:00 2001 From: Giacomo Tazzari Date: Tue, 29 Jul 2014 08:43:21 +0200 Subject: [PATCH 041/223] Revert "Fixed an array bound check in Main::on_debugStep_triggered()" (Messed up indentation) This reverts commit 83ad719ef15ae2ff4b0d1b7b93b34d2bc88b7aef. --- alethzero/MainWin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index 5b94c8753..b431c2afb 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -1513,7 +1513,7 @@ void Main::on_create_triggered() void Main::on_debugStep_triggered() { auto l = m_history[ui->debugTimeline->value()].levels.size(); - if ((ui->debugTimeline->value() + 1) < m_history.size() && m_history[ui->debugTimeline->value() + 1].levels.size() > l) + if (ui->debugTimeline->value() < m_history.size() && m_history[ui->debugTimeline->value() + 1].levels.size() > l) { on_debugStepInto_triggered(); if (m_history[ui->debugTimeline->value()].levels.size() > l) From 218f7f3a187d67f8570822ff2cf2c06bb6f6a797 Mon Sep 17 00:00:00 2001 From: Giacomo Tazzari Date: Tue, 29 Jul 2014 08:49:03 +0200 Subject: [PATCH 042/223] Fixed an array bound check in Main::on_debugStep_triggered() --- alethzero/MainWin.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index b431c2afb..a3dd89b75 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -1513,7 +1513,7 @@ void Main::on_create_triggered() void Main::on_debugStep_triggered() { auto l = m_history[ui->debugTimeline->value()].levels.size(); - if (ui->debugTimeline->value() < m_history.size() && m_history[ui->debugTimeline->value() + 1].levels.size() > l) + if ((ui->debugTimeline->value() + 1) < m_history.size() && m_history[ui->debugTimeline->value() + 1].levels.size() > l) { on_debugStepInto_triggered(); if (m_history[ui->debugTimeline->value()].levels.size() > l) From 8f15b0f6feb3a8bb50cecbc6b6944996af40bba8 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 29 Jul 2014 11:30:19 +0200 Subject: [PATCH 043/223] Remove unneeded lock. --- libethereum/Client.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index c44b0bd52..1f1248e76 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -171,8 +171,6 @@ void Client::uninstallWatch(unsigned _i) void Client::appendFromNewPending(h256 _bloom, h256Set& o_changed) const { lock_guard l(m_filterLock); - ReadGuard sl(x_stateDB); - for (pair const& i: m_filters) if ((unsigned)i.second.filter.latest() > m_bc.number() && i.second.filter.matches(_bloom)) o_changed.insert(i.first); From ad75ebaad34dc2b9c36cb2faf115987bce5546a0 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 29 Jul 2014 13:55:47 +0200 Subject: [PATCH 044/223] API alterations. Mostly s/transaction/message/. --- alethzero/MainWin.cpp | 16 ++++---- alethzero/MainWin.h | 4 +- libethereum/Client.cpp | 30 +++++++------- libethereum/Client.h | 83 ++++++++++++++++++++++++++++---------- libqethereum/QEthereum.cpp | 60 +++++++++++++-------------- libqethereum/QEthereum.h | 29 +++++++------ 6 files changed, 129 insertions(+), 93 deletions(-) diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index a3dd89b75..f8670314d 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -232,7 +232,7 @@ void Main::onKeysChanged() installBalancesWatch(); } -unsigned Main::installWatch(eth::TransactionFilter const& _tf, std::function const& _f) +unsigned Main::installWatch(eth::MessageFilter const& _tf, std::function const& _f) { auto ret = m_client->installWatch(_tf); m_handlers[ret] = _f; @@ -248,27 +248,27 @@ unsigned Main::installWatch(eth::h256 _tf, std::function const& _f) void Main::installWatches() { - installWatch(eth::TransactionFilter().altered(c_config, 0), [=](){ installNameRegWatch(); }); - installWatch(eth::TransactionFilter().altered(c_config, 1), [=](){ installCurrenciesWatch(); }); - installWatch(eth::NewPendingFilter, [=](){ onNewPending(); }); - installWatch(eth::NewBlockFilter, [=](){ onNewBlock(); }); + installWatch(eth::MessageFilter().altered(c_config, 0), [=](){ installNameRegWatch(); }); + installWatch(eth::MessageFilter().altered(c_config, 1), [=](){ installCurrenciesWatch(); }); + installWatch(eth::PendingChangedFilter, [=](){ onNewPending(); }); + installWatch(eth::ChainChangedFilter, [=](){ onNewBlock(); }); } void Main::installNameRegWatch() { m_client->uninstallWatch(m_nameRegFilter); - m_nameRegFilter = installWatch(eth::TransactionFilter().altered((u160)m_client->stateAt(c_config, 0)), [=](){ onNameRegChange(); }); + m_nameRegFilter = installWatch(eth::MessageFilter().altered((u160)m_client->stateAt(c_config, 0)), [=](){ onNameRegChange(); }); } void Main::installCurrenciesWatch() { m_client->uninstallWatch(m_currenciesFilter); - m_currenciesFilter = installWatch(eth::TransactionFilter().altered((u160)m_client->stateAt(c_config, 1)), [=](){ onCurrenciesChange(); }); + m_currenciesFilter = installWatch(eth::MessageFilter().altered((u160)m_client->stateAt(c_config, 1)), [=](){ onCurrenciesChange(); }); } void Main::installBalancesWatch() { - eth::TransactionFilter tf; + eth::MessageFilter tf; vector
altCoins; Address coinsAddr = right160(m_client->stateAt(c_config, 1)); diff --git a/alethzero/MainWin.h b/alethzero/MainWin.h index 24e9c5ae1..f3af3dcba 100644 --- a/alethzero/MainWin.h +++ b/alethzero/MainWin.h @@ -39,7 +39,7 @@ class Main; namespace eth { class Client; class State; -class TransactionFilter; +class MessageFilter; } class QQuickView; @@ -166,7 +166,7 @@ private: eth::u256 value() const; eth::u256 gasPrice() const; - unsigned installWatch(eth::TransactionFilter const& _tf, std::function const& _f); + unsigned installWatch(eth::MessageFilter const& _tf, std::function const& _f); unsigned installWatch(eth::h256 _tf, std::function const& _f); void onNewPending(); diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index 1f1248e76..723c5adef 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -30,12 +30,12 @@ using namespace std; using namespace eth; -void TransactionFilter::fillStream(RLPStream& _s) const +void MessageFilter::fillStream(RLPStream& _s) const { _s.appendList(8) << m_from << m_to << m_stateAltered << m_altered << m_earliest << m_latest << m_max << m_skip; } -h256 TransactionFilter::sha3() const +h256 MessageFilter::sha3() const { RLPStream s; fillStream(s); @@ -125,7 +125,7 @@ void Client::clearPending() h256Set changeds; for (unsigned i = 0; i < m_postMine.pending().size(); ++i) appendFromNewPending(m_postMine.bloom(i), changeds); - changeds.insert(NewPendingFilter); + changeds.insert(PendingChangedFilter); m_postMine = m_preMine; noteChanged(changeds); } @@ -133,12 +133,12 @@ void Client::clearPending() unsigned Client::installWatch(h256 _h) { auto ret = m_watches.size() ? m_watches.rbegin()->first + 1 : 0; - m_watches[ret] = Watch(_h); + m_watches[ret] = ClientWatch(_h); cwatch << "+++" << ret << _h; return ret; } -unsigned Client::installWatch(TransactionFilter const& _f) +unsigned Client::installWatch(MessageFilter const& _f) { lock_guard l(m_filterLock); @@ -444,8 +444,8 @@ void Client::work(bool _justQueue) { for (auto h: hs) appendFromNewBlock(h, changeds); - changeds.insert(NewBlockFilter); - //changeds.insert(NewPendingFilter); // if we mined the new block, then we've probably reset the pending transactions. + changeds.insert(ChainChangedFilter); + //changeds.insert(PendingChangedFilter); // if we mined the new block, then we've probably reset the pending transactions. } } } @@ -474,7 +474,7 @@ void Client::work(bool _justQueue) { for (auto i: newBlocks) appendFromNewBlock(i, changeds); - changeds.insert(NewBlockFilter); + changeds.insert(ChainChangedFilter); } x_stateDB.lock(); if (newBlocks.size()) @@ -487,7 +487,7 @@ void Client::work(bool _justQueue) cnote << "New block on chain: Restarting mining operation."; m_restartMining = true; // need to re-commit to mine. m_postMine = m_preMine; - changeds.insert(NewPendingFilter); + changeds.insert(PendingChangedFilter); } // returns h256s as blooms, once for each transaction. @@ -497,7 +497,7 @@ void Client::work(bool _justQueue) { for (auto i: newPendingBlooms) appendFromNewPending(i, changeds); - changeds.insert(NewPendingFilter); + changeds.insert(PendingChangedFilter); if (m_doMine) cnote << "Additional transaction ready: Restarting mining operation."; @@ -594,7 +594,7 @@ bytes Client::codeAt(Address _a, int _block) const return asOf(_block).code(_a); } -bool TransactionFilter::matches(h256 _bloom) const +bool MessageFilter::matches(h256 _bloom) const { auto have = [=](Address const& a) { return _bloom.contains(a.bloom()); }; if (m_from.size()) @@ -627,7 +627,7 @@ bool TransactionFilter::matches(h256 _bloom) const return true; } -bool TransactionFilter::matches(State const& _s, unsigned _i) const +bool MessageFilter::matches(State const& _s, unsigned _i) const { h256 b = _s.changesFromPending(_i).bloom(); if (!matches(b)) @@ -658,14 +658,14 @@ bool TransactionFilter::matches(State const& _s, unsigned _i) const return true; } -PastMessages TransactionFilter::matches(Manifest const& _m, unsigned _i) const +PastMessages MessageFilter::matches(Manifest const& _m, unsigned _i) const { PastMessages ret; matches(_m, vector(1, _i), _m.from, PastMessages(), ret); return ret; } -bool TransactionFilter::matches(Manifest const& _m, vector _p, Address _o, PastMessages _limbo, PastMessages& o_ret) const +bool MessageFilter::matches(Manifest const& _m, vector _p, Address _o, PastMessages _limbo, PastMessages& o_ret) const { bool ret; @@ -705,7 +705,7 @@ bool TransactionFilter::matches(Manifest const& _m, vector _p, Address return ret; } -PastMessages Client::transactions(TransactionFilter const& _f) const +PastMessages Client::messages(MessageFilter const& _f) const { PastMessages ret; unsigned begin = min(m_bc.number(), (unsigned)_f.latest()); diff --git a/libethereum/Client.h b/libethereum/Client.h index 420fe7ad3..a969284d5 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -93,10 +94,10 @@ struct PastMessage typedef std::vector PastMessages; -class TransactionFilter +class MessageFilter { public: - TransactionFilter(int _earliest = 0, int _latest = -1, unsigned _max = 10, unsigned _skip = 0): m_earliest(_earliest), m_latest(_latest), m_max(_max), m_skip(_skip) {} + MessageFilter(int _earliest = 0, int _latest = -1, unsigned _max = 10, unsigned _skip = 0): m_earliest(_earliest), m_latest(_latest), m_max(_max), m_skip(_skip) {} void fillStream(RLPStream& _s) const; h256 sha3() const; @@ -109,14 +110,14 @@ public: bool matches(State const& _s, unsigned _i) const; PastMessages matches(Manifest const& _m, unsigned _i) const; - TransactionFilter from(Address _a) { m_from.insert(_a); return *this; } - TransactionFilter to(Address _a) { m_to.insert(_a); return *this; } - TransactionFilter altered(Address _a, u256 _l) { m_stateAltered.insert(std::make_pair(_a, _l)); return *this; } - TransactionFilter altered(Address _a) { m_altered.insert(_a); return *this; } - TransactionFilter withMax(unsigned _m) { m_max = _m; return *this; } - TransactionFilter withSkip(unsigned _m) { m_skip = _m; return *this; } - TransactionFilter withEarliest(int _e) { m_earliest = _e; return *this; } - TransactionFilter withLatest(int _e) { m_latest = _e; return *this; } + MessageFilter from(Address _a) { m_from.insert(_a); return *this; } + MessageFilter to(Address _a) { m_to.insert(_a); return *this; } + MessageFilter altered(Address _a, u256 _l) { m_stateAltered.insert(std::make_pair(_a, _l)); return *this; } + MessageFilter altered(Address _a) { m_altered.insert(_a); return *this; } + MessageFilter withMax(unsigned _m) { m_max = _m; return *this; } + MessageFilter withSkip(unsigned _m) { m_skip = _m; return *this; } + MessageFilter withEarliest(int _e) { m_earliest = _e; return *this; } + MessageFilter withLatest(int _e) { m_latest = _e; return *this; } private: bool matches(Manifest const& _m, std::vector _p, Address _o, PastMessages _limbo, PastMessages& o_ret) const; @@ -133,19 +134,19 @@ private: struct InstalledFilter { - InstalledFilter(TransactionFilter const& _f): filter(_f) {} + InstalledFilter(MessageFilter const& _f): filter(_f) {} - TransactionFilter filter; + MessageFilter filter; unsigned refCount = 1; }; -static const h256 NewPendingFilter = u256(0); -static const h256 NewBlockFilter = u256(1); +static const h256 PendingChangedFilter = u256(0); +static const h256 ChainChangedFilter = u256(1); -struct Watch +struct ClientWatch { - Watch() {} - explicit Watch(h256 _id): id(_id) {} + ClientWatch() {} + explicit ClientWatch(h256 _id): id(_id) {} h256 id; unsigned changes = 1; @@ -192,6 +193,7 @@ public: // [NEW API] + int getDefault() const { return m_default; } void setDefault(int _block) { m_default = _block; } u256 balanceAt(Address _a) const { return balanceAt(_a, m_default); } @@ -206,14 +208,14 @@ public: bytes codeAt(Address _a, int _block) const; std::map storageAt(Address _a, int _block) const; - unsigned installWatch(TransactionFilter const& _filter); + unsigned installWatch(MessageFilter const& _filter); unsigned installWatch(h256 _filterId); void uninstallWatch(unsigned _watchId); bool peekWatch(unsigned _watchId) const { std::lock_guard l(m_filterLock); try { return m_watches.at(_watchId).changes != 0; } catch (...) { return false; } } bool checkWatch(unsigned _watchId) { std::lock_guard l(m_filterLock); bool ret = false; try { ret = m_watches.at(_watchId).changes != 0; m_watches.at(_watchId).changes = 0; } catch (...) {} return ret; } - PastMessages transactions(unsigned _watchId) const { try { std::lock_guard l(m_filterLock); return transactions(m_filters.at(m_watches.at(_watchId).id).filter); } catch (...) { return PastMessages(); } } - PastMessages transactions(TransactionFilter const& _filter) const; + PastMessages messages(unsigned _watchId) const { try { std::lock_guard l(m_filterLock); return messages(m_filters.at(m_watches.at(_watchId).id).filter); } catch (...) { return PastMessages(); } } + PastMessages messages(MessageFilter const& _filter) const; // [EXTRA API]: @@ -351,9 +353,48 @@ private: mutable std::mutex m_filterLock; std::map m_filters; - std::map m_watches; + std::map m_watches; int m_default = -1; }; +class Watch; + +} + +namespace std { void swap(eth::Watch& _a, eth::Watch& _b); } + +namespace eth +{ + +class Watch: public boost::noncopyable +{ + friend void std::swap(Watch& _a, Watch& _b); + +public: + Watch() {} + Watch(Client& _c, h256 _f): m_c(&_c), m_id(_c.installWatch(_f)) {} + Watch(Client& _c, MessageFilter const& _tf): m_c(&_c), m_id(_c.installWatch(_tf)) {} + ~Watch() { if (m_c) m_c->uninstallWatch(m_id); } + + bool check() { return m_c ? m_c->checkWatch(m_id) : false; } + bool peek() { return m_c ? m_c->peekWatch(m_id) : false; } + PastMessages messages() const { return m_c->messages(m_id); } + +private: + Client* m_c; + unsigned m_id; +}; + +} + +namespace std +{ + +inline void swap(eth::Watch& _a, eth::Watch& _b) +{ + swap(_a.m_c, _b.m_c); + swap(_a.m_id, _b.m_id); +} + } diff --git a/libqethereum/QEthereum.cpp b/libqethereum/QEthereum.cpp index 7300b558a..f6b114b6f 100644 --- a/libqethereum/QEthereum.cpp +++ b/libqethereum/QEthereum.cpp @@ -180,54 +180,50 @@ void QEthereum::setCoinbase(QString _a) } } -QString QEthereum::balanceAt(QString _a) const +void QEthereum::setDefault(int _block) { - return m_client ? toQJS(client()->postState().balance(toAddress(_a))) : ""; -} - -QString QEthereum::storageAt(QString _a, QString _p) const -{ - return m_client ? toQJS(client()->stateAt(toAddress(_a), toU256(_p))) : ""; + if (m_client) + m_client->setDefault(_block); } -double QEthereum::txCountAt(QString _a) const +int QEthereum::getDefault() const { - return m_client ? (double)client()->countAt(toAddress(_a)) : 0.0; + return m_client ? m_client->getDefault() : 0; } -bool QEthereum::isContractAt(QString _a) const +QString QEthereum::balanceAt(QString _a) const { - return m_client ? client()->codeAt(toAddress(_a)).size() : false; + return m_client ? toQJS(client()->balanceAt(toAddress(_a))) : ""; } -u256 QEthereum::balanceAt(Address _a) const +QString QEthereum::balanceAt(QString _a, int _block) const { - return m_client ? client()->balanceAt(_a) : 0; + return m_client ? toQJS(client()->balanceAt(toAddress(_a), _block)) : ""; } -double QEthereum::txCountAt(Address _a) const +QString QEthereum::stateAt(QString _a, QString _p) const { - return m_client ? (double)client()->countAt(_a) : 0.0; + return m_client ? toQJS(client()->stateAt(toAddress(_a), toU256(_p))) : ""; } -bool QEthereum::isContractAt(Address _a) const +QString QEthereum::stateAt(QString _a, QString _p, int _block) const { - return m_client ? client()->codeAt(_a).size() : false; + return m_client ? toQJS(client()->stateAt(toAddress(_a), toU256(_p), _block)) : ""; } -QString QEthereum::balanceAt(QString _a, int _block) const +QString QEthereum::codeAt(QString _a) const { - return m_client ? toQJS(client()->balanceAt(toAddress(_a), _block)) : ""; + return m_client ? ::fromBinary(client()->codeAt(toAddress(_a))) : ""; } -QString QEthereum::stateAt(QString _a, QString _p, int _block) const +QString QEthereum::codeAt(QString _a, int _block) const { - return m_client ? toQJS(client()->stateAt(toAddress(_a), toU256(_p), _block)) : ""; + return m_client ? ::fromBinary(client()->codeAt(toAddress(_a), _block)) : ""; } -QString QEthereum::codeAt(QString _a, int _block) const +double QEthereum::countAt(QString _a) const { - return m_client ? ::fromBinary(client()->codeAt(toAddress(_a), _block)) : ""; + return m_client ? (double)(uint64_t)client()->countAt(toAddress(_a)) : 0; } double QEthereum::countAt(QString _a, int _block) const @@ -235,9 +231,9 @@ double QEthereum::countAt(QString _a, int _block) const return m_client ? (double)(uint64_t)client()->countAt(toAddress(_a), _block) : 0; } -static eth::TransactionFilter toTransactionFilter(QString _json) +static eth::MessageFilter toMessageFilter(QString _json) { - eth::TransactionFilter filter; + eth::MessageFilter filter; QJsonObject f = QJsonDocument::fromJson(_json.toUtf8()).object(); if (f.contains("earliest")) @@ -305,9 +301,9 @@ static QString toJson(eth::PastMessages const& _pms) return QString::fromUtf8(QJsonDocument(jsonArray).toJson()); } -QString QEthereum::getTransactions(QString _json) const +QString QEthereum::getMessages(QString _json) const { - return m_client ? toJson(m_client->transactions(toTransactionFilter(_json))) : ""; + return m_client ? toJson(m_client->messages(toMessageFilter(_json))) : ""; } bool QEthereum::isMining() const @@ -369,20 +365,20 @@ unsigned QEthereum::newWatch(QString _json) return (unsigned)-1; unsigned ret; if (_json == "chainChanged") - ret = m_client->installWatch(eth::NewBlockFilter); + ret = m_client->installWatch(eth::ChainChangedFilter); else if (_json == "pendingChanged") - ret = m_client->installWatch(eth::NewPendingFilter); + ret = m_client->installWatch(eth::PendingChangedFilter); else - ret = m_client->installWatch(toTransactionFilter(_json)); + ret = m_client->installWatch(toMessageFilter(_json)); m_watches.push_back(ret); return ret; } -QString QEthereum::watchTransactions(unsigned _w) +QString QEthereum::watchMessages(unsigned _w) { if (!m_client) return ""; - return toJson(m_client->transactions(_w)); + return toJson(m_client->messages(_w)); } void QEthereum::killWatch(unsigned _w) diff --git a/libqethereum/QEthereum.h b/libqethereum/QEthereum.h index 70a41c32d..68bc76cdb 100644 --- a/libqethereum/QEthereum.h +++ b/libqethereum/QEthereum.h @@ -112,24 +112,24 @@ public: Q_INVOKABLE QString fromBinary(QString _s) const { return ::fromBinary(_s); } Q_INVOKABLE QString toDecimal(QString _s) const { return ::toDecimal(_s); } - // [OLD API] - Don't use this. - Q_INVOKABLE QString/*eth::u256*/ balanceAt(QString/*eth::Address*/ _a) const; - Q_INVOKABLE QString/*eth::u256*/ storageAt(QString/*eth::Address*/ _a, QString/*eth::u256*/ _p) const; - Q_INVOKABLE double txCountAt(QString/*eth::Address*/ _a) const; - Q_INVOKABLE bool isContractAt(QString/*eth::Address*/ _a) const; - // [NEW API] - Use this instead. Q_INVOKABLE QString/*eth::u256*/ balanceAt(QString/*eth::Address*/ _a, int _block) const; Q_INVOKABLE double countAt(QString/*eth::Address*/ _a, int _block) const; Q_INVOKABLE QString/*eth::u256*/ stateAt(QString/*eth::Address*/ _a, QString/*eth::u256*/ _p, int _block) const; Q_INVOKABLE QString/*eth::u256*/ codeAt(QString/*eth::Address*/ _a, int _block) const; - Q_INVOKABLE QString/*json*/ getTransactions(QString _attribs/*json*/) const; + + Q_INVOKABLE QString/*eth::u256*/ balanceAt(QString/*eth::Address*/ _a) const; + Q_INVOKABLE double countAt(QString/*eth::Address*/ _a) const; + Q_INVOKABLE QString/*eth::u256*/ stateAt(QString/*eth::Address*/ _a, QString/*eth::u256*/ _p) const; + Q_INVOKABLE QString/*eth::u256*/ codeAt(QString/*eth::Address*/ _a) const; + + Q_INVOKABLE QString/*json*/ getMessages(QString _attribs/*json*/) const; Q_INVOKABLE QString doCreate(QString _secret, QString _amount, QString _init, QString _gas, QString _gasPrice); Q_INVOKABLE void doTransact(QString _secret, QString _amount, QString _dest, QString _data, QString _gas, QString _gasPrice); Q_INVOKABLE unsigned newWatch(QString _json); - Q_INVOKABLE QString watchTransactions(unsigned _w); + Q_INVOKABLE QString watchMessages(unsigned _w); Q_INVOKABLE void killWatch(unsigned _w); void clearWatches(); @@ -138,11 +138,7 @@ public: QString/*eth::Address*/ coinbase() const; QString/*eth::u256*/ gasPrice() const { return toQJS(10 * eth::szabo); } - - QString number() const; - eth::u256 balanceAt(eth::Address _a) const; - double txCountAt(eth::Address _a) const; - bool isContractAt(eth::Address _a) const; + int getDefault() const; QString/*eth::KeyPair*/ key() const; QStringList/*list of eth::KeyPair*/ keys() const; @@ -155,6 +151,7 @@ public slots: void setCoinbase(QString/*eth::Address*/); void setMining(bool _l); void setListening(bool _l); + void setDefault(int _block); /// Check to see if anything has changed, fire off signals if so. /// @note Must be called in the QObject's thread. @@ -176,6 +173,7 @@ private: Q_PROPERTY(bool mining READ isMining WRITE setMining NOTIFY netChanged) Q_PROPERTY(bool listening READ isListening WRITE setListening NOTIFY netChanged) Q_PROPERTY(unsigned peerCount READ peerCount NOTIFY miningChanged) + Q_PROPERTY(int defaultBlock READ getDefault NOTIFY setDefault) eth::Client* m_client; std::vector m_watches; @@ -187,13 +185,14 @@ private: frame->disconnect(); \ frame->addToJavaScriptWindowObject("env", env, QWebFrame::QtOwnership); \ frame->addToJavaScriptWindowObject("eth", eth, QWebFrame::ScriptOwnership); \ - frame->evaluateJavaScript("eth.makeWatch = function(a) { var ww = eth.newWatch(a); var ret = { w: ww }; ret.uninstall = function() { eth.killWatch(w); }; ret.changed = function(f) { eth.watchChanged.connect(function(nw) { if (nw == ww) f() }); }; ret.transactions = function() { return JSON.parse(eth.watchTransactions(this.w)) }; return ret; }"); \ + frame->evaluateJavaScript("eth.makeWatch = function(a) { var ww = eth.newWatch(a); var ret = { w: ww }; ret.uninstall = function() { eth.killWatch(w); }; ret.changed = function(f) { eth.watchChanged.connect(function(nw) { if (nw == ww) f() }); }; ret.messages = function() { return JSON.parse(eth.watchMessages(this.w)) }; return ret; }"); \ frame->evaluateJavaScript("eth.watch = function(a) { return eth.makeWatch(JSON.stringify(a)) }"); \ frame->evaluateJavaScript("eth.watchChain = function() { return eth.makeWatch('chainChanged') }"); \ frame->evaluateJavaScript("eth.watchPending = function() { return eth.makeWatch('pendingChanged') }"); \ frame->evaluateJavaScript("eth.create = function(s, v, c, g, p, f) { var v = eth.doCreate(s, v, c, g, p); if (f) f(v) }"); \ frame->evaluateJavaScript("eth.transact = function(s, v, t, d, g, p, f) { eth.doTransact(s, v, t, d, g, p); if (f) f() }"); \ - frame->evaluateJavaScript("eth.transactions = function(a) { return JSON.parse(eth.getTransactions(JSON.stringify(a))); }"); \ + frame->evaluateJavaScript("eth.messages = function(a) { return JSON.parse(eth.getMessages(JSON.stringify(a))); }"); \ + frame->evaluateJavaScript("eth.transactions = function(a) { env.warn('THIS CALL IS DEPRECATED. USE eth.messages INSTEAD.'); return JSON.parse(eth.getMessages(JSON.stringify(a))); }"); \ frame->evaluateJavaScript("String.prototype.pad = function(l, r) { return eth.pad(this, l, r) }"); \ frame->evaluateJavaScript("String.prototype.bin = function() { return eth.toBinary(this) }"); \ frame->evaluateJavaScript("String.prototype.unbin = function(l) { return eth.fromBinary(this) }"); \ From 15bcd0b220a8e7b023454bdebfb20e3531047650 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 29 Jul 2014 15:25:32 +0200 Subject: [PATCH 045/223] Fix missing call. --- libqethereum/QEthereum.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libqethereum/QEthereum.h b/libqethereum/QEthereum.h index 68bc76cdb..e5476613e 100644 --- a/libqethereum/QEthereum.h +++ b/libqethereum/QEthereum.h @@ -138,6 +138,7 @@ public: QString/*eth::Address*/ coinbase() const; QString/*eth::u256*/ gasPrice() const { return toQJS(10 * eth::szabo); } + QString/*eth::u256*/ number() const; int getDefault() const; QString/*eth::KeyPair*/ key() const; @@ -173,7 +174,7 @@ private: Q_PROPERTY(bool mining READ isMining WRITE setMining NOTIFY netChanged) Q_PROPERTY(bool listening READ isListening WRITE setListening NOTIFY netChanged) Q_PROPERTY(unsigned peerCount READ peerCount NOTIFY miningChanged) - Q_PROPERTY(int defaultBlock READ getDefault NOTIFY setDefault) + Q_PROPERTY(int defaultBlock READ getDefault WRITE setDefault) eth::Client* m_client; std::vector m_watches; From f49b6b947605a352fa163dabd9917a7fb534a614 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 29 Jul 2014 15:53:00 +0200 Subject: [PATCH 046/223] Avoid mining on empty blocks. Protocol bump. --- alethzero/Main.ui | 9 ++++++--- alethzero/MainWin.cpp | 6 ++++-- alethzero/MainWin.h | 2 +- libethcore/CommonEth.cpp | 2 +- libethereum/Client.cpp | 8 +++++++- libethereum/Client.h | 5 +++++ libethereum/State.cpp | 2 +- 7 files changed, 25 insertions(+), 9 deletions(-) diff --git a/alethzero/Main.ui b/alethzero/Main.ui index bb9b4c5be..a01ac7856 100644 --- a/alethzero/Main.ui +++ b/alethzero/Main.ui @@ -184,7 +184,7 @@ - + @@ -1559,9 +1559,12 @@ font-size: 14pt Ctrl+F10 - + + + true + - &Clear Pending + &Force Mining diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index f8670314d..d31b8c734 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -327,9 +327,9 @@ void Main::onNewPending() refreshAccounts(); } -void Main::on_clearPending_triggered() +void Main::on_forceMining_triggered() { - m_client->clearPending(); + m_client->setForceMining(ui->forceMining->isChecked()); } void Main::load(QString _s) @@ -485,6 +485,7 @@ void Main::writeSettings() s.setValue("upnp", ui->upnp->isChecked()); s.setValue("forceAddress", ui->forceAddress->text()); s.setValue("usePast", ui->usePast->isChecked()); + s.setValue("forceMining", ui->forceMining->isChecked()); s.setValue("paranoia", ui->paranoia->isChecked()); s.setValue("showAll", ui->showAll->isChecked()); s.setValue("showAllAccounts", ui->showAllAccounts->isChecked()); @@ -529,6 +530,7 @@ void Main::readSettings() ui->upnp->setChecked(s.value("upnp", true).toBool()); ui->forceAddress->setText(s.value("forceAddress", "").toString()); ui->usePast->setChecked(s.value("usePast", true).toBool()); + ui->forceMining->setChecked(s.value("forceMining", false).toBool()); ui->paranoia->setChecked(s.value("paranoia", false).toBool()); ui->showAll->setChecked(s.value("showAll", false).toBool()); ui->showAllAccounts->setChecked(s.value("showAllAccounts", false).toBool()); diff --git a/alethzero/MainWin.h b/alethzero/MainWin.h index f3af3dcba..1098e3e1e 100644 --- a/alethzero/MainWin.h +++ b/alethzero/MainWin.h @@ -125,7 +125,7 @@ private slots: void on_showAllAccounts_triggered() { refreshAccounts(); } void on_loadJS_triggered(); void on_blockChainFilter_textChanged(); - void on_clearPending_triggered(); + void on_forceMining_triggered(); void on_dumpTrace_triggered(); void on_dumpTraceStorage_triggered(); void on_dumpTracePretty_triggered(); diff --git a/libethcore/CommonEth.cpp b/libethcore/CommonEth.cpp index 3c1a336eb..08d9d2de4 100644 --- a/libethcore/CommonEth.cpp +++ b/libethcore/CommonEth.cpp @@ -29,7 +29,7 @@ using namespace eth; //#define ETH_ADDRESS_DEBUG 1 -const unsigned eth::c_protocolVersion = 24; +const unsigned eth::c_protocolVersion = 25; const unsigned eth::c_databaseVersion = 1; static const vector> g_units = diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index 723c5adef..47beb94a3 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -388,7 +388,7 @@ void Client::work(bool _justQueue) h256Set changeds; // Do some mining. - if (!_justQueue) + if (!_justQueue && (m_pendingCount || m_forceMining)) { // TODO: Separate "Miner" object. @@ -455,6 +455,11 @@ void Client::work(bool _justQueue) this_thread::sleep_for(chrono::milliseconds(100)); } } + else + { + cwork << "SLEEP"; + this_thread::sleep_for(chrono::milliseconds(100)); + } // Synchronise state to block chain. // This should remove any transactions on our queue that are included within our state. @@ -503,6 +508,7 @@ void Client::work(bool _justQueue) cnote << "Additional transaction ready: Restarting mining operation."; m_restartMining = true; } + m_pendingCount = m_postMine.pending().size(); } cwork << "noteChanged" << changeds.size() << "items"; diff --git a/libethereum/Client.h b/libethereum/Client.h index a969284d5..e9b10b538 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -295,6 +295,9 @@ public: /// Get and clear the mining history. std::list miningHistory() { auto ret = m_mineHistory; m_mineHistory.clear(); return ret; } + bool forceMining() const { return m_forceMining; } + void setForceMining(bool _enable) { m_forceMining = _enable; } + /// Clears pending transactions. Just for debug use. void clearPending(); @@ -347,9 +350,11 @@ private: bool m_paranoia = false; bool m_doMine = false; ///< Are we supposed to be mining? + bool m_forceMining = false; ///< Mine even when there are no transactions pending? MineProgress m_mineProgress; std::list m_mineHistory; mutable bool m_restartMining = false; + mutable unsigned m_pendingCount = 0; mutable std::mutex m_filterLock; std::map m_filters; diff --git a/libethereum/State.cpp b/libethereum/State.cpp index f7fd5049c..4484391ca 100644 --- a/libethereum/State.cpp +++ b/libethereum/State.cpp @@ -681,7 +681,7 @@ void State::commitToMine(BlockChain const& _bc) { uncommitToMine(); - cnote << "Commiting to mine on block" << m_previousBlock.hash; + cnote << "Committing to mine on block" << m_previousBlock.hash; #ifdef ETH_PARANOIA commit(); cnote << "Pre-reward stateRoot:" << m_state.root(); From 46f44d1eb8ce3abbfd4cfaee0016794899de6b7c Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 29 Jul 2014 20:09:27 +0200 Subject: [PATCH 047/223] Fix butrace bug in AZ. Added dump trace to eth. --- alethzero/MainWin.cpp | 5 ++-- eth/main.cpp | 55 ++++++++++++++++++++++++++++++++++++------- 2 files changed, 50 insertions(+), 10 deletions(-) diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index d31b8c734..0226ac35d 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -1079,9 +1079,10 @@ void Main::on_debugCurrent_triggered() if (!item->data(Qt::UserRole + 1).isNull()) { unsigned txi = item->data(Qt::UserRole + 1).toInt(); - m_executiveState = m_client->state(txi, h); + m_executiveState = m_client->state(txi + 1, h); m_currentExecution = unique_ptr(new Executive(m_executiveState)); - Transaction t = m_client->pending()[txi]; + Transaction t = m_executiveState.pending()[txi]; + m_executiveState = m_executiveState.fromPending(txi); auto r = t.rlp(); populateDebugger(&r); m_currentExecution.reset(); diff --git a/eth/main.cpp b/eth/main.cpp index be2f9ccde..0f351c136 100644 --- a/eth/main.cpp +++ b/eth/main.cpp @@ -31,6 +31,7 @@ #endif #include #include +#include #include #if ETH_READLINE #include @@ -68,6 +69,7 @@ void interactiveHelp() << " verbosity () Gets or sets verbosity level." << endl << " minestart Starts mining." << endl << " minestop Stops mining." << endl + << " mineforce Forces mining, even when there are no transactions." << endl << " address Gives the current address." << endl << " secret Gives the current secret" << endl << " block Gives the current block height." << endl @@ -76,13 +78,14 @@ void interactiveHelp() << " send Execute a given transaction with current secret." << endl << " contract Create a new contract with current secret." << endl << " peers List the peers that are connected" << endl - << " listAccounts List the accounts on the network." << endl - << " listContracts List the contracts on the network." << endl - << " setSecret Set the secret to the hex secret key." < Set the coinbase (mining payout) address." < Export the config (.RLP) to the path provided." < Import the config (.RLP) from the path provided." < Dumps a contract to /.evm." << endl + << " listAccounts List the accounts on the network." << endl + << " listContracts List the contracts on the network." << endl + << " setSecret Set the secret to the hex secret key." < Set the coinbase (mining payout) address." < Export the config (.RLP) to the path provided." < Import the config (.RLP) from the path provided." < Dumps a contract to /.evm." << endl + << " dumptrace Dumps a transaction trace" << endl << "to . should be one of pretty, standard, standard+." << endl << " exit Exits the application." << endl; } @@ -357,6 +360,12 @@ int main(int argc, char** argv) { c.stopMining(); } + else if (cmd == "mineforce") + { + string enable; + iss >> enable; + c.setForceMining(isTrue(enable)); + } else if (cmd == "verbosity") { if (iss.peek() != -1) @@ -397,7 +406,7 @@ int main(int argc, char** argv) } else if (cmd == "block") { - cout << "Current block: " << c.blockChain().details().number; + cout << "Current block: " << c.blockChain().details().number << endl; } else if (cmd == "peers") { @@ -557,6 +566,36 @@ int main(int argc, char** argv) else cwarn << "Require parameters: contract ENDOWMENT GASPRICE GAS CODEHEX"; } + else if (cmd == "dumptrace") + { + unsigned block; + unsigned index; + string filename; + string format; + iss >> block >> index >> filename >> format; + ofstream f; + f.open(filename); + + eth::State state = c.state(index + 1, c.blockChain().numberHash(block)); + Executive e(state); + Transaction t = state.pending()[index]; + state = state.fromPending(index); + bytes r = t.rlp(); + e.setup(&r); + e.go([&](uint64_t steps, Instruction instr, unsigned newMemSize, bigint gasCost, void* vvm, void const* vextVM) + { + eth::VM* vm = (VM*)vvm; + eth::ExtVM const* ext = (ExtVM const*)vextVM; + f << endl << " STACK" << endl; + for (auto i: vm->stack()) + f << (h256)i << endl; + f << " MEMORY" << endl << eth::memDump(vm->memory()); + f << " STORAGE" << endl; + for (auto const& i: ext->state().storage(ext->myAddress)) + f << showbase << hex << i.first << ": " << i.second << endl; + f << dec << ext->level << " | " << ext->myAddress << " | #" << steps << " | " << hex << setw(4) << setfill('0') << vm->curPC() << " : " << c_instructionInfo.at(instr).name << " | " << dec << vm->gas() << " | -" << dec << gasCost << " | " << newMemSize << "x32"; + }); + } else if (cmd == "inspect") { string rechex; From 0b893fd30938f3bc1ab65e66ab440eeec34e81f6 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 29 Jul 2014 20:25:42 +0200 Subject: [PATCH 048/223] Other dumps. --- eth/main.cpp | 47 ++++++++++++++++++++++++++++++++++------------- 1 file changed, 34 insertions(+), 13 deletions(-) diff --git a/eth/main.cpp b/eth/main.cpp index 0f351c136..8f741106a 100644 --- a/eth/main.cpp +++ b/eth/main.cpp @@ -47,6 +47,8 @@ using namespace boost::algorithm; using eth::Instruction; using eth::c_instructionInfo; +#undef RETURN + bool isTrue(std::string const& _m) { return _m == "on" || _m == "yes" || _m == "true" || _m == "1"; @@ -582,19 +584,38 @@ int main(int argc, char** argv) state = state.fromPending(index); bytes r = t.rlp(); e.setup(&r); - e.go([&](uint64_t steps, Instruction instr, unsigned newMemSize, bigint gasCost, void* vvm, void const* vextVM) - { - eth::VM* vm = (VM*)vvm; - eth::ExtVM const* ext = (ExtVM const*)vextVM; - f << endl << " STACK" << endl; - for (auto i: vm->stack()) - f << (h256)i << endl; - f << " MEMORY" << endl << eth::memDump(vm->memory()); - f << " STORAGE" << endl; - for (auto const& i: ext->state().storage(ext->myAddress)) - f << showbase << hex << i.first << ": " << i.second << endl; - f << dec << ext->level << " | " << ext->myAddress << " | #" << steps << " | " << hex << setw(4) << setfill('0') << vm->curPC() << " : " << c_instructionInfo.at(instr).name << " | " << dec << vm->gas() << " | -" << dec << gasCost << " | " << newMemSize << "x32"; - }); + + if (format == "pretty") + e.go([&](uint64_t steps, Instruction instr, unsigned newMemSize, bigint gasCost, void* vvm, void const* vextVM) + { + eth::VM* vm = (VM*)vvm; + eth::ExtVM const* ext = (ExtVM const*)vextVM; + f << endl << " STACK" << endl; + for (auto i: vm->stack()) + f << (h256)i << endl; + f << " MEMORY" << endl << eth::memDump(vm->memory()); + f << " STORAGE" << endl; + for (auto const& i: ext->state().storage(ext->myAddress)) + f << showbase << hex << i.first << ": " << i.second << endl; + f << dec << ext->level << " | " << ext->myAddress << " | #" << steps << " | " << hex << setw(4) << setfill('0') << vm->curPC() << " : " << c_instructionInfo.at(instr).name << " | " << dec << vm->gas() << " | -" << dec << gasCost << " | " << newMemSize << "x32"; + }); + else if (format == "standard") + e.go([&](uint64_t, Instruction instr, unsigned, bigint, void* vvm, void const* vextVM) + { + eth::VM* vm = (VM*)vvm; + eth::ExtVM const* ext = (ExtVM const*)vextVM; + f << ext->myAddress << " " << hex << toHex(eth::toCompactBigEndian(vm->curPC(), 1)) << " " << hex << toHex(eth::toCompactBigEndian((int)(byte)instr, 1)) << " " << hex << toHex(eth::toCompactBigEndian((uint64_t)vm->gas(), 1)) << endl; + }); + else if (format == "standard+") + e.go([&](uint64_t, Instruction instr, unsigned, bigint, void* vvm, void const* vextVM) + { + eth::VM* vm = (VM*)vvm; + eth::ExtVM const* ext = (ExtVM const*)vextVM; + if (instr == Instruction::STOP || instr == Instruction::RETURN || instr == Instruction::SUICIDE) + for (auto const& i: ext->state().storage(ext->myAddress)) + f << toHex(eth::toCompactBigEndian(i.first, 1)) << " " << toHex(eth::toCompactBigEndian(i.second, 1)) << endl; + f << ext->myAddress << " " << hex << toHex(eth::toCompactBigEndian(vm->curPC(), 1)) << " " << hex << toHex(eth::toCompactBigEndian((int)(byte)instr, 1)) << " " << hex << toHex(eth::toCompactBigEndian((uint64_t)vm->gas(), 1)) << endl; + }); } else if (cmd == "inspect") { From 61b12cfd601f2fca4731e81fa38eb7468cba258b Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 29 Jul 2014 20:33:49 +0200 Subject: [PATCH 049/223] Fix for dumptrace. --- eth/main.cpp | 75 +++++++++++++++++++++++++++------------------------- 1 file changed, 39 insertions(+), 36 deletions(-) diff --git a/eth/main.cpp b/eth/main.cpp index 8f741106a..f8abb95e9 100644 --- a/eth/main.cpp +++ b/eth/main.cpp @@ -579,43 +579,46 @@ int main(int argc, char** argv) f.open(filename); eth::State state = c.state(index + 1, c.blockChain().numberHash(block)); - Executive e(state); - Transaction t = state.pending()[index]; - state = state.fromPending(index); - bytes r = t.rlp(); - e.setup(&r); - - if (format == "pretty") - e.go([&](uint64_t steps, Instruction instr, unsigned newMemSize, bigint gasCost, void* vvm, void const* vextVM) - { - eth::VM* vm = (VM*)vvm; - eth::ExtVM const* ext = (ExtVM const*)vextVM; - f << endl << " STACK" << endl; - for (auto i: vm->stack()) - f << (h256)i << endl; - f << " MEMORY" << endl << eth::memDump(vm->memory()); - f << " STORAGE" << endl; - for (auto const& i: ext->state().storage(ext->myAddress)) - f << showbase << hex << i.first << ": " << i.second << endl; - f << dec << ext->level << " | " << ext->myAddress << " | #" << steps << " | " << hex << setw(4) << setfill('0') << vm->curPC() << " : " << c_instructionInfo.at(instr).name << " | " << dec << vm->gas() << " | -" << dec << gasCost << " | " << newMemSize << "x32"; - }); - else if (format == "standard") - e.go([&](uint64_t, Instruction instr, unsigned, bigint, void* vvm, void const* vextVM) - { - eth::VM* vm = (VM*)vvm; - eth::ExtVM const* ext = (ExtVM const*)vextVM; - f << ext->myAddress << " " << hex << toHex(eth::toCompactBigEndian(vm->curPC(), 1)) << " " << hex << toHex(eth::toCompactBigEndian((int)(byte)instr, 1)) << " " << hex << toHex(eth::toCompactBigEndian((uint64_t)vm->gas(), 1)) << endl; - }); - else if (format == "standard+") - e.go([&](uint64_t, Instruction instr, unsigned, bigint, void* vvm, void const* vextVM) - { - eth::VM* vm = (VM*)vvm; - eth::ExtVM const* ext = (ExtVM const*)vextVM; - if (instr == Instruction::STOP || instr == Instruction::RETURN || instr == Instruction::SUICIDE) + if (index < state.pending().size()) + { + Executive e(state); + Transaction t = state.pending()[index]; + state = state.fromPending(index); + bytes r = t.rlp(); + e.setup(&r); + + if (format == "pretty") + e.go([&](uint64_t steps, Instruction instr, unsigned newMemSize, bigint gasCost, void* vvm, void const* vextVM) + { + eth::VM* vm = (VM*)vvm; + eth::ExtVM const* ext = (ExtVM const*)vextVM; + f << endl << " STACK" << endl; + for (auto i: vm->stack()) + f << (h256)i << endl; + f << " MEMORY" << endl << eth::memDump(vm->memory()); + f << " STORAGE" << endl; for (auto const& i: ext->state().storage(ext->myAddress)) - f << toHex(eth::toCompactBigEndian(i.first, 1)) << " " << toHex(eth::toCompactBigEndian(i.second, 1)) << endl; - f << ext->myAddress << " " << hex << toHex(eth::toCompactBigEndian(vm->curPC(), 1)) << " " << hex << toHex(eth::toCompactBigEndian((int)(byte)instr, 1)) << " " << hex << toHex(eth::toCompactBigEndian((uint64_t)vm->gas(), 1)) << endl; - }); + f << showbase << hex << i.first << ": " << i.second << endl; + f << dec << ext->level << " | " << ext->myAddress << " | #" << steps << " | " << hex << setw(4) << setfill('0') << vm->curPC() << " : " << c_instructionInfo.at(instr).name << " | " << dec << vm->gas() << " | -" << dec << gasCost << " | " << newMemSize << "x32"; + }); + else if (format == "standard") + e.go([&](uint64_t, Instruction instr, unsigned, bigint, void* vvm, void const* vextVM) + { + eth::VM* vm = (VM*)vvm; + eth::ExtVM const* ext = (ExtVM const*)vextVM; + f << ext->myAddress << " " << hex << toHex(eth::toCompactBigEndian(vm->curPC(), 1)) << " " << hex << toHex(eth::toCompactBigEndian((int)(byte)instr, 1)) << " " << hex << toHex(eth::toCompactBigEndian((uint64_t)vm->gas(), 1)) << endl; + }); + else if (format == "standard+") + e.go([&](uint64_t, Instruction instr, unsigned, bigint, void* vvm, void const* vextVM) + { + eth::VM* vm = (VM*)vvm; + eth::ExtVM const* ext = (ExtVM const*)vextVM; + if (instr == Instruction::STOP || instr == Instruction::RETURN || instr == Instruction::SUICIDE) + for (auto const& i: ext->state().storage(ext->myAddress)) + f << toHex(eth::toCompactBigEndian(i.first, 1)) << " " << toHex(eth::toCompactBigEndian(i.second, 1)) << endl; + f << ext->myAddress << " " << hex << toHex(eth::toCompactBigEndian(vm->curPC(), 1)) << " " << hex << toHex(eth::toCompactBigEndian((int)(byte)instr, 1)) << " " << hex << toHex(eth::toCompactBigEndian((uint64_t)vm->gas(), 1)) << endl; + }); + } } else if (cmd == "inspect") { From 500553d3b100a1aea2cbf4237eaa4c2302db9c6d Mon Sep 17 00:00:00 2001 From: Tim Hughes Date: Tue, 29 Jul 2014 20:49:11 +0100 Subject: [PATCH 050/223] Updated VS2013 projects. --- windows/LibEthereum.vcxproj | 9 +++------ windows/LibEthereum.vcxproj.filters | 6 ++++++ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/windows/LibEthereum.vcxproj b/windows/LibEthereum.vcxproj index c668fcb8e..454aee06a 100644 --- a/windows/LibEthereum.vcxproj +++ b/windows/LibEthereum.vcxproj @@ -32,12 +32,7 @@ - - true - true - true - true - + @@ -55,6 +50,7 @@ + @@ -110,6 +106,7 @@ + diff --git a/windows/LibEthereum.vcxproj.filters b/windows/LibEthereum.vcxproj.filters index 8421df84a..2fb0c3a39 100644 --- a/windows/LibEthereum.vcxproj.filters +++ b/windows/LibEthereum.vcxproj.filters @@ -124,6 +124,9 @@ libethereum + + libethereum + @@ -276,6 +279,9 @@ libethereum + + libethereum + From 694701c4902e1b5a415a6a9d65ded65c7fff87d7 Mon Sep 17 00:00:00 2001 From: Giacomo Tazzari Date: Tue, 29 Jul 2014 23:56:11 +0200 Subject: [PATCH 051/223] On startup, sets the "Force Mining" client option to match the settings loaded by the GUI --- alethzero/MainWin.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index 0226ac35d..1fbaf5e76 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -531,6 +531,7 @@ void Main::readSettings() ui->forceAddress->setText(s.value("forceAddress", "").toString()); ui->usePast->setChecked(s.value("usePast", true).toBool()); ui->forceMining->setChecked(s.value("forceMining", false).toBool()); + on_forceMining_triggered(); ui->paranoia->setChecked(s.value("paranoia", false).toBool()); ui->showAll->setChecked(s.value("showAll", false).toBool()); ui->showAllAccounts->setChecked(s.value("showAllAccounts", false).toBool()); From 59a858f45d26a380e423f421eb4bc61af889c08b Mon Sep 17 00:00:00 2001 From: Giacomo Tazzari Date: Wed, 30 Jul 2014 00:13:11 +0200 Subject: [PATCH 052/223] Allow MainWin::readSettings() to skip geometry restoration when killing the blockchain (it messes up widgets position when in full screen) --- alethzero/MainWin.cpp | 7 ++++--- alethzero/MainWin.h | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index 1fbaf5e76..a384f2fcb 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -504,11 +504,12 @@ void Main::writeSettings() s.setValue("windowState", saveState()); } -void Main::readSettings() +void Main::readSettings(bool _skipGeometry) { QSettings s("ethereum", "alethzero"); - restoreGeometry(s.value("geometry").toByteArray()); + if (!_skipGeometry) + restoreGeometry(s.value("geometry").toByteArray()); restoreState(s.value("windowState").toByteArray()); m_myKeys.clear(); @@ -1352,7 +1353,7 @@ void Main::on_killBlockchain_triggered() m_client.reset(); m_client.reset(new Client("AlethZero", Address(), string(), true)); m_ethereum->setClient(m_client.get()); - readSettings(); + readSettings(true); installWatches(); refreshAll(); } diff --git a/alethzero/MainWin.h b/alethzero/MainWin.h index 1098e3e1e..ac3d442d6 100644 --- a/alethzero/MainWin.h +++ b/alethzero/MainWin.h @@ -157,7 +157,8 @@ private: void alterDebugStateGroup(bool _enable) const; void updateFee(); - void readSettings(); + void readSettings(bool _skipGeometry); + void readSettings() { readSettings(false); } void writeSettings(); bool isCreation() const; From 06f65d373c690c4f13f4b022f3f9aceee3591aa6 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 31 Jul 2014 12:36:31 +0200 Subject: [PATCH 053/223] Include coinbase in block bloom filter. --- libethereum/State.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libethereum/State.cpp b/libethereum/State.cpp index 4484391ca..afa6b11f2 100644 --- a/libethereum/State.cpp +++ b/libethereum/State.cpp @@ -669,7 +669,7 @@ bool State::amIJustParanoid(BlockChain const& _bc) h256 State::bloom() const { - h256 ret; + h256 ret = m_currentBlock.coinbaseAddress.bloom(); for (auto const& i: m_transactions) ret |= i.changes.bloom(); return ret; From d5b57012bd98a0e530aa64650fcb5a521998234a Mon Sep 17 00:00:00 2001 From: Giacomo Tazzari Date: Thu, 31 Jul 2014 21:39:30 +0200 Subject: [PATCH 054/223] Fixed a bug in treefy() that tried to access vector oq before making sure it's not empty --- libserpent/parser.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libserpent/parser.cpp b/libserpent/parser.cpp index 38fdca6ed..497bdb526 100644 --- a/libserpent/parser.cpp +++ b/libserpent/parser.cpp @@ -150,10 +150,10 @@ Node treefy(std::vector stream) { else if (typ == RPAREN) { std::vector args; while (1) { + if (!oq.size()) err("Bracket without matching", tok.metadata); if (toktype(oq.back()) == LPAREN) break; args.push_back(oq.back()); oq.pop_back(); - if (!oq.size()) err("Bracket without matching", tok.metadata); } oq.pop_back(); args.push_back(oq.back()); From edd47b3cf54c28de72fb13d6da57c54eb35777de Mon Sep 17 00:00:00 2001 From: Giacomo Tazzari Date: Thu, 31 Jul 2014 21:58:24 +0200 Subject: [PATCH 055/223] Added another bound check in on_debugStep_triggered() to prevent the debugger from trying to move forward (e.g. if you hit F10) in case we are already at the end --- alethzero/MainWin.cpp | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index a384f2fcb..3ca3b1be3 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -1517,15 +1517,17 @@ void Main::on_create_triggered() void Main::on_debugStep_triggered() { - auto l = m_history[ui->debugTimeline->value()].levels.size(); - if ((ui->debugTimeline->value() + 1) < m_history.size() && m_history[ui->debugTimeline->value() + 1].levels.size() > l) - { - on_debugStepInto_triggered(); - if (m_history[ui->debugTimeline->value()].levels.size() > l) - on_debugStepOut_triggered(); + if (ui->debugTimeline->value() < m_history.size()) { + auto l = m_history[ui->debugTimeline->value()].levels.size(); + if ((ui->debugTimeline->value() + 1) < m_history.size() && m_history[ui->debugTimeline->value() + 1].levels.size() > l) + { + on_debugStepInto_triggered(); + if (m_history[ui->debugTimeline->value()].levels.size() > l) + on_debugStepOut_triggered(); + } + else + on_debugStepInto_triggered(); } - else - on_debugStepInto_triggered(); } void Main::on_debugStepInto_triggered() From 422448eb498658032c14080cf926ffe5ab463563 Mon Sep 17 00:00:00 2001 From: caktux Date: Mon, 4 Aug 2014 20:14:16 -0400 Subject: [PATCH 056/223] force mining for eth/neth, get actual balance instead of pending, fix clang warnings --- alethzero/MainWin.cpp | 2 +- eth/main.cpp | 12 ++++++++---- libethereum/State.cpp | 2 ++ neth/main.cpp | 15 ++++++++++----- 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index 3ca3b1be3..b59a81c6f 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -1683,7 +1683,7 @@ QString Main::prettyU256(eth::u256 _n) const s << "" << (uint64_t)_n << " (0x" << hex << (uint64_t)_n << ")"; else if (!~(_n >> 64)) s << "" << (int64_t)_n << " (0x" << hex << (int64_t)_n << ")"; - else if (_n >> 200 == 0) + else if ((_n >> 200) == 0) { Address a = right160(_n); QString n = pretty(a); diff --git a/eth/main.cpp b/eth/main.cpp index f8abb95e9..27c7fa99d 100644 --- a/eth/main.cpp +++ b/eth/main.cpp @@ -291,7 +291,11 @@ int main(int argc, char** argv) if (!clientName.empty()) clientName += "/"; - Client c("Ethereum(++)/" + clientName + "v" + eth::EthVersion + "/" ETH_QUOTED(ETH_BUILD_TYPE) "/" ETH_QUOTED(ETH_BUILD_PLATFORM), coinbase, dbPath); + + Client c("Ethereum(++)/" + clientName + "v" + eth::EthVersion + "/" ETH_QUOTED(ETH_BUILD_TYPE) "/" ETH_QUOTED(ETH_BUILD_PLATFORM), coinbase, dbPath); + + c.setForceMining(true); + cout << credits(); cout << "Address: " << endl << toHex(us.address().asArray()) << endl; @@ -419,7 +423,7 @@ int main(int argc, char** argv) } else if (cmd == "balance") { - cout << "Current balance: " << formatBalance(c.balanceAt(us.address(), 0)) << " = " << c.balanceAt(us.address(), 0) << " wei" << endl; + cout << "Current balance: " << formatBalance(c.balanceAt(us.address())) << " = " << c.balanceAt(us.address()) << " wei" << endl; } else if (cmd == "transact") { @@ -481,7 +485,7 @@ int main(int argc, char** argv) for (auto const& i: acs) if (c.codeAt(i, 0).size()) { - ss = toString(i) + " : " + toString(c.balanceAt(i, 0)) + " [" + toString((unsigned)c.countAt(i)) + "]"; + ss = toString(i) + " : " + toString(c.balanceAt(i)) + " [" + toString((unsigned)c.countAt(i)) + "]"; cout << ss << endl; } } @@ -492,7 +496,7 @@ int main(int argc, char** argv) for (auto const& i: acs) if (c.codeAt(i, 0).empty()) { - ss = toString(i) + " : " + toString(c.balanceAt(i, 0)) + " [" + toString((unsigned)c.countAt(i)) + "]"; + ss = toString(i) + " : " + toString(c.balanceAt(i)) + " [" + toString((unsigned)c.countAt(i)) + "]"; cout << ss << endl; } } diff --git a/libethereum/State.cpp b/libethereum/State.cpp index afa6b11f2..bf24a287b 100644 --- a/libethereum/State.cpp +++ b/libethereum/State.cpp @@ -1227,10 +1227,12 @@ std::ostream& eth::operator<<(std::ostream& _out, State const& _s) } if (cache) for (auto const& j: cache->storage()) + { if ((!mem.count(j.first) && j.second) || (mem.count(j.first) && mem.at(j.first) != j.second)) mem[j.first] = j.second, delta.insert(j.first); else if (j.second) cached.insert(j.first); + } if (delta.size()) lead = (lead == " . ") ? "*.* " : "*** "; diff --git a/neth/main.cpp b/neth/main.cpp index 8d36390ca..ae93a94af 100644 --- a/neth/main.cpp +++ b/neth/main.cpp @@ -411,7 +411,11 @@ int main(int argc, char** argv) if (!clientName.empty()) clientName += "/"; - Client c("NEthereum(++)/" + clientName + "v" + eth::EthVersion + "/" ETH_QUOTED(ETH_BUILD_TYPE) "/" ETH_QUOTED(ETH_BUILD_PLATFORM), coinbase, dbPath); + + Client c("NEthereum(++)/" + clientName + "v" + eth::EthVersion + "/" ETH_QUOTED(ETH_BUILD_TYPE) "/" ETH_QUOTED(ETH_BUILD_PLATFORM), coinbase, dbPath); + + c.setForceMining(true); + cout << credits(); std::ostringstream ccout; @@ -591,7 +595,7 @@ int main(int argc, char** argv) } else if (cmd == "balance") { - u256 balance = c.balanceAt(us.address(), 0); + u256 balance = c.balanceAt(us.address()); ccout << "Current balance:" << endl; ccout << toString(balance) << endl; } @@ -899,7 +903,7 @@ int main(int argc, char** argv) auto s = boost::format("%1%%2% : %3% [%4%]") % toString(i) % pretty(i, c.postState()) % - toString(formatBalance(c.balanceAt(i, 0))) % + toString(formatBalance(c.balanceAt(i))) % toString((unsigned)c.countAt(i, 0)); mvwaddnstr(contractswin, cc++, x, s.str().c_str(), qwidth); if (cc > qheight - 2) @@ -911,12 +915,13 @@ int main(int argc, char** argv) auto s = boost::format("%1%%2% : %3% [%4%]") % toString(i) % pretty(i, c.postState()) % - toString(formatBalance(c.balanceAt(i, 0))) % + toString(formatBalance(c.balanceAt(i))) % toString((unsigned)c.countAt(i, 0)); mvwaddnstr(addswin, y++, x, s.str().c_str(), width / 2 - 4); if (y > height * 3 / 5 - 4) break; } + // Peers y = 1; for (PeerInfo const& i: c.peers()) @@ -941,7 +946,7 @@ int main(int argc, char** argv) // Balance stringstream ssb; - u256 balance = c.balanceAt(us.address(), 0); + u256 balance = c.balanceAt(us.address()); Address coinsAddr = right160(c.stateAt(c_config, 1)); Address gavCoin = right160(c.stateAt(coinsAddr, c.stateAt(coinsAddr, 1))); u256 totalGavCoinBalance = c.stateAt(gavCoin, (u160)us.address()); From 158084652a46b04280db08cb99123ab28dc90d2a Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 5 Aug 2014 11:18:29 +0300 Subject: [PATCH 057/223] Fix contract endowment. --- libethereum/State.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libethereum/State.cpp b/libethereum/State.cpp index afa6b11f2..a13b0dfee 100644 --- a/libethereum/State.cpp +++ b/libethereum/State.cpp @@ -1106,7 +1106,7 @@ h160 State::create(Address _sender, u256 _endowment, u256 _gasPrice, u256* _gas, newAddress = (u160)newAddress + 1; // Set up new account... - m_cache[newAddress] = AddressState(0, 0, h256(), h256()); + m_cache[newAddress] = AddressState(0, _endowment, h256(), h256()); // Execute init code. VM vm(*_gas); From 12bc78fe3af1ec5e534210ee06bec5b901f266e2 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 5 Aug 2014 11:56:10 +0300 Subject: [PATCH 058/223] Add force mining to client. --- eth/main.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/eth/main.cpp b/eth/main.cpp index f8abb95e9..31df2a649 100644 --- a/eth/main.cpp +++ b/eth/main.cpp @@ -100,7 +100,8 @@ void help() << " -c,--client-name Add a name to your client's version string (default: blank)." << endl << " -d,--db-path Load database from path (default: ~/.ethereum " << endl << " /Etherum or Library/Application Support/Ethereum)." << endl - << " -h,--help Show this help message and exit." << endl + << " -f,--force-mining Mine even when there are no transaction to mine (Default: off)" << endl + << " -h,--help Show this help message and exit." << endl << " -i,--interactive Enter interactive mode (default: non-interactive)." << endl #if ETH_JSONRPC << " -j,--json-rpc Enable JSON-RPC server (default: off)." << endl @@ -108,7 +109,7 @@ void help() #endif << " -l,--listen Listen on the given port for incoming connected (default: 30303)." << endl << " -m,--mining Enable mining, optionally for a specified number of blocks (Default: off)" << endl - << " -n,--upnp Use upnp for NAT (default: on)." << endl + << " -n,--upnp Use upnp for NAT (default: on)." << endl << " -o,--mode Start a full node or a peer node (Default: full)." << endl << " -p,--port Connect to remote port (default: 30303)." << endl << " -r,--remote Connect to remote host (default: none)." << endl @@ -185,6 +186,7 @@ int main(int argc, char** argv) #endif string publicIP; bool upnp = true; + bool forceMining = false; string clientName; // Init defaults @@ -256,6 +258,8 @@ int main(int argc, char** argv) return -1; } } + else if (arg == "-f" || arg == "--force-mining") + forceMining = true; else if (arg == "-i" || arg == "--interactive") interactive = true; #if ETH_JSONRPC @@ -294,6 +298,8 @@ int main(int argc, char** argv) Client c("Ethereum(++)/" + clientName + "v" + eth::EthVersion + "/" ETH_QUOTED(ETH_BUILD_TYPE) "/" ETH_QUOTED(ETH_BUILD_PLATFORM), coinbase, dbPath); cout << credits(); + c.setForceMining(forceMining); + cout << "Address: " << endl << toHex(us.address().asArray()) << endl; c.startNetwork(listenPort, remoteHost, remotePort, mode, peers, publicIP, upnp); From b7a96fbbff99b964c65a3d80fbf3ce7c5bf48709 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 5 Aug 2014 21:27:00 +0200 Subject: [PATCH 059/223] Nicer transactions in JS API. Added call() in Client & JS API. --- libethereum/Client.cpp | 22 +++++++++++ libethereum/Client.h | 10 ++--- libqethereum/QEthereum.cpp | 79 ++++++++++++++++++++++++++++++++++++++ libqethereum/QEthereum.h | 12 ++++++ 4 files changed, 118 insertions(+), 5 deletions(-) diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index 47beb94a3..673973fbd 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -333,6 +333,28 @@ void Client::transact(Secret _secret, u256 _value, Address _dest, bytes const& _ m_tq.attemptImport(t.rlp()); } +bytes Client::call(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice) +{ + State temp; + Transaction t; +// cdebug << "Nonce at " << toAddress(_secret) << " pre:" << m_preMine.transactionsFrom(toAddress(_secret)) << " post:" << m_postMine.transactionsFrom(toAddress(_secret)); + { + ReadGuard l(x_stateDB); + temp = m_postMine; + t.nonce = temp.transactionsFrom(toAddress(_secret)); + } + t.value = _value; + t.gasPrice = _gasPrice; + t.gas = _gas; + t.receiveAddress = _dest; + t.data = _data; + t.sign(_secret); + bytes out; + u256 gasUsed = temp.execute(t.data, &out, false); + (void)gasUsed; // TODO: do something with gasused which it returns. + return out; +} + Address Client::transact(Secret _secret, u256 _endowment, bytes const& _init, u256 _gas, u256 _gasPrice) { ensureWorking(); diff --git a/libethereum/Client.h b/libethereum/Client.h index e9b10b538..c761e3d0f 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -180,14 +180,14 @@ public: /// @returns the new contract's address (assuming it all goes through). Address transact(Secret _secret, u256 _endowment, bytes const& _init, u256 _gas = 10000, u256 _gasPrice = 10 * szabo); - /// Blocks until all pending transactions have been processed. - void flushTransactions(); - /// Injects the RLP-encoded transaction given by the _rlp into the transaction queue directly. void inject(bytesConstRef _rlp); - /// Makes the given call. Nothing is recorded into the state. TODO -// bytes call(Secret _secret, u256 _amount, u256 _gasPrice, Address _dest, u256 _gas, bytes _data = bytes()); + /// Blocks until all pending transactions have been processed. + void flushTransactions(); + + /// Makes the given call. Nothing is recorded into the state. + bytes call(Secret _secret, u256 _value, Address _dest, bytes const& _data = bytes(), u256 _gas = 10000, u256 _gasPrice = 10 * szabo); // Informational stuff diff --git a/libqethereum/QEthereum.cpp b/libqethereum/QEthereum.cpp index f6b114b6f..1ae2fef21 100644 --- a/libqethereum/QEthereum.cpp +++ b/libqethereum/QEthereum.cpp @@ -131,6 +131,11 @@ QString QEthereum::sha3(QString _s) const return toQJS(eth::sha3(asBytes(_s))); } +QString QEthereum::offset(QString _s, int _i) const +{ + return toQJS(toU256(_s) + _i); +} + QString QEthereum::coinbase() const { return m_client ? toQJS(client()->address()) : ""; @@ -277,6 +282,45 @@ static eth::MessageFilter toMessageFilter(QString _json) return filter; } +struct TransactionSkeleton +{ + Secret from; + Address to; + u256 value; + bytes data; + u256 gas; + u256 gasPrice; +}; + +static TransactionSkeleton toTransaction(QString _json) +{ + TransactionSkeleton ret; + + QJsonObject f = QJsonDocument::fromJson(_json.toUtf8()).object(); + if (f.contains("from")) + ret.from = toSecret(f["from"].toString()); + if (f.contains("to")) + ret.to = toAddress(f["to"].toString()); + if (f.contains("value")) + ret.value = toU256(f["value"].toString()); + if (f.contains("gas")) + ret.gas = toU256(f["gas"].toString()); + if (f.contains("gasPrice")) + ret.gasPrice = toU256(f["gasPrice"].toString()); + if (f.contains("data")) + { + if (f["data"].isString()) + ret.data = toBytes(f["data"].toString()); + else if (f["data"].isArray()) + for (auto i: f["data"].toArray()) + eth::operator +=(ret.data, toBytes(padded(i.toString(), 32))); + else if (f["dataclose"].isArray()) + for (auto i: f["dataclose"].toArray()) + eth::operator +=(ret.data, toBytes(toBinary(i.toString()))); + } + return ret; +} + static QString toJson(eth::PastMessages const& _pms) { QJsonArray jsonArray; @@ -359,6 +403,41 @@ void QEthereum::doTransact(QString _secret, QString _amount, QString _dest, QStr client()->flushTransactions(); } +void QEthereum::doTransact(QString _json) +{ + if (!m_client) + return; + TransactionSkeleton t = toTransaction(_json); + if (!t.from && m_accounts.size()) + t.from = m_accounts[0].secret(); + if (!t.gasPrice) + t.gasPrice = 10 * eth::szabo; + if (!t.gas) + t.gas = client()->balanceAt(KeyPair(t.from).address()) / t.gasPrice; + if (t.to) + client()->transact(t.from, t.value, t.to, t.data, t.gas, t.gasPrice); + else + client()->transact(t.from, t.value, t.data, t.gas, t.gasPrice); + client()->flushTransactions(); +} + +QString QEthereum::doCall(QString _json) +{ + if (!m_client) + return QString(); + TransactionSkeleton t = toTransaction(_json); + if (!t.to) + return QString(); + if (!t.from && m_accounts.size()) + t.from = m_accounts[0].secret(); + if (!t.gasPrice) + t.gasPrice = 10 * eth::szabo; + if (!t.gas) + t.gas = client()->balanceAt(KeyPair(t.from).address()) / t.gasPrice; + bytes out = client()->call(t.from, t.value, t.to, t.data, t.gas, t.gasPrice); + return asQString(out); +} + unsigned QEthereum::newWatch(QString _json) { if (!m_client) diff --git a/libqethereum/QEthereum.h b/libqethereum/QEthereum.h index e5476613e..b702f09a4 100644 --- a/libqethereum/QEthereum.h +++ b/libqethereum/QEthereum.h @@ -72,6 +72,11 @@ inline QString toDecimal(QString const& _s) return QString::fromStdString(eth::toString(toU256(_s))); } +inline double toFixed(QString const& _s) +{ + return (double)toU256(_s) / (double)(eth::u256(1) << 128); +} + inline QString fromBinary(eth::bytes const& _s) { return QString::fromStdString("0x" + eth::toHex(_s)); @@ -105,12 +110,14 @@ public: Q_INVOKABLE QString lll(QString _s) const; Q_INVOKABLE QString sha3(QString _s) const; + Q_INVOKABLE QString offset(QString _s, int _offset) const; Q_INVOKABLE QString pad(QString _s, unsigned _l) const { return padded(_s, _l); } Q_INVOKABLE QString pad(QString _s, unsigned _l, unsigned _r) const { return padded(_s, _l, _r); } Q_INVOKABLE QString unpad(QString _s) const { return unpadded(_s); } Q_INVOKABLE QString toBinary(QString _s) const { return ::toBinary(_s); } Q_INVOKABLE QString fromBinary(QString _s) const { return ::fromBinary(_s); } Q_INVOKABLE QString toDecimal(QString _s) const { return ::toDecimal(_s); } + Q_INVOKABLE double toFixed(QString _s) const { return ::toFixed(_s); } // [NEW API] - Use this instead. Q_INVOKABLE QString/*eth::u256*/ balanceAt(QString/*eth::Address*/ _a, int _block) const; @@ -127,6 +134,8 @@ public: Q_INVOKABLE QString doCreate(QString _secret, QString _amount, QString _init, QString _gas, QString _gasPrice); Q_INVOKABLE void doTransact(QString _secret, QString _amount, QString _dest, QString _data, QString _gas, QString _gasPrice); + Q_INVOKABLE void doTransact(QString _json); + Q_INVOKABLE QString doCall(QString _json); Q_INVOKABLE unsigned newWatch(QString _json); Q_INVOKABLE QString watchMessages(unsigned _w); @@ -192,6 +201,8 @@ private: frame->evaluateJavaScript("eth.watchPending = function() { return eth.makeWatch('pendingChanged') }"); \ frame->evaluateJavaScript("eth.create = function(s, v, c, g, p, f) { var v = eth.doCreate(s, v, c, g, p); if (f) f(v) }"); \ frame->evaluateJavaScript("eth.transact = function(s, v, t, d, g, p, f) { eth.doTransact(s, v, t, d, g, p); if (f) f() }"); \ + frame->evaluateJavaScript("eth.transact = function(a, f) { eth.doTransactJson(JSON.stringify(a)); if (f) f() }"); \ + frame->evaluateJavaScript("eth.call = function(a, f) { var ret = eth.doCallJson(JSON.stringify(a)); if (f) f(ret); return ret; }"); \ frame->evaluateJavaScript("eth.messages = function(a) { return JSON.parse(eth.getMessages(JSON.stringify(a))); }"); \ frame->evaluateJavaScript("eth.transactions = function(a) { env.warn('THIS CALL IS DEPRECATED. USE eth.messages INSTEAD.'); return JSON.parse(eth.getMessages(JSON.stringify(a))); }"); \ frame->evaluateJavaScript("String.prototype.pad = function(l, r) { return eth.pad(this, l, r) }"); \ @@ -199,6 +210,7 @@ private: frame->evaluateJavaScript("String.prototype.unbin = function(l) { return eth.fromBinary(this) }"); \ frame->evaluateJavaScript("String.prototype.unpad = function(l) { return eth.unpad(this) }"); \ frame->evaluateJavaScript("String.prototype.dec = function() { return eth.toDecimal(this) }"); \ + frame->evaluateJavaScript("String.prototype.fix = function() { return eth.toFixed(this) }"); \ frame->evaluateJavaScript("String.prototype.sha3 = function() { return eth.sha3(this) }"); \ } From 946a3a97a7af56ed71c85c4e49f07ab5ce5543b6 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 5 Aug 2014 22:03:13 +0200 Subject: [PATCH 060/223] Fix for new JS API. --- libqethereum/QEthereum.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libqethereum/QEthereum.h b/libqethereum/QEthereum.h index b702f09a4..c1dc2208b 100644 --- a/libqethereum/QEthereum.h +++ b/libqethereum/QEthereum.h @@ -200,8 +200,7 @@ private: frame->evaluateJavaScript("eth.watchChain = function() { return eth.makeWatch('chainChanged') }"); \ frame->evaluateJavaScript("eth.watchPending = function() { return eth.makeWatch('pendingChanged') }"); \ frame->evaluateJavaScript("eth.create = function(s, v, c, g, p, f) { var v = eth.doCreate(s, v, c, g, p); if (f) f(v) }"); \ - frame->evaluateJavaScript("eth.transact = function(s, v, t, d, g, p, f) { eth.doTransact(s, v, t, d, g, p); if (f) f() }"); \ - frame->evaluateJavaScript("eth.transact = function(a, f) { eth.doTransactJson(JSON.stringify(a)); if (f) f() }"); \ + frame->evaluateJavaScript("eth.transact = function(a_s, f_v, t, d, g, p, f) { if (t == null) { eth.doTransact(JSON.stringify(a_s)); if (f_v) f_v(); } else { eth.doTransact(a_s, f_v, t, d, g, p); if (f) f() } }"); \ frame->evaluateJavaScript("eth.call = function(a, f) { var ret = eth.doCallJson(JSON.stringify(a)); if (f) f(ret); return ret; }"); \ frame->evaluateJavaScript("eth.messages = function(a) { return JSON.parse(eth.getMessages(JSON.stringify(a))); }"); \ frame->evaluateJavaScript("eth.transactions = function(a) { env.warn('THIS CALL IS DEPRECATED. USE eth.messages INSTEAD.'); return JSON.parse(eth.getMessages(JSON.stringify(a))); }"); \ From d224cc84a7e86ff57b293e7423e98c0be0dc927e Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 5 Aug 2014 22:11:21 +0200 Subject: [PATCH 061/223] Additional APIs and good defaults for Client & JSAPI. --- libethereum/Client.h | 3 +++ libethereum/State.h | 3 +++ libqethereum/QEthereum.cpp | 2 +- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/libethereum/Client.h b/libethereum/Client.h index c761e3d0f..1d6b072ce 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -235,6 +235,9 @@ public: /// Get the fee associated for a transaction with the given data. static u256 txGas(uint _dataCount, u256 _gas = 0) { return c_txDataGas * _dataCount + c_txGas + _gas; } + /// Get the remaining gas limit in this block. + u256 gasLimitRemaining() const { return m_postMine.gasLimitRemaining(); } + // [PRIVATE API - only relevant for base clients, not available in general] eth::State state(unsigned _txi, h256 _block) const; diff --git a/libethereum/State.h b/libethereum/State.h index 9f014f0ab..da8e099d1 100644 --- a/libethereum/State.h +++ b/libethereum/State.h @@ -192,6 +192,9 @@ public: u256 execute(bytes const& _rlp, bytes* o_output = nullptr, bool _commit = true) { return execute(&_rlp, o_output, _commit); } u256 execute(bytesConstRef _rlp, bytes* o_output = nullptr, bool _commit = true); + /// Get the remaining gas limit in this block. + u256 gasLimitRemaining() const { return m_currentBlock.gasLimit - gasUsed(); } + /// Check if the address is in use. bool addressInUse(Address _address) const; diff --git a/libqethereum/QEthereum.cpp b/libqethereum/QEthereum.cpp index 1ae2fef21..06a6a485b 100644 --- a/libqethereum/QEthereum.cpp +++ b/libqethereum/QEthereum.cpp @@ -413,7 +413,7 @@ void QEthereum::doTransact(QString _json) if (!t.gasPrice) t.gasPrice = 10 * eth::szabo; if (!t.gas) - t.gas = client()->balanceAt(KeyPair(t.from).address()) / t.gasPrice; + t.gas = min(client()->gasLimitRemaining(), client()->balanceAt(KeyPair(t.from).address()) / t.gasPrice); if (t.to) client()->transact(t.from, t.value, t.to, t.data, t.gas, t.gasPrice); else From b88cef7f6936e1b3c5f23e5164f94b9507606f0c Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 6 Aug 2014 13:10:54 +0100 Subject: [PATCH 062/223] Bugs fixed. --- alethzero/MainWin.cpp | 2 +- stdserv.js | 5 ++++- walleth/MainWin.cpp | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index b59a81c6f..fab200b89 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -461,7 +461,7 @@ Address Main::fromString(QString const& _a) const void Main::on_about_triggered() { - QMessageBox::about(this, "About AlethZero PoC-" + QString(eth::EthVersion).section('.', 1, 1), QString("AlethZero/v") + eth::EthVersion + "/" ETH_QUOTED(ETH_BUILD_TYPE) "/" ETH_QUOTED(ETH_BUILD_PLATFORM) "\n" ETH_QUOTED(ETH_COMMIT_HASH) + (ETH_CLEAN_REPO ? "\nCLEAN" : "\n+ LOCAL CHANGES") + "\n\nBy Gav Wood, 2014.\nBased on a design by Vitalik Buterin.\n\nTeam Ethereum++ includes: Eric Lombrozo, Marko Simovic, Alex Leverington, Tim Hughes and several others."); + QMessageBox::about(this, "About AlethZero PoC-" + QString(eth::EthVersion).section('.', 1, 1), QString("AlethZero/v") + eth::EthVersion + "/" ETH_QUOTED(ETH_BUILD_TYPE) "/" ETH_QUOTED(ETH_BUILD_PLATFORM) "\n" ETH_QUOTED(ETH_COMMIT_HASH) + (ETH_CLEAN_REPO ? "\nCLEAN" : "\n+ LOCAL CHANGES") + "\n\nBy Gav Wood, 2014.\nBased on a design by Vitalik Buterin.\n\nThanks to the various contributors including: Alex Leverington, Tim Hughes, caktux, Eric Lombrozo, Marko Simovic."); } void Main::on_paranoia_triggered() diff --git a/stdserv.js b/stdserv.js index ff0d35d97..705320eb0 100644 --- a/stdserv.js +++ b/stdserv.js @@ -60,12 +60,14 @@ var coinsCode = eth.lll(" (msg allgas " + nameReg + " 0 0 64) (returnlll { (def 'name $0) + (def 'denom $32) (def 'address (caller)) (when (|| (& 0xffffffffffffffffffffffffff name) @@name) (stop)) (set 'n (+ @@0 1)) [[0]] @n [[@n]] name [[name]] address + [[(sha3 name)]] denom }) } "); @@ -87,7 +89,8 @@ var gavCoinCode = eth.lll(" [0]'register [32]'GavCoin (msg allgas " + nameReg + " 0 0 64) -(msg " + coins + " 'GAV) +[0]'GAV [32]'1000 +(msg allgas " + coins + " 0 0 64) (returnlll { (when (&& (= $0 'kill) (= (caller) @@0x69)) (suicide (caller))) diff --git a/walleth/MainWin.cpp b/walleth/MainWin.cpp index d8f8a28c3..98dbc6e65 100644 --- a/walleth/MainWin.cpp +++ b/walleth/MainWin.cpp @@ -137,7 +137,7 @@ void Main::timerEvent(QTimerEvent *) void Main::on_about_triggered() { - QMessageBox::about(this, "About Walleth PoC-" + QString(eth::EthVersion).section('.', 1, 1), QString("Walleth/v") + eth::EthVersion + "/" ETH_QUOTED(ETH_BUILD_TYPE) "/" ETH_QUOTED(ETH_BUILD_PLATFORM) " - " ETH_QUOTED(ETH_COMMIT_HASH) "\nBy Gav Wood, 2014.\nBased on a design by Vitalik Buterin.\n\nTeam Ethereum++ includes: Tim Hughes, Eric Lombrozo, Marko Simovic, Alex Leverington and several others."); + QMessageBox::about(this, "About Walleth PoC-" + QString(eth::EthVersion).section('.', 1, 1), QString("Walleth/v") + eth::EthVersion + "/" ETH_QUOTED(ETH_BUILD_TYPE) "/" ETH_QUOTED(ETH_BUILD_PLATFORM) "\n" ETH_QUOTED(ETH_COMMIT_HASH) + (ETH_CLEAN_REPO ? "\nCLEAN" : "\n+ LOCAL CHANGES") + "\n\nBy Gav Wood, 2014.\nBased on a design by Vitalik Buterin.\n\nThanks to the various contributors including: Alex Leverington, Tim Hughes, caktux, Eric Lombrozo, Marko Simovic."); } void Main::writeSettings() From ab75743fcf8490457c676eaea1daf5d40e056b79 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 7 Aug 2014 17:32:07 +0100 Subject: [PATCH 063/223] DnsReg integration. --- alethzero/MainWin.cpp | 45 ++++++++++++++++++++++++++++++++++++++++++- alethzero/MainWin.h | 2 ++ stdserv.js | 3 ++- 3 files changed, 48 insertions(+), 2 deletions(-) diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index fab200b89..efa026b66 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -459,6 +459,40 @@ Address Main::fromString(QString const& _a) const return Address(); } +QString Main::lookup(QString const& _a) const +{ + if (!_a.endsWith(".eth")) + return _a; + + string sn = _a.mid(0, _a.size() - 4).toStdString(); + if (sn.size() > 32) + sn = sha3(sn, false); + h256 n; + memcpy(n.data(), sn.data(), sn.size()); + +/* string sn2 = _a.toStdString(); + if (sn2.size() > 32) + sn2 = sha3(sn2, false); + h256 n2; + memcpy(n2.data(), sn2.data(), sn2.size()); +*/ + + h256 ret; + if (h160 dnsReg = (u160)m_client->stateAt(c_config, 4, 0)) + ret = m_client->stateAt(dnsReg, n); +/* if (!ret) + if (h160 nameReg = (u160)m_client->stateAt(c_config, 0, 0)) + ret = m_client->stateAt(nameReg, n2); +*/ + if (ret && !((u256)ret >> 32)) + return QString("%1.%2.%3.%4").arg((int)ret[28]).arg((int)ret[29]).arg((int)ret[30]).arg((int)ret[31]); + // TODO: support IPv6. + else if (ret) + return fromRaw(ret); + else + return _a; +} + void Main::on_about_triggered() { QMessageBox::about(this, "About AlethZero PoC-" + QString(eth::EthVersion).section('.', 1, 1), QString("AlethZero/v") + eth::EthVersion + "/" ETH_QUOTED(ETH_BUILD_TYPE) "/" ETH_QUOTED(ETH_BUILD_PLATFORM) "\n" ETH_QUOTED(ETH_COMMIT_HASH) + (ETH_CLEAN_REPO ? "\nCLEAN" : "\n+ LOCAL CHANGES") + "\n\nBy Gav Wood, 2014.\nBased on a design by Vitalik Buterin.\n\nThanks to the various contributors including: Alex Leverington, Tim Hughes, caktux, Eric Lombrozo, Marko Simovic."); @@ -575,7 +609,16 @@ void Main::on_exportKey_triggered() void Main::on_urlEdit_returnPressed() { - ui->webView->setUrl(ui->urlEdit->text()); + QString s = ui->urlEdit->text(); + QRegExp r("([a-z]+://)?([^/]*)(.*)"); + if (r.exactMatch(s)) + if (r.cap(2).isEmpty()) + s = (r.cap(1).isEmpty() ? "file://" : r.cap(1)) + r.cap(3); + else + s = (r.cap(1).isEmpty() ? "http://" : r.cap(1)) + lookup(r.cap(2)) + r.cap(3); + else{} + qDebug() << s; + ui->webView->setUrl(s); } void Main::on_nameReg_textChanged() diff --git a/alethzero/MainWin.h b/alethzero/MainWin.h index ac3d442d6..825e05456 100644 --- a/alethzero/MainWin.h +++ b/alethzero/MainWin.h @@ -146,6 +146,8 @@ private: QString pretty(eth::Address _a) const; QString prettyU256(eth::u256 _n) const; + QString lookup(QString const& _n) const; + void populateDebugger(eth::bytesConstRef r); void initDebugger(); void updateDebugger(); diff --git a/stdserv.js b/stdserv.js index 705320eb0..b9006ae76 100644 --- a/stdserv.js +++ b/stdserv.js @@ -240,10 +240,11 @@ var exchangeCode = eth.lll(" (when (= $0 'delete) { (set 'offer $32) (set 'want $64) + (set 'rate $96) (set 'list (sha3pair @offer @want)) (set 'last @list) (set 'item @@ @last) - (for {} (&& @item (!= (idof @item) (caller))) { (set 'last @item) (inc item) } {}) + (for {} (&& @item (!= (idof @item) (caller)) (!= (rateof @item) @rate)) { (set 'last @item) (inc item) } {}) (when @item { (set 'xoffer (fpmul (wantof @item) (rateof @item))) [[ @last ]] @@ @item From e8af295beeeadcae3e3d77ab02a009dd44c50d34 Mon Sep 17 00:00:00 2001 From: Jason Huntley Date: Fri, 8 Aug 2014 08:53:58 -0400 Subject: [PATCH 064/223] Update for cmake to search PYTHON_INCLUDE_DIR path when specified --- CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ce9fb6d4..0adb4ffec 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -190,6 +190,7 @@ else() endif () find_path( PYTHON_ID pyconfig.h + ${PYTHON_INCLUDE_DIR} /usr/include/python2.7 /usr/local/include/python2.7 ) From 7524128cf2b6f1e82324006816fd0bf4b47fc6e0 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 9 Aug 2014 18:21:01 +0100 Subject: [PATCH 065/223] Better accounts. "Third" browser. --- CMakeLists.txt | 1 + alethzero/MainWin.cpp | 22 +- alethzero/MainWin.h | 7 +- libethcore/BlockInfo.cpp | 2 +- libethcore/CommonEth.cpp | 2 +- libethcore/Exceptions.h | 2 +- libethereum/Client.h | 8 +- libqethereum/QEthereum.cpp | 8 +- stdserv.js | 31 +- third/CMakeLists.txt | 100 ++++++ third/Main.ui | 480 ++++++++++++++++++++++++++++ third/MainWin.cpp | 629 +++++++++++++++++++++++++++++++++++++ third/MainWin.h | 129 ++++++++ third/main.cpp | 11 + 14 files changed, 1403 insertions(+), 29 deletions(-) create mode 100644 third/CMakeLists.txt create mode 100644 third/Main.ui create mode 100644 third/MainWin.cpp create mode 100644 third/MainWin.h create mode 100644 third/main.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 3ce9fb6d4..1ca9a9c37 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -358,6 +358,7 @@ if (NOT LANGUAGES) add_subdirectory(libqethereum) add_subdirectory(alethzero) + add_subdirectory(third) if(QTQML) add_subdirectory(walleth) endif() diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index efa026b66..c9d8e22ca 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -663,10 +663,18 @@ void Main::refreshBalances() // update all the balance-dependent stuff. ui->ourAccounts->clear(); u256 totalBalance = 0; - map> altCoins; + map> altCoins; Address coinsAddr = right160(m_client->stateAt(c_config, 1)); for (unsigned i = 0; i < m_client->stateAt(coinsAddr, 0); ++i) - altCoins[right160(m_client->stateAt(coinsAddr, m_client->stateAt(coinsAddr, i + 1)))] = make_pair(fromRaw(m_client->stateAt(coinsAddr, i + 1)), 0); + { + auto n = m_client->stateAt(coinsAddr, i + 1); + auto addr = right160(m_client->stateAt(coinsAddr, n)); + auto denom = m_client->stateAt(coinsAddr, sha3(h256(n).asBytes())); + if (denom == 0) + denom = 1; + cdebug << n << addr << denom << sha3(h256(n).asBytes()); + altCoins[addr] = make_tuple(fromRaw(n), 0, denom); + } for (auto i: m_myKeys) { u256 b = m_client->balanceAt(i.address()); @@ -675,13 +683,17 @@ void Main::refreshBalances() totalBalance += b; for (auto& c: altCoins) - c.second.second += (u256)m_client->stateAt(c.first, (u160)i.address()); + get<1>(c.second) += (u256)m_client->stateAt(c.first, (u160)i.address()); } QString b; for (auto const& c: altCoins) - if (c.second.second) - b += QString::fromStdString(toString(c.second.second)) + " " + c.second.first.toUpper() + " | "; + if (get<1>(c.second)) + { + stringstream s; + s << setw(toString(get<2>(c.second) - 1).size()) << setfill('0') << (get<1>(c.second) % get<2>(c.second)); + b += QString::fromStdString(toString(get<1>(c.second) / get<2>(c.second)) + "." + s.str() + " ") + get<0>(c.second).toUpper() + " | "; + } ui->balance->setText(b + QString::fromStdString(formatBalance(totalBalance))); } diff --git a/alethzero/MainWin.h b/alethzero/MainWin.h index 825e05456..f54a64053 100644 --- a/alethzero/MainWin.h +++ b/alethzero/MainWin.h @@ -159,8 +159,7 @@ private: void alterDebugStateGroup(bool _enable) const; void updateFee(); - void readSettings(bool _skipGeometry); - void readSettings() { readSettings(false); } + void readSettings(bool _skipGeometry = false); void writeSettings(); bool isCreation() const; @@ -205,10 +204,6 @@ private: unsigned m_balancesFilter = (unsigned)-1; QByteArray m_peers; - QMutex m_guiLock; - QTimer* m_ticker; - QTimer* m_refreshNetwork; - QTimer* m_refreshMining; QStringList m_servers; QList m_myKeys; bool m_keysChanged = false; diff --git a/libethcore/BlockInfo.cpp b/libethcore/BlockInfo.cpp index 172583271..2fdee90c7 100644 --- a/libethcore/BlockInfo.cpp +++ b/libethcore/BlockInfo.cpp @@ -176,7 +176,7 @@ void BlockInfo::verifyParent(BlockInfo const& _parent) const throw InvalidDifficulty(); if (gasLimit != calculateGasLimit(_parent)) - throw InvalidGasLimit(); + throw InvalidGasLimit(gasLimit, calculateGasLimit(_parent)); // Check timestamp is after previous timestamp. if (parentHash) diff --git a/libethcore/CommonEth.cpp b/libethcore/CommonEth.cpp index 08d9d2de4..336829c2f 100644 --- a/libethcore/CommonEth.cpp +++ b/libethcore/CommonEth.cpp @@ -29,7 +29,7 @@ using namespace eth; //#define ETH_ADDRESS_DEBUG 1 -const unsigned eth::c_protocolVersion = 25; +const unsigned eth::c_protocolVersion = 26; const unsigned eth::c_databaseVersion = 1; static const vector> g_units = diff --git a/libethcore/Exceptions.h b/libethcore/Exceptions.h index 127a84450..475ab1f05 100644 --- a/libethcore/Exceptions.h +++ b/libethcore/Exceptions.h @@ -26,7 +26,7 @@ class InvalidStateRoot: public Exception {}; class InvalidTransactionsHash: public Exception { public: InvalidTransactionsHash(h256 _head, h256 _real): m_head(_head), m_real(_real) {} h256 m_head; h256 m_real; virtual std::string description() const { return "Invalid transactions hash: header says: " + toHex(m_head.ref()) + " block is:" + toHex(m_real.ref()); } }; class InvalidTransaction: public Exception {}; class InvalidDifficulty: public Exception {}; -class InvalidGasLimit: public Exception {}; +class InvalidGasLimit: public Exception { public: InvalidGasLimit(u256 _provided = 0, u256 _valid = 0): provided(_provided), valid(_valid) {} u256 provided; u256 valid; virtual std::string description() const { return "Invalid gas limit (provided: " + toString(provided) + " valid:" + toString(valid) + ")"; } }; class InvalidMinGasPrice: public Exception { public: InvalidMinGasPrice(u256 _provided = 0, u256 _limit = 0): provided(_provided), limit(_limit) {} u256 provided; u256 limit; virtual std::string description() const { return "Invalid minimum gas price (provided: " + toString(provided) + " limit:" + toString(limit) + ")"; } }; class InvalidTransactionGasUsed: public Exception {}; class InvalidTransactionStateRoot: public Exception {}; diff --git a/libethereum/Client.h b/libethereum/Client.h index 1d6b072ce..f20b3538f 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -152,11 +152,11 @@ struct ClientWatch unsigned changes = 1; }; -struct WatchChannel: public LogChannel { static const char* name() { return "(o)"; } static const int verbosity = 6; }; +struct WatchChannel: public LogChannel { static const char* name() { return "(o)"; } static const int verbosity = 7; }; #define cwatch eth::LogOutputStream() -struct WorkInChannel: public LogChannel { static const char* name() { return ">W>"; } static const int verbosity = 5; }; -struct WorkOutChannel: public LogChannel { static const char* name() { return "W>"; } static const int verbosity = 6; }; +struct WorkOutChannel: public LogChannel { static const char* name() { return "() #define cworkin eth::LogOutputStream() #define cworkout eth::LogOutputStream() diff --git a/libqethereum/QEthereum.cpp b/libqethereum/QEthereum.cpp index 06a6a485b..ecc312d07 100644 --- a/libqethereum/QEthereum.cpp +++ b/libqethereum/QEthereum.cpp @@ -409,7 +409,13 @@ void QEthereum::doTransact(QString _json) return; TransactionSkeleton t = toTransaction(_json); if (!t.from && m_accounts.size()) - t.from = m_accounts[0].secret(); + { + auto b = m_accounts.first(); + for (auto a: m_accounts) + if (client()->balanceAt(KeyPair(a).address()) > client()->balanceAt(KeyPair(b).address())) + b = a; + t.from = b.secret(); + } if (!t.gasPrice) t.gasPrice = 10 * eth::szabo; if (!t.gas) diff --git a/stdserv.js b/stdserv.js index b9006ae76..d0eefc709 100644 --- a/stdserv.js +++ b/stdserv.js @@ -54,9 +54,20 @@ env.note('NameReg at address ' + nameReg) env.note('Register NameReg...') eth.transact(eth.key, '0', config, "0".pad(32) + nameReg.pad(32), 10000, eth.gasPrice); -var coinsCode = eth.lll(" +var dnsRegCode = '0x60006000546000600053602001546000600053604001546020604060206020600073661005d2720d855f1d9976f88bb10c1a3398c77f6103e8f17f7265676973746572000000000000000000000000000000000000000000000000600053606001600060200201547f446e735265670000000000000000000000000000000000000000000000000000600053606001600160200201546000600060006000604060606000600053604001536103e8f1327f6f776e65720000000000000000000000000000000000000000000000000000005761011663000000e46000396101166000f20060006000547f72656769737465720000000000000000000000000000000000000000000000006000602002350e0f630000006d596000600160200235560e0f630000006c59600032560e0f0f6300000057596000325657600260200235600160200235576001602002353257007f64657265676973746572000000000000000000000000000000000000000000006000602002350e0f63000000b95960016020023532560e0f63000000b959600032576000600160200235577f6b696c6c000000000000000000000000000000000000000000000000000000006000602002350e0f630000011559327f6f776e6572000000000000000000000000000000000000000000000000000000560e0f63000001155932ff00'; + +var dnsReg; +env.note('Create DnsReg...') +eth.create(eth.key, '0', dnsRegCode, 10000, eth.gasPrice, function(a) { dnsReg = a; }) + +env.note('DnsReg at address ' + dnsReg) + +env.note('Register DnsReg...') +eth.transact(eth.key, '0', config, "4".pad(32) + dnsReg.pad(32), 10000, eth.gasPrice); + +var coinRegCode = eth.lll(" { -[0]'register [32]'Coins +[0]'register [32]'CoinReg (msg allgas " + nameReg + " 0 0 64) (returnlll { (def 'name $0) @@ -72,14 +83,14 @@ var coinsCode = eth.lll(" } "); -var coins; -env.note('Create Coins...') -eth.create(eth.key, '0', coinsCode, 10000, eth.gasPrice, function(a) { coins = a; }) +var coinReg; +env.note('Create CoinReg...') +eth.create(eth.key, '0', coinRegCode, 10000, eth.gasPrice, function(a) { coinReg = a; }) -env.note('Coins at address ' + coins) +env.note('CoinReg at address ' + coinReg) -env.note('Register Coins...') -eth.transact(eth.key, '0', config, "1".pad(32) + coins.pad(32), 10000, eth.gasPrice); +env.note('Register CoinReg...') +eth.transact(eth.key, '0', config, "1".pad(32) + coinReg.pad(32), 10000, eth.gasPrice); var gavCoinCode = eth.lll(" { @@ -89,8 +100,8 @@ var gavCoinCode = eth.lll(" [0]'register [32]'GavCoin (msg allgas " + nameReg + " 0 0 64) -[0]'GAV [32]'1000 -(msg allgas " + coins + " 0 0 64) +[0]'GAV [32] 1000 +(msg allgas " + coinReg + " 0 0 64) (returnlll { (when (&& (= $0 'kill) (= (caller) @@0x69)) (suicide (caller))) diff --git a/third/CMakeLists.txt b/third/CMakeLists.txt new file mode 100644 index 000000000..3385fc265 --- /dev/null +++ b/third/CMakeLists.txt @@ -0,0 +1,100 @@ +set(CMAKE_INCLUDE_CURRENT_DIR ON) +aux_source_directory(. SRC_LIST) +include_directories(..) + +if (APPLE) + # Add homebrew path for qt5 + set(CMAKE_PREFIX_PATH /usr/local/opt/qt5) + include_directories(/usr/local/opt/qt5/include /usr/local/include) +elseif ("${TARGET_PLATFORM}" STREQUAL "w64") + set(SRC_LIST ${SRC_LIST} ../windows/qt_plugin_import.cpp) + include_directories(/usr/x86_64-w64-mingw32/include /usr/x86_64-w64-mingw32/include/QtCore /usr/x86_64-w64-mingw32/include/QtGui /usr/x86_64-w64-mingw32/include/QtQuick /usr/x86_64-w64-mingw32/include/QtQml /usr/x86_64-w64-mingw32/include/QtNetwork /usr/x86_64-w64-mingw32/include/QtWidgets /usr/x86_64-w64-mingw32/include/QtWebKit /usr/x86_64-w64-mingw32/include/QtWebKitWidgets) +elseif (UNIX) + set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ";$ENV{QTDIR}/lib/cmake") +endif () + +find_package(Qt5Core REQUIRED) +find_package(Qt5Gui REQUIRED) +find_package(Qt5Quick REQUIRED) +find_package(Qt5Qml REQUIRED) +find_package(Qt5Network REQUIRED) +find_package(Qt5Widgets REQUIRED) +find_package(Qt5WebKit REQUIRED) +find_package(Qt5WebKitWidgets REQUIRED) + +qt5_wrap_ui(ui_Main.h Main.ui) + +# Set name of binary and add_executable() +if (APPLE) + set(EXECUTEABLE Third) + set(BIN_INSTALL_DIR ".") + set(DOC_INSTALL_DIR ".") + + set(PROJECT_VERSION "${ETH_VERSION}") + set(MACOSX_BUNDLE_INFO_STRING "${PROJECT_NAME} ${PROJECT_VERSION}") + set(MACOSX_BUNDLE_BUNDLE_VERSION "${PROJECT_NAME} ${PROJECT_VERSION}") + set(MACOSX_BUNDLE_LONG_VERSION_STRING "${PROJECT_NAME} ${PROJECT_VERSION}") + set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${PROJECT_VERSION}") + set(MACOSX_BUNDLE_COPYRIGHT "${PROJECT_COPYRIGHT_YEAR} ${PROJECT_VENDOR}") + set(MACOSX_BUNDLE_GUI_IDENTIFIER "${PROJECT_DOMAIN_SECOND}.${PROJECT_DOMAIN_FIRST}") + set(MACOSX_BUNDLE_BUNDLE_NAME ${EXECUTEABLE}) + set(MACOSX_BUNDLE_ICON_FILE third) + include(BundleUtilities) + + add_executable(${EXECUTEABLE} MACOSX_BUNDLE third.icns Main.ui ${SRC_LIST}) + set_target_properties(${EXECUTEABLE} PROPERTIES MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/EthereumMacOSXBundleInfo.plist.in") + SET_SOURCE_FILES_PROPERTIES(${EXECUTEABLE} PROPERTIES MACOSX_PACKAGE_LOCATION MacOS) + SET_SOURCE_FILES_PROPERTIES(${MACOSX_BUNDLE_ICON_FILE}.icns PROPERTIES MACOSX_PACKAGE_LOCATION "Resources") + +else () + set(EXECUTEABLE third) + add_executable(${EXECUTEABLE} Main.ui ${SRC_LIST}) +endif () + +qt5_use_modules(${EXECUTEABLE} Core)# Gui Widgets Network WebKit WebKitWidgets) +target_link_libraries(${EXECUTEABLE} qethereum ethereum evm ethcore secp256k1 gmp ${CRYPTOPP_LS} serpent lll evmface ethential) + +if (APPLE) + # First have qt5 install plugins and frameworks + add_custom_command(TARGET ${EXECUTEABLE} POST_BUILD + COMMAND /usr/local/opt/qt5/bin/macdeployqt ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}/${EXECUTEABLE}.app + WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}) + + # This tool and next will inspect linked libraries in order to determine which dependencies are required + if (${CMAKE_CFG_INTDIR} STREQUAL ".") + set(APP_BUNDLE_PATH "${CMAKE_CURRENT_BINARY_DIR}/${EXECUTEABLE}.app") + else () + set(APP_BUNDLE_PATH "${CMAKE_CURRENT_BINARY_DIR}/\$ENV{CONFIGURATION}/${EXECUTEABLE}.app") + endif () + install(CODE " + include(BundleUtilities) + set(BU_CHMOD_BUNDLE_ITEMS 1) + fixup_bundle(\"${APP_BUNDLE_PATH}\" \"${BUNDLELIBS}\" \"../libqethereum ../libethereum ../secp256k1\") + " COMPONENT RUNTIME ) + # Cleanup duplicate libs from macdeployqt + install(CODE " + file(GLOB LINGER_RM \"${APP_BUNDLE_PATH}/Contents/Frameworks/*.dylib\") + if (LINGER_RM) + file(REMOVE \${LINGER_RM}) + endif () + ") +elseif ("${TARGET_PLATFORM}" STREQUAL "w64") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-keep-inline-dllexport -static-libgcc -static-libstdc++ -static") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-s -Wl,-subsystem,windows -mthreads -L/usr/x86_64-w64-mingw32/plugins/platforms") + target_link_libraries(${EXECUTEABLE} gcc) + target_link_libraries(${EXECUTEABLE} mingw32 qtmain mswsock iphlpapi qwindows shlwapi Qt5PlatformSupport opengl32 gdi32 comdlg32 oleaut32 imm32 winmm ole32 uuid ws2_32) + target_link_libraries(${EXECUTEABLE} boost_system-mt-s) + target_link_libraries(${EXECUTEABLE} boost_filesystem-mt-s) + target_link_libraries(${EXECUTEABLE} boost_thread_win32-mt-s) + target_link_libraries(${EXECUTEABLE} crypt32) + target_link_libraries(${EXECUTEABLE} Qt5PlatformSupport) + set(CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS) +elseif (UNIX) +else () + target_link_libraries(${EXECUTEABLE} boost_system) + target_link_libraries(${EXECUTEABLE} boost_filesystem) + find_package(Threads REQUIRED) + target_link_libraries(${EXECUTEABLE} ${CMAKE_THREAD_LIBS_INIT}) + install( TARGETS ${EXECUTEABLE} RUNTIME DESTINATION bin ) +endif () + diff --git a/third/Main.ui b/third/Main.ui new file mode 100644 index 000000000..52ad30e7b --- /dev/null +++ b/third/Main.ui @@ -0,0 +1,480 @@ + + + Main + + + + 0 + 0 + 1711 + 1138 + + + + Third + + + true + + + QMainWindow::AllowNestedDocks|QMainWindow::AllowTabbedDocks|QMainWindow::VerticalTabs + + + true + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + 0 wei + + + + + + + 0 peers + + + + + + + 1 block + + + + + + + + + + + + + + 0 + + + true + + + true + + + + Tab 1 + + + + 0 + + + 4 + + + 4 + + + 4 + + + 4 + + + + + + + + + about:blank + + + + + + + + + + + + + + + + 0 + 0 + 1711 + 25 + + + + + &File + + + + + + T&ools + + + + + + + + + + &Help + + + + + + &Network + + + + + + + + + + + + toolBar + + + TopToolBarArea + + + false + + + + + + QDockWidget::DockWidgetFeatureMask + + + Owned Accounts + + + 1 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + Qt::NoFocus + + + QFrame::NoFrame + + + QAbstractItemView::InternalMove + + + true + + + + + + + + + &Quit + + + Ctrl+Q + + + + + true + + + true + + + Use &UPnP + + + + + &Connect to Peer... + + + + + true + + + Enable &Network + + + + + true + + + &Mine + + + + + &New Address + + + + + &About... + + + + + true + + + &Preview + + + + + false + + + &Step Over + + + F10 + + + + + true + + + Mining &Paranoia + + + + + &Kill Blockchain + + + + + &Import Key... + + + + + &Export Selected Key... + + + + + &Inject Transaction + + + + + true + + + Show Ancient &Blocks + + + + + true + + + Show Anonymous &Accounts + + + + + true + + + Use &Past Peers + + + + + &Load Javascript... + + + + + false + + + Step Over &Backwards + + + Ctrl+F10 + + + + + true + + + &Force Mining + + + + + false + + + Standard with &Storage... + + + + + false + + + Step &Into + + + F11 + + + + + false + + + Step &Out + + + Shift+F11 + + + + + false + + + S&tandard... + + + + + false + + + Step Into Backwards + + + Ctrl+F11 + + + + + false + + + Step Out Backwards + + + Ctrl+Shift+F11 + + + + + false + + + Debu&g Current Transaction + + + + + false + + + D&ump Current Transaction State (post) + + + + + false + + + D&ump Current Transaction State (pre) + + + + + false + + + &Pretty... + + + + + &Refresh + + + + + &New Tab + + + + + + + QWebView + QWidget +
QtWebKitWidgets/QWebView
+
+
+ + tabWidget + urlEdit + + + +
diff --git a/third/MainWin.cpp b/third/MainWin.cpp new file mode 100644 index 000000000..784752959 --- /dev/null +++ b/third/MainWin.cpp @@ -0,0 +1,629 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file MainWin.cpp + * @author Gav Wood + * @date 2014 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "BuildInfo.h" +#include "MainWin.h" +#include "ui_Main.h" +using namespace std; + +// types +using eth::bytes; +using eth::bytesConstRef; +using eth::h160; +using eth::h256; +using eth::u160; +using eth::u256; +using eth::Address; +using eth::BlockInfo; +using eth::Client; +using eth::Instruction; +using eth::KeyPair; +using eth::NodeMode; +using eth::BlockChain; +using eth::PeerInfo; +using eth::RLP; +using eth::Secret; +using eth::Transaction; +using eth::Executive; + +// functions +using eth::toHex; +using eth::compileLLL; +using eth::disassemble; +using eth::formatBalance; +using eth::fromHex; +using eth::sha3; +using eth::left160; +using eth::right160; +using eth::simpleDebugOut; +using eth::toLog2; +using eth::toString; +using eth::units; +using eth::operator<<; + +// vars +using eth::g_logPost; +using eth::g_logVerbosity; +using eth::c_instructionInfo; + +static QString fromRaw(eth::h256 _n, unsigned* _inc = nullptr) +{ + if (_n) + { + std::string s((char const*)_n.data(), 32); + auto l = s.find_first_of('\0'); + if (!l) + return QString(); + if (l != string::npos) + { + auto p = s.find_first_not_of('\0', l); + if (!(p == string::npos || (_inc && p == 31))) + return QString(); + if (_inc) + *_inc = (byte)s[31]; + s.resize(l); + } + for (auto i: s) + if (i < 32) + return QString(); + return QString::fromStdString(s); + } + return QString(); +} + + +Address c_config = Address("661005d2720d855f1d9976f88bb10c1a3398c77f"); + +Main::Main(QWidget *parent) : + QMainWindow(parent), + ui(new Ui::Main) +{ + setWindowFlags(Qt::Window); + ui->setupUi(this); + + cerr << "State root: " << BlockChain::genesis().stateRoot << endl; + cerr << "Block Hash: " << sha3(BlockChain::createGenesisBlock()) << endl; + cerr << "Block RLP: " << RLP(BlockChain::createGenesisBlock()) << endl; + cerr << "Block Hex: " << toHex(BlockChain::createGenesisBlock()) << endl; + cerr << "Network protocol version: " << eth::c_protocolVersion << endl; + cerr << "Client database version: " << eth::c_databaseVersion << endl; + + ui->ownedAccountsDock->hide(); + + statusBar()->addPermanentWidget(ui->balance); + statusBar()->addPermanentWidget(ui->peerCount); + statusBar()->addPermanentWidget(ui->mineStatus); + statusBar()->addPermanentWidget(ui->blockCount); + + connect(ui->ourAccounts->model(), SIGNAL(rowsMoved(const QModelIndex &, int, int, const QModelIndex &, int)), SLOT(ourAccountsRowsMoved())); + + m_client.reset(new Client("Third")); + + connect(ui->webView, &QWebView::loadStarted, [this]() + { + // NOTE: no need to delete as QETH_INSTALL_JS_NAMESPACE adopts it. + m_ethereum = new QEthereum(this, m_client.get(), owned()); + + QWebFrame* f = ui->webView->page()->mainFrame(); + f->disconnect(SIGNAL(javaScriptWindowObjectCleared())); + auto qeth = m_ethereum; + connect(f, &QWebFrame::javaScriptWindowObjectCleared, QETH_INSTALL_JS_NAMESPACE(f, qeth, this)); + }); + + connect(ui->webView, &QWebView::loadFinished, [=]() + { + m_ethereum->poll(); + }); + + connect(ui->webView, &QWebView::titleChanged, [=]() + { + ui->tabWidget->setTabText(0, ui->webView->title()); + }); + + readSettings(); + + installWatches(); + + startTimer(100); + + { + QSettings s("ethereum", "third"); + if (s.value("splashMessage", true).toBool()) + { + QMessageBox::information(this, "Here Be Dragons!", "This is proof-of-concept software. The project as a whole is not even at the alpha-testing stage. It is here to show you, if you have a technical bent, the sort of thing that might be possible down the line.\nPlease don't blame us if it does something unexpected or if you're underwhelmed with the user-experience. We have great plans for it in terms of UX down the line but right now we just want to get the groundwork sorted. We welcome contributions, be they in code, testing or documentation!\nAfter you close this message it won't appear again."); + s.setValue("splashMessage", false); + } + } +} + +Main::~Main() +{ + // Must do this here since otherwise m_ethereum'll be deleted (and therefore clearWatches() called by the destructor) + // *after* the client is dead. + m_ethereum->clientDieing(); + + writeSettings(); +} + +void Main::onKeysChanged() +{ + installBalancesWatch(); +} + +unsigned Main::installWatch(eth::MessageFilter const& _tf, std::function const& _f) +{ + auto ret = m_client->installWatch(_tf); + m_handlers[ret] = _f; + return ret; +} + +unsigned Main::installWatch(eth::h256 _tf, std::function const& _f) +{ + auto ret = m_client->installWatch(_tf); + m_handlers[ret] = _f; + return ret; +} + +void Main::installWatches() +{ + installWatch(eth::MessageFilter().altered(c_config, 0), [=](){ installNameRegWatch(); }); + installWatch(eth::MessageFilter().altered(c_config, 1), [=](){ installCurrenciesWatch(); }); + installWatch(eth::ChainChangedFilter, [=](){ onNewBlock(); }); +} + +void Main::installNameRegWatch() +{ + m_client->uninstallWatch(m_nameRegFilter); + m_nameRegFilter = installWatch(eth::MessageFilter().altered((u160)m_client->stateAt(c_config, 0)), [=](){ onNameRegChange(); }); +} + +void Main::installCurrenciesWatch() +{ + m_client->uninstallWatch(m_currenciesFilter); + m_currenciesFilter = installWatch(eth::MessageFilter().altered((u160)m_client->stateAt(c_config, 1)), [=](){ onCurrenciesChange(); }); +} + +void Main::installBalancesWatch() +{ + eth::MessageFilter tf; + + vector
altCoins; + Address coinsAddr = right160(m_client->stateAt(c_config, 1)); + for (unsigned i = 0; i < m_client->stateAt(coinsAddr, 0); ++i) + altCoins.push_back(right160(m_client->stateAt(coinsAddr, i + 1))); + for (auto i: m_myKeys) + { + tf.altered(i.address()); + for (auto c: altCoins) + tf.altered(c, (u160)i.address()); + } + + m_client->uninstallWatch(m_balancesFilter); + m_balancesFilter = installWatch(tf, [=](){ onBalancesChange(); }); +} + +void Main::onNameRegChange() +{ + cwatch << "NameReg changed!"; + + // update any namereg-dependent stuff - for now force a full update. + refreshAll(); +} + +void Main::onCurrenciesChange() +{ + cwatch << "Currencies changed!"; + installBalancesWatch(); + + // TODO: update any currency-dependent stuff? +} + +void Main::onBalancesChange() +{ + cwatch << "Our balances changed!"; + + refreshBalances(); +} + +void Main::onNewBlock() +{ + cwatch << "Blockchain changed!"; + + // update blockchain dependent views. + refreshBlockCount(); +} + +void Main::note(QString _s) +{ + cnote << _s.toStdString(); +} + +void Main::debug(QString _s) +{ + cdebug << _s.toStdString(); +} + +void Main::warn(QString _s) +{ + cwarn << _s.toStdString(); +} + +void Main::eval(QString const& _js) +{ + if (_js.trimmed().isEmpty()) + return; + ui->webView->page()->currentFrame()->evaluateJavaScript("___RET=(" + _js + ")"); +} + +QString Main::pretty(eth::Address _a) const +{ + h256 n; + + if (h160 nameReg = (u160)m_client->stateAt(c_config, 0)) + n = m_client->stateAt(nameReg, (u160)(_a)); + + return fromRaw(n); +} + +QString Main::render(eth::Address _a) const +{ + QString p = pretty(_a); + if (!p.isNull()) + return p + " (" + QString::fromStdString(_a.abridged()) + ")"; + return QString::fromStdString(_a.abridged()); +} + +Address Main::fromString(QString const& _a) const +{ + if (_a == "(Create Contract)") + return Address(); + + string sn = _a.toStdString(); + if (sn.size() > 32) + sn.resize(32); + h256 n; + memcpy(n.data(), sn.data(), sn.size()); + memset(n.data() + sn.size(), 0, 32 - sn.size()); + if (_a.size()) + { + if (h160 nameReg = (u160)m_client->stateAt(c_config, 0)) + if (h256 a = m_client->stateAt(nameReg, n)) + return right160(a); + } + if (_a.size() == 40) + return Address(fromHex(_a.toStdString())); + else + return Address(); +} + +QString Main::lookup(QString const& _a) const +{ + if (!_a.endsWith(".eth")) + return _a; + + string sn = _a.mid(0, _a.size() - 4).toStdString(); + if (sn.size() > 32) + sn = sha3(sn, false); + h256 n; + memcpy(n.data(), sn.data(), sn.size()); + +/* string sn2 = _a.toStdString(); + if (sn2.size() > 32) + sn2 = sha3(sn2, false); + h256 n2; + memcpy(n2.data(), sn2.data(), sn2.size()); +*/ + + h256 ret; + if (h160 dnsReg = (u160)m_client->stateAt(c_config, 4, 0)) + ret = m_client->stateAt(dnsReg, n); +/* if (!ret) + if (h160 nameReg = (u160)m_client->stateAt(c_config, 0, 0)) + ret = m_client->stateAt(nameReg, n2); +*/ + if (ret && !((u256)ret >> 32)) + return QString("%1.%2.%3.%4").arg((int)ret[28]).arg((int)ret[29]).arg((int)ret[30]).arg((int)ret[31]); + // TODO: support IPv6. + else if (ret) + return fromRaw(ret); + else + return _a; +} + +void Main::on_about_triggered() +{ + QMessageBox::about(this, "About Third PoC-" + QString(eth::EthVersion).section('.', 1, 1), QString("Third/v") + eth::EthVersion + "/" ETH_QUOTED(ETH_BUILD_TYPE) "/" ETH_QUOTED(ETH_BUILD_PLATFORM) "\n" ETH_QUOTED(ETH_COMMIT_HASH) + (ETH_CLEAN_REPO ? "\nCLEAN" : "\n+ LOCAL CHANGES") + "\n\nBy Gav Wood, 2014.\nBased on a design by Vitalik Buterin.\n\nThanks to the various contributors including: Alex Leverington, Tim Hughes, caktux, Eric Lombrozo, Marko Simovic."); +} + +void Main::writeSettings() +{ + QSettings s("ethereum", "third"); + QByteArray b; + b.resize(sizeof(Secret) * m_myKeys.size()); + auto p = b.data(); + for (auto i: m_myKeys) + { + memcpy(p, &(i.secret()), sizeof(Secret)); + p += sizeof(Secret); + } + s.setValue("address", b); + s.setValue("url", ui->urlEdit->text()); + + bytes d = m_client->savePeers(); + if (d.size()) + m_peers = QByteArray((char*)d.data(), (int)d.size()); + s.setValue("peers", m_peers); + + s.setValue("geometry", saveGeometry()); + s.setValue("windowState", saveState()); +} + +void Main::readSettings(bool _skipGeometry) +{ + QSettings s("ethereum", "third"); + + if (!_skipGeometry) + restoreGeometry(s.value("geometry").toByteArray()); + restoreState(s.value("windowState").toByteArray()); + + m_myKeys.clear(); + QByteArray b = s.value("address").toByteArray(); + if (b.isEmpty()) + m_myKeys.append(KeyPair::create()); + else + { + h256 k; + for (unsigned i = 0; i < b.size() / sizeof(Secret); ++i) + { + memcpy(&k, b.data() + i * sizeof(Secret), sizeof(Secret)); + if (!count(m_myKeys.begin(), m_myKeys.end(), KeyPair(k))) + m_myKeys.append(KeyPair(k)); + } + } + m_client->setAddress(m_myKeys.back().address()); + m_peers = s.value("peers").toByteArray(); + ui->urlEdit->setText(s.value("url", "about:blank").toString()); //http://gavwood.com/gavcoin.html + on_urlEdit_returnPressed(); +} + +void Main::on_importKey_triggered() +{ + QString s = QInputDialog::getText(this, "Import Account Key", "Enter account's secret key"); + bytes b = fromHex(s.toStdString()); + if (b.size() == 32) + { + auto k = KeyPair(h256(b)); + if (std::find(m_myKeys.begin(), m_myKeys.end(), k) == m_myKeys.end()) + { + m_myKeys.append(k); + refreshBalances(); + } + else + QMessageBox::warning(this, "Already Have Key", "Could not import the secret key: we already own this account."); + } + else + QMessageBox::warning(this, "Invalid Entry", "Could not import the secret key; invalid key entered. Make sure it is 64 hex characters (0-9 or A-F)."); +} + +void Main::on_exportKey_triggered() +{ + if (ui->ourAccounts->currentRow() >= 0 && ui->ourAccounts->currentRow() < m_myKeys.size()) + { + auto k = m_myKeys[ui->ourAccounts->currentRow()]; + QMessageBox::information(this, "Export Account Key", "Secret key to account " + render(k.address()) + " is:\n" + QString::fromStdString(toHex(k.sec().ref()))); + } +} + +void Main::on_urlEdit_returnPressed() +{ + QString s = ui->urlEdit->text(); + QRegExp r("([a-z]+://)?([^/]*)(.*)"); + if (r.exactMatch(s)) + if (r.cap(2).isEmpty()) + s = (r.cap(1).isEmpty() ? "file://" : r.cap(1)) + r.cap(3); + else + s = (r.cap(1).isEmpty() ? "http://" : r.cap(1)) + lookup(r.cap(2)) + r.cap(3); + else{} + qDebug() << s; + ui->webView->setUrl(s); +} + +void Main::refreshMining() +{ + eth::MineProgress p = m_client->miningProgress(); + ui->mineStatus->setText(m_client->isMining() ? QString("%1s @ %2kH/s").arg(p.ms / 1000).arg(p.ms ? p.hashes / p.ms : 0) : "Not mining"); +} + +void Main::refreshBalances() +{ + cwatch << "refreshBalances()"; + // update all the balance-dependent stuff. + ui->ourAccounts->clear(); + u256 totalBalance = 0; + map> altCoins; + Address coinsAddr = right160(m_client->stateAt(c_config, 1)); + for (unsigned i = 0; i < m_client->stateAt(coinsAddr, 0); ++i) + altCoins[right160(m_client->stateAt(coinsAddr, m_client->stateAt(coinsAddr, i + 1)))] = make_pair(fromRaw(m_client->stateAt(coinsAddr, i + 1)), 0); + for (auto i: m_myKeys) + { + u256 b = m_client->balanceAt(i.address()); + (new QListWidgetItem(QString("%2: %1 [%3]").arg(formatBalance(b).c_str()).arg(render(i.address())).arg((unsigned)m_client->countAt(i.address())), ui->ourAccounts)) + ->setData(Qt::UserRole, QByteArray((char const*)i.address().data(), Address::size)); + totalBalance += b; + + for (auto& c: altCoins) + c.second.second += (u256)m_client->stateAt(c.first, (u160)i.address()); + } + + QString b; + for (auto const& c: altCoins) + if (c.second.second) + b += QString::fromStdString(toString(c.second.second)) + " " + c.second.first.toUpper() + " | "; + ui->balance->setText(b + QString::fromStdString(formatBalance(totalBalance))); +} + +void Main::refreshNetwork() +{ + auto ps = m_client->peers(); + + ui->peerCount->setText(QString::fromStdString(toString(ps.size())) + " peer(s)"); +} + +void Main::refreshAll() +{ + refreshBlockCount(); + refreshBalances(); +} + +void Main::refreshBlockCount() +{ + cwatch << "refreshBlockCount()"; + auto d = m_client->blockChain().details(); + auto diff = BlockInfo(m_client->blockChain().block()).difficulty; + ui->blockCount->setText(QString("#%1 @%3 T%2 N%4 D%5").arg(d.number).arg(toLog2(d.totalDifficulty)).arg(toLog2(diff)).arg(eth::c_protocolVersion).arg(eth::c_databaseVersion)); +} + +void Main::timerEvent(QTimerEvent*) +{ + // 7/18, Alex: aggregating timers, prelude to better threading? + // Runs much faster on slower dual-core processors + static int interval = 100; + + // refresh mining every 200ms + if (interval / 100 % 2 == 0) + refreshMining(); + + // refresh peer list every 1000ms, reset counter + if (interval == 1000) + { + interval = 0; + ensureNetwork(); + refreshNetwork(); + } + else + interval += 100; + + if (m_ethereum) + m_ethereum->poll(); + + for (auto const& i: m_handlers) + if (m_client->checkWatch(i.first)) + i.second(); +} + +void Main::ourAccountsRowsMoved() +{ + QList myKeys; + for (int i = 0; i < ui->ourAccounts->count(); ++i) + { + auto hba = ui->ourAccounts->item(i)->data(Qt::UserRole).toByteArray(); + auto h = Address((byte const*)hba.data(), Address::ConstructFromPointer); + for (auto i: m_myKeys) + if (i.address() == h) + myKeys.push_back(i); + } + m_myKeys = myKeys; + if (m_ethereum) + m_ethereum->setAccounts(myKeys); +} + +void Main::on_ourAccounts_doubleClicked() +{ + auto hba = ui->ourAccounts->currentItem()->data(Qt::UserRole).toByteArray(); + auto h = Address((byte const*)hba.data(), Address::ConstructFromPointer); + qApp->clipboard()->setText(QString::fromStdString(toHex(h.asArray()))); +} + +void Main::ensureNetwork() +{ + string n = string("Third/v") + eth::EthVersion; + n += "/" ETH_QUOTED(ETH_BUILD_TYPE) "/" ETH_QUOTED(ETH_BUILD_PLATFORM); + m_client->setClientVersion(n); + + int pocnumber = QString(eth::EthVersion).section('.', 1, 1).toInt(); + string defPeer; + if (pocnumber == 5) + defPeer = "54.72.69.180"; + else if (pocnumber == 6) + defPeer = "54.76.56.74"; + + if (!m_client->haveNetwork()) + m_client->startNetwork(30303, defPeer); + else + if (!m_client->peerCount()) + m_client->connect(defPeer); + if (m_peers.size()) + m_client->restorePeers(bytesConstRef((byte*)m_peers.data(), m_peers.size())); +} + +void Main::on_connect_triggered() +{ + bool ok = false; + QString s = QInputDialog::getItem(this, "Connect to a Network Peer", "Enter a peer to which a connection may be made:", m_servers, m_servers.count() ? rand() % m_servers.count() : 0, true, &ok); + if (ok && s.contains(":")) + { + string host = s.section(":", 0, 0).toStdString(); + unsigned short port = s.section(":", 1).toInt(); + m_client->connect(host, port); + } +} + +void Main::on_mine_triggered() +{ + if (ui->mine->isChecked()) + { + m_client->setAddress(m_myKeys.last().address()); + m_client->startMining(); + } + else + m_client->stopMining(); +} + +// extra bits needed to link on VS +#ifdef _MSC_VER + +// include moc file, ofuscated to hide from automoc +#include\ +"moc_MainWin.cpp" + +#include\ +"moc_MiningView.cpp" + +#endif diff --git a/third/MainWin.h b/third/MainWin.h new file mode 100644 index 000000000..4706b5158 --- /dev/null +++ b/third/MainWin.h @@ -0,0 +1,129 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file MainWin.h + * @author Gav Wood + * @date 2014 + */ + +#pragma once + + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace Ui { +class Main; +} + +namespace eth { +class Client; +class State; +class MessageFilter; +} + +class QQuickView; + +class Main : public QMainWindow +{ + Q_OBJECT + +public: + explicit Main(QWidget *parent = 0); + ~Main(); + + eth::Client* client() { return m_client.get(); } + + QList const& owned() const { return m_myKeys; } + +public slots: + void note(QString _entry); + void debug(QString _entry); + void warn(QString _entry); + void eval(QString const& _js); + + void onKeysChanged(); + +private slots: + void on_mine_triggered(); + void on_ourAccounts_doubleClicked(); + void ourAccountsRowsMoved(); + void on_about_triggered(); + void on_connect_triggered(); + void on_quit_triggered() { close(); } + void on_urlEdit_returnPressed(); + void on_importKey_triggered(); + void on_exportKey_triggered(); + +signals: + void poll(); + +private: + QString pretty(eth::Address _a) const; + QString render(eth::Address _a) const; + eth::Address fromString(QString const& _a) const; + QString lookup(QString const& _n) const; + + void readSettings(bool _skipGeometry = false); + void writeSettings(); + + unsigned installWatch(eth::MessageFilter const& _tf, std::function const& _f); + unsigned installWatch(eth::h256 _tf, std::function const& _f); + + void onNewBlock(); + void onNameRegChange(); + void onCurrenciesChange(); + void onBalancesChange(); + + void installWatches(); + void installCurrenciesWatch(); + void installNameRegWatch(); + void installBalancesWatch(); + + virtual void timerEvent(QTimerEvent*); + void ensureNetwork(); + + void refreshAll(); + void refreshBlockCount(); + void refreshBalances(); + void refreshNetwork(); + void refreshMining(); + + std::unique_ptr ui; + + std::unique_ptr m_client; + + QList m_myKeys; + + std::map> m_handlers; + unsigned m_nameRegFilter = (unsigned)-1; + unsigned m_currenciesFilter = (unsigned)-1; + unsigned m_balancesFilter = (unsigned)-1; + + QByteArray m_peers; + QStringList m_servers; + + QNetworkAccessManager m_webCtrl; + + QEthereum* m_ethereum = nullptr; +}; diff --git a/third/main.cpp b/third/main.cpp new file mode 100644 index 000000000..42afd5e66 --- /dev/null +++ b/third/main.cpp @@ -0,0 +1,11 @@ +#include "MainWin.h" +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + Main w; + w.show(); + + return a.exec(); +} From 98d425fa12e702d87d7d118662c9d97f4b6f4a2f Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 9 Aug 2014 19:34:32 +0100 Subject: [PATCH 066/223] New PoC-6 JS API. --- libqethereum/QEthereum.cpp | 12 +++++++++-- libqethereum/QEthereum.h | 43 ++++++++++++++++++++++++-------------- 2 files changed, 37 insertions(+), 18 deletions(-) diff --git a/libqethereum/QEthereum.cpp b/libqethereum/QEthereum.cpp index ecc312d07..9dd866a9a 100644 --- a/libqethereum/QEthereum.cpp +++ b/libqethereum/QEthereum.cpp @@ -53,8 +53,11 @@ eth::bytes toBytes(QString const& _s) // Decimal return eth::toCompactBigEndian(eth::bigint(_s.toStdString())); else + { // Binary + cwarn << "THIS FUNCTIONALITY IS DEPRECATED. DO NOT ASSUME ASCII/BINARY-STRINGS WILL BE ACCEPTED. USE eth.fromAscii()."; return asBytes(_s); + } } QString padded(QString const& _s, unsigned _l, unsigned _r) @@ -131,6 +134,11 @@ QString QEthereum::sha3(QString _s) const return toQJS(eth::sha3(asBytes(_s))); } +QString QEthereum::sha3old(QString _s) const +{ + return toQJS(eth::sha3(asBytes(_s))); +} + QString QEthereum::offset(QString _s, int _i) const { return toQJS(toU256(_s) + _i); @@ -449,9 +457,9 @@ unsigned QEthereum::newWatch(QString _json) if (!m_client) return (unsigned)-1; unsigned ret; - if (_json == "chainChanged") + if (_json == "chain") ret = m_client->installWatch(eth::ChainChangedFilter); - else if (_json == "pendingChanged") + else if (_json == "pending") ret = m_client->installWatch(eth::PendingChangedFilter); else ret = m_client->installWatch(toMessageFilter(_json)); diff --git a/libqethereum/QEthereum.h b/libqethereum/QEthereum.h index c1dc2208b..c4b10042a 100644 --- a/libqethereum/QEthereum.h +++ b/libqethereum/QEthereum.h @@ -77,14 +77,20 @@ inline double toFixed(QString const& _s) return (double)toU256(_s) / (double)(eth::u256(1) << 128); } -inline QString fromBinary(eth::bytes const& _s) +inline QString fromFixed(double _s) { + return toQJS(eth::u256(_s * (double)(eth::u256(1) << 128))); +} + +inline QString fromBinary(eth::bytes _s, unsigned _padding = 32) +{ + _s.resize(std::max(_s.size(), _padding)); return QString::fromStdString("0x" + eth::toHex(_s)); } -inline QString fromBinary(QString const& _s) +inline QString fromBinary(QString const& _s, unsigned _padding = 32) { - return fromBinary(asBytes(_s)); + return fromBinary(asBytes(_s), _padding); } class QEthereum: public QObject @@ -110,14 +116,19 @@ public: Q_INVOKABLE QString lll(QString _s) const; Q_INVOKABLE QString sha3(QString _s) const; + Q_INVOKABLE QString sha3old(QString _s) const; Q_INVOKABLE QString offset(QString _s, int _offset) const; + Q_INVOKABLE QString pad(QString _s, unsigned _l) const { return padded(_s, _l); } Q_INVOKABLE QString pad(QString _s, unsigned _l, unsigned _r) const { return padded(_s, _l, _r); } Q_INVOKABLE QString unpad(QString _s) const { return unpadded(_s); } - Q_INVOKABLE QString toBinary(QString _s) const { return ::toBinary(_s); } - Q_INVOKABLE QString fromBinary(QString _s) const { return ::fromBinary(_s); } + + Q_INVOKABLE QString toAscii(QString _s) const { return ::toBinary(_s); } + Q_INVOKABLE QString fromAscii(QString _s) const { return ::fromBinary(_s, 32); } + Q_INVOKABLE QString fromAscii(QString _s, unsigned _padding) const { return ::fromBinary(_s, _padding); } Q_INVOKABLE QString toDecimal(QString _s) const { return ::toDecimal(_s); } Q_INVOKABLE double toFixed(QString _s) const { return ::toFixed(_s); } + Q_INVOKABLE QString fromFixed(double _d) const { return ::fromFixed(_d); } // [NEW API] - Use this instead. Q_INVOKABLE QString/*eth::u256*/ balanceAt(QString/*eth::Address*/ _a, int _block) const; @@ -197,20 +208,20 @@ private: frame->addToJavaScriptWindowObject("eth", eth, QWebFrame::ScriptOwnership); \ frame->evaluateJavaScript("eth.makeWatch = function(a) { var ww = eth.newWatch(a); var ret = { w: ww }; ret.uninstall = function() { eth.killWatch(w); }; ret.changed = function(f) { eth.watchChanged.connect(function(nw) { if (nw == ww) f() }); }; ret.messages = function() { return JSON.parse(eth.watchMessages(this.w)) }; return ret; }"); \ frame->evaluateJavaScript("eth.watch = function(a) { return eth.makeWatch(JSON.stringify(a)) }"); \ - frame->evaluateJavaScript("eth.watchChain = function() { return eth.makeWatch('chainChanged') }"); \ - frame->evaluateJavaScript("eth.watchPending = function() { return eth.makeWatch('pendingChanged') }"); \ - frame->evaluateJavaScript("eth.create = function(s, v, c, g, p, f) { var v = eth.doCreate(s, v, c, g, p); if (f) f(v) }"); \ - frame->evaluateJavaScript("eth.transact = function(a_s, f_v, t, d, g, p, f) { if (t == null) { eth.doTransact(JSON.stringify(a_s)); if (f_v) f_v(); } else { eth.doTransact(a_s, f_v, t, d, g, p); if (f) f() } }"); \ + frame->evaluateJavaScript("eth.watchChain = function() { env.warn('THIS CALL IS DEPRECATED. USE eth.watch('chain') INSTEAD.'); return eth.makeWatch('chain') }"); \ + frame->evaluateJavaScript("eth.watchPending = function() { env.warn('THIS CALL IS DEPRECATED. USE eth.watch('pending') INSTEAD.'); return eth.makeWatch('pending') }"); \ + frame->evaluateJavaScript("eth.create = function(s, v, c, g, p, f) { env.warn('THIS CALL IS DEPRECATED. USE eth.transact INSTEAD.'); var v = eth.doCreate(s, v, c, g, p); if (f) f(v) }"); \ + frame->evaluateJavaScript("eth.transact = function(a_s, f_v, t, d, g, p, f) { if (t == null) { eth.doTransact(JSON.stringify(a_s)); if (f_v) f_v(); } else { env.warn('THIS FORM OF THIS CALL IS DEPRECATED.'); eth.doTransact(a_s, f_v, t, d, g, p); if (f) f() } }"); \ frame->evaluateJavaScript("eth.call = function(a, f) { var ret = eth.doCallJson(JSON.stringify(a)); if (f) f(ret); return ret; }"); \ frame->evaluateJavaScript("eth.messages = function(a) { return JSON.parse(eth.getMessages(JSON.stringify(a))); }"); \ frame->evaluateJavaScript("eth.transactions = function(a) { env.warn('THIS CALL IS DEPRECATED. USE eth.messages INSTEAD.'); return JSON.parse(eth.getMessages(JSON.stringify(a))); }"); \ - frame->evaluateJavaScript("String.prototype.pad = function(l, r) { return eth.pad(this, l, r) }"); \ - frame->evaluateJavaScript("String.prototype.bin = function() { return eth.toBinary(this) }"); \ - frame->evaluateJavaScript("String.prototype.unbin = function(l) { return eth.fromBinary(this) }"); \ - frame->evaluateJavaScript("String.prototype.unpad = function(l) { return eth.unpad(this) }"); \ - frame->evaluateJavaScript("String.prototype.dec = function() { return eth.toDecimal(this) }"); \ - frame->evaluateJavaScript("String.prototype.fix = function() { return eth.toFixed(this) }"); \ - frame->evaluateJavaScript("String.prototype.sha3 = function() { return eth.sha3(this) }"); \ + frame->evaluateJavaScript("String.prototype.pad = function(l, r) { env.warn('THIS CALL IS DEPRECATED. USE eth.* INSTEAD.'); return eth.pad(this, l, r) }"); \ + frame->evaluateJavaScript("String.prototype.bin = function() { env.warn('THIS CALL IS DEPRECATED. USE eth.* INSTEAD.'); return eth.toAscii(this) }"); \ + frame->evaluateJavaScript("String.prototype.unbin = function(l) { env.warn('THIS CALL IS DEPRECATED. USE eth.* INSTEAD.'); return eth.fromAscii(this) }"); \ + frame->evaluateJavaScript("String.prototype.unpad = function(l) { env.warn('THIS CALL IS DEPRECATED. USE eth.* INSTEAD.'); return eth.unpad(this) }"); \ + frame->evaluateJavaScript("String.prototype.dec = function() { env.warn('THIS CALL IS DEPRECATED. USE eth.* INSTEAD.'); return eth.toDecimal(this) }"); \ + frame->evaluateJavaScript("String.prototype.fix = function() { env.warn('THIS CALL IS DEPRECATED. USE eth.* INSTEAD.'); return eth.toFixed(this) }"); \ + frame->evaluateJavaScript("String.prototype.sha3 = function() { env.warn('THIS CALL IS DEPRECATED. USE eth.* INSTEAD.'); return eth.sha3old(this) }"); \ } template inline boost::multiprecision::number> toInt(QString const& _s) From 281fb1bf0ad76a0be9ab47464ef5eb152c508059 Mon Sep 17 00:00:00 2001 From: Giacomo Tazzari Date: Sat, 9 Aug 2014 23:42:38 +0200 Subject: [PATCH 067/223] Minor GUI improvement to prevent the Block Chain text widget, the Contracts text widget and the Pending text widget from automatically scrolling down to the bottom --- alethzero/MainWin.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index c9d8e22ca..6c18d39bd 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -1018,6 +1018,7 @@ void Main::on_transactionQueue_currentItemChanged() } ui->pendingInfo->setHtml(QString::fromStdString(s.str())); + ui->pendingInfo->moveCursor(QTextCursor::Start); } void Main::ourAccountsRowsMoved() @@ -1122,6 +1123,7 @@ void Main::on_blocks_currentItemChanged() } ui->info->appendHtml(QString::fromStdString(s.str())); + ui->info->moveCursor(QTextCursor::Start); } } @@ -1237,6 +1239,7 @@ void Main::on_contracts_currentItemChanged() { ui->contractInfo->appendHtml("Corrupted trie."); } + ui->contractInfo->moveCursor(QTextCursor::Start); } } From 02f1ed465c894d0b48435a25dd5180433cc38924 Mon Sep 17 00:00:00 2001 From: Giacomo Tazzari Date: Sun, 10 Aug 2014 00:42:34 +0200 Subject: [PATCH 068/223] Using boost::spirit::standard namespace instead of boost::spirit::ascii in parseTreeLLL() to prevent crashing when parsing code containing non-ascii characters --- liblll/Parser.cpp | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/liblll/Parser.cpp b/liblll/Parser.cpp index 1907fd17c..edcd38f32 100644 --- a/liblll/Parser.cpp +++ b/liblll/Parser.cpp @@ -85,7 +85,8 @@ struct tagNode void eth::parseTreeLLL(string const& _s, sp::utree& o_out) { - using qi::ascii::space; + using qi::standard::space; + using qi::standard::space_type; using eth::parseTreeLLL_::tagNode; typedef sp::basic_string symbol_type; typedef string::const_iterator it; @@ -94,24 +95,24 @@ void eth::parseTreeLLL(string const& _s, sp::utree& o_out) static const u256 finney = u256(1000000000) * 1000000; static const u256 szabo = u256(1000000000) * 1000; - qi::rule element; + qi::rule element; qi::rule str = '"' > qi::lexeme[+(~qi::char_(std::string("\"") + '\0'))] > '"'; qi::rule strsh = '\'' > qi::lexeme[+(~qi::char_(std::string(" ;$@()[]{}:\n\t") + '\0'))]; qi::rule symbol = qi::lexeme[+(~qi::char_(std::string(" $@[]{}:();\"\x01-\x1f\x7f") + '\0'))]; qi::rule intstr = qi::lexeme[ qi::no_case["0x"][qi::_val = "0x"] >> *qi::char_("0-9a-fA-F")[qi::_val += qi::_1]] | qi::lexeme[+qi::char_("0-9")[qi::_val += qi::_1]]; qi::rule integer = intstr; qi::rule multiplier = qi::lit("wei")[qi::_val = 1] | qi::lit("szabo")[qi::_val = szabo] | qi::lit("finney")[qi::_val = finney] | qi::lit("ether")[qi::_val = ether]; - qi::rule quantity = integer[qi::_val = qi::_1] >> -multiplier[qi::_val *= qi::_1]; - qi::rule atom = quantity[qi::_val = px::construct(px::new_(qi::_1))] | (str | strsh)[qi::_val = qi::_1] | symbol[qi::_val = qi::_1]; - qi::rule seq = '{' > *element > '}'; - qi::rule mload = '@' > element; - qi::rule sload = qi::lit("@@") > element; - qi::rule mstore = '[' > element > ']' > -qi::lit(":") > element; - qi::rule sstore = qi::lit("[[") > element > qi::lit("]]") > -qi::lit(":") > element; - qi::rule calldataload = qi::lit("$") > element; - qi::rule list = '(' > *element > ')'; - - qi::rule extra = sload[tagNode<2>()] | mload[tagNode<1>()] | sstore[tagNode<4>()] | mstore[tagNode<3>()] | seq[tagNode<5>()] | calldataload[tagNode<6>()]; + qi::rule quantity = integer[qi::_val = qi::_1] >> -multiplier[qi::_val *= qi::_1]; + qi::rule atom = quantity[qi::_val = px::construct(px::new_(qi::_1))] | (str | strsh)[qi::_val = qi::_1] | symbol[qi::_val = qi::_1]; + qi::rule seq = '{' > *element > '}'; + qi::rule mload = '@' > element; + qi::rule sload = qi::lit("@@") > element; + qi::rule mstore = '[' > element > ']' > -qi::lit(":") > element; + qi::rule sstore = qi::lit("[[") > element > qi::lit("]]") > -qi::lit(":") > element; + qi::rule calldataload = qi::lit("$") > element; + qi::rule list = '(' > *element > ')'; + + qi::rule extra = sload[tagNode<2>()] | mload[tagNode<1>()] | sstore[tagNode<4>()] | mstore[tagNode<3>()] | seq[tagNode<5>()] | calldataload[tagNode<6>()]; element = atom | list | extra; string s; From 9db294a5b7ca87e866aa9bf811078ff57ceeb48e Mon Sep 17 00:00:00 2001 From: Giacomo Tazzari Date: Sun, 10 Aug 2014 12:26:56 +0200 Subject: [PATCH 069/223] =?UTF-8?q?Fixed=20a=20crash=20in=20serpent=20comp?= =?UTF-8?q?iler=20when=20converting=20to=20number=20a=20string=20containin?= =?UTF-8?q?g=20non-ascii=20character=20(e.g.:=20a=20=3D=20"per=C3=B2").=20?= =?UTF-8?q?To=20prevent=20further=20signed=20int=20overflows:=20modified?= =?UTF-8?q?=20intToDecimal()=20and=20decimalToInt()=20into=20unsignedToDec?= =?UTF-8?q?imal()=20and=20decimalToUnsigned()=20because=20they=20currently?= =?UTF-8?q?=20make=20sense=20only=20on=20unsigned=20integers.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- libserpent/bignum.cpp | 8 ++++---- libserpent/bignum.h | 4 ++-- libserpent/compiler.cpp | 26 +++++++++++++------------- libserpent/rewriter.cpp | 8 ++++---- libserpent/util.cpp | 12 ++++++------ sc/cmdline.cpp | 2 +- 6 files changed, 30 insertions(+), 30 deletions(-) diff --git a/libserpent/bignum.cpp b/libserpent/bignum.cpp index 29315b871..877808ead 100644 --- a/libserpent/bignum.cpp +++ b/libserpent/bignum.cpp @@ -5,9 +5,9 @@ #include "bignum.h" //Integer to string conversion -std::string intToDecimal(int branch) { +std::string unsignedToDecimal(unsigned branch) { if (branch < 10) return nums.substr(branch, 1); - else return intToDecimal(branch / 10) + nums.substr(branch % 10,1); + else return unsignedToDecimal(branch / 10) + nums.substr(branch % 10,1); } //Add two strings representing decimal values @@ -91,8 +91,8 @@ std::string decimalMod(std::string a, std::string b) { } //String to int conversion -int decimalToInt(std::string a) { +unsigned decimalToUnsigned(std::string a) { if (a.size() == 0) return 0; else return (a[a.size() - 1] - '0') - + decimalToInt(a.substr(0,a.size()-1)) * 10; + + decimalToUnsigned(a.substr(0,a.size()-1)) * 10; } diff --git a/libserpent/bignum.h b/libserpent/bignum.h index a12929752..6656fdaec 100644 --- a/libserpent/bignum.h +++ b/libserpent/bignum.h @@ -11,7 +11,7 @@ const std::string tt255 = "57896044618658097711785492504343953926634992332820282019728792003956564819968" ; -std::string intToDecimal(int branch); +std::string unsignedToDecimal(unsigned branch); std::string decimalAdd(std::string a, std::string b); @@ -25,6 +25,6 @@ std::string decimalMod(std::string a, std::string b); bool decimalGt(std::string a, std::string b, bool eqAllowed=false); -int decimalToInt(std::string a); +unsigned decimalToUnsigned(std::string a); #endif diff --git a/libserpent/compiler.cpp b/libserpent/compiler.cpp index 959d2993b..69292091c 100644 --- a/libserpent/compiler.cpp +++ b/libserpent/compiler.cpp @@ -55,7 +55,7 @@ programData opcodeify(Node node, programAux aux=Aux()) { else if (node.val == "ref" || node.val == "get" || node.val == "set") { std::string varname = node.args[0].val; if (!aux.vars.count(varname)) { - aux.vars[varname] = intToDecimal(aux.vars.size() * 32); + aux.vars[varname] = unsignedToDecimal(aux.vars.size() * 32); } if (varname == "msg.data") aux.calldataUsed = true; // Set variable @@ -165,7 +165,7 @@ programData opcodeify(Node node, programAux aux=Aux()) { nodes.push_back(token("MSIZE", m)); nodes.push_back(token("0", m)); nodes.push_back(token("MSIZE", m)); - nodes.push_back(token(intToDecimal(subs.size() * 32 - 1), m)); + nodes.push_back(token(unsignedToDecimal(subs.size() * 32 - 1), m)); nodes.push_back(token("ADD", m)); nodes.push_back(token("MSTORE8", m)); for (unsigned i = 0; i < subs.size(); i++) { @@ -173,7 +173,7 @@ programData opcodeify(Node node, programAux aux=Aux()) { nodes.push_back(subs[i]); nodes.push_back(token("SWAP", m)); if (i > 0) { - nodes.push_back(token(intToDecimal(i * 32), m)); + nodes.push_back(token(unsignedToDecimal(i * 32), m)); nodes.push_back(token("ADD", m)); } nodes.push_back(token("MSTORE", m)); @@ -201,7 +201,7 @@ Node finalize(programData c) { if (c.aux.allocUsed && c.aux.vars.size() > 0) { Node nodelist[] = { token("0", m), - token(intToDecimal(c.aux.vars.size() * 32 - 1)), + token(unsignedToDecimal(c.aux.vars.size() * 32 - 1)), token("MSTORE8", m) }; bottom.push_back(multiToken(nodelist, 3, m)); @@ -235,7 +235,7 @@ programAux buildDict(Node program, programAux aux, int labelLength) { aux.step += 1 + toByteArr(program.val, m).size(); } else if (program.val[0] == '~') { - aux.vars[program.val.substr(1)] = intToDecimal(aux.step); + aux.vars[program.val.substr(1)] = unsignedToDecimal(aux.step); } else if (program.val[0] == '$') { aux.step += labelLength + 1; @@ -271,7 +271,7 @@ Node substDict(Node program, programAux aux, int labelLength) { std::vector inner; if (program.type == TOKEN) { if (program.val[0] == '$') { - std::string tokStr = "PUSH"+intToDecimal(labelLength); + std::string tokStr = "PUSH"+unsignedToDecimal(labelLength); out.push_back(token(tokStr, m)); int dotLoc = program.val.find('.'); if (dotLoc == -1) { @@ -289,7 +289,7 @@ Node substDict(Node program, programAux aux, int labelLength) { else if (program.val[0] == '~') { } else if (isNumberLike(program)) { inner = toByteArr(program.val, m); - out.push_back(token("PUSH"+intToDecimal(inner.size()))); + out.push_back(token("PUSH"+unsignedToDecimal(inner.size()))); out.push_back(astnode("_", inner, m)); } else return program; @@ -333,10 +333,10 @@ std::string serialize(std::vector codons) { for (unsigned i = 0; i < codons.size(); i++) { int v; if (isNumberLike(codons[i])) { - v = decimalToInt(codons[i].val); + v = decimalToUnsigned(codons[i].val); } else if (codons[i].val.substr(0,4) == "PUSH") { - v = 95 + decimalToInt(codons[i].val.substr(4)); + v = 95 + decimalToUnsigned(codons[i].val.substr(4)); } else { v = opcode(codons[i].val); @@ -355,9 +355,9 @@ std::vector deserialize(std::string ser) { std::string oper = op((int)v); if (oper != "" && backCount <= 0) o.push_back(token(oper)); else if (v >= 96 && v < 128 && backCount <= 0) { - o.push_back(token("PUSH"+intToDecimal(v - 95))); + o.push_back(token("PUSH"+unsignedToDecimal(v - 95))); } - else o.push_back(token(intToDecimal(v))); + else o.push_back(token(unsignedToDecimal(v))); if (v >= 96 && v < 128 && backCount <= 0) { backCount = v - 95; } @@ -392,7 +392,7 @@ std::string encodeDatalist(std::vector vals) { for (unsigned i = 0; i < vals.size(); i++) { std::vector n = toByteArr(strToNumeric(vals[i]), Metadata(), 32); for (unsigned j = 0; j < n.size(); j++) { - int v = decimalToInt(n[j].val); + int v = decimalToUnsigned(n[j].val); o += (char)v; } } @@ -406,7 +406,7 @@ std::vector decodeDatalist(std::string ser) { std::string o = "0"; for (unsigned j = i; j < i + 32; j++) { int vj = (int)(unsigned char)ser[j]; - o = decimalAdd(decimalMul(o, "256"), intToDecimal(vj)); + o = decimalAdd(decimalMul(o, "256"), unsignedToDecimal(vj)); } out.push_back(o); } diff --git a/libserpent/rewriter.cpp b/libserpent/rewriter.cpp index 8b50c9a28..3d59cb3bd 100644 --- a/libserpent/rewriter.cpp +++ b/libserpent/rewriter.cpp @@ -344,7 +344,7 @@ Node subst(Node pattern, Node array_lit_transform(Node node) { std::vector o1; - o1.push_back(token(intToDecimal(node.args.size() * 32), node.metadata)); + o1.push_back(token(unsignedToDecimal(node.args.size() * 32), node.metadata)); std::vector o2; std::string symb = "_temp"+mkUniqueToken()+"_0"; o2.push_back(token(symb, node.metadata)); @@ -357,7 +357,7 @@ Node array_lit_transform(Node node) { o5.push_back(token(symb, node.metadata)); std::vector o6; o6.push_back(astnode("get", o5, node.metadata)); - o6.push_back(token(intToDecimal(i * 32), node.metadata)); + o6.push_back(token(unsignedToDecimal(i * 32), node.metadata)); std::vector o7; o7.push_back(astnode("add", o6)); o7.push_back(node.args[i]); @@ -474,10 +474,10 @@ Node validate(Node inp) { int i = 0; while(valid[i][0] != "---END---") { if (inp.val == valid[i][0]) { - if (decimalGt(valid[i][1], intToDecimal(inp.args.size()))) { + if (decimalGt(valid[i][1], unsignedToDecimal(inp.args.size()))) { err("Too few arguments for "+inp.val, inp.metadata); } - if (decimalGt(intToDecimal(inp.args.size()), valid[i][2])) { + if (decimalGt(unsignedToDecimal(inp.args.size()), valid[i][2])) { err("Too many arguments for "+inp.val, inp.metadata); } } diff --git a/libserpent/util.cpp b/libserpent/util.cpp index 6ca39de9d..efef91425 100644 --- a/libserpent/util.cpp +++ b/libserpent/util.cpp @@ -60,8 +60,8 @@ std::string printAST(Node ast, bool printMetadata) { std::string o = "("; if (printMetadata) { o += ast.metadata.file + " "; - o += intToDecimal(ast.metadata.ln) + " "; - o += intToDecimal(ast.metadata.ch) + ": "; + o += unsignedToDecimal(ast.metadata.ln) + " "; + o += unsignedToDecimal(ast.metadata.ch) + ": "; } o += ast.val; std::vector subs; @@ -132,14 +132,14 @@ std::string strToNumeric(std::string inp) { else if ((inp[0] == '"' && inp[inp.length()-1] == '"') || (inp[0] == '\'' && inp[inp.length()-1] == '\'')) { for (unsigned i = 1; i < inp.length() - 1; i++) { - o = decimalAdd(decimalMul(o,"256"), intToDecimal(inp[i])); + o = decimalAdd(decimalMul(o,"256"), unsignedToDecimal((unsigned char)inp[i])); } } else if (inp.substr(0,2) == "0x") { for (unsigned i = 2; i < inp.length(); i++) { int dig = std::string("0123456789abcdef").find(inp[i]); if (dig < 0) return ""; - o = decimalAdd(decimalMul(o,"16"), intToDecimal(dig)); + o = decimalAdd(decimalMul(o,"16"), unsignedToDecimal(dig)); } } else { @@ -188,7 +188,7 @@ int counter = 0; //Makes a unique token std::string mkUniqueToken() { counter++; - return intToDecimal(counter); + return unsignedToDecimal(counter); } //Does a file exist? http://stackoverflow.com/questions/12774207 @@ -217,7 +217,7 @@ std::string get_file_contents(std::string filename) //Report error void err(std::string errtext, Metadata met) { std::string err = "Error (file \"" + met.file + "\", line " + - intToDecimal(met.ln) + ", char " + intToDecimal(met.ch) + + unsignedToDecimal(met.ln) + ", char " + unsignedToDecimal(met.ch) + "): " + errtext; std::cerr << err << "\n"; throw(err); diff --git a/sc/cmdline.cpp b/sc/cmdline.cpp index 232cbfeec..4b2eee160 100644 --- a/sc/cmdline.cpp +++ b/sc/cmdline.cpp @@ -96,7 +96,7 @@ int main(int argv, char** argc) { else if (command == "biject") { if (argv == 3) std::cerr << "Not enough arguments for biject\n"; - int pos = decimalToInt(secondInput); + int pos = decimalToUnsigned(secondInput); std::vector n = prettyCompile(input); if (pos >= (int)n.size()) std::cerr << "Code position too high\n"; From 6a4488bf698ddcd3a9fa4e490955fd8d8fb93dbc Mon Sep 17 00:00:00 2001 From: Giacomo Tazzari Date: Sun, 10 Aug 2014 20:28:51 +0200 Subject: [PATCH 070/223] Enabled click focus on the "code" text widget, to allow copying the text result of code compilation to clipboard --- alethzero/Main.ui | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/alethzero/Main.ui b/alethzero/Main.ui index a01ac7856..f52a2d043 100644 --- a/alethzero/Main.ui +++ b/alethzero/Main.ui @@ -563,7 +563,7 @@ - Qt::NoFocus + Qt::ClickFocus QFrame::NoFrame @@ -571,6 +571,9 @@ 0 + + true + From 9fcc4105162c910887a9b7cb84f05feb4e7d01e6 Mon Sep 17 00:00:00 2001 From: Giacomo Tazzari Date: Sun, 10 Aug 2014 22:51:29 +0200 Subject: [PATCH 071/223] Added a debug menu option to disable compiler optimization, for testing purposes --- alethzero/Main.ui | 9 +++++++++ alethzero/MainWin.cpp | 19 +++++++++++++++---- alethzero/MainWin.h | 2 ++ 3 files changed, 26 insertions(+), 4 deletions(-) diff --git a/alethzero/Main.ui b/alethzero/Main.ui index f52a2d043..d3f027ac1 100644 --- a/alethzero/Main.ui +++ b/alethzero/Main.ui @@ -185,6 +185,7 @@ + @@ -1667,6 +1668,14 @@ font-size: 14pt &Refresh + + + true + + + &Disable LLL Compiler Optimization + + diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index 6c18d39bd..19ce70587 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -332,6 +332,12 @@ void Main::on_forceMining_triggered() m_client->setForceMining(ui->forceMining->isChecked()); } +void Main::on_disableCompilerOptimization_triggered() +{ + m_disableCompilerOptimization = ui->disableCompilerOptimization->isChecked(); + on_data_textChanged(); +} + void Main::load(QString _s) { QFile fin(_s); @@ -1302,9 +1308,7 @@ void Main::on_data_textChanged() } else { - auto asmcode = eth::compileLLLToAsm(src, false); - auto asmcodeopt = eth::compileLLLToAsm(ui->data->toPlainText().toStdString(), true); - m_data = eth::compileLLL(ui->data->toPlainText().toStdString(), true, &errors); + m_data = eth::compileLLL(src, !m_disableCompilerOptimization, &errors); if (errors.size()) { try @@ -1319,7 +1323,14 @@ void Main::on_data_textChanged() } } else - lll = "

Opt

" + QString::fromStdString(asmcodeopt).toHtmlEscaped() + "

Pre

" + QString::fromStdString(asmcode).toHtmlEscaped() + "
"; + { + auto asmcode = eth::compileLLLToAsm(src, false); + lll = "

Pre

" + QString::fromStdString(asmcode).toHtmlEscaped() + "
"; + if (!m_disableCompilerOptimization) { + asmcode = eth::compileLLLToAsm(src, true); + lll = "

Opt

" + QString::fromStdString(asmcode).toHtmlEscaped() + "
" + lll; + } + } } QString errs; if (errors.size()) diff --git a/alethzero/MainWin.h b/alethzero/MainWin.h index f54a64053..98b4a2c03 100644 --- a/alethzero/MainWin.h +++ b/alethzero/MainWin.h @@ -138,6 +138,7 @@ private slots: void on_debugDumpState_triggered(int _add = 1); void on_debugDumpStatePre_triggered(); void on_refresh_triggered(); + void on_disableCompilerOptimization_triggered(); signals: void poll(); @@ -221,6 +222,7 @@ private: QMap m_pcWarp; QList m_history; std::map m_codes; // and pcWarps + bool m_disableCompilerOptimization = false; QNetworkAccessManager m_webCtrl; From e584148ba1a3b21feab665da43d8ad69cd5488ee Mon Sep 17 00:00:00 2001 From: Giacomo Tazzari Date: Mon, 11 Aug 2014 11:06:32 +0200 Subject: [PATCH 072/223] Fixed implementation of EXP opcode (wrong results when exponent >= 2^32) --- libevm/VM.h | 4 ++-- liblll/Assembly.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/libevm/VM.h b/libevm/VM.h index 15f05fd26..f014e2da8 100644 --- a/libevm/VM.h +++ b/libevm/VM.h @@ -241,9 +241,9 @@ template eth::bytesConstRef eth::VM::go(Ext& _ext, OnOpFunc const& _ { require(2); auto base = m_stack.back(); - unsigned expon = (unsigned)m_stack[m_stack.size() - 2]; + auto expon = m_stack[m_stack.size() - 2]; m_stack.pop_back(); - m_stack.back() = boost::multiprecision::pow(base, expon); + m_stack.back() = (u256)boost::multiprecision::powm((bigint)base, (bigint)expon, bigint(2) << 256); break; } case Instruction::NEG: diff --git a/liblll/Assembly.cpp b/liblll/Assembly.cpp index 78552f3c5..550f5c89e 100644 --- a/liblll/Assembly.cpp +++ b/liblll/Assembly.cpp @@ -230,7 +230,7 @@ Assembly& Assembly::optimise(bool _enable) { Instruction::SDIV, [](u256 a, u256 b)->u256{return s2u(u2s(a) / u2s(b));} }, { Instruction::MOD, [](u256 a, u256 b)->u256{return a % b;} }, { Instruction::SMOD, [](u256 a, u256 b)->u256{return s2u(u2s(a) % u2s(b));} }, - { Instruction::EXP, [](u256 a, u256 b)->u256{return boost::multiprecision::pow(a, (unsigned)b);} }, + { Instruction::EXP, [](u256 a, u256 b)->u256{return (u256)boost::multiprecision::powm((bigint)a, (bigint)b, bigint(2) << 256);} }, { Instruction::LT, [](u256 a, u256 b)->u256{return a < b ? 1 : 0;} }, { Instruction::GT, [](u256 a, u256 b)->u256{return a > b ? 1 : 0;} }, { Instruction::SLT, [](u256 a, u256 b)->u256{return u2s(a) < u2s(b) ? 1 : 0;} }, From 6ad2677523486b2a7a3e47ecd151925c60687289 Mon Sep 17 00:00:00 2001 From: Giacomo Tazzari Date: Mon, 11 Aug 2014 11:20:11 +0200 Subject: [PATCH 073/223] Code indentation fix --- liblll/Assembly.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/liblll/Assembly.cpp b/liblll/Assembly.cpp index 550f5c89e..491d3812e 100644 --- a/liblll/Assembly.cpp +++ b/liblll/Assembly.cpp @@ -227,14 +227,14 @@ Assembly& Assembly::optimise(bool _enable) { { Instruction::SUB, [](u256 a, u256 b)->u256{return a - b;} }, { Instruction::DIV, [](u256 a, u256 b)->u256{return a / b;} }, - { Instruction::SDIV, [](u256 a, u256 b)->u256{return s2u(u2s(a) / u2s(b));} }, + { Instruction::SDIV, [](u256 a, u256 b)->u256{return s2u(u2s(a) / u2s(b));} }, { Instruction::MOD, [](u256 a, u256 b)->u256{return a % b;} }, - { Instruction::SMOD, [](u256 a, u256 b)->u256{return s2u(u2s(a) % u2s(b));} }, + { Instruction::SMOD, [](u256 a, u256 b)->u256{return s2u(u2s(a) % u2s(b));} }, { Instruction::EXP, [](u256 a, u256 b)->u256{return (u256)boost::multiprecision::powm((bigint)a, (bigint)b, bigint(2) << 256);} }, { Instruction::LT, [](u256 a, u256 b)->u256{return a < b ? 1 : 0;} }, { Instruction::GT, [](u256 a, u256 b)->u256{return a > b ? 1 : 0;} }, - { Instruction::SLT, [](u256 a, u256 b)->u256{return u2s(a) < u2s(b) ? 1 : 0;} }, - { Instruction::SGT, [](u256 a, u256 b)->u256{return u2s(a) > u2s(b) ? 1 : 0;} }, + { Instruction::SLT, [](u256 a, u256 b)->u256{return u2s(a) < u2s(b) ? 1 : 0;} }, + { Instruction::SGT, [](u256 a, u256 b)->u256{return u2s(a) > u2s(b) ? 1 : 0;} }, { Instruction::EQ, [](u256 a, u256 b)->u256{return a == b ? 1 : 0;} }, }; map> c_associative = From d9b9873318f3c697e73e058d002695ecea85b4a0 Mon Sep 17 00:00:00 2001 From: caktux Date: Mon, 11 Aug 2014 15:53:33 -0400 Subject: [PATCH 074/223] fix unsigned warning in num_gmp.h --- secp256k1/impl/num_gmp.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/secp256k1/impl/num_gmp.h b/secp256k1/impl/num_gmp.h index 75ef9dcf6..067c15180 100644 --- a/secp256k1/impl/num_gmp.h +++ b/secp256k1/impl/num_gmp.h @@ -279,7 +279,7 @@ void static secp256k1_num_set_hex(secp256k1_num_t *r, const char *a, int alen) { 0, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0, 0, 0, 0, 0, 0, 0, 0,0,0,0,0,0,0,0,0,0 }; - char num[257] = {}; + unsigned char num[257] = {}; for (int i=0; i Date: Tue, 12 Aug 2014 09:48:06 +0200 Subject: [PATCH 075/223] Changed the new option "Disable LLL Compiler Optimization" to a more sane "Optimize LLL Compiler" (enabled by default) --- alethzero/Main.ui | 6 +++--- alethzero/MainWin.cpp | 10 ++++++---- alethzero/MainWin.h | 4 ++-- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/alethzero/Main.ui b/alethzero/Main.ui index d3f027ac1..0a727e505 100644 --- a/alethzero/Main.ui +++ b/alethzero/Main.ui @@ -185,7 +185,7 @@ - + @@ -1668,12 +1668,12 @@ font-size: 14pt &Refresh - + true - &Disable LLL Compiler Optimization + &Optimize LLL Compiler diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index 19ce70587..dd12236e4 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -332,9 +332,9 @@ void Main::on_forceMining_triggered() m_client->setForceMining(ui->forceMining->isChecked()); } -void Main::on_disableCompilerOptimization_triggered() +void Main::on_optimizeCompiler_triggered() { - m_disableCompilerOptimization = ui->disableCompilerOptimization->isChecked(); + m_optimizeCompiler = ui->optimizeCompiler->isChecked(); on_data_textChanged(); } @@ -576,6 +576,7 @@ void Main::readSettings(bool _skipGeometry) ui->paranoia->setChecked(s.value("paranoia", false).toBool()); ui->showAll->setChecked(s.value("showAll", false).toBool()); ui->showAllAccounts->setChecked(s.value("showAllAccounts", false).toBool()); + ui->optimizeCompiler->setChecked(m_optimizeCompiler); ui->clientName->setText(s.value("clientName", "").toString()); ui->idealPeers->setValue(s.value("idealPeers", ui->idealPeers->value()).toInt()); ui->port->setValue(s.value("port", ui->port->value()).toInt()); @@ -1308,7 +1309,7 @@ void Main::on_data_textChanged() } else { - m_data = eth::compileLLL(src, !m_disableCompilerOptimization, &errors); + m_data = eth::compileLLL(src, m_optimizeCompiler, &errors); if (errors.size()) { try @@ -1326,7 +1327,8 @@ void Main::on_data_textChanged() { auto asmcode = eth::compileLLLToAsm(src, false); lll = "

Pre

" + QString::fromStdString(asmcode).toHtmlEscaped() + "
"; - if (!m_disableCompilerOptimization) { + if (m_optimizeCompiler) + { asmcode = eth::compileLLLToAsm(src, true); lll = "

Opt

" + QString::fromStdString(asmcode).toHtmlEscaped() + "
" + lll; } diff --git a/alethzero/MainWin.h b/alethzero/MainWin.h index 98b4a2c03..fc12a567e 100644 --- a/alethzero/MainWin.h +++ b/alethzero/MainWin.h @@ -138,7 +138,7 @@ private slots: void on_debugDumpState_triggered(int _add = 1); void on_debugDumpStatePre_triggered(); void on_refresh_triggered(); - void on_disableCompilerOptimization_triggered(); + void on_optimizeCompiler_triggered(); signals: void poll(); @@ -222,7 +222,7 @@ private: QMap m_pcWarp; QList m_history; std::map m_codes; // and pcWarps - bool m_disableCompilerOptimization = false; + bool m_optimizeCompiler = true; QNetworkAccessManager m_webCtrl; From ac37ca74621bf8b60265efc04de080315b317a13 Mon Sep 17 00:00:00 2001 From: Giacomo Tazzari Date: Tue, 12 Aug 2014 09:55:33 +0200 Subject: [PATCH 076/223] Reverting fixes on Serpent because they should be merged on the Serpent repo (reverted from commit 9db294a5b7ca87e866aa9bf811078ff57ceeb48e) --- libserpent/bignum.cpp | 8 ++++---- libserpent/bignum.h | 4 ++-- libserpent/compiler.cpp | 26 +++++++++++++------------- libserpent/rewriter.cpp | 8 ++++---- libserpent/util.cpp | 12 ++++++------ sc/cmdline.cpp | 2 +- 6 files changed, 30 insertions(+), 30 deletions(-) diff --git a/libserpent/bignum.cpp b/libserpent/bignum.cpp index 877808ead..29315b871 100644 --- a/libserpent/bignum.cpp +++ b/libserpent/bignum.cpp @@ -5,9 +5,9 @@ #include "bignum.h" //Integer to string conversion -std::string unsignedToDecimal(unsigned branch) { +std::string intToDecimal(int branch) { if (branch < 10) return nums.substr(branch, 1); - else return unsignedToDecimal(branch / 10) + nums.substr(branch % 10,1); + else return intToDecimal(branch / 10) + nums.substr(branch % 10,1); } //Add two strings representing decimal values @@ -91,8 +91,8 @@ std::string decimalMod(std::string a, std::string b) { } //String to int conversion -unsigned decimalToUnsigned(std::string a) { +int decimalToInt(std::string a) { if (a.size() == 0) return 0; else return (a[a.size() - 1] - '0') - + decimalToUnsigned(a.substr(0,a.size()-1)) * 10; + + decimalToInt(a.substr(0,a.size()-1)) * 10; } diff --git a/libserpent/bignum.h b/libserpent/bignum.h index 6656fdaec..a12929752 100644 --- a/libserpent/bignum.h +++ b/libserpent/bignum.h @@ -11,7 +11,7 @@ const std::string tt255 = "57896044618658097711785492504343953926634992332820282019728792003956564819968" ; -std::string unsignedToDecimal(unsigned branch); +std::string intToDecimal(int branch); std::string decimalAdd(std::string a, std::string b); @@ -25,6 +25,6 @@ std::string decimalMod(std::string a, std::string b); bool decimalGt(std::string a, std::string b, bool eqAllowed=false); -unsigned decimalToUnsigned(std::string a); +int decimalToInt(std::string a); #endif diff --git a/libserpent/compiler.cpp b/libserpent/compiler.cpp index 69292091c..959d2993b 100644 --- a/libserpent/compiler.cpp +++ b/libserpent/compiler.cpp @@ -55,7 +55,7 @@ programData opcodeify(Node node, programAux aux=Aux()) { else if (node.val == "ref" || node.val == "get" || node.val == "set") { std::string varname = node.args[0].val; if (!aux.vars.count(varname)) { - aux.vars[varname] = unsignedToDecimal(aux.vars.size() * 32); + aux.vars[varname] = intToDecimal(aux.vars.size() * 32); } if (varname == "msg.data") aux.calldataUsed = true; // Set variable @@ -165,7 +165,7 @@ programData opcodeify(Node node, programAux aux=Aux()) { nodes.push_back(token("MSIZE", m)); nodes.push_back(token("0", m)); nodes.push_back(token("MSIZE", m)); - nodes.push_back(token(unsignedToDecimal(subs.size() * 32 - 1), m)); + nodes.push_back(token(intToDecimal(subs.size() * 32 - 1), m)); nodes.push_back(token("ADD", m)); nodes.push_back(token("MSTORE8", m)); for (unsigned i = 0; i < subs.size(); i++) { @@ -173,7 +173,7 @@ programData opcodeify(Node node, programAux aux=Aux()) { nodes.push_back(subs[i]); nodes.push_back(token("SWAP", m)); if (i > 0) { - nodes.push_back(token(unsignedToDecimal(i * 32), m)); + nodes.push_back(token(intToDecimal(i * 32), m)); nodes.push_back(token("ADD", m)); } nodes.push_back(token("MSTORE", m)); @@ -201,7 +201,7 @@ Node finalize(programData c) { if (c.aux.allocUsed && c.aux.vars.size() > 0) { Node nodelist[] = { token("0", m), - token(unsignedToDecimal(c.aux.vars.size() * 32 - 1)), + token(intToDecimal(c.aux.vars.size() * 32 - 1)), token("MSTORE8", m) }; bottom.push_back(multiToken(nodelist, 3, m)); @@ -235,7 +235,7 @@ programAux buildDict(Node program, programAux aux, int labelLength) { aux.step += 1 + toByteArr(program.val, m).size(); } else if (program.val[0] == '~') { - aux.vars[program.val.substr(1)] = unsignedToDecimal(aux.step); + aux.vars[program.val.substr(1)] = intToDecimal(aux.step); } else if (program.val[0] == '$') { aux.step += labelLength + 1; @@ -271,7 +271,7 @@ Node substDict(Node program, programAux aux, int labelLength) { std::vector inner; if (program.type == TOKEN) { if (program.val[0] == '$') { - std::string tokStr = "PUSH"+unsignedToDecimal(labelLength); + std::string tokStr = "PUSH"+intToDecimal(labelLength); out.push_back(token(tokStr, m)); int dotLoc = program.val.find('.'); if (dotLoc == -1) { @@ -289,7 +289,7 @@ Node substDict(Node program, programAux aux, int labelLength) { else if (program.val[0] == '~') { } else if (isNumberLike(program)) { inner = toByteArr(program.val, m); - out.push_back(token("PUSH"+unsignedToDecimal(inner.size()))); + out.push_back(token("PUSH"+intToDecimal(inner.size()))); out.push_back(astnode("_", inner, m)); } else return program; @@ -333,10 +333,10 @@ std::string serialize(std::vector codons) { for (unsigned i = 0; i < codons.size(); i++) { int v; if (isNumberLike(codons[i])) { - v = decimalToUnsigned(codons[i].val); + v = decimalToInt(codons[i].val); } else if (codons[i].val.substr(0,4) == "PUSH") { - v = 95 + decimalToUnsigned(codons[i].val.substr(4)); + v = 95 + decimalToInt(codons[i].val.substr(4)); } else { v = opcode(codons[i].val); @@ -355,9 +355,9 @@ std::vector deserialize(std::string ser) { std::string oper = op((int)v); if (oper != "" && backCount <= 0) o.push_back(token(oper)); else if (v >= 96 && v < 128 && backCount <= 0) { - o.push_back(token("PUSH"+unsignedToDecimal(v - 95))); + o.push_back(token("PUSH"+intToDecimal(v - 95))); } - else o.push_back(token(unsignedToDecimal(v))); + else o.push_back(token(intToDecimal(v))); if (v >= 96 && v < 128 && backCount <= 0) { backCount = v - 95; } @@ -392,7 +392,7 @@ std::string encodeDatalist(std::vector vals) { for (unsigned i = 0; i < vals.size(); i++) { std::vector n = toByteArr(strToNumeric(vals[i]), Metadata(), 32); for (unsigned j = 0; j < n.size(); j++) { - int v = decimalToUnsigned(n[j].val); + int v = decimalToInt(n[j].val); o += (char)v; } } @@ -406,7 +406,7 @@ std::vector decodeDatalist(std::string ser) { std::string o = "0"; for (unsigned j = i; j < i + 32; j++) { int vj = (int)(unsigned char)ser[j]; - o = decimalAdd(decimalMul(o, "256"), unsignedToDecimal(vj)); + o = decimalAdd(decimalMul(o, "256"), intToDecimal(vj)); } out.push_back(o); } diff --git a/libserpent/rewriter.cpp b/libserpent/rewriter.cpp index 3d59cb3bd..8b50c9a28 100644 --- a/libserpent/rewriter.cpp +++ b/libserpent/rewriter.cpp @@ -344,7 +344,7 @@ Node subst(Node pattern, Node array_lit_transform(Node node) { std::vector o1; - o1.push_back(token(unsignedToDecimal(node.args.size() * 32), node.metadata)); + o1.push_back(token(intToDecimal(node.args.size() * 32), node.metadata)); std::vector o2; std::string symb = "_temp"+mkUniqueToken()+"_0"; o2.push_back(token(symb, node.metadata)); @@ -357,7 +357,7 @@ Node array_lit_transform(Node node) { o5.push_back(token(symb, node.metadata)); std::vector o6; o6.push_back(astnode("get", o5, node.metadata)); - o6.push_back(token(unsignedToDecimal(i * 32), node.metadata)); + o6.push_back(token(intToDecimal(i * 32), node.metadata)); std::vector o7; o7.push_back(astnode("add", o6)); o7.push_back(node.args[i]); @@ -474,10 +474,10 @@ Node validate(Node inp) { int i = 0; while(valid[i][0] != "---END---") { if (inp.val == valid[i][0]) { - if (decimalGt(valid[i][1], unsignedToDecimal(inp.args.size()))) { + if (decimalGt(valid[i][1], intToDecimal(inp.args.size()))) { err("Too few arguments for "+inp.val, inp.metadata); } - if (decimalGt(unsignedToDecimal(inp.args.size()), valid[i][2])) { + if (decimalGt(intToDecimal(inp.args.size()), valid[i][2])) { err("Too many arguments for "+inp.val, inp.metadata); } } diff --git a/libserpent/util.cpp b/libserpent/util.cpp index efef91425..6ca39de9d 100644 --- a/libserpent/util.cpp +++ b/libserpent/util.cpp @@ -60,8 +60,8 @@ std::string printAST(Node ast, bool printMetadata) { std::string o = "("; if (printMetadata) { o += ast.metadata.file + " "; - o += unsignedToDecimal(ast.metadata.ln) + " "; - o += unsignedToDecimal(ast.metadata.ch) + ": "; + o += intToDecimal(ast.metadata.ln) + " "; + o += intToDecimal(ast.metadata.ch) + ": "; } o += ast.val; std::vector subs; @@ -132,14 +132,14 @@ std::string strToNumeric(std::string inp) { else if ((inp[0] == '"' && inp[inp.length()-1] == '"') || (inp[0] == '\'' && inp[inp.length()-1] == '\'')) { for (unsigned i = 1; i < inp.length() - 1; i++) { - o = decimalAdd(decimalMul(o,"256"), unsignedToDecimal((unsigned char)inp[i])); + o = decimalAdd(decimalMul(o,"256"), intToDecimal(inp[i])); } } else if (inp.substr(0,2) == "0x") { for (unsigned i = 2; i < inp.length(); i++) { int dig = std::string("0123456789abcdef").find(inp[i]); if (dig < 0) return ""; - o = decimalAdd(decimalMul(o,"16"), unsignedToDecimal(dig)); + o = decimalAdd(decimalMul(o,"16"), intToDecimal(dig)); } } else { @@ -188,7 +188,7 @@ int counter = 0; //Makes a unique token std::string mkUniqueToken() { counter++; - return unsignedToDecimal(counter); + return intToDecimal(counter); } //Does a file exist? http://stackoverflow.com/questions/12774207 @@ -217,7 +217,7 @@ std::string get_file_contents(std::string filename) //Report error void err(std::string errtext, Metadata met) { std::string err = "Error (file \"" + met.file + "\", line " + - unsignedToDecimal(met.ln) + ", char " + unsignedToDecimal(met.ch) + + intToDecimal(met.ln) + ", char " + intToDecimal(met.ch) + "): " + errtext; std::cerr << err << "\n"; throw(err); diff --git a/sc/cmdline.cpp b/sc/cmdline.cpp index 4b2eee160..232cbfeec 100644 --- a/sc/cmdline.cpp +++ b/sc/cmdline.cpp @@ -96,7 +96,7 @@ int main(int argv, char** argc) { else if (command == "biject") { if (argv == 3) std::cerr << "Not enough arguments for biject\n"; - int pos = decimalToUnsigned(secondInput); + int pos = decimalToInt(secondInput); std::vector n = prettyCompile(input); if (pos >= (int)n.size()) std::cerr << "Code position too high\n"; From a761484c2c77bfd3d26e3355a88ac11238caae2c Mon Sep 17 00:00:00 2001 From: Giacomo Tazzari Date: Tue, 12 Aug 2014 12:53:50 +0200 Subject: [PATCH 077/223] Renamed the new option to enableOptimizer and created its appropriate setting --- alethzero/Main.ui | 6 +++--- alethzero/MainWin.cpp | 12 +++++++----- alethzero/MainWin.h | 4 ++-- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/alethzero/Main.ui b/alethzero/Main.ui index 0a727e505..f5b52c309 100644 --- a/alethzero/Main.ui +++ b/alethzero/Main.ui @@ -185,7 +185,7 @@ - + @@ -1668,12 +1668,12 @@ font-size: 14pt &Refresh
- + true - &Optimize LLL Compiler + &Enable LLL &Optimizer diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index dd12236e4..a4fc22a43 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -332,9 +332,9 @@ void Main::on_forceMining_triggered() m_client->setForceMining(ui->forceMining->isChecked()); } -void Main::on_optimizeCompiler_triggered() +void Main::on_enableOptimizer_triggered() { - m_optimizeCompiler = ui->optimizeCompiler->isChecked(); + m_enableOptimizer = ui->enableOptimizer->isChecked(); on_data_textChanged(); } @@ -529,6 +529,7 @@ void Main::writeSettings() s.setValue("paranoia", ui->paranoia->isChecked()); s.setValue("showAll", ui->showAll->isChecked()); s.setValue("showAllAccounts", ui->showAllAccounts->isChecked()); + s.setValue("enableOptimizer", m_enableOptimizer); s.setValue("clientName", ui->clientName->text()); s.setValue("idealPeers", ui->idealPeers->value()); s.setValue("port", ui->port->value()); @@ -576,7 +577,8 @@ void Main::readSettings(bool _skipGeometry) ui->paranoia->setChecked(s.value("paranoia", false).toBool()); ui->showAll->setChecked(s.value("showAll", false).toBool()); ui->showAllAccounts->setChecked(s.value("showAllAccounts", false).toBool()); - ui->optimizeCompiler->setChecked(m_optimizeCompiler); + m_enableOptimizer = s.value("enableOptimizer", true).toBool(); + ui->enableOptimizer->setChecked(m_enableOptimizer); ui->clientName->setText(s.value("clientName", "").toString()); ui->idealPeers->setValue(s.value("idealPeers", ui->idealPeers->value()).toInt()); ui->port->setValue(s.value("port", ui->port->value()).toInt()); @@ -1309,7 +1311,7 @@ void Main::on_data_textChanged() } else { - m_data = eth::compileLLL(src, m_optimizeCompiler, &errors); + m_data = eth::compileLLL(src, m_enableOptimizer, &errors); if (errors.size()) { try @@ -1327,7 +1329,7 @@ void Main::on_data_textChanged() { auto asmcode = eth::compileLLLToAsm(src, false); lll = "

Pre

" + QString::fromStdString(asmcode).toHtmlEscaped() + "
"; - if (m_optimizeCompiler) + if (m_enableOptimizer) { asmcode = eth::compileLLLToAsm(src, true); lll = "

Opt

" + QString::fromStdString(asmcode).toHtmlEscaped() + "
" + lll; diff --git a/alethzero/MainWin.h b/alethzero/MainWin.h index fc12a567e..46cb1843a 100644 --- a/alethzero/MainWin.h +++ b/alethzero/MainWin.h @@ -138,7 +138,7 @@ private slots: void on_debugDumpState_triggered(int _add = 1); void on_debugDumpStatePre_triggered(); void on_refresh_triggered(); - void on_optimizeCompiler_triggered(); + void on_enableOptimizer_triggered(); signals: void poll(); @@ -222,7 +222,7 @@ private: QMap m_pcWarp; QList m_history; std::map m_codes; // and pcWarps - bool m_optimizeCompiler = true; + bool m_enableOptimizer = true; QNetworkAccessManager m_webCtrl; From 10087bf9332a86e7ef4566f93cf6d0a1da1bf99f Mon Sep 17 00:00:00 2001 From: Giacomo Tazzari Date: Tue, 12 Aug 2014 17:21:05 +0200 Subject: [PATCH 078/223] Fixed peer hanging when receiving a bad synchronisation token --- libethereum/PeerSession.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libethereum/PeerSession.cpp b/libethereum/PeerSession.cpp index 281e834f0..e9703a830 100644 --- a/libethereum/PeerSession.cpp +++ b/libethereum/PeerSession.cpp @@ -564,7 +564,11 @@ void PeerSession::doRead() while (m_incoming.size() > 8) { if (m_incoming[0] != 0x22 || m_incoming[1] != 0x40 || m_incoming[2] != 0x08 || m_incoming[3] != 0x91) - doRead(); + { + cwarn << "INVALID SYNCHRONISATION TOKEN; expected = 22400891; received = " << toHex(bytesConstRef(m_incoming.data(), 4)); + disconnect(BadProtocol); + return; + } else { uint32_t len = fromBigEndian(bytesConstRef(m_incoming.data() + 4, 4)); From 6ee6e7921aa69b7c56fc15e90cc292fb8befc236 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 12 Aug 2014 20:28:35 +0200 Subject: [PATCH 079/223] Add private blockchain feature. --- alethzero/Main.ui | 15 +++++++++++++++ alethzero/MainWin.cpp | 25 ++++++++++++++++++++++--- alethzero/MainWin.h | 2 ++ libethereum/Client.cpp | 4 ++-- libethereum/Client.h | 2 +- libethereum/PeerServer.cpp | 6 +++--- libethereum/PeerServer.h | 10 +++++----- libethereum/PeerSession.cpp | 4 ++-- libethereum/PeerSession.h | 6 +++--- 9 files changed, 55 insertions(+), 19 deletions(-) diff --git a/alethzero/Main.ui b/alethzero/Main.ui index a01ac7856..5ef2b28b3 100644 --- a/alethzero/Main.ui +++ b/alethzero/Main.ui @@ -185,6 +185,8 @@ + + @@ -1664,6 +1666,19 @@ font-size: 14pt &Refresh
+ + + true + + + &Use Private Chain... + + + + + &Join Private Chain + + diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index 6c18d39bd..30b02c626 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -527,6 +527,7 @@ void Main::writeSettings() s.setValue("idealPeers", ui->idealPeers->value()); s.setValue("port", ui->port->value()); s.setValue("url", ui->urlEdit->text()); + s.setValue("privateChain", m_privateChain); bytes d = m_client->savePeers(); if (d.size()) @@ -573,7 +574,10 @@ void Main::readSettings(bool _skipGeometry) ui->clientName->setText(s.value("clientName", "").toString()); ui->idealPeers->setValue(s.value("idealPeers", ui->idealPeers->value()).toInt()); ui->port->setValue(s.value("port", ui->port->value()).toInt()); - ui->nameReg->setText(s.value("NameReg", "").toString()); + ui->nameReg->setText(s.value("nameReg", "").toString()); + m_privateChain = s.value("privateChain", "").toString(); + ui->usePrivate->setChecked(m_privateChain.size()); + ui->urlEdit->setText(s.value("url", "about:blank").toString()); //http://gavwood.com/gavcoin.html on_urlEdit_returnPressed(); } @@ -607,6 +611,21 @@ void Main::on_exportKey_triggered() } } +void Main::on_usePrivate_triggered() +{ + if (ui->usePrivate->isChecked()) + { + m_privateChain = QInputDialog::getText(this, "Enter Name", "Enter the name of your private chain", QLineEdit::Normal, QString("NewChain-%1").arg(time(0))); + if (m_privateChain.isEmpty()) + ui->usePrivate->setChecked(false); + } + else + { + m_privateChain.clear(); + } + on_killBlockchain_triggered(); +} + void Main::on_urlEdit_returnPressed() { QString s = ui->urlEdit->text(); @@ -779,7 +798,7 @@ void Main::refreshBlockCount() cwatch << "refreshBlockCount()"; auto d = m_client->blockChain().details(); auto diff = BlockInfo(m_client->blockChain().block()).difficulty; - ui->blockCount->setText(QString("#%1 @%3 T%2 N%4 D%5").arg(d.number).arg(toLog2(d.totalDifficulty)).arg(toLog2(diff)).arg(eth::c_protocolVersion).arg(eth::c_databaseVersion)); + ui->blockCount->setText(QString("%6 #%1 @%3 T%2 N%4 D%5").arg(d.number).arg(toLog2(d.totalDifficulty)).arg(toLog2(diff)).arg(eth::c_protocolVersion).arg(eth::c_databaseVersion).arg(m_privateChain.size() ? "[" + m_privateChain + "] " : "testnet")); } static bool blockMatch(string const& _f, eth::BlockDetails const& _b, h256 _h, BlockChain const& _bc) @@ -1475,7 +1494,7 @@ void Main::on_net_triggered() m_client->setClientVersion(n); if (ui->net->isChecked()) { - m_client->startNetwork(ui->port->value(), string(), 0, NodeMode::Full, ui->idealPeers->value(), ui->forceAddress->text().toStdString(), ui->upnp->isChecked()); + m_client->startNetwork(ui->port->value(), string(), 0, NodeMode::Full, ui->idealPeers->value(), ui->forceAddress->text().toStdString(), ui->upnp->isChecked(), m_privateChain.size() ? sha3(m_privateChain.toStdString()) : 0); if (m_peers.size() && ui->usePast->isChecked()) m_client->restorePeers(bytesConstRef((byte*)m_peers.data(), m_peers.size())); } diff --git a/alethzero/MainWin.h b/alethzero/MainWin.h index f54a64053..8272b8506 100644 --- a/alethzero/MainWin.h +++ b/alethzero/MainWin.h @@ -138,6 +138,7 @@ private slots: void on_debugDumpState_triggered(int _add = 1); void on_debugDumpStatePre_triggered(); void on_refresh_triggered(); + void on_usePrivate_triggered(); signals: void poll(); @@ -206,6 +207,7 @@ private: QByteArray m_peers; QStringList m_servers; QList m_myKeys; + QString m_privateChain = 0; bool m_keysChanged = false; eth::bytes m_data; eth::Address m_nameReg; diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index 673973fbd..86c26d1db 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -197,7 +197,7 @@ void Client::noteChanged(h256Set const& _filters) } } -void Client::startNetwork(unsigned short _listenPort, std::string const& _seedHost, unsigned short _port, NodeMode _mode, unsigned _peers, string const& _publicIP, bool _upnp) +void Client::startNetwork(unsigned short _listenPort, std::string const& _seedHost, unsigned short _port, NodeMode _mode, unsigned _peers, string const& _publicIP, bool _upnp, u256 _networkId) { static const char* c_threadName = "net"; @@ -220,7 +220,7 @@ void Client::startNetwork(unsigned short _listenPort, std::string const& _seedHo try { - m_net.reset(new PeerServer(m_clientVersion, m_bc, 0, _listenPort, _mode, _publicIP, _upnp)); + m_net.reset(new PeerServer(m_clientVersion, m_bc, _networkId, _listenPort, _mode, _publicIP, _upnp)); } catch (std::exception const&) { diff --git a/libethereum/Client.h b/libethereum/Client.h index f20b3538f..d765494eb 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -263,7 +263,7 @@ public: void setIdealPeerCount(size_t _n) const; /// Start the network subsystem. - void startNetwork(unsigned short _listenPort = 30303, std::string const& _remoteHost = std::string(), unsigned short _remotePort = 30303, NodeMode _mode = NodeMode::Full, unsigned _peers = 5, std::string const& _publicIP = std::string(), bool _upnp = true); + void startNetwork(unsigned short _listenPort = 30303, std::string const& _remoteHost = std::string(), unsigned short _remotePort = 30303, NodeMode _mode = NodeMode::Full, unsigned _peers = 5, std::string const& _publicIP = std::string(), bool _upnp = true, u256 _networkId = 0); /// Connect to a particular peer. void connect(std::string const& _seedHost, unsigned short _port = 30303); /// Stop the network subsystem. diff --git a/libethereum/PeerServer.cpp b/libethereum/PeerServer.cpp index ccc044f93..a4330497d 100644 --- a/libethereum/PeerServer.cpp +++ b/libethereum/PeerServer.cpp @@ -55,7 +55,7 @@ static const set c_rejectAddresses = { {bi::address_v6::from_string("::")} }; -PeerServer::PeerServer(std::string const& _clientVersion, BlockChain const& _ch, unsigned int _networkId, unsigned short _port, NodeMode _m, string const& _publicAddress, bool _upnp): +PeerServer::PeerServer(std::string const& _clientVersion, BlockChain const& _ch, u256 _networkId, unsigned short _port, NodeMode _m, string const& _publicAddress, bool _upnp): m_clientVersion(_clientVersion), m_mode(_m), m_listenPort(_port), @@ -71,7 +71,7 @@ PeerServer::PeerServer(std::string const& _clientVersion, BlockChain const& _ch, clog(NetNote) << "Id:" << toHex(m_key.address().ref().cropped(0, 4)) << "Mode: " << (_m == NodeMode::PeerServer ? "PeerServer" : "Full"); } -PeerServer::PeerServer(std::string const& _clientVersion, BlockChain const& _ch, unsigned int _networkId, NodeMode _m, string const& _publicAddress, bool _upnp): +PeerServer::PeerServer(std::string const& _clientVersion, BlockChain const& _ch, u256 _networkId, NodeMode _m, string const& _publicAddress, bool _upnp): m_clientVersion(_clientVersion), m_mode(_m), m_listenPort(0), @@ -90,7 +90,7 @@ PeerServer::PeerServer(std::string const& _clientVersion, BlockChain const& _ch, clog(NetNote) << "Id:" << toHex(m_key.address().ref().cropped(0, 4)) << "Mode: " << (m_mode == NodeMode::PeerServer ? "PeerServer" : "Full"); } -PeerServer::PeerServer(std::string const& _clientVersion, BlockChain const& _ch, unsigned int _networkId, NodeMode _m): +PeerServer::PeerServer(std::string const& _clientVersion, BlockChain const& _ch, u256 _networkId, NodeMode _m): m_clientVersion(_clientVersion), m_mode(_m), m_listenPort(0), diff --git a/libethereum/PeerServer.h b/libethereum/PeerServer.h index 8439cce79..d3c2da65c 100644 --- a/libethereum/PeerServer.h +++ b/libethereum/PeerServer.h @@ -50,11 +50,11 @@ class PeerServer public: /// Start server, listening for connections on the given port. - PeerServer(std::string const& _clientVersion, BlockChain const& _ch, unsigned int _networkId, unsigned short _port, NodeMode _m = NodeMode::Full, std::string const& _publicAddress = std::string(), bool _upnp = true); + PeerServer(std::string const& _clientVersion, BlockChain const& _ch, u256 _networkId, unsigned short _port, NodeMode _m = NodeMode::Full, std::string const& _publicAddress = std::string(), bool _upnp = true); /// Start server, listening for connections on a system-assigned port. - PeerServer(std::string const& _clientVersion, BlockChain const& _ch, unsigned int _networkId, NodeMode _m = NodeMode::Full, std::string const& _publicAddress = std::string(), bool _upnp = true); + PeerServer(std::string const& _clientVersion, BlockChain const& _ch, u256 _networkId, NodeMode _m = NodeMode::Full, std::string const& _publicAddress = std::string(), bool _upnp = true); /// Start server, but don't listen. - PeerServer(std::string const& _clientVersion, BlockChain const& _ch, unsigned int _networkId, NodeMode _m = NodeMode::Full); + PeerServer(std::string const& _clientVersion, BlockChain const& _ch, u256 _networkId, NodeMode _m = NodeMode::Full); /// Will block on network process events. ~PeerServer(); @@ -63,7 +63,7 @@ public: void disconnectPeers(); static unsigned protocolVersion(); - unsigned networkId() { return m_networkId; } + u256 networkId() { return m_networkId; } /// Connect to a peer explicitly. void connect(std::string const& _addr, unsigned short _port = 30303) noexcept; @@ -137,7 +137,7 @@ private: bi::tcp::endpoint m_public; KeyPair m_key; - unsigned m_networkId; + u256 m_networkId; mutable std::mutex x_peers; std::map> m_peers; diff --git a/libethereum/PeerSession.cpp b/libethereum/PeerSession.cpp index 281e834f0..3a61430ed 100644 --- a/libethereum/PeerSession.cpp +++ b/libethereum/PeerSession.cpp @@ -35,7 +35,7 @@ static const eth::uint c_maxHashes = 4096; ///< Maximum number of hashes GetCha static const eth::uint c_maxBlocks = 2048; ///< Maximum number of blocks Blocks will ever send. static const eth::uint c_maxBlocksAsk = 512; ///< Maximum number of blocks we ask to receive in Blocks (when using GetChain). -PeerSession::PeerSession(PeerServer* _s, bi::tcp::socket _socket, uint _rNId, bi::address _peerAddress, unsigned short _peerPort): +PeerSession::PeerSession(PeerServer* _s, bi::tcp::socket _socket, u256 _rNId, bi::address _peerAddress, unsigned short _peerPort): m_server(_s), m_socket(std::move(_socket)), m_reqNetworkId(_rNId), @@ -78,7 +78,7 @@ bool PeerSession::interpret(RLP const& _r) case HelloPacket: { m_protocolVersion = _r[1].toInt(); - m_networkId = _r[2].toInt(); + m_networkId = _r[2].toInt(); auto clientVersion = _r[3].toString(); m_caps = _r[4].toInt(); m_listenPort = _r[5].toInt(); diff --git a/libethereum/PeerSession.h b/libethereum/PeerSession.h index 6c7e56d7c..562d27e50 100644 --- a/libethereum/PeerSession.h +++ b/libethereum/PeerSession.h @@ -38,7 +38,7 @@ class PeerSession: public std::enable_shared_from_this friend class PeerServer; public: - PeerSession(PeerServer* _server, bi::tcp::socket _socket, uint _rNId, bi::address _peerAddress, unsigned short _peerPort = 0); + PeerSession(PeerServer* _server, bi::tcp::socket _socket, u256 _rNId, bi::address _peerAddress, unsigned short _peerPort = 0); ~PeerSession(); void start(); @@ -79,8 +79,8 @@ private: bytes m_incoming; uint m_protocolVersion; - uint m_networkId; - uint m_reqNetworkId; + u256 m_networkId; + u256 m_reqNetworkId; unsigned short m_listenPort; ///< Port that the remote client is listening on for connections. Useful for giving to peers. uint m_caps; From bf28ebea0614da7f5c4547de168556ec185a3e7a Mon Sep 17 00:00:00 2001 From: caktux Date: Tue, 12 Aug 2014 19:35:32 -0400 Subject: [PATCH 080/223] add missing third.icns and plist for OSX --- third/EthereumMacOSXBundleInfo.plist.in | 38 ++++++++++++++++++++++++ third/third.icns | Bin 0 -> 514931 bytes 2 files changed, 38 insertions(+) create mode 100644 third/EthereumMacOSXBundleInfo.plist.in create mode 100644 third/third.icns diff --git a/third/EthereumMacOSXBundleInfo.plist.in b/third/EthereumMacOSXBundleInfo.plist.in new file mode 100644 index 000000000..684ad7908 --- /dev/null +++ b/third/EthereumMacOSXBundleInfo.plist.in @@ -0,0 +1,38 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + CFBundleGetInfoString + ${MACOSX_BUNDLE_INFO_STRING} + CFBundleIconFile + ${MACOSX_BUNDLE_ICON_FILE} + CFBundleIdentifier + ${MACOSX_BUNDLE_GUI_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleLongVersionString + ${MACOSX_BUNDLE_LONG_VERSION_STRING} + CFBundleName + ${MACOSX_BUNDLE_BUNDLE_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + ${MACOSX_BUNDLE_SHORT_VERSION_STRING} + CFBundleSignature + ???? + CFBundleVersion + ${MACOSX_BUNDLE_BUNDLE_VERSION} + CSResourcesFileMapped + + LSRequiresCarbon + + NSHumanReadableCopyright + ${MACOSX_BUNDLE_COPYRIGHT} + NSHighResolutionCapable + + + diff --git a/third/third.icns b/third/third.icns new file mode 100644 index 0000000000000000000000000000000000000000..105afb3d780caab0272375b08abce9146b9b414f GIT binary patch literal 514931 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
  • c!Gb@F^dIQ_soxZhW`6*AU(qe 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ⅇ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%0A2^YOyL5l!G~;n|BSZwepkiG`)?}xG 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 Date: Wed, 13 Aug 2014 11:22:58 +0200 Subject: [PATCH 081/223] JS API improvments. --- alethzero/MainWin.cpp | 2 +- alethzero/MainWin.h | 2 +- libqethereum/QEthereum.cpp | 30 +++++++++++++---- libqethereum/QEthereum.h | 9 +++-- stdserv.js | 68 ++++++++++++++++++-------------------- 5 files changed, 64 insertions(+), 47 deletions(-) diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index 14d618724..915b4a082 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -396,7 +396,7 @@ void Main::eval(QString const& _js) { if (_js.trimmed().isEmpty()) return; - QVariant ev = ui->webView->page()->currentFrame()->evaluateJavaScript("___RET=(" + _js + ")"); + QVariant ev = ui->webView->page()->currentFrame()->evaluateJavaScript((_js.startsWith("{") || _js.startsWith("if ") || _js.startsWith("if(")) ? _js : ("___RET=(" + _js + ")")); QVariant jsonEv = ui->webView->page()->currentFrame()->evaluateJavaScript("JSON.stringify(___RET)"); QString s; if (ev.isNull()) diff --git a/alethzero/MainWin.h b/alethzero/MainWin.h index 298857cc9..da77fb8e1 100644 --- a/alethzero/MainWin.h +++ b/alethzero/MainWin.h @@ -208,7 +208,7 @@ private: QByteArray m_peers; QStringList m_servers; QList m_myKeys; - QString m_privateChain = 0; + QString m_privateChain; bool m_keysChanged = false; eth::bytes m_data; eth::Address m_nameReg; diff --git a/libqethereum/QEthereum.cpp b/libqethereum/QEthereum.cpp index 9dd866a9a..4d77f189a 100644 --- a/libqethereum/QEthereum.cpp +++ b/libqethereum/QEthereum.cpp @@ -38,6 +38,7 @@ using eth::simpleDebugOut; using eth::toLog2; using eth::toString; using eth::units; +using eth::operator +; // vars using eth::g_logPost; @@ -126,12 +127,22 @@ Client* QEthereum::client() const QString QEthereum::lll(QString _s) const { - return asQString(eth::compileLLL(_s.toStdString())); + return toQJS(eth::compileLLL(_s.toStdString())); } QString QEthereum::sha3(QString _s) const { - return toQJS(eth::sha3(asBytes(_s))); + return toQJS(eth::sha3(toBytes(_s))); +} + +QString QEthereum::sha3(QString _s1, QString _s2) const +{ + return toQJS(eth::sha3(toBytes(_s1) + toBytes(_s2))); +} + +QString QEthereum::sha3(QString _s1, QString _s2, QString _s3) const +{ + return toQJS(eth::sha3(toBytes(_s1) + toBytes(_s2) + toBytes(_s3))); } QString QEthereum::sha3old(QString _s) const @@ -315,13 +326,18 @@ static TransactionSkeleton toTransaction(QString _json) ret.gas = toU256(f["gas"].toString()); if (f.contains("gasPrice")) ret.gasPrice = toU256(f["gasPrice"].toString()); - if (f.contains("data")) + if (f.contains("data") || f.contains("code") || f.contains("dataclose")) { if (f["data"].isString()) ret.data = toBytes(f["data"].toString()); + else if (f["code"].isString()) + ret.data = toBytes(f["code"].toString()); else if (f["data"].isArray()) for (auto i: f["data"].toArray()) eth::operator +=(ret.data, toBytes(padded(i.toString(), 32))); + else if (f["code"].isArray()) + for (auto i: f["code"].toArray()) + eth::operator +=(ret.data, toBytes(padded(i.toString(), 32))); else if (f["dataclose"].isArray()) for (auto i: f["dataclose"].toArray()) eth::operator +=(ret.data, toBytes(toBinary(i.toString()))); @@ -411,10 +427,11 @@ void QEthereum::doTransact(QString _secret, QString _amount, QString _dest, QStr client()->flushTransactions(); } -void QEthereum::doTransact(QString _json) +QString QEthereum::doTransact(QString _json) { + QString ret; if (!m_client) - return; + return ret; TransactionSkeleton t = toTransaction(_json); if (!t.from && m_accounts.size()) { @@ -431,8 +448,9 @@ void QEthereum::doTransact(QString _json) if (t.to) client()->transact(t.from, t.value, t.to, t.data, t.gas, t.gasPrice); else - client()->transact(t.from, t.value, t.data, t.gas, t.gasPrice); + ret = toQJS(client()->transact(t.from, t.value, t.data, t.gas, t.gasPrice)); client()->flushTransactions(); + return ret; } QString QEthereum::doCall(QString _json) diff --git a/libqethereum/QEthereum.h b/libqethereum/QEthereum.h index c4b10042a..f57f40bf6 100644 --- a/libqethereum/QEthereum.h +++ b/libqethereum/QEthereum.h @@ -61,10 +61,11 @@ inline eth::u256 toU256(QString const& _s) { return toInt<32>(_s); } template QString toQJS(eth::FixedHash const& _h) { return QString::fromStdString("0x" + toHex(_h.ref())); } template QString toQJS(boost::multiprecision::number> const& _n) { return QString::fromStdString("0x" + eth::toHex(eth::toCompactBigEndian(_n))); } +inline QString toQJS(eth::bytes const& _n) { return "0x" + QString::fromStdString(eth::toHex(_n)); } inline QString toBinary(QString const& _s) { - return asQString(toBytes(_s)); + return unpadded(asQString(toBytes(_s))); } inline QString toDecimal(QString const& _s) @@ -116,6 +117,8 @@ public: Q_INVOKABLE QString lll(QString _s) const; Q_INVOKABLE QString sha3(QString _s) const; + Q_INVOKABLE QString sha3(QString _s1, QString _s2) const; + Q_INVOKABLE QString sha3(QString _s1, QString _s2, QString _s3) const; Q_INVOKABLE QString sha3old(QString _s) const; Q_INVOKABLE QString offset(QString _s, int _offset) const; @@ -145,7 +148,7 @@ public: Q_INVOKABLE QString doCreate(QString _secret, QString _amount, QString _init, QString _gas, QString _gasPrice); Q_INVOKABLE void doTransact(QString _secret, QString _amount, QString _dest, QString _data, QString _gas, QString _gasPrice); - Q_INVOKABLE void doTransact(QString _json); + Q_INVOKABLE QString doTransact(QString _json); Q_INVOKABLE QString doCall(QString _json); Q_INVOKABLE unsigned newWatch(QString _json); @@ -211,7 +214,7 @@ private: frame->evaluateJavaScript("eth.watchChain = function() { env.warn('THIS CALL IS DEPRECATED. USE eth.watch('chain') INSTEAD.'); return eth.makeWatch('chain') }"); \ frame->evaluateJavaScript("eth.watchPending = function() { env.warn('THIS CALL IS DEPRECATED. USE eth.watch('pending') INSTEAD.'); return eth.makeWatch('pending') }"); \ frame->evaluateJavaScript("eth.create = function(s, v, c, g, p, f) { env.warn('THIS CALL IS DEPRECATED. USE eth.transact INSTEAD.'); var v = eth.doCreate(s, v, c, g, p); if (f) f(v) }"); \ - frame->evaluateJavaScript("eth.transact = function(a_s, f_v, t, d, g, p, f) { if (t == null) { eth.doTransact(JSON.stringify(a_s)); if (f_v) f_v(); } else { env.warn('THIS FORM OF THIS CALL IS DEPRECATED.'); eth.doTransact(a_s, f_v, t, d, g, p); if (f) f() } }"); \ + frame->evaluateJavaScript("eth.transact = function(a_s, f_v, t, d, g, p, f) { if (t == null) { var r = eth.doTransact(JSON.stringify(a_s)); if (f_v) f_v(r); } else { env.warn('THIS FORM OF THIS CALL IS DEPRECATED.'); eth.doTransact(a_s, f_v, t, d, g, p); if (f) f() } }"); \ frame->evaluateJavaScript("eth.call = function(a, f) { var ret = eth.doCallJson(JSON.stringify(a)); if (f) f(ret); return ret; }"); \ frame->evaluateJavaScript("eth.messages = function(a) { return JSON.parse(eth.getMessages(JSON.stringify(a))); }"); \ frame->evaluateJavaScript("eth.transactions = function(a) { env.warn('THIS CALL IS DEPRECATED. USE eth.messages INSTEAD.'); return JSON.parse(eth.getMessages(JSON.stringify(a))); }"); \ diff --git a/stdserv.js b/stdserv.js index d0eefc709..be9b90b79 100644 --- a/stdserv.js +++ b/stdserv.js @@ -12,9 +12,9 @@ var configCode = eth.lll(" }) } ") -env.note('Config code: ' + configCode.unbin()) -var config = "0x9ef0f0d81e040012600b0c1abdef7c48f720f88a"; -eth.create(eth.key, '0', configCode, 10000, eth.gasPrice, function(a) { config = a; }) +env.note('Config code: ' + configCode) +var config; +eth.transact({ 'code': configCode }, function(a) { config = a; }); env.note('Config at address ' + config) @@ -22,8 +22,8 @@ var nameRegCode = eth.lll(" { [[(address)]] 'NameReg [['NameReg]] (address) - [[" + config + "]] 'Config - [['Config]] " + config + " + [[config]] 'Config + [['Config]] config [[69]] (caller) (returnlll { (when (= $0 'register) { @@ -43,32 +43,30 @@ var nameRegCode = eth.lll(" }) } "); -env.note('NameReg code: ' + nameRegCode.unbin()) +env.note('NameReg code: ' + nameRegCode) -var nameReg = "0x3face8f2b3ef580265f0f67a57ce0fb78b135613"; -env.note('Create NameReg...') -eth.create(eth.key, '0', nameRegCode, 10000, eth.gasPrice, function(a) { nameReg = a; }) +var nameReg; -env.note('NameReg at address ' + nameReg) +env.note('Create NameReg...') +eth.transact({ 'code': nameRegCode }, function(a) { nameReg = a; }); env.note('Register NameReg...') -eth.transact(eth.key, '0', config, "0".pad(32) + nameReg.pad(32), 10000, eth.gasPrice); +eth.transact({ 'to': config, 'data': ['0', nameReg] }); var dnsRegCode = '0x60006000546000600053602001546000600053604001546020604060206020600073661005d2720d855f1d9976f88bb10c1a3398c77f6103e8f17f7265676973746572000000000000000000000000000000000000000000000000600053606001600060200201547f446e735265670000000000000000000000000000000000000000000000000000600053606001600160200201546000600060006000604060606000600053604001536103e8f1327f6f776e65720000000000000000000000000000000000000000000000000000005761011663000000e46000396101166000f20060006000547f72656769737465720000000000000000000000000000000000000000000000006000602002350e0f630000006d596000600160200235560e0f630000006c59600032560e0f0f6300000057596000325657600260200235600160200235576001602002353257007f64657265676973746572000000000000000000000000000000000000000000006000602002350e0f63000000b95960016020023532560e0f63000000b959600032576000600160200235577f6b696c6c000000000000000000000000000000000000000000000000000000006000602002350e0f630000011559327f6f776e6572000000000000000000000000000000000000000000000000000000560e0f63000001155932ff00'; var dnsReg; env.note('Create DnsReg...') -eth.create(eth.key, '0', dnsRegCode, 10000, eth.gasPrice, function(a) { dnsReg = a; }) +eth.transact({ 'code': dnsRegCode }, function(a) { dnsReg = a; }); env.note('DnsReg at address ' + dnsReg) env.note('Register DnsReg...') -eth.transact(eth.key, '0', config, "4".pad(32) + dnsReg.pad(32), 10000, eth.gasPrice); +eth.transact({ 'to': config, 'data': ['4', dnsReg] }); var coinRegCode = eth.lll(" { -[0]'register [32]'CoinReg -(msg allgas " + nameReg + " 0 0 64) +(regname 'CoinReg) (returnlll { (def 'name $0) (def 'denom $32) @@ -85,12 +83,10 @@ var coinRegCode = eth.lll(" var coinReg; env.note('Create CoinReg...') -eth.create(eth.key, '0', coinRegCode, 10000, eth.gasPrice, function(a) { coinReg = a; }) - -env.note('CoinReg at address ' + coinReg) +eth.transact({ 'code': coinRegCode }, function(a) { coinReg = a; }); env.note('Register CoinReg...') -eth.transact(eth.key, '0', config, "1".pad(32) + coinReg.pad(32), 10000, eth.gasPrice); +eth.transact({ 'to': config, 'data': ['1', coinReg] }); var gavCoinCode = eth.lll(" { @@ -98,10 +94,8 @@ var gavCoinCode = eth.lll(" [[ 0x69 ]] (caller) [[ 0x42 ]] (number) -[0]'register [32]'GavCoin -(msg allgas " + nameReg + " 0 0 64) -[0]'GAV [32] 1000 -(msg allgas " + coinReg + " 0 0 64) +(regname 'GavCoin) +(regcoin 'GAV) (returnlll { (when (&& (= $0 'kill) (= (caller) @@0x69)) (suicide (caller))) @@ -147,16 +141,15 @@ var gavCoinCode = eth.lll(" var gavCoin; env.note('Create GavCoin...') -eth.create(eth.key, '0', gavCoinCode, 10000, eth.gasPrice, function(a) { gavCoin = a; }); +eth.transact({ 'code': gavCoinCode }, function(a) { gavCoin = a; }); env.note('Register GavCoin...') -eth.transact(eth.key, '0', config, "2".pad(32) + gavCoin.pad(32), 10000, eth.gasPrice); +eth.transact({ 'to': config, 'data': ['2', gavCoin] }); + var exchangeCode = eth.lll(" { -[0] 'register -[32] 'Exchange -(msg allgas 0x50441127ea5b9dfd835a9aba4e1dc9c1257b58ca 0 0 64) +(regname 'Exchange) (def 'min (a b) (if (< a b) a b)) @@ -275,31 +268,34 @@ var exchangeCode = eth.lll(" var exchange; env.note('Create Exchange...') -eth.create(eth.key, '0', exchangeCode, 10000, eth.gasPrice, function(a) { exchange = a; }); +eth.transact({ 'code': exchangeCode }, function(a) { exchange = a; }); env.note('Register Exchange...') -eth.transact(eth.key, '0', config, "3".pad(32) + exchange.pad(32), 10000, eth.gasPrice); +eth.transact({ 'to': config, 'data': ['3', exchange] }); env.note('Register my name...') -eth.transact(eth.key, '0', nameReg, "register".pad(32) + "Gav".pad(32), 10000, eth.gasPrice); +eth.transact({ 'to': nameReg, 'data': [ eth.fromAscii('register'), eth.fromAscii('Gav') ] }); env.note('Dole out ETH to other address...') -eth.transact(eth.key, '100000000000000000000', eth.secretToAddress(eth.keys[1]), "", 10000, eth.gasPrice); +eth.transact({ 'value': '100000000000000000000', 'to': eth.secretToAddress(eth.keys[1]) }); env.note('Register my other name...') -eth.transact(eth.keys[1], '0', nameReg, "register".pad(32) + "Gav Would".pad(32), 10000, eth.gasPrice); +eth.transact({ 'from': eth.keys[1], 'to': nameReg, 'data': [ eth.fromAscii('register'), eth.fromAscii("Gav Would") ] }); env.note('Approve Exchange...') -eth.transact(eth.key, '0', gavCoin, "approve".pad(32) + exchange.pad(32), 10000, eth.gasPrice); +eth.transact({ 'to': gavCoin, 'data': [ eth.fromAscii('approve'), exchange ] }); env.note('Approve Exchange on other address...') -eth.transact(eth.keys[1], '0', gavCoin, "approve".pad(32) + exchange.pad(32), 10000, eth.gasPrice); +eth.transact({ 'from': eth.keys[1], 'to': gavCoin, 'data': [ eth.fromAscii('approve'), exchange ] }); env.note('Make offer 5000GAV/5ETH...') -eth.transact(eth.key, '0', exchange, "new".pad(32) + gavCoin.pad(32) + "5000".pad(32) + "0".pad(32) + "5000000000000000000".pad(32), 10000, eth.gasPrice); +eth.transact({ 'to': exchange, 'data': [eth.fromAscii('new'), gavCoin, '5000', '0', '5000000000000000000'] }); + +env.note('Register gav.eth...') +eth.transact({ 'to': dnsReg, 'data': [eth.fromAscii('register'), eth.fromAscii('gav'), eth.fromAscii('opensecrecy.com')] }); env.note('All done.') From 8eb5ff5c4dc128f86f5b86f60cc1c7aa689768ad Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 14 Aug 2014 13:26:28 +0200 Subject: [PATCH 082/223] Fix for padding in new SHA3. --- liblll/CompilerState.cpp | 12 +++++++----- libqethereum/QEthereum.cpp | 10 +++++----- stdserv.js | 2 +- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/liblll/CompilerState.cpp b/liblll/CompilerState.cpp index 01995674f..222775017 100644 --- a/liblll/CompilerState.cpp +++ b/liblll/CompilerState.cpp @@ -46,12 +46,7 @@ void CompilerState::populateStandard() static const string s = "{" "(def 'gav 0x51ba59315b3a95761d0863b05ccc7a7f54703d99)" "(def 'config 0x661005d2720d855f1d9976f88bb10c1a3398c77f)" - "(def 'namereg 0x50441127ea5b9dfd835a9aba4e1dc9c1257b58ca)" - "(def 'gavcoin 0x5620133321fcac7f15a5c570016f6cb6dc263f9d)" "(def 'allgas (- (gas) 21))" - "(def 'sendgavcoin (to value) { [0]'send [32]:to [64]:value (call allgas gavcoin 0 0 96 0 0) })" - "(def 'regname (name) { [0]'register [32]name (call allgas namereg 0 0 64 0 0) })" - "(def 'regcoins (name) { [0]'register [32]name (call allgas namereg 0 0 64 0 0) })" "(def 'send (to value) (call allgas to value 0 0 0 0))" "(def 'send (gaslimit to value) (call gaslimit to value 0 0 0 0))" "(def 'msg (gaslimit to value data datasize outsize) { (set x outsize) (set y (alloc @32)) (call gaslimit to value data datasize @0 @32) @0 })" @@ -69,6 +64,13 @@ void CompilerState::populateStandard() "(def 'makeperm (name pos) { (def name (sload pos)) (def name (v) (sstore pos v)) } )" "(def 'permcount 0)" "(def 'perm (name) { (makeperm name permcount) (def 'permcount (+ permcount 1)) } )" + "(def 'namereg (msg config 0))" + "(def 'coinreg (msg config 1))" + "(def 'gavcoin (msg config 2))" + "(def 'sendgavcoin (to value) { [32]'send [64]:to [96]:value (call allgas gavcoin 0 32 96 0 0) })" + "(def 'regname (name) { [32]'register [64]name (call allgas namereg 0 32 64 0 0) })" + "(def 'regcoin (name) { [32]name (call allgas coinreg 0 32 32 0 0) })" + "(def 'regcoin (name denom) { [32]name [64]denom (call allgas coinreg 0 32 64 0 0) })" "}"; CodeFragment::compile(s, *this); } diff --git a/libqethereum/QEthereum.cpp b/libqethereum/QEthereum.cpp index 4d77f189a..e1cae79ae 100644 --- a/libqethereum/QEthereum.cpp +++ b/libqethereum/QEthereum.cpp @@ -137,12 +137,12 @@ QString QEthereum::sha3(QString _s) const QString QEthereum::sha3(QString _s1, QString _s2) const { - return toQJS(eth::sha3(toBytes(_s1) + toBytes(_s2))); + return toQJS(eth::sha3(asBytes(padded(_s1, 32)) + asBytes(padded(_s2, 32)))); } QString QEthereum::sha3(QString _s1, QString _s2, QString _s3) const { - return toQJS(eth::sha3(toBytes(_s1) + toBytes(_s2) + toBytes(_s3))); + return toQJS(eth::sha3(asBytes(padded(_s1, 32)) + asBytes(padded(_s2, 32)) + asBytes(padded(_s3, 32)))); } QString QEthereum::sha3old(QString _s) const @@ -334,13 +334,13 @@ static TransactionSkeleton toTransaction(QString _json) ret.data = toBytes(f["code"].toString()); else if (f["data"].isArray()) for (auto i: f["data"].toArray()) - eth::operator +=(ret.data, toBytes(padded(i.toString(), 32))); + eth::operator +=(ret.data, asBytes(padded(i.toString(), 32))); else if (f["code"].isArray()) for (auto i: f["code"].toArray()) - eth::operator +=(ret.data, toBytes(padded(i.toString(), 32))); + eth::operator +=(ret.data, asBytes(padded(i.toString(), 32))); else if (f["dataclose"].isArray()) for (auto i: f["dataclose"].toArray()) - eth::operator +=(ret.data, toBytes(toBinary(i.toString()))); + eth::operator +=(ret.data, toBytes(i.toString())); } return ret; } diff --git a/stdserv.js b/stdserv.js index be9b90b79..af14360df 100644 --- a/stdserv.js +++ b/stdserv.js @@ -95,7 +95,7 @@ var gavCoinCode = eth.lll(" [[ 0x42 ]] (number) (regname 'GavCoin) -(regcoin 'GAV) +(regcoin 'GAV 1000) (returnlll { (when (&& (= $0 'kill) (= (caller) @@0x69)) (suicide (caller))) From af05698ef96a71659c84efebd195c06653ec590d Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 14 Aug 2014 14:45:13 +0200 Subject: [PATCH 083/223] Minor fix for JS API. --- libqethereum/QEthereum.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libqethereum/QEthereum.h b/libqethereum/QEthereum.h index f57f40bf6..b145da59a 100644 --- a/libqethereum/QEthereum.h +++ b/libqethereum/QEthereum.h @@ -73,12 +73,12 @@ inline QString toDecimal(QString const& _s) return QString::fromStdString(eth::toString(toU256(_s))); } -inline double toFixed(QString const& _s) +inline double fromFixed(QString const& _s) { return (double)toU256(_s) / (double)(eth::u256(1) << 128); } -inline QString fromFixed(double _s) +inline QString toFixed(double _s) { return toQJS(eth::u256(_s * (double)(eth::u256(1) << 128))); } @@ -130,8 +130,8 @@ public: Q_INVOKABLE QString fromAscii(QString _s) const { return ::fromBinary(_s, 32); } Q_INVOKABLE QString fromAscii(QString _s, unsigned _padding) const { return ::fromBinary(_s, _padding); } Q_INVOKABLE QString toDecimal(QString _s) const { return ::toDecimal(_s); } - Q_INVOKABLE double toFixed(QString _s) const { return ::toFixed(_s); } - Q_INVOKABLE QString fromFixed(double _d) const { return ::fromFixed(_d); } + Q_INVOKABLE double fromFixed(QString _s) const { return ::fromFixed(_s); } + Q_INVOKABLE QString toFixed(double _d) const { return ::toFixed(_d); } // [NEW API] - Use this instead. Q_INVOKABLE QString/*eth::u256*/ balanceAt(QString/*eth::Address*/ _a, int _block) const; From 1c23d788e9fe48cf190e5164e979b4687953b557 Mon Sep 17 00:00:00 2001 From: Giacomo Tazzari Date: Fri, 15 Aug 2014 21:19:00 +0200 Subject: [PATCH 084/223] Visual Studio 2013 Update 3 needs _WINSOCK_DEPRECATED_NO_WARNINGS for libminiupnpc --- windows/Alethzero.vcxproj | 4 ++-- windows/LibMiniUPnPc.vcxproj | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/windows/Alethzero.vcxproj b/windows/Alethzero.vcxproj index 06c78c4da..721c0729e 100644 --- a/windows/Alethzero.vcxproj +++ b/windows/Alethzero.vcxproj @@ -87,7 +87,7 @@ Use Disabled - WIN32;_DEBUG;%(PreprocessorDefinitions) + WIN32;_WINSOCK_DEPRECATED_NO_WARNINGS;_DEBUG;%(PreprocessorDefinitions) MultiThreadedDebugDLL true stdafx.h @@ -126,7 +126,7 @@ MaxSpeed true true - WIN32;NDEBUG;%(PreprocessorDefinitions) + WIN32;_WINSOCK_DEPRECATED_NO_WARNINGS;NDEBUG;%(PreprocessorDefinitions) MultiThreadedDLL true AnySuitable diff --git a/windows/LibMiniUPnPc.vcxproj b/windows/LibMiniUPnPc.vcxproj index 865b6c74d..7e5e44412 100644 --- a/windows/LibMiniUPnPc.vcxproj +++ b/windows/LibMiniUPnPc.vcxproj @@ -106,7 +106,7 @@ Disabled MultiThreadedDebugDLL - _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNINGS;_WINSOCK_DEPRECATED_NO_WARNINGS;%(PreprocessorDefinitions) 4100;4244;4245;4267;4389;%(DisableSpecificWarnings) @@ -142,7 +142,7 @@ true true MultiThreadedDLL - _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions) + _CRT_SECURE_NO_WARNINGS;_WINSOCK_DEPRECATED_NO_WARNINGS;%(PreprocessorDefinitions) 4100;4244;4245;4267;4389;%(DisableSpecificWarnings) From 9297669549ce0984eb7133cf95a1c57138749fc2 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 16 Aug 2014 10:01:45 +0200 Subject: [PATCH 085/223] Timing for EVM. --- alethzero/MainWin.cpp | 2 +- exp/main.cpp | 13 ++----------- libethereum/Executive.cpp | 6 +++++- 3 files changed, 8 insertions(+), 13 deletions(-) diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index 915b4a082..4e98bb780 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -700,7 +700,7 @@ void Main::refreshBalances() auto denom = m_client->stateAt(coinsAddr, sha3(h256(n).asBytes())); if (denom == 0) denom = 1; - cdebug << n << addr << denom << sha3(h256(n).asBytes()); +// cdebug << n << addr << denom << sha3(h256(n).asBytes()); altCoins[addr] = make_tuple(fromRaw(n), 0, denom); } for (auto i: m_myKeys) diff --git a/exp/main.cpp b/exp/main.cpp index 6a5e5557f..caae262c5 100644 --- a/exp/main.cpp +++ b/exp/main.cpp @@ -23,22 +23,13 @@ #include #include #include +#include #include "BuildInfo.h" using namespace std; using namespace eth; int main(int, char**) { - u256 z = 0; - u256 s = 7; - u256 ms = z - s; - s256 ams = -7; - s256 sms = u2s(ms); - cnote << sms; - cnote << ams; - cnote << ms; - u256 t = 3; - s256 st = u2s(t); - cnote << ms << t << (sms % t) << sms << st << (s2u(sms % st) + 70); + return 0; } diff --git a/libethereum/Executive.cpp b/libethereum/Executive.cpp index 1fff722eb..9096b5d64 100644 --- a/libethereum/Executive.cpp +++ b/libethereum/Executive.cpp @@ -19,6 +19,7 @@ * @date 2014 */ +#include #include #include "Executive.h" #include "State.h" @@ -84,7 +85,7 @@ bool Executive::setup(bytesConstRef _rlp) if (startGasUsed + m_t.gas > m_s.m_currentBlock.gasLimit) { clog(StateChat) << "Too much gas used in this block: Require <" << (m_s.m_currentBlock.gasLimit - startGasUsed) << " Got" << m_t.gas; - throw BlockGasLimitReached(); +// throw BlockGasLimitReached(); } // Increment associated nonce for sender. @@ -165,6 +166,8 @@ bool Executive::go(OnOpFunc const& _onOp) { if (m_vm) { + boost::timer t; + auto sgas = m_vm->gas(); bool revert = false; try { @@ -192,6 +195,7 @@ bool Executive::go(OnOpFunc const& _onOp) { clog(StateChat) << "std::exception in VM: " << _e.what(); } + cnote << "VM took:" << t.elapsed() << "; gas used: " << (sgas - m_endGas); // Write state out only in the case of a non-excepted transaction. if (revert) From f5c22035fcf644cc7594e617ed5ff8355a7aaab9 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 19 Aug 2014 17:55:41 +0200 Subject: [PATCH 086/223] PoC-6 networking. --- libethcore/BlockInfo.cpp | 2 +- libethcore/CommonEth.cpp | 2 +- libethential/FixedHash.h | 2 +- libethereum/BlockChain.cpp | 2 +- libethereum/Client.h | 6 +- libethereum/PeerNetwork.cpp | 2 +- libethereum/PeerNetwork.h | 16 +- libethereum/PeerServer.cpp | 63 +++++++- libethereum/PeerServer.h | 24 ++- libethereum/PeerSession.cpp | 296 ++++++++++++++++++------------------ libethereum/PeerSession.h | 16 ++ 11 files changed, 268 insertions(+), 163 deletions(-) diff --git a/libethcore/BlockInfo.cpp b/libethcore/BlockInfo.cpp index 2fdee90c7..a8af6fe21 100644 --- a/libethcore/BlockInfo.cpp +++ b/libethcore/BlockInfo.cpp @@ -30,7 +30,7 @@ using namespace std; using namespace eth; -u256 eth::c_genesisDifficulty = (u256)1 << 22; +u256 eth::c_genesisDifficulty = (u256)1 << 12; BlockInfo::BlockInfo(): timestamp(Invalid256) { diff --git a/libethcore/CommonEth.cpp b/libethcore/CommonEth.cpp index 336829c2f..10b550e31 100644 --- a/libethcore/CommonEth.cpp +++ b/libethcore/CommonEth.cpp @@ -29,7 +29,7 @@ using namespace eth; //#define ETH_ADDRESS_DEBUG 1 -const unsigned eth::c_protocolVersion = 26; +const unsigned eth::c_protocolVersion = 27; const unsigned eth::c_databaseVersion = 1; static const vector> g_units = diff --git a/libethential/FixedHash.h b/libethential/FixedHash.h index 4bac48743..e13345728 100644 --- a/libethential/FixedHash.h +++ b/libethential/FixedHash.h @@ -102,7 +102,7 @@ public: byte operator[](unsigned _i) const { return m_data[_i]; } /// @returns an abridged version of the hash as a user-readable hex string. - std::string abridged() const { return toHex(ref().cropped(0, 4)) + ".."; } + std::string abridged() const { return toHex(ref().cropped(0, 4)) + "\342\200\246"; } /// @returns a mutable byte vector_ref to the object's data. bytesRef ref() { return bytesRef(m_data.data(), N); } diff --git a/libethereum/BlockChain.cpp b/libethereum/BlockChain.cpp index 43ce6f150..52be37d51 100644 --- a/libethereum/BlockChain.cpp +++ b/libethereum/BlockChain.cpp @@ -402,7 +402,7 @@ bytes BlockChain::block(h256 _hash) const memcpy(m_cache[_hash].data(), d.data(), d.size()); if (!d.size()) - cwarn << "Couldn't find requested block:" << _hash; + cwarn << "Couldn't find requested block:" << _hash.abridged(); return m_cache[_hash]; } diff --git a/libethereum/Client.h b/libethereum/Client.h index d765494eb..cb532d38f 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -154,9 +154,9 @@ struct ClientWatch struct WatchChannel: public LogChannel { static const char* name() { return "(o)"; } static const int verbosity = 7; }; #define cwatch eth::LogOutputStream() -struct WorkInChannel: public LogChannel { static const char* name() { return ">W>"; } static const int verbosity = 6; }; -struct WorkOutChannel: public LogChannel { static const char* name() { return "W>"; } static const int verbosity = 16; }; +struct WorkOutChannel: public LogChannel { static const char* name() { return "() #define cworkin eth::LogOutputStream() #define cworkout eth::LogOutputStream() diff --git a/libethereum/PeerNetwork.cpp b/libethereum/PeerNetwork.cpp index 2f93ee74c..6585769e6 100644 --- a/libethereum/PeerNetwork.cpp +++ b/libethereum/PeerNetwork.cpp @@ -35,7 +35,7 @@ bool eth::isPrivateAddress(bi::address _addressToCheck) bi::address_v4::bytes_type bytesToCheck = v4Address.to_bytes(); if (bytesToCheck[0] == 10 || bytesToCheck[0] == 127) return true; - if (bytesToCheck[0] == 172 && (bytesToCheck[1] >= 16 && bytesToCheck[1] <=31)) + if (bytesToCheck[0] == 172 && (bytesToCheck[1] >= 16 && bytesToCheck[1] <= 31)) return true; if (bytesToCheck[0] == 192 && bytesToCheck[1] == 168) return true; diff --git a/libethereum/PeerNetwork.h b/libethereum/PeerNetwork.h index 22ebd98b7..f73f44e54 100644 --- a/libethereum/PeerNetwork.h +++ b/libethereum/PeerNetwork.h @@ -37,6 +37,11 @@ namespace eth bool isPrivateAddress(bi::address _addressToCheck); +static const eth::uint c_maxHashes = 32; ///< Maximum number of hashes BlockHashes will ever send. +static const eth::uint c_maxHashesAsk = 32; ///< Maximum number of hashes GetBlockHashes will ever ask for. +static const eth::uint c_maxBlocks = 16; ///< Maximum number of blocks Blocks will ever send. +static const eth::uint c_maxBlocksAsk = 16; ///< Maximum number of blocks we ask to receive in Blocks (when using GetChain). + class OverlayDB; class BlockChain; class TransactionQueue; @@ -50,9 +55,9 @@ struct NetConnect: public LogChannel { static const char* name() { return "+N+"; struct NetMessageDetail: public LogChannel { static const char* name() { return "=N="; } static const int verbosity = 5; }; struct NetTriviaSummary: public LogChannel { static const char* name() { return "-N-"; } static const int verbosity = 10; }; struct NetTriviaDetail: public LogChannel { static const char* name() { return "=N="; } static const int verbosity = 11; }; -struct NetAllDetail: public LogChannel { static const char* name() { return "=N="; } static const int verbosity = 15; }; -struct NetRight: public LogChannel { static const char* name() { return ">N>"; } static const int verbosity = 18; }; -struct NetLeft: public LogChannel { static const char* name() { return "N>"; } static const int verbosity = 14; }; +struct NetLeft: public LogChannel { static const char* name() { return " p = i.second.lock()) + p->giveUpOnFetch(); } void PeerServer::registerPeer(std::shared_ptr _s) @@ -363,13 +367,46 @@ void PeerServer::connect(bi::tcp::endpoint const& _ep) }); } +h256Set PeerServer::neededBlocks() +{ + Guard l(x_blocksNeeded); + h256Set ret; + if (m_blocksNeeded.size()) + { + while (ret.size() < c_maxBlocksAsk && m_blocksNeeded.size()) + { + ret.insert(m_blocksNeeded.back()); + m_blocksOnWay.insert(m_blocksNeeded.back()); + m_blocksNeeded.pop_back(); + } + } + else + for (auto i = m_blocksOnWay.begin(); ret.size() < c_maxBlocksAsk && i != m_blocksOnWay.end(); ++i) + ret.insert(*i); + return ret; +} + +bool PeerServer::havePeer(Public _id) const +{ + Guard l(x_peers); + + // Remove dead peers from list. + for (auto i = m_peers.begin(); i != m_peers.end();) + if (i->second.lock().get()) + ++i; + else + i = m_peers.erase(i); + + return m_peers.count(_id); +} + bool PeerServer::ensureInitialised(TransactionQueue& _tq) { if (m_latestBlockSent == h256()) { // First time - just initialise. m_latestBlockSent = m_chain->currentHash(); - clog(NetNote) << "Initialising: latest=" << m_latestBlockSent; + clog(NetNote) << "Initialising: latest=" << m_latestBlockSent.abridged(); for (auto const& i: _tq.transactions()) m_transactionsSent.insert(i.first); @@ -381,6 +418,8 @@ bool PeerServer::ensureInitialised(TransactionQueue& _tq) bool PeerServer::noteBlock(h256 _hash, bytesConstRef _data) { + Guard l(x_blocksNeeded); + m_blocksOnWay.erase(_hash); if (!m_chain->details(_hash)) { lock_guard l(m_incomingLock); @@ -522,6 +561,28 @@ void PeerServer::growPeers() } } +void PeerServer::noteHaveChain(std::shared_ptr const& _from) +{ + auto td = _from->m_totalDifficulty; + + if ((m_totalDifficultyOfNeeded && td < m_totalDifficultyOfNeeded) || td < m_chain->details().totalDifficulty) + return; + + { + Guard l(x_blocksNeeded); + m_blocksNeeded = _from->m_neededBlocks; + } + + // Looks like it's the best yet for total difficulty. Set to download. + { + Guard l(x_peers); + for (auto const& i: m_peers) + if (shared_ptr p = i.second.lock()) + p->ensureGettingChain(); + } +} + + void PeerServer::prunePeers() { Guard l(x_peers); diff --git a/libethereum/PeerServer.h b/libethereum/PeerServer.h index d3c2da65c..5d12c807c 100644 --- a/libethereum/PeerServer.h +++ b/libethereum/PeerServer.h @@ -37,6 +37,7 @@ namespace bi = boost::asio::ip; namespace eth { +class RLPStream; class TransactionQueue; class BlockQueue; @@ -77,11 +78,13 @@ public: /// This won't touch alter the blockchain. void process() { if (isInitialised()) m_ioService.poll(); } - bool havePeer(Public _id) const { Guard l(x_peers); return m_peers.count(_id) != 0; } + /// @returns true iff we have the a peer of the given id. + bool havePeer(Public _id) const; /// Set ideal number of peers. void setIdealPeerCount(unsigned _n) { m_idealPeerCount = _n; } + /// Set the mode of operation on the network. void setMode(NodeMode _m) { m_mode = _m; } /// Get peer information. @@ -96,7 +99,10 @@ public: /// Get the port we're listening on currently. unsigned short listenPort() const { return m_public.port(); } + /// Serialise the set of known peers. bytes savePeers() const; + + /// Deserialise the data and populate the set of known peers. void restorePeers(bytesConstRef _b); void registerPeer(std::shared_ptr _s); @@ -105,6 +111,10 @@ private: /// Session wants to pass us a block that we might not have. /// @returns true if we didn't have it. bool noteBlock(h256 _hash, bytesConstRef _data); + /// Session has finished getting the chain of hashes. + void noteHaveChain(std::shared_ptr const& _who); + /// Called when the session has provided us with a new peer we can connect to. + void noteNewPeers() {} void seal(bytes& _b); void populateAddresses(); @@ -116,6 +126,11 @@ private: void maintainTransactions(TransactionQueue& _tq, h256 _currentBlock); void maintainBlocks(BlockQueue& _bq, h256 _currentBlock); + /// Get a bunch of needed blocks. + /// Removes them from our list of needed blocks. + /// @returns empty if there's no more blocks left to fetch, otherwise the blocks to fetch. + h256Set neededBlocks(); + /// Check to see if the network peer-state initialisation has happened. bool isInitialised() const { return m_latestBlockSent; } /// Initialises the network peer-state, doing the stuff that needs to be once-only. @returns true if it really was first. @@ -140,7 +155,7 @@ private: u256 m_networkId; mutable std::mutex x_peers; - std::map> m_peers; + mutable std::map> m_peers; // mutable because we flush zombie entries (null-weakptrs) as regular maintenance from a const method. mutable std::recursive_mutex m_incomingLock; std::vector m_incomingTransactions; @@ -148,6 +163,11 @@ private: std::map> m_incomingPeers; std::vector m_freePeers; + mutable std::mutex x_blocksNeeded; + u256 m_totalDifficultyOfNeeded; + h256s m_blocksNeeded; /// From latest to earliest. + h256Set m_blocksOnWay; + h256 m_latestBlockSent; std::set m_transactionsSent; diff --git a/libethereum/PeerSession.cpp b/libethereum/PeerSession.cpp index 72c7bec09..15314ecef 100644 --- a/libethereum/PeerSession.cpp +++ b/libethereum/PeerSession.cpp @@ -31,10 +31,6 @@ using namespace eth; #define clogS(X) eth::LogOutputStream(false) << "| " << std::setw(2) << m_socket.native_handle() << "] " -static const eth::uint c_maxHashes = 4096; ///< Maximum number of hashes GetChain will ever send. -static const eth::uint c_maxBlocks = 2048; ///< Maximum number of blocks Blocks will ever send. -static const eth::uint c_maxBlocksAsk = 512; ///< Maximum number of blocks we ask to receive in Blocks (when using GetChain). - PeerSession::PeerSession(PeerServer* _s, bi::tcp::socket _socket, u256 _rNId, bi::address _peerAddress, unsigned short _peerPort): m_server(_s), m_socket(std::move(_socket)), @@ -49,6 +45,8 @@ PeerSession::PeerSession(PeerServer* _s, bi::tcp::socket _socket, u256 _rNId, bi PeerSession::~PeerSession() { + giveUpOnFetch(); + // Read-chain finished for one reason or another. try { @@ -58,6 +56,21 @@ PeerSession::~PeerSession() catch (...){} } +void PeerSession::giveUpOnFetch() +{ + if (m_askedBlocks.size()) + { + Guard l (m_server->x_blocksNeeded); + m_server->m_blocksNeeded.reserve(m_server->m_blocksNeeded.size() + m_askedBlocks.size()); + for (auto i: m_askedBlocks) + { + m_server->m_blocksOnWay.erase(i); + m_server->m_blocksNeeded.push_back(i); + } + m_askedBlocks.clear(); + } +} + bi::tcp::endpoint PeerSession::endpoint() const { if (m_socket.is_open()) @@ -83,6 +96,8 @@ bool PeerSession::interpret(RLP const& _r) m_caps = _r[4].toInt(); m_listenPort = _r[5].toInt(); m_id = _r[6].toHash(); + m_totalDifficulty = _r[7].toInt(); + m_latestHash = _r[8].toHash(); clogS(NetMessageSummary) << "Hello: " << clientVersion << "V[" << m_protocolVersion << "/" << m_networkId << "]" << m_id.abridged() << showbase << hex << m_caps << dec << m_listenPort; @@ -153,7 +168,7 @@ bool PeerSession::interpret(RLP const& _r) s << PeersPacket; for (auto i: peers) { - clogS(NetTriviaDetail) << "Sending peer " << toHex(i.first.ref().cropped(0, 4)) << i.second; + clogS(NetTriviaDetail) << "Sending peer " << i.first.abridged() << i.second; s.appendList(3) << bytesConstRef(i.second.address().to_v4().to_bytes().data(), 4) << i.second.port() << i.first; } sealAndSend(s); @@ -186,6 +201,7 @@ bool PeerSession::interpret(RLP const& _r) goto CONTINUE; m_server->m_incomingPeers[id] = make_pair(ep, 0); m_server->m_freePeers.push_back(id); + m_server->noteNewPeers(); clogS(NetTriviaDetail) << "New peer: " << ep << "(" << id << ")"; CONTINUE:; } @@ -201,25 +217,99 @@ bool PeerSession::interpret(RLP const& _r) m_knownTransactions.insert(sha3(_r[i].data())); } break; + case GetBlockHashesPacket: + { + if (m_server->m_mode == NodeMode::PeerServer) + break; + unsigned limit = _r[1].toInt(); + h256 later = _r[2].toHash(); + clogS(NetMessageSummary) << "GetBlockHashes (" << limit << "entries, " << later.abridged() << ")"; + + unsigned c = min(m_server->m_chain->number(later), limit); + + RLPStream s; + prep(s).appendList(1 + c).append(BlockHashesPacket); + h256 p = m_server->m_chain->details(later).parent; + for (unsigned i = 0; i < c; ++i, p = m_server->m_chain->details(p).parent) + s << p; + sealAndSend(s); + break; + } + case BlockHashesPacket: + { + if (m_server->m_mode == NodeMode::PeerServer) + break; + clogS(NetMessageSummary) << "BlockHashes (" << dec << (_r.itemCount() - 1) << " entries)"; + if (_r.itemCount() == 1) + { + m_server->noteHaveChain(shared_from_this()); + return true; + } + for (unsigned i = 1; i < _r.itemCount(); ++i) + { + auto h = _r[i].toHash(); + if (m_server->m_chain->details(h)) + { + m_server->noteHaveChain(shared_from_this()); + return true; + } + else + m_neededBlocks.push_back(h); + } + // run through - ask for more. + RLPStream s; + prep(s).appendList(3); + s << GetBlockHashesPacket << c_maxHashesAsk << m_neededBlocks.back(); + sealAndSend(s); + break; + } + case GetBlocksPacket: + { + if (m_server->m_mode == NodeMode::PeerServer) + break; + clogS(NetMessageSummary) << "GetBlocks (" << dec << (_r.itemCount() - 1) << " entries)"; + // TODO: return the requested blocks. + bytes rlp; + unsigned n = 0; + for (unsigned i = 1; i < _r.itemCount() && i <= c_maxBlocks; ++i) + { + auto b = m_server->m_chain->block(_r[i].toHash()); + if (b.size()) + { + rlp += b; + ++n; + } + } + RLPStream s; + sealAndSend(prep(s).appendList(n + 1).append(BlocksPacket).appendRaw(rlp, n)); + break; + } case BlocksPacket: { if (m_server->m_mode == NodeMode::PeerServer) break; clogS(NetMessageSummary) << "Blocks (" << dec << (_r.itemCount() - 1) << " entries)"; + + if (_r.itemCount() == 1) + { + // Couldn't get any from last batch - probably got to this peer's latest block - just give up. + giveUpOnFetch(); + break; + } + unsigned used = 0; for (unsigned i = 1; i < _r.itemCount(); ++i) { auto h = sha3(_r[i].data()); if (m_server->noteBlock(h, _r[i].data())) - { - m_knownBlocks.insert(h); used++; - } + m_askedBlocks.erase(h); + m_knownBlocks.insert(h); } m_rating += used; unsigned knownParents = 0; unsigned unknownParents = 0; - if (g_logVerbosity >= 2) + if (g_logVerbosity >= NetMessageSummary::verbosity) { for (unsigned i = 1; i < _r.itemCount(); ++i) { @@ -228,138 +318,17 @@ bool PeerSession::interpret(RLP const& _r) if (!m_server->m_chain->details(bi.parentHash) && !m_knownBlocks.count(bi.parentHash)) { unknownParents++; - clogS(NetMessageDetail) << "Unknown parent " << bi.parentHash << " of block " << h; + clogS(NetAllDetail) << "Unknown parent " << bi.parentHash << " of block " << h; } else { knownParents++; - clogS(NetMessageDetail) << "Known parent " << bi.parentHash << " of block " << h; + clogS(NetAllDetail) << "Known parent " << bi.parentHash << " of block " << h; } } } clogS(NetMessageSummary) << dec << knownParents << " known parents, " << unknownParents << "unknown, " << used << "used."; - if (used) // we received some - check if there's any more - { - RLPStream s; - prep(s).appendList(3); - s << GetChainPacket; - s << sha3(_r[1].data()); - s << c_maxBlocksAsk; - sealAndSend(s); - } - else - clogS(NetMessageSummary) << "Peer sent all blocks in chain."; - break; - } - case GetChainPacket: - { - if (m_server->m_mode == NodeMode::PeerServer) - break; - clogS(NetMessageSummary) << "GetChain (" << (_r.itemCount() - 2) << " hashes, " << (_r[_r.itemCount() - 1].toInt()) << ")"; - // ******************************************************************** - // NEEDS FULL REWRITE! - h256s parents; - parents.reserve(_r.itemCount() - 2); - for (unsigned i = 1; i < _r.itemCount() - 1; ++i) - parents.push_back(_r[i].toHash()); - if (_r.itemCount() == 2) - break; - // return 2048 block max. - uint baseCount = (uint)min(_r[_r.itemCount() - 1].toInt(), c_maxBlocks); - clogS(NetMessageSummary) << "GetChain (" << baseCount << " max, from " << parents.front() << " to " << parents.back() << ")"; - for (auto parent: parents) - { - auto h = m_server->m_chain->currentHash(); - h256 latest = m_server->m_chain->currentHash(); - uint latestNumber = 0; - uint parentNumber = 0; - RLPStream s; - - // try to find parent in our blockchain - // todo: add some delta() fn to blockchain - BlockDetails fParent = m_server->m_chain->details(parent); - if (fParent) - { - latestNumber = m_server->m_chain->number(latest); - parentNumber = fParent.number; - uint count = min(latestNumber - parentNumber, baseCount); - clogS(NetAllDetail) << "Requires " << dec << (latestNumber - parentNumber) << " blocks from " << latestNumber << " to " << parentNumber; - clogS(NetAllDetail) << latest << " - " << parent; - - prep(s); - s.appendList(1 + count) << BlocksPacket; - uint endNumber = parentNumber; - uint startNumber = endNumber + count; - clogS(NetAllDetail) << "Sending " << dec << count << " blocks from " << startNumber << " to " << endNumber; - - // append blocks - uint n = latestNumber; - // seek back (occurs when count is limited by baseCount) - for (; n > startNumber; n--, h = m_server->m_chain->details(h).parent) {} - for (uint i = 0; i < count; ++i, --n, h = m_server->m_chain->details(h).parent) - { - if (h == parent || n == endNumber) - { - cwarn << "BUG! Couldn't create the reply for GetChain!"; - return true; - } - clogS(NetAllDetail) << " " << dec << i << " " << h; - s.appendRaw(m_server->m_chain->block(h)); - } - - if (!count) - clogS(NetMessageSummary) << "Sent peer all we have."; - clogS(NetAllDetail) << "Parent: " << h; - } - else if (parent != parents.back()) - continue; - - if (h != parent) - { - // not in the blockchain; - if (parent == parents.back()) - { - // out of parents... - clogS(NetAllDetail) << "GetChain failed; not in chain"; - // No good - must have been on a different branch. - s.clear(); - prep(s).appendList(2) << NotInChainPacket << parents.back(); - } - else - // still some parents left - try them. - continue; - } - // send the packet (either Blocks or NotInChain) & exit. - sealAndSend(s); - break; - // ******************************************************************** - } - break; - } - case NotInChainPacket: - { - if (m_server->m_mode == NodeMode::PeerServer) - break; - h256 noGood = _r[1].toHash(); - clogS(NetMessageSummary) << "NotInChain (" << noGood << ")"; - if (noGood == m_server->m_chain->genesisHash()) - { - clogS(NetWarn) << "Discordance over genesis block! Disconnect."; - disconnect(WrongGenesis); - } - else - { - uint count = std::min(c_maxHashes, m_server->m_chain->number(noGood)); - RLPStream s; - prep(s).appendList(2 + count); - s << GetChainPacket; - auto h = m_server->m_chain->details(noGood).parent; - for (uint i = 0; i < count; ++i, h = m_server->m_chain->details(h).parent) - s << h; - s << c_maxBlocksAsk; - sealAndSend(s); - } - break; + ensureGettingChain(); } case GetTransactionsPacket: { @@ -374,6 +343,24 @@ bool PeerSession::interpret(RLP const& _r) return true; } +void PeerSession::ensureGettingChain() +{ + if (!m_askedBlocks.size()) + m_askedBlocks = m_server->neededBlocks(); + + if (m_askedBlocks.size()) + { + RLPStream s; + prep(s); + s.appendList(m_askedBlocks.size() + 1) << GetBlocksPacket; + for (auto i: m_askedBlocks) + s << i; + sealAndSend(s); + } + else + clogS(NetMessageSummary) << "No blocks left to get."; +} + void PeerSession::ping() { RLPStream s; @@ -381,6 +368,12 @@ void PeerSession::ping() m_ping = std::chrono::steady_clock::now(); } +void PeerSession::getPeers() +{ + RLPStream s; + sealAndSend(prep(s).appendList(1) << GetPeersPacket); +} + RLPStream& PeerSession::prep(RLPStream& _s) { return _s.appendRaw(bytes(8, 0)); @@ -508,30 +501,37 @@ void PeerSession::start() { RLPStream s; prep(s); - s.appendList(7) << HelloPacket << (uint)PeerServer::protocolVersion() << m_server->networkId() << m_server->m_clientVersion << (m_server->m_mode == NodeMode::Full ? 0x07 : m_server->m_mode == NodeMode::PeerServer ? 0x01 : 0) << m_server->m_public.port() << m_server->m_key.pub(); + s.appendList(9) << HelloPacket + << (uint)PeerServer::protocolVersion() + << m_server->networkId() + << m_server->m_clientVersion + << (m_server->m_mode == NodeMode::Full ? 0x07 : m_server->m_mode == NodeMode::PeerServer ? 0x01 : 0) + << m_server->m_public.port() + << m_server->m_key.pub() + << m_server->m_chain->details().totalDifficulty + << m_server->m_chain->currentHash(); sealAndSend(s); - ping(); + getPeers(); doRead(); } void PeerSession::startInitialSync() { - uint n = m_server->m_chain->number(m_server->m_latestBlockSent); - clogS(NetAllDetail) << "Want chain. Latest:" << m_server->m_latestBlockSent << ", number:" << n; - uint count = std::min(c_maxHashes, n + 1); - RLPStream s; - prep(s).appendList(2 + count); - s << GetChainPacket; - auto h = m_server->m_latestBlockSent; - for (uint i = 0; i < count; ++i, h = m_server->m_chain->details(h).parent) - { - clogS(NetAllDetail) << " " << i << ":" << h; - s << h; - } + h256 c = m_server->m_chain->currentHash(); + uint n = m_server->m_chain->number(); + u256 td = max(m_server->m_chain->details().totalDifficulty, m_server->m_totalDifficultyOfNeeded); + + clogS(NetAllDetail) << "Initial sync. Latest:" << c.abridged() << ", number:" << n << ", TD: max(" << m_server->m_chain->details().totalDifficulty << "," << m_server->m_totalDifficultyOfNeeded << ") versus " << m_totalDifficulty; + if (td > m_totalDifficulty) + return; // All good - we have the better chain. - s << c_maxBlocksAsk; + // Our chain isn't better - grab theirs. + RLPStream s; + prep(s).appendList(3); + s << GetBlockHashesPacket << c_maxHashesAsk << m_latestHash; + m_neededBlocks = h256s(1, m_latestHash); sealAndSend(s); } diff --git a/libethereum/PeerSession.h b/libethereum/PeerSession.h index 562d27e50..4cababbf4 100644 --- a/libethereum/PeerSession.h +++ b/libethereum/PeerSession.h @@ -33,6 +33,10 @@ namespace eth { +/** + * @brief The PeerSession class + * @todo Document fully. + */ class PeerSession: public std::enable_shared_from_this { friend class PeerServer; @@ -52,6 +56,12 @@ public: private: void startInitialSync(); + void getPeers(); + + /// Ensure that we are waiting for a bunch of blocks from our peer. + void ensureGettingChain(); + + void giveUpOnFetch(); void dropped(); void doRead(); @@ -84,6 +94,12 @@ private: unsigned short m_listenPort; ///< Port that the remote client is listening on for connections. Useful for giving to peers. uint m_caps; + h256 m_latestHash; ///< Peer's latest block's hash. + u256 m_totalDifficulty; ///< Peer's latest block's total difficulty. + h256s m_neededBlocks; ///< The blocks that we should download from this peer. + + h256Set m_askedBlocks; ///< The blocks for which we sent the last GetBlocks for but haven't received a corresponding Blocks. + std::chrono::steady_clock::time_point m_ping; std::chrono::steady_clock::time_point m_connect; std::chrono::steady_clock::time_point m_disconnect; From 022695af48ebc01a23b1e0986d958dc077258636 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 19 Aug 2014 18:08:47 +0200 Subject: [PATCH 087/223] Minor sync bug. --- libethereum/BlockChain.cpp | 14 +++++++++----- libethereum/BlockChain.h | 2 +- libethereum/PeerServer.cpp | 12 +++++++++--- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/libethereum/BlockChain.cpp b/libethereum/BlockChain.cpp index 52be37d51..ac9e9dc37 100644 --- a/libethereum/BlockChain.cpp +++ b/libethereum/BlockChain.cpp @@ -329,7 +329,7 @@ h256s BlockChain::import(bytes const& _block, OverlayDB const& _db) return ret; } -h256s BlockChain::treeRoute(h256 _from, h256 _to, h256* o_common) const +h256s BlockChain::treeRoute(h256 _from, h256 _to, h256* o_common, bool _pre, bool _post) const { h256s ret; h256s back; @@ -337,20 +337,24 @@ h256s BlockChain::treeRoute(h256 _from, h256 _to, h256* o_common) const unsigned tn = details(_to).number; while (fn > tn) { - ret.push_back(_from); + if (_pre) + ret.push_back(_from); _from = details(_from).parent; fn--; } while (fn < tn) { - back.push_back(_to); + if (_post) + back.push_back(_to); _to = details(_to).parent; tn--; } while (_from != _to) { - _from = details(_from).parent; - _to = details(_to).parent; + if (_pre) + _from = details(_from).parent; + if (_post) + _to = details(_to).parent; ret.push_back(_from); back.push_back(_to); } diff --git a/libethereum/BlockChain.h b/libethereum/BlockChain.h index 1c4e5e8fd..d13ef7614 100644 --- a/libethereum/BlockChain.h +++ b/libethereum/BlockChain.h @@ -128,7 +128,7 @@ public: * treeRoute(1b, 2a) == { 1b, 1a, 2a }; // *o_common == g * @endcode */ - h256s treeRoute(h256 _from, h256 _to, h256* o_common = nullptr) const; + h256s treeRoute(h256 _from, h256 _to, h256* o_common = nullptr, bool _pre = true, bool _post = true) const; private: template T queryExtras(h256 _h, std::map& _m, boost::shared_mutex& _x, T const& _n) const diff --git a/libethereum/PeerServer.cpp b/libethereum/PeerServer.cpp index 3117163f1..cd5ceaad4 100644 --- a/libethereum/PeerServer.cpp +++ b/libethereum/PeerServer.cpp @@ -507,12 +507,18 @@ void PeerServer::maintainBlocks(BlockQueue& _bq, h256 _currentHash) // Send any new blocks. if (_currentHash != m_latestBlockSent) { - // TODO: find where they diverge and send complete new branch. RLPStream ts; PeerSession::prep(ts); - ts.appendList(2) << BlocksPacket; + bytes bs; + unsigned c = 0; + for (auto h: m_chain->treeRoute(m_latestBlockSent, _currentHash, nullptr, false, true)) + { + bs += m_chain->block(h); + ++c; + } + ts.appendList(1 + c).append(BlocksPacket).appendRaw(bs, c); bytes b; - ts.appendRaw(m_chain->block()).swapOut(b); + ts.swapOut(b); seal(b); Guard l(x_peers); From e65eba8e9c4f02c0e73c86695484747dcee25787 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 19 Aug 2014 18:15:16 +0200 Subject: [PATCH 088/223] Version bump. --- libethential/Common.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libethential/Common.cpp b/libethential/Common.cpp index 2cc5c2e1c..7244107c2 100644 --- a/libethential/Common.cpp +++ b/libethential/Common.cpp @@ -27,6 +27,6 @@ using namespace eth; namespace eth { -char const* EthVersion = "0.6.2"; +char const* EthVersion = "0.6.3"; } From 22271c14babd35aa0a7bb79b6373aeedcf8f3d28 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 19 Aug 2014 18:18:42 +0200 Subject: [PATCH 089/223] Fix speed. --- libethcore/BlockInfo.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libethcore/BlockInfo.cpp b/libethcore/BlockInfo.cpp index a8af6fe21..2fdee90c7 100644 --- a/libethcore/BlockInfo.cpp +++ b/libethcore/BlockInfo.cpp @@ -30,7 +30,7 @@ using namespace std; using namespace eth; -u256 eth::c_genesisDifficulty = (u256)1 << 12; +u256 eth::c_genesisDifficulty = (u256)1 << 22; BlockInfo::BlockInfo(): timestamp(Invalid256) { From f0ea97a125be28e6dda647cbbaa174ee616ae659 Mon Sep 17 00:00:00 2001 From: SharpCoiner Date: Tue, 19 Aug 2014 21:27:43 +0200 Subject: [PATCH 090/223] Fixes Windows VS2013 build --- libethereum/PeerServer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libethereum/PeerServer.cpp b/libethereum/PeerServer.cpp index cd5ceaad4..71a80a90b 100644 --- a/libethereum/PeerServer.cpp +++ b/libethereum/PeerServer.cpp @@ -397,7 +397,7 @@ bool PeerServer::havePeer(Public _id) const else i = m_peers.erase(i); - return m_peers.count(_id); + return !!m_peers.count(_id); } bool PeerServer::ensureInitialised(TransactionQueue& _tq) From 103644dc82586924c89fae481f798e6501835bc5 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 20 Aug 2014 18:54:10 +0200 Subject: [PATCH 091/223] Faster mining, minor fix. --- alethzero/MainWin.cpp | 2 +- libethcore/BlockInfo.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index 4e98bb780..08cda9ece 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -845,7 +845,7 @@ void Main::refreshBlockChain() string filter = ui->blockChainFilter->text().toLower().toStdString(); auto const& bc = m_client->blockChain(); unsigned i = (ui->showAll->isChecked() || !filter.empty()) ? (unsigned)-1 : 10; - for (auto h = bc.currentHash(); h != bc.genesisHash() && i; h = bc.details(h).parent, --i) + for (auto h = bc.currentHash(); h != bc.genesisHash() && bc.details(h) && i; h = bc.details(h).parent, --i) { auto d = bc.details(h); auto bm = blockMatch(filter, d, h, bc); diff --git a/libethcore/BlockInfo.cpp b/libethcore/BlockInfo.cpp index 2fdee90c7..a8af6fe21 100644 --- a/libethcore/BlockInfo.cpp +++ b/libethcore/BlockInfo.cpp @@ -30,7 +30,7 @@ using namespace std; using namespace eth; -u256 eth::c_genesisDifficulty = (u256)1 << 22; +u256 eth::c_genesisDifficulty = (u256)1 << 12; BlockInfo::BlockInfo(): timestamp(Invalid256) { From 38495bfee3d32ff057d8e7dec7af263e63b0699f Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 21 Aug 2014 11:41:40 +0200 Subject: [PATCH 092/223] Correct packet order. --- libethereum/PeerSession.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libethereum/PeerSession.cpp b/libethereum/PeerSession.cpp index 15314ecef..393353b63 100644 --- a/libethereum/PeerSession.cpp +++ b/libethereum/PeerSession.cpp @@ -221,8 +221,8 @@ bool PeerSession::interpret(RLP const& _r) { if (m_server->m_mode == NodeMode::PeerServer) break; - unsigned limit = _r[1].toInt(); - h256 later = _r[2].toHash(); + h256 later = _r[1].toHash(); + unsigned limit = _r[2].toInt(); clogS(NetMessageSummary) << "GetBlockHashes (" << limit << "entries, " << later.abridged() << ")"; unsigned c = min(m_server->m_chain->number(later), limit); @@ -259,7 +259,7 @@ bool PeerSession::interpret(RLP const& _r) // run through - ask for more. RLPStream s; prep(s).appendList(3); - s << GetBlockHashesPacket << c_maxHashesAsk << m_neededBlocks.back(); + s << GetBlockHashesPacket << m_neededBlocks.back() << c_maxHashesAsk; sealAndSend(s); break; } @@ -530,7 +530,7 @@ void PeerSession::startInitialSync() // Our chain isn't better - grab theirs. RLPStream s; prep(s).appendList(3); - s << GetBlockHashesPacket << c_maxHashesAsk << m_latestHash; + s << GetBlockHashesPacket << m_latestHash << c_maxHashesAsk; m_neededBlocks = h256s(1, m_latestHash); sealAndSend(s); } From 2f524f645b536782085cc2f0be79289479f73d44 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 21 Aug 2014 15:53:59 +0200 Subject: [PATCH 093/223] Unambiguous licence. --- LICENSE | 693 ++++++++++++++++++++++++++++++++++++- libethcore/CommonEth.cpp | 2 +- libevm/VM.h | 72 ++-- libevmface/Instruction.cpp | 242 ++++++++----- libevmface/Instruction.h | 38 +- liblll/CodeFragment.cpp | 6 +- 6 files changed, 910 insertions(+), 143 deletions(-) diff --git a/LICENSE b/LICENSE index 72dc60d84..024073c02 100644 --- a/LICENSE +++ b/LICENSE @@ -1,19 +1,674 @@ -The MIT License (MIT) - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. +/bin/bash: wq: command not found + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/libethcore/CommonEth.cpp b/libethcore/CommonEth.cpp index 10b550e31..db1c9c636 100644 --- a/libethcore/CommonEth.cpp +++ b/libethcore/CommonEth.cpp @@ -29,7 +29,7 @@ using namespace eth; //#define ETH_ADDRESS_DEBUG 1 -const unsigned eth::c_protocolVersion = 27; +const unsigned eth::c_protocolVersion = 28; const unsigned eth::c_databaseVersion = 1; static const vector> g_units = diff --git a/libevm/VM.h b/libevm/VM.h index f014e2da8..df89c0b42 100644 --- a/libevm/VM.h +++ b/libevm/VM.h @@ -440,39 +440,52 @@ template eth::bytesConstRef eth::VM::go(Ext& _ext, OnOpFunc const& _ require(1); m_stack.pop_back(); break; - case Instruction::DUP: - require(1); - m_stack.push_back(m_stack.back()); - break; - /*case Instruction::DUPN: + case Instruction::DUP1: + case Instruction::DUP2: + case Instruction::DUP3: + case Instruction::DUP4: + case Instruction::DUP5: + case Instruction::DUP6: + case Instruction::DUP7: + case Instruction::DUP8: + case Instruction::DUP9: + case Instruction::DUP10: + case Instruction::DUP11: + case Instruction::DUP12: + case Instruction::DUP13: + case Instruction::DUP14: + case Instruction::DUP15: + case Instruction::DUP16: { - auto s = store(curPC + 1); - if (s == 0 || s > stack.size()) - throw OperandOutOfRange(1, stack.size(), s); - stack.push_back(stack[stack.size() - (uint)s]); - nextPC = curPC + 2; - break; - }*/ - case Instruction::SWAP: + auto n = 1 + (int)inst - (int)Instruction::DUP1; + require(n); + m_stack.push_back(m_stack[m_stack.size() - n]); + break; + } + case Instruction::SWAP1: + case Instruction::SWAP2: + case Instruction::SWAP3: + case Instruction::SWAP4: + case Instruction::SWAP5: + case Instruction::SWAP6: + case Instruction::SWAP7: + case Instruction::SWAP8: + case Instruction::SWAP9: + case Instruction::SWAP10: + case Instruction::SWAP11: + case Instruction::SWAP12: + case Instruction::SWAP13: + case Instruction::SWAP14: + case Instruction::SWAP15: + case Instruction::SWAP16: { - require(2); + unsigned n = (int)inst - (int)Instruction::SWAP1 + 2; + require(n); auto d = m_stack.back(); - m_stack.back() = m_stack[m_stack.size() - 2]; - m_stack[m_stack.size() - 2] = d; + m_stack.back() = m_stack[m_stack.size() - n]; + m_stack[m_stack.size() - n] = d; break; } - /*case Instruction::SWAPN: - { - require(1); - auto d = stack.back(); - auto s = store(curPC + 1); - if (s == 0 || s > stack.size()) - throw OperandOutOfRange(1, stack.size(), s); - stack.back() = stack[stack.size() - (uint)s]; - stack[stack.size() - (uint)s] = d; - nextPC = curPC + 2; - break; - }*/ case Instruction::MLOAD: { require(1); @@ -597,6 +610,9 @@ template eth::bytesConstRef eth::VM::go(Ext& _ext, OnOpFunc const& _ } case Instruction::STOP: return bytesConstRef(); + case Instruction::NOP1: + case Instruction::NOP2: + break; default: throw BadInstruction(); } diff --git a/libevmface/Instruction.cpp b/libevmface/Instruction.cpp index 559ebfb5b..7f2f51c67 100644 --- a/libevmface/Instruction.cpp +++ b/libevmface/Instruction.cpp @@ -66,8 +66,8 @@ const std::map eth::c_instructions = { "DIFFICULTY", Instruction::DIFFICULTY }, { "GASLIMIT", Instruction::GASLIMIT }, { "POP", Instruction::POP }, - { "DUP", Instruction::DUP }, - { "SWAP", Instruction::SWAP }, + { "NOP1", Instruction::NOP1 }, + { "NOP2", Instruction::NOP2 }, { "MLOAD", Instruction::MLOAD }, { "MSTORE", Instruction::MSTORE }, { "MSTORE8", Instruction::MSTORE8 }, @@ -110,6 +110,38 @@ const std::map eth::c_instructions = { "PUSH30", Instruction::PUSH30 }, { "PUSH31", Instruction::PUSH31 }, { "PUSH32", Instruction::PUSH32 }, + { "DUP1", Instruction::DUP1 }, + { "DUP2", Instruction::DUP2 }, + { "DUP3", Instruction::DUP3 }, + { "DUP4", Instruction::DUP4 }, + { "DUP5", Instruction::DUP5 }, + { "DUP6", Instruction::DUP6 }, + { "DUP7", Instruction::DUP7 }, + { "DUP8", Instruction::DUP8 }, + { "DUP9", Instruction::DUP9 }, + { "DUP10", Instruction::DUP10 }, + { "DUP11", Instruction::DUP11 }, + { "DUP12", Instruction::DUP12 }, + { "DUP13", Instruction::DUP13 }, + { "DUP14", Instruction::DUP14 }, + { "DUP15", Instruction::DUP15 }, + { "DUP16", Instruction::DUP16 }, + { "SWAP1", Instruction::SWAP1 }, + { "SWAP2", Instruction::SWAP2 }, + { "SWAP3", Instruction::SWAP3 }, + { "SWAP4", Instruction::SWAP4 }, + { "SWAP5", Instruction::SWAP5 }, + { "SWAP6", Instruction::SWAP6 }, + { "SWAP7", Instruction::SWAP7 }, + { "SWAP8", Instruction::SWAP8 }, + { "SWAP9", Instruction::SWAP9 }, + { "SWAP10", Instruction::SWAP10 }, + { "SWAP11", Instruction::SWAP11 }, + { "SWAP12", Instruction::SWAP12 }, + { "SWAP13", Instruction::SWAP13 }, + { "SWAP14", Instruction::SWAP14 }, + { "SWAP15", Instruction::SWAP15 }, + { "SWAP16", Instruction::SWAP16 }, { "CREATE", Instruction::CREATE }, { "CALL", Instruction::CALL }, { "RETURN", Instruction::RETURN }, @@ -117,94 +149,124 @@ const std::map eth::c_instructions = }; const std::map eth::c_instructionInfo = -{ // Add, Args, Ret - { Instruction::STOP, { "STOP", 0, 0, 0 } }, - { Instruction::ADD, { "ADD", 0, 2, 1 } }, - { Instruction::SUB, { "SUB", 0, 2, 1 } }, - { Instruction::MUL, { "MUL", 0, 2, 1 } }, - { Instruction::DIV, { "DIV", 0, 2, 1 } }, - { Instruction::SDIV, { "SDIV", 0, 2, 1 } }, - { Instruction::MOD, { "MOD", 0, 2, 1 } }, - { Instruction::SMOD, { "SMOD", 0, 2, 1 } }, - { Instruction::EXP, { "EXP", 0, 2, 1 } }, - { Instruction::NEG, { "NEG", 0, 1, 1 } }, - { Instruction::LT, { "LT", 0, 2, 1 } }, - { Instruction::GT, { "GT", 0, 2, 1 } }, - { Instruction::SLT, { "SLT", 0, 2, 1 } }, - { Instruction::SGT, { "SGT", 0, 2, 1 } }, - { Instruction::EQ, { "EQ", 0, 2, 1 } }, - { Instruction::NOT, { "NOT", 0, 1, 1 } }, - { Instruction::AND, { "AND", 0, 2, 1 } }, - { Instruction::OR, { "OR", 0, 2, 1 } }, - { Instruction::XOR, { "XOR", 0, 2, 1 } }, - { Instruction::BYTE, { "BYTE", 0, 2, 1 } }, - { Instruction::SHA3, { "SHA3", 0, 2, 1 } }, - { Instruction::ADDRESS, { "ADDRESS", 0, 0, 1 } }, - { Instruction::BALANCE, { "BALANCE", 0, 1, 1 } }, - { Instruction::ORIGIN, { "ORIGIN", 0, 0, 1 } }, - { Instruction::CALLER, { "CALLER", 0, 0, 1 } }, - { Instruction::CALLVALUE, { "CALLVALUE", 0, 0, 1 } }, - { Instruction::CALLDATALOAD, { "CALLDATALOAD", 0, 1, 1 } }, - { Instruction::CALLDATASIZE, { "CALLDATASIZE", 0, 0, 1 } }, - { Instruction::CALLDATACOPY, { "CALLDATACOPY", 0, 3, 0 } }, - { Instruction::CODESIZE, { "CODESIZE", 0, 0, 1 } }, - { Instruction::CODECOPY, { "CODECOPY", 0, 3, 0 } }, - { Instruction::GASPRICE, { "GASPRICE", 0, 0, 1 } }, - { Instruction::PREVHASH, { "PREVHASH", 0, 0, 1 } }, - { Instruction::COINBASE, { "COINBASE", 0, 0, 1 } }, - { Instruction::TIMESTAMP, { "TIMESTAMP", 0, 0, 1 } }, - { Instruction::NUMBER, { "NUMBER", 0, 0, 1 } }, - { Instruction::DIFFICULTY, { "DIFFICULTY", 0, 0, 1 } }, - { Instruction::GASLIMIT, { "GASLIMIT", 0, 0, 1 } }, - { Instruction::POP, { "POP", 0, 1, 0 } }, - { Instruction::DUP, { "DUP", 0, 1, 2 } }, - { Instruction::SWAP, { "SWAP", 0, 2, 2 } }, - { Instruction::MLOAD, { "MLOAD", 0, 1, 1 } }, - { Instruction::MSTORE, { "MSTORE", 0, 2, 0 } }, - { Instruction::MSTORE8, { "MSTORE8", 0, 2, 0 } }, - { Instruction::SLOAD, { "SLOAD", 0, 1, 1 } }, - { Instruction::SSTORE, { "SSTORE", 0, 2, 0 } }, - { Instruction::JUMP, { "JUMP", 0, 1, 0 } }, - { Instruction::JUMPI, { "JUMPI", 0, 2, 0 } }, - { Instruction::PC, { "PC", 0, 0, 1 } }, - { Instruction::MSIZE, { "MSIZE", 0, 0, 1 } }, - { Instruction::GAS, { "GAS", 0, 0, 1 } }, - { Instruction::PUSH1, { "PUSH1", 1, 0, 1 } }, - { Instruction::PUSH2, { "PUSH2", 2, 0, 1 } }, - { Instruction::PUSH3, { "PUSH3", 3, 0, 1 } }, - { Instruction::PUSH4, { "PUSH4", 4, 0, 1 } }, - { Instruction::PUSH5, { "PUSH5", 5, 0, 1 } }, - { Instruction::PUSH6, { "PUSH6", 6, 0, 1 } }, - { Instruction::PUSH7, { "PUSH7", 7, 0, 1 } }, - { Instruction::PUSH8, { "PUSH8", 8, 0, 1 } }, - { Instruction::PUSH9, { "PUSH9", 9, 0, 1 } }, - { Instruction::PUSH10, { "PUSH10", 10, 0, 1 } }, - { Instruction::PUSH11, { "PUSH11", 11, 0, 1 } }, - { Instruction::PUSH12, { "PUSH12", 12, 0, 1 } }, - { Instruction::PUSH13, { "PUSH13", 13, 0, 1 } }, - { Instruction::PUSH14, { "PUSH14", 14, 0, 1 } }, - { Instruction::PUSH15, { "PUSH15", 15, 0, 1 } }, - { Instruction::PUSH16, { "PUSH16", 16, 0, 1 } }, - { Instruction::PUSH17, { "PUSH17", 17, 0, 1 } }, - { Instruction::PUSH18, { "PUSH18", 18, 0, 1 } }, - { Instruction::PUSH19, { "PUSH19", 19, 0, 1 } }, - { Instruction::PUSH20, { "PUSH20", 20, 0, 1 } }, - { Instruction::PUSH21, { "PUSH21", 21, 0, 1 } }, - { Instruction::PUSH22, { "PUSH22", 22, 0, 1 } }, - { Instruction::PUSH23, { "PUSH23", 23, 0, 1 } }, - { Instruction::PUSH24, { "PUSH24", 24, 0, 1 } }, - { Instruction::PUSH25, { "PUSH25", 25, 0, 1 } }, - { Instruction::PUSH26, { "PUSH26", 26, 0, 1 } }, - { Instruction::PUSH27, { "PUSH27", 27, 0, 1 } }, - { Instruction::PUSH28, { "PUSH28", 28, 0, 1 } }, - { Instruction::PUSH29, { "PUSH29", 29, 0, 1 } }, - { Instruction::PUSH30, { "PUSH30", 30, 0, 1 } }, - { Instruction::PUSH31, { "PUSH31", 31, 0, 1 } }, - { Instruction::PUSH32, { "PUSH32", 32, 0, 1 } }, - { Instruction::CREATE, { "CREATE", 0, 3, 1 } }, - { Instruction::CALL, { "CALL", 0, 7, 1 } }, - { Instruction::RETURN, { "RETURN", 0, 2, 0 } }, - { Instruction::SUICIDE, { "SUICIDE", 0, 1, 0} } +{ // Add, Args, Ret + { Instruction::STOP, { "STOP", 0, 0, 0 } }, + { Instruction::ADD, { "ADD", 0, 2, 1 } }, + { Instruction::SUB, { "SUB", 0, 2, 1 } }, + { Instruction::MUL, { "MUL", 0, 2, 1 } }, + { Instruction::DIV, { "DIV", 0, 2, 1 } }, + { Instruction::SDIV, { "SDIV", 0, 2, 1 } }, + { Instruction::MOD, { "MOD", 0, 2, 1 } }, + { Instruction::SMOD, { "SMOD", 0, 2, 1 } }, + { Instruction::EXP, { "EXP", 0, 2, 1 } }, + { Instruction::NEG, { "NEG", 0, 1, 1 } }, + { Instruction::LT, { "LT", 0, 2, 1 } }, + { Instruction::GT, { "GT", 0, 2, 1 } }, + { Instruction::SLT, { "SLT", 0, 2, 1 } }, + { Instruction::SGT, { "SGT", 0, 2, 1 } }, + { Instruction::EQ, { "EQ", 0, 2, 1 } }, + { Instruction::NOT, { "NOT", 0, 1, 1 } }, + { Instruction::AND, { "AND", 0, 2, 1 } }, + { Instruction::OR, { "OR", 0, 2, 1 } }, + { Instruction::XOR, { "XOR", 0, 2, 1 } }, + { Instruction::BYTE, { "BYTE", 0, 2, 1 } }, + { Instruction::SHA3, { "SHA3", 0, 2, 1 } }, + { Instruction::ADDRESS, { "ADDRESS", 0, 0, 1 } }, + { Instruction::BALANCE, { "BALANCE", 0, 1, 1 } }, + { Instruction::ORIGIN, { "ORIGIN", 0, 0, 1 } }, + { Instruction::CALLER, { "CALLER", 0, 0, 1 } }, + { Instruction::CALLVALUE, { "CALLVALUE", 0, 0, 1 } }, + { Instruction::CALLDATALOAD,{ "CALLDATALOAD", 0, 1, 1 } }, + { Instruction::CALLDATASIZE,{ "CALLDATASIZE", 0, 0, 1 } }, + { Instruction::CALLDATACOPY,{ "CALLDATACOPY", 0, 3, 0 } }, + { Instruction::CODESIZE, { "CODESIZE", 0, 0, 1 } }, + { Instruction::CODECOPY, { "CODECOPY", 0, 3, 0 } }, + { Instruction::GASPRICE, { "GASPRICE", 0, 0, 1 } }, + { Instruction::PREVHASH, { "PREVHASH", 0, 0, 1 } }, + { Instruction::COINBASE, { "COINBASE", 0, 0, 1 } }, + { Instruction::TIMESTAMP, { "TIMESTAMP", 0, 0, 1 } }, + { Instruction::NUMBER, { "NUMBER", 0, 0, 1 } }, + { Instruction::DIFFICULTY, { "DIFFICULTY", 0, 0, 1 } }, + { Instruction::GASLIMIT, { "GASLIMIT", 0, 0, 1 } }, + { Instruction::POP, { "POP", 0, 1, 0 } }, + { Instruction::MLOAD, { "MLOAD", 0, 1, 1 } }, + { Instruction::MSTORE, { "MSTORE", 0, 2, 0 } }, + { Instruction::MSTORE8, { "MSTORE8", 0, 2, 0 } }, + { Instruction::SLOAD, { "SLOAD", 0, 1, 1 } }, + { Instruction::SSTORE, { "SSTORE", 0, 2, 0 } }, + { Instruction::JUMP, { "JUMP", 0, 1, 0 } }, + { Instruction::JUMPI, { "JUMPI", 0, 2, 0 } }, + { Instruction::PC, { "PC", 0, 0, 1 } }, + { Instruction::MSIZE, { "MSIZE", 0, 0, 1 } }, + { Instruction::GAS, { "GAS", 0, 0, 1 } }, + { Instruction::PUSH1, { "PUSH1", 1, 0, 1 } }, + { Instruction::PUSH2, { "PUSH2", 2, 0, 1 } }, + { Instruction::PUSH3, { "PUSH3", 3, 0, 1 } }, + { Instruction::PUSH4, { "PUSH4", 4, 0, 1 } }, + { Instruction::PUSH5, { "PUSH5", 5, 0, 1 } }, + { Instruction::PUSH6, { "PUSH6", 6, 0, 1 } }, + { Instruction::PUSH7, { "PUSH7", 7, 0, 1 } }, + { Instruction::PUSH8, { "PUSH8", 8, 0, 1 } }, + { Instruction::PUSH9, { "PUSH9", 9, 0, 1 } }, + { Instruction::PUSH10, { "PUSH10", 10, 0, 1 } }, + { Instruction::PUSH11, { "PUSH11", 11, 0, 1 } }, + { Instruction::PUSH12, { "PUSH12", 12, 0, 1 } }, + { Instruction::PUSH13, { "PUSH13", 13, 0, 1 } }, + { Instruction::PUSH14, { "PUSH14", 14, 0, 1 } }, + { Instruction::PUSH15, { "PUSH15", 15, 0, 1 } }, + { Instruction::PUSH16, { "PUSH16", 16, 0, 1 } }, + { Instruction::PUSH17, { "PUSH17", 17, 0, 1 } }, + { Instruction::PUSH18, { "PUSH18", 18, 0, 1 } }, + { Instruction::PUSH19, { "PUSH19", 19, 0, 1 } }, + { Instruction::PUSH20, { "PUSH20", 20, 0, 1 } }, + { Instruction::PUSH21, { "PUSH21", 21, 0, 1 } }, + { Instruction::PUSH22, { "PUSH22", 22, 0, 1 } }, + { Instruction::PUSH23, { "PUSH23", 23, 0, 1 } }, + { Instruction::PUSH24, { "PUSH24", 24, 0, 1 } }, + { Instruction::PUSH25, { "PUSH25", 25, 0, 1 } }, + { Instruction::PUSH26, { "PUSH26", 26, 0, 1 } }, + { Instruction::PUSH27, { "PUSH27", 27, 0, 1 } }, + { Instruction::PUSH28, { "PUSH28", 28, 0, 1 } }, + { Instruction::PUSH29, { "PUSH29", 29, 0, 1 } }, + { Instruction::PUSH30, { "PUSH30", 30, 0, 1 } }, + { Instruction::PUSH31, { "PUSH31", 31, 0, 1 } }, + { Instruction::PUSH32, { "PUSH32", 32, 0, 1 } }, + { Instruction::DUP1, { "DUP1", 0, 1, 2 } }, + { Instruction::DUP2, { "DUP2", 0, 2, 3 } }, + { Instruction::DUP3, { "DUP3", 0, 3, 4 } }, + { Instruction::DUP4, { "DUP4", 0, 4, 5 } }, + { Instruction::DUP5, { "DUP5", 0, 5, 6 } }, + { Instruction::DUP6, { "DUP6", 0, 6, 7 } }, + { Instruction::DUP7, { "DUP7", 0, 7, 8 } }, + { Instruction::DUP8, { "DUP8", 0, 8, 9 } }, + { Instruction::DUP9, { "DUP9", 0, 9, 10 } }, + { Instruction::DUP10, { "DUP10", 0, 10, 11 } }, + { Instruction::DUP11, { "DUP11", 0, 11, 12 } }, + { Instruction::DUP12, { "DUP12", 0, 12, 13 } }, + { Instruction::DUP13, { "DUP13", 0, 13, 14 } }, + { Instruction::DUP14, { "DUP14", 0, 14, 15 } }, + { Instruction::DUP15, { "DUP15", 0, 15, 16 } }, + { Instruction::DUP16, { "DUP16", 0, 16, 17 } }, + { Instruction::SWAP1, { "SWAP1", 0, 2, 2 } }, + { Instruction::SWAP2, { "SWAP2", 0, 3, 3 } }, + { Instruction::SWAP3, { "SWAP3", 0, 4, 4 } }, + { Instruction::SWAP4, { "SWAP4", 0, 5, 5 } }, + { Instruction::SWAP5, { "SWAP5", 0, 6, 6 } }, + { Instruction::SWAP6, { "SWAP6", 0, 7, 7 } }, + { Instruction::SWAP7, { "SWAP7", 0, 8, 8 } }, + { Instruction::SWAP8, { "SWAP8", 0, 9, 9 } }, + { Instruction::SWAP9, { "SWAP9", 0, 10, 10 } }, + { Instruction::SWAP10, { "SWAP10", 0, 11, 11 } }, + { Instruction::SWAP11, { "SWAP11", 0, 12, 12 } }, + { Instruction::SWAP12, { "SWAP12", 0, 13, 13 } }, + { Instruction::SWAP13, { "SWAP13", 0, 14, 14 } }, + { Instruction::SWAP14, { "SWAP14", 0, 15, 15 } }, + { Instruction::SWAP15, { "SWAP15", 0, 16, 16 } }, + { Instruction::SWAP16, { "SWAP16", 0, 17, 17 } }, + { Instruction::CREATE, { "CREATE", 0, 3, 1 } }, + { Instruction::CALL, { "CALL", 0, 7, 1 } }, + { Instruction::RETURN, { "RETURN", 0, 2, 0 } }, + { Instruction::SUICIDE, { "SUICIDE", 0, 1, 0} } }; string eth::disassemble(bytes const& _mem) diff --git a/libevmface/Instruction.h b/libevmface/Instruction.h index 4e0fe82b8..03f2b9d7d 100644 --- a/libevmface/Instruction.h +++ b/libevmface/Instruction.h @@ -79,8 +79,8 @@ enum class Instruction: uint8_t GASLIMIT, POP = 0x50, - DUP, - SWAP, + NOP1, + NOP2, MLOAD, MSTORE, MSTORE8, @@ -125,6 +125,40 @@ enum class Instruction: uint8_t PUSH31, PUSH32, + DUP1 = 0x80, + DUP2, + DUP3, + DUP4, + DUP5, + DUP6, + DUP7, + DUP8, + DUP9, + DUP10, + DUP11, + DUP12, + DUP13, + DUP14, + DUP15, + DUP16, + + SWAP1 = 0x90, + SWAP2, + SWAP3, + SWAP4, + SWAP5, + SWAP6, + SWAP7, + SWAP8, + SWAP9, + SWAP10, + SWAP11, + SWAP12, + SWAP13, + SWAP14, + SWAP15, + SWAP16, + CREATE = 0xf0, CALL, RETURN, diff --git a/liblll/CodeFragment.cpp b/liblll/CodeFragment.cpp index bc7fa6153..09f49bc73 100644 --- a/liblll/CodeFragment.cpp +++ b/liblll/CodeFragment.cpp @@ -313,7 +313,7 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s) ++ii; } m_asm.append((u256)data.size()); - m_asm.append(Instruction::DUP); + m_asm.append(Instruction::DUP1); m_asm.append(data); m_asm.append(pos.m_asm, 1); m_asm.append(Instruction::CODECOPY); @@ -494,7 +494,7 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s) requireDeposit(1, 1); auto subPush = m_asm.appendSubSize(code[0].assembly(ns)); - m_asm.append(Instruction::DUP); + m_asm.append(Instruction::DUP1); if (code.size() == 3) { requireDeposit(2, 1); @@ -502,7 +502,7 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s) m_asm.append(Instruction::LT); m_asm.append(Instruction::NOT); m_asm.append(Instruction::MUL); - m_asm.append(Instruction::DUP); + m_asm.append(Instruction::DUP1); } m_asm.append(subPush); m_asm.append(code[1].m_asm, 1); From 7581de5b087a2d6c5abac3e7df10d6eb51f179eb Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 21 Aug 2014 16:14:12 +0200 Subject: [PATCH 094/223] SWAP & DUP --- liblll/CodeFragment.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/liblll/CodeFragment.cpp b/liblll/CodeFragment.cpp index 09f49bc73..a9128e29c 100644 --- a/liblll/CodeFragment.cpp +++ b/liblll/CodeFragment.cpp @@ -77,7 +77,7 @@ CodeFragment::CodeFragment(sp::utree const& _t, CompilerState& _s, bool _allowAS string us = boost::algorithm::to_upper_copy(s); if (_allowASM && c_instructions.count(us)) m_asm.append(c_instructions.at(us)); - if (_s.defs.count(s)) + else if (_s.defs.count(s)) m_asm.append(_s.defs.at(s).m_asm); else if (_s.args.count(s)) m_asm.append(_s.args.at(s).m_asm); From c465368bb2585c0c7c79cd4101634159b39fcdd0 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 21 Aug 2014 16:15:12 +0200 Subject: [PATCH 095/223] Version bump. --- libethential/Common.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libethential/Common.cpp b/libethential/Common.cpp index 7244107c2..13cea2175 100644 --- a/libethential/Common.cpp +++ b/libethential/Common.cpp @@ -27,6 +27,6 @@ using namespace eth; namespace eth { -char const* EthVersion = "0.6.3"; +char const* EthVersion = "0.6.4"; } From 4807d43be686cc1a4e2d9a84c5ae6668c3869044 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 21 Aug 2014 17:22:46 +0200 Subject: [PATCH 096/223] Mining improvements. --- LICENSE | 1 - alethzero/Main.ui | 17 +++++++++++++---- alethzero/MainWin.cpp | 5 +++++ alethzero/MainWin.h | 1 + libethcore/BlockInfo.cpp | 2 +- libethcore/Dagger.cpp | 8 ++++++-- libethcore/Dagger.h | 2 +- libethereum/Client.cpp | 2 +- libethereum/Client.h | 4 ++++ libethereum/State.cpp | 4 ++-- libethereum/State.h | 2 +- 11 files changed, 35 insertions(+), 13 deletions(-) diff --git a/LICENSE b/LICENSE index 024073c02..5055c1434 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,3 @@ -/bin/bash: wq: command not found Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. diff --git a/alethzero/Main.ui b/alethzero/Main.ui index 73b45c43d..f233ed244 100644 --- a/alethzero/Main.ui +++ b/alethzero/Main.ui @@ -185,8 +185,9 @@ - - + + + @@ -1672,10 +1673,10 @@ font-size: 14pt - true + true - &Use Private Chain... + &Use Private Chain... @@ -1686,6 +1687,14 @@ font-size: 14pt &Enable LLL &Optimizer + + + true + + + Reserved Debug 1 + + diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index 08cda9ece..84f1af1ca 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -835,6 +835,11 @@ static bool transactionMatch(string const& _f, Transaction const& _t) return false; } +void Main::on_turboMining_triggered() +{ + m_client->setTurboMining(ui->turboMining->isChecked()); +} + void Main::refreshBlockChain() { cwatch << "refreshBlockChain()"; diff --git a/alethzero/MainWin.h b/alethzero/MainWin.h index da77fb8e1..d124b1fad 100644 --- a/alethzero/MainWin.h +++ b/alethzero/MainWin.h @@ -140,6 +140,7 @@ private slots: void on_refresh_triggered(); void on_usePrivate_triggered(); void on_enableOptimizer_triggered(); + void on_turboMining_triggered(); signals: void poll(); diff --git a/libethcore/BlockInfo.cpp b/libethcore/BlockInfo.cpp index a8af6fe21..1f845ad20 100644 --- a/libethcore/BlockInfo.cpp +++ b/libethcore/BlockInfo.cpp @@ -30,7 +30,7 @@ using namespace std; using namespace eth; -u256 eth::c_genesisDifficulty = (u256)1 << 12; +u256 eth::c_genesisDifficulty = (u256)1 << 17; BlockInfo::BlockInfo(): timestamp(Invalid256) { diff --git a/libethcore/Dagger.cpp b/libethcore/Dagger.cpp index 1651a1941..4b5841d82 100644 --- a/libethcore/Dagger.cpp +++ b/libethcore/Dagger.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include "Dagger.h" @@ -36,7 +37,7 @@ namespace eth #if FAKE_DAGGER -MineInfo Dagger::mine(h256& o_solution, h256 const& _root, u256 const& _difficulty, uint _msTimeout, bool const& _continue) +MineInfo Dagger::mine(h256& o_solution, h256 const& _root, u256 const& _difficulty, uint _msTimeout, bool _continue, bool _turbo) { MineInfo ret{0.f, 1e99, 0, false}; static std::mt19937_64 s_eng((time(0) + (unsigned)m_last)); @@ -49,7 +50,10 @@ MineInfo Dagger::mine(h256& o_solution, h256 const& _root, u256 const& _difficul // [--------*-------------------------] // // evaluate until we run out of time - for (auto startTime = steady_clock::now(); (steady_clock::now() - startTime) < milliseconds(_msTimeout) && _continue; s++, ret.hashes++) + auto startTime = steady_clock::now(); + if (!_turbo) + this_thread::sleep_for(chrono::milliseconds(_msTimeout * 90 / 100)); + for (; (steady_clock::now() - startTime) < milliseconds(_msTimeout) && _continue; s++, ret.hashes++) { o_solution = (h256)s; auto e = (bigint)(u256)eval(_root, o_solution); diff --git a/libethcore/Dagger.h b/libethcore/Dagger.h index e0c993ea7..e920549ce 100644 --- a/libethcore/Dagger.h +++ b/libethcore/Dagger.h @@ -52,7 +52,7 @@ public: static h256 eval(h256 const& _root, h256 const& _nonce) { h256 b[2] = { _root, _nonce }; return sha3(bytesConstRef((byte const*)&b[0], 64)); } static bool verify(h256 const& _root, h256 const& _nonce, u256 const& _difficulty) { return (bigint)(u256)eval(_root, _nonce) <= (bigint(1) << 256) / _difficulty; } - MineInfo mine(h256& o_solution, h256 const& _root, u256 const& _difficulty, uint _msTimeout = 100, bool const& _continue = bool(true)); + MineInfo mine(h256& o_solution, h256 const& _root, u256 const& _difficulty, uint _msTimeout = 100, bool _continue = true, bool _turbo = false); h256 m_last; }; diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index 86c26d1db..d9798e88e 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -446,7 +446,7 @@ void Client::work(bool _justQueue) m_restartMining = false; // Mine for a while. - MineInfo mineInfo = m_postMine.mine(100); + MineInfo mineInfo = m_postMine.mine(100, m_turboMining); m_mineProgress.best = min(m_mineProgress.best, mineInfo.best); m_mineProgress.current = mineInfo.best; diff --git a/libethereum/Client.h b/libethereum/Client.h index cb532d38f..f029e1d7f 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -304,6 +304,9 @@ public: /// Clears pending transactions. Just for debug use. void clearPending(); + void setTurboMining(bool _enable = true) { m_turboMining = _enable; } + bool turboMining() const { return m_turboMining; } + private: /// Ensure the worker thread is running. Needed for blockchain maintenance & mining. void ensureWorking(); @@ -353,6 +356,7 @@ private: bool m_paranoia = false; bool m_doMine = false; ///< Are we supposed to be mining? + bool m_turboMining = false; ///< Don't squander all of our time mining actually just sleeping. bool m_forceMining = false; ///< Mine even when there are no transactions pending? MineProgress m_mineProgress; std::list m_mineHistory; diff --git a/libethereum/State.cpp b/libethereum/State.cpp index 14db5c038..29f125a98 100644 --- a/libethereum/State.cpp +++ b/libethereum/State.cpp @@ -748,13 +748,13 @@ void State::commitToMine(BlockChain const& _bc) m_currentBlock.parentHash = m_previousBlock.hash; } -MineInfo State::mine(uint _msTimeout) +MineInfo State::mine(uint _msTimeout, bool _turbo) { // Update difficulty according to timestamp. m_currentBlock.difficulty = m_currentBlock.calculateDifficulty(m_previousBlock); // TODO: Miner class that keeps dagger between mine calls (or just non-polling mining). - auto ret = m_dagger.mine(/*out*/m_currentBlock.nonce, m_currentBlock.headerHashWithoutNonce(), m_currentBlock.difficulty, _msTimeout); + auto ret = m_dagger.mine(/*out*/m_currentBlock.nonce, m_currentBlock.headerHashWithoutNonce(), m_currentBlock.difficulty, _msTimeout, true, _turbo); if (!ret.completed) m_currentBytes.clear(); diff --git a/libethereum/State.h b/libethereum/State.h index da8e099d1..80380149f 100644 --- a/libethereum/State.h +++ b/libethereum/State.h @@ -157,7 +157,7 @@ public: /// This function is thread-safe. You can safely have other interactions with this object while it is happening. /// @param _msTimeout Timeout before return in milliseconds. /// @returns Information on the mining. - MineInfo mine(uint _msTimeout = 1000); + MineInfo mine(uint _msTimeout = 1000, bool _turbo = false); /** Commit to DB and build the final block if the previous call to mine()'s result is completion. * Typically looks like: From eff34d1ab00185d05ea66e8e919c93342d3de363 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 21 Aug 2014 18:44:14 +0200 Subject: [PATCH 097/223] ADDMOD, MULMOD, unsigned->bigint VM fixes. --- eth/main.cpp | 6 +++--- libethereum/Executive.cpp | 2 +- libevm/ExtVMFace.h | 2 +- libevm/VM.h | 43 +++++++++++++++++++++++--------------- libevmface/Instruction.cpp | 6 ++++-- libevmface/Instruction.h | 6 +++--- 6 files changed, 38 insertions(+), 27 deletions(-) diff --git a/eth/main.cpp b/eth/main.cpp index 2f4f7b571..f0af271d0 100644 --- a/eth/main.cpp +++ b/eth/main.cpp @@ -598,7 +598,7 @@ int main(int argc, char** argv) e.setup(&r); if (format == "pretty") - e.go([&](uint64_t steps, Instruction instr, unsigned newMemSize, bigint gasCost, void* vvm, void const* vextVM) + e.go([&](uint64_t steps, Instruction instr, bigint newMemSize, bigint gasCost, void* vvm, void const* vextVM) { eth::VM* vm = (VM*)vvm; eth::ExtVM const* ext = (ExtVM const*)vextVM; @@ -612,14 +612,14 @@ int main(int argc, char** argv) f << dec << ext->level << " | " << ext->myAddress << " | #" << steps << " | " << hex << setw(4) << setfill('0') << vm->curPC() << " : " << c_instructionInfo.at(instr).name << " | " << dec << vm->gas() << " | -" << dec << gasCost << " | " << newMemSize << "x32"; }); else if (format == "standard") - e.go([&](uint64_t, Instruction instr, unsigned, bigint, void* vvm, void const* vextVM) + e.go([&](uint64_t, Instruction instr, bigint, bigint, void* vvm, void const* vextVM) { eth::VM* vm = (VM*)vvm; eth::ExtVM const* ext = (ExtVM const*)vextVM; f << ext->myAddress << " " << hex << toHex(eth::toCompactBigEndian(vm->curPC(), 1)) << " " << hex << toHex(eth::toCompactBigEndian((int)(byte)instr, 1)) << " " << hex << toHex(eth::toCompactBigEndian((uint64_t)vm->gas(), 1)) << endl; }); else if (format == "standard+") - e.go([&](uint64_t, Instruction instr, unsigned, bigint, void* vvm, void const* vextVM) + e.go([&](uint64_t, Instruction instr, bigint, bigint, void* vvm, void const* vextVM) { eth::VM* vm = (VM*)vvm; eth::ExtVM const* ext = (ExtVM const*)vextVM; diff --git a/libethereum/Executive.cpp b/libethereum/Executive.cpp index 9096b5d64..6562a0cf8 100644 --- a/libethereum/Executive.cpp +++ b/libethereum/Executive.cpp @@ -144,7 +144,7 @@ bool Executive::create(Address _sender, u256 _endowment, u256 _gasPrice, u256 _g OnOpFunc Executive::simpleTrace() { - return [](uint64_t steps, Instruction inst, unsigned newMemSize, bigint gasCost, void* voidVM, void const* voidExt) + return [](uint64_t steps, Instruction inst, bigint newMemSize, bigint gasCost, void* voidVM, void const* voidExt) { ExtVM const& ext = *(ExtVM const*)voidExt; VM& vm = *(VM*)voidVM; diff --git a/libevm/ExtVMFace.h b/libevm/ExtVMFace.h index 4890a5979..e53d90734 100644 --- a/libevm/ExtVMFace.h +++ b/libevm/ExtVMFace.h @@ -83,6 +83,6 @@ public: std::set

    suicides; ///< Any accounts that have suicided. }; -typedef std::function OnOpFunc; +typedef std::function OnOpFunc; } diff --git a/libevm/VM.h b/libevm/VM.h index df89c0b42..7a3c6f6e1 100644 --- a/libevm/VM.h +++ b/libevm/VM.h @@ -98,7 +98,7 @@ template eth::bytesConstRef eth::VM::go(Ext& _ext, OnOpFunc const& _ // FEES... bigint runGas = c_stepGas; - unsigned newTempSize = (unsigned)m_temp.size(); + bigint newTempSize = m_temp.size(); switch (inst) { case Instruction::STOP: @@ -126,32 +126,32 @@ template eth::bytesConstRef eth::VM::go(Ext& _ext, OnOpFunc const& _ // These all operate on memory and therefore potentially expand it: case Instruction::MSTORE: require(2); - newTempSize = (unsigned)m_stack.back() + 32; + newTempSize = m_stack.back() + 32; break; case Instruction::MSTORE8: require(2); - newTempSize = (unsigned)m_stack.back() + 1; + newTempSize = m_stack.back() + 1; break; case Instruction::MLOAD: require(1); - newTempSize = (unsigned)m_stack.back() + 32; + newTempSize = m_stack.back() + 32; break; case Instruction::RETURN: require(2); - newTempSize = (unsigned)m_stack.back() + (unsigned)m_stack[m_stack.size() - 2]; + newTempSize = m_stack.back() + m_stack[m_stack.size() - 2]; break; case Instruction::SHA3: require(2); runGas = c_sha3Gas; - newTempSize = (unsigned)m_stack.back() + (unsigned)m_stack[m_stack.size() - 2]; + newTempSize = m_stack.back() + m_stack[m_stack.size() - 2]; break; case Instruction::CALLDATACOPY: require(3); - newTempSize = (unsigned)m_stack.back() + (unsigned)m_stack[m_stack.size() - 3]; + newTempSize = m_stack.back() + m_stack[m_stack.size() - 3]; break; case Instruction::CODECOPY: require(3); - newTempSize = (unsigned)m_stack.back() + (unsigned)m_stack[m_stack.size() - 3]; + newTempSize = m_stack.back() + m_stack[m_stack.size() - 3]; break; case Instruction::BALANCE: @@ -160,15 +160,15 @@ template eth::bytesConstRef eth::VM::go(Ext& _ext, OnOpFunc const& _ case Instruction::CALL: require(7); - runGas = c_callGas + (unsigned)m_stack[m_stack.size() - 1]; - newTempSize = std::max((unsigned)m_stack[m_stack.size() - 6] + (unsigned)m_stack[m_stack.size() - 7], (unsigned)m_stack[m_stack.size() - 4] + (unsigned)m_stack[m_stack.size() - 5]); + runGas = c_callGas + m_stack[m_stack.size() - 1]; + newTempSize = std::max(m_stack[m_stack.size() - 6] + m_stack[m_stack.size() - 7], m_stack[m_stack.size() - 4] + m_stack[m_stack.size() - 5]); break; case Instruction::CREATE: { require(3); - unsigned inOff = (unsigned)m_stack[m_stack.size() - 2]; - unsigned inSize = (unsigned)m_stack[m_stack.size() - 3]; + auto inOff = m_stack[m_stack.size() - 2]; + auto inSize = m_stack[m_stack.size() - 3]; newTempSize = inOff + inSize; runGas = c_createGas; break; @@ -183,7 +183,7 @@ template eth::bytesConstRef eth::VM::go(Ext& _ext, OnOpFunc const& _ runGas += c_memoryGas * (newTempSize - m_temp.size()) / 32; if (_onOp) - _onOp(osteps - _steps - 1, inst, newTempSize > m_temp.size() ? (newTempSize - m_temp.size()) / 32 : 0, runGas, this, &_ext); + _onOp(osteps - _steps - 1, inst, newTempSize > m_temp.size() ? (newTempSize - m_temp.size()) / 32 : bigint(0), runGas, this, &_ext); if (m_gas < runGas) { @@ -195,7 +195,7 @@ template eth::bytesConstRef eth::VM::go(Ext& _ext, OnOpFunc const& _ m_gas = (u256)((bigint)m_gas - runGas); if (newTempSize > m_temp.size()) - m_temp.resize(newTempSize); + m_temp.resize((size_t)newTempSize); // EXECUTE... switch (inst) @@ -299,6 +299,18 @@ template eth::bytesConstRef eth::VM::go(Ext& _ext, OnOpFunc const& _ m_stack[m_stack.size() - 2] = m_stack.back() < 32 ? (m_stack[m_stack.size() - 2] >> (uint)(8 * (31 - m_stack.back()))) & 0xff : 0; m_stack.pop_back(); break; + case Instruction::ADDMOD: + require(3); + m_stack[m_stack.size() - 3] = u256((bigint(m_stack.back()) + bigint(m_stack[m_stack.size() - 2])) % m_stack[m_stack.size() - 3]); + m_stack.pop_back(); + m_stack.pop_back(); + break; + case Instruction::MULMOD: + require(3); + m_stack[m_stack.size() - 3] = u256((bigint(m_stack.back()) * bigint(m_stack[m_stack.size() - 2])) % m_stack[m_stack.size() - 3]); + m_stack.pop_back(); + m_stack.pop_back(); + break; case Instruction::SHA3: { require(2); @@ -610,9 +622,6 @@ template eth::bytesConstRef eth::VM::go(Ext& _ext, OnOpFunc const& _ } case Instruction::STOP: return bytesConstRef(); - case Instruction::NOP1: - case Instruction::NOP2: - break; default: throw BadInstruction(); } diff --git a/libevmface/Instruction.cpp b/libevmface/Instruction.cpp index 7f2f51c67..b42244c3e 100644 --- a/libevmface/Instruction.cpp +++ b/libevmface/Instruction.cpp @@ -47,6 +47,8 @@ const std::map eth::c_instructions = { "OR", Instruction::OR }, { "XOR", Instruction::XOR }, { "BYTE", Instruction::BYTE }, + { "ADDMOD", Instruction::ADDMOD }, + { "MULMOD", Instruction::MULMOD }, { "SHA3", Instruction::SHA3 }, { "ADDRESS", Instruction::ADDRESS }, { "BALANCE", Instruction::BALANCE }, @@ -66,8 +68,6 @@ const std::map eth::c_instructions = { "DIFFICULTY", Instruction::DIFFICULTY }, { "GASLIMIT", Instruction::GASLIMIT }, { "POP", Instruction::POP }, - { "NOP1", Instruction::NOP1 }, - { "NOP2", Instruction::NOP2 }, { "MLOAD", Instruction::MLOAD }, { "MSTORE", Instruction::MSTORE }, { "MSTORE8", Instruction::MSTORE8 }, @@ -170,6 +170,8 @@ const std::map eth::c_instructionInfo = { Instruction::OR, { "OR", 0, 2, 1 } }, { Instruction::XOR, { "XOR", 0, 2, 1 } }, { Instruction::BYTE, { "BYTE", 0, 2, 1 } }, + { Instruction::ADDMOD, { "ADDMOD", 0, 3, 1 } }, + { Instruction::MULMOD, { "MULMOD", 0, 3, 1 } }, { Instruction::SHA3, { "SHA3", 0, 2, 1 } }, { Instruction::ADDRESS, { "ADDRESS", 0, 0, 1 } }, { Instruction::BALANCE, { "BALANCE", 0, 1, 1 } }, diff --git a/libevmface/Instruction.h b/libevmface/Instruction.h index 03f2b9d7d..bf107a860 100644 --- a/libevmface/Instruction.h +++ b/libevmface/Instruction.h @@ -56,6 +56,8 @@ enum class Instruction: uint8_t OR, XOR, BYTE, + ADDMOD, + MULMOD, SHA3 = 0x20, @@ -79,9 +81,7 @@ enum class Instruction: uint8_t GASLIMIT, POP = 0x50, - NOP1, - NOP2, - MLOAD, + MLOAD = 0x53, MSTORE, MSTORE8, SLOAD, From 27a709db0dbb00a107f135ead5a3a768e887428f Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 21 Aug 2014 18:45:56 +0200 Subject: [PATCH 098/223] Build fixes. --- alethzero/MainWin.cpp | 2 +- alethzero/MainWin.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index 84f1af1ca..af0942492 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -1220,7 +1220,7 @@ void Main::populateDebugger(eth::bytesConstRef _r) bytesConstRef lastData; h256 lastHash; h256 lastDataHash; - auto onOp = [&](uint64_t steps, Instruction inst, unsigned newMemSize, eth::bigint gasCost, void* voidVM, void const* voidExt) + auto onOp = [&](uint64_t steps, Instruction inst, eth::bigint newMemSize, eth::bigint gasCost, void* voidVM, void const* voidExt) { eth::VM& vm = *(eth::VM*)voidVM; eth::ExtVM const& ext = *(eth::ExtVM const*)voidExt; diff --git a/alethzero/MainWin.h b/alethzero/MainWin.h index d124b1fad..0474bb85d 100644 --- a/alethzero/MainWin.h +++ b/alethzero/MainWin.h @@ -50,7 +50,7 @@ struct WorldState eth::Address cur; eth::u256 curPC; eth::Instruction inst; - unsigned newMemSize; + eth::bigint newMemSize; eth::u256 gas; eth::h256 code; eth::h256 callData; From 16a80db196bdbd30f80797a8a314b61b1eabafbf Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 22 Aug 2014 12:21:36 +0200 Subject: [PATCH 099/223] Avoid potential conditions where mining is not restarted. --- libethereum/Client.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index d9798e88e..2e9b035bd 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -438,12 +438,12 @@ void Client::work(bool _justQueue) else m_postMine.commitToMine(m_bc); } + m_restartMining = false; } if (m_doMine) { cwork << "MINE"; - m_restartMining = false; // Mine for a while. MineInfo mineInfo = m_postMine.mine(100, m_turboMining); From 2a85f95542c02b70661bc81ba7851d93925393bc Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 22 Aug 2014 15:54:53 +0200 Subject: [PATCH 100/223] Separate out miner thread in preparation for own class. --- libethereum/Client.cpp | 177 ++++++++++++++++++++++++----------------- libethereum/Client.h | 24 ++++-- 2 files changed, 122 insertions(+), 79 deletions(-) diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index 2e9b035bd..ceb742bf7 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -74,7 +74,21 @@ Client::Client(std::string const& _clientVersion, Address _us, std::string const if (_dbPath.size()) Defaults::setDBPath(_dbPath); m_vc.setOk(); - work(true); + work(); +} + +Client::~Client() +{ + if (m_work) + { + if (m_workState.load(std::memory_order_acquire) == Active) + m_workState.store(Deleting, std::memory_order_release); + while (m_workState.load(std::memory_order_acquire) != Deleted) + this_thread::sleep_for(chrono::milliseconds(10)); + m_work->join(); + m_work.reset(nullptr); + } + stopNetwork(); } void Client::ensureWorking() @@ -98,23 +112,9 @@ void Client::ensureWorking() })); } -Client::~Client() -{ - if (m_work) - { - if (m_workState.load(std::memory_order_acquire) == Active) - m_workState.store(Deleting, std::memory_order_release); - while (m_workState.load(std::memory_order_acquire) != Deleted) - this_thread::sleep_for(chrono::milliseconds(10)); - m_work->join(); - m_work.reset(nullptr); - } - stopNetwork(); -} - void Client::flushTransactions() { - work(true); + work(); } void Client::clearPending() @@ -127,6 +127,7 @@ void Client::clearPending() appendFromNewPending(m_postMine.bloom(i), changeds); changeds.insert(PendingChangedFilter); m_postMine = m_preMine; + m_miningStatus = Preparing; noteChanged(changeds); } @@ -302,15 +303,33 @@ void Client::connect(std::string const& _seedHost, unsigned short _port) void Client::startMining() { - ensureWorking(); + static const char* c_threadName = "miner"; + + if (!m_workMine) + m_workMine.reset(new thread([&]() + { + setThreadName(c_threadName); + m_workMineState.store(Active, std::memory_order_release); + m_miningStatus = Preparing; + while (m_workMineState.load(std::memory_order_acquire) != Deleting) + workMine(); + m_workMineState.store(Deleted, std::memory_order_release); + })); - m_doMine = true; - m_restartMining = true; + ensureWorking(); } void Client::stopMining() { - m_doMine = false; + if (m_workMine) + { + if (m_workMineState.load(std::memory_order_acquire) == Active) + m_workMineState.store(Deleting, std::memory_order_release); + while (m_workMineState.load(std::memory_order_acquire) != Deleted) + this_thread::sleep_for(chrono::milliseconds(10)); + m_workMine->join(); + } + m_workMine.reset(nullptr); } void Client::transact(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice) @@ -404,85 +423,96 @@ void Client::workNet() this_thread::sleep_for(chrono::milliseconds(1)); } -void Client::work(bool _justQueue) +void Client::workMine() { - cworkin << "WORK"; - h256Set changeds; - // Do some mining. - if (!_justQueue && (m_pendingCount || m_forceMining)) + if ((m_pendingCount || m_forceMining) && m_miningStatus != Mined) { - // TODO: Separate "Miner" object. - if (m_doMine) + if (m_miningStatus == Preparing) { - if (m_restartMining) + m_miningStatus = Mining; + { + ReadGuard l(x_stateDB); + m_mineState = m_postMine; + } + + { + Guard l(x_mineProgress); m_mineProgress.best = (double)-1; m_mineProgress.hashes = 0; m_mineProgress.ms = 0; - WriteGuard l(x_stateDB); - if (m_paranoia) + } + + if (m_paranoia) + { + if (m_mineState.amIJustParanoid(m_bc)) { - if (m_postMine.amIJustParanoid(m_bc)) - { - cnote << "I'm just paranoid. Block is fine."; - m_postMine.commitToMine(m_bc); - } - else - { - cwarn << "I'm not just paranoid. Cannot mine. Please file a bug report."; - m_doMine = false; - } + cnote << "I'm just paranoid. Block is fine."; + m_mineState.commitToMine(m_bc); } else - m_postMine.commitToMine(m_bc); + { + cwarn << "I'm not just paranoid. Cannot mine. Please file a bug report."; + } } - m_restartMining = false; + else + m_mineState.commitToMine(m_bc); } - if (m_doMine) - { - cwork << "MINE"; + cwork << "MINE"; - // Mine for a while. - MineInfo mineInfo = m_postMine.mine(100, m_turboMining); + // Mine for a while. + MineInfo mineInfo = m_mineState.mine(100, m_turboMining); + { + Guard l(x_mineProgress); m_mineProgress.best = min(m_mineProgress.best, mineInfo.best); m_mineProgress.current = mineInfo.best; m_mineProgress.requirement = mineInfo.requirement; m_mineProgress.ms += 100; m_mineProgress.hashes += mineInfo.hashes; - WriteGuard l(x_stateDB); m_mineHistory.push_back(mineInfo); - if (mineInfo.completed) + } + if (mineInfo.completed) + { + // Import block. { - // Import block. + // Must lock stateDB here since we're actually pushing out to the database. + WriteGuard l(x_stateDB); cwork << "COMPLETE MINE"; - m_postMine.completeMine(); - cwork << "CHAIN <== postSTATE"; - h256s hs = m_bc.attemptImport(m_postMine.blockData(), m_stateDB); - if (hs.size()) - { - for (auto h: hs) - appendFromNewBlock(h, changeds); - changeds.insert(ChainChangedFilter); - //changeds.insert(PendingChangedFilter); // if we mined the new block, then we've probably reset the pending transactions. - } + m_mineState.completeMine(); } - } - else - { - cwork << "SLEEP"; - this_thread::sleep_for(chrono::milliseconds(100)); + m_miningStatus = Mined; } } else { - cwork << "SLEEP"; this_thread::sleep_for(chrono::milliseconds(100)); } +} + +void Client::work() +{ + cworkin << "WORK"; + h256Set changeds; + + if (m_miningStatus == Mined) + { + cwork << "CHAIN <== postSTATE"; + h256s hs = m_bc.attemptImport(m_mineState.blockData(), m_stateDB); + if (hs.size()) + { + for (auto h: hs) + appendFromNewBlock(h, changeds); + changeds.insert(ChainChangedFilter); + //changeds.insert(PendingChangedFilter); // if we mined the new block, then we've probably reset the pending transactions. + } + m_miningStatus = Preparing; + } + // 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. @@ -507,13 +537,14 @@ void Client::work(bool _justQueue) if (newBlocks.size()) m_stateDB = db; + bool rsm = false; + cwork << "preSTATE <== CHAIN"; if (m_preMine.sync(m_bc) || m_postMine.address() != m_preMine.address()) { - if (m_doMine) - cnote << "New block on chain: Restarting mining operation."; - m_restartMining = true; // need to re-commit to mine. + cnote << "New block on chain: Restarting mining operation."; m_postMine = m_preMine; + rsm = true; changeds.insert(PendingChangedFilter); } @@ -526,11 +557,13 @@ void Client::work(bool _justQueue) appendFromNewPending(i, changeds); changeds.insert(PendingChangedFilter); - if (m_doMine) - cnote << "Additional transaction ready: Restarting mining operation."; - m_restartMining = true; + cnote << "Additional transaction ready: Restarting mining operation."; + rsm = true; } m_pendingCount = m_postMine.pending().size(); + + if (rsm) + m_miningStatus = Preparing; } cwork << "noteChanged" << changeds.size() << "items"; diff --git a/libethereum/Client.h b/libethereum/Client.h index f029e1d7f..ac0d07199 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -286,17 +286,19 @@ public: /// Get the coinbase address. Address address() const { return m_preMine.address(); } /// Start mining. + /// NOT thread-safe void startMining(); /// Stop mining. + /// NOT thread-safe void stopMining(); /// Are we mining now? - bool isMining() { return m_doMine; } + bool isMining() { return !!m_workMine; } /// Register a callback for information concerning mining. /// This callback will be in an arbitrary thread, blocking progress. JUST COPY THE DATA AND GET OUT. /// Check the progress of the mining. - MineProgress miningProgress() const { return m_mineProgress; } + MineProgress miningProgress() const { Guard l(x_mineProgress); return m_mineProgress; } /// Get and clear the mining history. - std::list miningHistory() { auto ret = m_mineHistory; m_mineHistory.clear(); return ret; } + std::list miningHistory() { Guard l(x_mineProgress); auto ret = m_mineHistory; m_mineHistory.clear(); return ret; } bool forceMining() const { return m_forceMining; } void setForceMining(bool _enable) { m_forceMining = _enable; } @@ -313,11 +315,14 @@ private: /// Do some work. Handles blockchain maintenance and mining. /// @param _justQueue If true will only processing the transaction queues. - void work(bool _justQueue = false); + void work(); /// Do some work on the network. void workNet(); + /// Do some work on the mining. + void workMine(); + /// Collate the changed filters for the bloom filter of the given pending transaction. /// Insert any filters that are activated into @a o_changed. void appendFromNewPending(h256 _pendingTransactionBloom, h256Set& o_changed) const; @@ -341,7 +346,8 @@ private: BlockChain m_bc; ///< Maintains block database. TransactionQueue m_tq; ///< Maintains a list of incoming transactions not yet in a block on the blockchain. BlockQueue m_bq; ///< Maintains a list of incoming blocks not yet on the blockchain (to be imported). - mutable boost::shared_mutex x_stateDB; // TODO: remove in favour of copying m_stateDB as required and thread-safing/copying State. Have a good think about what state objects there should be. Probably want 4 (pre, post, mining, user-visible). + // TODO: remove in favour of copying m_stateDB as required and thread-safing/copying State. Have a good think about what state objects there should be. Probably want 4 (pre, post, mining, user-visible). + mutable boost::shared_mutex x_stateDB; ///< Lock on the state DB, effectively a lock on m_postMine. OverlayDB m_stateDB; ///< Acts as the central point for the state database, so multiple States can share it. State m_preMine; ///< The present state of the client. State m_postMine; ///< The state of the client which we're mining (i.e. it'll have all the rewards added). @@ -354,13 +360,17 @@ private: std::unique_ptr m_work; ///< The work thread. std::atomic m_workState; + std::unique_ptr m_workMine;///< The work thread. + std::atomic m_workMineState; + enum MiningStatus { Preparing, Mining, Mined }; + MiningStatus m_miningStatus = Preparing;///< TODO: consider mutex/atomic variable. + State m_mineState; ///< The state on which we are mining, generally equivalent to m_postMine. bool m_paranoia = false; - bool m_doMine = false; ///< Are we supposed to be mining? bool m_turboMining = false; ///< Don't squander all of our time mining actually just sleeping. bool m_forceMining = false; ///< Mine even when there are no transactions pending? + mutable std::mutex x_mineProgress; ///< Lock for the mining progress & history. MineProgress m_mineProgress; std::list m_mineHistory; - mutable bool m_restartMining = false; mutable unsigned m_pendingCount = 0; mutable std::mutex m_filterLock; From d17e7e62ae45f39ea9bc6c0fc53672ff1270cbc4 Mon Sep 17 00:00:00 2001 From: SharpCoiner Date: Fri, 22 Aug 2014 17:14:57 +0200 Subject: [PATCH 101/223] Fix 64-bit VS2013 build Do not pull in libethereum/Client.h into QT. This is not necessary the most sane thing to do, but without it I cannot avoid getting C:/Program Files (x86)/Windows Kits/8.1/Include/um/winsock2.h(949): error : Can't concatenate non identifier tokens erros. --- alethzero/MiningView.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/alethzero/MiningView.h b/alethzero/MiningView.h index 1b4a91b9a..91e17e029 100644 --- a/alethzero/MiningView.h +++ b/alethzero/MiningView.h @@ -27,7 +27,9 @@ #include #include +#ifndef Q_MOC_RUN #include +#endif namespace eth { From 5034f3541096435bd739ec71dbb334cc6cc506c9 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 22 Aug 2014 21:44:59 +0200 Subject: [PATCH 102/223] Miner class. Version bump. --- libethcore/CommonEth.cpp | 2 +- libethential/Common.cpp | 2 +- libethereum/Client.cpp | 137 ++++++++++----------------------------- libethereum/Client.h | 47 +++++--------- libethereum/Miner.cpp | 104 +++++++++++++++++++++++++++++ libethereum/Miner.h | 121 ++++++++++++++++++++++++++++++++++ 6 files changed, 279 insertions(+), 134 deletions(-) create mode 100644 libethereum/Miner.cpp create mode 100644 libethereum/Miner.h diff --git a/libethcore/CommonEth.cpp b/libethcore/CommonEth.cpp index db1c9c636..8c0e5518e 100644 --- a/libethcore/CommonEth.cpp +++ b/libethcore/CommonEth.cpp @@ -29,7 +29,7 @@ using namespace eth; //#define ETH_ADDRESS_DEBUG 1 -const unsigned eth::c_protocolVersion = 28; +const unsigned eth::c_protocolVersion = 29; const unsigned eth::c_databaseVersion = 1; static const vector> g_units = diff --git a/libethential/Common.cpp b/libethential/Common.cpp index 13cea2175..524a8f62a 100644 --- a/libethential/Common.cpp +++ b/libethential/Common.cpp @@ -27,6 +27,6 @@ using namespace eth; namespace eth { -char const* EthVersion = "0.6.4"; +char const* EthVersion = "0.6.5"; } diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index ceb742bf7..9c4576919 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -69,7 +69,7 @@ Client::Client(std::string const& _clientVersion, Address _us, std::string const m_stateDB(State::openDB(_dbPath, !m_vc.ok() || _forceClean)), m_preMine(_us, m_stateDB), m_postMine(_us, m_stateDB), - m_workState(Deleted) + m_miner(this) { if (_dbPath.size()) Defaults::setDBPath(_dbPath); @@ -127,7 +127,7 @@ void Client::clearPending() appendFromNewPending(m_postMine.bloom(i), changeds); changeds.insert(PendingChangedFilter); m_postMine = m_preMine; - m_miningStatus = Preparing; + m_miner.restart(); noteChanged(changeds); } @@ -301,35 +301,35 @@ void Client::connect(std::string const& _seedHost, unsigned short _port) m_net->connect(_seedHost, _port); } -void Client::startMining() +void Client::onComplete(State& _s) { - static const char* c_threadName = "miner"; - - if (!m_workMine) - m_workMine.reset(new thread([&]() - { - setThreadName(c_threadName); - m_workMineState.store(Active, std::memory_order_release); - m_miningStatus = Preparing; - while (m_workMineState.load(std::memory_order_acquire) != Deleting) - workMine(); - m_workMineState.store(Deleted, std::memory_order_release); - })); - - ensureWorking(); + // Must lock stateDB here since we're actually pushing out to the database. + WriteGuard l(x_stateDB); + cwork << "COMPLETE MINE"; + _s.completeMine(); } -void Client::stopMining() +void Client::setupState(State& _s) { - if (m_workMine) { - if (m_workMineState.load(std::memory_order_acquire) == Active) - m_workMineState.store(Deleting, std::memory_order_release); - while (m_workMineState.load(std::memory_order_acquire) != Deleted) - this_thread::sleep_for(chrono::milliseconds(10)); - m_workMine->join(); + ReadGuard l(x_stateDB); + cwork << "SETUP MINE"; + _s = m_postMine; } - m_workMine.reset(nullptr); + if (m_paranoia) + { + if (_s.amIJustParanoid(m_bc)) + { + cnote << "I'm just paranoid. Block is fine."; + _s.commitToMine(m_bc); + } + else + { + cwarn << "I'm not just paranoid. Cannot mine. Please file a bug report."; + } + } + else + _s.commitToMine(m_bc); } void Client::transact(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice) @@ -423,86 +423,19 @@ void Client::workNet() this_thread::sleep_for(chrono::milliseconds(1)); } -void Client::workMine() -{ - // Do some mining. - if ((m_pendingCount || m_forceMining) && m_miningStatus != Mined) - { - // TODO: Separate "Miner" object. - if (m_miningStatus == Preparing) - { - m_miningStatus = Mining; - - { - ReadGuard l(x_stateDB); - m_mineState = m_postMine; - } - - { - Guard l(x_mineProgress); - m_mineProgress.best = (double)-1; - m_mineProgress.hashes = 0; - m_mineProgress.ms = 0; - } - - if (m_paranoia) - { - if (m_mineState.amIJustParanoid(m_bc)) - { - cnote << "I'm just paranoid. Block is fine."; - m_mineState.commitToMine(m_bc); - } - else - { - cwarn << "I'm not just paranoid. Cannot mine. Please file a bug report."; - } - } - else - m_mineState.commitToMine(m_bc); - } - - cwork << "MINE"; - - // Mine for a while. - MineInfo mineInfo = m_mineState.mine(100, m_turboMining); - - { - Guard l(x_mineProgress); - m_mineProgress.best = min(m_mineProgress.best, mineInfo.best); - m_mineProgress.current = mineInfo.best; - m_mineProgress.requirement = mineInfo.requirement; - m_mineProgress.ms += 100; - m_mineProgress.hashes += mineInfo.hashes; - m_mineHistory.push_back(mineInfo); - } - if (mineInfo.completed) - { - // Import block. - { - // Must lock stateDB here since we're actually pushing out to the database. - WriteGuard l(x_stateDB); - cwork << "COMPLETE MINE"; - m_mineState.completeMine(); - } - m_miningStatus = Mined; - } - } - else - { - this_thread::sleep_for(chrono::milliseconds(100)); - } - -} - void Client::work() { cworkin << "WORK"; h256Set changeds; - if (m_miningStatus == Mined) + if (m_miner.isComplete()) { cwork << "CHAIN <== postSTATE"; - h256s hs = m_bc.attemptImport(m_mineState.blockData(), m_stateDB); + h256s hs; + { + ReadGuard l(x_stateDB); + hs = m_bc.attemptImport(m_miner.state().blockData(), m_stateDB); + } if (hs.size()) { for (auto h: hs) @@ -510,7 +443,7 @@ void Client::work() changeds.insert(ChainChangedFilter); //changeds.insert(PendingChangedFilter); // if we mined the new block, then we've probably reset the pending transactions. } - m_miningStatus = Preparing; + m_miner.restart(); } // Synchronise state to block chain. @@ -522,7 +455,6 @@ void Client::work() // Resynchronise state with block chain & trans { WriteGuard l(x_stateDB); - cwork << "BQ ==> CHAIN ==> STATE"; OverlayDB db = m_stateDB; x_stateDB.unlock(); @@ -560,15 +492,16 @@ void Client::work() cnote << "Additional transaction ready: Restarting mining operation."; rsm = true; } - m_pendingCount = m_postMine.pending().size(); if (rsm) - m_miningStatus = Preparing; + m_miner.restart(); } cwork << "noteChanged" << changeds.size() << "items"; noteChanged(changeds); cworkout << "WORK"; + + this_thread::sleep_for(chrono::milliseconds(100)); } unsigned Client::numberOf(int _n) const diff --git a/libethereum/Client.h b/libethereum/Client.h index ac0d07199..66601c656 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -35,19 +35,11 @@ #include "TransactionQueue.h" #include "State.h" #include "PeerNetwork.h" +#include "Miner.h" namespace eth { -struct MineProgress -{ - double requirement; - double best; - double current; - uint hashes; - uint ms; -}; - class Client; enum ClientWorkState @@ -164,8 +156,10 @@ struct WorkChannel: public LogChannel { static const char* name() { return "-W-" /** * @brief Main API hub for interfacing with Ethereum. */ -class Client +class Client: public MinerHost { + friend class Miner; + public: /// Constructor. explicit Client(std::string const& _clientVersion, Address _us = Address(), std::string const& _dbPath = std::string(), bool _forceClean = false); @@ -278,7 +272,7 @@ public: // Mining stuff: /// Check block validity prior to mining. - bool paranoia() const { return m_paranoia; } + bool miningParanoia() const { return m_paranoia; } /// Change whether we check block validity prior to mining. void setParanoia(bool _p) { m_paranoia = _p; } /// Set the coinbase address. @@ -287,18 +281,16 @@ public: Address address() const { return m_preMine.address(); } /// Start mining. /// NOT thread-safe - void startMining(); + void startMining() { m_miner.start(); ensureWorking(); } /// Stop mining. /// NOT thread-safe - void stopMining(); + void stopMining() { m_miner.stop(); } /// Are we mining now? - bool isMining() { return !!m_workMine; } - /// Register a callback for information concerning mining. - /// This callback will be in an arbitrary thread, blocking progress. JUST COPY THE DATA AND GET OUT. + bool isMining() { return m_miner.isRunning(); } /// Check the progress of the mining. - MineProgress miningProgress() const { Guard l(x_mineProgress); return m_mineProgress; } + MineProgress miningProgress() const { return m_miner.miningProgress(); } /// Get and clear the mining history. - std::list miningHistory() { Guard l(x_mineProgress); auto ret = m_mineHistory; m_mineHistory.clear(); return ret; } + std::list miningHistory() { return m_miner.miningHistory(); } bool forceMining() const { return m_forceMining; } void setForceMining(bool _enable) { m_forceMining = _enable; } @@ -320,8 +312,11 @@ private: /// Do some work on the network. void workNet(); - /// Do some work on the mining. - void workMine(); + /// Overrides for being a mining host. + virtual void onComplete(State& _s); + virtual void setupState(State& _s); + virtual bool turbo() const { return m_turboMining; } + virtual bool force() const { return m_forceMining; } /// Collate the changed filters for the bloom filter of the given pending transaction. /// Insert any filters that are activated into @a o_changed. @@ -360,18 +355,10 @@ private: std::unique_ptr m_work; ///< The work thread. std::atomic m_workState; - std::unique_ptr m_workMine;///< The work thread. - std::atomic m_workMineState; - enum MiningStatus { Preparing, Mining, Mined }; - MiningStatus m_miningStatus = Preparing;///< TODO: consider mutex/atomic variable. - State m_mineState; ///< The state on which we are mining, generally equivalent to m_postMine. - bool m_paranoia = false; + Miner m_miner; + bool m_paranoia = false; ///< Should we be paranoid about our state? bool m_turboMining = false; ///< Don't squander all of our time mining actually just sleeping. bool m_forceMining = false; ///< Mine even when there are no transactions pending? - mutable std::mutex x_mineProgress; ///< Lock for the mining progress & history. - MineProgress m_mineProgress; - std::list m_mineHistory; - mutable unsigned m_pendingCount = 0; mutable std::mutex m_filterLock; std::map m_filters; diff --git a/libethereum/Miner.cpp b/libethereum/Miner.cpp new file mode 100644 index 000000000..ef6c71a20 --- /dev/null +++ b/libethereum/Miner.cpp @@ -0,0 +1,104 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . + */ +/** @file Miner.cpp + * @author Alex Leverington + * @author Gav Wood + * @date 2014 + */ + +#include "Miner.h" +#include "State.h" +using namespace std; +using namespace eth; + +Miner::Miner(MinerHost* _host, unsigned _id): + m_host(_host), + m_id(_id) +{ +} + +void Miner::start() +{ + Guard l(x_work); + if (!m_work) + { + m_stop = false; + m_work.reset(new thread([&]() + { + setThreadName(("miner-" + toString(m_id)).c_str()); + m_miningStatus = Preparing; + while (!m_stop) + work(); + })); + } +} + +void Miner::stop() +{ + Guard l(x_work); + if (m_work) + { + m_stop = true; + m_work->join(); + m_work.reset(nullptr); + } +} + +void Miner::work() +{ + // Do some mining. + if ((m_pendingCount || m_host->force()) && m_miningStatus != Mined) + { + // TODO: Separate "Miner" object. + if (m_miningStatus == Preparing) + { + m_miningStatus = Mining; + + m_host->setupState(m_mineState); + m_pendingCount = m_mineState.pending().size(); + + { + Guard l(x_mineInfo); + m_mineProgress.best = (double)-1; + m_mineProgress.hashes = 0; + m_mineProgress.ms = 0; + } + } + + // Mine for a while. + MineInfo mineInfo = m_mineState.mine(100, m_host->turbo()); + + { + Guard l(x_mineInfo); + m_mineProgress.best = min(m_mineProgress.best, mineInfo.best); + m_mineProgress.current = mineInfo.best; + m_mineProgress.requirement = mineInfo.requirement; + m_mineProgress.ms += 100; + m_mineProgress.hashes += mineInfo.hashes; + m_mineHistory.push_back(mineInfo); + } + if (mineInfo.completed) + { + m_host->onComplete(m_mineState); + m_miningStatus = Mined; + } + } + else + { + this_thread::sleep_for(chrono::milliseconds(100)); + } +} diff --git a/libethereum/Miner.h b/libethereum/Miner.h new file mode 100644 index 000000000..4ea55869b --- /dev/null +++ b/libethereum/Miner.h @@ -0,0 +1,121 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . + */ +/** @file Miner.h + * @author Alex Leverington + * @author Gav Wood + * @date 2014 + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include "State.h" + +namespace eth +{ + +class BlockChain; +class Client; + +struct MineProgress +{ + double requirement; + double best; + double current; + uint hashes; + uint ms; +}; + +class MinerHost +{ +public: + virtual void setupState(State& _s) = 0; ///< Reset the given State object to the one that should be being mined. + virtual void onComplete(State& _s) = 0; ///< Completed the mine! + virtual bool turbo() const = 0; + virtual bool force() const = 0; +}; + +/** + * @brief Implements Miner. + * The miner will start a thread when there is work provided by @fn restart(). + * The _progressCb callback is called every ~100ms or when a block is found. + * @fn completeMine() is to be called once a block is found. + * If miner is not restarted from _progressCb the thread will terminate. + * @threadsafe + * @todo signal from child->parent thread to wait on exit; refactor redundant dagger/miner stats + */ +class Miner +{ +public: + /// Constructor. Starts miner. + Miner(MinerHost* _host, unsigned _id = 0); + + /// Destructor. Stops miner. + ~Miner() { stop(); } + + /// Start mining. + void start(); + + /// Stop mining. + void stop(); + + /// Restart mining. + void restart() { m_miningStatus = Preparing; } + + /// @returns if mining + bool isRunning() { return !!m_work; } + + /// @returns true if mining is complete. + bool isComplete() const { return m_miningStatus == Mined; } + + /// @returns the internal State object. + State& state() { return m_mineState; } + + /// Check the progress of the mining. + MineProgress miningProgress() const { Guard l(x_mineInfo); return m_mineProgress; } + + /// Get and clear the mining history. + std::list miningHistory() { Guard l(x_mineInfo); auto ret = m_mineHistory; m_mineHistory.clear(); return ret; } + +private: + /// Do some work on the mining. + void work(); + + MinerHost* m_host; ///< Our host. + unsigned m_id; ///< Our identity; + + std::mutex x_work; ///< Mutex protecting the creation of the work thread. + std::unique_ptr m_work; ///< The work thread. + bool m_stop = false; ///< Stop working? + + enum MiningStatus { Preparing, Mining, Mined }; + MiningStatus m_miningStatus = Preparing;///< TODO: consider mutex/atomic variable. + State m_mineState; ///< The state on which we are mining, generally equivalent to m_postMine. + mutable unsigned m_pendingCount = 0; ///< How many pending transactions are there in m_mineState? + + mutable std::mutex x_mineInfo; ///< Lock for the mining progress & history. + MineProgress m_mineProgress; ///< What's our progress? + std::list m_mineHistory; ///< What the history of our mining? +}; + +} From 2f9a279e9fe782a6dafe078d2f5828ddc940937f Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 23 Aug 2014 10:23:37 +0200 Subject: [PATCH 103/223] Various small improvements. --- alethzero/Main.ui | 3 +++ alethzero/MainWin.cpp | 4 +++- libethcore/CommonEth.cpp | 2 +- libethereum/BlockChain.cpp | 4 ++-- libethereum/State.cpp | 6 +++--- 5 files changed, 12 insertions(+), 7 deletions(-) diff --git a/alethzero/Main.ui b/alethzero/Main.ui index f233ed244..aa8a43f1c 100644 --- a/alethzero/Main.ui +++ b/alethzero/Main.ui @@ -396,6 +396,9 @@ 0 + + QPlainTextEdit::NoWrap + true diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index af0942492..3110ca700 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -535,6 +535,7 @@ void Main::writeSettings() s.setValue("port", ui->port->value()); s.setValue("url", ui->urlEdit->text()); s.setValue("privateChain", m_privateChain); + s.setValue("verbosity", ui->verbosity->value()); bytes d = m_client->savePeers(); if (d.size()) @@ -586,6 +587,7 @@ void Main::readSettings(bool _skipGeometry) ui->nameReg->setText(s.value("nameReg", "").toString()); m_privateChain = s.value("privateChain", "").toString(); ui->usePrivate->setChecked(m_privateChain.size()); + ui->verbosity->setValue(s.value("verbosity", 1).toInt()); ui->urlEdit->setText(s.value("url", "about:blank").toString()); //http://gavwood.com/gavcoin.html on_urlEdit_returnPressed(); @@ -928,7 +930,7 @@ void Main::timerEvent(QTimerEvent*) { m_logLock.lock(); m_logChanged = false; - ui->log->appendPlainText(m_logHistory); + ui->log->appendPlainText(m_logHistory.mid(0, m_logHistory.length() - 1)); m_logHistory.clear(); m_logLock.unlock(); } diff --git a/libethcore/CommonEth.cpp b/libethcore/CommonEth.cpp index 8c0e5518e..db1c9c636 100644 --- a/libethcore/CommonEth.cpp +++ b/libethcore/CommonEth.cpp @@ -29,7 +29,7 @@ using namespace eth; //#define ETH_ADDRESS_DEBUG 1 -const unsigned eth::c_protocolVersion = 29; +const unsigned eth::c_protocolVersion = 28; const unsigned eth::c_databaseVersion = 1; static const vector> g_units = diff --git a/libethereum/BlockChain.cpp b/libethereum/BlockChain.cpp index ac9e9dc37..842343480 100644 --- a/libethereum/BlockChain.cpp +++ b/libethereum/BlockChain.cpp @@ -247,7 +247,7 @@ h256s BlockChain::import(bytes const& _block, OverlayDB const& _db) throw FutureTime(); } - clog(BlockChainNote) << "Attempting import of " << newHash << "..."; + clog(BlockChainNote) << "Attempting import of " << newHash.abridged() << "..."; u256 td; #if ETH_CATCH @@ -320,7 +320,7 @@ h256s BlockChain::import(bytes const& _block, OverlayDB const& _db) m_extrasDB->Put(m_writeOptions, ldb::Slice("best"), ldb::Slice((char const*)&newHash, 32)); clog(BlockChainNote) << " Imported and best. Has" << (details(bi.parentHash).children.size() - 1) << "siblings. Route:"; for (auto r: ret) - clog(BlockChainNote) << r; + clog(BlockChainNote) << r.abridged(); } else { diff --git a/libethereum/State.cpp b/libethereum/State.cpp index 29f125a98..f81d3d55f 100644 --- a/libethereum/State.cpp +++ b/libethereum/State.cpp @@ -681,7 +681,7 @@ void State::commitToMine(BlockChain const& _bc) { uncommitToMine(); - cnote << "Committing to mine on block" << m_previousBlock.hash; + cnote << "Committing to mine on block" << m_previousBlock.hash.abridged(); #ifdef ETH_PARANOIA commit(); cnote << "Pre-reward stateRoot:" << m_state.root(); @@ -739,7 +739,7 @@ void State::commitToMine(BlockChain const& _bc) // Commit any and all changes to the trie that are in the cache, then update the state root accordingly. commit(); - cnote << "Post-reward stateRoot:" << m_state.root(); + cnote << "Post-reward stateRoot:" << m_state.root().abridged(); // cnote << m_state; // cnote << *this; @@ -778,7 +778,7 @@ void State::completeMine() ret.appendRaw(m_currentUncles); ret.swapOut(m_currentBytes); m_currentBlock.hash = sha3(m_currentBytes); - cnote << "Mined " << m_currentBlock.hash << "(parent: " << m_currentBlock.parentHash << ")"; + cnote << "Mined " << m_currentBlock.hash.abridged() << "(parent: " << m_currentBlock.parentHash.abridged() << ")"; // Quickly reset the transactions. // TODO: Leave this in a better state than this limbo, or at least record that it's in limbo. From 112bf7aa67a5e65c401bd44615e15b41927eee0b Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 23 Aug 2014 11:17:28 +0200 Subject: [PATCH 104/223] Repotting --- libethereum/Client.cpp | 125 +--------------------------- libethereum/Client.h | 62 +------------- libethereum/MessageFilter.cpp | 150 ++++++++++++++++++++++++++++++++++ libethereum/MessageFilter.h | 73 +++++++++++++++++ libethereum/PastMessage.cpp | 24 ++++++ libethereum/PastMessage.h | 53 ++++++++++++ 6 files changed, 304 insertions(+), 183 deletions(-) create mode 100644 libethereum/MessageFilter.cpp create mode 100644 libethereum/MessageFilter.h create mode 100644 libethereum/PastMessage.cpp create mode 100644 libethereum/PastMessage.h diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index 9c4576919..e3fcfc8d7 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -30,18 +30,6 @@ using namespace std; using namespace eth; -void MessageFilter::fillStream(RLPStream& _s) const -{ - _s.appendList(8) << m_from << m_to << m_stateAltered << m_altered << m_earliest << m_latest << m_max << m_skip; -} - -h256 MessageFilter::sha3() const -{ - RLPStream s; - fillStream(s); - return eth::sha3(s.out()); -} - VersionChecker::VersionChecker(string const& _dbPath): m_path(_dbPath.size() ? _dbPath : Defaults::dbPath()) { @@ -425,6 +413,8 @@ void Client::workNet() void Client::work() { + // TODO: Use condition variable rather than polling. + cworkin << "WORK"; h256Set changeds; @@ -588,117 +578,6 @@ bytes Client::codeAt(Address _a, int _block) const return asOf(_block).code(_a); } -bool MessageFilter::matches(h256 _bloom) const -{ - auto have = [=](Address const& a) { return _bloom.contains(a.bloom()); }; - if (m_from.size()) - { - for (auto i: m_from) - if (have(i)) - goto OK1; - return false; - } - OK1: - if (m_to.size()) - { - for (auto i: m_to) - if (have(i)) - goto OK2; - return false; - } - OK2: - if (m_stateAltered.size() || m_altered.size()) - { - for (auto i: m_altered) - if (have(i)) - goto OK3; - for (auto i: m_stateAltered) - if (have(i.first) && _bloom.contains(h256(i.second).bloom())) - goto OK3; - return false; - } - OK3: - return true; -} - -bool MessageFilter::matches(State const& _s, unsigned _i) const -{ - h256 b = _s.changesFromPending(_i).bloom(); - if (!matches(b)) - return false; - - Transaction t = _s.pending()[_i]; - if (!m_to.empty() && !m_to.count(t.receiveAddress)) - return false; - if (!m_from.empty() && !m_from.count(t.sender())) - return false; - if (m_stateAltered.empty() && m_altered.empty()) - return true; - StateDiff d = _s.pendingDiff(_i); - if (!m_altered.empty()) - { - for (auto const& s: m_altered) - if (d.accounts.count(s)) - return true; - return false; - } - if (!m_stateAltered.empty()) - { - for (auto const& s: m_stateAltered) - if (d.accounts.count(s.first) && d.accounts.at(s.first).storage.count(s.second)) - return true; - return false; - } - return true; -} - -PastMessages MessageFilter::matches(Manifest const& _m, unsigned _i) const -{ - PastMessages ret; - matches(_m, vector(1, _i), _m.from, PastMessages(), ret); - return ret; -} - -bool MessageFilter::matches(Manifest const& _m, vector _p, Address _o, PastMessages _limbo, PastMessages& o_ret) const -{ - bool ret; - - if ((m_from.empty() || m_from.count(_m.from)) && (m_to.empty() || m_to.count(_m.to))) - _limbo.push_back(PastMessage(_m, _p, _o)); - - // Handle limbos, by checking against all addresses in alteration. - bool alters = m_altered.empty() && m_stateAltered.empty(); - alters = alters || m_altered.count(_m.from) || m_altered.count(_m.to); - - if (!alters) - for (auto const& i: _m.altered) - if (m_altered.count(_m.to) || m_stateAltered.count(make_pair(_m.to, i))) - { - alters = true; - break; - } - // If we do alter stuff, - if (alters) - { - o_ret += _limbo; - _limbo.clear(); - ret = true; - } - - _p.push_back(0); - for (auto const& m: _m.internal) - { - if (matches(m, _p, _o, _limbo, o_ret)) - { - _limbo.clear(); - ret = true; - } - _p.back()++; - } - - return ret; -} - PastMessages Client::messages(MessageFilter const& _f) const { PastMessages ret; diff --git a/libethereum/Client.h b/libethereum/Client.h index 66601c656..453a03210 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -35,6 +35,8 @@ #include "TransactionQueue.h" #include "State.h" #include "PeerNetwork.h" +#include "PastMessage.h" +#include "MessageFilter.h" #include "Miner.h" namespace eth @@ -64,66 +66,6 @@ private: static const int GenesisBlock = INT_MIN; -struct PastMessage -{ - PastMessage(Manifest const& _m, std::vector _path, Address _o): to(_m.to), from(_m.from), value(_m.value), input(_m.input), output(_m.output), path(_path), origin(_o) {} - - PastMessage& polish(h256 _b, u256 _ts, unsigned _n, Address _coinbase) { block = _b; timestamp = _ts; number = _n; coinbase = _coinbase; return *this; } - - Address to; ///< The receiving address of the transaction. Address() in the case of a creation. - Address from; ///< The receiving address of the transaction. Address() in the case of a creation. - u256 value; ///< The value associated with the call. - bytes input; ///< The data associated with the message, or the initialiser if it's a creation transaction. - bytes output; ///< The data returned by the message, or the body code if it's a creation transaction. - - std::vector path; ///< Call path into the block transaction. size() is always > 0. First item is the transaction index in the block. - Address origin; ///< Originating sender of the transaction. - Address coinbase; ///< Block coinbase. - h256 block; ///< Block hash. - u256 timestamp; ///< Block timestamp. - unsigned number; ///< Block number. -}; - -typedef std::vector PastMessages; - -class MessageFilter -{ -public: - MessageFilter(int _earliest = 0, int _latest = -1, unsigned _max = 10, unsigned _skip = 0): m_earliest(_earliest), m_latest(_latest), m_max(_max), m_skip(_skip) {} - - void fillStream(RLPStream& _s) const; - h256 sha3() const; - - int earliest() const { return m_earliest; } - int latest() const { return m_latest; } - unsigned max() const { return m_max; } - unsigned skip() const { return m_skip; } - bool matches(h256 _bloom) const; - bool matches(State const& _s, unsigned _i) const; - PastMessages matches(Manifest const& _m, unsigned _i) const; - - MessageFilter from(Address _a) { m_from.insert(_a); return *this; } - MessageFilter to(Address _a) { m_to.insert(_a); return *this; } - MessageFilter altered(Address _a, u256 _l) { m_stateAltered.insert(std::make_pair(_a, _l)); return *this; } - MessageFilter altered(Address _a) { m_altered.insert(_a); return *this; } - MessageFilter withMax(unsigned _m) { m_max = _m; return *this; } - MessageFilter withSkip(unsigned _m) { m_skip = _m; return *this; } - MessageFilter withEarliest(int _e) { m_earliest = _e; return *this; } - MessageFilter withLatest(int _e) { m_latest = _e; return *this; } - -private: - bool matches(Manifest const& _m, std::vector _p, Address _o, PastMessages _limbo, PastMessages& o_ret) const; - - std::set
    m_from; - std::set
    m_to; - std::set> m_stateAltered; - std::set
    m_altered; - int m_earliest = 0; - int m_latest = -1; - unsigned m_max; - unsigned m_skip; -}; - struct InstalledFilter { InstalledFilter(MessageFilter const& _f): filter(_f) {} diff --git a/libethereum/MessageFilter.cpp b/libethereum/MessageFilter.cpp new file mode 100644 index 000000000..786440775 --- /dev/null +++ b/libethereum/MessageFilter.cpp @@ -0,0 +1,150 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file MessageFilter.cpp + * @author Gav Wood + * @date 2014 + */ + +#include "MessageFilter.h" + +#include +#include "State.h" +using namespace std; +using namespace eth; + +void MessageFilter::fillStream(RLPStream& _s) const +{ + _s.appendList(8) << m_from << m_to << m_stateAltered << m_altered << m_earliest << m_latest << m_max << m_skip; +} + +h256 MessageFilter::sha3() const +{ + RLPStream s; + fillStream(s); + return eth::sha3(s.out()); +} + +bool MessageFilter::matches(h256 _bloom) const +{ + auto have = [=](Address const& a) { return _bloom.contains(a.bloom()); }; + if (m_from.size()) + { + for (auto i: m_from) + if (have(i)) + goto OK1; + return false; + } + OK1: + if (m_to.size()) + { + for (auto i: m_to) + if (have(i)) + goto OK2; + return false; + } + OK2: + if (m_stateAltered.size() || m_altered.size()) + { + for (auto i: m_altered) + if (have(i)) + goto OK3; + for (auto i: m_stateAltered) + if (have(i.first) && _bloom.contains(h256(i.second).bloom())) + goto OK3; + return false; + } + OK3: + return true; +} + +bool MessageFilter::matches(State const& _s, unsigned _i) const +{ + h256 b = _s.changesFromPending(_i).bloom(); + if (!matches(b)) + return false; + + Transaction t = _s.pending()[_i]; + if (!m_to.empty() && !m_to.count(t.receiveAddress)) + return false; + if (!m_from.empty() && !m_from.count(t.sender())) + return false; + if (m_stateAltered.empty() && m_altered.empty()) + return true; + StateDiff d = _s.pendingDiff(_i); + if (!m_altered.empty()) + { + for (auto const& s: m_altered) + if (d.accounts.count(s)) + return true; + return false; + } + if (!m_stateAltered.empty()) + { + for (auto const& s: m_stateAltered) + if (d.accounts.count(s.first) && d.accounts.at(s.first).storage.count(s.second)) + return true; + return false; + } + return true; +} + +PastMessages MessageFilter::matches(Manifest const& _m, unsigned _i) const +{ + PastMessages ret; + matches(_m, vector(1, _i), _m.from, PastMessages(), ret); + return ret; +} + +bool MessageFilter::matches(Manifest const& _m, vector _p, Address _o, PastMessages _limbo, PastMessages& o_ret) const +{ + bool ret; + + if ((m_from.empty() || m_from.count(_m.from)) && (m_to.empty() || m_to.count(_m.to))) + _limbo.push_back(PastMessage(_m, _p, _o)); + + // Handle limbos, by checking against all addresses in alteration. + bool alters = m_altered.empty() && m_stateAltered.empty(); + alters = alters || m_altered.count(_m.from) || m_altered.count(_m.to); + + if (!alters) + for (auto const& i: _m.altered) + if (m_altered.count(_m.to) || m_stateAltered.count(make_pair(_m.to, i))) + { + alters = true; + break; + } + // If we do alter stuff, + if (alters) + { + o_ret += _limbo; + _limbo.clear(); + ret = true; + } + + _p.push_back(0); + for (auto const& m: _m.internal) + { + if (matches(m, _p, _o, _limbo, o_ret)) + { + _limbo.clear(); + ret = true; + } + _p.back()++; + } + + return ret; +} diff --git a/libethereum/MessageFilter.h b/libethereum/MessageFilter.h new file mode 100644 index 000000000..55d40d270 --- /dev/null +++ b/libethereum/MessageFilter.h @@ -0,0 +1,73 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file MessageFilter.h + * @author Gav Wood + * @date 2014 + */ + +#pragma once + +#include +#include +#include +#include "PastMessage.h" + +namespace eth +{ + +class Manifest; +class State; + +class MessageFilter +{ +public: + MessageFilter(int _earliest = 0, int _latest = -1, unsigned _max = 10, unsigned _skip = 0): m_earliest(_earliest), m_latest(_latest), m_max(_max), m_skip(_skip) {} + + void fillStream(RLPStream& _s) const; + h256 sha3() const; + + int earliest() const { return m_earliest; } + int latest() const { return m_latest; } + unsigned max() const { return m_max; } + unsigned skip() const { return m_skip; } + bool matches(h256 _bloom) const; + bool matches(State const& _s, unsigned _i) const; + PastMessages matches(Manifest const& _m, unsigned _i) const; + + MessageFilter from(Address _a) { m_from.insert(_a); return *this; } + MessageFilter to(Address _a) { m_to.insert(_a); return *this; } + MessageFilter altered(Address _a, u256 _l) { m_stateAltered.insert(std::make_pair(_a, _l)); return *this; } + MessageFilter altered(Address _a) { m_altered.insert(_a); return *this; } + MessageFilter withMax(unsigned _m) { m_max = _m; return *this; } + MessageFilter withSkip(unsigned _m) { m_skip = _m; return *this; } + MessageFilter withEarliest(int _e) { m_earliest = _e; return *this; } + MessageFilter withLatest(int _e) { m_latest = _e; return *this; } + +private: + bool matches(Manifest const& _m, std::vector _p, Address _o, PastMessages _limbo, PastMessages& o_ret) const; + + std::set
    m_from; + std::set
    m_to; + std::set> m_stateAltered; + std::set
    m_altered; + int m_earliest = 0; + int m_latest = -1; + unsigned m_max; + unsigned m_skip; +}; + +} diff --git a/libethereum/PastMessage.cpp b/libethereum/PastMessage.cpp new file mode 100644 index 000000000..852d4a4bd --- /dev/null +++ b/libethereum/PastMessage.cpp @@ -0,0 +1,24 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file PastMessage.cpp + * @author Gav Wood + * @date 2014 + */ + +#include "PastMessage.h" +using namespace std; +using namespace eth; diff --git a/libethereum/PastMessage.h b/libethereum/PastMessage.h new file mode 100644 index 000000000..789ce350a --- /dev/null +++ b/libethereum/PastMessage.h @@ -0,0 +1,53 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file PastMessage.h + * @author Gav Wood + * @date 2014 + */ + +#pragma once + +#include +#include +#include "Manifest.h" + +namespace eth +{ + +struct PastMessage +{ + PastMessage(Manifest const& _m, std::vector _path, Address _o): to(_m.to), from(_m.from), value(_m.value), input(_m.input), output(_m.output), path(_path), origin(_o) {} + + PastMessage& polish(h256 _b, u256 _ts, unsigned _n, Address _coinbase) { block = _b; timestamp = _ts; number = _n; coinbase = _coinbase; return *this; } + + Address to; ///< The receiving address of the transaction. Address() in the case of a creation. + Address from; ///< The receiving address of the transaction. Address() in the case of a creation. + u256 value; ///< The value associated with the call. + bytes input; ///< The data associated with the message, or the initialiser if it's a creation transaction. + bytes output; ///< The data returned by the message, or the body code if it's a creation transaction. + + std::vector path; ///< Call path into the block transaction. size() is always > 0. First item is the transaction index in the block. + Address origin; ///< Originating sender of the transaction. + Address coinbase; ///< Block coinbase. + h256 block; ///< Block hash. + u256 timestamp; ///< Block timestamp. + unsigned number; ///< Block number. +}; + +typedef std::vector PastMessages; + +} From 454d6118ca09d74bf170b887690fd493bb0dc2ae Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 23 Aug 2014 11:22:24 +0200 Subject: [PATCH 105/223] No need to commit to disk after successful mine - this will happen during blockchain import anyway. --- libethereum/Client.cpp | 18 +++++++++--------- libethereum/State.cpp | 3 --- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index e3fcfc8d7..05277c3ad 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -289,14 +289,6 @@ void Client::connect(std::string const& _seedHost, unsigned short _port) m_net->connect(_seedHost, _port); } -void Client::onComplete(State& _s) -{ - // Must lock stateDB here since we're actually pushing out to the database. - WriteGuard l(x_stateDB); - cwork << "COMPLETE MINE"; - _s.completeMine(); -} - void Client::setupState(State& _s) { { @@ -320,6 +312,14 @@ void Client::setupState(State& _s) _s.commitToMine(m_bc); } +void Client::onComplete(State& _s) +{ + // Must lock stateDB here since we're actually pushing out to the database. + WriteGuard l(x_stateDB); + cwork << "COMPLETE MINE"; + _s.completeMine(); +} + void Client::transact(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice) { ensureWorking(); @@ -423,7 +423,7 @@ void Client::work() cwork << "CHAIN <== postSTATE"; h256s hs; { - ReadGuard l(x_stateDB); + WriteGuard l(x_stateDB); hs = m_bc.attemptImport(m_miner.state().blockData(), m_stateDB); } if (hs.size()) diff --git a/libethereum/State.cpp b/libethereum/State.cpp index f81d3d55f..332a3c920 100644 --- a/libethereum/State.cpp +++ b/libethereum/State.cpp @@ -767,9 +767,6 @@ void State::completeMine() cdebug << "Completing mine!"; // Got it! - // Commit to disk. - m_db.commit(); - // Compile block: RLPStream ret; ret.appendList(3); From 2e65637892843acf435264f2c7ef30ba68ad5e5a Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 23 Aug 2014 13:26:49 +0200 Subject: [PATCH 106/223] Docs. --- libethereum/Client.cpp | 8 -------- libethereum/Client.h | 1 - libethereum/Miner.cpp | 6 ++++-- libethereum/Miner.h | 40 ++++++++++++++++++++++++---------------- 4 files changed, 28 insertions(+), 27 deletions(-) diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index 05277c3ad..f2e3c2764 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -312,14 +312,6 @@ void Client::setupState(State& _s) _s.commitToMine(m_bc); } -void Client::onComplete(State& _s) -{ - // Must lock stateDB here since we're actually pushing out to the database. - WriteGuard l(x_stateDB); - cwork << "COMPLETE MINE"; - _s.completeMine(); -} - void Client::transact(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice) { ensureWorking(); diff --git a/libethereum/Client.h b/libethereum/Client.h index 453a03210..4932097ac 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -255,7 +255,6 @@ private: void workNet(); /// Overrides for being a mining host. - virtual void onComplete(State& _s); virtual void setupState(State& _s); virtual bool turbo() const { return m_turboMining; } virtual bool force() const { return m_forceMining; } diff --git a/libethereum/Miner.cpp b/libethereum/Miner.cpp index ef6c71a20..6fe714a5e 100644 --- a/libethereum/Miner.cpp +++ b/libethereum/Miner.cpp @@ -63,7 +63,6 @@ void Miner::work() // Do some mining. if ((m_pendingCount || m_host->force()) && m_miningStatus != Mined) { - // TODO: Separate "Miner" object. if (m_miningStatus == Preparing) { m_miningStatus = Mining; @@ -93,9 +92,12 @@ void Miner::work() } if (mineInfo.completed) { - m_host->onComplete(m_mineState); + m_mineState.completeMine(); + m_host->onComplete(); m_miningStatus = Mined; } + else + m_host->onProgressed(); } else { diff --git a/libethereum/Miner.h b/libethereum/Miner.h index 4ea55869b..e7f995bcb 100644 --- a/libethereum/Miner.h +++ b/libethereum/Miner.h @@ -37,30 +37,38 @@ namespace eth class BlockChain; class Client; +/** + * @brief Describes the progress of a mining operation. + */ struct MineProgress { - double requirement; - double best; - double current; - uint hashes; - uint ms; + double requirement; ///< The PoW requirement - as the second logarithm of the minimum acceptable hash. + double best; ///< The PoW achievement - as the second logarithm of the minimum found hash. + double current; ///< The most recent PoW achievement - as the second logarithm of the presently found hash. + uint hashes; ///< Total number of hashes computed. + uint ms; ///< Total number of milliseconds of mining thus far. }; +/** + * @brief The MinerHost class. + * @warning Must be implemented in a threadsafe manner since it will be called from multiple + * miner threads. + */ class MinerHost { public: virtual void setupState(State& _s) = 0; ///< Reset the given State object to the one that should be being mined. - virtual void onComplete(State& _s) = 0; ///< Completed the mine! - virtual bool turbo() const = 0; - virtual bool force() const = 0; + virtual void onProgressed() {} ///< Called once some progress has been made. + virtual void onComplete() {} ///< Called once a block is found. + virtual bool turbo() const = 0; ///< @returns true iff the Miner should mine as fast as possible. + virtual bool force() const = 0; ///< @returns true iff the Miner should mine regardless of the number of transactions. }; /** * @brief Implements Miner. - * The miner will start a thread when there is work provided by @fn restart(). - * The _progressCb callback is called every ~100ms or when a block is found. - * @fn completeMine() is to be called once a block is found. - * If miner is not restarted from _progressCb the thread will terminate. + * To begin mining, use start() & stop(). restart() can be used to reset the mining and set up the + * State object according to the host. Use isRunning() to determine if the miner has been start()ed. + * Use isComplete() to determine if the miner has finished mining. * @threadsafe * @todo signal from child->parent thread to wait on exit; refactor redundant dagger/miner stats */ @@ -80,9 +88,9 @@ public: void stop(); /// Restart mining. - void restart() { m_miningStatus = Preparing; } + void restart() { start(); m_miningStatus = Preparing; } - /// @returns if mining + /// @returns true iff the mining has been start()ed. It may still not be actually mining, depending on the host's turbo() & force(). bool isRunning() { return !!m_work; } /// @returns true if mining is complete. @@ -108,12 +116,12 @@ private: std::unique_ptr m_work; ///< The work thread. bool m_stop = false; ///< Stop working? - enum MiningStatus { Preparing, Mining, Mined }; + enum MiningStatus { Preparing, Mining, Mined, Stopping, Stopped }; MiningStatus m_miningStatus = Preparing;///< TODO: consider mutex/atomic variable. State m_mineState; ///< The state on which we are mining, generally equivalent to m_postMine. mutable unsigned m_pendingCount = 0; ///< How many pending transactions are there in m_mineState? - mutable std::mutex x_mineInfo; ///< Lock for the mining progress & history. + mutable std::mutex x_mineInfo; ///< Lock for the mining progress & history. MineProgress m_mineProgress; ///< What's our progress? std::list m_mineHistory; ///< What the history of our mining? }; From 69b8c7764195365f777ee3992500ae0740dac62a Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 23 Aug 2014 14:25:08 +0200 Subject: [PATCH 107/223] Nicer API for Miner. --- libethereum/Client.cpp | 2 +- libethereum/Miner.h | 15 +++++++-------- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index f2e3c2764..7d0eb7d9c 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -416,7 +416,7 @@ void Client::work() h256s hs; { WriteGuard l(x_stateDB); - hs = m_bc.attemptImport(m_miner.state().blockData(), m_stateDB); + hs = m_bc.attemptImport(m_miner.blockData(), m_stateDB); } if (hs.size()) { diff --git a/libethereum/Miner.h b/libethereum/Miner.h index e7f995bcb..ca4eca8a3 100644 --- a/libethereum/Miner.h +++ b/libethereum/Miner.h @@ -27,16 +27,11 @@ #include #include #include -#include -#include #include "State.h" namespace eth { -class BlockChain; -class Client; - /** * @brief Describes the progress of a mining operation. */ @@ -50,7 +45,7 @@ struct MineProgress }; /** - * @brief The MinerHost class. + * @brief Class for hosting one or more Miners. * @warning Must be implemented in a threadsafe manner since it will be called from multiple * miner threads. */ @@ -69,6 +64,10 @@ public: * To begin mining, use start() & stop(). restart() can be used to reset the mining and set up the * State object according to the host. Use isRunning() to determine if the miner has been start()ed. * Use isComplete() to determine if the miner has finished mining. + * + * blockData() can be used to retrieve the complete block, ready for insertion into the BlockChain. + * + * Information on the mining can be queried through miningProgress() and miningHistory(). * @threadsafe * @todo signal from child->parent thread to wait on exit; refactor redundant dagger/miner stats */ @@ -87,7 +86,7 @@ public: /// Stop mining. void stop(); - /// Restart mining. + /// Restart mining (and start if not already running). void restart() { start(); m_miningStatus = Preparing; } /// @returns true iff the mining has been start()ed. It may still not be actually mining, depending on the host's turbo() & force(). @@ -97,7 +96,7 @@ public: bool isComplete() const { return m_miningStatus == Mined; } /// @returns the internal State object. - State& state() { return m_mineState; } + bytes const& blockData() { return m_mineState.blockData(); } /// Check the progress of the mining. MineProgress miningProgress() const { Guard l(x_mineInfo); return m_mineProgress; } From b902b3e701f7a06059e5290fb6b2f5edcd49c72f Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 23 Aug 2014 19:30:46 +0200 Subject: [PATCH 108/223] POST for PoC-6 --- alethzero/MainWin.cpp | 1 + eth/main.cpp | 15 +++++++++------ libethereum/Client.cpp | 6 +++--- libethereum/Executive.cpp | 12 +++++------- libethereum/Executive.h | 2 +- libethereum/ExtVM.h | 18 ++++++++++++++++-- libethereum/Miner.h | 10 +++++----- libethereum/PeerSession.cpp | 2 +- libethereum/State.cpp | 10 ++++++++-- libethereum/State.h | 5 ++--- libevm/ExtVMFace.h | 17 +++++++++++++++++ libevm/VM.h | 29 +++++++++++++++++++++++++++++ libevmface/Instruction.cpp | 2 ++ libevmface/Instruction.h | 1 + 14 files changed, 100 insertions(+), 30 deletions(-) diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index 3110ca700..f499f38b9 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -1247,6 +1247,7 @@ void Main::populateDebugger(eth::bytesConstRef _r) m_history.append(WorldState({steps, ext.myAddress, vm.curPC(), inst, newMemSize, vm.gas(), lastHash, lastDataHash, vm.stack(), vm.memory(), gasCost, ext.state().storage(ext.myAddress), levels})); }; m_currentExecution->go(onOp); + m_currentExecution->finalize(onOp); initDebugger(); updateDebugger(); } diff --git a/eth/main.cpp b/eth/main.cpp index f0af271d0..d6c49c867 100644 --- a/eth/main.cpp +++ b/eth/main.cpp @@ -597,8 +597,9 @@ int main(int argc, char** argv) bytes r = t.rlp(); e.setup(&r); + OnOpFunc oof; if (format == "pretty") - e.go([&](uint64_t steps, Instruction instr, bigint newMemSize, bigint gasCost, void* vvm, void const* vextVM) + oof = [&](uint64_t steps, Instruction instr, bigint newMemSize, bigint gasCost, void* vvm, void const* vextVM) { eth::VM* vm = (VM*)vvm; eth::ExtVM const* ext = (ExtVM const*)vextVM; @@ -610,16 +611,16 @@ int main(int argc, char** argv) for (auto const& i: ext->state().storage(ext->myAddress)) f << showbase << hex << i.first << ": " << i.second << endl; f << dec << ext->level << " | " << ext->myAddress << " | #" << steps << " | " << hex << setw(4) << setfill('0') << vm->curPC() << " : " << c_instructionInfo.at(instr).name << " | " << dec << vm->gas() << " | -" << dec << gasCost << " | " << newMemSize << "x32"; - }); + }; else if (format == "standard") - e.go([&](uint64_t, Instruction instr, bigint, bigint, void* vvm, void const* vextVM) + oof = [&](uint64_t, Instruction instr, bigint, bigint, void* vvm, void const* vextVM) { eth::VM* vm = (VM*)vvm; eth::ExtVM const* ext = (ExtVM const*)vextVM; f << ext->myAddress << " " << hex << toHex(eth::toCompactBigEndian(vm->curPC(), 1)) << " " << hex << toHex(eth::toCompactBigEndian((int)(byte)instr, 1)) << " " << hex << toHex(eth::toCompactBigEndian((uint64_t)vm->gas(), 1)) << endl; - }); + }; else if (format == "standard+") - e.go([&](uint64_t, Instruction instr, bigint, bigint, void* vvm, void const* vextVM) + oof = [&](uint64_t, Instruction instr, bigint, bigint, void* vvm, void const* vextVM) { eth::VM* vm = (VM*)vvm; eth::ExtVM const* ext = (ExtVM const*)vextVM; @@ -627,7 +628,9 @@ int main(int argc, char** argv) for (auto const& i: ext->state().storage(ext->myAddress)) f << toHex(eth::toCompactBigEndian(i.first, 1)) << " " << toHex(eth::toCompactBigEndian(i.second, 1)) << endl; f << ext->myAddress << " " << hex << toHex(eth::toCompactBigEndian(vm->curPC(), 1)) << " " << hex << toHex(eth::toCompactBigEndian((int)(byte)instr, 1)) << " " << hex << toHex(eth::toCompactBigEndian((uint64_t)vm->gas(), 1)) << endl; - }); + }; + e.go(oof); + e.finalize(oof); } } else if (cmd == "inspect") diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index 7d0eb7d9c..738beb9be 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -115,7 +115,7 @@ void Client::clearPending() appendFromNewPending(m_postMine.bloom(i), changeds); changeds.insert(PendingChangedFilter); m_postMine = m_preMine; - m_miner.restart(); + m_miner.noteStateChange(); noteChanged(changeds); } @@ -425,7 +425,7 @@ void Client::work() changeds.insert(ChainChangedFilter); //changeds.insert(PendingChangedFilter); // if we mined the new block, then we've probably reset the pending transactions. } - m_miner.restart(); + m_miner.noteStateChange(); } // Synchronise state to block chain. @@ -476,7 +476,7 @@ void Client::work() } if (rsm) - m_miner.restart(); + m_miner.noteStateChange(); } cwork << "noteChanged" << changeds.size() << "items"; diff --git a/libethereum/Executive.cpp b/libethereum/Executive.cpp index 6562a0cf8..699837701 100644 --- a/libethereum/Executive.cpp +++ b/libethereum/Executive.cpp @@ -217,21 +217,19 @@ u256 Executive::gas() const return m_vm ? m_vm->gas() : m_endGas; } -void Executive::finalize() +void Executive::finalize(OnOpFunc const& _onOp) { if (m_t.isCreation() && m_newAddress && m_out.size()) // non-reverted creation - put code in place. m_s.m_cache[m_newAddress].setCode(m_out); + if (m_ext) + m_endGas += m_ext->doPosts(_onOp); + // cnote << "Refunding" << formatBalance(m_endGas * m_ext->gasPrice) << "to origin (=" << m_endGas << "*" << formatBalance(m_ext->gasPrice) << ")"; m_s.addBalance(m_sender, m_endGas * m_t.gasPrice); - u256 gasSpentInEth = (m_t.gas - m_endGas) * m_t.gasPrice; -/* unsigned c_feesKept = 8; - u256 feesEarned = gasSpentInEth - (gasSpentInEth / c_feesKept); - cnote << "Transferring" << (100.0 - 100.0 / c_feesKept) << "% of" << formatBalance(gasSpent) << "=" << formatBalance(feesEarned) << "to miner (" << formatBalance(gasSpentInEth - feesEarned) << "is burnt)."; -*/ - u256 feesEarned = gasSpentInEth; + u256 feesEarned = (m_t.gas - m_endGas) * m_t.gasPrice; // cnote << "Transferring" << formatBalance(gasSpent) << "to miner."; m_s.addBalance(m_s.m_currentBlock.coinbaseAddress, feesEarned); diff --git a/libethereum/Executive.h b/libethereum/Executive.h index 0d3a5288f..e7ba44223 100644 --- a/libethereum/Executive.h +++ b/libethereum/Executive.h @@ -48,7 +48,7 @@ public: bool create(Address _txSender, u256 _endowment, u256 _gasPrice, u256 _gas, bytesConstRef _code, Address _originAddress); bool call(Address _myAddress, Address _txSender, u256 _txValue, u256 _gasPrice, bytesConstRef _txData, u256 _gas, Address _originAddress); bool go(OnOpFunc const& _onOp = OnOpFunc()); - void finalize(); + void finalize(OnOpFunc const& _onOp = OnOpFunc()); u256 gasUsed() const; static OnOpFunc simpleTrace(); diff --git a/libethereum/ExtVM.h b/libethereum/ExtVM.h index b980d6f01..737e003dd 100644 --- a/libethereum/ExtVM.h +++ b/libethereum/ExtVM.h @@ -56,7 +56,7 @@ public: m_s.noteSending(myAddress); if (m_ms) m_ms->internal.resize(m_ms->internal.size() + 1); - auto ret = m_s.create(myAddress, _endowment, gasPrice, _gas, _code, origin, &suicides, m_ms ? &(m_ms->internal.back()) : nullptr, _onOp, level + 1); + auto ret = m_s.create(myAddress, _endowment, gasPrice, _gas, _code, origin, &suicides, &posts, m_ms ? &(m_ms->internal.back()) : nullptr, _onOp, level + 1); if (m_ms && !m_ms->internal.back().from) m_ms->internal.pop_back(); return ret; @@ -67,7 +67,7 @@ public: { if (m_ms) m_ms->internal.resize(m_ms->internal.size() + 1); - auto ret = m_s.call(_receiveAddress, myAddress, _txValue, gasPrice, _txData, _gas, _out, origin, &suicides, m_ms ? &(m_ms->internal.back()) : nullptr, _onOp, level + 1); + auto ret = m_s.call(_receiveAddress, myAddress, _txValue, gasPrice, _txData, _gas, _out, origin, &suicides, &posts, m_ms ? &(m_ms->internal.back()) : nullptr, _onOp, level + 1); if (m_ms && !m_ms->internal.back().from) m_ms->internal.pop_back(); return ret; @@ -93,6 +93,20 @@ public: /// @TODO check call site for the parent manifest being discarded. void revert() { if (m_ms) *m_ms = Manifest(); m_s.m_cache = m_origCache; } + /// Execute any posts we have left. + u256 doPosts(OnOpFunc const& _onOp = OnOpFunc()) + { + u256 ret; + while (posts.size()) + { + Post& p = posts.front(); + call(p.to, p.value, &p.data, &p.gas, bytesRef(), _onOp); + ret += p.gas; + posts.pop_front(); + } + return ret; + } + State& state() const { return m_s; } /// @note not a part of the main API; just for use by tracing/debug stuff. diff --git a/libethereum/Miner.h b/libethereum/Miner.h index ca4eca8a3..7ddd54ea6 100644 --- a/libethereum/Miner.h +++ b/libethereum/Miner.h @@ -61,7 +61,7 @@ public: /** * @brief Implements Miner. - * To begin mining, use start() & stop(). restart() can be used to reset the mining and set up the + * To begin mining, use start() & stop(). noteStateChange() can be used to reset the mining and set up the * State object according to the host. Use isRunning() to determine if the miner has been start()ed. * Use isComplete() to determine if the miner has finished mining. * @@ -69,7 +69,7 @@ public: * * Information on the mining can be queried through miningProgress() and miningHistory(). * @threadsafe - * @todo signal from child->parent thread to wait on exit; refactor redundant dagger/miner stats + * @todo Signal Miner to restart once with condition variables. */ class Miner { @@ -86,8 +86,8 @@ public: /// Stop mining. void stop(); - /// Restart mining (and start if not already running). - void restart() { start(); m_miningStatus = Preparing; } + /// Call to notify Miner of a state change. + void noteStateChange() { m_miningStatus = Preparing; } /// @returns true iff the mining has been start()ed. It may still not be actually mining, depending on the host's turbo() & force(). bool isRunning() { return !!m_work; } @@ -109,7 +109,7 @@ private: void work(); MinerHost* m_host; ///< Our host. - unsigned m_id; ///< Our identity; + unsigned m_id; ///< Our identity. std::mutex x_work; ///< Mutex protecting the creation of the work thread. std::unique_ptr m_work; ///< The work thread. diff --git a/libethereum/PeerSession.cpp b/libethereum/PeerSession.cpp index 393353b63..b095745f7 100644 --- a/libethereum/PeerSession.cpp +++ b/libethereum/PeerSession.cpp @@ -184,7 +184,7 @@ bool PeerSession::interpret(RLP const& _r) if (isPrivateAddress(peerAddress)) goto CONTINUE; - clogS(NetAllDetail) << "Checking: " << ep << "(" << toHex(id.ref().cropped(0, 4)) << ")"; + clogS(NetAllDetail) << "Checking: " << ep << "(" << id.abridged() << ")"; // check that it's not us or one we already know: if (id && (m_server->m_key.pub() == id || m_server->havePeer(id) || m_server->m_incomingPeers.count(id))) diff --git a/libethereum/State.cpp b/libethereum/State.cpp index 332a3c920..5efb03b1f 100644 --- a/libethereum/State.cpp +++ b/libethereum/State.cpp @@ -1024,7 +1024,7 @@ u256 State::execute(bytesConstRef _rlp, bytes* o_output, bool _commit) return e.gasUsed(); } -bool State::call(Address _receiveAddress, Address _senderAddress, u256 _value, u256 _gasPrice, bytesConstRef _data, u256* _gas, bytesRef _out, Address _originAddress, std::set
    * o_suicides, Manifest* o_ms, OnOpFunc const& _onOp, unsigned _level) +bool State::call(Address _receiveAddress, Address _senderAddress, u256 _value, u256 _gasPrice, bytesConstRef _data, u256* _gas, bytesRef _out, Address _originAddress, std::set
    * o_suicides, PostList* o_posts, Manifest* o_ms, OnOpFunc const& _onOp, unsigned _level) { if (!_originAddress) _originAddress = _senderAddress; @@ -1053,6 +1053,9 @@ bool State::call(Address _receiveAddress, Address _senderAddress, u256 _value, u if (o_suicides) for (auto i: evm.suicides) o_suicides->insert(i); + if (o_posts) + for (auto i: evm.posts) + o_posts->push_back(i); if (o_ms) o_ms->output = out.toBytes(); } @@ -1085,7 +1088,7 @@ bool State::call(Address _receiveAddress, Address _senderAddress, u256 _value, u return true; } -h160 State::create(Address _sender, u256 _endowment, u256 _gasPrice, u256* _gas, bytesConstRef _code, Address _origin, std::set
    * o_suicides, Manifest* o_ms, OnOpFunc const& _onOp, unsigned _level) +h160 State::create(Address _sender, u256 _endowment, u256 _gasPrice, u256* _gas, bytesConstRef _code, Address _origin, std::set
    * o_suicides, PostList* o_posts, Manifest* o_ms, OnOpFunc const& _onOp, unsigned _level) { if (!_origin) _origin = _sender; @@ -1119,6 +1122,9 @@ h160 State::create(Address _sender, u256 _endowment, u256 _gasPrice, u256* _gas, if (o_suicides) for (auto i: evm.suicides) o_suicides->insert(i); + if (o_posts) + for (auto i: evm.posts) + o_posts->push_back(i); } catch (OutOfGas const& /*_e*/) { diff --git a/libethereum/State.h b/libethereum/State.h index 80380149f..519bb5349 100644 --- a/libethereum/State.h +++ b/libethereum/State.h @@ -307,12 +307,12 @@ private: // We assume all instrinsic fees are paid up before this point. /// Execute a contract-creation transaction. - h160 create(Address _txSender, u256 _endowment, u256 _gasPrice, u256* _gas, bytesConstRef _code, Address _originAddress = Address(), std::set
    * o_suicides = nullptr, Manifest* o_ms = nullptr, OnOpFunc const& _onOp = OnOpFunc(), unsigned _level = 0); + h160 create(Address _txSender, u256 _endowment, u256 _gasPrice, u256* _gas, bytesConstRef _code, Address _originAddress = Address(), std::set
    * o_suicides = nullptr, PostList* o_posts = nullptr, Manifest* o_ms = nullptr, OnOpFunc const& _onOp = OnOpFunc(), unsigned _level = 0); /// Execute a call. /// @a _gas points to the amount of gas to use for the call, and will lower it accordingly. /// @returns false if the call ran out of gas before completion. true otherwise. - bool call(Address _myAddress, Address _txSender, u256 _txValue, u256 _gasPrice, bytesConstRef _txData, u256* _gas, bytesRef _out, Address _originAddress = Address(), std::set
    * o_suicides = nullptr, Manifest* o_ms = nullptr, OnOpFunc const& _onOp = OnOpFunc(), unsigned _level = 0); + bool call(Address _myAddress, Address _txSender, u256 _txValue, u256 _gasPrice, bytesConstRef _txData, u256* _gas, bytesRef _out, Address _originAddress = Address(), std::set
    * o_suicides = nullptr, PostList* o_posts = nullptr, Manifest* o_ms = nullptr, OnOpFunc const& _onOp = OnOpFunc(), unsigned _level = 0); /// Sets m_currentBlock to a clean state, (i.e. no change from m_previousBlock). void resetCurrent(); @@ -332,7 +332,6 @@ private: TrieDB m_state; ///< Our state tree, as an OverlayDB DB. std::vector m_transactions; ///< The current list of transactions that we've included in the state. std::set m_transactionSet; ///< The set of transaction hashes that we've included in the state. -// GenericTrieDB m_transactionManifest; ///< The transactions trie; saved from the last commitToMine, or invalid/empty if commitToMine was never called. OverlayDB m_lastTx; mutable std::map m_cache; ///< Our address cache. This stores the states of each address that has (or at least might have) been changed. diff --git a/libevm/ExtVMFace.h b/libevm/ExtVMFace.h index e53d90734..3fe7bea5a 100644 --- a/libevm/ExtVMFace.h +++ b/libevm/ExtVMFace.h @@ -29,6 +29,16 @@ namespace eth { +struct Post +{ + Address to; + u256 value; + bytes data; + u256 gas; +}; + +using PostList = std::list; + /** * @brief A null implementation of the class for specifying VM externalities. */ @@ -68,9 +78,15 @@ public: /// Make a new message call. bool call(Address, u256, bytesConstRef, u256*, bytesRef) { return false; } + /// Post a new message call. + void post(Address _to, u256 _value, bytesConstRef _data, u256 _gas) { posts.push_back(Post({_to, _value, _data.toBytes(), _gas})); } + /// Revert any changes made (by any of the other calls). void revert() {} + /// Execute any posts that may exist, including those that are incurred as a result of earlier posts. + void doPosts() {} + Address myAddress; ///< Address associated with executing code (a contract, or contract-to-be). Address caller; ///< Address which sent the message (either equal to origin or a contract). Address origin; ///< Original transactor. @@ -81,6 +97,7 @@ public: BlockInfo previousBlock; ///< The previous block's information. BlockInfo currentBlock; ///< The current block's information. std::set
    suicides; ///< Any accounts that have suicided. + std::list posts; ///< Any posts that have been made. }; typedef std::function OnOpFunc; diff --git a/libevm/VM.h b/libevm/VM.h index 7a3c6f6e1..3e271dae0 100644 --- a/libevm/VM.h +++ b/libevm/VM.h @@ -164,6 +164,12 @@ template eth::bytesConstRef eth::VM::go(Ext& _ext, OnOpFunc const& _ newTempSize = std::max(m_stack[m_stack.size() - 6] + m_stack[m_stack.size() - 7], m_stack[m_stack.size() - 4] + m_stack[m_stack.size() - 5]); break; + case Instruction::POST: + require(5); + runGas = c_callGas + m_stack[m_stack.size() - 1]; + newTempSize = m_stack[m_stack.size() - 4] + m_stack[m_stack.size() - 5]; + break; + case Instruction::CREATE: { require(3); @@ -622,6 +628,29 @@ template eth::bytesConstRef eth::VM::go(Ext& _ext, OnOpFunc const& _ } case Instruction::STOP: return bytesConstRef(); + case Instruction::POST: + { + require(5); + + u256 gas = m_stack.back(); + m_stack.pop_back(); + u160 receiveAddress = asAddress(m_stack.back()); + m_stack.pop_back(); + u256 value = m_stack.back(); + m_stack.pop_back(); + + unsigned inOff = (unsigned)m_stack.back(); + m_stack.pop_back(); + unsigned inSize = (unsigned)m_stack.back(); + m_stack.pop_back(); + + if (_ext.balance(_ext.myAddress) >= value) + { + _ext.subBalance(value); + _ext.post(receiveAddress, value, bytesConstRef(m_temp.data() + inOff, inSize), gas); + } + break; + } default: throw BadInstruction(); } diff --git a/libevmface/Instruction.cpp b/libevmface/Instruction.cpp index b42244c3e..c31194475 100644 --- a/libevmface/Instruction.cpp +++ b/libevmface/Instruction.cpp @@ -145,6 +145,7 @@ const std::map eth::c_instructions = { "CREATE", Instruction::CREATE }, { "CALL", Instruction::CALL }, { "RETURN", Instruction::RETURN }, + { "POST", Instruction::POST }, { "SUICIDE", Instruction::SUICIDE } }; @@ -268,6 +269,7 @@ const std::map eth::c_instructionInfo = { Instruction::CREATE, { "CREATE", 0, 3, 1 } }, { Instruction::CALL, { "CALL", 0, 7, 1 } }, { Instruction::RETURN, { "RETURN", 0, 2, 0 } }, + { Instruction::POST, { "POST", 0, 5, 0 } }, { Instruction::SUICIDE, { "SUICIDE", 0, 1, 0} } }; diff --git a/libevmface/Instruction.h b/libevmface/Instruction.h index bf107a860..1afe930aa 100644 --- a/libevmface/Instruction.h +++ b/libevmface/Instruction.h @@ -162,6 +162,7 @@ enum class Instruction: uint8_t CREATE = 0xf0, CALL, RETURN, + POST, SUICIDE = 0xff }; From e067be15e0a16b5a4cd9f97ae4d7cb8dd45db179 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 23 Aug 2014 20:40:44 +0200 Subject: [PATCH 109/223] Null-account/address PoC-6 updates. --- libethereum/State.cpp | 2 +- libethereum/State.h | 10 ++++++++-- libethereum/Transaction.cpp | 9 +++++++-- 3 files changed, 16 insertions(+), 5 deletions(-) diff --git a/libethereum/State.cpp b/libethereum/State.cpp index 5efb03b1f..105c725f6 100644 --- a/libethereum/State.cpp +++ b/libethereum/State.cpp @@ -287,7 +287,7 @@ void State::ensureCached(std::map& _cache, Address _a, bo if (state.isNull()) s = AddressState(0, 0, h256(), EmptySHA3); else - s = AddressState(state[0].toInt(), state[1].toInt(), state[2].toHash(), state[3].toHash()); + s = AddressState(state[0].toInt(), state[1].toInt(), state[2].toHash(), state[3].isEmpty() ? EmptySHA3 : state[3].toHash()); bool ok; tie(it, ok) = _cache.insert(make_pair(_a, s)); } diff --git a/libethereum/State.h b/libethereum/State.h index 519bb5349..c8fd27e01 100644 --- a/libethereum/State.h +++ b/libethereum/State.h @@ -386,10 +386,16 @@ void commit(std::map const& _cache, DB& _db, TrieDB(); gasPrice = rlp[field = 1].toInt(); gas = rlp[field = 2].toInt(); - receiveAddress = rlp[field = 3].toHash
    (); + receiveAddress = rlp[field = 3].isEmpty() ? Address() : Address(); value = rlp[field = 4].toInt(); data = rlp[field = 5].toBytes(); vrs = Signature{ rlp[field = 6].toInt(), rlp[field = 7].toInt(), rlp[field = 8].toInt() }; @@ -119,7 +119,12 @@ void Transaction::sign(Secret _priv) void Transaction::fillStream(RLPStream& _s, bool _sig) const { _s.appendList((_sig ? 3 : 0) + 6); - _s << nonce << gasPrice << gas << receiveAddress << value << data; + _s << nonce << gasPrice << gas; + if (receiveAddress) + _s << receiveAddress; + else + _s << ""; + _s << value << data; if (_sig) _s << vrs.v << vrs.r << vrs.s; } From 4fe807536575994606e6b747898210991bb86eb9 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 23 Aug 2014 21:16:03 +0200 Subject: [PATCH 110/223] PoC-6 : 12 second blocks, allow multi-level uncles, rejig rewards. --- libethcore/BlockInfo.cpp | 2 +- libethcore/Exceptions.h | 3 ++- libethereum/BlockChain.cpp | 14 ++++++++++++++ libethereum/BlockChain.h | 5 +++++ libethereum/State.cpp | 26 ++++++++++++++++---------- libethereum/State.h | 4 ++-- 6 files changed, 40 insertions(+), 14 deletions(-) diff --git a/libethcore/BlockInfo.cpp b/libethcore/BlockInfo.cpp index 1f845ad20..4c71353d6 100644 --- a/libethcore/BlockInfo.cpp +++ b/libethcore/BlockInfo.cpp @@ -166,7 +166,7 @@ u256 BlockInfo::calculateDifficulty(BlockInfo const& _parent) const if (!parentHash) return c_genesisDifficulty; else - return timestamp >= _parent.timestamp + 42 ? _parent.difficulty - (_parent.difficulty >> 10) : (_parent.difficulty + (_parent.difficulty >> 10)); + return timestamp >= _parent.timestamp + 9 ? _parent.difficulty - (_parent.difficulty >> 10) : (_parent.difficulty + (_parent.difficulty >> 10)); } void BlockInfo::verifyParent(BlockInfo const& _parent) const diff --git a/libethcore/Exceptions.h b/libethcore/Exceptions.h index 475ab1f05..9fb80d8c6 100644 --- a/libethcore/Exceptions.h +++ b/libethcore/Exceptions.h @@ -20,7 +20,8 @@ class InvalidBlockFormat: public Exception { public: InvalidBlockFormat(int _f, class InvalidBlockHeaderFormat: public Exception { public: InvalidBlockHeaderFormat(int _f, bytesConstRef _d): m_f(_f), m_d(_d.toBytes()) {} int m_f; bytes m_d; virtual std::string description() const { return "Invalid block header format: Bad field " + toString(m_f) + " (" + toHex(m_d) + ")"; } }; class InvalidUnclesHash: public Exception {}; class InvalidUncle: public Exception {}; -class UncleNotAnUncle: public Exception {}; +class UncleTooOld: public Exception {}; +class UncleInChain: public Exception {}; class DuplicateUncleNonce: public Exception {}; class InvalidStateRoot: public Exception {}; class InvalidTransactionsHash: public Exception { public: InvalidTransactionsHash(h256 _head, h256 _real): m_head(_head), m_real(_real) {} h256 m_head; h256 m_real; virtual std::string description() const { return "Invalid transactions hash: header says: " + toHex(m_head.ref()) + " block is:" + toHex(m_real.ref()); } }; diff --git a/libethereum/BlockChain.cpp b/libethereum/BlockChain.cpp index 842343480..06117c01b 100644 --- a/libethereum/BlockChain.cpp +++ b/libethereum/BlockChain.cpp @@ -386,6 +386,20 @@ void BlockChain::checkConsistency() delete it; } +h256Set BlockChain::allUnclesFrom(h256 _parent) const +{ + // Get all uncles cited given a parent (i.e. featured as uncles/main in parent, parent + 1, ... parent + 5). + h256Set ret; + h256 p = _parent; + for (unsigned i = 0; i < 6 && p != m_genesisHash; ++i, p = details(p).parent) + { + ret.insert(sha3(RLP(block(p))[0].data())); + for (auto i: RLP(block(p))[2]) + ret.insert(sha3(i.data())); + } + return ret; +} + bytes BlockChain::block(h256 _hash) const { if (_hash == m_genesisHash) diff --git a/libethereum/BlockChain.h b/libethereum/BlockChain.h index d13ef7614..8fcf09691 100644 --- a/libethereum/BlockChain.h +++ b/libethereum/BlockChain.h @@ -107,6 +107,11 @@ public: /// Get the hash of a block of a given number. Slow; try not to use it too much. h256 numberHash(unsigned _n) const; + /// Get all blocks not allowed as uncles given a parent (i.e. featured as uncles/main in parent, parent + 1, ... parent + 5). + /// @returns set including the header-hash of every parent (including @a _parent) up to and including generation +5 + /// togther with all their quoted uncles. + h256Set allUnclesFrom(h256 _parent) const; + /// @returns the genesis block header. static BlockInfo const& genesis() { UpgradableGuard l(x_genesis); if (!s_genesis) { auto gb = createGenesisBlock(); UpgradeGuard ul(l); (s_genesis = new BlockInfo)->populate(&gb); } return *s_genesis; } diff --git a/libethereum/State.cpp b/libethereum/State.cpp index 105c725f6..7b46228b4 100644 --- a/libethereum/State.cpp +++ b/libethereum/State.cpp @@ -390,7 +390,7 @@ u256 State::enactOn(bytesConstRef _block, BlockInfo const& _bi, BlockChain const sync(_bc, _bi.parentHash); resetCurrent(); m_previousBlock = biParent; - return enact(_block, biGrandParent); + return enact(_block, &_bc); } map State::addresses() const @@ -499,7 +499,7 @@ h256s State::sync(TransactionQueue& _tq, bool* o_transactionQueueChanged) return ret; } -u256 State::enact(bytesConstRef _block, BlockInfo const& _grandParent, bool _checkNonce) +u256 State::enact(bytesConstRef _block, BlockChain const* _bc, bool _checkNonce) { // m_currentBlock is assumed to be prepopulated and reset. @@ -558,16 +558,21 @@ u256 State::enact(bytesConstRef _block, BlockInfo const& _grandParent, bool _che // Check uncles & apply their rewards to state. set nonces = { m_currentBlock.nonce }; Addresses rewarded; + set knownUncles = _bc ? _bc->allUnclesFrom(m_currentBlock.parentHash) : set(); for (auto const& i: RLP(_block)[2]) { BlockInfo uncle = BlockInfo::fromHeader(i.data()); - - if (m_previousBlock.parentHash != uncle.parentHash) - throw UncleNotAnUncle(); if (nonces.count(uncle.nonce)) throw DuplicateUncleNonce(); - if (_grandParent) - uncle.verifyParent(_grandParent); + if (_bc) + { + BlockInfo uncleParent(_bc->block(uncle.parentHash)); + if ((bigint)uncleParent.number < (bigint)m_currentBlock.number - 6) // TODO: check 6. might be 7 or something... + throw UncleTooOld(); + if (knownUncles.count(uncle.hash)) + throw UncleInChain(); + uncle.verifyParent(uncleParent); + } nonces.insert(uncle.nonce); tdIncrease += uncle.difficulty; @@ -651,7 +656,7 @@ bool State::amIJustParanoid(BlockChain const& _bc) cnote << "PARANOIA root:" << s.rootHash(); // s.m_currentBlock.populate(&block.out(), false); // s.m_currentBlock.verifyInternals(&block.out()); - s.enact(&block.out(), BlockInfo(), false); // don't check nonce for this since we haven't mined it yet. + s.enact(&block.out(), &_bc, false); // don't check nonce for this since we haven't mined it yet. s.cleanup(false); return true; } @@ -694,6 +699,7 @@ void State::commitToMine(BlockChain const& _bc) if (m_previousBlock != BlockChain::genesis()) { + // TODO: find great-uncles (or second-cousins or whatever they are) - children of great-grandparents, great-great-grandparents... that were not already uncles in previous generations. // Find uncles if we're not a direct child of the genesis. // cout << "Checking " << m_previousBlock.hash << ", parent=" << m_previousBlock.parentHash << endl; auto us = _bc.details(m_previousBlock.parentHash).children; @@ -1181,8 +1187,8 @@ void State::applyRewards(Addresses const& _uncleAddresses) u256 r = m_blockReward; for (auto const& i: _uncleAddresses) { - addBalance(i, m_blockReward * 3 / 4); - r += m_blockReward / 8; + addBalance(i, m_blockReward * 15 / 16); + r += m_blockReward / 32; } addBalance(m_currentBlock.coinbaseAddress, r); } diff --git a/libethereum/State.h b/libethereum/State.h index c8fd27e01..c605ce42e 100644 --- a/libethereum/State.h +++ b/libethereum/State.h @@ -299,9 +299,9 @@ private: /// Commit all changes waiting in the address cache to the DB. void commit(); - /// Execute the given block, assuming it corresponds to m_currentBlock. If _grandParent is passed, it will be used to check the uncles. + /// Execute the given block, assuming it corresponds to m_currentBlock. If _bc is passed, it will be used to check the uncles. /// Throws on failure. - u256 enact(bytesConstRef _block, BlockInfo const& _grandParent = BlockInfo(), bool _checkNonce = true); + u256 enact(bytesConstRef _block, BlockChain const* _bc = nullptr, bool _checkNonce = true); // Two priviledged entry points for the VM (these don't get added to the Transaction lists): // We assume all instrinsic fees are paid up before this point. From 939ab1be343194c33acb43893cc72636fb8df8a4 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 24 Aug 2014 11:23:14 +0200 Subject: [PATCH 111/223] Multi-threaded mining. --- libethcore/Dagger.cpp | 2 +- libethcore/Dagger.h | 9 ++-- libethereum/Client.cpp | 95 ++++++++++++++++++++++++++++++++---------- libethereum/Client.h | 34 +++++++++------ libethereum/Miner.cpp | 3 ++ libethereum/Miner.h | 29 +++++++++---- 6 files changed, 123 insertions(+), 49 deletions(-) diff --git a/libethcore/Dagger.cpp b/libethcore/Dagger.cpp index 4b5841d82..8be21bdbb 100644 --- a/libethcore/Dagger.cpp +++ b/libethcore/Dagger.cpp @@ -39,7 +39,7 @@ namespace eth MineInfo Dagger::mine(h256& o_solution, h256 const& _root, u256 const& _difficulty, uint _msTimeout, bool _continue, bool _turbo) { - MineInfo ret{0.f, 1e99, 0, false}; + MineInfo ret; static std::mt19937_64 s_eng((time(0) + (unsigned)m_last)); u256 s = (m_last = h256::random(s_eng)); diff --git a/libethcore/Dagger.h b/libethcore/Dagger.h index e920549ce..61848f8c9 100644 --- a/libethcore/Dagger.h +++ b/libethcore/Dagger.h @@ -38,10 +38,11 @@ inline uint toLog2(u256 _d) struct MineInfo { - double requirement; - double best; - uint hashes; - bool completed; + void combine(MineInfo const& _m) { requirement = std::max(requirement, _m.requirement); best = std::min(best, _m.best); hashes += _m.hashes; completed = completed || _m.completed; } + double requirement = 0; + double best = 1e99; + uint hashes = 0; + bool completed = false; }; #if FAKE_DAGGER diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index 738beb9be..2ae0d7de4 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -56,9 +56,9 @@ Client::Client(std::string const& _clientVersion, Address _us, std::string const m_bc(_dbPath, !m_vc.ok() || _forceClean), m_stateDB(State::openDB(_dbPath, !m_vc.ok() || _forceClean)), m_preMine(_us, m_stateDB), - m_postMine(_us, m_stateDB), - m_miner(this) + m_postMine(_us, m_stateDB) { + setMiningThreads(); if (_dbPath.size()) Defaults::setDBPath(_dbPath); m_vc.setOk(); @@ -115,7 +115,11 @@ void Client::clearPending() appendFromNewPending(m_postMine.bloom(i), changeds); changeds.insert(PendingChangedFilter); m_postMine = m_preMine; - m_miner.noteStateChange(); + { + ReadGuard l(x_miners); + for (auto& m: m_miners) + m.noteStateChange(); + } noteChanged(changeds); } @@ -289,6 +293,46 @@ void Client::connect(std::string const& _seedHost, unsigned short _port) m_net->connect(_seedHost, _port); } +void Client::setMiningThreads(unsigned _threads) +{ + stopMining(); + + auto t = _threads ? _threads : thread::hardware_concurrency(); + WriteGuard l(x_miners); + m_miners.clear(); + m_miners.resize(t); + unsigned i = 0; + for (auto& m: m_miners) + m.setup(this, i++); +} + +MineProgress Client::miningProgress() const +{ + MineProgress ret; + ReadGuard l(x_miners); + for (auto& m: m_miners) + ret.combine(m.miningProgress()); + return ret; +} + +std::list Client::miningHistory() +{ + std::list ret; + ReadGuard l(x_miners); + if (m_miners.empty()) + return ret; + ret = m_miners[0].miningHistory(); + for (unsigned i = 1; i < m_miners.size(); ++i) + { + auto l = m_miners[i].miningHistory(); + auto ri = ret.begin(); + auto li = l.begin(); + for (; ri != ret.end() && li != l.end(); ++ri, ++li) + ri->combine(*li); + } + return ret; +} + void Client::setupState(State& _s) { { @@ -410,23 +454,26 @@ void Client::work() cworkin << "WORK"; h256Set changeds; - if (m_miner.isComplete()) - { - cwork << "CHAIN <== postSTATE"; - h256s hs; - { - WriteGuard l(x_stateDB); - hs = m_bc.attemptImport(m_miner.blockData(), m_stateDB); - } - if (hs.size()) + ReadGuard l(x_miners); + for (auto& m: m_miners) + if (m.isComplete()) { - for (auto h: hs) - appendFromNewBlock(h, changeds); - changeds.insert(ChainChangedFilter); - //changeds.insert(PendingChangedFilter); // if we mined the new block, then we've probably reset the pending transactions. + cwork << "CHAIN <== postSTATE"; + h256s hs; + { + WriteGuard l(x_stateDB); + hs = m_bc.attemptImport(m.blockData(), m_stateDB); + } + if (hs.size()) + { + for (auto h: hs) + appendFromNewBlock(h, changeds); + changeds.insert(ChainChangedFilter); + //changeds.insert(PendingChangedFilter); // if we mined the new block, then we've probably reset the pending transactions. + } + for (auto& m: m_miners) + m.noteStateChange(); } - m_miner.noteStateChange(); - } // Synchronise state to block chain. // This should remove any transactions on our queue that are included within our state. @@ -435,6 +482,7 @@ void Client::work() // if there are no checkpoints before our fork) reverting to the genesis block and replaying // all blocks. // Resynchronise state with block chain & trans + bool rsm = false; { WriteGuard l(x_stateDB); cwork << "BQ ==> CHAIN ==> STATE"; @@ -451,8 +499,6 @@ void Client::work() if (newBlocks.size()) m_stateDB = db; - bool rsm = false; - cwork << "preSTATE <== CHAIN"; if (m_preMine.sync(m_bc) || m_postMine.address() != m_preMine.address()) { @@ -474,9 +520,12 @@ void Client::work() cnote << "Additional transaction ready: Restarting mining operation."; rsm = true; } - - if (rsm) - m_miner.noteStateChange(); + } + if (rsm) + { + ReadGuard l(x_miners); + for (auto& m: m_miners) + m.noteStateChange(); } cwork << "noteChanged" << changeds.size() << "items"; diff --git a/libethereum/Client.h b/libethereum/Client.h index 4932097ac..b62a9066f 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -217,32 +217,39 @@ public: bool miningParanoia() const { return m_paranoia; } /// Change whether we check block validity prior to mining. void setParanoia(bool _p) { m_paranoia = _p; } + /// Should we force mining to happen, even without transactions? + bool forceMining() const { return m_forceMining; } + /// Enable/disable forcing of mining to happen, even without transactions. + void setForceMining(bool _enable) { m_forceMining = _enable; } + /// Are we mining as fast as we can? + bool turboMining() const { return m_turboMining; } + /// Enable/disable fast mining. + void setTurboMining(bool _enable = true) { m_turboMining = _enable; } + /// Set the coinbase address. void setAddress(Address _us) { m_preMine.setAddress(_us); } /// Get the coinbase address. Address address() const { return m_preMine.address(); } + /// Stops mining and sets the number of mining threads (0 for automatic). + void setMiningThreads(unsigned _threads = 0); + /// Get the effective number of mining threads. + unsigned miningThreads() const { ReadGuard l(x_miners); return m_miners.size(); } /// Start mining. - /// NOT thread-safe - void startMining() { m_miner.start(); ensureWorking(); } + /// NOT thread-safe - call it & stopMining only from a single thread + void startMining() { ensureWorking(); ReadGuard l(x_miners); for (auto& m: m_miners) m.start(); } /// Stop mining. /// NOT thread-safe - void stopMining() { m_miner.stop(); } + void stopMining() { ReadGuard l(x_miners); for (auto& m: m_miners) m.stop(); } /// Are we mining now? - bool isMining() { return m_miner.isRunning(); } + bool isMining() { ReadGuard l(x_miners); return m_miners.size() && m_miners[0].isRunning(); } /// Check the progress of the mining. - MineProgress miningProgress() const { return m_miner.miningProgress(); } + MineProgress miningProgress() const; /// Get and clear the mining history. - std::list miningHistory() { return m_miner.miningHistory(); } - - bool forceMining() const { return m_forceMining; } - void setForceMining(bool _enable) { m_forceMining = _enable; } + std::list miningHistory(); /// Clears pending transactions. Just for debug use. void clearPending(); - void setTurboMining(bool _enable = true) { m_turboMining = _enable; } - bool turboMining() const { return m_turboMining; } - private: /// Ensure the worker thread is running. Needed for blockchain maintenance & mining. void ensureWorking(); @@ -296,7 +303,8 @@ private: std::unique_ptr m_work; ///< The work thread. std::atomic m_workState; - Miner m_miner; + std::vector m_miners; + mutable boost::shared_mutex x_miners; bool m_paranoia = false; ///< Should we be paranoid about our state? bool m_turboMining = false; ///< Don't squander all of our time mining actually just sleeping. bool m_forceMining = false; ///< Mine even when there are no transactions pending? diff --git a/libethereum/Miner.cpp b/libethereum/Miner.cpp index 6fe714a5e..3c0a8b003 100644 --- a/libethereum/Miner.cpp +++ b/libethereum/Miner.cpp @@ -33,6 +33,9 @@ Miner::Miner(MinerHost* _host, unsigned _id): void Miner::start() { + if (!m_host) + return; + Guard l(x_work); if (!m_work) { diff --git a/libethereum/Miner.h b/libethereum/Miner.h index 7ddd54ea6..c0aa09d56 100644 --- a/libethereum/Miner.h +++ b/libethereum/Miner.h @@ -37,11 +37,12 @@ namespace eth */ struct MineProgress { - double requirement; ///< The PoW requirement - as the second logarithm of the minimum acceptable hash. - double best; ///< The PoW achievement - as the second logarithm of the minimum found hash. - double current; ///< The most recent PoW achievement - as the second logarithm of the presently found hash. - uint hashes; ///< Total number of hashes computed. - uint ms; ///< Total number of milliseconds of mining thus far. + void combine(MineProgress const& _m) { requirement = std::max(requirement, _m.requirement); best = std::min(best, _m.best); current = std::max(current, _m.current); hashes += _m.hashes; ms = std::max(ms, _m.ms); } + double requirement = 0; ///< The PoW requirement - as the second logarithm of the minimum acceptable hash. + double best = 1e99; ///< The PoW achievement - as the second logarithm of the minimum found hash. + double current = 0; ///< The most recent PoW achievement - as the second logarithm of the presently found hash. + uint hashes = 0; ///< Total number of hashes computed. + uint ms = 0; ///< Total number of milliseconds of mining thus far. }; /** @@ -74,12 +75,24 @@ public: class Miner { public: - /// Constructor. Starts miner. + /// Null constructor. + Miner(): m_host(nullptr), m_id(0) {} + + /// Constructor. Miner(MinerHost* _host, unsigned _id = 0); + /// Move-constructor. + Miner(Miner&& _m) { std::swap(m_host, _m.m_host); std::swap(m_id, _m.m_id); } + + /// Move-assignment. + Miner& operator=(Miner&& _m) { std::swap(m_host, _m.m_host); std::swap(m_id, _m.m_id); return *this; } + /// Destructor. Stops miner. ~Miner() { stop(); } + /// Setup its basics. + void setup(MinerHost* _host, unsigned _id = 0) { m_host = _host; m_id = _id; } + /// Start mining. void start(); @@ -108,8 +121,8 @@ private: /// Do some work on the mining. void work(); - MinerHost* m_host; ///< Our host. - unsigned m_id; ///< Our identity. + MinerHost* m_host = nullptr; ///< Our host. + unsigned m_id = 0; ///< Our identity. std::mutex x_work; ///< Mutex protecting the creation of the work thread. std::unique_ptr m_work; ///< The work thread. From ba9d3d9248dc15c16f2a51224735e1e15df0926b Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 24 Aug 2014 13:56:38 +0200 Subject: [PATCH 112/223] Minor PoC-6 fix. --- libethereum/ExtVM.h | 8 ++++---- libevm/ExtVMFace.h | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/libethereum/ExtVM.h b/libethereum/ExtVM.h index 737e003dd..7709f9d7f 100644 --- a/libethereum/ExtVM.h +++ b/libethereum/ExtVM.h @@ -62,12 +62,12 @@ public: return ret; } - /// Create a new message call. - bool call(Address _receiveAddress, u256 _txValue, bytesConstRef _txData, u256* _gas, bytesRef _out, OnOpFunc const& _onOp = OnOpFunc()) + /// Create a new message call. Leave _myAddressOverride at he default to use the present address as caller. + bool call(Address _receiveAddress, u256 _txValue, bytesConstRef _txData, u256* _gas, bytesRef _out, OnOpFunc const& _onOp = OnOpFunc(), Address _myAddressOverride = Address()) { if (m_ms) m_ms->internal.resize(m_ms->internal.size() + 1); - auto ret = m_s.call(_receiveAddress, myAddress, _txValue, gasPrice, _txData, _gas, _out, origin, &suicides, &posts, m_ms ? &(m_ms->internal.back()) : nullptr, _onOp, level + 1); + auto ret = m_s.call(_receiveAddress, _myAddressOverride ? _myAddressOverride : myAddress, _txValue, gasPrice, _txData, _gas, _out, origin, &suicides, &posts, m_ms ? &(m_ms->internal.back()) : nullptr, _onOp, level + 1); if (m_ms && !m_ms->internal.back().from) m_ms->internal.pop_back(); return ret; @@ -100,7 +100,7 @@ public: while (posts.size()) { Post& p = posts.front(); - call(p.to, p.value, &p.data, &p.gas, bytesRef(), _onOp); + call(p.to, p.value, &p.data, &p.gas, bytesRef(), _onOp, p.from); ret += p.gas; posts.pop_front(); } diff --git a/libevm/ExtVMFace.h b/libevm/ExtVMFace.h index 3fe7bea5a..7aa8df8e5 100644 --- a/libevm/ExtVMFace.h +++ b/libevm/ExtVMFace.h @@ -31,6 +31,7 @@ namespace eth struct Post { + Address from; Address to; u256 value; bytes data; @@ -79,7 +80,7 @@ public: bool call(Address, u256, bytesConstRef, u256*, bytesRef) { return false; } /// Post a new message call. - void post(Address _to, u256 _value, bytesConstRef _data, u256 _gas) { posts.push_back(Post({_to, _value, _data.toBytes(), _gas})); } + void post(Address _to, u256 _value, bytesConstRef _data, u256 _gas) { posts.push_back(Post({myAddress, _to, _value, _data.toBytes(), _gas})); } /// Revert any changes made (by any of the other calls). void revert() {} From 1078681ccb2ad7ed39e7e2c8ecff772c6ec58749 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 24 Aug 2014 16:30:24 +0200 Subject: [PATCH 113/223] Better uncle building. Repotting. --- libethcore/BlockInfo.cpp | 5 +++ libethcore/BlockInfo.h | 3 ++ libethereum/AccountDiff.cpp | 77 ++++++++++++++++++++++++++++++++++ libethereum/AccountDiff.h | 73 ++++++++++++++++++++++++++++++++ libethereum/State.cpp | 83 ++++++++----------------------------- libethereum/State.h | 41 +----------------- 6 files changed, 177 insertions(+), 105 deletions(-) create mode 100644 libethereum/AccountDiff.cpp create mode 100644 libethereum/AccountDiff.h diff --git a/libethcore/BlockInfo.cpp b/libethcore/BlockInfo.cpp index 4c71353d6..0b56c6c85 100644 --- a/libethcore/BlockInfo.cpp +++ b/libethcore/BlockInfo.cpp @@ -64,6 +64,11 @@ void BlockInfo::fillStream(RLPStream& _s, bool _nonce) const _s << nonce; } +h256 BlockInfo::headerHash(bytesConstRef _block) +{ + return sha3(RLP(_block)[0].data()); +} + void BlockInfo::populateFromHeader(RLP const& _header, bool _checkNonce) { int field = 0; diff --git a/libethcore/BlockInfo.h b/libethcore/BlockInfo.h index ecec391c1..cf9735c7d 100644 --- a/libethcore/BlockInfo.h +++ b/libethcore/BlockInfo.h @@ -73,6 +73,9 @@ public: explicit BlockInfo(bytes const& _block): BlockInfo(&_block) {} explicit BlockInfo(bytesConstRef _block); + static h256 headerHash(bytes const& _block) { return headerHash(&_block); } + static h256 headerHash(bytesConstRef _block); + static BlockInfo fromHeader(bytesConstRef _block); explicit operator bool() const { return timestamp != Invalid256; } diff --git a/libethereum/AccountDiff.cpp b/libethereum/AccountDiff.cpp new file mode 100644 index 000000000..5b469508f --- /dev/null +++ b/libethereum/AccountDiff.cpp @@ -0,0 +1,77 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file AccountDiff.cpp + * @author Gav Wood + * @date 2014 + */ + +#include "AccountDiff.h" + +#include +using namespace std; +using namespace eth; + +AccountChange AccountDiff::changeType() const +{ + bool bn = (balance || nonce); + bool sc = (!storage.empty() || code); + return exist ? exist.from() ? AccountChange::Deletion : AccountChange::Creation : (bn && sc) ? AccountChange::All : bn ? AccountChange::Intrinsic: sc ? AccountChange::CodeStorage : AccountChange::None; +} + +char const* AccountDiff::lead() const +{ + bool bn = (balance || nonce); + bool sc = (!storage.empty() || code); + return exist ? exist.from() ? "XXX" : "+++" : (bn && sc) ? "***" : bn ? " * " : sc ? "* *" : " "; +} + +std::ostream& eth::operator<<(std::ostream& _out, AccountDiff const& _s) +{ + if (!_s.exist.to()) + return _out; + + if (_s.nonce) + { + _out << std::dec << "#" << _s.nonce.to() << " "; + if (_s.nonce.from()) + _out << "(" << std::showpos << (((bigint)_s.nonce.to()) - ((bigint)_s.nonce.from())) << std::noshowpos << ") "; + } + if (_s.balance) + { + _out << std::dec << _s.balance.to() << " "; + if (_s.balance.from()) + _out << "(" << std::showpos << (((bigint)_s.balance.to()) - ((bigint)_s.balance.from())) << std::noshowpos << ") "; + } + if (_s.code) + _out << "$" << std::hex << nouppercase << _s.code.to() << " (" << _s.code.from() << ") "; + for (pair> const& i: _s.storage) + if (!i.second.from()) + _out << endl << " + " << (h256)i.first << ": " << std::hex << nouppercase << i.second.to(); + else if (!i.second.to()) + _out << endl << "XXX " << (h256)i.first << " (" << std::hex << nouppercase << i.second.from() << ")"; + else + _out << endl << " * " << (h256)i.first << ": " << std::hex << nouppercase << i.second.to() << " (" << i.second.from() << ")"; + return _out; +} + +std::ostream& eth::operator<<(std::ostream& _out, StateDiff const& _s) +{ + _out << _s.accounts.size() << " accounts changed:" << endl; + for (auto const& i: _s.accounts) + _out << i.second.lead() << " " << i.first << ": " << i.second << endl; + return _out; +} diff --git a/libethereum/AccountDiff.h b/libethereum/AccountDiff.h new file mode 100644 index 000000000..a4a074e04 --- /dev/null +++ b/libethereum/AccountDiff.h @@ -0,0 +1,73 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file AccountDiff.h + * @author Gav Wood + * @date 2014 + */ + +#pragma once + +#include +#include + +namespace eth +{ + +enum class ExistDiff { Same, New, Dead }; +template +class Diff +{ +public: + Diff() {} + Diff(T _from, T _to): m_from(_from), m_to(_to) {} + + T const& from() const { return m_from; } + T const& to() const { return m_to; } + + explicit operator bool() const { return m_from != m_to; } + +private: + T m_from; + T m_to; +}; + +enum class AccountChange { None, Creation, Deletion, Intrinsic, CodeStorage, All }; + +struct AccountDiff +{ + inline bool changed() const { return storage.size() || code || nonce || balance || exist; } + char const* lead() const; + AccountChange changeType() const; + + Diff exist; + Diff balance; + Diff nonce; + std::map> storage; + Diff code; +}; + +struct StateDiff +{ + std::map accounts; +}; + +std::ostream& operator<<(std::ostream& _out, StateDiff const& _s); +std::ostream& operator<<(std::ostream& _out, AccountDiff const& _s); + +} + + diff --git a/libethereum/State.cpp b/libethereum/State.cpp index 7b46228b4..6393e2103 100644 --- a/libethereum/State.cpp +++ b/libethereum/State.cpp @@ -567,9 +567,9 @@ u256 State::enact(bytesConstRef _block, BlockChain const* _bc, bool _checkNonce) if (_bc) { BlockInfo uncleParent(_bc->block(uncle.parentHash)); - if ((bigint)uncleParent.number < (bigint)m_currentBlock.number - 6) // TODO: check 6. might be 7 or something... + if ((bigint)uncleParent.number < (bigint)m_currentBlock.number - 6) throw UncleTooOld(); - if (knownUncles.count(uncle.hash)) + if (knownUncles.count(sha3(i.data()))) throw UncleInChain(); uncle.verifyParent(uncleParent); } @@ -699,19 +699,23 @@ void State::commitToMine(BlockChain const& _bc) if (m_previousBlock != BlockChain::genesis()) { - // TODO: find great-uncles (or second-cousins or whatever they are) - children of great-grandparents, great-great-grandparents... that were not already uncles in previous generations. - // Find uncles if we're not a direct child of the genesis. + // Find great-uncles (or second-cousins or whatever they are) - children of great-grandparents, great-great-grandparents... that were not already uncles in previous generations. // cout << "Checking " << m_previousBlock.hash << ", parent=" << m_previousBlock.parentHash << endl; - auto us = _bc.details(m_previousBlock.parentHash).children; - 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) - 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); - } + set knownUncles = _bc.allUnclesFrom(m_currentBlock.parentHash); + auto p = m_previousBlock.parentHash; + for (unsigned gen = 0; gen < 6 && p != _bc.genesisHash(); ++gen, p = _bc.details(p).parent) + { + auto us = _bc.details(p).children; + 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) + if (!knownUncles.count(BlockInfo::headerHash(_bc.block(u)))) // ignore any uncles/mainline blocks that we know about. We use header-hash for this. + { + BlockInfo ubi(_bc.block(u)); + ubi.fillStream(uncles, true); + uncleAddresses.push_back(ubi.coinbaseAddress); + } + } } else uncles.appendList(0); @@ -1268,54 +1272,3 @@ std::ostream& eth::operator<<(std::ostream& _out, State const& _s) } return _out; } - -AccountChange AccountDiff::changeType() const -{ - bool bn = (balance || nonce); - bool sc = (!storage.empty() || code); - return exist ? exist.from() ? AccountChange::Deletion : AccountChange::Creation : (bn && sc) ? AccountChange::All : bn ? AccountChange::Intrinsic: sc ? AccountChange::CodeStorage : AccountChange::None; -} - -char const* AccountDiff::lead() const -{ - bool bn = (balance || nonce); - bool sc = (!storage.empty() || code); - return exist ? exist.from() ? "XXX" : "+++" : (bn && sc) ? "***" : bn ? " * " : sc ? "* *" : " "; -} - -std::ostream& eth::operator<<(std::ostream& _out, AccountDiff const& _s) -{ - if (!_s.exist.to()) - return _out; - - if (_s.nonce) - { - _out << std::dec << "#" << _s.nonce.to() << " "; - if (_s.nonce.from()) - _out << "(" << std::showpos << (((bigint)_s.nonce.to()) - ((bigint)_s.nonce.from())) << std::noshowpos << ") "; - } - if (_s.balance) - { - _out << std::dec << _s.balance.to() << " "; - if (_s.balance.from()) - _out << "(" << std::showpos << (((bigint)_s.balance.to()) - ((bigint)_s.balance.from())) << std::noshowpos << ") "; - } - if (_s.code) - _out << "$" << std::hex << nouppercase << _s.code.to() << " (" << _s.code.from() << ") "; - for (pair> const& i: _s.storage) - if (!i.second.from()) - _out << endl << " + " << (h256)i.first << ": " << std::hex << nouppercase << i.second.to(); - else if (!i.second.to()) - _out << endl << "XXX " << (h256)i.first << " (" << std::hex << nouppercase << i.second.from() << ")"; - else - _out << endl << " * " << (h256)i.first << ": " << std::hex << nouppercase << i.second.to() << " (" << i.second.from() << ")"; - return _out; -} - -std::ostream& eth::operator<<(std::ostream& _out, StateDiff const& _s) -{ - _out << _s.accounts.size() << " accounts changed:" << endl; - for (auto const& i: _s.accounts) - _out << i.second.lead() << " " << i.first << ": " << i.second << endl; - return _out; -} diff --git a/libethereum/State.h b/libethereum/State.h index c605ce42e..879e5e741 100644 --- a/libethereum/State.h +++ b/libethereum/State.h @@ -36,6 +36,7 @@ #include "AddressState.h" #include "Transaction.h" #include "Executive.h" +#include "AccountDiff.h" namespace eth { @@ -64,44 +65,6 @@ struct TransactionReceipt Manifest changes; }; -enum class ExistDiff { Same, New, Dead }; -template -class Diff -{ -public: - Diff() {} - Diff(T _from, T _to): m_from(_from), m_to(_to) {} - - T const& from() const { return m_from; } - T const& to() const { return m_to; } - - explicit operator bool() const { return m_from != m_to; } - -private: - T m_from; - T m_to; -}; - -enum class AccountChange { None, Creation, Deletion, Intrinsic, CodeStorage, All }; - -struct AccountDiff -{ - inline bool changed() const { return storage.size() || code || nonce || balance || exist; } - char const* lead() const; - AccountChange changeType() const; - - Diff exist; - Diff balance; - Diff nonce; - std::map> storage; - Diff code; -}; - -struct StateDiff -{ - std::map accounts; -}; - /** * @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). @@ -355,8 +318,6 @@ private: }; std::ostream& operator<<(std::ostream& _out, State const& _s); -std::ostream& operator<<(std::ostream& _out, StateDiff const& _s); -std::ostream& operator<<(std::ostream& _out, AccountDiff const& _s); template void commit(std::map const& _cache, DB& _db, TrieDB& _state) From 2250f100e98194349f2a932e8ae0ee2344a09a6d Mon Sep 17 00:00:00 2001 From: Tim Hughes Date: Sun, 24 Aug 2014 23:29:17 +0100 Subject: [PATCH 114/223] Fixed minor VS2013 compile errors and updated projects. --- libethereum/MessageFilter.h | 2 +- windows/LibEthereum.vcxproj | 6 ++++++ windows/LibEthereum.vcxproj.filters | 18 ++++++++++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/libethereum/MessageFilter.h b/libethereum/MessageFilter.h index 55d40d270..cefc424c0 100644 --- a/libethereum/MessageFilter.h +++ b/libethereum/MessageFilter.h @@ -29,7 +29,7 @@ namespace eth { -class Manifest; +struct Manifest; class State; class MessageFilter diff --git a/windows/LibEthereum.vcxproj b/windows/LibEthereum.vcxproj index 454aee06a..95277bbfa 100644 --- a/windows/LibEthereum.vcxproj +++ b/windows/LibEthereum.vcxproj @@ -35,6 +35,7 @@ + @@ -44,6 +45,8 @@ + + @@ -91,6 +94,7 @@ + @@ -100,6 +104,8 @@ + + diff --git a/windows/LibEthereum.vcxproj.filters b/windows/LibEthereum.vcxproj.filters index 2fb0c3a39..3280140fc 100644 --- a/windows/LibEthereum.vcxproj.filters +++ b/windows/LibEthereum.vcxproj.filters @@ -127,6 +127,15 @@ libethereum + + libethereum + + + libethereum + + + libethereum + @@ -282,6 +291,15 @@ libethereum + + libethereum + + + libethereum + + + libethereum + From bd4770944ef3cc87f9d5c8fa4f82eff45582fb6d Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 26 Aug 2014 12:53:35 +0200 Subject: [PATCH 115/223] Initial commits for skeletons of IEthXi and libethereumx. --- CMakeLists.txt | 2 + exp/main.cpp | 260 ++++++++++++++++++++++++++++++++++++ iethxi/CMakeLists.txt | 117 ++++++++++++++++ iethxi/Main.ui | 168 +++++++++++++++++++++++ iethxi/MainWin.cpp | 59 ++++++++ iethxi/MainWin.h | 18 +++ iethxi/Resources.qrc | 5 + iethxi/main.cpp | 9 ++ libethcore/Exceptions.h | 2 + libethereum/BlockChain.cpp | 10 +- libethereum/State.cpp | 3 + libethereumx/CMakeLists.txt | 66 +++++++++ libethereumx/Ethereum.cpp | 125 +++++++++++++++++ libethereumx/Ethereum.h | 144 ++++++++++++++++++++ 14 files changed, 984 insertions(+), 4 deletions(-) create mode 100644 iethxi/CMakeLists.txt create mode 100644 iethxi/Main.ui create mode 100644 iethxi/MainWin.cpp create mode 100644 iethxi/MainWin.h create mode 100644 iethxi/Resources.qrc create mode 100644 iethxi/main.cpp create mode 100644 libethereumx/CMakeLists.txt create mode 100644 libethereumx/Ethereum.cpp create mode 100644 libethereumx/Ethereum.h diff --git a/CMakeLists.txt b/CMakeLists.txt index b5f8c6521..34b671502 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -340,6 +340,7 @@ if (NOT LANGUAGES) add_subdirectory(libethcore) add_subdirectory(libevm) add_subdirectory(libethereum) + add_subdirectory(libethereumx) add_subdirectory(test) add_subdirectory(eth) if("x${CMAKE_BUILD_TYPE}" STREQUAL "xDebug") @@ -361,6 +362,7 @@ if (NOT LANGUAGES) add_subdirectory(alethzero) add_subdirectory(third) if(QTQML) + add_subdirectory(iethxi) add_subdirectory(walleth) endif() endif() diff --git a/exp/main.cpp b/exp/main.cpp index caae262c5..7406a883d 100644 --- a/exp/main.cpp +++ b/exp/main.cpp @@ -19,6 +19,12 @@ * @date 2014 * Ethereum client. */ +#if 0 +#define BOOST_RESULT_OF_USE_DECLTYPE +#define BOOST_SPIRIT_USE_PHOENIX_V3 +#include +#include +#include #include #include @@ -27,9 +33,263 @@ #include "BuildInfo.h" using namespace std; using namespace eth; +namespace qi = boost::spirit::qi; +namespace px = boost::phoenix; +namespace sp = boost::spirit; + +#if 0 +class ASTSymbol: public string +{ +public: + ASTSymbol() {} +}; + +enum class ASTType +{ + Symbol, + IntegerLiteral, + StringLiteral, + Call, + Return, + Operator, + Compound +}; + +class ASTNode: public vector +{ +public: + ASTNode() {} + ASTNode(ASTSymbol const& _s): m_type(ASTType::Symbol), m_s(_s) {} + ASTNode(string const& _s): m_type(ASTType::StringLiteral), m_s(_s) {} + ASTNode(bigint const& _i): m_type(ASTType::IntegerLiteral), m_i(_i) {} + ASTNode(ASTType _t): m_type(_t) {} + + ASTNode& operator=(ASTSymbol const& _s) { m_type = ASTType::Symbol; m_s = _s; return *this; } + ASTNode& operator=(string const& _s) { m_type = ASTType::StringLiteral; m_s = _s; return *this; } + ASTNode& operator=(bigint const& _i) { m_type = ASTType::IntegerLiteral; m_i = _i; return *this; } + ASTNode& operator=(ASTType const& _s) { m_type = _s; return *this; } + + void debugOut(ostream& _out) const; + +private: + ASTType m_type; + string m_s; + bigint m_i; +}; + +void parseTree(string const& _s, ASTNode& o_out) +{ + using qi::standard::space; + using qi::standard::space_type; + typedef string::const_iterator it; + +/* static const u256 ether = u256(1000000000) * 1000000000; + static const u256 finney = u256(1000000000) * 1000000; + static const u256 szabo = u256(1000000000) * 1000;*/ + + qi::rule element; + qi::rule call; + qi::rule str = '"' > qi::lexeme[+(~qi::char_(std::string("\"") + '\0'))] > '"'; + qi::rule symbol = qi::lexeme[+(~qi::char_(std::string(" $@[]{}:();\"\x01-\x1f\x7f") + '\0'))]; +/* qi::rule strsh = '\'' > qi::lexeme[+(~qi::char_(std::string(" ;$@()[]{}:\n\t") + '\0'))]; + qi::rule intstr = qi::lexeme[ qi::no_case["0x"][qi::_val = "0x"] >> *qi::char_("0-9a-fA-F")[qi::_val += qi::_1]] | qi::lexeme[+qi::char_("0-9")[qi::_val += qi::_1]]; + qi::rule integer = intstr; + qi::rule multiplier = qi::lit("wei")[qi::_val = 1] | qi::lit("szabo")[qi::_val = szabo] | qi::lit("finney")[qi::_val = finney] | qi::lit("ether")[qi::_val = ether]; + qi::rule quantity = integer[qi::_val = qi::_1] >> -multiplier[qi::_val *= qi::_1]; + qi::rule atom = quantity[qi::_val = px::construct(px::new_(qi::_1))] | (str | strsh)[qi::_val = qi::_1] | symbol[qi::_val = qi::_1]; + qi::rule seq = '{' > *element > '}'; + qi::rule mload = '@' > element; + qi::rule sload = qi::lit("@@") > element; + qi::rule mstore = '[' > element > ']' > -qi::lit(":") > element; + qi::rule sstore = qi::lit("[[") > element > qi::lit("]]") > -qi::lit(":") > element; + qi::rule calldataload = qi::lit("$") > element; + qi::rule list = '(' > *element > ')'; + + qi::rule extra = sload[tagNode<2>()] | mload[tagNode<1>()] | sstore[tagNode<4>()] | mstore[tagNode<3>()] | seq[tagNode<5>()] | calldataload[tagNode<6>()];*/ + qi::rule value = call[qi::_val = ASTType::Call] | str[qi::_val = qi::_1] | symbol[qi::_val = qi::_1]; + qi::rule compound = '{' > *element > '}'; + call = '(' > *value > ')'; //symbol > '(' > !(value > *(',' > value)) > ')'; + element = compound[qi::_val = ASTType::Compound] | value[qi::_val = qi::_1]; + + auto ret = _s.cbegin(); + qi::phrase_parse(ret, _s.cend(), element, space, qi::skip_flag::dont_postskip, o_out); + for (auto i = ret; i != _s.cend(); ++i) + if (!isspace(*i)) + throw std::exception(); +} + +void ASTNode::debugOut(ostream& _out) const +{ + switch (m_type) + { + case ASTType::StringLiteral: + _out << "\"" << m_s << "\""; + break; + case ASTType::Symbol: + _out << m_s; + break; + case ASTType::Compound: + { + unsigned n = 0; + _out << "{"; + for (auto const& i: *this) + { + i.debugOut(_out); + _out << ";"; + ++n; + } + _out << "}"; + break; + } + case ASTType::Call: + { + unsigned n = 0; + for (auto const& i: *this) + { + i.debugOut(_out); + if (n == 0) + _out << "("; + else if (n < size() - 1) + _out << ","; + if (n == size() - 1) + _out << ")"; + ++n; + } + break; + } + default: + _out << "nil"; + } +} int main(int, char**) { + ASTNode out; + parseTree("{x}", out); + out.debugOut(cout); + cout << endl; + return 0; +} +#endif + +void killBigints(sp::utree const& _this) +{ + switch (_this.which()) + { + case sp::utree_type::list_type: for (auto const& i: _this) killBigints(i); break; + case sp::utree_type::any_type: delete _this.get(); break; + default:; + } +} + +void debugOutAST(ostream& _out, sp::utree const& _this) +{ + switch (_this.which()) + { + case sp::utree_type::list_type: + switch (_this.tag()) + { + case 0: { int n = 0; for (auto const& i: _this) { debugOutAST(_out, i); if (n++) _out << ", "; } break; } + case 1: _out << "@ "; debugOutAST(_out, _this.front()); break; + case 2: _out << "@@ "; debugOutAST(_out, _this.front()); break; + case 3: _out << "[ "; debugOutAST(_out, _this.front()); _out << " ] "; debugOutAST(_out, _this.back()); break; + case 4: _out << "[[ "; debugOutAST(_out, _this.front()); _out << " ]] "; debugOutAST(_out, _this.back()); break; + case 5: _out << "{ "; for (auto const& i: _this) { debugOutAST(_out, i); _out << " "; } _out << "}"; break; + case 6: _out << "$ "; debugOutAST(_out, _this.front()); break; + default: + { _out << _this.tag() << ": "; int n = 0; for (auto const& i: _this) { debugOutAST(_out, i); if (n++) _out << ", "; } break; } + } + + break; + case sp::utree_type::int_type: _out << _this.get(); break; + case sp::utree_type::string_type: _out << "\"" << _this.get, sp::utree_type::string_type>>() << "\""; break; + case sp::utree_type::symbol_type: _out << _this.get, sp::utree_type::symbol_type>>(); break; + case sp::utree_type::any_type: _out << *_this.get(); break; + default: _out << "nil"; + } +} + +namespace eth { +namespace parseTreeLLL_ { + +template +struct tagNode +{ + void operator()(sp::utree& n, qi::rule::context_type& c) const + { + (boost::fusion::at_c<0>(c.attributes) = n).tag(N); + } +}; + +}} + +void parseTree(string const& _s, sp::utree& o_out) +{ + using qi::standard::space; + using qi::standard::space_type; + using eth::parseTreeLLL_::tagNode; + typedef sp::basic_string symbol_type; + typedef string::const_iterator it; + + static const u256 ether = u256(1000000000) * 1000000000; + static const u256 finney = u256(1000000000) * 1000000; + static const u256 szabo = u256(1000000000) * 1000; +#if 0 + qi::rule element; + qi::rule statement; + qi::rule str = '"' > qi::lexeme[+(~qi::char_(std::string("\"") + '\0'))] > '"'; + qi::rule strsh = '\'' > qi::lexeme[+(~qi::char_(std::string(" ;$@()[]{}:\n\t") + '\0'))]; + qi::rule symbol = qi::lexeme[+(~qi::char_(std::string(" $@[]{}:();\"\x01-\x1f\x7f") + '\0'))]; + qi::rule intstr = qi::lexeme[ qi::no_case["0x"][qi::_val = "0x"] >> *qi::char_("0-9a-fA-F")[qi::_val += qi::_1]] | qi::lexeme[+qi::char_("0-9")[qi::_val += qi::_1]]; + qi::rule integer = intstr; + qi::rule multiplier = qi::lit("wei")[qi::_val = 1] | qi::lit("szabo")[qi::_val = szabo] | qi::lit("finney")[qi::_val = finney] | qi::lit("ether")[qi::_val = ether]; + qi::rule quantity = integer[qi::_val = qi::_1] >> -multiplier[qi::_val *= qi::_1]; + qi::rule atom = quantity[qi::_val = px::construct(px::new_(qi::_1))] | (str | strsh)[qi::_val = qi::_1] | symbol[qi::_val = qi::_1]; + qi::rule compound = '{' > *statement > '}'; +/* qi::rule mload = '@' > element; + qi::rule sload = qi::lit("@@") > element; + qi::rule mstore = '[' > element > ']' > -qi::lit(":") > element; + qi::rule sstore = qi::lit("[[") > element > qi::lit("]]") > -qi::lit(":") > element; + qi::rule calldataload = qi::lit("$") > element;*/ +// qi::rule args = '(' > (element % ',') > ')'; + qi::rule expression; + qi::rule group = '(' >> expression[qi::_val = qi::_1] >> ')'; + qi::rule factor = atom | group; + qi::rule mul = '*' >> factor; + qi::rule div = '/' >> factor; + qi::rule op = mul[tagNode<10>()] | div[tagNode<11>()]; + qi::rule term = factor >> !op; + expression = term >> !(('+' >> term) | ('-' >> term)); + + // qi::rule extra = sload[tagNode<2>()] | mload[tagNode<1>()] | sstore[tagNode<4>()] | mstore[tagNode<3>()] | calldataload[tagNode<6>()]; + statement = compound[tagNode<5>()] | (element > ';')[qi::_val = qi::_1]; + element %= expression;// | extra; +#endif + qi::rule symbol = qi::lexeme[+(~qi::char_(std::string(" $@[]{}:();\"\x01-\x1f\x7f") + '\0'))]; + qi::rule intstr = qi::lexeme[ qi::no_case["0x"][qi::_val = "0x"] >> *qi::char_("0-9a-fA-F")[qi::_val += qi::_1]] | qi::lexeme[+qi::char_("0-9")[qi::_val += qi::_1]]; + qi::rule integer = intstr; + qi::rule intnode = integer[qi::_val = px::construct(px::new_(qi::_1))]; + qi::rule funcname = symbol; + qi::rule statement; + qi::rule call = funcname > '(' > funcname > ')'; + statement = call | intnode | symbol; + + auto ret = _s.cbegin(); + qi::phrase_parse(ret, _s.cend(), statement, space, qi::skip_flag::dont_postskip, o_out); + for (auto i = ret; i != _s.cend(); ++i) + if (!isspace(*i)) + throw std::exception(); +} +#endif +int main(int, char**) +{ +#if 0 + sp::utree out; + parseTree("x(2)", out); + debugOutAST(cout, out); + killBigints(out); + cout << endl; +#endif return 0; } diff --git a/iethxi/CMakeLists.txt b/iethxi/CMakeLists.txt new file mode 100644 index 000000000..fc8edf1dc --- /dev/null +++ b/iethxi/CMakeLists.txt @@ -0,0 +1,117 @@ +cmake_policy(SET CMP0015 NEW) + +if ("${TARGET_PLATFORM}" STREQUAL "w64") + cmake_policy(SET CMP0020 NEW) +endif () + + +set(CMAKE_INCLUDE_CURRENT_DIR ON) +aux_source_directory(. SRC_LIST) + +include_directories(..) +link_directories(../libethcore) +link_directories(../libethereum) +link_directories(../libqethereum) + +# Find Qt5 for Apple and update src_list for windows +if (APPLE) + # homebrew defaults to qt4 and installs qt5 as 'keg-only' + # which places it into /usr/local/opt insteadof /usr/local. + + set(CMAKE_PREFIX_PATH /usr/local/opt/qt5) + include_directories(/usr/local/opt/qt5/include /usr/local/include) +elseif ("${TARGET_PLATFORM}" STREQUAL "w64") + set(SRC_LIST ${SRC_LIST} ../windows/qt_plugin_import.cpp) +elseif (UNIX) + set(CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} ";$ENV{QTDIR}/lib/cmake") +endif () + + +find_package(Qt5Widgets REQUIRED) +find_package(Qt5Gui REQUIRED) +find_package(Qt5Quick REQUIRED) +find_package(Qt5Qml REQUIRED) +find_package(Qt5Network REQUIRED) +qt5_wrap_ui(ui_Main.h Main.ui) +qt5_add_resources(RESOURCE_ADDED Resources.qrc) + +# Set name of binary and add_executable() +if (APPLE) + set(EXECUTEABLE IEthXi) + set(CMAKE_INSTALL_PREFIX ./) + set(BIN_INSTALL_DIR ".") + set(DOC_INSTALL_DIR ".") + + set(PROJECT_VERSION "${ETH_VERSION}") + set(MACOSX_BUNDLE_INFO_STRING "${PROJECT_NAME} ${PROJECT_VERSION}") + set(MACOSX_BUNDLE_BUNDLE_VERSION "${PROJECT_NAME} ${PROJECT_VERSION}") + set(MACOSX_BUNDLE_LONG_VERSION_STRING "${PROJECT_NAME} ${PROJECT_VERSION}") + set(MACOSX_BUNDLE_SHORT_VERSION_STRING "${PROJECT_VERSION}") + set(MACOSX_BUNDLE_COPYRIGHT "${PROJECT_COPYRIGHT_YEAR} ${PROJECT_VENDOR}") + set(MACOSX_BUNDLE_GUI_IDENTIFIER "${PROJECT_DOMAIN_SECOND}.${PROJECT_DOMAIN_FIRST}") + set(MACOSX_BUNDLE_BUNDLE_NAME ${EXECUTEABLE}) + include(BundleUtilities) + + add_executable(${EXECUTEABLE} MACOSX_BUNDLE Main.ui ${RESOURCE_ADDED} ${SRC_LIST}) +else () + set(EXECUTEABLE iethxi) + add_executable(${EXECUTEABLE} Main.ui ${RESOURCE_ADDED} ${SRC_LIST}) +endif () + +qt5_use_modules(${EXECUTEABLE} Core Gui Widgets Network Quick Qml) +target_link_libraries(${EXECUTEABLE} qethereum ethereum secp256k1 ${CRYPTOPP_LS}) + +if (APPLE) + if (${ADDFRAMEWORKS}) + set_target_properties(${EXECUTEABLE} PROPERTIES MACOSX_BUNDLE_INFO_PLIST "${CMAKE_CURRENT_SOURCE_DIR}/EthereumMacOSXBundleInfo.plist.in") + endif () + + SET_SOURCE_FILES_PROPERTIES(${EXECUTEABLE} PROPERTIES MACOSX_PACKAGE_LOCATION MacOS) + + # This is a workaround for when the build-type defaults to Debug, and when a multi-config generator like xcode is used, where the type + # will not be set but defaults to release. + set(generator_lowercase "${CMAKE_GENERATOR}") + string(TOLOWER "${CMAKE_GENERATOR}" generator_lowercase) + if ("${generator_lowercase}" STREQUAL "xcode") + # TODO: Not sure how to resolve this. Possibly \${TARGET_BUILD_DIR} + set(binary_build_dir "${CMAKE_CURRENT_BINARY_DIR}/Debug") + else () + set(binary_build_dir "${CMAKE_CURRENT_BINARY_DIR}") + endif () + + set(APPS ${binary_build_dir}/${EXECUTEABLE}.app) + + # This tool and the next will automatically looked at the linked libraries in order to determine what dependencies are required. Thus, target_link_libaries only needs to add ethereum and secp256k1 (above) + install(CODE " + include(BundleUtilities) + set(BU_CHMOD_BUNDLE_ITEMS 1) + fixup_bundle(\"${APPS}\" \"${BUNDLELIBS}\" \"../libqethereum ../libethereum ../secp256k1\") + " COMPONENT RUNTIME ) + + if (${ADDFRAMEWORKS}) + add_custom_target(addframeworks ALL + COMMAND /usr/local/opt/qt5/bin/macdeployqt ${binary_build_dir}/${EXECUTEABLE}.app + WORKING_DIRECTORY ${CMAKE_RUNTIME_OUTPUT_DIRECTORY} + DEPENDS ${PROJECT_NAME} + ) + endif () + +elseif ("${TARGET_PLATFORM}" STREQUAL "w64") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-keep-inline-dllexport -static-libgcc -static-libstdc++ -static") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-s -Wl,-subsystem,windows -mthreads -L/usr/x86_64-w64-mingw32/plugins/platforms") + target_link_libraries(${EXECUTEABLE} gcc) + target_link_libraries(${EXECUTEABLE} mingw32 qtmain mswsock iphlpapi qwindows shlwapi Qt5PlatformSupport gdi32 comdlg32 oleaut32 imm32 winmm ole32 uuid ws2_32) + target_link_libraries(${EXECUTEABLE} boost_system-mt-s) + target_link_libraries(${EXECUTEABLE} boost_filesystem-mt-s) + target_link_libraries(${EXECUTEABLE} boost_thread_win32-mt-s) + target_link_libraries(${EXECUTEABLE} Qt5PlatformSupport) + set(CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS) +elseif (UNIX) +else () + target_link_libraries(${EXECUTEABLE} boost_system) + target_link_libraries(${EXECUTEABLE} boost_filesystem) + find_package(Threads REQUIRED) + target_link_libraries(${EXECUTEABLE} ${CMAKE_THREAD_LIBS_INIT}) + install( TARGETS ${EXECUTEABLE} RUNTIME DESTINATION bin ) +endif () + diff --git a/iethxi/Main.ui b/iethxi/Main.ui new file mode 100644 index 000000000..fe289ba9f --- /dev/null +++ b/iethxi/Main.ui @@ -0,0 +1,168 @@ + + + Main + + + + 0 + 0 + 562 + 488 + + + + Walleth + + + true + + + QMainWindow::AllowNestedDocks|QMainWindow::AllowTabbedDocks|QMainWindow::VerticalTabs + + + true + + + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + 0 wei + + + + + + + 0 peers + + + + + + + 1 block + + + + + + + + + + + 0 + 0 + 562 + 25 + + + + + &File + + + + + + &Network + + + + + + + + T&ools + + + + + + + + &Help + + + + + + + + + + + + &Quit + + + + + true + + + true + + + Use &UPnP + + + + + &Connect to Peer... + + + + + true + + + Enable &Network + + + + + true + + + &Mine + + + + + &New Address + + + + + &About... + + + + + true + + + &Preview + + + + + + + diff --git a/iethxi/MainWin.cpp b/iethxi/MainWin.cpp new file mode 100644 index 000000000..27814ebcd --- /dev/null +++ b/iethxi/MainWin.cpp @@ -0,0 +1,59 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "BuildInfo.h" +#include "MainWin.h" +#include "ui_Main.h" +using namespace std; +using namespace eth; + +Main::Main(QWidget *parent) : + QObject(parent) +{ +/* qRegisterMetaType("eth::u256"); + qRegisterMetaType("eth::KeyPair"); + qRegisterMetaType("eth::Secret"); + qRegisterMetaType("eth::Address"); + qRegisterMetaType("QmlAccount*"); + qRegisterMetaType("QmlEthereum*"); + + qmlRegisterType("org.ethereum", 1, 0, "Ethereum"); + qmlRegisterType("org.ethereum", 1, 0, "Account"); + qmlRegisterSingletonType("org.ethereum", 1, 0, "Balance", QmlEthereum::constructU256Helper); + qmlRegisterSingletonType("org.ethereum", 1, 0, "Key", QmlEthereum::constructKeyHelper); +*/ + /* + ui->librariesView->setModel(m_libraryMan); + ui->graphsView->setModel(m_graphMan); + */ + + + + +// QQmlContext* context = m_view->rootContext(); +// context->setContextProperty("u256", new U256Helper(this)); +} + +Main::~Main() +{ +} + +// extra bits needed to link on VS +#ifdef _MSC_VER + +// include moc file, ofuscated to hide from automoc +#include\ +"moc_MainWin.cpp" + +#endif diff --git a/iethxi/MainWin.h b/iethxi/MainWin.h new file mode 100644 index 000000000..acbea8ca8 --- /dev/null +++ b/iethxi/MainWin.h @@ -0,0 +1,18 @@ +#ifndef MAIN_H +#define MAIN_H + +#include + +class Main: public QObject +{ + Q_OBJECT + +public: + explicit Main(QWidget *parent = 0); + ~Main(); + +private: + QQmlApplicationEngine* m_view; +}; + +#endif // MAIN_H diff --git a/iethxi/Resources.qrc b/iethxi/Resources.qrc new file mode 100644 index 000000000..1789216ed --- /dev/null +++ b/iethxi/Resources.qrc @@ -0,0 +1,5 @@ + + + Simple.qml + + diff --git a/iethxi/main.cpp b/iethxi/main.cpp new file mode 100644 index 000000000..569b6d17c --- /dev/null +++ b/iethxi/main.cpp @@ -0,0 +1,9 @@ +#include +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + QQmlApplicationEngine app(QUrl("qrc:/Simple.qml")); + return a.exec(); +} diff --git a/libethcore/Exceptions.h b/libethcore/Exceptions.h index 9fb80d8c6..e11aff26a 100644 --- a/libethcore/Exceptions.h +++ b/libethcore/Exceptions.h @@ -5,6 +5,8 @@ namespace eth { +class DatabaseAlreadyOpen: public Exception {}; + class NotEnoughCash: public Exception {}; class GasPriceTooLow: public Exception {}; diff --git a/libethereum/BlockChain.cpp b/libethereum/BlockChain.cpp index 06117c01b..f196d540d 100644 --- a/libethereum/BlockChain.cpp +++ b/libethereum/BlockChain.cpp @@ -119,10 +119,12 @@ BlockChain::BlockChain(std::string _path, bool _killExisting) ldb::Options o; o.create_if_missing = true; - auto s = ldb::DB::Open(o, _path + "/blocks", &m_db); - assert(m_db); - s = ldb::DB::Open(o, _path + "/details", &m_extrasDB); - assert(m_extrasDB); + ldb::DB::Open(o, _path + "/blocks", &m_db); + ldb::DB::Open(o, _path + "/details", &m_extrasDB); + if (!m_db) + throw DatabaseAlreadyOpen(); + if (!m_extrasDB) + throw DatabaseAlreadyOpen(); // Initialise with the genesis as the last block on the longest chain. m_genesisHash = BlockChain::genesis().hash; diff --git a/libethereum/State.cpp b/libethereum/State.cpp index 6393e2103..d9d2fdfe8 100644 --- a/libethereum/State.cpp +++ b/libethereum/State.cpp @@ -52,6 +52,9 @@ OverlayDB State::openDB(std::string _path, bool _killExisting) o.create_if_missing = true; ldb::DB* db = nullptr; ldb::DB::Open(o, _path + "/state", &db); + if (!db) + throw DatabaseAlreadyOpen(); + cnote << "Opened state DB."; return OverlayDB(db); } diff --git a/libethereumx/CMakeLists.txt b/libethereumx/CMakeLists.txt new file mode 100644 index 000000000..d510fc8d0 --- /dev/null +++ b/libethereumx/CMakeLists.txt @@ -0,0 +1,66 @@ +cmake_policy(SET CMP0015 NEW) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DSTATICLIB") + +aux_source_directory(. SRC_LIST) + +set(EXECUTABLE ethereumx) + +# set(CMAKE_INSTALL_PREFIX ../lib) +if(ETH_STATIC) + add_library(${EXECUTABLE} STATIC ${SRC_LIST}) +else() + add_library(${EXECUTABLE} SHARED ${SRC_LIST}) +endif() + +file(GLOB HEADERS "*.h") + +include_directories(..) + +target_link_libraries(${EXECUTABLE} evm) +target_link_libraries(${EXECUTABLE} lll) +target_link_libraries(${EXECUTABLE} ethcore) +target_link_libraries(${EXECUTABLE} secp256k1) +if(MINIUPNPC_LS) +target_link_libraries(${EXECUTABLE} ${MINIUPNPC_LS}) +endif() +target_link_libraries(${EXECUTABLE} ${LEVELDB_LS}) +target_link_libraries(${EXECUTABLE} ${CRYPTOPP_LS}) +target_link_libraries(${EXECUTABLE} gmp) + +if("${TARGET_PLATFORM}" STREQUAL "w64") + target_link_libraries(${EXECUTABLE} boost_system-mt-s) + target_link_libraries(${EXECUTABLE} boost_regex-mt-s) + target_link_libraries(${EXECUTABLE} boost_filesystem-mt-s) + target_link_libraries(${EXECUTABLE} boost_thread_win32-mt-s) + target_link_libraries(${EXECUTABLE} iphlpapi) + target_link_libraries(${EXECUTABLE} ws2_32) + target_link_libraries(${EXECUTABLE} mswsock) + target_link_libraries(${EXECUTABLE} shlwapi) +elseif (APPLE) + # Latest mavericks boost libraries only come with -mt + target_link_libraries(${EXECUTABLE} boost_system-mt) + target_link_libraries(${EXECUTABLE} boost_regex-mt) + target_link_libraries(${EXECUTABLE} boost_filesystem-mt) + target_link_libraries(${EXECUTABLE} boost_thread-mt) + find_package(Threads REQUIRED) + target_link_libraries(${EXECUTABLE} ${CMAKE_THREAD_LIBS_INIT}) +elseif (UNIX) + target_link_libraries(${EXECUTABLE} ${Boost_SYSTEM_LIBRARY}) + target_link_libraries(${EXECUTABLE} ${Boost_REGEX_LIBRARY}) + target_link_libraries(${EXECUTABLE} ${Boost_FILESYSTEM_LIBRARY}) + target_link_libraries(${EXECUTABLE} ${Boost_THREAD_LIBRARY}) + target_link_libraries(${EXECUTABLE} ${Boost_DATE_TIME_LIBRARY}) + target_link_libraries(${EXECUTABLE} ${CMAKE_THREAD_LIBS_INIT}) +else () + target_link_libraries(${EXECUTABLE} boost_system) + target_link_libraries(${EXECUTABLE} boost_regex) + target_link_libraries(${EXECUTABLE} boost_filesystem) + target_link_libraries(${EXECUTABLE} boost_thread) + find_package(Threads REQUIRED) + target_link_libraries(${EXECUTABLE} ${CMAKE_THREAD_LIBS_INIT}) +endif () + +install( TARGETS ${EXECUTABLE} ARCHIVE DESTINATION lib LIBRARY DESTINATION lib ) +install( FILES ${HEADERS} DESTINATION include/${EXECUTABLE} ) + diff --git a/libethereumx/Ethereum.cpp b/libethereumx/Ethereum.cpp new file mode 100644 index 000000000..75971f42a --- /dev/null +++ b/libethereumx/Ethereum.cpp @@ -0,0 +1,125 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file Ethereum.cpp + * @author Gav Wood + * @date 2014 + */ + +#include "Ethereum.h" + +#include +#include +using namespace std; +using namespace eth; + +Ethereum::Ethereum() +{ + ensureReady(); +} + +void Ethereum::ensureReady() +{ + while (!m_client && connectionOpen()) + try + { + m_client = unique_ptr(new Client("+ethereum+")); + if (m_client) + startServer(); + } + catch (DatabaseAlreadyOpen) + { + startClient(); + } +} + +Ethereum::~Ethereum() +{ +} + +bool Ethereum::connectionOpen() const +{ + return false; +} + +void Ethereum::startClient() +{ +} + +void Ethereum::startServer() +{ +} + +void Client::flushTransactions() +{ +} + +std::vector Client::peers() +{ + return std::vector(); +} + +size_t Client::peerCount() const +{ + return 0; +} + +void Client::connect(std::string const& _seedHost, unsigned short _port) +{ +} + +void Client::transact(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice) +{ +} + +bytes Client::call(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice) +{ + return bytes(); +} + +Address Client::transact(Secret _secret, u256 _endowment, bytes const& _init, u256 _gas, u256 _gasPrice) +{ + return Address(); +} + +void Client::inject(bytesConstRef _rlp) +{ +} + +u256 Client::balanceAt(Address _a, int _block) const +{ + return u256(); +} + +std::map Client::storageAt(Address _a, int _block) const +{ + return std::map(); +} + +u256 Client::countAt(Address _a, int _block) const +{ + return u256(); +} + +u256 Client::stateAt(Address _a, u256 _l, int _block) const +{ + return u256(); +} + +bytes Client::codeAt(Address _a, int _block) const +{ + return bytes(); +} diff --git a/libethereumx/Ethereum.h b/libethereumx/Ethereum.h new file mode 100644 index 000000000..d55d4eaac --- /dev/null +++ b/libethereumx/Ethereum.h @@ -0,0 +1,144 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file Ethereum.h + * @author Gav Wood + * @date 2014 + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace eth +{ + +class Client; + +/** + * @brief Main API hub for interfacing with Ethereum. + * This class is automatically able to share a single machine-wide Client instance with other + * instances, cross-process. + * + * Other than that, it provides much the same subset of functionality as Client. + */ +class Ethereum +{ + friend class Miner; + +public: + /// Constructor. After this, everything should be set up to go. + Ethereum(); + + /// Destructor. + ~Ethereum(); + + /// Submits the given message-call transaction. + void transact(Secret _secret, u256 _value, Address _dest, bytes const& _data = bytes(), u256 _gas = 10000, u256 _gasPrice = 10 * szabo); + + /// Submits a new contract-creation transaction. + /// @returns the new contract's address (assuming it all goes through). + Address transact(Secret _secret, u256 _endowment, bytes const& _init, u256 _gas = 10000, u256 _gasPrice = 10 * szabo); + + /// Injects the RLP-encoded transaction given by the _rlp into the transaction queue directly. + void inject(bytesConstRef _rlp); + + /// Blocks until all pending transactions have been processed. + void flushTransactions(); + + /// Makes the given call. Nothing is recorded into the state. + bytes call(Secret _secret, u256 _value, Address _dest, bytes const& _data = bytes(), u256 _gas = 10000, u256 _gasPrice = 10 * szabo); + + // Informational stuff + + // [NEW API] + + int getDefault() const { return m_default; } + void setDefault(int _block) { m_default = _block; } + + u256 balanceAt(Address _a) const { return balanceAt(_a, m_default); } + u256 countAt(Address _a) const { return countAt(_a, m_default); } + u256 stateAt(Address _a, u256 _l) const { return stateAt(_a, _l, m_default); } + bytes codeAt(Address _a) const { return codeAt(_a, m_default); } + std::map storageAt(Address _a) const { return storageAt(_a, m_default); } + + u256 balanceAt(Address _a, int _block) const; + u256 countAt(Address _a, int _block) const; + u256 stateAt(Address _a, u256 _l, int _block) const; + bytes codeAt(Address _a, int _block) const; + std::map storageAt(Address _a, int _block) const; + + PastMessages messages(MessageFilter const& _filter) const; + + // [EXTRA API]: +#if 0 + /// Get a map containing each of the pending transactions. + /// @TODO: Remove in favour of transactions(). + Transactions pending() const { return m_postMine.pending(); } + + /// Differences between transactions. + StateDiff diff(unsigned _txi) const { return diff(_txi, m_default); } + StateDiff diff(unsigned _txi, h256 _block) const; + StateDiff diff(unsigned _txi, int _block) const; + + /// Get a list of all active addresses. + std::vector
    addresses() const { return addresses(m_default); } + std::vector
    addresses(int _block) const; + + /// Get the fee associated for a transaction with the given data. + static u256 txGas(uint _dataCount, u256 _gas = 0) { return c_txDataGas * _dataCount + c_txGas + _gas; } + + /// Get the remaining gas limit in this block. + u256 gasLimitRemaining() const { return m_postMine.gasLimitRemaining(); } +#endif + // Network stuff: + + /// Get information on the current peer set. + std::vector peers(); + /// Same as peers().size(), but more efficient. + size_t peerCount() const; + + /// Connect to a particular peer. + void connect(std::string const& _seedHost, unsigned short _port = 30303); + +private: + /// Ensure that through either the client or the + void ensureReady(); + /// Check to see if the client/server connection is open. + bool connectionOpen() const; + /// Start the API client. + void startClient(); + /// Start the API server. + void startServer(); + + std::unique_ptr m_client; + + int m_default = -1; +}; + +} From c3e6682c3b083b17e0f53e2b07a859ecdd742b46 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 26 Aug 2014 18:41:07 +0200 Subject: [PATCH 116/223] Numerous bug fixes. --- CMakeLists.txt | 1 + libethereum/Client.cpp | 1 + libethereum/State.cpp | 12 +++---- libethereum/Transaction.cpp | 2 +- libwhisper/CMakeLists.txt | 66 +++++++++++++++++++++++++++++++++++++ libwhisper/Whisper.cpp | 34 +++++++++++++++++++ libwhisper/Whisper.h | 50 ++++++++++++++++++++++++++++ 7 files changed, 159 insertions(+), 7 deletions(-) create mode 100644 libwhisper/CMakeLists.txt create mode 100644 libwhisper/Whisper.cpp create mode 100644 libwhisper/Whisper.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 34b671502..d8d3dc391 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -339,6 +339,7 @@ if (NOT LANGUAGES) add_subdirectory(secp256k1) add_subdirectory(libethcore) add_subdirectory(libevm) + add_subdirectory(libwhisper) add_subdirectory(libethereum) add_subdirectory(libethereumx) add_subdirectory(test) diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index 2ae0d7de4..6249c370b 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -506,6 +506,7 @@ void Client::work() m_postMine = m_preMine; rsm = true; changeds.insert(PendingChangedFilter); + // TODO: Move transactions pending from m_postMine back to transaction queue. } // returns h256s as blooms, once for each transaction. diff --git a/libethereum/State.cpp b/libethereum/State.cpp index d9d2fdfe8..41f4a6f1b 100644 --- a/libethereum/State.cpp +++ b/libethereum/State.cpp @@ -697,9 +697,10 @@ void State::commitToMine(BlockChain const& _bc) m_lastTx = m_db; - RLPStream uncles; Addresses uncleAddresses; + RLPStream unclesData; + unsigned unclesCount = 0; if (m_previousBlock != BlockChain::genesis()) { // Find great-uncles (or second-cousins or whatever they are) - children of great-grandparents, great-great-grandparents... that were not already uncles in previous generations. @@ -710,18 +711,16 @@ void State::commitToMine(BlockChain const& _bc) { auto us = _bc.details(p).children; 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) if (!knownUncles.count(BlockInfo::headerHash(_bc.block(u)))) // ignore any uncles/mainline blocks that we know about. We use header-hash for this. { BlockInfo ubi(_bc.block(u)); - ubi.fillStream(uncles, true); + ubi.fillStream(unclesData, true); + ++unclesCount; uncleAddresses.push_back(ubi.coinbaseAddress); } } } - else - uncles.appendList(0); MemoryDB tm; GenericTrieDB transactionReceipts(&tm); @@ -741,7 +740,8 @@ void State::commitToMine(BlockChain const& _bc) } txs.swapOut(m_currentTxs); - uncles.swapOut(m_currentUncles); + + RLPStream(unclesCount).appendRaw(unclesData.out(), unclesCount).swapOut(m_currentUncles); m_currentBlock.transactionsRoot = transactionReceipts.root(); m_currentBlock.sha3Uncles = sha3(m_currentUncles); diff --git a/libethereum/Transaction.cpp b/libethereum/Transaction.cpp index d248ae2fd..5a0fa8eeb 100644 --- a/libethereum/Transaction.cpp +++ b/libethereum/Transaction.cpp @@ -38,7 +38,7 @@ Transaction::Transaction(bytesConstRef _rlpData, bool _checkSender) nonce = rlp[field = 0].toInt(); gasPrice = rlp[field = 1].toInt(); gas = rlp[field = 2].toInt(); - receiveAddress = rlp[field = 3].isEmpty() ? Address() : Address(); + receiveAddress = rlp[field = 3].toHash
    (); value = rlp[field = 4].toInt(); data = rlp[field = 5].toBytes(); vrs = Signature{ rlp[field = 6].toInt(), rlp[field = 7].toInt(), rlp[field = 8].toInt() }; diff --git a/libwhisper/CMakeLists.txt b/libwhisper/CMakeLists.txt new file mode 100644 index 000000000..4d633495f --- /dev/null +++ b/libwhisper/CMakeLists.txt @@ -0,0 +1,66 @@ +cmake_policy(SET CMP0015 NEW) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DSTATICLIB") + +aux_source_directory(. SRC_LIST) + +set(EXECUTABLE whisper) + +# set(CMAKE_INSTALL_PREFIX ../lib) +if(ETH_STATIC) + add_library(${EXECUTABLE} STATIC ${SRC_LIST}) +else() + add_library(${EXECUTABLE} SHARED ${SRC_LIST}) +endif() + +file(GLOB HEADERS "*.h") + +include_directories(..) + +target_link_libraries(${EXECUTABLE} evm) +target_link_libraries(${EXECUTABLE} lll) +target_link_libraries(${EXECUTABLE} ethential) +target_link_libraries(${EXECUTABLE} secp256k1) +if(MINIUPNPC_LS) +target_link_libraries(${EXECUTABLE} ${MINIUPNPC_LS}) +endif() +target_link_libraries(${EXECUTABLE} ${LEVELDB_LS}) +target_link_libraries(${EXECUTABLE} ${CRYPTOPP_LS}) +target_link_libraries(${EXECUTABLE} gmp) + +if("${TARGET_PLATFORM}" STREQUAL "w64") + target_link_libraries(${EXECUTABLE} boost_system-mt-s) + target_link_libraries(${EXECUTABLE} boost_regex-mt-s) + target_link_libraries(${EXECUTABLE} boost_filesystem-mt-s) + target_link_libraries(${EXECUTABLE} boost_thread_win32-mt-s) + target_link_libraries(${EXECUTABLE} iphlpapi) + target_link_libraries(${EXECUTABLE} ws2_32) + target_link_libraries(${EXECUTABLE} mswsock) + target_link_libraries(${EXECUTABLE} shlwapi) +elseif (APPLE) + # Latest mavericks boost libraries only come with -mt + target_link_libraries(${EXECUTABLE} boost_system-mt) + target_link_libraries(${EXECUTABLE} boost_regex-mt) + target_link_libraries(${EXECUTABLE} boost_filesystem-mt) + target_link_libraries(${EXECUTABLE} boost_thread-mt) + find_package(Threads REQUIRED) + target_link_libraries(${EXECUTABLE} ${CMAKE_THREAD_LIBS_INIT}) +elseif (UNIX) + target_link_libraries(${EXECUTABLE} ${Boost_SYSTEM_LIBRARY}) + target_link_libraries(${EXECUTABLE} ${Boost_REGEX_LIBRARY}) + target_link_libraries(${EXECUTABLE} ${Boost_FILESYSTEM_LIBRARY}) + target_link_libraries(${EXECUTABLE} ${Boost_THREAD_LIBRARY}) + target_link_libraries(${EXECUTABLE} ${Boost_DATE_TIME_LIBRARY}) + target_link_libraries(${EXECUTABLE} ${CMAKE_THREAD_LIBS_INIT}) +else () + target_link_libraries(${EXECUTABLE} boost_system) + target_link_libraries(${EXECUTABLE} boost_regex) + target_link_libraries(${EXECUTABLE} boost_filesystem) + target_link_libraries(${EXECUTABLE} boost_thread) + find_package(Threads REQUIRED) + target_link_libraries(${EXECUTABLE} ${CMAKE_THREAD_LIBS_INIT}) +endif () + +install( TARGETS ${EXECUTABLE} ARCHIVE DESTINATION lib LIBRARY DESTINATION lib ) +install( FILES ${HEADERS} DESTINATION include/${EXECUTABLE} ) + diff --git a/libwhisper/Whisper.cpp b/libwhisper/Whisper.cpp new file mode 100644 index 000000000..905080f4b --- /dev/null +++ b/libwhisper/Whisper.cpp @@ -0,0 +1,34 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file Whisper.cpp + * @author Gav Wood + * @date 2014 + */ + +#include "Whisper.h" + +#include +using namespace std; +using namespace eth; + +Whisper::Whisper() +{ +} + +Whisper::~Whisper() +{ +} diff --git a/libwhisper/Whisper.h b/libwhisper/Whisper.h new file mode 100644 index 000000000..7be8a0c49 --- /dev/null +++ b/libwhisper/Whisper.h @@ -0,0 +1,50 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file Whisper.h + * @author Gav Wood + * @date 2014 + */ + +#pragma once + +namespace eth +{ +/* +class NetPeer +{ +public: + NetPeer(); + virtual ~NetPeer(); + +protected: + virtual void onIncoming(PeerId); + void send(PeerId); +}; +*/ +/** + */ +class Whisper//: public NetPeer +{ +public: + /// Constructor. + Whisper(); + + /// Destructor. + virtual ~Whisper(); +}; + +} From 7aea20bef204f37d0a8b09aab659e5c3c68ad6bc Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 26 Aug 2014 18:41:29 +0200 Subject: [PATCH 117/223] Version bump. --- libethential/Common.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libethential/Common.cpp b/libethential/Common.cpp index 524a8f62a..c64d3cb97 100644 --- a/libethential/Common.cpp +++ b/libethential/Common.cpp @@ -27,6 +27,6 @@ using namespace eth; namespace eth { -char const* EthVersion = "0.6.5"; +char const* EthVersion = "0.6.6"; } From 0571b637ab525487ad9d1cc5df5f077e5761df6d Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 26 Aug 2014 22:23:37 +0200 Subject: [PATCH 118/223] Manage invalid instructions without bombing. --- alethzero/MainWin.cpp | 9 ++++----- libethereum/Transaction.cpp | 4 ++-- libevmface/Instruction.cpp | 12 ++++++++++++ libevmface/Instruction.h | 4 +++- 4 files changed, 21 insertions(+), 8 deletions(-) diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index f499f38b9..4c18364ba 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -82,7 +82,6 @@ using eth::operator<<; // vars using eth::g_logPost; using eth::g_logVerbosity; -using eth::c_instructionInfo; static void initUnits(QComboBox* _b) { @@ -1702,7 +1701,7 @@ void Main::on_dumpTracePretty_triggered() f << " STORAGE" << endl; for (auto const& i: ws.storage) f << showbase << hex << i.first << ": " << i.second << endl; - f << dec << ws.levels.size() << " | " << ws.cur << " | #" << ws.steps << " | " << hex << setw(4) << setfill('0') << ws.curPC << " : " << c_instructionInfo.at(ws.inst).name << " | " << dec << ws.gas << " | -" << dec << ws.gasCost << " | " << ws.newMemSize << "x32"; + f << dec << ws.levels.size() << " | " << ws.cur << " | #" << ws.steps << " | " << hex << setw(4) << setfill('0') << ws.curPC << " : " << eth::instructionInfo(ws.inst).name << " | " << dec << ws.gas << " | -" << dec << ws.gasCost << " | " << ws.newMemSize << "x32"; } } @@ -1851,7 +1850,7 @@ void Main::updateDebugger() ostringstream out; out << s.cur.abridged(); if (i) - out << " " << c_instructionInfo.at(s.inst).name << " @0x" << hex << s.curPC; + out << " " << instructionInfo(s.inst).name << " @0x" << hex << s.curPC; ui->callStack->addItem(QString::fromStdString(out.str())); } } @@ -1867,7 +1866,7 @@ void Main::updateDebugger() byte b = i < code.size() ? code[i] : 0; try { - QString s = c_instructionInfo.at((Instruction)b).name; + QString s = QString::fromStdString(instructionInfo((Instruction)b).name); ostringstream out; out << hex << setw(4) << setfill('0') << i; m_pcWarp[i] = dc->count(); @@ -1917,7 +1916,7 @@ void Main::updateDebugger() cwarn << "PC (" << (unsigned)ws.curPC << ") is after code range (" << m_codes[ws.code].size() << ")"; ostringstream ss; - ss << dec << "STEP: " << ws.steps << " | PC: 0x" << hex << ws.curPC << " : " << c_instructionInfo.at(ws.inst).name << " | ADDMEM: " << dec << ws.newMemSize << " words | COST: " << dec << ws.gasCost << " | GAS: " << dec << ws.gas; + ss << dec << "STEP: " << ws.steps << " | PC: 0x" << hex << ws.curPC << " : " << eth::instructionInfo(ws.inst).name << " | ADDMEM: " << dec << ws.newMemSize << " words | COST: " << dec << ws.gasCost << " | GAS: " << dec << ws.gas; ui->debugStateInfo->setText(QString::fromStdString(ss.str())); stringstream s; for (auto const& i: ws.storage) diff --git a/libethereum/Transaction.cpp b/libethereum/Transaction.cpp index 5a0fa8eeb..9968683fe 100644 --- a/libethereum/Transaction.cpp +++ b/libethereum/Transaction.cpp @@ -27,7 +27,7 @@ using namespace std; using namespace eth; -#define ETH_ADDRESS_DEBUG 0 +#define ETH_ADDRESS_DEBUG 1 Transaction::Transaction(bytesConstRef _rlpData, bool _checkSender) { @@ -85,7 +85,7 @@ Address Transaction::sender() const cout << "MSG: " << msg << endl; cout << "R S V: " << sig[0] << " " << sig[1] << " " << (int)(vrs.v - 27) << "+27" << endl; cout << "PUB: " << toHex(bytesConstRef(&(pubkey[1]), 64)) << endl; - cout << "ADR: " << ret << endl; + cout << "ADR: " << m_sender << endl; #endif } return m_sender; diff --git a/libevmface/Instruction.cpp b/libevmface/Instruction.cpp index c31194475..0f68cdaf4 100644 --- a/libevmface/Instruction.cpp +++ b/libevmface/Instruction.cpp @@ -296,3 +296,15 @@ string eth::disassemble(bytes const& _mem) } return ret.str(); } + +InstructionInfo eth::instructionInfo(Instruction _inst) +{ + try + { + return c_instructionInfo.at(_inst); + } + catch (...) + { + return InstructionInfo({"", 0, 0, 0}); + } +} diff --git a/libevmface/Instruction.h b/libevmface/Instruction.h index 1afe930aa..54f243e11 100644 --- a/libevmface/Instruction.h +++ b/libevmface/Instruction.h @@ -169,12 +169,14 @@ enum class Instruction: uint8_t /// Information structure for a particular instruction. struct InstructionInfo { - char const* name; ///< The name of the instruction. + std::string name; ///< The name of the instruction. int additional; ///< Additional items required in memory for this instructions (only for PUSH). int args; ///< Number of items required on the stack for this instruction (and, for the purposes of ret, the number taken from the stack). int ret; ///< Number of items placed (back) on the stack by this instruction, assuming args items were removed. }; +InstructionInfo instructionInfo(Instruction _inst); + /// Information on all the instructions. extern const std::map c_instructionInfo; From be4bea3c11edb81d2dd02e171c55f1d27a48582d Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 26 Aug 2014 22:27:45 +0200 Subject: [PATCH 119/223] Better interface for instrInfo. --- eth/main.cpp | 3 +-- libethereum/Executive.cpp | 2 +- libevmface/Instruction.cpp | 2 +- libevmface/Instruction.h | 4 +--- liblll/Assembly.cpp | 6 +++--- liblll/CodeFragment.cpp | 2 +- libqethereum/QEthereum.cpp | 1 - libqethereum/QmlEthereum.cpp | 1 - neth/main.cpp | 1 - third/MainWin.cpp | 1 - walleth/MainWin.cpp | 1 - 11 files changed, 8 insertions(+), 16 deletions(-) diff --git a/eth/main.cpp b/eth/main.cpp index d6c49c867..bf474440e 100644 --- a/eth/main.cpp +++ b/eth/main.cpp @@ -45,7 +45,6 @@ using namespace std; using namespace eth; using namespace boost::algorithm; using eth::Instruction; -using eth::c_instructionInfo; #undef RETURN @@ -610,7 +609,7 @@ int main(int argc, char** argv) f << " STORAGE" << endl; for (auto const& i: ext->state().storage(ext->myAddress)) f << showbase << hex << i.first << ": " << i.second << endl; - f << dec << ext->level << " | " << ext->myAddress << " | #" << steps << " | " << hex << setw(4) << setfill('0') << vm->curPC() << " : " << c_instructionInfo.at(instr).name << " | " << dec << vm->gas() << " | -" << dec << gasCost << " | " << newMemSize << "x32"; + f << dec << ext->level << " | " << ext->myAddress << " | #" << steps << " | " << hex << setw(4) << setfill('0') << vm->curPC() << " : " << eth::instructionInfo(instr).name << " | " << dec << vm->gas() << " | -" << dec << gasCost << " | " << newMemSize << "x32"; }; else if (format == "standard") oof = [&](uint64_t, Instruction instr, bigint, bigint, void* vvm, void const* vextVM) diff --git a/libethereum/Executive.cpp b/libethereum/Executive.cpp index 699837701..d2b6fc0bc 100644 --- a/libethereum/Executive.cpp +++ b/libethereum/Executive.cpp @@ -158,7 +158,7 @@ OnOpFunc Executive::simpleTrace() for (auto const& i: ext.state().storage(ext.myAddress)) o << showbase << hex << i.first << ": " << i.second << endl; eth::LogOutputStream(true) << o.str(); - eth::LogOutputStream(false) << " | " << dec << ext.level << " | " << ext.myAddress << " | #" << steps << " | " << hex << setw(4) << setfill('0') << vm.curPC() << " : " << c_instructionInfo.at(inst).name << " | " << dec << vm.gas() << " | -" << dec << gasCost << " | " << newMemSize << "x32" << " ]"; + eth::LogOutputStream(false) << " | " << dec << ext.level << " | " << ext.myAddress << " | #" << steps << " | " << hex << setw(4) << setfill('0') << vm.curPC() << " : " << instructionInfo(inst).name << " | " << dec << vm.gas() << " | -" << dec << gasCost << " | " << newMemSize << "x32" << " ]"; }; } diff --git a/libevmface/Instruction.cpp b/libevmface/Instruction.cpp index 0f68cdaf4..2ef5981c0 100644 --- a/libevmface/Instruction.cpp +++ b/libevmface/Instruction.cpp @@ -149,7 +149,7 @@ const std::map eth::c_instructions = { "SUICIDE", Instruction::SUICIDE } }; -const std::map eth::c_instructionInfo = +static const std::map c_instructionInfo = { // Add, Args, Ret { Instruction::STOP, { "STOP", 0, 0, 0 } }, { Instruction::ADD, { "ADD", 0, 2, 1 } }, diff --git a/libevmface/Instruction.h b/libevmface/Instruction.h index 54f243e11..e1cefe7e4 100644 --- a/libevmface/Instruction.h +++ b/libevmface/Instruction.h @@ -175,10 +175,8 @@ struct InstructionInfo int ret; ///< Number of items placed (back) on the stack by this instruction, assuming args items were removed. }; -InstructionInfo instructionInfo(Instruction _inst); - /// Information on all the instructions. -extern const std::map c_instructionInfo; +InstructionInfo instructionInfo(Instruction _inst); /// Convert from string mnemonic to Instruction type. extern const std::map c_instructions; diff --git a/liblll/Assembly.cpp b/liblll/Assembly.cpp index 491d3812e..f397ec37d 100644 --- a/liblll/Assembly.cpp +++ b/liblll/Assembly.cpp @@ -31,7 +31,7 @@ int AssemblyItem::deposit() const switch (m_type) { case Operation: - return c_instructionInfo.at((Instruction)(byte)m_data).ret - c_instructionInfo.at((Instruction)(byte)m_data).args; + return instructionInfo((Instruction)(byte)m_data).ret - instructionInfo((Instruction)(byte)m_data).args; case Push: case PushString: case PushTag: case PushData: case PushSub: case PushSubSize: return 1; case Tag: @@ -116,7 +116,7 @@ ostream& eth::operator<<(ostream& _out, AssemblyItemsConstRef _i) switch (i.type()) { case Operation: - _out << " " << c_instructionInfo.at((Instruction)(byte)i.data()).name; + _out << " " << instructionInfo((Instruction)(byte)i.data()).name; break; case Push: _out << " PUSH" << i.data(); @@ -153,7 +153,7 @@ ostream& Assembly::streamOut(ostream& _out, string const& _prefix) const switch (i.m_type) { case Operation: - _out << _prefix << " " << c_instructionInfo.at((Instruction)(byte)i.m_data).name << endl; + _out << _prefix << " " << instructionInfo((Instruction)(byte)i.m_data).name << endl; break; case Push: _out << _prefix << " PUSH " << i.m_data << endl; diff --git a/liblll/CodeFragment.cpp b/liblll/CodeFragment.cpp index a9128e29c..07d17fbef 100644 --- a/liblll/CodeFragment.cpp +++ b/liblll/CodeFragment.cpp @@ -369,7 +369,7 @@ void CodeFragment::constructOperation(sp::utree const& _t, CompilerState& _s) else if (c_instructions.count(us)) { auto it = c_instructions.find(us); - int ea = c_instructionInfo.at(it->second).args; + int ea = instructionInfo(it->second).args; if (ea >= 0) requireSize(ea); else diff --git a/libqethereum/QEthereum.cpp b/libqethereum/QEthereum.cpp index e1cae79ae..e744cd148 100644 --- a/libqethereum/QEthereum.cpp +++ b/libqethereum/QEthereum.cpp @@ -43,7 +43,6 @@ using eth::operator +; // vars using eth::g_logPost; using eth::g_logVerbosity; -using eth::c_instructionInfo; eth::bytes toBytes(QString const& _s) { diff --git a/libqethereum/QmlEthereum.cpp b/libqethereum/QmlEthereum.cpp index 560d75ddc..7b8e1505a 100644 --- a/libqethereum/QmlEthereum.cpp +++ b/libqethereum/QmlEthereum.cpp @@ -45,7 +45,6 @@ using eth::units; // vars using eth::g_logPost; using eth::g_logVerbosity; -using eth::c_instructionInfo; // Horrible global for the mainwindow. Needed for the QmlEthereums to find the Main window which acts as multiplexer for now. // Can get rid of this once we've sorted out ITC for signalling & multiplexed querying. diff --git a/neth/main.cpp b/neth/main.cpp index ae93a94af..c22c9a22c 100644 --- a/neth/main.cpp +++ b/neth/main.cpp @@ -51,7 +51,6 @@ using namespace std; using namespace eth; using namespace boost::algorithm; using eth::Instruction; -using eth::c_instructionInfo; bool isTrue(std::string const& _m) { diff --git a/third/MainWin.cpp b/third/MainWin.cpp index 784752959..59423d83e 100644 --- a/third/MainWin.cpp +++ b/third/MainWin.cpp @@ -81,7 +81,6 @@ using eth::operator<<; // vars using eth::g_logPost; using eth::g_logVerbosity; -using eth::c_instructionInfo; static QString fromRaw(eth::h256 _n, unsigned* _inc = nullptr) { diff --git a/walleth/MainWin.cpp b/walleth/MainWin.cpp index 98dbc6e65..dfebd7dcd 100644 --- a/walleth/MainWin.cpp +++ b/walleth/MainWin.cpp @@ -50,7 +50,6 @@ using eth::units; // vars using eth::g_logPost; using eth::g_logVerbosity; -using eth::c_instructionInfo; Main::Main(QWidget *parent) : QMainWindow(parent), From a17cad3404ad44bf62f173b9001dcd298193e807 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 27 Aug 2014 09:37:00 +0200 Subject: [PATCH 120/223] A start. --- libethereum/PeerServer.h | 106 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 105 insertions(+), 1 deletion(-) diff --git a/libethereum/PeerServer.h b/libethereum/PeerServer.h index 5d12c807c..c38d49df4 100644 --- a/libethereum/PeerServer.h +++ b/libethereum/PeerServer.h @@ -40,12 +40,116 @@ namespace eth class RLPStream; class TransactionQueue; class BlockQueue; +/* +class BasePeerServer +{ + friend class BasePeerSession; + +public: + /// Start server, listening for connections on the given port. + BasePeerServer(std::string const& _clientVersion, u256 _networkId, unsigned short _port, std::string const& _publicAddress = std::string(), bool _upnp = true); + /// Start server, listening for connections on a system-assigned port. + BasePeerServer(std::string const& _clientVersion, u256 _networkId, std::string const& _publicAddress = std::string(), bool _upnp = true); + /// Start server, but don't listen. + BasePeerServer(std::string const& _clientVersion, u256 _networkId); + + /// Will block on network process events. + virtual ~BasePeerServer(); + + /// Closes all peers. + void disconnectPeers(); + + virtual unsigned protocolVersion(); + + /// Connect to a peer explicitly. + void connect(std::string const& _addr, unsigned short _port = 30303) noexcept; + void connect(bi::tcp::endpoint const& _ep); + + /// Conduct I/O, polling, syncing, whatever. + /// Ideally all time-consuming I/O is done in a background thread or otherwise asynchronously, but you get this call every 100ms or so anyway. + /// This won't touch alter the blockchain. + void process() { if (isInitialised()) m_ioService.poll(); } + + /// @returns true iff we have the a peer of the given id. + bool havePeer(Public _id) const; + + /// Set ideal number of peers. + void setIdealPeerCount(unsigned _n) { m_idealPeerCount = _n; } + + /// Get peer information. + std::vector peers(bool _updatePing = false) const; + + /// Get number of peers connected; equivalent to, but faster than, peers().size(). + size_t peerCount() const { Guard l(x_peers); return m_peers.size(); } + + /// Ping the peers, to update the latency information. + void pingAll(); + /// Get the port we're listening on currently. + unsigned short listenPort() const { return m_public.port(); } + + /// Serialise the set of known peers. + bytes savePeers() const; + + /// Deserialise the data and populate the set of known peers. + void restorePeers(bytesConstRef _b); + + void registerPeer(std::shared_ptr _s); + +protected: + /// Called when the session has provided us with a new peer we can connect to. + void noteNewPeers() {} + + void seal(bytes& _b); + void populateAddresses(); + void determinePublic(std::string const& _publicAddress, bool _upnp); + void ensureAccepting(); + + void growPeers(); + void prunePeers(); + + /// Check to see if the network peer-state initialisation has happened. + bool isInitialised() const { return m_latestBlockSent; } + /// Initialises the network peer-state, doing the stuff that needs to be once-only. @returns true if it really was first. + bool ensureInitialised(TransactionQueue& _tq); + + std::map potentialPeers(); + + std::string m_clientVersion; + + unsigned short m_listenPort; + + ba::io_service m_ioService; + bi::tcp::acceptor m_acceptor; + bi::tcp::socket m_socket; + + UPnP* m_upnp = nullptr; + bi::tcp::endpoint m_public; + KeyPair m_key; + + u256 m_networkId; + + mutable std::mutex x_peers; + mutable std::map> m_peers; // mutable because we flush zombie entries (null-weakptrs) as regular maintenance from a const method. + + mutable std::recursive_mutex m_incomingLock; + std::map> m_incomingPeers; + std::vector m_freePeers; + + std::chrono::steady_clock::time_point m_lastPeersRequest; + unsigned m_idealPeerCount = 5; + + std::vector m_addresses; + std::vector m_peerAddresses; + + bool m_accepting = false; +}; +*/ /** * @brief The PeerServer class * @warning None of this is thread-safe. You have been warned. */ -class PeerServer +class PeerServer//: public BasePeerServer { friend class PeerSession; From d47c4207564ffc1a3a788ecbc94b7763dc35fdca Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 27 Aug 2014 09:38:00 +0200 Subject: [PATCH 121/223] Add untracked files. --- iethxi/EthereumMacOSXBundleInfo.plist.in | 38 +++++++++++++++++++++++ iethxi/Simple.qml | 9 ++++++ third/alethzero.icns | Bin 0 -> 514931 bytes 3 files changed, 47 insertions(+) create mode 100644 iethxi/EthereumMacOSXBundleInfo.plist.in create mode 100644 iethxi/Simple.qml create mode 100644 third/alethzero.icns diff --git a/iethxi/EthereumMacOSXBundleInfo.plist.in b/iethxi/EthereumMacOSXBundleInfo.plist.in new file mode 100644 index 000000000..684ad7908 --- /dev/null +++ b/iethxi/EthereumMacOSXBundleInfo.plist.in @@ -0,0 +1,38 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleExecutable + ${MACOSX_BUNDLE_EXECUTABLE_NAME} + CFBundleGetInfoString + ${MACOSX_BUNDLE_INFO_STRING} + CFBundleIconFile + ${MACOSX_BUNDLE_ICON_FILE} + CFBundleIdentifier + ${MACOSX_BUNDLE_GUI_IDENTIFIER} + CFBundleInfoDictionaryVersion + 6.0 + CFBundleLongVersionString + ${MACOSX_BUNDLE_LONG_VERSION_STRING} + CFBundleName + ${MACOSX_BUNDLE_BUNDLE_NAME} + CFBundlePackageType + APPL + CFBundleShortVersionString + ${MACOSX_BUNDLE_SHORT_VERSION_STRING} + CFBundleSignature + ???? + CFBundleVersion + ${MACOSX_BUNDLE_BUNDLE_VERSION} + CSResourcesFileMapped + + LSRequiresCarbon + + NSHumanReadableCopyright + ${MACOSX_BUNDLE_COPYRIGHT} + NSHighResolutionCapable + + + diff --git a/iethxi/Simple.qml b/iethxi/Simple.qml new file mode 100644 index 000000000..ac9dc5e37 --- /dev/null +++ b/iethxi/Simple.qml @@ -0,0 +1,9 @@ +import QtQuick.Controls 1.1 + +ApplicationWindow { + title: "My App" + Button { + text: "Push Me" + anchors.centerIn: parent + } +} diff --git a/third/alethzero.icns b/third/alethzero.icns new file mode 100644 index 0000000000000000000000000000000000000000..105afb3d780caab0272375b08abce9146b9b414f GIT binary patch literal 514931 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
  • c!Gb@F^dIQ_soxZhW`6*AU(qe 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ⅇ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
    * o_suicides, PostList* o_posts, Manifest* o_ms, OnOpFunc const& _onOp, unsigned _level) +bool State::call(Address _receiveAddress, Address _codeAddress, Address _senderAddress, u256 _value, u256 _gasPrice, bytesConstRef _data, u256* _gas, bytesRef _out, Address _originAddress, std::set
    * o_suicides, PostList* o_posts, Manifest* o_ms, OnOpFunc const& _onOp, unsigned _level) { if (!_originAddress) _originAddress = _senderAddress; @@ -1053,10 +1053,10 @@ bool State::call(Address _receiveAddress, Address _senderAddress, u256 _value, u o_ms->input = _data.toBytes(); } - if (addressHasCode(_receiveAddress)) + if (addressHasCode(_codeAddress)) { VM vm(*_gas); - ExtVM evm(*this, _receiveAddress, _senderAddress, _originAddress, _value, _gasPrice, _data, &code(_receiveAddress), o_ms, _level); + ExtVM evm(*this, _receiveAddress, _senderAddress, _originAddress, _value, _gasPrice, _data, &code(_codeAddress), o_ms, _level); bool revert = false; try diff --git a/libethereum/State.h b/libethereum/State.h index 879e5e741..d2aa97632 100644 --- a/libethereum/State.h +++ b/libethereum/State.h @@ -275,7 +275,7 @@ private: /// Execute a call. /// @a _gas points to the amount of gas to use for the call, and will lower it accordingly. /// @returns false if the call ran out of gas before completion. true otherwise. - bool call(Address _myAddress, Address _txSender, u256 _txValue, u256 _gasPrice, bytesConstRef _txData, u256* _gas, bytesRef _out, Address _originAddress = Address(), std::set
    * o_suicides = nullptr, PostList* o_posts = nullptr, Manifest* o_ms = nullptr, OnOpFunc const& _onOp = OnOpFunc(), unsigned _level = 0); + bool call(Address _myAddress, Address _codeAddress, Address _txSender, u256 _txValue, u256 _gasPrice, bytesConstRef _txData, u256* _gas, bytesRef _out, Address _originAddress = Address(), std::set
    * o_suicides = nullptr, PostList* o_posts = nullptr, Manifest* o_ms = nullptr, OnOpFunc const& _onOp = OnOpFunc(), unsigned _level = 0); /// Sets m_currentBlock to a clean state, (i.e. no change from m_previousBlock). void resetCurrent(); diff --git a/libevm/ExtVMFace.h b/libevm/ExtVMFace.h index 7aa8df8e5..54bb86ae7 100644 --- a/libevm/ExtVMFace.h +++ b/libevm/ExtVMFace.h @@ -40,6 +40,8 @@ struct Post using PostList = std::list; +using OnOpFunc = std::function; + /** * @brief A null implementation of the class for specifying VM externalities. */ @@ -77,7 +79,7 @@ public: h160 create(u256, u256*, bytesConstRef, bytesConstRef) { return h160(); } /// Make a new message call. - bool call(Address, u256, bytesConstRef, u256*, bytesRef) { return false; } + bool call(Address, u256, bytesConstRef, u256*, bytesRef, OnOpFunc const&, Address, Address) { return false; } /// Post a new message call. void post(Address _to, u256 _value, bytesConstRef _data, u256 _gas) { posts.push_back(Post({myAddress, _to, _value, _data.toBytes(), _gas})); } @@ -101,6 +103,4 @@ public: std::list posts; ///< Any posts that have been made. }; -typedef std::function OnOpFunc; - } diff --git a/libevm/VM.h b/libevm/VM.h index de2d56431..b3cdc9be1 100644 --- a/libevm/VM.h +++ b/libevm/VM.h @@ -166,6 +166,12 @@ template eth::bytesConstRef eth::VM::go(Ext& _ext, OnOpFunc const& _ newTempSize = std::max(memNeed(m_stack[m_stack.size() - 6], m_stack[m_stack.size() - 7]), memNeed(m_stack[m_stack.size() - 4], m_stack[m_stack.size() - 5])); break; + case Instruction::CALLSTATELESS: + require(7); + runGas = c_callGas + m_stack[m_stack.size() - 1]; + newTempSize = std::max(memNeed(m_stack[m_stack.size() - 6], m_stack[m_stack.size() - 7]), memNeed(m_stack[m_stack.size() - 4], m_stack[m_stack.size() - 5])); + break; + case Instruction::POST: require(5); runGas = c_callGas + m_stack[m_stack.size() - 1]; @@ -580,12 +586,13 @@ template eth::bytesConstRef eth::VM::go(Ext& _ext, OnOpFunc const& _ break; } case Instruction::CALL: + case Instruction::CALLSTATELESS: { require(7); u256 gas = m_stack.back(); m_stack.pop_back(); - u160 receiveAddress = asAddress(m_stack.back()); + Address receiveAddress = asAddress(m_stack.back()); m_stack.pop_back(); u256 value = m_stack.back(); m_stack.pop_back(); @@ -602,7 +609,7 @@ template eth::bytesConstRef eth::VM::go(Ext& _ext, OnOpFunc const& _ if (_ext.balance(_ext.myAddress) >= value) { _ext.subBalance(value); - m_stack.push_back(_ext.call(receiveAddress, value, bytesConstRef(m_temp.data() + inOff, inSize), &gas, bytesRef(m_temp.data() + outOff, outSize), _onOp)); + m_stack.push_back(_ext.call(receiveAddress, value, bytesConstRef(m_temp.data() + inOff, inSize), &gas, bytesRef(m_temp.data() + outOff, outSize), _onOp, Address(), inst == Instruction::CALL ? receiveAddress : _ext.myAddress)); } else m_stack.push_back(0); diff --git a/libevmface/Instruction.cpp b/libevmface/Instruction.cpp index 2ef5981c0..734dc7792 100644 --- a/libevmface/Instruction.cpp +++ b/libevmface/Instruction.cpp @@ -144,6 +144,7 @@ const std::map eth::c_instructions = { "SWAP16", Instruction::SWAP16 }, { "CREATE", Instruction::CREATE }, { "CALL", Instruction::CALL }, + { "CALLSTATELESS", Instruction::CALLSTATELESS }, { "RETURN", Instruction::RETURN }, { "POST", Instruction::POST }, { "SUICIDE", Instruction::SUICIDE } @@ -268,6 +269,7 @@ static const std::map c_instructionInfo = { Instruction::SWAP16, { "SWAP16", 0, 17, 17 } }, { Instruction::CREATE, { "CREATE", 0, 3, 1 } }, { Instruction::CALL, { "CALL", 0, 7, 1 } }, + { Instruction::CALLSTATELESS, { "CALLSTATELESS",0, 7, 1 } }, { Instruction::RETURN, { "RETURN", 0, 2, 0 } }, { Instruction::POST, { "POST", 0, 5, 0 } }, { Instruction::SUICIDE, { "SUICIDE", 0, 1, 0} } diff --git a/libevmface/Instruction.h b/libevmface/Instruction.h index e1cefe7e4..ca65d4bbe 100644 --- a/libevmface/Instruction.h +++ b/libevmface/Instruction.h @@ -163,6 +163,7 @@ enum class Instruction: uint8_t CALL, RETURN, POST, + CALLSTATELESS, SUICIDE = 0xff }; diff --git a/test/vm.cpp b/test/vm.cpp index e54967a59..0647849a0 100644 --- a/test/vm.cpp +++ b/test/vm.cpp @@ -80,7 +80,7 @@ public: return na; } - bool call(Address _receiveAddress, u256 _value, bytesConstRef _data, u256* _gas, bytesRef _out, OnOpFunc) + bool call(Address _receiveAddress, u256 _value, bytesConstRef _data, u256* _gas, bytesRef _out, OnOpFunc, Address, Address) { /* if (get<0>(addresses[myAddress]) >= _value) { From a37d62f602f084089a8da4b0b172c06e2ce53b9b Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 3 Sep 2014 22:26:37 +0200 Subject: [PATCH 156/223] EXTCODECOPY/SIZE. --- libethereum/ExtVM.h | 3 +++ libevm/ExtVMFace.h | 3 +++ libevm/VM.h | 20 ++++++++++++++++++++ libevmface/Instruction.cpp | 4 ++++ libevmface/Instruction.h | 2 ++ 5 files changed, 32 insertions(+) diff --git a/libethereum/ExtVM.h b/libethereum/ExtVM.h index 48099b8fc..f3d8bee50 100644 --- a/libethereum/ExtVM.h +++ b/libethereum/ExtVM.h @@ -49,6 +49,9 @@ public: /// Write a value in storage. void setStore(u256 _n, u256 _v) { m_s.setStorage(myAddress, _n, _v); if (m_ms) m_ms->altered.push_back(_n); } + /// Read address's code. + bytes const& codeAt(Address _a) { return m_s.code(_a); } + /// Create a new contract. h160 create(u256 _endowment, u256* _gas, bytesConstRef _code, OnOpFunc const& _onOp = OnOpFunc()) { diff --git a/libevm/ExtVMFace.h b/libevm/ExtVMFace.h index 54bb86ae7..2c32c74f4 100644 --- a/libevm/ExtVMFace.h +++ b/libevm/ExtVMFace.h @@ -66,6 +66,9 @@ public: /// Read address's balance. u256 balance(Address) { return 0; } + /// Read address's code. + bytes const& codeAt(Address) { return NullBytes; } + /// Subtract amount from account's balance. void subBalance(u256) {} diff --git a/libevm/VM.h b/libevm/VM.h index b3cdc9be1..76b68a11c 100644 --- a/libevm/VM.h +++ b/libevm/VM.h @@ -401,6 +401,26 @@ template eth::bytesConstRef eth::VM::go(Ext& _ext, OnOpFunc const& _ memset(m_temp.data() + mf + el, 0, l - el); break; } + case Instruction::EXTCODESIZE: + require(1); + m_stack.back() = _ext.codeAt(asAddress(m_stack.back())).size(); + break; + case Instruction::EXTCODECOPY: + { + require(4); + Address a = asAddress(m_stack.back()); + m_stack.pop_back(); + unsigned mf = (unsigned)m_stack.back(); + m_stack.pop_back(); + unsigned cf = (unsigned)m_stack.back(); + m_stack.pop_back(); + unsigned l = (unsigned)m_stack.back(); + m_stack.pop_back(); + unsigned el = cf + l > _ext.codeAt(a).size() ? _ext.codeAt(a).size() < cf ? 0 : _ext.codeAt(a).size() - cf : l; + memcpy(m_temp.data() + mf, _ext.codeAt(a).data() + cf, el); + memset(m_temp.data() + mf + el, 0, l - el); + break; + } case Instruction::GASPRICE: m_stack.push_back(_ext.gasPrice); break; diff --git a/libevmface/Instruction.cpp b/libevmface/Instruction.cpp index 734dc7792..7d6fdd366 100644 --- a/libevmface/Instruction.cpp +++ b/libevmface/Instruction.cpp @@ -61,6 +61,8 @@ const std::map eth::c_instructions = { "CODESIZE", Instruction::CODESIZE }, { "CODECOPY", Instruction::CODECOPY }, { "GASPRICE", Instruction::GASPRICE }, + { "EXTCODESIZE", Instruction::EXTCODESIZE }, + { "EXTCODECOPY", Instruction::EXTCODECOPY }, { "PREVHASH", Instruction::PREVHASH }, { "COINBASE", Instruction::COINBASE }, { "TIMESTAMP", Instruction::TIMESTAMP }, @@ -186,6 +188,8 @@ static const std::map c_instructionInfo = { Instruction::CODESIZE, { "CODESIZE", 0, 0, 1 } }, { Instruction::CODECOPY, { "CODECOPY", 0, 3, 0 } }, { Instruction::GASPRICE, { "GASPRICE", 0, 0, 1 } }, + { Instruction::EXTCODESIZE, { "EXTCODESIZE", 0, 1, 1 } }, + { Instruction::EXTCODECOPY, { "EXTCODECOPY", 0, 4, 0 } }, { Instruction::PREVHASH, { "PREVHASH", 0, 0, 1 } }, { Instruction::COINBASE, { "COINBASE", 0, 0, 1 } }, { Instruction::TIMESTAMP, { "TIMESTAMP", 0, 0, 1 } }, diff --git a/libevmface/Instruction.h b/libevmface/Instruction.h index ca65d4bbe..1af42bada 100644 --- a/libevmface/Instruction.h +++ b/libevmface/Instruction.h @@ -72,6 +72,8 @@ enum class Instruction: uint8_t CODESIZE, CODECOPY, GASPRICE, + EXTCODESIZE, + EXTCODECOPY, PREVHASH = 0x40, COINBASE, From 4cb79936558282a805f306d4db75f6063d1d5dd8 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 5 Sep 2014 17:09:58 +0200 Subject: [PATCH 157/223] Project-wide reorganisation of namespaces. --- CMakeLists.txt | 4 +- alethzero/MainWin.cpp | 184 +++++++++++++++---------------- alethzero/MainWin.h | 72 ++++++------ alethzero/MiningView.cpp | 13 ++- alethzero/MiningView.h | 9 +- eth/CommonJS.cpp | 11 +- eth/CommonJS.h | 33 +++--- eth/EthStubServer.cpp | 5 +- eth/EthStubServer.h | 11 +- eth/main.cpp | 55 ++++----- exp/main.cpp | 12 +- libethcore/BlockInfo.cpp | 7 +- libethcore/BlockInfo.h | 5 +- libethcore/CommonEth.cpp | 23 ++-- libethcore/CommonEth.h | 3 + libethcore/Dagger.cpp | 8 +- libethcore/Dagger.h | 16 +-- libethcore/Exceptions.h | 67 +++++------ libethcore/FileSystem.cpp | 5 +- libethcore/FileSystem.h | 3 + libethcore/MemoryDB.cpp | 6 +- libethcore/MemoryDB.h | 5 +- libethcore/OverlayDB.cpp | 6 +- libethcore/OverlayDB.h | 8 ++ libethcore/SHA3.cpp | 20 +++- libethcore/SHA3.h | 3 + libethcore/TrieCommon.cpp | 29 ++--- libethcore/TrieCommon.h | 29 ++--- libethcore/TrieDB.cpp | 9 +- libethcore/TrieDB.h | 24 +++- libethential/Common.cpp | 7 +- libethential/Common.h | 19 ++-- libethential/CommonData.cpp | 16 +-- libethential/CommonData.h | 18 +-- libethential/CommonIO.cpp | 8 +- libethential/CommonIO.h | 2 +- libethential/Exceptions.h | 2 +- libethential/FixedHash.cpp | 4 +- libethential/FixedHash.h | 10 +- libethential/Guards.cpp | 4 +- libethential/Guards.h | 2 +- libethential/Log.cpp | 12 +- libethential/Log.h | 18 +-- libethential/RLP.cpp | 42 +++---- libethential/RLP.h | 68 ++++++------ libethential/vector_ref.h | 12 +- libethereum/AccountDiff.cpp | 13 ++- libethereum/AccountDiff.h | 9 +- libethereum/AddressState.cpp | 3 +- libethereum/AddressState.h | 4 +- libethereum/BlockChain.cpp | 25 +++-- libethereum/BlockChain.h | 12 +- libethereum/BlockDetails.cpp | 5 +- libethereum/BlockDetails.h | 12 +- libethereum/BlockQueue.cpp | 3 +- libethereum/BlockQueue.h | 5 +- libethereum/Client.cpp | 3 +- libethereum/Client.h | 28 +++-- libethereum/CommonNet.cpp | 3 +- libethereum/CommonNet.h | 11 +- libethereum/Defaults.cpp | 3 +- libethereum/Defaults.h | 3 + libethereum/EthereumHost.cpp | 5 +- libethereum/EthereumHost.h | 7 +- libethereum/EthereumPeer.cpp | 9 +- libethereum/EthereumPeer.h | 5 +- libethereum/Executive.cpp | 7 +- libethereum/Executive.h | 3 + libethereum/ExtVM.h | 4 +- libethereum/Manifest.cpp | 3 +- libethereum/Manifest.h | 3 + libethereum/MessageFilter.cpp | 5 +- libethereum/MessageFilter.h | 3 + libethereum/Miner.cpp | 3 +- libethereum/Miner.h | 7 +- libethereum/PastMessage.cpp | 3 +- libethereum/PastMessage.h | 3 + libethereum/State.cpp | 11 +- libethereum/State.h | 6 +- libethereum/Transaction.cpp | 5 +- libethereum/Transaction.h | 9 +- libethereum/TransactionQueue.cpp | 3 +- libethereum/TransactionQueue.h | 4 +- libethereum/Utility.cpp | 13 ++- libethereum/Utility.h | 3 + libethereumx/Ethereum.cpp | 3 +- libethereumx/Ethereum.h | 3 + libevm/ExtVMFace.cpp | 3 +- libevm/ExtVMFace.h | 3 + libevm/FeeStructure.cpp | 23 ++-- libevm/FeeStructure.h | 3 + libevm/VM.cpp | 3 +- libevm/VM.h | 10 +- libevmface/Instruction.cpp | 11 +- libevmface/Instruction.h | 3 + liblll/Assembly.cpp | 17 +-- liblll/Assembly.h | 3 + liblll/CodeFragment.cpp | 3 +- liblll/CodeFragment.h | 3 + liblll/Compiler.cpp | 9 +- liblll/Compiler.h | 4 +- liblll/CompilerState.cpp | 3 +- liblll/CompilerState.h | 3 + liblll/Exceptions.h | 7 +- liblll/Parser.cpp | 15 +-- liblll/Parser.h | 4 +- libp2p/CMakeLists.txt | 1 + libp2p/Capability.cpp | 4 +- libp2p/Capability.h | 3 + libp2p/Common.cpp | 4 +- libp2p/Common.h | 13 +-- libp2p/Host.cpp | 4 +- libp2p/Host.h | 8 +- libp2p/HostCapability.cpp | 4 +- libp2p/HostCapability.h | 5 + libp2p/Session.cpp | 6 +- libp2p/Session.h | 6 +- libp2p/UPnP.cpp | 4 +- libp2p/UPnP.h | 3 + libqethereum/QEthereum.cpp | 103 ++++++++--------- libqethereum/QEthereum.h | 90 +++++++-------- libqethereum/QmlEthereum.cpp | 60 +++++----- libqethereum/QmlEthereum.h | 168 ++++++++++++++-------------- libwebthree/CMakeLists.txt | 69 ++++++++++++ libwebthree/Client.cpp | 172 +++++++++++++++++++++++++++++ libwebthree/Client.h | 101 +++++++++++++++++ libwhisper/Common.cpp | 6 +- libwhisper/Common.h | 21 ++-- libwhisper/WhisperPeer.cpp | 8 +- libwhisper/WhisperPeer.h | 23 ++-- lllc/main.cpp | 7 +- neth/main.cpp | 31 +++--- test/MemTrie.cpp | 14 ++- test/MemTrie.h | 2 +- test/TestHelper.cpp | 6 +- test/TestHelper.h | 5 + test/TrieHash.cpp | 13 ++- test/TrieHash.h | 2 +- test/crypto.cpp | 5 +- test/dagger.cpp | 3 +- test/fork.cpp | 3 +- test/genesis.cpp | 3 +- test/hexPrefix.cpp | 3 +- test/main.cpp | 3 +- test/network.cpp | 3 +- test/peer.cpp | 4 +- test/rlp.cpp | 18 +-- test/state.cpp | 3 +- test/trie.cpp | 23 ++-- test/txTest.cpp | 9 +- test/vm.cpp | 13 ++- third/MainWin.cpp | 104 ++++++++--------- third/MainWin.h | 22 ++-- walleth/MainWin.cpp | 74 ++++++------- walleth/MainWin.h | 24 ++-- 155 files changed, 1602 insertions(+), 1012 deletions(-) create mode 100644 libwebthree/CMakeLists.txt create mode 100644 libwebthree/Client.cpp create mode 100644 libwebthree/Client.h diff --git a/CMakeLists.txt b/CMakeLists.txt index fcadd4be5..92d2986e1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -343,9 +343,9 @@ if (NOT LANGUAGES) add_subdirectory(libethcore) add_subdirectory(libevm) add_subdirectory(libethereum) - add_subdirectory(libethereumx) # TODO remove +# add_subdirectory(libethereumx) # TODO remove - #add_subdirectory(libwebthree) + add_subdirectory(libwebthree) add_subdirectory(test) add_subdirectory(eth) if("x${CMAKE_BUILD_TYPE}" STREQUAL "xDebug") diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index 8a78a2e7f..374f8a209 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -45,51 +45,51 @@ using namespace std; // types -using eth::bytes; -using eth::bytesConstRef; -using eth::h160; -using eth::h256; -using eth::u160; -using eth::u256; -using eth::Address; -using eth::BlockInfo; -using eth::Client; -using eth::Instruction; -using eth::KeyPair; -using eth::NodeMode; -using eth::BlockChain; -using p2p::PeerInfo; -using eth::RLP; -using eth::Secret; -using eth::Transaction; -using eth::Executive; +using dev::bytes; +using dev::bytesConstRef; +using dev::h160; +using dev::h256; +using dev::u160; +using dev::u256; +using dev::eth::Address; +using dev::eth::BlockInfo; +using dev::eth::Client; +using dev::eth::Instruction; +using dev::eth::KeyPair; +using dev::eth::NodeMode; +using dev::eth::BlockChain; +using dev::p2p::PeerInfo; +using dev::RLP; +using dev::eth::Secret; +using dev::eth::Transaction; +using dev::eth::Executive; // functions -using eth::toHex; -using eth::compileLLL; -using eth::disassemble; -using eth::formatBalance; -using eth::fromHex; -using eth::sha3; -using eth::left160; -using eth::right160; -using eth::simpleDebugOut; -using eth::toLog2; -using eth::toString; -using eth::units; -using eth::operator<<; +using dev::toHex; +using dev::fromHex; +using dev::left160; +using dev::right160; +using dev::simpleDebugOut; +using dev::toLog2; +using dev::toString; +using dev::operator<<; +using dev::eth::units; +using dev::eth::sha3; +using dev::eth::compileLLL; +using dev::eth::disassemble; +using dev::eth::formatBalance; // vars -using eth::g_logPost; -using eth::g_logVerbosity; +using dev::g_logPost; +using dev::g_logVerbosity; static void initUnits(QComboBox* _b) { - for (auto n = (::uint)units().size(); n-- != 0; ) + for (auto n = (unsigned)units().size(); n-- != 0; ) _b->addItem(QString::fromStdString(units()[n].second), n); } -static QString fromRaw(eth::h256 _n, unsigned* _inc = nullptr) +static QString fromRaw(dev::h256 _n, unsigned* _inc = nullptr) { if (_n) { @@ -136,7 +136,7 @@ Main::Main(QWidget *parent) : #if 0&Ð_DEBUG m_servers.append("192.168.0.10:30301"); #else - int pocnumber = QString(eth::EthVersion).section('.', 1, 1).toInt(); + int pocnumber = QString(dev::Version).section('.', 1, 1).toInt(); if (pocnumber == 5) m_servers.push_back("54.72.69.180:30303"); else if (pocnumber == 6) @@ -158,8 +158,8 @@ Main::Main(QWidget *parent) : cerr << "Block Hash: " << sha3(BlockChain::createGenesisBlock()) << endl; cerr << "Block RLP: " << RLP(BlockChain::createGenesisBlock()) << endl; cerr << "Block Hex: " << toHex(BlockChain::createGenesisBlock()) << endl; - cerr << "Network protocol version: " << eth::c_protocolVersion << endl; - cerr << "Client database version: " << eth::c_databaseVersion << endl; + cerr << "Network protocol version: " << dev::eth::c_protocolVersion << endl; + cerr << "Client database version: " << dev::eth::c_databaseVersion << endl; ui->configDock->close(); on_verbosity_valueChanged(); @@ -231,14 +231,14 @@ void Main::onKeysChanged() installBalancesWatch(); } -unsigned Main::installWatch(eth::MessageFilter const& _tf, std::function const& _f) +unsigned Main::installWatch(dev::eth::MessageFilter const& _tf, std::function const& _f) { auto ret = m_client->installWatch(_tf); m_handlers[ret] = _f; return ret; } -unsigned Main::installWatch(eth::h256 _tf, std::function const& _f) +unsigned Main::installWatch(dev::h256 _tf, std::function const& _f) { auto ret = m_client->installWatch(_tf); m_handlers[ret] = _f; @@ -247,27 +247,27 @@ unsigned Main::installWatch(eth::h256 _tf, std::function const& _f) void Main::installWatches() { - installWatch(eth::MessageFilter().altered(c_config, 0), [=](){ installNameRegWatch(); }); - installWatch(eth::MessageFilter().altered(c_config, 1), [=](){ installCurrenciesWatch(); }); - installWatch(eth::PendingChangedFilter, [=](){ onNewPending(); }); - installWatch(eth::ChainChangedFilter, [=](){ onNewBlock(); }); + installWatch(dev::eth::MessageFilter().altered(c_config, 0), [=](){ installNameRegWatch(); }); + installWatch(dev::eth::MessageFilter().altered(c_config, 1), [=](){ installCurrenciesWatch(); }); + installWatch(dev::eth::PendingChangedFilter, [=](){ onNewPending(); }); + installWatch(dev::eth::ChainChangedFilter, [=](){ onNewBlock(); }); } void Main::installNameRegWatch() { m_client->uninstallWatch(m_nameRegFilter); - m_nameRegFilter = installWatch(eth::MessageFilter().altered((u160)m_client->stateAt(c_config, 0)), [=](){ onNameRegChange(); }); + m_nameRegFilter = installWatch(dev::eth::MessageFilter().altered((u160)m_client->stateAt(c_config, 0)), [=](){ onNameRegChange(); }); } void Main::installCurrenciesWatch() { m_client->uninstallWatch(m_currenciesFilter); - m_currenciesFilter = installWatch(eth::MessageFilter().altered((u160)m_client->stateAt(c_config, 1)), [=](){ onCurrenciesChange(); }); + m_currenciesFilter = installWatch(dev::eth::MessageFilter().altered((u160)m_client->stateAt(c_config, 1)), [=](){ onCurrenciesChange(); }); } void Main::installBalancesWatch() { - eth::MessageFilter tf; + dev::eth::MessageFilter tf; vector
    altCoins; Address coinsAddr = right160(m_client->stateAt(c_config, 1)); @@ -417,7 +417,7 @@ void Main::eval(QString const& _js) ui->jsConsole->setHtml(s); } -QString Main::pretty(eth::Address _a) const +QString Main::pretty(dev::eth::Address _a) const { h256 n; @@ -430,7 +430,7 @@ QString Main::pretty(eth::Address _a) const return fromRaw(n); } -QString Main::render(eth::Address _a) const +QString Main::render(dev::eth::Address _a) const { QString p = pretty(_a); if (!p.isNull()) @@ -500,7 +500,7 @@ QString Main::lookup(QString const& _a) const void Main::on_about_triggered() { - QMessageBox::about(this, "About AlethZero PoC-" + QString(eth::EthVersion).section('.', 1, 1), QString("AlethZero/v") + eth::EthVersion + "/" ETH_QUOTED(ETH_BUILD_TYPE) "/" ETH_QUOTED(ETH_BUILD_PLATFORM) "\n" ETH_QUOTED(ETH_COMMIT_HASH) + (ETH_CLEAN_REPO ? "\nCLEAN" : "\n+ LOCAL CHANGES") + "\n\nBy Gav Wood, 2014.\nBased on a design by Vitalik Buterin.\n\nThanks to the various contributors including: Alex Leverington, Tim Hughes, caktux, Eric Lombrozo, Marko Simovic."); + QMessageBox::about(this, "About AlethZero PoC-" + QString(dev::Version).section('.', 1, 1), QString("AlethZero/v") + dev::Version + "/" DEV_QUOTED(ETH_BUILD_TYPE) "/" DEV_QUOTED(ETH_BUILD_PLATFORM) "\n" DEV_QUOTED(ETH_COMMIT_HASH) + (ETH_CLEAN_REPO ? "\nCLEAN" : "\n+ LOCAL CHANGES") + "\n\nBy Gav Wood, 2014.\nBased on a design by Vitalik Buterin.\n\nThanks to the various contributors including: Alex Leverington, Tim Hughes, caktux, Eric Lombrozo, Marko Simovic."); } void Main::on_paranoia_triggered() @@ -670,18 +670,18 @@ void Main::on_preview_triggered() void Main::refreshMining() { - eth::MineProgress p = m_client->miningProgress(); + dev::eth::MineProgress p = m_client->miningProgress(); ui->mineStatus->setText(m_client->isMining() ? QString("%1s @ %2kH/s").arg(p.ms / 1000).arg(p.ms ? p.hashes / p.ms : 0) : "Not mining"); if (!ui->miningView->isVisible()) return; - list l = m_client->miningHistory(); - static uint lh = 0; + list l = m_client->miningHistory(); + static unsigned lh = 0; if (p.hashes < lh) ui->miningView->resetStats(); lh = p.hashes; ui->miningView->appendStats(l, p); /* if (p.ms) - for (eth::MineInfo const& i: l) + for (dev::eth::MineInfo const& i: l) cnote << i.hashes * 10 << "h/sec, need:" << i.requirement << " best:" << i.best << " best-so-far:" << p.best << " avg-speed:" << (p.hashes * 1000 / p.ms) << "h/sec"; */ } @@ -808,10 +808,10 @@ void Main::refreshBlockCount() cwatch << "refreshBlockCount()"; auto d = m_client->blockChain().details(); auto diff = BlockInfo(m_client->blockChain().block()).difficulty; - ui->blockCount->setText(QString("%6 #%1 @%3 T%2 N%4 D%5").arg(d.number).arg(toLog2(d.totalDifficulty)).arg(toLog2(diff)).arg(eth::c_protocolVersion).arg(eth::c_databaseVersion).arg(m_privateChain.size() ? "[" + m_privateChain + "] " : "testnet")); + ui->blockCount->setText(QString("%6 #%1 @%3 T%2 N%4 D%5").arg(d.number).arg(toLog2(d.totalDifficulty)).arg(toLog2(diff)).arg(dev::eth::c_protocolVersion).arg(dev::eth::c_databaseVersion).arg(m_privateChain.size() ? "[" + m_privateChain + "] " : "testnet")); } -static bool blockMatch(string const& _f, eth::BlockDetails const& _b, h256 _h, BlockChain const& _bc) +static bool blockMatch(string const& _f, dev::eth::BlockDetails const& _b, h256 _h, BlockChain const& _bc) { try { @@ -951,7 +951,7 @@ void Main::timerEvent(QTimerEvent*) i.second(); } -string Main::renderDiff(eth::StateDiff const& _d) const +string Main::renderDiff(dev::eth::StateDiff const& _d) const { stringstream s; @@ -960,7 +960,7 @@ string Main::renderDiff(eth::StateDiff const& _d) const { s << "
    "; - eth::AccountDiff const& ad = i.second; + dev::eth::AccountDiff const& ad = i.second; s << "" << ad.lead() << " " << " " << render(i.first).toStdString() << ""; if (!ad.exist.to()) continue; @@ -968,12 +968,12 @@ string Main::renderDiff(eth::StateDiff const& _d) const if (ad.balance) { s << "
    " << indent << "Balance " << std::dec << formatBalance(ad.balance.to()); - s << " " << std::showpos << (((eth::bigint)ad.balance.to()) - ((eth::bigint)ad.balance.from())) << std::noshowpos << ""; + s << " " << std::showpos << (((dev::bigint)ad.balance.to()) - ((dev::bigint)ad.balance.from())) << std::noshowpos << ""; } if (ad.nonce) { s << "
    " << indent << "Count #" << std::dec << ad.nonce.to(); - s << " " << std::showpos << (((eth::bigint)ad.nonce.to()) - ((eth::bigint)ad.nonce.from())) << std::noshowpos << ""; + s << " " << std::showpos << (((dev::bigint)ad.nonce.to()) - ((dev::bigint)ad.nonce.from())) << std::noshowpos << ""; } if (ad.code) { @@ -982,7 +982,7 @@ string Main::renderDiff(eth::StateDiff const& _d) const s << " (" << ad.code.from().size() << " bytes)"; } - for (pair> const& i: ad.storage) + for (pair> const& i: ad.storage) { s << "
    "; if (!i.second.from()) @@ -1041,7 +1041,7 @@ void Main::on_transactionQueue_currentItemChanged() else { if (tx.data.size()) - s << eth::memDump(tx.data, 16, true); + s << dev::memDump(tx.data, 16, true); } s << "
    "; @@ -1148,7 +1148,7 @@ void Main::on_blocks_currentItemChanged() else { if (tx.data.size()) - s << eth::memDump(tx.data, 16, true); + s << dev::memDump(tx.data, 16, true); } s << renderDiff(m_client->diff(txi, h)); ui->debugCurrent->setEnabled(true); @@ -1209,7 +1209,7 @@ void Main::on_debugDumpStatePre_triggered() on_debugDumpState_triggered(0); } -void Main::populateDebugger(eth::bytesConstRef _r) +void Main::populateDebugger(dev::bytesConstRef _r) { bool done = m_currentExecution->setup(_r); if (!done) @@ -1221,10 +1221,10 @@ void Main::populateDebugger(eth::bytesConstRef _r) bytesConstRef lastData; h256 lastHash; h256 lastDataHash; - auto onOp = [&](uint64_t steps, Instruction inst, eth::bigint newMemSize, eth::bigint gasCost, void* voidVM, void const* voidExt) + auto onOp = [&](uint64_t steps, Instruction inst, dev::bigint newMemSize, dev::bigint gasCost, void* voidVM, void const* voidExt) { - eth::VM& vm = *(eth::VM*)voidVM; - eth::ExtVM const& ext = *(eth::ExtVM const*)voidExt; + dev::eth::VM& vm = *(dev::eth::VM*)voidVM; + dev::eth::ExtVM const& ext = *(dev::eth::ExtVM const*)voidExt; if (ext.code != lastExtCode) { lastExtCode = ext.code; @@ -1270,7 +1270,7 @@ void Main::on_contracts_currentItemChanged() s << "

    Body Code

    " << disassemble(m_client->codeAt(address)); ui->contractInfo->appendHtml(QString::fromStdString(s.str())); } - catch (eth::InvalidTrie) + catch (dev::eth::InvalidTrie) { ui->contractInfo->appendHtml("Corrupted trie."); } @@ -1337,12 +1337,12 @@ void Main::on_data_textChanged() } else { - m_data = eth::compileLLL(src, m_enableOptimizer, &errors); + m_data = dev::eth::compileLLL(src, m_enableOptimizer, &errors); if (errors.size()) { try { - m_data = eth::asBytes(::compile(src)); + m_data = dev::asBytes(::compile(src)); for (auto& i: errors) i = "(LLL " + i + ")"; } @@ -1353,11 +1353,11 @@ void Main::on_data_textChanged() } else { - auto asmcode = eth::compileLLLToAsm(src, false); + auto asmcode = dev::eth::compileLLLToAsm(src, false); lll = "

    Pre

    " + QString::fromStdString(asmcode).toHtmlEscaped() + "
    "; if (m_enableOptimizer) { - asmcode = eth::compileLLLToAsm(src, true); + asmcode = dev::eth::compileLLLToAsm(src, true); lll = "

    Opt

    " + QString::fromStdString(asmcode).toHtmlEscaped() + "
    " + lll; } } @@ -1399,12 +1399,12 @@ void Main::on_data_textChanged() { u256 v(d.cap(2).toStdString()); if (d.cap(6) == "szabo") - v *= eth::szabo; + v *= dev::eth::szabo; else if (d.cap(5) == "finney") - v *= eth::finney; + v *= dev::eth::finney; else if (d.cap(4) == "ether") - v *= eth::ether; - bytes bs = eth::toCompactBigEndian(v); + v *= dev::eth::ether; + bytes bs = dev::toCompactBigEndian(v); if (d.cap(1) != "$") for (auto i = bs.size(); i < 32; ++i) m_data.push_back(0); @@ -1425,7 +1425,7 @@ void Main::on_data_textChanged() else s = s.mid(1); } - ui->code->setHtml(QString::fromStdString(eth::memDump(m_data, 8, true))); + ui->code->setHtml(QString::fromStdString(dev::memDump(m_data, 8, true))); if (m_client->codeAt(fromString(ui->destination->currentText()), 0).size()) { ui->gas->setMinimum((qint64)Client::txGas(m_data.size(), 1)); @@ -1509,10 +1509,10 @@ void Main::on_net_triggered() { ui->port->setEnabled(!ui->net->isChecked()); ui->clientName->setEnabled(!ui->net->isChecked()); - string n = string("AlethZero/v") + eth::EthVersion; + string n = string("AlethZero/v") + dev::Version; if (ui->clientName->text().size()) n += "/" + ui->clientName->text().toStdString(); - n += "/" ETH_QUOTED(ETH_BUILD_TYPE) "/" ETH_QUOTED(ETH_BUILD_PLATFORM); + n += "/" DEV_QUOTED(ETH_BUILD_TYPE) "/" DEV_QUOTED(ETH_BUILD_PLATFORM); m_client->setClientVersion(n); if (ui->net->isChecked()) { @@ -1588,7 +1588,7 @@ void Main::on_debug_clicked() m_executiveState = m_client->postState(); m_currentExecution = unique_ptr(new Executive(m_executiveState)); Transaction t; - t.nonce = m_executiveState.transactionsFrom(toAddress(s)); + t.nonce = m_executiveState.transactionsFrom(dev::eth::toAddress(s)); t.value = value(); t.gasPrice = gasPrice(); t.gas = ui->gas->value(); @@ -1602,7 +1602,7 @@ void Main::on_debug_clicked() } statusBar()->showMessage("Couldn't make transaction: no single account contains at least the required amount."); } - catch (eth::Exception const& _e) + catch (dev::Exception const& _e) { statusBar()->showMessage("Error running transaction: " + QString::fromStdString(_e.description())); } @@ -1684,7 +1684,7 @@ void Main::on_dumpTrace_triggered() ofstream f(fn.toStdString()); if (f.is_open()) for (WorldState const& ws: m_history) - f << ws.cur << " " << hex << toHex(eth::toCompactBigEndian(ws.curPC, 1)) << " " << hex << toHex(eth::toCompactBigEndian((int)(byte)ws.inst, 1)) << " " << hex << toHex(eth::toCompactBigEndian((uint64_t)ws.gas, 1)) << endl; + f << ws.cur << " " << hex << toHex(dev::toCompactBigEndian(ws.curPC, 1)) << " " << hex << toHex(dev::toCompactBigEndian((int)(byte)ws.inst, 1)) << " " << hex << toHex(dev::toCompactBigEndian((uint64_t)ws.gas, 1)) << endl; } void Main::on_dumpTracePretty_triggered() @@ -1697,11 +1697,11 @@ void Main::on_dumpTracePretty_triggered() f << endl << " STACK" << endl; for (auto i: ws.stack) f << (h256)i << endl; - f << " MEMORY" << endl << eth::memDump(ws.memory); + f << " MEMORY" << endl << dev::memDump(ws.memory); f << " STORAGE" << endl; for (auto const& i: ws.storage) f << showbase << hex << i.first << ": " << i.second << endl; - f << dec << ws.levels.size() << " | " << ws.cur << " | #" << ws.steps << " | " << hex << setw(4) << setfill('0') << ws.curPC << " : " << eth::instructionInfo(ws.inst).name << " | " << dec << ws.gas << " | -" << dec << ws.gasCost << " | " << ws.newMemSize << "x32"; + f << dec << ws.levels.size() << " | " << ws.cur << " | #" << ws.steps << " | " << hex << setw(4) << setfill('0') << ws.curPC << " : " << dev::eth::instructionInfo(ws.inst).name << " | " << dec << ws.gas << " | -" << dec << ws.gasCost << " | " << ws.newMemSize << "x32"; } } @@ -1714,8 +1714,8 @@ void Main::on_dumpTraceStorage_triggered() { if (ws.inst == Instruction::STOP || ws.inst == Instruction::RETURN || ws.inst == Instruction::SUICIDE) for (auto i: ws.storage) - f << toHex(eth::toCompactBigEndian(i.first, 1)) << " " << toHex(eth::toCompactBigEndian(i.second, 1)) << endl; - f << ws.cur << " " << hex << toHex(eth::toCompactBigEndian(ws.curPC, 1)) << " " << hex << toHex(eth::toCompactBigEndian((int)(byte)ws.inst, 1)) << " " << hex << toHex(eth::toCompactBigEndian((uint64_t)ws.gas, 1)) << endl; + f << toHex(dev::toCompactBigEndian(i.first, 1)) << " " << toHex(dev::toCompactBigEndian(i.second, 1)) << endl; + f << ws.cur << " " << hex << toHex(dev::toCompactBigEndian(ws.curPC, 1)) << " " << hex << toHex(dev::toCompactBigEndian((int)(byte)ws.inst, 1)) << " " << hex << toHex(dev::toCompactBigEndian((uint64_t)ws.gas, 1)) << endl; } } @@ -1773,7 +1773,7 @@ void Main::on_debugTimeline_valueChanged() updateDebugger(); } -QString Main::prettyU256(eth::u256 _n) const +QString Main::prettyU256(dev::u256 _n) const { unsigned inc = 0; QString raw; @@ -1817,7 +1817,7 @@ void Main::updateDebugger() bytes out(size, 0); for (; o < size && from + o < ws.memory.size(); ++o) out[o] = ws.memory[from + o]; - ui->debugMemory->setHtml("

    RETURN

    " + QString::fromStdString(eth::memDump(out, 16, true))); + ui->debugMemory->setHtml("

    RETURN

    " + QString::fromStdString(dev::memDump(out, 16, true))); } else if (ws.inst == Instruction::STOP) ui->debugMemory->setHtml("

    STOP

    "); @@ -1827,7 +1827,7 @@ void Main::updateDebugger() ui->debugMemory->setHtml("

    EXCEPTION

    "); ostringstream ss; - ss << dec << "EXIT | GAS: " << dec << max(0, (eth::bigint)ws.gas - ws.gasCost); + ss << dec << "EXIT | GAS: " << dec << max(0, (dev::bigint)ws.gas - ws.gasCost); ui->debugStateInfo->setText(QString::fromStdString(ss.str())); ui->debugStorage->setHtml(""); ui->debugCallData->setHtml(""); @@ -1892,7 +1892,7 @@ void Main::updateDebugger() if (ws.callData) { assert(m_codes.count(ws.callData)); - ui->debugCallData->setHtml(QString::fromStdString(eth::memDump(m_codes[ws.callData], 16, true))); + ui->debugCallData->setHtml(QString::fromStdString(dev::memDump(m_codes[ws.callData], 16, true))); } else ui->debugCallData->setHtml(""); @@ -1902,7 +1902,7 @@ void Main::updateDebugger() for (auto i: ws.stack) stack.prepend("
    " + prettyU256(i) + "
    "); ui->debugStack->setHtml(stack); - ui->debugMemory->setHtml(QString::fromStdString(eth::memDump(ws.memory, 16, true))); + ui->debugMemory->setHtml(QString::fromStdString(dev::memDump(ws.memory, 16, true))); assert(m_codes.count(ws.code)); if (m_codes[ws.code].size() >= (unsigned)ws.curPC) @@ -1916,7 +1916,7 @@ void Main::updateDebugger() cwarn << "PC (" << (unsigned)ws.curPC << ") is after code range (" << m_codes[ws.code].size() << ")"; ostringstream ss; - ss << dec << "STEP: " << ws.steps << " | PC: 0x" << hex << ws.curPC << " : " << eth::instructionInfo(ws.inst).name << " | ADDMEM: " << dec << ws.newMemSize << " words | COST: " << dec << ws.gasCost << " | GAS: " << dec << ws.gas; + ss << dec << "STEP: " << ws.steps << " | PC: 0x" << hex << ws.curPC << " : " << dev::eth::instructionInfo(ws.inst).name << " | ADDMEM: " << dec << ws.newMemSize << " words | COST: " << dec << ws.gasCost << " | GAS: " << dec << ws.gas; ui->debugStateInfo->setText(QString::fromStdString(ss.str())); stringstream s; for (auto const& i: ws.storage) diff --git a/alethzero/MainWin.h b/alethzero/MainWin.h index 0474bb85d..e15f5ce2e 100644 --- a/alethzero/MainWin.h +++ b/alethzero/MainWin.h @@ -36,28 +36,28 @@ namespace Ui { class Main; } -namespace eth { +namespace dev { namespace eth { class Client; class State; class MessageFilter; -} +}} class QQuickView; struct WorldState { uint64_t steps; - eth::Address cur; - eth::u256 curPC; - eth::Instruction inst; - eth::bigint newMemSize; - eth::u256 gas; - eth::h256 code; - eth::h256 callData; - eth::u256s stack; - eth::bytes memory; - eth::bigint gasCost; - std::map storage; + dev::eth::Address cur; + dev::u256 curPC; + dev::eth::Instruction inst; + dev::bigint newMemSize; + dev::u256 gas; + dev::h256 code; + dev::h256 callData; + dev::u256s stack; + dev::bytes memory; + dev::bigint gasCost; + std::map storage; std::vector levels; }; @@ -69,9 +69,9 @@ public: explicit Main(QWidget *parent = 0); ~Main(); - eth::Client* client() { return m_client.get(); } + dev::eth::Client* client() { return m_client.get(); } - QList const& owned() const { return m_myKeys; } + QList const& owned() const { return m_myKeys; } public slots: void load(QString _file); @@ -146,18 +146,18 @@ signals: void poll(); private: - QString pretty(eth::Address _a) const; - QString prettyU256(eth::u256 _n) const; + QString pretty(dev::eth::Address _a) const; + QString prettyU256(dev::u256 _n) const; QString lookup(QString const& _n) const; - void populateDebugger(eth::bytesConstRef r); + void populateDebugger(dev::bytesConstRef r); void initDebugger(); void updateDebugger(); void debugFinished(); - QString render(eth::Address _a) const; - eth::Address fromString(QString const& _a) const; - std::string renderDiff(eth::StateDiff const& _d) const; + QString render(dev::eth::Address _a) const; + dev::eth::Address fromString(QString const& _a) const; + std::string renderDiff(dev::eth::StateDiff const& _d) const; void alterDebugStateGroup(bool _enable) const; @@ -166,13 +166,13 @@ private: void writeSettings(); bool isCreation() const; - eth::u256 fee() const; - eth::u256 total() const; - eth::u256 value() const; - eth::u256 gasPrice() const; + dev::u256 fee() const; + dev::u256 total() const; + dev::u256 value() const; + dev::u256 gasPrice() const; - unsigned installWatch(eth::MessageFilter const& _tf, std::function const& _f); - unsigned installWatch(eth::h256 _tf, std::function const& _f); + unsigned installWatch(dev::eth::MessageFilter const& _tf, std::function const& _f); + unsigned installWatch(dev::h256 _tf, std::function const& _f); void onNewPending(); void onNewBlock(); @@ -200,7 +200,7 @@ private: std::unique_ptr ui; - std::unique_ptr m_client; + std::unique_ptr m_client; std::map> m_handlers; unsigned m_nameRegFilter = (unsigned)-1; unsigned m_currenciesFilter = (unsigned)-1; @@ -208,23 +208,23 @@ private: QByteArray m_peers; QStringList m_servers; - QList m_myKeys; + QList m_myKeys; QString m_privateChain; bool m_keysChanged = false; - eth::bytes m_data; - eth::Address m_nameReg; + dev::bytes m_data; + dev::eth::Address m_nameReg; unsigned m_backupGas; - eth::State m_executiveState; - std::unique_ptr m_currentExecution; - eth::h256 m_lastCode; - eth::h256 m_lastData; + dev::eth::State m_executiveState; + std::unique_ptr m_currentExecution; + dev::h256 m_lastCode; + dev::h256 m_lastData; std::vector m_lastLevels; QMap m_pcWarp; QList m_history; - std::map m_codes; // and pcWarps + std::map m_codes; // and pcWarps bool m_enableOptimizer = true; QNetworkAccessManager m_webCtrl; diff --git a/alethzero/MiningView.cpp b/alethzero/MiningView.cpp index f09fe425f..fa6da737b 100644 --- a/alethzero/MiningView.cpp +++ b/alethzero/MiningView.cpp @@ -29,17 +29,18 @@ using namespace std; using namespace lb; -// do *not* use eth since eth::uint conflicts with Qt's global unit definition -// using namespace eth; +// do *not* use eth since unsigned conflicts with Qt's global unit definition +// using namespace dev; +using namespace dev::eth; // types -using eth::MineInfo; -using eth::MineProgress; +using dev::eth::MineInfo; +using dev::eth::MineProgress; // functions -using eth::toString; -using eth::trimFront; +using dev::toString; +using dev::trimFront; string id(float _y) { return toString(_y); } string s(float _x){ return toString(round(_x * 1000) / 1000) + (!_x ? "s" : ""); } diff --git a/alethzero/MiningView.h b/alethzero/MiningView.h index 91e17e029..8f3135f75 100644 --- a/alethzero/MiningView.h +++ b/alethzero/MiningView.h @@ -31,10 +31,9 @@ #include #endif -namespace eth -{ +namespace dev { namespace eth { struct MineInfo; -} +}} class MiningView: public QWidget { @@ -43,14 +42,14 @@ class MiningView: public QWidget public: MiningView(QWidget* _p = nullptr); - void appendStats(std::list const& _l, eth::MineProgress const& _p); + void appendStats(std::list const& _l, dev::eth::MineProgress const& _p); void resetStats(); protected: virtual void paintEvent(QPaintEvent*); private: - eth::MineProgress m_progress; + dev::eth::MineProgress m_progress; unsigned m_duration = 300; std::vector m_values; std::vector m_bests; diff --git a/eth/CommonJS.cpp b/eth/CommonJS.cpp index e4c60a120..57958a117 100644 --- a/eth/CommonJS.cpp +++ b/eth/CommonJS.cpp @@ -22,9 +22,10 @@ #include "CommonJS.h" using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; -bytes eth::jsToBytes(string const& _s) +bytes dev::eth::jsToBytes(string const& _s) { if (_s.substr(0, 2) == "0x") // Hex @@ -37,7 +38,7 @@ bytes eth::jsToBytes(string const& _s) return asBytes(_s); } -string eth::jsPadded(string const& _s, unsigned _l, unsigned _r) +string dev::eth::jsPadded(string const& _s, unsigned _l, unsigned _r) { bytes b = jsToBytes(_s); while (b.size() < _l) @@ -47,7 +48,7 @@ string eth::jsPadded(string const& _s, unsigned _l, unsigned _r) return asString(b).substr(b.size() - max(_l, _r)); } -string eth::jsPadded(string const& _s, unsigned _l) +string dev::eth::jsPadded(string const& _s, unsigned _l) { if (_s.substr(0, 2) == "0x" || _s.find_first_not_of("0123456789") == string::npos) // Numeric: pad to right @@ -57,7 +58,7 @@ string eth::jsPadded(string const& _s, unsigned _l) return jsPadded(_s, 0, _l); } -string eth::jsUnpadded(string _s) +string dev::eth::jsUnpadded(string _s) { auto p = _s.find_last_not_of((char)0); _s.resize(p == string::npos ? 0 : (p + 1)); diff --git a/eth/CommonJS.h b/eth/CommonJS.h index 57f2db39f..71a0c46d8 100644 --- a/eth/CommonJS.h +++ b/eth/CommonJS.h @@ -29,60 +29,63 @@ #include #include +namespace dev +{ namespace eth { -eth::bytes jsToBytes(std::string const& _s); +bytes jsToBytes(std::string const& _s); std::string jsPadded(std::string const& _s, unsigned _l, unsigned _r); std::string jsPadded(std::string const& _s, unsigned _l); std::string jsUnpadded(std::string _s); -template eth::FixedHash jsToFixed(std::string const& _s) +template FixedHash jsToFixed(std::string const& _s) { if (_s.substr(0, 2) == "0x") // Hex - return eth::FixedHash(_s.substr(2)); + return FixedHash(_s.substr(2)); else if (_s.find_first_not_of("0123456789") == std::string::npos) // Decimal - return (typename eth::FixedHash::Arith)(_s); + return (typename FixedHash::Arith)(_s); else // Binary - return eth::FixedHash(asBytes(jsPadded(_s, N))); + return FixedHash(asBytes(jsPadded(_s, N))); } template boost::multiprecision::number> jsToInt(std::string const& _s) { if (_s.substr(0, 2) == "0x") // Hex - return eth::fromBigEndian>>(eth::fromHex(_s.substr(2))); + return fromBigEndian>>(fromHex(_s.substr(2))); else if (_s.find_first_not_of("0123456789") == std::string::npos) // Decimal return boost::multiprecision::number>(_s); else // Binary - return eth::fromBigEndian>>(asBytes(jsPadded(_s, N))); + return fromBigEndian>>(asBytes(jsPadded(_s, N))); } -inline eth::Address jsToAddress(std::string const& _s) { return jsToFixed<20>(_s); } -inline eth::Secret jsToSecret(std::string const& _s) { return jsToFixed<32>(_s); } -inline eth::u256 jsToU256(std::string const& _s) { return jsToInt<32>(_s); } +inline Address jsToAddress(std::string const& _s) { return jsToFixed<20>(_s); } +inline Secret jsToSecret(std::string const& _s) { return jsToFixed<32>(_s); } +inline u256 jsToU256(std::string const& _s) { return jsToInt<32>(_s); } -template std::string toJS(eth::FixedHash const& _h) { return "0x" + toHex(_h.ref()); } -template std::string toJS(boost::multiprecision::number> const& _n) { return "0x" + eth::toHex(eth::toCompactBigEndian(_n)); } +template std::string toJS(FixedHash const& _h) { return "0x" + toHex(_h.ref()); } +template std::string toJS(boost::multiprecision::number> const& _n) { return "0x" + toHex(toCompactBigEndian(_n)); } inline std::string jsToBinary(std::string const& _s) { - return eth::asString(jsToBytes(_s)); + return asString(jsToBytes(_s)); } inline std::string jsToDecimal(std::string const& _s) { - return eth::toString(jsToU256(_s)); + return toString(jsToU256(_s)); } inline std::string jsToHex(std::string const& _s) { - return "0x" + eth::toHex(asBytes(_s)); + return "0x" + toHex(asBytes(_s)); } } +} diff --git a/eth/EthStubServer.cpp b/eth/EthStubServer.cpp index 666a658f1..86bb1ba58 100644 --- a/eth/EthStubServer.cpp +++ b/eth/EthStubServer.cpp @@ -27,7 +27,8 @@ #include #include "CommonJS.h" using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; EthStubServer::EthStubServer(jsonrpc::AbstractServerConnector* _conn, Client& _client): AbstractEthStubServer(_conn), @@ -89,7 +90,7 @@ std::string EthStubServer::create(const std::string& _bCode, const std::string& std::string EthStubServer::lll(const std::string& _s) { - return "0x" + toHex(eth::compileLLL(_s)); + return "0x" + toHex(dev::eth::compileLLL(_s)); } std::string EthStubServer::gasPrice() diff --git a/eth/EthStubServer.h b/eth/EthStubServer.h index bd7041d3d..1805cb410 100644 --- a/eth/EthStubServer.h +++ b/eth/EthStubServer.h @@ -28,13 +28,12 @@ #include "abstractethstubserver.h" #pragma GCC diagnostic pop -namespace eth { class Client; } -namespace eth { class KeyPair; } +namespace dev { namespace eth { class Client; class KeyPair; } } class EthStubServer: public AbstractEthStubServer { public: - EthStubServer(jsonrpc::AbstractServerConnector* _conn, eth::Client& _client); + EthStubServer(jsonrpc::AbstractServerConnector* _conn, dev::eth::Client& _client); virtual Json::Value procedures(); virtual std::string balanceAt(std::string const& _a); @@ -55,10 +54,10 @@ public: virtual Json::Value lastBlock(); virtual std::string lll(const std::string& s); virtual Json::Value block(const std::string&); - void setKeys(std::vector _keys) { m_keys = _keys; } + void setKeys(std::vector _keys) { m_keys = _keys; } private: - eth::Client& m_client; - std::vector m_keys; + dev::eth::Client& m_client; + std::vector m_keys; Json::Value jsontypeToValue(int); Json::Value blockJson(const std::string&); }; diff --git a/eth/main.cpp b/eth/main.cpp index bf474440e..576423a5f 100644 --- a/eth/main.cpp +++ b/eth/main.cpp @@ -42,9 +42,10 @@ #endif #include "BuildInfo.h" using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; using namespace boost::algorithm; -using eth::Instruction; +using dev::eth::Instruction; #undef RETURN @@ -124,13 +125,13 @@ string credits(bool _interactive = false) { std::ostringstream cout; cout - << "Ethereum (++) " << eth::EthVersion << endl + << "Ethereum (++) " << dev::Version << endl << " Code by Gav Wood, (c) 2013, 2014." << endl << " Based on a design by Vitalik Buterin." << endl << endl; if (_interactive) { - string vs = toString(eth::EthVersion); + string vs = toString(dev::Version); vs = vs.substr(vs.find_first_of('.') + 1)[0]; int pocnumber = stoi(vs); string m_servers; @@ -148,13 +149,13 @@ string credits(bool _interactive = false) void version() { - cout << "eth version " << eth::EthVersion << endl; - cout << "Build: " << ETH_QUOTED(ETH_BUILD_PLATFORM) << "/" << ETH_QUOTED(ETH_BUILD_TYPE) << endl; + cout << "eth version " << dev::Version << endl; + cout << "Build: " << DEV_QUOTED(ETH_BUILD_PLATFORM) << "/" << DEV_QUOTED(ETH_BUILD_TYPE) << endl; exit(0); } Address c_config = Address("ccdeac59d35627b7de09332e819d5159e7bb7250"); -string pretty(h160 _a, eth::State _st) +string pretty(h160 _a, dev::eth::State _st) { string ns; h256 n; @@ -176,7 +177,7 @@ int main(int argc, char** argv) string remoteHost; unsigned short remotePort = 30303; string dbPath; - eth::uint mining = ~(eth::uint)0; + unsigned mining = ~(unsigned)0; NodeMode mode = NodeMode::Full; unsigned peers = 5; bool interactive = false; @@ -246,7 +247,7 @@ int main(int argc, char** argv) { string m = argv[++i]; if (isTrue(m)) - mining = ~(eth::uint)0; + mining = ~(unsigned)0; else if (isFalse(m)) mining = 0; else if (int i = stoi(m)) @@ -295,7 +296,7 @@ int main(int argc, char** argv) if (!clientName.empty()) clientName += "/"; - Client c("Ethereum(++)/" + clientName + "v" + eth::EthVersion + "/" ETH_QUOTED(ETH_BUILD_TYPE) "/" ETH_QUOTED(ETH_BUILD_PLATFORM), coinbase, dbPath); + Client c("Ethereum(++)/" + clientName + "v" + dev::Version + "/" DEV_QUOTED(ETH_BUILD_TYPE) "/" DEV_QUOTED(ETH_BUILD_PLATFORM), coinbase, dbPath); c.setForceMining(true); @@ -348,14 +349,14 @@ int main(int argc, char** argv) iss >> cmd; if (cmd == "netstart") { - eth::uint port; + unsigned port; iss >> port; c.startNetwork((short)port); } else if (cmd == "connect") { string addr; - eth::uint port; + unsigned port; iss >> addr >> port; c.connect(addr, (short)port); } @@ -449,7 +450,7 @@ int main(int argc, char** argv) cnote << "Data:"; cnote << sdata; - bytes data = eth::parseData(sdata); + bytes data = dev::eth::parseData(sdata); cnote << "Bytes:"; string sbd = asString(data); bytes bbd = asBytes(sbd); @@ -587,7 +588,7 @@ int main(int argc, char** argv) ofstream f; f.open(filename); - eth::State state = c.state(index + 1, c.blockChain().numberHash(block)); + dev::eth::State state = c.state(index + 1, c.blockChain().numberHash(block)); if (index < state.pending().size()) { Executive e(state); @@ -600,33 +601,33 @@ int main(int argc, char** argv) if (format == "pretty") oof = [&](uint64_t steps, Instruction instr, bigint newMemSize, bigint gasCost, void* vvm, void const* vextVM) { - eth::VM* vm = (VM*)vvm; - eth::ExtVM const* ext = (ExtVM const*)vextVM; + dev::eth::VM* vm = (VM*)vvm; + dev::eth::ExtVM const* ext = (ExtVM const*)vextVM; f << endl << " STACK" << endl; for (auto i: vm->stack()) f << (h256)i << endl; - f << " MEMORY" << endl << eth::memDump(vm->memory()); + f << " MEMORY" << endl << dev::memDump(vm->memory()); f << " STORAGE" << endl; for (auto const& i: ext->state().storage(ext->myAddress)) f << showbase << hex << i.first << ": " << i.second << endl; - f << dec << ext->level << " | " << ext->myAddress << " | #" << steps << " | " << hex << setw(4) << setfill('0') << vm->curPC() << " : " << eth::instructionInfo(instr).name << " | " << dec << vm->gas() << " | -" << dec << gasCost << " | " << newMemSize << "x32"; + f << dec << ext->level << " | " << ext->myAddress << " | #" << steps << " | " << hex << setw(4) << setfill('0') << vm->curPC() << " : " << dev::eth::instructionInfo(instr).name << " | " << dec << vm->gas() << " | -" << dec << gasCost << " | " << newMemSize << "x32"; }; else if (format == "standard") oof = [&](uint64_t, Instruction instr, bigint, bigint, void* vvm, void const* vextVM) { - eth::VM* vm = (VM*)vvm; - eth::ExtVM const* ext = (ExtVM const*)vextVM; - f << ext->myAddress << " " << hex << toHex(eth::toCompactBigEndian(vm->curPC(), 1)) << " " << hex << toHex(eth::toCompactBigEndian((int)(byte)instr, 1)) << " " << hex << toHex(eth::toCompactBigEndian((uint64_t)vm->gas(), 1)) << endl; + dev::eth::VM* vm = (VM*)vvm; + dev::eth::ExtVM const* ext = (ExtVM const*)vextVM; + f << ext->myAddress << " " << hex << toHex(dev::toCompactBigEndian(vm->curPC(), 1)) << " " << hex << toHex(dev::toCompactBigEndian((int)(byte)instr, 1)) << " " << hex << toHex(dev::toCompactBigEndian((uint64_t)vm->gas(), 1)) << endl; }; else if (format == "standard+") oof = [&](uint64_t, Instruction instr, bigint, bigint, void* vvm, void const* vextVM) { - eth::VM* vm = (VM*)vvm; - eth::ExtVM const* ext = (ExtVM const*)vextVM; + dev::eth::VM* vm = (VM*)vvm; + dev::eth::ExtVM const* ext = (ExtVM const*)vextVM; if (instr == Instruction::STOP || instr == Instruction::RETURN || instr == Instruction::SUICIDE) for (auto const& i: ext->state().storage(ext->myAddress)) - f << toHex(eth::toCompactBigEndian(i.first, 1)) << " " << toHex(eth::toCompactBigEndian(i.second, 1)) << endl; - f << ext->myAddress << " " << hex << toHex(eth::toCompactBigEndian(vm->curPC(), 1)) << " " << hex << toHex(eth::toCompactBigEndian((int)(byte)instr, 1)) << " " << hex << toHex(eth::toCompactBigEndian((uint64_t)vm->gas(), 1)) << endl; + f << toHex(dev::toCompactBigEndian(i.first, 1)) << " " << toHex(dev::toCompactBigEndian(i.second, 1)) << endl; + f << ext->myAddress << " " << hex << toHex(dev::toCompactBigEndian(vm->curPC(), 1)) << " " << hex << toHex(dev::toCompactBigEndian((int)(byte)instr, 1)) << " " << hex << toHex(dev::toCompactBigEndian((uint64_t)vm->gas(), 1)) << endl; }; e.go(oof); e.finalize(oof); @@ -659,7 +660,7 @@ int main(int argc, char** argv) cnote << "Saved" << rechex << "to" << outFile; } - catch (eth::InvalidTrie) + catch (dev::eth::InvalidTrie) { cwarn << "Corrupted trie."; } @@ -736,7 +737,7 @@ int main(int argc, char** argv) } else { - eth::uint n = c.blockChain().details().number; + unsigned n = c.blockChain().details().number; if (mining) c.startMining(); while (true) diff --git a/exp/main.cpp b/exp/main.cpp index 6c3e21af6..1de09e4ac 100644 --- a/exp/main.cpp +++ b/exp/main.cpp @@ -37,9 +37,10 @@ #include "BuildInfo.h" #endif using namespace std; -using namespace p2p; -using namespace eth; -using namespace shh; +using namespace dev; +using namespace dev::eth; +using namespace dev::p2p; +using namespace dev::shh; #if 0 #if 0 namespace qi = boost::spirit::qi; @@ -218,6 +219,7 @@ void debugOutAST(ostream& _out, sp::utree const& _this) } } +namespace dev { namespace eth { namespace parseTreeLLL_ { @@ -230,13 +232,13 @@ struct tagNode } }; -}} +}}} void parseTree(string const& _s, sp::utree& o_out) { using qi::standard::space; using qi::standard::space_type; - using eth::parseTreeLLL_::tagNode; + using dev::eth::parseTreeLLL_::tagNode; typedef sp::basic_string symbol_type; typedef string::const_iterator it; diff --git a/libethcore/BlockInfo.cpp b/libethcore/BlockInfo.cpp index 55026f670..d1028c21e 100644 --- a/libethcore/BlockInfo.cpp +++ b/libethcore/BlockInfo.cpp @@ -28,9 +28,10 @@ #include "Exceptions.h" #include "BlockInfo.h" using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; -u256 eth::c_genesisDifficulty = (u256)1 << 17; +u256 dev::eth::c_genesisDifficulty = (u256)1 << 17; BlockInfo::BlockInfo(): timestamp(Invalid256) { @@ -71,7 +72,7 @@ h256 BlockInfo::headerHash(bytesConstRef _block) void BlockInfo::populateFromHeader(RLP const& _header, bool _checkNonce) { - hash = eth::sha3(_header.data()); + hash = dev::eth::sha3(_header.data()); int field = 0; try diff --git a/libethcore/BlockInfo.h b/libethcore/BlockInfo.h index d88b9d09f..54efd8c29 100644 --- a/libethcore/BlockInfo.h +++ b/libethcore/BlockInfo.h @@ -25,6 +25,8 @@ #include #include "CommonEth.h" +namespace dev +{ namespace eth { @@ -121,5 +123,4 @@ inline std::ostream& operator<<(std::ostream& _out, BlockInfo const& _bi) } } - - +} diff --git a/libethcore/CommonEth.cpp b/libethcore/CommonEth.cpp index 3fc817c72..c8632274c 100644 --- a/libethcore/CommonEth.cpp +++ b/libethcore/CommonEth.cpp @@ -25,12 +25,17 @@ #include #include "Exceptions.h" using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; //#define ETH_ADDRESS_DEBUG 1 +namespace dev +{ +namespace eth +{ -const unsigned eth::c_protocolVersion = 32; -const unsigned eth::c_databaseVersion = 1; +const unsigned c_protocolVersion = 32; +const unsigned c_databaseVersion = 1; static const vector> g_units = { @@ -55,12 +60,12 @@ static const vector> g_units = {u256(1), "wei"} }; -vector> const& eth::units() +vector> const& units() { return g_units; } -std::string eth::formatBalance(u256 _b) +std::string formatBalance(u256 _b) { ostringstream ret; if (_b > g_units[0].first * 10000) @@ -79,7 +84,7 @@ std::string eth::formatBalance(u256 _b) return ret.str(); } -Address eth::toAddress(Secret _private) +Address toAddress(Secret _private) { secp256k1_start(); @@ -95,7 +100,7 @@ Address eth::toAddress(Secret _private) ok = secp256k1_ecdsa_pubkey_verify(pubkey, 65); if (!ok) return Address(); - auto ret = right160(eth::sha3(bytesConstRef(&(pubkey[1]), 64))); + auto ret = right160(dev::eth::sha3(bytesConstRef(&(pubkey[1]), 64))); #if ETH_ADDRESS_DEBUG cout << "---- ADDRESS -------------------------------" << endl; cout << "SEC: " << _private << endl; @@ -105,6 +110,8 @@ Address eth::toAddress(Secret _private) return ret; } +}} + KeyPair KeyPair::create() { secp256k1_start(); @@ -143,7 +150,7 @@ KeyPair::KeyPair(h256 _sec): m_secret = m_secret; memcpy(m_public.data(), &(pubkey[1]), 64); - m_address = right160(eth::sha3(bytesConstRef(&(pubkey[1]), 64))); + m_address = right160(dev::eth::sha3(bytesConstRef(&(pubkey[1]), 64))); #if ETH_ADDRESS_DEBUG cout << "---- ADDRESS -------------------------------" << endl; diff --git a/libethcore/CommonEth.h b/libethcore/CommonEth.h index d336165cb..49b8a0c4f 100644 --- a/libethcore/CommonEth.h +++ b/libethcore/CommonEth.h @@ -26,6 +26,8 @@ #include #include +namespace dev +{ namespace eth { @@ -117,3 +119,4 @@ private: }; } +} diff --git a/libethcore/Dagger.cpp b/libethcore/Dagger.cpp index 8be21bdbb..cc37fb98d 100644 --- a/libethcore/Dagger.cpp +++ b/libethcore/Dagger.cpp @@ -32,12 +32,14 @@ using namespace std; using namespace std::chrono; +namespace dev +{ namespace eth { #if FAKE_DAGGER -MineInfo Dagger::mine(h256& o_solution, h256 const& _root, u256 const& _difficulty, uint _msTimeout, bool _continue, bool _turbo) +MineInfo Dagger::mine(h256& o_solution, h256 const& _root, u256 const& _difficulty, unsigned _msTimeout, bool _continue, bool _turbo) { MineInfo ret; static std::mt19937_64 s_eng((time(0) + (unsigned)m_last)); @@ -91,7 +93,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 const& _continue) +bool Dagger::mine(u256& o_solution, h256 const& _root, u256 const& _difficulty, unsigned _msTimeout, bool const& _continue) { // restart search if root has changed if (m_root != _root) @@ -185,5 +187,5 @@ h256 Dagger::eval(h256 const& _root, u256 const& _nonce) #endif } - +} #endif diff --git a/libethcore/Dagger.h b/libethcore/Dagger.h index 61848f8c9..58f6e3f80 100644 --- a/libethcore/Dagger.h +++ b/libethcore/Dagger.h @@ -28,20 +28,17 @@ #define FAKE_DAGGER 1 -namespace eth +namespace dev { - -inline uint toLog2(u256 _d) +namespace eth { - return (uint)log2((double)_d); -} struct MineInfo { void combine(MineInfo const& _m) { requirement = std::max(requirement, _m.requirement); best = std::min(best, _m.best); hashes += _m.hashes; completed = completed || _m.completed; } double requirement = 0; double best = 1e99; - uint hashes = 0; + unsigned hashes = 0; bool completed = false; }; @@ -53,7 +50,7 @@ public: static h256 eval(h256 const& _root, h256 const& _nonce) { h256 b[2] = { _root, _nonce }; return sha3(bytesConstRef((byte const*)&b[0], 64)); } static bool verify(h256 const& _root, h256 const& _nonce, u256 const& _difficulty) { return (bigint)(u256)eval(_root, _nonce) <= (bigint(1) << 256) / _difficulty; } - MineInfo mine(h256& o_solution, h256 const& _root, u256 const& _difficulty, uint _msTimeout = 100, bool _continue = true, bool _turbo = false); + MineInfo mine(h256& o_solution, h256 const& _root, u256 const& _difficulty, unsigned _msTimeout = 100, bool _continue = true, bool _turbo = false); h256 m_last; }; @@ -71,7 +68,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 const& _continue = bool(true)); + bool mine(u256& o_solution, h256 const& _root, u256 const& _difficulty, unsigned _msTimeout = 100, bool const& _continue = bool(true)); private: @@ -84,5 +81,4 @@ private: #endif } - - +} diff --git a/libethcore/Exceptions.h b/libethcore/Exceptions.h index e11aff26a..175add708 100644 --- a/libethcore/Exceptions.h +++ b/libethcore/Exceptions.h @@ -2,42 +2,45 @@ #include +namespace dev +{ namespace eth { -class DatabaseAlreadyOpen: public Exception {}; +class DatabaseAlreadyOpen: public dev::Exception {}; -class NotEnoughCash: public Exception {}; +class NotEnoughCash: public dev::Exception {}; -class GasPriceTooLow: public Exception {}; -class BlockGasLimitReached: public Exception {}; -class NoSuchContract: public Exception {}; -class ContractAddressCollision: public Exception {}; -class FeeTooSmall: public Exception {}; -class TooMuchGasUsed: public Exception {}; -class ExtraDataTooBig: public Exception {}; -class InvalidSignature: public Exception {}; -class InvalidTransactionFormat: public Exception { public: InvalidTransactionFormat(int _f, bytesConstRef _d): m_f(_f), m_d(_d.toBytes()) {} int m_f; bytes m_d; virtual std::string description() const { return "Invalid transaction format: Bad field " + toString(m_f) + " (" + toHex(m_d) + ")"; } }; -class InvalidBlockFormat: public Exception { public: InvalidBlockFormat(int _f, bytesConstRef _d): m_f(_f), m_d(_d.toBytes()) {} int m_f; bytes m_d; virtual std::string description() const { return "Invalid block format: Bad field " + toString(m_f) + " (" + toHex(m_d) + ")"; } }; -class InvalidBlockHeaderFormat: public Exception { public: InvalidBlockHeaderFormat(int _f, bytesConstRef _d): m_f(_f), m_d(_d.toBytes()) {} int m_f; bytes m_d; virtual std::string description() const { return "Invalid block header format: Bad field " + toString(m_f) + " (" + toHex(m_d) + ")"; } }; -class InvalidUnclesHash: public Exception {}; -class InvalidUncle: public Exception {}; -class UncleTooOld: public Exception {}; -class UncleInChain: public Exception {}; -class DuplicateUncleNonce: public Exception {}; -class InvalidStateRoot: public Exception {}; -class InvalidTransactionsHash: public Exception { public: InvalidTransactionsHash(h256 _head, h256 _real): m_head(_head), m_real(_real) {} h256 m_head; h256 m_real; virtual std::string description() const { return "Invalid transactions hash: header says: " + toHex(m_head.ref()) + " block is:" + toHex(m_real.ref()); } }; -class InvalidTransaction: public Exception {}; -class InvalidDifficulty: public Exception {}; -class InvalidGasLimit: public Exception { public: InvalidGasLimit(u256 _provided = 0, u256 _valid = 0): provided(_provided), valid(_valid) {} u256 provided; u256 valid; virtual std::string description() const { return "Invalid gas limit (provided: " + toString(provided) + " valid:" + toString(valid) + ")"; } }; -class InvalidMinGasPrice: public Exception { public: InvalidMinGasPrice(u256 _provided = 0, u256 _limit = 0): provided(_provided), limit(_limit) {} u256 provided; u256 limit; virtual std::string description() const { return "Invalid minimum gas price (provided: " + toString(provided) + " limit:" + toString(limit) + ")"; } }; -class InvalidTransactionGasUsed: public Exception {}; -class InvalidTransactionStateRoot: public Exception {}; -class InvalidTimestamp: public Exception {}; -class InvalidNonce: public Exception { public: InvalidNonce(u256 _required = 0, u256 _candidate = 0): required(_required), candidate(_candidate) {} u256 required; u256 candidate; virtual std::string description() const { return "Invalid nonce (r: " + toString(required) + " c:" + toString(candidate) + ")"; } }; -class InvalidBlockNonce: public Exception { public: InvalidBlockNonce(h256 _h = h256(), h256 _n = h256(), u256 _d = 0): h(_h), n(_n), d(_d) {} h256 h; h256 n; u256 d; virtual std::string description() const { return "Invalid nonce (h: " + toString(h) + " n:" + toString(n) + " d:" + toString(d) + ")"; } }; -class InvalidParentHash: public Exception {}; -class InvalidNumber: public Exception {}; -class InvalidContractAddress: public Exception {}; +class GasPriceTooLow: public dev::Exception {}; +class BlockGasLimitReached: public dev::Exception {}; +class NoSuchContract: public dev::Exception {}; +class ContractAddressCollision: public dev::Exception {}; +class FeeTooSmall: public dev::Exception {}; +class TooMuchGasUsed: public dev::Exception {}; +class ExtraDataTooBig: public dev::Exception {}; +class InvalidSignature: public dev::Exception {}; +class InvalidTransactionFormat: public dev::Exception { public: InvalidTransactionFormat(int _f, bytesConstRef _d): m_f(_f), m_d(_d.toBytes()) {} int m_f; bytes m_d; virtual std::string description() const { return "Invalid transaction format: Bad field " + toString(m_f) + " (" + toHex(m_d) + ")"; } }; +class InvalidBlockFormat: public dev::Exception { public: InvalidBlockFormat(int _f, bytesConstRef _d): m_f(_f), m_d(_d.toBytes()) {} int m_f; bytes m_d; virtual std::string description() const { return "Invalid block format: Bad field " + toString(m_f) + " (" + toHex(m_d) + ")"; } }; +class InvalidBlockHeaderFormat: public dev::Exception { public: InvalidBlockHeaderFormat(int _f, bytesConstRef _d): m_f(_f), m_d(_d.toBytes()) {} int m_f; bytes m_d; virtual std::string description() const { return "Invalid block header format: Bad field " + toString(m_f) + " (" + toHex(m_d) + ")"; } }; +class InvalidUnclesHash: public dev::Exception {}; +class InvalidUncle: public dev::Exception {}; +class UncleTooOld: public dev::Exception {}; +class UncleInChain: public dev::Exception {}; +class DuplicateUncleNonce: public dev::Exception {}; +class InvalidStateRoot: public dev::Exception {}; +class InvalidTransactionsHash: public dev::Exception { public: InvalidTransactionsHash(h256 _head, h256 _real): m_head(_head), m_real(_real) {} h256 m_head; h256 m_real; virtual std::string description() const { return "Invalid transactions hash: header says: " + toHex(m_head.ref()) + " block is:" + toHex(m_real.ref()); } }; +class InvalidTransaction: public dev::Exception {}; +class InvalidDifficulty: public dev::Exception {}; +class InvalidGasLimit: public dev::Exception { public: InvalidGasLimit(u256 _provided = 0, u256 _valid = 0): provided(_provided), valid(_valid) {} u256 provided; u256 valid; virtual std::string description() const { return "Invalid gas limit (provided: " + toString(provided) + " valid:" + toString(valid) + ")"; } }; +class InvalidMinGasPrice: public dev::Exception { public: InvalidMinGasPrice(u256 _provided = 0, u256 _limit = 0): provided(_provided), limit(_limit) {} u256 provided; u256 limit; virtual std::string description() const { return "Invalid minimum gas price (provided: " + toString(provided) + " limit:" + toString(limit) + ")"; } }; +class InvalidTransactionGasUsed: public dev::Exception {}; +class InvalidTransactionStateRoot: public dev::Exception {}; +class InvalidTimestamp: public dev::Exception {}; +class InvalidNonce: public dev::Exception { public: InvalidNonce(u256 _required = 0, u256 _candidate = 0): required(_required), candidate(_candidate) {} u256 required; u256 candidate; virtual std::string description() const { return "Invalid nonce (r: " + toString(required) + " c:" + toString(candidate) + ")"; } }; +class InvalidBlockNonce: public dev::Exception { public: InvalidBlockNonce(h256 _h = h256(), h256 _n = h256(), u256 _d = 0): h(_h), n(_n), d(_d) {} h256 h; h256 n; u256 d; virtual std::string description() const { return "Invalid nonce (h: " + toString(h) + " n:" + toString(n) + " d:" + toString(d) + ")"; } }; +class InvalidParentHash: public dev::Exception {}; +class InvalidNumber: public dev::Exception {}; +class InvalidContractAddress: public dev::Exception {}; } +} diff --git a/libethcore/FileSystem.cpp b/libethcore/FileSystem.cpp index 6d7044753..953789d9b 100644 --- a/libethcore/FileSystem.cpp +++ b/libethcore/FileSystem.cpp @@ -30,9 +30,10 @@ #endif #include using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; -std::string eth::getDataDir() +std::string dev::eth::getDataDir() { #ifdef _WIN32 char path[1024] = ""; diff --git a/libethcore/FileSystem.h b/libethcore/FileSystem.h index a6ce93fb0..9c597c421 100644 --- a/libethcore/FileSystem.h +++ b/libethcore/FileSystem.h @@ -25,6 +25,8 @@ #include +namespace dev +{ namespace eth { @@ -32,3 +34,4 @@ namespace eth std::string getDataDir(); } +} diff --git a/libethcore/MemoryDB.cpp b/libethcore/MemoryDB.cpp index 8755266c3..6fe8091d6 100644 --- a/libethcore/MemoryDB.cpp +++ b/libethcore/MemoryDB.cpp @@ -22,8 +22,11 @@ #include #include "MemoryDB.h" using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; +namespace dev +{ namespace eth { @@ -113,3 +116,4 @@ set MemoryDB::keys() const } } +} diff --git a/libethcore/MemoryDB.h b/libethcore/MemoryDB.h index 33cd71fcd..76c7e6c6b 100644 --- a/libethcore/MemoryDB.h +++ b/libethcore/MemoryDB.h @@ -27,6 +27,8 @@ #include #include +namespace dev +{ namespace eth { @@ -54,7 +56,7 @@ public: protected: std::map m_over; - std::map m_refCount; + std::map m_refCount; mutable bool m_enforceRefs = false; }; @@ -83,3 +85,4 @@ inline std::ostream& operator<<(std::ostream& _out, MemoryDB const& _m) } } +} diff --git a/libethcore/OverlayDB.cpp b/libethcore/OverlayDB.cpp index a8941e9ef..b009841cd 100644 --- a/libethcore/OverlayDB.cpp +++ b/libethcore/OverlayDB.cpp @@ -22,8 +22,11 @@ #include #include "OverlayDB.h" using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; +namespace dev +{ namespace eth { @@ -97,3 +100,4 @@ void OverlayDB::kill(h256 _h) } } +} diff --git a/libethcore/OverlayDB.h b/libethcore/OverlayDB.h index e2a5aba1a..c13b20e6b 100644 --- a/libethcore/OverlayDB.h +++ b/libethcore/OverlayDB.h @@ -21,12 +21,19 @@ #pragma once +#pragma warning(push) +#pragma warning(disable: 4100 4267) +#include +#pragma warning(pop) + #include #include #include #include "MemoryDB.h" namespace ldb = leveldb; +namespace dev +{ namespace eth { @@ -56,3 +63,4 @@ private: }; } +} diff --git a/libethcore/SHA3.cpp b/libethcore/SHA3.cpp index 90e85f4ef..cdd1002e6 100644 --- a/libethcore/SHA3.cpp +++ b/libethcore/SHA3.cpp @@ -23,11 +23,17 @@ #include "CryptoHeaders.h" using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; -h256 eth::EmptySHA3 = sha3(bytesConstRef()); +namespace dev +{ +namespace eth +{ + +h256 EmptySHA3 = sha3(bytesConstRef()); -std::string eth::sha3(std::string const& _input, bool _hex) +std::string sha3(std::string const& _input, bool _hex) { if (!_hex) { @@ -44,7 +50,7 @@ std::string eth::sha3(std::string const& _input, bool _hex) return ret; } -void eth::sha3(bytesConstRef _input, bytesRef _output) +void sha3(bytesConstRef _input, bytesRef _output) { CryptoPP::SHA3_256 ctx; ctx.Update((byte*)_input.data(), _input.size()); @@ -52,17 +58,19 @@ void eth::sha3(bytesConstRef _input, bytesRef _output) ctx.Final(_output.data()); } -bytes eth::sha3Bytes(bytesConstRef _input) +bytes sha3Bytes(bytesConstRef _input) { bytes ret(32); sha3(_input, &ret); return ret; } -h256 eth::sha3(bytesConstRef _input) +h256 sha3(bytesConstRef _input) { h256 ret; sha3(_input, bytesRef(&ret[0], 32)); return ret; } +} +} diff --git a/libethcore/SHA3.h b/libethcore/SHA3.h index 5972c0e47..2a76ea56f 100644 --- a/libethcore/SHA3.h +++ b/libethcore/SHA3.h @@ -27,6 +27,8 @@ #include #include +namespace dev +{ namespace eth { @@ -59,3 +61,4 @@ inline h256 sha3(std::string const& _input) { return sha3(bytesConstRef(_input)) extern h256 EmptySHA3; } +} diff --git a/libethcore/TrieCommon.cpp b/libethcore/TrieCommon.cpp index fedf3b095..bad29059e 100644 --- a/libethcore/TrieCommon.cpp +++ b/libethcore/TrieCommon.cpp @@ -21,6 +21,8 @@ #include "TrieCommon.h" +namespace dev +{ namespace eth { @@ -42,8 +44,8 @@ namespace eth std::string hexPrefixEncode(bytes const& _hexVector, bool _leaf, int _begin, int _end) { - uint begin = _begin; - uint end = _end < 0 ? _hexVector.size() + 1 + _end : _end; + unsigned begin = _begin; + unsigned end = _end < 0 ? _hexVector.size() + 1 + _end : _end; bool odd = ((end - begin) % 2) != 0; std::string ret(1, ((_leaf ? 2 : 0) | (odd ? 1 : 0)) * 16); @@ -52,21 +54,21 @@ std::string hexPrefixEncode(bytes const& _hexVector, bool _leaf, int _begin, int ret[0] |= _hexVector[begin]; ++begin; } - for (uint i = begin; i < end; i += 2) + for (unsigned i = begin; i < end; i += 2) ret += _hexVector[i] * 16 + _hexVector[i + 1]; return ret; } -std::string hexPrefixEncode(bytesConstRef _data, bool _leaf, int _beginNibble, int _endNibble, uint _offset) +std::string hexPrefixEncode(bytesConstRef _data, bool _leaf, int _beginNibble, int _endNibble, unsigned _offset) { - uint begin = _beginNibble + _offset; - uint end = (_endNibble < 0 ? (_data.size() * 2 - _offset) + 1 + _endNibble : _endNibble) + _offset; + unsigned begin = _beginNibble + _offset; + unsigned end = (_endNibble < 0 ? (_data.size() * 2 - _offset) + 1 + _endNibble : _endNibble) + _offset; bool odd = (end - begin) & 1; std::string ret(1, ((_leaf ? 2 : 0) | (odd ? 1 : 0)) * 16); ret.reserve((end - begin) / 2 + 1); - uint d = odd ? 1 : 2; + unsigned d = odd ? 1 : 2; for (auto i = begin; i < end; ++i, ++d) { byte n = nibble(_data, i); @@ -78,19 +80,19 @@ std::string hexPrefixEncode(bytesConstRef _data, bool _leaf, int _beginNibble, i return ret; } -std::string hexPrefixEncode(bytesConstRef _d1, uint _o1, bytesConstRef _d2, uint _o2, bool _leaf) +std::string hexPrefixEncode(bytesConstRef _d1, unsigned _o1, bytesConstRef _d2, unsigned _o2, bool _leaf) { - uint begin1 = _o1; - uint end1 = _d1.size() * 2; - uint begin2 = _o2; - uint end2 = _d2.size() * 2; + unsigned begin1 = _o1; + unsigned end1 = _d1.size() * 2; + unsigned begin2 = _o2; + unsigned end2 = _d2.size() * 2; bool odd = (end1 - begin1 + end2 - begin2) & 1; std::string ret(1, ((_leaf ? 2 : 0) | (odd ? 1 : 0)) * 16); ret.reserve((end1 - begin1 + end2 - begin2) / 2 + 1); - uint d = odd ? 1 : 2; + unsigned d = odd ? 1 : 2; for (auto i = begin1; i < end1; ++i, ++d) { byte n = nibble(_d1, i); @@ -125,3 +127,4 @@ byte uniqueInUse(RLP const& _orig, byte except) } } +} diff --git a/libethcore/TrieCommon.h b/libethcore/TrieCommon.h index c748fd8d0..afe527e3b 100644 --- a/libethcore/TrieCommon.h +++ b/libethcore/TrieCommon.h @@ -24,40 +24,42 @@ #include #include +namespace dev +{ namespace eth { -inline byte nibble(bytesConstRef _data, uint _i) +inline byte nibble(bytesConstRef _data, unsigned _i) { return (_i & 1) ? (_data[_i / 2] & 15) : (_data[_i / 2] >> 4); } -inline uint sharedNibbles(bytesConstRef _a, uint _ab, uint _ae, bytesConstRef _b, uint _bb, uint _be) +inline unsigned sharedNibbles(bytesConstRef _a, unsigned _ab, unsigned _ae, bytesConstRef _b, unsigned _bb, unsigned _be) { - uint ret = 0; - for (uint ai = _ab, bi = _bb; ai < _ae && bi < _be && nibble(_a, ai) == nibble(_b, bi); ++ai, ++bi, ++ret) {} + unsigned ret = 0; + for (unsigned ai = _ab, bi = _bb; ai < _ae && bi < _be && nibble(_a, ai) == nibble(_b, bi); ++ai, ++bi, ++ret) {} return ret; } struct NibbleSlice { bytesConstRef data; - uint offset; + unsigned offset; - NibbleSlice(bytesConstRef _d = bytesConstRef(), uint _o = 0): data(_d), offset(_o) {} - byte operator[](uint _index) const { return nibble(data, offset + _index); } - uint size() const { return data.size() * 2 - offset; } - NibbleSlice mid(uint _index) const { return NibbleSlice(data, offset + _index); } + NibbleSlice(bytesConstRef _d = bytesConstRef(), unsigned _o = 0): data(_d), offset(_o) {} + byte operator[](unsigned _index) const { return nibble(data, offset + _index); } + unsigned size() const { return data.size() * 2 - offset; } + NibbleSlice mid(unsigned _index) const { return NibbleSlice(data, offset + _index); } bool contains(NibbleSlice _k) const { return shared(_k) == _k.size(); } - uint shared(NibbleSlice _k) const { return sharedNibbles(data, offset, offset + size(), _k.data, _k.offset, _k.offset + _k.size()); } + unsigned shared(NibbleSlice _k) const { return sharedNibbles(data, offset, offset + size(), _k.data, _k.offset, _k.offset + _k.size()); } bool operator==(NibbleSlice _k) const { return _k.size() == size() && shared(_k) == _k.size(); } bool operator!=(NibbleSlice _s) const { return !operator==(_s); } }; inline std::ostream& operator<<(std::ostream& _out, NibbleSlice const& _m) { - for (uint i = 0; i < _m.size(); ++i) + for (unsigned i = 0; i < _m.size(); ++i) _out << std::hex << (int)_m[i] << std::dec; return _out; } @@ -86,8 +88,8 @@ inline NibbleSlice keyOf(RLP const& _twoItem) byte uniqueInUse(RLP const& _orig, byte except); std::string hexPrefixEncode(bytes const& _hexVector, bool _leaf = false, int _begin = 0, int _end = -1); -std::string hexPrefixEncode(bytesConstRef _data, bool _leaf, int _beginNibble, int _endNibble, uint _offset); -std::string hexPrefixEncode(bytesConstRef _d1, uint _o1, bytesConstRef _d2, uint _o2, bool _leaf); +std::string hexPrefixEncode(bytesConstRef _data, bool _leaf, int _beginNibble, int _endNibble, unsigned _offset); +std::string hexPrefixEncode(bytesConstRef _d1, unsigned _o1, bytesConstRef _d2, unsigned _o2, bool _leaf); inline std::string hexPrefixEncode(NibbleSlice _s, bool _leaf, int _begin = 0, int _end = -1) { @@ -100,3 +102,4 @@ inline std::string hexPrefixEncode(NibbleSlice _s1, NibbleSlice _s2, bool _leaf) } } +} diff --git a/libethcore/TrieDB.cpp b/libethcore/TrieDB.cpp index c0562889f..c6c1262bc 100644 --- a/libethcore/TrieDB.cpp +++ b/libethcore/TrieDB.cpp @@ -22,14 +22,11 @@ #include #include "TrieDB.h" using namespace std; -using namespace eth; - -namespace eth -{ +using namespace dev; +using namespace dev::eth; #if !ETH_LANGUAGES -const h256 c_shaNull = sha3(rlp("")); +const h256 dev::eth::c_shaNull = sha3(rlp("")); #endif -} diff --git a/libethcore/TrieDB.h b/libethcore/TrieDB.h index c6114b95d..ff87cc4f8 100644 --- a/libethcore/TrieDB.h +++ b/libethcore/TrieDB.h @@ -21,6 +21,11 @@ #pragma once +#pragma warning(push) +#pragma warning(disable: 4100 4267) +#include +#pragma warning(pop) + #include #include #include @@ -31,6 +36,8 @@ #include "TrieCommon.h" namespace ldb = leveldb; +namespace dev +{ namespace eth { @@ -223,7 +230,7 @@ private: // in: [K1 & K2, V] (DEL) : nibbles(K1) == _s, 0 < _s <= nibbles(K1 & K2) // out: [K1, H] ; [K2, V] => H (INS) (being [K1, [K2, V]] if necessary) - bytes cleve(RLP const& _orig, uint _s); + bytes cleve(RLP const& _orig, unsigned _s); // in: [K1, H] (DEL) ; H <= [K2, V] (DEL) (being [K1, [K2, V]] (DEL) if necessary) // out: [K1 & K2, V] @@ -308,9 +315,12 @@ std::ostream& operator<<(std::ostream& _out, TrieDB const& _db) return _out; } +} } // Template implementations... +namespace dev +{ namespace eth { @@ -756,7 +766,7 @@ template bytes GenericTrieDB::place(RLP const& _orig, NibbleSlice return (RLPStream(2) << _orig[0] << _s).out(); auto s = RLPStream(17); - for (uint i = 0; i < 16; ++i) + for (unsigned i = 0; i < 16; ++i) s << _orig[i]; s << _s; return s.out(); @@ -778,7 +788,7 @@ template bytes GenericTrieDB::remove(RLP const& _orig) if (_orig.itemCount() == 2) return RLPNull; RLPStream r(17); - for (uint i = 0; i < 16; ++i) + for (unsigned i = 0; i < 16; ++i) r << _orig[i]; r << ""; return r.out(); @@ -793,7 +803,7 @@ template RLPStream& GenericTrieDB::streamNode(RLPStream& _s, byte return _s; } -template bytes GenericTrieDB::cleve(RLP const& _orig, uint _s) +template bytes GenericTrieDB::cleve(RLP const& _orig, unsigned _s) { #if ETH_PARANOIA tdebug << "cleve " << _orig << _s; @@ -874,14 +884,14 @@ template bytes GenericTrieDB::branch(RLP const& _orig) if (k.size() == 0) { assert(isLeaf(_orig)); - for (uint i = 0; i < 16; ++i) + for (unsigned i = 0; i < 16; ++i) r << ""; r << _orig[1]; } else { byte b = k[0]; - for (uint i = 0; i < 16; ++i) + for (unsigned i = 0; i < 16; ++i) if (i == b) if (isLeaf(_orig) || k.size() > 1) { @@ -899,3 +909,5 @@ template bytes GenericTrieDB::branch(RLP const& _orig) } } +} + diff --git a/libethential/Common.cpp b/libethential/Common.cpp index 2f6feb9e5..665a663f3 100644 --- a/libethential/Common.cpp +++ b/libethential/Common.cpp @@ -22,11 +22,12 @@ #include "Common.h" using namespace std; -using namespace eth; +using namespace dev; -namespace eth +namespace dev { -char const* EthVersion = "0.6.8"; +char const* Version = "0.6.8"; } + diff --git a/libethential/Common.h b/libethential/Common.h index c06ede0c0..132429010 100644 --- a/libethential/Common.h +++ b/libethential/Common.h @@ -23,7 +23,7 @@ #pragma once -// way to many uint to size_t warnings in 32 bit build +// way to many unsigned to size_t warnings in 32 bit build #ifdef _M_IX86 #pragma warning(disable:4244) #endif @@ -43,13 +43,13 @@ using byte = uint8_t; // Quote a given token stream to turn it into a string. -#define ETH_QUOTED_HELPER(s) #s -#define ETH_QUOTED(s) ETH_QUOTED_HELPER(s) +#define DEV_QUOTED_HELPER(s) #s +#define DEV_QUOTED(s) DEV_QUOTED_HELPER(s) -namespace eth +namespace dev { -extern char const* EthVersion; +extern char const* Version; // Binary data types. using bytes = std::vector; @@ -62,8 +62,6 @@ using u256 = boost::multiprecision::number>; using u160 = boost::multiprecision::number>; using s160 = boost::multiprecision::number>; -using uint = uint64_t; -using sint = int64_t; using u256s = std::vector; using u160s = std::vector; using u256Set = std::set; @@ -98,4 +96,11 @@ inline u256 s2u(s256 _u) return (u256)(c_end + _u); } +inline unsigned int toLog2(u256 _x) +{ + unsigned ret; + for (ret = 0; _x >>= 1; ++ret) {} + return ret; +} + } diff --git a/libethential/CommonData.cpp b/libethential/CommonData.cpp index 3d4d9bdbe..96269bbc1 100644 --- a/libethential/CommonData.cpp +++ b/libethential/CommonData.cpp @@ -24,9 +24,9 @@ #include #include "Exceptions.h" using namespace std; -using namespace eth; +using namespace dev; -std::string eth::escaped(std::string const& _s, bool _all) +std::string dev::escaped(std::string const& _s, bool _all) { std::string ret; ret.reserve(_s.size()); @@ -48,7 +48,7 @@ std::string eth::escaped(std::string const& _s, bool _all) return ret; } -std::string eth::randomWord() +std::string dev::randomWord() { static std::mt19937_64 s_eng(0); std::string ret(std::uniform_int_distribution(1, 5)(s_eng), ' '); @@ -59,7 +59,7 @@ std::string eth::randomWord() return ret; } -int eth::fromHex(char _i) +int dev::fromHex(char _i) { if (_i >= '0' && _i <= '9') return _i - '0'; @@ -70,9 +70,9 @@ int eth::fromHex(char _i) throw BadHexCharacter(); } -bytes eth::fromHex(std::string const& _s) +bytes dev::fromHex(std::string const& _s) { - uint s = (_s[0] == '0' && _s[1] == 'x') ? 2 : 0; + unsigned s = (_s[0] == '0' && _s[1] == 'x') ? 2 : 0; std::vector ret; ret.reserve((_s.size() - s + 1) / 2); @@ -82,7 +82,7 @@ bytes eth::fromHex(std::string const& _s) ret.push_back(fromHex(_s[s++])); } catch (...){ ret.push_back(0); } - for (uint i = s; i < _s.size(); i += 2) + for (unsigned i = s; i < _s.size(); i += 2) try { ret.push_back((byte)(fromHex(_s[i]) * 16 + fromHex(_s[i + 1]))); @@ -91,7 +91,7 @@ bytes eth::fromHex(std::string const& _s) return ret; } -bytes eth::asNibbles(std::string const& _s) +bytes dev::asNibbles(std::string const& _s) { std::vector ret; ret.reserve(_s.size() * 2); diff --git a/libethential/CommonData.h b/libethential/CommonData.h index 6840194fc..1181e07e8 100644 --- a/libethential/CommonData.h +++ b/libethential/CommonData.h @@ -30,7 +30,7 @@ #include #include "Common.h" -namespace eth +namespace dev { // String conversion functions, mainly to/from hex/nibble/byte representations. @@ -79,7 +79,7 @@ bytes asNibbles(std::string const& _s); /// The size of the collection object will be unchanged. If it is too small, it will not represent the /// value properly, if too big then the additional elements will be zeroed out. /// @a _Out will typically be either std::string or bytes. -/// @a _T will typically by uint, u160, u256 or bigint. +/// @a _T will typically by unsigned, u160, u256 or bigint. template inline void toBigEndian(_T _val, _Out& o_out) { @@ -89,7 +89,7 @@ inline void toBigEndian(_T _val, _Out& o_out) /// Converts a big-endian byte-stream represented on a templated collection to a templated integer value. /// @a _In will typically be either std::string or bytes. -/// @a _T will typically by uint, u160, u256 or bigint. +/// @a _T will typically by unsigned, u160, u256 or bigint. template inline _T fromBigEndian(_In const& _bytes) { @@ -140,10 +140,10 @@ std::string escaped(std::string const& _s, bool _all = true); /// @returns the number of elements both @a _t and @a _u share, in order, at the beginning. /// @example commonPrefix("Hello world!", "Hello, world!") == 5 template -uint commonPrefix(_T const& _t, _U const& _u) +unsigned commonPrefix(_T const& _t, _U const& _u) { - uint s = std::min(_t.size(), _u.size()); - for (uint i = 0;; ++i) + unsigned s = std::min(_t.size(), _u.size()); + for (unsigned i = 0;; ++i) if (i == s || _t[i] != _u[i]) return i; return s; @@ -157,9 +157,9 @@ std::string randomWord(); /// Determine bytes required to encode the given integer value. @returns 0 if @a _i is zero. template -inline uint bytesRequired(_T _i) +inline unsigned bytesRequired(_T _i) { - uint i = 0; + unsigned i = 0; for (; _i != 0; ++i, _i >>= 8) {} return i; } @@ -167,7 +167,7 @@ inline uint bytesRequired(_T _i) /// Trims a given number of elements from the front of a collection. /// Only works for POD element types. template -void trimFront(_T& _t, uint _elements) +void trimFront(_T& _t, unsigned _elements) { static_assert(std::is_pod::value, ""); memmove(_t.data(), _t.data() + _elements, (_t.size() - _elements) * sizeof(_t[0])); diff --git a/libethential/CommonIO.cpp b/libethential/CommonIO.cpp index c2e2306d0..5797e44dc 100644 --- a/libethential/CommonIO.cpp +++ b/libethential/CommonIO.cpp @@ -24,9 +24,9 @@ #include #include "Exceptions.h" using namespace std; -using namespace eth; +using namespace dev; -string eth::memDump(bytes const& _b, unsigned _w, bool _html) +string dev::memDump(bytes const& _b, unsigned _w, bool _html) { stringstream ret; if (_html) @@ -57,7 +57,7 @@ string eth::memDump(bytes const& _b, unsigned _w, bool _html) return ret.str(); } -bytes eth::contents(std::string const& _file) +bytes dev::contents(std::string const& _file) { std::ifstream is(_file, std::ifstream::binary); if (!is) @@ -72,7 +72,7 @@ bytes eth::contents(std::string const& _file) return ret; } -void eth::writeFile(std::string const& _file, bytes const& _data) +void dev::writeFile(std::string const& _file, bytes const& _data) { ofstream(_file, ios::trunc).write((char const*)_data.data(), _data.size()); } diff --git a/libethential/CommonIO.h b/libethential/CommonIO.h index 824f2c270..068c5c777 100644 --- a/libethential/CommonIO.h +++ b/libethential/CommonIO.h @@ -37,7 +37,7 @@ #include #include "Common.h" -namespace eth +namespace dev { /// Retrieve and returns the contents of the given file. If the file doesn't exist or isn't readable, returns an empty bytes. diff --git a/libethential/Exceptions.h b/libethential/Exceptions.h index 18ddfe5e8..15ef2019f 100644 --- a/libethential/Exceptions.h +++ b/libethential/Exceptions.h @@ -26,7 +26,7 @@ #include "CommonData.h" #include "FixedHash.h" -namespace eth +namespace dev { class Exception: public std::exception diff --git a/libethential/FixedHash.cpp b/libethential/FixedHash.cpp index 885d593e5..0369ea854 100644 --- a/libethential/FixedHash.cpp +++ b/libethential/FixedHash.cpp @@ -23,6 +23,6 @@ #include "FixedHash.h" using namespace std; -using namespace eth; +using namespace dev; -std::mt19937_64 eth::s_fixedHashEngine(time(0)); +std::mt19937_64 dev::s_fixedHashEngine(time(0)); diff --git a/libethential/FixedHash.h b/libethential/FixedHash.h index 828e3aef6..f979f0b68 100644 --- a/libethential/FixedHash.h +++ b/libethential/FixedHash.h @@ -28,7 +28,7 @@ #include #include "CommonData.h" -namespace eth +namespace dev { extern std::mt19937_64 s_fixedHashEngine; @@ -65,13 +65,13 @@ public: FixedHash(Arith const& _arith) { toBigEndian(_arith, m_data); } /// Explicitly construct, copying from a byte array. - explicit FixedHash(bytes const& _b) { if (_b.size() == N) memcpy(m_data.data(), _b.data(), std::min(_b.size(), N)); } + explicit FixedHash(bytes const& _b) { if (_b.size() == N) memcpy(m_data.data(), _b.data(), std::min(_b.size(), N)); } /// Explicitly construct, copying from a bytes in memory with given pointer. explicit FixedHash(byte const* _bs, ConstructFromPointerType) { memcpy(m_data.data(), _bs, N); } /// Explicitly construct, copying from a string. - explicit FixedHash(std::string const& _s, ConstructFromStringType _t = FromHex): FixedHash(_t == FromHex ? fromHex(_s) : eth::asBytes(_s)) {} + explicit FixedHash(std::string const& _s, ConstructFromStringType _t = FromHex): FixedHash(_t == FromHex ? fromHex(_s) : dev::asBytes(_s)) {} /// Convert to arithmetic type. operator Arith() const { return fromBigEndian(m_data); } @@ -221,6 +221,6 @@ inline h160 left160(h256 const& _t) namespace std { - /// Forward std::hash to eth::h256::hash. - template<> struct hash: eth::h256::hash {}; + /// Forward std::hash to dev::h256::hash. + template<> struct hash: dev::h256::hash {}; } diff --git a/libethential/Guards.cpp b/libethential/Guards.cpp index b2e12f98e..69ae6c03a 100644 --- a/libethential/Guards.cpp +++ b/libethential/Guards.cpp @@ -21,9 +21,9 @@ #include "Guards.h" using namespace std; -using namespace eth; +using namespace dev; -namespace eth +namespace dev { } diff --git a/libethential/Guards.h b/libethential/Guards.h index b509b45ed..f5c64b041 100644 --- a/libethential/Guards.h +++ b/libethential/Guards.h @@ -24,7 +24,7 @@ #include #include -namespace eth +namespace dev { using Mutex = std::mutex; diff --git a/libethential/Log.cpp b/libethential/Log.cpp index 952069830..d3e00d683 100644 --- a/libethential/Log.cpp +++ b/libethential/Log.cpp @@ -24,20 +24,20 @@ #include #include using namespace std; -using namespace eth; +using namespace dev; // Logging -int eth::g_logVerbosity = 5; -map eth::g_logOverride; +int dev::g_logVerbosity = 5; +map dev::g_logOverride; -ThreadLocalLogName eth::t_logThreadName("main"); +ThreadLocalLogName dev::t_logThreadName("main"); // foward declare without all of Windows.h #ifdef _WIN32 extern "C" __declspec(dllimport) void __stdcall OutputDebugStringA(const char* lpOutputString); #endif -void eth::simpleDebugOut(std::string const& _s, char const*) +void dev::simpleDebugOut(std::string const& _s, char const*) { cout << _s << endl << flush; @@ -50,5 +50,5 @@ void eth::simpleDebugOut(std::string const& _s, char const*) #endif } -std::function eth::g_logPost = simpleDebugOut; +std::function dev::g_logPost = simpleDebugOut; diff --git a/libethential/Log.h b/libethential/Log.h index 77a84333c..704660276 100644 --- a/libethential/Log.h +++ b/libethential/Log.h @@ -28,7 +28,7 @@ #include #include "vector_ref.h" -namespace eth +namespace dev { /// The null output stream. Used when logging is disabled. @@ -107,19 +107,19 @@ private: // Simple cout-like stream objects for accessing common log channels. // Dirties the global namespace, but oh so convenient... -#define cnote eth::LogOutputStream() -#define cwarn eth::LogOutputStream() +#define cnote dev::LogOutputStream() +#define cwarn dev::LogOutputStream() // Null stream-like objects. -#define ndebug if (true) {} else eth::NullOutputStream() -#define nlog(X) if (true) {} else eth::NullOutputStream() -#define nslog(X) if (true) {} else eth::NullOutputStream() +#define ndebug if (true) {} else dev::NullOutputStream() +#define nlog(X) if (true) {} else dev::NullOutputStream() +#define nslog(X) if (true) {} else dev::NullOutputStream() // Kill debugging log channel when we're in release mode. #if NDEBUG #define cdebug ndebug #else -#define cdebug eth::LogOutputStream() +#define cdebug dev::LogOutputStream() #endif // Kill all logs when when NLOG is defined. @@ -127,8 +127,8 @@ private: #define clog(X) nlog(X) #define cslog(X) nslog(X) #else -#define clog(X) eth::LogOutputStream() -#define cslog(X) eth::LogOutputStream() +#define clog(X) dev::LogOutputStream() +#define cslog(X) dev::LogOutputStream() #endif } diff --git a/libethential/RLP.cpp b/libethential/RLP.cpp index e8647e8de..68ae02065 100644 --- a/libethential/RLP.cpp +++ b/libethential/RLP.cpp @@ -21,10 +21,10 @@ #include "RLP.h" using namespace std; -using namespace eth; +using namespace dev; -bytes eth::RLPNull = rlp(""); -bytes eth::RLPEmptyList = rlpList(); +bytes dev::RLPNull = rlp(""); +bytes dev::RLPEmptyList = rlpList(); RLP::iterator& RLP::iterator::operator++() { @@ -32,7 +32,7 @@ RLP::iterator& RLP::iterator::operator++() { m_lastItem.retarget(m_lastItem.next().data(), m_remaining); m_lastItem = m_lastItem.cropped(0, RLP(m_lastItem).actualSize()); - m_remaining -= std::min(m_remaining, m_lastItem.size()); + m_remaining -= std::min(m_remaining, m_lastItem.size()); } else m_lastItem.retarget(m_lastItem.next().data(), 0); @@ -54,7 +54,7 @@ RLP::iterator::iterator(RLP const& _parent, bool _begin) } } -RLP RLP::operator[](uint _i) const +RLP RLP::operator[](unsigned _i) const { if (_i < m_lastIndex) { @@ -81,7 +81,7 @@ RLPs RLP::toList() const return ret; } -eth::uint RLP::actualSize() const +unsigned RLP::actualSize() const { if (isNull()) return 0; @@ -118,11 +118,11 @@ bool RLP::isInt() const return false; } -eth::uint RLP::length() const +unsigned RLP::length() const { if (isNull()) return 0; - uint ret = 0; + unsigned ret = 0; byte n = m_data[0]; if (n < c_rlpDataImmLenStart) return 1; @@ -147,12 +147,12 @@ eth::uint RLP::length() const return ret; } -eth::uint RLP::items() const +unsigned RLP::items() const { if (isList()) { bytesConstRef d = payload().cropped(0, length()); - eth::uint i = 0; + unsigned i = 0; for (; d.size(); ++i) d = d.cropped(RLP(d).actualSize()); return i; @@ -160,16 +160,16 @@ eth::uint RLP::items() const return 0; } -RLPStream& RLPStream::appendRaw(bytesConstRef _s, uint _itemCount) +RLPStream& RLPStream::appendRaw(bytesConstRef _s, unsigned _itemCount) { - uint os = m_out.size(); + unsigned os = m_out.size(); m_out.resize(os + _s.size()); memcpy(m_out.data() + os, _s.data(), _s.size()); noteAppended(_itemCount); return *this; } -void RLPStream::noteAppended(uint _itemCount) +void RLPStream::noteAppended(unsigned _itemCount) { if (!_itemCount) return; @@ -184,9 +184,9 @@ void RLPStream::noteAppended(uint _itemCount) { auto p = m_listStack.back().second; m_listStack.pop_back(); - uint s = m_out.size() - p; // list size + unsigned s = m_out.size() - p; // list size auto brs = bytesRequired(s); - uint encodeSize = s < c_rlpListImmLenCount ? 1 : (1 + brs); + unsigned encodeSize = s < c_rlpListImmLenCount ? 1 : (1 + brs); // cdebug << "s: " << s << ", p: " << p << ", m_out.size(): " << m_out.size() << ", encodeSize: " << encodeSize << " (br: " << brs << ")"; auto os = m_out.size(); m_out.resize(os + encodeSize); @@ -205,7 +205,7 @@ void RLPStream::noteAppended(uint _itemCount) } } -RLPStream& RLPStream::appendList(uint _items) +RLPStream& RLPStream::appendList(unsigned _items) { // cdebug << "appendList(" << _items << ")"; if (_items) @@ -227,7 +227,7 @@ RLPStream& RLPStream::appendList(bytesConstRef _rlp) RLPStream& RLPStream::append(bytesConstRef _s, bool _compact) { - uint s = _s.size(); + unsigned s = _s.size(); byte const* d = _s.data(); if (_compact) for (unsigned i = 0; i < _s.size() && !*d; ++i, --s, ++d) {} @@ -254,7 +254,7 @@ RLPStream& RLPStream::append(bigint _i) m_out.push_back((byte)_i); else { - uint br = bytesRequired(_i); + unsigned br = bytesRequired(_i); if (br < c_rlpDataImmLenCount) m_out.push_back((byte)(br + c_rlpDataImmLenStart)); else @@ -269,21 +269,21 @@ RLPStream& RLPStream::append(bigint _i) return *this; } -void RLPStream::pushCount(uint _count, byte _base) +void RLPStream::pushCount(unsigned _count, byte _base) { auto br = bytesRequired(_count); m_out.push_back((byte)(br + _base)); // max 8 bytes. pushInt(_count, br); } -std::ostream& eth::operator<<(std::ostream& _out, eth::RLP const& _d) +std::ostream& dev::operator<<(std::ostream& _out, RLP const& _d) { if (_d.isNull()) _out << "null"; else if (_d.isInt()) _out << std::showbase << std::hex << std::nouppercase << _d.toInt(RLP::LaisezFaire) << dec; else if (_d.isData()) - _out << eth::escaped(_d.toString(), false); + _out << escaped(_d.toString(), false); else if (_d.isList()) { _out << "["; diff --git a/libethential/RLP.h b/libethential/RLP.h index 6d388763f..0398467e7 100644 --- a/libethential/RLP.h +++ b/libethential/RLP.h @@ -33,16 +33,16 @@ #include #include -namespace eth +namespace dev { class RLP; typedef std::vector RLPs; -template struct intTraits { static const uint maxSize = sizeof(_T); }; -template <> struct intTraits { static const uint maxSize = 20; }; -template <> struct intTraits { static const uint maxSize = 32; }; -template <> struct intTraits { static const uint maxSize = ~(uint)0; }; +template struct intTraits { static const unsigned maxSize = sizeof(_T); }; +template <> struct intTraits { static const unsigned maxSize = 20; }; +template <> struct intTraits { static const unsigned maxSize = 32; }; +template <> struct intTraits { static const unsigned maxSize = ~(unsigned)0; }; static const byte c_rlpMaxLengthBytes = 8; static const byte c_rlpDataImmLenStart = 0x80; @@ -72,7 +72,7 @@ public: explicit RLP(bytes const& _d): m_data(&_d) {} /// Construct a node to read RLP data in the bytes given. - RLP(byte const* _b, uint _s): m_data(bytesConstRef(_b, _s)) {} + RLP(byte const* _b, unsigned _s): m_data(bytesConstRef(_b, _s)) {} /// Construct a node to read RLP data in the string. explicit RLP(std::string const& _s): m_data(bytesConstRef((byte const*)_s.data(), _s.size())) {} @@ -99,12 +99,12 @@ public: bool isInt() const; /// @returns the number of items in the list, or zero if it isn't a list. - uint itemCount() const { return isList() ? items() : 0; } - uint itemCountStrict() const { if (!isList()) throw BadCast(); return items(); } + unsigned itemCount() const { return isList() ? items() : 0; } + unsigned itemCountStrict() const { if (!isList()) throw BadCast(); return items(); } /// @returns the number of bytes in the data, or zero if it isn't data. - uint size() const { return isData() ? length() : 0; } - uint sizeStrict() const { if (!isData()) throw BadCast(); return length(); } + unsigned size() const { return isData() ? length() : 0; } + unsigned sizeStrict() const { if (!isData()) throw BadCast(); return length(); } /// Equality operators; does best-effort conversion and checks for equality. bool operator==(char const* _s) const { return isData() && toString() == _s; } @@ -113,8 +113,8 @@ public: bool operator!=(std::string const& _s) const { return isData() && toString() != _s; } template bool operator==(FixedHash<_N> const& _h) const { return isData() && toHash<_N>() == _h; } template bool operator!=(FixedHash<_N> const& _s) const { return isData() && toHash<_N>() != _s; } - bool operator==(uint const& _i) const { return isInt() && toInt() == _i; } - bool operator!=(uint const& _i) const { return isInt() && toInt() != _i; } + bool operator==(unsigned const& _i) const { return isInt() && toInt() == _i; } + bool operator!=(unsigned const& _i) const { return isInt() && toInt() != _i; } bool operator==(u256 const& _i) const { return isInt() && toInt() == _i; } bool operator!=(u256 const& _i) const { return isInt() && toInt() != _i; } bool operator==(bigint const& _i) const { return isInt() && toInt() == _i; } @@ -123,7 +123,7 @@ public: /// Subscript operator. /// @returns the list item @a _i if isList() and @a _i < listItems(), or RLP() otherwise. /// @note if used to access items in ascending order, this is efficient. - RLP operator[](uint _i) const; + RLP operator[](unsigned _i) const; typedef RLP element_type; @@ -146,7 +146,7 @@ public: iterator() {} iterator(RLP const& _parent, bool _begin); - uint m_remaining = 0; + unsigned m_remaining = 0; bytesConstRef m_lastItem; }; @@ -160,7 +160,7 @@ public: explicit operator std::string() const { return toString(); } explicit operator RLPs() const { return toList(); } explicit operator byte() const { return toInt(); } - explicit operator uint() const { return toInt(); } + explicit operator unsigned() const { return toInt(); } explicit operator u256() const { return toInt(); } explicit operator bigint() const { return toInt(); } template explicit operator FixedHash<_N>() const { return toHash>(); } @@ -181,7 +181,7 @@ public: template std::vector toVector() const { std::vector ret; if (isList()) { ret.reserve(itemCount()); for (auto const& i: *this) ret.push_back((T)i); } return ret; } template std::set toSet() const { std::set ret; if (isList()) { for (auto const& i: *this) ret.insert((T)i); } return ret; } template std::pair toPair() const { std::pair ret; if (isList()) { ret.first = (T)((*this)[0]); ret.second = (U)((*this)[1]); } return ret; } - template std::array toArray() const { if (itemCount() != N || !isList()) throw BadCast(); std::array ret; for (uint i = 0; i < N; ++i) ret[i] = (T)operator[](i); return ret; } + template std::array toArray() const { if (itemCount() != N || !isList()) throw BadCast(); std::array ret; for (unsigned i = 0; i < N; ++i) ret[i] = (T)operator[](i); return ret; } /// Int conversion flags enum @@ -194,7 +194,7 @@ public: }; /// Converts to int of type given; if isString(), decodes as big-endian bytestream. @returns 0 if not an int or string. - template _T toInt(int _flags = Strict) const + template _T toInt(int _flags = Strict) const { if ((!isInt() && !(_flags & AllowNonCanon)) || isList() || isNull()) if (_flags & ThrowOnFail) @@ -237,27 +237,27 @@ public: /// @returns the theoretical size of this item. /// @note Under normal circumstances, is equivalent to m_data.size() - use that unless you know it won't work. - uint actualSize() const; + unsigned actualSize() const; private: /// Single-byte data payload. bool isSingleByte() const { return !isNull() && m_data[0] < c_rlpDataImmLenStart; } /// @returns the bytes used to encode the length of the data. Valid for all types. - uint lengthSize() const { if (isData() && m_data[0] > c_rlpDataIndLenZero) return m_data[0] - c_rlpDataIndLenZero; if (isList() && m_data[0] > c_rlpListIndLenZero) return m_data[0] - c_rlpListIndLenZero; return 0; } + unsigned lengthSize() const { if (isData() && m_data[0] > c_rlpDataIndLenZero) return m_data[0] - c_rlpDataIndLenZero; if (isList() && m_data[0] > c_rlpListIndLenZero) return m_data[0] - c_rlpListIndLenZero; return 0; } /// @returns the size in bytes of the payload, as given by the RLP as opposed to as inferred from m_data. - uint length() const; + unsigned length() const; /// @returns the number of data items. - uint items() const; + unsigned items() const; /// Our byte data. bytesConstRef m_data; /// The list-indexing cache. - mutable uint m_lastIndex = (uint)-1; - mutable uint m_lastEnd = 0; + mutable unsigned m_lastIndex = (unsigned)-1; + mutable unsigned m_lastEnd = 0; mutable bytesConstRef m_lastItem; }; @@ -271,12 +271,12 @@ public: RLPStream() {} /// Initializes the RLPStream as a list of @a _listItems items. - explicit RLPStream(uint _listItems) { appendList(_listItems); } + explicit RLPStream(unsigned _listItems) { appendList(_listItems); } ~RLPStream() {} /// Append given datum to the byte stream. - RLPStream& append(uint _s) { return append(bigint(_s)); } + RLPStream& append(unsigned _s) { return append(bigint(_s)); } RLPStream& append(u160 _s) { return append(bigint(_s)); } RLPStream& append(u256 _s) { return append(bigint(_s)); } RLPStream& append(bigint _s); @@ -287,7 +287,7 @@ public: template RLPStream& append(FixedHash _s, bool _compact = false, bool _allOrNothing = false) { return _allOrNothing && !_s ? append(bytesConstRef()) : append(_s.ref(), _compact); } /// Appends an arbitrary RLP fragment - this *must* be a single item. - RLPStream& append(RLP const& _rlp, uint _itemCount = 1) { return appendRaw(_rlp.data(), _itemCount); } + RLPStream& append(RLP const& _rlp, unsigned _itemCount = 1) { return appendRaw(_rlp.data(), _itemCount); } /// Appends a sequence of data to the stream as a list. template RLPStream& append(std::vector<_T> const& _s) { return appendVector(_s); } @@ -297,14 +297,14 @@ public: template RLPStream& append(std::pair const& _s) { appendList(2); append(_s.first); append(_s.second); return *this; } /// Appends a list. - RLPStream& appendList(uint _items); + RLPStream& appendList(unsigned _items); RLPStream& appendList(bytesConstRef _rlp); RLPStream& appendList(bytes const& _rlp) { return appendList(&_rlp); } RLPStream& appendList(RLPStream const& _s) { return appendList(&_s.out()); } /// Appends raw (pre-serialised) RLP data. Use with caution. - RLPStream& appendRaw(bytesConstRef _rlp, uint _itemCount = 1); - RLPStream& appendRaw(bytes const& _rlp, uint _itemCount = 1) { return appendRaw(&_rlp, _itemCount); } + RLPStream& appendRaw(bytesConstRef _rlp, unsigned _itemCount = 1); + RLPStream& appendRaw(bytes const& _rlp, unsigned _itemCount = 1) { return appendRaw(&_rlp, _itemCount); } /// Shift operators for appending data items. template RLPStream& operator<<(T _data) { return append(_data); } @@ -319,14 +319,14 @@ public: void swapOut(bytes& _dest) { assert(m_listStack.empty()); swap(m_out, _dest); } private: - void noteAppended(uint _itemCount = 1); + void noteAppended(unsigned _itemCount = 1); /// Push the node-type byte (using @a _base) along with the item count @a _count. /// @arg _count is number of characters for strings, data-bytes for ints, or items for lists. - void pushCount(uint _count, byte _offset); + void pushCount(unsigned _count, byte _offset); /// Push an integer as a raw big-endian byte-stream. - template void pushInt(_T _i, uint _br) + template void pushInt(_T _i, unsigned _br) { m_out.resize(m_out.size() + _br); byte* b = &m_out.back(); @@ -337,7 +337,7 @@ private: /// Our output byte stream. bytes m_out; - std::vector> m_listStack; + std::vector> m_listStack; }; template void rlpListAux(RLPStream& _out, _T _t) { _out << _t; } @@ -362,6 +362,6 @@ extern bytes RLPNull; extern bytes RLPEmptyList; /// Human readable version of RLP. -std::ostream& operator<<(std::ostream& _out, eth::RLP const& _d); +std::ostream& operator<<(std::ostream& _out, dev::RLP const& _d); } diff --git a/libethential/vector_ref.h b/libethential/vector_ref.h index 4bde3f582..be2fea13c 100644 --- a/libethential/vector_ref.h +++ b/libethential/vector_ref.h @@ -5,12 +5,7 @@ #include #include -#pragma warning(push) -#pragma warning(disable: 4100 4267) -#include -#pragma warning(pop) - -namespace eth +namespace dev { template @@ -26,8 +21,9 @@ public: vector_ref(std::string* _data): m_data((_T*)_data->data()), m_count(_data->size() / sizeof(_T)) {} vector_ref(typename std::conditional::value, std::vector::type> const*, std::vector<_T>*>::type _data): m_data(_data->data()), m_count(_data->size()) {} vector_ref(typename std::conditional::value, std::string const&, std::string&>::type _data): m_data((_T*)_data.data()), m_count(_data.size() / sizeof(_T)) {} +#ifdef STORAGE_LEVELDB_INCLUDE_DB_H_ vector_ref(leveldb::Slice const& _s): m_data(_s.data()), m_count(_s.size() / sizeof(_T)) {} - +#endif explicit operator bool() const { return m_data && m_count; } bool contentsEqual(std::vector const& _c) const { return _c.size() == m_count && !memcmp(_c.data(), m_data, m_count); } @@ -56,7 +52,9 @@ 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==(_cmp); } +#ifdef STORAGE_LEVELDB_INCLUDE_DB_H_ operator leveldb::Slice() const { return leveldb::Slice((char const*)m_data, m_count * sizeof(_T)); } +#endif void reset() { m_data = nullptr; m_count = 0; } diff --git a/libethereum/AccountDiff.cpp b/libethereum/AccountDiff.cpp index 5b469508f..08d6d8859 100644 --- a/libethereum/AccountDiff.cpp +++ b/libethereum/AccountDiff.cpp @@ -23,7 +23,8 @@ #include using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; AccountChange AccountDiff::changeType() const { @@ -39,7 +40,9 @@ char const* AccountDiff::lead() const return exist ? exist.from() ? "XXX" : "+++" : (bn && sc) ? "***" : bn ? " * " : sc ? "* *" : " "; } -std::ostream& eth::operator<<(std::ostream& _out, AccountDiff const& _s) +namespace dev { + +std::ostream& operator<<(std::ostream& _out, dev::eth::AccountDiff const& _s) { if (!_s.exist.to()) return _out; @@ -68,10 +71,14 @@ std::ostream& eth::operator<<(std::ostream& _out, AccountDiff const& _s) return _out; } -std::ostream& eth::operator<<(std::ostream& _out, StateDiff const& _s) +std::ostream& operator<<(std::ostream& _out, dev::eth::StateDiff const& _s) { _out << _s.accounts.size() << " accounts changed:" << endl; + dev::eth::AccountDiff d; + _out << d; for (auto const& i: _s.accounts) _out << i.second.lead() << " " << i.first << ": " << i.second << endl; return _out; } + +} diff --git a/libethereum/AccountDiff.h b/libethereum/AccountDiff.h index a4a074e04..4d8f63f0a 100644 --- a/libethereum/AccountDiff.h +++ b/libethereum/AccountDiff.h @@ -24,6 +24,8 @@ #include #include +namespace dev +{ namespace eth { @@ -65,9 +67,10 @@ struct StateDiff std::map accounts; }; -std::ostream& operator<<(std::ostream& _out, StateDiff const& _s); -std::ostream& operator<<(std::ostream& _out, AccountDiff const& _s); - } +std::ostream& operator<<(std::ostream& _out, dev::eth::StateDiff const& _s); +std::ostream& operator<<(std::ostream& _out, dev::eth::AccountDiff const& _s); + +} diff --git a/libethereum/AddressState.cpp b/libethereum/AddressState.cpp index a3eb65acb..268440314 100644 --- a/libethereum/AddressState.cpp +++ b/libethereum/AddressState.cpp @@ -22,7 +22,8 @@ #include "AddressState.h" #include using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; #pragma GCC diagnostic ignored "-Wunused-variable" namespace { char dummy; }; diff --git a/libethereum/AddressState.h b/libethereum/AddressState.h index 3425dc4d1..c7df6523b 100644 --- a/libethereum/AddressState.h +++ b/libethereum/AddressState.h @@ -25,6 +25,8 @@ #include #include +namespace dev +{ namespace eth { @@ -78,5 +80,5 @@ private: }; } - +} diff --git a/libethereum/BlockChain.cpp b/libethereum/BlockChain.cpp index 97d69eb41..e9dfa0519 100644 --- a/libethereum/BlockChain.cpp +++ b/libethereum/BlockChain.cpp @@ -31,11 +31,12 @@ #include "State.h" #include "Defaults.h" using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; #define ETH_CATCH 1 -std::ostream& eth::operator<<(std::ostream& _out, BlockChain const& _bc) +std::ostream& dev::eth::operator<<(std::ostream& _out, BlockChain const& _bc) { string cmp = toBigEndianString(_bc.currentHash()); auto it = _bc.m_extrasDB->NewIterator(_bc.m_readOptions); @@ -49,7 +50,7 @@ std::ostream& eth::operator<<(std::ostream& _out, BlockChain const& _bc) return _out; } -std::map const& eth::genesisState() +std::map const& dev::eth::genesisState() { static std::map s_ret; if (s_ret.empty()) @@ -71,7 +72,7 @@ std::map const& eth::genesisState() BlockInfo* BlockChain::s_genesis = nullptr; boost::shared_mutex BlockChain::x_genesis; -ldb::Slice eth::toSlice(h256 _h, unsigned _sub) +ldb::Slice dev::eth::toSlice(h256 _h, unsigned _sub) { #if ALL_COMPILERS_ARE_CPP11_COMPLIANT static thread_local h256 h = _h ^ h256(u256(_sub)); @@ -95,12 +96,12 @@ bytes BlockChain::createGenesisBlock() MemoryDB db; TrieDB state(&db); state.init(); - eth::commit(genesisState(), db, state); + dev::eth::commit(genesisState(), db, state); stateRoot = state.root(); } block.appendList(13) << h256() << sha3EmptyList << h160(); - block.append(stateRoot, false, true) << bytes() << c_genesisDifficulty << 0 << 0 << 1000000 << 0 << (uint)0 << string() << sha3(bytes(1, 42)); + block.append(stateRoot, false, true) << bytes() << c_genesisDifficulty << 0 << 0 << 1000000 << 0 << (unsigned)0 << string() << sha3(bytes(1, 42)); block.appendRaw(RLPEmptyList); block.appendRaw(RLPEmptyList); return block.out(); @@ -135,7 +136,7 @@ BlockChain::BlockChain(std::string _path, bool _killExisting) // Insert details of genesis block. m_details[m_genesisHash] = BlockDetails(0, c_genesisDifficulty, h256(), {}, h256()); auto r = m_details[m_genesisHash].rlp(); - m_extrasDB->Put(m_writeOptions, ldb::Slice((char const*)&m_genesisHash, 32), (ldb::Slice)eth::ref(r)); + m_extrasDB->Put(m_writeOptions, ldb::Slice((char const*)&m_genesisHash, 32), (ldb::Slice)dev::ref(r)); } checkConsistency(); @@ -287,7 +288,7 @@ h256s BlockChain::import(bytes const& _block, OverlayDB const& _db) // All ok - insert into DB { WriteGuard l(x_details); - m_details[newHash] = BlockDetails((uint)pd.number + 1, td, bi.parentHash, {}, b); + m_details[newHash] = BlockDetails((unsigned)pd.number + 1, td, bi.parentHash, {}, b); m_details[bi.parentHash].children.push_back(newHash); } { @@ -299,10 +300,10 @@ h256s BlockChain::import(bytes const& _block, OverlayDB const& _db) m_traces[newHash] = bt; } - m_extrasDB->Put(m_writeOptions, toSlice(newHash), (ldb::Slice)eth::ref(m_details[newHash].rlp())); - m_extrasDB->Put(m_writeOptions, toSlice(bi.parentHash), (ldb::Slice)eth::ref(m_details[bi.parentHash].rlp())); - m_extrasDB->Put(m_writeOptions, toSlice(newHash, 1), (ldb::Slice)eth::ref(m_blooms[newHash].rlp())); - m_extrasDB->Put(m_writeOptions, toSlice(newHash, 2), (ldb::Slice)eth::ref(m_traces[newHash].rlp())); + m_extrasDB->Put(m_writeOptions, toSlice(newHash), (ldb::Slice)dev::ref(m_details[newHash].rlp())); + m_extrasDB->Put(m_writeOptions, toSlice(bi.parentHash), (ldb::Slice)dev::ref(m_details[bi.parentHash].rlp())); + m_extrasDB->Put(m_writeOptions, toSlice(newHash, 1), (ldb::Slice)dev::ref(m_blooms[newHash].rlp())); + m_extrasDB->Put(m_writeOptions, toSlice(newHash, 2), (ldb::Slice)dev::ref(m_traces[newHash].rlp())); m_db->Put(m_writeOptions, toSlice(newHash), (ldb::Slice)ref(_block)); #if ETH_PARANOIA diff --git a/libethereum/BlockChain.h b/libethereum/BlockChain.h index cbae012ba..200740782 100644 --- a/libethereum/BlockChain.h +++ b/libethereum/BlockChain.h @@ -21,6 +21,11 @@ #pragma once +#pragma warning(push) +#pragma warning(disable: 4100 4267) +#include +#pragma warning(pop) + #include #include #include @@ -31,6 +36,8 @@ #include "BlockQueue.h" namespace ldb = leveldb; +namespace dev +{ namespace eth { @@ -95,8 +102,8 @@ public: bytes block() const { return block(currentHash()); } /// Get a number for the given hash (or the most recent mined if none given). Thread-safe. - uint number(h256 _hash) const { return details(_hash).number; } - uint number() const { return number(currentHash()); } + unsigned number(h256 _hash) const { return details(_hash).number; } + unsigned number() const { return number(currentHash()); } /// Get a given block (RLP format). Thread-safe. h256 currentHash() const { ReadGuard l(x_lastBlockHash); return m_lastBlockHash; } @@ -195,3 +202,4 @@ private: std::ostream& operator<<(std::ostream& _out, BlockChain const& _bc); } +} diff --git a/libethereum/BlockDetails.cpp b/libethereum/BlockDetails.cpp index ae107fa68..eac40920e 100644 --- a/libethereum/BlockDetails.cpp +++ b/libethereum/BlockDetails.cpp @@ -23,11 +23,12 @@ #include using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; BlockDetails::BlockDetails(RLP const& _r) { - number = _r[0].toInt(); + number = _r[0].toInt(); totalDifficulty = _r[1].toInt(); parent = _r[2].toHash(); children = _r[3].toVector(); diff --git a/libethereum/BlockDetails.h b/libethereum/BlockDetails.h index 6db2171ee..21e5e7d13 100644 --- a/libethereum/BlockDetails.h +++ b/libethereum/BlockDetails.h @@ -21,25 +21,32 @@ #pragma once +#pragma warning(push) +#pragma warning(disable: 4100 4267) +#include +#pragma warning(pop) + #include #include #include "Manifest.h" namespace ldb = leveldb; +namespace dev +{ namespace eth { struct BlockDetails { BlockDetails(): number(0), totalDifficulty(0) {} - BlockDetails(uint _n, u256 _tD, h256 _p, h256s _c, h256 _bloom): number(_n), totalDifficulty(_tD), parent(_p), children(_c), bloom(_bloom) {} + BlockDetails(unsigned _n, u256 _tD, h256 _p, h256s _c, h256 _bloom): number(_n), totalDifficulty(_tD), parent(_p), children(_c), bloom(_bloom) {} BlockDetails(RLP const& _r); bytes rlp() const; bool isNull() const { return !totalDifficulty; } explicit operator bool() const { return !isNull(); } - uint number; // TODO: remove? + unsigned number; // TODO: remove? u256 totalDifficulty; h256 parent; h256s children; @@ -74,3 +81,4 @@ static const BlockBlooms NullBlockBlooms; static const BlockTraces NullBlockTraces; } +} diff --git a/libethereum/BlockQueue.cpp b/libethereum/BlockQueue.cpp index 1aca07847..00b64c8bc 100644 --- a/libethereum/BlockQueue.cpp +++ b/libethereum/BlockQueue.cpp @@ -26,7 +26,8 @@ #include #include "BlockChain.h" using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; bool BlockQueue::import(bytesConstRef _block, BlockChain const& _bc) { diff --git a/libethereum/BlockQueue.h b/libethereum/BlockQueue.h index 30686d192..b8586a553 100644 --- a/libethereum/BlockQueue.h +++ b/libethereum/BlockQueue.h @@ -26,6 +26,8 @@ #include "libethcore/CommonEth.h" #include +namespace dev +{ namespace eth { @@ -67,5 +69,4 @@ private: }; } - - +} diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index 872892b23..afefa1d4f 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -29,7 +29,8 @@ #include "Defaults.h" #include "EthereumHost.h" using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; using namespace p2p; VersionChecker::VersionChecker(string const& _dbPath): diff --git a/libethereum/Client.h b/libethereum/Client.h index 4eb65b7ba..254fd1631 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -40,6 +40,8 @@ #include "MessageFilter.h" #include "Miner.h" +namespace dev +{ namespace eth { @@ -94,13 +96,13 @@ struct ClientWatch }; struct WatchChannel: public LogChannel { static const char* name() { return "(o)"; } static const int verbosity = 7; }; -#define cwatch eth::LogOutputStream() +#define cwatch dev::LogOutputStream() struct WorkInChannel: public LogChannel { static const char* name() { return ">W>"; } static const int verbosity = 16; }; struct WorkOutChannel: public LogChannel { static const char* name() { return "() -#define cworkin eth::LogOutputStream() -#define cworkout eth::LogOutputStream() +#define cwork dev::LogOutputStream() +#define cworkin dev::LogOutputStream() +#define cworkout dev::LogOutputStream() /** * @brief Main API hub for interfacing with Ethereum. @@ -176,19 +178,19 @@ public: std::vector
    addresses(int _block) const; /// Get the fee associated for a transaction with the given data. - static u256 txGas(uint _dataCount, u256 _gas = 0) { return c_txDataGas * _dataCount + c_txGas + _gas; } + static u256 txGas(unsigned _dataCount, u256 _gas = 0) { return c_txDataGas * _dataCount + c_txGas + _gas; } /// Get the remaining gas limit in this block. u256 gasLimitRemaining() const { return m_postMine.gasLimitRemaining(); } // [PRIVATE API - only relevant for base clients, not available in general] - eth::State state(unsigned _txi, h256 _block) const; - eth::State state(h256 _block) const; - eth::State state(unsigned _txi) const; + dev::eth::State state(unsigned _txi, h256 _block) const; + dev::eth::State state(h256 _block) const; + dev::eth::State state(unsigned _txi) const; /// Get the object representing the current state of Ethereum. - eth::State postState() const { ReadGuard l(x_stateDB); return m_postMine; } + dev::eth::State postState() const { ReadGuard l(x_stateDB); return m_postMine; } /// Get the object representing the current canonical blockchain. BlockChain const& blockChain() const { return m_bc; } @@ -325,10 +327,13 @@ private: class Watch; +} } -namespace std { void swap(eth::Watch& _a, eth::Watch& _b); } +namespace std { void swap(dev::eth::Watch& _a, dev::eth::Watch& _b); } +namespace dev +{ namespace eth { @@ -351,12 +356,13 @@ private: unsigned m_id; }; +} } namespace std { -inline void swap(eth::Watch& _a, eth::Watch& _b) +inline void swap(dev::eth::Watch& _a, dev::eth::Watch& _b) { swap(_a.m_c, _b.m_c); swap(_a.m_id, _b.m_id); diff --git a/libethereum/CommonNet.cpp b/libethereum/CommonNet.cpp index 9bc277f86..acf8e220f 100644 --- a/libethereum/CommonNet.cpp +++ b/libethereum/CommonNet.cpp @@ -21,4 +21,5 @@ #include "CommonNet.h" using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; diff --git a/libethereum/CommonNet.h b/libethereum/CommonNet.h index a6c6217d7..a4484ffb8 100644 --- a/libethereum/CommonNet.h +++ b/libethereum/CommonNet.h @@ -28,13 +28,15 @@ #include #include +namespace dev +{ namespace eth { -static const eth::uint c_maxHashes = 32; ///< Maximum number of hashes BlockHashes will ever send. -static const eth::uint c_maxHashesAsk = 32; ///< Maximum number of hashes GetBlockHashes will ever ask for. -static const eth::uint c_maxBlocks = 16; ///< Maximum number of blocks Blocks will ever send. -static const eth::uint c_maxBlocksAsk = 16; ///< Maximum number of blocks we ask to receive in Blocks (when using GetChain). +static const unsigned c_maxHashes = 32; ///< Maximum number of hashes BlockHashes will ever send. +static const unsigned c_maxHashesAsk = 32; ///< Maximum number of hashes GetBlockHashes will ever ask for. +static const unsigned c_maxBlocks = 16; ///< Maximum number of blocks Blocks will ever send. +static const unsigned c_maxBlocksAsk = 16; ///< Maximum number of blocks we ask to receive in Blocks (when using GetChain). class OverlayDB; class BlockChain; @@ -54,3 +56,4 @@ enum EthereumPacket }; } +} diff --git a/libethereum/Defaults.cpp b/libethereum/Defaults.cpp index e78156094..17d773427 100644 --- a/libethereum/Defaults.cpp +++ b/libethereum/Defaults.cpp @@ -23,7 +23,8 @@ #include using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; Defaults* Defaults::s_this = nullptr; diff --git a/libethereum/Defaults.h b/libethereum/Defaults.h index d4bd5322f..e0478455d 100644 --- a/libethereum/Defaults.h +++ b/libethereum/Defaults.h @@ -23,6 +23,8 @@ #include +namespace dev +{ namespace eth { @@ -45,3 +47,4 @@ private: }; } +} diff --git a/libethereum/EthereumHost.cpp b/libethereum/EthereumHost.cpp index 0f63712bb..0ba544248 100644 --- a/libethereum/EthereumHost.cpp +++ b/libethereum/EthereumHost.cpp @@ -35,7 +35,8 @@ #include "BlockQueue.h" #include "EthereumPeer.h" using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; using namespace p2p; EthereumHost::EthereumHost(BlockChain const& _ch, u256 _networkId): @@ -139,7 +140,7 @@ void EthereumHost::maintainTransactions(TransactionQueue& _tq, h256 _currentHash { auto ep = p->cap(); bytes b; - uint n = 0; + unsigned n = 0; for (auto const& i: _tq.transactions()) if ((!m_transactionsSent.count(i.first) && !ep->m_knownTransactions.count(i.first)) || ep->m_requireTransactions || resendAll) { diff --git a/libethereum/EthereumHost.h b/libethereum/EthereumHost.h index 0ee56b9cf..e0cf5200b 100644 --- a/libethereum/EthereumHost.h +++ b/libethereum/EthereumHost.h @@ -34,10 +34,14 @@ #include "CommonNet.h" #include "EthereumPeer.h" -namespace eth +namespace dev { class RLPStream; + +namespace eth +{ + class TransactionQueue; class BlockQueue; @@ -103,3 +107,4 @@ private: }; } +} diff --git a/libethereum/EthereumPeer.cpp b/libethereum/EthereumPeer.cpp index 3dc5dafc9..45165d348 100644 --- a/libethereum/EthereumPeer.cpp +++ b/libethereum/EthereumPeer.cpp @@ -28,10 +28,11 @@ #include "BlockChain.h" #include "EthereumHost.h" using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; using namespace p2p; -#define clogS(X) eth::LogOutputStream(false) << "| " << std::setw(2) << session()->socketId() << "] " +#define clogS(X) dev::LogOutputStream(false) << "| " << std::setw(2) << session()->socketId() << "] " EthereumPeer::EthereumPeer(Session* _s, HostCapabilityFace* _h): Capability(_s, _h) @@ -73,7 +74,7 @@ void EthereumPeer::startInitialSync() } h256 c = host()->m_chain->currentHash(); - uint n = host()->m_chain->number(); + unsigned n = host()->m_chain->number(); u256 td = max(host()->m_chain->details().totalDifficulty, host()->m_totalDifficultyOfNeeded); clogS(NetAllDetail) << "Initial sync. Latest:" << c.abridged() << ", number:" << n << ", TD: max(" << host()->m_chain->details().totalDifficulty << "," << host()->m_totalDifficultyOfNeeded << ") versus " << m_totalDifficulty; @@ -123,7 +124,7 @@ bool EthereumPeer::interpret(RLP const& _r) { case StatusPacket: { - m_protocolVersion = _r[1].toInt(); + m_protocolVersion = _r[1].toInt(); m_networkId = _r[2].toInt(); m_totalDifficulty = _r[3].toInt(); m_latestHash = _r[4].toHash(); diff --git a/libethereum/EthereumPeer.h b/libethereum/EthereumPeer.h index b799de314..c0c75f646 100644 --- a/libethereum/EthereumPeer.h +++ b/libethereum/EthereumPeer.h @@ -31,6 +31,8 @@ #include #include "CommonNet.h" +namespace dev +{ namespace eth { @@ -65,7 +67,7 @@ private: void giveUpOnFetch(); - uint m_protocolVersion; + unsigned m_protocolVersion; u256 m_networkId; h256 m_latestHash; ///< Peer's latest block's hash. @@ -83,3 +85,4 @@ private: }; } +} diff --git a/libethereum/Executive.cpp b/libethereum/Executive.cpp index 7fbd36102..e8c1fd1cb 100644 --- a/libethereum/Executive.cpp +++ b/libethereum/Executive.cpp @@ -25,7 +25,8 @@ #include "State.h" #include "ExtVM.h" using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; #define ETH_VMTRACE 1 @@ -157,8 +158,8 @@ OnOpFunc Executive::simpleTrace() o << " STORAGE" << endl; for (auto const& i: ext.state().storage(ext.myAddress)) o << showbase << hex << i.first << ": " << i.second << endl; - eth::LogOutputStream(true) << o.str(); - eth::LogOutputStream(false) << " | " << dec << ext.level << " | " << ext.myAddress << " | #" << steps << " | " << hex << setw(4) << setfill('0') << vm.curPC() << " : " << instructionInfo(inst).name << " | " << dec << vm.gas() << " | -" << dec << gasCost << " | " << newMemSize << "x32" << " ]"; + dev::LogOutputStream(true) << o.str(); + dev::LogOutputStream(false) << " | " << dec << ext.level << " | " << ext.myAddress << " | #" << steps << " | " << hex << setw(4) << setfill('0') << vm.curPC() << " : " << instructionInfo(inst).name << " | " << dec << vm.gas() << " | -" << dec << gasCost << " | " << newMemSize << "x32" << " ]"; }; } diff --git a/libethereum/Executive.h b/libethereum/Executive.h index e7ba44223..80e1035c9 100644 --- a/libethereum/Executive.h +++ b/libethereum/Executive.h @@ -29,6 +29,8 @@ #include "Transaction.h" #include "Manifest.h" +namespace dev +{ namespace eth { @@ -78,3 +80,4 @@ private: }; } +} diff --git a/libethereum/ExtVM.h b/libethereum/ExtVM.h index f3d8bee50..b4db1975e 100644 --- a/libethereum/ExtVM.h +++ b/libethereum/ExtVM.h @@ -27,6 +27,8 @@ #include #include "State.h" +namespace dev +{ namespace eth { @@ -122,5 +124,5 @@ private: }; } - +} diff --git a/libethereum/Manifest.cpp b/libethereum/Manifest.cpp index d5d7e2df9..2d1e4aa6e 100644 --- a/libethereum/Manifest.cpp +++ b/libethereum/Manifest.cpp @@ -21,7 +21,8 @@ #include "Manifest.h" using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; Manifest::Manifest(bytesConstRef _r) { diff --git a/libethereum/Manifest.h b/libethereum/Manifest.h index 0a873afc6..2f83d0cab 100644 --- a/libethereum/Manifest.h +++ b/libethereum/Manifest.h @@ -26,6 +26,8 @@ #include #include +namespace dev +{ namespace eth { @@ -68,3 +70,4 @@ struct Manifest }; } +} diff --git a/libethereum/MessageFilter.cpp b/libethereum/MessageFilter.cpp index 786440775..537bc4a9b 100644 --- a/libethereum/MessageFilter.cpp +++ b/libethereum/MessageFilter.cpp @@ -24,7 +24,8 @@ #include #include "State.h" using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; void MessageFilter::fillStream(RLPStream& _s) const { @@ -35,7 +36,7 @@ h256 MessageFilter::sha3() const { RLPStream s; fillStream(s); - return eth::sha3(s.out()); + return dev::eth::sha3(s.out()); } bool MessageFilter::matches(h256 _bloom) const diff --git a/libethereum/MessageFilter.h b/libethereum/MessageFilter.h index cefc424c0..8c44ce449 100644 --- a/libethereum/MessageFilter.h +++ b/libethereum/MessageFilter.h @@ -26,6 +26,8 @@ #include #include "PastMessage.h" +namespace dev +{ namespace eth { @@ -71,3 +73,4 @@ private: }; } +} diff --git a/libethereum/Miner.cpp b/libethereum/Miner.cpp index 3c0a8b003..95789889f 100644 --- a/libethereum/Miner.cpp +++ b/libethereum/Miner.cpp @@ -23,7 +23,8 @@ #include "Miner.h" #include "State.h" using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; Miner::Miner(MinerHost* _host, unsigned _id): m_host(_host), diff --git a/libethereum/Miner.h b/libethereum/Miner.h index c0aa09d56..46f59d0f5 100644 --- a/libethereum/Miner.h +++ b/libethereum/Miner.h @@ -29,6 +29,8 @@ #include #include "State.h" +namespace dev +{ namespace eth { @@ -41,8 +43,8 @@ struct MineProgress double requirement = 0; ///< The PoW requirement - as the second logarithm of the minimum acceptable hash. double best = 1e99; ///< The PoW achievement - as the second logarithm of the minimum found hash. double current = 0; ///< The most recent PoW achievement - as the second logarithm of the presently found hash. - uint hashes = 0; ///< Total number of hashes computed. - uint ms = 0; ///< Total number of milliseconds of mining thus far. + unsigned hashes = 0; ///< Total number of hashes computed. + unsigned ms = 0; ///< Total number of milliseconds of mining thus far. }; /** @@ -139,3 +141,4 @@ private: }; } +} diff --git a/libethereum/PastMessage.cpp b/libethereum/PastMessage.cpp index 852d4a4bd..377b7e4d2 100644 --- a/libethereum/PastMessage.cpp +++ b/libethereum/PastMessage.cpp @@ -21,4 +21,5 @@ #include "PastMessage.h" using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; diff --git a/libethereum/PastMessage.h b/libethereum/PastMessage.h index 789ce350a..1b7f79527 100644 --- a/libethereum/PastMessage.h +++ b/libethereum/PastMessage.h @@ -25,6 +25,8 @@ #include #include "Manifest.h" +namespace dev +{ namespace eth { @@ -51,3 +53,4 @@ struct PastMessage typedef std::vector PastMessages; } +} diff --git a/libethereum/State.cpp b/libethereum/State.cpp index 5ef528c58..9ff089566 100644 --- a/libethereum/State.cpp +++ b/libethereum/State.cpp @@ -33,7 +33,8 @@ #include "Defaults.h" #include "ExtVM.h" using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; #define ctrace clog(StateTrace) @@ -72,7 +73,7 @@ State::State(Address _coinbaseAddress, OverlayDB const& _db): paranoia("beginning of normal construction.", true); - eth::commit(genesisState(), m_db, m_state); + dev::eth::commit(genesisState(), m_db, m_state); m_db.commit(); paranoia("after DB commit of normal construction.", true); @@ -300,7 +301,7 @@ void State::ensureCached(std::map& _cache, Address _a, bo void State::commit() { - eth::commit(m_cache, m_db, m_state); + dev::eth::commit(m_cache, m_db, m_state); m_cache.clear(); } @@ -761,7 +762,7 @@ void State::commitToMine(BlockChain const& _bc) m_currentBlock.parentHash = m_previousBlock.hash; } -MineInfo State::mine(uint _msTimeout, bool _turbo) +MineInfo State::mine(unsigned _msTimeout, bool _turbo) { // Update difficulty according to timestamp. m_currentBlock.difficulty = m_currentBlock.calculateDifficulty(m_previousBlock); @@ -1200,7 +1201,7 @@ void State::applyRewards(Addresses const& _uncleAddresses) addBalance(m_currentBlock.coinbaseAddress, r); } -std::ostream& eth::operator<<(std::ostream& _out, State const& _s) +std::ostream& dev::eth::operator<<(std::ostream& _out, State const& _s) { _out << "--- " << _s.rootHash() << std::endl; std::set
    d; diff --git a/libethereum/State.h b/libethereum/State.h index d2aa97632..1a45b89ba 100644 --- a/libethereum/State.h +++ b/libethereum/State.h @@ -38,6 +38,8 @@ #include "Executive.h" #include "AccountDiff.h" +namespace dev +{ namespace eth { @@ -120,7 +122,7 @@ public: /// This function is thread-safe. You can safely have other interactions with this object while it is happening. /// @param _msTimeout Timeout before return in milliseconds. /// @returns Information on the mining. - MineInfo mine(uint _msTimeout = 1000, bool _turbo = false); + MineInfo mine(unsigned _msTimeout = 1000, bool _turbo = false); /** Commit to DB and build the final block if the previous call to mine()'s result is completion. * Typically looks like: @@ -363,5 +365,5 @@ void commit(std::map const& _cache, DB& _db, TrieDB #include "Transaction.h" using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; #define ETH_ADDRESS_DEBUG 0 @@ -78,7 +79,7 @@ Address Transaction::sender() const throw InvalidSignature(); // TODO: check right160 is correct and shouldn't be left160. - m_sender = right160(eth::sha3(bytesConstRef(&(pubkey[1]), 64))); + m_sender = right160(dev::eth::sha3(bytesConstRef(&(pubkey[1]), 64))); #if ETH_ADDRESS_DEBUG cout << "---- RECOVER -------------------------------" << endl; diff --git a/libethereum/Transaction.h b/libethereum/Transaction.h index 9e0ab3009..7aba44dcd 100644 --- a/libethereum/Transaction.h +++ b/libethereum/Transaction.h @@ -25,6 +25,8 @@ #include #include +namespace dev +{ namespace eth { @@ -65,8 +67,8 @@ struct Transaction void fillStream(RLPStream& _s, bool _sig = true) const; bytes rlp(bool _sig = true) const { RLPStream s; fillStream(s, _sig); return s.out(); } std::string rlpString(bool _sig = true) const { return asString(rlp(_sig)); } - h256 sha3(bool _sig = true) const { RLPStream s; fillStream(s, _sig); return eth::sha3(s.out()); } - bytes sha3Bytes(bool _sig = true) const { RLPStream s; fillStream(s, _sig); return eth::sha3Bytes(s.out()); } + h256 sha3(bool _sig = true) const { RLPStream s; fillStream(s, _sig); return dev::eth::sha3(s.out()); } + bytes sha3Bytes(bool _sig = true) const { RLPStream s; fillStream(s, _sig); return dev::eth::sha3Bytes(s.out()); } private: mutable Address m_sender; @@ -94,5 +96,4 @@ inline std::ostream& operator<<(std::ostream& _out, Transaction const& _t) } } - - +} diff --git a/libethereum/TransactionQueue.cpp b/libethereum/TransactionQueue.cpp index c2f03ddf1..4fb6c2299 100644 --- a/libethereum/TransactionQueue.cpp +++ b/libethereum/TransactionQueue.cpp @@ -25,7 +25,8 @@ #include #include "Transaction.h" using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; bool TransactionQueue::import(bytesConstRef _transactionRLP) { diff --git a/libethereum/TransactionQueue.h b/libethereum/TransactionQueue.h index 41cefc56f..6cf3bc250 100644 --- a/libethereum/TransactionQueue.h +++ b/libethereum/TransactionQueue.h @@ -26,6 +26,8 @@ #include "libethcore/CommonEth.h" #include +namespace dev +{ namespace eth { @@ -58,5 +60,5 @@ private: }; } - +} diff --git a/libethereum/Utility.cpp b/libethereum/Utility.cpp index 894041c59..b6653fe39 100644 --- a/libethereum/Utility.cpp +++ b/libethereum/Utility.cpp @@ -24,9 +24,10 @@ #include #include using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; -bytes eth::parseData(string const& _args) +bytes dev::eth::parseData(string const& _args) { bytes m_data; @@ -41,12 +42,12 @@ bytes eth::parseData(string const& _args) { u256 v((string)what[2]); if (what[6] == "szabo") - v *= eth::szabo; + v *= dev::eth::szabo; else if (what[5] == "finney") - v *= eth::finney; + v *= dev::eth::finney; else if (what[4] == "ether") - v *= eth::ether; - bytes bs = eth::toCompactBigEndian(v); + v *= dev::eth::ether; + bytes bs = dev::toCompactBigEndian(v); if (what[1] != "$") for (auto i = bs.size(); i < 32; ++i) m_data.push_back(0); diff --git a/libethereum/Utility.h b/libethereum/Utility.h index 8a0f5217d..0f9d178ff 100644 --- a/libethereum/Utility.h +++ b/libethereum/Utility.h @@ -24,9 +24,12 @@ #include #include +namespace dev +{ namespace eth { bytes parseData(std::string const& _args); } +} diff --git a/libethereumx/Ethereum.cpp b/libethereumx/Ethereum.cpp index f2793a366..d1968b7fa 100644 --- a/libethereumx/Ethereum.cpp +++ b/libethereumx/Ethereum.cpp @@ -24,7 +24,8 @@ #include #include using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; using namespace p2p; Ethereum::Ethereum() diff --git a/libethereumx/Ethereum.h b/libethereumx/Ethereum.h index 9f08234fe..517010b93 100644 --- a/libethereumx/Ethereum.h +++ b/libethereumx/Ethereum.h @@ -36,6 +36,8 @@ #include #include +namespace dev +{ namespace eth { @@ -143,3 +145,4 @@ private: }; } +} diff --git a/libevm/ExtVMFace.cpp b/libevm/ExtVMFace.cpp index bdaa8f8a6..7c938417a 100644 --- a/libevm/ExtVMFace.cpp +++ b/libevm/ExtVMFace.cpp @@ -22,7 +22,8 @@ #include "ExtVMFace.h" using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; ExtVMFace::ExtVMFace(Address _myAddress, Address _caller, Address _origin, u256 _value, u256 _gasPrice, bytesConstRef _data, bytesConstRef _code, BlockInfo const& _previousBlock, BlockInfo const& _currentBlock): myAddress(_myAddress), diff --git a/libevm/ExtVMFace.h b/libevm/ExtVMFace.h index 2c32c74f4..e768519de 100644 --- a/libevm/ExtVMFace.h +++ b/libevm/ExtVMFace.h @@ -26,6 +26,8 @@ #include #include +namespace dev +{ namespace eth { @@ -107,3 +109,4 @@ public: }; } +} diff --git a/libevm/FeeStructure.cpp b/libevm/FeeStructure.cpp index f7dbbfbf8..d29b9fef9 100644 --- a/libevm/FeeStructure.cpp +++ b/libevm/FeeStructure.cpp @@ -22,15 +22,16 @@ #include "FeeStructure.h" using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; -u256 const eth::c_stepGas = 1; -u256 const eth::c_balanceGas = 20; -u256 const eth::c_sha3Gas = 20; -u256 const eth::c_sloadGas = 20; -u256 const eth::c_sstoreGas = 100; -u256 const eth::c_createGas = 100; -u256 const eth::c_callGas = 20; -u256 const eth::c_memoryGas = 1; -u256 const eth::c_txDataGas = 5; -u256 const eth::c_txGas = 500; +u256 const dev::eth::c_stepGas = 1; +u256 const dev::eth::c_balanceGas = 20; +u256 const dev::eth::c_sha3Gas = 20; +u256 const dev::eth::c_sloadGas = 20; +u256 const dev::eth::c_sstoreGas = 100; +u256 const dev::eth::c_createGas = 100; +u256 const dev::eth::c_callGas = 20; +u256 const dev::eth::c_memoryGas = 1; +u256 const dev::eth::c_txDataGas = 5; +u256 const dev::eth::c_txGas = 500; diff --git a/libevm/FeeStructure.h b/libevm/FeeStructure.h index 341c81f8c..364a6de0d 100644 --- a/libevm/FeeStructure.h +++ b/libevm/FeeStructure.h @@ -23,6 +23,8 @@ #include +namespace dev +{ namespace eth { @@ -38,3 +40,4 @@ extern u256 const c_txDataGas; ///< Per byte of data attached to a transaction extern u256 const c_txGas; ///< Per transaction. NOTE: Not payable on data of calls between transactions. } +} diff --git a/libevm/VM.cpp b/libevm/VM.cpp index 5383f4906..e47237d5a 100644 --- a/libevm/VM.cpp +++ b/libevm/VM.cpp @@ -22,7 +22,8 @@ #include "VM.h" using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; void VM::reset(u256 _gas) { diff --git a/libevm/VM.h b/libevm/VM.h index 76b68a11c..e774a1787 100644 --- a/libevm/VM.h +++ b/libevm/VM.h @@ -30,6 +30,8 @@ #include "FeeStructure.h" #include "ExtVMFace.h" +namespace dev +{ namespace eth { @@ -87,9 +89,9 @@ private: } // INLINE: -template eth::bytesConstRef eth::VM::go(Ext& _ext, OnOpFunc const& _onOp, uint64_t _steps) +template dev::bytesConstRef dev::eth::VM::go(Ext& _ext, OnOpFunc const& _onOp, uint64_t _steps) { - auto memNeed = [](eth::u256 _offset, eth::u256 _size) { return _size ? _offset + _size : 0; }; + auto memNeed = [](dev::u256 _offset, dev::u256 _size) { return _size ? _offset + _size : 0; }; u256 nextPC = m_curPC + 1; auto osteps = _steps; @@ -310,7 +312,7 @@ template eth::bytesConstRef eth::VM::go(Ext& _ext, OnOpFunc const& _ break; case Instruction::BYTE: require(2); - m_stack[m_stack.size() - 2] = m_stack.back() < 32 ? (m_stack[m_stack.size() - 2] >> (uint)(8 * (31 - m_stack.back()))) & 0xff : 0; + m_stack[m_stack.size() - 2] = m_stack.back() < 32 ? (m_stack[m_stack.size() - 2] >> (unsigned)(8 * (31 - m_stack.back()))) & 0xff : 0; m_stack.pop_back(); break; case Instruction::ADDMOD: @@ -688,4 +690,4 @@ template eth::bytesConstRef eth::VM::go(Ext& _ext, OnOpFunc const& _ throw StepsDone(); return bytesConstRef(); } - +} diff --git a/libevmface/Instruction.cpp b/libevmface/Instruction.cpp index 7d6fdd366..83dc22c42 100644 --- a/libevmface/Instruction.cpp +++ b/libevmface/Instruction.cpp @@ -23,9 +23,10 @@ #include using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; -const std::map eth::c_instructions = +const std::map dev::eth::c_instructions = { { "STOP", Instruction::STOP }, { "ADD", Instruction::ADD }, @@ -279,10 +280,10 @@ static const std::map c_instructionInfo = { Instruction::SUICIDE, { "SUICIDE", 0, 1, 0} } }; -string eth::disassemble(bytes const& _mem) +string dev::eth::disassemble(bytes const& _mem) { stringstream ret; - uint numerics = 0; + unsigned numerics = 0; for (auto it = _mem.begin(); it != _mem.end(); ++it) { byte n = *it; @@ -303,7 +304,7 @@ string eth::disassemble(bytes const& _mem) return ret.str(); } -InstructionInfo eth::instructionInfo(Instruction _inst) +InstructionInfo dev::eth::instructionInfo(Instruction _inst) { try { diff --git a/libevmface/Instruction.h b/libevmface/Instruction.h index 1af42bada..01b4d6354 100644 --- a/libevmface/Instruction.h +++ b/libevmface/Instruction.h @@ -27,6 +27,8 @@ namespace boost { namespace spirit { class utree; } } namespace sp = boost::spirit; +namespace dev +{ namespace eth { @@ -188,3 +190,4 @@ extern const std::map c_instructions; std::string disassemble(bytes const& _mem); } +} diff --git a/liblll/Assembly.cpp b/liblll/Assembly.cpp index f397ec37d..475eaeabf 100644 --- a/liblll/Assembly.cpp +++ b/liblll/Assembly.cpp @@ -24,7 +24,8 @@ #include using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; int AssemblyItem::deposit() const { @@ -59,7 +60,7 @@ unsigned Assembly::bytesRequired() const ret += 33; break; case Push: - ret += 1 + max(1, eth::bytesRequired(i.m_data)); + ret += 1 + max(1, dev::bytesRequired(i.m_data)); break; case PushSubSize: ret += 4; // worst case: a 16MB program @@ -71,7 +72,7 @@ unsigned Assembly::bytesRequired() const case Tag:; default:; } - if (eth::bytesRequired(ret) <= br) + if (dev::bytesRequired(ret) <= br) return ret; } } @@ -110,7 +111,7 @@ void Assembly::append(Assembly const& _a, int _deposit) } } -ostream& eth::operator<<(ostream& _out, AssemblyItemsConstRef _i) +ostream& dev::eth::operator<<(ostream& _out, AssemblyItemsConstRef _i) { for (AssemblyItem const& i: _i) switch (i.type()) @@ -217,7 +218,7 @@ inline bool matches(AssemblyItemsConstRef _a, AssemblyItemsConstRef _b) } struct OptimiserChannel: public LogChannel { static const char* name() { return "OPT"; } static const int verbosity = 12; }; -#define copt eth::LogOutputStream() +#define copt dev::LogOutputStream() Assembly& Assembly::optimise(bool _enable) { @@ -353,7 +354,7 @@ bytes Assembly::assemble() const vector tagPos(m_usedTags); map tagRef; multimap dataRef; - unsigned bytesPerTag = eth::bytesRequired(totalBytes); + unsigned bytesPerTag = dev::bytesRequired(totalBytes); byte tagPush = (byte)Instruction::PUSH1 - 1 + bytesPerTag; for (auto const& i: m_subs) @@ -380,7 +381,7 @@ bytes Assembly::assemble() const } case Push: { - byte b = max(1, eth::bytesRequired(i.m_data)); + byte b = max(1, dev::bytesRequired(i.m_data)); ret.push_back((byte)Instruction::PUSH1 - 1 + b); ret.resize(ret.size() + b); bytesRef byr(&ret.back() + 1 - b, b); @@ -404,7 +405,7 @@ bytes Assembly::assemble() const case PushSubSize: { auto s = m_data[i.m_data].size(); - byte b = max(1, eth::bytesRequired(s)); + byte b = max(1, dev::bytesRequired(s)); ret.push_back((byte)Instruction::PUSH1 - 1 + b); ret.resize(ret.size() + b); bytesRef byr(&ret.back() + 1 - b, b); diff --git a/liblll/Assembly.h b/liblll/Assembly.h index f0f93297f..cfeb6d1fb 100644 --- a/liblll/Assembly.h +++ b/liblll/Assembly.h @@ -27,6 +27,8 @@ #include #include "Exceptions.h" +namespace dev +{ namespace eth { @@ -130,3 +132,4 @@ inline std::ostream& operator<<(std::ostream& _out, Assembly const& _a) } } +} diff --git a/liblll/CodeFragment.cpp b/liblll/CodeFragment.cpp index 07d17fbef..ecad3d364 100644 --- a/liblll/CodeFragment.cpp +++ b/liblll/CodeFragment.cpp @@ -28,7 +28,8 @@ #include "CompilerState.h" #include "Parser.h" using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; namespace qi = boost::spirit::qi; namespace px = boost::phoenix; namespace sp = boost::spirit; diff --git a/liblll/CodeFragment.h b/liblll/CodeFragment.h index 98a6f15c7..a81d8ea22 100644 --- a/liblll/CodeFragment.h +++ b/liblll/CodeFragment.h @@ -29,6 +29,8 @@ namespace boost { namespace spirit { class utree; } } namespace sp = boost::spirit; +namespace dev +{ namespace eth { @@ -58,3 +60,4 @@ private: static const CodeFragment NullCodeFragment; } +} diff --git a/liblll/Compiler.cpp b/liblll/Compiler.cpp index ebe2638be..389c10be2 100644 --- a/liblll/Compiler.cpp +++ b/liblll/Compiler.cpp @@ -25,9 +25,10 @@ #include "CodeFragment.h" using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; -bytes eth::compileLLL(string const& _src, bool _opt, vector* _errors) +bytes dev::eth::compileLLL(string const& _src, bool _opt, vector* _errors) { try { @@ -52,7 +53,7 @@ bytes eth::compileLLL(string const& _src, bool _opt, vector* _errors) return bytes(); } -std::string eth::compileLLLToAsm(std::string const& _src, bool _opt, std::vector* _errors) +std::string dev::eth::compileLLLToAsm(std::string const& _src, bool _opt, std::vector* _errors) { try { @@ -76,7 +77,7 @@ std::string eth::compileLLLToAsm(std::string const& _src, bool _opt, std::vector return string(); } -string eth::parseLLL(string const& _src) +string dev::eth::parseLLL(string const& _src) { sp::utree o; try diff --git a/liblll/Compiler.h b/liblll/Compiler.h index da84a2fea..5daec0a54 100644 --- a/liblll/Compiler.h +++ b/liblll/Compiler.h @@ -25,6 +25,8 @@ #include #include +namespace dev +{ namespace eth { @@ -33,4 +35,4 @@ std::string compileLLLToAsm(std::string const& _src, bool _opt = true, std::vect bytes compileLLL(std::string const& _src, bool _opt = true, std::vector* _errors = nullptr); } - +} diff --git a/liblll/CompilerState.cpp b/liblll/CompilerState.cpp index 222775017..c3dc2dda3 100644 --- a/liblll/CompilerState.cpp +++ b/liblll/CompilerState.cpp @@ -23,7 +23,8 @@ #include "CodeFragment.h" using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; CompilerState::CompilerState() { diff --git a/liblll/CompilerState.h b/liblll/CompilerState.h index a0b3bf46e..bfe56f927 100644 --- a/liblll/CompilerState.h +++ b/liblll/CompilerState.h @@ -24,6 +24,8 @@ #include #include "CodeFragment.h" +namespace dev +{ namespace eth { @@ -52,3 +54,4 @@ struct CompilerState }; } +} diff --git a/liblll/Exceptions.h b/liblll/Exceptions.h index a1aee1738..1dcbbcc66 100644 --- a/liblll/Exceptions.h +++ b/liblll/Exceptions.h @@ -21,11 +21,15 @@ #pragma once +#include + +namespace dev +{ namespace eth { /// Compile a Low-level Lisp-like Language program into EVM-code. -class CompilerException: public Exception {}; +class CompilerException: public dev::Exception {}; class InvalidOperation: public CompilerException {}; class IntegerOutOfRange: public CompilerException {}; class StringTooLong: public CompilerException {}; @@ -40,3 +44,4 @@ class BareSymbol: public CompilerException {}; class ExpectedLiteral: public CompilerException {}; } +} diff --git a/liblll/Parser.cpp b/liblll/Parser.cpp index edcd38f32..52a05174b 100644 --- a/liblll/Parser.cpp +++ b/liblll/Parser.cpp @@ -28,12 +28,13 @@ #include using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; namespace qi = boost::spirit::qi; namespace px = boost::phoenix; namespace sp = boost::spirit; -void eth::killBigints(sp::utree const& _this) +void dev::eth::killBigints(sp::utree const& _this) { switch (_this.which()) { @@ -43,7 +44,7 @@ void eth::killBigints(sp::utree const& _this) } } -void eth::debugOutAST(ostream& _out, sp::utree const& _this) +void dev::eth::debugOutAST(ostream& _out, sp::utree const& _this) { switch (_this.which()) { @@ -69,7 +70,7 @@ void eth::debugOutAST(ostream& _out, sp::utree const& _this) } } -namespace eth { +namespace dev { namespace eth { namespace parseTreeLLL_ { template @@ -81,13 +82,13 @@ struct tagNode } }; -}} +}}} -void eth::parseTreeLLL(string const& _s, sp::utree& o_out) +void dev::eth::parseTreeLLL(string const& _s, sp::utree& o_out) { using qi::standard::space; using qi::standard::space_type; - using eth::parseTreeLLL_::tagNode; + using dev::eth::parseTreeLLL_::tagNode; typedef sp::basic_string symbol_type; typedef string::const_iterator it; diff --git a/liblll/Parser.h b/liblll/Parser.h index 5286c3e8a..f90c6b5df 100644 --- a/liblll/Parser.h +++ b/liblll/Parser.h @@ -28,6 +28,8 @@ namespace boost { namespace spirit { class utree; } } namespace sp = boost::spirit; +namespace dev +{ namespace eth { @@ -36,4 +38,4 @@ void parseTreeLLL(std::string const& _s, sp::utree& o_out); void debugOutAST(std::ostream& _out, sp::utree const& _this); } - +} diff --git a/libp2p/CMakeLists.txt b/libp2p/CMakeLists.txt index 98d9b06bb..118aecbe8 100644 --- a/libp2p/CMakeLists.txt +++ b/libp2p/CMakeLists.txt @@ -17,6 +17,7 @@ file(GLOB HEADERS "*.h") include_directories(..) +target_link_libraries(${EXECUTABLE} ethential) target_link_libraries(${EXECUTABLE} secp256k1) if(MINIUPNPC_LS) target_link_libraries(${EXECUTABLE} ${MINIUPNPC_LS}) diff --git a/libp2p/Capability.cpp b/libp2p/Capability.cpp index 85ccf9aa9..5ca674120 100644 --- a/libp2p/Capability.cpp +++ b/libp2p/Capability.cpp @@ -23,8 +23,8 @@ #include "Session.h" using namespace std; -using namespace eth; -using namespace p2p; +using namespace dev; +using namespace dev::p2p; void Capability::disable(std::string const& _problem) { diff --git a/libp2p/Capability.h b/libp2p/Capability.h index 275470c65..bffd38c79 100644 --- a/libp2p/Capability.h +++ b/libp2p/Capability.h @@ -24,6 +24,8 @@ #include "Common.h" #include "HostCapability.h" +namespace dev +{ namespace p2p { @@ -60,3 +62,4 @@ private: }; } +} diff --git a/libp2p/Common.cpp b/libp2p/Common.cpp index 297bd4715..21f23696a 100644 --- a/libp2p/Common.cpp +++ b/libp2p/Common.cpp @@ -21,8 +21,8 @@ #include "Common.h" using namespace std; -using namespace eth; -using namespace p2p; +using namespace dev; +using namespace dev::p2p; // Helper function to determine if an address falls within one of the reserved ranges // For V4: diff --git a/libp2p/Common.h b/libp2p/Common.h index 7061b8090..94be0b04b 100644 --- a/libp2p/Common.h +++ b/libp2p/Common.h @@ -33,23 +33,15 @@ namespace ba = boost::asio; namespace bi = boost::asio::ip; -namespace eth +namespace dev { + class RLP; class RLPStream; -} namespace p2p { -using eth::LogChannel; -using eth::bytes; -using eth::h256; -using eth::h512; -using eth::bytesConstRef; -using eth::RLP; -using eth::RLPStream; - bool isPrivateAddress(bi::address _addressToCheck); class UPnP; @@ -105,3 +97,4 @@ struct PeerInfo }; } +} diff --git a/libp2p/Host.cpp b/libp2p/Host.cpp index 580a5ea1a..1a375608d 100644 --- a/libp2p/Host.cpp +++ b/libp2p/Host.cpp @@ -40,8 +40,8 @@ #include "Capability.h" #include "UPnP.h" using namespace std; -using namespace eth; -using namespace p2p; +using namespace dev; +using namespace dev::p2p; // Addresses we will skip during network interface discovery // Use a vector as the list is small diff --git a/libp2p/Host.h b/libp2p/Host.h index 08a39a8c2..c362bbe11 100644 --- a/libp2p/Host.h +++ b/libp2p/Host.h @@ -33,14 +33,13 @@ namespace ba = boost::asio; namespace bi = boost::asio::ip; -namespace p2p +namespace dev { class RLPStream; -class TransactionQueue; -class BlockQueue; -using eth::Guard; +namespace p2p +{ /** * @brief The Host class @@ -157,3 +156,4 @@ protected: }; } +} diff --git a/libp2p/HostCapability.cpp b/libp2p/HostCapability.cpp index 5d81e1213..a3a47cd5c 100644 --- a/libp2p/HostCapability.cpp +++ b/libp2p/HostCapability.cpp @@ -24,8 +24,8 @@ #include "Session.h" #include "Host.h" using namespace std; -using namespace eth; -using namespace p2p; +using namespace dev; +using namespace dev::p2p; void HostCapabilityFace::seal(bytes& _b) { diff --git a/libp2p/HostCapability.h b/libp2p/HostCapability.h index e4b3d403b..4b6d38f64 100644 --- a/libp2p/HostCapability.h +++ b/libp2p/HostCapability.h @@ -25,6 +25,9 @@ #include "Common.h" +namespace dev +{ + namespace p2p { @@ -68,3 +71,5 @@ protected: }; } + +} diff --git a/libp2p/Session.cpp b/libp2p/Session.cpp index f2b5a1d01..cdd168db4 100644 --- a/libp2p/Session.cpp +++ b/libp2p/Session.cpp @@ -27,10 +27,10 @@ #include "Host.h" #include "Capability.h" using namespace std; -using namespace eth; -using namespace p2p; +using namespace dev; +using namespace dev::p2p; -#define clogS(X) eth::LogOutputStream(false) << "| " << std::setw(2) << m_socket.native_handle() << "] " +#define clogS(X) dev::LogOutputStream(false) << "| " << std::setw(2) << m_socket.native_handle() << "] " Session::Session(Host* _s, bi::tcp::socket _socket, bi::address _peerAddress, unsigned short _peerPort): m_server(_s), diff --git a/libp2p/Session.h b/libp2p/Session.h index 52628210c..a005e35ed 100644 --- a/libp2p/Session.h +++ b/libp2p/Session.h @@ -29,6 +29,9 @@ #include #include "Common.h" +namespace dev +{ + namespace p2p { @@ -98,7 +101,7 @@ private: std::chrono::steady_clock::time_point m_connect; std::chrono::steady_clock::time_point m_disconnect; - uint m_rating; + unsigned m_rating; std::map> m_capabilities; @@ -106,3 +109,4 @@ private: }; } +} diff --git a/libp2p/UPnP.cpp b/libp2p/UPnP.cpp index ce0286681..6b3ef4cc5 100644 --- a/libp2p/UPnP.cpp +++ b/libp2p/UPnP.cpp @@ -33,8 +33,8 @@ #include #include using namespace std; -using namespace eth; -using namespace p2p; +using namespace dev; +using namespace dev::p2p; UPnP::UPnP() { diff --git a/libp2p/UPnP.h b/libp2p/UPnP.h index 8f118bae4..0031298a6 100644 --- a/libp2p/UPnP.h +++ b/libp2p/UPnP.h @@ -29,6 +29,8 @@ struct UPNPUrls; struct IGDdatas; +namespace dev +{ namespace p2p { @@ -51,3 +53,4 @@ public: }; } +} diff --git a/libqethereum/QEthereum.cpp b/libqethereum/QEthereum.cpp index 96963f6ff..2fd94b228 100644 --- a/libqethereum/QEthereum.cpp +++ b/libqethereum/QEthereum.cpp @@ -10,48 +10,48 @@ using namespace std; // types -using eth::bytes; -using eth::bytesConstRef; -using eth::h160; -using eth::h256; -using eth::u160; -using eth::u256; -using eth::u256s; -using eth::Address; -using eth::BlockInfo; -using eth::Client; -using eth::Instruction; -using eth::KeyPair; -using eth::NodeMode; -using p2p::PeerInfo; -using eth::RLP; -using eth::Secret; -using eth::Transaction; +using dev::bytes; +using dev::bytesConstRef; +using dev::h160; +using dev::h256; +using dev::u160; +using dev::u256; +using dev::u256s; +using dev::RLP; +using dev::eth::Address; +using dev::eth::BlockInfo; +using dev::eth::Client; +using dev::eth::Instruction; +using dev::eth::KeyPair; +using dev::eth::NodeMode; +using dev::p2p::PeerInfo; +using dev::eth::Secret; +using dev::eth::Transaction; // functions -using eth::toHex; -using eth::disassemble; -using eth::formatBalance; -using eth::fromHex; -using eth::right160; -using eth::simpleDebugOut; -using eth::toLog2; -using eth::toString; -using eth::units; -using eth::operator +; +using dev::toHex; +using dev::fromHex; +using dev::right160; +using dev::simpleDebugOut; +using dev::toLog2; +using dev::toString; +using dev::operator+; +using dev::eth::disassemble; +using dev::eth::units; +using dev::eth::formatBalance; // vars -using eth::g_logPost; -using eth::g_logVerbosity; +using dev::g_logPost; +using dev::g_logVerbosity; -eth::bytes toBytes(QString const& _s) +dev::bytes toBytes(QString const& _s) { if (_s.startsWith("0x")) // Hex - return eth::fromHex(_s.mid(2).toStdString()); + return dev::fromHex(_s.mid(2).toStdString()); else if (!_s.contains(QRegExp("[^0-9]"))) // Decimal - return eth::toCompactBigEndian(eth::bigint(_s.toStdString())); + return dev::toCompactBigEndian(dev::bigint(_s.toStdString())); else { // Binary @@ -62,12 +62,12 @@ eth::bytes toBytes(QString const& _s) QString padded(QString const& _s, unsigned _l, unsigned _r) { - eth::bytes b = toBytes(_s); + dev::bytes b = toBytes(_s); while (b.size() < _l) b.insert(b.begin(), 0); while (b.size() < _r) b.push_back(0); - return asQString(eth::asBytes(eth::asString(b).substr(b.size() - max(_l, _r)))); + return asQString(dev::asBytes(dev::asString(b).substr(b.size() - max(_l, _r)))); } //"0xff".bin().unbin() @@ -89,7 +89,8 @@ QString unpadded(QString _s) return _s; } -QEthereum::QEthereum(QObject* _p, Client* _c, QList _accounts): QObject(_p), m_client(_c), m_accounts(_accounts) +QEthereum::QEthereum(QObject* _p, Client* _c, QList _accounts): + QObject(_p), m_client(_c), m_accounts(_accounts) { // required to prevent crash on osx when performing addto/evaluatejavascript calls moveToThread(_p->thread()); @@ -126,27 +127,27 @@ Client* QEthereum::client() const QString QEthereum::lll(QString _s) const { - return toQJS(eth::compileLLL(_s.toStdString())); + return toQJS(dev::eth::compileLLL(_s.toStdString())); } QString QEthereum::sha3(QString _s) const { - return toQJS(eth::sha3(toBytes(_s))); + return toQJS(dev::eth::sha3(toBytes(_s))); } QString QEthereum::sha3(QString _s1, QString _s2) const { - return toQJS(eth::sha3(asBytes(padded(_s1, 32)) + asBytes(padded(_s2, 32)))); + return toQJS(dev::eth::sha3(asBytes(padded(_s1, 32)) + asBytes(padded(_s2, 32)))); } QString QEthereum::sha3(QString _s1, QString _s2, QString _s3) const { - return toQJS(eth::sha3(asBytes(padded(_s1, 32)) + asBytes(padded(_s2, 32)) + asBytes(padded(_s3, 32)))); + return toQJS(dev::eth::sha3(asBytes(padded(_s1, 32)) + asBytes(padded(_s2, 32)) + asBytes(padded(_s3, 32)))); } QString QEthereum::sha3old(QString _s) const { - return toQJS(eth::sha3(asBytes(_s))); + return toQJS(dev::eth::sha3(asBytes(_s))); } QString QEthereum::offset(QString _s, int _i) const @@ -254,9 +255,9 @@ double QEthereum::countAt(QString _a, int _block) const return m_client ? (double)(uint64_t)client()->countAt(toAddress(_a), _block) : 0; } -static eth::MessageFilter toMessageFilter(QString _json) +static dev::eth::MessageFilter toMessageFilter(QString _json) { - eth::MessageFilter filter; + dev::eth::MessageFilter filter; QJsonObject f = QJsonDocument::fromJson(_json.toUtf8()).object(); if (f.contains("earliest")) @@ -333,21 +334,21 @@ static TransactionSkeleton toTransaction(QString _json) ret.data = toBytes(f["code"].toString()); else if (f["data"].isArray()) for (auto i: f["data"].toArray()) - eth::operator +=(ret.data, asBytes(padded(i.toString(), 32))); + dev::operator +=(ret.data, asBytes(padded(i.toString(), 32))); else if (f["code"].isArray()) for (auto i: f["code"].toArray()) - eth::operator +=(ret.data, asBytes(padded(i.toString(), 32))); + dev::operator +=(ret.data, asBytes(padded(i.toString(), 32))); else if (f["dataclose"].isArray()) for (auto i: f["dataclose"].toArray()) - eth::operator +=(ret.data, toBytes(i.toString())); + dev::operator +=(ret.data, toBytes(i.toString())); } return ret; } -static QString toJson(eth::PastMessages const& _pms) +static QString toJson(dev::eth::PastMessages const& _pms) { QJsonArray jsonArray; - for (eth::PastMessage const& t: _pms) + for (dev::eth::PastMessage const& t: _pms) { QJsonObject v; v["input"] = ::fromBinary(t.input); @@ -441,7 +442,7 @@ QString QEthereum::doTransact(QString _json) t.from = b.secret(); } if (!t.gasPrice) - t.gasPrice = 10 * eth::szabo; + t.gasPrice = 10 * dev::eth::szabo; if (!t.gas) t.gas = min(client()->gasLimitRemaining(), client()->balanceAt(KeyPair(t.from).address()) / t.gasPrice); if (t.to) @@ -462,7 +463,7 @@ QString QEthereum::doCall(QString _json) if (!t.from && m_accounts.size()) t.from = m_accounts[0].secret(); if (!t.gasPrice) - t.gasPrice = 10 * eth::szabo; + t.gasPrice = 10 * dev::eth::szabo; if (!t.gas) t.gas = client()->balanceAt(KeyPair(t.from).address()) / t.gasPrice; bytes out = client()->call(t.from, t.value, t.to, t.data, t.gas, t.gasPrice); @@ -475,9 +476,9 @@ unsigned QEthereum::newWatch(QString _json) return (unsigned)-1; unsigned ret; if (_json == "chain") - ret = m_client->installWatch(eth::ChainChangedFilter); + ret = m_client->installWatch(dev::eth::ChainChangedFilter); else if (_json == "pending") - ret = m_client->installWatch(eth::PendingChangedFilter); + ret = m_client->installWatch(dev::eth::PendingChangedFilter); else ret = m_client->installWatch(toMessageFilter(_json)); m_watches.push_back(ret); diff --git a/libqethereum/QEthereum.h b/libqethereum/QEthereum.h index 645c91740..41451118e 100644 --- a/libqethereum/QEthereum.h +++ b/libqethereum/QEthereum.h @@ -6,26 +6,26 @@ #include #include -namespace eth { +namespace dev { namespace eth { class Client; class State; -} +}} class QJSEngine; class QWebFrame; class QEthereum; -inline eth::bytes asBytes(QString const& _s) +inline dev::bytes asBytes(QString const& _s) { - eth::bytes ret; + dev::bytes ret; ret.reserve(_s.size()); for (QChar c: _s) ret.push_back(c.cell()); return ret; } -inline QString asQString(eth::bytes const& _s) +inline QString asQString(dev::bytes const& _s) { QString ret; ret.reserve(_s.size()); @@ -34,34 +34,34 @@ inline QString asQString(eth::bytes const& _s) return ret; } -eth::bytes toBytes(QString const& _s); +dev::bytes toBytes(QString const& _s); QString padded(QString const& _s, unsigned _l, unsigned _r); QString padded(QString const& _s, unsigned _l); QString unpadded(QString _s); -template eth::FixedHash toFixed(QString const& _s) +template dev::FixedHash toFixed(QString const& _s) { if (_s.startsWith("0x")) // Hex - return eth::FixedHash(_s.mid(2).toStdString()); + return dev::FixedHash(_s.mid(2).toStdString()); else if (!_s.contains(QRegExp("[^0-9]"))) // Decimal - return (typename eth::FixedHash::Arith)(_s.toStdString()); + return (typename dev::FixedHash::Arith)(_s.toStdString()); else // Binary - return eth::FixedHash(asBytes(padded(_s, N))); + return dev::FixedHash(asBytes(padded(_s, N))); } template inline boost::multiprecision::number> toInt(QString const& _s); -inline eth::Address toAddress(QString const& _s) { return toFixed<20>(_s); } -inline eth::Secret toSecret(QString const& _s) { return toFixed<32>(_s); } -inline eth::u256 toU256(QString const& _s) { return toInt<32>(_s); } +inline dev::eth::Address toAddress(QString const& _s) { return toFixed<20>(_s); } +inline dev::eth::Secret toSecret(QString const& _s) { return toFixed<32>(_s); } +inline dev::u256 toU256(QString const& _s) { return toInt<32>(_s); } -template QString toQJS(eth::FixedHash const& _h) { return QString::fromStdString("0x" + toHex(_h.ref())); } -template QString toQJS(boost::multiprecision::number> const& _n) { return QString::fromStdString("0x" + eth::toHex(eth::toCompactBigEndian(_n))); } -inline QString toQJS(eth::bytes const& _n) { return "0x" + QString::fromStdString(eth::toHex(_n)); } +template QString toQJS(dev::FixedHash const& _h) { return QString::fromStdString("0x" + toHex(_h.ref())); } +template QString toQJS(boost::multiprecision::number> const& _n) { return QString::fromStdString("0x" + dev::toHex(dev::toCompactBigEndian(_n))); } +inline QString toQJS(dev::bytes const& _n) { return "0x" + QString::fromStdString(dev::toHex(_n)); } inline QString toBinary(QString const& _s) { @@ -70,23 +70,23 @@ inline QString toBinary(QString const& _s) inline QString toDecimal(QString const& _s) { - return QString::fromStdString(eth::toString(toU256(_s))); + return QString::fromStdString(dev::toString(toU256(_s))); } inline double fromFixed(QString const& _s) { - return (double)toU256(_s) / (double)(eth::u256(1) << 128); + return (double)toU256(_s) / (double)(dev::u256(1) << 128); } inline QString toFixed(double _s) { - return toQJS(eth::u256(_s * (double)(eth::u256(1) << 128))); + return toQJS(dev::u256(_s * (double)(dev::u256(1) << 128))); } -inline QString fromBinary(eth::bytes _s, unsigned _padding = 32) +inline QString fromBinary(dev::bytes _s, unsigned _padding = 32) { _s.resize(std::max(_s.size(), _padding)); - return QString::fromStdString("0x" + eth::toHex(_s)); + return QString::fromStdString("0x" + dev::toHex(_s)); } inline QString fromBinary(QString const& _s, unsigned _padding = 32) @@ -99,16 +99,16 @@ class QEthereum: public QObject Q_OBJECT public: - QEthereum(QObject* _p, eth::Client* _c, QList _accounts); + QEthereum(QObject* _p, dev::eth::Client* _c, QList _accounts); virtual ~QEthereum(); - eth::Client* client() const; - void setClient(eth::Client* _c) { m_client = _c; } + dev::eth::Client* client() const; + void setClient(dev::eth::Client* _c) { m_client = _c; } /// Call when the client() is going to be deleted to make this object useless but safe. void clientDieing(); - void setAccounts(QList _l) { m_accounts = _l; keysChanged(); } + void setAccounts(QList _l) { m_accounts = _l; keysChanged(); } Q_INVOKABLE QString ethTest() const { return "Hello world!"; } Q_INVOKABLE QEthereum* self() { return this; } @@ -134,15 +134,15 @@ public: Q_INVOKABLE QString toFixed(double _d) const { return ::toFixed(_d); } // [NEW API] - Use this instead. - Q_INVOKABLE QString/*eth::u256*/ balanceAt(QString/*eth::Address*/ _a, int _block) const; - Q_INVOKABLE double countAt(QString/*eth::Address*/ _a, int _block) const; - Q_INVOKABLE QString/*eth::u256*/ stateAt(QString/*eth::Address*/ _a, QString/*eth::u256*/ _p, int _block) const; - Q_INVOKABLE QString/*eth::u256*/ codeAt(QString/*eth::Address*/ _a, int _block) const; + Q_INVOKABLE QString/*dev::u256*/ balanceAt(QString/*dev::Address*/ _a, int _block) const; + Q_INVOKABLE double countAt(QString/*dev::Address*/ _a, int _block) const; + Q_INVOKABLE QString/*dev::u256*/ stateAt(QString/*dev::Address*/ _a, QString/*dev::u256*/ _p, int _block) const; + Q_INVOKABLE QString/*dev::u256*/ codeAt(QString/*dev::Address*/ _a, int _block) const; - Q_INVOKABLE QString/*eth::u256*/ balanceAt(QString/*eth::Address*/ _a) const; - Q_INVOKABLE double countAt(QString/*eth::Address*/ _a) const; - Q_INVOKABLE QString/*eth::u256*/ stateAt(QString/*eth::Address*/ _a, QString/*eth::u256*/ _p) const; - Q_INVOKABLE QString/*eth::u256*/ codeAt(QString/*eth::Address*/ _a) const; + Q_INVOKABLE QString/*dev::u256*/ balanceAt(QString/*dev::Address*/ _a) const; + Q_INVOKABLE double countAt(QString/*dev::Address*/ _a) const; + Q_INVOKABLE QString/*dev::u256*/ stateAt(QString/*dev::Address*/ _a, QString/*dev::u256*/ _p) const; + Q_INVOKABLE QString/*dev::u256*/ codeAt(QString/*dev::Address*/ _a) const; Q_INVOKABLE QString/*json*/ getMessages(QString _attribs/*json*/) const; @@ -159,20 +159,20 @@ public: bool isListening() const; bool isMining() const; - QString/*eth::Address*/ coinbase() const; - QString/*eth::u256*/ gasPrice() const { return toQJS(10 * eth::szabo); } - QString/*eth::u256*/ number() const; + QString/*dev::Address*/ coinbase() const; + QString/*dev::u256*/ gasPrice() const { return toQJS(10 * dev::eth::szabo); } + QString/*dev::u256*/ number() const; int getDefault() const; - QString/*eth::KeyPair*/ key() const; - QStringList/*list of eth::KeyPair*/ keys() const; - QString/*eth::Address*/ account() const; - QStringList/*list of eth::Address*/ accounts() const; + QString/*dev::KeyPair*/ key() const; + QStringList/*list of dev::eth::KeyPair*/ keys() const; + QString/*dev::Address*/ account() const; + QStringList/*list of dev::eth::Address*/ accounts() const; unsigned peerCount() const; public slots: - void setCoinbase(QString/*eth::Address*/); + void setCoinbase(QString/*dev::Address*/); void setMining(bool _l); void setListening(bool _l); void setDefault(int _block); @@ -199,9 +199,9 @@ private: Q_PROPERTY(unsigned peerCount READ peerCount NOTIFY miningChanged) Q_PROPERTY(int defaultBlock READ getDefault WRITE setDefault) - eth::Client* m_client; + dev::eth::Client* m_client; std::vector m_watches; - QList m_accounts; + QList m_accounts; }; #define QETH_INSTALL_JS_NAMESPACE(frame, eth, env) [frame, eth, env]() \ @@ -232,12 +232,12 @@ private: template inline boost::multiprecision::number> toInt(QString const& _s) { if (_s.startsWith("0x")) - return eth::fromBigEndian>>(eth::fromHex(_s.toStdString().substr(2))); + return dev::fromBigEndian>>(dev::fromHex(_s.toStdString().substr(2))); else if (!_s.contains(QRegExp("[^0-9]"))) // Hex or Decimal return boost::multiprecision::number>(_s.toStdString()); else // Binary - return eth::fromBigEndian>>(asBytes(padded(_s, N))); + return dev::fromBigEndian>>(asBytes(padded(_s, N))); } diff --git a/libqethereum/QmlEthereum.cpp b/libqethereum/QmlEthereum.cpp index 3896382fd..b718bfb7b 100644 --- a/libqethereum/QmlEthereum.cpp +++ b/libqethereum/QmlEthereum.cpp @@ -13,42 +13,42 @@ using namespace std; // types -using eth::bytes; -using eth::bytesConstRef; -using eth::h160; -using eth::h256; -using eth::u160; -using eth::u256; -using eth::u256s; -using eth::Address; -using eth::BlockInfo; -using eth::Client; -using eth::Instruction; -using eth::KeyPair; -using eth::NodeMode; -using p2p::PeerInfo; -using eth::RLP; -using eth::Secret; -using eth::Transaction; +using dev::bytes; +using dev::bytesConstRef; +using dev::h160; +using dev::h256; +using dev::u160; +using dev::u256; +using dev::u256s; +using dev::RLP; +using dev::eth::Address; +using dev::eth::BlockInfo; +using dev::eth::Client; +using dev::eth::Instruction; +using dev::eth::KeyPair; +using dev::eth::NodeMode; +using dev::eth::Secret; +using dev::eth::Transaction; +using dev::p2p::PeerInfo; // functions -using eth::toHex; -using eth::disassemble; -using eth::formatBalance; -using eth::fromHex; -using eth::right160; -using eth::simpleDebugOut; -using eth::toLog2; -using eth::toString; -using eth::units; +using dev::toHex; +using dev::fromHex; +using dev::right160; +using dev::simpleDebugOut; +using dev::toLog2; +using dev::toString; +using dev::eth::disassemble; +using dev::eth::formatBalance; +using dev::eth::units; // vars -using eth::g_logPost; -using eth::g_logVerbosity; +using dev::g_logPost; +using dev::g_logVerbosity; // Horrible global for the mainwindow. Needed for the QmlEthereums to find the Main window which acts as multiplexer for now. // Can get rid of this once we've sorted out ITC for signalling & multiplexed querying. -eth::Client* g_qmlClient; +dev::eth::Client* g_qmlClient; QObject* g_qmlMain; QmlAccount::QmlAccount(QObject*) @@ -72,7 +72,7 @@ void QmlAccount::setEthereum(QmlEthereum* _eth) // changed(); } -eth::u256 QmlAccount::balance() const +dev::u256 QmlAccount::balance() const { if (m_eth) return m_eth->balanceAt(m_address); diff --git a/libqethereum/QmlEthereum.h b/libqethereum/QmlEthereum.h index 2554dd02f..59dff6f5e 100644 --- a/libqethereum/QmlEthereum.h +++ b/libqethereum/QmlEthereum.h @@ -7,22 +7,22 @@ #include #include -namespace eth { +namespace dev { namespace eth { class Client; class State; -} +}} class QQmlEngine; class QmlAccount; class QmlEthereum; -extern eth::Client* g_qmlClient; +extern dev::eth::Client* g_qmlClient; extern QObject* g_qmlMain; -Q_DECLARE_METATYPE(eth::u256) -Q_DECLARE_METATYPE(eth::Address) -Q_DECLARE_METATYPE(eth::Secret) -Q_DECLARE_METATYPE(eth::KeyPair) +Q_DECLARE_METATYPE(dev::u256) +Q_DECLARE_METATYPE(dev::eth::Address) +Q_DECLARE_METATYPE(dev::eth::Secret) +Q_DECLARE_METATYPE(dev::eth::KeyPair) Q_DECLARE_METATYPE(QmlAccount*) Q_DECLARE_METATYPE(QmlEthereum*) @@ -33,28 +33,28 @@ class QmlU256Helper: public QObject public: QmlU256Helper(QObject* _p = nullptr): QObject(_p) {} - Q_INVOKABLE eth::u256 add(eth::u256 _a, eth::u256 _b) const { return _a + _b; } - Q_INVOKABLE eth::u256 sub(eth::u256 _a, eth::u256 _b) const { return _a - _b; } - Q_INVOKABLE eth::u256 mul(eth::u256 _a, int _b) const { return _a * _b; } - Q_INVOKABLE eth::u256 mul(int _a, eth::u256 _b) const { return _a * _b; } - Q_INVOKABLE eth::u256 div(eth::u256 _a, int _b) const { return _a / _b; } - - Q_INVOKABLE eth::u256 wei(double _s) const { return (eth::u256)_s; } - Q_INVOKABLE eth::u256 szabo(double _s) const { return (eth::u256)(_s * (double)eth::szabo); } - Q_INVOKABLE eth::u256 finney(double _s) const { return (eth::u256)(_s * (double)eth::finney); } - Q_INVOKABLE eth::u256 ether(double _s) const { return (eth::u256)(_s * (double)eth::ether); } - Q_INVOKABLE eth::u256 wei(unsigned _s) const { return (eth::u256)_s; } - Q_INVOKABLE eth::u256 szabo(unsigned _s) const { return (eth::u256)(_s * eth::szabo); } - Q_INVOKABLE eth::u256 finney(unsigned _s) const { return (eth::u256)(_s * eth::finney); } - Q_INVOKABLE eth::u256 ether(unsigned _s) const { return (eth::u256)(_s * eth::ether); } - Q_INVOKABLE double toWei(eth::u256 _t) const { return (double)_t; } - Q_INVOKABLE double toSzabo(eth::u256 _t) const { return toWei(_t) / (double)eth::szabo; } - Q_INVOKABLE double toFinney(eth::u256 _t) const { return toWei(_t) / (double)eth::finney; } - Q_INVOKABLE double toEther(eth::u256 _t) const { return toWei(_t) / (double)eth::ether; } - - Q_INVOKABLE double value(eth::u256 _t) const { return (double)_t; } - - Q_INVOKABLE QString stringOf(eth::u256 _t) const { return QString::fromStdString(eth::formatBalance(_t)); } + Q_INVOKABLE dev::u256 add(dev::u256 _a, dev::u256 _b) const { return _a + _b; } + Q_INVOKABLE dev::u256 sub(dev::u256 _a, dev::u256 _b) const { return _a - _b; } + Q_INVOKABLE dev::u256 mul(dev::u256 _a, int _b) const { return _a * _b; } + Q_INVOKABLE dev::u256 mul(int _a, dev::u256 _b) const { return _a * _b; } + Q_INVOKABLE dev::u256 div(dev::u256 _a, int _b) const { return _a / _b; } + + Q_INVOKABLE dev::u256 wei(double _s) const { return (dev::u256)_s; } + Q_INVOKABLE dev::u256 szabo(double _s) const { return (dev::u256)(_s * (double)dev::eth::szabo); } + Q_INVOKABLE dev::u256 finney(double _s) const { return (dev::u256)(_s * (double)dev::eth::finney); } + Q_INVOKABLE dev::u256 ether(double _s) const { return (dev::u256)(_s * (double)dev::eth::ether); } + Q_INVOKABLE dev::u256 wei(unsigned _s) const { return (dev::u256)_s; } + Q_INVOKABLE dev::u256 szabo(unsigned _s) const { return (dev::u256)(_s * dev::eth::szabo); } + Q_INVOKABLE dev::u256 finney(unsigned _s) const { return (dev::u256)(_s * dev::eth::finney); } + Q_INVOKABLE dev::u256 ether(unsigned _s) const { return (dev::u256)(_s * dev::eth::ether); } + Q_INVOKABLE double toWei(dev::u256 _t) const { return (double)_t; } + Q_INVOKABLE double toSzabo(dev::u256 _t) const { return toWei(_t) / (double)dev::eth::szabo; } + Q_INVOKABLE double toFinney(dev::u256 _t) const { return toWei(_t) / (double)dev::eth::finney; } + Q_INVOKABLE double toEther(dev::u256 _t) const { return toWei(_t) / (double)dev::eth::ether; } + + Q_INVOKABLE double value(dev::u256 _t) const { return (double)_t; } + + Q_INVOKABLE QString stringOf(dev::u256 _t) const { return QString::fromStdString(dev::eth::formatBalance(_t)); } }; class QmlKeyHelper: public QObject @@ -64,16 +64,16 @@ class QmlKeyHelper: public QObject public: QmlKeyHelper(QObject* _p = nullptr): QObject(_p) {} - Q_INVOKABLE eth::KeyPair create() const { return eth::KeyPair::create(); } - Q_INVOKABLE eth::Address address(eth::KeyPair _p) const { return _p.address(); } - Q_INVOKABLE eth::Secret secret(eth::KeyPair _p) const { return _p.secret(); } - Q_INVOKABLE eth::KeyPair keypair(eth::Secret _k) const { return eth::KeyPair(_k); } + Q_INVOKABLE dev::eth::KeyPair create() const { return dev::eth::KeyPair::create(); } + Q_INVOKABLE dev::eth::Address address(dev::eth::KeyPair _p) const { return _p.address(); } + Q_INVOKABLE dev::eth::Secret secret(dev::eth::KeyPair _p) const { return _p.secret(); } + Q_INVOKABLE dev::eth::KeyPair keypair(dev::eth::Secret _k) const { return dev::eth::KeyPair(_k); } - Q_INVOKABLE bool isNull(eth::Address _a) const { return !_a; } + Q_INVOKABLE bool isNull(dev::eth::Address _a) const { return !_a; } - Q_INVOKABLE eth::Address addressOf(QString _s) const { return eth::Address(_s.toStdString()); } - Q_INVOKABLE QString stringOf(eth::Address _a) const { return QString::fromStdString(eth::toHex(_a.asArray())); } - Q_INVOKABLE QString toAbridged(eth::Address _a) const { return QString::fromStdString(_a.abridged()); } + Q_INVOKABLE dev::eth::Address addressOf(QString _s) const { return dev::eth::Address(_s.toStdString()); } + Q_INVOKABLE QString stringOf(dev::eth::Address _a) const { return QString::fromStdString(dev::toHex(_a.asArray())); } + Q_INVOKABLE QString toAbridged(dev::eth::Address _a) const { return QString::fromStdString(_a.abridged()); } }; class QmlAccount: public QObject @@ -85,16 +85,16 @@ public: virtual ~QmlAccount(); Q_INVOKABLE QmlEthereum* ethereum() const { return m_eth; } - Q_INVOKABLE eth::u256 balance() const; + Q_INVOKABLE dev::u256 balance() const; Q_INVOKABLE double txCount() const; Q_INVOKABLE bool isContract() const; - Q_INVOKABLE eth::Address address() const { return m_address; } + Q_INVOKABLE dev::eth::Address address() const { return m_address; } // TODO: past transactions models. public slots: void setEthereum(QmlEthereum* _eth); - void setAddress(eth::Address _a) { m_address = _a; } + void setAddress(dev::eth::Address _a) { m_address = _a; } signals: void changed(); @@ -102,12 +102,12 @@ signals: private: QmlEthereum* m_eth = nullptr; - eth::Address m_address; + dev::eth::Address m_address; - Q_PROPERTY(eth::u256 balance READ balance NOTIFY changed STORED false) + Q_PROPERTY(dev::u256 balance READ balance NOTIFY changed STORED false) Q_PROPERTY(double txCount READ txCount NOTIFY changed STORED false) Q_PROPERTY(bool isContract READ isContract NOTIFY changed STORED false) - Q_PROPERTY(eth::Address address READ address WRITE setAddress NOTIFY changed) + Q_PROPERTY(dev::eth::Address address READ address WRITE setAddress NOTIFY changed) Q_PROPERTY(QmlEthereum* ethereum READ ethereum WRITE setEthereum NOTIFY ethChanged) }; @@ -119,28 +119,28 @@ public: QmlEthereum(QObject* _p = nullptr); virtual ~QmlEthereum(); - eth::Client* client() const; + dev::eth::Client* client() const; static QObject* constructU256Helper(QQmlEngine*, QJSEngine*) { return new QmlU256Helper; } static QObject* constructKeyHelper(QQmlEngine*, QJSEngine*) { return new QmlKeyHelper; } - Q_INVOKABLE eth::Address coinbase() const; + Q_INVOKABLE dev::eth::Address coinbase() const; Q_INVOKABLE bool isListening() const; Q_INVOKABLE bool isMining() const; - Q_INVOKABLE eth::u256 balanceAt(eth::Address _a) const; - Q_INVOKABLE double txCountAt(eth::Address _a) const; - Q_INVOKABLE bool isContractAt(eth::Address _a) const; + Q_INVOKABLE dev::u256 balanceAt(dev::eth::Address _a) const; + Q_INVOKABLE double txCountAt(dev::eth::Address _a) const; + Q_INVOKABLE bool isContractAt(dev::eth::Address _a) const; Q_INVOKABLE unsigned peerCount() const; Q_INVOKABLE QmlEthereum* self() { return this; } public slots: - void transact(eth::Secret _secret, eth::Address _dest, eth::u256 _amount, eth::u256 _gasPrice, eth::u256 _gas, QByteArray _data); - void transact(eth::Secret _secret, eth::u256 _amount, eth::u256 _gasPrice, eth::u256 _gas, QByteArray _init); - void setCoinbase(eth::Address); + void transact(dev::eth::Secret _secret, dev::eth::Address _dest, dev::u256 _amount, dev::u256 _gasPrice, dev::u256 _gas, QByteArray _data); + void transact(dev::eth::Secret _secret, dev::u256 _amount, dev::u256 _gasPrice, dev::u256 _gas, QByteArray _init); + void setCoinbase(dev::eth::Address); void setMining(bool _l); void setListening(bool _l); @@ -151,7 +151,7 @@ signals: // void miningChanged(); private: - Q_PROPERTY(eth::Address coinbase READ coinbase WRITE setCoinbase NOTIFY coinbaseChanged) + Q_PROPERTY(dev::eth::Address coinbase READ coinbase WRITE setCoinbase NOTIFY coinbaseChanged) Q_PROPERTY(bool listening READ isListening WRITE setListening) Q_PROPERTY(bool mining READ isMining WRITE setMining) }; @@ -167,8 +167,8 @@ class U256Helper: public QObject public: U256Helper(QObject* _p = nullptr): QObject(_p) {} - static eth::u256 in(QVariant const& _s) { return to(_s); } - static QVariant out(eth::u256 const& _s) { return toQJS(_s); } + static dev::u256 in(QVariant const& _s) { return to(_s); } + static QVariant out(dev::u256 const& _s) { return toQJS(_s); } Q_INVOKABLE QVariant add(QVariant _a, QVariant _b) const { return out(in(_a) + in(_b)); } Q_INVOKABLE QVariant sub(QVariant _a, QVariant _b) const { return out(in(_a) - in(_b)); } @@ -176,32 +176,32 @@ public: Q_INVOKABLE QVariant mul(int _a, QVariant _b) const { return out(in(_a) * in(_b)); } Q_INVOKABLE QVariant div(QVariant _a, int _b) const { return out(in(_a) / in(_b)); } - Q_INVOKABLE QVariant wei(double _s) const { return out(eth::u256(_s)); } - Q_INVOKABLE QVariant szabo(double _s) const { return out(eth::u256(_s * (double)eth::szabo)); } - Q_INVOKABLE QVariant finney(double _s) const { return out(eth::u256(_s * (double)eth::finney)); } - Q_INVOKABLE QVariant ether(double _s) const { return out(eth::u256(_s * (double)eth::ether)); } + Q_INVOKABLE QVariant wei(double _s) const { return out(dev::u256(_s)); } + Q_INVOKABLE QVariant szabo(double _s) const { return out(dev::u256(_s * (double)dev::eth::szabo)); } + Q_INVOKABLE QVariant finney(double _s) const { return out(dev::u256(_s * (double)dev::eth::finney)); } + Q_INVOKABLE QVariant ether(double _s) const { return out(dev::u256(_s * (double)dev::eth::ether)); } Q_INVOKABLE QVariant wei(unsigned _s) const { return value(_s); } - Q_INVOKABLE QVariant szabo(unsigned _s) const { return out(eth::u256(_s) * eth::szabo); } - Q_INVOKABLE QVariant finney(unsigned _s) const { return out(eth::u256(_s) * eth::finney); } - Q_INVOKABLE QVariant ether(unsigned _s) const { return out(eth::u256(_s) * eth::ether); } + Q_INVOKABLE QVariant szabo(unsigned _s) const { return out(dev::u256(_s) * dev::eth::szabo); } + Q_INVOKABLE QVariant finney(unsigned _s) const { return out(dev::u256(_s) * dev::eth::finney); } + Q_INVOKABLE QVariant ether(unsigned _s) const { return out(dev::u256(_s) * dev::eth::ether); } Q_INVOKABLE double toWei(QVariant _t) const { return toValue(_t); } - Q_INVOKABLE double toSzabo(QVariant _t) const { return toWei(_t) / (double)eth::szabo; } - Q_INVOKABLE double toFinney(QVariant _t) const { return toWei(_t) / (double)eth::finney; } - Q_INVOKABLE double toEther(QVariant _t) const { return toWei(_t) / (double)eth::ether; } + Q_INVOKABLE double toSzabo(QVariant _t) const { return toWei(_t) / (double)dev::eth::szabo; } + Q_INVOKABLE double toFinney(QVariant _t) const { return toWei(_t) / (double)dev::eth::finney; } + Q_INVOKABLE double toEther(QVariant _t) const { return toWei(_t) / (double)dev::eth::ether; } - Q_INVOKABLE QVariant value(unsigned _s) const { return out(eth::u256(_s)); } + Q_INVOKABLE QVariant value(unsigned _s) const { return out(dev::u256(_s)); } Q_INVOKABLE double toValue(QVariant _t) const { return (double)in(_t); } - Q_INVOKABLE QString ethOf(QVariant _t) const { return QString::fromStdString(eth::formatBalance(in(_t))); } - Q_INVOKABLE QString stringOf(QVariant _t) const { return QString::fromStdString(eth::toString(in(_t))); } + Q_INVOKABLE QString ethOf(QVariant _t) const { return QString::fromStdString(dev::eth::formatBalance(in(_t))); } + Q_INVOKABLE QString stringOf(QVariant _t) const { return QString::fromStdString(dev::eth::toString(in(_t))); } - Q_INVOKABLE QByteArray bytesOf(QVariant _t) const { eth::h256 b = in(_t); return QByteArray((char const*)&b, sizeof(eth::h256)); } - Q_INVOKABLE QVariant fromHex(QString _s) const { return out((eth::u256)eth::h256(_s.toStdString())); } + Q_INVOKABLE QByteArray bytesOf(QVariant _t) const { dev::h256 b = in(_t); return QByteArray((char const*)&b, sizeof(dev::h256)); } + Q_INVOKABLE QVariant fromHex(QString _s) const { return out((dev::u256)dev::h256(_s.toStdString())); } - Q_INVOKABLE QVariant fromAddress(QVariant/*eth::Address*/ _a) const { return out((eth::u160)to(_a)); } - Q_INVOKABLE QVariant toAddress(QVariant/*eth::Address*/ _a) const { return toQJS((eth::u160)in(_a)); } + Q_INVOKABLE QVariant fromAddress(QVariant/*dev::eth::Address*/ _a) const { return out((dev::eth::u160)to(_a)); } + Q_INVOKABLE QVariant toAddress(QVariant/*dev::eth::Address*/ _a) const { return toQJS((dev::eth::u160)in(_a)); } - Q_INVOKABLE bool isNull(QVariant/*eth::Address*/ _a) const { return !in(_a); } + Q_INVOKABLE bool isNull(QVariant/*dev::eth::Address*/ _a) const { return !in(_a); } }; class KeyHelper: public QObject @@ -211,19 +211,19 @@ class KeyHelper: public QObject public: KeyHelper(QObject* _p = nullptr): QObject(_p) {} - static eth::Address in(QVariant const& _s) { return to(_s); } - static QVariant out(eth::Address const& _s) { return toQJS(_s); } + static dev::eth::Address in(QVariant const& _s) { return to(_s); } + static QVariant out(dev::eth::Address const& _s) { return toQJS(_s); } - Q_INVOKABLE QVariant/*eth::KeyPair*/ create() const { return toQJS(eth::KeyPair::create()); } - Q_INVOKABLE QVariant/*eth::Address*/ address(QVariant/*eth::KeyPair*/ _p) const { return out(to(_p).address()); } - Q_INVOKABLE QVariant/*eth::Secret*/ secret(QVariant/*eth::KeyPair*/ _p) const { return toQJS(to(_p).secret()); } - Q_INVOKABLE QVariant/*eth::KeyPair*/ keypair(QVariant/*eth::Secret*/ _k) const { return toQJS(eth::KeyPair(to(_k))); } + Q_INVOKABLE QVariant/*dev::eth::KeyPair*/ create() const { return toQJS(dev::eth::KeyPair::create()); } + Q_INVOKABLE QVariant/*dev::eth::Address*/ address(QVariant/*dev::eth::KeyPair*/ _p) const { return out(to(_p).address()); } + Q_INVOKABLE QVariant/*dev::eth::Secret*/ secret(QVariant/*dev::eth::KeyPair*/ _p) const { return toQJS(to(_p).secret()); } + Q_INVOKABLE QVariant/*dev::eth::KeyPair*/ keypair(QVariant/*dev::eth::Secret*/ _k) const { return toQJS(dev::eth::KeyPair(to(_k))); } - Q_INVOKABLE bool isNull(QVariant/*eth::Address*/ _a) const { return !in(_a); } + Q_INVOKABLE bool isNull(QVariant/*dev::eth::Address*/ _a) const { return !in(_a); } - Q_INVOKABLE QVariant/*eth::Address*/ addressOf(QString _s) const { return out(eth::Address(_s.toStdString())); } - Q_INVOKABLE QString stringOf(QVariant/*eth::Address*/ _a) const { return QString::fromStdString(eth::toHex(in(_a).asArray())); } - Q_INVOKABLE QString toAbridged(QVariant/*eth::Address*/ _a) const { return QString::fromStdString(in(_a).abridged()); } + Q_INVOKABLE QVariant/*dev::eth::Address*/ addressOf(QString _s) const { return out(dev::eth::Address(_s.toStdString())); } + Q_INVOKABLE QString stringOf(QVariant/*dev::eth::Address*/ _a) const { return QString::fromStdString(dev::eth::toHex(in(_a).asArray())); } + Q_INVOKABLE QString toAbridged(QVariant/*dev::eth::Address*/ _a) const { return QString::fromStdString(in(_a).abridged()); } }; @@ -276,8 +276,8 @@ public: { while (_s.size() < 32) _s.append((char)0); - eth::h256 ret((uint8_t const*)_s.data(), eth::h256::ConstructFromPointer); - return toQJS(ret); + dev::h256 ret((uint8_t const*)_s.data(), dev::h256::ConstructFromPointer); + return toQJS(ret); } }; #endif diff --git a/libwebthree/CMakeLists.txt b/libwebthree/CMakeLists.txt new file mode 100644 index 000000000..54bfd586d --- /dev/null +++ b/libwebthree/CMakeLists.txt @@ -0,0 +1,69 @@ +cmake_policy(SET CMP0015 NEW) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DSTATICLIB") + +aux_source_directory(. SRC_LIST) + +set(EXECUTABLE webthree) + +# set(CMAKE_INSTALL_PREFIX ../lib) +if(ETH_STATIC) + add_library(${EXECUTABLE} STATIC ${SRC_LIST}) +else() + add_library(${EXECUTABLE} SHARED ${SRC_LIST}) +endif() + +file(GLOB HEADERS "*.h") + +include_directories(..) + +target_link_libraries(${EXECUTABLE} ethereum) +target_link_libraries(${EXECUTABLE} evm) +target_link_libraries(${EXECUTABLE} lll) +target_link_libraries(${EXECUTABLE} whisper) +target_link_libraries(${EXECUTABLE} p2p) +target_link_libraries(${EXECUTABLE} ethcore) +target_link_libraries(${EXECUTABLE} secp256k1) +if(MINIUPNPC_LS) +target_link_libraries(${EXECUTABLE} ${MINIUPNPC_LS}) +endif() +target_link_libraries(${EXECUTABLE} ${LEVELDB_LS}) +target_link_libraries(${EXECUTABLE} ${CRYPTOPP_LS}) +target_link_libraries(${EXECUTABLE} gmp) + +if("${TARGET_PLATFORM}" STREQUAL "w64") + target_link_libraries(${EXECUTABLE} boost_system-mt-s) + target_link_libraries(${EXECUTABLE} boost_regex-mt-s) + target_link_libraries(${EXECUTABLE} boost_filesystem-mt-s) + target_link_libraries(${EXECUTABLE} boost_thread_win32-mt-s) + target_link_libraries(${EXECUTABLE} iphlpapi) + target_link_libraries(${EXECUTABLE} ws2_32) + target_link_libraries(${EXECUTABLE} mswsock) + target_link_libraries(${EXECUTABLE} shlwapi) +elseif (APPLE) + # Latest mavericks boost libraries only come with -mt + target_link_libraries(${EXECUTABLE} boost_system-mt) + target_link_libraries(${EXECUTABLE} boost_regex-mt) + target_link_libraries(${EXECUTABLE} boost_filesystem-mt) + target_link_libraries(${EXECUTABLE} boost_thread-mt) + find_package(Threads REQUIRED) + target_link_libraries(${EXECUTABLE} ${CMAKE_THREAD_LIBS_INIT}) +elseif (UNIX) + target_link_libraries(${EXECUTABLE} ${Boost_SYSTEM_LIBRARY}) + target_link_libraries(${EXECUTABLE} ${Boost_REGEX_LIBRARY}) + target_link_libraries(${EXECUTABLE} ${Boost_FILESYSTEM_LIBRARY}) + target_link_libraries(${EXECUTABLE} ${Boost_THREAD_LIBRARY}) + target_link_libraries(${EXECUTABLE} ${Boost_DATE_TIME_LIBRARY}) + target_link_libraries(${EXECUTABLE} ${CMAKE_THREAD_LIBS_INIT}) +else () + target_link_libraries(${EXECUTABLE} boost_system) + target_link_libraries(${EXECUTABLE} boost_regex) + target_link_libraries(${EXECUTABLE} boost_filesystem) + target_link_libraries(${EXECUTABLE} boost_thread) + find_package(Threads REQUIRED) + target_link_libraries(${EXECUTABLE} ${CMAKE_THREAD_LIBS_INIT}) +endif () + +install( TARGETS ${EXECUTABLE} ARCHIVE DESTINATION lib LIBRARY DESTINATION lib ) +install( FILES ${HEADERS} DESTINATION include/${EXECUTABLE} ) + diff --git a/libwebthree/Client.cpp b/libwebthree/Client.cpp new file mode 100644 index 000000000..f8b3ad538 --- /dev/null +++ b/libwebthree/Client.cpp @@ -0,0 +1,172 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file RawWebThree.cpp + * @author Gav Wood + * @date 2014 + */ + +#include "Client.h" + +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; +using namespace dev; +using namespace dev::p2p; +using namespace dev::eth; +using namespace dev::shh; + +RawWebThree::RawWebThree(std::string const& _clientVersion, std::string const& _dbPath, bool _forceClean): + m_clientVersion(_clientVersion) +{ + if (_dbPath.size()) + Defaults::setDBPath(_dbPath); +} + +RawWebThree::~RawWebThree() +{ + stopNetwork(); +} + +void RawWebThree::startNetwork(unsigned short _listenPort, std::string const& _seedHost, unsigned short _port, NodeMode _mode, unsigned _peers, string const& _publicIP, bool _upnp, u256 _networkId) +{ + static const char* c_threadName = "net"; + + { + UpgradableGuard l(x_net); + if (m_net.get()) + return; + { + UpgradeGuard ul(l); + + if (!m_workNet) + m_workNet.reset(new thread([&]() + { + setThreadName(c_threadName); + m_workNetState.store(Active, std::memory_order_release); + while (m_workNetState.load(std::memory_order_acquire) != Deleting) + workNet(); + m_workNetState.store(Deleted, std::memory_order_release); + })); + + try + { + m_net.reset(new Host(m_clientVersion, _listenPort, _publicIP, _upnp)); + } + catch (std::exception const&) + { + // Probably already have the port open. + cwarn << "Could not initialize with specified/default port. Trying system-assigned port"; + m_net.reset(new Host(m_clientVersion, 0, _publicIP, _upnp)); + } +/* if (_mode == NodeMode::Full) + m_net->registerCapability(new EthereumHost(m_bc, _networkId)); + if (_mode == NodeMode::Full) + m_net->registerCapability(new WhisperHost());*/ + } + m_net->setIdealPeerCount(_peers); + } + + if (_seedHost.size()) + connect(_seedHost, _port); +} + +void RawWebThree::stopNetwork() +{ + UpgradableGuard l(x_net); + + if (m_workNet) + { + if (m_workNetState.load(std::memory_order_acquire) == Active) + m_workNetState.store(Deleting, std::memory_order_release); + while (m_workNetState.load(std::memory_order_acquire) != Deleted) + this_thread::sleep_for(chrono::milliseconds(10)); + m_workNet->join(); + } + if (m_net) + { + UpgradeGuard ul(l); + m_net.reset(nullptr); + m_workNet.reset(nullptr); + } +} + +std::vector RawWebThree::peers() +{ + ReadGuard l(x_net); + return m_net ? m_net->peers() : std::vector(); +} + +size_t RawWebThree::peerCount() const +{ + ReadGuard l(x_net); + return m_net ? m_net->peerCount() : 0; +} + +void RawWebThree::setIdealPeerCount(size_t _n) const +{ + ReadGuard l(x_net); + if (m_net) + return m_net->setIdealPeerCount(_n); +} + +bytes RawWebThree::savePeers() +{ + ReadGuard l(x_net); + if (m_net) + return m_net->savePeers(); + return bytes(); +} + +void RawWebThree::restorePeers(bytesConstRef _saved) +{ + ReadGuard l(x_net); + if (m_net) + return m_net->restorePeers(_saved); +} + +void RawWebThree::connect(std::string const& _seedHost, unsigned short _port) +{ + ReadGuard l(x_net); + if (!m_net.get()) + return; + m_net->connect(_seedHost, _port); +} + +void RawWebThree::workNet() +{ + // 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. + { + ReadGuard l(x_net); + if (m_net) + { + m_net->process(); // must be in guard for now since it uses the blockchain. + + // returns h256Set as block hashes, once for each block that has come in/gone out. +// m_net->cap()->sync(m_tq, m_bq); + } + } + this_thread::sleep_for(chrono::milliseconds(1)); +} + diff --git a/libwebthree/Client.h b/libwebthree/Client.h new file mode 100644 index 000000000..5c418d931 --- /dev/null +++ b/libwebthree/Client.h @@ -0,0 +1,101 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file Client.h + * @author Gav Wood + * @date 2014 + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace dev +{ + +enum WorkState +{ + Active = 0, + Deleting, + Deleted +}; + +enum class NodeMode +{ + PeerServer, + Full +}; + +/** + * @brief Main API hub for interfacing with Web 3 components. This doesn't do any local multiplexing, so you can only have one + * running on any given machine for the provided DB path. + */ +class RawWebThree +{ +public: + /// Constructor. + RawWebThree(std::string const& _clientVersion, std::string const& _dbPath = std::string(), bool _forceClean = false); + + /// Destructor. + ~RawWebThree(); + + // Misc stuff: + + void setClientVersion(std::string const& _name) { m_clientVersion = _name; } + + // Network stuff: + + /// Get information on the current peer set. + std::vector peers(); + /// Same as peers().size(), but more efficient. + size_t peerCount() const; + /// Same as peers().size(), but more efficient. + void setIdealPeerCount(size_t _n) const; + + /// Start the network subsystem. + void startNetwork(unsigned short _listenPort = 30303, std::string const& _remoteHost = std::string(), unsigned short _remotePort = 30303, NodeMode _mode = NodeMode::Full, unsigned _peers = 5, std::string const& _publicIP = std::string(), bool _upnp = true, dev::u256 _networkId = 0); + /// Connect to a particular peer. + void connect(std::string const& _seedHost, unsigned short _port = 30303); + /// Stop the network subsystem. + void stopNetwork(); + /// Is the network subsystem up? + bool haveNetwork() { ReadGuard l(x_net); return !!m_net; } + /// Save peers + dev::bytes savePeers(); + /// Restore peers + void restorePeers(bytesConstRef _saved); + +private: + /// Do some work on the network. + void workNet(); + + std::string m_clientVersion; ///< Our end-application client's name/version. + + std::unique_ptr m_workNet; ///< The network thread. + std::atomic m_workNetState; + mutable boost::shared_mutex x_net; ///< Lock for the network existance. + std::unique_ptr m_net; ///< Should run in background and send us events when blocks found and allow us to send blocks as required. +}; + +} diff --git a/libwhisper/Common.cpp b/libwhisper/Common.cpp index 73420e32a..52bed0742 100644 --- a/libwhisper/Common.cpp +++ b/libwhisper/Common.cpp @@ -22,7 +22,7 @@ #include "Common.h" using namespace std; -using namespace eth; -using namespace p2p; -using namespace shh; +using namespace dev; +using namespace dev::p2p; +using namespace dev::shh; diff --git a/libwhisper/Common.h b/libwhisper/Common.h index 0addc4b75..9ca727c1e 100644 --- a/libwhisper/Common.h +++ b/libwhisper/Common.h @@ -28,18 +28,20 @@ #include #include +namespace dev +{ namespace shh { -using h256 = eth::h256; -using h512 = eth::h512; -using h256s = eth::h256s; -using bytes = eth::bytes; -using RLPStream = eth::RLPStream; -using RLP = eth::RLP; -using bytesRef = eth::bytesRef; -using bytesConstRef = eth::bytesConstRef; -using h256Set = eth::h256Set; +using h256 = dev::h256; +using h512 = dev::h512; +using h256s = dev::h256s; +using bytes = dev::bytes; +using RLPStream = dev::RLPStream; +using RLP = dev::RLP; +using bytesRef = dev::bytesRef; +using bytesConstRef = dev::bytesConstRef; +using h256Set = dev::h256Set; class WhisperHost; class WhisperPeer; @@ -54,3 +56,4 @@ enum WhisperPacket }; } +} diff --git a/libwhisper/WhisperPeer.cpp b/libwhisper/WhisperPeer.cpp index ee548949e..020170013 100644 --- a/libwhisper/WhisperPeer.cpp +++ b/libwhisper/WhisperPeer.cpp @@ -24,11 +24,11 @@ #include #include using namespace std; -using namespace eth; -using namespace p2p; -using namespace shh; +using namespace dev; +using namespace dev::p2p; +using namespace dev::shh; -#define clogS(X) eth::LogOutputStream(false) << "| " << std::setw(2) << session()->socketId() << "] " +#define clogS(X) dev::LogOutputStream(false) << "| " << std::setw(2) << session()->socketId() << "] " WhisperPeer::WhisperPeer(Session* _s, HostCapabilityFace* _h): Capability(_s, _h) { diff --git a/libwhisper/WhisperPeer.h b/libwhisper/WhisperPeer.h index 5db7ffa2b..dba3efed8 100644 --- a/libwhisper/WhisperPeer.h +++ b/libwhisper/WhisperPeer.h @@ -31,6 +31,8 @@ #include #include "Common.h" +namespace dev +{ namespace shh { @@ -59,7 +61,7 @@ struct Message operator bool () const { return !!expiry; } void streamOut(RLPStream& _s) const { _s.appendList(4) << expiry << ttl << topic << payload; } - h256 sha3() const { RLPStream s; streamOut(s); return eth::sha3(s.out()); } + h256 sha3() const { RLPStream s; streamOut(s); return dev::eth::sha3(s.out()); } }; /** @@ -84,7 +86,7 @@ private: unsigned rating(Message const&) const { return 0; } // TODO void noteNewMessage(h256 _h, Message const& _m); - mutable eth::Mutex x_unseen; + mutable dev::Mutex x_unseen; std::map m_unseen; ///< Rated according to what they want. }; @@ -96,7 +98,7 @@ public: MessageFilter(RLP const& _r): m_topicMasks((std::vector >)_r) {} void fillStream(RLPStream& _s) const { _s << m_topicMasks; } - h256 sha3() const { RLPStream s; fillStream(s); return eth::sha3(s.out()); } + h256 sha3() const { RLPStream s; fillStream(s); return dev::eth::sha3(s.out()); } bool matches(Message const& _m) const; @@ -136,10 +138,10 @@ public: unsigned installWatch(MessageFilter const& _filter); unsigned installWatch(h256 _filterId); void uninstallWatch(unsigned _watchId); - h256s peekWatch(unsigned _watchId) const { eth::Guard l(m_filterLock); try { return m_watches.at(_watchId).changes; } catch (...) { return h256s(); } } - h256s checkWatch(unsigned _watchId) { eth::Guard l(m_filterLock); h256s ret; try { ret = m_watches.at(_watchId).changes; m_watches.at(_watchId).changes.clear(); } catch (...) {} return ret; } + h256s peekWatch(unsigned _watchId) const { dev::Guard l(m_filterLock); try { return m_watches.at(_watchId).changes; } catch (...) { return h256s(); } } + h256s checkWatch(unsigned _watchId) { dev::Guard l(m_filterLock); h256s ret; try { ret = m_watches.at(_watchId).changes; m_watches.at(_watchId).changes.clear(); } catch (...) {} return ret; } - Message message(h256 _m) const { try { eth::ReadGuard l(x_messages); return m_messages.at(_m); } catch (...) { return Message(); } } + Message message(h256 _m) const { try { dev::ReadGuard l(x_messages); return m_messages.at(_m); } catch (...) { return Message(); } } void sendRaw(bytes const& _payload, bytes const& _topic, unsigned _ttl) { inject(Message(time(0) + _ttl, _ttl, _topic, _payload)); } @@ -148,19 +150,20 @@ private: void noteChanged(h256 _messageHash, h256 _filter); - mutable eth::SharedMutex x_messages; + mutable dev::SharedMutex x_messages; std::map m_messages; - mutable eth::Mutex m_filterLock; + mutable dev::Mutex m_filterLock; std::map m_filters; std::map m_watches; }; -struct WatchChannel: public eth::LogChannel { static const char* name() { return "shh"; } static const int verbosity = 1; }; -#define cwatch eth::LogOutputStream() +struct WatchChannel: public dev::LogChannel { static const char* name() { return "shh"; } static const int verbosity = 1; }; +#define cwatch dev::LogOutputStream() class Watch; +} } /* namespace std { void swap(shh::Watch& _a, shh::Watch& _b); } diff --git a/lllc/main.cpp b/lllc/main.cpp index 22d40d2da..ccdf5a11b 100644 --- a/lllc/main.cpp +++ b/lllc/main.cpp @@ -28,7 +28,8 @@ #include #include "BuildInfo.h" using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; void help() { @@ -46,9 +47,9 @@ void help() void version() { - cout << "LLLC, the Lovely Little Language Compiler " << eth::EthVersion << endl; + cout << "LLLC, the Lovely Little Language Compiler " << dev::Version << endl; cout << " By Gav Wood, (c) 2014." << endl; - cout << "Build: " << ETH_QUOTED(ETH_BUILD_PLATFORM) << "/" << ETH_QUOTED(ETH_BUILD_TYPE) << endl; + cout << "Build: " << DEV_QUOTED(ETH_BUILD_PLATFORM) << "/" << DEV_QUOTED(ETH_BUILD_TYPE) << endl; exit(0); } diff --git a/neth/main.cpp b/neth/main.cpp index dbb4ed4b6..67afc2664 100644 --- a/neth/main.cpp +++ b/neth/main.cpp @@ -48,10 +48,11 @@ #undef OK using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; using namespace p2p; using namespace boost::algorithm; -using eth::Instruction; +using dev::eth::Instruction; bool isTrue(std::string const& _m) { @@ -121,11 +122,11 @@ string credits() { std::ostringstream ccout; ccout - << "NEthereum (++) " << eth::EthVersion << endl + << "NEthereum (++) " << dev::Version << endl << " Code by Gav Wood & , (c) 2013, 2014." << endl << " Based on a design by Vitalik Buterin." << endl << endl; - string vs = toString(eth::EthVersion); + string vs = toString(dev::Version); vs = vs.substr(vs.find_first_of('.') + 1)[0]; int pocnumber = stoi(vs); string m_servers; @@ -142,14 +143,14 @@ string credits() void version() { - cout << "neth version " << eth::EthVersion << endl; - cout << "Build: " << ETH_QUOTED(ETH_BUILD_PLATFORM) << "/" << ETH_QUOTED(ETH_BUILD_TYPE) << endl; + cout << "neth version " << dev::Version << endl; + cout << "Build: " << DEV_QUOTED(ETH_BUILD_PLATFORM) << "/" << DEV_QUOTED(ETH_BUILD_TYPE) << endl; exit(0); } Address c_config = Address("661005d2720d855f1d9976f88bb10c1a3398c77f"); -string pretty(h160 _a, eth::State _st) +string pretty(h160 _a, dev::eth::State _st) { string ns; h256 n; @@ -412,7 +413,7 @@ int main(int argc, char** argv) if (!clientName.empty()) clientName += "/"; - Client c("NEthereum(++)/" + clientName + "v" + eth::EthVersion + "/" ETH_QUOTED(ETH_BUILD_TYPE) "/" ETH_QUOTED(ETH_BUILD_PLATFORM), coinbase, dbPath); + Client c("NEthereum(++)/" + clientName + "v" + dev::Version + "/" DEV_QUOTED(ETH_BUILD_TYPE) "/" DEV_QUOTED(ETH_BUILD_PLATFORM), coinbase, dbPath); c.setForceMining(true); @@ -525,14 +526,14 @@ int main(int argc, char** argv) if (cmd == "netstart") { - eth::uint port; + unsigned port; iss >> port; c.startNetwork((short)port); } else if (cmd == "connect") { string addr; - eth::uint port; + unsigned port; iss >> addr >> port; c.connect(addr, (short)port); } @@ -582,7 +583,7 @@ int main(int argc, char** argv) } else if (cmd == "block") { - eth::uint n = c.blockChain().details().number; + unsigned n = c.blockChain().details().number; ccout << "Current block # "; ccout << toString(n) << endl; } @@ -645,7 +646,7 @@ int main(int argc, char** argv) string sdata = fields[5]; cnote << "Data:"; cnote << sdata; - bytes data = eth::parseData(sdata); + bytes data = dev::eth::parseData(sdata); cnote << "Bytes:"; string sbd = asString(data); bytes bbd = asBytes(sbd); @@ -810,7 +811,7 @@ int main(int argc, char** argv) cnote << "Saved" << rechex << "to" << outFile; } - catch (eth::InvalidTrie) + catch (dev::eth::InvalidTrie) { cwarn << "Corrupted trie."; } @@ -955,7 +956,7 @@ int main(int argc, char** argv) // Block mvwprintw(blockswin, 0, x, "Block # "); - eth::uint n = c.blockChain().details().number; + unsigned n = c.blockChain().details().number; mvwprintw(blockswin, 0, 10, toString(n).c_str()); // Pending @@ -976,7 +977,7 @@ int main(int argc, char** argv) if (c.isMining()) { mvwprintw(consolewin, qheight - 1, width / 4 - 11, "Mining ON"); - eth::MineProgress p = c.miningProgress(); + dev::eth::MineProgress p = c.miningProgress(); auto speed = boost::format("%2% kH/s @ %1%s") % (p.ms / 1000) % (p.ms ? p.hashes / p.ms : 0); mvwprintw(consolewin, qheight - 2, width / 4 - speed.str().length() - 2, speed.str().c_str()); } diff --git a/test/MemTrie.cpp b/test/MemTrie.cpp index 5c819ffbd..b5d875acb 100644 --- a/test/MemTrie.cpp +++ b/test/MemTrie.cpp @@ -25,9 +25,10 @@ #include #include using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; -namespace eth +namespace dev { #define ENABLE_DEBUG_PRINT 0 @@ -54,7 +55,7 @@ public: #endif /// 256-bit hash of the node - this is a SHA-3/256 hash of the RLP of the node. - h256 hash256() const { RLPStream s; makeRLP(s); return eth::sha3(s.out()); } + h256 hash256() const { RLPStream s; makeRLP(s); return dev::eth::sha3(s.out()); } bytes rlp() const { RLPStream s; makeRLP(s); return s.out(); } void mark() { m_hash256 = h256(); } @@ -199,7 +200,7 @@ void MemTrieNode::putRLP(RLPStream& _parentStream) const if (s.out().size() < 32) _parentStream.APPEND_CHILD(s.out()); else - _parentStream << eth::sha3(s.out()); + _parentStream << dev::eth::sha3(s.out()); } void TrieBranchNode::makeRLP(RLPStream& _intoStream) const @@ -228,7 +229,7 @@ void TrieInfixNode::makeRLP(RLPStream& _intoStream) const MemTrieNode* MemTrieNode::newBranch(bytesConstRef _k1, std::string const& _v1, bytesConstRef _k2, std::string const& _v2) { - uint prefix = commonPrefix(_k1, _k2); + unsigned prefix = commonPrefix(_k1, _k2); MemTrieNode* ret; if (_k1.size() == prefix) @@ -347,7 +348,7 @@ MemTrieNode* TrieInfixNode::insert(bytesConstRef _key, std::string const& _value } else { - uint prefix = commonPrefix(_key, m_ext); + unsigned prefix = commonPrefix(_key, m_ext); if (prefix) { // one infix becomes two infixes, then insert into the second @@ -478,3 +479,4 @@ void MemTrie::remove(std::string const& _key) } } + diff --git a/test/MemTrie.h b/test/MemTrie.h index 8c90f2f3f..66669653c 100644 --- a/test/MemTrie.h +++ b/test/MemTrie.h @@ -24,7 +24,7 @@ #include #include -namespace eth +namespace dev { class MemTrieNode; diff --git a/test/TestHelper.cpp b/test/TestHelper.cpp index 06f188a89..02a883dbc 100644 --- a/test/TestHelper.cpp +++ b/test/TestHelper.cpp @@ -19,11 +19,14 @@ * @date 2014 */ +#include "TestHelper.h" + #include #include #include -#include "TestHelper.h" +namespace dev +{ namespace eth { @@ -47,3 +50,4 @@ void connectClients(Client& c1, Client& c2) } } +} diff --git a/test/TestHelper.h b/test/TestHelper.h index 748373baa..d6924a17c 100644 --- a/test/TestHelper.h +++ b/test/TestHelper.h @@ -21,10 +21,15 @@ #pragma once +namespace dev +{ namespace eth { +class Client; + void mine(Client& c, int numBlocks); void connectClients(Client& c1, Client& c2); } +} diff --git a/test/TrieHash.cpp b/test/TrieHash.cpp index 8f4161a17..b2ebce731 100644 --- a/test/TrieHash.cpp +++ b/test/TrieHash.cpp @@ -25,9 +25,10 @@ #include #include using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; -namespace eth +namespace dev { /*/ @@ -67,12 +68,12 @@ void hash256rlp(HexMap const& _s, HexMap::const_iterator _begin, HexMap::const_i { // find the number of common prefix nibbles shared // i.e. the minimum number of nibbles shared at the beginning between the first hex string and each successive. - uint sharedPre = (uint)-1; - uint c = 0; + unsigned sharedPre = (unsigned)-1; + unsigned c = 0; for (auto i = std::next(_begin); i != _end && sharedPre; ++i, ++c) { - uint x = std::min(sharedPre, std::min((uint)_begin->first.size(), (uint)i->first.size())); - uint shared = _preLen; + unsigned x = std::min(sharedPre, std::min((unsigned)_begin->first.size(), (unsigned)i->first.size())); + unsigned shared = _preLen; for (; shared < x && _begin->first[shared] == i->first[shared]; ++shared) {} sharedPre = std::min(shared, sharedPre); } diff --git a/test/TrieHash.h b/test/TrieHash.h index 391c42633..4d86c4dbf 100644 --- a/test/TrieHash.h +++ b/test/TrieHash.h @@ -24,7 +24,7 @@ #include #include -namespace eth +namespace dev { bytes rlp256(StringMap const& _s); diff --git a/test/crypto.cpp b/test/crypto.cpp index c39de78d7..2e4ffa055 100644 --- a/test/crypto.cpp +++ b/test/crypto.cpp @@ -29,7 +29,8 @@ #include using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; BOOST_AUTO_TEST_CASE(crypto_tests) @@ -145,7 +146,7 @@ int cryptoTest() int ret = secp256k1_ecdsa_recover_compact((byte const*)hmsg.data(), (int)hmsg.size(), (byte const*)sig64.data(), pubkey.data(), &pubkeylen, 0, (int)t.vrs.v - 27); pubkey.resize(pubkeylen); cout << "RECPUB: " << dec << ret << " " << pubkeylen << " " << toHex(pubkey) << endl; - cout << "SENDER: " << hex << toAddress(eth::sha3(bytesConstRef(&pubkey).cropped(1))) << dec << endl; + cout << "SENDER: " << hex << toAddress(dev::eth::sha3(bytesConstRef(&pubkey).cropped(1))) << dec << endl; } #endif return 0; diff --git a/test/dagger.cpp b/test/dagger.cpp index 4f65b47aa..fd60a8efa 100644 --- a/test/dagger.cpp +++ b/test/dagger.cpp @@ -25,7 +25,8 @@ #include using namespace std; using namespace std::chrono; -using namespace eth; +using namespace dev; +using namespace dev::eth; int daggerTest() { diff --git a/test/fork.cpp b/test/fork.cpp index f0edb702f..1cdb8822c 100644 --- a/test/fork.cpp +++ b/test/fork.cpp @@ -27,7 +27,8 @@ #include #include "TestHelper.h" using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; // Disabled since tests shouldn't block. Need a short cut to avoid real mining. /* diff --git a/test/genesis.cpp b/test/genesis.cpp index 441826e78..be6655aba 100644 --- a/test/genesis.cpp +++ b/test/genesis.cpp @@ -28,7 +28,8 @@ #include using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; namespace js = json_spirit; diff --git a/test/hexPrefix.cpp b/test/hexPrefix.cpp index 5ea670923..7dbe80a57 100644 --- a/test/hexPrefix.cpp +++ b/test/hexPrefix.cpp @@ -27,7 +27,8 @@ #include using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; namespace js = json_spirit; BOOST_AUTO_TEST_CASE(hexPrefix_test) diff --git a/test/main.cpp b/test/main.cpp index 1497f2981..08142095c 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -38,7 +38,8 @@ int peerTest(int argc, char** argv); #include #include using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; BOOST_AUTO_TEST_CASE(basic_tests) { diff --git a/test/network.cpp b/test/network.cpp index 978d68934..acdd649d9 100644 --- a/test/network.cpp +++ b/test/network.cpp @@ -27,7 +27,8 @@ #include #include "TestHelper.h" using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; // Disabled since tests shouldn't block (not the worst offender, but timeout should be reduced anyway). /* diff --git a/test/peer.cpp b/test/peer.cpp index f903d0429..821aab514 100644 --- a/test/peer.cpp +++ b/test/peer.cpp @@ -24,8 +24,8 @@ #include #include using namespace std; -using namespace eth; -using namespace p2p; +using namespace dev; +using namespace dev::p2p; int peerTest(int argc, char** argv) { diff --git a/test/rlp.cpp b/test/rlp.cpp index 548be8d9b..963d61091 100644 --- a/test/rlp.cpp +++ b/test/rlp.cpp @@ -30,10 +30,10 @@ #include using namespace std; -using namespace eth; +using namespace dev; namespace js = json_spirit; -namespace eth +namespace dev { namespace test { @@ -116,7 +116,7 @@ namespace eth BOOST_CHECK( !u.isData() ); js::mArray& arr = v.get_array(); BOOST_CHECK( u.itemCount() == arr.size() ); - uint i; + unsigned i; for( i = 0; i < arr.size(); i++ ) { RLP item = u[i]; @@ -137,16 +137,16 @@ BOOST_AUTO_TEST_CASE(rlp_encoding_test) { cnote << "Testing RLP Encoding..."; js::mValue v; - eth::test::getRLPTestCases(v); + dev::test::getRLPTestCases(v); for (auto& i: v.get_obj()) { js::mObject& o = i.second.get_obj(); cnote << i.first; - eth::test::checkRLPTestCase(o); + dev::test::checkRLPTestCase(o); RLPStream s; - eth::test::buildRLP(o["in"], s); + dev::test::buildRLP(o["in"], s); std::string expectedText(o["out"].get_str()); std::transform(expectedText.begin(), expectedText.end(), expectedText.begin(), ::tolower ); @@ -173,19 +173,19 @@ BOOST_AUTO_TEST_CASE(rlp_decoding_test) // and then compare the output structure to the json of the // input object. js::mValue v; - eth::test::getRLPTestCases(v); + dev::test::getRLPTestCases(v); for (auto& i: v.get_obj()) { js::mObject& o = i.second.get_obj(); cnote << i.first; - eth::test::checkRLPTestCase(o); + dev::test::checkRLPTestCase(o); js::mValue& inputData = o["in"]; bytes payloadToDecode = fromHex(o["out"].get_str()); RLP payload(payloadToDecode); - eth::test::checkRLPAgainstJson(inputData, payload); + dev::test::checkRLPAgainstJson(inputData, payload); } } diff --git a/test/state.cpp b/test/state.cpp index 8d82b0c2b..99ce30957 100644 --- a/test/state.cpp +++ b/test/state.cpp @@ -26,7 +26,8 @@ #include #include using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; int stateTest() { diff --git a/test/trie.cpp b/test/trie.cpp index b28be8b18..4ddf30a61 100644 --- a/test/trie.cpp +++ b/test/trie.cpp @@ -29,22 +29,23 @@ #include using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; namespace js = json_spirit; -namespace eth -{ - namespace test - { - static unsigned fac(unsigned _i) - { - return _i > 2 ? _i * fac(_i - 1) : _i; - } +namespace dev +{ +namespace test +{ - } +static unsigned fac(unsigned _i) +{ + return _i > 2 ? _i * fac(_i - 1) : _i; } +} +} BOOST_AUTO_TEST_CASE(trie_tests) { @@ -66,7 +67,7 @@ BOOST_AUTO_TEST_CASE(trie_tests) if (!ss.back().second.find("0x")) ss.back().second = asString(fromHex(ss.back().second.substr(2))); } - for (unsigned j = 0; j < min(1000u, eth::test::fac((unsigned)ss.size())); ++j) + for (unsigned j = 0; j < min(1000u, dev::test::fac((unsigned)ss.size())); ++j) { next_permutation(ss.begin(), ss.end()); MemoryDB m; diff --git a/test/txTest.cpp b/test/txTest.cpp index 314cf9644..cc78c26a2 100644 --- a/test/txTest.cpp +++ b/test/txTest.cpp @@ -27,7 +27,8 @@ #include #include "TestHelper.h" using namespace std; -using namespace eth; +using namespace dev; +using namespace dev::eth; // Disabled since tests shouldn't block. Need a short cut to avoid real mining. /* @@ -46,7 +47,7 @@ BOOST_AUTO_TEST_CASE(mine_local_simple_tx) //send c2 some eth from c1 auto txAmount = c1bal / 2u; auto gasPrice = 10 * szabo; - auto gas = eth::c_callGas; + auto gas = dev::eth::c_callGas; c1.transact(kp1.secret(), txAmount, kp2.address(), bytes(), gas, gasPrice); //mine some more to include the transaction on chain @@ -74,7 +75,7 @@ BOOST_AUTO_TEST_CASE(mine_and_send_to_peer) //send c2 some eth from c1 auto txAmount = c1bal / 2u; auto gasPrice = 10 * szabo; - auto gas = eth::c_callGas; + auto gas = dev::eth::c_callGas; c1.transact(kp1.secret(), txAmount, kp2.address(), bytes(), gas, gasPrice); //mine some more to include the transaction on chain @@ -105,7 +106,7 @@ BOOST_AUTO_TEST_CASE(mine_and_send_to_peer_fee_check) //send c2 some eth from c1 auto txAmount = c1StartBalance / 2u; auto gasPrice = 10 * szabo; - auto gas = eth::c_callGas; + auto gas = dev::eth::c_callGas; c1.transact(kp1.secret(), txAmount, c2.address(), bytes(), gas, gasPrice); //mine some more, this time with second client (so he can get fees from first client's tx) diff --git a/test/vm.cpp b/test/vm.cpp index 0647849a0..2b75f0b3d 100644 --- a/test/vm.cpp +++ b/test/vm.cpp @@ -33,9 +33,10 @@ using namespace std; using namespace json_spirit; -using namespace eth; +using namespace dev; +using namespace dev::eth; -namespace eth { namespace test { +namespace dev { namespace test { class FakeExtVM: public ExtVMFace { @@ -387,7 +388,7 @@ void doTests(json_spirit::mValue& v, bool _fillin) BOOST_REQUIRE(o.count("exec") > 0); VM vm; - eth::test::FakeExtVM fev; + dev::test::FakeExtVM fev; fev.importEnv(o["env"].get_obj()); fev.importState(o["pre"].get_obj()); @@ -419,7 +420,7 @@ void doTests(json_spirit::mValue& v, bool _fillin) BOOST_REQUIRE(o.count("out") > 0); BOOST_REQUIRE(o.count("gas") > 0); - eth::test::FakeExtVM test; + dev::test::FakeExtVM test; test.importState(o["post"].get_obj()); test.importCallCreates(o["callcreates"].get_array()); int i = 0; @@ -481,7 +482,7 @@ BOOST_AUTO_TEST_CASE(vm_tests) string s = asString(contents("../../../cpp-ethereum/test/vmtests.json")); BOOST_REQUIRE_MESSAGE(s.length() > 0, "Contents of 'vmtests.json' is empty."); json_spirit::read_string(s, v); - eth::test::doTests(v, true); + dev::test::doTests(v, true); writeFile("../../../tests/vmtests.json", asBytes(json_spirit::write_string(v, true))); } /* catch (std::exception const& e) @@ -496,7 +497,7 @@ BOOST_AUTO_TEST_CASE(vm_tests) string s = asString(contents("../../../tests/vmtests.json")); BOOST_REQUIRE_MESSAGE(s.length() > 0, "Contents of 'vmtests.json' is empty. Have you cloned the 'tests' repo branch develop?"); json_spirit::read_string(s, v); - eth::test::doTests(v, false); + dev::test::doTests(v, false); } catch (std::exception const& e) { diff --git a/third/MainWin.cpp b/third/MainWin.cpp index 22ce25b66..b8722f2d7 100644 --- a/third/MainWin.cpp +++ b/third/MainWin.cpp @@ -44,45 +44,45 @@ using namespace std; // types -using eth::bytes; -using eth::bytesConstRef; -using eth::h160; -using eth::h256; -using eth::u160; -using eth::u256; -using eth::Address; -using eth::BlockInfo; -using eth::Client; -using eth::Instruction; -using eth::KeyPair; -using eth::NodeMode; -using eth::BlockChain; -using p2p::PeerInfo; -using eth::RLP; -using eth::Secret; -using eth::Transaction; -using eth::Executive; +using dev::bytes; +using dev::bytesConstRef; +using dev::h160; +using dev::h256; +using dev::u160; +using dev::u256; +using dev::eth::Address; +using dev::eth::BlockInfo; +using dev::eth::Client; +using dev::eth::Instruction; +using dev::eth::KeyPair; +using dev::eth::NodeMode; +using dev::eth::BlockChain; +using dev::p2p::PeerInfo; +using dev::RLP; +using dev::eth::Secret; +using dev::eth::Transaction; +using dev::eth::Executive; // functions -using eth::toHex; -using eth::compileLLL; -using eth::disassemble; -using eth::formatBalance; -using eth::fromHex; -using eth::sha3; -using eth::left160; -using eth::right160; -using eth::simpleDebugOut; -using eth::toLog2; -using eth::toString; -using eth::units; -using eth::operator<<; +using dev::toHex; +using dev::fromHex; +using dev::left160; +using dev::right160; +using dev::simpleDebugOut; +using dev::toLog2; +using dev::toString; +using dev::operator<<; +using dev::eth::units; +using dev::eth::compileLLL; +using dev::eth::disassemble; +using dev::eth::formatBalance; +using dev::eth::sha3; // vars -using eth::g_logPost; -using eth::g_logVerbosity; +using dev::g_logPost; +using dev::g_logVerbosity; -static QString fromRaw(eth::h256 _n, unsigned* _inc = nullptr) +static QString fromRaw(dev::h256 _n, unsigned* _inc = nullptr) { if (_n) { @@ -121,8 +121,8 @@ Main::Main(QWidget *parent) : cerr << "Block Hash: " << sha3(BlockChain::createGenesisBlock()) << endl; cerr << "Block RLP: " << RLP(BlockChain::createGenesisBlock()) << endl; cerr << "Block Hex: " << toHex(BlockChain::createGenesisBlock()) << endl; - cerr << "Network protocol version: " << eth::c_protocolVersion << endl; - cerr << "Client database version: " << eth::c_databaseVersion << endl; + cerr << "Network protocol version: " << dev::eth::c_protocolVersion << endl; + cerr << "Client database version: " << dev::eth::c_databaseVersion << endl; ui->ownedAccountsDock->hide(); @@ -186,14 +186,14 @@ void Main::onKeysChanged() installBalancesWatch(); } -unsigned Main::installWatch(eth::MessageFilter const& _tf, std::function const& _f) +unsigned Main::installWatch(dev::eth::MessageFilter const& _tf, std::function const& _f) { auto ret = m_client->installWatch(_tf); m_handlers[ret] = _f; return ret; } -unsigned Main::installWatch(eth::h256 _tf, std::function const& _f) +unsigned Main::installWatch(dev::h256 _tf, std::function const& _f) { auto ret = m_client->installWatch(_tf); m_handlers[ret] = _f; @@ -202,26 +202,26 @@ unsigned Main::installWatch(eth::h256 _tf, std::function const& _f) void Main::installWatches() { - installWatch(eth::MessageFilter().altered(c_config, 0), [=](){ installNameRegWatch(); }); - installWatch(eth::MessageFilter().altered(c_config, 1), [=](){ installCurrenciesWatch(); }); - installWatch(eth::ChainChangedFilter, [=](){ onNewBlock(); }); + installWatch(dev::eth::MessageFilter().altered(c_config, 0), [=](){ installNameRegWatch(); }); + installWatch(dev::eth::MessageFilter().altered(c_config, 1), [=](){ installCurrenciesWatch(); }); + installWatch(dev::eth::ChainChangedFilter, [=](){ onNewBlock(); }); } void Main::installNameRegWatch() { m_client->uninstallWatch(m_nameRegFilter); - m_nameRegFilter = installWatch(eth::MessageFilter().altered((u160)m_client->stateAt(c_config, 0)), [=](){ onNameRegChange(); }); + m_nameRegFilter = installWatch(dev::eth::MessageFilter().altered((u160)m_client->stateAt(c_config, 0)), [=](){ onNameRegChange(); }); } void Main::installCurrenciesWatch() { m_client->uninstallWatch(m_currenciesFilter); - m_currenciesFilter = installWatch(eth::MessageFilter().altered((u160)m_client->stateAt(c_config, 1)), [=](){ onCurrenciesChange(); }); + m_currenciesFilter = installWatch(dev::eth::MessageFilter().altered((u160)m_client->stateAt(c_config, 1)), [=](){ onCurrenciesChange(); }); } void Main::installBalancesWatch() { - eth::MessageFilter tf; + dev::eth::MessageFilter tf; vector
    altCoins; Address coinsAddr = right160(m_client->stateAt(c_config, 1)); @@ -291,7 +291,7 @@ void Main::eval(QString const& _js) ui->webView->page()->currentFrame()->evaluateJavaScript("___RET=(" + _js + ")"); } -QString Main::pretty(eth::Address _a) const +QString Main::pretty(dev::eth::Address _a) const { h256 n; @@ -301,7 +301,7 @@ QString Main::pretty(eth::Address _a) const return fromRaw(n); } -QString Main::render(eth::Address _a) const +QString Main::render(dev::eth::Address _a) const { QString p = pretty(_a); if (!p.isNull()) @@ -368,7 +368,7 @@ QString Main::lookup(QString const& _a) const void Main::on_about_triggered() { - QMessageBox::about(this, "About Third PoC-" + QString(eth::EthVersion).section('.', 1, 1), QString("Third/v") + eth::EthVersion + "/" ETH_QUOTED(ETH_BUILD_TYPE) "/" ETH_QUOTED(ETH_BUILD_PLATFORM) "\n" ETH_QUOTED(ETH_COMMIT_HASH) + (ETH_CLEAN_REPO ? "\nCLEAN" : "\n+ LOCAL CHANGES") + "\n\nBy Gav Wood, 2014.\nBased on a design by Vitalik Buterin.\n\nThanks to the various contributors including: Alex Leverington, Tim Hughes, caktux, Eric Lombrozo, Marko Simovic."); + QMessageBox::about(this, "About Third PoC-" + QString(dev::Version).section('.', 1, 1), QString("Third/v") + dev::Version + "/" DEV_QUOTED(ETH_BUILD_TYPE) "/" DEV_QUOTED(ETH_BUILD_PLATFORM) "\n" DEV_QUOTED(ETH_COMMIT_HASH) + (ETH_CLEAN_REPO ? "\nCLEAN" : "\n+ LOCAL CHANGES") + "\n\nBy Gav Wood, 2014.\nBased on a design by Vitalik Buterin.\n\nThanks to the various contributors including: Alex Leverington, Tim Hughes, caktux, Eric Lombrozo, Marko Simovic."); } void Main::writeSettings() @@ -466,7 +466,7 @@ void Main::on_urlEdit_returnPressed() void Main::refreshMining() { - eth::MineProgress p = m_client->miningProgress(); + dev::eth::MineProgress p = m_client->miningProgress(); ui->mineStatus->setText(m_client->isMining() ? QString("%1s @ %2kH/s").arg(p.ms / 1000).arg(p.ms ? p.hashes / p.ms : 0) : "Not mining"); } @@ -516,7 +516,7 @@ void Main::refreshBlockCount() cwatch << "refreshBlockCount()"; auto d = m_client->blockChain().details(); auto diff = BlockInfo(m_client->blockChain().block()).difficulty; - ui->blockCount->setText(QString("#%1 @%3 T%2 N%4 D%5").arg(d.number).arg(toLog2(d.totalDifficulty)).arg(toLog2(diff)).arg(eth::c_protocolVersion).arg(eth::c_databaseVersion)); + ui->blockCount->setText(QString("#%1 @%3 T%2 N%4 D%5").arg(d.number).arg(toLog2(d.totalDifficulty)).arg(toLog2(diff)).arg(dev::eth::c_protocolVersion).arg(dev::eth::c_databaseVersion)); } void Main::timerEvent(QTimerEvent*) @@ -572,11 +572,11 @@ void Main::on_ourAccounts_doubleClicked() void Main::ensureNetwork() { - string n = string("Third/v") + eth::EthVersion; - n += "/" ETH_QUOTED(ETH_BUILD_TYPE) "/" ETH_QUOTED(ETH_BUILD_PLATFORM); + string n = string("Third/v") + dev::Version; + n += "/" DEV_QUOTED(ETH_BUILD_TYPE) "/" DEV_QUOTED(ETH_BUILD_PLATFORM); m_client->setClientVersion(n); - int pocnumber = QString(eth::EthVersion).section('.', 1, 1).toInt(); + int pocnumber = QString(dev::Version).section('.', 1, 1).toInt(); string defPeer; if (pocnumber == 5) defPeer = "54.72.69.180"; diff --git a/third/MainWin.h b/third/MainWin.h index 4706b5158..6fc7a6a5d 100644 --- a/third/MainWin.h +++ b/third/MainWin.h @@ -36,11 +36,11 @@ namespace Ui { class Main; } -namespace eth { +namespace dev { namespace eth { class Client; class State; class MessageFilter; -} +}} class QQuickView; @@ -52,9 +52,9 @@ public: explicit Main(QWidget *parent = 0); ~Main(); - eth::Client* client() { return m_client.get(); } + dev::eth::Client* client() { return m_client.get(); } - QList const& owned() const { return m_myKeys; } + QList const& owned() const { return m_myKeys; } public slots: void note(QString _entry); @@ -79,16 +79,16 @@ signals: void poll(); private: - QString pretty(eth::Address _a) const; - QString render(eth::Address _a) const; - eth::Address fromString(QString const& _a) const; + QString pretty(dev::eth::Address _a) const; + QString render(dev::eth::Address _a) const; + dev::eth::Address fromString(QString const& _a) const; QString lookup(QString const& _n) const; void readSettings(bool _skipGeometry = false); void writeSettings(); - unsigned installWatch(eth::MessageFilter const& _tf, std::function const& _f); - unsigned installWatch(eth::h256 _tf, std::function const& _f); + unsigned installWatch(dev::eth::MessageFilter const& _tf, std::function const& _f); + unsigned installWatch(dev::h256 _tf, std::function const& _f); void onNewBlock(); void onNameRegChange(); @@ -111,9 +111,9 @@ private: std::unique_ptr ui; - std::unique_ptr m_client; + std::unique_ptr m_client; - QList m_myKeys; + QList m_myKeys; std::map> m_handlers; unsigned m_nameRegFilter = (unsigned)-1; diff --git a/walleth/MainWin.cpp b/walleth/MainWin.cpp index 8abd9ba60..a07230885 100644 --- a/walleth/MainWin.cpp +++ b/walleth/MainWin.cpp @@ -18,38 +18,38 @@ using namespace std; // types -using eth::bytes; -using eth::bytesConstRef; -using eth::h160; -using eth::h256; -using eth::u160; -using eth::u256; -using eth::u256s; -using eth::Address; -using eth::BlockInfo; -using eth::Client; -using eth::Instruction; -using eth::KeyPair; -using eth::NodeMode; -using p2p::PeerInfo; -using eth::RLP; -using eth::Secret; -using eth::Transaction; +using dev::bytes; +using dev::bytesConstRef; +using dev::h160; +using dev::h256; +using dev::u160; +using dev::u256; +using dev::u256s; +using dev::eth::Address; +using dev::eth::BlockInfo; +using dev::eth::Client; +using dev::eth::Instruction; +using dev::eth::KeyPair; +using dev::eth::NodeMode; +using dev::p2p::PeerInfo; +using dev::RLP; +using dev::eth::Secret; +using dev::eth::Transaction; // functions -using eth::toHex; -using eth::disassemble; -using eth::formatBalance; -using eth::fromHex; -using eth::right160; -using eth::simpleDebugOut; -using eth::toLog2; -using eth::toString; -using eth::units; +using dev::toHex; +using dev::fromHex; +using dev::right160; +using dev::simpleDebugOut; +using dev::toLog2; +using dev::toString; +using dev::eth::units; +using dev::eth::disassemble; +using dev::eth::formatBalance; // vars -using eth::g_logPost; -using eth::g_logVerbosity; +using dev::g_logPost; +using dev::g_logVerbosity; Main::Main(QWidget *parent) : QMainWindow(parent), @@ -61,14 +61,14 @@ Main::Main(QWidget *parent) : g_qmlMain = this; - m_client.reset(new Client("Walleth", Address(), eth::getDataDir() + "/Walleth")); + m_client.reset(new Client("Walleth", Address(), dev::eth::getDataDir() + "/Walleth")); g_qmlClient = m_client.get(); - qRegisterMetaType("eth::u256"); - qRegisterMetaType("eth::KeyPair"); - qRegisterMetaType("eth::Secret"); - qRegisterMetaType("eth::Address"); + qRegisterMetaType("dev::u256"); + qRegisterMetaType("dev::eth::KeyPair"); + qRegisterMetaType("dev::eth::Secret"); + qRegisterMetaType("dev::eth::Address"); qRegisterMetaType("QmlAccount*"); qRegisterMetaType("QmlEthereum*"); @@ -112,7 +112,7 @@ Main::Main(QWidget *parent) : on_net_triggered(true); } }); - QNetworkRequest r(QUrl("http://www.ethereum.org/servers.poc" + QString(eth::EthVersion).section('.', 1, 1) + ".txt")); + QNetworkRequest r(QUrl("http://www.ethereum.org/servers.poc" + QString(dev::Version).section('.', 1, 1) + ".txt")); r.setHeader(QNetworkRequest::UserAgentHeader, "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1712.0 Safari/537.36"); m_webCtrl.get(r); srand(time(0)); @@ -136,7 +136,7 @@ void Main::timerEvent(QTimerEvent *) void Main::on_about_triggered() { - QMessageBox::about(this, "About Walleth PoC-" + QString(eth::EthVersion).section('.', 1, 1), QString("Walleth/v") + eth::EthVersion + "/" ETH_QUOTED(ETH_BUILD_TYPE) "/" ETH_QUOTED(ETH_BUILD_PLATFORM) "\n" ETH_QUOTED(ETH_COMMIT_HASH) + (ETH_CLEAN_REPO ? "\nCLEAN" : "\n+ LOCAL CHANGES") + "\n\nBy Gav Wood, 2014.\nBased on a design by Vitalik Buterin.\n\nThanks to the various contributors including: Alex Leverington, Tim Hughes, caktux, Eric Lombrozo, Marko Simovic."); + QMessageBox::about(this, "About Walleth PoC-" + QString(dev::Version).section('.', 1, 1), QString("Walleth/v") + dev::Version + "/" DEV_QUOTED(ETH_BUILD_TYPE) "/" DEV_QUOTED(ETH_BUILD_PLATFORM) "\n" DEV_QUOTED(ETH_COMMIT_HASH) + (ETH_CLEAN_REPO ? "\nCLEAN" : "\n+ LOCAL CHANGES") + "\n\nBy Gav Wood, 2014.\nBased on a design by Vitalik Buterin.\n\nThanks to the various contributors including: Alex Leverington, Tim Hughes, caktux, Eric Lombrozo, Marko Simovic."); } void Main::writeSettings() @@ -217,10 +217,10 @@ void Main::refresh() void Main::on_net_triggered(bool _auto) { - string n = string("Walleth/v") + eth::EthVersion; + string n = string("Walleth/v") + dev::Version; if (m_clientName.size()) n += "/" + m_clientName.toStdString(); - n += "/" ETH_QUOTED(ETH_BUILD_TYPE) "/" ETH_QUOTED(ETH_BUILD_PLATFORM); + n += "/" DEV_QUOTED(ETH_BUILD_TYPE) "/" DEV_QUOTED(ETH_BUILD_PLATFORM); client()->setClientVersion(n); if (ui->net->isChecked()) { diff --git a/walleth/MainWin.h b/walleth/MainWin.h index b4c192d01..0edbff226 100644 --- a/walleth/MainWin.h +++ b/walleth/MainWin.h @@ -12,10 +12,10 @@ namespace Ui { class Main; } -namespace eth { +namespace dev { namespace eth { class Client; class State; -} +}} class QQuickView; class QQmlEngine; @@ -29,7 +29,7 @@ public: explicit Main(QWidget *parent = 0); ~Main(); - eth::Client* client() const { return m_client.get(); } + dev::eth::Client* client() const { return m_client.get(); } private slots: void on_connect_triggered(); @@ -47,19 +47,19 @@ protected: virtual void timerEvent(QTimerEvent *); private: -/* QString pretty(eth::Address _a) const; - QString render(eth::Address _a) const; - eth::Address fromString(QString const& _a) const; +/* QString pretty(dev::eth::Address _a) const; + QString render(dev::eth::Address _a) const; + dev::eth::Address fromString(QString const& _a) const; */ - eth::State const& state() const; + dev::eth::State const& state() const; void updateFee(); void readSettings(); void writeSettings(); - eth::u256 fee() const; - eth::u256 total() const; - eth::u256 value() const; + dev::u256 fee() const; + dev::u256 total() const; + dev::u256 value() const; std::unique_ptr ui; @@ -67,7 +67,7 @@ private: QMutex m_guiLock; QTimer* m_refresh; QTimer* m_refreshNetwork; - QVector m_myKeys; + QVector m_myKeys; bool m_keysChanged = false; int m_port; int m_idealPeers; @@ -78,7 +78,7 @@ private: QNetworkAccessManager m_webCtrl; - std::unique_ptr m_client; + std::unique_ptr m_client; }; #endif // MAIN_H From 515a1c22a9e85f616ae58aa05b45e6b613e4b777 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 5 Sep 2014 18:24:29 +0200 Subject: [PATCH 158/223] Split ethcore off into devcrypto ready for Whisper's crypto and repot namespace. Rename ethential to devcore. --- CMakeLists.txt | 3 +- alethzero/CMakeLists.txt | 2 +- alethzero/MainWin.cpp | 12 +-- alethzero/MainWin.h | 16 +-- eth/CommonJS.h | 8 +- eth/EthStubServer.h | 7 +- eth/main.cpp | 2 +- exp/main.cpp | 8 +- {libethential => libdevcore}/All.h | 0 {libethential => libdevcore}/CMakeLists.txt | 4 +- {libethential => libdevcore}/Common.cpp | 0 {libethential => libdevcore}/Common.h | 0 {libethential => libdevcore}/CommonData.cpp | 0 {libethential => libdevcore}/CommonData.h | 0 {libethential => libdevcore}/CommonIO.cpp | 0 {libethential => libdevcore}/CommonIO.h | 0 {libethential => libdevcore}/Exceptions.h | 0 {libethential => libdevcore}/FixedHash.cpp | 0 {libethential => libdevcore}/FixedHash.h | 0 {libethential => libdevcore}/Guards.cpp | 0 {libethential => libdevcore}/Guards.h | 0 {libethential => libdevcore}/Log.cpp | 0 {libethential => libdevcore}/Log.h | 0 {libethential => libdevcore}/RLP.cpp | 0 {libethential => libdevcore}/RLP.h | 8 +- {libethential => libdevcore}/vector_ref.h | 0 libdevcrypto/All.h | 9 ++ libdevcrypto/CMakeLists.txt | 56 +++++++++++ libdevcrypto/Common.cpp | 104 ++++++++++++++++++++ libdevcrypto/Common.h | 86 ++++++++++++++++ libdevcrypto/CryptoHeaders.h | 36 +++++++ {libethcore => libdevcrypto}/FileSystem.cpp | 4 +- {libethcore => libdevcrypto}/FileSystem.h | 0 {libethcore => libdevcrypto}/MemoryDB.cpp | 2 +- {libethcore => libdevcrypto}/MemoryDB.h | 8 +- {libethcore => libdevcrypto}/OverlayDB.cpp | 2 +- {libethcore => libdevcrypto}/OverlayDB.h | 4 +- {libethcore => libdevcrypto}/SHA3.cpp | 0 {libethcore => libdevcrypto}/SHA3.h | 4 +- {libethcore => libdevcrypto}/TrieCommon.cpp | 0 {libethcore => libdevcrypto}/TrieCommon.h | 4 +- {libethcore => libdevcrypto}/TrieDB.cpp | 2 +- {libethcore => libdevcrypto}/TrieDB.h | 6 +- libethcore/BlockInfo.cpp | 6 +- libethcore/BlockInfo.h | 4 +- libethcore/CMakeLists.txt | 3 +- libethcore/CommonEth.cpp | 50 +--------- libethcore/CommonEth.h | 59 +---------- libethcore/Dagger.cpp | 2 +- libethcore/Dagger.h | 2 +- libethcore/Exceptions.h | 2 +- libethereum/AccountDiff.cpp | 2 +- libethereum/AccountDiff.h | 2 +- libethereum/AddressState.h | 6 +- libethereum/BlockChain.cpp | 6 +- libethereum/BlockChain.h | 4 +- libethereum/BlockDetails.cpp | 2 +- libethereum/BlockDetails.h | 4 +- libethereum/BlockQueue.cpp | 2 +- libethereum/BlockQueue.h | 4 +- libethereum/CMakeLists.txt | 1 + libethereum/Client.cpp | 2 +- libethereum/Client.h | 6 +- libethereum/CommonNet.h | 4 +- libethereum/Defaults.cpp | 2 +- libethereum/Defaults.h | 2 +- libethereum/EthereumHost.cpp | 2 +- libethereum/EthereumHost.h | 2 +- libethereum/EthereumPeer.cpp | 2 +- libethereum/EthereumPeer.h | 2 +- libethereum/Executive.h | 2 +- libethereum/Manifest.h | 2 +- libethereum/MessageFilter.cpp | 2 +- libethereum/MessageFilter.h | 4 +- libethereum/Miner.h | 2 +- libethereum/PastMessage.h | 2 +- libethereum/State.h | 6 +- libethereum/Transaction.cpp | 4 +- libethereum/Transaction.h | 4 +- libethereum/TransactionQueue.cpp | 2 +- libethereum/TransactionQueue.h | 4 +- libethereum/Utility.h | 2 +- libevm/CMakeLists.txt | 3 +- libevm/ExtVMFace.h | 2 +- libevm/FeeStructure.h | 2 +- libevm/VM.h | 4 +- libevmface/CMakeLists.txt | 2 +- libevmface/Instruction.cpp | 2 +- libevmface/Instruction.h | 4 +- liblll/Assembly.cpp | 2 +- liblll/Assembly.h | 2 +- liblll/CMakeLists.txt | 2 +- liblll/CodeFragment.cpp | 2 +- liblll/CodeFragment.h | 2 +- liblll/Compiler.h | 2 +- liblll/Exceptions.h | 2 +- liblll/Parser.h | 2 +- libp2p/CMakeLists.txt | 2 +- libp2p/Common.h | 6 +- libp2p/Host.cpp | 2 +- libp2p/Host.h | 2 +- libp2p/Session.cpp | 2 +- libp2p/Session.h | 2 +- libp2p/UPnP.cpp | 6 +- libpyserpent/CMakeLists.txt | 2 +- libqethereum/QEthereum.cpp | 10 +- libqethereum/QEthereum.h | 16 +-- libqethereum/QmlEthereum.cpp | 8 +- libqethereum/QmlEthereum.h | 74 +++++++------- libserpent/CMakeLists.txt | 2 +- libwebthree/CMakeLists.txt | 1 + libwebthree/Client.cpp | 2 +- libwebthree/Client.h | 6 +- libwhisper/CMakeLists.txt | 3 +- libwhisper/Common.h | 4 +- libwhisper/WhisperPeer.cpp | 2 +- libwhisper/WhisperPeer.h | 6 +- lllc/CMakeLists.txt | 2 +- lllc/main.cpp | 4 +- neth/main.cpp | 2 +- sc/CMakeLists.txt | 2 +- test/MemTrie.cpp | 4 +- test/MemTrie.h | 4 +- test/TrieHash.cpp | 4 +- test/TrieHash.h | 4 +- test/crypto.cpp | 6 +- test/dagger.cpp | 2 +- test/genesis.cpp | 2 +- test/hexPrefix.cpp | 4 +- test/main.cpp | 4 +- test/rlp.cpp | 6 +- test/trie.cpp | 2 +- test/vm.cpp | 2 +- third/CMakeLists.txt | 2 +- third/MainWin.cpp | 10 +- third/MainWin.h | 12 +-- walleth/MainWin.cpp | 14 +-- walleth/MainWin.h | 8 +- 138 files changed, 554 insertions(+), 357 deletions(-) rename {libethential => libdevcore}/All.h (100%) rename {libethential => libdevcore}/CMakeLists.txt (95%) rename {libethential => libdevcore}/Common.cpp (100%) rename {libethential => libdevcore}/Common.h (100%) rename {libethential => libdevcore}/CommonData.cpp (100%) rename {libethential => libdevcore}/CommonData.h (100%) rename {libethential => libdevcore}/CommonIO.cpp (100%) rename {libethential => libdevcore}/CommonIO.h (100%) rename {libethential => libdevcore}/Exceptions.h (100%) rename {libethential => libdevcore}/FixedHash.cpp (100%) rename {libethential => libdevcore}/FixedHash.h (100%) rename {libethential => libdevcore}/Guards.cpp (100%) rename {libethential => libdevcore}/Guards.h (100%) rename {libethential => libdevcore}/Log.cpp (100%) rename {libethential => libdevcore}/Log.h (100%) rename {libethential => libdevcore}/RLP.cpp (100%) rename {libethential => libdevcore}/RLP.h (99%) rename {libethential => libdevcore}/vector_ref.h (100%) create mode 100644 libdevcrypto/All.h create mode 100644 libdevcrypto/CMakeLists.txt create mode 100644 libdevcrypto/Common.cpp create mode 100644 libdevcrypto/Common.h create mode 100644 libdevcrypto/CryptoHeaders.h rename {libethcore => libdevcrypto}/FileSystem.cpp (96%) rename {libethcore => libdevcrypto}/FileSystem.h (100%) rename {libethcore => libdevcrypto}/MemoryDB.cpp (98%) rename {libethcore => libdevcrypto}/MemoryDB.h (93%) rename {libethcore => libdevcrypto}/OverlayDB.cpp (98%) rename {libethcore => libdevcrypto}/OverlayDB.h (95%) rename {libethcore => libdevcrypto}/SHA3.cpp (100%) rename {libethcore => libdevcrypto}/SHA3.h (96%) rename {libethcore => libdevcrypto}/TrieCommon.cpp (100%) rename {libethcore => libdevcrypto}/TrieCommon.h (98%) rename {libethcore => libdevcrypto}/TrieDB.cpp (96%) rename {libethcore => libdevcrypto}/TrieDB.h (99%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 92d2986e1..46cc0753f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -324,7 +324,7 @@ if(${CMAKE_SYSTEM_NAME} MATCHES "Darwin") include_directories(/usr/local/include) endif() -add_subdirectory(libethential) +add_subdirectory(libdevcore) add_subdirectory(libevmface) add_subdirectory(liblll) add_subdirectory(libserpent) @@ -338,6 +338,7 @@ add_subdirectory(sc) if (NOT LANGUAGES) add_subdirectory(secp256k1) add_subdirectory(libp2p) + add_subdirectory(libdevcrypto) add_subdirectory(libwhisper) add_subdirectory(libethcore) diff --git a/alethzero/CMakeLists.txt b/alethzero/CMakeLists.txt index 9551dd403..3eac0a0b6 100644 --- a/alethzero/CMakeLists.txt +++ b/alethzero/CMakeLists.txt @@ -52,7 +52,7 @@ else () endif () qt5_use_modules(${EXECUTEABLE} Core)# Gui Widgets Network WebKit WebKitWidgets) -target_link_libraries(${EXECUTEABLE} qethereum ethereum evm ethcore secp256k1 gmp ${CRYPTOPP_LS} serpent lll evmface ethential) +target_link_libraries(${EXECUTEABLE} qethereum ethereum evm ethcore devcrypto secp256k1 gmp ${CRYPTOPP_LS} serpent lll evmface devcore) if (APPLE) # First have qt5 install plugins and frameworks diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index 374f8a209..e5ee1cd70 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -51,16 +51,16 @@ using dev::h160; using dev::h256; using dev::u160; using dev::u256; -using dev::eth::Address; +using dev::Address; using dev::eth::BlockInfo; using dev::eth::Client; using dev::eth::Instruction; -using dev::eth::KeyPair; +using dev::KeyPair; using dev::eth::NodeMode; using dev::eth::BlockChain; using dev::p2p::PeerInfo; using dev::RLP; -using dev::eth::Secret; +using dev::Secret; using dev::eth::Transaction; using dev::eth::Executive; @@ -417,7 +417,7 @@ void Main::eval(QString const& _js) ui->jsConsole->setHtml(s); } -QString Main::pretty(dev::eth::Address _a) const +QString Main::pretty(dev::Address _a) const { h256 n; @@ -430,7 +430,7 @@ QString Main::pretty(dev::eth::Address _a) const return fromRaw(n); } -QString Main::render(dev::eth::Address _a) const +QString Main::render(dev::Address _a) const { QString p = pretty(_a); if (!p.isNull()) @@ -1588,7 +1588,7 @@ void Main::on_debug_clicked() m_executiveState = m_client->postState(); m_currentExecution = unique_ptr(new Executive(m_executiveState)); Transaction t; - t.nonce = m_executiveState.transactionsFrom(dev::eth::toAddress(s)); + t.nonce = m_executiveState.transactionsFrom(dev::toAddress(s)); t.value = value(); t.gasPrice = gasPrice(); t.gas = ui->gas->value(); diff --git a/alethzero/MainWin.h b/alethzero/MainWin.h index e15f5ce2e..2902db3a2 100644 --- a/alethzero/MainWin.h +++ b/alethzero/MainWin.h @@ -27,7 +27,7 @@ #include #include #include -#include +#include #include #include #include @@ -47,7 +47,7 @@ class QQuickView; struct WorldState { uint64_t steps; - dev::eth::Address cur; + dev::Address cur; dev::u256 curPC; dev::eth::Instruction inst; dev::bigint newMemSize; @@ -71,7 +71,7 @@ public: dev::eth::Client* client() { return m_client.get(); } - QList const& owned() const { return m_myKeys; } + QList const& owned() const { return m_myKeys; } public slots: void load(QString _file); @@ -146,7 +146,7 @@ signals: void poll(); private: - QString pretty(dev::eth::Address _a) const; + QString pretty(dev::Address _a) const; QString prettyU256(dev::u256 _n) const; QString lookup(QString const& _n) const; @@ -155,8 +155,8 @@ private: void initDebugger(); void updateDebugger(); void debugFinished(); - QString render(dev::eth::Address _a) const; - dev::eth::Address fromString(QString const& _a) const; + QString render(dev::Address _a) const; + dev::Address fromString(QString const& _a) const; std::string renderDiff(dev::eth::StateDiff const& _d) const; void alterDebugStateGroup(bool _enable) const; @@ -208,11 +208,11 @@ private: QByteArray m_peers; QStringList m_servers; - QList m_myKeys; + QList m_myKeys; QString m_privateChain; bool m_keysChanged = false; dev::bytes m_data; - dev::eth::Address m_nameReg; + dev::Address m_nameReg; unsigned m_backupGas; diff --git a/eth/CommonJS.h b/eth/CommonJS.h index 71a0c46d8..caa7e6651 100644 --- a/eth/CommonJS.h +++ b/eth/CommonJS.h @@ -23,10 +23,10 @@ #pragma once #include -#include -#include -#include -#include +#include +#include +#include +#include #include namespace dev diff --git a/eth/EthStubServer.h b/eth/EthStubServer.h index 1805cb410..28af916a2 100644 --- a/eth/EthStubServer.h +++ b/eth/EthStubServer.h @@ -23,12 +23,13 @@ #include #include +#include #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wunused-parameter" #include "abstractethstubserver.h" #pragma GCC diagnostic pop -namespace dev { namespace eth { class Client; class KeyPair; } } +namespace dev { namespace eth { class Client; } class KeyPair; } class EthStubServer: public AbstractEthStubServer { @@ -54,10 +55,10 @@ public: virtual Json::Value lastBlock(); virtual std::string lll(const std::string& s); virtual Json::Value block(const std::string&); - void setKeys(std::vector _keys) { m_keys = _keys; } + void setKeys(std::vector _keys) { m_keys = _keys; } private: dev::eth::Client& m_client; - std::vector m_keys; + std::vector m_keys; Json::Value jsontypeToValue(int); Json::Value blockJson(const std::string&); }; diff --git a/eth/main.cpp b/eth/main.cpp index 576423a5f..bd71f6677 100644 --- a/eth/main.cpp +++ b/eth/main.cpp @@ -29,7 +29,7 @@ #if ETH_JSONRPC #include #endif -#include +#include #include #include #include diff --git a/exp/main.cpp b/exp/main.cpp index 1de09e4ac..c49b13e44 100644 --- a/exp/main.cpp +++ b/exp/main.cpp @@ -26,10 +26,10 @@ #include #include #endif -#include -#include -#include -#include +#include +#include +#include +#include #include #include #if 0 diff --git a/libethential/All.h b/libdevcore/All.h similarity index 100% rename from libethential/All.h rename to libdevcore/All.h diff --git a/libethential/CMakeLists.txt b/libdevcore/CMakeLists.txt similarity index 95% rename from libethential/CMakeLists.txt rename to libdevcore/CMakeLists.txt index 139e80ce4..3f3ecb619 100644 --- a/libethential/CMakeLists.txt +++ b/libdevcore/CMakeLists.txt @@ -8,7 +8,7 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DSTATICLIB") aux_source_directory(. SRC_LIST) -set(EXECUTABLE ethential) +set(EXECUTABLE devcore) # set(CMAKE_INSTALL_PREFIX ../lib) if(ETH_STATIC) @@ -20,7 +20,7 @@ file(GLOB HEADERS "*.h") include_directories(..) -target_link_libraries(${EXECUTABLE} ethential) +target_link_libraries(${EXECUTABLE} devcore) if("${TARGET_PLATFORM}" STREQUAL "w64") include_directories(/usr/x86_64-w64-mingw32/include/cryptopp) diff --git a/libethential/Common.cpp b/libdevcore/Common.cpp similarity index 100% rename from libethential/Common.cpp rename to libdevcore/Common.cpp diff --git a/libethential/Common.h b/libdevcore/Common.h similarity index 100% rename from libethential/Common.h rename to libdevcore/Common.h diff --git a/libethential/CommonData.cpp b/libdevcore/CommonData.cpp similarity index 100% rename from libethential/CommonData.cpp rename to libdevcore/CommonData.cpp diff --git a/libethential/CommonData.h b/libdevcore/CommonData.h similarity index 100% rename from libethential/CommonData.h rename to libdevcore/CommonData.h diff --git a/libethential/CommonIO.cpp b/libdevcore/CommonIO.cpp similarity index 100% rename from libethential/CommonIO.cpp rename to libdevcore/CommonIO.cpp diff --git a/libethential/CommonIO.h b/libdevcore/CommonIO.h similarity index 100% rename from libethential/CommonIO.h rename to libdevcore/CommonIO.h diff --git a/libethential/Exceptions.h b/libdevcore/Exceptions.h similarity index 100% rename from libethential/Exceptions.h rename to libdevcore/Exceptions.h diff --git a/libethential/FixedHash.cpp b/libdevcore/FixedHash.cpp similarity index 100% rename from libethential/FixedHash.cpp rename to libdevcore/FixedHash.cpp diff --git a/libethential/FixedHash.h b/libdevcore/FixedHash.h similarity index 100% rename from libethential/FixedHash.h rename to libdevcore/FixedHash.h diff --git a/libethential/Guards.cpp b/libdevcore/Guards.cpp similarity index 100% rename from libethential/Guards.cpp rename to libdevcore/Guards.cpp diff --git a/libethential/Guards.h b/libdevcore/Guards.h similarity index 100% rename from libethential/Guards.h rename to libdevcore/Guards.h diff --git a/libethential/Log.cpp b/libdevcore/Log.cpp similarity index 100% rename from libethential/Log.cpp rename to libdevcore/Log.cpp diff --git a/libethential/Log.h b/libdevcore/Log.h similarity index 100% rename from libethential/Log.h rename to libdevcore/Log.h diff --git a/libethential/RLP.cpp b/libdevcore/RLP.cpp similarity index 100% rename from libethential/RLP.cpp rename to libdevcore/RLP.cpp diff --git a/libethential/RLP.h b/libdevcore/RLP.h similarity index 99% rename from libethential/RLP.h rename to libdevcore/RLP.h index 0398467e7..d3cb5eed1 100644 --- a/libethential/RLP.h +++ b/libdevcore/RLP.h @@ -28,10 +28,10 @@ #include #include #include -#include -#include -#include -#include +#include +#include +#include +#include namespace dev { diff --git a/libethential/vector_ref.h b/libdevcore/vector_ref.h similarity index 100% rename from libethential/vector_ref.h rename to libdevcore/vector_ref.h diff --git a/libdevcrypto/All.h b/libdevcrypto/All.h new file mode 100644 index 000000000..6d1844857 --- /dev/null +++ b/libdevcrypto/All.h @@ -0,0 +1,9 @@ +#pragma once + +#include "Common.h" +#include "FileSystem.h" +#include "MemoryDB.h" +#include "OverlayDB.h" +#include "SHA3.h" +#include "TrieCommon.h" +#include "TrieDB.h" diff --git a/libdevcrypto/CMakeLists.txt b/libdevcrypto/CMakeLists.txt new file mode 100644 index 000000000..e7f112f95 --- /dev/null +++ b/libdevcrypto/CMakeLists.txt @@ -0,0 +1,56 @@ +cmake_policy(SET CMP0015 NEW) + +aux_source_directory(. SRC_LIST) + +set(EXECUTABLE devcrypto) + +# set(CMAKE_INSTALL_PREFIX ../lib) +if(ETH_STATIC) + add_library(${EXECUTABLE} STATIC ${SRC_LIST}) +else() + add_library(${EXECUTABLE} SHARED ${SRC_LIST}) +endif() + +file(GLOB HEADERS "*.h") + +include_directories(..) + +target_link_libraries(${EXECUTABLE} devcore) +target_link_libraries(${EXECUTABLE} secp256k1) +target_link_libraries(${EXECUTABLE} gmp) +target_link_libraries(${EXECUTABLE} ${LEVELDB_LS}) +target_link_libraries(${EXECUTABLE} ${CRYPTOPP_LS}) + +if("${TARGET_PLATFORM}" STREQUAL "w64") + target_link_libraries(${EXECUTABLE} boost_system-mt-s) + target_link_libraries(${EXECUTABLE} boost_filesystem-mt-s) + target_link_libraries(${EXECUTABLE} boost_thread_win32-mt-s) + target_link_libraries(${EXECUTABLE} iphlpapi) + target_link_libraries(${EXECUTABLE} ws2_32) + target_link_libraries(${EXECUTABLE} mswsock) + target_link_libraries(${EXECUTABLE} shlwapi) +elseif (APPLE) + # Latest mavericks boost libraries only come with -mt + target_link_libraries(${EXECUTABLE} boost_system-mt) + target_link_libraries(${EXECUTABLE} boost_filesystem-mt) + target_link_libraries(${EXECUTABLE} boost_thread-mt) + find_package(Threads REQUIRED) + target_link_libraries(${EXECUTABLE} ${CMAKE_THREAD_LIBS_INIT}) +elseif (UNIX) + find_package(Boost 1.53 REQUIRED COMPONENTS filesystem) + target_link_libraries(${EXECUTABLE} ${Boost_SYSTEM_LIBRARY}) + target_link_libraries(${EXECUTABLE} ${Boost_FILESYSTEM_LIBRARY}) + target_link_libraries(${EXECUTABLE} ${Boost_THREAD_LIBRARY}) + target_link_libraries(${EXECUTABLE} ${Boost_DATE_TIME_LIBRARY}) + target_link_libraries(${EXECUTABLE} ${CMAKE_THREAD_LIBS_INIT}) +else () + target_link_libraries(${EXECUTABLE} boost_system) + target_link_libraries(${EXECUTABLE} boost_filesystem) + target_link_libraries(${EXECUTABLE} boost_thread) + find_package(Threads REQUIRED) + target_link_libraries(${EXECUTABLE} ${CMAKE_THREAD_LIBS_INIT}) +endif () + +install( TARGETS ${EXECUTABLE} ARCHIVE DESTINATION lib LIBRARY DESTINATION lib ) +install( FILES ${HEADERS} DESTINATION include/${EXECUTABLE} ) + diff --git a/libdevcrypto/Common.cpp b/libdevcrypto/Common.cpp new file mode 100644 index 000000000..ddc7b0b4b --- /dev/null +++ b/libdevcrypto/Common.cpp @@ -0,0 +1,104 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file CommonEth.cpp + * @author Gav Wood + * @date 2014 + */ + +#include "Common.h" +#include +#include +#include "SHA3.h" +using namespace std; +using namespace dev; +using namespace dev::eth; + +//#define ETH_ADDRESS_DEBUG 1 + +Address dev::toAddress(Secret _private) +{ + secp256k1_start(); + + byte pubkey[65]; + int pubkeylen = 65; + int ok = secp256k1_ecdsa_seckey_verify(_private.data()); + if (!ok) + return Address(); + ok = secp256k1_ecdsa_pubkey_create(pubkey, &pubkeylen, _private.data(), 0); + assert(pubkeylen == 65); + if (!ok) + return Address(); + ok = secp256k1_ecdsa_pubkey_verify(pubkey, 65); + if (!ok) + return Address(); + auto ret = right160(dev::eth::sha3(bytesConstRef(&(pubkey[1]), 64))); +#if ETH_ADDRESS_DEBUG + cout << "---- ADDRESS -------------------------------" << endl; + cout << "SEC: " << _private << endl; + cout << "PUB: " << toHex(bytesConstRef(&(pubkey[1]), 64)) << endl; + cout << "ADR: " << ret << endl; +#endif + return ret; +} + +KeyPair KeyPair::create() +{ + secp256k1_start(); + static std::mt19937_64 s_eng(time(0)); + std::uniform_int_distribution d(0, 255); + + for (int i = 0; i < 100; ++i) + { + h256 sec; + for (unsigned i = 0; i < 32; ++i) + sec[i] = (byte)d(s_eng); + + KeyPair ret(sec); + if (ret.address()) + return ret; + } + return KeyPair(); +} + +KeyPair::KeyPair(h256 _sec): + m_secret(_sec) +{ + int ok = secp256k1_ecdsa_seckey_verify(m_secret.data()); + if (!ok) + return; + + byte pubkey[65]; + int pubkeylen = 65; + ok = secp256k1_ecdsa_pubkey_create(pubkey, &pubkeylen, m_secret.data(), 0); + if (!ok || pubkeylen != 65) + return; + + ok = secp256k1_ecdsa_pubkey_verify(pubkey, 65); + if (!ok) + return; + + m_secret = m_secret; + memcpy(m_public.data(), &(pubkey[1]), 64); + m_address = right160(dev::eth::sha3(bytesConstRef(&(pubkey[1]), 64))); + +#if ETH_ADDRESS_DEBUG + cout << "---- ADDRESS -------------------------------" << endl; + cout << "SEC: " << m_secret << endl; + cout << "PUB: " << m_public << endl; + cout << "ADR: " << m_address << endl; +#endif +} diff --git a/libdevcrypto/Common.h b/libdevcrypto/Common.h new file mode 100644 index 000000000..2694d7f8d --- /dev/null +++ b/libdevcrypto/Common.h @@ -0,0 +1,86 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file CommonEth.h + * @author Gav Wood + * @date 2014 + * + * Ethereum-specific data structures & algorithms. + */ + +#pragma once + +#include +#include + +namespace dev +{ + +/// A secret key: 32 bytes. +/// @NOTE This is not endian-specific; it's just a bunch of bytes. +using Secret = h256; + +/// A public key: 64 bytes. +/// @NOTE This is not endian-specific; it's just a bunch of bytes. +using Public = h512; + +/// An Ethereum address: 20 bytes. +/// @NOTE This is not endian-specific; it's just a bunch of bytes. +using Address = h160; + +/// A vector of Ethereum addresses. +using Addresses = h160s; + +/// Convert a private key into the public key equivalent. +/// @returns 0 if it's not a valid private key. +Address toAddress(h256 _private); + +/// Simple class that represents a "key pair". +/// All of the data of the class can be regenerated from the secret key (m_secret) alone. +/// Actually stores a tuplet of secret, public and address (the right 160-bits of the public). +class KeyPair +{ +public: + /// Null constructor. + KeyPair() {} + + /// Normal constructor - populates object from the given secret key. + KeyPair(Secret _k); + + /// Create a new, randomly generated object. + static KeyPair create(); + + /// Retrieve the secret key. + Secret const& secret() const { return m_secret; } + /// Retrieve the secret key. + Secret const& sec() const { return m_secret; } + + /// Retrieve the public key. + Public const& pub() const { return m_public; } + + /// Retrieve the associated address of the public key. + Address const& address() const { return m_address; } + + bool operator==(KeyPair const& _c) const { return m_secret == _c.m_secret; } + bool operator!=(KeyPair const& _c) const { return m_secret != _c.m_secret; } + +private: + Secret m_secret; + Public m_public; + Address m_address; +}; + +} diff --git a/libdevcrypto/CryptoHeaders.h b/libdevcrypto/CryptoHeaders.h new file mode 100644 index 000000000..4ff63f1d7 --- /dev/null +++ b/libdevcrypto/CryptoHeaders.h @@ -0,0 +1,36 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file CryptoHeaders.h + * @author Tim Hughes + * @date 2014 + */ +#pragma once + +// need to leave this one disabled +#pragma GCC diagnostic ignored "-Wunused-function" + +#pragma warning(push) +#pragma warning(disable:4100 4244) +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wconversion" +#pragma GCC diagnostic ignored "-Wunused-parameter" +#include +#include +#include +#include +#pragma warning(pop) +#pragma GCC diagnostic pop diff --git a/libethcore/FileSystem.cpp b/libdevcrypto/FileSystem.cpp similarity index 96% rename from libethcore/FileSystem.cpp rename to libdevcrypto/FileSystem.cpp index 953789d9b..1fb38b57f 100644 --- a/libethcore/FileSystem.cpp +++ b/libdevcrypto/FileSystem.cpp @@ -22,8 +22,8 @@ */ #include "FileSystem.h" -#include -#include +#include +#include #ifdef _WIN32 #include diff --git a/libethcore/FileSystem.h b/libdevcrypto/FileSystem.h similarity index 100% rename from libethcore/FileSystem.h rename to libdevcrypto/FileSystem.h diff --git a/libethcore/MemoryDB.cpp b/libdevcrypto/MemoryDB.cpp similarity index 98% rename from libethcore/MemoryDB.cpp rename to libdevcrypto/MemoryDB.cpp index 6fe8091d6..4480417fc 100644 --- a/libethcore/MemoryDB.cpp +++ b/libdevcrypto/MemoryDB.cpp @@ -19,7 +19,7 @@ * @date 2014 */ -#include +#include #include "MemoryDB.h" using namespace std; using namespace dev; diff --git a/libethcore/MemoryDB.h b/libdevcrypto/MemoryDB.h similarity index 93% rename from libethcore/MemoryDB.h rename to libdevcrypto/MemoryDB.h index 76c7e6c6b..59435d3bf 100644 --- a/libethcore/MemoryDB.h +++ b/libdevcrypto/MemoryDB.h @@ -22,10 +22,10 @@ #pragma once #include -#include -#include -#include -#include +#include +#include +#include +#include namespace dev { diff --git a/libethcore/OverlayDB.cpp b/libdevcrypto/OverlayDB.cpp similarity index 98% rename from libethcore/OverlayDB.cpp rename to libdevcrypto/OverlayDB.cpp index b009841cd..460609fb3 100644 --- a/libethcore/OverlayDB.cpp +++ b/libdevcrypto/OverlayDB.cpp @@ -19,7 +19,7 @@ * @date 2014 */ -#include +#include #include "OverlayDB.h" using namespace std; using namespace dev; diff --git a/libethcore/OverlayDB.h b/libdevcrypto/OverlayDB.h similarity index 95% rename from libethcore/OverlayDB.h rename to libdevcrypto/OverlayDB.h index c13b20e6b..9db4eaed6 100644 --- a/libethcore/OverlayDB.h +++ b/libdevcrypto/OverlayDB.h @@ -27,8 +27,8 @@ #pragma warning(pop) #include -#include -#include +#include +#include #include "MemoryDB.h" namespace ldb = leveldb; diff --git a/libethcore/SHA3.cpp b/libdevcrypto/SHA3.cpp similarity index 100% rename from libethcore/SHA3.cpp rename to libdevcrypto/SHA3.cpp diff --git a/libethcore/SHA3.h b/libdevcrypto/SHA3.h similarity index 96% rename from libethcore/SHA3.h rename to libdevcrypto/SHA3.h index 2a76ea56f..1b37846b3 100644 --- a/libethcore/SHA3.h +++ b/libdevcrypto/SHA3.h @@ -24,8 +24,8 @@ #pragma once #include -#include -#include +#include +#include namespace dev { diff --git a/libethcore/TrieCommon.cpp b/libdevcrypto/TrieCommon.cpp similarity index 100% rename from libethcore/TrieCommon.cpp rename to libdevcrypto/TrieCommon.cpp diff --git a/libethcore/TrieCommon.h b/libdevcrypto/TrieCommon.h similarity index 98% rename from libethcore/TrieCommon.h rename to libdevcrypto/TrieCommon.h index afe527e3b..ecfb4de67 100644 --- a/libethcore/TrieCommon.h +++ b/libdevcrypto/TrieCommon.h @@ -21,8 +21,8 @@ #pragma once -#include -#include +#include +#include namespace dev { diff --git a/libethcore/TrieDB.cpp b/libdevcrypto/TrieDB.cpp similarity index 96% rename from libethcore/TrieDB.cpp rename to libdevcrypto/TrieDB.cpp index c6c1262bc..3c551d2b4 100644 --- a/libethcore/TrieDB.cpp +++ b/libdevcrypto/TrieDB.cpp @@ -19,7 +19,7 @@ * @date 2014 */ -#include +#include #include "TrieDB.h" using namespace std; using namespace dev; diff --git a/libethcore/TrieDB.h b/libdevcrypto/TrieDB.h similarity index 99% rename from libethcore/TrieDB.h rename to libdevcrypto/TrieDB.h index ff87cc4f8..8ee6f3bca 100644 --- a/libethcore/TrieDB.h +++ b/libdevcrypto/TrieDB.h @@ -28,9 +28,9 @@ #include #include -#include -#include -#include +#include +#include +#include #include "MemoryDB.h" #include "OverlayDB.h" #include "TrieCommon.h" diff --git a/libethcore/BlockInfo.cpp b/libethcore/BlockInfo.cpp index d1028c21e..9ceb4c708 100644 --- a/libethcore/BlockInfo.cpp +++ b/libethcore/BlockInfo.cpp @@ -21,9 +21,9 @@ #if !ETH_LANGUAGES -#include -#include -#include +#include +#include +#include #include "Dagger.h" #include "Exceptions.h" #include "BlockInfo.h" diff --git a/libethcore/BlockInfo.h b/libethcore/BlockInfo.h index 54efd8c29..8186acaee 100644 --- a/libethcore/BlockInfo.h +++ b/libethcore/BlockInfo.h @@ -21,8 +21,8 @@ #pragma once -#include -#include +#include +#include #include "CommonEth.h" namespace dev diff --git a/libethcore/CMakeLists.txt b/libethcore/CMakeLists.txt index 0f9c2023a..6aba644f1 100644 --- a/libethcore/CMakeLists.txt +++ b/libethcore/CMakeLists.txt @@ -15,7 +15,8 @@ file(GLOB HEADERS "*.h") include_directories(..) -target_link_libraries(${EXECUTABLE} ethential) +target_link_libraries(${EXECUTABLE} devcrypto) +target_link_libraries(${EXECUTABLE} devcore) target_link_libraries(${EXECUTABLE} secp256k1) target_link_libraries(${EXECUTABLE} gmp) target_link_libraries(${EXECUTABLE} ${LEVELDB_LS}) diff --git a/libethcore/CommonEth.cpp b/libethcore/CommonEth.cpp index c8632274c..f96fb5480 100644 --- a/libethcore/CommonEth.cpp +++ b/libethcore/CommonEth.cpp @@ -22,7 +22,7 @@ #include "CommonEth.h" #include #include -#include +#include #include "Exceptions.h" using namespace std; using namespace dev; @@ -111,51 +111,3 @@ Address toAddress(Secret _private) } }} - -KeyPair KeyPair::create() -{ - secp256k1_start(); - static std::mt19937_64 s_eng(time(0)); - std::uniform_int_distribution d(0, 255); - - for (int i = 0; i < 100; ++i) - { - h256 sec; - for (unsigned i = 0; i < 32; ++i) - sec[i] = (byte)d(s_eng); - - KeyPair ret(sec); - if (ret.address()) - return ret; - } - return KeyPair(); -} - -KeyPair::KeyPair(h256 _sec): - m_secret(_sec) -{ - int ok = secp256k1_ecdsa_seckey_verify(m_secret.data()); - if (!ok) - return; - - byte pubkey[65]; - int pubkeylen = 65; - ok = secp256k1_ecdsa_pubkey_create(pubkey, &pubkeylen, m_secret.data(), 0); - if (!ok || pubkeylen != 65) - return; - - ok = secp256k1_ecdsa_pubkey_verify(pubkey, 65); - if (!ok) - return; - - m_secret = m_secret; - memcpy(m_public.data(), &(pubkey[1]), 64); - m_address = right160(dev::eth::sha3(bytesConstRef(&(pubkey[1]), 64))); - -#if ETH_ADDRESS_DEBUG - cout << "---- ADDRESS -------------------------------" << endl; - cout << "SEC: " << m_secret << endl; - cout << "PUB: " << m_public << endl; - cout << "ADR: " << m_address << endl; -#endif -} diff --git a/libethcore/CommonEth.h b/libethcore/CommonEth.h index 49b8a0c4f..1965a881a 100644 --- a/libethcore/CommonEth.h +++ b/libethcore/CommonEth.h @@ -23,8 +23,9 @@ #pragma once -#include -#include +#include +#include +#include namespace dev { @@ -37,21 +38,6 @@ extern const unsigned c_protocolVersion; /// Current database version. extern const unsigned c_databaseVersion; -/// A secret key: 32 bytes. -/// @NOTE This is not endian-specific; it's just a bunch of bytes. -using Secret = h256; - -/// A public key: 64 bytes. -/// @NOTE This is not endian-specific; it's just a bunch of bytes. -using Public = h512; - -/// An Ethereum address: 20 bytes. -/// @NOTE This is not endian-specific; it's just a bunch of bytes. -using Address = h160; - -/// A vector of Ethereum addresses. -using Addresses = h160s; - /// User-friendly string representation of the amount _b in wei. std::string formatBalance(u256 _b); @@ -79,44 +65,5 @@ static const u256 Mwei = u256(1000000); static const u256 Kwei = u256(1000); static const u256 wei = u256(1); -/// Convert a private key into the public key equivalent. -/// @returns 0 if it's not a valid private key. -Address toAddress(h256 _private); - -/// Simple class that represents a "key pair". -/// All of the data of the class can be regenerated from the secret key (m_secret) alone. -/// Actually stores a tuplet of secret, public and address (the right 160-bits of the public). -class KeyPair -{ -public: - /// Null constructor. - KeyPair() {} - - /// Normal constructor - populates object from the given secret key. - KeyPair(Secret _k); - - /// Create a new, randomly generated object. - static KeyPair create(); - - /// Retrieve the secret key. - Secret const& secret() const { return m_secret; } - /// Retrieve the secret key. - Secret const& sec() const { return m_secret; } - - /// Retrieve the public key. - Public const& pub() const { return m_public; } - - /// Retrieve the associated address of the public key. - Address const& address() const { return m_address; } - - bool operator==(KeyPair const& _c) const { return m_secret == _c.m_secret; } - bool operator!=(KeyPair const& _c) const { return m_secret != _c.m_secret; } - -private: - Secret m_secret; - Public m_public; - Address m_address; -}; - } } diff --git a/libethcore/Dagger.cpp b/libethcore/Dagger.cpp index cc37fb98d..956557b64 100644 --- a/libethcore/Dagger.cpp +++ b/libethcore/Dagger.cpp @@ -27,7 +27,7 @@ #include #include #include -#include +#include #include "Dagger.h" using namespace std; using namespace std::chrono; diff --git a/libethcore/Dagger.h b/libethcore/Dagger.h index 58f6e3f80..c32ef38be 100644 --- a/libethcore/Dagger.h +++ b/libethcore/Dagger.h @@ -23,7 +23,7 @@ #pragma once -#include +#include #include "CommonEth.h" #define FAKE_DAGGER 1 diff --git a/libethcore/Exceptions.h b/libethcore/Exceptions.h index 175add708..c936b1f1b 100644 --- a/libethcore/Exceptions.h +++ b/libethcore/Exceptions.h @@ -1,6 +1,6 @@ #pragma once -#include +#include namespace dev { diff --git a/libethereum/AccountDiff.cpp b/libethereum/AccountDiff.cpp index 08d6d8859..c434086fa 100644 --- a/libethereum/AccountDiff.cpp +++ b/libethereum/AccountDiff.cpp @@ -21,7 +21,7 @@ #include "AccountDiff.h" -#include +#include using namespace std; using namespace dev; using namespace dev::eth; diff --git a/libethereum/AccountDiff.h b/libethereum/AccountDiff.h index 4d8f63f0a..d4c30ead5 100644 --- a/libethereum/AccountDiff.h +++ b/libethereum/AccountDiff.h @@ -21,7 +21,7 @@ #pragma once -#include +#include #include namespace dev diff --git a/libethereum/AddressState.h b/libethereum/AddressState.h index c7df6523b..668e9225f 100644 --- a/libethereum/AddressState.h +++ b/libethereum/AddressState.h @@ -21,9 +21,9 @@ #pragma once -#include -#include -#include +#include +#include +#include namespace dev { diff --git a/libethereum/BlockChain.cpp b/libethereum/BlockChain.cpp index e9dfa0519..c23258f08 100644 --- a/libethereum/BlockChain.cpp +++ b/libethereum/BlockChain.cpp @@ -22,9 +22,9 @@ #include "BlockChain.h" #include -#include -#include -#include +#include +#include +#include #include #include #include diff --git a/libethereum/BlockChain.h b/libethereum/BlockChain.h index 200740782..c0b3988a9 100644 --- a/libethereum/BlockChain.h +++ b/libethereum/BlockChain.h @@ -27,10 +27,10 @@ #pragma warning(pop) #include -#include +#include #include #include -#include +#include #include "BlockDetails.h" #include "AddressState.h" #include "BlockQueue.h" diff --git a/libethereum/BlockDetails.cpp b/libethereum/BlockDetails.cpp index eac40920e..5a61eb037 100644 --- a/libethereum/BlockDetails.cpp +++ b/libethereum/BlockDetails.cpp @@ -21,7 +21,7 @@ #include "BlockDetails.h" -#include +#include using namespace std; using namespace dev; using namespace dev::eth; diff --git a/libethereum/BlockDetails.h b/libethereum/BlockDetails.h index 21e5e7d13..9cb49a21c 100644 --- a/libethereum/BlockDetails.h +++ b/libethereum/BlockDetails.h @@ -26,8 +26,8 @@ #include #pragma warning(pop) -#include -#include +#include +#include #include "Manifest.h" namespace ldb = leveldb; diff --git a/libethereum/BlockQueue.cpp b/libethereum/BlockQueue.cpp index 00b64c8bc..f8bc7d64c 100644 --- a/libethereum/BlockQueue.cpp +++ b/libethereum/BlockQueue.cpp @@ -21,7 +21,7 @@ #include "BlockQueue.h" -#include +#include #include #include #include "BlockChain.h" diff --git a/libethereum/BlockQueue.h b/libethereum/BlockQueue.h index b8586a553..c68e1f1e4 100644 --- a/libethereum/BlockQueue.h +++ b/libethereum/BlockQueue.h @@ -22,9 +22,9 @@ #pragma once #include -#include +#include #include "libethcore/CommonEth.h" -#include +#include namespace dev { diff --git a/libethereum/CMakeLists.txt b/libethereum/CMakeLists.txt index 43812b7d3..128dd5999 100644 --- a/libethereum/CMakeLists.txt +++ b/libethereum/CMakeLists.txt @@ -21,6 +21,7 @@ target_link_libraries(${EXECUTABLE} evm) target_link_libraries(${EXECUTABLE} lll) target_link_libraries(${EXECUTABLE} whisper) target_link_libraries(${EXECUTABLE} p2p) +target_link_libraries(${EXECUTABLE} devcrypto) target_link_libraries(${EXECUTABLE} ethcore) target_link_libraries(${EXECUTABLE} secp256k1) if(MINIUPNPC_LS) diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index afefa1d4f..4c91c9aaf 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include #include "Defaults.h" #include "EthereumHost.h" diff --git a/libethereum/Client.h b/libethereum/Client.h index 254fd1631..76e903b31 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -26,9 +26,9 @@ #include #include #include -#include -#include -#include +#include +#include +#include #include #include #include diff --git a/libethereum/CommonNet.h b/libethereum/CommonNet.h index a4484ffb8..13f68a977 100644 --- a/libethereum/CommonNet.h +++ b/libethereum/CommonNet.h @@ -25,8 +25,8 @@ #include #include -#include -#include +#include +#include namespace dev { diff --git a/libethereum/Defaults.cpp b/libethereum/Defaults.cpp index 17d773427..febe53d84 100644 --- a/libethereum/Defaults.cpp +++ b/libethereum/Defaults.cpp @@ -21,7 +21,7 @@ #include "Defaults.h" -#include +#include using namespace std; using namespace dev; using namespace dev::eth; diff --git a/libethereum/Defaults.h b/libethereum/Defaults.h index e0478455d..91d5872f7 100644 --- a/libethereum/Defaults.h +++ b/libethereum/Defaults.h @@ -21,7 +21,7 @@ #pragma once -#include +#include namespace dev { diff --git a/libethereum/EthereumHost.cpp b/libethereum/EthereumHost.cpp index 0ba544248..069ab4291 100644 --- a/libethereum/EthereumHost.cpp +++ b/libethereum/EthereumHost.cpp @@ -26,7 +26,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/libethereum/EthereumHost.h b/libethereum/EthereumHost.h index e0cf5200b..bd0a5d0bc 100644 --- a/libethereum/EthereumHost.h +++ b/libethereum/EthereumHost.h @@ -28,7 +28,7 @@ #include #include #include -#include +#include #include #include #include "CommonNet.h" diff --git a/libethereum/EthereumPeer.cpp b/libethereum/EthereumPeer.cpp index 45165d348..1211ac03d 100644 --- a/libethereum/EthereumPeer.cpp +++ b/libethereum/EthereumPeer.cpp @@ -22,7 +22,7 @@ #include "EthereumPeer.h" #include -#include +#include #include #include #include "BlockChain.h" diff --git a/libethereum/EthereumPeer.h b/libethereum/EthereumPeer.h index c0c75f646..2e166b1d0 100644 --- a/libethereum/EthereumPeer.h +++ b/libethereum/EthereumPeer.h @@ -26,7 +26,7 @@ #include #include #include -#include +#include #include #include #include "CommonNet.h" diff --git a/libethereum/Executive.h b/libethereum/Executive.h index 80e1035c9..82b7df7e9 100644 --- a/libethereum/Executive.h +++ b/libethereum/Executive.h @@ -22,7 +22,7 @@ #pragma once #include -#include +#include #include #include #include diff --git a/libethereum/Manifest.h b/libethereum/Manifest.h index 2f83d0cab..12c40b3a9 100644 --- a/libethereum/Manifest.h +++ b/libethereum/Manifest.h @@ -23,7 +23,7 @@ #include #include -#include +#include #include namespace dev diff --git a/libethereum/MessageFilter.cpp b/libethereum/MessageFilter.cpp index 537bc4a9b..d88bc8a9b 100644 --- a/libethereum/MessageFilter.cpp +++ b/libethereum/MessageFilter.cpp @@ -21,7 +21,7 @@ #include "MessageFilter.h" -#include +#include #include "State.h" using namespace std; using namespace dev; diff --git a/libethereum/MessageFilter.h b/libethereum/MessageFilter.h index 8c44ce449..c9fb4e51c 100644 --- a/libethereum/MessageFilter.h +++ b/libethereum/MessageFilter.h @@ -21,8 +21,8 @@ #pragma once -#include -#include +#include +#include #include #include "PastMessage.h" diff --git a/libethereum/Miner.h b/libethereum/Miner.h index 46f59d0f5..d8ae20cc1 100644 --- a/libethereum/Miner.h +++ b/libethereum/Miner.h @@ -25,7 +25,7 @@ #include #include #include -#include +#include #include #include "State.h" diff --git a/libethereum/PastMessage.h b/libethereum/PastMessage.h index 1b7f79527..6446eff5a 100644 --- a/libethereum/PastMessage.h +++ b/libethereum/PastMessage.h @@ -21,7 +21,7 @@ #pragma once -#include +#include #include #include "Manifest.h" diff --git a/libethereum/State.h b/libethereum/State.h index 1a45b89ba..335f27168 100644 --- a/libethereum/State.h +++ b/libethereum/State.h @@ -24,9 +24,9 @@ #include #include #include -#include -#include -#include +#include +#include +#include #include #include #include diff --git a/libethereum/Transaction.cpp b/libethereum/Transaction.cpp index c464ead46..f607dc3b4 100644 --- a/libethereum/Transaction.cpp +++ b/libethereum/Transaction.cpp @@ -20,8 +20,8 @@ */ #include -#include -#include +#include +#include #include #include "Transaction.h" using namespace std; diff --git a/libethereum/Transaction.h b/libethereum/Transaction.h index 7aba44dcd..5b327d96b 100644 --- a/libethereum/Transaction.h +++ b/libethereum/Transaction.h @@ -21,8 +21,8 @@ #pragma once -#include -#include +#include +#include #include namespace dev diff --git a/libethereum/TransactionQueue.cpp b/libethereum/TransactionQueue.cpp index 4fb6c2299..5275732cb 100644 --- a/libethereum/TransactionQueue.cpp +++ b/libethereum/TransactionQueue.cpp @@ -21,7 +21,7 @@ #include "TransactionQueue.h" -#include +#include #include #include "Transaction.h" using namespace std; diff --git a/libethereum/TransactionQueue.h b/libethereum/TransactionQueue.h index 6cf3bc250..690dc658a 100644 --- a/libethereum/TransactionQueue.h +++ b/libethereum/TransactionQueue.h @@ -22,9 +22,9 @@ #pragma once #include -#include +#include #include "libethcore/CommonEth.h" -#include +#include namespace dev { diff --git a/libethereum/Utility.h b/libethereum/Utility.h index 0f9d178ff..93c3d54c8 100644 --- a/libethereum/Utility.h +++ b/libethereum/Utility.h @@ -22,7 +22,7 @@ #pragma once #include -#include +#include namespace dev { diff --git a/libevm/CMakeLists.txt b/libevm/CMakeLists.txt index 2365821b4..f19119f83 100644 --- a/libevm/CMakeLists.txt +++ b/libevm/CMakeLists.txt @@ -18,8 +18,9 @@ file(GLOB HEADERS "*.h") include_directories(..) target_link_libraries(${EXECUTABLE} ethcore) +target_link_libraries(${EXECUTABLE} devcrypto) target_link_libraries(${EXECUTABLE} evmface) -target_link_libraries(${EXECUTABLE} ethential) +target_link_libraries(${EXECUTABLE} devcore) target_link_libraries(${EXECUTABLE} secp256k1) target_link_libraries(${EXECUTABLE} gmp) if(MINIUPNPC_LS) diff --git a/libevm/ExtVMFace.h b/libevm/ExtVMFace.h index e768519de..19cc944a4 100644 --- a/libevm/ExtVMFace.h +++ b/libevm/ExtVMFace.h @@ -21,7 +21,7 @@ #pragma once -#include +#include #include #include #include diff --git a/libevm/FeeStructure.h b/libevm/FeeStructure.h index 364a6de0d..76be9a398 100644 --- a/libevm/FeeStructure.h +++ b/libevm/FeeStructure.h @@ -21,7 +21,7 @@ #pragma once -#include +#include namespace dev { diff --git a/libevm/VM.h b/libevm/VM.h index e774a1787..e7d218ec6 100644 --- a/libevm/VM.h +++ b/libevm/VM.h @@ -22,10 +22,10 @@ #pragma once #include -#include +#include #include #include -#include +#include #include #include "FeeStructure.h" #include "ExtVMFace.h" diff --git a/libevmface/CMakeLists.txt b/libevmface/CMakeLists.txt index 93b33b66b..212ce825d 100644 --- a/libevmface/CMakeLists.txt +++ b/libevmface/CMakeLists.txt @@ -17,7 +17,7 @@ file(GLOB HEADERS "*.h") include_directories(..) -target_link_libraries(${EXECUTABLE} ethential) +target_link_libraries(${EXECUTABLE} devcore) if("${TARGET_PLATFORM}" STREQUAL "w64") target_link_libraries(${EXECUTABLE} boost_thread_win32-mt-s) diff --git a/libevmface/Instruction.cpp b/libevmface/Instruction.cpp index 83dc22c42..7577c2aab 100644 --- a/libevmface/Instruction.cpp +++ b/libevmface/Instruction.cpp @@ -21,7 +21,7 @@ #include "Instruction.h" -#include +#include using namespace std; using namespace dev; using namespace dev::eth; diff --git a/libevmface/Instruction.h b/libevmface/Instruction.h index 01b4d6354..8d4ca989a 100644 --- a/libevmface/Instruction.h +++ b/libevmface/Instruction.h @@ -21,8 +21,8 @@ #pragma once -#include -#include +#include +#include namespace boost { namespace spirit { class utree; } } namespace sp = boost::spirit; diff --git a/liblll/Assembly.cpp b/liblll/Assembly.cpp index 475eaeabf..2250a71fe 100644 --- a/liblll/Assembly.cpp +++ b/liblll/Assembly.cpp @@ -21,7 +21,7 @@ #include "Assembly.h" -#include +#include using namespace std; using namespace dev; diff --git a/liblll/Assembly.h b/liblll/Assembly.h index cfeb6d1fb..b8152fe02 100644 --- a/liblll/Assembly.h +++ b/liblll/Assembly.h @@ -23,7 +23,7 @@ #include #include -#include +#include #include #include "Exceptions.h" diff --git a/liblll/CMakeLists.txt b/liblll/CMakeLists.txt index 98bebdfed..7746613ec 100644 --- a/liblll/CMakeLists.txt +++ b/liblll/CMakeLists.txt @@ -18,7 +18,7 @@ file(GLOB HEADERS "*.h") include_directories(..) target_link_libraries(${EXECUTABLE} evmface) -target_link_libraries(${EXECUTABLE} ethential) +target_link_libraries(${EXECUTABLE} devcore) if("${TARGET_PLATFORM}" STREQUAL "w64") diff --git a/liblll/CodeFragment.cpp b/liblll/CodeFragment.cpp index ecad3d364..533d00a96 100644 --- a/liblll/CodeFragment.cpp +++ b/liblll/CodeFragment.cpp @@ -23,7 +23,7 @@ #include #include -#include +#include #include #include "CompilerState.h" #include "Parser.h" diff --git a/liblll/CodeFragment.h b/liblll/CodeFragment.h index a81d8ea22..60ab7c6de 100644 --- a/liblll/CodeFragment.h +++ b/liblll/CodeFragment.h @@ -21,7 +21,7 @@ #pragma once -#include +#include #include #include "Assembly.h" #include "Exceptions.h" diff --git a/liblll/Compiler.h b/liblll/Compiler.h index 5daec0a54..0fadd2785 100644 --- a/liblll/Compiler.h +++ b/liblll/Compiler.h @@ -23,7 +23,7 @@ #include #include -#include +#include namespace dev { diff --git a/liblll/Exceptions.h b/liblll/Exceptions.h index 1dcbbcc66..c45215f1a 100644 --- a/liblll/Exceptions.h +++ b/liblll/Exceptions.h @@ -21,7 +21,7 @@ #pragma once -#include +#include namespace dev { diff --git a/liblll/Parser.h b/liblll/Parser.h index f90c6b5df..b21989f06 100644 --- a/liblll/Parser.h +++ b/liblll/Parser.h @@ -23,7 +23,7 @@ #include #include -#include +#include namespace boost { namespace spirit { class utree; } } namespace sp = boost::spirit; diff --git a/libp2p/CMakeLists.txt b/libp2p/CMakeLists.txt index 118aecbe8..ab852fc57 100644 --- a/libp2p/CMakeLists.txt +++ b/libp2p/CMakeLists.txt @@ -17,7 +17,7 @@ file(GLOB HEADERS "*.h") include_directories(..) -target_link_libraries(${EXECUTABLE} ethential) +target_link_libraries(${EXECUTABLE} devcore) target_link_libraries(${EXECUTABLE} secp256k1) if(MINIUPNPC_LS) target_link_libraries(${EXECUTABLE} ${MINIUPNPC_LS}) diff --git a/libp2p/Common.h b/libp2p/Common.h index 94be0b04b..8035f462f 100644 --- a/libp2p/Common.h +++ b/libp2p/Common.h @@ -27,9 +27,9 @@ #include #include #include -#include -#include -#include +#include +#include +#include namespace ba = boost::asio; namespace bi = boost::asio::ip; diff --git a/libp2p/Host.cpp b/libp2p/Host.cpp index 1a375608d..fdf1338b3 100644 --- a/libp2p/Host.cpp +++ b/libp2p/Host.cpp @@ -34,7 +34,7 @@ #include #include #include -#include +#include #include #include "Session.h" #include "Capability.h" diff --git a/libp2p/Host.h b/libp2p/Host.h index c362bbe11..a4897602d 100644 --- a/libp2p/Host.h +++ b/libp2p/Host.h @@ -28,7 +28,7 @@ #include #include #include -#include +#include #include "HostCapability.h" namespace ba = boost::asio; namespace bi = boost::asio::ip; diff --git a/libp2p/Session.cpp b/libp2p/Session.cpp index cdd168db4..5ed987cff 100644 --- a/libp2p/Session.cpp +++ b/libp2p/Session.cpp @@ -22,7 +22,7 @@ #include "Session.h" #include -#include +#include #include #include "Host.h" #include "Capability.h" diff --git a/libp2p/Session.h b/libp2p/Session.h index a005e35ed..289ec1063 100644 --- a/libp2p/Session.h +++ b/libp2p/Session.h @@ -26,7 +26,7 @@ #include #include #include -#include +#include #include "Common.h" namespace dev diff --git a/libp2p/UPnP.cpp b/libp2p/UPnP.cpp index 6b3ef4cc5..4fe01159a 100644 --- a/libp2p/UPnP.cpp +++ b/libp2p/UPnP.cpp @@ -29,9 +29,9 @@ #include #include #endif -#include -#include -#include +#include +#include +#include using namespace std; using namespace dev; using namespace dev::p2p; diff --git a/libpyserpent/CMakeLists.txt b/libpyserpent/CMakeLists.txt index fb0c101e9..5108000f4 100644 --- a/libpyserpent/CMakeLists.txt +++ b/libpyserpent/CMakeLists.txt @@ -14,7 +14,7 @@ include_directories(..) target_link_libraries(${EXECUTABLE} serpent) target_link_libraries(${EXECUTABLE} lll) target_link_libraries(${EXECUTABLE} evmface) -target_link_libraries(${EXECUTABLE} ethential) +target_link_libraries(${EXECUTABLE} devcore) target_link_libraries(${EXECUTABLE} ${PYTHON_LS}) if("${TARGET_PLATFORM}" STREQUAL "w64") diff --git a/libqethereum/QEthereum.cpp b/libqethereum/QEthereum.cpp index 2fd94b228..689daf8d7 100644 --- a/libqethereum/QEthereum.cpp +++ b/libqethereum/QEthereum.cpp @@ -1,6 +1,6 @@ #include #include -#include +#include #include #include #include @@ -18,14 +18,14 @@ using dev::u160; using dev::u256; using dev::u256s; using dev::RLP; -using dev::eth::Address; +using dev::Address; using dev::eth::BlockInfo; using dev::eth::Client; using dev::eth::Instruction; -using dev::eth::KeyPair; +using dev::KeyPair; using dev::eth::NodeMode; using dev::p2p::PeerInfo; -using dev::eth::Secret; +using dev::Secret; using dev::eth::Transaction; // functions @@ -89,7 +89,7 @@ QString unpadded(QString _s) return _s; } -QEthereum::QEthereum(QObject* _p, Client* _c, QList _accounts): +QEthereum::QEthereum(QObject* _p, Client* _c, QList _accounts): QObject(_p), m_client(_c), m_accounts(_accounts) { // required to prevent crash on osx when performing addto/evaluatejavascript calls diff --git a/libqethereum/QEthereum.h b/libqethereum/QEthereum.h index 41451118e..cacf1cfc4 100644 --- a/libqethereum/QEthereum.h +++ b/libqethereum/QEthereum.h @@ -3,7 +3,7 @@ #include #include #include -#include +#include #include namespace dev { namespace eth { @@ -55,8 +55,8 @@ template dev::FixedHash toFixed(QString const& _s) template inline boost::multiprecision::number> toInt(QString const& _s); -inline dev::eth::Address toAddress(QString const& _s) { return toFixed<20>(_s); } -inline dev::eth::Secret toSecret(QString const& _s) { return toFixed<32>(_s); } +inline dev::Address toAddress(QString const& _s) { return toFixed<20>(_s); } +inline dev::Secret toSecret(QString const& _s) { return toFixed<32>(_s); } inline dev::u256 toU256(QString const& _s) { return toInt<32>(_s); } template QString toQJS(dev::FixedHash const& _h) { return QString::fromStdString("0x" + toHex(_h.ref())); } @@ -99,7 +99,7 @@ class QEthereum: public QObject Q_OBJECT public: - QEthereum(QObject* _p, dev::eth::Client* _c, QList _accounts); + QEthereum(QObject* _p, dev::eth::Client* _c, QList _accounts); virtual ~QEthereum(); dev::eth::Client* client() const; @@ -108,7 +108,7 @@ public: /// Call when the client() is going to be deleted to make this object useless but safe. void clientDieing(); - void setAccounts(QList _l) { m_accounts = _l; keysChanged(); } + void setAccounts(QList _l) { m_accounts = _l; keysChanged(); } Q_INVOKABLE QString ethTest() const { return "Hello world!"; } Q_INVOKABLE QEthereum* self() { return this; } @@ -165,9 +165,9 @@ public: int getDefault() const; QString/*dev::KeyPair*/ key() const; - QStringList/*list of dev::eth::KeyPair*/ keys() const; + QStringList/*list of dev::KeyPair*/ keys() const; QString/*dev::Address*/ account() const; - QStringList/*list of dev::eth::Address*/ accounts() const; + QStringList/*list of dev::Address*/ accounts() const; unsigned peerCount() const; @@ -201,7 +201,7 @@ private: dev::eth::Client* m_client; std::vector m_watches; - QList m_accounts; + QList m_accounts; }; #define QETH_INSTALL_JS_NAMESPACE(frame, eth, env) [frame, eth, env]() \ diff --git a/libqethereum/QmlEthereum.cpp b/libqethereum/QmlEthereum.cpp index b718bfb7b..350427f3e 100644 --- a/libqethereum/QmlEthereum.cpp +++ b/libqethereum/QmlEthereum.cpp @@ -3,7 +3,7 @@ #endif #include #include -#include +#include #include #include #include @@ -21,13 +21,13 @@ using dev::u160; using dev::u256; using dev::u256s; using dev::RLP; -using dev::eth::Address; +using dev::Address; using dev::eth::BlockInfo; using dev::eth::Client; using dev::eth::Instruction; -using dev::eth::KeyPair; +using dev::KeyPair; using dev::eth::NodeMode; -using dev::eth::Secret; +using dev::Secret; using dev::eth::Transaction; using dev::p2p::PeerInfo; diff --git a/libqethereum/QmlEthereum.h b/libqethereum/QmlEthereum.h index 59dff6f5e..dcb636301 100644 --- a/libqethereum/QmlEthereum.h +++ b/libqethereum/QmlEthereum.h @@ -4,7 +4,7 @@ #if ETH_QTQML #include #endif -#include +#include #include namespace dev { namespace eth { @@ -20,9 +20,9 @@ extern dev::eth::Client* g_qmlClient; extern QObject* g_qmlMain; Q_DECLARE_METATYPE(dev::u256) -Q_DECLARE_METATYPE(dev::eth::Address) -Q_DECLARE_METATYPE(dev::eth::Secret) -Q_DECLARE_METATYPE(dev::eth::KeyPair) +Q_DECLARE_METATYPE(dev::Address) +Q_DECLARE_METATYPE(dev::Secret) +Q_DECLARE_METATYPE(dev::KeyPair) Q_DECLARE_METATYPE(QmlAccount*) Q_DECLARE_METATYPE(QmlEthereum*) @@ -64,16 +64,16 @@ class QmlKeyHelper: public QObject public: QmlKeyHelper(QObject* _p = nullptr): QObject(_p) {} - Q_INVOKABLE dev::eth::KeyPair create() const { return dev::eth::KeyPair::create(); } - Q_INVOKABLE dev::eth::Address address(dev::eth::KeyPair _p) const { return _p.address(); } - Q_INVOKABLE dev::eth::Secret secret(dev::eth::KeyPair _p) const { return _p.secret(); } - Q_INVOKABLE dev::eth::KeyPair keypair(dev::eth::Secret _k) const { return dev::eth::KeyPair(_k); } + Q_INVOKABLE dev::KeyPair create() const { return dev::KeyPair::create(); } + Q_INVOKABLE dev::Address address(dev::KeyPair _p) const { return _p.address(); } + Q_INVOKABLE dev::Secret secret(dev::KeyPair _p) const { return _p.secret(); } + Q_INVOKABLE dev::KeyPair keypair(dev::Secret _k) const { return dev::KeyPair(_k); } - Q_INVOKABLE bool isNull(dev::eth::Address _a) const { return !_a; } + Q_INVOKABLE bool isNull(dev::Address _a) const { return !_a; } - Q_INVOKABLE dev::eth::Address addressOf(QString _s) const { return dev::eth::Address(_s.toStdString()); } - Q_INVOKABLE QString stringOf(dev::eth::Address _a) const { return QString::fromStdString(dev::toHex(_a.asArray())); } - Q_INVOKABLE QString toAbridged(dev::eth::Address _a) const { return QString::fromStdString(_a.abridged()); } + Q_INVOKABLE dev::Address addressOf(QString _s) const { return dev::Address(_s.toStdString()); } + Q_INVOKABLE QString stringOf(dev::Address _a) const { return QString::fromStdString(dev::toHex(_a.asArray())); } + Q_INVOKABLE QString toAbridged(dev::Address _a) const { return QString::fromStdString(_a.abridged()); } }; class QmlAccount: public QObject @@ -88,13 +88,13 @@ public: Q_INVOKABLE dev::u256 balance() const; Q_INVOKABLE double txCount() const; Q_INVOKABLE bool isContract() const; - Q_INVOKABLE dev::eth::Address address() const { return m_address; } + Q_INVOKABLE dev::Address address() const { return m_address; } // TODO: past transactions models. public slots: void setEthereum(QmlEthereum* _eth); - void setAddress(dev::eth::Address _a) { m_address = _a; } + void setAddress(dev::Address _a) { m_address = _a; } signals: void changed(); @@ -102,12 +102,12 @@ signals: private: QmlEthereum* m_eth = nullptr; - dev::eth::Address m_address; + dev::Address m_address; Q_PROPERTY(dev::u256 balance READ balance NOTIFY changed STORED false) Q_PROPERTY(double txCount READ txCount NOTIFY changed STORED false) Q_PROPERTY(bool isContract READ isContract NOTIFY changed STORED false) - Q_PROPERTY(dev::eth::Address address READ address WRITE setAddress NOTIFY changed) + Q_PROPERTY(dev::Address address READ address WRITE setAddress NOTIFY changed) Q_PROPERTY(QmlEthereum* ethereum READ ethereum WRITE setEthereum NOTIFY ethChanged) }; @@ -124,23 +124,23 @@ public: static QObject* constructU256Helper(QQmlEngine*, QJSEngine*) { return new QmlU256Helper; } static QObject* constructKeyHelper(QQmlEngine*, QJSEngine*) { return new QmlKeyHelper; } - Q_INVOKABLE dev::eth::Address coinbase() const; + Q_INVOKABLE dev::Address coinbase() const; Q_INVOKABLE bool isListening() const; Q_INVOKABLE bool isMining() const; - Q_INVOKABLE dev::u256 balanceAt(dev::eth::Address _a) const; - Q_INVOKABLE double txCountAt(dev::eth::Address _a) const; - Q_INVOKABLE bool isContractAt(dev::eth::Address _a) const; + Q_INVOKABLE dev::u256 balanceAt(dev::Address _a) const; + Q_INVOKABLE double txCountAt(dev::Address _a) const; + Q_INVOKABLE bool isContractAt(dev::Address _a) const; Q_INVOKABLE unsigned peerCount() const; Q_INVOKABLE QmlEthereum* self() { return this; } public slots: - void transact(dev::eth::Secret _secret, dev::eth::Address _dest, dev::u256 _amount, dev::u256 _gasPrice, dev::u256 _gas, QByteArray _data); - void transact(dev::eth::Secret _secret, dev::u256 _amount, dev::u256 _gasPrice, dev::u256 _gas, QByteArray _init); - void setCoinbase(dev::eth::Address); + void transact(dev::Secret _secret, dev::Address _dest, dev::u256 _amount, dev::u256 _gasPrice, dev::u256 _gas, QByteArray _data); + void transact(dev::Secret _secret, dev::u256 _amount, dev::u256 _gasPrice, dev::u256 _gas, QByteArray _init); + void setCoinbase(dev::Address); void setMining(bool _l); void setListening(bool _l); @@ -151,7 +151,7 @@ signals: // void miningChanged(); private: - Q_PROPERTY(dev::eth::Address coinbase READ coinbase WRITE setCoinbase NOTIFY coinbaseChanged) + Q_PROPERTY(dev::Address coinbase READ coinbase WRITE setCoinbase NOTIFY coinbaseChanged) Q_PROPERTY(bool listening READ isListening WRITE setListening) Q_PROPERTY(bool mining READ isMining WRITE setMining) }; @@ -198,10 +198,10 @@ public: Q_INVOKABLE QByteArray bytesOf(QVariant _t) const { dev::h256 b = in(_t); return QByteArray((char const*)&b, sizeof(dev::h256)); } Q_INVOKABLE QVariant fromHex(QString _s) const { return out((dev::u256)dev::h256(_s.toStdString())); } - Q_INVOKABLE QVariant fromAddress(QVariant/*dev::eth::Address*/ _a) const { return out((dev::eth::u160)to(_a)); } - Q_INVOKABLE QVariant toAddress(QVariant/*dev::eth::Address*/ _a) const { return toQJS((dev::eth::u160)in(_a)); } + Q_INVOKABLE QVariant fromAddress(QVariant/*dev::Address*/ _a) const { return out((dev::eth::u160)to(_a)); } + Q_INVOKABLE QVariant toAddress(QVariant/*dev::Address*/ _a) const { return toQJS((dev::eth::u160)in(_a)); } - Q_INVOKABLE bool isNull(QVariant/*dev::eth::Address*/ _a) const { return !in(_a); } + Q_INVOKABLE bool isNull(QVariant/*dev::Address*/ _a) const { return !in(_a); } }; class KeyHelper: public QObject @@ -211,19 +211,19 @@ class KeyHelper: public QObject public: KeyHelper(QObject* _p = nullptr): QObject(_p) {} - static dev::eth::Address in(QVariant const& _s) { return to(_s); } - static QVariant out(dev::eth::Address const& _s) { return toQJS(_s); } + static dev::Address in(QVariant const& _s) { return to(_s); } + static QVariant out(dev::Address const& _s) { return toQJS(_s); } - Q_INVOKABLE QVariant/*dev::eth::KeyPair*/ create() const { return toQJS(dev::eth::KeyPair::create()); } - Q_INVOKABLE QVariant/*dev::eth::Address*/ address(QVariant/*dev::eth::KeyPair*/ _p) const { return out(to(_p).address()); } - Q_INVOKABLE QVariant/*dev::eth::Secret*/ secret(QVariant/*dev::eth::KeyPair*/ _p) const { return toQJS(to(_p).secret()); } - Q_INVOKABLE QVariant/*dev::eth::KeyPair*/ keypair(QVariant/*dev::eth::Secret*/ _k) const { return toQJS(dev::eth::KeyPair(to(_k))); } + Q_INVOKABLE QVariant/*dev::KeyPair*/ create() const { return toQJS(dev::KeyPair::create()); } + Q_INVOKABLE QVariant/*dev::Address*/ address(QVariant/*dev::KeyPair*/ _p) const { return out(to(_p).address()); } + Q_INVOKABLE QVariant/*dev::Secret*/ secret(QVariant/*dev::KeyPair*/ _p) const { return toQJS(to(_p).secret()); } + Q_INVOKABLE QVariant/*dev::KeyPair*/ keypair(QVariant/*dev::Secret*/ _k) const { return toQJS(dev::KeyPair(to(_k))); } - Q_INVOKABLE bool isNull(QVariant/*dev::eth::Address*/ _a) const { return !in(_a); } + Q_INVOKABLE bool isNull(QVariant/*dev::Address*/ _a) const { return !in(_a); } - Q_INVOKABLE QVariant/*dev::eth::Address*/ addressOf(QString _s) const { return out(dev::eth::Address(_s.toStdString())); } - Q_INVOKABLE QString stringOf(QVariant/*dev::eth::Address*/ _a) const { return QString::fromStdString(dev::eth::toHex(in(_a).asArray())); } - Q_INVOKABLE QString toAbridged(QVariant/*dev::eth::Address*/ _a) const { return QString::fromStdString(in(_a).abridged()); } + Q_INVOKABLE QVariant/*dev::Address*/ addressOf(QString _s) const { return out(dev::Address(_s.toStdString())); } + Q_INVOKABLE QString stringOf(QVariant/*dev::Address*/ _a) const { return QString::fromStdString(dev::eth::toHex(in(_a).asArray())); } + Q_INVOKABLE QString toAbridged(QVariant/*dev::Address*/ _a) const { return QString::fromStdString(in(_a).abridged()); } }; diff --git a/libserpent/CMakeLists.txt b/libserpent/CMakeLists.txt index 0d392bd49..0d66393b2 100644 --- a/libserpent/CMakeLists.txt +++ b/libserpent/CMakeLists.txt @@ -19,7 +19,7 @@ include_directories(..) target_link_libraries(${EXECUTABLE} lll) target_link_libraries(${EXECUTABLE} evmface) -target_link_libraries(${EXECUTABLE} ethential) +target_link_libraries(${EXECUTABLE} devcore) if("${TARGET_PLATFORM}" STREQUAL "w64") target_link_libraries(${EXECUTABLE} boost_thread_win32-mt-s) diff --git a/libwebthree/CMakeLists.txt b/libwebthree/CMakeLists.txt index 54bfd586d..d3332d8b0 100644 --- a/libwebthree/CMakeLists.txt +++ b/libwebthree/CMakeLists.txt @@ -23,6 +23,7 @@ target_link_libraries(${EXECUTABLE} lll) target_link_libraries(${EXECUTABLE} whisper) target_link_libraries(${EXECUTABLE} p2p) target_link_libraries(${EXECUTABLE} ethcore) +target_link_libraries(${EXECUTABLE} devcrypto) target_link_libraries(${EXECUTABLE} secp256k1) if(MINIUPNPC_LS) target_link_libraries(${EXECUTABLE} ${MINIUPNPC_LS}) diff --git a/libwebthree/Client.cpp b/libwebthree/Client.cpp index f8b3ad538..fc5d0511f 100644 --- a/libwebthree/Client.cpp +++ b/libwebthree/Client.cpp @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/libwebthree/Client.h b/libwebthree/Client.h index 5c418d931..d0ca8fb5b 100644 --- a/libwebthree/Client.h +++ b/libwebthree/Client.h @@ -26,9 +26,9 @@ #include #include #include -#include -#include -#include +#include +#include +#include #include namespace dev diff --git a/libwhisper/CMakeLists.txt b/libwhisper/CMakeLists.txt index c85853162..49857d16a 100644 --- a/libwhisper/CMakeLists.txt +++ b/libwhisper/CMakeLists.txt @@ -18,7 +18,8 @@ file(GLOB HEADERS "*.h") include_directories(..) target_link_libraries(${EXECUTABLE} ethcore) -target_link_libraries(${EXECUTABLE} ethential) +target_link_libraries(${EXECUTABLE} devcrypto) +target_link_libraries(${EXECUTABLE} devcore) target_link_libraries(${EXECUTABLE} p2p) target_link_libraries(${EXECUTABLE} secp256k1) if(MINIUPNPC_LS) diff --git a/libwhisper/Common.h b/libwhisper/Common.h index 9ca727c1e..2324be024 100644 --- a/libwhisper/Common.h +++ b/libwhisper/Common.h @@ -23,8 +23,8 @@ #include #include -#include -#include +#include +#include #include #include diff --git a/libwhisper/WhisperPeer.cpp b/libwhisper/WhisperPeer.cpp index 020170013..a1598010e 100644 --- a/libwhisper/WhisperPeer.cpp +++ b/libwhisper/WhisperPeer.cpp @@ -21,7 +21,7 @@ #include "WhisperPeer.h" -#include +#include #include using namespace std; using namespace dev; diff --git a/libwhisper/WhisperPeer.h b/libwhisper/WhisperPeer.h index dba3efed8..9163b544d 100644 --- a/libwhisper/WhisperPeer.h +++ b/libwhisper/WhisperPeer.h @@ -26,9 +26,9 @@ #include #include #include -#include -#include -#include +#include +#include +#include #include "Common.h" namespace dev diff --git a/lllc/CMakeLists.txt b/lllc/CMakeLists.txt index 97eaea7ac..9d5e8c5ff 100644 --- a/lllc/CMakeLists.txt +++ b/lllc/CMakeLists.txt @@ -10,7 +10,7 @@ add_executable(${EXECUTABLE} ${SRC_LIST}) target_link_libraries(${EXECUTABLE} lll) target_link_libraries(${EXECUTABLE} evmface) -target_link_libraries(${EXECUTABLE} ethential) +target_link_libraries(${EXECUTABLE} devcore) if ("${TARGET_PLATFORM}" STREQUAL "w64") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static-libgcc -static-libstdc++") diff --git a/lllc/main.cpp b/lllc/main.cpp index ccdf5a11b..a4c9df78c 100644 --- a/lllc/main.cpp +++ b/lllc/main.cpp @@ -23,8 +23,8 @@ #include #include #include -#include -#include +#include +#include #include #include "BuildInfo.h" using namespace std; diff --git a/neth/main.cpp b/neth/main.cpp index 67afc2664..e492d1a3c 100644 --- a/neth/main.cpp +++ b/neth/main.cpp @@ -29,7 +29,7 @@ #if ETH_JSONRPC #include #endif -#include +#include #include #include #if ETH_JSONRPC diff --git a/sc/CMakeLists.txt b/sc/CMakeLists.txt index 20a8c23f3..3cacf78e6 100644 --- a/sc/CMakeLists.txt +++ b/sc/CMakeLists.txt @@ -11,7 +11,7 @@ add_executable(${EXECUTABLE} ${SRC_LIST}) target_link_libraries(${EXECUTABLE} serpent) target_link_libraries(${EXECUTABLE} lll) target_link_libraries(${EXECUTABLE} evmface) -target_link_libraries(${EXECUTABLE} ethential) +target_link_libraries(${EXECUTABLE} devcore) if ("${TARGET_PLATFORM}" STREQUAL "w64") set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -static-libgcc -static-libstdc++") diff --git a/test/MemTrie.cpp b/test/MemTrie.cpp index b5d875acb..d654179f3 100644 --- a/test/MemTrie.cpp +++ b/test/MemTrie.cpp @@ -21,8 +21,8 @@ #include "MemTrie.h" -#include -#include +#include +#include #include using namespace std; using namespace dev; diff --git a/test/MemTrie.h b/test/MemTrie.h index 66669653c..86b09540f 100644 --- a/test/MemTrie.h +++ b/test/MemTrie.h @@ -21,8 +21,8 @@ #pragma once -#include -#include +#include +#include namespace dev { diff --git a/test/TrieHash.cpp b/test/TrieHash.cpp index b2ebce731..af32e870d 100644 --- a/test/TrieHash.cpp +++ b/test/TrieHash.cpp @@ -21,8 +21,8 @@ #include "TrieHash.h" -#include -#include +#include +#include #include using namespace std; using namespace dev; diff --git a/test/TrieHash.h b/test/TrieHash.h index 4d86c4dbf..be1d84480 100644 --- a/test/TrieHash.h +++ b/test/TrieHash.h @@ -21,8 +21,8 @@ #pragma once -#include -#include +#include +#include namespace dev { diff --git a/test/crypto.cpp b/test/crypto.cpp index 2e4ffa055..d2943984e 100644 --- a/test/crypto.cpp +++ b/test/crypto.cpp @@ -22,9 +22,9 @@ #include #include -#include -#include -#include +#include +#include +#include #include #include diff --git a/test/dagger.cpp b/test/dagger.cpp index fd60a8efa..555c5d776 100644 --- a/test/dagger.cpp +++ b/test/dagger.cpp @@ -21,7 +21,7 @@ */ #include -#include +#include #include using namespace std; using namespace std::chrono; diff --git a/test/genesis.cpp b/test/genesis.cpp index be6655aba..5f17d2762 100644 --- a/test/genesis.cpp +++ b/test/genesis.cpp @@ -23,7 +23,7 @@ #include #include #include "JsonSpiritHeaders.h" -#include +#include #include #include diff --git a/test/hexPrefix.cpp b/test/hexPrefix.cpp index 7dbe80a57..9b65db0e4 100644 --- a/test/hexPrefix.cpp +++ b/test/hexPrefix.cpp @@ -22,8 +22,8 @@ #include #include "JsonSpiritHeaders.h" -#include -#include +#include +#include #include using namespace std; diff --git a/test/main.cpp b/test/main.cpp index 08142095c..ef009e299 100644 --- a/test/main.cpp +++ b/test/main.cpp @@ -20,7 +20,7 @@ * Main test functions. */ -#include +#include #include "TrieHash.h" #include "MemTrie.h" @@ -35,7 +35,7 @@ int vmTest(); int hexPrefixTest(); int peerTest(int argc, char** argv); -#include +#include #include using namespace std; using namespace dev; diff --git a/test/rlp.cpp b/test/rlp.cpp index 963d61091..b51f3a225 100644 --- a/test/rlp.cpp +++ b/test/rlp.cpp @@ -23,9 +23,9 @@ #include #include #include "JsonSpiritHeaders.h" -#include -#include -#include +#include +#include +#include #include #include diff --git a/test/trie.cpp b/test/trie.cpp index 4ddf30a61..cdebd09fc 100644 --- a/test/trie.cpp +++ b/test/trie.cpp @@ -23,7 +23,7 @@ #include #include #include "JsonSpiritHeaders.h" -#include +#include #include "TrieHash.h" #include "MemTrie.h" #include diff --git a/test/vm.cpp b/test/vm.cpp index 2b75f0b3d..e177855cb 100644 --- a/test/vm.cpp +++ b/test/vm.cpp @@ -22,7 +22,7 @@ #include #include -#include +#include #include #include #include diff --git a/third/CMakeLists.txt b/third/CMakeLists.txt index 3385fc265..a68b90813 100644 --- a/third/CMakeLists.txt +++ b/third/CMakeLists.txt @@ -52,7 +52,7 @@ else () endif () qt5_use_modules(${EXECUTEABLE} Core)# Gui Widgets Network WebKit WebKitWidgets) -target_link_libraries(${EXECUTEABLE} qethereum ethereum evm ethcore secp256k1 gmp ${CRYPTOPP_LS} serpent lll evmface ethential) +target_link_libraries(${EXECUTEABLE} qethereum ethereum evm ethcore secp256k1 gmp ${CRYPTOPP_LS} serpent lll evmface devcore) if (APPLE) # First have qt5 install plugins and frameworks diff --git a/third/MainWin.cpp b/third/MainWin.cpp index b8722f2d7..7bbdd721b 100644 --- a/third/MainWin.cpp +++ b/third/MainWin.cpp @@ -50,16 +50,16 @@ using dev::h160; using dev::h256; using dev::u160; using dev::u256; -using dev::eth::Address; +using dev::Address; using dev::eth::BlockInfo; using dev::eth::Client; using dev::eth::Instruction; -using dev::eth::KeyPair; +using dev::KeyPair; using dev::eth::NodeMode; using dev::eth::BlockChain; using dev::p2p::PeerInfo; using dev::RLP; -using dev::eth::Secret; +using dev::Secret; using dev::eth::Transaction; using dev::eth::Executive; @@ -291,7 +291,7 @@ void Main::eval(QString const& _js) ui->webView->page()->currentFrame()->evaluateJavaScript("___RET=(" + _js + ")"); } -QString Main::pretty(dev::eth::Address _a) const +QString Main::pretty(dev::Address _a) const { h256 n; @@ -301,7 +301,7 @@ QString Main::pretty(dev::eth::Address _a) const return fromRaw(n); } -QString Main::render(dev::eth::Address _a) const +QString Main::render(dev::Address _a) const { QString p = pretty(_a); if (!p.isNull()) diff --git a/third/MainWin.h b/third/MainWin.h index 6fc7a6a5d..76505d603 100644 --- a/third/MainWin.h +++ b/third/MainWin.h @@ -27,7 +27,7 @@ #include #include #include -#include +#include #include #include #include @@ -54,7 +54,7 @@ public: dev::eth::Client* client() { return m_client.get(); } - QList const& owned() const { return m_myKeys; } + QList const& owned() const { return m_myKeys; } public slots: void note(QString _entry); @@ -79,9 +79,9 @@ signals: void poll(); private: - QString pretty(dev::eth::Address _a) const; - QString render(dev::eth::Address _a) const; - dev::eth::Address fromString(QString const& _a) const; + QString pretty(dev::Address _a) const; + QString render(dev::Address _a) const; + dev::Address fromString(QString const& _a) const; QString lookup(QString const& _n) const; void readSettings(bool _skipGeometry = false); @@ -113,7 +113,7 @@ private: std::unique_ptr m_client; - QList m_myKeys; + QList m_myKeys; std::map> m_handlers; unsigned m_nameRegFilter = (unsigned)-1; diff --git a/walleth/MainWin.cpp b/walleth/MainWin.cpp index a07230885..ade04c4fa 100644 --- a/walleth/MainWin.cpp +++ b/walleth/MainWin.cpp @@ -7,7 +7,7 @@ #include #include #include -#include +#include #include #include #include @@ -25,15 +25,15 @@ using dev::h256; using dev::u160; using dev::u256; using dev::u256s; -using dev::eth::Address; +using dev::Address; using dev::eth::BlockInfo; using dev::eth::Client; using dev::eth::Instruction; -using dev::eth::KeyPair; +using dev::KeyPair; using dev::eth::NodeMode; using dev::p2p::PeerInfo; using dev::RLP; -using dev::eth::Secret; +using dev::Secret; using dev::eth::Transaction; // functions @@ -66,9 +66,9 @@ Main::Main(QWidget *parent) : g_qmlClient = m_client.get(); qRegisterMetaType("dev::u256"); - qRegisterMetaType("dev::eth::KeyPair"); - qRegisterMetaType("dev::eth::Secret"); - qRegisterMetaType("dev::eth::Address"); + qRegisterMetaType("dev::KeyPair"); + qRegisterMetaType("dev::Secret"); + qRegisterMetaType("dev::Address"); qRegisterMetaType("QmlAccount*"); qRegisterMetaType("QmlEthereum*"); diff --git a/walleth/MainWin.h b/walleth/MainWin.h index 0edbff226..860db8bc9 100644 --- a/walleth/MainWin.h +++ b/walleth/MainWin.h @@ -47,9 +47,9 @@ protected: virtual void timerEvent(QTimerEvent *); private: -/* QString pretty(dev::eth::Address _a) const; - QString render(dev::eth::Address _a) const; - dev::eth::Address fromString(QString const& _a) const; +/* QString pretty(dev::Address _a) const; + QString render(dev::Address _a) const; + dev::Address fromString(QString const& _a) const; */ dev::eth::State const& state() const; @@ -67,7 +67,7 @@ private: QMutex m_guiLock; QTimer* m_refresh; QTimer* m_refreshNetwork; - QVector m_myKeys; + QVector m_myKeys; bool m_keysChanged = false; int m_port; int m_idealPeers; From 6ee38d54fba1d0e27d99e43ea6b7bd87fa22f5bc Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 6 Sep 2014 11:52:12 +0200 Subject: [PATCH 159/223] Deinterleave logging. --- libdevcore/Log.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/libdevcore/Log.cpp b/libdevcore/Log.cpp index d3e00d683..1c7a34932 100644 --- a/libdevcore/Log.cpp +++ b/libdevcore/Log.cpp @@ -23,6 +23,7 @@ #include #include +#include "Guards.h" using namespace std; using namespace dev; @@ -39,6 +40,9 @@ extern "C" __declspec(dllimport) void __stdcall OutputDebugStringA(const char* l void dev::simpleDebugOut(std::string const& _s, char const*) { + static Mutex s_lock; + Guard l(s_lock); + cout << _s << endl << flush; // helpful to use OutputDebugString on windows From 7a235158893a71aa128004a4b8557add74140249 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 8 Sep 2014 18:48:24 +0200 Subject: [PATCH 160/223] Fix peersever. --- libethereum/Client.cpp | 10 ++++++---- libethereum/State.cpp | 33 +++++++++++++++++++++------------ libwebthree/Client.h | 10 ++++++++++ 3 files changed, 37 insertions(+), 16 deletions(-) diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index 4c91c9aaf..9e9ae4773 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -443,10 +443,12 @@ void Client::workNet() m_net->process(); // must be in guard for now since it uses the blockchain. // returns h256Set as block hashes, once for each block that has come in/gone out. - cwork << "NET <==> TQ ; CHAIN ==> NET ==> BQ"; - m_net->cap()->sync(m_tq, m_bq); - - cwork << "TQ:" << m_tq.items() << "; BQ:" << m_bq.items(); + if (m_net->cap()) + { + cwork << "NET <==> TQ ; CHAIN ==> NET ==> BQ"; + m_net->cap()->sync(m_tq, m_bq); + cwork << "TQ:" << m_tq.items() << "; BQ:" << m_bq.items(); + } } } this_thread::sleep_for(chrono::milliseconds(1)); diff --git a/libethereum/State.cpp b/libethereum/State.cpp index 9ff089566..eef01bb23 100644 --- a/libethereum/State.cpp +++ b/libethereum/State.cpp @@ -315,22 +315,31 @@ bool State::sync(BlockChain const& _bc, h256 _block, BlockInfo const& _bi) bool ret = false; // BLOCK BlockInfo bi = _bi; - try + while (1) { - if (!bi) + try { - auto b = _bc.block(_block); - bi.populate(b); -// bi.verifyInternals(_bc.block(_block)); // Unneeded - we already verify on import into the blockchain. + if (!bi) + { + auto b = _bc.block(_block); + bi.populate(b); + // bi.verifyInternals(_bc.block(_block)); // Unneeded - we already verify on import into the blockchain. + break; + } + } + catch (Exception const& _e) + { + // TODO: Slightly nicer handling? :-) + cerr << "ERROR: Corrupt block-chain! Delete your block-chain DB and restart." << endl; + cerr << _e.description() << endl; + } + catch (std::exception const& _e) + { + // TODO: Slightly nicer handling? :-) + cerr << "ERROR: Corrupt block-chain! Delete your block-chain DB and restart." << endl; + cerr << _e.what() << endl; } } - catch (...) - { - // TODO: Slightly nicer handling? :-) - cerr << "ERROR: Corrupt block-chain! Delete your block-chain DB and restart." << endl; - exit(1); - } - if (bi == m_currentBlock) { // We mined the last block. diff --git a/libwebthree/Client.h b/libwebthree/Client.h index d0ca8fb5b..4c3cc502f 100644 --- a/libwebthree/Client.h +++ b/libwebthree/Client.h @@ -47,6 +47,10 @@ enum class NodeMode Full }; +namespace eth { class Interface; } +namespace shh { class Interface; } +namespace bzz { class Interface; } + /** * @brief Main API hub for interfacing with Web 3 components. This doesn't do any local multiplexing, so you can only have one * running on any given machine for the provided DB path. @@ -60,6 +64,12 @@ public: /// Destructor. ~RawWebThree(); + // The mainline interfaces: + + eth::Interface* ethereum() const; + shh::Interface* whisper() const; + bzz::Interface* swarm() const; + // Misc stuff: void setClientVersion(std::string const& _name) { m_clientVersion = _name; } From b7b17b996345782c13a726a710ba4a95466932c4 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 8 Sep 2014 19:30:55 +0200 Subject: [PATCH 161/223] Queue future blocks, don't discard. --- libdevcore/Common.cpp | 2 +- libethereum/BlockChain.cpp | 10 ++++++- libethereum/BlockQueue.cpp | 45 ++++++++++++++++++++++---------- libethereum/BlockQueue.h | 13 ++++++--- libethereum/CommonNet.h | 8 +++--- libethereum/TransactionQueue.cpp | 10 +++---- libethereum/TransactionQueue.h | 4 +-- 7 files changed, 61 insertions(+), 31 deletions(-) diff --git a/libdevcore/Common.cpp b/libdevcore/Common.cpp index 665a663f3..325ba274e 100644 --- a/libdevcore/Common.cpp +++ b/libdevcore/Common.cpp @@ -27,7 +27,7 @@ using namespace dev; namespace dev { -char const* Version = "0.6.8"; +char const* Version = "0.6.8b"; } diff --git a/libethereum/BlockChain.cpp b/libethereum/BlockChain.cpp index c23258f08..716add2ae 100644 --- a/libethereum/BlockChain.cpp +++ b/libethereum/BlockChain.cpp @@ -178,6 +178,8 @@ inline string toString(h256s const& _bs) h256s BlockChain::sync(BlockQueue& _bq, OverlayDB const& _stateDB, unsigned _max) { + _bq.tick(*this); + vector blocks; _bq.drain(blocks); @@ -197,7 +199,13 @@ h256s BlockChain::sync(BlockQueue& _bq, OverlayDB const& _stateDB, unsigned _max cwarn << "Unknown parent of block!!!" << BlockInfo::headerHash(block).abridged(); _bq.import(&block, *this); } - catch (...){} + catch (Exception const& _e) + { + cwarn << "Unexpected exception!" << _e.description(); + _bq.import(&block, *this); + } + catch (...) + {} } _bq.doneDrain(); return ret; diff --git a/libethereum/BlockQueue.cpp b/libethereum/BlockQueue.cpp index f8bc7d64c..24f979072 100644 --- a/libethereum/BlockQueue.cpp +++ b/libethereum/BlockQueue.cpp @@ -38,7 +38,7 @@ bool BlockQueue::import(bytesConstRef _block, BlockChain const& _bc) UpgradableGuard l(m_lock); - if (m_readySet.count(h) || m_drainingSet.count(h) || m_futureSet.count(h)) + if (m_readySet.count(h) || m_drainingSet.count(h) || m_unknownSet.count(h)) { // Already know about this one. cnote << "Already known."; @@ -70,23 +70,20 @@ bool BlockQueue::import(bytesConstRef _block, BlockChain const& _bc) return false; } - // Check it's not crazy - if (bi.timestamp > (u256)time(0) + 10) - { - cnote << "Invalid timestamp."; - return false; - } + UpgradeGuard ul(l); + // Check it's not in the future + if (bi.timestamp > (u256)time(0)) + m_future.insert(make_pair((unsigned)bi.timestamp, _block.toBytes())); + else { - UpgradeGuard ul(l); - // We now know it. if (!m_readySet.count(bi.parentHash) && !m_drainingSet.count(bi.parentHash) && !_bc.details(bi.parentHash)) { // We don't know the parent (yet) - queue it up for later. It'll get resent to us if we find out about its ancestry later on. // cnote << "OK - queued for future."; - m_future.insert(make_pair(bi.parentHash, make_pair(h, _block.toBytes()))); - m_futureSet.insert(h); + m_unknown.insert(make_pair(bi.parentHash, make_pair(h, _block.toBytes()))); + m_unknownSet.insert(h); } else { @@ -102,21 +99,41 @@ bool BlockQueue::import(bytesConstRef _block, BlockChain const& _bc) return true; } +void BlockQueue::tick(BlockChain const& _bc) +{ + unsigned t = time(0); + for (auto i = m_future.begin(); i != m_future.end() && i->first < time(0); ++i) + import(&(i->second), _bc); + + WriteGuard l(m_lock); + m_future.erase(m_future.begin(), m_future.upper_bound(t)); +} + +void BlockQueue::drain(std::vector& o_out) +{ + WriteGuard l(m_lock); + if (m_drainingSet.empty()) + { + swap(o_out, m_ready); + swap(m_drainingSet, m_readySet); + } +} + void BlockQueue::noteReadyWithoutWriteGuard(h256 _good) { list goodQueue(1, _good); while (goodQueue.size()) { - auto r = m_future.equal_range(goodQueue.front()); + auto r = m_unknown.equal_range(goodQueue.front()); goodQueue.pop_front(); for (auto it = r.first; it != r.second; ++it) { m_ready.push_back(it->second.second); auto newReady = it->second.first; - m_futureSet.erase(newReady); + m_unknownSet.erase(newReady); m_readySet.insert(newReady); goodQueue.push_back(newReady); } - m_future.erase(r.first, r.second); + m_unknown.erase(r.first, r.second); } } diff --git a/libethereum/BlockQueue.h b/libethereum/BlockQueue.h index c68e1f1e4..2aaae3280 100644 --- a/libethereum/BlockQueue.h +++ b/libethereum/BlockQueue.h @@ -44,9 +44,12 @@ public: /// Import a block into the queue. bool import(bytesConstRef _tx, BlockChain const& _bc); + /// Notes that time has moved on and some blocks that used to be "in the future" may no be valid. + void tick(BlockChain const& _bc); + /// Grabs the blocks that are ready, giving them in the correct order for insertion into the chain. /// Don't forget to call doneDrain() once you're done importing. - void drain(std::vector& o_out) { WriteGuard l(m_lock); if (m_drainingSet.empty()) { swap(o_out, m_ready); swap(m_drainingSet, m_readySet); } } + void drain(std::vector& o_out); /// Must be called after a drain() call. Notes that the drained blocks have been imported into the blockchain, so we can forget about them. void doneDrain() { WriteGuard l(m_lock); m_drainingSet.clear(); } @@ -55,17 +58,19 @@ public: void noteReady(h256 _b) { WriteGuard l(m_lock); noteReadyWithoutWriteGuard(_b); } /// Get information on the items queued. - std::pair items() const { ReadGuard l(m_lock); return std::make_pair(m_ready.size(), m_future.size()); } + std::pair items() const { ReadGuard l(m_lock); return std::make_pair(m_ready.size(), m_unknown.size()); } private: void noteReadyWithoutWriteGuard(h256 _b); + void notePresentWithoutWriteGuard(bytesConstRef _block); mutable boost::shared_mutex m_lock; ///< General lock. std::set m_readySet; ///< All blocks ready for chain-import. std::set m_drainingSet; ///< All blocks being imported. std::vector m_ready; ///< List of blocks, in correct order, ready for chain-import. - std::set m_futureSet; ///< Set of all blocks whose parents are not ready/in-chain. - std::multimap> m_future; ///< For transactions that have an unknown parent; we map their parent hash to the block stuff, and insert once the block appears. + std::set m_unknownSet; ///< Set of all blocks whose parents are not ready/in-chain. + std::multimap> m_unknown; ///< For transactions that have an unknown parent; we map their parent hash to the block stuff, and insert once the block appears. + std::multimap m_future; ///< Set of blocks that are not yet valid. }; } diff --git a/libethereum/CommonNet.h b/libethereum/CommonNet.h index 13f68a977..3bb45b55d 100644 --- a/libethereum/CommonNet.h +++ b/libethereum/CommonNet.h @@ -33,10 +33,10 @@ namespace dev namespace eth { -static const unsigned c_maxHashes = 32; ///< Maximum number of hashes BlockHashes will ever send. -static const unsigned c_maxHashesAsk = 32; ///< Maximum number of hashes GetBlockHashes will ever ask for. -static const unsigned c_maxBlocks = 16; ///< Maximum number of blocks Blocks will ever send. -static const unsigned c_maxBlocksAsk = 16; ///< Maximum number of blocks we ask to receive in Blocks (when using GetChain). +static const unsigned c_maxHashes = 256; ///< Maximum number of hashes BlockHashes will ever send. +static const unsigned c_maxHashesAsk = 256; ///< Maximum number of hashes GetBlockHashes will ever ask for. +static const unsigned c_maxBlocks = 128; ///< Maximum number of blocks Blocks will ever send. +static const unsigned c_maxBlocksAsk = 128; ///< Maximum number of blocks we ask to receive in Blocks (when using GetChain). class OverlayDB; class BlockChain; diff --git a/libethereum/TransactionQueue.cpp b/libethereum/TransactionQueue.cpp index 5275732cb..f15cefc26 100644 --- a/libethereum/TransactionQueue.cpp +++ b/libethereum/TransactionQueue.cpp @@ -69,17 +69,17 @@ void TransactionQueue::setFuture(std::pair const& _t) if (m_current.count(_t.first)) { m_current.erase(_t.first); - m_future.insert(make_pair(Transaction(_t.second).sender(), _t)); + m_unknown.insert(make_pair(Transaction(_t.second).sender(), _t)); } } void TransactionQueue::noteGood(std::pair const& _t) { WriteGuard l(m_lock); - auto r = m_future.equal_range(Transaction(_t.second).sender()); + auto r = m_unknown.equal_range(Transaction(_t.second).sender()); for (auto it = r.first; it != r.second; ++it) m_current.insert(it->second); - m_future.erase(r.first, r.second); + m_unknown.erase(r.first, r.second); } void TransactionQueue::drop(h256 _txHash) @@ -96,10 +96,10 @@ void TransactionQueue::drop(h256 _txHash) m_current.erase(_txHash); else { - for (auto i = m_future.begin(); i != m_future.end(); ++i) + for (auto i = m_unknown.begin(); i != m_unknown.end(); ++i) if (i->second.first == _txHash) { - m_future.erase(i); + m_unknown.erase(i); break; } } diff --git a/libethereum/TransactionQueue.h b/libethereum/TransactionQueue.h index 690dc658a..154eed9d2 100644 --- a/libethereum/TransactionQueue.h +++ b/libethereum/TransactionQueue.h @@ -47,7 +47,7 @@ public: void drop(h256 _txHash); std::map transactions() const { ReadGuard l(m_lock); return m_current; } - std::pair items() const { ReadGuard l(m_lock); return std::make_pair(m_current.size(), m_future.size()); } + std::pair items() const { ReadGuard l(m_lock); return std::make_pair(m_current.size(), m_unknown.size()); } void setFuture(std::pair const& _t); void noteGood(std::pair const& _t); @@ -56,7 +56,7 @@ private: mutable boost::shared_mutex m_lock; ///< General lock. std::set m_known; ///< Hashes of transactions in both sets. std::map m_current; ///< Map of SHA3(tx) to tx. - std::multimap> m_future; ///< For transactions that have a future nonce; we map their sender address to the tx stuff, and insert once the sender has a valid TX. + std::multimap> m_unknown; ///< For transactions that have a future nonce; we map their sender address to the tx stuff, and insert once the sender has a valid TX. }; } From 6d209e78855a04a9d3dbcd7a9d4df376077c9439 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 9 Sep 2014 15:19:54 +0200 Subject: [PATCH 162/223] Add framework for WebThree clients. --- libethereum/Client.cpp | 99 +++++++++++------ libethereum/Client.h | 32 +++--- libethereum/Interface.cpp | 22 ++++ libethereum/Interface.h | 123 +++++++++++++++++++++ libp2p/Host.h | 2 +- libwebthree/Client.cpp | 172 ----------------------------- libwebthree/Client.h | 111 ------------------- libwebthree/WebThree.cpp | 136 +++++++++++++++++++++++ libwebthree/WebThree.h | 220 +++++++++++++++++++++++++++++++++++++ libwhisper/WhisperPeer.cpp | 6 +- libwhisper/WhisperPeer.h | 24 +++- 11 files changed, 609 insertions(+), 338 deletions(-) create mode 100644 libethereum/Interface.cpp create mode 100644 libethereum/Interface.h delete mode 100644 libwebthree/Client.cpp delete mode 100644 libwebthree/Client.h create mode 100644 libwebthree/WebThree.cpp create mode 100644 libwebthree/WebThree.h diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index 9e9ae4773..92ff5d1cd 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -68,6 +68,22 @@ Client::Client(std::string const& _clientVersion, Address _us, std::string const work(); } +Client::Client(p2p::Host* _extNet, std::string const& _dbPath, bool _forceClean, u256 _networkId): + m_vc(_dbPath), + m_bc(_dbPath, !m_vc.ok() || _forceClean), + m_stateDB(State::openDB(_dbPath, !m_vc.ok() || _forceClean)), + m_preMine(Address(), m_stateDB), + m_postMine(Address(), m_stateDB) +{ + m_extHost = _extNet->registerCapability(new EthereumHost(m_bc, _networkId)); + +// setMiningThreads(); + if (_dbPath.size()) + Defaults::setDBPath(_dbPath); + m_vc.setOk(); + work(); +} + Client::~Client() { if (m_work) @@ -118,6 +134,8 @@ void Client::clearPending() appendFromNewPending(m_postMine.bloom(i), changeds); changeds.insert(PendingChangedFilter); m_postMine = m_preMine; + + if (!m_extHost.lock()) { ReadGuard l(x_miners); for (auto& m: m_miners) @@ -214,24 +232,28 @@ void Client::startNetwork(unsigned short _listenPort, std::string const& _seedHo m_workNetState.store(Deleted, std::memory_order_release); })); - try + if (!m_extHost.lock()) { - m_net.reset(new Host(m_clientVersion, _listenPort, _publicIP, _upnp)); - } - catch (std::exception const&) - { - // Probably already have the port open. - cwarn << "Could not initialize with specified/default port. Trying system-assigned port"; - m_net.reset(new Host(m_clientVersion, 0, _publicIP, _upnp)); + try + { + m_net.reset(new Host(m_clientVersion, _listenPort, _publicIP, _upnp)); + } + catch (std::exception const&) + { + // Probably already have the port open. + cwarn << "Could not initialize with specified/default port. Trying system-assigned port"; + m_net.reset(new Host(m_clientVersion, 0, _publicIP, _upnp)); + } + if (_mode == NodeMode::Full) + m_net->registerCapability(new EthereumHost(m_bc, _networkId)); } - if (_mode == NodeMode::Full) - m_net->registerCapability(new EthereumHost(m_bc, _networkId)); } m_net->setIdealPeerCount(_peers); } - if (_seedHost.size()) - connect(_seedHost, _port); + if (!m_extHost.lock()) + if (_seedHost.size()) + connect(_seedHost, _port); ensureWorking(); } @@ -300,6 +322,9 @@ void Client::connect(std::string const& _seedHost, unsigned short _port) void Client::setMiningThreads(unsigned _threads) { + if (m_extHost.lock()) + return; + stopMining(); auto t = _threads ? _threads : thread::hardware_concurrency(); @@ -314,6 +339,8 @@ void Client::setMiningThreads(unsigned _threads) MineProgress Client::miningProgress() const { MineProgress ret; + if (m_extHost.lock()) + return ret; ReadGuard l(x_miners); for (auto& m: m_miners) ret.combine(m.miningProgress()); @@ -323,6 +350,9 @@ MineProgress Client::miningProgress() const std::list Client::miningHistory() { std::list ret; + if (m_extHost.lock()) + return ret; + ReadGuard l(x_miners); if (m_miners.empty()) return ret; @@ -451,6 +481,10 @@ void Client::workNet() } } } + + if (auto h = m_extHost.lock()) + h->sync(m_tq, m_bq); + this_thread::sleep_for(chrono::milliseconds(1)); } @@ -461,26 +495,29 @@ void Client::work() cworkin << "WORK"; h256Set changeds; - ReadGuard l(x_miners); - for (auto& m: m_miners) - if (m.isComplete()) - { - cwork << "CHAIN <== postSTATE"; - h256s hs; - { - WriteGuard l(x_stateDB); - hs = m_bc.attemptImport(m.blockData(), m_stateDB); - } - if (hs.size()) + if (!m_extHost.lock()) + { + ReadGuard l(x_miners); + for (auto& m: m_miners) + if (m.isComplete()) { - for (auto h: hs) - appendFromNewBlock(h, changeds); - changeds.insert(ChainChangedFilter); - //changeds.insert(PendingChangedFilter); // if we mined the new block, then we've probably reset the pending transactions. + cwork << "CHAIN <== postSTATE"; + h256s hs; + { + WriteGuard l(x_stateDB); + hs = m_bc.attemptImport(m.blockData(), m_stateDB); + } + if (hs.size()) + { + for (auto h: hs) + appendFromNewBlock(h, changeds); + changeds.insert(ChainChangedFilter); + //changeds.insert(PendingChangedFilter); // if we mined the new block, then we've probably reset the pending transactions. + } + for (auto& m: m_miners) + m.noteStateChange(); } - for (auto& m: m_miners) - m.noteStateChange(); - } + } // Synchronise state to block chain. // This should remove any transactions on our queue that are included within our state. @@ -529,7 +566,7 @@ void Client::work() rsm = true; } } - if (rsm) + if (!m_extHost.lock() && rsm) { ReadGuard l(x_miners); for (auto& m: m_miners) diff --git a/libethereum/Client.h b/libethereum/Client.h index 76e903b31..13fb4a516 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -39,6 +39,7 @@ #include "PastMessage.h" #include "MessageFilter.h" #include "Miner.h" +#include "Interface.h" namespace dev { @@ -107,14 +108,17 @@ struct WorkChannel: public LogChannel { static const char* name() { return "-W-" /** * @brief Main API hub for interfacing with Ethereum. */ -class Client: public MinerHost +class Client: public MinerHost, public Interface { friend class Miner; public: - /// Constructor. + /// Original Constructor. explicit Client(std::string const& _clientVersion, Address _us = Address(), std::string const& _dbPath = std::string(), bool _forceClean = false); + /// New-style Constructor. + explicit Client(p2p::Host* _host, std::string const& _dbPath = std::string(), bool _forceClean = false, u256 _networkId = 0); + /// Destructor. ~Client(); @@ -138,14 +142,11 @@ public: // [NEW API] - int getDefault() const { return m_default; } - void setDefault(int _block) { m_default = _block; } - - u256 balanceAt(Address _a) const { return balanceAt(_a, m_default); } - u256 countAt(Address _a) const { return countAt(_a, m_default); } - u256 stateAt(Address _a, u256 _l) const { return stateAt(_a, _l, m_default); } - bytes codeAt(Address _a) const { return codeAt(_a, m_default); } - std::map storageAt(Address _a) const { return storageAt(_a, m_default); } + using Interface::balanceAt; + using Interface::countAt; + using Interface::stateAt; + using Interface::codeAt; + using Interface::storageAt; u256 balanceAt(Address _a, int _block) const; u256 countAt(Address _a, int _block) const; @@ -169,17 +170,14 @@ public: Transactions pending() const { return m_postMine.pending(); } /// Differences between transactions. - StateDiff diff(unsigned _txi) const { return diff(_txi, m_default); } + using Interface::diff; StateDiff diff(unsigned _txi, h256 _block) const; StateDiff diff(unsigned _txi, int _block) const; /// Get a list of all active addresses. - std::vector
    addresses() const { return addresses(m_default); } + using Interface::addresses; std::vector
    addresses(int _block) const; - /// Get the fee associated for a transaction with the given data. - static u256 txGas(unsigned _dataCount, u256 _gas = 0) { return c_txDataGas * _dataCount + c_txGas + _gas; } - /// Get the remaining gas limit in this block. u256 gasLimitRemaining() const { return m_postMine.gasLimitRemaining(); } @@ -309,6 +307,8 @@ private: mutable boost::shared_mutex x_net; ///< Lock for the network existance. std::unique_ptr m_net; ///< Should run in background and send us events when blocks found and allow us to send blocks as required. + std::weak_ptr m_extHost; ///< Our Ethereum Host. Don't do anything if we can't lock. + std::unique_ptr m_work; ///< The work thread. std::atomic m_workState; @@ -321,8 +321,6 @@ private: mutable std::mutex m_filterLock; std::map m_filters; std::map m_watches; - - int m_default = -1; }; class Watch; diff --git a/libethereum/Interface.cpp b/libethereum/Interface.cpp new file mode 100644 index 000000000..a9801cc33 --- /dev/null +++ b/libethereum/Interface.cpp @@ -0,0 +1,22 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file Interface.cpp + * @author Gav Wood + * @date 2014 + */ + +#include "Interface.h" diff --git a/libethereum/Interface.h b/libethereum/Interface.h new file mode 100644 index 000000000..beb193d53 --- /dev/null +++ b/libethereum/Interface.h @@ -0,0 +1,123 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file Interface.h + * @author Gav Wood + * @date 2014 + */ + +#pragma once + +#include +#include +#include +#include +#include +#include "MessageFilter.h" +#include "Transaction.h" +#include "AccountDiff.h" + +namespace dev +{ +namespace eth +{ + +/** + * @brief Main API hub for interfacing with Ethereum. + * @TODO Mining hooks. + */ +class Interface +{ +public: + /// Constructor. + Interface() {} + + /// Destructor. + virtual ~Interface() {} + + // [TRANSACTION API] + + /// Submits the given message-call transaction. + virtual void transact(Secret _secret, u256 _value, Address _dest, bytes const& _data = bytes(), u256 _gas = 10000, u256 _gasPrice = 10 * szabo) = 0; + + /// Submits a new contract-creation transaction. + /// @returns the new contract's address (assuming it all goes through). + virtual Address transact(Secret _secret, u256 _endowment, bytes const& _init, u256 _gas = 10000, u256 _gasPrice = 10 * szabo) = 0; + + /// Injects the RLP-encoded transaction given by the _rlp into the transaction queue directly. + virtual void inject(bytesConstRef _rlp) = 0; + + /// Blocks until all pending transactions have been processed. + virtual void flushTransactions() = 0; + + /// Makes the given call. Nothing is recorded into the state. + virtual bytes call(Secret _secret, u256 _value, Address _dest, bytes const& _data = bytes(), u256 _gas = 10000, u256 _gasPrice = 10 * szabo) = 0; + + // Informational stuff + + // [NEW API] + + int getDefault() const { return m_default; } + void setDefault(int _block) { m_default = _block; } + + u256 balanceAt(Address _a) const { return balanceAt(_a, m_default); } + u256 countAt(Address _a) const { return countAt(_a, m_default); } + u256 stateAt(Address _a, u256 _l) const { return stateAt(_a, _l, m_default); } + bytes codeAt(Address _a) const { return codeAt(_a, m_default); } + std::map storageAt(Address _a) const { return storageAt(_a, m_default); } + + virtual u256 balanceAt(Address _a, int _block) const = 0; + virtual u256 countAt(Address _a, int _block) const = 0; + virtual u256 stateAt(Address _a, u256 _l, int _block) const = 0; + virtual bytes codeAt(Address _a, int _block) const = 0; + virtual std::map storageAt(Address _a, int _block) const = 0; + + virtual unsigned installWatch(MessageFilter const& _filter) = 0; + virtual unsigned installWatch(h256 _filterId) = 0; + virtual void uninstallWatch(unsigned _watchId) = 0; + virtual bool peekWatch(unsigned _watchId) const = 0; + virtual bool checkWatch(unsigned _watchId) = 0; + + virtual PastMessages messages(unsigned _watchId) const = 0; + virtual PastMessages messages(MessageFilter const& _filter) const = 0; + + // [EXTRA API]: + + /// Get a map containing each of the pending transactions. + /// @TODO: Remove in favour of transactions(). + virtual Transactions pending() const = 0; + + /// Differences between transactions. + StateDiff diff(unsigned _txi) const { return diff(_txi, m_default); } + virtual StateDiff diff(unsigned _txi, h256 _block) const = 0; + virtual StateDiff diff(unsigned _txi, int _block) const = 0; + + /// Get a list of all active addresses. + virtual Addresses addresses() const { return addresses(m_default); } + virtual Addresses addresses(int _block) const = 0; + + /// Get the fee associated for a transaction with the given data. + static u256 txGas(unsigned _dataCount, u256 _gas = 0) { return c_txDataGas * _dataCount + c_txGas + _gas; } + + /// Get the remaining gas limit in this block. + virtual u256 gasLimitRemaining() const = 0; + +protected: + int m_default = -1; +}; + +} +} diff --git a/libp2p/Host.h b/libp2p/Host.h index a4897602d..d6673a072 100644 --- a/libp2p/Host.h +++ b/libp2p/Host.h @@ -68,7 +68,7 @@ public: unsigned protocolVersion() const; /// Register a peer-capability; all new peer connections will have this capability. - template void registerCapability(T* _t) { _t->m_host = this; m_capabilities[T::staticName()] = std::shared_ptr(_t); } + template std::shared_ptr registerCapability(T* _t) { _t->m_host = this; auto ret = std::shared_ptr(_t); m_capabilities[T::staticName()] = ret; return ret; } bool haveCapability(std::string const& _name) const { return m_capabilities.count(_name); } std::vector caps() const { std::vector ret; for (auto const& i: m_capabilities) ret.push_back(i.first); return ret; } diff --git a/libwebthree/Client.cpp b/libwebthree/Client.cpp deleted file mode 100644 index fc5d0511f..000000000 --- a/libwebthree/Client.cpp +++ /dev/null @@ -1,172 +0,0 @@ -/* - This file is part of cpp-ethereum. - - cpp-ethereum is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - cpp-ethereum is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with cpp-ethereum. If not, see . -*/ -/** @file RawWebThree.cpp - * @author Gav Wood - * @date 2014 - */ - -#include "Client.h" - -#include -#include -#include -#include -#include -#include -#include -#include -using namespace std; -using namespace dev; -using namespace dev::p2p; -using namespace dev::eth; -using namespace dev::shh; - -RawWebThree::RawWebThree(std::string const& _clientVersion, std::string const& _dbPath, bool _forceClean): - m_clientVersion(_clientVersion) -{ - if (_dbPath.size()) - Defaults::setDBPath(_dbPath); -} - -RawWebThree::~RawWebThree() -{ - stopNetwork(); -} - -void RawWebThree::startNetwork(unsigned short _listenPort, std::string const& _seedHost, unsigned short _port, NodeMode _mode, unsigned _peers, string const& _publicIP, bool _upnp, u256 _networkId) -{ - static const char* c_threadName = "net"; - - { - UpgradableGuard l(x_net); - if (m_net.get()) - return; - { - UpgradeGuard ul(l); - - if (!m_workNet) - m_workNet.reset(new thread([&]() - { - setThreadName(c_threadName); - m_workNetState.store(Active, std::memory_order_release); - while (m_workNetState.load(std::memory_order_acquire) != Deleting) - workNet(); - m_workNetState.store(Deleted, std::memory_order_release); - })); - - try - { - m_net.reset(new Host(m_clientVersion, _listenPort, _publicIP, _upnp)); - } - catch (std::exception const&) - { - // Probably already have the port open. - cwarn << "Could not initialize with specified/default port. Trying system-assigned port"; - m_net.reset(new Host(m_clientVersion, 0, _publicIP, _upnp)); - } -/* if (_mode == NodeMode::Full) - m_net->registerCapability(new EthereumHost(m_bc, _networkId)); - if (_mode == NodeMode::Full) - m_net->registerCapability(new WhisperHost());*/ - } - m_net->setIdealPeerCount(_peers); - } - - if (_seedHost.size()) - connect(_seedHost, _port); -} - -void RawWebThree::stopNetwork() -{ - UpgradableGuard l(x_net); - - if (m_workNet) - { - if (m_workNetState.load(std::memory_order_acquire) == Active) - m_workNetState.store(Deleting, std::memory_order_release); - while (m_workNetState.load(std::memory_order_acquire) != Deleted) - this_thread::sleep_for(chrono::milliseconds(10)); - m_workNet->join(); - } - if (m_net) - { - UpgradeGuard ul(l); - m_net.reset(nullptr); - m_workNet.reset(nullptr); - } -} - -std::vector RawWebThree::peers() -{ - ReadGuard l(x_net); - return m_net ? m_net->peers() : std::vector(); -} - -size_t RawWebThree::peerCount() const -{ - ReadGuard l(x_net); - return m_net ? m_net->peerCount() : 0; -} - -void RawWebThree::setIdealPeerCount(size_t _n) const -{ - ReadGuard l(x_net); - if (m_net) - return m_net->setIdealPeerCount(_n); -} - -bytes RawWebThree::savePeers() -{ - ReadGuard l(x_net); - if (m_net) - return m_net->savePeers(); - return bytes(); -} - -void RawWebThree::restorePeers(bytesConstRef _saved) -{ - ReadGuard l(x_net); - if (m_net) - return m_net->restorePeers(_saved); -} - -void RawWebThree::connect(std::string const& _seedHost, unsigned short _port) -{ - ReadGuard l(x_net); - if (!m_net.get()) - return; - m_net->connect(_seedHost, _port); -} - -void RawWebThree::workNet() -{ - // 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. - { - ReadGuard l(x_net); - if (m_net) - { - m_net->process(); // must be in guard for now since it uses the blockchain. - - // returns h256Set as block hashes, once for each block that has come in/gone out. -// m_net->cap()->sync(m_tq, m_bq); - } - } - this_thread::sleep_for(chrono::milliseconds(1)); -} - diff --git a/libwebthree/Client.h b/libwebthree/Client.h deleted file mode 100644 index 4c3cc502f..000000000 --- a/libwebthree/Client.h +++ /dev/null @@ -1,111 +0,0 @@ -/* - This file is part of cpp-ethereum. - - cpp-ethereum is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - cpp-ethereum is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with cpp-ethereum. If not, see . -*/ -/** @file Client.h - * @author Gav Wood - * @date 2014 - */ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace dev -{ - -enum WorkState -{ - Active = 0, - Deleting, - Deleted -}; - -enum class NodeMode -{ - PeerServer, - Full -}; - -namespace eth { class Interface; } -namespace shh { class Interface; } -namespace bzz { class Interface; } - -/** - * @brief Main API hub for interfacing with Web 3 components. This doesn't do any local multiplexing, so you can only have one - * running on any given machine for the provided DB path. - */ -class RawWebThree -{ -public: - /// Constructor. - RawWebThree(std::string const& _clientVersion, std::string const& _dbPath = std::string(), bool _forceClean = false); - - /// Destructor. - ~RawWebThree(); - - // The mainline interfaces: - - eth::Interface* ethereum() const; - shh::Interface* whisper() const; - bzz::Interface* swarm() const; - - // Misc stuff: - - void setClientVersion(std::string const& _name) { m_clientVersion = _name; } - - // Network stuff: - - /// Get information on the current peer set. - std::vector peers(); - /// Same as peers().size(), but more efficient. - size_t peerCount() const; - /// Same as peers().size(), but more efficient. - void setIdealPeerCount(size_t _n) const; - - /// Start the network subsystem. - void startNetwork(unsigned short _listenPort = 30303, std::string const& _remoteHost = std::string(), unsigned short _remotePort = 30303, NodeMode _mode = NodeMode::Full, unsigned _peers = 5, std::string const& _publicIP = std::string(), bool _upnp = true, dev::u256 _networkId = 0); - /// Connect to a particular peer. - void connect(std::string const& _seedHost, unsigned short _port = 30303); - /// Stop the network subsystem. - void stopNetwork(); - /// Is the network subsystem up? - bool haveNetwork() { ReadGuard l(x_net); return !!m_net; } - /// Save peers - dev::bytes savePeers(); - /// Restore peers - void restorePeers(bytesConstRef _saved); - -private: - /// Do some work on the network. - void workNet(); - - std::string m_clientVersion; ///< Our end-application client's name/version. - - std::unique_ptr m_workNet; ///< The network thread. - std::atomic m_workNetState; - mutable boost::shared_mutex x_net; ///< Lock for the network existance. - std::unique_ptr m_net; ///< Should run in background and send us events when blocks found and allow us to send blocks as required. -}; - -} diff --git a/libwebthree/WebThree.cpp b/libwebthree/WebThree.cpp new file mode 100644 index 000000000..93f81dd0a --- /dev/null +++ b/libwebthree/WebThree.cpp @@ -0,0 +1,136 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file WebThree.cpp + * @author Gav Wood + * @date 2014 + */ + +#include "WebThree.h" + +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; +using namespace dev; +using namespace dev::p2p; +using namespace dev::eth; +using namespace dev::shh; + +WebThreeDirect::WebThreeDirect(std::string const& _clientVersion, std::string const& _dbPath, bool _forceClean, std::set const& _interfaces, unsigned short _listenPort, std::string const& _publicIP, bool _upnp, dev::u256 _networkId, bool _localNetworking): + m_clientVersion(_clientVersion), + m_net(m_clientVersion, _listenPort, _publicIP, _upnp, _localNetworking) +{ + if (_dbPath.size()) + Defaults::setDBPath(_dbPath); + + if (_interfaces.count("eth")) + m_ethereum.reset(new eth::Client(&m_net, _dbPath, _forceClean, _networkId)); + +// if (_interfaces.count("shh")) +// m_whisper = new eth::Whisper(m_net.get()); + + static const char* c_threadName = "net"; + + UpgradableGuard l(x_work); + { + UpgradeGuard ul(l); + + if (!m_work) + m_work.reset(new thread([&]() + { + setThreadName(c_threadName); + m_workState.store(Active, std::memory_order_release); + while (m_workState.load(std::memory_order_acquire) != Deleting) + workNet(); + m_workState.store(Deleted, std::memory_order_release); + })); + + } +} + +WebThreeDirect::~WebThreeDirect() +{ + UpgradableGuard l(x_work); + + if (m_work) + { + if (m_workState.load(std::memory_order_acquire) == Active) + m_workState.store(Deleting, std::memory_order_release); + while (m_workState.load(std::memory_order_acquire) != Deleted) + this_thread::sleep_for(chrono::milliseconds(10)); + m_work->join(); + } + if (m_work) + { + UpgradeGuard ul(l); + m_work.reset(nullptr); + } +} + +std::vector WebThreeDirect::peers() +{ + ReadGuard l(x_work); + return m_net.peers(); +} + +size_t WebThreeDirect::peerCount() const +{ + ReadGuard l(x_work); + return m_net.peerCount(); +} + +void WebThreeDirect::setIdealPeerCount(size_t _n) +{ + ReadGuard l(x_work); + return m_net.setIdealPeerCount(_n); +} + +bytes WebThreeDirect::savePeers() +{ + ReadGuard l(x_work); + return m_net.savePeers(); +} + +void WebThreeDirect::restorePeers(bytesConstRef _saved) +{ + ReadGuard l(x_work); + return m_net.restorePeers(_saved); +} + +void WebThreeDirect::connect(std::string const& _seedHost, unsigned short _port) +{ + ReadGuard l(x_work); + m_net.connect(_seedHost, _port); +} + +void WebThreeDirect::workNet() +{ + // 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. + { + ReadGuard l(x_work); + m_net.process(); // must be in guard for now since it uses the blockchain. + } + this_thread::sleep_for(chrono::milliseconds(1)); +} + diff --git a/libwebthree/WebThree.h b/libwebthree/WebThree.h new file mode 100644 index 000000000..cf02c3122 --- /dev/null +++ b/libwebthree/WebThree.h @@ -0,0 +1,220 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file Client.h + * @author Gav Wood + * @date 2014 + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace dev +{ + +class InterfaceNotSupported: public Exception { public: InterfaceNotSupported(std::string _f): m_f(_f) {} virtual std::string description() const { return "Interface " + m_f + " not supported."; } private: std::string m_f; }; + +enum WorkState +{ + Active = 0, + Deleting, + Deleted +}; + +namespace eth { class Interface; } +namespace shh { class Interface; } +namespace bzz { class Interface; } + +/** + * @brief Main API hub for interfacing with Web 3 components. This doesn't do any local multiplexing, so you can only have one + * running on any given machine for the provided DB path. + * + * Keeps a libp2p Host going (administering the work thread with m_workNet). + * + * Encapsulates a bunch of P2P protocols (interfaces), each using the same underlying libp2p Host. + * + * Provides a baseline for the multiplexed multi-protocol session class, WebThree. + */ +class WebThreeDirect +{ +public: + /// Constructor for private instance. If there is already another process on the machine using @a _dbPath, then this will throw an exception. + /// ethereum() may be safely static_cast()ed to a eth::Client*. + WebThreeDirect(std::string const& _clientVersion, std::string const& _dbPath, bool _forceClean = false, std::set const& _interfaces = {"eth", "shh"}, unsigned short _listenPort = 30303, std::string const& _publicIP = std::string(), bool _upnp = true, dev::u256 _networkId = 0, bool _localNetworking = false); + + /// Destructor. + ~WebThreeDirect(); + + // The mainline interfaces: + + eth::Client* ethereum() const { if (!m_ethereum) throw InterfaceNotSupported("eth"); return m_ethereum.get(); } + shh::WhisperHost* whisper() const { if (!m_whisper) throw InterfaceNotSupported("shh"); return m_whisper.get(); } + bzz::Interface* swarm() const { throw InterfaceNotSupported("bzz"); } + + // Misc stuff: + + void setClientVersion(std::string const& _name) { m_clientVersion = _name; } + + // Network stuff: + + /// Get information on the current peer set. + std::vector peers(); + + /// Same as peers().size(), but more efficient. + size_t peerCount() const; + + /// Connect to a particular peer. + void connect(std::string const& _seedHost, unsigned short _port = 30303); + + /// Is the network subsystem up? + bool haveNetwork() { return peerCount(); } + + /// Save peers + dev::bytes savePeers(); + + /// Restore peers + void restorePeers(bytesConstRef _saved); + + /// Sets the ideal number of peers. + void setIdealPeerCount(size_t _n); + + /// Start the network subsystem. + void startNetwork() { setIdealPeerCount(5); } + + /// Stop the network subsystem. + void stopNetwork() { setIdealPeerCount(0); } + +private: + /// Do some work on the network. + void workNet(); + + std::string m_clientVersion; ///< Our end-application client's name/version. + + std::unique_ptr m_ethereum; ///< Main interface for Ethereum ("eth") protocol. + std::unique_ptr m_whisper; ///< Main interface for Whisper ("shh") protocol. + + p2p::Host m_net; ///< Should run in background and send us events when blocks found and allow us to send blocks as required. + std::unique_ptr m_work; ///< The network thread. + mutable boost::shared_mutex x_work; ///< Lock for the network existance. + std::atomic m_workState; +}; + + + +// TODO, probably move into libdevrpc: + +class RPCSlave {}; +class RPCMaster {}; + +// TODO, probably move into eth: + +class EthereumSlave: public eth::Interface +{ +public: + EthereumSlave(RPCSlave* _c) {} + + // TODO: implement all of the virtuals with the RLPClient link. +}; + +class EthereumMaster +{ +public: + EthereumMaster(RPCMaster* _m) {} + + // TODO: implement the master-end of whatever the RLPClient link will send over. +}; + +// TODO, probably move into shh: + +class WhisperSlave: public shh::Interface +{ +public: + WhisperSlave(RPCSlave* _c) {} + + // TODO: implement all of the virtuals with the RLPClient link. +}; + +class WhisperMaster +{ +public: + WhisperMaster(RPCMaster* _m) {} + + // TODO: implement the master-end of whatever the RLPClient link will send over. +}; + +/** + * @brief Main API hub for interfacing with Web 3 components. + * + * This does transparent local multiplexing, so you can have as many running on the + * same machine all working from a single DB path. + */ +class WebThree +{ +public: + /// Constructor for public instance. This will be shared across the local machine. + WebThree(); + + /// Destructor. + ~WebThree(); + + // The mainline interfaces. + + eth::Interface* ethereum() const { if (!m_ethereum) throw InterfaceNotSupported("eth"); return m_ethereum; } + shh::Interface* whisper() const { if (!m_whisper) throw InterfaceNotSupported("shh"); return m_whisper; } + bzz::Interface* swarm() const { throw InterfaceNotSupported("bzz"); } + + // Peer network stuff - forward through RPCSlave, probably with P2PNetworkSlave/Master classes like Whisper & Ethereum. + + /// Get information on the current peer set. + std::vector peers(); + + /// Same as peers().size(), but more efficient. + size_t peerCount() const; + + /// Connect to a particular peer. + void connect(std::string const& _seedHost, unsigned short _port = 30303); + + /// Is the network subsystem up? + bool haveNetwork(); + + /// Save peers + dev::bytes savePeers(); + + /// Restore peers + void restorePeers(bytesConstRef _saved); + +private: + EthereumSlave* m_ethereum = nullptr; + WhisperSlave* m_whisper = nullptr; + + // TODO: + RPCSlave m_rpcSlave; +}; + +} diff --git a/libwhisper/WhisperPeer.cpp b/libwhisper/WhisperPeer.cpp index a1598010e..a1f9c99b8 100644 --- a/libwhisper/WhisperPeer.cpp +++ b/libwhisper/WhisperPeer.cpp @@ -158,7 +158,7 @@ void WhisperHost::noteChanged(h256 _messageHash, h256 _filter) for (auto& i: m_watches) if (i.second.id == _filter) { - cwatch << "!!!" << i.first << i.second.id; + cwatshh << "!!!" << i.first << i.second.id; i.second.changes.push_back(_messageHash); } } @@ -182,7 +182,7 @@ unsigned WhisperHost::installWatch(h256 _h) { auto ret = m_watches.size() ? m_watches.rbegin()->first + 1 : 0; m_watches[ret] = ClientWatch(_h); - cwatch << "+++" << ret << _h; + cwatshh << "+++" << ret << _h; return ret; } @@ -200,7 +200,7 @@ unsigned WhisperHost::installWatch(shh::MessageFilter const& _f) void WhisperHost::uninstallWatch(unsigned _i) { - cwatch << "XXX" << _i; + cwatshh << "XXX" << _i; Guard l(m_filterLock); diff --git a/libwhisper/WhisperPeer.h b/libwhisper/WhisperPeer.h index 9163b544d..d428670c3 100644 --- a/libwhisper/WhisperPeer.h +++ b/libwhisper/WhisperPeer.h @@ -123,7 +123,25 @@ struct ClientWatch h256s changes; }; -class WhisperHost: public HostCapability +class Interface +{ +public: + virtual ~Interface() {} + + virtual void inject(Message const& _m, WhisperPeer* _from = nullptr) = 0; + + virtual unsigned installWatch(MessageFilter const& _filter) = 0; + virtual unsigned installWatch(h256 _filterId) = 0; + virtual void uninstallWatch(unsigned _watchId) = 0; + virtual h256s peekWatch(unsigned _watchId) const = 0; + virtual h256s checkWatch(unsigned _watchId) = 0; + + virtual Message message(h256 _m) const = 0; + + virtual void sendRaw(bytes const& _payload, bytes const& _topic, unsigned _ttl) = 0; +}; + +class WhisperHost: public HostCapability, public Interface { friend class WhisperPeer; @@ -158,8 +176,8 @@ private: std::map m_watches; }; -struct WatchChannel: public dev::LogChannel { static const char* name() { return "shh"; } static const int verbosity = 1; }; -#define cwatch dev::LogOutputStream() +struct WatshhChannel: public dev::LogChannel { static const char* name() { return "shh"; } static const int verbosity = 1; }; +#define cwatshh dev::LogOutputStream() class Watch; From 2ee6b73e3d09389112d97b993ea70f77f324c2b4 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 9 Sep 2014 21:44:31 +0200 Subject: [PATCH 163/223] Move mining & Watch into Etheruem interface. --- libethereum/Client.h | 42 --------------------- libethereum/Interface.h | 81 +++++++++++++++++++++++++++++++++++++---- 2 files changed, 74 insertions(+), 49 deletions(-) diff --git a/libethereum/Client.h b/libethereum/Client.h index 13fb4a516..8fc55aafb 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -323,47 +323,5 @@ private: std::map m_watches; }; -class Watch; - -} } - -namespace std { void swap(dev::eth::Watch& _a, dev::eth::Watch& _b); } - -namespace dev -{ -namespace eth -{ - -class Watch: public boost::noncopyable -{ - friend void std::swap(Watch& _a, Watch& _b); - -public: - Watch() {} - Watch(Client& _c, h256 _f): m_c(&_c), m_id(_c.installWatch(_f)) {} - Watch(Client& _c, MessageFilter const& _tf): m_c(&_c), m_id(_c.installWatch(_tf)) {} - ~Watch() { if (m_c) m_c->uninstallWatch(m_id); } - - bool check() { return m_c ? m_c->checkWatch(m_id) : false; } - bool peek() { return m_c ? m_c->peekWatch(m_id) : false; } - PastMessages messages() const { return m_c->messages(m_id); } - -private: - Client* m_c; - unsigned m_id; -}; - -} -} - -namespace std -{ - -inline void swap(dev::eth::Watch& _a, dev::eth::Watch& _b) -{ - swap(_a.m_c, _b.m_c); - swap(_a.m_id, _b.m_id); -} - } diff --git a/libethereum/Interface.h b/libethereum/Interface.h index beb193d53..3fa93a286 100644 --- a/libethereum/Interface.h +++ b/libethereum/Interface.h @@ -29,6 +29,7 @@ #include "MessageFilter.h" #include "Transaction.h" #include "AccountDiff.h" +#include "Miner.h" namespace dev { @@ -37,7 +38,6 @@ namespace eth /** * @brief Main API hub for interfacing with Ethereum. - * @TODO Mining hooks. */ class Interface { @@ -66,9 +66,7 @@ public: /// Makes the given call. Nothing is recorded into the state. virtual bytes call(Secret _secret, u256 _value, Address _dest, bytes const& _data = bytes(), u256 _gas = 10000, u256 _gasPrice = 10 * szabo) = 0; - // Informational stuff - - // [NEW API] + // [STATE-QUERY API] int getDefault() const { return m_default; } void setDefault(int _block) { m_default = _block; } @@ -85,15 +83,18 @@ public: virtual bytes codeAt(Address _a, int _block) const = 0; virtual std::map storageAt(Address _a, int _block) const = 0; + // [MESSAGE API] + + virtual PastMessages messages(unsigned _watchId) const = 0; + virtual PastMessages messages(MessageFilter const& _filter) const = 0; + + /// Install, uninstall and query watches. virtual unsigned installWatch(MessageFilter const& _filter) = 0; virtual unsigned installWatch(h256 _filterId) = 0; virtual void uninstallWatch(unsigned _watchId) = 0; virtual bool peekWatch(unsigned _watchId) const = 0; virtual bool checkWatch(unsigned _watchId) = 0; - virtual PastMessages messages(unsigned _watchId) const = 0; - virtual PastMessages messages(MessageFilter const& _filter) const = 0; - // [EXTRA API]: /// Get a map containing each of the pending transactions. @@ -115,9 +116,75 @@ public: /// Get the remaining gas limit in this block. virtual u256 gasLimitRemaining() const = 0; + // [MINING API]: + + /// Set the coinbase address. + virtual void setAddress(Address _us) = 0; + /// Get the coinbase address. + virtual Address address() const = 0; + + /// Stops mining and sets the number of mining threads (0 for automatic). + virtual void setMiningThreads(unsigned _threads = 0) = 0; + /// Get the effective number of mining threads. + virtual unsigned miningThreads() const = 0; + + /// Start mining. + /// NOT thread-safe - call it & stopMining only from a single thread + virtual void startMining() = 0; + /// Stop mining. + /// NOT thread-safe + virtual void stopMining() = 0; + /// Are we mining now? + virtual bool isMining() = 0; + + /// Check the progress of the mining. + virtual MineProgress miningProgress() const = 0; + protected: int m_default = -1; }; +class Watch; + } } + +namespace std { void swap(dev::eth::Watch& _a, dev::eth::Watch& _b); } + +namespace dev +{ +namespace eth +{ + +class Watch: public boost::noncopyable +{ + friend void std::swap(Watch& _a, Watch& _b); + +public: + Watch() {} + Watch(Interface& _c, h256 _f): m_c(&_c), m_id(_c.installWatch(_f)) {} + Watch(Interface& _c, MessageFilter const& _tf): m_c(&_c), m_id(_c.installWatch(_tf)) {} + ~Watch() { if (m_c) m_c->uninstallWatch(m_id); } + + bool check() { return m_c ? m_c->checkWatch(m_id) : false; } + bool peek() { return m_c ? m_c->peekWatch(m_id) : false; } + PastMessages messages() const { return m_c->messages(m_id); } + +private: + Interface* m_c = nullptr; + unsigned m_id = 0; +}; + +} +} + +namespace std +{ + +inline void swap(dev::eth::Watch& _a, dev::eth::Watch& _b) +{ + swap(_a.m_c, _b.m_c); + swap(_a.m_id, _b.m_id); +} + +} From 00a3c29d137e76ce6e66117a9eaeaceb77e0534f Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 9 Sep 2014 21:53:46 +0200 Subject: [PATCH 164/223] Avoid using m_client. --- alethzero/MainWin.cpp | 157 +++++++++++++++++++++--------------------- alethzero/MainWin.h | 1 + 2 files changed, 80 insertions(+), 78 deletions(-) diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index e5ee1cd70..e9cdf90bc 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -182,7 +182,7 @@ Main::Main(QWidget *parent) : connect(ui->webView, &QWebView::loadStarted, [this]() { // NOTE: no need to delete as QETH_INSTALL_JS_NAMESPACE adopts it. - m_ethereum = new QEthereum(this, m_client.get(), owned()); + m_ethereum = new QEthereum(this, client(), owned()); QWebFrame* f = ui->webView->page()->mainFrame(); f->disconnect(SIGNAL(javaScriptWindowObjectCleared())); @@ -233,14 +233,14 @@ void Main::onKeysChanged() unsigned Main::installWatch(dev::eth::MessageFilter const& _tf, std::function const& _f) { - auto ret = m_client->installWatch(_tf); + auto ret = client()->installWatch(_tf); m_handlers[ret] = _f; return ret; } unsigned Main::installWatch(dev::h256 _tf, std::function const& _f) { - auto ret = m_client->installWatch(_tf); + auto ret = client()->installWatch(_tf); m_handlers[ret] = _f; return ret; } @@ -255,14 +255,14 @@ void Main::installWatches() void Main::installNameRegWatch() { - m_client->uninstallWatch(m_nameRegFilter); - m_nameRegFilter = installWatch(dev::eth::MessageFilter().altered((u160)m_client->stateAt(c_config, 0)), [=](){ onNameRegChange(); }); + client()->uninstallWatch(m_nameRegFilter); + m_nameRegFilter = installWatch(dev::eth::MessageFilter().altered((u160)client()->stateAt(c_config, 0)), [=](){ onNameRegChange(); }); } void Main::installCurrenciesWatch() { - m_client->uninstallWatch(m_currenciesFilter); - m_currenciesFilter = installWatch(dev::eth::MessageFilter().altered((u160)m_client->stateAt(c_config, 1)), [=](){ onCurrenciesChange(); }); + client()->uninstallWatch(m_currenciesFilter); + m_currenciesFilter = installWatch(dev::eth::MessageFilter().altered((u160)client()->stateAt(c_config, 1)), [=](){ onCurrenciesChange(); }); } void Main::installBalancesWatch() @@ -270,9 +270,9 @@ void Main::installBalancesWatch() dev::eth::MessageFilter tf; vector
    altCoins; - Address coinsAddr = right160(m_client->stateAt(c_config, 1)); - for (unsigned i = 0; i < m_client->stateAt(coinsAddr, 0); ++i) - altCoins.push_back(right160(m_client->stateAt(coinsAddr, i + 1))); + Address coinsAddr = right160(client()->stateAt(c_config, 1)); + for (unsigned i = 0; i < client()->stateAt(coinsAddr, 0); ++i) + altCoins.push_back(right160(client()->stateAt(coinsAddr, i + 1))); for (auto i: m_myKeys) { tf.altered(i.address()); @@ -280,7 +280,7 @@ void Main::installBalancesWatch() tf.altered(c, (u160)i.address()); } - m_client->uninstallWatch(m_balancesFilter); + client()->uninstallWatch(m_balancesFilter); m_balancesFilter = installWatch(tf, [=](){ onBalancesChange(); }); } @@ -328,7 +328,7 @@ void Main::onNewPending() void Main::on_forceMining_triggered() { - m_client->setForceMining(ui->forceMining->isChecked()); + client()->setForceMining(ui->forceMining->isChecked()); } void Main::on_enableOptimizer_triggered() @@ -361,6 +361,7 @@ void Main::load(QString _s) } } } + // env.load("/home/gav/eth/init.eth") void Main::on_loadJS_triggered() @@ -421,11 +422,11 @@ QString Main::pretty(dev::Address _a) const { h256 n; - if (h160 nameReg = (u160)m_client->stateAt(c_config, 0)) - n = m_client->stateAt(nameReg, (u160)(_a)); + if (h160 nameReg = (u160)client()->stateAt(c_config, 0)) + n = client()->stateAt(nameReg, (u160)(_a)); if (!n) - n = m_client->stateAt(m_nameReg, (u160)(_a)); + n = client()->stateAt(m_nameReg, (u160)(_a)); return fromRaw(n); } @@ -451,11 +452,11 @@ Address Main::fromString(QString const& _a) const memset(n.data() + sn.size(), 0, 32 - sn.size()); if (_a.size()) { - if (h160 nameReg = (u160)m_client->stateAt(c_config, 0)) - if (h256 a = m_client->stateAt(nameReg, n)) + if (h160 nameReg = (u160)client()->stateAt(c_config, 0)) + if (h256 a = client()->stateAt(nameReg, n)) return right160(a); - if (h256 a = m_client->stateAt(m_nameReg, n)) + if (h256 a = client()->stateAt(m_nameReg, n)) return right160(a); } if (_a.size() == 40) @@ -483,8 +484,8 @@ QString Main::lookup(QString const& _a) const */ h256 ret; - if (h160 dnsReg = (u160)m_client->stateAt(c_config, 4, 0)) - ret = m_client->stateAt(dnsReg, n); + if (h160 dnsReg = (u160)client()->stateAt(c_config, 4, 0)) + ret = client()->stateAt(dnsReg, n); /* if (!ret) if (h160 nameReg = (u160)m_client->stateAt(c_config, 0, 0)) ret = m_client->stateAt(nameReg, n2); @@ -505,7 +506,7 @@ void Main::on_about_triggered() void Main::on_paranoia_triggered() { - m_client->setParanoia(ui->paranoia->isChecked()); + client()->setParanoia(ui->paranoia->isChecked()); } void Main::writeSettings() @@ -536,7 +537,7 @@ void Main::writeSettings() s.setValue("privateChain", m_privateChain); s.setValue("verbosity", ui->verbosity->value()); - bytes d = m_client->savePeers(); + bytes d = client()->savePeers(); if (d.size()) m_peers = QByteArray((char*)d.data(), (int)d.size()); s.setValue("peers", m_peers); @@ -568,7 +569,7 @@ void Main::readSettings(bool _skipGeometry) m_myKeys.append(KeyPair(k)); } } - m_client->setAddress(m_myKeys.back().address()); + client()->setAddress(m_myKeys.back().address()); m_peers = s.value("peers").toByteArray(); ui->upnp->setChecked(s.value("upnp", true).toBool()); ui->forceAddress->setText(s.value("forceAddress", "").toString()); @@ -664,17 +665,17 @@ void Main::on_nameReg_textChanged() void Main::on_preview_triggered() { - m_client->setDefault(ui->preview->isChecked() ? 0 : -1); + client()->setDefault(ui->preview->isChecked() ? 0 : -1); refreshAll(); } void Main::refreshMining() { - dev::eth::MineProgress p = m_client->miningProgress(); - ui->mineStatus->setText(m_client->isMining() ? QString("%1s @ %2kH/s").arg(p.ms / 1000).arg(p.ms ? p.hashes / p.ms : 0) : "Not mining"); + dev::eth::MineProgress p = client()->miningProgress(); + ui->mineStatus->setText(client()->isMining() ? QString("%1s @ %2kH/s").arg(p.ms / 1000).arg(p.ms ? p.hashes / p.ms : 0) : "Not mining"); if (!ui->miningView->isVisible()) return; - list l = m_client->miningHistory(); + list l = client()->miningHistory(); static unsigned lh = 0; if (p.hashes < lh) ui->miningView->resetStats(); @@ -693,12 +694,12 @@ void Main::refreshBalances() ui->ourAccounts->clear(); u256 totalBalance = 0; map> altCoins; - Address coinsAddr = right160(m_client->stateAt(c_config, 1)); - for (unsigned i = 0; i < m_client->stateAt(coinsAddr, 0); ++i) + Address coinsAddr = right160(client()->stateAt(c_config, 1)); + for (unsigned i = 0; i < client()->stateAt(coinsAddr, 0); ++i) { - auto n = m_client->stateAt(coinsAddr, i + 1); - auto addr = right160(m_client->stateAt(coinsAddr, n)); - auto denom = m_client->stateAt(coinsAddr, sha3(h256(n).asBytes())); + auto n = client()->stateAt(coinsAddr, i + 1); + auto addr = right160(client()->stateAt(coinsAddr, n)); + auto denom = client()->stateAt(coinsAddr, sha3(h256(n).asBytes())); if (denom == 0) denom = 1; // cdebug << n << addr << denom << sha3(h256(n).asBytes()); @@ -706,13 +707,13 @@ void Main::refreshBalances() } for (auto i: m_myKeys) { - u256 b = m_client->balanceAt(i.address()); - (new QListWidgetItem(QString("%2: %1 [%3]").arg(formatBalance(b).c_str()).arg(render(i.address())).arg((unsigned)m_client->countAt(i.address())), ui->ourAccounts)) + u256 b = client()->balanceAt(i.address()); + (new QListWidgetItem(QString("%2: %1 [%3]").arg(formatBalance(b).c_str()).arg(render(i.address())).arg((unsigned)client()->countAt(i.address())), ui->ourAccounts)) ->setData(Qt::UserRole, QByteArray((char const*)i.address().data(), Address::size)); totalBalance += b; for (auto& c: altCoins) - get<1>(c.second) += (u256)m_client->stateAt(c.first, (u160)i.address()); + get<1>(c.second) += (u256)client()->stateAt(c.first, (u160)i.address()); } QString b; @@ -728,7 +729,7 @@ void Main::refreshBalances() void Main::refreshNetwork() { - auto ps = m_client->peers(); + auto ps = client()->peers(); ui->peerCount->setText(QString::fromStdString(toString(ps.size())) + " peer(s)"); ui->peers->clear(); @@ -750,7 +751,7 @@ void Main::refreshPending() { cwatch << "refreshPending()"; ui->transactionQueue->clear(); - for (Transaction const& t: m_client->pending()) + for (Transaction const& t: client()->pending()) { QString s = t.receiveAddress ? QString("%2 %5> %3: %1 [%4]") @@ -758,7 +759,7 @@ void Main::refreshPending() .arg(render(t.safeSender())) .arg(render(t.receiveAddress)) .arg((unsigned)t.nonce) - .arg(m_client->codeAt(t.receiveAddress).size() ? '*' : '-') : + .arg(client()->codeAt(t.receiveAddress).size() ? '*' : '-') : QString("%2 +> %3: %1 [%4]") .arg(formatBalance(t.value).c_str()) .arg(render(t.safeSender())) @@ -774,16 +775,16 @@ void Main::refreshAccounts() ui->accounts->clear(); ui->contracts->clear(); for (auto n = 0; n < 2; ++n) - for (auto i: m_client->addresses()) + for (auto i: client()->addresses()) { auto r = render(i); if (r.contains('(') == !n) { if (n == 0 || ui->showAllAccounts->isChecked()) - (new QListWidgetItem(QString("%2: %1 [%3]").arg(formatBalance(m_client->balanceAt(i)).c_str()).arg(r).arg((unsigned)m_client->countAt(i)), ui->accounts)) + (new QListWidgetItem(QString("%2: %1 [%3]").arg(formatBalance(client()->balanceAt(i)).c_str()).arg(r).arg((unsigned)client()->countAt(i)), ui->accounts)) ->setData(Qt::UserRole, QByteArray((char const*)i.data(), Address::size)); - if (m_client->codeAt(i).size()) - (new QListWidgetItem(QString("%2: %1 [%3]").arg(formatBalance(m_client->balanceAt(i)).c_str()).arg(r).arg((unsigned)m_client->countAt(i)), ui->contracts)) + if (client()->codeAt(i).size()) + (new QListWidgetItem(QString("%2: %1 [%3]").arg(formatBalance(client()->balanceAt(i)).c_str()).arg(r).arg((unsigned)client()->countAt(i)), ui->contracts)) ->setData(Qt::UserRole, QByteArray((char const*)i.data(), Address::size)); } } @@ -793,7 +794,7 @@ void Main::refreshDestination() { cwatch << "refreshDestination()"; QString s; - for (auto i: m_client->addresses()) + for (auto i: client()->addresses()) if ((s = pretty(i)).size()) // A namereg address if (ui->destination->findText(s, Qt::MatchExactly | Qt::MatchCaseSensitive) == -1) @@ -806,8 +807,8 @@ void Main::refreshDestination() void Main::refreshBlockCount() { cwatch << "refreshBlockCount()"; - auto d = m_client->blockChain().details(); - auto diff = BlockInfo(m_client->blockChain().block()).difficulty; + auto d = client()->blockChain().details(); + auto diff = BlockInfo(client()->blockChain().block()).difficulty; ui->blockCount->setText(QString("%6 #%1 @%3 T%2 N%4 D%5").arg(d.number).arg(toLog2(d.totalDifficulty)).arg(toLog2(diff)).arg(dev::eth::c_protocolVersion).arg(dev::eth::c_databaseVersion).arg(m_privateChain.size() ? "[" + m_privateChain + "] " : "testnet")); } @@ -838,7 +839,7 @@ static bool transactionMatch(string const& _f, Transaction const& _t) void Main::on_turboMining_triggered() { - m_client->setTurboMining(ui->turboMining->isChecked()); + client()->setTurboMining(ui->turboMining->isChecked()); } void Main::refreshBlockChain() @@ -849,7 +850,7 @@ void Main::refreshBlockChain() ui->blocks->clear(); string filter = ui->blockChainFilter->text().toLower().toStdString(); - auto const& bc = m_client->blockChain(); + auto const& bc = client()->blockChain(); unsigned i = (ui->showAll->isChecked() || !filter.empty()) ? (unsigned)-1 : 10; for (auto h = bc.currentHash(); h != bc.genesisHash() && bc.details(h) && i; h = bc.details(h).parent, --i) { @@ -876,7 +877,7 @@ void Main::refreshBlockChain() .arg(render(t.safeSender())) .arg(render(t.receiveAddress)) .arg((unsigned)t.nonce) - .arg(m_client->codeAt(t.receiveAddress).size() ? '*' : '-') : + .arg(client()->codeAt(t.receiveAddress).size() ? '*' : '-') : QString(" %2 +> %3: %1 [%4]") .arg(formatBalance(t.value).c_str()) .arg(render(t.safeSender())) @@ -947,7 +948,7 @@ void Main::timerEvent(QTimerEvent*) m_ethereum->poll(); for (auto const& i: m_handlers) - if (m_client->checkWatch(i.first)) + if (client()->checkWatch(i.first)) i.second(); } @@ -1018,9 +1019,9 @@ void Main::on_transactionQueue_currentItemChanged() stringstream s; int i = ui->transactionQueue->currentRow(); - if (i >= 0 && i < (int)m_client->pending().size()) + if (i >= 0 && i < (int)client()->pending().size()) { - Transaction tx(m_client->pending()[i]); + Transaction tx(client()->pending()[i]); auto ss = tx.safeSender(); h256 th = sha3(rlpList(ss, tx.nonce)); s << "

    " << th << "

    "; @@ -1048,7 +1049,7 @@ void Main::on_transactionQueue_currentItemChanged() // s << "Pre: " << fs.rootHash() << "
    "; // s << "Post: " << ts.rootHash() << ""; - s << renderDiff(m_client->diff(i, 0)); + s << renderDiff(client()->diff(i, 0)); } ui->pendingInfo->setHtml(QString::fromStdString(s.str())); @@ -1075,7 +1076,7 @@ void Main::on_inject_triggered() { QString s = QInputDialog::getText(this, "Inject Transaction", "Enter transaction dump in hex"); bytes b = fromHex(s.toStdString()); - m_client->inject(&b); + client()->inject(&b); } void Main::on_blocks_currentItemChanged() @@ -1089,8 +1090,8 @@ void Main::on_blocks_currentItemChanged() auto hba = item->data(Qt::UserRole).toByteArray(); assert(hba.size() == 32); auto h = h256((byte const*)hba.data(), h256::ConstructFromPointer); - auto details = m_client->blockChain().details(h); - auto blockData = m_client->blockChain().block(h); + auto details = client()->blockChain().details(h); + auto blockData = client()->blockChain().block(h); auto block = RLP(blockData); BlockInfo info(blockData); @@ -1114,7 +1115,7 @@ void Main::on_blocks_currentItemChanged() s << "
    Bloom: " << details.bloom << ""; s << "
    Transactions: " << block[1].itemCount() << " @" << info.transactionsRoot << ""; s << "
    Uncles: " << block[2].itemCount() << " @" << info.sha3Uncles << ""; - s << "
    Pre: " << BlockInfo(m_client->blockChain().block(info.parentHash)).stateRoot << ""; + s << "
    Pre: " << BlockInfo(client()->blockChain().block(info.parentHash)).stateRoot << ""; for (auto const& i: block[1]) s << "
    " << sha3(i[0].data()).abridged() << ": " << i[1].toHash() << " [" << i[2].toInt() << " used]"; s << "
    Post: " << info.stateRoot << ""; @@ -1150,7 +1151,7 @@ void Main::on_blocks_currentItemChanged() if (tx.data.size()) s << dev::memDump(tx.data, 16, true); } - s << renderDiff(m_client->diff(txi, h)); + s << renderDiff(client()->diff(txi, h)); ui->debugCurrent->setEnabled(true); ui->debugDumpState->setEnabled(true); ui->debugDumpStatePre->setEnabled(true); @@ -1172,7 +1173,7 @@ void Main::on_debugCurrent_triggered() if (!item->data(Qt::UserRole + 1).isNull()) { unsigned txi = item->data(Qt::UserRole + 1).toInt(); - m_executiveState = m_client->state(txi + 1, h); + m_executiveState = client()->state(txi + 1, h); m_currentExecution = unique_ptr(new Executive(m_executiveState)); Transaction t = m_executiveState.pending()[txi]; m_executiveState = m_executiveState.fromPending(txi); @@ -1198,7 +1199,7 @@ void Main::on_debugDumpState_triggered(int _add) if (f.is_open()) { unsigned txi = item->data(Qt::UserRole + 1).toInt(); - f << m_client->state(txi + _add, h) << endl; + f << client()->state(txi + _add, h) << endl; } } } @@ -1264,10 +1265,10 @@ void Main::on_contracts_currentItemChanged() stringstream s; try { - auto storage = m_client->storageAt(address); + auto storage = client()->storageAt(address); for (auto const& i: storage) s << "@" << showbase << hex << prettyU256(i.first).toStdString() << "    " << showbase << hex << prettyU256(i.second).toStdString() << "
    "; - s << "

    Body Code

    " << disassemble(m_client->codeAt(address)); + s << "

    Body Code

    " << disassemble(client()->codeAt(address)); ui->contractInfo->appendHtml(QString::fromStdString(s.str())); } catch (dev::eth::InvalidTrie) @@ -1280,7 +1281,7 @@ void Main::on_contracts_currentItemChanged() void Main::on_idealPeers_valueChanged() { - m_client->setIdealPeerCount(ui->idealPeers->value()); + client()->setIdealPeerCount(ui->idealPeers->value()); } void Main::on_ourAccounts_doubleClicked() @@ -1426,7 +1427,7 @@ void Main::on_data_textChanged() s = s.mid(1); } ui->code->setHtml(QString::fromStdString(dev::memDump(m_data, 8, true))); - if (m_client->codeAt(fromString(ui->destination->currentText()), 0).size()) + if (client()->codeAt(fromString(ui->destination->currentText()), 0).size()) { ui->gas->setMinimum((qint64)Client::txGas(m_data.size(), 1)); if (!ui->gas->isEnabled()) @@ -1451,7 +1452,7 @@ void Main::on_killBlockchain_triggered() ui->net->setChecked(false); m_client.reset(); m_client.reset(new Client("AlethZero", Address(), string(), true)); - m_ethereum->setClient(m_client.get()); + m_ethereum->setClient(client()); readSettings(true); installWatches(); refreshAll(); @@ -1494,7 +1495,7 @@ void Main::updateFee() bool ok = false; for (auto i: m_myKeys) - if (m_client->balanceAt(i.address()) >= totalReq) + if (client()->balanceAt(i.address()) >= totalReq) { ok = true; break; @@ -1513,15 +1514,15 @@ void Main::on_net_triggered() if (ui->clientName->text().size()) n += "/" + ui->clientName->text().toStdString(); n += "/" DEV_QUOTED(ETH_BUILD_TYPE) "/" DEV_QUOTED(ETH_BUILD_PLATFORM); - m_client->setClientVersion(n); + client()->setClientVersion(n); if (ui->net->isChecked()) { - m_client->startNetwork(ui->port->value(), string(), 0, NodeMode::Full, ui->idealPeers->value(), ui->forceAddress->text().toStdString(), ui->upnp->isChecked(), m_privateChain.size() ? sha3(m_privateChain.toStdString()) : 0); + client()->startNetwork(ui->port->value(), string(), 0, NodeMode::Full, ui->idealPeers->value(), ui->forceAddress->text().toStdString(), ui->upnp->isChecked(), m_privateChain.size() ? sha3(m_privateChain.toStdString()) : 0); if (m_peers.size() && ui->usePast->isChecked()) - m_client->restorePeers(bytesConstRef((byte*)m_peers.data(), m_peers.size())); + client()->restorePeers(bytesConstRef((byte*)m_peers.data(), m_peers.size())); } else - m_client->stopNetwork(); + client()->stopNetwork(); } void Main::on_connect_triggered() @@ -1537,7 +1538,7 @@ void Main::on_connect_triggered() { string host = s.section(":", 0, 0).toStdString(); unsigned short port = s.section(":", 1).toInt(); - m_client->connect(host, port); + client()->connect(host, port); } } @@ -1551,25 +1552,25 @@ void Main::on_mine_triggered() { if (ui->mine->isChecked()) { - m_client->setAddress(m_myKeys.last().address()); - m_client->startMining(); + client()->setAddress(m_myKeys.last().address()); + client()->startMining(); } else - m_client->stopMining(); + client()->stopMining(); } void Main::on_send_clicked() { u256 totalReq = value() + fee(); for (auto i: m_myKeys) - if (m_client->balanceAt(i.address(), 0) >= totalReq) + if (client()->balanceAt(i.address(), 0) >= totalReq) { debugFinished(); Secret s = i.secret(); if (isCreation()) - m_client->transact(s, value(), m_data, ui->gas->value(), gasPrice()); + client()->transact(s, value(), m_data, ui->gas->value(), gasPrice()); else - m_client->transact(s, value(), fromString(ui->destination->currentText()), m_data, ui->gas->value(), gasPrice()); + client()->transact(s, value(), fromString(ui->destination->currentText()), m_data, ui->gas->value(), gasPrice()); return; } statusBar()->showMessage("Couldn't make transaction: no single account contains at least the required amount."); @@ -1582,10 +1583,10 @@ void Main::on_debug_clicked() { u256 totalReq = value() + fee(); for (auto i: m_myKeys) - if (m_client->balanceAt(i.address()) >= totalReq) + if (client()->balanceAt(i.address()) >= totalReq) { Secret s = i.secret(); - m_executiveState = m_client->postState(); + m_executiveState = client()->postState(); m_currentExecution = unique_ptr(new Executive(m_executiveState)); Transaction t; t.nonce = m_executiveState.transactionsFrom(dev::toAddress(s)); diff --git a/alethzero/MainWin.h b/alethzero/MainWin.h index 2902db3a2..2325f7e12 100644 --- a/alethzero/MainWin.h +++ b/alethzero/MainWin.h @@ -201,6 +201,7 @@ private: std::unique_ptr ui; std::unique_ptr m_client; + std::map> m_handlers; unsigned m_nameRegFilter = (unsigned)-1; unsigned m_currenciesFilter = (unsigned)-1; From d25224822e63e78bdd373909371fe0c11c50dc4e Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 9 Sep 2014 22:27:04 +0200 Subject: [PATCH 165/223] Fixes for state getting. --- alethzero/MainWin.h | 2 +- libethereum/BlockQueue.cpp | 6 +++--- libethereum/BlockQueue.h | 6 +++++- libethereum/EthereumHost.cpp | 7 +++++-- libethereum/State.cpp | 32 +++++++++++++++----------------- 5 files changed, 29 insertions(+), 24 deletions(-) diff --git a/alethzero/MainWin.h b/alethzero/MainWin.h index 2325f7e12..a3bfa718d 100644 --- a/alethzero/MainWin.h +++ b/alethzero/MainWin.h @@ -69,7 +69,7 @@ public: explicit Main(QWidget *parent = 0); ~Main(); - dev::eth::Client* client() { return m_client.get(); } + dev::eth::Client* client() const { return m_client.get(); } QList const& owned() const { return m_myKeys; } diff --git a/libethereum/BlockQueue.cpp b/libethereum/BlockQueue.cpp index 24f979072..21aeb5a72 100644 --- a/libethereum/BlockQueue.cpp +++ b/libethereum/BlockQueue.cpp @@ -34,14 +34,14 @@ bool BlockQueue::import(bytesConstRef _block, BlockChain const& _bc) // Check if we already know this block. h256 h = BlockInfo::headerHash(_block); - cnote << "Queuing block" << h.abridged() << "for import..."; + cblockq << "Queuing block" << h.abridged() << "for import..."; UpgradableGuard l(m_lock); if (m_readySet.count(h) || m_drainingSet.count(h) || m_unknownSet.count(h)) { // Already know about this one. - cnote << "Already known."; + cblockq << "Already known."; return false; } @@ -66,7 +66,7 @@ bool BlockQueue::import(bytesConstRef _block, BlockChain const& _bc) // Check block doesn't already exist first! if (_bc.details(h)) { - cnote << "Already known in chain."; + cblockq << "Already known in chain."; return false; } diff --git a/libethereum/BlockQueue.h b/libethereum/BlockQueue.h index 2aaae3280..5a4a86ad6 100644 --- a/libethereum/BlockQueue.h +++ b/libethereum/BlockQueue.h @@ -23,7 +23,8 @@ #include #include -#include "libethcore/CommonEth.h" +#include +#include #include namespace dev @@ -33,6 +34,9 @@ namespace eth class BlockChain; +struct BlockQueueChannel: public LogChannel { static const char* name() { return "[]Q"; } static const int verbosity = 7; }; +#define cblockq dev::LogOutputStream() + /** * @brief A queue of blocks. Sits between network or other I/O and the BlockChain. * Sorts them ready for blockchain insertion (with the BlockChain::sync() method). diff --git a/libethereum/EthereumHost.cpp b/libethereum/EthereumHost.cpp index 069ab4291..7d3bd9a92 100644 --- a/libethereum/EthereumHost.cpp +++ b/libethereum/EthereumHost.cpp @@ -97,7 +97,10 @@ void EthereumHost::noteDoneBlocks() if (m_blocksOnWay.empty()) { // Done our chain-get. - clog(NetNote) << "No more blocks coming. Missing" << m_blocksNeeded.size() << "blocks."; + if (m_blocksNeeded.size()) + clog(NetNote) << "No more blocks coming. Missing" << m_blocksNeeded.size() << "blocks."; + else + clog(NetNote) << "No more blocks to get."; m_latestBlockSent = m_chain->currentHash(); } } @@ -192,7 +195,7 @@ void EthereumHost::maintainBlocks(BlockQueue& _bq, h256 _currentHash) bs += m_chain->block(h); ++c; } - clog(NetNote) << "Sending" << c << "new blocks (current is" << _currentHash << ", was" << m_latestBlockSent << ")"; + clog(NetMessageSummary) << "Sending" << c << "new blocks (current is" << _currentHash << ", was" << m_latestBlockSent << ")"; ts.appendList(1 + c).append(BlocksPacket).appendRaw(bs, c); bytes b; ts.swapOut(b); diff --git a/libethereum/State.cpp b/libethereum/State.cpp index eef01bb23..093db299f 100644 --- a/libethereum/State.cpp +++ b/libethereum/State.cpp @@ -315,31 +315,29 @@ bool State::sync(BlockChain const& _bc, h256 _block, BlockInfo const& _bi) bool ret = false; // BLOCK BlockInfo bi = _bi; - while (1) - { - try + if (!bi) + while (1) { - if (!bi) + try { auto b = _bc.block(_block); bi.populate(b); // bi.verifyInternals(_bc.block(_block)); // Unneeded - we already verify on import into the blockchain. break; } + catch (Exception const& _e) + { + // TODO: Slightly nicer handling? :-) + cerr << "ERROR: Corrupt block-chain! Delete your block-chain DB and restart." << endl; + cerr << _e.description() << endl; + } + catch (std::exception const& _e) + { + // TODO: Slightly nicer handling? :-) + cerr << "ERROR: Corrupt block-chain! Delete your block-chain DB and restart." << endl; + cerr << _e.what() << endl; + } } - catch (Exception const& _e) - { - // TODO: Slightly nicer handling? :-) - cerr << "ERROR: Corrupt block-chain! Delete your block-chain DB and restart." << endl; - cerr << _e.description() << endl; - } - catch (std::exception const& _e) - { - // TODO: Slightly nicer handling? :-) - cerr << "ERROR: Corrupt block-chain! Delete your block-chain DB and restart." << endl; - cerr << _e.what() << endl; - } - } if (bi == m_currentBlock) { // We mined the last block. From 1accc20d21fcd7daf67bca3c7a12c445c8799ed2 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 10 Sep 2014 13:40:33 +0200 Subject: [PATCH 166/223] Moving over to use WebThree. --- alethzero/CMakeLists.txt | 2 +- alethzero/MainWin.cpp | 210 +++++++++++++++--------------------- alethzero/MainWin.h | 7 +- libdevcrypto/FileSystem.cpp | 3 +- libdevcrypto/FileSystem.h | 3 - libethcore/BlockInfo.h | 2 +- libethereum/Client.cpp | 5 + libethereum/Client.h | 7 ++ libethereum/Interface.h | 5 + libqethereum/QEthereum.cpp | 53 ++------- libqethereum/QEthereum.h | 11 +- libwebthree/WebThree.h | 8 +- walleth/MainWin.cpp | 2 +- 13 files changed, 133 insertions(+), 185 deletions(-) diff --git a/alethzero/CMakeLists.txt b/alethzero/CMakeLists.txt index 3eac0a0b6..c5d1aa0be 100644 --- a/alethzero/CMakeLists.txt +++ b/alethzero/CMakeLists.txt @@ -52,7 +52,7 @@ else () endif () qt5_use_modules(${EXECUTEABLE} Core)# Gui Widgets Network WebKit WebKitWidgets) -target_link_libraries(${EXECUTEABLE} qethereum ethereum evm ethcore devcrypto secp256k1 gmp ${CRYPTOPP_LS} serpent lll evmface devcore) +target_link_libraries(${EXECUTEABLE} webthree qethereum ethereum evm ethcore devcrypto secp256k1 gmp ${CRYPTOPP_LS} serpent lll evmface devcore) if (APPLE) # First have qt5 install plugins and frameworks diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index e9cdf90bc..0e2565ae6 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -43,45 +44,9 @@ #include "MainWin.h" #include "ui_Main.h" using namespace std; - -// types -using dev::bytes; -using dev::bytesConstRef; -using dev::h160; -using dev::h256; -using dev::u160; -using dev::u256; -using dev::Address; -using dev::eth::BlockInfo; -using dev::eth::Client; -using dev::eth::Instruction; -using dev::KeyPair; -using dev::eth::NodeMode; -using dev::eth::BlockChain; -using dev::p2p::PeerInfo; -using dev::RLP; -using dev::Secret; -using dev::eth::Transaction; -using dev::eth::Executive; - -// functions -using dev::toHex; -using dev::fromHex; -using dev::left160; -using dev::right160; -using dev::simpleDebugOut; -using dev::toLog2; -using dev::toString; -using dev::operator<<; -using dev::eth::units; -using dev::eth::sha3; -using dev::eth::compileLLL; -using dev::eth::disassemble; -using dev::eth::formatBalance; - -// vars -using dev::g_logPost; -using dev::g_logVerbosity; +using namespace dev; +using namespace dev::p2p; +using namespace dev::eth; static void initUnits(QComboBox* _b) { @@ -177,12 +142,12 @@ Main::Main(QWidget *parent) : connect(ui->ourAccounts->model(), SIGNAL(rowsMoved(const QModelIndex &, int, int, const QModelIndex &, int)), SLOT(ourAccountsRowsMoved())); - m_client.reset(new Client("AlethZero")); + m_webThree.reset(new WebThreeDirect("AlethZero", getDataDir() + "/AlethZero", false)); connect(ui->webView, &QWebView::loadStarted, [this]() { // NOTE: no need to delete as QETH_INSTALL_JS_NAMESPACE adopts it. - m_ethereum = new QEthereum(this, client(), owned()); + m_ethereum = new QEthereum(this, ethereum(), owned()); QWebFrame* f = ui->webView->page()->mainFrame(); f->disconnect(SIGNAL(javaScriptWindowObjectCleared())); @@ -233,14 +198,14 @@ void Main::onKeysChanged() unsigned Main::installWatch(dev::eth::MessageFilter const& _tf, std::function const& _f) { - auto ret = client()->installWatch(_tf); + auto ret = ethereum()->installWatch(_tf); m_handlers[ret] = _f; return ret; } unsigned Main::installWatch(dev::h256 _tf, std::function const& _f) { - auto ret = client()->installWatch(_tf); + auto ret = ethereum()->installWatch(_tf); m_handlers[ret] = _f; return ret; } @@ -255,14 +220,14 @@ void Main::installWatches() void Main::installNameRegWatch() { - client()->uninstallWatch(m_nameRegFilter); - m_nameRegFilter = installWatch(dev::eth::MessageFilter().altered((u160)client()->stateAt(c_config, 0)), [=](){ onNameRegChange(); }); + ethereum()->uninstallWatch(m_nameRegFilter); + m_nameRegFilter = installWatch(dev::eth::MessageFilter().altered((u160)ethereum()->stateAt(c_config, 0)), [=](){ onNameRegChange(); }); } void Main::installCurrenciesWatch() { - client()->uninstallWatch(m_currenciesFilter); - m_currenciesFilter = installWatch(dev::eth::MessageFilter().altered((u160)client()->stateAt(c_config, 1)), [=](){ onCurrenciesChange(); }); + ethereum()->uninstallWatch(m_currenciesFilter); + m_currenciesFilter = installWatch(dev::eth::MessageFilter().altered((u160)ethereum()->stateAt(c_config, 1)), [=](){ onCurrenciesChange(); }); } void Main::installBalancesWatch() @@ -270,9 +235,9 @@ void Main::installBalancesWatch() dev::eth::MessageFilter tf; vector
    altCoins; - Address coinsAddr = right160(client()->stateAt(c_config, 1)); - for (unsigned i = 0; i < client()->stateAt(coinsAddr, 0); ++i) - altCoins.push_back(right160(client()->stateAt(coinsAddr, i + 1))); + Address coinsAddr = right160(ethereum()->stateAt(c_config, 1)); + for (unsigned i = 0; i < ethereum()->stateAt(coinsAddr, 0); ++i) + altCoins.push_back(right160(ethereum()->stateAt(coinsAddr, i + 1))); for (auto i: m_myKeys) { tf.altered(i.address()); @@ -280,7 +245,7 @@ void Main::installBalancesWatch() tf.altered(c, (u160)i.address()); } - client()->uninstallWatch(m_balancesFilter); + ethereum()->uninstallWatch(m_balancesFilter); m_balancesFilter = installWatch(tf, [=](){ onBalancesChange(); }); } @@ -328,7 +293,7 @@ void Main::onNewPending() void Main::on_forceMining_triggered() { - client()->setForceMining(ui->forceMining->isChecked()); + ethereum()->setForceMining(ui->forceMining->isChecked()); } void Main::on_enableOptimizer_triggered() @@ -422,11 +387,11 @@ QString Main::pretty(dev::Address _a) const { h256 n; - if (h160 nameReg = (u160)client()->stateAt(c_config, 0)) - n = client()->stateAt(nameReg, (u160)(_a)); + if (h160 nameReg = (u160)ethereum()->stateAt(c_config, 0)) + n = ethereum()->stateAt(nameReg, (u160)(_a)); if (!n) - n = client()->stateAt(m_nameReg, (u160)(_a)); + n = ethereum()->stateAt(m_nameReg, (u160)(_a)); return fromRaw(n); } @@ -452,11 +417,11 @@ Address Main::fromString(QString const& _a) const memset(n.data() + sn.size(), 0, 32 - sn.size()); if (_a.size()) { - if (h160 nameReg = (u160)client()->stateAt(c_config, 0)) - if (h256 a = client()->stateAt(nameReg, n)) + if (h160 nameReg = (u160)ethereum()->stateAt(c_config, 0)) + if (h256 a = ethereum()->stateAt(nameReg, n)) return right160(a); - if (h256 a = client()->stateAt(m_nameReg, n)) + if (h256 a = ethereum()->stateAt(m_nameReg, n)) return right160(a); } if (_a.size() == 40) @@ -484,11 +449,11 @@ QString Main::lookup(QString const& _a) const */ h256 ret; - if (h160 dnsReg = (u160)client()->stateAt(c_config, 4, 0)) - ret = client()->stateAt(dnsReg, n); + if (h160 dnsReg = (u160)ethereum()->stateAt(c_config, 4, 0)) + ret = ethereum()->stateAt(dnsReg, n); /* if (!ret) - if (h160 nameReg = (u160)m_client->stateAt(c_config, 0, 0)) - ret = m_client->stateAt(nameReg, n2); + if (h160 nameReg = (u160)ethereum()->stateAt(c_config, 0, 0)) + ret = ethereum()->stateAt(nameReg, n2); */ if (ret && !((u256)ret >> 32)) return QString("%1.%2.%3.%4").arg((int)ret[28]).arg((int)ret[29]).arg((int)ret[30]).arg((int)ret[31]); @@ -506,7 +471,7 @@ void Main::on_about_triggered() void Main::on_paranoia_triggered() { - client()->setParanoia(ui->paranoia->isChecked()); + ethereum()->setParanoia(ui->paranoia->isChecked()); } void Main::writeSettings() @@ -537,7 +502,7 @@ void Main::writeSettings() s.setValue("privateChain", m_privateChain); s.setValue("verbosity", ui->verbosity->value()); - bytes d = client()->savePeers(); + bytes d = ethereum()->savePeers(); if (d.size()) m_peers = QByteArray((char*)d.data(), (int)d.size()); s.setValue("peers", m_peers); @@ -569,7 +534,7 @@ void Main::readSettings(bool _skipGeometry) m_myKeys.append(KeyPair(k)); } } - client()->setAddress(m_myKeys.back().address()); + ethereum()->setAddress(m_myKeys.back().address()); m_peers = s.value("peers").toByteArray(); ui->upnp->setChecked(s.value("upnp", true).toBool()); ui->forceAddress->setText(s.value("forceAddress", "").toString()); @@ -665,17 +630,17 @@ void Main::on_nameReg_textChanged() void Main::on_preview_triggered() { - client()->setDefault(ui->preview->isChecked() ? 0 : -1); + ethereum()->setDefault(ui->preview->isChecked() ? 0 : -1); refreshAll(); } void Main::refreshMining() { - dev::eth::MineProgress p = client()->miningProgress(); - ui->mineStatus->setText(client()->isMining() ? QString("%1s @ %2kH/s").arg(p.ms / 1000).arg(p.ms ? p.hashes / p.ms : 0) : "Not mining"); + dev::eth::MineProgress p = ethereum()->miningProgress(); + ui->mineStatus->setText(ethereum()->isMining() ? QString("%1s @ %2kH/s").arg(p.ms / 1000).arg(p.ms ? p.hashes / p.ms : 0) : "Not mining"); if (!ui->miningView->isVisible()) return; - list l = client()->miningHistory(); + list l = ethereum()->miningHistory(); static unsigned lh = 0; if (p.hashes < lh) ui->miningView->resetStats(); @@ -694,12 +659,12 @@ void Main::refreshBalances() ui->ourAccounts->clear(); u256 totalBalance = 0; map> altCoins; - Address coinsAddr = right160(client()->stateAt(c_config, 1)); - for (unsigned i = 0; i < client()->stateAt(coinsAddr, 0); ++i) + Address coinsAddr = right160(ethereum()->stateAt(c_config, 1)); + for (unsigned i = 0; i < ethereum()->stateAt(coinsAddr, 0); ++i) { - auto n = client()->stateAt(coinsAddr, i + 1); - auto addr = right160(client()->stateAt(coinsAddr, n)); - auto denom = client()->stateAt(coinsAddr, sha3(h256(n).asBytes())); + auto n = ethereum()->stateAt(coinsAddr, i + 1); + auto addr = right160(ethereum()->stateAt(coinsAddr, n)); + auto denom = ethereum()->stateAt(coinsAddr, sha3(h256(n).asBytes())); if (denom == 0) denom = 1; // cdebug << n << addr << denom << sha3(h256(n).asBytes()); @@ -707,13 +672,13 @@ void Main::refreshBalances() } for (auto i: m_myKeys) { - u256 b = client()->balanceAt(i.address()); - (new QListWidgetItem(QString("%2: %1 [%3]").arg(formatBalance(b).c_str()).arg(render(i.address())).arg((unsigned)client()->countAt(i.address())), ui->ourAccounts)) + u256 b = ethereum()->balanceAt(i.address()); + (new QListWidgetItem(QString("%2: %1 [%3]").arg(formatBalance(b).c_str()).arg(render(i.address())).arg((unsigned)ethereum()->countAt(i.address())), ui->ourAccounts)) ->setData(Qt::UserRole, QByteArray((char const*)i.address().data(), Address::size)); totalBalance += b; for (auto& c: altCoins) - get<1>(c.second) += (u256)client()->stateAt(c.first, (u160)i.address()); + get<1>(c.second) += (u256)ethereum()->stateAt(c.first, (u160)i.address()); } QString b; @@ -729,7 +694,7 @@ void Main::refreshBalances() void Main::refreshNetwork() { - auto ps = client()->peers(); + auto ps = web3()->peers(); ui->peerCount->setText(QString::fromStdString(toString(ps.size())) + " peer(s)"); ui->peers->clear(); @@ -751,7 +716,7 @@ void Main::refreshPending() { cwatch << "refreshPending()"; ui->transactionQueue->clear(); - for (Transaction const& t: client()->pending()) + for (Transaction const& t: ethereum()->pending()) { QString s = t.receiveAddress ? QString("%2 %5> %3: %1 [%4]") @@ -759,7 +724,7 @@ void Main::refreshPending() .arg(render(t.safeSender())) .arg(render(t.receiveAddress)) .arg((unsigned)t.nonce) - .arg(client()->codeAt(t.receiveAddress).size() ? '*' : '-') : + .arg(ethereum()->codeAt(t.receiveAddress).size() ? '*' : '-') : QString("%2 +> %3: %1 [%4]") .arg(formatBalance(t.value).c_str()) .arg(render(t.safeSender())) @@ -775,16 +740,16 @@ void Main::refreshAccounts() ui->accounts->clear(); ui->contracts->clear(); for (auto n = 0; n < 2; ++n) - for (auto i: client()->addresses()) + for (auto i: ethereum()->addresses()) { auto r = render(i); if (r.contains('(') == !n) { if (n == 0 || ui->showAllAccounts->isChecked()) - (new QListWidgetItem(QString("%2: %1 [%3]").arg(formatBalance(client()->balanceAt(i)).c_str()).arg(r).arg((unsigned)client()->countAt(i)), ui->accounts)) + (new QListWidgetItem(QString("%2: %1 [%3]").arg(formatBalance(ethereum()->balanceAt(i)).c_str()).arg(r).arg((unsigned)ethereum()->countAt(i)), ui->accounts)) ->setData(Qt::UserRole, QByteArray((char const*)i.data(), Address::size)); - if (client()->codeAt(i).size()) - (new QListWidgetItem(QString("%2: %1 [%3]").arg(formatBalance(client()->balanceAt(i)).c_str()).arg(r).arg((unsigned)client()->countAt(i)), ui->contracts)) + if (ethereum()->codeAt(i).size()) + (new QListWidgetItem(QString("%2: %1 [%3]").arg(formatBalance(ethereum()->balanceAt(i)).c_str()).arg(r).arg((unsigned)ethereum()->countAt(i)), ui->contracts)) ->setData(Qt::UserRole, QByteArray((char const*)i.data(), Address::size)); } } @@ -794,7 +759,7 @@ void Main::refreshDestination() { cwatch << "refreshDestination()"; QString s; - for (auto i: client()->addresses()) + for (auto i: ethereum()->addresses()) if ((s = pretty(i)).size()) // A namereg address if (ui->destination->findText(s, Qt::MatchExactly | Qt::MatchCaseSensitive) == -1) @@ -807,8 +772,8 @@ void Main::refreshDestination() void Main::refreshBlockCount() { cwatch << "refreshBlockCount()"; - auto d = client()->blockChain().details(); - auto diff = BlockInfo(client()->blockChain().block()).difficulty; + auto d = ethereum()->blockChain().details(); + auto diff = BlockInfo(ethereum()->blockChain().block()).difficulty; ui->blockCount->setText(QString("%6 #%1 @%3 T%2 N%4 D%5").arg(d.number).arg(toLog2(d.totalDifficulty)).arg(toLog2(diff)).arg(dev::eth::c_protocolVersion).arg(dev::eth::c_databaseVersion).arg(m_privateChain.size() ? "[" + m_privateChain + "] " : "testnet")); } @@ -839,7 +804,7 @@ static bool transactionMatch(string const& _f, Transaction const& _t) void Main::on_turboMining_triggered() { - client()->setTurboMining(ui->turboMining->isChecked()); + ethereum()->setTurboMining(ui->turboMining->isChecked()); } void Main::refreshBlockChain() @@ -850,7 +815,7 @@ void Main::refreshBlockChain() ui->blocks->clear(); string filter = ui->blockChainFilter->text().toLower().toStdString(); - auto const& bc = client()->blockChain(); + auto const& bc = ethereum()->blockChain(); unsigned i = (ui->showAll->isChecked() || !filter.empty()) ? (unsigned)-1 : 10; for (auto h = bc.currentHash(); h != bc.genesisHash() && bc.details(h) && i; h = bc.details(h).parent, --i) { @@ -877,7 +842,7 @@ void Main::refreshBlockChain() .arg(render(t.safeSender())) .arg(render(t.receiveAddress)) .arg((unsigned)t.nonce) - .arg(client()->codeAt(t.receiveAddress).size() ? '*' : '-') : + .arg(ethereum()->codeAt(t.receiveAddress).size() ? '*' : '-') : QString(" %2 +> %3: %1 [%4]") .arg(formatBalance(t.value).c_str()) .arg(render(t.safeSender())) @@ -948,7 +913,7 @@ void Main::timerEvent(QTimerEvent*) m_ethereum->poll(); for (auto const& i: m_handlers) - if (client()->checkWatch(i.first)) + if (ethereum()->checkWatch(i.first)) i.second(); } @@ -1019,9 +984,9 @@ void Main::on_transactionQueue_currentItemChanged() stringstream s; int i = ui->transactionQueue->currentRow(); - if (i >= 0 && i < (int)client()->pending().size()) + if (i >= 0 && i < (int)ethereum()->pending().size()) { - Transaction tx(client()->pending()[i]); + Transaction tx(ethereum()->pending()[i]); auto ss = tx.safeSender(); h256 th = sha3(rlpList(ss, tx.nonce)); s << "

    " << th << "

    "; @@ -1049,7 +1014,7 @@ void Main::on_transactionQueue_currentItemChanged() // s << "Pre: " << fs.rootHash() << "
    "; // s << "Post: " << ts.rootHash() << ""; - s << renderDiff(client()->diff(i, 0)); + s << renderDiff(ethereum()->diff(i, 0)); } ui->pendingInfo->setHtml(QString::fromStdString(s.str())); @@ -1076,7 +1041,7 @@ void Main::on_inject_triggered() { QString s = QInputDialog::getText(this, "Inject Transaction", "Enter transaction dump in hex"); bytes b = fromHex(s.toStdString()); - client()->inject(&b); + ethereum()->inject(&b); } void Main::on_blocks_currentItemChanged() @@ -1090,8 +1055,8 @@ void Main::on_blocks_currentItemChanged() auto hba = item->data(Qt::UserRole).toByteArray(); assert(hba.size() == 32); auto h = h256((byte const*)hba.data(), h256::ConstructFromPointer); - auto details = client()->blockChain().details(h); - auto blockData = client()->blockChain().block(h); + auto details = ethereum()->blockChain().details(h); + auto blockData = ethereum()->blockChain().block(h); auto block = RLP(blockData); BlockInfo info(blockData); @@ -1115,7 +1080,7 @@ void Main::on_blocks_currentItemChanged() s << "
    Bloom: " << details.bloom << ""; s << "
    Transactions: " << block[1].itemCount() << " @" << info.transactionsRoot << ""; s << "
    Uncles: " << block[2].itemCount() << " @" << info.sha3Uncles << ""; - s << "
    Pre: " << BlockInfo(client()->blockChain().block(info.parentHash)).stateRoot << ""; + s << "
    Pre: " << BlockInfo(ethereum()->blockChain().block(info.parentHash)).stateRoot << ""; for (auto const& i: block[1]) s << "
    " << sha3(i[0].data()).abridged() << ": " << i[1].toHash() << " [" << i[2].toInt() << " used]"; s << "
    Post: " << info.stateRoot << ""; @@ -1151,7 +1116,7 @@ void Main::on_blocks_currentItemChanged() if (tx.data.size()) s << dev::memDump(tx.data, 16, true); } - s << renderDiff(client()->diff(txi, h)); + s << renderDiff(ethereum()->diff(txi, h)); ui->debugCurrent->setEnabled(true); ui->debugDumpState->setEnabled(true); ui->debugDumpStatePre->setEnabled(true); @@ -1173,7 +1138,7 @@ void Main::on_debugCurrent_triggered() if (!item->data(Qt::UserRole + 1).isNull()) { unsigned txi = item->data(Qt::UserRole + 1).toInt(); - m_executiveState = client()->state(txi + 1, h); + m_executiveState = ethereum()->state(txi + 1, h); m_currentExecution = unique_ptr(new Executive(m_executiveState)); Transaction t = m_executiveState.pending()[txi]; m_executiveState = m_executiveState.fromPending(txi); @@ -1199,7 +1164,7 @@ void Main::on_debugDumpState_triggered(int _add) if (f.is_open()) { unsigned txi = item->data(Qt::UserRole + 1).toInt(); - f << client()->state(txi + _add, h) << endl; + f << ethereum()->state(txi + _add, h) << endl; } } } @@ -1265,10 +1230,10 @@ void Main::on_contracts_currentItemChanged() stringstream s; try { - auto storage = client()->storageAt(address); + auto storage = ethereum()->storageAt(address); for (auto const& i: storage) s << "@" << showbase << hex << prettyU256(i.first).toStdString() << "    " << showbase << hex << prettyU256(i.second).toStdString() << "
    "; - s << "

    Body Code

    " << disassemble(client()->codeAt(address)); + s << "

    Body Code

    " << disassemble(ethereum()->codeAt(address)); ui->contractInfo->appendHtml(QString::fromStdString(s.str())); } catch (dev::eth::InvalidTrie) @@ -1281,7 +1246,7 @@ void Main::on_contracts_currentItemChanged() void Main::on_idealPeers_valueChanged() { - client()->setIdealPeerCount(ui->idealPeers->value()); + ethereum()->setIdealPeerCount(ui->idealPeers->value()); } void Main::on_ourAccounts_doubleClicked() @@ -1427,7 +1392,7 @@ void Main::on_data_textChanged() s = s.mid(1); } ui->code->setHtml(QString::fromStdString(dev::memDump(m_data, 8, true))); - if (client()->codeAt(fromString(ui->destination->currentText()), 0).size()) + if (ethereum()->codeAt(fromString(ui->destination->currentText()), 0).size()) { ui->gas->setMinimum((qint64)Client::txGas(m_data.size(), 1)); if (!ui->gas->isEnabled()) @@ -1450,9 +1415,8 @@ void Main::on_killBlockchain_triggered() writeSettings(); ui->mine->setChecked(false); ui->net->setChecked(false); - m_client.reset(); - m_client.reset(new Client("AlethZero", Address(), string(), true)); - m_ethereum->setClient(client()); + ethereum()->killChain(); + m_ethereum->setClient(ethereum()); readSettings(true); installWatches(); refreshAll(); @@ -1495,7 +1459,7 @@ void Main::updateFee() bool ok = false; for (auto i: m_myKeys) - if (client()->balanceAt(i.address()) >= totalReq) + if (ethereum()->balanceAt(i.address()) >= totalReq) { ok = true; break; @@ -1514,15 +1478,17 @@ void Main::on_net_triggered() if (ui->clientName->text().size()) n += "/" + ui->clientName->text().toStdString(); n += "/" DEV_QUOTED(ETH_BUILD_TYPE) "/" DEV_QUOTED(ETH_BUILD_PLATFORM); - client()->setClientVersion(n); + web3()->setClientVersion(n); if (ui->net->isChecked()) { - client()->startNetwork(ui->port->value(), string(), 0, NodeMode::Full, ui->idealPeers->value(), ui->forceAddress->text().toStdString(), ui->upnp->isChecked(), m_privateChain.size() ? sha3(m_privateChain.toStdString()) : 0); + // TODO: alter network stuff? + //ui->port->value(), string(), 0, NodeMode::Full, ui->idealPeers->value(), ui->forceAddress->text().toStdString(), ui->upnp->isChecked(), m_privateChain.size() ? sha3(m_privateChain.toStdString()) : 0 + web3()->startNetwork(); if (m_peers.size() && ui->usePast->isChecked()) - client()->restorePeers(bytesConstRef((byte*)m_peers.data(), m_peers.size())); + web3()->restorePeers(bytesConstRef((byte*)m_peers.data(), m_peers.size())); } else - client()->stopNetwork(); + web3()->stopNetwork(); } void Main::on_connect_triggered() @@ -1538,7 +1504,7 @@ void Main::on_connect_triggered() { string host = s.section(":", 0, 0).toStdString(); unsigned short port = s.section(":", 1).toInt(); - client()->connect(host, port); + web3()->connect(host, port); } } @@ -1552,25 +1518,25 @@ void Main::on_mine_triggered() { if (ui->mine->isChecked()) { - client()->setAddress(m_myKeys.last().address()); - client()->startMining(); + ethereum()->setAddress(m_myKeys.last().address()); + ethereum()->startMining(); } else - client()->stopMining(); + ethereum()->stopMining(); } void Main::on_send_clicked() { u256 totalReq = value() + fee(); for (auto i: m_myKeys) - if (client()->balanceAt(i.address(), 0) >= totalReq) + if (ethereum()->balanceAt(i.address(), 0) >= totalReq) { debugFinished(); Secret s = i.secret(); if (isCreation()) - client()->transact(s, value(), m_data, ui->gas->value(), gasPrice()); + ethereum()->transact(s, value(), m_data, ui->gas->value(), gasPrice()); else - client()->transact(s, value(), fromString(ui->destination->currentText()), m_data, ui->gas->value(), gasPrice()); + ethereum()->transact(s, value(), fromString(ui->destination->currentText()), m_data, ui->gas->value(), gasPrice()); return; } statusBar()->showMessage("Couldn't make transaction: no single account contains at least the required amount."); @@ -1583,10 +1549,10 @@ void Main::on_debug_clicked() { u256 totalReq = value() + fee(); for (auto i: m_myKeys) - if (client()->balanceAt(i.address()) >= totalReq) + if (ethereum()->balanceAt(i.address()) >= totalReq) { Secret s = i.secret(); - m_executiveState = client()->postState(); + m_executiveState = ethereum()->postState(); m_currentExecution = unique_ptr(new Executive(m_executiveState)); Transaction t; t.nonce = m_executiveState.transactionsFrom(dev::toAddress(s)); diff --git a/alethzero/MainWin.h b/alethzero/MainWin.h index a3bfa718d..212f0133a 100644 --- a/alethzero/MainWin.h +++ b/alethzero/MainWin.h @@ -31,6 +31,7 @@ #include #include #include +#include namespace Ui { class Main; @@ -69,7 +70,9 @@ public: explicit Main(QWidget *parent = 0); ~Main(); - dev::eth::Client* client() const { return m_client.get(); } + dev::WebThreeDirect* web3() const { return m_webThree.get(); } + dev::eth::Client* ethereum() const { return m_webThree->ethereum(); } + dev::shh::WhisperHost* whisper() const { return m_webThree->whisper(); } QList const& owned() const { return m_myKeys; } @@ -200,7 +203,7 @@ private: std::unique_ptr ui; - std::unique_ptr m_client; + std::unique_ptr m_webThree; std::map> m_handlers; unsigned m_nameRegFilter = (unsigned)-1; diff --git a/libdevcrypto/FileSystem.cpp b/libdevcrypto/FileSystem.cpp index 1fb38b57f..e161c3234 100644 --- a/libdevcrypto/FileSystem.cpp +++ b/libdevcrypto/FileSystem.cpp @@ -31,9 +31,8 @@ #include using namespace std; using namespace dev; -using namespace dev::eth; -std::string dev::eth::getDataDir() +std::string dev::getDataDir() { #ifdef _WIN32 char path[1024] = ""; diff --git a/libdevcrypto/FileSystem.h b/libdevcrypto/FileSystem.h index 9c597c421..605545b0d 100644 --- a/libdevcrypto/FileSystem.h +++ b/libdevcrypto/FileSystem.h @@ -27,11 +27,8 @@ namespace dev { -namespace eth -{ /// @returns the path for user data. std::string getDataDir(); } -} diff --git a/libethcore/BlockInfo.h b/libethcore/BlockInfo.h index 8186acaee..283f11b88 100644 --- a/libethcore/BlockInfo.h +++ b/libethcore/BlockInfo.h @@ -56,7 +56,7 @@ extern u256 c_genesisDifficulty; struct BlockInfo { public: - h256 hash; ///< SHA3 hash of the entire block! Not serialised (the only member not contained in a block header). + h256 hash; ///< SHA3 hash of the block header! Not serialised (the only member not contained in a block header). h256 parentHash; h256 sha3Uncles; Address coinbaseAddress; diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index 92ff5d1cd..29f405448 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -124,6 +124,11 @@ void Client::flushTransactions() work(); } +void Client::killChain() +{ + // TODO +} + void Client::clearPending() { WriteGuard l(x_stateDB); diff --git a/libethereum/Client.h b/libethereum/Client.h index 8fc55aafb..5e6cabd70 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -165,6 +165,9 @@ public: // [EXTRA API]: + /// @returns the length of the chain. + virtual unsigned number() const { return m_bc.number(); } + /// Get a map containing each of the pending transactions. /// @TODO: Remove in favour of transactions(). Transactions pending() const { return m_postMine.pending(); } @@ -254,8 +257,12 @@ public: /// Get and clear the mining history. std::list miningHistory(); + // Debug stuff: + /// Clears pending transactions. Just for debug use. void clearPending(); + /// Kills the blockchain. Just for debug use. + void killChain(); private: /// Ensure the worker thread is running. Needed for blockchain maintenance & mining. diff --git a/libethereum/Interface.h b/libethereum/Interface.h index 3fa93a286..a5df3e5b1 100644 --- a/libethereum/Interface.h +++ b/libethereum/Interface.h @@ -95,8 +95,13 @@ public: virtual bool peekWatch(unsigned _watchId) const = 0; virtual bool checkWatch(unsigned _watchId) = 0; + // TODO: Block query API. + // [EXTRA API]: + /// @returns The height of the chain. + virtual unsigned number() const = 0; + /// Get a map containing each of the pending transactions. /// @TODO: Remove in favour of transactions(). virtual Transactions pending() const = 0; diff --git a/libqethereum/QEthereum.cpp b/libqethereum/QEthereum.cpp index 689daf8d7..714081dfe 100644 --- a/libqethereum/QEthereum.cpp +++ b/libqethereum/QEthereum.cpp @@ -8,41 +8,8 @@ #include #include "QEthereum.h" using namespace std; - -// types -using dev::bytes; -using dev::bytesConstRef; -using dev::h160; -using dev::h256; -using dev::u160; -using dev::u256; -using dev::u256s; -using dev::RLP; -using dev::Address; -using dev::eth::BlockInfo; -using dev::eth::Client; -using dev::eth::Instruction; -using dev::KeyPair; -using dev::eth::NodeMode; -using dev::p2p::PeerInfo; -using dev::Secret; -using dev::eth::Transaction; - -// functions -using dev::toHex; -using dev::fromHex; -using dev::right160; -using dev::simpleDebugOut; -using dev::toLog2; -using dev::toString; -using dev::operator+; -using dev::eth::disassemble; -using dev::eth::units; -using dev::eth::formatBalance; - -// vars -using dev::g_logPost; -using dev::g_logVerbosity; +using namespace dev; +using namespace dev::eth; dev::bytes toBytes(QString const& _s) { @@ -89,7 +56,7 @@ QString unpadded(QString _s) return _s; } -QEthereum::QEthereum(QObject* _p, Client* _c, QList _accounts): +QEthereum::QEthereum(QObject* _p, eth::Interface* _c, QList _accounts): QObject(_p), m_client(_c), m_accounts(_accounts) { // required to prevent crash on osx when performing addto/evaluatejavascript calls @@ -120,7 +87,7 @@ QString QEthereum::secretToAddress(QString _s) const return toQJS(KeyPair(toSecret(_s)).address()); } -Client* QEthereum::client() const +eth::Interface* QEthereum::client() const { return m_client; } @@ -162,7 +129,7 @@ QString QEthereum::coinbase() const QString QEthereum::number() const { - return m_client ? QString::number(client()->blockChain().number() + 1) : ""; + return m_client ? QString::number(client()->number() + 1) : ""; } QString QEthereum::account() const @@ -381,7 +348,7 @@ bool QEthereum::isMining() const bool QEthereum::isListening() const { - return m_client ? client()->haveNetwork() : false; + return /*m_client ? client()->haveNetwork() :*/ false; } void QEthereum::setMining(bool _l) @@ -395,19 +362,19 @@ void QEthereum::setMining(bool _l) } } -void QEthereum::setListening(bool _l) +void QEthereum::setListening(bool) { if (!m_client) return; - if (_l) +/* if (_l) client()->startNetwork(); else - client()->stopNetwork(); + client()->stopNetwork();*/ } unsigned QEthereum::peerCount() const { - return m_client ? (unsigned)client()->peerCount() : 0; + return /*m_client ? (unsigned)client()->peerCount() :*/ 0; } QString QEthereum::doCreate(QString _secret, QString _amount, QString _init, QString _gas, QString _gasPrice) diff --git a/libqethereum/QEthereum.h b/libqethereum/QEthereum.h index cacf1cfc4..50f2d96a5 100644 --- a/libqethereum/QEthereum.h +++ b/libqethereum/QEthereum.h @@ -7,8 +7,7 @@ #include namespace dev { namespace eth { -class Client; -class State; +class Interface; }} class QJSEngine; @@ -99,11 +98,11 @@ class QEthereum: public QObject Q_OBJECT public: - QEthereum(QObject* _p, dev::eth::Client* _c, QList _accounts); + QEthereum(QObject* _p, dev::eth::Interface* _c, QList _accounts); virtual ~QEthereum(); - dev::eth::Client* client() const; - void setClient(dev::eth::Client* _c) { m_client = _c; } + dev::eth::Interface* client() const; + void setClient(dev::eth::Interface* _c) { m_client = _c; } /// Call when the client() is going to be deleted to make this object useless but safe. void clientDieing(); @@ -199,7 +198,7 @@ private: Q_PROPERTY(unsigned peerCount READ peerCount NOTIFY miningChanged) Q_PROPERTY(int defaultBlock READ getDefault WRITE setDefault) - dev::eth::Client* m_client; + dev::eth::Interface* m_client; std::vector m_watches; QList m_accounts; }; diff --git a/libwebthree/WebThree.h b/libwebthree/WebThree.h index cf02c3122..05f0ca104 100644 --- a/libwebthree/WebThree.h +++ b/libwebthree/WebThree.h @@ -137,7 +137,7 @@ class RPCMaster {}; class EthereumSlave: public eth::Interface { public: - EthereumSlave(RPCSlave* _c) {} + EthereumSlave(RPCSlave*) {} // TODO: implement all of the virtuals with the RLPClient link. }; @@ -145,7 +145,7 @@ public: class EthereumMaster { public: - EthereumMaster(RPCMaster* _m) {} + EthereumMaster(RPCMaster*) {} // TODO: implement the master-end of whatever the RLPClient link will send over. }; @@ -155,7 +155,7 @@ public: class WhisperSlave: public shh::Interface { public: - WhisperSlave(RPCSlave* _c) {} + WhisperSlave(RPCSlave*) {} // TODO: implement all of the virtuals with the RLPClient link. }; @@ -163,7 +163,7 @@ public: class WhisperMaster { public: - WhisperMaster(RPCMaster* _m) {} + WhisperMaster(RPCMaster*) {} // TODO: implement the master-end of whatever the RLPClient link will send over. }; diff --git a/walleth/MainWin.cpp b/walleth/MainWin.cpp index ade04c4fa..3fa5a9388 100644 --- a/walleth/MainWin.cpp +++ b/walleth/MainWin.cpp @@ -61,7 +61,7 @@ Main::Main(QWidget *parent) : g_qmlMain = this; - m_client.reset(new Client("Walleth", Address(), dev::eth::getDataDir() + "/Walleth")); + m_client.reset(new Client("Walleth", Address(), dev::getDataDir() + "/Walleth")); g_qmlClient = m_client.get(); From 484279c548ebbb6582b670e501922d5dd3ce0151 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 11 Sep 2014 13:27:15 +0200 Subject: [PATCH 167/223] Beginning of merge into AZ. --- alethzero/MainWin.cpp | 11 ++++- alethzero/MainWin.h | 2 + exp/main.cpp | 2 +- libethereum/Client.cpp | 29 +++++++----- libethereum/Client.h | 2 + libethereum/EthereumHost.cpp | 3 +- libethereum/EthereumHost.h | 3 +- libp2p/Host.cpp | 85 ++++++++++++++++++++---------------- libp2p/Host.h | 27 +++++++++--- libp2p/HostCapability.h | 1 - libp2p/Session.cpp | 2 +- libwebthree/WebThree.cpp | 42 +++++++++--------- libwebthree/WebThree.h | 12 ++--- test/peer.cpp | 2 +- 14 files changed, 135 insertions(+), 88 deletions(-) diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index 0e2565ae6..734854c2a 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -79,7 +79,6 @@ static QString fromRaw(dev::h256 _n, unsigned* _inc = nullptr) return QString(); } - Address c_config = Address("661005d2720d855f1d9976f88bb10c1a3398c77f"); Main::Main(QWidget *parent) : @@ -142,7 +141,7 @@ Main::Main(QWidget *parent) : connect(ui->ourAccounts->model(), SIGNAL(rowsMoved(const QModelIndex &, int, int, const QModelIndex &, int)), SLOT(ourAccountsRowsMoved())); - m_webThree.reset(new WebThreeDirect("AlethZero", getDataDir() + "/AlethZero", false)); + m_webThree.reset(new WebThreeDirect("AlethZero", getDataDir() + "/AlethZero", false, {"eth", "shh"})); connect(ui->webView, &QWebView::loadStarted, [this]() { @@ -191,6 +190,11 @@ Main::~Main() writeSettings(); } +dev::p2p::NetworkPreferences Main::netPrefs() const +{ + return NetworkPreferences(ui->port->value(), ui->forceAddress->text().toStdString(), ui->upnp->isChecked(), false); +} + void Main::onKeysChanged() { installBalancesWatch(); @@ -1483,6 +1487,9 @@ void Main::on_net_triggered() { // TODO: alter network stuff? //ui->port->value(), string(), 0, NodeMode::Full, ui->idealPeers->value(), ui->forceAddress->text().toStdString(), ui->upnp->isChecked(), m_privateChain.size() ? sha3(m_privateChain.toStdString()) : 0 + web3()->setIdealPeerCount(ui->idealPeers->value()); + web3()->setNetworkPreferences(netPrefs()); + ethereum()->setNetworkId(m_privateChain.size() ? sha3(m_privateChain.toStdString()) : 0); web3()->startNetwork(); if (m_peers.size() && ui->usePast->isChecked()) web3()->restorePeers(bytesConstRef((byte*)m_peers.data(), m_peers.size())); diff --git a/alethzero/MainWin.h b/alethzero/MainWin.h index 212f0133a..ce1a9b670 100644 --- a/alethzero/MainWin.h +++ b/alethzero/MainWin.h @@ -149,6 +149,8 @@ signals: void poll(); private: + dev::p2p::NetworkPreferences netPrefs() const; + QString pretty(dev::Address _a) const; QString prettyU256(dev::u256 _n) const; diff --git a/exp/main.cpp b/exp/main.cpp index c49b13e44..9e9971852 100644 --- a/exp/main.cpp +++ b/exp/main.cpp @@ -322,7 +322,7 @@ int main(int argc, char** argv) remoteHost = argv[i]; } - Host ph("Test", listenPort, "", false, true); + Host ph("Test", NetworkPreferences(listenPort, "", false, true)); ph.registerCapability(new WhisperHost()); auto wh = ph.cap(); diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index 29f405448..2cf96731d 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -82,6 +82,18 @@ Client::Client(p2p::Host* _extNet, std::string const& _dbPath, bool _forceClean, Defaults::setDBPath(_dbPath); m_vc.setOk(); work(); + + static const char* c_threadName = "ethsync"; + m_workNet.reset(new thread([&]() + { + setThreadName(c_threadName); + m_workNetState.store(Active, std::memory_order_release); + while (m_workNetState.load(std::memory_order_acquire) != Deleting) + workNet(); + m_workNetState.store(Deleted, std::memory_order_release); + })); + + ensureWorking(); } Client::~Client() @@ -239,16 +251,7 @@ void Client::startNetwork(unsigned short _listenPort, std::string const& _seedHo if (!m_extHost.lock()) { - try - { - m_net.reset(new Host(m_clientVersion, _listenPort, _publicIP, _upnp)); - } - catch (std::exception const&) - { - // Probably already have the port open. - cwarn << "Could not initialize with specified/default port. Trying system-assigned port"; - m_net.reset(new Host(m_clientVersion, 0, _publicIP, _upnp)); - } + m_net.reset(new Host(m_clientVersion, NetworkPreferences(_listenPort, _publicIP, _upnp, false))); if (_mode == NodeMode::Full) m_net->registerCapability(new EthereumHost(m_bc, _networkId)); } @@ -283,6 +286,12 @@ void Client::stopNetwork() } } +void Client::setNetworkId(u256 _n) +{ + if (auto h = m_extHost.lock()) + h->setNetworkId(_n); +} + std::vector Client::peers() { ReadGuard l(x_net); diff --git a/libethereum/Client.h b/libethereum/Client.h index 5e6cabd70..c11b1322b 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -259,6 +259,8 @@ public: // Debug stuff: + /// Sets the network id. + void setNetworkId(u256 _n); /// Clears pending transactions. Just for debug use. void clearPending(); /// Kills the blockchain. Just for debug use. diff --git a/libethereum/EthereumHost.cpp b/libethereum/EthereumHost.cpp index 7d3bd9a92..80b7cfe88 100644 --- a/libethereum/EthereumHost.cpp +++ b/libethereum/EthereumHost.cpp @@ -44,6 +44,7 @@ EthereumHost::EthereumHost(BlockChain const& _ch, u256 _networkId): m_chain (&_ch), m_networkId (_networkId) { + m_latestBlockSent = _ch.currentHash(); } EthereumHost::~EthereumHost() @@ -78,7 +79,7 @@ h256Set EthereumHost::neededBlocks(h256Set const& _exclude) bool EthereumHost::ensureInitialised(TransactionQueue& _tq) { - if (m_latestBlockSent == h256()) + if (!m_latestBlockSent) { // First time - just initialise. m_latestBlockSent = m_chain->currentHash(); diff --git a/libethereum/EthereumHost.h b/libethereum/EthereumHost.h index bd0a5d0bc..61b716209 100644 --- a/libethereum/EthereumHost.h +++ b/libethereum/EthereumHost.h @@ -62,6 +62,7 @@ public: unsigned protocolVersion() const { return c_protocolVersion; } u256 networkId() const { return m_networkId; } + void setNetworkId(u256 _n) { m_networkId = _n; } /// Sync with the BlockChain. It might contain one of our mined blocks, we might have new candidates from the network. bool sync(TransactionQueue&, BlockQueue& _bc); @@ -84,7 +85,7 @@ private: h256Set neededBlocks(h256Set const& _exclude); /// Check to see if the network peer-state initialisation has happened. - virtual bool isInitialised() const { return m_latestBlockSent; } + bool isInitialised() const { return m_latestBlockSent; } /// Initialises the network peer-state, doing the stuff that needs to be once-only. @returns true if it really was first. bool ensureInitialised(TransactionQueue& _tq); diff --git a/libp2p/Host.cpp b/libp2p/Host.cpp index fdf1338b3..32bebc599 100644 --- a/libp2p/Host.cpp +++ b/libp2p/Host.cpp @@ -54,55 +54,69 @@ static const set c_rejectAddresses = { {bi::address_v6::from_string("::")} }; -Host::Host(std::string const& _clientVersion, unsigned short _port, string const& _publicAddress, bool _upnp, bool _localNetworking): +Host::Host(std::string const& _clientVersion, NetworkPreferences const& _n, bool _start): m_clientVersion(_clientVersion), - m_listenPort(_port), - m_localNetworking(_localNetworking), - m_acceptor(m_ioService, bi::tcp::endpoint(bi::tcp::v4(), _port)), + m_netPrefs(_n), + m_acceptor(m_ioService), m_socket(m_ioService), m_id(h512::random()) { populateAddresses(); - determinePublic(_publicAddress, _upnp); - ensureAccepting(); m_lastPeersRequest = chrono::steady_clock::time_point::min(); clog(NetNote) << "Id:" << m_id.abridged(); + if (_start) + start(); } -Host::Host(std::string const& _clientVersion, string const& _publicAddress, bool _upnp, bool _localNetworking): - m_clientVersion(_clientVersion), - m_listenPort(0), - m_localNetworking(_localNetworking), - m_acceptor(m_ioService, bi::tcp::endpoint(bi::tcp::v4(), 0)), - m_socket(m_ioService), - m_id(h512::random()) +Host::~Host() { - m_listenPort = m_acceptor.local_endpoint().port(); - - // populate addresses. - populateAddresses(); - determinePublic(_publicAddress, _upnp); - ensureAccepting(); - m_lastPeersRequest = chrono::steady_clock::time_point::min(); - clog(NetNote) << "Id:" << m_id.abridged(); + disconnectPeers(); + stop(); } -Host::Host(std::string const& _clientVersion): - m_clientVersion(_clientVersion), - m_listenPort(0), - m_acceptor(m_ioService, bi::tcp::endpoint(bi::tcp::v4(), 0)), - m_socket(m_ioService), - m_id(h512::random()) +void Host::start() { - // populate addresses. - populateAddresses(); + stop(); + for (unsigned i = 0; i < 2; ++i) + { + bi::tcp::endpoint endpoint(bi::tcp::v4(), i ? 0 : m_netPrefs.listenPort); + try + { + m_acceptor.open(endpoint.protocol()); + m_acceptor.set_option(ba::socket_base::reuse_address(true)); + m_acceptor.bind(endpoint); + m_acceptor.listen(); + } + catch (...) + { + if (i) + { + cwarn << "Couldn't start accepting connections on host. Something very wrong with network?"; + return; + } + m_acceptor.close(); + continue; + } + } + m_listenPort = m_acceptor.local_endpoint().port(); + + determinePublic(m_netPrefs.publicIP, m_netPrefs.upnp); + ensureAccepting(); m_lastPeersRequest = chrono::steady_clock::time_point::min(); clog(NetNote) << "Id:" << m_id.abridged(); } -Host::~Host() +void Host::stop() { - disconnectPeers(); + if (m_acceptor.is_open()) + { + if (m_accepting) + m_acceptor.cancel(); + m_acceptor.close(); + m_accepting = false; + } + if (m_socket.is_open()) + m_socket.close(); } unsigned Host::protocolVersion() const @@ -284,7 +298,7 @@ std::map Host::potentialPeers() { auto ep = j->endpoint(); // Skip peers with a listen port of zero or are on a private network - bool peerOnNet = (j->m_listenPort != 0 && (!isPrivateAddress(ep.address()) || m_localNetworking)); + bool peerOnNet = (j->m_listenPort != 0 && (!isPrivateAddress(ep.address()) || m_netPrefs.localNetworking)); if (peerOnNet && ep.port() && j->m_id) ret.insert(make_pair(i.first, ep)); } @@ -293,7 +307,7 @@ std::map Host::potentialPeers() void Host::ensureAccepting() { - if (m_accepting == false) + if (!m_accepting) { clog(NetConnect) << "Listening on local port " << m_listenPort << " (public: " << m_public << ")"; m_accepting = true; @@ -315,7 +329,7 @@ void Host::ensureAccepting() clog(NetWarn) << "ERROR: " << _e.what(); } m_accepting = false; - if (ec.value() != 1) + if (ec.value() < 1) ensureAccepting(); }); } @@ -459,9 +473,6 @@ std::vector Host::peers(bool _updatePing) const void Host::process() { - for (auto const& i: m_capabilities) - if (!i.second->isInitialised()) - return; growPeers(); prunePeers(); m_ioService.poll(); diff --git a/libp2p/Host.h b/libp2p/Host.h index d6673a072..b055b9aeb 100644 --- a/libp2p/Host.h +++ b/libp2p/Host.h @@ -41,6 +41,16 @@ class RLPStream; namespace p2p { +struct NetworkPreferences +{ + NetworkPreferences(unsigned short p = 30303, std::string i = std::string(), bool u = true, bool l = false): listenPort(p), publicIP(i), upnp(u), localNetworking(l) {} + + unsigned short listenPort = 30303; + std::string publicIP; + bool upnp = true; + bool localNetworking = false; +}; + /** * @brief The Host class * Capabilities should be registered prior to startNetwork, since m_capabilities is not thread-safe. @@ -52,11 +62,7 @@ class Host public: /// Start server, listening for connections on the given port. - Host(std::string const& _clientVersion, unsigned short _port, std::string const& _publicAddress = std::string(), bool _upnp = true, bool _localNetworking = false); - /// Start server, listening for connections on a system-assigned port. - Host(std::string const& _clientVersion, std::string const& _publicAddress = std::string(), bool _upnp = true, bool _localNetworking = false); - /// Start server, but don't listen. - Host(std::string const& _clientVersion); + Host(std::string const& _clientVersion, NetworkPreferences const& _n = NetworkPreferences(), bool _start = false); /// Will block on network process events. virtual ~Host(); @@ -107,6 +113,11 @@ public: /// Deserialise the data and populate the set of known peers. void restorePeers(bytesConstRef _b); + void setNetworkPreferences(NetworkPreferences const& _p) { stop(); m_netPrefs = _p; start(); } + + void start(); + void stop(); + h512 id() const { return m_id; } void registerPeer(std::shared_ptr _s, std::vector const& _caps); @@ -127,8 +138,10 @@ protected: std::string m_clientVersion; - unsigned short m_listenPort; - bool m_localNetworking = false; + NetworkPreferences m_netPrefs; + + static const int NetworkStopped = -1; + int m_listenPort = NetworkStopped; ba::io_service m_ioService; bi::tcp::acceptor m_acceptor; diff --git a/libp2p/HostCapability.h b/libp2p/HostCapability.h index 4b6d38f64..cf2399b83 100644 --- a/libp2p/HostCapability.h +++ b/libp2p/HostCapability.h @@ -48,7 +48,6 @@ public: protected: virtual std::string name() const = 0; virtual Capability* newPeerCapability(Session* _s) = 0; - virtual bool isInitialised() const { return true; } void seal(bytes& _b); diff --git a/libp2p/Session.cpp b/libp2p/Session.cpp index 5ed987cff..ea75c687f 100644 --- a/libp2p/Session.cpp +++ b/libp2p/Session.cpp @@ -159,7 +159,7 @@ bool Session::interpret(RLP const& _r) bi::address_v4 peerAddress(_r[i][0].toHash>().asArray()); auto ep = bi::tcp::endpoint(peerAddress, _r[i][1].toInt()); h512 id = _r[i][2].toHash(); - if (isPrivateAddress(peerAddress) && !m_server->m_localNetworking) + if (isPrivateAddress(peerAddress) && !m_server->m_netPrefs.localNetworking) goto CONTINUE; clogS(NetAllDetail) << "Checking: " << ep << "(" << id.abridged() << ")"; diff --git a/libwebthree/WebThree.cpp b/libwebthree/WebThree.cpp index 93f81dd0a..6718ec96d 100644 --- a/libwebthree/WebThree.cpp +++ b/libwebthree/WebThree.cpp @@ -35,20 +35,30 @@ using namespace dev::p2p; using namespace dev::eth; using namespace dev::shh; -WebThreeDirect::WebThreeDirect(std::string const& _clientVersion, std::string const& _dbPath, bool _forceClean, std::set const& _interfaces, unsigned short _listenPort, std::string const& _publicIP, bool _upnp, dev::u256 _networkId, bool _localNetworking): +WebThreeDirect::WebThreeDirect(std::string const& _clientVersion, std::string const& _dbPath, bool _forceClean, std::set const& _interfaces, NetworkPreferences const& _n): m_clientVersion(_clientVersion), - m_net(m_clientVersion, _listenPort, _publicIP, _upnp, _localNetworking) + m_net(_clientVersion, _n) { if (_dbPath.size()) Defaults::setDBPath(_dbPath); if (_interfaces.count("eth")) - m_ethereum.reset(new eth::Client(&m_net, _dbPath, _forceClean, _networkId)); + m_ethereum.reset(new eth::Client(&m_net, _dbPath, _forceClean)); // if (_interfaces.count("shh")) // m_whisper = new eth::Whisper(m_net.get()); +} + +WebThreeDirect::~WebThreeDirect() +{ + stopNetwork(); +} + +void WebThreeDirect::startNetwork() +{ + static const char* c_threadName = "p2p"; - static const char* c_threadName = "net"; + m_net.start(); UpgradableGuard l(x_work); { @@ -60,15 +70,20 @@ WebThreeDirect::WebThreeDirect(std::string const& _clientVersion, std::string co setThreadName(c_threadName); m_workState.store(Active, std::memory_order_release); while (m_workState.load(std::memory_order_acquire) != Deleting) - workNet(); + { + this_thread::sleep_for(chrono::milliseconds(1)); + ReadGuard l(x_work); + m_net.process(); // must be in guard for now since it uses the blockchain. + } m_workState.store(Deleted, std::memory_order_release); })); - } } -WebThreeDirect::~WebThreeDirect() +void WebThreeDirect::stopNetwork() { + m_net.stop(); + UpgradableGuard l(x_work); if (m_work) @@ -121,16 +136,3 @@ void WebThreeDirect::connect(std::string const& _seedHost, unsigned short _port) ReadGuard l(x_work); m_net.connect(_seedHost, _port); } - -void WebThreeDirect::workNet() -{ - // 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. - { - ReadGuard l(x_work); - m_net.process(); // must be in guard for now since it uses the blockchain. - } - this_thread::sleep_for(chrono::milliseconds(1)); -} - diff --git a/libwebthree/WebThree.h b/libwebthree/WebThree.h index 05f0ca104..5cda915e8 100644 --- a/libwebthree/WebThree.h +++ b/libwebthree/WebThree.h @@ -66,7 +66,7 @@ class WebThreeDirect public: /// Constructor for private instance. If there is already another process on the machine using @a _dbPath, then this will throw an exception. /// ethereum() may be safely static_cast()ed to a eth::Client*. - WebThreeDirect(std::string const& _clientVersion, std::string const& _dbPath, bool _forceClean = false, std::set const& _interfaces = {"eth", "shh"}, unsigned short _listenPort = 30303, std::string const& _publicIP = std::string(), bool _upnp = true, dev::u256 _networkId = 0, bool _localNetworking = false); + WebThreeDirect(std::string const& _clientVersion, std::string const& _dbPath, bool _forceClean = false, std::set const& _interfaces = {"eth", "shh"}, p2p::NetworkPreferences const& _n = p2p::NetworkPreferences()); /// Destructor. ~WebThreeDirect(); @@ -104,16 +104,15 @@ public: /// Sets the ideal number of peers. void setIdealPeerCount(size_t _n); + void setNetworkPreferences(p2p::NetworkPreferences const& _n) { stopNetwork(); m_netPrefs = _n; startNetwork(); } + /// Start the network subsystem. - void startNetwork() { setIdealPeerCount(5); } + void startNetwork(); /// Stop the network subsystem. - void stopNetwork() { setIdealPeerCount(0); } + void stopNetwork(); private: - /// Do some work on the network. - void workNet(); - std::string m_clientVersion; ///< Our end-application client's name/version. std::unique_ptr m_ethereum; ///< Main interface for Ethereum ("eth") protocol. @@ -123,6 +122,7 @@ private: std::unique_ptr m_work; ///< The network thread. mutable boost::shared_mutex x_work; ///< Lock for the network existance. std::atomic m_workState; + p2p::NetworkPreferences m_netPrefs; }; diff --git a/test/peer.cpp b/test/peer.cpp index 821aab514..a99ce7201 100644 --- a/test/peer.cpp +++ b/test/peer.cpp @@ -46,7 +46,7 @@ int peerTest(int argc, char** argv) remoteHost = argv[i]; } - Host ph("Test", listenPort); + Host ph("Test", NetworkPreferences(listenPort)); if (!remoteHost.empty()) ph.connect(remoteHost, remotePort); From 51aa4c95c4c1cd4d619d8e1dfd1319d961fd61f5 Mon Sep 17 00:00:00 2001 From: Giacomo Tazzari Date: Fri, 12 Sep 2014 23:11:22 +0200 Subject: [PATCH 168/223] Making the Miner class actually react to new pending transactions when not force-mining. Added a new miner state (Waiting) to accomplish that. --- libethereum/Miner.cpp | 46 +++++++++++++++++++++++-------------------- libethereum/Miner.h | 5 ++--- 2 files changed, 27 insertions(+), 24 deletions(-) diff --git a/libethereum/Miner.cpp b/libethereum/Miner.cpp index 95789889f..c602cedc1 100644 --- a/libethereum/Miner.cpp +++ b/libethereum/Miner.cpp @@ -65,14 +65,15 @@ void Miner::stop() void Miner::work() { // Do some mining. - if ((m_pendingCount || m_host->force()) && m_miningStatus != Mined) + if (m_miningStatus != Waiting && m_miningStatus != Mined) { if (m_miningStatus == Preparing) { - m_miningStatus = Mining; - m_host->setupState(m_mineState); - m_pendingCount = m_mineState.pending().size(); + if (m_host->force() || m_mineState.pending().size()) + m_miningStatus = Mining; + else + m_miningStatus = Waiting; { Guard l(x_mineInfo); @@ -82,26 +83,29 @@ void Miner::work() } } + if (m_miningStatus == Mining) + { // Mine for a while. - MineInfo mineInfo = m_mineState.mine(100, m_host->turbo()); + MineInfo mineInfo = m_mineState.mine(100, m_host->turbo()); - { - Guard l(x_mineInfo); - m_mineProgress.best = min(m_mineProgress.best, mineInfo.best); - m_mineProgress.current = mineInfo.best; - m_mineProgress.requirement = mineInfo.requirement; - m_mineProgress.ms += 100; - m_mineProgress.hashes += mineInfo.hashes; - m_mineHistory.push_back(mineInfo); - } - if (mineInfo.completed) - { - m_mineState.completeMine(); - m_host->onComplete(); - m_miningStatus = Mined; + { + Guard l(x_mineInfo); + m_mineProgress.best = min(m_mineProgress.best, mineInfo.best); + m_mineProgress.current = mineInfo.best; + m_mineProgress.requirement = mineInfo.requirement; + m_mineProgress.ms += 100; + m_mineProgress.hashes += mineInfo.hashes; + m_mineHistory.push_back(mineInfo); + } + if (mineInfo.completed) + { + m_mineState.completeMine(); + m_host->onComplete(); + m_miningStatus = Mined; + } + else + m_host->onProgressed(); } - else - m_host->onProgressed(); } else { diff --git a/libethereum/Miner.h b/libethereum/Miner.h index d8ae20cc1..8912b0e2a 100644 --- a/libethereum/Miner.h +++ b/libethereum/Miner.h @@ -130,10 +130,9 @@ private: std::unique_ptr m_work; ///< The work thread. bool m_stop = false; ///< Stop working? - enum MiningStatus { Preparing, Mining, Mined, Stopping, Stopped }; - MiningStatus m_miningStatus = Preparing;///< TODO: consider mutex/atomic variable. + enum MiningStatus { Waiting, Preparing, Mining, Mined, Stopping, Stopped }; + MiningStatus m_miningStatus = Waiting; ///< TODO: consider mutex/atomic variable. State m_mineState; ///< The state on which we are mining, generally equivalent to m_postMine. - mutable unsigned m_pendingCount = 0; ///< How many pending transactions are there in m_mineState? mutable std::mutex x_mineInfo; ///< Lock for the mining progress & history. MineProgress m_mineProgress; ///< What's our progress? From 7678dec012a4b3d7a1781bb839264547db89b91f Mon Sep 17 00:00:00 2001 From: Giacomo Tazzari Date: Fri, 12 Sep 2014 23:13:03 +0200 Subject: [PATCH 169/223] When setting force-mining on/off, notify the miners. --- libethereum/Client.cpp | 11 +++++++++++ libethereum/Client.h | 2 +- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index 92ff5d1cd..3bc159f17 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -312,6 +312,17 @@ void Client::restorePeers(bytesConstRef _saved) return m_net->restorePeers(_saved); } +void Client::setForceMining(bool _enable) +{ + m_forceMining = _enable; + if (!m_extHost.lock()) + { + ReadGuard l(x_miners); + for (auto& m: m_miners) + m.noteStateChange(); + } +} + void Client::connect(std::string const& _seedHost, unsigned short _port) { ReadGuard l(x_net); diff --git a/libethereum/Client.h b/libethereum/Client.h index 8fc55aafb..8bdb48051 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -227,7 +227,7 @@ public: /// Should we force mining to happen, even without transactions? bool forceMining() const { return m_forceMining; } /// Enable/disable forcing of mining to happen, even without transactions. - void setForceMining(bool _enable) { m_forceMining = _enable; } + void setForceMining(bool _enable); /// Are we mining as fast as we can? bool turboMining() const { return m_turboMining; } /// Enable/disable fast mining. From 65401e5b37b0d02f80360ca00050b7a66ebb4ce5 Mon Sep 17 00:00:00 2001 From: Tim Hughes Date: Sun, 14 Sep 2014 23:26:57 +0100 Subject: [PATCH 170/223] Fixed all the VS2013 build issues. - Updated projects after all the re-potting. - For simplicity, there's just one VS project for all the libethereum projects, however VS doesn't like duplicate source file names within the same project. To get around this I've created some "single compilation units" for some folders, these probably also compile faster than multiple files. - Some 32bit only compile errors in RLP (cryptic template ambiguity) were resolved by explicitly invoking operator T() rather than doing a cast to T. - Moved multiple implementations of toString(h256s const& _bs) into FixedHash.h - Release shutdown deadlock problem is outstanding. --- libdevcore/FixedHash.h | 10 + libdevcore/RLP.h | 57 ++++- libdevcore/_libdevcore.cpp | 8 + libethcore/All.h | 10 +- libethcore/_libethcore.cpp | 4 + libethereum/CommonNet.cpp | 3 + libethereum/EthereumPeer.cpp | 10 - libethereum/Interface.cpp | 3 + libethereum/PastMessage.cpp | 3 + libevm/_libevm.cpp | 4 + libp2p/Host.h | 2 +- libp2p/Session.h | 1 + libp2p/_libp2p.cpp | 8 + libwhisper/Common.h | 2 + libwhisper/WhisperPeer.h | 2 +- libwhisper/_libwhisper.cpp | 3 + windows/LibEthereum.vcxproj | 355 +++++++++++++++++++++++---- windows/LibEthereum.vcxproj.filters | 363 +++++++++++++++++----------- 18 files changed, 643 insertions(+), 205 deletions(-) create mode 100644 libdevcore/_libdevcore.cpp create mode 100644 libethcore/_libethcore.cpp create mode 100644 libevm/_libevm.cpp create mode 100644 libp2p/_libp2p.cpp create mode 100644 libwhisper/_libwhisper.cpp diff --git a/libdevcore/FixedHash.h b/libdevcore/FixedHash.h index f979f0b68..36b10d0bf 100644 --- a/libdevcore/FixedHash.h +++ b/libdevcore/FixedHash.h @@ -217,6 +217,16 @@ inline h160 left160(h256 const& _t) return ret; } +inline std::string toString(h256s const& _bs) +{ + std::ostringstream out; + out << "[ "; + for (auto i: _bs) + out << i.abridged() << ", "; + out << "]"; + return out.str(); +} + } namespace std diff --git a/libdevcore/RLP.h b/libdevcore/RLP.h index d3cb5eed1..ed311a082 100644 --- a/libdevcore/RLP.h +++ b/libdevcore/RLP.h @@ -178,10 +178,59 @@ public: /// Converts to string. @throws BadCast if not a string. std::string toStringStrict() const { if (!isData()) throw BadCast(); return payload().cropped(0, length()).toString(); } - template std::vector toVector() const { std::vector ret; if (isList()) { ret.reserve(itemCount()); for (auto const& i: *this) ret.push_back((T)i); } return ret; } - template std::set toSet() const { std::set ret; if (isList()) { for (auto const& i: *this) ret.insert((T)i); } return ret; } - template std::pair toPair() const { std::pair ret; if (isList()) { ret.first = (T)((*this)[0]); ret.second = (U)((*this)[1]); } return ret; } - template std::array toArray() const { if (itemCount() != N || !isList()) throw BadCast(); std::array ret; for (unsigned i = 0; i < N; ++i) ret[i] = (T)operator[](i); return ret; } + template + std::vector toVector() const + { + std::vector ret; + if (isList()) + { + ret.reserve(itemCount()); + for (auto const& i: *this) + { + ret.push_back((T)i); + } + } + return ret; + } + + template + std::set toSet() const + { + std::set ret; + if (isList()) + { + for (auto const& i: *this) + { + ret.insert((T)i); + } + } + return ret; + } + + template + std::pair toPair() const + { + std::pair ret; + if (isList()) + { + ret.first = (*this)[0].operator T(); + ret.second = (*this)[1].operator U(); + } + return ret; + } + + template + std::array toArray() const + { + if (itemCount() != N || !isList()) + throw BadCast(); + std::array ret; + for (unsigned i = 0; i < N; ++i) + { + ret[i] = (T)operator[](i); + } + return ret; + } /// Int conversion flags enum diff --git a/libdevcore/_libdevcore.cpp b/libdevcore/_libdevcore.cpp new file mode 100644 index 000000000..fa0882d81 --- /dev/null +++ b/libdevcore/_libdevcore.cpp @@ -0,0 +1,8 @@ +#include "All.h" +#include "Common.cpp" +#include "CommonData.cpp" +#include "CommonIO.cpp" +#include "FixedHash.cpp" +#include "Guards.cpp" +#include "Log.cpp" +#include "RLP.cpp" diff --git a/libethcore/All.h b/libethcore/All.h index 9cd9b72ee..4a6747ff0 100644 --- a/libethcore/All.h +++ b/libethcore/All.h @@ -3,10 +3,6 @@ #include "BlockInfo.h" #include "CommonEth.h" #include "Dagger.h" -#include "FileSystem.h" -#include "MemoryDB.h" -#include "OverlayDB.h" -#include "SHA3.h" -#include "TrieCommon.h" -#include "TrieDB.h" -#include "UPnP.h" +#include "CryptoHeaders.h" +#include "Exceptions.h" + diff --git a/libethcore/_libethcore.cpp b/libethcore/_libethcore.cpp new file mode 100644 index 000000000..5f2208665 --- /dev/null +++ b/libethcore/_libethcore.cpp @@ -0,0 +1,4 @@ +#include "All.h" +#include "BlockInfo.cpp" +#include "CommonEth.cpp" +#include "Dagger.cpp" diff --git a/libethereum/CommonNet.cpp b/libethereum/CommonNet.cpp index acf8e220f..eb92f0b18 100644 --- a/libethereum/CommonNet.cpp +++ b/libethereum/CommonNet.cpp @@ -23,3 +23,6 @@ using namespace std; using namespace dev; using namespace dev::eth; + +#pragma GCC diagnostic ignored "-Wunused-variable" +namespace { char dummy; }; diff --git a/libethereum/EthereumPeer.cpp b/libethereum/EthereumPeer.cpp index 1211ac03d..9e72f8429 100644 --- a/libethereum/EthereumPeer.cpp +++ b/libethereum/EthereumPeer.cpp @@ -91,16 +91,6 @@ void EthereumPeer::startInitialSync() } } -inline string toString(h256s const& _bs) -{ - ostringstream out; - out << "[ "; - for (auto i: _bs) - out << i.abridged() << ", "; - out << "]"; - return out.str(); -} - void EthereumPeer::giveUpOnFetch() { clogS(NetNote) << "GIVE UP FETCH; can't get" << toString(m_askedBlocks); diff --git a/libethereum/Interface.cpp b/libethereum/Interface.cpp index a9801cc33..7d731bb1f 100644 --- a/libethereum/Interface.cpp +++ b/libethereum/Interface.cpp @@ -20,3 +20,6 @@ */ #include "Interface.h" + +#pragma GCC diagnostic ignored "-Wunused-variable" +namespace { char dummy; }; diff --git a/libethereum/PastMessage.cpp b/libethereum/PastMessage.cpp index 377b7e4d2..d81ae672c 100644 --- a/libethereum/PastMessage.cpp +++ b/libethereum/PastMessage.cpp @@ -23,3 +23,6 @@ using namespace std; using namespace dev; using namespace dev::eth; + +#pragma GCC diagnostic ignored "-Wunused-variable" +namespace { char dummy; }; diff --git a/libevm/_libevm.cpp b/libevm/_libevm.cpp new file mode 100644 index 000000000..ca1926ae0 --- /dev/null +++ b/libevm/_libevm.cpp @@ -0,0 +1,4 @@ +#include "All.h" +#include "ExtVMFace.cpp" +#include "FeeStructure.cpp" +#include "VM.cpp" diff --git a/libp2p/Host.h b/libp2p/Host.h index d6673a072..54e8f967e 100644 --- a/libp2p/Host.h +++ b/libp2p/Host.h @@ -70,7 +70,7 @@ public: /// Register a peer-capability; all new peer connections will have this capability. template std::shared_ptr registerCapability(T* _t) { _t->m_host = this; auto ret = std::shared_ptr(_t); m_capabilities[T::staticName()] = ret; return ret; } - bool haveCapability(std::string const& _name) const { return m_capabilities.count(_name); } + bool haveCapability(std::string const& _name) const { return m_capabilities.count(_name) != 0; } std::vector caps() const { std::vector ret; for (auto const& i: m_capabilities) ret.push_back(i.first); return ret; } template std::shared_ptr cap() const { try { return std::static_pointer_cast(m_capabilities.at(T::staticName())); } catch (...) { return nullptr; } } diff --git a/libp2p/Session.h b/libp2p/Session.h index 289ec1063..bc43934b3 100644 --- a/libp2p/Session.h +++ b/libp2p/Session.h @@ -23,6 +23,7 @@ #include #include +#include #include #include #include diff --git a/libp2p/_libp2p.cpp b/libp2p/_libp2p.cpp new file mode 100644 index 000000000..ecf61159a --- /dev/null +++ b/libp2p/_libp2p.cpp @@ -0,0 +1,8 @@ +#include "All.h" +#include "Capability.cpp" +#include "Common.cpp" +#include "Host.cpp" +#include "HostCapability.cpp" +#include "Session.cpp" +#include "UPnP.cpp" + diff --git a/libwhisper/Common.h b/libwhisper/Common.h index 2324be024..ba4285f2b 100644 --- a/libwhisper/Common.h +++ b/libwhisper/Common.h @@ -33,6 +33,7 @@ namespace dev namespace shh { +/* this makes these symbols ambiguous on VS2013 using h256 = dev::h256; using h512 = dev::h512; using h256s = dev::h256s; @@ -42,6 +43,7 @@ using RLP = dev::RLP; using bytesRef = dev::bytesRef; using bytesConstRef = dev::bytesConstRef; using h256Set = dev::h256Set; +*/ class WhisperHost; class WhisperPeer; diff --git a/libwhisper/WhisperPeer.h b/libwhisper/WhisperPeer.h index d428670c3..ea79572c8 100644 --- a/libwhisper/WhisperPeer.h +++ b/libwhisper/WhisperPeer.h @@ -95,7 +95,7 @@ class MessageFilter public: MessageFilter() {} MessageFilter(std::vector > const& _m): m_topicMasks(_m) {} - MessageFilter(RLP const& _r): m_topicMasks((std::vector >)_r) {} + MessageFilter(RLP const& _r): m_topicMasks(_r.operator std::vector>()) {} void fillStream(RLPStream& _s) const { _s << m_topicMasks; } h256 sha3() const { RLPStream s; fillStream(s); return dev::eth::sha3(s.out()); } diff --git a/libwhisper/_libwhisper.cpp b/libwhisper/_libwhisper.cpp new file mode 100644 index 000000000..0d6f96cf8 --- /dev/null +++ b/libwhisper/_libwhisper.cpp @@ -0,0 +1,3 @@ +#include "All.h" +#include "Common.cpp" +#include "WhisperPeer.cpp" diff --git a/windows/LibEthereum.vcxproj b/windows/LibEthereum.vcxproj index c806de064..196325353 100644 --- a/windows/LibEthereum.vcxproj +++ b/windows/LibEthereum.vcxproj @@ -19,22 +19,69 @@ - - - - - - - - - - - - - - - - + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + + + + + + + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + @@ -44,9 +91,10 @@ - + + @@ -56,14 +104,68 @@ - - - + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + + true + true + true + true + + stdafx.h Create @@ -73,30 +175,114 @@ - - - - - - - - - - - - - - - - - - - - - - + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + + + + + + + + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + @@ -104,9 +290,10 @@ - + + @@ -116,10 +303,30 @@ - - - - + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + @@ -127,6 +334,60 @@ + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + + + true + true + true + true + diff --git a/windows/LibEthereum.vcxproj.filters b/windows/LibEthereum.vcxproj.filters index 3280140fc..5a650b0c5 100644 --- a/windows/LibEthereum.vcxproj.filters +++ b/windows/LibEthereum.vcxproj.filters @@ -22,15 +22,6 @@ libethereum - - libethereum - - - libethereum - - - libethereum - libethereum @@ -40,15 +31,6 @@ libethereum - - libethcore - - - libethcore - - - libethcore - libevm @@ -58,48 +40,9 @@ libevm - - libethential - - - libethential - - - libethential - - - libethential - - - libethential - - - libethential - libevmface - - libethcore - - - libethcore - - - libethcore - - - libethcore - - - libethcore - - - libethcore - - - libethcore - liblll @@ -136,6 +79,102 @@ libethereum + + libdevcrypto + + + libdevcrypto + + + libdevcrypto + + + libdevcrypto + + + libdevcrypto + + + libdevcrypto + + + libdevcrypto + + + libethcore + + + libethcore + + + libethcore + + + libdevcore + + + libdevcore + + + libdevcore + + + libdevcore + + + libdevcore + + + libdevcore + + + libdevcore + + + libethcore + + + libevm + + + libethereum + + + libethereum + + + libethereum + + + libethereum + + + libethereum + + + libp2p + + + libp2p + + + libp2p + + + libp2p + + + libp2p + + + libp2p + + + libwhisper + + + libwhisper + @@ -159,15 +198,6 @@ libethereum - - libethereum - - - libethereum - - - libethereum - libethereum @@ -177,18 +207,6 @@ libethereum - - libethcore - - - libethcore - - - libethcore - - - libethcore - libevm @@ -198,66 +216,12 @@ libevm - - libethential - - - libethential - - - libethential - - - libethential - - - libethential - - - libethential - - - libethential - - - libethential - - - libethential - libevm libevmface - - libethcore - - - libethcore - - - libethcore - - - libethcore - - - libethcore - - - libethcore - - - libethcore - - - libethcore - - - libethcore - liblll @@ -300,6 +264,126 @@ libethereum + + libdevcrypto + + + libdevcrypto + + + libdevcrypto + + + libdevcrypto + + + libdevcrypto + + + libdevcrypto + + + libdevcrypto + + + libdevcrypto + + + libdevcrypto + + + libethcore + + + libethcore + + + libethcore + + + libethcore + + + libethcore + + + libethcore + + + libdevcore + + + libdevcore + + + libdevcore + + + libdevcore + + + libdevcore + + + libdevcore + + + libdevcore + + + libdevcore + + + libdevcore + + + libdevcore + + + libethereum + + + libethereum + + + libethereum + + + libethereum + + + libethereum + + + libethereum + + + libp2p + + + libp2p + + + libp2p + + + libp2p + + + libp2p + + + libp2p + + + libp2p + + + libwhisper + + + libwhisper + @@ -317,11 +401,20 @@ {ed9ad1b3-700c-47f9-8548-a90b5ef179ac} - - {35c32f6c-3f19-4603-8084-1b88ec9ae498} - {e6332606-e0ca-48aa-8a6b-303971ba7a93} + + {fae2102b-d574-40fc-9f90-1b9ed0d117ac} + + + {35c32f6c-3f19-4603-8084-1b88ec9ae498} + + + {fc2cb618-ab0c-45b6-8eb9-6d88e0336fa9} + + + {36748e80-c977-4fee-84e6-699c039dff87} + \ No newline at end of file From 00d461a74ad0b96d72afb674d12c25471e98e14c Mon Sep 17 00:00:00 2001 From: Tim Hughes Date: Mon, 15 Sep 2014 19:19:22 +0100 Subject: [PATCH 171/223] Disable SCUs on non MSVC builds. --- libdevcore/_libdevcore.cpp | 2 ++ libethcore/_libethcore.cpp | 2 ++ libevm/_libevm.cpp | 2 ++ libp2p/_libp2p.cpp | 3 ++- libwhisper/_libwhisper.cpp | 2 ++ 5 files changed, 10 insertions(+), 1 deletion(-) diff --git a/libdevcore/_libdevcore.cpp b/libdevcore/_libdevcore.cpp index fa0882d81..4160a7602 100644 --- a/libdevcore/_libdevcore.cpp +++ b/libdevcore/_libdevcore.cpp @@ -1,3 +1,4 @@ +#ifdef _MSC_VER #include "All.h" #include "Common.cpp" #include "CommonData.cpp" @@ -6,3 +7,4 @@ #include "Guards.cpp" #include "Log.cpp" #include "RLP.cpp" +#endif diff --git a/libethcore/_libethcore.cpp b/libethcore/_libethcore.cpp index 5f2208665..8a3831584 100644 --- a/libethcore/_libethcore.cpp +++ b/libethcore/_libethcore.cpp @@ -1,4 +1,6 @@ +#ifdef _MSC_VER #include "All.h" #include "BlockInfo.cpp" #include "CommonEth.cpp" #include "Dagger.cpp" +#endif diff --git a/libevm/_libevm.cpp b/libevm/_libevm.cpp index ca1926ae0..27186cbf2 100644 --- a/libevm/_libevm.cpp +++ b/libevm/_libevm.cpp @@ -1,4 +1,6 @@ +#ifdef _MSC_VER #include "All.h" #include "ExtVMFace.cpp" #include "FeeStructure.cpp" #include "VM.cpp" +#endif diff --git a/libp2p/_libp2p.cpp b/libp2p/_libp2p.cpp index ecf61159a..440ba362b 100644 --- a/libp2p/_libp2p.cpp +++ b/libp2p/_libp2p.cpp @@ -1,3 +1,4 @@ +#ifdef _MSC_VER #include "All.h" #include "Capability.cpp" #include "Common.cpp" @@ -5,4 +6,4 @@ #include "HostCapability.cpp" #include "Session.cpp" #include "UPnP.cpp" - +#endif diff --git a/libwhisper/_libwhisper.cpp b/libwhisper/_libwhisper.cpp index 0d6f96cf8..58bd97213 100644 --- a/libwhisper/_libwhisper.cpp +++ b/libwhisper/_libwhisper.cpp @@ -1,3 +1,5 @@ +#ifdef _MSC_VER #include "All.h" #include "Common.cpp" #include "WhisperPeer.cpp" +#endif From dde79b2b88815828e3198ba47cc655a611e964b4 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 15 Sep 2014 21:13:29 +0200 Subject: [PATCH 172/223] Move eth over to WebThreeDirect. --- eth/CMakeLists.txt | 4 ++-- eth/main.cpp | 14 +++++++++----- libethereum/EthereumHost.cpp | 1 + libethereum/EthereumPeer.cpp | 2 ++ libethereum/EthereumPeer.h | 2 ++ libp2p/Host.cpp | 6 ++++-- libwebthree/WebThree.h | 4 +++- 7 files changed, 23 insertions(+), 10 deletions(-) diff --git a/eth/CMakeLists.txt b/eth/CMakeLists.txt index 93946aa8f..eee1d258b 100644 --- a/eth/CMakeLists.txt +++ b/eth/CMakeLists.txt @@ -4,13 +4,13 @@ aux_source_directory(. SRC_LIST) include_directories(..) link_directories(../libethcore) -link_directories(../libethereum) +link_directories(../libwebthree) set(EXECUTABLE eth) add_executable(${EXECUTABLE} ${SRC_LIST}) -target_link_libraries(${EXECUTABLE} ethereum) +target_link_libraries(${EXECUTABLE} webthree) target_link_libraries(${EXECUTABLE} secp256k1) target_link_libraries(${EXECUTABLE} gmp) if(MINIUPNPC_LS) diff --git a/eth/main.cpp b/eth/main.cpp index bd71f6677..ee09d666d 100644 --- a/eth/main.cpp +++ b/eth/main.cpp @@ -33,6 +33,7 @@ #include #include #include +#include #if ETH_READLINE #include #include @@ -43,6 +44,7 @@ #include "BuildInfo.h" using namespace std; using namespace dev; +using namespace dev::p2p; using namespace dev::eth; using namespace boost::algorithm; using dev::eth::Instruction; @@ -296,16 +298,18 @@ int main(int argc, char** argv) if (!clientName.empty()) clientName += "/"; - Client c("Ethereum(++)/" + clientName + "v" + dev::Version + "/" DEV_QUOTED(ETH_BUILD_TYPE) "/" DEV_QUOTED(ETH_BUILD_PLATFORM), coinbase, dbPath); - - c.setForceMining(true); - cout << credits(); + dev::WebThreeDirect web3("Ethereum(++)/" + clientName + "v" + dev::Version + "/" DEV_QUOTED(ETH_BUILD_TYPE) "/" DEV_QUOTED(ETH_BUILD_PLATFORM), dbPath, false, mode == NodeMode::Full ? set{"eth", "shh"} : set{}, NetworkPreferences(listenPort, publicIP, upnp, false)); + web3.setIdealPeerCount(peers); + eth::Client& c = *web3.ethereum(); + c.setForceMining(forceMining); + c.setAddress(coinbase); cout << "Address: " << endl << toHex(us.address().asArray()) << endl; - c.startNetwork(listenPort, remoteHost, remotePort, mode, peers, publicIP, upnp); + web3.startNetwork(); + web3.connect(remoteHost, remotePort); #if ETH_JSONRPC auto_ptr jsonrpcServer; diff --git a/libethereum/EthereumHost.cpp b/libethereum/EthereumHost.cpp index 80b7cfe88..a80c0df6f 100644 --- a/libethereum/EthereumHost.cpp +++ b/libethereum/EthereumHost.cpp @@ -205,6 +205,7 @@ void EthereumHost::maintainBlocks(BlockQueue& _bq, h256 _currentHash) for (auto j: peers()) { auto p = j->cap(); + Guard l(p->x_knownBlocks); if (!p->m_knownBlocks.count(_currentHash)) p->send(&b); p->m_knownBlocks.clear(); diff --git a/libethereum/EthereumPeer.cpp b/libethereum/EthereumPeer.cpp index 1211ac03d..5918a7c49 100644 --- a/libethereum/EthereumPeer.cpp +++ b/libethereum/EthereumPeer.cpp @@ -235,6 +235,7 @@ bool EthereumPeer::interpret(RLP const& _r) if (host()->noteBlock(h, _r[i].data())) used++; m_askedBlocks.erase(h); + Guard l(x_knownBlocks); m_knownBlocks.insert(h); } addRating(used); @@ -246,6 +247,7 @@ bool EthereumPeer::interpret(RLP const& _r) { auto h = BlockInfo::headerHash(_r[i].data()); BlockInfo bi(_r[i].data()); + Guard l(x_knownBlocks); if (!host()->m_chain->details(bi.parentHash) && !m_knownBlocks.count(bi.parentHash)) { unknownParents++; diff --git a/libethereum/EthereumPeer.h b/libethereum/EthereumPeer.h index 2e166b1d0..f09710d97 100644 --- a/libethereum/EthereumPeer.h +++ b/libethereum/EthereumPeer.h @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include "CommonNet.h" @@ -80,6 +81,7 @@ private: bool m_requireTransactions; + Mutex x_knownBlocks; std::set m_knownBlocks; std::set m_knownTransactions; }; diff --git a/libp2p/Host.cpp b/libp2p/Host.cpp index 32bebc599..ac3d6bc10 100644 --- a/libp2p/Host.cpp +++ b/libp2p/Host.cpp @@ -70,7 +70,6 @@ Host::Host(std::string const& _clientVersion, NetworkPreferences const& _n, bool Host::~Host() { - disconnectPeers(); stop(); } @@ -86,6 +85,8 @@ void Host::start() m_acceptor.set_option(ba::socket_base::reuse_address(true)); m_acceptor.bind(endpoint); m_acceptor.listen(); + m_listenPort = i ? m_acceptor.local_endpoint().port() : m_netPrefs.listenPort; + break; } catch (...) { @@ -98,7 +99,6 @@ void Host::start() continue; } } - m_listenPort = m_acceptor.local_endpoint().port(); determinePublic(m_netPrefs.publicIP, m_netPrefs.upnp); ensureAccepting(); @@ -117,6 +117,7 @@ void Host::stop() } if (m_socket.is_open()) m_socket.close(); + disconnectPeers(); } unsigned Host::protocolVersion() const @@ -339,6 +340,7 @@ void Host::connect(std::string const& _addr, unsigned short _port) noexcept { try { + // TODO: actual DNS lookup. connect(bi::tcp::endpoint(bi::address::from_string(_addr), _port)); } catch (exception const& e) diff --git a/libwebthree/WebThree.h b/libwebthree/WebThree.h index 5cda915e8..4799e7765 100644 --- a/libwebthree/WebThree.h +++ b/libwebthree/WebThree.h @@ -104,7 +104,9 @@ public: /// Sets the ideal number of peers. void setIdealPeerCount(size_t _n); - void setNetworkPreferences(p2p::NetworkPreferences const& _n) { stopNetwork(); m_netPrefs = _n; startNetwork(); } + bool haveNetwork() const { return !!m_work; } + + void setNetworkPreferences(p2p::NetworkPreferences const& _n) { auto had = haveNetwork(); if (had) stopNetwork(); m_netPrefs = _n; if (had) startNetwork(); } /// Start the network subsystem. void startNetwork(); From c05b71cf0de9fcd7d9e107159e9c3c8b40b4c9e0 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 15 Sep 2014 21:26:16 +0200 Subject: [PATCH 173/223] Move Third over to WebThree. --- third/CMakeLists.txt | 2 +- third/MainWin.cpp | 140 ++++++++++++++++++------------------------- third/MainWin.h | 8 ++- 3 files changed, 63 insertions(+), 87 deletions(-) diff --git a/third/CMakeLists.txt b/third/CMakeLists.txt index a68b90813..77b2bb496 100644 --- a/third/CMakeLists.txt +++ b/third/CMakeLists.txt @@ -52,7 +52,7 @@ else () endif () qt5_use_modules(${EXECUTEABLE} Core)# Gui Widgets Network WebKit WebKitWidgets) -target_link_libraries(${EXECUTEABLE} qethereum ethereum evm ethcore secp256k1 gmp ${CRYPTOPP_LS} serpent lll evmface devcore) +target_link_libraries(${EXECUTEABLE} webthree qethereum ethereum evm ethcore secp256k1 gmp ${CRYPTOPP_LS} serpent lll evmface devcore) if (APPLE) # First have qt5 install plugins and frameworks diff --git a/third/MainWin.cpp b/third/MainWin.cpp index 7bbdd721b..11db6376f 100644 --- a/third/MainWin.cpp +++ b/third/MainWin.cpp @@ -31,6 +31,7 @@ #include #include #include +#include #include #include #include @@ -38,49 +39,14 @@ #include #include #include +#include #include "BuildInfo.h" #include "MainWin.h" #include "ui_Main.h" using namespace std; - -// types -using dev::bytes; -using dev::bytesConstRef; -using dev::h160; -using dev::h256; -using dev::u160; -using dev::u256; -using dev::Address; -using dev::eth::BlockInfo; -using dev::eth::Client; -using dev::eth::Instruction; -using dev::KeyPair; -using dev::eth::NodeMode; -using dev::eth::BlockChain; -using dev::p2p::PeerInfo; -using dev::RLP; -using dev::Secret; -using dev::eth::Transaction; -using dev::eth::Executive; - -// functions -using dev::toHex; -using dev::fromHex; -using dev::left160; -using dev::right160; -using dev::simpleDebugOut; -using dev::toLog2; -using dev::toString; -using dev::operator<<; -using dev::eth::units; -using dev::eth::compileLLL; -using dev::eth::disassemble; -using dev::eth::formatBalance; -using dev::eth::sha3; - -// vars -using dev::g_logPost; -using dev::g_logVerbosity; +using namespace dev; +using namespace dev::eth; +using namespace dev::p2p; static QString fromRaw(dev::h256 _n, unsigned* _inc = nullptr) { @@ -133,12 +99,12 @@ Main::Main(QWidget *parent) : connect(ui->ourAccounts->model(), SIGNAL(rowsMoved(const QModelIndex &, int, int, const QModelIndex &, int)), SLOT(ourAccountsRowsMoved())); - m_client.reset(new Client("Third")); + m_web3.reset(new WebThreeDirect("Third", getDataDir() + "/Third", false, {"eth", "shh"})); connect(ui->webView, &QWebView::loadStarted, [this]() { // NOTE: no need to delete as QETH_INSTALL_JS_NAMESPACE adopts it. - m_ethereum = new QEthereum(this, m_client.get(), owned()); + m_ethereum = new QEthereum(this, ethereum(), owned()); QWebFrame* f = ui->webView->page()->mainFrame(); f->disconnect(SIGNAL(javaScriptWindowObjectCleared())); @@ -181,6 +147,11 @@ Main::~Main() writeSettings(); } +eth::Client* Main::ethereum() const +{ + return m_web3->ethereum(); +} + void Main::onKeysChanged() { installBalancesWatch(); @@ -188,14 +159,14 @@ void Main::onKeysChanged() unsigned Main::installWatch(dev::eth::MessageFilter const& _tf, std::function const& _f) { - auto ret = m_client->installWatch(_tf); + auto ret = ethereum()->installWatch(_tf); m_handlers[ret] = _f; return ret; } unsigned Main::installWatch(dev::h256 _tf, std::function const& _f) { - auto ret = m_client->installWatch(_tf); + auto ret = ethereum()->installWatch(_tf); m_handlers[ret] = _f; return ret; } @@ -209,14 +180,14 @@ void Main::installWatches() void Main::installNameRegWatch() { - m_client->uninstallWatch(m_nameRegFilter); - m_nameRegFilter = installWatch(dev::eth::MessageFilter().altered((u160)m_client->stateAt(c_config, 0)), [=](){ onNameRegChange(); }); + ethereum()->uninstallWatch(m_nameRegFilter); + m_nameRegFilter = installWatch(dev::eth::MessageFilter().altered((u160)ethereum()->stateAt(c_config, 0)), [=](){ onNameRegChange(); }); } void Main::installCurrenciesWatch() { - m_client->uninstallWatch(m_currenciesFilter); - m_currenciesFilter = installWatch(dev::eth::MessageFilter().altered((u160)m_client->stateAt(c_config, 1)), [=](){ onCurrenciesChange(); }); + ethereum()->uninstallWatch(m_currenciesFilter); + m_currenciesFilter = installWatch(dev::eth::MessageFilter().altered((u160)ethereum()->stateAt(c_config, 1)), [=](){ onCurrenciesChange(); }); } void Main::installBalancesWatch() @@ -224,9 +195,9 @@ void Main::installBalancesWatch() dev::eth::MessageFilter tf; vector
    altCoins; - Address coinsAddr = right160(m_client->stateAt(c_config, 1)); - for (unsigned i = 0; i < m_client->stateAt(coinsAddr, 0); ++i) - altCoins.push_back(right160(m_client->stateAt(coinsAddr, i + 1))); + Address coinsAddr = right160(ethereum()->stateAt(c_config, 1)); + for (unsigned i = 0; i < ethereum()->stateAt(coinsAddr, 0); ++i) + altCoins.push_back(right160(ethereum()->stateAt(coinsAddr, i + 1))); for (auto i: m_myKeys) { tf.altered(i.address()); @@ -234,7 +205,7 @@ void Main::installBalancesWatch() tf.altered(c, (u160)i.address()); } - m_client->uninstallWatch(m_balancesFilter); + ethereum()->uninstallWatch(m_balancesFilter); m_balancesFilter = installWatch(tf, [=](){ onBalancesChange(); }); } @@ -295,8 +266,8 @@ QString Main::pretty(dev::Address _a) const { h256 n; - if (h160 nameReg = (u160)m_client->stateAt(c_config, 0)) - n = m_client->stateAt(nameReg, (u160)(_a)); + if (h160 nameReg = (u160)ethereum()->stateAt(c_config, 0)) + n = ethereum()->stateAt(nameReg, (u160)(_a)); return fromRaw(n); } @@ -322,8 +293,8 @@ Address Main::fromString(QString const& _a) const memset(n.data() + sn.size(), 0, 32 - sn.size()); if (_a.size()) { - if (h160 nameReg = (u160)m_client->stateAt(c_config, 0)) - if (h256 a = m_client->stateAt(nameReg, n)) + if (h160 nameReg = (u160)ethereum()->stateAt(c_config, 0)) + if (h256 a = ethereum()->stateAt(nameReg, n)) return right160(a); } if (_a.size() == 40) @@ -351,11 +322,11 @@ QString Main::lookup(QString const& _a) const */ h256 ret; - if (h160 dnsReg = (u160)m_client->stateAt(c_config, 4, 0)) - ret = m_client->stateAt(dnsReg, n); + if (h160 dnsReg = (u160)ethereum()->stateAt(c_config, 4, 0)) + ret = ethereum()->stateAt(dnsReg, n); /* if (!ret) - if (h160 nameReg = (u160)m_client->stateAt(c_config, 0, 0)) - ret = m_client->stateAt(nameReg, n2); + if (h160 nameReg = (u160)ethereum()->stateAt(c_config, 0, 0)) + ret = ethereum()->stateAt(nameReg, n2); */ if (ret && !((u256)ret >> 32)) return QString("%1.%2.%3.%4").arg((int)ret[28]).arg((int)ret[29]).arg((int)ret[30]).arg((int)ret[31]); @@ -385,7 +356,7 @@ void Main::writeSettings() s.setValue("address", b); s.setValue("url", ui->urlEdit->text()); - bytes d = m_client->savePeers(); + bytes d = ethereum()->savePeers(); if (d.size()) m_peers = QByteArray((char*)d.data(), (int)d.size()); s.setValue("peers", m_peers); @@ -416,7 +387,7 @@ void Main::readSettings(bool _skipGeometry) m_myKeys.append(KeyPair(k)); } } - m_client->setAddress(m_myKeys.back().address()); + ethereum()->setAddress(m_myKeys.back().address()); m_peers = s.value("peers").toByteArray(); ui->urlEdit->setText(s.value("url", "about:blank").toString()); //http://gavwood.com/gavcoin.html on_urlEdit_returnPressed(); @@ -466,8 +437,8 @@ void Main::on_urlEdit_returnPressed() void Main::refreshMining() { - dev::eth::MineProgress p = m_client->miningProgress(); - ui->mineStatus->setText(m_client->isMining() ? QString("%1s @ %2kH/s").arg(p.ms / 1000).arg(p.ms ? p.hashes / p.ms : 0) : "Not mining"); + dev::eth::MineProgress p = ethereum()->miningProgress(); + ui->mineStatus->setText(ethereum()->isMining() ? QString("%1s @ %2kH/s").arg(p.ms / 1000).arg(p.ms ? p.hashes / p.ms : 0) : "Not mining"); } void Main::refreshBalances() @@ -477,18 +448,18 @@ void Main::refreshBalances() ui->ourAccounts->clear(); u256 totalBalance = 0; map> altCoins; - Address coinsAddr = right160(m_client->stateAt(c_config, 1)); - for (unsigned i = 0; i < m_client->stateAt(coinsAddr, 0); ++i) - altCoins[right160(m_client->stateAt(coinsAddr, m_client->stateAt(coinsAddr, i + 1)))] = make_pair(fromRaw(m_client->stateAt(coinsAddr, i + 1)), 0); + Address coinsAddr = right160(ethereum()->stateAt(c_config, 1)); + for (unsigned i = 0; i < ethereum()->stateAt(coinsAddr, 0); ++i) + altCoins[right160(ethereum()->stateAt(coinsAddr, ethereum()->stateAt(coinsAddr, i + 1)))] = make_pair(fromRaw(ethereum()->stateAt(coinsAddr, i + 1)), 0); for (auto i: m_myKeys) { - u256 b = m_client->balanceAt(i.address()); - (new QListWidgetItem(QString("%2: %1 [%3]").arg(formatBalance(b).c_str()).arg(render(i.address())).arg((unsigned)m_client->countAt(i.address())), ui->ourAccounts)) + u256 b = ethereum()->balanceAt(i.address()); + (new QListWidgetItem(QString("%2: %1 [%3]").arg(formatBalance(b).c_str()).arg(render(i.address())).arg((unsigned)ethereum()->countAt(i.address())), ui->ourAccounts)) ->setData(Qt::UserRole, QByteArray((char const*)i.address().data(), Address::size)); totalBalance += b; for (auto& c: altCoins) - c.second.second += (u256)m_client->stateAt(c.first, (u160)i.address()); + c.second.second += (u256)ethereum()->stateAt(c.first, (u160)i.address()); } QString b; @@ -500,7 +471,7 @@ void Main::refreshBalances() void Main::refreshNetwork() { - auto ps = m_client->peers(); + auto ps = ethereum()->peers(); ui->peerCount->setText(QString::fromStdString(toString(ps.size())) + " peer(s)"); } @@ -514,8 +485,8 @@ void Main::refreshAll() void Main::refreshBlockCount() { cwatch << "refreshBlockCount()"; - auto d = m_client->blockChain().details(); - auto diff = BlockInfo(m_client->blockChain().block()).difficulty; + auto d = ethereum()->blockChain().details(); + auto diff = BlockInfo(ethereum()->blockChain().block()).difficulty; ui->blockCount->setText(QString("#%1 @%3 T%2 N%4 D%5").arg(d.number).arg(toLog2(d.totalDifficulty)).arg(toLog2(diff)).arg(dev::eth::c_protocolVersion).arg(dev::eth::c_databaseVersion)); } @@ -543,7 +514,7 @@ void Main::timerEvent(QTimerEvent*) m_ethereum->poll(); for (auto const& i: m_handlers) - if (m_client->checkWatch(i.first)) + if (ethereum()->checkWatch(i.first)) i.second(); } @@ -574,7 +545,7 @@ void Main::ensureNetwork() { string n = string("Third/v") + dev::Version; n += "/" DEV_QUOTED(ETH_BUILD_TYPE) "/" DEV_QUOTED(ETH_BUILD_PLATFORM); - m_client->setClientVersion(n); + web3()->setClientVersion(n); int pocnumber = QString(dev::Version).section('.', 1, 1).toInt(); string defPeer; @@ -583,13 +554,16 @@ void Main::ensureNetwork() else if (pocnumber == 6) defPeer = "54.76.56.74"; - if (!m_client->haveNetwork()) - m_client->startNetwork(30303, defPeer); + if (!web3()->haveNetwork()) + { + web3()->startNetwork(); + web3()->connect(defPeer); + } else - if (!m_client->peerCount()) - m_client->connect(defPeer); + if (!ethereum()->peerCount()) + ethereum()->connect(defPeer); if (m_peers.size()) - m_client->restorePeers(bytesConstRef((byte*)m_peers.data(), m_peers.size())); + ethereum()->restorePeers(bytesConstRef((byte*)m_peers.data(), m_peers.size())); } void Main::on_connect_triggered() @@ -600,7 +574,7 @@ void Main::on_connect_triggered() { string host = s.section(":", 0, 0).toStdString(); unsigned short port = s.section(":", 1).toInt(); - m_client->connect(host, port); + web3()->connect(host, port); } } @@ -608,11 +582,11 @@ void Main::on_mine_triggered() { if (ui->mine->isChecked()) { - m_client->setAddress(m_myKeys.last().address()); - m_client->startMining(); + ethereum()->setAddress(m_myKeys.last().address()); + ethereum()->startMining(); } else - m_client->stopMining(); + ethereum()->stopMining(); } // extra bits needed to link on VS diff --git a/third/MainWin.h b/third/MainWin.h index 76505d603..df05d6828 100644 --- a/third/MainWin.h +++ b/third/MainWin.h @@ -36,7 +36,8 @@ namespace Ui { class Main; } -namespace dev { namespace eth { +namespace dev { class WebThreeDirect; +namespace eth { class Client; class State; class MessageFilter; @@ -52,7 +53,8 @@ public: explicit Main(QWidget *parent = 0); ~Main(); - dev::eth::Client* client() { return m_client.get(); } + dev::WebThreeDirect* web3() const { return m_web3.get(); } + dev::eth::Client* ethereum() const; QList const& owned() const { return m_myKeys; } @@ -111,7 +113,7 @@ private: std::unique_ptr ui; - std::unique_ptr m_client; + std::unique_ptr m_web3; QList m_myKeys; From 5735e8c55e446740f7b359f8b2e678d612449007 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 15 Sep 2014 21:49:52 +0200 Subject: [PATCH 174/223] Remove old Client network code & API. Move over JsonRpcServer to WebThree. --- CMakeLists.txt | 4 +- alethzero/MainWin.cpp | 4 +- eth/EthStubServer.cpp | 34 ++++--- eth/EthStubServer.h | 7 +- eth/main.cpp | 19 ++-- libethereum/Client.cpp | 170 +++++------------------------------ libethereum/Client.h | 34 +------ libqethereum/QmlEthereum.cpp | 4 +- libqethereum/QmlEthereum.h | 8 +- test/TestHelper.cpp | 2 + third/MainWin.cpp | 10 +-- 11 files changed, 73 insertions(+), 223 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 46cc0753f..6c6ec18cc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -353,7 +353,7 @@ if (NOT LANGUAGES) add_subdirectory(exp) endif () if(NOT ("${TARGET_PLATFORM}" STREQUAL "w64")) - add_subdirectory(neth) + #add_subdirectory(neth) // resurect once moved over to WebThree API. endif () if(QTQML) add_definitions(-DETH_QTQML) @@ -369,7 +369,7 @@ if (NOT LANGUAGES) add_subdirectory(third) if(QTQML) #add_subdirectory(iethxi) - add_subdirectory(walleth) + #add_subdirectory(walleth) // resurect once we want to submit ourselves to QML. endif() endif() endif() diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index 734854c2a..37c33de02 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -506,7 +506,7 @@ void Main::writeSettings() s.setValue("privateChain", m_privateChain); s.setValue("verbosity", ui->verbosity->value()); - bytes d = ethereum()->savePeers(); + bytes d = m_webThree->savePeers(); if (d.size()) m_peers = QByteArray((char*)d.data(), (int)d.size()); s.setValue("peers", m_peers); @@ -1250,7 +1250,7 @@ void Main::on_contracts_currentItemChanged() void Main::on_idealPeers_valueChanged() { - ethereum()->setIdealPeerCount(ui->idealPeers->value()); + m_webThree->setIdealPeerCount(ui->idealPeers->value()); } void Main::on_ourAccounts_doubleClicked() diff --git a/eth/EthStubServer.cpp b/eth/EthStubServer.cpp index 86bb1ba58..a3f21bf42 100644 --- a/eth/EthStubServer.cpp +++ b/eth/EthStubServer.cpp @@ -25,14 +25,15 @@ #include #include #include +#include #include "CommonJS.h" using namespace std; using namespace dev; using namespace dev::eth; -EthStubServer::EthStubServer(jsonrpc::AbstractServerConnector* _conn, Client& _client): +EthStubServer::EthStubServer(jsonrpc::AbstractServerConnector* _conn, WebThreeDirect& _web3): AbstractEthStubServer(_conn), - m_client(_client) + m_web3(_web3) { } @@ -59,20 +60,25 @@ Json::Value EthStubServer::procedures() return ret; } +dev::eth::Client& EthStubServer::ethereum() const +{ + return *m_web3.ethereum(); +} + std::string EthStubServer::coinbase() { - return toJS(m_client.address()); + return toJS(ethereum().address()); } std::string EthStubServer::balanceAt(std::string const& _a) { - return toJS(m_client.balanceAt(jsToAddress(_a), 0)); + return toJS(ethereum().balanceAt(jsToAddress(_a), 0)); } Json::Value EthStubServer::check(Json::Value const& _as) { // TODO -// if (m_client.changed()) +// if (ethereum().changed()) return _as; /* else { @@ -84,7 +90,7 @@ Json::Value EthStubServer::check(Json::Value const& _as) std::string EthStubServer::create(const std::string& _bCode, const std::string& _sec, const std::string& _xEndowment, const std::string& _xGas, const std::string& _xGasPrice) { - Address ret = m_client.transact(jsToSecret(_sec), jsToU256(_xEndowment), jsToBytes(_bCode), jsToU256(_xGas), jsToU256(_xGasPrice)); + Address ret = ethereum().transact(jsToSecret(_sec), jsToU256(_xEndowment), jsToBytes(_bCode), jsToU256(_xGas), jsToU256(_xGasPrice)); return toJS(ret); } @@ -100,17 +106,17 @@ std::string EthStubServer::gasPrice() bool EthStubServer::isContractAt(const std::string& _a) { - return m_client.codeAt(jsToAddress(_a), 0).size(); + return ethereum().codeAt(jsToAddress(_a), 0).size(); } bool EthStubServer::isListening() { - return m_client.haveNetwork(); + return m_web3.haveNetwork(); } bool EthStubServer::isMining() { - return m_client.isMining(); + return ethereum().isMining(); } std::string EthStubServer::key() @@ -130,23 +136,23 @@ Json::Value EthStubServer::keys() int EthStubServer::peerCount() { - return m_client.peerCount(); + return m_web3.peerCount(); } std::string EthStubServer::storageAt(const std::string& _a, const std::string& x) { - return toJS(m_client.stateAt(jsToAddress(_a), jsToU256(x), 0)); + return toJS(ethereum().stateAt(jsToAddress(_a), jsToU256(x), 0)); } Json::Value EthStubServer::transact(const std::string& _aDest, const std::string& _bData, const std::string& _sec, const std::string& _xGas, const std::string& _xGasPrice, const std::string& _xValue) { - m_client.transact(jsToSecret(_sec), jsToU256(_xValue), jsToAddress(_aDest), jsToBytes(_bData), jsToU256(_xGas), jsToU256(_xGasPrice)); + ethereum().transact(jsToSecret(_sec), jsToU256(_xValue), jsToAddress(_aDest), jsToBytes(_bData), jsToU256(_xGas), jsToU256(_xGasPrice)); return Json::Value(); } std::string EthStubServer::txCountAt(const std::string& _a) { - return toJS(m_client.countAt(jsToAddress(_a), 0)); + return toJS(ethereum().countAt(jsToAddress(_a), 0)); } std::string EthStubServer::secretToAddress(const std::string& _a) @@ -167,7 +173,7 @@ Json::Value EthStubServer::block(const std::string& _hash) Json::Value EthStubServer::blockJson(const std::string& _hash) { Json::Value res; - auto const& bc = m_client.blockChain(); + auto const& bc = ethereum().blockChain(); auto b = _hash.length() ? bc.block(h256(_hash)) : bc.block(); diff --git a/eth/EthStubServer.h b/eth/EthStubServer.h index 28af916a2..49b595bb3 100644 --- a/eth/EthStubServer.h +++ b/eth/EthStubServer.h @@ -29,12 +29,12 @@ #include "abstractethstubserver.h" #pragma GCC diagnostic pop -namespace dev { namespace eth { class Client; } class KeyPair; } +namespace dev { class WebThreeDirect; namespace eth { class Client; } class KeyPair; } class EthStubServer: public AbstractEthStubServer { public: - EthStubServer(jsonrpc::AbstractServerConnector* _conn, dev::eth::Client& _client); + EthStubServer(jsonrpc::AbstractServerConnector* _conn, dev::WebThreeDirect& _web3); virtual Json::Value procedures(); virtual std::string balanceAt(std::string const& _a); @@ -57,7 +57,8 @@ public: virtual Json::Value block(const std::string&); void setKeys(std::vector _keys) { m_keys = _keys; } private: - dev::eth::Client& m_client; + dev::eth::Client& ethereum() const; + dev::WebThreeDirect& m_web3; std::vector m_keys; Json::Value jsontypeToValue(int); Json::Value blockJson(const std::string&); diff --git a/eth/main.cpp b/eth/main.cpp index ee09d666d..69a555904 100644 --- a/eth/main.cpp +++ b/eth/main.cpp @@ -300,7 +300,8 @@ int main(int argc, char** argv) cout << credits(); - dev::WebThreeDirect web3("Ethereum(++)/" + clientName + "v" + dev::Version + "/" DEV_QUOTED(ETH_BUILD_TYPE) "/" DEV_QUOTED(ETH_BUILD_PLATFORM), dbPath, false, mode == NodeMode::Full ? set{"eth", "shh"} : set{}, NetworkPreferences(listenPort, publicIP, upnp, false)); + NetworkPreferences netPrefs(listenPort, publicIP, upnp, false); + dev::WebThreeDirect web3("Ethereum(++)/" + clientName + "v" + dev::Version + "/" DEV_QUOTED(ETH_BUILD_TYPE) "/" DEV_QUOTED(ETH_BUILD_PLATFORM), dbPath, false, mode == NodeMode::Full ? set{"eth", "shh"} : set{}, netPrefs); web3.setIdealPeerCount(peers); eth::Client& c = *web3.ethereum(); @@ -315,7 +316,7 @@ int main(int argc, char** argv) auto_ptr jsonrpcServer; if (jsonrpc > -1) { - jsonrpcServer = auto_ptr(new EthStubServer(new jsonrpc::HttpServer(jsonrpc), c)); + jsonrpcServer = auto_ptr(new EthStubServer(new jsonrpc::HttpServer(jsonrpc), web3)); jsonrpcServer->setKeys({us}); jsonrpcServer->StartListening(); } @@ -353,20 +354,20 @@ int main(int argc, char** argv) iss >> cmd; if (cmd == "netstart") { - unsigned port; - iss >> port; - c.startNetwork((short)port); + iss >> netPrefs.listenPort; + web3.setNetworkPreferences(netPrefs); + web3.startNetwork(); } else if (cmd == "connect") { string addr; unsigned port; iss >> addr >> port; - c.connect(addr, (short)port); + web3.connect(addr, (short)port); } else if (cmd == "netstop") { - c.stopNetwork(); + web3.stopNetwork(); } else if (cmd == "minestart") { @@ -399,7 +400,7 @@ int main(int argc, char** argv) { if (jsonrpc < 0) jsonrpc = 8080; - jsonrpcServer = auto_ptr(new EthStubServer(new jsonrpc::HttpServer(jsonrpc), c)); + jsonrpcServer = auto_ptr(new EthStubServer(new jsonrpc::HttpServer(jsonrpc), web3)); jsonrpcServer->setKeys({us}); jsonrpcServer->StartListening(); } @@ -426,7 +427,7 @@ int main(int argc, char** argv) } else if (cmd == "peers") { - for (auto it: c.peers()) + for (auto it: web3.peers()) cout << it.host << ":" << it.port << ", " << it.clientVersion << ", " << std::chrono::duration_cast(it.lastPing).count() << "ms" << endl; diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index 2cf96731d..9779acfbe 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -53,21 +53,6 @@ void VersionChecker::setOk() } } -Client::Client(std::string const& _clientVersion, Address _us, std::string const& _dbPath, bool _forceClean): - m_clientVersion(_clientVersion), - m_vc(_dbPath), - m_bc(_dbPath, !m_vc.ok() || _forceClean), - m_stateDB(State::openDB(_dbPath, !m_vc.ok() || _forceClean)), - m_preMine(_us, m_stateDB), - m_postMine(_us, m_stateDB) -{ - setMiningThreads(); - if (_dbPath.size()) - Defaults::setDBPath(_dbPath); - m_vc.setOk(); - work(); -} - Client::Client(p2p::Host* _extNet, std::string const& _dbPath, bool _forceClean, u256 _networkId): m_vc(_dbPath), m_bc(_dbPath, !m_vc.ok() || _forceClean), @@ -107,7 +92,15 @@ Client::~Client() m_work->join(); m_work.reset(nullptr); } - stopNetwork(); + if (m_workNet) + { + if (m_workNetState.load(std::memory_order_acquire) == Active) + m_workNetState.store(Deleting, std::memory_order_release); + while (m_workNetState.load(std::memory_order_acquire) != Deleted) + this_thread::sleep_for(chrono::milliseconds(10)); + m_workNet->join(); + m_workNet.reset(nullptr); + } } void Client::ensureWorking() @@ -143,21 +136,23 @@ void Client::killChain() void Client::clearPending() { - WriteGuard l(x_stateDB); - if (!m_postMine.pending().size()) - return; h256Set changeds; - for (unsigned i = 0; i < m_postMine.pending().size(); ++i) - appendFromNewPending(m_postMine.bloom(i), changeds); - changeds.insert(PendingChangedFilter); - m_postMine = m_preMine; + { + WriteGuard l(x_stateDB); + if (!m_postMine.pending().size()) + return; + for (unsigned i = 0; i < m_postMine.pending().size(); ++i) + appendFromNewPending(m_postMine.bloom(i), changeds); + changeds.insert(PendingChangedFilter); + m_postMine = m_preMine; + } - if (!m_extHost.lock()) { ReadGuard l(x_miners); for (auto& m: m_miners) m.noteStateChange(); } + noteChanged(changeds); } @@ -228,117 +223,14 @@ void Client::appendFromNewBlock(h256 _block, h256Set& o_changed) const o_changed.insert(i.first); } -void Client::startNetwork(unsigned short _listenPort, std::string const& _seedHost, unsigned short _port, NodeMode _mode, unsigned _peers, string const& _publicIP, bool _upnp, u256 _networkId) -{ - static const char* c_threadName = "net"; - - { - UpgradableGuard l(x_net); - if (m_net.get()) - return; - { - UpgradeGuard ul(l); - - if (!m_workNet) - m_workNet.reset(new thread([&]() - { - setThreadName(c_threadName); - m_workNetState.store(Active, std::memory_order_release); - while (m_workNetState.load(std::memory_order_acquire) != Deleting) - workNet(); - m_workNetState.store(Deleted, std::memory_order_release); - })); - - if (!m_extHost.lock()) - { - m_net.reset(new Host(m_clientVersion, NetworkPreferences(_listenPort, _publicIP, _upnp, false))); - if (_mode == NodeMode::Full) - m_net->registerCapability(new EthereumHost(m_bc, _networkId)); - } - } - m_net->setIdealPeerCount(_peers); - } - - if (!m_extHost.lock()) - if (_seedHost.size()) - connect(_seedHost, _port); - - ensureWorking(); -} - -void Client::stopNetwork() -{ - UpgradableGuard l(x_net); - - if (m_workNet) - { - if (m_workNetState.load(std::memory_order_acquire) == Active) - m_workNetState.store(Deleting, std::memory_order_release); - while (m_workNetState.load(std::memory_order_acquire) != Deleted) - this_thread::sleep_for(chrono::milliseconds(10)); - m_workNet->join(); - } - if (m_net) - { - UpgradeGuard ul(l); - m_net.reset(nullptr); - m_workNet.reset(nullptr); - } -} - void Client::setNetworkId(u256 _n) { if (auto h = m_extHost.lock()) h->setNetworkId(_n); } -std::vector Client::peers() -{ - ReadGuard l(x_net); - return m_net ? m_net->peers() : std::vector(); -} - -size_t Client::peerCount() const -{ - ReadGuard l(x_net); - return m_net ? m_net->peerCount() : 0; -} - -void Client::setIdealPeerCount(size_t _n) const -{ - ReadGuard l(x_net); - if (m_net) - return m_net->setIdealPeerCount(_n); -} - -bytes Client::savePeers() -{ - ReadGuard l(x_net); - if (m_net) - return m_net->savePeers(); - return bytes(); -} - -void Client::restorePeers(bytesConstRef _saved) -{ - ReadGuard l(x_net); - if (m_net) - return m_net->restorePeers(_saved); -} - -void Client::connect(std::string const& _seedHost, unsigned short _port) -{ - ReadGuard l(x_net); - if (!m_net.get()) - return; - m_net->connect(_seedHost, _port); -} - void Client::setMiningThreads(unsigned _threads) { - if (m_extHost.lock()) - return; - stopMining(); auto t = _threads ? _threads : thread::hardware_concurrency(); @@ -353,8 +245,6 @@ void Client::setMiningThreads(unsigned _threads) MineProgress Client::miningProgress() const { MineProgress ret; - if (m_extHost.lock()) - return ret; ReadGuard l(x_miners); for (auto& m: m_miners) ret.combine(m.miningProgress()); @@ -364,8 +254,6 @@ MineProgress Client::miningProgress() const std::list Client::miningHistory() { std::list ret; - if (m_extHost.lock()) - return ret; ReadGuard l(x_miners); if (m_miners.empty()) @@ -479,23 +367,6 @@ void Client::workNet() // 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. - { - ReadGuard l(x_net); - if (m_net) - { - cwork << "NETWORK"; - m_net->process(); // must be in guard for now since it uses the blockchain. - - // returns h256Set as block hashes, once for each block that has come in/gone out. - if (m_net->cap()) - { - cwork << "NET <==> TQ ; CHAIN ==> NET ==> BQ"; - m_net->cap()->sync(m_tq, m_bq); - cwork << "TQ:" << m_tq.items() << "; BQ:" << m_bq.items(); - } - } - } - if (auto h = m_extHost.lock()) h->sync(m_tq, m_bq); @@ -509,7 +380,6 @@ void Client::work() cworkin << "WORK"; h256Set changeds; - if (!m_extHost.lock()) { ReadGuard l(x_miners); for (auto& m: m_miners) @@ -580,7 +450,7 @@ void Client::work() rsm = true; } } - if (!m_extHost.lock() && rsm) + if (rsm) { ReadGuard l(x_miners); for (auto& m: m_miners) diff --git a/libethereum/Client.h b/libethereum/Client.h index c11b1322b..9d0b16767 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -113,9 +113,6 @@ class Client: public MinerHost, public Interface friend class Miner; public: - /// Original Constructor. - explicit Client(std::string const& _clientVersion, Address _us = Address(), std::string const& _dbPath = std::string(), bool _forceClean = false); - /// New-style Constructor. explicit Client(p2p::Host* _host, std::string const& _dbPath = std::string(), bool _forceClean = false, u256 _networkId = 0); @@ -195,32 +192,6 @@ public: /// Get the object representing the current canonical blockchain. BlockChain const& blockChain() const { return m_bc; } - // Misc stuff: - - void setClientVersion(std::string const& _name) { m_clientVersion = _name; } - - // Network stuff: - - /// Get information on the current peer set. - std::vector peers(); - /// Same as peers().size(), but more efficient. - size_t peerCount() const; - /// Same as peers().size(), but more efficient. - void setIdealPeerCount(size_t _n) const; - - /// Start the network subsystem. - void startNetwork(unsigned short _listenPort = 30303, std::string const& _remoteHost = std::string(), unsigned short _remotePort = 30303, NodeMode _mode = NodeMode::Full, unsigned _peers = 5, std::string const& _publicIP = std::string(), bool _upnp = true, u256 _networkId = 0); - /// Connect to a particular peer. - void connect(std::string const& _seedHost, unsigned short _port = 30303); - /// Stop the network subsystem. - void stopNetwork(); - /// Is the network subsystem up? - bool haveNetwork() { ReadGuard l(x_net); return !!m_net; } - /// Save peers - bytes savePeers(); - /// Restore peers - void restorePeers(bytesConstRef _saved); - // Mining stuff: /// Check block validity prior to mining. @@ -274,7 +245,7 @@ private: /// @param _justQueue If true will only processing the transaction queues. void work(); - /// Do some work on the network. + /// Syncs the queues with the network. void workNet(); /// Overrides for being a mining host. @@ -300,7 +271,6 @@ private: State asOf(int _h) const; State asOf(unsigned _h) const; - std::string m_clientVersion; ///< Our end-application client's name/version. VersionChecker m_vc; ///< Dummy object to check & update the protocol version. BlockChain m_bc; ///< Maintains block database. TransactionQueue m_tq; ///< Maintains a list of incoming transactions not yet in a block on the blockchain. @@ -313,8 +283,6 @@ private: std::unique_ptr m_workNet; ///< The network thread. std::atomic m_workNetState; - mutable boost::shared_mutex x_net; ///< Lock for the network existance. - std::unique_ptr m_net; ///< Should run in background and send us events when blocks found and allow us to send blocks as required. std::weak_ptr m_extHost; ///< Our Ethereum Host. Don't do anything if we can't lock. diff --git a/libqethereum/QmlEthereum.cpp b/libqethereum/QmlEthereum.cpp index 350427f3e..89578a29a 100644 --- a/libqethereum/QmlEthereum.cpp +++ b/libqethereum/QmlEthereum.cpp @@ -50,7 +50,7 @@ using dev::g_logVerbosity; // Can get rid of this once we've sorted out ITC for signalling & multiplexed querying. dev::eth::Client* g_qmlClient; QObject* g_qmlMain; - +#if 0 QmlAccount::QmlAccount(QObject*) { } @@ -177,6 +177,8 @@ void QmlEthereum::transact(Secret _secret, Address _dest, u256 _amount, u256 _ga client()->transact(_secret, _amount, _dest, bytes(_data.data(), _data.data() + _data.size()), _gas, _gasPrice); } +#endif + // extra bits needed to link on VS #ifdef _MSC_VER diff --git a/libqethereum/QmlEthereum.h b/libqethereum/QmlEthereum.h index dcb636301..d242540a1 100644 --- a/libqethereum/QmlEthereum.h +++ b/libqethereum/QmlEthereum.h @@ -23,8 +23,8 @@ Q_DECLARE_METATYPE(dev::u256) Q_DECLARE_METATYPE(dev::Address) Q_DECLARE_METATYPE(dev::Secret) Q_DECLARE_METATYPE(dev::KeyPair) -Q_DECLARE_METATYPE(QmlAccount*) -Q_DECLARE_METATYPE(QmlEthereum*) +//Q_DECLARE_METATYPE(QmlAccount*) +//Q_DECLARE_METATYPE(QmlEthereum*) class QmlU256Helper: public QObject { @@ -75,7 +75,7 @@ public: Q_INVOKABLE QString stringOf(dev::Address _a) const { return QString::fromStdString(dev::toHex(_a.asArray())); } Q_INVOKABLE QString toAbridged(dev::Address _a) const { return QString::fromStdString(_a.abridged()); } }; - +#if 0 class QmlAccount: public QObject { Q_OBJECT @@ -155,7 +155,7 @@ private: Q_PROPERTY(bool listening READ isListening WRITE setListening) Q_PROPERTY(bool mining READ isMining WRITE setMining) }; - +#endif #if 0 template T to(QVariant const& _s) { if (_s.type() != QVariant::String) return T(); auto s = _s.toString().toLatin1(); assert(s.size() == sizeof(T)); return *(T*)s.data(); } template QVariant toQJS(T const& _s) { QLatin1String ret((char*)&_s, sizeof(T)); assert(QVariant(QString(ret)).toString().toLatin1().size() == sizeof(T)); assert(*(T*)(QVariant(QString(ret)).toString().toLatin1().data()) == _s); return QVariant(QString(ret)); } diff --git a/test/TestHelper.cpp b/test/TestHelper.cpp index 02a883dbc..3b3be3fec 100644 --- a/test/TestHelper.cpp +++ b/test/TestHelper.cpp @@ -42,11 +42,13 @@ void mine(Client& c, int numBlocks) void connectClients(Client& c1, Client& c2) { +#if 0 short c1Port = 20000; short c2Port = 21000; c1.startNetwork(c1Port); c2.startNetwork(c2Port); c2.connect("127.0.0.1", c1Port); +#endif } } diff --git a/third/MainWin.cpp b/third/MainWin.cpp index 11db6376f..5e147bbda 100644 --- a/third/MainWin.cpp +++ b/third/MainWin.cpp @@ -356,7 +356,7 @@ void Main::writeSettings() s.setValue("address", b); s.setValue("url", ui->urlEdit->text()); - bytes d = ethereum()->savePeers(); + bytes d = m_web3->savePeers(); if (d.size()) m_peers = QByteArray((char*)d.data(), (int)d.size()); s.setValue("peers", m_peers); @@ -471,7 +471,7 @@ void Main::refreshBalances() void Main::refreshNetwork() { - auto ps = ethereum()->peers(); + auto ps = m_web3->peers(); ui->peerCount->setText(QString::fromStdString(toString(ps.size())) + " peer(s)"); } @@ -560,10 +560,10 @@ void Main::ensureNetwork() web3()->connect(defPeer); } else - if (!ethereum()->peerCount()) - ethereum()->connect(defPeer); + if (!m_web3->peerCount()) + m_web3->connect(defPeer); if (m_peers.size()) - ethereum()->restorePeers(bytesConstRef((byte*)m_peers.data(), m_peers.size())); + m_web3->restorePeers(bytesConstRef((byte*)m_peers.data(), m_peers.size())); } void Main::on_connect_triggered() From f0cc349333d3790e10ef3c501d5beffd007e9338 Mon Sep 17 00:00:00 2001 From: Nick Savers Date: Tue, 16 Sep 2014 01:50:01 +0200 Subject: [PATCH 175/223] Add memsize calculation for EXTCODECOPY --- libevm/VM.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/libevm/VM.h b/libevm/VM.h index e7d218ec6..a07e225e6 100644 --- a/libevm/VM.h +++ b/libevm/VM.h @@ -157,7 +157,11 @@ template dev::bytesConstRef dev::eth::VM::go(Ext& _ext, OnOpFunc con require(3); newTempSize = memNeed(m_stack.back(), m_stack[m_stack.size() - 3]); break; - + case Instruction::EXTCODECOPY: + require(4); + newTempSize = memNeed(m_stack[m_stack.size() - 2], m_stack[m_stack.size() - 4]); + break; + case Instruction::BALANCE: runGas = c_balanceGas; break; From 682603f8345a3d488db1ef24ec2d958b65e79aca Mon Sep 17 00:00:00 2001 From: Tim Hughes Date: Tue, 16 Sep 2014 12:21:53 +0100 Subject: [PATCH 176/223] Revert use of operator T(), etc and remove conflicting cast operator to unsigned from RLP. unsigned is size_t on 32 bit builds, and the compiler therefore thinks conversion from RLP to vector is ambiguous between: RLP::operator vector() const and vector::vector(size_t size) --- libdevcore/RLP.h | 1 - libwhisper/WhisperPeer.h | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/libdevcore/RLP.h b/libdevcore/RLP.h index ed311a082..d22362c9d 100644 --- a/libdevcore/RLP.h +++ b/libdevcore/RLP.h @@ -160,7 +160,6 @@ public: explicit operator std::string() const { return toString(); } explicit operator RLPs() const { return toList(); } explicit operator byte() const { return toInt(); } - explicit operator unsigned() const { return toInt(); } explicit operator u256() const { return toInt(); } explicit operator bigint() const { return toInt(); } template explicit operator FixedHash<_N>() const { return toHash>(); } diff --git a/libwhisper/WhisperPeer.h b/libwhisper/WhisperPeer.h index ea79572c8..94d3233c1 100644 --- a/libwhisper/WhisperPeer.h +++ b/libwhisper/WhisperPeer.h @@ -95,7 +95,7 @@ class MessageFilter public: MessageFilter() {} MessageFilter(std::vector > const& _m): m_topicMasks(_m) {} - MessageFilter(RLP const& _r): m_topicMasks(_r.operator std::vector>()) {} + MessageFilter(RLP const& _r): m_topicMasks((std::vector>)_r) {} void fillStream(RLPStream& _s) const { _s << m_topicMasks; } h256 sha3() const { RLPStream s; fillStream(s); return dev::eth::sha3(s.out()); } From 066fc1811618bbe3c6e8748f2b8ff5ef12c26bae Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 16 Sep 2014 07:53:34 -0400 Subject: [PATCH 177/223] Move out worker thread stuff into other class. p2p::Host is worker rather than WebThree. Client is single worker. EthereumHost works. --- exp/main.cpp | 5 +- libdevcore/Worker.cpp | 63 +++++++++++++++++++++++ libdevcore/Worker.h | 49 ++++++++++++++++++ libethereum/Client.cpp | 97 ++++++++++-------------------------- libethereum/Client.h | 22 +++----- libethereum/EthereumHost.cpp | 36 +++++++------ libethereum/EthereumHost.h | 18 ++++--- libethereum/EthereumPeer.cpp | 28 +++++------ libp2p/Host.cpp | 15 +++++- libp2p/Host.h | 16 +++--- libp2p/HostCapability.h | 3 ++ libwebthree/WebThree.cpp | 54 -------------------- libwebthree/WebThree.h | 9 ++-- 13 files changed, 220 insertions(+), 195 deletions(-) create mode 100644 libdevcore/Worker.cpp create mode 100644 libdevcore/Worker.h diff --git a/exp/main.cpp b/exp/main.cpp index 9e9971852..77d4aa00f 100644 --- a/exp/main.cpp +++ b/exp/main.cpp @@ -326,16 +326,17 @@ int main(int argc, char** argv) ph.registerCapability(new WhisperHost()); auto wh = ph.cap(); + ph.start(); + if (!remoteHost.empty()) ph.connect(remoteHost, remotePort); /// Only interested in the packet if the lowest bit is 1 auto w = wh->installWatch(MessageFilter(std::vector >({{fromHex("0000000000000000000000000000000000000000000000000000000000000001"), fromHex("0000000000000000000000000000000000000000000000000000000000000001")}}))); + for (int i = 0; ; ++i) { - this_thread::sleep_for(chrono::milliseconds(1000)); - ph.process(); wh->sendRaw(h256(u256(i * i)).asBytes(), h256(u256(i)).asBytes(), 1000); for (auto i: wh->checkWatch(w)) cnote << "New message:" << (u256)h256(wh->message(i).payload); diff --git a/libdevcore/Worker.cpp b/libdevcore/Worker.cpp new file mode 100644 index 000000000..fe0a4fe92 --- /dev/null +++ b/libdevcore/Worker.cpp @@ -0,0 +1,63 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file Worker.cpp + * @author Gav Wood + * @date 2014 + */ + +#include "Worker.h" + +#include +#include +#include "Log.h" +using namespace std; +using namespace dev; + +void Worker::startWorking() +{ + cdebug << "startWorking for thread" << m_name; + Guard l(x_work); + if (m_work) + return; + cdebug << "Spawning" << m_name; + m_stop = false; + m_work.reset(new thread([&]() + { + setThreadName(m_name.c_str()); + while (!m_stop) + { + this_thread::sleep_for(chrono::milliseconds(1)); + doWork(); + } + cdebug << "Finishing up worker thread"; + doneWorking(); + })); +} + +void Worker::stopWorking() +{ + cdebug << "stopWorking for thread" << m_name; + Guard l(x_work); + if (!m_work) + return; + cdebug << "Stopping" << m_name; + m_stop = true; + m_work->join(); + m_work.reset(); + cdebug << "Stopped" << m_name; +} + diff --git a/libdevcore/Worker.h b/libdevcore/Worker.h new file mode 100644 index 000000000..2d07b5592 --- /dev/null +++ b/libdevcore/Worker.h @@ -0,0 +1,49 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file Worker.h + * @author Gav Wood + * @date 2014 + */ + +#pragma once + +#include +#include +#include "Guards.h" + +namespace dev +{ + +class Worker +{ +protected: + Worker(std::string const& _name): m_name(_name) {} + virtual ~Worker() { stopWorking(); } + void startWorking(); + void stopWorking(); + bool isWorking() const { Guard l(x_work); return !!m_work; } + virtual void doWork() = 0; + virtual void doneWorking() {} + +private: + mutable Mutex x_work; ///< Lock for the network existance. + std::unique_ptr m_work; ///< The network thread. + bool m_stop = false; + std::string m_name; +}; + +} diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index 9779acfbe..53222f343 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -54,79 +54,47 @@ void VersionChecker::setOk() } Client::Client(p2p::Host* _extNet, std::string const& _dbPath, bool _forceClean, u256 _networkId): + Worker("eth"), m_vc(_dbPath), m_bc(_dbPath, !m_vc.ok() || _forceClean), m_stateDB(State::openDB(_dbPath, !m_vc.ok() || _forceClean)), m_preMine(Address(), m_stateDB), m_postMine(Address(), m_stateDB) { - m_extHost = _extNet->registerCapability(new EthereumHost(m_bc, _networkId)); + m_host = _extNet->registerCapability(new EthereumHost(m_bc, m_tq, m_bq, _networkId)); // setMiningThreads(); if (_dbPath.size()) Defaults::setDBPath(_dbPath); m_vc.setOk(); - work(); + doWork(); - static const char* c_threadName = "ethsync"; - m_workNet.reset(new thread([&]() - { - setThreadName(c_threadName); - m_workNetState.store(Active, std::memory_order_release); - while (m_workNetState.load(std::memory_order_acquire) != Deleting) - workNet(); - m_workNetState.store(Deleted, std::memory_order_release); - })); - - ensureWorking(); + startWorking(); } Client::~Client() { - if (m_work) - { - if (m_workState.load(std::memory_order_acquire) == Active) - m_workState.store(Deleting, std::memory_order_release); - while (m_workState.load(std::memory_order_acquire) != Deleted) - this_thread::sleep_for(chrono::milliseconds(10)); - m_work->join(); - m_work.reset(nullptr); - } - if (m_workNet) - { - if (m_workNetState.load(std::memory_order_acquire) == Active) - m_workNetState.store(Deleting, std::memory_order_release); - while (m_workNetState.load(std::memory_order_acquire) != Deleted) - this_thread::sleep_for(chrono::milliseconds(10)); - m_workNet->join(); - m_workNet.reset(nullptr); - } + stopWorking(); } -void Client::ensureWorking() +void Client::setNetworkId(u256 _n) { - static const char* c_threadName = "eth"; + if (auto h = m_host.lock()) + h->setNetworkId(_n); +} - if (!m_work) - m_work.reset(new thread([&]() - { - setThreadName(c_threadName); - m_workState.store(Active, std::memory_order_release); - while (m_workState.load(std::memory_order_acquire) != Deleting) - work(); - m_workState.store(Deleted, std::memory_order_release); - - // Synchronise the state according to the head of the block chain. - // TODO: currently it contains keys for *all* blocks. Make it remove old ones. - WriteGuard l(x_stateDB); - m_preMine.sync(m_bc); - m_postMine = m_preMine; - })); +void Client::doneWorking() +{ + // Synchronise the state according to the head of the block chain. + // TODO: currently it contains keys for *all* blocks. Make it remove old ones. + WriteGuard l(x_stateDB); + m_preMine.sync(m_bc); + m_postMine = m_preMine; } void Client::flushTransactions() { - work(); + doWork(); } void Client::killChain() @@ -223,12 +191,6 @@ void Client::appendFromNewBlock(h256 _block, h256Set& o_changed) const o_changed.insert(i.first); } -void Client::setNetworkId(u256 _n) -{ - if (auto h = m_extHost.lock()) - h->setNetworkId(_n); -} - void Client::setMiningThreads(unsigned _threads) { stopMining(); @@ -295,7 +257,7 @@ void Client::setupState(State& _s) void Client::transact(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice) { - ensureWorking(); + startWorking(); Transaction t; // cdebug << "Nonce at " << toAddress(_secret) << " pre:" << m_preMine.transactionsFrom(toAddress(_secret)) << " post:" << m_postMine.transactionsFrom(toAddress(_secret)); @@ -337,7 +299,7 @@ bytes Client::call(Secret _secret, u256 _value, Address _dest, bytes const& _dat Address Client::transact(Secret _secret, u256 _endowment, bytes const& _init, u256 _gas, u256 _gasPrice) { - ensureWorking(); + startWorking(); Transaction t; { @@ -357,23 +319,12 @@ Address Client::transact(Secret _secret, u256 _endowment, bytes const& _init, u2 void Client::inject(bytesConstRef _rlp) { - ensureWorking(); + startWorking(); m_tq.attemptImport(_rlp); } -void Client::workNet() -{ - // 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. - if (auto h = m_extHost.lock()) - h->sync(m_tq, m_bq); - - this_thread::sleep_for(chrono::milliseconds(1)); -} - -void Client::work() +void Client::doWork() { // TODO: Use condition variable rather than polling. @@ -430,7 +381,8 @@ void Client::work() cwork << "preSTATE <== CHAIN"; if (m_preMine.sync(m_bc) || m_postMine.address() != m_preMine.address()) { - cnote << "New block on chain: Restarting mining operation."; + if (isMining()) + cnote << "New block on chain: Restarting mining operation."; m_postMine = m_preMine; rsm = true; changeds.insert(PendingChangedFilter); @@ -446,7 +398,8 @@ void Client::work() appendFromNewPending(i, changeds); changeds.insert(PendingChangedFilter); - cnote << "Additional transaction ready: Restarting mining operation."; + if (isMining()) + cnote << "Additional transaction ready: Restarting mining operation."; rsm = true; } } diff --git a/libethereum/Client.h b/libethereum/Client.h index 9d0b16767..25ec794af 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include @@ -108,7 +109,7 @@ struct WorkChannel: public LogChannel { static const char* name() { return "-W-" /** * @brief Main API hub for interfacing with Ethereum. */ -class Client: public MinerHost, public Interface +class Client: public MinerHost, public Interface, Worker { friend class Miner; @@ -217,7 +218,7 @@ public: unsigned miningThreads() const { ReadGuard l(x_miners); return m_miners.size(); } /// Start mining. /// NOT thread-safe - call it & stopMining only from a single thread - void startMining() { ensureWorking(); ReadGuard l(x_miners); for (auto& m: m_miners) m.start(); } + void startMining() { startWorking(); ReadGuard l(x_miners); for (auto& m: m_miners) m.start(); } /// Stop mining. /// NOT thread-safe void stopMining() { ReadGuard l(x_miners); for (auto& m: m_miners) m.stop(); } @@ -238,15 +239,10 @@ public: void killChain(); private: - /// Ensure the worker thread is running. Needed for blockchain maintenance & mining. - void ensureWorking(); - /// Do some work. Handles blockchain maintenance and mining. - /// @param _justQueue If true will only processing the transaction queues. - void work(); + virtual void doWork(); - /// Syncs the queues with the network. - void workNet(); + virtual void doneWorking(); /// Overrides for being a mining host. virtual void setupState(State& _s); @@ -281,13 +277,7 @@ private: State m_preMine; ///< The present state of the client. State m_postMine; ///< The state of the client which we're mining (i.e. it'll have all the rewards added). - std::unique_ptr m_workNet; ///< The network thread. - std::atomic m_workNetState; - - std::weak_ptr m_extHost; ///< Our Ethereum Host. Don't do anything if we can't lock. - - std::unique_ptr m_work; ///< The work thread. - std::atomic m_workState; + std::weak_ptr m_host; ///< Our Ethereum Host. Don't do anything if we can't lock. std::vector m_miners; mutable boost::shared_mutex x_miners; diff --git a/libethereum/EthereumHost.cpp b/libethereum/EthereumHost.cpp index a80c0df6f..2b88fc768 100644 --- a/libethereum/EthereumHost.cpp +++ b/libethereum/EthereumHost.cpp @@ -39,9 +39,12 @@ using namespace dev; using namespace dev::eth; using namespace p2p; -EthereumHost::EthereumHost(BlockChain const& _ch, u256 _networkId): +EthereumHost::EthereumHost(BlockChain const& _ch, TransactionQueue& _tq, BlockQueue& _bq, u256 _networkId): HostCapability(), - m_chain (&_ch), + Worker("ethsync"), + m_chain (_ch), + m_tq (_tq), + m_bq (_bq), m_networkId (_networkId) { m_latestBlockSent = _ch.currentHash(); @@ -82,7 +85,7 @@ bool EthereumHost::ensureInitialised(TransactionQueue& _tq) if (!m_latestBlockSent) { // First time - just initialise. - m_latestBlockSent = m_chain->currentHash(); + m_latestBlockSent = m_chain.currentHash(); clog(NetNote) << "Initialising: latest=" << m_latestBlockSent.abridged(); for (auto const& i: _tq.transactions()) @@ -102,7 +105,7 @@ void EthereumHost::noteDoneBlocks() clog(NetNote) << "No more blocks coming. Missing" << m_blocksNeeded.size() << "blocks."; else clog(NetNote) << "No more blocks to get."; - m_latestBlockSent = m_chain->currentHash(); + m_latestBlockSent = m_chain.currentHash(); } } @@ -110,7 +113,7 @@ bool EthereumHost::noteBlock(h256 _hash, bytesConstRef _data) { Guard l(x_blocksNeeded); m_blocksOnWay.erase(_hash); - if (!m_chain->details(_hash)) + if (!m_chain.details(_hash)) { lock_guard l(m_incomingLock); m_incomingBlocks.push_back(_data.toBytes()); @@ -119,13 +122,14 @@ bool EthereumHost::noteBlock(h256 _hash, bytesConstRef _data) return false; } -bool EthereumHost::sync(TransactionQueue& _tq, BlockQueue& _bq) +void EthereumHost::doWork() { - bool netChange = ensureInitialised(_tq); - auto h = m_chain->currentHash(); - maintainTransactions(_tq, h); - maintainBlocks(_bq, h); - return netChange; + bool netChange = ensureInitialised(m_tq); + auto h = m_chain.currentHash(); + maintainTransactions(m_tq, h); + maintainBlocks(m_bq, h); +// return netChange; + // TODO: Figure out what to do with netChange. } void EthereumHost::maintainTransactions(TransactionQueue& _tq, h256 _currentHash) @@ -172,7 +176,7 @@ void EthereumHost::maintainBlocks(BlockQueue& _bq, h256 _currentHash) { lock_guard l(m_incomingLock); for (auto it = m_incomingBlocks.rbegin(); it != m_incomingBlocks.rend(); ++it) - if (_bq.import(&*it, *m_chain)) + if (_bq.import(&*it, m_chain)) {} else{} // TODO: don't forward it. m_incomingBlocks.clear(); @@ -191,9 +195,9 @@ void EthereumHost::maintainBlocks(BlockQueue& _bq, h256 _currentHash) EthereumPeer::prep(ts); bytes bs; unsigned c = 0; - for (auto h: m_chain->treeRoute(m_latestBlockSent, _currentHash, nullptr, false, true)) + for (auto h: m_chain.treeRoute(m_latestBlockSent, _currentHash, nullptr, false, true)) { - bs += m_chain->block(h); + bs += m_chain.block(h); ++c; } clog(NetMessageSummary) << "Sending" << c << "new blocks (current is" << _currentHash << ", was" << m_latestBlockSent << ")"; @@ -221,9 +225,9 @@ void EthereumHost::noteHaveChain(EthereumPeer* _from) if (_from->m_neededBlocks.empty()) return; - clog(NetNote) << "Hash-chain COMPLETE:" << _from->m_totalDifficulty << "vs" << m_chain->details().totalDifficulty << "," << m_totalDifficultyOfNeeded << ";" << _from->m_neededBlocks.size() << " blocks, ends" << _from->m_neededBlocks.back().abridged(); + clog(NetNote) << "Hash-chain COMPLETE:" << _from->m_totalDifficulty << "vs" << m_chain.details().totalDifficulty << "," << m_totalDifficultyOfNeeded << ";" << _from->m_neededBlocks.size() << " blocks, ends" << _from->m_neededBlocks.back().abridged(); - if ((m_totalDifficultyOfNeeded && td < m_totalDifficultyOfNeeded) || td < m_chain->details().totalDifficulty) + if ((m_totalDifficultyOfNeeded && td < m_totalDifficultyOfNeeded) || td < m_chain.details().totalDifficulty) { clog(NetNote) << "Difficulty of hashchain LOWER. Ignoring."; return; diff --git a/libethereum/EthereumHost.h b/libethereum/EthereumHost.h index 61b716209..7c6b4b865 100644 --- a/libethereum/EthereumHost.h +++ b/libethereum/EthereumHost.h @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include "CommonNet.h" @@ -49,13 +50,13 @@ class BlockQueue; * @brief The EthereumHost class * @warning None of this is thread-safe. You have been warned. */ -class EthereumHost: public p2p::HostCapability +class EthereumHost: public p2p::HostCapability, Worker { friend class EthereumPeer; public: /// Start server, but don't listen. - EthereumHost(BlockChain const& _ch, u256 _networkId); + EthereumHost(BlockChain const& _ch, TransactionQueue& _tq, BlockQueue& _bq, u256 _networkId); /// Will block on network process events. virtual ~EthereumHost(); @@ -64,9 +65,6 @@ public: u256 networkId() const { return m_networkId; } void setNetworkId(u256 _n) { m_networkId = _n; } - /// Sync with the BlockChain. It might contain one of our mined blocks, we might have new candidates from the network. - bool sync(TransactionQueue&, BlockQueue& _bc); - private: /// Session wants to pass us a block that we might not have. /// @returns true if we didn't have it. @@ -76,6 +74,9 @@ private: /// Called when the peer can no longer provide us with any needed blocks. void noteDoneBlocks(); + /// Sync with the BlockChain. It might contain one of our mined blocks, we might have new candidates from the network. + void doWork(); + void maintainTransactions(TransactionQueue& _tq, h256 _currentBlock); void maintainBlocks(BlockQueue& _bq, h256 _currentBlock); @@ -90,7 +91,12 @@ private: /// Initialises the network peer-state, doing the stuff that needs to be once-only. @returns true if it really was first. bool ensureInitialised(TransactionQueue& _tq); - BlockChain const* m_chain = nullptr; + virtual void onStarting() { startWorking(); } + virtual void onStopping() { stopWorking(); } + + BlockChain const& m_chain; + TransactionQueue& m_tq; ///< Maintains a list of incoming transactions not yet in a block on the blockchain. + BlockQueue& m_bq; ///< Maintains a list of incoming blocks not yet on the blockchain (to be imported). u256 m_networkId; diff --git a/libethereum/EthereumPeer.cpp b/libethereum/EthereumPeer.cpp index 5918a7c49..238bc3d8d 100644 --- a/libethereum/EthereumPeer.cpp +++ b/libethereum/EthereumPeer.cpp @@ -57,9 +57,9 @@ void EthereumPeer::sendStatus() s.appendList(6) << StatusPacket << host()->protocolVersion() << host()->networkId() - << host()->m_chain->details().totalDifficulty - << host()->m_chain->currentHash() - << host()->m_chain->genesisHash(); + << host()->m_chain.details().totalDifficulty + << host()->m_chain.currentHash() + << host()->m_chain.genesisHash(); sealAndSend(s); } @@ -73,11 +73,11 @@ void EthereumPeer::startInitialSync() sealAndSend(s); } - h256 c = host()->m_chain->currentHash(); - unsigned n = host()->m_chain->number(); - u256 td = max(host()->m_chain->details().totalDifficulty, host()->m_totalDifficultyOfNeeded); + h256 c = host()->m_chain.currentHash(); + unsigned n = host()->m_chain.number(); + u256 td = max(host()->m_chain.details().totalDifficulty, host()->m_totalDifficultyOfNeeded); - clogS(NetAllDetail) << "Initial sync. Latest:" << c.abridged() << ", number:" << n << ", TD: max(" << host()->m_chain->details().totalDifficulty << "," << host()->m_totalDifficultyOfNeeded << ") versus " << m_totalDifficulty; + clogS(NetAllDetail) << "Initial sync. Latest:" << c.abridged() << ", number:" << n << ", TD: max(" << host()->m_chain.details().totalDifficulty << "," << host()->m_totalDifficultyOfNeeded << ") versus " << m_totalDifficulty; if (td > m_totalDifficulty) return; // All good - we have the better chain. @@ -132,7 +132,7 @@ bool EthereumPeer::interpret(RLP const& _r) clogS(NetMessageSummary) << "Status:" << m_protocolVersion << "/" << m_networkId << "/" << genesisHash.abridged() << ", TD:" << m_totalDifficulty << "=" << m_latestHash.abridged(); - if (genesisHash != host()->m_chain->genesisHash()) + if (genesisHash != host()->m_chain.genesisHash()) disable("Invalid genesis hash"); if (m_protocolVersion != host()->protocolVersion()) disable("Invalid protocol version."); @@ -162,12 +162,12 @@ bool EthereumPeer::interpret(RLP const& _r) unsigned limit = _r[2].toInt(); clogS(NetMessageSummary) << "GetBlockHashes (" << limit << "entries," << later.abridged() << ")"; - unsigned c = min(host()->m_chain->number(later), limit); + unsigned c = min(host()->m_chain.number(later), limit); RLPStream s; prep(s).appendList(1 + c).append(BlockHashesPacket); - h256 p = host()->m_chain->details(later).parent; - for (unsigned i = 0; i < c; ++i, p = host()->m_chain->details(p).parent) + h256 p = host()->m_chain.details(later).parent; + for (unsigned i = 0; i < c; ++i, p = host()->m_chain.details(p).parent) s << p; sealAndSend(s); break; @@ -183,7 +183,7 @@ bool EthereumPeer::interpret(RLP const& _r) for (unsigned i = 1; i < _r.itemCount(); ++i) { auto h = _r[i].toHash(); - if (host()->m_chain->details(h)) + if (host()->m_chain.details(h)) { host()->noteHaveChain(this); return true; @@ -206,7 +206,7 @@ bool EthereumPeer::interpret(RLP const& _r) unsigned n = 0; for (unsigned i = 1; i < _r.itemCount() && i <= c_maxBlocks; ++i) { - auto b = host()->m_chain->block(_r[i].toHash()); + auto b = host()->m_chain.block(_r[i].toHash()); if (b.size()) { rlp += b; @@ -248,7 +248,7 @@ bool EthereumPeer::interpret(RLP const& _r) auto h = BlockInfo::headerHash(_r[i].data()); BlockInfo bi(_r[i].data()); Guard l(x_knownBlocks); - if (!host()->m_chain->details(bi.parentHash) && !m_knownBlocks.count(bi.parentHash)) + if (!host()->m_chain.details(bi.parentHash) && !m_knownBlocks.count(bi.parentHash)) { unknownParents++; clogS(NetAllDetail) << "Unknown parent" << bi.parentHash << "of block" << h; diff --git a/libp2p/Host.cpp b/libp2p/Host.cpp index ac3d6bc10..7e9589646 100644 --- a/libp2p/Host.cpp +++ b/libp2p/Host.cpp @@ -17,7 +17,7 @@ /** @file Host.cpp * @authors: * Gav Wood - * Eric Lombrozo + * Eric Lombrozo (Windows version of populateAddresses()) * @date 2014 */ @@ -55,6 +55,7 @@ static const set c_rejectAddresses = { }; Host::Host(std::string const& _clientVersion, NetworkPreferences const& _n, bool _start): + Worker("p2p"), m_clientVersion(_clientVersion), m_netPrefs(_n), m_acceptor(m_ioService), @@ -104,10 +105,20 @@ void Host::start() ensureAccepting(); m_lastPeersRequest = chrono::steady_clock::time_point::min(); clog(NetNote) << "Id:" << m_id.abridged(); + + for (auto const& h: m_capabilities) + h.second->onStarting(); + + startWorking(); } void Host::stop() { + for (auto const& h: m_capabilities) + h.second->onStopping(); + + stopWorking(); + if (m_acceptor.is_open()) { if (m_accepting) @@ -473,7 +484,7 @@ std::vector Host::peers(bool _updatePing) const return ret; } -void Host::process() +void Host::doWork() { growPeers(); prunePeers(); diff --git a/libp2p/Host.h b/libp2p/Host.h index b055b9aeb..57f0a0022 100644 --- a/libp2p/Host.h +++ b/libp2p/Host.h @@ -29,6 +29,7 @@ #include #include #include +#include #include "HostCapability.h" namespace ba = boost::asio; namespace bi = boost::asio::ip; @@ -55,7 +56,7 @@ struct NetworkPreferences * @brief The Host class * Capabilities should be registered prior to startNetwork, since m_capabilities is not thread-safe. */ -class Host +class Host: public Worker { friend class Session; friend class HostCapabilityFace; @@ -84,11 +85,6 @@ public: void connect(std::string const& _addr, unsigned short _port = 30303) noexcept; void connect(bi::tcp::endpoint const& _ep); - /// Conduct I/O, polling, syncing, whatever. - /// Ideally all time-consuming I/O is done in a background thread or otherwise asynchronously, but you get this call every 100ms or so anyway. - /// This won't touch alter the blockchain. - void process(); - /// @returns true iff we have the a peer of the given id. bool havePeer(h512 _id) const; @@ -117,12 +113,13 @@ public: void start(); void stop(); + bool isStarted() const { return isWorking(); } h512 id() const { return m_id; } void registerPeer(std::shared_ptr _s, std::vector const& _caps); -protected: +private: /// Called when the session has provided us with a new peer we can connect to. void noteNewPeers() {} @@ -134,6 +131,11 @@ protected: void growPeers(); void prunePeers(); + /// Conduct I/O, polling, syncing, whatever. + /// Ideally all time-consuming I/O is done in a background thread or otherwise asynchronously, but you get this call every 100ms or so anyway. + /// This won't touch alter the blockchain. + virtual void doWork(); + std::map potentialPeers(); std::string m_clientVersion; diff --git a/libp2p/HostCapability.h b/libp2p/HostCapability.h index cf2399b83..1c532788b 100644 --- a/libp2p/HostCapability.h +++ b/libp2p/HostCapability.h @@ -49,6 +49,9 @@ protected: virtual std::string name() const = 0; virtual Capability* newPeerCapability(Session* _s) = 0; + virtual void onStarting() {} + virtual void onStopping() {} + void seal(bytes& _b); private: diff --git a/libwebthree/WebThree.cpp b/libwebthree/WebThree.cpp index 6718ec96d..55e82a36f 100644 --- a/libwebthree/WebThree.cpp +++ b/libwebthree/WebThree.cpp @@ -51,88 +51,34 @@ WebThreeDirect::WebThreeDirect(std::string const& _clientVersion, std::string co WebThreeDirect::~WebThreeDirect() { - stopNetwork(); -} - -void WebThreeDirect::startNetwork() -{ - static const char* c_threadName = "p2p"; - - m_net.start(); - - UpgradableGuard l(x_work); - { - UpgradeGuard ul(l); - - if (!m_work) - m_work.reset(new thread([&]() - { - setThreadName(c_threadName); - m_workState.store(Active, std::memory_order_release); - while (m_workState.load(std::memory_order_acquire) != Deleting) - { - this_thread::sleep_for(chrono::milliseconds(1)); - ReadGuard l(x_work); - m_net.process(); // must be in guard for now since it uses the blockchain. - } - m_workState.store(Deleted, std::memory_order_release); - })); - } -} - -void WebThreeDirect::stopNetwork() -{ - m_net.stop(); - - UpgradableGuard l(x_work); - - if (m_work) - { - if (m_workState.load(std::memory_order_acquire) == Active) - m_workState.store(Deleting, std::memory_order_release); - while (m_workState.load(std::memory_order_acquire) != Deleted) - this_thread::sleep_for(chrono::milliseconds(10)); - m_work->join(); - } - if (m_work) - { - UpgradeGuard ul(l); - m_work.reset(nullptr); - } } std::vector WebThreeDirect::peers() { - ReadGuard l(x_work); return m_net.peers(); } size_t WebThreeDirect::peerCount() const { - ReadGuard l(x_work); return m_net.peerCount(); } void WebThreeDirect::setIdealPeerCount(size_t _n) { - ReadGuard l(x_work); return m_net.setIdealPeerCount(_n); } bytes WebThreeDirect::savePeers() { - ReadGuard l(x_work); return m_net.savePeers(); } void WebThreeDirect::restorePeers(bytesConstRef _saved) { - ReadGuard l(x_work); return m_net.restorePeers(_saved); } void WebThreeDirect::connect(std::string const& _seedHost, unsigned short _port) { - ReadGuard l(x_work); m_net.connect(_seedHost, _port); } diff --git a/libwebthree/WebThree.h b/libwebthree/WebThree.h index 4799e7765..da41f9e76 100644 --- a/libwebthree/WebThree.h +++ b/libwebthree/WebThree.h @@ -104,15 +104,15 @@ public: /// Sets the ideal number of peers. void setIdealPeerCount(size_t _n); - bool haveNetwork() const { return !!m_work; } + bool haveNetwork() const { return m_net.isStarted(); } void setNetworkPreferences(p2p::NetworkPreferences const& _n) { auto had = haveNetwork(); if (had) stopNetwork(); m_netPrefs = _n; if (had) startNetwork(); } /// Start the network subsystem. - void startNetwork(); + void startNetwork() { m_net.start(); } /// Stop the network subsystem. - void stopNetwork(); + void stopNetwork() { m_net.stop(); } private: std::string m_clientVersion; ///< Our end-application client's name/version. @@ -121,9 +121,6 @@ private: std::unique_ptr m_whisper; ///< Main interface for Whisper ("shh") protocol. p2p::Host m_net; ///< Should run in background and send us events when blocks found and allow us to send blocks as required. - std::unique_ptr m_work; ///< The network thread. - mutable boost::shared_mutex x_work; ///< Lock for the network existance. - std::atomic m_workState; p2p::NetworkPreferences m_netPrefs; }; From 4eb549cd8070b568bca9a35c553d3b08b7d7f273 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 16 Sep 2014 08:09:48 -0400 Subject: [PATCH 178/223] Moved miner over to new Worker class. --- libdevcore/Worker.h | 12 +++++++++++- libethereum/Client.cpp | 2 +- libethereum/Miner.cpp | 36 +++--------------------------------- libethereum/Miner.h | 24 ++++++++++-------------- test/TestHelper.cpp | 1 + 5 files changed, 26 insertions(+), 49 deletions(-) diff --git a/libdevcore/Worker.h b/libdevcore/Worker.h index 2d07b5592..8f0baaf60 100644 --- a/libdevcore/Worker.h +++ b/libdevcore/Worker.h @@ -31,8 +31,18 @@ namespace dev class Worker { protected: - Worker(std::string const& _name): m_name(_name) {} + Worker(std::string const& _name = "anon"): m_name(_name) {} + + /// Move-constructor. + Worker(Worker&& _m) { std::swap(m_name, _m.m_name); } + + /// Move-assignment. + Worker& operator=(Worker&& _m) { std::swap(m_name, _m.m_name); return *this; } + virtual ~Worker() { stopWorking(); } + + void setName(std::string _n) { if (!isWorking()) m_name = _n; } + void startWorking(); void stopWorking(); bool isWorking() const { Guard l(x_work); return !!m_work; } diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index 53222f343..6527d1eb1 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -63,7 +63,7 @@ Client::Client(p2p::Host* _extNet, std::string const& _dbPath, bool _forceClean, { m_host = _extNet->registerCapability(new EthereumHost(m_bc, m_tq, m_bq, _networkId)); -// setMiningThreads(); + setMiningThreads(); if (_dbPath.size()) Defaults::setDBPath(_dbPath); m_vc.setOk(); diff --git a/libethereum/Miner.cpp b/libethereum/Miner.cpp index 95789889f..5020a72c9 100644 --- a/libethereum/Miner.cpp +++ b/libethereum/Miner.cpp @@ -27,42 +27,12 @@ using namespace dev; using namespace dev::eth; Miner::Miner(MinerHost* _host, unsigned _id): - m_host(_host), - m_id(_id) + Worker("miner-" + toString(_id)), + m_host(_host) { } -void Miner::start() -{ - if (!m_host) - return; - - Guard l(x_work); - if (!m_work) - { - m_stop = false; - m_work.reset(new thread([&]() - { - setThreadName(("miner-" + toString(m_id)).c_str()); - m_miningStatus = Preparing; - while (!m_stop) - work(); - })); - } -} - -void Miner::stop() -{ - Guard l(x_work); - if (m_work) - { - m_stop = true; - m_work->join(); - m_work.reset(nullptr); - } -} - -void Miner::work() +void Miner::doWork() { // Do some mining. if ((m_pendingCount || m_host->force()) && m_miningStatus != Mined) diff --git a/libethereum/Miner.h b/libethereum/Miner.h index d8ae20cc1..bee4d70e4 100644 --- a/libethereum/Miner.h +++ b/libethereum/Miner.h @@ -26,6 +26,7 @@ #include #include #include +#include #include #include "State.h" @@ -74,38 +75,38 @@ public: * @threadsafe * @todo Signal Miner to restart once with condition variables. */ -class Miner +class Miner: Worker { public: /// Null constructor. - Miner(): m_host(nullptr), m_id(0) {} + Miner(): m_host(nullptr) {} /// Constructor. Miner(MinerHost* _host, unsigned _id = 0); /// Move-constructor. - Miner(Miner&& _m) { std::swap(m_host, _m.m_host); std::swap(m_id, _m.m_id); } + Miner(Miner&& _m): Worker((Worker&&)_m) { std::swap(m_host, _m.m_host); } /// Move-assignment. - Miner& operator=(Miner&& _m) { std::swap(m_host, _m.m_host); std::swap(m_id, _m.m_id); return *this; } + Miner& operator=(Miner&& _m) { Worker::operator=((Worker&&)_m); std::swap(m_host, _m.m_host); return *this; } /// Destructor. Stops miner. ~Miner() { stop(); } /// Setup its basics. - void setup(MinerHost* _host, unsigned _id = 0) { m_host = _host; m_id = _id; } + void setup(MinerHost* _host, unsigned _id = 0) { m_host = _host; setName("miner-" + toString(_id)); } /// Start mining. - void start(); + void start() { startWorking(); } /// Stop mining. - void stop(); + void stop() { stopWorking(); } /// Call to notify Miner of a state change. void noteStateChange() { m_miningStatus = Preparing; } /// @returns true iff the mining has been start()ed. It may still not be actually mining, depending on the host's turbo() & force(). - bool isRunning() { return !!m_work; } + bool isRunning() { return isWorking(); } /// @returns true if mining is complete. bool isComplete() const { return m_miningStatus == Mined; } @@ -121,14 +122,9 @@ public: private: /// Do some work on the mining. - void work(); + virtual void doWork(); MinerHost* m_host = nullptr; ///< Our host. - unsigned m_id = 0; ///< Our identity. - - std::mutex x_work; ///< Mutex protecting the creation of the work thread. - std::unique_ptr m_work; ///< The work thread. - bool m_stop = false; ///< Stop working? enum MiningStatus { Preparing, Mining, Mined, Stopping, Stopped }; MiningStatus m_miningStatus = Preparing;///< TODO: consider mutex/atomic variable. diff --git a/test/TestHelper.cpp b/test/TestHelper.cpp index 3b3be3fec..20c42a9b8 100644 --- a/test/TestHelper.cpp +++ b/test/TestHelper.cpp @@ -42,6 +42,7 @@ void mine(Client& c, int numBlocks) void connectClients(Client& c1, Client& c2) { + // TODO: Move to WebThree. eth::Client no longer handles networking. #if 0 short c1Port = 20000; short c2Port = 21000; From 3c3bde3ef83ac0410b01eb1e4da1e50d06b0cdc9 Mon Sep 17 00:00:00 2001 From: Tim Hughes Date: Tue, 16 Sep 2014 19:49:47 +0100 Subject: [PATCH 179/223] Missing part of previous commit. --- libdevcore/RLP.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libdevcore/RLP.h b/libdevcore/RLP.h index d22362c9d..35e1d1dcf 100644 --- a/libdevcore/RLP.h +++ b/libdevcore/RLP.h @@ -212,8 +212,8 @@ public: std::pair ret; if (isList()) { - ret.first = (*this)[0].operator T(); - ret.second = (*this)[1].operator U(); + ret.first = (T)(*this)[0]; + ret.second = (U)(*this)[1]; } return ret; } From 81a1f8d949ff8c9acfc399c67726e4bedcafc258 Mon Sep 17 00:00:00 2001 From: Christoph Jentzsch Date: Wed, 17 Sep 2014 09:22:52 +0200 Subject: [PATCH 180/223] Added documentation for EVM commands --- libevmface/Instruction.h | 261 +++++++++++++++++++-------------------- 1 file changed, 129 insertions(+), 132 deletions(-) diff --git a/libevmface/Instruction.h b/libevmface/Instruction.h index 8d4ca989a..6c87b7a76 100644 --- a/libevmface/Instruction.h +++ b/libevmface/Instruction.h @@ -32,143 +32,140 @@ namespace dev namespace eth { -// TODO: Update comments. - /// Virtual machine bytecode instruction. enum class Instruction: uint8_t { STOP = 0x00, ///< halts execution - ADD, - MUL, - SUB, - DIV, - SDIV, - MOD, - SMOD, - EXP, - NEG, - LT, - GT, - SLT, - SGT, - EQ, - NOT, - - AND = 0x10, - OR, - XOR, - BYTE, - ADDMOD, - MULMOD, - - SHA3 = 0x20, - - ADDRESS = 0x30, - BALANCE, - ORIGIN, - CALLER, - CALLVALUE, - CALLDATALOAD, - CALLDATASIZE, - CALLDATACOPY, - CODESIZE, - CODECOPY, - GASPRICE, - EXTCODESIZE, - EXTCODECOPY, - - PREVHASH = 0x40, - COINBASE, - TIMESTAMP, - NUMBER, - DIFFICULTY, - GASLIMIT, - - POP = 0x50, - MLOAD = 0x53, - MSTORE, - MSTORE8, - SLOAD, - SSTORE, - JUMP, - JUMPI, - PC, - MSIZE, - GAS, - - PUSH1 = 0x60, - PUSH2, - PUSH3, - PUSH4, - PUSH5, - PUSH6, - PUSH7, - PUSH8, - PUSH9, - PUSH10, - PUSH11, - PUSH12, - PUSH13, - PUSH14, - PUSH15, - PUSH16, - PUSH17, - PUSH18, - PUSH19, - PUSH20, - PUSH21, - PUSH22, - PUSH23, - PUSH24, - PUSH25, - PUSH26, - PUSH27, - PUSH28, - PUSH29, - PUSH30, - PUSH31, - PUSH32, - - DUP1 = 0x80, - DUP2, - DUP3, - DUP4, - DUP5, - DUP6, - DUP7, - DUP8, - DUP9, - DUP10, - DUP11, - DUP12, - DUP13, - DUP14, - DUP15, - DUP16, - - SWAP1 = 0x90, - SWAP2, - SWAP3, - SWAP4, - SWAP5, - SWAP6, - SWAP7, - SWAP8, - SWAP9, - SWAP10, - SWAP11, - SWAP12, - SWAP13, - SWAP14, - SWAP15, - SWAP16, - - CREATE = 0xf0, - CALL, - RETURN, - POST, + ADD, ///< addition operation + MUL, ///< mulitplication operation + SUB, ///< subtraction operation + DIV, ///< integer division operation + SDIV, ///< signed integer division operation + MOD, ///< modulo remainder operation + SMOD, ///< signed modulo remainder operation + EXP, ///< exponential operation + NEG, ///< negation operation + LT, ///< less-than comparision + GT, ///< greater-than comparision + SLT, ///< signed less-than comparision + SGT, ///< signed greater-than comparision + EQ, ///< equality comparision + NOT, ///< simple not operator + + AND = 0x10, ///< bitwise AND operation + OR, ///< bitwise OR operation + XOR, ///< bitwise XOR operation + BYTE, ///< retrieve single byte from word + ADDMOD, ///< unsigned modular addition + MULMOD, ///< unsigned modular multiplication + SHA3 = 0x20, ///< compute SHA3-256 hash + + ADDRESS = 0x30, ///< get address of currently executing account + BALANCE, ///< get balance of the given account + ORIGIN, ///< get execution origination address + CALLER, ///< get caller address + CALLVALUE, ///< get deposited value by the instruction/transaction responsible for this execution + CALLDATALOAD, ///< get input data of current environment + CALLDATASIZE, ///< get size of input data in current environment + CALLDATACOPY, ///< copy input data in current environment to memory + CODESIZE, ///< get size of code running in current environment + CODECOPY, ///< copy code running in current environment to memory + GASPRICE, ///< get price of gas in current environment + EXTCODESIZE, ///< get external code size (from another contract) + EXTCODECOPY, ///< copy external code (from another contract) + + PREVHASH = 0x40, ///< get hash of most recent complete block + COINBASE, ///< get the block's coinbase address + TIMESTAMP, ///< get the block's timestamp + NUMBER, ///< get the block's number + DIFFICULTY, ///< get the block's difficulty + GASLIMIT, ///< get the block's gas limit + + POP = 0x50, ///< remove item from stack + MLOAD = 0x53, ///< load word from memory + MSTORE, ///< save word to memory + MSTORE8, ///< save byte to memory + SLOAD, ///< load word from storage + SSTORE, ///< save word to storage + JUMP, ///< alter the program counter + JUMPI, ///< conditionally alter the program counter + PC, ///< get the program counter + MSIZE, ///< get the size of active memory + GAS, ///< get the amount of available gas + + PUSH1 = 0x60, ///< place 1 byte item on stack + PUSH2, ///< place 2 byte item on stack + PUSH3, ///< place 3 byte item on stack + PUSH4, ///< place 4 byte item on stack + PUSH5, ///< place 5 byte item on stack + PUSH6, ///< place 6 byte item on stack + PUSH7, ///< place 7 byte item on stack + PUSH8, ///< place 8 byte item on stack + PUSH9, ///< place 9 byte item on stack + PUSH10, ///< place 10 byte item on stack + PUSH11, ///< place 11 byte item on stack + PUSH12, ///< place 12 byte item on stack + PUSH13, ///< place 13 byte item on stack + PUSH14, ///< place 14 byte item on stack + PUSH15, ///< place 15 byte item on stack + PUSH16, ///< place 16 byte item on stack + PUSH17, ///< place 17 byte item on stack + PUSH18, ///< place 18 byte item on stack + PUSH19, ///< place 19 byte item on stack + PUSH20, ///< place 20 byte item on stack + PUSH21, ///< place 21 byte item on stack + PUSH22, ///< place 22 byte item on stack + PUSH23, ///< place 23 byte item on stack + PUSH24, ///< place 24 byte item on stack + PUSH25, ///< place 25 byte item on stack + PUSH26, ///< place 26 byte item on stack + PUSH27, ///< place 27 byte item on stack + PUSH28, ///< place 28 byte item on stack + PUSH29, ///< place 29 byte item on stack + PUSH30, ///< place 30 byte item on stack + PUSH31, ///< place 31 byte item on stack + PUSH32, ///< place 32 byte item on stack + + DUP1 = 0x80, ///< copies the highest item in the stack to the top of the stack + DUP2, ///< copies the second highest item in the stack to the top of the stack + DUP3, ///< copies the third highest item in the stack to the top of the stack + DUP4, ///< copies the 4th highest item in the stack to the top of the stack + DUP5, ///< copies the 5th highest item in the stack to the top of the stack + DUP6, ///< copies the 6th highest item in the stack to the top of the stack + DUP7, ///< copies the 7th highest item in the stack to the top of the stack + DUP8, ///< copies the 8th highest item in the stack to the top of the stack + DUP9, ///< copies the 9th highest item in the stack to the top of the stack + DUP10, ///< copies the 10th highest item in the stack to the top of the stack + DUP11, ///< copies the 11th highest item in the stack to the top of the stack + DUP12, ///< copies the 12th highest item in the stack to the top of the stack + DUP13, ///< copies the 13th highest item in the stack to the top of the stack + DUP14, ///< copies the 14th highest item in the stack to the top of the stack + DUP15, ///< copies the 15th highest item in the stack to the top of the stack + DUP16, ///< copies the 16th highest item in the stack to the top of the stack + + SWAP1 = 0x90, ///< swaps the highest and second highest value on the stack + SWAP2, ///< swaps the highest and third highest value on the stack + SWAP3, ///< swaps the highest and 4th highest value on the stack + SWAP4, ///< swaps the highest and 5th highest value on the stack + SWAP5, ///< swaps the highest and 6th highest value on the stack + SWAP6, ///< swaps the highest and 7th highest value on the stack + SWAP7, ///< swaps the highest and 8th highest value on the stack + SWAP8, ///< swaps the highest and 9th highest value on the stack + SWAP9, ///< swaps the highest and 10th highest value on the stack + SWAP10, ///< swaps the highest and 11th highest value on the stack + SWAP11, ///< swaps the highest and 12th highest value on the stack + SWAP12, ///< swaps the highest and 13th highest value on the stack + SWAP13, ///< swaps the highest and 14th highest value on the stack + SWAP14, ///< swaps the highest and 15th highest value on the stack + SWAP15, ///< swaps the highest and 16th highest value on the stack + SWAP16, ///< swaps the highest and 17th highest value on the stack + + CREATE = 0xf0, ///< create a new account with associated code + CALL, ///< message-call into an account + RETURN, ///< halt execution returning output data + POST, ///< asynchronous call without output (adds a message to the post queue) CALLSTATELESS, - SUICIDE = 0xff + SUICIDE = 0xff ///< halt execution and register account for later deletion }; /// Information structure for a particular instruction. From 91c1819d4331063adf6e7c8ec78c3d0afc60c0e2 Mon Sep 17 00:00:00 2001 From: subtly Date: Wed, 17 Sep 2014 17:15:34 +0200 Subject: [PATCH 181/223] mutex shared host/peer sets, write-loop crash fix --- libethereum/EthereumHost.cpp | 53 ++++++++++++++++++++---------------- libethereum/EthereumHost.h | 3 ++ libethereum/EthereumPeer.cpp | 4 ++- libethereum/EthereumPeer.h | 3 ++ libp2p/Session.cpp | 22 +++++++++------ libp2p/Session.h | 2 +- 6 files changed, 53 insertions(+), 34 deletions(-) diff --git a/libethereum/EthereumHost.cpp b/libethereum/EthereumHost.cpp index 7d3bd9a92..1e623b605 100644 --- a/libethereum/EthereumHost.cpp +++ b/libethereum/EthereumHost.cpp @@ -131,37 +131,44 @@ void EthereumHost::maintainTransactions(TransactionQueue& _tq, h256 _currentHash { bool resendAll = (_currentHash != m_latestBlockSent); - for (auto it = m_incomingTransactions.begin(); it != m_incomingTransactions.end(); ++it) - if (_tq.import(&*it)) - {}//ret = true; // just putting a transaction in the queue isn't enough to change the state - it might have an invalid nonce... - else - m_transactionsSent.insert(sha3(*it)); // if we already had the transaction, then don't bother sending it on. - m_incomingTransactions.clear(); + { + lock_guard l(m_incomingLock); + for (auto it = m_incomingTransactions.begin(); it != m_incomingTransactions.end(); ++it) + if (_tq.import(&*it)) + {}//ret = true; // just putting a transaction in the queue isn't enough to change the state - it might have an invalid nonce... + else + 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. for (auto const& p: peers()) { auto ep = p->cap(); - bytes b; - unsigned n = 0; - for (auto const& i: _tq.transactions()) - if ((!m_transactionsSent.count(i.first) && !ep->m_knownTransactions.count(i.first)) || ep->m_requireTransactions || resendAll) + if (ep) + { + bytes b; + unsigned n = 0; + for (auto const& i: _tq.transactions()) + if ((!m_transactionsSent.count(i.first) && !ep->m_knownTransactions.count(i.first)) || ep->m_requireTransactions || resendAll) + { + b += i.second; + ++n; + m_transactionsSent.insert(i.first); + } + ep->clearKnownTransactions(); + + if (n) { - b += i.second; - ++n; - m_transactionsSent.insert(i.first); + RLPStream ts; + EthereumPeer::prep(ts); + ts.appendList(n + 1) << TransactionsPacket; + ts.appendRaw(b, n).swapOut(b); + seal(b); + ep->send(&b); } - if (n) - { - RLPStream ts; - EthereumPeer::prep(ts); - ts.appendList(n + 1) << TransactionsPacket; - ts.appendRaw(b, n).swapOut(b); - seal(b); - ep->send(&b); + ep->m_requireTransactions = false; } - ep->m_knownTransactions.clear(); - ep->m_requireTransactions = false; } } diff --git a/libethereum/EthereumHost.h b/libethereum/EthereumHost.h index bd0a5d0bc..6cd9332a2 100644 --- a/libethereum/EthereumHost.h +++ b/libethereum/EthereumHost.h @@ -75,6 +75,9 @@ private: /// Called when the peer can no longer provide us with any needed blocks. void noteDoneBlocks(); + /// Called by peer to add incoming transactions. + void addIncomingTransaction(bytes const& _bytes) { std::lock_guard l(m_incomingLock); m_incomingTransactions.push_back(_bytes); } + void maintainTransactions(TransactionQueue& _tq, h256 _currentBlock); void maintainBlocks(BlockQueue& _bq, h256 _currentBlock); diff --git a/libethereum/EthereumPeer.cpp b/libethereum/EthereumPeer.cpp index 9e72f8429..c961f6733 100644 --- a/libethereum/EthereumPeer.cpp +++ b/libethereum/EthereumPeer.cpp @@ -142,7 +142,9 @@ bool EthereumPeer::interpret(RLP const& _r) addRating(_r.itemCount() - 1); for (unsigned i = 1; i < _r.itemCount(); ++i) { - host()->m_incomingTransactions.push_back(_r[i].data().toBytes()); + host()->addIncomingTransaction(_r[i].data().toBytes()); + + lock_guard l(x_knownTransactions); m_knownTransactions.insert(sha3(_r[i].data())); } break; diff --git a/libethereum/EthereumPeer.h b/libethereum/EthereumPeer.h index 2e166b1d0..92eb475ec 100644 --- a/libethereum/EthereumPeer.h +++ b/libethereum/EthereumPeer.h @@ -67,6 +67,8 @@ private: void giveUpOnFetch(); + void clearKnownTransactions() { std::lock_guard l(x_knownTransactions); m_knownTransactions.clear(); } + unsigned m_protocolVersion; u256 m_networkId; @@ -82,6 +84,7 @@ private: std::set m_knownBlocks; std::set m_knownTransactions; + std::mutex x_knownTransactions; }; } diff --git a/libp2p/Session.cpp b/libp2p/Session.cpp index 5ed987cff..777717371 100644 --- a/libp2p/Session.cpp +++ b/libp2p/Session.cpp @@ -266,19 +266,19 @@ void Session::writeImpl(bytes& _buffer) if (!m_socket.is_open()) return; - lock_guard l(m_writeLock); - m_writeQueue.push_back(_buffer); - if (m_writeQueue.size() == 1) + bool doWrite = false; + { + lock_guard l(m_writeLock); + m_writeQueue.push_back(_buffer); + doWrite = (m_writeQueue.size() == 1); + } + + if (doWrite) write(); } void Session::write() { -// cerr << (void*)this << " write" << endl; - lock_guard l(m_writeLock); - if (m_writeQueue.empty()) - return; - const bytes& bytes = m_writeQueue[0]; auto self(shared_from_this()); ba::async_write(m_socket, ba::buffer(bytes), [this, self](boost::system::error_code ec, std::size_t /*length*/) @@ -290,12 +290,16 @@ void Session::write() { cwarn << "Error sending: " << ec.message(); dropped(); + return; } else { + lock_guard l(m_writeLock); m_writeQueue.pop_front(); - write(); + if (m_writeQueue.empty()) + return; } + write(); }); } diff --git a/libp2p/Session.h b/libp2p/Session.h index bc43934b3..7c3fc3732 100644 --- a/libp2p/Session.h +++ b/libp2p/Session.h @@ -86,7 +86,7 @@ private: Host* m_server; - std::recursive_mutex m_writeLock; + std::mutex m_writeLock; std::deque m_writeQueue; mutable bi::tcp::socket m_socket; ///< Mutable to ask for native_handle(). From 1e3bb187983276a907ea70c18025f8b356b6e92e Mon Sep 17 00:00:00 2001 From: caktux Date: Wed, 17 Sep 2014 18:02:46 -0400 Subject: [PATCH 182/223] add RPC stateAt --- eth/EthStubServer.cpp | 5 +++++ eth/EthStubServer.h | 1 + eth/abstractethstubserver.h | 7 +++++++ eth/eth.js | 18 ++++++++++++++---- 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/eth/EthStubServer.cpp b/eth/EthStubServer.cpp index 86bb1ba58..dd82541de 100644 --- a/eth/EthStubServer.cpp +++ b/eth/EthStubServer.cpp @@ -138,6 +138,11 @@ std::string EthStubServer::storageAt(const std::string& _a, const std::string& x return toJS(m_client.stateAt(jsToAddress(_a), jsToU256(x), 0)); } +std::string EthStubServer::stateAt(const std::string& _a, const std::string& x, const std::string& b) +{ + return toJS(m_client.stateAt(jsToAddress(_a), jsToU256(x), std::atol(b.c_str()))); +} + Json::Value EthStubServer::transact(const std::string& _aDest, const std::string& _bData, const std::string& _sec, const std::string& _xGas, const std::string& _xGasPrice, const std::string& _xValue) { m_client.transact(jsToSecret(_sec), jsToU256(_xValue), jsToAddress(_aDest), jsToBytes(_bData), jsToU256(_xGas), jsToU256(_xGasPrice)); diff --git a/eth/EthStubServer.h b/eth/EthStubServer.h index 28af916a2..8046eb4ca 100644 --- a/eth/EthStubServer.h +++ b/eth/EthStubServer.h @@ -49,6 +49,7 @@ public: virtual Json::Value keys(); virtual int peerCount(); virtual std::string storageAt(const std::string& a, const std::string& x); + virtual std::string stateAt(const std::string& a, const std::string& x, const std::string& b); virtual Json::Value transact(const std::string& aDest, const std::string& bData, const std::string& sec, const std::string& xGas, const std::string& xGasPrice, const std::string& xValue); virtual std::string txCountAt(const std::string& a); virtual std::string secretToAddress(const std::string& a); diff --git a/eth/abstractethstubserver.h b/eth/abstractethstubserver.h index 940e4c809..e6e21fcf9 100644 --- a/eth/abstractethstubserver.h +++ b/eth/abstractethstubserver.h @@ -30,6 +30,7 @@ class AbstractEthStubServer : public jsonrpc::AbstractServerbindAndAddMethod(new jsonrpc::Procedure("procedures", jsonrpc::PARAMS_BY_NAME, jsonrpc::JSON_ARRAY, NULL), &AbstractEthStubServer::proceduresI); this->bindAndAddMethod(new jsonrpc::Procedure("secretToAddress", jsonrpc::PARAMS_BY_NAME, jsonrpc::JSON_STRING, "a",jsonrpc::JSON_STRING, NULL), &AbstractEthStubServer::secretToAddressI); this->bindAndAddMethod(new jsonrpc::Procedure("storageAt", jsonrpc::PARAMS_BY_NAME, jsonrpc::JSON_STRING, "a",jsonrpc::JSON_STRING,"x",jsonrpc::JSON_STRING, NULL), &AbstractEthStubServer::storageAtI); + this->bindAndAddMethod(new jsonrpc::Procedure("stateAt", jsonrpc::PARAMS_BY_NAME, jsonrpc::JSON_STRING, "a",jsonrpc::JSON_STRING,"x",jsonrpc::JSON_STRING,"b",jsonrpc::JSON_STRING, NULL), &AbstractEthStubServer::stateAtI); this->bindAndAddMethod(new jsonrpc::Procedure("transact", jsonrpc::PARAMS_BY_NAME, jsonrpc::JSON_OBJECT, "aDest",jsonrpc::JSON_STRING,"bData",jsonrpc::JSON_STRING,"sec",jsonrpc::JSON_STRING,"xGas",jsonrpc::JSON_STRING,"xGasPrice",jsonrpc::JSON_STRING,"xValue",jsonrpc::JSON_STRING, NULL), &AbstractEthStubServer::transactI); this->bindAndAddMethod(new jsonrpc::Procedure("txCountAt", jsonrpc::PARAMS_BY_NAME, jsonrpc::JSON_STRING, "a",jsonrpc::JSON_STRING, NULL), &AbstractEthStubServer::txCountAtI); @@ -120,6 +121,11 @@ class AbstractEthStubServer : public jsonrpc::AbstractServerstorageAt(request["a"].asString(), request["x"].asString()); } + inline virtual void stateAtI(const Json::Value& request, Json::Value& response) + { + response = this->stateAt(request["a"].asString(), request["x"].asString(), request["b"].asString()); + } + inline virtual void transactI(const Json::Value& request, Json::Value& response) { response = this->transact(request["aDest"].asString(), request["bData"].asString(), request["sec"].asString(), request["xGas"].asString(), request["xGasPrice"].asString(), request["xValue"].asString()); @@ -148,6 +154,7 @@ class AbstractEthStubServer : public jsonrpc::AbstractServer Date: Wed, 17 Sep 2014 18:12:15 -0500 Subject: [PATCH 183/223] Download view as will be. Fixes. --- alethzero/DownloadView.cpp | 40 ++++++++++++ alethzero/DownloadView.h | 49 ++++++++++++++ alethzero/Main.ui | 39 ++++++++++++ eth/main.cpp | 2 +- libethereum/EthereumHost.cpp | 17 ++--- libethereum/EthereumHost.h | 120 +++++++++++++++++++++++++++++++++++ libethereum/EthereumPeer.cpp | 4 ++ libethereum/Executive.cpp | 1 + libevm/VM.h | 1 - 9 files changed, 263 insertions(+), 10 deletions(-) create mode 100644 alethzero/DownloadView.cpp create mode 100644 alethzero/DownloadView.h diff --git a/alethzero/DownloadView.cpp b/alethzero/DownloadView.cpp new file mode 100644 index 000000000..0fe5e1414 --- /dev/null +++ b/alethzero/DownloadView.cpp @@ -0,0 +1,40 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file DownloadView.cpp + * @author Gav Wood + * @date 2014 + */ + +#include "DownloadView.h" + +#include +#include +#include +#include "Grapher.h" + +using namespace std; +using namespace dev; +using namespace dev::eth; + +DownloadView::DownloadView(QWidget* _p): QWidget(_p) +{ +} + +void DownloadView::paintEvent(QPaintEvent*) +{ + QPainter p(this); +} diff --git a/alethzero/DownloadView.h b/alethzero/DownloadView.h new file mode 100644 index 000000000..ea3e05b8f --- /dev/null +++ b/alethzero/DownloadView.h @@ -0,0 +1,49 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file DownloadView.h + * @author Gav Wood + * @date 2014 + */ + +#pragma once + +#ifdef Q_MOC_RUN +#define BOOST_MPL_IF_HPP_INCLUDED +#endif + +#include +#include +#ifndef Q_MOC_RUN +#include +#endif + +namespace dev { namespace eth { +struct MineInfo; +}} + +class DownloadView: public QWidget +{ + Q_OBJECT + +public: + DownloadView(QWidget* _p = nullptr); + +protected: + virtual void paintEvent(QPaintEvent*); + +private: +}; diff --git a/alethzero/Main.ui b/alethzero/Main.ui index aa8a43f1c..7ae434ff5 100644 --- a/alethzero/Main.ui +++ b/alethzero/Main.ui @@ -1432,6 +1432,39 @@ font-size: 14pt + + + QDockWidget::DockWidgetFeatureMask + + + Blockchain Download + + + 2 + + + + + 0 + + + 0 + + + 0 + + + 0 + + + 0 + + + + + + + &Quit @@ -1712,6 +1745,12 @@ font-size: 14pt
    MiningView.h
    1 + + DownloadView + QWidget +
    DownloadView.h
    + 1 +
    destination diff --git a/eth/main.cpp b/eth/main.cpp index 69a555904..cebec34e9 100644 --- a/eth/main.cpp +++ b/eth/main.cpp @@ -747,7 +747,7 @@ int main(int argc, char** argv) c.startMining(); while (true) { - if (c.blockChain().details().number - n == mining) + if (c.isMining() && c.blockChain().details().number - n == mining) c.stopMining(); this_thread::sleep_for(chrono::milliseconds(100)); } diff --git a/libethereum/EthereumHost.cpp b/libethereum/EthereumHost.cpp index 2b88fc768..03435ad4f 100644 --- a/libethereum/EthereumHost.cpp +++ b/libethereum/EthereumHost.cpp @@ -97,7 +97,6 @@ bool EthereumHost::ensureInitialised(TransactionQueue& _tq) void EthereumHost::noteDoneBlocks() { - clog(NetNote) << "Peer given up on blocks fetch."; if (m_blocksOnWay.empty()) { // Done our chain-get. @@ -135,13 +134,15 @@ void EthereumHost::doWork() void EthereumHost::maintainTransactions(TransactionQueue& _tq, h256 _currentHash) { bool resendAll = (_currentHash != m_latestBlockSent); - - for (auto it = m_incomingTransactions.begin(); it != m_incomingTransactions.end(); ++it) - if (_tq.import(&*it)) - {}//ret = true; // just putting a transaction in the queue isn't enough to change the state - it might have an invalid nonce... - else - m_transactionsSent.insert(sha3(*it)); // if we already had the transaction, then don't bother sending it on. - m_incomingTransactions.clear(); + { + lock_guard l(m_incomingLock); + for (auto it = m_incomingTransactions.begin(); it != m_incomingTransactions.end(); ++it) + if (_tq.import(&*it)) + {}//ret = true; // just putting a transaction in the queue isn't enough to change the state - it might have an invalid nonce... + else + 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. for (auto const& p: peers()) diff --git a/libethereum/EthereumHost.h b/libethereum/EthereumHost.h index 7c6b4b865..7e2f38230 100644 --- a/libethereum/EthereumHost.h +++ b/libethereum/EthereumHost.h @@ -46,6 +46,126 @@ namespace eth class TransactionQueue; class BlockQueue; +using UnsignedRange = std::pair; +using UnsignedRanges = std::vector; + +class RangeMask +{ +public: + RangeMask() {} + RangeMask(unsigned _begin, unsigned _end): m_ranges({{_begin, _end}}) {} + + RangeMask& operator+=(RangeMask const& _m) + { + for (auto const& i: _m.m_ranges) + operator+=(i); + return *this; + } + RangeMask& operator+=(UnsignedRange const& _m) + { + for (auto i = _m.first; i < _m.second;) + { + // for each number, we find the element equal or next lower. this must contain the value. + auto it = m_ranges.lower_bound(i); + auto uit = m_ranges.upper_bound(i + 1); + if (it == m_ranges.end() || it->second < i) + // lower range is too low to merge. + // if the next higher range is too high. + if (uit == m_ranges.end() || uit->first > _m.second) + { + // just create a new range + m_ranges[i] = _m.second; + break; + } + else + { + if (uit->first == i) + // move i to end of range + i = uit->second; + else + { + // merge with the next higher range + // move i to end of range + i = m_ranges[i] = uit->second; + i = uit->second; + m_ranges.erase(uit); + } + } + else if (it->second == i) + { + // if the next higher range is too high. + if (uit == m_ranges.end() || uit->first > _m.second) + { + // merge with the next lower range + m_ranges[it->first] = _m.second; + break; + } + else + { + // merge with both next lower & next higher. + i = m_ranges[it->first] = uit->second; + m_ranges.erase(uit); + } + } + else + i = it->second; + } + return *this; + } + + RangeMask& operator+=(unsigned _i) + { + return operator+=(UnsignedRange(_i, _i + 1)); + } + + bool contains(unsigned _i) const + { + auto it = m_ranges.lower_bound(_i); + return it != m_ranges.end() && it->first <= _i && it->second > _i; + } + +private: + std::map m_ranges; +}; + +#if 0 +class DownloadSub +{ + friend class DownloadMan; + +public: + h256s nextFetch(); + void noteBlock(h256 _hash, bytesConstRef _data); + +private: + void resetFetch(); // Called by DownloadMan when we need to reset the download. + + DownloadMan* m_man; + + Mutex m_fetch; + h256s m_fetching; + h256s m_activeGet; + bool m_killFetch; + RangeMask m_attempted; +}; + +class DownloadMan +{ + friend class DownloadSub; + +public: + void resetToChain(h256s const& _chain); + +private: + void cancelFetch(DownloadSub* ); + void noteBlock(h256 _hash, bytesConstRef _data); + + h256s m_chain; + RangeMask m_complete; + std::map m_fetching; +}; +#endif + /** * @brief The EthereumHost class * @warning None of this is thread-safe. You have been warned. diff --git a/libethereum/EthereumPeer.cpp b/libethereum/EthereumPeer.cpp index 238bc3d8d..6990e4f59 100644 --- a/libethereum/EthereumPeer.cpp +++ b/libethereum/EthereumPeer.cpp @@ -148,14 +148,17 @@ bool EthereumPeer::interpret(RLP const& _r) break; } case TransactionsPacket: + { clogS(NetMessageSummary) << "Transactions (" << dec << (_r.itemCount() - 1) << "entries)"; addRating(_r.itemCount() - 1); + lock_guard l(host()->m_incomingLock); for (unsigned i = 1; i < _r.itemCount(); ++i) { host()->m_incomingTransactions.push_back(_r[i].data().toBytes()); m_knownTransactions.insert(sha3(_r[i].data())); } break; + } case GetBlockHashesPacket: { h256 later = _r[1].toHash(); @@ -306,6 +309,7 @@ void EthereumPeer::continueGettingChain() } else { + if (m_failedBlocks.size()) clogS(NetMessageSummary) << "No blocks left to get. Peer doesn't seem to have" << m_failedBlocks.size() << "of our needed blocks."; host()->noteDoneBlocks(); } diff --git a/libethereum/Executive.cpp b/libethereum/Executive.cpp index e8c1fd1cb..a6b57a05c 100644 --- a/libethereum/Executive.cpp +++ b/libethereum/Executive.cpp @@ -187,6 +187,7 @@ bool Executive::go(OnOpFunc const& _onOp) catch (VMException const& _e) { clog(StateChat) << "VM Exception: " << _e.description(); + m_endGas = m_vm->gas(); } catch (Exception const& _e) { diff --git a/libevm/VM.h b/libevm/VM.h index e7d218ec6..801779e50 100644 --- a/libevm/VM.h +++ b/libevm/VM.h @@ -41,7 +41,6 @@ class BreakPointHit: public VMException {}; class BadInstruction: public VMException {}; class OutOfGas: public VMException {}; class StackTooSmall: public VMException { public: StackTooSmall(u256 _req, u256 _got): req(_req), got(_got) {} u256 req; u256 got; }; -class OperandOutOfRange: public VMException { public: OperandOutOfRange(u256 _min, u256 _max, u256 _got): mn(_min), mx(_max), got(_got) {} u256 mn; u256 mx; u256 got; }; // Convert from a 256-bit integer stack/memory entry into a 160-bit Address hash. // Currently we just pull out the right (low-order in BE) 160-bits. From 5820373d73d0440c9fa5fa15cb05cb2487149987 Mon Sep 17 00:00:00 2001 From: caktux Date: Wed, 17 Sep 2014 20:10:20 -0400 Subject: [PATCH 184/223] use s for stateAt block number param to prevent conflict with bData and such, some style fixes --- eth/EthStubServer.cpp | 4 ++-- eth/EthStubServer.h | 2 +- eth/abstractethstubserver.h | 4 ++-- eth/eth.js | 18 +++++++----------- 4 files changed, 12 insertions(+), 16 deletions(-) diff --git a/eth/EthStubServer.cpp b/eth/EthStubServer.cpp index dd82541de..c82d61103 100644 --- a/eth/EthStubServer.cpp +++ b/eth/EthStubServer.cpp @@ -138,9 +138,9 @@ std::string EthStubServer::storageAt(const std::string& _a, const std::string& x return toJS(m_client.stateAt(jsToAddress(_a), jsToU256(x), 0)); } -std::string EthStubServer::stateAt(const std::string& _a, const std::string& x, const std::string& b) +std::string EthStubServer::stateAt(const std::string& _a, const std::string& x, const std::string& s) { - return toJS(m_client.stateAt(jsToAddress(_a), jsToU256(x), std::atol(b.c_str()))); + return toJS(m_client.stateAt(jsToAddress(_a), jsToU256(x), std::atol(s.c_str()))); } Json::Value EthStubServer::transact(const std::string& _aDest, const std::string& _bData, const std::string& _sec, const std::string& _xGas, const std::string& _xGasPrice, const std::string& _xValue) diff --git a/eth/EthStubServer.h b/eth/EthStubServer.h index 8046eb4ca..1034dbc5e 100644 --- a/eth/EthStubServer.h +++ b/eth/EthStubServer.h @@ -49,7 +49,7 @@ public: virtual Json::Value keys(); virtual int peerCount(); virtual std::string storageAt(const std::string& a, const std::string& x); - virtual std::string stateAt(const std::string& a, const std::string& x, const std::string& b); + virtual std::string stateAt(const std::string& a, const std::string& x, const std::string& s); virtual Json::Value transact(const std::string& aDest, const std::string& bData, const std::string& sec, const std::string& xGas, const std::string& xGasPrice, const std::string& xValue); virtual std::string txCountAt(const std::string& a); virtual std::string secretToAddress(const std::string& a); diff --git a/eth/abstractethstubserver.h b/eth/abstractethstubserver.h index e6e21fcf9..7f110513d 100644 --- a/eth/abstractethstubserver.h +++ b/eth/abstractethstubserver.h @@ -30,7 +30,7 @@ class AbstractEthStubServer : public jsonrpc::AbstractServerbindAndAddMethod(new jsonrpc::Procedure("procedures", jsonrpc::PARAMS_BY_NAME, jsonrpc::JSON_ARRAY, NULL), &AbstractEthStubServer::proceduresI); this->bindAndAddMethod(new jsonrpc::Procedure("secretToAddress", jsonrpc::PARAMS_BY_NAME, jsonrpc::JSON_STRING, "a",jsonrpc::JSON_STRING, NULL), &AbstractEthStubServer::secretToAddressI); this->bindAndAddMethod(new jsonrpc::Procedure("storageAt", jsonrpc::PARAMS_BY_NAME, jsonrpc::JSON_STRING, "a",jsonrpc::JSON_STRING,"x",jsonrpc::JSON_STRING, NULL), &AbstractEthStubServer::storageAtI); - this->bindAndAddMethod(new jsonrpc::Procedure("stateAt", jsonrpc::PARAMS_BY_NAME, jsonrpc::JSON_STRING, "a",jsonrpc::JSON_STRING,"x",jsonrpc::JSON_STRING,"b",jsonrpc::JSON_STRING, NULL), &AbstractEthStubServer::stateAtI); + this->bindAndAddMethod(new jsonrpc::Procedure("stateAt", jsonrpc::PARAMS_BY_NAME, jsonrpc::JSON_STRING, "a",jsonrpc::JSON_STRING,"x",jsonrpc::JSON_STRING,"s",jsonrpc::JSON_STRING, NULL), &AbstractEthStubServer::stateAtI); this->bindAndAddMethod(new jsonrpc::Procedure("transact", jsonrpc::PARAMS_BY_NAME, jsonrpc::JSON_OBJECT, "aDest",jsonrpc::JSON_STRING,"bData",jsonrpc::JSON_STRING,"sec",jsonrpc::JSON_STRING,"xGas",jsonrpc::JSON_STRING,"xGasPrice",jsonrpc::JSON_STRING,"xValue",jsonrpc::JSON_STRING, NULL), &AbstractEthStubServer::transactI); this->bindAndAddMethod(new jsonrpc::Procedure("txCountAt", jsonrpc::PARAMS_BY_NAME, jsonrpc::JSON_STRING, "a",jsonrpc::JSON_STRING, NULL), &AbstractEthStubServer::txCountAtI); @@ -123,7 +123,7 @@ class AbstractEthStubServer : public jsonrpc::AbstractServerstateAt(request["a"].asString(), request["x"].asString(), request["b"].asString()); + response = this->stateAt(request["a"].asString(), request["x"].asString(), request["s"].asString()); } inline virtual void transactI(const Json::Value& request, Json::Value& response) diff --git a/eth/eth.js b/eth/eth.js index d507ba0cc..791386a0b 100644 --- a/eth/eth.js +++ b/eth/eth.js @@ -25,7 +25,7 @@ var spec = [ { "method": "peerCount", "params": null, "order": [], "returns" : 0 }, { "method": "balanceAt", "params": { "a": "" }, "order": ["a"], "returns" : "" }, { "method": "storageAt", "params": { "a": "", "x": "" }, "order": ["a", "x"], "returns" : "" }, - { "method": "stateAt", "params": { "a": "", "x": "", "b": "" }, "order": ["a", "x", "b"], "returns" : "" }, + { "method": "stateAt", "params": { "a": "", "x": "", "s": "" }, "order": ["a", "x", "s"], "returns" : "" }, { "method": "txCountAt", "params": { "a": "" },"order": ["a"], "returns" : "" }, { "method": "isContractAt", "params": { "a": "" }, "order": ["a"], "returns" : false }, { "method": "create", "params": { "sec": "", "xEndowment": "", "bCode": "", "xGas": "", "xGasPrice": "" }, "order": ["sec", "xEndowment", "bCode", "xGas", "xGasPrice"] , "returns": "" }, @@ -72,17 +72,13 @@ window.eth = (function ethScope() { var am = "get" + m.slice(0, 1).toUpperCase() + m.slice(1); var getParams = function(a) { var p = s.params ? {} : null; - for (j in s.order) { - if (m == "stateAt") { - if (typeof(a[j]) == 'undefined') { - p[s.order[j]] = "0"; - } - else - p[s.order[j]] = String(a[j]); - } + if (m == "stateAt") + if (a.length == 2) + a[2] = "0"; else - p[s.order[j]] = a[j]; - } + a[2] = String(a[2]); + for (j in s.order) + p[s.order[j]] = (s.order[j][0] === "b") ? a[j].unbin() : a[j]; return p }; if (m == "create" || m == "transact") From 6a2b875637f31058318ca955d637d9a815417179 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 18 Sep 2014 17:58:27 -0500 Subject: [PATCH 185/223] Fix for io service (thanks, alex!) --- libethereum/Client.cpp | 11 +++++++++++ libp2p/Host.cpp | 2 ++ 2 files changed, 13 insertions(+) diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index 6527d1eb1..f0513716b 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -191,6 +191,17 @@ void Client::appendFromNewBlock(h256 _block, h256Set& o_changed) const o_changed.insert(i.first); } +void Client::setForceMining(bool _enable) +{ + m_forceMining = _enable; + if (!m_host.lock()) + { + ReadGuard l(x_miners); + for (auto& m: m_miners) + m.noteStateChange(); + } +} + void Client::setMiningThreads(unsigned _threads) { stopMining(); diff --git a/libp2p/Host.cpp b/libp2p/Host.cpp index 7e9589646..15727f9c1 100644 --- a/libp2p/Host.cpp +++ b/libp2p/Host.cpp @@ -129,6 +129,8 @@ void Host::stop() if (m_socket.is_open()) m_socket.close(); disconnectPeers(); + + m_ioService.reset(); } unsigned Host::protocolVersion() const From 0d3f298e4532e378cb8b190fd130d8373ff435ab Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 19 Sep 2014 12:02:34 -0500 Subject: [PATCH 186/223] Blocks come down in order (well... unless a peer bugs out). Peer hash-chains downloaded one-at-once. KillChain works again. Local networking option. Don't resend blocks during sync. --- alethzero/Main.ui | 9 +++ alethzero/MainWin.cpp | 5 +- eth/main.cpp | 12 ++- libdevcore/Common.cpp | 2 +- libethcore/CommonEth.cpp | 2 +- libethereum/BlockChain.cpp | 47 +++++++++-- libethereum/BlockChain.h | 8 ++ libethereum/BlockQueue.cpp | 9 ++- libethereum/BlockQueue.h | 5 +- libethereum/Client.cpp | 30 ++++++- libethereum/Client.h | 4 +- libethereum/CommonNet.h | 16 +++- libethereum/EthereumHost.cpp | 142 +++++++++++++++++++++++---------- libethereum/EthereumHost.h | 12 ++- libethereum/EthereumPeer.cpp | 47 +++++++++-- libethereum/EthereumPeer.h | 4 + libethereum/State.cpp | 5 ++ libethereum/State.h | 2 + libethereum/TransactionQueue.h | 2 + libwebthree/WebThree.h | 3 +- 20 files changed, 291 insertions(+), 75 deletions(-) diff --git a/alethzero/Main.ui b/alethzero/Main.ui index 7ae434ff5..dbc4bb144 100644 --- a/alethzero/Main.ui +++ b/alethzero/Main.ui @@ -132,6 +132,7 @@ + @@ -1731,6 +1732,14 @@ font-size: 14pt
    Reserved Debug 1 + + + true + + + Enable Local Addresses + + diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index 37c33de02..5842664b3 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -192,7 +192,7 @@ Main::~Main() dev::p2p::NetworkPreferences Main::netPrefs() const { - return NetworkPreferences(ui->port->value(), ui->forceAddress->text().toStdString(), ui->upnp->isChecked(), false); + return NetworkPreferences(ui->port->value(), ui->forceAddress->text().toStdString(), ui->upnp->isChecked(), ui->localNetworking->isChecked()); } void Main::onKeysChanged() @@ -494,6 +494,7 @@ void Main::writeSettings() s.setValue("upnp", ui->upnp->isChecked()); s.setValue("forceAddress", ui->forceAddress->text()); s.setValue("usePast", ui->usePast->isChecked()); + s.setValue("localNetworking", ui->localNetworking->isChecked()); s.setValue("forceMining", ui->forceMining->isChecked()); s.setValue("paranoia", ui->paranoia->isChecked()); s.setValue("showAll", ui->showAll->isChecked()); @@ -543,6 +544,7 @@ void Main::readSettings(bool _skipGeometry) ui->upnp->setChecked(s.value("upnp", true).toBool()); ui->forceAddress->setText(s.value("forceAddress", "").toString()); ui->usePast->setChecked(s.value("usePast", true).toBool()); + ui->localNetworking->setChecked(s.value("localNetworking", true).toBool()); ui->forceMining->setChecked(s.value("forceMining", false).toBool()); on_forceMining_triggered(); ui->paranoia->setChecked(s.value("paranoia", false).toBool()); @@ -1419,6 +1421,7 @@ void Main::on_killBlockchain_triggered() writeSettings(); ui->mine->setChecked(false); ui->net->setChecked(false); + web3()->stopNetwork(); ethereum()->killChain(); m_ethereum->setClient(ethereum()); readSettings(true); diff --git a/eth/main.cpp b/eth/main.cpp index cebec34e9..3ea02f744 100644 --- a/eth/main.cpp +++ b/eth/main.cpp @@ -112,7 +112,8 @@ void help() << " -l,--listen Listen on the given port for incoming connected (default: 30303)." << endl << " -m,--mining Enable mining, optionally for a specified number of blocks (Default: off)" << endl << " -n,--upnp Use upnp for NAT (default: on)." << endl - << " -o,--mode Start a full node or a peer node (Default: full)." << endl + << " -L,--local-networking Use peers whose addresses are local." << endl + << " -o,--mode Start a full node or a peer node (Default: full)." << endl << " -p,--port Connect to remote port (default: 30303)." << endl << " -r,--remote Connect to remote host (default: none)." << endl << " -s,--secret Set the secret key for use with send command (default: auto)." << endl @@ -188,6 +189,7 @@ int main(int argc, char** argv) #endif string publicIP; bool upnp = true; + bool useLocal = false; bool forceMining = false; string clientName; @@ -233,10 +235,12 @@ int main(int argc, char** argv) upnp = false; else { - cerr << "Invalid UPnP option: " << m << endl; + cerr << "Invalid -n/--upnp option: " << m << endl; return -1; } } + else if (arg == "-L" || arg == "--local-networking") + useLocal = true; else if ((arg == "-c" || arg == "--client-name") && i + 1 < argc) clientName = argv[++i]; else if ((arg == "-a" || arg == "--address" || arg == "--coinbase-address") && i + 1 < argc) @@ -256,7 +260,7 @@ int main(int argc, char** argv) mining = i; else { - cerr << "Unknown mining option: " << m << endl; + cerr << "Unknown -m/--mining option: " << m << endl; return -1; } } @@ -300,7 +304,7 @@ int main(int argc, char** argv) cout << credits(); - NetworkPreferences netPrefs(listenPort, publicIP, upnp, false); + NetworkPreferences netPrefs(listenPort, publicIP, upnp, useLocal); dev::WebThreeDirect web3("Ethereum(++)/" + clientName + "v" + dev::Version + "/" DEV_QUOTED(ETH_BUILD_TYPE) "/" DEV_QUOTED(ETH_BUILD_PLATFORM), dbPath, false, mode == NodeMode::Full ? set{"eth", "shh"} : set{}, netPrefs); web3.setIdealPeerCount(peers); eth::Client& c = *web3.ethereum(); diff --git a/libdevcore/Common.cpp b/libdevcore/Common.cpp index 325ba274e..ad779e35d 100644 --- a/libdevcore/Common.cpp +++ b/libdevcore/Common.cpp @@ -27,7 +27,7 @@ using namespace dev; namespace dev { -char const* Version = "0.6.8b"; +char const* Version = "0.6.8c"; } diff --git a/libethcore/CommonEth.cpp b/libethcore/CommonEth.cpp index f96fb5480..eed8b4bb9 100644 --- a/libethcore/CommonEth.cpp +++ b/libethcore/CommonEth.cpp @@ -34,7 +34,7 @@ namespace dev namespace eth { -const unsigned c_protocolVersion = 32; +const unsigned c_protocolVersion = 33; const unsigned c_databaseVersion = 1; static const vector> g_units = diff --git a/libethereum/BlockChain.cpp b/libethereum/BlockChain.cpp index 716add2ae..181f4fd64 100644 --- a/libethereum/BlockChain.cpp +++ b/libethereum/BlockChain.cpp @@ -108,6 +108,20 @@ bytes BlockChain::createGenesisBlock() } BlockChain::BlockChain(std::string _path, bool _killExisting) +{ + // Initialise with the genesis as the last block on the longest chain. + m_genesisHash = BlockChain::genesis().hash; + m_genesisBlock = BlockChain::createGenesisBlock(); + + open(_path, _killExisting); +} + +BlockChain::~BlockChain() +{ + close(); +} + +void BlockChain::open(std::string _path, bool _killExisting) { if (_path.empty()) _path = Defaults::get()->m_dbPath; @@ -127,10 +141,6 @@ BlockChain::BlockChain(std::string _path, bool _killExisting) if (!m_extrasDB) throw DatabaseAlreadyOpen(); - // Initialise with the genesis as the last block on the longest chain. - m_genesisHash = BlockChain::genesis().hash; - m_genesisBlock = BlockChain::createGenesisBlock(); - if (!details(m_genesisHash)) { // Insert details of genesis block. @@ -150,11 +160,16 @@ BlockChain::BlockChain(std::string _path, bool _killExisting) cnote << "Opened blockchain DB. Latest: " << currentHash(); } -BlockChain::~BlockChain() +void BlockChain::close() { cnote << "Closing blockchain DB"; delete m_extrasDB; delete m_db; + m_lastBlockHash = m_genesisHash; + m_details.clear(); + m_blooms.clear(); + m_traces.clear(); + m_cache.clear(); } template @@ -245,21 +260,23 @@ h256s BlockChain::import(bytes const& _block, OverlayDB const& _db) auto newHash = BlockInfo::headerHash(_block); // Check block doesn't already exist first! - if (details(newHash)) + if (isKnown(newHash)) { clog(BlockChainNote) << newHash << ": Not new."; throw AlreadyHaveBlock(); } // Work out its number as the parent's number + 1 - auto pd = details(bi.parentHash); - if (!pd) + if (!isKnown(bi.parentHash)) { clog(BlockChainNote) << newHash << ": Unknown parent " << bi.parentHash; // 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(); } + auto pd = details(bi.parentHash); + assert(pd); + // Check it's not crazy if (bi.timestamp > (u256)time(0)) { @@ -432,6 +449,20 @@ h256Set BlockChain::allUnclesFrom(h256 _parent) const return ret; } +bool BlockChain::isKnown(h256 _hash) const +{ + if (_hash == m_genesisHash) + return true; + { + ReadGuard l(x_cache); + if (m_cache.count(_hash)) + return true; + } + string d; + m_db->Get(m_readOptions, ldb::Slice((char const*)&_hash, 32), &d); + return d.size(); +} + bytes BlockChain::block(h256 _hash) const { if (_hash == m_genesisHash) diff --git a/libethereum/BlockChain.h b/libethereum/BlockChain.h index c0b3988a9..cf33a5104 100644 --- a/libethereum/BlockChain.h +++ b/libethereum/BlockChain.h @@ -70,6 +70,8 @@ public: BlockChain(std::string _path, bool _killExisting = false); ~BlockChain(); + void reopen(std::string _path, bool _killExisting = false) { close(); open(_path, _killExisting); } + /// (Potentially) renders invalid existing bytesConstRef returned by lastBlock. /// To be called from main loop every 100ms or so. void process(); @@ -85,6 +87,9 @@ public: /// @returns the block hashes of any blocks that came into/went out of the canonical block chain. h256s import(bytes const& _block, OverlayDB const& _stateDB); + /// Returns true if the given block is known (though not necessarily a part of the canon chain). + bool isKnown(h256 _hash) const; + /// Get the familial details concerning a block (or the most recent mined if none given). Thread-safe. BlockDetails details(h256 _hash) const { return queryExtras(_hash, m_details, x_details, NullBlockDetails); } BlockDetails details() const { return details(currentHash()); } @@ -143,6 +148,9 @@ public: h256s treeRoute(h256 _from, h256 _to, h256* o_common = nullptr, bool _pre = true, bool _post = true) const; private: + void open(std::string _path, bool _killExisting = false); + void close(); + template T queryExtras(h256 _h, std::map& _m, boost::shared_mutex& _x, T const& _n) const { { diff --git a/libethereum/BlockQueue.cpp b/libethereum/BlockQueue.cpp index 21aeb5a72..a845965d5 100644 --- a/libethereum/BlockQueue.cpp +++ b/libethereum/BlockQueue.cpp @@ -74,21 +74,24 @@ bool BlockQueue::import(bytesConstRef _block, BlockChain const& _bc) // Check it's not in the future if (bi.timestamp > (u256)time(0)) + { m_future.insert(make_pair((unsigned)bi.timestamp, _block.toBytes())); + cblockq << "OK - queued for future."; + } else { // We now know it. - if (!m_readySet.count(bi.parentHash) && !m_drainingSet.count(bi.parentHash) && !_bc.details(bi.parentHash)) + if (!m_readySet.count(bi.parentHash) && !m_drainingSet.count(bi.parentHash) && !_bc.isKnown(bi.parentHash)) { // We don't know the parent (yet) - queue it up for later. It'll get resent to us if we find out about its ancestry later on. -// cnote << "OK - queued for future."; + cblockq << "OK - queued as unknown parent:" << bi.parentHash.abridged(); m_unknown.insert(make_pair(bi.parentHash, make_pair(h, _block.toBytes()))); m_unknownSet.insert(h); } else { // If valid, append to blocks. -// cnote << "OK - ready for chain insertion."; + cblockq << "OK - ready for chain insertion."; m_ready.push_back(_block.toBytes()); m_readySet.insert(h); diff --git a/libethereum/BlockQueue.h b/libethereum/BlockQueue.h index 5a4a86ad6..20bc4ce59 100644 --- a/libethereum/BlockQueue.h +++ b/libethereum/BlockQueue.h @@ -34,7 +34,7 @@ namespace eth class BlockChain; -struct BlockQueueChannel: public LogChannel { static const char* name() { return "[]Q"; } static const int verbosity = 7; }; +struct BlockQueueChannel: public LogChannel { static const char* name() { return "[]Q"; } static const int verbosity = 4; }; #define cblockq dev::LogOutputStream() /** @@ -64,6 +64,9 @@ public: /// Get information on the items queued. std::pair items() const { ReadGuard l(m_lock); return std::make_pair(m_ready.size(), m_unknown.size()); } + /// Clear everything. + void clear() { WriteGuard l(m_lock); m_readySet.clear(); m_drainingSet.clear(); m_ready.clear(); m_unknownSet.clear(); m_unknown.clear(); m_future.clear(); } + private: void noteReadyWithoutWriteGuard(h256 _b); void notePresentWithoutWriteGuard(bytesConstRef _block); diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index f0513716b..d64c9fafb 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -99,7 +99,35 @@ void Client::flushTransactions() void Client::killChain() { - // TODO + bool wasMining = isMining(); + if (wasMining) + stopMining(); + stopWorking(); + + m_tq.clear(); + m_bq.clear(); + m_miners.clear(); + m_preMine = State(); + m_postMine = State(); + + { + WriteGuard l(x_stateDB); + m_stateDB = OverlayDB(); + m_stateDB = State::openDB(Defaults::dbPath(), true); + } + m_bc.reopen(Defaults::dbPath(), true); + + m_preMine = State(Address(), m_stateDB); + m_postMine = State(Address(), m_stateDB); + + if (auto h = m_host.lock()) + h->reset(); + + doWork(); + + startWorking(); + if (wasMining) + startMining(); } void Client::clearPending() diff --git a/libethereum/Client.h b/libethereum/Client.h index 2c29175c2..6415defb0 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -271,13 +271,13 @@ private: BlockChain m_bc; ///< Maintains block database. TransactionQueue m_tq; ///< Maintains a list of incoming transactions not yet in a block on the blockchain. BlockQueue m_bq; ///< Maintains a list of incoming blocks not yet on the blockchain (to be imported). - // TODO: remove in favour of copying m_stateDB as required and thread-safing/copying State. Have a good think about what state objects there should be. Probably want 4 (pre, post, mining, user-visible). + mutable boost::shared_mutex x_stateDB; ///< Lock on the state DB, effectively a lock on m_postMine. OverlayDB m_stateDB; ///< Acts as the central point for the state database, so multiple States can share it. State m_preMine; ///< The present state of the client. State m_postMine; ///< The state of the client which we're mining (i.e. it'll have all the rewards added). - std::weak_ptr m_host; ///< Our Ethereum Host. Don't do anything if we can't lock. + std::weak_ptr m_host; ///< Our Ethereum Host. Don't do anything if we can't lock. std::vector m_miners; mutable boost::shared_mutex x_miners; diff --git a/libethereum/CommonNet.h b/libethereum/CommonNet.h index 3bb45b55d..6774c02ae 100644 --- a/libethereum/CommonNet.h +++ b/libethereum/CommonNet.h @@ -33,11 +33,17 @@ namespace dev namespace eth { +#if ETH_DEBUG +static const unsigned c_maxHashes = 4; ///< Maximum number of hashes BlockHashes will ever send. +static const unsigned c_maxHashesAsk = 4; ///< Maximum number of hashes GetBlockHashes will ever ask for. +static const unsigned c_maxBlocks = 2; ///< Maximum number of blocks Blocks will ever send. +static const unsigned c_maxBlocksAsk = 2; ///< Maximum number of blocks we ask to receive in Blocks (when using GetChain). +#else static const unsigned c_maxHashes = 256; ///< Maximum number of hashes BlockHashes will ever send. static const unsigned c_maxHashesAsk = 256; ///< Maximum number of hashes GetBlockHashes will ever ask for. static const unsigned c_maxBlocks = 128; ///< Maximum number of blocks Blocks will ever send. static const unsigned c_maxBlocksAsk = 128; ///< Maximum number of blocks we ask to receive in Blocks (when using GetChain). - +#endif class OverlayDB; class BlockChain; class TransactionQueue; @@ -55,5 +61,13 @@ enum EthereumPacket BlocksPacket, }; +enum class Grabbing +{ + State, + Hashes, + Chain, + Nothing +}; + } } diff --git a/libethereum/EthereumHost.cpp b/libethereum/EthereumHost.cpp index 13a02a146..2add39482 100644 --- a/libethereum/EthereumHost.cpp +++ b/libethereum/EthereumHost.cpp @@ -41,7 +41,7 @@ using namespace p2p; EthereumHost::EthereumHost(BlockChain const& _ch, TransactionQueue& _tq, BlockQueue& _bq, u256 _networkId): HostCapability(), - Worker("ethsync"), + Worker ("ethsync"), m_chain (_ch), m_tq (_tq), m_bq (_bq), @@ -72,11 +72,11 @@ h256Set EthereumHost::neededBlocks(h256Set const& _exclude) m_blocksNeeded.erase(it); } } - if (!ret.size()) + if (ret.empty()) for (auto i = m_blocksOnWay.begin(); ret.size() < c_maxBlocksAsk && i != m_blocksOnWay.end() && !_exclude.count(*i); ++i) ret.insert(*i); - - clog(NetMessageSummary) << "Asking for" << ret.size() << "blocks that we don't yet have." << m_blocksNeeded.size() << "blocks still needed," << m_blocksOnWay.size() << "blocks on way."; + if (ret.size()) + clog(NetMessageSummary) << "Asking for" << ret.size() << "blocks that we don't yet have." << m_blocksNeeded.size() << "blocks still needed," << m_blocksOnWay.size() << "total blocks on way."; return ret; } @@ -95,6 +95,83 @@ bool EthereumHost::ensureInitialised(TransactionQueue& _tq) return false; } +void EthereumHost::noteHavePeerState(EthereumPeer* _who) +{ + clog(NetAllDetail) << "Have peer state."; + + // if already downloading hash-chain, ignore. + if (m_grabbing != Grabbing::Nothing) + { + clog(NetAllDetail) << "Already downloading chain. Just set to help out."; + _who->restartGettingChain(); + return; + } + + // otherwise check to see if we should be downloading... + _who->tryGrabbingHashChain(); +} + +void EthereumHost::updateGrabbing(Grabbing _g) +{ + m_grabbing = _g; + if (_g == Grabbing::Nothing) + readyForSync(); + else if (_g == Grabbing::Chain) + for (auto j: peers()) + j->cap()->restartGettingChain(); +} + +void EthereumHost::noteHaveChain(EthereumPeer* _from) +{ + auto td = _from->m_totalDifficulty; + + if (_from->m_neededBlocks.empty()) + { + _from->m_grabbing = Grabbing::Nothing; + updateGrabbing(Grabbing::Nothing); + return; + } + + clog(NetNote) << "Hash-chain COMPLETE:" << _from->m_totalDifficulty << "vs" << m_chain.details().totalDifficulty << "," << m_totalDifficultyOfNeeded << ";" << _from->m_neededBlocks.size() << " blocks, ends" << _from->m_neededBlocks.back().abridged(); + + if ((m_totalDifficultyOfNeeded && (td < m_totalDifficultyOfNeeded || (td == m_totalDifficultyOfNeeded && m_latestBlockSent == _from->m_latestHash))) || td < m_chain.details().totalDifficulty || (td == m_chain.details().totalDifficulty && m_chain.currentHash() == _from->m_latestHash)) + { + clog(NetNote) << "Difficulty of hashchain not HIGHER. Ignoring."; + _from->m_grabbing = Grabbing::Nothing; + updateGrabbing(Grabbing::Nothing); + return; + } + + clog(NetNote) << "Difficulty of hashchain HIGHER. Replacing fetch queue [latest now" << _from->m_latestHash.abridged() << ", was" << m_latestBlockSent.abridged() << "]"; + + // Looks like it's the best yet for total difficulty. Set to download. + { + Guard l(x_blocksNeeded); + m_blocksNeeded = _from->m_neededBlocks; + m_blocksOnWay.clear(); + m_totalDifficultyOfNeeded = td; + m_latestBlockSent = _from->m_latestHash; + } + + _from->m_grabbing = Grabbing::Chain; + updateGrabbing(Grabbing::Chain); +} + +void EthereumHost::readyForSync() +{ + // start grabbing next hash chain if there is one. + for (auto j: peers()) + { + j->cap()->tryGrabbingHashChain(); + if (j->cap()->m_grabbing == Grabbing::Hashes) + { + m_grabbing = Grabbing::Hashes; + return; + } + } + clog(NetNote) << "No more peers to sync with."; +} + void EthereumHost::noteDoneBlocks() { if (m_blocksOnWay.empty()) @@ -104,7 +181,7 @@ void EthereumHost::noteDoneBlocks() clog(NetNote) << "No more blocks coming. Missing" << m_blocksNeeded.size() << "blocks."; else clog(NetNote) << "No more blocks to get."; - m_latestBlockSent = m_chain.currentHash(); + updateGrabbing(Grabbing::Nothing); } } @@ -129,6 +206,7 @@ void EthereumHost::doWork() maintainBlocks(m_bq, h); // return netChange; // TODO: Figure out what to do with netChange. + (void)netChange; } void EthereumHost::maintainTransactions(TransactionQueue& _tq, h256 _currentHash) @@ -175,6 +253,21 @@ void EthereumHost::maintainTransactions(TransactionQueue& _tq, h256 _currentHash } } +void EthereumHost::reset() +{ + m_grabbing = Grabbing::Nothing; + + m_incomingTransactions.clear(); + m_incomingBlocks.clear(); + + m_totalDifficultyOfNeeded = 0; + m_blocksNeeded.clear(); + m_blocksOnWay.clear(); + + m_latestBlockSent = h256(); + m_transactionsSent.clear(); +} + void EthereumHost::maintainBlocks(BlockQueue& _bq, h256 _currentHash) { // Import new blocks @@ -187,14 +280,8 @@ void EthereumHost::maintainBlocks(BlockQueue& _bq, h256 _currentHash) m_incomingBlocks.clear(); } - // If we've finished our initial sync... - { - Guard l(x_blocksNeeded); - if (m_blocksOnWay.size()) - return; - } - // ...send any new blocks. - if (m_latestBlockSent != _currentHash) + // If we've finished our initial sync send any new blocks. + if (m_grabbing == Grabbing::Nothing && m_chain.details(m_latestBlockSent) && m_chain.details(m_latestBlockSent).totalDifficulty < m_chain.details(_currentHash).totalDifficulty) { RLPStream ts; EthereumPeer::prep(ts); @@ -222,32 +309,3 @@ void EthereumHost::maintainBlocks(BlockQueue& _bq, h256 _currentHash) m_latestBlockSent = _currentHash; } } - -void EthereumHost::noteHaveChain(EthereumPeer* _from) -{ - auto td = _from->m_totalDifficulty; - - if (_from->m_neededBlocks.empty()) - return; - - clog(NetNote) << "Hash-chain COMPLETE:" << _from->m_totalDifficulty << "vs" << m_chain.details().totalDifficulty << "," << m_totalDifficultyOfNeeded << ";" << _from->m_neededBlocks.size() << " blocks, ends" << _from->m_neededBlocks.back().abridged(); - - if ((m_totalDifficultyOfNeeded && td < m_totalDifficultyOfNeeded) || td < m_chain.details().totalDifficulty) - { - clog(NetNote) << "Difficulty of hashchain LOWER. Ignoring."; - return; - } - - clog(NetNote) << "Difficulty of hashchain HIGHER. Replacing fetch queue."; - - // Looks like it's the best yet for total difficulty. Set to download. - { - Guard l(x_blocksNeeded); - m_blocksNeeded = _from->m_neededBlocks; - m_blocksOnWay.clear(); - m_totalDifficultyOfNeeded = td; - } - - for (auto j: peers()) - j->cap()->restartGettingChain(); -} diff --git a/libethereum/EthereumHost.h b/libethereum/EthereumHost.h index 418439114..c875d74fe 100644 --- a/libethereum/EthereumHost.h +++ b/libethereum/EthereumHost.h @@ -185,7 +185,10 @@ public: u256 networkId() const { return m_networkId; } void setNetworkId(u256 _n) { m_networkId = _n; } + void reset(); + private: + void noteHavePeerState(EthereumPeer* _who); /// Session wants to pass us a block that we might not have. /// @returns true if we didn't have it. bool noteBlock(h256 _hash, bytesConstRef _data); @@ -199,7 +202,7 @@ private: /// Called by peer to add incoming transactions. void addIncomingTransaction(bytes const& _bytes) { std::lock_guard l(m_incomingLock); m_incomingTransactions.push_back(_bytes); } - + void maintainTransactions(TransactionQueue& _tq, h256 _currentBlock); void maintainBlocks(BlockQueue& _bq, h256 _currentBlock); @@ -217,12 +220,17 @@ private: virtual void onStarting() { startWorking(); } virtual void onStopping() { stopWorking(); } + void readyForSync(); + void updateGrabbing(Grabbing _g); + BlockChain const& m_chain; TransactionQueue& m_tq; ///< Maintains a list of incoming transactions not yet in a block on the blockchain. BlockQueue& m_bq; ///< Maintains a list of incoming blocks not yet on the blockchain (to be imported). u256 m_networkId; + Grabbing m_grabbing = Grabbing::Nothing; + mutable std::recursive_mutex m_incomingLock; std::vector m_incomingTransactions; std::vector m_incomingBlocks; @@ -233,7 +241,7 @@ private: h256Set m_blocksOnWay; h256 m_latestBlockSent; - std::set m_transactionsSent; + h256Set m_transactionsSent; }; } diff --git a/libethereum/EthereumPeer.cpp b/libethereum/EthereumPeer.cpp index f8f944916..7ccf6939b 100644 --- a/libethereum/EthereumPeer.cpp +++ b/libethereum/EthereumPeer.cpp @@ -73,16 +73,35 @@ void EthereumPeer::startInitialSync() sealAndSend(s); } + host()->noteHavePeerState(this); +} + +void EthereumPeer::tryGrabbingHashChain() +{ + // if already done this, then ignore. + if (m_grabbing != Grabbing::State) + { + clogS(NetAllDetail) << "Already synced with this peer."; + return; + } + h256 c = host()->m_chain.currentHash(); unsigned n = host()->m_chain.number(); u256 td = max(host()->m_chain.details().totalDifficulty, host()->m_totalDifficultyOfNeeded); - clogS(NetAllDetail) << "Initial sync. Latest:" << c.abridged() << ", number:" << n << ", TD: max(" << host()->m_chain.details().totalDifficulty << "," << host()->m_totalDifficultyOfNeeded << ") versus " << m_totalDifficulty; + clogS(NetAllDetail) << "Attempt chain-grab? Latest:" << c.abridged() << ", number:" << n << ", TD: max(" << host()->m_chain.details().totalDifficulty << "," << host()->m_totalDifficultyOfNeeded << ") versus " << m_totalDifficulty; if (td > m_totalDifficulty) + { + clogS(NetAllDetail) << "No. Our chain is better."; + m_grabbing = Grabbing::Nothing; return; // All good - we have the better chain. + } // Our chain isn't better - grab theirs. { + clogS(NetAllDetail) << "Yes. Their chain is better."; + + m_grabbing = Grabbing::Hashes; RLPStream s; prep(s).appendList(3); s << GetBlockHashesPacket << m_latestHash << c_maxHashesAsk; @@ -94,6 +113,16 @@ void EthereumPeer::startInitialSync() void EthereumPeer::giveUpOnFetch() { clogS(NetNote) << "GIVE UP FETCH; can't get" << toString(m_askedBlocks); + + // a bit overkill given that the other nodes may yet have the needed blocks, but better to be safe than sorry. + if (m_grabbing == Grabbing::Chain) + { + m_grabbing = Grabbing::Nothing; + host()->updateGrabbing(Grabbing::Nothing); + } + + // NOTE: need to notify of giving up on chain-hashes, too, altering state as necessary. + if (m_askedBlocks.size()) { Guard l (host()->x_blocksNeeded); @@ -157,7 +186,7 @@ bool EthereumPeer::interpret(RLP const& _r) unsigned limit = _r[2].toInt(); clogS(NetMessageSummary) << "GetBlockHashes (" << limit << "entries," << later.abridged() << ")"; - unsigned c = min(host()->m_chain.number(later), limit); + unsigned c = min(max(1, host()->m_chain.number(later)) - 1, limit); RLPStream s; prep(s).appendList(1 + c).append(BlockHashesPacket); @@ -169,7 +198,13 @@ bool EthereumPeer::interpret(RLP const& _r) } case BlockHashesPacket: { - clogS(NetMessageSummary) << "BlockHashes (" << dec << (_r.itemCount() - 1) << "entries)"; + clogS(NetMessageSummary) << "BlockHashes (" << dec << (_r.itemCount() - 1) << "entries)" << (_r.itemCount() - 1 ? "" : ": NoMoreHashes"); + + if (m_grabbing != Grabbing::Hashes) + { + cwarn << "Peer giving us hashes when we didn't ask for them."; + break; + } if (_r.itemCount() == 1) { host()->noteHaveChain(this); @@ -196,7 +231,7 @@ bool EthereumPeer::interpret(RLP const& _r) case GetBlocksPacket: { clogS(NetMessageSummary) << "GetBlocks (" << dec << (_r.itemCount() - 1) << "entries)"; - // TODO: return the requested blocks. + // return the requested blocks. bytes rlp; unsigned n = 0; for (unsigned i = 1; i < _r.itemCount() && i <= c_maxBlocks; ++i) @@ -214,7 +249,7 @@ bool EthereumPeer::interpret(RLP const& _r) } case BlocksPacket: { - clogS(NetMessageSummary) << "Blocks (" << dec << (_r.itemCount() - 1) << "entries)"; + clogS(NetMessageSummary) << "Blocks (" << dec << (_r.itemCount() - 1) << "entries)" << (_r.itemCount() - 1 ? "" : ": NoMoreBlocks"); if (_r.itemCount() == 1 && !m_askedBlocksChanged) { @@ -302,7 +337,7 @@ void EthereumPeer::continueGettingChain() else { if (m_failedBlocks.size()) - clogS(NetMessageSummary) << "No blocks left to get. Peer doesn't seem to have" << m_failedBlocks.size() << "of our needed blocks."; + clogS(NetMessageSummary) << "No blocks left to get. Peer doesn't seem to have" << m_failedBlocks.size() << "of our needed blocks."; host()->noteDoneBlocks(); } } diff --git a/libethereum/EthereumPeer.h b/libethereum/EthereumPeer.h index f171767cd..7248fabd0 100644 --- a/libethereum/EthereumPeer.h +++ b/libethereum/EthereumPeer.h @@ -59,6 +59,8 @@ private: void sendStatus(); void startInitialSync(); + void tryGrabbingHashChain(); + /// Ensure that we are waiting for a bunch of blocks from our peer. void ensureGettingChain(); /// Ensure that we are waiting for a bunch of blocks from our peer. @@ -73,6 +75,8 @@ private: unsigned m_protocolVersion; u256 m_networkId; + Grabbing m_grabbing = Grabbing::State; + h256 m_latestHash; ///< Peer's latest block's hash. u256 m_totalDifficulty; ///< Peer's latest block's total difficulty. h256s m_neededBlocks; ///< The blocks that we should download from this peer. diff --git a/libethereum/State.cpp b/libethereum/State.cpp index 093db299f..a74b6ba18 100644 --- a/libethereum/State.cpp +++ b/libethereum/State.cpp @@ -152,10 +152,15 @@ State& State::operator=(State const& _s) m_currentBlock = _s.m_currentBlock; m_ourAddress = _s.m_ourAddress; m_blockReward = _s.m_blockReward; + m_lastTx = _s.m_lastTx; paranoia("after state cloning (assignment op)", true); return *this; } +State::~State() +{ +} + struct CachedAddressState { CachedAddressState(std::string const& _rlp, AddressState const* _s, OverlayDB const* _o): rS(_rlp), r(rS), s(_s), o(_o) {} diff --git a/libethereum/State.h b/libethereum/State.h index 335f27168..fea8e06c7 100644 --- a/libethereum/State.h +++ b/libethereum/State.h @@ -90,6 +90,8 @@ public: /// Copy state object. State& operator=(State const& _s); + ~State(); + /// 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(); } diff --git a/libethereum/TransactionQueue.h b/libethereum/TransactionQueue.h index 154eed9d2..22a420602 100644 --- a/libethereum/TransactionQueue.h +++ b/libethereum/TransactionQueue.h @@ -52,6 +52,8 @@ public: void setFuture(std::pair const& _t); void noteGood(std::pair const& _t); + void clear() { WriteGuard l(m_lock); m_known.clear(); m_current.clear(); m_unknown.clear(); } + private: mutable boost::shared_mutex m_lock; ///< General lock. std::set m_known; ///< Hashes of transactions in both sets. diff --git a/libwebthree/WebThree.h b/libwebthree/WebThree.h index da41f9e76..06717ee26 100644 --- a/libwebthree/WebThree.h +++ b/libwebthree/WebThree.h @@ -106,7 +106,7 @@ public: bool haveNetwork() const { return m_net.isStarted(); } - void setNetworkPreferences(p2p::NetworkPreferences const& _n) { auto had = haveNetwork(); if (had) stopNetwork(); m_netPrefs = _n; if (had) startNetwork(); } + void setNetworkPreferences(p2p::NetworkPreferences const& _n) { auto had = haveNetwork(); if (had) stopNetwork(); m_net.setNetworkPreferences(_n); if (had) startNetwork(); } /// Start the network subsystem. void startNetwork() { m_net.start(); } @@ -121,7 +121,6 @@ private: std::unique_ptr m_whisper; ///< Main interface for Whisper ("shh") protocol. p2p::Host m_net; ///< Should run in background and send us events when blocks found and allow us to send blocks as required. - p2p::NetworkPreferences m_netPrefs; }; From fdb829e7f6d614f20f7af3397810269b843fe3da Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 19 Sep 2014 12:15:29 -0500 Subject: [PATCH 187/223] Merge fix. --- eth/EthStubServer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eth/EthStubServer.cpp b/eth/EthStubServer.cpp index e0baaa9c7..ad6a87781 100644 --- a/eth/EthStubServer.cpp +++ b/eth/EthStubServer.cpp @@ -146,7 +146,7 @@ std::string EthStubServer::storageAt(const std::string& _a, const std::string& x std::string EthStubServer::stateAt(const std::string& _a, const std::string& x, const std::string& s) { - return toJS(m_client.stateAt(jsToAddress(_a), jsToU256(x), std::atol(s.c_str()))); + return toJS(ethereum().stateAt(jsToAddress(_a), jsToU256(x), std::atol(s.c_str()))); } Json::Value EthStubServer::transact(const std::string& _aDest, const std::string& _bData, const std::string& _sec, const std::string& _xGas, const std::string& _xGasPrice, const std::string& _xValue) From 614555b8df5d727368e2fe99a6a45e529812ce05 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 19 Sep 2014 12:47:07 -0500 Subject: [PATCH 188/223] eth web3 fix. --- eth/main.cpp | 87 +++++++++++++++++++++++++++------------------------- 1 file changed, 46 insertions(+), 41 deletions(-) diff --git a/eth/main.cpp b/eth/main.cpp index 3ea02f744..215350c09 100644 --- a/eth/main.cpp +++ b/eth/main.cpp @@ -307,10 +307,13 @@ int main(int argc, char** argv) NetworkPreferences netPrefs(listenPort, publicIP, upnp, useLocal); dev::WebThreeDirect web3("Ethereum(++)/" + clientName + "v" + dev::Version + "/" DEV_QUOTED(ETH_BUILD_TYPE) "/" DEV_QUOTED(ETH_BUILD_PLATFORM), dbPath, false, mode == NodeMode::Full ? set{"eth", "shh"} : set{}, netPrefs); web3.setIdealPeerCount(peers); - eth::Client& c = *web3.ethereum(); + eth::Client* c = mode == NodeMode::Full ? web3.ethereum() : nullptr; - c.setForceMining(forceMining); - c.setAddress(coinbase); + if (c) + { + c->setForceMining(forceMining); + c->setAddress(coinbase); + } cout << "Address: " << endl << toHex(us.address().asArray()) << endl; web3.startNetwork(); @@ -373,19 +376,19 @@ int main(int argc, char** argv) { web3.stopNetwork(); } - else if (cmd == "minestart") + else if (c && cmd == "minestart") { - c.startMining(); + c->startMining(); } - else if (cmd == "minestop") + else if (c && cmd == "minestop") { - c.stopMining(); + c->stopMining(); } - else if (cmd == "mineforce") + else if (c && cmd == "mineforce") { string enable; iss >> enable; - c.setForceMining(isTrue(enable)); + c->setForceMining(isTrue(enable)); } else if (cmd == "verbosity") { @@ -425,9 +428,9 @@ int main(int argc, char** argv) { cout << "Secret Key: " << toHex(us.secret().asArray()) << endl; } - else if (cmd == "block") + else if (c && cmd == "block") { - cout << "Current block: " << c.blockChain().details().number << endl; + cout << "Current block: " <blockChain().details().number << endl; } else if (cmd == "peers") { @@ -436,13 +439,13 @@ int main(int argc, char** argv) << std::chrono::duration_cast(it.lastPing).count() << "ms" << endl; } - else if (cmd == "balance") + else if (c && cmd == "balance") { - cout << "Current balance: " << formatBalance(c.balanceAt(us.address())) << " = " << c.balanceAt(us.address()) << " wei" << endl; + cout << "Current balance: " << formatBalance( c->balanceAt(us.address())) << " = " <balanceAt(us.address()) << " wei" << endl; } - else if (cmd == "transact") + else if (c && cmd == "transact") { - auto const& bc = c.blockChain(); + auto const& bc =c->blockChain(); auto h = bc.currentHash(); auto blockData = bc.block(h); BlockInfo info(blockData); @@ -487,35 +490,35 @@ int main(int argc, char** argv) { Secret secret = h256(fromHex(sechex)); Address dest = h160(fromHex(hexAddr)); - c.transact(secret, amount, dest, data, gas, gasPrice); + c->transact(secret, amount, dest, data, gas, gasPrice); } } else cwarn << "Require parameters: transact ADDRESS AMOUNT GASPRICE GAS SECRET DATA"; } - else if (cmd == "listContracts") + else if (c && cmd == "listContracts") { - auto acs = c.addresses(); + auto acs =c->addresses(); string ss; for (auto const& i: acs) - if (c.codeAt(i, 0).size()) + if ( c->codeAt(i, 0).size()) { - ss = toString(i) + " : " + toString(c.balanceAt(i)) + " [" + toString((unsigned)c.countAt(i)) + "]"; + ss = toString(i) + " : " + toString( c->balanceAt(i)) + " [" + toString((unsigned) c->countAt(i)) + "]"; cout << ss << endl; } } - else if (cmd == "listAccounts") + else if (c && cmd == "listAccounts") { - auto acs = c.addresses(); + auto acs =c->addresses(); string ss; for (auto const& i: acs) - if (c.codeAt(i, 0).empty()) + if ( c->codeAt(i, 0).empty()) { - ss = toString(i) + " : " + toString(c.balanceAt(i)) + " [" + toString((unsigned)c.countAt(i)) + "]"; + ss = toString(i) + " : " + toString( c->balanceAt(i)) + " [" + toString((unsigned) c->countAt(i)) + "]"; cout << ss << endl; } } - else if (cmd == "send") + else if (c && cmd == "send") { if (iss.peek() != -1) { @@ -531,21 +534,21 @@ int main(int argc, char** argv) } else { - auto const& bc = c.blockChain(); + auto const& bc =c->blockChain(); auto h = bc.currentHash(); auto blockData = bc.block(h); BlockInfo info(blockData); u256 minGas = (u256)Client::txGas(0, 0); Address dest = h160(fromHex(hexAddr)); - c.transact(us.secret(), amount, dest, bytes(), minGas, info.minGasPrice); + c->transact(us.secret(), amount, dest, bytes(), minGas, info.minGasPrice); } } else cwarn << "Require parameters: send ADDRESS AMOUNT"; } - else if (cmd == "contract") + else if (c && cmd == "contract") { - auto const& bc = c.blockChain(); + auto const& bc =c->blockChain(); auto h = bc.currentHash(); auto blockData = bc.block(h); BlockInfo info(blockData); @@ -582,12 +585,12 @@ int main(int argc, char** argv) else if (gas < minGas) cwarn << "Minimum gas amount is" << minGas; else - c.transact(us.secret(), endowment, init, gas, gasPrice); + c->transact(us.secret(), endowment, init, gas, gasPrice); } else cwarn << "Require parameters: contract ENDOWMENT GASPRICE GAS CODEHEX"; } - else if (cmd == "dumptrace") + else if (c && cmd == "dumptrace") { unsigned block; unsigned index; @@ -597,7 +600,7 @@ int main(int argc, char** argv) ofstream f; f.open(filename); - dev::eth::State state = c.state(index + 1, c.blockChain().numberHash(block)); + dev::eth::State state =c->state(index + 1,c->blockChain().numberHash(block)); if (index < state.pending().size()) { Executive e(state); @@ -642,7 +645,7 @@ int main(int argc, char** argv) e.finalize(oof); } } - else if (cmd == "inspect") + else if (c && cmd == "inspect") { string rechex; iss >> rechex; @@ -656,10 +659,10 @@ int main(int argc, char** argv) try { - auto storage = c.storageAt(h, 0); + auto storage =c->storageAt(h, 0); for (auto const& i: storage) s << "@" << showbase << hex << i.first << " " << showbase << hex << i.second << endl; - s << endl << disassemble(c.codeAt(h, 0)) << endl; + s << endl << disassemble( c->codeAt(h, 0)) << endl; string outFile = getDataDir() + "/" + rechex + ".evm"; ofstream ofs; @@ -744,19 +747,21 @@ int main(int argc, char** argv) jsonrpcServer->StopListening(); #endif } - else + else if (c) { - unsigned n = c.blockChain().details().number; + unsigned n =c->blockChain().details().number; if (mining) - c.startMining(); + c->startMining(); while (true) { - if (c.isMining() && c.blockChain().details().number - n == mining) - c.stopMining(); + if ( c->isMining() &&c->blockChain().details().number - n == mining) + c->stopMining(); this_thread::sleep_for(chrono::milliseconds(100)); } } - + else + while (true) + this_thread::sleep_for(chrono::milliseconds(1000)); return 0; } From 89ee5fd14e26ba8257a76f236f00f4840382f85f Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 19 Sep 2014 13:16:15 -0500 Subject: [PATCH 189/223] Minor fix for mining. --- libethereum/Client.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index d64c9fafb..7025cb03c 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -125,6 +125,8 @@ void Client::killChain() doWork(); + setMiningThreads(0); + startWorking(); if (wasMining) startMining(); From 4c30b7cdcf296dfe32922a5a07f4974bf6d0fe90 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 19 Sep 2014 13:20:44 -0500 Subject: [PATCH 190/223] fix nick's warning. --- libethereum/BlockChain.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libethereum/BlockChain.cpp b/libethereum/BlockChain.cpp index 181f4fd64..4c7a7b48c 100644 --- a/libethereum/BlockChain.cpp +++ b/libethereum/BlockChain.cpp @@ -460,7 +460,7 @@ bool BlockChain::isKnown(h256 _hash) const } string d; m_db->Get(m_readOptions, ldb::Slice((char const*)&_hash, 32), &d); - return d.size(); + return !!d.size(); } bytes BlockChain::block(h256 _hash) const From ecb3e5823e381a7966bcf6a25f048fe0d55c505d Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 19 Sep 2014 16:50:06 -0500 Subject: [PATCH 191/223] Add RangeMask. --- exp/main.cpp | 283 +++---------------------------------- libdevcore/RangeMask.cpp | 5 + libdevcore/RangeMask.h | 192 +++++++++++++++++++++++++ libethereum/EthereumHost.h | 82 ----------- 4 files changed, 213 insertions(+), 349 deletions(-) create mode 100644 libdevcore/RangeMask.cpp create mode 100644 libdevcore/RangeMask.h diff --git a/exp/main.cpp b/exp/main.cpp index 77d4aa00f..a88023fae 100644 --- a/exp/main.cpp +++ b/exp/main.cpp @@ -19,290 +19,39 @@ * @date 2014 * Ethereum client. */ -#if 0 -#define BOOST_RESULT_OF_USE_DECLTYPE -#define BOOST_SPIRIT_USE_PHOENIX_V3 -#include -#include -#include -#endif #include #include #include #include #include +#include #include -#if 0 -#include -#include "BuildInfo.h" -#endif using namespace std; using namespace dev; using namespace dev::eth; using namespace dev::p2p; using namespace dev::shh; -#if 0 -#if 0 -namespace qi = boost::spirit::qi; -namespace px = boost::phoenix; -namespace sp = boost::spirit; - -class ASTSymbol: public string -{ -public: - ASTSymbol() {} -}; - -enum class ASTType -{ - Symbol, - IntegerLiteral, - StringLiteral, - Call, - Return, - Operator, - Compound -}; - -class ASTNode: public vector -{ -public: - ASTNode() {} - ASTNode(ASTSymbol const& _s): m_type(ASTType::Symbol), m_s(_s) {} - ASTNode(string const& _s): m_type(ASTType::StringLiteral), m_s(_s) {} - ASTNode(bigint const& _i): m_type(ASTType::IntegerLiteral), m_i(_i) {} - ASTNode(ASTType _t): m_type(_t) {} - - ASTNode& operator=(ASTSymbol const& _s) { m_type = ASTType::Symbol; m_s = _s; return *this; } - ASTNode& operator=(string const& _s) { m_type = ASTType::StringLiteral; m_s = _s; return *this; } - ASTNode& operator=(bigint const& _i) { m_type = ASTType::IntegerLiteral; m_i = _i; return *this; } - ASTNode& operator=(ASTType const& _s) { m_type = _s; return *this; } - - void debugOut(ostream& _out) const; - -private: - ASTType m_type; - string m_s; - bigint m_i; -}; - -void parseTree(string const& _s, ASTNode& o_out) +int main() { - using qi::standard::space; - using qi::standard::space_type; - typedef string::const_iterator it; - -/* static const u256 ether = u256(1000000000) * 1000000000; - static const u256 finney = u256(1000000000) * 1000000; - static const u256 szabo = u256(1000000000) * 1000;*/ - - qi::rule element; - qi::rule call; - qi::rule str = '"' > qi::lexeme[+(~qi::char_(std::string("\"") + '\0'))] > '"'; - qi::rule symbol = qi::lexeme[+(~qi::char_(std::string(" $@[]{}:();\"\x01-\x1f\x7f") + '\0'))]; -/* qi::rule strsh = '\'' > qi::lexeme[+(~qi::char_(std::string(" ;$@()[]{}:\n\t") + '\0'))]; - qi::rule intstr = qi::lexeme[ qi::no_case["0x"][qi::_val = "0x"] >> *qi::char_("0-9a-fA-F")[qi::_val += qi::_1]] | qi::lexeme[+qi::char_("0-9")[qi::_val += qi::_1]]; - qi::rule integer = intstr; - qi::rule multiplier = qi::lit("wei")[qi::_val = 1] | qi::lit("szabo")[qi::_val = szabo] | qi::lit("finney")[qi::_val = finney] | qi::lit("ether")[qi::_val = ether]; - qi::rule quantity = integer[qi::_val = qi::_1] >> -multiplier[qi::_val *= qi::_1]; - qi::rule atom = quantity[qi::_val = px::construct(px::new_(qi::_1))] | (str | strsh)[qi::_val = qi::_1] | symbol[qi::_val = qi::_1]; - qi::rule seq = '{' > *element > '}'; - qi::rule mload = '@' > element; - qi::rule sload = qi::lit("@@") > element; - qi::rule mstore = '[' > element > ']' > -qi::lit(":") > element; - qi::rule sstore = qi::lit("[[") > element > qi::lit("]]") > -qi::lit(":") > element; - qi::rule calldataload = qi::lit("$") > element; - qi::rule list = '(' > *element > ')'; - - qi::rule extra = sload[tagNode<2>()] | mload[tagNode<1>()] | sstore[tagNode<4>()] | mstore[tagNode<3>()] | seq[tagNode<5>()] | calldataload[tagNode<6>()];*/ - qi::rule value = call[qi::_val = ASTType::Call] | str[qi::_val = qi::_1] | symbol[qi::_val = qi::_1]; - qi::rule compound = '{' > *element > '}'; - call = '(' > *value > ')'; //symbol > '(' > !(value > *(',' > value)) > ')'; - element = compound[qi::_val = ASTType::Compound] | value[qi::_val = qi::_1]; - - auto ret = _s.cbegin(); - qi::phrase_parse(ret, _s.cend(), element, space, qi::skip_flag::dont_postskip, o_out); - for (auto i = ret; i != _s.cend(); ++i) - if (!isspace(*i)) - throw std::exception(); -} - -void ASTNode::debugOut(ostream& _out) const -{ - switch (m_type) - { - case ASTType::StringLiteral: - _out << "\"" << m_s << "\""; - break; - case ASTType::Symbol: - _out << m_s; - break; - case ASTType::Compound: - { - unsigned n = 0; - _out << "{"; - for (auto const& i: *this) - { - i.debugOut(_out); - _out << ";"; - ++n; - } - _out << "}"; - break; - } - case ASTType::Call: - { - unsigned n = 0; - for (auto const& i: *this) - { - i.debugOut(_out); - if (n == 0) - _out << "("; - else if (n < size() - 1) - _out << ","; - if (n == size() - 1) - _out << ")"; - ++n; - } - break; - } - default: - _out << "nil"; - } -} - -int main(int, char**) -{ - ASTNode out; - parseTree("{x}", out); - out.debugOut(cout); - cout << endl; + RangeMask m(0, 100); + cnote << m; + m += UnsignedRange(3, 10); + cnote << m; + m += UnsignedRange(11, 16); + cnote << m; + m += UnsignedRange(10, 11); + cnote << m; + cnote << ~m; + cnote << (~m).lowest(10); + for (auto i: (~m).lowest(10)) + cnote << i; return 0; } -#endif -void killBigints(sp::utree const& _this) -{ - switch (_this.which()) - { - case sp::utree_type::list_type: for (auto const& i: _this) killBigints(i); break; - case sp::utree_type::any_type: delete _this.get(); break; - default:; - } -} - -void debugOutAST(ostream& _out, sp::utree const& _this) -{ - switch (_this.which()) - { - case sp::utree_type::list_type: - switch (_this.tag()) - { - case 0: { int n = 0; for (auto const& i: _this) { debugOutAST(_out, i); if (n++) _out << ", "; } break; } - case 1: _out << "@ "; debugOutAST(_out, _this.front()); break; - case 2: _out << "@@ "; debugOutAST(_out, _this.front()); break; - case 3: _out << "[ "; debugOutAST(_out, _this.front()); _out << " ] "; debugOutAST(_out, _this.back()); break; - case 4: _out << "[[ "; debugOutAST(_out, _this.front()); _out << " ]] "; debugOutAST(_out, _this.back()); break; - case 5: _out << "{ "; for (auto const& i: _this) { debugOutAST(_out, i); _out << " "; } _out << "}"; break; - case 6: _out << "$ "; debugOutAST(_out, _this.front()); break; - default: - { _out << _this.tag() << ": "; int n = 0; for (auto const& i: _this) { debugOutAST(_out, i); if (n++) _out << ", "; } break; } - } - - break; - case sp::utree_type::int_type: _out << _this.get(); break; - case sp::utree_type::string_type: _out << "\"" << _this.get, sp::utree_type::string_type>>() << "\""; break; - case sp::utree_type::symbol_type: _out << _this.get, sp::utree_type::symbol_type>>(); break; - case sp::utree_type::any_type: _out << *_this.get(); break; - default: _out << "nil"; - } -} - -namespace dev { -namespace eth { -namespace parseTreeLLL_ { - -template -struct tagNode -{ - void operator()(sp::utree& n, qi::rule::context_type& c) const - { - (boost::fusion::at_c<0>(c.attributes) = n).tag(N); - } -}; - -}}} - -void parseTree(string const& _s, sp::utree& o_out) -{ - using qi::standard::space; - using qi::standard::space_type; - using dev::eth::parseTreeLLL_::tagNode; - typedef sp::basic_string symbol_type; - typedef string::const_iterator it; - - static const u256 ether = u256(1000000000) * 1000000000; - static const u256 finney = u256(1000000000) * 1000000; - static const u256 szabo = u256(1000000000) * 1000; -#if 0 - qi::rule element; - qi::rule statement; - qi::rule str = '"' > qi::lexeme[+(~qi::char_(std::string("\"") + '\0'))] > '"'; - qi::rule strsh = '\'' > qi::lexeme[+(~qi::char_(std::string(" ;$@()[]{}:\n\t") + '\0'))]; - qi::rule symbol = qi::lexeme[+(~qi::char_(std::string(" $@[]{}:();\"\x01-\x1f\x7f") + '\0'))]; - qi::rule intstr = qi::lexeme[ qi::no_case["0x"][qi::_val = "0x"] >> *qi::char_("0-9a-fA-F")[qi::_val += qi::_1]] | qi::lexeme[+qi::char_("0-9")[qi::_val += qi::_1]]; - qi::rule integer = intstr; - qi::rule multiplier = qi::lit("wei")[qi::_val = 1] | qi::lit("szabo")[qi::_val = szabo] | qi::lit("finney")[qi::_val = finney] | qi::lit("ether")[qi::_val = ether]; - qi::rule quantity = integer[qi::_val = qi::_1] >> -multiplier[qi::_val *= qi::_1]; - qi::rule atom = quantity[qi::_val = px::construct(px::new_(qi::_1))] | (str | strsh)[qi::_val = qi::_1] | symbol[qi::_val = qi::_1]; - qi::rule compound = '{' > *statement > '}'; -/* qi::rule mload = '@' > element; - qi::rule sload = qi::lit("@@") > element; - qi::rule mstore = '[' > element > ']' > -qi::lit(":") > element; - qi::rule sstore = qi::lit("[[") > element > qi::lit("]]") > -qi::lit(":") > element; - qi::rule calldataload = qi::lit("$") > element;*/ -// qi::rule args = '(' > (element % ',') > ')'; - - qi::rule expression; - qi::rule group = '(' >> expression[qi::_val = qi::_1] >> ')'; - qi::rule factor = atom | group; - qi::rule mul = '*' >> factor; - qi::rule div = '/' >> factor; - qi::rule op = mul[tagNode<10>()] | div[tagNode<11>()]; - qi::rule term = factor >> !op; - expression = term >> !(('+' >> term) | ('-' >> term)); - - // qi::rule extra = sload[tagNode<2>()] | mload[tagNode<1>()] | sstore[tagNode<4>()] | mstore[tagNode<3>()] | calldataload[tagNode<6>()]; - statement = compound[tagNode<5>()] | (element > ';')[qi::_val = qi::_1]; - element %= expression;// | extra; -#endif - qi::rule symbol = qi::lexeme[+(~qi::char_(std::string(" $@[]{}:();\"\x01-\x1f\x7f") + '\0'))]; - qi::rule intstr = qi::lexeme[ qi::no_case["0x"][qi::_val = "0x"] >> *qi::char_("0-9a-fA-F")[qi::_val += qi::_1]] | qi::lexeme[+qi::char_("0-9")[qi::_val += qi::_1]]; - qi::rule integer = intstr; - qi::rule intnode = integer[qi::_val = px::construct(px::new_(qi::_1))]; - qi::rule funcname = symbol; - qi::rule statement; - qi::rule call = funcname > '(' > funcname > ')'; - statement = call | intnode | symbol; - - auto ret = _s.cbegin(); - qi::phrase_parse(ret, _s.cend(), statement, space, qi::skip_flag::dont_postskip, o_out); - for (auto i = ret; i != _s.cend(); ++i) - if (!isspace(*i)) - throw std::exception(); -} -#endif +/* int main(int argc, char** argv) { -#if 0 - sp::utree out; - parseTree("x(2)", out); - debugOutAST(cout, out); - killBigints(out); - cout << endl; -#endif - g_logVerbosity = 20; short listenPort = 30303; @@ -341,6 +90,6 @@ int main(int argc, char** argv) for (auto i: wh->checkWatch(w)) cnote << "New message:" << (u256)h256(wh->message(i).payload); } - return 0; } +*/ diff --git a/libdevcore/RangeMask.cpp b/libdevcore/RangeMask.cpp new file mode 100644 index 000000000..5ba4a1f73 --- /dev/null +++ b/libdevcore/RangeMask.cpp @@ -0,0 +1,5 @@ +#include "RangeMask.h" + +RangeMask::RangeMask() +{ +} diff --git a/libdevcore/RangeMask.h b/libdevcore/RangeMask.h new file mode 100644 index 000000000..ba8594779 --- /dev/null +++ b/libdevcore/RangeMask.h @@ -0,0 +1,192 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file EthereumHost.h + * @author Gav Wood + * @date 2014 + */ + +#pragma once + +#include +#include +#include + +namespace dev +{ + +class RLPStream; + +using UnsignedRange = std::pair; +using UnsignedRanges = std::vector; + +template +class RangeMask +{ + template friend std::ostream& operator<<(std::ostream& _out, RangeMask const& _r); + +public: + using Range = std::pair; + using Ranges = std::vector; + + RangeMask() {} + RangeMask(T _begin, T _end): m_all(_begin, _end) {} + RangeMask(Range const& _c): m_all(_c) {} + + RangeMask operator+(RangeMask const& _m) const { return RangeMask(*this) += _m; } + + RangeMask lowest(T _items) const + { + RangeMask ret(m_all); + for (auto i = m_ranges.begin(); i != m_ranges.end() && _items; ++i) + _items -= (ret.m_ranges[i->first] = std::min(i->first + _items, i->second)); + return ret; + } + + RangeMask operator~() const + { + RangeMask ret(m_all); + T last = m_all.first; + for (auto i: m_ranges) + { + if (i.first != last) + ret.m_ranges[last] = i.first; + last = i.second; + } + if (last != m_all.second) + ret.m_ranges[last] = m_all.second; + return ret; + } + + RangeMask& operator+=(RangeMask const& _m) + { + for (auto const& i: _m.m_ranges) + operator+=(i); + return *this; + } + RangeMask& operator+=(UnsignedRange const& _m) + { + for (auto i = _m.first; i < _m.second;) + { + // for each number, we find the element equal or next lower. this, if any, must contain the value. + auto uit = m_ranges.upper_bound(i); + auto it = uit == m_ranges.begin() ? m_ranges.end() : std::prev(uit); + if (it == m_ranges.end() || it->second < i) + // lower range is too low to merge. + // if the next higher range is too high. + if (uit == m_ranges.end() || uit->first > _m.second) + { + // just create a new range + m_ranges[i] = _m.second; + break; + } + else + { + if (uit->first == i) + // move i to end of range + i = uit->second; + else + { + // merge with the next higher range + // move i to end of range + i = m_ranges[i] = uit->second; + i = uit->second; + m_ranges.erase(uit); + } + } + else if (it->second == i) + { + // if the next higher range is too high. + if (uit == m_ranges.end() || uit->first > _m.second) + { + // merge with the next lower range + m_ranges[it->first] = _m.second; + break; + } + else + { + // merge with both next lower & next higher. + i = m_ranges[it->first] = uit->second; + m_ranges.erase(uit); + } + } + else + i = it->second; + } + return *this; + } + + RangeMask& operator+=(T _i) + { + return operator+=(Range(_i, _i + 1)); + } + + bool contains(T _i) const + { + auto it = m_ranges.lower_bound(_i); + return it != m_ranges.end() && it->first <= _i && it->second > _i; + } + + class const_iterator + { + friend class RangeMask; + + public: + const_iterator() {} + + T operator*() const { return m_value; } + const_iterator& operator++() { if (m_owner) m_value = m_owner->next(m_value); return *this; } + const_iterator operator++(int) { auto ret = *this; if (m_owner) m_value = m_owner->next(m_value); return ret; } + + bool operator==(const_iterator const& _i) const { return m_owner == _i.m_owner && m_value == _i.m_value; } + bool operator!=(const_iterator const& _i) const { return !operator==(_i); } + bool operator<(const_iterator const& _i) const { return m_value < _i.m_value; } + + private: + const_iterator(RangeMask const& _m, bool _end): m_owner(&_m), m_value(_m.m_ranges.empty() || _end ? _m.m_all.second : _m.m_ranges.begin()->first) {} + + RangeMask const* m_owner = nullptr; + T m_value = 0; + }; + + const_iterator begin() const { return const_iterator(*this, false); } + const_iterator end() const { return const_iterator(*this, true); } + T next(T _t) const + { + _t++; + // find next item in range at least _t + auto uit = m_ranges.upper_bound(_t); // > _t + auto it = uit == m_ranges.begin() ? m_ranges.end() : std::prev(uit); + if (it != m_ranges.end() && it->first <= _t && it->second > _t) + return _t; + return uit == m_ranges.end() ? m_all.second : uit->first; + } + +private: + UnsignedRange m_all; + std::map m_ranges; +}; + +template inline std::ostream& operator<<(std::ostream& _out, RangeMask const& _r) +{ + _out << _r.m_all.first << "{ "; + for (auto const& i: _r.m_ranges) + _out << "[" << i.first << ", " << i.second << ") "; + _out << "}" << _r.m_all.second; + return _out; +} + +} diff --git a/libethereum/EthereumHost.h b/libethereum/EthereumHost.h index c875d74fe..b038e0785 100644 --- a/libethereum/EthereumHost.h +++ b/libethereum/EthereumHost.h @@ -46,88 +46,6 @@ namespace eth class TransactionQueue; class BlockQueue; -using UnsignedRange = std::pair; -using UnsignedRanges = std::vector; - -class RangeMask -{ -public: - RangeMask() {} - RangeMask(unsigned _begin, unsigned _end): m_ranges({{_begin, _end}}) {} - - RangeMask& operator+=(RangeMask const& _m) - { - for (auto const& i: _m.m_ranges) - operator+=(i); - return *this; - } - RangeMask& operator+=(UnsignedRange const& _m) - { - for (auto i = _m.first; i < _m.second;) - { - // for each number, we find the element equal or next lower. this must contain the value. - auto it = m_ranges.lower_bound(i); - auto uit = m_ranges.upper_bound(i + 1); - if (it == m_ranges.end() || it->second < i) - // lower range is too low to merge. - // if the next higher range is too high. - if (uit == m_ranges.end() || uit->first > _m.second) - { - // just create a new range - m_ranges[i] = _m.second; - break; - } - else - { - if (uit->first == i) - // move i to end of range - i = uit->second; - else - { - // merge with the next higher range - // move i to end of range - i = m_ranges[i] = uit->second; - i = uit->second; - m_ranges.erase(uit); - } - } - else if (it->second == i) - { - // if the next higher range is too high. - if (uit == m_ranges.end() || uit->first > _m.second) - { - // merge with the next lower range - m_ranges[it->first] = _m.second; - break; - } - else - { - // merge with both next lower & next higher. - i = m_ranges[it->first] = uit->second; - m_ranges.erase(uit); - } - } - else - i = it->second; - } - return *this; - } - - RangeMask& operator+=(unsigned _i) - { - return operator+=(UnsignedRange(_i, _i + 1)); - } - - bool contains(unsigned _i) const - { - auto it = m_ranges.lower_bound(_i); - return it != m_ranges.end() && it->first <= _i && it->second > _i; - } - -private: - std::map m_ranges; -}; - #if 0 class DownloadSub { From e30615db9cc86c1ac644eb721794227d2686455a Mon Sep 17 00:00:00 2001 From: Christoph Jentzsch Date: Sat, 20 Sep 2014 02:05:04 +0200 Subject: [PATCH 192/223] Added arithmetic test --- test/vm.cpp | 58 +- test/vmArithmeticTestFiller.json | 2055 ++++++++++++++++++++++++++++++ 2 files changed, 2102 insertions(+), 11 deletions(-) create mode 100644 test/vmArithmeticTestFiller.json diff --git a/test/vm.cpp b/test/vm.cpp index e177855cb..5caba5cd2 100644 --- a/test/vm.cpp +++ b/test/vm.cpp @@ -169,6 +169,15 @@ public: a.push_back(toString(_v)); } + static string u256toHex(u256 const& _v) + { + std::ostringstream oss; + oss << std::hex << std::setfill('0') << std::setw(2) << _v; + string ret = "0x" + oss.str(); + std::transform(ret.begin(), ret.end(), ret.begin(), ::tolower); + return ret; + } + mObject exportEnv() { mObject ret; @@ -219,13 +228,13 @@ public: if (li) store[curKey] = curVal; li = s.first; - curKey = toString(li); + curKey = u256toHex(li); curVal = mArray(); } else for (; li != s.first; ++li) curVal.push_back(0); - push(curVal, s.second); + curVal.push_back(u256toHex(s.second)); ++li; } if (li) @@ -476,15 +485,15 @@ BOOST_AUTO_TEST_CASE(vm_tests) { // Populate tests first: // try - { - cnote << "Populating VM tests..."; - json_spirit::mValue v; - string s = asString(contents("../../../cpp-ethereum/test/vmtests.json")); - BOOST_REQUIRE_MESSAGE(s.length() > 0, "Contents of 'vmtests.json' is empty."); - json_spirit::read_string(s, v); - dev::test::doTests(v, true); - writeFile("../../../tests/vmtests.json", asBytes(json_spirit::write_string(v, true))); - } +// { +// cnote << "Populating VM tests..."; +// json_spirit::mValue v; +// string s = asString(contents("../../../cpp-ethereum/test/vmtests.json")); +// BOOST_REQUIRE_MESSAGE(s.length() > 0, "Contents of 'vmtests.json' is empty."); +// json_spirit::read_string(s, v); +// dev::test::doTests(v, true); +// writeFile("../../../tests/vmtests.json", asBytes(json_spirit::write_string(v, true))); +// } /* catch (std::exception const& e) { BOOST_ERROR("Failed VM Test with Exception: " << e.what()); @@ -504,3 +513,30 @@ BOOST_AUTO_TEST_CASE(vm_tests) BOOST_ERROR("Failed VM Test with Exception: " << e.what()); } } + +BOOST_AUTO_TEST_CASE(vmArithmeticTest) +{ + +// cnote << "Populating VM tests..."; +// json_spirit::mValue v; +// string s = asString(contents("../../../cpp-ethereum/test/vmArithmeticTestFiller.json")); +// BOOST_REQUIRE_MESSAGE(s.length() > 0, "Contents of 'vmtests.json' is empty."); +// json_spirit::read_string(s, v); +// dev::test::doTests(v, true); +// writeFile("../../../tests/vmArithmeticTest.json", asBytes(json_spirit::write_string(v, true))); + + + try + { + cnote << "Testing VM arithmetic commands..."; + json_spirit::mValue v; + string s = asString(contents("../../../tests/vmArithmeticTest.json")); + BOOST_REQUIRE_MESSAGE(s.length() > 0, "Contents of 'vmArithmeticTest.json' is empty. Have you cloned the 'tests' repo branch develop?"); + json_spirit::read_string(s, v); + dev::test::doTests(v, false); + } + catch (std::exception const& e) + { + BOOST_ERROR("Failed VM arithmetic test with Exception: " << e.what()); + } +} diff --git a/test/vmArithmeticTestFiller.json b/test/vmArithmeticTestFiller.json new file mode 100644 index 000000000..d7826d198 --- /dev/null +++ b/test/vmArithmeticTestFiller.json @@ -0,0 +1,2055 @@ +{ + "stop": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "0x00", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "add0": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (+ 115792089237316195423570985008687907853269984665640564039457584007913129639935 115792089237316195423570985008687907853269984665640564039457584007913129639935) }", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "add1": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (+ 115792089237316195423570985008687907853269984665640564039457584007913129639935 4) }", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "add2": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (+ 115792089237316195423570985008687907853269984665640564039457584007913129639936 1) }", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "add3": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (+ 0 0) }", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "add4": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (+ 1 115792089237316195423570985008687907853269984665640564039457584007913129639935) }", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + + "mul0": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (* 2 3) }", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + + "mul1": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (* 115792089237316195423570985008687907853269984665640564039457584007913129639935 115792089237316195423570985008687907853269984665640564039457584007913129639935) }", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "mul2": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (* 0 23) }", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "mul3": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (* 23 1) }", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "sub0": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (- 23 1) }", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "sub1": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (- 2 3) }", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "sub2": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (- 0 23) }", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "sub3": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (- 0 115792089237316195423570985008687907853269984665640564039457584007913129639935) }", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "sub4": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (- 115792089237316195423570985008687907853269984665640564039457584007913129639935 0) }", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "divByZero": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (/ 2 0) }", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "divByNonZero0": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (/ 5 2) }", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "divByNonZero1": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (/ 23 24) }", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "divByNonZero2": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (/ 0 24) }", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "divByNonZero3": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (/ 1 1) }", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "sdivByZero0": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (SDIV (- 0 3) (- 0 0)) }", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "sdivByZero1": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (SDIV (- 0 115792089237316195423570985008687907853269984665640564039457584007913129639935) 0) }", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "sdiv0": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (SDIV (- 0 115792089237316195423570985008687907853269984665640564039457584007913129639935) 115792089237316195423570985008687907853269984665640564039457584007913129639935) }", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "sdiv1": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (SDIV 115792089237316195423570985008687907853269984665640564039457584007913129639935 (- 0 115792089237316195423570985008687907853269984665640564039457584007913129639935) ) }", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "sdiv2": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (SDIV (- 0 2) (- 0 4) ) }", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "sdiv3": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (SDIV 4 (- 0 2) ) }", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "mod0": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (% 2 3 ) }", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "mod1": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (% 115792089237316195423570985008687907853269984665640564039457584007913129639935 2 ) }", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "mod2": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (% 0 115792089237316195423570985008687907853269984665640564039457584007913129639935 ) }", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "mod3": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (% 3 0) }", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "mod4": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (% (- 0 2) 3) }", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "smod0": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (SMOD (- 0 5) (- 0 3))}", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "smod1": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (SMOD 5 (- 0 3))}", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "smod2": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (SMOD (- 0 5) 3)}", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "smod3": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (SMOD (- 0 2) 115792089237316195423570985008687907853269984665640564039457584007913129639935)}", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "smod4": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (SMOD (- 0 2) 0)}", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "exp0": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (EXP 2 2)}", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "exp1": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (EXP 115792089237316195423570985008687907853269984665640564039457584007913129639935 115792089237316195423570985008687907853269984665640564039457584007913129639934 )}", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "exp2": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (EXP 2147483647 2147483647)}", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "exp3": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (EXP 0 2147483647)}", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "exp4": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (EXP 2147483647 0)}", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "exp5": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (EXP 257 1)}", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "exp6": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (EXP 1 257)}", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "exp7": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (EXP 2 257)}", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + + "neg0": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (NEG 0 )}", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "neg1": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (NEG 2 )}", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "neg2": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (NEG 115792089237316195423570985008687907853269984665640564039457584007913129639935 )}", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "neg3": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (NEG (- 0 2) )}", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "neg4": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (NEG (- 0 115792089237316195423570985008687907853269984665640564039457584007913129639935) )}", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "neg5": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (NEG (- 0 0) )}", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "lt0": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (LT (- 0 2) 0 )}", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "lt1": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (LT 0 (- 0 2) )}", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "lt2": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (LT 115792089237316195423570985008687907853269984665640564039457584007913129639935 0 )}", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + + "lt3": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (LT 0 115792089237316195423570985008687907853269984665640564039457584007913129639935 )}", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "gt0": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] ( GT (- 0 2) 0 )}", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "gt1": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (GT 0 (- 0 2) )}", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "gt2": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (GT 115792089237316195423570985008687907853269984665640564039457584007913129639935 0 )}", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + + "gt3": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (GT 0 115792089237316195423570985008687907853269984665640564039457584007913129639935 )}", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "slt0": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (SLT (- 0 2) 0 )}", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "slt1": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (SLT 0 (- 0 2) )}", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "slt2": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (SLT 115792089237316195423570985008687907853269984665640564039457584007913129639935 0 )}", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + + "slt3": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (SLT 0 115792089237316195423570985008687907853269984665640564039457584007913129639935 )}", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "slt4": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (SLT (- 0 5) (- 0 3) )}", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "sgt0": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (SGT (- 0 2) 0 )}", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "sgt1": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (SGT 0 (- 0 2) )}", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "sgt2": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (SGT 115792089237316195423570985008687907853269984665640564039457584007913129639935 0 )}", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + + "sgt3": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (SGT 0 115792089237316195423570985008687907853269984665640564039457584007913129639935 )}", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "sgt4": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (SGT (- 0 5) (- 0 3) )}", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "eq0": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (EQ (- 0 5) (- 0 3) )}", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "eq1": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (EQ 0 0)}", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "eq2": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (EQ 115792089237316195423570985008687907853269984665640564039457584007913129639935 115792089237316195423570985008687907853269984665640564039457584007913129639935 )}", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "not0": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (NOT 115792089237316195423570985008687907853269984665640564039457584007913129639935 )}", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + "not1": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "1000000", + "currentDifficulty" : "256", + "currentTimestamp" : 1, + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : 0, + "code" : "{ [[ 0 ]] (NOT 0 )}", + "storage": {} + } + }, + "exec" : { + "address" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "origin" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "caller" : "cd1722f3947def4cf144679da39c4c32bdc35681", + "value" : "1000000000000000000", + "data" : "", + "gasPrice" : "100000000000000", + "gas" : "10000" + } + }, + + + +} From 8ec86bb3629b0620ebfdee8fe1ebc91dca24a9a0 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Fri, 19 Sep 2014 20:39:49 -0500 Subject: [PATCH 193/223] Add DownloadMan for download management. --- exp/main.cpp | 24 ++++++- libdevcore/RangeMask.cpp | 25 ++++++-- libdevcore/RangeMask.h | 13 +++- libethereum/DownloadMan.cpp | 70 +++++++++++++++++++++ libethereum/DownloadMan.h | 118 +++++++++++++++++++++++++++++++++++ libethereum/EthereumHost.cpp | 5 +- libethereum/EthereumHost.h | 40 +----------- libethereum/EthereumPeer.h | 3 + 8 files changed, 250 insertions(+), 48 deletions(-) create mode 100644 libethereum/DownloadMan.cpp create mode 100644 libethereum/DownloadMan.h diff --git a/exp/main.cpp b/exp/main.cpp index a88023fae..40804958e 100644 --- a/exp/main.cpp +++ b/exp/main.cpp @@ -19,12 +19,14 @@ * @date 2014 * Ethereum client. */ +#include #include #include #include #include #include #include +#include #include using namespace std; using namespace dev; @@ -34,7 +36,25 @@ using namespace dev::shh; int main() { - RangeMask m(0, 100); + DownloadMan man; + DownloadSub s0(&man); + DownloadSub s1(&man); + DownloadSub s2(&man); + man.resetToChain(h256s({u256(0), u256(1), u256(2), u256(3), u256(4), u256(5), u256(6), u256(7), u256(8)})); + cnote << s0.nextFetch(2); + cnote << s1.nextFetch(2); + cnote << s2.nextFetch(2); + s0.noteBlock(u256(0)); + s0.doneFetch(); + cnote << s0.nextFetch(2); + s1.noteBlock(u256(2)); + s1.noteBlock(u256(3)); + s1.doneFetch(); + cnote << s1.nextFetch(2); + s0.doneFetch(); + cnote << s0.nextFetch(2); + +/* RangeMask m(0, 100); cnote << m; m += UnsignedRange(3, 10); cnote << m; @@ -45,7 +65,7 @@ int main() cnote << ~m; cnote << (~m).lowest(10); for (auto i: (~m).lowest(10)) - cnote << i; + cnote << i;*/ return 0; } diff --git a/libdevcore/RangeMask.cpp b/libdevcore/RangeMask.cpp index 5ba4a1f73..5317e00e8 100644 --- a/libdevcore/RangeMask.cpp +++ b/libdevcore/RangeMask.cpp @@ -1,5 +1,22 @@ -#include "RangeMask.h" +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. -RangeMask::RangeMask() -{ -} + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file RangeMask.cpp + * @author Gav Wood + * @date 2014 + */ + +#include "RangeMask.h" diff --git a/libdevcore/RangeMask.h b/libdevcore/RangeMask.h index ba8594779..d26e034f4 100644 --- a/libdevcore/RangeMask.h +++ b/libdevcore/RangeMask.h @@ -24,6 +24,7 @@ #include #include #include +#include namespace dev { @@ -52,7 +53,7 @@ public: { RangeMask ret(m_all); for (auto i = m_ranges.begin(); i != m_ranges.end() && _items; ++i) - _items -= (ret.m_ranges[i->first] = std::min(i->first + _items, i->second)); + _items -= (ret.m_ranges[i->first] = std::min(i->first + _items, i->second)) - i->first; return ret; } @@ -140,6 +141,16 @@ public: return it != m_ranges.end() && it->first <= _i && it->second > _i; } + bool empty() const + { + return m_ranges.empty(); + } + + void clear() + { + m_ranges.clear(); + } + class const_iterator { friend class RangeMask; diff --git a/libethereum/DownloadMan.cpp b/libethereum/DownloadMan.cpp new file mode 100644 index 000000000..65ad6adc4 --- /dev/null +++ b/libethereum/DownloadMan.cpp @@ -0,0 +1,70 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file DownloadMan.cpp + * @author Gav Wood + * @date 2014 + */ + +#include "DownloadMan.h" +using namespace std; +using namespace dev; +using namespace dev::eth; + +DownloadSub::DownloadSub(DownloadMan* _man): m_man(_man) +{ + WriteGuard l(m_man->x_subs); + m_man->m_subs.insert(this); +} + +DownloadSub::~DownloadSub() +{ + if (m_man) + { + WriteGuard l(m_man->x_subs); + m_man->m_subs.erase(this); + } +} + +h256s DownloadSub::nextFetch(unsigned _n) +{ + Guard l(m_fetch); + + m_asked.clear(); + if (!m_man) + return h256s(); + + m_asked = (~(m_man->taken() + m_attempted)).lowest(_n); + if (m_asked.empty()) + m_asked = (~(m_man->taken(true) + m_attempted)).lowest(_n); + m_attempted += m_asked; + m_indices.clear(); + h256s ret; + for (auto i: m_asked) + { + ret.push_back(m_man->m_chain[i]); + m_indices[ret.back()] = i; + } + return ret; +} + +void DownloadSub::noteBlock(h256 _hash) +{ + Guard l(m_fetch); + if (m_man && m_indices.count(_hash)) + m_man->m_blocksGot += m_indices[_hash]; +} + diff --git a/libethereum/DownloadMan.h b/libethereum/DownloadMan.h new file mode 100644 index 000000000..056f089b6 --- /dev/null +++ b/libethereum/DownloadMan.h @@ -0,0 +1,118 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file DownloadMan.h + * @author Gav Wood + * @date 2014 + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace dev +{ + +namespace eth +{ + +class DownloadMan; + +class DownloadSub +{ + friend class DownloadMan; + +public: + DownloadSub(DownloadMan* _man); + ~DownloadSub(); + + /// Finished last fetch - grab the next bunch of block hashes to download. + h256s nextFetch(unsigned _n); + + /// Note that we've received a particular block. + void noteBlock(h256 _hash); + + void doneFetch() { nextFetch(0); } + +private: + void resetFetch() // Called by DownloadMan when we need to reset the download. + { + Guard l(m_fetch); + m_indices.clear(); + m_asked.clear(); + m_attempted.clear(); + } + + DownloadMan* m_man = nullptr; + + Mutex m_fetch; + std::map m_indices; + RangeMask m_asked; + RangeMask m_attempted; +}; + +class DownloadMan +{ + friend class DownloadSub; + +public: + ~DownloadMan() + { + for (auto i: m_subs) + i->m_man = nullptr; + } + + void resetToChain(h256s const& _chain) + { + { + ReadGuard l(x_subs); + for (auto i: m_subs) + i->resetFetch(); + } + + m_chain = _chain; + m_blocksGot = RangeMask(0, m_chain.size()); + } + + RangeMask taken(bool _desperate = false) const + { + auto ret = m_blocksGot; + if (!_desperate) + { + ReadGuard l(x_subs); + for (auto i: m_subs) + ret += i->m_asked; + } + return ret; + } + +private: + h256s m_chain; + RangeMask m_blocksGot; + + mutable SharedMutex x_subs; + std::set m_subs; +}; + +} + +} diff --git a/libethereum/EthereumHost.cpp b/libethereum/EthereumHost.cpp index 2add39482..bbf673419 100644 --- a/libethereum/EthereumHost.cpp +++ b/libethereum/EthereumHost.cpp @@ -15,9 +15,7 @@ along with cpp-ethereum. If not, see . */ /** @file EthereumHost.cpp - * @authors: - * Gav Wood - * Eric Lombrozo + * @author Gav Wood * @date 2014 */ @@ -34,6 +32,7 @@ #include "TransactionQueue.h" #include "BlockQueue.h" #include "EthereumPeer.h" +#include "DownloadMan.h" using namespace std; using namespace dev; using namespace dev::eth; diff --git a/libethereum/EthereumHost.h b/libethereum/EthereumHost.h index b038e0785..3021f26ee 100644 --- a/libethereum/EthereumHost.h +++ b/libethereum/EthereumHost.h @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include "CommonNet.h" @@ -46,44 +47,6 @@ namespace eth class TransactionQueue; class BlockQueue; -#if 0 -class DownloadSub -{ - friend class DownloadMan; - -public: - h256s nextFetch(); - void noteBlock(h256 _hash, bytesConstRef _data); - -private: - void resetFetch(); // Called by DownloadMan when we need to reset the download. - - DownloadMan* m_man; - - Mutex m_fetch; - h256s m_fetching; - h256s m_activeGet; - bool m_killFetch; - RangeMask m_attempted; -}; - -class DownloadMan -{ - friend class DownloadSub; - -public: - void resetToChain(h256s const& _chain); - -private: - void cancelFetch(DownloadSub* ); - void noteBlock(h256 _hash, bytesConstRef _data); - - h256s m_chain; - RangeMask m_complete; - std::map m_fetching; -}; -#endif - /** * @brief The EthereumHost class * @warning None of this is thread-safe. You have been warned. @@ -156,6 +119,7 @@ private: mutable std::mutex x_blocksNeeded; u256 m_totalDifficultyOfNeeded; h256s m_blocksNeeded; + h256Set m_blocksOnWay; h256 m_latestBlockSent; diff --git a/libethereum/EthereumPeer.h b/libethereum/EthereumPeer.h index 7248fabd0..adf82c215 100644 --- a/libethereum/EthereumPeer.h +++ b/libethereum/EthereumPeer.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include "CommonNet.h" @@ -85,6 +86,8 @@ private: h256Set m_askedBlocks; ///< The blocks for which we sent the last GetBlocks for but haven't received a corresponding Blocks. bool m_askedBlocksChanged = true; + RangeMask m_blocksAsked; + bool m_requireTransactions; Mutex x_knownBlocks; From b883f0c135b97586c125037a120c670f40b912d1 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 20 Sep 2014 08:09:27 -0500 Subject: [PATCH 194/223] User DNS & poc-5.ethdev.com for peerserver. --- alethzero/MainWin.cpp | 22 +++------------------- exp/main.cpp | 6 +++--- libethereum/DownloadMan.cpp | 2 +- libethereum/DownloadMan.h | 2 +- libethereum/EthereumHost.h | 3 +++ libethereum/EthereumPeer.cpp | 5 +++-- libethereum/EthereumPeer.h | 3 +++ libp2p/Host.cpp | 13 +++++++++++-- libp2p/Host.h | 1 + 9 files changed, 29 insertions(+), 28 deletions(-) diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index 5842664b3..f940a2961 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -97,25 +97,9 @@ Main::Main(QWidget *parent) : // ui->log->addItem(QString::fromStdString(s)); }; -#if 0&Ð_DEBUG - m_servers.append("192.168.0.10:30301"); -#else - int pocnumber = QString(dev::Version).section('.', 1, 1).toInt(); - if (pocnumber == 5) - m_servers.push_back("54.72.69.180:30303"); - else if (pocnumber == 6) - m_servers.push_back("54.76.56.74:30303"); - else - { - connect(&m_webCtrl, &QNetworkAccessManager::finished, [&](QNetworkReply* _r) - { - m_servers = QString::fromUtf8(_r->readAll()).split("\n", QString::SkipEmptyParts); - }); - QNetworkRequest r(QUrl("http://www.ethereum.org/servers.poc" + QString::number(pocnumber) + ".txt")); - r.setHeader(QNetworkRequest::UserAgentHeader, "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1712.0 Safari/537.36"); - m_webCtrl.get(r); - srand(time(0)); - } + m_servers.append(QString::fromStdString(Host::pocHost() + ":30303")); +#if ETH_DEBUG + m_servers.append("localhost:30303"); #endif cerr << "State root: " << BlockChain::genesis().stateRoot << endl; diff --git a/exp/main.cpp b/exp/main.cpp index 40804958e..1f29ab207 100644 --- a/exp/main.cpp +++ b/exp/main.cpp @@ -37,9 +37,9 @@ using namespace dev::shh; int main() { DownloadMan man; - DownloadSub s0(&man); - DownloadSub s1(&man); - DownloadSub s2(&man); + DownloadSub s0(man); + DownloadSub s1(man); + DownloadSub s2(man); man.resetToChain(h256s({u256(0), u256(1), u256(2), u256(3), u256(4), u256(5), u256(6), u256(7), u256(8)})); cnote << s0.nextFetch(2); cnote << s1.nextFetch(2); diff --git a/libethereum/DownloadMan.cpp b/libethereum/DownloadMan.cpp index 65ad6adc4..115813a31 100644 --- a/libethereum/DownloadMan.cpp +++ b/libethereum/DownloadMan.cpp @@ -24,7 +24,7 @@ using namespace std; using namespace dev; using namespace dev::eth; -DownloadSub::DownloadSub(DownloadMan* _man): m_man(_man) +DownloadSub::DownloadSub(DownloadMan& _man): m_man(&_man) { WriteGuard l(m_man->x_subs); m_man->m_subs.insert(this); diff --git a/libethereum/DownloadMan.h b/libethereum/DownloadMan.h index 056f089b6..6375f69e4 100644 --- a/libethereum/DownloadMan.h +++ b/libethereum/DownloadMan.h @@ -42,7 +42,7 @@ class DownloadSub friend class DownloadMan; public: - DownloadSub(DownloadMan* _man); + DownloadSub(DownloadMan& _man); ~DownloadSub(); /// Finished last fetch - grab the next bunch of block hashes to download. diff --git a/libethereum/EthereumHost.h b/libethereum/EthereumHost.h index 3021f26ee..2ff8e35d4 100644 --- a/libethereum/EthereumHost.h +++ b/libethereum/EthereumHost.h @@ -35,6 +35,7 @@ #include #include "CommonNet.h" #include "EthereumPeer.h" +#include "DownloadMan.h" namespace dev { @@ -122,6 +123,8 @@ private: h256Set m_blocksOnWay; + DownloadMan m_man; + h256 m_latestBlockSent; h256Set m_transactionsSent; }; diff --git a/libethereum/EthereumPeer.cpp b/libethereum/EthereumPeer.cpp index 7ccf6939b..557c67242 100644 --- a/libethereum/EthereumPeer.cpp +++ b/libethereum/EthereumPeer.cpp @@ -35,7 +35,8 @@ using namespace p2p; #define clogS(X) dev::LogOutputStream(false) << "| " << std::setw(2) << session()->socketId() << "] " EthereumPeer::EthereumPeer(Session* _s, HostCapabilityFace* _h): - Capability(_s, _h) + Capability(_s, _h), + m_sub(host()->m_man) { sendStatus(); } @@ -90,7 +91,7 @@ void EthereumPeer::tryGrabbingHashChain() u256 td = max(host()->m_chain.details().totalDifficulty, host()->m_totalDifficultyOfNeeded); clogS(NetAllDetail) << "Attempt chain-grab? Latest:" << c.abridged() << ", number:" << n << ", TD: max(" << host()->m_chain.details().totalDifficulty << "," << host()->m_totalDifficultyOfNeeded << ") versus " << m_totalDifficulty; - if (td > m_totalDifficulty) + if (td >= m_totalDifficulty) { clogS(NetAllDetail) << "No. Our chain is better."; m_grabbing = Grabbing::Nothing; diff --git a/libethereum/EthereumPeer.h b/libethereum/EthereumPeer.h index adf82c215..960f1f482 100644 --- a/libethereum/EthereumPeer.h +++ b/libethereum/EthereumPeer.h @@ -32,6 +32,7 @@ #include #include #include "CommonNet.h" +#include "DownloadMan.h" namespace dev { @@ -94,6 +95,8 @@ private: std::set m_knownBlocks; std::set m_knownTransactions; std::mutex x_knownTransactions; + + DownloadSub m_sub; }; } diff --git a/libp2p/Host.cpp b/libp2p/Host.cpp index 15727f9c1..2a27a4b85 100644 --- a/libp2p/Host.cpp +++ b/libp2p/Host.cpp @@ -34,6 +34,7 @@ #include #include #include +#include #include #include #include "Session.h" @@ -349,12 +350,20 @@ void Host::ensureAccepting() } } +string Host::pocHost() +{ + vector strs; + boost::split(strs, dev::Version, boost::is_any_of(".")); + return "poc-" + strs[1] + ".ethdev.com"; +} + void Host::connect(std::string const& _addr, unsigned short _port) noexcept { try { - // TODO: actual DNS lookup. - connect(bi::tcp::endpoint(bi::address::from_string(_addr), _port)); + bi::tcp::resolver r(m_ioService); + connect(r.resolve({_addr, toString(_port)})->endpoint()); +// connect(bi::tcp::endpoint(bi::address::from_string(_addr), _port)); } catch (exception const& e) { diff --git a/libp2p/Host.h b/libp2p/Host.h index e8b95e2a8..f5f2f9e97 100644 --- a/libp2p/Host.h +++ b/libp2p/Host.h @@ -82,6 +82,7 @@ public: template std::shared_ptr cap() const { try { return std::static_pointer_cast(m_capabilities.at(T::staticName())); } catch (...) { return nullptr; } } /// Connect to a peer explicitly. + static std::string pocHost(); void connect(std::string const& _addr, unsigned short _port = 30303) noexcept; void connect(bi::tcp::endpoint const& _ep); From 5633006e2fe2e623f690275208f2abc5eda4a067 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 20 Sep 2014 08:15:41 -0500 Subject: [PATCH 195/223] Everything using PoC servers. --- alethzero/MainWin.cpp | 2 +- eth/main.cpp | 21 ++++++++++----------- third/MainWin.cpp | 1 + 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index f940a2961..8d8d4b854 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -97,10 +97,10 @@ Main::Main(QWidget *parent) : // ui->log->addItem(QString::fromStdString(s)); }; - m_servers.append(QString::fromStdString(Host::pocHost() + ":30303")); #if ETH_DEBUG m_servers.append("localhost:30303"); #endif + m_servers.append(QString::fromStdString(Host::pocHost() + ":30303")); cerr << "State root: " << BlockChain::genesis().stateRoot << endl; cerr << "Block Hash: " << sha3(BlockChain::createGenesisBlock()) << endl; diff --git a/eth/main.cpp b/eth/main.cpp index 215350c09..58b3b6d05 100644 --- a/eth/main.cpp +++ b/eth/main.cpp @@ -99,6 +99,7 @@ void help() << "Usage eth [OPTIONS] " << endl << "Options:" << endl << " -a,--address Set the coinbase (mining payout) address to addr (default: auto)." << endl + << " -b,--bootstrap Connect to the default Ethereum peerserver." << endl << " -c,--client-name Add a name to your client's version string (default: blank)." << endl << " -d,--db-path Load database from path (default: ~/.ethereum " << endl << " /Etherum or Library/Application Support/Ethereum)." << endl @@ -134,17 +135,8 @@ string credits(bool _interactive = false) if (_interactive) { - string vs = toString(dev::Version); - vs = vs.substr(vs.find_first_of('.') + 1)[0]; - int pocnumber = stoi(vs); - string m_servers; - if (pocnumber == 4) - m_servers = "54.72.31.55"; - else - m_servers = "54.72.69.180"; - cout << "Type 'netstart 30303' to start networking" << endl; - cout << "Type 'connect " << m_servers << " 30303' to connect" << endl; + cout << "Type 'connect " << Host::pocHost() << " 30303' to connect" << endl; cout << "Type 'exit' to quit" << endl << endl; } return cout.str(); @@ -188,6 +180,7 @@ int main(int argc, char** argv) int jsonrpc = 8080; #endif string publicIP; + bool bootstrap = false; bool upnp = true; bool useLocal = false; bool forceMining = false; @@ -264,6 +257,8 @@ int main(int argc, char** argv) return -1; } } + else if (arg == "-b" || arg == "--bootstrap") + bootstrap = true; else if (arg == "-f" || arg == "--force-mining") forceMining = true; else if (arg == "-i" || arg == "--interactive") @@ -317,7 +312,11 @@ int main(int argc, char** argv) cout << "Address: " << endl << toHex(us.address().asArray()) << endl; web3.startNetwork(); - web3.connect(remoteHost, remotePort); + + if (bootstrap) + web3.connect(Host::pocHost()); + if (remoteHost.size()) + web3.connect(remoteHost, remotePort); #if ETH_JSONRPC auto_ptr jsonrpcServer; diff --git a/third/MainWin.cpp b/third/MainWin.cpp index 5e147bbda..be8420426 100644 --- a/third/MainWin.cpp +++ b/third/MainWin.cpp @@ -100,6 +100,7 @@ Main::Main(QWidget *parent) : connect(ui->ourAccounts->model(), SIGNAL(rowsMoved(const QModelIndex &, int, int, const QModelIndex &, int)), SLOT(ourAccountsRowsMoved())); m_web3.reset(new WebThreeDirect("Third", getDataDir() + "/Third", false, {"eth", "shh"})); + m_web3->connect(Host::pocHost()); connect(ui->webView, &QWebView::loadStarted, [this]() { From aa1da892fbdeeef39c34533d7031ed1ab096ba2c Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 20 Sep 2014 10:19:50 -0500 Subject: [PATCH 196/223] Debug code for peer discovery. --- libp2p/Host.cpp | 1 + libp2p/Session.cpp | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/libp2p/Host.cpp b/libp2p/Host.cpp index 2a27a4b85..e94a3215d 100644 --- a/libp2p/Host.cpp +++ b/libp2p/Host.cpp @@ -312,6 +312,7 @@ std::map Host::potentialPeers() if (auto j = i.second.lock()) { auto ep = j->endpoint(); + cdebug << "Checking potential peer" << j->m_listenPort << j->endpoint() << isPrivateAddress(ep.address()) << ep.port() << j->m_id.abridged(); // Skip peers with a listen port of zero or are on a private network bool peerOnNet = (j->m_listenPort != 0 && (!isPrivateAddress(ep.address()) || m_netPrefs.localNetworking)); if (peerOnNet && ep.port() && j->m_id) diff --git a/libp2p/Session.cpp b/libp2p/Session.cpp index f117b426e..6f1edd693 100644 --- a/libp2p/Session.cpp +++ b/libp2p/Session.cpp @@ -159,10 +159,11 @@ bool Session::interpret(RLP const& _r) bi::address_v4 peerAddress(_r[i][0].toHash>().asArray()); auto ep = bi::tcp::endpoint(peerAddress, _r[i][1].toInt()); h512 id = _r[i][2].toHash(); + clogS(NetAllDetail) << "Checking: " << ep << "(" << id.abridged() << ")"; + if (isPrivateAddress(peerAddress) && !m_server->m_netPrefs.localNetworking) goto CONTINUE; - clogS(NetAllDetail) << "Checking: " << ep << "(" << id.abridged() << ")"; // check that it's not us or one we already know: if (id && (m_server->m_id == id || m_server->havePeer(id) || m_server->m_incomingPeers.count(id))) From f791775e585ddb6ea5d4b5f5c811668181619bcb Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 20 Sep 2014 11:04:51 -0500 Subject: [PATCH 197/223] Allow peers to tell us of their actual IPs. --- libp2p/Host.cpp | 2 +- libp2p/Session.cpp | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/libp2p/Host.cpp b/libp2p/Host.cpp index e94a3215d..1100eb54d 100644 --- a/libp2p/Host.cpp +++ b/libp2p/Host.cpp @@ -312,7 +312,7 @@ std::map Host::potentialPeers() if (auto j = i.second.lock()) { auto ep = j->endpoint(); - cdebug << "Checking potential peer" << j->m_listenPort << j->endpoint() << isPrivateAddress(ep.address()) << ep.port() << j->m_id.abridged(); + cnote << "Checking potential peer" << j->m_listenPort << j->endpoint() << isPrivateAddress(ep.address()) << ep.port() << j->m_id.abridged(); // Skip peers with a listen port of zero or are on a private network bool peerOnNet = (j->m_listenPort != 0 && (!isPrivateAddress(ep.address()) || m_netPrefs.localNetworking)); if (peerOnNet && ep.port() && j->m_id) diff --git a/libp2p/Session.cpp b/libp2p/Session.cpp index 6f1edd693..3bff33490 100644 --- a/libp2p/Session.cpp +++ b/libp2p/Session.cpp @@ -164,9 +164,8 @@ bool Session::interpret(RLP const& _r) if (isPrivateAddress(peerAddress) && !m_server->m_netPrefs.localNetworking) goto CONTINUE; - // check that it's not us or one we already know: - if (id && (m_server->m_id == id || m_server->havePeer(id) || m_server->m_incomingPeers.count(id))) + if (id && (m_server->m_id == id || (m_id == id && isPrivateAddress(endpoint().address())) || m_server->m_incomingPeers.count(id))) goto CONTINUE; // check that we're not already connected to addr: From c236d01c7c2dec8d5aa7f648dfa3be5f5755d978 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 20 Sep 2014 11:13:42 -0500 Subject: [PATCH 198/223] More network debugging. --- libp2p/Session.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libp2p/Session.cpp b/libp2p/Session.cpp index 3bff33490..e81192f00 100644 --- a/libp2p/Session.cpp +++ b/libp2p/Session.cpp @@ -159,13 +159,13 @@ bool Session::interpret(RLP const& _r) bi::address_v4 peerAddress(_r[i][0].toHash>().asArray()); auto ep = bi::tcp::endpoint(peerAddress, _r[i][1].toInt()); h512 id = _r[i][2].toHash(); - clogS(NetAllDetail) << "Checking: " << ep << "(" << id.abridged() << ")"; + clogS(NetAllDetail) << "Checking: " << ep << "(" << id.abridged() << ")" << isPrivateAddress(peerAddress) << m_id.abridged() << id.abridged() << isPrivateAddress(endpoint().address()) << m_server->m_incomingPeers.count(id) << id << m_server->m_incomingPeers.count(id); if (isPrivateAddress(peerAddress) && !m_server->m_netPrefs.localNetworking) goto CONTINUE; // check that it's not us or one we already know: - if (id && (m_server->m_id == id || (m_id == id && isPrivateAddress(endpoint().address())) || m_server->m_incomingPeers.count(id))) + if (!(m_id == id && isPrivateAddress(endpoint().address()) && (!m_server->m_incomingPeers.count(id) || isPrivateAddress(m_server->m_incomingPeers.at(id).first.address()))) && (!id || m_server->m_id == id || m_server->m_incomingPeers.count(id))) goto CONTINUE; // check that we're not already connected to addr: From 88bebb4c3bd46b2c9c1d4c81862b2254bc3a259f Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 20 Sep 2014 11:20:01 -0500 Subject: [PATCH 199/223] More debug stuff. --- libp2p/Session.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libp2p/Session.cpp b/libp2p/Session.cpp index e81192f00..1fa09b074 100644 --- a/libp2p/Session.cpp +++ b/libp2p/Session.cpp @@ -159,7 +159,7 @@ bool Session::interpret(RLP const& _r) bi::address_v4 peerAddress(_r[i][0].toHash>().asArray()); auto ep = bi::tcp::endpoint(peerAddress, _r[i][1].toInt()); h512 id = _r[i][2].toHash(); - clogS(NetAllDetail) << "Checking: " << ep << "(" << id.abridged() << ")" << isPrivateAddress(peerAddress) << m_id.abridged() << id.abridged() << isPrivateAddress(endpoint().address()) << m_server->m_incomingPeers.count(id) << id << m_server->m_incomingPeers.count(id); + clogS(NetAllDetail) << "Checking: " << ep << "(" << id.abridged() << ")" << isPrivateAddress(peerAddress) << m_id.abridged() << isPrivateAddress(endpoint().address()) << m_server->m_incomingPeers.count(id) << (m_server->m_incomingPeers.count(id) ? isPrivateAddress(m_server->m_incomingPeers.at(id).first.address()) : -1); if (isPrivateAddress(peerAddress) && !m_server->m_netPrefs.localNetworking) goto CONTINUE; From e480eadaa2e2519e72709b060a70a7ffa48f7581 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 20 Sep 2014 11:23:28 -0500 Subject: [PATCH 200/223] Probable fix for local/global peer discovery. --- libp2p/Host.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/libp2p/Host.cpp b/libp2p/Host.cpp index 1100eb54d..aabd22cdc 100644 --- a/libp2p/Host.cpp +++ b/libp2p/Host.cpp @@ -315,6 +315,11 @@ std::map Host::potentialPeers() cnote << "Checking potential peer" << j->m_listenPort << j->endpoint() << isPrivateAddress(ep.address()) << ep.port() << j->m_id.abridged(); // Skip peers with a listen port of zero or are on a private network bool peerOnNet = (j->m_listenPort != 0 && (!isPrivateAddress(ep.address()) || m_netPrefs.localNetworking)); + if (!peerOnNet && m_incomingPeers.count(j->m_id)) + { + ep = m_incomingPeers.at(j->m_id).first; + peerOnNet = (j->m_listenPort != 0 && (!isPrivateAddress(ep.address()) || m_netPrefs.localNetworking)); + } if (peerOnNet && ep.port() && j->m_id) ret.insert(make_pair(i.first, ep)); } From ccde7abcf6935506f582e7b7d0a0ef70cabffeca Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 20 Sep 2014 11:26:14 -0500 Subject: [PATCH 201/223] JSON RPC fix. --- eth/main.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/eth/main.cpp b/eth/main.cpp index 58b3b6d05..a9ea43f14 100644 --- a/eth/main.cpp +++ b/eth/main.cpp @@ -177,7 +177,7 @@ int main(int argc, char** argv) unsigned peers = 5; bool interactive = false; #if ETH_JSONRPC - int jsonrpc = 8080; + int jsonrpc = -1; #endif string publicIP; bool bootstrap = false; @@ -265,7 +265,7 @@ int main(int argc, char** argv) interactive = true; #if ETH_JSONRPC else if ((arg == "-j" || arg == "--json-rpc")) - jsonrpc = jsonrpc ? jsonrpc : 8080; + jsonrpc = jsonrpc == -1 ? 8080 : jsonrpc; else if (arg == "--json-rpc-port" && i + 1 < argc) jsonrpc = atoi(argv[++i]); #endif From 900d7eb0ccccc7204384f4b5ad790e4e4376ea2d Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 20 Sep 2014 11:32:55 -0500 Subject: [PATCH 202/223] Remove some verboisity. --- libp2p/Host.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libp2p/Host.cpp b/libp2p/Host.cpp index aabd22cdc..eb3d99c0b 100644 --- a/libp2p/Host.cpp +++ b/libp2p/Host.cpp @@ -312,7 +312,7 @@ std::map Host::potentialPeers() if (auto j = i.second.lock()) { auto ep = j->endpoint(); - cnote << "Checking potential peer" << j->m_listenPort << j->endpoint() << isPrivateAddress(ep.address()) << ep.port() << j->m_id.abridged(); +// cnote << "Checking potential peer" << j->m_listenPort << j->endpoint() << isPrivateAddress(ep.address()) << ep.port() << j->m_id.abridged(); // Skip peers with a listen port of zero or are on a private network bool peerOnNet = (j->m_listenPort != 0 && (!isPrivateAddress(ep.address()) || m_netPrefs.localNetworking)); if (!peerOnNet && m_incomingPeers.count(j->m_id)) From 0ecfc0bc6d4666deacda7f6f9c703e177189dc22 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 20 Sep 2014 11:41:43 -0500 Subject: [PATCH 203/223] Minor debug fix. --- libp2p/Session.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libp2p/Session.cpp b/libp2p/Session.cpp index 1fa09b074..252a1e8e6 100644 --- a/libp2p/Session.cpp +++ b/libp2p/Session.cpp @@ -180,7 +180,7 @@ bool Session::interpret(RLP const& _r) m_server->m_incomingPeers[id] = make_pair(ep, 0); m_server->m_freePeers.push_back(id); m_server->noteNewPeers(); - clogS(NetTriviaDetail) << "New peer: " << ep << "(" << id << ")"; + clogS(NetTriviaDetail) << "New peer: " << ep << "(" << id .abridged()<< ")"; CONTINUE:; } break; From 17f55abd3715df86f27fd4fc26714f0e03640ae8 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 20 Sep 2014 12:54:20 -0500 Subject: [PATCH 204/223] Get blocks in right order. --- libdevcore/RangeMask.h | 5 +++++ libethereum/DownloadMan.cpp | 21 +++++++++++++-------- libethereum/DownloadMan.h | 12 ++++++++++-- libethereum/EthereumHost.cpp | 7 +++++-- libethereum/EthereumHost.h | 2 +- libethereum/EthereumPeer.cpp | 18 ++++++++++-------- libp2p/Host.cpp | 8 +++++++- 7 files changed, 51 insertions(+), 22 deletions(-) diff --git a/libdevcore/RangeMask.h b/libdevcore/RangeMask.h index d26e034f4..5d271cf3c 100644 --- a/libdevcore/RangeMask.h +++ b/libdevcore/RangeMask.h @@ -146,6 +146,11 @@ public: return m_ranges.empty(); } + bool full() const + { + return m_ranges.size() == 1 && m_ranges.begin()->first == m_all.first && m_ranges.begin()->second == m_all.second; + } + void clear() { m_ranges.clear(); diff --git a/libethereum/DownloadMan.cpp b/libethereum/DownloadMan.cpp index 115813a31..09a3bcc00 100644 --- a/libethereum/DownloadMan.cpp +++ b/libethereum/DownloadMan.cpp @@ -39,26 +39,31 @@ DownloadSub::~DownloadSub() } } -h256s DownloadSub::nextFetch(unsigned _n) +h256Set DownloadSub::nextFetch(unsigned _n) { Guard l(m_fetch); + if (m_remaining.size()) + return m_remaining; + m_asked.clear(); + m_indices.clear(); + m_remaining.clear(); + if (!m_man) - return h256s(); + return h256Set(); m_asked = (~(m_man->taken() + m_attempted)).lowest(_n); if (m_asked.empty()) m_asked = (~(m_man->taken(true) + m_attempted)).lowest(_n); m_attempted += m_asked; - m_indices.clear(); - h256s ret; for (auto i: m_asked) { - ret.push_back(m_man->m_chain[i]); - m_indices[ret.back()] = i; + auto x = m_man->m_chain[i]; + m_remaining.insert(x); + m_indices[x] = i; } - return ret; + return m_remaining; } void DownloadSub::noteBlock(h256 _hash) @@ -66,5 +71,5 @@ void DownloadSub::noteBlock(h256 _hash) Guard l(m_fetch); if (m_man && m_indices.count(_hash)) m_man->m_blocksGot += m_indices[_hash]; + m_remaining.erase(_hash); } - diff --git a/libethereum/DownloadMan.h b/libethereum/DownloadMan.h index 6375f69e4..3ca04f0a3 100644 --- a/libethereum/DownloadMan.h +++ b/libethereum/DownloadMan.h @@ -46,17 +46,19 @@ public: ~DownloadSub(); /// Finished last fetch - grab the next bunch of block hashes to download. - h256s nextFetch(unsigned _n); + h256Set nextFetch(unsigned _n); /// Note that we've received a particular block. void noteBlock(h256 _hash); - void doneFetch() { nextFetch(0); } + /// Nothing doing here. + void doneFetch() { resetFetch(); } private: void resetFetch() // Called by DownloadMan when we need to reset the download. { Guard l(m_fetch); + m_remaining.clear(); m_indices.clear(); m_asked.clear(); m_attempted.clear(); @@ -65,6 +67,7 @@ private: DownloadMan* m_man = nullptr; Mutex m_fetch; + h256Set m_remaining; std::map m_indices; RangeMask m_asked; RangeMask m_attempted; @@ -105,6 +108,11 @@ public: return ret; } + bool isComplete() const + { + return m_blocksGot.full(); + } + private: h256s m_chain; RangeMask m_blocksGot; diff --git a/libethereum/EthereumHost.cpp b/libethereum/EthereumHost.cpp index bbf673419..6a621eab6 100644 --- a/libethereum/EthereumHost.cpp +++ b/libethereum/EthereumHost.cpp @@ -144,9 +144,12 @@ void EthereumHost::noteHaveChain(EthereumPeer* _from) clog(NetNote) << "Difficulty of hashchain HIGHER. Replacing fetch queue [latest now" << _from->m_latestHash.abridged() << ", was" << m_latestBlockSent.abridged() << "]"; // Looks like it's the best yet for total difficulty. Set to download. + m_man.resetToChain(_from->m_neededBlocks); { Guard l(x_blocksNeeded); - m_blocksNeeded = _from->m_neededBlocks; + m_blocksNeeded.clear(); + for (auto i = _from->m_neededBlocks.rbegin(); i != _from->m_neededBlocks.rend(); ++i) + m_blocksNeeded.push_back(*i); m_blocksOnWay.clear(); m_totalDifficultyOfNeeded = td; m_latestBlockSent = _from->m_latestHash; @@ -173,7 +176,7 @@ void EthereumHost::readyForSync() void EthereumHost::noteDoneBlocks() { - if (m_blocksOnWay.empty()) + if (m_man.isComplete()) { // Done our chain-get. if (m_blocksNeeded.size()) diff --git a/libethereum/EthereumHost.h b/libethereum/EthereumHost.h index 2ff8e35d4..08fbd5d14 100644 --- a/libethereum/EthereumHost.h +++ b/libethereum/EthereumHost.h @@ -111,7 +111,7 @@ private: u256 m_networkId; - Grabbing m_grabbing = Grabbing::Nothing; + Grabbing m_grabbing = Grabbing::Nothing; // TODO: needs to be thread-safe & switch to just having a peer id. mutable std::recursive_mutex m_incomingLock; std::vector m_incomingTransactions; diff --git a/libethereum/EthereumPeer.cpp b/libethereum/EthereumPeer.cpp index 557c67242..672c5aa13 100644 --- a/libethereum/EthereumPeer.cpp +++ b/libethereum/EthereumPeer.cpp @@ -66,7 +66,7 @@ void EthereumPeer::sendStatus() void EthereumPeer::startInitialSync() { - // Grab trsansactions off them. + // Grab transactions off them. { RLPStream s; prep(s).appendList(1); @@ -102,6 +102,7 @@ void EthereumPeer::tryGrabbingHashChain() { clogS(NetAllDetail) << "Yes. Their chain is better."; + host()->updateGrabbing(Grabbing::Hashes); m_grabbing = Grabbing::Hashes; RLPStream s; prep(s).appendList(3); @@ -255,6 +256,7 @@ bool EthereumPeer::interpret(RLP const& _r) if (_r.itemCount() == 1 && !m_askedBlocksChanged) { // Couldn't get any from last batch - probably got to this peer's latest block - just give up. + m_sub.doneFetch(); giveUpOnFetch(); } m_askedBlocksChanged = false; @@ -263,6 +265,7 @@ bool EthereumPeer::interpret(RLP const& _r) for (unsigned i = 1; i < _r.itemCount(); ++i) { auto h = BlockInfo::headerHash(_r[i].data()); + m_sub.noteBlock(h); if (host()->noteBlock(h, _r[i].data())) used++; m_askedBlocks.erase(h); @@ -282,12 +285,12 @@ bool EthereumPeer::interpret(RLP const& _r) if (!host()->m_chain.details(bi.parentHash) && !m_knownBlocks.count(bi.parentHash)) { unknownParents++; - clogS(NetAllDetail) << "Unknown parent" << bi.parentHash << "of block" << h; + clogS(NetAllDetail) << "Unknown parent" << bi.parentHash.abridged() << "of block" << h.abridged(); } else { knownParents++; - clogS(NetAllDetail) << "Known parent" << bi.parentHash << "of block" << h; + clogS(NetAllDetail) << "Known parent" << bi.parentHash.abridged() << "of block" << h.abridged(); } } } @@ -323,15 +326,14 @@ void EthereumPeer::ensureGettingChain() void EthereumPeer::continueGettingChain() { - if (!m_askedBlocks.size()) - m_askedBlocks = host()->neededBlocks(m_failedBlocks); + auto blocks = m_sub.nextFetch(c_maxBlocksAsk); - if (m_askedBlocks.size()) + if (blocks.size()) { RLPStream s; prep(s); - s.appendList(m_askedBlocks.size() + 1) << GetBlocksPacket; - for (auto i: m_askedBlocks) + s.appendList(blocks.size() + 1) << GetBlocksPacket; + for (auto const& i: blocks) s << i; sealAndSend(s); } diff --git a/libp2p/Host.cpp b/libp2p/Host.cpp index eb3d99c0b..a0a595bce 100644 --- a/libp2p/Host.cpp +++ b/libp2p/Host.cpp @@ -77,7 +77,9 @@ Host::~Host() void Host::start() { - stop(); + if (isWorking()) + stop(); + for (unsigned i = 0; i < 2; ++i) { bi::tcp::endpoint endpoint(bi::tcp::v4(), i ? 0 : m_netPrefs.listenPort); @@ -104,6 +106,10 @@ void Host::start() determinePublic(m_netPrefs.publicIP, m_netPrefs.upnp); ensureAccepting(); + + m_incomingPeers.clear(); + m_freePeers.clear(); + m_lastPeersRequest = chrono::steady_clock::time_point::min(); clog(NetNote) << "Id:" << m_id.abridged(); From 8415581ab299968884037426038fd7624ce081b8 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 20 Sep 2014 13:09:29 -0500 Subject: [PATCH 205/223] Actually download in right order. --- alethzero/MainWin.cpp | 2 +- libethereum/DownloadMan.h | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index 8d8d4b854..6602cd4cf 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -98,7 +98,7 @@ Main::Main(QWidget *parent) : }; #if ETH_DEBUG - m_servers.append("localhost:30303"); + m_servers.append("localhost:30300"); #endif m_servers.append(QString::fromStdString(Host::pocHost() + ":30303")); diff --git a/libethereum/DownloadMan.h b/libethereum/DownloadMan.h index 3ca04f0a3..e9cd37e3c 100644 --- a/libethereum/DownloadMan.h +++ b/libethereum/DownloadMan.h @@ -91,8 +91,10 @@ public: for (auto i: m_subs) i->resetFetch(); } - - m_chain = _chain; + m_chain.clear(); + m_chain.reserve(_chain.size()); + for (auto i = _chain.rbegin(); i != _chain.rend(); ++i) + m_chain.push_back(*i); m_blocksGot = RangeMask(0, m_chain.size()); } From 5ce45164bd5ee9fbbec3a2f9fe7cbce37cb6e886 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 20 Sep 2014 13:54:55 -0500 Subject: [PATCH 206/223] Get rid of nasty old code. --- alethzero/MainWin.cpp | 2 +- libethereum/CommonNet.h | 1 + libethereum/EthereumHost.cpp | 59 +++++++++--------------------------- libethereum/EthereumHost.h | 6 +--- libethereum/EthereumPeer.cpp | 45 ++++++--------------------- libethereum/EthereumPeer.h | 8 ----- libp2p/Host.cpp | 7 +++-- 7 files changed, 32 insertions(+), 96 deletions(-) diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index 6602cd4cf..9b9ae69e9 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -125,7 +125,7 @@ Main::Main(QWidget *parent) : connect(ui->ourAccounts->model(), SIGNAL(rowsMoved(const QModelIndex &, int, int, const QModelIndex &, int)), SLOT(ourAccountsRowsMoved())); - m_webThree.reset(new WebThreeDirect("AlethZero", getDataDir() + "/AlethZero", false, {"eth", "shh"})); + m_webThree.reset(new WebThreeDirect(string("AlethZero/v") + dev::Version + "/" DEV_QUOTED(ETH_BUILD_TYPE) "/" DEV_QUOTED(ETH_BUILD_PLATFORM), getDataDir() + "/AlethZero", false, {"eth", "shh"})); connect(ui->webView, &QWebView::loadStarted, [this]() { diff --git a/libethereum/CommonNet.h b/libethereum/CommonNet.h index 6774c02ae..4fdfa77a0 100644 --- a/libethereum/CommonNet.h +++ b/libethereum/CommonNet.h @@ -66,6 +66,7 @@ enum class Grabbing State, Hashes, Chain, + ChainHelper, Nothing }; diff --git a/libethereum/EthereumHost.cpp b/libethereum/EthereumHost.cpp index 6a621eab6..a2bac994e 100644 --- a/libethereum/EthereumHost.cpp +++ b/libethereum/EthereumHost.cpp @@ -55,30 +55,6 @@ EthereumHost::~EthereumHost() i->cap()->giveUpOnFetch(); } -h256Set EthereumHost::neededBlocks(h256Set const& _exclude) -{ - Guard l(x_blocksNeeded); - h256Set ret; - if (m_blocksNeeded.size()) - { - int s = m_blocksNeeded.size() - 1; - for (; ret.size() < c_maxBlocksAsk && s < (int)m_blocksNeeded.size() && s >= 0; --s) - if (!_exclude.count(m_blocksNeeded[s])) - { - auto it = m_blocksNeeded.begin() + s; - ret.insert(*it); - m_blocksOnWay.insert(*it); - m_blocksNeeded.erase(it); - } - } - if (ret.empty()) - for (auto i = m_blocksOnWay.begin(); ret.size() < c_maxBlocksAsk && i != m_blocksOnWay.end() && !_exclude.count(*i); ++i) - ret.insert(*i); - if (ret.size()) - clog(NetMessageSummary) << "Asking for" << ret.size() << "blocks that we don't yet have." << m_blocksNeeded.size() << "blocks still needed," << m_blocksOnWay.size() << "total blocks on way."; - return ret; -} - bool EthereumHost::ensureInitialised(TransactionQueue& _tq) { if (!m_latestBlockSent) @@ -102,7 +78,7 @@ void EthereumHost::noteHavePeerState(EthereumPeer* _who) if (m_grabbing != Grabbing::Nothing) { clog(NetAllDetail) << "Already downloading chain. Just set to help out."; - _who->restartGettingChain(); + _who->ensureGettingChain(); return; } @@ -117,7 +93,7 @@ void EthereumHost::updateGrabbing(Grabbing _g) readyForSync(); else if (_g == Grabbing::Chain) for (auto j: peers()) - j->cap()->restartGettingChain(); + j->cap()->ensureGettingChain(); } void EthereumHost::noteHaveChain(EthereumPeer* _from) @@ -145,15 +121,8 @@ void EthereumHost::noteHaveChain(EthereumPeer* _from) // Looks like it's the best yet for total difficulty. Set to download. m_man.resetToChain(_from->m_neededBlocks); - { - Guard l(x_blocksNeeded); - m_blocksNeeded.clear(); - for (auto i = _from->m_neededBlocks.rbegin(); i != _from->m_neededBlocks.rend(); ++i) - m_blocksNeeded.push_back(*i); - m_blocksOnWay.clear(); - m_totalDifficultyOfNeeded = td; - m_latestBlockSent = _from->m_latestHash; - } + m_totalDifficultyOfNeeded = td; + m_latestBlockSent = _from->m_latestHash; _from->m_grabbing = Grabbing::Chain; updateGrabbing(Grabbing::Chain); @@ -174,23 +143,25 @@ void EthereumHost::readyForSync() clog(NetNote) << "No more peers to sync with."; } -void EthereumHost::noteDoneBlocks() +void EthereumHost::noteDoneBlocks(EthereumPeer* _who) { if (m_man.isComplete()) { // Done our chain-get. - if (m_blocksNeeded.size()) - clog(NetNote) << "No more blocks coming. Missing" << m_blocksNeeded.size() << "blocks."; - else - clog(NetNote) << "No more blocks to get."; + clog(NetNote) << "Chain download complete."; + updateGrabbing(Grabbing::Nothing); + } + if (_who->m_grabbing == Grabbing::Chain) + { + // Done our chain-get. + clog(NetNote) << "Chain download failed. Peer with blocks didn't have them all. This peer is bad and should be punished."; + // TODO: note that peer is BADBADBAD! updateGrabbing(Grabbing::Nothing); } } bool EthereumHost::noteBlock(h256 _hash, bytesConstRef _data) { - Guard l(x_blocksNeeded); - m_blocksOnWay.erase(_hash); if (!m_chain.details(_hash)) { lock_guard l(m_incomingLock); @@ -259,12 +230,12 @@ void EthereumHost::reset() { m_grabbing = Grabbing::Nothing; + m_man.resetToChain(h256s()); + m_incomingTransactions.clear(); m_incomingBlocks.clear(); m_totalDifficultyOfNeeded = 0; - m_blocksNeeded.clear(); - m_blocksOnWay.clear(); m_latestBlockSent = h256(); m_transactionsSent.clear(); diff --git a/libethereum/EthereumHost.h b/libethereum/EthereumHost.h index 08fbd5d14..b735532b8 100644 --- a/libethereum/EthereumHost.h +++ b/libethereum/EthereumHost.h @@ -77,7 +77,7 @@ private: /// Session has finished getting the chain of hashes. void noteHaveChain(EthereumPeer* _who); /// Called when the peer can no longer provide us with any needed blocks. - void noteDoneBlocks(); + void noteDoneBlocks(EthereumPeer* _who); /// Sync with the BlockChain. It might contain one of our mined blocks, we might have new candidates from the network. void doWork(); @@ -117,11 +117,7 @@ private: std::vector m_incomingTransactions; std::vector m_incomingBlocks; - mutable std::mutex x_blocksNeeded; u256 m_totalDifficultyOfNeeded; - h256s m_blocksNeeded; - - h256Set m_blocksOnWay; DownloadMan m_man; diff --git a/libethereum/EthereumPeer.cpp b/libethereum/EthereumPeer.cpp index 672c5aa13..6f9ff226e 100644 --- a/libethereum/EthereumPeer.cpp +++ b/libethereum/EthereumPeer.cpp @@ -114,29 +114,17 @@ void EthereumPeer::tryGrabbingHashChain() void EthereumPeer::giveUpOnFetch() { - clogS(NetNote) << "GIVE UP FETCH; can't get" << toString(m_askedBlocks); + clogS(NetNote) << "GIVE UP FETCH"; // a bit overkill given that the other nodes may yet have the needed blocks, but better to be safe than sorry. if (m_grabbing == Grabbing::Chain) { + host()->noteDoneBlocks(this); m_grabbing = Grabbing::Nothing; - host()->updateGrabbing(Grabbing::Nothing); } + m_sub.doneFetch(); // NOTE: need to notify of giving up on chain-hashes, too, altering state as necessary. - - if (m_askedBlocks.size()) - { - Guard l (host()->x_blocksNeeded); - host()->m_blocksNeeded.reserve(host()->m_blocksNeeded.size() + m_askedBlocks.size()); - for (auto i: m_askedBlocks) - { - m_failedBlocks.insert(i); - host()->m_blocksOnWay.erase(i); - host()->m_blocksNeeded.push_back(i); - } - m_askedBlocks.clear(); - } } bool EthereumPeer::interpret(RLP const& _r) @@ -253,13 +241,12 @@ bool EthereumPeer::interpret(RLP const& _r) { clogS(NetMessageSummary) << "Blocks (" << dec << (_r.itemCount() - 1) << "entries)" << (_r.itemCount() - 1 ? "" : ": NoMoreBlocks"); - if (_r.itemCount() == 1 && !m_askedBlocksChanged) + if (_r.itemCount() == 1) { // Couldn't get any from last batch - probably got to this peer's latest block - just give up. m_sub.doneFetch(); giveUpOnFetch(); } - m_askedBlocksChanged = false; unsigned used = 0; for (unsigned i = 1; i < _r.itemCount(); ++i) @@ -268,7 +255,6 @@ bool EthereumPeer::interpret(RLP const& _r) m_sub.noteBlock(h); if (host()->noteBlock(h, _r[i].data())) used++; - m_askedBlocks.erase(h); Guard l(x_knownBlocks); m_knownBlocks.insert(h); } @@ -304,21 +290,9 @@ bool EthereumPeer::interpret(RLP const& _r) return true; } -void EthereumPeer::restartGettingChain() -{ - if (m_askedBlocks.size()) - { - m_askedBlocksChanged = true; // So that we continue even if the Ask's reply is empty. - m_askedBlocks.clear(); // So that we restart once we get the Ask's reply. - m_failedBlocks.clear(); - } - else - ensureGettingChain(); -} - void EthereumPeer::ensureGettingChain() { - if (m_askedBlocks.size()) + if (m_grabbing == Grabbing::ChainHelper) return; // Already asked & waiting for some. continueGettingChain(); @@ -326,6 +300,9 @@ void EthereumPeer::ensureGettingChain() void EthereumPeer::continueGettingChain() { + if (m_grabbing != Grabbing::Chain) + m_grabbing = Grabbing::ChainHelper; + auto blocks = m_sub.nextFetch(c_maxBlocksAsk); if (blocks.size()) @@ -338,9 +315,5 @@ void EthereumPeer::continueGettingChain() sealAndSend(s); } else - { - if (m_failedBlocks.size()) - clogS(NetMessageSummary) << "No blocks left to get. Peer doesn't seem to have" << m_failedBlocks.size() << "of our needed blocks."; - host()->noteDoneBlocks(); - } + giveUpOnFetch(); } diff --git a/libethereum/EthereumPeer.h b/libethereum/EthereumPeer.h index 960f1f482..f88a757d8 100644 --- a/libethereum/EthereumPeer.h +++ b/libethereum/EthereumPeer.h @@ -67,8 +67,6 @@ private: void ensureGettingChain(); /// Ensure that we are waiting for a bunch of blocks from our peer. void continueGettingChain(); - /// Now getting a different chain so we need to make sure we restart. - void restartGettingChain(); void giveUpOnFetch(); @@ -82,12 +80,6 @@ private: h256 m_latestHash; ///< Peer's latest block's hash. u256 m_totalDifficulty; ///< Peer's latest block's total difficulty. h256s m_neededBlocks; ///< The blocks that we should download from this peer. - h256Set m_failedBlocks; ///< Blocks that the peer doesn't seem to have. - - h256Set m_askedBlocks; ///< The blocks for which we sent the last GetBlocks for but haven't received a corresponding Blocks. - bool m_askedBlocksChanged = true; - - RangeMask m_blocksAsked; bool m_requireTransactions; diff --git a/libp2p/Host.cpp b/libp2p/Host.cpp index a0a595bce..cf76397bf 100644 --- a/libp2p/Host.cpp +++ b/libp2p/Host.cpp @@ -456,8 +456,11 @@ void Host::growPeers() auto x = time(0) % m_freePeers.size(); m_incomingPeers[m_freePeers[x]].second++; - connect(m_incomingPeers[m_freePeers[x]].first); - m_freePeers.erase(m_freePeers.begin() + x); + if (!m_peers.count(m_freePeers[x])) + { + connect(m_incomingPeers[m_freePeers[x]].first); + m_freePeers.erase(m_freePeers.begin() + x); + } } } From a5196ed685e5ddadc6f8c0cda887c1e21885814e Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 20 Sep 2014 13:56:58 -0500 Subject: [PATCH 207/223] Version bump. --- libdevcore/Common.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libdevcore/Common.cpp b/libdevcore/Common.cpp index ad779e35d..908948236 100644 --- a/libdevcore/Common.cpp +++ b/libdevcore/Common.cpp @@ -27,7 +27,7 @@ using namespace dev; namespace dev { -char const* Version = "0.6.8c"; +char const* Version = "0.6.8d"; } From 70d9acac7015a3826e36c75b8e0d314fbd864077 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 20 Sep 2014 14:14:29 -0500 Subject: [PATCH 208/223] Fix neth. I'm such a soft touch. --- CMakeLists.txt | 2 +- neth/CMakeLists.txt | 1 + neth/main.cpp | 29 +++++++++++++++++++---------- 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6c6ec18cc..e623c7319 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -353,7 +353,7 @@ if (NOT LANGUAGES) add_subdirectory(exp) endif () if(NOT ("${TARGET_PLATFORM}" STREQUAL "w64")) - #add_subdirectory(neth) // resurect once moved over to WebThree API. + add_subdirectory(neth) endif () if(QTQML) add_definitions(-DETH_QTQML) diff --git a/neth/CMakeLists.txt b/neth/CMakeLists.txt index 3e8dab70d..2b3f92947 100644 --- a/neth/CMakeLists.txt +++ b/neth/CMakeLists.txt @@ -11,6 +11,7 @@ set(EXECUTABLE neth) add_executable(${EXECUTABLE} ${SRC_LIST}) +target_link_libraries(${EXECUTABLE} webthree) target_link_libraries(${EXECUTABLE} ethereum) target_link_libraries(${EXECUTABLE} secp256k1) target_link_libraries(${EXECUTABLE} gmp) diff --git a/neth/main.cpp b/neth/main.cpp index e492d1a3c..d9818c72f 100644 --- a/neth/main.cpp +++ b/neth/main.cpp @@ -39,6 +39,7 @@ #include #include #endif +#include #include "BuildInfo.h" #undef KEY_EVENT // from windows.h @@ -413,7 +414,9 @@ int main(int argc, char** argv) if (!clientName.empty()) clientName += "/"; - Client c("NEthereum(++)/" + clientName + "v" + dev::Version + "/" DEV_QUOTED(ETH_BUILD_TYPE) "/" DEV_QUOTED(ETH_BUILD_PLATFORM), coinbase, dbPath); + WebThreeDirect web3("NEthereum(++)/" + clientName + "v" + dev::Version + "/" DEV_QUOTED(ETH_BUILD_TYPE) "/" DEV_QUOTED(ETH_BUILD_PLATFORM), dbPath); + // mode doesn't work anymore: see eth for how that should be structured. + Client& c = *web3.ethereum(); c.setForceMining(true); @@ -479,7 +482,12 @@ int main(int argc, char** argv) wmove(mainwin, 1, 4); if (!remoteHost.empty()) - c.startNetwork(listenPort, remoteHost, remotePort, mode, peers, publicIP, upnp); + { + web3.setIdealPeerCount(peers); + web3.setNetworkPreferences(NetworkPreferences(listenPort, publicIP, upnp)); + web3.startNetwork(); + web3.connect(remoteHost, remotePort); + } if (mining) c.startMining(); @@ -487,7 +495,7 @@ int main(int argc, char** argv) auto_ptr jsonrpcServer; if (jsonrpc > -1) { - jsonrpcServer = auto_ptr(new EthStubServer(new jsonrpc::HttpServer(jsonrpc), c)); + jsonrpcServer = auto_ptr(new EthStubServer(new jsonrpc::HttpServer(jsonrpc), web3)); jsonrpcServer->setKeys({us}); jsonrpcServer->StartListening(); } @@ -528,18 +536,19 @@ int main(int argc, char** argv) { unsigned port; iss >> port; - c.startNetwork((short)port); + web3.setNetworkPreferences(NetworkPreferences((short)port, publicIP, upnp)); + web3.startNetwork(); } else if (cmd == "connect") { string addr; unsigned port; iss >> addr >> port; - c.connect(addr, (short)port); + web3.connect(addr, (short)port); } else if (cmd == "netstop") { - c.stopNetwork(); + web3.stopNetwork(); } else if (cmd == "minestart") { @@ -560,7 +569,7 @@ int main(int argc, char** argv) { if (jsonrpc < 0) jsonrpc = 8080; - jsonrpcServer = auto_ptr(new EthStubServer(new jsonrpc::HttpServer(jsonrpc), c)); + jsonrpcServer = auto_ptr(new EthStubServer(new jsonrpc::HttpServer(jsonrpc), web3)); jsonrpcServer->setKeys({us}); jsonrpcServer->StartListening(); } @@ -589,7 +598,7 @@ int main(int argc, char** argv) } else if (cmd == "peers") { - for (auto it: c.peers()) + for (auto it: web3.peers()) cout << it.host << ":" << it.port << ", " << it.clientVersion << ", " << std::chrono::duration_cast(it.lastPing).count() << "ms" << endl; @@ -925,7 +934,7 @@ int main(int argc, char** argv) // Peers y = 1; - for (PeerInfo const& i: c.peers()) + for (PeerInfo const& i: web3.peers()) { auto s = boost::format("%1% ms - %2%:%3% - %4%") % toString(chrono::duration_cast(i.lastPing).count()) % @@ -971,7 +980,7 @@ int main(int argc, char** argv) // Peers mvwprintw(peerswin, 0, x, "Peers: "); - mvwprintw(peerswin, 0, 9, toString(c.peers().size()).c_str()); + mvwprintw(peerswin, 0, 9, toString(web3.peers().size()).c_str()); // Mining flag if (c.isMining()) From fb8e2e0dbe1c3a4a1c8ee510a1beb49ba98bfd9d Mon Sep 17 00:00:00 2001 From: Christoph Jentzsch Date: Sat, 20 Sep 2014 22:18:42 +0200 Subject: [PATCH 209/223] style fix --- test/vm.cpp | 54 +++++++++++++++++++++++------------------------------ 1 file changed, 23 insertions(+), 31 deletions(-) diff --git a/test/vm.cpp b/test/vm.cpp index 5caba5cd2..8100ee822 100644 --- a/test/vm.cpp +++ b/test/vm.cpp @@ -169,15 +169,6 @@ public: a.push_back(toString(_v)); } - static string u256toHex(u256 const& _v) - { - std::ostringstream oss; - oss << std::hex << std::setfill('0') << std::setw(2) << _v; - string ret = "0x" + oss.str(); - std::transform(ret.begin(), ret.end(), ret.begin(), ::tolower); - return ret; - } - mObject exportEnv() { mObject ret; @@ -228,13 +219,13 @@ public: if (li) store[curKey] = curVal; li = s.first; - curKey = u256toHex(li); + curKey = "0x"+toHex(toCompactBigEndian(li)); curVal = mArray(); } else for (; li != s.first; ++li) curVal.push_back(0); - curVal.push_back(u256toHex(s.second)); + curVal.push_back("0x"+toHex(toCompactBigEndian(s.second))); ++li; } if (li) @@ -483,18 +474,19 @@ void doTests(json_spirit::mValue& v, bool _fillin) BOOST_AUTO_TEST_CASE(vm_tests) { + /* // Populate tests first: -// try -// { -// cnote << "Populating VM tests..."; -// json_spirit::mValue v; -// string s = asString(contents("../../../cpp-ethereum/test/vmtests.json")); -// BOOST_REQUIRE_MESSAGE(s.length() > 0, "Contents of 'vmtests.json' is empty."); -// json_spirit::read_string(s, v); -// dev::test::doTests(v, true); -// writeFile("../../../tests/vmtests.json", asBytes(json_spirit::write_string(v, true))); -// } -/* catch (std::exception const& e) + try + { + cnote << "Populating VM tests..."; + json_spirit::mValue v; + string s = asString(contents("../../../cpp-ethereum/test/vmtests.json")); + BOOST_REQUIRE_MESSAGE(s.length() > 0, "Contents of 'vmtests.json' is empty."); + json_spirit::read_string(s, v); + dev::test::doTests(v, true); + writeFile("../../../tests/vmtests.json", asBytes(json_spirit::write_string(v, true))); + } + catch (std::exception const& e) { BOOST_ERROR("Failed VM Test with Exception: " << e.what()); }*/ @@ -516,15 +508,15 @@ BOOST_AUTO_TEST_CASE(vm_tests) BOOST_AUTO_TEST_CASE(vmArithmeticTest) { - -// cnote << "Populating VM tests..."; -// json_spirit::mValue v; -// string s = asString(contents("../../../cpp-ethereum/test/vmArithmeticTestFiller.json")); -// BOOST_REQUIRE_MESSAGE(s.length() > 0, "Contents of 'vmtests.json' is empty."); -// json_spirit::read_string(s, v); -// dev::test::doTests(v, true); -// writeFile("../../../tests/vmArithmeticTest.json", asBytes(json_spirit::write_string(v, true))); - + /* + cnote << "Populating VM tests..."; + json_spirit::mValue v; + string s = asString(contents("../../../cpp-ethereum/test/vmArithmeticTestFiller.json")); + BOOST_REQUIRE_MESSAGE(s.length() > 0, "Contents of 'vmtests.json' is empty."); + json_spirit::read_string(s, v); + dev::test::doTests(v, true); + writeFile("../../../tests/vmArithmeticTest.json", asBytes(json_spirit::write_string(v, true))); + */ try { From 05d3d8096ed362555792195b2edd7cff599f8fa6 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 20 Sep 2014 17:55:34 -0500 Subject: [PATCH 210/223] Simple chain download view. --- alethzero/DownloadView.cpp | 28 +++++++++++++++++++++++++++- alethzero/DownloadView.h | 4 ++++ alethzero/MainWin.cpp | 9 +++++++++ libdevcore/RangeMask.h | 8 ++++++-- libethereum/Client.cpp | 3 +++ libethereum/Client.h | 3 +++ libethereum/DownloadMan.h | 7 +++++++ libethereum/EthereumHost.cpp | 7 ++----- libethereum/EthereumHost.h | 5 +++-- libethereum/EthereumPeer.cpp | 5 +++-- libp2p/Host.cpp | 27 ++++++++++++++++----------- 11 files changed, 83 insertions(+), 23 deletions(-) diff --git a/alethzero/DownloadView.cpp b/alethzero/DownloadView.cpp index 0fe5e1414..52dc99004 100644 --- a/alethzero/DownloadView.cpp +++ b/alethzero/DownloadView.cpp @@ -23,7 +23,7 @@ #include #include -#include +#include #include "Grapher.h" using namespace std; @@ -37,4 +37,30 @@ DownloadView::DownloadView(QWidget* _p): QWidget(_p) void DownloadView::paintEvent(QPaintEvent*) { QPainter p(this); + + if (!m_man || m_man->chain().empty()) + { + p.fillRect(rect(), Qt::white); + return; + } + + p.fillRect(rect(), Qt::black); + + double n = sqrt(double(rect().width()) * rect().height() / m_man->chain().size()); + QSizeF area(n, n); + QPointF pos(0, 0); + + auto const& bg = m_man->blocksGot(); + + for (unsigned i = bg.all().first, ei = bg.all().second; i < ei; ++i) + { + QColor c = Qt::black; + if (bg.contains(i)) + c = Qt::white; + p.fillRect(QRectF(pos, area), QBrush(c)); + + pos.setX(pos.x() + n); + if (pos.x() >= rect().width() - n) + pos = QPoint(0, pos.y() + n); + } } diff --git a/alethzero/DownloadView.h b/alethzero/DownloadView.h index ea3e05b8f..22a11651c 100644 --- a/alethzero/DownloadView.h +++ b/alethzero/DownloadView.h @@ -33,6 +33,7 @@ namespace dev { namespace eth { struct MineInfo; +class DownloadMan; }} class DownloadView: public QWidget @@ -42,8 +43,11 @@ class DownloadView: public QWidget public: DownloadView(QWidget* _p = nullptr); + void setDownloadMan(dev::eth::DownloadMan const* _man) { m_man = _man; } + protected: virtual void paintEvent(QPaintEvent*); private: + dev::eth::DownloadMan const* m_man = nullptr; }; diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index 9b9ae69e9..087b3eae8 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -39,6 +39,8 @@ #include #include #include +#include +#include "DownloadView.h" #include "MiningView.h" #include "BuildInfo.h" #include "MainWin.h" @@ -881,6 +883,9 @@ void Main::timerEvent(QTimerEvent*) if (interval / 100 % 2 == 0) refreshMining(); + if (interval / 100 % 2 == 0 && m_webThree->ethereum()->isSyncing()) + ui->downloadView->update(); + if (m_logChanged) { m_logLock.lock(); @@ -1478,11 +1483,15 @@ void Main::on_net_triggered() web3()->setNetworkPreferences(netPrefs()); ethereum()->setNetworkId(m_privateChain.size() ? sha3(m_privateChain.toStdString()) : 0); web3()->startNetwork(); + ui->downloadView->setDownloadMan(ethereum()->downloadMan()); if (m_peers.size() && ui->usePast->isChecked()) web3()->restorePeers(bytesConstRef((byte*)m_peers.data(), m_peers.size())); } else + { + ui->downloadView->setDownloadMan(nullptr); web3()->stopNetwork(); + } } void Main::on_connect_triggered() diff --git a/libdevcore/RangeMask.h b/libdevcore/RangeMask.h index 5d271cf3c..f1b0043ff 100644 --- a/libdevcore/RangeMask.h +++ b/libdevcore/RangeMask.h @@ -137,8 +137,10 @@ public: bool contains(T _i) const { - auto it = m_ranges.lower_bound(_i); - return it != m_ranges.end() && it->first <= _i && it->second > _i; + auto it = m_ranges.upper_bound(_i); + if (it == m_ranges.begin()) + return false; + return (--it)->second > _i; } bool empty() const @@ -156,6 +158,8 @@ public: m_ranges.clear(); } + std::pair const& all() const { return m_all; } + class const_iterator { friend class RangeMask; diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index 7025cb03c..de0077f89 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -83,6 +83,9 @@ void Client::setNetworkId(u256 _n) h->setNetworkId(_n); } +DownloadMan const* Client::downloadMan() const { if (auto h = m_host.lock()) return &(h->downloadMan()); return nullptr; } +bool Client::isSyncing() const { if (auto h = m_host.lock()) return h->isSyncing(); return false; } + void Client::doneWorking() { // Synchronise the state according to the head of the block chain. diff --git a/libethereum/Client.h b/libethereum/Client.h index 6415defb0..d00bf53ba 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -48,6 +48,7 @@ namespace eth { class Client; +class DownloadMan; enum ClientWorkState { @@ -231,6 +232,8 @@ public: // Debug stuff: + DownloadMan const* downloadMan() const; + bool isSyncing() const; /// Sets the network id. void setNetworkId(u256 _n); /// Clears pending transactions. Just for debug use. diff --git a/libethereum/DownloadMan.h b/libethereum/DownloadMan.h index e9cd37e3c..3a2506bb5 100644 --- a/libethereum/DownloadMan.h +++ b/libethereum/DownloadMan.h @@ -54,6 +54,9 @@ public: /// Nothing doing here. void doneFetch() { resetFetch(); } + RangeMask const& asked() const { return m_asked; } + RangeMask const& attemped() const { return m_attempted; } + private: void resetFetch() // Called by DownloadMan when we need to reset the download. { @@ -115,6 +118,10 @@ public: return m_blocksGot.full(); } + h256s chain() const { return m_chain; } + void foreachSub(std::function const& _f) const { ReadGuard l(x_subs); for(auto i: m_subs) _f(*i); } + RangeMask blocksGot() const { return m_blocksGot; } + private: h256s m_chain; RangeMask m_blocksGot; diff --git a/libethereum/EthereumHost.cpp b/libethereum/EthereumHost.cpp index a2bac994e..1b3049fba 100644 --- a/libethereum/EthereumHost.cpp +++ b/libethereum/EthereumHost.cpp @@ -107,9 +107,9 @@ void EthereumHost::noteHaveChain(EthereumPeer* _from) return; } - clog(NetNote) << "Hash-chain COMPLETE:" << _from->m_totalDifficulty << "vs" << m_chain.details().totalDifficulty << "," << m_totalDifficultyOfNeeded << ";" << _from->m_neededBlocks.size() << " blocks, ends" << _from->m_neededBlocks.back().abridged(); + clog(NetNote) << "Hash-chain COMPLETE:" << _from->m_totalDifficulty << "vs" << m_chain.details().totalDifficulty << ";" << _from->m_neededBlocks.size() << " blocks, ends" << _from->m_neededBlocks.back().abridged(); - if ((m_totalDifficultyOfNeeded && (td < m_totalDifficultyOfNeeded || (td == m_totalDifficultyOfNeeded && m_latestBlockSent == _from->m_latestHash))) || td < m_chain.details().totalDifficulty || (td == m_chain.details().totalDifficulty && m_chain.currentHash() == _from->m_latestHash)) + if (td < m_chain.details().totalDifficulty || (td == m_chain.details().totalDifficulty && m_chain.currentHash() == _from->m_latestHash)) { clog(NetNote) << "Difficulty of hashchain not HIGHER. Ignoring."; _from->m_grabbing = Grabbing::Nothing; @@ -121,7 +121,6 @@ void EthereumHost::noteHaveChain(EthereumPeer* _from) // Looks like it's the best yet for total difficulty. Set to download. m_man.resetToChain(_from->m_neededBlocks); - m_totalDifficultyOfNeeded = td; m_latestBlockSent = _from->m_latestHash; _from->m_grabbing = Grabbing::Chain; @@ -235,8 +234,6 @@ void EthereumHost::reset() m_incomingTransactions.clear(); m_incomingBlocks.clear(); - m_totalDifficultyOfNeeded = 0; - m_latestBlockSent = h256(); m_transactionsSent.clear(); } diff --git a/libethereum/EthereumHost.h b/libethereum/EthereumHost.h index b735532b8..07ef92513 100644 --- a/libethereum/EthereumHost.h +++ b/libethereum/EthereumHost.h @@ -69,6 +69,9 @@ public: void reset(); + DownloadMan const& downloadMan() const { return m_man; } + bool isSyncing() const { return m_grabbing == Grabbing::Chain; } + private: void noteHavePeerState(EthereumPeer* _who); /// Session wants to pass us a block that we might not have. @@ -117,8 +120,6 @@ private: std::vector m_incomingTransactions; std::vector m_incomingBlocks; - u256 m_totalDifficultyOfNeeded; - DownloadMan m_man; h256 m_latestBlockSent; diff --git a/libethereum/EthereumPeer.cpp b/libethereum/EthereumPeer.cpp index 6f9ff226e..ceff02bb5 100644 --- a/libethereum/EthereumPeer.cpp +++ b/libethereum/EthereumPeer.cpp @@ -88,9 +88,9 @@ void EthereumPeer::tryGrabbingHashChain() h256 c = host()->m_chain.currentHash(); unsigned n = host()->m_chain.number(); - u256 td = max(host()->m_chain.details().totalDifficulty, host()->m_totalDifficultyOfNeeded); + u256 td = host()->m_chain.details().totalDifficulty; - clogS(NetAllDetail) << "Attempt chain-grab? Latest:" << c.abridged() << ", number:" << n << ", TD: max(" << host()->m_chain.details().totalDifficulty << "," << host()->m_totalDifficultyOfNeeded << ") versus " << m_totalDifficulty; + clogS(NetAllDetail) << "Attempt chain-grab? Latest:" << c.abridged() << ", number:" << n << ", TD:" << td << " versus " << m_totalDifficulty; if (td >= m_totalDifficulty) { clogS(NetAllDetail) << "No. Our chain is better."; @@ -233,6 +233,7 @@ bool EthereumPeer::interpret(RLP const& _r) ++n; } } + sleep(1); RLPStream s; sealAndSend(prep(s).appendList(n + 1).append(BlocksPacket).appendRaw(rlp, n)); break; diff --git a/libp2p/Host.cpp b/libp2p/Host.cpp index cf76397bf..7c523e550 100644 --- a/libp2p/Host.cpp +++ b/libp2p/Host.cpp @@ -371,17 +371,22 @@ string Host::pocHost() void Host::connect(std::string const& _addr, unsigned short _port) noexcept { - try - { - bi::tcp::resolver r(m_ioService); - connect(r.resolve({_addr, toString(_port)})->endpoint()); -// connect(bi::tcp::endpoint(bi::address::from_string(_addr), _port)); - } - catch (exception const& e) - { - // Couldn't connect - clog(NetConnect) << "Bad host " << _addr << " (" << e.what() << ")"; - } + for (int i = 0; i < 2; ++i) + try + { + if (i == 0) + { + bi::tcp::resolver r(m_ioService); + connect(r.resolve({_addr, toString(_port)})->endpoint()); + } + else + connect(bi::tcp::endpoint(bi::address::from_string(_addr), _port)); + } + catch (exception const& e) + { + // Couldn't connect + clog(NetConnect) << "Bad host " << _addr << " (" << e.what() << ")"; + } } void Host::connect(bi::tcp::endpoint const& _ep) From 1220d199f7cabff5b7c64ce3189a7c198aff449c Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 20 Sep 2014 18:08:23 -0500 Subject: [PATCH 211/223] Don't take up quite so much CPU. --- libdevcore/Worker.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libdevcore/Worker.cpp b/libdevcore/Worker.cpp index fe0a4fe92..88ed57602 100644 --- a/libdevcore/Worker.cpp +++ b/libdevcore/Worker.cpp @@ -40,7 +40,7 @@ void Worker::startWorking() setThreadName(m_name.c_str()); while (!m_stop) { - this_thread::sleep_for(chrono::milliseconds(1)); + this_thread::sleep_for(chrono::milliseconds(100)); doWork(); } cdebug << "Finishing up worker thread"; From d60ee48ddd3e8b6725801fd8facda68c8ea342d8 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 20 Sep 2014 18:56:15 -0500 Subject: [PATCH 212/223] Various networking fixes. --- alethzero/DownloadView.cpp | 18 ++++++++++++++++-- libdevcore/Worker.cpp | 2 +- libethereum/DownloadMan.h | 1 + libethereum/EthereumPeer.cpp | 2 +- libp2p/Host.cpp | 5 +---- 5 files changed, 20 insertions(+), 8 deletions(-) diff --git a/alethzero/DownloadView.cpp b/alethzero/DownloadView.cpp index 52dc99004..8242a2793 100644 --- a/alethzero/DownloadView.cpp +++ b/alethzero/DownloadView.cpp @@ -38,7 +38,7 @@ void DownloadView::paintEvent(QPaintEvent*) { QPainter p(this); - if (!m_man || m_man->chain().empty()) + if (!m_man || m_man->chain().empty() || !m_man->subCount()) { p.fillRect(rect(), Qt::white); return; @@ -46,7 +46,13 @@ void DownloadView::paintEvent(QPaintEvent*) p.fillRect(rect(), Qt::black); - double n = sqrt(double(rect().width()) * rect().height() / m_man->chain().size()); + + double ratio = (double)rect().width() / rect().height(); + if (ratio < 1) + ratio = 1 / ratio; + double n = min(rect().width(), rect().height()) / ceil(sqrt(m_man->chain().size() / ratio)); + +// double n = sqrt(double(rect().width()) * rect().height() / (m_man->chain().size())); QSizeF area(n, n); QPointF pos(0, 0); @@ -57,6 +63,14 @@ void DownloadView::paintEvent(QPaintEvent*) QColor c = Qt::black; if (bg.contains(i)) c = Qt::white; + unsigned h = 0; + unsigned dh = 360 / m_man->subCount(); + m_man->foreachSub([&](DownloadSub const& s) + { + if (s.asked().contains(i)) + c = QColor::fromHsv(h, 64, 128); + h += dh; + }); p.fillRect(QRectF(pos, area), QBrush(c)); pos.setX(pos.x() + n); diff --git a/libdevcore/Worker.cpp b/libdevcore/Worker.cpp index 88ed57602..29ff766d7 100644 --- a/libdevcore/Worker.cpp +++ b/libdevcore/Worker.cpp @@ -40,7 +40,7 @@ void Worker::startWorking() setThreadName(m_name.c_str()); while (!m_stop) { - this_thread::sleep_for(chrono::milliseconds(100)); + this_thread::sleep_for(chrono::milliseconds(30)); doWork(); } cdebug << "Finishing up worker thread"; diff --git a/libethereum/DownloadMan.h b/libethereum/DownloadMan.h index 3a2506bb5..e67076d5a 100644 --- a/libethereum/DownloadMan.h +++ b/libethereum/DownloadMan.h @@ -120,6 +120,7 @@ public: h256s chain() const { return m_chain; } void foreachSub(std::function const& _f) const { ReadGuard l(x_subs); for(auto i: m_subs) _f(*i); } + unsigned subCount() const { ReadGuard l(x_subs); return m_subs.size(); } RangeMask blocksGot() const { return m_blocksGot; } private: diff --git a/libethereum/EthereumPeer.cpp b/libethereum/EthereumPeer.cpp index ceff02bb5..2ee0905d0 100644 --- a/libethereum/EthereumPeer.cpp +++ b/libethereum/EthereumPeer.cpp @@ -117,7 +117,7 @@ void EthereumPeer::giveUpOnFetch() clogS(NetNote) << "GIVE UP FETCH"; // a bit overkill given that the other nodes may yet have the needed blocks, but better to be safe than sorry. - if (m_grabbing == Grabbing::Chain) + if (m_grabbing == Grabbing::Chain || m_grabbing == Grabbing::ChainHelper) { host()->noteDoneBlocks(this); m_grabbing = Grabbing::Nothing; diff --git a/libp2p/Host.cpp b/libp2p/Host.cpp index 7c523e550..9d033e6f8 100644 --- a/libp2p/Host.cpp +++ b/libp2p/Host.cpp @@ -452,7 +452,6 @@ void Host::growPeers() m_lastPeersRequest = chrono::steady_clock::now(); } - if (!m_accepting) ensureAccepting(); @@ -462,10 +461,8 @@ void Host::growPeers() auto x = time(0) % m_freePeers.size(); m_incomingPeers[m_freePeers[x]].second++; if (!m_peers.count(m_freePeers[x])) - { connect(m_incomingPeers[m_freePeers[x]].first); - m_freePeers.erase(m_freePeers.begin() + x); - } + m_freePeers.erase(m_freePeers.begin() + x); } } From 0b4abba562a12020faf195d88c94b5c08eafa9bf Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 20 Sep 2014 19:13:15 -0500 Subject: [PATCH 213/223] Nice downloading view. --- alethzero/DownloadView.cpp | 37 ++++++++++++++++++++----------------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/alethzero/DownloadView.cpp b/alethzero/DownloadView.cpp index 8242a2793..657727a6a 100644 --- a/alethzero/DownloadView.cpp +++ b/alethzero/DownloadView.cpp @@ -38,21 +38,16 @@ void DownloadView::paintEvent(QPaintEvent*) { QPainter p(this); + p.fillRect(rect(), Qt::white); if (!m_man || m_man->chain().empty() || !m_man->subCount()) - { - p.fillRect(rect(), Qt::white); return; - } - - p.fillRect(rect(), Qt::black); - double ratio = (double)rect().width() / rect().height(); if (ratio < 1) ratio = 1 / ratio; double n = min(rect().width(), rect().height()) / ceil(sqrt(m_man->chain().size() / ratio)); -// double n = sqrt(double(rect().width()) * rect().height() / (m_man->chain().size())); +// QSizeF area(rect().width() / floor(rect().width() / n), rect().height() / floor(rect().height() / n)); QSizeF area(n, n); QPointF pos(0, 0); @@ -60,18 +55,26 @@ void DownloadView::paintEvent(QPaintEvent*) for (unsigned i = bg.all().first, ei = bg.all().second; i < ei; ++i) { - QColor c = Qt::black; + int s = -2; if (bg.contains(i)) - c = Qt::white; - unsigned h = 0; - unsigned dh = 360 / m_man->subCount(); - m_man->foreachSub([&](DownloadSub const& s) + s = -1; + else { - if (s.asked().contains(i)) - c = QColor::fromHsv(h, 64, 128); - h += dh; - }); - p.fillRect(QRectF(pos, area), QBrush(c)); + unsigned h = 0; + m_man->foreachSub([&](DownloadSub const& sub) + { + if (sub.asked().contains(i)) + s = h; + h++; + }); + } + unsigned dh = 360 / m_man->subCount(); + if (s == -2) + p.fillRect(QRectF(QPointF(pos) + QPointF(3 * area.width() / 8, 3 * area.height() / 8), area / 4), Qt::black); + else if (s == -1) + p.fillRect(QRectF(QPointF(pos) + QPointF(1 * area.width() / 8, 1 * area.height() / 8), area * 3 / 4), Qt::black); + else + p.fillRect(QRectF(QPointF(pos) + QPointF(1 * area.width() / 8, 1 * area.height() / 8), area * 3 / 4), QColor::fromHsv(s * dh, 64, 128)); pos.setX(pos.x() + n); if (pos.x() >= rect().width() - n) From f9610136be385b162059ca124b9c58d37bcdb2f9 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 20 Sep 2014 23:47:53 -0500 Subject: [PATCH 214/223] Remove sleep. --- libethereum/EthereumPeer.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/libethereum/EthereumPeer.cpp b/libethereum/EthereumPeer.cpp index 2ee0905d0..7608afc2a 100644 --- a/libethereum/EthereumPeer.cpp +++ b/libethereum/EthereumPeer.cpp @@ -233,7 +233,6 @@ bool EthereumPeer::interpret(RLP const& _r) ++n; } } - sleep(1); RLPStream s; sealAndSend(prep(s).appendList(n + 1).append(BlocksPacket).appendRaw(rlp, n)); break; From 68d1a5ce089d3b7f8666a471038b106a202953dc Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 20 Sep 2014 23:59:54 -0500 Subject: [PATCH 215/223] Make debug downloading a bit faster. --- libethereum/CommonNet.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libethereum/CommonNet.h b/libethereum/CommonNet.h index 4fdfa77a0..c53b644e4 100644 --- a/libethereum/CommonNet.h +++ b/libethereum/CommonNet.h @@ -34,10 +34,10 @@ namespace eth { #if ETH_DEBUG -static const unsigned c_maxHashes = 4; ///< Maximum number of hashes BlockHashes will ever send. -static const unsigned c_maxHashesAsk = 4; ///< Maximum number of hashes GetBlockHashes will ever ask for. -static const unsigned c_maxBlocks = 2; ///< Maximum number of blocks Blocks will ever send. -static const unsigned c_maxBlocksAsk = 2; ///< Maximum number of blocks we ask to receive in Blocks (when using GetChain). +static const unsigned c_maxHashes = 64; ///< Maximum number of hashes BlockHashes will ever send. +static const unsigned c_maxHashesAsk = 64; ///< Maximum number of hashes GetBlockHashes will ever ask for. +static const unsigned c_maxBlocks = 32; ///< Maximum number of blocks Blocks will ever send. +static const unsigned c_maxBlocksAsk = 32; ///< Maximum number of blocks we ask to receive in Blocks (when using GetChain). #else static const unsigned c_maxHashes = 256; ///< Maximum number of hashes BlockHashes will ever send. static const unsigned c_maxHashesAsk = 256; ///< Maximum number of hashes GetBlockHashes will ever ask for. From 7507f95203d201270fabf55a5568d26f8183f03c Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 21 Sep 2014 08:23:13 -0500 Subject: [PATCH 216/223] VS fix. --- alethzero/MainWin.cpp | 5 ++++- libp2p/Common.h | 1 + libp2p/Session.cpp | 2 +- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index 087b3eae8..b4bbb8ca7 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -691,7 +691,7 @@ void Main::refreshNetwork() ui->peerCount->setText(QString::fromStdString(toString(ps.size())) + " peer(s)"); ui->peers->clear(); for (PeerInfo const& i: ps) - ui->peers->addItem(QString("%3 ms - %1:%2 - %4").arg(i.host.c_str()).arg(i.port).arg(chrono::duration_cast(i.lastPing).count()).arg(i.clientVersion.c_str())); + ui->peers->addItem(QString("%3 ms - %1:%2 - %4 %5").arg(i.host.c_str()).arg(i.port).arg(chrono::duration_cast(i.lastPing).count()).arg(i.clientVersion.c_str()).arg(QString::fromStdString(toString(i.caps)))); } void Main::refreshAll() @@ -1906,4 +1906,7 @@ void Main::updateDebugger() #include\ "moc_MiningView.cpp" +#include\ +"moc_DownloadView.cpp" + #endif diff --git a/libp2p/Common.h b/libp2p/Common.h index 8035f462f..a83ac03de 100644 --- a/libp2p/Common.h +++ b/libp2p/Common.h @@ -94,6 +94,7 @@ struct PeerInfo std::string host; unsigned short port; std::chrono::steady_clock::duration lastPing; + std::set caps; }; } diff --git a/libp2p/Session.cpp b/libp2p/Session.cpp index 252a1e8e6..2dc60e0de 100644 --- a/libp2p/Session.cpp +++ b/libp2p/Session.cpp @@ -102,7 +102,7 @@ bool Session::interpret(RLP const& _r) return false; } try - { m_info = PeerInfo({clientVersion, m_socket.remote_endpoint().address().to_string(), m_listenPort, std::chrono::steady_clock::duration()}); } + { m_info = PeerInfo({clientVersion, m_socket.remote_endpoint().address().to_string(), m_listenPort, std::chrono::steady_clock::duration(), _r[3].toSet()}); } catch (...) { disconnect(BadProtocol); From 2b6f1576bc8d5c5d019c0ff8e862582e02a6096a Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 21 Sep 2014 08:24:48 -0500 Subject: [PATCH 217/223] About to begin base58. --- libdevcore/CommonData.cpp | 178 ++++++++++++++++++++++++++++++++++++++ libdevcore/CommonData.h | 5 ++ 2 files changed, 183 insertions(+) diff --git a/libdevcore/CommonData.cpp b/libdevcore/CommonData.cpp index 96269bbc1..6e889a6b0 100644 --- a/libdevcore/CommonData.cpp +++ b/libdevcore/CommonData.cpp @@ -102,3 +102,181 @@ bytes dev::asNibbles(std::string const& _s) } return ret; } + +#if 0 + +/* Following code is copyright 2012-2014 Luke Dashjr + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the standard MIT license. See COPYING for more details. + */ + +#include +#include +#include +#include + +static const int8_t b58digits_map[] = { + -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8,-1,-1,-1,-1,-1,-1, + -1, 9,10,11,12,13,14,15, 16,-1,17,18,19,20,21,-1, + 22,23,24,25,26,27,28,29, 30,31,32,-1,-1,-1,-1,-1, + -1,33,34,35,36,37,38,39, 40,41,42,43,-1,44,45,46, + 47,48,49,50,51,52,53,54, 55,56,57,-1,-1,-1,-1,-1, +}; + +bool b58tobin(void *bin, size_t *binszp, const char *b58, size_t b58sz) +{ + size_t binsz = *binszp; + const unsigned char *b58u = (void*)b58; + unsigned char *binu = bin; + size_t outisz = (binsz + 3) / 4; + uint32_t outi[outisz]; + uint64_t t; + uint32_t c; + size_t i, j; + uint8_t bytesleft = binsz % 4; + uint32_t zeromask = bytesleft ? (0xffffffff << (bytesleft * 8)) : 0; + unsigned zerocount = 0; + + if (!b58sz) + b58sz = strlen(b58); + + memset(outi, 0, outisz * sizeof(*outi)); + + // Leading zeros, just count + for (i = 0; i < b58sz && !b58digits_map[b58u[i]]; ++i) + ++zerocount; + + for ( ; i < b58sz; ++i) + { + if (b58u[i] & 0x80) + // High-bit set on invalid digit + return false; + if (b58digits_map[b58u[i]] == -1) + // Invalid base58 digit + return false; + c = (unsigned)b58digits_map[b58u[i]]; + for (j = outisz; j--; ) + { + t = ((uint64_t)outi[j]) * 58 + c; + c = (t & 0x3f00000000) >> 32; + outi[j] = t & 0xffffffff; + } + if (c) + // Output number too big (carry to the next int32) + return false; + if (outi[0] & zeromask) + // Output number too big (last int32 filled too far) + return false; + } + + j = 0; + switch (bytesleft) { + case 3: + *(binu++) = (outi[0] & 0xff0000) >> 16; + case 2: + *(binu++) = (outi[0] & 0xff00) >> 8; + case 1: + *(binu++) = (outi[0] & 0xff); + ++j; + default: + break; + } + + for (; j < outisz; ++j) + { + *(binu++) = (outi[j] >> 0x18) & 0xff; + *(binu++) = (outi[j] >> 0x10) & 0xff; + *(binu++) = (outi[j] >> 8) & 0xff; + *(binu++) = (outi[j] >> 0) & 0xff; + } + + // Count canonical base58 byte count + binu = bin; + for (i = 0; i < binsz; ++i) + { + if (binu[i]) + break; + --*binszp; + } + *binszp += zerocount; + + return true; +} + +static +bool my_dblsha256(void *hash, const void *data, size_t datasz) +{ + uint8_t buf[0x20]; + return b58_sha256_impl(buf, data, datasz) && b58_sha256_impl(hash, buf, sizeof(buf)); +} + +int b58check(const void *bin, size_t binsz, const char *base58str, size_t b58sz) +{ + unsigned char buf[32]; + const uint8_t *binc = bin; + unsigned i; + if (binsz < 4) + return -4; + if (!my_dblsha256(buf, bin, binsz - 4)) + return -2; + if (memcmp(&binc[binsz - 4], buf, 4)) + return -1; + + // Check number of zeros is correct AFTER verifying checksum (to avoid possibility of accessing base58str beyond the end) + for (i = 0; binc[i] == '\0' && base58str[i] == '1'; ++i) + {} // Just finding the end of zeros, nothing to do in loop + if (binc[i] == '\0' || base58str[i] == '1') + return -3; + + return binc[0]; +} + +static const char b58digits_ordered[] = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; + +bool b58enc(char *b58, size_t *b58sz, const void *data, size_t binsz) +{ + const uint8_t *bin = data; + int carry; + size_t i, j, high, zcount = 0; + size_t size; + + while (zcount < binsz && !bin[zcount]) + ++zcount; + + size = (binsz - zcount) * 138 / 100 + 1; + uint8_t buf[size]; + memset(buf, 0, size); + + for (i = zcount, high = size - 1; i < binsz; ++i, high = j) + { + for (carry = bin[i], j = size - 1; (j > high) || carry; --j) + { + carry += 256 * buf[j]; + buf[j] = carry % 58; + carry /= 58; + } + } + + for (j = 0; j < size && !buf[j]; ++j); + + if (*b58sz <= zcount + size - j) + { + *b58sz = zcount + size - j + 1; + return false; + } + + if (zcount) + memset(b58, '1', zcount); + for (i = zcount; j < size; ++i, ++j) + b58[i] = b58digits_ordered[buf[j]]; + b58[i] = '\0'; + *b58sz = i + 1; + + return true; +} + +#endif diff --git a/libdevcore/CommonData.h b/libdevcore/CommonData.h index 1181e07e8..11850fa69 100644 --- a/libdevcore/CommonData.h +++ b/libdevcore/CommonData.h @@ -55,6 +55,11 @@ int fromHex(char _i); /// @example fromHex("41626261") == asBytes("Abba") bytes fromHex(std::string const& _s); +#if 0 +std::string toBase58(bytesConstRef _data); +bytes fromBase58(std::string const& _s); +#endif + /// Converts byte array to a string containing the same (binary) data. Unless /// the byte array happens to contain ASCII data, this won't be printable. inline std::string asString(bytes const& _b) From 0a8b796135b12b7b3a9e72ef381f569d3346f20f Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 21 Sep 2014 08:27:20 -0500 Subject: [PATCH 218/223] Remove mode stuff from neth for now. --- neth/main.cpp | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/neth/main.cpp b/neth/main.cpp index d9818c72f..0dc552e39 100644 --- a/neth/main.cpp +++ b/neth/main.cpp @@ -305,7 +305,6 @@ int main(int argc, char** argv) unsigned short remotePort = 30303; string dbPath; bool mining = false; - NodeMode mode = NodeMode::Full; unsigned peers = 5; #if ETH_JSONRPC int jsonrpc = 8080; @@ -390,19 +389,6 @@ int main(int argc, char** argv) g_logVerbosity = atoi(argv[++i]); else if ((arg == "-x" || arg == "--peers") && i + 1 < argc) peers = atoi(argv[++i]); - else if ((arg == "-o" || arg == "--mode") && i + 1 < argc) - { - string m = argv[++i]; - if (m == "full") - mode = NodeMode::Full; - else if (m == "peer") - mode = NodeMode::PeerServer; - else - { - cerr << "Unknown mode: " << m << endl; - return -1; - } - } else if (arg == "-h" || arg == "--help") help(); else if (arg == "-V" || arg == "--version") @@ -415,7 +401,6 @@ int main(int argc, char** argv) clientName += "/"; WebThreeDirect web3("NEthereum(++)/" + clientName + "v" + dev::Version + "/" DEV_QUOTED(ETH_BUILD_TYPE) "/" DEV_QUOTED(ETH_BUILD_PLATFORM), dbPath); - // mode doesn't work anymore: see eth for how that should be structured. Client& c = *web3.ethereum(); c.setForceMining(true); From b87d9f28f189a5c20d9ddf3b30fdb9e668bbcf33 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 22 Sep 2014 09:22:12 -0500 Subject: [PATCH 219/223] Fix for clang's pedanticism. --- libdevcore/CommonIO.h | 20 +++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/libdevcore/CommonIO.h b/libdevcore/CommonIO.h index 068c5c777..98c5a96c7 100644 --- a/libdevcore/CommonIO.h +++ b/libdevcore/CommonIO.h @@ -49,15 +49,6 @@ void writeFile(std::string const& _file, bytes const& _data); /// Nicely renders the given bytes to a string, optionally as HTML. std::string memDump(bytes const& _b, unsigned _w = 8, bool _html = false); -/// Converts arbitrary value to string representation using std::stringstream. -template -std::string toString(_T const& _t) -{ - std::ostringstream o; - o << _t; - return o.str(); -} - // Stream I/O functions. // Provides templated stream I/O for all STL collections so they can be shifted on to any iostream-like interface. @@ -223,4 +214,15 @@ template inline std::ostream& operator<<(std::ostream& _out, template _S& operator<<(_S& _out, std::shared_ptr<_T> const& _p) { if (_p) _out << "@" << (*_p); else _out << "nullptr"; return _out; } +// Functions that use streaming stuff. + +/// Converts arbitrary value to string representation using std::stringstream. +template +std::string toString(_T const& _t) +{ + std::ostringstream o; + o << _t; + return o.str(); +} + } From 4483e1a8161564a62c5f6944fb28d615a5f9d8c4 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 22 Sep 2014 09:30:41 -0500 Subject: [PATCH 220/223] Kill Walleth from Windows. --- windows/Ethereum.sln | 2 -- 1 file changed, 2 deletions(-) diff --git a/windows/Ethereum.sln b/windows/Ethereum.sln index 6403d7fad..26fe794c2 100644 --- a/windows/Ethereum.sln +++ b/windows/Ethereum.sln @@ -23,8 +23,6 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LibMiniUPnPc", "LibMiniUPnP EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "AlethZero", "Alethzero.vcxproj", "{BFCFFC46-78A3-4350-9938-0EA3A2B1CCCD}" EndProject -Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Walleth", "Walleth.vcxproj", "{326EF470-463F-4751-A22A-48BBAAD8B143}" -EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "LibQEthereum", "LibQEthereum.vcxproj", "{DF3C5B07-A1A2-4F16-B37F-A27333622CDD}" EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "eth", "Eth.vcxproj", "{C60C065C-2135-4B2B-AFD4-35FD7AC56B40}" From 599970e9e0a2793bb245135f6882aa448e4487bc Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 22 Sep 2014 10:09:14 -0500 Subject: [PATCH 221/223] Moc downloadview.h. --- windows/Alethzero.vcxproj | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/windows/Alethzero.vcxproj b/windows/Alethzero.vcxproj index 721c0729e..d418e5d03 100644 --- a/windows/Alethzero.vcxproj +++ b/windows/Alethzero.vcxproj @@ -209,6 +209,24 @@ + + $(Lua) moc.lua "$(QtBin)/moc" "$(IntDir)moc_%(FileName).cpp" "@(ClCompile->'%(AdditionalIncludeDirectories)');$(IncludePath)" "@(ClCompile->'%(PreprocessorDefinitions)');_MSC_VER=1800" "%(FullPath)" + + + $(Lua) moc.lua "$(QtBin)/moc" "$(IntDir)moc_%(FileName).cpp" "@(ClCompile->'%(AdditionalIncludeDirectories)');$(IncludePath)" "@(ClCompile->'%(PreprocessorDefinitions)');_MSC_VER=1800" "%(FullPath)" + + + $(Lua) moc.lua "$(QtBin)/moc" "$(IntDir)moc_%(FileName).cpp" "@(ClCompile->'%(AdditionalIncludeDirectories)');$(IncludePath)" "@(ClCompile->'%(PreprocessorDefinitions)');_MSC_VER=1800" "%(FullPath)" + + + $(Lua) moc.lua "$(QtBin)/moc" "$(IntDir)moc_%(FileName).cpp" "@(ClCompile->'%(AdditionalIncludeDirectories)');$(IncludePath)" "@(ClCompile->'%(PreprocessorDefinitions)');_MSC_VER=1800" "%(FullPath)" + + + $(IntDir)moc_%(FileName).cpp + $(IntDir)moc_%(FileName).cpp + $(IntDir)moc_%(FileName).cpp + $(IntDir)moc_%(FileName).cpp + $(Lua) moc.lua "$(QtBin)/moc" "$(IntDir)moc_%(FileName).cpp" "@(ClCompile->'%(AdditionalIncludeDirectories)');$(IncludePath)" "@(ClCompile->'%(PreprocessorDefinitions)');_MSC_VER=1800" "%(FullPath)" @@ -289,4 +307,4 @@ - \ No newline at end of file + From 817a4ff2fca4dffdd4db7b9a3b701ba67b321ea0 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 22 Sep 2014 10:28:08 -0500 Subject: [PATCH 222/223] Version bump. --- libdevcore/Common.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libdevcore/Common.cpp b/libdevcore/Common.cpp index 908948236..8312960c1 100644 --- a/libdevcore/Common.cpp +++ b/libdevcore/Common.cpp @@ -27,7 +27,7 @@ using namespace dev; namespace dev { -char const* Version = "0.6.8d"; +char const* Version = "0.6.9"; } From 6c63d5dd849b2acb4ab477f8f6f3431a40b44f58 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 22 Sep 2014 11:52:58 -0500 Subject: [PATCH 223/223] Update package script. --- package.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.sh b/package.sh index 681c20096..f6d45b07c 100755 --- a/package.sh +++ b/package.sh @@ -3,7 +3,7 @@ opwd="$PWD" br=$(git branch | grep '\*' | sed 's/^..//') -n=cpp-ethereum-src-$(date "+%Y%m%d%H%M%S" --date="1970-01-01 $(git log -1 --date=short --pretty=format:%ct) sec GMT")-$(grep "EthVersion = " libethential/Common.cpp | sed 's/^[^"]*"//' | sed 's/".*$//')-$(git rev-parse HEAD | cut -c1-6) +n=cpp-ethereum-src-$(date "+%Y%m%d%H%M%S" --date="1970-01-01 $(git log -1 --date=short --pretty=format:%ct) sec GMT")-$(grep "Version = " libdevcore/Common.cpp | sed 's/^[^"]*"//' | sed 's/".*$//')-$(git rev-parse HEAD | cut -c1-6) cd /tmp git clone "$opwd" $n

    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%0A2^YOyL5l!G~;n|BSZwepkiG`)?}xG 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 Date: Wed, 27 Aug 2014 12:48:30 +0200 Subject: [PATCH 122/223] Split off general network layer. --- CMakeLists.txt | 3 +- alethzero/MainWin.cpp | 2 +- iethxi/MainWin.cpp | 2 +- {libethereum => libethential}/Guards.cpp | 0 {libethereum => libethential}/Guards.h | 4 + libethereum/All.h | 6 +- libethereum/BlockChain.h | 2 +- libethereum/BlockQueue.h | 2 +- libethereum/Client.cpp | 6 +- libethereum/Client.h | 6 +- .../{PeerNetwork.cpp => CommonNet.cpp} | 2 +- libethereum/{PeerNetwork.h => CommonNet.h} | 4 +- .../{PeerServer.cpp => EthereumHost.cpp} | 80 +-- libethereum/{PeerServer.h => EthereumHost.h} | 132 +---- .../{PeerSession.cpp => EthereumSession.cpp} | 50 +- .../{PeerSession.h => EthereumSession.h} | 16 +- libethereum/TransactionQueue.h | 2 +- libethereumx/Ethereum.h | 4 +- libethnet/CMakeLists.txt | 66 +++ libethnet/Common.cpp | 61 +++ libethnet/Common.h | 91 ++++ libethnet/PeerHost.cpp | 480 ++++++++++++++++++ libethnet/PeerHost.h | 145 ++++++ libethnet/PeerSession.cpp | 418 +++++++++++++++ libethnet/PeerSession.h | 105 ++++ libethnet/UPnP.cpp | 183 +++++++ libethnet/UPnP.h | 53 ++ libqethereum/QEthereum.cpp | 2 +- libqethereum/QmlEthereum.cpp | 2 +- test/fork.cpp | 2 +- test/network.cpp | 2 +- test/peer.cpp | 4 +- test/txTest.cpp | 2 +- third/MainWin.cpp | 2 +- walleth/MainWin.cpp | 2 +- 35 files changed, 1723 insertions(+), 220 deletions(-) rename {libethereum => libethential}/Guards.cpp (100%) rename {libethereum => libethential}/Guards.h (91%) rename libethereum/{PeerNetwork.cpp => CommonNet.cpp} (98%) rename libethereum/{PeerNetwork.h => CommonNet.h} (98%) rename libethereum/{PeerServer.cpp => EthereumHost.cpp} (86%) rename libethereum/{PeerServer.h => EthereumHost.h} (52%) rename libethereum/{PeerSession.cpp => EthereumSession.cpp} (92%) rename libethereum/{PeerSession.h => EthereumSession.h} (88%) create mode 100644 libethnet/CMakeLists.txt create mode 100644 libethnet/Common.cpp create mode 100644 libethnet/Common.h create mode 100644 libethnet/PeerHost.cpp create mode 100644 libethnet/PeerHost.h create mode 100644 libethnet/PeerSession.cpp create mode 100644 libethnet/PeerSession.h create mode 100644 libethnet/UPnP.cpp create mode 100644 libethnet/UPnP.h diff --git a/CMakeLists.txt b/CMakeLists.txt index d8d3dc391..36ab9dec8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -337,6 +337,7 @@ add_subdirectory(lllc) add_subdirectory(sc) if (NOT LANGUAGES) add_subdirectory(secp256k1) + add_subdirectory(libethnet) add_subdirectory(libethcore) add_subdirectory(libevm) add_subdirectory(libwhisper) @@ -363,7 +364,7 @@ if (NOT LANGUAGES) add_subdirectory(alethzero) add_subdirectory(third) if(QTQML) - add_subdirectory(iethxi) + #add_subdirectory(iethxi) add_subdirectory(walleth) endif() endif() diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index 4c18364ba..64beb7e83 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -37,7 +37,7 @@ #include #include #include -#include +#include #include "MiningView.h" #include "BuildInfo.h" #include "MainWin.h" diff --git a/iethxi/MainWin.cpp b/iethxi/MainWin.cpp index 27814ebcd..276ff7630 100644 --- a/iethxi/MainWin.cpp +++ b/iethxi/MainWin.cpp @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include "BuildInfo.h" #include "MainWin.h" #include "ui_Main.h" diff --git a/libethereum/Guards.cpp b/libethential/Guards.cpp similarity index 100% rename from libethereum/Guards.cpp rename to libethential/Guards.cpp diff --git a/libethereum/Guards.h b/libethential/Guards.h similarity index 91% rename from libethereum/Guards.h rename to libethential/Guards.h index 64a95eb78..b509b45ed 100644 --- a/libethereum/Guards.h +++ b/libethential/Guards.h @@ -27,6 +27,10 @@ namespace eth { +using Mutex = std::mutex; +using RecursiveMutex = std::recursive_mutex; +using SharedMutex = boost::shared_mutex; + using Guard = std::lock_guard; using RecursiveGuard = std::lock_guard; using ReadGuard = boost::shared_lock; diff --git a/libethereum/All.h b/libethereum/All.h index 96f23aa74..17151f1b0 100644 --- a/libethereum/All.h +++ b/libethereum/All.h @@ -6,9 +6,9 @@ #include "Defaults.h" #include "Executive.h" #include "ExtVM.h" -#include "PeerNetwork.h" -#include "PeerServer.h" -#include "PeerSession.h" +#include "CommonNet.h" +#include "EthereumHost.h" +#include "EthereumSession.h" #include "State.h" #include "Transaction.h" #include "TransactionQueue.h" diff --git a/libethereum/BlockChain.h b/libethereum/BlockChain.h index 8fcf09691..cbae012ba 100644 --- a/libethereum/BlockChain.h +++ b/libethereum/BlockChain.h @@ -25,7 +25,7 @@ #include #include #include -#include "Guards.h" +#include #include "BlockDetails.h" #include "AddressState.h" #include "BlockQueue.h" diff --git a/libethereum/BlockQueue.h b/libethereum/BlockQueue.h index d66bfabd9..30686d192 100644 --- a/libethereum/BlockQueue.h +++ b/libethereum/BlockQueue.h @@ -24,7 +24,7 @@ #include #include #include "libethcore/CommonEth.h" -#include "Guards.h" +#include namespace eth { diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index 6249c370b..c62fe42de 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -26,7 +26,7 @@ #include #include #include "Defaults.h" -#include "PeerServer.h" +#include "EthereumHost.h" using namespace std; using namespace eth; @@ -213,13 +213,13 @@ void Client::startNetwork(unsigned short _listenPort, std::string const& _seedHo try { - m_net.reset(new PeerServer(m_clientVersion, m_bc, _networkId, _listenPort, _mode, _publicIP, _upnp)); + m_net.reset(new EthereumHost(m_clientVersion, m_bc, _networkId, _listenPort, _mode, _publicIP, _upnp)); } catch (std::exception const&) { // Probably already have the port open. cwarn << "Could not initialize with specified/default port. Trying system-assigned port"; - m_net.reset(new PeerServer(m_clientVersion, m_bc, 0, _mode, _publicIP, _upnp)); + m_net.reset(new EthereumHost(m_clientVersion, m_bc, 0, _mode, _publicIP, _upnp)); } } m_net->setIdealPeerCount(_peers); diff --git a/libethereum/Client.h b/libethereum/Client.h index b62a9066f..9499b0d4c 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -30,11 +30,11 @@ #include #include #include -#include "Guards.h" +#include #include "BlockChain.h" #include "TransactionQueue.h" #include "State.h" -#include "PeerNetwork.h" +#include "CommonNet.h" #include "PastMessage.h" #include "MessageFilter.h" #include "Miner.h" @@ -298,7 +298,7 @@ private: std::unique_ptr m_workNet; ///< The network thread. std::atomic m_workNetState; mutable boost::shared_mutex x_net; ///< Lock for the network existance. - std::unique_ptr m_net; ///< Should run in background and send us events when blocks found and allow us to send blocks as required. + std::unique_ptr m_net; ///< Should run in background and send us events when blocks found and allow us to send blocks as required. std::unique_ptr m_work; ///< The work thread. std::atomic m_workState; diff --git a/libethereum/PeerNetwork.cpp b/libethereum/CommonNet.cpp similarity index 98% rename from libethereum/PeerNetwork.cpp rename to libethereum/CommonNet.cpp index 6585769e6..84443eb11 100644 --- a/libethereum/PeerNetwork.cpp +++ b/libethereum/CommonNet.cpp @@ -19,7 +19,7 @@ * @date 2014 */ -#include "PeerNetwork.h" +#include "CommonNet.h" using namespace std; using namespace eth; diff --git a/libethereum/PeerNetwork.h b/libethereum/CommonNet.h similarity index 98% rename from libethereum/PeerNetwork.h rename to libethereum/CommonNet.h index f73f44e54..b76893d5c 100644 --- a/libethereum/PeerNetwork.h +++ b/libethereum/CommonNet.h @@ -45,8 +45,8 @@ static const eth::uint c_maxBlocksAsk = 16; ///< Maximum number of blocks we ask class OverlayDB; class BlockChain; class TransactionQueue; -class PeerServer; -class PeerSession; +class EthereumHost; +class EthereumSession; struct NetWarn: public LogChannel { static const char* name() { return "!N!"; } static const int verbosity = 0; }; struct NetNote: public LogChannel { static const char* name() { return "*N*"; } static const int verbosity = 1; }; diff --git a/libethereum/PeerServer.cpp b/libethereum/EthereumHost.cpp similarity index 86% rename from libethereum/PeerServer.cpp rename to libethereum/EthereumHost.cpp index 71a80a90b..9ed782800 100644 --- a/libethereum/PeerServer.cpp +++ b/libethereum/EthereumHost.cpp @@ -14,14 +14,14 @@ You should have received a copy of the GNU General Public License along with cpp-ethereum. If not, see . */ -/** @file PeerNetwork.cpp +/** @file EthereumHost.cpp * @authors: * Gav Wood * Eric Lombrozo * @date 2014 */ -#include "PeerServer.h" +#include "EthereumHost.h" #include #ifdef _WIN32 @@ -40,7 +40,7 @@ #include "BlockChain.h" #include "TransactionQueue.h" #include "BlockQueue.h" -#include "PeerSession.h" +#include "EthereumSession.h" using namespace std; using namespace eth; @@ -55,7 +55,7 @@ static const set c_rejectAddresses = { {bi::address_v6::from_string("::")} }; -PeerServer::PeerServer(std::string const& _clientVersion, BlockChain const& _ch, u256 _networkId, unsigned short _port, NodeMode _m, string const& _publicAddress, bool _upnp): +EthereumHost::EthereumHost(std::string const& _clientVersion, BlockChain const& _ch, u256 _networkId, unsigned short _port, NodeMode _m, string const& _publicAddress, bool _upnp): m_clientVersion(_clientVersion), m_mode(_m), m_listenPort(_port), @@ -71,7 +71,7 @@ PeerServer::PeerServer(std::string const& _clientVersion, BlockChain const& _ch, clog(NetNote) << "Id:" << toHex(m_key.address().ref().cropped(0, 4)) << "Mode: " << (_m == NodeMode::PeerServer ? "PeerServer" : "Full"); } -PeerServer::PeerServer(std::string const& _clientVersion, BlockChain const& _ch, u256 _networkId, NodeMode _m, string const& _publicAddress, bool _upnp): +EthereumHost::EthereumHost(std::string const& _clientVersion, BlockChain const& _ch, u256 _networkId, NodeMode _m, string const& _publicAddress, bool _upnp): m_clientVersion(_clientVersion), m_mode(_m), m_listenPort(0), @@ -90,7 +90,7 @@ PeerServer::PeerServer(std::string const& _clientVersion, BlockChain const& _ch, clog(NetNote) << "Id:" << toHex(m_key.address().ref().cropped(0, 4)) << "Mode: " << (m_mode == NodeMode::PeerServer ? "PeerServer" : "Full"); } -PeerServer::PeerServer(std::string const& _clientVersion, BlockChain const& _ch, u256 _networkId, NodeMode _m): +EthereumHost::EthereumHost(std::string const& _clientVersion, BlockChain const& _ch, u256 _networkId, NodeMode _m): m_clientVersion(_clientVersion), m_mode(_m), m_listenPort(0), @@ -105,22 +105,22 @@ PeerServer::PeerServer(std::string const& _clientVersion, BlockChain const& _ch, clog(NetNote) << "Id:" << toHex(m_key.address().ref().cropped(0, 4)) << "Mode: " << (m_mode == NodeMode::PeerServer ? "PeerServer" : "Full"); } -PeerServer::~PeerServer() +EthereumHost::~EthereumHost() { disconnectPeers(); for (auto i: m_peers) - if (shared_ptr p = i.second.lock()) + if (shared_ptr p = i.second.lock()) p->giveUpOnFetch(); } -void PeerServer::registerPeer(std::shared_ptr _s) +void EthereumHost::registerPeer(std::shared_ptr _s) { Guard l(x_peers); m_peers[_s->m_id] = _s; } -void PeerServer::disconnectPeers() +void EthereumHost::disconnectPeers() { for (unsigned n = 0;; n = 0) { @@ -142,12 +142,12 @@ void PeerServer::disconnectPeers() delete m_upnp; } -unsigned PeerServer::protocolVersion() +unsigned EthereumHost::protocolVersion() { return c_protocolVersion; } -void PeerServer::seal(bytes& _b) +void EthereumHost::seal(bytes& _b) { _b[0] = 0x22; _b[1] = 0x40; @@ -160,7 +160,7 @@ void PeerServer::seal(bytes& _b) _b[7] = len & 0xff; } -void PeerServer::determinePublic(string const& _publicAddress, bool _upnp) +void EthereumHost::determinePublic(string const& _publicAddress, bool _upnp) { if (_upnp) try @@ -202,7 +202,7 @@ void PeerServer::determinePublic(string const& _publicAddress, bool _upnp) } } -void PeerServer::populateAddresses() +void EthereumHost::populateAddresses() { #ifdef _WIN32 WSAData wsaData; @@ -277,7 +277,7 @@ void PeerServer::populateAddresses() #endif } -std::map PeerServer::potentialPeers() +std::map EthereumHost::potentialPeers() { std::map ret; if (!m_public.address().is_unspecified()) @@ -295,7 +295,7 @@ std::map PeerServer::potentialPeers() return ret; } -void PeerServer::ensureAccepting() +void EthereumHost::ensureAccepting() { if (m_accepting == false) { @@ -311,7 +311,7 @@ void PeerServer::ensureAccepting() } catch (...){} bi::address remoteAddress = m_socket.remote_endpoint().address(); // Port defaults to 0 - we let the hello tell us which port the peer listens to - auto p = std::make_shared(this, std::move(m_socket), m_networkId, remoteAddress); + auto p = std::make_shared(this, std::move(m_socket), m_networkId, remoteAddress); p->start(); } catch (std::exception const& _e) @@ -325,7 +325,7 @@ void PeerServer::ensureAccepting() } } -void PeerServer::connect(std::string const& _addr, unsigned short _port) noexcept +void EthereumHost::connect(std::string const& _addr, unsigned short _port) noexcept { try { @@ -338,7 +338,7 @@ void PeerServer::connect(std::string const& _addr, unsigned short _port) noexcep } } -void PeerServer::connect(bi::tcp::endpoint const& _ep) +void EthereumHost::connect(bi::tcp::endpoint const& _ep) { clog(NetConnect) << "Attempting connection to " << _ep; bi::tcp::socket* s = new bi::tcp::socket(m_ioService); @@ -359,7 +359,7 @@ void PeerServer::connect(bi::tcp::endpoint const& _ep) } else { - auto p = make_shared(this, std::move(*s), m_networkId, _ep.address(), _ep.port()); + auto p = make_shared(this, std::move(*s), m_networkId, _ep.address(), _ep.port()); clog(NetConnect) << "Connected to " << _ep; p->start(); } @@ -367,7 +367,7 @@ void PeerServer::connect(bi::tcp::endpoint const& _ep) }); } -h256Set PeerServer::neededBlocks() +h256Set EthereumHost::neededBlocks() { Guard l(x_blocksNeeded); h256Set ret; @@ -386,7 +386,7 @@ h256Set PeerServer::neededBlocks() return ret; } -bool PeerServer::havePeer(Public _id) const +bool EthereumHost::havePeer(Public _id) const { Guard l(x_peers); @@ -400,7 +400,7 @@ bool PeerServer::havePeer(Public _id) const return !!m_peers.count(_id); } -bool PeerServer::ensureInitialised(TransactionQueue& _tq) +bool EthereumHost::ensureInitialised(TransactionQueue& _tq) { if (m_latestBlockSent == h256()) { @@ -416,7 +416,7 @@ bool PeerServer::ensureInitialised(TransactionQueue& _tq) return false; } -bool PeerServer::noteBlock(h256 _hash, bytesConstRef _data) +bool EthereumHost::noteBlock(h256 _hash, bytesConstRef _data) { Guard l(x_blocksNeeded); m_blocksOnWay.erase(_hash); @@ -429,7 +429,7 @@ bool PeerServer::noteBlock(h256 _hash, bytesConstRef _data) return false; } -bool PeerServer::sync(TransactionQueue& _tq, BlockQueue& _bq) +bool EthereumHost::sync(TransactionQueue& _tq, BlockQueue& _bq) { bool netChange = ensureInitialised(_tq); @@ -453,7 +453,7 @@ bool PeerServer::sync(TransactionQueue& _tq, BlockQueue& _bq) return netChange; } -void PeerServer::maintainTransactions(TransactionQueue& _tq, h256 _currentHash) +void EthereumHost::maintainTransactions(TransactionQueue& _tq, h256 _currentHash) { bool resendAll = (_currentHash != m_latestBlockSent); @@ -481,7 +481,7 @@ void PeerServer::maintainTransactions(TransactionQueue& _tq, h256 _currentHash) if (n) { RLPStream ts; - PeerSession::prep(ts); + EthereumSession::prep(ts); ts.appendList(n + 1) << TransactionsPacket; ts.appendRaw(b, n).swapOut(b); seal(b); @@ -492,7 +492,7 @@ void PeerServer::maintainTransactions(TransactionQueue& _tq, h256 _currentHash) } } -void PeerServer::maintainBlocks(BlockQueue& _bq, h256 _currentHash) +void EthereumHost::maintainBlocks(BlockQueue& _bq, h256 _currentHash) { // Import new blocks { @@ -508,7 +508,7 @@ void PeerServer::maintainBlocks(BlockQueue& _bq, h256 _currentHash) if (_currentHash != m_latestBlockSent) { RLPStream ts; - PeerSession::prep(ts); + EthereumSession::prep(ts); bytes bs; unsigned c = 0; for (auto h: m_chain->treeRoute(m_latestBlockSent, _currentHash, nullptr, false, true)) @@ -533,7 +533,7 @@ void PeerServer::maintainBlocks(BlockQueue& _bq, h256 _currentHash) m_latestBlockSent = _currentHash; } -void PeerServer::growPeers() +void EthereumHost::growPeers() { Guard l(x_peers); while (m_peers.size() < m_idealPeerCount) @@ -544,7 +544,7 @@ void PeerServer::growPeers() { RLPStream s; bytes b; - (PeerSession::prep(s).appendList(1) << GetPeersPacket).swapOut(b); + (EthereumSession::prep(s).appendList(1) << GetPeersPacket).swapOut(b); seal(b); for (auto const& i: m_peers) if (auto p = i.second.lock()) @@ -567,7 +567,7 @@ void PeerServer::growPeers() } } -void PeerServer::noteHaveChain(std::shared_ptr const& _from) +void EthereumHost::noteHaveChain(std::shared_ptr const& _from) { auto td = _from->m_totalDifficulty; @@ -583,13 +583,13 @@ void PeerServer::noteHaveChain(std::shared_ptr const& _from) { Guard l(x_peers); for (auto const& i: m_peers) - if (shared_ptr p = i.second.lock()) + if (shared_ptr p = i.second.lock()) p->ensureGettingChain(); } } -void PeerServer::prunePeers() +void EthereumHost::prunePeers() { Guard l(x_peers); // We'll keep at most twice as many as is ideal, halfing what counts as "too young to kill" until we get there. @@ -598,7 +598,7 @@ void PeerServer::prunePeers() { // look for worst peer to kick off // first work out how many are old enough to kick off. - shared_ptr worst; + shared_ptr worst; unsigned agedPeers = 0; for (auto i: m_peers) if (auto p = i.second.lock()) @@ -621,11 +621,11 @@ void PeerServer::prunePeers() i = m_peers.erase(i); } -std::vector PeerServer::peers(bool _updatePing) const +std::vector EthereumHost::peers(bool _updatePing) const { Guard l(x_peers); if (_updatePing) - const_cast(this)->pingAll(); + const_cast(this)->pingAll(); this_thread::sleep_for(chrono::milliseconds(200)); std::vector ret; for (auto& i: m_peers) @@ -635,7 +635,7 @@ std::vector PeerServer::peers(bool _updatePing) const return ret; } -void PeerServer::pingAll() +void EthereumHost::pingAll() { Guard l(x_peers); for (auto& i: m_peers) @@ -643,7 +643,7 @@ void PeerServer::pingAll() j->ping(); } -bytes PeerServer::savePeers() const +bytes EthereumHost::savePeers() const { Guard l(x_peers); RLPStream ret; @@ -658,7 +658,7 @@ bytes PeerServer::savePeers() const return RLPStream(n).appendRaw(ret.out(), n).out(); } -void PeerServer::restorePeers(bytesConstRef _b) +void EthereumHost::restorePeers(bytesConstRef _b) { for (auto i: RLP(_b)) { diff --git a/libethereum/PeerServer.h b/libethereum/EthereumHost.h similarity index 52% rename from libethereum/PeerServer.h rename to libethereum/EthereumHost.h index c38d49df4..bbe0d8b86 100644 --- a/libethereum/PeerServer.h +++ b/libethereum/EthereumHost.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU General Public License along with cpp-ethereum. If not, see . */ -/** @file PeerServer.h +/** @file EthereumHost.h * @author Gav Wood * @date 2014 */ @@ -28,9 +28,9 @@ #include #include #include +#include #include -#include "PeerNetwork.h" -#include "Guards.h" +#include "CommonNet.h" namespace ba = boost::asio; namespace bi = boost::asio::ip; @@ -40,129 +40,25 @@ namespace eth class RLPStream; class TransactionQueue; class BlockQueue; -/* -class BasePeerServer -{ - friend class BasePeerSession; - -public: - /// Start server, listening for connections on the given port. - BasePeerServer(std::string const& _clientVersion, u256 _networkId, unsigned short _port, std::string const& _publicAddress = std::string(), bool _upnp = true); - /// Start server, listening for connections on a system-assigned port. - BasePeerServer(std::string const& _clientVersion, u256 _networkId, std::string const& _publicAddress = std::string(), bool _upnp = true); - /// Start server, but don't listen. - BasePeerServer(std::string const& _clientVersion, u256 _networkId); - - /// Will block on network process events. - virtual ~BasePeerServer(); - - /// Closes all peers. - void disconnectPeers(); - - virtual unsigned protocolVersion(); - - /// Connect to a peer explicitly. - void connect(std::string const& _addr, unsigned short _port = 30303) noexcept; - void connect(bi::tcp::endpoint const& _ep); - - /// Conduct I/O, polling, syncing, whatever. - /// Ideally all time-consuming I/O is done in a background thread or otherwise asynchronously, but you get this call every 100ms or so anyway. - /// This won't touch alter the blockchain. - void process() { if (isInitialised()) m_ioService.poll(); } - - /// @returns true iff we have the a peer of the given id. - bool havePeer(Public _id) const; - - /// Set ideal number of peers. - void setIdealPeerCount(unsigned _n) { m_idealPeerCount = _n; } - - /// Get peer information. - std::vector peers(bool _updatePing = false) const; - - /// Get number of peers connected; equivalent to, but faster than, peers().size(). - size_t peerCount() const { Guard l(x_peers); return m_peers.size(); } - - /// Ping the peers, to update the latency information. - void pingAll(); - - /// Get the port we're listening on currently. - unsigned short listenPort() const { return m_public.port(); } - - /// Serialise the set of known peers. - bytes savePeers() const; - - /// Deserialise the data and populate the set of known peers. - void restorePeers(bytesConstRef _b); - - void registerPeer(std::shared_ptr _s); -protected: - /// Called when the session has provided us with a new peer we can connect to. - void noteNewPeers() {} - - void seal(bytes& _b); - void populateAddresses(); - void determinePublic(std::string const& _publicAddress, bool _upnp); - void ensureAccepting(); - - void growPeers(); - void prunePeers(); - - /// Check to see if the network peer-state initialisation has happened. - bool isInitialised() const { return m_latestBlockSent; } - /// Initialises the network peer-state, doing the stuff that needs to be once-only. @returns true if it really was first. - bool ensureInitialised(TransactionQueue& _tq); - - std::map potentialPeers(); - - std::string m_clientVersion; - - unsigned short m_listenPort; - - ba::io_service m_ioService; - bi::tcp::acceptor m_acceptor; - bi::tcp::socket m_socket; - - UPnP* m_upnp = nullptr; - bi::tcp::endpoint m_public; - KeyPair m_key; - - u256 m_networkId; - - mutable std::mutex x_peers; - mutable std::map> m_peers; // mutable because we flush zombie entries (null-weakptrs) as regular maintenance from a const method. - - mutable std::recursive_mutex m_incomingLock; - std::map> m_incomingPeers; - std::vector m_freePeers; - - std::chrono::steady_clock::time_point m_lastPeersRequest; - unsigned m_idealPeerCount = 5; - - std::vector m_addresses; - std::vector m_peerAddresses; - - bool m_accepting = false; -}; -*/ /** - * @brief The PeerServer class + * @brief The EthereumHost class * @warning None of this is thread-safe. You have been warned. */ -class PeerServer//: public BasePeerServer +class EthereumHost { - friend class PeerSession; + friend class EthereumSession; public: /// Start server, listening for connections on the given port. - PeerServer(std::string const& _clientVersion, BlockChain const& _ch, u256 _networkId, unsigned short _port, NodeMode _m = NodeMode::Full, std::string const& _publicAddress = std::string(), bool _upnp = true); + EthereumHost(std::string const& _clientVersion, BlockChain const& _ch, u256 _networkId, unsigned short _port, NodeMode _m = NodeMode::Full, std::string const& _publicAddress = std::string(), bool _upnp = true); /// Start server, listening for connections on a system-assigned port. - PeerServer(std::string const& _clientVersion, BlockChain const& _ch, u256 _networkId, NodeMode _m = NodeMode::Full, std::string const& _publicAddress = std::string(), bool _upnp = true); + EthereumHost(std::string const& _clientVersion, BlockChain const& _ch, u256 _networkId, NodeMode _m = NodeMode::Full, std::string const& _publicAddress = std::string(), bool _upnp = true); /// Start server, but don't listen. - PeerServer(std::string const& _clientVersion, BlockChain const& _ch, u256 _networkId, NodeMode _m = NodeMode::Full); + EthereumHost(std::string const& _clientVersion, BlockChain const& _ch, u256 _networkId, NodeMode _m = NodeMode::Full); /// Will block on network process events. - ~PeerServer(); + ~EthereumHost(); /// Closes all peers. void disconnectPeers(); @@ -209,14 +105,14 @@ public: /// Deserialise the data and populate the set of known peers. void restorePeers(bytesConstRef _b); - void registerPeer(std::shared_ptr _s); + void registerPeer(std::shared_ptr _s); private: /// Session wants to pass us a block that we might not have. /// @returns true if we didn't have it. bool noteBlock(h256 _hash, bytesConstRef _data); /// Session has finished getting the chain of hashes. - void noteHaveChain(std::shared_ptr const& _who); + void noteHaveChain(std::shared_ptr const& _who); /// Called when the session has provided us with a new peer we can connect to. void noteNewPeers() {} @@ -236,7 +132,7 @@ private: h256Set neededBlocks(); /// Check to see if the network peer-state initialisation has happened. - bool isInitialised() const { return m_latestBlockSent; } + virtual bool isInitialised() const { return m_latestBlockSent; } /// Initialises the network peer-state, doing the stuff that needs to be once-only. @returns true if it really was first. bool ensureInitialised(TransactionQueue& _tq); @@ -259,7 +155,7 @@ private: u256 m_networkId; mutable std::mutex x_peers; - mutable std::map> m_peers; // mutable because we flush zombie entries (null-weakptrs) as regular maintenance from a const method. + mutable std::map> m_peers; // mutable because we flush zombie entries (null-weakptrs) as regular maintenance from a const method. mutable std::recursive_mutex m_incomingLock; std::vector m_incomingTransactions; diff --git a/libethereum/PeerSession.cpp b/libethereum/EthereumSession.cpp similarity index 92% rename from libethereum/PeerSession.cpp rename to libethereum/EthereumSession.cpp index b095745f7..835509b63 100644 --- a/libethereum/PeerSession.cpp +++ b/libethereum/EthereumSession.cpp @@ -14,24 +14,24 @@ You should have received a copy of the GNU General Public License along with cpp-ethereum. If not, see . */ -/** @file PeerSession.cpp +/** @file EthereumSession.cpp * @author Gav Wood * @date 2014 */ -#include "PeerSession.h" +#include "EthereumSession.h" #include #include #include #include "BlockChain.h" -#include "PeerServer.h" +#include "EthereumHost.h" using namespace std; using namespace eth; #define clogS(X) eth::LogOutputStream(false) << "| " << std::setw(2) << m_socket.native_handle() << "] " -PeerSession::PeerSession(PeerServer* _s, bi::tcp::socket _socket, u256 _rNId, bi::address _peerAddress, unsigned short _peerPort): +EthereumSession::EthereumSession(EthereumHost* _s, bi::tcp::socket _socket, u256 _rNId, bi::address _peerAddress, unsigned short _peerPort): m_server(_s), m_socket(std::move(_socket)), m_reqNetworkId(_rNId), @@ -43,7 +43,7 @@ PeerSession::PeerSession(PeerServer* _s, bi::tcp::socket _socket, u256 _rNId, bi m_info = PeerInfo({"?", _peerAddress.to_string(), m_listenPort, std::chrono::steady_clock::duration(0)}); } -PeerSession::~PeerSession() +EthereumSession::~EthereumSession() { giveUpOnFetch(); @@ -56,7 +56,7 @@ PeerSession::~PeerSession() catch (...){} } -void PeerSession::giveUpOnFetch() +void EthereumSession::giveUpOnFetch() { if (m_askedBlocks.size()) { @@ -71,7 +71,7 @@ void PeerSession::giveUpOnFetch() } } -bi::tcp::endpoint PeerSession::endpoint() const +bi::tcp::endpoint EthereumSession::endpoint() const { if (m_socket.is_open()) try @@ -83,7 +83,7 @@ bi::tcp::endpoint PeerSession::endpoint() const return bi::tcp::endpoint(); } -bool PeerSession::interpret(RLP const& _r) +bool EthereumSession::interpret(RLP const& _r) { clogS(NetRight) << _r; switch (_r[0].toInt()) @@ -109,7 +109,7 @@ bool PeerSession::interpret(RLP const& _r) return false; } - if (m_protocolVersion != PeerServer::protocolVersion() || m_networkId != m_server->networkId() || !m_id) + if (m_protocolVersion != EthereumHost::protocolVersion() || m_networkId != m_server->networkId() || !m_id) { disconnect(IncompatibleProtocol); return false; @@ -343,7 +343,7 @@ bool PeerSession::interpret(RLP const& _r) return true; } -void PeerSession::ensureGettingChain() +void EthereumSession::ensureGettingChain() { if (!m_askedBlocks.size()) m_askedBlocks = m_server->neededBlocks(); @@ -361,25 +361,25 @@ void PeerSession::ensureGettingChain() clogS(NetMessageSummary) << "No blocks left to get."; } -void PeerSession::ping() +void EthereumSession::ping() { RLPStream s; sealAndSend(prep(s).appendList(1) << PingPacket); m_ping = std::chrono::steady_clock::now(); } -void PeerSession::getPeers() +void EthereumSession::getPeers() { RLPStream s; sealAndSend(prep(s).appendList(1) << GetPeersPacket); } -RLPStream& PeerSession::prep(RLPStream& _s) +RLPStream& EthereumSession::prep(RLPStream& _s) { return _s.appendRaw(bytes(8, 0)); } -void PeerSession::sealAndSend(RLPStream& _s) +void EthereumSession::sealAndSend(RLPStream& _s) { bytes b; _s.swapOut(b); @@ -387,7 +387,7 @@ void PeerSession::sealAndSend(RLPStream& _s) sendDestroy(b); } -bool PeerSession::checkPacket(bytesConstRef _msg) +bool EthereumSession::checkPacket(bytesConstRef _msg) { if (_msg.size() < 8) return false; @@ -402,7 +402,7 @@ bool PeerSession::checkPacket(bytesConstRef _msg) return true; } -void PeerSession::sendDestroy(bytes& _msg) +void EthereumSession::sendDestroy(bytes& _msg) { clogS(NetLeft) << RLP(bytesConstRef(&_msg).cropped(8)); @@ -415,7 +415,7 @@ void PeerSession::sendDestroy(bytes& _msg) writeImpl(buffer); } -void PeerSession::send(bytesConstRef _msg) +void EthereumSession::send(bytesConstRef _msg) { clogS(NetLeft) << RLP(_msg.cropped(8)); @@ -428,7 +428,7 @@ void PeerSession::send(bytesConstRef _msg) writeImpl(buffer); } -void PeerSession::writeImpl(bytes& _buffer) +void EthereumSession::writeImpl(bytes& _buffer) { // cerr << (void*)this << " writeImpl" << endl; if (!m_socket.is_open()) @@ -440,7 +440,7 @@ void PeerSession::writeImpl(bytes& _buffer) write(); } -void PeerSession::write() +void EthereumSession::write() { // cerr << (void*)this << " write" << endl; lock_guard l(m_writeLock); @@ -467,7 +467,7 @@ void PeerSession::write() }); } -void PeerSession::dropped() +void EthereumSession::dropped() { // cerr << (void*)this << " dropped" << endl; if (m_socket.is_open()) @@ -479,7 +479,7 @@ void PeerSession::dropped() catch (...) {} } -void PeerSession::disconnect(int _reason) +void EthereumSession::disconnect(int _reason) { clogS(NetConnect) << "Disconnecting (reason:" << reasonOf((DisconnectReason)_reason) << ")"; if (m_socket.is_open()) @@ -497,12 +497,12 @@ void PeerSession::disconnect(int _reason) } } -void PeerSession::start() +void EthereumSession::start() { RLPStream s; prep(s); s.appendList(9) << HelloPacket - << (uint)PeerServer::protocolVersion() + << (uint)EthereumHost::protocolVersion() << m_server->networkId() << m_server->m_clientVersion << (m_server->m_mode == NodeMode::Full ? 0x07 : m_server->m_mode == NodeMode::PeerServer ? 0x01 : 0) @@ -517,7 +517,7 @@ void PeerSession::start() doRead(); } -void PeerSession::startInitialSync() +void EthereumSession::startInitialSync() { h256 c = m_server->m_chain->currentHash(); uint n = m_server->m_chain->number(); @@ -535,7 +535,7 @@ void PeerSession::startInitialSync() sealAndSend(s); } -void PeerSession::doRead() +void EthereumSession::doRead() { // ignore packets received while waiting to disconnect if (chrono::steady_clock::now() - m_disconnect > chrono::seconds(0)) diff --git a/libethereum/PeerSession.h b/libethereum/EthereumSession.h similarity index 88% rename from libethereum/PeerSession.h rename to libethereum/EthereumSession.h index 4cababbf4..ca44179c6 100644 --- a/libethereum/PeerSession.h +++ b/libethereum/EthereumSession.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU General Public License along with cpp-ethereum. If not, see . */ -/** @file PeerSession.h +/** @file EthereumSession.h * @author Gav Wood * @date 2014 */ @@ -28,22 +28,22 @@ #include #include #include -#include "PeerNetwork.h" +#include "CommonNet.h" namespace eth { /** - * @brief The PeerSession class + * @brief The EthereumSession class * @todo Document fully. */ -class PeerSession: public std::enable_shared_from_this +class EthereumSession: public std::enable_shared_from_this { - friend class PeerServer; + friend class EthereumHost; public: - PeerSession(PeerServer* _server, bi::tcp::socket _socket, u256 _rNId, bi::address _peerAddress, unsigned short _peerPort = 0); - ~PeerSession(); + EthereumSession(EthereumHost* _server, bi::tcp::socket _socket, u256 _rNId, bi::address _peerAddress, unsigned short _peerPort = 0); + ~EthereumSession(); void start(); void disconnect(int _reason); @@ -77,7 +77,7 @@ private: void send(bytesConstRef _msg); void writeImpl(bytes& _buffer); void write(); - PeerServer* m_server; + EthereumHost* m_server; std::recursive_mutex m_writeLock; std::deque m_writeQueue; diff --git a/libethereum/TransactionQueue.h b/libethereum/TransactionQueue.h index 2c6556a71..41cefc56f 100644 --- a/libethereum/TransactionQueue.h +++ b/libethereum/TransactionQueue.h @@ -24,7 +24,7 @@ #include #include #include "libethcore/CommonEth.h" -#include "Guards.h" +#include namespace eth { diff --git a/libethereumx/Ethereum.h b/libethereumx/Ethereum.h index d55d4eaac..fb272c030 100644 --- a/libethereumx/Ethereum.h +++ b/libethereumx/Ethereum.h @@ -28,12 +28,12 @@ #include #include #include +#include #include #include -#include #include #include -#include +#include namespace eth { diff --git a/libethnet/CMakeLists.txt b/libethnet/CMakeLists.txt new file mode 100644 index 000000000..a694ab205 --- /dev/null +++ b/libethnet/CMakeLists.txt @@ -0,0 +1,66 @@ +cmake_policy(SET CMP0015 NEW) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DSTATICLIB") + +aux_source_directory(. SRC_LIST) + +set(EXECUTABLE ethnet) + +# set(CMAKE_INSTALL_PREFIX ../lib) +if(ETH_STATIC) + add_library(${EXECUTABLE} STATIC ${SRC_LIST}) +else() + add_library(${EXECUTABLE} SHARED ${SRC_LIST}) +endif() + +file(GLOB HEADERS "*.h") + +include_directories(..) + +target_link_libraries(${EXECUTABLE} evm) +target_link_libraries(${EXECUTABLE} lll) +target_link_libraries(${EXECUTABLE} ethcore) +target_link_libraries(${EXECUTABLE} secp256k1) +if(MINIUPNPC_LS) +target_link_libraries(${EXECUTABLE} ${MINIUPNPC_LS}) +endif() +target_link_libraries(${EXECUTABLE} ${LEVELDB_LS}) +target_link_libraries(${EXECUTABLE} ${CRYPTOPP_LS}) +target_link_libraries(${EXECUTABLE} gmp) + +if("${TARGET_PLATFORM}" STREQUAL "w64") + target_link_libraries(${EXECUTABLE} boost_system-mt-s) + target_link_libraries(${EXECUTABLE} boost_regex-mt-s) + target_link_libraries(${EXECUTABLE} boost_filesystem-mt-s) + target_link_libraries(${EXECUTABLE} boost_thread_win32-mt-s) + target_link_libraries(${EXECUTABLE} iphlpapi) + target_link_libraries(${EXECUTABLE} ws2_32) + target_link_libraries(${EXECUTABLE} mswsock) + target_link_libraries(${EXECUTABLE} shlwapi) +elseif (APPLE) + # Latest mavericks boost libraries only come with -mt + target_link_libraries(${EXECUTABLE} boost_system-mt) + target_link_libraries(${EXECUTABLE} boost_regex-mt) + target_link_libraries(${EXECUTABLE} boost_filesystem-mt) + target_link_libraries(${EXECUTABLE} boost_thread-mt) + find_package(Threads REQUIRED) + target_link_libraries(${EXECUTABLE} ${CMAKE_THREAD_LIBS_INIT}) +elseif (UNIX) + target_link_libraries(${EXECUTABLE} ${Boost_SYSTEM_LIBRARY}) + target_link_libraries(${EXECUTABLE} ${Boost_REGEX_LIBRARY}) + target_link_libraries(${EXECUTABLE} ${Boost_FILESYSTEM_LIBRARY}) + target_link_libraries(${EXECUTABLE} ${Boost_THREAD_LIBRARY}) + target_link_libraries(${EXECUTABLE} ${Boost_DATE_TIME_LIBRARY}) + target_link_libraries(${EXECUTABLE} ${CMAKE_THREAD_LIBS_INIT}) +else () + target_link_libraries(${EXECUTABLE} boost_system) + target_link_libraries(${EXECUTABLE} boost_regex) + target_link_libraries(${EXECUTABLE} boost_filesystem) + target_link_libraries(${EXECUTABLE} boost_thread) + find_package(Threads REQUIRED) + target_link_libraries(${EXECUTABLE} ${CMAKE_THREAD_LIBS_INIT}) +endif () + +install( TARGETS ${EXECUTABLE} ARCHIVE DESTINATION lib LIBRARY DESTINATION lib ) +install( FILES ${HEADERS} DESTINATION include/${EXECUTABLE} ) + diff --git a/libethnet/Common.cpp b/libethnet/Common.cpp new file mode 100644 index 000000000..5958ee9df --- /dev/null +++ b/libethnet/Common.cpp @@ -0,0 +1,61 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file Common.cpp + * @author Gav Wood + * @date 2014 + */ + +#include "Common.h" +using namespace std; +using namespace eth; + +// Helper function to determine if an address falls within one of the reserved ranges +// For V4: +// Class A "10.*", Class B "172.[16->31].*", Class C "192.168.*" +// Not implemented yet for V6 +bool eth::isPrivateAddress(bi::address _addressToCheck) +{ + if (_addressToCheck.is_v4()) + { + bi::address_v4 v4Address = _addressToCheck.to_v4(); + bi::address_v4::bytes_type bytesToCheck = v4Address.to_bytes(); + if (bytesToCheck[0] == 10 || bytesToCheck[0] == 127) + return true; + if (bytesToCheck[0] == 172 && (bytesToCheck[1] >= 16 && bytesToCheck[1] <= 31)) + return true; + if (bytesToCheck[0] == 192 && bytesToCheck[1] == 168) + return true; + } + return false; +} + +std::string eth::reasonOf(DisconnectReason _r) +{ + switch (_r) + { + case DisconnectRequested: return "Disconnect was requested."; + case TCPError: return "Low-level TCP communication error."; + case BadProtocol: return "Data format error."; + case UselessPeer: return "Peer had no use for this node."; + case TooManyPeers: return "Peer had too many connections."; + case DuplicatePeer: return "Peer was already connected."; + case IncompatibleProtocol: return "Peer protocol versions are incompatible."; + case ClientQuit: return "Peer is exiting."; + default: return "Unknown reason."; + } +} + diff --git a/libethnet/Common.h b/libethnet/Common.h new file mode 100644 index 000000000..099fe085f --- /dev/null +++ b/libethnet/Common.h @@ -0,0 +1,91 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file Common.h + * @author Gav Wood + * @date 2014 + * + * Miscellanea required for the PeerHost/PeerSession classes. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +namespace ba = boost::asio; +namespace bi = boost::asio::ip; + +namespace eth +{ + +bool isPrivateAddress(bi::address _addressToCheck); + +class PeerHost; +class PeerSession; + +struct NetWarn: public LogChannel { static const char* name() { return "!N!"; } static const int verbosity = 0; }; +struct NetNote: public LogChannel { static const char* name() { return "*N*"; } static const int verbosity = 1; }; +struct NetMessageSummary: public LogChannel { static const char* name() { return "-N-"; } static const int verbosity = 2; }; +struct NetConnect: public LogChannel { static const char* name() { return "+N+"; } static const int verbosity = 4; }; +struct NetMessageDetail: public LogChannel { static const char* name() { return "=N="; } static const int verbosity = 5; }; +struct NetTriviaSummary: public LogChannel { static const char* name() { return "-N-"; } static const int verbosity = 10; }; +struct NetTriviaDetail: public LogChannel { static const char* name() { return "=N="; } static const int verbosity = 11; }; +struct NetAllDetail: public LogChannel { static const char* name() { return "=N="; } static const int verbosity = 13; }; +struct NetRight: public LogChannel { static const char* name() { return ">N>"; } static const int verbosity = 14; }; +struct NetLeft: public LogChannel { static const char* name() { return ". +*/ +/** @file PeerHost.cpp + * @authors: + * Gav Wood + * Eric Lombrozo + * @date 2014 + */ + +#include "PeerHost.h" + +#include +#ifdef _WIN32 +// winsock is already included +// #include +#else +#include +#endif + +#include +#include +#include +#include +#include +#include +#include "PeerSession.h" +using namespace std; +using namespace eth; + +// Addresses we will skip during network interface discovery +// Use a vector as the list is small +// Why this and not names? +// Under MacOSX loopback (127.0.0.1) can be named lo0 and br0 are bridges (0.0.0.0) +static const set c_rejectAddresses = { + {bi::address_v4::from_string("127.0.0.1")}, + {bi::address_v6::from_string("::1")}, + {bi::address_v4::from_string("0.0.0.0")}, + {bi::address_v6::from_string("::")} +}; + +PeerHost::PeerHost(std::string const& _clientVersion, u256 _networkId, unsigned short _port, string const& _publicAddress, bool _upnp): + m_clientVersion(_clientVersion), + m_listenPort(_port), + m_acceptor(m_ioService, bi::tcp::endpoint(bi::tcp::v4(), _port)), + m_socket(m_ioService), + m_key(KeyPair::create()), + m_networkId(_networkId) +{ + populateAddresses(); + determinePublic(_publicAddress, _upnp); + ensureAccepting(); + clog(NetNote) << "Id:" << toHex(m_key.address().ref().cropped(0, 4)); +} + +PeerHost::PeerHost(std::string const& _clientVersion, u256 _networkId, string const& _publicAddress, bool _upnp): + m_clientVersion(_clientVersion), + m_listenPort(0), + m_acceptor(m_ioService, bi::tcp::endpoint(bi::tcp::v4(), 0)), + m_socket(m_ioService), + m_key(KeyPair::create()), + m_networkId(_networkId) +{ + m_listenPort = m_acceptor.local_endpoint().port(); + + // populate addresses. + populateAddresses(); + determinePublic(_publicAddress, _upnp); + ensureAccepting(); + clog(NetNote) << "Id:" << toHex(m_key.address().ref().cropped(0, 4)); +} + +PeerHost::PeerHost(std::string const& _clientVersion, u256 _networkId): + m_clientVersion(_clientVersion), + m_listenPort(0), + m_acceptor(m_ioService, bi::tcp::endpoint(bi::tcp::v4(), 0)), + m_socket(m_ioService), + m_key(KeyPair::create()), + m_networkId(_networkId) +{ + // populate addresses. + populateAddresses(); + clog(NetNote) << "Id:" << toHex(m_key.address().ref().cropped(0, 4)); +} + +PeerHost::~PeerHost() +{ + disconnectPeers(); +} + +void PeerHost::registerPeer(std::shared_ptr _s) +{ + Guard l(x_peers); + m_peers[_s->m_id] = _s; +} + +void PeerHost::disconnectPeers() +{ + for (unsigned n = 0;; n = 0) + { + { + Guard l(x_peers); + for (auto i: m_peers) + if (auto p = i.second.lock()) + { + p->disconnect(ClientQuit); + n++; + } + } + if (!n) + break; + m_ioService.poll(); + this_thread::sleep_for(chrono::milliseconds(100)); + } + + delete m_upnp; +} + +void PeerHost::seal(bytes& _b) +{ + _b[0] = 0x22; + _b[1] = 0x40; + _b[2] = 0x08; + _b[3] = 0x91; + uint32_t len = (uint32_t)_b.size() - 8; + _b[4] = (len >> 24) & 0xff; + _b[5] = (len >> 16) & 0xff; + _b[6] = (len >> 8) & 0xff; + _b[7] = len & 0xff; +} + +void PeerHost::determinePublic(string const& _publicAddress, bool _upnp) +{ + if (_upnp) + try + { + m_upnp = new UPnP; + } + catch (NoUPnPDevice) {} // let m_upnp continue as null - we handle it properly. + + bi::tcp::resolver r(m_ioService); + if (m_upnp && m_upnp->isValid() && m_peerAddresses.size()) + { + clog(NetNote) << "External addr: " << m_upnp->externalIP(); + int p = m_upnp->addRedirect(m_peerAddresses[0].to_string().c_str(), m_listenPort); + if (p) + clog(NetNote) << "Punched through NAT and mapped local port" << m_listenPort << "onto external port" << p << "."; + else + { + // couldn't map + clog(NetWarn) << "Couldn't punch through NAT (or no NAT in place). Assuming " << m_listenPort << " is local & external port."; + p = m_listenPort; + } + + auto eip = m_upnp->externalIP(); + if (eip == string("0.0.0.0") && _publicAddress.empty()) + m_public = bi::tcp::endpoint(bi::address(), (unsigned short)p); + else + { + m_public = bi::tcp::endpoint(bi::address::from_string(_publicAddress.empty() ? eip : _publicAddress), (unsigned short)p); + m_addresses.push_back(m_public.address().to_v4()); + } + } + else + { + // No UPnP - fallback on given public address or, if empty, the assumed peer address. + m_public = bi::tcp::endpoint(_publicAddress.size() ? bi::address::from_string(_publicAddress) + : m_peerAddresses.size() ? m_peerAddresses[0] + : bi::address(), m_listenPort); + m_addresses.push_back(m_public.address().to_v4()); + } +} + +void PeerHost::populateAddresses() +{ +#ifdef _WIN32 + WSAData wsaData; + if (WSAStartup(MAKEWORD(1, 1), &wsaData) != 0) + throw NoNetworking(); + + char ac[80]; + if (gethostname(ac, sizeof(ac)) == SOCKET_ERROR) + { + clog(NetWarn) << "Error " << WSAGetLastError() << " when getting local host name."; + WSACleanup(); + throw NoNetworking(); + } + + struct hostent* phe = gethostbyname(ac); + if (phe == 0) + { + clog(NetWarn) << "Bad host lookup."; + WSACleanup(); + throw NoNetworking(); + } + + for (int i = 0; phe->h_addr_list[i] != 0; ++i) + { + struct in_addr addr; + memcpy(&addr, phe->h_addr_list[i], sizeof(struct in_addr)); + char *addrStr = inet_ntoa(addr); + bi::address ad(bi::address::from_string(addrStr)); + m_addresses.push_back(ad.to_v4()); + bool isLocal = std::find(c_rejectAddresses.begin(), c_rejectAddresses.end(), ad) != c_rejectAddresses.end(); + if (!isLocal) + m_peerAddresses.push_back(ad.to_v4()); + clog(NetNote) << "Address: " << ac << " = " << m_addresses.back() << (isLocal ? " [LOCAL]" : " [PEER]"); + } + + WSACleanup(); +#else + ifaddrs* ifaddr; + if (getifaddrs(&ifaddr) == -1) + throw NoNetworking(); + + bi::tcp::resolver r(m_ioService); + + for (ifaddrs* ifa = ifaddr; ifa; ifa = ifa->ifa_next) + { + if (!ifa->ifa_addr) + continue; + if (ifa->ifa_addr->sa_family == AF_INET) + { + char host[NI_MAXHOST]; + if (getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in), host, NI_MAXHOST, NULL, 0, NI_NUMERICHOST)) + continue; + try + { + auto it = r.resolve({host, "30303"}); + bi::tcp::endpoint ep = it->endpoint(); + bi::address ad = ep.address(); + m_addresses.push_back(ad.to_v4()); + bool isLocal = std::find(c_rejectAddresses.begin(), c_rejectAddresses.end(), ad) != c_rejectAddresses.end(); + if (!isLocal) + m_peerAddresses.push_back(ad.to_v4()); + clog(NetNote) << "Address: " << host << " = " << m_addresses.back() << (isLocal ? " [LOCAL]" : " [PEER]"); + } + catch (...) + { + clog(NetNote) << "Couldn't resolve: " << host; + } + } + } + + freeifaddrs(ifaddr); +#endif +} + +std::map PeerHost::potentialPeers() +{ + std::map ret; + if (!m_public.address().is_unspecified()) + ret.insert(make_pair(m_key.pub(), m_public)); + Guard l(x_peers); + for (auto i: m_peers) + if (auto j = i.second.lock()) + { + auto ep = j->endpoint(); + // Skip peers with a listen port of zero or are on a private network + bool peerOnNet = (j->m_listenPort != 0 && !isPrivateAddress(ep.address())); + if (peerOnNet && ep.port() && j->m_id) + ret.insert(make_pair(i.first, ep)); + } + return ret; +} + +void PeerHost::ensureAccepting() +{ + if (m_accepting == false) + { + clog(NetConnect) << "Listening on local port " << m_listenPort << " (public: " << m_public << ")"; + m_accepting = true; + m_acceptor.async_accept(m_socket, [=](boost::system::error_code ec) + { + if (!ec) + try + { + try { + clog(NetConnect) << "Accepted connection from " << m_socket.remote_endpoint(); + } catch (...){} + bi::address remoteAddress = m_socket.remote_endpoint().address(); + // Port defaults to 0 - we let the hello tell us which port the peer listens to + auto p = std::make_shared(this, std::move(m_socket), m_networkId, remoteAddress); + p->start(); + } + catch (std::exception const& _e) + { + clog(NetWarn) << "ERROR: " << _e.what(); + } + m_accepting = false; + if (ec.value() != 1) + ensureAccepting(); + }); + } +} + +void PeerHost::connect(std::string const& _addr, unsigned short _port) noexcept +{ + try + { + connect(bi::tcp::endpoint(bi::address::from_string(_addr), _port)); + } + catch (exception const& e) + { + // Couldn't connect + clog(NetConnect) << "Bad host " << _addr << " (" << e.what() << ")"; + } +} + +void PeerHost::connect(bi::tcp::endpoint const& _ep) +{ + clog(NetConnect) << "Attempting connection to " << _ep; + bi::tcp::socket* s = new bi::tcp::socket(m_ioService); + s->async_connect(_ep, [=](boost::system::error_code const& ec) + { + if (ec) + { + clog(NetConnect) << "Connection refused to " << _ep << " (" << ec.message() << ")"; + for (auto i = m_incomingPeers.begin(); i != m_incomingPeers.end(); ++i) + if (i->second.first == _ep && i->second.second < 3) + { + m_freePeers.push_back(i->first); + goto OK; + } + // for-else + clog(NetConnect) << "Giving up."; + OK:; + } + else + { + auto p = make_shared(this, std::move(*s), m_networkId, _ep.address(), _ep.port()); + clog(NetConnect) << "Connected to " << _ep; + p->start(); + } + delete s; + }); +} + +bool PeerHost::havePeer(Public _id) const +{ + Guard l(x_peers); + + // Remove dead peers from list. + for (auto i = m_peers.begin(); i != m_peers.end();) + if (i->second.lock().get()) + ++i; + else + i = m_peers.erase(i); + + return !!m_peers.count(_id); +} + +void PeerHost::growPeers() +{ + Guard l(x_peers); + while (m_peers.size() < m_idealPeerCount) + { + if (m_freePeers.empty()) + { + if (chrono::steady_clock::now() > m_lastPeersRequest + chrono::seconds(10)) + { + RLPStream s; + bytes b; + (PeerSession::prep(s).appendList(1) << GetPeersPacket).swapOut(b); + seal(b); + for (auto const& i: m_peers) + if (auto p = i.second.lock()) + if (p->isOpen()) + p->send(&b); + m_lastPeersRequest = chrono::steady_clock::now(); + } + + + if (!m_accepting) + ensureAccepting(); + + break; + } + + auto x = time(0) % m_freePeers.size(); + m_incomingPeers[m_freePeers[x]].second++; + connect(m_incomingPeers[m_freePeers[x]].first); + m_freePeers.erase(m_freePeers.begin() + x); + } +} + +void PeerHost::prunePeers() +{ + Guard l(x_peers); + // We'll keep at most twice as many as is ideal, halfing what counts as "too young to kill" until we get there. + for (uint old = 15000; m_peers.size() > m_idealPeerCount * 2 && old > 100; old /= 2) + while (m_peers.size() > m_idealPeerCount) + { + // look for worst peer to kick off + // first work out how many are old enough to kick off. + shared_ptr worst; + unsigned agedPeers = 0; + for (auto i: m_peers) + if (auto p = i.second.lock()) + if (/*(m_mode != NodeMode::PeerHost || p->m_caps != 0x01) &&*/ chrono::steady_clock::now() > p->m_connect + chrono::milliseconds(old)) // don't throw off new peers; peer-servers should never kick off other peer-servers. + { + ++agedPeers; + if ((!worst || p->m_rating < worst->m_rating || (p->m_rating == worst->m_rating && p->m_connect > worst->m_connect))) // kill older ones + worst = p; + } + if (!worst || agedPeers <= m_idealPeerCount) + break; + worst->disconnect(TooManyPeers); + } + + // Remove dead peers from list. + for (auto i = m_peers.begin(); i != m_peers.end();) + if (i->second.lock().get()) + ++i; + else + i = m_peers.erase(i); +} + +std::vector PeerHost::peers(bool _updatePing) const +{ + Guard l(x_peers); + if (_updatePing) + const_cast(this)->pingAll(); + this_thread::sleep_for(chrono::milliseconds(200)); + std::vector ret; + for (auto& i: m_peers) + if (auto j = i.second.lock()) + if (j->m_socket.is_open()) + ret.push_back(j->m_info); + return ret; +} + +void PeerHost::pingAll() +{ + Guard l(x_peers); + for (auto& i: m_peers) + if (auto j = i.second.lock()) + j->ping(); +} + +bytes PeerHost::savePeers() const +{ + Guard l(x_peers); + RLPStream ret; + int n = 0; + for (auto& i: m_peers) + if (auto p = i.second.lock()) + if (p->m_socket.is_open() && p->endpoint().port()) + { + ret.appendList(3) << p->endpoint().address().to_v4().to_bytes() << p->endpoint().port() << p->m_id; + n++; + } + return RLPStream(n).appendRaw(ret.out(), n).out(); +} + +void PeerHost::restorePeers(bytesConstRef _b) +{ + for (auto i: RLP(_b)) + { + auto k = (Public)i[2]; + if (!m_incomingPeers.count(k)) + { + m_incomingPeers.insert(make_pair(k, make_pair(bi::tcp::endpoint(bi::address_v4(i[0].toArray()), i[1].toInt()), 0))); + m_freePeers.push_back(k); + } + } +} diff --git a/libethnet/PeerHost.h b/libethnet/PeerHost.h new file mode 100644 index 000000000..b23b1d1ee --- /dev/null +++ b/libethnet/PeerHost.h @@ -0,0 +1,145 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file EthereumHost.h + * @author Gav Wood + * @date 2014 + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "Common.h" +namespace ba = boost::asio; +namespace bi = boost::asio::ip; + +namespace eth +{ + +class RLPStream; +class TransactionQueue; +class BlockQueue; + +class PeerHost +{ + friend class PeerSession; + +public: + /// Start server, listening for connections on the given port. + PeerHost(std::string const& _clientVersion, u256 _networkId, unsigned short _port, std::string const& _publicAddress = std::string(), bool _upnp = true); + /// Start server, listening for connections on a system-assigned port. + PeerHost(std::string const& _clientVersion, u256 _networkId, std::string const& _publicAddress = std::string(), bool _upnp = true); + /// Start server, but don't listen. + PeerHost(std::string const& _clientVersion, u256 _networkId); + + /// Will block on network process events. + virtual ~PeerHost(); + + /// Closes all peers. + void disconnectPeers(); + + virtual u256 networkId() { return m_networkId; } + virtual unsigned protocolVersion() { return 0; } + + /// Connect to a peer explicitly. + void connect(std::string const& _addr, unsigned short _port = 30303) noexcept; + void connect(bi::tcp::endpoint const& _ep); + + /// Conduct I/O, polling, syncing, whatever. + /// Ideally all time-consuming I/O is done in a background thread or otherwise asynchronously, but you get this call every 100ms or so anyway. + /// This won't touch alter the blockchain. + void process() { if (isInitialised()) m_ioService.poll(); } + + /// @returns true iff we have the a peer of the given id. + bool havePeer(Public _id) const; + + /// Set ideal number of peers. + void setIdealPeerCount(unsigned _n) { m_idealPeerCount = _n; } + + /// Get peer information. + std::vector peers(bool _updatePing = false) const; + + /// Get number of peers connected; equivalent to, but faster than, peers().size(). + size_t peerCount() const { Guard l(x_peers); return m_peers.size(); } + + /// Ping the peers, to update the latency information. + void pingAll(); + + /// Get the port we're listening on currently. + unsigned short listenPort() const { return m_public.port(); } + + /// Serialise the set of known peers. + bytes savePeers() const; + + /// Deserialise the data and populate the set of known peers. + void restorePeers(bytesConstRef _b); + + void registerPeer(std::shared_ptr _s); + +protected: + /// Called when the session has provided us with a new peer we can connect to. + void noteNewPeers() {} + + virtual bool isInitialised() const { return true; } + void seal(bytes& _b); + void populateAddresses(); + void determinePublic(std::string const& _publicAddress, bool _upnp); + void ensureAccepting(); + + void growPeers(); + void prunePeers(); + + std::map potentialPeers(); + + std::string m_clientVersion; + + unsigned short m_listenPort; + + ba::io_service m_ioService; + bi::tcp::acceptor m_acceptor; + bi::tcp::socket m_socket; + + UPnP* m_upnp = nullptr; + bi::tcp::endpoint m_public; + KeyPair m_key; + + u256 m_networkId; + + mutable std::mutex x_peers; + mutable std::map> m_peers; // mutable because we flush zombie entries (null-weakptrs) as regular maintenance from a const method. + + mutable std::recursive_mutex m_incomingLock; + std::map> m_incomingPeers; + std::vector m_freePeers; + + std::chrono::steady_clock::time_point m_lastPeersRequest; + unsigned m_idealPeerCount = 5; + + std::vector m_addresses; + std::vector m_peerAddresses; + + bool m_accepting = false; +}; + +} diff --git a/libethnet/PeerSession.cpp b/libethnet/PeerSession.cpp new file mode 100644 index 000000000..a08357dc0 --- /dev/null +++ b/libethnet/PeerSession.cpp @@ -0,0 +1,418 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file PeerSession.cpp + * @author Gav Wood + * @date 2014 + */ + +#include "PeerSession.h" + +#include +#include +#include +#include "PeerHost.h" +using namespace std; +using namespace eth; + +#define clogS(X) eth::LogOutputStream(false) << "| " << std::setw(2) << m_socket.native_handle() << "] " + +PeerSession::PeerSession(PeerHost* _s, bi::tcp::socket _socket, u256 _rNId, bi::address _peerAddress, unsigned short _peerPort): + m_server(_s), + m_socket(std::move(_socket)), + m_reqNetworkId(_rNId), + m_listenPort(_peerPort), + m_rating(0) +{ + m_disconnect = std::chrono::steady_clock::time_point::max(); + m_connect = std::chrono::steady_clock::now(); + m_info = PeerInfo({"?", _peerAddress.to_string(), m_listenPort, std::chrono::steady_clock::duration(0)}); +} + +PeerSession::~PeerSession() +{ + // Read-chain finished for one reason or another. + try + { + if (m_socket.is_open()) + m_socket.close(); + } + catch (...){} +} + +bi::tcp::endpoint PeerSession::endpoint() const +{ + if (m_socket.is_open()) + try + { + return bi::tcp::endpoint(m_socket.remote_endpoint().address(), m_listenPort); + } + catch (...){} + + return bi::tcp::endpoint(); +} + +bool PeerSession::preInterpret(RLP const& _r) +{ + clogS(NetRight) << _r; + switch (_r[0].toInt()) + { + case HelloPacket: + { + m_protocolVersion = _r[1].toInt(); + m_networkId = _r[2].toInt(); + auto clientVersion = _r[3].toString(); + m_caps = _r[4].toInt(); + m_listenPort = _r[5].toInt(); + m_id = _r[6].toHash(); + + clogS(NetMessageSummary) << "Hello: " << clientVersion << "V[" << m_protocolVersion << "/" << m_networkId << "]" << m_id.abridged() << showbase << hex << m_caps << dec << m_listenPort; + + if (m_server->havePeer(m_id)) + { + // Already connected. + cwarn << "Already have peer id" << m_id.abridged();// << "at" << l->endpoint() << "rather than" << endpoint(); + disconnect(DuplicatePeer); + return false; + } + + if (m_protocolVersion != m_server->protocolVersion() || m_networkId != m_server->networkId() || !m_id) + { + disconnect(IncompatibleProtocol); + return false; + } + try + { m_info = PeerInfo({clientVersion, m_socket.remote_endpoint().address().to_string(), m_listenPort, std::chrono::steady_clock::duration()}); } + catch (...) + { + disconnect(BadProtocol); + return false; + } + + m_server->registerPeer(shared_from_this()); + onNewPeer(); + break; + } + case DisconnectPacket: + { + string reason = "Unspecified"; + if (_r[1].isInt()) + reason = reasonOf((DisconnectReason)_r[1].toInt()); + + clogS(NetMessageSummary) << "Disconnect (reason: " << reason << ")"; + if (m_socket.is_open()) + clogS(NetNote) << "Closing " << m_socket.remote_endpoint(); + else + clogS(NetNote) << "Remote closed."; + m_socket.close(); + return false; + } + case PingPacket: + { + clogS(NetTriviaSummary) << "Ping"; + RLPStream s; + sealAndSend(prep(s).appendList(1) << PongPacket); + break; + } + case PongPacket: + m_info.lastPing = std::chrono::steady_clock::now() - m_ping; + clogS(NetTriviaSummary) << "Latency: " << chrono::duration_cast(m_info.lastPing).count() << " ms"; + break; + case GetPeersPacket: + { + clogS(NetTriviaSummary) << "GetPeers"; + auto peers = m_server->potentialPeers(); + RLPStream s; + prep(s).appendList(peers.size() + 1); + s << PeersPacket; + for (auto i: peers) + { + clogS(NetTriviaDetail) << "Sending peer " << i.first.abridged() << i.second; + s.appendList(3) << bytesConstRef(i.second.address().to_v4().to_bytes().data(), 4) << i.second.port() << i.first; + } + sealAndSend(s); + break; + } + case PeersPacket: + clogS(NetTriviaSummary) << "Peers (" << dec << (_r.itemCount() - 1) << " entries)"; + for (unsigned i = 1; i < _r.itemCount(); ++i) + { + bi::address_v4 peerAddress(_r[i][0].toHash>().asArray()); + auto ep = bi::tcp::endpoint(peerAddress, _r[i][1].toInt()); + Public id = _r[i][2].toHash(); + if (isPrivateAddress(peerAddress)) + goto CONTINUE; + + clogS(NetAllDetail) << "Checking: " << ep << "(" << id.abridged() << ")"; + + // check that it's not us or one we already know: + if (id && (m_server->m_key.pub() == id || m_server->havePeer(id) || m_server->m_incomingPeers.count(id))) + goto CONTINUE; + + // check that we're not already connected to addr: + if (!ep.port()) + goto CONTINUE; + for (auto i: m_server->m_addresses) + if (ep.address() == i && ep.port() == m_server->listenPort()) + goto CONTINUE; + for (auto i: m_server->m_incomingPeers) + if (i.second.first == ep) + goto CONTINUE; + m_server->m_incomingPeers[id] = make_pair(ep, 0); + m_server->m_freePeers.push_back(id); + m_server->noteNewPeers(); + clogS(NetTriviaDetail) << "New peer: " << ep << "(" << id << ")"; + CONTINUE:; + } + break; + default: + return interpret(_r); + } + return true; +} + +void PeerSession::ping() +{ + RLPStream s; + sealAndSend(prep(s).appendList(1) << PingPacket); + m_ping = std::chrono::steady_clock::now(); +} + +void PeerSession::getPeers() +{ + RLPStream s; + sealAndSend(prep(s).appendList(1) << GetPeersPacket); +} + +RLPStream& PeerSession::prep(RLPStream& _s) +{ + return _s.appendRaw(bytes(8, 0)); +} + +void PeerSession::sealAndSend(RLPStream& _s) +{ + bytes b; + _s.swapOut(b); + m_server->seal(b); + sendDestroy(b); +} + +bool PeerSession::checkPacket(bytesConstRef _msg) +{ + if (_msg.size() < 8) + return false; + if (!(_msg[0] == 0x22 && _msg[1] == 0x40 && _msg[2] == 0x08 && _msg[3] == 0x91)) + return false; + uint32_t len = ((_msg[4] * 256 + _msg[5]) * 256 + _msg[6]) * 256 + _msg[7]; + if (_msg.size() != len + 8) + return false; + RLP r(_msg.cropped(8)); + if (r.actualSize() != len) + return false; + return true; +} + +void PeerSession::sendDestroy(bytes& _msg) +{ + clogS(NetLeft) << RLP(bytesConstRef(&_msg).cropped(8)); + + if (!checkPacket(bytesConstRef(&_msg))) + { + cwarn << "INVALID PACKET CONSTRUCTED!"; + } + + bytes buffer = bytes(std::move(_msg)); + writeImpl(buffer); +} + +void PeerSession::send(bytesConstRef _msg) +{ + clogS(NetLeft) << RLP(_msg.cropped(8)); + + if (!checkPacket(_msg)) + { + cwarn << "INVALID PACKET CONSTRUCTED!"; + } + + bytes buffer = bytes(_msg.toBytes()); + writeImpl(buffer); +} + +void PeerSession::writeImpl(bytes& _buffer) +{ +// cerr << (void*)this << " writeImpl" << endl; + if (!m_socket.is_open()) + return; + + lock_guard l(m_writeLock); + m_writeQueue.push_back(_buffer); + if (m_writeQueue.size() == 1) + write(); +} + +void PeerSession::write() +{ +// cerr << (void*)this << " write" << endl; + lock_guard l(m_writeLock); + if (m_writeQueue.empty()) + return; + + const bytes& bytes = m_writeQueue[0]; + auto self(shared_from_this()); + ba::async_write(m_socket, ba::buffer(bytes), [this, self](boost::system::error_code ec, std::size_t /*length*/) + { +// cerr << (void*)this << " write.callback" << endl; + + // must check queue, as write callback can occur following dropped() + if (ec) + { + cwarn << "Error sending: " << ec.message(); + dropped(); + } + else + { + m_writeQueue.pop_front(); + write(); + } + }); +} + +void PeerSession::dropped() +{ +// cerr << (void*)this << " dropped" << endl; + if (m_socket.is_open()) + try + { + clogS(NetConnect) << "Closing " << m_socket.remote_endpoint(); + m_socket.close(); + } + catch (...) {} +} + +void PeerSession::disconnect(int _reason) +{ + clogS(NetConnect) << "Disconnecting (reason:" << reasonOf((DisconnectReason)_reason) << ")"; + if (m_socket.is_open()) + { + if (m_disconnect == chrono::steady_clock::time_point::max()) + { + RLPStream s; + prep(s); + s.appendList(2) << DisconnectPacket << _reason; + sealAndSend(s); + m_disconnect = chrono::steady_clock::now(); + } + else + dropped(); + } +} + +void PeerSession::start() +{ + RLPStream s; + prep(s); + s.appendList(9) << HelloPacket + << m_server->protocolVersion() + << m_server->networkId() + << m_server->m_clientVersion + << m_server->m_public.port() + << m_server->m_key.pub(); + sealAndSend(s); + ping(); + getPeers(); + + doRead(); +} + +void PeerSession::doRead() +{ + // ignore packets received while waiting to disconnect + if (chrono::steady_clock::now() - m_disconnect > chrono::seconds(0)) + return; + + auto self(shared_from_this()); + m_socket.async_read_some(boost::asio::buffer(m_data), [this,self](boost::system::error_code ec, std::size_t length) + { + // If error is end of file, ignore + if (ec && ec.category() != boost::asio::error::get_misc_category() && ec.value() != boost::asio::error::eof) + { + // got here with length of 1241... + cwarn << "Error reading: " << ec.message(); + dropped(); + } + else if (ec && length == 0) + { + return; + } + else + { + try + { + m_incoming.resize(m_incoming.size() + length); + memcpy(m_incoming.data() + m_incoming.size() - length, m_data.data(), length); + while (m_incoming.size() > 8) + { + if (m_incoming[0] != 0x22 || m_incoming[1] != 0x40 || m_incoming[2] != 0x08 || m_incoming[3] != 0x91) + { + cwarn << "INVALID SYNCHRONISATION TOKEN; expected = 22400891; received = " << toHex(bytesConstRef(m_incoming.data(), 4)); + disconnect(BadProtocol); + return; + } + else + { + uint32_t len = fromBigEndian(bytesConstRef(m_incoming.data() + 4, 4)); + uint32_t tlen = len + 8; + if (m_incoming.size() < tlen) + break; + + // enough has come in. + auto data = bytesConstRef(m_incoming.data(), tlen); + if (!checkPacket(data)) + { + cerr << "Received " << len << ": " << toHex(bytesConstRef(m_incoming.data() + 8, len)) << endl; + cwarn << "INVALID MESSAGE RECEIVED"; + disconnect(BadProtocol); + return; + } + else + { + RLP r(data.cropped(8)); + if (!preInterpret(r)) + { + // error + dropped(); + return; + } + } + memmove(m_incoming.data(), m_incoming.data() + tlen, m_incoming.size() - tlen); + m_incoming.resize(m_incoming.size() - tlen); + } + } + doRead(); + } + catch (Exception const& _e) + { + clogS(NetWarn) << "ERROR: " << _e.description(); + dropped(); + } + catch (std::exception const& _e) + { + clogS(NetWarn) << "ERROR: " << _e.what(); + dropped(); + } + } + }); +} diff --git a/libethnet/PeerSession.h b/libethnet/PeerSession.h new file mode 100644 index 000000000..f2f4bced7 --- /dev/null +++ b/libethnet/PeerSession.h @@ -0,0 +1,105 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file PeerSession.h + * @author Gav Wood + * @date 2014 + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include "Common.h" + +namespace eth +{ + +/** + * @brief The PeerSession class + * @todo Document fully. + */ +class PeerSession: public std::enable_shared_from_this +{ + friend class PeerHost; + +public: + PeerSession(PeerHost* _server, bi::tcp::socket _socket, u256 _rNId, bi::address _peerAddress, unsigned short _peerPort = 0); + virtual ~PeerSession(); + + void start(); + void disconnect(int _reason); + + void ping(); + + bool isOpen() const { return m_socket.is_open(); } + + bi::tcp::endpoint endpoint() const; ///< for other peers to connect to. + +protected: + virtual bool interpret(RLP const& _r); + virtual void onNewPeer(); + + static RLPStream& prep(RLPStream& _s); + void sealAndSend(RLPStream& _s); + void sendDestroy(bytes& _msg); + void send(bytesConstRef _msg); + +private: + void dropped(); + void doRead(); + void doWrite(std::size_t length); + void writeImpl(bytes& _buffer); + void write(); + + void getPeers(); + bool preInterpret(RLP const& _r); + + /// @returns true iff the _msg forms a valid message for sending or receiving on the network. + static bool checkPacket(bytesConstRef _msg); + + PeerHost* m_server; + + std::recursive_mutex m_writeLock; + std::deque m_writeQueue; + + bi::tcp::socket m_socket; + std::array m_data; + PeerInfo m_info; + Public m_id; + + bytes m_incoming; + uint m_protocolVersion; + u256 m_networkId; + u256 m_reqNetworkId; + unsigned short m_listenPort; ///< Port that the remote client is listening on for connections. Useful for giving to peers. + uint m_caps; + + std::chrono::steady_clock::time_point m_ping; + std::chrono::steady_clock::time_point m_connect; + std::chrono::steady_clock::time_point m_disconnect; + + uint m_rating; + + bool m_willBeDeleted = false; ///< True if we already posted a deleter on the strand. +}; + +} diff --git a/libethnet/UPnP.cpp b/libethnet/UPnP.cpp new file mode 100644 index 000000000..427450e03 --- /dev/null +++ b/libethnet/UPnP.cpp @@ -0,0 +1,183 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file UPnP.cpp + * @authors: + * Gav Wood + * @date 2014 + */ + +#include "UPnP.h" + +#include +#include +#if ETH_MINIUPNPC +#include +#include +#include +#endif +#include +#include +#include +using namespace std; +using namespace eth; + +UPnP::UPnP() +{ +#if ETH_MINIUPNPC + m_urls.reset(new UPNPUrls); + m_data.reset(new IGDdatas); + + m_ok = false; + + struct UPNPDev* devlist; + struct UPNPDev* dev; + char* descXML; + int descXMLsize = 0; + int upnperror = 0; + memset(m_urls.get(), 0, sizeof(struct UPNPUrls)); + memset(m_data.get(), 0, sizeof(struct IGDdatas)); + devlist = upnpDiscover(2000, NULL/*multicast interface*/, NULL/*minissdpd socket path*/, 0/*sameport*/, 0/*ipv6*/, &upnperror); + if (devlist) + { + dev = devlist; + while (dev) + { + if (strstr (dev->st, "InternetGatewayDevice")) + break; + dev = dev->pNext; + } + if (!dev) + dev = devlist; /* defaulting to first device */ + + cnote << "UPnP device:" << dev->descURL << "[st:" << dev->st << "]"; +#if MINIUPNPC_API_VERSION >= 9 + descXML = (char*)miniwget(dev->descURL, &descXMLsize, 0); +#else + descXML = (char*)miniwget(dev->descURL, &descXMLsize); +#endif + if (descXML) + { + parserootdesc (descXML, descXMLsize, m_data.get()); + free (descXML); descXML = 0; +#if MINIUPNPC_API_VERSION >= 9 + GetUPNPUrls (m_urls.get(), m_data.get(), dev->descURL, 0); +#else + GetUPNPUrls (m_urls.get(), m_data.get(), dev->descURL); +#endif + m_ok = true; + } + freeUPNPDevlist(devlist); + } + else +#endif + { + cnote << "UPnP device not found."; + throw NoUPnPDevice(); + } +} + +UPnP::~UPnP() +{ + auto r = m_reg; + for (auto i: r) + removeRedirect(i); +} + +string UPnP::externalIP() +{ +#if ETH_MINIUPNPC + char addr[16]; + if (!UPNP_GetExternalIPAddress(m_urls->controlURL, m_data->first.servicetype, addr)) + return addr; + else +#endif + return "0.0.0.0"; +} + +int UPnP::addRedirect(char const* _addr, int _port) +{ + (void)_addr; + (void)_port; +#if ETH_MINIUPNPC + if (m_urls->controlURL[0] == '\0') + { + cwarn << "UPnP::addRedirect() called without proper initialisation?"; + return -1; + } + + // Try direct mapping first (port external, port internal). + char port_str[16]; + sprintf(port_str, "%d", _port); + if (!UPNP_AddPortMapping(m_urls->controlURL, m_data->first.servicetype, port_str, port_str, _addr, "ethereum", "TCP", NULL, NULL)) + return _port; + + // Failed - now try (random external, port internal) and cycle up to 10 times. + for (uint i = 0; i < 10; ++i) + { + _port = rand() % 65535 - 1024 + 1024; + sprintf(port_str, "%d", _port); + if (!UPNP_AddPortMapping(m_urls->controlURL, m_data->first.servicetype, NULL, port_str, _addr, "ethereum", "TCP", NULL, NULL)) + return _port; + } + + // Failed. Try asking the router to give us a free external port. + if (UPNP_AddPortMapping(m_urls->controlURL, m_data->first.servicetype, port_str, NULL, _addr, "ethereum", "TCP", NULL, NULL)) + // Failed. Exit. + return 0; + + // We got mapped, but we don't know which ports we got mapped to. Now to find... + unsigned num = 0; + UPNP_GetPortMappingNumberOfEntries(m_urls->controlURL, m_data->first.servicetype, &num); + for (unsigned i = 0; i < num; ++i) + { + char extPort[16]; + char intClient[16]; + char intPort[6]; + char protocol[4]; + char desc[80]; + char enabled[4]; + char rHost[64]; + char duration[16]; + UPNP_GetGenericPortMappingEntry(m_urls->controlURL, m_data->first.servicetype, toString(i).c_str(), extPort, intClient, intPort, protocol, desc, enabled, rHost, duration); + if (string("ethereum") == desc) + { + m_reg.insert(atoi(extPort)); + return atoi(extPort); + } + } + cerr << "ERROR: Mapped port not found." << endl; +#endif + return 0; +} + +void UPnP::removeRedirect(int _port) +{ + (void)_port; +#if ETH_MINIUPNPC + char port_str[16]; +// int t; + printf("TB : upnp_rem_redir (%d)\n", _port); + if (m_urls->controlURL[0] == '\0') + { + printf("TB : the init was not done !\n"); + return; + } + sprintf(port_str, "%d", _port); + UPNP_DeletePortMapping(m_urls->controlURL, m_data->first.servicetype, port_str, "TCP", NULL); + m_reg.erase(_port); +#endif +} diff --git a/libethnet/UPnP.h b/libethnet/UPnP.h new file mode 100644 index 000000000..836e350b0 --- /dev/null +++ b/libethnet/UPnP.h @@ -0,0 +1,53 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file UPnP.h + * @authors: + * Gav Wood + * @date 2014 + */ + +#pragma once + +#include +#include +#include + +struct UPNPUrls; +struct IGDdatas; + +namespace eth +{ + +class UPnP +{ +public: + UPnP(); + ~UPnP(); + + std::string externalIP(); + int addRedirect(char const* addr, int port); + void removeRedirect(int port); + + bool isValid() const { return m_ok; } + + std::set m_reg; + bool m_ok; + std::shared_ptr m_urls; + std::shared_ptr m_data; +}; + +} diff --git a/libqethereum/QEthereum.cpp b/libqethereum/QEthereum.cpp index e744cd148..96b06ec89 100644 --- a/libqethereum/QEthereum.cpp +++ b/libqethereum/QEthereum.cpp @@ -5,7 +5,7 @@ #include #include #include -#include +#include #include "QEthereum.h" using namespace std; diff --git a/libqethereum/QmlEthereum.cpp b/libqethereum/QmlEthereum.cpp index 7b8e1505a..80ed891eb 100644 --- a/libqethereum/QmlEthereum.cpp +++ b/libqethereum/QmlEthereum.cpp @@ -8,7 +8,7 @@ #include #include #include -#include +#include #include "QmlEthereum.h" using namespace std; diff --git a/test/fork.cpp b/test/fork.cpp index 09a866fb1..f0edb702f 100644 --- a/test/fork.cpp +++ b/test/fork.cpp @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include "TestHelper.h" using namespace std; using namespace eth; diff --git a/test/network.cpp b/test/network.cpp index 2a1614187..978d68934 100644 --- a/test/network.cpp +++ b/test/network.cpp @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include "TestHelper.h" using namespace std; using namespace eth; diff --git a/test/peer.cpp b/test/peer.cpp index 7370df34b..63f2b0861 100644 --- a/test/peer.cpp +++ b/test/peer.cpp @@ -24,7 +24,7 @@ #include #include #include -#include +#include using namespace std; using namespace eth; using boost::asio::ip::tcp; @@ -49,7 +49,7 @@ int peerTest(int argc, char** argv) } BlockChain ch(boost::filesystem::temp_directory_path().string()); - PeerServer pn("Test", ch, 0, listenPort); + EthereumHost pn("Test", ch, 0, listenPort); if (!remoteHost.empty()) pn.connect(remoteHost, remotePort); diff --git a/test/txTest.cpp b/test/txTest.cpp index 53df93a61..314cf9644 100644 --- a/test/txTest.cpp +++ b/test/txTest.cpp @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include "TestHelper.h" using namespace std; using namespace eth; diff --git a/third/MainWin.cpp b/third/MainWin.cpp index 59423d83e..3bebdb7dc 100644 --- a/third/MainWin.cpp +++ b/third/MainWin.cpp @@ -37,7 +37,7 @@ #include #include #include -#include +#include #include "BuildInfo.h" #include "MainWin.h" #include "ui_Main.h" diff --git a/walleth/MainWin.cpp b/walleth/MainWin.cpp index dfebd7dcd..7b8873d45 100644 --- a/walleth/MainWin.cpp +++ b/walleth/MainWin.cpp @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include "BuildInfo.h" #include "MainWin.h" #include "ui_Main.h" From ffe228b385f9e2a72c25780a331473bdacec0b9e Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 28 Aug 2014 13:16:37 +0200 Subject: [PATCH 123/223] Blockchain syncing fixed. --- libethereum/BlockChain.cpp | 21 +++++++++++++++++---- libethereum/EthereumHost.cpp | 14 +++++++++++++- libethereum/EthereumSession.cpp | 12 ++++++++++++ 3 files changed, 42 insertions(+), 5 deletions(-) diff --git a/libethereum/BlockChain.cpp b/libethereum/BlockChain.cpp index f196d540d..d345271a9 100644 --- a/libethereum/BlockChain.cpp +++ b/libethereum/BlockChain.cpp @@ -333,16 +333,23 @@ h256s BlockChain::import(bytes const& _block, OverlayDB const& _db) h256s BlockChain::treeRoute(h256 _from, h256 _to, h256* o_common, bool _pre, bool _post) const { + cdebug << "treeRoute" << _from.abridged() << "..." << _to.abridged(); + if (!_from || !_to) + { + return h256s(); + } h256s ret; h256s back; unsigned fn = details(_from).number; unsigned tn = details(_to).number; + cdebug << "treeRoute" << fn << "..." << tn; while (fn > tn) { if (_pre) ret.push_back(_from); _from = details(_from).parent; fn--; + cdebug << "from:" << fn << _from.abridged(); } while (fn < tn) { @@ -350,15 +357,21 @@ h256s BlockChain::treeRoute(h256 _from, h256 _to, h256* o_common, bool _pre, boo back.push_back(_to); _to = details(_to).parent; tn--; + cdebug << "to:" << tn << _to.abridged(); } while (_from != _to) { + assert(_from); + assert(_to); + _from = details(_from).parent; + _to = details(_to).parent; if (_pre) - _from = details(_from).parent; + ret.push_back(_from); if (_post) - _to = details(_to).parent; - ret.push_back(_from); - back.push_back(_to); + back.push_back(_to); + fn--; + tn--; + cdebug << "from:" << fn << _from.abridged() << "; to:" << tn << _to.abridged(); } if (o_common) *o_common = _from; diff --git a/libethereum/EthereumHost.cpp b/libethereum/EthereumHost.cpp index 9ed782800..beb7a6fbd 100644 --- a/libethereum/EthereumHost.cpp +++ b/libethereum/EthereumHost.cpp @@ -571,15 +571,27 @@ void EthereumHost::noteHaveChain(std::shared_ptr const& _from) { auto td = _from->m_totalDifficulty; + if (_from->m_neededBlocks.empty()) + return; + + clog(NetNote) << "Hash-chain COMPLETE:" << log2((double)_from->m_totalDifficulty) << "vs" << log2((double)m_chain->details().totalDifficulty) << "," << log2((double)m_totalDifficultyOfNeeded) << ";" << _from->m_neededBlocks.size() << " blocks, ends" << _from->m_neededBlocks.back().abridged(); + if ((m_totalDifficultyOfNeeded && td < m_totalDifficultyOfNeeded) || td < m_chain->details().totalDifficulty) + { + clog(NetNote) << "Difficulty of hashchain LOWER. Ignoring."; return; + } + + clog(NetNote) << "Difficulty of hashchain HIGHER. Replacing fetch queue."; + // Looks like it's the best yet for total difficulty. Set to download. { Guard l(x_blocksNeeded); m_blocksNeeded = _from->m_neededBlocks; + m_blocksOnWay.clear(); + m_totalDifficultyOfNeeded = td; } - // Looks like it's the best yet for total difficulty. Set to download. { Guard l(x_peers); for (auto const& i: m_peers) diff --git a/libethereum/EthereumSession.cpp b/libethereum/EthereumSession.cpp index 835509b63..fd0002967 100644 --- a/libethereum/EthereumSession.cpp +++ b/libethereum/EthereumSession.cpp @@ -56,8 +56,19 @@ EthereumSession::~EthereumSession() catch (...){} } +string toString(h256s const& _bs) +{ + ostringstream out; + out << "[ "; + for (auto i: _bs) + out << i.abridged() << ", "; + out << "]"; + return out.str(); +} + void EthereumSession::giveUpOnFetch() { + clogS(NetNote) << "GIVE UP FETCH; can't get " << toString(m_askedBlocks); if (m_askedBlocks.size()) { Guard l (m_server->x_blocksNeeded); @@ -329,6 +340,7 @@ bool EthereumSession::interpret(RLP const& _r) } clogS(NetMessageSummary) << dec << knownParents << " known parents, " << unknownParents << "unknown, " << used << "used."; ensureGettingChain(); + break; } case GetTransactionsPacket: { From a60661f1c32eea237302891ae385c82b2aa0bc98 Mon Sep 17 00:00:00 2001 From: AronVanAmmers Date: Thu, 28 Aug 2014 16:34:22 +0200 Subject: [PATCH 124/223] Updated LibEthereum.vcxproj with removed/added files to fix build in VS2013 --- windows/LibEthereum.vcxproj | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/windows/LibEthereum.vcxproj b/windows/LibEthereum.vcxproj index 95277bbfa..c806de064 100644 --- a/windows/LibEthereum.vcxproj +++ b/windows/LibEthereum.vcxproj @@ -41,15 +41,16 @@ + + + - - - + @@ -100,15 +101,16 @@ + + + - - - + From 0b283fac4005d60370aaeadd4671c96e337e6e09 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 28 Aug 2014 22:01:27 +0200 Subject: [PATCH 125/223] Version bump. New protocol. Network stuff slightly more robust. --- libethcore/CommonEth.cpp | 2 +- libethential/Common.cpp | 2 +- libethereum/BlockChain.cpp | 26 +++++++++++------- libethereum/EthereumHost.cpp | 25 +++++++++++------- libethereum/EthereumHost.h | 2 +- libethereum/EthereumSession.cpp | 47 ++++++++++++++++++++++++++------- libethereum/EthereumSession.h | 6 +++++ libethereum/Transaction.cpp | 2 +- 8 files changed, 80 insertions(+), 32 deletions(-) diff --git a/libethcore/CommonEth.cpp b/libethcore/CommonEth.cpp index db1c9c636..8c0e5518e 100644 --- a/libethcore/CommonEth.cpp +++ b/libethcore/CommonEth.cpp @@ -29,7 +29,7 @@ using namespace eth; //#define ETH_ADDRESS_DEBUG 1 -const unsigned eth::c_protocolVersion = 28; +const unsigned eth::c_protocolVersion = 29; const unsigned eth::c_databaseVersion = 1; static const vector> g_units = diff --git a/libethential/Common.cpp b/libethential/Common.cpp index c64d3cb97..a12dfb903 100644 --- a/libethential/Common.cpp +++ b/libethential/Common.cpp @@ -27,6 +27,6 @@ using namespace eth; namespace eth { -char const* EthVersion = "0.6.6"; +char const* EthVersion = "0.6.7"; } diff --git a/libethereum/BlockChain.cpp b/libethereum/BlockChain.cpp index d345271a9..6a44c8d27 100644 --- a/libethereum/BlockChain.cpp +++ b/libethereum/BlockChain.cpp @@ -165,6 +165,16 @@ bool contains(T const& _t, V const& _v) return false; } +inline string toString(h256s const& _bs) +{ + ostringstream out; + out << "[ "; + for (auto i: _bs) + out << i.abridged() << ", "; + out << "]"; + return out.str(); +} + h256s BlockChain::sync(BlockQueue& _bq, OverlayDB const& _stateDB, unsigned _max) { vector blocks; @@ -320,20 +330,18 @@ h256s BlockChain::import(bytes const& _block, OverlayDB const& _db) m_lastBlockHash = newHash; } m_extrasDB->Put(m_writeOptions, ldb::Slice("best"), ldb::Slice((char const*)&newHash, 32)); - clog(BlockChainNote) << " Imported and best. Has" << (details(bi.parentHash).children.size() - 1) << "siblings. Route:"; - for (auto r: ret) - clog(BlockChainNote) << r.abridged(); + clog(BlockChainNote) << " Imported and best" << td << ". Has" << (details(bi.parentHash).children.size() - 1) << "siblings. Route:" << toString(ret); } else { - clog(BlockChainNote) << " Imported but not best (oTD:" << details(last).totalDifficulty << ", TD:" << td << ")"; + clog(BlockChainNote) << " Imported but not best (oTD:" << details(last).totalDifficulty << " > TD:" << td << ")"; } return ret; } h256s BlockChain::treeRoute(h256 _from, h256 _to, h256* o_common, bool _pre, bool _post) const { - cdebug << "treeRoute" << _from.abridged() << "..." << _to.abridged(); +// cdebug << "treeRoute" << _from.abridged() << "..." << _to.abridged(); if (!_from || !_to) { return h256s(); @@ -342,14 +350,14 @@ h256s BlockChain::treeRoute(h256 _from, h256 _to, h256* o_common, bool _pre, boo h256s back; unsigned fn = details(_from).number; unsigned tn = details(_to).number; - cdebug << "treeRoute" << fn << "..." << tn; +// cdebug << "treeRoute" << fn << "..." << tn; while (fn > tn) { if (_pre) ret.push_back(_from); _from = details(_from).parent; fn--; - cdebug << "from:" << fn << _from.abridged(); +// cdebug << "from:" << fn << _from.abridged(); } while (fn < tn) { @@ -357,7 +365,7 @@ h256s BlockChain::treeRoute(h256 _from, h256 _to, h256* o_common, bool _pre, boo back.push_back(_to); _to = details(_to).parent; tn--; - cdebug << "to:" << tn << _to.abridged(); +// cdebug << "to:" << tn << _to.abridged(); } while (_from != _to) { @@ -371,7 +379,7 @@ h256s BlockChain::treeRoute(h256 _from, h256 _to, h256* o_common, bool _pre, boo back.push_back(_to); fn--; tn--; - cdebug << "from:" << fn << _from.abridged() << "; to:" << tn << _to.abridged(); +// cdebug << "from:" << fn << _from.abridged() << "; to:" << tn << _to.abridged(); } if (o_common) *o_common = _from; diff --git a/libethereum/EthereumHost.cpp b/libethereum/EthereumHost.cpp index beb7a6fbd..5f41e67f2 100644 --- a/libethereum/EthereumHost.cpp +++ b/libethereum/EthereumHost.cpp @@ -367,22 +367,27 @@ void EthereumHost::connect(bi::tcp::endpoint const& _ep) }); } -h256Set EthereumHost::neededBlocks() +h256Set EthereumHost::neededBlocks(h256Set const& _exclude) { Guard l(x_blocksNeeded); h256Set ret; if (m_blocksNeeded.size()) { - while (ret.size() < c_maxBlocksAsk && m_blocksNeeded.size()) - { - ret.insert(m_blocksNeeded.back()); - m_blocksOnWay.insert(m_blocksNeeded.back()); - m_blocksNeeded.pop_back(); - } + int s = m_blocksNeeded.size() - 1; + for (; ret.size() < c_maxBlocksAsk && s < m_blocksNeeded.size(); --s) + if (!_exclude.count(m_blocksNeeded[s])) + { + auto it = m_blocksNeeded.begin() + s; + ret.insert(*it); + m_blocksOnWay.insert(*it); + m_blocksNeeded.erase(it); + } } else - for (auto i = m_blocksOnWay.begin(); ret.size() < c_maxBlocksAsk && i != m_blocksOnWay.end(); ++i) + for (auto i = m_blocksOnWay.begin(); ret.size() < c_maxBlocksAsk && i != m_blocksOnWay.end() && !_exclude.count(*i); ++i) ret.insert(*i); + + clog(NetMessageSummary) << "Asking for" << ret.size() << "blocks that we don't yet have." << m_blocksNeeded.size() << "blocks still needed," << m_blocksOnWay.size() << "blocks on way."; return ret; } @@ -574,7 +579,7 @@ void EthereumHost::noteHaveChain(std::shared_ptr const& _from) if (_from->m_neededBlocks.empty()) return; - clog(NetNote) << "Hash-chain COMPLETE:" << log2((double)_from->m_totalDifficulty) << "vs" << log2((double)m_chain->details().totalDifficulty) << "," << log2((double)m_totalDifficultyOfNeeded) << ";" << _from->m_neededBlocks.size() << " blocks, ends" << _from->m_neededBlocks.back().abridged(); + clog(NetNote) << "Hash-chain COMPLETE:" << _from->m_totalDifficulty << "vs" << m_chain->details().totalDifficulty << "," << m_totalDifficultyOfNeeded << ";" << _from->m_neededBlocks.size() << " blocks, ends" << _from->m_neededBlocks.back().abridged(); if ((m_totalDifficultyOfNeeded && td < m_totalDifficultyOfNeeded) || td < m_chain->details().totalDifficulty) { @@ -596,7 +601,7 @@ void EthereumHost::noteHaveChain(std::shared_ptr const& _from) Guard l(x_peers); for (auto const& i: m_peers) if (shared_ptr p = i.second.lock()) - p->ensureGettingChain(); + p->restartGettingChain(); } } diff --git a/libethereum/EthereumHost.h b/libethereum/EthereumHost.h index bbe0d8b86..9487e40f6 100644 --- a/libethereum/EthereumHost.h +++ b/libethereum/EthereumHost.h @@ -129,7 +129,7 @@ private: /// Get a bunch of needed blocks. /// Removes them from our list of needed blocks. /// @returns empty if there's no more blocks left to fetch, otherwise the blocks to fetch. - h256Set neededBlocks(); + h256Set neededBlocks(h256Set const& _exclude); /// Check to see if the network peer-state initialisation has happened. virtual bool isInitialised() const { return m_latestBlockSent; } diff --git a/libethereum/EthereumSession.cpp b/libethereum/EthereumSession.cpp index fd0002967..db25441e5 100644 --- a/libethereum/EthereumSession.cpp +++ b/libethereum/EthereumSession.cpp @@ -56,7 +56,7 @@ EthereumSession::~EthereumSession() catch (...){} } -string toString(h256s const& _bs) +inline string toString(h256s const& _bs) { ostringstream out; out << "[ "; @@ -75,6 +75,7 @@ void EthereumSession::giveUpOnFetch() m_server->m_blocksNeeded.reserve(m_server->m_blocksNeeded.size() + m_askedBlocks.size()); for (auto i: m_askedBlocks) { + m_failedBlocks.insert(i); m_server->m_blocksOnWay.erase(i); m_server->m_blocksNeeded.push_back(i); } @@ -109,8 +110,9 @@ bool EthereumSession::interpret(RLP const& _r) m_id = _r[6].toHash(); m_totalDifficulty = _r[7].toInt(); m_latestHash = _r[8].toHash(); + auto genesisHash = _r[9].toHash(); - clogS(NetMessageSummary) << "Hello: " << clientVersion << "V[" << m_protocolVersion << "/" << m_networkId << "]" << m_id.abridged() << showbase << hex << m_caps << dec << m_listenPort; + clogS(NetMessageSummary) << "Hello: " << clientVersion << "V[" << m_protocolVersion << "/" << m_networkId << "/" << genesisHash.abridged() << "]" << m_id.abridged() << showbase << hex << m_caps << dec << m_listenPort; if (m_server->havePeer(m_id)) { @@ -120,6 +122,12 @@ bool EthereumSession::interpret(RLP const& _r) return false; } + if (genesisHash != m_server->m_chain->genesisHash()) + { + disconnect(WrongGenesis); + return false; + } + if (m_protocolVersion != EthereumHost::protocolVersion() || m_networkId != m_server->networkId() || !m_id) { disconnect(IncompatibleProtocol); @@ -301,12 +309,12 @@ bool EthereumSession::interpret(RLP const& _r) break; clogS(NetMessageSummary) << "Blocks (" << dec << (_r.itemCount() - 1) << " entries)"; - if (_r.itemCount() == 1) + if (_r.itemCount() == 1 && !m_askedBlocksChanged) { // Couldn't get any from last batch - probably got to this peer's latest block - just give up. giveUpOnFetch(); - break; } + m_askedBlocksChanged = false; unsigned used = 0; for (unsigned i = 1; i < _r.itemCount(); ++i) @@ -339,7 +347,7 @@ bool EthereumSession::interpret(RLP const& _r) } } clogS(NetMessageSummary) << dec << knownParents << " known parents, " << unknownParents << "unknown, " << used << "used."; - ensureGettingChain(); + continueGettingChain(); break; } case GetTransactionsPacket: @@ -355,10 +363,30 @@ bool EthereumSession::interpret(RLP const& _r) return true; } +void EthereumSession::restartGettingChain() +{ + if (m_askedBlocks.size()) + { + m_askedBlocksChanged = true; // So that we continue even if the Ask's reply is empty. + m_askedBlocks.clear(); // So that we restart once we get the Ask's reply. + m_failedBlocks.clear(); + } + else + ensureGettingChain(); +} + void EthereumSession::ensureGettingChain() +{ + if (m_askedBlocks.size()) + return; // Already asked & waiting for some. + + continueGettingChain(); +} + +void EthereumSession::continueGettingChain() { if (!m_askedBlocks.size()) - m_askedBlocks = m_server->neededBlocks(); + m_askedBlocks = m_server->neededBlocks(m_failedBlocks); if (m_askedBlocks.size()) { @@ -370,7 +398,7 @@ void EthereumSession::ensureGettingChain() sealAndSend(s); } else - clogS(NetMessageSummary) << "No blocks left to get."; + clogS(NetMessageSummary) << "No blocks left to get. Peer doesn't seem to have " << m_failedBlocks.size() << "of our needed blocks."; } void EthereumSession::ping() @@ -513,7 +541,7 @@ void EthereumSession::start() { RLPStream s; prep(s); - s.appendList(9) << HelloPacket + s.appendList(10) << HelloPacket << (uint)EthereumHost::protocolVersion() << m_server->networkId() << m_server->m_clientVersion @@ -521,7 +549,8 @@ void EthereumSession::start() << m_server->m_public.port() << m_server->m_key.pub() << m_server->m_chain->details().totalDifficulty - << m_server->m_chain->currentHash(); + << m_server->m_chain->currentHash() + << m_server->m_chain->genesisHash(); sealAndSend(s); ping(); getPeers(); diff --git a/libethereum/EthereumSession.h b/libethereum/EthereumSession.h index ca44179c6..2a7e31155 100644 --- a/libethereum/EthereumSession.h +++ b/libethereum/EthereumSession.h @@ -60,6 +60,10 @@ private: /// Ensure that we are waiting for a bunch of blocks from our peer. void ensureGettingChain(); + /// Ensure that we are waiting for a bunch of blocks from our peer. + void continueGettingChain(); + /// Now getting a different chain so we need to make sure we restart. + void restartGettingChain(); void giveUpOnFetch(); @@ -97,8 +101,10 @@ private: h256 m_latestHash; ///< Peer's latest block's hash. u256 m_totalDifficulty; ///< Peer's latest block's total difficulty. h256s m_neededBlocks; ///< The blocks that we should download from this peer. + h256Set m_failedBlocks; ///< Blocks that the peer doesn't seem to have. h256Set m_askedBlocks; ///< The blocks for which we sent the last GetBlocks for but haven't received a corresponding Blocks. + bool m_askedBlocksChanged = true; std::chrono::steady_clock::time_point m_ping; std::chrono::steady_clock::time_point m_connect; diff --git a/libethereum/Transaction.cpp b/libethereum/Transaction.cpp index 9968683fe..ef89971c5 100644 --- a/libethereum/Transaction.cpp +++ b/libethereum/Transaction.cpp @@ -27,7 +27,7 @@ using namespace std; using namespace eth; -#define ETH_ADDRESS_DEBUG 1 +#define ETH_ADDRESS_DEBUG 0 Transaction::Transaction(bytesConstRef _rlpData, bool _checkSender) { From 2f5c1e742ffab604b1e646ecd18acecc61956da1 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 28 Aug 2014 22:03:51 +0200 Subject: [PATCH 126/223] Minor fix. --- libethereum/EthereumHost.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libethereum/EthereumHost.cpp b/libethereum/EthereumHost.cpp index 5f41e67f2..18c6535b8 100644 --- a/libethereum/EthereumHost.cpp +++ b/libethereum/EthereumHost.cpp @@ -374,7 +374,7 @@ h256Set EthereumHost::neededBlocks(h256Set const& _exclude) if (m_blocksNeeded.size()) { int s = m_blocksNeeded.size() - 1; - for (; ret.size() < c_maxBlocksAsk && s < m_blocksNeeded.size(); --s) + for (; ret.size() < c_maxBlocksAsk && s < (int)m_blocksNeeded.size() && s >= 0; --s) if (!_exclude.count(m_blocksNeeded[s])) { auto it = m_blocksNeeded.begin() + s; From ecb451efe7504fca2bcb579304976df954137258 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 28 Aug 2014 22:04:52 +0200 Subject: [PATCH 127/223] Another minor fix. --- libethereum/EthereumHost.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libethereum/EthereumHost.cpp b/libethereum/EthereumHost.cpp index 18c6535b8..176c7465c 100644 --- a/libethereum/EthereumHost.cpp +++ b/libethereum/EthereumHost.cpp @@ -383,7 +383,7 @@ h256Set EthereumHost::neededBlocks(h256Set const& _exclude) m_blocksNeeded.erase(it); } } - else + if (!ret.size()) for (auto i = m_blocksOnWay.begin(); ret.size() < c_maxBlocksAsk && i != m_blocksOnWay.end() && !_exclude.count(*i); ++i) ret.insert(*i); From f50af8e10ea7061ec76e55b5173a3099f01a42d5 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 28 Aug 2014 22:15:59 +0200 Subject: [PATCH 128/223] Queue diagnostics. --- libethereum/BlockQueue.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/libethereum/BlockQueue.cpp b/libethereum/BlockQueue.cpp index 3f636b102..110137537 100644 --- a/libethereum/BlockQueue.cpp +++ b/libethereum/BlockQueue.cpp @@ -33,11 +33,16 @@ bool BlockQueue::import(bytesConstRef _block, BlockChain const& _bc) // Check if we already know this block. h256 h = sha3(_block); + cnote << "Queuing block" << h.abridged() << "for import..."; + UpgradableGuard l(m_lock); if (m_readySet.count(h) || m_drainingSet.count(h) || m_futureSet.count(h)) + { // Already know about this one. + cnote << "Already known."; return false; + } // VERIFY: populates from the block and checks the block is internally coherent. BlockInfo bi; @@ -60,11 +65,17 @@ bool BlockQueue::import(bytesConstRef _block, BlockChain const& _bc) // Check block doesn't already exist first! if (_bc.details(newHash)) + { + cnote << "Already known in chain."; return false; + } // Check it's not crazy if (bi.timestamp > (u256)time(0)) + { + cnote << "Invalid timestamp."; return false; + } { UpgradeGuard ul(l); @@ -73,12 +84,14 @@ bool BlockQueue::import(bytesConstRef _block, BlockChain const& _bc) if (!m_readySet.count(bi.parentHash) && !m_drainingSet.count(bi.parentHash) && !_bc.details(bi.parentHash)) { // We don't know the parent (yet) - queue it up for later. It'll get resent to us if we find out about its ancestry later on. + cnote << "OK - queued for future."; m_future.insert(make_pair(bi.parentHash, make_pair(h, _block.toBytes()))); m_futureSet.insert(h); } else { // If valid, append to blocks. + cnote << "OK - ready for chain insertion."; m_ready.push_back(_block.toBytes()); m_readySet.insert(h); From 83b3a7b9806cfd1b571c33073579eb7f7d71f9c7 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 28 Aug 2014 22:27:08 +0200 Subject: [PATCH 129/223] Relax time sync requirements slightly. --- libethereum/BlockQueue.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/libethereum/BlockQueue.cpp b/libethereum/BlockQueue.cpp index 110137537..5799e8a54 100644 --- a/libethereum/BlockQueue.cpp +++ b/libethereum/BlockQueue.cpp @@ -71,7 +71,7 @@ bool BlockQueue::import(bytesConstRef _block, BlockChain const& _bc) } // Check it's not crazy - if (bi.timestamp > (u256)time(0)) + if (bi.timestamp > (u256)time(0) - 10) { cnote << "Invalid timestamp."; return false; @@ -84,14 +84,14 @@ bool BlockQueue::import(bytesConstRef _block, BlockChain const& _bc) if (!m_readySet.count(bi.parentHash) && !m_drainingSet.count(bi.parentHash) && !_bc.details(bi.parentHash)) { // We don't know the parent (yet) - queue it up for later. It'll get resent to us if we find out about its ancestry later on. - cnote << "OK - queued for future."; +// cnote << "OK - queued for future."; m_future.insert(make_pair(bi.parentHash, make_pair(h, _block.toBytes()))); m_futureSet.insert(h); } else { // If valid, append to blocks. - cnote << "OK - ready for chain insertion."; +// cnote << "OK - ready for chain insertion."; m_ready.push_back(_block.toBytes()); m_readySet.insert(h); From 78dae17c491caced595a4006763c18a25d3e7a47 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 28 Aug 2014 22:30:22 +0200 Subject: [PATCH 130/223] Actually relax timestamp. --- libethereum/BlockQueue.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libethereum/BlockQueue.cpp b/libethereum/BlockQueue.cpp index 5799e8a54..2d8b6a6b6 100644 --- a/libethereum/BlockQueue.cpp +++ b/libethereum/BlockQueue.cpp @@ -71,7 +71,7 @@ bool BlockQueue::import(bytesConstRef _block, BlockChain const& _bc) } // Check it's not crazy - if (bi.timestamp > (u256)time(0) - 10) + if (bi.timestamp > (u256)time(0) + 10) { cnote << "Invalid timestamp."; return false; From 2a18aa2d8e6cb3de1560a7e5dd30a37fba555c8a Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Thu, 28 Aug 2014 22:53:35 +0200 Subject: [PATCH 131/223] Avoid sending chain back to peer once initial sync complete. --- libethereum/EthereumHost.cpp | 24 +++++++++++++++++++++--- libethereum/EthereumHost.h | 2 ++ libethereum/EthereumSession.cpp | 3 +++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/libethereum/EthereumHost.cpp b/libethereum/EthereumHost.cpp index 176c7465c..2c2e327d5 100644 --- a/libethereum/EthereumHost.cpp +++ b/libethereum/EthereumHost.cpp @@ -421,6 +421,17 @@ bool EthereumHost::ensureInitialised(TransactionQueue& _tq) return false; } +void EthereumHost::noteDoneBlocks() +{ + clog(NetNote) << "Peer given up on blocks fetch."; + if (m_blocksOnWay.empty()) + { + // Done our chain-get. + clog(NetNote) << "No more blocks coming. Missing" << m_blocksNeeded.size() << "blocks."; + m_latestBlockSent = m_chain->currentHash(); + } +} + bool EthereumHost::noteBlock(h256 _hash, bytesConstRef _data) { Guard l(x_blocksNeeded); @@ -509,8 +520,14 @@ void EthereumHost::maintainBlocks(BlockQueue& _bq, h256 _currentHash) m_incomingBlocks.clear(); } - // Send any new blocks. - if (_currentHash != m_latestBlockSent) + // If we've finished our initial sync... + { + Guard l(x_blocksNeeded); + if (m_blocksOnWay.size()) + return; + } + // ...send any new blocks. + if (m_latestBlockSent != _currentHash) { RLPStream ts; EthereumSession::prep(ts); @@ -521,6 +538,7 @@ void EthereumHost::maintainBlocks(BlockQueue& _bq, h256 _currentHash) bs += m_chain->block(h); ++c; } + clog(NetNote) << "Sending" << c << "new blocks (current is" << _currentHash << ", was" << m_latestBlockSent << ")"; ts.appendList(1 + c).append(BlocksPacket).appendRaw(bs, c); bytes b; ts.swapOut(b); @@ -534,8 +552,8 @@ void EthereumHost::maintainBlocks(BlockQueue& _bq, h256 _currentHash) p->send(&b); p->m_knownBlocks.clear(); } + m_latestBlockSent = _currentHash; } - m_latestBlockSent = _currentHash; } void EthereumHost::growPeers() diff --git a/libethereum/EthereumHost.h b/libethereum/EthereumHost.h index 9487e40f6..9a9f83b1b 100644 --- a/libethereum/EthereumHost.h +++ b/libethereum/EthereumHost.h @@ -113,6 +113,8 @@ private: bool noteBlock(h256 _hash, bytesConstRef _data); /// Session has finished getting the chain of hashes. void noteHaveChain(std::shared_ptr const& _who); + /// Called when the peer can no longer provide us with any needed blocks. + void noteDoneBlocks(); /// Called when the session has provided us with a new peer we can connect to. void noteNewPeers() {} diff --git a/libethereum/EthereumSession.cpp b/libethereum/EthereumSession.cpp index db25441e5..5f9ee76fc 100644 --- a/libethereum/EthereumSession.cpp +++ b/libethereum/EthereumSession.cpp @@ -398,7 +398,10 @@ void EthereumSession::continueGettingChain() sealAndSend(s); } else + { clogS(NetMessageSummary) << "No blocks left to get. Peer doesn't seem to have " << m_failedBlocks.size() << "of our needed blocks."; + m_server->noteDoneBlocks(); + } } void EthereumSession::ping() From 278995e19a4faf4c6bcef44d4a4d21951827d6e7 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 30 Aug 2014 11:20:48 +0200 Subject: [PATCH 132/223] Use sha3(header) rather than sha3(block) for hash. --- exp/main.cpp | 12 ++++++++++-- libethcore/BlockInfo.cpp | 5 +++-- libethereum/BlockChain.cpp | 6 +++--- libethereum/BlockQueue.cpp | 5 ++--- libethereum/EthereumSession.cpp | 4 ++-- 5 files changed, 20 insertions(+), 12 deletions(-) diff --git a/exp/main.cpp b/exp/main.cpp index 7406a883d..d79fbb973 100644 --- a/exp/main.cpp +++ b/exp/main.cpp @@ -25,19 +25,24 @@ #include #include #include - +#endif #include #include #include +#include +#if 0 #include #include "BuildInfo.h" +#endif using namespace std; using namespace eth; +#if 0 +#if 0 namespace qi = boost::spirit::qi; namespace px = boost::phoenix; namespace sp = boost::spirit; -#if 0 + class ASTSymbol: public string { public: @@ -291,5 +296,8 @@ int main(int, char**) killBigints(out); cout << endl; #endif + + cnote << RLP(fromHex("f837c0c0b4600160003556601359506301000000600035040f6018590060005660805460016080530160005760003560805760203560003557")); + cnote << toHex(RLPStream(1).append(bytes(54, 0)).out()); return 0; } diff --git a/libethcore/BlockInfo.cpp b/libethcore/BlockInfo.cpp index 0b56c6c85..7f9b94860 100644 --- a/libethcore/BlockInfo.cpp +++ b/libethcore/BlockInfo.cpp @@ -71,6 +71,8 @@ h256 BlockInfo::headerHash(bytesConstRef _block) void BlockInfo::populateFromHeader(RLP const& _header, bool _checkNonce) { + hash = eth::sha3(_header.data()); + int field = 0; try { @@ -106,10 +108,9 @@ void BlockInfo::populateFromHeader(RLP const& _header, bool _checkNonce) void BlockInfo::populate(bytesConstRef _block, bool _checkNonce) { - hash = eth::sha3(_block); - RLP root(_block); RLP header = root[0]; + if (!header.isList()) throw InvalidBlockFormat(0, header.data()); populateFromHeader(header, _checkNonce); diff --git a/libethereum/BlockChain.cpp b/libethereum/BlockChain.cpp index 6a44c8d27..97d69eb41 100644 --- a/libethereum/BlockChain.cpp +++ b/libethereum/BlockChain.cpp @@ -193,7 +193,7 @@ h256s BlockChain::sync(BlockQueue& _bq, OverlayDB const& _stateDB, unsigned _max } catch (UnknownParent) { - cwarn << "Unknown parent of block!!!" << eth::sha3(block).abridged(); + cwarn << "Unknown parent of block!!!" << BlockInfo::headerHash(block).abridged(); _bq.import(&block, *this); } catch (...){} @@ -233,7 +233,7 @@ h256s BlockChain::import(bytes const& _block, OverlayDB const& _db) throw; } #endif - auto newHash = eth::sha3(_block); + auto newHash = BlockInfo::headerHash(_block); // Check block doesn't already exist first! if (details(newHash)) @@ -416,7 +416,7 @@ h256Set BlockChain::allUnclesFrom(h256 _parent) const h256 p = _parent; for (unsigned i = 0; i < 6 && p != m_genesisHash; ++i, p = details(p).parent) { - ret.insert(sha3(RLP(block(p))[0].data())); + ret.insert(p); // TODO: check: should this be details(p).parent? for (auto i: RLP(block(p))[2]) ret.insert(sha3(i.data())); } diff --git a/libethereum/BlockQueue.cpp b/libethereum/BlockQueue.cpp index 2d8b6a6b6..1aca07847 100644 --- a/libethereum/BlockQueue.cpp +++ b/libethereum/BlockQueue.cpp @@ -31,7 +31,7 @@ using namespace eth; bool BlockQueue::import(bytesConstRef _block, BlockChain const& _bc) { // Check if we already know this block. - h256 h = sha3(_block); + h256 h = BlockInfo::headerHash(_block); cnote << "Queuing block" << h.abridged() << "for import..."; @@ -61,10 +61,9 @@ bool BlockQueue::import(bytesConstRef _block, BlockChain const& _bc) return false; } #endif - auto newHash = eth::sha3(_block); // Check block doesn't already exist first! - if (_bc.details(newHash)) + if (_bc.details(h)) { cnote << "Already known in chain."; return false; diff --git a/libethereum/EthereumSession.cpp b/libethereum/EthereumSession.cpp index 5f9ee76fc..a04ccf0a3 100644 --- a/libethereum/EthereumSession.cpp +++ b/libethereum/EthereumSession.cpp @@ -319,7 +319,7 @@ bool EthereumSession::interpret(RLP const& _r) unsigned used = 0; for (unsigned i = 1; i < _r.itemCount(); ++i) { - auto h = sha3(_r[i].data()); + auto h = BlockInfo::headerHash(_r[i].data()); if (m_server->noteBlock(h, _r[i].data())) used++; m_askedBlocks.erase(h); @@ -332,7 +332,7 @@ bool EthereumSession::interpret(RLP const& _r) { for (unsigned i = 1; i < _r.itemCount(); ++i) { - auto h = sha3(_r[i].data()); + auto h = BlockInfo::headerHash(_r[i].data()); BlockInfo bi(_r[i].data()); if (!m_server->m_chain->details(bi.parentHash) && !m_knownBlocks.count(bi.parentHash)) { From ac0fce1b4a8b53e6401b395d57dadcd90dbf545d Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 30 Aug 2014 11:21:47 +0200 Subject: [PATCH 133/223] Target block time of 4s. --- libethcore/BlockInfo.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libethcore/BlockInfo.cpp b/libethcore/BlockInfo.cpp index 7f9b94860..55026f670 100644 --- a/libethcore/BlockInfo.cpp +++ b/libethcore/BlockInfo.cpp @@ -172,7 +172,7 @@ u256 BlockInfo::calculateDifficulty(BlockInfo const& _parent) const if (!parentHash) return c_genesisDifficulty; else - return timestamp >= _parent.timestamp + 9 ? _parent.difficulty - (_parent.difficulty >> 10) : (_parent.difficulty + (_parent.difficulty >> 10)); + return timestamp >= _parent.timestamp + 5 ? _parent.difficulty - (_parent.difficulty >> 10) : (_parent.difficulty + (_parent.difficulty >> 10)); } void BlockInfo::verifyParent(BlockInfo const& _parent) const From 7da31dd62d13aab1972158efbf8ebc8f870e6f77 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 30 Aug 2014 11:31:18 +0200 Subject: [PATCH 134/223] Version bumps. --- libethcore/CommonEth.cpp | 2 +- libethential/Common.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/libethcore/CommonEth.cpp b/libethcore/CommonEth.cpp index 8c0e5518e..b63ade94e 100644 --- a/libethcore/CommonEth.cpp +++ b/libethcore/CommonEth.cpp @@ -29,7 +29,7 @@ using namespace eth; //#define ETH_ADDRESS_DEBUG 1 -const unsigned eth::c_protocolVersion = 29; +const unsigned eth::c_protocolVersion = 30; const unsigned eth::c_databaseVersion = 1; static const vector> g_units = diff --git a/libethential/Common.cpp b/libethential/Common.cpp index a12dfb903..2f6feb9e5 100644 --- a/libethential/Common.cpp +++ b/libethential/Common.cpp @@ -27,6 +27,6 @@ using namespace eth; namespace eth { -char const* EthVersion = "0.6.7"; +char const* EthVersion = "0.6.8"; } From 322be22de62594455edf2abeca64055db6a17f05 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 30 Aug 2014 11:47:51 +0200 Subject: [PATCH 135/223] Fix libethereumx build issues. --- libethereumx/Ethereum.cpp | 30 +++++++++++++++++------------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/libethereumx/Ethereum.cpp b/libethereumx/Ethereum.cpp index 75971f42a..a7725abb5 100644 --- a/libethereumx/Ethereum.cpp +++ b/libethereumx/Ethereum.cpp @@ -63,63 +63,67 @@ void Ethereum::startServer() { } -void Client::flushTransactions() +void Ethereum::flushTransactions() { } -std::vector Client::peers() +std::vector Ethereum::peers() { return std::vector(); } -size_t Client::peerCount() const +size_t Ethereum::peerCount() const { return 0; } -void Client::connect(std::string const& _seedHost, unsigned short _port) +void Ethereum::connect(std::string const& _seedHost, unsigned short _port) { } -void Client::transact(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice) +void Ethereum::transact(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice) { } -bytes Client::call(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice) +bytes Ethereum::call(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice) { return bytes(); } -Address Client::transact(Secret _secret, u256 _endowment, bytes const& _init, u256 _gas, u256 _gasPrice) +Address Ethereum::transact(Secret _secret, u256 _endowment, bytes const& _init, u256 _gas, u256 _gasPrice) { return Address(); } -void Client::inject(bytesConstRef _rlp) +void Ethereum::inject(bytesConstRef _rlp) { } -u256 Client::balanceAt(Address _a, int _block) const +u256 Ethereum::balanceAt(Address _a, int _block) const { return u256(); } -std::map Client::storageAt(Address _a, int _block) const +PastMessages Ethereum::messages(MessageFilter const& _filter) const +{ +} + +std::map Ethereum::storageAt(Address _a, int _block) const { return std::map(); } -u256 Client::countAt(Address _a, int _block) const +u256 Ethereum::countAt(Address _a, int _block) const { return u256(); } -u256 Client::stateAt(Address _a, u256 _l, int _block) const +u256 Ethereum::stateAt(Address _a, u256 _l, int _block) const { return u256(); } -bytes Client::codeAt(Address _a, int _block) const +bytes Ethereum::codeAt(Address _a, int _block) const { return bytes(); } From 3f21dba939e50d395c9e912ce104ac7ac76c67fd Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 30 Aug 2014 11:49:34 +0200 Subject: [PATCH 136/223] Fix undefined symbols. --- libethnet/PeerSession.cpp | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/libethnet/PeerSession.cpp b/libethnet/PeerSession.cpp index a08357dc0..7d47d8444 100644 --- a/libethnet/PeerSession.cpp +++ b/libethnet/PeerSession.cpp @@ -53,6 +53,15 @@ PeerSession::~PeerSession() catch (...){} } +void PeerSession::onNewPeer() +{ +} + +bool PeerSession::interpret(RLP const&) +{ + return false; +} + bi::tcp::endpoint PeerSession::endpoint() const { if (m_socket.is_open()) From b386ac0586497e83a5bb088e98ab8bac32b27c74 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 30 Aug 2014 13:08:53 +0200 Subject: [PATCH 137/223] Link ethereum. --- libethereumx/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/libethereumx/CMakeLists.txt b/libethereumx/CMakeLists.txt index d510fc8d0..1c74bf2c3 100644 --- a/libethereumx/CMakeLists.txt +++ b/libethereumx/CMakeLists.txt @@ -17,6 +17,7 @@ file(GLOB HEADERS "*.h") include_directories(..) +target_link_libraries(${EXECUTABLE} ethereum) target_link_libraries(${EXECUTABLE} evm) target_link_libraries(${EXECUTABLE} lll) target_link_libraries(${EXECUTABLE} ethcore) From c99cee7a65f7b833b3cd941281655dd96d11da93 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 30 Aug 2014 18:39:30 +0200 Subject: [PATCH 138/223] Move to capabilities-based networking. --- libethnet/Common.h | 52 +++++++++++++++++++++++++++++++++++++++ libethnet/PeerHost.cpp | 33 +++++++++++++++---------- libethnet/PeerHost.h | 20 +++++++++------ libethnet/PeerSession.cpp | 40 +++++++++++++----------------- libethnet/PeerSession.h | 13 ++++------ 5 files changed, 107 insertions(+), 51 deletions(-) diff --git a/libethnet/Common.h b/libethnet/Common.h index 099fe085f..64bc20370 100644 --- a/libethnet/Common.h +++ b/libethnet/Common.h @@ -37,6 +37,7 @@ namespace eth bool isPrivateAddress(bi::address _addressToCheck); +class RLP; class PeerHost; class PeerSession; @@ -88,4 +89,55 @@ struct PeerInfo class UPnP; +class PeerCapability; + +class HostCapabilityFace +{ +public: + HostCapabilityFace(PeerHost*) {} + virtual ~HostCapabilityFace() {} + + virtual std::string name() const = 0; + virtual PeerCapability* newPeerCapability(PeerSession* _s) = 0; +}; + +template +class HostCapability: public HostCapabilityFace +{ +public: + HostCapability(PeerHost* _h): m_host(_h) {} + virtual ~HostCapability() {} + + static std::string staticName() { return PeerCap::name(); } + + PeerHost* host() const { return m_host; } + +protected: + virtual std::string name() const { return PeerCap::name(); } + virtual PeerCapability* newPeerCapability(PeerSession* _s) { return new PeerCap(_s); } + +private: + PeerHost* m_host; +}; + +class PeerCapability +{ + friend class PeerSession; + +public: + PeerCapability(PeerSession* _s): m_session(_s) {} + virtual ~PeerCapability() {} + + /// Must return the capability name. + static std::string name() { return ""; } + + PeerSession* session() const { return m_session; } + +protected: + virtual bool interpret(RLP const&) = 0; + +private: + PeerSession* m_session; +}; + } diff --git a/libethnet/PeerHost.cpp b/libethnet/PeerHost.cpp index 54e42d470..d5ca05cc4 100644 --- a/libethnet/PeerHost.cpp +++ b/libethnet/PeerHost.cpp @@ -52,13 +52,12 @@ static const set c_rejectAddresses = { {bi::address_v6::from_string("::")} }; -PeerHost::PeerHost(std::string const& _clientVersion, u256 _networkId, unsigned short _port, string const& _publicAddress, bool _upnp): +PeerHost::PeerHost(std::string const& _clientVersion, unsigned short _port, string const& _publicAddress, bool _upnp): m_clientVersion(_clientVersion), m_listenPort(_port), m_acceptor(m_ioService, bi::tcp::endpoint(bi::tcp::v4(), _port)), m_socket(m_ioService), - m_key(KeyPair::create()), - m_networkId(_networkId) + m_key(KeyPair::create()) { populateAddresses(); determinePublic(_publicAddress, _upnp); @@ -66,13 +65,12 @@ PeerHost::PeerHost(std::string const& _clientVersion, u256 _networkId, unsigned clog(NetNote) << "Id:" << toHex(m_key.address().ref().cropped(0, 4)); } -PeerHost::PeerHost(std::string const& _clientVersion, u256 _networkId, string const& _publicAddress, bool _upnp): +PeerHost::PeerHost(std::string const& _clientVersion, string const& _publicAddress, bool _upnp): m_clientVersion(_clientVersion), m_listenPort(0), m_acceptor(m_ioService, bi::tcp::endpoint(bi::tcp::v4(), 0)), m_socket(m_ioService), - m_key(KeyPair::create()), - m_networkId(_networkId) + m_key(KeyPair::create()) { m_listenPort = m_acceptor.local_endpoint().port(); @@ -83,13 +81,12 @@ PeerHost::PeerHost(std::string const& _clientVersion, u256 _networkId, string co clog(NetNote) << "Id:" << toHex(m_key.address().ref().cropped(0, 4)); } -PeerHost::PeerHost(std::string const& _clientVersion, u256 _networkId): +PeerHost::PeerHost(std::string const& _clientVersion): m_clientVersion(_clientVersion), m_listenPort(0), m_acceptor(m_ioService, bi::tcp::endpoint(bi::tcp::v4(), 0)), m_socket(m_ioService), - m_key(KeyPair::create()), - m_networkId(_networkId) + m_key(KeyPair::create()) { // populate addresses. populateAddresses(); @@ -101,10 +98,20 @@ PeerHost::~PeerHost() disconnectPeers(); } +unsigned PeerHost::protocolVersion() const +{ + return 0; +} + void PeerHost::registerPeer(std::shared_ptr _s) { - Guard l(x_peers); - m_peers[_s->m_id] = _s; + { + Guard l(x_peers); + m_peers[_s->m_id] = _s; + } + for (auto const& i: _s->m_caps) + if (haveCapability(i)) + _s->m_capabilities.push_back(shared_ptr(m_capabilities[i]->newPeerCapability(_s.get()))); } void PeerHost::disconnectPeers() @@ -293,7 +300,7 @@ void PeerHost::ensureAccepting() } catch (...){} bi::address remoteAddress = m_socket.remote_endpoint().address(); // Port defaults to 0 - we let the hello tell us which port the peer listens to - auto p = std::make_shared(this, std::move(m_socket), m_networkId, remoteAddress); + auto p = std::make_shared(this, std::move(m_socket), remoteAddress); p->start(); } catch (std::exception const& _e) @@ -341,7 +348,7 @@ void PeerHost::connect(bi::tcp::endpoint const& _ep) } else { - auto p = make_shared(this, std::move(*s), m_networkId, _ep.address(), _ep.port()); + auto p = make_shared(this, std::move(*s), _ep.address(), _ep.port()); clog(NetConnect) << "Connected to " << _ep; p->start(); } diff --git a/libethnet/PeerHost.h b/libethnet/PeerHost.h index b23b1d1ee..f44d202eb 100644 --- a/libethnet/PeerHost.h +++ b/libethnet/PeerHost.h @@ -47,11 +47,11 @@ class PeerHost public: /// Start server, listening for connections on the given port. - PeerHost(std::string const& _clientVersion, u256 _networkId, unsigned short _port, std::string const& _publicAddress = std::string(), bool _upnp = true); + PeerHost(std::string const& _clientVersion, unsigned short _port, std::string const& _publicAddress = std::string(), bool _upnp = true); /// Start server, listening for connections on a system-assigned port. - PeerHost(std::string const& _clientVersion, u256 _networkId, std::string const& _publicAddress = std::string(), bool _upnp = true); + PeerHost(std::string const& _clientVersion, std::string const& _publicAddress = std::string(), bool _upnp = true); /// Start server, but don't listen. - PeerHost(std::string const& _clientVersion, u256 _networkId); + PeerHost(std::string const& _clientVersion); /// Will block on network process events. virtual ~PeerHost(); @@ -59,8 +59,11 @@ public: /// Closes all peers. void disconnectPeers(); - virtual u256 networkId() { return m_networkId; } - virtual unsigned protocolVersion() { return 0; } + /// Basic peer network protocol version. + unsigned protocolVersion() const; + + /// Register a peer-capability; all new peer connections will have this capability. + template void registerCapability() { m_capabilities[T::name()] = std::shared_ptr(new T(this)); } /// Connect to a peer explicitly. void connect(std::string const& _addr, unsigned short _port = 30303) noexcept; @@ -97,6 +100,9 @@ public: void registerPeer(std::shared_ptr _s); + bool haveCapability(std::string const& _name) const { return m_capabilities.count(_name); } + std::vector caps() const { std::vector ret; for (auto const& i: m_capabilities) ret.push_back(i.first); return ret; } + protected: /// Called when the session has provided us with a new peer we can connect to. void noteNewPeers() {} @@ -124,8 +130,6 @@ protected: bi::tcp::endpoint m_public; KeyPair m_key; - u256 m_networkId; - mutable std::mutex x_peers; mutable std::map> m_peers; // mutable because we flush zombie entries (null-weakptrs) as regular maintenance from a const method. @@ -139,6 +143,8 @@ protected: std::vector m_addresses; std::vector m_peerAddresses; + std::map> m_capabilities; + bool m_accepting = false; }; diff --git a/libethnet/PeerSession.cpp b/libethnet/PeerSession.cpp index 7d47d8444..c2b8ba757 100644 --- a/libethnet/PeerSession.cpp +++ b/libethnet/PeerSession.cpp @@ -30,10 +30,9 @@ using namespace eth; #define clogS(X) eth::LogOutputStream(false) << "| " << std::setw(2) << m_socket.native_handle() << "] " -PeerSession::PeerSession(PeerHost* _s, bi::tcp::socket _socket, u256 _rNId, bi::address _peerAddress, unsigned short _peerPort): +PeerSession::PeerSession(PeerHost* _s, bi::tcp::socket _socket, bi::address _peerAddress, unsigned short _peerPort): m_server(_s), m_socket(std::move(_socket)), - m_reqNetworkId(_rNId), m_listenPort(_peerPort), m_rating(0) { @@ -45,6 +44,9 @@ PeerSession::PeerSession(PeerHost* _s, bi::tcp::socket _socket, u256 _rNId, bi:: PeerSession::~PeerSession() { // Read-chain finished for one reason or another. + for (auto& i: m_capabilities) + i.reset(); + try { if (m_socket.is_open()) @@ -53,15 +55,6 @@ PeerSession::~PeerSession() catch (...){} } -void PeerSession::onNewPeer() -{ -} - -bool PeerSession::interpret(RLP const&) -{ - return false; -} - bi::tcp::endpoint PeerSession::endpoint() const { if (m_socket.is_open()) @@ -74,7 +67,7 @@ bi::tcp::endpoint PeerSession::endpoint() const return bi::tcp::endpoint(); } -bool PeerSession::preInterpret(RLP const& _r) +bool PeerSession::interpret(RLP const& _r) { clogS(NetRight) << _r; switch (_r[0].toInt()) @@ -82,13 +75,12 @@ bool PeerSession::preInterpret(RLP const& _r) case HelloPacket: { m_protocolVersion = _r[1].toInt(); - m_networkId = _r[2].toInt(); - auto clientVersion = _r[3].toString(); - m_caps = _r[4].toInt(); - m_listenPort = _r[5].toInt(); - m_id = _r[6].toHash(); + auto clientVersion = _r[2].toString(); + m_caps = _r[3].toVector(); + m_listenPort = _r[4].toInt(); + m_id = _r[5].toHash(); - clogS(NetMessageSummary) << "Hello: " << clientVersion << "V[" << m_protocolVersion << "/" << m_networkId << "]" << m_id.abridged() << showbase << hex << m_caps << dec << m_listenPort; + clogS(NetMessageSummary) << "Hello: " << clientVersion << "V[" << m_protocolVersion << "]" << m_id.abridged() << showbase << hex << m_caps << dec << m_listenPort; if (m_server->havePeer(m_id)) { @@ -98,7 +90,7 @@ bool PeerSession::preInterpret(RLP const& _r) return false; } - if (m_protocolVersion != m_server->protocolVersion() || m_networkId != m_server->networkId() || !m_id) + if (m_protocolVersion != m_server->protocolVersion() || !m_id) { disconnect(IncompatibleProtocol); return false; @@ -112,7 +104,6 @@ bool PeerSession::preInterpret(RLP const& _r) } m_server->registerPeer(shared_from_this()); - onNewPeer(); break; } case DisconnectPacket: @@ -188,7 +179,10 @@ bool PeerSession::preInterpret(RLP const& _r) } break; default: - return interpret(_r); + for (auto const& i: m_capabilities) + if (i->interpret(_r)) + return true; + return false; } return true; } @@ -335,8 +329,8 @@ void PeerSession::start() prep(s); s.appendList(9) << HelloPacket << m_server->protocolVersion() - << m_server->networkId() << m_server->m_clientVersion + << m_server->caps() << m_server->m_public.port() << m_server->m_key.pub(); sealAndSend(s); @@ -399,7 +393,7 @@ void PeerSession::doRead() else { RLP r(data.cropped(8)); - if (!preInterpret(r)) + if (!interpret(r)) { // error dropped(); diff --git a/libethnet/PeerSession.h b/libethnet/PeerSession.h index f2f4bced7..f51511e04 100644 --- a/libethnet/PeerSession.h +++ b/libethnet/PeerSession.h @@ -42,7 +42,7 @@ class PeerSession: public std::enable_shared_from_this friend class PeerHost; public: - PeerSession(PeerHost* _server, bi::tcp::socket _socket, u256 _rNId, bi::address _peerAddress, unsigned short _peerPort = 0); + PeerSession(PeerHost* _server, bi::tcp::socket _socket, bi::address _peerAddress, unsigned short _peerPort = 0); virtual ~PeerSession(); void start(); @@ -55,9 +55,6 @@ public: bi::tcp::endpoint endpoint() const; ///< for other peers to connect to. protected: - virtual bool interpret(RLP const& _r); - virtual void onNewPeer(); - static RLPStream& prep(RLPStream& _s); void sealAndSend(RLPStream& _s); void sendDestroy(bytes& _msg); @@ -71,7 +68,7 @@ private: void write(); void getPeers(); - bool preInterpret(RLP const& _r); + bool interpret(RLP const& _r); /// @returns true iff the _msg forms a valid message for sending or receiving on the network. static bool checkPacket(bytesConstRef _msg); @@ -88,10 +85,8 @@ private: bytes m_incoming; uint m_protocolVersion; - u256 m_networkId; - u256 m_reqNetworkId; unsigned short m_listenPort; ///< Port that the remote client is listening on for connections. Useful for giving to peers. - uint m_caps; + std::vector m_caps; std::chrono::steady_clock::time_point m_ping; std::chrono::steady_clock::time_point m_connect; @@ -99,6 +94,8 @@ private: uint m_rating; + std::vector> m_capabilities; + bool m_willBeDeleted = false; ///< True if we already posted a deleter on the strand. }; From 126335cdef54823f01f5cb5b87226a543c8adc68 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sat, 30 Aug 2014 20:40:56 +0200 Subject: [PATCH 139/223] Partially rewired network. --- libethereum/CommonNet.cpp | 38 --- libethereum/CommonNet.h | 63 +---- libethereum/EthereumHost.cpp | 487 +------------------------------- libethereum/EthereumHost.h | 99 +------ libethereum/EthereumSession.cpp | 469 ++++-------------------------- libethereum/EthereumSession.h | 67 ++--- libethnet/Common.cpp | 5 + libethnet/Common.h | 14 +- libethnet/PeerHost.cpp | 10 + libethnet/PeerHost.h | 10 +- libethnet/PeerSession.cpp | 2 +- libethnet/PeerSession.h | 1 - 12 files changed, 142 insertions(+), 1123 deletions(-) diff --git a/libethereum/CommonNet.cpp b/libethereum/CommonNet.cpp index 84443eb11..9bc277f86 100644 --- a/libethereum/CommonNet.cpp +++ b/libethereum/CommonNet.cpp @@ -22,41 +22,3 @@ #include "CommonNet.h" using namespace std; using namespace eth; - -// Helper function to determine if an address falls within one of the reserved ranges -// For V4: -// Class A "10.*", Class B "172.[16->31].*", Class C "192.168.*" -// Not implemented yet for V6 -bool eth::isPrivateAddress(bi::address _addressToCheck) -{ - if (_addressToCheck.is_v4()) - { - bi::address_v4 v4Address = _addressToCheck.to_v4(); - bi::address_v4::bytes_type bytesToCheck = v4Address.to_bytes(); - if (bytesToCheck[0] == 10 || bytesToCheck[0] == 127) - return true; - if (bytesToCheck[0] == 172 && (bytesToCheck[1] >= 16 && bytesToCheck[1] <= 31)) - return true; - if (bytesToCheck[0] == 192 && bytesToCheck[1] == 168) - return true; - } - return false; -} - -std::string eth::reasonOf(DisconnectReason _r) -{ - switch (_r) - { - case DisconnectRequested: return "Disconnect was requested."; - case TCPError: return "Low-level TCP communication error."; - case BadProtocol: return "Data format error."; - case UselessPeer: return "Peer had no use for this node."; - case TooManyPeers: return "Peer had too many connections."; - case DuplicatePeer: return "Peer was already connected."; - case WrongGenesis: return "Disagreement over genesis block."; - case IncompatibleProtocol: return "Peer protocol versions are incompatible."; - case ClientQuit: return "Peer is exiting."; - default: return "Unknown reason."; - } -} - diff --git a/libethereum/CommonNet.h b/libethereum/CommonNet.h index b76893d5c..0b848e2c3 100644 --- a/libethereum/CommonNet.h +++ b/libethereum/CommonNet.h @@ -24,89 +24,34 @@ #pragma once #include -#include -#include #include #include #include -namespace ba = boost::asio; -namespace bi = boost::asio::ip; namespace eth { -bool isPrivateAddress(bi::address _addressToCheck); - static const eth::uint c_maxHashes = 32; ///< Maximum number of hashes BlockHashes will ever send. static const eth::uint c_maxHashesAsk = 32; ///< Maximum number of hashes GetBlockHashes will ever ask for. static const eth::uint c_maxBlocks = 16; ///< Maximum number of blocks Blocks will ever send. static const eth::uint c_maxBlocksAsk = 16; ///< Maximum number of blocks we ask to receive in Blocks (when using GetChain). +class UPnP; class OverlayDB; class BlockChain; class TransactionQueue; class EthereumHost; class EthereumSession; -struct NetWarn: public LogChannel { static const char* name() { return "!N!"; } static const int verbosity = 0; }; -struct NetNote: public LogChannel { static const char* name() { return "*N*"; } static const int verbosity = 1; }; -struct NetMessageSummary: public LogChannel { static const char* name() { return "-N-"; } static const int verbosity = 2; }; -struct NetConnect: public LogChannel { static const char* name() { return "+N+"; } static const int verbosity = 4; }; -struct NetMessageDetail: public LogChannel { static const char* name() { return "=N="; } static const int verbosity = 5; }; -struct NetTriviaSummary: public LogChannel { static const char* name() { return "-N-"; } static const int verbosity = 10; }; -struct NetTriviaDetail: public LogChannel { static const char* name() { return "=N="; } static const int verbosity = 11; }; -struct NetAllDetail: public LogChannel { static const char* name() { return "=N="; } static const int verbosity = 13; }; -struct NetRight: public LogChannel { static const char* name() { return ">N>"; } static const int verbosity = 14; }; -struct NetLeft: public LogChannel { static const char* name() { return " -#ifdef _WIN32 -// winsock is already included -// #include -#else -#include -#endif - #include #include #include #include +#include #include #include #include "BlockChain.h" @@ -44,329 +37,19 @@ using namespace std; using namespace eth; -// Addresses we will skip during network interface discovery -// Use a vector as the list is small -// Why this and not names? -// Under MacOSX loopback (127.0.0.1) can be named lo0 and br0 are bridges (0.0.0.0) -static const set c_rejectAddresses = { - {bi::address_v4::from_string("127.0.0.1")}, - {bi::address_v6::from_string("::1")}, - {bi::address_v4::from_string("0.0.0.0")}, - {bi::address_v6::from_string("::")} -}; - -EthereumHost::EthereumHost(std::string const& _clientVersion, BlockChain const& _ch, u256 _networkId, unsigned short _port, NodeMode _m, string const& _publicAddress, bool _upnp): - m_clientVersion(_clientVersion), - m_mode(_m), - m_listenPort(_port), +EthereumHost::EthereumHost(BlockChain const& _ch, u256 _networkId): m_chain(&_ch), - m_acceptor(m_ioService, bi::tcp::endpoint(bi::tcp::v4(), _port)), - m_socket(m_ioService), - m_key(KeyPair::create()), m_networkId(_networkId) { - populateAddresses(); - determinePublic(_publicAddress, _upnp); - ensureAccepting(); - clog(NetNote) << "Id:" << toHex(m_key.address().ref().cropped(0, 4)) << "Mode: " << (_m == NodeMode::PeerServer ? "PeerServer" : "Full"); -} - -EthereumHost::EthereumHost(std::string const& _clientVersion, BlockChain const& _ch, u256 _networkId, NodeMode _m, string const& _publicAddress, bool _upnp): - m_clientVersion(_clientVersion), - m_mode(_m), - m_listenPort(0), - m_chain(&_ch), - m_acceptor(m_ioService, bi::tcp::endpoint(bi::tcp::v4(), 0)), - m_socket(m_ioService), - m_key(KeyPair::create()), - m_networkId(_networkId) -{ - m_listenPort = m_acceptor.local_endpoint().port(); - - // populate addresses. - populateAddresses(); - determinePublic(_publicAddress, _upnp); - ensureAccepting(); - clog(NetNote) << "Id:" << toHex(m_key.address().ref().cropped(0, 4)) << "Mode: " << (m_mode == NodeMode::PeerServer ? "PeerServer" : "Full"); -} - -EthereumHost::EthereumHost(std::string const& _clientVersion, BlockChain const& _ch, u256 _networkId, NodeMode _m): - m_clientVersion(_clientVersion), - m_mode(_m), - m_listenPort(0), - m_chain(&_ch), - m_acceptor(m_ioService, bi::tcp::endpoint(bi::tcp::v4(), 0)), - m_socket(m_ioService), - m_key(KeyPair::create()), - m_networkId(_networkId) -{ - // populate addresses. - populateAddresses(); - clog(NetNote) << "Id:" << toHex(m_key.address().ref().cropped(0, 4)) << "Mode: " << (m_mode == NodeMode::PeerServer ? "PeerServer" : "Full"); } EthereumHost::~EthereumHost() { - disconnectPeers(); - - for (auto i: m_peers) + for (auto i: host()->m_peers) if (shared_ptr p = i.second.lock()) p->giveUpOnFetch(); } -void EthereumHost::registerPeer(std::shared_ptr _s) -{ - Guard l(x_peers); - m_peers[_s->m_id] = _s; -} - -void EthereumHost::disconnectPeers() -{ - for (unsigned n = 0;; n = 0) - { - { - Guard l(x_peers); - for (auto i: m_peers) - if (auto p = i.second.lock()) - { - p->disconnect(ClientQuit); - n++; - } - } - if (!n) - break; - m_ioService.poll(); - this_thread::sleep_for(chrono::milliseconds(100)); - } - - delete m_upnp; -} - -unsigned EthereumHost::protocolVersion() -{ - return c_protocolVersion; -} - -void EthereumHost::seal(bytes& _b) -{ - _b[0] = 0x22; - _b[1] = 0x40; - _b[2] = 0x08; - _b[3] = 0x91; - uint32_t len = (uint32_t)_b.size() - 8; - _b[4] = (len >> 24) & 0xff; - _b[5] = (len >> 16) & 0xff; - _b[6] = (len >> 8) & 0xff; - _b[7] = len & 0xff; -} - -void EthereumHost::determinePublic(string const& _publicAddress, bool _upnp) -{ - if (_upnp) - try - { - m_upnp = new UPnP; - } - catch (NoUPnPDevice) {} // let m_upnp continue as null - we handle it properly. - - bi::tcp::resolver r(m_ioService); - if (m_upnp && m_upnp->isValid() && m_peerAddresses.size()) - { - clog(NetNote) << "External addr: " << m_upnp->externalIP(); - int p = m_upnp->addRedirect(m_peerAddresses[0].to_string().c_str(), m_listenPort); - if (p) - clog(NetNote) << "Punched through NAT and mapped local port" << m_listenPort << "onto external port" << p << "."; - else - { - // couldn't map - clog(NetWarn) << "Couldn't punch through NAT (or no NAT in place). Assuming " << m_listenPort << " is local & external port."; - p = m_listenPort; - } - - auto eip = m_upnp->externalIP(); - if (eip == string("0.0.0.0") && _publicAddress.empty()) - m_public = bi::tcp::endpoint(bi::address(), (unsigned short)p); - else - { - m_public = bi::tcp::endpoint(bi::address::from_string(_publicAddress.empty() ? eip : _publicAddress), (unsigned short)p); - m_addresses.push_back(m_public.address().to_v4()); - } - } - else - { - // No UPnP - fallback on given public address or, if empty, the assumed peer address. - m_public = bi::tcp::endpoint(_publicAddress.size() ? bi::address::from_string(_publicAddress) - : m_peerAddresses.size() ? m_peerAddresses[0] - : bi::address(), m_listenPort); - m_addresses.push_back(m_public.address().to_v4()); - } -} - -void EthereumHost::populateAddresses() -{ -#ifdef _WIN32 - WSAData wsaData; - if (WSAStartup(MAKEWORD(1, 1), &wsaData) != 0) - throw NoNetworking(); - - char ac[80]; - if (gethostname(ac, sizeof(ac)) == SOCKET_ERROR) - { - clog(NetWarn) << "Error " << WSAGetLastError() << " when getting local host name."; - WSACleanup(); - throw NoNetworking(); - } - - struct hostent* phe = gethostbyname(ac); - if (phe == 0) - { - clog(NetWarn) << "Bad host lookup."; - WSACleanup(); - throw NoNetworking(); - } - - for (int i = 0; phe->h_addr_list[i] != 0; ++i) - { - struct in_addr addr; - memcpy(&addr, phe->h_addr_list[i], sizeof(struct in_addr)); - char *addrStr = inet_ntoa(addr); - bi::address ad(bi::address::from_string(addrStr)); - m_addresses.push_back(ad.to_v4()); - bool isLocal = std::find(c_rejectAddresses.begin(), c_rejectAddresses.end(), ad) != c_rejectAddresses.end(); - if (!isLocal) - m_peerAddresses.push_back(ad.to_v4()); - clog(NetNote) << "Address: " << ac << " = " << m_addresses.back() << (isLocal ? " [LOCAL]" : " [PEER]"); - } - - WSACleanup(); -#else - ifaddrs* ifaddr; - if (getifaddrs(&ifaddr) == -1) - throw NoNetworking(); - - bi::tcp::resolver r(m_ioService); - - for (ifaddrs* ifa = ifaddr; ifa; ifa = ifa->ifa_next) - { - if (!ifa->ifa_addr) - continue; - if (ifa->ifa_addr->sa_family == AF_INET) - { - char host[NI_MAXHOST]; - if (getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in), host, NI_MAXHOST, NULL, 0, NI_NUMERICHOST)) - continue; - try - { - auto it = r.resolve({host, "30303"}); - bi::tcp::endpoint ep = it->endpoint(); - bi::address ad = ep.address(); - m_addresses.push_back(ad.to_v4()); - bool isLocal = std::find(c_rejectAddresses.begin(), c_rejectAddresses.end(), ad) != c_rejectAddresses.end(); - if (!isLocal) - m_peerAddresses.push_back(ad.to_v4()); - clog(NetNote) << "Address: " << host << " = " << m_addresses.back() << (isLocal ? " [LOCAL]" : " [PEER]"); - } - catch (...) - { - clog(NetNote) << "Couldn't resolve: " << host; - } - } - } - - freeifaddrs(ifaddr); -#endif -} - -std::map EthereumHost::potentialPeers() -{ - std::map ret; - if (!m_public.address().is_unspecified()) - ret.insert(make_pair(m_key.pub(), m_public)); - Guard l(x_peers); - for (auto i: m_peers) - if (auto j = i.second.lock()) - { - auto ep = j->endpoint(); - // Skip peers with a listen port of zero or are on a private network - bool peerOnNet = (j->m_listenPort != 0 && !isPrivateAddress(ep.address())); - if (peerOnNet && ep.port() && j->m_id) - ret.insert(make_pair(i.first, ep)); - } - return ret; -} - -void EthereumHost::ensureAccepting() -{ - if (m_accepting == false) - { - clog(NetConnect) << "Listening on local port " << m_listenPort << " (public: " << m_public << ")"; - m_accepting = true; - m_acceptor.async_accept(m_socket, [=](boost::system::error_code ec) - { - if (!ec) - try - { - try { - clog(NetConnect) << "Accepted connection from " << m_socket.remote_endpoint(); - } catch (...){} - bi::address remoteAddress = m_socket.remote_endpoint().address(); - // Port defaults to 0 - we let the hello tell us which port the peer listens to - auto p = std::make_shared(this, std::move(m_socket), m_networkId, remoteAddress); - p->start(); - } - catch (std::exception const& _e) - { - clog(NetWarn) << "ERROR: " << _e.what(); - } - m_accepting = false; - if (ec.value() != 1 && (m_mode == NodeMode::PeerServer || peerCount() < m_idealPeerCount * 2)) - ensureAccepting(); - }); - } -} - -void EthereumHost::connect(std::string const& _addr, unsigned short _port) noexcept -{ - try - { - connect(bi::tcp::endpoint(bi::address::from_string(_addr), _port)); - } - catch (exception const& e) - { - // Couldn't connect - clog(NetConnect) << "Bad host " << _addr << " (" << e.what() << ")"; - } -} - -void EthereumHost::connect(bi::tcp::endpoint const& _ep) -{ - clog(NetConnect) << "Attempting connection to " << _ep; - bi::tcp::socket* s = new bi::tcp::socket(m_ioService); - s->async_connect(_ep, [=](boost::system::error_code const& ec) - { - if (ec) - { - clog(NetConnect) << "Connection refused to " << _ep << " (" << ec.message() << ")"; - for (auto i = m_incomingPeers.begin(); i != m_incomingPeers.end(); ++i) - if (i->second.first == _ep && i->second.second < 3) - { - m_freePeers.push_back(i->first); - goto OK; - } - // for-else - clog(NetConnect) << "Giving up."; - OK:; - } - else - { - auto p = make_shared(this, std::move(*s), m_networkId, _ep.address(), _ep.port()); - clog(NetConnect) << "Connected to " << _ep; - p->start(); - } - delete s; - }); -} - h256Set EthereumHost::neededBlocks(h256Set const& _exclude) { Guard l(x_blocksNeeded); @@ -391,20 +74,6 @@ h256Set EthereumHost::neededBlocks(h256Set const& _exclude) return ret; } -bool EthereumHost::havePeer(Public _id) const -{ - Guard l(x_peers); - - // Remove dead peers from list. - for (auto i = m_peers.begin(); i != m_peers.end();) - if (i->second.lock().get()) - ++i; - else - i = m_peers.erase(i); - - return !!m_peers.count(_id); -} - bool EthereumHost::ensureInitialised(TransactionQueue& _tq) { if (m_latestBlockSent == h256()) @@ -448,24 +117,9 @@ bool EthereumHost::noteBlock(h256 _hash, bytesConstRef _data) bool EthereumHost::sync(TransactionQueue& _tq, BlockQueue& _bq) { bool netChange = ensureInitialised(_tq); - - if (m_mode == NodeMode::Full) - { - auto h = m_chain->currentHash(); - - maintainTransactions(_tq, h); - maintainBlocks(_bq, h); - - // Connect to additional peers - growPeers(); - } - - // platform for consensus of social contract. - // restricts your freedom but does so fairly. and that's the value proposition. - // guarantees that everyone else respect the rules of the system. (i.e. obeys laws). - - prunePeers(); - + auto h = m_chain->currentHash(); + maintainTransactions(_tq, h); + maintainBlocks(_bq, h); return netChange; } @@ -481,8 +135,8 @@ void EthereumHost::maintainTransactions(TransactionQueue& _tq, h256 _currentHash m_incomingTransactions.clear(); // Send any new transactions. - Guard l(x_peers); - for (auto j: m_peers) + Guard l(host()->x_peers); + for (auto j: host()->m_peers) if (auto p = j.second.lock()) { bytes b; @@ -544,8 +198,8 @@ void EthereumHost::maintainBlocks(BlockQueue& _bq, h256 _currentHash) ts.swapOut(b); seal(b); - Guard l(x_peers); - for (auto j: m_peers) + Guard l(host()->x_peers); + for (auto j: host()->m_peers) if (auto p = j.second.lock()) { if (!p->m_knownBlocks.count(_currentHash)) @@ -556,40 +210,6 @@ void EthereumHost::maintainBlocks(BlockQueue& _bq, h256 _currentHash) } } -void EthereumHost::growPeers() -{ - Guard l(x_peers); - while (m_peers.size() < m_idealPeerCount) - { - if (m_freePeers.empty()) - { - if (chrono::steady_clock::now() > m_lastPeersRequest + chrono::seconds(10)) - { - RLPStream s; - bytes b; - (EthereumSession::prep(s).appendList(1) << GetPeersPacket).swapOut(b); - seal(b); - for (auto const& i: m_peers) - if (auto p = i.second.lock()) - if (p->isOpen()) - p->send(&b); - m_lastPeersRequest = chrono::steady_clock::now(); - } - - - if (!m_accepting) - ensureAccepting(); - - break; - } - - auto x = time(0) % m_freePeers.size(); - m_incomingPeers[m_freePeers[x]].second++; - connect(m_incomingPeers[m_freePeers[x]].first); - m_freePeers.erase(m_freePeers.begin() + x); - } -} - void EthereumHost::noteHaveChain(std::shared_ptr const& _from) { auto td = _from->m_totalDifficulty; @@ -616,92 +236,9 @@ void EthereumHost::noteHaveChain(std::shared_ptr const& _from) } { - Guard l(x_peers); - for (auto const& i: m_peers) + Guard l(host()->x_peers); + for (auto const& i: host()->m_peers) if (shared_ptr p = i.second.lock()) p->restartGettingChain(); } } - - -void EthereumHost::prunePeers() -{ - Guard l(x_peers); - // We'll keep at most twice as many as is ideal, halfing what counts as "too young to kill" until we get there. - for (uint old = 15000; m_peers.size() > m_idealPeerCount * 2 && old > 100; old /= 2) - while (m_peers.size() > m_idealPeerCount) - { - // look for worst peer to kick off - // first work out how many are old enough to kick off. - shared_ptr worst; - unsigned agedPeers = 0; - for (auto i: m_peers) - if (auto p = i.second.lock()) - if ((m_mode != NodeMode::PeerServer || p->m_caps != 0x01) && chrono::steady_clock::now() > p->m_connect + chrono::milliseconds(old)) // don't throw off new peers; peer-servers should never kick off other peer-servers. - { - ++agedPeers; - if ((!worst || p->m_rating < worst->m_rating || (p->m_rating == worst->m_rating && p->m_connect > worst->m_connect))) // kill older ones - worst = p; - } - if (!worst || agedPeers <= m_idealPeerCount) - break; - worst->disconnect(TooManyPeers); - } - - // Remove dead peers from list. - for (auto i = m_peers.begin(); i != m_peers.end();) - if (i->second.lock().get()) - ++i; - else - i = m_peers.erase(i); -} - -std::vector EthereumHost::peers(bool _updatePing) const -{ - Guard l(x_peers); - if (_updatePing) - const_cast(this)->pingAll(); - this_thread::sleep_for(chrono::milliseconds(200)); - std::vector ret; - for (auto& i: m_peers) - if (auto j = i.second.lock()) - if (j->m_socket.is_open()) - ret.push_back(j->m_info); - return ret; -} - -void EthereumHost::pingAll() -{ - Guard l(x_peers); - for (auto& i: m_peers) - if (auto j = i.second.lock()) - j->ping(); -} - -bytes EthereumHost::savePeers() const -{ - Guard l(x_peers); - RLPStream ret; - int n = 0; - for (auto& i: m_peers) - if (auto p = i.second.lock()) - if (p->m_socket.is_open() && p->endpoint().port()) - { - ret.appendList(3) << p->endpoint().address().to_v4().to_bytes() << p->endpoint().port() << p->m_id; - n++; - } - return RLPStream(n).appendRaw(ret.out(), n).out(); -} - -void EthereumHost::restorePeers(bytesConstRef _b) -{ - for (auto i: RLP(_b)) - { - auto k = (Public)i[2]; - if (!m_incomingPeers.count(k)) - { - m_incomingPeers.insert(make_pair(k, make_pair(bi::tcp::endpoint(bi::address_v4(i[0].toArray()), i[1].toInt()), 0))); - m_freePeers.push_back(k); - } - } -} diff --git a/libethereum/EthereumHost.h b/libethereum/EthereumHost.h index 9a9f83b1b..f92456e8b 100644 --- a/libethereum/EthereumHost.h +++ b/libethereum/EthereumHost.h @@ -30,83 +30,37 @@ #include #include #include +#include #include "CommonNet.h" -namespace ba = boost::asio; -namespace bi = boost::asio::ip; +#include "EthereumSession.h" namespace eth { class RLPStream; class TransactionQueue; -class BlockQueue; /** * @brief The EthereumHost class * @warning None of this is thread-safe. You have been warned. */ -class EthereumHost +class EthereumHost: public HostCapability { - friend class EthereumSession; + friend class EthereumPeer; public: - /// Start server, listening for connections on the given port. - EthereumHost(std::string const& _clientVersion, BlockChain const& _ch, u256 _networkId, unsigned short _port, NodeMode _m = NodeMode::Full, std::string const& _publicAddress = std::string(), bool _upnp = true); - /// Start server, listening for connections on a system-assigned port. - EthereumHost(std::string const& _clientVersion, BlockChain const& _ch, u256 _networkId, NodeMode _m = NodeMode::Full, std::string const& _publicAddress = std::string(), bool _upnp = true); /// Start server, but don't listen. - EthereumHost(std::string const& _clientVersion, BlockChain const& _ch, u256 _networkId, NodeMode _m = NodeMode::Full); + EthereumHost(BlockChain const& _ch, u256 _networkId); /// Will block on network process events. - ~EthereumHost(); + virtual ~EthereumHost(); - /// Closes all peers. - void disconnectPeers(); - - static unsigned protocolVersion(); - u256 networkId() { return m_networkId; } - - /// Connect to a peer explicitly. - void connect(std::string const& _addr, unsigned short _port = 30303) noexcept; - void connect(bi::tcp::endpoint const& _ep); + unsigned protocolVersion() const { return c_protocolVersion; } + u256 networkId() const { return m_networkId; } /// Sync with the BlockChain. It might contain one of our mined blocks, we might have new candidates from the network. bool sync(TransactionQueue&, BlockQueue& _bc); - /// Conduct I/O, polling, syncing, whatever. - /// Ideally all time-consuming I/O is done in a background thread or otherwise asynchronously, but you get this call every 100ms or so anyway. - /// This won't touch alter the blockchain. - void process() { if (isInitialised()) m_ioService.poll(); } - - /// @returns true iff we have the a peer of the given id. - bool havePeer(Public _id) const; - - /// Set ideal number of peers. - void setIdealPeerCount(unsigned _n) { m_idealPeerCount = _n; } - - /// Set the mode of operation on the network. - void setMode(NodeMode _m) { m_mode = _m; } - - /// Get peer information. - std::vector peers(bool _updatePing = false) const; - - /// Get number of peers connected; equivalent to, but faster than, peers().size(). - size_t peerCount() const { Guard l(x_peers); return m_peers.size(); } - - /// Ping the peers, to update the latency information. - void pingAll(); - - /// Get the port we're listening on currently. - unsigned short listenPort() const { return m_public.port(); } - - /// Serialise the set of known peers. - bytes savePeers() const; - - /// Deserialise the data and populate the set of known peers. - void restorePeers(bytesConstRef _b); - - void registerPeer(std::shared_ptr _s); - private: /// Session wants to pass us a block that we might not have. /// @returns true if we didn't have it. @@ -115,16 +69,7 @@ private: void noteHaveChain(std::shared_ptr const& _who); /// Called when the peer can no longer provide us with any needed blocks. void noteDoneBlocks(); - /// Called when the session has provided us with a new peer we can connect to. - void noteNewPeers() {} - - void seal(bytes& _b); - void populateAddresses(); - void determinePublic(std::string const& _publicAddress, bool _upnp); - void ensureAccepting(); - void growPeers(); - void prunePeers(); void maintainTransactions(TransactionQueue& _tq, h256 _currentBlock); void maintainBlocks(BlockQueue& _bq, h256 _currentBlock); @@ -135,35 +80,17 @@ private: /// Check to see if the network peer-state initialisation has happened. virtual bool isInitialised() const { return m_latestBlockSent; } + /// Initialises the network peer-state, doing the stuff that needs to be once-only. @returns true if it really was first. bool ensureInitialised(TransactionQueue& _tq); - std::map potentialPeers(); - - std::string m_clientVersion; - NodeMode m_mode = NodeMode::Full; - - unsigned short m_listenPort; - BlockChain const* m_chain = nullptr; - ba::io_service m_ioService; - bi::tcp::acceptor m_acceptor; - bi::tcp::socket m_socket; - - UPnP* m_upnp = nullptr; - bi::tcp::endpoint m_public; - KeyPair m_key; u256 m_networkId; - mutable std::mutex x_peers; - mutable std::map> m_peers; // mutable because we flush zombie entries (null-weakptrs) as regular maintenance from a const method. - mutable std::recursive_mutex m_incomingLock; std::vector m_incomingTransactions; std::vector m_incomingBlocks; - std::map> m_incomingPeers; - std::vector m_freePeers; mutable std::mutex x_blocksNeeded; u256 m_totalDifficultyOfNeeded; @@ -172,14 +99,6 @@ private: h256 m_latestBlockSent; std::set m_transactionsSent; - - std::chrono::steady_clock::time_point m_lastPeersRequest; - unsigned m_idealPeerCount = 5; - - std::vector m_addresses; - std::vector m_peerAddresses; - - bool m_accepting = false; }; } diff --git a/libethereum/EthereumSession.cpp b/libethereum/EthereumSession.cpp index a04ccf0a3..b90665b4b 100644 --- a/libethereum/EthereumSession.cpp +++ b/libethereum/EthereumSession.cpp @@ -31,29 +31,55 @@ using namespace eth; #define clogS(X) eth::LogOutputStream(false) << "| " << std::setw(2) << m_socket.native_handle() << "] " -EthereumSession::EthereumSession(EthereumHost* _s, bi::tcp::socket _socket, u256 _rNId, bi::address _peerAddress, unsigned short _peerPort): - m_server(_s), - m_socket(std::move(_socket)), - m_reqNetworkId(_rNId), - m_listenPort(_peerPort), - m_rating(0) +EthereumPeer::EthereumPeer(PeerSession* _s, HostCapability* _h): PeerCapability(_s, _h) { - m_disconnect = std::chrono::steady_clock::time_point::max(); - m_connect = std::chrono::steady_clock::now(); - m_info = PeerInfo({"?", _peerAddress.to_string(), m_listenPort, std::chrono::steady_clock::duration(0)}); + sendStatus(); } -EthereumSession::~EthereumSession() +EthereumPeer::~EthereumPeer() { giveUpOnFetch(); +} + +void EthereumPeer::sendStatus() +{ + RLPStream s; + m_session->prep(s); + s.appendList(9) << StatusPacket + << hostCapability()->protocolVersion() + << hostCapability()->networkId() + << hostCapability()->m_chain->details().totalDifficulty + << hostCapability()->m_chain->currentHash() + << hostCapability()->m_chain->genesisHash(); + sealAndSend(s); +} + +void EthereumPeer::startInitialSync() +{ + // Grab trsansactions off them. + { + RLPStream s; + prep(s).appendList(1); + s << GetTransactionsPacket; + sealAndSend(s); + } + + h256 c = m_server->m_chain->currentHash(); + uint n = m_server->m_chain->number(); + u256 td = max(m_server->m_chain->details().totalDifficulty, m_server->m_totalDifficultyOfNeeded); - // Read-chain finished for one reason or another. - try + clogS(NetAllDetail) << "Initial sync. Latest:" << c.abridged() << ", number:" << n << ", TD: max(" << m_server->m_chain->details().totalDifficulty << "," << m_server->m_totalDifficultyOfNeeded << ") versus " << m_totalDifficulty; + if (td > m_totalDifficulty) + return; // All good - we have the better chain. + + // Our chain isn't better - grab theirs. { - if (m_socket.is_open()) - m_socket.close(); + RLPStream s; + prep(s).appendList(3); + s << GetBlockHashesPacket << m_latestHash << c_maxHashesAsk; + m_neededBlocks = h256s(1, m_latestHash); + sealAndSend(s); } - catch (...){} } inline string toString(h256s const& _bs) @@ -66,7 +92,7 @@ inline string toString(h256s const& _bs) return out.str(); } -void EthereumSession::giveUpOnFetch() +void EthereumPeer::giveUpOnFetch() { clogS(NetNote) << "GIVE UP FETCH; can't get " << toString(m_askedBlocks); if (m_askedBlocks.size()) @@ -83,148 +109,37 @@ void EthereumSession::giveUpOnFetch() } } -bi::tcp::endpoint EthereumSession::endpoint() const +bool EthereumPeer::interpret(RLP const& _r) { - if (m_socket.is_open()) - try - { - return bi::tcp::endpoint(m_socket.remote_endpoint().address(), m_listenPort); - } - catch (...){} - - return bi::tcp::endpoint(); -} - -bool EthereumSession::interpret(RLP const& _r) -{ - clogS(NetRight) << _r; switch (_r[0].toInt()) { - case HelloPacket: + case StatusPacket: { m_protocolVersion = _r[1].toInt(); m_networkId = _r[2].toInt(); - auto clientVersion = _r[3].toString(); - m_caps = _r[4].toInt(); - m_listenPort = _r[5].toInt(); - m_id = _r[6].toHash(); - m_totalDifficulty = _r[7].toInt(); - m_latestHash = _r[8].toHash(); - auto genesisHash = _r[9].toHash(); - - clogS(NetMessageSummary) << "Hello: " << clientVersion << "V[" << m_protocolVersion << "/" << m_networkId << "/" << genesisHash.abridged() << "]" << m_id.abridged() << showbase << hex << m_caps << dec << m_listenPort; + m_totalDifficulty = _r[3].toInt(); + m_latestHash = _r[4].toHash(); + auto genesisHash = _r[5].toHash(); - if (m_server->havePeer(m_id)) - { - // Already connected. - cwarn << "Already have peer id" << m_id.abridged();// << "at" << l->endpoint() << "rather than" << endpoint(); - disconnect(DuplicatePeer); - return false; - } + clogS(NetMessageSummary) << "Status: " << m_protocolVersion << "/" << m_networkId << "/" << genesisHash.abridged() << ", TD:" << m_totalDifficulty << "=" << m_latestHash.abridged(); if (genesisHash != m_server->m_chain->genesisHash()) - { - disconnect(WrongGenesis); - return false; - } + disable("Invalid genesis hash"); + if (m_protocolVersion != EthereumHost::protocolVersion()) + disable("Invalid protocol version."); + if (m_networkId != m_server->networkId() || !m_id) + disable("Invalid network identifier."); - if (m_protocolVersion != EthereumHost::protocolVersion() || m_networkId != m_server->networkId() || !m_id) - { - disconnect(IncompatibleProtocol); - return false; - } - try - { m_info = PeerInfo({clientVersion, m_socket.remote_endpoint().address().to_string(), m_listenPort, std::chrono::steady_clock::duration()}); } - catch (...) - { - disconnect(BadProtocol); - return false; - } - - m_server->registerPeer(shared_from_this()); startInitialSync(); - - // Grab trsansactions off them. - { - RLPStream s; - prep(s).appendList(1); - s << GetTransactionsPacket; - sealAndSend(s); - } - break; - } - case DisconnectPacket: - { - string reason = "Unspecified"; - if (_r[1].isInt()) - reason = reasonOf((DisconnectReason)_r[1].toInt()); - - clogS(NetMessageSummary) << "Disconnect (reason: " << reason << ")"; - if (m_socket.is_open()) - clogS(NetNote) << "Closing " << m_socket.remote_endpoint(); - else - clogS(NetNote) << "Remote closed."; - m_socket.close(); - return false; - } - case PingPacket: - { - clogS(NetTriviaSummary) << "Ping"; - RLPStream s; - sealAndSend(prep(s).appendList(1) << PongPacket); break; } - case PongPacket: - m_info.lastPing = std::chrono::steady_clock::now() - m_ping; - clogS(NetTriviaSummary) << "Latency: " << chrono::duration_cast(m_info.lastPing).count() << " ms"; - break; - case GetPeersPacket: + case GetTransactionsPacket: { - clogS(NetTriviaSummary) << "GetPeers"; - auto peers = m_server->potentialPeers(); - RLPStream s; - prep(s).appendList(peers.size() + 1); - s << PeersPacket; - for (auto i: peers) - { - clogS(NetTriviaDetail) << "Sending peer " << i.first.abridged() << i.second; - s.appendList(3) << bytesConstRef(i.second.address().to_v4().to_bytes().data(), 4) << i.second.port() << i.first; - } - sealAndSend(s); + if (m_server->m_mode == NodeMode::PeerServer) + break; + m_requireTransactions = true; break; } - case PeersPacket: - clogS(NetTriviaSummary) << "Peers (" << dec << (_r.itemCount() - 1) << " entries)"; - for (unsigned i = 1; i < _r.itemCount(); ++i) - { - bi::address_v4 peerAddress(_r[i][0].toHash>().asArray()); - auto ep = bi::tcp::endpoint(peerAddress, _r[i][1].toInt()); - Public id = _r[i][2].toHash(); - if (isPrivateAddress(peerAddress)) - goto CONTINUE; - - clogS(NetAllDetail) << "Checking: " << ep << "(" << id.abridged() << ")"; - - // check that it's not us or one we already know: - if (id && (m_server->m_key.pub() == id || m_server->havePeer(id) || m_server->m_incomingPeers.count(id))) - goto CONTINUE; - - // check that we're not already connected to addr: - if (!ep.port()) - goto CONTINUE; - for (auto i: m_server->m_addresses) - if (ep.address() == i && ep.port() == m_server->listenPort()) - goto CONTINUE; - for (auto i: m_server->m_incomingPeers) - if (i.second.first == ep) - goto CONTINUE; - m_server->m_incomingPeers[id] = make_pair(ep, 0); - m_server->m_freePeers.push_back(id); - m_server->noteNewPeers(); - clogS(NetTriviaDetail) << "New peer: " << ep << "(" << id << ")"; - CONTINUE:; - } - break; case TransactionsPacket: if (m_server->m_mode == NodeMode::PeerServer) break; @@ -350,20 +265,13 @@ bool EthereumSession::interpret(RLP const& _r) continueGettingChain(); break; } - case GetTransactionsPacket: - { - if (m_server->m_mode == NodeMode::PeerServer) - break; - m_requireTransactions = true; - break; - } default: - break; + return false; } return true; } -void EthereumSession::restartGettingChain() +void EthereumPeer::restartGettingChain() { if (m_askedBlocks.size()) { @@ -375,7 +283,7 @@ void EthereumSession::restartGettingChain() ensureGettingChain(); } -void EthereumSession::ensureGettingChain() +void EthereumPeer::ensureGettingChain() { if (m_askedBlocks.size()) return; // Already asked & waiting for some. @@ -383,7 +291,7 @@ void EthereumSession::ensureGettingChain() continueGettingChain(); } -void EthereumSession::continueGettingChain() +void EthereumPeer::continueGettingChain() { if (!m_askedBlocks.size()) m_askedBlocks = m_server->neededBlocks(m_failedBlocks); @@ -403,258 +311,3 @@ void EthereumSession::continueGettingChain() m_server->noteDoneBlocks(); } } - -void EthereumSession::ping() -{ - RLPStream s; - sealAndSend(prep(s).appendList(1) << PingPacket); - m_ping = std::chrono::steady_clock::now(); -} - -void EthereumSession::getPeers() -{ - RLPStream s; - sealAndSend(prep(s).appendList(1) << GetPeersPacket); -} - -RLPStream& EthereumSession::prep(RLPStream& _s) -{ - return _s.appendRaw(bytes(8, 0)); -} - -void EthereumSession::sealAndSend(RLPStream& _s) -{ - bytes b; - _s.swapOut(b); - m_server->seal(b); - sendDestroy(b); -} - -bool EthereumSession::checkPacket(bytesConstRef _msg) -{ - if (_msg.size() < 8) - return false; - if (!(_msg[0] == 0x22 && _msg[1] == 0x40 && _msg[2] == 0x08 && _msg[3] == 0x91)) - return false; - uint32_t len = ((_msg[4] * 256 + _msg[5]) * 256 + _msg[6]) * 256 + _msg[7]; - if (_msg.size() != len + 8) - return false; - RLP r(_msg.cropped(8)); - if (r.actualSize() != len) - return false; - return true; -} - -void EthereumSession::sendDestroy(bytes& _msg) -{ - clogS(NetLeft) << RLP(bytesConstRef(&_msg).cropped(8)); - - if (!checkPacket(bytesConstRef(&_msg))) - { - cwarn << "INVALID PACKET CONSTRUCTED!"; - } - - bytes buffer = bytes(std::move(_msg)); - writeImpl(buffer); -} - -void EthereumSession::send(bytesConstRef _msg) -{ - clogS(NetLeft) << RLP(_msg.cropped(8)); - - if (!checkPacket(_msg)) - { - cwarn << "INVALID PACKET CONSTRUCTED!"; - } - - bytes buffer = bytes(_msg.toBytes()); - writeImpl(buffer); -} - -void EthereumSession::writeImpl(bytes& _buffer) -{ -// cerr << (void*)this << " writeImpl" << endl; - if (!m_socket.is_open()) - return; - - lock_guard l(m_writeLock); - m_writeQueue.push_back(_buffer); - if (m_writeQueue.size() == 1) - write(); -} - -void EthereumSession::write() -{ -// cerr << (void*)this << " write" << endl; - lock_guard l(m_writeLock); - if (m_writeQueue.empty()) - return; - - const bytes& bytes = m_writeQueue[0]; - auto self(shared_from_this()); - ba::async_write(m_socket, ba::buffer(bytes), [this, self](boost::system::error_code ec, std::size_t /*length*/) - { -// cerr << (void*)this << " write.callback" << endl; - - // must check queue, as write callback can occur following dropped() - if (ec) - { - cwarn << "Error sending: " << ec.message(); - dropped(); - } - else - { - m_writeQueue.pop_front(); - write(); - } - }); -} - -void EthereumSession::dropped() -{ -// cerr << (void*)this << " dropped" << endl; - if (m_socket.is_open()) - try - { - clogS(NetConnect) << "Closing " << m_socket.remote_endpoint(); - m_socket.close(); - } - catch (...) {} -} - -void EthereumSession::disconnect(int _reason) -{ - clogS(NetConnect) << "Disconnecting (reason:" << reasonOf((DisconnectReason)_reason) << ")"; - if (m_socket.is_open()) - { - if (m_disconnect == chrono::steady_clock::time_point::max()) - { - RLPStream s; - prep(s); - s.appendList(2) << DisconnectPacket << _reason; - sealAndSend(s); - m_disconnect = chrono::steady_clock::now(); - } - else - dropped(); - } -} - -void EthereumSession::start() -{ - RLPStream s; - prep(s); - s.appendList(10) << HelloPacket - << (uint)EthereumHost::protocolVersion() - << m_server->networkId() - << m_server->m_clientVersion - << (m_server->m_mode == NodeMode::Full ? 0x07 : m_server->m_mode == NodeMode::PeerServer ? 0x01 : 0) - << m_server->m_public.port() - << m_server->m_key.pub() - << m_server->m_chain->details().totalDifficulty - << m_server->m_chain->currentHash() - << m_server->m_chain->genesisHash(); - sealAndSend(s); - ping(); - getPeers(); - - doRead(); -} - -void EthereumSession::startInitialSync() -{ - h256 c = m_server->m_chain->currentHash(); - uint n = m_server->m_chain->number(); - u256 td = max(m_server->m_chain->details().totalDifficulty, m_server->m_totalDifficultyOfNeeded); - - clogS(NetAllDetail) << "Initial sync. Latest:" << c.abridged() << ", number:" << n << ", TD: max(" << m_server->m_chain->details().totalDifficulty << "," << m_server->m_totalDifficultyOfNeeded << ") versus " << m_totalDifficulty; - if (td > m_totalDifficulty) - return; // All good - we have the better chain. - - // Our chain isn't better - grab theirs. - RLPStream s; - prep(s).appendList(3); - s << GetBlockHashesPacket << m_latestHash << c_maxHashesAsk; - m_neededBlocks = h256s(1, m_latestHash); - sealAndSend(s); -} - -void EthereumSession::doRead() -{ - // ignore packets received while waiting to disconnect - if (chrono::steady_clock::now() - m_disconnect > chrono::seconds(0)) - return; - - auto self(shared_from_this()); - m_socket.async_read_some(boost::asio::buffer(m_data), [this,self](boost::system::error_code ec, std::size_t length) - { - // If error is end of file, ignore - if (ec && ec.category() != boost::asio::error::get_misc_category() && ec.value() != boost::asio::error::eof) - { - // got here with length of 1241... - cwarn << "Error reading: " << ec.message(); - dropped(); - } - else if (ec && length == 0) - { - return; - } - else - { - try - { - m_incoming.resize(m_incoming.size() + length); - memcpy(m_incoming.data() + m_incoming.size() - length, m_data.data(), length); - while (m_incoming.size() > 8) - { - if (m_incoming[0] != 0x22 || m_incoming[1] != 0x40 || m_incoming[2] != 0x08 || m_incoming[3] != 0x91) - { - cwarn << "INVALID SYNCHRONISATION TOKEN; expected = 22400891; received = " << toHex(bytesConstRef(m_incoming.data(), 4)); - disconnect(BadProtocol); - return; - } - else - { - uint32_t len = fromBigEndian(bytesConstRef(m_incoming.data() + 4, 4)); - uint32_t tlen = len + 8; - if (m_incoming.size() < tlen) - break; - - // enough has come in. - auto data = bytesConstRef(m_incoming.data(), tlen); - if (!checkPacket(data)) - { - cerr << "Received " << len << ": " << toHex(bytesConstRef(m_incoming.data() + 8, len)) << endl; - cwarn << "INVALID MESSAGE RECEIVED"; - disconnect(BadProtocol); - return; - } - else - { - RLP r(data.cropped(8)); - if (!interpret(r)) - { - // error - dropped(); - return; - } - } - memmove(m_incoming.data(), m_incoming.data() + tlen, m_incoming.size() - tlen); - m_incoming.resize(m_incoming.size() - tlen); - } - } - doRead(); - } - catch (Exception const& _e) - { - clogS(NetWarn) << "ERROR: " << _e.description(); - dropped(); - } - catch (std::exception const& _e) - { - clogS(NetWarn) << "ERROR: " << _e.what(); - dropped(); - } - } - }); -} diff --git a/libethereum/EthereumSession.h b/libethereum/EthereumSession.h index 2a7e31155..1b10c71fe 100644 --- a/libethereum/EthereumSession.h +++ b/libethereum/EthereumSession.h @@ -29,34 +29,44 @@ #include #include #include "CommonNet.h" +#include "EthereumHost.h" namespace eth { +class WhisperSession: public PeerSession +{ +public: + WhisperSession(); + virtual ~WhisperSession(); + + static std::string name() { return "shh"; } + +private: + virtual bool interpret(RLP const&) {} +}; + /** * @brief The EthereumSession class * @todo Document fully. */ -class EthereumSession: public std::enable_shared_from_this +class EthereumPeer: public PeerCapability { friend class EthereumHost; public: - EthereumSession(EthereumHost* _server, bi::tcp::socket _socket, u256 _rNId, bi::address _peerAddress, unsigned short _peerPort = 0); - ~EthereumSession(); - - void start(); - void disconnect(int _reason); + EthereumPeer(PeerSession* _s, HostCapability* _h); + virtual ~EthereumPeer(); - void ping(); + static std::string name() { return "eth"; } - bool isOpen() const { return m_socket.is_open(); } - - bi::tcp::endpoint endpoint() const; ///< for other peers to connect to. + EthereumHost* hostCapability() const { return static_cast(PeerCapability::hostCapability()); } private: + virtual bool interpret(RLP const& _r); + + void sendStatus(); void startInitialSync(); - void getPeers(); /// Ensure that we are waiting for a bunch of blocks from our peer. void ensureGettingChain(); @@ -67,36 +77,10 @@ private: void giveUpOnFetch(); - void dropped(); - void doRead(); - void doWrite(std::size_t length); - bool interpret(RLP const& _r); - - /// @returns true iff the _msg forms a valid message for sending or receiving on the network. - static bool checkPacket(bytesConstRef _msg); - - static RLPStream& prep(RLPStream& _s); - void sealAndSend(RLPStream& _s); - void sendDestroy(bytes& _msg); - void send(bytesConstRef _msg); - void writeImpl(bytes& _buffer); - void write(); - EthereumHost* m_server; + EthereumHost* m_host; - std::recursive_mutex m_writeLock; - std::deque m_writeQueue; - - bi::tcp::socket m_socket; - std::array m_data; - PeerInfo m_info; - Public m_id; - - bytes m_incoming; uint m_protocolVersion; u256 m_networkId; - u256 m_reqNetworkId; - unsigned short m_listenPort; ///< Port that the remote client is listening on for connections. Useful for giving to peers. - uint m_caps; h256 m_latestHash; ///< Peer's latest block's hash. u256 m_totalDifficulty; ///< Peer's latest block's total difficulty. @@ -106,17 +90,10 @@ private: h256Set m_askedBlocks; ///< The blocks for which we sent the last GetBlocks for but haven't received a corresponding Blocks. bool m_askedBlocksChanged = true; - std::chrono::steady_clock::time_point m_ping; - std::chrono::steady_clock::time_point m_connect; - std::chrono::steady_clock::time_point m_disconnect; - - uint m_rating; bool m_requireTransactions; std::set m_knownBlocks; std::set m_knownTransactions; - - bool m_willBeDeleted = false; ///< True if we already posted a deleter on the strand. }; } diff --git a/libethnet/Common.cpp b/libethnet/Common.cpp index 5958ee9df..6224f93ca 100644 --- a/libethnet/Common.cpp +++ b/libethnet/Common.cpp @@ -59,3 +59,8 @@ std::string eth::reasonOf(DisconnectReason _r) } } +void PeerCapability::disable(std::string const& _problem) +{ + clogS(NetConnect) << "Disabling capability '" << m_host->name() << "'. Reason:" << _problem; + m_enabled = false; +} diff --git a/libethnet/Common.h b/libethnet/Common.h index 64bc20370..c70e42aaa 100644 --- a/libethnet/Common.h +++ b/libethnet/Common.h @@ -93,12 +93,16 @@ class PeerCapability; class HostCapabilityFace { + friend class PeerHost; + public: HostCapabilityFace(PeerHost*) {} virtual ~HostCapabilityFace() {} +protected: virtual std::string name() const = 0; virtual PeerCapability* newPeerCapability(PeerSession* _s) = 0; + virtual bool isInitialised() const = 0; }; template @@ -113,8 +117,9 @@ public: PeerHost* host() const { return m_host; } protected: + virtual bool isInitialised() const = 0; virtual std::string name() const { return PeerCap::name(); } - virtual PeerCapability* newPeerCapability(PeerSession* _s) { return new PeerCap(_s); } + virtual PeerCapability* newPeerCapability(PeerSession* _s) { return new PeerCap(_s, this); } private: PeerHost* m_host; @@ -125,19 +130,24 @@ class PeerCapability friend class PeerSession; public: - PeerCapability(PeerSession* _s): m_session(_s) {} + PeerCapability(PeerSession* _s, HostCapability* _h): m_session(_s), m_host(_h) {} virtual ~PeerCapability() {} /// Must return the capability name. static std::string name() { return ""; } PeerSession* session() const { return m_session; } + HostCapability* hostCapability() const { return m_host; } protected: virtual bool interpret(RLP const&) = 0; + void disable(std::string const& _problem); + private: PeerSession* m_session; + HostCapability* m_host; + bool m_enabled = true; }; } diff --git a/libethnet/PeerHost.cpp b/libethnet/PeerHost.cpp index d5ca05cc4..b5627719f 100644 --- a/libethnet/PeerHost.cpp +++ b/libethnet/PeerHost.cpp @@ -450,6 +450,16 @@ std::vector PeerHost::peers(bool _updatePing) const return ret; } +void PeerHost::process() +{ + for (auto const& i: m_capabilities) + if (!i->isInitialised()) + return false; + growPeers(); + prunePeers(); + m_ioService.poll(); +} + void PeerHost::pingAll() { Guard l(x_peers); diff --git a/libethnet/PeerHost.h b/libethnet/PeerHost.h index f44d202eb..00c5beb88 100644 --- a/libethnet/PeerHost.h +++ b/libethnet/PeerHost.h @@ -41,6 +41,10 @@ class RLPStream; class TransactionQueue; class BlockQueue; +/** + * @brief The PeerHost class + * Capabilities should be registered prior to startNetwork, since m_capabilities is not thread-safe. + */ class PeerHost { friend class PeerSession; @@ -72,7 +76,7 @@ public: /// Conduct I/O, polling, syncing, whatever. /// Ideally all time-consuming I/O is done in a background thread or otherwise asynchronously, but you get this call every 100ms or so anyway. /// This won't touch alter the blockchain. - void process() { if (isInitialised()) m_ioService.poll(); } + void process(); /// @returns true iff we have the a peer of the given id. bool havePeer(Public _id) const; @@ -107,7 +111,6 @@ protected: /// Called when the session has provided us with a new peer we can connect to. void noteNewPeers() {} - virtual bool isInitialised() const { return true; } void seal(bytes& _b); void populateAddresses(); void determinePublic(std::string const& _publicAddress, bool _upnp); @@ -133,8 +136,7 @@ protected: mutable std::mutex x_peers; mutable std::map> m_peers; // mutable because we flush zombie entries (null-weakptrs) as regular maintenance from a const method. - mutable std::recursive_mutex m_incomingLock; - std::map> m_incomingPeers; + std::map> m_incomingPeers; // TODO: does this need a lock? std::vector m_freePeers; std::chrono::steady_clock::time_point m_lastPeersRequest; diff --git a/libethnet/PeerSession.cpp b/libethnet/PeerSession.cpp index c2b8ba757..dfa5d6195 100644 --- a/libethnet/PeerSession.cpp +++ b/libethnet/PeerSession.cpp @@ -180,7 +180,7 @@ bool PeerSession::interpret(RLP const& _r) break; default: for (auto const& i: m_capabilities) - if (i->interpret(_r)) + if (i->m_enabled && i->interpret(_r)) return true; return false; } diff --git a/libethnet/PeerSession.h b/libethnet/PeerSession.h index f51511e04..5919bf11d 100644 --- a/libethnet/PeerSession.h +++ b/libethnet/PeerSession.h @@ -54,7 +54,6 @@ public: bi::tcp::endpoint endpoint() const; ///< for other peers to connect to. -protected: static RLPStream& prep(RLPStream& _s); void sealAndSend(RLPStream& _s); void sendDestroy(bytes& _msg); From dcc7430dbe7cf8c6c134d8c36e1844e999cc06d2 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 1 Sep 2014 11:10:06 +0200 Subject: [PATCH 140/223] Network work ongoing. --- libethereum/EthereumHost.h | 2 +- libethereum/EthereumSession.cpp | 18 +++--------------- 2 files changed, 4 insertions(+), 16 deletions(-) diff --git a/libethereum/EthereumHost.h b/libethereum/EthereumHost.h index f92456e8b..f172ea508 100644 --- a/libethereum/EthereumHost.h +++ b/libethereum/EthereumHost.h @@ -94,7 +94,7 @@ private: mutable std::mutex x_blocksNeeded; u256 m_totalDifficultyOfNeeded; - h256s m_blocksNeeded; /// From latest to earliest. + h256s m_blocksNeeded; h256Set m_blocksOnWay; h256 m_latestBlockSent; diff --git a/libethereum/EthereumSession.cpp b/libethereum/EthereumSession.cpp index b90665b4b..579aa5354 100644 --- a/libethereum/EthereumSession.cpp +++ b/libethereum/EthereumSession.cpp @@ -123,11 +123,11 @@ bool EthereumPeer::interpret(RLP const& _r) clogS(NetMessageSummary) << "Status: " << m_protocolVersion << "/" << m_networkId << "/" << genesisHash.abridged() << ", TD:" << m_totalDifficulty << "=" << m_latestHash.abridged(); - if (genesisHash != m_server->m_chain->genesisHash()) + if (genesisHash != hostCapability()->m_chain->genesisHash()) disable("Invalid genesis hash"); - if (m_protocolVersion != EthereumHost::protocolVersion()) + if (m_protocolVersion != hostCapability()->protocolVersion()) disable("Invalid protocol version."); - if (m_networkId != m_server->networkId() || !m_id) + if (m_networkId != hostCapability()->networkId() || !m_id) disable("Invalid network identifier."); startInitialSync(); @@ -135,14 +135,10 @@ bool EthereumPeer::interpret(RLP const& _r) } case GetTransactionsPacket: { - if (m_server->m_mode == NodeMode::PeerServer) - break; m_requireTransactions = true; break; } case TransactionsPacket: - if (m_server->m_mode == NodeMode::PeerServer) - break; clogS(NetMessageSummary) << "Transactions (" << dec << (_r.itemCount() - 1) << " entries)"; m_rating += _r.itemCount() - 1; for (unsigned i = 1; i < _r.itemCount(); ++i) @@ -153,8 +149,6 @@ bool EthereumPeer::interpret(RLP const& _r) break; case GetBlockHashesPacket: { - if (m_server->m_mode == NodeMode::PeerServer) - break; h256 later = _r[1].toHash(); unsigned limit = _r[2].toInt(); clogS(NetMessageSummary) << "GetBlockHashes (" << limit << "entries, " << later.abridged() << ")"; @@ -171,8 +165,6 @@ bool EthereumPeer::interpret(RLP const& _r) } case BlockHashesPacket: { - if (m_server->m_mode == NodeMode::PeerServer) - break; clogS(NetMessageSummary) << "BlockHashes (" << dec << (_r.itemCount() - 1) << " entries)"; if (_r.itemCount() == 1) { @@ -199,8 +191,6 @@ bool EthereumPeer::interpret(RLP const& _r) } case GetBlocksPacket: { - if (m_server->m_mode == NodeMode::PeerServer) - break; clogS(NetMessageSummary) << "GetBlocks (" << dec << (_r.itemCount() - 1) << " entries)"; // TODO: return the requested blocks. bytes rlp; @@ -220,8 +210,6 @@ bool EthereumPeer::interpret(RLP const& _r) } case BlocksPacket: { - if (m_server->m_mode == NodeMode::PeerServer) - break; clogS(NetMessageSummary) << "Blocks (" << dec << (_r.itemCount() - 1) << " entries)"; if (_r.itemCount() == 1 && !m_askedBlocksChanged) From 664cc77d4337da4fc6879f8daf4a1258737eebb3 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 1 Sep 2014 11:10:28 +0200 Subject: [PATCH 141/223] Add caktux's JS console helpers. --- libqethereum/QEthereum.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/libqethereum/QEthereum.h b/libqethereum/QEthereum.h index b145da59a..645c91740 100644 --- a/libqethereum/QEthereum.h +++ b/libqethereum/QEthereum.h @@ -225,6 +225,8 @@ private: frame->evaluateJavaScript("String.prototype.dec = function() { env.warn('THIS CALL IS DEPRECATED. USE eth.* INSTEAD.'); return eth.toDecimal(this) }"); \ frame->evaluateJavaScript("String.prototype.fix = function() { env.warn('THIS CALL IS DEPRECATED. USE eth.* INSTEAD.'); return eth.toFixed(this) }"); \ frame->evaluateJavaScript("String.prototype.sha3 = function() { env.warn('THIS CALL IS DEPRECATED. USE eth.* INSTEAD.'); return eth.sha3old(this) }"); \ + frame->evaluateJavaScript("console.log = env.note"); \ + frame->evaluateJavaScript("window.onerror = function (errorMsg, url, lineNumber, column, errorObj) { env.warn('Error: ' + errorMsg + ', Script: ' + url + ', Line: ' + lineNumber + ', Column: ' + column + ', StackTrace: ' + String(errorObj)) }"); \ } template inline boost::multiprecision::number> toInt(QString const& _s) From 9e76b49578ef1088bfc0218d4ea9b8925e681425 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 1 Sep 2014 16:45:58 +0200 Subject: [PATCH 142/223] Slowing bashing code into shape. --- libethereum/Client.cpp | 5 +- libethereum/Client.h | 9 +- libethereum/CommonNet.h | 4 +- libethereum/EthereumHost.cpp | 84 +++++++++---------- libethereum/EthereumHost.h | 5 +- .../{EthereumSession.cpp => EthereumPeer.cpp} | 76 +++++++++-------- .../{EthereumSession.h => EthereumPeer.h} | 18 ++-- libethereumx/Ethereum.cpp | 8 +- libethereumx/Ethereum.h | 4 +- libethnet/Common.cpp | 47 ++++++++++- libethnet/Common.h | 35 +++++--- libethnet/PeerHost.cpp | 13 +-- libethnet/PeerHost.h | 5 +- libethnet/PeerSession.cpp | 18 ++-- libethnet/PeerSession.h | 13 ++- 15 files changed, 215 insertions(+), 129 deletions(-) rename libethereum/{EthereumSession.cpp => EthereumPeer.cpp} (76%) rename libethereum/{EthereumSession.h => EthereumPeer.h} (86%) diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index c62fe42de..c5bdf4c1a 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include "Defaults.h" #include "EthereumHost.h" using namespace std; @@ -213,7 +214,9 @@ void Client::startNetwork(unsigned short _listenPort, std::string const& _seedHo try { - m_net.reset(new EthereumHost(m_clientVersion, m_bc, _networkId, _listenPort, _mode, _publicIP, _upnp)); + m_net.reset(new PeerHost(m_clientVersion, _listenPort, _publicIP, _upnp)); + if (_mode == NodeMode::Full) + m_net->registerCapability(new EthereumHost(m_bc, _networkId)); } catch (std::exception const&) { diff --git a/libethereum/Client.h b/libethereum/Client.h index 9499b0d4c..2848bd85a 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -28,9 +28,10 @@ #include #include #include +#include #include #include -#include +#include #include "BlockChain.h" #include "TransactionQueue.h" #include "State.h" @@ -51,6 +52,12 @@ enum ClientWorkState Deleted }; +enum class NodeMode +{ + Peer, + Full +}; + class VersionChecker { public: diff --git a/libethereum/CommonNet.h b/libethereum/CommonNet.h index 0b848e2c3..1ab4ab2d3 100644 --- a/libethereum/CommonNet.h +++ b/libethereum/CommonNet.h @@ -41,9 +41,9 @@ class OverlayDB; class BlockChain; class TransactionQueue; class EthereumHost; -class EthereumSession; +class EthereumPeer; -enum PacketType +enum { StatusPacket = 0x10, GetTransactionsPacket, diff --git a/libethereum/EthereumHost.cpp b/libethereum/EthereumHost.cpp index d5d8e08cd..836f3f8f0 100644 --- a/libethereum/EthereumHost.cpp +++ b/libethereum/EthereumHost.cpp @@ -28,26 +28,27 @@ #include #include #include +#include #include #include #include "BlockChain.h" #include "TransactionQueue.h" #include "BlockQueue.h" -#include "EthereumSession.h" +#include "EthereumPeer.h" using namespace std; using namespace eth; EthereumHost::EthereumHost(BlockChain const& _ch, u256 _networkId): - m_chain(&_ch), - m_networkId(_networkId) + HostCapability(), + m_chain (&_ch), + m_networkId (_networkId) { } EthereumHost::~EthereumHost() { - for (auto i: host()->m_peers) - if (shared_ptr p = i.second.lock()) - p->giveUpOnFetch(); + for (auto const& i: peers()) + i->cap()->giveUpOnFetch(); } h256Set EthereumHost::neededBlocks(h256Set const& _exclude) @@ -84,7 +85,6 @@ bool EthereumHost::ensureInitialised(TransactionQueue& _tq) for (auto const& i: _tq.transactions()) m_transactionsSent.insert(i.first); - m_lastPeersRequest = chrono::steady_clock::time_point::min(); return true; } return false; @@ -135,31 +135,30 @@ void EthereumHost::maintainTransactions(TransactionQueue& _tq, h256 _currentHash m_incomingTransactions.clear(); // Send any new transactions. - Guard l(host()->x_peers); - for (auto j: host()->m_peers) - if (auto p = j.second.lock()) - { - bytes b; - uint n = 0; - for (auto const& i: _tq.transactions()) - if ((!m_transactionsSent.count(i.first) && !p->m_knownTransactions.count(i.first)) || p->m_requireTransactions || resendAll) - { - b += i.second; - ++n; - m_transactionsSent.insert(i.first); - } - if (n) + for (auto const& p: peers()) + { + auto ep = p->cap(); + bytes b; + uint n = 0; + for (auto const& i: _tq.transactions()) + if ((!m_transactionsSent.count(i.first) && !ep->m_knownTransactions.count(i.first)) || ep->m_requireTransactions || resendAll) { - RLPStream ts; - EthereumSession::prep(ts); - ts.appendList(n + 1) << TransactionsPacket; - ts.appendRaw(b, n).swapOut(b); - seal(b); - p->send(&b); + b += i.second; + ++n; + m_transactionsSent.insert(i.first); } - p->m_knownTransactions.clear(); - p->m_requireTransactions = false; + if (n) + { + RLPStream ts; + EthereumPeer::prep(ts); + ts.appendList(n + 1) << TransactionsPacket; + ts.appendRaw(b, n).swapOut(b); + seal(b); + ep->send(&b); } + ep->m_knownTransactions.clear(); + ep->m_requireTransactions = false; + } } void EthereumHost::maintainBlocks(BlockQueue& _bq, h256 _currentHash) @@ -184,7 +183,7 @@ void EthereumHost::maintainBlocks(BlockQueue& _bq, h256 _currentHash) if (m_latestBlockSent != _currentHash) { RLPStream ts; - EthereumSession::prep(ts); + EthereumPeer::prep(ts); bytes bs; unsigned c = 0; for (auto h: m_chain->treeRoute(m_latestBlockSent, _currentHash, nullptr, false, true)) @@ -198,19 +197,18 @@ void EthereumHost::maintainBlocks(BlockQueue& _bq, h256 _currentHash) ts.swapOut(b); seal(b); - Guard l(host()->x_peers); - for (auto j: host()->m_peers) - if (auto p = j.second.lock()) - { - if (!p->m_knownBlocks.count(_currentHash)) - p->send(&b); - p->m_knownBlocks.clear(); - } + for (auto j: peers()) + { + auto p = j->cap(); + if (!p->m_knownBlocks.count(_currentHash)) + p->send(&b); + p->m_knownBlocks.clear(); + } m_latestBlockSent = _currentHash; } } -void EthereumHost::noteHaveChain(std::shared_ptr const& _from) +void EthereumHost::noteHaveChain(EthereumPeer* _from) { auto td = _from->m_totalDifficulty; @@ -235,10 +233,6 @@ void EthereumHost::noteHaveChain(std::shared_ptr const& _from) m_totalDifficultyOfNeeded = td; } - { - Guard l(host()->x_peers); - for (auto const& i: host()->m_peers) - if (shared_ptr p = i.second.lock()) - p->restartGettingChain(); - } + for (auto j: peers()) + j->cap()->restartGettingChain(); } diff --git a/libethereum/EthereumHost.h b/libethereum/EthereumHost.h index f172ea508..d2c28644b 100644 --- a/libethereum/EthereumHost.h +++ b/libethereum/EthereumHost.h @@ -32,13 +32,14 @@ #include #include #include "CommonNet.h" -#include "EthereumSession.h" +#include "EthereumPeer.h" namespace eth { class RLPStream; class TransactionQueue; +class BlockQueue; /** * @brief The EthereumHost class @@ -66,7 +67,7 @@ private: /// @returns true if we didn't have it. bool noteBlock(h256 _hash, bytesConstRef _data); /// Session has finished getting the chain of hashes. - void noteHaveChain(std::shared_ptr const& _who); + void noteHaveChain(EthereumPeer* _who); /// Called when the peer can no longer provide us with any needed blocks. void noteDoneBlocks(); diff --git a/libethereum/EthereumSession.cpp b/libethereum/EthereumPeer.cpp similarity index 76% rename from libethereum/EthereumSession.cpp rename to libethereum/EthereumPeer.cpp index 579aa5354..72de75e35 100644 --- a/libethereum/EthereumSession.cpp +++ b/libethereum/EthereumPeer.cpp @@ -14,24 +14,25 @@ You should have received a copy of the GNU General Public License along with cpp-ethereum. If not, see . */ -/** @file EthereumSession.cpp +/** @file EthereumPeer.cpp * @author Gav Wood * @date 2014 */ -#include "EthereumSession.h" +#include "EthereumPeer.h" #include #include #include +#include #include "BlockChain.h" #include "EthereumHost.h" using namespace std; using namespace eth; -#define clogS(X) eth::LogOutputStream(false) << "| " << std::setw(2) << m_socket.native_handle() << "] " +#define clogS(X) eth::LogOutputStream(false) << "| " << std::setw(2) << session()->id() << "] " -EthereumPeer::EthereumPeer(PeerSession* _s, HostCapability* _h): PeerCapability(_s, _h) +EthereumPeer::EthereumPeer(PeerSession* _s, HostCapabilityFace* _h): PeerCapability(_s, _h) { sendStatus(); } @@ -41,16 +42,21 @@ EthereumPeer::~EthereumPeer() giveUpOnFetch(); } +EthereumHost* EthereumPeer::host() const +{ + return static_cast(PeerCapability::hostCapability()); +} + void EthereumPeer::sendStatus() { RLPStream s; - m_session->prep(s); + prep(s); s.appendList(9) << StatusPacket - << hostCapability()->protocolVersion() - << hostCapability()->networkId() - << hostCapability()->m_chain->details().totalDifficulty - << hostCapability()->m_chain->currentHash() - << hostCapability()->m_chain->genesisHash(); + << host()->protocolVersion() + << host()->networkId() + << host()->m_chain->details().totalDifficulty + << host()->m_chain->currentHash() + << host()->m_chain->genesisHash(); sealAndSend(s); } @@ -64,11 +70,11 @@ void EthereumPeer::startInitialSync() sealAndSend(s); } - h256 c = m_server->m_chain->currentHash(); - uint n = m_server->m_chain->number(); - u256 td = max(m_server->m_chain->details().totalDifficulty, m_server->m_totalDifficultyOfNeeded); + h256 c = host()->m_chain->currentHash(); + uint n = host()->m_chain->number(); + u256 td = max(host()->m_chain->details().totalDifficulty, host()->m_totalDifficultyOfNeeded); - clogS(NetAllDetail) << "Initial sync. Latest:" << c.abridged() << ", number:" << n << ", TD: max(" << m_server->m_chain->details().totalDifficulty << "," << m_server->m_totalDifficultyOfNeeded << ") versus " << m_totalDifficulty; + clogS(NetAllDetail) << "Initial sync. Latest:" << c.abridged() << ", number:" << n << ", TD: max(" << host()->m_chain->details().totalDifficulty << "," << host()->m_totalDifficultyOfNeeded << ") versus " << m_totalDifficulty; if (td > m_totalDifficulty) return; // All good - we have the better chain. @@ -97,13 +103,13 @@ void EthereumPeer::giveUpOnFetch() clogS(NetNote) << "GIVE UP FETCH; can't get " << toString(m_askedBlocks); if (m_askedBlocks.size()) { - Guard l (m_server->x_blocksNeeded); - m_server->m_blocksNeeded.reserve(m_server->m_blocksNeeded.size() + m_askedBlocks.size()); + Guard l (host()->x_blocksNeeded); + host()->m_blocksNeeded.reserve(host()->m_blocksNeeded.size() + m_askedBlocks.size()); for (auto i: m_askedBlocks) { m_failedBlocks.insert(i); - m_server->m_blocksOnWay.erase(i); - m_server->m_blocksNeeded.push_back(i); + host()->m_blocksOnWay.erase(i); + host()->m_blocksNeeded.push_back(i); } m_askedBlocks.clear(); } @@ -123,11 +129,11 @@ bool EthereumPeer::interpret(RLP const& _r) clogS(NetMessageSummary) << "Status: " << m_protocolVersion << "/" << m_networkId << "/" << genesisHash.abridged() << ", TD:" << m_totalDifficulty << "=" << m_latestHash.abridged(); - if (genesisHash != hostCapability()->m_chain->genesisHash()) + if (genesisHash != host()->m_chain->genesisHash()) disable("Invalid genesis hash"); - if (m_protocolVersion != hostCapability()->protocolVersion()) + if (m_protocolVersion != host()->protocolVersion()) disable("Invalid protocol version."); - if (m_networkId != hostCapability()->networkId() || !m_id) + if (m_networkId != host()->networkId()) disable("Invalid network identifier."); startInitialSync(); @@ -140,10 +146,10 @@ bool EthereumPeer::interpret(RLP const& _r) } case TransactionsPacket: clogS(NetMessageSummary) << "Transactions (" << dec << (_r.itemCount() - 1) << " entries)"; - m_rating += _r.itemCount() - 1; + addRating(_r.itemCount() - 1); for (unsigned i = 1; i < _r.itemCount(); ++i) { - m_server->m_incomingTransactions.push_back(_r[i].data().toBytes()); + host()->m_incomingTransactions.push_back(_r[i].data().toBytes()); m_knownTransactions.insert(sha3(_r[i].data())); } break; @@ -153,12 +159,12 @@ bool EthereumPeer::interpret(RLP const& _r) unsigned limit = _r[2].toInt(); clogS(NetMessageSummary) << "GetBlockHashes (" << limit << "entries, " << later.abridged() << ")"; - unsigned c = min(m_server->m_chain->number(later), limit); + unsigned c = min(host()->m_chain->number(later), limit); RLPStream s; prep(s).appendList(1 + c).append(BlockHashesPacket); - h256 p = m_server->m_chain->details(later).parent; - for (unsigned i = 0; i < c; ++i, p = m_server->m_chain->details(p).parent) + h256 p = host()->m_chain->details(later).parent; + for (unsigned i = 0; i < c; ++i, p = host()->m_chain->details(p).parent) s << p; sealAndSend(s); break; @@ -168,15 +174,15 @@ bool EthereumPeer::interpret(RLP const& _r) clogS(NetMessageSummary) << "BlockHashes (" << dec << (_r.itemCount() - 1) << " entries)"; if (_r.itemCount() == 1) { - m_server->noteHaveChain(shared_from_this()); + host()->noteHaveChain(this); return true; } for (unsigned i = 1; i < _r.itemCount(); ++i) { auto h = _r[i].toHash(); - if (m_server->m_chain->details(h)) + if (host()->m_chain->details(h)) { - m_server->noteHaveChain(shared_from_this()); + host()->noteHaveChain(this); return true; } else @@ -197,7 +203,7 @@ bool EthereumPeer::interpret(RLP const& _r) unsigned n = 0; for (unsigned i = 1; i < _r.itemCount() && i <= c_maxBlocks; ++i) { - auto b = m_server->m_chain->block(_r[i].toHash()); + auto b = host()->m_chain->block(_r[i].toHash()); if (b.size()) { rlp += b; @@ -223,12 +229,12 @@ bool EthereumPeer::interpret(RLP const& _r) for (unsigned i = 1; i < _r.itemCount(); ++i) { auto h = BlockInfo::headerHash(_r[i].data()); - if (m_server->noteBlock(h, _r[i].data())) + if (host()->noteBlock(h, _r[i].data())) used++; m_askedBlocks.erase(h); m_knownBlocks.insert(h); } - m_rating += used; + addRating(used); unsigned knownParents = 0; unsigned unknownParents = 0; if (g_logVerbosity >= NetMessageSummary::verbosity) @@ -237,7 +243,7 @@ bool EthereumPeer::interpret(RLP const& _r) { auto h = BlockInfo::headerHash(_r[i].data()); BlockInfo bi(_r[i].data()); - if (!m_server->m_chain->details(bi.parentHash) && !m_knownBlocks.count(bi.parentHash)) + if (!host()->m_chain->details(bi.parentHash) && !m_knownBlocks.count(bi.parentHash)) { unknownParents++; clogS(NetAllDetail) << "Unknown parent " << bi.parentHash << " of block " << h; @@ -282,7 +288,7 @@ void EthereumPeer::ensureGettingChain() void EthereumPeer::continueGettingChain() { if (!m_askedBlocks.size()) - m_askedBlocks = m_server->neededBlocks(m_failedBlocks); + m_askedBlocks = host()->neededBlocks(m_failedBlocks); if (m_askedBlocks.size()) { @@ -296,6 +302,6 @@ void EthereumPeer::continueGettingChain() else { clogS(NetMessageSummary) << "No blocks left to get. Peer doesn't seem to have " << m_failedBlocks.size() << "of our needed blocks."; - m_server->noteDoneBlocks(); + host()->noteDoneBlocks(); } } diff --git a/libethereum/EthereumSession.h b/libethereum/EthereumPeer.h similarity index 86% rename from libethereum/EthereumSession.h rename to libethereum/EthereumPeer.h index 1b10c71fe..3b182b450 100644 --- a/libethereum/EthereumSession.h +++ b/libethereum/EthereumPeer.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU General Public License along with cpp-ethereum. If not, see . */ -/** @file EthereumSession.h +/** @file EthereumPeer.h * @author Gav Wood * @date 2014 */ @@ -28,13 +28,15 @@ #include #include #include +#include #include "CommonNet.h" -#include "EthereumHost.h" namespace eth { -class WhisperSession: public PeerSession +class HostCapabilityFace; + +class WhisperSession: public PeerCapability { public: WhisperSession(); @@ -43,11 +45,11 @@ public: static std::string name() { return "shh"; } private: - virtual bool interpret(RLP const&) {} + virtual bool interpret(RLP const&) { return false; } }; /** - * @brief The EthereumSession class + * @brief The EthereumPeer class * @todo Document fully. */ class EthereumPeer: public PeerCapability @@ -55,12 +57,12 @@ class EthereumPeer: public PeerCapability friend class EthereumHost; public: - EthereumPeer(PeerSession* _s, HostCapability* _h); + EthereumPeer(PeerSession* _s, HostCapabilityFace* _h); virtual ~EthereumPeer(); static std::string name() { return "eth"; } - EthereumHost* hostCapability() const { return static_cast(PeerCapability::hostCapability()); } + EthereumHost* host() const; private: virtual bool interpret(RLP const& _r); @@ -77,8 +79,6 @@ private: void giveUpOnFetch(); - EthereumHost* m_host; - uint m_protocolVersion; u256 m_networkId; diff --git a/libethereumx/Ethereum.cpp b/libethereumx/Ethereum.cpp index a7725abb5..c4cc29d87 100644 --- a/libethereumx/Ethereum.cpp +++ b/libethereumx/Ethereum.cpp @@ -38,11 +38,11 @@ void Ethereum::ensureReady() { m_client = unique_ptr(new Client("+ethereum+")); if (m_client) - startServer(); + startRPCServer(); } catch (DatabaseAlreadyOpen) { - startClient(); + connectToRPCServer(); } } @@ -55,11 +55,11 @@ bool Ethereum::connectionOpen() const return false; } -void Ethereum::startClient() +void Ethereum::connectToRPCServer() { } -void Ethereum::startServer() +void Ethereum::startRPCServer() { } diff --git a/libethereumx/Ethereum.h b/libethereumx/Ethereum.h index fb272c030..0966fd699 100644 --- a/libethereumx/Ethereum.h +++ b/libethereumx/Ethereum.h @@ -132,9 +132,9 @@ private: /// Check to see if the client/server connection is open. bool connectionOpen() const; /// Start the API client. - void startClient(); + void connectToRPCServer(); /// Start the API server. - void startServer(); + void startRPCServer(); std::unique_ptr m_client; diff --git a/libethnet/Common.cpp b/libethnet/Common.cpp index 6224f93ca..f0f0e4d2d 100644 --- a/libethnet/Common.cpp +++ b/libethnet/Common.cpp @@ -20,6 +20,10 @@ */ #include "Common.h" + +#include +#include "PeerSession.h" +#include "PeerHost.h" using namespace std; using namespace eth; @@ -61,6 +65,47 @@ std::string eth::reasonOf(DisconnectReason _r) void PeerCapability::disable(std::string const& _problem) { - clogS(NetConnect) << "Disabling capability '" << m_host->name() << "'. Reason:" << _problem; + clog(NetConnect) << "Disabling capability '" << m_host->name() << "'. Reason:" << _problem; m_enabled = false; } + +void HostCapabilityFace::seal(bytes& _b) +{ + m_host->seal(_b); +} + +std::vector > HostCapabilityFace::peers() const +{ + Guard l(m_host->x_peers); + std::vector > ret; + for (auto const& i: m_host->m_peers) + if (std::shared_ptr p = i.second.lock()) + if (p->m_capabilities.count(name())) + ret.push_back(p); + return ret; +} + +RLPStream& PeerCapability::prep(RLPStream& _s) +{ + return PeerSession::prep(_s); +} + +void PeerCapability::sealAndSend(RLPStream& _s) +{ + m_session->sealAndSend(_s); +} + +void PeerCapability::sendDestroy(bytes& _msg) +{ + m_session->sendDestroy(_msg); +} + +void PeerCapability::send(bytesConstRef _msg) +{ + m_session->send(_msg); +} + +void PeerCapability::addRating(unsigned _r) +{ + m_session->addRating(_r); +} diff --git a/libethnet/Common.h b/libethnet/Common.h index c70e42aaa..ccc066e88 100644 --- a/libethnet/Common.h +++ b/libethnet/Common.h @@ -38,6 +38,7 @@ namespace eth bool isPrivateAddress(bi::address _addressToCheck); class RLP; +class RLPStream; class PeerHost; class PeerSession; @@ -72,6 +73,7 @@ enum DisconnectReason TooManyPeers, DuplicatePeer, IncompatibleProtocol, + InvalidIdentity, ClientQuit, UserReason = 0x10 }; @@ -94,35 +96,41 @@ class PeerCapability; class HostCapabilityFace { friend class PeerHost; + template friend class HostCapability; + friend class PeerCapability; public: - HostCapabilityFace(PeerHost*) {} + HostCapabilityFace() {} virtual ~HostCapabilityFace() {} + PeerHost* host() const { return m_host; } + + std::vector > peers() const; + protected: virtual std::string name() const = 0; virtual PeerCapability* newPeerCapability(PeerSession* _s) = 0; virtual bool isInitialised() const = 0; + + void seal(bytes& _b); + +private: + PeerHost* m_host = nullptr; }; template class HostCapability: public HostCapabilityFace { public: - HostCapability(PeerHost* _h): m_host(_h) {} + HostCapability() {} virtual ~HostCapability() {} static std::string staticName() { return PeerCap::name(); } - PeerHost* host() const { return m_host; } - protected: virtual bool isInitialised() const = 0; virtual std::string name() const { return PeerCap::name(); } virtual PeerCapability* newPeerCapability(PeerSession* _s) { return new PeerCap(_s, this); } - -private: - PeerHost* m_host; }; class PeerCapability @@ -130,23 +138,30 @@ class PeerCapability friend class PeerSession; public: - PeerCapability(PeerSession* _s, HostCapability* _h): m_session(_s), m_host(_h) {} + PeerCapability(PeerSession* _s, HostCapabilityFace* _h): m_session(_s), m_host(_h) {} virtual ~PeerCapability() {} /// Must return the capability name. static std::string name() { return ""; } PeerSession* session() const { return m_session; } - HostCapability* hostCapability() const { return m_host; } + HostCapabilityFace* hostCapability() const { return m_host; } protected: virtual bool interpret(RLP const&) = 0; void disable(std::string const& _problem); + static RLPStream& prep(RLPStream& _s); + void sealAndSend(RLPStream& _s); + void sendDestroy(bytes& _msg); + void send(bytesConstRef _msg); + + void addRating(unsigned _r); + private: PeerSession* m_session; - HostCapability* m_host; + HostCapabilityFace* m_host; bool m_enabled = true; }; diff --git a/libethnet/PeerHost.cpp b/libethnet/PeerHost.cpp index b5627719f..1206eb0bf 100644 --- a/libethnet/PeerHost.cpp +++ b/libethnet/PeerHost.cpp @@ -62,6 +62,7 @@ PeerHost::PeerHost(std::string const& _clientVersion, unsigned short _port, stri populateAddresses(); determinePublic(_publicAddress, _upnp); ensureAccepting(); + m_lastPeersRequest = chrono::steady_clock::time_point::min(); clog(NetNote) << "Id:" << toHex(m_key.address().ref().cropped(0, 4)); } @@ -78,6 +79,7 @@ PeerHost::PeerHost(std::string const& _clientVersion, string const& _publicAddre populateAddresses(); determinePublic(_publicAddress, _upnp); ensureAccepting(); + m_lastPeersRequest = chrono::steady_clock::time_point::min(); clog(NetNote) << "Id:" << toHex(m_key.address().ref().cropped(0, 4)); } @@ -90,6 +92,7 @@ PeerHost::PeerHost(std::string const& _clientVersion): { // populate addresses. populateAddresses(); + m_lastPeersRequest = chrono::steady_clock::time_point::min(); clog(NetNote) << "Id:" << toHex(m_key.address().ref().cropped(0, 4)); } @@ -103,15 +106,15 @@ unsigned PeerHost::protocolVersion() const return 0; } -void PeerHost::registerPeer(std::shared_ptr _s) +void PeerHost::registerPeer(std::shared_ptr _s, vector const& _caps) { { Guard l(x_peers); m_peers[_s->m_id] = _s; } - for (auto const& i: _s->m_caps) + for (auto const& i: _caps) if (haveCapability(i)) - _s->m_capabilities.push_back(shared_ptr(m_capabilities[i]->newPeerCapability(_s.get()))); + _s->m_capabilities[i] = shared_ptr(m_capabilities[i]->newPeerCapability(_s.get())); } void PeerHost::disconnectPeers() @@ -453,8 +456,8 @@ std::vector PeerHost::peers(bool _updatePing) const void PeerHost::process() { for (auto const& i: m_capabilities) - if (!i->isInitialised()) - return false; + if (!i.second->isInitialised()) + return; growPeers(); prunePeers(); m_ioService.poll(); diff --git a/libethnet/PeerHost.h b/libethnet/PeerHost.h index 00c5beb88..d56b512c7 100644 --- a/libethnet/PeerHost.h +++ b/libethnet/PeerHost.h @@ -48,6 +48,7 @@ class BlockQueue; class PeerHost { friend class PeerSession; + friend class HostCapabilityFace; public: /// Start server, listening for connections on the given port. @@ -67,7 +68,7 @@ public: unsigned protocolVersion() const; /// Register a peer-capability; all new peer connections will have this capability. - template void registerCapability() { m_capabilities[T::name()] = std::shared_ptr(new T(this)); } + template void registerCapability(T* _t) { _t->m_host = this; m_capabilities[T::name()] = std::shared_ptr(_t); } /// Connect to a peer explicitly. void connect(std::string const& _addr, unsigned short _port = 30303) noexcept; @@ -102,7 +103,7 @@ public: /// Deserialise the data and populate the set of known peers. void restorePeers(bytesConstRef _b); - void registerPeer(std::shared_ptr _s); + void registerPeer(std::shared_ptr _s, std::vector const& _caps); bool haveCapability(std::string const& _name) const { return m_capabilities.count(_name); } std::vector caps() const { std::vector ret; for (auto const& i: m_capabilities) ret.push_back(i.first); return ret; } diff --git a/libethnet/PeerSession.cpp b/libethnet/PeerSession.cpp index dfa5d6195..d69100047 100644 --- a/libethnet/PeerSession.cpp +++ b/libethnet/PeerSession.cpp @@ -45,7 +45,7 @@ PeerSession::~PeerSession() { // Read-chain finished for one reason or another. for (auto& i: m_capabilities) - i.reset(); + i.second.reset(); try { @@ -76,11 +76,11 @@ bool PeerSession::interpret(RLP const& _r) { m_protocolVersion = _r[1].toInt(); auto clientVersion = _r[2].toString(); - m_caps = _r[3].toVector(); + auto caps = _r[3].toVector(); m_listenPort = _r[4].toInt(); m_id = _r[5].toHash(); - clogS(NetMessageSummary) << "Hello: " << clientVersion << "V[" << m_protocolVersion << "]" << m_id.abridged() << showbase << hex << m_caps << dec << m_listenPort; + clogS(NetMessageSummary) << "Hello: " << clientVersion << "V[" << m_protocolVersion << "]" << m_id.abridged() << showbase << hex << caps << dec << m_listenPort; if (m_server->havePeer(m_id)) { @@ -89,8 +89,12 @@ bool PeerSession::interpret(RLP const& _r) disconnect(DuplicatePeer); return false; } - - if (m_protocolVersion != m_server->protocolVersion() || !m_id) + if (!m_id) + { + disconnect(InvalidIdentity); + return false; + } + if (m_protocolVersion != m_server->protocolVersion()) { disconnect(IncompatibleProtocol); return false; @@ -103,7 +107,7 @@ bool PeerSession::interpret(RLP const& _r) return false; } - m_server->registerPeer(shared_from_this()); + m_server->registerPeer(shared_from_this(), caps); break; } case DisconnectPacket: @@ -180,7 +184,7 @@ bool PeerSession::interpret(RLP const& _r) break; default: for (auto const& i: m_capabilities) - if (i->m_enabled && i->interpret(_r)) + if (i.second->m_enabled && i.second->interpret(_r)) return true; return false; } diff --git a/libethnet/PeerSession.h b/libethnet/PeerSession.h index 5919bf11d..13ab6bb90 100644 --- a/libethnet/PeerSession.h +++ b/libethnet/PeerSession.h @@ -40,6 +40,7 @@ namespace eth class PeerSession: public std::enable_shared_from_this { friend class PeerHost; + friend class HostCapabilityFace; public: PeerSession(PeerHost* _server, bi::tcp::socket _socket, bi::address _peerAddress, unsigned short _peerPort = 0); @@ -52,13 +53,20 @@ public: bool isOpen() const { return m_socket.is_open(); } + unsigned id() const { return m_socket.native_handle(); } + bi::tcp::endpoint endpoint() const; ///< for other peers to connect to. + template + PeerCap* cap() const { try { return static_cast(m_capabilities.at(PeerCap::name())); } catch (...) { return nullptr; } } + static RLPStream& prep(RLPStream& _s); void sealAndSend(RLPStream& _s); void sendDestroy(bytes& _msg); void send(bytesConstRef _msg); + void addRating(unsigned _r) { m_rating += _r; } + private: void dropped(); void doRead(); @@ -77,7 +85,7 @@ private: std::recursive_mutex m_writeLock; std::deque m_writeQueue; - bi::tcp::socket m_socket; + mutable bi::tcp::socket m_socket; ///< Mutable to ask for native_handle(). std::array m_data; PeerInfo m_info; Public m_id; @@ -85,7 +93,6 @@ private: bytes m_incoming; uint m_protocolVersion; unsigned short m_listenPort; ///< Port that the remote client is listening on for connections. Useful for giving to peers. - std::vector m_caps; std::chrono::steady_clock::time_point m_ping; std::chrono::steady_clock::time_point m_connect; @@ -93,7 +100,7 @@ private: uint m_rating; - std::vector> m_capabilities; + std::map> m_capabilities; bool m_willBeDeleted = false; ///< True if we already posted a deleter on the strand. }; From aecb8c8c1a37f1f24037a2b053f2a3a84f342249 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Mon, 1 Sep 2014 18:54:06 +0200 Subject: [PATCH 143/223] New net is compiling. --- libethereum/All.h | 2 +- libethereum/CMakeLists.txt | 1 + libethereum/Client.cpp | 8 ++++---- libethereum/Client.h | 4 ++-- libethereum/CommonNet.h | 2 +- libethereumx/Ethereum.h | 1 + libethnet/PeerHost.h | 9 +++++---- libethnet/PeerSession.h | 2 +- test/peer.cpp | 11 ++++------- 9 files changed, 20 insertions(+), 20 deletions(-) diff --git a/libethereum/All.h b/libethereum/All.h index 17151f1b0..ba22640e7 100644 --- a/libethereum/All.h +++ b/libethereum/All.h @@ -8,7 +8,7 @@ #include "ExtVM.h" #include "CommonNet.h" #include "EthereumHost.h" -#include "EthereumSession.h" +#include "EthereumPeer.h" #include "State.h" #include "Transaction.h" #include "TransactionQueue.h" diff --git a/libethereum/CMakeLists.txt b/libethereum/CMakeLists.txt index 22637d301..68597f249 100644 --- a/libethereum/CMakeLists.txt +++ b/libethereum/CMakeLists.txt @@ -20,6 +20,7 @@ include_directories(..) target_link_libraries(${EXECUTABLE} evm) target_link_libraries(${EXECUTABLE} lll) target_link_libraries(${EXECUTABLE} ethcore) +target_link_libraries(${EXECUTABLE} ethnet) target_link_libraries(${EXECUTABLE} secp256k1) if(MINIUPNPC_LS) target_link_libraries(${EXECUTABLE} ${MINIUPNPC_LS}) diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index c5bdf4c1a..b29c808ba 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -215,15 +215,15 @@ void Client::startNetwork(unsigned short _listenPort, std::string const& _seedHo try { m_net.reset(new PeerHost(m_clientVersion, _listenPort, _publicIP, _upnp)); - if (_mode == NodeMode::Full) - m_net->registerCapability(new EthereumHost(m_bc, _networkId)); } catch (std::exception const&) { // Probably already have the port open. cwarn << "Could not initialize with specified/default port. Trying system-assigned port"; - m_net.reset(new EthereumHost(m_clientVersion, m_bc, 0, _mode, _publicIP, _upnp)); + m_net.reset(new PeerHost(m_clientVersion, 0, _publicIP, _upnp)); } + if (_mode == NodeMode::Full) + m_net->registerCapability(new EthereumHost(m_bc, _networkId)); } m_net->setIdealPeerCount(_peers); } @@ -442,7 +442,7 @@ void Client::workNet() // returns h256Set as block hashes, once for each block that has come in/gone out. cwork << "NET <==> TQ ; CHAIN ==> NET ==> BQ"; - m_net->sync(m_tq, m_bq); + m_net->cap()->sync(m_tq, m_bq); cwork << "TQ:" << m_tq.items() << "; BQ:" << m_bq.items(); } diff --git a/libethereum/Client.h b/libethereum/Client.h index 2848bd85a..1ff553082 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -54,7 +54,7 @@ enum ClientWorkState enum class NodeMode { - Peer, + PeerServer, Full }; @@ -305,7 +305,7 @@ private: std::unique_ptr m_workNet; ///< The network thread. std::atomic m_workNetState; mutable boost::shared_mutex x_net; ///< Lock for the network existance. - std::unique_ptr m_net; ///< Should run in background and send us events when blocks found and allow us to send blocks as required. + std::unique_ptr m_net; ///< Should run in background and send us events when blocks found and allow us to send blocks as required. std::unique_ptr m_work; ///< The work thread. std::atomic m_workState; diff --git a/libethereum/CommonNet.h b/libethereum/CommonNet.h index 1ab4ab2d3..bb459ce99 100644 --- a/libethereum/CommonNet.h +++ b/libethereum/CommonNet.h @@ -43,7 +43,7 @@ class TransactionQueue; class EthereumHost; class EthereumPeer; -enum +enum EthereumPacket { StatusPacket = 0x10, GetTransactionsPacket, diff --git a/libethereumx/Ethereum.h b/libethereumx/Ethereum.h index 0966fd699..6ede791cf 100644 --- a/libethereumx/Ethereum.h +++ b/libethereumx/Ethereum.h @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include diff --git a/libethnet/PeerHost.h b/libethnet/PeerHost.h index d56b512c7..a67e03f48 100644 --- a/libethnet/PeerHost.h +++ b/libethnet/PeerHost.h @@ -68,7 +68,11 @@ public: unsigned protocolVersion() const; /// Register a peer-capability; all new peer connections will have this capability. - template void registerCapability(T* _t) { _t->m_host = this; m_capabilities[T::name()] = std::shared_ptr(_t); } + template void registerCapability(T* _t) { _t->m_host = this; m_capabilities[T::staticName()] = std::shared_ptr(_t); } + + bool haveCapability(std::string const& _name) const { return m_capabilities.count(_name); } + std::vector caps() const { std::vector ret; for (auto const& i: m_capabilities) ret.push_back(i.first); return ret; } + template std::shared_ptr cap() const { try { return std::static_pointer_cast(m_capabilities.at(T::staticName())); } catch (...) { return nullptr; } } /// Connect to a peer explicitly. void connect(std::string const& _addr, unsigned short _port = 30303) noexcept; @@ -105,9 +109,6 @@ public: void registerPeer(std::shared_ptr _s, std::vector const& _caps); - bool haveCapability(std::string const& _name) const { return m_capabilities.count(_name); } - std::vector caps() const { std::vector ret; for (auto const& i: m_capabilities) ret.push_back(i.first); return ret; } - protected: /// Called when the session has provided us with a new peer we can connect to. void noteNewPeers() {} diff --git a/libethnet/PeerSession.h b/libethnet/PeerSession.h index 13ab6bb90..4983bce4b 100644 --- a/libethnet/PeerSession.h +++ b/libethnet/PeerSession.h @@ -58,7 +58,7 @@ public: bi::tcp::endpoint endpoint() const; ///< for other peers to connect to. template - PeerCap* cap() const { try { return static_cast(m_capabilities.at(PeerCap::name())); } catch (...) { return nullptr; } } + std::shared_ptr cap() const { try { return std::static_pointer_cast(m_capabilities.at(PeerCap::name())); } catch (...) { return nullptr; } } static RLPStream& prep(RLPStream& _s); void sealAndSend(RLPStream& _s); diff --git a/test/peer.cpp b/test/peer.cpp index 63f2b0861..4825eea30 100644 --- a/test/peer.cpp +++ b/test/peer.cpp @@ -22,9 +22,7 @@ #include #include -#include -#include -#include +#include using namespace std; using namespace eth; using boost::asio::ip::tcp; @@ -48,18 +46,17 @@ int peerTest(int argc, char** argv) remoteHost = argv[i]; } - BlockChain ch(boost::filesystem::temp_directory_path().string()); - EthereumHost pn("Test", ch, 0, listenPort); + PeerHost ph("Test", listenPort); if (!remoteHost.empty()) - pn.connect(remoteHost, remotePort); + ph.connect(remoteHost, remotePort); for (int i = 0; ; ++i) { this_thread::sleep_for(chrono::milliseconds(100)); // pn.sync(); if (!(i % 10)) - pn.pingAll(); + ph.pingAll(); } return 0; From 70a2a7b1e1ee5fabf450418a1c4c9e77839b64f7 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 2 Sep 2014 12:21:41 +0200 Subject: [PATCH 144/223] Allow local networking option. --- exp/CMakeLists.txt | 1 + exp/main.cpp | 37 ++++++++++++++++++++++++++++++++++--- libethereum/Manifest.h | 17 +++++++++++++++++ libethnet/All.h | 6 ++++++ libethnet/PeerHost.cpp | 18 ++++++++++-------- libethnet/PeerHost.h | 5 +++-- libethnet/PeerSession.cpp | 4 ++-- test/peer.cpp | 1 - 8 files changed, 73 insertions(+), 16 deletions(-) create mode 100644 libethnet/All.h diff --git a/exp/CMakeLists.txt b/exp/CMakeLists.txt index 5b76b4c7a..036169fee 100644 --- a/exp/CMakeLists.txt +++ b/exp/CMakeLists.txt @@ -9,6 +9,7 @@ set(EXECUTABLE exp) add_executable(${EXECUTABLE} ${SRC_LIST}) target_link_libraries(${EXECUTABLE} ethereum) +target_link_libraries(${EXECUTABLE} ethnet) target_link_libraries(${EXECUTABLE} gmp) target_link_libraries(${EXECUTABLE} ${CRYPTOPP_LS}) if(MINIUPNPC_LS) diff --git a/exp/main.cpp b/exp/main.cpp index d79fbb973..910b62ae4 100644 --- a/exp/main.cpp +++ b/exp/main.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #if 0 #include #include "BuildInfo.h" @@ -287,7 +288,7 @@ void parseTree(string const& _s, sp::utree& o_out) throw std::exception(); } #endif -int main(int, char**) +int main(int argc, char** argv) { #if 0 sp::utree out; @@ -297,7 +298,37 @@ int main(int, char**) cout << endl; #endif - cnote << RLP(fromHex("f837c0c0b4600160003556601359506301000000600035040f6018590060005660805460016080530160005760003560805760203560003557")); - cnote << toHex(RLPStream(1).append(bytes(54, 0)).out()); + g_logVerbosity = 20; + + short listenPort = 30303; + string remoteHost; + short remotePort = 30303; + + for (int i = 1; i < argc; ++i) + { + string arg = argv[i]; + if (arg == "-l" && i + 1 < argc) + listenPort = (short)atoi(argv[++i]); + else if (arg == "-r" && i + 1 < argc) + remoteHost = argv[++i]; + else if (arg == "-p" && i + 1 < argc) + remotePort = (short)atoi(argv[++i]); + else + remoteHost = argv[i]; + } + + PeerHost ph("Test", listenPort, "", false, true); + + if (!remoteHost.empty()) + ph.connect(remoteHost, remotePort); + + for (int i = 0; ; ++i) + { + this_thread::sleep_for(chrono::milliseconds(100)); + if (!(i % 100)) + ph.pingAll(); + ph.process(); + } + return 0; } diff --git a/libethereum/Manifest.h b/libethereum/Manifest.h index 94ecd1496..0a873afc6 100644 --- a/libethereum/Manifest.h +++ b/libethereum/Manifest.h @@ -21,6 +21,8 @@ #pragma once +#include +#include #include #include @@ -41,6 +43,21 @@ struct Manifest h256 bloom() const { h256 ret = from.bloom() | to.bloom(); for (auto const& i: internal) ret |= i.bloom(); for (auto const& i: altered) ret |= h256(i).bloom(); return ret; } + std::string toString(unsigned _indent = 0) const + { + std::ostringstream oss; + oss << std::string(_indent * 3, ' ') << from << " -> " << to << " [" << value << "]: {"; + if (internal.size()) + { + oss << std::endl; + for (auto const& m: internal) + oss << m.toString(_indent + 1) << std::endl; + oss << std::string(_indent * 3, ' '); + } + oss << "} I:" << toHex(input) << "; O:" << toHex(output); + return oss.str(); + } + Address from; Address to; u256 value; diff --git a/libethnet/All.h b/libethnet/All.h new file mode 100644 index 000000000..55a5423b7 --- /dev/null +++ b/libethnet/All.h @@ -0,0 +1,6 @@ +#pragma once + +#include "Common.h" +#include "PeerHost.h" +#include "PeerSession.h" + diff --git a/libethnet/PeerHost.cpp b/libethnet/PeerHost.cpp index 1206eb0bf..4ff34b5d4 100644 --- a/libethnet/PeerHost.cpp +++ b/libethnet/PeerHost.cpp @@ -52,9 +52,10 @@ static const set c_rejectAddresses = { {bi::address_v6::from_string("::")} }; -PeerHost::PeerHost(std::string const& _clientVersion, unsigned short _port, string const& _publicAddress, bool _upnp): +PeerHost::PeerHost(std::string const& _clientVersion, unsigned short _port, string const& _publicAddress, bool _upnp, bool _localNetworking): m_clientVersion(_clientVersion), m_listenPort(_port), + m_localNetworking(_localNetworking), m_acceptor(m_ioService, bi::tcp::endpoint(bi::tcp::v4(), _port)), m_socket(m_ioService), m_key(KeyPair::create()) @@ -63,12 +64,13 @@ PeerHost::PeerHost(std::string const& _clientVersion, unsigned short _port, stri determinePublic(_publicAddress, _upnp); ensureAccepting(); m_lastPeersRequest = chrono::steady_clock::time_point::min(); - clog(NetNote) << "Id:" << toHex(m_key.address().ref().cropped(0, 4)); + clog(NetNote) << "Id:" << m_key.address().abridged(); } -PeerHost::PeerHost(std::string const& _clientVersion, string const& _publicAddress, bool _upnp): +PeerHost::PeerHost(std::string const& _clientVersion, string const& _publicAddress, bool _upnp, bool _localNetworking): m_clientVersion(_clientVersion), m_listenPort(0), + m_localNetworking(_localNetworking), m_acceptor(m_ioService, bi::tcp::endpoint(bi::tcp::v4(), 0)), m_socket(m_ioService), m_key(KeyPair::create()) @@ -80,7 +82,7 @@ PeerHost::PeerHost(std::string const& _clientVersion, string const& _publicAddre determinePublic(_publicAddress, _upnp); ensureAccepting(); m_lastPeersRequest = chrono::steady_clock::time_point::min(); - clog(NetNote) << "Id:" << toHex(m_key.address().ref().cropped(0, 4)); + clog(NetNote) << "Id:" << m_key.address().abridged(); } PeerHost::PeerHost(std::string const& _clientVersion): @@ -93,7 +95,7 @@ PeerHost::PeerHost(std::string const& _clientVersion): // populate addresses. populateAddresses(); m_lastPeersRequest = chrono::steady_clock::time_point::min(); - clog(NetNote) << "Id:" << toHex(m_key.address().ref().cropped(0, 4)); + clog(NetNote) << "Id:" << m_key.address().abridged(); } PeerHost::~PeerHost() @@ -164,14 +166,14 @@ void PeerHost::determinePublic(string const& _publicAddress, bool _upnp) bi::tcp::resolver r(m_ioService); if (m_upnp && m_upnp->isValid() && m_peerAddresses.size()) { - clog(NetNote) << "External addr: " << m_upnp->externalIP(); + clog(NetNote) << "External addr:" << m_upnp->externalIP(); int p = m_upnp->addRedirect(m_peerAddresses[0].to_string().c_str(), m_listenPort); if (p) clog(NetNote) << "Punched through NAT and mapped local port" << m_listenPort << "onto external port" << p << "."; else { // couldn't map - clog(NetWarn) << "Couldn't punch through NAT (or no NAT in place). Assuming " << m_listenPort << " is local & external port."; + clog(NetWarn) << "Couldn't punch through NAT (or no NAT in place). Assuming" << m_listenPort << "is local & external port."; p = m_listenPort; } @@ -280,7 +282,7 @@ std::map PeerHost::potentialPeers() { auto ep = j->endpoint(); // Skip peers with a listen port of zero or are on a private network - bool peerOnNet = (j->m_listenPort != 0 && !isPrivateAddress(ep.address())); + bool peerOnNet = (j->m_listenPort != 0 && (!isPrivateAddress(ep.address()) || m_localNetworking)); if (peerOnNet && ep.port() && j->m_id) ret.insert(make_pair(i.first, ep)); } diff --git a/libethnet/PeerHost.h b/libethnet/PeerHost.h index a67e03f48..15e629ebe 100644 --- a/libethnet/PeerHost.h +++ b/libethnet/PeerHost.h @@ -52,9 +52,9 @@ class PeerHost public: /// Start server, listening for connections on the given port. - PeerHost(std::string const& _clientVersion, unsigned short _port, std::string const& _publicAddress = std::string(), bool _upnp = true); + PeerHost(std::string const& _clientVersion, unsigned short _port, std::string const& _publicAddress = std::string(), bool _upnp = true, bool _localNetworking = false); /// Start server, listening for connections on a system-assigned port. - PeerHost(std::string const& _clientVersion, std::string const& _publicAddress = std::string(), bool _upnp = true); + PeerHost(std::string const& _clientVersion, std::string const& _publicAddress = std::string(), bool _upnp = true, bool _localNetworking = false); /// Start server, but don't listen. PeerHost(std::string const& _clientVersion); @@ -126,6 +126,7 @@ protected: std::string m_clientVersion; unsigned short m_listenPort; + bool m_localNetworking = false; ba::io_service m_ioService; bi::tcp::acceptor m_acceptor; diff --git a/libethnet/PeerSession.cpp b/libethnet/PeerSession.cpp index d69100047..7ddc61eec 100644 --- a/libethnet/PeerSession.cpp +++ b/libethnet/PeerSession.cpp @@ -157,7 +157,7 @@ bool PeerSession::interpret(RLP const& _r) bi::address_v4 peerAddress(_r[i][0].toHash>().asArray()); auto ep = bi::tcp::endpoint(peerAddress, _r[i][1].toInt()); Public id = _r[i][2].toHash(); - if (isPrivateAddress(peerAddress)) + if (isPrivateAddress(peerAddress) && !m_server->m_localNetworking) goto CONTINUE; clogS(NetAllDetail) << "Checking: " << ep << "(" << id.abridged() << ")"; @@ -331,7 +331,7 @@ void PeerSession::start() { RLPStream s; prep(s); - s.appendList(9) << HelloPacket + s.appendList(6) << HelloPacket << m_server->protocolVersion() << m_server->m_clientVersion << m_server->caps() diff --git a/test/peer.cpp b/test/peer.cpp index 4825eea30..16f8383e8 100644 --- a/test/peer.cpp +++ b/test/peer.cpp @@ -54,7 +54,6 @@ int peerTest(int argc, char** argv) for (int i = 0; ; ++i) { this_thread::sleep_for(chrono::milliseconds(100)); -// pn.sync(); if (!(i % 10)) ph.pingAll(); } From 298ed72e41f0f40d8daab570dcb4edf00d8948df Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 2 Sep 2014 12:24:31 +0200 Subject: [PATCH 145/223] Minor fix. --- libethereum/EthereumPeer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libethereum/EthereumPeer.cpp b/libethereum/EthereumPeer.cpp index 72de75e35..a39bebab8 100644 --- a/libethereum/EthereumPeer.cpp +++ b/libethereum/EthereumPeer.cpp @@ -51,7 +51,7 @@ void EthereumPeer::sendStatus() { RLPStream s; prep(s); - s.appendList(9) << StatusPacket + s.appendList(6) << StatusPacket << host()->protocolVersion() << host()->networkId() << host()->m_chain->details().totalDifficulty From 05b28f0384c47d6bcbc388e77c9b5e5cf5fa3737 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Tue, 2 Sep 2014 13:07:03 +0200 Subject: [PATCH 146/223] Fixed #299. --- libethential/CommonIO.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libethential/CommonIO.cpp b/libethential/CommonIO.cpp index 82d79ccd7..c2e2306d0 100644 --- a/libethential/CommonIO.cpp +++ b/libethential/CommonIO.cpp @@ -36,7 +36,7 @@ string eth::memDump(bytes const& _b, unsigned _w, bool _html) ret << hex << setw(4) << setfill('0') << i << " "; for (unsigned j = i; j < i + _w; ++j) if (j < _b.size()) - if (_b[j] >= 32 && _b[j] < 128) + if (_b[j] >= 32 && _b[j] < 127) if ((char)_b[j] == '<' && _html) ret << "<"; else if ((char)_b[j] == '&' && _html) From a7ce9a80f1c1badb4d9f50026669a070bd6eba7c Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 3 Sep 2014 01:20:48 +0200 Subject: [PATCH 147/223] First vaguely semi-functional version of whisper. --- exp/main.cpp | 14 +- libethcore/CMakeLists.txt | 3 - libethcore/UPnP.cpp | 183 --------------------- libethcore/UPnP.h | 53 ------ libethereum/CMakeLists.txt | 1 + libethereum/Client.cpp | 22 +-- libethereum/CommonNet.h | 1 - libethereum/EthereumHost.cpp | 1 - libethereum/EthereumPeer.cpp | 24 +-- libethereum/EthereumPeer.h | 12 -- libethnet/Common.h | 3 +- libethnet/PeerHost.cpp | 2 +- libethnet/PeerHost.h | 2 + libethnet/PeerSession.h | 3 +- libwhisper/CMakeLists.txt | 1 + libwhisper/{Whisper.cpp => Common.cpp} | 13 +- libwhisper/{Whisper.h => Common.h} | 50 +++--- libwhisper/WhisperPeer.cpp | 216 +++++++++++++++++++++++++ libwhisper/WhisperPeer.h | 200 +++++++++++++++++++++++ 19 files changed, 489 insertions(+), 315 deletions(-) delete mode 100644 libethcore/UPnP.cpp delete mode 100644 libethcore/UPnP.h rename libwhisper/{Whisper.cpp => Common.cpp} (86%) rename libwhisper/{Whisper.h => Common.h} (55%) create mode 100644 libwhisper/WhisperPeer.cpp create mode 100644 libwhisper/WhisperPeer.h diff --git a/exp/main.cpp b/exp/main.cpp index 910b62ae4..db41def59 100644 --- a/exp/main.cpp +++ b/exp/main.cpp @@ -31,12 +31,14 @@ #include #include #include +#include #if 0 #include #include "BuildInfo.h" #endif using namespace std; using namespace eth; +using namespace shh; #if 0 #if 0 namespace qi = boost::spirit::qi; @@ -318,16 +320,22 @@ int main(int argc, char** argv) } PeerHost ph("Test", listenPort, "", false, true); + ph.registerCapability(new WhisperHost()); + auto wh = ph.cap(); if (!remoteHost.empty()) ph.connect(remoteHost, remotePort); + /// Only interested in the packet if the lowest bit is 1 + auto w = wh->installWatch(MessageFilter(std::vector >({{fromHex("0000000000000000000000000000000000000000000000000000000000000001"), fromHex("0000000000000000000000000000000000000000000000000000000000000001")}}))); + for (int i = 0; ; ++i) { - this_thread::sleep_for(chrono::milliseconds(100)); - if (!(i % 100)) - ph.pingAll(); + this_thread::sleep_for(chrono::milliseconds(1000)); ph.process(); + wh->sendRaw(h256(u256(i * i)).asBytes(), h256(u256(i)).asBytes(), 1000); + for (auto i: wh->checkWatch(w)) + cnote << "New message:" << (u256)h256(wh->message(i).payload); } return 0; diff --git a/libethcore/CMakeLists.txt b/libethcore/CMakeLists.txt index e10f6a481..0f9c2023a 100644 --- a/libethcore/CMakeLists.txt +++ b/libethcore/CMakeLists.txt @@ -18,9 +18,6 @@ include_directories(..) target_link_libraries(${EXECUTABLE} ethential) target_link_libraries(${EXECUTABLE} secp256k1) target_link_libraries(${EXECUTABLE} gmp) -if(MINIUPNPC_LS) -target_link_libraries(${EXECUTABLE} ${MINIUPNPC_LS}) -endif() target_link_libraries(${EXECUTABLE} ${LEVELDB_LS}) target_link_libraries(${EXECUTABLE} ${CRYPTOPP_LS}) diff --git a/libethcore/UPnP.cpp b/libethcore/UPnP.cpp deleted file mode 100644 index 427450e03..000000000 --- a/libethcore/UPnP.cpp +++ /dev/null @@ -1,183 +0,0 @@ -/* - This file is part of cpp-ethereum. - - cpp-ethereum is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - cpp-ethereum is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with cpp-ethereum. If not, see . -*/ -/** @file UPnP.cpp - * @authors: - * Gav Wood - * @date 2014 - */ - -#include "UPnP.h" - -#include -#include -#if ETH_MINIUPNPC -#include -#include -#include -#endif -#include -#include -#include -using namespace std; -using namespace eth; - -UPnP::UPnP() -{ -#if ETH_MINIUPNPC - m_urls.reset(new UPNPUrls); - m_data.reset(new IGDdatas); - - m_ok = false; - - struct UPNPDev* devlist; - struct UPNPDev* dev; - char* descXML; - int descXMLsize = 0; - int upnperror = 0; - memset(m_urls.get(), 0, sizeof(struct UPNPUrls)); - memset(m_data.get(), 0, sizeof(struct IGDdatas)); - devlist = upnpDiscover(2000, NULL/*multicast interface*/, NULL/*minissdpd socket path*/, 0/*sameport*/, 0/*ipv6*/, &upnperror); - if (devlist) - { - dev = devlist; - while (dev) - { - if (strstr (dev->st, "InternetGatewayDevice")) - break; - dev = dev->pNext; - } - if (!dev) - dev = devlist; /* defaulting to first device */ - - cnote << "UPnP device:" << dev->descURL << "[st:" << dev->st << "]"; -#if MINIUPNPC_API_VERSION >= 9 - descXML = (char*)miniwget(dev->descURL, &descXMLsize, 0); -#else - descXML = (char*)miniwget(dev->descURL, &descXMLsize); -#endif - if (descXML) - { - parserootdesc (descXML, descXMLsize, m_data.get()); - free (descXML); descXML = 0; -#if MINIUPNPC_API_VERSION >= 9 - GetUPNPUrls (m_urls.get(), m_data.get(), dev->descURL, 0); -#else - GetUPNPUrls (m_urls.get(), m_data.get(), dev->descURL); -#endif - m_ok = true; - } - freeUPNPDevlist(devlist); - } - else -#endif - { - cnote << "UPnP device not found."; - throw NoUPnPDevice(); - } -} - -UPnP::~UPnP() -{ - auto r = m_reg; - for (auto i: r) - removeRedirect(i); -} - -string UPnP::externalIP() -{ -#if ETH_MINIUPNPC - char addr[16]; - if (!UPNP_GetExternalIPAddress(m_urls->controlURL, m_data->first.servicetype, addr)) - return addr; - else -#endif - return "0.0.0.0"; -} - -int UPnP::addRedirect(char const* _addr, int _port) -{ - (void)_addr; - (void)_port; -#if ETH_MINIUPNPC - if (m_urls->controlURL[0] == '\0') - { - cwarn << "UPnP::addRedirect() called without proper initialisation?"; - return -1; - } - - // Try direct mapping first (port external, port internal). - char port_str[16]; - sprintf(port_str, "%d", _port); - if (!UPNP_AddPortMapping(m_urls->controlURL, m_data->first.servicetype, port_str, port_str, _addr, "ethereum", "TCP", NULL, NULL)) - return _port; - - // Failed - now try (random external, port internal) and cycle up to 10 times. - for (uint i = 0; i < 10; ++i) - { - _port = rand() % 65535 - 1024 + 1024; - sprintf(port_str, "%d", _port); - if (!UPNP_AddPortMapping(m_urls->controlURL, m_data->first.servicetype, NULL, port_str, _addr, "ethereum", "TCP", NULL, NULL)) - return _port; - } - - // Failed. Try asking the router to give us a free external port. - if (UPNP_AddPortMapping(m_urls->controlURL, m_data->first.servicetype, port_str, NULL, _addr, "ethereum", "TCP", NULL, NULL)) - // Failed. Exit. - return 0; - - // We got mapped, but we don't know which ports we got mapped to. Now to find... - unsigned num = 0; - UPNP_GetPortMappingNumberOfEntries(m_urls->controlURL, m_data->first.servicetype, &num); - for (unsigned i = 0; i < num; ++i) - { - char extPort[16]; - char intClient[16]; - char intPort[6]; - char protocol[4]; - char desc[80]; - char enabled[4]; - char rHost[64]; - char duration[16]; - UPNP_GetGenericPortMappingEntry(m_urls->controlURL, m_data->first.servicetype, toString(i).c_str(), extPort, intClient, intPort, protocol, desc, enabled, rHost, duration); - if (string("ethereum") == desc) - { - m_reg.insert(atoi(extPort)); - return atoi(extPort); - } - } - cerr << "ERROR: Mapped port not found." << endl; -#endif - return 0; -} - -void UPnP::removeRedirect(int _port) -{ - (void)_port; -#if ETH_MINIUPNPC - char port_str[16]; -// int t; - printf("TB : upnp_rem_redir (%d)\n", _port); - if (m_urls->controlURL[0] == '\0') - { - printf("TB : the init was not done !\n"); - return; - } - sprintf(port_str, "%d", _port); - UPNP_DeletePortMapping(m_urls->controlURL, m_data->first.servicetype, port_str, "TCP", NULL); - m_reg.erase(_port); -#endif -} diff --git a/libethcore/UPnP.h b/libethcore/UPnP.h deleted file mode 100644 index 836e350b0..000000000 --- a/libethcore/UPnP.h +++ /dev/null @@ -1,53 +0,0 @@ -/* - This file is part of cpp-ethereum. - - cpp-ethereum is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - cpp-ethereum is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with cpp-ethereum. If not, see . -*/ -/** @file UPnP.h - * @authors: - * Gav Wood - * @date 2014 - */ - -#pragma once - -#include -#include -#include - -struct UPNPUrls; -struct IGDdatas; - -namespace eth -{ - -class UPnP -{ -public: - UPnP(); - ~UPnP(); - - std::string externalIP(); - int addRedirect(char const* addr, int port); - void removeRedirect(int port); - - bool isValid() const { return m_ok; } - - std::set m_reg; - bool m_ok; - std::shared_ptr m_urls; - std::shared_ptr m_data; -}; - -} diff --git a/libethereum/CMakeLists.txt b/libethereum/CMakeLists.txt index 68597f249..337b97d98 100644 --- a/libethereum/CMakeLists.txt +++ b/libethereum/CMakeLists.txt @@ -19,6 +19,7 @@ include_directories(..) target_link_libraries(${EXECUTABLE} evm) target_link_libraries(${EXECUTABLE} lll) +target_link_libraries(${EXECUTABLE} whisper) target_link_libraries(${EXECUTABLE} ethcore) target_link_libraries(${EXECUTABLE} ethnet) target_link_libraries(${EXECUTABLE} secp256k1) diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index b29c808ba..9e51f94b3 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -162,6 +162,17 @@ void Client::uninstallWatch(unsigned _i) m_filters.erase(fit); } +void Client::noteChanged(h256Set const& _filters) +{ + lock_guard l(m_filterLock); + for (auto& i: m_watches) + if (_filters.count(i.second.id)) + { + cwatch << "!!!" << i.first << i.second.id; + i.second.changes++; + } +} + void Client::appendFromNewPending(h256 _bloom, h256Set& o_changed) const { lock_guard l(m_filterLock); @@ -180,17 +191,6 @@ void Client::appendFromNewBlock(h256 _block, h256Set& o_changed) const o_changed.insert(i.first); } -void Client::noteChanged(h256Set const& _filters) -{ - lock_guard l(m_filterLock); - for (auto& i: m_watches) - if (_filters.count(i.second.id)) - { - cwatch << "!!!" << i.first << i.second.id; - i.second.changes++; - } -} - void Client::startNetwork(unsigned short _listenPort, std::string const& _seedHost, unsigned short _port, NodeMode _mode, unsigned _peers, string const& _publicIP, bool _upnp, u256 _networkId) { static const char* c_threadName = "net"; diff --git a/libethereum/CommonNet.h b/libethereum/CommonNet.h index bb459ce99..c3f721170 100644 --- a/libethereum/CommonNet.h +++ b/libethereum/CommonNet.h @@ -36,7 +36,6 @@ static const eth::uint c_maxHashesAsk = 32; ///< Maximum number of hashes GetBlo static const eth::uint c_maxBlocks = 16; ///< Maximum number of blocks Blocks will ever send. static const eth::uint c_maxBlocksAsk = 16; ///< Maximum number of blocks we ask to receive in Blocks (when using GetChain). -class UPnP; class OverlayDB; class BlockChain; class TransactionQueue; diff --git a/libethereum/EthereumHost.cpp b/libethereum/EthereumHost.cpp index 836f3f8f0..584aaeb4d 100644 --- a/libethereum/EthereumHost.cpp +++ b/libethereum/EthereumHost.cpp @@ -29,7 +29,6 @@ #include #include #include -#include #include #include "BlockChain.h" #include "TransactionQueue.h" diff --git a/libethereum/EthereumPeer.cpp b/libethereum/EthereumPeer.cpp index a39bebab8..0e651c651 100644 --- a/libethereum/EthereumPeer.cpp +++ b/libethereum/EthereumPeer.cpp @@ -30,7 +30,7 @@ using namespace std; using namespace eth; -#define clogS(X) eth::LogOutputStream(false) << "| " << std::setw(2) << session()->id() << "] " +#define clogS(X) eth::LogOutputStream(false) << "| " << std::setw(2) << session()->socketId() << "] " EthereumPeer::EthereumPeer(PeerSession* _s, HostCapabilityFace* _h): PeerCapability(_s, _h) { @@ -100,7 +100,7 @@ inline string toString(h256s const& _bs) void EthereumPeer::giveUpOnFetch() { - clogS(NetNote) << "GIVE UP FETCH; can't get " << toString(m_askedBlocks); + clogS(NetNote) << "GIVE UP FETCH; can't get" << toString(m_askedBlocks); if (m_askedBlocks.size()) { Guard l (host()->x_blocksNeeded); @@ -127,7 +127,7 @@ bool EthereumPeer::interpret(RLP const& _r) m_latestHash = _r[4].toHash(); auto genesisHash = _r[5].toHash(); - clogS(NetMessageSummary) << "Status: " << m_protocolVersion << "/" << m_networkId << "/" << genesisHash.abridged() << ", TD:" << m_totalDifficulty << "=" << m_latestHash.abridged(); + clogS(NetMessageSummary) << "Status:" << m_protocolVersion << "/" << m_networkId << "/" << genesisHash.abridged() << ", TD:" << m_totalDifficulty << "=" << m_latestHash.abridged(); if (genesisHash != host()->m_chain->genesisHash()) disable("Invalid genesis hash"); @@ -145,7 +145,7 @@ bool EthereumPeer::interpret(RLP const& _r) break; } case TransactionsPacket: - clogS(NetMessageSummary) << "Transactions (" << dec << (_r.itemCount() - 1) << " entries)"; + clogS(NetMessageSummary) << "Transactions (" << dec << (_r.itemCount() - 1) << "entries)"; addRating(_r.itemCount() - 1); for (unsigned i = 1; i < _r.itemCount(); ++i) { @@ -157,7 +157,7 @@ bool EthereumPeer::interpret(RLP const& _r) { h256 later = _r[1].toHash(); unsigned limit = _r[2].toInt(); - clogS(NetMessageSummary) << "GetBlockHashes (" << limit << "entries, " << later.abridged() << ")"; + clogS(NetMessageSummary) << "GetBlockHashes (" << limit << "entries," << later.abridged() << ")"; unsigned c = min(host()->m_chain->number(later), limit); @@ -171,7 +171,7 @@ bool EthereumPeer::interpret(RLP const& _r) } case BlockHashesPacket: { - clogS(NetMessageSummary) << "BlockHashes (" << dec << (_r.itemCount() - 1) << " entries)"; + clogS(NetMessageSummary) << "BlockHashes (" << dec << (_r.itemCount() - 1) << "entries)"; if (_r.itemCount() == 1) { host()->noteHaveChain(this); @@ -197,7 +197,7 @@ bool EthereumPeer::interpret(RLP const& _r) } case GetBlocksPacket: { - clogS(NetMessageSummary) << "GetBlocks (" << dec << (_r.itemCount() - 1) << " entries)"; + clogS(NetMessageSummary) << "GetBlocks (" << dec << (_r.itemCount() - 1) << "entries)"; // TODO: return the requested blocks. bytes rlp; unsigned n = 0; @@ -216,7 +216,7 @@ bool EthereumPeer::interpret(RLP const& _r) } case BlocksPacket: { - clogS(NetMessageSummary) << "Blocks (" << dec << (_r.itemCount() - 1) << " entries)"; + clogS(NetMessageSummary) << "Blocks (" << dec << (_r.itemCount() - 1) << "entries)"; if (_r.itemCount() == 1 && !m_askedBlocksChanged) { @@ -246,16 +246,16 @@ bool EthereumPeer::interpret(RLP const& _r) if (!host()->m_chain->details(bi.parentHash) && !m_knownBlocks.count(bi.parentHash)) { unknownParents++; - clogS(NetAllDetail) << "Unknown parent " << bi.parentHash << " of block " << h; + clogS(NetAllDetail) << "Unknown parent" << bi.parentHash << "of block" << h; } else { knownParents++; - clogS(NetAllDetail) << "Known parent " << bi.parentHash << " of block " << h; + clogS(NetAllDetail) << "Known parent" << bi.parentHash << "of block" << h; } } } - clogS(NetMessageSummary) << dec << knownParents << " known parents, " << unknownParents << "unknown, " << used << "used."; + clogS(NetMessageSummary) << dec << knownParents << "known parents," << unknownParents << "unknown," << used << "used."; continueGettingChain(); break; } @@ -301,7 +301,7 @@ void EthereumPeer::continueGettingChain() } else { - clogS(NetMessageSummary) << "No blocks left to get. Peer doesn't seem to have " << m_failedBlocks.size() << "of our needed blocks."; + clogS(NetMessageSummary) << "No blocks left to get. Peer doesn't seem to have" << m_failedBlocks.size() << "of our needed blocks."; host()->noteDoneBlocks(); } } diff --git a/libethereum/EthereumPeer.h b/libethereum/EthereumPeer.h index 3b182b450..57df62a11 100644 --- a/libethereum/EthereumPeer.h +++ b/libethereum/EthereumPeer.h @@ -36,18 +36,6 @@ namespace eth class HostCapabilityFace; -class WhisperSession: public PeerCapability -{ -public: - WhisperSession(); - virtual ~WhisperSession(); - - static std::string name() { return "shh"; } - -private: - virtual bool interpret(RLP const&) { return false; } -}; - /** * @brief The EthereumPeer class * @todo Document fully. diff --git a/libethnet/Common.h b/libethnet/Common.h index ccc066e88..ed12cb81f 100644 --- a/libethnet/Common.h +++ b/libethnet/Common.h @@ -110,7 +110,7 @@ public: protected: virtual std::string name() const = 0; virtual PeerCapability* newPeerCapability(PeerSession* _s) = 0; - virtual bool isInitialised() const = 0; + virtual bool isInitialised() const { return true; } void seal(bytes& _b); @@ -128,7 +128,6 @@ public: static std::string staticName() { return PeerCap::name(); } protected: - virtual bool isInitialised() const = 0; virtual std::string name() const { return PeerCap::name(); } virtual PeerCapability* newPeerCapability(PeerSession* _s) { return new PeerCap(_s, this); } }; diff --git a/libethnet/PeerHost.cpp b/libethnet/PeerHost.cpp index 4ff34b5d4..8806aba07 100644 --- a/libethnet/PeerHost.cpp +++ b/libethnet/PeerHost.cpp @@ -35,9 +35,9 @@ #include #include #include -#include #include #include "PeerSession.h" +#include "UPnP.h" using namespace std; using namespace eth; diff --git a/libethnet/PeerHost.h b/libethnet/PeerHost.h index 15e629ebe..012700091 100644 --- a/libethnet/PeerHost.h +++ b/libethnet/PeerHost.h @@ -107,6 +107,8 @@ public: /// Deserialise the data and populate the set of known peers. void restorePeers(bytesConstRef _b); + h512 id() const { return m_key.pub(); } + void registerPeer(std::shared_ptr _s, std::vector const& _caps); protected: diff --git a/libethnet/PeerSession.h b/libethnet/PeerSession.h index 4983bce4b..73e0764f3 100644 --- a/libethnet/PeerSession.h +++ b/libethnet/PeerSession.h @@ -53,7 +53,8 @@ public: bool isOpen() const { return m_socket.is_open(); } - unsigned id() const { return m_socket.native_handle(); } + h512 id() const { return m_id; } + unsigned socketId() const { return m_socket.native_handle(); } bi::tcp::endpoint endpoint() const; ///< for other peers to connect to. diff --git a/libwhisper/CMakeLists.txt b/libwhisper/CMakeLists.txt index 4d633495f..36c773979 100644 --- a/libwhisper/CMakeLists.txt +++ b/libwhisper/CMakeLists.txt @@ -20,6 +20,7 @@ include_directories(..) target_link_libraries(${EXECUTABLE} evm) target_link_libraries(${EXECUTABLE} lll) target_link_libraries(${EXECUTABLE} ethential) +target_link_libraries(${EXECUTABLE} ethcore) target_link_libraries(${EXECUTABLE} secp256k1) if(MINIUPNPC_LS) target_link_libraries(${EXECUTABLE} ${MINIUPNPC_LS}) diff --git a/libwhisper/Whisper.cpp b/libwhisper/Common.cpp similarity index 86% rename from libwhisper/Whisper.cpp rename to libwhisper/Common.cpp index 905080f4b..e3a355a8f 100644 --- a/libwhisper/Whisper.cpp +++ b/libwhisper/Common.cpp @@ -14,21 +14,14 @@ You should have received a copy of the GNU General Public License along with cpp-ethereum. If not, see . */ -/** @file Whisper.cpp +/** @file Common.cpp * @author Gav Wood * @date 2014 */ -#include "Whisper.h" +#include "Common.h" -#include using namespace std; using namespace eth; +using namespace shh; -Whisper::Whisper() -{ -} - -Whisper::~Whisper() -{ -} diff --git a/libwhisper/Whisper.h b/libwhisper/Common.h similarity index 55% rename from libwhisper/Whisper.h rename to libwhisper/Common.h index 7be8a0c49..f3e2e78cb 100644 --- a/libwhisper/Whisper.h +++ b/libwhisper/Common.h @@ -14,37 +14,43 @@ You should have received a copy of the GNU General Public License along with cpp-ethereum. If not, see . */ -/** @file Whisper.h +/** @file Common.h * @author Gav Wood * @date 2014 */ #pragma once -namespace eth -{ -/* -class NetPeer -{ -public: - NetPeer(); - virtual ~NetPeer(); +#include +#include +#include +#include +#include +#include -protected: - virtual void onIncoming(PeerId); - void send(PeerId); -}; -*/ -/** - */ -class Whisper//: public NetPeer +namespace shh { -public: - /// Constructor. - Whisper(); - /// Destructor. - virtual ~Whisper(); +using h256 = eth::h256; +using h512 = eth::h512; +using h256s = eth::h256s; +using bytes = eth::bytes; +using RLPStream = eth::RLPStream; +using RLP = eth::RLP; +using bytesRef = eth::bytesRef; +using bytesConstRef = eth::bytesConstRef; +using h256Set = eth::h256Set; + +class WhisperHost; +class WhisperPeer; +class Whisper; + +enum WhisperPacket +{ + StatusPacket = 0x20, + MessagesPacket, + AddFilterPacket, + RemoveFilterPacket }; } diff --git a/libwhisper/WhisperPeer.cpp b/libwhisper/WhisperPeer.cpp new file mode 100644 index 000000000..04c02821e --- /dev/null +++ b/libwhisper/WhisperPeer.cpp @@ -0,0 +1,216 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file Whisper.cpp + * @author Gav Wood + * @date 2014 + */ + +#include "WhisperPeer.h" + +#include +#include +using namespace std; +using namespace eth; +using namespace shh; + +#define clogS(X) eth::LogOutputStream(false) << "| " << std::setw(2) << session()->socketId() << "] " + +WhisperPeer::WhisperPeer(PeerSession* _s, HostCapabilityFace* _h): PeerCapability(_s, _h) +{ + RLPStream s; + prep(s); + sealAndSend(s.appendList(2) << StatusPacket << host()->protocolVersion()); +} + +WhisperPeer::~WhisperPeer() +{ +} + +WhisperHost* WhisperPeer::host() const +{ + return static_cast(PeerCapability::hostCapability()); +} + +bool WhisperPeer::interpret(RLP const& _r) +{ + switch (_r[0].toInt()) + { + case StatusPacket: + { + auto protocolVersion = _r[1].toInt(); + + clogS(NetMessageSummary) << "Status: " << protocolVersion; + + if (protocolVersion != host()->protocolVersion()) + disable("Invalid protocol version."); + + if (session()->id() < host()->host()->id()) + sendMessages(); + break; + } + case MessagesPacket: + { + unsigned n = 0; + for (auto i: _r) + if (n++) + host()->inject(Message(i), this); + sendMessages(); + break; + } + default: + return false; + } + return true; +} + +void WhisperPeer::sendMessages() +{ + RLPStream amalg; + unsigned n = 0; + + Guard l(x_unseen); + while (m_unseen.size()) + { + auto p = *m_unseen.begin(); + m_unseen.erase(m_unseen.begin()); + host()->streamMessage(p.second, amalg); + n++; + } + + // pause before sending if no messages to send + if (!n) + this_thread::sleep_for(chrono::milliseconds(100)); + + RLPStream s; + prep(s); + s.appendList(n + 1) << MessagesPacket; + s.appendRaw(amalg.out(), n); + sealAndSend(s); +} + +void WhisperPeer::noteNewMessage(h256 _h, Message const& _m) +{ + Guard l(x_unseen); + m_unseen[rating(_m)] = _h; +} + +WhisperHost::WhisperHost() +{ +} + +WhisperHost::~WhisperHost() +{ +} + +void WhisperHost::streamMessage(h256 _m, RLPStream& _s) const +{ + UpgradableGuard l(x_messages); + if (m_messages.count(_m)) + { + UpgradeGuard ll(l); + m_messages.at(_m).streamOut(_s); + } +} + +void WhisperHost::inject(Message const& _m, WhisperPeer* _p) +{ + auto h = _m.sha3(); + { + UpgradableGuard l(x_messages); + if (m_messages.count(h)) + return; + UpgradeGuard ll(l); + m_messages[h] = _m; + } + + if (_p) + { + Guard l(m_filterLock); + for (auto const& f: m_filters) + if (f.second.filter.matches(_m)) + noteChanged(h, f.first); + } + + for (auto& i: peers()) + if (i->cap().get() == _p) + i->addRating(1); + else + i->cap()->noteNewMessage(h, _m); +} + +void WhisperHost::noteChanged(h256 _messageHash, h256 _filter) +{ + for (auto& i: m_watches) + if (i.second.id == _filter) + { + cwatch << "!!!" << i.first << i.second.id; + i.second.changes.push_back(_messageHash); + } +} + +bool MessageFilter::matches(Message const& _m) const +{ + for (auto const& t: m_topicMasks) + { + if (t.first.size() != t.second.size() || _m.topic.size() < t.first.size()) + continue; + for (unsigned i = 0; i < t.first.size(); ++i) + if (((t.first[i] ^ _m.topic[i]) & t.second[i]) != 0) + goto NEXT; + return true; + NEXT:; + } + return false; +} + +unsigned WhisperHost::installWatch(h256 _h) +{ + auto ret = m_watches.size() ? m_watches.rbegin()->first + 1 : 0; + m_watches[ret] = ClientWatch(_h); + cwatch << "+++" << ret << _h; + return ret; +} + +unsigned WhisperHost::installWatch(shh::MessageFilter const& _f) +{ + Guard l(m_filterLock); + + h256 h = _f.sha3(); + + if (!m_filters.count(h)) + m_filters.insert(make_pair(h, _f)); + + return installWatch(h); +} + +void WhisperHost::uninstallWatch(unsigned _i) +{ + cwatch << "XXX" << _i; + + Guard l(m_filterLock); + + auto it = m_watches.find(_i); + if (it == m_watches.end()) + return; + auto id = it->second.id; + m_watches.erase(it); + + auto fit = m_filters.find(id); + if (fit != m_filters.end()) + if (!--fit->second.refCount) + m_filters.erase(fit); +} diff --git a/libwhisper/WhisperPeer.h b/libwhisper/WhisperPeer.h new file mode 100644 index 000000000..8228f6a3d --- /dev/null +++ b/libwhisper/WhisperPeer.h @@ -0,0 +1,200 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file Whisper.h + * @author Gav Wood + * @date 2014 + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include "Common.h" + +namespace shh +{ + +using eth::PeerSession; +using eth::HostCapabilityFace; +using eth::HostCapability; + +struct Message +{ + unsigned expiry = 0; + unsigned ttl = 0; + bytes topic; + bytes payload; + + Message() {} + Message(unsigned _exp, unsigned _ttl, bytes const& _topic, bytes const& _payload): expiry(_exp), ttl(_ttl), topic(_topic), payload(_payload) {} + Message(RLP const& _m) + { + expiry = _m[0].toInt(); + ttl = _m[1].toInt(); + topic = _m[2].toBytes(); + payload = _m[3].toBytes(); + } + + operator bool () const { return !!expiry; } + + void streamOut(RLPStream& _s) const { _s.appendList(4) << expiry << ttl << topic << payload; } + h256 sha3() const { RLPStream s; streamOut(s); return eth::sha3(s.out()); } +}; + +/** + */ +class WhisperPeer: public eth::PeerCapability +{ + friend class WhisperHost; + +public: + WhisperPeer(PeerSession* _s, HostCapabilityFace* _h); + virtual ~WhisperPeer(); + + static std::string name() { return "shh"; } + + WhisperHost* host() const; + +private: + virtual bool interpret(RLP const&); + + void sendMessages(); + + unsigned rating(Message const&) const { return 0; } // TODO + void noteNewMessage(h256 _h, Message const& _m); + + mutable eth::Mutex x_unseen; + std::map m_unseen; ///< Rated according to what they want. +}; + +class MessageFilter +{ +public: + MessageFilter() {} + MessageFilter(std::vector > const& _m): m_topicMasks(_m) {} + MessageFilter(RLP const& _r): m_topicMasks((std::vector >)_r) {} + + void fillStream(RLPStream& _s) const { _s << m_topicMasks; } + h256 sha3() const { RLPStream s; fillStream(s); return eth::sha3(s.out()); } + + bool matches(Message const& _m) const; + +private: + std::vector > m_topicMasks; +}; + +struct InstalledFilter +{ + InstalledFilter(MessageFilter const& _f): filter(_f) {} + + MessageFilter filter; + unsigned refCount = 1; +}; + +struct ClientWatch +{ + ClientWatch() {} + explicit ClientWatch(h256 _id): id(_id) {} + + h256 id; + h256s changes; +}; + +class WhisperHost: public HostCapability +{ + friend class WhisperPeer; + +public: + WhisperHost(); + virtual ~WhisperHost(); + + unsigned protocolVersion() const { return 0; } + + void inject(Message const& _m, WhisperPeer* _from = nullptr); + + unsigned installWatch(MessageFilter const& _filter); + unsigned installWatch(h256 _filterId); + void uninstallWatch(unsigned _watchId); + h256s peekWatch(unsigned _watchId) const { eth::Guard l(m_filterLock); try { return m_watches.at(_watchId).changes; } catch (...) { return h256s(); } } + h256s checkWatch(unsigned _watchId) { eth::Guard l(m_filterLock); h256s ret; try { ret = m_watches.at(_watchId).changes; m_watches.at(_watchId).changes.clear(); } catch (...) {} return ret; } + + Message message(h256 _m) const { try { eth::ReadGuard l(x_messages); return m_messages.at(_m); } catch (...) { return Message(); } } + + void sendRaw(bytes const& _payload, bytes const& _topic, unsigned _ttl) { inject(Message(time(0) + _ttl, _ttl, _topic, _payload)); } + +private: + void streamMessage(h256 _m, RLPStream& _s) const; + + void noteChanged(h256 _messageHash, h256 _filter); + + mutable eth::SharedMutex x_messages; + std::map m_messages; + + mutable eth::Mutex m_filterLock; + std::map m_filters; + std::map m_watches; +}; + +struct WatchChannel: public eth::LogChannel { static const char* name() { return "shh"; } static const int verbosity = 1; }; +#define cwatch eth::LogOutputStream() + +class Watch; + +} +/* +namespace std { void swap(shh::Watch& _a, shh::Watch& _b); } + +namespace shh +{ + +class Watch: public boost::noncopyable +{ + friend void std::swap(Watch& _a, Watch& _b); + +public: + Watch() {} + Watch(Whisper& _c, h256 _f): m_c(&_c), m_id(_c.installWatch(_f)) {} + Watch(Whisper& _c, MessageFilter const& _tf): m_c(&_c), m_id(_c.installWatch(_tf)) {} + ~Watch() { if (m_c) m_c->uninstallWatch(m_id); } + + bool check() { return m_c ? m_c->checkWatch(m_id) : false; } + bool peek() { return m_c ? m_c->peekWatch(m_id) : false; } + +private: + Whisper* m_c; + unsigned m_id; +}; + +} + +namespace shh +{ + +inline void swap(shh::Watch& _a, shh::Watch& _b) +{ + swap(_a.m_c, _b.m_c); + swap(_a.m_id, _b.m_id); +} + +} +*/ From 3653cba8c53f42cdf81fe43c9170f22975c13b87 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 3 Sep 2014 09:45:12 +0200 Subject: [PATCH 148/223] Fix block gas limit. --- libethereum/Executive.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libethereum/Executive.cpp b/libethereum/Executive.cpp index d2b6fc0bc..7fbd36102 100644 --- a/libethereum/Executive.cpp +++ b/libethereum/Executive.cpp @@ -85,7 +85,7 @@ bool Executive::setup(bytesConstRef _rlp) if (startGasUsed + m_t.gas > m_s.m_currentBlock.gasLimit) { clog(StateChat) << "Too much gas used in this block: Require <" << (m_s.m_currentBlock.gasLimit - startGasUsed) << " Got" << m_t.gas; -// throw BlockGasLimitReached(); + throw BlockGasLimitReached(); } // Increment associated nonce for sender. From 54a21ab3bd2f5c6435cfff82d64000a9980d8847 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 3 Sep 2014 16:51:22 +0200 Subject: [PATCH 149/223] Quick commit. --- libethcore/BlockInfo.h | 2 +- libethential/FixedHash.h | 4 +++- libethereum/EthereumHost.cpp | 1 + libethereum/EthereumHost.h | 2 +- libethereum/EthereumPeer.cpp | 4 +++- libethereum/EthereumPeer.h | 6 ++---- libethnet/CMakeLists.txt | 3 --- libethnet/Common.cpp | 5 +++-- libethnet/Common.h | 17 +++++++++++++++-- libethnet/PeerHost.cpp | 25 +++++++++++++------------ libethnet/PeerHost.h | 19 ++++++++++--------- libethnet/PeerSession.cpp | 9 +++++---- libethnet/PeerSession.h | 7 +++---- libethnet/UPnP.cpp | 3 ++- libethnet/UPnP.h | 2 +- libwhisper/WhisperPeer.cpp | 1 + libwhisper/WhisperPeer.h | 9 +++++---- 17 files changed, 69 insertions(+), 50 deletions(-) diff --git a/libethcore/BlockInfo.h b/libethcore/BlockInfo.h index cf9735c7d..d88b9d09f 100644 --- a/libethcore/BlockInfo.h +++ b/libethcore/BlockInfo.h @@ -48,7 +48,7 @@ extern u256 c_genesisDifficulty; * and calculateGasLimit() and the object serialised to RLP with fillStream. To determine the * header hash without the nonce (for mining), the method headerHashWithoutNonce() is provided. * - * The defualt constructor creates an empty object, which can be tested against with the boolean + * The default constructor creates an empty object, which can be tested against with the boolean * conversion operator. */ struct BlockInfo diff --git a/libethential/FixedHash.h b/libethential/FixedHash.h index e13345728..828e3aef6 100644 --- a/libethential/FixedHash.h +++ b/libethential/FixedHash.h @@ -127,7 +127,7 @@ public: /// @returns a randomly-valued hash template - static FixedHash random(Engine& _eng = s_fixedHashEngine) + static FixedHash random(Engine& _eng) { FixedHash ret; for (auto& i: ret.m_data) @@ -135,6 +135,8 @@ public: return ret; } + static FixedHash random() { return random(s_fixedHashEngine); } + /// A generic std::hash compatible function object. struct hash { diff --git a/libethereum/EthereumHost.cpp b/libethereum/EthereumHost.cpp index 584aaeb4d..e318bd66d 100644 --- a/libethereum/EthereumHost.cpp +++ b/libethereum/EthereumHost.cpp @@ -36,6 +36,7 @@ #include "EthereumPeer.h" using namespace std; using namespace eth; +using namespace p2p; EthereumHost::EthereumHost(BlockChain const& _ch, u256 _networkId): HostCapability(), diff --git a/libethereum/EthereumHost.h b/libethereum/EthereumHost.h index d2c28644b..e8aa57892 100644 --- a/libethereum/EthereumHost.h +++ b/libethereum/EthereumHost.h @@ -45,7 +45,7 @@ class BlockQueue; * @brief The EthereumHost class * @warning None of this is thread-safe. You have been warned. */ -class EthereumHost: public HostCapability +class EthereumHost: public p2p::HostCapability { friend class EthereumPeer; diff --git a/libethereum/EthereumPeer.cpp b/libethereum/EthereumPeer.cpp index 0e651c651..75983a2bf 100644 --- a/libethereum/EthereumPeer.cpp +++ b/libethereum/EthereumPeer.cpp @@ -29,10 +29,12 @@ #include "EthereumHost.h" using namespace std; using namespace eth; +using namespace p2p; #define clogS(X) eth::LogOutputStream(false) << "| " << std::setw(2) << session()->socketId() << "] " -EthereumPeer::EthereumPeer(PeerSession* _s, HostCapabilityFace* _h): PeerCapability(_s, _h) +EthereumPeer::EthereumPeer(PeerSession* _s, HostCapabilityFace* _h): + PeerCapability(_s, _h) { sendStatus(); } diff --git a/libethereum/EthereumPeer.h b/libethereum/EthereumPeer.h index 57df62a11..2aea2bdb3 100644 --- a/libethereum/EthereumPeer.h +++ b/libethereum/EthereumPeer.h @@ -34,18 +34,16 @@ namespace eth { -class HostCapabilityFace; - /** * @brief The EthereumPeer class * @todo Document fully. */ -class EthereumPeer: public PeerCapability +class EthereumPeer: public p2p::PeerCapability { friend class EthereumHost; public: - EthereumPeer(PeerSession* _s, HostCapabilityFace* _h); + EthereumPeer(p2p::PeerSession* _s, p2p::HostCapabilityFace* _h); virtual ~EthereumPeer(); static std::string name() { return "eth"; } diff --git a/libethnet/CMakeLists.txt b/libethnet/CMakeLists.txt index a694ab205..79d07d7e4 100644 --- a/libethnet/CMakeLists.txt +++ b/libethnet/CMakeLists.txt @@ -17,9 +17,6 @@ file(GLOB HEADERS "*.h") include_directories(..) -target_link_libraries(${EXECUTABLE} evm) -target_link_libraries(${EXECUTABLE} lll) -target_link_libraries(${EXECUTABLE} ethcore) target_link_libraries(${EXECUTABLE} secp256k1) if(MINIUPNPC_LS) target_link_libraries(${EXECUTABLE} ${MINIUPNPC_LS}) diff --git a/libethnet/Common.cpp b/libethnet/Common.cpp index f0f0e4d2d..6226df296 100644 --- a/libethnet/Common.cpp +++ b/libethnet/Common.cpp @@ -26,12 +26,13 @@ #include "PeerHost.h" using namespace std; using namespace eth; +using namespace p2p; // Helper function to determine if an address falls within one of the reserved ranges // For V4: // Class A "10.*", Class B "172.[16->31].*", Class C "192.168.*" // Not implemented yet for V6 -bool eth::isPrivateAddress(bi::address _addressToCheck) +bool p2p::isPrivateAddress(bi::address _addressToCheck) { if (_addressToCheck.is_v4()) { @@ -47,7 +48,7 @@ bool eth::isPrivateAddress(bi::address _addressToCheck) return false; } -std::string eth::reasonOf(DisconnectReason _r) +std::string p2p::reasonOf(DisconnectReason _r) { switch (_r) { diff --git a/libethnet/Common.h b/libethnet/Common.h index ed12cb81f..da7dc0a53 100644 --- a/libethnet/Common.h +++ b/libethnet/Common.h @@ -29,16 +29,29 @@ #include #include #include +#include namespace ba = boost::asio; namespace bi = boost::asio::ip; namespace eth { +class RLP; +class RLPStream; +} + +namespace p2p +{ + +using eth::LogChannel; +using eth::bytes; +using eth::h256; +using eth::h512; +using eth::bytesConstRef; +using eth::RLP; +using eth::RLPStream; bool isPrivateAddress(bi::address _addressToCheck); -class RLP; -class RLPStream; class PeerHost; class PeerSession; diff --git a/libethnet/PeerHost.cpp b/libethnet/PeerHost.cpp index 8806aba07..00d4705ee 100644 --- a/libethnet/PeerHost.cpp +++ b/libethnet/PeerHost.cpp @@ -40,6 +40,7 @@ #include "UPnP.h" using namespace std; using namespace eth; +using namespace p2p; // Addresses we will skip during network interface discovery // Use a vector as the list is small @@ -58,13 +59,13 @@ PeerHost::PeerHost(std::string const& _clientVersion, unsigned short _port, stri m_localNetworking(_localNetworking), m_acceptor(m_ioService, bi::tcp::endpoint(bi::tcp::v4(), _port)), m_socket(m_ioService), - m_key(KeyPair::create()) + m_id(h512::random()) { populateAddresses(); determinePublic(_publicAddress, _upnp); ensureAccepting(); m_lastPeersRequest = chrono::steady_clock::time_point::min(); - clog(NetNote) << "Id:" << m_key.address().abridged(); + clog(NetNote) << "Id:" << m_id.abridged(); } PeerHost::PeerHost(std::string const& _clientVersion, string const& _publicAddress, bool _upnp, bool _localNetworking): @@ -73,7 +74,7 @@ PeerHost::PeerHost(std::string const& _clientVersion, string const& _publicAddre m_localNetworking(_localNetworking), m_acceptor(m_ioService, bi::tcp::endpoint(bi::tcp::v4(), 0)), m_socket(m_ioService), - m_key(KeyPair::create()) + m_id(h512::random()) { m_listenPort = m_acceptor.local_endpoint().port(); @@ -82,7 +83,7 @@ PeerHost::PeerHost(std::string const& _clientVersion, string const& _publicAddre determinePublic(_publicAddress, _upnp); ensureAccepting(); m_lastPeersRequest = chrono::steady_clock::time_point::min(); - clog(NetNote) << "Id:" << m_key.address().abridged(); + clog(NetNote) << "Id:" << m_id.abridged(); } PeerHost::PeerHost(std::string const& _clientVersion): @@ -90,12 +91,12 @@ PeerHost::PeerHost(std::string const& _clientVersion): m_listenPort(0), m_acceptor(m_ioService, bi::tcp::endpoint(bi::tcp::v4(), 0)), m_socket(m_ioService), - m_key(KeyPair::create()) + m_id(h512::random()) { // populate addresses. populateAddresses(); m_lastPeersRequest = chrono::steady_clock::time_point::min(); - clog(NetNote) << "Id:" << m_key.address().abridged(); + clog(NetNote) << "Id:" << m_id.abridged(); } PeerHost::~PeerHost() @@ -271,11 +272,11 @@ void PeerHost::populateAddresses() #endif } -std::map PeerHost::potentialPeers() +std::map PeerHost::potentialPeers() { - std::map ret; + std::map ret; if (!m_public.address().is_unspecified()) - ret.insert(make_pair(m_key.pub(), m_public)); + ret.insert(make_pair(m_id, m_public)); Guard l(x_peers); for (auto i: m_peers) if (auto j = i.second.lock()) @@ -361,7 +362,7 @@ void PeerHost::connect(bi::tcp::endpoint const& _ep) }); } -bool PeerHost::havePeer(Public _id) const +bool PeerHost::havePeer(h512 _id) const { Guard l(x_peers); @@ -413,7 +414,7 @@ void PeerHost::prunePeers() { Guard l(x_peers); // We'll keep at most twice as many as is ideal, halfing what counts as "too young to kill" until we get there. - for (uint old = 15000; m_peers.size() > m_idealPeerCount * 2 && old > 100; old /= 2) + for (unsigned old = 15000; m_peers.size() > m_idealPeerCount * 2 && old > 100; old /= 2) while (m_peers.size() > m_idealPeerCount) { // look for worst peer to kick off @@ -492,7 +493,7 @@ void PeerHost::restorePeers(bytesConstRef _b) { for (auto i: RLP(_b)) { - auto k = (Public)i[2]; + auto k = (h512)i[2]; if (!m_incomingPeers.count(k)) { m_incomingPeers.insert(make_pair(k, make_pair(bi::tcp::endpoint(bi::address_v4(i[0].toArray()), i[1].toInt()), 0))); diff --git a/libethnet/PeerHost.h b/libethnet/PeerHost.h index 012700091..e9e1a5987 100644 --- a/libethnet/PeerHost.h +++ b/libethnet/PeerHost.h @@ -29,18 +29,19 @@ #include #include #include -#include #include "Common.h" namespace ba = boost::asio; namespace bi = boost::asio::ip; -namespace eth +namespace p2p { class RLPStream; class TransactionQueue; class BlockQueue; +using eth::Guard; + /** * @brief The PeerHost class * Capabilities should be registered prior to startNetwork, since m_capabilities is not thread-safe. @@ -84,7 +85,7 @@ public: void process(); /// @returns true iff we have the a peer of the given id. - bool havePeer(Public _id) const; + bool havePeer(h512 _id) const; /// Set ideal number of peers. void setIdealPeerCount(unsigned _n) { m_idealPeerCount = _n; } @@ -107,7 +108,7 @@ public: /// Deserialise the data and populate the set of known peers. void restorePeers(bytesConstRef _b); - h512 id() const { return m_key.pub(); } + h512 id() const { return m_id; } void registerPeer(std::shared_ptr _s, std::vector const& _caps); @@ -123,7 +124,7 @@ protected: void growPeers(); void prunePeers(); - std::map potentialPeers(); + std::map potentialPeers(); std::string m_clientVersion; @@ -136,13 +137,13 @@ protected: UPnP* m_upnp = nullptr; bi::tcp::endpoint m_public; - KeyPair m_key; + h512 m_id; mutable std::mutex x_peers; - mutable std::map> m_peers; // mutable because we flush zombie entries (null-weakptrs) as regular maintenance from a const method. + mutable std::map> m_peers; // mutable because we flush zombie entries (null-weakptrs) as regular maintenance from a const method. - std::map> m_incomingPeers; // TODO: does this need a lock? - std::vector m_freePeers; + std::map> m_incomingPeers; // TODO: does this need a lock? + std::vector m_freePeers; std::chrono::steady_clock::time_point m_lastPeersRequest; unsigned m_idealPeerCount = 5; diff --git a/libethnet/PeerSession.cpp b/libethnet/PeerSession.cpp index 7ddc61eec..1f19bd032 100644 --- a/libethnet/PeerSession.cpp +++ b/libethnet/PeerSession.cpp @@ -27,6 +27,7 @@ #include "PeerHost.h" using namespace std; using namespace eth; +using namespace p2p; #define clogS(X) eth::LogOutputStream(false) << "| " << std::setw(2) << m_socket.native_handle() << "] " @@ -74,7 +75,7 @@ bool PeerSession::interpret(RLP const& _r) { case HelloPacket: { - m_protocolVersion = _r[1].toInt(); + m_protocolVersion = _r[1].toInt(); auto clientVersion = _r[2].toString(); auto caps = _r[3].toVector(); m_listenPort = _r[4].toInt(); @@ -156,14 +157,14 @@ bool PeerSession::interpret(RLP const& _r) { bi::address_v4 peerAddress(_r[i][0].toHash>().asArray()); auto ep = bi::tcp::endpoint(peerAddress, _r[i][1].toInt()); - Public id = _r[i][2].toHash(); + h512 id = _r[i][2].toHash(); if (isPrivateAddress(peerAddress) && !m_server->m_localNetworking) goto CONTINUE; clogS(NetAllDetail) << "Checking: " << ep << "(" << id.abridged() << ")"; // check that it's not us or one we already know: - if (id && (m_server->m_key.pub() == id || m_server->havePeer(id) || m_server->m_incomingPeers.count(id))) + if (id && (m_server->m_id == id || m_server->havePeer(id) || m_server->m_incomingPeers.count(id))) goto CONTINUE; // check that we're not already connected to addr: @@ -336,7 +337,7 @@ void PeerSession::start() << m_server->m_clientVersion << m_server->caps() << m_server->m_public.port() - << m_server->m_key.pub(); + << m_server->m_id; sealAndSend(s); ping(); getPeers(); diff --git a/libethnet/PeerSession.h b/libethnet/PeerSession.h index 73e0764f3..50fb86e3d 100644 --- a/libethnet/PeerSession.h +++ b/libethnet/PeerSession.h @@ -27,10 +27,9 @@ #include #include #include -#include #include "Common.h" -namespace eth +namespace p2p { /** @@ -89,10 +88,10 @@ private: mutable bi::tcp::socket m_socket; ///< Mutable to ask for native_handle(). std::array m_data; PeerInfo m_info; - Public m_id; + h512 m_id; bytes m_incoming; - uint m_protocolVersion; + unsigned m_protocolVersion; unsigned short m_listenPort; ///< Port that the remote client is listening on for connections. Useful for giving to peers. std::chrono::steady_clock::time_point m_ping; diff --git a/libethnet/UPnP.cpp b/libethnet/UPnP.cpp index 427450e03..ce0286681 100644 --- a/libethnet/UPnP.cpp +++ b/libethnet/UPnP.cpp @@ -34,6 +34,7 @@ #include using namespace std; using namespace eth; +using namespace p2p; UPnP::UPnP() { @@ -126,7 +127,7 @@ int UPnP::addRedirect(char const* _addr, int _port) return _port; // Failed - now try (random external, port internal) and cycle up to 10 times. - for (uint i = 0; i < 10; ++i) + for (unsigned i = 0; i < 10; ++i) { _port = rand() % 65535 - 1024 + 1024; sprintf(port_str, "%d", _port); diff --git a/libethnet/UPnP.h b/libethnet/UPnP.h index 836e350b0..8f118bae4 100644 --- a/libethnet/UPnP.h +++ b/libethnet/UPnP.h @@ -29,7 +29,7 @@ struct UPNPUrls; struct IGDdatas; -namespace eth +namespace p2p { class UPnP diff --git a/libwhisper/WhisperPeer.cpp b/libwhisper/WhisperPeer.cpp index 04c02821e..b59ae9c4a 100644 --- a/libwhisper/WhisperPeer.cpp +++ b/libwhisper/WhisperPeer.cpp @@ -25,6 +25,7 @@ #include using namespace std; using namespace eth; +using namespace p2p; using namespace shh; #define clogS(X) eth::LogOutputStream(false) << "| " << std::setw(2) << session()->socketId() << "] " diff --git a/libwhisper/WhisperPeer.h b/libwhisper/WhisperPeer.h index 8228f6a3d..9853399ae 100644 --- a/libwhisper/WhisperPeer.h +++ b/libwhisper/WhisperPeer.h @@ -34,9 +34,10 @@ namespace shh { -using eth::PeerSession; -using eth::HostCapabilityFace; -using eth::HostCapability; +using p2p::PeerSession; +using p2p::HostCapabilityFace; +using p2p::HostCapability; +using p2p::PeerCapability; struct Message { @@ -63,7 +64,7 @@ struct Message /** */ -class WhisperPeer: public eth::PeerCapability +class WhisperPeer: public PeerCapability { friend class WhisperHost; From 4842accecb7316c4136fa48dad97bc3bc3a774d6 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 3 Sep 2014 16:51:56 +0200 Subject: [PATCH 150/223] Version bump. --- libethcore/CommonEth.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libethcore/CommonEth.cpp b/libethcore/CommonEth.cpp index b63ade94e..49a57d785 100644 --- a/libethcore/CommonEth.cpp +++ b/libethcore/CommonEth.cpp @@ -29,7 +29,7 @@ using namespace eth; //#define ETH_ADDRESS_DEBUG 1 -const unsigned eth::c_protocolVersion = 30; +const unsigned eth::c_protocolVersion = 31; const unsigned eth::c_databaseVersion = 1; static const vector> g_units = From fa1b3853b376a97a21ed6e4fa96b71996c9a063f Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 3 Sep 2014 20:18:02 +0200 Subject: [PATCH 151/223] Reorganisation. --- CMakeLists.txt | 9 +- alethzero/MainWin.cpp | 2 +- exp/CMakeLists.txt | 2 +- exp/main.cpp | 3 +- libethereum/CMakeLists.txt | 2 +- libethereum/Client.cpp | 3 +- libethereum/Client.h | 6 +- libethereum/EthereumHost.cpp | 4 +- libethereum/EthereumHost.h | 2 +- libethereum/EthereumPeer.cpp | 2 +- libethereum/EthereumPeer.h | 2 +- libethereumx/Ethereum.cpp | 1 + libethereumx/Ethereum.h | 4 +- libethnet/All.h | 6 - {libethnet => libp2p}/CMakeLists.txt | 2 +- {libethnet => libp2p}/Common.cpp | 0 {libethnet => libp2p}/Common.h | 0 {libethnet => libp2p}/PeerHost.cpp | 0 {libethnet => libp2p}/PeerHost.h | 0 {libethnet => libp2p}/PeerSession.cpp | 0 {libethnet => libp2p}/PeerSession.h | 0 {libethnet => libp2p}/UPnP.cpp | 0 {libethnet => libp2p}/UPnP.h | 0 libpyserpent/pyserpent.cpp | 10 +- libqethereum/QEthereum.cpp | 2 +- libqethereum/QmlEthereum.cpp | 2 +- libserpent/bignum.cpp | 8 +- libserpent/bignum.h | 4 +- libserpent/compiler.cpp | 262 ++++++++++++++++++-------- libserpent/opcodes.h | 166 +++++++++------- libserpent/parser.cpp | 11 +- libserpent/rewriter.cpp | 85 +++++++-- libserpent/tokenize.cpp | 5 +- libserpent/util.cpp | 19 +- libserpent/util.h | 3 + libwhisper/CMakeLists.txt | 5 +- libwhisper/Common.cpp | 1 + libwhisper/Common.h | 2 +- libwhisper/WhisperPeer.cpp | 2 +- neth/main.cpp | 1 + sc/cmdline.cpp | 4 +- test/peer.cpp | 4 +- third/MainWin.cpp | 2 +- walleth/MainWin.cpp | 2 +- 44 files changed, 437 insertions(+), 213 deletions(-) delete mode 100644 libethnet/All.h rename {libethnet => libp2p}/CMakeLists.txt (99%) rename {libethnet => libp2p}/Common.cpp (100%) rename {libethnet => libp2p}/Common.h (100%) rename {libethnet => libp2p}/PeerHost.cpp (100%) rename {libethnet => libp2p}/PeerHost.h (100%) rename {libethnet => libp2p}/PeerSession.cpp (100%) rename {libethnet => libp2p}/PeerSession.h (100%) rename {libethnet => libp2p}/UPnP.cpp (100%) rename {libethnet => libp2p}/UPnP.h (100%) diff --git a/CMakeLists.txt b/CMakeLists.txt index 36ab9dec8..fcadd4be5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -337,12 +337,15 @@ add_subdirectory(lllc) add_subdirectory(sc) if (NOT LANGUAGES) add_subdirectory(secp256k1) - add_subdirectory(libethnet) + add_subdirectory(libp2p) + add_subdirectory(libwhisper) + add_subdirectory(libethcore) add_subdirectory(libevm) - add_subdirectory(libwhisper) add_subdirectory(libethereum) - add_subdirectory(libethereumx) + add_subdirectory(libethereumx) # TODO remove + + #add_subdirectory(libwebthree) add_subdirectory(test) add_subdirectory(eth) if("x${CMAKE_BUILD_TYPE}" STREQUAL "xDebug") diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index 64beb7e83..8a78a2e7f 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -58,7 +58,7 @@ using eth::Instruction; using eth::KeyPair; using eth::NodeMode; using eth::BlockChain; -using eth::PeerInfo; +using p2p::PeerInfo; using eth::RLP; using eth::Secret; using eth::Transaction; diff --git a/exp/CMakeLists.txt b/exp/CMakeLists.txt index 036169fee..da6775798 100644 --- a/exp/CMakeLists.txt +++ b/exp/CMakeLists.txt @@ -9,7 +9,7 @@ set(EXECUTABLE exp) add_executable(${EXECUTABLE} ${SRC_LIST}) target_link_libraries(${EXECUTABLE} ethereum) -target_link_libraries(${EXECUTABLE} ethnet) +target_link_libraries(${EXECUTABLE} p2p) target_link_libraries(${EXECUTABLE} gmp) target_link_libraries(${EXECUTABLE} ${CRYPTOPP_LS}) if(MINIUPNPC_LS) diff --git a/exp/main.cpp b/exp/main.cpp index db41def59..eeff5a55f 100644 --- a/exp/main.cpp +++ b/exp/main.cpp @@ -30,13 +30,14 @@ #include #include #include -#include +#include #include #if 0 #include #include "BuildInfo.h" #endif using namespace std; +using namespace p2p; using namespace eth; using namespace shh; #if 0 diff --git a/libethereum/CMakeLists.txt b/libethereum/CMakeLists.txt index 337b97d98..43812b7d3 100644 --- a/libethereum/CMakeLists.txt +++ b/libethereum/CMakeLists.txt @@ -20,8 +20,8 @@ include_directories(..) target_link_libraries(${EXECUTABLE} evm) target_link_libraries(${EXECUTABLE} lll) target_link_libraries(${EXECUTABLE} whisper) +target_link_libraries(${EXECUTABLE} p2p) target_link_libraries(${EXECUTABLE} ethcore) -target_link_libraries(${EXECUTABLE} ethnet) target_link_libraries(${EXECUTABLE} secp256k1) if(MINIUPNPC_LS) target_link_libraries(${EXECUTABLE} ${MINIUPNPC_LS}) diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index 9e51f94b3..e3598ec57 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -25,11 +25,12 @@ #include #include #include -#include +#include #include "Defaults.h" #include "EthereumHost.h" using namespace std; using namespace eth; +using namespace p2p; VersionChecker::VersionChecker(string const& _dbPath): m_path(_dbPath.size() ? _dbPath : Defaults::dbPath()) diff --git a/libethereum/Client.h b/libethereum/Client.h index 1ff553082..64e26ec12 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -31,7 +31,7 @@ #include #include #include -#include +#include #include "BlockChain.h" #include "TransactionQueue.h" #include "State.h" @@ -199,7 +199,7 @@ public: // Network stuff: /// Get information on the current peer set. - std::vector peers(); + std::vector peers(); /// Same as peers().size(), but more efficient. size_t peerCount() const; /// Same as peers().size(), but more efficient. @@ -305,7 +305,7 @@ private: std::unique_ptr m_workNet; ///< The network thread. std::atomic m_workNetState; mutable boost::shared_mutex x_net; ///< Lock for the network existance. - std::unique_ptr m_net; ///< Should run in background and send us events when blocks found and allow us to send blocks as required. + std::unique_ptr m_net; ///< Should run in background and send us events when blocks found and allow us to send blocks as required. std::unique_ptr m_work; ///< The work thread. std::atomic m_workState; diff --git a/libethereum/EthereumHost.cpp b/libethereum/EthereumHost.cpp index e318bd66d..b35186fff 100644 --- a/libethereum/EthereumHost.cpp +++ b/libethereum/EthereumHost.cpp @@ -27,8 +27,8 @@ #include #include #include -#include -#include +#include +#include #include #include "BlockChain.h" #include "TransactionQueue.h" diff --git a/libethereum/EthereumHost.h b/libethereum/EthereumHost.h index e8aa57892..0ee56b9cf 100644 --- a/libethereum/EthereumHost.h +++ b/libethereum/EthereumHost.h @@ -30,7 +30,7 @@ #include #include #include -#include +#include #include "CommonNet.h" #include "EthereumPeer.h" diff --git a/libethereum/EthereumPeer.cpp b/libethereum/EthereumPeer.cpp index 75983a2bf..5c121928d 100644 --- a/libethereum/EthereumPeer.cpp +++ b/libethereum/EthereumPeer.cpp @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include "BlockChain.h" #include "EthereumHost.h" using namespace std; diff --git a/libethereum/EthereumPeer.h b/libethereum/EthereumPeer.h index 2aea2bdb3..5f01148af 100644 --- a/libethereum/EthereumPeer.h +++ b/libethereum/EthereumPeer.h @@ -28,7 +28,7 @@ #include #include #include -#include +#include #include "CommonNet.h" namespace eth diff --git a/libethereumx/Ethereum.cpp b/libethereumx/Ethereum.cpp index c4cc29d87..f2793a366 100644 --- a/libethereumx/Ethereum.cpp +++ b/libethereumx/Ethereum.cpp @@ -25,6 +25,7 @@ #include using namespace std; using namespace eth; +using namespace p2p; Ethereum::Ethereum() { diff --git a/libethereumx/Ethereum.h b/libethereumx/Ethereum.h index 6ede791cf..9f08234fe 100644 --- a/libethereumx/Ethereum.h +++ b/libethereumx/Ethereum.h @@ -30,7 +30,7 @@ #include #include #include -#include +#include #include #include #include @@ -120,7 +120,7 @@ public: // Network stuff: /// Get information on the current peer set. - std::vector peers(); + std::vector peers(); /// Same as peers().size(), but more efficient. size_t peerCount() const; diff --git a/libethnet/All.h b/libethnet/All.h deleted file mode 100644 index 55a5423b7..000000000 --- a/libethnet/All.h +++ /dev/null @@ -1,6 +0,0 @@ -#pragma once - -#include "Common.h" -#include "PeerHost.h" -#include "PeerSession.h" - diff --git a/libethnet/CMakeLists.txt b/libp2p/CMakeLists.txt similarity index 99% rename from libethnet/CMakeLists.txt rename to libp2p/CMakeLists.txt index 79d07d7e4..98d9b06bb 100644 --- a/libethnet/CMakeLists.txt +++ b/libp2p/CMakeLists.txt @@ -4,7 +4,7 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DSTATICLIB") aux_source_directory(. SRC_LIST) -set(EXECUTABLE ethnet) +set(EXECUTABLE p2p) # set(CMAKE_INSTALL_PREFIX ../lib) if(ETH_STATIC) diff --git a/libethnet/Common.cpp b/libp2p/Common.cpp similarity index 100% rename from libethnet/Common.cpp rename to libp2p/Common.cpp diff --git a/libethnet/Common.h b/libp2p/Common.h similarity index 100% rename from libethnet/Common.h rename to libp2p/Common.h diff --git a/libethnet/PeerHost.cpp b/libp2p/PeerHost.cpp similarity index 100% rename from libethnet/PeerHost.cpp rename to libp2p/PeerHost.cpp diff --git a/libethnet/PeerHost.h b/libp2p/PeerHost.h similarity index 100% rename from libethnet/PeerHost.h rename to libp2p/PeerHost.h diff --git a/libethnet/PeerSession.cpp b/libp2p/PeerSession.cpp similarity index 100% rename from libethnet/PeerSession.cpp rename to libp2p/PeerSession.cpp diff --git a/libethnet/PeerSession.h b/libp2p/PeerSession.h similarity index 100% rename from libethnet/PeerSession.h rename to libp2p/PeerSession.h diff --git a/libethnet/UPnP.cpp b/libp2p/UPnP.cpp similarity index 100% rename from libethnet/UPnP.cpp rename to libp2p/UPnP.cpp diff --git a/libethnet/UPnP.h b/libp2p/UPnP.h similarity index 100% rename from libethnet/UPnP.h rename to libp2p/UPnP.h diff --git a/libpyserpent/pyserpent.cpp b/libpyserpent/pyserpent.cpp index ba3750b51..d97fd8fed 100644 --- a/libpyserpent/pyserpent.cpp +++ b/libpyserpent/pyserpent.cpp @@ -8,8 +8,14 @@ #define PYMETHOD(name, FROM, method, TO) \ static PyObject * name(PyObject *, PyObject *args) { \ + try { \ FROM(med) \ return TO(method(med)); \ + } \ + catch (std::string e) { \ + PyErr_SetString(PyExc_Exception, e.c_str()); \ + return NULL; \ + } \ } #define FROMSTR(v) \ @@ -149,7 +155,7 @@ static PyMethodDef PyextMethods[] = { {NULL, NULL, 0, NULL} /* Sentinel */ }; -PyMODINIT_FUNC initpyext(void) +PyMODINIT_FUNC initserpent_pyext(void) { - Py_InitModule( "pyext", PyextMethods ); + Py_InitModule( "serpent_pyext", PyextMethods ); } diff --git a/libqethereum/QEthereum.cpp b/libqethereum/QEthereum.cpp index 96b06ec89..96963f6ff 100644 --- a/libqethereum/QEthereum.cpp +++ b/libqethereum/QEthereum.cpp @@ -23,7 +23,7 @@ using eth::Client; using eth::Instruction; using eth::KeyPair; using eth::NodeMode; -using eth::PeerInfo; +using p2p::PeerInfo; using eth::RLP; using eth::Secret; using eth::Transaction; diff --git a/libqethereum/QmlEthereum.cpp b/libqethereum/QmlEthereum.cpp index 80ed891eb..3896382fd 100644 --- a/libqethereum/QmlEthereum.cpp +++ b/libqethereum/QmlEthereum.cpp @@ -26,7 +26,7 @@ using eth::Client; using eth::Instruction; using eth::KeyPair; using eth::NodeMode; -using eth::PeerInfo; +using p2p::PeerInfo; using eth::RLP; using eth::Secret; using eth::Transaction; diff --git a/libserpent/bignum.cpp b/libserpent/bignum.cpp index 29315b871..877808ead 100644 --- a/libserpent/bignum.cpp +++ b/libserpent/bignum.cpp @@ -5,9 +5,9 @@ #include "bignum.h" //Integer to string conversion -std::string intToDecimal(int branch) { +std::string unsignedToDecimal(unsigned branch) { if (branch < 10) return nums.substr(branch, 1); - else return intToDecimal(branch / 10) + nums.substr(branch % 10,1); + else return unsignedToDecimal(branch / 10) + nums.substr(branch % 10,1); } //Add two strings representing decimal values @@ -91,8 +91,8 @@ std::string decimalMod(std::string a, std::string b) { } //String to int conversion -int decimalToInt(std::string a) { +unsigned decimalToUnsigned(std::string a) { if (a.size() == 0) return 0; else return (a[a.size() - 1] - '0') - + decimalToInt(a.substr(0,a.size()-1)) * 10; + + decimalToUnsigned(a.substr(0,a.size()-1)) * 10; } diff --git a/libserpent/bignum.h b/libserpent/bignum.h index a12929752..6656fdaec 100644 --- a/libserpent/bignum.h +++ b/libserpent/bignum.h @@ -11,7 +11,7 @@ const std::string tt255 = "57896044618658097711785492504343953926634992332820282019728792003956564819968" ; -std::string intToDecimal(int branch); +std::string unsignedToDecimal(unsigned branch); std::string decimalAdd(std::string a, std::string b); @@ -25,6 +25,6 @@ std::string decimalMod(std::string a, std::string b); bool decimalGt(std::string a, std::string b, bool eqAllowed=false); -int decimalToInt(std::string a); +unsigned decimalToUnsigned(std::string a); #endif diff --git a/libserpent/compiler.cpp b/libserpent/compiler.cpp index 959d2993b..4360bfba6 100644 --- a/libserpent/compiler.cpp +++ b/libserpent/compiler.cpp @@ -17,6 +17,7 @@ struct programAux { struct programData { programAux aux; Node code; + int outs; }; programAux Aux() { @@ -27,10 +28,11 @@ programAux Aux() { return o; } -programData pd(programAux aux = Aux(), Node code=token("_")) { +programData pd(programAux aux = Aux(), Node code=token("_"), int outs=0) { programData o; o.aux = aux; o.code = code; + o.outs = outs; return o; } @@ -44,151 +46,249 @@ Node multiToken(Node nodes[], int len, Metadata met) { Node finalize(programData c); +Node popwrap(Node node) { + Node nodelist[] = { + node, + token("POP", node.metadata) + }; + return multiToken(nodelist, 2, node.metadata); +} + // Turns LLL tree into tree of code fragments -programData opcodeify(Node node, programAux aux=Aux()) { +programData opcodeify(Node node, + programAux aux=Aux(), + int height=0, + std::map dupvars= + std::map()) { std::string symb = "_"+mkUniqueToken(); Metadata m = node.metadata; // Numbers if (node.type == TOKEN) { - return pd(aux, nodeToNumeric(node)); + return pd(aux, nodeToNumeric(node), 1); } else if (node.val == "ref" || node.val == "get" || node.val == "set") { std::string varname = node.args[0].val; if (!aux.vars.count(varname)) { - aux.vars[varname] = intToDecimal(aux.vars.size() * 32); + aux.vars[varname] = unsignedToDecimal(aux.vars.size() * 32); } - if (varname == "msg.data") aux.calldataUsed = true; + if (varname == "'msg.data") aux.calldataUsed = true; // Set variable if (node.val == "set") { - programData sub = opcodeify(node.args[1], aux); - Node nodelist[] = { - sub.code, - token(aux.vars[varname], m), - token("MSTORE", m), - }; - return pd(sub.aux, multiToken(nodelist, 3, m)); + programData sub = opcodeify(node.args[1], aux, height, dupvars); + if (!sub.outs) + err("Value to set variable must have nonzero arity!", m); + if (dupvars.count(node.args[0].val)) { + int h = height - dupvars[node.args[0].val]; + if (h > 16) err("Too deep for stack variable (max 16)", m); + Node nodelist[] = { + sub.code, + token("SWAP"+unsignedToDecimal(h), m), + token("POP", m) + }; + return pd(sub.aux, multiToken(nodelist, 3, m), 0); + } + Node nodelist[] = { + sub.code, + token(sub.aux.vars[varname], m), + token("MSTORE", m), + }; + return pd(sub.aux, multiToken(nodelist, 3, m), 0); } // Get variable else if (node.val == "get") { - Node nodelist[] = - { token(aux.vars[varname], m), token("MLOAD", m) }; - return pd(aux, multiToken(nodelist, 2, m)); + if (dupvars.count(node.args[0].val)) { + int h = height - dupvars[node.args[0].val]; + if (h > 16) err("Too deep for stack variable (max 16)", m); + return pd(aux, token("DUP"+unsignedToDecimal(h)), 1); + } + Node nodelist[] = + { token(aux.vars[varname], m), token("MLOAD", m) }; + return pd(aux, multiToken(nodelist, 2, m), 1); } // Refer variable - else return pd(aux, token(aux.vars[varname], m)); + else { + if (dupvars.count(node.args[0].val)) + err("Cannot ref stack variable!", m); + return pd(aux, token(aux.vars[varname], m), 1); + } } // Code blocks if (node.val == "lll" && node.args.size() == 2) { if (node.args[1].val != "0") aux.allocUsed = true; std::vector o; o.push_back(finalize(opcodeify(node.args[0]))); - programData sub = opcodeify(node.args[1], aux); + programData sub = opcodeify(node.args[1], aux, height, dupvars); Node code = astnode("____CODE", o, m); Node nodelist[] = { - token("$begincode"+symb+".endcode"+symb, m), token("DUP", m), + token("$begincode"+symb+".endcode"+symb, m), token("DUP1", m), token("$begincode"+symb, m), sub.code, token("CODECOPY", m), token("$endcode"+symb, m), token("JUMP", m), token("~begincode"+symb, m), code, token("~endcode"+symb, m) }; - return pd(sub.aux, multiToken(nodelist, 10, m)); + return pd(sub.aux, multiToken(nodelist, 10, m), 1); } - std::vector subs; - for (unsigned i = 0; i < node.args.size(); i++) { - programData sub = opcodeify(node.args[i], aux); - aux = sub.aux; - subs.push_back(sub.code); - } - // Debug - if (node.val == "debug") { + // Stack variables + if (node.val == "with") { + std::map dupvars2 = dupvars; + dupvars2[node.args[0].val] = height; + programData initial = opcodeify(node.args[1], aux, height, dupvars); + if (!initial.outs) + err("Initial variable value must have nonzero arity!", m); + programData sub = opcodeify(node.args[2], initial.aux, height + 1, dupvars2); Node nodelist[] = { - subs[0], - token("DUP", m), token("POP", m), token("POP", m) + initial.code, + sub.code }; - return pd(aux, multiToken(nodelist, 4, m)); + programData o = pd(sub.aux, multiToken(nodelist, 2, m), sub.outs); + if (sub.outs) + o.code.args.push_back(token("SWAP1", m)); + o.code.args.push_back(token("POP", m)); + return o; } // Seq of multiple statements if (node.val == "seq") { - return pd(aux, astnode("_", subs, m)); + std::vector children; + int lastOut = 0; + for (unsigned i = 0; i < node.args.size(); i++) { + programData sub = opcodeify(node.args[i], aux, height, dupvars); + aux = sub.aux; + if (sub.outs == 1) { + if (i < node.args.size() - 1) sub.code = popwrap(sub.code); + else lastOut = 1; + } + children.push_back(sub.code); + } + return pd(aux, astnode("_", children, m), lastOut); } // 2-part conditional (if gets rewritten to unless in rewrites) else if (node.val == "unless" && node.args.size() == 2) { + programData cond = opcodeify(node.args[0], aux, height, dupvars); + programData action = opcodeify(node.args[1], cond.aux, height, dupvars); + aux = action.aux; + if (!cond.outs) err("Condition of if/unless statement has arity 0", m); + if (action.outs) action.code = popwrap(action.code); Node nodelist[] = { - subs[0], + cond.code, token("$endif"+symb, m), token("JUMPI", m), - subs[1], + action.code, token("~endif"+symb, m) }; - return pd(aux, multiToken(nodelist, 5, m)); + return pd(aux, multiToken(nodelist, 5, m), 0); } // 3-part conditional else if (node.val == "if" && node.args.size() == 3) { + programData ifd = opcodeify(node.args[0], aux, height, dupvars); + programData thend = opcodeify(node.args[1], ifd.aux, height, dupvars); + programData elsed = opcodeify(node.args[2], thend.aux, height, dupvars); + aux = elsed.aux; + if (!ifd.outs) + err("Condition of if/unless statement has arity 0", m); + // Handle cases where one conditional outputs something + // and the other does not + int outs = (thend.outs && elsed.outs) ? 1 : 0; + if (thend.outs > outs) thend.code = popwrap(thend.code); + if (elsed.outs > outs) elsed.code = popwrap(elsed.code); Node nodelist[] = { - subs[0], + ifd.code, token("NOT", m), token("$else"+symb, m), token("JUMPI", m), - subs[1], + thend.code, token("$endif"+symb, m), token("JUMP", m), token("~else"+symb, m), - subs[2], + elsed.code, token("~endif"+symb, m) }; - return pd(aux, multiToken(nodelist, 10, m)); + return pd(aux, multiToken(nodelist, 10, m), outs); } // While (rewritten to this in rewrites) else if (node.val == "until") { + programData cond = opcodeify(node.args[0], aux, height, dupvars); + programData action = opcodeify(node.args[1], cond.aux, height, dupvars); + aux = action.aux; + if (!cond.outs) + err("Condition of while/until loop has arity 0", m); + if (action.outs) action.code = popwrap(action.code); Node nodelist[] = { token("~beg"+symb, m), - subs[0], + cond.code, token("$end"+symb, m), token("JUMPI", m), - subs[1], + action.code, token("$beg"+symb, m), token("JUMP", m), token("~end"+symb, m) }; return pd(aux, multiToken(nodelist, 8, m)); } // Memory allocations else if (node.val == "alloc") { + programData bytez = opcodeify(node.args[0], aux, height, dupvars); + aux = bytez.aux; + if (!bytez.outs) + err("Alloc input has arity 0", m); aux.allocUsed = true; Node nodelist[] = { - subs[0], - token("MSIZE", m), token("SWAP", m), token("MSIZE", m), + bytez.code, + token("MSIZE", m), token("SWAP1", m), token("MSIZE", m), token("ADD", m), - token("0", m), token("SWAP", m), token("MSTORE", m) + token("0", m), token("SWAP1", m), token("MSTORE", m) }; - return pd(aux, multiToken(nodelist, 8, m)); + return pd(aux, multiToken(nodelist, 8, m), 1); } // Array literals else if (node.val == "array_lit") { aux.allocUsed = true; std::vector nodes; - if (!subs.size()) { + if (!node.args.size()) { nodes.push_back(token("MSIZE", m)); return pd(aux, astnode("_", nodes, m)); } nodes.push_back(token("MSIZE", m)); nodes.push_back(token("0", m)); nodes.push_back(token("MSIZE", m)); - nodes.push_back(token(intToDecimal(subs.size() * 32 - 1), m)); + nodes.push_back(token(unsignedToDecimal(node.args.size() * 32 - 1), m)); nodes.push_back(token("ADD", m)); nodes.push_back(token("MSTORE8", m)); - for (unsigned i = 0; i < subs.size(); i++) { - nodes.push_back(token("DUP", m)); - nodes.push_back(subs[i]); - nodes.push_back(token("SWAP", m)); + for (unsigned i = 0; i < node.args.size(); i++) { + Metadata m2 = node.args[i].metadata; + nodes.push_back(token("DUP1", m2)); + programData sub = opcodeify(node.args[i], aux, height + 2, dupvars); + if (!sub.outs) + err("Array_lit item " + unsignedToDecimal(i) + " has zero arity", m2); + aux = sub.aux; + nodes.push_back(sub.code); + nodes.push_back(token("SWAP1", m2)); if (i > 0) { - nodes.push_back(token(intToDecimal(i * 32), m)); - nodes.push_back(token("ADD", m)); + nodes.push_back(token(unsignedToDecimal(i * 32), m2)); + nodes.push_back(token("ADD", m2)); } - nodes.push_back(token("MSTORE", m)); + nodes.push_back(token("MSTORE", m2)); } - return pd(aux, astnode("_", nodes, m)); + return pd(aux, astnode("_", nodes, m), 1); } // All other functions/operators else { - std::vector subs2; - while (subs.size()) { - subs2.push_back(subs.back()); - subs.pop_back(); + std::vector subs2; + int depth = opinputs(upperCase(node.val)); + if (node.val != "debug") { + if (depth == -1) + err("Not a function or opcode: "+node.val, m); + if ((int)node.args.size() != depth) + err("Invalid arity for "+node.val, m); + } + for (int i = node.args.size() - 1; i >= 0; i--) { + programData sub = opcodeify(node.args[i], + aux, + height - i - 1 + node.args.size(), + dupvars); + aux = sub.aux; + if (!sub.outs) + err("Input "+unsignedToDecimal(i)+" has arity 0", sub.code.metadata); + subs2.push_back(sub.code); + } + if (node.val == "debug") { + subs2.push_back(token("DUP"+unsignedToDecimal(node.args.size()), m)); + for (int i = 0; i <= (int)node.args.size(); i++) + subs2.push_back(token("POP", m)); } - subs2.push_back(token(upperCase(node.val), m)); - return pd(aux, astnode("_", subs2, m)); + else subs2.push_back(token(upperCase(node.val), m)); + int outdepth = node.val == "debug" ? 0 : opoutputs(upperCase(node.val)); + return pd(aux, astnode("_", subs2, m), outdepth); } } @@ -201,7 +301,7 @@ Node finalize(programData c) { if (c.aux.allocUsed && c.aux.vars.size() > 0) { Node nodelist[] = { token("0", m), - token(intToDecimal(c.aux.vars.size() * 32 - 1)), + token(unsignedToDecimal(c.aux.vars.size() * 32 - 1)), token("MSTORE8", m) }; bottom.push_back(multiToken(nodelist, 3, m)); @@ -211,7 +311,7 @@ Node finalize(programData c) { Node nodelist[] = { token("MSIZE", m), token("CALLDATASIZE", m), token("MSIZE", m), token("0", m), token("CALLDATACOPY", m), - token(c.aux.vars["msg.data"], m), token("MSTORE", m) + token(c.aux.vars["'msg.data"], m), token("MSTORE", m) }; bottom.push_back(multiToken(nodelist, 7, m)); } @@ -235,7 +335,7 @@ programAux buildDict(Node program, programAux aux, int labelLength) { aux.step += 1 + toByteArr(program.val, m).size(); } else if (program.val[0] == '~') { - aux.vars[program.val.substr(1)] = intToDecimal(aux.step); + aux.vars[program.val.substr(1)] = unsignedToDecimal(aux.step); } else if (program.val[0] == '$') { aux.step += labelLength + 1; @@ -245,7 +345,7 @@ programAux buildDict(Node program, programAux aux, int labelLength) { // A sub-program (ie. LLL) else if (program.val == "____CODE") { programAux auks = Aux(); - for (unsigned i = 0; i < program.args.size(); i++) { + for (unsigned i = 0; i < program.args.size(); i++) { auks = buildDict(program.args[i], auks, labelLength); } for (std::map::iterator it=auks.vars.begin(); @@ -257,7 +357,7 @@ programAux buildDict(Node program, programAux aux, int labelLength) { } // Normal sub-block else { - for (unsigned i = 0; i < program.args.size(); i++) { + for (unsigned i = 0; i < program.args.size(); i++) { aux = buildDict(program.args[i], aux, labelLength); } } @@ -271,7 +371,7 @@ Node substDict(Node program, programAux aux, int labelLength) { std::vector inner; if (program.type == TOKEN) { if (program.val[0] == '$') { - std::string tokStr = "PUSH"+intToDecimal(labelLength); + std::string tokStr = "PUSH"+unsignedToDecimal(labelLength); out.push_back(token(tokStr, m)); int dotLoc = program.val.find('.'); if (dotLoc == -1) { @@ -289,13 +389,13 @@ Node substDict(Node program, programAux aux, int labelLength) { else if (program.val[0] == '~') { } else if (isNumberLike(program)) { inner = toByteArr(program.val, m); - out.push_back(token("PUSH"+intToDecimal(inner.size()))); + out.push_back(token("PUSH"+unsignedToDecimal(inner.size()))); out.push_back(astnode("_", inner, m)); } else return program; } else { - for (unsigned i = 0; i < program.args.size(); i++) { + for (unsigned i = 0; i < program.args.size(); i++) { Node n = substDict(program.args[i], aux, labelLength); if (n.type == TOKEN || n.args.size()) out.push_back(n); } @@ -319,9 +419,9 @@ std::vector flatten(Node derefed) { o.push_back(derefed); } else { - for (unsigned i = 0; i < derefed.args.size(); i++) { + for (unsigned i = 0; i < derefed.args.size(); i++) { std::vector oprime = flatten(derefed.args[i]); - for (unsigned j = 0; j < oprime.size(); j++) o.push_back(oprime[j]); + for (unsigned j = 0; j < oprime.size(); j++) o.push_back(oprime[j]); } } return o; @@ -330,13 +430,13 @@ std::vector flatten(Node derefed) { // Opcodes -> bin std::string serialize(std::vector codons) { std::string o; - for (unsigned i = 0; i < codons.size(); i++) { + for (unsigned i = 0; i < codons.size(); i++) { int v; if (isNumberLike(codons[i])) { - v = decimalToInt(codons[i].val); + v = decimalToUnsigned(codons[i].val); } else if (codons[i].val.substr(0,4) == "PUSH") { - v = 95 + decimalToInt(codons[i].val.substr(4)); + v = 95 + decimalToUnsigned(codons[i].val.substr(4)); } else { v = opcode(codons[i].val); @@ -350,14 +450,14 @@ std::string serialize(std::vector codons) { std::vector deserialize(std::string ser) { std::vector o; int backCount = 0; - for (unsigned i = 0; i < ser.length(); i++) { + for (unsigned i = 0; i < ser.length(); i++) { unsigned char v = (unsigned char)ser[i]; std::string oper = op((int)v); if (oper != "" && backCount <= 0) o.push_back(token(oper)); else if (v >= 96 && v < 128 && backCount <= 0) { - o.push_back(token("PUSH"+intToDecimal(v - 95))); + o.push_back(token("PUSH"+unsignedToDecimal(v - 95))); } - else o.push_back(token(intToDecimal(v))); + else o.push_back(token(unsignedToDecimal(v))); if (v >= 96 && v < 128 && backCount <= 0) { backCount = v - 95; } @@ -389,10 +489,10 @@ std::vector prettyCompileLLL(Node program) { // Converts a list of integer values to binary transaction data std::string encodeDatalist(std::vector vals) { std::string o; - for (unsigned i = 0; i < vals.size(); i++) { + for (unsigned i = 0; i < vals.size(); i++) { std::vector n = toByteArr(strToNumeric(vals[i]), Metadata(), 32); - for (unsigned j = 0; j < n.size(); j++) { - int v = decimalToInt(n[j].val); + for (unsigned j = 0; j < n.size(); j++) { + int v = decimalToUnsigned(n[j].val); o += (char)v; } } @@ -402,11 +502,11 @@ std::string encodeDatalist(std::vector vals) { // Converts binary transaction data into a list of integer values std::vector decodeDatalist(std::string ser) { std::vector out; - for (unsigned i = 0; i < ser.length(); i+= 32) { + for (unsigned i = 0; i < ser.length(); i+= 32) { std::string o = "0"; for (unsigned j = i; j < i + 32; j++) { int vj = (int)(unsigned char)ser[j]; - o = decimalAdd(decimalMul(o, "256"), intToDecimal(vj)); + o = decimalAdd(decimalMul(o, "256"), unsignedToDecimal(vj)); } out.push_back(o); } diff --git a/libserpent/opcodes.h b/libserpent/opcodes.h index 6b42df97a..f55834efa 100644 --- a/libserpent/opcodes.h +++ b/libserpent/opcodes.h @@ -6,86 +6,124 @@ #include #include -std::map opcodes; +class Mapping { + public: + Mapping(std::string Op, int Opcode, int In, int Out) { + op = Op; + opcode = Opcode; + in = In; + out = Out; + } + std::string op; + int opcode; + int in; + int out; +}; + +Mapping mapping[] = { + Mapping("STOP", 0x00, 0, 0), + Mapping("ADD", 0x01, 2, 1), + Mapping("MUL", 0x02, 2, 1), + Mapping("SUB", 0x03, 2, 1), + Mapping("DIV", 0x04, 2, 1), + Mapping("SDIV", 0x05, 2, 1), + Mapping("MOD", 0x06, 2, 1), + Mapping("SMOD", 0x07, 2, 1), + Mapping("EXP", 0x08, 2, 1), + Mapping("NEG", 0x09, 1, 1), + Mapping("LT", 0x0a, 2, 1), + Mapping("GT", 0x0b, 2, 1), + Mapping("SLT", 0x0c, 2, 1), + Mapping("SGT", 0x0d, 2, 1), + Mapping("EQ", 0x0e, 2, 1), + Mapping("NOT", 0x0f, 1, 1), + Mapping("AND", 0x10, 2, 1), + Mapping("OR", 0x11, 2, 1), + Mapping("XOR", 0x12, 2, 1), + Mapping("BYTE", 0x13, 2, 1), + Mapping("ADDMOD", 0x14, 3, 1), + Mapping("MULMOD", 0x15, 3, 1), + Mapping("SHA3", 0x20, 2, 1), + Mapping("ADDRESS", 0x30, 0, 1), + Mapping("BALANCE", 0x31, 1, 1), + Mapping("ORIGIN", 0x32, 0, 1), + Mapping("CALLER", 0x33, 0, 1), + Mapping("CALLVALUE", 0x34, 0, 1), + Mapping("CALLDATALOAD", 0x35, 1, 1), + Mapping("CALLDATASIZE", 0x36, 0, 1), + Mapping("CALLDATACOPY", 0x37, 3, 1), + Mapping("CODESIZE", 0x38, 0, 1), + Mapping("CODECOPY", 0x39, 3, 1), + Mapping("GASPRICE", 0x3a, 0, 1), + Mapping("PREVHASH", 0x40, 0, 1), + Mapping("COINBASE", 0x41, 0, 1), + Mapping("TIMESTAMP", 0x42, 0, 1), + Mapping("NUMBER", 0x43, 0, 1), + Mapping("DIFFICULTY", 0x44, 0, 1), + Mapping("GASLIMIT", 0x45, 0, 1), + Mapping("POP", 0x50, 1, 0), + Mapping("MLOAD", 0x53, 1, 1), + Mapping("MSTORE", 0x54, 2, 0), + Mapping("MSTORE8", 0x55, 2, 0), + Mapping("SLOAD", 0x56, 1, 1), + Mapping("SSTORE", 0x57, 2, 0), + Mapping("JUMP", 0x58, 1, 0), + Mapping("JUMPI", 0x59, 2, 0), + Mapping("PC", 0x5a, 0, 1), + Mapping("MSIZE", 0x5b, 0, 1), + Mapping("GAS", 0x5c, 0, 1), + Mapping("CREATE", 0xf0, 3, 1), + Mapping("CALL", 0xf1, 7, 1), + Mapping("RETURN", 0xf2, 2, 0), + Mapping("POST", 0xf3, 5, 0), + Mapping("CALL_STATELESS", 0xf4, 7, 1), + Mapping("SUICIDE", 0xff, 1, 0), + Mapping("---END---", 0x00, 0, 0), +}; + +std::map > opcodes; std::map reverseOpcodes; // Fetches everything EXCEPT PUSH1..32 -std::pair _opcode(std::string ops, int opi) { +std::pair > _opdata(std::string ops, int opi) { if (!opcodes.size()) { - opcodes["STOP"] = 0x00; - opcodes["ADD"] = 0x01; - opcodes["MUL"] = 0x02; - opcodes["SUB"] = 0x03; - opcodes["DIV"] = 0x04; - opcodes["SDIV"] = 0x05; - opcodes["MOD"] = 0x06; - opcodes["SMOD"] = 0x07; - opcodes["EXP"] = 0x08; - opcodes["NEG"] = 0x09; - opcodes["LT"] = 0x0a; - opcodes["GT"] = 0x0b; - opcodes["SLT"] = 0x0c; - opcodes["SGT"] = 0x0d; - opcodes["EQ"] = 0x0e; - opcodes["NOT"] = 0x0f; - opcodes["AND"] = 0x10; - opcodes["OR"] = 0x11; - opcodes["XOR"] = 0x12; - opcodes["BYTE"] = 0x13; - opcodes["SHA3"] = 0x20; - opcodes["ADDRESS"] = 0x30; - opcodes["BALANCE"] = 0x31; - opcodes["ORIGIN"] = 0x32; - opcodes["CALLER"] = 0x33; - opcodes["CALLVALUE"] = 0x34; - opcodes["CALLDATALOAD"] = 0x35; - opcodes["CALLDATASIZE"] = 0x36; - opcodes["CALLDATACOPY"] = 0x37; - opcodes["CODESIZE"] = 0x38; - opcodes["CODECOPY"] = 0x39; - opcodes["GASPRICE"] = 0x3a; - opcodes["PREVHASH"] = 0x40; - opcodes["COINBASE"] = 0x41; - opcodes["TIMESTAMP"] = 0x42; - opcodes["NUMBER"] = 0x43; - opcodes["DIFFICULTY"] = 0x44; - opcodes["GASLIMIT"] = 0x45; - opcodes["POP"] = 0x50; - opcodes["DUP"] = 0x51; - opcodes["SWAP"] = 0x52; - opcodes["MLOAD"] = 0x53; - opcodes["MSTORE"] = 0x54; - opcodes["MSTORE8"] = 0x55; - opcodes["SLOAD"] = 0x56; - opcodes["SSTORE"] = 0x57; - opcodes["JUMP"] = 0x58; - opcodes["JUMPI"] = 0x59; - opcodes["PC"] = 0x5a; - opcodes["MSIZE"] = 0x5b; - opcodes["GAS"] = 0x5c; - opcodes["CREATE"] = 0xf0; - opcodes["CALL"] = 0xf1; - opcodes["RETURN"] = 0xf2; - opcodes["SUICIDE"] = 0xff; - for (std::map::iterator it=opcodes.begin(); + int i = 0; + while (mapping[i].op != "---END---") { + Mapping mi = mapping[i]; + opcodes[mi.op] = triple(mi.opcode, mi.in, mi.out); + i++; + } + for (i = 1; i <= 16; i++) { + opcodes["DUP"+unsignedToDecimal(i)] = triple(0x7f + i, i, i+1); + opcodes["SWAP"+unsignedToDecimal(i)] = triple(0x8f + i, i+1, i+1); + } + for (std::map >::iterator it=opcodes.begin(); it != opcodes.end(); it++) { - reverseOpcodes[(*it).second] = (*it).first; + reverseOpcodes[(*it).second[0]] = (*it).first; } } std::string op; - int opcode; + std::vector opdata; op = reverseOpcodes.count(opi) ? reverseOpcodes[opi] : ""; - opcode = opcodes.count(ops) ? opcodes[ops] : -1; - return std::pair(op, opcode); + opdata = opcodes.count(ops) ? opcodes[ops] : triple(-1, -1, -1); + return std::pair >(op, opdata); } int opcode(std::string op) { - return _opcode(op, 0).second; + return _opdata(op, -1).second[0]; +} + +int opinputs(std::string op) { + return _opdata(op, -1).second[1]; +} + +int opoutputs(std::string op) { + return _opdata(op, -1).second[2]; } std::string op(int opcode) { - return _opcode("", opcode).first; + return _opdata("", opcode).first; } #endif diff --git a/libserpent/parser.cpp b/libserpent/parser.cpp index 497bdb526..5adf1672d 100644 --- a/libserpent/parser.cpp +++ b/libserpent/parser.cpp @@ -85,8 +85,11 @@ std::vector shuntingYard(std::vector tokens) { } oq.push_back(tok); } + else if (toktyp == UNARY_OP) { + stack.push_back(tok); + } // If binary op, keep popping from stack while higher bedmas precedence - else if (toktyp == UNARY_OP || toktyp == BINARY_OP) { + else if (toktyp == BINARY_OP) { if (tok.val == "-" && prevtyp != ALPHANUM && prevtyp != RPAREN) { oq.push_back(token("0", tok.metadata)); } @@ -239,14 +242,16 @@ int spaceCount(std::string s) { // Is this a command that takes an argument on the same line? bool bodied(std::string tok) { - return tok == "if" || tok == "elif" || tok == "while"; + return tok == "if" || tok == "elif" || tok == "while" + || tok == "with" || tok == "def"; } // Is this a command that takes an argument as a child block? bool childBlocked(std::string tok) { return tok == "if" || tok == "elif" || tok == "else" || tok == "code" || tok == "shared" || tok == "init" - || tok == "while" || tok == "repeat" || tok == "for"; + || tok == "while" || tok == "repeat" || tok == "for" + || tok == "with" || tok == "def"; } // Are the two commands meant to continue each other? diff --git a/libserpent/rewriter.cpp b/libserpent/rewriter.cpp index 8b50c9a28..72feb1277 100644 --- a/libserpent/rewriter.cpp +++ b/libserpent/rewriter.cpp @@ -17,8 +17,12 @@ std::string valid[][3] = { { "alloc", "1", "1" }, { "array", "1", "1" }, { "call", "2", "4" }, + { "call_stateless", "2", "4" }, + { "post", "4", "5" }, + { "postcall", "3", "4" }, { "create", "1", "4" }, { "msg", "4", "6" }, + { "msg_stateless", "4", "6" }, { "getch", "2", "2" }, { "setch", "3", "3" }, { "sha3", "1", "2" }, @@ -78,6 +82,10 @@ std::string macros[][2] = { "(access msg.data $ind)", "(calldataload (mul 32 $ind))" }, + { + "(slice $arr $pos)", + "(add $arr (mul 32 $pos))", + }, { "(array $len)", "(alloc (mul 32 $len))" @@ -126,6 +134,22 @@ std::string macros[][2] = { "(send $to $value)", "(call (sub (gas) 25) $to $value 0 0 0 0)" }, + { + "(post $gas $to $value $datain $datainsz)", + "(~post $gas $to $value $datain (mul $datainsz 32))" + }, + { + "(post $gas $to $value $datain)", + "(seq (set $1 $datain) (~post $gas $to $value (ref $1) 32))" + }, + { + "(postcall $gas $to $datain)", + "(post $gas $to 0 $datain)", + }, + { + "(postcall $gas $to $datain $datainsz)", + "(post $gas $to 0 $datain $datainsz)", + }, { "(send $gas $to $value)", "(call $gas $to $value 0 0 0 0)" @@ -156,7 +180,7 @@ std::string macros[][2] = { }, { "(|| $x $y)", - "(seq (set $1 $x) (if (get $1) (get $1) $y))" + "(with $1 $x (if (get $1) (get $1) $y))" }, { "(>= $x $y)", @@ -180,8 +204,9 @@ std::string macros[][2] = { }, { "(create $endowment $code)", - "(seq (set $1 (msize)) (create $endowment (get $1) (lll (outer $code) (msize))))" + "(with $1 (msize) (create $endowment (get $1) (lll (outer $code) (msize))))" }, + // Call and msg { "(call $f $dataval)", "(msg (sub (gas) 45) $f 0 $dataval)" @@ -192,7 +217,7 @@ std::string macros[][2] = { }, { "(call $f $inp $inpsz $outsz)", - "(seq (set $1 $outsz) (set $2 (alloc (mul 32 (get $1)))) (pop (call (sub (gas) (add 25 (get $1))) $f 0 $inp (mul 32 $inpsz) (get $2) (mul 32 (get $1)))) (get $2))" + "(with $1 $outsz (with $2 (alloc (mul 32 (get $1))) (seq (call (sub (gas) (add 25 (get $1))) $f 0 $inp (mul 32 $inpsz) (get $2) (mul 32 (get $1))) (get $2))))" }, { "(msg $gas $to $val $inp $inpsz)", @@ -204,8 +229,34 @@ std::string macros[][2] = { }, { "(msg $gas $to $val $inp $inpsz $outsz)", - "(seq (set $1 (mul 32 $outsz)) (set $2 (alloc (get $1))) (pop (call $gas $to $val $inp (mul 32 $inpsz) (get $2) (get $1))) (get $2))" + "(with $1 (mul 32 $outsz) (with $2 (alloc (get $1)) (call $gas $to $val $inp (mul 32 $inpsz) (get $2) (get $1)) (get $2)))" }, + // Call stateless and msg stateless + { + "(call_stateless $f $dataval)", + "(msg_stateless (sub (gas) 45) $f 0 $dataval)" + }, + { + "(call_stateless $f $inp $inpsz)", + "(msg_stateless (sub (gas) 25) $f 0 $inp $inpsz)" + }, + { + "(call_stateless $f $inp $inpsz $outsz)", + "(with $1 $outsz (with $2 (alloc (mul 32 (get $1))) (seq (call_stateless (sub (gas) (add 25 (get $1))) $f 0 $inp (mul 32 $inpsz) (get $2) (mul 32 (get $1))) (get $2))))" + }, + { + "(msg_stateless $gas $to $val $inp $inpsz)", + "(seq (call_stateless $gas $to $val $inp (mul 32 $inpsz) (ref $1) 32) (get $1))" + }, + { + "(msg_stateless $gas $to $val $dataval)", + "(seq (set $1 $dataval) (call_stateless $gas $to $val (ref $1) 32 (ref $2) 32) (get $2))" + }, + { + "(msg_stateless $gas $to $val $inp $inpsz $outsz)", + "(with $1 (mul 32 $outsz) (with $2 (alloc (get $1)) (call_stateless $gas $to $val $inp (mul 32 $inpsz) (get $2) (get $1)) (get $2)))" + }, + // Wrappers { "(outer (init $init $code))", "(seq $init (~return 0 (lll $code 0)))" @@ -228,7 +279,11 @@ std::string macros[][2] = { }, { "(create $x)", - "(seq (set $1 (msize)) (create $val (get $1) (lll $code (get $1))))" + "(with $1 (msize) (create $val (get $1) (lll $code (get $1))))" + }, + { + "(with (= $var $val) $cond)", + "(with $var $val $cond)" }, { "msg.datasize", "(div (calldatasize) 32)" }, { "msg.sender", "(caller)" }, @@ -253,6 +308,8 @@ std::vector > nodeMacros; std::string synonyms[][2] = { { "or", "||" }, { "and", "&&" }, + { "|", "~or" }, + { "&", "~and" }, { "elif", "if" }, { "!", "not" }, { "string", "alloc" }, @@ -344,7 +401,7 @@ Node subst(Node pattern, Node array_lit_transform(Node node) { std::vector o1; - o1.push_back(token(intToDecimal(node.args.size() * 32), node.metadata)); + o1.push_back(token(unsignedToDecimal(node.args.size() * 32), node.metadata)); std::vector o2; std::string symb = "_temp"+mkUniqueToken()+"_0"; o2.push_back(token(symb, node.metadata)); @@ -357,7 +414,7 @@ Node array_lit_transform(Node node) { o5.push_back(token(symb, node.metadata)); std::vector o6; o6.push_back(astnode("get", o5, node.metadata)); - o6.push_back(token(intToDecimal(i * 32), node.metadata)); + o6.push_back(token(unsignedToDecimal(i * 32), node.metadata)); std::vector o7; o7.push_back(astnode("add", o6)); o7.push_back(node.args[i]); @@ -407,7 +464,8 @@ Node apply_rules(Node node) { node = array_lit_transform(node); if (node.type == ASTNODE) { unsigned i = 0; - if (node.val == "set" || node.val == "ref" || node.val == "get") { + if (node.val == "set" || node.val == "ref" + || node.val == "get" || node.val == "with") { node.args[0].val = "'" + node.args[0].val; i = 1; } @@ -430,7 +488,12 @@ Node apply_rules(Node node) { } Node optimize(Node inp) { - if (inp.type == TOKEN) return tryNumberize(inp); + if (inp.type == TOKEN) { + Node o = tryNumberize(inp); + if (decimalGt(o.val, tt256, true)) + err("Value too large (exceeds 32 bytes or 2^256)", inp.metadata); + return o; + } for (unsigned i = 0; i < inp.args.size(); i++) { inp.args[i] = optimize(inp.args[i]); } @@ -474,10 +537,10 @@ Node validate(Node inp) { int i = 0; while(valid[i][0] != "---END---") { if (inp.val == valid[i][0]) { - if (decimalGt(valid[i][1], intToDecimal(inp.args.size()))) { + if (decimalGt(valid[i][1], unsignedToDecimal(inp.args.size()))) { err("Too few arguments for "+inp.val, inp.metadata); } - if (decimalGt(intToDecimal(inp.args.size()), valid[i][2])) { + if (decimalGt(unsignedToDecimal(inp.args.size()), valid[i][2])) { err("Too many arguments for "+inp.val, inp.metadata); } } diff --git a/libserpent/tokenize.cpp b/libserpent/tokenize.cpp index fc33d5dad..a5d3f1c5b 100644 --- a/libserpent/tokenize.cpp +++ b/libserpent/tokenize.cpp @@ -85,9 +85,10 @@ std::vector tokenize(std::string inp, Metadata metadata, bool lispMode) { } } // Special case the minus sign - if (cur.length() > 1 && cur[cur.length() - 1] == '-') { + if (cur.length() > 1 && (cur.substr(cur.length() - 1) == "-" + || cur.substr(cur.length() - 1) == "!")) { out.push_back(token(cur.substr(0, cur.length() - 1), metadata)); - out.push_back(token("-", metadata)); + out.push_back(token(cur.substr(cur.length() - 1), metadata)); cur = ""; } // Boundary between different char types diff --git a/libserpent/util.cpp b/libserpent/util.cpp index 6ca39de9d..0f7570a18 100644 --- a/libserpent/util.cpp +++ b/libserpent/util.cpp @@ -60,8 +60,8 @@ std::string printAST(Node ast, bool printMetadata) { std::string o = "("; if (printMetadata) { o += ast.metadata.file + " "; - o += intToDecimal(ast.metadata.ln) + " "; - o += intToDecimal(ast.metadata.ch) + ": "; + o += unsignedToDecimal(ast.metadata.ln) + " "; + o += unsignedToDecimal(ast.metadata.ch) + ": "; } o += ast.val; std::vector subs; @@ -132,14 +132,14 @@ std::string strToNumeric(std::string inp) { else if ((inp[0] == '"' && inp[inp.length()-1] == '"') || (inp[0] == '\'' && inp[inp.length()-1] == '\'')) { for (unsigned i = 1; i < inp.length() - 1; i++) { - o = decimalAdd(decimalMul(o,"256"), intToDecimal(inp[i])); + o = decimalAdd(decimalMul(o,"256"), unsignedToDecimal((unsigned char)inp[i])); } } else if (inp.substr(0,2) == "0x") { for (unsigned i = 2; i < inp.length(); i++) { int dig = std::string("0123456789abcdef").find(inp[i]); if (dig < 0) return ""; - o = decimalAdd(decimalMul(o,"16"), intToDecimal(dig)); + o = decimalAdd(decimalMul(o,"16"), unsignedToDecimal(dig)); } } else { @@ -188,7 +188,7 @@ int counter = 0; //Makes a unique token std::string mkUniqueToken() { counter++; - return intToDecimal(counter); + return unsignedToDecimal(counter); } //Does a file exist? http://stackoverflow.com/questions/12774207 @@ -217,7 +217,7 @@ std::string get_file_contents(std::string filename) //Report error void err(std::string errtext, Metadata met) { std::string err = "Error (file \"" + met.file + "\", line " + - intToDecimal(met.ln) + ", char " + intToDecimal(met.ch) + + unsignedToDecimal(met.ln + 1) + ", char " + unsignedToDecimal(met.ch) + "): " + errtext; std::cerr << err << "\n"; throw(err); @@ -254,3 +254,10 @@ std::string upperCase(std::string inp) { } return o; } + +//Three-int vector +std::vector triple(int a, int b, int c) { + std::vector o; + o.push_back(a); o.push_back(b); o.push_back(c); + return o; +} diff --git a/libserpent/util.h b/libserpent/util.h index a593d7451..4fb19bb98 100644 --- a/libserpent/util.h +++ b/libserpent/util.h @@ -103,4 +103,7 @@ std::string hexToBin(std::string inp); //Lower to upper std::string upperCase(std::string inp); +//Three-int vector +std::vector triple(int a, int b, int c); + #endif diff --git a/libwhisper/CMakeLists.txt b/libwhisper/CMakeLists.txt index 36c773979..c85853162 100644 --- a/libwhisper/CMakeLists.txt +++ b/libwhisper/CMakeLists.txt @@ -17,10 +17,9 @@ file(GLOB HEADERS "*.h") include_directories(..) -target_link_libraries(${EXECUTABLE} evm) -target_link_libraries(${EXECUTABLE} lll) -target_link_libraries(${EXECUTABLE} ethential) target_link_libraries(${EXECUTABLE} ethcore) +target_link_libraries(${EXECUTABLE} ethential) +target_link_libraries(${EXECUTABLE} p2p) target_link_libraries(${EXECUTABLE} secp256k1) if(MINIUPNPC_LS) target_link_libraries(${EXECUTABLE} ${MINIUPNPC_LS}) diff --git a/libwhisper/Common.cpp b/libwhisper/Common.cpp index e3a355a8f..73420e32a 100644 --- a/libwhisper/Common.cpp +++ b/libwhisper/Common.cpp @@ -23,5 +23,6 @@ using namespace std; using namespace eth; +using namespace p2p; using namespace shh; diff --git a/libwhisper/Common.h b/libwhisper/Common.h index f3e2e78cb..c669d6ea9 100644 --- a/libwhisper/Common.h +++ b/libwhisper/Common.h @@ -26,7 +26,7 @@ #include #include #include -#include +#include namespace shh { diff --git a/libwhisper/WhisperPeer.cpp b/libwhisper/WhisperPeer.cpp index b59ae9c4a..f355e5df7 100644 --- a/libwhisper/WhisperPeer.cpp +++ b/libwhisper/WhisperPeer.cpp @@ -22,7 +22,7 @@ #include "WhisperPeer.h" #include -#include +#include using namespace std; using namespace eth; using namespace p2p; diff --git a/neth/main.cpp b/neth/main.cpp index c22c9a22c..dbb4ed4b6 100644 --- a/neth/main.cpp +++ b/neth/main.cpp @@ -49,6 +49,7 @@ using namespace std; using namespace eth; +using namespace p2p; using namespace boost::algorithm; using eth::Instruction; diff --git a/sc/cmdline.cpp b/sc/cmdline.cpp index 232cbfeec..69e96b41e 100644 --- a/sc/cmdline.cpp +++ b/sc/cmdline.cpp @@ -64,7 +64,7 @@ int main(int argv, char** argc) { std::cout << assemble(parseLLL(input, true)) << "\n"; } else if (command == "serialize") { - std::cout << binToHex(serialize(tokenize(input))) << "\n"; + std::cout << binToHex(serialize(tokenize(input, Metadata(), false))) << "\n"; } else if (command == "flatten") { std::cout << printTokens(flatten(parseLLL(input, true))) << "\n"; @@ -96,7 +96,7 @@ int main(int argv, char** argc) { else if (command == "biject") { if (argv == 3) std::cerr << "Not enough arguments for biject\n"; - int pos = decimalToInt(secondInput); + int pos = decimalToUnsigned(secondInput); std::vector n = prettyCompile(input); if (pos >= (int)n.size()) std::cerr << "Code position too high\n"; diff --git a/test/peer.cpp b/test/peer.cpp index 16f8383e8..8db4cc129 100644 --- a/test/peer.cpp +++ b/test/peer.cpp @@ -22,10 +22,10 @@ #include #include -#include +#include using namespace std; using namespace eth; -using boost::asio::ip::tcp; +using namespace p2p; int peerTest(int argc, char** argv) { diff --git a/third/MainWin.cpp b/third/MainWin.cpp index 3bebdb7dc..22ce25b66 100644 --- a/third/MainWin.cpp +++ b/third/MainWin.cpp @@ -57,7 +57,7 @@ using eth::Instruction; using eth::KeyPair; using eth::NodeMode; using eth::BlockChain; -using eth::PeerInfo; +using p2p::PeerInfo; using eth::RLP; using eth::Secret; using eth::Transaction; diff --git a/walleth/MainWin.cpp b/walleth/MainWin.cpp index 7b8873d45..8abd9ba60 100644 --- a/walleth/MainWin.cpp +++ b/walleth/MainWin.cpp @@ -31,7 +31,7 @@ using eth::Client; using eth::Instruction; using eth::KeyPair; using eth::NodeMode; -using eth::PeerInfo; +using p2p::PeerInfo; using eth::RLP; using eth::Secret; using eth::Transaction; From d171557267eb653f2cb4a9dc1e8c3d123ff748d4 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 3 Sep 2014 20:18:22 +0200 Subject: [PATCH 152/223] Add missing. --- libp2p/All.h | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 libp2p/All.h diff --git a/libp2p/All.h b/libp2p/All.h new file mode 100644 index 000000000..55a5423b7 --- /dev/null +++ b/libp2p/All.h @@ -0,0 +1,6 @@ +#pragma once + +#include "Common.h" +#include "PeerHost.h" +#include "PeerSession.h" + From a3740efebb0cdfc08fcff06d56210647fb3afa16 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 3 Sep 2014 21:06:36 +0200 Subject: [PATCH 153/223] Repot. No memsize increase on 0. --- exp/main.cpp | 2 +- libethereum/Client.cpp | 6 +- libethereum/Client.h | 2 +- libethereum/CommonNet.h | 2 +- libethereum/EthereumHost.cpp | 4 +- libethereum/EthereumPeer.cpp | 8 +-- libethereum/EthereumPeer.h | 6 +- libevm/VM.h | 14 +++-- libp2p/All.h | 6 +- libp2p/Capability.cpp | 58 +++++++++++++++++ libp2p/Capability.h | 62 ++++++++++++++++++ libp2p/Common.cpp | 51 --------------- libp2p/Common.h | 83 ++----------------------- libp2p/{PeerHost.cpp => Host.cpp} | 65 +++++++++---------- libp2p/{PeerHost.h => Host.h} | 20 +++--- libp2p/HostCapability.cpp | 44 +++++++++++++ libp2p/HostCapability.h | 70 +++++++++++++++++++++ libp2p/{PeerSession.cpp => Session.cpp} | 41 ++++++------ libp2p/{PeerSession.h => Session.h} | 16 ++--- libwhisper/Common.h | 2 +- libwhisper/WhisperPeer.cpp | 4 +- libwhisper/WhisperPeer.h | 8 +-- test/peer.cpp | 4 +- 23 files changed, 347 insertions(+), 231 deletions(-) create mode 100644 libp2p/Capability.cpp create mode 100644 libp2p/Capability.h rename libp2p/{PeerHost.cpp => Host.cpp} (86%) rename libp2p/{PeerHost.h => Host.h} (86%) create mode 100644 libp2p/HostCapability.cpp create mode 100644 libp2p/HostCapability.h rename libp2p/{PeerSession.cpp => Session.cpp} (92%) rename libp2p/{PeerSession.h => Session.h} (87%) diff --git a/exp/main.cpp b/exp/main.cpp index eeff5a55f..6c3e21af6 100644 --- a/exp/main.cpp +++ b/exp/main.cpp @@ -320,7 +320,7 @@ int main(int argc, char** argv) remoteHost = argv[i]; } - PeerHost ph("Test", listenPort, "", false, true); + Host ph("Test", listenPort, "", false, true); ph.registerCapability(new WhisperHost()); auto wh = ph.cap(); diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index e3598ec57..872892b23 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -25,7 +25,7 @@ #include #include #include -#include +#include #include "Defaults.h" #include "EthereumHost.h" using namespace std; @@ -215,13 +215,13 @@ void Client::startNetwork(unsigned short _listenPort, std::string const& _seedHo try { - m_net.reset(new PeerHost(m_clientVersion, _listenPort, _publicIP, _upnp)); + m_net.reset(new Host(m_clientVersion, _listenPort, _publicIP, _upnp)); } catch (std::exception const&) { // Probably already have the port open. cwarn << "Could not initialize with specified/default port. Trying system-assigned port"; - m_net.reset(new PeerHost(m_clientVersion, 0, _publicIP, _upnp)); + m_net.reset(new Host(m_clientVersion, 0, _publicIP, _upnp)); } if (_mode == NodeMode::Full) m_net->registerCapability(new EthereumHost(m_bc, _networkId)); diff --git a/libethereum/Client.h b/libethereum/Client.h index 64e26ec12..4eb65b7ba 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -305,7 +305,7 @@ private: std::unique_ptr m_workNet; ///< The network thread. std::atomic m_workNetState; mutable boost::shared_mutex x_net; ///< Lock for the network existance. - std::unique_ptr m_net; ///< Should run in background and send us events when blocks found and allow us to send blocks as required. + std::unique_ptr m_net; ///< Should run in background and send us events when blocks found and allow us to send blocks as required. std::unique_ptr m_work; ///< The work thread. std::atomic m_workState; diff --git a/libethereum/CommonNet.h b/libethereum/CommonNet.h index c3f721170..a6c6217d7 100644 --- a/libethereum/CommonNet.h +++ b/libethereum/CommonNet.h @@ -18,7 +18,7 @@ * @author Gav Wood * @date 2014 * - * Miscellanea required for the PeerServer/PeerSession classes. + * Miscellanea required for the PeerServer/Session classes. */ #pragma once diff --git a/libethereum/EthereumHost.cpp b/libethereum/EthereumHost.cpp index b35186fff..0f63712bb 100644 --- a/libethereum/EthereumHost.cpp +++ b/libethereum/EthereumHost.cpp @@ -27,8 +27,8 @@ #include #include #include -#include -#include +#include +#include #include #include "BlockChain.h" #include "TransactionQueue.h" diff --git a/libethereum/EthereumPeer.cpp b/libethereum/EthereumPeer.cpp index 5c121928d..3dc5dafc9 100644 --- a/libethereum/EthereumPeer.cpp +++ b/libethereum/EthereumPeer.cpp @@ -24,7 +24,7 @@ #include #include #include -#include +#include #include "BlockChain.h" #include "EthereumHost.h" using namespace std; @@ -33,8 +33,8 @@ using namespace p2p; #define clogS(X) eth::LogOutputStream(false) << "| " << std::setw(2) << session()->socketId() << "] " -EthereumPeer::EthereumPeer(PeerSession* _s, HostCapabilityFace* _h): - PeerCapability(_s, _h) +EthereumPeer::EthereumPeer(Session* _s, HostCapabilityFace* _h): + Capability(_s, _h) { sendStatus(); } @@ -46,7 +46,7 @@ EthereumPeer::~EthereumPeer() EthereumHost* EthereumPeer::host() const { - return static_cast(PeerCapability::hostCapability()); + return static_cast(Capability::hostCapability()); } void EthereumPeer::sendStatus() diff --git a/libethereum/EthereumPeer.h b/libethereum/EthereumPeer.h index 5f01148af..b799de314 100644 --- a/libethereum/EthereumPeer.h +++ b/libethereum/EthereumPeer.h @@ -28,7 +28,7 @@ #include #include #include -#include +#include #include "CommonNet.h" namespace eth @@ -38,12 +38,12 @@ namespace eth * @brief The EthereumPeer class * @todo Document fully. */ -class EthereumPeer: public p2p::PeerCapability +class EthereumPeer: public p2p::Capability { friend class EthereumHost; public: - EthereumPeer(p2p::PeerSession* _s, p2p::HostCapabilityFace* _h); + EthereumPeer(p2p::Session* _s, p2p::HostCapabilityFace* _h); virtual ~EthereumPeer(); static std::string name() { return "eth"; } diff --git a/libevm/VM.h b/libevm/VM.h index 3e271dae0..de2d56431 100644 --- a/libevm/VM.h +++ b/libevm/VM.h @@ -89,6 +89,8 @@ private: // INLINE: template eth::bytesConstRef eth::VM::go(Ext& _ext, OnOpFunc const& _onOp, uint64_t _steps) { + auto memNeed = [](eth::u256 _offset, eth::u256 _size) { return _size ? _offset + _size : 0; }; + u256 nextPC = m_curPC + 1; auto osteps = _steps; for (bool stopped = false; !stopped && _steps--; m_curPC = nextPC, nextPC = m_curPC + 1) @@ -138,20 +140,20 @@ template eth::bytesConstRef eth::VM::go(Ext& _ext, OnOpFunc const& _ break; case Instruction::RETURN: require(2); - newTempSize = m_stack.back() + m_stack[m_stack.size() - 2]; + newTempSize = memNeed(m_stack.back(), m_stack[m_stack.size() - 2]); break; case Instruction::SHA3: require(2); runGas = c_sha3Gas; - newTempSize = m_stack.back() + m_stack[m_stack.size() - 2]; + newTempSize = memNeed(m_stack.back(), m_stack[m_stack.size() - 2]); break; case Instruction::CALLDATACOPY: require(3); - newTempSize = m_stack.back() + m_stack[m_stack.size() - 3]; + newTempSize = memNeed(m_stack.back(), m_stack[m_stack.size() - 3]); break; case Instruction::CODECOPY: require(3); - newTempSize = m_stack.back() + m_stack[m_stack.size() - 3]; + newTempSize = memNeed(m_stack.back(), m_stack[m_stack.size() - 3]); break; case Instruction::BALANCE: @@ -161,13 +163,13 @@ template eth::bytesConstRef eth::VM::go(Ext& _ext, OnOpFunc const& _ case Instruction::CALL: require(7); runGas = c_callGas + m_stack[m_stack.size() - 1]; - newTempSize = std::max(m_stack[m_stack.size() - 6] + m_stack[m_stack.size() - 7], m_stack[m_stack.size() - 4] + m_stack[m_stack.size() - 5]); + newTempSize = std::max(memNeed(m_stack[m_stack.size() - 6], m_stack[m_stack.size() - 7]), memNeed(m_stack[m_stack.size() - 4], m_stack[m_stack.size() - 5])); break; case Instruction::POST: require(5); runGas = c_callGas + m_stack[m_stack.size() - 1]; - newTempSize = m_stack[m_stack.size() - 4] + m_stack[m_stack.size() - 5]; + newTempSize = memNeed(m_stack[m_stack.size() - 4], m_stack[m_stack.size() - 5]); break; case Instruction::CREATE: diff --git a/libp2p/All.h b/libp2p/All.h index 55a5423b7..b9dd26276 100644 --- a/libp2p/All.h +++ b/libp2p/All.h @@ -1,6 +1,8 @@ #pragma once #include "Common.h" -#include "PeerHost.h" -#include "PeerSession.h" +#include "HostCapability.h" +#include "Capability.h" +#include "Host.h" +#include "Session.h" diff --git a/libp2p/Capability.cpp b/libp2p/Capability.cpp new file mode 100644 index 000000000..85ccf9aa9 --- /dev/null +++ b/libp2p/Capability.cpp @@ -0,0 +1,58 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file Capability.cpp + * @author Gav Wood + * @date 2014 + */ + +#include "Capability.h" + +#include "Session.h" +using namespace std; +using namespace eth; +using namespace p2p; + +void Capability::disable(std::string const& _problem) +{ + clog(NetConnect) << "Disabling capability '" << m_host->name() << "'. Reason:" << _problem; + m_enabled = false; +} + +RLPStream& Capability::prep(RLPStream& _s) +{ + return Session::prep(_s); +} + +void Capability::sealAndSend(RLPStream& _s) +{ + m_session->sealAndSend(_s); +} + +void Capability::sendDestroy(bytes& _msg) +{ + m_session->sendDestroy(_msg); +} + +void Capability::send(bytesConstRef _msg) +{ + m_session->send(_msg); +} + +void Capability::addRating(unsigned _r) +{ + m_session->addRating(_r); +} diff --git a/libp2p/Capability.h b/libp2p/Capability.h new file mode 100644 index 000000000..275470c65 --- /dev/null +++ b/libp2p/Capability.h @@ -0,0 +1,62 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file Capability.h + * @author Gav Wood + * @date 2014 + */ + +#pragma once + +#include "Common.h" +#include "HostCapability.h" + +namespace p2p +{ + +class Capability +{ + friend class Session; + +public: + Capability(Session* _s, HostCapabilityFace* _h): m_session(_s), m_host(_h) {} + virtual ~Capability() {} + + /// Must return the capability name. + static std::string name() { return ""; } + + Session* session() const { return m_session; } + HostCapabilityFace* hostCapability() const { return m_host; } + +protected: + virtual bool interpret(RLP const&) = 0; + + void disable(std::string const& _problem); + + static RLPStream& prep(RLPStream& _s); + void sealAndSend(RLPStream& _s); + void sendDestroy(bytes& _msg); + void send(bytesConstRef _msg); + + void addRating(unsigned _r); + +private: + Session* m_session; + HostCapabilityFace* m_host; + bool m_enabled = true; +}; + +} diff --git a/libp2p/Common.cpp b/libp2p/Common.cpp index 6226df296..297bd4715 100644 --- a/libp2p/Common.cpp +++ b/libp2p/Common.cpp @@ -20,10 +20,6 @@ */ #include "Common.h" - -#include -#include "PeerSession.h" -#include "PeerHost.h" using namespace std; using namespace eth; using namespace p2p; @@ -63,50 +59,3 @@ std::string p2p::reasonOf(DisconnectReason _r) default: return "Unknown reason."; } } - -void PeerCapability::disable(std::string const& _problem) -{ - clog(NetConnect) << "Disabling capability '" << m_host->name() << "'. Reason:" << _problem; - m_enabled = false; -} - -void HostCapabilityFace::seal(bytes& _b) -{ - m_host->seal(_b); -} - -std::vector > HostCapabilityFace::peers() const -{ - Guard l(m_host->x_peers); - std::vector > ret; - for (auto const& i: m_host->m_peers) - if (std::shared_ptr p = i.second.lock()) - if (p->m_capabilities.count(name())) - ret.push_back(p); - return ret; -} - -RLPStream& PeerCapability::prep(RLPStream& _s) -{ - return PeerSession::prep(_s); -} - -void PeerCapability::sealAndSend(RLPStream& _s) -{ - m_session->sealAndSend(_s); -} - -void PeerCapability::sendDestroy(bytes& _msg) -{ - m_session->sendDestroy(_msg); -} - -void PeerCapability::send(bytesConstRef _msg) -{ - m_session->send(_msg); -} - -void PeerCapability::addRating(unsigned _r) -{ - m_session->addRating(_r); -} diff --git a/libp2p/Common.h b/libp2p/Common.h index da7dc0a53..7061b8090 100644 --- a/libp2p/Common.h +++ b/libp2p/Common.h @@ -18,7 +18,7 @@ * @author Gav Wood * @date 2014 * - * Miscellanea required for the PeerHost/PeerSession classes. + * Miscellanea required for the Host/Session classes. */ #pragma once @@ -52,8 +52,10 @@ using eth::RLPStream; bool isPrivateAddress(bi::address _addressToCheck); -class PeerHost; -class PeerSession; +class UPnP; +class Capability; +class Host; +class Session; struct NetWarn: public LogChannel { static const char* name() { return "!N!"; } static const int verbosity = 0; }; struct NetNote: public LogChannel { static const char* name() { return "*N*"; } static const int verbosity = 1; }; @@ -102,79 +104,4 @@ struct PeerInfo std::chrono::steady_clock::duration lastPing; }; -class UPnP; - -class PeerCapability; - -class HostCapabilityFace -{ - friend class PeerHost; - template friend class HostCapability; - friend class PeerCapability; - -public: - HostCapabilityFace() {} - virtual ~HostCapabilityFace() {} - - PeerHost* host() const { return m_host; } - - std::vector > peers() const; - -protected: - virtual std::string name() const = 0; - virtual PeerCapability* newPeerCapability(PeerSession* _s) = 0; - virtual bool isInitialised() const { return true; } - - void seal(bytes& _b); - -private: - PeerHost* m_host = nullptr; -}; - -template -class HostCapability: public HostCapabilityFace -{ -public: - HostCapability() {} - virtual ~HostCapability() {} - - static std::string staticName() { return PeerCap::name(); } - -protected: - virtual std::string name() const { return PeerCap::name(); } - virtual PeerCapability* newPeerCapability(PeerSession* _s) { return new PeerCap(_s, this); } -}; - -class PeerCapability -{ - friend class PeerSession; - -public: - PeerCapability(PeerSession* _s, HostCapabilityFace* _h): m_session(_s), m_host(_h) {} - virtual ~PeerCapability() {} - - /// Must return the capability name. - static std::string name() { return ""; } - - PeerSession* session() const { return m_session; } - HostCapabilityFace* hostCapability() const { return m_host; } - -protected: - virtual bool interpret(RLP const&) = 0; - - void disable(std::string const& _problem); - - static RLPStream& prep(RLPStream& _s); - void sealAndSend(RLPStream& _s); - void sendDestroy(bytes& _msg); - void send(bytesConstRef _msg); - - void addRating(unsigned _r); - -private: - PeerSession* m_session; - HostCapabilityFace* m_host; - bool m_enabled = true; -}; - } diff --git a/libp2p/PeerHost.cpp b/libp2p/Host.cpp similarity index 86% rename from libp2p/PeerHost.cpp rename to libp2p/Host.cpp index 00d4705ee..580a5ea1a 100644 --- a/libp2p/PeerHost.cpp +++ b/libp2p/Host.cpp @@ -14,14 +14,14 @@ You should have received a copy of the GNU General Public License along with cpp-ethereum. If not, see . */ -/** @file PeerHost.cpp +/** @file Host.cpp * @authors: * Gav Wood * Eric Lombrozo * @date 2014 */ -#include "PeerHost.h" +#include "Host.h" #include #ifdef _WIN32 @@ -36,7 +36,8 @@ #include #include #include -#include "PeerSession.h" +#include "Session.h" +#include "Capability.h" #include "UPnP.h" using namespace std; using namespace eth; @@ -53,7 +54,7 @@ static const set c_rejectAddresses = { {bi::address_v6::from_string("::")} }; -PeerHost::PeerHost(std::string const& _clientVersion, unsigned short _port, string const& _publicAddress, bool _upnp, bool _localNetworking): +Host::Host(std::string const& _clientVersion, unsigned short _port, string const& _publicAddress, bool _upnp, bool _localNetworking): m_clientVersion(_clientVersion), m_listenPort(_port), m_localNetworking(_localNetworking), @@ -68,7 +69,7 @@ PeerHost::PeerHost(std::string const& _clientVersion, unsigned short _port, stri clog(NetNote) << "Id:" << m_id.abridged(); } -PeerHost::PeerHost(std::string const& _clientVersion, string const& _publicAddress, bool _upnp, bool _localNetworking): +Host::Host(std::string const& _clientVersion, string const& _publicAddress, bool _upnp, bool _localNetworking): m_clientVersion(_clientVersion), m_listenPort(0), m_localNetworking(_localNetworking), @@ -86,7 +87,7 @@ PeerHost::PeerHost(std::string const& _clientVersion, string const& _publicAddre clog(NetNote) << "Id:" << m_id.abridged(); } -PeerHost::PeerHost(std::string const& _clientVersion): +Host::Host(std::string const& _clientVersion): m_clientVersion(_clientVersion), m_listenPort(0), m_acceptor(m_ioService, bi::tcp::endpoint(bi::tcp::v4(), 0)), @@ -99,17 +100,17 @@ PeerHost::PeerHost(std::string const& _clientVersion): clog(NetNote) << "Id:" << m_id.abridged(); } -PeerHost::~PeerHost() +Host::~Host() { disconnectPeers(); } -unsigned PeerHost::protocolVersion() const +unsigned Host::protocolVersion() const { return 0; } -void PeerHost::registerPeer(std::shared_ptr _s, vector const& _caps) +void Host::registerPeer(std::shared_ptr _s, vector const& _caps) { { Guard l(x_peers); @@ -117,10 +118,10 @@ void PeerHost::registerPeer(std::shared_ptr _s, vector cons } for (auto const& i: _caps) if (haveCapability(i)) - _s->m_capabilities[i] = shared_ptr(m_capabilities[i]->newPeerCapability(_s.get())); + _s->m_capabilities[i] = shared_ptr(m_capabilities[i]->newPeerCapability(_s.get())); } -void PeerHost::disconnectPeers() +void Host::disconnectPeers() { for (unsigned n = 0;; n = 0) { @@ -142,7 +143,7 @@ void PeerHost::disconnectPeers() delete m_upnp; } -void PeerHost::seal(bytes& _b) +void Host::seal(bytes& _b) { _b[0] = 0x22; _b[1] = 0x40; @@ -155,7 +156,7 @@ void PeerHost::seal(bytes& _b) _b[7] = len & 0xff; } -void PeerHost::determinePublic(string const& _publicAddress, bool _upnp) +void Host::determinePublic(string const& _publicAddress, bool _upnp) { if (_upnp) try @@ -197,7 +198,7 @@ void PeerHost::determinePublic(string const& _publicAddress, bool _upnp) } } -void PeerHost::populateAddresses() +void Host::populateAddresses() { #ifdef _WIN32 WSAData wsaData; @@ -272,7 +273,7 @@ void PeerHost::populateAddresses() #endif } -std::map PeerHost::potentialPeers() +std::map Host::potentialPeers() { std::map ret; if (!m_public.address().is_unspecified()) @@ -290,7 +291,7 @@ std::map PeerHost::potentialPeers() return ret; } -void PeerHost::ensureAccepting() +void Host::ensureAccepting() { if (m_accepting == false) { @@ -306,7 +307,7 @@ void PeerHost::ensureAccepting() } catch (...){} bi::address remoteAddress = m_socket.remote_endpoint().address(); // Port defaults to 0 - we let the hello tell us which port the peer listens to - auto p = std::make_shared(this, std::move(m_socket), remoteAddress); + auto p = std::make_shared(this, std::move(m_socket), remoteAddress); p->start(); } catch (std::exception const& _e) @@ -320,7 +321,7 @@ void PeerHost::ensureAccepting() } } -void PeerHost::connect(std::string const& _addr, unsigned short _port) noexcept +void Host::connect(std::string const& _addr, unsigned short _port) noexcept { try { @@ -333,7 +334,7 @@ void PeerHost::connect(std::string const& _addr, unsigned short _port) noexcept } } -void PeerHost::connect(bi::tcp::endpoint const& _ep) +void Host::connect(bi::tcp::endpoint const& _ep) { clog(NetConnect) << "Attempting connection to " << _ep; bi::tcp::socket* s = new bi::tcp::socket(m_ioService); @@ -354,7 +355,7 @@ void PeerHost::connect(bi::tcp::endpoint const& _ep) } else { - auto p = make_shared(this, std::move(*s), _ep.address(), _ep.port()); + auto p = make_shared(this, std::move(*s), _ep.address(), _ep.port()); clog(NetConnect) << "Connected to " << _ep; p->start(); } @@ -362,7 +363,7 @@ void PeerHost::connect(bi::tcp::endpoint const& _ep) }); } -bool PeerHost::havePeer(h512 _id) const +bool Host::havePeer(h512 _id) const { Guard l(x_peers); @@ -376,7 +377,7 @@ bool PeerHost::havePeer(h512 _id) const return !!m_peers.count(_id); } -void PeerHost::growPeers() +void Host::growPeers() { Guard l(x_peers); while (m_peers.size() < m_idealPeerCount) @@ -387,7 +388,7 @@ void PeerHost::growPeers() { RLPStream s; bytes b; - (PeerSession::prep(s).appendList(1) << GetPeersPacket).swapOut(b); + (Session::prep(s).appendList(1) << GetPeersPacket).swapOut(b); seal(b); for (auto const& i: m_peers) if (auto p = i.second.lock()) @@ -410,7 +411,7 @@ void PeerHost::growPeers() } } -void PeerHost::prunePeers() +void Host::prunePeers() { Guard l(x_peers); // We'll keep at most twice as many as is ideal, halfing what counts as "too young to kill" until we get there. @@ -419,11 +420,11 @@ void PeerHost::prunePeers() { // look for worst peer to kick off // first work out how many are old enough to kick off. - shared_ptr worst; + shared_ptr worst; unsigned agedPeers = 0; for (auto i: m_peers) if (auto p = i.second.lock()) - if (/*(m_mode != NodeMode::PeerHost || p->m_caps != 0x01) &&*/ chrono::steady_clock::now() > p->m_connect + chrono::milliseconds(old)) // don't throw off new peers; peer-servers should never kick off other peer-servers. + if (/*(m_mode != NodeMode::Host || p->m_caps != 0x01) &&*/ chrono::steady_clock::now() > p->m_connect + chrono::milliseconds(old)) // don't throw off new peers; peer-servers should never kick off other peer-servers. { ++agedPeers; if ((!worst || p->m_rating < worst->m_rating || (p->m_rating == worst->m_rating && p->m_connect > worst->m_connect))) // kill older ones @@ -442,11 +443,11 @@ void PeerHost::prunePeers() i = m_peers.erase(i); } -std::vector PeerHost::peers(bool _updatePing) const +std::vector Host::peers(bool _updatePing) const { Guard l(x_peers); if (_updatePing) - const_cast(this)->pingAll(); + const_cast(this)->pingAll(); this_thread::sleep_for(chrono::milliseconds(200)); std::vector ret; for (auto& i: m_peers) @@ -456,7 +457,7 @@ std::vector PeerHost::peers(bool _updatePing) const return ret; } -void PeerHost::process() +void Host::process() { for (auto const& i: m_capabilities) if (!i.second->isInitialised()) @@ -466,7 +467,7 @@ void PeerHost::process() m_ioService.poll(); } -void PeerHost::pingAll() +void Host::pingAll() { Guard l(x_peers); for (auto& i: m_peers) @@ -474,7 +475,7 @@ void PeerHost::pingAll() j->ping(); } -bytes PeerHost::savePeers() const +bytes Host::savePeers() const { Guard l(x_peers); RLPStream ret; @@ -489,7 +490,7 @@ bytes PeerHost::savePeers() const return RLPStream(n).appendRaw(ret.out(), n).out(); } -void PeerHost::restorePeers(bytesConstRef _b) +void Host::restorePeers(bytesConstRef _b) { for (auto i: RLP(_b)) { diff --git a/libp2p/PeerHost.h b/libp2p/Host.h similarity index 86% rename from libp2p/PeerHost.h rename to libp2p/Host.h index e9e1a5987..08a39a8c2 100644 --- a/libp2p/PeerHost.h +++ b/libp2p/Host.h @@ -29,7 +29,7 @@ #include #include #include -#include "Common.h" +#include "HostCapability.h" namespace ba = boost::asio; namespace bi = boost::asio::ip; @@ -43,24 +43,24 @@ class BlockQueue; using eth::Guard; /** - * @brief The PeerHost class + * @brief The Host class * Capabilities should be registered prior to startNetwork, since m_capabilities is not thread-safe. */ -class PeerHost +class Host { - friend class PeerSession; + friend class Session; friend class HostCapabilityFace; public: /// Start server, listening for connections on the given port. - PeerHost(std::string const& _clientVersion, unsigned short _port, std::string const& _publicAddress = std::string(), bool _upnp = true, bool _localNetworking = false); + Host(std::string const& _clientVersion, unsigned short _port, std::string const& _publicAddress = std::string(), bool _upnp = true, bool _localNetworking = false); /// Start server, listening for connections on a system-assigned port. - PeerHost(std::string const& _clientVersion, std::string const& _publicAddress = std::string(), bool _upnp = true, bool _localNetworking = false); + Host(std::string const& _clientVersion, std::string const& _publicAddress = std::string(), bool _upnp = true, bool _localNetworking = false); /// Start server, but don't listen. - PeerHost(std::string const& _clientVersion); + Host(std::string const& _clientVersion); /// Will block on network process events. - virtual ~PeerHost(); + virtual ~Host(); /// Closes all peers. void disconnectPeers(); @@ -110,7 +110,7 @@ public: h512 id() const { return m_id; } - void registerPeer(std::shared_ptr _s, std::vector const& _caps); + void registerPeer(std::shared_ptr _s, std::vector const& _caps); protected: /// Called when the session has provided us with a new peer we can connect to. @@ -140,7 +140,7 @@ protected: h512 m_id; mutable std::mutex x_peers; - mutable std::map> m_peers; // mutable because we flush zombie entries (null-weakptrs) as regular maintenance from a const method. + mutable std::map> m_peers; // mutable because we flush zombie entries (null-weakptrs) as regular maintenance from a const method. std::map> m_incomingPeers; // TODO: does this need a lock? std::vector m_freePeers; diff --git a/libp2p/HostCapability.cpp b/libp2p/HostCapability.cpp new file mode 100644 index 000000000..5d81e1213 --- /dev/null +++ b/libp2p/HostCapability.cpp @@ -0,0 +1,44 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file HostCapability.cpp + * @author Gav Wood + * @date 2014 + */ + +#include "HostCapability.h" + +#include "Session.h" +#include "Host.h" +using namespace std; +using namespace eth; +using namespace p2p; + +void HostCapabilityFace::seal(bytes& _b) +{ + m_host->seal(_b); +} + +std::vector > HostCapabilityFace::peers() const +{ + Guard l(m_host->x_peers); + std::vector > ret; + for (auto const& i: m_host->m_peers) + if (std::shared_ptr p = i.second.lock()) + if (p->m_capabilities.count(name())) + ret.push_back(p); + return ret; +} diff --git a/libp2p/HostCapability.h b/libp2p/HostCapability.h new file mode 100644 index 000000000..e4b3d403b --- /dev/null +++ b/libp2p/HostCapability.h @@ -0,0 +1,70 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file Common.h + * @author Gav Wood + * @date 2014 + * + * Miscellanea required for the Host/Session classes. + */ + +#pragma once + +#include "Common.h" + +namespace p2p +{ + +class HostCapabilityFace +{ + friend class Host; + template friend class HostCapability; + friend class Capability; + +public: + HostCapabilityFace() {} + virtual ~HostCapabilityFace() {} + + Host* host() const { return m_host; } + + std::vector > peers() const; + +protected: + virtual std::string name() const = 0; + virtual Capability* newPeerCapability(Session* _s) = 0; + virtual bool isInitialised() const { return true; } + + void seal(bytes& _b); + +private: + Host* m_host = nullptr; +}; + +template +class HostCapability: public HostCapabilityFace +{ +public: + HostCapability() {} + virtual ~HostCapability() {} + + static std::string staticName() { return PeerCap::name(); } + +protected: + virtual std::string name() const { return PeerCap::name(); } + virtual Capability* newPeerCapability(Session* _s) { return new PeerCap(_s, this); } +}; + +} diff --git a/libp2p/PeerSession.cpp b/libp2p/Session.cpp similarity index 92% rename from libp2p/PeerSession.cpp rename to libp2p/Session.cpp index 1f19bd032..f2b5a1d01 100644 --- a/libp2p/PeerSession.cpp +++ b/libp2p/Session.cpp @@ -14,24 +14,25 @@ You should have received a copy of the GNU General Public License along with cpp-ethereum. If not, see . */ -/** @file PeerSession.cpp +/** @file Session.cpp * @author Gav Wood * @date 2014 */ -#include "PeerSession.h" +#include "Session.h" #include #include #include -#include "PeerHost.h" +#include "Host.h" +#include "Capability.h" using namespace std; using namespace eth; using namespace p2p; #define clogS(X) eth::LogOutputStream(false) << "| " << std::setw(2) << m_socket.native_handle() << "] " -PeerSession::PeerSession(PeerHost* _s, bi::tcp::socket _socket, bi::address _peerAddress, unsigned short _peerPort): +Session::Session(Host* _s, bi::tcp::socket _socket, bi::address _peerAddress, unsigned short _peerPort): m_server(_s), m_socket(std::move(_socket)), m_listenPort(_peerPort), @@ -42,7 +43,7 @@ PeerSession::PeerSession(PeerHost* _s, bi::tcp::socket _socket, bi::address _pee m_info = PeerInfo({"?", _peerAddress.to_string(), m_listenPort, std::chrono::steady_clock::duration(0)}); } -PeerSession::~PeerSession() +Session::~Session() { // Read-chain finished for one reason or another. for (auto& i: m_capabilities) @@ -56,7 +57,7 @@ PeerSession::~PeerSession() catch (...){} } -bi::tcp::endpoint PeerSession::endpoint() const +bi::tcp::endpoint Session::endpoint() const { if (m_socket.is_open()) try @@ -68,7 +69,7 @@ bi::tcp::endpoint PeerSession::endpoint() const return bi::tcp::endpoint(); } -bool PeerSession::interpret(RLP const& _r) +bool Session::interpret(RLP const& _r) { clogS(NetRight) << _r; switch (_r[0].toInt()) @@ -192,25 +193,25 @@ bool PeerSession::interpret(RLP const& _r) return true; } -void PeerSession::ping() +void Session::ping() { RLPStream s; sealAndSend(prep(s).appendList(1) << PingPacket); m_ping = std::chrono::steady_clock::now(); } -void PeerSession::getPeers() +void Session::getPeers() { RLPStream s; sealAndSend(prep(s).appendList(1) << GetPeersPacket); } -RLPStream& PeerSession::prep(RLPStream& _s) +RLPStream& Session::prep(RLPStream& _s) { return _s.appendRaw(bytes(8, 0)); } -void PeerSession::sealAndSend(RLPStream& _s) +void Session::sealAndSend(RLPStream& _s) { bytes b; _s.swapOut(b); @@ -218,7 +219,7 @@ void PeerSession::sealAndSend(RLPStream& _s) sendDestroy(b); } -bool PeerSession::checkPacket(bytesConstRef _msg) +bool Session::checkPacket(bytesConstRef _msg) { if (_msg.size() < 8) return false; @@ -233,7 +234,7 @@ bool PeerSession::checkPacket(bytesConstRef _msg) return true; } -void PeerSession::sendDestroy(bytes& _msg) +void Session::sendDestroy(bytes& _msg) { clogS(NetLeft) << RLP(bytesConstRef(&_msg).cropped(8)); @@ -246,7 +247,7 @@ void PeerSession::sendDestroy(bytes& _msg) writeImpl(buffer); } -void PeerSession::send(bytesConstRef _msg) +void Session::send(bytesConstRef _msg) { clogS(NetLeft) << RLP(_msg.cropped(8)); @@ -259,7 +260,7 @@ void PeerSession::send(bytesConstRef _msg) writeImpl(buffer); } -void PeerSession::writeImpl(bytes& _buffer) +void Session::writeImpl(bytes& _buffer) { // cerr << (void*)this << " writeImpl" << endl; if (!m_socket.is_open()) @@ -271,7 +272,7 @@ void PeerSession::writeImpl(bytes& _buffer) write(); } -void PeerSession::write() +void Session::write() { // cerr << (void*)this << " write" << endl; lock_guard l(m_writeLock); @@ -298,7 +299,7 @@ void PeerSession::write() }); } -void PeerSession::dropped() +void Session::dropped() { // cerr << (void*)this << " dropped" << endl; if (m_socket.is_open()) @@ -310,7 +311,7 @@ void PeerSession::dropped() catch (...) {} } -void PeerSession::disconnect(int _reason) +void Session::disconnect(int _reason) { clogS(NetConnect) << "Disconnecting (reason:" << reasonOf((DisconnectReason)_reason) << ")"; if (m_socket.is_open()) @@ -328,7 +329,7 @@ void PeerSession::disconnect(int _reason) } } -void PeerSession::start() +void Session::start() { RLPStream s; prep(s); @@ -345,7 +346,7 @@ void PeerSession::start() doRead(); } -void PeerSession::doRead() +void Session::doRead() { // ignore packets received while waiting to disconnect if (chrono::steady_clock::now() - m_disconnect > chrono::seconds(0)) diff --git a/libp2p/PeerSession.h b/libp2p/Session.h similarity index 87% rename from libp2p/PeerSession.h rename to libp2p/Session.h index 50fb86e3d..52628210c 100644 --- a/libp2p/PeerSession.h +++ b/libp2p/Session.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU General Public License along with cpp-ethereum. If not, see . */ -/** @file PeerSession.h +/** @file Session.h * @author Gav Wood * @date 2014 */ @@ -33,17 +33,17 @@ namespace p2p { /** - * @brief The PeerSession class + * @brief The Session class * @todo Document fully. */ -class PeerSession: public std::enable_shared_from_this +class Session: public std::enable_shared_from_this { - friend class PeerHost; + friend class Host; friend class HostCapabilityFace; public: - PeerSession(PeerHost* _server, bi::tcp::socket _socket, bi::address _peerAddress, unsigned short _peerPort = 0); - virtual ~PeerSession(); + Session(Host* _server, bi::tcp::socket _socket, bi::address _peerAddress, unsigned short _peerPort = 0); + virtual ~Session(); void start(); void disconnect(int _reason); @@ -80,7 +80,7 @@ private: /// @returns true iff the _msg forms a valid message for sending or receiving on the network. static bool checkPacket(bytesConstRef _msg); - PeerHost* m_server; + Host* m_server; std::recursive_mutex m_writeLock; std::deque m_writeQueue; @@ -100,7 +100,7 @@ private: uint m_rating; - std::map> m_capabilities; + std::map> m_capabilities; bool m_willBeDeleted = false; ///< True if we already posted a deleter on the strand. }; diff --git a/libwhisper/Common.h b/libwhisper/Common.h index c669d6ea9..0addc4b75 100644 --- a/libwhisper/Common.h +++ b/libwhisper/Common.h @@ -26,7 +26,7 @@ #include #include #include -#include +#include namespace shh { diff --git a/libwhisper/WhisperPeer.cpp b/libwhisper/WhisperPeer.cpp index f355e5df7..ee548949e 100644 --- a/libwhisper/WhisperPeer.cpp +++ b/libwhisper/WhisperPeer.cpp @@ -30,7 +30,7 @@ using namespace shh; #define clogS(X) eth::LogOutputStream(false) << "| " << std::setw(2) << session()->socketId() << "] " -WhisperPeer::WhisperPeer(PeerSession* _s, HostCapabilityFace* _h): PeerCapability(_s, _h) +WhisperPeer::WhisperPeer(Session* _s, HostCapabilityFace* _h): Capability(_s, _h) { RLPStream s; prep(s); @@ -43,7 +43,7 @@ WhisperPeer::~WhisperPeer() WhisperHost* WhisperPeer::host() const { - return static_cast(PeerCapability::hostCapability()); + return static_cast(Capability::hostCapability()); } bool WhisperPeer::interpret(RLP const& _r) diff --git a/libwhisper/WhisperPeer.h b/libwhisper/WhisperPeer.h index 9853399ae..5db7ffa2b 100644 --- a/libwhisper/WhisperPeer.h +++ b/libwhisper/WhisperPeer.h @@ -34,10 +34,10 @@ namespace shh { -using p2p::PeerSession; +using p2p::Session; using p2p::HostCapabilityFace; using p2p::HostCapability; -using p2p::PeerCapability; +using p2p::Capability; struct Message { @@ -64,12 +64,12 @@ struct Message /** */ -class WhisperPeer: public PeerCapability +class WhisperPeer: public Capability { friend class WhisperHost; public: - WhisperPeer(PeerSession* _s, HostCapabilityFace* _h); + WhisperPeer(Session* _s, HostCapabilityFace* _h); virtual ~WhisperPeer(); static std::string name() { return "shh"; } diff --git a/test/peer.cpp b/test/peer.cpp index 8db4cc129..f903d0429 100644 --- a/test/peer.cpp +++ b/test/peer.cpp @@ -22,7 +22,7 @@ #include #include -#include +#include using namespace std; using namespace eth; using namespace p2p; @@ -46,7 +46,7 @@ int peerTest(int argc, char** argv) remoteHost = argv[i]; } - PeerHost ph("Test", listenPort); + Host ph("Test", listenPort); if (!remoteHost.empty()) ph.connect(remoteHost, remotePort); From 334118c76ac7d5fd43f266bcb4ab82798f9757e4 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 3 Sep 2014 21:12:08 +0200 Subject: [PATCH 154/223] size=0 doesn't increase memsize. --- libethcore/CommonEth.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libethcore/CommonEth.cpp b/libethcore/CommonEth.cpp index 49a57d785..3fc817c72 100644 --- a/libethcore/CommonEth.cpp +++ b/libethcore/CommonEth.cpp @@ -29,7 +29,7 @@ using namespace eth; //#define ETH_ADDRESS_DEBUG 1 -const unsigned eth::c_protocolVersion = 31; +const unsigned eth::c_protocolVersion = 32; const unsigned eth::c_databaseVersion = 1; static const vector> g_units = From 69126fe1b7ab1604cbd9b1c27d4cec0987c2dd75 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Wed, 3 Sep 2014 21:28:37 +0200 Subject: [PATCH 155/223] CALL_STATELESS --- libethereum/ExtVM.h | 6 +++--- libethereum/State.cpp | 6 +++--- libethereum/State.h | 2 +- libevm/ExtVMFace.h | 6 +++--- libevm/VM.h | 11 +++++++++-- libevmface/Instruction.cpp | 2 ++ libevmface/Instruction.h | 1 + test/vm.cpp | 2 +- 8 files changed, 23 insertions(+), 13 deletions(-) diff --git a/libethereum/ExtVM.h b/libethereum/ExtVM.h index 7709f9d7f..48099b8fc 100644 --- a/libethereum/ExtVM.h +++ b/libethereum/ExtVM.h @@ -62,12 +62,12 @@ public: return ret; } - /// Create a new message call. Leave _myAddressOverride at he default to use the present address as caller. - bool call(Address _receiveAddress, u256 _txValue, bytesConstRef _txData, u256* _gas, bytesRef _out, OnOpFunc const& _onOp = OnOpFunc(), Address _myAddressOverride = Address()) + /// Create a new message call. Leave _myAddressOverride as the default to use the present address as caller. + bool call(Address _receiveAddress, u256 _txValue, bytesConstRef _txData, u256* _gas, bytesRef _out, OnOpFunc const& _onOp = OnOpFunc(), Address _myAddressOverride = Address(), Address _codeAddressOverride = Address()) { if (m_ms) m_ms->internal.resize(m_ms->internal.size() + 1); - auto ret = m_s.call(_receiveAddress, _myAddressOverride ? _myAddressOverride : myAddress, _txValue, gasPrice, _txData, _gas, _out, origin, &suicides, &posts, m_ms ? &(m_ms->internal.back()) : nullptr, _onOp, level + 1); + auto ret = m_s.call(_receiveAddress, _codeAddressOverride ? _codeAddressOverride : _receiveAddress, _myAddressOverride ? _myAddressOverride : myAddress, _txValue, gasPrice, _txData, _gas, _out, origin, &suicides, &posts, m_ms ? &(m_ms->internal.back()) : nullptr, _onOp, level + 1); if (m_ms && !m_ms->internal.back().from) m_ms->internal.pop_back(); return ret; diff --git a/libethereum/State.cpp b/libethereum/State.cpp index 41f4a6f1b..5ef528c58 100644 --- a/libethereum/State.cpp +++ b/libethereum/State.cpp @@ -1037,7 +1037,7 @@ u256 State::execute(bytesConstRef _rlp, bytes* o_output, bool _commit) return e.gasUsed(); } -bool State::call(Address _receiveAddress, Address _senderAddress, u256 _value, u256 _gasPrice, bytesConstRef _data, u256* _gas, bytesRef _out, Address _originAddress, std::set