diff --git a/CMakeLists.txt b/CMakeLists.txt index ae0274f6a..56ef68e32 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,15 +14,21 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake") # user defined, defaults # Normally, set(...CACHE...) creates cache variables, but does not modify them. function(createDefaultCacheConfig) - set(HEADLESS OFF CACHE BOOL "Do not compile GUI (AlethZero)") set(VMTRACE OFF CACHE BOOL "VM tracing and run-time checks (useful for cross-implementation VM debugging)") set(PARANOIA OFF CACHE BOOL "Additional run-time checks") set(JSONRPC ON CACHE BOOL "Build with jsonprc. default on") - set(EVMJIT OFF CACHE BOOL "Build a just-in-time compiler for EVM code (requires LLVM)") set(FATDB OFF CACHE BOOL "Build with ability to list entries in the Trie. Doubles DB size, slows everything down, but good for looking at state diffs and trie contents.") - set(JUSTTESTS OFF CACHE BOOL "Build only for tests.") - set(SOLIDITY ON CACHE BOOL "Build the Solidity language components (requried unless HEADLESS)") set(USENPM OFF CACHE BOOL "Use npm to recompile ethereum.js if it was changed") + set(PROFILING OFF CACHE BOOL "Build in support for profiling") + + set(BUNDLE "none" CACHE STRING "Predefined bundle of software to build (none, full, user, tests, minimal).") + set(SOLIDITY ON CACHE BOOL "Build the Solidity language components") + set(SERPENT ON CACHE BOOL "Build the Serpent language components") + set(TOOLS ON CACHE BOOL "Build the tools components") + set(NCURSES ON CACHE BOOL "Build the NCurses components") + set(GUI ON CACHE BOOL "Build GUI components (AlethZero, Mix)") + set(TESTS ON CACHE BOOL "Build the tests.") + set(EVMJIT OFF CACHE BOOL "Build just-in-time compiler for EVM code (requires LLVM)") set(ETHASHCL OFF CACHE BOOL "Build in support for GPU mining via OpenCL") endfunction() @@ -30,19 +36,11 @@ endfunction() # propagates CMake configuration options to the compiler function(configureProject) if (PARANOIA) - if ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") - add_definitions(-DETH_PARANOIA) - else () - message(FATAL_ERROR "Paranoia requires debug.") - endif () + add_definitions(-DETH_PARANOIA) endif () if (VMTRACE) - if ("${CMAKE_BUILD_TYPE}" STREQUAL "Debug") - add_definitions(-DETH_VMTRACE) - else () - message(FATAL_ERROR "VM tracing requires debug.") - endif () + add_definitions(-DETH_VMTRACE) endif () if (ETHASHCL) @@ -61,8 +59,8 @@ function(configureProject) add_definitions(-DETH_SOLIDITY) endif() - if (HEADLESS OR JUSTTESTS) - add_definitions(-DETH_HEADLESS) + if (GUI) + add_definitions(-DETH_GUI) endif() endfunction() @@ -123,6 +121,17 @@ endfunction() set(CMAKE_AUTOMOC ON) cmake_policy(SET CMP0015 NEW) +# Clear invalid option +if ("${CMAKE_BUILD_TYPE}" STREQUAL "Release") + if (PARANOIA) + message("Paranoia requires debug - disabling for release build.") + set(PARANOIA OFF) + endif () + if (VMTRACE) + message("VM Tracing requires debug - disabling for release build.") + set (VMTRACE OFF) + endif () +endif () createDefaultCacheConfig() configureProject() @@ -130,16 +139,162 @@ configureProject() # Force chromium. set (ETH_HAVE_WEBENGINE 1) -message(STATUS "CMAKE_VERSION: ${CMAKE_VERSION}") -message("-- VMTRACE: ${VMTRACE}; PARANOIA: ${PARANOIA}; HEADLESS: ${HEADLESS}; JSONRPC: ${JSONRPC}; EVMJIT: ${EVMJIT}; FATDB: ${FATDB}; CHROMIUM: ${ETH_HAVE_WEBENGINE}; USENPM: ${USENPM}; ETHASHCL: ${ETHASHCL}") +# Normalise build options +# TODO: Abstract into something sensible and move into a function. +if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") + set(DECENT_PLATFORM OFF) +else () + set(DECENT_PLATFORM ON) +endif () + +if (PARANOIA) + set(PARANOIA ON) +else () + set(PARANOIA OFF) +endif () +if (VMTRACE) + set(VMTRACE ON) +else () + set(VMTRACE OFF) +endif () +if (EVMJIT) + set(EVMJIT ON) +else () + set(EVMJIT OFF) +endif() +if (FATDB) + set(FATDB ON) +else () + set(FATDB OFF) +endif() +if (JSONRPC) + set(JSONRPC ON) +else () + set(JSONRPC OFF) +endif () +if (USENPM) + set(USENPM ON) +else () + set(USENPM OFF) +endif () +if (PROFILING) + set(PROFILING ON) +else () + set(PROFILING OFF) +endif () +if (SOLIDITY) + set(SOLIDITY ON) +else () + set(SOLIDITY OFF) +endif() +if (SERPENT) + set(SERPENT ON) +else () + set(SERPENT OFF) +endif() +if (GUI) + set(GUI ON) + set(JSONRPC ON) +else () + set(GUI OFF) +endif () +if (TESTS) + set(TESTS ON) +else () + set(TESTS OFF) +endif () +if (TOOLS) + set(TOOLS ON) +else () + set(TOOLS OFF) +endif () +if (ETHASHCL) + set(ETHASHCL ON) +else () + set(ETHASHCL OFF) +endif() +if (NCURSES) + set(NCURSES ON) +else () + set(NCURSES OFF) +endif () + +if (BUNDLE STREQUAL "minimal") + set(SERPENT OFF) + set(SOLIDITY OFF) + set(USENPM OFF) + set(GUI OFF) + set(NCURSES OFF) + set(TOOLS ON) + set(TESTS OFF) +elseif (BUNDLE STREQUAL "full") + set(SERPENT ON) + set(SOLIDITY ON) + set(USENPM ON) + set(GUI ON) + set(NCURSES ${DECENT_PLATFORM}) + set(TOOLS ON) + set(TESTS ON) + set(FATDB ON) +elseif (BUNDLE STREQUAL "tests") + set(SERPENT ON) + set(SOLIDITY ON) + set(USENPM OFF) + set(GUI OFF) + set(NCURSES OFF) + set(TOOLS OFF) + set(TESTS ON) + set(FATDB ON) +elseif (BUNDLE STREQUAL "user") + set(SERPENT OFF) + set(SOLIDITY OFF) + set(USENPM OFF) + set(GUI ON) + set(NCURSES ${DECENT_PLATFORM}) + set(TOOLS ON) + set(TESTS OFF) +endif () + +# Default CMAKE_BUILD_TYPE to "Release". +set(CMAKE_BUILD_TYPE CACHE STRING "Release") +if ("x${CMAKE_BUILD_TYPE}" STREQUAL "x") + set(CMAKE_BUILD_TYPE "Release") +endif () + # Default TARGET_PLATFORM to "linux". set(TARGET_PLATFORM CACHE STRING "linux") if ("x${TARGET_PLATFORM}" STREQUAL "x") set(TARGET_PLATFORM "linux") endif () +message("------------------------------------------------------------------------") +message("-- CMake Version ${CMAKE_VERSION}") +message("-- CMAKE_BUILD_TYPE Build type ${CMAKE_BUILD_TYPE}") +message("-- TARGET_PLATFORM Target platform ${TARGET_PLATFORM}") +message("-- BUNDLE Build bundle ${BUNDLE}") +message("--------------------------------------------------------------- features") +message("-- Chromium support ${ETH_HAVE_WEBENGINE}") +message("-- VMTRACE VM execution tracing ${VMTRACE}") +message("-- PROFILING Profiling support ${PROFILING}") +message("-- PARANOIA Additional (SLOW) database checking ${PARANOIA}") +message("-- FATDB Full database exploring ${FATDB}") +message("-- JSONRPC JSON-RPC support ${JSONRPC}") +message("-- USENPM Javascript source building ${USENPM}") +message("------------------------------------------------------------- components") +message("-- TOOLS Build basic tools ${TOOLS}") +message("-- SOLIDITY Build Solidity language components ${SOLIDITY}") +message("-- SERPENT Build Serpent language components ${SERPENT}") +message("-- GUI Build GUI components ${GUI}") +message("-- NCURSES Build NCurses components ${NCURSES}") +message("-- TESTS Build tests ${TESTS}") +message("-- ETHASHCL Build OpenCL components (experimental!) ${ETHASHCL}") +message("-- EVMJIT Build LLVM-based JIT EVM (experimental!) ${EVMJIT}") +message("------------------------------------------------------------------------") +message("") + + if ("${TARGET_PLATFORM}" STREQUAL "linux") set(CMAKE_THREAD_LIBS_INIT pthread) endif () @@ -163,21 +318,24 @@ add_subdirectory(libdevcore) add_subdirectory(libevmcore) add_subdirectory(liblll) -if (NOT ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")) +if (SERPENT) add_subdirectory(libserpent) add_subdirectory(sc) endif () -add_subdirectory(libsolidity) +if (SOLIDITY) + add_subdirectory(libsolidity) +endif () -if (NOT JUSTTESTS) +if (TOOLS) add_subdirectory(lllc) - add_subdirectory(solc) + if (SOLIDITY) + add_subdirectory(solc) + endif () endif() if (JSONRPC) add_subdirectory(libweb3jsonrpc) - add_subdirectory(ethrpctest) endif() add_subdirectory(secp256k1) @@ -193,12 +351,17 @@ endif () add_subdirectory(libethcore) add_subdirectory(libevm) add_subdirectory(libethereum) - add_subdirectory(libwebthree) -add_subdirectory(libtestutils) -add_subdirectory(test) -if (NOT JUSTTESTS) +if (TESTS) + add_subdirectory(libtestutils) + add_subdirectory(test) + if (JSONRPC) + add_subdirectory(ethrpctest) + endif () +endif () + +if (TOOLS) add_subdirectory(rlp) add_subdirectory(abi) @@ -208,25 +371,26 @@ if (NOT JUSTTESTS) add_subdirectory(exp) endif () - # TODO check msvc - if(NOT ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")) - add_subdirectory(neth) - endif () +endif() - if (NOT HEADLESS) +if (NCURSES) + add_subdirectory(neth) +endif () - add_subdirectory(libnatspec) - add_subdirectory(libjsqrc) +if (GUI) - if (ETH_HAVE_WEBENGINE) - add_subdirectory(alethzero) -# add_subdirectory(third) // reenable once not qtwebkit. - endif() - - add_subdirectory(mix) + add_subdirectory(libnatspec) + add_subdirectory(libjsqrc) + if (ETH_HAVE_WEBENGINE) + add_subdirectory(alethzero) +# add_subdirectory(third) // reenable once not qtwebkit. endif() + if (SOLIDITY) + add_subdirectory(mix) + endif () + endif() #unset(TARGET_PLATFORM CACHE) diff --git a/abi/main.cpp b/abi/main.cpp index 80742fcd2..e7382c761 100644 --- a/abi/main.cpp +++ b/abi/main.cpp @@ -436,7 +436,7 @@ struct ABIMethod if (_params[pi].second != Format::Open) throw ExpectedOpen(); ++pi; - int l = a.dims[addr.size()]; + int l = a.dims[a.dims.size() - 1 - addr.size()]; if (l == -1) { // read ahead in params and discover the arity. @@ -486,7 +486,7 @@ struct ABIMethod put(); else { - int l = a.dims[addr.size()]; + int l = a.dims[a.dims.size() - 1 - addr.size()]; if (l == -1) { l = fromBigEndian(bytesConstRef(&_data).cropped(di, 32)); @@ -525,7 +525,7 @@ struct ABIMethod { out << "["; addr.push_back(0); - int l = a.dims[addr.size() - 1]; + int l = a.dims[a.dims.size() - 1 - (addr.size() - 1)]; if (l == -1) l = catDims[d++]; for (addr.back() = 0; addr.back() < l; ++addr.back()) diff --git a/alethzero/Connect.cpp b/alethzero/Connect.cpp index 32fa74f38..69cc43f93 100644 --- a/alethzero/Connect.cpp +++ b/alethzero/Connect.cpp @@ -38,13 +38,14 @@ Connect::~Connect() void Connect::setEnvironment(QStringList const& _nodes) { - ui->host->addItems(_nodes); + if (ui->host->count() == 0) + ui->host->addItems(_nodes); } void Connect::reset() { ui->nodeId->clear(); - ui->required->setChecked(false); + ui->required->setChecked(true); } QString Connect::host() diff --git a/alethzero/Connect.ui b/alethzero/Connect.ui index 9a0522e5f..1ace94c8d 100644 --- a/alethzero/Connect.ui +++ b/alethzero/Connect.ui @@ -59,6 +59,9 @@ Required (Always Connect to this Peer) + + true + false diff --git a/alethzero/DappLoader.cpp b/alethzero/DappLoader.cpp index 821629906..9baf0c82e 100644 --- a/alethzero/DappLoader.cpp +++ b/alethzero/DappLoader.cpp @@ -193,10 +193,26 @@ Manifest DappLoader::loadManifest(std::string const& _manifest) void DappLoader::loadDapp(QString const& _uri) { - DappLocation location = resolveAppUri(_uri); - QUrl uri(location.contentUri); - QNetworkRequest request(uri); - m_uriHashes[uri] = location.contentHash; + QUrl uri(_uri); + QUrl contentUri; + h256 hash; + if (uri.path().endsWith(".dapp") && uri.query().startsWith("hash=")) + { + contentUri = uri; + QString query = uri.query(); + query.remove("hash="); + if (!query.startsWith("0x")) + query.insert(0, "0x"); + hash = jsToFixed<32>(query.toStdString()); + } + else + { + DappLocation location = resolveAppUri(_uri); + contentUri = location.contentUri; + hash = location.contentHash; + } + QNetworkRequest request(contentUri); + m_uriHashes[uri] = hash; m_net.get(request); } diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index 610fd032d..a15bc833d 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -148,7 +148,7 @@ Main::Main(QWidget *parent) : cerr << "State root: " << CanonBlockChain::genesis().stateRoot << endl; auto block = CanonBlockChain::createGenesisBlock(); - cerr << "Block Hash: " << CanonBlockChain::genesis().hash << endl; + cerr << "Block Hash: " << CanonBlockChain::genesis().hash() << endl; cerr << "Block RLP: " << RLP(block) << endl; cerr << "Block Hex: " << toHex(block) << endl; cerr << "eth Network protocol version: " << eth::c_protocolVersion << endl; @@ -168,7 +168,7 @@ Main::Main(QWidget *parent) : QSettings s("ethereum", "alethzero"); m_networkConfig = s.value("peers").toByteArray(); bytesConstRef network((byte*)m_networkConfig.data(), m_networkConfig.size()); - m_webThree.reset(new WebThreeDirect(string("AlethZero/v") + dev::Version + "/" DEV_QUOTED(ETH_BUILD_TYPE) "/" DEV_QUOTED(ETH_BUILD_PLATFORM), getDataDir(), false, {"eth", "shh"}, p2p::NetworkPreferences(), network)); + m_webThree.reset(new WebThreeDirect(string("AlethZero/v") + dev::Version + "/" DEV_QUOTED(ETH_BUILD_TYPE) "/" DEV_QUOTED(ETH_BUILD_PLATFORM), getDataDir(), WithExisting::Trust, {"eth", "shh"}, p2p::NetworkPreferences(), network)); m_httpConnector.reset(new jsonrpc::HttpServer(SensibleHttpPort, "", "", dev::SensibleHttpThreads)); m_server.reset(new OurWebThreeStubServer(*m_httpConnector, *web3(), keysAsVector(m_myKeys), this)); @@ -285,7 +285,7 @@ void Main::onKeysChanged() unsigned Main::installWatch(LogFilter const& _tf, WatchHandler const& _f) { - auto ret = ethereum()->installWatch(_tf); + auto ret = ethereum()->installWatch(_tf, Reaping::Manual); m_handlers[ret] = _f; _f(LocalisedLogEntries()); return ret; @@ -900,7 +900,7 @@ void Main::on_urlEdit_returnPressed() { QString s = ui->urlEdit->text(); QUrl url(s); - if (url.scheme().isEmpty() || url.scheme() == "eth") + if (url.scheme().isEmpty() || url.scheme() == "eth" || url.path().endsWith(".dapp")) { try { @@ -1491,7 +1491,7 @@ void Main::on_blocks_currentItemChanged() { BlockInfo uncle = BlockInfo::fromHeader(u.data()); char const* line = "
 "; - s << line << "Hash: " << uncle.hash << "" << "
