diff --git a/libdevcore/Exceptions.h b/libdevcore/Exceptions.h index 5d03c195f..061f181b3 100644 --- a/libdevcore/Exceptions.h +++ b/libdevcore/Exceptions.h @@ -22,6 +22,7 @@ #pragma once #include +#include #include #include #include "CommonData.h" @@ -40,6 +41,7 @@ struct NoNetworking: virtual Exception {}; struct NoUPnPDevice: virtual Exception {}; struct RootNotFound: virtual Exception {}; struct FileError: virtual Exception {}; +struct InterfaceNotSupported: virtual Exception { public: InterfaceNotSupported(std::string _f): m_f("Interface " + _f + " not supported.") {} virtual const char* what() const noexcept { return m_f.c_str(); } private: std::string m_f; }; // error information to be added to exceptions typedef boost::error_info errinfo_invalidSymbol; diff --git a/libweb3jsonrpc/WebThreeStubServer.cpp b/libweb3jsonrpc/WebThreeStubServer.cpp index 874c14331..f728a29f5 100644 --- a/libweb3jsonrpc/WebThreeStubServer.cpp +++ b/libweb3jsonrpc/WebThreeStubServer.cpp @@ -21,198 +21,19 @@ * @date 2014 */ -#include -#include -#include -#include "WebThreeStubServer.h" -#include -#include -#include -#include -#include #include +#include #include -#include -#include -#include +#include "WebThreeStubServer.h" using namespace std; using namespace dev; using namespace dev::eth; -static Json::Value toJson(dev::eth::BlockInfo const& _bi) -{ - Json::Value res; - res["hash"] = boost::lexical_cast(_bi.hash); - res["parentHash"] = toJS(_bi.parentHash); - res["sha3Uncles"] = toJS(_bi.sha3Uncles); - res["miner"] = toJS(_bi.coinbaseAddress); - res["stateRoot"] = toJS(_bi.stateRoot); - res["transactionsRoot"] = toJS(_bi.transactionsRoot); - res["difficulty"] = toJS(_bi.difficulty); - res["number"] = (int)_bi.number; - res["gasLimit"] = (int)_bi.gasLimit; - res["timestamp"] = (int)_bi.timestamp; - res["extraData"] = jsFromBinary(_bi.extraData); - res["nonce"] = toJS(_bi.nonce); - return res; -} - -static Json::Value toJson(dev::eth::Transaction const& _t) -{ - Json::Value res; - res["hash"] = toJS(_t.sha3()); - res["input"] = jsFromBinary(_t.data()); - res["to"] = toJS(_t.receiveAddress()); - res["from"] = toJS(_t.sender()); - res["gas"] = (int)_t.gas(); - res["gasPrice"] = toJS(_t.gasPrice()); - res["nonce"] = toJS(_t.nonce()); - res["value"] = toJS(_t.value()); - return res; -} - -static Json::Value toJson(dev::eth::LogEntry const& _e) -{ - Json::Value res; - - res["data"] = jsFromBinary(_e.data); - res["address"] = toJS(_e.address); - for (auto const& t: _e.topics) - res["topics"].append(toJS(t)); - return res; -} - -static Json::Value toJson(dev::eth::LogEntries const& _es) // commented to avoid warning. Uncomment once in use @ poC-7. -{ - Json::Value res; - for (dev::eth::LogEntry const& e: _es) - res.append(toJson(e)); - return res; -} - -static Json::Value toJson(std::map const& _storage) -{ - Json::Value res(Json::objectValue); - for (auto i: _storage) - res[toJS(i.first)] = toJS(i.second); - return res; -} - -static dev::eth::LogFilter toLogFilter(Json::Value const& _json) // commented to avoid warning. Uncomment once in use @ PoC-7. -{ - dev::eth::LogFilter filter; - if (!_json.isObject() || _json.empty()) - return filter; - - if (_json["earliest"].isInt()) - filter.withEarliest(_json["earliest"].asInt()); - if (_json["latest"].isInt()) - filter.withLatest(_json["lastest"].asInt()); - if (_json["max"].isInt()) - filter.withMax(_json["max"].asInt()); - if (_json["skip"].isInt()) - filter.withSkip(_json["skip"].asInt()); - if (!_json["address"].empty()) - { - if (_json["address"].isArray()) - { - for (auto i : _json["address"]) - if (i.isString()) - filter.address(jsToAddress(i.asString())); - } - else if (_json["address"].isString()) - filter.address(jsToAddress(_json["address"].asString())); - } - if (!_json["topics"].empty()) - { - if (_json["topics"].isArray()) - { - for (auto i: _json["topics"]) - if (i.isString()) - filter.topic(jsToU256(i.asString())); - } - else if(_json["topics"].isString()) - filter.topic(jsToU256(_json["topics"].asString())); - } - return filter; -} - -static shh::Message toMessage(Json::Value const& _json) -{ - shh::Message ret; - if (_json["from"].isString()) - ret.setFrom(jsToPublic(_json["from"].asString())); - if (_json["to"].isString()) - ret.setTo(jsToPublic(_json["to"].asString())); - if (_json["payload"].isString()) - ret.setPayload(jsToBytes(_json["payload"].asString())); - return ret; -} - -static shh::Envelope toSealed(Json::Value const& _json, shh::Message const& _m, Secret _from) -{ - unsigned ttl = 50; - unsigned workToProve = 50; - shh::BuildTopic bt; - - if (_json["ttl"].isInt()) - ttl = _json["ttl"].asInt(); - if (_json["workToProve"].isInt()) - workToProve = _json["workToProve"].asInt(); - if (!_json["topic"].empty()) - { - if (_json["topic"].isString()) - bt.shift(jsToBytes(_json["topic"].asString())); - else if (_json["topic"].isArray()) - for (auto i: _json["topic"]) - if (i.isString()) - bt.shift(jsToBytes(i.asString())); - } - return _m.seal(_from, bt, ttl, workToProve); -} - -static pair toWatch(Json::Value const& _json) -{ - shh::BuildTopicMask bt; - Public to; - - if (_json["to"].isString()) - to = jsToPublic(_json["to"].asString()); - - if (!_json["topic"].empty()) - { - if (_json["topic"].isString()) - bt.shift(jsToBytes(_json["topic"].asString())); - else if (_json["topic"].isArray()) - for (auto i: _json["topic"]) - if (i.isString()) - bt.shift(jsToBytes(i.asString())); - } - return make_pair(bt.toTopicMask(), to); -} - -static Json::Value toJson(h256 const& _h, shh::Envelope const& _e, shh::Message const& _m) -{ - Json::Value res; - res["hash"] = toJS(_h); - res["expiry"] = (int)_e.expiry(); - res["sent"] = (int)_e.sent(); - res["ttl"] = (int)_e.ttl(); - res["workProved"] = (int)_e.workProved(); - for (auto const& t: _e.topics()) - res["topics"].append(toJS(t)); - res["payload"] = toJS(_m.payload()); - res["from"] = toJS(_m.from()); - res["to"] = toJS(_m.to()); - return res; -} - WebThreeStubServer::WebThreeStubServer(jsonrpc::AbstractServerConnector& _conn, WebThreeDirect& _web3, std::vector const& _accounts): - AbstractWebThreeStubServer(_conn), + WebThreeStubServerBase(_conn, _accounts), m_web3(_web3) { - setAccounts(_accounts); auto path = getDataDir() + "/.web3"; boost::filesystem::create_directories(path); ldb::Options o; @@ -220,181 +41,27 @@ WebThreeStubServer::WebThreeStubServer(jsonrpc::AbstractServerConnector& _conn, ldb::DB::Open(o, path, &m_db); } -void WebThreeStubServer::setAccounts(std::vector const& _accounts) -{ - m_accounts.clear(); - for (auto i: _accounts) - m_accounts[i.address()] = i.secret(); -} - -void WebThreeStubServer::setIdentities(std::vector const& _ids) -{ - m_ids.clear(); - for (auto i: _ids) - m_ids[i.pub()] = i.secret(); -} - -dev::eth::Interface* WebThreeStubServer::client() const +dev::eth::Interface* WebThreeStubServer::client() { return m_web3.ethereum(); } -std::shared_ptr WebThreeStubServer::face() const +std::shared_ptr WebThreeStubServer::face() { return m_web3.whisper(); } -std::string WebThreeStubServer::web3_sha3(std::string const& _param1) -{ - return toJS(sha3(jsToBytes(_param1))); -} - -Json::Value WebThreeStubServer::eth_accounts() -{ - Json::Value ret(Json::arrayValue); - for (auto i: m_accounts) - ret.append(toJS(i.first)); - return ret; -} - -std::string WebThreeStubServer::shh_addToGroup(std::string const& _group, std::string const& _who) -{ - (void)_group; - (void)_who; - return ""; -} - -std::string WebThreeStubServer::eth_balanceAt(string const& _address) -{ - return toJS(client()->balanceAt(jsToAddress(_address), client()->getDefault())); -} - -Json::Value WebThreeStubServer::eth_blockByHash(std::string const& _hash) -{ - return toJson(client()->blockInfo(jsToFixed<32>(_hash))); -} - -Json::Value WebThreeStubServer::eth_blockByNumber(int const& _number) -{ - return toJson(client()->blockInfo(client()->hashFromNumber(_number))); -} - -static TransactionSkeleton toTransaction(Json::Value const& _json) -{ - TransactionSkeleton ret; - if (!_json.isObject() || _json.empty()) - return ret; - - if (_json["from"].isString()) - ret.from = jsToAddress(_json["from"].asString()); - if (_json["to"].isString()) - ret.to = jsToAddress(_json["to"].asString()); - if (!_json["value"].empty()) - { - if (_json["value"].isString()) - ret.value = jsToU256(_json["value"].asString()); - else if (_json["value"].isInt()) - ret.value = u256(_json["value"].asInt()); - } - if (!_json["gas"].empty()) - { - if (_json["gas"].isString()) - ret.gas = jsToU256(_json["gas"].asString()); - else if (_json["gas"].isInt()) - ret.gas = u256(_json["gas"].asInt()); - } - if (!_json["gasPrice"].empty()) - { - if (_json["gasPrice"].isString()) - ret.gasPrice = jsToU256(_json["gasPrice"].asString()); - else if (_json["gasPrice"].isInt()) - ret.gas = u256(_json["gas"].asInt()); - } - if (!_json["data"].empty()) - { - if (_json["data"].isString()) // ethereum.js has preconstructed the data array - ret.data = jsToBytes(_json["data"].asString()); - else if (_json["data"].isArray()) // old style: array of 32-byte-padded values. TODO: remove PoC-8 - for (auto i: _json["data"]) - dev::operator +=(ret.data, padded(jsToBytes(i.asString()), 32)); - } - - if (_json["code"].isString()) - ret.data = jsToBytes(_json["code"].asString()); - return ret; -} - -std::string WebThreeStubServer::eth_call(Json::Value const& _json) -{ - std::string ret; - TransactionSkeleton t = toTransaction(_json); - if (!t.from && m_accounts.size()) - { - auto b = m_accounts.begin()->first; - for (auto a: m_accounts) - if (client()->balanceAt(a.first) > client()->balanceAt(b)) - b = a.first; - t.from = b; - } - if (!m_accounts.count(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[t.from].secret(), t.value, t.to, t.data, t.gas, t.gasPrice)); - return ret; -} - -bool WebThreeStubServer::eth_changed(int const& _id) -{ - return client()->checkWatch(_id); -} - -std::string WebThreeStubServer::eth_codeAt(string const& _address) +dev::WebThreeNetworkFace* WebThreeStubServer::network() { - return jsFromBinary(client()->codeAt(jsToAddress(_address), client()->getDefault())); + return &m_web3; } -std::string WebThreeStubServer::eth_coinbase() +dev::WebThreeStubDatabaseFace* WebThreeStubServer::db() { - return toJS(client()->address()); + return this; } -double WebThreeStubServer::eth_countAt(string const& _address) -{ - return (double)(uint64_t)client()->countAt(jsToAddress(_address), client()->getDefault()); -} - -int WebThreeStubServer::eth_defaultBlock() -{ - return client()->getDefault(); -} - -std::string WebThreeStubServer::eth_gasPrice() -{ - return toJS(10 * dev::eth::szabo); -} - -std::string WebThreeStubServer::db_get(std::string const& _name, std::string const& _key) -{ - bytes k = sha3(_name).asBytes() + sha3(_key).asBytes(); - string ret; - m_db->Get(m_readOptions, ldb::Slice((char const*)k.data(), k.size()), &ret); - return toJS(dev::asBytes(ret)); -} - -Json::Value WebThreeStubServer::eth_filterLogs(int const& _id) -{ - return toJson(client()->logs(_id)); -} - -Json::Value WebThreeStubServer::eth_logs(Json::Value const& _json) -{ - return toJson(client()->logs(toLogFilter(_json))); -} - -std::string WebThreeStubServer::db_getString(std::string const& _name, std::string const& _key) +std::string WebThreeStubServer::get(std::string const& _name, std::string const& _key) { bytes k = sha3(_name).asBytes() + sha3(_key).asBytes(); string ret; @@ -402,289 +69,9 @@ std::string WebThreeStubServer::db_getString(std::string const& _name, std::stri return ret; } -bool WebThreeStubServer::shh_haveIdentity(std::string const& _id) -{ - return m_ids.count(jsToPublic(_id)) > 0; -} - -bool WebThreeStubServer::eth_listening() -{ - return m_web3.isNetworkStarted(); -} - -bool WebThreeStubServer::eth_mining() -{ - return client()->isMining(); -} - -int WebThreeStubServer::eth_newFilter(Json::Value const& _json) -{ - unsigned ret = -1; - ret = client()->installWatch(toLogFilter(_json)); - return ret; -} - -int WebThreeStubServer::eth_newFilterString(std::string const& _filter) -{ - unsigned ret = -1; - if (_filter.compare("chain") == 0) - ret = client()->installWatch(dev::eth::ChainChangedFilter); - else if (_filter.compare("pending") == 0) - ret = client()->installWatch(dev::eth::PendingChangedFilter); - return ret; -} - -std::string WebThreeStubServer::shh_newGroup(std::string const& _id, std::string const& _who) -{ - (void)_id; - (void)_who; - return ""; -} - -std::string WebThreeStubServer::shh_newIdentity() -{ -// cnote << this << m_ids; - KeyPair kp = KeyPair::create(); - m_ids[kp.pub()] = kp.secret(); - return toJS(kp.pub()); -} - -Json::Value WebThreeStubServer::eth_compilers() -{ - Json::Value ret(Json::arrayValue); - ret.append("lll"); - ret.append("solidity"); - ret.append("serpent"); - return ret; -} - -std::string WebThreeStubServer::eth_lll(std::string const& _code) -{ - string res; - vector errors; - res = toJS(dev::eth::compileLLL(_code, true, &errors)); - cwarn << "LLL compilation errors: " << errors; - return res; -} - -std::string WebThreeStubServer::eth_serpent(std::string const& _code) -{ - string res; - try - { - res = toJS(dev::asBytes(::compile(_code))); - } - catch (string err) - { - cwarn << "Solidity compilation error: " << err; - } - catch (...) - { - cwarn << "Uncought serpent compilation exception"; - } - return res; -} - -std::string WebThreeStubServer::eth_solidity(std::string const& _code) -{ - string res; - dev::solidity::CompilerStack compiler; - try - { - res = toJS(compiler.compile(_code, true)); - } - catch (dev::Exception const& exception) - { - ostringstream error; - solidity::SourceReferenceFormatter::printExceptionInformation(error, exception, "Error", compiler); - cwarn << "Solidity compilation error: " << error.str(); - } - catch (...) - { - cwarn << "Uncought solidity compilation exception"; - } - return res; -} - -int WebThreeStubServer::eth_number() -{ - return client()->number() + 1; -} - -int WebThreeStubServer::eth_peerCount() -{ - return m_web3.peerCount(); -} - -bool WebThreeStubServer::shh_post(Json::Value const& _json) -{ - shh::Message m = toMessage(_json); - Secret from; - - if (m.from() && m_ids.count(m.from())) - { - cwarn << "Silently signing message from identity" << m.from().abridged() << ": User validation hook goes here."; - // TODO: insert validification hook here. - from = m_ids[m.from()]; - } - - face()->inject(toSealed(_json, m, from)); - return true; -} - -bool WebThreeStubServer::db_put(std::string const& _name, std::string const& _key, std::string const& _value) +void WebThreeStubServer::put(std::string const& _name, std::string const& _key, std::string const& _value) { bytes k = sha3(_name).asBytes() + sha3(_key).asBytes(); - bytes v = jsToBytes(_value); - m_db->Put(m_writeOptions, ldb::Slice((char const*)k.data(), k.size()), ldb::Slice((char const*)v.data(), v.size())); - return true; -} - -bool WebThreeStubServer::db_putString(std::string const& _name, std::string const& _key, std::string const& _value) -{ - bytes k = sha3(_name).asBytes() + sha3(_key).asBytes(); - string v = _value; - m_db->Put(m_writeOptions, ldb::Slice((char const*)k.data(), k.size()), ldb::Slice((char const*)v.data(), v.size())); - return true; -} - -bool WebThreeStubServer::eth_setCoinbase(std::string const& _address) -{ - client()->setAddress(jsToAddress(_address)); - return true; -} - -bool WebThreeStubServer::eth_setDefaultBlock(int const& _block) -{ - client()->setDefault(_block); - return true; -} - -bool WebThreeStubServer::eth_setListening(bool const& _listening) -{ - if (_listening) - m_web3.startNetwork(); - else - m_web3.stopNetwork(); - return true; -} - -bool WebThreeStubServer::eth_setMining(bool const& _mining) -{ - if (_mining) - client()->startMining(); - else - client()->stopMining(); - return true; -} - -Json::Value WebThreeStubServer::shh_changed(int const& _id) -{ - Json::Value ret(Json::arrayValue); - auto pub = m_shhWatches[_id]; - if (!pub || m_ids.count(pub)) - for (h256 const& h: face()->checkWatch(_id)) - { - auto e = face()->envelope(h); - shh::Message m; - if (pub) - { - cwarn << "Silently decrypting message from identity" << pub.abridged() << ": User validation hook goes here."; - m = e.open(m_ids[pub]); - if (!m) - continue; - } - else - m = e.open(); - ret.append(toJson(h, e, m)); - } - - return ret; -} - -int WebThreeStubServer::shh_newFilter(Json::Value const& _json) -{ - auto w = toWatch(_json); - auto ret = face()->installWatch(w.first); - m_shhWatches.insert(make_pair(ret, w.second)); - return ret; -} - -bool WebThreeStubServer::shh_uninstallFilter(int const& _id) -{ - face()->uninstallWatch(_id); - return true; -} - -std::string WebThreeStubServer::eth_stateAt(string const& _address, string const& _storage) -{ - return toJS(client()->stateAt(jsToAddress(_address), jsToU256(_storage), client()->getDefault())); -} - -Json::Value WebThreeStubServer::eth_storageAt(string const& _address) -{ - return toJson(client()->storageAt(jsToAddress(_address))); -} - -std::string WebThreeStubServer::eth_transact(Json::Value const& _json) -{ - std::string ret; - TransactionSkeleton t = toTransaction(_json); - if (!t.from && m_accounts.size()) - { - auto b = m_accounts.begin()->first; - for (auto a: m_accounts) - if (client()->balanceAt(a.first) > client()->balanceAt(b)) - b = a.first; - t.from = b; - } - if (!m_accounts.count(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); - if (authenticate(t)) - { - if (t.to) - // TODO: from qethereum, insert validification hook here. - client()->transact(m_accounts[t.from].secret(), t.value, t.to, t.data, t.gas, t.gasPrice); - else - ret = toJS(client()->transact(m_accounts[t.from].secret(), t.value, t.data, t.gas, t.gasPrice)); - client()->flushTransactions(); - } - return ret; -} - -bool WebThreeStubServer::authenticate(TransactionSkeleton const& _t) const -{ - cwarn << "Silently signing transaction from address" << _t.from.abridged() << ": User validation hook goes here."; - return true; -} - -Json::Value WebThreeStubServer::eth_transactionByHash(std::string const& _hash, int const& _i) -{ - return toJson(client()->transaction(jsToFixed<32>(_hash), _i)); -} - -Json::Value WebThreeStubServer::eth_transactionByNumber(int const& _number, int const& _i) -{ - return toJson(client()->transaction(client()->hashFromNumber(_number), _i)); -} - -Json::Value WebThreeStubServer::eth_uncleByHash(std::string const& _hash, int const& _i) -{ - return toJson(client()->uncle(jsToFixed<32>(_hash), _i)); -} - -Json::Value WebThreeStubServer::eth_uncleByNumber(int const& _number, int const& _i) -{ - return toJson(client()->uncle(client()->hashFromNumber(_number), _i)); -} - -bool WebThreeStubServer::eth_uninstallFilter(int const& _id) -{ - client()->uninstallWatch(_id); - return true; + m_db->Put(m_writeOptions, ldb::Slice((char const*)k.data(), k.size()), ldb::Slice((char const*)_value.data(), _value.size())); } diff --git a/libweb3jsonrpc/WebThreeStubServer.h b/libweb3jsonrpc/WebThreeStubServer.h index 0f81fce9d..b76fc17e9 100644 --- a/libweb3jsonrpc/WebThreeStubServer.h +++ b/libweb3jsonrpc/WebThreeStubServer.h @@ -28,111 +28,32 @@ #include #pragma warning(pop) -#include -#include -#include -#pragma GCC diagnostic push -#pragma GCC diagnostic ignored "-Wunused-parameter" -#include "abstractwebthreestubserver.h" -#pragma GCC diagnostic pop - -namespace ldb = leveldb; +#include "WebThreeStubServerBase.h" namespace dev { class WebThreeDirect; -class KeyPair; -class TransactionSkeleton; -namespace eth -{ -class Interface; -} -namespace shh -{ -class Interface; -} } /** - * @brief JSON-RPC api implementation - * @todo filters should work on unsigned instead of int - * unsigned are not supported in json-rpc-cpp and there are bugs with double in json-rpc-cpp version 0.2.1 - * @todo split these up according to subprotocol (eth, shh, db, p2p, web3) and make it /very/ clear about how to add other subprotocols. - * @todo modularise everything so additional subprotocols don't need to change this file. + * @brief JSON-RPC api implementation for WebThreeDirect */ -class WebThreeStubServer: public AbstractWebThreeStubServer +class WebThreeStubServer: public dev::WebThreeStubServerBase, public dev::WebThreeStubDatabaseFace { public: WebThreeStubServer(jsonrpc::AbstractServerConnector& _conn, dev::WebThreeDirect& _web3, std::vector const& _accounts); - virtual std::string web3_sha3(std::string const& _param1); - virtual Json::Value eth_accounts(); - virtual std::string eth_balanceAt(std::string const& _address); - virtual Json::Value eth_blockByHash(std::string const& _hash); - virtual Json::Value eth_blockByNumber(int const& _number); - virtual std::string eth_call(Json::Value const& _json); - virtual bool eth_changed(int const& _id); - virtual std::string eth_codeAt(std::string const& _address); - virtual std::string eth_coinbase(); - virtual Json::Value eth_compilers(); - virtual double eth_countAt(std::string const& _address); - virtual int eth_defaultBlock(); - virtual std::string eth_gasPrice(); - virtual Json::Value eth_filterLogs(int const& _id); - virtual Json::Value eth_logs(Json::Value const& _json); - virtual bool eth_listening(); - virtual bool eth_mining(); - virtual int eth_newFilter(Json::Value const& _json); - virtual int eth_newFilterString(std::string const& _filter); - virtual int eth_number(); - virtual int eth_peerCount(); - virtual bool eth_setCoinbase(std::string const& _address); - virtual bool eth_setDefaultBlock(int const& _block); - virtual bool eth_setListening(bool const& _listening); - virtual std::string eth_lll(std::string const& _s); - virtual std::string eth_serpent(std::string const& _s); - virtual bool eth_setMining(bool const& _mining); - virtual std::string eth_solidity(std::string const& _code); - virtual std::string eth_stateAt(std::string const& _address, std::string const& _storage); - virtual Json::Value eth_storageAt(std::string const& _address); - virtual std::string eth_transact(Json::Value const& _json); - virtual Json::Value eth_transactionByHash(std::string const& _hash, int const& _i); - virtual Json::Value eth_transactionByNumber(int const& _number, int const& _i); - virtual Json::Value eth_uncleByHash(std::string const& _hash, int const& _i); - virtual Json::Value eth_uncleByNumber(int const& _number, int const& _i); - virtual bool eth_uninstallFilter(int const& _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); - virtual bool db_putString(std::string const& _name, std::string const& _key, std::string const& _value); - - virtual std::string shh_addToGroup(std::string const& _group, std::string const& _who); - virtual Json::Value shh_changed(int const& _id); - virtual bool shh_haveIdentity(std::string const& _id); - virtual int shh_newFilter(Json::Value const& _json); - virtual std::string shh_newGroup(std::string const& _id, std::string const& _who); - virtual std::string shh_newIdentity(); - virtual bool shh_post(Json::Value const& _json); - virtual bool shh_uninstallFilter(int const& _id); - - void setAccounts(std::vector const& _accounts); - void setIdentities(std::vector const& _ids); - std::map const& ids() const { return m_ids; } - -protected: - virtual bool authenticate(dev::TransactionSkeleton const& _t) const; +private: + dev::eth::Interface* client() override; + std::shared_ptr face() override; + dev::WebThreeNetworkFace* network() override; + dev::WebThreeStubDatabaseFace* db() override; + std::string get(std::string const& _name, std::string const& _key) override; + void put(std::string const& _name, std::string const& _key, std::string const& _value) override; private: - dev::eth::Interface* client() const; - std::shared_ptr face() const; dev::WebThreeDirect& m_web3; - std::map m_accounts; - - ldb::ReadOptions m_readOptions; - ldb::WriteOptions m_writeOptions; - ldb::DB* m_db; - - std::map m_ids; - std::map m_shhWatches; + leveldb::ReadOptions m_readOptions; + leveldb::WriteOptions m_writeOptions; + leveldb::DB* m_db; }; diff --git a/libweb3jsonrpc/WebThreeStubServerBase.cpp b/libweb3jsonrpc/WebThreeStubServerBase.cpp new file mode 100644 index 000000000..565db9555 --- /dev/null +++ b/libweb3jsonrpc/WebThreeStubServerBase.cpp @@ -0,0 +1,664 @@ +/* + 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 WebThreeStubServerBase.cpp + * @authors: + * Gav Wood + * Marek Kotewicz + * @date 2014 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "WebThreeStubServerBase.h" + +using namespace std; +using namespace dev; +using namespace dev::eth; + +static Json::Value toJson(dev::eth::BlockInfo const& _bi) +{ + Json::Value res; + res["hash"] = boost::lexical_cast(_bi.hash); + res["parentHash"] = toJS(_bi.parentHash); + res["sha3Uncles"] = toJS(_bi.sha3Uncles); + res["miner"] = toJS(_bi.coinbaseAddress); + res["stateRoot"] = toJS(_bi.stateRoot); + res["transactionsRoot"] = toJS(_bi.transactionsRoot); + res["difficulty"] = toJS(_bi.difficulty); + res["number"] = (int)_bi.number; + res["gasLimit"] = (int)_bi.gasLimit; + res["timestamp"] = (int)_bi.timestamp; + res["extraData"] = jsFromBinary(_bi.extraData); + res["nonce"] = toJS(_bi.nonce); + return res; +} + +static Json::Value toJson(dev::eth::Transaction const& _t) +{ + Json::Value res; + res["hash"] = toJS(_t.sha3()); + res["input"] = jsFromBinary(_t.data()); + res["to"] = toJS(_t.receiveAddress()); + res["from"] = toJS(_t.sender()); + res["gas"] = (int)_t.gas(); + res["gasPrice"] = toJS(_t.gasPrice()); + res["nonce"] = toJS(_t.nonce()); + res["value"] = toJS(_t.value()); + return res; +} + +static Json::Value toJson(dev::eth::LogEntry const& _e) +{ + Json::Value res; + + res["data"] = jsFromBinary(_e.data); + res["address"] = toJS(_e.address); + for (auto const& t: _e.topics) + res["topics"].append(toJS(t)); + return res; +} + +static Json::Value toJson(dev::eth::LogEntries const& _es) // commented to avoid warning. Uncomment once in use @ poC-7. +{ + Json::Value res; + for (dev::eth::LogEntry const& e: _es) + res.append(toJson(e)); + return res; +} + +static Json::Value toJson(std::map const& _storage) +{ + Json::Value res(Json::objectValue); + for (auto i: _storage) + res[toJS(i.first)] = toJS(i.second); + return res; +} + +static dev::eth::LogFilter toLogFilter(Json::Value const& _json) // commented to avoid warning. Uncomment once in use @ PoC-7. +{ + dev::eth::LogFilter filter; + if (!_json.isObject() || _json.empty()) + return filter; + + if (_json["earliest"].isInt()) + filter.withEarliest(_json["earliest"].asInt()); + if (_json["latest"].isInt()) + filter.withLatest(_json["lastest"].asInt()); + if (_json["max"].isInt()) + filter.withMax(_json["max"].asInt()); + if (_json["skip"].isInt()) + filter.withSkip(_json["skip"].asInt()); + if (!_json["address"].empty()) + { + if (_json["address"].isArray()) + { + for (auto i : _json["address"]) + if (i.isString()) + filter.address(jsToAddress(i.asString())); + } + else if (_json["address"].isString()) + filter.address(jsToAddress(_json["address"].asString())); + } + if (!_json["topics"].empty()) + { + if (_json["topics"].isArray()) + { + for (auto i: _json["topics"]) + if (i.isString()) + filter.topic(jsToU256(i.asString())); + } + else if(_json["topics"].isString()) + filter.topic(jsToU256(_json["topics"].asString())); + } + return filter; +} + +static shh::Message toMessage(Json::Value const& _json) +{ + shh::Message ret; + if (_json["from"].isString()) + ret.setFrom(jsToPublic(_json["from"].asString())); + if (_json["to"].isString()) + ret.setTo(jsToPublic(_json["to"].asString())); + if (_json["payload"].isString()) + ret.setPayload(jsToBytes(_json["payload"].asString())); + return ret; +} + +static shh::Envelope toSealed(Json::Value const& _json, shh::Message const& _m, Secret _from) +{ + unsigned ttl = 50; + unsigned workToProve = 50; + shh::BuildTopic bt; + + if (_json["ttl"].isInt()) + ttl = _json["ttl"].asInt(); + if (_json["workToProve"].isInt()) + workToProve = _json["workToProve"].asInt(); + if (!_json["topic"].empty()) + { + if (_json["topic"].isString()) + bt.shift(jsToBytes(_json["topic"].asString())); + else if (_json["topic"].isArray()) + for (auto i: _json["topic"]) + if (i.isString()) + bt.shift(jsToBytes(i.asString())); + } + return _m.seal(_from, bt, ttl, workToProve); +} + +static pair toWatch(Json::Value const& _json) +{ + shh::BuildTopicMask bt; + Public to; + + if (_json["to"].isString()) + to = jsToPublic(_json["to"].asString()); + + if (!_json["topic"].empty()) + { + if (_json["topic"].isString()) + bt.shift(jsToBytes(_json["topic"].asString())); + else if (_json["topic"].isArray()) + for (auto i: _json["topic"]) + if (i.isString()) + bt.shift(jsToBytes(i.asString())); + } + return make_pair(bt.toTopicMask(), to); +} + +static Json::Value toJson(h256 const& _h, shh::Envelope const& _e, shh::Message const& _m) +{ + Json::Value res; + res["hash"] = toJS(_h); + res["expiry"] = (int)_e.expiry(); + res["sent"] = (int)_e.sent(); + res["ttl"] = (int)_e.ttl(); + res["workProved"] = (int)_e.workProved(); + for (auto const& t: _e.topics()) + res["topics"].append(toJS(t)); + res["payload"] = toJS(_m.payload()); + res["from"] = toJS(_m.from()); + res["to"] = toJS(_m.to()); + return res; +} + +WebThreeStubServerBase::WebThreeStubServerBase(jsonrpc::AbstractServerConnector& _conn, std::vector const& _accounts): + AbstractWebThreeStubServer(_conn) +{ + setAccounts(_accounts); +} + +void WebThreeStubServerBase::setAccounts(std::vector const& _accounts) +{ + m_accounts.clear(); + for (auto i: _accounts) + m_accounts[i.address()] = i.secret(); +} + +void WebThreeStubServerBase::setIdentities(std::vector const& _ids) +{ + m_ids.clear(); + for (auto i: _ids) + m_ids[i.pub()] = i.secret(); +} + +std::string WebThreeStubServerBase::web3_sha3(std::string const& _param1) +{ + return toJS(sha3(jsToBytes(_param1))); +} + +Json::Value WebThreeStubServerBase::eth_accounts() +{ + Json::Value ret(Json::arrayValue); + for (auto i: m_accounts) + ret.append(toJS(i.first)); + return ret; +} + +std::string WebThreeStubServerBase::shh_addToGroup(std::string const& _group, std::string const& _who) +{ + (void)_group; + (void)_who; + return ""; +} + +std::string WebThreeStubServerBase::eth_balanceAt(string const& _address) +{ + return toJS(client()->balanceAt(jsToAddress(_address), client()->getDefault())); +} + +Json::Value WebThreeStubServerBase::eth_blockByHash(std::string const& _hash) +{ + return toJson(client()->blockInfo(jsToFixed<32>(_hash))); +} + +Json::Value WebThreeStubServerBase::eth_blockByNumber(int const& _number) +{ + return toJson(client()->blockInfo(client()->hashFromNumber(_number))); +} + +static TransactionSkeleton toTransaction(Json::Value const& _json) +{ + TransactionSkeleton ret; + if (!_json.isObject() || _json.empty()) + return ret; + + if (_json["from"].isString()) + ret.from = jsToAddress(_json["from"].asString()); + if (_json["to"].isString()) + ret.to = jsToAddress(_json["to"].asString()); + if (!_json["value"].empty()) + { + if (_json["value"].isString()) + ret.value = jsToU256(_json["value"].asString()); + else if (_json["value"].isInt()) + ret.value = u256(_json["value"].asInt()); + } + if (!_json["gas"].empty()) + { + if (_json["gas"].isString()) + ret.gas = jsToU256(_json["gas"].asString()); + else if (_json["gas"].isInt()) + ret.gas = u256(_json["gas"].asInt()); + } + if (!_json["gasPrice"].empty()) + { + if (_json["gasPrice"].isString()) + ret.gasPrice = jsToU256(_json["gasPrice"].asString()); + else if (_json["gasPrice"].isInt()) + ret.gas = u256(_json["gas"].asInt()); + } + if (!_json["data"].empty()) + { + if (_json["data"].isString()) // ethereum.js has preconstructed the data array + ret.data = jsToBytes(_json["data"].asString()); + else if (_json["data"].isArray()) // old style: array of 32-byte-padded values. TODO: remove PoC-8 + for (auto i: _json["data"]) + dev::operator +=(ret.data, padded(jsToBytes(i.asString()), 32)); + } + + if (_json["code"].isString()) + ret.data = jsToBytes(_json["code"].asString()); + return ret; +} + +std::string WebThreeStubServerBase::eth_call(Json::Value const& _json) +{ + std::string ret; + TransactionSkeleton t = toTransaction(_json); + if (!t.from && m_accounts.size()) + { + auto b = m_accounts.begin()->first; + for (auto a: m_accounts) + if (client()->balanceAt(a.first) > client()->balanceAt(b)) + b = a.first; + t.from = b; + } + if (!m_accounts.count(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[t.from].secret(), t.value, t.to, t.data, t.gas, t.gasPrice)); + return ret; +} + +bool WebThreeStubServerBase::eth_changed(int const& _id) +{ + return client()->checkWatch(_id); +} + +std::string WebThreeStubServerBase::eth_codeAt(string const& _address) +{ + return jsFromBinary(client()->codeAt(jsToAddress(_address), client()->getDefault())); +} + +std::string WebThreeStubServerBase::eth_coinbase() +{ + return toJS(client()->address()); +} + +double WebThreeStubServerBase::eth_countAt(string const& _address) +{ + return (double)(uint64_t)client()->countAt(jsToAddress(_address), client()->getDefault()); +} + +int WebThreeStubServerBase::eth_defaultBlock() +{ + return client()->getDefault(); +} + +std::string WebThreeStubServerBase::eth_gasPrice() +{ + return toJS(10 * dev::eth::szabo); +} + +std::string WebThreeStubServerBase::db_get(std::string const& _name, std::string const& _key) +{ + string ret = db()->get(_name, _key); + return toJS(dev::asBytes(ret)); +} + +Json::Value WebThreeStubServerBase::eth_filterLogs(int const& _id) +{ + return toJson(client()->logs(_id)); +} + +Json::Value WebThreeStubServerBase::eth_logs(Json::Value const& _json) +{ + return toJson(client()->logs(toLogFilter(_json))); +} + +std::string WebThreeStubServerBase::db_getString(std::string const& _name, std::string const& _key) +{ + return db()->get(_name, _key);; +} + +bool WebThreeStubServerBase::shh_haveIdentity(std::string const& _id) +{ + return m_ids.count(jsToPublic(_id)) > 0; +} + +bool WebThreeStubServerBase::eth_listening() +{ + return network()->isNetworkStarted(); +} + +bool WebThreeStubServerBase::eth_mining() +{ + return client()->isMining(); +} + +int WebThreeStubServerBase::eth_newFilter(Json::Value const& _json) +{ + unsigned ret = -1; + ret = client()->installWatch(toLogFilter(_json)); + return ret; +} + +int WebThreeStubServerBase::eth_newFilterString(std::string const& _filter) +{ + unsigned ret = -1; + if (_filter.compare("chain") == 0) + ret = client()->installWatch(dev::eth::ChainChangedFilter); + else if (_filter.compare("pending") == 0) + ret = client()->installWatch(dev::eth::PendingChangedFilter); + return ret; +} + +std::string WebThreeStubServerBase::shh_newGroup(std::string const& _id, std::string const& _who) +{ + (void)_id; + (void)_who; + return ""; +} + +std::string WebThreeStubServerBase::shh_newIdentity() +{ +// cnote << this << m_ids; + KeyPair kp = KeyPair::create(); + m_ids[kp.pub()] = kp.secret(); + return toJS(kp.pub()); +} + +Json::Value WebThreeStubServerBase::eth_compilers() +{ + Json::Value ret(Json::arrayValue); + ret.append("lll"); + ret.append("solidity"); + ret.append("serpent"); + return ret; +} + +std::string WebThreeStubServerBase::eth_lll(std::string const& _code) +{ + string res; + vector errors; + res = toJS(dev::eth::compileLLL(_code, true, &errors)); + cwarn << "LLL compilation errors: " << errors; + return res; +} + +std::string WebThreeStubServerBase::eth_serpent(std::string const& _code) +{ + string res; + try + { + res = toJS(dev::asBytes(::compile(_code))); + } + catch (string err) + { + cwarn << "Solidity compilation error: " << err; + } + catch (...) + { + cwarn << "Uncought serpent compilation exception"; + } + return res; +} + +std::string WebThreeStubServerBase::eth_solidity(std::string const& _code) +{ + string res; + dev::solidity::CompilerStack compiler; + try + { + res = toJS(compiler.compile(_code, true)); + } + catch (dev::Exception const& exception) + { + ostringstream error; + solidity::SourceReferenceFormatter::printExceptionInformation(error, exception, "Error", compiler); + cwarn << "Solidity compilation error: " << error.str(); + } + catch (...) + { + cwarn << "Uncought solidity compilation exception"; + } + return res; +} + +int WebThreeStubServerBase::eth_number() +{ + return client()->number() + 1; +} + +int WebThreeStubServerBase::eth_peerCount() +{ + return network()->peerCount(); +} + +bool WebThreeStubServerBase::shh_post(Json::Value const& _json) +{ + shh::Message m = toMessage(_json); + Secret from; + + if (m.from() && m_ids.count(m.from())) + { + cwarn << "Silently signing message from identity" << m.from().abridged() << ": User validation hook goes here."; + // TODO: insert validification hook here. + from = m_ids[m.from()]; + } + + face()->inject(toSealed(_json, m, from)); + return true; +} + +bool WebThreeStubServerBase::db_put(std::string const& _name, std::string const& _key, std::string const& _value) +{ + string v = asString(jsToBytes(_value)); + db()->put(_name, _key, v); + return true; +} + +bool WebThreeStubServerBase::db_putString(std::string const& _name, std::string const& _key, std::string const& _value) +{ + db()->put(_name, _key,_value); + return true; +} + +bool WebThreeStubServerBase::eth_setCoinbase(std::string const& _address) +{ + client()->setAddress(jsToAddress(_address)); + return true; +} + +bool WebThreeStubServerBase::eth_setDefaultBlock(int const& _block) +{ + client()->setDefault(_block); + return true; +} + +bool WebThreeStubServerBase::eth_setListening(bool const& _listening) +{ + if (_listening) + network()->startNetwork(); + else + network()->stopNetwork(); + return true; +} + +bool WebThreeStubServerBase::eth_setMining(bool const& _mining) +{ + if (_mining) + client()->startMining(); + else + client()->stopMining(); + return true; +} + +Json::Value WebThreeStubServerBase::shh_changed(int const& _id) +{ + Json::Value ret(Json::arrayValue); + auto pub = m_shhWatches[_id]; + if (!pub || m_ids.count(pub)) + for (h256 const& h: face()->checkWatch(_id)) + { + auto e = face()->envelope(h); + shh::Message m; + if (pub) + { + cwarn << "Silently decrypting message from identity" << pub.abridged() << ": User validation hook goes here."; + m = e.open(m_ids[pub]); + if (!m) + continue; + } + else + m = e.open(); + ret.append(toJson(h, e, m)); + } + + return ret; +} + +int WebThreeStubServerBase::shh_newFilter(Json::Value const& _json) +{ + auto w = toWatch(_json); + auto ret = face()->installWatch(w.first); + m_shhWatches.insert(make_pair(ret, w.second)); + return ret; +} + +bool WebThreeStubServerBase::shh_uninstallFilter(int const& _id) +{ + face()->uninstallWatch(_id); + return true; +} + +std::string WebThreeStubServerBase::eth_stateAt(string const& _address, string const& _storage) +{ + return toJS(client()->stateAt(jsToAddress(_address), jsToU256(_storage), client()->getDefault())); +} + +Json::Value WebThreeStubServerBase::eth_storageAt(string const& _address) +{ + return toJson(client()->storageAt(jsToAddress(_address))); +} + +std::string WebThreeStubServerBase::eth_transact(Json::Value const& _json) +{ + std::string ret; + TransactionSkeleton t = toTransaction(_json); + if (!t.from && m_accounts.size()) + { + auto b = m_accounts.begin()->first; + for (auto a: m_accounts) + if (client()->balanceAt(a.first) > client()->balanceAt(b)) + b = a.first; + t.from = b; + } + if (!m_accounts.count(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); + if (authenticate(t)) + { + if (t.to) + // TODO: from qethereum, insert validification hook here. + client()->transact(m_accounts[t.from].secret(), t.value, t.to, t.data, t.gas, t.gasPrice); + else + ret = toJS(client()->transact(m_accounts[t.from].secret(), t.value, t.data, t.gas, t.gasPrice)); + client()->flushTransactions(); + } + return ret; +} + +bool WebThreeStubServerBase::authenticate(TransactionSkeleton const& _t) const +{ + cwarn << "Silently signing transaction from address" << _t.from.abridged() << ": User validation hook goes here."; + return true; +} + +Json::Value WebThreeStubServerBase::eth_transactionByHash(std::string const& _hash, int const& _i) +{ + return toJson(client()->transaction(jsToFixed<32>(_hash), _i)); +} + +Json::Value WebThreeStubServerBase::eth_transactionByNumber(int const& _number, int const& _i) +{ + return toJson(client()->transaction(client()->hashFromNumber(_number), _i)); +} + +Json::Value WebThreeStubServerBase::eth_uncleByHash(std::string const& _hash, int const& _i) +{ + return toJson(client()->uncle(jsToFixed<32>(_hash), _i)); +} + +Json::Value WebThreeStubServerBase::eth_uncleByNumber(int const& _number, int const& _i) +{ + return toJson(client()->uncle(client()->hashFromNumber(_number), _i)); +} + +bool WebThreeStubServerBase::eth_uninstallFilter(int const& _id) +{ + client()->uninstallWatch(_id); + return true; +} + diff --git a/libweb3jsonrpc/WebThreeStubServerBase.h b/libweb3jsonrpc/WebThreeStubServerBase.h new file mode 100644 index 000000000..6868207a6 --- /dev/null +++ b/libweb3jsonrpc/WebThreeStubServerBase.h @@ -0,0 +1,138 @@ +/* + 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 WebThreeStubServer.h + * @authors: + * Gav Wood + * Marek Kotewicz + * @date 2014 + */ + +#pragma once + +#include +#include +#include +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" +#include "abstractwebthreestubserver.h" +#pragma GCC diagnostic pop + + +namespace dev +{ +class WebThreeNetworkFace; +class KeyPair; +class TransactionSkeleton; +namespace eth +{ +class Interface; +} +namespace shh +{ +class Interface; +} + +class WebThreeStubDatabaseFace +{ +public: + virtual std::string get(std::string const& _name, std::string const& _key) = 0; + virtual void put(std::string const& _name, std::string const& _key, std::string const& _value) = 0; +}; + +/** + * @brief JSON-RPC api implementation + * @todo filters should work on unsigned instead of int + * unsigned are not supported in json-rpc-cpp and there are bugs with double in json-rpc-cpp version 0.2.1 + * @todo split these up according to subprotocol (eth, shh, db, p2p, web3) and make it /very/ clear about how to add other subprotocols. + * @todo modularise everything so additional subprotocols don't need to change this file. + */ +class WebThreeStubServerBase: public AbstractWebThreeStubServer +{ +public: + WebThreeStubServerBase(jsonrpc::AbstractServerConnector& _conn, std::vector const& _accounts); + + virtual std::string web3_sha3(std::string const& _param1); + virtual Json::Value eth_accounts(); + virtual std::string eth_balanceAt(std::string const& _address); + virtual Json::Value eth_blockByHash(std::string const& _hash); + virtual Json::Value eth_blockByNumber(int const& _number); + virtual std::string eth_call(Json::Value const& _json); + virtual bool eth_changed(int const& _id); + virtual std::string eth_codeAt(std::string const& _address); + virtual std::string eth_coinbase(); + virtual Json::Value eth_compilers(); + virtual double eth_countAt(std::string const& _address); + virtual int eth_defaultBlock(); + virtual std::string eth_gasPrice(); + virtual Json::Value eth_filterLogs(int const& _id); + virtual Json::Value eth_logs(Json::Value const& _json); + virtual bool eth_listening(); + virtual bool eth_mining(); + virtual int eth_newFilter(Json::Value const& _json); + virtual int eth_newFilterString(std::string const& _filter); + virtual int eth_number(); + virtual int eth_peerCount(); + virtual bool eth_setCoinbase(std::string const& _address); + virtual bool eth_setDefaultBlock(int const& _block); + virtual bool eth_setListening(bool const& _listening); + virtual std::string eth_lll(std::string const& _s); + virtual std::string eth_serpent(std::string const& _s); + virtual bool eth_setMining(bool const& _mining); + virtual std::string eth_solidity(std::string const& _code); + virtual std::string eth_stateAt(std::string const& _address, std::string const& _storage); + virtual Json::Value eth_storageAt(std::string const& _address); + virtual std::string eth_transact(Json::Value const& _json); + virtual Json::Value eth_transactionByHash(std::string const& _hash, int const& _i); + virtual Json::Value eth_transactionByNumber(int const& _number, int const& _i); + virtual Json::Value eth_uncleByHash(std::string const& _hash, int const& _i); + virtual Json::Value eth_uncleByNumber(int const& _number, int const& _i); + virtual bool eth_uninstallFilter(int const& _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); + virtual bool db_putString(std::string const& _name, std::string const& _key, std::string const& _value); + + virtual std::string shh_addToGroup(std::string const& _group, std::string const& _who); + virtual Json::Value shh_changed(int const& _id); + virtual bool shh_haveIdentity(std::string const& _id); + virtual int shh_newFilter(Json::Value const& _json); + virtual std::string shh_newGroup(std::string const& _id, std::string const& _who); + virtual std::string shh_newIdentity(); + virtual bool shh_post(Json::Value const& _json); + virtual bool shh_uninstallFilter(int const& _id); + + void setAccounts(std::vector const& _accounts); + void setIdentities(std::vector const& _ids); + std::map const& ids() const { return m_ids; } + +protected: + virtual bool authenticate(dev::TransactionSkeleton const& _t) const; + +protected: + virtual dev::eth::Interface* client() = 0; + virtual std::shared_ptr face() = 0; + virtual dev::WebThreeNetworkFace* network() = 0; + virtual dev::WebThreeStubDatabaseFace* db() = 0; + + std::map m_accounts; + + std::map m_ids; + std::map m_shhWatches; +}; + +} //namespace dev diff --git a/libwebthree/WebThree.h b/libwebthree/WebThree.h index ec7bf2406..682fdc0b6 100644 --- a/libwebthree/WebThree.h +++ b/libwebthree/WebThree.h @@ -38,8 +38,6 @@ namespace dev { -class InterfaceNotSupported: public Exception { public: InterfaceNotSupported(std::string _f): m_f(_f) {} virtual const char* what() const noexcept { return ("Interface " + m_f + " not supported.").c_str(); } private: std::string m_f; }; - enum WorkState { Active = 0, @@ -51,6 +49,48 @@ namespace eth { class Interface; } namespace shh { class Interface; } namespace bzz { class Interface; } + +class WebThreeNetworkFace +{ +public: + /// Get information on the current peer set. + virtual std::vector peers() = 0; + + /// Same as peers().size(), but more efficient. + virtual size_t peerCount() const = 0; + + /// Connect to a particular peer. + virtual void connect(std::string const& _seedHost, unsigned short _port) = 0; + + /// Save peers + virtual dev::bytes saveNodes() = 0; + + /// Restore peers + virtual void restoreNodes(bytesConstRef _saved) = 0; + + /// Sets the ideal number of peers. + virtual void setIdealPeerCount(size_t _n) = 0; + + virtual bool haveNetwork() const = 0; + + virtual void setNetworkPreferences(p2p::NetworkPreferences const& _n) = 0; + + virtual p2p::NodeId id() const = 0; + + /// Gets the nodes. + virtual p2p::Nodes nodes() const = 0; + + /// Start the network subsystem. + virtual void startNetwork() = 0; + + /// Stop the network subsystem. + virtual void stopNetwork() = 0; + + /// Is network working? there may not be any peers yet. + virtual bool isNetworkStarted() const = 0; +}; + + /** * @brief Main API hub for interfacing with Web 3 components. This doesn't do any local multiplexing, so you can only have one * running on any given machine for the provided DB path. @@ -61,7 +101,7 @@ namespace bzz { class Interface; } * * Provides a baseline for the multiplexed multi-protocol session class, WebThree. */ -class WebThreeDirect +class WebThreeDirect : public WebThreeNetworkFace { public: /// Constructor for private instance. If there is already another process on the machine using @a _dbPath, then this will throw an exception. @@ -84,40 +124,40 @@ public: // Network stuff: /// Get information on the current peer set. - std::vector peers(); + std::vector peers() override; /// Same as peers().size(), but more efficient. - size_t peerCount() const; + size_t peerCount() const override; /// Connect to a particular peer. - void connect(std::string const& _seedHost, unsigned short _port = 30303); + void connect(std::string const& _seedHost, unsigned short _port = 30303) override; /// Save peers - dev::bytes saveNodes(); + dev::bytes saveNodes() override; /// Restore peers - void restoreNodes(bytesConstRef _saved); + void restoreNodes(bytesConstRef _saved) override; /// Sets the ideal number of peers. - void setIdealPeerCount(size_t _n); + void setIdealPeerCount(size_t _n) override; - bool haveNetwork() const { return m_net.isStarted(); } + bool haveNetwork() const override { return m_net.isStarted(); } - void setNetworkPreferences(p2p::NetworkPreferences const& _n); + void setNetworkPreferences(p2p::NetworkPreferences const& _n) override; - p2p::NodeId id() const { return m_net.id(); } + p2p::NodeId id() const override { return m_net.id(); } /// Gets the nodes. - p2p::Nodes nodes() const { return m_net.nodes(); } + p2p::Nodes nodes() const override { return m_net.nodes(); } /// Start the network subsystem. - void startNetwork() { m_net.start(); } + void startNetwork() override { m_net.start(); } /// Stop the network subsystem. - void stopNetwork() { m_net.stop(); } + void stopNetwork() override { m_net.stop(); } /// Is network working? there may not be any peers yet. - bool isNetworkStarted() { return m_net.isStarted(); } + bool isNetworkStarted() const override { return m_net.isStarted(); } private: std::string m_clientVersion; ///< Our end-application client's name/version. diff --git a/mix/AppContext.cpp b/mix/AppContext.cpp index 049cb04ea..4bede3c49 100644 --- a/mix/AppContext.cpp +++ b/mix/AppContext.cpp @@ -27,13 +27,10 @@ #include #include #include -#include -#include -#include -#include #include #include "CodeModel.h" #include "FileIo.h" +#include "ClientModel.h" #include "AppContext.h" @@ -47,7 +44,8 @@ AppContext::AppContext(QQmlApplicationEngine* _engine) { m_applicationEngine = _engine; //m_webThree = std::unique_ptr(new WebThreeDirect(std::string("Mix/v") + dev::Version + "/" DEV_QUOTED(ETH_BUILD_TYPE) "/" DEV_QUOTED(ETH_BUILD_PLATFORM), getDataDir() + "/Mix", false, {"eth", "shh"})); - m_codeModel = std::unique_ptr(new CodeModel(this)); + m_codeModel.reset(new CodeModel(this)); + m_clientModel.reset(new ClientModel(this)); m_fileIo.reset(new FileIo()); m_applicationEngine->rootContext()->setContextProperty("appContext", this); qmlRegisterType("org.ethereum.qml", 1, 0, "FileIo"); @@ -61,21 +59,6 @@ AppContext::~AppContext() { } -void AppContext::loadProject() -{ - QString path = QStandardPaths::locate(QStandardPaths::DataLocation, c_projectFileName); - if (!path.isEmpty()) - { - QFile file(path); - if (file.open(QIODevice::ReadOnly | QIODevice::Text)) - { - QTextStream stream(&file); - QString json = stream.readAll(); - emit projectLoaded(json); - } - } -} - QQmlApplicationEngine* AppContext::appEngine() { return m_applicationEngine; @@ -93,19 +76,3 @@ void AppContext::displayMessageDialog(QString _title, QString _message) dialogWin->findChild("messageContent", Qt::FindChildrenRecursively)->setProperty("text", _message); QMetaObject::invokeMethod(dialogWin, "open"); } - -void AppContext::saveProject(QString const& _json) -{ - QDir dirPath(QStandardPaths::writableLocation(QStandardPaths::DataLocation)); - QString path = QDir(dirPath).filePath(c_projectFileName); - if (!path.isEmpty()) - { - dirPath.mkpath(dirPath.path()); - QFile file(path); - if (file.open(QIODevice::WriteOnly | QIODevice::Text)) - { - QTextStream stream(&file); - stream << _json; - } - } -} diff --git a/mix/AppContext.h b/mix/AppContext.h index 3db3414d9..dec3b319e 100644 --- a/mix/AppContext.h +++ b/mix/AppContext.h @@ -47,6 +47,7 @@ namespace mix { class CodeModel; +class ClientModel; class FileIo; /** * @brief Provides access to application scope variable. @@ -63,24 +64,25 @@ public: QQmlApplicationEngine* appEngine(); /// Get code model CodeModel* codeModel() { return m_codeModel.get(); } + /// Get client model + ClientModel* clientModel() { return m_clientModel.get(); } /// Display an alert message. void displayMessageDialog(QString _title, QString _message); - /// Load project settings - void loadProject(); + signals: - void projectLoaded(QString const& _json); + /// Triggered once components have been loaded + void appLoaded(); private: QQmlApplicationEngine* m_applicationEngine; //owned by app std::unique_ptr m_webThree; std::unique_ptr m_codeModel; + std::unique_ptr m_clientModel; std::unique_ptr m_fileIo; public slots: /// Delete the current instance when application quit. void quitApplication() {} - /// Write json to a settings file - void saveProject(QString const& _json); }; } diff --git a/mix/AssemblyDebuggerControl.cpp b/mix/AssemblyDebuggerControl.cpp index 136353dc6..b503757d0 100644 --- a/mix/AssemblyDebuggerControl.cpp +++ b/mix/AssemblyDebuggerControl.cpp @@ -17,53 +17,19 @@ * display opcode debugging. */ -#include #include #include #include -#include -#include -#include -#include "AssemblyDebuggerModel.h" -#include "AssemblyDebuggerControl.h" #include "AppContext.h" -#include "DebuggingStateWrapper.h" -#include "QContractDefinition.h" -#include "QVariableDeclaration.h" -#include "ContractCallDataEncoder.h" -#include "CodeModel.h" +#include "ClientModel.h" +#include "AssemblyDebuggerControl.h" -using namespace dev::eth; using namespace dev::mix; -/// @todo Move this to QML -dev::u256 fromQString(QString const& _s) -{ - return dev::jsToU256(_s.toStdString()); -} - -/// @todo Move this to QML -QString toQString(dev::u256 _value) -{ - std::ostringstream s; - s << _value; - return QString::fromStdString(s.str()); -} - AssemblyDebuggerControl::AssemblyDebuggerControl(AppContext* _context): - Extension(_context, ExtensionDisplayBehavior::ModalDialog), m_running(false) + Extension(_context, ExtensionDisplayBehavior::ModalDialog) { - qRegisterMetaType("QVariableDefinition*"); - qRegisterMetaType("QVariableDefinitionList*"); - qRegisterMetaType>("QList"); - qRegisterMetaType>("QList"); - qRegisterMetaType("QVariableDeclaration*"); - qRegisterMetaType("AssemblyDebuggerData"); - - connect(this, &AssemblyDebuggerControl::dataAvailable, this, &AssemblyDebuggerControl::showDebugger, Qt::QueuedConnection); - m_modelDebugger = std::unique_ptr(new AssemblyDebuggerModel); - - _context->appEngine()->rootContext()->setContextProperty("debugModel", this); + connect(_context->clientModel(), &ClientModel::showDebuggerWindow, this, &AssemblyDebuggerControl::showDebugger, Qt::QueuedConnection); } QString AssemblyDebuggerControl::contentUrl() const @@ -80,132 +46,7 @@ void AssemblyDebuggerControl::start() const { } -void AssemblyDebuggerControl::debugDeployment() -{ - executeSequence(std::vector(), 0); -} - -void AssemblyDebuggerControl::debugState(QVariantMap _state) -{ - u256 balance = fromQString(_state.value("balance").toString()); - QVariantList transactions = _state.value("transactions").toList(); - - std::vector transactionSequence; - - for (auto const& t: transactions) - { - QVariantMap transaction = t.toMap(); - - QString functionId = transaction.value("functionId").toString(); - u256 value = fromQString(transaction.value("value").toString()); - u256 gas = fromQString(transaction.value("gas").toString()); - u256 gasPrice = fromQString(transaction.value("gasPrice").toString()); - QVariantMap params = transaction.value("parameters").toMap(); - TransactionSettings transactionSettings(functionId, value, gas, gasPrice); - - for (auto p = params.cbegin(); p != params.cend(); ++p) - transactionSettings.parameterValues.insert(std::make_pair(p.key(), fromQString(p.value().toString()))); - - transactionSequence.push_back(transactionSettings); - } - executeSequence(transactionSequence, balance); -} - -void AssemblyDebuggerControl::executeSequence(std::vector const& _sequence, u256 _balance) -{ - if (m_running) - throw (std::logic_error("debugging already running")); - auto compilerRes = m_ctx->codeModel()->code(); - std::shared_ptr contractDef = compilerRes->sharedContract(); - m_running = true; - - emit runStarted(); - emit stateChanged(); - - //run sequence - QtConcurrent::run([=]() - { - try - { - bytes contractCode = compilerRes->bytes(); - std::vector transactonData; - QFunctionDefinition* f; - ContractCallDataEncoder c; - //encode data for all transactions - for (auto const& t: _sequence) - { - f = nullptr; - for (int tf = 0; tf < contractDef->functionsList().size(); tf++) - { - if (contractDef->functionsList().at(tf)->name() == t.functionId) - { - f = contractDef->functionsList().at(tf); - break; - } - } - if (!f) - throw std::runtime_error("function " + t.functionId.toStdString() + " not found"); - - c.encode(f->index()); - for (int p = 0; p < f->parametersList().size(); p++) - { - QVariableDeclaration* var = (QVariableDeclaration*)f->parametersList().at(p); - u256 value = 0; - auto v = t.parameterValues.find(var->name()); - if (v != t.parameterValues.cend()) - value = v->second; - c.encode(var, value); - } - transactonData.emplace_back(c.encodedData()); - } - - //run contract creation first - m_modelDebugger->resetState(_balance); - DebuggingContent debuggingContent = m_modelDebugger->deployContract(contractCode); - Address address = debuggingContent.contractAddress; - for (unsigned i = 0; i < _sequence.size(); ++i) - debuggingContent = m_modelDebugger->callContract(address, transactonData.at(i), _sequence.at(i)); - - if (f) - debuggingContent.returnParameters = c.decode(f->returnParameters(), debuggingContent.returnValue); - - //we need to wrap states in a QObject before sending to QML. - QList wStates; - for (int i = 0; i < debuggingContent.machineStates.size(); i++) - { - QPointer s(new DebuggingStateWrapper(debuggingContent.executionCode, debuggingContent.executionData.toBytes())); - s->setState(debuggingContent.machineStates.at(i)); - wStates.append(s); - } - //collect states for last transaction - AssemblyDebuggerData code = DebuggingStateWrapper::getHumanReadableCode(debuggingContent.executionCode); - emit dataAvailable(debuggingContent.returnParameters, wStates, code); - emit runComplete(); - } - catch(boost::exception const&) - { - emit runFailed(QString::fromStdString(boost::current_exception_diagnostic_information())); - } - - catch(std::exception const& e) - { - emit runFailed(e.what()); - } - m_running = false; - emit stateChanged(); - }); -} - -void AssemblyDebuggerControl::showDebugger(QList const& _returnParam, QList const& _wStates, AssemblyDebuggerData const& _code) +void AssemblyDebuggerControl::showDebugger() { - m_appEngine->rootContext()->setContextProperty("debugStates", QVariant::fromValue(_wStates)); - m_appEngine->rootContext()->setContextProperty("humanReadableExecutionCode", QVariant::fromValue(std::get<0>(_code))); - m_appEngine->rootContext()->setContextProperty("bytesCodeMapping", QVariant::fromValue(std::get<1>(_code))); - m_appEngine->rootContext()->setContextProperty("contractCallReturnParameters", QVariant::fromValue(new QVariableDefinitionList(_returnParam))); this->addContentOn(this); } - -void AssemblyDebuggerControl::showDebugError(QString const& _error) -{ - m_ctx->displayMessageDialog(QApplication::tr("Debugger"), _error); -} diff --git a/mix/AssemblyDebuggerControl.h b/mix/AssemblyDebuggerControl.h index b4dff38f5..dbe9b0676 100644 --- a/mix/AssemblyDebuggerControl.h +++ b/mix/AssemblyDebuggerControl.h @@ -20,14 +20,7 @@ #pragma once #include -#include #include "Extension.h" -#include "AssemblyDebuggerModel.h" - -using AssemblyDebuggerData = std::tuple, dev::mix::QQMLMap*>; - -Q_DECLARE_METATYPE(AssemblyDebuggerData) -Q_DECLARE_METATYPE(dev::mix::DebuggingContent) class AppContext; @@ -37,7 +30,7 @@ namespace mix { /** - * @brief Extension which display transaction creation or transaction call debugging. handle: F5 to deploy contract, F6 to reset state. + * @brief Extension which display transaction creation or transaction call debugging. */ class AssemblyDebuggerControl: public Extension { @@ -50,40 +43,9 @@ public: QString title() const override; QString contentUrl() const override; - Q_PROPERTY(bool running MEMBER m_running NOTIFY stateChanged) - -private: - void executeSequence(std::vector const& _sequence, u256 _balance); - - std::unique_ptr m_modelDebugger; - std::atomic m_running; - -public slots: - /// Run the contract constructor and show debugger window. - void debugDeployment(); - /// Setup state, run transaction sequence, show debugger for the last transaction - /// @param _state JS object with state configuration - void debugState(QVariantMap _state); - private slots: /// Update UI with machine states result. Display a modal dialog. - void showDebugger(QList const& _returnParams = QList(), QList const& _wStates = QList(), AssemblyDebuggerData const& _code = AssemblyDebuggerData()); - /// Update UI with transaction run error. - void showDebugError(QString const& _error); - -signals: - /// Transaction execution started - void runStarted(); - /// Transaction execution completed successfully - void runComplete(); - /// Transaction execution completed with error - /// @param _message Error message - void runFailed(QString const& _message); - /// Execution state changed - void stateChanged(); - - /// Emited when machine states are available. - void dataAvailable(QList const& _returnParams = QList(), QList const& _wStates = QList(), AssemblyDebuggerData const& _code = AssemblyDebuggerData()); + void showDebugger(); }; } diff --git a/mix/AssemblyDebuggerModel.cpp b/mix/AssemblyDebuggerModel.cpp deleted file mode 100644 index e822d0a3f..000000000 --- a/mix/AssemblyDebuggerModel.cpp +++ /dev/null @@ -1,119 +0,0 @@ -/* - This file is part of cpp-ethereum. - cpp-ethereum is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - cpp-ethereum is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with cpp-ethereum. If not, see . -*/ -/** @file AssemblyDebuggerModel.cpp - * @author Yann yann@ethdev.com - * @date 2014 - * used as a model to debug contract assembly code. - */ - -#include -#include -#include -#include -#include -#include -#include "AssemblyDebuggerModel.h" -#include "AppContext.h" -#include "DebuggingStateWrapper.h" - -using namespace std; -using namespace dev; -using namespace dev::eth; - -namespace dev -{ -namespace mix -{ - -AssemblyDebuggerModel::AssemblyDebuggerModel(): - m_userAccount(KeyPair::create()) -{ - resetState(10000000 * ether); -} - -DebuggingContent AssemblyDebuggerModel::executeTransaction(bytesConstRef const& _rawTransaction) -{ - QList machineStates; - eth::Executive execution(m_executiveState, LastHashes(), 0); - execution.setup(_rawTransaction); - std::vector levels; - bytes code; - bytesConstRef data; - bool firstIteration = true; - auto onOp = [&](uint64_t steps, Instruction inst, dev::bigint newMemSize, dev::bigint gasCost, void* voidVM, void const* voidExt) - { - VM& vm = *(VM*)voidVM; - ExtVM const& ext = *(ExtVM const*)voidExt; - - if (firstIteration) - { - code = ext.code; - data = ext.data; - firstIteration = false; - } - - if (levels.size() < ext.depth) - levels.push_back(&machineStates.back()); - else - levels.resize(ext.depth); - - machineStates.append(DebuggingState({steps, ext.myAddress, vm.curPC(), inst, newMemSize, vm.gas(), - vm.stack(), vm.memory(), gasCost, ext.state().storage(ext.myAddress), levels})); - }; - - execution.go(onOp); - execution.finalize(); - - DebuggingContent d; - d.returnValue = execution.out().toVector(); - d.machineStates = machineStates; - d.executionCode = code; - d.executionData = data; - d.contentAvailable = true; - d.message = "ok"; - return d; -} - -DebuggingContent AssemblyDebuggerModel::deployContract(bytes const& _code) -{ - u256 gasPrice = 10000000000000; - u256 gas = 1000000; - u256 amount = 100; - Transaction _tr(amount, gasPrice, min(gas, m_executiveState.gasLimitRemaining()), _code, m_executiveState.transactionsFrom(dev::toAddress(m_userAccount.secret())), m_userAccount.secret()); - bytes b = _tr.rlp(); - dev::bytesConstRef bytesRef = &b; - DebuggingContent d = executeTransaction(bytesRef); - h256 th = sha3(rlpList(_tr.sender(), _tr.nonce())); - d.contractAddress = right160(th); - return d; -} - -DebuggingContent AssemblyDebuggerModel::callContract(Address const& _contract, bytes const& _data, TransactionSettings const& _tr) -{ - Transaction tr = Transaction(_tr.value, _tr.gasPrice, min(_tr.gas, m_executiveState.gasLimitRemaining()), _contract, _data, m_executiveState.transactionsFrom(dev::toAddress(m_userAccount.secret())), m_userAccount.secret()); - bytes b = tr.rlp(); - dev::bytesConstRef bytesRef = &b; - DebuggingContent d = executeTransaction(bytesRef); - d.contractAddress = tr.receiveAddress(); - return d; -} - -void AssemblyDebuggerModel::resetState(u256 _balance) -{ - m_executiveState = eth::State(Address(), m_overlayDB, BaseState::Empty); - m_executiveState.addBalance(m_userAccount.address(), _balance); -} - -} -} diff --git a/mix/AssemblyDebuggerModel.h b/mix/AssemblyDebuggerModel.h deleted file mode 100644 index c12e711c6..000000000 --- a/mix/AssemblyDebuggerModel.h +++ /dev/null @@ -1,76 +0,0 @@ -/* - This file is part of cpp-ethereum. - cpp-ethereum is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - cpp-ethereum is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with cpp-ethereum. If not, see . -*/ -/** @file AssemblyDebuggerModel.h - * @author Yann yann@ethdev.com - * @date 2014 - * Used as a model to debug contract assembly code. - */ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include "DebuggingStateWrapper.h" - -namespace dev -{ -namespace mix -{ - -/// Backend transaction config class -struct TransactionSettings -{ - TransactionSettings(QString const& _functionId, u256 _value, u256 _gas, u256 _gasPrice): - functionId(_functionId), value(_value), gas(_gas), gasPrice(_gasPrice) {} - - /// Contract function name - QString functionId; - /// Transaction value - u256 value; - /// Gas - u256 gas; - /// Gas price - u256 gasPrice; - /// Mapping from contract function parameter name to value - std::map parameterValues; -}; - - -/** - * @brief Long-life object for managing all executions. - */ -class AssemblyDebuggerModel -{ -public: - AssemblyDebuggerModel(); - /// Call function in a already deployed contract. - DebuggingContent callContract(Address const& _contract, bytes const& _data, TransactionSettings const& _tr); - /// Deploy the contract described by _code. - DebuggingContent deployContract(bytes const& _code); - /// Reset state to the empty state with given balance. - void resetState(u256 _balance); - -private: - KeyPair m_userAccount; - OverlayDB m_overlayDB; - eth::State m_executiveState; - DebuggingContent executeTransaction(dev::bytesConstRef const& _rawTransaction); -}; - -} -} diff --git a/mix/ClientModel.cpp b/mix/ClientModel.cpp new file mode 100644 index 000000000..6859b0279 --- /dev/null +++ b/mix/ClientModel.cpp @@ -0,0 +1,222 @@ +/* + 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 ClientModel.cpp + * @author Yann yann@ethdev.com + * @author Arkadiy Paronyan arkadiy@ethdev.com + * @date 2015 + * Ethereum IDE client. + */ + +#include +#include +#include +#include +#include +#include +#include "ClientModel.h" +#include "AppContext.h" +#include "DebuggingStateWrapper.h" +#include "QContractDefinition.h" +#include "QVariableDeclaration.h" +#include "ContractCallDataEncoder.h" +#include "CodeModel.h" +#include "ClientModel.h" + +using namespace dev::eth; +using namespace dev::mix; + +/// @todo Move this to QML +dev::u256 fromQString(QString const& _s) +{ + return dev::jsToU256(_s.toStdString()); +} + +/// @todo Move this to QML +QString toQString(dev::u256 _value) +{ + std::ostringstream s; + s << _value; + return QString::fromStdString(s.str()); +} + +ClientModel::ClientModel(AppContext* _context): + m_context(_context), m_running(false) +{ + qRegisterMetaType("QVariableDefinition*"); + qRegisterMetaType("QVariableDefinitionList*"); + qRegisterMetaType>("QList"); + qRegisterMetaType>("QList"); + qRegisterMetaType("QVariableDeclaration*"); + qRegisterMetaType("AssemblyDebuggerData"); + + connect(this, &ClientModel::dataAvailable, this, &ClientModel::showDebugger, Qt::QueuedConnection); + m_client.reset(new MixClient()); + + _context->appEngine()->rootContext()->setContextProperty("clientModel", this); +} + +void ClientModel::debugDeployment() +{ + executeSequence(std::vector(), 10000000 * ether); +} + +void ClientModel::debugState(QVariantMap _state) +{ + u256 balance = fromQString(_state.value("balance").toString()); + QVariantList transactions = _state.value("transactions").toList(); + + std::vector transactionSequence; + + for (auto const& t: transactions) + { + QVariantMap transaction = t.toMap(); + + QString functionId = transaction.value("functionId").toString(); + u256 value = fromQString(transaction.value("value").toString()); + u256 gas = fromQString(transaction.value("gas").toString()); + u256 gasPrice = fromQString(transaction.value("gasPrice").toString()); + QVariantMap params = transaction.value("parameters").toMap(); + TransactionSettings transactionSettings(functionId, value, gas, gasPrice); + + for (auto p = params.cbegin(); p != params.cend(); ++p) + transactionSettings.parameterValues.insert(std::make_pair(p.key(), fromQString(p.value().toString()))); + + transactionSequence.push_back(transactionSettings); + } + executeSequence(transactionSequence, balance); +} + +void ClientModel::executeSequence(std::vector const& _sequence, u256 _balance) +{ + if (m_running) + throw (std::logic_error("debugging already running")); + auto compilerRes = m_context->codeModel()->code(); + std::shared_ptr contractDef = compilerRes->sharedContract(); + m_running = true; + + emit runStarted(); + emit stateChanged(); + + //run sequence + QtConcurrent::run([=]() + { + try + { + bytes contractCode = compilerRes->bytes(); + std::vector transactonData; + QFunctionDefinition* f; + ContractCallDataEncoder c; + //encode data for all transactions + for (auto const& t: _sequence) + { + f = nullptr; + for (int tf = 0; tf < contractDef->functionsList().size(); tf++) + { + if (contractDef->functionsList().at(tf)->name() == t.functionId) + { + f = contractDef->functionsList().at(tf); + break; + } + } + if (!f) + throw std::runtime_error("function " + t.functionId.toStdString() + " not found"); + + c.encode(f); + for (int p = 0; p < f->parametersList().size(); p++) + { + QVariableDeclaration* var = (QVariableDeclaration*)f->parametersList().at(p); + u256 value = 0; + auto v = t.parameterValues.find(var->name()); + if (v != t.parameterValues.cend()) + value = v->second; + c.encode(var, value); + } + transactonData.emplace_back(c.encodedData()); + } + + //run contract creation first + m_client->resetState(_balance); + ExecutionResult debuggingContent = deployContract(contractCode); + Address address = debuggingContent.contractAddress; + for (unsigned i = 0; i < _sequence.size(); ++i) + debuggingContent = callContract(address, transactonData.at(i), _sequence.at(i)); + + QList returnParameters; + + if (f) + returnParameters = c.decode(f->returnParameters(), debuggingContent.returnValue); + + //we need to wrap states in a QObject before sending to QML. + QList wStates; + for (unsigned i = 0; i < debuggingContent.machineStates.size(); i++) + { + QPointer s(new DebuggingStateWrapper(debuggingContent.executionCode, debuggingContent.executionData.toBytes())); + s->setState(debuggingContent.machineStates[i]); + wStates.append(s); + } + //collect states for last transaction + AssemblyDebuggerData code = DebuggingStateWrapper::getHumanReadableCode(debuggingContent.executionCode); + emit dataAvailable(returnParameters, wStates, code); + emit runComplete(); + } + catch(boost::exception const&) + { + emit runFailed(QString::fromStdString(boost::current_exception_diagnostic_information())); + } + + catch(std::exception const& e) + { + emit runFailed(e.what()); + } + m_running = false; + emit stateChanged(); + }); +} + +void ClientModel::showDebugger(QList const& _returnParam, QList const& _wStates, AssemblyDebuggerData const& _code) +{ + m_context->appEngine()->rootContext()->setContextProperty("debugStates", QVariant::fromValue(_wStates)); + m_context->appEngine()->rootContext()->setContextProperty("humanReadableExecutionCode", QVariant::fromValue(std::get<0>(_code))); + m_context->appEngine()->rootContext()->setContextProperty("bytesCodeMapping", QVariant::fromValue(std::get<1>(_code))); + m_context->appEngine()->rootContext()->setContextProperty("contractCallReturnParameters", QVariant::fromValue(new QVariableDefinitionList(_returnParam))); + showDebuggerWindow(); +} + +void ClientModel::showDebugError(QString const& _error) +{ + //TODO: change that to a signal + m_context->displayMessageDialog(tr("Debugger"), _error); +} + +ExecutionResult ClientModel::deployContract(bytes const& _code) +{ + u256 gasPrice = 10000000000000; + u256 gas = 125000; + u256 amount = 100; + + Address contractAddress = m_client->transact(m_client->userAccount().secret(), amount, _code, gas, gasPrice); + ExecutionResult r = m_client->lastExecutionResult(); + r.contractAddress = contractAddress; + return r; +} + +ExecutionResult ClientModel::callContract(Address const& _contract, bytes const& _data, TransactionSettings const& _tr) +{ + //bytes call(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice) override; + m_client->transact(m_client->userAccount().secret(), _tr.value, _contract, _data, _tr.gas, _tr.gasPrice); + ExecutionResult r = m_client->lastExecutionResult(); + r.contractAddress = _contract; + return r; +} + diff --git a/mix/ClientModel.h b/mix/ClientModel.h new file mode 100644 index 000000000..509a5995b --- /dev/null +++ b/mix/ClientModel.h @@ -0,0 +1,113 @@ +/* + 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 ClientModel.h + * @author Yann yann@ethdev.com + * @author Arkadiy Paronyan arkadiy@ethdev.com + * @date 2015 + * Ethereum IDE client. + */ + +#pragma once + +#include +#include "DebuggingStateWrapper.h" +#include "MixClient.h" + +using AssemblyDebuggerData = std::tuple, dev::mix::QQMLMap*>; + +Q_DECLARE_METATYPE(AssemblyDebuggerData) +Q_DECLARE_METATYPE(dev::mix::ExecutionResult) + +namespace dev +{ +namespace mix +{ + +class AppContext; + +/// Backend transaction config class +struct TransactionSettings +{ + TransactionSettings(QString const& _functionId, u256 _value, u256 _gas, u256 _gasPrice): + functionId(_functionId), value(_value), gas(_gas), gasPrice(_gasPrice) {} + + /// Contract function name + QString functionId; + /// Transaction value + u256 value; + /// Gas + u256 gas; + /// Gas price + u256 gasPrice; + /// Mapping from contract function parameter name to value + std::map parameterValues; +}; + + +/** + * @brief Ethereum state control + */ +class ClientModel: public QObject +{ + Q_OBJECT + +public: + ClientModel(AppContext* _context); + + Q_PROPERTY(bool running MEMBER m_running NOTIFY stateChanged) + +public slots: + /// Run the contract constructor and show debugger window. + void debugDeployment(); + /// Setup state, run transaction sequence, show debugger for the last transaction + /// @param _state JS object with state configuration + void debugState(QVariantMap _state); + +private slots: + /// Update UI with machine states result. Display a modal dialog. + void showDebugger(QList const& _returnParams = QList(), QList const& _wStates = QList(), AssemblyDebuggerData const& _code = AssemblyDebuggerData()); + /// Update UI with transaction run error. + void showDebugError(QString const& _error); + +signals: + /// Transaction execution started + void runStarted(); + /// Transaction execution completed successfully + void runComplete(); + /// Transaction execution completed with error + /// @param _message Error message + void runFailed(QString const& _message); + /// Execution state changed + void stateChanged(); + /// Show debugger window request + void showDebuggerWindow(); + + /// Emited when machine states are available. + void dataAvailable(QList const& _returnParams = QList(), QList const& _wStates = QList(), AssemblyDebuggerData const& _code = AssemblyDebuggerData()); + +private: + void executeSequence(std::vector const& _sequence, u256 _balance); + ExecutionResult deployContract(bytes const& _code); + ExecutionResult callContract(Address const& _contract, bytes const& _data, TransactionSettings const& _tr); + + AppContext* m_context; + std::atomic m_running; + std::unique_ptr m_client; +}; + +} +} diff --git a/mix/CodeEditorExtensionManager.cpp b/mix/CodeEditorExtensionManager.cpp index 14795c223..f5ceb333e 100644 --- a/mix/CodeEditorExtensionManager.cpp +++ b/mix/CodeEditorExtensionManager.cpp @@ -31,6 +31,7 @@ #include "AppContext.h" #include "MixApplication.h" #include "CodeModel.h" +#include "ClientModel.h" #include "CodeHighlighter.h" #include "CodeEditorExtensionManager.h" @@ -58,7 +59,7 @@ void CodeEditorExtensionManager::initExtensions() std::shared_ptr output = std::make_shared(m_appContext); std::shared_ptr debug = std::make_shared(m_appContext); std::shared_ptr stateList = std::make_shared(m_appContext); - QObject::connect(debug.get(), &AssemblyDebuggerControl::runFailed, output.get(), &ConstantCompilationControl::displayError); + QObject::connect(m_appContext->clientModel(), &ClientModel::runFailed, output.get(), &ConstantCompilationControl::displayError); QObject::connect(m_appContext->codeModel(), &CodeModel::compilationComplete, this, &CodeEditorExtensionManager::applyCodeHighlight); initExtension(output); diff --git a/mix/CodeModel.cpp b/mix/CodeModel.cpp index 7d8b0442a..87152360e 100644 --- a/mix/CodeModel.cpp +++ b/mix/CodeModel.cpp @@ -162,7 +162,7 @@ void CodeModel::onCompilationComplete(CompilationResult*_newResult) m_result.reset(_newResult); emit compilationComplete(); emit stateChanged(); - if (m_result->successfull()) + if (m_result->successful()) emit codeChanged(); } diff --git a/mix/CodeModel.h b/mix/CodeModel.h index c66703b22..e1a2a0fbd 100644 --- a/mix/CodeModel.h +++ b/mix/CodeModel.h @@ -69,7 +69,7 @@ class CompilationResult: public QObject public: /// Empty compilation result constructor CompilationResult(); - /// Successfull compilation result constructor + /// Successful compilation result constructor CompilationResult(solidity::CompilerStack const& _compiler); /// Failed compilation result constructor CompilationResult(CompilationResult const& _prev, QString const& _compilerMessage); @@ -78,9 +78,9 @@ public: QContractDefinition* contract() { return m_contract.get(); } /// @returns contract definition std::shared_ptr sharedContract() { return m_contract; } - /// Indicates if the compilation was successfull - bool successfull() const { return m_successful; } - /// @returns compiler error message in case of unsuccessfull compilation + /// Indicates if the compilation was successful + bool successful() const { return m_successful; } + /// @returns compiler error message in case of unsuccessful compilation QString compilerMessage() const { return m_compilerMessage; } /// @returns contract bytecode dev::bytes const& bytes() const { return m_bytes; } diff --git a/mix/ConstantCompilationControl.cpp b/mix/ConstantCompilationControl.cpp index c9f21c11b..6352f070d 100644 --- a/mix/ConstantCompilationControl.cpp +++ b/mix/ConstantCompilationControl.cpp @@ -61,7 +61,7 @@ void ConstantCompilationControl::update() QObject* status = m_view->findChild("status", Qt::FindChildrenRecursively); QObject* content = m_view->findChild("content", Qt::FindChildrenRecursively); - if (result->successfull()) + if (result->successful()) { status->setProperty("text", "succeeded"); status->setProperty("color", "green"); diff --git a/mix/ContractCallDataEncoder.cpp b/mix/ContractCallDataEncoder.cpp index 757f7243c..f2cd5d587 100644 --- a/mix/ContractCallDataEncoder.cpp +++ b/mix/ContractCallDataEncoder.cpp @@ -27,6 +27,7 @@ #include #include "QVariableDeclaration.h" #include "QVariableDefinition.h" +#include "QFunctionDefinition.h" #include "ContractCallDataEncoder.h" using namespace dev; using namespace dev::solidity; @@ -37,9 +38,9 @@ bytes ContractCallDataEncoder::encodedData() return m_encodedData; } -void ContractCallDataEncoder::encode(int _functionIndex) +void ContractCallDataEncoder::encode(QFunctionDefinition const* _function) { - bytes i = jsToBytes(std::to_string(_functionIndex)); + bytes i = _function->hash().asBytes(); m_encodedData.insert(m_encodedData.end(), i.begin(), i.end()); } diff --git a/mix/ContractCallDataEncoder.h b/mix/ContractCallDataEncoder.h index fd67a7b7a..49410a7cd 100644 --- a/mix/ContractCallDataEncoder.h +++ b/mix/ContractCallDataEncoder.h @@ -30,6 +30,10 @@ namespace dev namespace mix { +class QFunctionDefinition; +class QVariableDeclaration; +class QVariableDefinition; + /** * @brief Encode/Decode data to be sent to a transaction or to be displayed in a view. */ @@ -43,8 +47,8 @@ public: void encode(QVariableDeclaration const* _dec, u256 _value); /// Encode variable in order to be sent as parameter. void encode(QVariableDeclaration const* _dec, bool _value); - /// Encode index of the function to call. - void encode(int _functionIndex); + /// Encode hash of the function to call. + void encode(QFunctionDefinition const* _function); /// Decode variable in order to be sent to QML view. QList decode(QList _dec, bytes _value); /// Get all encoded data encoded by encode function. diff --git a/mix/DebuggingStateWrapper.h b/mix/DebuggingStateWrapper.h index bf3efe34d..6d1ce4220 100644 --- a/mix/DebuggingStateWrapper.h +++ b/mix/DebuggingStateWrapper.h @@ -28,45 +28,13 @@ #include #include #include "QVariableDefinition.h" +#include "MixClient.h" namespace dev { namespace mix { -/** - * @brief Store information about a machine state. - */ -struct DebuggingState -{ - uint64_t steps; - dev::Address cur; - dev::u256 curPC; - dev::eth::Instruction inst; - dev::bigint newMemSize; - dev::u256 gas; - dev::u256s stack; - dev::bytes memory; - dev::bigint gasCost; - std::map storage; - std::vector levels; -}; - -/** - * @brief Store information about a machine states. - */ -struct DebuggingContent -{ - QList machineStates; - bytes executionCode; - bytesConstRef executionData; - Address contractAddress; - bool contentAvailable; - QString message; - bytes returnValue; - QList returnParameters; -}; - /** * @brief Contains the line nb of the assembly code and the corresponding index in the code bytes array. */ @@ -151,14 +119,14 @@ public: /// Get all previous steps. QStringList levels(); /// Get the current processed machine state. - DebuggingState state() { return m_state; } + MachineState state() { return m_state; } /// Set the current processed machine state. - void setState(DebuggingState _state) { m_state = _state; } + void setState(MachineState _state) { m_state = _state; } /// Convert all machine state in human readable code. static std::tuple, QQMLMap*> getHumanReadableCode(bytes const& _code); private: - DebuggingState m_state; + MachineState m_state; bytes m_code; bytes m_data; }; diff --git a/mix/FileIo.cpp b/mix/FileIo.cpp index 4ecbc9c08..a91dfc167 100644 --- a/mix/FileIo.cpp +++ b/mix/FileIo.cpp @@ -73,3 +73,8 @@ void FileIo::copyFile(QString const& _sourceUrl, QString const& _destUrl) if (!QFile::copy(sourceUrl.path(), destUrl.path())) error(tr("Error copying file %1 to %2").arg(_sourceUrl).arg(_destUrl)); } + +QString FileIo::getHomePath() const +{ + return QDir::homePath(); +} diff --git a/mix/FileIo.h b/mix/FileIo.h index 83352476b..3c251cfbc 100644 --- a/mix/FileIo.h +++ b/mix/FileIo.h @@ -33,6 +33,7 @@ namespace mix class FileIo : public QObject { Q_OBJECT + Q_PROPERTY(QString homePath READ getHomePath CONSTANT); signals: /// Signalled in case of IO error @@ -47,6 +48,9 @@ public: Q_INVOKABLE void writeFile(QString const& _url, QString const& _data); /// Copy a file from _sourcePath to _destPath. Signals on failure. Q_INVOKABLE void copyFile(QString const& _sourceUrl, QString const& _destUrl); + +private: + QString getHomePath() const; }; } diff --git a/mix/MixApplication.cpp b/mix/MixApplication.cpp index 1a55b1e47..2cdc4b30d 100644 --- a/mix/MixApplication.cpp +++ b/mix/MixApplication.cpp @@ -33,10 +33,9 @@ MixApplication::MixApplication(int _argc, char* _argv[]): QApplication(_argc, _argv), m_engine(new QQmlApplicationEngine()), m_appContext(new AppContext(m_engine.get())) { qmlRegisterType("CodeEditorExtensionManager", 1, 0, "CodeEditorExtensionManager"); - //QObject::connect(this, SIGNAL(lastWindowClosed()), context(), SLOT(quitApplication())); //use to kill ApplicationContext and other stuff + QObject::connect(this, SIGNAL(lastWindowClosed()), context(), SLOT(quitApplication())); //use to kill ApplicationContext and other stuff m_engine->load(QUrl("qrc:/qml/main.qml")); - //m_engine->load(QUrl("qrc:/qml/ProjectModel.qml")); - m_appContext->loadProject(); + m_appContext->appLoaded(); } MixApplication::~MixApplication() diff --git a/mix/MixClient.cpp b/mix/MixClient.cpp new file mode 100644 index 000000000..5d635ce1f --- /dev/null +++ b/mix/MixClient.cpp @@ -0,0 +1,336 @@ +/* + 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 MixClient.cpp + * @author Yann yann@ethdev.com + * @author Arkadiy Paronyan arkadiy@ethdev.com + * @date 2015 + * Ethereum IDE client. + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "MixClient.h" + +using namespace dev; +using namespace dev::eth; +using namespace dev::mix; + +MixClient::MixClient(): + m_userAccount(KeyPair::create()) +{ + resetState(10000000 * ether); +} + +void MixClient::resetState(u256 _balance) +{ + WriteGuard l(x_state); + m_state = eth::State(Address(), m_stateDB, BaseState::Empty); + m_state.addBalance(m_userAccount.address(), _balance); +} + +void MixClient::executeTransaction(bytesConstRef _rlp, State& _state) +{ + Executive execution(_state, LastHashes(), 0); + execution.setup(_rlp); + bytes code; + bytesConstRef data; + bool firstIteration = true; + std::vector machineStates; + std::vector levels; + auto onOp = [&](uint64_t steps, Instruction inst, dev::bigint newMemSize, dev::bigint gasCost, void* voidVM, void const* voidExt) + { + VM& vm = *(VM*)voidVM; + ExtVM const& ext = *(ExtVM const*)voidExt; + + if (firstIteration) + { + code = ext.code; + data = ext.data; + firstIteration = false; + } + + if (levels.size() < ext.depth) + levels.push_back(&machineStates.back()); + else + levels.resize(ext.depth); + + machineStates.push_back(MachineState({steps, ext.myAddress, vm.curPC(), inst, newMemSize, vm.gas(), + vm.stack(), vm.memory(), gasCost, ext.state().storage(ext.myAddress), levels})); + }; + + execution.go(onOp); + execution.finalize(); + + ExecutionResult d; + d.returnValue = execution.out().toVector(); + d.machineStates = machineStates; + d.executionCode = code; + d.executionData = data; + d.contentAvailable = true; + d.message = "ok"; + m_lastExecutionResult = d; +} + +void MixClient::validateBlock(int _block) const +{ + //TODO: throw exception here if _block != 0 + (void)_block; +} + +void MixClient::transact(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice) +{ + WriteGuard l(x_state); + u256 n = m_state.transactionsFrom(toAddress(_secret)); + Transaction t(_value, _gasPrice, _gas, _dest, _data, n, _secret); + bytes rlp = t.rlp(); + executeTransaction(&rlp, m_state); +} + +Address MixClient::transact(Secret _secret, u256 _endowment, bytes const& _init, u256 _gas, u256 _gasPrice) +{ + WriteGuard l(x_state); + u256 n = m_state.transactionsFrom(toAddress(_secret)); + eth::Transaction t(_endowment, _gasPrice, _gas, _init, n, _secret); + bytes rlp = t.rlp(); + executeTransaction(&rlp, m_state); + return right160(sha3(rlpList(t.sender(), t.nonce()))); +} + +void MixClient::inject(bytesConstRef _rlp) +{ + WriteGuard l(x_state); + executeTransaction(_rlp, m_state); +} + +void MixClient::flushTransactions() +{ +} + +bytes MixClient::call(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice) +{ + bytes out; + u256 n; + State temp; + { + ReadGuard lr(x_state); + temp = m_state; + n = temp.transactionsFrom(toAddress(_secret)); + } + Transaction t(_value, _gasPrice, _gas, _dest, _data, n, _secret); + bytes rlp = t.rlp(); + WriteGuard lw(x_state); //TODO: lock is required only for last executoin state + executeTransaction(&rlp, temp); + return m_lastExecutionResult.returnValue; +} + +u256 MixClient::balanceAt(Address _a, int _block) const +{ + validateBlock(_block); + ReadGuard l(x_state); + return m_state.balance(_a); +} + +u256 MixClient::countAt(Address _a, int _block) const +{ + validateBlock(_block); + ReadGuard l(x_state); + return m_state.transactionsFrom(_a); +} + +u256 MixClient::stateAt(Address _a, u256 _l, int _block) const +{ + validateBlock(_block); + ReadGuard l(x_state); + return m_state.storage(_a, _l); +} + +bytes MixClient::codeAt(Address _a, int _block) const +{ + validateBlock(_block); + ReadGuard l(x_state); + return m_state.code(_a); +} + +std::map MixClient::storageAt(Address _a, int _block) const +{ + validateBlock(_block); + ReadGuard l(x_state); + return m_state.storage(_a); +} + +eth::LogEntries MixClient::logs(unsigned _watchId) const +{ + (void)_watchId; + return LogEntries(); +} + +eth::LogEntries MixClient::logs(eth::LogFilter const& _filter) const +{ + (void)_filter; + return LogEntries(); +} + +unsigned MixClient::installWatch(eth::LogFilter const& _filter) +{ + (void)_filter; + BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::installWatch")); +} + +unsigned MixClient::installWatch(h256 _filterId) +{ + (void)_filterId; + BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::installWatch")); +} + +void MixClient::uninstallWatch(unsigned _watchId) +{ + (void)_watchId; + BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::uninstallWatch")); +} + +bool MixClient::peekWatch(unsigned _watchId) const +{ + (void)_watchId; + BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::peekWatch")); +} + +bool MixClient::checkWatch(unsigned _watchId) +{ + (void)_watchId; + BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::checkWatch")); +} + +h256 MixClient::hashFromNumber(unsigned _number) const +{ + (void)_number; + BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::hashFromNumber")); +} + +eth::BlockInfo MixClient::blockInfo(h256 _hash) const +{ + (void)_hash; + BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::blockInfo")); +} + +eth::BlockDetails MixClient::blockDetails(h256 _hash) const +{ + (void)_hash; + BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::blockDetails")); +} + +eth::Transaction MixClient::transaction(h256 _blockHash, unsigned _i) const +{ + (void)_blockHash; + (void)_i; + BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::transaction")); +} + +eth::BlockInfo MixClient::uncle(h256 _blockHash, unsigned _i) const +{ + (void)_blockHash; + (void)_i; + BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::uncle")); +} + +unsigned MixClient::number() const +{ + return 0; +} + +eth::Transactions MixClient::pending() const +{ + return eth::Transactions(); +} + +eth::StateDiff MixClient::diff(unsigned _txi, h256 _block) const +{ + (void)_txi; + (void)_block; + BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::diff")); +} + +eth::StateDiff MixClient::diff(unsigned _txi, int _block) const +{ + (void)_txi; + (void)_block; + BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::diff")); +} + +Addresses MixClient::addresses(int _block) const +{ + validateBlock(_block); + ReadGuard l(x_state); + Addresses ret; + for (auto const& i: m_state.addresses()) + ret.push_back(i.first); + return ret; +} + +u256 MixClient::gasLimitRemaining() const +{ + ReadGuard l(x_state); + return m_state.gasLimitRemaining(); +} + +void MixClient::setAddress(Address _us) +{ + WriteGuard l(x_state); + m_state.setAddress(_us); +} + +Address MixClient::address() const +{ + ReadGuard l(x_state); + return m_state.address(); +} + +void MixClient::setMiningThreads(unsigned _threads) +{ + (void)_threads; + BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::setMiningThreads")); +} + +unsigned MixClient::miningThreads() const +{ + return 0; +} + +void MixClient::startMining() +{ + BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::startMining")); +} + +void MixClient::stopMining() +{ + BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::Interface::stopMining")); +} + +bool MixClient::isMining() +{ + return false; +} + +eth::MineProgress MixClient::miningProgress() const +{ + return eth::MineProgress(); +} diff --git a/mix/MixClient.h b/mix/MixClient.h new file mode 100644 index 000000000..4cbaad6e8 --- /dev/null +++ b/mix/MixClient.h @@ -0,0 +1,125 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file MixClient.h + * @author Yann yann@ethdev.com + * @author Arkadiy Paronyan arkadiy@ethdev.com + * @date 2015 + * Ethereum IDE client. + */ + +#pragma once + +#include + +namespace dev +{ +namespace mix +{ + +/** + * @brief Store information about a machine state. + */ +struct MachineState +{ + uint64_t steps; + dev::Address cur; + dev::u256 curPC; + dev::eth::Instruction inst; + dev::bigint newMemSize; + dev::u256 gas; + dev::u256s stack; + dev::bytes memory; + dev::bigint gasCost; + std::map storage; + std::vector levels; +}; + +/** + * @brief Store information about a machine states. + */ +struct ExecutionResult +{ + std::vector machineStates; + bytes executionCode; + bytesConstRef executionData; + Address contractAddress; + bool contentAvailable; + std::string message; + bytes returnValue; +}; + +class MixClient: public dev::eth::Interface +{ +public: + MixClient(); + /// Reset state to the empty state with given balance. + void resetState(u256 _balance); + const KeyPair& userAccount() const { return m_userAccount; } + const ExecutionResult lastExecutionResult() const { ReadGuard l(x_state); return m_lastExecutionResult; } + + //dev::eth::Interface + void transact(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice) override; + Address transact(Secret _secret, u256 _endowment, bytes const& _init, u256 _gas, u256 _gasPrice) override; + void inject(bytesConstRef _rlp) override; + void flushTransactions() override; + bytes call(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice) override; + u256 balanceAt(Address _a, int _block) const override; + u256 countAt(Address _a, int _block) const override; + u256 stateAt(Address _a, u256 _l, int _block) const override; + bytes codeAt(Address _a, int _block) const override; + std::map storageAt(Address _a, int _block) const override; + eth::LogEntries logs(unsigned _watchId) const override; + eth::LogEntries logs(eth::LogFilter const& _filter) const override; + unsigned installWatch(eth::LogFilter const& _filter) override; + unsigned installWatch(h256 _filterId) override; + void uninstallWatch(unsigned _watchId) override; + bool peekWatch(unsigned _watchId) const override; + bool checkWatch(unsigned _watchId) override; + h256 hashFromNumber(unsigned _number) const override; + eth::BlockInfo blockInfo(h256 _hash) const override; + eth::BlockDetails blockDetails(h256 _hash) const override; + eth::Transaction transaction(h256 _blockHash, unsigned _i) const override; + eth::BlockInfo uncle(h256 _blockHash, unsigned _i) const override; + unsigned number() const override; + eth::Transactions pending() const override; + eth::StateDiff diff(unsigned _txi, h256 _block) const override; + eth::StateDiff diff(unsigned _txi, int _block) const override; + Addresses addresses(int _block) const override; + u256 gasLimitRemaining() const override; + void setAddress(Address _us) override; + Address address() const override; + void setMiningThreads(unsigned _threads) override; + unsigned miningThreads() const override; + void startMining() override; + void stopMining() override; + bool isMining() override; + eth::MineProgress miningProgress() const override; + +private: + void executeTransaction(bytesConstRef _rlp, eth::State& _state); + void validateBlock(int _block) const; + + KeyPair m_userAccount; + eth::State m_state; + OverlayDB m_stateDB; + mutable boost::shared_mutex x_state; + ExecutionResult m_lastExecutionResult; +}; + +} + +} diff --git a/mix/QFunctionDefinition.cpp b/mix/QFunctionDefinition.cpp index 7149809af..97ce0ff58 100644 --- a/mix/QFunctionDefinition.cpp +++ b/mix/QFunctionDefinition.cpp @@ -20,12 +20,14 @@ */ #include +#include #include "QVariableDeclaration.h" #include "QFunctionDefinition.h" + using namespace dev::solidity; using namespace dev::mix; -QFunctionDefinition::QFunctionDefinition(dev::solidity::FunctionDefinition const* _f, int _index): QBasicNodeDefinition(_f), m_index(_index) +QFunctionDefinition::QFunctionDefinition(dev::solidity::FunctionDefinition const* _f, int _index): QBasicNodeDefinition(_f), m_index(_index), m_hash(dev::sha3(_f->getCanonicalSignature())) { std::vector> parameters = _f->getParameterList().getParameters(); for (unsigned i = 0; i < parameters.size(); i++) diff --git a/mix/QFunctionDefinition.h b/mix/QFunctionDefinition.h index b2d0cd0b7..7f606c8a1 100644 --- a/mix/QFunctionDefinition.h +++ b/mix/QFunctionDefinition.h @@ -49,9 +49,12 @@ public: QList returnParameters() const { return m_returnParameters; } /// Get the index of this function on the contract ABI. int index() const { return m_index; } + /// Get the hash of this function declaration on the contract ABI. + FixedHash<4> hash() const { return m_hash; } private: int m_index; + FixedHash<4> m_hash; QList m_parameters; QList m_returnParameters; void initQParameters(); diff --git a/mix/Web3Server.cpp b/mix/Web3Server.cpp new file mode 100644 index 000000000..469ca907d --- /dev/null +++ b/mix/Web3Server.cpp @@ -0,0 +1,55 @@ +/* + 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 Web3Server.h.cpp + * @author Arkadiy Paronyan arkadiy@ethdev.com + * @date 2014 + * Ethereum IDE client. + */ + + +#include +#include "Web3Server.h" + +using namespace dev::mix; + +Web3Server::Web3Server(jsonrpc::AbstractServerConnector& _conn, std::vector const& _accounts, dev::eth::Interface* _client): + WebThreeStubServerBase(_conn, _accounts), + m_client(_client) +{ +} + +std::shared_ptr Web3Server::face() +{ + BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::shh::Interface")); +} + +dev::WebThreeNetworkFace* Web3Server::network() +{ + BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::WebThreeNetworkFace")); +} + +std::string Web3Server::get(std::string const& _name, std::string const& _key) +{ + std::string k(_name + "/" + _key); + return m_db[k]; +} + +void Web3Server::put(std::string const& _name, std::string const& _key, std::string const& _value) +{ + std::string k(_name + "/" + _key); + m_db[k] = _value; +} diff --git a/mix/Web3Server.h b/mix/Web3Server.h new file mode 100644 index 000000000..c603b48a2 --- /dev/null +++ b/mix/Web3Server.h @@ -0,0 +1,56 @@ +/* + 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 Web3Server.h + * @author Arkadiy Paronyan arkadiy@ethdev.com + * @date 2015 + * Ethereum IDE client. + */ + +#pragma once + +#include +#include +#include + +namespace dev +{ + +namespace mix +{ + +class Web3Server: public dev::WebThreeStubServerBase, public dev::WebThreeStubDatabaseFace +{ +public: + Web3Server(jsonrpc::AbstractServerConnector& _conn, std::vector const& _accounts, dev::eth::Interface* _client); + +private: + dev::eth::Interface* client() override { return m_client; } + std::shared_ptr face() override; + dev::WebThreeNetworkFace* network() override; + dev::WebThreeStubDatabaseFace* db() override { return this; } + + std::string get(std::string const& _name, std::string const& _key) override; + void put(std::string const& _name, std::string const& _key, std::string const& _value) override; + +private: + dev::eth::Interface* m_client; + std::map m_db; +}; + +} + +} diff --git a/mix/qml.qrc b/mix/qml.qrc index 0e25de765..28b82865f 100644 --- a/mix/qml.qrc +++ b/mix/qml.qrc @@ -18,5 +18,6 @@ qml/CodeEditor.qml qml/CodeEditorView.qml qml/js/ProjectModel.js + qml/WebPreview.qml diff --git a/mix/qml/CodeEditorView.qml b/mix/qml/CodeEditorView.qml index 45d2cfb2a..4e95bd49c 100644 --- a/mix/qml/CodeEditorView.qml +++ b/mix/qml/CodeEditorView.qml @@ -44,7 +44,7 @@ Item { onDocumentOpened: { openDocument(document); } - onProjectSaved: { + onProjectSaving: { for (var i = 0; i < editorListModel.count; i++) fileIo.writeFile(editorListModel.get(i).path, editors.itemAt(i).item.getText()); } diff --git a/mix/qml/ProjectModel.qml b/mix/qml/ProjectModel.qml index 976f70316..cc5ecccd9 100644 --- a/mix/qml/ProjectModel.qml +++ b/mix/qml/ProjectModel.qml @@ -15,7 +15,7 @@ Item { signal projectClosed signal projectLoaded signal documentOpened(var document) - signal projectSaved + signal projectSaving(var projectData) property bool isEmpty: (projectData === null) readonly property string projectFileName: ".mix" @@ -35,9 +35,12 @@ Item { function addExistingFile() { ProjectModelCode.addExistingFile(); } function openDocument(documentId) { ProjectModelCode.openDocument(documentId); } - Component.onCompleted: { - if (projectSettings.lastProjectPath) - loadProject(projectSettings.lastProjectPath) + Connections { + target: appContext + onAppLoaded: { + if (projectSettings.lastProjectPath) + loadProject(projectSettings.lastProjectPath) + } } NewProjectDialog { diff --git a/mix/qml/StateDialog.qml b/mix/qml/StateDialog.qml index 862e6f854..208c766f9 100644 --- a/mix/qml/StateDialog.qml +++ b/mix/qml/StateDialog.qml @@ -122,7 +122,7 @@ Window { var item = { value: "0", functionId: "", - gas: "1000000000000", + gas: "125000", gasPrice: "100000" }; diff --git a/mix/qml/StateList.qml b/mix/qml/StateList.qml index de59fec4a..12f0318d1 100644 --- a/mix/qml/StateList.qml +++ b/mix/qml/StateList.qml @@ -29,6 +29,9 @@ Rectangle { stateList.push(items[i]) } } + onProjectSaving: { + projectData.states = stateList; + } } ListView { @@ -78,7 +81,7 @@ Rectangle { function runState(index) { var item = stateList[index]; - debugModel.debugState(item); + clientModel.debugState(item); } function deleteState(index) { @@ -88,7 +91,6 @@ Rectangle { } function save() { - console.log(parent.id); ProjectModel.saveProject(); } } @@ -131,7 +133,7 @@ Rectangle { id: addStateAction text: "&Add State" shortcut: "Ctrl+T" - enabled: codeModel.hasContract && !debugModel.running; + enabled: codeModel.hasContract && !clientModel.running; onTriggered: stateListModel.addState(); } } diff --git a/mix/qml/WebPreview.qml b/mix/qml/WebPreview.qml new file mode 100644 index 000000000..77e60ee66 --- /dev/null +++ b/mix/qml/WebPreview.qml @@ -0,0 +1,81 @@ +import QtQuick 2.0 +import QtQuick.Window 2.0 +import QtQuick.Layouts 1.0 +import QtQuick.Controls 1.0 +import QtQuick.Controls.Styles 1.1 + +Component { + Item { + signal editorTextChanged + + function setText(text) { + codeEditor.text = text; + } + + function getText() { + return codeEditor.text; + } + + anchors.fill: parent + id: contentView + width: parent.width + height: parent.height * 0.7 + Rectangle { + id: lineColumn + property int rowHeight: codeEditor.font.pixelSize + 3 + color: "#202020" + width: 50 + height: parent.height + Column { + y: -codeEditor.flickableItem.contentY + 4 + width: parent.width + Repeater { + model: Math.max(codeEditor.lineCount + 2, (lineColumn.height/lineColumn.rowHeight)) + delegate: Text { + id: text + color: codeEditor.textColor + font: codeEditor.font + width: lineColumn.width - 4 + horizontalAlignment: Text.AlignRight + verticalAlignment: Text.AlignVCenter + height: lineColumn.rowHeight + renderType: Text.NativeRendering + text: index + 1 + } + } + } + } + + TextArea { + id: codeEditor + textColor: "#EEE8D5" + style: TextAreaStyle { + backgroundColor: "#002B36" + } + + anchors.left: lineColumn.right + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: parent.bottom + wrapMode: TextEdit.NoWrap + frameVisible: false + + height: parent.height + font.family: "Monospace" + font.pointSize: 12 + width: parent.width + + tabChangesFocus: false + Keys.onPressed: { + if (event.key === Qt.Key_Tab) { + codeEditor.insert(codeEditor.cursorPosition, "\t"); + event.accepted = true; + } + } + onTextChanged: { + editorTextChanged(); + } + + } + } +} diff --git a/mix/qml/js/ProjectModel.js b/mix/qml/js/ProjectModel.js index 7bcfb5cd7..a547457b0 100644 --- a/mix/qml/js/ProjectModel.js +++ b/mix/qml/js/ProjectModel.js @@ -22,7 +22,6 @@ function saveAll() { saveProject(); - projectSaved(); } function createProject() { @@ -44,6 +43,7 @@ function closeProject() { function saveProject() { if (!isEmpty) { + projectSaving(projectData); var json = JSON.stringify(projectData); var projectFile = projectPath + projectFileName; fileIo.writeFile(projectFile, json); diff --git a/mix/qml/main.qml b/mix/qml/main.qml index 4bea535c2..f59c5874c 100644 --- a/mix/qml/main.qml +++ b/mix/qml/main.qml @@ -63,15 +63,15 @@ ApplicationWindow { id: debugRunAction text: "&Run" shortcut: "F5" - enabled: codeModel.hasContract && !debugModel.running; - onTriggered: debugModel.debugDeployment(); + enabled: codeModel.hasContract && !clientModel.running; + onTriggered: clientModel.debugDeployment(); } Action { id: debugResetStateAction text: "Reset &State" shortcut: "F6" - onTriggered: debugModel.resetState(); + onTriggered: clientModel.resetState(); } Action {