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 *);