From 2eee2b2af5219bea0a29a6373297692b57cefd33 Mon Sep 17 00:00:00 2001 From: Gav Wood Date: Sun, 4 May 2014 15:37:22 +0100 Subject: [PATCH] First JSON-RPC stubs. --- eth/CMakeLists.txt | 4 + eth/CommonJS.cpp | 65 ++++++++++++ eth/CommonJS.h | 88 ++++++++++++++++ eth/EthStubServer.cpp | 59 +++++++++++ eth/EthStubServer.h | 41 ++++++++ eth/main.cpp | 237 +++++++++++++++++++++++++++++++++++++++--- 6 files changed, 481 insertions(+), 13 deletions(-) create mode 100644 eth/CommonJS.cpp create mode 100644 eth/CommonJS.h create mode 100644 eth/EthStubServer.cpp create mode 100644 eth/EthStubServer.h diff --git a/eth/CMakeLists.txt b/eth/CMakeLists.txt index 009dc3d47..ed58108a8 100644 --- a/eth/CMakeLists.txt +++ b/eth/CMakeLists.txt @@ -23,6 +23,10 @@ if (${TARGET_PLATFORM} STREQUAL "w64") target_link_libraries(eth boost_thread_win32-mt-s) set(CMAKE_SHARED_LIBRARY_LINK_CXX_FLAGS) elseif (UNIX) + add_definitions(-DETH_JSONRPC) + add_definitions(-DETH_READLINE) + target_link_libraries(eth jsonrpc) + target_link_libraries(eth readline) target_link_libraries(eth form) else () target_link_libraries(eth ${CRYPTOPP_LIBRARIES}) diff --git a/eth/CommonJS.cpp b/eth/CommonJS.cpp new file mode 100644 index 000000000..e4c60a120 --- /dev/null +++ b/eth/CommonJS.cpp @@ -0,0 +1,65 @@ +/* + 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 CommonJS.cpp + * @authors: + * Gav Wood + * @date 2014 + */ + +#include "CommonJS.h" +using namespace std; +using namespace eth; + +bytes eth::jsToBytes(string const& _s) +{ + if (_s.substr(0, 2) == "0x") + // Hex + return fromHex(_s.substr(2)); + else if (_s.find_first_not_of("0123456789") == string::npos) + // Decimal + return toCompactBigEndian(bigint(_s)); + else + // Binary + return asBytes(_s); +} + +string eth::jsPadded(string const& _s, unsigned _l, unsigned _r) +{ + bytes b = jsToBytes(_s); + while (b.size() < _l) + b.insert(b.begin(), 0); + while (b.size() < _r) + b.push_back(0); + return asString(b).substr(b.size() - max(_l, _r)); +} + +string eth::jsPadded(string const& _s, unsigned _l) +{ + if (_s.substr(0, 2) == "0x" || _s.find_first_not_of("0123456789") == string::npos) + // Numeric: pad to right + return jsPadded(_s, _l, _l); + else + // Text: pad to the left + return jsPadded(_s, 0, _l); +} + +string eth::jsUnpadded(string _s) +{ + auto p = _s.find_last_not_of((char)0); + _s.resize(p == string::npos ? 0 : (p + 1)); + return _s; +} diff --git a/eth/CommonJS.h b/eth/CommonJS.h new file mode 100644 index 000000000..0dc5cd9fb --- /dev/null +++ b/eth/CommonJS.h @@ -0,0 +1,88 @@ +/* + 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 CommonJS.h + * @authors: + * Gav Wood + * @date 2014 + */ + +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace eth +{ + +eth::bytes jsToBytes(std::string const& _s); +std::string jsPadded(std::string const& _s, unsigned _l, unsigned _r); +std::string jsPadded(std::string const& _s, unsigned _l); +std::string jsUnpadded(std::string _s); + +template eth::FixedHash jsToFixed(std::string const& _s) +{ + if (_s.substr(0, 2) == "0x") + // Hex + return eth::FixedHash(_s.substr(2)); + else if (_s.find_first_not_of("0123456789") == std::string::npos) + // Decimal + return (typename eth::FixedHash::Arith)(_s); + else + // Binary + return eth::FixedHash(asBytes(jsPadded(_s, N))); +} + +template boost::multiprecision::number> jsToInt(std::string const& _s) +{ + if (_s.substr(0, 2) == "0x") + // Hex + return eth::fromBigEndian>>(eth::fromHex(_s.substr(2))); + else if (_s.find_first_not_of("0123456789") == std::string::npos) + // Decimal + return boost::multiprecision::number>(_s); + else + // Binary + return eth::fromBigEndian>>(asBytes(jsPadded(_s, N))); +} + +inline eth::Address jsToAddress(std::string const& _s) { return jsToFixed<20>(_s); } +inline eth::Secret jsToSecret(std::string const& _s) { return jsToFixed<32>(_s); } +inline eth::u256 jsToU256(std::string const& _s) { return jsToInt<32>(_s); } + +template std::string toJS(eth::FixedHash const& _h) { return "0x" + toHex(_h.ref()); } +template std::string toJS(boost::multiprecision::number> const& _n) { return "0x" + eth::toHex(eth::toCompactBigEndian(_n)); } + +inline std::string jsToBinary(std::string const& _s) +{ + return eth::asString(jsToBytes(_s)); +} + +inline std::string jsToDecimal(std::string const& _s) +{ + return eth::toString(jsToU256(_s)); +} + +inline std::string jsToHex(std::string const& _s) +{ + return "0x" + eth::toHex(asBytes(_s)); +} + +} diff --git a/eth/EthStubServer.cpp b/eth/EthStubServer.cpp new file mode 100644 index 000000000..0334f3fd4 --- /dev/null +++ b/eth/EthStubServer.cpp @@ -0,0 +1,59 @@ +/* + 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 EthStubServer.cpp + * @authors: + * Gav Wood + * @date 2014 + */ + +#include "EthStubServer.h" +#include +#include "CommonJS.h" +using namespace std; +using namespace eth; + +EthStubServer::EthStubServer(jsonrpc::AbstractServerConnector* _conn, Client& _client): + AbstractEthStubServer(_conn), + m_client(_client) +{ +} + +std::string EthStubServer::coinbase() +{ + return toJS(m_client.address()); +} + +std::string EthStubServer::balanceAt(std::string const& _a) +{ + return toJS(m_client.postState().balance(jsToAddress(_a))); +} + +Json::Value EthStubServer::check(Json::Value const& _as) +{ + if (m_client.changed()) + return _as; + else + { + Json::Value ret; + ret.resize(0); + return ret; + } +} + + + + diff --git a/eth/EthStubServer.h b/eth/EthStubServer.h new file mode 100644 index 000000000..27400da8f --- /dev/null +++ b/eth/EthStubServer.h @@ -0,0 +1,41 @@ +/* + 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 EthStubServer.h + * @author Gav Wood + * @date 2014 + */ + +#pragma once + +#include +#include +#include "abstractethstubserver.h" + +namespace eth { class Client; } + +class EthStubServer: public AbstractEthStubServer +{ +public: + EthStubServer(jsonrpc::AbstractServerConnector* _conn, eth::Client& _client); + + virtual std::string coinbase(); + virtual std::string balanceAt(std::string const& _a); + virtual Json::Value check(Json::Value const& _as); + +private: + eth::Client& m_client; +}; diff --git a/eth/main.cpp b/eth/main.cpp index b208fda68..0499b63bc 100644 --- a/eth/main.cpp +++ b/eth/main.cpp @@ -20,6 +20,9 @@ * Ethereum client. */ +#define ETH_READLINE 1 +#define ETH_JSONRPC 1 + #include #include #include @@ -31,6 +34,13 @@ #include #include #include +#if ETH_READLINE +#include +#include +#endif +#if ETH_JSONRPC +#include "EthStubServer.h" +#endif #include "BuildInfo.h" using namespace std; using namespace eth; @@ -47,6 +57,29 @@ bool isFalse(std::string const& _m) return _m == "off" || _m == "no" || _m == "false" || _m == "0"; } +void interactiveHelp() +{ + cout + << "Commands:" << endl + << " netstart Starts the network subsystem on a specific port." << endl + << " netstop Stops the network subsystem." << endl + << " jsonstart Starts the JSON-RPC server." << endl + << " jsonstop Stops the JSON-RPC server." << endl + << " connect Connects to a specific peer." << endl + << " minestart Starts mining." << endl + << " minestop Stops mining." << endl + << " address Gives the current address." << endl + << " secret Gives the current secret" << endl + << " block Gives the current block height." << endl + << " balance Gives the current balance." << endl + << " peers List the peers that are connected" << endl + << " transact Execute a given transaction. TODO." << endl + << " send Execute a given transaction with current secret. TODO." << endl + << " create Create a new contract with current secret. TODO." << endl + << " inspect Dumps a contract to /.evm." << endl + << " exit Exits the application." << endl; +} + void help() { cout @@ -58,6 +91,9 @@ void help() << " /Etherum or Library/Application Support/Ethereum)." << endl << " -h,--help Show this help message and exit." << endl << " -i,--interactive Enter interactive mode (default: non-interactive)." << endl +#if ETH_JSONRPC + << " -j,--json-rpc Start JSON-RPC server." << endl +#endif << " -l,--listen Listen on the given port for incoming connected (default: 30303)." << endl << " -m,--mining Enable mining, optionally for a specified number of blocks (Default: off)" << endl << " -n,--upnp Use upnp for NAT (default: on)." << endl @@ -74,8 +110,8 @@ void help() string credits(bool _interactive = false) { - std::ostringstream ccout; - ccout + std::ostringstream cout; + cout << "Ethereum (++) " << ETH_QUOTED(ETH_VERSION) << endl << " Code by Gav Wood, (c) 2013, 2014." << endl << " Based on a design by Vitalik Buterin." << endl << endl; @@ -91,11 +127,11 @@ string credits(bool _interactive = false) if (pocnumber == 4) m_servers = "54.72.31.55"; - ccout << "Type 'netstart 30303' to start networking" << endl; - ccout << "Type 'connect " << m_servers << " 30303' to connect" << endl; - ccout << "Type 'exit' to quit" << endl << endl; + cout << "Type 'netstart 30303' to start networking" << endl; + cout << "Type 'connect " << m_servers << " 30303' to connect" << endl; + cout << "Type 'exit' to quit" << endl << endl; } - return ccout.str(); + return cout.str(); } void version() @@ -133,6 +169,10 @@ int main(int argc, char** argv) eth::uint mining = ~(eth::uint)0; NodeMode mode = NodeMode::Full; unsigned peers = 5; + bool interactive = false; +#if ETH_JSONRPC + int jsonrpc = -1; +#endif string publicIP; bool upnp = true; string clientName; @@ -206,6 +246,12 @@ int main(int argc, char** argv) return -1; } } + else if (arg == "-i" || arg == "--interactive") + interactive = true; +#if ETH_JSONRPC + else if ((arg == "-j" || arg == "--json-rpc") && i + 1 < argc) + jsonrpc = atoi(argv[++i]); +#endif else if ((arg == "-v" || arg == "--verbosity") && i + 1 < argc) g_logVerbosity = atoi(argv[++i]); else if ((arg == "-x" || arg == "--peers") && i + 1 < argc) @@ -238,15 +284,180 @@ int main(int argc, char** argv) cout << "Address: " << endl << toHex(us.address().asArray()) << endl; c.startNetwork(listenPort, remoteHost, remotePort, mode, peers, publicIP, upnp); - eth::uint n = c.blockChain().details().number; - if (mining) - c.startMining(); - while (true) + +#if ETH_JSONRPC + auto_ptr jsonrpcServer; + if (jsonrpc > -1) + { + jsonrpcServer = auto_ptr(new EthStubServer(new jsonrpc::HttpServer(jsonrpc), c)); + jsonrpcServer->StartListening(); + } +#endif + + if (interactive) + { + string l; + while (true) + { +#if ETH_READLINE + if (l.size()) + add_history(l.c_str()); + if (auto c = readline("> ")) + { + l = c; + free(c); + } + else + break; +#else + string l; + cout << "> " << flush; + std::getline(cin, l); +#endif + istringstream iss(l); + string cmd; + iss >> cmd; + if (cmd == "netstart") + { + eth::uint port; + iss >> port; + c.lock(); + c.startNetwork((short)port); + c.unlock(); + } + else if (cmd == "connect") + { + string addr; + eth::uint port; + iss >> addr >> port; + c.lock(); + c.connect(addr, (short)port); + c.unlock(); + } + else if (cmd == "netstop") + { + c.lock(); + c.stopNetwork(); + c.unlock(); + } + else if (cmd == "minestart") + { + c.lock(); + c.startMining(); + c.unlock(); + } + else if (cmd == "minestop") + { + c.lock(); + c.stopMining(); + c.unlock(); + } + else if (cmd == "jsonstart") + { + unsigned port; + iss >> port; + jsonrpcServer = auto_ptr(new EthStubServer(new jsonrpc::HttpServer(port), c)); + jsonrpcServer->StartListening(); + } + else if (cmd == "jsonstop") + { + jsonrpcServer->StopListening(); + jsonrpcServer.reset(); + } + else if (cmd == "address") + { + cout << "Current address:" << endl; + const char* addchr = toHex(us.address().asArray()).c_str(); + cout << addchr << endl; + } + else if (cmd == "secret") + { + cout << "Current secret:" << endl; + const char* addchr = toHex(us.secret().asArray()).c_str(); + cout << addchr << endl; + } + else if (cmd == "block") + { + eth::uint n = c.blockChain().details().number; + cout << "Current block # "; + const char* addchr = toString(n).c_str(); + cout << addchr << endl; + } + else if (cmd == "peers") + { + for (auto it: c.peers()) + cout << it.host << ":" << it.port << ", " << it.clientVersion << ", " + << std::chrono::duration_cast(it.lastPing).count() << "ms" + << endl; + } + else if (cmd == "balance") + { + u256 balance = c.state().balance(us.address()); + cout << "Current balance:" << endl; + const char* addchr = toString(balance).c_str(); + cout << addchr << endl; + } + else if (cmd == "transact") + { + //TODO. + } + else if (cmd == "send") + { + //TODO + } + else if (cmd == "create") + { + //TODO + } + else if (cmd == "inspect") + { + string rechex; + iss >> rechex; + + if (rechex.length() != 40) + cwarn << "Invalid address length"; + else + { + c.lock(); + auto h = h160(fromHex(rechex)); + stringstream s; + auto mem = c.state().storage(h); + + for (auto const& i: mem) + s << "@" << showbase << hex << i.first << " " << showbase << hex << i.second << endl; + s << endl << disassemble(c.state().code(h)); + + string outFile = getDataDir() + "/" + rechex + ".evm"; + ofstream ofs; + ofs.open(outFile, ofstream::binary); + ofs.write(s.str().c_str(), s.str().length()); + ofs.close(); + + c.unlock(); + } + } + else if (cmd == "help") + interactiveHelp(); + else if (cmd == "exit") + break; + else + cout << "Unrecognised command. Type 'help' for help in interactive mode." << endl; + } + jsonrpcServer->StopListening(); + } + else { - if (c.blockChain().details().number - n == mining) - c.stopMining(); - this_thread::sleep_for(chrono::milliseconds(100)); + eth::uint n = c.blockChain().details().number; + if (mining) + c.startMining(); + while (true) + { + if (c.blockChain().details().number - n == mining) + c.stopMining(); + this_thread::sleep_for(chrono::milliseconds(100)); + } } + return 0; }