diff --git a/eth/CMakeLists.txt b/eth/CMakeLists.txt index 8de0dc2b0e0..584dd27445d 100644 --- a/eth/CMakeLists.txt +++ b/eth/CMakeLists.txt @@ -8,7 +8,7 @@ set( add_executable(eth ${sources}) target_link_libraries( eth - PRIVATE ethereum ethashseal evm web3jsonrpc webthree devcore + PRIVATE ethereum ethashseal evm web3jsonrpc webthree devcore Boost::program_options ) target_include_directories(eth PRIVATE ../utils) diff --git a/eth/MinerAux.h b/eth/MinerAux.h index d0d72abcd46..f75564a895d 100644 --- a/eth/MinerAux.h +++ b/eth/MinerAux.h @@ -101,8 +101,9 @@ class MinerCLI BasicAuthority::init(); } - bool interpretOption(int& i, int argc, char** argv) + bool interpretOption(size_t& i, vector const& argv) { + size_t argc = argv.size(); string arg = argv[i]; if (arg == "--benchmark-warmup" && i + 1 < argc) try { diff --git a/eth/main.cpp b/eth/main.cpp index 520022475ea..3de2477ced4 100644 --- a/eth/main.cpp +++ b/eth/main.cpp @@ -30,6 +30,8 @@ #include #include #include +#include +#include #include #include @@ -66,93 +68,14 @@ using namespace dev; using namespace dev::p2p; using namespace dev::eth; using namespace boost::algorithm; +namespace po = boost::program_options; namespace fs = boost::filesystem; -static std::atomic g_silence = {false}; - -void help() +namespace { - cout - << "Usage eth [OPTIONS]\n" - << "Options:\n\n" - << "Wallet usage:\n"; - AccountManager::streamAccountHelp(cout); - AccountManager::streamWalletHelp(cout); - cout - << "\nClient mode (default):\n" - << " --mainnet Use the main network protocol.\n" - << " --ropsten Use the Ropsten testnet.\n" - << " --private Use a private chain.\n" - << " --test Testing mode: Disable PoW and provide test rpc interface.\n" - << " --config Configure specialised blockchain using given JSON information.\n" - << " -o,--mode Start a full node or a peer node (default: full).\n\n" - << " --ipc Enable IPC server (default: on).\n" - << " --ipcpath Set .ipc socket path (default: data directory)\n" - << " --no-ipc Disable IPC server.\n" - << " --admin Specify admin session key for JSON-RPC (default: auto-generated and printed at start-up).\n" - << " -K,--kill Kill the blockchain first.\n" - << " -R,--rebuild Rebuild the blockchain from the existing database.\n" - << " --rescue Attempt to rescue a corrupt database.\n\n" - << " --import-presale Import a pre-sale key; you'll need to specify the password to this key.\n" - << " -s,--import-secret Import a secret key into the key store.\n" - << " --master Give the master password for the key store. Use --master \"\" to show a prompt.\n" - << " --password Give a password for a private key.\n\n" - << "Client transacting:\n" - << " --ask Set the minimum ask gas price under which no transaction will be mined (default " << toString(DefaultGasPrice) << " ).\n" - << " --bid Set the bid gas price to pay for transactions (default " << toString(DefaultGasPrice) << " ).\n" - << " --unsafe-transactions Allow all transactions to proceed without verification. EXTREMELY UNSAFE.\n" - << "Client mining:\n" - << " -a,--address Set the author (mining payout) address to given address (default: auto).\n" - << " -m,--mining Enable mining, optionally for a specified number of blocks (default: off).\n" - << " -f,--force-mining Mine even when there are no transactions to mine (default: off).\n" - << " -C,--cpu When mining, use the CPU.\n" - << " -t, --mining-threads Limit number of CPU/GPU miners to n (default: use everything available on selected platform).\n\n" - << "Client networking:\n" - << " --client-name Add a name to your client's version string (default: blank).\n" - << " --bootstrap Connect to the default Ethereum peer servers (default unless --no-discovery used).\n" - << " --no-bootstrap Do not connect to the default Ethereum peer servers (default only when --no-discovery is used).\n" - << " -x,--peers Attempt to connect to a given number of peers (default: 11).\n" - << " --peer-stretch Give the accepted connection multiplier (default: 7).\n" - - << " --public-ip Force advertised public IP to the given IP (default: auto).\n" - << " --listen-ip (:) Listen on the given IP for incoming connections (default: 0.0.0.0).\n" - << " --listen Listen on the given port for incoming connections (default: 30303).\n" - << " -r,--remote (:) Connect to the given remote host (default: none).\n" - << " --port Connect to the given remote port (default: 30303).\n" - << " --network-id Only connect to other hosts with this network id.\n" - << " --upnp Use UPnP for NAT (default: on).\n" - - << " --peerset Space delimited list of peers; element format: type:publickey@ipAddress[:port].\n" - << " Types:\n" - << " default Attempt connection when no other peers are available and pinning is disabled.\n" - << " required Keep connected at all times.\n" -// TODO: -// << " --trust-peers Space delimited list of publickeys." << endl - - << " --no-discovery Disable node discovery, implies --no-bootstrap.\n" - << " --pin Only accept or connect to trusted peers.\n" - << " --hermit Equivalent to --no-discovery --pin.\n" - << " --sociable Force discovery and no pinning.\n\n"; - MinerCLI::streamHelp(cout); - cout - << "Import/export modes:\n" - << " --from Export only from block n; n may be a decimal, a '0x' prefixed hash, or 'latest'.\n" - << " --to Export only to block n (inclusive); n may be a decimal, a '0x' prefixed hash, or 'latest'.\n" - << " --only Equivalent to --export-from n --export-to n.\n" - << " --dont-check Prevent checking some block aspects. Faster importing, but to apply only when the data is known to be valid.\n\n" - << " --import-snapshot Import blockchain and state data from the Parity Warp Sync snapshot." << endl - << "General Options:\n" - << " -d,--db-path,--datadir Load database from path (default: " << getDataDir() << ").\n" -#if ETH_EVMJIT - << " --vm Select VM; options are: interpreter, jit or smart (default: interpreter).\n" -#endif // ETH_EVMJIT - << " -v,--verbosity <0 - 9> Set the log verbosity from 0 to 9 (default: 8).\n" - << " -V,--version Show the version and exit.\n" - << " -h,--help Show this help message and exit.\n\n" - << "Experimental / Proof of Concept:\n" - << " --shh Enable Whisper.\n\n"; - exit(0); -} + +std::atomic g_silence = {false}; +unsigned const c_lineWidth = 160; string ethCredits(bool _interactive = false) { @@ -169,7 +92,6 @@ void version() cout << "eth network protocol version: " << dev::eth::c_protocolVersion << "\n"; cout << "Client database version: " << dev::eth::c_databaseVersion << "\n"; cout << "Build: " << DEV_QUOTED(ETH_BUILD_PLATFORM) << "/" << DEV_QUOTED(ETH_BUILD_TYPE) << "\n"; - exit(0); } /* @@ -201,33 +123,6 @@ void importPresale(KeyManager& _km, string const& _file, function _pas _km.import(k.secret(), "Presale wallet" + _file + " (insecure)"); } -Address c_config = Address("ccdeac59d35627b7de09332e819d5159e7bb7250"); -string pretty(h160 _a, dev::eth::State const& _st) -{ - string ns; - h256 n; - if (h160 nameReg = (u160)_st.storage(c_config, 0)) - n = _st.storage(nameReg, (u160)(_a)); - if (n) - { - std::string s((char const*)n.data(), 32); - if (s.find_first_of('\0') != string::npos) - s.resize(s.find_first_of('\0')); - ns = " " + s; - } - return ns; -} - -inline bool isPrime(unsigned _number) -{ - if (((!(_number & 1)) && _number != 2 ) || (_number < 2) || (_number % 3 == 0 && _number != 3)) - return false; - for(unsigned k = 1; 36 * k * k - 12 * k < _number; ++k) - if ((_number % (6 * k + 1) == 0) || (_number % (6 * k - 1) == 0)) - return false; - return true; -} - enum class NodeMode { PeerServer, @@ -278,6 +173,8 @@ class ExitHandler bool ExitHandler::s_shouldExit = false; +} + int main(int argc, char** argv) { setDefaultOrCLocale(); @@ -294,9 +191,6 @@ int main(int argc, char** argv) /// Operating mode. OperationMode mode = OperationMode::Node; -// unsigned prime = 0; -// bool yesIReallyKnowWhatImDoing = false; - strings scripts; /// File name for import/export. string filename; @@ -314,14 +208,12 @@ int main(int argc, char** argv) string jsonAdmin; ChainParams chainParams; - u256 gasFloor = Invalid256; string privateChain; bool upnp = true; WithExisting withExisting = WithExisting::Trust; /// Networking params. - string clientName; string listenIP; unsigned short listenPort = 30303; string publicIP; @@ -358,7 +250,6 @@ int main(int argc, char** argv) bool masterSet = false; /// Whisper - bool useWhisper = false; bool testingMode = false; fs::path configFile = getDataDir() / fs::path("config.rlp"); @@ -389,394 +280,479 @@ int main(int argc, char** argv) bool chainConfigIsSet = false; string configJSON; string genesisJSON; - for (int i = 1; i < argc; ++i) + + po::options_description clientDefaultMode("Client mode (default)", c_lineWidth); + clientDefaultMode.add_options() + ("mainnet", "Use the main network protocol.") + ("ropsten", "Use the Ropsten testnet.") + ("private", po::value()->value_name(""), "Use a private chain.") + ("test", "Testing mode: Disable PoW and provide test rpc interface.") + ("config", po::value()->value_name(""), "Configure specialised blockchain using given JSON information.\n") + ("genesis", po::value()->value_name(""), "Set genesis JSON file.") + ("mode,o", po::value()->value_name(""), "Start a full node or a peer node (default: full).\n") + ("ipc", "Enable IPC server (default: on).") + ("ipcpath", po::value()->value_name(""), "Set .ipc socket path (default: data directory)") + ("no-ipc", "Disable IPC server.") + ("admin", po::value()->value_name(""), "Specify admin session key for JSON-RPC (default: auto-generated and printed at start-up).") + ("kill,K", "Kill the blockchain first.") + ("rebuild,R", "Rebuild the blockchain from the existing database.") + ("rescue", "Attempt to rescue a corrupt database.\n") + ("import-presale", po::value()->value_name(""), "Import a pre-sale key; you'll need to specify the password to this key.") + ("import-secret,s", po::value()->value_name(""), "Import a secret key into the key store.") + ("import-session-secret,S", po::value()->value_name(""), "Import a secret session into the key store.") + ("master", po::value()->value_name(""), "Give the master password for the key store. Use --master \"\" to show a prompt.") + ("password", po::value()->value_name(""), "Give a password for a private key.\n"); + + po::options_description clientTransacting("Client transacting", c_lineWidth); + clientTransacting.add_options() + ("ask", po::value()->value_name(""), ("Set the minimum ask gas price under which no transaction will be mined\n(default " + toString(DefaultGasPrice) + ").").c_str()) + ("bid", po::value()->value_name(""), ("Set the bid gas price to pay for transactions\n(default " + toString(DefaultGasPrice) + ").").c_str()) + ("unsafe-transactions", "Allow all transactions to proceed without verification. EXTREMELY UNSAFE.\n"); + + po::options_description clientMining("Client mining", c_lineWidth); + clientMining.add_options() + ("address,a", po::value
()->value_name(""), "Set the author (mining payout) address to given address (default: auto).") + ("mining,m", po::value()->value_name(""), "Enable mining, optionally for a specified number of blocks (default: off).") + ("extra-data", po::value(), "Set extra data for the sealed blocks.\n"); + + po::options_description clientNetworking("Client networking", c_lineWidth); + clientNetworking.add_options() + ("bootstrap,b", "Connect to the default Ethereum peer servers (default unless --no-discovery used).") + ("no-bootstrap", "Do not connect to the default Ethereum peer servers (default only when --no-discovery is used).") + ("peers,x", po::value()->value_name(""), "Attempt to connect to a given number of peers (default: 11).") + ("peer-stretch", po::value()->value_name(""), "Give the accepted connection multiplier (default: 7).") + ("public-ip", po::value()->value_name(""), "Force advertised public IP to the given IP (default: auto).") + ("listen-ip", po::value()->value_name("(:)"), "Listen on the given IP for incoming connections (default: 0.0.0.0).") + ("listen", po::value()->value_name(""), "Listen on the given port for incoming connections (default: 30303).") + ("remote,r", po::value()->value_name("(:)"), "Connect to the given remote host (default: none).") + ("port", po::value()->value_name(""), "Connect to the given remote port (default: 30303).") + ("network-id", po::value()->value_name(""), "Only connect to other hosts with this network id.") + ("upnp", po::value()->value_name(""), "Use UPnP for NAT (default: on).") + ("peerset", po::value()->value_name(""), "Space delimited list of peers; element format: type:publickey@ipAddress[:port].\n Types:\n default Attempt connection when no other peers are available and pinning is disabled.\n required Keep connected at all times.\n") + ("no-discovery", "Disable node discovery, implies --no-bootstrap.") + ("pin", "Only accept or connect to trusted peers.") + ("hermit", "Equivalent to --no-discovery --pin.") + ("sociable", "Force discovery and no pinning.\n"); + + po::options_description importExportMode("Import/export modes", c_lineWidth); + importExportMode.add_options() + ("import,I", po::value()->value_name(""), "Import blocks from file.") + ("export,E", po::value()->value_name(""), "Export blocks to file.") + ("from", po::value()->value_name(""), "Export only from block n; n may be a decimal, a '0x' prefixed hash, or 'latest'.") + ("to", po::value()->value_name(""), "Export only to block n (inclusive); n may be a decimal, a '0x' prefixed hash, or 'latest'.") + ("only", po::value()->value_name(""), "Equivalent to --export-from n --export-to n.") + ("format", po::value()->value_name(""), "Set export format.") + ("dont-check", "Prevent checking some block aspects. Faster importing, but to apply only when the data is known to be valid.") + ("import-snapshot", po::value()->value_name(""), "Import blockchain and state data from the Parity Warp Sync snapshot.\n"); + + po::options_description generalOptions("General Options", c_lineWidth); + generalOptions.add_options() + ("db-path,d", po::value()->value_name(""), ("Load database from path\n(default: " + getDataDir().string() + ").\n").c_str()) +#if ETH_EVMJIT + ("vm", " Select VM; options are: interpreter, jit or smart (default: interpreter)") +#endif // ETH_EVMJIT + ("verbosity,v", po::value()->value_name("<0 - 9>"), "Set the log verbosity from 0 to 9 (default: 8).") + ("version,V", "Show the version and exit.") + ("help,h", "Show this help message and exit.\n"); + + + po::options_description allowedOptions("Allowed options"); + allowedOptions.add(clientDefaultMode).add(clientTransacting).add(clientMining).add(clientNetworking).add(importExportMode).add(generalOptions); + + po::variables_map vm; + vector unrecognisedOptions; + try { - string arg = argv[i]; - if (m.interpretOption(i, argc, argv)) - { - } - else if (arg == "--listen-ip" && i + 1 < argc) + po::parsed_options parsed = po::command_line_parser(argc, argv).options(allowedOptions).allow_unregistered().run(); + unrecognisedOptions = collect_unrecognized(parsed.options, po::include_positional); + po::store(parsed, vm); + po::notify(vm); + } + catch (po::error const& e) + { + cerr << e.what(); + return -1; + } + for (size_t i = 0; i < unrecognisedOptions.size(); ++i) + if (!m.interpretOption(i, unrecognisedOptions)) { - listenIP = argv[++i]; - listenSet = true; + cerr << "Invalid argument: " << unrecognisedOptions[i] << "\n"; + return -1; } - else if ((arg == "--listen" || arg == "--listen-port") && i + 1 < argc) + +#if ETH_EVMJIT + if (vm.count("vm")) + { + string vmKind = vm["vm"].as(); + if (vmKind == "interpreter") + VMFactory::setKind(VMKind::Interpreter); + else if (vmKind == "jit") + VMFactory::setKind(VMKind::JIT); + else if (vmKind == "smart") + VMFactory::setKind(VMKind::Smart); + else { - listenPort = (short)atoi(argv[++i]); - listenSet = true; + cerr << "Unknown VM kind: " << vmKind << "\n"; + return -1; } - else if ((arg == "--public-ip" || arg == "--public") && i + 1 < argc) + } +#endif + if (vm.count("import-snapshot")) + { + mode = OperationMode::ImportSnapshot; + filename = vm["import-snapshot"].as(); + } + if (vm.count("version")) + { + version(); + return 0; + } + if (vm.count("test")) + { + testingMode = true; + enableDiscovery = false; + disableDiscovery = true; + noPinning = true; + bootstrap = false; + } + if (vm.count("verbosity")) + g_logVerbosity = vm["verbosity"].as(); + if (vm.count("peers")) + peers = vm["peers"].as(); + if (vm.count("peer-stretch")) + peerStretch = vm["peer-stretch"].as(); + if (vm.count("peerset")) + { + string peerset = vm["peerset"].as(); + if (peerset.empty()) { - publicIP = argv[++i]; + cerr << "--peerset argument must not be empty"; + return -1; } - else if ((arg == "-r" || arg == "--remote") && i + 1 < argc) + + vector each; + boost::split(each, peerset, boost::is_any_of("\t ")); + for (auto const& p: each) { - string host = argv[++i]; - string::size_type found = host.find_first_of(':'); - if (found != std::string::npos) + string type; + string pubk; + string hostIP; + unsigned short port = c_defaultListenPort; + + // type:key@ip[:port] + vector typeAndKeyAtHostAndPort; + boost::split(typeAndKeyAtHostAndPort, p, boost::is_any_of(":")); + if (typeAndKeyAtHostAndPort.size() < 2 || typeAndKeyAtHostAndPort.size() > 3) + continue; + + type = typeAndKeyAtHostAndPort[0]; + if (typeAndKeyAtHostAndPort.size() == 3) + port = (uint16_t)atoi(typeAndKeyAtHostAndPort[2].c_str()); + + vector keyAndHost; + boost::split(keyAndHost, typeAndKeyAtHostAndPort[1], boost::is_any_of("@")); + if (keyAndHost.size() != 2) + continue; + pubk = keyAndHost[0]; + if (pubk.size() != 128) + continue; + hostIP = keyAndHost[1]; + + // todo: use Network::resolveHost() + if (hostIP.size() < 4 /* g.it */) + continue; + + bool required = type == "required"; + if (!required && type != "default") + continue; + + Public publicKey(fromHex(pubk)); + try { - remoteHost = host.substr(0, found); - remotePort = (short)atoi(host.substr(found + 1, host.length()).c_str()); + preferredNodes[publicKey] = make_pair(NodeIPEndpoint(bi::address::from_string(hostIP), port, port), required); } - else - remoteHost = host; - } - else if (arg == "--port" && i + 1 < argc) - { - remotePort = (short)atoi(argv[++i]); - } - else if (arg == "--password" && i + 1 < argc) - passwordsToNote.push_back(argv[++i]); - else if (arg == "--master" && i + 1 < argc) - { - masterPassword = argv[++i]; - masterSet = true; - } - else if ((arg == "-I" || arg == "--import" || arg == "import") && i + 1 < argc) - { - mode = OperationMode::Import; - filename = argv[++i]; - } - else if (arg == "--dont-check") - safeImport = true; - else if ((arg == "-E" || arg == "--export" || arg == "export") && i + 1 < argc) - { - mode = OperationMode::Export; - filename = argv[++i]; - } - else if (arg == "--script" && i + 1 < argc) - scripts.push_back(argv[++i]); - else if (arg == "--format" && i + 1 < argc) - { - string m = argv[++i]; - if (m == "binary") - exportFormat = Format::Binary; - else if (m == "hex") - exportFormat = Format::Hex; - else if (m == "human") - exportFormat = Format::Human; - else + catch (...) { - cerr << "Bad " << arg << " option: " << m << "\n"; + cerr << "Unrecognized peerset: " << peerset << "\n"; return -1; } } - else if (arg == "--to" && i + 1 < argc) - exportTo = argv[++i]; - else if (arg == "--from" && i + 1 < argc) - exportFrom = argv[++i]; - else if (arg == "--only" && i + 1 < argc) - exportTo = exportFrom = argv[++i]; - else if (arg == "--upnp" && i + 1 < argc) + } + if (vm.count("mode")) + { + string m = vm["mode"].as(); + if (m == "full") + nodeMode = NodeMode::Full; + else if (m == "peer") + nodeMode = NodeMode::PeerServer; + else { - string m = argv[++i]; - if (isTrue(m)) - upnp = true; - else if (isFalse(m)) - upnp = false; - else - { - cerr << "Bad " << arg << " option: " << m << "\n"; - return -1; - } + cerr << "Unknown mode: " << m << "\n"; + return -1; } - else if (arg == "--network-id" && i + 1 < argc) - try { - networkID = stol(argv[++i]); - } - catch (...) - { - cerr << "Bad " << arg << " option: " << argv[i] << "\n"; - return -1; - } - else if (arg == "--private" && i + 1 < argc) - try { - privateChain = argv[++i]; - } - catch (...) - { - cerr << "Bad " << arg << " option: " << argv[i] << "\n"; - return -1; - } - else if (arg == "--independent" && i + 1 < argc) - try { - privateChain = argv[++i]; - noPinning = enableDiscovery = true; - } - catch (...) - { - cerr << "Bad " << arg << " option: " << argv[i] << "\n"; - return -1; - } - else if (arg == "-K" || arg == "--kill-blockchain" || arg == "--kill") - withExisting = WithExisting::Kill; - else if (arg == "-R" || arg == "--rebuild") - withExisting = WithExisting::Verify; - else if (arg == "-R" || arg == "--rescue") - withExisting = WithExisting::Rescue; - else if (arg == "--client-name" && i + 1 < argc) - clientName = argv[++i]; - else if ((arg == "-a" || arg == "--address" || arg == "--author") && i + 1 < argc) - try { - author = h160(fromHex(argv[++i], WhenError::Throw)); - } - catch (BadHexCharacter&) + } + if (vm.count("import-presale")) + presaleImports.push_back(vm["import-presale"].as()); + if (vm.count("admin")) + jsonAdmin = vm["admin"].as(); + if (vm.count("ipc")) + ipc = true; + if (vm.count("no-ipc")) + ipc = false; + if (vm.count("mining")) + { + string m = vm["mining"].as(); + if (isTrue(m)) + mining = ~(unsigned)0; + else if (isFalse(m)) + mining = 0; + else + try { - cerr << "Bad hex in " << arg << " option: " << argv[i] << "\n"; - return -1; + mining = stoi(m); } - catch (...) - { - cerr << "Bad " << arg << " option: " << argv[i] << "\n"; + catch (...) { + cerr << "Unknown --mining option: " << m << "\n"; return -1; } - else if ((arg == "-s" || arg == "--import-secret") && i + 1 < argc) + } + if (vm.count("bootstrap")) + bootstrap = true; + if (vm.count("no-bootstrap")) + bootstrap = false; + if (vm.count("no-discovery")) + { + disableDiscovery = true; + bootstrap = false; + } + if (vm.count("pin")) + pinning = true; + if (vm.count("hermit")) + pinning = disableDiscovery = true; + if (vm.count("sociable")) + noPinning = enableDiscovery = true; + if (vm.count("unsafe-transactions")) + alwaysConfirm = false; + if (vm.count("db-path")) + setDataDir(vm["db-path"].as()); + if (vm.count("ipcpath")) + setIpcPath(vm["ipcpath"].as()); + if (vm.count("genesis")) + { + try { - Secret s(fromHex(argv[++i])); - toImport.emplace_back(s); + genesisJSON = contentsString(vm["genesis"].as()); } - else if ((arg == "-S" || arg == "--import-session-secret") && i + 1 < argc) + catch (...) { - Secret s(fromHex(argv[++i])); - toImport.emplace_back(s); + cerr << "Bad --genesis option: " << vm["genesis"].as() << "\n"; + return -1; } - else if ((arg == "-d" || arg == "--path" || arg == "--db-path" || arg == "--datadir") && i + 1 < argc) - setDataDir(argv[++i]); - else if (arg == "--ipcpath" && i + 1 < argc ) - setIpcPath(argv[++i]); - else if ((arg == "--genesis-json" || arg == "--genesis") && i + 1 < argc) + } + if (vm.count("config")) + { + try { - try - { - genesisJSON = contentsString(argv[++i]); - } - catch (...) - { - cerr << "Bad " << arg << " option: " << argv[i] << "\n"; - return -1; - } + configJSON = contentsString(vm["config"].as()); } - else if (arg == "--config" && i + 1 < argc) + catch (...) { - try - { - configJSON = contentsString(argv[++i]); - } - catch (...) - { - cerr << "Bad " << arg << " option: " << argv[i] << "\n"; - return -1; - } + cerr << "Bad --config option: " << vm["config"].as() << "\n"; + return -1; } - else if (arg == "--extra-data" && i + 1 < argc) + } + if (vm.count("extra-data")) + { + try { - try - { - extraData = fromHex(argv[++i]); - } - catch (...) - { - cerr << "Bad " << arg << " option: " << argv[i] << "\n"; - return -1; - } + extraData = fromHex(vm["extra-data"].as()); } - else if (arg == "--gas-floor" && i + 1 < argc) - gasFloor = u256(argv[++i]); - else if (arg == "--mainnet") + catch (...) { - chainParams = ChainParams(genesisInfo(eth::Network::MainNetwork), genesisStateRoot(eth::Network::MainNetwork)); - chainConfigIsSet = true; + cerr << "Bad " << "--extra-data" << " option: " << vm["extra-data"].as() << "\n"; + return -1; } - else if (arg == "--ropsten" || arg == "--testnet") + } + if (vm.count("mainnet")) + { + chainParams = ChainParams(genesisInfo(eth::Network::MainNetwork), genesisStateRoot(eth::Network::MainNetwork)); + chainConfigIsSet = true; + } + if (vm.count("ropsten")) + { + chainParams = ChainParams(genesisInfo(eth::Network::Ropsten), genesisStateRoot(eth::Network::Ropsten)); + chainConfigIsSet = true; + } + if (vm.count("ask")) + { + try { - chainParams = ChainParams(genesisInfo(eth::Network::Ropsten), genesisStateRoot(eth::Network::Ropsten)); - chainConfigIsSet = true; + askPrice = vm["ask"].as(); } - else if (arg == "--ask" && i + 1 < argc) + catch (...) { - try - { - askPrice = u256(argv[++i]); - } - catch (...) - { - cerr << "Bad " << arg << " option: " << argv[i] << "\n"; - return -1; - } + cerr << "Bad --ask option: " << vm["ask"].as() << "\n"; + return -1; } - else if (arg == "--bid" && i + 1 < argc) + } + if (vm.count("bid")) + { + try { - try - { - bidPrice = u256(argv[++i]); - } - catch (...) - { - cerr << "Bad " << arg << " option: " << argv[i] << "\n"; - return -1; - } + bidPrice = vm["bid"].as(); } - else if ((arg == "-m" || arg == "--mining") && i + 1 < argc) + catch (...) { - string m = argv[++i]; - if (isTrue(m)) - mining = ~(unsigned)0; - else if (isFalse(m)) - mining = 0; - else - try { - mining = stoi(m); - } - catch (...) { - cerr << "Unknown " << arg << " option: " << m << "\n"; - return -1; - } + cerr << "Bad --bid option: " << vm["bid"].as() << "\n"; + return -1; } - else if (arg == "-b" || arg == "--bootstrap") - bootstrap = true; - else if (arg == "--no-bootstrap") - bootstrap = false; - else if (arg == "--no-discovery") + } + if (vm.count("listen-ip")) + { + listenIP = vm["listen-ip"].as(); + listenSet = true; + } + if (vm.count("listen")) { + listenPort = vm["listen"].as(); + listenSet = true; + } + if (vm.count("public-ip")) { + publicIP = vm["public-ip"].as(); + } + if (vm.count("remote")) + { + string host = vm["remote"].as(); + string::size_type found = host.find_first_of(':'); + if (found != std::string::npos) { - disableDiscovery = true; - bootstrap = false; + remoteHost = host.substr(0, found); + remotePort = (short)atoi(host.substr(found + 1, host.length()).c_str()); } - else if (arg == "--pin") - pinning = true; - else if (arg == "--hermit") - pinning = disableDiscovery = true; - else if (arg == "--sociable") - noPinning = enableDiscovery = true; - else if (arg == "--unsafe-transactions") - alwaysConfirm = false; - else if (arg == "--import-presale" && i + 1 < argc) - presaleImports.push_back(argv[++i]); - - else if (arg == "--json-admin" && i + 1 < argc) - jsonAdmin = argv[++i]; - else if (arg == "--ipc") - ipc = true; - else if (arg == "--no-ipc") - ipc = false; - - else if ((arg == "-v" || arg == "--verbosity") && i + 1 < argc) - g_logVerbosity = atoi(argv[++i]); - else if ((arg == "-x" || arg == "--peers") && i + 1 < argc) - peers = atoi(argv[++i]); - else if (arg == "--peer-stretch" && i + 1 < argc) - peerStretch = atoi(argv[++i]); - else if (arg == "--peerset" && i + 1 < argc) + else + remoteHost = host; + } + if (vm.count("port")) + { + remotePort = vm["port"].as(); + } + if (vm.count("import")) + { + mode = OperationMode::Import; + filename = vm["import"].as(); + } + if (vm.count("export")) + { + mode = OperationMode::Export; + filename = vm["export"].as(); + } + if (vm.count("password")) + passwordsToNote.push_back(vm["password"].as()); + if (vm.count("master")) + { + masterPassword = vm["master"].as(); + masterSet = true; + } + if (vm.count("dont-check")) + safeImport = true; + if (vm.count("format")) + { + string m = vm["format"].as(); + if (m == "binary") + exportFormat = Format::Binary; + else if (m == "hex") + exportFormat = Format::Hex; + else if (m == "human") + exportFormat = Format::Human; + else { - string peerset = argv[++i]; - if (peerset.empty()) - { - cerr << "--peerset argument must not be empty"; - return -1; - } - - vector each; - boost::split(each, peerset, boost::is_any_of("\t ")); - for (auto const& p: each) - { - string type; - string pubk; - string hostIP; - unsigned short port = c_defaultListenPort; - - // type:key@ip[:port] - vector typeAndKeyAtHostAndPort; - boost::split(typeAndKeyAtHostAndPort, p, boost::is_any_of(":")); - if (typeAndKeyAtHostAndPort.size() < 2 || typeAndKeyAtHostAndPort.size() > 3) - continue; - - type = typeAndKeyAtHostAndPort[0]; - if (typeAndKeyAtHostAndPort.size() == 3) - port = (uint16_t)atoi(typeAndKeyAtHostAndPort[2].c_str()); - - vector keyAndHost; - boost::split(keyAndHost, typeAndKeyAtHostAndPort[1], boost::is_any_of("@")); - if (keyAndHost.size() != 2) - continue; - pubk = keyAndHost[0]; - if (pubk.size() != 128) - continue; - hostIP = keyAndHost[1]; - - // todo: use Network::resolveHost() - if (hostIP.size() < 4 /* g.it */) - continue; - - bool required = type == "required"; - if (!required && type != "default") - continue; - - Public publicKey(fromHex(pubk)); - try - { - preferredNodes[publicKey] = make_pair(NodeIPEndpoint(bi::address::from_string(hostIP), port, port), required); - } - catch (...) - { - cerr << "Unrecognized peerset: " << peerset << "\n"; - return -1; - } - } + cerr << "Bad " << "--format" << " option: " << m << "\n"; + return -1; } - else if ((arg == "-o" || arg == "--mode") && i + 1 < argc) + } + if (vm.count("to")) + exportTo = vm["to"].as(); + if (vm.count("from")) + exportFrom = vm["from"].as(); + if (vm.count("only")) + exportTo = exportFrom = vm["only"].as(); + if (vm.count("upnp")) + { + string m = vm["upnp"].as(); + if (isTrue(m)) + upnp = true; + else if (isFalse(m)) + upnp = false; + else { - string m = argv[++i]; - if (m == "full") - nodeMode = NodeMode::Full; - else if (m == "peer") - nodeMode = NodeMode::PeerServer; - else - { - cerr << "Unknown mode: " << m << "\n"; - return -1; - } + cerr << "Bad " << "--upnp" << " option: " << m << "\n"; + return -1; } -#if ETH_EVMJIT - else if (arg == "--vm" && i + 1 < argc) + } + if (vm.count("network-id")) + try { - string vmKind = argv[++i]; - if (vmKind == "interpreter") - VMFactory::setKind(VMKind::Interpreter); - else if (vmKind == "jit") - VMFactory::setKind(VMKind::JIT); - else if (vmKind == "smart") - VMFactory::setKind(VMKind::Smart); - else - { - cerr << "Unknown VM kind: " << vmKind << "\n"; - return -1; - } + networkID = vm["network-id"].as(); } -#endif - else if (arg == "--shh") - useWhisper = true; - else if (arg == "-h" || arg == "--help") - help(); - else if (arg == "-V" || arg == "--version") - version(); - else if (arg == "--test") + catch (...) + { + cerr << "Bad " << "--network-id" << " option: " << vm["network-id"].as() << "\n"; + return -1; + } + if (vm.count("private")) + try { - testingMode = true; - enableDiscovery = false; - disableDiscovery = true; - noPinning = true; - bootstrap = false; + privateChain = vm["private"].as(); } - else if ((arg == std::string("--import-snapshot")) && i + 1 < argc) + catch (...) { - mode = OperationMode::ImportSnapshot; - filename = argv[++i]; + cerr << "Bad " << "--private" << " option: " << vm["private"].as() << "\n"; + return -1; } - else + if (vm.count("kill")) + withExisting = WithExisting::Kill; + if (vm.count("rebuild")) + withExisting = WithExisting::Verify; + if (vm.count("rescue")) + withExisting = WithExisting::Rescue; + if (vm.count("address")) + try { - cerr << "Invalid argument: " << arg << "\n"; - exit(-1); + author = vm["address"].as
(); } + catch (BadHexCharacter&) + { + cerr << "Bad hex in " << "--address" << " option: " << vm["address"].as() << "\n"; + return -1; + } + catch (...) + { + cerr << "Bad " << "--address" << " option: " << vm["address"].as() << "\n"; + return -1; + } + if ((vm.count("import-secret"))) + { + Secret s(fromHex(vm["import-secret"].as())); + toImport.emplace_back(s); } + if (vm.count("import-session-secret")) + { + Secret s(fromHex(vm["import-session-secret"].as())); + toImport.emplace_back(s); + } + if (vm.count("help")) + { + cout + << "Usage eth [OPTIONS]\n" + << "Options:\n\n" + << "Wallet usage:\n"; + AccountManager::streamAccountHelp(cout); + AccountManager::streamWalletHelp(cout); + cout << clientDefaultMode << clientTransacting << clientMining << clientNetworking; + MinerCLI::streamHelp(cout); + cout << importExportMode << generalOptions; + return 0; + } + if (!configJSON.empty()) { @@ -829,12 +805,9 @@ int main(int argc, char** argv) chainParams.difficulty = chainParams.minimumDifficulty; chainParams.gasLimit = u256(1) << 32; } - // TODO: Open some other API path -// if (gasFloor != Invalid256) -// c_gasFloorTarget = gasFloor; if (!chainConfigIsSet) - // default to mainnet if not already set with any of `--mainnet`, `--testnet`, `--genesis`, `--config` + // default to mainnet if not already set with any of `--mainnet`, `--ropsten`, `--genesis`, `--config` chainParams = ChainParams(genesisInfo(eth::Network::MainNetwork), genesisStateRoot(eth::Network::MainNetwork)); if (g_logVerbosity > 0) @@ -854,9 +827,6 @@ int main(int argc, char** argv) // the first value is deprecated (never used) writeFile(configFile, rlpList(author, author)); - if (!clientName.empty()) - clientName += "/"; - string logbuf; std::string additional; @@ -893,7 +863,7 @@ int main(int argc, char** argv) netPrefs.pin = (pinning || !privateChain.empty()) && !noPinning; auto nodesState = contents(getDataDir() / fs::path("network.rlp")); - auto caps = useWhisper ? set{"eth", "shh"} : set{"eth"}; + auto caps = set{"eth"}; if (testingMode) { @@ -920,13 +890,14 @@ int main(int argc, char** argv) return web3.ethereum()->number(); if (s.size() == 64 || (s.size() == 66 && s.substr(0, 2) == "0x")) return web3.ethereum()->blockChain().number(h256(s)); - try { + try + { return stol(s); } catch (...) { cerr << "Bad block number/hash option: " << s << "\n"; - exit(-1); + return -1; } }; @@ -1052,7 +1023,7 @@ int main(int argc, char** argv) auto stateImporter = web3.ethereum()->createStateImporter(); auto blockChainImporter = web3.ethereum()->createBlockChainImporter(); SnapshotImporter importer(*stateImporter, *blockChainImporter); - + auto snapshotStorage(createSnapshotStorage(filename)); importer.import(*snapshotStorage); // continue with regular sync from the snapshot block @@ -1156,7 +1127,6 @@ int main(int argc, char** argv) jsonrpcIpcServer->addConnector(ipcConnector); ipcConnector->StartListening(); - if (jsonAdmin.empty()) jsonAdmin = sessionManager->newSession(rpc::SessionPermissions{{rpc::Privilege::Admin}}); else @@ -1201,4 +1171,4 @@ int main(int argc, char** argv) if (!netData.empty()) writeFile(getDataDir() / fs::path("network.rlp"), netData); return 0; -} +} \ No newline at end of file diff --git a/ethkey/CMakeLists.txt b/ethkey/CMakeLists.txt index 43629dd2a7a..c65a7ed8fab 100644 --- a/ethkey/CMakeLists.txt +++ b/ethkey/CMakeLists.txt @@ -1,2 +1,2 @@ add_executable(ethkey KeyAux.h main.cpp) -target_link_libraries(ethkey PRIVATE ethcore devcore) +target_link_libraries(ethkey PRIVATE ethcore devcore Boost::program_options) diff --git a/ethkey/KeyAux.h b/ethkey/KeyAux.h index 0017f95f519..7d61d289cd8 100644 --- a/ethkey/KeyAux.h +++ b/ethkey/KeyAux.h @@ -107,8 +107,9 @@ class KeyCLI KeyCLI(OperationMode _mode = OperationMode::None): m_mode(_mode) {} - bool interpretOption(int& i, int argc, char** argv) + bool interpretOption(size_t& i, vector const& argv) { + size_t argc = argv.size(); string arg = argv[i]; if (arg == "--wallet-path" && i + 1 < argc) m_walletPath = argv[++i]; diff --git a/ethkey/main.cpp b/ethkey/main.cpp index e6353e30424..7c06fac2d41 100644 --- a/ethkey/main.cpp +++ b/ethkey/main.cpp @@ -5,7 +5,6 @@ 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 @@ -26,26 +25,15 @@ #include #include #include +#include +#include #include "BuildInfo.h" #include "KeyAux.h" using namespace std; using namespace dev; using namespace dev::eth; -void help() -{ - cout - << "Usage ethkey [OPTIONS]" << endl - << "Options:" << endl << endl; - KeyCLI::streamHelp(cout); - cout - << "General Options:" << endl - << " -v,--verbosity <0 - 9> Set the log verbosity from 0 to 9 (default: 8)." << endl - << " -V,--version Show the version and exit." << endl - << " -h,--help Show this help message and exit." << endl - ; - exit(0); -} +namespace po = boost::program_options; void version() { @@ -82,25 +70,49 @@ int main(int argc, char** argv) setDefaultOrCLocale(); KeyCLI m(KeyCLI::OperationMode::ListBare); g_logVerbosity = 0; + po::options_description generalOptions("General Options"); + generalOptions.add_options() + ("verbosity,v", po::value()->value_name("<0 - 9>"), "Set the log verbosity from 0 to 9 (default: 8).") + ("version,V", "Show the version and exit.") + ("help,h", "Show this help message and exit."); - for (int i = 1; i < argc; ++i) + po::variables_map vm; + vector unrecognisedOptions; + try + { + po::parsed_options parsed = po::command_line_parser(argc, argv).options(generalOptions).allow_unregistered().run(); + unrecognisedOptions = collect_unrecognized(parsed.options, po::include_positional); + po::store(parsed, vm); + po::notify(vm); + } + catch (po::error const& e) { - string arg = argv[i]; - if (m.interpretOption(i, argc, argv)) {} - else if ((arg == "-v" || arg == "--verbosity") && i + 1 < argc) - g_logVerbosity = atoi(argv[++i]); - else if (arg == "-h" || arg == "--help") - help(); - else if (arg == "-V" || arg == "--version") - version(); - else + cerr << e.what(); + return -1; + } + + for (size_t i = 0; i < unrecognisedOptions.size(); ++i) + if (!m.interpretOption(i, unrecognisedOptions)) { - cerr << "Invalid argument: " << arg << endl; - exit(-1); + cerr << "Invalid argument: " << unrecognisedOptions[i] << endl; + return -1; } + + if (vm.count("help")) + { + cout + << "Usage ethkey [OPTIONS]" << endl + << "Options:" << endl << endl; + KeyCLI::streamHelp(cout); + cout << generalOptions; + return 0; } + if (vm.count("version")) + version(); + if (vm.count("verbosity")) + g_logVerbosity = vm["verbosity"].as(); m.execute(); return 0; -} +} \ No newline at end of file