From 0b014457d4b631924a0811f3104572aad3f0fc3e Mon Sep 17 00:00:00 2001 From: Christian Date: Thu, 19 Feb 2015 15:13:03 +0100 Subject: [PATCH] Implemented account proxy queues. --- libweb3jsonrpc/AccountHolder.cpp | 102 ++++++++++++++++++++ libweb3jsonrpc/AccountHolder.h | 74 ++++++++++++++ libweb3jsonrpc/WebThreeStubServerBase.cpp | 91 ++++++++++------- libweb3jsonrpc/WebThreeStubServerBase.h | 37 ++----- libweb3jsonrpc/abstractwebthreestubserver.h | 18 ++++ libweb3jsonrpc/spec.json | 4 + test/AccountHolder.cpp | 74 ++++++++++++++ test/webthreestubclient.h | 30 ++++++ 8 files changed, 365 insertions(+), 65 deletions(-) create mode 100644 libweb3jsonrpc/AccountHolder.cpp create mode 100644 libweb3jsonrpc/AccountHolder.h create mode 100644 test/AccountHolder.cpp diff --git a/libweb3jsonrpc/AccountHolder.cpp b/libweb3jsonrpc/AccountHolder.cpp new file mode 100644 index 000000000..e12380110 --- /dev/null +++ b/libweb3jsonrpc/AccountHolder.cpp @@ -0,0 +1,102 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file AccountHolder.cpp + * @authors: + * Christian R + * Lefteris Karapetsas + * @date 2015 + */ + +#include "AccountHolder.h" +#include + + +using namespace std; +using namespace dev; +using namespace dev::eth; + +vector emptyQueue; + +void AccountHolder::setAccounts(vector const& _accounts) +{ + m_accounts.clear(); + for (auto const& keyPair: _accounts) + { + m_accounts.push_back(keyPair.address()); + m_keyPairs[keyPair.address()] = keyPair; + } +} + +vector
AccountHolder::getAllAccounts() const +{ + vector
accounts = m_accounts; + for (auto const& pair: m_proxyAccounts) + if (!isRealAccount(pair.first)) + accounts.push_back(pair.first); + return accounts; +} + +Address const& AccountHolder::getDefaultCallAccount() const +{ + if (m_accounts.empty()) + return ZeroAddress; + Address const* bestMatch = &m_accounts.front(); + for (auto const& account: m_accounts) + if (m_client()->balanceAt(account) > m_client()->balanceAt(*bestMatch)) + bestMatch = &account; + return *bestMatch; +} + +int AccountHolder::addProxyAccount(const Address& _account) +{ + int const c_id = m_transactionQueues.empty() ? 1 : m_transactionQueues.rbegin()->first + 1; + if (isProxyAccount(_account)) + return 0; + m_proxyAccounts.insert(make_pair(_account, c_id)); + m_transactionQueues[c_id].first = _account; + return c_id; +} + +bool AccountHolder::removeProxyAccount(unsigned _id) +{ + if (!m_transactionQueues.count(_id)) + return false; + m_proxyAccounts.erase(m_transactionQueues[_id].first); + m_transactionQueues.erase(_id); + return true; +} + +void AccountHolder::queueTransaction(TransactionSkeleton const& _transaction) +{ + if (!m_proxyAccounts.count(_transaction.from)) + return; + int id = m_proxyAccounts[_transaction.from]; + m_transactionQueues[id].second.push_back(_transaction); +} + +vector const& AccountHolder::getQueuedTransactions(int _id) const +{ + if (!m_transactionQueues.count(_id)) + return emptyQueue; + return m_transactionQueues.at(_id).second; +} + +void AccountHolder::clearQueue(int _id) +{ + if (m_transactionQueues.count(_id)) + m_transactionQueues.at(_id).second.clear(); +} diff --git a/libweb3jsonrpc/AccountHolder.h b/libweb3jsonrpc/AccountHolder.h new file mode 100644 index 000000000..939947ea2 --- /dev/null +++ b/libweb3jsonrpc/AccountHolder.h @@ -0,0 +1,74 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file AccountHolder.h + * @authors: + * Christian R + * Lefteris Karapetsas + * @date 2015 + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace dev +{ +namespace eth +{ +class Interface; +} + +/** + * Manages real accounts (where we know the secret key) and proxy accounts (where transactions + * to be sent from these accounts are forwarded to a proxy on the other side). + */ +class AccountHolder +{ +public: + explicit AccountHolder(std::function const& _client): m_client(_client) {} + + /// Sets or resets the list of real accounts. + void setAccounts(std::vector const& _accounts); + std::vector
const& getRealAccounts() const { return m_accounts; } + bool isRealAccount(Address const& _account) const { return m_keyPairs.count(_account) > 0; } + bool isProxyAccount(Address const& _account) const { return m_proxyAccounts.count(_account) > 0; } + Secret const& secretKey(Address const& _account) const { return m_keyPairs.at(_account).secret(); } + std::vector
getAllAccounts() const; + Address const& getDefaultCallAccount() const; + + int addProxyAccount(Address const& _account); + bool removeProxyAccount(unsigned _id); + void queueTransaction(eth::TransactionSkeleton const& _transaction); + + std::vector const& getQueuedTransactions(int _id) const; + void clearQueue(int _id); + +private: + using TransactionQueue = std::vector; + + std::map m_keyPairs; + std::vector
m_accounts; + std::map m_proxyAccounts; + std::map> m_transactionQueues; + std::function m_client; +}; + +} diff --git a/libweb3jsonrpc/WebThreeStubServerBase.cpp b/libweb3jsonrpc/WebThreeStubServerBase.cpp index 185d3f6e0..b5cc6e078 100644 --- a/libweb3jsonrpc/WebThreeStubServerBase.cpp +++ b/libweb3jsonrpc/WebThreeStubServerBase.cpp @@ -35,6 +35,7 @@ #include #endif #include "WebThreeStubServerBase.h" +#include "AccountHolder.h" using namespace std; using namespace dev; @@ -72,6 +73,18 @@ static Json::Value toJson(dev::eth::Transaction const& _t) return res; } +static Json::Value toJson(dev::eth::TransactionSkeleton const& _t) +{ + Json::Value res; + res["to"] = toJS(_t.to); + res["from"] = toJS(_t.from); + res["gas"] = (int)_t.gas; + res["gasPrice"] = toJS(_t.gasPrice); + res["value"] = toJS(_t.value); + res["data"] = jsFromBinary(_t.data); + return res; +} + static Json::Value toJson(dev::eth::LocalisedLogEntry const& _e) { Json::Value res; @@ -211,31 +224,10 @@ static Json::Value toJson(h256 const& _h, shh::Envelope const& _e, shh::Message return res; } -void AccountHolder::setAccounts(std::vector const& _accounts) -{ - m_accounts.clear(); - for (auto const& keyPair: _accounts) - { - m_accounts.push_back(keyPair.address()); - m_keyPairs[keyPair.address()] = keyPair; - } -} - -Address const& AccountHolder::getDefaultCallAccount() const -{ - if (m_accounts.empty()) - return ZeroAddress; - Address const* bestMatch = &m_accounts.front(); - for (auto const& account: m_accounts) - if (m_client()->balanceAt(account) > m_client()->balanceAt(*bestMatch)) - bestMatch = &account; - return *bestMatch; -} - WebThreeStubServerBase::WebThreeStubServerBase(jsonrpc::AbstractServerConnector& _conn, std::vector const& _accounts): - AbstractWebThreeStubServer(_conn), m_accounts(std::bind(&WebThreeStubServerBase::client, this)) + AbstractWebThreeStubServer(_conn), m_accounts(make_shared(std::bind(&WebThreeStubServerBase::client, this))) { - m_accounts.setAccounts(_accounts); + m_accounts->setAccounts(_accounts); } void WebThreeStubServerBase::setIdentities(std::vector const& _ids) @@ -253,7 +245,7 @@ std::string WebThreeStubServerBase::web3_sha3(std::string const& _param1) Json::Value WebThreeStubServerBase::eth_accounts() { Json::Value ret(Json::arrayValue); - for (auto const& i: m_accounts.getAllAccounts()) + for (auto const& i: m_accounts->getAllAccounts()) ret.append(toJS(i)); return ret; } @@ -338,14 +330,14 @@ std::string WebThreeStubServerBase::eth_call(Json::Value const& _json) std::string ret; TransactionSkeleton t = toTransaction(_json); if (!t.from) - t.from = m_accounts.getDefaultCallAccount(); - if (!m_accounts.isRealAccount(t.from)) + t.from = m_accounts->getDefaultCallAccount(); + if (!m_accounts->isRealAccount(t.from)) return ret; if (!t.gasPrice) t.gasPrice = 10 * dev::eth::szabo; if (!t.gas) t.gas = min(client()->gasLimitRemaining(), client()->balanceAt(t.from) / t.gasPrice); - ret = toJS(client()->call(m_accounts.secretKey(t.from), t.value, t.to, t.data, t.gas, t.gasPrice)); + ret = toJS(client()->call(m_accounts->secretKey(t.from), t.value, t.to, t.data, t.gas, t.gasPrice)); return ret; } @@ -469,6 +461,25 @@ bool WebThreeStubServerBase::eth_submitWork(std::string const& _nonce) return client()->submitNonce(jsToFixed<32>(_nonce)); } +int WebThreeStubServerBase::eth_register(std::string const& _address) +{ + return m_accounts->addProxyAccount(jsToAddress(_address)); +} + +bool WebThreeStubServerBase::eth_unregister(int _id) +{ + return m_accounts->removeProxyAccount(_id); +} + +Json::Value WebThreeStubServerBase::eth_queuedTransactions(int _id) +{ + Json::Value ret(Json::arrayValue); + for (TransactionSkeleton const& t: m_accounts->getQueuedTransactions(_id)) + ret.append(toJson(t)); + m_accounts->clearQueue(_id); + return ret; +} + std::string WebThreeStubServerBase::shh_newGroup(std::string const& _id, std::string const& _who) { (void)_id; @@ -690,24 +701,27 @@ std::string WebThreeStubServerBase::eth_transact(Json::Value const& _json) std::string ret; TransactionSkeleton t = toTransaction(_json); if (!t.from) - t.from = m_accounts.getDefaultCallAccount(); - if (!m_accounts.isRealAccount(t.from)) - { -// if (m_accounts.isProxyAccount(t.from)) -// m_accounts.storeTransaction(t); - return ret; - } + t.from = m_accounts->getDefaultCallAccount(); if (!t.gasPrice) t.gasPrice = 10 * dev::eth::szabo; if (!t.gas) t.gas = min(client()->gasLimitRemaining(), client()->balanceAt(t.from) / t.gasPrice); + if (!m_accounts->isRealAccount(t.from)) + { + if (m_accounts->isProxyAccount(t.from)) + { + // todo "authenticate for proxy" + m_accounts->queueTransaction(t); + } + return ret; + } if (authenticate(t)) { if (t.to) // TODO: from qethereum, insert validification hook here. - client()->transact(m_accounts.secretKey(t.from), t.value, t.to, t.data, t.gas, t.gasPrice); + client()->transact(m_accounts->secretKey(t.from), t.value, t.to, t.data, t.gas, t.gasPrice); else - ret = toJS(client()->transact(m_accounts.secretKey(t.from), t.value, t.data, t.gas, t.gasPrice)); + ret = toJS(client()->transact(m_accounts->secretKey(t.from), t.value, t.data, t.gas, t.gasPrice)); client()->flushTransactions(); } return ret; @@ -744,3 +758,8 @@ bool WebThreeStubServerBase::eth_uninstallFilter(int _id) client()->uninstallWatch(_id); return true; } + +void WebThreeStubServerBase::setAccounts(const std::vector& _accounts) +{ + m_accounts->setAccounts(_accounts); +} diff --git a/libweb3jsonrpc/WebThreeStubServerBase.h b/libweb3jsonrpc/WebThreeStubServerBase.h index 988f13b8f..62260d11d 100644 --- a/libweb3jsonrpc/WebThreeStubServerBase.h +++ b/libweb3jsonrpc/WebThreeStubServerBase.h @@ -23,8 +23,8 @@ #pragma once +#include #include -#include #include #include #pragma GCC diagnostic push @@ -36,6 +36,7 @@ namespace dev { class WebThreeNetworkFace; +class AccountHolder; class KeyPair; namespace eth { @@ -54,32 +55,6 @@ public: virtual void put(std::string const& _name, std::string const& _key, std::string const& _value) = 0; }; - -/** - * Manages real accounts (where we know the secret key) and proxy accounts (where transactions - * to be sent from these accounts are forwarded to a proxy on the other side). - */ -class AccountHolder -{ -public: - explicit AccountHolder(std::function const& _client): m_client(_client) {} - - /// Sets or resets the list of real accounts. - void setAccounts(std::vector const& _accounts); - std::vector const& getRealAccounts() const { return m_accounts; } - bool isRealAccount(dev::Address const& _account) const { return m_keyPairs.count(_account) > 0; } - Secret const& secretKey(dev::Address const& _account) const { return m_keyPairs.at(_account).secret(); } - std::vector const& getAllAccounts() const { return m_accounts; /*todo */} - dev::Address const& getDefaultCallAccount() const; - -private: - std::map m_keyPairs; - std::vector m_accounts; - std::function m_client; - -}; - - /** * @brief JSON-RPC api implementation * @todo filters should work on unsigned instead of int @@ -137,6 +112,10 @@ public: virtual Json::Value eth_getWork(); virtual bool eth_submitWork(std::string const& _nonce); + virtual int eth_register(std::string const& _address); + virtual bool eth_unregister(int _id); + virtual Json::Value eth_queuedTransactions(int _id); + virtual std::string db_get(std::string const& _name, std::string const& _key); virtual std::string db_getString(std::string const& _name, std::string const& _key); virtual bool db_put(std::string const& _name, std::string const& _key, std::string const& _value); @@ -152,7 +131,7 @@ public: virtual bool shh_post(Json::Value const& _json); virtual bool shh_uninstallFilter(int _id); - void setAccounts(std::vector const& _accounts) { m_accounts.setAccounts(_accounts); } + void setAccounts(std::vector const& _accounts); void setIdentities(std::vector const& _ids); std::map const& ids() const { return m_ids; } @@ -167,7 +146,7 @@ protected: std::map m_ids; std::map m_shhWatches; - AccountHolder m_accounts; + std::shared_ptr m_accounts; }; } //namespace dev diff --git a/libweb3jsonrpc/abstractwebthreestubserver.h b/libweb3jsonrpc/abstractwebthreestubserver.h index 53d7f856d..e40c68acd 100644 --- a/libweb3jsonrpc/abstractwebthreestubserver.h +++ b/libweb3jsonrpc/abstractwebthreestubserver.h @@ -55,6 +55,9 @@ class AbstractWebThreeStubServer : public jsonrpc::AbstractServerbindAndAddMethod(jsonrpc::Procedure("eth_logs", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_ARRAY, "param1",jsonrpc::JSON_OBJECT, NULL), &AbstractWebThreeStubServer::eth_logsI); this->bindAndAddMethod(jsonrpc::Procedure("eth_getWork", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_ARRAY, NULL), &AbstractWebThreeStubServer::eth_getWorkI); this->bindAndAddMethod(jsonrpc::Procedure("eth_submitWork", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_BOOLEAN, "param1",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_submitWorkI); + this->bindAndAddMethod(jsonrpc::Procedure("eth_register", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_INTEGER, "param1",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_registerI); + this->bindAndAddMethod(jsonrpc::Procedure("eth_unregister", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_BOOLEAN, "param1",jsonrpc::JSON_INTEGER, NULL), &AbstractWebThreeStubServer::eth_unregisterI); + this->bindAndAddMethod(jsonrpc::Procedure("eth_queuedTransactions", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_ARRAY, "param1",jsonrpc::JSON_INTEGER, NULL), &AbstractWebThreeStubServer::eth_queuedTransactionsI); this->bindAndAddMethod(jsonrpc::Procedure("db_put", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_BOOLEAN, "param1",jsonrpc::JSON_STRING,"param2",jsonrpc::JSON_STRING,"param3",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::db_putI); this->bindAndAddMethod(jsonrpc::Procedure("db_get", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_STRING, "param1",jsonrpc::JSON_STRING,"param2",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::db_getI); this->bindAndAddMethod(jsonrpc::Procedure("db_putString", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_BOOLEAN, "param1",jsonrpc::JSON_STRING,"param2",jsonrpc::JSON_STRING,"param3",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::db_putStringI); @@ -253,6 +256,18 @@ class AbstractWebThreeStubServer : public jsonrpc::AbstractServereth_submitWork(request[0u].asString()); } + inline virtual void eth_registerI(const Json::Value &request, Json::Value &response) + { + response = this->eth_register(request[0u].asString()); + } + inline virtual void eth_unregisterI(const Json::Value &request, Json::Value &response) + { + response = this->eth_unregister(request[0u].asInt()); + } + inline virtual void eth_queuedTransactionsI(const Json::Value &request, Json::Value &response) + { + response = this->eth_queuedTransactions(request[0u].asInt()); + } inline virtual void db_putI(const Json::Value &request, Json::Value &response) { response = this->db_put(request[0u].asString(), request[1u].asString(), request[2u].asString()); @@ -349,6 +364,9 @@ class AbstractWebThreeStubServer : public jsonrpc::AbstractServer. + */ +/** + * @author Christian R + * @date 2015 + * Unit tests for the account holder used by the WebThreeStubServer. + */ + +#include +#include + +namespace dev +{ +namespace test +{ + +BOOST_AUTO_TEST_SUITE(AccountHolderTest) + +BOOST_AUTO_TEST_CASE(ProxyAccountUseCase) +{ + AccountHolder h = AccountHolder(std::function()); + BOOST_CHECK(h.getAllAccounts().empty()); + BOOST_CHECK(h.getRealAccounts().empty()); + Address addr("abababababababababababababababababababab"); + Address addr2("abababababababababababababababababababab"); + int id = h.addProxyAccount(addr); + BOOST_CHECK(h.getQueuedTransactions(id).empty()); + // register it again + int secondID = h.addProxyAccount(addr); + BOOST_CHECK(h.getQueuedTransactions(secondID).empty()); + + eth::TransactionSkeleton t1; + eth::TransactionSkeleton t2; + t1.from = addr; + t1.data = fromHex("12345678"); + t2.from = addr; + t2.data = fromHex("abcdef"); + BOOST_CHECK(h.getQueuedTransactions(id).empty()); + h.queueTransaction(t1); + BOOST_CHECK_EQUAL(1, h.getQueuedTransactions(id).size()); + h.queueTransaction(t2); + BOOST_REQUIRE_EQUAL(2, h.getQueuedTransactions(id).size()); + + // second proxy should not see transactions + BOOST_CHECK(h.getQueuedTransactions(secondID).empty()); + + BOOST_CHECK(h.getQueuedTransactions(id)[0].data == t1.data); + BOOST_CHECK(h.getQueuedTransactions(id)[1].data == t2.data); + + h.clearQueue(id); + BOOST_CHECK(h.getQueuedTransactions(id).empty()); + // removing fails because it never existed + BOOST_CHECK(!h.removeProxyAccount(secondID)); + BOOST_CHECK(h.removeProxyAccount(id)); +} + +BOOST_AUTO_TEST_SUITE_END() + +} +} diff --git a/test/webthreestubclient.h b/test/webthreestubclient.h index ee175b058..70aa9db99 100644 --- a/test/webthreestubclient.h +++ b/test/webthreestubclient.h @@ -447,6 +447,36 @@ class WebThreeStubClient : public jsonrpc::Client else throw jsonrpc::JsonRpcException(jsonrpc::Errors::ERROR_CLIENT_INVALID_RESPONSE, result.toStyledString()); } + int eth_register(const std::string& param1) throw (jsonrpc::JsonRpcException) + { + Json::Value p; + p.append(param1); + Json::Value result = this->CallMethod("eth_register",p); + if (result.isInt()) + return result.asInt(); + else + throw jsonrpc::JsonRpcException(jsonrpc::Errors::ERROR_CLIENT_INVALID_RESPONSE, result.toStyledString()); + } + bool eth_unregister(int param1) throw (jsonrpc::JsonRpcException) + { + Json::Value p; + p.append(param1); + Json::Value result = this->CallMethod("eth_unregister",p); + if (result.isBool()) + return result.asBool(); + else + throw jsonrpc::JsonRpcException(jsonrpc::Errors::ERROR_CLIENT_INVALID_RESPONSE, result.toStyledString()); + } + Json::Value eth_queuedTransactions(int param1) throw (jsonrpc::JsonRpcException) + { + Json::Value p; + p.append(param1); + Json::Value result = this->CallMethod("eth_queuedTransactions",p); + if (result.isArray()) + return result; + else + throw jsonrpc::JsonRpcException(jsonrpc::Errors::ERROR_CLIENT_INVALID_RESPONSE, result.toStyledString()); + } bool db_put(const std::string& param1, const std::string& param2, const std::string& param3) throw (jsonrpc::JsonRpcException) { Json::Value p;