diff --git a/eth/main.cpp b/eth/main.cpp index 2514a2a1b..ba44b15a8 100644 --- a/eth/main.cpp +++ b/eth/main.cpp @@ -91,10 +91,8 @@ void interactiveHelp() << " minestart Starts mining." << endl << " minestop Stops mining." << endl << " mineforce Forces mining, even when there are no transactions." << 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 + << " accounts Gives information on all owned accounts (balances, mining beneficiary and default signer)." << endl << " transact Execute a given transaction." << endl << " send Execute a given transaction with current secret." << endl << " contract Create a new contract with current secret." << endl @@ -103,7 +101,7 @@ void interactiveHelp() << " listaccounts List the accounts on the network." << endl << " listcontracts List the contracts on the network." << endl #endif - << " setsecret Set the secret to the hex secret key." << endl + << " setsigningkey Set the address with which to sign transactions." << endl << " setaddress Set the coinbase (mining payout) address." << endl << " exportconfig Export the config (.RLP) to the path provided." << endl << " importconfig Import the config (.RLP) from the path provided." << endl @@ -127,13 +125,18 @@ void help() #endif << " -K,--kill First kill the blockchain." << endl << " -R,--rebuild Rebuild the blockchain from the existing database." << endl - << " -s,--secret Set the secret key for use with send command (default: auto)." << endl - << " -S,--session-secret Set the secret key for use with send command, for this session only." << endl + << " -s,--import-secret Import a secret key into the key store and use as the default." << endl + << " -S,--import-session-secret Import a secret key into the key store and use as the default for this session only." << endl + << " --sign-key
Sign all transactions with the key of the given address." << endl + << " --session-sign-key
Sign all transactions with the key of the given address for this session only." << endl << " --master Give the master password for the key store." << endl + << " --password Give a password for a private key." << endl + << endl << "Client transacting:" << endl << " -B,--block-fees Set the block fee profit in the reference unit e.g. ¢ (default: 15)." << endl << " -e,--ether-price Set the ether price in the reference unit e.g. ¢ (default: 30.679)." << endl << " -P,--priority <0 - 100> Default % priority of a transaction (default: 50)." << endl + << endl << "Client mining:" << endl << " -a,--address Set the coinbase (mining payout) address to addr (default: auto)." << endl << " -m,--mining Enable mining, optionally for a specified number of blocks (default: off)" << endl @@ -143,6 +146,7 @@ void help() << " --opencl-platform When mining using -G/--opencl use OpenCL platform n (default: 0)." << endl << " --opencl-device When mining using -G/--opencl use OpenCL device n (default: 0)." << endl << " -t, --mining-threads Limit number of CPU/GPU miners to n (default: use everything available on selected platform)" << endl + << endl << "Client networking:" << endl << " --client-name Add a name to your client's version string (default: blank)." << endl << " -b,--bootstrap Connect to the default Ethereum peerserver." << endl @@ -155,12 +159,15 @@ void help() << " --network-id Only connect to other hosts with this network id (default:0)." << endl << " --upnp Use UPnP for NAT (default: on)." << endl #if ETH_JSONRPC || !ETH_TRUE + << endl << "Work farming mode:" << endl << " -F,--farm Put into mining farm mode with the work server at URL. Use with -G/--opencl." << endl << " --farm-recheck Leave n ms between checks for changed work (default: 500)." << endl #endif + << endl << "Ethash verify mode:" << endl << " -w,--check-pow Check PoW credentials for validity." << endl + << endl << "Benchmarking mode:" << endl << " -M,--benchmark Benchmark for mining and exit; use with --cpu and --opencl." << endl << " --benchmark-warmup Set the duration of warmup for the benchmark tests (default: 3)." << endl @@ -169,14 +176,17 @@ void help() #if ETH_JSONRPC || !ETH_TRUE << " --phone-home When benchmarking, publish results (default: on)" << endl #endif + << endl << "DAG creation mode:" << endl << " -D,--create-dag Create the DAG in preparation for mining on given block and exit." << endl + << endl << "Import/export modes:" << endl << " -I,--import Import file as a concatenated series of blocks and exit." << endl << " -E,--export Export file as a concatenated series of blocks and exit." << endl << " --from Export only from block n; n may be a decimal, a '0x' prefixed hash, or 'latest'." << endl << " --to Export only to block n (inclusive); n may be a decimal, a '0x' prefixed hash, or 'latest'." << endl << " --only Equivalent to --export-from n --export-to n." << endl + << endl << "General Options:" << endl << " -d,--db-path Load database from path (default: " << getDataDir() << ")" << endl #if ETH_EVMJIT || !ETH_TRUE @@ -539,13 +549,14 @@ int main(int argc, char** argv) /// Mining params unsigned mining = 0; bool forceMining = false; - KeyPair sigKey = KeyPair::create(); - Secret sessionSecret; - Address coinbase = sigKey.address(); + Address signingKey; + Address sessionKey; + Address beneficiary = signingKey; /// Structured logging params bool structuredLogging = false; string structuredLoggingFormat = "%Y-%m-%dT%H:%M:%S"; + string structuredLoggingURL; /// Transaction params TransactionPriority priority = TransactionPriority::Medium; @@ -570,11 +581,20 @@ int main(int argc, char** argv) string configFile = getDataDir() + "/config.rlp"; bytes b = contents(configFile); + + strings passwordsToNote; + Secrets toImport; if (b.size()) { RLP config(b); - sigKey = KeyPair(config[0].toHash()); - coinbase = config[1].toHash
(); + if (config[0].size() == 32) // secret key - import and forget. + { + Secret s = config[0].toHash(); + toImport.push_back(s); + } + else // new format - just use it as an address. + signingKey = config[0].toHash
(); + beneficiary = config[1].toHash
(); } for (int i = 1; i < argc; ++i) @@ -602,6 +622,8 @@ int main(int argc, char** argv) cerr << "-p is DEPRECATED. It will be removed for the Frontier. Use --port instead (or place directly as host:port)." << endl; 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]; else if ((arg == "-I" || arg == "--import") && i + 1 < argc) @@ -744,7 +766,7 @@ int main(int argc, char** argv) } else if ((arg == "-a" || arg == "--address" || arg == "--coinbase-address") && i + 1 < argc) try { - coinbase = h160(fromHex(argv[++i], WhenError::Throw)); + beneficiary = h160(fromHex(argv[++i], WhenError::Throw)); } catch (BadHexCharacter&) { @@ -760,14 +782,32 @@ int main(int argc, char** argv) minerType = MinerType::CPU; else if (arg == "-G" || arg == "--opencl") minerType = MinerType::GPU; - else if ((arg == "-s" || arg == "--secret") && i + 1 < argc) - sigKey = KeyPair(h256(fromHex(argv[++i]))); - else if ((arg == "-S" || arg == "--session-secret") && i + 1 < argc) - sessionSecret = h256(fromHex(argv[++i])); + /*<< " -s,--import-secret Import a secret key into the key store and use as the default." << endl + << " -S,--import-session-secret Import a secret key into the key store and use as the default for this session only." << endl + << " --sign-key
Sign all transactions with the key of the given address." << endl + << " --session-sign-key
Sign all transactions with the key of the given address for this session only." << endl*/ + else if ((arg == "-s" || arg == "--import-secret") && i + 1 < argc) + { + Secret s(fromHex(argv[++i])); + toImport.push_back(s); + signingKey = toAddress(s); + } + else if ((arg == "-S" || arg == "--import-session-secret") && i + 1 < argc) + { + Secret s(fromHex(argv[++i])); + toImport.push_back(s); + sessionKey = toAddress(s); + } + else if ((arg == "--sign-key") && i + 1 < argc) + sessionKey = Address(fromHex(argv[++i])); + else if ((arg == "--session-sign-key") && i + 1 < argc) + sessionKey = Address(fromHex(argv[++i])); else if (arg == "--structured-logging-format" && i + 1 < argc) structuredLoggingFormat = string(argv[++i]); else if (arg == "--structured-logging") structuredLogging = true; + else if (arg == "--structured-logging-destination" && i + 1 < argc) + structuredLoggingURL = argv[++i]; else if ((arg == "-d" || arg == "--path" || arg == "--db-path") && i + 1 < argc) dbPath = argv[++i]; else if ((arg == "-D" || arg == "--create-dag") && i + 1 < argc) @@ -951,16 +991,24 @@ int main(int argc, char** argv) } } + KeyManager keyManager; + for (auto const& s: passwordsToNote) + keyManager.notePassword(s); + for (auto const& s: toImport) + { + keyManager.import(s, "Imported key"); + if (!signingKey) + signingKey = toAddress(s); + } + { RLPStream config(2); - config << sigKey.secret() << coinbase; + config << signingKey << beneficiary; writeFile(configFile, config.out()); } - if (sessionSecret) - sigKey = KeyPair(sessionSecret); - - + if (sessionKey) + signingKey = sessionKey; if (minerType == MinerType::CPU) ProofOfWork::CPUMiner::setNumInstances(miningThreads); @@ -985,7 +1033,7 @@ int main(int argc, char** argv) if (!clientName.empty()) clientName += "/"; - StructuredLogger::get().initialize(structuredLogging, structuredLoggingFormat); + StructuredLogger::get().initialize(structuredLogging, structuredLoggingFormat, structuredLoggingURL); VMFactory::setKind(jit ? VMKind::JIT : VMKind::Interpreter); auto netPrefs = publicIP.empty() ? NetworkPreferences(listenIP ,listenPort, upnp) : NetworkPreferences(publicIP, listenIP ,listenPort, upnp); auto nodesState = contents((dbPath.size() ? dbPath : getDataDir()) + "/network.rlp"); @@ -1074,12 +1122,12 @@ int main(int argc, char** argv) c->setGasPricer(gasPricer); c->setForceMining(forceMining); c->setTurboMining(minerType == MinerType::GPU); - c->setAddress(coinbase); + c->setAddress(beneficiary); c->setNetworkId(networkId); } - cout << "Transaction Signer: " << sigKey.address() << endl; - cout << "Mining Benefactor: " << coinbase << endl; + cout << "Transaction Signer: " << signingKey << endl; + cout << "Mining Benefactor: " << beneficiary << endl; web3.startNetwork(); cout << "Node ID: " << web3.enode() << endl; @@ -1088,9 +1136,7 @@ int main(int argc, char** argv) if (remoteHost.size()) web3.addNode(p2p::NodeId(), remoteHost + ":" + toString(remotePort)); - KeyManager keyManager; if (keyManager.exists()) - { while (masterPassword.empty()) { masterPassword = getPassword("Please enter your MASTER password: "); @@ -1100,14 +1146,12 @@ int main(int argc, char** argv) masterPassword.clear(); } } - } else { while (masterPassword.empty()) { masterPassword = getPassword("Please enter a MASTER password to protect your key store (make it strong!): "); string confirm = getPassword("Please confirm the password by entering it again: "); - getline(cin, confirm); if (masterPassword != confirm) { cout << "Passwords were different. Try again." << endl; @@ -1123,7 +1167,14 @@ int main(int argc, char** argv) g_logPost = [&](std::string const& a, char const*) { if (silence) logbuf += a + "\n"; else cout << "\r \r" << a << endl << additional << flush; }; // TODO: give hints &c. - auto getPassword = [&](Address const& a){ auto s = silence; silence = true; cout << endl; string ret = dev::getPassword("Enter password for address " + a.abridged() + ": "); silence = s; return ret; }; + auto getPassword = [&](Address const& a){ + auto s = silence; + silence = true; + cout << endl; + string ret = dev::getPassword("Enter password for address " + keyManager.accountDetails()[a].first + " (" + a.abridged() + "; hint:" + keyManager.accountDetails()[a].second + "): "); + silence = s; + return ret; + }; #if ETH_JSONRPC || !ETH_TRUE shared_ptr jsonrpcServer; @@ -1131,7 +1182,7 @@ int main(int argc, char** argv) if (jsonrpc > -1) { jsonrpcConnector = unique_ptr(new jsonrpc::HttpServer(jsonrpc, "", "", SensibleHttpThreads)); - jsonrpcServer = shared_ptr(new WebThreeStubServer(*jsonrpcConnector.get(), web3, make_shared([&](){return web3.ethereum();}, getPassword, keyManager), vector({sigKey}))); + jsonrpcServer = shared_ptr(new WebThreeStubServer(*jsonrpcConnector.get(), web3, make_shared([&](){return web3.ethereum();}, getPassword, keyManager), vector())); jsonrpcServer->StartListening(); } #endif @@ -1275,7 +1326,7 @@ int main(int argc, char** argv) if (jsonrpc < 0) jsonrpc = SensibleHttpPort; jsonrpcConnector = unique_ptr(new jsonrpc::HttpServer(jsonrpc, "", "", SensibleHttpThreads)); - jsonrpcServer = shared_ptr(new WebThreeStubServer(*jsonrpcConnector.get(), web3, make_shared([&](){return web3.ethereum();}, getPassword, keyManager), vector({sigKey}))); + jsonrpcServer = shared_ptr(new WebThreeStubServer(*jsonrpcConnector.get(), web3, make_shared([&](){return web3.ethereum();}, getPassword, keyManager), vector())); jsonrpcServer->StartListening(); } else if (cmd == "jsonstop") @@ -1287,11 +1338,8 @@ int main(int argc, char** argv) #endif else if (cmd == "address") { - cout << "Current address:" << endl << sigKey.address() << endl; - } - else if (cmd == "secret") - { - cout << "Secret Key: " << sigKey.secret() << endl; + cout << "Current mining beneficiary:" << endl << beneficiary << endl; + cout << "Current signing account:" << endl << signingKey << endl; } else if (c && cmd == "block") { @@ -1306,7 +1354,15 @@ int main(int argc, char** argv) } else if (c && cmd == "balance") { - cout << "Current balance: " << formatBalance( c->balanceAt(sigKey.address())) << " = " <balanceAt(sigKey.address()) << " wei" << endl; + cout << "Current balance:" << endl; + u256 total = 0; + for (auto const& i: keyManager.accountDetails()) + { + auto b = c->balanceAt(i.first); + cout << ((i.first == signingKey) ? "SIGNING " : " ") << ((i.first == beneficiary) ? "COINBASE " : " ") << i.second.first << " (" << i.first << "): " << formatBalance(b) << " = " << b << " wei" << endl; + total += b; + } + cout << "Total: " << formatBalance(total) << " = " << total << " wei" << endl; } else if (c && cmd == "transact") { @@ -1422,7 +1478,7 @@ int main(int argc, char** argv) try { Address dest = h160(fromHex(hexAddr, WhenError::Throw)); - c->submitTransaction(sigKey.secret(), amount, dest, bytes(), minGas); + c->submitTransaction(keyManager.secret(signingKey, [&](){ return getPassword(signingKey); }), amount, dest, bytes(), minGas); } catch (BadHexCharacter& _e) { @@ -1491,7 +1547,7 @@ int main(int argc, char** argv) else if (gas < minGas) cwarn << "Minimum gas amount is" << minGas; else - c->submitTransaction(sigKey.secret(), endowment, init, gas, gasPrice); + c->submitTransaction(keyManager.secret(signingKey, [&](){ return getPassword(signingKey); }), endowment, init, gas, gasPrice); } else cwarn << "Require parameters: contract ENDOWMENT GASPRICE GAS CODEHEX"; @@ -1602,13 +1658,13 @@ int main(int argc, char** argv) } } } - else if (cmd == "setsecret") + else if (cmd == "setsigningkey") { if (iss.peek() != -1) { string hexSec; iss >> hexSec; - sigKey = KeyPair(h256(fromHex(hexSec))); + signingKey = Address(fromHex(hexSec)); } else cwarn << "Require parameter: setSecret HEXSECRETKEY"; @@ -1625,7 +1681,7 @@ int main(int argc, char** argv) { try { - coinbase = h160(fromHex(hexAddr, WhenError::Throw)); + beneficiary = h160(fromHex(hexAddr, WhenError::Throw)); } catch (BadHexCharacter& _e) { @@ -1648,7 +1704,7 @@ int main(int argc, char** argv) string path; iss >> path; RLPStream config(2); - config << sigKey.secret() << coinbase; + config << signingKey << beneficiary; writeFile(path, config.out()); } else @@ -1664,8 +1720,8 @@ int main(int argc, char** argv) if (b.size()) { RLP config(b); - sigKey = KeyPair(config[0].toHash()); - coinbase = config[1].toHash
(); + signingKey = config[0].toHash
(); + beneficiary = config[1].toHash
(); } else cwarn << path << "has no content!"; diff --git a/libdevcore/StructuredLogger.cpp b/libdevcore/StructuredLogger.cpp index f51ed310a..7d8a17722 100644 --- a/libdevcore/StructuredLogger.cpp +++ b/libdevcore/StructuredLogger.cpp @@ -34,6 +34,15 @@ using namespace std; namespace dev { +void StructuredLogger::initialize(bool _enabled, std::string const& _timeFormat, std::string const& _destinationURL) +{ + m_enabled = _enabled; + m_timeFormat = _timeFormat; + if (_destinationURL.size() > 7 && _destinationURL.substr(0, 7) == "file://") + m_out.open(_destinationURL.substr(7)); + // TODO: support tcp:// +} + void StructuredLogger::outputJson(Json::Value const& _value, std::string const& _name) const { Json::Value event; @@ -41,7 +50,7 @@ void StructuredLogger::outputJson(Json::Value const& _value, std::string const& Json::FastWriter fastWriter; Guard l(s_lock); event[_name] = _value; - cout << fastWriter.write(event) << endl; + (m_out.is_open() ? m_out : cout) << fastWriter.write(event) << endl; } void StructuredLogger::starting(string const& _clientImpl, const char* _ethVersion) @@ -51,6 +60,7 @@ void StructuredLogger::starting(string const& _clientImpl, const char* _ethVersi Json::Value event; event["client_impl"] = _clientImpl; event["eth_version"] = std::string(_ethVersion); + // TODO net_version event["ts"] = dev::toString(chrono::system_clock::now(), get().m_timeFormat.c_str()); get().outputJson(event, "starting"); @@ -64,6 +74,7 @@ void StructuredLogger::stopping(string const& _clientImpl, const char* _ethVersi Json::Value event; event["client_impl"] = _clientImpl; event["eth_version"] = std::string(_ethVersion); + // TODO net_version event["ts"] = dev::toString(chrono::system_clock::now(), get().m_timeFormat.c_str()); get().outputJson(event, "stopping"); diff --git a/libdevcore/StructuredLogger.h b/libdevcore/StructuredLogger.h index 2c30541e4..107598706 100644 --- a/libdevcore/StructuredLogger.h +++ b/libdevcore/StructuredLogger.h @@ -25,6 +25,7 @@ #pragma once +#include #include #include @@ -46,11 +47,7 @@ public: * http://en.cppreference.com/w/cpp/chrono/c/strftime * with which to display timestamps */ - void initialize(bool _enabled, std::string const& _timeFormat) - { - m_enabled = _enabled; - m_timeFormat = _timeFormat; - } + void initialize(bool _enabled, std::string const& _timeFormat, std::string const& _destinationURL); static StructuredLogger& get() { @@ -92,6 +89,11 @@ public: std::string const& _prevHash ); static void transactionReceived(std::string const& _hash, std::string const& _remoteId); + // TODO: static void pendingQueueChanged(std::vector const& _hashes); + // TODO: static void miningStarted(); + // TODO: static void stillMining(unsigned _hashrate); + // TODO: static void miningStopped(); + private: // Singleton class. Private default ctor and no copying StructuredLogger() = default; @@ -102,6 +104,8 @@ private: bool m_enabled = false; std::string m_timeFormat = "%Y-%m-%dT%H:%M:%S"; + + mutable std::ofstream m_out; }; } diff --git a/libethereum/BlockChain.cpp b/libethereum/BlockChain.cpp index 607fc586c..f13f83588 100644 --- a/libethereum/BlockChain.cpp +++ b/libethereum/BlockChain.cpp @@ -456,7 +456,7 @@ ImportRoute BlockChain::import(bytes const& _block, OverlayDB const& _db, Import { // Check transactions are valid and that they result in a state equivalent to our state_root. // Get total difficulty increase and update state, checking it. - State s(_db); //, bi.coinbaseAddress + State s(_db); auto tdIncrease = s.enactOn(&_block, bi, *this, _ir); BlockLogBlooms blb; @@ -467,6 +467,7 @@ ImportRoute BlockChain::import(bytes const& _block, OverlayDB const& _db, Import br.receipts.push_back(s.receipt(i)); } s.cleanup(true); + td = pd.totalDifficulty + tdIncrease; #if ETH_TIMED_IMPORTS @@ -603,7 +604,6 @@ ImportRoute BlockChain::import(bytes const& _block, OverlayDB const& _db, Import { newLastBlockHash = bi.hash(); newLastBlockNumber = (unsigned)bi.number; - extrasBatch.Put(ldb::Slice("best"), ldb::Slice((char const*)&(bi.hash()), 32)); } clog(BlockChainNote) << " Imported and best" << td << " (#" << bi.number << "). Has" << (details(bi.parentHash).children.size() - 1) << "siblings. Route:" << route; @@ -623,12 +623,33 @@ ImportRoute BlockChain::import(bytes const& _block, OverlayDB const& _db, Import m_blocksDB->Write(m_writeOptions, &blocksBatch); m_extrasDB->Write(m_writeOptions, &extrasBatch); - DEV_WRITE_GUARDED(x_lastBlockHash) + if (isKnown(bi.hash()) && !details(bi.hash())) { - m_lastBlockHash = newLastBlockHash; - m_lastBlockNumber = newLastBlockNumber; + clog(BlockChainDebug) << "Known block just inserted has no details."; + clog(BlockChainDebug) << "Block:" << bi; + clog(BlockChainDebug) << "DATABASE CORRUPTION: CRITICAL FAILURE"; + exit(-1); } + try { + State canary(_db, *this, bi.hash()); + } + catch (...) + { + clog(BlockChainDebug) << "Failed to initialise State object form imported block."; + clog(BlockChainDebug) << "Block:" << bi; + clog(BlockChainDebug) << "DATABASE CORRUPTION: CRITICAL FAILURE"; + exit(-1); + } + + if (m_lastBlockHash != newLastBlockHash) + DEV_WRITE_GUARDED(x_lastBlockHash) + { + m_lastBlockHash = newLastBlockHash; + m_lastBlockNumber = newLastBlockNumber; + m_extrasDB->Put(m_writeOptions, ldb::Slice("best"), ldb::Slice((char const*)&m_lastBlockHash, 32)); + } + #if ETH_PARANOIA || !ETH_TRUE checkConsistency(); #endif @@ -646,14 +667,6 @@ ImportRoute BlockChain::import(bytes const& _block, OverlayDB const& _db, Import if (!route.empty()) noteCanonChanged(); - if (isKnown(bi.hash()) && !details(bi.hash())) - { - clog(BlockChainDebug) << "Known block just inserted has no details."; - clog(BlockChainDebug) << "Block:" << bi; - clog(BlockChainDebug) << "DATABASE CORRUPTION: CRITICAL FAILURE"; - exit(-1); - } - h256s fresh; h256s dead; bool isOld = true; diff --git a/libethereum/KeyManager.h b/libethereum/KeyManager.h index 391121b1c..38e8d8853 100644 --- a/libethereum/KeyManager.h +++ b/libethereum/KeyManager.h @@ -66,6 +66,8 @@ public: bool load(std::string const& _pass); void save(std::string const& _pass) const { write(_pass, m_keysFile); } + void notePassword(std::string const& _pass) { m_cachedPasswords[hashPassword(_pass)] = _pass; } + AddressHash accounts() const; std::unordered_map> accountDetails() const; diff --git a/libethereum/State.cpp b/libethereum/State.cpp index 2e4fe5749..50e4b26ab 100644 --- a/libethereum/State.cpp +++ b/libethereum/State.cpp @@ -707,6 +707,7 @@ void State::cleanup(bool _fullCommit) { if (_fullCommit) { + paranoia("immediately before database commit", true); // Commit the new trie to disk. diff --git a/libethereum/State.h b/libethereum/State.h index 74d75685c..a11812c6f 100644 --- a/libethereum/State.h +++ b/libethereum/State.h @@ -49,7 +49,7 @@ class BlockChain; class State; struct StateChat: public LogChannel { static const char* name(); static const int verbosity = 4; }; -struct StateTrace: public LogChannel { static const char* name(); static const int verbosity = 7; }; +struct StateTrace: public LogChannel { static const char* name(); static const int verbosity = 5; }; struct StateDetail: public LogChannel { static const char* name(); static const int verbosity = 14; }; struct StateSafeExceptions: public LogChannel { static const char* name(); static const int verbosity = 21; };