"; + s << line << "Hash: " << uncle.hash() << "" << ""; s << line << "Parent: " << uncle.parentHash << "" << ""; s << line << "Number: " << uncle.number << "" << ""; s << line << "Coinbase: " << pretty(uncle.coinbaseAddress).toHtmlEscaped().toStdString() << " " << uncle.coinbaseAddress << "" << ""; @@ -1582,7 +1582,7 @@ void Main::on_debugCurrent_triggered() unsigned txi = item->data(Qt::UserRole + 1).toInt(); bytes t = ethereum()->blockChain().transaction(h, txi); State s(ethereum()->state(txi, h)); - Executive e(s, ethereum()->blockChain(), PendingBlock); + Executive e(s, ethereum()->blockChain()); Debugger dw(this, this); dw.populate(e, Transaction(t, CheckSignature::Sender)); dw.exec(); diff --git a/cmake/EthCompilerSettings.cmake b/cmake/EthCompilerSettings.cmake index dd14b0650..d781ad2b3 100755 --- a/cmake/EthCompilerSettings.cmake +++ b/cmake/EthCompilerSettings.cmake @@ -7,9 +7,17 @@ if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g -DETH_DEBUG") set(CMAKE_CXX_FLAGS_MINSIZEREL "-Os -DNDEBUG -DETH_RELEASE") set(CMAKE_CXX_FLAGS_RELEASE "-O3 -DNDEBUG -DETH_RELEASE") - set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 -g -DETH_DEBUG") + set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 -g -DETH_RELEASE") set(ETH_SHARED 1) + if (PROFILING) + set(CMAKE_CXX_FLAGS "-g ${CMAKE_CXX_FLAGS}") + add_definitions(-DETH_PROFILING_GPERF) + set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -lprofiler") +# set(CMAKE_STATIC_LINKER_FLAGS "${CMAKE_STATIC_LINKER_FLAGS} -lprofiler") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -lprofiler") + endif () + execute_process( COMMAND ${CMAKE_CXX_COMPILER} -dumpversion OUTPUT_VARIABLE GCC_VERSION) if (NOT (GCC_VERSION VERSION_GREATER 4.7 OR GCC_VERSION VERSION_EQUAL 4.7)) diff --git a/cmake/EthDependencies.cmake b/cmake/EthDependencies.cmake index 5c0eaa65b..61c87efd2 100644 --- a/cmake/EthDependencies.cmake +++ b/cmake/EthDependencies.cmake @@ -101,7 +101,7 @@ find_program(ETH_JSON_RPC_STUB jsonrpcstub) message(" - jsonrpcstub location : ${ETH_JSON_RPC_STUB}") # do not compile GUI -if (NOT HEADLESS) +if (GUI) # we need json rpc to build alethzero if (NOT JSON_RPC_CPP_FOUND) @@ -153,7 +153,7 @@ if (NOT HEADLESS) endif() endif() -endif() #HEADLESS +endif() #GUI # use multithreaded boost libraries, with -mt suffix set(Boost_USE_MULTITHREADED ON) diff --git a/eth/main.cpp b/eth/main.cpp index 2a065cc8c..9ec6dec98 100644 --- a/eth/main.cpp +++ b/eth/main.cpp @@ -117,10 +117,10 @@ void help() << " -h,--help Show this help message and exit." << endl << " -i,--interactive Enter interactive mode (default: non-interactive)." << endl #if ETH_JSONRPC - << " -j,--json-rpc Enable JSON-RPC server (default: off)." << endl - << " --json-rpc-port Specify JSON-RPC server port (implies '-j', default: " << SensibleHttpPort << ")." << endl + << " -j,--json-rpc Enable JSON-RPC server (default: off)." << endl + << " --json-rpc-port Specify JSON-RPC server port (implies '-j', default: " << SensibleHttpPort << ")." << endl #endif - << " -K,--kill-blockchain First kill the blockchain." << endl + << " -K,--kill First kill the blockchain." << endl << " --listen-ip Listen on the given port for incoming connections (default: 30303)." << endl << " -l,--listen Listen on the given IP for incoming connections (default: 0.0.0.0)." << endl << " -u,--public-ip Force public ip to given (default: auto)." << endl @@ -129,6 +129,7 @@ void help() << " -o,--mode Start a full node or a peer node (Default: full)." << endl << " -p,--port Connect to remote port (default: 30303)." << endl << " -P,--priority <0 - 100> Default % priority of a transaction (default: 50)." << endl + << " -R,--rebuild First rebuild the blockchain from the existing database." << endl << " -r,--remote Connect to remote host (default: none)." << endl << " -s,--secret Set the secret key for use with send command (default: auto)." << endl << " -t,--miners Number of mining threads to start (Default: " << thread::hardware_concurrency() << ")" << endl @@ -168,7 +169,7 @@ void version() } Address c_config = Address("ccdeac59d35627b7de09332e819d5159e7bb7250"); -string pretty(h160 _a, dev::eth::State _st) +string pretty(h160 _a, dev::eth::State const& _st) { string ns; h256 n; @@ -216,7 +217,7 @@ int main(int argc, char** argv) bool bootstrap = false; bool upnp = true; bool forceMining = false; - bool killChain = false; + WithExisting killChain = WithExisting::Trust; bool jit = false; bool structuredLogging = false; string structuredLoggingFormat = "%Y-%m-%dT%H:%M:%S"; @@ -273,8 +274,10 @@ int main(int argc, char** argv) return -1; } } - else if (arg == "-K" || arg == "--kill-blockchain") - killChain = true; + else if (arg == "-K" || arg == "--kill-blockchain" || arg == "--kill") + killChain = WithExisting::Kill; + else if (arg == "-B" || arg == "--rebuild") + killChain = WithExisting::Verify; else if ((arg == "-c" || arg == "--client-name") && i + 1 < argc) clientName = argv[++i]; else if ((arg == "-a" || arg == "--address" || arg == "--coinbase-address") && i + 1 < argc) diff --git a/evmjit/libevmjit/BasicBlock.cpp b/evmjit/libevmjit/BasicBlock.cpp index 93d73b991..a41743d0b 100644 --- a/evmjit/libevmjit/BasicBlock.cpp +++ b/evmjit/libevmjit/BasicBlock.cpp @@ -49,6 +49,7 @@ void BasicBlock::LocalStack::push(llvm::Value* _value) assert(_value->getType() == Type::Word); m_bblock.m_currentStack.push_back(_value); m_bblock.m_tosOffset += 1; + m_maxSize = std::max(m_maxSize, m_bblock.m_currentStack.size()); } llvm::Value* BasicBlock::LocalStack::pop() diff --git a/evmjit/libevmjit/BasicBlock.h b/evmjit/libevmjit/BasicBlock.h index 7469b7b69..5e19235a7 100644 --- a/evmjit/libevmjit/BasicBlock.h +++ b/evmjit/libevmjit/BasicBlock.h @@ -33,6 +33,9 @@ public: /// @param _index Index of value to be swaped. Must be > 0. void swap(size_t _index); + size_t getMaxSize() const { return m_maxSize; } + int getDiff() const { return m_bblock.m_tosOffset; } + private: LocalStack(BasicBlock& _owner); LocalStack(LocalStack const&) = delete; @@ -49,6 +52,7 @@ public: private: BasicBlock& m_bblock; + size_t m_maxSize = 0; ///< Max size reached by the stack. }; explicit BasicBlock(instr_idx _firstInstrIdx, code_iterator _begin, code_iterator _end, llvm::Function* _mainFunc, llvm::IRBuilder<>& _builder, bool isJumpDest); diff --git a/evmjit/libevmjit/Cache.cpp b/evmjit/libevmjit/Cache.cpp index d3cda1098..47a6386e9 100644 --- a/evmjit/libevmjit/Cache.cpp +++ b/evmjit/libevmjit/Cache.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -59,6 +60,36 @@ void Cache::clear() fs::remove(it->path()); } +void Cache::preload(llvm::ExecutionEngine& _ee, std::unordered_map& _funcCache) +{ + // TODO: Cache dir should be in one place + using namespace llvm::sys; + llvm::SmallString<256> cachePath; + path::system_temp_directory(false, cachePath); + path::append(cachePath, "evm_objs"); + + // Disable listener + auto listener = g_listener; + g_listener = nullptr; + + std::error_code err; + for (auto it = fs::directory_iterator{cachePath.str(), err}; it != fs::directory_iterator{}; it.increment(err)) + { + auto name = it->path().substr(cachePath.size() + 1); + if (auto module = getObject(name)) + { + DLOG(cache) << "Preload: " << name << "\n"; + _ee.addModule(module.get()); + module.release(); + auto addr = _ee.getFunctionAddress(name); + assert(addr); + _funcCache[std::move(name)] = addr; + } + } + + g_listener = listener; +} + std::unique_ptr Cache::getObject(std::string const& id) { if (g_mode != CacheMode::on && g_mode != CacheMode::read) diff --git a/evmjit/libevmjit/Cache.h b/evmjit/libevmjit/Cache.h index b0d26d080..f6a0a3400 100644 --- a/evmjit/libevmjit/Cache.h +++ b/evmjit/libevmjit/Cache.h @@ -1,9 +1,15 @@ #pragma once #include +#include #include +namespace llvm +{ + class ExecutionEngine; +} + namespace dev { namespace eth @@ -18,7 +24,8 @@ enum class CacheMode off, read, write, - clear + clear, + preload }; class ObjectCache : public llvm::ObjectCache @@ -43,6 +50,9 @@ public: /// Clears cache storage static void clear(); + + /// Loads all available cached objects to ExecutionEngine + static void preload(llvm::ExecutionEngine& _ee, std::unordered_map& _funcCache); }; } diff --git a/evmjit/libevmjit/Compiler.cpp b/evmjit/libevmjit/Compiler.cpp index be3670874..1e1f8fe93 100644 --- a/evmjit/libevmjit/Compiler.cpp +++ b/evmjit/libevmjit/Compiler.cpp @@ -838,6 +838,9 @@ void Compiler::compileBasicBlock(BasicBlock& _basicBlock, RuntimeManager& _runti // Block may have no terminator if the next instruction is a jump destination. if (!_basicBlock.llvm()->getTerminator()) m_builder.CreateBr(_nextBasicBlock); + + m_builder.SetInsertPoint(_basicBlock.llvm()->getFirstNonPHI()); + _runtimeManager.checkStackLimit(_basicBlock.localStack().getMaxSize(), _basicBlock.localStack().getDiff()); } diff --git a/evmjit/libevmjit/ExecutionEngine.cpp b/evmjit/libevmjit/ExecutionEngine.cpp index ce1f530d7..0ed4a65b5 100644 --- a/evmjit/libevmjit/ExecutionEngine.cpp +++ b/evmjit/libevmjit/ExecutionEngine.cpp @@ -76,6 +76,7 @@ cl::opt g_cache{"cache", cl::desc{"Cache compiled EVM code on disk"}, clEnumValN(CacheMode::read, "r", "Read only. No new objects are added to cache."), clEnumValN(CacheMode::write, "w", "Write only. No objects are loaded from cache."), clEnumValN(CacheMode::clear, "c", "Clear the cache storage. Cache is disabled."), + clEnumValN(CacheMode::preload, "p", "Preload all cached objects."), clEnumValEnd)}; cl::opt g_stats{"st", cl::desc{"Statistics"}}; cl::opt g_dump{"dump", cl::desc{"Dump LLVM IR module"}}; @@ -111,8 +112,15 @@ ReturnCode ExecutionEngine::run(RuntimeData* _data, Env* _env) std::unique_ptr listener{new ExecStats}; listener->stateChanged(ExecState::Started); + bool preloadCache = g_cache == CacheMode::preload; + if (preloadCache) + g_cache = CacheMode::on; + + // TODO: Do not pseudo-init the cache every time auto objectCache = (g_cache != CacheMode::off && g_cache != CacheMode::clear) ? Cache::getObjectCache(g_cache, listener.get()) : nullptr; + static std::unordered_map funcCache; + static std::unique_ptr ee; if (!ee) { @@ -138,6 +146,9 @@ ReturnCode ExecutionEngine::run(RuntimeData* _data, Env* _env) return ReturnCode::LLVMConfigError; module.release(); // Successfully created llvm::ExecutionEngine takes ownership of the module ee->setObjectCache(objectCache); + + if (preloadCache) + Cache::preload(*ee, funcCache); } static StatsCollector statsCollector; @@ -146,10 +157,9 @@ ReturnCode ExecutionEngine::run(RuntimeData* _data, Env* _env) m_runtime.init(_data, _env); EntryFuncPtr entryFuncPtr = nullptr; - static std::unordered_map funcCache; auto it = funcCache.find(mainFuncName); if (it != funcCache.end()) - entryFuncPtr = it->second; + entryFuncPtr = (EntryFuncPtr) it->second; if (!entryFuncPtr) { @@ -177,7 +187,8 @@ ReturnCode ExecutionEngine::run(RuntimeData* _data, Env* _env) if (!CHECK(entryFuncPtr)) return ReturnCode::LLVMLinkError; - funcCache[mainFuncName] = entryFuncPtr; + if (it == funcCache.end()) + funcCache[mainFuncName] = (uint64_t) entryFuncPtr; listener->stateChanged(ExecState::Execution); auto returnCode = entryFuncPtr(&m_runtime); diff --git a/evmjit/libevmjit/RuntimeManager.cpp b/evmjit/libevmjit/RuntimeManager.cpp index d1ccaea8a..dc7bc24a3 100644 --- a/evmjit/libevmjit/RuntimeManager.cpp +++ b/evmjit/libevmjit/RuntimeManager.cpp @@ -78,7 +78,7 @@ llvm::Twine getName(RuntimeData::Index _index) case RuntimeData::CodeSize: return "code"; case RuntimeData::CallDataSize: return "callDataSize"; case RuntimeData::Gas: return "gas"; - case RuntimeData::Number: return "number"; + case RuntimeData::Number: return "number"; case RuntimeData::Timestamp: return "timestamp"; } } @@ -101,6 +101,48 @@ RuntimeManager::RuntimeManager(llvm::IRBuilder<>& _builder, code_iterator _codeB assert(m_memPtr->getType() == Array::getType()->getPointerTo()); m_envPtr = m_builder.CreateLoad(m_builder.CreateStructGEP(rtPtr, 1), "env"); assert(m_envPtr->getType() == Type::EnvPtr); + + m_stackSize = m_builder.CreateAlloca(Type::Size, nullptr, "stackSize"); + m_builder.CreateStore(m_builder.getInt64(0), m_stackSize); + + llvm::Type* checkStackLimitArgs[] = {Type::Size->getPointerTo(), Type::Size, Type::Size, Type::BytePtr}; + m_checkStackLimit = llvm::Function::Create(llvm::FunctionType::get(Type::Void, checkStackLimitArgs, false), llvm::Function::PrivateLinkage, "stack.checkSize", getModule()); + m_checkStackLimit->setDoesNotThrow(); + m_checkStackLimit->setDoesNotCapture(1); + + auto checkBB = llvm::BasicBlock::Create(_builder.getContext(), "Check", m_checkStackLimit); + auto updateBB = llvm::BasicBlock::Create(_builder.getContext(), "Update", m_checkStackLimit); + auto outOfStackBB = llvm::BasicBlock::Create(_builder.getContext(), "OutOfStack", m_checkStackLimit); + + auto currSizePtr = &m_checkStackLimit->getArgumentList().front(); + currSizePtr->setName("currSize"); + auto max = currSizePtr->getNextNode(); + max->setName("max"); + auto diff = max->getNextNode(); + diff->setName("diff"); + auto jmpBuf = diff->getNextNode(); + jmpBuf->setName("jmpBuf"); + + InsertPointGuard guard{m_builder}; + m_builder.SetInsertPoint(checkBB); + auto currSize = m_builder.CreateLoad(currSizePtr, "cur"); + auto maxSize = m_builder.CreateNUWAdd(currSize, max, "maxSize"); + auto ok = m_builder.CreateICmpULE(maxSize, m_builder.getInt64(1024), "ok"); + m_builder.CreateCondBr(ok, updateBB, outOfStackBB, Type::expectTrue); + + m_builder.SetInsertPoint(updateBB); + auto newSize = m_builder.CreateNSWAdd(currSize, diff); + m_builder.CreateStore(newSize, currSizePtr); + m_builder.CreateRetVoid(); + + m_builder.SetInsertPoint(outOfStackBB); + abort(jmpBuf); + m_builder.CreateUnreachable(); +} + +void RuntimeManager::checkStackLimit(size_t _max, int _diff) +{ + createCall(m_checkStackLimit, {m_stackSize, m_builder.getInt64(_max), m_builder.getInt64(_diff), getJmpBuf()}); } llvm::Value* RuntimeManager::getRuntimePtr() diff --git a/evmjit/libevmjit/RuntimeManager.h b/evmjit/libevmjit/RuntimeManager.h index eb8dadf6f..c430a1098 100644 --- a/evmjit/libevmjit/RuntimeManager.h +++ b/evmjit/libevmjit/RuntimeManager.h @@ -48,6 +48,8 @@ public: static llvm::StructType* getRuntimeType(); static llvm::StructType* getRuntimeDataType(); + void checkStackLimit(size_t _max, int _diff); + private: llvm::Value* getPtr(RuntimeData::Index _index); void set(RuntimeData::Index _index, llvm::Value* _value); @@ -59,6 +61,9 @@ private: llvm::Value* m_memPtr = nullptr; llvm::Value* m_envPtr = nullptr; + llvm::Value* m_stackSize = nullptr; + llvm::Function* m_checkStackLimit = nullptr; + code_iterator m_codeBegin = {}; code_iterator m_codeEnd = {}; diff --git a/libdevcore/Common.cpp b/libdevcore/Common.cpp index cf6bf44c4..883914ea6 100644 --- a/libdevcore/Common.cpp +++ b/libdevcore/Common.cpp @@ -27,7 +27,7 @@ using namespace dev; namespace dev { -char const* Version = "0.9.1"; +char const* Version = "0.9.4"; } diff --git a/libdevcore/Common.h b/libdevcore/Common.h index 5e778644d..49491d4cc 100644 --- a/libdevcore/Common.h +++ b/libdevcore/Common.h @@ -134,4 +134,20 @@ private: std::function m_f; }; +enum class WithExisting: int +{ + Trust = 0, + Verify, + Kill +}; + +} + +namespace std { + +inline dev::WithExisting max(dev::WithExisting _a, dev::WithExisting _b) +{ + return static_cast(max(static_cast(_a), static_cast(_b))); +} + } diff --git a/libethcore/BlockInfo.cpp b/libethcore/BlockInfo.cpp index bbd78224f..66f08d2bf 100644 --- a/libethcore/BlockInfo.cpp +++ b/libethcore/BlockInfo.cpp @@ -35,9 +35,9 @@ BlockInfo::BlockInfo(): timestamp(Invalid256) { } -BlockInfo::BlockInfo(bytesConstRef _block, Strictness _s) +BlockInfo::BlockInfo(bytesConstRef _block, Strictness _s, h256 const& _h) { - populate(_block, _s); + populate(_block, _s, _h); } void BlockInfo::setEmpty() @@ -57,8 +57,7 @@ void BlockInfo::setEmpty() extraData.clear(); mixHash = h256(); nonce = Nonce(); - m_seedHash = h256(); - hash = headerHash(WithNonce); + m_hash = m_seedHash = h256(); } h256 const& BlockInfo::seedHash() const @@ -69,10 +68,17 @@ h256 const& BlockInfo::seedHash() const return m_seedHash; } -BlockInfo BlockInfo::fromHeader(bytesConstRef _block, Strictness _s) +h256 const& BlockInfo::hash() const +{ + if (!m_hash) + m_hash = headerHash(WithNonce); + return m_hash; +} + +BlockInfo BlockInfo::fromHeader(bytesConstRef _header, Strictness _s, h256 const& _h) { BlockInfo ret; - ret.populateFromHeader(RLP(_block), _s); + ret.populateFromHeader(RLP(_header), _s, _h); return ret; } @@ -97,9 +103,11 @@ h256 BlockInfo::headerHash(bytesConstRef _block) return sha3(RLP(_block)[0].data()); } -void BlockInfo::populateFromHeader(RLP const& _header, Strictness _s) +void BlockInfo::populateFromHeader(RLP const& _header, Strictness _s, h256 const& _h) { - hash = dev::sha3(_header.data()); +// m_hash = dev::sha3(_header.data()); + m_hash = _h; + m_seedHash = h256(); int field = 0; try @@ -149,14 +157,14 @@ void BlockInfo::populateFromHeader(RLP const& _header, Strictness _s) } } -void BlockInfo::populate(bytesConstRef _block, Strictness _s) +void BlockInfo::populate(bytesConstRef _block, Strictness _s, h256 const& _h) { RLP root(_block); RLP header = root[0]; if (!header.isList()) BOOST_THROW_EXCEPTION(InvalidBlockFormat() << errinfo_comment("block header needs to be a list") << BadFieldError(0, header.data().toString())); - populateFromHeader(header, _s); + populateFromHeader(header, _s, _h); if (!root[1].isList()) BOOST_THROW_EXCEPTION(InvalidBlockFormat() << errinfo_comment("block transactions need to be a list") << BadFieldError(1, root[1].data().toString())); @@ -191,8 +199,9 @@ void BlockInfo::verifyInternals(bytesConstRef _block) const void BlockInfo::populateFromParent(BlockInfo const& _parent) { + m_hash = m_seedHash = h256(); stateRoot = _parent.stateRoot; - parentHash = _parent.hash; + parentHash = _parent.hash(); number = _parent.number + 1; gasLimit = selectGasLimit(_parent); gasUsed = 0; @@ -230,7 +239,7 @@ void BlockInfo::verifyParent(BlockInfo const& _parent) const // Check timestamp is after previous timestamp. if (parentHash) { - if (parentHash != _parent.hash) + if (parentHash != _parent.hash()) BOOST_THROW_EXCEPTION(InvalidParentHash()); if (timestamp <= _parent.timestamp) diff --git a/libethcore/BlockInfo.h b/libethcore/BlockInfo.h index 8f42dc038..6a3ca68b2 100644 --- a/libethcore/BlockInfo.h +++ b/libethcore/BlockInfo.h @@ -68,7 +68,6 @@ struct BlockInfo { public: // TODO: make them all private! - h256 hash; ///< SHA3 hash of the block header! Not serialised (the only member not contained in a block header). h256 parentHash; h256 sha3Uncles; Address coinbaseAddress; @@ -80,48 +79,50 @@ public: u256 number; u256 gasLimit; u256 gasUsed; - u256 timestamp; + u256 timestamp = Invalid256; bytes extraData; h256 mixHash; Nonce nonce; BlockInfo(); - explicit BlockInfo(bytes const& _block, Strictness _s = CheckEverything): BlockInfo(&_block, _s) {} - explicit BlockInfo(bytesConstRef _block, Strictness _s = CheckEverything); + explicit BlockInfo(bytes const& _block, Strictness _s = IgnoreNonce, h256 const& _h = h256()): BlockInfo(&_block, _s, _h) {} + explicit BlockInfo(bytesConstRef _block, Strictness _s = IgnoreNonce, h256 const& _h = h256()); static h256 headerHash(bytes const& _block) { return headerHash(&_block); } static h256 headerHash(bytesConstRef _block); - static BlockInfo fromHeader(bytes const& _block, Strictness _s = CheckEverything) { return fromHeader(bytesConstRef(&_block), _s); } - static BlockInfo fromHeader(bytesConstRef _block, Strictness _s = CheckEverything); + static BlockInfo fromHeader(bytes const& _header, Strictness _s = IgnoreNonce, h256 const& _h = h256()) { return fromHeader(bytesConstRef(&_header), _s, _h); } + static BlockInfo fromHeader(bytesConstRef _header, Strictness _s = IgnoreNonce, h256 const& _h = h256()); explicit operator bool() const { return timestamp != Invalid256; } bool operator==(BlockInfo const& _cmp) const { return parentHash == _cmp.parentHash && - sha3Uncles == _cmp.sha3Uncles && - coinbaseAddress == _cmp.coinbaseAddress && - stateRoot == _cmp.stateRoot && - transactionsRoot == _cmp.transactionsRoot && - receiptsRoot == _cmp.receiptsRoot && - logBloom == _cmp.logBloom && - difficulty == _cmp.difficulty && - number == _cmp.number && - gasLimit == _cmp.gasLimit && - gasUsed == _cmp.gasUsed && - timestamp == _cmp.timestamp && - extraData == _cmp.extraData && - mixHash == _cmp.mixHash && - nonce == _cmp.nonce; + sha3Uncles == _cmp.sha3Uncles && + coinbaseAddress == _cmp.coinbaseAddress && + stateRoot == _cmp.stateRoot && + transactionsRoot == _cmp.transactionsRoot && + receiptsRoot == _cmp.receiptsRoot && + logBloom == _cmp.logBloom && + difficulty == _cmp.difficulty && + number == _cmp.number && + gasLimit == _cmp.gasLimit && + gasUsed == _cmp.gasUsed && + timestamp == _cmp.timestamp && + extraData == _cmp.extraData && + mixHash == _cmp.mixHash && + nonce == _cmp.nonce; } bool operator!=(BlockInfo const& _cmp) const { return !operator==(_cmp); } void setEmpty(); - void populateFromHeader(RLP const& _header, Strictness _s = CheckEverything); - void populate(bytesConstRef _block, Strictness _s = CheckEverything); - void populate(bytes const& _block, Strictness _s = CheckEverything) { populate(&_block, _s); } + void noteDirty() const { m_hash = m_seedHash= h256(); } + + void populateFromHeader(RLP const& _header, Strictness _s = IgnoreNonce, h256 const& _h = h256()); + void populate(bytesConstRef _block, Strictness _s = IgnoreNonce, h256 const& _h = h256()); + void populate(bytes const& _block, Strictness _s = IgnoreNonce, h256 const& _h = h256()) { populate(&_block, _s, _h); } void verifyInternals(bytesConstRef _block) const; void verifyParent(BlockInfo const& _parent) const; void populateFromParent(BlockInfo const& parent); @@ -129,6 +130,7 @@ public: u256 calculateDifficulty(BlockInfo const& _parent) const; u256 selectGasLimit(BlockInfo const& _parent) const; h256 const& seedHash() const; + h256 const& hash() const; /// sha3 of the header only. h256 headerHash(IncludeNonce _n) const; @@ -136,11 +138,12 @@ public: private: mutable h256 m_seedHash; + mutable h256 m_hash; ///< SHA3 hash of the block header! Not serialised. }; inline std::ostream& operator<<(std::ostream& _out, BlockInfo const& _bi) { - _out << _bi.hash << " " << _bi.parentHash << " " << _bi.sha3Uncles << " " << _bi.coinbaseAddress << " " << _bi.stateRoot << " " << _bi.transactionsRoot << " " << + _out << _bi.hash() << " " << _bi.parentHash << " " << _bi.sha3Uncles << " " << _bi.coinbaseAddress << " " << _bi.stateRoot << " " << _bi.transactionsRoot << " " << _bi.receiptsRoot << " " << _bi.logBloom << " " << _bi.difficulty << " " << _bi.number << " " << _bi.gasLimit << " " << _bi.gasUsed << " " << _bi.timestamp << " " << _bi.mixHash << " " << _bi.nonce << " (" << _bi.seedHash() << ")"; return _out; diff --git a/libethcore/Common.cpp b/libethcore/Common.cpp index d3ecbcc5f..572ade3e2 100644 --- a/libethcore/Common.cpp +++ b/libethcore/Common.cpp @@ -34,8 +34,9 @@ namespace eth { const unsigned c_ethashVersion = c_ethashRevision; -const unsigned c_protocolVersion = 59; -const unsigned c_databaseBaseVersion = 8; +const unsigned c_protocolVersion = 60; +const unsigned c_minorProtocolVersion = 0; +const unsigned c_databaseBaseVersion = 9; #if ETH_FATDB const unsigned c_databaseVersionModifier = 1; #else diff --git a/libethcore/Common.h b/libethcore/Common.h index 51322dcb2..56f1b704a 100644 --- a/libethcore/Common.h +++ b/libethcore/Common.h @@ -35,6 +35,9 @@ namespace eth /// Current protocol version. extern const unsigned c_protocolVersion; +/// Current minor protocol version. +extern const unsigned c_minorProtocolVersion; + /// Current database version. extern const unsigned c_databaseVersion; @@ -82,5 +85,15 @@ enum class RelativeBlock: BlockNumber Pending = PendingBlock }; +enum class ImportResult +{ + Success = 0, + UnknownParent, + FutureTime, + AlreadyInChain, + AlreadyKnown, + Malformed +}; + } } diff --git a/libethcore/Ethasher.cpp b/libethcore/Ethasher.cpp index 64db69187..75f0bcd5a 100644 --- a/libethcore/Ethasher.cpp +++ b/libethcore/Ethasher.cpp @@ -43,21 +43,21 @@ Ethasher* dev::eth::Ethasher::s_this = nullptr; Ethasher::~Ethasher() { - while (!m_caches.empty()) - killCache(m_caches.begin()->first); + while (!m_lights.empty()) + killCache(m_lights.begin()->first); } void Ethasher::killCache(h256 const& _s) { RecursiveGuard l(x_this); - if (m_caches.count(_s)) + if (m_lights.count(_s)) { - ethash_delete_light(m_caches.at(_s)); - m_caches.erase(_s); + ethash_delete_light(m_lights.at(_s)); + m_lights.erase(_s); } } -void const* Ethasher::cache(BlockInfo const& _header) +void const* Ethasher::light(BlockInfo const& _header) { RecursiveGuard l(x_this); if (_header.number > c_ethashEpochLength * 2048) @@ -67,41 +67,52 @@ void const* Ethasher::cache(BlockInfo const& _header) throw std::invalid_argument( error.str() ); } - if (!m_caches.count(_header.seedHash())) + if (!m_lights.count(_header.seedHash())) { ethash_params p = params((unsigned)_header.number); - m_caches[_header.seedHash()] = ethash_new_light(&p, _header.seedHash().data()); + m_lights[_header.seedHash()] = ethash_new_light(&p, _header.seedHash().data()); } - return m_caches[_header.seedHash()]; + return m_lights[_header.seedHash()]; } +#define IGNORE_EXCEPTIONS(X) try { X; } catch (...) {} + bytesConstRef Ethasher::full(BlockInfo const& _header) { RecursiveGuard l(x_this); if (!m_fulls.count(_header.seedHash())) { - if (!m_fulls.empty()) + // @memoryleak @bug place it on a pile for deletion - perhaps use shared_ptr. +/* if (!m_fulls.empty()) { delete [] m_fulls.begin()->second.data(); m_fulls.erase(m_fulls.begin()); - } + }*/ + try { boost::filesystem::create_directories(getDataDir("ethash")); } catch (...) {} - std::string memoFile = getDataDir("ethash") + "/full"; auto info = rlpList(c_ethashRevision, _header.seedHash()); - if (boost::filesystem::exists(memoFile) && contents(memoFile + ".info") != info) - boost::filesystem::remove(memoFile); + std::string oldMemoFile = getDataDir("ethash") + "/full"; + std::string memoFile = getDataDir("ethash") + "/full-R" + toString(c_ethashRevision) + "-" + toHex(_header.seedHash().ref().cropped(0, 8)); + if (boost::filesystem::exists(oldMemoFile) && contents(oldMemoFile + ".info") == info) + { + // memofile valid - rename. + boost::filesystem::rename(oldMemoFile, memoFile); + } + + IGNORE_EXCEPTIONS(boost::filesystem::remove(oldMemoFile)); + IGNORE_EXCEPTIONS(boost::filesystem::remove(oldMemoFile + ".info")); + m_fulls[_header.seedHash()] = contentsNew(memoFile); if (!m_fulls[_header.seedHash()]) { ethash_params p = params((unsigned)_header.number); m_fulls[_header.seedHash()] = bytesRef(new byte[p.full_size], p.full_size); - auto c = cache(_header); + auto c = light(_header); ethash_prep_full(m_fulls[_header.seedHash()].data(), &p, c); writeFile(memoFile, m_fulls[_header.seedHash()]); - writeFile(memoFile + ".info", info); } } return m_fulls[_header.seedHash()]; @@ -162,7 +173,10 @@ Ethasher::Result Ethasher::eval(BlockInfo const& _header, Nonce const& _nonce) { auto p = Ethasher::params(_header); ethash_return_value r; - ethash_compute_light(&r, Ethasher::get()->cache(_header), &p, _header.headerHash(WithoutNonce).data(), (uint64_t)(u64)_nonce); + if (Ethasher::get()->m_fulls.count(_header.seedHash())) + ethash_compute_full(&r, Ethasher::get()->full(_header).data(), &p, _header.headerHash(WithoutNonce).data(), (uint64_t)(u64)_nonce); + else + ethash_compute_light(&r, Ethasher::get()->light(_header), &p, _header.headerHash(WithoutNonce).data(), (uint64_t)(u64)_nonce); // cdebug << "Ethasher::eval sha3(cache):" << sha3(Ethasher::get()->cache(_header)) << "hh:" << _header.headerHash(WithoutNonce) << "nonce:" << _nonce << " => " << h256(r.result, h256::ConstructFromPointer); return Result{h256(r.result, h256::ConstructFromPointer), h256(r.mix_hash, h256::ConstructFromPointer)}; } diff --git a/libethcore/Ethasher.h b/libethcore/Ethasher.h index a10c206d1..8d1a1e84c 100644 --- a/libethcore/Ethasher.h +++ b/libethcore/Ethasher.h @@ -51,7 +51,7 @@ public: using LightType = void const*; using FullType = void const*; - LightType cache(BlockInfo const& _header); + LightType light(BlockInfo const& _header); bytesConstRef full(BlockInfo const& _header); static ethash_params params(BlockInfo const& _header); static ethash_params params(unsigned _n); @@ -99,7 +99,7 @@ private: static Ethasher* s_this; RecursiveMutex x_this; - std::map m_caches; + std::map m_lights; std::map m_fulls; }; diff --git a/libethereum/BlockChain.cpp b/libethereum/BlockChain.cpp index 8c0bd2b8b..74aeb4fad 100644 --- a/libethereum/BlockChain.cpp +++ b/libethereum/BlockChain.cpp @@ -19,10 +19,13 @@ * @date 2014 */ -#include - #include "BlockChain.h" +#if ETH_PROFILING_GPERF +#include +#endif +#include +#include #include #include #include @@ -33,6 +36,7 @@ #include #include #include +#include #include #include "GenesisInfo.h" #include "State.h" @@ -43,6 +47,7 @@ using namespace dev::eth; namespace js = json_spirit; #define ETH_CATCH 1 +#define ETH_TIMED_IMPORTS 0 std::ostream& dev::eth::operator<<(std::ostream& _out, BlockChain const& _bc) { @@ -63,7 +68,7 @@ std::ostream& dev::eth::operator<<(std::ostream& _out, BlockChain const& _bc) return _out; } -ldb::Slice dev::eth::toSlice(h256 const& _h, unsigned _sub) +ldb::Slice dev::eth::oldToSlice(h256 const& _h, unsigned _sub) { #if ALL_COMPILERS_ARE_CPP11_COMPLIANT static thread_local h256 h = _h ^ sha3(h256(u256(_sub))); @@ -77,6 +82,21 @@ ldb::Slice dev::eth::toSlice(h256 const& _h, unsigned _sub) #endif } +ldb::Slice dev::eth::toSlice(h256 const& _h, unsigned _sub) +{ +#if ALL_COMPILERS_ARE_CPP11_COMPLIANT + static thread_local h256 h = _h ^ sha3(h256(u256(_sub))); + return ldb::Slice((char const*)&h, 32); +#else + static boost::thread_specific_ptr> t_h; + if (!t_h.get()) + t_h.reset(new FixedHash<33>); + *t_h = FixedHash<33>(_h); + (*t_h)[32] = (uint8_t)_sub; + return (ldb::Slice)t_h->ref();//(char const*)t_h.get(), 32); +#endif +} + #if ETH_DEBUG static const chrono::system_clock::duration c_collectionDuration = chrono::seconds(15); static const unsigned c_collectionQueueSize = 2; @@ -98,7 +118,7 @@ static const unsigned c_minCacheSize = 1024 * 1024 * 32; #endif -BlockChain::BlockChain(bytes const& _genesisBlock, std::string _path, bool _killExisting) +BlockChain::BlockChain(bytes const& _genesisBlock, std::string _path, WithExisting _we, ProgressCallback const& _p) { // initialise deathrow. m_cacheUsage.resize(c_collectionQueueSize); @@ -108,7 +128,9 @@ BlockChain::BlockChain(bytes const& _genesisBlock, std::string _path, bool _kill m_genesisBlock = _genesisBlock; m_genesisHash = sha3(RLP(m_genesisBlock)[0].data()); - open(_path, _killExisting); + open(_path, _we); + if (_we == WithExisting::Verify) + rebuild(_path, _p); } BlockChain::~BlockChain() @@ -116,24 +138,23 @@ BlockChain::~BlockChain() close(); } -void BlockChain::open(std::string _path, bool _killExisting) +void BlockChain::open(std::string const& _path, WithExisting _we) { - if (_path.empty()) - _path = Defaults::get()->m_dbPath; - boost::filesystem::create_directories(_path); - if (_killExisting) + std::string path = _path.empty() ? Defaults::get()->m_dbPath : _path; + boost::filesystem::create_directories(path); + if (_we == WithExisting::Kill) { - boost::filesystem::remove_all(_path + "/blocks"); - boost::filesystem::remove_all(_path + "/details"); + boost::filesystem::remove_all(path + "/blocks"); + boost::filesystem::remove_all(path + "/details"); } ldb::Options o; o.create_if_missing = true; - ldb::DB::Open(o, _path + "/blocks", &m_blocksDB); - ldb::DB::Open(o, _path + "/details", &m_extrasDB); + ldb::DB::Open(o, path + "/blocks", &m_blocksDB); + ldb::DB::Open(o, path + "/details", &m_extrasDB); if (!m_blocksDB || !m_extrasDB) { - if (boost::filesystem::space(_path + "/blocks").available < 1024) + if (boost::filesystem::space(path + "/blocks").available < 1024) { cwarn << "Not enough available space found on hard drive. Please free some up and then re-run. Bailing."; BOOST_THROW_EXCEPTION(NotEnoughAvailableSpace()); @@ -145,7 +166,7 @@ void BlockChain::open(std::string _path, bool _killExisting) } } - if (!details(m_genesisHash)) + if (_we != WithExisting::Verify && !details(m_genesisHash)) { // Insert details of genesis block. m_details[m_genesisHash] = BlockDetails(0, c_genesisDifficulty, h256(), {}); @@ -153,12 +174,13 @@ void BlockChain::open(std::string _path, bool _killExisting) m_extrasDB->Put(m_writeOptions, toSlice(m_genesisHash, ExtraDetails), (ldb::Slice)dev::ref(r)); } +#if ETH_PARANOIA checkConsistency(); +#endif // TODO: Implement ability to rebuild details map from DB. std::string l; m_extrasDB->Get(m_readOptions, ldb::Slice("best"), &l); - m_lastBlockHash = l.empty() ? m_genesisHash : *(h256*)l.data(); cnote << "Opened blockchain DB. Latest: " << currentHash(); @@ -174,6 +196,84 @@ void BlockChain::close() m_blocks.clear(); } +#define IGNORE_EXCEPTIONS(X) try { X; } catch (...) {} + +void BlockChain::rebuild(std::string const& _path, std::function const& _progress) +{ +#if ETH_PROFILING_GPERF + ProfilerStart("BlockChain_rebuild.log"); +#endif + +// unsigned originalNumber = (unsigned)BlockInfo(oldBlock(m_lastBlockHash)).number; + unsigned originalNumber = number(); + + // Keep extras DB around, but under a temp name + delete m_extrasDB; + m_extrasDB = nullptr; + IGNORE_EXCEPTIONS(boost::filesystem::remove_all(_path + "/details.old")); + boost::filesystem::rename(_path + "/details", _path + "/details.old"); + ldb::DB* oldExtrasDB; + ldb::Options o; + o.create_if_missing = true; + ldb::DB::Open(o, _path + "/details.old", &oldExtrasDB); + ldb::DB::Open(o, _path + "/details", &m_extrasDB); + + // Open a fresh state DB + State s(State::openDB(_path, WithExisting::Kill), BaseState::CanonGenesis); + + // Clear all memos ready for replay. + m_details.clear(); + m_logBlooms.clear(); + m_receipts.clear(); + m_transactionAddresses.clear(); + m_blockHashes.clear(); + m_blocksBlooms.clear(); + m_lastLastHashes.clear(); + m_lastBlockHash = genesisHash(); + + h256 lastHash = genesisHash(); + boost::timer t; + for (unsigned d = 1; d < originalNumber; ++d) + { + if (!(d % 1000)) + { + cerr << "\n1000 blocks in " << t.elapsed() << "s = " << (1000.0 / t.elapsed()) << "b/s" << endl; + t.restart(); + } + try + { + bytes b = block(queryExtras(h256(u256(d)), m_blockHashes, x_blockHashes, NullBlockHash, oldExtrasDB).value); + + BlockInfo bi(b); + if (bi.number % c_ethashEpochLength == 1) + Ethasher::get()->full(bi); + + if (bi.parentHash != lastHash) + { + cwarn << "DISJOINT CHAIN DETECTED; " << bi.hash().abridged() << "#" << d << " -> parent is" << bi.parentHash.abridged() << "; expected" << lastHash.abridged() << "#" << (d - 1); + return; + } + lastHash = bi.hash(); + import(b, s.db(), true); + } + catch (...) + { + // Failed to import - stop here. + break; + } + + if (_progress) + _progress(d, originalNumber); + } + +#if ETH_PROFILING_GPERF + ProfilerStop(); +#endif + + delete oldExtrasDB; + boost::filesystem::remove_all(_path + "/details.old"); +} + template bool contains(T const& _t, V const& _v) { @@ -206,12 +306,12 @@ LastHashes BlockChain::lastHashes(unsigned _n) const return m_lastLastHashes; } -h256s BlockChain::sync(BlockQueue& _bq, OverlayDB const& _stateDB, unsigned _max) +pair BlockChain::sync(BlockQueue& _bq, OverlayDB const& _stateDB, unsigned _max) { _bq.tick(*this); vector blocks; - _bq.drain(blocks); + _bq.drain(blocks, _max); h256s ret; for (auto const& block: blocks) @@ -219,10 +319,7 @@ h256s BlockChain::sync(BlockQueue& _bq, OverlayDB const& _stateDB, unsigned _max try { for (auto h: import(block, _stateDB)) - if (!_max--) - break; - else - ret.push_back(h); + ret.push_back(h); } catch (UnknownParent) { @@ -237,15 +334,15 @@ h256s BlockChain::sync(BlockQueue& _bq, OverlayDB const& _stateDB, unsigned _max catch (...) {} } - _bq.doneDrain(); - return ret; + bool yetMore = _bq.doneDrain(); + return make_pair(ret, yetMore); } -h256s BlockChain::attemptImport(bytes const& _block, OverlayDB const& _stateDB) noexcept +h256s BlockChain::attemptImport(bytes const& _block, OverlayDB const& _stateDB, bool _force) noexcept { try { - return import(_block, _stateDB); + return import(_block, _stateDB, _force); } catch (...) { @@ -254,8 +351,20 @@ h256s BlockChain::attemptImport(bytes const& _block, OverlayDB const& _stateDB) } } -h256s BlockChain::import(bytes const& _block, OverlayDB const& _db) +h256s BlockChain::import(bytes const& _block, OverlayDB const& _db, bool _force) { + //@tidy This is a behemoth of a method - could do to be split into a few smaller ones. + +#if ETH_TIMED_IMPORTS + boost::timer total; + double preliminaryChecks; + double enactment; + double collation; + double writing; + double checkBest; + boost::timer t; +#endif + // VERIFY: populates from the block and checks the block is internally coherent. BlockInfo bi; @@ -282,7 +391,7 @@ h256s BlockChain::import(bytes const& _block, OverlayDB const& _db) auto newHash = BlockInfo::headerHash(_block); // Check block doesn't already exist first! - if (isKnown(newHash)) + if (isKnown(newHash) && !_force) { clog(BlockChainNote) << newHash << ": Not new."; BOOST_THROW_EXCEPTION(AlreadyHaveBlock()); @@ -315,6 +424,11 @@ h256s BlockChain::import(bytes const& _block, OverlayDB const& _db) clog(BlockChainNote) << "Attempting import of " << newHash.abridged() << "..."; +#if ETH_TIMED_IMPORTS + preliminaryChecks = t.elapsed(); + t.restart(); +#endif + u256 td; #if ETH_CATCH try @@ -322,8 +436,9 @@ h256s BlockChain::import(bytes const& _block, OverlayDB const& _db) { // 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(bi.coinbaseAddress, _db); + State s(_db); //, bi.coinbaseAddress auto tdIncrease = s.enactOn(&_block, bi, *this); + BlockLogBlooms blb; BlockReceipts br; for (unsigned i = 0; i < s.pending().size(); ++i) @@ -334,6 +449,11 @@ h256s BlockChain::import(bytes const& _block, OverlayDB const& _db) s.cleanup(true); td = pd.totalDifficulty + tdIncrease; +#if ETH_TIMED_IMPORTS + enactment = t.elapsed(); + t.restart(); +#endif + #if ETH_PARANOIA checkConsistency(); #endif @@ -350,37 +470,6 @@ h256s BlockChain::import(bytes const& _block, OverlayDB const& _db) m_details[newHash] = BlockDetails((unsigned)pd.number + 1, td, bi.parentHash, {}); m_details[bi.parentHash].children.push_back(newHash); } - { - WriteGuard l(x_blockHashes); - m_blockHashes[h256(bi.number)].value = newHash; - } - h256s alteredBlooms; - { - WriteGuard l(x_blocksBlooms); - LogBloom blockBloom = bi.logBloom; - blockBloom.shiftBloom<3>(sha3(bi.coinbaseAddress.ref())); - unsigned index = (unsigned)bi.number; - for (unsigned level = 0; level < c_bloomIndexLevels; level++, index /= c_bloomIndexSize) - { - unsigned i = index / c_bloomIndexSize; - unsigned o = index % c_bloomIndexSize; - alteredBlooms.push_back(chunkId(level, i)); - m_blocksBlooms[alteredBlooms.back()].blooms[o] |= blockBloom; - } - } - // Collate transaction hashes and remember who they were. - h256s newTransactionAddresses; - { - RLP blockRLP(_block); - TransactionAddress ta; - ta.blockHash = newHash; - WriteGuard l(x_transactionAddresses); - for (ta.index = 0; ta.index < blockRLP[1].itemCount(); ++ta.index) - { - newTransactionAddresses.push_back(sha3(blockRLP[1][ta.index].data())); - m_transactionAddresses[newTransactionAddresses.back()] = ta; - } - } { WriteGuard l(x_logBlooms); m_logBlooms[newHash] = blb; @@ -390,36 +479,48 @@ h256s BlockChain::import(bytes const& _block, OverlayDB const& _db) m_receipts[newHash] = br; } +#if ETH_TIMED_IMPORTS + collation = t.elapsed(); + t.restart(); +#endif + { - ReadGuard l1(x_blocksBlooms); ReadGuard l2(x_details); - ReadGuard l3(x_blockHashes); ReadGuard l4(x_receipts); ReadGuard l5(x_logBlooms); - ReadGuard l6(x_transactionAddresses); m_blocksDB->Put(m_writeOptions, toSlice(newHash), (ldb::Slice)ref(_block)); m_extrasDB->Put(m_writeOptions, toSlice(newHash, ExtraDetails), (ldb::Slice)dev::ref(m_details[newHash].rlp())); m_extrasDB->Put(m_writeOptions, toSlice(bi.parentHash, ExtraDetails), (ldb::Slice)dev::ref(m_details[bi.parentHash].rlp())); - m_extrasDB->Put(m_writeOptions, toSlice(h256(bi.number), ExtraBlockHash), (ldb::Slice)dev::ref(m_blockHashes[h256(bi.number)].rlp())); - for (auto const& h: newTransactionAddresses) - m_extrasDB->Put(m_writeOptions, toSlice(h, ExtraTransactionAddress), (ldb::Slice)dev::ref(m_transactionAddresses[h].rlp())); m_extrasDB->Put(m_writeOptions, toSlice(newHash, ExtraLogBlooms), (ldb::Slice)dev::ref(m_logBlooms[newHash].rlp())); m_extrasDB->Put(m_writeOptions, toSlice(newHash, ExtraReceipts), (ldb::Slice)dev::ref(m_receipts[newHash].rlp())); - for (auto const& h: alteredBlooms) - m_extrasDB->Put(m_writeOptions, toSlice(h, ExtraBlocksBlooms), (ldb::Slice)dev::ref(m_blocksBlooms[h].rlp())); } +#if ETH_TIMED_IMPORTS + writing = t.elapsed(); + t.restart(); +#endif + #if ETH_PARANOIA checkConsistency(); #endif } #if ETH_CATCH - catch (Exception const& _e) + catch (InvalidNonce const& _e) { clog(BlockChainNote) << " Malformed block: " << diagnostic_information(_e); _e << errinfo_comment("Malformed block "); throw; } + catch (Exception const& _e) + { + clog(BlockChainWarn) << " Malformed block: " << diagnostic_information(_e); + _e << errinfo_comment("Malformed block "); + clog(BlockChainWarn) << "Block: " << bi.hash(); + clog(BlockChainWarn) << bi; + clog(BlockChainWarn) << "Block parent: " << bi.parentHash; + clog(BlockChainWarn) << BlockInfo(block(bi.parentHash)); + throw; + } #endif StructuredLogger::chainReceivedNewBlock( @@ -436,16 +537,78 @@ h256s BlockChain::import(bytes const& _block, OverlayDB const& _db) h256 last = currentHash(); if (td > details(last).totalDifficulty) { - ret = treeRoute(last, newHash); + h256 common; + unsigned commonIndex; + tie(ret, common, commonIndex) = treeRoute(last, newHash); { WriteGuard l(x_lastBlockHash); m_lastBlockHash = newHash; } - noteCanonChanged(); - m_extrasDB->Put(m_writeOptions, ldb::Slice("best"), ldb::Slice((char const*)&newHash, 32)); + + // Most of the time these two will be equal - only when we're doing a chain revert will they not be + if (common != last) + // If we are reverting previous blocks, we need to clear their blooms (in particular, to + // rebuild any higher level blooms that they contributed to). + clearBlockBlooms(number(common) + 1, number(last) + 1); + + // Go through ret backwards until hash != last.parent and update m_transactionAddresses, m_blockHashes + for (auto i = ret.rbegin(); i != ret.rend() && *i != common; ++i) + { + auto b = block(*i); + BlockInfo bi(b); + // Collate logs into blooms. + h256s alteredBlooms; + { + LogBloom blockBloom = bi.logBloom; + blockBloom.shiftBloom<3>(sha3(bi.coinbaseAddress.ref())); + + // Pre-memoize everything we need before locking x_blocksBlooms + for (unsigned level = 0, index = (unsigned)bi.number; level < c_bloomIndexLevels; level++, index /= c_bloomIndexSize) + blocksBlooms(chunkId(level, index / c_bloomIndexSize)); + + WriteGuard l(x_blocksBlooms); + for (unsigned level = 0, index = (unsigned)bi.number; level < c_bloomIndexLevels; level++, index /= c_bloomIndexSize) + { + unsigned i = index / c_bloomIndexSize; + unsigned o = index % c_bloomIndexSize; + alteredBlooms.push_back(chunkId(level, i)); + m_blocksBlooms[alteredBlooms.back()].blooms[o] |= blockBloom; + } + } + // Collate transaction hashes and remember who they were. + h256s newTransactionAddresses; + { + RLP blockRLP(b); + TransactionAddress ta; + ta.blockHash = bi.hash(); + WriteGuard l(x_transactionAddresses); + for (ta.index = 0; ta.index < blockRLP[1].itemCount(); ++ta.index) + { + newTransactionAddresses.push_back(sha3(blockRLP[1][ta.index].data())); + m_transactionAddresses[newTransactionAddresses.back()] = ta; + } + } + { + WriteGuard l(x_blockHashes); + m_blockHashes[h256(bi.number)].value = bi.hash(); + } + + // Update database with them. + ReadGuard l1(x_blocksBlooms); + ReadGuard l3(x_blockHashes); + ReadGuard l6(x_transactionAddresses); + for (auto const& h: alteredBlooms) + m_extrasDB->Put(m_writeOptions, toSlice(h, ExtraBlocksBlooms), (ldb::Slice)dev::ref(m_blocksBlooms[h].rlp())); + m_extrasDB->Put(m_writeOptions, toSlice(h256(bi.number), ExtraBlockHash), (ldb::Slice)dev::ref(m_blockHashes[h256(bi.number)].rlp())); + for (auto const& h: newTransactionAddresses) + m_extrasDB->Put(m_writeOptions, toSlice(h, ExtraTransactionAddress), (ldb::Slice)dev::ref(m_transactionAddresses[h].rlp())); + } + clog(BlockChainNote) << " Imported and best" << td << ". Has" << (details(bi.parentHash).children.size() - 1) << "siblings. Route:" << toString(ret); + noteCanonChanged(); + StructuredLogger::chainNewHead( bi.headerHash(WithoutNonce).abridged(), bi.nonce.abridged(), @@ -457,19 +620,72 @@ h256s BlockChain::import(bytes const& _block, OverlayDB const& _db) { clog(BlockChainNote) << " Imported but not best (oTD:" << details(last).totalDifficulty << " > TD:" << td << ")"; } + +#if ETH_TIMED_IMPORTS + checkBest = t.elapsed(); + cnote << "Import took:" << total.elapsed(); + cnote << "preliminaryChecks:" << preliminaryChecks; + cnote << "enactment:" << enactment; + cnote << "collation:" << collation; + cnote << "writing:" << writing; + cnote << "checkBest:" << checkBest; +#endif + return ret; } -h256s BlockChain::treeRoute(h256 const& _from, h256 const& _to, h256* o_common, bool _pre, bool _post) const +void BlockChain::clearBlockBlooms(unsigned _begin, unsigned _end) { - cdebug << "treeRoute" << _from.abridged() << "..." << _to.abridged(); + // ... c c c c c c c c c c C o o o o o o + // ... /=15 /=21 + // L0...| ' | ' | ' | ' | ' | ' | ' | 'b|x'x|x'x|x'e| /=11 + // L1...| ' | ' | ' | ' b | x ' x | x ' e | /=6 + // L2...| ' | ' b | x ' x | e /=3 + // L3...| ' b | x ' e + // model: c_bloomIndexLevels = 4, c_bloomIndexSize = 2 + + // ... /=15 /=21 + // L0...| ' ' ' | ' ' ' | ' ' ' | ' ' 'b|x'x'x'x|x'e' ' | + // L1...| ' ' ' b | x ' x ' e ' | + // L2...| b ' x ' e ' | + // model: c_bloomIndexLevels = 2, c_bloomIndexSize = 4 + + // algorithm doesn't have the best memoisation coherence, but eh well... + + unsigned beginDirty = _begin; + unsigned endDirty = _end; + for (unsigned level = 0; level < c_bloomIndexLevels; level++, beginDirty /= c_bloomIndexSize, endDirty = (endDirty - 1) / c_bloomIndexSize + 1) + { + // compute earliest & latest index for each level, rebuild from previous levels. + for (unsigned item = beginDirty; item != endDirty; ++item) + { + unsigned bunch = item / c_bloomIndexSize; + unsigned offset = item % c_bloomIndexSize; + auto id = chunkId(level, bunch); + LogBloom acc; + if (!!level) + { + // rebuild the bloom from the previous (lower) level (if there is one). + auto lowerChunkId = chunkId(level - 1, item); + for (auto const& bloom: blocksBlooms(lowerChunkId).blooms) + acc |= bloom; + } + blocksBlooms(id); // make sure it has been memoized. + m_blocksBlooms[id].blooms[offset] = acc; + } + } +} + +tuple BlockChain::treeRoute(h256 const& _from, h256 const& _to, bool _common, bool _pre, bool _post) const +{ +// cdebug << "treeRoute" << _from.abridged() << "..." << _to.abridged(); if (!_from || !_to) - return h256s(); + return make_tuple(h256s(), h256(), 0); h256s ret; h256s back; unsigned fn = details(_from).number; unsigned tn = details(_to).number; - cdebug << "treeRoute" << fn << "..." << tn; +// cdebug << "treeRoute" << fn << "..." << tn; h256 from = _from; while (fn > tn) { @@ -477,7 +693,7 @@ h256s BlockChain::treeRoute(h256 const& _from, h256 const& _to, h256* o_common, ret.push_back(from); from = details(from).parent; fn--; - cdebug << "from:" << fn << _from.abridged(); +// cdebug << "from:" << fn << _from.abridged(); } h256 to = _to; while (fn < tn) @@ -486,7 +702,7 @@ h256s BlockChain::treeRoute(h256 const& _from, h256 const& _to, h256* o_common, back.push_back(to); to = details(to).parent; tn--; - cdebug << "to:" << tn << _to.abridged(); +// cdebug << "to:" << tn << _to.abridged(); } while (from != to) { @@ -496,20 +712,19 @@ h256s BlockChain::treeRoute(h256 const& _from, h256 const& _to, h256* o_common, assert(to); from = details(from).parent; to = details(to).parent; - if (_pre) + if (_pre && (from != to || _common)) ret.push_back(from); - if (_post) + if (_post && (from != to || (!_pre && _common))) back.push_back(to); fn--; tn--; // cdebug << "from:" << fn << _from.abridged() << "; to:" << tn << _to.abridged(); } - if (o_common) - *o_common = from; ret.reserve(ret.size() + back.size()); - for (auto it = back.cbegin(); it != back.cend(); ++it) + unsigned i = ret.size() - (int)(_common && !ret.empty() && !back.empty()); + for (auto it = back.rbegin(); it != back.rend(); ++it) ret.push_back(*it); - return ret; + return make_tuple(ret, from, i); } void BlockChain::noteUsed(h256 const& _h, unsigned _extra) const @@ -771,3 +986,33 @@ bytes BlockChain::block(h256 const& _hash) const return m_blocks[_hash]; } + +bytes BlockChain::oldBlock(h256 const& _hash) const +{ + if (_hash == m_genesisHash) + return m_genesisBlock; + + { + ReadGuard l(x_blocks); + auto it = m_blocks.find(_hash); + if (it != m_blocks.end()) + return it->second; + } + + string d; + m_blocksDB->Get(m_readOptions, oldToSlice(_hash), &d); + + if (!d.size()) + { + cwarn << "Couldn't find requested block:" << _hash.abridged(); + return bytes(); + } + + WriteGuard l(x_blocks); + m_blocks[_hash].resize(d.size()); + memcpy(m_blocks[_hash].data(), d.data(), d.size()); + + noteUsed(_hash); + + return m_blocks[_hash]; +} diff --git a/libethereum/BlockChain.h b/libethereum/BlockChain.h index 03c0fdcfd..83b1926e8 100644 --- a/libethereum/BlockChain.h +++ b/libethereum/BlockChain.h @@ -58,11 +58,13 @@ struct FutureTime: virtual Exception {}; struct BlockChainChat: public LogChannel { static const char* name() { return "-B-"; } static const int verbosity = 7; }; struct BlockChainNote: public LogChannel { static const char* name() { return "=B="; } static const int verbosity = 4; }; +struct BlockChainWarn: public LogChannel { static const char* name() { return "=B="; } static const int verbosity = 1; }; // TODO: Move all this Genesis stuff into Genesis.h/.cpp std::map const& genesisState(); ldb::Slice toSlice(h256 const& _h, unsigned _sub = 0); +ldb::Slice oldToSlice(h256 const& _h, unsigned _sub = 0); using BlocksHash = std::map; using TransactionHashes = h256s; @@ -77,6 +79,8 @@ enum { ExtraBlocksBlooms }; +using ProgressCallback = std::function; + /** * @brief Implements the blockchain database. All data this gives is disk-backed. * @threadsafe @@ -85,36 +89,38 @@ enum { class BlockChain { public: - BlockChain(bytes const& _genesisBlock, std::string _path, bool _killExisting); + BlockChain(bytes const& _genesisBlock, std::string _path, WithExisting _we, ProgressCallback const& _p = ProgressCallback()); ~BlockChain(); - void reopen(std::string _path, bool _killExisting = false) { close(); open(_path, _killExisting); } + /// Attempt a database re-open. + void reopen(std::string const& _path, WithExisting _we = WithExisting::Trust) { close(); open(_path, _we); } /// (Potentially) renders invalid existing bytesConstRef returned by lastBlock. /// To be called from main loop every 100ms or so. void process(); /// Sync the chain with any incoming blocks. All blocks should, if processed in order - h256s sync(BlockQueue& _bq, OverlayDB const& _stateDB, unsigned _max); + std::pair sync(BlockQueue& _bq, OverlayDB const& _stateDB, unsigned _max); /// Attempt to import the given block directly into the CanonBlockChain and sync with the state DB. /// @returns the block hashes of any blocks that came into/went out of the canonical block chain. - h256s attemptImport(bytes const& _block, OverlayDB const& _stateDB) noexcept; + h256s attemptImport(bytes const& _block, OverlayDB const& _stateDB, bool _force = false) noexcept; /// Import block into disk-backed DB /// @returns the block hashes of any blocks that came into/went out of the canonical block chain. - h256s import(bytes const& _block, OverlayDB const& _stateDB); + h256s import(bytes const& _block, OverlayDB const& _stateDB, bool _force = false); /// Returns true if the given block is known (though not necessarily a part of the canon chain). bool isKnown(h256 const& _hash) const; /// Get the familial details concerning a block (or the most recent mined if none given). Thread-safe. - BlockInfo info(h256 const& _hash) const { return BlockInfo(block(_hash)); } - BlockInfo info() const { return BlockInfo(block()); } + BlockInfo info(h256 const& _hash) const { return BlockInfo(block(_hash), IgnoreNonce, _hash); } + BlockInfo info() const { return info(currentHash()); } /// Get a block (RLP format) for the given hash (or the most recent mined if none given). Thread-safe. bytes block(h256 const& _hash) const; bytes block() const { return block(currentHash()); } + bytes oldBlock(h256 const& _hash) const; /// Get the familial details concerning a block (or the most recent mined if none given). Thread-safe. BlockDetails details(h256 const& _hash) const { return queryExtras(_hash, m_details, x_details, NullBlockDetails); } @@ -159,6 +165,7 @@ public: */ BlocksBlooms blocksBlooms(unsigned _level, unsigned _index) const { return blocksBlooms(chunkId(_level, _index)); } BlocksBlooms blocksBlooms(h256 const& _chunkId) const { return queryExtras(_chunkId, m_blocksBlooms, x_blocksBlooms, NullBlocksBlooms); } + void clearBlockBlooms(unsigned _begin, unsigned _end); LogBloom blockBloom(unsigned _number) const { return blocksBlooms(chunkId(0, _number / c_bloomIndexSize)).blooms[_number % c_bloomIndexSize]; } std::vector withBlockBloom(LogBloom const& _b, unsigned _earliest, unsigned _latest) const; std::vector withBlockBloom(LogBloom const& _b, unsigned _earliest, unsigned _latest, unsigned _topLevel, unsigned _index) const; @@ -186,21 +193,35 @@ public: /// togther with all their quoted uncles. h256Set allUnclesFrom(h256 const& _parent) const; - /** @returns the hash of all blocks between @a _from and @a _to, all blocks are ordered first by a number of - * blocks that are parent-to-child, then two sibling blocks, then a number of blocks that are child-to-parent. + /// Run through database and verify all blocks by reevaluating. + /// Will call _progress with the progress in this operation first param done, second total. + void rebuild(std::string const& _path, ProgressCallback const& _progress = std::function()); + + /** @returns a tuple of: + * - an vector of hashes of all blocks between @a _from and @a _to, all blocks are ordered first by a number of + * blocks that are parent-to-child, then two sibling blocks, then a number of blocks that are child-to-parent; + * - the block hash of the latest common ancestor of both blocks; + * - the index where the latest common ancestor of both blocks would either be found or inserted, depending + * on whether it is included. * - * If non-null, the h256 at @a o_common is set to the latest common ancestor of both blocks. + * @param _common if true, include the common ancestor in the returned vector. + * @param _pre if true, include all block hashes running from @a _from until the common ancestor in the returned vector. + * @param _post if true, include all block hashes running from the common ancestor until @a _to in the returned vector. * * e.g. if the block tree is 3a -> 2a -> 1a -> g and 2b -> 1b -> g (g is genesis, *a, *b are competing chains), * then: * @code - * treeRoute(3a, 2b) == { 3a, 2a, 1a, 1b, 2b }; // *o_common == g - * treeRoute(2a, 1a) == { 2a, 1a }; // *o_common == 1a - * treeRoute(1a, 2a) == { 1a, 2a }; // *o_common == 1a - * treeRoute(1b, 2a) == { 1b, 1a, 2a }; // *o_common == g + * treeRoute(3a, 2b, false) == make_tuple({ 3a, 2a, 1a, 1b, 2b }, g, 3); + * treeRoute(2a, 1a, false) == make_tuple({ 2a, 1a }, 1a, 1) + * treeRoute(1a, 2a, false) == make_tuple({ 1a, 2a }, 1a, 0) + * treeRoute(1b, 2a, false) == make_tuple({ 1b, 1a, 2a }, g, 1) + * treeRoute(3a, 2b, true) == make_tuple({ 3a, 2a, 1a, g, 1b, 2b }, g, 3); + * treeRoute(2a, 1a, true) == make_tuple({ 2a, 1a }, 1a, 1) + * treeRoute(1a, 2a, true) == make_tuple({ 1a, 2a }, 1a, 0) + * treeRoute(1b, 2a, true) == make_tuple({ 1b, g, 1a, 2a }, g, 1) * @endcode */ - h256s treeRoute(h256 const& _from, h256 const& _to, h256* o_common = nullptr, bool _pre = true, bool _post = true) const; + std::tuple treeRoute(h256 const& _from, h256 const& _to, bool _common = true, bool _pre = true, bool _post = true) const; struct Statistics { @@ -222,10 +243,34 @@ public: private: static h256 chunkId(unsigned _level, unsigned _index) { return h256(_index * 0xff + _level); } - void open(std::string _path, bool _killExisting = false); + void open(std::string const& _path, WithExisting _we = WithExisting::Trust); void close(); - template T queryExtras(h256 const& _h, std::map& _m, boost::shared_mutex& _x, T const& _n) const + template T queryExtras(h256 const& _h, std::map& _m, boost::shared_mutex& _x, T const& _n, ldb::DB* _extrasDB = nullptr) const + { + { + ReadGuard l(_x); + auto it = _m.find(_h); + if (it != _m.end()) + return it->second; + } + + std::string s; + (_extrasDB ? _extrasDB : m_extrasDB)->Get(m_readOptions, toSlice(_h, N), &s); + if (s.empty()) + { +// cout << "Not found in DB: " << _h << endl; + return _n; + } + + noteUsed(_h, N); + + WriteGuard l(_x); + auto ret = _m.insert(std::make_pair(_h, T(RLP(s)))); + return ret.first->second; + } + + template T oldQueryExtras(h256 const& _h, std::map& _m, boost::shared_mutex& _x, T const& _n, ldb::DB* _extrasDB = nullptr) const { { ReadGuard l(_x); @@ -235,7 +280,7 @@ private: } std::string s; - m_extrasDB->Get(m_readOptions, toSlice(_h, N), &s); + (_extrasDB ? _extrasDB : m_extrasDB)->Get(m_readOptions, oldToSlice(_h, N), &s); if (s.empty()) { // cout << "Not found in DB: " << _h << endl; diff --git a/libethereum/BlockQueue.cpp b/libethereum/BlockQueue.cpp index 29c9a4c71..69e387923 100644 --- a/libethereum/BlockQueue.cpp +++ b/libethereum/BlockQueue.cpp @@ -114,13 +114,29 @@ void BlockQueue::tick(BlockChain const& _bc) m_future.erase(m_future.begin(), m_future.upper_bound(t)); } -void BlockQueue::drain(std::vector& o_out) +template T advanced(T _t, unsigned _n) +{ + std::advance(_t, _n); + return _t; +} + +void BlockQueue::drain(std::vector& o_out, unsigned _max) { WriteGuard l(m_lock); if (m_drainingSet.empty()) { - swap(o_out, m_ready); - swap(m_drainingSet, m_readySet); + o_out.resize(min(_max, m_ready.size())); + for (unsigned i = 0; i < o_out.size(); ++i) + swap(o_out[i], m_ready[i]); + m_ready.erase(m_ready.begin(), advanced(m_ready.begin(), o_out.size())); + for (auto const& bs: o_out) + { + auto h = sha3(bs); + m_drainingSet.insert(h); + m_readySet.erase(h); + } +// swap(o_out, m_ready); +// swap(m_drainingSet, m_readySet); } } diff --git a/libethereum/BlockQueue.h b/libethereum/BlockQueue.h index 5eefa9d8e..f34a53812 100644 --- a/libethereum/BlockQueue.h +++ b/libethereum/BlockQueue.h @@ -26,6 +26,7 @@ #include #include #include +#include namespace dev { @@ -37,16 +38,6 @@ class BlockChain; struct BlockQueueChannel: public LogChannel { static const char* name() { return "[]Q"; } static const int verbosity = 4; }; #define cblockq dev::LogOutputStream() -enum class ImportResult -{ - Success = 0, - UnknownParent, - FutureTime, - AlreadyInChain, - AlreadyKnown, - Malformed -}; - /** * @brief A queue of blocks. Sits between network or other I/O and the BlockChain. * Sorts them ready for blockchain insertion (with the BlockChain::sync() method). @@ -61,12 +52,13 @@ public: /// Notes that time has moved on and some blocks that used to be "in the future" may no be valid. void tick(BlockChain const& _bc); - /// Grabs the blocks that are ready, giving them in the correct order for insertion into the chain. + /// Grabs at most @a _max of the blocks that are ready, giving them in the correct order for insertion into the chain. /// Don't forget to call doneDrain() once you're done importing. - void drain(std::vector& o_out); + void drain(std::vector& o_out, unsigned _max); /// Must be called after a drain() call. Notes that the drained blocks have been imported into the blockchain, so we can forget about them. - void doneDrain() { WriteGuard l(m_lock); m_drainingSet.clear(); } + /// @returns true iff there are additional blocks ready to be processed. + bool doneDrain() { WriteGuard l(m_lock); m_drainingSet.clear(); return !m_readySet.empty(); } /// Notify the queue that the chain has changed and a new block has attained 'ready' status (i.e. is in the chain). void noteReady(h256 _b) { WriteGuard l(m_lock); noteReadyWithoutWriteGuard(_b); } diff --git a/libethereum/CanonBlockChain.cpp b/libethereum/CanonBlockChain.cpp index 21b30d8ef..b5c47b653 100644 --- a/libethereum/CanonBlockChain.cpp +++ b/libethereum/CanonBlockChain.cpp @@ -92,6 +92,6 @@ bytes CanonBlockChain::createGenesisBlock() return block.out(); } -CanonBlockChain::CanonBlockChain(std::string _path, bool _killExisting): BlockChain(CanonBlockChain::createGenesisBlock(), _path, _killExisting) +CanonBlockChain::CanonBlockChain(std::string const& _path, WithExisting _we, ProgressCallback const& _pc): BlockChain(CanonBlockChain::createGenesisBlock(), _path, _we, _pc) { } diff --git a/libethereum/CanonBlockChain.h b/libethereum/CanonBlockChain.h index 7110dbc90..619af87eb 100644 --- a/libethereum/CanonBlockChain.h +++ b/libethereum/CanonBlockChain.h @@ -55,8 +55,8 @@ std::map const& genesisState(); class CanonBlockChain: public BlockChain { public: - CanonBlockChain(bool _killExisting = false): CanonBlockChain(std::string(), _killExisting) {} - CanonBlockChain(std::string _path, bool _killExisting = false); + CanonBlockChain(WithExisting _we = WithExisting::Trust, ProgressCallback const& _pc = ProgressCallback()): CanonBlockChain(std::string(), _we, _pc) {} + CanonBlockChain(std::string const& _path, WithExisting _we = WithExisting::Trust, ProgressCallback const& _pc = ProgressCallback()); ~CanonBlockChain() {} /// @returns the genesis block header. diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index 54f23dc84..71a9a4dbe 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -38,14 +38,30 @@ using namespace p2p; VersionChecker::VersionChecker(string const& _dbPath): m_path(_dbPath.size() ? _dbPath : Defaults::dbPath()) { - auto protocolContents = contents(m_path + "/protocol"); - auto databaseContents = contents(m_path + "/database"); - m_ok = RLP(protocolContents).toInt(RLP::LaisezFaire) == eth::c_protocolVersion && RLP(databaseContents).toInt(RLP::LaisezFaire) == c_databaseVersion; + bytes statusBytes = contents(m_path + "/status"); + RLP status(statusBytes); + try + { + auto protocolVersion = (unsigned)status[0]; + auto minorProtocolVersion = (unsigned)status[1]; + auto databaseVersion = (unsigned)status[2]; + m_action = + protocolVersion != eth::c_protocolVersion || databaseVersion != c_databaseVersion ? + WithExisting::Kill + : minorProtocolVersion != eth::c_minorProtocolVersion ? + WithExisting::Verify + : + WithExisting::Trust; + } + catch (...) + { + m_action = WithExisting::Kill; + } } void VersionChecker::setOk() { - if (!m_ok) + if (m_action != WithExisting::Trust) { try { @@ -55,8 +71,7 @@ void VersionChecker::setOk() { cwarn << "Unhandled exception! Failed to create directory: " << m_path << "\n" << boost::current_exception_diagnostic_information(); } - writeFile(m_path + "/protocol", rlp(eth::c_protocolVersion)); - writeFile(m_path + "/database", rlp(c_databaseVersion)); + writeFile(m_path + "/status", rlpList(eth::c_protocolVersion, eth::c_minorProtocolVersion, c_databaseVersion)); } } @@ -75,7 +90,7 @@ void BasicGasPricer::update(BlockChain const& _bc) { auto bb = _bc.block(p); RLP r(bb); - BlockReceipts brs(_bc.receipts(bi.hash)); + BlockReceipts brs(_bc.receipts(bi.hash())); for (unsigned i = 0; i < r[1].size(); ++i) { auto gu = brs.receipts[i].gasUsed(); @@ -102,14 +117,14 @@ void BasicGasPricer::update(BlockChain const& _bc) } } -Client::Client(p2p::Host* _extNet, std::string const& _dbPath, bool _forceClean, u256 _networkId, int _miners): +Client::Client(p2p::Host* _extNet, std::string const& _dbPath, WithExisting _forceAction, u256 _networkId, int _miners): Worker("eth"), m_vc(_dbPath), - m_bc(_dbPath, !m_vc.ok() || _forceClean), + m_bc(_dbPath, max(m_vc.action(), _forceAction), [](unsigned d, unsigned t){ cerr << "REVISING BLOCKCHAIN: Processed " << d << " of " << t << "...\r"; }), m_gp(new TrivialGasPricer), - m_stateDB(State::openDB(_dbPath, !m_vc.ok() || _forceClean)), - m_preMine(Address(), m_stateDB), - m_postMine(Address(), m_stateDB) + m_stateDB(State::openDB(_dbPath, max(m_vc.action(), _forceAction))), + m_preMine(m_stateDB, BaseState::CanonGenesis), + m_postMine(m_stateDB) { m_gp->update(m_bc); @@ -127,14 +142,14 @@ Client::Client(p2p::Host* _extNet, std::string const& _dbPath, bool _forceClean, startWorking(); } -Client::Client(p2p::Host* _extNet, std::shared_ptr _gp, std::string const& _dbPath, bool _forceClean, u256 _networkId, int _miners): +Client::Client(p2p::Host* _extNet, std::shared_ptr _gp, std::string const& _dbPath, WithExisting _forceAction, u256 _networkId, int _miners): Worker("eth"), m_vc(_dbPath), - m_bc(_dbPath, !m_vc.ok() || _forceClean), + m_bc(_dbPath, max(m_vc.action(), _forceAction), [](unsigned d, unsigned t){ cerr << "REVISING BLOCKCHAIN: Processed " << d << " of " << t << "...\r"; }), m_gp(_gp), - m_stateDB(State::openDB(_dbPath, !m_vc.ok() || _forceClean)), - m_preMine(Address(), m_stateDB), - m_postMine(Address(), m_stateDB) + m_stateDB(State::openDB(_dbPath, max(m_vc.action(), _forceAction))), + m_preMine(m_stateDB), + m_postMine(m_stateDB) { m_gp->update(m_bc); @@ -202,12 +217,12 @@ void Client::killChain() { WriteGuard l(x_stateDB); m_stateDB = OverlayDB(); - m_stateDB = State::openDB(Defaults::dbPath(), true); + m_stateDB = State::openDB(Defaults::dbPath(), WithExisting::Kill); } - m_bc.reopen(Defaults::dbPath(), true); + m_bc.reopen(Defaults::dbPath(), WithExisting::Kill); - m_preMine = State(Address(), m_stateDB); - m_postMine = State(Address(), m_stateDB); + m_preMine = State(m_stateDB); + m_postMine = State(m_stateDB); if (auto h = m_host.lock()) h->reset(); @@ -298,7 +313,7 @@ void Client::appendFromNewBlock(h256 const& _block, h256Set& io_changed) auto m = i.second.filter.matches(tr); if (m.size()) { - auto transactionHash = transaction(d.hash, j).sha3(); + auto transactionHash = transaction(d.hash(), j).sha3(); // filter catches them for (LogEntry const& l: m) i.second.changes.push_back(LocalisedLogEntry(l, (unsigned)d.number, transactionHash)); @@ -426,6 +441,8 @@ void Client::doWork() { // TODO: Use condition variable rather than polling. + bool stillGotWork = false; + cworkin << "WORK"; h256Set changeds; @@ -481,7 +498,10 @@ void Client::doWork() cwork << "BQ ==> CHAIN ==> STATE"; OverlayDB db = m_stateDB; x_stateDB.unlock(); - h256s newBlocks = m_bc.sync(m_bq, db, 100); // TODO: remove transactions from m_tq nicely rather than relying on out of date nonce later on. + h256s newBlocks; + bool sgw; + tie(newBlocks, sgw) = m_bc.sync(m_bq, db, 100); // TODO: remove transactions from m_tq nicely rather than relying on out of date nonce later on. + stillGotWork = stillGotWork | sgw; if (newBlocks.size()) { for (auto i: newBlocks) @@ -529,7 +549,9 @@ void Client::doWork() noteChanged(changeds); cworkout << "WORK"; - this_thread::sleep_for(chrono::milliseconds(100)); + if (!stillGotWork) + this_thread::sleep_for(chrono::milliseconds(100)); + if (chrono::system_clock::now() - m_lastGarbageCollection > chrono::seconds(5)) { // watches garbage collection @@ -586,7 +608,7 @@ void Client::inject(bytesConstRef _rlp) { startWorking(); - m_tq.attemptImport(_rlp); + m_tq.import(_rlp); } void Client::flushTransactions() diff --git a/libethereum/Client.h b/libethereum/Client.h index 1091bba58..f07e4c590 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -65,10 +65,10 @@ public: VersionChecker(std::string const& _dbPath); void setOk(); - bool ok() const { return m_ok; } + WithExisting action() const { return m_action; } private: - bool m_ok; + WithExisting m_action; std::string m_path; }; @@ -126,7 +126,7 @@ public: explicit Client( p2p::Host* _host, std::string const& _dbPath = std::string(), - bool _forceClean = false, + WithExisting _forceAction = WithExisting::Trust, u256 _networkId = 0, int _miners = -1 ); @@ -135,7 +135,7 @@ public: p2p::Host* _host, std::shared_ptr _gpForAdoption, // pass it in with new. std::string const& _dbPath = std::string(), - bool _forceClean = false, + WithExisting _forceAction = WithExisting::Trust, u256 _networkId = 0, int _miners = -1 ); diff --git a/libethereum/ClientBase.cpp b/libethereum/ClientBase.cpp index b45b9cf27..b9c2fa878 100644 --- a/libethereum/ClientBase.cpp +++ b/libethereum/ClientBase.cpp @@ -44,7 +44,7 @@ void ClientBase::submitTransaction(Secret _secret, u256 _value, Address _dest, b u256 n = postMine().transactionsFrom(toAddress(_secret)); Transaction t(_value, _gasPrice, _gas, _dest, _data, n, _secret); - m_tq.attemptImport(t.rlp()); + m_tq.import(t.rlp()); StructuredLogger::transactionReceived(t.sha3().abridged(), t.sender().abridged()); cnote << "New transaction " << t; @@ -56,7 +56,7 @@ Address ClientBase::submitTransaction(Secret _secret, u256 _endowment, bytes con u256 n = postMine().transactionsFrom(toAddress(_secret)); Transaction t(_endowment, _gasPrice, _gas, _init, n, _secret); - m_tq.attemptImport(t.rlp()); + m_tq.import(t.rlp()); StructuredLogger::transactionReceived(t.sha3().abridged(), t.sender().abridged()); cnote << "New transaction " << t; @@ -182,7 +182,7 @@ LocalisedLogEntries ClientBase::logs(LogFilter const& _f) const if (_f.matches(receipt.bloom())) { auto info = bc().info(h); - auto th = transaction(info.hash, i).sha3(); + auto th = transaction(info.hash(), i).sha3(); LogEntries le = _f.matches(receipt); if (le.size()) { @@ -265,7 +265,8 @@ LocalisedLogEntries ClientBase::peekWatch(unsigned _watchId) const // cwatch << "peekWatch" << _watchId; auto& w = m_watches.at(_watchId); // cwatch << "lastPoll updated to " << chrono::duration_cast(chrono::system_clock::now().time_since_epoch()).count(); - w.lastPoll = chrono::system_clock::now(); + if (w.lastPoll != chrono::system_clock::time_point::max()) + w.lastPoll = chrono::system_clock::now(); return w.changes; } @@ -278,8 +279,9 @@ LocalisedLogEntries ClientBase::checkWatch(unsigned _watchId) auto& w = m_watches.at(_watchId); // cwatch << "lastPoll updated to " << chrono::duration_cast(chrono::system_clock::now().time_since_epoch()).count(); std::swap(ret, w.changes); - w.lastPoll = chrono::system_clock::now(); - + if (w.lastPoll != chrono::system_clock::time_point::max()) + w.lastPoll = chrono::system_clock::now(); + return ret; } diff --git a/libethereum/EthereumPeer.cpp b/libethereum/EthereumPeer.cpp index f318a1757..ca0195efe 100644 --- a/libethereum/EthereumPeer.cpp +++ b/libethereum/EthereumPeer.cpp @@ -326,15 +326,27 @@ bool EthereumPeer::interpret(unsigned _id, RLP const& _r) case TransactionsPacket: { clogS(NetMessageSummary) << "Transactions (" << dec << _r.itemCount() << "entries)"; - addRating(_r.itemCount()); Guard l(x_knownTransactions); for (unsigned i = 0; i < _r.itemCount(); ++i) { auto h = sha3(_r[i].data()); m_knownTransactions.insert(h); - if (!host()->m_tq.import(_r[i].data())) + ImportResult ir = host()->m_tq.import(_r[i].data()); + switch (ir) + { + case ImportResult::Malformed: + addRating(-100); + break; + case ImportResult::AlreadyKnown: // if we already had the transaction, then don't bother sending it on. + addRating(0); + break; + case ImportResult::Success: + addRating(100); host()->m_transactionsSent.insert(h); + break; + default:; + } } break; } @@ -352,6 +364,7 @@ bool EthereumPeer::interpret(unsigned _id, RLP const& _r) for (unsigned i = 0; i < c && p; ++i, p = host()->m_chain.details(p).parent) s << p; sealAndSend(s); + addRating(0); break; } case BlockHashesPacket: @@ -370,6 +383,7 @@ bool EthereumPeer::interpret(unsigned _id, RLP const& _r) } for (unsigned i = 0; i < _r.itemCount(); ++i) { + addRating(1); auto h = _r[i].toHash(); if (host()->m_chain.isKnown(h)) { @@ -398,6 +412,7 @@ bool EthereumPeer::interpret(unsigned _id, RLP const& _r) ++n; } } + addRating(0); RLPStream s; prep(s, BlocksPacket, n).appendRaw(rlp, n); sealAndSend(s); @@ -463,7 +478,12 @@ bool EthereumPeer::interpret(unsigned _id, RLP const& _r) clogS(NetMessageSummary) << dec << success << "imported OK," << unknown << "with unknown parents," << future << "with future timestamps," << got << " already known," << repeated << " repeats received."; if (m_asking == Asking::Blocks) - transition(Asking::Blocks); + { + if (!got) + transition(Asking::Blocks); + else + transition(Asking::Nothing); + } break; } case NewBlockPacket: @@ -497,6 +517,7 @@ bool EthereumPeer::interpret(unsigned _id, RLP const& _r) setNeedsSyncing(h, _r[1].toInt()); break; } + Guard l(x_knownBlocks); m_knownBlocks.insert(h); } diff --git a/libethereum/Executive.h b/libethereum/Executive.h index 158e86330..3efdf6f0f 100644 --- a/libethereum/Executive.h +++ b/libethereum/Executive.h @@ -61,9 +61,9 @@ class Executive { public: /// Basic constructor. - Executive(State& _s, LastHashes const& _lh, unsigned _level): m_s(_s), m_lastHashes(_lh), m_depth(_level) {} + Executive(State& _s, LastHashes const& _lh, unsigned _level = 0): m_s(_s), m_lastHashes(_lh), m_depth(_level) {} /// Basic constructor. - Executive(State& _s, BlockChain const& _bc, unsigned _level); + Executive(State& _s, BlockChain const& _bc, unsigned _level = 0); /// Basic destructor. ~Executive() = default; diff --git a/libethereum/State.cpp b/libethereum/State.cpp index fa457dc41..a58ba9d0f 100644 --- a/libethereum/State.cpp +++ b/libethereum/State.cpp @@ -43,16 +43,17 @@ using namespace dev; using namespace dev::eth; #define ctrace clog(StateTrace) +#define ETH_TIMED_ENACTMENTS 0 static const u256 c_blockReward = 1500 * finney; -OverlayDB State::openDB(std::string _path, bool _killExisting) +OverlayDB State::openDB(std::string _path, WithExisting _we) { if (_path.empty()) _path = Defaults::get()->m_dbPath; boost::filesystem::create_directory(_path); - if (_killExisting) + if (_we == WithExisting::Kill) boost::filesystem::remove_all(_path + "/state"); ldb::Options o; @@ -77,23 +78,24 @@ OverlayDB State::openDB(std::string _path, bool _killExisting) return OverlayDB(db); } -State::State(Address _coinbaseAddress, OverlayDB const& _db, BaseState _bs): +State::State(OverlayDB const& _db, BaseState _bs, Address _coinbaseAddress): m_db(_db), m_state(&m_db), m_ourAddress(_coinbaseAddress), m_blockReward(c_blockReward) { - // Initialise to the state entailed by the genesis block; this guarantees the trie is built correctly. - m_state.init(); + if (_bs != BaseState::PreExisting) + // Initialise to the state entailed by the genesis block; this guarantees the trie is built correctly. + m_state.init(); - paranoia("beginning of normal construction.", true); + paranoia("beginning of Genesis construction.", true); if (_bs == BaseState::CanonGenesis) { dev::eth::commit(genesisState(), m_db, m_state); m_db.commit(); - paranoia("after DB commit of normal construction.", true); + paranoia("after DB commit of Genesis construction.", true); m_previousBlock = CanonBlockChain::genesis(); } else @@ -320,7 +322,7 @@ bool State::sync(BlockChain const& _bc, h256 _block, BlockInfo const& _bi) std::vector chain; while (bi.number != 0 && m_db.lookup(bi.stateRoot).empty()) // while we don't have the state root of the latest block... { - chain.push_back(bi.hash); // push back for later replay. + chain.push_back(bi.hash()); // push back for later replay. bi.populate(_bc.block(bi.parentHash)); // move to parent. } @@ -353,16 +355,48 @@ bool State::sync(BlockChain const& _bc, h256 _block, BlockInfo const& _bi) u256 State::enactOn(bytesConstRef _block, BlockInfo const& _bi, BlockChain const& _bc) { +#if ETH_TIMED_ENACTMENTS + boost::timer t; + double populateVerify; + double populateGrand; + double syncReset; + double enactment; +#endif + // Check family: BlockInfo biParent(_bc.block(_bi.parentHash)); _bi.verifyParent(biParent); + +#if ETH_TIMED_ENACTMENTS + populateVerify = t.elapsed(); + t.restart(); +#endif + BlockInfo biGrandParent; if (biParent.number) biGrandParent.populate(_bc.block(biParent.parentHash)); + +#if ETH_TIMED_ENACTMENTS + populateGrand = t.elapsed(); + t.restart(); +#endif + sync(_bc, _bi.parentHash); resetCurrent(); + +#if ETH_TIMED_ENACTMENTS + syncReset = t.elapsed(); + t.restart(); +#endif + m_previousBlock = biParent; - return enact(_block, _bc); + auto ret = enact(_block, _bc); + +#if ETH_TIMED_ENACTMENTS + enactment = t.elapsed(); + cnote << "popVer/popGrand/syncReset/enactment = " << populateVerify << "/" << populateGrand << "/" << syncReset << "/" << enactment; +#endif + return ret; } map State::addresses() const @@ -504,18 +538,18 @@ u256 State::enact(bytesConstRef _block, BlockChain const& _bc, bool _checkNonce) { // m_currentBlock is assumed to be prepopulated and reset. -#if !ETH_RELEASE BlockInfo bi(_block, _checkNonce ? CheckEverything : IgnoreNonce); - assert(m_previousBlock.hash == bi.parentHash); +#if !ETH_RELEASE + assert(m_previousBlock.hash() == bi.parentHash); assert(m_currentBlock.parentHash == bi.parentHash); assert(rootHash() == m_previousBlock.stateRoot); #endif - if (m_currentBlock.parentHash != m_previousBlock.hash) + if (m_currentBlock.parentHash != m_previousBlock.hash()) BOOST_THROW_EXCEPTION(InvalidParentHash()); // Populate m_currentBlock with the correct values. - m_currentBlock.populate(_block, _checkNonce ? CheckEverything : IgnoreNonce); + m_currentBlock = bi; m_currentBlock.verifyInternals(_block); // cnote << "playback begins:" << m_state.root(); @@ -565,7 +599,7 @@ u256 State::enact(bytesConstRef _block, BlockChain const& _bc, bool _checkNonce) cwarn << TransactionReceipt(&b); } cwarn << "Recorded: " << m_currentBlock.receiptsRoot; - auto rs = _bc.receipts(m_currentBlock.hash); + auto rs = _bc.receipts(m_currentBlock.hash()); for (unsigned j = 0; j < rs.receipts.size(); ++j) { auto b = rs.receipts[j].rlp(); @@ -807,7 +841,7 @@ void State::commitToMine(BlockChain const& _bc) m_currentBlock.gasUsed = gasUsed(); m_currentBlock.stateRoot = m_state.root(); - m_currentBlock.parentHash = m_previousBlock.hash; + m_currentBlock.parentHash = m_previousBlock.hash(); } MineInfo State::mine(unsigned _msTimeout, bool _turbo) @@ -857,10 +891,10 @@ void State::completeMine() ret.appendRaw(m_currentTxs); ret.appendRaw(m_currentUncles); ret.swapOut(m_currentBytes); - m_currentBlock.hash = sha3(RLP(m_currentBytes)[0].data()); - cnote << "Mined " << m_currentBlock.hash.abridged() << "(parent: " << m_currentBlock.parentHash.abridged() << ")"; + m_currentBlock.noteDirty(); + cnote << "Mined " << m_currentBlock.hash().abridged() << "(parent: " << m_currentBlock.parentHash.abridged() << ")"; StructuredLogger::minedNewBlock( - m_currentBlock.hash.abridged(), + m_currentBlock.hash().abridged(), m_currentBlock.nonce.abridged(), "", //TODO: chain head hash here ?? m_currentBlock.parentHash.abridged() diff --git a/libethereum/State.h b/libethereum/State.h index 5ed76cc27..662426e17 100644 --- a/libethereum/State.h +++ b/libethereum/State.h @@ -54,7 +54,12 @@ struct StateTrace: public LogChannel { static const char* name() { return "=S="; struct StateDetail: public LogChannel { static const char* name() { return "/S/"; } static const int verbosity = 14; }; struct StateSafeExceptions: public LogChannel { static const char* name() { return "(S)"; } static const int verbosity = 21; }; -enum class BaseState { Empty, CanonGenesis }; +enum class BaseState +{ + PreExisting, + Empty, + CanonGenesis +}; enum class TransactionPriority { @@ -103,8 +108,15 @@ class State friend class Executive; public: - /// Construct state object. - State(Address _coinbaseAddress = Address(), OverlayDB const& _db = OverlayDB(), BaseState _bs = BaseState::CanonGenesis); + /// Default constructor; creates with a blank database prepopulated with the genesis block. + State(): State(OverlayDB(), BaseState::Empty) {} + + /// Basic state object from database. + /// Use the default when you already have a database and you just want to make a State object + /// which uses it. If you have no preexisting database then set BaseState to something other + /// than BaseState::PreExisting in order to prepopulate the Trie. + /// You can also set the coinbase address. + explicit State(OverlayDB const& _db, BaseState _bs = BaseState::PreExisting, Address _coinbaseAddress = Address()); /// Construct state object from arbitrary point in blockchain. State(OverlayDB const& _db, BlockChain const& _bc, h256 _hash); @@ -123,8 +135,8 @@ public: Address address() const { return m_ourAddress; } /// Open a DB - useful for passing into the constructor & keeping for other states that are necessary. - static OverlayDB openDB(std::string _path, bool _killExisting = false); - static OverlayDB openDB(bool _killExisting = false) { return openDB(std::string(), _killExisting); } + static OverlayDB openDB(std::string _path, WithExisting _we = WithExisting::Trust); + static OverlayDB openDB(WithExisting _we = WithExisting::Trust) { return openDB(std::string(), _we); } OverlayDB const& db() const { return m_db; } /// @returns the set containing all addresses currently in use in Ethereum. @@ -335,6 +347,7 @@ private: /// Debugging only. Good for checking the Trie is in shape. void paranoia(std::string const& _when, bool _enforceRefs = false) const; + OverlayDB m_db; ///< Our overlay for the state tree. SecureTrieDB m_state; ///< Our state tree, as an OverlayDB DB. Transactions m_transactions; ///< The current list of transactions that we've included in the state. diff --git a/libethereum/TransactionQueue.cpp b/libethereum/TransactionQueue.cpp index 803d320ee..26962ed90 100644 --- a/libethereum/TransactionQueue.cpp +++ b/libethereum/TransactionQueue.cpp @@ -28,14 +28,16 @@ using namespace std; using namespace dev; using namespace dev::eth; -bool TransactionQueue::import(bytesConstRef _transactionRLP) +ImportResult TransactionQueue::import(bytesConstRef _transactionRLP) { // Check if we already know this transaction. h256 h = sha3(_transactionRLP); UpgradableGuard l(m_lock); + // TODO: keep old transactions around and check in State for nonce validity + if (m_known.count(h)) - return false; + return ImportResult::AlreadyKnown; try { @@ -52,15 +54,15 @@ bool TransactionQueue::import(bytesConstRef _transactionRLP) catch (Exception const& _e) { cwarn << "Ignoring invalid transaction: " << diagnostic_information(_e); - return false; + return ImportResult::Malformed; } catch (std::exception const& _e) { cwarn << "Ignoring invalid transaction: " << _e.what(); - return false; + return ImportResult::Malformed; } - return true; + return ImportResult::Success; } void TransactionQueue::setFuture(std::pair const& _t) diff --git a/libethereum/TransactionQueue.h b/libethereum/TransactionQueue.h index b104b98ca..cf40e1209 100644 --- a/libethereum/TransactionQueue.h +++ b/libethereum/TransactionQueue.h @@ -23,8 +23,8 @@ #include #include -#include "libethcore/Common.h" #include +#include "libethcore/Common.h" #include "Transaction.h" namespace dev @@ -34,6 +34,7 @@ namespace eth class BlockChain; + /** * @brief A queue of Transactions, each stored as RLP. * @threadsafe @@ -41,9 +42,8 @@ class BlockChain; class TransactionQueue { public: - bool attemptImport(bytesConstRef _tx) { try { import(_tx); return true; } catch (...) { return false; } } - bool attemptImport(bytes const& _tx) { return attemptImport(&_tx); } - bool import(bytesConstRef _tx); + ImportResult import(bytes const& _tx) { return import(&_tx); } + ImportResult import(bytesConstRef _tx); void drop(h256 _txHash); diff --git a/libevm/VM.h b/libevm/VM.h index b14a117df..1cf06b78b 100644 --- a/libevm/VM.h +++ b/libevm/VM.h @@ -56,7 +56,7 @@ public: virtual bytesConstRef go(ExtVMFace& _ext, OnOpFunc const& _onOp = {}, uint64_t _steps = (uint64_t)-1) override final; - void require(u256 _n, u256 _d) { if (m_stack.size() < _n) { if (m_onFail) m_onFail(); BOOST_THROW_EXCEPTION(StackUnderflow() << RequirementError((bigint)_n, (bigint)m_stack.size())); } if (m_stack.size() - _n + _d >= c_stackLimit) { if (m_onFail) m_onFail(); BOOST_THROW_EXCEPTION(OutOfStack() << RequirementError((bigint)(_d - _n), (bigint)m_stack.size())); } } + void require(u256 _n, u256 _d) { if (m_stack.size() < _n) { if (m_onFail) m_onFail(); BOOST_THROW_EXCEPTION(StackUnderflow() << RequirementError((bigint)_n, (bigint)m_stack.size())); } if (m_stack.size() - _n + _d > c_stackLimit) { if (m_onFail) m_onFail(); BOOST_THROW_EXCEPTION(OutOfStack() << RequirementError((bigint)(_d - _n), (bigint)m_stack.size())); } } void requireMem(unsigned _n) { if (m_temp.size() < _n) { m_temp.resize(_n); } } u256 curPC() const { return m_curPC; } diff --git a/libp2p/Capability.cpp b/libp2p/Capability.cpp index dccc130cd..f59fd8cdd 100644 --- a/libp2p/Capability.cpp +++ b/libp2p/Capability.cpp @@ -53,7 +53,7 @@ void Capability::sealAndSend(RLPStream& _s) m_session->sealAndSend(_s); } -void Capability::addRating(unsigned _r) +void Capability::addRating(int _r) { m_session->addRating(_r); } diff --git a/libp2p/Capability.h b/libp2p/Capability.h index ad8127bb5..d09391655 100644 --- a/libp2p/Capability.h +++ b/libp2p/Capability.h @@ -52,7 +52,7 @@ protected: RLPStream& prep(RLPStream& _s, unsigned _id, unsigned _args = 0); void sealAndSend(RLPStream& _s); - void addRating(unsigned _r); + void addRating(int _r); private: Session* m_session; diff --git a/libp2p/Host.cpp b/libp2p/Host.cpp index 45ebd6db1..e49baa1be 100644 --- a/libp2p/Host.cpp +++ b/libp2p/Host.cpp @@ -182,10 +182,8 @@ void Host::startPeerSession(Public const& _id, RLP const& _rlp, RLPXFrameIO* _io } else p = m_peers[_id]; - p->m_lastDisconnect = NoDisconnect; if (p->isOffline()) p->m_lastConnected = std::chrono::system_clock::now(); - p->m_failedAttempts = 0; p->endpoint.tcp.address(_endpoint.address()); auto protocolVersion = _rlp[0].toInt(); @@ -219,16 +217,19 @@ void Host::startPeerSession(Public const& _id, RLP const& _rlp, RLPXFrameIO* _io ps->disconnect(DuplicatePeer); return; } + + // todo: mutex Session::m_capabilities and move for(:caps) out of mutex. + unsigned o = (unsigned)UserPacket; + for (auto const& i: caps) + if (haveCapability(i)) + { + ps->m_capabilities[i] = shared_ptr(m_capabilities[i]->newPeerCapability(ps.get(), o)); + o += m_capabilities[i]->messageCount(); + } + ps->start(); m_sessions[_id] = ps; } - ps->start(); - unsigned o = (unsigned)UserPacket; - for (auto const& i: caps) - if (haveCapability(i)) - { - ps->m_capabilities[i] = shared_ptr(m_capabilities[i]->newPeerCapability(ps.get(), o)); - o += m_capabilities[i]->messageCount(); - } + clog(NetNote) << "p2p.host.peer.register" << _id.abridged(); StructuredLogger::p2pConnected(_id.abridged(), ps->m_peer->peerEndpoint(), ps->m_peer->m_lastConnected, clientVersion, peerCount()); } @@ -484,12 +485,14 @@ void Host::connect(std::shared_ptr const& _p) auto socket = make_shared(new bi::tcp::socket(m_ioService)); socket->ref().async_connect(_p->peerEndpoint(), [=](boost::system::error_code const& ec) { + _p->m_lastAttempted = std::chrono::system_clock::now(); + _p->m_failedAttempts++; + if (ec) { clog(NetConnect) << "Connection refused to node" << _p->id.abridged() << "@" << _p->peerEndpoint() << "(" << ec.message() << ")"; + // Manually set error (session not present) _p->m_lastDisconnect = TCPError; - _p->m_lastAttempted = std::chrono::system_clock::now(); - _p->m_failedAttempts++; } else { @@ -499,9 +502,7 @@ void Host::connect(std::shared_ptr const& _p) Guard l(x_connecting); m_connecting.push_back(handshake); } - - // preempt setting failedAttempts; this value is cleared upon success - _p->m_failedAttempts++; + handshake->start(); } diff --git a/libp2p/Peer.cpp b/libp2p/Peer.cpp index 4be0fd799..e37953e12 100644 --- a/libp2p/Peer.cpp +++ b/libp2p/Peer.cpp @@ -44,6 +44,7 @@ unsigned Peer::fallbackSeconds() const return 30 * (m_failedAttempts + 1); case UselessPeer: case TooManyPeers: + return 25 * (m_failedAttempts + 1); case ClientQuit: return 15 * (m_failedAttempts + 1); case NoDisconnect: diff --git a/libp2p/Peer.h b/libp2p/Peer.h index bfa2eaeb6..cb9155bbc 100644 --- a/libp2p/Peer.h +++ b/libp2p/Peer.h @@ -75,6 +75,9 @@ public: /// Reason peer was previously disconnected. DisconnectReason lastDisconnect() const { return m_lastDisconnect; } + /// Peer session is noted as useful. + void noteSessionGood() { m_failedAttempts = 0; } + protected: /// Returns number of seconds to wait until attempting connection, based on attempted connection history. unsigned fallbackSeconds() const; diff --git a/libp2p/Session.cpp b/libp2p/Session.cpp index dac588149..0bf07bbe1 100644 --- a/libp2p/Session.cpp +++ b/libp2p/Session.cpp @@ -46,6 +46,7 @@ Session::Session(Host* _s, RLPXFrameIO* _io, std::shared_ptr const& _n, Pe m_info(_info), m_ping(chrono::steady_clock::time_point::max()) { + m_peer->m_lastDisconnect = NoDisconnect; m_lastReceived = m_connect = chrono::steady_clock::now(); } @@ -76,12 +77,14 @@ NodeId Session::id() const return m_peer ? m_peer->id : NodeId(); } -void Session::addRating(unsigned _r) +void Session::addRating(int _r) { if (m_peer) { m_peer->m_rating += _r; m_peer->m_score += _r; + if (_r >= 0) + m_peer->noteSessionGood(); } } @@ -358,16 +361,11 @@ void Session::drop(DisconnectReason _reason) } catch (...) {} - if (m_peer) + m_peer->m_lastDisconnect = _reason; + if (_reason == BadProtocol) { - if (_reason != m_peer->m_lastDisconnect || _reason == NoDisconnect || _reason == ClientQuit || _reason == DisconnectRequested) - m_peer->m_failedAttempts = 0; - m_peer->m_lastDisconnect = _reason; - if (_reason == BadProtocol) - { - m_peer->m_rating /= 2; - m_peer->m_score /= 2; - } + m_peer->m_rating /= 2; + m_peer->m_score /= 2; } m_dropped = true; } diff --git a/libp2p/Session.h b/libp2p/Session.h index 51db5adc3..95053d2a9 100644 --- a/libp2p/Session.h +++ b/libp2p/Session.h @@ -74,7 +74,7 @@ public: void sealAndSend(RLPStream& _s); int rating() const; - void addRating(unsigned _r); + void addRating(int _r); void addNote(std::string const& _k, std::string const& _v) { m_info.notes[_k] = _v; } diff --git a/libtestutils/BlockChainLoader.cpp b/libtestutils/BlockChainLoader.cpp index 898812b2a..ba0def59e 100644 --- a/libtestutils/BlockChainLoader.cpp +++ b/libtestutils/BlockChainLoader.cpp @@ -35,7 +35,7 @@ BlockChainLoader::BlockChainLoader(Json::Value const& _json) m_state = sl.state(); // load genesisBlock - m_bc.reset(new BlockChain(fromHex(_json["genesisRLP"].asString()), m_dir.path(), true)); + m_bc.reset(new BlockChain(fromHex(_json["genesisRLP"].asString()), m_dir.path(), WithExisting::Kill)); // load blocks for (auto const& block: _json["blocks"]) diff --git a/libtestutils/CMakeLists.txt b/libtestutils/CMakeLists.txt index 4ae52e0c9..3ac4f34f8 100644 --- a/libtestutils/CMakeLists.txt +++ b/libtestutils/CMakeLists.txt @@ -32,7 +32,10 @@ endif() target_link_libraries(${EXECUTABLE} ${Boost_FILESYSTEM_LIBRARIES}) target_link_libraries(${EXECUTABLE} ${JSONCPP_LIBRARIES}) target_link_libraries(${EXECUTABLE} ethereum) -target_link_libraries(${EXECUTABLE} web3jsonrpc) + +if (JSONRPC) + target_link_libraries(${EXECUTABLE} web3jsonrpc) +endif() install( TARGETS ${EXECUTABLE} RUNTIME DESTINATION bin ARCHIVE DESTINATION lib LIBRARY DESTINATION lib ) install( FILES ${HEADERS} DESTINATION include/${EXECUTABLE} ) diff --git a/libtestutils/StateLoader.cpp b/libtestutils/StateLoader.cpp index b56475b5a..464df0ec5 100644 --- a/libtestutils/StateLoader.cpp +++ b/libtestutils/StateLoader.cpp @@ -26,7 +26,7 @@ using namespace dev; using namespace dev::eth; using namespace dev::test; -StateLoader::StateLoader(Json::Value const& _json) : m_state(Address(), OverlayDB(), BaseState::Empty) +StateLoader::StateLoader(Json::Value const& _json) { for (string const& name: _json.getMemberNames()) { diff --git a/libweb3jsonrpc/CMakeLists.txt b/libweb3jsonrpc/CMakeLists.txt index d3f4b70b6..7b47a3f47 100644 --- a/libweb3jsonrpc/CMakeLists.txt +++ b/libweb3jsonrpc/CMakeLists.txt @@ -22,9 +22,9 @@ file(GLOB HEADERS "*.h") if (ETH_STATIC) add_library(${EXECUTABLE} STATIC ${SRC_LIST} ${HEADERS}) -else() +else () add_library(${EXECUTABLE} SHARED ${SRC_LIST} ${HEADERS}) -endif() +endif () target_link_libraries(${EXECUTABLE} ${LEVELDB_LIBRARIES}) target_link_libraries(${EXECUTABLE} ${JSONCPP_LIBRARIES}) @@ -33,11 +33,13 @@ target_link_libraries(${EXECUTABLE} ${MHD_LIBRARIES}) target_link_libraries(${EXECUTABLE} webthree) target_link_libraries(${EXECUTABLE} secp256k1) -target_link_libraries(${EXECUTABLE} solidity) -if (NOT ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")) +if (SOLIDITY) + target_link_libraries(${EXECUTABLE} solidity) +endif () +if (SERPENT) target_link_libraries(${EXECUTABLE} serpent) -endif() +endif () if (ETH_JSON_RPC_STUB) add_custom_target(jsonrpcstub) @@ -51,7 +53,7 @@ if (ETH_JSON_RPC_STUB) -P "${ETH_SCRIPTS_DIR}/jsonrpcstub.cmake" ) add_dependencies(${EXECUTABLE} jsonrpcstub) -endif() +endif () install( TARGETS ${EXECUTABLE} RUNTIME DESTINATION bin ARCHIVE DESTINATION lib LIBRARY DESTINATION lib ) install( FILES ${HEADERS} DESTINATION include/${EXECUTABLE} ) diff --git a/libweb3jsonrpc/WebThreeStubServer.cpp b/libweb3jsonrpc/WebThreeStubServer.cpp index 8b24edf38..30a634b3d 100644 --- a/libweb3jsonrpc/WebThreeStubServer.cpp +++ b/libweb3jsonrpc/WebThreeStubServer.cpp @@ -44,6 +44,11 @@ WebThreeStubServer::WebThreeStubServer(jsonrpc::AbstractServerConnector& _conn, ldb::DB::Open(o, path, &m_db); } +std::string WebThreeStubServer::web3_clientVersion() +{ + return m_web3.clientVersion(); +} + dev::eth::Interface* WebThreeStubServer::client() { return m_web3.ethereum(); diff --git a/libweb3jsonrpc/WebThreeStubServer.h b/libweb3jsonrpc/WebThreeStubServer.h index 106842981..08991d2f5 100644 --- a/libweb3jsonrpc/WebThreeStubServer.h +++ b/libweb3jsonrpc/WebThreeStubServer.h @@ -42,7 +42,9 @@ class WebThreeStubServer: public dev::WebThreeStubServerBase, public dev::WebThr { public: WebThreeStubServer(jsonrpc::AbstractServerConnector& _conn, dev::WebThreeDirect& _web3, std::vector const& _accounts); - + + virtual std::string web3_clientVersion(); + private: virtual dev::eth::Interface* client() override; virtual std::shared_ptr face() override; diff --git a/libweb3jsonrpc/WebThreeStubServerBase.cpp b/libweb3jsonrpc/WebThreeStubServerBase.cpp index 7524e7519..da0108b3e 100644 --- a/libweb3jsonrpc/WebThreeStubServerBase.cpp +++ b/libweb3jsonrpc/WebThreeStubServerBase.cpp @@ -26,9 +26,11 @@ #include #include +#if ETH_SOLIDITY #include #include #include +#endif #include #include #include @@ -36,7 +38,7 @@ #include #include #include -#ifndef _MSC_VER +#if ETH_SERPENT #include #endif #include "WebThreeStubServerBase.h" @@ -57,58 +59,70 @@ const unsigned dev::SensibleHttpPort = 8080; static Json::Value toJson(dev::eth::BlockInfo const& _bi) { Json::Value res; - res["hash"] = toJS(_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"] = toJS(_bi.number); - res["gasUsed"] = toJS(_bi.gasUsed); - res["gasLimit"] = toJS(_bi.gasLimit); - res["timestamp"] = toJS(_bi.timestamp); - res["extraData"] = toJS(_bi.extraData); - res["nonce"] = toJS(_bi.nonce); - res["logsBloom"] = toJS(_bi.logBloom); + if (_bi) + { + res["hash"] = toJS(_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"] = toJS(_bi.number); + res["gasUsed"] = toJS(_bi.gasUsed); + res["gasLimit"] = toJS(_bi.gasLimit); + res["timestamp"] = toJS(_bi.timestamp); + res["extraData"] = toJS(_bi.extraData); + res["nonce"] = toJS(_bi.nonce); + res["logsBloom"] = toJS(_bi.logBloom); + } return res; } static Json::Value toJson(dev::eth::Transaction const& _t) { Json::Value res; - res["hash"] = toJS(_t.sha3()); - res["input"] = toJS(_t.data()); - res["to"] = _t.isCreation() ? Json::Value() : toJS(_t.receiveAddress()); - res["from"] = toJS(_t.safeSender()); - res["gas"] = toJS(_t.gas()); - res["gasPrice"] = toJS(_t.gasPrice()); - res["nonce"] = toJS(_t.nonce()); - res["value"] = toJS(_t.value()); + if (_t) + { + res["hash"] = toJS(_t.sha3()); + res["input"] = toJS(_t.data()); + res["to"] = _t.isCreation() ? Json::Value() : toJS(_t.receiveAddress()); + res["from"] = toJS(_t.safeSender()); + res["gas"] = toJS(_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::BlockInfo const& _bi, UncleHashes const& _us, Transactions const& _ts) { Json::Value res = toJson(_bi); - res["uncles"] = Json::Value(Json::arrayValue); - for (h256 h: _us) - res["uncles"].append(toJS(h)); - res["transactions"] = Json::Value(Json::arrayValue); - for (Transaction const& t: _ts) - res["transactions"].append(toJson(t)); + if (_bi) + { + res["uncles"] = Json::Value(Json::arrayValue); + for (h256 h: _us) + res["uncles"].append(toJS(h)); + res["transactions"] = Json::Value(Json::arrayValue); + for (Transaction const& t: _ts) + res["transactions"].append(toJson(t)); + } return res; } static Json::Value toJson(dev::eth::BlockInfo const& _bi, UncleHashes const& _us, TransactionHashes const& _ts) { Json::Value res = toJson(_bi); - res["uncles"] = Json::Value(Json::arrayValue); - for (h256 h: _us) - res["uncles"].append(toJS(h)); - res["transactions"] = Json::Value(Json::arrayValue); - for (h256 const& t: _ts) - res["transactions"].append(toJS(t)); + if (_bi) + { + res["uncles"] = Json::Value(Json::arrayValue); + for (h256 h: _us) + res["uncles"].append(toJS(h)); + res["transactions"] = Json::Value(Json::arrayValue); + for (h256 const& t: _ts) + res["transactions"].append(toJS(t)); + } return res; } @@ -284,6 +298,11 @@ bool WebThreeStubServerBase::net_listening() return network()->isNetworkStarted(); } +string WebThreeStubServerBase::eth_protocolVersion() +{ + return toJS(eth::c_protocolVersion); +} + string WebThreeStubServerBase::eth_coinbase() { return toJS(client()->address()); @@ -603,8 +622,10 @@ Json::Value WebThreeStubServerBase::eth_getCompilers() { Json::Value ret(Json::arrayValue); ret.append("lll"); +#if SOLIDITY ret.append("solidity"); -#ifndef _MSC_VER +#endif +#if SERPENT ret.append("serpent"); #endif return ret; @@ -625,7 +646,8 @@ string WebThreeStubServerBase::eth_compileSerpent(string const& _code) { // TODO throw here jsonrpc errors string res; -#ifndef _MSC_VER + (void)_code; +#if SERPENT try { res = toJS(dev::asBytes(::compile(_code))); @@ -645,7 +667,9 @@ string WebThreeStubServerBase::eth_compileSerpent(string const& _code) string WebThreeStubServerBase::eth_compileSolidity(string const& _code) { // TOOD throw here jsonrpc errors + (void)_code; string res; +#if SOLIDITY dev::solidity::CompilerStack compiler; try { @@ -661,6 +685,7 @@ string WebThreeStubServerBase::eth_compileSolidity(string const& _code) { cwarn << "Uncought solidity compilation exception"; } +#endif return res; } diff --git a/libweb3jsonrpc/WebThreeStubServerBase.h b/libweb3jsonrpc/WebThreeStubServerBase.h index 40265ac10..b57a54c87 100644 --- a/libweb3jsonrpc/WebThreeStubServerBase.h +++ b/libweb3jsonrpc/WebThreeStubServerBase.h @@ -77,6 +77,7 @@ public: virtual std::string net_peerCount(); virtual bool net_listening(); + virtual std::string eth_protocolVersion(); virtual std::string eth_coinbase(); virtual bool eth_mining(); virtual std::string eth_gasPrice(); diff --git a/libweb3jsonrpc/abstractwebthreestubserver.h b/libweb3jsonrpc/abstractwebthreestubserver.h index 516817d5c..6cc5de3e6 100644 --- a/libweb3jsonrpc/abstractwebthreestubserver.h +++ b/libweb3jsonrpc/abstractwebthreestubserver.h @@ -17,6 +17,7 @@ class AbstractWebThreeStubServer : public jsonrpc::AbstractServerbindAndAddMethod(jsonrpc::Procedure("net_version", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::net_versionI); this->bindAndAddMethod(jsonrpc::Procedure("net_peerCount", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::net_peerCountI); this->bindAndAddMethod(jsonrpc::Procedure("net_listening", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_BOOLEAN, NULL), &AbstractWebThreeStubServer::net_listeningI); + this->bindAndAddMethod(jsonrpc::Procedure("eth_protocolVersion", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_protocolVersionI); this->bindAndAddMethod(jsonrpc::Procedure("eth_coinbase", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_coinbaseI); this->bindAndAddMethod(jsonrpc::Procedure("eth_mining", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_BOOLEAN, NULL), &AbstractWebThreeStubServer::eth_miningI); this->bindAndAddMethod(jsonrpc::Procedure("eth_gasPrice", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_gasPriceI); @@ -92,6 +93,11 @@ class AbstractWebThreeStubServer : public jsonrpc::AbstractServernet_listening(); } + inline virtual void eth_protocolVersionI(const Json::Value &request, Json::Value &response) + { + (void)request; + response = this->eth_protocolVersion(); + } inline virtual void eth_coinbaseI(const Json::Value &request, Json::Value &response) { (void)request; @@ -302,6 +308,7 @@ class AbstractWebThreeStubServer : public jsonrpc::AbstractServer const& _interfaces, NetworkPreferences const& _n, bytesConstRef _network, int _miners @@ -50,7 +50,7 @@ WebThreeDirect::WebThreeDirect( if (_dbPath.size()) Defaults::setDBPath(_dbPath); if (_interfaces.count("eth")) - m_ethereum.reset(new eth::Client(&m_net, _dbPath, _forceClean, 0, _miners)); + m_ethereum.reset(new eth::Client(&m_net, _dbPath, _we, 0, _miners)); if (_interfaces.count("shh")) m_whisper = m_net.registerCapability(new WhisperHost); diff --git a/libwebthree/WebThree.h b/libwebthree/WebThree.h index a0e5cc666..92feb8d40 100644 --- a/libwebthree/WebThree.h +++ b/libwebthree/WebThree.h @@ -25,17 +25,13 @@ #include #include #include - -// Make sure boost/asio.hpp is included before windows.h. -#include +#include // Make sure boost/asio.hpp is included before windows.h. #include - #include #include #include #include #include - #include #include @@ -113,7 +109,7 @@ public: WebThreeDirect( std::string const& _clientVersion, std::string const& _dbPath, - bool _forceClean = false, + WithExisting _we = WithExisting::Trust, std::set const& _interfaces = {"eth", "shh"}, p2p::NetworkPreferences const& _n = p2p::NetworkPreferences(), bytesConstRef _network = bytesConstRef(), @@ -131,6 +127,8 @@ public: // Misc stuff: + std::string const& clientVersion() const { return m_clientVersion; } + void setClientVersion(std::string const& _name) { m_clientVersion = _name; } // Network stuff: diff --git a/libwhisper/WhisperHost.cpp b/libwhisper/WhisperHost.cpp index 633eef5b5..97a24e112 100644 --- a/libwhisper/WhisperHost.cpp +++ b/libwhisper/WhisperHost.cpp @@ -162,7 +162,7 @@ void WhisperHost::uninstallWatch(unsigned _i) void WhisperHost::doWork() { - for (auto& i: peerSessions()) + for (auto i: peerSessions()) i.first->cap().get()->sendMessages(); cleanup(); } diff --git a/mix/CMakeLists.txt b/mix/CMakeLists.txt index d57dafcac..6a434534f 100644 --- a/mix/CMakeLists.txt +++ b/mix/CMakeLists.txt @@ -29,7 +29,9 @@ else() qt5_add_resources(UI_RESOURCES noweb.qrc) endif() -add_definitions(-DQT_QML_DEBUG) +if (CMAKE_BUILD_TYPE MATCHES Debug) + add_definitions(-DQT_QML_DEBUG) +endif() # eth_add_executable is defined in cmake/EthExecutableHelper.cmake eth_add_executable(${EXECUTABLE} @@ -37,33 +39,15 @@ eth_add_executable(${EXECUTABLE} UI_RESOURCES ${UI_RESOURCES} ) -target_link_libraries(${EXECUTABLE} Qt5::Core) -target_link_libraries(${EXECUTABLE} Qt5::Gui) -target_link_libraries(${EXECUTABLE} Qt5::Widgets) -target_link_libraries(${EXECUTABLE} Qt5::Network) -target_link_libraries(${EXECUTABLE} Qt5::Quick) -target_link_libraries(${EXECUTABLE} Qt5::Qml) -target_link_libraries(${EXECUTABLE} webthree) -target_link_libraries(${EXECUTABLE} ethereum) -target_link_libraries(${EXECUTABLE} evm) -target_link_libraries(${EXECUTABLE} ethcore) -target_link_libraries(${EXECUTABLE} devcrypto) -target_link_libraries(${EXECUTABLE} secp256k1) -if (NOT ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")) -target_link_libraries(${EXECUTABLE} serpent) -endif() -target_link_libraries(${EXECUTABLE} lll) -target_link_libraries(${EXECUTABLE} solidity) -target_link_libraries(${EXECUTABLE} evmcore) -target_link_libraries(${EXECUTABLE} devcore) -target_link_libraries(${EXECUTABLE} jsqrc) -target_link_libraries(${EXECUTABLE} web3jsonrpc) +set(LIBRARIES "Qt5::Core;Qt5::Gui;Qt5::Widgets;Qt5::Network;Qt5::Quick;Qt5::Qml;webthree;ethereum;evm;ethcore;devcrypto;solidity;evmcore;devcore;jsqrc;web3jsonrpc") if (${ETH_HAVE_WEBENGINE}) add_definitions(-DETH_HAVE_WEBENGINE) - target_link_libraries(${EXECUTABLE} Qt5::WebEngine) + list(APPEND LIBRARIES "Qt5::WebEngine") endif() +target_link_libraries(${EXECUTABLE} ${LIBRARIES}) + # eth_install_executable is defined in cmake/EthExecutableHelper.cmake eth_install_executable(${EXECUTABLE} QMLDIR ${CMAKE_CURRENT_SOURCE_DIR}/qml @@ -71,5 +55,22 @@ eth_install_executable(${EXECUTABLE} #add qml asnd stdc files to project tree in Qt creator file(GLOB_RECURSE QMLFILES "qml/*.*") +file(GLOB_RECURSE TESTFILES "test/qml/*.*") file(GLOB_RECURSE SOLFILES "stdc/*.*") -add_custom_target(mix_qml SOURCES ${QMLFILES} ${SOLFILES}) +add_custom_target(mix_qml SOURCES ${QMLFILES} ${SOLFILES} ${TESTFILES}) + +#test target +find_package(Qt5QuickTest REQUIRED) +find_package(Qt5Test REQUIRED) +set(TEST_EXECUTABLE mix_test) +list(APPEND LIBRARIES "Qt5::QuickTest") +list(APPEND LIBRARIES "Qt5::Test") +list(REMOVE_ITEM SRC_LIST "./main.cpp") +aux_source_directory(test SRC_LIST) +file(GLOB HEADERS "test/*.h") +add_executable(${TEST_EXECUTABLE} ${UI_RESOURCES} ${SRC_LIST} ${HEADERS}) +target_link_libraries(${TEST_EXECUTABLE} ${LIBRARIES}) +set_target_properties(${TEST_EXECUTABLE} PROPERTIES EXCLUDE_FROM_ALL 1 EXCLUDE_FROM_DEFAULT_BUILD 1) + + + diff --git a/mix/ClientModel.cpp b/mix/ClientModel.cpp index b6139d949..cf573965e 100644 --- a/mix/ClientModel.cpp +++ b/mix/ClientModel.cpp @@ -91,6 +91,7 @@ ClientModel::ClientModel(): ClientModel::~ClientModel() { + m_runFuture.waitForFinished(); } QString ClientModel::apiCall(QString const& _message) @@ -114,7 +115,7 @@ void ClientModel::mine() m_mining = true; emit miningStarted(); emit miningStateChanged(); - QtConcurrent::run([=]() + m_runFuture = QtConcurrent::run([=]() { try { @@ -139,6 +140,12 @@ QString ClientModel::newAddress() return QString::fromStdString(toHex(a.secret().ref())); } +QString ClientModel::encodeAbiString(QString _string) +{ + ContractCallDataEncoder encoder; + return QString::fromStdString(toHex(encoder.encodeBytes(_string))); +} + QVariantMap ClientModel::contractAddresses() const { QVariantMap res; @@ -207,7 +214,7 @@ void ClientModel::executeSequence(vector const& _sequence, emit runStateChanged(); //run sequence - QtConcurrent::run([=]() + m_runFuture = QtConcurrent::run([=]() { try { @@ -512,7 +519,7 @@ RecordLogEntry* ClientModel::lastBlock() const strGas << blockInfo.gasUsed; stringstream strNumber; strNumber << blockInfo.number; - RecordLogEntry* record = new RecordLogEntry(0, QString::fromStdString(strNumber.str()), tr(" - Block - "), tr("Hash: ") + QString(QString::fromStdString(toHex(blockInfo.hash.ref()))), tr("Gas Used: ") + QString::fromStdString(strGas.str()), QString(), QString(), false, RecordLogEntry::RecordType::Block); + RecordLogEntry* record = new RecordLogEntry(0, QString::fromStdString(strNumber.str()), tr(" - Block - "), tr("Hash: ") + QString(QString::fromStdString(toHex(blockInfo.hash().ref()))), tr("Gas Used: ") + QString::fromStdString(strGas.str()), QString(), QString(), false, RecordLogEntry::RecordType::Block); QQmlEngine::setObjectOwnership(record, QQmlEngine::JavaScriptOwnership); return record; } diff --git a/mix/ClientModel.h b/mix/ClientModel.h index 8c090f355..4b597a1ea 100644 --- a/mix/ClientModel.h +++ b/mix/ClientModel.h @@ -27,6 +27,7 @@ #include #include #include +#include #include "MachineStates.h" namespace dev @@ -154,7 +155,10 @@ public slots: Q_INVOKABLE void debugRecord(unsigned _index); /// Show the debugger for an empty record Q_INVOKABLE void emptyRecord(); + /// Generate new adress Q_INVOKABLE QString newAddress(); + /// Encode a string to ABI parameter. Returns a hex string + Q_INVOKABLE QString encodeAbiString(QString _string); private slots: /// Update UI with machine states result. Display a modal dialog. @@ -204,6 +208,7 @@ private: std::atomic m_running; std::atomic m_mining; + QFuture m_runFuture; std::unique_ptr m_client; std::unique_ptr m_rpcConnector; std::unique_ptr m_web3Server; diff --git a/mix/ContractCallDataEncoder.cpp b/mix/ContractCallDataEncoder.cpp index 2b673a53b..56aeb1d34 100644 --- a/mix/ContractCallDataEncoder.cpp +++ b/mix/ContractCallDataEncoder.cpp @@ -134,11 +134,6 @@ unsigned ContractCallDataEncoder::encodeSingleItem(QString const& _data, Solidit return dataSize; } -void ContractCallDataEncoder::push(bytes const& _b) -{ - m_encodedData.insert(m_encodedData.end(), _b.begin(), _b.end()); -} - bigint ContractCallDataEncoder::decodeInt(dev::bytes const& _rawValue) { dev::u256 un = dev::fromBigEndian(_rawValue); diff --git a/mix/ContractCallDataEncoder.h b/mix/ContractCallDataEncoder.h index 6c27f9c57..805f26691 100644 --- a/mix/ContractCallDataEncoder.h +++ b/mix/ContractCallDataEncoder.h @@ -52,8 +52,10 @@ public: QVariant decode(SolidityType const& _type, bytes const& _value); /// Get all encoded data encoded by encode function. bytes encodedData(); - /// Push the given @a _b to the current param context. - void push(bytes const& _b); + /// Encode a string to ABI bytes + dev::bytes encodeBytes(QString const& _str); + /// Decode bytes from ABI + dev::bytes decodeBytes(dev::bytes const& _rawValue); private: unsigned encodeSingleItem(QString const& _data, SolidityType const& _type, bytes& _dest); @@ -63,8 +65,6 @@ private: dev::bytes encodeBool(QString const& _str); bool decodeBool(dev::bytes const& _rawValue); QString toString(bool _b); - dev::bytes encodeBytes(QString const& _str); - dev::bytes decodeBytes(dev::bytes const& _rawValue); QString toString(dev::bytes const& _b); bool asString(dev::bytes const& _b, QString& _str); diff --git a/mix/FileIo.cpp b/mix/FileIo.cpp index 58668cad6..6d3d9c1c3 100644 --- a/mix/FileIo.cpp +++ b/mix/FileIo.cpp @@ -196,6 +196,7 @@ QStringList FileIo::makePackage(QString const& _deploymentFolder) QStringList ret; ret.append(QString::fromStdString(toHex(dappHash.ref()))); ret.append(qFileBytes.toBase64()); + ret.append(url.toString()); return ret; } diff --git a/mix/MixApplication.cpp b/mix/MixApplication.cpp index d1e7fdb86..54c860a8d 100644 --- a/mix/MixApplication.cpp +++ b/mix/MixApplication.cpp @@ -24,6 +24,7 @@ #include #include #include +#include #ifdef ETH_HAVE_WEBENGINE #include #endif @@ -36,23 +37,54 @@ #include "Clipboard.h" #include "HttpServer.h" +extern int qInitResources_js(); using namespace dev::mix; +ApplicationService::ApplicationService() +{ +#ifdef ETH_HAVE_WEBENGINE + QtWebEngine::initialize(); +#endif + QFont f; + m_systemPointSize = f.pointSize(); +} + MixApplication::MixApplication(int& _argc, char* _argv[]): QApplication(_argc, _argv), m_engine(new QQmlApplicationEngine()) { + m_engine->load(QUrl("qrc:/qml/Application.qml")); + if (!m_engine->rootObjects().empty()) + { + QWindow *window = qobject_cast(m_engine->rootObjects().at(0)); + if (window) + window->setIcon(QIcon(":/res/mix_256x256x32.png")); + } +} + +void MixApplication::initialize() +{ +#if __linux + //work around ubuntu appmenu-qt5 bug + //https://bugs.launchpad.net/ubuntu/+source/appmenu-qt5/+bug/1323853 + putenv((char*)"QT_QPA_PLATFORMTHEME="); + putenv((char*)"QSG_RENDER_LOOP=threaded"); +#endif +#if (defined(_WIN32) || defined(_WIN64)) + if (!getenv("OPENSSL_CONF")) + putenv((char*)"OPENSSL_CONF=c:\\"); +#endif +#ifdef ETH_HAVE_WEBENGINE + qInitResources_js(); +#endif + setOrganizationName(tr("Ethereum")); setOrganizationDomain(tr("ethereum.org")); setApplicationName(tr("Mix")); setApplicationVersion("0.1"); -#ifdef ETH_HAVE_WEBENGINE - QtWebEngine::initialize(); -#endif - QFont f; - m_engine->rootContext()->setContextProperty("systemPointSize", f.pointSize()); qmlRegisterType("org.ethereum.qml.CodeModel", 1, 0, "CodeModel"); qmlRegisterType("org.ethereum.qml.ClientModel", 1, 0, "ClientModel"); + qmlRegisterType("org.ethereum.qml.ApplicationService", 1, 0, "ApplicationService"); qmlRegisterType("org.ethereum.qml.FileIo", 1, 0, "FileIo"); qmlRegisterType("org.ethereum.qml.QEther", 1, 0, "QEther"); qmlRegisterType("org.ethereum.qml.QBigInt", 1, 0, "QBigInt"); @@ -64,10 +96,7 @@ MixApplication::MixApplication(int& _argc, char* _argv[]): qmlRegisterType("HttpServer", 1, 0, "HttpServer"); qRegisterMetaType("CodeModel*"); qRegisterMetaType("ClientModel*"); - - m_engine->load(QUrl("qrc:/qml/main.qml")); - QWindow *window = qobject_cast(m_engine->rootObjects().at(0)); - window->setIcon(QIcon(":/res/mix_256x256x32.png")); + qRegisterMetaType("ClientModel*"); } MixApplication::~MixApplication() diff --git a/mix/MixApplication.h b/mix/MixApplication.h index 136cd8cf0..c1fd73d66 100644 --- a/mix/MixApplication.h +++ b/mix/MixApplication.h @@ -33,12 +33,33 @@ namespace dev namespace mix { +class ApplicationService: public QObject +{ + Q_OBJECT + Q_PROPERTY(int systemPointSize READ systemPointSize CONSTANT) + Q_PROPERTY(bool haveWebEngine READ haveWebEngine CONSTANT) + +public: + ApplicationService(); + int systemPointSize() const { return m_systemPointSize; } +#ifdef ETH_HAVE_WEBENGINE + bool haveWebEngine() const { return true; } +#else + bool haveWebEngine() const { return false; } +#endif + +private: + int m_systemPointSize = 0; +}; + + class MixApplication: public QApplication { Q_OBJECT public: MixApplication(int& _argc, char* _argv[]); + static void initialize(); virtual ~MixApplication(); QQmlApplicationEngine* engine() { return m_engine.get(); } bool notify(QObject* _receiver, QEvent* _event) override; diff --git a/mix/MixClient.cpp b/mix/MixClient.cpp index fadc8a45a..58cab151d 100644 --- a/mix/MixClient.cpp +++ b/mix/MixClient.cpp @@ -93,7 +93,7 @@ void MixClient::resetState(std::map _accounts) h256 stateRoot = accountState.root(); m_bc.reset(); m_bc.reset(new MixBlockChain(m_dbPath, stateRoot)); - m_state = eth::State(genesisState.begin()->first , m_stateDB, BaseState::Empty); + m_state = eth::State(m_stateDB, BaseState::PreExisting, genesisState.begin()->first); m_state.sync(bc()); m_startState = m_state; WriteGuard lx(x_executions); diff --git a/mix/MixClient.h b/mix/MixClient.h index 179b445ac..abd9445c7 100644 --- a/mix/MixClient.h +++ b/mix/MixClient.h @@ -37,7 +37,7 @@ namespace mix class MixBlockChain: public dev::eth::BlockChain { public: - MixBlockChain(std::string const& _path, h256 _stateRoot): BlockChain(createGenesisBlock(_stateRoot), _path, true) {} + MixBlockChain(std::string const& _path, h256 _stateRoot): BlockChain(createGenesisBlock(_stateRoot), _path, WithExisting::Kill) {} static bytes createGenesisBlock(h256 _stateRoot); }; diff --git a/mix/QBasicNodeDefinition.cpp b/mix/QBasicNodeDefinition.cpp new file mode 100644 index 000000000..8905caef6 --- /dev/null +++ b/mix/QBasicNodeDefinition.cpp @@ -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 QBasicNodeDefinition.cpp + * @author Yann yann@ethdev.com + * @date 2014 + */ + +#include "QBasicNodeDefinition.h" +#include + +namespace dev +{ +namespace mix +{ + +QBasicNodeDefinition::QBasicNodeDefinition(QObject* _parent, solidity::Declaration const* _d): + QObject(_parent), m_name(QString::fromStdString(_d->getName())) +{ +} + +QBasicNodeDefinition::QBasicNodeDefinition(QObject* _parent, std::string const& _name): + QObject(_parent), m_name(QString::fromStdString(_name)) +{ +} + +} +} diff --git a/mix/QBasicNodeDefinition.h b/mix/QBasicNodeDefinition.h index 42f8ac15d..eb81d4f85 100644 --- a/mix/QBasicNodeDefinition.h +++ b/mix/QBasicNodeDefinition.h @@ -21,11 +21,17 @@ #pragma once +#include #include -#include namespace dev { + +namespace solidity +{ +class Declaration; +} + namespace mix { @@ -37,8 +43,8 @@ class QBasicNodeDefinition: public QObject public: QBasicNodeDefinition(QObject* _parent = nullptr): QObject(_parent) {} ~QBasicNodeDefinition() {} - QBasicNodeDefinition(QObject* _parent, solidity::Declaration const* _d): QObject(_parent), m_name(QString::fromStdString(_d->getName())) {} - QBasicNodeDefinition(QObject* _parent, std::string const& _name): QObject(_parent), m_name(QString::fromStdString(_name)) {} + QBasicNodeDefinition(QObject* _parent, solidity::Declaration const* _d); + QBasicNodeDefinition(QObject* _parent, std::string const& _name); /// Get the name of the node. QString name() const { return m_name; } diff --git a/mix/QVariableDeclaration.cpp b/mix/QVariableDeclaration.cpp index 391d26c04..3bbb0d523 100644 --- a/mix/QVariableDeclaration.cpp +++ b/mix/QVariableDeclaration.cpp @@ -21,6 +21,7 @@ */ #include "QVariableDeclaration.h" +#include #include "CodeModel.h" namespace dev diff --git a/mix/QVariableDefinition.cpp b/mix/QVariableDefinition.cpp new file mode 100644 index 000000000..4553ffad6 --- /dev/null +++ b/mix/QVariableDefinition.cpp @@ -0,0 +1,37 @@ +/* + 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 QVariableDefinition.cpp + * @author Yann yann@ethdev.com + * @date 2014 + */ + +#include "QVariableDefinition.h" +#include + +namespace dev +{ +namespace mix +{ + +QString QVariableDefinition::encodeValueAsString() +{ + return QString::fromStdString(dev::toHex(encodeValue())); +} + +} +} + diff --git a/mix/QVariableDefinition.h b/mix/QVariableDefinition.h index 70a663ee3..e6c38f971 100644 --- a/mix/QVariableDefinition.h +++ b/mix/QVariableDefinition.h @@ -21,14 +21,14 @@ #pragma once -#include -#include "QBigInt.h" -#include "QVariableDeclaration.h" +#include +#include namespace dev { namespace mix { +class QVariableDeclaration; class QVariableDefinition: public QObject { @@ -54,7 +54,7 @@ public: /// Decode the return value @a _rawValue. virtual void decodeValue(dev::bytes const& _rawValue) = 0; /// returns String representation of the encoded value. - Q_INVOKABLE QString encodeValueAsString() { return QString::fromStdString(dev::toHex(encodeValue())); } + Q_INVOKABLE QString encodeValueAsString(); protected: QString m_value; diff --git a/mix/Web3Server.cpp b/mix/Web3Server.cpp index 3de2d7ef8..4acb262df 100644 --- a/mix/Web3Server.cpp +++ b/mix/Web3Server.cpp @@ -23,13 +23,93 @@ #include #include #include +#include #include "Web3Server.h" using namespace dev::mix; +using namespace dev; + +namespace +{ +class EmptyNetwork : public dev::WebThreeNetworkFace +{ + std::vector peers() override + { + return std::vector(); + } + + size_t peerCount() const override + { + return 0; + } + + void addNode(p2p::NodeId const& _node, bi::tcp::endpoint const& _hostEndpoint) override + { + (void)_node; + (void)_hostEndpoint; + } + + void requirePeer(p2p::NodeId const& _node, bi::tcp::endpoint const& _endpoint) override + { + (void)_node; + (void)_endpoint; + } + + dev::bytes saveNetwork() override + { + return dev::bytes(); + } + + void setIdealPeerCount(size_t _n) override + { + (void)_n; + } + + bool haveNetwork() const override + { + return false; + } + + void setNetworkPreferences(p2p::NetworkPreferences const& _n, bool _dropPeers) override + { + (void)_n; + (void)_dropPeers; + } + + p2p::NodeId id() const override + { + return p2p::NodeId(); + } + + p2p::Peers nodes() const override + { + return p2p::Peers(); + } + + void startNetwork() override + { + } + + void stopNetwork() override + { + } + + bool isNetworkStarted() const override + { + return false; + } +}; + +} Web3Server::Web3Server(jsonrpc::AbstractServerConnector& _conn, std::vector const& _accounts, dev::eth::Interface* _client): WebThreeStubServerBase(_conn, _accounts), - m_client(_client) + m_client(_client), + m_network(new EmptyNetwork()) +{ +} + +Web3Server::~Web3Server() { } @@ -40,7 +120,7 @@ std::shared_ptr Web3Server::face() dev::WebThreeNetworkFace* Web3Server::network() { - BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::WebThreeNetworkFace")); + return m_network.get(); } std::string Web3Server::get(std::string const& _name, std::string const& _key) diff --git a/mix/Web3Server.h b/mix/Web3Server.h index 71e2131ba..cfe5c889e 100644 --- a/mix/Web3Server.h +++ b/mix/Web3Server.h @@ -39,6 +39,7 @@ class Web3Server: public QObject, public dev::WebThreeStubServerBase, public dev public: Web3Server(jsonrpc::AbstractServerConnector& _conn, std::vector const& _accounts, dev::eth::Interface* _client); + virtual ~Web3Server(); signals: void newTransaction(); @@ -60,6 +61,7 @@ private: private: dev::eth::Interface* m_client; std::map m_db; + std::unique_ptr m_network; }; } diff --git a/mix/main.cpp b/mix/main.cpp index 798520e39..78b5261ac 100644 --- a/mix/main.cpp +++ b/mix/main.cpp @@ -22,27 +22,17 @@ #include #include +#include +#include #include "MixApplication.h" -#include "Exceptions.h" + using namespace dev::mix; int main(int _argc, char* _argv[]) { -#ifdef ETH_HAVE_WEBENGINE - Q_INIT_RESOURCE(js); -#endif -#if __linux - //work around ubuntu appmenu-qt5 bug - //https://bugs.launchpad.net/ubuntu/+source/appmenu-qt5/+bug/1323853 - putenv((char*)"QT_QPA_PLATFORMTHEME="); - putenv((char*)"QSG_RENDER_LOOP=threaded"); -#endif -#if (defined(_WIN32) || defined(_WIN64)) - if (!getenv("OPENSSL_CONF")) - putenv((char*)"OPENSSL_CONF=c:\\"); -#endif try { + MixApplication::initialize(); MixApplication app(_argc, _argv); return app.exec(); } diff --git a/mix/qml.qrc b/mix/qml.qrc index 5ceaaced6..47d9d3bb9 100644 --- a/mix/qml.qrc +++ b/mix/qml.qrc @@ -1,6 +1,7 @@ qml/AlertMessageDialog.qml + qml/Application.qml qml/BasicMessage.qml qml/BigIntValue.qml qml/CallStack.qml @@ -47,7 +48,8 @@ qml/StatusPaneStyle.qml qml/StepActionImage.qml qml/StorageView.qml - qml/StructView.qml + qml/StatesComboBox.qml + qml/StructView.qml qml/Style.qml qml/TabStyle.qml qml/TransactionDialog.qml @@ -59,8 +61,5 @@ qml/js/ProjectModel.js qml/js/QEtherHelper.js qml/js/TransactionHelper.js - qml/main.qml - qml/qmldir - qml/StatesComboBox.qml diff --git a/mix/qml/main.qml b/mix/qml/Application.qml similarity index 95% rename from mix/qml/main.qml rename to mix/qml/Application.qml index 5544d4639..b7a065cdf 100644 --- a/mix/qml/main.qml +++ b/mix/qml/Application.qml @@ -11,6 +11,7 @@ import org.ethereum.qml.CodeModel 1.0 import org.ethereum.qml.ClientModel 1.0 import org.ethereum.qml.FileIo 1.0 import org.ethereum.qml.Clipboard 1.0 +import org.ethereum.qml.ApplicationService 1.0 ApplicationWindow { @@ -22,6 +23,16 @@ ApplicationWindow { minimumWidth: 400 minimumHeight: 300 title: qsTr("Mix") + property alias systemPointSize: appService.systemPointSize; + property alias mainContent: mainContent; + property alias codeModel: codeModel; + property alias clientModel: clientModel; + property alias projectModel: projectModel; + property alias appService: appService; + + ApplicationService { + id: appService + } CodeModel { id: codeModel @@ -44,6 +55,10 @@ ApplicationWindow { id: clipboard } + Style { + id: appStyle + } + Connections { target: mainApplication onClosing: diff --git a/mix/qml/CallStack.qml b/mix/qml/CallStack.qml index c9e22532d..9a938078c 100644 --- a/mix/qml/CallStack.qml +++ b/mix/qml/CallStack.qml @@ -2,7 +2,6 @@ import QtQuick 2.2 import QtQuick.Controls 1.1 import QtQuick.Controls.Styles 1.1 import QtQuick.Layouts 1.1 -import "." DebugInfoList { @@ -37,7 +36,7 @@ DebugInfoList anchors.leftMargin: 5 color: "#4a4a4a" text: styleData.row; - font.pointSize: DebuggerPaneStyle.general.basicFontSize + font.pointSize: dbgStyle.general.basicFontSize width: parent.width - 5 elide: Text.ElideRight } @@ -58,7 +57,7 @@ DebugInfoList color: "#4a4a4a" text: styleData.value; elide: Text.ElideRight - font.pointSize: DebuggerPaneStyle.general.basicFontSize + font.pointSize: dbgStyle.general.basicFontSize } } } diff --git a/mix/qml/CodeEditor.qml b/mix/qml/CodeEditor.qml index 9d3df69e5..25f76b701 100644 --- a/mix/qml/CodeEditor.qml +++ b/mix/qml/CodeEditor.qml @@ -24,31 +24,36 @@ Item { 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 - } + + CodeEditorStyle { + id: style + } + + 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 @@ -66,7 +71,7 @@ Item { height: parent.height font.family: "Monospace" - font.pointSize: CodeEditorStyle.general.basicFontSize + font.pointSize: style.general.basicFontSize width: parent.width tabChangesFocus: false diff --git a/mix/qml/CodeEditorStyle.qml b/mix/qml/CodeEditorStyle.qml index c9740957c..999054d55 100644 --- a/mix/qml/CodeEditorStyle.qml +++ b/mix/qml/CodeEditorStyle.qml @@ -1,4 +1,3 @@ -pragma Singleton import QtQuick 2.0 QtObject { diff --git a/mix/qml/CodeEditorView.qml b/mix/qml/CodeEditorView.qml index 25ecbc98c..c04776c25 100644 --- a/mix/qml/CodeEditorView.qml +++ b/mix/qml/CodeEditorView.qml @@ -7,13 +7,15 @@ import QtQuick.Dialogs 1.1 Item { id: codeEditorView property string currentDocumentId: "" + property int openDocCount: 0 signal documentEdit(string documentId) signal breakpointsChanged(string documentId) signal isCleanChanged(var isClean, string documentId) + signal loadComplete function getDocumentText(documentId) { - for (var i = 0; i < editorListModel.count; i++) { + for (var i = 0; i < openDocCount; i++) { if (editorListModel.get(i).documentId === documentId) { return editors.itemAt(i).item.getText(); } @@ -22,7 +24,7 @@ Item { } function isDocumentOpen(documentId) { - for (var i = 0; i < editorListModel.count; i++) + for (var i = 0; i < openDocCount; i++) if (editorListModel.get(i).documentId === documentId && editors.itemAt(i).item) return true; @@ -35,15 +37,27 @@ Item { } function loadDocument(document) { - for (var i = 0; i < editorListModel.count; i++) + for (var i = 0; i < openDocCount; i++) if (editorListModel.get(i).documentId === document.documentId) return; //already open - editorListModel.append(document); + if (editorListModel.count <= openDocCount) + editorListModel.append(document); + else + { + editorListModel.set(openDocCount, document); + editors.itemAt(openDocCount).visible = true; + doLoadDocument(editors.itemAt(openDocCount).item, editorListModel.get(openDocCount)) + } + openDocCount++; + } function doLoadDocument(editor, document) { var data = fileIo.readFile(document.path); + editor.onLoadComplete.connect(function() { + loadComplete(); + }); editor.onEditorTextChanged.connect(function() { documentEdit(document.documentId); if (document.isContract) @@ -60,7 +74,7 @@ Item { } function getEditor(documentId) { - for (var i = 0; i < editorListModel.count; i++) + for (var i = 0; i < openDocCount; i++) { if (editorListModel.get(i).documentId === documentId) return editors.itemAt(i).item; @@ -91,7 +105,7 @@ Item { } function editingContract() { - for (var i = 0; i < editorListModel.count; i++) + for (var i = 0; i < openDocCount; i++) if (editorListModel.get(i).documentId === currentDocumentId) return editorListModel.get(i).isContract; return false; @@ -99,7 +113,7 @@ Item { function getBreakpoints() { var bpMap = {}; - for (var i = 0; i < editorListModel.count; i++) { + for (var i = 0; i < openDocCount; i++) { var documentId = editorListModel.get(i).documentId; var editor = editors.itemAt(i).item; if (editor) { @@ -130,7 +144,7 @@ Item { } onProjectSaving: { - for (var i = 0; i < editorListModel.count; i++) + for (var i = 0; i < openDocCount; i++) { var doc = editorListModel.get(i); var editor = editors.itemAt(i).item; @@ -142,7 +156,7 @@ Item { onProjectSaved: { if (projectModel.appIsClosing || projectModel.projectIsClosing) return; - for (var i = 0; i < editorListModel.count; i++) + for (var i = 0; i < openDocCount; i++) { var doc = editorListModel.get(i); resetEditStatus(doc.documentId); @@ -152,8 +166,9 @@ Item { onProjectClosed: { for (var i = 0; i < editorListModel.count; i++) editors.itemAt(i).visible = false; - editorListModel.clear(); + //editorListModel.clear(); currentDocumentId = ""; + openDocCount = 0; } onDocumentSaved: { @@ -177,6 +192,11 @@ Item { } } + CodeEditorStyle + { + id: style; + } + MessageDialog { id: messageDialog @@ -194,12 +214,15 @@ Item { Repeater { id: editors model: editorListModel + onItemRemoved: { + item.item.unloaded = true; + } delegate: Loader { id: loader active: false asynchronous: true anchors.fill: parent - source: "CodeEditor.qml" + source: appService.haveWebEngine ? "WebCodeEditor.qml" : "CodeEditor.qml" visible: (index >= 0 && index < editorListModel.count && currentDocumentId === editorListModel.get(index).documentId) property bool changed: false onVisibleChanged: { diff --git a/mix/qml/CommonSeparator.qml b/mix/qml/CommonSeparator.qml index c81a81f63..6644802c7 100644 --- a/mix/qml/CommonSeparator.qml +++ b/mix/qml/CommonSeparator.qml @@ -4,6 +4,6 @@ import "." Rectangle { height: 1 - color: Style.generic.layout.separatorColor + color: appStyle.generic.layout.separatorColor } diff --git a/mix/qml/Debugger.qml b/mix/qml/Debugger.qml index 836bbaf94..ad03f1456 100644 --- a/mix/qml/Debugger.qml +++ b/mix/qml/Debugger.qml @@ -65,6 +65,10 @@ Rectangle { Debugger.setBreakpoints(bp); } + DebuggerPaneStyle { + id: dbgStyle + } + Connections { target: clientModel onDebugDataReady: { @@ -449,7 +453,7 @@ Rectangle { color: "#b2b3ae" text: styleData.value.split(' ')[0] font.family: "monospace" - font.pointSize: DebuggerPaneStyle.general.basicFontSize + font.pointSize: dbgStyle.general.basicFontSize wrapMode: Text.NoWrap id: id } @@ -459,7 +463,7 @@ Rectangle { color: styleData.selected ? "white" : "black" font.family: "monospace" text: styleData.value.replace(styleData.value.split(' ')[0], '') - font.pointSize: DebuggerPaneStyle.general.basicFontSize + font.pointSize: dbgStyle.general.basicFontSize } } } @@ -532,7 +536,7 @@ Rectangle { font.family: "monospace" color: "#4a4a4a" text: styleData.row; - font.pointSize: DebuggerPaneStyle.general.basicFontSize + font.pointSize: dbgStyle.general.basicFontSize } } @@ -550,7 +554,7 @@ Rectangle { anchors.verticalCenter: parent.verticalCenter color: "#4a4a4a" text: styleData.value - font.pointSize: DebuggerPaneStyle.general.basicFontSize + font.pointSize: dbgStyle.general.basicFontSize } } } diff --git a/mix/qml/DebuggerPaneStyle.qml b/mix/qml/DebuggerPaneStyle.qml index 1078df2f9..db8bbe253 100644 --- a/mix/qml/DebuggerPaneStyle.qml +++ b/mix/qml/DebuggerPaneStyle.qml @@ -1,4 +1,3 @@ -pragma Singleton import QtQuick 2.0 QtObject { diff --git a/mix/qml/DefaultLabel.qml b/mix/qml/DefaultLabel.qml index d8ef1faff..5d00df137 100644 --- a/mix/qml/DefaultLabel.qml +++ b/mix/qml/DefaultLabel.qml @@ -1,6 +1,5 @@ import QtQuick 2.0 import QtQuick.Controls 1.1 -import "." Label { text: text diff --git a/mix/qml/DeploymentDialog.qml b/mix/qml/DeploymentDialog.qml index c194977cf..e8af2f664 100644 --- a/mix/qml/DeploymentDialog.qml +++ b/mix/qml/DeploymentDialog.qml @@ -24,14 +24,15 @@ Window { visible: false property alias applicationUrlEth: applicationUrlEth.text property alias applicationUrlHttp: applicationUrlHttp.text - property string urlHintContract: urlHintAddr.text + property alias urlHintContract: urlHintAddr.text + property alias localPackageUrl: localPackageUrl.text property string packageHash property string packageBase64 property string eth: registrarAddr.text property string currentAccount property alias gasToUse: gasToUseInput.text - color: Style.generic.layout.backgroundColor + color: appStyle.generic.layout.backgroundColor function close() { @@ -131,8 +132,8 @@ Window { var jsonRpcRequestId = 0; requests.push({ jsonrpc: "2.0", - method: "eth_countAt", - params: [ currentAccount ], + method: "eth_getTransactionCount", + params: [ currentAccount, "pending" ], id: jsonRpcRequestId++ }); TransactionHelper.rpcCall(requests, function (httpRequest, response){ @@ -299,7 +300,7 @@ Window { DefaultTextField { - text: "20000" + text: "1000000" Layout.preferredWidth: 350 id: gasToUseInput } @@ -329,7 +330,7 @@ Window { anchors.verticalCenter: parent.verticalCenter; anchors.left: applicationUrlEth.right font.italic: true - font.pointSize: Style.absoluteSize(-1) + font.pointSize: appStyle.absoluteSize(-1) } } } @@ -416,6 +417,20 @@ Window { columns: 2 Layout.fillWidth: true + DefaultLabel + { + Layout.preferredWidth: 355 + text: qsTr("Local package URL") + } + + DefaultTextField + { + Layout.preferredWidth: 350 + id: localPackageUrl + readOnly: true + enabled: rowRegister.isOkToRegister() + } + DefaultLabel { Layout.preferredWidth: 355 diff --git a/mix/qml/FilesSection.qml b/mix/qml/FilesSection.qml index d9f664894..d89875583 100644 --- a/mix/qml/FilesSection.qml +++ b/mix/qml/FilesSection.qml @@ -18,21 +18,21 @@ Rectangle property string sectionName; property variant selManager; property int index; - color: index % 2 === 0 ? "transparent" : ProjectFilesStyle.title.background + color: index % 2 === 0 ? "transparent" : projectFilesStyle.title.background function hiddenHeightTopLevel() { - return section.state === "hidden" ? ProjectFilesStyle.documentsList.height : ProjectFilesStyle.documentsList.fileNameHeight * model.count + ProjectFilesStyle.documentsList.height; + return section.state === "hidden" ? projectFilesStyle.documentsList.height : projectFilesStyle.documentsList.fileNameHeight * model.count + projectFilesStyle.documentsList.height; } function hiddenHeightRepeater() { - return section.state === "hidden" ? 0 : ProjectFilesStyle.documentsList.fileNameHeight * wrapperItem.model.count; + return section.state === "hidden" ? 0 : projectFilesStyle.documentsList.fileNameHeight * wrapperItem.model.count; } function hiddenHeightElement() { - return section.state === "hidden" ? 0 : ProjectFilesStyle.documentsList.fileNameHeight; + return section.state === "hidden" ? 0 : projectFilesStyle.documentsList.fileNameHeight; } function getDocumentIndex(documentId) @@ -68,7 +68,7 @@ Rectangle { anchors.top: parent.top id: rowCol - height: ProjectFilesStyle.documentsList.height + height: projectFilesStyle.documentsList.height Layout.fillWidth: true @@ -88,15 +88,15 @@ Rectangle id: section text: sectionName anchors.left: parent.left - anchors.leftMargin: ProjectFilesStyle.general.leftMargin - color: ProjectFilesStyle.documentsList.sectionColor + anchors.leftMargin: projectFilesStyle.general.leftMargin + color: projectFilesStyle.documentsList.sectionColor font.family: boldFont.name - font.pointSize: ProjectFilesStyle.documentsList.sectionFontSize + font.pointSize: projectFilesStyle.documentsList.sectionFontSize states: [ State { name: "hidden" PropertyChanges { target: filesList; visible: false; } - PropertyChanges { target: rowCol; Layout.minimumHeight: ProjectFilesStyle.documentsList.height; Layout.maximumHeight: ProjectFilesStyle.documentsList.height; height: ProjectFilesStyle.documentsList.height; } + PropertyChanges { target: rowCol; Layout.minimumHeight: projectFilesStyle.documentsList.height; Layout.maximumHeight: projectFilesStyle.documentsList.height; height: projectFilesStyle.documentsList.height; } PropertyChanges { target: imgArrow; source: "qrc:/qml/img/closedtriangleindicator_filesproject.png" } } ] @@ -138,7 +138,7 @@ Rectangle Layout.preferredHeight: wrapperItem.hiddenHeightElement() Layout.maximumHeight: wrapperItem.hiddenHeightElement() height: wrapperItem.hiddenHeightElement() - color: isSelected ? ProjectFilesStyle.documentsList.highlightColor : "transparent" + color: isSelected ? projectFilesStyle.documentsList.highlightColor : "transparent" property bool isSelected property bool renameMode @@ -147,15 +147,15 @@ Rectangle anchors.verticalCenter: parent.verticalCenter anchors.fill: parent anchors.left: parent.left - anchors.leftMargin: ProjectFilesStyle.general.leftMargin + 2 + anchors.leftMargin: projectFilesStyle.general.leftMargin + 2 Text { id: nameText height: parent.height visible: !renameMode - color: rootItem.isSelected ? ProjectFilesStyle.documentsList.selectedColor : ProjectFilesStyle.documentsList.color + color: rootItem.isSelected ? projectFilesStyle.documentsList.selectedColor : projectFilesStyle.documentsList.color text: name; font.family: fileNameFont.name - font.pointSize: ProjectFilesStyle.documentsList.fontSize + font.pointSize: projectFilesStyle.documentsList.fontSize verticalAlignment: Text.AlignVCenter Connections @@ -182,7 +182,7 @@ Rectangle DefaultLabel { id: editStatusLabel visible: false - color: rootItem.isSelected ? ProjectFilesStyle.documentsList.selectedColor : ProjectFilesStyle.documentsList.color + color: rootItem.isSelected ? projectFilesStyle.documentsList.selectedColor : projectFilesStyle.documentsList.color verticalAlignment: Text.AlignVCenter text: "*" width: 10 @@ -196,7 +196,7 @@ Rectangle visible: renameMode anchors.verticalCenter: parent.verticalCenter anchors.left: parent.left - anchors.leftMargin: ProjectFilesStyle.general.leftMargin + anchors.leftMargin: projectFilesStyle.general.leftMargin MouseArea { id: textMouseArea anchors.fill: parent diff --git a/mix/qml/ItemDelegateDataDump.qml b/mix/qml/ItemDelegateDataDump.qml index 9aa6d00db..2c1419e61 100644 --- a/mix/qml/ItemDelegateDataDump.qml +++ b/mix/qml/ItemDelegateDataDump.qml @@ -28,7 +28,7 @@ Rectangle { font.bold: true color: "#4a4a4a" text: modelData[0] - font.pointSize: DebuggerPaneStyle.general.dataDumpFontSize; + font.pointSize: dbgStyle.general.dataDumpFontSize; } } @@ -47,7 +47,7 @@ Rectangle { anchors.leftMargin: 4 color: "#4a4a4a" text: modelData[1] - font.pointSize: DebuggerPaneStyle.general.dataDumpFontSize + font.pointSize: dbgStyle.general.dataDumpFontSize } } } diff --git a/mix/qml/LogsPane.qml b/mix/qml/LogsPane.qml index ca123f6a0..d8d73ac0c 100644 --- a/mix/qml/LogsPane.qml +++ b/mix/qml/LogsPane.qml @@ -3,7 +3,6 @@ import QtQuick.Layouts 1.0 import QtQuick.Controls 1.1 import QtQuick.Controls.Styles 1.3 import org.ethereum.qml.SortFilterProxyModel 1.0 -import "." Rectangle { @@ -99,15 +98,15 @@ Rectangle Rectangle { - width: LogsPaneStyle.generic.layout.dateWidth + LogsPaneStyle.generic.layout.contentWidth + LogsPaneStyle.generic.layout.typeWidth + width: logStyle.generic.layout.dateWidth + logStyle.generic.layout.contentWidth + logStyle.generic.layout.typeWidth height: 30 color: { var cl; if (level === "warning" || level === "error") - cl = LogsPaneStyle.generic.layout.errorColor; + cl = logStyle.generic.layout.errorColor; else - cl = index % 2 === 0 ? "transparent" : LogsPaneStyle.generic.layout.logAlternateColor; + cl = index % 2 === 0 ? "transparent" : logStyle.generic.layout.logAlternateColor; if (index === 0) logsRepeater.frontColor = cl; return cl; @@ -137,9 +136,9 @@ Rectangle DefaultLabel { text: date; - font.family: LogsPaneStyle.generic.layout.logLabelFont - width: LogsPaneStyle.generic.layout.dateWidth - font.pointSize: Style.absoluteSize(-1) + font.family: logStyle.generic.layout.logLabelFont + width: logStyle.generic.layout.dateWidth + font.pointSize: appStyle.absoluteSize(-1) anchors.left: parent.left anchors.leftMargin: 15 anchors.verticalCenter: parent.verticalCenter @@ -150,9 +149,9 @@ Rectangle DefaultLabel { text: type; - font.family: LogsPaneStyle.generic.layout.logLabelFont - width: LogsPaneStyle.generic.layout.typeWidth - font.pointSize: Style.absoluteSize(-1) + font.family: logStyle.generic.layout.logLabelFont + width: logStyle.generic.layout.typeWidth + font.pointSize: appStyle.absoluteSize(-1) anchors.left: parent.left anchors.leftMargin: 100 anchors.verticalCenter: parent.verticalCenter @@ -164,9 +163,9 @@ Rectangle Text { id: logContent text: content; - font.family: LogsPaneStyle.generic.layout.logLabelFont - width: LogsPaneStyle.generic.layout.contentWidth - font.pointSize: Style.absoluteSize(-1) + font.family: logStyle.generic.layout.logLabelFont + width: logStyle.generic.layout.contentWidth + font.pointSize: appStyle.absoluteSize(-1) anchors.verticalCenter: parent.verticalCenter elide: Text.ElideRight anchors.left: parent.left @@ -194,8 +193,8 @@ Rectangle id: itemDelegate DefaultLabel { text: styleData.value; - font.family: LogsPaneStyle.generic.layout.logLabelFont - font.pointSize: Style.absoluteSize(-1) + font.family: logStyle.generic.layout.logLabelFont + font.pointSize: appStyle.absoluteSize(-1) color: { if (proxyModel.get(styleData.row).level === "error") return "red"; @@ -214,16 +213,16 @@ Rectangle GradientStop { position: 0.0; color: "#f1f1f1" } GradientStop { position: 1.0; color: "#d9d7da" } } - Layout.preferredHeight: LogsPaneStyle.generic.layout.headerHeight - height: LogsPaneStyle.generic.layout.headerHeight + Layout.preferredHeight: logStyle.generic.layout.headerHeight + height: logStyle.generic.layout.headerHeight width: logsPane.width anchors.bottom: parent.bottom Row { id: rowAction - anchors.leftMargin: LogsPaneStyle.generic.layout.leftMargin + anchors.leftMargin: logStyle.generic.layout.leftMargin anchors.left: parent.left - spacing: LogsPaneStyle.generic.layout.headerButtonSpacing + spacing: logStyle.generic.layout.headerButtonSpacing height: parent.height Rectangle { @@ -233,9 +232,9 @@ Rectangle DefaultLabel { anchors.verticalCenter: parent.verticalCenter - color: LogsPaneStyle.generic.layout.logLabelColor - font.pointSize: Style.absoluteSize(-3) - font.family: LogsPaneStyle.generic.layout.logLabelFont + color: logStyle.generic.layout.logLabelColor + font.pointSize: appStyle.absoluteSize(-3) + font.family: logStyle.generic.layout.logLabelFont text: qsTr("Show:") } } @@ -244,20 +243,20 @@ Rectangle anchors.verticalCenter: parent.verticalCenter width: 1; height: parent.height - color: LogsPaneStyle.generic.layout.buttonSeparatorColor1 + color: logStyle.generic.layout.buttonSeparatorColor1 } Rectangle { anchors.verticalCenter: parent.verticalCenter width: 2; height: parent.height - color: LogsPaneStyle.generic.layout.buttonSeparatorColor2 + color: logStyle.generic.layout.buttonSeparatorColor2 } ToolButton { id: javascriptButton checkable: true - height: LogsPaneStyle.generic.layout.headerButtonHeight + height: logStyle.generic.layout.headerButtonHeight width: 20 anchors.verticalCenter: parent.verticalCenter checked: true @@ -270,16 +269,16 @@ Rectangle label: Item { DefaultLabel { - font.family: LogsPaneStyle.generic.layout.logLabelFont - font.pointSize: Style.absoluteSize(-3) - color: LogsPaneStyle.generic.layout.logLabelColor + font.family: logStyle.generic.layout.logLabelFont + font.pointSize: appStyle.absoluteSize(-3) + color: logStyle.generic.layout.logLabelColor anchors.centerIn: parent text: qsTr("JS") } } background: Rectangle { - color: javascriptButton.checked ? LogsPaneStyle.generic.layout.buttonSelected : "transparent" + color: javascriptButton.checked ? logStyle.generic.layout.buttonSelected : "transparent" } } } @@ -288,20 +287,20 @@ Rectangle anchors.verticalCenter: parent.verticalCenter width: 1; height: parent.height - color: LogsPaneStyle.generic.layout.buttonSeparatorColor1 + color: logStyle.generic.layout.buttonSeparatorColor1 } Rectangle { anchors.verticalCenter: parent.verticalCenter width: 2; height: parent.height - color: LogsPaneStyle.generic.layout.buttonSeparatorColor2 + color: logStyle.generic.layout.buttonSeparatorColor2 } ToolButton { id: runButton checkable: true - height: LogsPaneStyle.generic.layout.headerButtonHeight + height: logStyle.generic.layout.headerButtonHeight width: 30 anchors.verticalCenter: parent.verticalCenter checked: true @@ -314,16 +313,16 @@ Rectangle label: Item { DefaultLabel { - font.family: LogsPaneStyle.generic.layout.logLabelFont - font.pointSize: Style.absoluteSize(-3) - color: LogsPaneStyle.generic.layout.logLabelColor + font.family: logStyle.generic.layout.logLabelFont + font.pointSize: appStyle.absoluteSize(-3) + color: logStyle.generic.layout.logLabelColor anchors.centerIn: parent text: qsTr("Run") } } background: Rectangle { - color: runButton.checked ? LogsPaneStyle.generic.layout.buttonSelected : "transparent" + color: runButton.checked ? logStyle.generic.layout.buttonSelected : "transparent" } } } @@ -332,20 +331,20 @@ Rectangle anchors.verticalCenter: parent.verticalCenter width: 1; height: parent.height - color: LogsPaneStyle.generic.layout.buttonSeparatorColor1 + color: logStyle.generic.layout.buttonSeparatorColor1 } Rectangle { anchors.verticalCenter: parent.verticalCenter width: 2; height: parent.height - color: LogsPaneStyle.generic.layout.buttonSeparatorColor2 + color: logStyle.generic.layout.buttonSeparatorColor2 } ToolButton { id: stateButton checkable: true - height: LogsPaneStyle.generic.layout.headerButtonHeight + height: logStyle.generic.layout.headerButtonHeight anchors.verticalCenter: parent.verticalCenter width: 35 checked: true @@ -358,16 +357,16 @@ Rectangle label: Item { DefaultLabel { - font.family: LogsPaneStyle.generic.layout.logLabelFont - font.pointSize: Style.absoluteSize(-3) - color: LogsPaneStyle.generic.layout.logLabelColor + font.family: logStyle.generic.layout.logLabelFont + font.pointSize: appStyle.absoluteSize(-3) + color: logStyle.generic.layout.logLabelColor anchors.centerIn: parent text: qsTr("State") } } background: Rectangle { - color: stateButton.checked ? LogsPaneStyle.generic.layout.buttonSelected : "transparent" + color: stateButton.checked ? logStyle.generic.layout.buttonSelected : "transparent" } } } @@ -376,14 +375,14 @@ Rectangle anchors.verticalCenter: parent.verticalCenter width: 1; height: parent.height - color: LogsPaneStyle.generic.layout.buttonSeparatorColor1 + color: logStyle.generic.layout.buttonSeparatorColor1 } Rectangle { anchors.verticalCenter: parent.verticalCenter width: 2; height: parent.height - color: LogsPaneStyle.generic.layout.buttonSeparatorColor2 + color: logStyle.generic.layout.buttonSeparatorColor2 } } @@ -395,7 +394,7 @@ Rectangle spacing: 10 Rectangle { - height: LogsPaneStyle.generic.layout.headerButtonHeight + height: logStyle.generic.layout.headerButtonHeight anchors.verticalCenter: parent.verticalCenter color: "transparent" width: 20 @@ -410,8 +409,8 @@ Rectangle ButtonStyle { background: Rectangle { - height: LogsPaneStyle.generic.layout.headerButtonHeight - implicitHeight: LogsPaneStyle.generic.layout.headerButtonHeight + height: logStyle.generic.layout.headerButtonHeight + implicitHeight: logStyle.generic.layout.headerButtonHeight color: "transparent" } } @@ -438,7 +437,7 @@ Rectangle Rectangle { - height: LogsPaneStyle.generic.layout.headerButtonHeight + height: logStyle.generic.layout.headerButtonHeight anchors.verticalCenter: parent.verticalCenter color: "transparent" width: 20 @@ -453,8 +452,8 @@ Rectangle ButtonStyle { background: Rectangle { - height: LogsPaneStyle.generic.layout.headerButtonHeight - implicitHeight: LogsPaneStyle.generic.layout.headerButtonHeight + height: logStyle.generic.layout.headerButtonHeight + implicitHeight: logStyle.generic.layout.headerButtonHeight color: "transparent" } } @@ -510,8 +509,8 @@ Rectangle width: 100 anchors.left: searchImg.right anchors.leftMargin: -7 - font.family: LogsPaneStyle.generic.layout.logLabelFont - font.pointSize: Style.absoluteSize(-3) + font.family: logStyle.generic.layout.logLabelFont + font.pointSize: appStyle.absoluteSize(-3) font.italic: true text: qsTr(" - Search - ") onFocusChanged: @@ -540,7 +539,7 @@ Rectangle Rectangle { - height: LogsPaneStyle.generic.layout.headerButtonHeight + height: logStyle.generic.layout.headerButtonHeight anchors.verticalCenter: parent.verticalCenter color: "transparent" width: 20 @@ -555,8 +554,8 @@ Rectangle ButtonStyle { background: Rectangle { - height: LogsPaneStyle.generic.layout.headerButtonHeight - implicitHeight: LogsPaneStyle.generic.layout.headerButtonHeight + height: logStyle.generic.layout.headerButtonHeight + implicitHeight: logStyle.generic.layout.headerButtonHeight color: "transparent" } } diff --git a/mix/qml/LogsPaneStyle.qml b/mix/qml/LogsPaneStyle.qml index 0bbdfcee4..fe50610c8 100644 --- a/mix/qml/LogsPaneStyle.qml +++ b/mix/qml/LogsPaneStyle.qml @@ -1,4 +1,3 @@ -pragma Singleton import QtQuick 2.0 QtObject { diff --git a/mix/qml/MainContent.qml b/mix/qml/MainContent.qml index 67034f108..ca08485c3 100644 --- a/mix/qml/MainContent.qml +++ b/mix/qml/MainContent.qml @@ -22,6 +22,7 @@ Rectangle { property alias rightViewVisible: rightView.visible property alias webViewVisible: webPreview.visible + property alias webView: webPreview property alias projectViewVisible: projectList.visible property alias runOnProjectLoad: mainSettings.runOnProjectLoad property alias rightPane: rightView diff --git a/mix/qml/NewProjectDialog.qml b/mix/qml/NewProjectDialog.qml index 1ec53e1d9..3ea2a5a4b 100644 --- a/mix/qml/NewProjectDialog.qml +++ b/mix/qml/NewProjectDialog.qml @@ -15,6 +15,7 @@ Window { property alias projectTitle: titleField.text readonly property string projectPath: "file://" + pathField.text + property alias pathFieldText: pathField.text signal accepted function open() { diff --git a/mix/qml/ProjectFilesStyle.qml b/mix/qml/ProjectFilesStyle.qml index f4b83c728..ca4499196 100644 --- a/mix/qml/ProjectFilesStyle.qml +++ b/mix/qml/ProjectFilesStyle.qml @@ -1,4 +1,3 @@ -pragma Singleton import QtQuick 2.0 QtObject { diff --git a/mix/qml/ProjectList.qml b/mix/qml/ProjectList.qml index 6ca6e6d1f..a98c2587b 100644 --- a/mix/qml/ProjectList.qml +++ b/mix/qml/ProjectList.qml @@ -8,6 +8,11 @@ import "." Item { property bool renameMode: false; + + ProjectFilesStyle { + id: projectFilesStyle + } + ColumnLayout { anchors.fill: parent id: filesCol @@ -20,8 +25,8 @@ Item { Rectangle { - color: ProjectFilesStyle.title.background - height: ProjectFilesStyle.title.height + color: projectFilesStyle.title.background + height: projectFilesStyle.title.height Layout.fillWidth: true Image { id: projectIcon @@ -37,14 +42,14 @@ Item { Text { id: projectTitle - color: ProjectFilesStyle.title.color + color: projectFilesStyle.title.color text: projectModel.projectTitle anchors.verticalCenter: parent.verticalCenter visible: !projectModel.isEmpty; anchors.left: parent.left - anchors.leftMargin: ProjectFilesStyle.general.leftMargin + anchors.leftMargin: projectFilesStyle.general.leftMargin font.family: srcSansProLight.name - font.pointSize: ProjectFilesStyle.title.fontSize + font.pointSize: projectFilesStyle.title.fontSize font.weight: Font.Light } @@ -54,7 +59,7 @@ Item { anchors.right: parent.right anchors.rightMargin: 15 font.family: srcSansProLight.name - font.pointSize: ProjectFilesStyle.title.fontSize + font.pointSize: projectFilesStyle.title.fontSize anchors.verticalCenter: parent.verticalCenter font.weight: Font.Light } @@ -64,14 +69,14 @@ Item { { Layout.fillWidth: true height: 3 - color: ProjectFilesStyle.documentsList.background + color: projectFilesStyle.documentsList.background } Rectangle { Layout.fillWidth: true Layout.fillHeight: true - color: ProjectFilesStyle.documentsList.background + color: projectFilesStyle.documentsList.background ColumnLayout { diff --git a/mix/qml/ProjectModel.qml b/mix/qml/ProjectModel.qml index ec7681f16..58eb18a16 100644 --- a/mix/qml/ProjectModel.qml +++ b/mix/qml/ProjectModel.qml @@ -43,8 +43,10 @@ Item { property string deploymentDir property var listModel: projectListModel property var stateListModel: projectStateListModel.model + property alias stateDialog: projectStateListModel.stateDialog property CodeEditorView codeEditor: null property var unsavedFiles: [] + property alias newProjectDialog: newProjectDialog //interface function saveAll() { ProjectModelCode.saveAll(); } @@ -158,7 +160,7 @@ Item { } MessageDialog { - id: deployRessourcesDialog + id: deployResourcesDialog title: qsTr("Project") standardButtons: StandardButton.Ok } diff --git a/mix/qml/Splitter.qml b/mix/qml/Splitter.qml index 96e823795..012379700 100644 --- a/mix/qml/Splitter.qml +++ b/mix/qml/Splitter.qml @@ -9,6 +9,3 @@ SplitView color: "#cccccc" } } - - - diff --git a/mix/qml/StateDialog.qml b/mix/qml/StateDialog.qml index b229ed433..9dc1549c9 100644 --- a/mix/qml/StateDialog.qml +++ b/mix/qml/StateDialog.qml @@ -17,10 +17,12 @@ Window { height: 480 title: qsTr("Edit State") visible: false - color: StateDialogStyle.generic.backgroundColor + color: stateDialogStyle.generic.backgroundColor property alias stateTitle: titleField.text property alias isDefault: defaultCheckBox.checked + property alias model: transactionsModel + property alias transactionDialog: transactionDialog property int stateIndex property var stateTransactions: [] property var stateAccounts: [] @@ -56,6 +58,11 @@ Window { forceActiveFocus(); } + function acceptAndClose() { + close(); + accepted(); + } + function close() { visible = false; } @@ -71,6 +78,10 @@ Window { return item; } + StateDialogStyle { + id: stateDialogStyle + } + ColumnLayout { anchors.fill: parent anchors.margins: 10 @@ -292,8 +303,7 @@ Window { Button { text: qsTr("OK"); onClicked: { - close(); - accepted(); + acceptAndClose(); } } Button { diff --git a/mix/qml/StateDialogStyle.qml b/mix/qml/StateDialogStyle.qml index 39214312a..993e6a1c3 100644 --- a/mix/qml/StateDialogStyle.qml +++ b/mix/qml/StateDialogStyle.qml @@ -1,4 +1,3 @@ -pragma Singleton import QtQuick 2.0 QtObject { diff --git a/mix/qml/StateListModel.qml b/mix/qml/StateListModel.qml index 682c4b52c..c90692572 100644 --- a/mix/qml/StateListModel.qml +++ b/mix/qml/StateListModel.qml @@ -12,6 +12,7 @@ Item { property alias model: stateListModel property var stateList: [] + property alias stateDialog: stateDialog property string defaultAccount: "cb73d9408c4720e230387d956eb0f829d8a4dd2c1055f96257167e14e7169074" //support for old project function fromPlainStateItem(s) { diff --git a/mix/qml/StatusPane.qml b/mix/qml/StatusPane.qml index 6d8042add..a1f3ebe13 100644 --- a/mix/qml/StatusPane.qml +++ b/mix/qml/StatusPane.qml @@ -54,6 +54,10 @@ Rectangle { currentStatus = { "type": type, "date": Qt.formatDateTime(new Date(), "hh:mm:ss"), "content": text, "level": "error" } } + StatusPaneStyle { + id: statusPaneStyle + } + Connections { target: webPreview onJavaScriptMessage: @@ -123,7 +127,7 @@ Rectangle { Text { anchors.verticalCenter: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter - font.pointSize: Style.absoluteSize(-1) + font.pointSize: appStyle.absoluteSize(-1) height: 15 font.family: "sans serif" objectName: "status" @@ -227,7 +231,11 @@ Rectangle { top = top.parent var coordinates = logsContainer.mapToItem(top, 0, 0); logsContainer.parent = top; - logsContainer.x = status.x + statusContainer.x - LogsPaneStyle.generic.layout.dateWidth - LogsPaneStyle.generic.layout.typeWidth + 70 + logsContainer.x = status.x + statusContainer.x - logStyle.generic.layout.dateWidth - logStyle.generic.layout.typeWidth + 70 + } + + LogsPaneStyle { + id: logStyle } LogsPane diff --git a/mix/qml/StatusPaneStyle.qml b/mix/qml/StatusPaneStyle.qml index e6a1c9910..1eb11b48e 100644 --- a/mix/qml/StatusPaneStyle.qml +++ b/mix/qml/StatusPaneStyle.qml @@ -1,4 +1,3 @@ -pragma Singleton import QtQuick 2.0 QtObject { diff --git a/mix/qml/StorageView.qml b/mix/qml/StorageView.qml index f4831e3c3..ecd64ccf8 100644 --- a/mix/qml/StorageView.qml +++ b/mix/qml/StorageView.qml @@ -29,7 +29,7 @@ DebugInfoList anchors.leftMargin: 5 color: "#4a4a4a" text: styleData.value.split('\t')[0]; - font.pointSize: DebuggerPaneStyle.general.basicFontSize + font.pointSize: dbgStyle.general.basicFontSize width: parent.width - 5 elide: Text.ElideRight } @@ -52,7 +52,7 @@ DebugInfoList color: "#4a4a4a" text: styleData.value.split('\t')[1]; elide: Text.ElideRight - font.pointSize: DebuggerPaneStyle.general.basicFontSize + font.pointSize: dbgStyle.general.basicFontSize } } } diff --git a/mix/qml/Style.qml b/mix/qml/Style.qml index c317177a3..422831c78 100644 --- a/mix/qml/Style.qml +++ b/mix/qml/Style.qml @@ -1,4 +1,3 @@ -pragma Singleton import QtQuick 2.0 QtObject { diff --git a/mix/qml/TransactionDialog.qml b/mix/qml/TransactionDialog.qml index 33ab8c5d0..62a799b26 100644 --- a/mix/qml/TransactionDialog.qml +++ b/mix/qml/TransactionDialog.qml @@ -13,7 +13,7 @@ Window { width: 520 height: 500; visible: false - color: StateDialogStyle.generic.backgroundColor + color: transactionDialogStyle.generic.backgroundColor title: qsTr("Edit Transaction") property int transactionIndex property alias gas: gasValueEdit.gasValue; @@ -59,16 +59,7 @@ Window { contractComboBox.currentIndex = contractIndex; loadFunctions(contractComboBox.currentValue()); - - var functionIndex = -1; - for (var f = 0; f < functionsModel.count; f++) - if (functionsModel.get(f).text === item.functionId) - functionIndex = f; - - if (functionIndex == -1 && functionsModel.count > 0) - functionIndex = 0; //@todo suggest unused function - - functionComboBox.currentIndex = functionIndex; + selectFunction(functionId); paramsModel = []; if (functionId !== contractComboBox.currentValue()) @@ -87,9 +78,6 @@ Window { visible = true; valueField.focus = true; - modalTransactionDialog.height = (paramsModel.length > 0 ? 500 : 300); - paramLabel.visible = paramsModel.length > 0; - paramScroll.visible = paramsModel.length > 0; } function loadFunctions(contractId) @@ -107,6 +95,19 @@ Window { } + function selectFunction(functionId) + { + var functionIndex = -1; + for (var f = 0; f < functionsModel.count; f++) + if (functionsModel.get(f).text === functionId) + functionIndex = f; + + if (functionIndex == -1 && functionsModel.count > 0) + functionIndex = 0; //@todo suggest unused function + + functionComboBox.currentIndex = functionIndex; + } + function loadParameter(parameter) { var type = parameter.type; @@ -136,6 +137,15 @@ Window { typeLoader.members = [] typeLoader.value = paramValues; typeLoader.members = paramsModel; + paramLabel.visible = paramsModel.length > 0; + paramScroll.visible = paramsModel.length > 0; + modalTransactionDialog.height = (paramsModel.length > 0 ? 500 : 300); + } + + function acceptAndClose() + { + close(); + accepted(); } function close() @@ -169,6 +179,10 @@ Window { return item; } + StateDialogStyle { + id: transactionDialogStyle + } + ColumnLayout { anchors.fill: parent anchors.margins: 10 @@ -365,8 +379,7 @@ Window { Button { text: qsTr("OK"); onClicked: { - close(); - accepted(); + acceptAndClose(); } } Button { diff --git a/mix/qml/WebCodeEditor.qml b/mix/qml/WebCodeEditor.qml index 39c840dca..6ab0b398a 100644 --- a/mix/qml/WebCodeEditor.qml +++ b/mix/qml/WebCodeEditor.qml @@ -4,23 +4,27 @@ import QtQuick.Layouts 1.0 import QtQuick.Controls.Styles 1.1 import QtWebEngine 1.0 import QtWebEngine.experimental 1.0 +import org.ethereum.qml.Clipboard 1.0 import "js/ErrorLocationFormater.js" as ErrorLocationFormater Item { - signal editorTextChanged signal breakpointsChanged + signal editorTextChanged + signal loadComplete property bool isClean: true property string currentText: "" property string currentMode: "" property bool initialized: false + property bool unloaded: false property var currentBreakpoints: []; function setText(text, mode) { currentText = text; - currentMode = mode; + if (mode !== undefined) + currentMode = mode; if (initialized && editorBrowser) { editorBrowser.runJavaScript("setTextBase64(\"" + Qt.btoa(text) + "\")"); - editorBrowser.runJavaScript("setMode(\"" + mode + "\")"); + editorBrowser.runJavaScript("setMode(\"" + currentMode + "\")"); } setFocus(); } @@ -65,6 +69,11 @@ Item { editorBrowser.runJavaScript("changeGeneration()", function(result) {}); } + Clipboard + { + id: clipboard + } + Connections { target: clipboard onClipboardChanged: syncClipboard() @@ -133,7 +142,7 @@ Item { if (!editorBrowser) return; editorBrowser.runJavaScript("getTextChanged()", function(result) { - if (result === true) { + if (result === true && editorBrowser) { editorBrowser.runJavaScript("getText()" , function(textValue) { currentText = textValue; editorTextChanged(); @@ -141,7 +150,7 @@ Item { } }); editorBrowser.runJavaScript("getBreakpointsChanged()", function(result) { - if (result === true) { + if (result === true && editorBrowser) { editorBrowser.runJavaScript("getBreakpoints()" , function(bp) { if (currentBreakpoints !== bp) { currentBreakpoints = bp; diff --git a/mix/qml/WebPreview.qml b/mix/qml/WebPreview.qml index 768d188cb..d2b52be65 100644 --- a/mix/qml/WebPreview.qml +++ b/mix/qml/WebPreview.qml @@ -12,6 +12,7 @@ Item { id: webPreview property string pendingPageUrl: "" property bool initialized: false + property alias urlInput: urlInput signal javaScriptMessage(var _level, string _sourceId, var _lineNb, string _content) function setPreviewUrl(url) { @@ -60,6 +61,10 @@ Item { setPreviewUrl(urlInput.text); } + WebPreviewStyle { + id: webPreviewStyle + } + Connections { target: mainApplication onLoaded: { @@ -183,7 +188,7 @@ Item { Rectangle { anchors.leftMargin: 4 - color: WebPreviewStyle.general.headerBackgroundColor + color: webPreviewStyle.general.headerBackgroundColor Layout.preferredWidth: parent.width Layout.preferredHeight: 32 Row { @@ -230,7 +235,7 @@ Item { { width: 1 height: parent.height - 10 - color: WebPreviewStyle.general.separatorColor + color: webPreviewStyle.general.separatorColor anchors.verticalCenter: parent.verticalCenter } @@ -251,7 +256,7 @@ Item { { width: 1 height: parent.height - 10 - color: WebPreviewStyle.general.separatorColor + color: webPreviewStyle.general.separatorColor anchors.verticalCenter: parent.verticalCenter } @@ -285,7 +290,7 @@ Item { { Layout.preferredHeight: 1 Layout.preferredWidth: parent.width - color: WebPreviewStyle.general.separatorColor + color: webPreviewStyle.general.separatorColor } Splitter @@ -299,6 +304,7 @@ Item { id: webView experimental.settings.localContentCanAccessRemoteUrls: true onJavaScriptConsoleMessage: { + console.log(sourceID + ":" + lineNumber + ": " + message); webPreview.javaScriptMessage(level, sourceID, lineNumber, message); } onLoadingChanged: { @@ -355,9 +361,9 @@ Item { id: expressionInput width: parent.width - 15 height: 20 - font.family: WebPreviewStyle.general.fontName + font.family: webPreviewStyle.general.fontName font.italic: true - font.pointSize: Style.absoluteSize(-3) + font.pointSize: appStyle.absoluteSize(-3) anchors.verticalCenter: parent.verticalCenter property var history: [] @@ -417,8 +423,8 @@ Item { id: resultTextArea width: expressionPanel.width wrapMode: Text.Wrap - font.family: WebPreviewStyle.general.fontName - font.pointSize: Style.absoluteSize(-3) + font.family: webPreviewStyle.general.fontName + font.pointSize: appStyle.absoluteSize(-3) backgroundVisible: true style: TextAreaStyle { backgroundColor: "#f0f0f0" diff --git a/mix/qml/WebPreviewStyle.qml b/mix/qml/WebPreviewStyle.qml index 231bbe16c..1fc0c8b99 100644 --- a/mix/qml/WebPreviewStyle.qml +++ b/mix/qml/WebPreviewStyle.qml @@ -1,4 +1,3 @@ -pragma Singleton import QtQuick 2.0 QtObject { diff --git a/mix/qml/js/ProjectModel.js b/mix/qml/js/ProjectModel.js index 25e8dcb77..de0bcb5a0 100644 --- a/mix/qml/js/ProjectModel.js +++ b/mix/qml/js/ProjectModel.js @@ -367,23 +367,8 @@ function startDeployProject(erasePrevious) var ctrNames = Object.keys(codeModel.contracts); var ctrAddresses = {}; - setDefaultBlock(0, function() { - deployContracts(0, ctrAddresses, ctrNames, function (){ - finalizeDeployment(deploymentId, ctrAddresses); - }); - }); -} - -function setDefaultBlock(val, callBack) -{ - var requests = [{ - jsonrpc: "2.0", - method: "eth_setDefaultBlock", - params: [val], - id: 0 - }]; - rpcCall(requests, function (httpCall, response){ - callBack(); + deployContracts(0, ctrAddresses, ctrNames, function (){ + finalizeDeployment(deploymentId, ctrAddresses); }); } @@ -392,7 +377,7 @@ function deployContracts(ctrIndex, ctrAddresses, ctrNames, callBack) var code = codeModel.contracts[ctrNames[ctrIndex]].codeHex; var requests = [{ jsonrpc: "2.0", - method: "eth_transact", + method: "eth_sendTransaction", params: [ { "from": deploymentDialog.currentAccount, "gas": deploymentDialog.gasToUse, "code": code } ], id: 0 }]; @@ -452,7 +437,8 @@ function finalizeDeployment(deploymentId, addresses) { "\tinterface: " + codeModel.contracts[c].contractInterface + ",\n" + "\taddress: \"" + addresses[c] + "\"\n" + "};\n" + - contractAccessor + ".contract = web3.eth.contract(" + contractAccessor + ".address, " + contractAccessor + ".interface);\n"; + contractAccessor + ".contractClass = web3.eth.contract(" + contractAccessor + ".interface);\n" + + contractAccessor + ".contract = new " + contractAccessor + ".contractClass(" + contractAccessor + ".address);\n"; } fileIo.writeFile(deploymentDir + "deployment.js", deploymentJs); deploymentAddresses = addresses; @@ -461,6 +447,7 @@ function finalizeDeployment(deploymentId, addresses) { var packageRet = fileIo.makePackage(deploymentDir); deploymentDialog.packageHash = packageRet[0]; deploymentDialog.packageBase64 = packageRet[1]; + deploymentDialog.localPackageUrl = packageRet[2] + "?hash=" + packageRet[0]; var applicationUrlEth = deploymentDialog.applicationUrlEth; @@ -468,9 +455,8 @@ function finalizeDeployment(deploymentId, addresses) { deploymentStepChanged(qsTr("Registering application on the Ethereum network ...")); checkEthPath(applicationUrlEth, function () { deploymentComplete(); - deployRessourcesDialog.text = qsTr("Register Web Application to finalize deployment."); - deployRessourcesDialog.open(); - setDefaultBlock(-1, function() {}); + deployResourcesDialog.text = qsTr("Register Web Application to finalize deployment."); + deployResourcesDialog.open(); }); } @@ -567,7 +553,7 @@ function checkRegistration(dappUrl, addr, callBack) requests.push({ jsonrpc: "2.0", - method: "eth_transact", + method: "eth_sendTransaction", params: [ { "from": deploymentDialog.currentAccount, "gas": 20000, "code": "0x60056013565b61059e8061001d6000396000f35b33600081905550560060003560e060020a90048063019848921461009a578063449c2090146100af5780635d574e32146100cd5780635fd4b08a146100e1578063618242da146100f65780636be16bed1461010b5780636c4489b414610129578063893d20e8146101585780639607730714610173578063c284bc2a14610187578063e50f599a14610198578063e5811b35146101af578063ec7b9200146101cd57005b6100a560043561031b565b8060005260206000f35b6100ba6004356103a0565b80600160a060020a031660005260206000f35b6100db600435602435610537565b60006000f35b6100ec600435610529565b8060005260206000f35b6101016004356103dd565b8060005260206000f35b6101166004356103bd565b80600160a060020a031660005260206000f35b61013460043561034b565b82600160a060020a031660005281600160a060020a03166020528060405260606000f35b610160610341565b80600160a060020a031660005260206000f35b6101816004356024356102b4565b60006000f35b6101926004356103fd565b60006000f35b6101a96004356024356044356101f2565b60006000f35b6101ba6004356101eb565b80600160a060020a031660005260206000f35b6101d8600435610530565b80600160a060020a031660005260206000f35b6000919050565b600054600160a060020a031633600160a060020a031614610212576102af565b8160026000858152602001908152602001600020819055508061023457610287565b81600160a060020a0316837f680ad70765443c2967675ab0fb91a46350c01c6df59bf9a41ff8a8dd097464ec60006000a3826001600084600160a060020a03168152602001908152602001600020819055505b827f18d67da0cd86808336a3aa8912f6ea70c5250f1a98b586d1017ef56fe199d4fc60006000a25b505050565b600054600160a060020a031633600160a060020a0316146102d457610317565b806002600084815260200190815260200160002060010181905550817f18d67da0cd86808336a3aa8912f6ea70c5250f1a98b586d1017ef56fe199d4fc60006000a25b5050565b60006001600083600160a060020a03168152602001908152602001600020549050919050565b6000600054905090565b6000600060006002600085815260200190815260200160002054925060026000858152602001908152602001600020600101549150600260008581526020019081526020016000206002015490509193909250565b600060026000838152602001908152602001600020549050919050565b600060026000838152602001908152602001600020600101549050919050565b600060026000838152602001908152602001600020600201549050919050565b600054600160a060020a031633600160a060020a03161461041d57610526565b80600160006002600085815260200190815260200160002054600160a060020a031681526020019081526020016000205414610458576104d2565b6002600082815260200190815260200160002054600160a060020a0316817f680ad70765443c2967675ab0fb91a46350c01c6df59bf9a41ff8a8dd097464ec60006000a36000600160006002600085815260200190815260200160002054600160a060020a03168152602001908152602001600020819055505b6002600082815260200190815260200160002060008101600090556001810160009055600281016000905550807f18d67da0cd86808336a3aa8912f6ea70c5250f1a98b586d1017ef56fe199d4fc60006000a25b50565b6000919050565b6000919050565b600054600160a060020a031633600160a060020a0316146105575761059a565b806002600084815260200190815260200160002060020181905550817f18d67da0cd86808336a3aa8912f6ea70c5250f1a98b586d1017ef56fe199d4fc60006000a25b505056" } ], id: jsonRpcRequestId++ }); @@ -588,7 +574,7 @@ function checkRegistration(dappUrl, addr, callBack) requests.push({ //setRegister() jsonrpc: "2.0", - method: "eth_transact", + method: "eth_sendTransaction", params: [ { "from": deploymentDialog.currentAccount, "gas": 30000, "to": '0x' + addr, "data": "0x96077307" + crLevel + deploymentDialog.pad(newCtrAddress) } ], id: jsonRpcRequestId++ }); @@ -617,12 +603,12 @@ function registerContentHash(registrar, callBack) deploymentStepChanged(txt); console.log(txt); var requests = []; - var paramTitle = createString(projectModel.projectTitle); + var paramTitle = clientModel.encodeAbiString(projectModel.projectTitle); requests.push({ //setContent() jsonrpc: "2.0", - method: "eth_transact", - params: [ { "from": deploymentDialog.currentAccount, "gas": 30000, "gasPrice": "10", "to": '0x' + registrar, "data": "0x5d574e32" + paramTitle.encodeValueAsString() + deploymentDialog.packageHash } ], + method: "eth_sendTransaction", + params: [ { "from": deploymentDialog.currentAccount, "gas": 30000, "gasPrice": "10", "to": '0x' + registrar, "data": "0x5d574e32" + paramTitle + deploymentDialog.packageHash } ], id: jsonRpcRequestId++ }); rpcCall(requests, function (httpRequest, response) { @@ -638,7 +624,7 @@ function registerToUrlHint() requests.push({ //urlHint => suggestUrl jsonrpc: "2.0", - method: "eth_transact", + method: "eth_sendTransaction", params: [ { "to": '0x' + deploymentDialog.urlHintContract, "gas": 30000, "data": "0x4983e19c" + deploymentDialog.packageHash + paramUrlHttp.encodeValueAsString() } ], id: jsonRpcRequestId++ }); diff --git a/mix/qml/js/QEtherHelper.js b/mix/qml/js/QEtherHelper.js index 71ee258c3..7563941d2 100644 --- a/mix/qml/js/QEtherHelper.js +++ b/mix/qml/js/QEtherHelper.js @@ -15,19 +15,3 @@ function createBigInt(_value) return bigint; } -function createString(_value) -{ - var stringComponent = Qt.createComponent("qrc:/qml/QStringType.qml"); - var stringC = stringComponent.createObject(); - stringC.setValue(_value); - return stringC; -} - -function createHash(_value) -{ - var hComponent = Qt.createComponent("qrc:/qml/QHashType.qml"); - var hC = hComponent.createObject(); - hC.setValue(_value); - return hC; -} - diff --git a/mix/qml/qmldir b/mix/qml/qmldir deleted file mode 100644 index 73c117941..000000000 --- a/mix/qml/qmldir +++ /dev/null @@ -1,8 +0,0 @@ -singleton Style 1.0 Style.qml -singleton StateDialogStyle 1.0 StateDialogStyle.qml -singleton ProjectFilesStyle 1.0 ProjectFilesStyle.qml -singleton DebuggerPaneStyle 1.0 DebuggerPaneStyle.qml -singleton StateStyle 1.0 StateStyle.qml -singleton StatusPaneStyle 1.0 StatusPaneStyle.qml -singleton WebPreviewStyle 1.0 WebPreviewStyle.qml -singleton LogsPaneStyle 1.0 LogsPaneStyle.qml diff --git a/mix/test.qrc b/mix/test.qrc new file mode 100644 index 000000000..e7d2271a4 --- /dev/null +++ b/mix/test.qrc @@ -0,0 +1,6 @@ + + + test/TestMain.qml + test/TestTransactionDebug.qml + + diff --git a/mix/test/TestMain.cpp b/mix/test/TestMain.cpp new file mode 100644 index 000000000..f21319004 --- /dev/null +++ b/mix/test/TestMain.cpp @@ -0,0 +1,50 @@ +/* + 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 main.cpp + * @author Yann yann@ethdev.com + * @date 2014 + * Ethereum IDE client. + */ + +#include +#include +#include +#include +#include "MixApplication.h" +#include "Exceptions.h" +#include "TestService.h" + +using namespace dev::mix; + +int main(int _argc, char* _argv[]) +{ + try + { + MixApplication::initialize(); + qmlRegisterType("org.ethereum.qml.TestService", 1, 0, "TestService"); + + return quick_test_main(_argc, _argv, "mix", _argv[1]); + } + catch (boost::exception const& _e) + { + std::cerr << boost::diagnostic_information(_e); + } + catch (std::exception const& _e) + { + std::cerr << _e.what(); + } +} diff --git a/mix/test/TestService.cpp b/mix/test/TestService.cpp new file mode 100644 index 000000000..7ad2bc13d --- /dev/null +++ b/mix/test/TestService.cpp @@ -0,0 +1,194 @@ +/* + 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 TestService.cpp + * @author Arkadiy Paronyan arkadiy@ethdev.com + * @date 2015 + * Ethereum IDE client. + */ + +#include "TestService.h" +#include +#include +#include +#include +#include +#include + +namespace dev +{ +namespace mix +{ + +enum MouseAction { MousePress, MouseRelease, MouseClick, MouseDoubleClick, MouseMove }; + +static void mouseEvent(MouseAction _action, QWindow* _window, QObject* _item, Qt::MouseButton _button, Qt::KeyboardModifiers _stateKey, QPointF _pos, int _delay = -1) +{ + if (_delay == -1 || _delay < 30) + _delay = 30; + if (_delay > 0) + QTest::qWait(_delay); + + if (_action == MouseClick) + { + mouseEvent(MousePress, _window, _item, _button, _stateKey, _pos); + mouseEvent(MouseRelease, _window, _item, _button, _stateKey, _pos); + return; + } + + QPoint pos = _pos.toPoint(); + QQuickItem* sgitem = qobject_cast(_item); + if (sgitem) + pos = sgitem->mapToScene(_pos).toPoint(); + + _stateKey &= static_cast(Qt::KeyboardModifierMask); + + QMouseEvent me(QEvent::User, QPoint(), Qt::LeftButton, _button, _stateKey); + switch (_action) + { + case MousePress: + me = QMouseEvent(QEvent::MouseButtonPress, pos, _window->mapToGlobal(pos), _button, _button, _stateKey); + break; + case MouseRelease: + me = QMouseEvent(QEvent::MouseButtonRelease, pos, _window->mapToGlobal(pos), _button, 0, _stateKey); + break; + case MouseDoubleClick: + me = QMouseEvent(QEvent::MouseButtonDblClick, pos, _window->mapToGlobal(pos), _button, _button, _stateKey); + break; + case MouseMove: + // with move event the _button is NoButton, but 'buttons' holds the currently pressed buttons + me = QMouseEvent(QEvent::MouseMove, pos, _window->mapToGlobal(pos), Qt::NoButton, _button, _stateKey); + break; + default: + break; + } + QSpontaneKeyEvent::setSpontaneous(&me); + if (!qApp->notify(_window, &me)) + { + static const char* mouseActionNames[] = { "MousePress", "MouseRelease", "MouseClick", "MouseDoubleClick", "MouseMove" }; + QString warning = QString::fromLatin1("Mouse event \"%1\" not accepted by receiving window"); + QWARN(warning.arg(QString::fromLatin1(mouseActionNames[static_cast(_action)])).toLatin1().data()); + } +} + +bool TestService::waitForSignal(QObject* _item, QString _signalName, int _timeout) +{ + QSignalSpy spy(_item, ("2" + _signalName.toStdString()).c_str()); + QMetaObject const* mo = _item->metaObject(); + + QStringList methods; + + for (int i = mo->methodOffset(); i < mo->methodCount(); ++i) + if (mo->method(i).methodType() == QMetaMethod::Signal) + methods << QString::fromLatin1(mo->method(i).methodSignature()); + + QElapsedTimer timer; + timer.start(); + + while (!spy.size()) + { + int remaining = _timeout - int(timer.elapsed()); + if (remaining <= 0) + break; + QCoreApplication::processEvents(QEventLoop::AllEvents, remaining); + QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); + QTest::qSleep(10); + } + + return spy.size(); +} + +bool TestService::keyPress(QObject* _item, int _key, int _modifiers, int _delay) +{ + QWindow* window = eventWindow(_item); + QTest::keyPress(window, Qt::Key(_key), Qt::KeyboardModifiers(_modifiers), _delay); + return true; +} + +bool TestService::keyRelease(QObject* _item, int _key, int _modifiers, int _delay) +{ + QWindow* window = eventWindow(_item); + QTest::keyRelease(window, Qt::Key(_key), Qt::KeyboardModifiers(_modifiers), _delay); + return true; +} + +bool TestService::keyClick(QObject* _item, int _key, int _modifiers, int _delay) +{ + QWindow* window = eventWindow(_item); + QTest::keyClick(window, Qt::Key(_key), Qt::KeyboardModifiers(_modifiers), _delay); + return true; +} + +bool TestService::keyPressChar(QObject* _item, QString const& _character, int _modifiers, int _delay) +{ + QWindow* window = eventWindow(_item); + QTest::keyPress(window, _character[0].toLatin1(), Qt::KeyboardModifiers(_modifiers), _delay); + return true; +} + +bool TestService::keyReleaseChar(QObject* _item, QString const& _character, int _modifiers, int _delay) +{ + QWindow* window = eventWindow(_item); + QTest::keyRelease(window, _character[0].toLatin1(), Qt::KeyboardModifiers(_modifiers), _delay); + return true; +} + +bool TestService::keyClickChar(QObject* _item, QString const& _character, int _modifiers, int _delay) +{ + QWindow* window = eventWindow(_item); + QTest::keyClick(window, _character[0].toLatin1(), Qt::KeyboardModifiers(_modifiers), _delay); + return true; +} + +bool TestService::mouseClick(QObject* _item, qreal _x, qreal _y, int _button, int _modifiers, int _delay) +{ + QWindow* window = qobject_cast(_item); + if (!window) + window = eventWindow(_item); + mouseEvent(MouseClick, window, _item, Qt::MouseButton(_button), Qt::KeyboardModifiers(_modifiers), QPointF(_x, _y), _delay); + return true; +} + +void TestService::setTargetWindow(QObject* _window) +{ + QQuickWindow* window = qobject_cast(_window); + if (window) + m_targetWindow = window; + window->requestActivate(); +} + +QWindow* TestService::eventWindow(QObject* _item) +{ + QQuickItem* item = qobject_cast(_item); + if (item && item->window()) + return item->window(); + + QQuickWindow* window = qobject_cast(_item); + if (!window) + window = qobject_cast(m_targetWindow); + if (window) + { + window->requestActivate(); + return window; + } + item = qobject_cast(m_targetWindow); + if (item) + return item->window(); + return 0; +} + +} +} diff --git a/mix/test/TestService.h b/mix/test/TestService.h new file mode 100644 index 000000000..fbf6533e6 --- /dev/null +++ b/mix/test/TestService.h @@ -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 TestService.h + * @author Arkadiy Paronyan arkadiy@ethdev.com + * @date 2015 + * Ethereum IDE client. + */ + +#pragma once + +#include + +class QWindow; + +namespace dev +{ +namespace mix +{ + +class TestService: public QObject +{ + Q_OBJECT + Q_PROPERTY(QObject* targetWindow READ targetWindow WRITE setTargetWindow) + +public: + QObject* targetWindow() const { return m_targetWindow; } + void setTargetWindow(QObject* _window); + +public slots: + bool waitForSignal(QObject* _item, QString _signalName, int _timeout); + bool keyPress(QObject* _item, int _key, int _modifiers, int _delay); + bool keyRelease(QObject* _item, int _key, int _modifiers, int _delay); + bool keyClick(QObject* _item, int _key, int _modifiers, int _delay); + bool keyPressChar(QObject* _item, QString const& _character, int _modifiers, int _delay); + bool keyReleaseChar(QObject* _item, QString const& _character, int _modifiers, int _delay); + bool keyClickChar(QObject* _item, QString const& _character, int _modifiers, int _delay); + bool mouseClick(QObject* _item, qreal _x, qreal _y, int _button, int _modifiers, int _delay); + +private: + QWindow* eventWindow(QObject* _item); + QObject* m_targetWindow; +}; + +} +} diff --git a/mix/test/qml/TestMain.qml b/mix/test/qml/TestMain.qml new file mode 100644 index 000000000..65b5a5076 --- /dev/null +++ b/mix/test/qml/TestMain.qml @@ -0,0 +1,142 @@ +import QtQuick 2.2 +import QtTest 1.1 +import org.ethereum.qml.TestService 1.0 +import "../../qml" + +TestCase +{ + id: tc + TestService + { + id: ts + targetWindow: mainApplication + function typeString(str, el) + { + if (el === undefined) + el = mainApplication; + + for (var c in str) + { + ts.keyPressChar(el, str[c], Qt.NoModifier, 0); + ts.keyReleaseChar(el, str[c], Qt.NoModifier, 0); + } + } + } + + function newProject() + { + waitForRendering(mainApplication.mainContent, 10000); + mainApplication.projectModel.createProject(); + var projectDlg = mainApplication.projectModel.newProjectDialog; + wait(30); + projectDlg.projectTitle = "TestProject"; + projectDlg.pathFieldText = "/tmp/MixTest"; //TODO: get platform temp path + projectDlg.acceptAndClose(); + wait(30); + } + + function editContract(c) + { + mainApplication.mainContent.codeEditor.getEditor("contract.sol").setText(c); + ts.keyPressChar(mainApplication, "S", Qt.ControlModifier, 200); //Ctrl+S + if (!ts.waitForSignal(mainApplication.codeModel, "compilationComplete()", 5000)) + fail("not compiled"); + } + + function clickElement(el, x, y) + { + ts.mouseClick(el, x, y, Qt.LeftButton, Qt.NoModifier, 10) + } + + function test_defaultTransactionSequence() + { + newProject(); + editContract( + "contract Contract {\r" + + " function Contract() {\r" + + " uint x = 69;\r" + + " uint y = 5;\r" + + " for (uint i = 0; i < y; ++i) {\r" + + " x += 42;\r" + + " z += x;\r" + + " }\r" + + " }\r" + + " uint z;\r" + + "}\r" + ); + if (!ts.waitForSignal(mainApplication.clientModel, "runComplete()", 5000)) + fail("not run"); + tryCompare(mainApplication.mainContent.rightPane.transactionLog.transactionModel, "count", 3); + } + + function test_transactionWithParameter() + { + newProject(); + editContract( + "contract Contract {\r" + + " function setZ(uint256 x) {\r" + + " z = x;\r" + + " }\r" + + " function getZ() returns(uint256) {\r" + + " return z;\r" + + " }\r" + + " uint z;\r" + + "}\r" + ); + mainApplication.projectModel.stateListModel.editState(0); + mainApplication.projectModel.stateDialog.model.addTransaction(); + var transactionDialog = mainApplication.projectModel.stateDialog.transactionDialog; + transactionDialog.selectFunction("setZ"); + clickElement(transactionDialog, 140, 300); + ts.typeString("442", transactionDialog); + transactionDialog.acceptAndClose(); + mainApplication.projectModel.stateDialog.model.addTransaction(); + transactionDialog.selectFunction("getZ"); + transactionDialog.acceptAndClose(); + mainApplication.projectModel.stateDialog.acceptAndClose(); + mainApplication.mainContent.startQuickDebugging(); + wait(1); + if (!ts.waitForSignal(mainApplication.clientModel, "runComplete()", 5000)) + fail("not run"); + tryCompare(mainApplication.mainContent.rightPane.transactionLog.transactionModel, "count", 5); + tryCompare(mainApplication.mainContent.rightPane.transactionLog.transactionModel.get(4), "returned", "(442)"); + } + + function test_constructorParameters() + { + newProject(); + editContract( + "contract Contract {\r" + + " function Contract(uint256 x) {\r" + + " z = x;\r" + + " }\r" + + " function getZ() returns(uint256) {\r" + + " return z;\r" + + " }\r" + + " uint z;\r" + + "}\r" + ); + mainApplication.projectModel.stateListModel.editState(0); + mainApplication.projectModel.stateDialog.model.editTransaction(2); + var transactionDialog = mainApplication.projectModel.stateDialog.transactionDialog; + clickElement(transactionDialog, 140, 300); + ts.typeString("442", transactionDialog); + transactionDialog.acceptAndClose(); + mainApplication.projectModel.stateDialog.model.addTransaction(); + transactionDialog.selectFunction("getZ"); + transactionDialog.acceptAndClose(); + mainApplication.projectModel.stateDialog.acceptAndClose(); + wait(1); + mainApplication.mainContent.startQuickDebugging(); + if (!ts.waitForSignal(mainApplication.clientModel, "runComplete()", 5000)) + fail("not run"); + tryCompare(mainApplication.mainContent.rightPane.transactionLog.transactionModel, "count", 4); + tryCompare(mainApplication.mainContent.rightPane.transactionLog.transactionModel.get(3), "returned", "(442)"); + } + + Application + { + id: mainApplication + } +} + diff --git a/mix/web.qrc b/mix/web.qrc index 63a3a668f..6870411c5 100644 --- a/mix/web.qrc +++ b/mix/web.qrc @@ -1,44 +1,44 @@ - - qml/WebPreview.qml - qml/html/WebContainer.html - qml/html/cm/active-line.js - qml/html/codeeditor.html - qml/html/cm/codemirror.css - qml/html/cm/codemirror.js - qml/html/cm/javascript.js - qml/html/cm/matchbrackets.js - qml/WebCodeEditor.qml - qml/html/codeeditor.js - qml/html/cm/fullscreen.css - qml/html/cm/fullscreen.js - qml/html/cm/solarized.css - qml/html/cm/xml.js - qml/html/cm/htmlmixed.js - qml/html/cm/css.js - qml/html/cm/solidity.js - qml/html/cm/dialog.css - qml/html/cm/dialog.js - qml/html/cm/search.js - qml/html/cm/searchcursor.js - qml/html/cm/anyword-hint.js - qml/html/cm/show-hint.js - qml/html/cm/show-hint.css - qml/html/cm/closebrackets.js - qml/html/cm/solidityToken.js - qml/html/cm/javascript-hint.js - qml/html/cm/errorannotation.js - qml/html/cm/tern.js - qml/html/cm/ecma5spec.js - qml/html/cm/comment.js - qml/html/cm/def.js - qml/html/cm/doc_comment.js - qml/html/cm/infer.js - qml/html/cm/signal.js - qml/html/cm/ternserver.js - qml/html/cm/acorn.js - qml/html/cm/acorn_loose.js - qml/html/cm/walk.js - qml/html/cm/mark-selection.js - + + qml/WebCodeEditor.qml + qml/WebPreview.qml + qml/html/WebContainer.html + qml/html/cm/acorn.js + qml/html/cm/acorn_loose.js + qml/html/cm/active-line.js + qml/html/cm/anyword-hint.js + qml/html/cm/closebrackets.js + qml/html/cm/codemirror.css + qml/html/cm/codemirror.js + qml/html/cm/comment.js + qml/html/cm/css.js + qml/html/cm/def.js + qml/html/cm/dialog.css + qml/html/cm/dialog.js + qml/html/cm/doc_comment.js + qml/html/cm/ecma5spec.js + qml/html/cm/errorannotation.js + qml/html/cm/fullscreen.css + qml/html/cm/fullscreen.js + qml/html/cm/htmlmixed.js + qml/html/cm/infer.js + qml/html/cm/javascript-hint.js + qml/html/cm/javascript.js + qml/html/cm/mark-selection.js + qml/html/cm/matchbrackets.js + qml/html/cm/search.js + qml/html/cm/searchcursor.js + qml/html/cm/show-hint.css + qml/html/cm/show-hint.js + qml/html/cm/signal.js + qml/html/cm/solarized.css + qml/html/cm/solidity.js + qml/html/cm/solidityToken.js + qml/html/cm/tern.js + qml/html/cm/ternserver.js + qml/html/cm/walk.js + qml/html/cm/xml.js + qml/html/codeeditor.html + qml/html/codeeditor.js + diff --git a/neth/main.cpp b/neth/main.cpp index e7dde3cc6..7ee4962e9 100644 --- a/neth/main.cpp +++ b/neth/main.cpp @@ -550,7 +550,7 @@ int main(int argc, char** argv) dev::WebThreeDirect web3( clientImplString, dbPath, - killChain, + killChain ? WithExisting::Kill : WithExisting::Trust, mode == NodeMode::Full ? set{"eth", "shh"} : set(), netPrefs, &nodesState, diff --git a/test/Assembly.cpp b/test/Assembly.cpp index fbc8e47b5..fab260a9d 100644 --- a/test/Assembly.cpp +++ b/test/Assembly.cpp @@ -20,6 +20,8 @@ * Unit tests for Assembly Items from evmcore/Assembly.h */ +#if ETH_SOLIDITY + #include #include #include @@ -119,3 +121,4 @@ BOOST_AUTO_TEST_SUITE_END() } } // end namespaces +#endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 01681dbef..90af5122e 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -44,9 +44,11 @@ target_link_libraries(testeth ${CURL_LIBRARIES}) target_link_libraries(testeth ethereum) target_link_libraries(testeth ethcore) target_link_libraries(testeth secp256k1) -target_link_libraries(testeth solidity) +if (SOLIDITY) + target_link_libraries(testeth solidity) +endif () target_link_libraries(testeth testutils) -if (NOT HEADLESS AND NOT JUSTTESTS) +if (GUI AND NOT JUSTTESTS) target_link_libraries(testeth webthree) target_link_libraries(testeth natspec) endif() diff --git a/test/ClientBase.cpp b/test/ClientBase.cpp index 304182cfc..7597b6612 100644 --- a/test/ClientBase.cpp +++ b/test/ClientBase.cpp @@ -120,11 +120,15 @@ BOOST_AUTO_TEST_CASE(blocks) ETH_CHECK_EQUAL(expectedBlockInfoBloom, _blockInfo.logBloom); ETH_CHECK_EQUAL(expectedBlockInfoCoinbase, _blockInfo.coinbaseAddress); ETH_CHECK_EQUAL(expectedBlockInfoDifficulty, _blockInfo.difficulty); - ETH_CHECK_EQUAL_COLLECTIONS(expectedBlockInfoExtraData.begin(), expectedBlockInfoExtraData.end(), - _blockInfo.extraData.begin(), _blockInfo.extraData.end()); + ETH_CHECK_EQUAL_COLLECTIONS( + expectedBlockInfoExtraData.begin(), + expectedBlockInfoExtraData.end(), + _blockInfo.extraData.begin(), + _blockInfo.extraData.end() + ); ETH_CHECK_EQUAL(expectedBlockInfoGasLimit, _blockInfo.gasLimit); ETH_CHECK_EQUAL(expectedBlockInfoGasUsed, _blockInfo.gasUsed); - ETH_CHECK_EQUAL(expectedBlockInfoHash, _blockInfo.hash); + ETH_CHECK_EQUAL(expectedBlockInfoHash, _blockInfo.hash()); ETH_CHECK_EQUAL(expectedBlockInfoMixHash, _blockInfo.mixHash); ETH_CHECK_EQUAL(expectedBlockInfoNonce, _blockInfo.nonce); ETH_CHECK_EQUAL(expectedBlockInfoNumber, _blockInfo.number); @@ -155,8 +159,12 @@ BOOST_AUTO_TEST_CASE(blocks) u256 expectedTransactionSignatureS = h256(fromHex(_t["s"].asString())); // unsigned expectedTransactionSignatureV = jsToInt(t["v"].asString()); - ETH_CHECK_EQUAL_COLLECTIONS(expectedTransactionData.begin(), expectedTransactionData.end(), - _transaction.data().begin(), _transaction.data().end()); + ETH_CHECK_EQUAL_COLLECTIONS( + expectedTransactionData.begin(), + expectedTransactionData.end(), + _transaction.data().begin(), + _transaction.data().end() + ); ETH_CHECK_EQUAL(expectedTransactionGasLimit, _transaction.gas()); ETH_CHECK_EQUAL(expectedTransactionGasPrice, _transaction.gasPrice()); ETH_CHECK_EQUAL(expectedTransactionNonce, _transaction.nonce()); diff --git a/test/SolidityABIJSON.cpp b/test/SolidityABIJSON.cpp index f7f968ea8..bbe5fd8c4 100644 --- a/test/SolidityABIJSON.cpp +++ b/test/SolidityABIJSON.cpp @@ -19,6 +19,7 @@ * @date 2014 * Unit tests for the solidity compiler JSON Interface output. */ +#if ETH_SOLIDITY #include "TestHelper.h" #include @@ -500,3 +501,5 @@ BOOST_AUTO_TEST_SUITE_END() } } } + +#endif diff --git a/test/SolidityCompiler.cpp b/test/SolidityCompiler.cpp index 1369b038f..bb16c88cd 100644 --- a/test/SolidityCompiler.cpp +++ b/test/SolidityCompiler.cpp @@ -20,6 +20,8 @@ * Unit tests for the solidity compiler. */ +#if ETH_SOLIDITY + #include #include #include @@ -192,3 +194,4 @@ BOOST_AUTO_TEST_SUITE_END() } } // end namespaces +#endif diff --git a/test/SolidityEndToEndTest.cpp b/test/SolidityEndToEndTest.cpp index ea6ada603..b4da07892 100644 --- a/test/SolidityEndToEndTest.cpp +++ b/test/SolidityEndToEndTest.cpp @@ -1,4 +1,3 @@ - /* This file is part of cpp-ethereum. @@ -22,6 +21,8 @@ * Unit tests for the solidity expression compiler, testing the behaviour of the code. */ +#if ETH_SOLIDITY + #include #include #include @@ -3627,3 +3628,4 @@ BOOST_AUTO_TEST_SUITE_END() } } // end namespaces +#endif diff --git a/test/SolidityExpressionCompiler.cpp b/test/SolidityExpressionCompiler.cpp index 7034085ef..b748d887d 100644 --- a/test/SolidityExpressionCompiler.cpp +++ b/test/SolidityExpressionCompiler.cpp @@ -1,4 +1,3 @@ - /* This file is part of cpp-ethereum. @@ -21,6 +20,8 @@ * Unit tests for the solidity expression compiler. */ +#if ETH_SOLIDITY + #include #include @@ -491,3 +492,4 @@ BOOST_AUTO_TEST_SUITE_END() } } // end namespaces +#endif diff --git a/test/SolidityInterface.cpp b/test/SolidityInterface.cpp index 48e2fd7aa..c836f0fa7 100644 --- a/test/SolidityInterface.cpp +++ b/test/SolidityInterface.cpp @@ -20,6 +20,8 @@ * Unit tests for generating source interfaces for Solidity contracts. */ +#if ETH_SOLIDITY + #include "TestHelper.h" #include #include @@ -147,3 +149,5 @@ BOOST_AUTO_TEST_SUITE_END() } } } + +#endif diff --git a/test/SolidityNameAndTypeResolution.cpp b/test/SolidityNameAndTypeResolution.cpp index 531f3bc13..74a488883 100644 --- a/test/SolidityNameAndTypeResolution.cpp +++ b/test/SolidityNameAndTypeResolution.cpp @@ -20,6 +20,8 @@ * Unit tests for the name and type resolution of the solidity parser. */ +#if ETH_SOLIDITY + #include #include @@ -1627,3 +1629,4 @@ BOOST_AUTO_TEST_SUITE_END() } } // end namespaces +#endif diff --git a/test/SolidityNatspecJSON.cpp b/test/SolidityNatspecJSON.cpp index aeaad1966..28d657357 100644 --- a/test/SolidityNatspecJSON.cpp +++ b/test/SolidityNatspecJSON.cpp @@ -20,6 +20,8 @@ * Unit tests for the solidity compiler JSON Interface output. */ +#if ETH_SOLIDITY + #include "TestHelper.h" #include #include @@ -537,3 +539,5 @@ BOOST_AUTO_TEST_SUITE_END() } } } + +#endif diff --git a/test/SolidityOptimizer.cpp b/test/SolidityOptimizer.cpp index e69d5120e..4fedd642d 100644 --- a/test/SolidityOptimizer.cpp +++ b/test/SolidityOptimizer.cpp @@ -1,4 +1,3 @@ - /* This file is part of cpp-ethereum. @@ -21,6 +20,8 @@ * Tests for the Solidity optimizer. */ +#if ETH_SOLIDITY + #include #include #include @@ -573,3 +574,5 @@ BOOST_AUTO_TEST_SUITE_END() } } } // end namespaces + +#endif diff --git a/test/SolidityParser.cpp b/test/SolidityParser.cpp index 7640f91ad..b76f00656 100644 --- a/test/SolidityParser.cpp +++ b/test/SolidityParser.cpp @@ -20,6 +20,8 @@ * Unit tests for the solidity parser. */ +#if ETH_SOLIDITY + #include #include #include @@ -845,3 +847,4 @@ BOOST_AUTO_TEST_SUITE_END() } } // end namespaces +#endif diff --git a/test/SolidityScanner.cpp b/test/SolidityScanner.cpp index 8d3e53929..20b946ee0 100644 --- a/test/SolidityScanner.cpp +++ b/test/SolidityScanner.cpp @@ -20,6 +20,8 @@ * Unit tests for the solidity scanner. */ +#if ETH_SOLIDITY + #include #include @@ -286,3 +288,5 @@ BOOST_AUTO_TEST_SUITE_END() } } } // end namespaces + +#endif diff --git a/test/SolidityTypes.cpp b/test/SolidityTypes.cpp index 6b6306479..da8b48303 100644 --- a/test/SolidityTypes.cpp +++ b/test/SolidityTypes.cpp @@ -20,6 +20,8 @@ * Unit tests for the type system of Solidity. */ +#if ETH_SOLIDITY + #include #include @@ -91,3 +93,5 @@ BOOST_AUTO_TEST_SUITE_END() } } } + +#endif diff --git a/test/Stats.cpp b/test/Stats.cpp index fa615cb50..e76d1ee00 100644 --- a/test/Stats.cpp +++ b/test/Stats.cpp @@ -19,6 +19,7 @@ #include #include +#include namespace dev { @@ -31,6 +32,11 @@ Stats& Stats::get() return instance; } +void Stats::suiteStarted(std::string const& _name) +{ + m_currentSuite = _name; +} + void Stats::testStarted(std::string const& _name) { m_currentTest = _name; @@ -39,7 +45,7 @@ void Stats::testStarted(std::string const& _name) void Stats::testFinished() { - m_stats[clock::now() - m_tp] = std::move(m_currentTest); + m_stats.push_back({clock::now() - m_tp, m_currentSuite + "/" + m_currentTest}); } std::ostream& operator<<(std::ostream& out, Stats::clock::duration const& d) @@ -52,31 +58,42 @@ Stats::~Stats() if (m_stats.empty()) return; + std::sort(m_stats.begin(), m_stats.end(), [](Stats::Item const& a, Stats::Item const& b){ + return a.duration < b.duration; + }); + auto& out = std::cout; auto itr = m_stats.begin(); auto min = *itr; auto max = *m_stats.rbegin(); std::advance(itr, m_stats.size() / 2); auto med = *itr; - auto tot = std::accumulate(m_stats.begin(), m_stats.end(), clock::duration{}, [](clock::duration const& a, stats_t::value_type const& v) + auto tot = std::accumulate(m_stats.begin(), m_stats.end(), clock::duration{}, [](clock::duration const& a, Stats::Item const& v) { - return a + v.first; + return a + v.duration; }); out << "\nSTATS:\n\n" << std::setfill(' '); - if (Options::get().statsFull) + if (Options::get().statsOutFile == "out") { for (auto&& s: m_stats) - out << " " << std::setw(40) << std::left << s.second.substr(0, 40) << s.first << " \n"; + out << " " << std::setw(40) << std::left << s.name.substr(0, 40) << s.duration << " \n"; out << "\n"; } + else if (!Options::get().statsOutFile.empty()) + { + // Output stats to file + std::ofstream file{Options::get().statsOutFile}; + for (auto&& s: m_stats) + file << s.name << "\t" << std::chrono::duration_cast(s.duration).count() << "\n"; + } out << " tot: " << tot << "\n" << " avg: " << (tot / m_stats.size()) << "\n\n" - << " min: " << min.first << " (" << min.second << ")\n" - << " med: " << med.first << " (" << med.second << ")\n" - << " max: " << max.first << " (" << max.second << ")\n"; + << " min: " << min.duration << " (" << min.name << ")\n" + << " med: " << med.duration << " (" << med.name << ")\n" + << " max: " << max.duration << " (" << max.name << ")\n"; } } diff --git a/test/Stats.h b/test/Stats.h index 7692a2b30..9b40c5fce 100644 --- a/test/Stats.h +++ b/test/Stats.h @@ -18,7 +18,7 @@ #pragma once #include -#include +#include #include "TestHelper.h" @@ -31,19 +31,26 @@ class Stats: public Listener { public: using clock = std::chrono::high_resolution_clock; - using stats_t = std::map; + + struct Item + { + clock::duration duration; + std::string name; + }; static Stats& get(); ~Stats(); + void suiteStarted(std::string const& _name) override; void testStarted(std::string const& _name) override; void testFinished() override; private: clock::time_point m_tp; + std::string m_currentSuite; std::string m_currentTest; - stats_t m_stats; + std::vector m_stats; }; } diff --git a/test/TestHelper.cpp b/test/TestHelper.cpp index 7dc001498..dd7c09eab 100644 --- a/test/TestHelper.cpp +++ b/test/TestHelper.cpp @@ -29,6 +29,7 @@ #include #include #include +#include "Stats.h" using namespace std; using namespace dev::eth; @@ -69,7 +70,10 @@ namespace test struct ValueTooLarge: virtual Exception {}; bigint const c_max256plus1 = bigint(1) << 256; -ImportTest::ImportTest(json_spirit::mObject& _o, bool isFiller) : m_statePre(Address(_o["env"].get_obj()["currentCoinbase"].get_str()), OverlayDB(), eth::BaseState::Empty), m_statePost(Address(_o["env"].get_obj()["currentCoinbase"].get_str()), OverlayDB(), eth::BaseState::Empty), m_TestObject(_o) +ImportTest::ImportTest(json_spirit::mObject& _o, bool isFiller): + m_statePre(OverlayDB(), eth::BaseState::Empty, Address(_o["env"].get_obj()["currentCoinbase"].get_str())), + m_statePost(OverlayDB(), eth::BaseState::Empty, Address(_o["env"].get_obj()["currentCoinbase"].get_str())), + m_TestObject(_o) { importEnv(_o["env"].get_obj()); importState(_o["pre"].get_obj(), m_statePre); @@ -91,7 +95,7 @@ void ImportTest::importEnv(json_spirit::mObject& _o) assert(_o.count("currentCoinbase") > 0); assert(_o.count("currentNumber") > 0); - m_environment.previousBlock.hash = h256(_o["previousHash"].get_str()); + m_environment.currentBlock.parentHash = h256(_o["previousHash"].get_str()); m_environment.currentBlock.number = toInt(_o["currentNumber"]); m_environment.currentBlock.gasLimit = toInt(_o["currentGasLimit"]); m_environment.currentBlock.difficulty = toInt(_o["currentDifficulty"]); @@ -431,6 +435,9 @@ void executeTests(const string& _name, const string& _testPathAppendix, std::fun string testPath = getTestPath(); testPath += _testPathAppendix; + if (Options::get().stats) + Listener::registerListener(Stats::get()); + if (Options::get().fillTests) { try @@ -462,6 +469,7 @@ void executeTests(const string& _name, const string& _testPathAppendix, std::fun string s = asString(dev::contents(testPath + "/" + _name + ".json")); BOOST_REQUIRE_MESSAGE(s.length() > 0, "Contents of " + testPath + "/" + _name + ".json is empty. Have you cloned the 'tests' repo branch develop and set ETHEREUM_TEST_PATH to its path?"); json_spirit::read_string(s, v); + Listener::notifySuiteStarted(_name); doTests(v, false); } catch (Exception const& _e) @@ -535,10 +543,12 @@ Options::Options() vmtrace = true; else if (arg == "--filltests") fillTests = true; - else if (arg == "--stats") + else if (arg.compare(0, 7, "--stats") == 0) + { stats = true; - else if (arg == "--stats=full") - stats = statsFull = true; + if (arg.size() > 7) + statsOutFile = arg.substr(8); // skip '=' char + } else if (arg == "--performance") performance = true; else if (arg == "--quadratic") @@ -586,6 +596,12 @@ void Listener::registerListener(Listener& _listener) g_listener = &_listener; } +void Listener::notifySuiteStarted(std::string const& _name) +{ + if (g_listener) + g_listener->suiteStarted(_name); +} + void Listener::notifyTestStarted(std::string const& _name) { if (g_listener) diff --git a/test/TestHelper.h b/test/TestHelper.h index 022c715f8..7f6d73365 100644 --- a/test/TestHelper.h +++ b/test/TestHelper.h @@ -101,7 +101,7 @@ namespace test class ImportTest { public: - ImportTest(json_spirit::mObject& _o) : m_statePre(Address(), OverlayDB(), eth::BaseState::Empty), m_statePost(Address(), OverlayDB(), eth::BaseState::Empty), m_TestObject(_o) {} + ImportTest(json_spirit::mObject& _o): m_TestObject(_o) {} ImportTest(json_spirit::mObject& _o, bool isFiller); // imports void importEnv(json_spirit::mObject& _o); @@ -164,7 +164,7 @@ public: bool vmtrace = false; ///< Create EVM execution tracer // TODO: Link with log verbosity? bool fillTests = false; ///< Create JSON test files from execution results bool stats = false; ///< Execution time stats - bool statsFull = false; ///< Output full stats - execution times for every test + std::string statsOutFile; ///< Stats output file. "out" for standard output /// Test selection /// @{ @@ -191,10 +191,12 @@ class Listener public: virtual ~Listener() = default; + virtual void suiteStarted(std::string const&) {} virtual void testStarted(std::string const& _name) = 0; virtual void testFinished() = 0; static void registerListener(Listener& _listener); + static void notifySuiteStarted(std::string const& _name); static void notifyTestStarted(std::string const& _name); static void notifyTestFinished(); diff --git a/test/blockchain.cpp b/test/blockchain.cpp index 17e6c3588..50c17bdee 100644 --- a/test/blockchain.cpp +++ b/test/blockchain.cpp @@ -52,7 +52,7 @@ void doBlockchainTests(json_spirit::mValue& _v, bool _fillin) BOOST_REQUIRE(o.count("pre")); ImportTest importer(o["pre"].get_obj()); - State state(biGenesisBlock.coinbaseAddress, OverlayDB(), BaseState::Empty); + State state(OverlayDB(), BaseState::Empty, biGenesisBlock.coinbaseAddress); importer.importState(o["pre"].get_obj(), state); o["pre"] = fillJsonWithState(state); state.commit(); @@ -78,7 +78,7 @@ void doBlockchainTests(json_spirit::mValue& _v, bool _fillin) // construct blockchain TransientDirectory td; - BlockChain bc(rlpGenesisBlock.out(), td.path(), true); + BlockChain bc(rlpGenesisBlock.out(), td.path(), WithExisting::Kill); if (_fillin) { @@ -98,7 +98,7 @@ void doBlockchainTests(json_spirit::mValue& _v, bool _fillin) { mObject tx = txObj.get_obj(); importer.importTransaction(tx); - if (!txs.attemptImport(importer.m_transaction.rlp())) + if (txs.import(importer.m_transaction.rlp()) != ImportResult::Success) cnote << "failed importing transaction\n"; } @@ -599,7 +599,7 @@ void updatePoW(BlockInfo& _bi) ret = pow.mine(_bi, 10000, true, true); Ethash::assignResult(ret.second, _bi); } - _bi.hash = _bi.headerHash(WithNonce); + _bi.noteDirty(); } void writeBlockHeaderToJson(mObject& _o, BlockInfo const& _bi) @@ -619,7 +619,7 @@ void writeBlockHeaderToJson(mObject& _o, BlockInfo const& _bi) _o["extraData"] ="0x" + toHex(_bi.extraData); _o["mixHash"] = toString(_bi.mixHash); _o["nonce"] = toString(_bi.nonce); - _o["hash"] = toString(_bi.hash); + _o["hash"] = toString(_bi.hash()); } RLPStream createFullBlockFromHeader(BlockInfo const& _bi, bytes const& _txs, bytes const& _uncles) diff --git a/test/dagger.cpp b/test/dagger.cpp index 4dda9c4fc..4abba5090 100644 --- a/test/dagger.cpp +++ b/test/dagger.cpp @@ -64,7 +64,7 @@ BOOST_AUTO_TEST_CASE(basic_test) unsigned cacheSize(o["cache_size"].get_int()); h256 cacheHash(o["cache_hash"].get_str()); BOOST_REQUIRE_EQUAL(Ethasher::get()->params(header).cache_size, cacheSize); - BOOST_REQUIRE_EQUAL(sha3(bytesConstRef((byte const*)Ethasher::get()->cache(header), cacheSize)), cacheHash); + BOOST_REQUIRE_EQUAL(sha3(bytesConstRef((byte const*)Ethasher::get()->light(header), cacheSize)), cacheHash); #if TEST_FULL unsigned fullSize(o["full_size"].get_int()); diff --git a/test/natspec.cpp b/test/natspec.cpp index cdcedca46..56478f93e 100644 --- a/test/natspec.cpp +++ b/test/natspec.cpp @@ -19,7 +19,7 @@ * @date 2015 */ -#if !ETH_HEADLESS +#if ETH_GUI #include #include diff --git a/test/solidityExecutionFramework.h b/test/solidityExecutionFramework.h index 2451aa381..2134d424d 100644 --- a/test/solidityExecutionFramework.h +++ b/test/solidityExecutionFramework.h @@ -1,4 +1,3 @@ - /* This file is part of cpp-ethereum. diff --git a/test/stMemoryTestFiller.json b/test/stMemoryTestFiller.json index c1754d52f..23f52b657 100644 --- a/test/stMemoryTestFiller.json +++ b/test/stMemoryTestFiller.json @@ -1461,7 +1461,7 @@ } }, - "stackLimitPush32_1024": { + "stackLimitPush32_1023": { "env" : { "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", "currentNumber" : "0", @@ -1495,7 +1495,7 @@ } }, - "stackLimitPush32_1025": { + "stackLimitPush32_1024": { "env" : { "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", "currentNumber" : "0", @@ -1529,7 +1529,41 @@ } }, - "stackLimitPush31_1024": { + "stackLimitPush32_1025": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "42949672960", + "currentDifficulty" : "256", + "currentTimestamp" : "1", + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : "0", + "code" : "(asm 1023 0x00 MSTORE JUMPDEST 0x0102030405060708090a0102030405060708090a0102030405060708090a0102 0x01 0x00 MLOAD SUB 0x00 MSTORE 0x00 MLOAD 0x06 JUMPI STOP )", + "storage": {} + }, + "a94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "balance" : "429496729600", + "nonce" : "0", + "code" : "", + "storage": {} + } + }, + "transaction" : { + "nonce" : "0", + "gasPrice" : "1", + "gasLimit" : "100000", + "to" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "value" : "10", + "secretKey" : "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8", + "data" : "" + } + }, + + "stackLimitPush31_1023": { "env" : { "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", "currentNumber" : "0", @@ -1563,7 +1597,7 @@ } }, - "stackLimitPush31_1025": { + "stackLimitPush31_1024": { "env" : { "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", "currentNumber" : "0", @@ -1597,7 +1631,41 @@ } }, - "stackLimitGas_1024": { + "stackLimitPush31_1025": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "42949672960", + "currentDifficulty" : "256", + "currentTimestamp" : "1", + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : "0", + "code" : "(asm 1023 0x00 MSTORE JUMPDEST 0x0102030405060708090a0102030405060708090a0102030405060708090a01 0x01 0x00 MLOAD SUB 0x00 MSTORE 0x00 MLOAD 0x06 JUMPI STOP )", + "storage": {} + }, + "a94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "balance" : "429496729600", + "nonce" : "0", + "code" : "", + "storage": {} + } + }, + "transaction" : { + "nonce" : "0", + "gasPrice" : "1", + "gasLimit" : "100000", + "to" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "value" : "10", + "secretKey" : "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8", + "data" : "" + } + }, + + "stackLimitGas_1023": { "env" : { "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", "currentNumber" : "0", @@ -1631,7 +1699,7 @@ } }, - "stackLimitGas_1025": { + "stackLimitGas_1024": { "env" : { "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", "currentNumber" : "0", @@ -1665,6 +1733,40 @@ } }, + "stackLimitGas_1025": { + "env" : { + "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", + "currentNumber" : "0", + "currentGasLimit" : "42949672960", + "currentDifficulty" : "256", + "currentTimestamp" : "1", + "currentCoinbase" : "2adc25665018aa1fe0e6bc666dac8fc2697ff9ba" + }, + "pre" : { + "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6" : { + "balance" : "1000000000000000000", + "nonce" : "0", + "code" : "(asm 1023 0x00 MSTORE JUMPDEST GAS 0x01 0x00 MLOAD SUB 0x00 MSTORE 0x00 MLOAD 0x06 JUMPI STOP )", + "storage": {} + }, + "a94f5374fce5edbc8e2a8697c15331677e6ebf0b" : { + "balance" : "429496729600", + "nonce" : "0", + "code" : "", + "storage": {} + } + }, + "transaction" : { + "nonce" : "0", + "gasPrice" : "1", + "gasLimit" : "100000", + "to" : "0f572e5295c57f15886f9b263e2f6d2d6c7b5ec6", + "value" : "10", + "secretKey" : "45a915e4d060149eb4365960e6a7a45f334393093061116b197e3240065ff2d8", + "data" : "" + } + }, + "mstroe8_dejavu": { "env" : { "previousHash" : "5e20a0453cecd065ea59c37ac63e079ee08998b6045136a8ce6635c7912ec0b6", diff --git a/test/state.cpp b/test/state.cpp index 7c586ec7d..e4bf06bdc 100644 --- a/test/state.cpp +++ b/test/state.cpp @@ -31,7 +31,6 @@ #include #include #include "TestHelper.h" -#include "Stats.h" using namespace std; using namespace json_spirit; @@ -42,9 +41,6 @@ namespace dev { namespace test { void doStateTests(json_spirit::mValue& v, bool _fillin) { - if (Options::get().stats) - Listener::registerListener(Stats::get()); - for (auto& i: v.get_obj()) { std::cout << " " << i.first << "\n"; @@ -254,6 +250,7 @@ BOOST_AUTO_TEST_CASE(stRandom) string s = asString(dev::contents(path.string())); BOOST_REQUIRE_MESSAGE(s.length() > 0, "Content of " + path.string() + " is empty. Have you cloned the 'tests' repo branch develop and set ETHEREUM_TEST_PATH to its path?"); json_spirit::read_string(s, v); + test::Listener::notifySuiteStarted(path.filename().string()); dev::test::doStateTests(v, false); } catch (Exception const& _e) diff --git a/test/stateOriginal.cpp b/test/stateOriginal.cpp index 384d85344..572e84dcf 100644 --- a/test/stateOriginal.cpp +++ b/test/stateOriginal.cpp @@ -58,7 +58,7 @@ BOOST_AUTO_TEST_CASE(Complex) CanonBlockChain bc; cout << bc; - State s(myMiner.address(), stateDB); + State s(stateDB, BaseState::Empty, myMiner.address()); cout << s; // Sync up - this won't do much until we use the last state. diff --git a/test/vm.cpp b/test/vm.cpp index 2bdafb270..ff8903523 100644 --- a/test/vm.cpp +++ b/test/vm.cpp @@ -25,7 +25,6 @@ #include #include #include "vm.h" -#include "Stats.h" using namespace std; using namespace json_spirit; @@ -97,7 +96,7 @@ void FakeExtVM::push(mArray& a, u256 _v) mObject FakeExtVM::exportEnv() { mObject ret; - ret["previousHash"] = toString(previousBlock.hash); + ret["previousHash"] = toString(currentBlock.parentHash); push(ret, "currentDifficulty", currentBlock.difficulty); push(ret, "currentTimestamp", currentBlock.timestamp); ret["currentCoinbase"] = toString(currentBlock.coinbaseAddress); @@ -116,7 +115,7 @@ void FakeExtVM::importEnv(mObject& _o) assert(_o.count("currentCoinbase") > 0); assert(_o.count("currentNumber") > 0); - previousBlock.hash = h256(_o["previousHash"].get_str()); + currentBlock.parentHash = h256(_o["previousHash"].get_str()); currentBlock.number = toInt(_o["currentNumber"]); lastHashes = test::lastHashes(currentBlock.number); currentBlock.gasLimit = toInt(_o["currentGasLimit"]); @@ -311,9 +310,6 @@ namespace dev { namespace test { void doVMTests(json_spirit::mValue& v, bool _fillin) { - if (Options::get().stats) - Listener::registerListener(Stats::get()); - for (auto& i: v.get_obj()) { std::cout << " " << i.first << "\n"; @@ -549,6 +545,7 @@ BOOST_AUTO_TEST_CASE(vmRandom) string s = asString(dev::contents(path.string())); BOOST_REQUIRE_MESSAGE(s.length() > 0, "Content of " + path.string() + " is empty. Have you cloned the 'tests' repo branch develop and set ETHEREUM_TEST_PATH to its path?"); json_spirit::read_string(s, v); + test::Listener::notifySuiteStarted(path.filename().string()); doVMTests(v, false); } catch (Exception const& _e) diff --git a/test/webthreestubclient.h b/test/webthreestubclient.h index 4754bbbef..a460ddda4 100644 --- a/test/webthreestubclient.h +++ b/test/webthreestubclient.h @@ -62,6 +62,16 @@ class WebThreeStubClient : public jsonrpc::Client else throw jsonrpc::JsonRpcException(jsonrpc::Errors::ERROR_CLIENT_INVALID_RESPONSE, result.toStyledString()); } + std::string eth_protocolVersion() throw (jsonrpc::JsonRpcException) + { + Json::Value p; + p = Json::nullValue; + Json::Value result = this->CallMethod("eth_protocolVersion",p); + if (result.isString()) + return result.asString(); + else + throw jsonrpc::JsonRpcException(jsonrpc::Errors::ERROR_CLIENT_INVALID_RESPONSE, result.toStyledString()); + } std::string eth_coinbase() throw (jsonrpc::JsonRpcException) { Json::Value p;