diff --git a/.gitignore b/.gitignore index 8d8974809..43ed10adf 100644 --- a/.gitignore +++ b/.gitignore @@ -64,6 +64,8 @@ profile DerivedData project.pbxproj +# JetBrains stuff +.idea/ doc/html *.autosave diff --git a/CMakeLists.txt b/CMakeLists.txt index 4603bb898..2da01d20f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -14,35 +14,39 @@ 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() # 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) + add_definitions(-DETH_ETHASHCL) + endif() + if (EVMJIT) add_definitions(-DETH_EVMJIT) endif() @@ -55,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() @@ -117,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() @@ -124,9 +139,133 @@ 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}") +# 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 () +# Backwards compatibility +if (HEADLESS) + set(BUNDLE "minimal") +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 ${DECENT_PLATFORM}) +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 ${DECENT_PLATFORM}) +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 ${DECENT_PLATFORM}) + 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 ${DECENT_PLATFORM}) + 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") @@ -134,6 +273,32 @@ 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 () @@ -154,20 +319,23 @@ if (EVMJIT) endif() add_subdirectory(libdevcore) -add_subdirectory(rlp) 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) @@ -180,44 +348,54 @@ add_subdirectory(libdevcrypto) add_subdirectory(libwhisper) add_subdirectory(libethash) +if (ETHASHCL) + add_subdirectory(libethash-cl) +endif () + add_subdirectory(libethcore) add_subdirectory(libevm) add_subdirectory(libethereum) - add_subdirectory(libwebthree) -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) add_subdirectory(eth) if("x${CMAKE_BUILD_TYPE}" STREQUAL "xDebug") 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() -endif() + if (SOLIDITY) + add_subdirectory(mix) + endif () -enable_testing() -add_test(NAME alltests WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/test COMMAND testeth) +endif() #unset(TARGET_PLATFORM CACHE) diff --git a/CodingStandards.txt b/CodingStandards.txt index e1a1b3ba9..a98a74c78 100644 --- a/CodingStandards.txt +++ b/CodingStandards.txt @@ -13,7 +13,7 @@ c. Don't use braces for condition-body one-liners. d. Never place condition bodies on same line as condition. e. Space between first paren and keyword, but *not* following first paren or preceeding final paren. f. No spaces when fewer than intra-expression three parens together; when three or more, space according to clarity. -g. No spaces for subscripting. +g. No spaces for subscripting or unary operators. h. No space before ':' but one after it, except in the ternary operator: one on both sides. i. Space all other operators. j. Braces, when used, always have their own lines and are at same indentation level as "parent" scope. diff --git a/README.md b/README.md index 6cd9c7adf..2bb57566c 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ ## Ethereum C++ Client. +[![Join the chat at https://gitter.im/ethereum/cpp-ethereum](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/ethereum/cpp-ethereum?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + By Gav Wood et al, 2013, 2014, 2015. | Linux | OSX | Windows diff --git a/abi/CMakeLists.txt b/abi/CMakeLists.txt new file mode 100644 index 000000000..82c7c4240 --- /dev/null +++ b/abi/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_policy(SET CMP0015 NEW) +set(CMAKE_AUTOMOC OFF) + +aux_source_directory(. SRC_LIST) + +include_directories(BEFORE ..) +include_directories(${LEVELDB_INCLUDE_DIRS}) + +set(EXECUTABLE abi) + +add_executable(${EXECUTABLE} ${SRC_LIST}) + +target_link_libraries(${EXECUTABLE} ethereum) + +install( TARGETS ${EXECUTABLE} DESTINATION bin) + diff --git a/abi/main.cpp b/abi/main.cpp new file mode 100644 index 000000000..e7382c761 --- /dev/null +++ b/abi/main.cpp @@ -0,0 +1,769 @@ +/* + 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 Gav Wood + * @date 2014 + * RLP tool. + */ +#include +#include +#include +#include +#include "../test/JsonSpiritHeaders.h" +#include +#include +#include +#include +using namespace std; +using namespace dev; +namespace js = json_spirit; + +void help() +{ + cout + << "Usage abi enc (, (, ... ))" << endl + << " abi enc -a (, (, ... ))" << endl + << " abi dec -a [ | ]" << endl + << "Options:" << endl + << " -a,--abi-file Specify the JSON ABI file." << endl + << " -h,--help Print this help message and exit." << endl + << " -V,--version Show the version and exit." << endl + << "Input options:" << endl + << " -f,--format-prefix Require all input formats to be prefixed e.g. 0x for hex, . for decimal, @ for binary." << endl + << " -F,--no-format-prefix Require no input format to be prefixed." << endl + << " -t,--typing Require all arguments to be typed e.g. b32: (bytes32), u64: (uint64), b[]: (byte[]), i: (int256)." << endl + << " -T,--no-typing Require no arguments to be typed." << endl + << "Output options:" << endl + << " -i,--index Output only the nth (counting from 0) return value." << endl + << " -d,--decimal All data should be displayed as decimal." << endl + << " -x,--hex Display all data as hex." << endl + << " -b,--binary Display all data as binary." << endl + << " -p,--prefix Prefix by a base identifier." << endl + ; + exit(0); +} + +void version() +{ + cout << "abi version " << dev::Version << endl; + exit(0); +} + +enum class Mode +{ + Encode, + Decode +}; + +enum class Encoding +{ + Auto, + Decimal, + Hex, + Binary, +}; + +enum class Tristate +{ + False = false, + True = true, + Mu +}; + +enum class Format +{ + Binary, + Hex, + Decimal, + Open, + Close +}; + +struct InvalidUserString: public Exception {}; +struct InvalidFormat: public Exception {}; + +enum class Base +{ + Unknown, + Bytes, + Address, + Int, + Uint, + Fixed +}; + +static const map s_bases = +{ + { Base::Bytes, "bytes" }, + { Base::Address, "address" }, + { Base::Int, "int" }, + { Base::Uint, "uint" }, + { Base::Fixed, "fixed" } +}; + +struct EncodingPrefs +{ + Encoding e = Encoding::Auto; + bool prefix = true; +}; + +struct ABIType +{ + Base base = Base::Unknown; + unsigned size = 32; + unsigned ssize = 0; + vector dims; + string name; + ABIType() = default; + ABIType(std::string const& _type, std::string const& _name): + name(_name) + { + string rest; + for (auto const& i: s_bases) + if (boost::algorithm::starts_with(_type, i.second)) + { + base = i.first; + rest = _type.substr(i.second.size()); + } + if (base == Base::Unknown) + throw InvalidFormat(); + boost::regex r("(\\d*)(x(\\d+))?((\\[\\d*\\])*)"); + boost::smatch res; + boost::regex_match(rest, res, r); + size = res[1].length() > 0 ? stoi(res[1]) : 0; + ssize = res[3].length() > 0 ? stoi(res[3]) : 0; + boost::regex r2("\\[(\\d*)\\](.*)"); + for (rest = res[4]; boost::regex_match(rest, res, r2); rest = res[2]) + dims.push_back(!res[1].length() ? -1 : stoi(res[1])); + } + + ABIType(std::string const& _s) + { + if (_s.size() < 1) + return; + switch (_s[0]) + { + case 'b': base = Base::Bytes; break; + case 'a': base = Base::Address; break; + case 'i': base = Base::Int; break; + case 'u': base = Base::Uint; break; + case 'f': base = Base::Fixed; break; + default: throw InvalidFormat(); + } + if (_s.size() < 2) + { + if (base == Base::Fixed) + size = ssize = 16; + else if (base == Base::Address || base == Base::Bytes) + size = 0; + else + size = 32; + return; + } + strings d; + boost::algorithm::split(d, _s, boost::is_any_of("*")); + string s = d[0]; + if (s.find_first_of('x') == string::npos) + size = stoi(s.substr(1)); + else + { + size = stoi(s.substr(1, s.find_first_of('x') - 1)); + ssize = stoi(s.substr(s.find_first_of('x') + 1)); + } + for (unsigned i = 1; i < d.size(); ++i) + if (d[i].empty()) + dims.push_back(-1); + else + dims.push_back(stoi(d[i])); + } + + string canon() const + { + string ret; + switch (base) + { + case Base::Bytes: ret = "bytes" + (size > 0 ? toString(size) : ""); break; + case Base::Address: ret = "address"; break; + case Base::Int: ret = "int" + toString(size); break; + case Base::Uint: ret = "uint" + toString(size); break; + case Base::Fixed: ret = "fixed" + toString(size) + "x" + toString(ssize); break; + default: throw InvalidFormat(); + } + for (int i: dims) + ret += "[" + ((i > -1) ? toString(i) : "") + "]"; + return ret; + } + + bool isBytes() const { return base == Base::Bytes && !size; } + + string render(bytes const& _data, EncodingPrefs _e) const + { + if (base == Base::Uint || base == Base::Int) + { + if (_e.e == Encoding::Hex) + return (_e.prefix ? "0x" : "") + toHex(toCompactBigEndian(fromBigEndian(bytesConstRef(&_data).cropped(32 - size / 8)))); + else + { + bigint i = fromBigEndian(bytesConstRef(&_data).cropped(32 - size / 8)); + if (base == Base::Int && i > (bigint(1) << (size - 1))) + i -= (bigint(1) << size); + return toString(i); + } + } + else if (base == Base::Address) + { + Address a = Address(h256(_data), Address::AlignRight); + return _e.e == Encoding::Binary ? asString(a.asBytes()) : ((_e.prefix ? "0x" : "") + toString(a)); + } + else if (isBytes()) + { + return _e.e == Encoding::Binary ? asString(_data) : ((_e.prefix ? "0x" : "") + toHex(_data)); + } + else if (base == Base::Bytes) + { + bytesConstRef b(&_data); + b = b.cropped(0, size); + return _e.e == Encoding::Binary ? asString(b) : ((_e.prefix ? "0x" : "") + toHex(b)); + } + else + throw InvalidFormat(); + } + + bytes unrender(bytes const& _data, Format _f) const + { + if (isBytes()) + { + auto ret = _data; + while (ret.size() % 32 != 0) + ret.push_back(0); + return ret; + } + else + return aligned(_data, _f, 32); + } + + void noteHexInput(unsigned _nibbles) { if (base == Base::Unknown) { if (_nibbles == 40) base = Base::Address; else { base = Base::Bytes; size = _nibbles / 2; } } } + void noteBinaryInput() { if (base == Base::Unknown) { base = Base::Bytes; size = 32; } } + void noteDecimalInput() { if (base == Base::Unknown) { base = Base::Uint; size = 32; } } + + bytes aligned(bytes const& _b, Format _f, unsigned _length) const + { + bytes ret = _b; + while (ret.size() < _length) + if (base == Base::Bytes || (base == Base::Unknown && _f == Format::Binary)) + ret.push_back(0); + else + ret.insert(ret.begin(), 0); + while (ret.size() > _length) + if (base == Base::Bytes || (base == Base::Unknown && _f == Format::Binary)) + ret.pop_back(); + else + ret.erase(ret.begin()); + return ret; + } +}; + +tuple fromUser(std::string const& _arg, Tristate _prefix, Tristate _typing) +{ + ABIType type; + string val; + if (_typing == Tristate::True || (_typing == Tristate::Mu && _arg.find(':') != string::npos)) + { + if (_arg.find(':') == string::npos) + throw InvalidUserString(); + type = ABIType(_arg.substr(0, _arg.find(':'))); + val = _arg.substr(_arg.find(':') + 1); + } + else + val = _arg; + + if (_prefix != Tristate::False) + { + if (val.substr(0, 2) == "0x") + { + type.noteHexInput(val.size() - 2); + return make_tuple(fromHex(val), type, Format::Hex); + } + if (val.substr(0, 1) == "+") + { + type.noteDecimalInput(); + return make_tuple(toCompactBigEndian(bigint(val.substr(1))), type, Format::Decimal); + } + if (val.substr(0, 1) == "'") + { + type.noteBinaryInput(); + return make_tuple(asBytes(val.substr(1)), type, Format::Binary); + } + if (val == "[") + return make_tuple(bytes(), type, Format::Open); + if (val == "]") + return make_tuple(bytes(), type, Format::Close); + } + if (_prefix != Tristate::True) + { + if (val.find_first_not_of("0123456789") == string::npos) + { + type.noteDecimalInput(); + return make_tuple(toCompactBigEndian(bigint(val)), type, Format::Decimal); + } + if (val.find_first_not_of("0123456789abcdefABCDEF") == string::npos) + { + type.noteHexInput(val.size()); + return make_tuple(fromHex(val), type, Format::Hex); + } + if (val == "[") + return make_tuple(bytes(), type, Format::Open); + if (val == "]") + return make_tuple(bytes(), type, Format::Close); + type.noteBinaryInput(); + return make_tuple(asBytes(val), type, Format::Binary); + } + throw InvalidUserString(); +} + +struct ExpectedAdditionalParameter: public Exception {}; +struct ExpectedOpen: public Exception {}; +struct ExpectedClose: public Exception {}; + +struct ABIMethod +{ + string name; + vector ins; + vector outs; + bool isConstant = false; + + // isolation *IS* documentation. + + ABIMethod() = default; + + ABIMethod(js::mObject _o) + { + name = _o["name"].get_str(); + isConstant = _o["constant"].get_bool(); + if (_o.count("inputs")) + for (auto const& i: _o["inputs"].get_array()) + { + js::mObject a = i.get_obj(); + ins.push_back(ABIType(a["type"].get_str(), a["name"].get_str())); + } + if (_o.count("outputs")) + for (auto const& i: _o["outputs"].get_array()) + { + js::mObject a = i.get_obj(); + outs.push_back(ABIType(a["type"].get_str(), a["name"].get_str())); + } + } + + ABIMethod(string const& _name, vector const& _args) + { + name = _name; + ins = _args; + } + + string sig() const + { + string methodArgs; + for (auto const& arg: ins) + methodArgs += (methodArgs.empty() ? "" : ",") + arg.canon(); + return name + "(" + methodArgs + ")"; + } + FixedHash<4> id() const { return FixedHash<4>(sha3(sig())); } + + std::string solidityDeclaration() const + { + ostringstream ss; + ss << "function " << name << "("; + int f = 0; + for (ABIType const& i: ins) + ss << (f++ ? ", " : "") << i.canon() << " " << i.name; + ss << ") "; + if (isConstant) + ss << "constant "; + if (!outs.empty()) + { + ss << "returns("; + f = 0; + for (ABIType const& i: outs) + ss << (f++ ? ", " : "") << i.canon() << " " << i.name; + ss << ")"; + } + return ss.str(); + } + + bytes encode(vector> const& _params) const + { + bytes ret = name.empty() ? bytes() : id().asBytes(); + bytes suffix; + + // int int[] int + // example: 42 [ 1 2 3 ] 69 + // int[2][][3] + // example: [ [ [ 1 2 3 ] [ 4 5 6 ] ] [ ] ] + + unsigned pi = 0; + + for (ABIType const& a: ins) + { + if (pi >= _params.size()) + throw ExpectedAdditionalParameter(); + auto put = [&]() { + if (a.isBytes()) + ret += h256(u256(_params[pi].first.size())).asBytes(); + suffix += a.unrender(_params[pi].first, _params[pi].second); + pi++; + if (pi >= _params.size()) + throw ExpectedAdditionalParameter(); + }; + function, unsigned)> putDim = [&](vector addr, unsigned q) { + if (addr.size() == a.dims.size()) + put(); + else + { + if (_params[pi].second != Format::Open) + throw ExpectedOpen(); + ++pi; + int l = a.dims[a.dims.size() - 1 - addr.size()]; + if (l == -1) + { + // read ahead in params and discover the arity. + unsigned depth = 0; + l = 0; + for (unsigned pi2 = pi; depth || _params[pi2].second != Format::Close;) + { + if (_params[pi2].second == Format::Open) + ++depth; + if (_params[pi2].second == Format::Close) + --depth; + if (!depth) + ++l; + if (++pi2 == _params.size()) + throw ExpectedClose(); + } + ret += h256(u256(l)).asBytes(); + } + q *= l; + for (addr.push_back(0); addr.back() < l; ++addr.back()) + putDim(addr, q); + if (_params[pi].second != Format::Close) + throw ExpectedClose(); + ++pi; + } + }; + putDim(vector(), 1); + } + return ret + suffix; + } + string decode(bytes const& _data, int _index, EncodingPrefs _ep) + { + stringstream out; + unsigned di = 0; + vector catDims; + for (ABIType const& a: outs) + { + auto put = [&]() { + if (a.isBytes()) + { + catDims.push_back(fromBigEndian(bytesConstRef(&_data).cropped(di, 32))); + di += 32; + } + }; + function, unsigned)> putDim = [&](vector addr, unsigned q) { + if (addr.size() == a.dims.size()) + put(); + else + { + int l = a.dims[a.dims.size() - 1 - addr.size()]; + if (l == -1) + { + l = fromBigEndian(bytesConstRef(&_data).cropped(di, 32)); + catDims.push_back(l); + di += 32; + } + q *= l; + for (addr.push_back(0); addr.back() < l; ++addr.back()) + putDim(addr, q); + } + }; + putDim(vector(), 1); + } + unsigned d = 0; + for (ABIType const& a: outs) + { + if (_index == -1 && out.tellp() > 0) + out << ", "; + auto put = [&]() { + if (a.isBytes()) + { + out << a.render(bytesConstRef(&_data).cropped(di, catDims[d]).toBytes(), _ep); + di += ((catDims[d] + 31) / 32) * 32; + d++; + } + else + { + out << a.render(bytesConstRef(&_data).cropped(di, 32).toBytes(), _ep); + di += 32; + } + }; + function)> putDim = [&](vector addr) { + if (addr.size() == a.dims.size()) + put(); + else + { + out << "["; + addr.push_back(0); + 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()) + { + if (addr.back()) + out << ", "; + putDim(addr); + } + out << "]"; + } + }; + putDim(vector()); + } + return out.str(); + } +}; + +string canonSig(string const& _name, vector const& _args) +{ + try { + string methodArgs; + for (auto const& arg: _args) + methodArgs += (methodArgs.empty() ? "" : ",") + arg.canon(); + return _name + "(" + methodArgs + ")"; + } + catch (...) { + return string(); + } +} + +struct UnknownMethod: public Exception {}; +struct OverloadedMethod: public Exception {}; + +class ABI +{ +public: + ABI() = default; + ABI(std::string const& _json) + { + js::mValue v; + js::read_string(_json, v); + for (auto const& i: v.get_array()) + { + js::mObject o = i.get_obj(); + if (o["type"].get_str() != "function") + continue; + ABIMethod m(o); + m_methods[m.id()] = m; + } + } + + ABIMethod method(string _nameOrSig, vector const& _args) const + { + auto id = FixedHash<4>(sha3(_nameOrSig)); + if (!m_methods.count(id)) + id = FixedHash<4>(sha3(canonSig(_nameOrSig, _args))); + if (!m_methods.count(id)) + for (auto const& m: m_methods) + if (m.second.name == _nameOrSig) + { + if (m_methods.count(id)) + throw OverloadedMethod(); + id = m.first; + } + if (m_methods.count(id)) + return m_methods.at(id); + throw UnknownMethod(); + } + + friend ostream& operator<<(ostream& _out, ABI const& _abi); + +private: + map, ABIMethod> m_methods; +}; + +ostream& operator<<(ostream& _out, ABI const& _abi) +{ + _out << "contract {" << endl; + for (auto const& i: _abi.m_methods) + _out << " " << i.second.solidityDeclaration() << "; // " << i.first.abridged() << endl; + _out << "}" << endl; + return _out; +} + +void userOutput(ostream& _out, bytes const& _data, Encoding _e) +{ + switch (_e) + { + case Encoding::Binary: + _out.write((char const*)_data.data(), _data.size()); + break; + default: + _out << toHex(_data) << endl; + } +} + +template vector(T()))>::type> retrieve(vector const& _t) +{ + vector(T()))>::type> ret; + for (T const& i: _t) + ret.push_back(get(i)); + return ret; +} + +int main(int argc, char** argv) +{ + Encoding encoding = Encoding::Auto; + Mode mode = Mode::Encode; + string abiFile; + string method; + Tristate formatPrefix = Tristate::Mu; + Tristate typePrefix = Tristate::Mu; + EncodingPrefs prefs; + bool verbose = false; + int outputIndex = -1; + vector> params; + vector args; + string incoming; + + for (int i = 1; i < argc; ++i) + { + string arg = argv[i]; + if (arg == "-h" || arg == "--help") + help(); + else if (arg == "enc" && i == 1) + mode = Mode::Encode; + else if (arg == "dec" && i == 1) + mode = Mode::Decode; + else if ((arg == "-a" || arg == "--abi") && argc > i) + abiFile = argv[++i]; + else if ((arg == "-i" || arg == "--index") && argc > i) + outputIndex = atoi(argv[++i]); + else if (arg == "-p" || arg == "--prefix") + prefs.prefix = true; + else if (arg == "-f" || arg == "--format-prefix") + formatPrefix = Tristate::True; + else if (arg == "-F" || arg == "--no-format-prefix") + formatPrefix = Tristate::False; + else if (arg == "-t" || arg == "--typing") + typePrefix = Tristate::True; + else if (arg == "-T" || arg == "--no-typing") + typePrefix = Tristate::False; + else if (arg == "-x" || arg == "--hex") + prefs.e = Encoding::Hex; + else if (arg == "-d" || arg == "--decimal" || arg == "--dec") + prefs.e = Encoding::Decimal; + else if (arg == "-b" || arg == "--binary" || arg == "--bin") + prefs.e = Encoding::Binary; + else if (arg == "-v" || arg == "--verbose") + verbose = true; + else if (arg == "-V" || arg == "--version") + version(); + else if (method.empty()) + method = arg; + else if (mode == Mode::Encode) + { + auto u = fromUser(arg, formatPrefix, typePrefix); + args.push_back(get<1>(u)); + params.push_back(make_pair(get<0>(u), get<2>(u))); + } + else if (mode == Mode::Decode) + incoming += arg; + } + + string abiData; + if (!abiFile.empty()) + abiData = contentsString(abiFile); + + if (mode == Mode::Encode) + { + ABIMethod m; + if (abiData.empty()) + m = ABIMethod(method, args); + else + { + ABI abi(abiData); + if (verbose) + cerr << "ABI:" << endl << abi; + try { + m = abi.method(method, args); + } + catch (...) + { + cerr << "Unknown method in ABI." << endl; + exit(-1); + } + } + try { + userOutput(cout, m.encode(params), encoding); + } + catch (ExpectedAdditionalParameter const&) + { + cerr << "Expected additional parameter in input." << endl; + exit(-1); + } + catch (ExpectedOpen const&) + { + cerr << "Expected open-bracket '[' in input." << endl; + exit(-1); + } + catch (ExpectedClose const&) + { + cerr << "Expected close-bracket ']' in input." << endl; + exit(-1); + } + } + else if (mode == Mode::Decode) + { + if (abiData.empty()) + { + cerr << "Please specify an ABI file." << endl; + exit(-1); + } + else + { + ABI abi(abiData); + ABIMethod m; + if (verbose) + cerr << "ABI:" << endl << abi; + try { + m = abi.method(method, args); + } + catch(...) + { + cerr << "Unknown method in ABI." << endl; + exit(-1); + } + string encoded; + if (incoming == "--" || incoming.empty()) + for (int i = cin.get(); i != -1; i = cin.get()) + encoded.push_back((char)i); + else + { + encoded = contentsString(incoming); + } + cout << m.decode(fromHex(boost::trim_copy(encoded)), outputIndex, prefs) << endl; + } + } + + return 0; +} diff --git a/alethzero/CMakeLists.txt b/alethzero/CMakeLists.txt index cbc9e087a..c81c86222 100644 --- a/alethzero/CMakeLists.txt +++ b/alethzero/CMakeLists.txt @@ -19,6 +19,7 @@ find_package (Qt5WebEngine QUIET) find_package (Qt5WebEngineWidgets QUIET) qt5_wrap_ui(ui_Main.h Main.ui) +qt5_wrap_ui(ui_Connect.h Connect.ui) qt5_wrap_ui(ui_Debugger.h Debugger.ui) qt5_wrap_ui(ui_Transact.h Transact.ui) @@ -32,8 +33,8 @@ endif () # eth_add_executable is defined in cmake/EthExecutableHelper.cmake eth_add_executable(${EXECUTABLE} - ICON alethzero - UI_RESOURCES alethzero.icns Main.ui Debugger.ui Transact.ui + ICON alethzero + UI_RESOURCES alethzero.icns Main.ui Connect.ui Debugger.ui Transact.ui WIN_RESOURCES alethzero.rc ) @@ -56,6 +57,7 @@ target_link_libraries(${EXECUTABLE} devcore) target_link_libraries(${EXECUTABLE} web3jsonrpc) target_link_libraries(${EXECUTABLE} jsqrc) target_link_libraries(${EXECUTABLE} natspec) +target_link_libraries(${EXECUTABLE} ${MHD_LIBRARIES}) if (NOT ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")) target_link_libraries(${EXECUTABLE} serpent) diff --git a/alethzero/Connect.cpp b/alethzero/Connect.cpp new file mode 100644 index 000000000..69cc43f93 --- /dev/null +++ b/alethzero/Connect.cpp @@ -0,0 +1,64 @@ +/* + 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 Connect.cpp + * @author Alex Leverington + * @date 2015 + */ + +#include "Connect.h" + +#include +#include "ui_Connect.h" + +Connect::Connect(QWidget *parent) : + QDialog(parent), + ui(new Ui::Connect) +{ + ui->setupUi(this); +} + +Connect::~Connect() +{ + delete ui; +} + +void Connect::setEnvironment(QStringList const& _nodes) +{ + if (ui->host->count() == 0) + ui->host->addItems(_nodes); +} + +void Connect::reset() +{ + ui->nodeId->clear(); + ui->required->setChecked(true); +} + +QString Connect::host() +{ + return ui->host->currentText(); +} + +QString Connect::nodeId() +{ + return ui->nodeId->text(); +} + +bool Connect::required() +{ + return ui->required->isChecked(); +} diff --git a/alethzero/Connect.h b/alethzero/Connect.h new file mode 100644 index 000000000..8209a78af --- /dev/null +++ b/alethzero/Connect.h @@ -0,0 +1,55 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file Connect.h + * @author Alex Leverington + * @date 2015 + */ + +#pragma once + +#include +#include + +namespace Ui { class Connect; } +namespace dev { namespace p2p { class Host; } } + +class Connect : public QDialog +{ + Q_OBJECT + +public: + explicit Connect(QWidget* _parent = 0); + ~Connect(); + + /// Populate host chooser with default host entries. + void setEnvironment(QStringList const& _nodes); + + /// Clear dialogue inputs. + void reset(); + + /// @returns the host string, as chosen or entered by the user. Assumed to be "hostOrIP:port" (:port is optional). + QString host(); + + /// @returns the identity of the node, as entered by the user. Assumed to be a 64-character hex string. + QString nodeId(); + + /// @returns true if Required is checked by the user, indicating that the host is a required Peer. + bool required(); + +private: + Ui::Connect* ui; +}; diff --git a/alethzero/Connect.ui b/alethzero/Connect.ui new file mode 100644 index 000000000..1ace94c8d --- /dev/null +++ b/alethzero/Connect.ui @@ -0,0 +1,126 @@ + + + Connect + + + Qt::WindowModal + + + + 0 + 0 + 343 + 178 + + + + + 0 + 0 + + + + + 343 + 178 + + + + Connect to Peer + + + true + + + + + + + + + 311 + 0 + + + + true + + + + + + + Node Id + + + + + + + Required (Always Connect to this Peer) + + + true + + + false + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + Enter a peer to which a connection may be made: + + + + + + + + + buttonBox + accepted() + Connect + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + Connect + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/alethzero/DappHost.cpp b/alethzero/DappHost.cpp new file mode 100644 index 000000000..fd73c1b17 --- /dev/null +++ b/alethzero/DappHost.cpp @@ -0,0 +1,139 @@ +/* + 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 DappHost.cpp + * @author Arkadiy Paronyan + * @date 2015 + */ + +#include "DappHost.h" +#include +#include +#include +#include + +using namespace dev; + +DappHost::DappHost(int _port, int _threads): + m_port(_port), m_threads(_threads), m_running(false), m_daemon(nullptr) +{ + startListening(); +} + +DappHost::~DappHost() +{ + stopListening(); +} + +void DappHost::startListening() +{ + if(!this->m_running) + { + this->m_daemon = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY, this->m_port, nullptr, nullptr, &DappHost::callback, this, MHD_OPTION_THREAD_POOL_SIZE, this->m_threads, MHD_OPTION_END); + if (this->m_daemon != nullptr) + this->m_running = true; + } +} + +void DappHost::stopListening() +{ + if(this->m_running) + { + MHD_stop_daemon(this->m_daemon); + this->m_running = false; + } +} + +void DappHost::sendOptionsResponse(MHD_Connection* _connection) +{ + MHD_Response *result = MHD_create_response_from_data(0, NULL, 0, 1); + MHD_add_response_header(result, "Allow", "GET, OPTIONS"); + MHD_add_response_header(result, "Access-Control-Allow-Headers", "origin, content-type, accept"); + MHD_add_response_header(result, "DAV", "1"); + MHD_queue_response(_connection, MHD_HTTP_OK, result); + MHD_destroy_response(result); +} + +void DappHost::sendNotAllowedResponse(MHD_Connection* _connection) +{ + MHD_Response *result = MHD_create_response_from_data(0, NULL, 0, 1); + MHD_add_response_header(result, "Allow", "GET, OPTIONS"); + MHD_queue_response(_connection, MHD_HTTP_METHOD_NOT_ALLOWED, result); + MHD_destroy_response(result); +} + +void DappHost::sendResponse(std::string const& _url, MHD_Connection* _connection) +{ + QUrl requestUrl(QString::fromStdString(_url)); + QString path = requestUrl.path().toLower(); + if (path.isEmpty()) + path = "/"; + + bytesConstRef response; + unsigned code = MHD_HTTP_NOT_FOUND; + std::string contentType; + + while (!path.isEmpty()) + { + auto iter = m_entriesByPath.find(path); + if (iter != m_entriesByPath.end()) + { + ManifestEntry const* entry = iter->second; + auto contentIter = m_dapp.content.find(entry->hash); + if (contentIter == m_dapp.content.end()) + break; + + response = bytesConstRef(contentIter->second.data(), contentIter->second.size()); + code = entry->httpStatus != 0 ? entry->httpStatus : MHD_HTTP_OK; + contentType = entry->contentType; + break; + } + path.truncate(path.length() - 1); + path = path.mid(0, path.lastIndexOf('/')); + } + + MHD_Response *result = MHD_create_response_from_data(response.size(), const_cast(response.data()), 0, 1); + if (!contentType.empty()) + MHD_add_response_header(result, "Content-Type", contentType.c_str()); + MHD_queue_response(_connection, code, result); + MHD_destroy_response(result); +} + +int DappHost::callback(void* _cls, MHD_Connection* _connection, char const* _url, char const* _method, char const* _version, char const* _uploadData, size_t* _uploadDataSize, void** _conCls) +{ + (void)_version; + (void)_uploadData; + (void)_uploadDataSize; + (void)_conCls; + DappHost* host = static_cast(_cls); + if (std::string("GET") == _method) + host->sendResponse(std::string(_url), _connection); + else if (std::string("OPTIONS") == _method) + host->sendOptionsResponse(_connection); + else + host->sendNotAllowedResponse(_connection); + return MHD_YES; +} + +QUrl DappHost::hostDapp(Dapp&& _dapp) +{ + m_dapp = std::move(_dapp); + m_entriesByPath.clear(); + for (ManifestEntry const& entry: m_dapp.manifest.entries) + m_entriesByPath[QString::fromStdString(entry.path)] = &entry; + + return QUrl(QString("http://localhost:%1/").arg(m_port)); +} diff --git a/alethzero/DappHost.h b/alethzero/DappHost.h new file mode 100644 index 000000000..985bd34d9 --- /dev/null +++ b/alethzero/DappHost.h @@ -0,0 +1,58 @@ +/* + 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 DappHost.h + * @author Arkadiy Paronyan + * @date 2015 + */ + +#pragma once + +#include +#include +#include +#include "DappLoader.h" + +struct MHD_Daemon; +struct MHD_Connection; + +/// DApp web server. Servers web content, resolves paths by hashes +class DappHost +{ +public: + /// @param _port Network pork to listen for incoming connections + /// @param _threads Max number of threads to process requests + DappHost(int _port, int _threads = 10); + virtual ~DappHost(); + /// Load and host a dapp. Previsous dapp in discarded. Synchronous + QUrl hostDapp(Dapp&& _dapp); + +private: + void startListening(); + void stopListening(); + void sendOptionsResponse(MHD_Connection* _connection); + void sendNotAllowedResponse(MHD_Connection* _connection); + void sendResponse(std::string const& _url, MHD_Connection* _connection); + static int callback(void* _cls, MHD_Connection* _connection, char const* _url, char const* _method, char const* _version, char const* _uploadData, size_t* _uploadDataSize, void** _conCls); + + int m_port; + int m_threads; + bool m_running; + MHD_Daemon* m_daemon; + Dapp m_dapp; + std::map m_entriesByPath; +}; + diff --git a/alethzero/DappLoader.cpp b/alethzero/DappLoader.cpp new file mode 100644 index 000000000..9baf0c82e --- /dev/null +++ b/alethzero/DappLoader.cpp @@ -0,0 +1,218 @@ +/* + 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 DappLoader.cpp + * @author Arkadiy Paronyan + * @date 2015 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "DappLoader.h" + +using namespace dev; +using namespace dev::eth; +using namespace dev::crypto; + +Address c_registrar = Address("0000000000000000000000000000000000000a28"); +Address c_urlHint = Address("0000000000000000000000000000000000000a29"); + +QString contentsOfQResource(std::string const& res); + +DappLoader::DappLoader(QObject* _parent, WebThreeDirect* _web3): + QObject(_parent), m_web3(_web3) +{ + connect(&m_net, &QNetworkAccessManager::finished, this, &DappLoader::downloadComplete); +} + +DappLocation DappLoader::resolveAppUri(QString const& _uri) +{ + QUrl url(_uri); + if (!url.scheme().isEmpty() && url.scheme() != "eth") + throw dev::Exception(); //TODO: + + QStringList parts = url.host().split('.', QString::SkipEmptyParts); + QStringList domainParts; + std::reverse(parts.begin(), parts.end()); + parts.append(url.path().split('/', QString::SkipEmptyParts)); + + Address address = c_registrar; + Address lastAddress; + int partIndex = 0; + + h256 contentHash; + + while (address && partIndex < parts.length()) + { + lastAddress = address; + string32 name = ZeroString32; + QByteArray utf8 = parts[partIndex].toUtf8(); + std::copy(utf8.data(), utf8.data() + utf8.size(), name.data()); + address = abiOut
(web3()->ethereum()->call(address, abiIn("addr(string32)", name)).output); + domainParts.append(parts[partIndex]); + if (!address) + { + //we have the address of the last part, try to get content hash + contentHash = abiOut(web3()->ethereum()->call(lastAddress, abiIn("content(string32)", name)).output); + if (!contentHash) + throw dev::Exception() << errinfo_comment("Can't resolve address"); + } + ++partIndex; + } + + string32 contentUrl = abiOut(web3()->ethereum()->call(c_urlHint, abiIn("url(hash256)", contentHash)).output); + QString domain = domainParts.join('/'); + parts.erase(parts.begin(), parts.begin() + partIndex); + QString path = parts.join('/'); + QString contentUrlString = QString::fromUtf8(std::string(contentUrl.data(), contentUrl.size()).c_str()); + if (!contentUrlString.startsWith("http://") || !contentUrlString.startsWith("https://")) + contentUrlString = "http://" + contentUrlString; + return DappLocation { domain, path, contentUrlString, contentHash }; +} + +void DappLoader::downloadComplete(QNetworkReply* _reply) +{ + try + { + //try to interpret as rlp + QByteArray data = _reply->readAll(); + _reply->deleteLater(); + + h256 expected = m_uriHashes[_reply->request().url()]; + bytes package(reinterpret_cast(data.constData()), reinterpret_cast(data.constData() + data.size())); + Secp256k1 dec; + dec.decrypt(expected, package); + h256 got = sha3(package); + if (got != expected) + { + //try base64 + data = QByteArray::fromBase64(data); + package = bytes(reinterpret_cast(data.constData()), reinterpret_cast(data.constData() + data.size())); + dec.decrypt(expected, package); + got = sha3(package); + if (got != expected) + throw dev::Exception() << errinfo_comment("Dapp content hash does not match"); + } + + RLP rlp(package); + loadDapp(rlp); + } + catch (...) + { + qWarning() << tr("Error downloading DApp: ") << boost::current_exception_diagnostic_information().c_str(); + emit dappError(); + } + +} + +void DappLoader::loadDapp(RLP const& _rlp) +{ + Dapp dapp; + unsigned len = _rlp.itemCountStrict(); + dapp.manifest = loadManifest(_rlp[0].toString()); + for (unsigned c = 1; c < len; ++c) + { + bytesConstRef content = _rlp[c].toBytesConstRef(); + h256 hash = sha3(content); + auto entry = std::find_if(dapp.manifest.entries.cbegin(), dapp.manifest.entries.cend(), [=](ManifestEntry const& _e) { return _e.hash == hash; }); + if (entry != dapp.manifest.entries.cend()) + { + if (entry->path == "/deployment.js") + { + //inject web3 code + QString code; + code += contentsOfQResource(":/js/bignumber.min.js"); + code += "\n"; + code += contentsOfQResource(":/js/webthree.js"); + code += "\n"; + code += contentsOfQResource(":/js/setup.js"); + code += "\n"; + QByteArray res = code.toLatin1(); + bytes b(res.data(), res.data() + res.size()); + b.insert(b.end(), content.begin(), content.end()); + dapp.content[hash] = b; + } + else + dapp.content[hash] = content.toBytes(); + } + else + throw dev::Exception() << errinfo_comment("Dapp content hash does not match"); + } + emit dappReady(dapp); +} + +Manifest DappLoader::loadManifest(std::string const& _manifest) +{ + /// https://github.com/ethereum/go-ethereum/wiki/URL-Scheme + Manifest manifest; + Json::Reader jsonReader; + Json::Value root; + jsonReader.parse(_manifest, root, false); + + Json::Value entries = root["entries"]; + for (Json::ValueIterator it = entries.begin(); it != entries.end(); ++it) + { + Json::Value const& entryValue = *it; + std::string path = entryValue["path"].asString(); + if (path.size() == 0 || path[0] != '/') + path = "/" + path; + std::string contentType = entryValue["contentType"].asString(); + std::string strHash = entryValue["hash"].asString(); + if (strHash.length() == 64) + strHash = "0x" + strHash; + h256 hash = jsToFixed<32>(strHash); + unsigned httpStatus = entryValue["status"].asInt(); + manifest.entries.push_back(ManifestEntry{ path, hash, contentType, httpStatus }); + } + return manifest; +} + +void DappLoader::loadDapp(QString const& _uri) +{ + 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/DappLoader.h b/alethzero/DappLoader.h new file mode 100644 index 000000000..463b65d0a --- /dev/null +++ b/alethzero/DappLoader.h @@ -0,0 +1,94 @@ +/* + 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 DappLoader.h + * @author Arkadiy Paronyan + * @date 2015 + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace dev +{ + class WebThreeDirect; + class RLP; +} + +struct ManifestEntry +{ + std::string path; + dev::h256 hash; + std::string contentType; + unsigned httpStatus; +}; + +struct Manifest +{ + std::vector entries; +}; + +struct Dapp +{ + Manifest manifest; + std::map content; +}; + + +struct DappLocation +{ + QString canonDomain; + QString path; + QString contentUri; + dev::h256 contentHash; +}; + +///Downloads, unpacks and prepares DApps for hosting +class DappLoader: public QObject +{ + Q_OBJECT +public: + DappLoader(QObject* _parent, dev::WebThreeDirect* _web3); + ///Load a new DApp. Resolves a name with a name reg contract. Asynchronous. dappReady is emitted once everything is read, dappError othervise + ///@param _uri Eth name path + void loadDapp(QString const& _uri); + +signals: + void dappReady(Dapp& _dapp); + void dappError(); + +private slots: + void downloadComplete(QNetworkReply* _reply); + +private: + dev::WebThreeDirect* web3() const { return m_web3; } + DappLocation resolveAppUri(QString const& _uri); + void loadDapp(dev::RLP const& _rlp); + Manifest loadManifest(std::string const& _manifest); + + dev::WebThreeDirect* m_web3; + QNetworkAccessManager m_net; + std::map m_uriHashes; +}; + diff --git a/alethzero/Debugger.cpp b/alethzero/Debugger.cpp index 93d6b1f6a..371630456 100644 --- a/alethzero/Debugger.cpp +++ b/alethzero/Debugger.cpp @@ -67,7 +67,8 @@ void Debugger::populate(dev::eth::Executive& _executive, dev::eth::Transaction c bool DebugSession::populate(dev::eth::Executive& _executive, dev::eth::Transaction const& _transaction) { try { - if (_executive.setup(_transaction)) + _executive.initialize(_transaction); + if (_executive.execute()) return false; } catch (...) diff --git a/alethzero/Main.ui b/alethzero/Main.ui index da5ee6c4a..14f3f5b67 100644 --- a/alethzero/Main.ui +++ b/alethzero/Main.ui @@ -45,6 +45,13 @@ + + + + #0 + + + @@ -134,8 +141,7 @@ - - + @@ -208,7 +214,7 @@ - + QDockWidget::DockWidgetFeatureMask @@ -284,7 +290,27 @@ - + + + + &Listen on + + + port + + + + + + + 1 + + + 5 + + + + 1024 @@ -297,8 +323,8 @@ - - + + @@ -310,16 +336,6 @@ - - - - &Listen on - - - port - - - @@ -330,20 +346,17 @@ - - - - 1 - - - 5 + + + + Automatic - - - - Anonymous + + + + Public IP @@ -357,6 +370,13 @@ + + + + Anonymous + + + @@ -566,7 +586,7 @@ - + QDockWidget::DockWidgetFeatureMask @@ -1443,12 +1463,12 @@ font-size: 14pt Show &Anonymous Accounts - + true - Use &Past Peers + &Drop Past Peers @@ -1694,9 +1714,8 @@ font-size: 14pt tabWidget urlEdit idealPeers - forceAddress + listenIP port - clientName transactionQueue pendingInfo blockChainFilter diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index 88b9103cb..33ced7e98 100644 --- a/alethzero/MainWin.cpp +++ b/alethzero/MainWin.cpp @@ -19,7 +19,6 @@ * @date 2014 */ -#define QWEBENGINEINSPECTOR 1 #include // Make sure boost/asio.hpp is included before windows.h. @@ -29,6 +28,7 @@ //pragma GCC diagnostic ignored "-Werror=pedantic" #include #include +#include #include #include #include @@ -68,6 +68,9 @@ #include "OurWebThreeStubServer.h" #include "Transact.h" #include "Debugger.h" +#include "DappLoader.h" +#include "DappHost.h" +#include "WebPage.h" #include "ui_Main.h" using namespace std; using namespace dev; @@ -116,7 +119,9 @@ Address c_newConfig = Address("c6d9d2cd449a754c494264e1809c50e34d64562b"); Main::Main(QWidget *parent) : QMainWindow(parent), ui(new Ui::Main), - m_transact(this, this) + m_transact(this, this), + m_dappLoader(nullptr), + m_webPage(nullptr) { QtWebEngine::initialize(); setWindowFlags(Qt::Window); @@ -131,17 +136,22 @@ Main::Main(QWidget *parent) : // ui->log->addItem(QString::fromStdString(s)); }; +#if !ETH_FATDB + delete ui->dockWidget_accounts; + delete ui->dockWidget_contracts; +#endif + #if ETH_DEBUG - m_servers.append("localhost:30300"); + m_servers.append("127.0.0.1:30300"); #endif m_servers.append(QString::fromStdString(Host::pocHost() + ":30303")); 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 << "Network protocol version: " << c_protocolVersion << endl; + cerr << "eth Network protocol version: " << eth::c_protocolVersion << endl; cerr << "Client database version: " << c_databaseVersion << endl; ui->configDock->close(); @@ -151,25 +161,30 @@ Main::Main(QWidget *parent) : statusBar()->addPermanentWidget(ui->balance); statusBar()->addPermanentWidget(ui->peerCount); statusBar()->addPermanentWidget(ui->mineStatus); + statusBar()->addPermanentWidget(ui->chainStatus); statusBar()->addPermanentWidget(ui->blockCount); + ui->blockCount->setText(QString("PV%2 D%3 H%4 v%5").arg(eth::c_protocolVersion).arg(c_databaseVersion).arg(c_ethashVersion).arg(dev::Version)); + connect(ui->ourAccounts->model(), SIGNAL(rowsMoved(const QModelIndex &, int, int, const QModelIndex &, int)), SLOT(ourAccountsRowsMoved())); 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() + "/AlethZero", 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(8080)); + m_httpConnector.reset(new jsonrpc::HttpServer(SensibleHttpPort, "", "", dev::SensibleHttpThreads)); m_server.reset(new OurWebThreeStubServer(*m_httpConnector, *web3(), keysAsVector(m_myKeys), this)); connect(&*m_server, SIGNAL(onNewId(QString)), SLOT(addNewId(QString))); m_server->setIdentities(keysAsVector(owned())); m_server->StartListening(); + WebPage* webPage= new WebPage(this); + m_webPage = webPage; + connect(webPage, &WebPage::consoleMessage, [this](QString const& _msg) { Main::addConsoleMessage(_msg, QString()); }); + ui->webView->setPage(m_webPage); connect(ui->webView, &QWebEngineView::loadFinished, [this]() { -// f->disconnect(); -// f->addToJavaScriptWindowObject("env", this, QWebFrame::QtOwnership); auto f = ui->webView->page(); f->runJavaScript(contentsOfQResource(":/js/bignumber.min.js")); f->runJavaScript(contentsOfQResource(":/js/webthree.js")); @@ -181,6 +196,9 @@ Main::Main(QWidget *parent) : ui->tabWidget->setTabText(0, ui->webView->title()); }); + m_dappHost.reset(new DappHost(8081)); + m_dappLoader = new DappLoader(this, web3()); + connect(m_dappLoader, &DappLoader::dappReady, this, &Main::dappLoaded); // ui->webView->page()->settings()->setAttribute(QWebEngineSettings::DeveloperExtrasEnabled, true); // QWebEngineInspector* inspector = new QWebEngineInspector(); // inspector->setPage(page); @@ -237,7 +255,30 @@ void Main::addNewId(QString _ids) NetworkPreferences Main::netPrefs() const { - return NetworkPreferences(ui->port->value(), ui->forceAddress->text().toStdString(), ui->upnp->isChecked(), ui->localNetworking->isChecked()); + auto listenIP = ui->listenIP->text().toStdString(); + try + { + listenIP = bi::address::from_string(listenIP).to_string(); + } + catch (...) + { + listenIP.clear(); + } + + auto publicIP = ui->forcePublicIP->text().toStdString(); + try + { + publicIP = bi::address::from_string(publicIP).to_string(); + } + catch (...) + { + publicIP.clear(); + } + + if (isPublicAddress(publicIP)) + return NetworkPreferences(publicIP, listenIP, ui->port->value(), ui->upnp->isChecked()); + else + return NetworkPreferences(listenIP, ui->port->value(), ui->upnp->isChecked()); } void Main::onKeysChanged() @@ -247,40 +288,51 @@ 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; } unsigned Main::installWatch(dev::h256 _tf, WatchHandler const& _f) { - auto ret = ethereum()->installWatch(_tf); + auto ret = ethereum()->installWatch(_tf, Reaping::Manual); m_handlers[ret] = _f; + _f(LocalisedLogEntries()); return ret; } void Main::uninstallWatch(unsigned _w) { + cdebug << "!!! Main: uninstalling watch" << _w; ethereum()->uninstallWatch(_w); m_handlers.erase(_w); } void Main::installWatches() { + auto newBlockId = installWatch(ChainChangedFilter, [=](LocalisedLogEntries const&){ + onNewBlock(); + }); + auto newPendingId = installWatch(PendingChangedFilter, [=](LocalisedLogEntries const&){ + onNewPending(); + }); + + cdebug << "newBlock watch ID: " << newBlockId; + cdebug << "newPending watch ID: " << newPendingId; + installWatch(LogFilter().address(c_newConfig), [=](LocalisedLogEntries const&) { installNameRegWatch(); }); installWatch(LogFilter().address(c_newConfig), [=](LocalisedLogEntries const&) { installCurrenciesWatch(); }); - installWatch(PendingChangedFilter, [=](LocalisedLogEntries const&){ onNewPending(); }); - installWatch(ChainChangedFilter, [=](LocalisedLogEntries const&){ onNewBlock(); }); } Address Main::getNameReg() const { - return abiOut
(ethereum()->call(c_newConfig, abiIn("lookup(uint256)", (u256)1))); + return abiOut
(ethereum()->call(c_newConfig, abiIn("lookup(uint256)", (u256)1)).output); } Address Main::getCurrencies() const { - return abiOut
(ethereum()->call(c_newConfig, abiIn("lookup(uint256)", (u256)3))); + return abiOut
(ethereum()->call(c_newConfig, abiIn("lookup(uint256)", (u256)3)).output); } void Main::installNameRegWatch() @@ -303,7 +355,7 @@ void Main::installBalancesWatch() Address coinsAddr = getCurrencies(); // TODO: Update for new currencies reg. - for (unsigned i = 0; i < ethereum()->stateAt(coinsAddr, 0); ++i) + for (unsigned i = 0; i < ethereum()->stateAt(coinsAddr, PendingBlock); ++i) altCoins.push_back(right160(ethereum()->stateAt(coinsAddr, i + 1))); for (auto i: m_myKeys) for (auto c: altCoins) @@ -425,13 +477,7 @@ void Main::eval(QString const& _js) s = "" + jsonEv.toString().toHtmlEscaped() + ""; else s = "unknown type"; - m_consoleHistory.push_back(qMakePair(_js, s)); - s = "" Div(Mono "position: absolute; bottom: 0; border: 0px; margin: 0px; width: 100%"); - for (auto const& i: m_consoleHistory) - s += "
>" + i.first.toHtmlEscaped() + "
" - "
 " + i.second + "
"; - s += ""; - ui->jsConsole->setHtml(s); + addConsoleMessage(_js, s); }; ui->webView->page()->runJavaScript("JSON.stringify(___RET)", f2); }; @@ -439,6 +485,17 @@ void Main::eval(QString const& _js) ui->webView->page()->runJavaScript(c, f); } +void Main::addConsoleMessage(QString const& _js, QString const& _s) +{ + m_consoleHistory.push_back(qMakePair(_js, _s)); + QString r = "" Div(Mono "position: absolute; bottom: 0; border: 0px; margin: 0px; width: 100%"); + for (auto const& i: m_consoleHistory) + r += "
>" + i.first.toHtmlEscaped() + "
" + "
 " + i.second + "
"; + r += ""; + ui->jsConsole->setHtml(r); +} + static Public stringToPublic(QString const& _a) { string sn = _a.toStdString(); @@ -456,7 +513,7 @@ QString Main::pretty(dev::Address _a) const if (g_newNameReg) { - QString s = QString::fromStdString(toString(abiOut(ethereum()->call(g_newNameReg, abiIn("nameOf(address)", _a))))); + QString s = QString::fromStdString(toString(abiOut(ethereum()->call(g_newNameReg, abiIn("nameOf(address)", _a)).output))); if (s.size()) return s; } @@ -464,10 +521,83 @@ QString Main::pretty(dev::Address _a) const return fromRaw(n); } +template inline string toBase36(FixedHash const& _h) +{ + static char const* c_alphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + typename FixedHash::Arith a = _h; + std::string ret; + for (; a > 0; a /= 36) + ret = c_alphabet[(unsigned)a % 36] + ret; + return ret; +} + +template inline FixedHash fromBase36(string const& _h) +{ + typename FixedHash::Arith ret = 0; + for (char c: _h) + ret = ret * 36 + (c < 'A' ? c - '0' : (c - 'A' + 10)); + return ret; +} + +static string iban(std::string _c, std::string _d) +{ + boost::to_upper(_c); + boost::to_upper(_d); + auto totStr = _d + _c + "00"; + bigint tot = 0; + for (char x: totStr) + if (x >= 'A') + tot = tot * 100 + x - 'A' + 10; + else + tot = tot * 10 + x - '0'; + unsigned check = (unsigned)(u256)(98 - tot % 97); + ostringstream out; + out << _c << setfill('0') << setw(2) << check << _d; + return out.str(); +} + +static std::pair fromIban(std::string _iban) +{ + if (_iban.size() < 4) + return std::make_pair(string(), string()); + boost::to_upper(_iban); + std::string c = _iban.substr(0, 2); + std::string d = _iban.substr(4); + if (iban(c, d) != _iban) + return std::make_pair(string(), string()); + return make_pair(c, d); +} + +static string directICAP(dev::Address _a) +{ + if (!!_a[0]) + return string(); + std::string d = toBase36(_a); + while (d.size() < 30) + d = "0" + d; + return iban("XE", d); +} + +static Address fromICAP(std::string const& _s) +{ + std::string country; + std::string data; + std::tie(country, data) = fromIban(_s); + if (country.empty()) + return Address(); + if (country == "XE" && data.size() == 30) + // Direct ICAP + return fromBase36(data); + // TODO: Indirect ICAP + return Address(); +} + QString Main::render(dev::Address _a) const { QString p = pretty(_a); - if (!p.isNull()) + if (!_a[0]) + p += QString(p.isEmpty() ? "" : " ") + QString::fromStdString(directICAP(_a)); + if (!p.isEmpty()) return p + " (" + QString::fromStdString(_a.abridged()) + ")"; return QString::fromStdString(_a.abridged()); } @@ -488,7 +618,7 @@ Address Main::fromString(QString const& _n) const auto g_newNameReg = getNameReg(); if (g_newNameReg) { - Address a = abiOut
(ethereum()->call(g_newNameReg, abiIn("addressOf(string32)", ::fromString(_n.toStdString())))); + Address a = abiOut
(ethereum()->call(g_newNameReg, abiIn("addressOf(string32)", ::fromString(_n.toStdString()))).output); if (a) return a; } @@ -496,7 +626,7 @@ Address Main::fromString(QString const& _n) const { try { - return Address(fromHex(_n.toStdString(), ThrowType::Throw)); + return Address(fromHex(_n.toStdString(), WhenError::Throw)); } catch (BadHexCharacter& _e) { @@ -510,6 +640,8 @@ Address Main::fromString(QString const& _n) const return Address(); } } + else if (Address a = fromICAP(_n.toStdString())) + return a; else return Address(); } @@ -527,10 +659,10 @@ QString Main::lookup(QString const& _a) const h256 ret; // TODO: fix with the new DNSreg contract -// if (h160 dnsReg = (u160)ethereum()->stateAt(c_config, 4, 0)) +// if (h160 dnsReg = (u160)ethereum()->stateAt(c_config, 4, PendingBlock)) // ret = ethereum()->stateAt(dnsReg, n); /* if (!ret) - if (h160 nameReg = (u160)ethereum()->stateAt(c_config, 0, 0)) + if (h160 nameReg = (u160)ethereum()->stateAt(c_config, 0, PendingBlock)) ret = ethereum()->stateAt(nameReg, n2); */ if (ret && !((u256)ret >> 32)) @@ -579,9 +711,7 @@ void Main::writeSettings() } s.setValue("upnp", ui->upnp->isChecked()); - s.setValue("forceAddress", ui->forceAddress->text()); - s.setValue("usePast", ui->usePast->isChecked()); - s.setValue("localNetworking", ui->localNetworking->isChecked()); + s.setValue("forceAddress", ui->forcePublicIP->text()); s.setValue("forceMining", ui->forceMining->isChecked()); s.setValue("paranoia", ui->paranoia->isChecked()); s.setValue("natSpec", ui->natSpec->isChecked()); @@ -589,6 +719,7 @@ void Main::writeSettings() s.setValue("showAllAccounts", ui->showAllAccounts->isChecked()); s.setValue("clientName", ui->clientName->text()); s.setValue("idealPeers", ui->idealPeers->value()); + s.setValue("listenIP", ui->listenIP->text()); s.setValue("port", ui->port->value()); s.setValue("url", ui->urlEdit->text()); s.setValue("privateChain", m_privateChain); @@ -648,9 +779,8 @@ void Main::readSettings(bool _skipGeometry) } ui->upnp->setChecked(s.value("upnp", true).toBool()); - ui->forceAddress->setText(s.value("forceAddress", "").toString()); - ui->usePast->setChecked(s.value("usePast", true).toBool()); - ui->localNetworking->setChecked(s.value("localNetworking", true).toBool()); + ui->forcePublicIP->setText(s.value("forceAddress", "").toString()); + ui->dropPeers->setChecked(false); ui->forceMining->setChecked(s.value("forceMining", false).toBool()); on_forceMining_triggered(); ui->paranoia->setChecked(s.value("paranoia", false).toBool()); @@ -661,6 +791,7 @@ void Main::readSettings(bool _skipGeometry) if (ui->clientName->text().isEmpty()) ui->clientName->setText(QInputDialog::getText(nullptr, "Enter identity", "Enter a name that will identify you on the peer network")); ui->idealPeers->setValue(s.value("idealPeers", ui->idealPeers->value()).toInt()); + ui->listenIP->setText(s.value("listenIP", "").toString()); ui->port->setValue(s.value("port", ui->port->value()).toInt()); ui->nameReg->setText(s.value("nameReg", "").toString()); m_privateChain = s.value("privateChain", "").toString(); @@ -729,7 +860,7 @@ void Main::on_importKeyFile_triggered() m_myKeys.push_back(KeyPair::create()); keysChanged(); } - ethereum()->transact(k.sec(), ethereum()->balanceAt(k.address()) - gasPrice() * c_txGas, m_myKeys.back().address(), {}, c_txGas, gasPrice()); + ethereum()->submitTransaction(k.sec(), ethereum()->balanceAt(k.address()) - gasPrice() * c_txGas, m_myKeys.back().address(), {}, c_txGas, gasPrice()); } else QMessageBox::warning(this, "Already Have Key", "Could not import the secret key: we already own this account."); @@ -780,15 +911,28 @@ void Main::on_jitvm_triggered() void Main::on_urlEdit_returnPressed() { QString s = ui->urlEdit->text(); - QRegExp r("([a-z]+://)?([^/]*)(.*)"); - if (r.exactMatch(s)) - if (r.cap(2).isEmpty()) - s = (r.cap(1).isEmpty() ? "file://" : r.cap(1)) + r.cap(3); + QUrl url(s); + if (url.scheme().isEmpty() || url.scheme() == "eth" || url.path().endsWith(".dapp")) + { + try + { + //try do resolve dapp url + m_dappLoader->loadDapp(s); + } + catch (...) + { + qWarning() << boost::current_exception_diagnostic_information().c_str(); + } + } + + if (url.scheme().isEmpty()) + if (url.path().indexOf('/') < url.path().indexOf('.')) + url.setScheme("file"); else - s = (r.cap(1).isEmpty() ? "http://" : r.cap(1)) + lookup(r.cap(2)) + r.cap(3); - else{} - qDebug() << s; - ui->webView->setUrl(s); + url.setScheme("http"); + else {} + qDebug() << url.toString(); + ui->webView->page()->setUrl(url); } void Main::on_nameReg_textChanged() @@ -805,7 +949,7 @@ void Main::on_nameReg_textChanged() void Main::on_preview_triggered() { - ethereum()->setDefault(ui->preview->isChecked() ? 0 : -1); + ethereum()->setDefault(ui->preview->isChecked() ? PendingBlock : LatestBlock); refreshAll(); } @@ -835,7 +979,7 @@ void Main::refreshBalances() u256 totalBalance = 0; /* map> altCoins; Address coinsAddr = getCurrencies(); - for (unsigned i = 0; i < ethereum()->stateAt(coinsAddr, 0); ++i) + for (unsigned i = 0; i < ethereum()->stateAt(coinsAddr, PendingBlock); ++i) { auto n = ethereum()->stateAt(coinsAddr, i + 1); auto addr = right160(ethereum()->stateAt(coinsAddr, n)); @@ -934,6 +1078,7 @@ void Main::refreshPending() void Main::refreshAccounts() { +#if ETH_FATDB cwatch << "refreshAccounts()"; ui->accounts->clear(); ui->contracts->clear(); @@ -951,14 +1096,14 @@ void Main::refreshAccounts() ->setData(Qt::UserRole, QByteArray((char const*)i.data(), Address::size)); } } +#endif } void Main::refreshBlockCount() { - cwatch << "refreshBlockCount()"; auto d = ethereum()->blockChain().details(); - auto diff = BlockInfo(ethereum()->blockChain().block()).difficulty; - ui->blockCount->setText(QString("%6 #%1 @%3 T%2 PV%4 D%5").arg(d.number).arg(toLog2(d.totalDifficulty)).arg(toLog2(diff)).arg(c_protocolVersion).arg(c_databaseVersion).arg(m_privateChain.size() ? "[" + m_privateChain + "] " : "testnet")); + BlockQueueStatus b = ethereum()->blockQueueStatus(); + ui->chainStatus->setText(QString("%3 ready %4 future %5 unknown %6 bad %1 #%2").arg(m_privateChain.size() ? "[" + m_privateChain + "] " : "testnet").arg(d.number).arg(b.ready).arg(b.future).arg(b.unknown).arg(b.bad)); } void Main::on_turboMining_triggered() @@ -973,7 +1118,7 @@ void Main::refreshBlockChain() // TODO: keep the same thing highlighted. // TODO: refactor into MVC // TODO: use get by hash/number - // TODO: transactions, log addresses, log topics + // TODO: transactions auto const& bc = ethereum()->blockChain(); QStringList filters = ui->blockChainFilter->text().toLower().split(QRegExp("\\s+"), QString::SkipEmptyParts); @@ -985,15 +1130,17 @@ void Main::refreshBlockChain() h256 h(f.toStdString()); if (bc.isKnown(h)) blocks.insert(h); + for (auto const& b: bc.withBlockBloom(LogBloom().shiftBloom<3>(sha3(h)), 0, -1)) + blocks.insert(bc.numberHash(b)); } else if (f.toLongLong() <= bc.number()) - blocks.insert(bc.numberHash(u256(f.toLongLong()))); - /*else if (f.size() == 40) + blocks.insert(bc.numberHash((unsigned)f.toLongLong())); + else if (f.size() == 40) { - Address h(f[0]); - if (bc.(h)) - blocks.insert(h); - }*/ + Address h(f.toStdString()); + for (auto const& b: bc.withBlockBloom(LogBloom().shiftBloom<3>(sha3(h)), 0, -1)) + blocks.insert(bc.numberHash(b)); + } QByteArray oldSelected = ui->blocks->count() ? ui->blocks->currentItem()->data(Qt::UserRole).toByteArray() : QByteArray(); ui->blocks->clear(); @@ -1135,6 +1282,7 @@ void Main::timerEvent(QTimerEvent*) refreshNetwork(); refreshWhispers(); refreshCache(); + refreshBlockCount(); poll(); } else @@ -1142,7 +1290,7 @@ void Main::timerEvent(QTimerEvent*) for (auto const& i: m_handlers) { - auto ls = ethereum()->checkWatch(i.first); + auto ls = ethereum()->checkWatchSafe(i.first); if (ls.size()) { cnote << "FIRING WATCH" << i.first << ls.size(); @@ -1247,11 +1395,14 @@ void Main::on_transactionQueue_currentItemChanged() } s << "
Hex: " Span(Mono) << toHex(tx.rlp()) << "
"; s << "
"; - s << "
Log Bloom: " << receipt.bloom() << "
"; + if (!!receipt.bloom()) + s << "
Log Bloom: " << receipt.bloom() << "
"; + else + s << "
Log Bloom: Uneventful
"; auto r = receipt.rlp(); s << "
Receipt: " << toString(RLP(r)) << "
"; s << "
Receipt-Hex: " Span(Mono) << toHex(receipt.rlp()) << "
"; - s << renderDiff(ethereum()->diff(i, 0)); + s << renderDiff(ethereum()->diff(i, PendingBlock)); // s << "Pre: " << fs.rootHash() << "
"; // s << "Post: " << ts.rootHash() << ""; } @@ -1282,7 +1433,7 @@ void Main::on_inject_triggered() QString s = QInputDialog::getText(this, "Inject Transaction", "Enter transaction dump in hex"); try { - bytes b = fromHex(s.toStdString(), ThrowType::Throw); + bytes b = fromHex(s.toStdString(), WhenError::Throw); ethereum()->inject(&b); } catch (BadHexCharacter& _e) @@ -1322,57 +1473,64 @@ void Main::on_blocks_currentItemChanged() s << "

" << h << "

"; s << "

#" << info.number; s << "   " << timestamp << "

"; - s << "
D/TD: 2^" << log2((double)info.difficulty) << "/2^" << log2((double)details.totalDifficulty) << ""; - s << "   Children: " << details.children.size() << ""; - s << "
Gas used/limit: " << info.gasUsed << "/" << info.gasLimit << ""; - s << "
Coinbase: " << pretty(info.coinbaseAddress).toHtmlEscaped().toStdString() << " " << info.coinbaseAddress; - s << "
Seed hash: " << info.seedHash << ""; - s << "
Mix hash: " << info.mixHash << ""; - s << "
Nonce: " << info.nonce << ""; - s << "
Hash w/o nonce: " << info.headerHash(WithoutNonce) << ""; - s << "
Difficulty: " << info.difficulty << ""; + s << "
D/TD: " << info.difficulty << "/" << details.totalDifficulty << " = 2^" << log2((double)info.difficulty) << "/2^" << log2((double)details.totalDifficulty) << "
"; + s << "   Children: " << details.children.size() << ""; + s << "
Gas used/limit: " << info.gasUsed << "/" << info.gasLimit << "" << "
"; + s << "
Beneficiary: " << pretty(info.coinbaseAddress).toHtmlEscaped().toStdString() << " " << info.coinbaseAddress << "" << "
"; + s << "
Seed hash: " << info.seedHash() << "" << "
"; + s << "
Mix hash: " << info.mixHash << "" << "
"; + s << "
Nonce: " << info.nonce << "" << "
"; + s << "
Hash w/o nonce: " << info.headerHash(WithoutNonce) << "" << "
"; + s << "
Difficulty: " << info.difficulty << "" << "
"; if (info.number) { auto e = Ethasher::eval(info); - s << "
Proof-of-Work: " << e.value << " <= " << (h256)u256((bigint(1) << 256) / info.difficulty) << " (mixhash: " << e.mixHash.abridged() << ")"; + s << "
Proof-of-Work: " << e.value << " <= " << (h256)u256((bigint(1) << 256) / info.difficulty) << " (mixhash: " << e.mixHash.abridged() << ")" << "
"; + s << "
Parent: " << info.parentHash << "" << "
"; } else - s << "
Proof-of-Work: Phil has nothing to prove"; - s << "
Parent: " << info.parentHash << ""; -// s << "
Bloom: " << details.bloom << ""; - s << "
Log Bloom: " << info.logBloom << ""; - s << "
Transactions: " << block[1].itemCount() << " @" << info.transactionsRoot << ""; - s << "
Receipts: @" << info.receiptsRoot << ":"; - s << "
Uncles: " << block[2].itemCount() << " @" << info.sha3Uncles << ""; + { + s << "
Proof-of-Work: Phil has nothing to prove
"; + s << "
Parent: It was a virgin birth
"; + } +// s << "
Bloom: " << details.bloom << ""; + if (!!info.logBloom) + s << "
Log Bloom: " << info.logBloom << "
"; + else + s << "
Log Bloom: Uneventful
"; + s << "
Transactions: " << block[1].itemCount() << " @" << info.transactionsRoot << "" << "
"; + s << "
Uncles: " << block[2].itemCount() << " @" << info.sha3Uncles << "" << "
"; for (auto u: block[2]) { BlockInfo uncle = BlockInfo::fromHeader(u.data()); - char const* line = "
 "; - 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; - s << line << "Seed hash: " << uncle.seedHash << ""; - s << line << "Mix hash: " << uncle.mixHash << ""; - s << line << "Nonce: " << uncle.nonce << ""; - s << line << "Hash w/o nonce: " << uncle.headerHash(WithoutNonce) << ""; - s << line << "Difficulty: " << uncle.difficulty << ""; + char const* line = "
 "; + 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 << "" << ""; + s << line << "Seed hash: " << uncle.seedHash() << "" << ""; + s << line << "Mix hash: " << uncle.mixHash << "" << ""; + s << line << "Nonce: " << uncle.nonce << "" << ""; + s << line << "Hash w/o nonce: " << uncle.headerHash(WithoutNonce) << "" << ""; + s << line << "Difficulty: " << uncle.difficulty << "" << ""; auto e = Ethasher::eval(uncle); - s << line << "Proof-of-Work: " << e.value << " <= " << (h256)u256((bigint(1) << 256) / uncle.difficulty) << " (mixhash: " << e.mixHash.abridged() << ")"; + s << line << "Proof-of-Work: " << e.value << " <= " << (h256)u256((bigint(1) << 256) / uncle.difficulty) << " (mixhash: " << e.mixHash.abridged() << ")" << ""; } if (info.parentHash) - s << "
Pre: " << BlockInfo(ethereum()->blockChain().block(info.parentHash)).stateRoot << ""; + s << "
Pre: " << BlockInfo(ethereum()->blockChain().block(info.parentHash)).stateRoot << "" << "
"; else - s << "
Pre: Nothing is before Phil"; + s << "
Pre: Nothing is before Phil" << "
"; + + s << "
Receipts: @" << info.receiptsRoot << ":" << "
"; BlockReceipts receipts = ethereum()->blockChain().receipts(h); unsigned ii = 0; for (auto const& i: block[1]) { - s << "
" << sha3(i.data()).abridged() << ": " << receipts.receipts[ii].stateRoot() << " [" << receipts.receipts[ii].gasUsed() << " used]"; + s << "
" << sha3(i.data()).abridged() << ": " << receipts.receipts[ii].stateRoot() << " [" << receipts.receipts[ii].gasUsed() << " used]" << "
"; ++ii; } - s << "
Post: " << info.stateRoot << ""; - s << "
Dump: " Span(Mono) << toHex(block[0].data()) << ""; + s << "
Post: " << info.stateRoot << "" << "
"; + s << "
Dump: " Span(Mono) << toHex(block[0].data()) << "" << "
"; s << "
Receipts-Hex: " Span(Mono) << toHex(receipts.rlp()) << "
"; } else @@ -1384,36 +1542,36 @@ void Main::on_blocks_currentItemChanged() TransactionReceipt receipt = ethereum()->blockChain().receipts(h).receipts[txi]; s << "

" << th << "

"; s << "

" << h << "[" << txi << "]

"; - s << "
From: " << pretty(ss).toHtmlEscaped().toStdString() << " " << ss; + s << "
From: " << pretty(ss).toHtmlEscaped().toStdString() << " " << ss << "" << "
"; if (tx.isCreation()) - s << "
Creates: " << pretty(right160(th)).toHtmlEscaped().toStdString() << " " << right160(th); + s << "
Creates: " << pretty(right160(th)).toHtmlEscaped().toStdString() << " " << right160(th) << "
"; else - s << "
To: " << pretty(tx.receiveAddress()).toHtmlEscaped().toStdString() << " " << tx.receiveAddress(); - s << "
Value: " << formatBalance(tx.value()) << ""; - s << "   #" << tx.nonce() << ""; - s << "
Gas price: " << formatBalance(tx.gasPrice()) << ""; - s << "
Gas: " << tx.gas() << ""; - s << "
V: " << hex << nouppercase << (int)tx.signature().v << " + 27"; - s << "
R: " << hex << nouppercase << tx.signature().r << ""; - s << "
S: " << hex << nouppercase << tx.signature().s << ""; - s << "
Msg: " << tx.sha3(eth::WithoutSignature) << ""; - if (tx.isCreation()) + s << "
To: " << pretty(tx.receiveAddress()).toHtmlEscaped().toStdString() << " " << tx.receiveAddress() << "
"; + s << "
Value: " << formatBalance(tx.value()) << "" << "
"; + s << "   #" << tx.nonce() << "" << ""; + s << "
Gas price: " << formatBalance(tx.gasPrice()) << "" << "
"; + s << "
Gas: " << tx.gas() << "" << "
"; + s << "
V: " << hex << nouppercase << (int)tx.signature().v << " + 27" << "
"; + s << "
R: " << hex << nouppercase << tx.signature().r << "" << "
"; + s << "
S: " << hex << nouppercase << tx.signature().s << "" << "
"; + s << "
Msg: " << tx.sha3(eth::WithoutSignature) << "" << "
"; + if (!tx.data().empty()) { - if (tx.data().size()) + if (tx.isCreation()) s << "

Code

" << disassemble(tx.data()); - } - else - { - if (tx.data().size()) - s << dev::memDump(tx.data(), 16, true); + else + s << "

Data

" << dev::memDump(tx.data(), 16, true); } s << "
Hex: " Span(Mono) << toHex(block[1][txi].data()) << "
"; s << "
"; - s << "
Log Bloom: " << receipt.bloom() << "
"; + if (!!receipt.bloom()) + s << "
Log Bloom: " << receipt.bloom() << "
"; + else + s << "
Log Bloom: Uneventful
"; auto r = receipt.rlp(); s << "
Receipt: " << toString(RLP(r)) << "
"; s << "
Receipt-Hex: " Span(Mono) << toHex(receipt.rlp()) << "
"; - s << renderDiff(ethereum()->diff(txi, h)); + s << "

Diff

" << renderDiff(ethereum()->diff(txi, h)); ui->debugCurrent->setEnabled(true); ui->debugDumpState->setEnabled(true); ui->debugDumpStatePre->setEnabled(true); @@ -1437,7 +1595,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(), 0); + Executive e(s, ethereum()->blockChain()); Debugger dw(this, this); dw.populate(e, Transaction(t, CheckSignature::Sender)); dw.exec(); @@ -1513,16 +1671,22 @@ void Main::on_ourAccounts_doubleClicked() void Main::on_accounts_doubleClicked() { - auto hba = ui->accounts->currentItem()->data(Qt::UserRole).toByteArray(); - auto h = Address((byte const*)hba.data(), Address::ConstructFromPointer); - qApp->clipboard()->setText(QString::fromStdString(toHex(h.asArray()))); + if (ui->accounts->count()) + { + auto hba = ui->accounts->currentItem()->data(Qt::UserRole).toByteArray(); + auto h = Address((byte const*)hba.data(), Address::ConstructFromPointer); + qApp->clipboard()->setText(QString::fromStdString(toHex(h.asArray()))); + } } void Main::on_contracts_doubleClicked() { - auto hba = ui->contracts->currentItem()->data(Qt::UserRole).toByteArray(); - auto h = Address((byte const*)hba.data(), Address::ConstructFromPointer); - qApp->clipboard()->setText(QString::fromStdString(toHex(h.asArray()))); + if (ui->contracts->count()) + { + auto hba = ui->contracts->currentItem()->data(Qt::UserRole).toByteArray(); + auto h = Address((byte const*)hba.data(), Address::ConstructFromPointer); + qApp->clipboard()->setText(QString::fromStdString(toHex(h.asArray()))); + } } static shh::FullTopic topicFromText(QString _s) @@ -1615,17 +1779,15 @@ void Main::on_net_triggered() if (ui->net->isChecked()) { web3()->setIdealPeerCount(ui->idealPeers->value()); - web3()->setNetworkPreferences(netPrefs()); + web3()->setNetworkPreferences(netPrefs(), ui->dropPeers->isChecked()); ethereum()->setNetworkId(m_privateChain.size() ? sha3(m_privateChain.toStdString()) : h256()); - // TODO: p2p -// if (m_networkConfig.size()/* && ui->usePast->isChecked()*/) -// web3()->restoreNetwork(bytesConstRef((byte*)m_networkConfig.data(), m_networkConfig.size())); web3()->startNetwork(); ui->downloadView->setDownloadMan(ethereum()->downloadMan()); } else { ui->downloadView->setDownloadMan(nullptr); + writeSettings(); web3()->stopNetwork(); } } @@ -1637,13 +1799,25 @@ void Main::on_connect_triggered() ui->net->setChecked(true); on_net_triggered(); } - bool ok = false; - QString s = QInputDialog::getItem(this, "Connect to a Network Peer", "Enter a peer to which a connection may be made:", m_servers, m_servers.count() ? rand() % m_servers.count() : 0, true, &ok); - if (ok && s.contains(":")) + + m_connect.setEnvironment(m_servers); + if (m_connect.exec() == QDialog::Accepted) { - string host = s.section(":", 0, 0).toStdString(); - unsigned short port = s.section(":", 1).toInt(); - web3()->connect(host, port); + bool required = m_connect.required(); + string host(m_connect.host().toStdString()); + NodeId nodeID; + try + { + nodeID = NodeId(fromHex(m_connect.nodeId().toStdString())); + } + catch (BadHexCharacter&) {} + + m_connect.reset(); + + if (required) + web3()->requirePeer(nodeID, host); + else + web3()->addNode(nodeID, host); } } @@ -1681,9 +1855,9 @@ bool beginsWith(Address _a, bytes const& _b) void Main::on_newAccount_triggered() { bool ok = true; - enum { NoVanity = 0, FirstTwo, FirstTwoNextTwo, FirstThree, FirstFour, StringMatch }; - QStringList items = {"No vanity (instant)", "Two pairs first (a few seconds)", "Two pairs first and second (a few minutes)", "Three pairs first (a few minutes)", "Four pairs first (several hours)", "Specific hex string"}; - unsigned v = items.QList::indexOf(QInputDialog::getItem(this, "Vanity Key?", "Would you a vanity key? This could take several hours.", items, 0, false, &ok)); + enum { NoVanity = 0, DirectICAP, FirstTwo, FirstTwoNextTwo, FirstThree, FirstFour, StringMatch }; + QStringList items = {"No vanity (instant)", "Direct ICAP address", "Two pairs first (a few seconds)", "Two pairs first and second (a few minutes)", "Three pairs first (a few minutes)", "Four pairs first (several hours)", "Specific hex string"}; + unsigned v = items.QList::indexOf(QInputDialog::getItem(this, "Vanity Key?", "Would you a vanity key? This could take several hours.", items, 1, false, &ok)); if (!ok) return; @@ -1709,6 +1883,7 @@ void Main::on_newAccount_triggered() lp = KeyPair::create(); auto a = lp.address(); if (v == NoVanity || + (v == DirectICAP && !a[0]) || (v == FirstTwo && a[0] == a[1]) || (v == FirstTwoNextTwo && a[0] == a[1] && a[2] == a[3]) || (v == FirstThree && a[0] == a[1] && a[1] == a[2]) || @@ -1753,7 +1928,7 @@ void Main::on_go_triggered() ui->net->setChecked(true); on_net_triggered(); } - web3()->connect(Host::pocHost()); + web3()->addNode(p2p::NodeId(), Host::pocHost()); } QString Main::prettyU256(dev::u256 _n) const @@ -1835,3 +2010,9 @@ void Main::refreshWhispers() ui->whispers->addItem(item); } } + +void Main::dappLoaded(Dapp& _dapp) +{ + QUrl url = m_dappHost->hostDapp(std::move(_dapp)); + ui->webView->page()->setUrl(url); +} diff --git a/alethzero/MainWin.h b/alethzero/MainWin.h index 378877468..fccbc855d 100644 --- a/alethzero/MainWin.h +++ b/alethzero/MainWin.h @@ -40,6 +40,7 @@ #include "Context.h" #include "Transact.h" #include "NatspecHandler.h" +#include "Connect.h" namespace Ui { class Main; @@ -54,8 +55,11 @@ namespace jsonrpc { class HttpServer; } -class QQuickView; +class QWebEnginePage; class OurWebThreeStubServer; +class DappLoader; +class DappHost; +struct Dapp; using WatchHandler = std::function; @@ -99,6 +103,7 @@ public slots: private slots: void eval(QString const& _js); + void addConsoleMessage(QString const& _js, QString const& _s); // Application void on_about_triggered(); @@ -172,6 +177,9 @@ private slots: void refreshBlockChain(); void addNewId(QString _ids); + // Dapps + void dappLoaded(Dapp& _dapp); //qt does not support rvalue refs for signals + signals: void poll(); @@ -234,8 +242,6 @@ private: QString m_privateChain; dev::Address m_nameReg; - QNetworkAccessManager m_webCtrl; - QList> m_consoleHistory; QMutex m_logLock; QString m_logHistory; @@ -248,4 +254,9 @@ private: NatspecHandler m_natSpecDB; Transact m_transact; + std::unique_ptr m_dappHost; + DappLoader* m_dappLoader; + QWebEnginePage* m_webPage; + + Connect m_connect; }; diff --git a/alethzero/Transact.cpp b/alethzero/Transact.cpp index df6c5258d..1ff3427e3 100644 --- a/alethzero/Transact.cpp +++ b/alethzero/Transact.cpp @@ -25,6 +25,7 @@ #include "Transact.h" #include +#include #include #include #include @@ -135,7 +136,39 @@ void Transact::updateFee() ui->total->setPalette(p); } -string Transact::getFunctionHashes(dev::solidity::CompilerStack const& _compiler, string const& _contractName) +void Transact::on_destination_currentTextChanged(QString) +{ + if (ui->destination->currentText().size() && ui->destination->currentText() != "(Create Contract)") + if (Address a = m_context->fromString(ui->destination->currentText())) + ui->calculatedName->setText(m_context->render(a)); + else + ui->calculatedName->setText("Unknown Address"); + else + ui->calculatedName->setText("Create Contract"); + rejigData(); +// updateFee(); +} + +static std::string toString(TransactionException _te) +{ + switch (_te) + { + case TransactionException::Unknown: return "Unknown error"; + case TransactionException::InvalidSignature: return "Permanent Abort: Invalid transaction signature"; + case TransactionException::InvalidNonce: return "Transient Abort: Invalid transaction nonce"; + case TransactionException::NotEnoughCash: return "Transient Abort: Not enough cash to pay for transaction"; + case TransactionException::OutOfGasBase: return "Permanent Abort: Not enough gas to consider transaction"; + case TransactionException::BlockGasLimitReached: return "Transient Abort: Gas limit of block reached"; + case TransactionException::BadInstruction: return "VM Error: Attempt to execute invalid instruction"; + case TransactionException::BadJumpDestination: return "VM Error: Attempt to jump to invalid destination"; + case TransactionException::OutOfGas: return "VM Error: Out of gas"; + case TransactionException::OutOfStack: return "VM Error: VM stack limit reached during execution"; + case TransactionException::StackUnderflow: return "VM Error: Stack underflow"; + default:; return std::string(); + } +} + +static string getFunctionHashes(dev::solidity::CompilerStack const& _compiler, string const& _contractName) { string ret = ""; auto const& contract = _compiler.getContractDefinition(_contractName); @@ -150,184 +183,256 @@ string Transact::getFunctionHashes(dev::solidity::CompilerStack const& _compiler return ret; } -void Transact::on_destination_currentTextChanged(QString) -{ - if (ui->destination->currentText().size() && ui->destination->currentText() != "(Create Contract)") - if (Address a = m_context->fromString(ui->destination->currentText())) - ui->calculatedName->setText(m_context->render(a)); - else - ui->calculatedName->setText("Unknown Address"); - else - ui->calculatedName->setText("Create Contract"); - rejigData(); -// updateFee(); -} - -void Transact::rejigData() +static tuple, bytes, string> userInputToCode(string const& _user, bool _opt) { - if (isCreation()) + string lll; + string solidity; + bytes data; + vector errors; + if (_user.find_first_not_of("1234567890abcdefABCDEF\n\t ") == string::npos && _user.size() % 2 == 0) { - string src = ui->data->toPlainText().toStdString(); - vector errors; - QString lll; - QString solidity; - if (src.find_first_not_of("1234567890abcdefABCDEF") == string::npos && src.size() % 2 == 0) - m_data = fromHex(src); - else if (sourceIsSolidity(src)) + std::string u = _user; + boost::replace_all_copy(u, "\n", ""); + boost::replace_all_copy(u, "\t", ""); + boost::replace_all_copy(u, " ", ""); + data = fromHex(u); + } + else if (sourceIsSolidity(_user)) + { + dev::solidity::CompilerStack compiler(true); + try { - dev::solidity::CompilerStack compiler; - try - { // compiler.addSources(dev::solidity::StandardSources); - m_data = compiler.compile(src, ui->optimize->isChecked()); - solidity = "

Solidity

"; - solidity += "
var " + QString::fromStdString(compiler.defaultContractName()) + " = web3.eth.contractFromAbi(" + QString::fromStdString(compiler.getInterface()).replace(QRegExp("\\s"), "").toHtmlEscaped() + ");
"; - solidity += "
" + QString::fromStdString(compiler.getSolidityInterface()).toHtmlEscaped() + "
"; - solidity += "
" + QString::fromStdString(getFunctionHashes(compiler)).toHtmlEscaped() + "
"; - } - catch (dev::Exception const& exception) - { - ostringstream error; - solidity::SourceReferenceFormatter::printExceptionInformation(error, exception, "Error", compiler); - solidity = "

Solidity

" + QString::fromStdString(error.str()).toHtmlEscaped() + "
"; - } - catch (...) - { - solidity = "

Solidity

Uncaught exception.
"; - } + data = compiler.compile(_user, _opt); + solidity = "

Solidity

"; + solidity += "
var " + compiler.defaultContractName() + " = web3.eth.contract(" + QString::fromStdString(compiler.getInterface()).replace(QRegExp("\\s"), "").toHtmlEscaped().toStdString() + ");
"; + solidity += "
" + QString::fromStdString(compiler.getSolidityInterface()).toHtmlEscaped().toStdString() + "
"; + solidity += "
" + QString::fromStdString(getFunctionHashes(compiler, "")).toHtmlEscaped().toStdString() + "
"; } + catch (dev::Exception const& exception) + { + ostringstream error; + solidity::SourceReferenceFormatter::printExceptionInformation(error, exception, "Error", compiler); + errors.push_back("Solidity: " + error.str()); + } + catch (...) + { + errors.push_back("Solidity: Uncaught exception"); + } + } #ifndef _MSC_VER - else if (sourceIsSerpent(src)) + else if (sourceIsSerpent(_user)) + { + try { - try - { - m_data = dev::asBytes(::compile(src)); - for (auto& i: errors) - i = "(LLL " + i + ")"; - } - catch (string const& err) - { - errors.push_back("Serpent " + err); - } + data = dev::asBytes(::compile(_user)); } + catch (string const& err) + { + errors.push_back("Serpent " + err); + } + } #endif + else + { + data = compileLLL(_user, _opt, &errors); + if (errors.empty()) + { + auto asmcode = compileLLLToAsm(_user, _opt); + lll = "

LLL

" + QString::fromStdString(asmcode).toHtmlEscaped().toStdString() + "
"; + } + } + return make_tuple(errors, data, lll + solidity); +} + +string Transact::natspecNotice(Address _to, bytes const& _data) +{ + if (ethereum()->codeAt(_to, PendingBlock).size()) + { + string userNotice = m_natSpecDB->getUserNotice(ethereum()->postState().codeHash(_to), _data); + if (userNotice.empty()) + return "Destination contract unknown."; else { - m_data = compileLLL(src, ui->optimize->isChecked(), &errors); - if (errors.empty()) - { - auto asmcode = compileLLLToAsm(src, false); - lll = "

Pre

" + QString::fromStdString(asmcode).toHtmlEscaped() + "
"; - if (ui->optimize->isChecked()) - { - asmcode = compileLLLToAsm(src, true); - lll = "

Opt

" + QString::fromStdString(asmcode).toHtmlEscaped() + "
" + lll; - } - } + NatspecExpressionEvaluator evaluator; + return evaluator.evalExpression(QString::fromStdString(userNotice)).toStdString(); } - QString errs; + } + else + return "Destination not a contract."; +} + +void Transact::rejigData() +{ + if (!ethereum()) + return; + + // Determine how much balance we have to play with... + auto s = findSecret(value() + ethereum()->gasLimitRemaining() * gasPrice()); + auto b = ethereum()->balanceAt(KeyPair(s).address(), PendingBlock); + + m_allGood = true; + QString htmlInfo; + + auto bail = [&](QString he) { + m_allGood = false; + ui->send->setEnabled(false); + ui->code->setHtml(he + htmlInfo); + }; + + // Determine m_info. + if (isCreation()) + { + string info; + vector errors; + tie(errors, m_data, info) = userInputToCode(ui->data->toPlainText().toStdString(), ui->optimize->isChecked()); if (errors.size()) { - errs = "

Errors

"; + // Errors determining transaction data (i.e. init code). Bail. + QString htmlErrors; for (auto const& i: errors) - errs.append("
" + QString::fromStdString(i).toHtmlEscaped() + "
"); + htmlErrors.append("
ERROR " + QString::fromStdString(i).toHtmlEscaped() + "
"); + bail(htmlErrors); + return; } - ui->code->setHtml(errs + lll + solidity + "

Code

" + QString::fromStdString(disassemble(m_data)).toHtmlEscaped() + "

Hex

" Div(Mono) + QString::fromStdString(toHex(m_data)) + ""); - ui->gas->setMinimum((qint64)Interface::txGas(m_data, 0)); - if (!ui->gas->isEnabled()) - ui->gas->setValue(m_backupGas); - ui->gas->setEnabled(true); + htmlInfo = QString::fromStdString(info) + "

Code

" + QString::fromStdString(disassemble(m_data)).toHtmlEscaped(); } else { m_data = parseData(ui->data->toPlainText().toStdString()); - auto to = m_context->fromString(ui->destination->currentText()); - QString natspec; - if (ethereum()->codeAt(to, 0).size()) - { - string userNotice = m_natSpecDB->getUserNotice(ethereum()->postState().codeHash(to), m_data); - if (userNotice.empty()) - natspec = "Destination contract unknown."; - else - { - NatspecExpressionEvaluator evaluator; - natspec = evaluator.evalExpression(QString::fromStdString(userNotice)); - } - ui->gas->setMinimum((qint64)Interface::txGas(m_data, 1)); - if (!ui->gas->isEnabled()) - ui->gas->setValue(m_backupGas); - ui->gas->setEnabled(true); - } - else - { - natspec += "Destination not a contract."; - if (ui->gas->isEnabled()) - m_backupGas = ui->gas->value(); - ui->gas->setValue((qint64)Interface::txGas(m_data)); - ui->gas->setEnabled(false); - } - ui->code->setHtml("

NatSpec

" + natspec + "

Dump

" + QString::fromStdString(dev::memDump(m_data, 8, true)) + "

Hex

" + Div(Mono) + QString::fromStdString(toHex(m_data)) + ""); + htmlInfo = "

Dump

" + QString::fromStdString(dev::memDump(m_data, 8, true)); + } + + htmlInfo += "

Hex

" + QString(Div(Mono)) + QString::fromStdString(toHex(m_data)) + ""; + + // Determine the minimum amount of gas we need to play... + qint64 baseGas = (qint64)Interface::txGas(m_data, 0); + qint64 gasNeeded = 0; + + if (b < value() + baseGas * gasPrice()) + { + // Not enough - bail. + bail("
ERROR No single account contains enough for paying even the basic amount of gas required.
"); + return; + } + else + gasNeeded = (qint64)min(ethereum()->gasLimitRemaining(), ((b - value()) / gasPrice())); + + // Dry-run execution to determine gas requirement and any execution errors + Address to; + ExecutionResult er; + if (isCreation()) + er = ethereum()->create(s, value(), m_data, gasNeeded, gasPrice()); + else + { + to = m_context->fromString(ui->destination->currentText()); + er = ethereum()->call(s, value(), to, m_data, gasNeeded, gasPrice()); + } + gasNeeded = (qint64)(er.gasUsed + er.gasRefunded); + htmlInfo = QString("
INFO Gas required: %1 total = %2 base, %3 exec [%4 refunded later]
").arg(gasNeeded).arg(baseGas).arg(gasNeeded - baseGas).arg((qint64)er.gasRefunded) + htmlInfo; + + if (er.excepted != TransactionException::None) + { + bail("
ERROR " + QString::fromStdString(toString(er.excepted)) + "
"); + return; + } + if (er.codeDeposit == CodeDeposit::Failed) + { + bail("
ERROR Code deposit failed due to insufficient gas; " + QString::fromStdString(toString(er.gasForDeposit)) + " GAS < " + QString::fromStdString(toString(er.depositSize)) + " bytes * " + QString::fromStdString(toString(c_createDataGas)) + "GAS/byte
"); + return; + } + + // Add Natspec information + if (!isCreation()) + htmlInfo = "
INFO " + QString::fromStdString(natspecNotice(to, m_data)).toHtmlEscaped() + "
" + htmlInfo; + + // Update gas + if (ui->gas->value() == ui->gas->minimum()) + { + ui->gas->setMinimum(gasNeeded); + ui->gas->setValue(gasNeeded); } + else + ui->gas->setMinimum(gasNeeded); + updateFee(); + + ui->code->setHtml(htmlInfo); + ui->send->setEnabled(m_allGood); } -void Transact::on_send_clicked() +Secret Transact::findSecret(u256 _totalReq) const { - u256 totalReq = value() + fee(); + if (!ethereum()) + return Secret(); + + Secret best; + u256 bestBalance = 0; for (auto const& i: m_myKeys) - if (ethereum()->balanceAt(i.address(), 0) >= totalReq) - { - Secret s = i.secret(); - if (isCreation()) + { + auto b = ethereum()->balanceAt(i.address(), PendingBlock); + if (b >= _totalReq) + return i.secret(); + if (b > bestBalance) + bestBalance = b, best = i.secret(); + } + return best; +} + +void Transact::on_send_clicked() +{ + Secret s = findSecret(value() + fee()); + auto b = ethereum()->balanceAt(KeyPair(s).address(), PendingBlock); + if (!s || b < value() + fee()) + { + QMessageBox::critical(this, "Transaction Failed", "Couldn't make transaction: no single account contains at least the required amount."); + return; + } + + if (isCreation()) + { + // If execution is a contract creation, add Natspec to + // a local Natspec LEVELDB + ethereum()->submitTransaction(s, value(), m_data, ui->gas->value(), gasPrice()); + string src = ui->data->toPlainText().toStdString(); + if (sourceIsSolidity(src)) + try { - // If execution is a contract creation, add Natspec to - // a local Natspec LEVELDB - ethereum()->transact(s, value(), m_data, ui->gas->value(), gasPrice()); - string src = ui->data->toPlainText().toStdString(); - if (sourceIsSolidity(src)) - try - { - dev::solidity::CompilerStack compiler; - m_data = compiler.compile(src, ui->optimize->isChecked()); - for (string const& s: compiler.getContractNames()) - { - h256 contractHash = compiler.getContractCodeHash(s); - m_natSpecDB->add(contractHash, compiler.getMetadata(s, dev::solidity::DocumentationType::NatspecUser)); - } - } - catch (...) - { - } - close(); - return; + dev::solidity::CompilerStack compiler(true); + m_data = compiler.compile(src, ui->optimize->isChecked()); + for (string const& s: compiler.getContractNames()) + { + h256 contractHash = compiler.getContractCodeHash(s); + m_natSpecDB->add(contractHash, compiler.getMetadata(s, dev::solidity::DocumentationType::NatspecUser)); + } } - else - ethereum()->transact(s, value(), m_context->fromString(ui->destination->currentText()), m_data, ui->gas->value(), gasPrice()); - return; - } - QMessageBox::critical(this, "Transaction Failed", "Couldn't make transaction: no single account contains at least the required amount."); + catch (...) {} + } + else + ethereum()->submitTransaction(s, value(), m_context->fromString(ui->destination->currentText()), m_data, ui->gas->value(), gasPrice()); + close(); } void Transact::on_debug_clicked() { + Secret s = findSecret(value() + fee()); + auto b = ethereum()->balanceAt(KeyPair(s).address(), PendingBlock); + if (!s || b < value() + fee()) + { + QMessageBox::critical(this, "Transaction Failed", "Couldn't make transaction: no single account contains at least the required amount."); + return; + } + try { - u256 totalReq = value() + fee(); - for (auto i: m_myKeys) - if (ethereum()->balanceAt(i.address()) >= totalReq) - { - State st(ethereum()->postState()); - Secret s = i.secret(); - Transaction t = isCreation() ? - Transaction(value(), gasPrice(), ui->gas->value(), m_data, st.transactionsFrom(dev::toAddress(s)), s) : - Transaction(value(), gasPrice(), ui->gas->value(), m_context->fromString(ui->destination->currentText()), m_data, st.transactionsFrom(dev::toAddress(s)), s); - Debugger dw(m_context, this); - Executive e(st, ethereum()->blockChain(), 0); - dw.populate(e, t); - dw.exec(); - return; - } - QMessageBox::critical(this, "Transaction Failed", "Couldn't make transaction: no single account contains at least the required amount."); + State st(ethereum()->postState()); + Transaction t = isCreation() ? + Transaction(value(), gasPrice(), ui->gas->value(), m_data, st.transactionsFrom(dev::toAddress(s)), s) : + Transaction(value(), gasPrice(), ui->gas->value(), m_context->fromString(ui->destination->currentText()), m_data, st.transactionsFrom(dev::toAddress(s)), s); + Debugger dw(m_context, this); + Executive e(st, ethereum()->blockChain(), 0); + dw.populate(e, t); + dw.exec(); } catch (dev::Exception const& _e) { diff --git a/alethzero/Transact.h b/alethzero/Transact.h index 8ef4f41e0..71bc393b2 100644 --- a/alethzero/Transact.h +++ b/alethzero/Transact.h @@ -57,7 +57,7 @@ private slots: void on_cancel_clicked() { close(); } private: - dev::eth::Client* ethereum() { return m_ethereum; } + dev::eth::Client* ethereum() const { return m_ethereum; } void rejigData(); void updateDestination(); @@ -68,15 +68,17 @@ private: dev::u256 value() const; dev::u256 gasPrice() const; - std::string getFunctionHashes(dev::solidity::CompilerStack const& _compiler, std::string const& _contractName = std::string()); + std::string natspecNotice(dev::Address _to, dev::bytes const& _data); + dev::Secret findSecret(dev::u256 _totalReq) const; - Ui::Transact* ui; + Ui::Transact* ui = nullptr; - unsigned m_backupGas; + unsigned m_backupGas = 0; dev::bytes m_data; QList m_myKeys; - dev::eth::Client* m_ethereum; - Context* m_context; - NatSpecFace* m_natSpecDB; + dev::eth::Client* m_ethereum = nullptr; + Context* m_context = nullptr; + NatSpecFace* m_natSpecDB = nullptr; + bool m_allGood = false; }; diff --git a/alethzero/WebPage.cpp b/alethzero/WebPage.cpp new file mode 100644 index 000000000..08c5c3cc6 --- /dev/null +++ b/alethzero/WebPage.cpp @@ -0,0 +1,28 @@ +/* + 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 WebPage.cpp + * @author Arkadiy Paronyan arkadiy@ethdev.com> + * @date 2015 + */ + +#include "WebPage.h" + +void WebPage::javaScriptConsoleMessage(QWebEnginePage::JavaScriptConsoleMessageLevel _level, const QString& _message, int _lineNumber, const QString& _sourceID) +{ + QString prefix = _level == QWebEnginePage::ErrorMessageLevel ? "error" : _level == QWebEnginePage::WarningMessageLevel ? "warning" : ""; + emit consoleMessage(QString("%1(%2:%3):%4").arg(prefix).arg(_sourceID).arg(_lineNumber).arg(_message)); +} diff --git a/alethzero/WebPage.h b/alethzero/WebPage.h new file mode 100644 index 000000000..5f1bb1dc8 --- /dev/null +++ b/alethzero/WebPage.h @@ -0,0 +1,40 @@ +/* + 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 WebPage.h + * @author Arkadiy Paronyan arkadiy@ethdev.com> + * @date 2015 + */ + +#pragma once + +#include +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wpedantic" //QtWebEngineWidgets/qwebenginecertificateerror.h:78:348: error: extra ';' +#include +#pragma GCC diagnostic pop + +class WebPage: public QWebEnginePage +{ + Q_OBJECT +public: + WebPage(QObject* _parent): QWebEnginePage(_parent) { } +signals: + void consoleMessage(QString const& _msg); + +protected: + void javaScriptConsoleMessage(QWebEnginePage::JavaScriptConsoleMessageLevel _level, const QString& _message, int _lineNumber, const QString& _sourceID) override; +}; diff --git a/cmake/EthCompilerSettings.cmake b/cmake/EthCompilerSettings.cmake index 6f2d1ad6c..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)) @@ -25,6 +33,10 @@ elseif ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang") set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "-O2 -g -DETH_DEBUG") set(ETH_SHARED 1) + if ("${CMAKE_SYSTEM_NAME}" MATCHES "Linux") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libstdc++ -fcolor-diagnostics -Qunused-arguments -DBOOST_ASIO_HAS_CLANG_LIBCXX") + endif() + elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") # enable parallel compilation @@ -34,12 +46,14 @@ elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") # disable decorated name length exceeded, name was truncated (4503) # disable warning C4535: calling _set_se_translator() requires /EHa (for boost tests) # declare Windows XP requirement - add_compile_options(/MP /EHsc /wd4068 /wd4996 /wd4503 -D_WIN32_WINNT=0x0501) + # undefine windows.h MAX && MIN macros cause it cause conflicts with std::min && std::max functions + add_compile_options(/MP /EHsc /wd4068 /wd4996 /wd4503 -D_WIN32_WINNT=0x0501 /DNOMINMAX) # disable empty object file warning set(CMAKE_STATIC_LINKER_FLAGS "${CMAKE_STATIC_LINKER_FLAGS} /ignore:4221") # warning LNK4075: ignoring '/EDITANDCONTINUE' due to '/SAFESEH' specification # warning LNK4099: pdb was not found with lib - set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /ignore:4099,4075") + # stack size 16MB + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} /ignore:4099,4075 /STACK:16777216") # windows likes static set(ETH_STATIC 1) @@ -47,3 +61,14 @@ else () message(WARNING "Your compiler is not tested, if you run into any issues, we'd welcome any patches.") endif () +if (("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") OR ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")) + option(USE_LD_GOLD "Use GNU gold linker" ON) + if (USE_LD_GOLD) + execute_process(COMMAND ${CMAKE_C_COMPILER} -fuse-ld=gold -Wl,--version ERROR_QUIET OUTPUT_VARIABLE LD_VERSION) + if ("${LD_VERSION}" MATCHES "GNU gold") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fuse-ld=gold") + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fuse-ld=gold") + endif () + endif () +endif () + diff --git a/cmake/EthDependencies.cmake b/cmake/EthDependencies.cmake index 645942998..61c87efd2 100644 --- a/cmake/EthDependencies.cmake +++ b/cmake/EthDependencies.cmake @@ -30,6 +30,9 @@ if (APPLE) set (CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} "/usr/local/opt/qt5") endif() +find_program(CTEST_COMMAND ctest) +message(STATUS "ctest path: ${CTEST_COMMAND}") + # Dependencies must have a version number, to ensure reproducible build. The version provided here is the one that is in the extdep repository. If you use system libraries, version numbers may be different. find_package (CryptoPP 5.6.2 EXACT REQUIRED) @@ -45,16 +48,10 @@ find_package (Jsoncpp 0.60 REQUIRED) message(" - Jsoncpp header: ${JSONCPP_INCLUDE_DIRS}") message(" - Jsoncpp lib : ${JSONCPP_LIBRARIES}") -# TODO the JsonRpcCpp package does not yet check for correct version number -# json-rpc-cpp support is currently not mandatory -# TODO make headless client optional # TODO get rid of -DETH_JSONRPC +# TODO add EXACT once we commit ourselves to cmake 3.x if (JSONRPC) - - find_package (JsonRpcCpp 0.3.2) - if (NOT JSON_RPC_CPP_FOUND) - message (FATAL_ERROR "JSONRPC 0.3.2. not found") - endif() + find_package (json_rpc_cpp 0.4 REQUIRED) message (" - json-rpc-cpp header: ${JSON_RPC_CPP_INCLUDE_DIRS}") message (" - json-rpc-cpp lib : ${JSON_RPC_CPP_LIBRARIES}") add_definitions(-DETH_JSONRPC) @@ -104,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) @@ -114,14 +111,17 @@ if (NOT HEADLESS) # find all of the Qt packages # remember to use 'Qt' instead of 'QT', cause unix is case sensitive # TODO make headless client optional - find_package (Qt5Core REQUIRED) - find_package (Qt5Gui REQUIRED) - find_package (Qt5Quick REQUIRED) - find_package (Qt5Qml REQUIRED) - find_package (Qt5Network REQUIRED) - find_package (Qt5Widgets REQUIRED) - find_package (Qt5WebEngine REQUIRED) - find_package (Qt5WebEngineWidgets REQUIRED) + + set (ETH_QT_VERSION 5.4) + + find_package (Qt5Core ${ETH_QT_VERSION} REQUIRED) + find_package (Qt5Gui ${ETH_QT_VERSION} REQUIRED) + find_package (Qt5Quick ${ETH_QT_VERSION} REQUIRED) + find_package (Qt5Qml ${ETH_QT_VERSION} REQUIRED) + find_package (Qt5Network ${ETH_QT_VERSION} REQUIRED) + find_package (Qt5Widgets ${ETH_QT_VERSION} REQUIRED) + find_package (Qt5WebEngine ${ETH_QT_VERSION} REQUIRED) + find_package (Qt5WebEngineWidgets ${ETH_QT_VERSION} REQUIRED) # we need to find path to macdeployqt on mac if (APPLE) @@ -134,16 +134,26 @@ if (NOT HEADLESS) message(" - windeployqt path: ${WINDEPLOYQT_APP}") endif() -# TODO check node && npm version - find_program(ETH_NODE node) - string(REGEX REPLACE "node" "" ETH_NODE_DIRECTORY ${ETH_NODE}) - message(" - nodejs location : ${ETH_NODE}") + if (USENPM) + + # TODO check node && npm version + find_program(ETH_NODE node) + string(REGEX REPLACE "node" "" ETH_NODE_DIRECTORY ${ETH_NODE}) + message(" - nodejs location : ${ETH_NODE}") - find_program(ETH_NPM npm) - string(REGEX REPLACE "npm" "" ETH_NPM_DIRECTORY ${ETH_NPM}) - message(" - npm location : ${ETH_NPM}") + find_program(ETH_NPM npm) + string(REGEX REPLACE "npm" "" ETH_NPM_DIRECTORY ${ETH_NPM}) + message(" - npm location : ${ETH_NPM}") + + if (NOT ETH_NODE) + message(FATAL_ERROR "node not found!") + endif() + if (NOT ETH_NPM) + message(FATAL_ERROR "npm not found!") + endif() + endif() -endif() #HEADLESS +endif() #GUI # use multithreaded boost libraries, with -mt suffix set(Boost_USE_MULTITHREADED ON) diff --git a/cmake/EthUtils.cmake b/cmake/EthUtils.cmake index c6fd43ed4..69690156a 100644 --- a/cmake/EthUtils.cmake +++ b/cmake/EthUtils.cmake @@ -22,3 +22,43 @@ macro(replace_if_different SOURCE DST) endif() endmacro() +macro(eth_add_test NAME) + + # parse arguments here + set(commands) + set(current_command "") + foreach (arg ${ARGN}) + if (arg STREQUAL "ARGS") + if (current_command) + list(APPEND commands ${current_command}) + endif() + set(current_command "") + else () + set(current_command "${current_command} ${arg}") + endif() + endforeach(arg) + list(APPEND commands ${current_command}) + + message(STATUS "test: ${NAME} | ${commands}") + + # create tests + set(index 0) + list(LENGTH commands count) + while (index LESS count) + list(GET commands ${index} test_arguments) + + set(run_test "--run_test=${NAME}") + add_test(NAME "${NAME}.${index}" COMMAND testeth ${run_test} ${test_arguments}) + + math(EXPR index "${index} + 1") + endwhile(index LESS count) + + # add target to run them + add_custom_target("test.${NAME}" + DEPENDS testeth + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMAND ${CMAKE_COMMAND} -DETH_TEST_NAME="${NAME}" -DCTEST_COMMAND="${CTEST_COMMAND}" -P "${ETH_SCRIPTS_DIR}/runtest.cmake" + ) + +endmacro() + diff --git a/cmake/FindJsonRpcCpp.cmake b/cmake/Findjson_rpc_cpp.cmake similarity index 65% rename from cmake/FindJsonRpcCpp.cmake rename to cmake/Findjson_rpc_cpp.cmake index 39fd3b39c..9b64cda2b 100644 --- a/cmake/FindJsonRpcCpp.cmake +++ b/cmake/Findjson_rpc_cpp.cmake @@ -10,6 +10,11 @@ # JSON_RPC_CPP_SERVER_LIBRARIES, the libraries needed to use json-rpc-cpp-server # JSON_RPC_CPP_CLIENT_LIBRARIES, the libraries needed to use json-rpc-cpp-client # JSON_RCP_CPP_FOUND, If false, do not try to use json-rpc-cpp. +# JSON_RPC_CPP_VERSION, version of library +# JSON_RPC_CPP_VERSION_MAJOR +# JSON_RPC_CPP_VERSION_MINOR +# JSON_RPC_CPP_VERSION_PATCH + # only look in default directories find_path( @@ -90,10 +95,28 @@ if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") endif() +if (JSON_RPC_CPP_INCLUDE_DIR) + set (JSON_RPC_CPP_VERSION_HEADER "${JSON_RPC_CPP_INCLUDE_DIR}/jsonrpccpp/version.h") + if (EXISTS ${JSON_RPC_CPP_VERSION_HEADER}) + file (STRINGS ${JSON_RPC_CPP_VERSION_HEADER} JSON_RPC_CPP_VERSION_MAJOR REGEX "^#define JSONRPC_CPP_MAJOR_VERSION[ \t]+[0-9]+$") + file (STRINGS ${JSON_RPC_CPP_VERSION_HEADER} JSON_RPC_CPP_VERSION_MINOR REGEX "^#define JSONRPC_CPP_MINOR_VERSION[ \t]+[0-9]+$") + file (STRINGS ${JSON_RPC_CPP_VERSION_HEADER} JSON_RPC_CPP_VERSION_PATCH REGEX "^#define JSONRPC_CPP_PATCH_VERSION[ \t]+[0-9]+$") + string (REGEX REPLACE "^#define JSONRPC_CPP_MAJOR_VERSION[ \t]+([0-9]+)" "\\1" JSON_RPC_CPP_VERSION_MAJOR ${JSON_RPC_CPP_VERSION_MAJOR}) + string (REGEX REPLACE "^#define JSONRPC_CPP_MINOR_VERSION[ \t]+([0-9]+)" "\\1" JSON_RPC_CPP_VERSION_MINOR ${JSON_RPC_CPP_VERSION_MINOR}) + string (REGEX REPLACE "^#define JSONRPC_CPP_PATCH_VERSION[ \t]+([0-9]+)" "\\1" JSON_RPC_CPP_VERSION_PATCH ${JSON_RPC_CPP_VERSION_PATCH}) + set (JSON_RPC_CPP_VERSION ${JSON_RPC_CPP_VERSION_MAJOR}.${JSON_RPC_CPP_VERSION_MINOR}.${JSON_RPC_CPP_VERSION_PATCH}) + endif() +endif() + # handle the QUIETLY and REQUIRED arguments and set JSON_RPC_CPP_FOUND to TRUE # if all listed variables are TRUE, hide their existence from configuration view include(FindPackageHandleStandardArgs) -find_package_handle_standard_args(json_rpc_cpp DEFAULT_MSG - JSON_RPC_CPP_COMMON_LIBRARY JSON_RPC_CPP_SERVER_LIBRARY JSON_RPC_CPP_CLIENT_LIBRARY JSON_RPC_CPP_INCLUDE_DIR) -mark_as_advanced (JSON_RPC_CPP_COMMON_LIBRARY JSON_RPC_CPP_SERVER_LIBRARY JSON_RPC_CPP_CLIENT_LIBRARY JSON_RPC_CPP_INCLUDE_DIR) + +find_package_handle_standard_args( + json_rpc_cpp + REQUIRED_VARS JSON_RPC_CPP_INCLUDE_DIR JSON_RPC_CPP_COMMON_LIBRARY JSON_RPC_CPP_SERVER_LIBRARY JSON_RPC_CPP_CLIENT_LIBRARY + VERSION_VAR JSON_RPC_CPP_VERSION +) + +mark_as_advanced (JSON_RPC_CPP_INCLUDE_DIR JSON_RPC_CPP_COMMON_LIBRARY JSON_RPC_CPP_SERVER_LIBRARY JSON_RPC_CPP_CLIENT_LIBRARY) diff --git a/cmake/scripts/runtest.cmake b/cmake/scripts/runtest.cmake new file mode 100644 index 000000000..15f7409ef --- /dev/null +++ b/cmake/scripts/runtest.cmake @@ -0,0 +1,15 @@ +# Should be used to run ctest +# +# example usage: +# cmake -DETH_TEST_NAME=TestInterfaceStub -DCTEST_COMMAND=/path/to/ctest -P scripts/runtest.cmake + +if (NOT CTEST_COMMAND) + message(FATAL_ERROR "ctest could not be found!") +endif() + +# verbosity is off, cause BOOST_MESSAGE is not thread safe and output is a trash +# see https://codecrafter.wordpress.com/2012/11/01/c-unit-test-framework-adapter-part-3/ +# +# output might not be usefull cause of thread safety issue +execute_process(COMMAND ${CTEST_COMMAND} --force-new-ctest-process -C Debug --output-on-failure -j 4 -R "${ETH_TEST_NAME}[.].*") + diff --git a/eth/main.cpp b/eth/main.cpp index 3b97c561d..9ec6dec98 100644 --- a/eth/main.cpp +++ b/eth/main.cpp @@ -77,7 +77,6 @@ void interactiveHelp() << " setetherprice

Resets the ether price." << endl << " setpriority

Resets the transaction priority." << endl << " minestart Starts mining." << endl - << " minestart Starts mining." << endl << " minestop Stops mining." << endl << " mineforce Forces mining, even when there are no transactions." << endl << " address Gives the current address." << endl @@ -88,12 +87,14 @@ void interactiveHelp() << " send Execute a given transaction with current secret." << endl << " contract Create a new contract with current secret." << endl << " peers List the peers that are connected" << endl +#if ETH_FATDB << " listaccounts List the accounts on the network." << endl << " listcontracts List the contracts on the network." << endl - << " setsecret Set the secret to the hex secret key." < Set the coinbase (mining payout) address." < Export the config (.RLP) to the path provided." < Import the config (.RLP) from the path provided." < Set the secret to the hex secret key." << endl + << " setaddress Set the coinbase (mining payout) address." << endl + << " exportconfig Export the config (.RLP) to the path provided." << endl + << " importconfig Import the config (.RLP) from the path provided." << endl << " inspect Dumps a contract to /.evm." << endl << " dumptrace Dumps a transaction trace" << endl << "to . should be one of pretty, standard, standard+." << endl << " dumpreceipt Dumps a transation receipt." << endl @@ -103,7 +104,7 @@ void interactiveHelp() void help() { cout - << "Usage eth [OPTIONS] " << endl + << "Usage eth [OPTIONS]" << endl << "Options:" << endl << " -a,--address Set the coinbase (mining payout) address to addr (default: auto)." << endl << " -b,--bootstrap Connect to the default Ethereum peerserver." << endl @@ -116,20 +117,22 @@ 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: 8080)." << 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 - << " -l,--listen Listen on the given port for incoming connected (default: 30303)." << endl - << " -L,--local-networking Use peers whose addresses are local." << 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 << " -m,--mining Enable mining, optionally for a specified number of blocks (Default: off)" << endl << " -n,--upnp Use upnp for NAT (default: on)." << endl << " -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 - << " -u,--public-ip Force public ip to given (default; auto)." << endl << " -v,--verbosity <0 - 9> Set the log verbosity from 0 to 9 (Default: 8)." << endl << " -x,--peers Attempt to connect to given number of peers (Default: 5)." << endl << " -V,--version Show the version and exit." << endl @@ -159,14 +162,14 @@ string credits(bool _interactive = false) void version() { cout << "eth version " << dev::Version << endl; - cout << "Network protocol version: " << dev::eth::c_protocolVersion << endl; + cout << "eth network protocol version: " << dev::eth::c_protocolVersion << endl; cout << "Client database version: " << dev::eth::c_databaseVersion << endl; cout << "Build: " << DEV_QUOTED(ETH_BUILD_PLATFORM) << "/" << DEV_QUOTED(ETH_BUILD_TYPE) << endl; exit(0); } 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; @@ -197,7 +200,9 @@ enum class NodeMode int main(int argc, char** argv) { + string listenIP; unsigned short listenPort = 30303; + string publicIP; string remoteHost; unsigned short remotePort = 30303; string dbPath; @@ -209,11 +214,10 @@ int main(int argc, char** argv) #if ETH_JSONRPC int jsonrpc = -1; #endif - string publicIP; bool bootstrap = false; bool upnp = true; - bool useLocal = false; bool forceMining = false; + WithExisting killChain = WithExisting::Trust; bool jit = false; bool structuredLogging = false; string structuredLoggingFormat = "%Y-%m-%dT%H:%M:%S"; @@ -247,7 +251,9 @@ int main(int argc, char** argv) for (int i = 1; i < argc; ++i) { string arg = argv[i]; - if ((arg == "-l" || arg == "--listen" || arg == "--listen-port") && i + 1 < argc) + if (arg == "--listen-ip" && i + 1 < argc) + listenIP = argv[++i]; + else if ((arg == "-l" || arg == "--listen" || arg == "--listen-port") && i + 1 < argc) listenPort = (short)atoi(argv[++i]); else if ((arg == "-u" || arg == "--public-ip" || arg == "--public") && i + 1 < argc) publicIP = argv[++i]; @@ -268,15 +274,16 @@ int main(int argc, char** argv) return -1; } } - else if (arg == "-L" || arg == "--local-networking") - useLocal = 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) - { try { - coinbase = h160(fromHex(argv[++i], ThrowType::Throw)); + coinbase = h160(fromHex(argv[++i], WhenError::Throw)); } catch (BadHexCharacter& _e) { @@ -289,7 +296,6 @@ int main(int argc, char** argv) cwarn << "coinbase rejected"; break; } - } else if ((arg == "-s" || arg == "--secret") && i + 1 < argc) us = KeyPair(h256(fromHex(argv[++i]))); else if (arg == "--structured-logging-format" && i + 1 < argc) @@ -300,20 +306,24 @@ int main(int argc, char** argv) dbPath = argv[++i]; else if ((arg == "-B" || arg == "--block-fees") && i + 1 < argc) { - try { + try + { blockFees = stof(argv[++i]); } - catch (...) { + catch (...) + { cerr << "Bad " << arg << " option: " << argv[++i] << endl; return -1; } } else if ((arg == "-e" || arg == "--ether-price") && i + 1 < argc) { - try { + try + { etherPrice = stof(argv[++i]); } - catch (...) { + catch (...) + { cerr << "Bad " << arg << " option: " << argv[++i] << endl; return -1; } @@ -364,7 +374,7 @@ int main(int argc, char** argv) interactive = true; #if ETH_JSONRPC else if ((arg == "-j" || arg == "--json-rpc")) - jsonrpc = jsonrpc == -1 ? 8080 : jsonrpc; + jsonrpc = jsonrpc == -1 ? SensibleHttpPort : jsonrpc; else if (arg == "--json-rpc-port" && i + 1 < argc) jsonrpc = atoi(argv[++i]); #endif @@ -401,7 +411,10 @@ int main(int argc, char** argv) else if (arg == "-V" || arg == "--version") version(); else - remoteHost = argv[i]; + { + cerr << "Invalid argument: " << arg << endl; + exit(-1); + } } if (!clientName.empty()) @@ -411,13 +424,13 @@ int main(int argc, char** argv) StructuredLogger::get().initialize(structuredLogging, structuredLoggingFormat); VMFactory::setKind(jit ? VMKind::JIT : VMKind::Interpreter); - NetworkPreferences netPrefs(listenPort, publicIP, upnp, useLocal); + auto netPrefs = publicIP.empty() ? NetworkPreferences(listenIP ,listenPort, upnp) : NetworkPreferences(publicIP, listenIP ,listenPort, upnp); auto nodesState = contents((dbPath.size() ? dbPath : getDataDir()) + "/network.rlp"); std::string clientImplString = "Ethereum(++)/" + clientName + "v" + dev::Version + "/" DEV_QUOTED(ETH_BUILD_TYPE) "/" DEV_QUOTED(ETH_BUILD_PLATFORM) + (jit ? "/JIT" : ""); dev::WebThreeDirect web3( clientImplString, dbPath, - false, + killChain, mode == NodeMode::Full ? set{"eth", "shh"} : set(), netPrefs, &nodesState, @@ -434,20 +447,21 @@ int main(int argc, char** argv) c->setAddress(coinbase); } - cout << "Address: " << endl << toHex(us.address().asArray()) << endl; + cout << "Transaction Signer: " << us.address() << endl; + cout << "Mining Benefactor: " << coinbase << endl; web3.startNetwork(); if (bootstrap) - web3.connect(Host::pocHost()); + web3.addNode(p2p::NodeId(), Host::pocHost()); if (remoteHost.size()) - web3.connect(remoteHost, remotePort); + web3.addNode(p2p::NodeId(), remoteHost + ":" + toString(remotePort)); #if ETH_JSONRPC shared_ptr jsonrpcServer; unique_ptr jsonrpcConnector; if (jsonrpc > -1) { - jsonrpcConnector = unique_ptr(new jsonrpc::HttpServer(jsonrpc)); + jsonrpcConnector = unique_ptr(new jsonrpc::HttpServer(jsonrpc, "", "", SensibleHttpThreads)); jsonrpcServer = shared_ptr(new WebThreeStubServer(*jsonrpcConnector.get(), web3, vector({us}))); jsonrpcServer->setIdentities({us}); jsonrpcServer->StartListening(); @@ -500,7 +514,7 @@ int main(int argc, char** argv) string addr; unsigned port; iss >> addr >> port; - web3.connect(addr, (short)port); + web3.addNode(p2p::NodeId(), addr + ":" + toString(port ? port : p2p::c_defaultIPPort)); } else if (cmd == "netstop") { @@ -572,8 +586,8 @@ int main(int argc, char** argv) else if (cmd == "jsonstart") { if (jsonrpc < 0) - jsonrpc = 8080; - jsonrpcConnector = unique_ptr(new jsonrpc::HttpServer(jsonrpc)); + jsonrpc = SensibleHttpPort; + jsonrpcConnector = unique_ptr(new jsonrpc::HttpServer(jsonrpc, "", "", SensibleHttpThreads)); jsonrpcServer = shared_ptr(new WebThreeStubServer(*jsonrpcConnector.get(), web3, vector({us}))); jsonrpcServer->setIdentities({us}); jsonrpcServer->StartListening(); @@ -659,7 +673,7 @@ int main(int argc, char** argv) { Secret secret = h256(fromHex(sechex)); Address dest = h160(fromHex(hexAddr)); - c->transact(secret, amount, dest, data, gas, gasPrice); + c->submitTransaction(secret, amount, dest, data, gas, gasPrice); } catch (BadHexCharacter& _e) { @@ -673,14 +687,15 @@ int main(int argc, char** argv) } } else - cwarn << "Require parameters: transact ADDRESS AMOUNT GASPRICE GAS SECRET DATA"; + cwarn << "Require parameters: submitTransaction ADDRESS AMOUNT GASPRICE GAS SECRET DATA"; } +#if ETH_FATDB else if (c && cmd == "listcontracts") { auto acs =c->addresses(); string ss; for (auto const& i: acs) - if ( c->codeAt(i, 0).size()) + if ( c->codeAt(i, PendingBlock).size()) { ss = toString(i) + " : " + toString( c->balanceAt(i)) + " [" + toString((unsigned) c->countAt(i)) + "]"; cout << ss << endl; @@ -691,12 +706,13 @@ int main(int argc, char** argv) auto acs =c->addresses(); string ss; for (auto const& i: acs) - if ( c->codeAt(i, 0).empty()) + if ( c->codeAt(i, PendingBlock).empty()) { ss = toString(i) + " : " + toString( c->balanceAt(i)) + " [" + toString((unsigned) c->countAt(i)) + "]"; cout << ss << endl; } } +#endif else if (c && cmd == "send") { if (iss.peek() != -1) @@ -720,8 +736,8 @@ int main(int argc, char** argv) u256 minGas = (u256)Client::txGas(bytes(), 0); try { - Address dest = h160(fromHex(hexAddr, ThrowType::Throw)); - c->transact(us.secret(), amount, dest, bytes(), minGas); + Address dest = h160(fromHex(hexAddr, WhenError::Throw)); + c->submitTransaction(us.secret(), amount, dest, bytes(), minGas); } catch (BadHexCharacter& _e) { @@ -764,7 +780,7 @@ int main(int argc, char** argv) stringstream ssc; try { - init = fromHex(sinit, ThrowType::Throw); + init = fromHex(sinit, WhenError::Throw); } catch (BadHexCharacter& _e) { @@ -790,7 +806,7 @@ int main(int argc, char** argv) else if (gas < minGas) cwarn << "Minimum gas amount is" << minGas; else - c->transact(us.secret(), endowment, init, gas, gasPrice); + c->submitTransaction(us.secret(), endowment, init, gas, gasPrice); } else cwarn << "Require parameters: contract ENDOWMENT GASPRICE GAS CODEHEX"; @@ -822,11 +838,8 @@ int main(int argc, char** argv) Executive e(state, c->blockChain(), 0); Transaction t = state.pending()[index]; state = state.fromPending(index); - bytes r = t.rlp(); try { - e.setup(&r); - OnOpFunc oof; if (format == "pretty") oof = [&](uint64_t steps, Instruction instr, bigint newMemSize, bigint gasCost, dev::eth::VM* vvm, dev::eth::ExtVMFace const* vextVM) @@ -859,7 +872,9 @@ int main(int argc, char** argv) f << toHex(dev::toCompactBigEndian(i.first, 1)) << " " << toHex(dev::toCompactBigEndian(i.second, 1)) << endl; f << ext->myAddress << " " << hex << toHex(dev::toCompactBigEndian(vm->curPC(), 1)) << " " << hex << toHex(dev::toCompactBigEndian((int)(byte)instr, 1)) << " " << hex << toHex(dev::toCompactBigEndian((uint64_t)vm->gas(), 1)) << endl; }; - e.go(oof); + e.initialize(t); + if (!e.execute()) + e.go(oof); e.finalize(); } catch(Exception const& _e) @@ -883,10 +898,10 @@ int main(int argc, char** argv) try { - auto storage =c->storageAt(h, 0); + auto storage =c->storageAt(h, PendingBlock); for (auto const& i: storage) s << "@" << showbase << hex << i.first << " " << showbase << hex << i.second << endl; - s << endl << disassemble( c->codeAt(h, 0)) << endl; + s << endl << disassemble( c->codeAt(h, PendingBlock)) << endl; string outFile = getDataDir() + "/" + rechex + ".evm"; ofstream ofs; @@ -925,7 +940,7 @@ int main(int argc, char** argv) { try { - coinbase = h160(fromHex(hexAddr, ThrowType::Throw)); + coinbase = h160(fromHex(hexAddr, WhenError::Throw)); } catch (BadHexCharacter& _e) { diff --git a/ethrpctest/CMakeLists.txt b/ethrpctest/CMakeLists.txt new file mode 100644 index 000000000..5d3fef542 --- /dev/null +++ b/ethrpctest/CMakeLists.txt @@ -0,0 +1,35 @@ +cmake_policy(SET CMP0015 NEW) +set(CMAKE_AUTOMOC OFF) + +aux_source_directory(. SRC_LIST) + +include_directories(BEFORE ${JSONCPP_INCLUDE_DIRS}) +include_directories(BEFORE ..) +include_directories(${Boost_INCLUDE_DIRS}) +include_directories(${JSON_RPC_CPP_INCLUDE_DIRS}) + +set(EXECUTABLE ethrpctest) + +file(GLOB HEADERS "*.h") + +add_executable(${EXECUTABLE} ${SRC_LIST} ${HEADERS}) + +add_dependencies(${EXECUTABLE} BuildInfo.h) + +target_link_libraries(${EXECUTABLE} ${Boost_REGEX_LIBRARIES}) + +if (READLINE_FOUND) + target_link_libraries(${EXECUTABLE} ${READLINE_LIBRARIES}) +endif() + +target_link_libraries(${EXECUTABLE} ${Boost_FILESYSTEM_LIBRARIES}) +target_link_libraries(${EXECUTABLE} ${Boost_PROGRAM_OPTIONS_LIBRARIES}) +target_link_libraries(${EXECUTABLE} testutils) +target_link_libraries(${EXECUTABLE} web3jsonrpc) + +if (DEFINED WIN32 AND NOT DEFINED CMAKE_COMPILER_IS_MINGW) + add_custom_command(TARGET ${EXECUTABLE} POST_BUILD COMMAND ${CMAKE_COMMAND} ARGS -E copy ${MHD_DLL_RELEASE} "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_CFG_INTDIR}") +endif() + +install( TARGETS ${EXECUTABLE} DESTINATION bin ) + diff --git a/ethrpctest/CommandLineInterface.cpp b/ethrpctest/CommandLineInterface.cpp new file mode 100644 index 000000000..c4b3d8f27 --- /dev/null +++ b/ethrpctest/CommandLineInterface.cpp @@ -0,0 +1,129 @@ +/* + 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 CommandLineInterface.cpp + * @author Marek Kotewicz + * @date 2015 + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "CommandLineInterface.h" +#include "BuildInfo.h" + +using namespace std; +using namespace dev; +using namespace dev::eth; +using namespace dev::test; +namespace po = boost::program_options; + +bool CommandLineInterface::parseArguments(int argc, char** argv) +{ + // Declare the supported options. + po::options_description desc("Allowed options"); + desc.add_options() + ("help", "Show help message and exit") + ("json", po::value>()->required(), "input file") + ("test", po::value>()->required(), "test case name"); + + // All positional options should be interpreted as input files + po::positional_options_description p; + + // parse the compiler arguments + try + { + po::store(po::command_line_parser(argc, argv).options(desc).positional(p).allow_unregistered().run(), m_args); + + if (m_args.count("help")) + { + cout << desc; + return false; + } + + po::notify(m_args); + } + catch (po::error const& _exception) + { + cout << _exception.what() << endl; + return false; + } + + return true; +} + +bool CommandLineInterface::processInput() +{ + string infile = m_args["json"].as>()[0]; + + auto path = boost::filesystem::path(infile); + if (!boost::filesystem::exists(path)) + { + cout << "Non existant input file \"" << infile << "\"" << endl; + return false; + } + + string test = m_args["test"].as>()[0]; + Json::Value j = dev::test::loadJsonFromFile(path.string()); + + if (j[test].empty()) + { + cout << "Non existant test case \"" << infile << "\"" << endl; + return false; + } + + if (!j[test].isObject()) + { + cout << "Incorrect JSON file \"" << infile << "\"" << endl; + return false; + } + + m_json = j[test]; + + return true; +} + +bool g_exit = false; + +void sighandler(int) +{ + g_exit = true; +} + +void CommandLineInterface::actOnInput() +{ + BlockChainLoader bcl(m_json); + FixedClient client(bcl.bc(), bcl.state()); + unique_ptr jsonrpcServer; + auto server = new jsonrpc::HttpServer(8080, "", "", 2); + jsonrpcServer.reset(new FixedWebThreeServer(*server, {}, &client)); + jsonrpcServer->StartListening(); + + signal(SIGABRT, &sighandler); + signal(SIGTERM, &sighandler); + signal(SIGINT, &sighandler); + + while (!g_exit) + this_thread::sleep_for(chrono::milliseconds(1000)); +} diff --git a/ethrpctest/CommandLineInterface.h b/ethrpctest/CommandLineInterface.h new file mode 100644 index 000000000..b57e1df5e --- /dev/null +++ b/ethrpctest/CommandLineInterface.h @@ -0,0 +1,46 @@ +/* + 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 . +*/ +/** CommandLineInterface.h + * @author Marek Kotewicz + * @date 2015 + */ +#pragma once + +#include +#include + +class CommandLineInterface +{ +public: + CommandLineInterface() {} + + /// Parse command line arguments and return false if we should not continue + bool parseArguments(int argc, char** argv); + /// Parse input file and check if test exists + bool processInput(); + /// Start FixedJsonRpcServer + void actOnInput(); + +private: + + /// Compiler arguments variable map + boost::program_options::variables_map m_args; + + /// loaded json test case + Json::Value m_json; +}; + diff --git a/ethrpctest/main.cpp b/ethrpctest/main.cpp new file mode 100644 index 000000000..3d710dd5b --- /dev/null +++ b/ethrpctest/main.cpp @@ -0,0 +1,34 @@ +/* + 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 . +*/ +/** main.cpp + * @author Marek Kotewicz + * @date 2015 + */ + +#include "CommandLineInterface.h" + +int main(int argc, char** argv) +{ + CommandLineInterface cli; + if (!cli.parseArguments(argc, argv)) + return 1; + if (!cli.processInput()) + return 1; + cli.actOnInput(); + + return 0; +} diff --git a/evmjit/CMakeLists.txt b/evmjit/CMakeLists.txt index 14fca2cde..5ab394a80 100644 --- a/evmjit/CMakeLists.txt +++ b/evmjit/CMakeLists.txt @@ -7,7 +7,7 @@ set(CMAKE_AUTOMOC OFF) if(${CMAKE_CXX_COMPILER_ID} STREQUAL "MSVC") else() - set(CMAKE_CXX_FLAGS "-std=c++11 -Wall -Wextra -Wconversion -Wno-unknown-pragmas") + set(CMAKE_CXX_FLAGS "-std=c++11 -Wall -Wextra -Wconversion -Wno-sign-conversion -Wno-unknown-pragmas") endif() if(${CMAKE_SYSTEM_NAME} STREQUAL "Linux") @@ -22,7 +22,7 @@ if(LLVM_DIR OR APPLE) # local LLVM build message(STATUS "Using LLVMConfig.cmake in: ${LLVM_DIR}") add_definitions(${LLVM_DEFINITIONS}) # TODO: bitwriter is needed only for evmcc - llvm_map_components_to_libnames(LLVM_LIBS core support mcjit x86asmparser x86codegen bitwriter) + llvm_map_components_to_libnames(LLVM_LIBS core support mcjit x86asmparser x86codegen bitwriter ipo) else() # Workaround for Ubuntu broken LLVM package message(STATUS "Using llvm-3.5-dev package from Ubuntu. If does not work, build LLVM and set -DLLVM_DIR=llvm-build/share/llvm/cmake") diff --git a/evmjit/libevmjit-cpp/CMakeLists.txt b/evmjit/libevmjit-cpp/CMakeLists.txt index 53448332b..add132f2a 100644 --- a/evmjit/libevmjit-cpp/CMakeLists.txt +++ b/evmjit/libevmjit-cpp/CMakeLists.txt @@ -4,8 +4,9 @@ set(TARGET_NAME evmjit-cpp) find_package(Boost REQUIRED) set(SOURCES - Env.cpp + Env.cpp JitVM.cpp JitVM.h + Utils.h ) source_group("" FILES ${SOURCES}) diff --git a/evmjit/libevmjit-cpp/Env.cpp b/evmjit/libevmjit-cpp/Env.cpp index 874993c84..b89aca726 100644 --- a/evmjit/libevmjit-cpp/Env.cpp +++ b/evmjit/libevmjit-cpp/Env.cpp @@ -52,7 +52,6 @@ extern "C" auto endowment = llvm2eth(*_endowment); if (_env->balance(_env->myAddress) >= endowment && _env->depth < 1024) { - _env->subBalance(endowment); u256 gas = *io_gas; h256 address(_env->create(endowment, gas, {_initBeg, _initSize}, {}), h256::AlignRight); *io_gas = static_cast(gas); @@ -62,23 +61,37 @@ extern "C" *o_address = {}; } - EXPORT bool env_call(ExtVMFace* _env, int64_t* io_gas, h256* _receiveAddress, i256* _value, byte* _inBeg, uint64_t _inSize, byte* _outBeg, uint64_t _outSize, h256* _codeAddress) + EXPORT bool env_call(ExtVMFace* _env, int64_t* io_gas, int64_t _callGas, h256* _receiveAddress, i256* _value, byte* _inBeg, uint64_t _inSize, byte* _outBeg, uint64_t _outSize, h256* _codeAddress) { auto value = llvm2eth(*_value); - if (_env->balance(_env->myAddress) >= value && _env->depth < 1024) + auto receiveAddress = right160(*_receiveAddress); + auto codeAddress = right160(*_codeAddress); + const auto isCall = receiveAddress == codeAddress; // OPT: The same address pointer can be used if not CODECALL + + *io_gas -= _callGas; + if (*io_gas < 0) + return false; + + if (isCall && !_env->exists(receiveAddress)) + *io_gas -= static_cast(c_callNewAccountGas); // no underflow, *io_gas non-negative before + + if (value > 0) // value transfer { - _env->subBalance(value); - auto receiveAddress = right160(*_receiveAddress); - auto inRef = bytesConstRef{_inBeg, _inSize}; - auto outRef = bytesRef{_outBeg, _outSize}; - auto codeAddress = right160(*_codeAddress); - u256 gas = *io_gas; - auto ret = _env->call(receiveAddress, value, inRef, gas, outRef, {}, {}, codeAddress); - *io_gas = static_cast(gas); - return ret; + /*static*/ assert(c_callValueTransferGas > c_callStipend && "Overflow possible"); + *io_gas -= static_cast(c_callValueTransferGas); // no underflow + _callGas += static_cast(c_callStipend); // overflow possibility, but in the same time *io_gas < 0 } - return false; + if (*io_gas < 0) + return false; + + auto ret = false; + auto callGas = u256{_callGas}; + if (_env->balance(_env->myAddress) >= value && _env->depth < 1024) + ret = _env->call(receiveAddress, value, {_inBeg, _inSize}, callGas, {_outBeg, _outSize}, {}, {}, codeAddress); + + *io_gas += static_cast(callGas); // it is never more than initial _callGas + return ret; } EXPORT void env_sha3(byte* _begin, uint64_t _size, h256* o_hash) diff --git a/evmjit/libevmjit-cpp/JitVM.cpp b/evmjit/libevmjit-cpp/JitVM.cpp index e28fcd39f..84cc41dd1 100644 --- a/evmjit/libevmjit-cpp/JitVM.cpp +++ b/evmjit/libevmjit-cpp/JitVM.cpp @@ -69,8 +69,8 @@ bytesConstRef JitVM::go(ExtVMFace& _ext, OnOpFunc const& _onOp, uint64_t _step) BOOST_THROW_EXCEPTION(BadJumpDestination()); case ReturnCode::OutOfGas: BOOST_THROW_EXCEPTION(OutOfGas()); - case ReturnCode::StackTooSmall: - BOOST_THROW_EXCEPTION(StackTooSmall()); + case ReturnCode::StackUnderflow: + BOOST_THROW_EXCEPTION(StackUnderflow()); case ReturnCode::BadInstruction: BOOST_THROW_EXCEPTION(BadInstruction()); case ReturnCode::LinkerWorkaround: // never happens diff --git a/evmjit/libevmjit/Arith256.cpp b/evmjit/libevmjit/Arith256.cpp index ddf06b463..e88fda432 100644 --- a/evmjit/libevmjit/Arith256.cpp +++ b/evmjit/libevmjit/Arith256.cpp @@ -9,6 +9,7 @@ #include "Type.h" #include "Endianness.h" +#include "Utils.h" namespace dev { @@ -38,6 +39,8 @@ llvm::Function* Arith256::getMulFunc() { llvm::Type* argTypes[] = {Type::Word, Type::Word}; func = llvm::Function::Create(llvm::FunctionType::get(Type::Word, argTypes, false), llvm::Function::PrivateLinkage, "mul", getModule()); + func->setDoesNotThrow(); + func->setDoesNotAccessMemory(); auto x = &func->getArgumentList().front(); x->setName("x"); @@ -50,12 +53,16 @@ llvm::Function* Arith256::getMulFunc() auto i64 = Type::Size; auto i128 = m_builder.getIntNTy(128); auto i256 = Type::Word; + auto c64 = Constant::get(64); + auto c128 = Constant::get(128); + auto c192 = Constant::get(192); + auto x_lo = m_builder.CreateTrunc(x, i64, "x.lo"); auto y_lo = m_builder.CreateTrunc(y, i64, "y.lo"); - auto x_mi = m_builder.CreateTrunc(m_builder.CreateLShr(x, Constant::get(64)), i64); - auto y_mi = m_builder.CreateTrunc(m_builder.CreateLShr(y, Constant::get(64)), i64); - auto x_hi = m_builder.CreateTrunc(m_builder.CreateLShr(x, Constant::get(128)), i128); - auto y_hi = m_builder.CreateTrunc(m_builder.CreateLShr(y, Constant::get(128)), i128); + auto x_mi = m_builder.CreateTrunc(m_builder.CreateLShr(x, c64), i64); + auto y_mi = m_builder.CreateTrunc(m_builder.CreateLShr(y, c64), i64); + auto x_hi = m_builder.CreateTrunc(m_builder.CreateLShr(x, c128), i128); + auto y_hi = m_builder.CreateTrunc(m_builder.CreateLShr(y, c128), i128); auto t1 = m_builder.CreateMul(m_builder.CreateZExt(x_lo, i128), m_builder.CreateZExt(y_lo, i128)); auto t2 = m_builder.CreateMul(m_builder.CreateZExt(x_lo, i128), m_builder.CreateZExt(y_mi, i128)); @@ -67,13 +74,13 @@ llvm::Function* Arith256::getMulFunc() auto t8 = m_builder.CreateMul(x_hi, m_builder.CreateZExt(y_mi, i128)); auto p = m_builder.CreateZExt(t1, i256); - p = m_builder.CreateAdd(p, m_builder.CreateShl(m_builder.CreateZExt(t2, i256), Constant::get(64))); - p = m_builder.CreateAdd(p, m_builder.CreateShl(m_builder.CreateZExt(t3, i256), Constant::get(128))); - p = m_builder.CreateAdd(p, m_builder.CreateShl(m_builder.CreateZExt(t4, i256), Constant::get(64))); - p = m_builder.CreateAdd(p, m_builder.CreateShl(m_builder.CreateZExt(t5, i256), Constant::get(128))); - p = m_builder.CreateAdd(p, m_builder.CreateShl(m_builder.CreateZExt(t6, i256), Constant::get(192))); - p = m_builder.CreateAdd(p, m_builder.CreateShl(m_builder.CreateZExt(t7, i256), Constant::get(128))); - p = m_builder.CreateAdd(p, m_builder.CreateShl(m_builder.CreateZExt(t8, i256), Constant::get(192))); + p = m_builder.CreateAdd(p, m_builder.CreateShl(m_builder.CreateZExt(t2, i256), c64)); + p = m_builder.CreateAdd(p, m_builder.CreateShl(m_builder.CreateZExt(t3, i256), c128)); + p = m_builder.CreateAdd(p, m_builder.CreateShl(m_builder.CreateZExt(t4, i256), c64)); + p = m_builder.CreateAdd(p, m_builder.CreateShl(m_builder.CreateZExt(t5, i256), c128)); + p = m_builder.CreateAdd(p, m_builder.CreateShl(m_builder.CreateZExt(t6, i256), c192)); + p = m_builder.CreateAdd(p, m_builder.CreateShl(m_builder.CreateZExt(t7, i256), c128)); + p = m_builder.CreateAdd(p, m_builder.CreateShl(m_builder.CreateZExt(t8, i256), c192)); m_builder.CreateRet(p); } return func; @@ -87,6 +94,8 @@ llvm::Function* Arith256::getMul512Func() auto i512 = m_builder.getIntNTy(512); llvm::Type* argTypes[] = {Type::Word, Type::Word}; func = llvm::Function::Create(llvm::FunctionType::get(i512, argTypes, false), llvm::Function::PrivateLinkage, "mul512", getModule()); + func->setDoesNotThrow(); + func->setDoesNotAccessMemory(); auto x = &func->getArgumentList().front(); x->setName("x"); @@ -130,6 +139,8 @@ llvm::Function* Arith256::getDivFunc(llvm::Type* _type) auto retType = llvm::StructType::get(m_builder.getContext(), llvm::ArrayRef{argTypes}); auto funcName = _type == Type::Word ? "div" : "div512"; func = llvm::Function::Create(llvm::FunctionType::get(retType, argTypes, false), llvm::Function::PrivateLinkage, funcName, getModule()); + func->setDoesNotThrow(); + func->setDoesNotAccessMemory(); auto zero = llvm::ConstantInt::get(_type, 0); auto one = llvm::ConstantInt::get(_type, 1); @@ -221,6 +232,8 @@ llvm::Function* Arith256::getExpFunc() { llvm::Type* argTypes[] = {Type::Word, Type::Word}; m_exp = llvm::Function::Create(llvm::FunctionType::get(Type::Word, argTypes, false), llvm::Function::PrivateLinkage, "exp", getModule()); + m_exp->setDoesNotThrow(); + m_exp->setDoesNotAccessMemory(); auto base = &m_exp->getArgumentList().front(); base->setName("base"); @@ -289,6 +302,8 @@ llvm::Function* Arith256::getAddModFunc() auto i512Ty = m_builder.getIntNTy(512); llvm::Type* argTypes[] = {Type::Word, Type::Word, Type::Word}; m_addmod = llvm::Function::Create(llvm::FunctionType::get(Type::Word, argTypes, false), llvm::Function::PrivateLinkage, "addmod", getModule()); + m_addmod->setDoesNotThrow(); + m_addmod->setDoesNotAccessMemory(); auto x = &m_addmod->getArgumentList().front(); x->setName("x"); @@ -318,6 +333,8 @@ llvm::Function* Arith256::getMulModFunc() { llvm::Type* argTypes[] = {Type::Word, Type::Word, Type::Word}; m_mulmod = llvm::Function::Create(llvm::FunctionType::get(Type::Word, argTypes, false), llvm::Function::PrivateLinkage, "mulmod", getModule()); + m_mulmod->setDoesNotThrow(); + m_mulmod->setDoesNotAccessMemory(); auto i512Ty = m_builder.getIntNTy(512); auto x = &m_mulmod->getArgumentList().front(); @@ -343,18 +360,51 @@ llvm::Function* Arith256::getMulModFunc() llvm::Value* Arith256::mul(llvm::Value* _arg1, llvm::Value* _arg2) { + if (auto c1 = llvm::dyn_cast(_arg1)) + { + if (auto c2 = llvm::dyn_cast(_arg2)) + return Constant::get(c1->getValue() * c2->getValue()); + } + return createCall(getMulFunc(), {_arg1, _arg2}); } std::pair Arith256::div(llvm::Value* _arg1, llvm::Value* _arg2) { - auto div = m_builder.CreateExtractValue(createCall(getDivFunc(Type::Word), {_arg1, _arg2}), 0, "div"); - auto mod = m_builder.CreateExtractValue(createCall(getDivFunc(Type::Word), {_arg1, _arg2}), 1, "mod"); + // FIXME: Disabled because of llvm::APInt::urem bug +// if (auto c1 = llvm::dyn_cast(_arg1)) +// { +// if (auto c2 = llvm::dyn_cast(_arg2)) +// { +// if (!c2->getValue()) +// return std::make_pair(Constant::get(0), Constant::get(0)); +// auto div = Constant::get(c1->getValue().udiv(c2->getValue())); +// auto mod = Constant::get(c1->getValue().urem(c2->getValue())); +// return std::make_pair(div, mod); +// } +// } + + auto r = createCall(getDivFunc(Type::Word), {_arg1, _arg2}); + auto div = m_builder.CreateExtractValue(r, 0, "div"); + auto mod = m_builder.CreateExtractValue(r, 1, "mod"); return std::make_pair(div, mod); } std::pair Arith256::sdiv(llvm::Value* _x, llvm::Value* _y) { + // FIXME: Disabled because of llvm::APInt::urem bug +// if (auto c1 = llvm::dyn_cast(_x)) +// { +// if (auto c2 = llvm::dyn_cast(_y)) +// { +// if (!c2->getValue()) +// return std::make_pair(Constant::get(0), Constant::get(0)); +// auto div = Constant::get(c1->getValue().sdiv(c2->getValue())); +// auto mod = Constant::get(c1->getValue().srem(c2->getValue())); +// return std::make_pair(div, mod); +// } +// } + auto xIsNeg = m_builder.CreateICmpSLT(_x, Constant::get(0)); auto xNeg = m_builder.CreateSub(Constant::get(0), _x); auto xAbs = m_builder.CreateSelect(xIsNeg, xNeg, _x); @@ -378,16 +428,73 @@ std::pair Arith256::sdiv(llvm::Value* _x, llvm::Valu llvm::Value* Arith256::exp(llvm::Value* _arg1, llvm::Value* _arg2) { + // while (e != 0) { + // if (e % 2 == 1) + // r *= b; + // b *= b; + // e /= 2; + // } + + if (auto c1 = llvm::dyn_cast(_arg1)) + { + if (auto c2 = llvm::dyn_cast(_arg2)) + { + auto b = c1->getValue(); + auto e = c2->getValue(); + auto r = llvm::APInt{256, 1}; + while (e != 0) + { + if (e[0]) + r *= b; + b *= b; + e = e.lshr(1); + } + return Constant::get(r); + } + } + return createCall(getExpFunc(), {_arg1, _arg2}); } llvm::Value* Arith256::addmod(llvm::Value* _arg1, llvm::Value* _arg2, llvm::Value* _arg3) { + // FIXME: Disabled because of llvm::APInt::urem bug +// if (auto c1 = llvm::dyn_cast(_arg1)) +// { +// if (auto c2 = llvm::dyn_cast(_arg2)) +// { +// if (auto c3 = llvm::dyn_cast(_arg3)) +// { +// if (!c3->getValue()) +// return Constant::get(0); +// auto s = c1->getValue().zext(256+64) + c2->getValue().zext(256+64); +// auto r = s.urem(c3->getValue().zext(256+64)).trunc(256); +// return Constant::get(r); +// } +// } +// } + return createCall(getAddModFunc(), {_arg1, _arg2, _arg3}); } llvm::Value* Arith256::mulmod(llvm::Value* _arg1, llvm::Value* _arg2, llvm::Value* _arg3) { + // FIXME: Disabled because of llvm::APInt::urem bug +// if (auto c1 = llvm::dyn_cast(_arg1)) +// { +// if (auto c2 = llvm::dyn_cast(_arg2)) +// { +// if (auto c3 = llvm::dyn_cast(_arg3)) +// { +// if (!c3->getValue()) +// return Constant::get(0); +// auto p = c1->getValue().zext(512) * c2->getValue().zext(512); +// auto r = p.urem(c3->getValue().zext(512)).trunc(256); +// return Constant::get(r); +// } +// } +// } + return createCall(getMulModFunc(), {_arg1, _arg2, _arg3}); } @@ -400,7 +507,7 @@ extern "C" { EXPORT void debug(uint64_t a, uint64_t b, uint64_t c, uint64_t d, char z) { - std::cerr << "DEBUG " << std::dec << z << ": " //<< d << c << b << a + DLOG(JIT) << "DEBUG " << std::dec << z << ": " //<< d << c << b << a << " [" << std::hex << std::setfill('0') << std::setw(16) << d << std::setw(16) << c << std::setw(16) << b << std::setw(16) << a << "]\n"; } } diff --git a/evmjit/libevmjit/Array.cpp b/evmjit/libevmjit/Array.cpp new file mode 100644 index 000000000..3266038db --- /dev/null +++ b/evmjit/libevmjit/Array.cpp @@ -0,0 +1,320 @@ +#include "Array.h" + +#include "preprocessor/llvm_includes_start.h" +#include +#include +#include "preprocessor/llvm_includes_end.h" + +#include "RuntimeManager.h" +#include "Runtime.h" +#include "Utils.h" + +#include // DEBUG only + +namespace dev +{ +namespace eth +{ +namespace jit +{ + +static const auto c_reallocStep = 1; +static const auto c_reallocMultipier = 2; + +llvm::Value* LazyFunction::call(llvm::IRBuilder<>& _builder, std::initializer_list const& _args, llvm::Twine const& _name) +{ + if (!m_func) + m_func = m_creator(); + + return _builder.CreateCall(m_func, {_args.begin(), _args.size()}, _name); +} + +llvm::Function* Array::createArrayPushFunc() +{ + llvm::Type* argTypes[] = {m_array->getType(), Type::Word}; + auto func = llvm::Function::Create(llvm::FunctionType::get(Type::Void, argTypes, false), llvm::Function::PrivateLinkage, "array.push", getModule()); + func->setDoesNotThrow(); + func->setDoesNotCapture(1); + + auto arrayPtr = &func->getArgumentList().front(); + arrayPtr->setName("arrayPtr"); + auto value = arrayPtr->getNextNode(); + value->setName("value"); + + InsertPointGuard guard{m_builder}; + auto entryBB = llvm::BasicBlock::Create(m_builder.getContext(), "Entry", func); + auto reallocBB = llvm::BasicBlock::Create(m_builder.getContext(), "Realloc", func); + auto pushBB = llvm::BasicBlock::Create(m_builder.getContext(), "Push", func); + + m_builder.SetInsertPoint(entryBB); + auto dataPtr = m_builder.CreateStructGEP(arrayPtr, 0, "dataPtr"); + auto sizePtr = m_builder.CreateStructGEP(arrayPtr, 1, "sizePtr"); + auto capPtr = m_builder.CreateStructGEP(arrayPtr, 2, "capPtr"); + auto data = m_builder.CreateLoad(dataPtr, "data"); + auto size = m_builder.CreateLoad(sizePtr, "size"); + auto cap = m_builder.CreateLoad(capPtr, "cap"); + auto reallocReq = m_builder.CreateICmpEQ(cap, size, "reallocReq"); + m_builder.CreateCondBr(reallocReq, reallocBB, pushBB); + + m_builder.SetInsertPoint(reallocBB); + auto newCap = m_builder.CreateNUWAdd(cap, m_builder.getInt64(c_reallocStep), "newCap"); + //newCap = m_builder.CreateNUWMul(newCap, m_builder.getInt64(c_reallocMultipier)); + auto reallocSize = m_builder.CreateShl(newCap, 5, "reallocSize"); // size in bytes: newCap * 32 + auto bytes = m_builder.CreateBitCast(data, Type::BytePtr, "bytes"); + auto newBytes = m_reallocFunc.call(m_builder, {bytes, reallocSize}, "newBytes"); + auto newData = m_builder.CreateBitCast(newBytes, Type::WordPtr, "newData"); + m_builder.CreateStore(newData, dataPtr); + m_builder.CreateStore(newCap, capPtr); + m_builder.CreateBr(pushBB); + + m_builder.SetInsertPoint(pushBB); + auto dataPhi = m_builder.CreatePHI(Type::WordPtr, 2, "dataPhi"); + dataPhi->addIncoming(data, entryBB); + dataPhi->addIncoming(newData, reallocBB); + auto newElemPtr = m_builder.CreateGEP(dataPhi, size, "newElemPtr"); + m_builder.CreateStore(value, newElemPtr); + auto newSize = m_builder.CreateNUWAdd(size, m_builder.getInt64(1), "newSize"); + m_builder.CreateStore(newSize, sizePtr); + m_builder.CreateRetVoid(); + + return func; +} + +llvm::Function* Array::createArraySetFunc() +{ + llvm::Type* argTypes[] = {m_array->getType(), Type::Size, Type::Word}; + auto func = llvm::Function::Create(llvm::FunctionType::get(Type::Void, argTypes, false), llvm::Function::PrivateLinkage, "array.set", getModule()); + func->setDoesNotThrow(); + func->setDoesNotCapture(1); + + auto arrayPtr = &func->getArgumentList().front(); + arrayPtr->setName("arrayPtr"); + auto index = arrayPtr->getNextNode(); + index->setName("index"); + auto value = index->getNextNode(); + value->setName("value"); + + InsertPointGuard guard{m_builder}; + m_builder.SetInsertPoint(llvm::BasicBlock::Create(m_builder.getContext(), {}, func)); + auto dataPtr = m_builder.CreateStructGEP(arrayPtr, 0, "dataPtr"); + auto data = m_builder.CreateLoad(dataPtr, "data"); + auto valuePtr = m_builder.CreateGEP(data, index, "valuePtr"); + m_builder.CreateStore(value, valuePtr); + m_builder.CreateRetVoid(); + return func; +} + +llvm::Function* Array::createArrayGetFunc() +{ + llvm::Type* argTypes[] = {m_array->getType(), Type::Size}; + auto func = llvm::Function::Create(llvm::FunctionType::get(Type::Word, argTypes, false), llvm::Function::PrivateLinkage, "array.get", getModule()); + func->setDoesNotThrow(); + func->setDoesNotCapture(1); + + auto arrayPtr = &func->getArgumentList().front(); + arrayPtr->setName("arrayPtr"); + auto index = arrayPtr->getNextNode(); + index->setName("index"); + + InsertPointGuard guard{m_builder}; + m_builder.SetInsertPoint(llvm::BasicBlock::Create(m_builder.getContext(), {}, func)); + auto dataPtr = m_builder.CreateStructGEP(arrayPtr, 0, "dataPtr"); + auto data = m_builder.CreateLoad(dataPtr, "data"); + auto valuePtr = m_builder.CreateGEP(data, index, "valuePtr"); + auto value = m_builder.CreateLoad(valuePtr, "value"); + m_builder.CreateRet(value); + return func; +} + +llvm::Function* Array::createGetPtrFunc() +{ + llvm::Type* argTypes[] = {m_array->getType(), Type::Size}; + auto func = llvm::Function::Create(llvm::FunctionType::get(Type::WordPtr, argTypes, false), llvm::Function::PrivateLinkage, "array.getPtr", getModule()); + func->setDoesNotThrow(); + func->setDoesNotCapture(1); + + auto arrayPtr = &func->getArgumentList().front(); + arrayPtr->setName("arrayPtr"); + auto index = arrayPtr->getNextNode(); + index->setName("index"); + + InsertPointGuard guard{m_builder}; + m_builder.SetInsertPoint(llvm::BasicBlock::Create(m_builder.getContext(), {}, func)); + auto dataPtr = m_builder.CreateBitCast(arrayPtr, Type::BytePtr->getPointerTo(), "dataPtr"); + auto data = m_builder.CreateLoad(dataPtr, "data"); + auto bytePtr = m_builder.CreateGEP(data, index, "bytePtr"); + auto wordPtr = m_builder.CreateBitCast(bytePtr, Type::WordPtr, "wordPtr"); + m_builder.CreateRet(wordPtr); + return func; +} + +llvm::Function* Array::createFreeFunc() +{ + auto func = llvm::Function::Create(llvm::FunctionType::get(Type::Void, m_array->getType(), false), llvm::Function::PrivateLinkage, "array.free", getModule()); + func->setDoesNotThrow(); + func->setDoesNotCapture(1); + + auto freeFunc = llvm::Function::Create(llvm::FunctionType::get(Type::Void, Type::BytePtr, false), llvm::Function::ExternalLinkage, "ext_free", getModule()); + freeFunc->setDoesNotThrow(); + freeFunc->setDoesNotCapture(1); + + auto arrayPtr = &func->getArgumentList().front(); + arrayPtr->setName("arrayPtr"); + + InsertPointGuard guard{m_builder}; + m_builder.SetInsertPoint(llvm::BasicBlock::Create(m_builder.getContext(), {}, func)); + auto dataPtr = m_builder.CreateStructGEP(arrayPtr, 0, "dataPtr"); + auto data = m_builder.CreateLoad(dataPtr, "data"); + auto mem = m_builder.CreateBitCast(data, Type::BytePtr, "mem"); + m_builder.CreateCall(freeFunc, mem); + m_builder.CreateRetVoid(); + return func; +} + +llvm::Function* Array::getReallocFunc() +{ + if (auto func = getModule()->getFunction("ext_realloc")) + return func; + + llvm::Type* reallocArgTypes[] = {Type::BytePtr, Type::Size}; + auto reallocFunc = llvm::Function::Create(llvm::FunctionType::get(Type::BytePtr, reallocArgTypes, false), llvm::Function::ExternalLinkage, "ext_realloc", getModule()); + reallocFunc->setDoesNotThrow(); + reallocFunc->setDoesNotAlias(0); + reallocFunc->setDoesNotCapture(1); + return reallocFunc; +} + +llvm::Function* Array::createExtendFunc() +{ + llvm::Type* argTypes[] = {m_array->getType(), Type::Size}; + auto func = llvm::Function::Create(llvm::FunctionType::get(Type::Void, argTypes, false), llvm::Function::PrivateLinkage, "array.extend", getModule()); + func->setDoesNotThrow(); + func->setDoesNotCapture(1); + + auto arrayPtr = &func->getArgumentList().front(); + arrayPtr->setName("arrayPtr"); + auto newSize = arrayPtr->getNextNode(); + newSize->setName("newSize"); + + InsertPointGuard guard{m_builder}; + m_builder.SetInsertPoint(llvm::BasicBlock::Create(m_builder.getContext(), {}, func)); + auto dataPtr = m_builder.CreateBitCast(arrayPtr, Type::BytePtr->getPointerTo(), "dataPtr");// TODO: Use byte* in Array + auto sizePtr = m_builder.CreateStructGEP(arrayPtr, 1, "sizePtr"); + auto capPtr = m_builder.CreateStructGEP(arrayPtr, 2, "capPtr"); + auto data = m_builder.CreateLoad(dataPtr, "data"); + auto size = m_builder.CreateLoad(sizePtr, "size"); + auto extSize = m_builder.CreateNUWSub(newSize, size, "extSize"); + auto newData = m_reallocFunc.call(m_builder, {data, newSize}, "newData"); // TODO: Check realloc result for null + auto extPtr = m_builder.CreateGEP(newData, size, "extPtr"); + m_builder.CreateMemSet(extPtr, m_builder.getInt8(0), extSize, 16); + m_builder.CreateStore(newData, dataPtr); + m_builder.CreateStore(newSize, sizePtr); + m_builder.CreateStore(newSize, capPtr); + m_builder.CreateRetVoid(); + return func; +} + +llvm::Type* Array::getType() +{ + llvm::Type* elementTys[] = {Type::WordPtr, Type::Size, Type::Size}; + static auto arrayTy = llvm::StructType::create(elementTys, "Array"); + return arrayTy; +} + +Array::Array(llvm::IRBuilder<>& _builder, char const* _name) : + CompilerHelper(_builder), + m_pushFunc([this](){ return createArrayPushFunc(); }), + m_setFunc([this](){ return createArraySetFunc(); }), + m_getFunc([this](){ return createArrayGetFunc(); }), + m_freeFunc([this](){ return createFreeFunc(); }) +{ + m_array = m_builder.CreateAlloca(getType(), nullptr, _name); + m_builder.CreateStore(llvm::ConstantAggregateZero::get(getType()), m_array); +} + +Array::Array(llvm::IRBuilder<>& _builder, llvm::Value* _array) : + CompilerHelper(_builder), + m_array(_array), + m_pushFunc([this](){ return createArrayPushFunc(); }), + m_setFunc([this](){ return createArraySetFunc(); }), + m_getFunc([this](){ return createArrayGetFunc(); }), + m_freeFunc([this](){ return createFreeFunc(); }) +{ + m_builder.CreateStore(llvm::ConstantAggregateZero::get(getType()), m_array); +} + + +void Array::pop(llvm::Value* _count) +{ + auto sizePtr = m_builder.CreateStructGEP(m_array, 1, "sizePtr"); + auto size = m_builder.CreateLoad(sizePtr, "size"); + auto newSize = m_builder.CreateNUWSub(size, _count, "newSize"); + m_builder.CreateStore(newSize, sizePtr); +} + +llvm::Value* Array::size(llvm::Value* _array) +{ + auto sizePtr = m_builder.CreateStructGEP(_array ? _array : m_array, 1, "sizePtr"); + return m_builder.CreateLoad(sizePtr, "array.size"); +} + +void Array::extend(llvm::Value* _arrayPtr, llvm::Value* _size) +{ + assert(_arrayPtr->getType() == m_array->getType()); + assert(_size->getType() == Type::Size); + m_extendFunc.call(m_builder, {_arrayPtr, _size}); +} + +} +} +} + +namespace +{ + struct AllocatedMemoryWatchdog + { + std::set allocatedMemory; + + ~AllocatedMemoryWatchdog() + { + if (!allocatedMemory.empty()) + { + DLOG(mem) << allocatedMemory.size() << " MEM LEAKS!\n"; + for (auto&& leak : allocatedMemory) + DLOG(mem) << "\t" << leak << "\n"; + } + } + }; + + AllocatedMemoryWatchdog watchdog; +} + +extern "C" +{ + using namespace dev::eth::jit; + + EXPORT void* ext_realloc(void* _data, size_t _size) noexcept + { + //std::cerr << "REALLOC: " << _data << " [" << _size << "]" << std::endl; + auto newData = std::realloc(_data, _size); + if (_data != newData) + { + DLOG(mem) << "REALLOC: " << newData << " <- " << _data << " [" << _size << "]\n"; + watchdog.allocatedMemory.erase(_data); + watchdog.allocatedMemory.insert(newData); + } + return newData; + } + + EXPORT void ext_free(void* _data) noexcept + { + std::free(_data); + if (_data) + { + DLOG(mem) << "FREE : " << _data << "\n"; + watchdog.allocatedMemory.erase(_data); + } + } + +} // extern "C" + diff --git a/evmjit/libevmjit/Array.h b/evmjit/libevmjit/Array.h new file mode 100644 index 000000000..41842f0c9 --- /dev/null +++ b/evmjit/libevmjit/Array.h @@ -0,0 +1,74 @@ +#pragma once + +#include + +#include "CompilerHelper.h" + +namespace dev +{ +namespace eth +{ +namespace jit +{ + +class LazyFunction +{ +public: + using Creator = std::function; + + LazyFunction(Creator _creator) : + m_creator(_creator) + {} + + llvm::Value* call(llvm::IRBuilder<>& _builder, std::initializer_list const& _args, llvm::Twine const& _name = ""); + +private: + llvm::Function* m_func = nullptr; + Creator m_creator; +}; + +class Array : public CompilerHelper +{ +public: + Array(llvm::IRBuilder<>& _builder, char const* _name); + Array(llvm::IRBuilder<>& _builder, llvm::Value* _array); + + void push(llvm::Value* _value) { m_pushFunc.call(m_builder, {m_array, _value}); } + void set(llvm::Value* _index, llvm::Value* _value) { m_setFunc.call(m_builder, {m_array, _index, _value}); } + llvm::Value* get(llvm::Value* _index) { return m_getFunc.call(m_builder, {m_array, _index}); } + void pop(llvm::Value* _count); + llvm::Value* size(llvm::Value* _array = nullptr); + void free() { m_freeFunc.call(m_builder, {m_array}); } + + void extend(llvm::Value* _arrayPtr, llvm::Value* _size); + llvm::Value* getPtr(llvm::Value* _arrayPtr, llvm::Value* _index) { return m_getPtrFunc.call(m_builder, {_arrayPtr, _index}); } + + llvm::Value* getPointerTo() const { return m_array; } + + static llvm::Type* getType(); + +private: + llvm::Value* m_array = nullptr; + + llvm::Function* createArrayPushFunc(); + llvm::Function* createArraySetFunc(); + llvm::Function* createArrayGetFunc(); + llvm::Function* createGetPtrFunc(); + llvm::Function* createFreeFunc(); + llvm::Function* createExtendFunc(); + llvm::Function* getReallocFunc(); + + LazyFunction m_pushFunc = {[this](){ return createArrayPushFunc(); }}; // TODO: If works on MSVC, remove form initialization list + LazyFunction m_setFunc; + LazyFunction m_getPtrFunc = {[this](){ return createGetPtrFunc(); }}; + LazyFunction m_getFunc; + LazyFunction m_freeFunc; + LazyFunction m_extendFunc = {[this](){ return createExtendFunc(); }}; + LazyFunction m_reallocFunc = {[this](){ return getReallocFunc(); }}; +}; + +} +} +} + + diff --git a/evmjit/libevmjit/BasicBlock.cpp b/evmjit/libevmjit/BasicBlock.cpp index c9e71be9a..a41743d0b 100644 --- a/evmjit/libevmjit/BasicBlock.cpp +++ b/evmjit/libevmjit/BasicBlock.cpp @@ -11,6 +11,7 @@ #include "preprocessor/llvm_includes_end.h" #include "Type.h" +#include "Utils.h" namespace dev { @@ -48,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() @@ -136,32 +138,34 @@ void BasicBlock::synchronizeLocalStack(Stack& _evmStack) { auto blockTerminator = m_llvmBB->getTerminator(); assert(blockTerminator != nullptr); - m_builder.SetInsertPoint(blockTerminator); + if (blockTerminator->getOpcode() != llvm::Instruction::Ret) + { + // Not needed in case of ret instruction. Ret also invalidates the stack. + m_builder.SetInsertPoint(blockTerminator); - auto currIter = m_currentStack.begin(); - auto endIter = m_currentStack.end(); + auto currIter = m_currentStack.begin(); + auto endIter = m_currentStack.end(); - // Update (emit set()) changed values - for (int idx = (int)m_currentStack.size() - 1 - m_tosOffset; - currIter < endIter && idx >= 0; - ++currIter, --idx) - { - assert(static_cast(idx) < m_initialStack.size()); - if (*currIter != m_initialStack[idx]) // value needs update - _evmStack.set(static_cast(idx), *currIter); - } + // Update (emit set()) changed values + for (int idx = (int)m_currentStack.size() - 1 - m_tosOffset; + currIter < endIter && idx >= 0; + ++currIter, --idx) + { + assert(static_cast(idx) < m_initialStack.size()); + if (*currIter != m_initialStack[idx]) // value needs update + _evmStack.set(static_cast(idx), *currIter); + } - if (m_tosOffset < 0) - { // Pop values - _evmStack.pop(static_cast(-m_tosOffset)); - } + if (m_tosOffset < 0) + _evmStack.pop(static_cast(-m_tosOffset)); - // Push new values - for (; currIter < endIter; ++currIter) - { - assert(*currIter != nullptr); - _evmStack.push(*currIter); + // Push new values + for (; currIter < endIter; ++currIter) + { + assert(*currIter != nullptr); + _evmStack.push(*currIter); + } } // Emit get() for all (used) values from the initial stack @@ -233,7 +237,7 @@ void BasicBlock::linkLocalStacks(std::vector basicBlocks, llvm::IRB for (auto predIt = llvm::pred_begin(bb); predIt != llvm::pred_end(bb); ++predIt) { auto predInfoEntry = cfg.find(*predIt); - if (predInfoEntry != cfg.end()) + if (predInfoEntry != cfg.end()) // FIXME: It is wrong - will skip entry block info.predecessors.push_back(&predInfoEntry->second); } } @@ -242,13 +246,12 @@ void BasicBlock::linkLocalStacks(std::vector basicBlocks, llvm::IRB bool valuesChanged = true; while (valuesChanged) { - if (getenv("EVMCC_DEBUG_BLOCKS")) + for (auto& pair : cfg) { - for (auto& pair : cfg) - std::cerr << pair.second.bblock.llvm()->getName().str() - << ": in " << pair.second.inputItems - << ", out " << pair.second.outputItems - << "\n"; + DLOG(bb) << pair.second.bblock.llvm()->getName().str() + << ": in " << pair.second.inputItems + << ", out " << pair.second.outputItems + << "\n"; } valuesChanged = false; @@ -256,6 +259,9 @@ void BasicBlock::linkLocalStacks(std::vector basicBlocks, llvm::IRB { auto& info = pair.second; + if (&info.bblock == basicBlocks.front()) + info.inputItems = 0; // we cannot use phi nodes for first block as it is a successor of entry block + if (info.predecessors.empty()) info.inputItems = 0; // no consequences for other blocks, so leave valuesChanged false @@ -340,6 +346,8 @@ void BasicBlock::dump(std::ostream& _out, bool _dotOutput) { if (val == nullptr) out << " ?"; + else if (llvm::isa(val)) + out << " " << val->getName(); else if (llvm::isa(val)) out << *val; else @@ -361,6 +369,8 @@ void BasicBlock::dump(std::ostream& _out, bool _dotOutput) { if (*val == nullptr) out << " ?"; + else if (llvm::isa(*val)) + out << " " << (*val)->getName(); else if (llvm::isa(*val)) out << **val; else 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/BuildInfo.h.in b/evmjit/libevmjit/BuildInfo.h.in index 204b4d89b..4b72144ed 100644 --- a/evmjit/libevmjit/BuildInfo.h.in +++ b/evmjit/libevmjit/BuildInfo.h.in @@ -8,3 +8,4 @@ #define LLVM_VERSION "${LLVM_PACKAGE_VERSION}" #define LLVM_ASSERTIONS "${LLVM_ENABLE_ASSERTIONS}" +#define LLVM_DEBUG ${LLVM_DEBUG} diff --git a/evmjit/libevmjit/CMakeLists.txt b/evmjit/libevmjit/CMakeLists.txt index 943c64e42..7f4e763d7 100644 --- a/evmjit/libevmjit/CMakeLists.txt +++ b/evmjit/libevmjit/CMakeLists.txt @@ -1,9 +1,29 @@ set(TARGET_NAME evmjit) -file(GLOB SOURCES "*.cpp") -file(GLOB HEADERS "*.h") -set(INTERFACE_HEADERS interface.h) -source_group("" FILES ${HEADERS}) +set(SOURCES + Arith256.cpp Arith256.h + Array.cpp Array.h + BasicBlock.cpp BasicBlock.h + Cache.cpp Cache.h + Common.h + Compiler.cpp Compiler.h + CompilerHelper.cpp CompilerHelper.h + Endianness.cpp Endianness.h + ExecStats.cpp ExecStats.h + ExecutionEngine.cpp ExecutionEngine.h + Ext.cpp Ext.h + GasMeter.cpp GasMeter.h + Instruction.cpp Instruction.h + interface.cpp interface.h + Memory.cpp Memory.h + Optimizer.cpp Optimizer.h + Runtime.cpp Runtime.h + RuntimeData.h + RuntimeManager.cpp RuntimeManager.h + Stack.cpp Stack.h + Type.cpp Type.h + Utils.cpp Utils.h +) source_group("" FILES ${SOURCES}) if(${CMAKE_CXX_COMPILER_ID} STREQUAL "MSVC") @@ -48,11 +68,13 @@ else() set(EVMJIT_SOVERSION ${EVMJIT_VERSION_MAJOR}) endif() + +string(COMPARE EQUAL "${LLVM_ENABLE_ASSERTIONS}" "ON" LLVM_DEBUG) configure_file(BuildInfo.h.in ${CMAKE_CURRENT_BINARY_DIR}/gen/BuildInfo.gen.h) message(STATUS "EVM JIT version: ${EVMJIT_VERSION_MAJOR}.${EVMJIT_VERSION_MINOR}.${EVMJIT_VERSION_PATCH} ${EVMJIT_VERSION_PRERELEASE} (${EVMJIT_VERSION_FULL})") -add_library(${TARGET_NAME} SHARED ${SOURCES} ${HEADERS} gen/BuildInfo.gen.h) +add_library(${TARGET_NAME} SHARED ${SOURCES} gen/BuildInfo.gen.h) set_target_properties(${TARGET_NAME} PROPERTIES VERSION ${EVMJIT_VERSION} SOVERSION ${EVMJIT_SOVERSION} FOLDER "libs") @@ -62,7 +84,4 @@ include_directories(${CMAKE_CURRENT_BINARY_DIR}/gen) target_link_libraries(${TARGET_NAME} PRIVATE ${LLVM_LIBS}) -#set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti") - install(TARGETS ${TARGET_NAME} LIBRARY DESTINATION lib ARCHIVE DESTINATION lib RUNTIME DESTINATION bin) -#install(FILES ${INTERFACE_HEADERS} DESTINATION include/${TARGET_NAME}) \ No newline at end of file diff --git a/evmjit/libevmjit/Cache.cpp b/evmjit/libevmjit/Cache.cpp index fe226eefb..47a6386e9 100644 --- a/evmjit/libevmjit/Cache.cpp +++ b/evmjit/libevmjit/Cache.cpp @@ -1,12 +1,10 @@ #include "Cache.h" -#include -#include - #include "preprocessor/llvm_includes_start.h" #include #include #include +#include #include #include #include @@ -14,6 +12,7 @@ #include "ExecutionEngine.h" #include "Utils.h" +#include "BuildInfo.gen.h" namespace dev { @@ -22,28 +21,84 @@ namespace eth namespace jit { -//#define CACHE_LOG std::cerr << "CACHE " -#define CACHE_LOG std::ostream(nullptr) - namespace { + CacheMode g_mode; llvm::MemoryBuffer* g_lastObject; ExecutionEngineListener* g_listener; + static const size_t c_versionStampLength = 32; + + llvm::StringRef getLibVersionStamp() + { + static auto version = llvm::SmallString{}; + if (version.empty()) + { + version = EVMJIT_VERSION_FULL; + version.resize(c_versionStampLength); + } + return version; + } } -ObjectCache* Cache::getObjectCache(ExecutionEngineListener* _listener) +ObjectCache* Cache::getObjectCache(CacheMode _mode, ExecutionEngineListener* _listener) { static ObjectCache objectCache; + g_mode = _mode; g_listener = _listener; return &objectCache; } +void Cache::clear() +{ + using namespace llvm::sys; + llvm::SmallString<256> cachePath; + path::system_temp_directory(false, cachePath); + path::append(cachePath, "evm_objs"); + + std::error_code err; + for (auto it = fs::directory_iterator{cachePath.str(), err}; it != fs::directory_iterator{}; it.increment(err)) + 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) + return nullptr; + if (g_listener) g_listener->stateChanged(ExecState::CacheLoad); - CACHE_LOG << id << ": search\n"; + DLOG(cache) << id << ": search\n"; if (!CHECK(!g_lastObject)) g_lastObject = nullptr; @@ -51,28 +106,21 @@ std::unique_ptr Cache::getObject(std::string const& id) llvm::sys::path::system_temp_directory(false, cachePath); llvm::sys::path::append(cachePath, "evm_objs", id); -#if defined(__GNUC__) && !defined(NDEBUG) - llvm::sys::fs::file_status st; - auto err = llvm::sys::fs::status(cachePath.str(), st); - if (err) - return nullptr; - auto mtime = st.getLastModificationTime().toEpochTime(); - - std::tm tm; - strptime(__DATE__ __TIME__, " %b %d %Y %H:%M:%S", &tm); - auto btime = (uint64_t)std::mktime(&tm); - if (btime > mtime) - return nullptr; -#endif - if (auto r = llvm::MemoryBuffer::getFile(cachePath.str(), -1, false)) - g_lastObject = llvm::MemoryBuffer::getMemBufferCopy(r.get()->getBuffer()); + { + auto& buf = r.get(); + auto objVersionStamp = buf->getBufferSize() >= c_versionStampLength ? llvm::StringRef{buf->getBufferEnd() - c_versionStampLength, c_versionStampLength} : llvm::StringRef{}; + if (objVersionStamp == getLibVersionStamp()) + g_lastObject = llvm::MemoryBuffer::getMemBufferCopy(r.get()->getBuffer()); + else + DLOG(cache) << "Unmatched version: " << objVersionStamp.str() << ", expected " << getLibVersionStamp().str() << "\n"; + } else if (r.getError() != std::make_error_code(std::errc::no_such_file_or_directory)) - std::cerr << r.getError().message(); // TODO: Add log + DLOG(cache) << r.getError().message(); // TODO: Add warning log if (g_lastObject) // if object found create fake module { - CACHE_LOG << id << ": found\n"; + DLOG(cache) << id << ": found\n"; auto&& context = llvm::getGlobalContext(); auto module = std::unique_ptr(new llvm::Module(id, context)); auto mainFuncType = llvm::FunctionType::get(llvm::Type::getVoidTy(context), {}, false); @@ -81,13 +129,17 @@ std::unique_ptr Cache::getObject(std::string const& id) bb->getInstList().push_back(new llvm::UnreachableInst{context}); return module; } - CACHE_LOG << id << ": not found\n"; + DLOG(cache) << id << ": not found\n"; return nullptr; } void ObjectCache::notifyObjectCompiled(llvm::Module const* _module, llvm::MemoryBuffer const* _object) { + // Only in "on" and "write" mode + if (g_mode != CacheMode::on && g_mode != CacheMode::write) + return; + if (g_listener) g_listener->stateChanged(ExecState::CacheWrite); @@ -97,19 +149,19 @@ void ObjectCache::notifyObjectCompiled(llvm::Module const* _module, llvm::Memory llvm::sys::path::append(cachePath, "evm_objs"); if (llvm::sys::fs::create_directory(cachePath.str())) - return; // TODO: Add log + DLOG(cache) << "Cannot create cache dir " << cachePath.str().str() << "\n"; llvm::sys::path::append(cachePath, id); - CACHE_LOG << id << ": write\n"; + DLOG(cache) << id << ": write\n"; std::string error; llvm::raw_fd_ostream cacheFile(cachePath.c_str(), error, llvm::sys::fs::F_None); - cacheFile << _object->getBuffer(); + cacheFile << _object->getBuffer() << getLibVersionStamp(); } llvm::MemoryBuffer* ObjectCache::getObject(llvm::Module const* _module) { - CACHE_LOG << _module->getModuleIdentifier() << ": use\n"; + DLOG(cache) << _module->getModuleIdentifier() << ": use\n"; auto o = g_lastObject; g_lastObject = nullptr; return o; diff --git a/evmjit/libevmjit/Cache.h b/evmjit/libevmjit/Cache.h index e8f01d38d..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 @@ -12,6 +18,16 @@ namespace jit { class ExecutionEngineListener; +enum class CacheMode +{ + on, + off, + read, + write, + clear, + preload +}; + class ObjectCache : public llvm::ObjectCache { public: @@ -29,8 +45,14 @@ public: class Cache { public: - static ObjectCache* getObjectCache(ExecutionEngineListener* _listener); + static ObjectCache* getObjectCache(CacheMode _mode, ExecutionEngineListener* _listener); static std::unique_ptr getObject(std::string const& id); + + /// 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/Common.h b/evmjit/libevmjit/Common.h index 62731292f..028f0b3c5 100644 --- a/evmjit/libevmjit/Common.h +++ b/evmjit/libevmjit/Common.h @@ -1,11 +1,12 @@ #pragma once -#include #include #include #ifdef _MSC_VER -#define EXPORT __declspec(dllexport) +#define EXPORT __declspec(dllexport) +#define _ALLOW_KEYWORD_MACROS +#define noexcept throw() #else #define EXPORT #endif @@ -18,12 +19,9 @@ namespace jit { using byte = uint8_t; -using bytes = std::vector; using bytes_ref = std::tuple; using code_iterator = byte const*; -struct NoteChannel {}; // FIXME: Use some log library? - enum class ReturnCode { // Success codes @@ -33,7 +31,7 @@ enum class ReturnCode // Standard error codes OutOfGas = -1, - StackTooSmall = -2, + StackUnderflow = -2, BadJumpDestination = -3, BadInstruction = -4, Rejected = -5, ///< Input data (code, gas, block info, etc.) does not meet JIT requirement and execution request has been rejected diff --git a/evmjit/libevmjit/Compiler.cpp b/evmjit/libevmjit/Compiler.cpp index de48e8ef9..1e1f8fe93 100644 --- a/evmjit/libevmjit/Compiler.cpp +++ b/evmjit/libevmjit/Compiler.cpp @@ -6,13 +6,9 @@ #include #include "preprocessor/llvm_includes_start.h" -#include #include #include #include -#include -#include -#include #include "preprocessor/llvm_includes_end.h" #include "Instruction.h" @@ -93,7 +89,7 @@ void Compiler::createBasicBlocks(code_iterator _codeBegin, code_iterator _codeEn } } -llvm::BasicBlock* Compiler::getJumpTableBlock() +llvm::BasicBlock* Compiler::getJumpTableBlock(RuntimeManager& _runtimeManager) { if (!m_jumpTableBlock) { @@ -101,7 +97,7 @@ llvm::BasicBlock* Compiler::getJumpTableBlock() InsertPointGuard g{m_builder}; m_builder.SetInsertPoint(m_jumpTableBlock->llvm()); auto dest = m_builder.CreatePHI(Type::Word, 8, "target"); - auto switchInstr = m_builder.CreateSwitch(dest, getBadJumpBlock()); + auto switchInstr = m_builder.CreateSwitch(dest, getBadJumpBlock(_runtimeManager)); for (auto&& p : m_basicBlocks) { if (p.second.isJumpDest()) @@ -111,21 +107,20 @@ llvm::BasicBlock* Compiler::getJumpTableBlock() return m_jumpTableBlock->llvm(); } -llvm::BasicBlock* Compiler::getBadJumpBlock() +llvm::BasicBlock* Compiler::getBadJumpBlock(RuntimeManager& _runtimeManager) { if (!m_badJumpBlock) { m_badJumpBlock.reset(new BasicBlock("BadJump", m_mainFunc, m_builder, true)); InsertPointGuard g{m_builder}; m_builder.SetInsertPoint(m_badJumpBlock->llvm()); - m_builder.CreateRet(Constant::get(ReturnCode::BadJumpDestination)); + _runtimeManager.exit(ReturnCode::BadJumpDestination); } return m_badJumpBlock->llvm(); } std::unique_ptr Compiler::compile(code_iterator _begin, code_iterator _end, std::string const& _id) { - auto compilationStartTime = std::chrono::high_resolution_clock::now(); auto module = std::unique_ptr(new llvm::Module(_id, m_builder.getContext())); // Create main function @@ -137,6 +132,17 @@ std::unique_ptr Compiler::compile(code_iterator _begin, code_itera auto entryBlock = llvm::BasicBlock::Create(m_builder.getContext(), {}, m_mainFunc); m_builder.SetInsertPoint(entryBlock); + createBasicBlocks(_begin, _end); + + // Init runtime structures. + RuntimeManager runtimeManager(m_builder, _begin, _end); + GasMeter gasMeter(m_builder, runtimeManager); + Memory memory(runtimeManager, gasMeter); + Ext ext(runtimeManager, memory); + Stack stack(m_builder, runtimeManager); + runtimeManager.setStack(stack); // Runtime Manager will free stack memory + Arith256 arith(m_builder); + auto jmpBufWords = m_builder.CreateAlloca(Type::BytePtr, m_builder.getInt64(3), "jmpBuf.words"); auto frameaddress = llvm::Intrinsic::getDeclaration(module.get(), llvm::Intrinsic::frameaddress); auto fp = m_builder.CreateCall(frameaddress, m_builder.getInt32(0), "fp"); @@ -149,24 +155,14 @@ std::unique_ptr Compiler::compile(code_iterator _begin, code_itera auto jmpBuf = m_builder.CreateBitCast(jmpBufWords, Type::BytePtr, "jmpBuf"); auto r = m_builder.CreateCall(setjmp, jmpBuf); auto normalFlow = m_builder.CreateICmpEQ(r, m_builder.getInt32(0)); - - createBasicBlocks(_begin, _end); - - // Init runtime structures. - RuntimeManager runtimeManager(m_builder, jmpBuf, _begin, _end); - GasMeter gasMeter(m_builder, runtimeManager); - Memory memory(runtimeManager, gasMeter); - Ext ext(runtimeManager, memory); - Stack stack(m_builder, runtimeManager); - Arith256 arith(m_builder); + runtimeManager.setJmpBuf(jmpBuf); // TODO: Create Stop basic block on demand m_stopBB = llvm::BasicBlock::Create(m_mainFunc->getContext(), "Stop", m_mainFunc); auto abortBB = llvm::BasicBlock::Create(m_mainFunc->getContext(), "Abort", m_mainFunc); auto firstBB = m_basicBlocks.empty() ? m_stopBB : m_basicBlocks.begin()->second.llvm(); - auto expectTrue = llvm::MDBuilder{m_builder.getContext()}.createBranchWeights(1, 0); - m_builder.CreateCondBr(normalFlow, firstBB, abortBB, expectTrue); + m_builder.CreateCondBr(normalFlow, firstBB, abortBB, Type::expectTrue); for (auto basicBlockPairIt = m_basicBlocks.begin(); basicBlockPairIt != m_basicBlocks.end(); ++basicBlockPairIt) { @@ -180,10 +176,10 @@ std::unique_ptr Compiler::compile(code_iterator _begin, code_itera // Code for special blocks: // TODO: move to separate function. m_builder.SetInsertPoint(m_stopBB); - m_builder.CreateRet(Constant::get(ReturnCode::Stop)); + runtimeManager.exit(ReturnCode::Stop); m_builder.SetInsertPoint(abortBB); - m_builder.CreateRet(Constant::get(ReturnCode::OutOfGas)); + runtimeManager.exit(ReturnCode::OutOfGas); removeDeadBlocks(); @@ -230,16 +226,6 @@ std::unique_ptr Compiler::compile(code_iterator _begin, code_itera dumpCFGifRequired("blocks-sync.dot"); - if (m_jumpTableBlock && m_options.rewriteSwitchToBranches) - { - llvm::FunctionPassManager fpManager(module.get()); - fpManager.add(llvm::createLowerSwitchPass()); - fpManager.doInitialization(); - fpManager.run(*m_mainFunc); - } - - auto compilationEndTime = std::chrono::high_resolution_clock::now(); - clog(JIT) << "JIT: " << std::chrono::duration_cast(compilationEndTime - compilationStartTime).count(); return module; } @@ -516,7 +502,7 @@ void Compiler::compileBasicBlock(BasicBlock& _basicBlock, RuntimeManager& _runti auto val = stack.pop(); static_cast(val); // Generate a dummy use of val to make sure that a get(0) will be emitted at this point, - // so that StackTooSmall will be thrown + // so that StackUnderflow will be thrown // m_builder.CreateICmpEQ(val, val, "dummy"); break; } @@ -600,7 +586,7 @@ void Compiler::compileBasicBlock(BasicBlock& _basicBlock, RuntimeManager& _runti auto&& c = constant->getValue(); auto targetIdx = c.getActiveBits() <= 64 ? c.getZExtValue() : -1; auto it = m_basicBlocks.find(targetIdx); - targetBlock = (it != m_basicBlocks.end() && it->second.isJumpDest()) ? it->second.llvm() : getBadJumpBlock(); + targetBlock = (it != m_basicBlocks.end() && it->second.isJumpDest()) ? it->second.llvm() : getBadJumpBlock(_runtimeManager); } // TODO: Improve; check for constants @@ -613,7 +599,7 @@ void Compiler::compileBasicBlock(BasicBlock& _basicBlock, RuntimeManager& _runti else { _basicBlock.setJumpTarget(target); - m_builder.CreateBr(getJumpTableBlock()); + m_builder.CreateBr(getJumpTableBlock(_runtimeManager)); } } else // JUMPI @@ -629,7 +615,7 @@ void Compiler::compileBasicBlock(BasicBlock& _basicBlock, RuntimeManager& _runti else { _basicBlock.setJumpTarget(target); - m_builder.CreateCondBr(cond, getJumpTableBlock(), _nextBasicBlock); + m_builder.CreateCondBr(cond, getJumpTableBlock(_runtimeManager), _nextBasicBlock); } } break; @@ -769,7 +755,7 @@ void Compiler::compileBasicBlock(BasicBlock& _basicBlock, RuntimeManager& _runti case Instruction::CALL: case Instruction::CALLCODE: { - auto callGas256 = stack.pop(); + auto callGas = stack.pop(); auto codeAddress = stack.pop(); auto value = stack.pop(); auto inOff = stack.pop(); @@ -787,13 +773,8 @@ void Compiler::compileBasicBlock(BasicBlock& _basicBlock, RuntimeManager& _runti if (inst == Instruction::CALLCODE) receiveAddress = _runtimeManager.get(RuntimeData::Address); - auto gas = _runtimeManager.getGas(); - _gasMeter.count(callGas256); - auto callGas = m_builder.CreateTrunc(callGas256, Type::Gas); - auto gasLeft = m_builder.CreateNSWSub(gas, callGas); - _runtimeManager.setGas(callGas); - auto ret = _ext.call(receiveAddress, value, inOff, inSize, outOff, outSize, codeAddress); - _gasMeter.giveBack(gasLeft); + auto ret = _ext.call(callGas, receiveAddress, value, inOff, inSize, outOff, outSize, codeAddress); + _gasMeter.count(m_builder.getInt64(0), _runtimeManager.getJmpBuf(), _runtimeManager.getGasPtr()); stack.push(ret); break; } @@ -806,14 +787,14 @@ void Compiler::compileBasicBlock(BasicBlock& _basicBlock, RuntimeManager& _runti _memory.require(index, size); _runtimeManager.registerReturnData(index, size); - m_builder.CreateRet(Constant::get(ReturnCode::Return)); + _runtimeManager.exit(ReturnCode::Return); break; } case Instruction::SUICIDE: { _runtimeManager.registerSuicide(stack.pop()); - m_builder.CreateRet(Constant::get(ReturnCode::Suicide)); + _runtimeManager.exit(ReturnCode::Suicide); break; } @@ -857,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/Compiler.h b/evmjit/libevmjit/Compiler.h index c9795fb99..4469389bb 100644 --- a/evmjit/libevmjit/Compiler.h +++ b/evmjit/libevmjit/Compiler.h @@ -38,9 +38,9 @@ private: void compileBasicBlock(BasicBlock& _basicBlock, class RuntimeManager& _runtimeManager, class Arith256& _arith, class Memory& _memory, class Ext& _ext, class GasMeter& _gasMeter, llvm::BasicBlock* _nextBasicBlock); - llvm::BasicBlock* getJumpTableBlock(); + llvm::BasicBlock* getJumpTableBlock(RuntimeManager& _runtimeManager); - llvm::BasicBlock* getBadJumpBlock(); + llvm::BasicBlock* getBadJumpBlock(RuntimeManager& _runtimeManager); void removeDeadBlocks(); diff --git a/evmjit/libevmjit/Endianness.cpp b/evmjit/libevmjit/Endianness.cpp index 38f71560c..d36f4b7fa 100644 --- a/evmjit/libevmjit/Endianness.cpp +++ b/evmjit/libevmjit/Endianness.cpp @@ -2,6 +2,7 @@ #include "preprocessor/llvm_includes_start.h" #include +#include #include "preprocessor/llvm_includes_end.h" #include "Type.h" @@ -15,13 +16,7 @@ namespace jit llvm::Value* Endianness::bswapIfLE(llvm::IRBuilder<>& _builder, llvm::Value* _word) { - union tester - { - unsigned int x; - unsigned char isLE; - }; - - if (tester{1}.isLE) + if (llvm::sys::IsLittleEndianHost) { // FIXME: Disabled because of problems with BYTE //if (auto constant = llvm::dyn_cast(_word)) diff --git a/evmjit/libevmjit/ExecStats.cpp b/evmjit/libevmjit/ExecStats.cpp index 684f6d39a..ff8c05307 100644 --- a/evmjit/libevmjit/ExecStats.cpp +++ b/evmjit/libevmjit/ExecStats.cpp @@ -67,6 +67,7 @@ char const* getExecStateName(ExecState _state) case ExecState::CacheLoad: return "CacheLoad"; case ExecState::CacheWrite: return "CacheWrite"; case ExecState::Compilation: return "Compilation"; + case ExecState::Optimization: return "Optimization"; case ExecState::CodeGen: return "CodeGen"; case ExecState::Execution: return "Execution"; case ExecState::Return: return "Return"; diff --git a/evmjit/libevmjit/ExecStats.h b/evmjit/libevmjit/ExecStats.h index 1ac9b6995..0451ccb05 100644 --- a/evmjit/libevmjit/ExecStats.h +++ b/evmjit/libevmjit/ExecStats.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include diff --git a/evmjit/libevmjit/ExecutionEngine.cpp b/evmjit/libevmjit/ExecutionEngine.cpp index 1d2ff91b1..0ed4a65b5 100644 --- a/evmjit/libevmjit/ExecutionEngine.cpp +++ b/evmjit/libevmjit/ExecutionEngine.cpp @@ -1,8 +1,11 @@ #include "ExecutionEngine.h" #include -#include // env options +#include #include +#include +#include +#include #include "preprocessor/llvm_includes_start.h" #include @@ -12,10 +15,13 @@ #include #include #include +#include +#include #include "preprocessor/llvm_includes_end.h" #include "Runtime.h" #include "Compiler.h" +#include "Optimizer.h" #include "Cache.h" #include "ExecStats.h" #include "Utils.h" @@ -48,42 +54,79 @@ std::string codeHash(i256 const& _hash) return str; } -bool getEnvOption(char const* _name, bool _default) +void printVersion() { - auto var = std::getenv(_name); - if (!var) - return _default; - return std::strtol(var, nullptr, 10) != 0; + std::cout << "Ethereum EVM JIT Compiler (http://github.com/ethereum/evmjit):\n" + << " EVMJIT version " << EVMJIT_VERSION << "\n" +#ifdef NDEBUG + << " Optimized build, " EVMJIT_VERSION_FULL "\n" +#else + << " DEBUG build, " EVMJIT_VERSION_FULL "\n" +#endif + << " Built " << __DATE__ << " (" << __TIME__ << ")\n" + << std::endl; } -bool showInfo() +namespace cl = llvm::cl; +cl::opt g_optimize{"O", cl::desc{"Optimize"}}; +cl::opt g_cache{"cache", cl::desc{"Cache compiled EVM code on disk"}, + cl::values( + clEnumValN(CacheMode::on, "1", "Enabled"), + clEnumValN(CacheMode::off, "0", "Disabled"), + 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"}}; + +void parseOptions() { - auto show = getEnvOption("EVMJIT_INFO", false); - if (show) - { - std::cout << "The Ethereum EVM JIT " EVMJIT_VERSION_FULL " LLVM " LLVM_VERSION << std::endl; - } - return show; + static llvm::llvm_shutdown_obj shutdownObj{}; + cl::AddExtraVersionPrinter(printVersion); + //cl::ParseEnvironmentOptions("evmjit", "EVMJIT", "Ethereum EVM JIT Compiler"); + + // FIXME: LLVM workaround: + // Manually select instruction scheduler other than "source". + // "source" scheduler has a bug: http://llvm.org/bugs/show_bug.cgi?id=22304 + auto envLine = std::getenv("EVMJIT"); + auto commandLine = std::string{"evmjit "} + (envLine ? envLine : "") + " -pre-RA-sched=list-burr\0"; + static const auto c_maxArgs = 20; + char const* argv[c_maxArgs] = {nullptr, }; + auto arg = std::strtok(&*commandLine.begin(), " "); + auto i = 0; + for (; i < c_maxArgs && arg; ++i, arg = std::strtok(nullptr, " ")) + argv[i] = arg; + cl::ParseCommandLineOptions(i, argv, "Ethereum EVM JIT Compiler"); } } + ReturnCode ExecutionEngine::run(RuntimeData* _data, Env* _env) { - static auto debugDumpModule = getEnvOption("EVMJIT_DUMP", false); - static auto objectCacheEnabled = getEnvOption("EVMJIT_CACHE", true); - static auto statsCollectingEnabled = getEnvOption("EVMJIT_STATS", false); - static auto infoShown = showInfo(); - (void) infoShown; + static std::once_flag flag; + std::call_once(flag, parseOptions); std::unique_ptr listener{new ExecStats}; listener->stateChanged(ExecState::Started); - auto objectCache = objectCacheEnabled ? Cache::getObjectCache(listener.get()) : nullptr; + 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) { + if (g_cache == CacheMode::clear) + Cache::clear(); + llvm::InitializeNativeTarget(); llvm::InitializeNativeTargetAsmPrinter(); @@ -91,7 +134,7 @@ ReturnCode ExecutionEngine::run(RuntimeData* _data, Env* _env) llvm::EngineBuilder builder(module.get()); builder.setEngineKind(llvm::EngineKind::JIT); builder.setUseMCJIT(true); - builder.setOptLevel(llvm::CodeGenOpt::None); + builder.setOptLevel(g_optimize ? llvm::CodeGenOpt::Default : llvm::CodeGenOpt::None); auto triple = llvm::Triple(llvm::sys::getProcessTriple()); if (triple.getOS() == llvm::Triple::OSType::Win32) @@ -103,14 +146,21 @@ 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; auto mainFuncName = codeHash(_data->codeHash); - Runtime runtime(_data, _env); // TODO: I don't know why but it must be created before getFunctionAddress() calls + m_runtime.init(_data, _env); + + EntryFuncPtr entryFuncPtr = nullptr; + auto it = funcCache.find(mainFuncName); + if (it != funcCache.end()) + entryFuncPtr = (EntryFuncPtr) it->second; - auto entryFuncPtr = (EntryFuncPtr)ee->getFunctionAddress(mainFuncName); if (!entryFuncPtr) { auto module = objectCache ? Cache::getObject(mainFuncName) : nullptr; @@ -118,9 +168,15 @@ ReturnCode ExecutionEngine::run(RuntimeData* _data, Env* _env) { listener->stateChanged(ExecState::Compilation); assert(_data->code || !_data->codeSize); //TODO: Is it good idea to execute empty code? - module = Compiler({}).compile(_data->code, _data->code + _data->codeSize, mainFuncName); + module = Compiler{{}}.compile(_data->code, _data->code + _data->codeSize, mainFuncName); + + if (g_optimize) + { + listener->stateChanged(ExecState::Optimization); + optimize(*module); + } } - if (debugDumpModule) + if (g_dump) module->dump(); ee->addModule(module.get()); @@ -131,18 +187,19 @@ ReturnCode ExecutionEngine::run(RuntimeData* _data, Env* _env) if (!CHECK(entryFuncPtr)) return ReturnCode::LLVMLinkError; + if (it == funcCache.end()) + funcCache[mainFuncName] = (uint64_t) entryFuncPtr; + listener->stateChanged(ExecState::Execution); - auto returnCode = entryFuncPtr(&runtime); + auto returnCode = entryFuncPtr(&m_runtime); listener->stateChanged(ExecState::Return); if (returnCode == ReturnCode::Return) - { - returnData = runtime.getReturnData(); // Save reference to return data - std::swap(m_memory, runtime.getMemory()); // Take ownership of memory - } + returnData = m_runtime.getReturnData(); // Save reference to return data + listener->stateChanged(ExecState::Finished); - if (statsCollectingEnabled) + if (g_stats) statsCollector.stats.push_back(std::move(listener)); return returnCode; diff --git a/evmjit/libevmjit/ExecutionEngine.h b/evmjit/libevmjit/ExecutionEngine.h index 4c2965e58..26da6977c 100644 --- a/evmjit/libevmjit/ExecutionEngine.h +++ b/evmjit/libevmjit/ExecutionEngine.h @@ -2,7 +2,7 @@ #include -#include "RuntimeData.h" +#include "Runtime.h" namespace dev { @@ -17,6 +17,7 @@ enum class ExecState CacheLoad, CacheWrite, Compilation, + Optimization, CodeGen, Execution, Return, @@ -50,9 +51,7 @@ public: bytes_ref returnData; private: - /// After execution, if RETURN used, memory is moved there - /// to allow client copy the returned data - bytes m_memory; + Runtime m_runtime; }; } diff --git a/evmjit/libevmjit/Ext.cpp b/evmjit/libevmjit/Ext.cpp index 38deef214..d35aebc68 100644 --- a/evmjit/libevmjit/Ext.cpp +++ b/evmjit/libevmjit/Ext.cpp @@ -41,7 +41,7 @@ std::array::value> const& getEnvFuncDescs() FuncDesc{"env_sha3", getFunctionType(Type::Void, {Type::BytePtr, Type::Size, Type::WordPtr})}, FuncDesc{"env_balance", getFunctionType(Type::Void, {Type::EnvPtr, Type::WordPtr, Type::WordPtr})}, FuncDesc{"env_create", getFunctionType(Type::Void, {Type::EnvPtr, Type::GasPtr, Type::WordPtr, Type::BytePtr, Type::Size, Type::WordPtr})}, - FuncDesc{"env_call", getFunctionType(Type::Bool, {Type::EnvPtr, Type::GasPtr, Type::WordPtr, Type::WordPtr, Type::BytePtr, Type::Size, Type::BytePtr, Type::Size, Type::WordPtr})}, + FuncDesc{"env_call", getFunctionType(Type::Bool, {Type::EnvPtr, Type::GasPtr, Type::Gas, Type::WordPtr, Type::WordPtr, Type::BytePtr, Type::Size, Type::BytePtr, Type::Size, Type::WordPtr})}, FuncDesc{"env_log", getFunctionType(Type::Void, {Type::EnvPtr, Type::BytePtr, Type::Size, Type::WordPtr, Type::WordPtr, Type::WordPtr, Type::WordPtr})}, FuncDesc{"env_blockhash", getFunctionType(Type::Void, {Type::EnvPtr, Type::WordPtr, Type::WordPtr})}, FuncDesc{"env_extcode", getFunctionType(Type::BytePtr, {Type::EnvPtr, Type::WordPtr, Type::Size->getPointerTo()})}, @@ -136,7 +136,7 @@ llvm::Value* Ext::create(llvm::Value* _endowment, llvm::Value* _initOff, llvm::V return address; } -llvm::Value* Ext::call(llvm::Value* _receiveAddress, llvm::Value* _value, llvm::Value* _inOff, llvm::Value* _inSize, llvm::Value* _outOff, llvm::Value* _outSize, llvm::Value* _codeAddress) +llvm::Value* Ext::call(llvm::Value* _callGas, llvm::Value* _receiveAddress, llvm::Value* _value, llvm::Value* _inOff, llvm::Value* _inSize, llvm::Value* _outOff, llvm::Value* _outSize, llvm::Value* _codeAddress) { auto receiveAddress = Endianness::toBE(m_builder, _receiveAddress); auto inBeg = m_memoryMan.getBytePtr(_inOff); @@ -144,7 +144,11 @@ llvm::Value* Ext::call(llvm::Value* _receiveAddress, llvm::Value* _value, llvm:: auto outBeg = m_memoryMan.getBytePtr(_outOff); auto outSize = m_builder.CreateTrunc(_outSize, Type::Size, "out.size"); auto codeAddress = Endianness::toBE(m_builder, _codeAddress); - auto ret = createCall(EnvFunc::call, {getRuntimeManager().getEnvPtr(), getRuntimeManager().getGasPtr(), byPtr(receiveAddress), byPtr(_value), inBeg, inSize, outBeg, outSize, byPtr(codeAddress)}); + auto callGas = m_builder.CreateSelect( + m_builder.CreateICmpULE(_callGas, m_builder.CreateZExt(Constant::gasMax, Type::Word)), + m_builder.CreateTrunc(_callGas, Type::Gas), + Constant::gasMax); + auto ret = createCall(EnvFunc::call, {getRuntimeManager().getEnvPtr(), getRuntimeManager().getGasPtr(), callGas, byPtr(receiveAddress), byPtr(_value), inBeg, inSize, outBeg, outSize, byPtr(codeAddress)}); return m_builder.CreateZExt(ret, Type::Word, "ret"); } diff --git a/evmjit/libevmjit/Ext.h b/evmjit/libevmjit/Ext.h index 1c0c0fc56..b0048d2e9 100644 --- a/evmjit/libevmjit/Ext.h +++ b/evmjit/libevmjit/Ext.h @@ -51,7 +51,7 @@ public: llvm::Value* balance(llvm::Value* _address); llvm::Value* calldataload(llvm::Value* _index); llvm::Value* create(llvm::Value* _endowment, llvm::Value* _initOff, llvm::Value* _initSize); - llvm::Value* call(llvm::Value* _receiveAddress, llvm::Value* _value, llvm::Value* _inOff, llvm::Value* _inSize, llvm::Value* _outOff, llvm::Value* _outSize, llvm::Value* _codeAddress); + llvm::Value* call(llvm::Value* _callGas, llvm::Value* _receiveAddress, llvm::Value* _value, llvm::Value* _inOff, llvm::Value* _inSize, llvm::Value* _outOff, llvm::Value* _outSize, llvm::Value* _codeAddress); llvm::Value* blockhash(llvm::Value* _number); llvm::Value* sha3(llvm::Value* _inOff, llvm::Value* _inSize); diff --git a/evmjit/libevmjit/GasMeter.cpp b/evmjit/libevmjit/GasMeter.cpp index ca21714e0..ffbd654e6 100644 --- a/evmjit/libevmjit/GasMeter.cpp +++ b/evmjit/libevmjit/GasMeter.cpp @@ -17,52 +17,113 @@ namespace jit namespace // Helper functions { -int64_t const c_stepGas = 1; -int64_t const c_balanceGas = 20; -int64_t const c_sha3Gas = 10; -int64_t const c_sha3WordGas = 10; -int64_t const c_sloadGas = 20; -int64_t const c_sstoreSetGas = 300; -int64_t const c_sstoreResetGas = 100; -int64_t const c_sstoreRefundGas = 100; -int64_t const c_createGas = 100; -int64_t const c_createDataGas = 5; -int64_t const c_callGas = 20; -int64_t const c_expGas = 1; -int64_t const c_expByteGas = 1; -int64_t const c_memoryGas = 1; -int64_t const c_txDataZeroGas = 1; -int64_t const c_txDataNonZeroGas = 5; -int64_t const c_txGas = 500; -int64_t const c_logGas = 32; -int64_t const c_logDataGas = 1; -int64_t const c_logTopicGas = 32; -int64_t const c_copyGas = 1; +int64_t const c_stepGas[] = {0, 2, 3, 5, 8, 10, 20}; +int64_t const c_expByteGas = 10; +int64_t const c_sha3Gas = 30; +int64_t const c_sha3WordGas = 6; +int64_t const c_sloadGas = 50; +int64_t const c_sstoreSetGas = 20000; +int64_t const c_sstoreResetGas = 5000; +int64_t const c_sstoreClearGas = 5000; +int64_t const c_jumpdestGas = 1; +int64_t const c_logGas = 375; +int64_t const c_logTopicGas = 375; +int64_t const c_logDataGas = 8; +int64_t const c_callGas = 40; +int64_t const c_createGas = 32000; +int64_t const c_memoryGas = 3; +int64_t const c_copyGas = 3; int64_t getStepCost(Instruction inst) { switch (inst) { - default: // Assumes instruction code is valid - return c_stepGas; - + // Tier 0 case Instruction::STOP: + case Instruction::RETURN: case Instruction::SUICIDE: case Instruction::SSTORE: // Handle cost of SSTORE separately in GasMeter::countSStore() - return 0; - - case Instruction::EXP: return c_expGas; - - case Instruction::SLOAD: return c_sloadGas; - - case Instruction::SHA3: return c_sha3Gas; - - case Instruction::BALANCE: return c_balanceGas; - - case Instruction::CALL: - case Instruction::CALLCODE: return c_callGas; - - case Instruction::CREATE: return c_createGas; + return c_stepGas[0]; + + // Tier 1 + case Instruction::ADDRESS: + case Instruction::ORIGIN: + case Instruction::CALLER: + case Instruction::CALLVALUE: + case Instruction::CALLDATASIZE: + case Instruction::CODESIZE: + case Instruction::GASPRICE: + case Instruction::COINBASE: + case Instruction::TIMESTAMP: + case Instruction::NUMBER: + case Instruction::DIFFICULTY: + case Instruction::GASLIMIT: + case Instruction::POP: + case Instruction::PC: + case Instruction::MSIZE: + case Instruction::GAS: + return c_stepGas[1]; + + // Tier 2 + case Instruction::ADD: + case Instruction::SUB: + case Instruction::LT: + case Instruction::GT: + case Instruction::SLT: + case Instruction::SGT: + case Instruction::EQ: + case Instruction::ISZERO: + case Instruction::AND: + case Instruction::OR: + case Instruction::XOR: + case Instruction::NOT: + case Instruction::BYTE: + case Instruction::CALLDATALOAD: + case Instruction::CALLDATACOPY: + case Instruction::CODECOPY: + case Instruction::MLOAD: + case Instruction::MSTORE: + case Instruction::MSTORE8: + case Instruction::ANY_PUSH: + case Instruction::ANY_DUP: + case Instruction::ANY_SWAP: + return c_stepGas[2]; + + // Tier 3 + case Instruction::MUL: + case Instruction::DIV: + case Instruction::SDIV: + case Instruction::MOD: + case Instruction::SMOD: + case Instruction::SIGNEXTEND: + return c_stepGas[3]; + + // Tier 4 + case Instruction::ADDMOD: + case Instruction::MULMOD: + case Instruction::JUMP: + return c_stepGas[4]; + + // Tier 5 + case Instruction::EXP: + case Instruction::JUMPI: + return c_stepGas[5]; + + // Tier 6 + case Instruction::BALANCE: + case Instruction::EXTCODESIZE: + case Instruction::EXTCODECOPY: + case Instruction::BLOCKHASH: + return c_stepGas[6]; + + case Instruction::SHA3: + return c_sha3Gas; + + case Instruction::SLOAD: + return c_sloadGas; + + case Instruction::JUMPDEST: + return c_jumpdestGas; case Instruction::LOG0: case Instruction::LOG1: @@ -73,7 +134,16 @@ int64_t getStepCost(Instruction inst) auto numTopics = static_cast(inst) - static_cast(Instruction::LOG0); return c_logGas + numTopics * c_logTopicGas; } + + case Instruction::CALL: + case Instruction::CALLCODE: + return c_callGas; + + case Instruction::CREATE: + return c_createGas; } + + return 0; // TODO: Add UNREACHABLE macro } } @@ -82,34 +152,36 @@ GasMeter::GasMeter(llvm::IRBuilder<>& _builder, RuntimeManager& _runtimeManager) CompilerHelper(_builder), m_runtimeManager(_runtimeManager) { - auto module = getModule(); - - llvm::Type* gasCheckArgs[] = {Type::RuntimePtr, Type::Gas}; - m_gasCheckFunc = llvm::Function::Create(llvm::FunctionType::get(Type::Void, gasCheckArgs, false), llvm::Function::PrivateLinkage, "gas.check", module); - InsertPointGuard guard(m_builder); + llvm::Type* gasCheckArgs[] = {Type::Gas->getPointerTo(), Type::Gas, Type::BytePtr}; + m_gasCheckFunc = llvm::Function::Create(llvm::FunctionType::get(Type::Void, gasCheckArgs, false), llvm::Function::PrivateLinkage, "gas.check", getModule()); + m_gasCheckFunc->setDoesNotThrow(); + m_gasCheckFunc->setDoesNotCapture(1); auto checkBB = llvm::BasicBlock::Create(_builder.getContext(), "Check", m_gasCheckFunc); - auto outOfGasBB = llvm::BasicBlock::Create(_builder.getContext(), "OutOfGas", m_gasCheckFunc); auto updateBB = llvm::BasicBlock::Create(_builder.getContext(), "Update", m_gasCheckFunc); + auto outOfGasBB = llvm::BasicBlock::Create(_builder.getContext(), "OutOfGas", m_gasCheckFunc); - auto rt = &m_gasCheckFunc->getArgumentList().front(); - rt->setName("rt"); - auto cost = rt->getNextNode(); + auto gasPtr = &m_gasCheckFunc->getArgumentList().front(); + gasPtr->setName("gasPtr"); + auto cost = gasPtr->getNextNode(); cost->setName("cost"); + auto jmpBuf = cost->getNextNode(); + jmpBuf->setName("jmpBuf"); + InsertPointGuard guard(m_builder); m_builder.SetInsertPoint(checkBB); - auto gas = m_runtimeManager.getGas(); - gas = m_builder.CreateNSWSub(gas, cost, "gasUpdated"); - auto isOutOfGas = m_builder.CreateICmpSLT(gas, m_builder.getInt64(0), "isOutOfGas"); // gas < 0, with gas == 0 we can still do 0 cost instructions - m_builder.CreateCondBr(isOutOfGas, outOfGasBB, updateBB); - - m_builder.SetInsertPoint(outOfGasBB); - m_runtimeManager.abort(); - m_builder.CreateUnreachable(); + auto gas = m_builder.CreateLoad(gasPtr, "gas"); + auto gasUpdated = m_builder.CreateNSWSub(gas, cost, "gasUpdated"); + auto gasOk = m_builder.CreateICmpSGE(gasUpdated, m_builder.getInt64(0), "gasOk"); // gas >= 0, with gas == 0 we can still do 0 cost instructions + m_builder.CreateCondBr(gasOk, updateBB, outOfGasBB, Type::expectTrue); m_builder.SetInsertPoint(updateBB); - m_runtimeManager.setGas(gas); + m_builder.CreateStore(gasUpdated, gasPtr); m_builder.CreateRetVoid(); + + m_builder.SetInsertPoint(outOfGasBB); + m_runtimeManager.abort(jmpBuf); + m_builder.CreateUnreachable(); } void GasMeter::count(Instruction _inst) @@ -117,13 +189,13 @@ void GasMeter::count(Instruction _inst) if (!m_checkCall) { // Create gas check call with mocked block cost at begining of current cost-block - m_checkCall = createCall(m_gasCheckFunc, {m_runtimeManager.getRuntimePtr(), llvm::UndefValue::get(Type::Gas)}); + m_checkCall = createCall(m_gasCheckFunc, {m_runtimeManager.getGasPtr(), llvm::UndefValue::get(Type::Gas), m_runtimeManager.getJmpBuf()}); } m_blockCost += getStepCost(_inst); } -void GasMeter::count(llvm::Value* _cost) +void GasMeter::count(llvm::Value* _cost, llvm::Value* _jmpBuf, llvm::Value* _gasPtr) { if (_cost->getType() == Type::Word) { @@ -134,7 +206,7 @@ void GasMeter::count(llvm::Value* _cost) } assert(_cost->getType() == Type::Gas); - createCall(m_gasCheckFunc, {m_runtimeManager.getRuntimePtr(), _cost}); + createCall(m_gasCheckFunc, {_gasPtr ? _gasPtr : m_runtimeManager.getGasPtr(), _cost, _jmpBuf ? _jmpBuf : m_runtimeManager.getJmpBuf()}); } void GasMeter::countExp(llvm::Value* _exponent) @@ -145,25 +217,31 @@ void GasMeter::countExp(llvm::Value* _exponent) // OPT: Can gas update be done in exp algorithm? - auto ctlz = llvm::Intrinsic::getDeclaration(getModule(), llvm::Intrinsic::ctlz, Type::Word); - auto lz256 = m_builder.CreateCall2(ctlz, _exponent, m_builder.getInt1(false)); - auto lz = m_builder.CreateTrunc(lz256, Type::Gas, "lz"); - auto sigBits = m_builder.CreateSub(m_builder.getInt64(256), lz, "sigBits"); - auto sigBytes = m_builder.CreateUDiv(m_builder.CreateAdd(sigBits, m_builder.getInt64(7)), m_builder.getInt64(8)); - count(sigBytes); + auto t = llvm::APInt{256, 1}; + auto c = m_builder.CreateSelect(m_builder.CreateICmpUGE(_exponent, Constant::get(t)), m_builder.getInt64(1), m_builder.getInt64(0)); + for (auto i = 2; i <= 32; ++i) + { + t <<= 8; + c = m_builder.CreateSelect(m_builder.CreateICmpUGE(_exponent, Constant::get(t)), m_builder.getInt64(i), c); + } + + // FIXME: Does not work because of LLVM bug: https://llvm.org/bugs/show_bug.cgi?id=22304 +// auto ctlz = llvm::Intrinsic::getDeclaration(getModule(), llvm::Intrinsic::ctlz, Type::Word); +// auto lz256 = m_builder.CreateCall2(ctlz, _exponent, m_builder.getInt1(false)); +// auto lz = m_builder.CreateTrunc(lz256, Type::Gas, "lz"); +// auto sigBits = m_builder.CreateSub(m_builder.getInt64(256), lz, "sigBits"); +// auto sigBytes = m_builder.CreateUDiv(m_builder.CreateAdd(sigBits, m_builder.getInt64(7)), m_builder.getInt64(8)); + count(m_builder.CreateNUWMul(c, m_builder.getInt64(c_expByteGas))); } void GasMeter::countSStore(Ext& _ext, llvm::Value* _index, llvm::Value* _newValue) { auto oldValue = _ext.sload(_index); auto oldValueIsZero = m_builder.CreateICmpEQ(oldValue, Constant::get(0), "oldValueIsZero"); - auto newValueIsZero = m_builder.CreateICmpEQ(_newValue, Constant::get(0), "newValueIsZero"); - auto oldValueIsntZero = m_builder.CreateICmpNE(oldValue, Constant::get(0), "oldValueIsntZero"); auto newValueIsntZero = m_builder.CreateICmpNE(_newValue, Constant::get(0), "newValueIsntZero"); auto isInsert = m_builder.CreateAnd(oldValueIsZero, newValueIsntZero, "isInsert"); - auto isDelete = m_builder.CreateAnd(oldValueIsntZero, newValueIsZero, "isDelete"); + static_assert(c_sstoreResetGas == c_sstoreClearGas, "Update SSTORE gas cost"); auto cost = m_builder.CreateSelect(isInsert, m_builder.getInt64(c_sstoreSetGas), m_builder.getInt64(c_sstoreResetGas), "cost"); - cost = m_builder.CreateSelect(isDelete, m_builder.getInt64(0), cost, "cost"); count(cost); } @@ -171,8 +249,8 @@ void GasMeter::countLogData(llvm::Value* _dataLength) { assert(m_checkCall); assert(m_blockCost > 0); // LOGn instruction is already counted - static_assert(c_logDataGas == 1, "Log data gas cost has changed. Update GasMeter."); - count(_dataLength); + static_assert(c_logDataGas != 1, "Log data gas cost has changed. Update GasMeter."); + count(m_builder.CreateNUWMul(_dataLength, Constant::get(c_logDataGas))); // TODO: Use i64 } void GasMeter::countSha3Data(llvm::Value* _dataLength) @@ -213,16 +291,16 @@ void GasMeter::commitCostBlock() assert(m_blockCost == 0); } -void GasMeter::countMemory(llvm::Value* _additionalMemoryInWords) +void GasMeter::countMemory(llvm::Value* _additionalMemoryInWords, llvm::Value* _jmpBuf, llvm::Value* _gasPtr) { - static_assert(c_memoryGas == 1, "Memory gas cost has changed. Update GasMeter."); - count(_additionalMemoryInWords); + static_assert(c_memoryGas != 1, "Memory gas cost has changed. Update GasMeter."); + count(_additionalMemoryInWords, _jmpBuf, _gasPtr); } void GasMeter::countCopy(llvm::Value* _copyWords) { - static_assert(c_copyGas == 1, "Copy gas cost has changed. Update GasMeter."); - count(_copyWords); + static_assert(c_copyGas != 1, "Copy gas cost has changed. Update GasMeter."); + count(m_builder.CreateNUWMul(_copyWords, m_builder.getInt64(c_copyGas))); } } diff --git a/evmjit/libevmjit/GasMeter.h b/evmjit/libevmjit/GasMeter.h index 4056cd64d..aecc07315 100644 --- a/evmjit/libevmjit/GasMeter.h +++ b/evmjit/libevmjit/GasMeter.h @@ -20,7 +20,7 @@ public: void count(Instruction _inst); /// Count additional cost - void count(llvm::Value* _cost); + void count(llvm::Value* _cost, llvm::Value* _jmpBuf = nullptr, llvm::Value* _gasPtr = nullptr); /// Calculate & count gas cost for SSTORE instruction void countSStore(class Ext& _ext, llvm::Value* _index, llvm::Value* _newValue); @@ -41,7 +41,7 @@ public: void giveBack(llvm::Value* _gas); /// Generate code that checks the cost of additional memory used by program - void countMemory(llvm::Value* _additionalMemoryInWords); + void countMemory(llvm::Value* _additionalMemoryInWords, llvm::Value* _jmpBuf, llvm::Value* _gasPtr); /// Count addional gas cost for memory copy void countCopy(llvm::Value* _copyWords); diff --git a/evmjit/libevmjit/Memory.cpp b/evmjit/libevmjit/Memory.cpp index 647c5f26a..79a82a8ae 100644 --- a/evmjit/libevmjit/Memory.cpp +++ b/evmjit/libevmjit/Memory.cpp @@ -19,6 +19,7 @@ namespace jit Memory::Memory(RuntimeManager& _runtimeManager, GasMeter& _gasMeter): RuntimeHelper(_runtimeManager), // TODO: RuntimeHelper not needed + m_memory{getBuilder(), _runtimeManager.getMem()}, m_gasMeter(_gasMeter) {} @@ -27,20 +28,20 @@ llvm::Function* Memory::getRequireFunc() auto& func = m_require; if (!func) { - llvm::Type* argTypes[] = {Type::RuntimePtr, Type::Word, Type::Word}; + llvm::Type* argTypes[] = {Array::getType()->getPointerTo(), Type::Word, Type::Word, Type::BytePtr, Type::GasPtr}; func = llvm::Function::Create(llvm::FunctionType::get(Type::Void, argTypes, false), llvm::Function::PrivateLinkage, "mem.require", getModule()); - auto rt = func->arg_begin(); - rt->setName("rt"); - auto offset = rt->getNextNode(); - offset->setName("offset"); - auto size = offset->getNextNode(); - size->setName("size"); - - llvm::Type* resizeArgs[] = {Type::RuntimePtr, Type::WordPtr}; - auto resize = llvm::Function::Create(llvm::FunctionType::get(Type::BytePtr, resizeArgs, false), llvm::Function::ExternalLinkage, "mem_resize", getModule()); - llvm::AttrBuilder attrBuilder; - attrBuilder.addAttribute(llvm::Attribute::NoAlias).addAttribute(llvm::Attribute::NoCapture).addAttribute(llvm::Attribute::NonNull).addAttribute(llvm::Attribute::ReadOnly); - resize->setAttributes(llvm::AttributeSet::get(resize->getContext(), 1, attrBuilder)); + func->setDoesNotThrow(); + + auto mem = &func->getArgumentList().front(); + mem->setName("mem"); + auto blkOffset = mem->getNextNode(); + blkOffset->setName("blkOffset"); + auto blkSize = blkOffset->getNextNode(); + blkSize->setName("blkSize"); + auto jmpBuf = blkSize->getNextNode(); + jmpBuf->setName("jmpBuf"); + auto gas = jmpBuf->getNextNode(); + gas->setName("gas"); auto preBB = llvm::BasicBlock::Create(func->getContext(), "Pre", func); auto checkBB = llvm::BasicBlock::Create(func->getContext(), "Check", func); @@ -51,40 +52,38 @@ llvm::Function* Memory::getRequireFunc() // BB "Pre": Ignore checks with size 0 m_builder.SetInsertPoint(preBB); - auto sizeIsZero = m_builder.CreateICmpEQ(size, Constant::get(0)); - m_builder.CreateCondBr(sizeIsZero, returnBB, checkBB); + m_builder.CreateCondBr(m_builder.CreateICmpNE(blkSize, Constant::get(0)), checkBB, returnBB, Type::expectTrue); // BB "Check" m_builder.SetInsertPoint(checkBB); - auto uaddWO = llvm::Intrinsic::getDeclaration(getModule(), llvm::Intrinsic::uadd_with_overflow, Type::Word); - auto uaddRes = m_builder.CreateCall2(uaddWO, offset, size, "res"); - auto sizeRequired = m_builder.CreateExtractValue(uaddRes, 0, "sizeReq"); - auto overflow1 = m_builder.CreateExtractValue(uaddRes, 1, "overflow1"); - auto rtPtr = getRuntimeManager().getRuntimePtr(); - auto sizePtr = m_builder.CreateStructGEP(rtPtr, 4); - auto currSize = m_builder.CreateLoad(sizePtr, "currSize"); - auto tooSmall = m_builder.CreateICmpULE(currSize, sizeRequired, "tooSmall"); - auto resizeNeeded = m_builder.CreateOr(tooSmall, overflow1, "resizeNeeded"); - m_builder.CreateCondBr(resizeNeeded, resizeBB, returnBB); // OPT branch weights? + static const auto c_inputMax = uint64_t(1) << 33; // max value of blkSize and blkOffset that will not result in integer overflow in calculations below + auto blkOffsetOk = m_builder.CreateICmpULE(blkOffset, Constant::get(c_inputMax), "blkOffsetOk"); + auto blkO = m_builder.CreateSelect(blkOffsetOk, m_builder.CreateTrunc(blkOffset, Type::Size), m_builder.getInt64(c_inputMax), "bklO"); + auto blkSizeOk = m_builder.CreateICmpULE(blkSize, Constant::get(c_inputMax), "blkSizeOk"); + auto blkS = m_builder.CreateSelect(blkSizeOk, m_builder.CreateTrunc(blkSize, Type::Size), m_builder.getInt64(c_inputMax), "bklS"); + + auto sizeReq0 = m_builder.CreateNUWAdd(blkO, blkS, "sizeReq0"); + auto sizeReq = m_builder.CreateAnd(m_builder.CreateNUWAdd(sizeReq0, m_builder.getInt64(31)), uint64_t(-1) << 5, "sizeReq"); // s' = ((s0 + 31) / 32) * 32 + auto sizeCur = m_memory.size(mem); + auto sizeOk = m_builder.CreateICmpULE(sizeReq, sizeCur, "sizeOk"); + + m_builder.CreateCondBr(sizeOk, returnBB, resizeBB, Type::expectTrue); // BB "Resize" m_builder.SetInsertPoint(resizeBB); // Check gas first - uaddRes = m_builder.CreateCall2(uaddWO, sizeRequired, Constant::get(31), "res"); - auto wordsRequired = m_builder.CreateExtractValue(uaddRes, 0); - auto overflow2 = m_builder.CreateExtractValue(uaddRes, 1, "overflow2"); - auto overflow = m_builder.CreateOr(overflow1, overflow2, "overflow"); - wordsRequired = m_builder.CreateSelect(overflow, Constant::get(-1), wordsRequired); - wordsRequired = m_builder.CreateUDiv(wordsRequired, Constant::get(32), "wordsReq"); - sizeRequired = m_builder.CreateMul(wordsRequired, Constant::get(32), "roundedSizeReq"); - auto words = m_builder.CreateUDiv(currSize, Constant::get(32), "words"); // size is always 32*k - auto newWords = m_builder.CreateSub(wordsRequired, words, "addtionalWords"); - m_gasMeter.countMemory(newWords); + auto w1 = m_builder.CreateLShr(sizeReq, 5); + auto w1s = m_builder.CreateNUWMul(w1, w1); + auto c1 = m_builder.CreateAdd(m_builder.CreateNUWMul(w1, m_builder.getInt64(3)), m_builder.CreateLShr(w1s, 9)); + auto w0 = m_builder.CreateLShr(sizeCur, 5); + auto w0s = m_builder.CreateNUWMul(w0, w0); + auto c0 = m_builder.CreateAdd(m_builder.CreateNUWMul(w0, m_builder.getInt64(3)), m_builder.CreateLShr(w0s, 9)); + auto cc = m_builder.CreateNUWSub(c1, c0); + auto costOk = m_builder.CreateAnd(blkOffsetOk, blkSizeOk, "costOk"); + auto c = m_builder.CreateSelect(costOk, cc, m_builder.getInt64(std::numeric_limits::max()), "c"); + m_gasMeter.count(c, jmpBuf, gas); // Resize - m_builder.CreateStore(sizeRequired, sizePtr); - auto newData = m_builder.CreateCall2(resize, rt, sizePtr, "newData"); - auto dataPtr = m_builder.CreateStructGEP(rtPtr, 3); - m_builder.CreateStore(newData, dataPtr); + m_memory.extend(mem, sizeReq); m_builder.CreateBr(returnBB); // BB "Return" @@ -94,12 +93,12 @@ llvm::Function* Memory::getRequireFunc() return func; } -llvm::Function* Memory::createFunc(bool _isStore, llvm::Type* _valueType, GasMeter&) +llvm::Function* Memory::createFunc(bool _isStore, llvm::Type* _valueType) { auto isWord = _valueType == Type::Word; - llvm::Type* storeArgs[] = {Type::RuntimePtr, Type::Word, _valueType}; - llvm::Type* loadArgs[] = {Type::RuntimePtr, Type::Word}; + llvm::Type* storeArgs[] = {Array::getType()->getPointerTo(), Type::Word, _valueType}; + llvm::Type* loadArgs[] = {Array::getType()->getPointerTo(), Type::Word}; auto name = _isStore ? isWord ? "mstore" : "mstore8" : "mload"; auto funcType = _isStore ? llvm::FunctionType::get(Type::Void, storeArgs, false) : llvm::FunctionType::get(Type::Word, loadArgs, false); auto func = llvm::Function::Create(funcType, llvm::Function::PrivateLinkage, name, getModule()); @@ -107,28 +106,25 @@ llvm::Function* Memory::createFunc(bool _isStore, llvm::Type* _valueType, GasMet InsertPointGuard guard(m_builder); // Restores insert point at function exit m_builder.SetInsertPoint(llvm::BasicBlock::Create(func->getContext(), {}, func)); - auto rt = func->arg_begin(); - rt->setName("rt"); - auto index = rt->getNextNode(); + auto mem = &func->getArgumentList().front(); + mem->setName("mem"); + auto index = mem->getNextNode(); index->setName("index"); - auto valueSize = _valueType->getPrimitiveSizeInBits() / 8; - this->require(index, Constant::get(valueSize)); - auto ptr = getBytePtr(index); - if (isWord) - ptr = m_builder.CreateBitCast(ptr, Type::WordPtr, "wordPtr"); if (_isStore) { - llvm::Value* value = index->getNextNode(); - value->setName("value"); - if (isWord) - value = Endianness::toBE(m_builder, value); - m_builder.CreateStore(value, ptr); + auto valueArg = index->getNextNode(); + valueArg->setName("value"); + auto value = isWord ? Endianness::toBE(m_builder, valueArg) : valueArg; + auto memPtr = m_memory.getPtr(mem, m_builder.CreateTrunc(index, Type::Size)); + auto valuePtr = m_builder.CreateBitCast(memPtr, _valueType->getPointerTo(), "valuePtr"); + m_builder.CreateStore(value, valuePtr); m_builder.CreateRetVoid(); } else { - llvm::Value* ret = m_builder.CreateLoad(ptr); + auto memPtr = m_memory.getPtr(mem, m_builder.CreateTrunc(index, Type::Size)); + llvm::Value* ret = m_builder.CreateLoad(memPtr); ret = Endianness::toNative(m_builder, ret); m_builder.CreateRet(ret); } @@ -140,7 +136,7 @@ llvm::Function* Memory::getLoadWordFunc() { auto& func = m_loadWord; if (!func) - func = createFunc(false, Type::Word, m_gasMeter); + func = createFunc(false, Type::Word); return func; } @@ -148,7 +144,7 @@ llvm::Function* Memory::getStoreWordFunc() { auto& func = m_storeWord; if (!func) - func = createFunc(true, Type::Word, m_gasMeter); + func = createFunc(true, Type::Word); return func; } @@ -156,39 +152,41 @@ llvm::Function* Memory::getStoreByteFunc() { auto& func = m_storeByte; if (!func) - func = createFunc(true, Type::Byte, m_gasMeter); + func = createFunc(true, Type::Byte); return func; } llvm::Value* Memory::loadWord(llvm::Value* _addr) { - return createCall(getLoadWordFunc(), {getRuntimeManager().getRuntimePtr(), _addr}); + require(_addr, Constant::get(Type::Word->getPrimitiveSizeInBits() / 8)); + return createCall(getLoadWordFunc(), {getRuntimeManager().getMem(), _addr}); } void Memory::storeWord(llvm::Value* _addr, llvm::Value* _word) { - createCall(getStoreWordFunc(), {getRuntimeManager().getRuntimePtr(), _addr, _word}); + require(_addr, Constant::get(Type::Word->getPrimitiveSizeInBits() / 8)); + createCall(getStoreWordFunc(), {getRuntimeManager().getMem(), _addr, _word}); } void Memory::storeByte(llvm::Value* _addr, llvm::Value* _word) { + require(_addr, Constant::get(Type::Byte->getPrimitiveSizeInBits() / 8)); auto byte = m_builder.CreateTrunc(_word, Type::Byte, "byte"); - createCall(getStoreByteFunc(), {getRuntimeManager().getRuntimePtr(), _addr, byte}); + createCall(getStoreByteFunc(), {getRuntimeManager().getMem(), _addr, byte}); } llvm::Value* Memory::getData() { - auto rtPtr = getRuntimeManager().getRuntimePtr(); - auto dataPtr = m_builder.CreateStructGEP(rtPtr, 3); - return m_builder.CreateLoad(dataPtr, "data"); + auto memPtr = m_builder.CreateBitCast(getRuntimeManager().getMem(), Type::BytePtr->getPointerTo()); + auto data = m_builder.CreateLoad(memPtr, "data"); + assert(data->getType() == Type::BytePtr); + return data; } llvm::Value* Memory::getSize() { - auto rtPtr = getRuntimeManager().getRuntimePtr(); - auto sizePtr = m_builder.CreateStructGEP(rtPtr, 4); - return m_builder.CreateLoad(sizePtr, "size"); + return m_builder.CreateZExt(m_memory.size(), Type::Word, "msize"); // TODO: Allow placing i64 on stack } llvm::Value* Memory::getBytePtr(llvm::Value* _index) @@ -204,7 +202,7 @@ void Memory::require(llvm::Value* _offset, llvm::Value* _size) if (!constant->getValue()) return; } - createCall(getRequireFunc(), {getRuntimeManager().getRuntimePtr(), _offset, _size}); + createCall(getRequireFunc(), {getRuntimeManager().getMem(), _offset, _size, getRuntimeManager().getJmpBuf(), getRuntimeManager().getGasPtr()}); } void Memory::copyBytes(llvm::Value* _srcPtr, llvm::Value* _srcSize, llvm::Value* _srcIdx, @@ -233,28 +231,18 @@ void Memory::copyBytes(llvm::Value* _srcPtr, llvm::Value* _srcSize, llvm::Value* auto dataLeftSize = m_builder.CreateNUWSub(size64, idx64); auto outOfBound = m_builder.CreateICmpUGT(reqBytes, dataLeftSize); auto bytesToCopyInner = m_builder.CreateSelect(outOfBound, dataLeftSize, reqBytes); - auto bytesToCopy = m_builder.CreateSelect(isOutsideData, m_builder.getInt64(0), bytesToCopyInner); + auto bytesToCopy = m_builder.CreateSelect(isOutsideData, m_builder.getInt64(0), bytesToCopyInner, "bytesToCopy"); + auto bytesToZero = m_builder.CreateNUWSub(reqBytes, bytesToCopy, "bytesToZero"); auto src = m_builder.CreateGEP(_srcPtr, idx64, "src"); auto dstIdx = m_builder.CreateTrunc(_destMemIdx, Type::Size, "dstIdx"); // Never allow memory index be a type bigger than i64 - auto dst = m_builder.CreateGEP(getData(), dstIdx, "dst"); + auto padIdx = m_builder.CreateNUWAdd(dstIdx, bytesToCopy, "padIdx"); + auto dst = m_memory.getPtr(getRuntimeManager().getMem(), dstIdx); + auto pad = m_memory.getPtr(getRuntimeManager().getMem(), padIdx); m_builder.CreateMemCpy(dst, src, bytesToCopy, 0); + m_builder.CreateMemSet(pad, m_builder.getInt8(0), bytesToZero, 0); } } } } - - -extern "C" -{ - using namespace dev::eth::jit; - - EXPORT byte* mem_resize(Runtime* _rt, i256* _size) // TODO: Use uint64 as size OR use realloc in LLVM IR - { - auto size = _size->a; // Trunc to 64-bit - auto& memory = _rt->getMemory(); - memory.resize(size); - return memory.data(); - } -} diff --git a/evmjit/libevmjit/Memory.h b/evmjit/libevmjit/Memory.h index e8edce735..beb535226 100644 --- a/evmjit/libevmjit/Memory.h +++ b/evmjit/libevmjit/Memory.h @@ -1,6 +1,6 @@ #pragma once -#include "CompilerHelper.h" +#include "Array.h" namespace dev { @@ -28,9 +28,11 @@ public: void require(llvm::Value* _offset, llvm::Value* _size); private: + Array m_memory; + GasMeter& m_gasMeter; - llvm::Function* createFunc(bool _isStore, llvm::Type* _type, GasMeter& _gasMeter); + llvm::Function* createFunc(bool _isStore, llvm::Type* _type); llvm::Function* getRequireFunc(); llvm::Function* getLoadWordFunc(); diff --git a/evmjit/libevmjit/Optimizer.cpp b/evmjit/libevmjit/Optimizer.cpp new file mode 100644 index 000000000..84a7c3a6a --- /dev/null +++ b/evmjit/libevmjit/Optimizer.cpp @@ -0,0 +1,29 @@ +#include "Optimizer.h" + +#include "preprocessor/llvm_includes_start.h" +#include +#include +#include +#include "preprocessor/llvm_includes_end.h" + +namespace dev +{ +namespace eth +{ +namespace jit +{ + +bool optimize(llvm::Module& _module) +{ + auto pm = llvm::PassManager{}; + //pm.add(llvm::createFunctionInliningPass(2, 2)); // Produces invalid IR + pm.add(llvm::createCFGSimplificationPass()); + //pm.add(llvm::createInstructionCombiningPass()); // Produces invalid runtime results + pm.add(llvm::createAggressiveDCEPass()); + pm.add(llvm::createLowerSwitchPass()); + return pm.run(_module); +} + +} +} +} diff --git a/evmjit/libevmjit/Optimizer.h b/evmjit/libevmjit/Optimizer.h new file mode 100644 index 000000000..4a3147a7f --- /dev/null +++ b/evmjit/libevmjit/Optimizer.h @@ -0,0 +1,19 @@ +#pragma once + +namespace llvm +{ + class Module; +} + +namespace dev +{ +namespace eth +{ +namespace jit +{ + +bool optimize(llvm::Module& _module); + +} +} +} diff --git a/evmjit/libevmjit/Runtime.cpp b/evmjit/libevmjit/Runtime.cpp index 69937368c..7e9a7d52e 100644 --- a/evmjit/libevmjit/Runtime.cpp +++ b/evmjit/libevmjit/Runtime.cpp @@ -9,20 +9,29 @@ namespace eth namespace jit { -Runtime::Runtime(RuntimeData* _data, Env* _env) : - m_data(*_data), - m_env(*_env) -{} +void Runtime::init(RuntimeData* _data, Env* _env) +{ + m_data = _data; + m_env = _env; +} + +extern "C" void ext_free(void* _data) noexcept; + +Runtime::~Runtime() +{ + if (m_memData) + ext_free(m_memData); // Use helper free to check memory leaks +} bytes_ref Runtime::getReturnData() const { - auto data = m_data.callData; - auto size = static_cast(m_data.callDataSize); + auto data = m_data->callData; + auto size = static_cast(m_data->callDataSize); - if (data < m_memory.data() || data >= m_memory.data() + m_memory.size() || size == 0) + if (data < m_memData || data >= m_memData + m_memSize || size == 0) { assert(size == 0); // data can be an invalid pointer only if size is 0 - m_data.callData = nullptr; + m_data->callData = nullptr; return {}; } diff --git a/evmjit/libevmjit/Runtime.h b/evmjit/libevmjit/Runtime.h index 82be4a0c8..895128a59 100644 --- a/evmjit/libevmjit/Runtime.h +++ b/evmjit/libevmjit/Runtime.h @@ -9,30 +9,20 @@ namespace eth namespace jit { -using StackImpl = std::vector; -using MemoryImpl = bytes; - class Runtime { public: - Runtime(RuntimeData* _data, Env* _env); - - Runtime(const Runtime&) = delete; - Runtime& operator=(const Runtime&) = delete; - - StackImpl& getStack() { return m_stack; } - MemoryImpl& getMemory() { return m_memory; } + void init(RuntimeData* _data, Env* _env); + EXPORT ~Runtime(); bytes_ref getReturnData() const; private: - RuntimeData& m_data; ///< Pointer to data. Expected by compiled contract. - Env& m_env; ///< Pointer to environment proxy. Expected by compiled contract. - void* m_currJmpBuf = nullptr; ///< Pointer to jump buffer. Expected by compiled contract. - byte* m_memoryData = nullptr; - i256 m_memorySize; - StackImpl m_stack; - MemoryImpl m_memory; + RuntimeData* m_data = nullptr; ///< Pointer to data. Expected by compiled contract. + Env* m_env = nullptr; ///< Pointer to environment proxy. Expected by compiled contract. + byte* m_memData = nullptr; + uint64_t m_memSize = 0; + uint64_t m_memCap = 0; }; } diff --git a/evmjit/libevmjit/RuntimeManager.cpp b/evmjit/libevmjit/RuntimeManager.cpp index 0b5762fa2..dc7bc24a3 100644 --- a/evmjit/libevmjit/RuntimeManager.cpp +++ b/evmjit/libevmjit/RuntimeManager.cpp @@ -4,6 +4,9 @@ #include #include "preprocessor/llvm_includes_end.h" +#include "Stack.h" +#include "Utils.h" + namespace dev { namespace eth @@ -48,9 +51,7 @@ llvm::StructType* RuntimeManager::getRuntimeType() { Type::RuntimeDataPtr, // data Type::EnvPtr, // Env* - Type::BytePtr, // jmpbuf - Type::BytePtr, // memory data - Type::Word, // memory size + Array::getType() // memory }; type = llvm::StructType::create(elems, "Runtime"); } @@ -77,30 +78,71 @@ 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"; } } } -RuntimeManager::RuntimeManager(llvm::IRBuilder<>& _builder, llvm::Value* _jmpBuf, code_iterator _codeBegin, code_iterator _codeEnd): +RuntimeManager::RuntimeManager(llvm::IRBuilder<>& _builder, code_iterator _codeBegin, code_iterator _codeEnd): CompilerHelper(_builder), - m_jmpBuf(_jmpBuf), m_codeBegin(_codeBegin), m_codeEnd(_codeEnd) { m_longjmp = llvm::Intrinsic::getDeclaration(getModule(), llvm::Intrinsic::eh_sjlj_longjmp); - // save jmpBuf to be used in helper functions - auto ptr = m_builder.CreateStructGEP(getRuntimePtr(), 2); - m_builder.CreateStore(m_jmpBuf, ptr, "jmpBufExt"); - // Unpack data auto rtPtr = getRuntimePtr(); m_dataPtr = m_builder.CreateLoad(m_builder.CreateStructGEP(rtPtr, 0), "data"); assert(m_dataPtr->getType() == Type::RuntimeDataPtr); + m_gasPtr = m_builder.CreateStructGEP(m_dataPtr, 0, "gas"); + assert(m_gasPtr->getType() == Type::Gas->getPointerTo()); + m_memPtr = m_builder.CreateStructGEP(rtPtr, 2, "mem"); + 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() @@ -150,7 +192,7 @@ void RuntimeManager::set(RuntimeData::Index _index, llvm::Value* _value) void RuntimeManager::registerReturnData(llvm::Value* _offset, llvm::Value* _size) { - auto memPtr = getBuilder().CreateStructGEP(getRuntimePtr(), 3); + auto memPtr = m_builder.CreateBitCast(getMem(), Type::BytePtr->getPointerTo()); auto mem = getBuilder().CreateLoad(memPtr, "memory"); auto idx = m_builder.CreateTrunc(_offset, Type::Size, "idx"); // Never allow memory index be a type bigger than i64 // TODO: Report bug & fix to LLVM auto returnDataPtr = getBuilder().CreateGEP(mem, idx); @@ -165,6 +207,14 @@ void RuntimeManager::registerSuicide(llvm::Value* _balanceAddress) set(RuntimeData::SuicideDestAddress, _balanceAddress); } +void RuntimeManager::exit(ReturnCode _returnCode) +{ + if (m_stack) + m_stack->free(); + + m_builder.CreateRet(Constant::get(_returnCode)); +} + void RuntimeManager::abort(llvm::Value* _jmpBuf) { auto longjmp = llvm::Intrinsic::getDeclaration(getModule(), llvm::Intrinsic::eh_sjlj_longjmp); @@ -213,12 +263,6 @@ llvm::Value* RuntimeManager::getCallDataSize() return getBuilder().CreateZExt(value, Type::Word); } -llvm::Value* RuntimeManager::getJmpBufExt() -{ - auto ptr = getBuilder().CreateStructGEP(getRuntimePtr(), 2); - return getBuilder().CreateLoad(ptr, "jmpBufExt"); -} - llvm::Value* RuntimeManager::getGas() { auto gas = get(RuntimeData::Gas); @@ -228,7 +272,14 @@ llvm::Value* RuntimeManager::getGas() llvm::Value* RuntimeManager::getGasPtr() { - return getPtr(RuntimeData::Gas); + assert(getMainFunction()); + return m_gasPtr; +} + +llvm::Value* RuntimeManager::getMem() +{ + assert(getMainFunction()); + return m_memPtr; } void RuntimeManager::setGas(llvm::Value* _gas) diff --git a/evmjit/libevmjit/RuntimeManager.h b/evmjit/libevmjit/RuntimeManager.h index 30c69ec88..c430a1098 100644 --- a/evmjit/libevmjit/RuntimeManager.h +++ b/evmjit/libevmjit/RuntimeManager.h @@ -11,11 +11,12 @@ namespace eth { namespace jit { +class Stack; class RuntimeManager: public CompilerHelper { public: - RuntimeManager(llvm::IRBuilder<>& _builder, llvm::Value* _jmpBuf, code_iterator _codeBegin, code_iterator _codeEnd); + RuntimeManager(llvm::IRBuilder<>& _builder, code_iterator _codeBegin, code_iterator _codeEnd); llvm::Value* getRuntimePtr(); llvm::Value* getDataPtr(); @@ -32,27 +33,41 @@ public: llvm::Value* getJmpBuf() { return m_jmpBuf; } void setGas(llvm::Value* _gas); - void registerReturnData(llvm::Value* _index, llvm::Value* _size); + llvm::Value* getMem(); + + void registerReturnData(llvm::Value* _index, llvm::Value* _size); // TODO: Move to Memory. void registerSuicide(llvm::Value* _balanceAddress); + void exit(ReturnCode _returnCode); + void abort(llvm::Value* _jmpBuf); - void abort() { abort(getJmpBufExt()); } + + void setStack(Stack& _stack) { m_stack = &_stack; } + void setJmpBuf(llvm::Value* _jmpBuf) { m_jmpBuf = _jmpBuf; } 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); - llvm::Value* getJmpBufExt(); llvm::Function* m_longjmp = nullptr; - llvm::Value* const m_jmpBuf; + llvm::Value* m_jmpBuf = nullptr; llvm::Value* m_dataPtr = nullptr; + llvm::Value* m_gasPtr = nullptr; + 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 = {}; + + Stack* m_stack = nullptr; }; } diff --git a/evmjit/libevmjit/Stack.cpp b/evmjit/libevmjit/Stack.cpp index 81a954991..3a686c766 100644 --- a/evmjit/libevmjit/Stack.cpp +++ b/evmjit/libevmjit/Stack.cpp @@ -6,6 +6,9 @@ #include "RuntimeManager.h" #include "Runtime.h" +#include "Utils.h" + +#include // DEBUG only namespace dev { @@ -16,20 +19,62 @@ namespace jit Stack::Stack(llvm::IRBuilder<>& _builder, RuntimeManager& _runtimeManager): CompilerHelper(_builder), - m_runtimeManager(_runtimeManager) + m_runtimeManager(_runtimeManager), + m_stack(_builder, "stack") +{} + +llvm::Function* Stack::getPushFunc() { - m_arg = m_builder.CreateAlloca(Type::Word, nullptr, "stack.arg"); + auto& func = m_push; + if (!func) + { + llvm::Type* argTypes[] = {Type::RuntimePtr, Type::Word}; + func = llvm::Function::Create(llvm::FunctionType::get(Type::Void, argTypes, false), llvm::Function::ExternalLinkage, "stack.push", getModule()); + llvm::Type* extArgTypes[] = {Type::RuntimePtr, Type::WordPtr}; + auto extPushFunc = llvm::Function::Create(llvm::FunctionType::get(Type::Void, extArgTypes, false), llvm::Function::ExternalLinkage, "stack_push", getModule()); + + auto rt = &func->getArgumentList().front(); + rt->setName("rt"); + auto value = rt->getNextNode(); + value->setName("value"); - using namespace llvm; - using Linkage = GlobalValue::LinkageTypes; + InsertPointGuard guard{m_builder}; + auto entryBB = llvm::BasicBlock::Create(m_builder.getContext(), {}, func); + m_builder.SetInsertPoint(entryBB); + auto a = m_builder.CreateAlloca(Type::Word); + m_builder.CreateStore(value, a); + createCall(extPushFunc, {rt, a}); + m_builder.CreateRetVoid(); + } + return func; +} - auto module = getModule(); +llvm::Function* Stack::getSetFunc() +{ + auto& func = m_set; + if (!func) + { + llvm::Type* argTypes[] = {Type::RuntimePtr, Type::Size, Type::Word}; + func = llvm::Function::Create(llvm::FunctionType::get(Type::Void, argTypes, false), llvm::Function::ExternalLinkage, "stack.set", getModule()); + llvm::Type* extArgTypes[] = {Type::RuntimePtr, Type::Size, Type::WordPtr}; + auto extSetFunc = llvm::Function::Create(llvm::FunctionType::get(Type::Void, extArgTypes, false), llvm::Function::ExternalLinkage, "stack_set", getModule()); - llvm::Type* pushArgTypes[] = {Type::RuntimePtr, Type::WordPtr}; - m_push = Function::Create(FunctionType::get(Type::Void, pushArgTypes, false), Linkage::ExternalLinkage, "stack_push", module); + auto rt = &func->getArgumentList().front(); + rt->setName("rt"); + auto index = rt->getNextNode(); + index->setName("index"); + auto value = index->getNextNode(); + value->setName("value"); - llvm::Type* getSetArgTypes[] = {Type::RuntimePtr, Type::Size, Type::WordPtr}; - m_set = Function::Create(FunctionType::get(Type::Void, getSetArgTypes, false), Linkage::ExternalLinkage, "stack_set", module); + InsertPointGuard guard{m_builder}; + auto entryBB = llvm::BasicBlock::Create(m_builder.getContext(), {}, func); + m_builder.SetInsertPoint(entryBB); + auto a = m_builder.CreateAlloca(Type::Word); + m_builder.CreateStore(value, a); + createCall(extSetFunc, {rt, index, a}); + m_builder.CreateRetVoid(); + } + return func; } llvm::Function* Stack::getPopFunc() @@ -73,16 +118,14 @@ llvm::Function* Stack::getGetFunc() auto& func = m_get; if (!func) { - llvm::Type* argTypes[] = {Type::RuntimePtr, Type::Size, Type::BytePtr}; - func = llvm::Function::Create(llvm::FunctionType::get(Type::WordPtr, argTypes, false), llvm::Function::ExternalLinkage, "stack.get", getModule()); - llvm::Type* extArgTypes[] = {Type::RuntimePtr, Type::Size}; - auto extGetFunc = llvm::Function::Create(llvm::FunctionType::get(Type::WordPtr, extArgTypes, false), llvm::Function::ExternalLinkage, "stack_get", getModule()); + llvm::Type* argTypes[] = {Type::Size, Type::Size, Type::BytePtr}; + func = llvm::Function::Create(llvm::FunctionType::get(Type::Void, argTypes, false), llvm::Function::ExternalLinkage, "stack.require", getModule()); - auto rt = &func->getArgumentList().front(); - rt->setName("rt"); - auto index = rt->getNextNode(); + auto index = &func->getArgumentList().front(); index->setName("index"); - auto jmpBuf = index->getNextNode(); + auto size = index->getNextNode(); + size->setName("size"); + auto jmpBuf = size->getNextNode(); jmpBuf->setName("jmpBuf"); InsertPointGuard guard{m_builder}; @@ -91,46 +134,44 @@ llvm::Function* Stack::getGetFunc() auto returnBB = llvm::BasicBlock::Create(m_builder.getContext(), "Return", func); m_builder.SetInsertPoint(entryBB); - auto valuePtr = createCall(extGetFunc, {rt, index}); - auto ok = m_builder.CreateICmpNE(valuePtr, llvm::ConstantPointerNull::get(Type::WordPtr)); - m_builder.CreateCondBr(ok, returnBB, underflowBB); //TODO: Add branch weight + auto underflow = m_builder.CreateICmpUGE(index, size, "underflow"); + m_builder.CreateCondBr(underflow, underflowBB, returnBB); m_builder.SetInsertPoint(underflowBB); m_runtimeManager.abort(jmpBuf); m_builder.CreateUnreachable(); m_builder.SetInsertPoint(returnBB); - m_builder.CreateRet(valuePtr); + m_builder.CreateRetVoid(); } return func; } llvm::Value* Stack::get(size_t _index) { - auto valuePtr = createCall(getGetFunc(), {m_runtimeManager.getRuntimePtr(), m_builder.getInt64(_index), m_runtimeManager.getJmpBuf()}); - return m_builder.CreateLoad(valuePtr); + createCall(getGetFunc(), {m_builder.getInt64(_index), m_stack.size(), m_runtimeManager.getJmpBuf()}); + auto value = m_stack.get(m_builder.CreateSub(m_stack.size(), m_builder.getInt64(_index + 1))); + //return m_builder.CreateLoad(valuePtr); + return value; } void Stack::set(size_t _index, llvm::Value* _value) { - m_builder.CreateStore(_value, m_arg); - m_builder.CreateCall3(m_set, m_runtimeManager.getRuntimePtr(), llvm::ConstantInt::get(Type::Size, _index, false), m_arg); + m_stack.set(m_builder.CreateSub(m_stack.size(), m_builder.getInt64(_index + 1)), _value); } void Stack::pop(size_t _count) { - createCall(getPopFunc(), {m_runtimeManager.getRuntimePtr(), m_builder.getInt64(_count), m_runtimeManager.getJmpBuf()}); + // FIXME: Pop does not check for stack underflow but looks like not needed + // We should place stack.require() check and begining of every BB + m_stack.pop(m_builder.getInt64(_count)); } void Stack::push(llvm::Value* _value) { - m_builder.CreateStore(_value, m_arg); - m_builder.CreateCall2(m_push, m_runtimeManager.getRuntimePtr(), m_arg); + m_stack.push(_value); } - -size_t Stack::maxStackSize = 0; - } } } @@ -139,41 +180,6 @@ extern "C" { using namespace dev::eth::jit; - EXPORT bool stack_pop(Runtime* _rt, uint64_t _count) - { - auto& stack = _rt->getStack(); - if (stack.size() < _count) - return false; - - stack.erase(stack.end() - _count, stack.end()); - return true; - } - - EXPORT void stack_push(Runtime* _rt, i256 const* _word) - { - auto& stack = _rt->getStack(); - stack.push_back(*_word); - - if (stack.size() > Stack::maxStackSize) - Stack::maxStackSize = stack.size(); - } - - EXPORT i256* stack_get(Runtime* _rt, uint64_t _index) - { - auto& stack = _rt->getStack(); - return _index < stack.size() ? &*(stack.rbegin() + _index) : nullptr; - } - - EXPORT void stack_set(Runtime* _rt, uint64_t _index, i256 const* _word) - { - auto& stack = _rt->getStack(); - assert(_index < stack.size()); - if (_index >= stack.size()) - return; - - *(stack.rbegin() + _index) = *_word; - } - EXPORT void ext_calldataload(RuntimeData* _rtData, i256* _index, byte* o_value) { // It asumes all indexes are less than 2^64 diff --git a/evmjit/libevmjit/Stack.h b/evmjit/libevmjit/Stack.h index 4b6fa374f..3c526f0d3 100644 --- a/evmjit/libevmjit/Stack.h +++ b/evmjit/libevmjit/Stack.h @@ -1,6 +1,8 @@ #pragma once -#include "CompilerHelper.h" +#include + +#include "Array.h" namespace dev { @@ -19,21 +21,22 @@ public: void set(size_t _index, llvm::Value* _value); void pop(size_t _count); void push(llvm::Value* _value); - - static size_t maxStackSize; + void free() { m_stack.free(); } private: llvm::Function* getPopFunc(); + llvm::Function* getPushFunc(); llvm::Function* getGetFunc(); + llvm::Function* getSetFunc(); RuntimeManager& m_runtimeManager; llvm::Function* m_pop = nullptr; - llvm::Function* m_push; + llvm::Function* m_push = nullptr; llvm::Function* m_get = nullptr; - llvm::Function* m_set; + llvm::Function* m_set = nullptr; - llvm::Value* m_arg; + Array m_stack; }; diff --git a/evmjit/libevmjit/Type.cpp b/evmjit/libevmjit/Type.cpp index 8e2bc13fc..00a143dea 100644 --- a/evmjit/libevmjit/Type.cpp +++ b/evmjit/libevmjit/Type.cpp @@ -1,4 +1,7 @@ #include "Type.h" + +#include + #include "RuntimeManager.h" namespace dev @@ -23,6 +26,7 @@ llvm::PointerType* Type::EnvPtr; llvm::PointerType* Type::RuntimeDataPtr; llvm::PointerType* Type::RuntimePtr; llvm::ConstantInt* Constant::gasMax; +llvm::MDNode* Type::expectTrue; void Type::init(llvm::LLVMContext& _context) { @@ -46,6 +50,8 @@ void Type::init(llvm::LLVMContext& _context) RuntimePtr = RuntimeManager::getRuntimeType()->getPointerTo(); Constant::gasMax = llvm::ConstantInt::getSigned(Type::Gas, std::numeric_limits::max()); + + expectTrue = llvm::MDBuilder{_context}.createBranchWeights(1, 0); } } diff --git a/evmjit/libevmjit/Type.h b/evmjit/libevmjit/Type.h index b8a4a09eb..ffacc5407 100644 --- a/evmjit/libevmjit/Type.h +++ b/evmjit/libevmjit/Type.h @@ -40,6 +40,9 @@ struct Type static llvm::PointerType* RuntimeDataPtr; static llvm::PointerType* RuntimePtr; + // TODO: Redesign static LLVM objects + static llvm::MDNode* expectTrue; + static void init(llvm::LLVMContext& _context); }; diff --git a/evmjit/libevmjit/Utils.cpp b/evmjit/libevmjit/Utils.cpp index bf3bf93b3..b010b1d96 100644 --- a/evmjit/libevmjit/Utils.cpp +++ b/evmjit/libevmjit/Utils.cpp @@ -1,13 +1,27 @@ #include "Utils.h" +#include + +#include "BuildInfo.gen.h" + +#if !defined(NDEBUG) // Debug + namespace dev { -namespace eth -{ -namespace jit +namespace evmjit { - +std::ostream& getLogStream(char const* _channel) +{ + static std::ostream nullStream{nullptr}; +#if LLVM_DEBUG + return (llvm::DebugFlag && llvm::isCurrentDebugType(_channel)) ? std::cerr : nullStream; +#else + return (void)_channel, nullStream; +#endif } + } } + +#endif diff --git a/evmjit/libevmjit/Utils.h b/evmjit/libevmjit/Utils.h index aad975f5b..0caf5e1bd 100644 --- a/evmjit/libevmjit/Utils.h +++ b/evmjit/libevmjit/Utils.h @@ -2,23 +2,39 @@ #include -#include "Common.h" +// The same as assert, but expression is always evaluated and result returned +#define CHECK(expr) (assert(expr), expr) + +#if !defined(NDEBUG) // Debug namespace dev { -namespace eth -{ -namespace jit +namespace evmjit { -struct JIT: public NoteChannel { static const char* name() { return "JIT"; } }; +std::ostream& getLogStream(char const* _channel); + +} +} -//#define clog(CHANNEL) std::cerr -#define clog(CHANNEL) std::ostream(nullptr) +#define DLOG(CHANNEL) ::dev::evmjit::getLogStream(#CHANNEL) -// The same as assert, but expression is always evaluated and result returned -#define CHECK(expr) (assert(expr), expr) +#else // Release + +namespace dev +{ +namespace evmjit +{ + +struct Voider +{ + void operator=(std::ostream const&) {} +}; } } -} + + +#define DLOG(CHANNEL) true ? (void)0 : ::dev::evmjit::Voider{} = std::cerr + +#endif diff --git a/evmjit/libevmjit/interface.cpp b/evmjit/libevmjit/interface.cpp index 645f3d150..01f743a2e 100644 --- a/evmjit/libevmjit/interface.cpp +++ b/evmjit/libevmjit/interface.cpp @@ -5,11 +5,6 @@ extern "C" using namespace dev::eth::jit; -#ifdef _MSC_VER -#define _ALLOW_KEYWORD_MACROS -#define noexcept throw() -#endif - EXPORT void* evmjit_create() noexcept { // TODO: Make sure ExecutionEngine constructor does not throw diff --git a/libdevcore/Assertions.h b/libdevcore/Assertions.h new file mode 100644 index 000000000..7b4a4a765 --- /dev/null +++ b/libdevcore/Assertions.h @@ -0,0 +1,111 @@ +/* + 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 Assertions.h + * @author Christian + * @date 2015 + * + * Assertion handling. + */ + +#pragma once + +#include "Exceptions.h" +#include "debugbreak.h" + +namespace dev +{ + +#if defined(_MSC_VER) +#define ETH_FUNC __FUNCSIG__ +#elif defined(__GNUC__) +#define ETH_FUNC __PRETTY_FUNCTION__ +#else +#define ETH_FUNC __func__ +#endif + +#define asserts(A) ::dev::assertAux(A, #A, __LINE__, __FILE__, ETH_FUNC) +#define assertsEqual(A, B) ::dev::assertEqualAux(A, B, #A, #B, __LINE__, __FILE__, ETH_FUNC) + +inline bool assertAux(bool _a, char const* _aStr, unsigned _line, char const* _file, char const* _func) +{ + bool ret = _a; + if (!ret) + { + std::cerr << "Assertion failed:" << _aStr << " [func=" << _func << ", line=" << _line << ", file=" << _file << "]" << std::endl; +#if ETH_DEBUG + debug_break(); +#endif + } + return !ret; +} + +template +inline bool assertEqualAux(A const& _a, B const& _b, char const* _aStr, char const* _bStr, unsigned _line, char const* _file, char const* _func) +{ + bool ret = _a == _b; + if (!ret) + { + std::cerr << "Assertion failed: " << _aStr << " == " << _bStr << " [func=" << _func << ", line=" << _line << ", file=" << _file << "]" << std::endl; + std::cerr << " Fail equality: " << _a << "==" << _b << std::endl; +#if ETH_DEBUG + debug_break(); +#endif + } + return !ret; +} + +/// Assertion that throws an exception containing the given description if it is not met. +/// Use it as assertThrow(1 == 1, ExceptionType, "Mathematics is wrong."); +/// Do NOT supply an exception object as the second parameter. +#define assertThrow(_condition, _ExceptionType, _description) \ + ::dev::assertThrowAux<_ExceptionType>(_condition, _description, __LINE__, __FILE__, ETH_FUNC) + +using errinfo_comment = boost::error_info; + +template +inline void assertThrowAux( + bool _condition, + ::std::string const& _errorDescription, + unsigned _line, + char const* _file, + char const* _function +) +{ + if (!_condition) + ::boost::throw_exception( + _ExceptionType() << + ::dev::errinfo_comment(_errorDescription) << + ::boost::throw_function(_function) << + ::boost::throw_file(_file) << + ::boost::throw_line(_line) + ); +} + +template +inline void assertThrowAux( + void const* _pointer, + ::std::string const& _errorDescription, + unsigned _line, + char const* _file, + char const* _function +) +{ + assertThrowAux<_ExceptionType>(_pointer != nullptr, _errorDescription, _line, _file, _function); +} + +} diff --git a/libdevcore/Common.cpp b/libdevcore/Common.cpp index 431a95580..dc0f42bf7 100644 --- a/libdevcore/Common.cpp +++ b/libdevcore/Common.cpp @@ -27,7 +27,7 @@ using namespace dev; namespace dev { -char const* Version = "0.8.2"; +char const* Version = "0.9.5"; } diff --git a/libdevcore/Common.h b/libdevcore/Common.h index b82cb827e..49491d4cc 100644 --- a/libdevcore/Common.h +++ b/libdevcore/Common.h @@ -44,7 +44,6 @@ #pragma warning(pop) #pragma GCC diagnostic pop #include "vector_ref.h" -#include "debugbreak.h" // CryptoPP defines byte in the global namespace, so must we. using byte = uint8_t; @@ -87,6 +86,7 @@ using strings = std::vector; // Fixed-length string types. using string32 = std::array; +static const string32 ZeroString32 = {{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }}; // Null/Invalid values for convenience. static const u256 Invalid256 = ~(u256)0; @@ -134,45 +134,20 @@ private: std::function m_f; }; -// Assertions... - -#if defined(_MSC_VER) -#define ETH_FUNC __FUNCSIG__ -#elif defined(__GNUC__) -#define ETH_FUNC __PRETTY_FUNCTION__ -#else -#define ETH_FUNC __func__ -#endif - -#define asserts(A) ::dev::assertAux(A, #A, __LINE__, __FILE__, ETH_FUNC) -#define assertsEqual(A, B) ::dev::assertEqualAux(A, B, #A, #B, __LINE__, __FILE__, ETH_FUNC) - -inline bool assertAux(bool _a, char const* _aStr, unsigned _line, char const* _file, char const* _func) +enum class WithExisting: int { - bool ret = _a; - if (!ret) - { - std::cerr << "Assertion failed:" << _aStr << " [func=" << _func << ", line=" << _line << ", file=" << _file << "]" << std::endl; -#if ETH_DEBUG - debug_break(); -#endif - } - return !ret; + Trust = 0, + Verify, + Kill +}; + } -template -inline bool assertEqualAux(A const& _a, B const& _b, char const* _aStr, char const* _bStr, unsigned _line, char const* _file, char const* _func) +namespace std { + +inline dev::WithExisting max(dev::WithExisting _a, dev::WithExisting _b) { - bool ret = _a == _b; - if (!ret) - { - std::cerr << "Assertion failed: " << _aStr << " == " << _bStr << " [func=" << _func << ", line=" << _line << ", file=" << _file << "]" << std::endl; - std::cerr << " Fail equality: " << _a << "==" << _b << std::endl; -#if ETH_DEBUG - debug_break(); -#endif - } - return !ret; + return static_cast(max(static_cast(_a), static_cast(_b))); } } diff --git a/libdevcore/CommonData.cpp b/libdevcore/CommonData.cpp index 09b525d59..6cad29952 100644 --- a/libdevcore/CommonData.cpp +++ b/libdevcore/CommonData.cpp @@ -39,7 +39,7 @@ std::string dev::escaped(std::string const& _s, bool _all) ret += "\\\""; else if (i == '\\' && !_all) ret += "\\\\"; - else if (prettyEscapes.count(i)) + else if (prettyEscapes.count(i) && !_all) { ret += '\\'; ret += prettyEscapes.find(i)->second; @@ -78,7 +78,7 @@ int dev::fromHex(char _i) BOOST_THROW_EXCEPTION(BadHexCharacter() << errinfo_invalidSymbol(_i)); } -bytes dev::fromHex(std::string const& _s, ThrowType _throw) +bytes dev::fromHex(std::string const& _s, WhenError _throw) { unsigned s = (_s[0] == '0' && _s[1] == 'x') ? 2 : 0; std::vector ret; @@ -96,7 +96,7 @@ bytes dev::fromHex(std::string const& _s, ThrowType _throw) #ifndef BOOST_NO_EXCEPTIONS cwarn << boost::current_exception_diagnostic_information(); #endif - if (_throw == ThrowType::Throw) + if (_throw == WhenError::Throw) throw; } for (unsigned i = s; i < _s.size(); i += 2) @@ -109,7 +109,7 @@ bytes dev::fromHex(std::string const& _s, ThrowType _throw) #ifndef BOOST_NO_EXCEPTIONS cwarn << boost::current_exception_diagnostic_information(); #endif - if (_throw == ThrowType::Throw) + if (_throw == WhenError::Throw) throw; } return ret; diff --git a/libdevcore/CommonData.h b/libdevcore/CommonData.h index 2c9fd30ef..6468e250f 100644 --- a/libdevcore/CommonData.h +++ b/libdevcore/CommonData.h @@ -35,21 +35,22 @@ namespace dev // String conversion functions, mainly to/from hex/nibble/byte representations. -enum class ThrowType +enum class WhenError { - NoThrow = 0, + DontThrow = 0, Throw = 1, }; /// Convert a series of bytes to the corresponding string of hex duplets. -/// @param _w specifies the width of each of the elements. Defaults to two - enough to represent a byte. +/// @param _w specifies the width of the first of the elements. Defaults to two - enough to represent a byte. /// @example toHex("A\x69") == "4169" template std::string toHex(_T const& _data, int _w = 2) { std::ostringstream ret; + unsigned ii = 0; for (auto i: _data) - ret << std::hex << std::setfill('0') << std::setw(_w) << (int)(typename std::make_unsigned::type)i; + ret << std::hex << std::setfill('0') << std::setw(ii++ ? 2 : _w) << (int)(typename std::make_unsigned::type)i; return ret.str(); } @@ -59,8 +60,8 @@ int fromHex(char _i); /// Converts a (printable) ASCII hex string into the corresponding byte stream. /// @example fromHex("41626261") == asBytes("Abba") -/// If _throw = ThrowType::NoThrow, it replaces bad hex characters with 0's, otherwise it will throw an exception. -bytes fromHex(std::string const& _s, ThrowType _throw = ThrowType::NoThrow); +/// If _throw = ThrowType::DontThrow, it replaces bad hex characters with 0's, otherwise it will throw an exception. +bytes fromHex(std::string const& _s, WhenError _throw = WhenError::DontThrow); #if 0 std::string toBase58(bytesConstRef _data); @@ -74,6 +75,13 @@ inline std::string asString(bytes const& _b) return std::string((char const*)_b.data(), (char const*)(_b.data() + _b.size())); } +/// Converts byte array ref to a string containing the same (binary) data. Unless +/// the byte array happens to contain ASCII data, this won't be printable. +inline std::string asString(bytesConstRef _b) +{ + return std::string((char const*)_b.data(), (char const*)(_b.data() + _b.size())); +} + /// Converts a string to a byte array containing the string's (byte) data. inline bytes asBytes(std::string const& _b) { diff --git a/libdevcore/CommonIO.cpp b/libdevcore/CommonIO.cpp index 4fa132073..46d6e3a6b 100644 --- a/libdevcore/CommonIO.cpp +++ b/libdevcore/CommonIO.cpp @@ -112,6 +112,6 @@ string dev::contentsString(std::string const& _file) void dev::writeFile(std::string const& _file, bytesConstRef _data) { - ofstream(_file, ios::trunc).write((char const*)_data.data(), _data.size()); + ofstream(_file, ios::trunc|ios::binary).write((char const*)_data.data(), _data.size()); } diff --git a/libdevcore/CommonIO.h b/libdevcore/CommonIO.h index 03fc9cffc..f42f449bb 100644 --- a/libdevcore/CommonIO.h +++ b/libdevcore/CommonIO.h @@ -35,6 +35,7 @@ #include #include #include +#include #include "Common.h" #include "Base64.h" @@ -75,6 +76,27 @@ template inline std::ostream& operator<<(std::ostream& _out, template inline std::ostream& operator<<(std::ostream& _out, std::multimap const& _e); template _S& operator<<(_S& _out, std::shared_ptr<_T> const& _p); +template inline std::string toString(std::chrono::time_point const& _e, std::string _format = "") +{ + unsigned long milliSecondsSinceEpoch = std::chrono::duration_cast(_e.time_since_epoch()).count(); + auto const durationSinceEpoch = std::chrono::milliseconds(milliSecondsSinceEpoch); + std::chrono::time_point const tpAfterDuration(durationSinceEpoch); + + tm timeValue; + auto time = std::chrono::system_clock::to_time_t(tpAfterDuration); +#ifdef _WIN32 + gmtime_s(&timeValue, &time); +#else + gmtime_r(&time, &timeValue); +#endif + + unsigned const millisRemainder = milliSecondsSinceEpoch % 1000; + char buffer[1024]; + if (strftime(buffer, sizeof(buffer), _format.c_str(), &timeValue)) + return std::string(buffer) + "." + (millisRemainder < 1 ? "000" : millisRemainder < 10 ? "00" : millisRemainder < 100 ? "0" : "") + std::to_string(millisRemainder) + "Z"; + return std::string(); +} + template inline S& streamout(S& _out, std::vector const& _e) { diff --git a/libdevcore/CommonJS.cpp b/libdevcore/CommonJS.cpp index f173e779e..262944b65 100644 --- a/libdevcore/CommonJS.cpp +++ b/libdevcore/CommonJS.cpp @@ -23,15 +23,17 @@ #include "CommonJS.h" +using namespace std; + namespace dev { -bytes jsToBytes(std::string const& _s) +bytes jsToBytes(string const& _s) { if (_s.substr(0, 2) == "0x") // Hex return fromHex(_s.substr(2)); - else if (_s.find_first_not_of("0123456789") == std::string::npos) + else if (_s.find_first_not_of("0123456789") == string::npos) // Decimal return toCompactBigEndian(bigint(_s)); else @@ -42,7 +44,7 @@ bytes padded(bytes _b, unsigned _l) { while (_b.size() < _l) _b.insert(_b.begin(), 0); - return asBytes(asString(_b).substr(_b.size() - std::max(_l, _l))); + return asBytes(asString(_b).substr(_b.size() - max(_l, _l))); } bytes paddedRight(bytes _b, unsigned _l) @@ -54,7 +56,7 @@ bytes paddedRight(bytes _b, unsigned _l) bytes unpadded(bytes _b) { auto p = asString(_b).find_last_not_of((char)0); - _b.resize(p == std::string::npos ? 0 : (p + 1)); + _b.resize(p == string::npos ? 0 : (p + 1)); return _b; } @@ -72,18 +74,18 @@ bytes unpadLeft(bytes _b) return _b; } -std::string fromRaw(h256 _n, unsigned* _inc) +string fromRaw(h256 _n, unsigned* _inc) { if (_n) { - std::string s((char const*)_n.data(), 32); + string s((char const*)_n.data(), 32); auto l = s.find_first_of('\0'); if (!l) return ""; - if (l != std::string::npos) + if (l != string::npos) { auto p = s.find_first_not_of('\0', l); - if (!(p == std::string::npos || (_inc && p == 31))) + if (!(p == string::npos || (_inc && p == 31))) return ""; if (_inc) *_inc = (byte)s[31]; diff --git a/libdevcore/CommonJS.h b/libdevcore/CommonJS.h index d3e8e6daa..ade089e16 100644 --- a/libdevcore/CommonJS.h +++ b/libdevcore/CommonJS.h @@ -38,12 +38,24 @@ template std::string toJS(FixedHash const& _h) template std::string toJS(boost::multiprecision::number> const& _n) { - return "0x" + toHex(toCompactBigEndian(_n)); + std::string h = toHex(toCompactBigEndian(_n, 1)); + // remove first 0, if it is necessary; + std::string res = h[0] != '0' ? h : h.substr(1); + return "0x" + res; } -inline std::string toJS(dev::bytes const& _n) +inline std::string toJS(bytes const& _n, std::size_t _padding = 0) { - return "0x" + dev::toHex(_n); + bytes n = _n; + n.resize(std::max(n.size(), _padding)); + return "0x" + toHex(n); +} + +template< typename T >std::string toJS( T const& i ) +{ + std::stringstream stream; + stream << "0x" << std::hex << i; + return stream.str(); } /// Convert string to byte array. Input parameters can be hex or dec. Returns empty array if invalid input e.g neither dec or hex. @@ -74,7 +86,7 @@ template FixedHash jsToFixed(std::string const& _s) inline std::string jsToFixed(double _s) { - return toJS(dev::u256(_s * (double)(dev::u256(1) << 128))); + return toJS(u256(_s * (double)(u256(1) << 128))); } template boost::multiprecision::number> jsToInt(std::string const& _s) @@ -92,25 +104,16 @@ template boost::multiprecision::number(_s); } -inline std::string jsToDecimal(std::string const& _s) +inline int jsToInt(std::string const& _s) { - return dev::toString(jsToU256(_s)); + if (_s.size() > 2 && _s.substr(0, 2).compare("0x") == 0) + return std::stoi(_s, nullptr, 16); + return std::stoi(_s, nullptr, 10); } -inline std::string jsFromBinary(dev::bytes _s, unsigned _padding = 32) -{ - _s.resize(std::max(_s.size(), _padding)); - return "0x" + dev::toHex(_s); -} - -inline std::string jsFromBinary(std::string const& _s, unsigned _padding = 32) -{ - return jsFromBinary(asBytes(_s), _padding); -} - -inline double jsFromFixed(std::string const& _s) +inline std::string jsToDecimal(std::string const& _s) { - return (double)jsToU256(_s) / (double)(dev::u256(1) << 128); + return toString(jsToU256(_s)); } } diff --git a/libdevcore/Exceptions.h b/libdevcore/Exceptions.h index 8398f9c7f..377420003 100644 --- a/libdevcore/Exceptions.h +++ b/libdevcore/Exceptions.h @@ -33,7 +33,7 @@ namespace dev // base class for all exceptions struct Exception: virtual std::exception, virtual boost::exception { - Exception(std::string _message = {}): m_message(std::move(_message)) {} + Exception(std::string _message = std::string()): m_message(std::move(_message)) {} const char* what() const noexcept override { return m_message.empty() ? std::exception::what() : m_message.c_str(); } private: @@ -44,6 +44,8 @@ struct BadHexCharacter: virtual Exception {}; struct RLPException: virtual Exception {}; struct BadCast: virtual RLPException {}; struct BadRLP: virtual RLPException {}; +struct OversizeRLP: virtual RLPException {}; +struct UndersizeRLP: virtual RLPException {}; struct NoNetworking: virtual Exception {}; struct NoUPnPDevice: virtual Exception {}; struct RootNotFound: virtual Exception {}; diff --git a/libdevcore/FixedHash.h b/libdevcore/FixedHash.h index 49c6ed2bf..456365299 100644 --- a/libdevcore/FixedHash.h +++ b/libdevcore/FixedHash.h @@ -87,6 +87,8 @@ public: bool operator!=(FixedHash const& _c) const { return m_data != _c.m_data; } bool operator<(FixedHash const& _c) const { for (unsigned i = 0; i < N; ++i) if (m_data[i] < _c.m_data[i]) return true; else if (m_data[i] > _c.m_data[i]) return false; return false; } bool operator>=(FixedHash const& _c) const { return !operator<(_c); } + bool operator<=(FixedHash const& _c) const { return operator==(_c) || operator<(_c); } + bool operator>(FixedHash const& _c) const { return !operator<=(_c); } // The obvious binary operators. FixedHash& operator^=(FixedHash const& _c) { for (unsigned i = 0; i < N; ++i) m_data[i] ^= _c.m_data[i]; return *this; } @@ -139,6 +141,7 @@ public: return ret; } + /// @returns a random valued object. static FixedHash random() { return random(s_fixedHashEngine); } /// A generic std::hash compatible function object. @@ -154,25 +157,17 @@ public: } }; - inline FixedHash<32> bloom() const - { - FixedHash<32> ret; - for (auto i: m_data) - ret[i / 8] |= 1 << (i % 8); - return ret; - } - template inline FixedHash& shiftBloom(FixedHash const& _h) { - return (*this |= _h.template nbloom()); + return (*this |= _h.template bloom()); } template inline bool containsBloom(FixedHash const& _h) { - return contains(_h.template nbloom()); + return contains(_h.template bloom()); } - template inline FixedHash nbloom() const + template inline FixedHash bloom() const { static const unsigned c_bloomBits = M * 8; unsigned mask = c_bloomBits - 1; diff --git a/libdevcore/RLP.cpp b/libdevcore/RLP.cpp index 0dd61b876..26f10ecf5 100644 --- a/libdevcore/RLP.cpp +++ b/libdevcore/RLP.cpp @@ -26,12 +26,31 @@ using namespace dev; bytes dev::RLPNull = rlp(""); bytes dev::RLPEmptyList = rlpList(); +RLP::RLP(bytesConstRef _d, Strictness _s): + m_data(_d) +{ + if ((_s & FailIfTooBig) && actualSize() < _d.size()) + { + if (_s & ThrowOnFail) + BOOST_THROW_EXCEPTION(OversizeRLP()); + else + m_data.reset(); + } + if ((_s & FailIfTooSmall) && actualSize() > _d.size()) + { + if (_s & ThrowOnFail) + BOOST_THROW_EXCEPTION(UndersizeRLP()); + else + m_data.reset(); + } +} + RLP::iterator& RLP::iterator::operator++() { if (m_remaining) { m_lastItem.retarget(m_lastItem.next().data(), m_remaining); - m_lastItem = m_lastItem.cropped(0, RLP(m_lastItem).actualSize()); + m_lastItem = m_lastItem.cropped(0, RLP(m_lastItem, ThrowOnFail | FailIfTooSmall).actualSize()); m_remaining -= std::min(m_remaining, m_lastItem.size()); } else @@ -44,7 +63,7 @@ RLP::iterator::iterator(RLP const& _parent, bool _begin) if (_begin && _parent.isList()) { auto pl = _parent.payload(); - m_lastItem = pl.cropped(0, RLP(pl).actualSize()); + m_lastItem = pl.cropped(0, RLP(pl, ThrowOnFail | FailIfTooSmall).actualSize()); m_remaining = pl.size() - m_lastItem.size(); } else @@ -58,17 +77,17 @@ RLP RLP::operator[](unsigned _i) const { if (_i < m_lastIndex) { - m_lastEnd = RLP(payload()).actualSize(); + m_lastEnd = RLP(payload(), ThrowOnFail | FailIfTooSmall).actualSize(); m_lastItem = payload().cropped(0, m_lastEnd); m_lastIndex = 0; } for (; m_lastIndex < _i && m_lastItem.size(); ++m_lastIndex) { m_lastItem = payload().cropped(m_lastEnd); - m_lastItem = m_lastItem.cropped(0, RLP(m_lastItem).actualSize()); + m_lastItem = m_lastItem.cropped(0, RLP(m_lastItem, ThrowOnFail | FailIfTooSmall).actualSize()); m_lastEnd += m_lastItem.size(); } - return RLP(m_lastItem); + return RLP(m_lastItem, ThrowOnFail | FailIfTooSmall); } RLPs RLP::toList() const @@ -154,7 +173,7 @@ unsigned RLP::items() const bytesConstRef d = payload().cropped(0, length()); unsigned i = 0; for (; d.size(); ++i) - d = d.cropped(RLP(d).actualSize()); + d = d.cropped(RLP(d, ThrowOnFail | FailIfTooSmall).actualSize()); return i; } return 0; diff --git a/libdevcore/RLP.h b/libdevcore/RLP.h index a538fac21..a837f9221 100644 --- a/libdevcore/RLP.h +++ b/libdevcore/RLP.h @@ -62,20 +62,34 @@ static const byte c_rlpListIndLenZero = c_rlpListStart + c_rlpListImmLenCount - class RLP { public: + /// Conversion flags + enum + { + AllowNonCanon = 1, + ThrowOnFail = 4, + FailIfTooBig = 8, + FailIfTooSmall = 16, + Strict = ThrowOnFail | FailIfTooBig, + VeryStrict = ThrowOnFail | FailIfTooBig | FailIfTooSmall, + LaisezFaire = AllowNonCanon + }; + + using Strictness = int; + /// Construct a null node. RLP() {} /// Construct a node of value given in the bytes. - explicit RLP(bytesConstRef _d): m_data(_d) {} + explicit RLP(bytesConstRef _d, Strictness _s = VeryStrict); /// Construct a node of value given in the bytes. - explicit RLP(bytes const& _d): m_data(&_d) {} + explicit RLP(bytes const& _d, Strictness _s = VeryStrict): RLP(&_d, _s) {} /// Construct a node to read RLP data in the bytes given. - RLP(byte const* _b, unsigned _s): m_data(bytesConstRef(_b, _s)) {} + RLP(byte const* _b, unsigned _s, Strictness _st = VeryStrict): RLP(bytesConstRef(_b, _s), _st) {} /// Construct a node to read RLP data in the string. - explicit RLP(std::string const& _s): m_data(bytesConstRef((byte const*)_s.data(), _s.size())) {} + explicit RLP(std::string const& _s, Strictness _st = VeryStrict): RLP(bytesConstRef((byte const*)_s.data(), _s.size()), _st) {} /// The bare data of the RLP. bytesConstRef data() const { return m_data; } @@ -236,18 +250,6 @@ public: return ret; } - /// Int conversion flags - enum - { - AllowNonCanon = 1, - ThrowOnFail = 4, - FailIfTooBig = 8, - FailIfTooSmall = 16, - Strict = ThrowOnFail | FailIfTooBig, - VeryStrict = ThrowOnFail | FailIfTooBig | FailIfTooSmall, - LaisezFaire = AllowNonCanon - }; - /// Converts to int of type given; if isString(), decodes as big-endian bytestream. @returns 0 if not an int or string. template _T toInt(int _flags = Strict) const { diff --git a/libdevcore/StructuredLogger.cpp b/libdevcore/StructuredLogger.cpp index d93a9496e..f51ed310a 100644 --- a/libdevcore/StructuredLogger.cpp +++ b/libdevcore/StructuredLogger.cpp @@ -22,36 +22,26 @@ */ #include "StructuredLogger.h" - -#include +#include #include + +#include #include "Guards.h" +namespace ba = boost::asio; using namespace std; namespace dev { -string StructuredLogger::timePointToString(chrono::system_clock::time_point const& _ts) -{ - // not using C++11 std::put_time due to gcc bug - // http://stackoverflow.com/questions/14136833/stdput-time-implementation-status-in-gcc - - char buffer[64]; - time_t time = chrono::system_clock::to_time_t(_ts); - tm* ptm = localtime(&time); - if (strftime(buffer, sizeof(buffer), get().m_timeFormat.c_str(), ptm)) - return string(buffer); - return ""; -} - void StructuredLogger::outputJson(Json::Value const& _value, std::string const& _name) const { Json::Value event; static Mutex s_lock; + Json::FastWriter fastWriter; Guard l(s_lock); event[_name] = _value; - cout << event << endl << flush; + cout << fastWriter.write(event) << endl; } void StructuredLogger::starting(string const& _clientImpl, const char* _ethVersion) @@ -61,7 +51,7 @@ void StructuredLogger::starting(string const& _clientImpl, const char* _ethVersi Json::Value event; event["client_impl"] = _clientImpl; event["eth_version"] = std::string(_ethVersion); - event["ts"] = timePointToString(std::chrono::system_clock::now()); + event["ts"] = dev::toString(chrono::system_clock::now(), get().m_timeFormat.c_str()); get().outputJson(event, "starting"); } @@ -74,7 +64,7 @@ void StructuredLogger::stopping(string const& _clientImpl, const char* _ethVersi Json::Value event; event["client_impl"] = _clientImpl; event["eth_version"] = std::string(_ethVersion); - event["ts"] = timePointToString(std::chrono::system_clock::now()); + event["ts"] = dev::toString(chrono::system_clock::now(), get().m_timeFormat.c_str()); get().outputJson(event, "stopping"); } @@ -96,7 +86,7 @@ void StructuredLogger::p2pConnected( event["remote_addr"] = addrStream.str(); event["remote_id"] = _id; event["num_connections"] = Json::Value(_numConnections); - event["ts"] = timePointToString(_ts); + event["ts"] = dev::toString(_ts, get().m_timeFormat.c_str()); get().outputJson(event, "p2p.connected"); } @@ -112,7 +102,7 @@ void StructuredLogger::p2pDisconnected(string const& _id, bi::tcp::endpoint cons event["remote_addr"] = addrStream.str(); event["remote_id"] = _id; event["num_connections"] = Json::Value(_numConnections); - event["ts"] = timePointToString(chrono::system_clock::now()); + event["ts"] = dev::toString(chrono::system_clock::now(), get().m_timeFormat.c_str()); get().outputJson(event, "p2p.disconnected"); } @@ -130,7 +120,7 @@ void StructuredLogger::minedNewBlock( event["block_hash"] = _hash; event["block_number"] = _blockNumber; event["chain_head_hash"] = _chainHeadHash; - event["ts"] = timePointToString(std::chrono::system_clock::now()); + event["ts"] = dev::toString(chrono::system_clock::now(), get().m_timeFormat.c_str()); event["block_prev_hash"] = _prevHash; get().outputJson(event, "eth.miner.new_block"); @@ -151,7 +141,7 @@ void StructuredLogger::chainReceivedNewBlock( event["block_number"] = _blockNumber; event["chain_head_hash"] = _chainHeadHash; event["remote_id"] = _remoteID; - event["ts"] = timePointToString(chrono::system_clock::now()); + event["ts"] = dev::toString(chrono::system_clock::now(), get().m_timeFormat.c_str()); event["block_prev_hash"] = _prevHash; get().outputJson(event, "eth.chain.received.new_block"); @@ -170,7 +160,7 @@ void StructuredLogger::chainNewHead( event["block_hash"] = _hash; event["block_number"] = _blockNumber; event["chain_head_hash"] = _chainHeadHash; - event["ts"] = timePointToString(chrono::system_clock::now()); + event["ts"] = dev::toString(chrono::system_clock::now(), get().m_timeFormat.c_str()); event["block_prev_hash"] = _prevHash; get().outputJson(event, "eth.miner.new_block"); @@ -184,7 +174,7 @@ void StructuredLogger::transactionReceived(string const& _hash, string const& _r Json::Value event; event["tx_hash"] = _hash; event["remote_id"] = _remoteId; - event["ts"] = timePointToString(chrono::system_clock::now()); + event["ts"] = dev::toString(chrono::system_clock::now(), get().m_timeFormat.c_str()); get().outputJson(event, "eth.tx.received"); } diff --git a/libdevcore/StructuredLogger.h b/libdevcore/StructuredLogger.h index 58b408ede..2c30541e4 100644 --- a/libdevcore/StructuredLogger.h +++ b/libdevcore/StructuredLogger.h @@ -27,9 +27,10 @@ #include #include -#include namespace Json { class Value; } +namespace boost { namespace asio { namespace ip { templateclass basic_endpoint; class tcp; }}} +namespace bi = boost::asio::ip; namespace dev { @@ -61,12 +62,16 @@ public: static void stopping(std::string const& _clientImpl, const char* _ethVersion); static void p2pConnected( std::string const& _id, - bi::tcp::endpoint const& _addr, + bi::basic_endpoint const& _addr, std::chrono::system_clock::time_point const& _ts, std::string const& _remoteVersion, unsigned int _numConnections ); - static void p2pDisconnected(std::string const& _id, bi::tcp::endpoint const& _addr, unsigned int _numConnections); + static void p2pDisconnected( + std::string const& _id, + bi::basic_endpoint const& _addr, + unsigned int _numConnections + ); static void minedNewBlock( std::string const& _hash, std::string const& _blockNumber, @@ -93,8 +98,6 @@ private: StructuredLogger(StructuredLogger const&) = delete; void operator=(StructuredLogger const&) = delete; - /// @returns a string representation of a timepoint - static std::string timePointToString(std::chrono::system_clock::time_point const& _ts); void outputJson(Json::Value const& _value, std::string const& _name) const; bool m_enabled = false; diff --git a/libdevcore/vector_ref.h b/libdevcore/vector_ref.h index 2c5f07e51..5e9bba3e8 100644 --- a/libdevcore/vector_ref.h +++ b/libdevcore/vector_ref.h @@ -39,8 +39,9 @@ public: size_t size() const { return m_count; } bool empty() const { return !m_count; } vector_ref<_T> next() const { return vector_ref<_T>(m_data + m_count, m_count); } - vector_ref<_T> cropped(size_t _begin, size_t _count = ~size_t(0)) const { if (m_data && _begin + std::max(size_t(0), _count) <= m_count) return vector_ref<_T>(m_data + _begin, _count == ~size_t(0) ? m_count - _begin : _count); else return vector_ref<_T>(); } - void retarget(_T const* _d, size_t _s) { m_data = _d; m_count = _s; } + vector_ref<_T> cropped(size_t _begin, size_t _count) const { if (m_data && _begin + _count <= m_count) return vector_ref<_T>(m_data + _begin, _count == ~size_t(0) ? m_count - _begin : _count); else return vector_ref<_T>(); } + vector_ref<_T> cropped(size_t _begin) const { if (m_data && _begin <= m_count) return vector_ref<_T>(m_data + _begin, m_count - _begin); else return vector_ref<_T>(); } + void retarget(_T* _d, size_t _s) { m_data = _d; m_count = _s; } void retarget(std::vector<_T> const& _t) { m_data = _t.data(); m_count = _t.size(); } void copyTo(vector_ref::type> _t) const { memcpy(_t.data(), m_data, std::min(_t.size(), m_count) * sizeof(_T)); } void populate(vector_ref::type> _t) const { copyTo(_t); memset(_t.data() + m_count, 0, std::max(_t.size(), m_count) - m_count); } diff --git a/libdevcrypto/Common.cpp b/libdevcrypto/Common.cpp index 048134de8..e108b230f 100644 --- a/libdevcrypto/Common.cpp +++ b/libdevcrypto/Common.cpp @@ -15,8 +15,8 @@ along with cpp-ethereum. If not, see . */ /** @file Common.cpp - * @author Gav Wood * @author Alex Leverington + * @author Gav Wood * @date 2014 */ @@ -82,6 +82,22 @@ bool dev::decrypt(Secret const& _k, bytesConstRef _cipher, bytes& o_plaintext) return true; } +void dev::encryptECIES(Public const& _k, bytesConstRef _plain, bytes& o_cipher) +{ + bytes io = _plain.toBytes(); + s_secp256k1.encryptECIES(_k, io); + o_cipher = std::move(io); +} + +bool dev::decryptECIES(Secret const& _k, bytesConstRef _cipher, bytes& o_plaintext) +{ + bytes io = _cipher.toBytes(); + if (!s_secp256k1.decryptECIES(_k, io)) + return false; + o_plaintext = std::move(io); + return true; +} + void dev::encryptSym(Secret const& _k, bytesConstRef _plain, bytes& o_cipher) { // TOOD: @alex @subtly do this properly. @@ -94,6 +110,54 @@ bool dev::decryptSym(Secret const& _k, bytesConstRef _cipher, bytes& o_plain) return decrypt(_k, _cipher, o_plain); } +h128 dev::encryptSymNoAuth(Secret const& _k, bytesConstRef _plain, bytes& o_cipher) +{ + h128 iv(Nonce::get()); + return encryptSymNoAuth(_k, _plain, o_cipher, iv); +} + +h128 dev::encryptSymNoAuth(Secret const& _k, bytesConstRef _plain, bytes& o_cipher, h128 const& _iv) +{ + o_cipher.resize(_plain.size()); + + const int c_aesKeyLen = 16; + SecByteBlock key(_k.data(), c_aesKeyLen); + try + { + CTR_Mode::Encryption e; + e.SetKeyWithIV(key, key.size(), _iv.data()); + e.ProcessData(o_cipher.data(), _plain.data(), _plain.size()); + return _iv; + } + catch (CryptoPP::Exception& _e) + { + cerr << _e.what() << endl; + o_cipher.resize(0); + return h128(); + } +} + +bool dev::decryptSymNoAuth(Secret const& _k, h128 const& _iv, bytesConstRef _cipher, bytes& o_plaintext) +{ + o_plaintext.resize(_cipher.size()); + + const size_t c_aesKeyLen = 16; + SecByteBlock key(_k.data(), c_aesKeyLen); + try + { + CTR_Mode::Decryption d; + d.SetKeyWithIV(key, key.size(), _iv.data()); + d.ProcessData(o_plaintext.data(), _cipher.data(), _cipher.size()); + return true; + } + catch (CryptoPP::Exception& _e) + { + cerr << _e.what() << endl; + o_plaintext.resize(0); + return false; + } +} + Public dev::recover(Signature const& _sig, h256 const& _message) { return s_secp256k1.recover(_sig, _message.ref()); diff --git a/libdevcrypto/Common.h b/libdevcrypto/Common.h index ccbd0953b..3159f4e7e 100644 --- a/libdevcrypto/Common.h +++ b/libdevcrypto/Common.h @@ -15,8 +15,8 @@ along with cpp-ethereum. If not, see . */ /** @file Common.h - * @author Gav Wood * @author Alex Leverington + * @author Gav Wood * @date 2014 * * Ethereum-specific data structures & algorithms. @@ -45,7 +45,7 @@ using Signature = h520; struct SignatureStruct { - SignatureStruct() {} + SignatureStruct() = default; SignatureStruct(Signature const& _s) { *(h520*)this = _s; } SignatureStruct(h256 const& _r, h256 const& _s, byte _v): r(_r), s(_s), v(_v) {} operator Signature() const { return *(h520 const*)this; } @@ -55,7 +55,7 @@ struct SignatureStruct h256 r; h256 s; - byte v; + byte v = 0; }; /// An Ethereum address: 20 bytes. @@ -96,6 +96,21 @@ void encryptSym(Secret const& _k, bytesConstRef _plain, bytes& o_cipher); /// Symmetric decryption. bool decryptSym(Secret const& _k, bytesConstRef _cipher, bytes& o_plaintext); +/// Encrypt payload using ECIES standard with AES128-CTR. +void encryptECIES(Public const& _k, bytesConstRef _plain, bytes& o_cipher); + +/// Decrypt payload using ECIES standard with AES128-CTR. +bool decryptECIES(Secret const& _k, bytesConstRef _cipher, bytes& o_plaintext); + +/// Encrypts payload with random IV/ctr using AES128-CTR. +h128 encryptSymNoAuth(Secret const& _k, bytesConstRef _plain, bytes& o_cipher); + +/// Encrypts payload with specified IV/ctr using AES128-CTR. +h128 encryptSymNoAuth(Secret const& _k, bytesConstRef _plain, bytes& o_cipher, h128 const& _iv); + +/// Decrypts payload with specified IV/ctr. +bool decryptSymNoAuth(Secret const& _k, h128 const& _iv, bytesConstRef _cipher, bytes& o_plaintext); + /// Recovers Public key from signed message hash. Public recover(Signature const& _sig, h256 const& _hash); diff --git a/libdevcrypto/CryptoPP.cpp b/libdevcrypto/CryptoPP.cpp index 43993e0c5..ff22b9b45 100644 --- a/libdevcrypto/CryptoPP.cpp +++ b/libdevcrypto/CryptoPP.cpp @@ -19,8 +19,10 @@ * @date 2014 */ -#include "CryptoPP.h" #include +#include +#include "ECDHE.h" +#include "CryptoPP.h" using namespace std; using namespace dev; @@ -31,6 +33,119 @@ static_assert(dev::Secret::size == 32, "Secret key must be 32 bytes."); static_assert(dev::Public::size == 64, "Public key must be 64 bytes."); static_assert(dev::Signature::size == 65, "Signature must be 65 bytes."); +bytes Secp256k1::eciesKDF(Secret _z, bytes _s1, unsigned kdByteLen) +{ + // interop w/go ecies implementation + + // for sha3, blocksize is 136 bytes + // for sha256, blocksize is 64 bytes + auto reps = ((kdByteLen + 7) * 8) / (64 * 8); + bytes ctr({0, 0, 0, 1}); + bytes k; + CryptoPP::SHA256 ctx; + for (unsigned i = 0; i <= reps; i++) + { + ctx.Update(ctr.data(), ctr.size()); + ctx.Update(_z.data(), Secret::size); + ctx.Update(_s1.data(), _s1.size()); + // append hash to k + bytes digest(32); + ctx.Final(digest.data()); + ctx.Restart(); + + k.reserve(k.size() + h256::size); + move(digest.begin(), digest.end(), back_inserter(k)); + + if (++ctr[3] || ++ctr[2] || ++ctr[1] || ++ctr[0]) + continue; + } + + k.resize(kdByteLen); + return move(k); +} + +void Secp256k1::encryptECIES(Public const& _k, bytes& io_cipher) +{ + // interop w/go ecies implementation + auto r = KeyPair::create(); + h256 z; + ecdh::agree(r.sec(), _k, z); + auto key = eciesKDF(z, bytes(), 32); + bytesConstRef eKey = bytesConstRef(&key).cropped(0, 16); + bytesRef mKeyMaterial = bytesRef(&key).cropped(16, 16); + CryptoPP::SHA256 ctx; + ctx.Update(mKeyMaterial.data(), mKeyMaterial.size()); + bytes mKey(32); + ctx.Final(mKey.data()); + + bytes cipherText; + encryptSymNoAuth(*(Secret*)eKey.data(), bytesConstRef(&io_cipher), cipherText, h128()); + if (cipherText.empty()) + return; + + bytes msg(1 + Public::size + h128::size + cipherText.size() + 32); + msg[0] = 0x04; + r.pub().ref().copyTo(bytesRef(&msg).cropped(1, Public::size)); + bytesRef msgCipherRef = bytesRef(&msg).cropped(1 + Public::size + h128::size, cipherText.size()); + bytesConstRef(&cipherText).copyTo(msgCipherRef); + + // tag message + CryptoPP::HMAC hmacctx(mKey.data(), mKey.size()); + bytesConstRef cipherWithIV = bytesRef(&msg).cropped(1 + Public::size, h128::size + cipherText.size()); + hmacctx.Update(cipherWithIV.data(), cipherWithIV.size()); + hmacctx.Final(msg.data() + 1 + Public::size + cipherWithIV.size()); + + io_cipher.resize(msg.size()); + io_cipher.swap(msg); +} + +bool Secp256k1::decryptECIES(Secret const& _k, bytes& io_text) +{ + // interop w/go ecies implementation + + // io_cipher[0] must be 2, 3, or 4, else invalidpublickey + if (io_text[0] < 2 || io_text[0] > 4) + // invalid message: publickey + return false; + + if (io_text.size() < (1 + Public::size + h128::size + 1 + h256::size)) + // invalid message: length + return false; + + h256 z; + ecdh::agree(_k, *(Public*)(io_text.data()+1), z); + auto key = eciesKDF(z, bytes(), 64); + bytesConstRef eKey = bytesConstRef(&key).cropped(0, 16); + bytesRef mKeyMaterial = bytesRef(&key).cropped(16, 16); + bytes mKey(32); + CryptoPP::SHA256 ctx; + ctx.Update(mKeyMaterial.data(), mKeyMaterial.size()); + ctx.Final(mKey.data()); + + bytes plain; + size_t cipherLen = io_text.size() - 1 - Public::size - h128::size - h256::size; + bytesConstRef cipherWithIV(io_text.data() + 1 + Public::size, h128::size + cipherLen); + bytesConstRef cipherIV = cipherWithIV.cropped(0, h128::size); + bytesConstRef cipherNoIV = cipherWithIV.cropped(h128::size, cipherLen); + bytesConstRef msgMac(cipherNoIV.data() + cipherLen, h256::size); + h128 iv(cipherIV.toBytes()); + + // verify tag + CryptoPP::HMAC hmacctx(mKey.data(), mKey.size()); + hmacctx.Update(cipherWithIV.data(), cipherWithIV.size()); + h256 mac; + hmacctx.Final(mac.data()); + for (unsigned i = 0; i < h256::size; i++) + if (mac[i] != msgMac[i]) + return false; + + decryptSymNoAuth(*(Secret*)eKey.data(), iv, cipherNoIV, plain); + io_text.resize(plain.size()); + io_text.swap(plain); + + return true; +} + void Secp256k1::encrypt(Public const& _k, bytes& io_cipher) { ECIES::Encryptor e; @@ -199,13 +314,13 @@ bool Secp256k1::verifySecret(Secret const& _s, Public& _p) void Secp256k1::agree(Secret const& _s, Public const& _r, h256& o_s) { - (void)o_s; - (void)_s; - ECDH::Domain d(m_oid); + // TODO: mutex ASN1::secp256k1() singleton + // Creating Domain is non-const for m_oid and m_oid is not thread-safe + ECDH::Domain d(ASN1::secp256k1()); assert(d.AgreedValueLength() == sizeof(o_s)); byte remote[65] = {0x04}; memcpy(&remote[1], _r.data(), 64); - assert(d.Agree(o_s.data(), _s.data(), remote)); + d.Agree(o_s.data(), _s.data(), remote); } void Secp256k1::exportPublicKey(CryptoPP::DL_PublicKey_EC const& _k, Public& o_p) diff --git a/libdevcrypto/CryptoPP.h b/libdevcrypto/CryptoPP.h index fa9d92aa1..4991e3713 100644 --- a/libdevcrypto/CryptoPP.h +++ b/libdevcrypto/CryptoPP.h @@ -65,6 +65,7 @@ inline Integer secretToExponent(Secret const& _s) { return std::move(Integer(_s. /** * CryptoPP secp256k1 algorithms. + * @todo Collect ECIES methods into class. */ class Secp256k1 { @@ -75,12 +76,21 @@ public: void toPublic(Secret const& _s, Public& o_public) { exponentToPublic(Integer(_s.data(), sizeof(_s)), o_public); } - /// Encrypts text (replace input). + /// Encrypts text (replace input). (ECIES w/XOR-SHA1) void encrypt(Public const& _k, bytes& io_cipher); - /// Decrypts text (replace input). + /// Decrypts text (replace input). (ECIES w/XOR-SHA1) void decrypt(Secret const& _k, bytes& io_text); + /// Encrypts text (replace input). (ECIES w/AES128-CTR-SHA256) + void encryptECIES(Public const& _k, bytes& io_cipher); + + /// Decrypts text (replace input). (ECIES w/AES128-CTR-SHA256) + bool decryptECIES(Secret const& _k, bytes& io_text); + + /// Key derivation function used by encryptECIES and decryptECIES. + bytes eciesKDF(Secret _z, bytes _s1, unsigned kdBitLen = 256); + /// @returns siganture of message. Signature sign(Secret const& _k, bytesConstRef _message); diff --git a/libdevcrypto/ECDHE.cpp b/libdevcrypto/ECDHE.cpp index deae3bc6d..a00a92872 100644 --- a/libdevcrypto/ECDHE.cpp +++ b/libdevcrypto/ECDHE.cpp @@ -29,7 +29,12 @@ using namespace dev::crypto; static Secp256k1 s_secp256k1; -void ECDHE::agree(Public const& _remote, Secret& o_sharedSecret) +void dev::crypto::ecdh::agree(Secret const& _s, Public const& _r, h256& o_s) +{ + s_secp256k1.agree(_s, _r, o_s); +} + +void ECDHE::agree(Public const& _remote, Secret& o_sharedSecret) const { if (m_remoteEphemeral) // agreement can only occur once diff --git a/libdevcrypto/ECDHE.h b/libdevcrypto/ECDHE.h index 4450aec4b..d3c9ae325 100644 --- a/libdevcrypto/ECDHE.h +++ b/libdevcrypto/ECDHE.h @@ -48,6 +48,11 @@ private: std::map m_sessions; Secret m_secret; }; + +namespace ecdh +{ +void agree(Secret const& _s, Public const& _r, h256& o_s); +} /** * @brief Derive DH shared secret from EC keypairs. @@ -62,12 +67,14 @@ public: /// Public key sent to remote. Public pubkey() { return m_ephemeral.pub(); } + Secret seckey() { return m_ephemeral.sec(); } + /// Input public key for dh agreement, output generated shared secret. - void agree(Public const& _remoteEphemeral, Secret& o_sharedSecret); + void agree(Public const& _remoteEphemeral, Secret& o_sharedSecret) const; protected: - KeyPair m_ephemeral; ///< Ephemeral keypair; generated. - Public m_remoteEphemeral; ///< Public key of remote; parameter. + KeyPair m_ephemeral; ///< Ephemeral keypair; generated. + mutable Public m_remoteEphemeral; ///< Public key of remote; parameter. Set once when agree is called, otherwise immutable. }; /** @@ -80,10 +87,10 @@ class ECDHEKeyExchange: private ECDHE { public: /// Exchange with unknown remote (pass public key for ingress exchange) - ECDHEKeyExchange(Alias& _k): m_alias(_k) {}; + ECDHEKeyExchange(Alias& _k): m_alias(_k) {} /// Exchange with known remote - ECDHEKeyExchange(Alias& _k, AliasSession _known): m_alias(_k), m_known(_known) {}; + ECDHEKeyExchange(Alias& _k, AliasSession _known): m_alias(_k), m_known(_known) {} /// Provide public key for dh agreement to generate shared secret. void agree(Public const& _remoteEphemeral); diff --git a/libdevcrypto/FileSystem.cpp b/libdevcrypto/FileSystem.cpp index 81b23e886..ed054553d 100644 --- a/libdevcrypto/FileSystem.cpp +++ b/libdevcrypto/FileSystem.cpp @@ -32,12 +32,15 @@ using namespace std; using namespace dev; -std::string dev::getDataDir() +std::string dev::getDataDir(std::string _prefix) { + if (_prefix.empty()) + _prefix = "ethereum"; #ifdef _WIN32 + _prefix[0] = toupper(_prefix[0]); char path[1024] = ""; if (SHGetSpecialFolderPathA(NULL, path, CSIDL_APPDATA, true)) - return (boost::filesystem::path(path) / "Ethereum").string(); + return (boost::filesystem::path(path) / _prefix).string(); else { #ifndef _MSC_VER // todo? @@ -57,7 +60,7 @@ std::string dev::getDataDir() // This eventually needs to be put in proper wrapper (to support sandboxing) return (dataDirPath / "Library/Application Support/Ethereum").string(); #else - return (dataDirPath / ".ethereum").string(); + return (dataDirPath / ("." + _prefix)).string(); #endif #endif } diff --git a/libdevcrypto/FileSystem.h b/libdevcrypto/FileSystem.h index 281e60e24..6c8160a58 100644 --- a/libdevcrypto/FileSystem.h +++ b/libdevcrypto/FileSystem.h @@ -30,6 +30,6 @@ namespace dev { /// @returns the path for user data. -std::string getDataDir(); +std::string getDataDir(std::string _prefix = "ethereum"); } diff --git a/libdevcrypto/MemoryDB.h b/libdevcrypto/MemoryDB.h index 7d39ba73b..7858126f8 100644 --- a/libdevcrypto/MemoryDB.h +++ b/libdevcrypto/MemoryDB.h @@ -32,8 +32,10 @@ namespace dev { struct DBChannel: public LogChannel { static const char* name() { return "TDB"; } static const int verbosity = 18; }; +struct DBWarn: public LogChannel { static const char* name() { return "TDB"; } static const int verbosity = 1; }; #define dbdebug clog(DBChannel) +#define dbwarn clog(DBWarn) class MemoryDB { diff --git a/libethash/CMakeLists.txt b/libethash/CMakeLists.txt index 7bc147af7..c92240086 100644 --- a/libethash/CMakeLists.txt +++ b/libethash/CMakeLists.txt @@ -2,7 +2,6 @@ set(LIBRARY ethash) if (CPPETHEREUM) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fPIC") -#else () endif () set(CMAKE_BUILD_TYPE Release) @@ -13,6 +12,7 @@ endif() set(FILES util.c util.h + io.c internal.c ethash.h endian.h @@ -20,6 +20,12 @@ set(FILES util.c fnv.h data_sizes.h) +if (MSVC) + list(APPEND FILES io_win32.c) +else() + list(APPEND FILES io_posix.c) +endif() + if (NOT CRYPTOPP_FOUND) find_package(CryptoPP 5.6.2) endif() diff --git a/libethash/data_sizes.h b/libethash/data_sizes.h index ccdf554a8..cf52ae4f8 100644 --- a/libethash/data_sizes.h +++ b/libethash/data_sizes.h @@ -20,8 +20,6 @@ * @date 2015 */ -// TODO: Update this after ~3.5 years - #pragma once #include @@ -33,213 +31,780 @@ extern "C" { #include -// 500 Epochs worth of tabulated DAG sizes (~3.5 Years) +// 2048 Epochs (~20 years) worth of tabulated DAG sizes // Generated with the following Mathematica Code: -// GetDataSizes[n_] := Module[{ -// DAGSizeBytesInit = 2^30, -// MixBytes = 128, -// DAGGrowth = 113000000, + +// GetCacheSizes[n_] := Module[{ +// CacheSizeBytesInit = 2^24, +// CacheGrowth = 2^17, +// HashBytes = 64, // j = 0}, -// Reap[ -// While[j < n, -// Module[{i = -// Floor[(DAGSizeBytesInit + DAGGrowth * j) / MixBytes]}, -// While[! PrimeQ[i], i--]; -// Sow[i*MixBytes]; j++]]]][[2]][[1]] - -static const size_t dag_sizes[] = { - 1073739904U, 1186739584U, 1299741568U, 1412741248U, 1525741696U, - 1638736768U, 1751741312U, 1864740736U, 1977740672U, 2090740864U, - 2203740544U, 2316741248U, 2429739392U, 2542740352U, 2655741824U, - 2768739712U, 2881740416U, 2994741632U, 3107740544U, 3220741504U, - 3333738112U, 3446741632U, 3559741312U, 3672740224U, 3785740928U, - 3898738304U, 4011741824U, 4124739712U, 4237735808U, 4350740864U, - 4463741824U, 4576741504U, 4689741184U, 4802739328U, 4915741568U, - 5028740224U, 5141740672U, 5254738304U, 5367741824U, 5480737664U, - 5593738112U, 5706741632U, 5819740544U, 5932734592U, 6045739904U, - 6158740096U, 6271740032U, 6384731776U, 6497732992U, 6610740352U, - 6723741056U, 6836741504U, 6949740416U, 7062740096U, 7175741824U, - 7288740224U, 7401741184U, 7514741632U, 7627741568U, 7740739712U, - 7853739136U, 7966740352U, 8079741568U, 8192739712U, 8305738624U, - 8418740864U, 8531740288U, 8644740736U, 8757735808U, 8870738816U, - 8983739264U, 9096740992U, 9209740928U, 9322739584U, 9435741824U, - 9548741504U, 9661739392U, 9774738304U, 9887741312U, 10000738688U, - 10113739136U, 10226741632U, 10339739776U, 10452741248U, 10565740928U, - 10678736512U, 10791734656U, 10904741248U, 11017738112U, 11130741632U, - 11243741312U, 11356739456U, 11469740416U, 11582734976U, 11695739008U, - 11808741248U, 11921734784U, 12034739072U, 12147741568U, 12260737408U, - 12373741696U, 12486738304U, 12599740544U, 12712740224U, 12825741184U, - 12938736256U, 13051741312U, 13164737408U, 13277738368U, 13390738048U, - 13503741824U, 13616741504U, 13729737088U, 13842740096U, 13955741312U, - 14068741504U, 14181740416U, 14294741632U, 14407739776U, 14520740224U, - 14633740928U, 14746736512U, 14859741824U, 14972740736U, 15085740928U, - 15198738304U, 15311732096U, 15424740736U, 15537739904U, 15650741632U, - 15763741568U, 15876737152U, 15989741696U, 16102740608U, 16215741056U, - 16328741248U, 16441740416U, 16554737792U, 16667740288U, 16780740992U, - 16893738112U, 17006741632U, 17119739008U, 17232735616U, 17345739392U, - 17458740352U, 17571736192U, 17684739712U, 17797739392U, 17910740096U, - 18023741312U, 18136740736U, 18249738112U, 18362738816U, 18475735424U, - 18588740224U, 18701738368U, 18814736768U, 18927737216U, 19040739968U, - 19153739648U, 19266736768U, 19379737984U, 19492739456U, 19605738368U, - 19718740352U, 19831741312U, 19944736384U, 20057741696U, 20170741376U, - 20283741824U, 20396737408U, 20509741696U, 20622741376U, 20735739008U, - 20848741504U, 20961740672U, 21074739328U, 21187740032U, 21300739456U, - 21413741696U, 21526740608U, 21639741824U, 21752737408U, 21865741696U, - 21978741376U, 22091741824U, 22204738432U, 22317740672U, 22430740096U, - 22543736704U, 22656741248U, 22769739904U, 22882739584U, 22995740288U, - 23108740736U, 23221740928U, 23334741376U, 23447737216U, 23560740992U, - 23673741184U, 23786740864U, 23899737728U, 24012741248U, 24125734784U, - 24238736512U, 24351741824U, 24464740736U, 24577737088U, 24690741632U, - 24803739776U, 24916740736U, 25029740416U, 25142740864U, 25255741568U, - 25368741248U, 25481740672U, 25594741376U, 25707741568U, 25820741504U, - 25933730432U, 26046739072U, 26159741824U, 26272741504U, 26385740672U, - 26498740096U, 26611741568U, 26724740992U, 26837739904U, 26950735232U, - 27063738496U, 27176741248U, 27289741184U, 27402740864U, 27515740544U, - 27628737152U, 27741740672U, 27854741632U, 27967740544U, 28080739712U, - 28193738368U, 28306741376U, 28419737728U, 28532739968U, 28645739648U, - 28758740096U, 28871741312U, 28984739456U, 29097740416U, 29210740864U, - 29323741312U, 29436740224U, 29549741696U, 29662738304U, 29775741568U, - 29888741504U, 30001740928U, 30114737024U, 30227735168U, 30340737664U, - 30453738368U, 30566737024U, 30679733632U, 30792740224U, 30905740928U, - 31018740352U, 31131740032U, 31244738944U, 31357737344U, 31470741376U, - 31583740544U, 31696740224U, 31809738112U, 31922739328U, 32035737472U, - 32148740992U, 32261741696U, 32374740352U, 32487741824U, 32600740736U, - 32713739648U, 32826740608U, 32939729792U, 33052740992U, 33165740672U, - 33278739584U, 33391741312U, 33504739712U, 33617740928U, 33730740608U, - 33843738496U, 33956739968U, 34069741696U, 34182739328U, 34295741824U, - 34408739968U, 34521740672U, 34634736512U, 34747741568U, 34860741248U, - 34973739392U, 35086738304U, 35199741056U, 35312736896U, 35425741184U, - 35538741376U, 35651740288U, 35764737152U, 35877741184U, 35990739584U, - 36103740544U, 36216740992U, 36329739392U, 36442737536U, 36555741568U, - 36668740736U, 36781741184U, 36894737024U, 37007741312U, 37120739456U, - 37233741184U, 37346736256U, 37459736192U, 37572734336U, 37685739904U, - 37798740352U, 37911737728U, 38024741504U, 38137739648U, 38250740608U, - 38363741824U, 38476740992U, 38589741184U, 38702740096U, 38815741312U, - 38928741248U, 39041738368U, 39154739584U, 39267741824U, 39380739712U, - 39493735808U, 39606741632U, 39719741312U, 39832741504U, 39945739648U, - 40058740352U, 40171740032U, 40284740992U, 40397740672U, 40510740352U, - 40623740288U, 40736738176U, 40849737856U, 40962741376U, 41075739776U, - 41188737664U, 41301735808U, 41414738048U, 41527741312U, 41640740992U, - 41753739904U, 41866739072U, 41979738496U, 42092740736U, 42205739648U, - 42318740608U, 42431741312U, 42544738688U, 42657741184U, 42770738048U, - 42883741568U, 42996741248U, 43109740928U, 43222736512U, 43335741056U, - 43448730496U, 43561740416U, 43674741632U, 43787740544U, 43900741504U, - 44013739648U, 44126740864U, 44239740544U, 44352741248U, 44465738368U, - 44578735232U, 44691739264U, 44804741504U, 44917741696U, 45030741376U, - 45143741824U, 45256740992U, 45369739136U, 45482740096U, 45595739776U, - 45708739712U, 45821740672U, 45934741376U, 46047741056U, 46160741248U, - 46273737088U, 46386740864U, 46499739008U, 46612739968U, 46725735296U, - 46838740864U, 46951741568U, 47064737152U, 47177741696U, 47290741376U, - 47403738752U, 47516741248U, 47629739648U, 47742741632U, 47855737984U, - 47968740224U, 48081738368U, 48194741632U, 48307739264U, 48420739712U, - 48533739136U, 48646738304U, 48759741824U, 48872741504U, 48985739392U, - 49098741376U, 49211741056U, 49324740992U, 49437738368U, 49550740864U, - 49663735424U, 49776737408U, 49889740672U, 50002738816U, 50115738752U, - 50228739712U, 50341741696U, 50454736768U, 50567738752U, 50680739968U, - 50793736832U, 50906734976U, 51019741568U, 51132739456U, 51245741696U, - 51358741376U, 51471741056U, 51584738944U, 51697734272U, 51810739072U, - 51923736448U, 52036740736U, 52149741184U, 52262737024U, 52375738496U, - 52488740992U, 52601739136U, 52714740352U, 52827736448U, 52940738176U, - 53053741696U, 53166740864U, 53279741824U, 53392741504U, 53505739136U, - 53618739584U, 53731741312U, 53844741248U, 53957741696U, 54070741376U, - 54183740288U, 54296741504U, 54409741696U, 54522739072U, 54635737472U, - 54748741504U, 54861736064U, 54974740096U, 55087741568U, 55200733568U, - 55313741696U, 55426734464U, 55539741056U, 55652741504U, 55765741184U, - 55878741376U, 55991730304U, 56104740992U, 56217740672U, 56330731648U, - 56443737472U, 56556724352U, 56669740672U, 56782739072U, 56895740032U, - 57008741248U, 57121741696U, 57234740096U, 57347741312U, 57460741504U +// Reap[ +// While[j < n, +// Module[{i = +// Floor[(CacheSizeBytesInit + CacheGrowth * j) / HashBytes]}, +// While[! PrimeQ[i], i--]; +// Sow[i*HashBytes]; j++]]]][[2]][[1]] + + +static const uint64_t dag_sizes[2048] = { + 1073739904U, 1082130304U, 1090514816U, 1098906752U, 1107293056U, + 1115684224U, 1124070016U, 1132461952U, 1140849536U, 1149232768U, + 1157627776U, 1166013824U, 1174404736U, 1182786944U, 1191180416U, + 1199568512U, 1207958912U, 1216345216U, 1224732032U, 1233124736U, + 1241513344U, 1249902464U, 1258290304U, 1266673792U, 1275067264U, + 1283453312U, 1291844992U, 1300234112U, 1308619904U, 1317010048U, + 1325397376U, 1333787776U, 1342176128U, 1350561664U, 1358954368U, + 1367339392U, 1375731584U, 1384118144U, 1392507008U, 1400897408U, + 1409284736U, 1417673344U, 1426062464U, 1434451072U, 1442839168U, + 1451229056U, 1459615616U, 1468006016U, 1476394112U, 1484782976U, + 1493171584U, 1501559168U, 1509948032U, 1518337664U, 1526726528U, + 1535114624U, 1543503488U, 1551892096U, 1560278656U, 1568669056U, + 1577056384U, 1585446272U, 1593831296U, 1602219392U, 1610610304U, + 1619000192U, 1627386752U, 1635773824U, 1644164224U, 1652555648U, + 1660943488U, 1669332608U, 1677721216U, 1686109312U, 1694497664U, + 1702886272U, 1711274624U, 1719661184U, 1728047744U, 1736434816U, + 1744829056U, 1753218944U, 1761606272U, 1769995904U, 1778382464U, + 1786772864U, 1795157888U, 1803550592U, 1811937664U, 1820327552U, + 1828711552U, 1837102976U, 1845488768U, 1853879936U, 1862269312U, + 1870656896U, 1879048064U, 1887431552U, 1895825024U, 1904212096U, + 1912601216U, 1920988544U, 1929379456U, 1937765504U, 1946156672U, + 1954543232U, 1962932096U, 1971321728U, 1979707264U, 1988093056U, + 1996487552U, 2004874624U, 2013262208U, 2021653888U, 2030039936U, + 2038430848U, 2046819968U, 2055208576U, 2063596672U, 2071981952U, + 2080373632U, 2088762752U, 2097149056U, 2105539712U, 2113928576U, + 2122315136U, 2130700672U, 2139092608U, 2147483264U, 2155872128U, + 2164257664U, 2172642176U, 2181035392U, 2189426048U, 2197814912U, + 2206203008U, 2214587264U, 2222979712U, 2231367808U, 2239758208U, + 2248145024U, 2256527744U, 2264922752U, 2273312128U, 2281701248U, + 2290086272U, 2298476672U, 2306867072U, 2315251072U, 2323639168U, + 2332032128U, 2340420224U, 2348808064U, 2357196416U, 2365580416U, + 2373966976U, 2382363008U, 2390748544U, 2399139968U, 2407530368U, + 2415918976U, 2424307328U, 2432695424U, 2441084288U, 2449472384U, + 2457861248U, 2466247808U, 2474637184U, 2483026816U, 2491414144U, + 2499803776U, 2508191872U, 2516582272U, 2524970368U, 2533359232U, + 2541743488U, 2550134144U, 2558525056U, 2566913408U, 2575301504U, + 2583686528U, 2592073856U, 2600467328U, 2608856192U, 2617240448U, + 2625631616U, 2634022016U, 2642407552U, 2650796416U, 2659188352U, + 2667574912U, 2675965312U, 2684352896U, 2692738688U, 2701130624U, + 2709518464U, 2717907328U, 2726293376U, 2734685056U, 2743073152U, + 2751462016U, 2759851648U, 2768232832U, 2776625536U, 2785017728U, + 2793401984U, 2801794432U, 2810182016U, 2818571648U, 2826959488U, + 2835349376U, 2843734144U, 2852121472U, 2860514432U, 2868900992U, + 2877286784U, 2885676928U, 2894069632U, 2902451584U, 2910843008U, + 2919234688U, 2927622784U, 2936011648U, 2944400768U, 2952789376U, + 2961177728U, 2969565568U, 2977951616U, 2986338944U, 2994731392U, + 3003120256U, 3011508352U, 3019895936U, 3028287104U, 3036675968U, + 3045063808U, 3053452928U, 3061837696U, 3070228352U, 3078615424U, + 3087003776U, 3095394944U, 3103782272U, 3112173184U, 3120562048U, + 3128944768U, 3137339264U, 3145725056U, 3154109312U, 3162505088U, + 3170893184U, 3179280256U, 3187669376U, 3196056704U, 3204445568U, + 3212836736U, 3221224064U, 3229612928U, 3238002304U, 3246391168U, + 3254778496U, 3263165824U, 3271556224U, 3279944576U, 3288332416U, + 3296719232U, 3305110912U, 3313500032U, 3321887104U, 3330273152U, + 3338658944U, 3347053184U, 3355440512U, 3363827072U, 3372220288U, + 3380608384U, 3388997504U, 3397384576U, 3405774208U, 3414163072U, + 3422551936U, 3430937984U, 3439328384U, 3447714176U, 3456104576U, + 3464493952U, 3472883584U, 3481268864U, 3489655168U, 3498048896U, + 3506434432U, 3514826368U, 3523213952U, 3531603584U, 3539987072U, + 3548380288U, 3556763264U, 3565157248U, 3573545344U, 3581934464U, + 3590324096U, 3598712704U, 3607098752U, 3615488384U, 3623877248U, + 3632265856U, 3640646528U, 3649043584U, 3657430144U, 3665821568U, + 3674207872U, 3682597504U, 3690984832U, 3699367808U, 3707764352U, + 3716152448U, 3724541056U, 3732925568U, 3741318016U, 3749706368U, + 3758091136U, 3766481536U, 3774872704U, 3783260032U, 3791650432U, + 3800036224U, 3808427648U, 3816815488U, 3825204608U, 3833592704U, + 3841981568U, 3850370432U, 3858755968U, 3867147904U, 3875536256U, + 3883920512U, 3892313728U, 3900702592U, 3909087872U, 3917478784U, + 3925868416U, 3934256512U, 3942645376U, 3951032192U, 3959422336U, + 3967809152U, 3976200064U, 3984588416U, 3992974976U, 4001363584U, + 4009751168U, 4018141312U, 4026530432U, 4034911616U, 4043308928U, + 4051695488U, 4060084352U, 4068472448U, 4076862848U, 4085249408U, + 4093640576U, 4102028416U, 4110413696U, 4118805632U, 4127194496U, + 4135583104U, 4143971968U, 4152360832U, 4160746112U, 4169135744U, + 4177525888U, 4185912704U, 4194303616U, 4202691968U, 4211076736U, + 4219463552U, 4227855488U, 4236246656U, 4244633728U, 4253022848U, + 4261412224U, 4269799808U, 4278184832U, 4286578048U, 4294962304U, + 4303349632U, 4311743104U, 4320130432U, 4328521088U, 4336909184U, + 4345295488U, 4353687424U, 4362073472U, 4370458496U, 4378852736U, + 4387238528U, 4395630208U, 4404019072U, 4412407424U, 4420790656U, + 4429182848U, 4437571456U, 4445962112U, 4454344064U, 4462738048U, + 4471119232U, 4479516544U, 4487904128U, 4496289664U, 4504682368U, + 4513068416U, 4521459584U, 4529846144U, 4538232704U, 4546619776U, + 4555010176U, 4563402112U, 4571790208U, 4580174464U, 4588567936U, + 4596957056U, 4605344896U, 4613734016U, 4622119808U, 4630511488U, + 4638898816U, 4647287936U, 4655675264U, 4664065664U, 4672451968U, + 4680842624U, 4689231488U, 4697620352U, 4706007424U, 4714397056U, + 4722786176U, 4731173248U, 4739562368U, 4747951744U, 4756340608U, + 4764727936U, 4773114496U, 4781504384U, 4789894784U, 4798283648U, + 4806667648U, 4815059584U, 4823449472U, 4831835776U, 4840226176U, + 4848612224U, 4857003392U, 4865391488U, 4873780096U, 4882169728U, + 4890557312U, 4898946944U, 4907333248U, 4915722368U, 4924110976U, + 4932499328U, 4940889728U, 4949276032U, 4957666432U, 4966054784U, + 4974438016U, 4982831488U, 4991221376U, 4999607168U, 5007998848U, + 5016386432U, 5024763776U, 5033164672U, 5041544576U, 5049941888U, + 5058329728U, 5066717056U, 5075107456U, 5083494272U, 5091883904U, + 5100273536U, 5108662144U, 5117048192U, 5125436032U, 5133827456U, + 5142215296U, 5150605184U, 5158993024U, 5167382144U, 5175769472U, + 5184157568U, 5192543872U, 5200936064U, 5209324928U, 5217711232U, + 5226102656U, 5234490496U, 5242877312U, 5251263872U, 5259654016U, + 5268040832U, 5276434304U, 5284819328U, 5293209728U, 5301598592U, + 5309986688U, 5318374784U, 5326764416U, 5335151488U, 5343542144U, + 5351929472U, 5360319872U, 5368706944U, 5377096576U, 5385484928U, + 5393871232U, 5402263424U, 5410650496U, 5419040384U, 5427426944U, + 5435816576U, 5444205952U, 5452594816U, 5460981376U, 5469367936U, + 5477760896U, 5486148736U, 5494536832U, 5502925952U, 5511315328U, + 5519703424U, 5528089984U, 5536481152U, 5544869504U, 5553256064U, + 5561645696U, 5570032768U, 5578423936U, 5586811264U, 5595193216U, + 5603585408U, 5611972736U, 5620366208U, 5628750464U, 5637143936U, + 5645528192U, 5653921408U, 5662310272U, 5670694784U, 5679082624U, + 5687474048U, 5695864448U, 5704251008U, 5712641408U, 5721030272U, + 5729416832U, 5737806208U, 5746194304U, 5754583936U, 5762969984U, + 5771358592U, 5779748224U, 5788137856U, 5796527488U, 5804911232U, + 5813300608U, 5821692544U, 5830082176U, 5838468992U, 5846855552U, + 5855247488U, 5863636096U, 5872024448U, 5880411008U, 5888799872U, + 5897186432U, 5905576832U, 5913966976U, 5922352768U, 5930744704U, + 5939132288U, 5947522432U, 5955911296U, 5964299392U, 5972688256U, + 5981074304U, 5989465472U, 5997851008U, 6006241408U, 6014627968U, + 6023015552U, 6031408256U, 6039796096U, 6048185216U, 6056574848U, + 6064963456U, 6073351808U, 6081736064U, 6090128768U, 6098517632U, + 6106906496U, 6115289216U, 6123680896U, 6132070016U, 6140459648U, + 6148849024U, 6157237376U, 6165624704U, 6174009728U, 6182403712U, + 6190792064U, 6199176064U, 6207569792U, 6215952256U, 6224345216U, + 6232732544U, 6241124224U, 6249510272U, 6257899136U, 6266287744U, + 6274676864U, 6283065728U, 6291454336U, 6299843456U, 6308232064U, + 6316620928U, 6325006208U, 6333395584U, 6341784704U, 6350174848U, + 6358562176U, 6366951296U, 6375337856U, 6383729536U, 6392119168U, + 6400504192U, 6408895616U, 6417283456U, 6425673344U, 6434059136U, + 6442444672U, 6450837376U, 6459223424U, 6467613056U, 6476004224U, + 6484393088U, 6492781952U, 6501170048U, 6509555072U, 6517947008U, + 6526336384U, 6534725504U, 6543112832U, 6551500672U, 6559888768U, + 6568278656U, 6576662912U, 6585055616U, 6593443456U, 6601834112U, + 6610219648U, 6618610304U, 6626999168U, 6635385472U, 6643777408U, + 6652164224U, 6660552832U, 6668941952U, 6677330048U, 6685719424U, + 6694107776U, 6702493568U, 6710882176U, 6719274112U, 6727662976U, + 6736052096U, 6744437632U, 6752825984U, 6761213824U, 6769604224U, + 6777993856U, 6786383488U, 6794770816U, 6803158144U, 6811549312U, + 6819937664U, 6828326528U, 6836706176U, 6845101696U, 6853491328U, + 6861880448U, 6870269312U, 6878655104U, 6887046272U, 6895433344U, + 6903822208U, 6912212864U, 6920596864U, 6928988288U, 6937377152U, + 6945764992U, 6954149248U, 6962544256U, 6970928768U, 6979317376U, + 6987709312U, 6996093824U, 7004487296U, 7012875392U, 7021258624U, + 7029652352U, 7038038912U, 7046427776U, 7054818944U, 7063207808U, + 7071595136U, 7079980928U, 7088372608U, 7096759424U, 7105149824U, + 7113536896U, 7121928064U, 7130315392U, 7138699648U, 7147092352U, + 7155479168U, 7163865728U, 7172249984U, 7180648064U, 7189036672U, + 7197424768U, 7205810816U, 7214196608U, 7222589824U, 7230975104U, + 7239367552U, 7247755904U, 7256145536U, 7264533376U, 7272921472U, + 7281308032U, 7289694848U, 7298088832U, 7306471808U, 7314864512U, + 7323253888U, 7331643008U, 7340029568U, 7348419712U, 7356808832U, + 7365196672U, 7373585792U, 7381973888U, 7390362752U, 7398750592U, + 7407138944U, 7415528576U, 7423915648U, 7432302208U, 7440690304U, + 7449080192U, 7457472128U, 7465860992U, 7474249088U, 7482635648U, + 7491023744U, 7499412608U, 7507803008U, 7516192384U, 7524579968U, + 7532967296U, 7541358464U, 7549745792U, 7558134656U, 7566524032U, + 7574912896U, 7583300992U, 7591690112U, 7600075136U, 7608466816U, + 7616854912U, 7625244544U, 7633629824U, 7642020992U, 7650410368U, + 7658794112U, 7667187328U, 7675574912U, 7683961984U, 7692349568U, + 7700739712U, 7709130368U, 7717519232U, 7725905536U, 7734295424U, + 7742683264U, 7751069056U, 7759457408U, 7767849088U, 7776238208U, + 7784626816U, 7793014912U, 7801405312U, 7809792128U, 7818179968U, + 7826571136U, 7834957184U, 7843347328U, 7851732352U, 7860124544U, + 7868512384U, 7876902016U, 7885287808U, 7893679744U, 7902067072U, + 7910455936U, 7918844288U, 7927230848U, 7935622784U, 7944009344U, + 7952400256U, 7960786048U, 7969176704U, 7977565312U, 7985953408U, + 7994339968U, 8002730368U, 8011119488U, 8019508096U, 8027896192U, + 8036285056U, 8044674688U, 8053062272U, 8061448832U, 8069838464U, + 8078227328U, 8086616704U, 8095006592U, 8103393664U, 8111783552U, + 8120171392U, 8128560256U, 8136949376U, 8145336704U, 8153726848U, + 8162114944U, 8170503296U, 8178891904U, 8187280768U, 8195669632U, + 8204058496U, 8212444544U, 8220834176U, 8229222272U, 8237612672U, + 8246000768U, 8254389376U, 8262775168U, 8271167104U, 8279553664U, + 8287944064U, 8296333184U, 8304715136U, 8313108352U, 8321497984U, + 8329885568U, 8338274432U, 8346663296U, 8355052928U, 8363441536U, + 8371828352U, 8380217984U, 8388606592U, 8396996224U, 8405384576U, + 8413772672U, 8422161536U, 8430549376U, 8438939008U, 8447326592U, + 8455715456U, 8464104832U, 8472492928U, 8480882048U, 8489270656U, + 8497659776U, 8506045312U, 8514434944U, 8522823808U, 8531208832U, + 8539602304U, 8547990656U, 8556378752U, 8564768384U, 8573154176U, + 8581542784U, 8589933952U, 8598322816U, 8606705024U, 8615099264U, + 8623487872U, 8631876992U, 8640264064U, 8648653952U, 8657040256U, + 8665430656U, 8673820544U, 8682209152U, 8690592128U, 8698977152U, + 8707374464U, 8715763328U, 8724151424U, 8732540032U, 8740928384U, + 8749315712U, 8757704576U, 8766089344U, 8774480768U, 8782871936U, + 8791260032U, 8799645824U, 8808034432U, 8816426368U, 8824812928U, + 8833199488U, 8841591424U, 8849976448U, 8858366336U, 8866757248U, + 8875147136U, 8883532928U, 8891923328U, 8900306816U, 8908700288U, + 8917088384U, 8925478784U, 8933867392U, 8942250368U, 8950644608U, + 8959032704U, 8967420544U, 8975809664U, 8984197504U, 8992584064U, + 9000976256U, 9009362048U, 9017752448U, 9026141312U, 9034530688U, + 9042917504U, 9051307904U, 9059694208U, 9068084864U, 9076471424U, + 9084861824U, 9093250688U, 9101638528U, 9110027648U, 9118416512U, + 9126803584U, 9135188096U, 9143581312U, 9151969664U, 9160356224U, + 9168747136U, 9177134464U, 9185525632U, 9193910144U, 9202302848U, + 9210690688U, 9219079552U, 9227465344U, 9235854464U, 9244244864U, + 9252633472U, 9261021824U, 9269411456U, 9277799296U, 9286188928U, + 9294574208U, 9302965888U, 9311351936U, 9319740032U, 9328131968U, + 9336516736U, 9344907392U, 9353296768U, 9361685888U, 9370074752U, + 9378463616U, 9386849408U, 9395239808U, 9403629184U, 9412016512U, + 9420405376U, 9428795008U, 9437181568U, 9445570688U, 9453960832U, + 9462346624U, 9470738048U, 9479121536U, 9487515008U, 9495903616U, + 9504289664U, 9512678528U, 9521067904U, 9529456256U, 9537843584U, + 9546233728U, 9554621312U, 9563011456U, 9571398784U, 9579788672U, + 9588178304U, 9596567168U, 9604954496U, 9613343104U, 9621732992U, + 9630121856U, 9638508416U, 9646898816U, 9655283584U, 9663675776U, + 9672061312U, 9680449664U, 9688840064U, 9697230464U, 9705617536U, + 9714003584U, 9722393984U, 9730772608U, 9739172224U, 9747561088U, + 9755945344U, 9764338816U, 9772726144U, 9781116544U, 9789503872U, + 9797892992U, 9806282624U, 9814670464U, 9823056512U, 9831439232U, + 9839833984U, 9848224384U, 9856613504U, 9865000576U, 9873391232U, + 9881772416U, 9890162816U, 9898556288U, 9906940544U, 9915333248U, + 9923721088U, 9932108672U, 9940496512U, 9948888448U, 9957276544U, + 9965666176U, 9974048384U, 9982441088U, 9990830464U, 9999219584U, + 10007602816U, 10015996544U, 10024385152U, 10032774016U, 10041163648U, + 10049548928U, 10057940096U, 10066329472U, 10074717824U, 10083105152U, + 10091495296U, 10099878784U, 10108272256U, 10116660608U, 10125049216U, + 10133437312U, 10141825664U, 10150213504U, 10158601088U, 10166991232U, + 10175378816U, 10183766144U, 10192157312U, 10200545408U, 10208935552U, + 10217322112U, 10225712768U, 10234099328U, 10242489472U, 10250876032U, + 10259264896U, 10267656064U, 10276042624U, 10284429184U, 10292820352U, + 10301209472U, 10309598848U, 10317987712U, 10326375296U, 10334763392U, + 10343153536U, 10351541632U, 10359930752U, 10368318592U, 10376707456U, + 10385096576U, 10393484672U, 10401867136U, 10410262144U, 10418647424U, + 10427039104U, 10435425664U, 10443810176U, 10452203648U, 10460589952U, + 10468982144U, 10477369472U, 10485759104U, 10494147712U, 10502533504U, + 10510923392U, 10519313536U, 10527702656U, 10536091264U, 10544478592U, + 10552867712U, 10561255808U, 10569642368U, 10578032768U, 10586423168U, + 10594805632U, 10603200128U, 10611588992U, 10619976064U, 10628361344U, + 10636754048U, 10645143424U, 10653531776U, 10661920384U, 10670307968U, + 10678696832U, 10687086464U, 10695475072U, 10703863168U, 10712246144U, + 10720639616U, 10729026688U, 10737414784U, 10745806208U, 10754190976U, + 10762581376U, 10770971264U, 10779356288U, 10787747456U, 10796135552U, + 10804525184U, 10812915584U, 10821301888U, 10829692288U, 10838078336U, + 10846469248U, 10854858368U, 10863247232U, 10871631488U, 10880023424U, + 10888412032U, 10896799616U, 10905188992U, 10913574016U, 10921964672U, + 10930352768U, 10938742912U, 10947132544U, 10955518592U, 10963909504U, + 10972298368U, 10980687488U, 10989074816U, 10997462912U, 11005851776U, + 11014241152U, 11022627712U, 11031017344U, 11039403904U, 11047793024U, + 11056184704U, 11064570752U, 11072960896U, 11081343872U, 11089737856U, + 11098128256U, 11106514816U, 11114904448U, 11123293568U, 11131680128U, + 11140065152U, 11148458368U, 11156845696U, 11165236864U, 11173624192U, + 11182013824U, 11190402688U, 11198790784U, 11207179136U, 11215568768U, + 11223957376U, 11232345728U, 11240734592U, 11249122688U, 11257511296U, + 11265899648U, 11274285952U, 11282675584U, 11291065472U, 11299452544U, + 11307842432U, 11316231296U, 11324616832U, 11333009024U, 11341395584U, + 11349782656U, 11358172288U, 11366560384U, 11374950016U, 11383339648U, + 11391721856U, 11400117376U, 11408504192U, 11416893568U, 11425283456U, + 11433671552U, 11442061184U, 11450444672U, 11458837888U, 11467226752U, + 11475611776U, 11484003968U, 11492392064U, 11500780672U, 11509169024U, + 11517550976U, 11525944448U, 11534335616U, 11542724224U, 11551111808U, + 11559500672U, 11567890304U, 11576277376U, 11584667008U, 11593056128U, + 11601443456U, 11609830016U, 11618221952U, 11626607488U, 11634995072U, + 11643387776U, 11651775104U, 11660161664U, 11668552576U, 11676940928U, + 11685330304U, 11693718656U, 11702106496U, 11710496128U, 11718882688U, + 11727273088U, 11735660416U, 11744050048U, 11752437376U, 11760824704U, + 11769216128U, 11777604736U, 11785991296U, 11794381952U, 11802770048U, + 11811157888U, 11819548544U, 11827932544U, 11836324736U, 11844713344U, + 11853100928U, 11861486464U, 11869879936U, 11878268032U, 11886656896U, + 11895044992U, 11903433088U, 11911822976U, 11920210816U, 11928600448U, + 11936987264U, 11945375872U, 11953761152U, 11962151296U, 11970543488U, + 11978928512U, 11987320448U, 11995708288U, 12004095104U, 12012486272U, + 12020875136U, 12029255552U, 12037652096U, 12046039168U, 12054429568U, + 12062813824U, 12071206528U, 12079594624U, 12087983744U, 12096371072U, + 12104759936U, 12113147264U, 12121534592U, 12129924992U, 12138314624U, + 12146703232U, 12155091584U, 12163481216U, 12171864704U, 12180255872U, + 12188643968U, 12197034112U, 12205424512U, 12213811328U, 12222199424U, + 12230590336U, 12238977664U, 12247365248U, 12255755392U, 12264143488U, + 12272531584U, 12280920448U, 12289309568U, 12297694592U, 12306086528U, + 12314475392U, 12322865024U, 12331253632U, 12339640448U, 12348029312U, + 12356418944U, 12364805248U, 12373196672U, 12381580928U, 12389969024U, + 12398357632U, 12406750592U, 12415138432U, 12423527552U, 12431916416U, + 12440304512U, 12448692352U, 12457081216U, 12465467776U, 12473859968U, + 12482245504U, 12490636672U, 12499025536U, 12507411584U, 12515801728U, + 12524190592U, 12532577152U, 12540966272U, 12549354368U, 12557743232U, + 12566129536U, 12574523264U, 12582911872U, 12591299456U, 12599688064U, + 12608074624U, 12616463488U, 12624845696U, 12633239936U, 12641631616U, + 12650019968U, 12658407296U, 12666795136U, 12675183232U, 12683574656U, + 12691960192U, 12700350592U, 12708740224U, 12717128576U, 12725515904U, + 12733906816U, 12742295168U, 12750680192U, 12759071872U, 12767460736U, + 12775848832U, 12784236928U, 12792626816U, 12801014656U, 12809404288U, + 12817789312U, 12826181504U, 12834568832U, 12842954624U, 12851345792U, + 12859732352U, 12868122496U, 12876512128U, 12884901248U, 12893289088U, + 12901672832U, 12910067584U, 12918455168U, 12926842496U, 12935232896U, + 12943620736U, 12952009856U, 12960396928U, 12968786816U, 12977176192U, + 12985563776U, 12993951104U, 13002341504U, 13010730368U, 13019115392U, + 13027506304U, 13035895168U, 13044272512U, 13052673152U, 13061062528U, + 13069446272U, 13077838976U, 13086227072U, 13094613632U, 13103000192U, + 13111393664U, 13119782528U, 13128157568U, 13136559232U, 13144945024U, + 13153329536U, 13161724288U, 13170111872U, 13178502784U, 13186884736U, + 13195279744U, 13203667072U, 13212057472U, 13220445824U, 13228832128U, + 13237221248U, 13245610624U, 13254000512U, 13262388352U, 13270777472U, + 13279166336U, 13287553408U, 13295943296U, 13304331904U, 13312719488U, + 13321108096U, 13329494656U, 13337885824U, 13346274944U, 13354663808U, + 13363051136U, 13371439232U, 13379825024U, 13388210816U, 13396605056U, + 13404995456U, 13413380224U, 13421771392U, 13430159744U, 13438546048U, + 13446937216U, 13455326848U, 13463708288U, 13472103808U, 13480492672U, + 13488875648U, 13497269888U, 13505657728U, 13514045312U, 13522435712U, + 13530824576U, 13539210112U, 13547599232U, 13555989376U, 13564379008U, + 13572766336U, 13581154432U, 13589544832U, 13597932928U, 13606320512U, + 13614710656U, 13623097472U, 13631477632U, 13639874944U, 13648264064U, + 13656652928U, 13665041792U, 13673430656U, 13681818496U, 13690207616U, + 13698595712U, 13706982272U, 13715373184U, 13723762048U, 13732150144U, + 13740536704U, 13748926592U, 13757316224U, 13765700992U, 13774090112U, + 13782477952U, 13790869376U, 13799259008U, 13807647872U, 13816036736U, + 13824425344U, 13832814208U, 13841202304U, 13849591424U, 13857978752U, + 13866368896U, 13874754688U, 13883145344U, 13891533184U, 13899919232U, + 13908311168U, 13916692096U, 13925085056U, 13933473152U, 13941866368U, + 13950253696U, 13958643584U, 13967032192U, 13975417216U, 13983807616U, + 13992197504U, 14000582272U, 14008973696U, 14017363072U, 14025752192U, + 14034137984U, 14042528384U, 14050918016U, 14059301504U, 14067691648U, + 14076083584U, 14084470144U, 14092852352U, 14101249664U, 14109635968U, + 14118024832U, 14126407552U, 14134804352U, 14143188608U, 14151577984U, + 14159968384U, 14168357248U, 14176741504U, 14185127296U, 14193521024U, + 14201911424U, 14210301824U, 14218685056U, 14227067264U, 14235467392U, + 14243855488U, 14252243072U, 14260630144U, 14269021568U, 14277409408U, + 14285799296U, 14294187904U, 14302571392U, 14310961792U, 14319353728U, + 14327738752U, 14336130944U, 14344518784U, 14352906368U, 14361296512U, + 14369685376U, 14378071424U, 14386462592U, 14394848128U, 14403230848U, + 14411627392U, 14420013952U, 14428402304U, 14436793472U, 14445181568U, + 14453569664U, 14461959808U, 14470347904U, 14478737024U, 14487122816U, + 14495511424U, 14503901824U, 14512291712U, 14520677504U, 14529064832U, + 14537456768U, 14545845632U, 14554234496U, 14562618496U, 14571011456U, + 14579398784U, 14587789184U, 14596172672U, 14604564608U, 14612953984U, + 14621341312U, 14629724288U, 14638120832U, 14646503296U, 14654897536U, + 14663284864U, 14671675264U, 14680061056U, 14688447616U, 14696835968U, + 14705228416U, 14713616768U, 14722003328U, 14730392192U, 14738784128U, + 14747172736U, 14755561088U, 14763947648U, 14772336512U, 14780725376U, + 14789110144U, 14797499776U, 14805892736U, 14814276992U, 14822670208U, + 14831056256U, 14839444352U, 14847836032U, 14856222848U, 14864612992U, + 14872997504U, 14881388672U, 14889775744U, 14898165376U, 14906553472U, + 14914944896U, 14923329664U, 14931721856U, 14940109696U, 14948497024U, + 14956887424U, 14965276544U, 14973663616U, 14982053248U, 14990439808U, + 14998830976U, 15007216768U, 15015605888U, 15023995264U, 15032385152U, + 15040768384U, 15049154944U, 15057549184U, 15065939072U, 15074328448U, + 15082715008U, 15091104128U, 15099493504U, 15107879296U, 15116269184U, + 15124659584U, 15133042304U, 15141431936U, 15149824384U, 15158214272U, + 15166602368U, 15174991232U, 15183378304U, 15191760512U, 15200154496U, + 15208542592U, 15216931712U, 15225323392U, 15233708416U, 15242098048U, + 15250489216U, 15258875264U, 15267265408U, 15275654528U, 15284043136U, + 15292431488U, 15300819584U, 15309208192U, 15317596544U, 15325986176U, + 15334374784U, 15342763648U, 15351151744U, 15359540608U, 15367929728U, + 15376318336U, 15384706432U, 15393092992U, 15401481856U, 15409869952U, + 15418258816U, 15426649984U, 15435037568U, 15443425664U, 15451815296U, + 15460203392U, 15468589184U, 15476979328U, 15485369216U, 15493755776U, + 15502146944U, 15510534272U, 15518924416U, 15527311232U, 15535699072U, + 15544089472U, 15552478336U, 15560866688U, 15569254528U, 15577642624U, + 15586031488U, 15594419072U, 15602809472U, 15611199104U, 15619586432U, + 15627975296U, 15636364928U, 15644753792U, 15653141888U, 15661529216U, + 15669918848U, 15678305152U, 15686696576U, 15695083136U, 15703474048U, + 15711861632U, 15720251264U, 15728636288U, 15737027456U, 15745417088U, + 15753804928U, 15762194048U, 15770582656U, 15778971008U, 15787358336U, + 15795747712U, 15804132224U, 15812523392U, 15820909696U, 15829300096U, + 15837691264U, 15846071936U, 15854466944U, 15862855808U, 15871244672U, + 15879634816U, 15888020608U, 15896409728U, 15904799104U, 15913185152U, + 15921577088U, 15929966464U, 15938354816U, 15946743424U, 15955129472U, + 15963519872U, 15971907968U, 15980296064U, 15988684928U, 15997073024U, + 16005460864U, 16013851264U, 16022241152U, 16030629248U, 16039012736U, + 16047406976U, 16055794816U, 16064181376U, 16072571264U, 16080957824U, + 16089346688U, 16097737856U, 16106125184U, 16114514816U, 16122904192U, + 16131292544U, 16139678848U, 16148066944U, 16156453504U, 16164839552U, + 16173236096U, 16181623424U, 16190012032U, 16198401152U, 16206790528U, + 16215177344U, 16223567744U, 16231956352U, 16240344704U, 16248731008U, + 16257117824U, 16265504384U, 16273898624U, 16282281856U, 16290668672U, + 16299064192U, 16307449216U, 16315842176U, 16324230016U, 16332613504U, + 16341006464U, 16349394304U, 16357783168U, 16366172288U, 16374561664U, + 16382951296U, 16391337856U, 16399726208U, 16408116352U, 16416505472U, + 16424892032U, 16433282176U, 16441668224U, 16450058624U, 16458448768U, + 16466836864U, 16475224448U, 16483613056U, 16492001408U, 16500391808U, + 16508779648U, 16517166976U, 16525555328U, 16533944192U, 16542330752U, + 16550719616U, 16559110528U, 16567497088U, 16575888512U, 16584274816U, + 16592665472U, 16601051008U, 16609442944U, 16617832064U, 16626218624U, + 16634607488U, 16642996096U, 16651385728U, 16659773824U, 16668163712U, + 16676552576U, 16684938112U, 16693328768U, 16701718144U, 16710095488U, + 16718492288U, 16726883968U, 16735272832U, 16743661184U, 16752049792U, + 16760436608U, 16768827008U, 16777214336U, 16785599104U, 16793992832U, + 16802381696U, 16810768768U, 16819151744U, 16827542656U, 16835934848U, + 16844323712U, 16852711552U, 16861101952U, 16869489536U, 16877876864U, + 16886265728U, 16894653056U, 16903044736U, 16911431296U, 16919821696U, + 16928207488U, 16936592768U, 16944987776U, 16953375616U, 16961763968U, + 16970152832U, 16978540928U, 16986929536U, 16995319168U, 17003704448U, + 17012096896U, 17020481152U, 17028870784U, 17037262208U, 17045649536U, + 17054039936U, 17062426496U, 17070814336U, 17079205504U, 17087592064U, + 17095978112U, 17104369024U, 17112759424U, 17121147776U, 17129536384U, + 17137926016U, 17146314368U, 17154700928U, 17163089792U, 17171480192U, + 17179864192U, 17188256896U, 17196644992U, 17205033856U, 17213423488U, + 17221811072U, 17230198912U, 17238588032U, 17246976896U, 17255360384U, + 17263754624U, 17272143232U, 17280530048U, 17288918912U, 17297309312U, + 17305696384U, 17314085504U, 17322475136U, 17330863744U, 17339252096U, + 17347640192U, 17356026496U, 17364413824U, 17372796544U, 17381190016U, + 17389583488U, 17397972608U, 17406360704U, 17414748544U, 17423135872U, + 17431527296U, 17439915904U, 17448303232U, 17456691584U, 17465081728U, + 17473468288U, 17481857408U, 17490247552U, 17498635904U, 17507022464U, + 17515409024U, 17523801728U, 17532189824U, 17540577664U, 17548966016U, + 17557353344U, 17565741184U, 17574131584U, 17582519168U, 17590907008U, + 17599296128U, 17607687808U, 17616076672U, 17624455808U, 17632852352U, + 17641238656U, 17649630848U, 17658018944U, 17666403968U, 17674794112U, + 17683178368U, 17691573376U, 17699962496U, 17708350592U, 17716739968U, + 17725126528U, 17733517184U, 17741898112U, 17750293888U, 17758673024U, + 17767070336U, 17775458432U, 17783848832U, 17792236928U, 17800625536U, + 17809012352U, 17817402752U, 17825785984U, 17834178944U, 17842563968U, + 17850955648U, 17859344512U, 17867732864U, 17876119424U, 17884511872U, + 17892900224U, 17901287296U, 17909677696U, 17918058112U, 17926451072U, + 17934843776U, 17943230848U, 17951609216U, 17960008576U, 17968397696U, + 17976784256U, 17985175424U, 17993564032U, 18001952128U, 18010339712U, + 18018728576U, 18027116672U, 18035503232U, 18043894144U, 18052283264U, + 18060672128U, 18069056384U, 18077449856U, 18085837184U, 18094225792U, + 18102613376U, 18111004544U, 18119388544U, 18127781248U, 18136170368U, + 18144558976U, 18152947328U, 18161336192U, 18169724288U, 18178108544U, + 18186498944U, 18194886784U, 18203275648U, 18211666048U, 18220048768U, + 18228444544U, 18236833408U, 18245220736U }; -// 500 Epochs worth of tabulated DAG sizes (~3.5 Years) // Generated with the following Mathematica Code: + // GetCacheSizes[n_] := Module[{ -// DAGSizeBytesInit = 2^30, -// MixBytes = 128, -// DAGGrowth = 113000000, -// HashBytes = 64, -// DAGParents = 1024, -// j = 0}, -// Reap[ -// While[j < n, -// Module[{i = Floor[(DAGSizeBytesInit + DAGGrowth * j) / (DAGParents * HashBytes)]}, -// While[! PrimeQ[i], i--]; -// Sow[i*HashBytes]; j++]]]][[2]][[1]] - -const size_t cache_sizes[] = { - 1048384U, 1158208U, 1268416U, 1377856U, 1489856U, 1599296U, 1710656U, - 1820608U, 1930816U, 2041024U, 2151872U, 2261696U, 2371904U, 2482624U, - 2593216U, 2703296U, 2814016U, 2924224U, 3034816U, 3144896U, 3255488U, - 3365312U, 3475904U, 3586624U, 3696064U, 3806272U, 3917504U, 4027456U, - 4138304U, 4248512U, 4359104U, 4469312U, 4579264U, 4689728U, 4797376U, - 4909888U, 5020096U, 5131328U, 5241664U, 5351744U, 5461312U, 5572544U, - 5683264U, 5793472U, 5903552U, 6014144U, 6121664U, 6235072U, 6344896U, - 6454592U, 6565952U, 6675904U, 6786112U, 6896704U, 7006784U, 7117888U, - 7228096U, 7338304U, 7448768U, 7557952U, 7669184U, 7779776U, 7889216U, - 8000192U, 8110912U, 8220736U, 8331712U, 8441536U, 8552384U, 8662592U, - 8772928U, 8883136U, 8993728U, 9103168U, 9214528U, 9323968U, 9434816U, - 9545152U, 9655616U, 9766336U, 9876544U, 9986624U, 10097344U, 10207424U, - 10316864U, 10427968U, 10538432U, 10649152U, 10758976U, 10869568U, 10979776U, - 11089472U, 11200832U, 11309632U, 11420608U, 11531584U, 11641792U, 11751104U, - 11862976U, 11973184U, 12083264U, 12193856U, 12304064U, 12414656U, 12524608U, - 12635072U, 12745792U, 12855616U, 12965824U, 13076416U, 13187008U, 13297216U, - 13407808U, 13518016U, 13627072U, 13738688U, 13848256U, 13959488U, 14069696U, - 14180288U, 14290624U, 14399552U, 14511424U, 14621504U, 14732096U, 14841664U, - 14951744U, 15062336U, 15172672U, 15283264U, 15393088U, 15504448U, 15614272U, - 15723712U, 15834944U, 15945152U, 16055744U, 16165696U, 16277056U, 16387136U, - 16494784U, 16607936U, 16718272U, 16828736U, 16938176U, 17048384U, 17159872U, - 17266624U, 17380544U, 17490496U, 17600192U, 17711296U, 17821376U, 17931968U, - 18041152U, 18152896U, 18261952U, 18373568U, 18483392U, 18594112U, 18703936U, - 18814912U, 18924992U, 19034944U, 19145408U, 19256128U, 19366208U, 19477184U, - 19587136U, 19696576U, 19808192U, 19916992U, 20028352U, 20137664U, 20249024U, - 20358848U, 20470336U, 20580544U, 20689472U, 20801344U, 20911424U, 21020096U, - 21130688U, 21242176U, 21352384U, 21462208U, 21573824U, 21683392U, 21794624U, - 21904448U, 22013632U, 22125248U, 22235968U, 22344512U, 22456768U, 22566848U, - 22677056U, 22786496U, 22897984U, 23008064U, 23118272U, 23228992U, 23338816U, - 23449408U, 23560256U, 23670464U, 23780672U, 23891264U, 24001216U, 24110656U, - 24221888U, 24332608U, 24442688U, 24552512U, 24662464U, 24773696U, 24884032U, - 24994496U, 25105216U, 25215296U, 25324864U, 25435712U, 25546432U, 25655744U, - 25767232U, 25876672U, 25986368U, 26098112U, 26207936U, 26318912U, 26428736U, - 26539712U, 26650048U, 26760256U, 26869184U, 26979776U, 27091136U, 27201728U, - 27311552U, 27422272U, 27532352U, 27642304U, 27752896U, 27863744U, 27973952U, - 28082752U, 28194752U, 28305344U, 28415168U, 28524992U, 28636352U, 28746304U, - 28857152U, 28967104U, 29077184U, 29187904U, 29298496U, 29408576U, 29518912U, - 29628992U, 29739968U, 29850176U, 29960512U, 30070336U, 30180544U, 30290752U, - 30398912U, 30512192U, 30622784U, 30732992U, 30842176U, 30953536U, 31063744U, - 31174336U, 31284544U, 31395136U, 31504448U, 31615552U, 31725632U, 31835072U, - 31946176U, 32057024U, 32167232U, 32277568U, 32387008U, 32497984U, 32608832U, - 32719168U, 32829376U, 32939584U, 33050048U, 33160768U, 33271232U, 33381184U, - 33491648U, 33601856U, 33712576U, 33822016U, 33932992U, 34042816U, 34153024U, - 34263104U, 34373824U, 34485056U, 34594624U, 34704832U, 34816064U, 34926272U, - 35036224U, 35146816U, 35255104U, 35367104U, 35478208U, 35588416U, 35698496U, - 35808832U, 35918656U, 36029888U, 36139456U, 36250688U, 36360512U, 36471104U, - 36581696U, 36691136U, 36802112U, 36912448U, 37022912U, 37132864U, 37242944U, - 37354048U, 37464512U, 37574848U, 37684928U, 37794752U, 37904704U, 38015552U, - 38125888U, 38236864U, 38345792U, 38457152U, 38567744U, 38678336U, 38787776U, - 38897216U, 39009088U, 39117632U, 39230144U, 39340352U, 39450304U, 39560384U, - 39671488U, 39781312U, 39891392U, 40002112U, 40112704U, 40223168U, 40332608U, - 40443968U, 40553792U, 40664768U, 40774208U, 40884416U, 40993984U, 41105984U, - 41215424U, 41326528U, 41436992U, 41546048U, 41655872U, 41768128U, 41878336U, - 41988928U, 42098752U, 42209344U, 42319168U, 42429248U, 42540352U, 42649792U, - 42761024U, 42871616U, 42981824U, 43092032U, 43201856U, 43312832U, 43423552U, - 43533632U, 43643584U, 43753792U, 43864384U, 43974976U, 44084032U, 44195392U, - 44306368U, 44415296U, 44526016U, 44637248U, 44746816U, 44858048U, 44967872U, - 45078848U, 45188288U, 45299264U, 45409216U, 45518272U, 45630272U, 45740224U, - 45850432U, 45960896U, 46069696U, 46182208U, 46292416U, 46402624U, 46512064U, - 46623296U, 46733888U, 46843712U, 46953664U, 47065024U, 47175104U, 47285696U, - 47395904U, 47506496U, 47615296U, 47726912U, 47837632U, 47947712U, 48055232U, - 48168128U, 48277952U, 48387392U, 48499648U, 48609472U, 48720064U, 48830272U, - 48940096U, 49050944U, 49160896U, 49271744U, 49381568U, 49492288U, 49602752U, - 49712576U, 49822016U, 49934272U, 50042816U, 50154304U, 50264128U, 50374336U, - 50484416U, 50596288U, 50706752U, 50816704U, 50927168U, 51035456U, 51146944U, - 51258176U, 51366976U, 51477824U, 51589568U, 51699776U, 51809728U, 51920576U, - 52030016U, 52140736U, 52251328U, 52361152U, 52470592U, 52582592U, 52691776U, - 52803136U, 52912576U, 53020736U, 53132224U, 53242688U, 53354816U, 53465536U, - 53575232U, 53685568U, 53796544U, 53906752U, 54016832U, 54126656U, 54236992U, - 54347456U, 54457408U, 54569024U, 54679232U, 54789184U, 54899776U, 55008832U, - 55119296U, 55231168U, 55341248U, 55451584U, 55562048U, 55672256U, 55782208U, - 55893184U, 56002112U, 56113216U +// DataSetSizeBytesInit = 2^30, +// MixBytes = 128, +// DataSetGrowth = 2^23, +// HashBytes = 64, +// CacheMultiplier = 1024, +// j = 0}, +// Reap[ +// While[j < n, +// Module[{i = Floor[(DataSetSizeBytesInit + DataSetGrowth * j) / (CacheMultiplier * HashBytes)]}, +// While[! PrimeQ[i], i--]; +// Sow[i*HashBytes]; j++]]]][[2]][[1]] + +const uint64_t cache_sizes[2048] = { + 16776896U, 16907456U, 17039296U, 17170112U, 17301056U, 17432512U, 17563072U, + 17693888U, 17824192U, 17955904U, 18087488U, 18218176U, 18349504U, 18481088U, + 18611392U, 18742336U, 18874304U, 19004224U, 19135936U, 19267264U, 19398208U, + 19529408U, 19660096U, 19791424U, 19922752U, 20053952U, 20184896U, 20315968U, + 20446912U, 20576576U, 20709184U, 20840384U, 20971072U, 21102272U, 21233216U, + 21364544U, 21494848U, 21626816U, 21757376U, 21887552U, 22019392U, 22151104U, + 22281536U, 22412224U, 22543936U, 22675264U, 22806464U, 22935872U, 23068096U, + 23198272U, 23330752U, 23459008U, 23592512U, 23723968U, 23854912U, 23986112U, + 24116672U, 24247616U, 24378688U, 24509504U, 24640832U, 24772544U, 24903488U, + 25034432U, 25165376U, 25296704U, 25427392U, 25558592U, 25690048U, 25820096U, + 25951936U, 26081728U, 26214208U, 26345024U, 26476096U, 26606656U, 26737472U, + 26869184U, 26998208U, 27131584U, 27262528U, 27393728U, 27523904U, 27655744U, + 27786688U, 27917888U, 28049344U, 28179904U, 28311488U, 28441792U, 28573504U, + 28700864U, 28835648U, 28966208U, 29096768U, 29228608U, 29359808U, 29490752U, + 29621824U, 29752256U, 29882816U, 30014912U, 30144448U, 30273728U, 30406976U, + 30538432U, 30670784U, 30799936U, 30932672U, 31063744U, 31195072U, 31325248U, + 31456192U, 31588288U, 31719232U, 31850432U, 31981504U, 32110784U, 32243392U, + 32372672U, 32505664U, 32636608U, 32767808U, 32897344U, 33029824U, 33160768U, + 33289664U, 33423296U, 33554368U, 33683648U, 33816512U, 33947456U, 34076992U, + 34208704U, 34340032U, 34471744U, 34600256U, 34734016U, 34864576U, 34993984U, + 35127104U, 35258176U, 35386688U, 35518528U, 35650624U, 35782336U, 35910976U, + 36044608U, 36175808U, 36305728U, 36436672U, 36568384U, 36699968U, 36830656U, + 36961984U, 37093312U, 37223488U, 37355072U, 37486528U, 37617472U, 37747904U, + 37879232U, 38009792U, 38141888U, 38272448U, 38403392U, 38535104U, 38660672U, + 38795584U, 38925632U, 39059264U, 39190336U, 39320768U, 39452096U, 39581632U, + 39713984U, 39844928U, 39974848U, 40107968U, 40238144U, 40367168U, 40500032U, + 40631744U, 40762816U, 40894144U, 41023552U, 41155904U, 41286208U, 41418304U, + 41547712U, 41680448U, 41811904U, 41942848U, 42073792U, 42204992U, 42334912U, + 42467008U, 42597824U, 42729152U, 42860096U, 42991552U, 43122368U, 43253696U, + 43382848U, 43515712U, 43646912U, 43777088U, 43907648U, 44039104U, 44170432U, + 44302144U, 44433344U, 44564288U, 44694976U, 44825152U, 44956864U, 45088448U, + 45219008U, 45350464U, 45481024U, 45612608U, 45744064U, 45874496U, 46006208U, + 46136768U, 46267712U, 46399424U, 46529344U, 46660672U, 46791488U, 46923328U, + 47053504U, 47185856U, 47316928U, 47447872U, 47579072U, 47710144U, 47839936U, + 47971648U, 48103232U, 48234176U, 48365248U, 48496192U, 48627136U, 48757312U, + 48889664U, 49020736U, 49149248U, 49283008U, 49413824U, 49545152U, 49675712U, + 49807168U, 49938368U, 50069056U, 50200256U, 50331584U, 50462656U, 50593472U, + 50724032U, 50853952U, 50986048U, 51117632U, 51248576U, 51379904U, 51510848U, + 51641792U, 51773248U, 51903296U, 52035136U, 52164032U, 52297664U, 52427968U, + 52557376U, 52690112U, 52821952U, 52952896U, 53081536U, 53213504U, 53344576U, + 53475776U, 53608384U, 53738816U, 53870528U, 54000832U, 54131776U, 54263744U, + 54394688U, 54525248U, 54655936U, 54787904U, 54918592U, 55049152U, 55181248U, + 55312064U, 55442752U, 55574336U, 55705024U, 55836224U, 55967168U, 56097856U, + 56228672U, 56358592U, 56490176U, 56621888U, 56753728U, 56884928U, 57015488U, + 57146816U, 57278272U, 57409216U, 57540416U, 57671104U, 57802432U, 57933632U, + 58064576U, 58195264U, 58326976U, 58457408U, 58588864U, 58720192U, 58849984U, + 58981696U, 59113024U, 59243456U, 59375552U, 59506624U, 59637568U, 59768512U, + 59897792U, 60030016U, 60161984U, 60293056U, 60423872U, 60554432U, 60683968U, + 60817216U, 60948032U, 61079488U, 61209664U, 61341376U, 61471936U, 61602752U, + 61733696U, 61865792U, 61996736U, 62127808U, 62259136U, 62389568U, 62520512U, + 62651584U, 62781632U, 62910784U, 63045056U, 63176128U, 63307072U, 63438656U, + 63569216U, 63700928U, 63831616U, 63960896U, 64093888U, 64225088U, 64355392U, + 64486976U, 64617664U, 64748608U, 64879424U, 65009216U, 65142464U, 65273792U, + 65402816U, 65535424U, 65666752U, 65797696U, 65927744U, 66060224U, 66191296U, + 66321344U, 66453056U, 66584384U, 66715328U, 66846656U, 66977728U, 67108672U, + 67239104U, 67370432U, 67501888U, 67631296U, 67763776U, 67895104U, 68026304U, + 68157248U, 68287936U, 68419264U, 68548288U, 68681408U, 68811968U, 68942912U, + 69074624U, 69205568U, 69337024U, 69467584U, 69599168U, 69729472U, 69861184U, + 69989824U, 70122944U, 70253888U, 70385344U, 70515904U, 70647232U, 70778816U, + 70907968U, 71040832U, 71171648U, 71303104U, 71432512U, 71564992U, 71695168U, + 71826368U, 71958464U, 72089536U, 72219712U, 72350144U, 72482624U, 72613568U, + 72744512U, 72875584U, 73006144U, 73138112U, 73268672U, 73400128U, 73530944U, + 73662272U, 73793344U, 73924544U, 74055104U, 74185792U, 74316992U, 74448832U, + 74579392U, 74710976U, 74841664U, 74972864U, 75102784U, 75233344U, 75364544U, + 75497024U, 75627584U, 75759296U, 75890624U, 76021696U, 76152256U, 76283072U, + 76414144U, 76545856U, 76676672U, 76806976U, 76937792U, 77070016U, 77200832U, + 77331392U, 77462464U, 77593664U, 77725376U, 77856448U, 77987776U, 78118336U, + 78249664U, 78380992U, 78511424U, 78642496U, 78773056U, 78905152U, 79033664U, + 79166656U, 79297472U, 79429568U, 79560512U, 79690816U, 79822784U, 79953472U, + 80084672U, 80214208U, 80346944U, 80477632U, 80608576U, 80740288U, 80870848U, + 81002048U, 81133504U, 81264448U, 81395648U, 81525952U, 81657536U, 81786304U, + 81919808U, 82050112U, 82181312U, 82311616U, 82443968U, 82573376U, 82705984U, + 82835776U, 82967744U, 83096768U, 83230528U, 83359552U, 83491264U, 83622464U, + 83753536U, 83886016U, 84015296U, 84147776U, 84277184U, 84409792U, 84540608U, + 84672064U, 84803008U, 84934336U, 85065152U, 85193792U, 85326784U, 85458496U, + 85589312U, 85721024U, 85851968U, 85982656U, 86112448U, 86244416U, 86370112U, + 86506688U, 86637632U, 86769344U, 86900672U, 87031744U, 87162304U, 87293632U, + 87424576U, 87555392U, 87687104U, 87816896U, 87947968U, 88079168U, 88211264U, + 88341824U, 88473152U, 88603712U, 88735424U, 88862912U, 88996672U, 89128384U, + 89259712U, 89390272U, 89521984U, 89652544U, 89783872U, 89914816U, 90045376U, + 90177088U, 90307904U, 90438848U, 90569152U, 90700096U, 90832832U, 90963776U, + 91093696U, 91223744U, 91356992U, 91486784U, 91618496U, 91749824U, 91880384U, + 92012224U, 92143552U, 92273344U, 92405696U, 92536768U, 92666432U, 92798912U, + 92926016U, 93060544U, 93192128U, 93322816U, 93453632U, 93583936U, 93715136U, + 93845056U, 93977792U, 94109504U, 94240448U, 94371776U, 94501184U, 94632896U, + 94764224U, 94895552U, 95023424U, 95158208U, 95287744U, 95420224U, 95550016U, + 95681216U, 95811904U, 95943872U, 96075328U, 96203584U, 96337856U, 96468544U, + 96599744U, 96731072U, 96860992U, 96992576U, 97124288U, 97254848U, 97385536U, + 97517248U, 97647808U, 97779392U, 97910464U, 98041408U, 98172608U, 98303168U, + 98434496U, 98565568U, 98696768U, 98827328U, 98958784U, 99089728U, 99220928U, + 99352384U, 99482816U, 99614272U, 99745472U, 99876416U, 100007104U, + 100138048U, 100267072U, 100401088U, 100529984U, 100662592U, 100791872U, + 100925248U, 101056064U, 101187392U, 101317952U, 101449408U, 101580608U, + 101711296U, 101841728U, 101973824U, 102104896U, 102235712U, 102366016U, + 102498112U, 102628672U, 102760384U, 102890432U, 103021888U, 103153472U, + 103284032U, 103415744U, 103545152U, 103677248U, 103808576U, 103939648U, + 104070976U, 104201792U, 104332736U, 104462528U, 104594752U, 104725952U, + 104854592U, 104988608U, 105118912U, 105247808U, 105381184U, 105511232U, + 105643072U, 105774784U, 105903296U, 106037056U, 106167872U, 106298944U, + 106429504U, 106561472U, 106691392U, 106822592U, 106954304U, 107085376U, + 107216576U, 107346368U, 107478464U, 107609792U, 107739712U, 107872192U, + 108003136U, 108131392U, 108265408U, 108396224U, 108527168U, 108657344U, + 108789568U, 108920384U, 109049792U, 109182272U, 109312576U, 109444928U, + 109572928U, 109706944U, 109837888U, 109969088U, 110099648U, 110230976U, + 110362432U, 110492992U, 110624704U, 110755264U, 110886208U, 111017408U, + 111148864U, 111279296U, 111410752U, 111541952U, 111673024U, 111803456U, + 111933632U, 112066496U, 112196416U, 112328512U, 112457792U, 112590784U, + 112715968U, 112852672U, 112983616U, 113114944U, 113244224U, 113376448U, + 113505472U, 113639104U, 113770304U, 113901376U, 114031552U, 114163264U, + 114294592U, 114425536U, 114556864U, 114687424U, 114818624U, 114948544U, + 115080512U, 115212224U, 115343296U, 115473472U, 115605184U, 115736128U, + 115867072U, 115997248U, 116128576U, 116260288U, 116391488U, 116522944U, + 116652992U, 116784704U, 116915648U, 117046208U, 117178304U, 117308608U, + 117440192U, 117569728U, 117701824U, 117833024U, 117964096U, 118094656U, + 118225984U, 118357312U, 118489024U, 118617536U, 118749632U, 118882112U, + 119012416U, 119144384U, 119275328U, 119406016U, 119537344U, 119668672U, + 119798464U, 119928896U, 120061376U, 120192832U, 120321728U, 120454336U, + 120584512U, 120716608U, 120848192U, 120979136U, 121109056U, 121241408U, + 121372352U, 121502912U, 121634752U, 121764416U, 121895744U, 122027072U, + 122157632U, 122289088U, 122421184U, 122550592U, 122682944U, 122813888U, + 122945344U, 123075776U, 123207488U, 123338048U, 123468736U, 123600704U, + 123731264U, 123861952U, 123993664U, 124124608U, 124256192U, 124386368U, + 124518208U, 124649024U, 124778048U, 124911296U, 125041088U, 125173696U, + 125303744U, 125432896U, 125566912U, 125696576U, 125829056U, 125958592U, + 126090304U, 126221248U, 126352832U, 126483776U, 126615232U, 126746432U, + 126876608U, 127008704U, 127139392U, 127270336U, 127401152U, 127532224U, + 127663552U, 127794752U, 127925696U, 128055232U, 128188096U, 128319424U, + 128449856U, 128581312U, 128712256U, 128843584U, 128973632U, 129103808U, + 129236288U, 129365696U, 129498944U, 129629888U, 129760832U, 129892288U, + 130023104U, 130154048U, 130283968U, 130416448U, 130547008U, 130678336U, + 130807616U, 130939456U, 131071552U, 131202112U, 131331776U, 131464384U, + 131594048U, 131727296U, 131858368U, 131987392U, 132120256U, 132250816U, + 132382528U, 132513728U, 132644672U, 132774976U, 132905792U, 133038016U, + 133168832U, 133299392U, 133429312U, 133562048U, 133692992U, 133823296U, + 133954624U, 134086336U, 134217152U, 134348608U, 134479808U, 134607296U, + 134741056U, 134872384U, 135002944U, 135134144U, 135265472U, 135396544U, + 135527872U, 135659072U, 135787712U, 135921472U, 136052416U, 136182848U, + 136313792U, 136444864U, 136576448U, 136707904U, 136837952U, 136970048U, + 137099584U, 137232064U, 137363392U, 137494208U, 137625536U, 137755712U, + 137887424U, 138018368U, 138149824U, 138280256U, 138411584U, 138539584U, + 138672832U, 138804928U, 138936128U, 139066688U, 139196864U, 139328704U, + 139460032U, 139590208U, 139721024U, 139852864U, 139984576U, 140115776U, + 140245696U, 140376512U, 140508352U, 140640064U, 140769856U, 140902336U, + 141032768U, 141162688U, 141294016U, 141426496U, 141556544U, 141687488U, + 141819584U, 141949888U, 142080448U, 142212544U, 142342336U, 142474432U, + 142606144U, 142736192U, 142868288U, 142997824U, 143129408U, 143258944U, + 143392448U, 143523136U, 143653696U, 143785024U, 143916992U, 144045632U, + 144177856U, 144309184U, 144440768U, 144570688U, 144701888U, 144832448U, + 144965056U, 145096384U, 145227584U, 145358656U, 145489856U, 145620928U, + 145751488U, 145883072U, 146011456U, 146144704U, 146275264U, 146407232U, + 146538176U, 146668736U, 146800448U, 146931392U, 147062336U, 147193664U, + 147324224U, 147455936U, 147586624U, 147717056U, 147848768U, 147979456U, + 148110784U, 148242368U, 148373312U, 148503232U, 148635584U, 148766144U, + 148897088U, 149028416U, 149159488U, 149290688U, 149420224U, 149551552U, + 149683136U, 149814976U, 149943616U, 150076352U, 150208064U, 150338624U, + 150470464U, 150600256U, 150732224U, 150862784U, 150993088U, 151125952U, + 151254976U, 151388096U, 151519168U, 151649728U, 151778752U, 151911104U, + 152042944U, 152174144U, 152304704U, 152435648U, 152567488U, 152698816U, + 152828992U, 152960576U, 153091648U, 153222976U, 153353792U, 153484096U, + 153616192U, 153747008U, 153878336U, 154008256U, 154139968U, 154270912U, + 154402624U, 154533824U, 154663616U, 154795712U, 154926272U, 155057984U, + 155188928U, 155319872U, 155450816U, 155580608U, 155712064U, 155843392U, + 155971136U, 156106688U, 156237376U, 156367424U, 156499264U, 156630976U, + 156761536U, 156892352U, 157024064U, 157155008U, 157284416U, 157415872U, + 157545536U, 157677248U, 157810496U, 157938112U, 158071744U, 158203328U, + 158334656U, 158464832U, 158596288U, 158727616U, 158858048U, 158988992U, + 159121216U, 159252416U, 159381568U, 159513152U, 159645632U, 159776192U, + 159906496U, 160038464U, 160169536U, 160300352U, 160430656U, 160563008U, + 160693952U, 160822208U, 160956352U, 161086784U, 161217344U, 161349184U, + 161480512U, 161611456U, 161742272U, 161873216U, 162002752U, 162135872U, + 162266432U, 162397888U, 162529216U, 162660032U, 162790976U, 162922048U, + 163052096U, 163184576U, 163314752U, 163446592U, 163577408U, 163707968U, + 163839296U, 163969984U, 164100928U, 164233024U, 164364224U, 164494912U, + 164625856U, 164756672U, 164887616U, 165019072U, 165150016U, 165280064U, + 165412672U, 165543104U, 165674944U, 165805888U, 165936832U, 166067648U, + 166198336U, 166330048U, 166461248U, 166591552U, 166722496U, 166854208U, + 166985408U, 167116736U, 167246656U, 167378368U, 167508416U, 167641024U, + 167771584U, 167903168U, 168034112U, 168164032U, 168295744U, 168427456U, + 168557632U, 168688448U, 168819136U, 168951616U, 169082176U, 169213504U, + 169344832U, 169475648U, 169605952U, 169738048U, 169866304U, 169999552U, + 170131264U, 170262464U, 170393536U, 170524352U, 170655424U, 170782016U, + 170917696U, 171048896U, 171179072U, 171310784U, 171439936U, 171573184U, + 171702976U, 171835072U, 171966272U, 172097216U, 172228288U, 172359232U, + 172489664U, 172621376U, 172747712U, 172883264U, 173014208U, 173144512U, + 173275072U, 173407424U, 173539136U, 173669696U, 173800768U, 173931712U, + 174063424U, 174193472U, 174325696U, 174455744U, 174586816U, 174718912U, + 174849728U, 174977728U, 175109696U, 175242688U, 175374272U, 175504832U, + 175636288U, 175765696U, 175898432U, 176028992U, 176159936U, 176291264U, + 176422592U, 176552512U, 176684864U, 176815424U, 176946496U, 177076544U, + 177209152U, 177340096U, 177470528U, 177600704U, 177731648U, 177864256U, + 177994816U, 178126528U, 178257472U, 178387648U, 178518464U, 178650176U, + 178781888U, 178912064U, 179044288U, 179174848U, 179305024U, 179436736U, + 179568448U, 179698496U, 179830208U, 179960512U, 180092608U, 180223808U, + 180354752U, 180485696U, 180617152U, 180748096U, 180877504U, 181009984U, + 181139264U, 181272512U, 181402688U, 181532608U, 181663168U, 181795136U, + 181926592U, 182057536U, 182190016U, 182320192U, 182451904U, 182582336U, + 182713792U, 182843072U, 182976064U, 183107264U, 183237056U, 183368384U, + 183494848U, 183631424U, 183762752U, 183893824U, 184024768U, 184154816U, + 184286656U, 184417984U, 184548928U, 184680128U, 184810816U, 184941248U, + 185072704U, 185203904U, 185335616U, 185465408U, 185596352U, 185727296U, + 185859904U, 185989696U, 186121664U, 186252992U, 186383552U, 186514112U, + 186645952U, 186777152U, 186907328U, 187037504U, 187170112U, 187301824U, + 187429184U, 187562048U, 187693504U, 187825472U, 187957184U, 188087104U, + 188218304U, 188349376U, 188481344U, 188609728U, 188743616U, 188874304U, + 189005248U, 189136448U, 189265088U, 189396544U, 189528128U, 189660992U, + 189791936U, 189923264U, 190054208U, 190182848U, 190315072U, 190447424U, + 190577984U, 190709312U, 190840768U, 190971328U, 191102656U, 191233472U, + 191364032U, 191495872U, 191626816U, 191758016U, 191888192U, 192020288U, + 192148928U, 192282176U, 192413504U, 192542528U, 192674752U, 192805952U, + 192937792U, 193068608U, 193198912U, 193330496U, 193462208U, 193592384U, + 193723456U, 193854272U, 193985984U, 194116672U, 194247232U, 194379712U, + 194508352U, 194641856U, 194772544U, 194900672U, 195035072U, 195166016U, + 195296704U, 195428032U, 195558592U, 195690304U, 195818176U, 195952576U, + 196083392U, 196214336U, 196345792U, 196476736U, 196607552U, 196739008U, + 196869952U, 197000768U, 197130688U, 197262784U, 197394368U, 197523904U, + 197656384U, 197787584U, 197916608U, 198049472U, 198180544U, 198310208U, + 198442432U, 198573632U, 198705088U, 198834368U, 198967232U, 199097792U, + 199228352U, 199360192U, 199491392U, 199621696U, 199751744U, 199883968U, + 200014016U, 200146624U, 200276672U, 200408128U, 200540096U, 200671168U, + 200801984U, 200933312U, 201062464U, 201194944U, 201326144U, 201457472U, + 201588544U, 201719744U, 201850816U, 201981632U, 202111552U, 202244032U, + 202374464U, 202505152U, 202636352U, 202767808U, 202898368U, 203030336U, + 203159872U, 203292608U, 203423296U, 203553472U, 203685824U, 203816896U, + 203947712U, 204078272U, 204208192U, 204341056U, 204472256U, 204603328U, + 204733888U, 204864448U, 204996544U, 205125568U, 205258304U, 205388864U, + 205517632U, 205650112U, 205782208U, 205913536U, 206044736U, 206176192U, + 206307008U, 206434496U, 206569024U, 206700224U, 206831168U, 206961856U, + 207093056U, 207223616U, 207355328U, 207486784U, 207616832U, 207749056U, + 207879104U, 208010048U, 208141888U, 208273216U, 208404032U, 208534336U, + 208666048U, 208796864U, 208927424U, 209059264U, 209189824U, 209321792U, + 209451584U, 209582656U, 209715136U, 209845568U, 209976896U, 210106432U, + 210239296U, 210370112U, 210501568U, 210630976U, 210763712U, 210894272U, + 211024832U, 211156672U, 211287616U, 211418176U, 211549376U, 211679296U, + 211812032U, 211942592U, 212074432U, 212204864U, 212334016U, 212467648U, + 212597824U, 212727616U, 212860352U, 212991424U, 213120832U, 213253952U, + 213385024U, 213515584U, 213645632U, 213777728U, 213909184U, 214040128U, + 214170688U, 214302656U, 214433728U, 214564544U, 214695232U, 214826048U, + 214956992U, 215089088U, 215219776U, 215350592U, 215482304U, 215613248U, + 215743552U, 215874752U, 216005312U, 216137024U, 216267328U, 216399296U, + 216530752U, 216661696U, 216790592U, 216923968U, 217054528U, 217183168U, + 217316672U, 217448128U, 217579072U, 217709504U, 217838912U, 217972672U, + 218102848U, 218233024U, 218364736U, 218496832U, 218627776U, 218759104U, + 218888896U, 219021248U, 219151936U, 219281728U, 219413056U, 219545024U, + 219675968U, 219807296U, 219938624U, 220069312U, 220200128U, 220331456U, + 220461632U, 220592704U, 220725184U, 220855744U, 220987072U, 221117888U, + 221249216U, 221378368U, 221510336U, 221642048U, 221772736U, 221904832U, + 222031808U, 222166976U, 222297536U, 222428992U, 222559936U, 222690368U, + 222820672U, 222953152U, 223083968U, 223213376U, 223345984U, 223476928U, + 223608512U, 223738688U, 223869376U, 224001472U, 224132672U, 224262848U, + 224394944U, 224524864U, 224657344U, 224788288U, 224919488U, 225050432U, + 225181504U, 225312704U, 225443776U, 225574592U, 225704768U, 225834176U, + 225966784U, 226097216U, 226229824U, 226360384U, 226491712U, 226623424U, + 226754368U, 226885312U, 227015104U, 227147456U, 227278528U, 227409472U, + 227539904U, 227669696U, 227802944U, 227932352U, 228065216U, 228196288U, + 228326464U, 228457792U, 228588736U, 228720064U, 228850112U, 228981056U, + 229113152U, 229243328U, 229375936U, 229505344U, 229636928U, 229769152U, + 229894976U, 230030272U, 230162368U, 230292416U, 230424512U, 230553152U, + 230684864U, 230816704U, 230948416U, 231079616U, 231210944U, 231342016U, + 231472448U, 231603776U, 231733952U, 231866176U, 231996736U, 232127296U, + 232259392U, 232388672U, 232521664U, 232652608U, 232782272U, 232914496U, + 233043904U, 233175616U, 233306816U, 233438528U, 233569984U, 233699776U, + 233830592U, 233962688U, 234092224U, 234221888U, 234353984U, 234485312U, + 234618304U, 234749888U, 234880832U, 235011776U, 235142464U, 235274048U, + 235403456U, 235535936U, 235667392U, 235797568U, 235928768U, 236057152U, + 236190272U, 236322752U, 236453312U, 236583616U, 236715712U, 236846528U, + 236976448U, 237108544U, 237239104U, 237371072U, 237501632U, 237630784U, + 237764416U, 237895232U, 238026688U, 238157632U, 238286912U, 238419392U, + 238548032U, 238681024U, 238812608U, 238941632U, 239075008U, 239206336U, + 239335232U, 239466944U, 239599168U, 239730496U, 239861312U, 239992384U, + 240122816U, 240254656U, 240385856U, 240516928U, 240647872U, 240779072U, + 240909632U, 241040704U, 241171904U, 241302848U, 241433408U, 241565248U, + 241696192U, 241825984U, 241958848U, 242088256U, 242220224U, 242352064U, + 242481856U, 242611648U, 242744896U, 242876224U, 243005632U, 243138496U, + 243268672U, 243400384U, 243531712U, 243662656U, 243793856U, 243924544U, + 244054592U, 244187072U, 244316608U, 244448704U, 244580032U, 244710976U, + 244841536U, 244972864U, 245104448U, 245233984U, 245365312U, 245497792U, + 245628736U, 245759936U, 245889856U, 246021056U, 246152512U, 246284224U, + 246415168U, 246545344U, 246675904U, 246808384U, 246939584U, 247070144U, + 247199552U, 247331648U, 247463872U, 247593536U, 247726016U, 247857088U, + 247987648U, 248116928U, 248249536U, 248380736U, 248512064U, 248643008U, + 248773312U, 248901056U, 249036608U, 249167552U, 249298624U, 249429184U, + 249560512U, 249692096U, 249822784U, 249954112U, 250085312U, 250215488U, + 250345792U, 250478528U, 250608704U, 250739264U, 250870976U, 251002816U, + 251133632U, 251263552U, 251395136U, 251523904U, 251657792U, 251789248U, + 251919424U, 252051392U, 252182464U, 252313408U, 252444224U, 252575552U, + 252706624U, 252836032U, 252968512U, 253099712U, 253227584U, 253361728U, + 253493056U, 253623488U, 253754432U, 253885504U, 254017216U, 254148032U, + 254279488U, 254410432U, 254541376U, 254672576U, 254803264U, 254933824U, + 255065792U, 255196736U, 255326528U, 255458752U, 255589952U, 255721408U, + 255851072U, 255983296U, 256114624U, 256244416U, 256374208U, 256507712U, + 256636096U, 256768832U, 256900544U, 257031616U, 257162176U, 257294272U, + 257424448U, 257555776U, 257686976U, 257818432U, 257949632U, 258079552U, + 258211136U, 258342464U, 258473408U, 258603712U, 258734656U, 258867008U, + 258996544U, 259127744U, 259260224U, 259391296U, 259522112U, 259651904U, + 259784384U, 259915328U, 260045888U, 260175424U, 260308544U, 260438336U, + 260570944U, 260700992U, 260832448U, 260963776U, 261092672U, 261226304U, + 261356864U, 261487936U, 261619648U, 261750592U, 261879872U, 262011968U, + 262143424U, 262274752U, 262404416U, 262537024U, 262667968U, 262799296U, + 262928704U, 263061184U, 263191744U, 263322944U, 263454656U, 263585216U, + 263716672U, 263847872U, 263978944U, 264108608U, 264241088U, 264371648U, + 264501184U, 264632768U, 264764096U, 264895936U, 265024576U, 265158464U, + 265287488U, 265418432U, 265550528U, 265681216U, 265813312U, 265943488U, + 266075968U, 266206144U, 266337728U, 266468032U, 266600384U, 266731072U, + 266862272U, 266993344U, 267124288U, 267255616U, 267386432U, 267516992U, + 267648704U, 267777728U, 267910592U, 268040512U, 268172096U, 268302784U, + 268435264U, 268566208U, 268696256U, 268828096U, 268959296U, 269090368U, + 269221312U, 269352256U, 269482688U, 269614784U, 269745856U, 269876416U, + 270007616U, 270139328U, 270270272U, 270401216U, 270531904U, 270663616U, + 270791744U, 270924736U, 271056832U, 271186112U, 271317184U, 271449536U, + 271580992U, 271711936U, 271843136U, 271973056U, 272105408U, 272236352U, + 272367296U, 272498368U, 272629568U, 272759488U, 272891456U, 273022784U, + 273153856U, 273284672U, 273415616U, 273547072U, 273677632U, 273808448U, + 273937088U, 274071488U, 274200896U, 274332992U, 274463296U, 274595392U, + 274726208U, 274857536U, 274988992U, 275118656U, 275250496U, 275382208U, + 275513024U, 275643968U, 275775296U, 275906368U, 276037184U, 276167872U, + 276297664U, 276429376U, 276560576U, 276692672U, 276822976U, 276955072U, + 277085632U, 277216832U, 277347008U, 277478848U, 277609664U, 277740992U, + 277868608U, 278002624U, 278134336U, 278265536U, 278395328U, 278526784U, + 278657728U, 278789824U, 278921152U, 279052096U, 279182912U, 279313088U, + 279443776U, 279576256U, 279706048U, 279838528U, 279969728U, 280099648U, + 280230976U, 280361408U, 280493632U, 280622528U, 280755392U, 280887104U, + 281018176U, 281147968U, 281278912U, 281411392U, 281542592U, 281673152U, + 281803712U, 281935552U, 282066496U, 282197312U, 282329024U, 282458816U, + 282590272U, 282720832U, 282853184U, 282983744U, 283115072U, 283246144U, + 283377344U, 283508416U, 283639744U, 283770304U, 283901504U, 284032576U, + 284163136U, 284294848U, 284426176U, 284556992U, 284687296U, 284819264U, + 284950208U, 285081536U }; #ifdef __cplusplus diff --git a/libethash/ethash.h b/libethash/ethash.h index 918a77413..7594fc835 100644 --- a/libethash/ethash.h +++ b/libethash/ethash.h @@ -1,19 +1,20 @@ /* - This file is part of cpp-ethereum. + This file is part of ethash. - cpp-ethereum is free software: you can redistribute it and/or modify + ethash 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, + ethash 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 . + along with ethash. If not, see . */ + /** @file ethash.h * @date 2015 */ @@ -23,70 +24,115 @@ #include #include #include +#include #include "compiler.h" -#define REVISION 19 -#define DAGSIZE_BYTES_INIT 1073741824U // 2**30 -#define DAG_GROWTH 113000000U -#define EPOCH_LENGTH 30000U -#define MIX_BYTES 128 -#define DAG_PARENTS 256 -#define CACHE_ROUNDS 3 -#define ACCESSES 64 +#define ETHASH_REVISION 23 +#define ETHASH_DATASET_BYTES_INIT 1073741824U // 2**30 +#define ETHASH_DATASET_BYTES_GROWTH 8388608U // 2**23 +#define ETHASH_CACHE_BYTES_INIT 1073741824U // 2**24 +#define ETHASH_CACHE_BYTES_GROWTH 131072U // 2**17 +#define ETHASH_EPOCH_LENGTH 30000U +#define ETHASH_MIX_BYTES 128 +#define ETHASH_HASH_BYTES 64 +#define ETHASH_DATASET_PARENTS 256 +#define ETHASH_CACHE_ROUNDS 3 +#define ETHASH_ACCESSES 64 #ifdef __cplusplus extern "C" { #endif typedef struct ethash_params { - size_t full_size; // Size of full data set (in bytes, multiple of mix size (128)). - size_t cache_size; // Size of compute cache (in bytes, multiple of node size (64)). + uint64_t full_size; // Size of full data set (in bytes, multiple of mix size (128)). + uint64_t cache_size; // Size of compute cache (in bytes, multiple of node size (64)). } ethash_params; typedef struct ethash_return_value { - uint8_t result[32]; - uint8_t mix_hash[32]; + uint8_t result[32]; + uint8_t mix_hash[32]; } ethash_return_value; -size_t ethash_get_datasize(const uint32_t block_number); -size_t ethash_get_cachesize(const uint32_t block_number); +uint64_t ethash_get_datasize(const uint32_t block_number); +uint64_t ethash_get_cachesize(const uint32_t block_number); // initialize the parameters static inline void ethash_params_init(ethash_params *params, const uint32_t block_number) { - params->full_size = ethash_get_datasize(block_number); - params->cache_size = ethash_get_cachesize(block_number); + params->full_size = ethash_get_datasize(block_number); + params->cache_size = ethash_get_cachesize(block_number); } -typedef struct ethash_cache { - void *mem; -} ethash_cache; +/*********************************** + * OLD API ************************* + *********************************** + ******************** (deprecated) * + ***********************************/ -void ethash_mkcache(ethash_cache *cache, ethash_params const *params, const uint8_t seed[32]); -void ethash_compute_full_data(void *mem, ethash_params const *params, ethash_cache const *cache); +void ethash_get_seedhash(uint8_t seedhash[32], const uint32_t block_number); +void ethash_mkcache(void *cache, ethash_params const *params, const uint8_t seed[32]); +void ethash_light(ethash_return_value *ret, void const *cache, ethash_params const *params, const uint8_t header_hash[32], const uint64_t nonce); +void ethash_compute_full_data(void *mem, ethash_params const *params, void const *cache); void ethash_full(ethash_return_value *ret, void const *full_mem, ethash_params const *params, const uint8_t header_hash[32], const uint64_t nonce); -void ethash_light(ethash_return_value *ret, ethash_cache const *cache, ethash_params const *params, const uint8_t header_hash[32], const uint64_t nonce); - -static inline void ethash_prep_light(void *cache, ethash_params const *params, const uint8_t seed[32]) { ethash_cache c; c.mem = cache; ethash_mkcache(&c, params, seed); } -static inline void ethash_compute_light(ethash_return_value *ret, void const *cache, ethash_params const *params, const uint8_t header_hash[32], const uint64_t nonce) { ethash_cache c; c.mem = (void*)cache; ethash_light(ret, &c, params, header_hash, nonce); } -static inline void ethash_prep_full(void *full, ethash_params const *params, void const *cache) { ethash_cache c; c.mem = (void*)cache; ethash_compute_full_data(full, params, &c); } -static inline void ethash_compute_full(ethash_return_value *ret, void const *full, ethash_params const *params, const uint8_t header_hash[32], const uint64_t nonce) { ethash_full(ret, full, params, header_hash, nonce); } - -static inline int ethash_check_difficulty( - const uint8_t hash[32], - const uint8_t difficulty[32]) { - // Difficulty is big endian - for (int i = 0; i < 32; i++) { - if (hash[i] == difficulty[i]) continue; - return hash[i] < difficulty[i]; - } + +/*********************************** + * NEW API ************************* + ***********************************/ + +// TODO: compute params and seed in ethash_new_light; it should take only block_number +// TODO: store params in ethash_light_t/ethash_full_t to avoid having to repass into compute/new_full + +typedef uint8_t const ethash_seedhash_t[32]; + +typedef void const* ethash_light_t; +static inline ethash_light_t ethash_new_light(ethash_params const* params, ethash_seedhash_t seed) { + void* ret = malloc(params->cache_size); + ethash_mkcache(ret, params, seed); + return ret; +} +static inline void ethash_compute_light(ethash_return_value *ret, ethash_light_t light, ethash_params const *params, const uint8_t header_hash[32], const uint64_t nonce) { + ethash_light(ret, light, params, header_hash, nonce); +} +static inline void ethash_delete_light(ethash_light_t light) { + free((void*)light); +} + +typedef void const* ethash_full_t; +static inline ethash_full_t ethash_new_full(ethash_params const* params, ethash_light_t light) { + void* ret = malloc(params->full_size); + ethash_compute_full_data(ret, params, light); + return ret; +} +static inline void ethash_prep_full(void *full, ethash_params const *params, void const *cache) { + ethash_compute_full_data(full, params, cache); +} +static inline void ethash_compute_full(ethash_return_value *ret, void const *full, ethash_params const *params, const uint8_t header_hash[32], const uint64_t nonce) { + ethash_full(ret, full, params, header_hash, nonce); +} + +/// @brief Compare two s256-bit big-endian values. +/// @returns 1 if @a a is less than or equal to @a b, 0 otherwise. +/// Both parameters are 256-bit big-endian values. +static inline int ethash_leq_be256(const uint8_t a[32], const uint8_t b[32]) { + // Boundary is big endian + for (int i = 0; i < 32; i++) { + if (a[i] == b[i]) + continue; + return a[i] < b[i]; + } return 1; } -int ethash_quick_check_difficulty( - const uint8_t header_hash[32], - const uint64_t nonce, - const uint8_t mix_hash[32], - const uint8_t difficulty[32]); +/// Perofrms a cursory check on the validity of the nonce. +/// @returns 1 if the nonce may possibly be valid for the given header_hash & boundary. +/// @p boundary equivalent to 2 ^ 256 / block_difficulty, represented as a 256-bit big-endian. +int ethash_preliminary_check_boundary( + const uint8_t header_hash[32], + const uint64_t nonce, + const uint8_t mix_hash[32], + const uint8_t boundary[32]); + +#define ethash_quick_check_difficulty ethash_preliminary_check_boundary +#define ethash_check_difficulty ethash_leq_be256 #ifdef __cplusplus } diff --git a/libethash/internal.c b/libethash/internal.c index a2b82d375..130ca13c3 100644 --- a/libethash/internal.c +++ b/libethash/internal.c @@ -1,12 +1,12 @@ /* - This file is part of cpp-ethereum. + This file is part of ethash. - cpp-ethereum is free software: you can redistribute it and/or modify + ethash 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, + ethash 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. @@ -14,7 +14,7 @@ You should have received a copy of the GNU General Public License along with cpp-ethereum. If not, see . */ -/** @file dash.cpp +/** @file internal.c * @author Tim Hughes * @author Matthew Wampler-Doty * @date 2015 @@ -37,14 +37,14 @@ #include "sha3.h" #endif // WITH_CRYPTOPP -size_t ethash_get_datasize(const uint32_t block_number) { - assert(block_number / EPOCH_LENGTH < 500); - return dag_sizes[block_number / EPOCH_LENGTH]; +uint64_t ethash_get_datasize(const uint32_t block_number) { + assert(block_number / ETHASH_EPOCH_LENGTH < 2048); + return dag_sizes[block_number / ETHASH_EPOCH_LENGTH]; } -size_t ethash_get_cachesize(const uint32_t block_number) { - assert(block_number / EPOCH_LENGTH < 500); - return cache_sizes[block_number / EPOCH_LENGTH]; +uint64_t ethash_get_cachesize(const uint32_t block_number) { + assert(block_number / ETHASH_EPOCH_LENGTH < 2048); + return cache_sizes[block_number / ETHASH_EPOCH_LENGTH]; } // Follows Sergio's "STRICT MEMORY HARD HASHING FUNCTIONS" (2014) @@ -55,7 +55,7 @@ void static ethash_compute_cache_nodes( ethash_params const *params, const uint8_t seed[32]) { assert((params->cache_size % sizeof(node)) == 0); - uint32_t const num_nodes = (uint32_t)(params->cache_size / sizeof(node)); + uint32_t const num_nodes = (uint32_t) (params->cache_size / sizeof(node)); SHA3_512(nodes[0].bytes, seed, 32); @@ -63,15 +63,14 @@ void static ethash_compute_cache_nodes( SHA3_512(nodes[i].bytes, nodes[i - 1].bytes, 64); } - for (unsigned j = 0; j != CACHE_ROUNDS; j++) { + for (unsigned j = 0; j != ETHASH_CACHE_ROUNDS; j++) { for (unsigned i = 0; i != num_nodes; i++) { uint32_t const idx = nodes[i].words[0] % num_nodes; node data; data = nodes[(num_nodes - 1 + i) % num_nodes]; - for (unsigned w = 0; w != NODE_WORDS; ++w) - { - data.words[w] ^= nodes[idx].words[w]; - } + for (unsigned w = 0; w != NODE_WORDS; ++w) { + data.words[w] ^= nodes[idx].words[w]; + } SHA3_512(nodes[i].bytes, data.bytes, sizeof(data)); } } @@ -86,10 +85,10 @@ void static ethash_compute_cache_nodes( } void ethash_mkcache( - ethash_cache *cache, + void *cache, ethash_params const *params, const uint8_t seed[32]) { - node *nodes = (node *) cache->mem; + node *nodes = (node *) cache; ethash_compute_cache_nodes(nodes, params, seed); } @@ -97,15 +96,15 @@ void ethash_calculate_dag_item( node *const ret, const unsigned node_index, const struct ethash_params *params, - const struct ethash_cache *cache) { + const void *cache) { - uint32_t num_parent_nodes = (uint32_t)(params->cache_size / sizeof(node)); - node const *cache_nodes = (node const *) cache->mem; + uint32_t num_parent_nodes = (uint32_t) (params->cache_size / sizeof(node)); + node const *cache_nodes = (node const *) cache; node const *init = &cache_nodes[node_index % num_parent_nodes]; - memcpy(ret, init, sizeof(node)); - ret->words[0] ^= node_index; - SHA3_512(ret->bytes, ret->bytes, sizeof(node)); + memcpy(ret, init, sizeof(node)); + ret->words[0] ^= node_index; + SHA3_512(ret->bytes, ret->bytes, sizeof(node)); #if defined(_M_X64) && ENABLE_SSE __m128i const fnv_prime = _mm_set1_epi32(FNV_PRIME); @@ -115,12 +114,11 @@ void ethash_calculate_dag_item( __m128i xmm3 = ret->xmm[3]; #endif - for (unsigned i = 0; i != DAG_PARENTS; ++i) - { - uint32_t parent_index = ((node_index ^ i)*FNV_PRIME ^ ret->words[i % NODE_WORDS]) % num_parent_nodes; + for (unsigned i = 0; i != ETHASH_DATASET_PARENTS; ++i) { + uint32_t parent_index = ((node_index ^ i) * FNV_PRIME ^ ret->words[i % NODE_WORDS]) % num_parent_nodes; node const *parent = &cache_nodes[parent_index]; - #if defined(_M_X64) && ENABLE_SSE +#if defined(_M_X64) && ENABLE_SSE { xmm0 = _mm_mullo_epi32(xmm0, fnv_prime); xmm1 = _mm_mullo_epi32(xmm1, fnv_prime); @@ -143,16 +141,16 @@ void ethash_calculate_dag_item( ret->words[w] = fnv_hash(ret->words[w], parent->words[w]); } } - #endif +#endif } - SHA3_512(ret->bytes, ret->bytes, sizeof(node)); + SHA3_512(ret->bytes, ret->bytes, sizeof(node)); } void ethash_compute_full_data( void *mem, ethash_params const *params, - ethash_cache const *cache) { + void const *cache) { assert((params->full_size % (sizeof(uint32_t) * MIX_WORDS)) == 0); assert((params->full_size % sizeof(node)) == 0); node *full_nodes = mem; @@ -164,9 +162,9 @@ void ethash_compute_full_data( } static void ethash_hash( - ethash_return_value * ret, + ethash_return_value *ret, node const *full_nodes, - ethash_cache const *cache, + void const *cache, ethash_params const *params, const uint8_t header_hash[32], const uint64_t nonce) { @@ -174,7 +172,7 @@ static void ethash_hash( assert((params->full_size % MIX_WORDS) == 0); // pack hash and nonce together into first 40 bytes of s_mix - assert(sizeof(node)*8 == 512); + assert(sizeof(node) * 8 == 512); node s_mix[MIX_NODES + 1]; memcpy(s_mix[0].bytes, header_hash, 32); @@ -193,23 +191,21 @@ static void ethash_hash( } #endif - node* const mix = s_mix + 1; + node *const mix = s_mix + 1; for (unsigned w = 0; w != MIX_WORDS; ++w) { mix->words[w] = s_mix[0].words[w % NODE_WORDS]; } unsigned const page_size = sizeof(uint32_t) * MIX_WORDS, - num_full_pages = (unsigned)(params->full_size / page_size); + num_full_pages = (unsigned) (params->full_size / page_size); - for (unsigned i = 0; i != ACCESSES; ++i) - { - uint32_t const index = ((s_mix->words[0] ^ i)*FNV_PRIME ^ mix->words[i % MIX_WORDS]) % num_full_pages; + for (unsigned i = 0; i != ETHASH_ACCESSES; ++i) { + uint32_t const index = ((s_mix->words[0] ^ i) * FNV_PRIME ^ mix->words[i % MIX_WORDS]) % num_full_pages; - for (unsigned n = 0; n != MIX_NODES; ++n) - { - const node * dag_node = &full_nodes[MIX_NODES * index + n]; + for (unsigned n = 0; n != MIX_NODES; ++n) { + const node *dag_node = &full_nodes[MIX_NODES * index + n]; if (!full_nodes) { node tmp_node; @@ -217,7 +213,7 @@ static void ethash_hash( dag_node = &tmp_node; } - #if defined(_M_X64) && ENABLE_SSE +#if defined(_M_X64) && ENABLE_SSE { __m128i fnv_prime = _mm_set1_epi32(FNV_PRIME); __m128i xmm0 = _mm_mullo_epi32(fnv_prime, mix[n].xmm[0]); @@ -235,20 +231,19 @@ static void ethash_hash( mix[n].words[w] = fnv_hash(mix[n].words[w], dag_node->words[w]); } } - #endif +#endif } } - // compress mix - for (unsigned w = 0; w != MIX_WORDS; w += 4) - { - uint32_t reduction = mix->words[w+0]; - reduction = reduction*FNV_PRIME ^ mix->words[w+1]; - reduction = reduction*FNV_PRIME ^ mix->words[w+2]; - reduction = reduction*FNV_PRIME ^ mix->words[w+3]; - mix->words[w/4] = reduction; - } + // compress mix + for (unsigned w = 0; w != MIX_WORDS; w += 4) { + uint32_t reduction = mix->words[w + 0]; + reduction = reduction * FNV_PRIME ^ mix->words[w + 1]; + reduction = reduction * FNV_PRIME ^ mix->words[w + 2]; + reduction = reduction * FNV_PRIME ^ mix->words[w + 3]; + mix->words[w / 4] = reduction; + } #if BYTE_ORDER != LITTLE_ENDIAN for (unsigned w = 0; w != MIX_WORDS/4; ++w) { @@ -258,7 +253,7 @@ static void ethash_hash( memcpy(ret->mix_hash, mix->bytes, 32); // final Keccak hash - SHA3_256(ret->result, s_mix->bytes, 64+32); // Keccak-256(s + compressed_mix) + SHA3_256(ret->result, s_mix->bytes, 64 + 32); // Keccak-256(s + compressed_mix) } void ethash_quick_hash( @@ -267,7 +262,7 @@ void ethash_quick_hash( const uint64_t nonce, const uint8_t mix_hash[32]) { - uint8_t buf[64+32]; + uint8_t buf[64 + 32]; memcpy(buf, header_hash, 32); #if BYTE_ORDER != LITTLE_ENDIAN nonce = fix_endian64(nonce); @@ -275,24 +270,31 @@ void ethash_quick_hash( memcpy(&(buf[32]), &nonce, 8); SHA3_512(buf, buf, 40); memcpy(&(buf[64]), mix_hash, 32); - SHA3_256(return_hash, buf, 64+32); + SHA3_256(return_hash, buf, 64 + 32); +} + +void ethash_get_seedhash(uint8_t seedhash[32], const uint32_t block_number) { + memset(seedhash, 0, 32); + const uint32_t epochs = block_number / ETHASH_EPOCH_LENGTH; + for (uint32_t i = 0; i < epochs; ++i) + SHA3_256(seedhash, seedhash, 32); } -int ethash_quick_check_difficulty( +int ethash_preliminary_check_boundary( const uint8_t header_hash[32], const uint64_t nonce, const uint8_t mix_hash[32], - const uint8_t difficulty[32]) { + const uint8_t difficulty[32]) { - uint8_t return_hash[32]; + uint8_t return_hash[32]; ethash_quick_hash(return_hash, header_hash, nonce, mix_hash); - return ethash_check_difficulty(return_hash, difficulty); + return ethash_leq_be256(return_hash, difficulty); } -void ethash_full(ethash_return_value * ret, void const *full_mem, ethash_params const *params, const uint8_t previous_hash[32], const uint64_t nonce) { +void ethash_full(ethash_return_value *ret, void const *full_mem, ethash_params const *params, const uint8_t previous_hash[32], const uint64_t nonce) { ethash_hash(ret, (node const *) full_mem, NULL, params, previous_hash, nonce); } -void ethash_light(ethash_return_value * ret, ethash_cache const *cache, ethash_params const *params, const uint8_t previous_hash[32], const uint64_t nonce) { +void ethash_light(ethash_return_value *ret, void const *cache, ethash_params const *params, const uint8_t previous_hash[32], const uint64_t nonce) { ethash_hash(ret, NULL, cache, params, previous_hash, nonce); } diff --git a/libethash/internal.h b/libethash/internal.h index bcbacdaa4..dec7e6b13 100644 --- a/libethash/internal.h +++ b/libethash/internal.h @@ -3,7 +3,7 @@ #include "endian.h" #include "ethash.h" -#define ENABLE_SSE 1 +#define ENABLE_SSE 0 #if defined(_M_X64) && ENABLE_SSE #include @@ -15,7 +15,7 @@ extern "C" { // compile time settings #define NODE_WORDS (64/4) -#define MIX_WORDS (MIX_BYTES/4) +#define MIX_WORDS (ETHASH_MIX_BYTES/4) #define MIX_NODES (MIX_WORDS / NODE_WORDS) #include @@ -34,7 +34,7 @@ void ethash_calculate_dag_item( node *const ret, const unsigned node_index, ethash_params const *params, - ethash_cache const *cache + void const *cache ); void ethash_quick_hash( @@ -45,4 +45,4 @@ void ethash_quick_hash( #ifdef __cplusplus } -#endif \ No newline at end of file +#endif diff --git a/libethash/io.c b/libethash/io.c new file mode 100644 index 000000000..0e935fa59 --- /dev/null +++ b/libethash/io.c @@ -0,0 +1,89 @@ +/* + This file is part of ethash. + + ethash 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. + + ethash 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 ethash. If not, see . +*/ +/** @file io.c + * @author Lefteris Karapetsas + * @date 2015 + */ +#include "io.h" +#include +#include + +// silly macro to save some typing +#define PASS_ARR(c_) (c_), sizeof(c_) + +static bool ethash_io_write_file(char const *dirname, + char const* filename, + size_t filename_length, + void const* data, + size_t data_size) +{ + bool ret = false; + char *fullname = ethash_io_create_filename(dirname, filename, filename_length); + if (!fullname) { + return false; + } + FILE *f = fopen(fullname, "wb"); + if (!f) { + goto free_name; + } + if (data_size != fwrite(data, 1, data_size, f)) { + goto close; + } + + ret = true; +close: + fclose(f); +free_name: + free(fullname); + return ret; +} + +bool ethash_io_write(char const *dirname, + ethash_params const* params, + ethash_blockhash_t seedhash, + void const* cache, + uint8_t **data, + size_t *data_size) +{ + char info_buffer[DAG_MEMO_BYTESIZE]; + // allocate the bytes + uint8_t *temp_data_ptr = malloc(params->full_size); + if (!temp_data_ptr) { + goto end; + } + ethash_compute_full_data(temp_data_ptr, params, cache); + + if (!ethash_io_write_file(dirname, PASS_ARR(DAG_FILE_NAME), temp_data_ptr, params->full_size)) { + goto fail_free; + } + + ethash_io_serialize_info(ETHASH_REVISION, seedhash, info_buffer); + if (!ethash_io_write_file(dirname, PASS_ARR(DAG_MEMO_NAME), info_buffer, DAG_MEMO_BYTESIZE)) { + goto fail_free; + } + + *data = temp_data_ptr; + *data_size = params->full_size; + return true; + +fail_free: + free(temp_data_ptr); +end: + return false; +} + +#undef PASS_ARR diff --git a/libethash/io.h b/libethash/io.h new file mode 100644 index 000000000..0fa292362 --- /dev/null +++ b/libethash/io.h @@ -0,0 +1,116 @@ +/* + This file is part of ethash. + + ethash 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. + + ethash 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 ethash. If not, see . +*/ +/** @file io.h + * @author Lefteris Karapetsas + * @date 2015 + */ +#pragma once +#include +#include +#include +#include "ethash.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef struct ethash_blockhash { uint8_t b[32]; } ethash_blockhash_t; + +static const char DAG_FILE_NAME[] = "full"; +static const char DAG_MEMO_NAME[] = "full.info"; +// MSVC thinks that "static const unsigned int" is not a compile time variable. Sorry for the #define :( +#define DAG_MEMO_BYTESIZE 36 + +/// Possible return values of @see ethash_io_prepare +enum ethash_io_rc { + ETHASH_IO_FAIL = 0, ///< There has been an IO failure + ETHASH_IO_MEMO_MISMATCH, ///< Memo file either did not exist or there was content mismatch + ETHASH_IO_MEMO_MATCH, ///< Memo file existed and contents matched. No need to do anything +}; + +/** + * Prepares io for ethash + * + * Create the DAG directory if it does not exist, and check if the memo file matches. + * If it does not match then it's deleted to pave the way for @ref ethash_io_write() + * + * @param dirname A null terminated c-string of the path of the ethash + * data directory. If it does not exist it's created. + * @param seedhash The seedhash of the current block number + * @return For possible return values @see enum ethash_io_rc + */ +enum ethash_io_rc ethash_io_prepare(char const *dirname, ethash_blockhash_t seedhash); +/** + * Fully computes data and writes it to the file on disk. + * + * This function should be called after @see ethash_io_prepare() and only if + * its return value is @c ETHASH_IO_MEMO_MISMATCH. Will write both the full data + * and the memo file. + * + * @param[in] dirname A null terminated c-string of the path of the ethash + * data directory. Has to exist. + * @param[in] params An ethash_params object containing the full size + * and the cache size + * @param[in] seedhash The seedhash of the current block number + * @param[in] cache The cache data. Would have usually been calulated by + * @see ethash_prep_light(). + * @param[out] data Pass a pointer to uint8_t by reference here. If the + * function is succesfull then this point to the allocated + * data calculated by @see ethash_prep_full(). Memory + * ownership is transfered to the callee. Remember that + * you eventually need to free this with a call to free(). + * @param[out] data_size Pass a size_t by value. If the function is succesfull + * then this will contain the number of bytes allocated + * for @a data. + * @return True for success and false in case of failure. + */ +bool ethash_io_write(char const *dirname, + ethash_params const* params, + ethash_blockhash_t seedhash, + void const* cache, + uint8_t **data, + size_t *data_size); + +static inline void ethash_io_serialize_info(uint32_t revision, + ethash_blockhash_t seed_hash, + char *output) +{ + // if .info is only consumed locally we don't really care about endianess + memcpy(output, &revision, 4); + memcpy(output + 4, &seed_hash, 32); +} + +static inline char *ethash_io_create_filename(char const *dirname, + char const* filename, + size_t filename_length) +{ + // in C the cast is not needed, but a C++ compiler will complain for invalid conversion + char *name = (char*)malloc(strlen(dirname) + filename_length); + if (!name) { + return NULL; + } + + name[0] = '\0'; + strcat(name, dirname); + strcat(name, filename); + return name; +} + + +#ifdef __cplusplus +} +#endif diff --git a/libethash/io_posix.c b/libethash/io_posix.c new file mode 100644 index 000000000..9d9ccac69 --- /dev/null +++ b/libethash/io_posix.c @@ -0,0 +1,76 @@ +/* + This file is part of ethash. + + ethash 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. + + ethash 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 ethash. If not, see . +*/ +/** @file io_posix.c + * @author Lefteris Karapetsas + * @date 2015 + */ + +#include "io.h" +#include +#include +#include +#include +#include +#include + +enum ethash_io_rc ethash_io_prepare(char const *dirname, ethash_blockhash_t seedhash) +{ + char read_buffer[DAG_MEMO_BYTESIZE]; + char expect_buffer[DAG_MEMO_BYTESIZE]; + enum ethash_io_rc ret = ETHASH_IO_FAIL; + + // assert directory exists, full owner permissions and read/search for others + int rc = mkdir(dirname, S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH); + if (rc == -1 && errno != EEXIST) { + goto end; + } + + char *memofile = ethash_io_create_filename(dirname, DAG_MEMO_NAME, sizeof(DAG_MEMO_NAME)); + if (!memofile) { + goto end; + } + + // try to open memo file + FILE *f = fopen(memofile, "rb"); + if (!f) { + // file does not exist, so no checking happens. All is fine. + ret = ETHASH_IO_MEMO_MISMATCH; + goto free_memo; + } + + if (fread(read_buffer, 1, DAG_MEMO_BYTESIZE, f) != DAG_MEMO_BYTESIZE) { + goto close; + } + + ethash_io_serialize_info(ETHASH_REVISION, seedhash, expect_buffer); + if (memcmp(read_buffer, expect_buffer, DAG_MEMO_BYTESIZE) != 0) { + // we have different memo contents so delete the memo file + if (unlink(memofile) != 0) { + goto close; + } + ret = ETHASH_IO_MEMO_MISMATCH; + } + + ret = ETHASH_IO_MEMO_MATCH; + +close: + fclose(f); +free_memo: + free(memofile); +end: + return ret; +} diff --git a/libethash/io_win32.c b/libethash/io_win32.c new file mode 100644 index 000000000..77367fdd9 --- /dev/null +++ b/libethash/io_win32.c @@ -0,0 +1,73 @@ +/* + This file is part of ethash. + + ethash 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. + + ethash 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 ethash. If not, see . +*/ +/** @file io_win32.c + * @author Lefteris Karapetsas + * @date 2015 + */ + +#include "io.h" +#include +#include +#include + +enum ethash_io_rc ethash_io_prepare(char const *dirname, ethash_blockhash_t seedhash) +{ + char read_buffer[DAG_MEMO_BYTESIZE]; + char expect_buffer[DAG_MEMO_BYTESIZE]; + enum ethash_io_rc ret = ETHASH_IO_FAIL; + + // assert directory exists + int rc = _mkdir(dirname); + if (rc == -1 && errno != EEXIST) { + goto end; + } + + char *memofile = ethash_io_create_filename(dirname, DAG_MEMO_NAME, sizeof(DAG_MEMO_NAME)); + if (!memofile) { + goto end; + } + + // try to open memo file + FILE *f = fopen(memofile, "rb"); + if (!f) { + // file does not exist, so no checking happens. All is fine. + ret = ETHASH_IO_MEMO_MISMATCH; + goto free_memo; + } + + if (fread(read_buffer, 1, DAG_MEMO_BYTESIZE, f) != DAG_MEMO_BYTESIZE) { + goto close; + } + + ethash_io_serialize_info(ETHASH_REVISION, seedhash, expect_buffer); + if (memcmp(read_buffer, expect_buffer, DAG_MEMO_BYTESIZE) != 0) { + // we have different memo contents so delete the memo file + if (_unlink(memofile) != 0) { + goto close; + } + ret = ETHASH_IO_MEMO_MISMATCH; + } + + ret = ETHASH_IO_MEMO_MATCH; + +close: + fclose(f); +free_memo: + free(memofile); +end: + return ret; +} diff --git a/libethash/sha3_cryptopp.cpp b/libethash/sha3_cryptopp.cpp index 9454ce04a..6cbbcad8f 100644 --- a/libethash/sha3_cryptopp.cpp +++ b/libethash/sha3_cryptopp.cpp @@ -1,18 +1,18 @@ /* - This file is part of cpp-ethereum. + This file is part of ethash. - cpp-ethereum is free software: you can redistribute it and/or modify + ethash 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, + ethash 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 . + along with ethash. If not, see . */ /** @file sha3.cpp diff --git a/libethash/util.h b/libethash/util.h index 2f59076f6..ba8957815 100644 --- a/libethash/util.h +++ b/libethash/util.h @@ -1,18 +1,18 @@ /* - This file is part of cpp-ethereum. + This file is part of ethash. - cpp-ethereum is free software: you can redistribute it and/or modify + ethash 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, + ethash 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 . + along with ethash. If not, see . */ /** @file util.h * @author Tim Hughes diff --git a/libethcore/BlockInfo.cpp b/libethcore/BlockInfo.cpp index 0f37294c2..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() @@ -55,16 +55,30 @@ void BlockInfo::setEmpty() gasUsed = 0; timestamp = 0; extraData.clear(); - seedHash = h256(); mixHash = h256(); nonce = Nonce(); - hash = headerHash(WithNonce); + m_hash = m_seedHash = h256(); } -BlockInfo BlockInfo::fromHeader(bytesConstRef _block, Strictness _s) +h256 const& BlockInfo::seedHash() const +{ + if (!m_seedHash) + for (u256 n = number; n >= c_epochDuration; n -= c_epochDuration) + m_seedHash = sha3(m_seedHash); + return m_seedHash; +} + +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; } @@ -77,9 +91,9 @@ h256 BlockInfo::headerHash(IncludeNonce _n) const void BlockInfo::streamRLP(RLPStream& _s, IncludeNonce _n) const { - _s.appendList(_n == WithNonce ? 16 : 14) + _s.appendList(_n == WithNonce ? 15 : 13) << parentHash << sha3Uncles << coinbaseAddress << stateRoot << transactionsRoot << receiptsRoot << logBloom - << difficulty << number << gasLimit << gasUsed << timestamp << extraData << seedHash; + << difficulty << number << gasLimit << gasUsed << timestamp << extraData; if (_n == WithNonce) _s << mixHash << nonce; } @@ -89,13 +103,17 @@ 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 { + if (_header.itemCount() != 15) + throw InvalidBlockHeaderItemCount(); parentHash = _header[field = 0].toHash(RLP::VeryStrict); sha3Uncles = _header[field = 1].toHash(RLP::VeryStrict); coinbaseAddress = _header[field = 2].toHash

(RLP::VeryStrict); @@ -109,9 +127,8 @@ void BlockInfo::populateFromHeader(RLP const& _header, Strictness _s) gasUsed = _header[field = 10].toInt(); timestamp = _header[field = 11].toInt(); extraData = _header[field = 12].toBytes(); - seedHash = _header[field = 13].toHash(RLP::VeryStrict); - mixHash = _header[field = 14].toHash(RLP::VeryStrict); - nonce = _header[field = 15].toHash(RLP::VeryStrict); + mixHash = _header[field = 13].toHash(RLP::VeryStrict); + nonce = _header[field = 14].toHash(RLP::VeryStrict); } catch (Exception const& _e) @@ -140,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())); @@ -182,26 +199,22 @@ 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 = calculateGasLimit(_parent); + gasLimit = selectGasLimit(_parent); gasUsed = 0; difficulty = calculateDifficulty(_parent); - seedHash = calculateSeedHash(_parent); } -h256 BlockInfo::calculateSeedHash(BlockInfo const& _parent) const -{ - return number % c_epochDuration == 0 ? sha3(_parent.seedHash.asBytes()) : _parent.seedHash; -} - -u256 BlockInfo::calculateGasLimit(BlockInfo const& _parent) const +u256 BlockInfo::selectGasLimit(BlockInfo const& _parent) const { if (!parentHash) return c_genesisGasLimit; else - return max(c_minGasLimit, (_parent.gasLimit * (c_gasLimitBoundDivisor - 1) + (_parent.gasUsed * 6 / 5)) / c_gasLimitBoundDivisor); + // target minimum of 3141592 + return max(max(c_minGasLimit, 3141592), (_parent.gasLimit * (c_gasLimitBoundDivisor - 1) + (_parent.gasUsed * 6 / 5)) / c_gasLimitBoundDivisor); } u256 BlockInfo::calculateDifficulty(BlockInfo const& _parent) const @@ -218,17 +231,15 @@ void BlockInfo::verifyParent(BlockInfo const& _parent) const if (difficulty != calculateDifficulty(_parent)) BOOST_THROW_EXCEPTION(InvalidDifficulty() << RequirementError((bigint)calculateDifficulty(_parent), (bigint)difficulty)); - if (gasLimit < _parent.gasLimit * (c_gasLimitBoundDivisor - 1) / c_gasLimitBoundDivisor || + if (gasLimit < c_minGasLimit || + gasLimit < _parent.gasLimit * (c_gasLimitBoundDivisor - 1) / c_gasLimitBoundDivisor || gasLimit > _parent.gasLimit * (c_gasLimitBoundDivisor + 1) / c_gasLimitBoundDivisor) BOOST_THROW_EXCEPTION(InvalidGasLimit() << errinfo_min((bigint)_parent.gasLimit * (c_gasLimitBoundDivisor - 1) / c_gasLimitBoundDivisor) << errinfo_got((bigint)gasLimit) << errinfo_max((bigint)_parent.gasLimit * (c_gasLimitBoundDivisor + 1) / c_gasLimitBoundDivisor)); - if (seedHash != calculateSeedHash(_parent)) - BOOST_THROW_EXCEPTION(InvalidSeedHash()); - // 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 20134f059..6a3ca68b2 100644 --- a/libethcore/BlockInfo.h +++ b/libethcore/BlockInfo.h @@ -67,7 +67,7 @@ enum Strictness struct BlockInfo { public: - h256 hash; ///< SHA3 hash of the block header! Not serialised (the only member not contained in a block header). + // TODO: make them all private! h256 parentHash; h256 sha3Uncles; Address coinbaseAddress; @@ -79,68 +79,73 @@ public: u256 number; u256 gasLimit; u256 gasUsed; - u256 timestamp; + u256 timestamp = Invalid256; bytes extraData; h256 mixHash; - h256 seedHash; 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 && - seedHash == _cmp.seedHash && - 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); u256 calculateDifficulty(BlockInfo const& _parent) const; - u256 calculateGasLimit(BlockInfo const& _parent) const; - h256 calculateSeedHash(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; void streamRLP(RLPStream& _s, IncludeNonce _n) const; + +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.seedHash << " " << _bi.nonce; + _bi.gasUsed << " " << _bi.timestamp << " " << _bi.mixHash << " " << _bi.nonce << " (" << _bi.seedHash() << ")"; return _out; } diff --git a/libethcore/Common.cpp b/libethcore/Common.cpp index 9eb622fe3..572ade3e2 100644 --- a/libethcore/Common.cpp +++ b/libethcore/Common.cpp @@ -22,6 +22,7 @@ #include "Common.h" #include #include +#include "Ethasher.h" #include "Exceptions.h" using namespace std; using namespace dev; @@ -32,15 +33,17 @@ namespace dev namespace eth { -const unsigned c_protocolVersion = 56; -const unsigned c_databaseBaseVersion = 7; +const unsigned c_ethashVersion = c_ethashRevision; +const unsigned c_protocolVersion = 60; +const unsigned c_minorProtocolVersion = 0; +const unsigned c_databaseBaseVersion = 9; #if ETH_FATDB -const unsigned c_databaseVersionModifier = 1000; +const unsigned c_databaseVersionModifier = 1; #else const unsigned c_databaseVersionModifier = 0; #endif -const unsigned c_databaseVersion = c_databaseBaseVersion + c_databaseVersionModifier; +const unsigned c_databaseVersion = c_databaseBaseVersion + (c_databaseVersionModifier << 8) + (c_ethashVersion << 9); vector> const& units() { diff --git a/libethcore/Common.h b/libethcore/Common.h index 80a253bdd..ec826d2d1 100644 --- a/libethcore/Common.h +++ b/libethcore/Common.h @@ -35,9 +35,15 @@ 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; +/// Current database version. +extern const unsigned c_ethashVersion; + /// User-friendly string representation of the amount _b in wei. std::string formatBalance(bigint const& _b); @@ -68,5 +74,27 @@ static const u256 wei = exp10<0>(); using Nonce = h64; +using BlockNumber = unsigned; + +static const BlockNumber LatestBlock = (BlockNumber)-2; +static const BlockNumber PendingBlock = (BlockNumber)-1; + +enum class RelativeBlock: BlockNumber +{ + Latest = LatestBlock, + Pending = PendingBlock +}; + +enum class ImportResult +{ + Success = 0, + UnknownParent, + FutureTime, + AlreadyInChain, + AlreadyKnown, + Malformed, + BadChain +}; + } } diff --git a/libethcore/Ethasher.cpp b/libethcore/Ethasher.cpp index a28895a1a..75f0bcd5a 100644 --- a/libethcore/Ethasher.cpp +++ b/libethcore/Ethasher.cpp @@ -25,12 +25,13 @@ #include #include #include +#include #include #include #include +#include #include -#include -#include +#include #include "BlockInfo.h" #include "Ethasher.h" using namespace std; @@ -40,49 +41,81 @@ using namespace eth; Ethasher* dev::eth::Ethasher::s_this = nullptr; -bytes const& Ethasher::cache(BlockInfo const& _header) +Ethasher::~Ethasher() +{ + while (!m_lights.empty()) + killCache(m_lights.begin()->first); +} + +void Ethasher::killCache(h256 const& _s) { RecursiveGuard l(x_this); - if (!m_caches.count(_header.seedHash)) + if (m_lights.count(_s)) { - try { - boost::filesystem::create_directories(getDataDir() + "/ethashcache"); - } catch (...) {} - std::string memoFile = getDataDir() + "/ethashcache/" + toHex(_header.seedHash.ref().cropped(0, 4)) + ".cache"; - m_caches[_header.seedHash] = contents(memoFile); - if (m_caches[_header.seedHash].empty()) - { - ethash_params p = params((unsigned)_header.number); - m_caches[_header.seedHash].resize(p.cache_size); - ethash_prep_light(m_caches[_header.seedHash].data(), &p, _header.seedHash.data()); - writeFile(memoFile, m_caches[_header.seedHash]); - } + ethash_delete_light(m_lights.at(_s)); + m_lights.erase(_s); + } +} + +void const* Ethasher::light(BlockInfo const& _header) +{ + RecursiveGuard l(x_this); + if (_header.number > c_ethashEpochLength * 2048) + { + std::ostringstream error; + error << "block number is too high; max is " << c_ethashEpochLength * 2048 << "(was " << _header.number << ")"; + throw std::invalid_argument( error.str() ); + } + + if (!m_lights.count(_header.seedHash())) + { + ethash_params p = params((unsigned)_header.number); + 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.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 (...) {} + + auto info = rlpList(c_ethashRevision, _header.seedHash()); + 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); } - std::string memoFile = getDataDir() + "/ethashcache/" + toHex(_header.seedHash.ref().cropped(0, 4)) + ".full"; - m_fulls[_header.seedHash] = contentsNew(memoFile); - if (!m_fulls[_header.seedHash]) + + 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); - ethash_prep_full(m_fulls[_header.seedHash].data(), &p, c.data()); - writeFile(memoFile, m_fulls[_header.seedHash]); + m_fulls[_header.seedHash()] = bytesRef(new byte[p.full_size], p.full_size); + auto c = light(_header); + ethash_prep_full(m_fulls[_header.seedHash()].data(), &p, c); + writeFile(memoFile, m_fulls[_header.seedHash()]); } } - return m_fulls[_header.seedHash]; + return m_fulls[_header.seedHash()]; } ethash_params Ethasher::params(BlockInfo const& _header) @@ -100,16 +133,50 @@ ethash_params Ethasher::params(unsigned _n) bool Ethasher::verify(BlockInfo const& _header) { - bigint boundary = (bigint(1) << 256) / _header.difficulty; - auto e = eval(_header, _header.nonce); - return (u256)e.value <= boundary && e.mixHash == _header.mixHash; + if (_header.number >= c_ethashEpochLength * 2048) + return false; + + h256 boundary = u256((bigint(1) << 256) / _header.difficulty); + + bool quick = ethash_quick_check_difficulty( + _header.headerHash(WithoutNonce).data(), + (uint64_t)(u64)_header.nonce, + _header.mixHash.data(), + boundary.data()); + +#if !ETH_DEBUG + if (!quick) + return false; +#endif + + auto result = eval(_header); + bool slow = result.value <= boundary && result.mixHash == _header.mixHash; + +#if ETH_DEBUG + if (!quick && slow) + { + cwarn << "WARNING: evaluated result gives true whereas ethash_quick_check_difficulty gives false."; + cwarn << "headerHash:" << _header.headerHash(WithoutNonce); + cwarn << "nonce:" << _header.nonce; + cwarn << "mixHash:" << _header.mixHash; + cwarn << "difficulty:" << _header.difficulty; + cwarn << "boundary:" << boundary; + cwarn << "result.value:" << result.value; + cwarn << "result.mixHash:" << result.mixHash; + } +#endif + + return slow; } 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).data(), &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 cfe0d1c82..8d1a1e84c 100644 --- a/libethcore/Ethasher.h +++ b/libethcore/Ethasher.h @@ -27,8 +27,11 @@ #include #include #include +#include #include #include // TODO: REMOVE once everything merged into this class and an opaque API can be provided. +static const unsigned c_ethashRevision = ETHASH_REVISION; +static const unsigned c_ethashEpochLength = ETHASH_EPOCH_LENGTH; #include "Common.h" #include "BlockInfo.h" @@ -41,10 +44,14 @@ class Ethasher { public: Ethasher() {} + ~Ethasher(); static Ethasher* get() { if (!s_this) s_this = new Ethasher(); return s_this; } - bytes const& cache(BlockInfo const& _header); + using LightType = void const*; + using FullType = void const*; + + LightType light(BlockInfo const& _header); bytesConstRef full(BlockInfo const& _header); static ethash_params params(BlockInfo const& _header); static ethash_params params(unsigned _n); @@ -71,6 +78,7 @@ public: inline h256 mine(uint64_t _nonce) { ethash_compute_full(&m_ethashReturn, m_datasetPointer, &m_params, m_headerHash.data(), _nonce); +// cdebug << "Ethasher::mine hh:" << m_headerHash << "nonce:" << (Nonce)(u64)_nonce << " => " << h256(m_ethashReturn.result, h256::ConstructFromPointer); return h256(m_ethashReturn.result, h256::ConstructFromPointer); } @@ -87,9 +95,11 @@ public: }; private: + void killCache(h256 const& _s); + static Ethasher* s_this; RecursiveMutex x_this; - std::map m_caches; + std::map m_lights; std::map m_fulls; }; diff --git a/libethcore/Exceptions.h b/libethcore/Exceptions.h index 0059854f7..db2718fc4 100644 --- a/libethcore/Exceptions.h +++ b/libethcore/Exceptions.h @@ -38,6 +38,7 @@ using errinfo_difficulty = boost::error_info; using BadFieldError = boost::tuple; struct DatabaseAlreadyOpen: virtual dev::Exception {}; +struct NotEnoughAvailableSpace: virtual dev::Exception {}; struct NotEnoughCash: virtual dev::Exception {}; struct GasPriceTooLow: virtual dev::Exception {}; struct BlockGasLimitReached: virtual dev::Exception {}; @@ -59,7 +60,6 @@ struct InvalidGasUsed: virtual dev::Exception {}; class InvalidTransactionsHash: virtual public dev::Exception {}; struct InvalidTransaction: virtual dev::Exception {}; struct InvalidDifficulty: virtual dev::Exception {}; -struct InvalidSeedHash: virtual dev::Exception {}; class InvalidGasLimit: virtual public dev::Exception {}; struct InvalidTransactionGasUsed: virtual dev::Exception {}; struct InvalidTransactionsStateRoot: virtual dev::Exception {}; @@ -67,6 +67,7 @@ struct InvalidReceiptsStateRoot: virtual dev::Exception {}; struct InvalidTimestamp: virtual dev::Exception {}; struct InvalidLogBloom: virtual dev::Exception {}; class InvalidNonce: virtual public dev::Exception {}; +class InvalidBlockHeaderItemCount: virtual public dev::Exception {}; class InvalidBlockNonce: virtual public dev::Exception {}; struct InvalidParentHash: virtual dev::Exception {}; struct InvalidNumber: virtual dev::Exception {}; diff --git a/libethcore/Params.cpp b/libethcore/Params.cpp index d1154abcc..d70443fd9 100644 --- a/libethcore/Params.cpp +++ b/libethcore/Params.cpp @@ -27,16 +27,17 @@ namespace dev namespace eth { -//--- BEGIN: AUTOGENERATED FROM /feeStructure.json +//--- BEGIN: AUTOGENERATED FROM github.com/ethereum/common/params.json u256 const c_genesisDifficulty = 131072; u256 const c_maximumExtraDataSize = 1024; -u256 const c_epochDuration = 3000; -u256 const c_genesisGasLimit = 1000000; +u256 const c_epochDuration = 30000; +u256 const c_genesisGasLimit = 3141592; u256 const c_minGasLimit = 125000; u256 const c_gasLimitBoundDivisor = 1024; u256 const c_minimumDifficulty = 131072; u256 const c_difficultyBoundDivisor = 2048; u256 const c_durationLimit = 8; +u256 const c_stackLimit = 1024; u256 const c_tierStepGas[] = {0, 2, 3, 5, 8, 10, 20, 0}; u256 const c_expGas = 10; u256 const c_expByteGas = 10; diff --git a/libethcore/Params.h b/libethcore/Params.h index cab1852fa..62cf6b2d8 100644 --- a/libethcore/Params.h +++ b/libethcore/Params.h @@ -29,15 +29,16 @@ namespace eth { //--- BEGIN: AUTOGENERATED FROM /feeStructure.json -extern u256 const c_genesisDifficulty; -extern u256 const c_maximumExtraDataSize; -extern u256 const c_epochDuration; extern u256 const c_genesisGasLimit; extern u256 const c_minGasLimit; extern u256 const c_gasLimitBoundDivisor; +extern u256 const c_genesisDifficulty; extern u256 const c_minimumDifficulty; extern u256 const c_difficultyBoundDivisor; extern u256 const c_durationLimit; +extern u256 const c_maximumExtraDataSize; +extern u256 const c_epochDuration; +extern u256 const c_stackLimit; extern u256 const c_tierStepGas[8]; ///< Once per operation, for a selection of them. extern u256 const c_expGas; ///< Once per EXP instuction. diff --git a/libethcore/ProofOfWork.cpp b/libethcore/ProofOfWork.cpp index d261ccb1c..a34e75d71 100644 --- a/libethcore/ProofOfWork.cpp +++ b/libethcore/ProofOfWork.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -30,6 +31,35 @@ #include #include #include +#if ETH_ETHASHCL +#include +#define ETHASH_REVISION REVISION +#define ETHASH_DATASET_BYTES_INIT DATASET_BYTES_INIT +#define ETHASH_DATASET_BYTES_GROWTH DATASET_BYTES_GROWTH +#define ETHASH_CACHE_BYTES_INIT CACHE_BYTES_INIT +#define ETHASH_CACHE_BYTES_GROWTH CACHE_BYTES_GROWTH +#define ETHASH_DAGSIZE_BYTES_INIT DAGSIZE_BYTES_INIT +#define ETHASH_DAG_GROWTH DAG_GROWTH +#define ETHASH_EPOCH_LENGTH EPOCH_LENGTH +#define ETHASH_MIX_BYTES MIX_BYTES +#define ETHASH_HASH_BYTES HASH_BYTES +#define ETHASH_DATASET_PARENTS DATASET_PARENTS +#define ETHASH_CACHE_ROUNDS CACHE_ROUNDS +#define ETHASH_ACCESSES ACCESSES +#undef REVISION +#undef DATASET_BYTES_INIT +#undef DATASET_BYTES_GROWTH +#undef CACHE_BYTES_INIT +#undef CACHE_BYTES_GROWTH +#undef DAGSIZE_BYTES_INIT +#undef DAG_GROWTH +#undef EPOCH_LENGTH +#undef MIX_BYTES +#undef HASH_BYTES +#undef DATASET_PARENTS +#undef CACHE_ROUNDS +#undef ACCESSES +#endif #include "BlockInfo.h" #include "Ethasher.h" #include "ProofOfWork.h" @@ -41,21 +71,22 @@ namespace dev namespace eth { -bool Ethash::verify(BlockInfo const& _header) +bool EthashCPU::verify(BlockInfo const& _header) { return Ethasher::verify(_header); } -std::pair Ethash::mine(BlockInfo const& _header, unsigned _msTimeout, bool _continue, bool _turbo) +std::pair EthashCPU::mine(BlockInfo const& _header, unsigned _msTimeout, bool _continue, bool _turbo) { Ethasher::Miner m(_header); std::pair ret; - static std::mt19937_64 s_eng((time(0) + *reinterpret_cast(m_last.data()))); + auto tid = std::this_thread::get_id(); + static std::mt19937_64 s_eng((time(0) + *reinterpret_cast(m_last.data()) + std::hash()(tid))); uint64_t tryNonce = (uint64_t)(u64)(m_last = Nonce::random(s_eng)); - bigint boundary = (bigint(1) << 256) / _header.difficulty; - ret.first.requirement = log2((double)boundary); + h256 boundary = u256((bigint(1) << 256) / _header.difficulty); + ret.first.requirement = log2((double)(u256)boundary); // 2^ 0 32 64 128 256 // [--------*-------------------------] @@ -69,13 +100,17 @@ std::pair Ethash::mine(BlockInfo const& _header, unsign unsigned hashCount = 0; for (; (std::chrono::steady_clock::now() - startTime) < std::chrono::milliseconds(_msTimeout) && _continue; tryNonce++, hashCount++) { - u256 val(m.mine(tryNonce)); - best = std::min(best, log2((double)val)); + h256 val(m.mine(tryNonce)); + best = std::min(best, log2((double)(u256)val)); if (val <= boundary) { ret.first.completed = true; + assert(Ethasher::eval(_header, (Nonce)(u64)tryNonce).value == val); result.mixHash = m.lastMixHash(); result.nonce = u64(tryNonce); + BlockInfo test = _header; + assignResult(result, test); + assert(verify(test)); break; } } @@ -93,5 +128,113 @@ std::pair Ethash::mine(BlockInfo const& _header, unsign return ret; } +#if ETH_ETHASHCL + +/* +struct ethash_cl_search_hook +{ + // reports progress, return true to abort + virtual bool found(uint64_t const* nonces, uint32_t count) = 0; + virtual bool searched(uint64_t start_nonce, uint32_t count) = 0; +}; + +class ethash_cl_miner +{ +public: + ethash_cl_miner(); + + bool init(ethash_params const& params, const uint8_t seed[32], unsigned workgroup_size = 64); + + void hash(uint8_t* ret, uint8_t const* header, uint64_t nonce, unsigned count); + void search(uint8_t const* header, uint64_t target, search_hook& hook); +}; +*/ + +struct EthashCLHook: public ethash_cl_search_hook +{ + virtual bool found(uint64_t const* _nonces, uint32_t _count) + { + Guard l(x_all); + for (unsigned i = 0; i < _count; ++i) + found.push_back((Nonce)(u64)_nonces[i]); + if (abort) + { + aborted = true; + return true; + } + return false; + } + + virtual bool searched(uint64_t _startNonce, uint32_t _count) + { + Guard l(x_all); + total += _count; + last = _startNonce + _count; + if (abort) + { + aborted = true; + return true; + } + return false; + } + + vector fetchFound() { vector ret; Guard l(x_all); std::swap(ret, found); return ret; } + uint64_t fetchTotal() { Guard l(x_all); auto ret = total; total = 0; return ret; } + + Mutex x_all; + vector found; + uint64_t total; + uint64_t last; + bool abort = false; + bool aborted = false; +}; + +EthashCL::EthashCL(): + m_miner(new ethash_cl_miner), + m_hook(new EthashCLHook) +{ +} + +EthashCL::~EthashCL() +{ + m_hook->abort = true; + for (unsigned timeout = 0; timeout < 100 && !m_hook->aborted; ++timeout) + std::this_thread::sleep_for(chrono::milliseconds(30)); + if (!m_hook->aborted) + cwarn << "Couldn't abort. Abandoning OpenCL process."; +} + +bool EthashCL::verify(BlockInfo const& _header) +{ + return Ethasher::verify(_header); +} + +std::pair EthashCL::mine(BlockInfo const& _header, unsigned _msTimeout, bool, bool) +{ + if (m_lastHeader.seedHash() != _header.seedHash()) + { + m_miner->init(Ethasher::params(_header), _header.seedHash().data()); + // TODO: reinit probably won't work when seed changes. + } + if (m_lastHeader != _header) + { + static std::random_device s_eng; + uint64_t tryNonce = (uint64_t)(u64)(m_last = Nonce::random(s_eng)); + m_miner->search(_header.headerHash(WithoutNonce).data(), tryNonce, *m_hook); + } + m_lastHeader = _header; + + std::this_thread::sleep_for(chrono::milliseconds(_msTimeout)); + auto found = m_hook->fetchFound(); + if (!found.empty()) + { + h256 mixHash; // ????? + return std::make_pair(MineInfo{0.0, 1e99, 0, true}, EthashCL::Proof((Nonce)(u64)found[0], mixHash)); + } + return std::make_pair(MineInfo{0.0, 1e99, 0, false}, EthashCL::Proof()); +} + +#endif + } } diff --git a/libethcore/ProofOfWork.h b/libethcore/ProofOfWork.h index 250ddb73d..245a96a02 100644 --- a/libethcore/ProofOfWork.h +++ b/libethcore/ProofOfWork.h @@ -32,6 +32,9 @@ #define FAKE_DAGGER 1 +class ethash_cl_miner; +struct ethash_cl_search_hook; + namespace dev { namespace eth @@ -46,7 +49,7 @@ struct MineInfo bool completed = false; }; -class Ethash +class EthashCPU { public: struct Proof @@ -63,6 +66,36 @@ protected: Nonce m_last; }; +#if ETH_ETHASHCL +class EthashCL +{ +public: + struct Proof + { + Nonce nonce; + h256 mixHash; + }; + + EthashCL(); + ~EthashCL(); + + static bool verify(BlockInfo const& _header); + std::pair mine(BlockInfo const& _header, unsigned _msTimeout = 100, bool _continue = true, bool _turbo = false); + static void assignResult(Proof const& _r, BlockInfo& _header) { _header.nonce = _r.nonce; _header.mixHash = _r.mixHash; } + +protected: + Nonce m_last; + BlockInfo m_lastHeader; + Nonce m_mined; + std::unique_ptr m_miner; + std::unique_ptr m_hook; +}; + +using Ethash = EthashCL; +#else +using Ethash = EthashCPU; +#endif + template class ProofOfWorkEngine: public Evaluator { diff --git a/libethereum/ABI.cpp b/libethereum/ABI.cpp new file mode 100644 index 000000000..eada1c419 --- /dev/null +++ b/libethereum/ABI.cpp @@ -0,0 +1,27 @@ +/* + 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 ABI.cpp + * @author Gav Wood + * @date 2014 + */ + +#include "ABI.h" + +using namespace std; +using namespace dev; +using namespace dev::eth; + diff --git a/libethereum/ABI.h b/libethereum/ABI.h new file mode 100644 index 000000000..04c623ac5 --- /dev/null +++ b/libethereum/ABI.h @@ -0,0 +1,64 @@ +/* + 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 ABI.h + * @author Gav Wood + * @date 2014 + */ + +#pragma once + +#include +#include +#include +#include + +namespace dev +{ +namespace eth +{ + +template struct ABISerialiser {}; +template struct ABISerialiser> { static bytes serialise(FixedHash const& _t) { static_assert(N <= 32, "Cannot serialise hash > 32 bytes."); static_assert(N > 0, "Cannot serialise zero-length hash."); return bytes(32 - N, 0) + _t.asBytes(); } }; +template <> struct ABISerialiser { static bytes serialise(u256 const& _t) { return h256(_t).asBytes(); } }; +template <> struct ABISerialiser { static bytes serialise(u160 const& _t) { return bytes(12, 0) + h160(_t).asBytes(); } }; +template <> struct ABISerialiser { static bytes serialise(string32 const& _t) { return bytesConstRef((byte const*)_t.data(), 32).toBytes(); } }; + +inline bytes abiInAux() { return {}; } +template bytes abiInAux(T const& _t, U const& ... _u) +{ + return ABISerialiser::serialise(_t) + abiInAux(_u ...); +} + +template bytes abiIn(std::string _id, T const& ... _t) +{ + return sha3(_id).ref().cropped(0, 4).toBytes() + abiInAux(_t ...); +} + +template struct ABIDeserialiser {}; +template struct ABIDeserialiser> { static FixedHash deserialise(bytesConstRef& io_t) { static_assert(N <= 32, "Parameter sizes must be at most 32 bytes."); FixedHash ret; io_t.cropped(32 - N, N).populate(ret.ref()); io_t = io_t.cropped(32); return ret; } }; +template <> struct ABIDeserialiser { static u256 deserialise(bytesConstRef& io_t) { u256 ret = fromBigEndian(io_t.cropped(0, 32)); io_t = io_t.cropped(32); return ret; } }; +template <> struct ABIDeserialiser { static u160 deserialise(bytesConstRef& io_t) { u160 ret = fromBigEndian(io_t.cropped(12, 20)); io_t = io_t.cropped(32); return ret; } }; +template <> struct ABIDeserialiser { static string32 deserialise(bytesConstRef& io_t) { string32 ret; io_t.cropped(0, 32).populate(bytesRef((byte*)ret.data(), 32)); io_t = io_t.cropped(32); return ret; } }; + +template T abiOut(bytes const& _data) +{ + bytesConstRef o(&_data); + return ABIDeserialiser::deserialise(o); +} + +} +} diff --git a/libethereum/BlockChain.cpp b/libethereum/BlockChain.cpp index dd0bfad67..a4061a1a0 100644 --- a/libethereum/BlockChain.cpp +++ b/libethereum/BlockChain.cpp @@ -19,19 +19,24 @@ * @date 2014 */ -#include - #include "BlockChain.h" +#if ETH_PROFILING_GPERF +#include +#endif +#include +#include #include #include #include +#include #include #include #include #include #include #include +#include #include #include "GenesisInfo.h" #include "State.h" @@ -42,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) { @@ -62,20 +68,35 @@ std::ostream& dev::eth::operator<<(std::ostream& _out, BlockChain const& _bc) return _out; } -ldb::Slice dev::eth::toSlice(h256 _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 ^ h256(u256(_sub)); + 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 h256); - *t_h = _h ^ h256(u256(_sub)); + *t_h = _h ^ sha3(h256(u256(_sub))); return ldb::Slice((char const*)t_h.get(), 32); #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; @@ -97,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); @@ -107,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() @@ -115,40 +138,49 @@ 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); - if (!m_blocksDB) - BOOST_THROW_EXCEPTION(DatabaseAlreadyOpen()); - if (!m_extrasDB) - BOOST_THROW_EXCEPTION(DatabaseAlreadyOpen()); + 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) + { + cwarn << "Not enough available space found on hard drive. Please free some up and then re-run. Bailing."; + BOOST_THROW_EXCEPTION(NotEnoughAvailableSpace()); + } + else + { + cwarn << "Database already open. You appear to have another instance of ethereum running. Bailing."; + BOOST_THROW_EXCEPTION(DatabaseAlreadyOpen()); + } + } - 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(), {}); auto r = m_details[m_genesisHash].rlp(); - m_extrasDB->Put(m_writeOptions, ldb::Slice((char const*)&m_genesisHash, 32), (ldb::Slice)dev::ref(r)); + 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(); @@ -164,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) { @@ -183,56 +293,88 @@ inline string toString(h256s const& _bs) return out.str(); } -h256s BlockChain::sync(BlockQueue& _bq, OverlayDB const& _stateDB, unsigned _max) +LastHashes BlockChain::lastHashes(unsigned _n) const +{ + Guard l(x_lastLastHashes); + if (m_lastLastHashesNumber != _n || m_lastLastHashes.empty()) + { + m_lastLastHashes.resize(256); + for (unsigned i = 0; i < 256; ++i) + m_lastLastHashes[i] = _n >= i ? numberHash(_n - i) : h256(); + m_lastLastHashesNumber = _n; + } + return m_lastLastHashes; +} + +tuple BlockChain::sync(BlockQueue& _bq, OverlayDB const& _stateDB, unsigned _max) { _bq.tick(*this); vector blocks; - _bq.drain(blocks); + _bq.drain(blocks, _max); - h256s ret; + h256s fresh; + h256s dead; + h256s badBlocks; for (auto const& block: blocks) { try { - for (auto h: import(block, _stateDB)) - if (!_max--) - break; + auto r = import(block, _stateDB); + bool isOld = true; + for (auto const& h: r.first) + if (h == r.second) + isOld = false; + else if (isOld) + dead.push_back(h); else - ret.push_back(h); + fresh.push_back(h); } catch (UnknownParent) { - cwarn << "Unknown parent of block!!!" << BlockInfo::headerHash(block).abridged() << boost::current_exception_diagnostic_information(); - _bq.import(&block, *this); + cwarn << "ODD: Import queue contains block with unknown parent." << boost::current_exception_diagnostic_information(); + // NOTE: don't reimport since the queue should guarantee everything in the right order. + // Can't continue - chain bad. + badBlocks.push_back(BlockInfo::headerHash(block)); } catch (Exception const& _e) { - cwarn << "Unexpected exception!" << diagnostic_information(_e); - _bq.import(&block, *this); + cnote << "Exception while importing block. Someone (Jeff? That you?) seems to be giving us dodgy blocks!" << diagnostic_information(_e); + // NOTE: don't reimport since the queue should guarantee everything in the right order. + // Can't continue - chain bad. + badBlocks.push_back(BlockInfo::headerHash(block)); } - catch (...) - {} } - _bq.doneDrain(); - return ret; + return make_tuple(fresh, dead, _bq.doneDrain(badBlocks)); } -h256s BlockChain::attemptImport(bytes const& _block, OverlayDB const& _stateDB) noexcept +pair BlockChain::attemptImport(bytes const& _block, OverlayDB const& _stateDB, bool _force) noexcept { try { - return import(_block, _stateDB); + return import(_block, _stateDB, _force); } catch (...) { cwarn << "Unexpected exception! Could not import block!" << boost::current_exception_diagnostic_information(); - return h256s(); + return make_pair(h256s(), h256()); } } -h256s BlockChain::import(bytes const& _block, OverlayDB const& _db) +pair 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; @@ -259,7 +401,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()); @@ -292,6 +434,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 @@ -299,8 +446,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) @@ -311,32 +459,27 @@ 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 // All ok - insert into DB { + // ensure parent is cached for later addition. + // TODO: this is a bit horrible would be better refactored into an enveloping UpgradableGuard + // together with an "ensureCachedWithUpdatableLock(l)" method. + // This is safe in practice since the caches don't get flushed nearly often enough to be + // done here. + details(bi.parentHash); + WriteGuard l(x_details); 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; - } - // Collate transaction hashes and remember who they were. - h256s tas; - { - RLP blockRLP(_block); - TransactionAddress ta; - ta.blockHash = newHash; - WriteGuard l(x_transactionAddresses); - for (ta.index = 0; ta.index < blockRLP[1].itemCount(); ++ta.index) - { - tas.push_back(sha3(blockRLP[1][ta.index].data())); - m_transactionAddresses[tas.back()] = ta; - } - } { WriteGuard l(x_logBlooms); m_logBlooms[newHash] = blb; @@ -346,26 +489,48 @@ h256s BlockChain::import(bytes const& _block, OverlayDB const& _db) m_receipts[newHash] = br; } - 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: tas) - 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())); +#if ETH_TIMED_IMPORTS + collation = t.elapsed(); + t.restart(); +#endif + + { + ReadGuard l2(x_details); + ReadGuard l4(x_receipts); + ReadGuard l5(x_logBlooms); + 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(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())); + } + +#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( @@ -377,18 +542,83 @@ h256s BlockChain::import(bytes const& _block, OverlayDB const& _db) ); // cnote << "Parent " << bi.parentHash << " has " << details(bi.parentHash).children.size() << " children."; - h256s ret; + h256s route; + h256 common; // This might be the new best block... h256 last = currentHash(); if (td > details(last).totalDifficulty) { - ret = treeRoute(last, newHash); + unsigned commonIndex; + tie(route, common, commonIndex) = treeRoute(last, newHash); { WriteGuard l(x_lastBlockHash); m_lastBlockHash = newHash; } + m_extrasDB->Put(m_writeOptions, ldb::Slice("best"), ldb::Slice((char const*)&newHash, 32)); - clog(BlockChainNote) << " Imported and best" << td << ". Has" << (details(bi.parentHash).children.size() - 1) << "siblings. Route:" << toString(ret); + + // 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 = route.rbegin(); i != route.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(route); + noteCanonChanged(); + StructuredLogger::chainNewHead( bi.headerHash(WithoutNonce).abridged(), bi.nonce.abridged(), @@ -400,57 +630,111 @@ h256s BlockChain::import(bytes const& _block, OverlayDB const& _db) { clog(BlockChainNote) << " Imported but not best (oTD:" << details(last).totalDifficulty << " > TD:" << td << ")"; } - return ret; + +#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 make_pair(route, common); } -h256s BlockChain::treeRoute(h256 _from, h256 _to, h256* o_common, bool _pre, bool _post) const +void BlockChain::clearBlockBlooms(unsigned _begin, unsigned _end) { - // cdebug << "treeRoute" << _from.abridged() << "..." << _to.abridged(); - if (!_from || !_to) + // ... 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) { - return h256s(); + // 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 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) { if (_pre) - ret.push_back(_from); - _from = details(_from).parent; + 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) { if (_post) - back.push_back(_to); - _to = details(_to).parent; + back.push_back(to); + to = details(to).parent; tn--; - // cdebug << "to:" << tn << _to.abridged(); +// cdebug << "to:" << tn << _to.abridged(); } - while (_from != _to) + for (;; from = details(from).parent, to = details(to).parent) { - assert(_from); - assert(_to); - _from = details(_from).parent; - _to = details(_to).parent; - if (_pre) - ret.push_back(_from); - if (_post) - back.push_back(_to); + if (_pre && (from != to || _common)) + ret.push_back(from); + if (_post && (from != to || (!_pre && _common))) + back.push_back(to); fn--; tn--; - // cdebug << "from:" << fn << _from.abridged() << "; to:" << tn << _to.abridged(); +// cdebug << "from:" << fn << _from.abridged() << "; to:" << tn << _to.abridged(); + if (from == to) + break; + if (!from) + assert(from); + if (!to) + assert(to); } - 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 @@ -475,29 +759,30 @@ template static unsigned getHashSize(map const& _map) void BlockChain::updateStats() const { { - ReadGuard l1(x_blocks); + ReadGuard l(x_blocks); m_lastStats.memBlocks = 0; for (auto const& i: m_blocks) m_lastStats.memBlocks += i.second.size() + 64; } { - ReadGuard l2(x_details); + ReadGuard l(x_details); m_lastStats.memDetails = getHashSize(m_details); } { - ReadGuard l5(x_logBlooms); - m_lastStats.memLogBlooms = getHashSize(m_logBlooms); + ReadGuard l1(x_logBlooms); + ReadGuard l2(x_blocksBlooms); + m_lastStats.memLogBlooms = getHashSize(m_logBlooms) + getHashSize(m_blocksBlooms); } { - ReadGuard l4(x_receipts); + ReadGuard l(x_receipts); m_lastStats.memReceipts = getHashSize(m_receipts); } { - ReadGuard l3(x_blockHashes); + ReadGuard l(x_blockHashes); m_lastStats.memBlockHashes = getHashSize(m_blockHashes); } { - ReadGuard l6(x_transactionAddresses); + ReadGuard l(x_transactionAddresses); m_lastStats.memTransactionAddresses = getHashSize(m_transactionAddresses); } } @@ -520,6 +805,7 @@ void BlockChain::garbageCollect(bool _force) WriteGuard l4(x_receipts); WriteGuard l5(x_logBlooms); WriteGuard l6(x_transactionAddresses); + WriteGuard l7(x_blocksBlooms); for (CacheID const& id: m_cacheUsage.back()) { m_inUse.erase(id); @@ -544,6 +830,9 @@ void BlockChain::garbageCollect(bool _force) case ExtraTransactionAddress: m_transactionAddresses.erase(id.first); break; + case ExtraBlocksBlooms: + m_blocksBlooms.erase(id.first); + break; } } m_cacheUsage.pop_back(); @@ -579,7 +868,77 @@ void BlockChain::checkConsistency() delete it; } -h256Set BlockChain::allUnclesFrom(h256 _parent) const +static inline unsigned upow(unsigned a, unsigned b) { while (b-- > 0) a *= a; return a; } +static inline unsigned ceilDiv(unsigned n, unsigned d) { return n / (n + d - 1); } +//static inline unsigned floorDivPow(unsigned n, unsigned a, unsigned b) { return n / upow(a, b); } +//static inline unsigned ceilDivPow(unsigned n, unsigned a, unsigned b) { return ceilDiv(n, upow(a, b)); } + +// Level 1 +// [xxx. ] + +// Level 0 +// [.x............F.] +// [........x.......] +// [T.............x.] +// [............ ] + +// F = 14. T = 32 + +vector BlockChain::withBlockBloom(LogBloom const& _b, unsigned _earliest, unsigned _latest) const +{ + vector ret; + + // start from the top-level + unsigned u = upow(c_bloomIndexSize, c_bloomIndexLevels); + + // run through each of the top-level blockbloom blocks + for (unsigned index = _earliest / u; index <= ceilDiv(_latest, u); ++index) // 0 + ret += withBlockBloom(_b, _earliest, _latest, c_bloomIndexLevels - 1, index); + + return ret; +} + +vector BlockChain::withBlockBloom(LogBloom const& _b, unsigned _earliest, unsigned _latest, unsigned _level, unsigned _index) const +{ + // 14, 32, 1, 0 + // 14, 32, 0, 0 + // 14, 32, 0, 1 + // 14, 32, 0, 2 + + vector ret; + + unsigned uCourse = upow(c_bloomIndexSize, _level + 1); + // 256 + // 16 + unsigned uFine = upow(c_bloomIndexSize, _level); + // 16 + // 1 + + unsigned obegin = _index == _earliest / uCourse ? _earliest / uFine % c_bloomIndexSize : 0; + // 0 + // 14 + // 0 + // 0 + unsigned oend = _index == _latest / uCourse ? (_latest / uFine) % c_bloomIndexSize + 1 : c_bloomIndexSize; + // 3 + // 16 + // 16 + // 1 + + BlocksBlooms bb = blocksBlooms(_level, _index); + for (unsigned o = obegin; o < oend; ++o) + if (bb.blooms[o].contains(_b)) + { + // This level has something like what we want. + if (_level > 0) + ret += withBlockBloom(_b, _earliest, _latest, _level - 1, o + _index * c_bloomIndexSize); + else + ret.push_back(o + _index * c_bloomIndexSize); + } + return ret; +} + +h256Set BlockChain::allUnclesFrom(h256 const& _parent) const { // Get all uncles cited given a parent (i.e. featured as uncles/main in parent, parent + 1, ... parent + 5). h256Set ret; @@ -594,7 +953,7 @@ h256Set BlockChain::allUnclesFrom(h256 _parent) const return ret; } -bool BlockChain::isKnown(h256 _hash) const +bool BlockChain::isKnown(h256 const& _hash) const { if (_hash == m_genesisHash) return true; @@ -604,11 +963,41 @@ bool BlockChain::isKnown(h256 _hash) const return true; } string d; - m_blocksDB->Get(m_readOptions, ldb::Slice((char const*)&_hash, 32), &d); + m_blocksDB->Get(m_readOptions, toSlice(_hash), &d); return !!d.size(); } -bytes BlockChain::block(h256 _hash) const +bytes BlockChain::block(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, toSlice(_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]; +} + +bytes BlockChain::oldBlock(h256 const& _hash) const { if (_hash == m_genesisHash) return m_genesisBlock; @@ -621,7 +1010,7 @@ bytes BlockChain::block(h256 _hash) const } string d; - m_blocksDB->Get(m_readOptions, ldb::Slice((char const*)&_hash, 32), &d); + m_blocksDB->Get(m_readOptions, oldToSlice(_hash), &d); if (!d.size()) { diff --git a/libethereum/BlockChain.h b/libethereum/BlockChain.h index 0c1a81066..70432f19c 100644 --- a/libethereum/BlockChain.h +++ b/libethereum/BlockChain.h @@ -30,9 +30,10 @@ #include #include #include +#include #include #include -#include +#include #include "BlockDetails.h" #include "Account.h" #include "Transaction.h" @@ -57,23 +58,29 @@ 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 _h, unsigned _sub = 0); +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; +using UncleHashes = h256s; enum { ExtraDetails = 0, ExtraBlockHash, ExtraTransactionAddress, ExtraLogBlooms, - ExtraReceipts + ExtraReceipts, + ExtraBlocksBlooms }; +using ProgressCallback = std::function; + /** * @brief Implements the blockchain database. All data this gives is disk-backed. * @threadsafe @@ -82,65 +89,101 @@ 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::tuple 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; + std::pair 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); + std::pair 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 _hash) const; + 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 _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 _hash) const; + 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 _hash) const { return queryExtras(_hash, m_details, x_details, NullBlockDetails); } + BlockDetails details(h256 const& _hash) const { return queryExtras(_hash, m_details, x_details, NullBlockDetails); } BlockDetails details() const { return details(currentHash()); } /// Get the transactions' log blooms of a block (or the most recent mined if none given). Thread-safe. - BlockLogBlooms logBlooms(h256 _hash) const { return queryExtras(_hash, m_logBlooms, x_logBlooms, NullBlockLogBlooms); } + BlockLogBlooms logBlooms(h256 const& _hash) const { return queryExtras(_hash, m_logBlooms, x_logBlooms, NullBlockLogBlooms); } BlockLogBlooms logBlooms() const { return logBlooms(currentHash()); } /// Get the transactions' receipts of a block (or the most recent mined if none given). Thread-safe. - BlockReceipts receipts(h256 _hash) const { return queryExtras(_hash, m_receipts, x_receipts, NullBlockReceipts); } + BlockReceipts receipts(h256 const& _hash) const { return queryExtras(_hash, m_receipts, x_receipts, NullBlockReceipts); } BlockReceipts receipts() const { return receipts(currentHash()); } /// Get a list of transaction hashes for a given block. Thread-safe. - TransactionHashes transactionHashes(h256 _hash) const { auto b = block(_hash); RLP rlp(b); h256s ret; for (auto t: rlp[1]) ret.push_back(sha3(t.data())); return ret; } + TransactionHashes transactionHashes(h256 const& _hash) const { auto b = block(_hash); RLP rlp(b); h256s ret; for (auto t: rlp[1]) ret.push_back(sha3(t.data())); return ret; } TransactionHashes transactionHashes() const { return transactionHashes(currentHash()); } - /// Get a list of transaction hashes for a given block. Thread-safe. - h256 numberHash(u256 _index) const { if (!_index) return genesisHash(); return queryExtras(h256(_index), m_blockHashes, x_blockHashes, NullBlockHash).value; } + /// Get a list of uncle hashes for a given block. Thread-safe. + UncleHashes uncleHashes(h256 const& _hash) const { auto b = block(_hash); RLP rlp(b); h256s ret; for (auto t: rlp[2]) ret.push_back(sha3(t.data())); return ret; } + UncleHashes uncleHashes() const { return uncleHashes(currentHash()); } + + /// Get the hash for a given block's number. + h256 numberHash(unsigned _i) const { if (!_i) return genesisHash(); return queryExtras(h256(u256(_i)), m_blockHashes, x_blockHashes, NullBlockHash).value; } + + /// Get the last N hashes for a given block. (N is determined by the LastHashes type.) + LastHashes lastHashes() const { return lastHashes(number()); } + LastHashes lastHashes(unsigned _i) const; + + /** Get the block blooms for a number of blocks. Thread-safe. + * @returns the object pertaining to the blocks: + * level 0: + * 0x, 0x + 1, .. (1x - 1) + * 1x, 1x + 1, .. (2x - 1) + * ... + * (255x .. (256x - 1)) + * level 1: + * 0x .. (1x - 1), 1x .. (2x - 1), ..., (255x .. (256x - 1)) + * 256x .. (257x - 1), 257x .. (258x - 1), ..., (511x .. (512x - 1)) + * ... + * level n, index i, offset o: + * i * (x ^ n) + o * x ^ (n - 1) + */ + 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; /// Get a transaction from its hash. Thread-safe. - bytes transaction(h256 _transactionHash) const { TransactionAddress ta = queryExtras(_transactionHash, m_transactionAddresses, x_transactionAddresses, NullTransactionAddress); if (!ta) return bytes(); return transaction(ta.blockHash, ta.index); } + bytes transaction(h256 const& _transactionHash) const { TransactionAddress ta = queryExtras(_transactionHash, m_transactionAddresses, x_transactionAddresses, NullTransactionAddress); if (!ta) return bytes(); return transaction(ta.blockHash, ta.index); } + std::pair transactionLocation(h256 const& _transactionHash) const { TransactionAddress ta = queryExtras(_transactionHash, m_transactionAddresses, x_transactionAddresses, NullTransactionAddress); if (!ta) return std::pair(h256(), 0); return std::make_pair(ta.blockHash, ta.index); } /// Get a block's transaction (RLP format) for the given block hash (or the most recent mined if none given) & index. Thread-safe. - bytes transaction(h256 _blockHash, unsigned _i) const { bytes b = block(_blockHash); return RLP(b)[1][_i].data().toBytes(); } + bytes transaction(h256 const& _blockHash, unsigned _i) const { bytes b = block(_blockHash); return RLP(b)[1][_i].data().toBytes(); } bytes transaction(unsigned _i) const { return transaction(currentHash(), _i); } + /// Get all transactions from a block. + std::vector transactions(h256 const& _blockHash) const { bytes b = block(_blockHash); std::vector ret; for (auto const& i: RLP(b)[1]) ret.push_back(i.data().toBytes()); return ret; } + std::vector transactions() const { return transactions(currentHash()); } + /// Get a number for the given hash (or the most recent mined if none given). Thread-safe. - unsigned number(h256 _hash) const { return details(_hash).number; } + unsigned number(h256 const& _hash) const { return details(_hash).number; } unsigned number() const { return number(currentHash()); } /// Get a given block (RLP format). Thread-safe. @@ -152,23 +195,37 @@ public: /// Get all blocks not allowed as uncles given a parent (i.e. featured as uncles/main in parent, parent + 1, ... parent + 5). /// @returns set including the header-hash of every parent (including @a _parent) up to and including generation +5 /// togther with all their quoted uncles. - h256Set allUnclesFrom(h256 _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. + h256Set allUnclesFrom(h256 const& _parent) const; + + /// 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 _from, h256 _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 { @@ -188,10 +245,12 @@ public: void garbageCollect(bool _force = false); private: - void open(std::string _path, bool _killExisting = false); + static h256 chunkId(unsigned _level, unsigned _index) { return h256(_index * 0xff + _level); } + + void open(std::string const& _path, WithExisting _we = WithExisting::Trust); void close(); - template T queryExtras(h256 _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); @@ -201,7 +260,31 @@ private: } std::string s; - m_extrasDB->Get(m_readOptions, toSlice(_h, N), &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); + auto it = _m.find(_h); + if (it != _m.end()) + return it->second; + } + + std::string s; + (_extrasDB ? _extrasDB : m_extrasDB)->Get(m_readOptions, oldToSlice(_h, N), &s); if (s.empty()) { // cout << "Not found in DB: " << _h << endl; @@ -230,6 +313,8 @@ private: mutable TransactionAddressHash m_transactionAddresses; mutable SharedMutex x_blockHashes; mutable BlockHashHash m_blockHashes; + mutable SharedMutex x_blocksBlooms; + mutable BlocksBloomsHash m_blocksBlooms; using CacheID = std::pair; mutable Mutex x_cacheUsage; @@ -238,6 +323,11 @@ private: void noteUsed(h256 const& _h, unsigned _extra = (unsigned)-1) const; std::chrono::system_clock::time_point m_lastCollection; + void noteCanonChanged() const { Guard l(x_lastLastHashes); m_lastLastHashes.clear(); } + mutable Mutex x_lastLastHashes; + mutable LastHashes m_lastLastHashes; + mutable unsigned m_lastLastHashesNumber = (unsigned)-1; + void updateStats() const; mutable Statistics m_lastStats; diff --git a/libethereum/BlockDetails.h b/libethereum/BlockDetails.h index ed478568d..572ed1888 100644 --- a/libethereum/BlockDetails.h +++ b/libethereum/BlockDetails.h @@ -36,6 +36,11 @@ namespace dev namespace eth { +// TODO: OPTIMISE: constructors take bytes, RLP used only in necessary classes. + +static const unsigned c_bloomIndexSize = 16; +static const unsigned c_bloomIndexLevels = 2; + struct BlockDetails { BlockDetails(): number(0), totalDifficulty(0) {} @@ -64,6 +69,16 @@ struct BlockLogBlooms mutable unsigned size; }; +struct BlocksBlooms +{ + BlocksBlooms() {} + BlocksBlooms(RLP const& _r) { blooms = _r.toArray(); size = _r.data().size(); } + bytes rlp() const { RLPStream s; s << blooms; size = s.out().size(); return s.out(); } + + std::array blooms; + mutable unsigned size; +}; + struct BlockReceipts { BlockReceipts() {} @@ -103,12 +118,14 @@ using BlockLogBloomsHash = std::map; using BlockReceiptsHash = std::map; using TransactionAddressHash = std::map; using BlockHashHash = std::map; +using BlocksBloomsHash = std::map; static const BlockDetails NullBlockDetails; static const BlockLogBlooms NullBlockLogBlooms; static const BlockReceipts NullBlockReceipts; static const TransactionAddress NullTransactionAddress; static const BlockHash NullBlockHash; +static const BlocksBlooms NullBlocksBlooms; } } diff --git a/libethereum/BlockQueue.cpp b/libethereum/BlockQueue.cpp index 29c9a4c71..25f6ad438 100644 --- a/libethereum/BlockQueue.cpp +++ b/libethereum/BlockQueue.cpp @@ -38,7 +38,7 @@ ImportResult BlockQueue::import(bytesConstRef _block, BlockChain const& _bc) UpgradableGuard l(m_lock); - if (m_readySet.count(h) || m_drainingSet.count(h) || m_unknownSet.count(h)) + if (m_readySet.count(h) || m_drainingSet.count(h) || m_unknownSet.count(h) || m_knownBad.count(h)) { // Already know about this one. cblockq << "Already known."; @@ -48,20 +48,17 @@ ImportResult BlockQueue::import(bytesConstRef _block, BlockChain const& _bc) // VERIFY: populates from the block and checks the block is internally coherent. BlockInfo bi; -#if ETH_CATCH try -#endif { + // TODO: quick verify bi.populate(_block); bi.verifyInternals(_block); } -#if ETH_CATCH catch (Exception const& _e) { cwarn << "Ignoring malformed block: " << diagnostic_information(_e); return ImportResult::Malformed; } -#endif // Check block doesn't already exist first! if (_bc.details(h)) @@ -82,7 +79,13 @@ ImportResult BlockQueue::import(bytesConstRef _block, BlockChain const& _bc) else { // We now know it. - if (!m_readySet.count(bi.parentHash) && !m_drainingSet.count(bi.parentHash) && !_bc.isKnown(bi.parentHash)) + if (m_knownBad.count(bi.parentHash)) + { + m_knownBad.insert(bi.hash()); + // bad parent; this is bad too, note it as such + return ImportResult::BadChain; + } + else if (!m_readySet.count(bi.parentHash) && !m_drainingSet.count(bi.parentHash) && !_bc.isKnown(bi.parentHash)) { // We don't know the parent (yet) - queue it up for later. It'll get resent to us if we find out about its ancestry later on. cblockq << "OK - queued as unknown parent:" << bi.parentHash.abridged(); @@ -104,6 +107,35 @@ ImportResult BlockQueue::import(bytesConstRef _block, BlockChain const& _bc) } } +namespace dev { +template std::set& operator+=(std::set& _a, U const& _b) +{ + for (auto const& i: _b) + _a.insert(i); + return _a; +} } + +bool BlockQueue::doneDrain(h256s const& _bad) +{ + WriteGuard l(m_lock); + m_drainingSet.clear(); + if (_bad.size()) + { + vector old; + swap(m_ready, old); + for (auto& b: old) + { + BlockInfo bi(b); + if (m_knownBad.count(bi.parentHash)) + m_knownBad.insert(bi.hash()); + else + m_ready.push_back(std::move(b)); + } + } + m_knownBad += _bad; + return !m_readySet.empty(); +} + void BlockQueue::tick(BlockChain const& _bc) { unsigned t = time(0); @@ -114,13 +146,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..ce0582db2 100644 --- a/libethereum/BlockQueue.h +++ b/libethereum/BlockQueue.h @@ -26,6 +26,7 @@ #include #include #include +#include namespace dev { @@ -37,14 +38,12 @@ class BlockChain; struct BlockQueueChannel: public LogChannel { static const char* name() { return "[]Q"; } static const int verbosity = 4; }; #define cblockq dev::LogOutputStream() -enum class ImportResult +struct BlockQueueStatus { - Success = 0, - UnknownParent, - FutureTime, - AlreadyInChain, - AlreadyKnown, - Malformed + size_t ready; + size_t future; + size_t unknown; + size_t bad; }; /** @@ -61,12 +60,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(h256s const& _knownBad = h256s()); /// 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); } @@ -80,6 +80,9 @@ public: /// Return first block with an unknown parent. h256 firstUnknown() const { ReadGuard l(m_lock); return m_unknownSet.size() ? *m_unknownSet.begin() : h256(); } + /// Get some infomration on the current status. + BlockQueueStatus status() const { ReadGuard l(m_lock); return BlockQueueStatus{m_ready.size(), m_future.size(), m_unknown.size(), m_knownBad.size()}; } + private: void noteReadyWithoutWriteGuard(h256 _b); void notePresentWithoutWriteGuard(bytesConstRef _block); @@ -91,6 +94,7 @@ private: std::set m_unknownSet; ///< Set of all blocks whose parents are not ready/in-chain. std::multimap> m_unknown; ///< For transactions that have an unknown parent; we map their parent hash to the block stuff, and insert once the block appears. std::multimap m_future; ///< Set of blocks that are not yet valid. + std::set m_knownBad; ///< Set of blocks that we know will never be valid. }; } diff --git a/libethereum/CMakeLists.txt b/libethereum/CMakeLists.txt index 94a4e4497..3df3b9bc3 100644 --- a/libethereum/CMakeLists.txt +++ b/libethereum/CMakeLists.txt @@ -36,6 +36,10 @@ target_link_libraries(${EXECUTABLE} devcrypto) target_link_libraries(${EXECUTABLE} ethcore) target_link_libraries(${EXECUTABLE} secp256k1) +if (ETHASHCL) + target_link_libraries(${EXECUTABLE} ethash-cl) +endif () + if (CMAKE_COMPILER_IS_MINGW) target_link_libraries(${EXECUTABLE} ssp shlwapi) endif() diff --git a/libethereum/CanonBlockChain.cpp b/libethereum/CanonBlockChain.cpp index 569bddca1..b5c47b653 100644 --- a/libethereum/CanonBlockChain.cpp +++ b/libethereum/CanonBlockChain.cpp @@ -85,13 +85,13 @@ bytes CanonBlockChain::createGenesisBlock() stateRoot = state.root(); } - block.appendList(16) - << h256() << EmptyListSHA3 << h160() << stateRoot << EmptyTrie << EmptyTrie << LogBloom() << c_genesisDifficulty << 0 << 1000000 << 0 << (unsigned)0 << string() << h256() << h256() << Nonce(u64(42)); + block.appendList(15) + << h256() << EmptyListSHA3 << h160() << stateRoot << EmptyTrie << EmptyTrie << LogBloom() << c_genesisDifficulty << 0 << c_genesisGasLimit << 0 << (unsigned)0 << string() << h256() << Nonce(u64(42)); block.appendRaw(RLPEmptyList); block.appendRaw(RLPEmptyList); 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 b6c310d34..4ad20b403 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) == 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(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); @@ -186,11 +201,6 @@ void Client::doneWorking() m_postMine = m_preMine; } -void Client::flushTransactions() -{ - doWork(); -} - void Client::killChain() { bool wasMining = isMining(); @@ -207,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(); @@ -249,124 +259,31 @@ void Client::clearPending() noteChanged(changeds); } -unsigned Client::installWatch(h256 _h) -{ - unsigned ret; - { - Guard l(m_filterLock); - ret = m_watches.size() ? m_watches.rbegin()->first + 1 : 0; - m_watches[ret] = ClientWatch(_h); - cwatch << "+++" << ret << _h.abridged(); - } - auto ch = logs(ret); - if (ch.empty()) - ch.push_back(InitialChange); - { - Guard l(m_filterLock); - swap(m_watches[ret].changes, ch); - } - return ret; -} - -unsigned Client::installWatch(LogFilter const& _f) -{ - h256 h = _f.sha3(); - { - Guard l(m_filterLock); - if (!m_filters.count(h)) - { - cwatch << "FFF" << _f << h.abridged(); - m_filters.insert(make_pair(h, _f)); - } - } - return installWatch(h); -} - -void Client::uninstallWatch(unsigned _i) -{ - cwatch << "XXX" << _i; - - Guard l(m_filterLock); - - auto it = m_watches.find(_i); - if (it == m_watches.end()) - return; - auto id = it->second.id; - m_watches.erase(it); - - auto fit = m_filters.find(id); - if (fit != m_filters.end()) - if (!--fit->second.refCount) - { - cwatch << "*X*" << fit->first << ":" << fit->second.filter; - m_filters.erase(fit); - } -} - void Client::noteChanged(h256Set const& _filters) { - Guard l(m_filterLock); + Guard l(x_filtersWatches); if (_filters.size()) cnote << "noteChanged(" << _filters << ")"; // accrue all changes left in each filter into the watches. - for (auto& i: m_watches) - if (_filters.count(i.second.id)) + for (auto& w: m_watches) + if (_filters.count(w.second.id)) { - cwatch << "!!!" << i.first << i.second.id; - if (m_filters.count(i.second.id)) - i.second.changes += m_filters.at(i.second.id).changes; - else - i.second.changes.push_back(LocalisedLogEntry(SpecialLogEntry, 0)); + cwatch << "!!!" << w.first << w.second.id; + if (m_filters.count(w.second.id)) // Normal filtering watch + w.second.changes += m_filters.at(w.second.id).changes; + else // Special ('pending'/'latest') watch + w.second.changes.push_back(LocalisedLogEntry(SpecialLogEntry, 0)); } // clear the filters now. for (auto& i: m_filters) i.second.changes.clear(); } -LocalisedLogEntries Client::peekWatch(unsigned _watchId) const +void Client::appendFromNewPending(TransactionReceipt const& _receipt, h256Set& io_changed, h256 _transactionHash) { - Guard l(m_filterLock); - - try { -#if ETH_DEBUG - cdebug << "peekWatch" << _watchId; -#endif - auto& w = m_watches.at(_watchId); -#if ETH_DEBUG - cdebug << "lastPoll updated to " << chrono::duration_cast(chrono::system_clock::now().time_since_epoch()).count(); -#endif - w.lastPoll = chrono::system_clock::now(); - return w.changes; - } catch (...) {} - - return LocalisedLogEntries(); -} - -LocalisedLogEntries Client::checkWatch(unsigned _watchId) -{ - Guard l(m_filterLock); - LocalisedLogEntries ret; - - try { -#if ETH_DEBUG && 0 - cdebug << "checkWatch" << _watchId; -#endif - auto& w = m_watches.at(_watchId); -#if ETH_DEBUG && 0 - cdebug << "lastPoll updated to " << chrono::duration_cast(chrono::system_clock::now().time_since_epoch()).count(); -#endif - std::swap(ret, w.changes); - w.lastPoll = chrono::system_clock::now(); - } catch (...) {} - - return ret; -} - -void Client::appendFromNewPending(TransactionReceipt const& _receipt, h256Set& io_changed, h256 _sha3) -{ - Guard l(m_filterLock); + Guard l(x_filtersWatches); for (pair& i: m_filters) - if ((unsigned)i.second.filter.latest() > m_bc.number()) + if (i.second.filter.envelops(RelativeBlock::Pending, m_bc.number() + 1)) { // acceptable number. auto m = i.second.filter.matches(_receipt); @@ -374,7 +291,7 @@ void Client::appendFromNewPending(TransactionReceipt const& _receipt, h256Set& i { // filter catches them for (LogEntry const& l: m) - i.second.changes.push_back(LocalisedLogEntry(l, m_bc.number() + 1, _sha3)); + i.second.changes.push_back(LocalisedLogEntry(l, m_bc.number() + 1, _transactionHash)); io_changed.insert(i.first); } } @@ -386,9 +303,9 @@ void Client::appendFromNewBlock(h256 const& _block, h256Set& io_changed) auto d = m_bc.info(_block); auto br = m_bc.receipts(_block); - Guard l(m_filterLock); + Guard l(x_filtersWatches); for (pair& i: m_filters) - if ((unsigned)i.second.filter.latest() >= d.number && (unsigned)i.second.filter.earliest() <= d.number && i.second.filter.matches(d.logBloom)) + if (i.second.filter.envelops(RelativeBlock::Latest, d.number) && i.second.filter.matches(d.logBloom)) // acceptable number & looks like block may contain a matching log entry. for (size_t j = 0; j < br.receipts.size(); j++) { @@ -396,10 +313,10 @@ void Client::appendFromNewBlock(h256 const& _block, h256Set& io_changed) auto m = i.second.filter.matches(tr); if (m.size()) { - auto sha3 = 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, sha3)); + i.second.changes.push_back(LocalisedLogEntry(l, (unsigned)d.number, transactionHash)); io_changed.insert(i.first); } } @@ -416,8 +333,11 @@ void Client::setForceMining(bool _enable) void Client::setMiningThreads(unsigned _threads) { stopMining(); - +#if ETH_ETHASHCL + unsigned t = 1; +#else auto t = _threads ? _threads : thread::hardware_concurrency(); +#endif WriteGuard l(x_localMiners); m_localMiners.clear(); m_localMiners.resize(t); @@ -477,48 +397,9 @@ void Client::setupState(State& _s) _s.commitToMine(m_bc); } -void Client::transact(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice) -{ - startWorking(); - - u256 n; - { - ReadGuard l(x_stateDB); - n = m_postMine.transactionsFrom(toAddress(_secret)); - } - Transaction t(_value, _gasPrice, _gas, _dest, _data, n, _secret); -// cdebug << "Nonce at " << toAddress(_secret) << " pre:" << m_preMine.transactionsFrom(toAddress(_secret)) << " post:" << m_postMine.transactionsFrom(toAddress(_secret)); - StructuredLogger::transactionReceived(t.sha3().abridged(), t.sender().abridged()); - cnote << "New transaction " << t; - m_tq.attemptImport(t.rlp()); -} - -bytes Client::call(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice) -{ - bytes out; - try - { - u256 n; - State temp; - // cdebug << "Nonce at " << toAddress(_secret) << " pre:" << m_preMine.transactionsFrom(toAddress(_secret)) << " post:" << m_postMine.transactionsFrom(toAddress(_secret)); - { - ReadGuard l(x_stateDB); - temp = m_postMine; - n = temp.transactionsFrom(toAddress(_secret)); - } - Transaction t(_value, _gasPrice, _gas, _dest, _data, n, _secret); - u256 gasUsed = temp.execute(m_bc, t.rlp(), &out, false); - (void)gasUsed; // TODO: do something with gasused which it returns. - } - catch (...) - { - // TODO: Some sort of notification of failure. - } - return out; -} - -bytes Client::call(Address _dest, bytes const& _data, u256 _gas, u256 _value, u256 _gasPrice) +ExecutionResult Client::call(Address _dest, bytes const& _data, u256 _gas, u256 _value, u256 _gasPrice) { + ExecutionResult ret; try { State temp; @@ -526,41 +407,18 @@ bytes Client::call(Address _dest, bytes const& _data, u256 _gas, u256 _value, u2 { ReadGuard l(x_stateDB); temp = m_postMine; + temp.addBalance(Address(), _value + _gasPrice * _gas); } Executive e(temp, LastHashes(), 0); if (!e.call(_dest, _dest, Address(), _value, _gasPrice, &_data, _gas, Address())) - { e.go(); - return e.out().toBytes(); - } + ret = e.executionResult(); } catch (...) { // TODO: Some sort of notification of failure. } - return bytes(); -} - -Address Client::transact(Secret _secret, u256 _endowment, bytes const& _init, u256 _gas, u256 _gasPrice) -{ - startWorking(); - - u256 n; - { - ReadGuard l(x_stateDB); - n = m_postMine.transactionsFrom(toAddress(_secret)); - } - Transaction t(_endowment, _gasPrice, _gas, _init, n, _secret); - cnote << "New transaction " << t; - m_tq.attemptImport(t.rlp()); - return right160(sha3(rlpList(t.sender(), t.nonce()))); -} - -void Client::inject(bytesConstRef _rlp) -{ - startWorking(); - - m_tq.attemptImport(_rlp); + return ret; } pair Client::getWork() @@ -583,6 +441,8 @@ void Client::doWork() { // TODO: Use condition variable rather than polling. + bool stillGotWork = false; + cworkin << "WORK"; h256Set changeds; @@ -593,6 +453,7 @@ void Client::doWork() // TODO: enable a short-circuit option since we mined it. will need to get the end state from the miner. auto lm = dynamic_cast(&m); h256s hs; + h256 c; if (false && lm && !m_verifyOwnBlocks) { // TODO: implement @@ -603,12 +464,13 @@ void Client::doWork() { cwork << "CHAIN <== postSTATE"; WriteGuard l(x_stateDB); - hs = m_bc.attemptImport(m.blockData(), m_stateDB); + tie(hs, c) = m_bc.attemptImport(m.blockData(), m_stateDB); } if (hs.size()) { for (auto const& h: hs) - appendFromNewBlock(h, changeds); + if (h != c) + appendFromNewBlock(h, changeds); changeds.insert(ChainChangedFilter); } for (auto& m: m_localMiners) @@ -638,15 +500,42 @@ 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. - if (newBlocks.size()) + h256s fresh; + h256s dead; + bool sgw; + tie(fresh, dead, sgw) = m_bc.sync(m_bq, db, 100); + + // insert transactions that we are declaring the dead part of the chain + for (auto const& h: dead) + { + clog(ClientNote) << "Dead block:" << h.abridged(); + for (auto const& t: m_bc.transactions(h)) + { + clog(ClientNote) << "Resubmitting transaction " << Transaction(t, CheckSignature::None); + m_tq.import(t); + } + } + + // remove transactions from m_tq nicely rather than relying on out of date nonce later on. + for (auto const& h: fresh) + { + clog(ClientChat) << "Mined block:" << h.abridged(); + for (auto const& th: m_bc.transactionHashes(h)) + { + clog(ClientNote) << "Safely dropping transaction " << th; + m_tq.drop(th); + } + } + + stillGotWork = stillGotWork | sgw; + if (!fresh.empty()) { - for (auto i: newBlocks) + for (auto i: fresh) appendFromNewBlock(i, changeds); changeds.insert(ChainChangedFilter); } x_stateDB.lock(); - if (newBlocks.size()) + if (fresh.size()) m_stateDB = db; cwork << "preSTATE <== CHAIN"; @@ -660,7 +549,7 @@ void Client::doWork() // TODO: Move transactions pending from m_postMine back to transaction queue. } - // returns h256s as blooms, once for each transaction. + // returns TransactionReceipts, once for each transaction. cwork << "postSTATE <== TQ"; TransactionReceipts newPendingReceipts = m_postMine.sync(m_bc, m_tq, *m_gp); if (newPendingReceipts.size()) @@ -673,8 +562,15 @@ void Client::doWork() if (isMining()) cnote << "Additional transaction ready: Restarting mining operation."; resyncStateNeeded = true; + if (auto h = m_host.lock()) + h->noteNewTransactions(); } } + + if (!changeds.empty()) + if (auto h = m_host.lock()) + h->noteNewBlocks(); + if (resyncStateNeeded) { ReadGuard l(x_localMiners); @@ -686,15 +582,17 @@ 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 vector toUninstall; { - Guard l(m_filterLock); + Guard l(x_filtersWatches); for (auto key: keysOf(m_watches)) - if (chrono::system_clock::now() - m_watches[key].lastPoll > chrono::seconds(20)) + if (m_watches[key].lastPoll != chrono::system_clock::time_point::max() && chrono::system_clock::now() - m_watches[key].lastPoll > chrono::seconds(20)) { toUninstall.push_back(key); cnote << "GC: Uninstall" << key << "(" << chrono::duration_cast(chrono::system_clock::now() - m_watches[key].lastPoll).count() << "s old)"; @@ -710,25 +608,15 @@ void Client::doWork() } } -unsigned Client::numberOf(int _n) const +State Client::asOf(h256 const& _block) const { - if (_n > 0) - return _n; - else if (_n == GenesisBlock) - return 0; - else - return m_bc.details().number + max(-(int)m_bc.details().number, 1 + _n); + ReadGuard l(x_stateDB); + return State(m_stateDB, bc(), _block); } -State Client::asOf(int _h) const +void Client::prepareForTransaction() { - ReadGuard l(x_stateDB); - if (_h == 0) - return m_postMine; - else if (_h == -1) - return m_preMine; - else - return State(m_stateDB, m_bc, m_bc.numberHash(numberOf(_h))); + startWorking(); } State Client::state(unsigned _txi, h256 _block) const @@ -749,166 +637,14 @@ eth::State Client::state(unsigned _txi) const return m_postMine.fromPending(_txi); } -StateDiff Client::diff(unsigned _txi, int _block) const -{ - State st = asOf(_block); - return st.fromPending(_txi).diff(st.fromPending(_txi + 1)); -} - -StateDiff Client::diff(unsigned _txi, h256 _block) const -{ - State st = state(_block); - return st.fromPending(_txi).diff(st.fromPending(_txi + 1)); -} - -std::vector
Client::addresses(int _block) const -{ - vector
ret; - for (auto const& i: asOf(_block).addresses()) - ret.push_back(i.first); - return ret; -} - -u256 Client::balanceAt(Address _a, int _block) const -{ - return asOf(_block).balance(_a); -} - -std::map Client::storageAt(Address _a, int _block) const -{ - return asOf(_block).storage(_a); -} - -u256 Client::countAt(Address _a, int _block) const -{ - return asOf(_block).transactionsFrom(_a); -} - -u256 Client::stateAt(Address _a, u256 _l, int _block) const -{ - return asOf(_block).storage(_a, _l); -} - -bytes Client::codeAt(Address _a, int _block) const -{ - return asOf(_block).code(_a); -} - -Transaction Client::transaction(h256 _blockHash, unsigned _i) const -{ - auto bl = m_bc.block(_blockHash); - RLP b(bl); - if (_i < b[1].itemCount()) - return Transaction(b[1][_i].data(), CheckSignature::Range); - else - return Transaction(); -} - -BlockInfo Client::uncle(h256 _blockHash, unsigned _i) const -{ - auto bl = m_bc.block(_blockHash); - RLP b(bl); - if (_i < b[2].itemCount()) - return BlockInfo::fromHeader(b[2][_i].data()); - else - return BlockInfo(); -} - -unsigned Client::transactionCount(h256 _blockHash) const -{ - auto bl = m_bc.block(_blockHash); - RLP b(bl); - return b[1].itemCount(); -} - -unsigned Client::uncleCount(h256 _blockHash) const +void Client::inject(bytesConstRef _rlp) { - auto bl = m_bc.block(_blockHash); - RLP b(bl); - return b[2].itemCount(); + startWorking(); + + m_tq.import(_rlp); } -LocalisedLogEntries Client::logs(LogFilter const& _f) const +void Client::flushTransactions() { - LocalisedLogEntries ret; - unsigned begin = min(m_bc.number() + 1, (unsigned)_f.latest()); - unsigned end = min(m_bc.number(), min(begin, (unsigned)_f.earliest())); - unsigned m = _f.max(); - unsigned s = _f.skip(); - - // Handle pending transactions differently as they're not on the block chain. - if (begin > m_bc.number()) - { - ReadGuard l(x_stateDB); - for (unsigned i = 0; i < m_postMine.pending().size(); ++i) - { - // Might have a transaction that contains a matching log. - TransactionReceipt const& tr = m_postMine.receipt(i); - auto sha3 = m_postMine.pending()[i].sha3(); - LogEntries le = _f.matches(tr); - if (le.size()) - { - for (unsigned j = 0; j < le.size() && ret.size() != m; ++j) - if (s) - s--; - else - ret.insert(ret.begin(), LocalisedLogEntry(le[j], begin, sha3)); - } - } - begin = m_bc.number(); - } - -#if ETH_DEBUG - // fill these params - unsigned skipped = 0; - unsigned falsePos = 0; -#endif - auto h = m_bc.numberHash(begin); - unsigned n = begin; - for (; ret.size() != m && n != end; n--, h = m_bc.details(h).parent) - { -#if ETH_DEBUG - int total = 0; -#endif - // check block bloom - auto info = m_bc.info(h); - auto receipts = m_bc.receipts(h).receipts; - if (_f.matches(info.logBloom)) - for (size_t i = 0; i < receipts.size(); i++) - { - TransactionReceipt receipt = receipts[i]; - if (_f.matches(receipt.bloom())) - { - auto h = transaction(info.hash, i).sha3(); - LogEntries le = _f.matches(receipt); - if (le.size()) - { -#if ETH_DEBUG - total += le.size(); -#endif - for (unsigned j = 0; j < le.size() && ret.size() != m; ++j) - { - if (s) - s--; - else - ret.insert(ret.begin(), LocalisedLogEntry(le[j], n, h)); - } - } - } -#if ETH_DEBUG - if (!total) - falsePos++; -#endif - } -#if ETH_DEBUG - else - skipped++; -#endif - if (n == end) - break; - } -#if ETH_DEBUG - cdebug << (begin - n) << "searched; " << skipped << "skipped; " << falsePos << "false +ves"; -#endif - return ret; + doWork(); } diff --git a/libethereum/Client.h b/libethereum/Client.h index 9cbfd7989..bdd875e95 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -40,13 +40,12 @@ #include "TransactionQueue.h" #include "State.h" #include "CommonNet.h" -#include "LogFilter.h" #include "Miner.h" -#include "Interface.h" +#include "ABI.h" +#include "ClientBase.h" namespace dev { - namespace eth { @@ -66,78 +65,13 @@ 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; }; -static const int GenesisBlock = INT_MIN; - -struct InstalledFilter -{ - InstalledFilter(LogFilter const& _f): filter(_f) {} - - LogFilter filter; - unsigned refCount = 1; - LocalisedLogEntries changes; -}; - -static const h256 PendingChangedFilter = u256(0); -static const h256 ChainChangedFilter = u256(1); - -static const LogEntry SpecialLogEntry = LogEntry(Address(), h256s(), bytes()); -static const LocalisedLogEntry InitialChange(SpecialLogEntry, 0); - -struct ClientWatch -{ - ClientWatch(): lastPoll(std::chrono::system_clock::now()) {} - explicit ClientWatch(h256 _id): id(_id), lastPoll(std::chrono::system_clock::now()) {} - - h256 id; - LocalisedLogEntries changes = LocalisedLogEntries{ InitialChange }; - mutable std::chrono::system_clock::time_point lastPoll = std::chrono::system_clock::now(); -}; - -struct WatchChannel: public LogChannel { static const char* name() { return "(o)"; } static const int verbosity = 7; }; -#define cwatch dev::LogOutputStream() -struct WorkInChannel: public LogChannel { static const char* name() { return ">W>"; } static const int verbosity = 16; }; -struct WorkOutChannel: public LogChannel { static const char* name() { return "() -#define cworkin dev::LogOutputStream() -#define cworkout dev::LogOutputStream() - -template struct ABISerialiser {}; -template struct ABISerialiser> { static bytes serialise(FixedHash const& _t) { static_assert(N <= 32, "Cannot serialise hash > 32 bytes."); static_assert(N > 0, "Cannot serialise zero-length hash."); return bytes(32 - N, 0) + _t.asBytes(); } }; -template <> struct ABISerialiser { static bytes serialise(u256 const& _t) { return h256(_t).asBytes(); } }; -template <> struct ABISerialiser { static bytes serialise(u160 const& _t) { return bytes(12, 0) + h160(_t).asBytes(); } }; -template <> struct ABISerialiser { static bytes serialise(string32 const& _t) { return bytesConstRef((byte const*)_t.data(), 32).toBytes(); } }; - -inline bytes abiInAux() { return {}; } -template bytes abiInAux(T const& _t, U const& ... _u) -{ - return ABISerialiser::serialise(_t) + abiInAux(_u ...); -} - -template bytes abiIn(std::string _id, T const& ... _t) -{ - return sha3(_id).ref().cropped(0, 4).toBytes() + abiInAux(_t ...); -} - -template struct ABIDeserialiser {}; -template struct ABIDeserialiser> { static FixedHash deserialise(bytesConstRef& io_t) { static_assert(N <= 32, "Parameter sizes must be at most 32 bytes."); FixedHash ret; io_t.cropped(32 - N, N).populate(ret.ref()); io_t = io_t.cropped(32); return ret; } }; -template <> struct ABIDeserialiser { static u256 deserialise(bytesConstRef& io_t) { u256 ret = fromBigEndian(io_t.cropped(0, 32)); io_t = io_t.cropped(32); return ret; } }; -template <> struct ABIDeserialiser { static u160 deserialise(bytesConstRef& io_t) { u160 ret = fromBigEndian(io_t.cropped(12, 20)); io_t = io_t.cropped(32); return ret; } }; -template <> struct ABIDeserialiser { static string32 deserialise(bytesConstRef& io_t) { string32 ret; io_t.cropped(0, 32).populate(bytesRef((byte*)ret.data(), 32)); io_t = io_t.cropped(32); return ret; } }; - -template T abiOut(bytes const& _data) -{ - bytesConstRef o(&_data); - return ABIDeserialiser::deserialise(o); -} - class RemoteMiner: public Miner { public: @@ -176,14 +110,19 @@ public: private: u256 m_weiPerRef; u256 m_refsPerBlock; - u256 m_gasPerBlock = 1000000; + u256 m_gasPerBlock = 3141592; std::array m_octiles; }; +struct ClientNote: public LogChannel { static const char* name() { return "*C*"; } static const int verbosity = 2; }; +struct ClientChat: public LogChannel { static const char* name() { return "=C="; } static const int verbosity = 4; }; +struct ClientTrace: public LogChannel { static const char* name() { return "-C-"; } static const int verbosity = 7; }; +struct ClientDetail: public LogChannel { static const char* name() { return " C "; } static const int verbosity = 14; }; + /** * @brief Main API hub for interfacing with Ethereum. */ -class Client: public MinerHost, public Interface, Worker +class Client: public MinerHost, public ClientBase, Worker { friend class Miner; @@ -192,7 +131,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 ); @@ -201,7 +140,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 ); @@ -212,81 +151,20 @@ public: /// Resets the gas pricer to some other object. void setGasPricer(std::shared_ptr _gp) { m_gp = _gp; } - /// Submits the given message-call transaction. - virtual void transact(Secret _secret, u256 _value, Address _dest, bytes const& _data = bytes(), u256 _gas = 10000, u256 _gasPrice = 10 * szabo); - - /// Submits a new contract-creation transaction. - /// @returns the new contract's address (assuming it all goes through). - virtual Address transact(Secret _secret, u256 _endowment, bytes const& _init, u256 _gas = 10000, u256 _gasPrice = 10 * szabo); - /// Injects the RLP-encoded transaction given by the _rlp into the transaction queue directly. virtual void inject(bytesConstRef _rlp); /// Blocks until all pending transactions have been processed. - virtual void flushTransactions(); - - /// Makes the given call. Nothing is recorded into the state. - virtual bytes call(Secret _secret, u256 _value, Address _dest, bytes const& _data = bytes(), u256 _gas = 10000, u256 _gasPrice = 10 * szabo); + virtual void flushTransactions() override; + using Interface::call; // to remove warning about hiding virtual function /// Makes the given call. Nothing is recorded into the state. This cheats by creating a null address and endowing it with a lot of ETH. - virtual bytes call(Address _dest, bytes const& _data = bytes(), u256 _gas = 125000, u256 _value = 0, u256 _gasPrice = 1 * ether); - - // Informational stuff - - // [NEW API] - - using Interface::balanceAt; - using Interface::countAt; - using Interface::stateAt; - using Interface::codeAt; - using Interface::storageAt; - - virtual u256 balanceAt(Address _a, int _block) const; - virtual u256 countAt(Address _a, int _block) const; - virtual u256 stateAt(Address _a, u256 _l, int _block) const; - virtual bytes codeAt(Address _a, int _block) const; - virtual std::map storageAt(Address _a, int _block) const; - - virtual unsigned installWatch(LogFilter const& _filter); - virtual unsigned installWatch(h256 _filterId); - virtual void uninstallWatch(unsigned _watchId); - virtual LocalisedLogEntries peekWatch(unsigned _watchId) const; - virtual LocalisedLogEntries checkWatch(unsigned _watchId); - - virtual LocalisedLogEntries logs(unsigned _watchId) const { try { Guard l(m_filterLock); return logs(m_filters.at(m_watches.at(_watchId).id).filter); } catch (...) { return LocalisedLogEntries(); } } - virtual LocalisedLogEntries logs(LogFilter const& _filter) const; - - // [EXTRA API]: - - /// @returns the length of the chain. - virtual unsigned number() const { return m_bc.number(); } - - /// Get the list of pending transactions. - /// @TODO: Remove in favour of transactions(). - virtual Transactions pending() const { return m_postMine.pending(); } - - virtual h256 hashFromNumber(unsigned _number) const { return m_bc.numberHash(_number); } - virtual BlockInfo blockInfo(h256 _hash) const { return BlockInfo(m_bc.block(_hash)); } - virtual BlockDetails blockDetails(h256 _hash) const { return m_bc.details(_hash); } - virtual Transaction transaction(h256 _blockHash, unsigned _i) const; - virtual BlockInfo uncle(h256 _blockHash, unsigned _i) const; - virtual unsigned transactionCount(h256 _blockHash) const; - virtual unsigned uncleCount(h256 _blockHash) const; - - /// Differences between transactions. - using Interface::diff; - virtual StateDiff diff(unsigned _txi, h256 _block) const; - virtual StateDiff diff(unsigned _txi, int _block) const; - - /// Get a list of all active addresses. - using Interface::addresses; - virtual std::vector
addresses(int _block) const; + ExecutionResult call(Address _dest, bytes const& _data = bytes(), u256 _gas = 125000, u256 _value = 0, u256 _gasPrice = 1 * ether); /// Get the remaining gas limit in this block. virtual u256 gasLimitRemaining() const { return m_postMine.gasLimitRemaining(); } // [PRIVATE API - only relevant for base clients, not available in general] - dev::eth::State state(unsigned _txi, h256 _block) const; dev::eth::State state(h256 _block) const; dev::eth::State state(unsigned _txi) const; @@ -295,9 +173,13 @@ public: dev::eth::State postState() const { ReadGuard l(x_stateDB); return m_postMine; } /// Get the object representing the current canonical blockchain. CanonBlockChain const& blockChain() const { return m_bc; } + /// Get some information on the block queue. + BlockQueueStatus blockQueueStatus() const { return m_bq.status(); } // Mining stuff: + void setAddress(Address _us) { WriteGuard l(x_stateDB); m_preMine.setAddress(_us); } + /// Check block validity prior to mining. bool miningParanoia() const { return m_paranoia; } /// Change whether we check block validity prior to mining. @@ -311,22 +193,18 @@ public: /// Enable/disable fast mining. void setTurboMining(bool _enable = true) { m_turboMining = _enable; } - /// Set the coinbase address. - virtual void setAddress(Address _us) { m_preMine.setAddress(_us); } - /// Get the coinbase address. - virtual Address address() const { return m_preMine.address(); } /// Stops mining and sets the number of mining threads (0 for automatic). virtual void setMiningThreads(unsigned _threads = 0); /// Get the effective number of mining threads. virtual unsigned miningThreads() const { ReadGuard l(x_localMiners); return m_localMiners.size(); } /// Start mining. /// NOT thread-safe - call it & stopMining only from a single thread - virtual void startMining() { startWorking(); ReadGuard l(x_localMiners); for (auto& m: m_localMiners) m.start(); } + virtual void startMining() { startWorking(); { ReadGuard l(x_localMiners); for (auto& m: m_localMiners) m.start(); } } /// Stop mining. /// NOT thread-safe - virtual void stopMining() { ReadGuard l(x_localMiners); for (auto& m: m_localMiners) m.stop(); } + virtual void stopMining() { { ReadGuard l(x_localMiners); for (auto& m: m_localMiners) m.stop(); } } /// Are we mining now? - virtual bool isMining() { ReadGuard l(x_localMiners); return m_localMiners.size() && m_localMiners[0].isRunning(); } + virtual bool isMining() { { ReadGuard l(x_localMiners); if (!m_localMiners.empty() && m_localMiners[0].isRunning()) return true; } return false; } /// Check the progress of the mining. virtual MineProgress miningProgress() const; /// Get and clear the mining history. @@ -350,6 +228,17 @@ public: void killChain(); protected: + /// InterfaceStub methods + virtual BlockChain const& bc() const override { return m_bc; } + + /// Returns the state object for the full block (i.e. the terminal state) for index _h. + /// Works properly with LatestBlock and PendingBlock. + using ClientBase::asOf; + virtual State asOf(h256 const& _block) const override; + virtual State preMine() const override { ReadGuard l(x_stateDB); return m_preMine; } + virtual State postMine() const override { ReadGuard l(x_stateDB); return m_postMine; } + virtual void prepareForTransaction() override; + /// Collate the changed filters for the bloom filter of the given pending transaction. /// Insert any filters that are activated into @a o_changed. void appendFromNewPending(TransactionReceipt const& _receipt, h256Set& io_changed, h256 _sha3); @@ -374,15 +263,8 @@ private: virtual bool turbo() const { return m_turboMining; } virtual bool force() const { return m_forceMining; } - /// Return the actual block number of the block with the given int-number (positive is the same, INT_MIN is genesis block, < 0 is negative age, thus -1 is most recently mined, 0 is pending. - unsigned numberOf(int _b) const; - - State asOf(int _h) const; - State asOf(unsigned _h) const; - VersionChecker m_vc; ///< Dummy object to check & update the protocol version. CanonBlockChain m_bc; ///< Maintains block database. - TransactionQueue m_tq; ///< Maintains a list of incoming transactions not yet in a block on the blockchain. BlockQueue m_bq; ///< Maintains a list of incoming blocks not yet on the blockchain (to be imported). std::shared_ptr m_gp; ///< The gas pricer. @@ -403,12 +285,6 @@ private: bool m_forceMining = false; ///< Mine even when there are no transactions pending? bool m_verifyOwnBlocks = true; ///< Should be verify blocks that we mined? - - - mutable Mutex m_filterLock; - std::map m_filters; - std::map m_watches; - mutable std::chrono::system_clock::time_point m_lastGarbageCollection; }; diff --git a/libethereum/ClientBase.cpp b/libethereum/ClientBase.cpp new file mode 100644 index 000000000..b9c2fa878 --- /dev/null +++ b/libethereum/ClientBase.cpp @@ -0,0 +1,401 @@ +/* + 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 ClientBase.cpp + * @author Gav Wood + * @author Marek Kotewicz + * @date 2015 + */ + +#include +#include "ClientBase.h" +#include "BlockChain.h" +#include "Executive.h" + +using namespace std; +using namespace dev; +using namespace dev::eth; + +State ClientBase::asOf(BlockNumber _h) const +{ + if (_h == PendingBlock) + return postMine(); + else if (_h == LatestBlock) + return preMine(); + return asOf(bc().numberHash(_h)); +} + +void ClientBase::submitTransaction(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice) +{ + prepareForTransaction(); + + u256 n = postMine().transactionsFrom(toAddress(_secret)); + Transaction t(_value, _gasPrice, _gas, _dest, _data, n, _secret); + m_tq.import(t.rlp()); + + StructuredLogger::transactionReceived(t.sha3().abridged(), t.sender().abridged()); + cnote << "New transaction " << t; +} + +Address ClientBase::submitTransaction(Secret _secret, u256 _endowment, bytes const& _init, u256 _gas, u256 _gasPrice) +{ + prepareForTransaction(); + + u256 n = postMine().transactionsFrom(toAddress(_secret)); + Transaction t(_endowment, _gasPrice, _gas, _init, n, _secret); + m_tq.import(t.rlp()); + + StructuredLogger::transactionReceived(t.sha3().abridged(), t.sender().abridged()); + cnote << "New transaction " << t; + + return right160(sha3(rlpList(t.sender(), t.nonce()))); +} + +// TODO: remove try/catch, allow exceptions +ExecutionResult ClientBase::call(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice, BlockNumber _blockNumber) +{ + ExecutionResult ret; + try + { + State temp = asOf(_blockNumber); + u256 n = temp.transactionsFrom(toAddress(_secret)); + Transaction t(_value, _gasPrice, _gas, _dest, _data, n, _secret); + ret = temp.execute(bc().lastHashes(), t, Permanence::Reverted); + } + catch (...) + { + // TODO: Some sort of notification of failure. + } + return ret; +} + +ExecutionResult ClientBase::create(Secret _secret, u256 _value, bytes const& _data, u256 _gas, u256 _gasPrice, BlockNumber _blockNumber) +{ + ExecutionResult ret; + try + { + State temp = asOf(_blockNumber); + u256 n = temp.transactionsFrom(toAddress(_secret)); + // cdebug << "Nonce at " << toAddress(_secret) << " pre:" << m_preMine.transactionsFrom(toAddress(_secret)) << " post:" << m_postMine.transactionsFrom(toAddress(_secret)); + + Transaction t(_value, _gasPrice, _gas, _data, n, _secret); + ret = temp.execute(bc().lastHashes(), t, Permanence::Reverted); + } + catch (...) + { + // TODO: Some sort of notification of failure. + } + return ret; +} + +u256 ClientBase::balanceAt(Address _a, BlockNumber _block) const +{ + return asOf(_block).balance(_a); +} + +u256 ClientBase::countAt(Address _a, BlockNumber _block) const +{ + return asOf(_block).transactionsFrom(_a); +} + +u256 ClientBase::stateAt(Address _a, u256 _l, BlockNumber _block) const +{ + return asOf(_block).storage(_a, _l); +} + +bytes ClientBase::codeAt(Address _a, BlockNumber _block) const +{ + return asOf(_block).code(_a); +} + +map ClientBase::storageAt(Address _a, BlockNumber _block) const +{ + return asOf(_block).storage(_a); +} + +// TODO: remove try/catch, allow exceptions +LocalisedLogEntries ClientBase::logs(unsigned _watchId) const +{ + LogFilter f; + try + { + Guard l(x_filtersWatches); + f = m_filters.at(m_watches.at(_watchId).id).filter; + } + catch (...) + { + return LocalisedLogEntries(); + } + return logs(f); +} + +LocalisedLogEntries ClientBase::logs(LogFilter const& _f) const +{ + LocalisedLogEntries ret; + unsigned begin = min(bc().number() + 1, (unsigned)_f.latest()); + unsigned end = min(bc().number(), min(begin, (unsigned)_f.earliest())); + + // Handle pending transactions differently as they're not on the block chain. + if (begin > bc().number()) + { + State temp = postMine(); + for (unsigned i = 0; i < temp.pending().size(); ++i) + { + // Might have a transaction that contains a matching log. + TransactionReceipt const& tr = temp.receipt(i); + auto th = temp.pending()[i].sha3(); + LogEntries le = _f.matches(tr); + if (le.size()) + for (unsigned j = 0; j < le.size(); ++j) + ret.insert(ret.begin(), LocalisedLogEntry(le[j], begin, th)); + } + begin = bc().number(); + } + + set matchingBlocks; + for (auto const& i: _f.bloomPossibilities()) + for (auto u: bc().withBlockBloom(i, end, begin)) + matchingBlocks.insert(u); + + unsigned falsePos = 0; + for (auto n: matchingBlocks) + { + int total = 0; + auto h = bc().numberHash(n); + auto receipts = bc().receipts(h).receipts; + for (size_t i = 0; i < receipts.size(); i++) + { + TransactionReceipt receipt = receipts[i]; + if (_f.matches(receipt.bloom())) + { + auto info = bc().info(h); + auto th = transaction(info.hash(), i).sha3(); + LogEntries le = _f.matches(receipt); + if (le.size()) + { + total += le.size(); + for (unsigned j = 0; j < le.size(); ++j) + ret.insert(ret.begin(), LocalisedLogEntry(le[j], n, th)); + } + } + + if (!total) + falsePos++; + } + } + + cdebug << matchingBlocks.size() << "searched from" << (end - begin) << "skipped; " << falsePos << "false +ves"; + return ret; +} + +unsigned ClientBase::installWatch(LogFilter const& _f, Reaping _r) +{ + h256 h = _f.sha3(); + { + Guard l(x_filtersWatches); + if (!m_filters.count(h)) + { + cwatch << "FFF" << _f << h.abridged(); + m_filters.insert(make_pair(h, _f)); + } + } + return installWatch(h, _r); +} + +unsigned ClientBase::installWatch(h256 _h, Reaping _r) +{ + unsigned ret; + { + Guard l(x_filtersWatches); + ret = m_watches.size() ? m_watches.rbegin()->first + 1 : 0; + m_watches[ret] = ClientWatch(_h, _r); + cwatch << "+++" << ret << _h.abridged(); + } +#if INITIAL_STATE_AS_CHANGES + auto ch = logs(ret); + if (ch.empty()) + ch.push_back(InitialChange); + { + Guard l(x_filtersWatches); + swap(m_watches[ret].changes, ch); + } +#endif + return ret; +} + +bool ClientBase::uninstallWatch(unsigned _i) +{ + cwatch << "XXX" << _i; + + Guard l(x_filtersWatches); + + auto it = m_watches.find(_i); + if (it == m_watches.end()) + return false; + auto id = it->second.id; + m_watches.erase(it); + + auto fit = m_filters.find(id); + if (fit != m_filters.end()) + if (!--fit->second.refCount) + { + cwatch << "*X*" << fit->first << ":" << fit->second.filter; + m_filters.erase(fit); + } + return true; +} + +LocalisedLogEntries ClientBase::peekWatch(unsigned _watchId) const +{ + Guard l(x_filtersWatches); + +// cwatch << "peekWatch" << _watchId; + auto& w = m_watches.at(_watchId); +// cwatch << "lastPoll updated to " << chrono::duration_cast(chrono::system_clock::now().time_since_epoch()).count(); + if (w.lastPoll != chrono::system_clock::time_point::max()) + w.lastPoll = chrono::system_clock::now(); + return w.changes; +} + +LocalisedLogEntries ClientBase::checkWatch(unsigned _watchId) +{ + Guard l(x_filtersWatches); + LocalisedLogEntries ret; + +// cwatch << "checkWatch" << _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); + if (w.lastPoll != chrono::system_clock::time_point::max()) + w.lastPoll = chrono::system_clock::now(); + + return ret; +} + +h256 ClientBase::hashFromNumber(unsigned _number) const +{ + return bc().numberHash(_number); +} + +BlockInfo ClientBase::blockInfo(h256 _hash) const +{ + return BlockInfo(bc().block(_hash)); +} + +BlockDetails ClientBase::blockDetails(h256 _hash) const +{ + return bc().details(_hash); +} + +Transaction ClientBase::transaction(h256 _transactionHash) const +{ + return Transaction(bc().transaction(_transactionHash), CheckSignature::Range); +} + +Transaction ClientBase::transaction(h256 _blockHash, unsigned _i) const +{ + auto bl = bc().block(_blockHash); + RLP b(bl); + if (_i < b[1].itemCount()) + return Transaction(b[1][_i].data(), CheckSignature::Range); + else + return Transaction(); +} + +Transactions ClientBase::transactions(h256 _blockHash) const +{ + auto bl = bc().block(_blockHash); + RLP b(bl); + Transactions res; + for (unsigned i = 0; i < b[1].itemCount(); i++) + res.emplace_back(b[1][i].data(), CheckSignature::Range); + return res; +} + +TransactionHashes ClientBase::transactionHashes(h256 _blockHash) const +{ + return bc().transactionHashes(_blockHash); +} + +BlockInfo ClientBase::uncle(h256 _blockHash, unsigned _i) const +{ + auto bl = bc().block(_blockHash); + RLP b(bl); + if (_i < b[2].itemCount()) + return BlockInfo::fromHeader(b[2][_i].data()); + else + return BlockInfo(); +} + +UncleHashes ClientBase::uncleHashes(h256 _blockHash) const +{ + return bc().uncleHashes(_blockHash); +} + +unsigned ClientBase::transactionCount(h256 _blockHash) const +{ + auto bl = bc().block(_blockHash); + RLP b(bl); + return b[1].itemCount(); +} + +unsigned ClientBase::uncleCount(h256 _blockHash) const +{ + auto bl = bc().block(_blockHash); + RLP b(bl); + return b[2].itemCount(); +} + +unsigned ClientBase::number() const +{ + return bc().number(); +} + +Transactions ClientBase::pending() const +{ + return postMine().pending(); +} + + +StateDiff ClientBase::diff(unsigned _txi, h256 _block) const +{ + State st = asOf(_block); + return st.fromPending(_txi).diff(st.fromPending(_txi + 1)); +} + +StateDiff ClientBase::diff(unsigned _txi, BlockNumber _block) const +{ + State st = asOf(_block); + return st.fromPending(_txi).diff(st.fromPending(_txi + 1)); +} + +Addresses ClientBase::addresses(BlockNumber _block) const +{ + Addresses ret; + for (auto const& i: asOf(_block).addresses()) + ret.push_back(i.first); + return ret; +} + +u256 ClientBase::gasLimitRemaining() const +{ + return postMine().gasLimitRemaining(); +} + +Address ClientBase::address() const +{ + return preMine().address(); +} diff --git a/libethereum/ClientBase.h b/libethereum/ClientBase.h new file mode 100644 index 000000000..10e4ca84b --- /dev/null +++ b/libethereum/ClientBase.h @@ -0,0 +1,171 @@ +/* + 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 ClientBase.h + * @author Gav Wood + * @author Marek Kotewicz + * @date 2015 + */ + +#pragma once + +#include +#include "Interface.h" +#include "LogFilter.h" + +namespace dev { + +namespace eth { + +struct InstalledFilter +{ + InstalledFilter(LogFilter const& _f): filter(_f) {} + + LogFilter filter; + unsigned refCount = 1; + LocalisedLogEntries changes; +}; + +static const h256 PendingChangedFilter = u256(0); +static const h256 ChainChangedFilter = u256(1); + +static const LogEntry SpecialLogEntry = LogEntry(Address(), h256s(), bytes()); +static const LocalisedLogEntry InitialChange(SpecialLogEntry, 0); + +struct ClientWatch +{ + ClientWatch(): lastPoll(std::chrono::system_clock::now()) {} + explicit ClientWatch(h256 _id, Reaping _r): id(_id), lastPoll(_r == Reaping::Automatic ? std::chrono::system_clock::now() : std::chrono::system_clock::time_point::max()) {} + + h256 id; +#if INITIAL_STATE_AS_CHANGES + LocalisedLogEntries changes = LocalisedLogEntries{ InitialChange }; +#else + LocalisedLogEntries changes; +#endif + mutable std::chrono::system_clock::time_point lastPoll = std::chrono::system_clock::now(); +}; + +struct WatchChannel: public LogChannel { static const char* name() { return "(o)"; } static const int verbosity = 7; }; +#define cwatch dev::LogOutputStream() +struct WorkInChannel: public LogChannel { static const char* name() { return ">W>"; } static const int verbosity = 16; }; +struct WorkOutChannel: public LogChannel { static const char* name() { return "() +#define cworkin dev::LogOutputStream() +#define cworkout dev::LogOutputStream() + +class ClientBase: public dev::eth::Interface +{ +public: + ClientBase() {} + virtual ~ClientBase() {} + + /// Submits the given message-call transaction. + virtual void submitTransaction(Secret _secret, u256 _value, Address _dest, bytes const& _data = bytes(), u256 _gas = 10000, u256 _gasPrice = 10 * szabo) override; + + /// Submits a new contract-creation transaction. + /// @returns the new contract's address (assuming it all goes through). + virtual Address submitTransaction(Secret _secret, u256 _endowment, bytes const& _init, u256 _gas = 10000, u256 _gasPrice = 10 * szabo) override; + + /// Makes the given call. Nothing is recorded into the state. + virtual ExecutionResult call(Secret _secret, u256 _value, Address _dest, bytes const& _data = bytes(), u256 _gas = 10000, u256 _gasPrice = 10 * szabo, BlockNumber _blockNumber = PendingBlock) override; + + virtual ExecutionResult create(Secret _secret, u256 _value, bytes const& _data = bytes(), u256 _gas = 10000, u256 _gasPrice = 10 * szabo, BlockNumber _blockNumber = PendingBlock) override; + + using Interface::balanceAt; + using Interface::countAt; + using Interface::stateAt; + using Interface::codeAt; + using Interface::storageAt; + + virtual u256 balanceAt(Address _a, BlockNumber _block) const override; + virtual u256 countAt(Address _a, BlockNumber _block) const override; + virtual u256 stateAt(Address _a, u256 _l, BlockNumber _block) const override; + virtual bytes codeAt(Address _a, BlockNumber _block) const override; + virtual std::map storageAt(Address _a, BlockNumber _block) const override; + + virtual LocalisedLogEntries logs(unsigned _watchId) const override; + virtual LocalisedLogEntries logs(LogFilter const& _filter) const override; + + /// Install, uninstall and query watches. + virtual unsigned installWatch(LogFilter const& _filter, Reaping _r = Reaping::Automatic) override; + virtual unsigned installWatch(h256 _filterId, Reaping _r = Reaping::Automatic) override; + virtual bool uninstallWatch(unsigned _watchId) override; + virtual LocalisedLogEntries peekWatch(unsigned _watchId) const override; + virtual LocalisedLogEntries checkWatch(unsigned _watchId) override; + + // TODO: switch all the _blockHash arguments to also accept _blockNumber + virtual h256 hashFromNumber(unsigned _number) const override; + virtual eth::BlockInfo blockInfo(h256 _hash) const override; + virtual eth::BlockDetails blockDetails(h256 _hash) const override; + virtual eth::Transaction transaction(h256 _transactionHash) const override; + virtual eth::Transaction transaction(h256 _blockHash, unsigned _i) const override; + virtual eth::Transactions transactions(h256 _blockHash) const override; + virtual eth::TransactionHashes transactionHashes(h256 _blockHash) const override; + virtual eth::BlockInfo uncle(h256 _blockHash, unsigned _i) const override; + virtual eth::UncleHashes uncleHashes(h256 _blockHash) const override; + virtual unsigned transactionCount(h256 _blockHash) const override; + virtual unsigned uncleCount(h256 _blockHash) const override; + virtual unsigned number() const override; + virtual eth::Transactions pending() const override; + + using Interface::diff; + virtual StateDiff diff(unsigned _txi, h256 _block) const override; + virtual StateDiff diff(unsigned _txi, BlockNumber _block) const override; + + using Interface::addresses; + virtual Addresses addresses(BlockNumber _block) const override; + virtual u256 gasLimitRemaining() const override; + + /// Set the coinbase address + virtual void setAddress(Address _us) = 0; + + /// Get the coinbase address + virtual Address address() const override; + + /// TODO: consider moving it to a separate interface + + virtual void setMiningThreads(unsigned _threads) override { (void)_threads; BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::ClientBase::setMiningThreads")); } + virtual unsigned miningThreads() const override { BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::ClientBase::miningThreads")); } + virtual void startMining() override { BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::ClientBase::startMining")); } + virtual void stopMining() override { BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::ClientBase::stopMining")); } + virtual bool isMining() override { BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::ClientBase::isMining")); } + virtual eth::MineProgress miningProgress() const override { BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::ClientBase::miningProgress")); } + virtual std::pair getWork() override { BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::ClientBase::getWork")); } + virtual bool submitWork(eth::ProofOfWork::Proof const&) override { BOOST_THROW_EXCEPTION(InterfaceNotSupported("dev::eth::ClientBase::submitWork")); } + + State asOf(BlockNumber _h) const; + +protected: + /// The interface that must be implemented in any class deriving this. + /// { + virtual BlockChain const& bc() const = 0; + virtual State asOf(h256 const& _h) const = 0; + virtual State preMine() const = 0; + virtual State postMine() const = 0; + virtual void prepareForTransaction() = 0; + /// } + + TransactionQueue m_tq; ///< Maintains a list of incoming transactions not yet in a block on the blockchain. + + // filters + mutable Mutex x_filtersWatches; ///< Our lock. + std::map m_filters; ///< The dictionary of filters that are active. + std::map m_watches; ///< Each and every watch - these reference a filter. +}; + +}} diff --git a/libethereum/EthereumHost.cpp b/libethereum/EthereumHost.cpp index 7dfc51b47..6a69a6d55 100644 --- a/libethereum/EthereumHost.cpp +++ b/libethereum/EthereumHost.cpp @@ -159,9 +159,22 @@ void EthereumHost::doWork() // If we've finished our initial sync (including getting all the blocks into the chain so as to reduce invalid transactions), start trading transactions & blocks if (!isSyncing() && m_chain.isKnown(m_latestBlockSent)) { - maintainTransactions(); - maintainBlocks(h); + if (m_newTransactions) + { + m_newTransactions = false; + maintainTransactions(); + } + if (m_newBlocks) + { + m_newBlocks = false; + maintainBlocks(h); + } } + + for (auto p: peerSessions()) + if (shared_ptr const& ep = p.first->cap()) + ep->tick(); + // return netChange; // TODO: Figure out what to do with netChange. (void)netChange; @@ -170,18 +183,27 @@ void EthereumHost::doWork() void EthereumHost::maintainTransactions() { // Send any new transactions. + map, h256s> peerTransactions; + auto ts = m_tq.transactions(); + for (auto const& i: ts) + { + bool unsent = !m_transactionsSent.count(i.first); + for (auto const& p: randomSelection(25, [&](EthereumPeer* p) { return p->m_requireTransactions || (unsent && !p->m_knownTransactions.count(i.first)); })) + peerTransactions[p].push_back(i.first); + } for (auto p: peerSessions()) - if (auto ep = p.first->cap().get()) + if (auto ep = p.first->cap()) { bytes b; unsigned n = 0; - for (auto const& i: m_tq.transactions()) - if (ep->m_requireTransactions || (!m_transactionsSent.count(i.first) && !ep->m_knownTransactions.count(i.first))) - { - b += i.second; - ++n; - m_transactionsSent.insert(i.first); - } + for (auto const& h: peerTransactions[ep]) + { + b += ts[h].rlp(); + ++n; + } + for (auto const& t: ts) + m_transactionsSent.insert(t.first); + ep->clearKnownTransactions(); if (n || ep->m_requireTransactions) @@ -194,6 +216,27 @@ void EthereumHost::maintainTransactions() } } +std::vector> EthereumHost::randomSelection(unsigned _percent, std::function const& _allow) +{ + std::vector> candidates; + candidates.reserve(peerSessions().size()); + for (auto const& j: peerSessions()) + { + auto pp = j.first->cap(); + if (_allow(pp.get())) + candidates.push_back(pp); + } + + std::vector> ret; + for (unsigned i = (peerSessions().size() * _percent + 99) / 100; i-- && candidates.size();) + { + unsigned n = rand() % candidates.size(); + ret.push_back(std::move(candidates[n])); + candidates.erase(candidates.begin() + n); + } + return ret; +} + void EthereumHost::maintainBlocks(h256 _currentHash) { // Send any new blocks. @@ -201,16 +244,13 @@ void EthereumHost::maintainBlocks(h256 _currentHash) { clog(NetMessageSummary) << "Sending a new block (current is" << _currentHash << ", was" << m_latestBlockSent << ")"; - for (auto j: peerSessions()) + for (auto const& p: randomSelection(25, [&](EthereumPeer* p){return !p->m_knownBlocks.count(_currentHash); })) { - auto p = j.first->cap().get(); - RLPStream ts; p->prep(ts, NewBlockPacket, 2).appendRaw(m_chain.block(), 1).append(m_chain.details().totalDifficulty); Guard l(p->x_knownBlocks); - if (!p->m_knownBlocks.count(_currentHash)) - p->sealAndSend(ts); + p->sealAndSend(ts); p->m_knownBlocks.clear(); } m_latestBlockSent = _currentHash; diff --git a/libethereum/EthereumHost.h b/libethereum/EthereumHost.h index 06575092c..c2fffcd82 100644 --- a/libethereum/EthereumHost.h +++ b/libethereum/EthereumHost.h @@ -76,7 +76,12 @@ public: bool isBanned(p2p::NodeId _id) const { return !!m_banned.count(_id); } + void noteNewTransactions() { m_newTransactions = true; } + void noteNewBlocks() { m_newBlocks = true; } + private: + std::vector> randomSelection(unsigned _percent = 25, std::function const& _allow = [](EthereumPeer const*){ return true; }); + /// Session is tell us that we may need (re-)syncing with the peer. void noteNeedsSyncing(EthereumPeer* _who); @@ -119,6 +124,9 @@ private: h256Set m_transactionsSent; std::set m_banned; + + bool m_newTransactions = false; + bool m_newBlocks = false; }; } diff --git a/libethereum/EthereumPeer.cpp b/libethereum/EthereumPeer.cpp index 95e1aadda..3bd7ecab3 100644 --- a/libethereum/EthereumPeer.cpp +++ b/libethereum/EthereumPeer.cpp @@ -48,6 +48,7 @@ EthereumPeer::EthereumPeer(Session* _s, HostCapabilityFace* _h, unsigned _i): EthereumPeer::~EthereumPeer() { + clogS(NetMessageSummary) << "Aborting Sync :-("; abortSync(); } @@ -219,6 +220,8 @@ void EthereumPeer::setAsking(Asking _a, bool _isSyncing) m_syncingNeededBlocks.clear(); } + m_lastAsk = chrono::system_clock::now(); + session()->addNote("ask", _a == Asking::Nothing ? "nothing" : _a == Asking::State ? "state" : _a == Asking::Hashes ? "hashes" : _a == Asking::Blocks ? "blocks" : "?"); session()->addNote("sync", string(isSyncing() ? "ongoing" : "holding") + (needsSyncing() ? " & needed" : "")); } @@ -234,6 +237,13 @@ void EthereumPeer::setNeedsSyncing(h256 _latestHash, u256 _td) session()->addNote("sync", string(isSyncing() ? "ongoing" : "holding") + (needsSyncing() ? " & needed" : "")); } +void EthereumPeer::tick() +{ + if (chrono::system_clock::now() - m_lastAsk > chrono::seconds(10) && m_asking != Asking::Nothing) + // timeout + session()->disconnect(PingTimeout); +} + bool EthereumPeer::isSyncing() const { return host()->m_syncer == this; @@ -297,13 +307,13 @@ bool EthereumPeer::interpret(unsigned _id, RLP const& _r) { case StatusPacket: { - m_protocolVersion = _r[1].toInt(); - m_networkId = _r[2].toInt(); + m_protocolVersion = _r[0].toInt(); + m_networkId = _r[1].toInt(); // a bit dirty as we're misusing these to communicate the values to transition, but harmless. - m_totalDifficulty = _r[3].toInt(); - m_latestHash = _r[4].toHash(); - auto genesisHash = _r[5].toHash(); + m_totalDifficulty = _r[2].toInt(); + m_latestHash = _r[3].toHash(); + auto genesisHash = _r[4].toHash(); clogS(NetMessageSummary) << "Status:" << m_protocolVersion << "/" << m_networkId << "/" << genesisHash.abridged() << ", TD:" << m_totalDifficulty << "=" << m_latestHash.abridged(); @@ -324,23 +334,35 @@ bool EthereumPeer::interpret(unsigned _id, RLP const& _r) case GetTransactionsPacket: break; // DEPRECATED. case TransactionsPacket: { - clogS(NetMessageSummary) << "Transactions (" << dec << (_r.itemCount() - 1) << "entries)"; - addRating(_r.itemCount() - 1); + clogS(NetMessageSummary) << "Transactions (" << dec << _r.itemCount() << "entries)"; Guard l(x_knownTransactions); - for (unsigned i = 1; i < _r.itemCount(); ++i) + 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; } case GetBlockHashesPacket: { - h256 later = _r[1].toHash(); - unsigned limit = _r[2].toInt(); + h256 later = _r[0].toHash(); + unsigned limit = _r[1].toInt(); clogS(NetMessageSummary) << "GetBlockHashes (" << limit << "entries," << later.abridged() << ")"; unsigned c = min(host()->m_chain.number(later), limit); @@ -351,24 +373,26 @@ 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: { - clogS(NetMessageSummary) << "BlockHashes (" << dec << (_r.itemCount() - 1) << "entries)" << (_r.itemCount() - 1 ? "" : ": NoMoreHashes"); + clogS(NetMessageSummary) << "BlockHashes (" << dec << _r.itemCount() << "entries)" << (_r.itemCount() ? "" : ": NoMoreHashes"); if (m_asking != Asking::Hashes) { cwarn << "Peer giving us hashes when we didn't ask for them."; break; } - if (_r.itemCount() == 1) + if (_r.itemCount() == 0) { transition(Asking::Blocks); return true; } - for (unsigned i = 1; i < _r.itemCount(); ++i) + for (unsigned i = 0; i < _r.itemCount(); ++i) { + addRating(1); auto h = _r[i].toHash(); if (host()->m_chain.isKnown(h)) { @@ -384,11 +408,11 @@ bool EthereumPeer::interpret(unsigned _id, RLP const& _r) } case GetBlocksPacket: { - clogS(NetMessageSummary) << "GetBlocks (" << dec << (_r.itemCount() - 1) << "entries)"; + clogS(NetMessageSummary) << "GetBlocks (" << dec << _r.itemCount() << "entries)"; // return the requested blocks. bytes rlp; unsigned n = 0; - for (unsigned i = 1; i < _r.itemCount() && i <= c_maxBlocks; ++i) + for (unsigned i = 0; i < _r.itemCount() && i <= c_maxBlocks; ++i) { auto b = host()->m_chain.block(_r[i].toHash()); if (b.size()) @@ -397,6 +421,7 @@ bool EthereumPeer::interpret(unsigned _id, RLP const& _r) ++n; } } + addRating(0); RLPStream s; prep(s, BlocksPacket, n).appendRaw(rlp, n); sealAndSend(s); @@ -404,12 +429,12 @@ bool EthereumPeer::interpret(unsigned _id, RLP const& _r) } case BlocksPacket: { - clogS(NetMessageSummary) << "Blocks (" << dec << (_r.itemCount() - 1) << "entries)" << (_r.itemCount() - 1 ? "" : ": NoMoreBlocks"); + clogS(NetMessageSummary) << "Blocks (" << dec << _r.itemCount() << "entries)" << (_r.itemCount() ? "" : ": NoMoreBlocks"); if (m_asking != Asking::Blocks) clogS(NetWarn) << "Unexpected Blocks received!"; - if (_r.itemCount() == 1) + if (_r.itemCount() == 0) { // Got to this peer's latest block - just give up. transition(Asking::Nothing); @@ -422,7 +447,7 @@ bool EthereumPeer::interpret(unsigned _id, RLP const& _r) unsigned got = 0; unsigned repeated = 0; - for (unsigned i = 1; i < _r.itemCount(); ++i) + for (unsigned i = 0; i < _r.itemCount(); ++i) { auto h = BlockInfo::headerHash(_r[i].data()); if (m_sub.noteBlock(h)) @@ -435,6 +460,7 @@ bool EthereumPeer::interpret(unsigned _id, RLP const& _r) break; case ImportResult::Malformed: + case ImportResult::BadChain: disable("Malformed block received."); return true; @@ -462,19 +488,24 @@ 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: { - auto h = BlockInfo::headerHash(_r[1].data()); + auto h = BlockInfo::headerHash(_r[0].data()); clogS(NetMessageSummary) << "NewBlock: " << h.abridged(); - if (_r.itemCount() != 3) + if (_r.itemCount() != 2) disable("NewBlock without 2 data fields."); else { - switch (host()->m_bq.import(_r[1].data(), host()->m_chain)) + switch (host()->m_bq.import(_r[0].data(), host()->m_chain)) { case ImportResult::Success: addRating(100); @@ -484,8 +515,9 @@ bool EthereumPeer::interpret(unsigned _id, RLP const& _r) break; case ImportResult::Malformed: + case ImportResult::BadChain: disable("Malformed block received."); - break; + return true; case ImportResult::AlreadyInChain: case ImportResult::AlreadyKnown: @@ -493,9 +525,10 @@ bool EthereumPeer::interpret(unsigned _id, RLP const& _r) case ImportResult::UnknownParent: clogS(NetMessageSummary) << "Received block with no known parent. Resyncing..."; - setNeedsSyncing(h, _r[2].toInt()); + setNeedsSyncing(h, _r[1].toInt()); break; } + Guard l(x_knownBlocks); m_knownBlocks.insert(h); } @@ -505,6 +538,10 @@ bool EthereumPeer::interpret(unsigned _id, RLP const& _r) return false; } } + catch (Exception const& _e) + { + clogS(NetWarn) << "Peer causing an Exception:" << _e.what() << _r; + } catch (std::exception const& _e) { clogS(NetWarn) << "Peer causing an exception:" << _e.what() << _r; diff --git a/libethereum/EthereumPeer.h b/libethereum/EthereumPeer.h index c95df6945..da144134b 100644 --- a/libethereum/EthereumPeer.h +++ b/libethereum/EthereumPeer.h @@ -105,6 +105,9 @@ private: /// Check whether the session should bother grabbing the peer's blocks. bool shouldGrabBlocks() const; + /// Runs period checks to check up on the peer. + void tick(); + /// Peer's protocol version. unsigned m_protocolVersion; /// Peer's network id. @@ -112,6 +115,8 @@ private: /// What, if anything, we last asked the other peer for. Asking m_asking = Asking::Nothing; + /// When we asked for it. Allows a time out. + std::chrono::system_clock::time_point m_lastAsk; /// Whether this peer is in the process of syncing or not. Only one peer can be syncing at once. bool m_isSyncing = false; diff --git a/libethereum/Executive.cpp b/libethereum/Executive.cpp index e009b49fb..0b75a07af 100644 --- a/libethereum/Executive.cpp +++ b/libethereum/Executive.cpp @@ -35,7 +35,7 @@ using namespace dev::eth; Executive::Executive(State& _s, BlockChain const& _bc, unsigned _level): m_s(_s), - m_lastHashes(_s.getLastHashes(_bc, (unsigned)_s.info().number - 1)), + m_lastHashes(_bc.lastHashes((unsigned)_s.info().number - 1)), m_depth(_level) {} @@ -44,75 +44,90 @@ u256 Executive::gasUsed() const return m_t.gas() - m_endGas; } -void Executive::accrueSubState(SubState& _parentContext) +ExecutionResult Executive::executionResult() const { - if (m_ext) - _parentContext += m_ext->sub; + return ExecutionResult(gasUsed(), m_excepted, m_newAddress, m_out, m_codeDeposit, m_ext ? m_ext->sub.refunds : 0, m_depositSize, m_gasForDeposit); } -bool Executive::setup(bytesConstRef _rlp) +void Executive::accrueSubState(SubState& _parentContext) { - // Entry point for a user-executed transaction. - m_t = Transaction(_rlp, CheckSignature::Sender); - return setup(); + if (m_ext) + _parentContext += m_ext->sub; } -bool Executive::setup() +void Executive::initialize(Transaction const& _transaction) { - // Entry point for a user-executed transaction. + m_t = _transaction; - // Avoid invalid transactions. - auto nonceReq = m_s.transactionsFrom(m_t.sender()); - if (m_t.nonce() != nonceReq) + // Avoid transactions that would take us beyond the block gas limit. + u256 startGasUsed = m_s.gasUsed(); + if (startGasUsed + (bigint)m_t.gas() > m_s.m_currentBlock.gasLimit) { - clog(StateDetail) << "Invalid Nonce: Require" << nonceReq << " Got" << m_t.nonce(); - BOOST_THROW_EXCEPTION(InvalidNonce() << RequirementError((bigint)nonceReq, (bigint)m_t.nonce())); + clog(StateDetail) << "Too much gas used in this block: Require <" << (m_s.m_currentBlock.gasLimit - startGasUsed) << " Got" << m_t.gas(); + m_excepted = TransactionException::BlockGasLimitReached; + BOOST_THROW_EXCEPTION(BlockGasLimitReached() << RequirementError((bigint)(m_s.m_currentBlock.gasLimit - startGasUsed), (bigint)m_t.gas())); } // Check gas cost is enough. - auto gasCost = Interface::txGas(m_t.data()); - - if (m_t.gas() < gasCost) + m_gasRequired = Interface::txGas(m_t.data()); + if (m_t.gas() < m_gasRequired) { - clog(StateDetail) << "Not enough gas to pay for the transaction: Require >" << gasCost << " Got" << m_t.gas(); - BOOST_THROW_EXCEPTION(OutOfGas() << RequirementError((bigint)gasCost, (bigint)m_t.gas())); + clog(StateDetail) << "Not enough gas to pay for the transaction: Require >" << m_gasRequired << " Got" << m_t.gas(); + m_excepted = TransactionException::OutOfGas; + BOOST_THROW_EXCEPTION(OutOfGas() << RequirementError((bigint)m_gasRequired, (bigint)m_t.gas())); } - bigint cost = m_t.value() + (bigint)m_t.gas() * m_t.gasPrice(); - - // Avoid unaffordable transactions. - if (m_s.balance(m_t.sender()) < cost) + // Avoid invalid transactions. + u256 nonceReq; + try { - clog(StateDetail) << "Not enough cash: Require >" << cost << " Got" << m_s.balance(m_t.sender()); - BOOST_THROW_EXCEPTION(NotEnoughCash() << RequirementError(cost, (bigint)m_s.balance(m_t.sender()))); + nonceReq = m_s.transactionsFrom(m_t.sender()); + } + catch (...) + { + clog(StateDetail) << "Invalid Signature"; + m_excepted = TransactionException::InvalidSignature; + throw; + } + if (m_t.nonce() != nonceReq) + { + clog(StateDetail) << "Invalid Nonce: Require" << nonceReq << " Got" << m_t.nonce(); + m_excepted = TransactionException::InvalidNonce; + BOOST_THROW_EXCEPTION(InvalidNonce() << RequirementError((bigint)nonceReq, (bigint)m_t.nonce())); } - u256 startGasUsed = m_s.gasUsed(); - if (startGasUsed + (bigint)m_t.gas() > m_s.m_currentBlock.gasLimit) + // Avoid unaffordable transactions. + m_gasCost = (bigint)m_t.gas() * m_t.gasPrice(); + m_totalCost = m_t.value() + m_gasCost; + if (m_s.balance(m_t.sender()) < m_totalCost) { - clog(StateDetail) << "Too much gas used in this block: Require <" << (m_s.m_currentBlock.gasLimit - startGasUsed) << " Got" << m_t.gas(); - BOOST_THROW_EXCEPTION(BlockGasLimitReached() << RequirementError((bigint)(m_s.m_currentBlock.gasLimit - startGasUsed), (bigint)m_t.gas())); + clog(StateDetail) << "Not enough cash: Require >" << m_totalCost << " Got" << m_s.balance(m_t.sender()); + m_excepted = TransactionException::NotEnoughCash; + BOOST_THROW_EXCEPTION(NotEnoughCash() << RequirementError(m_totalCost, (bigint)m_s.balance(m_t.sender()))); } +} + +bool Executive::execute() +{ + // Entry point for a user-executed transaction. // Increment associated nonce for sender. m_s.noteSending(m_t.sender()); // Pay... - clog(StateDetail) << "Paying" << formatBalance(u256(cost)) << "from sender (includes" << m_t.gas() << "gas at" << formatBalance(m_t.gasPrice()) << ")"; - m_s.subBalance(m_t.sender(), cost); + clog(StateDetail) << "Paying" << formatBalance(u256(m_gasCost)) << "from sender for gas (" << m_t.gas() << "gas at" << formatBalance(m_t.gasPrice()) << ")"; + m_s.subBalance(m_t.sender(), m_gasCost); if (m_t.isCreation()) - return create(m_t.sender(), m_t.value(), m_t.gasPrice(), m_t.gas() - (u256)gasCost, &m_t.data(), m_t.sender()); + return create(m_t.sender(), m_t.value(), m_t.gasPrice(), m_t.gas() - (u256)m_gasRequired, &m_t.data(), m_t.sender()); else - return call(m_t.receiveAddress(), m_t.receiveAddress(), m_t.sender(), m_t.value(), m_t.gasPrice(), bytesConstRef(&m_t.data()), m_t.gas() - (u256)gasCost, m_t.sender()); + return call(m_t.receiveAddress(), m_t.receiveAddress(), m_t.sender(), m_t.value(), m_t.gasPrice(), bytesConstRef(&m_t.data()), m_t.gas() - (u256)m_gasRequired, m_t.sender()); } bool Executive::call(Address _receiveAddress, Address _codeAddress, Address _senderAddress, u256 _value, u256 _gasPrice, bytesConstRef _data, u256 _gas, Address _originAddress) { m_isCreation = false; // cnote << "Transferring" << formatBalance(_value) << "to receiver."; - m_s.addBalance(_receiveAddress, _value); - auto it = !(_codeAddress & ~h160(0xffffffff)) ? precompiled().find((unsigned)(u160)_codeAddress) : precompiled().end(); if (it != precompiled().end()) { @@ -120,7 +135,9 @@ bool Executive::call(Address _receiveAddress, Address _codeAddress, Address _sen if (_gas < g) { m_endGas = 0; - m_excepted = true; + m_excepted = TransactionException::OutOfGasBase; + // Bail from exception. + return true; // true actually means "all finished - nothing more to be done regarding go(). } else { @@ -137,6 +154,9 @@ bool Executive::call(Address _receiveAddress, Address _codeAddress, Address _sen } else m_endGas = _gas; + + m_s.transferBalance(_senderAddress, _receiveAddress, _value); + return !m_ext; } @@ -148,20 +168,22 @@ bool Executive::create(Address _sender, u256 _endowment, u256 _gasPrice, u256 _g // we delete it explicitly if we decide we need to revert. m_newAddress = right160(sha3(rlpList(_sender, m_s.transactionsFrom(_sender) - 1))); - // Set up new account... - m_s.m_cache[m_newAddress] = Account(m_s.balance(m_newAddress) + _endowment, Account::ContractConception); - // Execute _init. + if (!_init.empty()) + { + m_vm = VMFactory::create(_gas); + m_ext = make_shared(m_s, m_lastHashes, m_newAddress, _sender, _origin, _endowment, _gasPrice, bytesConstRef(), _init, m_depth); + } + + m_s.m_cache[m_newAddress] = Account(m_s.balance(m_newAddress), Account::ContractConception); + m_s.transferBalance(_sender, m_newAddress, _endowment); + if (_init.empty()) { m_s.m_cache[m_newAddress].setCode({}); m_endGas = _gas; } - else - { - m_vm = VMFactory::create(_gas); - m_ext = make_shared(m_s, m_lastHashes, m_newAddress, _sender, _origin, _endowment, _gasPrice, bytesConstRef(), _init, m_depth); - } + return !m_ext; } @@ -199,10 +221,19 @@ bool Executive::go(OnOpFunc const& _onOp) if (m_isCreation) { + m_gasForDeposit = m_endGas; + m_depositSize = m_out.size(); if (m_out.size() * c_createDataGas <= m_endGas) + { + m_codeDeposit = CodeDeposit::Success; m_endGas -= m_out.size() * c_createDataGas; + } else + { + + m_codeDeposit = CodeDeposit::Failed; m_out.reset(); + } m_s.m_cache[m_newAddress].setCode(m_out.toBytes()); } } @@ -214,7 +245,7 @@ bool Executive::go(OnOpFunc const& _onOp) { clog(StateSafeExceptions) << "Safe VM Exception. " << diagnostic_information(_e); m_endGas = 0; - m_excepted = true; + m_excepted = toTransactionException(_e); m_ext->revert(); } catch (Exception const& _e) diff --git a/libethereum/Executive.h b/libethereum/Executive.h index 2e89f0623..3efdf6f0f 100644 --- a/libethereum/Executive.h +++ b/libethereum/Executive.h @@ -41,35 +41,46 @@ struct VMTraceChannel: public LogChannel { static const char* name() { return "E * @brief Message-call/contract-creation executor; useful for executing transactions. * * Two ways of using this class - either as a transaction executive or a CALL/CREATE executive. - * In the first use, after construction, begin with setup() and end with finalize(). Call go() - * after setup() only if it returns false. + * + * In the first use, after construction, begin with initialize(), then execute() and end with finalize(). Call go() + * after execute() only if it returns false. + * * In the second use, after construction, begin with call() or create() and end with * accrueSubState(). Call go() after call()/create() only if it returns false. + * + * Example: + * @code + * Executive e(state, blockchain, 0); + * e.initialize(transaction); + * if (!e.execute()) + * e.go(); + * e.finalize(); + * @endcode */ 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; Executive(Executive const&) = delete; void operator=(Executive) = delete; - /// Set up the executive for evaluating a transaction. You must call finalize() following this. - /// @returns true iff go() must be called (and thus a VM execution in required). - bool setup(bytesConstRef _transaction); - /// Set up the executive for evaluating a transaction. You must call finalize() following this. - /// @returns true iff go() must be called (and thus a VM execution in required). - bool setup(Transaction const& _transaction) { m_t = _transaction; return setup(); } - /// Finalise a transaction previously set up with setup(). - /// @warning Only valid after setup(), and possibly go(). + /// Initializes the executive for evaluating a transaction. You must call finalize() at some point following this. + void initialize(bytesConstRef _transaction) { initialize(Transaction(_transaction, CheckSignature::None)); } + void initialize(Transaction const& _transaction); + /// Finalise a transaction previously set up with initialize(). + /// @warning Only valid after initialize() and execute(), and possibly go(). void finalize(); - /// @returns the transaction from setup(). - /// @warning Only valid after setup(). + /// Begins execution of a transaction. You must call finalize() following this. + /// @returns true if the transaction is done, false if go() must be called. + bool execute(); + /// @returns the transaction from initialize(). + /// @warning Only valid after initialize(). Transaction const& t() const { return m_t; } /// @returns the log entries created by this operation. /// @warning Only valid after finalise(). @@ -101,11 +112,12 @@ public: /// @returns the new address for the created contract in the CREATE operation. h160 newAddress() const { return m_newAddress; } /// @returns true iff the operation ended with a VM exception. - bool excepted() const { return m_excepted; } + bool excepted() const { return m_excepted != TransactionException::None; } -private: - bool setup(); + /// Get the above in an amalgamated fashion. + ExecutionResult executionResult() const; +private: State& m_s; ///< The state to which this operation/transaction is applied. LastHashes m_lastHashes; std::shared_ptr m_ext; ///< The VM externality object for the VM execution or null if no VM is required. @@ -116,11 +128,18 @@ private: unsigned m_depth = 0; ///< The context's call-depth. bool m_isCreation = false; ///< True if the transaction creates a contract, or if create() is called. - bool m_excepted = false; ///< True if the VM execution resulted in an exception. + unsigned m_depositSize = 0; ///< Amount of code of the creation's attempted deposit. + u256 m_gasForDeposit; ///< Amount of gas remaining for the code deposit phase. + CodeDeposit m_codeDeposit = CodeDeposit::None; ///< True if an attempted deposit failed due to lack of gas. + TransactionException m_excepted = TransactionException::None; ///< Details if the VM's execution resulted in an exception. u256 m_endGas; ///< The final amount of gas for the transaction. Transaction m_t; ///< The original transaction. Set by setup(). LogEntries m_logs; ///< The log entries created by this transaction. Set by finalize(). + + bigint m_gasRequired; + bigint m_gasCost; + bigint m_totalCost; }; } diff --git a/libethereum/ExtVM.cpp b/libethereum/ExtVM.cpp index a8eba5a50..68d146ce1 100644 --- a/libethereum/ExtVM.cpp +++ b/libethereum/ExtVM.cpp @@ -29,7 +29,7 @@ using namespace dev::eth; bool ExtVM::call(Address _receiveAddress, u256 _txValue, bytesConstRef _txData, u256& io_gas, bytesRef _out, OnOpFunc const& _onOp, Address _myAddressOverride, Address _codeAddressOverride) { Executive e(m_s, lastHashes, depth + 1); - if (!e.call(_receiveAddress, _codeAddressOverride ? _codeAddressOverride : _receiveAddress, _myAddressOverride ? _myAddressOverride : myAddress, _txValue, gasPrice, _txData, io_gas, origin)) + if (!e.call(_receiveAddress, _codeAddressOverride, _myAddressOverride ? _myAddressOverride : myAddress, _txValue, gasPrice, _txData, io_gas, origin)) { e.go(_onOp); e.accrueSubState(sub); diff --git a/libethereum/ExtVM.h b/libethereum/ExtVM.h index d63cd943a..8807bcd58 100644 --- a/libethereum/ExtVM.h +++ b/libethereum/ExtVM.h @@ -82,7 +82,11 @@ public: /// Revert any changes made (by any of the other calls). /// @TODO check call site for the parent manifest being discarded. - virtual void revert() override final { m_s.m_cache = m_origCache; sub.clear(); } + virtual void revert() override final + { + m_s.m_cache = m_origCache; + sub.clear(); + } State& state() const { return m_s; } diff --git a/libethereum/Interface.h b/libethereum/Interface.h index 817a5e4b0..529ddc093 100644 --- a/libethereum/Interface.h +++ b/libethereum/Interface.h @@ -37,6 +37,15 @@ namespace dev namespace eth { +using TransactionHashes = h256s; +using UncleHashes = h256s; + +enum class Reaping +{ + Automatic, + Manual +}; + /** * @brief Main API hub for interfacing with Ethereum. */ @@ -52,25 +61,28 @@ public: // [TRANSACTION API] /// Submits the given message-call transaction. - virtual void transact(Secret _secret, u256 _value, Address _dest, bytes const& _data = bytes(), u256 _gas = 10000, u256 _gasPrice = 10 * szabo) = 0; + virtual void submitTransaction(Secret _secret, u256 _value, Address _dest, bytes const& _data = bytes(), u256 _gas = 10000, u256 _gasPrice = 10 * szabo) = 0; /// Submits a new contract-creation transaction. /// @returns the new contract's address (assuming it all goes through). - virtual Address transact(Secret _secret, u256 _endowment, bytes const& _init, u256 _gas = 10000, u256 _gasPrice = 10 * szabo) = 0; - - /// Injects the RLP-encoded transaction given by the _rlp into the transaction queue directly. - virtual void inject(bytesConstRef _rlp) = 0; + virtual Address submitTransaction(Secret _secret, u256 _endowment, bytes const& _init, u256 _gas = 10000, u256 _gasPrice = 10 * szabo) = 0; /// Blocks until all pending transactions have been processed. virtual void flushTransactions() = 0; /// Makes the given call. Nothing is recorded into the state. - virtual bytes call(Secret _secret, u256 _value, Address _dest, bytes const& _data = bytes(), u256 _gas = 10000, u256 _gasPrice = 10 * szabo) = 0; + virtual ExecutionResult call(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice, BlockNumber _blockNumber) = 0; + ExecutionResult call(Secret _secret, u256 _value, Address _dest, bytes const& _data = bytes(), u256 _gas = 10000, u256 _gasPrice = 10 * szabo) { return call(_secret, _value, _dest, _data, _gas, _gasPrice, m_default); } + + /// Does the given creation. Nothing is recorded into the state. + /// @returns the pair of the Address of the created contract together with its code. + virtual ExecutionResult create(Secret _secret, u256 _value, bytes const& _data, u256 _gas, u256 _gasPrice, BlockNumber _blockNumber) = 0; + ExecutionResult create(Secret _secret, u256 _value, bytes const& _data = bytes(), u256 _gas = 10000, u256 _gasPrice = 10 * szabo) { return create(_secret, _value, _data, _gas, _gasPrice, m_default); } // [STATE-QUERY API] int getDefault() const { return m_default; } - void setDefault(int _block) { m_default = _block; } + void setDefault(BlockNumber _block) { m_default = _block; } u256 balanceAt(Address _a) const { return balanceAt(_a, m_default); } u256 countAt(Address _a) const { return countAt(_a, m_default); } @@ -78,11 +90,11 @@ public: bytes codeAt(Address _a) const { return codeAt(_a, m_default); } std::map storageAt(Address _a) const { return storageAt(_a, m_default); } - virtual u256 balanceAt(Address _a, int _block) const = 0; - virtual u256 countAt(Address _a, int _block) const = 0; - virtual u256 stateAt(Address _a, u256 _l, int _block) const = 0; - virtual bytes codeAt(Address _a, int _block) const = 0; - virtual std::map storageAt(Address _a, int _block) const = 0; + virtual u256 balanceAt(Address _a, BlockNumber _block) const = 0; + virtual u256 countAt(Address _a, BlockNumber _block) const = 0; + virtual u256 stateAt(Address _a, u256 _l, BlockNumber _block) const = 0; + virtual bytes codeAt(Address _a, BlockNumber _block) const = 0; + virtual std::map storageAt(Address _a, BlockNumber _block) const = 0; // [LOGS API] @@ -90,9 +102,11 @@ public: virtual LocalisedLogEntries logs(LogFilter const& _filter) const = 0; /// Install, uninstall and query watches. - virtual unsigned installWatch(LogFilter const& _filter) = 0; - virtual unsigned installWatch(h256 _filterId) = 0; - virtual void uninstallWatch(unsigned _watchId) = 0; + virtual unsigned installWatch(LogFilter const& _filter, Reaping _r = Reaping::Automatic) = 0; + virtual unsigned installWatch(h256 _filterId, Reaping _r = Reaping::Automatic) = 0; + virtual bool uninstallWatch(unsigned _watchId) = 0; + LocalisedLogEntries peekWatchSafe(unsigned _watchId) const { try { return peekWatch(_watchId); } catch (...) { return LocalisedLogEntries(); } } + LocalisedLogEntries checkWatchSafe(unsigned _watchId) { try { return checkWatch(_watchId); } catch (...) { return LocalisedLogEntries(); } } virtual LocalisedLogEntries peekWatch(unsigned _watchId) const = 0; virtual LocalisedLogEntries checkWatch(unsigned _watchId) = 0; @@ -101,10 +115,14 @@ public: virtual h256 hashFromNumber(unsigned _number) const = 0; virtual BlockInfo blockInfo(h256 _hash) const = 0; virtual BlockDetails blockDetails(h256 _hash) const = 0; + virtual Transaction transaction(h256 _transactionHash) const = 0; virtual Transaction transaction(h256 _blockHash, unsigned _i) const = 0; virtual BlockInfo uncle(h256 _blockHash, unsigned _i) const = 0; + virtual UncleHashes uncleHashes(h256 _blockHash) const = 0; virtual unsigned transactionCount(h256 _blockHash) const = 0; virtual unsigned uncleCount(h256 _blockHash) const = 0; + virtual Transactions transactions(h256 _blockHash) const = 0; + virtual TransactionHashes transactionHashes(h256 _blockHash) const = 0; // [EXTRA API]: @@ -118,11 +136,12 @@ public: /// Differences between transactions. StateDiff diff(unsigned _txi) const { return diff(_txi, m_default); } virtual StateDiff diff(unsigned _txi, h256 _block) const = 0; - virtual StateDiff diff(unsigned _txi, int _block) const = 0; + virtual StateDiff diff(unsigned _txi, BlockNumber _block) const = 0; /// Get a list of all active addresses. + /// NOTE: This only works when compiled with ETH_FATDB; otherwise will throw InterfaceNotSupported. virtual Addresses addresses() const { return addresses(m_default); } - virtual Addresses addresses(int _block) const = 0; + virtual Addresses addresses(BlockNumber _block) const = 0; /// Get the fee associated for a transaction with the given data. template static bigint txGas(T const& _data, u256 _gas = 0) { bigint ret = c_txGas + _gas; for (auto i: _data) ret += i ? c_txDataNonZeroGas : c_txDataZeroGas; return ret; } @@ -160,7 +179,7 @@ public: virtual MineProgress miningProgress() const = 0; protected: - int m_default = -1; + int m_default = PendingBlock; }; class Watch; diff --git a/libethereum/LogFilter.cpp b/libethereum/LogFilter.cpp index 1784094b0..ab9848d59 100644 --- a/libethereum/LogFilter.cpp +++ b/libethereum/LogFilter.cpp @@ -30,13 +30,13 @@ using namespace dev::eth; std::ostream& dev::eth::operator<<(std::ostream& _out, LogFilter const& _s) { // TODO - _out << "(@" << _s.m_addresses << "#" << _s.m_topics << ">" << _s.m_earliest << "-" << _s.m_latest << "< +" << _s.m_skip << "^" << _s.m_max << ")"; + _out << "(@" << _s.m_addresses << "#" << _s.m_topics << ">" << _s.m_earliest << "-" << _s.m_latest << "< )"; return _out; } void LogFilter::streamRLP(RLPStream& _s) const { - _s.appendList(6) << m_addresses << m_topics << m_earliest << m_latest << m_max << m_skip; + _s.appendList(4) << m_addresses << m_topics << m_earliest << m_latest; } h256 LogFilter::sha3() const @@ -46,6 +46,33 @@ h256 LogFilter::sha3() const return dev::sha3(s.out()); } +static bool isNoLater(RelativeBlock _logBlockRelation, u256 _logBlockNumber, unsigned _latest) +{ + if (_latest == PendingBlock) + return true; + else if (_latest == LatestBlock) + return _logBlockRelation == RelativeBlock::Latest; + else + return _logBlockNumber <= _latest; +} + +static bool isNoEarlier(RelativeBlock _logBlockRelation, u256 _logBlockNumber, unsigned _earliest) +{ + if (_earliest == PendingBlock) + return _logBlockRelation == RelativeBlock::Pending; + else if (_earliest == LatestBlock) + return true; + else + return _logBlockNumber >= _earliest; +} + +bool LogFilter::envelops(RelativeBlock _logBlockRelation, u256 _logBlockNumber) const +{ + return + isNoLater(_logBlockRelation, _logBlockNumber, m_latest) && + isNoEarlier(_logBlockRelation, _logBlockNumber, m_earliest); +} + bool LogFilter::matches(LogBloom _bloom) const { if (m_addresses.size()) @@ -73,6 +100,16 @@ bool LogFilter::matches(State const& _s, unsigned _i) const return matches(_s.receipt(_i)).size() > 0; } +vector LogFilter::bloomPossibilities() const +{ + // return combination of each of the addresses/topics + vector ret; + // TODO proper combinatorics. + for (auto i: m_addresses) + ret.push_back(LogBloom().shiftBloom<3>(dev::sha3(i))); + return ret; +} + LogEntries LogFilter::matches(TransactionReceipt const& _m) const { LogEntries ret; diff --git a/libethereum/LogFilter.h b/libethereum/LogFilter.h index 7b8922a03..304fab317 100644 --- a/libethereum/LogFilter.h +++ b/libethereum/LogFilter.h @@ -45,23 +45,22 @@ class State; class LogFilter { public: - LogFilter(int _earliest = 0, int _latest = -1, unsigned _max = 10, unsigned _skip = 0): m_earliest(_earliest), m_latest(_latest), m_max(_max), m_skip(_skip) {} + LogFilter(unsigned _earliest = 0, unsigned _latest = PendingBlock): m_earliest(_earliest), m_latest(_latest) {} void streamRLP(RLPStream& _s) const; h256 sha3() const; - int earliest() const { return m_earliest; } - int latest() const { return m_latest; } - unsigned max() const { return m_max; } - unsigned skip() const { return m_skip; } + unsigned earliest() const { return m_earliest; } + unsigned latest() const { return m_latest; } + + bool envelops(RelativeBlock _logBlockRelation, u256 _logBlockNumber) const; + std::vector bloomPossibilities() const; bool matches(LogBloom _bloom) const; bool matches(State const& _s, unsigned _i) const; LogEntries matches(TransactionReceipt const& _r) const; LogFilter address(Address _a) { m_addresses.insert(_a); return *this; } LogFilter topic(unsigned _index, h256 const& _t) { if (_index < 4) m_topics[_index].insert(_t); return *this; } - LogFilter withMax(unsigned _m) { m_max = _m; return *this; } - LogFilter withSkip(unsigned _m) { m_skip = _m; return *this; } LogFilter withEarliest(int _e) { m_earliest = _e; return *this; } LogFilter withLatest(int _e) { m_latest = _e; return *this; } @@ -70,10 +69,8 @@ public: private: AddressSet m_addresses; std::array m_topics; - int m_earliest = 0; - int m_latest = -1; - unsigned m_max = 10; - unsigned m_skip = 0; + unsigned m_earliest = 0; + unsigned m_latest = LatestBlock; }; } diff --git a/libethereum/Miner.cpp b/libethereum/Miner.cpp index a049fca2f..08bc426dc 100644 --- a/libethereum/Miner.cpp +++ b/libethereum/Miner.cpp @@ -31,15 +31,15 @@ using namespace dev::eth; Miner::~Miner() {} LocalMiner::LocalMiner(MinerHost* _host, unsigned _id): - Worker("miner-" + toString(_id)), - m_host(_host) + AsyncMiner(_host, _id), + Worker("miner-" + toString(_id)) { } void LocalMiner::setup(MinerHost* _host, unsigned _id) { - m_host = _host; - setName("miner-" + toString(_id)); + AsyncMiner::setup(_host, _id); + setName("miner-" + toString(m_id)); } void LocalMiner::doWork() diff --git a/libethereum/Miner.h b/libethereum/Miner.h index e472c6f64..7c4f7e767 100644 --- a/libethereum/Miner.h +++ b/libethereum/Miner.h @@ -74,6 +74,32 @@ public: virtual bytes const& blockData() const = 0; }; +class AsyncMiner: public Miner +{ +public: + /// Null constructor. + AsyncMiner(): m_host(nullptr) {} + + /// Constructor. + AsyncMiner(MinerHost* _host, unsigned _id = 0): m_host(_host), m_id(_id) {} + + /// Setup its basics. + void setup(MinerHost* _host, unsigned _id = 0) { m_host = _host; m_id = _id; } + + /// Start mining. + virtual void start() {} + + /// Stop mining. + virtual void stop() {} + + /// @returns true iff the mining has been start()ed. It may still not be actually mining, depending on the host's turbo() & force(). + virtual bool isRunning() { return false; } + +protected: + MinerHost* m_host = nullptr; ///< Our host. + unsigned m_id = 0; ///< Our unique id. +}; + /** * @brief Implements Miner. * To begin mining, use start() & stop(). noteStateChange() can be used to reset the mining and set up the @@ -86,11 +112,11 @@ public: * @threadsafe * @todo Signal Miner to restart once with condition variables. */ -class LocalMiner: public Miner, Worker +class LocalMiner: public AsyncMiner, Worker { public: /// Null constructor. - LocalMiner(): m_host(nullptr) {} + LocalMiner() {} /// Constructor. LocalMiner(MinerHost* _host, unsigned _id = 0); @@ -138,8 +164,6 @@ private: /// Do some work on the mining. virtual void doWork(); - MinerHost* m_host = nullptr; ///< Our host. - enum MiningStatus { Waiting, Preparing, Mining, Mined, Stopping, Stopped }; MiningStatus m_miningStatus = Waiting; ///< TODO: consider mutex/atomic variable. State m_mineState; ///< The state on which we are mining, generally equivalent to m_postMine. diff --git a/libethereum/State.cpp b/libethereum/State.cpp index d1e430dbe..a58ba9d0f 100644 --- a/libethereum/State.cpp +++ b/libethereum/State.cpp @@ -27,6 +27,7 @@ #include #include #include +#include #include #include #include @@ -42,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; @@ -59,29 +61,41 @@ OverlayDB State::openDB(std::string _path, bool _killExisting) ldb::DB* db = nullptr; ldb::DB::Open(o, _path + "/state", &db); if (!db) - BOOST_THROW_EXCEPTION(DatabaseAlreadyOpen()); + { + if (boost::filesystem::space(_path + "/state").available < 1024) + { + cwarn << "Not enough available space found on hard drive. Please free some up and then re-run. Bailing."; + BOOST_THROW_EXCEPTION(NotEnoughAvailableSpace()); + } + else + { + cwarn << "Database already open. You appear to have another instance of ethereum running. Bailing."; + BOOST_THROW_EXCEPTION(DatabaseAlreadyOpen()); + } + } cnote << "Opened state DB."; 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 @@ -99,22 +113,36 @@ State::State(OverlayDB const& _db, BlockChain const& _bc, h256 _h): m_state(&m_db), m_blockReward(c_blockReward) { - // TODO THINK: is this necessary? - m_state.init(); - auto b = _bc.block(_h); - BlockInfo bi; - BlockInfo bip; - if (_h) - bi.populate(b); - if (bi && bi.number) - bip.populate(_bc.block(bi.parentHash)); - if (!_h || !bip) + BlockInfo bi(b); + + if (!bi) + { + // Might be worth throwing here. + cwarn << "Invalid block given for state population: " << _h; return; - m_ourAddress = bi.coinbaseAddress; + } + + if (bi.number) + { + // Non-genesis: - sync(_bc, bi.parentHash, bip); - enact(&b, _bc); + // 1. Start at parent's end state (state root). + BlockInfo bip; + bip.populate(_bc.block(bi.parentHash)); + sync(_bc, bi.parentHash, bip); + + // 2. Enact the block's transactions onto this state. + m_ourAddress = bi.coinbaseAddress; + enact(&b, _bc); + } + else + { + // Genesis required: + // We know there are no transactions, so just populate directly. + m_state.init(); + sync(_bc, _h, bi); + } } State::State(State const& _s): @@ -294,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. } @@ -327,20 +355,53 @@ 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 { +#if ETH_FATDB map ret; for (auto i: m_cache) if (i.second.isAlive()) @@ -349,6 +410,9 @@ map State::addresses() const if (m_cache.find(i.first) == m_cache.end()) ret[i.first] = RLP(i.second)[1].toInt(); return ret; +#else + throw InterfaceNotSupported("State::addresses()"); +#endif } void State::resetCurrent() @@ -383,8 +447,7 @@ bool State::cull(TransactionQueue& _tq) const { try { - Transaction t(i.second, CheckSignature::Sender); - if (t.nonce() <= transactionsFrom(t.sender())) + if (i.second.nonce() <= transactionsFrom(i.second.sender())) { _tq.drop(i.first); ret = true; @@ -406,7 +469,7 @@ TransactionReceipts State::sync(BlockChain const& _bc, TransactionQueue& _tq, Ga TransactionReceipts ret; auto ts = _tq.transactions(); - auto lh = getLastHashes(_bc, _bc.number()); + LastHashes lh; for (int goodTxs = 1; goodTxs;) { @@ -416,12 +479,11 @@ TransactionReceipts State::sync(BlockChain const& _bc, TransactionQueue& _tq, Ga { try { - Transaction t(i.second, CheckSignature::Sender); - if (t.gasPrice() >= _gp.ask(*this)) + if (i.second.gasPrice() >= _gp.ask(*this)) { - // don't have it yet! Execute it now. - uncommitToMine(); // boost::timer t; + if (lh.empty()) + lh = _bc.lastHashes(); execute(lh, i.second); ret.push_back(m_receipts.back()); _tq.noteGood(i); @@ -429,6 +491,7 @@ TransactionReceipts State::sync(BlockChain const& _bc, TransactionQueue& _tq, Ga // cnote << "TX took:" << t.elapsed() * 1000; } } +#if ETH_DEBUG catch (InvalidNonce const& in) { bigint const* req = boost::get_error_info(in); @@ -444,13 +507,19 @@ TransactionReceipts State::sync(BlockChain const& _bc, TransactionQueue& _tq, Ga else _tq.setFuture(i); } + catch (BlockGasLimitReached const& e) + { + _tq.setFuture(i); + } +#endif catch (Exception const& _e) { // Something else went wrong - drop it. _tq.drop(i.first); if (o_transactionQueueChanged) *o_transactionQueueChanged = true; - cwarn << "Sync went wrong\n" << diagnostic_information(_e); + cnote << "Dropping invalid transaction:"; + cnote << diagnostic_information(_e); } catch (std::exception const&) { @@ -458,6 +527,7 @@ TransactionReceipts State::sync(BlockChain const& _bc, TransactionQueue& _tq, Ga _tq.drop(i.first); if (o_transactionQueueChanged) *o_transactionQueueChanged = true; + cnote << "Transaction caused low-level exception :("; } } } @@ -468,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(); @@ -493,7 +563,7 @@ u256 State::enact(bytesConstRef _block, BlockChain const& _bc, bool _checkNonce) GenericTrieDB receiptsTrie(&rm); receiptsTrie.init(); - LastHashes lh = getLastHashes(_bc, (unsigned)m_previousBlock.number); + LastHashes lh = _bc.lastHashes((unsigned)m_previousBlock.number); RLP rlp(_block); // All ok with the block generally. Play back the transactions now... @@ -504,7 +574,7 @@ u256 State::enact(bytesConstRef _block, BlockChain const& _bc, bool _checkNonce) k << i; transactionsTrie.insert(&k.out(), tr.data()); - execute(lh, tr.data()); + execute(lh, Transaction(tr.data(), CheckSignature::Sender)); RLPStream receiptrlp; m_receipts.back().streamRLP(receiptrlp); @@ -529,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(); @@ -555,7 +625,7 @@ u256 State::enact(bytesConstRef _block, BlockChain const& _bc, bool _checkNonce) BOOST_THROW_EXCEPTION(TooManyUncles()); set nonces = { m_currentBlock.nonce }; - Addresses rewarded; + vector rewarded; set knownUncles = _bc.allUnclesFrom(m_currentBlock.parentHash); for (auto const& i: rlp[2]) @@ -574,7 +644,7 @@ u256 State::enact(bytesConstRef _block, BlockChain const& _bc, bool _checkNonce) nonces.insert(uncle.nonce); tdIncrease += uncle.difficulty; - rewarded.push_back(uncle.coinbaseAddress); + rewarded.push_back(uncle); } applyRewards(rewarded); @@ -689,14 +759,14 @@ void State::commitToMine(BlockChain const& _bc) uncommitToMine(); // cnote << "Committing to mine on block" << m_previousBlock.hash.abridged(); -#ifdef ETH_PARANOIA +#if ETH_PARANOIA && 0 commit(); cnote << "Pre-reward stateRoot:" << m_state.root(); #endif m_lastTx = m_db; - Addresses uncleAddresses; + vector uncleBlockHeaders; RLPStream unclesData; unsigned unclesCount = 0; @@ -716,7 +786,7 @@ void State::commitToMine(BlockChain const& _bc) BlockInfo ubi(_bc.block(u)); ubi.streamRLP(unclesData, WithNonce); ++unclesCount; - uncleAddresses.push_back(ubi.coinbaseAddress); + uncleBlockHeaders.push_back(ubi); if (unclesCount == 2) break; } @@ -760,7 +830,7 @@ void State::commitToMine(BlockChain const& _bc) m_currentBlock.sha3Uncles = sha3(m_currentUncles); // Apply rewards last of all. - applyRewards(uncleAddresses); + applyRewards(uncleBlockHeaders); // Commit any and all changes to the trie that are in the cache, then update the state root accordingly. commit(); @@ -771,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) @@ -821,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() @@ -1035,56 +1105,34 @@ bool State::isTrieGood(bool _enforceRefs, bool _requireNoLeftOvers) const return true; } -LastHashes State::getLastHashes(BlockChain const& _bc, unsigned _n) const -{ - LastHashes ret; - ret.resize(256); - if (c_protocolVersion > 49) - { - ret[0] = _bc.numberHash(_n); - for (unsigned i = 1; i < 256; ++i) - ret[i] = ret[i - 1] ? _bc.details(ret[i - 1]).parent : h256(); - } - return ret; -} - -u256 State::execute(BlockChain const& _bc, bytes const& _rlp, bytes* o_output, bool _commit) -{ - return execute(getLastHashes(_bc, _bc.number()), &_rlp, o_output, _commit); -} - -u256 State::execute(BlockChain const& _bc, bytesConstRef _rlp, bytes* o_output, bool _commit) +ExecutionResult State::execute(LastHashes const& _lh, Transaction const& _t, Permanence _p) { - return execute(getLastHashes(_bc, _bc.number()), _rlp, o_output, _commit); -} - -// TODO: maintain node overlay revisions for stateroots -> each commit gives a stateroot + OverlayDB; allow overlay copying for rewind operations. -u256 State::execute(LastHashes const& _lh, bytesConstRef _rlp, bytes* o_output, bool _commit) -{ -#ifndef ETH_RELEASE - commit(); // get an updated hash -#endif - +#if ETH_PARANOIA paranoia("start of execution.", true); - State old(*this); -#if ETH_PARANOIA auto h = rootHash(); #endif + // Create and initialize the executive. This will throw fairly cheaply and quickly if the + // transaction is bad in any way. Executive e(*this, _lh, 0); - e.setup(_rlp); + e.initialize(_t); - u256 startGasUsed = gasUsed(); + // Uncommitting is a non-trivial operation - only do it once we've verified as much of the + // transaction as possible. + uncommitToMine(); + // OK - transaction looks valid - execute. + u256 startGasUsed = gasUsed(); #if ETH_PARANOIA ctrace << "Executing" << e.t() << "on" << h; ctrace << toHex(e.t().rlp()); #endif + if (!e.execute()) #if ETH_VMTRACE - e.go(e.simpleTrace()); + e.go(e.simpleTrace()); #else - e.go(); + e.go(); #endif e.finalize(); @@ -1093,41 +1141,38 @@ u256 State::execute(LastHashes const& _lh, bytesConstRef _rlp, bytes* o_output, ctrace << old.diff(*this); #endif - if (o_output) - *o_output = e.out().toBytes(); - - if (!_commit) - { + if (_p == Permanence::Reverted) m_cache.clear(); - return e.gasUsed(); - } - - commit(); - -#if ETH_PARANOIA && !ETH_FATDB - ctrace << "Executed; now" << rootHash(); - ctrace << old.diff(*this); - - paranoia("after execution commit.", true); - - if (e.t().receiveAddress()) + else { - EnforceRefs r(m_db, true); - if (storageRoot(e.t().receiveAddress()) && m_db.lookup(storageRoot(e.t().receiveAddress())).empty()) + commit(); + +#if ETH_PARANOIA && !ETH_FATDB + ctrace << "Executed; now" << rootHash(); + ctrace << old.diff(*this); + + paranoia("after execution commit.", true); + + if (e.t().receiveAddress()) { - cwarn << "TRIE immediately after execution; no node for receiveAddress"; - BOOST_THROW_EXCEPTION(InvalidTrie()); + EnforceRefs r(m_db, true); + if (storageRoot(e.t().receiveAddress()) && m_db.lookup(storageRoot(e.t().receiveAddress())).empty()) + { + cwarn << "TRIE immediately after execution; no node for receiveAddress"; + BOOST_THROW_EXCEPTION(InvalidTrie()); + } } - } #endif + + // TODO: CHECK TRIE after level DB flush to make sure exactly the same. + + // Add to the user-originated transactions that we've executed. + m_transactions.push_back(e.t()); + m_receipts.push_back(TransactionReceipt(rootHash(), startGasUsed + e.gasUsed(), e.logs())); + m_transactionSet.insert(e.t().sha3()); + } - // TODO: CHECK TRIE after level DB flush to make sure exactly the same. - - // Add to the user-originated transactions that we've executed. - m_transactions.push_back(e.t()); - m_receipts.push_back(TransactionReceipt(rootHash(), startGasUsed + e.gasUsed(), e.logs())); - m_transactionSet.insert(e.t().sha3()); - return e.gasUsed(); + return e.executionResult(); } State State::fromPending(unsigned _i) const @@ -1148,12 +1193,12 @@ State State::fromPending(unsigned _i) const return ret; } -void State::applyRewards(Addresses const& _uncleAddresses) +void State::applyRewards(vector const& _uncleBlockHeaders) { u256 r = m_blockReward; - for (auto const& i: _uncleAddresses) + for (auto const& i: _uncleBlockHeaders) { - addBalance(i, m_blockReward * 15 / 16); + addBalance(i.coinbaseAddress, m_blockReward * (8 + i.number - m_currentBlock.number) / 8); r += m_blockReward / 32; } addBalance(m_currentBlock.coinbaseAddress, r); diff --git a/libethereum/State.h b/libethereum/State.h index 78ec85f38..662426e17 100644 --- a/libethereum/State.h +++ b/libethereum/State.h @@ -41,7 +41,7 @@ namespace dev { -namespace test { class ImportTest; } +namespace test { class ImportTest; class StateLoader; } namespace eth { @@ -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 { @@ -68,7 +73,8 @@ enum class TransactionPriority class GasPricer { public: - GasPricer() {} + GasPricer() = default; + virtual ~GasPricer() = default; virtual u256 ask(State const&) const = 0; virtual u256 bid(TransactionPriority _p = TransactionPriority::Medium) const = 0; @@ -83,6 +89,12 @@ protected: u256 bid(TransactionPriority = TransactionPriority::Medium) const override { return 10 * szabo; } }; +enum class Permanence +{ + Reverted, + Committed +}; + /** * @brief Model of the current state of the ledger. * Maintains current ledger (m_current) as a fast hash-map. This is hashed only when required (i.e. to create or verify a block). @@ -92,11 +104,19 @@ class State { friend class ExtVM; friend class dev::test::ImportTest; + friend class dev::test::StateLoader; 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); @@ -115,11 +135,12 @@ 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. + /// @throws InterfaceNotSupported if compiled without ETH_FATDB. std::map addresses() const; /// Get the header information on the present block. @@ -179,15 +200,9 @@ public: /// Like sync but only operate on _tq, killing the invalid/old ones. bool cull(TransactionQueue& _tq) const; - /// Returns the last few block hashes of the current chain. - LastHashes getLastHashes(BlockChain const& _bc, unsigned _n) const; - /// Execute a given transaction. /// This will append @a _t to the transaction list and change the state accordingly. - u256 execute(BlockChain const& _bc, bytes const& _rlp, bytes* o_output = nullptr, bool _commit = true); - u256 execute(BlockChain const& _bc, bytesConstRef _rlp, bytes* o_output = nullptr, bool _commit = true); - u256 execute(LastHashes const& _lh, bytes const& _rlp, bytes* o_output = nullptr, bool _commit = true) { return execute(_lh, &_rlp, o_output, _commit); } - u256 execute(LastHashes const& _lh, bytesConstRef _rlp, bytes* o_output = nullptr, bool _commit = true); + ExecutionResult execute(LastHashes const& _lh, Transaction const& _t, Permanence _p = Permanence::Committed); /// Get the remaining gas limit in this block. u256 gasLimitRemaining() const { return m_currentBlock.gasLimit - gasUsed(); } @@ -212,6 +227,14 @@ public: */ void subBalance(Address _id, bigint _value); + /** + * @brief Transfers "the balance @a _value between two accounts. + * @param _from Account from which @a _value will be deducted. + * @param _to Account to which @a _value will be added. + * @param _value Amount to be transferred. + */ + void transferBalance(Address _from, Address _to, u256 _value) { subBalance(_from, _value); addBalance(_to, _value); } + /// Get the root of the storage of an account. h256 storageRoot(Address _contract) const; @@ -314,7 +337,7 @@ private: u256 enact(bytesConstRef _block, BlockChain const& _bc, bool _checkNonce = true); /// Finalise the block, applying the earned rewards. - void applyRewards(Addresses const& _uncleAddresses); + void applyRewards(std::vector const& _uncleBlockHeaders); /// @returns gas used by transactions thus far executed. u256 gasUsed() const { return m_receipts.size() ? m_receipts.back().gasUsed() : 0; } @@ -324,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/Transaction.cpp b/libethereum/Transaction.cpp index a89083648..3228eca70 100644 --- a/libethereum/Transaction.cpp +++ b/libethereum/Transaction.cpp @@ -21,8 +21,10 @@ #include #include +#include #include #include +#include #include "Transaction.h" using namespace std; using namespace dev; @@ -30,6 +32,27 @@ using namespace dev::eth; #define ETH_ADDRESS_DEBUG 0 +std::ostream& dev::eth::operator<<(std::ostream& _out, ExecutionResult const& _er) +{ + _out << "{" << _er.gasUsed << ", " << _er.newAddress << ", " << toHex(_er.output) << "}"; + return _out; +} + +TransactionException dev::eth::toTransactionException(VMException const& _e) +{ + if (!!dynamic_cast(&_e)) + return TransactionException::BadInstruction; + if (!!dynamic_cast(&_e)) + return TransactionException::BadJumpDestination; + if (!!dynamic_cast(&_e)) + return TransactionException::OutOfGas; + if (!!dynamic_cast(&_e)) + return TransactionException::OutOfStack; + if (!!dynamic_cast(&_e)) + return TransactionException::StackUnderflow; + return TransactionException::Unknown; +} + Transaction::Transaction(bytesConstRef _rlpData, CheckSignature _checkSig) { int field = 0; @@ -45,6 +68,10 @@ Transaction::Transaction(bytesConstRef _rlpData, CheckSignature _checkSig) m_type = rlp[field = 3].isEmpty() ? ContractCreation : MessageCall; m_receiveAddress = rlp[field = 3].isEmpty() ? Address() : rlp[field = 3].toHash
(RLP::VeryStrict); m_value = rlp[field = 4].toInt(); + + if (!rlp[field = 5].isData()) + BOOST_THROW_EXCEPTION(BadRLP() << errinfo_comment("transaction data RLP must be an array")); + m_data = rlp[field = 5].toBytes(); byte v = rlp[field = 6].toInt() - 27; h256 r = rlp[field = 7].toInt(); @@ -61,12 +88,12 @@ Transaction::Transaction(bytesConstRef _rlpData, CheckSignature _checkSig) } catch (Exception& _e) { - _e << errinfo_name("invalid transaction format") << BadFieldError(field,toHex(rlp[field].data().toBytes())); + _e << errinfo_name("invalid transaction format") << BadFieldError(field, toHex(rlp[field].data().toBytes())); throw; } } -Address Transaction::safeSender() const noexcept +Address const& Transaction::safeSender() const noexcept { try { @@ -75,11 +102,11 @@ Address Transaction::safeSender() const noexcept catch (...) { cwarn << "safeSender() did throw an exception: " << boost::current_exception_diagnostic_information(); - return Address(); + return NullAddress; } } -Address Transaction::sender() const +Address const& Transaction::sender() const { if (!m_sender) { diff --git a/libethereum/Transaction.h b/libethereum/Transaction.h index 7dd28f7c6..ab2c12b50 100644 --- a/libethereum/Transaction.h +++ b/libethereum/Transaction.h @@ -44,6 +44,61 @@ enum class CheckSignature Sender }; +enum class TransactionException +{ + None = 0, + Unknown, + InvalidSignature, + InvalidNonce, + NotEnoughCash, + OutOfGasBase, ///< Too little gas to pay for the base transaction cost. + BlockGasLimitReached, + BadInstruction, + BadJumpDestination, + OutOfGas, ///< Ran out of gas executing code of the transaction. + OutOfStack, ///< Ran out of stack executing code of the transaction. + StackUnderflow +}; + +enum class CodeDeposit +{ + None = 0, + Failed, + Success +}; + +struct VMException; + +TransactionException toTransactionException(VMException const& _e); + +/// Description of the result of executing a transaction. +struct ExecutionResult +{ + ExecutionResult() = default; + ExecutionResult(u256 const& _gasUsed, TransactionException _excepted, Address const& _newAddress, bytesConstRef _output, CodeDeposit _codeDeposit, u256 const& _gasRefund, unsigned _depositSize, u256 const& _gasForDeposit): + gasUsed(_gasUsed), + excepted(_excepted), + newAddress(_newAddress), + output(_output.toBytes()), + codeDeposit(_codeDeposit), + gasRefunded(_gasRefund), + depositSize(_depositSize), + gasForDeposit(_gasForDeposit) + {} + u256 gasUsed = 0; + TransactionException excepted = TransactionException::Unknown; + Address newAddress; + bytes output; + CodeDeposit codeDeposit = CodeDeposit::None; + u256 gasRefunded = 0; + unsigned depositSize = 0; + u256 gasForDeposit; +}; + +std::ostream& operator<<(std::ostream& _out, ExecutionResult const& _er); + +static const Address NullAddress; + /// Encodes a transaction, ready to be exported to or freshly imported from RLP. class Transaction { @@ -52,16 +107,16 @@ public: Transaction() {} /// Constructs a signed message-call transaction. - Transaction(u256 _value, u256 _gasPrice, u256 _gas, Address const& _dest, bytes const& _data, u256 _nonce, Secret const& _secret): m_type(MessageCall), m_nonce(_nonce), m_value(_value), m_receiveAddress(_dest), m_gasPrice(_gasPrice), m_gas(_gas), m_data(_data) { sign(_secret); } + Transaction(u256 const& _value, u256 const& _gasPrice, u256 const& _gas, Address const& _dest, bytes const& _data, u256 const& _nonce, Secret const& _secret): m_type(MessageCall), m_nonce(_nonce), m_value(_value), m_receiveAddress(_dest), m_gasPrice(_gasPrice), m_gas(_gas), m_data(_data) { sign(_secret); } /// Constructs a signed contract-creation transaction. - Transaction(u256 _value, u256 _gasPrice, u256 _gas, bytes const& _data, u256 _nonce, Secret const& _secret): m_type(ContractCreation), m_nonce(_nonce), m_value(_value), m_gasPrice(_gasPrice), m_gas(_gas), m_data(_data) { sign(_secret); } + Transaction(u256 const& _value, u256 const& _gasPrice, u256 const& _gas, bytes const& _data, u256 const& _nonce, Secret const& _secret): m_type(ContractCreation), m_nonce(_nonce), m_value(_value), m_gasPrice(_gasPrice), m_gas(_gas), m_data(_data) { sign(_secret); } /// Constructs an unsigned message-call transaction. - Transaction(u256 _value, u256 _gasPrice, u256 _gas, Address const& _dest, bytes const& _data): m_type(MessageCall), m_value(_value), m_receiveAddress(_dest), m_gasPrice(_gasPrice), m_gas(_gas), m_data(_data) {} + Transaction(u256 const& _value, u256 const& _gasPrice, u256 const& _gas, Address const& _dest, bytes const& _data): m_type(MessageCall), m_value(_value), m_receiveAddress(_dest), m_gasPrice(_gasPrice), m_gas(_gas), m_data(_data) {} /// Constructs an unsigned contract-creation transaction. - Transaction(u256 _value, u256 _gasPrice, u256 _gas, bytes const& _data): m_type(ContractCreation), m_value(_value), m_gasPrice(_gasPrice), m_gas(_gas), m_data(_data) {} + Transaction(u256 const& _value, u256 const& _gasPrice, u256 const& _gas, bytes const& _data): m_type(ContractCreation), m_value(_value), m_gasPrice(_gasPrice), m_gas(_gas), m_data(_data) {} /// Constructs a transaction from the given RLP. explicit Transaction(bytesConstRef _rlp, CheckSignature _checkSig); @@ -76,9 +131,9 @@ public: bool operator!=(Transaction const& _c) const { return !operator==(_c); } /// @returns sender of the transaction from the signature (and hash). - Address sender() const; + Address const& sender() const; /// Like sender() but will never throw. @returns a null Address if the signature is invalid. - Address safeSender() const noexcept; + Address const& safeSender() const noexcept; /// @returns true if transaction is non-null. explicit operator bool() const { return m_type != NullTransaction; } diff --git a/libethereum/TransactionQueue.cpp b/libethereum/TransactionQueue.cpp index 5701fc4a5..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 { @@ -46,37 +48,37 @@ bool TransactionQueue::import(bytesConstRef _transactionRLP) UpgradeGuard ul(l); // If valid, append to blocks. - m_current[h] = _transactionRLP.toBytes(); + m_current[h] = t; m_known.insert(h); } 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) +void TransactionQueue::setFuture(std::pair const& _t) { WriteGuard l(m_lock); if (m_current.count(_t.first)) { m_current.erase(_t.first); - m_unknown.insert(make_pair(Transaction(_t.second, CheckSignature::Sender).sender(), _t)); + m_unknown.insert(make_pair(_t.second.sender(), _t)); } } -void TransactionQueue::noteGood(std::pair const& _t) +void TransactionQueue::noteGood(std::pair const& _t) { WriteGuard l(m_lock); - auto r = m_unknown.equal_range(Transaction(_t.second, CheckSignature::Sender).sender()); + auto r = m_unknown.equal_range(_t.second.sender()); for (auto it = r.first; it != r.second; ++it) m_current.insert(it->second); m_unknown.erase(r.first, r.second); diff --git a/libethereum/TransactionQueue.h b/libethereum/TransactionQueue.h index b58944e4f..cf40e1209 100644 --- a/libethereum/TransactionQueue.h +++ b/libethereum/TransactionQueue.h @@ -23,8 +23,9 @@ #include #include -#include "libethcore/Common.h" #include +#include "libethcore/Common.h" +#include "Transaction.h" namespace dev { @@ -33,6 +34,7 @@ namespace eth class BlockChain; + /** * @brief A queue of Transactions, each stored as RLP. * @threadsafe @@ -40,25 +42,24 @@ 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); - std::map transactions() const { ReadGuard l(m_lock); return m_current; } + std::map transactions() const { ReadGuard l(m_lock); return m_current; } std::pair items() const { ReadGuard l(m_lock); return std::make_pair(m_current.size(), m_unknown.size()); } - void setFuture(std::pair const& _t); - void noteGood(std::pair const& _t); + void setFuture(std::pair const& _t); + void noteGood(std::pair const& _t); void clear() { WriteGuard l(m_lock); m_known.clear(); m_current.clear(); m_unknown.clear(); } private: mutable boost::shared_mutex m_lock; ///< General lock. std::set m_known; ///< Hashes of transactions in both sets. - std::map m_current; ///< Map of SHA3(tx) to tx. - std::multimap> m_unknown; ///< For transactions that have a future nonce; we map their sender address to the tx stuff, and insert once the sender has a valid TX. + std::map m_current; ///< Map of SHA3(tx) to tx. + std::multimap> m_unknown; ///< For transactions that have a future nonce; we map their sender address to the tx stuff, and insert once the sender has a valid TX. }; } diff --git a/libethereumx/Ethereum.cpp b/libethereumx/Ethereum.cpp index d1968b7fa..54b698646 100644 --- a/libethereumx/Ethereum.cpp +++ b/libethereumx/Ethereum.cpp @@ -83,7 +83,7 @@ void Ethereum::connect(std::string const& _seedHost, unsigned short _port) { } -void Ethereum::transact(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice) +void Ethereum::submitTransaction(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice) { } @@ -92,7 +92,7 @@ bytes Ethereum::call(Secret _secret, u256 _value, Address _dest, bytes const& _d return bytes(); } -Address Ethereum::transact(Secret _secret, u256 _endowment, bytes const& _init, u256 _gas, u256 _gasPrice) +Address Ethereum::submitTransaction(Secret _secret, u256 _endowment, bytes const& _init, u256 _gas, u256 _gasPrice) { return Address(); } diff --git a/libethereumx/Ethereum.h b/libethereumx/Ethereum.h index 517010b93..7ff685339 100644 --- a/libethereumx/Ethereum.h +++ b/libethereumx/Ethereum.h @@ -62,11 +62,11 @@ public: ~Ethereum(); /// Submits the given message-call transaction. - void transact(Secret _secret, u256 _value, Address _dest, bytes const& _data = bytes(), u256 _gas = 10000, u256 _gasPrice = 10 * szabo); + void submitTransaction(Secret _secret, u256 _value, Address _dest, bytes const& _data = bytes(), u256 _gas = 10000, u256 _gasPrice = 10 * szabo); /// Submits a new contract-creation transaction. /// @returns the new contract's address (assuming it all goes through). - Address transact(Secret _secret, u256 _endowment, bytes const& _init, u256 _gas = 10000, u256 _gasPrice = 10 * szabo); + Address submitTransaction(Secret _secret, u256 _endowment, bytes const& _init, u256 _gas = 10000, u256 _gasPrice = 10 * szabo); /// Injects the RLP-encoded transaction given by the _rlp into the transaction queue directly. void inject(bytesConstRef _rlp); diff --git a/libevm/ExtVMFace.h b/libevm/ExtVMFace.h index 4c6bcad0c..48fbd0b01 100644 --- a/libevm/ExtVMFace.h +++ b/libevm/ExtVMFace.h @@ -47,9 +47,9 @@ struct LogEntry LogBloom bloom() const { LogBloom ret; - ret.shiftBloom<3, 32>(sha3(address.ref())); + ret.shiftBloom<3>(sha3(address.ref())); for (auto t: topics) - ret.shiftBloom<3, 32>(sha3(t.ref())); + ret.shiftBloom<3>(sha3(t.ref())); return ret; } @@ -63,10 +63,10 @@ using LogEntries = std::vector; struct LocalisedLogEntry: public LogEntry { LocalisedLogEntry() {} - LocalisedLogEntry(LogEntry const& _le, unsigned _number, h256 _sha3 = {}): LogEntry(_le), number(_number), sha3(_sha3) {} + LocalisedLogEntry(LogEntry const& _le, unsigned _number, h256 _transactionHash = h256()): LogEntry(_le), number(_number), transactionHash(_transactionHash) {} unsigned number = 0; - h256 sha3; + h256 transactionHash; }; using LocalisedLogEntries = std::vector; diff --git a/libevm/VM.cpp b/libevm/VM.cpp index e0b487c9b..33c943938 100644 --- a/libevm/VM.cpp +++ b/libevm/VM.cpp @@ -36,6 +36,7 @@ struct InstructionMetric { int gasPriceTier; int args; + int ret; }; static array metrics() @@ -46,12 +47,15 @@ static array metrics() InstructionInfo inst = instructionInfo((Instruction)i); s_ret[i].gasPriceTier = inst.gasPriceTier; s_ret[i].args = inst.args; + s_ret[i].ret = inst.ret; } return s_ret; } bytesConstRef VM::go(ExtVMFace& _ext, OnOpFunc const& _onOp, uint64_t _steps) { + m_stack.reserve((unsigned)c_stackLimit); + static const array c_metrics = metrics(); auto memNeed = [](u256 _offset, dev::u256 _size) { return _size ? (bigint)_offset + _size : (bigint)0; }; @@ -89,7 +93,7 @@ bytesConstRef VM::go(ExtVMFace& _ext, OnOpFunc const& _onOp, uint64_t _steps) // should work, but just seems to result in immediate errorless exit on initial execution. yeah. weird. //m_onFail = std::function(onOperation); - require(metric.args); + require(metric.args, metric.ret); auto onOperation = [&]() { @@ -341,8 +345,10 @@ bytesConstRef VM::go(ExtVMFace& _ext, OnOpFunc const& _onOp, uint64_t _steps) break; case Instruction::CALLDATALOAD: { - if ((unsigned)m_stack.back() + (uint64_t)31 < _ext.data.size()) - m_stack.back() = (u256)*(h256 const*)(_ext.data.data() + (unsigned)m_stack.back()); + if ((bigint)m_stack.back() + 31 < _ext.data.size()) + m_stack.back() = (u256)*(h256 const*)(_ext.data.data() + (size_t)m_stack.back()); + else if ((bigint)m_stack.back() >= _ext.data.size()) + m_stack.back() = u256(); else { h256 r; @@ -608,10 +614,7 @@ bytesConstRef VM::go(ExtVMFace& _ext, OnOpFunc const& _onOp, uint64_t _steps) m_stack.pop_back(); if (_ext.balance(_ext.myAddress) >= endowment && _ext.depth < 1024) - { - _ext.subBalance(endowment); m_stack.push_back((u160)_ext.create(endowment, m_gas, bytesConstRef(m_temp.data() + initOff, initSize), _onOp)); - } else m_stack.push_back(0); break; @@ -638,10 +641,7 @@ bytesConstRef VM::go(ExtVMFace& _ext, OnOpFunc const& _onOp, uint64_t _steps) m_stack.pop_back(); if (_ext.balance(_ext.myAddress) >= value && _ext.depth < 1024) - { - _ext.subBalance(value); m_stack.push_back(_ext.call(inst == Instruction::CALL ? receiveAddress : _ext.myAddress, value, bytesConstRef(m_temp.data() + inOff, inSize), gas, bytesRef(m_temp.data() + outOff, outSize), _onOp, {}, receiveAddress)); - } else m_stack.push_back(0); diff --git a/libevm/VM.h b/libevm/VM.h index f2c773e4b..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) { if (m_stack.size() < _n) { if (m_onFail) m_onFail(); BOOST_THROW_EXCEPTION(StackTooSmall() << RequirementError((bigint)_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/libevm/VMFace.h b/libevm/VMFace.h index f8c20feb1..d2689d13e 100644 --- a/libevm/VMFace.h +++ b/libevm/VMFace.h @@ -31,7 +31,8 @@ struct BreakPointHit: virtual VMException {}; struct BadInstruction: virtual VMException {}; struct BadJumpDestination: virtual VMException {}; struct OutOfGas: virtual VMException {}; -struct StackTooSmall: virtual VMException {}; +struct OutOfStack: virtual VMException {}; +struct StackUnderflow: virtual VMException {}; /// EVM Virtual Machine interface class VMFace diff --git a/libevm/VMFactory.cpp b/libevm/VMFactory.cpp index 40f8a3500..1092906e4 100644 --- a/libevm/VMFactory.cpp +++ b/libevm/VMFactory.cpp @@ -16,6 +16,7 @@ */ #include "VMFactory.h" +#include #include "VM.h" #if ETH_EVMJIT diff --git a/libevmcore/Assembly.cpp b/libevmcore/Assembly.cpp index 5e9d2fcd3..904903761 100644 --- a/libevmcore/Assembly.cpp +++ b/libevmcore/Assembly.cpp @@ -20,85 +20,23 @@ */ #include "Assembly.h" - #include - #include +#include using namespace std; using namespace dev; using namespace dev::eth; -unsigned AssemblyItem::bytesRequired(unsigned _addressLength) const -{ - switch (m_type) - { - case Operation: - case Tag: // 1 byte for the JUMPDEST - return 1; - case PushString: - return 33; - case Push: - return 1 + max(1, dev::bytesRequired(m_data)); - case PushSubSize: - case PushProgramSize: - return 4; // worst case: a 16MB program - case PushTag: - case PushData: - case PushSub: - return 1 + _addressLength; - case NoOptimizeBegin: - case NoOptimizeEnd: - return 0; - default: - break; - } - BOOST_THROW_EXCEPTION(InvalidOpcode()); -} - -int AssemblyItem::deposit() const -{ - switch (m_type) - { - case Operation: - return instructionInfo((Instruction)(byte)m_data).ret - instructionInfo((Instruction)(byte)m_data).args; - case Push: - case PushString: - case PushTag: - case PushData: - case PushSub: - case PushSubSize: - case PushProgramSize: - return 1; - case Tag: - return 0; - default:; - } - return 0; -} - -unsigned Assembly::bytesRequired() const -{ - for (unsigned br = 1;; ++br) - { - unsigned ret = 1; - for (auto const& i: m_data) - ret += i.second.size(); - - for (AssemblyItem const& i: m_items) - ret += i.bytesRequired(br); - if (dev::bytesRequired(ret) <= br) - return ret; - } -} - void Assembly::append(Assembly const& _a) { auto newDeposit = m_deposit + _a.deposit(); for (AssemblyItem i: _a.m_items) { if (i.type() == Tag || i.type() == PushTag) - i.m_data += m_usedTags; + i.setData(i.data() + m_usedTags); + else if (i.type() == PushSub || i.type() == PushSubSize) + i.setData(i.data() + m_usedTags); append(i); } m_deposit = newDeposit; @@ -108,7 +46,7 @@ void Assembly::append(Assembly const& _a) for (auto const& i: _a.m_strings) m_strings.insert(i); for (auto const& i: _a.m_subs) - m_subs.insert(i); + m_subs.push_back(i); assert(!_a.m_baseDeposit); assert(!_a.m_totalDeposit); @@ -126,51 +64,19 @@ void Assembly::append(Assembly const& _a, int _deposit) } } -ostream& dev::eth::operator<<(ostream& _out, AssemblyItemsConstRef _i) +unsigned Assembly::bytesRequired() const { - for (AssemblyItem const& i: _i) - switch (i.type()) - { - case Operation: - _out << " " << instructionInfo((Instruction)(byte)i.data()).name; - break; - case Push: - _out << " PUSH" << i.data(); - break; - case PushString: - _out << " PUSH'[" << hex << (unsigned)i.data() << "]"; - break; - case PushTag: - _out << " PUSH[tag" << i.data() << "]"; - break; - case Tag: - _out << " tag" << i.data() << ": JUMPDEST"; - break; - case PushData: - _out << " PUSH*[" << hex << (unsigned)i.data() << "]"; - break; - case PushSub: - _out << " PUSHs[" << hex << h256(i.data()).abridged() << "]"; - break; - case PushSubSize: - _out << " PUSHss[" << hex << h256(i.data()).abridged() << "]"; - break; - case PushProgramSize: - _out << " PUSHSIZE"; - break; - case NoOptimizeBegin: - _out << " DoNotOptimze{{"; - break; - case NoOptimizeEnd: - _out << " DoNotOptimze}}"; - break; - case UndefinedItem: - _out << " ???"; - break; - default: - BOOST_THROW_EXCEPTION(InvalidOpcode()); - } - return _out; + for (unsigned br = 1;; ++br) + { + unsigned ret = 1; + for (auto const& i: m_data) + ret += i.second.size(); + + for (AssemblyItem const& i: m_items) + ret += i.bytesRequired(br); + if (dev::bytesRequired(ret) <= br) + return ret; + } } string Assembly::getLocationFromSources(StringMap const& _sourceCodes, SourceLocation const& _location) const @@ -194,74 +100,68 @@ string Assembly::getLocationFromSources(StringMap const& _sourceCodes, SourceLoc return move(cut); } -ostream& Assembly::streamRLP(ostream& _out, string const& _prefix, StringMap const& _sourceCodes) const +ostream& Assembly::stream(ostream& _out, string const& _prefix, StringMap const& _sourceCodes) const { _out << _prefix << ".code:" << endl; for (AssemblyItem const& i: m_items) { - string sourceLine = getLocationFromSources(_sourceCodes, i.getLocation()); _out << _prefix; - switch (i.m_type) + switch (i.type()) { case Operation: - _out << " " << instructionInfo((Instruction)(byte)i.m_data).name; + _out << " " << instructionInfo(i.instruction()).name << "\t" << i.getJumpTypeAsString(); break; case Push: - _out << " PUSH " << i.m_data; + _out << " PUSH " << i.data(); break; case PushString: - _out << " PUSH \"" << m_strings.at((h256)i.m_data) << "\""; + _out << " PUSH \"" << m_strings.at((h256)i.data()) << "\""; break; case PushTag: - _out << " PUSH [tag" << i.m_data << "]"; + _out << " PUSH [tag" << i.data() << "]"; break; case PushSub: - _out << " PUSH [$" << h256(i.m_data).abridged() << "]"; + _out << " PUSH [$" << h256(i.data()).abridged() << "]"; break; case PushSubSize: - _out << " PUSH #[$" << h256(i.m_data).abridged() << "]"; + _out << " PUSH #[$" << h256(i.data()).abridged() << "]"; break; case PushProgramSize: _out << " PUSHSIZE"; break; case Tag: - _out << "tag" << i.m_data << ": " << endl << _prefix << " JUMPDEST"; + _out << "tag" << i.data() << ": " << endl << _prefix << " JUMPDEST"; break; case PushData: - _out << " PUSH [" << hex << (unsigned)i.m_data << "]"; - break; - case NoOptimizeBegin: - _out << "DoNotOptimze{{"; - break; - case NoOptimizeEnd: - _out << "DoNotOptimze}}"; + _out << " PUSH [" << hex << (unsigned)i.data() << "]"; break; default: BOOST_THROW_EXCEPTION(InvalidOpcode()); } - _out << string("\t\t") << sourceLine << endl; + _out << "\t\t" << getLocationFromSources(_sourceCodes, i.getLocation()) << endl; } if (!m_data.empty() || !m_subs.empty()) { _out << _prefix << ".data:" << endl; for (auto const& i: m_data) - if (!m_subs.count(i.first)) + if (u256(i.first) >= m_subs.size()) _out << _prefix << " " << hex << (unsigned)(u256)i.first << ": " << toHex(i.second) << endl; - for (auto const& i: m_subs) + for (size_t i = 0; i < m_subs.size(); ++i) { - _out << _prefix << " " << hex << (unsigned)(u256)i.first << ": " << endl; - i.second.streamRLP(_out, _prefix + " "); + _out << _prefix << " " << hex << i << ": " << endl; + m_subs[i].stream(_out, _prefix + " ", _sourceCodes); } } return _out; } -AssemblyItem const& Assembly::append(AssemblyItem const& _i, SourceLocation const& _location) +AssemblyItem const& Assembly::append(AssemblyItem const& _i) { m_deposit += _i.deposit(); m_items.push_back(_i); - m_items.back().setLocation(_location); + if (m_items.back().getLocation().isEmpty() && !m_currentSourceLocation.isEmpty()) + m_items.back().setLocation(m_currentSourceLocation); return back(); } @@ -280,24 +180,6 @@ inline bool matches(AssemblyItemsConstRef _a, AssemblyItemsConstRef _b) return true; } -inline bool popCountIncreased(AssemblyItemsConstRef _pre, AssemblyItems const& _post) -{ - auto isPop = [](AssemblyItem const& _item) -> bool { return _item.match(AssemblyItem(Instruction::POP)); }; - return count_if(begin(_post), end(_post), isPop) > count_if(begin(_pre), end(_pre), isPop); -} - -//@todo this has to move to a special optimizer class soon -template -unsigned bytesRequiredBySlice(Iterator _begin, Iterator _end) -{ - // this is only used in the optimizer, so we can provide a guess for the address length - unsigned addressLength = 4; - unsigned size = 0; - for (; _begin != _end; ++_begin) - size += _begin->bytesRequired(addressLength); - return size; -} - struct OptimiserChannel: public LogChannel { static const char* name() { return "OPT"; } static const int verbosity = 12; }; #define copt dev::LogOutputStream() @@ -305,127 +187,57 @@ Assembly& Assembly::optimise(bool _enable) { if (!_enable) return *this; - auto signextend = [](u256 a, u256 b) -> u256 - { - if (a >= 31) - return b; - unsigned testBit = unsigned(a) * 8 + 7; - u256 mask = (u256(1) << testBit) - 1; - return boost::multiprecision::bit_test(b, testBit) ? b | ~mask : b & mask; - }; - map> const c_simple = - { - { Instruction::SUB, [](u256 a, u256 b)->u256{return a - b;} }, - { Instruction::DIV, [](u256 a, u256 b)->u256{return a / b;} }, - { Instruction::SDIV, [](u256 a, u256 b)->u256{return s2u(u2s(a) / u2s(b));} }, - { Instruction::MOD, [](u256 a, u256 b)->u256{return a % b;} }, - { Instruction::SMOD, [](u256 a, u256 b)->u256{return s2u(u2s(a) % u2s(b));} }, - { Instruction::EXP, [](u256 a, u256 b)->u256{return (u256)boost::multiprecision::powm((bigint)a, (bigint)b, bigint(1) << 256);} }, - { Instruction::SIGNEXTEND, signextend }, - { Instruction::LT, [](u256 a, u256 b)->u256{return a < b ? 1 : 0;} }, - { Instruction::GT, [](u256 a, u256 b)->u256{return a > b ? 1 : 0;} }, - { Instruction::SLT, [](u256 a, u256 b)->u256{return u2s(a) < u2s(b) ? 1 : 0;} }, - { Instruction::SGT, [](u256 a, u256 b)->u256{return u2s(a) > u2s(b) ? 1 : 0;} }, - { Instruction::EQ, [](u256 a, u256 b)->u256{return a == b ? 1 : 0;} }, - }; - map> const c_associative = - { - { Instruction::ADD, [](u256 a, u256 b)->u256{return a + b;} }, - { Instruction::MUL, [](u256 a, u256 b)->u256{return a * b;} }, - { Instruction::AND, [](u256 a, u256 b)->u256{return a & b;} }, - { Instruction::OR, [](u256 a, u256 b)->u256{return a | b;} }, - { Instruction::XOR, [](u256 a, u256 b)->u256{return a ^ b;} }, - }; - std::vector> const c_identities = - { { Instruction::ADD, 0}, { Instruction::MUL, 1}, { Instruction::MOD, 0}, { Instruction::OR, 0}, { Instruction::XOR, 0} }; - std::vector>> rules = - { - { { Push, Instruction::POP }, [](AssemblyItemsConstRef) -> AssemblyItems { return {}; } }, - { { PushTag, Instruction::POP }, [](AssemblyItemsConstRef) -> AssemblyItems { return {}; } }, - { { PushString, Instruction::POP }, [](AssemblyItemsConstRef) -> AssemblyItems { return {}; } }, - { { PushSub, Instruction::POP }, [](AssemblyItemsConstRef) -> AssemblyItems { return {}; } }, - { { PushSubSize, Instruction::POP }, [](AssemblyItemsConstRef) -> AssemblyItems { return {}; } }, - { { PushProgramSize, Instruction::POP }, [](AssemblyItemsConstRef) -> AssemblyItems { return {}; } }, - { { Push, PushTag, Instruction::JUMPI }, [](AssemblyItemsConstRef m) -> AssemblyItems { if (m[0].data()) return { m[1], Instruction::JUMP }; else return {}; } }, - { { Instruction::ISZERO, Instruction::ISZERO }, [](AssemblyItemsConstRef) -> AssemblyItems { return {}; } }, - }; - - for (auto const& i: c_simple) - rules.push_back({ { Push, Push, i.first }, [&](AssemblyItemsConstRef m) -> AssemblyItems { return { i.second(m[1].data(), m[0].data()) }; } }); - for (auto const& i: c_associative) - { - rules.push_back({ { Push, Push, i.first }, [&](AssemblyItemsConstRef m) -> AssemblyItems { return { i.second(m[1].data(), m[0].data()) }; } }); - rules.push_back({ { Push, i.first, Push, i.first }, [&](AssemblyItemsConstRef m) -> AssemblyItems { return { i.second(m[2].data(), m[0].data()), i.first }; } }); - } - for (auto const& i: c_identities) - rules.push_back({{Push, i.first}, [&](AssemblyItemsConstRef m) -> AssemblyItems - { return m[0].data() == i.second ? AssemblyItems() : m.toVector(); }}); + std::vector>> rules; // jump to next instruction - rules.push_back({ { PushTag, Instruction::JUMP, Tag }, [](AssemblyItemsConstRef m) -> AssemblyItems { if (m[0].m_data == m[2].m_data) return {m[2]}; else return m.toVector(); }}); - - // pop optimization, do not compute values that are popped again anyway - rules.push_back({ { AssemblyItem(UndefinedItem), Instruction::POP }, [](AssemblyItemsConstRef m) -> AssemblyItems - { - if (m[0].type() != Operation) - return m.toVector(); - Instruction instr = Instruction(byte(m[0].data())); - if (Instruction::DUP1 <= instr && instr <= Instruction::DUP16) - return {}; - InstructionInfo info = instructionInfo(instr); - if (info.sideEffects || info.additional != 0 || info.ret != 1) - return m.toVector(); - return AssemblyItems(info.args, Instruction::POP); - } }); - // compute constants close to powers of two by expressions - auto computeConstants = [](AssemblyItemsConstRef m) -> AssemblyItems - { - u256 const& c = m[0].data(); - unsigned const minBits = 4 * 8; - if (c < (bigint(1) << minBits)) - return m.toVector(); // we need at least "PUSH1 PUSH1 <2> EXP" - if (c == u256(-1)) - return {u256(0), Instruction::NOT}; - for (unsigned bits = minBits; bits < 256; ++bits) - { - bigint const diff = c - (bigint(1) << bits); - if (abs(diff) > 0xff) - continue; - AssemblyItems powerOfTwo{u256(bits), u256(2), Instruction::EXP}; - if (diff == 0) - return powerOfTwo; - return AssemblyItems{u256(abs(diff))} + powerOfTwo + - AssemblyItems{diff > 0 ? Instruction::ADD : Instruction::SUB}; - } - return m.toVector(); - }; - rules.push_back({{Push}, computeConstants}); - - copt << *this; + rules.push_back({ { PushTag, Instruction::JUMP, Tag }, [](AssemblyItemsConstRef m) -> AssemblyItems { if (m[0].data() == m[2].data()) return {m[2]}; else return m.toVector(); }}); unsigned total = 0; for (unsigned count = 1; count > 0; total += count) { + copt << *this; count = 0; - for (unsigned i = 0; i < m_items.size(); ++i) + + copt << "Performing common subexpression elimination..."; + for (auto iter = m_items.begin(); iter != m_items.end();) { - if (m_items[i].type() == NoOptimizeBegin) + CommonSubexpressionEliminator eliminator; + auto orig = iter; + iter = eliminator.feedItems(iter, m_items.end()); + AssemblyItems optItems; + bool shouldReplace = false; + try + { + optItems = eliminator.getOptimizedItems(); + shouldReplace = (optItems.size() < size_t(iter - orig)); + } + catch (StackTooDeepException const&) + { + // This might happen if the opcode reconstruction is not as efficient + // as the hand-crafted code. + } + + if (shouldReplace) { - while (i < m_items.size() && m_items[i].type() != NoOptimizeEnd) - ++i; - continue; + copt << "Old size: " << (iter - orig) << ", new size: " << optItems.size(); + count++; + for (auto moveIter = optItems.begin(); moveIter != optItems.end(); ++orig, ++moveIter) + *orig = move(*moveIter); + iter = m_items.erase(orig, iter); } + } + + for (unsigned i = 0; i < m_items.size(); ++i) + { for (auto const& r: rules) { auto vr = AssemblyItemsConstRef(&m_items).cropped(i, r.first.size()); if (matches(vr, &r.first)) { auto rw = r.second(vr); - unsigned const vrSizeInBytes = bytesRequiredBySlice(vr.begin(), vr.end()); - unsigned const rwSizeInBytes = bytesRequiredBySlice(rw.begin(), rw.end()); - if (rwSizeInBytes < vrSizeInBytes || (rwSizeInBytes == vrSizeInBytes && popCountIncreased(vr, rw))) + if (rw.size() < vr.size()) { - copt << vr << "matches" << AssemblyItemsConstRef(&r.first) << "becomes..."; - copt << AssemblyItemsConstRef(&rw); + copt << "Rule " << vr << " matches " << AssemblyItemsConstRef(&r.first) << " becomes..."; + copt << AssemblyItemsConstRef(&rw) << "\n"; if (rw.size() > vr.size()) { // create hole in the vector @@ -439,17 +251,15 @@ Assembly& Assembly::optimise(bool _enable) copy(rw.begin(), rw.end(), m_items.begin() + i); count++; - copt << "Now:\n" << m_items; + copt << "Now:" << m_items; } } } - if (m_items[i].type() == Operation && m_items[i].data() == (byte)Instruction::JUMP) + if (m_items[i].type() == Operation && m_items[i].instruction() == Instruction::JUMP) { bool o = false; while (m_items.size() > i + 1 && m_items[i + 1].type() != Tag) { - if (m_items[i + 1].type() == NoOptimizeBegin) - break; m_items.erase(m_items.begin() + i + 1); o = true; } @@ -474,7 +284,7 @@ Assembly& Assembly::optimise(bool _enable) { auto t = *tags.begin(); unsigned i = t.second; - if (i && m_items[i - 1].type() == Operation && m_items[i - 1].data() == (byte)Instruction::JUMP) + if (i && m_items[i - 1].type() == Operation && m_items[i - 1].instruction() == Instruction::JUMP) while (i < m_items.size() && (m_items[i].type() != Tag || tags.count(m_items[i].data()))) { if (m_items[i].type() == Tag && tags.count(m_items[i].data())) @@ -493,8 +303,8 @@ Assembly& Assembly::optimise(bool _enable) copt << total << " optimisations done."; - for (auto& i: m_subs) - i.second.optimise(true); + for (auto& sub: m_subs) + sub.optimise(true); return *this; } @@ -511,8 +321,8 @@ bytes Assembly::assemble() const unsigned bytesPerTag = dev::bytesRequired(totalBytes); byte tagPush = (byte)Instruction::PUSH1 - 1 + bytesPerTag; - for (auto const& i: m_subs) - m_data[i.first] = i.second.assemble(); + for (size_t i = 0; i < m_subs.size(); ++i) + m_data[u256(i)] = m_subs[i].assemble(); unsigned bytesRequiredIncludingData = bytesRequired(); unsigned bytesPerDataRef = dev::bytesRequired(bytesRequiredIncludingData); @@ -521,16 +331,16 @@ bytes Assembly::assemble() const // m_data must not change from here on for (AssemblyItem const& i: m_items) - switch (i.m_type) + switch (i.type()) { case Operation: - ret.push_back((byte)i.m_data); + ret.push_back((byte)i.data()); break; case PushString: { ret.push_back((byte)Instruction::PUSH32); unsigned ii = 0; - for (auto j: m_strings.at((h256)i.m_data)) + for (auto j: m_strings.at((h256)i.data())) if (++ii > 32) break; else @@ -541,30 +351,30 @@ bytes Assembly::assemble() const } case Push: { - byte b = max(1, dev::bytesRequired(i.m_data)); + byte b = max(1, dev::bytesRequired(i.data())); ret.push_back((byte)Instruction::PUSH1 - 1 + b); ret.resize(ret.size() + b); bytesRef byr(&ret.back() + 1 - b, b); - toBigEndian(i.m_data, byr); + toBigEndian(i.data(), byr); break; } case PushTag: { ret.push_back(tagPush); - tagRef[ret.size()] = (unsigned)i.m_data; + tagRef[ret.size()] = (unsigned)i.data(); ret.resize(ret.size() + bytesPerTag); break; } case PushData: case PushSub: { ret.push_back(dataRefPush); - dataRef.insert(make_pair((h256)i.m_data, ret.size())); + dataRef.insert(make_pair((h256)i.data(), ret.size())); ret.resize(ret.size() + bytesPerDataRef); break; } case PushSubSize: { - auto s = m_data[i.m_data].size(); + auto s = m_data[i.data()].size(); byte b = max(1, dev::bytesRequired(s)); ret.push_back((byte)Instruction::PUSH1 - 1 + b); ret.resize(ret.size() + b); @@ -580,12 +390,9 @@ bytes Assembly::assemble() const break; } case Tag: - tagPos[(unsigned)i.m_data] = ret.size(); + tagPos[(unsigned)i.data()] = ret.size(); ret.push_back((byte)Instruction::JUMPDEST); break; - case NoOptimizeBegin: - case NoOptimizeEnd: - break; default: BOOST_THROW_EXCEPTION(InvalidOpcode()); } diff --git a/libevmcore/Assembly.h b/libevmcore/Assembly.h index 4b818e624..2744af900 100644 --- a/libevmcore/Assembly.h +++ b/libevmcore/Assembly.h @@ -24,8 +24,10 @@ #include #include #include +#include #include #include +#include #include "Exceptions.h" namespace dev @@ -33,46 +35,6 @@ namespace dev namespace eth { -enum AssemblyItemType { UndefinedItem, Operation, Push, PushString, PushTag, PushSub, PushSubSize, PushProgramSize, Tag, PushData, NoOptimizeBegin, NoOptimizeEnd }; - -class Assembly; - -class AssemblyItem -{ - friend class Assembly; - -public: - AssemblyItem(u256 _push): m_type(Push), m_data(_push) {} - AssemblyItem(Instruction _i): m_type(Operation), m_data((byte)_i) {} - AssemblyItem(AssemblyItemType _type, u256 _data = 0): m_type(_type), m_data(_data) {} - - AssemblyItem tag() const { if (asserts(m_type == PushTag || m_type == Tag)) BOOST_THROW_EXCEPTION(Exception()); return AssemblyItem(Tag, m_data); } - AssemblyItem pushTag() const { if (asserts(m_type == PushTag || m_type == Tag)) BOOST_THROW_EXCEPTION(Exception()); return AssemblyItem(PushTag, m_data); } - - AssemblyItemType type() const { return m_type; } - u256 data() const { return m_data; } - - /// @returns an upper bound for the number of bytes required by this item, assuming that - /// the value of a jump tag takes @a _addressLength bytes. - unsigned bytesRequired(unsigned _addressLength) const; - int deposit() const; - - bool match(AssemblyItem const& _i) const { return _i.m_type == UndefinedItem || (m_type == _i.m_type && (m_type != Operation || m_data == _i.m_data)); } - void setLocation(SourceLocation const& _location) { m_location = _location;} - SourceLocation const& getLocation() const { return m_location; } - -private: - AssemblyItemType m_type; - u256 m_data; - SourceLocation m_location; -}; - -using AssemblyItems = std::vector; -using AssemblyItemsConstRef = vector_ref; - -std::ostream& operator<<(std::ostream& _out, AssemblyItemsConstRef _i); -inline std::ostream& operator<<(std::ostream& _out, AssemblyItems const& _i) { return operator<<(_out, AssemblyItemsConstRef(&_i)); } - class Assembly { public: @@ -81,16 +43,16 @@ public: AssemblyItem newTag() { return AssemblyItem(Tag, m_usedTags++); } AssemblyItem newPushTag() { return AssemblyItem(PushTag, m_usedTags++); } AssemblyItem newData(bytes const& _data) { h256 h = (u256)std::hash()(asString(_data)); m_data[h] = _data; return AssemblyItem(PushData, h); } - AssemblyItem newSub(Assembly const& _sub) { h256 h = h256::random(s_fixedHashEngine); m_subs[h] = _sub; return AssemblyItem(PushSub, h); } + AssemblyItem newSub(Assembly const& _sub) { m_subs.push_back(_sub); return AssemblyItem(PushSub, m_subs.size() - 1); } AssemblyItem newPushString(std::string const& _data) { h256 h = (u256)std::hash()(_data); m_strings[h] = _data; return AssemblyItem(PushString, h); } - AssemblyItem newPushSubSize(h256 const& _subId) { return AssemblyItem(PushSubSize, _subId); } + AssemblyItem newPushSubSize(u256 const& _subId) { return AssemblyItem(PushSubSize, _subId); } AssemblyItem append() { return append(newTag()); } void append(Assembly const& _a); void append(Assembly const& _a, int _deposit); - AssemblyItem const& append(AssemblyItem const& _i, SourceLocation const& _location = SourceLocation()); - AssemblyItem const& append(std::string const& _data, SourceLocation const& _location = SourceLocation()) { return append(newPushString(_data), _location); } - AssemblyItem const& append(bytes const& _data, SourceLocation const& _location = SourceLocation()) { return append(newData(_data), _location); } + AssemblyItem const& append(AssemblyItem const& _i); + AssemblyItem const& append(std::string const& _data) { return append(newPushString(_data)); } + AssemblyItem const& append(bytes const& _data) { return append(newData(_data)); } AssemblyItem appendSubSize(Assembly const& _a) { auto ret = newSub(_a); append(newPushSubSize(ret.data())); return ret; } /// Pushes the final size of the current assembly itself. Use this when the code is modified /// after compilation and CODESIZE is not an option. @@ -103,7 +65,7 @@ public: template Assembly& operator<<(T const& _d) { append(_d); return *this; } AssemblyItems const& getItems() const { return m_items; } AssemblyItem const& back() const { return m_items.back(); } - std::string backString() const { return m_items.size() && m_items.back().m_type == PushString ? m_strings.at((h256)m_items.back().m_data) : std::string(); } + std::string backString() const { return m_items.size() && m_items.back().type() == PushString ? m_strings.at((h256)m_items.back().data()) : std::string(); } void onePath() { if (asserts(!m_totalDeposit && !m_baseDeposit)) BOOST_THROW_EXCEPTION(InvalidDeposit()); m_baseDeposit = m_deposit; m_totalDeposit = INT_MAX; } void otherPath() { donePath(); m_totalDeposit = m_deposit; m_deposit = m_baseDeposit; } @@ -114,14 +76,17 @@ public: void popTo(int _deposit) { while (m_deposit > _deposit) append(Instruction::POP); } void injectStart(AssemblyItem const& _i); - std::string out() const { std::stringstream ret; streamRLP(ret); return ret.str(); } + std::string out() const { std::stringstream ret; stream(ret); return ret.str(); } int deposit() const { return m_deposit; } void adjustDeposit(int _adjustment) { m_deposit += _adjustment; if (asserts(m_deposit >= 0)) BOOST_THROW_EXCEPTION(InvalidDeposit()); } void setDeposit(int _deposit) { m_deposit = _deposit; if (asserts(m_deposit >= 0)) BOOST_THROW_EXCEPTION(InvalidDeposit()); } + /// Changes the source location used for each appended item. + void setSourceLocation(SourceLocation const& _location) { m_currentSourceLocation = _location; } + bytes assemble() const; Assembly& optimise(bool _enable); - std::ostream& streamRLP(std::ostream& _out, std::string const& _prefix = "", const StringMap &_sourceCodes = StringMap()) const; + std::ostream& stream(std::ostream& _out, std::string const& _prefix = "", const StringMap &_sourceCodes = StringMap()) const; protected: std::string getLocationFromSources(StringMap const& _sourceCodes, SourceLocation const& _location) const; @@ -131,17 +96,19 @@ protected: unsigned m_usedTags = 0; AssemblyItems m_items; mutable std::map m_data; - std::map m_subs; + std::vector m_subs; std::map m_strings; int m_deposit = 0; int m_baseDeposit = 0; int m_totalDeposit = 0; + + SourceLocation m_currentSourceLocation; }; inline std::ostream& operator<<(std::ostream& _out, Assembly const& _a) { - _a.streamRLP(_out); + _a.stream(_out); return _out; } diff --git a/libevmcore/AssemblyItem.cpp b/libevmcore/AssemblyItem.cpp new file mode 100644 index 000000000..a4485a144 --- /dev/null +++ b/libevmcore/AssemblyItem.cpp @@ -0,0 +1,135 @@ +/* + 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 Assembly.cpp + * @author Gav Wood + * @date 2014 + */ + +#include "AssemblyItem.h" +#include + +using namespace std; +using namespace dev; +using namespace dev::eth; + +unsigned AssemblyItem::bytesRequired(unsigned _addressLength) const +{ + switch (m_type) + { + case Operation: + case Tag: // 1 byte for the JUMPDEST + return 1; + case PushString: + return 33; + case Push: + return 1 + max(1, dev::bytesRequired(m_data)); + case PushSubSize: + case PushProgramSize: + return 4; // worst case: a 16MB program + case PushTag: + case PushData: + case PushSub: + return 1 + _addressLength; + default: + break; + } + BOOST_THROW_EXCEPTION(InvalidOpcode()); +} + +int AssemblyItem::deposit() const +{ + switch (m_type) + { + case Operation: + return instructionInfo(instruction()).ret - instructionInfo(instruction()).args; + case Push: + case PushString: + case PushTag: + case PushData: + case PushSub: + case PushSubSize: + case PushProgramSize: + return 1; + case Tag: + return 0; + default:; + } + return 0; +} + +string AssemblyItem::getJumpTypeAsString() const +{ + switch (m_jumpType) + { + case JumpType::IntoFunction: + return "[in]"; + case JumpType::OutOfFunction: + return "[out]"; + case JumpType::Ordinary: + default: + return ""; + } +} + +ostream& dev::eth::operator<<(ostream& _out, AssemblyItem const& _item) +{ + switch (_item.type()) + { + case Operation: + _out << " " << instructionInfo(_item.instruction()).name; + if (_item.instruction() == eth::Instruction::JUMP || _item.instruction() == eth::Instruction::JUMPI) + _out << "\t" << _item.getJumpTypeAsString(); + break; + case Push: + _out << " PUSH " << hex << _item.data(); + break; + case PushString: + _out << " PushString" << hex << (unsigned)_item.data(); + break; + case PushTag: + _out << " PushTag " << _item.data(); + break; + case Tag: + _out << " Tag " << _item.data(); + break; + case PushData: + _out << " PushData " << hex << (unsigned)_item.data(); + break; + case PushSub: + _out << " PushSub " << hex << h256(_item.data()).abridged(); + break; + case PushSubSize: + _out << " PushSubSize " << hex << h256(_item.data()).abridged(); + break; + case PushProgramSize: + _out << " PushProgramSize"; + break; + case UndefinedItem: + _out << " ???"; + break; + default: + BOOST_THROW_EXCEPTION(InvalidOpcode()); + } + return _out; +} + +ostream& dev::eth::operator<<(ostream& _out, AssemblyItemsConstRef _i) +{ + for (AssemblyItem const& i: _i) + _out << i; + return _out; +} diff --git a/libevmcore/AssemblyItem.h b/libevmcore/AssemblyItem.h new file mode 100644 index 000000000..3e222a6eb --- /dev/null +++ b/libevmcore/AssemblyItem.h @@ -0,0 +1,93 @@ +/* + 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 Assembly.h + * @author Gav Wood + * @date 2014 + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include "Exceptions.h" + +namespace dev +{ +namespace eth +{ + +enum AssemblyItemType { UndefinedItem, Operation, Push, PushString, PushTag, PushSub, PushSubSize, PushProgramSize, Tag, PushData }; + +class Assembly; + +class AssemblyItem +{ +public: + enum class JumpType { Ordinary, IntoFunction, OutOfFunction }; + + AssemblyItem(u256 _push): m_type(Push), m_data(_push) {} + AssemblyItem(Instruction _i): m_type(Operation), m_data((byte)_i) {} + AssemblyItem(AssemblyItemType _type, u256 _data = 0): m_type(_type), m_data(_data) {} + + AssemblyItem tag() const { assertThrow(m_type == PushTag || m_type == Tag, Exception, ""); return AssemblyItem(Tag, m_data); } + AssemblyItem pushTag() const { assertThrow(m_type == PushTag || m_type == Tag, Exception, ""); return AssemblyItem(PushTag, m_data); } + + AssemblyItemType type() const { return m_type; } + u256 const& data() const { return m_data; } + void setType(AssemblyItemType const _type) { m_type = _type; } + void setData(u256 const& _data) { m_data = _data; } + + /// @returns the instruction of this item (only valid if type() == Operation) + Instruction instruction() const { return Instruction(byte(m_data)); } + + /// @returns true iff the type and data of the items are equal. + bool operator==(AssemblyItem const& _other) const { return m_type == _other.m_type && m_data == _other.m_data; } + bool operator!=(AssemblyItem const& _other) const { return !operator==(_other); } + + /// @returns an upper bound for the number of bytes required by this item, assuming that + /// the value of a jump tag takes @a _addressLength bytes. + unsigned bytesRequired(unsigned _addressLength) const; + int deposit() const; + + bool match(AssemblyItem const& _i) const { return _i.m_type == UndefinedItem || (m_type == _i.m_type && (m_type != Operation || m_data == _i.m_data)); } + void setLocation(SourceLocation const& _location) { m_location = _location; } + SourceLocation const& getLocation() const { return m_location; } + + void setJumpType(JumpType _jumpType) { m_jumpType = _jumpType; } + JumpType getJumpType() const { return m_jumpType; } + std::string getJumpTypeAsString() const; + +private: + AssemblyItemType m_type; + u256 m_data; + SourceLocation m_location; + JumpType m_jumpType = JumpType::Ordinary; +}; + +using AssemblyItems = std::vector; +using AssemblyItemsConstRef = vector_ref; + +std::ostream& operator<<(std::ostream& _out, AssemblyItem const& _item); +std::ostream& operator<<(std::ostream& _out, AssemblyItemsConstRef _i); +inline std::ostream& operator<<(std::ostream& _out, AssemblyItems const& _i) { return operator<<(_out, AssemblyItemsConstRef(&_i)); } + +} +} diff --git a/libevmcore/CommonSubexpressionEliminator.cpp b/libevmcore/CommonSubexpressionEliminator.cpp new file mode 100644 index 000000000..5c6ba95af --- /dev/null +++ b/libevmcore/CommonSubexpressionEliminator.cpp @@ -0,0 +1,558 @@ +/* + 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 CommonSubexpressionEliminator.cpp + * @author Christian + * @date 2015 + * Optimizer step for common subexpression elimination and stack reorganisation. + */ + +#include +#include +#include +#include + +using namespace std; +using namespace dev; +using namespace dev::eth; + +vector CommonSubexpressionEliminator::getOptimizedItems() +{ + optimizeBreakingItem(); + + map initialStackContents; + map targetStackContents; + int minHeight = m_stackHeight + 1; + if (!m_stackElements.empty()) + minHeight = min(minHeight, m_stackElements.begin()->first); + for (int height = minHeight; height <= 0; ++height) + initialStackContents[height] = initialStackElement(height); + for (int height = minHeight; height <= m_stackHeight; ++height) + targetStackContents[height] = stackElement(height); + + // Debug info: + //stream(cout, initialStackContents, targetStackContents); + + AssemblyItems items = CSECodeGenerator(m_expressionClasses, m_storeOperations).generateCode( + initialStackContents, + targetStackContents + ); + if (m_breakingItem) + items.push_back(*m_breakingItem); + return items; +} + +ostream& CommonSubexpressionEliminator::stream( + ostream& _out, + map _initialStack, + map _targetStack +) const +{ + auto streamExpressionClass = [this](ostream& _out, ExpressionClasses::Id _id) + { + auto const& expr = m_expressionClasses.representative(_id); + _out << " " << dec << _id << ": " << *expr.item; + if (expr.sequenceNumber) + _out << "@" << dec << expr.sequenceNumber; + _out << "("; + for (ExpressionClasses::Id arg: expr.arguments) + _out << dec << arg << ","; + _out << ")" << endl; + }; + + _out << "Optimizer analysis:" << endl; + _out << "Final stack height: " << dec << m_stackHeight << endl; + _out << "Equivalence classes: " << endl; + for (ExpressionClasses::Id eqClass = 0; eqClass < m_expressionClasses.size(); ++eqClass) + streamExpressionClass(_out, eqClass); + + _out << "Initial stack: " << endl; + for (auto const& it: _initialStack) + { + _out << " " << dec << it.first << ": "; + streamExpressionClass(_out, it.second); + } + _out << "Target stack: " << endl; + for (auto const& it: _targetStack) + { + _out << " " << dec << it.first << ": "; + streamExpressionClass(_out, it.second); + } + + return _out; +} + +void CommonSubexpressionEliminator::feedItem(AssemblyItem const& _item, bool _copyItem) +{ + if (_item.type() != Operation) + { + assertThrow(_item.deposit() == 1, InvalidDeposit, ""); + setStackElement(++m_stackHeight, m_expressionClasses.find(_item, {}, _copyItem)); + } + else + { + Instruction instruction = _item.instruction(); + InstructionInfo info = instructionInfo(instruction); + if (SemanticInformation::isDupInstruction(_item)) + setStackElement( + m_stackHeight + 1, + stackElement(m_stackHeight - int(instruction) + int(Instruction::DUP1)) + ); + else if (SemanticInformation::isSwapInstruction(_item)) + swapStackElements( + m_stackHeight, + m_stackHeight - 1 - int(instruction) + int(Instruction::SWAP1) + ); + else if (instruction != Instruction::POP) + { + vector arguments(info.args); + for (int i = 0; i < info.args; ++i) + arguments[i] = stackElement(m_stackHeight - i); + if (_item.instruction() == Instruction::SSTORE) + storeInStorage(arguments[0], arguments[1]); + else if (_item.instruction() == Instruction::SLOAD) + setStackElement(m_stackHeight + _item.deposit(), loadFromStorage(arguments[0])); + else if (_item.instruction() == Instruction::MSTORE) + storeInMemory(arguments[0], arguments[1]); + else if (_item.instruction() == Instruction::MLOAD) + setStackElement(m_stackHeight + _item.deposit(), loadFromMemory(arguments[0])); + else + setStackElement(m_stackHeight + _item.deposit(), m_expressionClasses.find(_item, arguments, _copyItem)); + } + m_stackHeight += _item.deposit(); + } +} + +void CommonSubexpressionEliminator::optimizeBreakingItem() +{ + if (!m_breakingItem || *m_breakingItem != AssemblyItem(Instruction::JUMPI)) + return; + + using Id = ExpressionClasses::Id; + static AssemblyItem s_jump = Instruction::JUMP; + + Id condition = stackElement(m_stackHeight - 1); + Id zero = m_expressionClasses.find(u256(0)); + if (m_expressionClasses.knownToBeDifferent(condition, zero)) + { + feedItem(Instruction::SWAP1, true); + feedItem(Instruction::POP, true); + m_breakingItem = &s_jump; + return; + } + Id negatedCondition = m_expressionClasses.find(Instruction::ISZERO, {condition}); + if (m_expressionClasses.knownToBeDifferent(negatedCondition, zero)) + { + feedItem(Instruction::POP, true); + feedItem(Instruction::POP, true); + m_breakingItem = nullptr; + } +} + +void CommonSubexpressionEliminator::setStackElement(int _stackHeight, ExpressionClasses::Id _class) +{ + m_stackElements[_stackHeight] = _class; +} + +void CommonSubexpressionEliminator::swapStackElements(int _stackHeightA, int _stackHeightB) +{ + assertThrow(_stackHeightA != _stackHeightB, OptimizerException, "Swap on same stack elements."); + // ensure they are created + stackElement(_stackHeightA); + stackElement(_stackHeightB); + + swap(m_stackElements[_stackHeightA], m_stackElements[_stackHeightB]); +} + +ExpressionClasses::Id CommonSubexpressionEliminator::stackElement(int _stackHeight) +{ + if (m_stackElements.count(_stackHeight)) + return m_stackElements.at(_stackHeight); + // Stack element not found (not assigned yet), create new equivalence class. + return m_stackElements[_stackHeight] = initialStackElement(_stackHeight); +} + +ExpressionClasses::Id CommonSubexpressionEliminator::initialStackElement(int _stackHeight) +{ + assertThrow(_stackHeight <= 0, OptimizerException, "Initial stack element of positive height requested."); + assertThrow(_stackHeight > -16, StackTooDeepException, ""); + // This is a special assembly item that refers to elements pre-existing on the initial stack. + return m_expressionClasses.find(AssemblyItem(dupInstruction(1 - _stackHeight))); +} + +void CommonSubexpressionEliminator::storeInStorage(ExpressionClasses::Id _slot, ExpressionClasses::Id _value) +{ + if (m_storageContent.count(_slot) && m_storageContent[_slot] == _value) + // do not execute the storage if we know that the value is already there + return; + m_sequenceNumber++; + decltype(m_storageContent) storageContents; + // copy over values at points where we know that they are different from _slot + for (auto const& storageItem: m_storageContent) + if (m_expressionClasses.knownToBeDifferent(storageItem.first, _slot)) + storageContents.insert(storageItem); + m_storageContent = move(storageContents); + ExpressionClasses::Id id = m_expressionClasses.find(Instruction::SSTORE, {_slot, _value}, true, m_sequenceNumber); + m_storeOperations.push_back(StoreOperation(StoreOperation::Storage, _slot, m_sequenceNumber, id)); + m_storageContent[_slot] = _value; + // increment a second time so that we get unique sequence numbers for writes + m_sequenceNumber++; +} + +ExpressionClasses::Id CommonSubexpressionEliminator::loadFromStorage(ExpressionClasses::Id _slot) +{ + if (m_storageContent.count(_slot)) + return m_storageContent.at(_slot); + else + return m_storageContent[_slot] = m_expressionClasses.find(Instruction::SLOAD, {_slot}, true, m_sequenceNumber); +} + +void CommonSubexpressionEliminator::storeInMemory(ExpressionClasses::Id _slot, ExpressionClasses::Id _value) +{ + if (m_memoryContent.count(_slot) && m_memoryContent[_slot] == _value) + // do not execute the store if we know that the value is already there + return; + m_sequenceNumber++; + decltype(m_memoryContent) memoryContents; + // copy over values at points where we know that they are different from _slot by at least 32 + for (auto const& memoryItem: m_memoryContent) + if (m_expressionClasses.knownToBeDifferentBy32(memoryItem.first, _slot)) + memoryContents.insert(memoryItem); + m_memoryContent = move(memoryContents); + ExpressionClasses::Id id = m_expressionClasses.find(Instruction::MSTORE, {_slot, _value}, true, m_sequenceNumber); + m_storeOperations.push_back(StoreOperation(StoreOperation::Memory, _slot, m_sequenceNumber, id)); + m_memoryContent[_slot] = _value; + // increment a second time so that we get unique sequence numbers for writes + m_sequenceNumber++; +} + +ExpressionClasses::Id CommonSubexpressionEliminator::loadFromMemory(ExpressionClasses::Id _slot) +{ + if (m_memoryContent.count(_slot)) + return m_memoryContent.at(_slot); + else + return m_memoryContent[_slot] = m_expressionClasses.find(Instruction::MLOAD, {_slot}, true, m_sequenceNumber); +} + +CSECodeGenerator::CSECodeGenerator( + ExpressionClasses& _expressionClasses, + vector const& _storeOperations +): + m_expressionClasses(_expressionClasses) +{ + for (auto const& store: _storeOperations) + m_storeOperations[make_pair(store.target, store.slot)].push_back(store); +} + +AssemblyItems CSECodeGenerator::generateCode( + map const& _initialStack, + map const& _targetStackContents +) +{ + m_stack = _initialStack; + for (auto const& item: m_stack) + if (!m_classPositions.count(item.second)) + m_classPositions[item.second] = item.first; + + // @todo: provide information about the positions of copies of class elements + + // generate the dependency graph starting from final storage and memory writes and target stack contents + for (auto const& p: m_storeOperations) + addDependencies(p.second.back().expression); + for (auto const& targetItem: _targetStackContents) + { + m_finalClasses.insert(targetItem.second); + addDependencies(targetItem.second); + } + + // store all needed sequenced expressions + set> sequencedExpressions; + for (auto const& p: m_neededBy) + for (auto id: {p.first, p.second}) + if (unsigned seqNr = m_expressionClasses.representative(id).sequenceNumber) + sequencedExpressions.insert(make_pair(seqNr, id)); + + // Perform all operations on storage and memory in order, if they are needed. + for (auto const& seqAndId: sequencedExpressions) + if (!m_classPositions.count(seqAndId.second)) + generateClassElement(seqAndId.second, true); + + // generate the target stack elements + for (auto const& targetItem: _targetStackContents) + { + int position = generateClassElement(targetItem.second); + assertThrow(position != c_invalidPosition, OptimizerException, ""); + if (position == targetItem.first) + continue; + if (position < targetItem.first) + // it is already at its target, we need another copy + appendDup(position); + else + appendOrRemoveSwap(position); + appendOrRemoveSwap(targetItem.first); + } + + // remove surplus elements + while (removeStackTopIfPossible()) + { + // no-op + } + + // check validity + int finalHeight = 0; + if (!_targetStackContents.empty()) + // have target stack, so its height should be the final height + finalHeight = (--_targetStackContents.end())->first; + else if (!_initialStack.empty()) + // no target stack, only erase the initial stack + finalHeight = _initialStack.begin()->first - 1; + else + // neither initial no target stack, no change in height + finalHeight = 0; + assertThrow(finalHeight == m_stackHeight, OptimizerException, "Incorrect final stack height."); + return m_generatedItems; +} + +void CSECodeGenerator::addDependencies(ExpressionClasses::Id _c) +{ + if (m_neededBy.count(_c)) + return; // we already computed the dependencies for _c + ExpressionClasses::Expression expr = m_expressionClasses.representative(_c); + for (ExpressionClasses::Id argument: expr.arguments) + { + addDependencies(argument); + m_neededBy.insert(make_pair(argument, _c)); + } + if (expr.item->type() == Operation && ( + expr.item->instruction() == Instruction::SLOAD || + expr.item->instruction() == Instruction::MLOAD + )) + { + // this loads an unknown value from storage or memory and thus, in addition to its + // arguments, depends on all store operations to addresses where we do not know that + // they are different that occur before this load + StoreOperation::Target target = expr.item->instruction() == Instruction::SLOAD ? + StoreOperation::Storage : StoreOperation::Memory; + ExpressionClasses::Id slotToLoadFrom = expr.arguments.at(0); + for (auto const& p: m_storeOperations) + { + if (p.first.first != target) + continue; + ExpressionClasses::Id slot = p.first.second; + StoreOperations const& storeOps = p.second; + if (storeOps.front().sequenceNumber > expr.sequenceNumber) + continue; + if ( + (target == StoreOperation::Memory && m_expressionClasses.knownToBeDifferentBy32(slot, slotToLoadFrom)) || + (target == StoreOperation::Storage && m_expressionClasses.knownToBeDifferent(slot, slotToLoadFrom)) + ) + continue; + // note that store and load never have the same sequence number + ExpressionClasses::Id latestStore = storeOps.front().expression; + for (auto it = ++storeOps.begin(); it != storeOps.end(); ++it) + if (it->sequenceNumber < expr.sequenceNumber) + latestStore = it->expression; + addDependencies(latestStore); + m_neededBy.insert(make_pair(latestStore, _c)); + } + } +} + +int CSECodeGenerator::generateClassElement(ExpressionClasses::Id _c, bool _allowSequenced) +{ + // do some cleanup + removeStackTopIfPossible(); + + if (m_classPositions.count(_c)) + { + assertThrow( + m_classPositions[_c] != c_invalidPosition, + OptimizerException, + "Element already removed but still needed." + ); + return m_classPositions[_c]; + } + ExpressionClasses::Expression const& expr = m_expressionClasses.representative(_c); + assertThrow( + _allowSequenced || expr.sequenceNumber == 0, + OptimizerException, + "Sequence constrained operation requested out of sequence." + ); + ExpressionClasses::Ids const& arguments = expr.arguments; + for (ExpressionClasses::Id arg: boost::adaptors::reverse(arguments)) + generateClassElement(arg); + + // The arguments are somewhere on the stack now, so it remains to move them at the correct place. + // This is quite difficult as sometimes, the values also have to removed in this process + // (if canBeRemoved() returns true) and the two arguments can be equal. For now, this is + // implemented for every single case for combinations of up to two arguments manually. + if (arguments.size() == 1) + { + if (canBeRemoved(arguments[0], _c)) + appendOrRemoveSwap(classElementPosition(arguments[0])); + else + appendDup(classElementPosition(arguments[0])); + } + else if (arguments.size() == 2) + { + if (canBeRemoved(arguments[1], _c)) + { + appendOrRemoveSwap(classElementPosition(arguments[1])); + if (arguments[0] == arguments[1]) + appendDup(m_stackHeight); + else if (canBeRemoved(arguments[0], _c)) + { + appendOrRemoveSwap(m_stackHeight - 1); + appendOrRemoveSwap(classElementPosition(arguments[0])); + } + else + appendDup(classElementPosition(arguments[0])); + } + else + { + if (arguments[0] == arguments[1]) + { + appendDup(classElementPosition(arguments[0])); + appendDup(m_stackHeight); + } + else if (canBeRemoved(arguments[0], _c)) + { + appendOrRemoveSwap(classElementPosition(arguments[0])); + appendDup(classElementPosition(arguments[1])); + appendOrRemoveSwap(m_stackHeight - 1); + } + else + { + appendDup(classElementPosition(arguments[1])); + appendDup(classElementPosition(arguments[0])); + } + } + } + else + assertThrow( + arguments.size() <= 2, + OptimizerException, + "Opcodes with more than two arguments not implemented yet." + ); + for (size_t i = 0; i < arguments.size(); ++i) + assertThrow(m_stack[m_stackHeight - i] == arguments[i], OptimizerException, "Expected arguments not present." ); + + while (SemanticInformation::isCommutativeOperation(*expr.item) && + !m_generatedItems.empty() && + m_generatedItems.back() == AssemblyItem(Instruction::SWAP1)) + // this will not append a swap but remove the one that is already there + appendOrRemoveSwap(m_stackHeight - 1); + for (auto arg: arguments) + if (canBeRemoved(arg, _c)) + m_classPositions[arg] = c_invalidPosition; + for (size_t i = 0; i < arguments.size(); ++i) + m_stack.erase(m_stackHeight - i); + appendItem(*expr.item); + if (expr.item->type() != Operation || instructionInfo(expr.item->instruction()).ret == 1) + { + m_stack[m_stackHeight] = _c; + return m_classPositions[_c] = m_stackHeight; + } + else + { + assertThrow( + instructionInfo(expr.item->instruction()).ret == 0, + OptimizerException, + "Invalid number of return values." + ); + return m_classPositions[_c] = c_invalidPosition; + } +} + +int CSECodeGenerator::classElementPosition(ExpressionClasses::Id _id) const +{ + assertThrow( + m_classPositions.count(_id) && m_classPositions.at(_id) != c_invalidPosition, + OptimizerException, + "Element requested but is not present." + ); + return m_classPositions.at(_id); +} + +bool CSECodeGenerator::canBeRemoved(ExpressionClasses::Id _element, ExpressionClasses::Id _result) +{ + // Returns false if _element is finally needed or is needed by a class that has not been + // computed yet. Note that m_classPositions also includes classes that were deleted in the meantime. + if (m_finalClasses.count(_element)) + return false; + + auto range = m_neededBy.equal_range(_element); + for (auto it = range.first; it != range.second; ++it) + if (it->second != _result && !m_classPositions.count(it->second)) + return false; + return true; +} + +bool CSECodeGenerator::removeStackTopIfPossible() +{ + if (m_stack.empty()) + return false; + assertThrow(m_stack.count(m_stackHeight), OptimizerException, ""); + ExpressionClasses::Id top = m_stack[m_stackHeight]; + if (!canBeRemoved(top)) + return false; + m_generatedItems.push_back(AssemblyItem(Instruction::POP)); + m_stack.erase(m_stackHeight); + m_stackHeight--; + return true; +} + +void CSECodeGenerator::appendDup(int _fromPosition) +{ + assertThrow(_fromPosition != c_invalidPosition, OptimizerException, ""); + int instructionNum = 1 + m_stackHeight - _fromPosition; + assertThrow(instructionNum <= 16, StackTooDeepException, "Stack too deep."); + assertThrow(1 <= instructionNum, OptimizerException, "Invalid stack access."); + appendItem(AssemblyItem(dupInstruction(instructionNum))); + m_stack[m_stackHeight] = m_stack[_fromPosition]; +} + +void CSECodeGenerator::appendOrRemoveSwap(int _fromPosition) +{ + assertThrow(_fromPosition != c_invalidPosition, OptimizerException, ""); + if (_fromPosition == m_stackHeight) + return; + int instructionNum = m_stackHeight - _fromPosition; + assertThrow(instructionNum <= 16, StackTooDeepException, "Stack too deep."); + assertThrow(1 <= instructionNum, OptimizerException, "Invalid stack access."); + appendItem(AssemblyItem(swapInstruction(instructionNum))); + // The value of a class can be present in multiple locations on the stack. We only update the + // "canonical" one that is tracked by m_classPositions + if (m_classPositions[m_stack[m_stackHeight]] == m_stackHeight) + m_classPositions[m_stack[m_stackHeight]] = _fromPosition; + if (m_classPositions[m_stack[_fromPosition]] == _fromPosition) + m_classPositions[m_stack[_fromPosition]] = m_stackHeight; + swap(m_stack[m_stackHeight], m_stack[_fromPosition]); + if (m_generatedItems.size() >= 2 && + SemanticInformation::isSwapInstruction(m_generatedItems.back()) && + *(m_generatedItems.end() - 2) == m_generatedItems.back()) + { + m_generatedItems.pop_back(); + m_generatedItems.pop_back(); + } +} + +void CSECodeGenerator::appendItem(AssemblyItem const& _item) +{ + m_generatedItems.push_back(_item); + m_stackHeight += _item.deposit(); +} diff --git a/libevmcore/CommonSubexpressionEliminator.h b/libevmcore/CommonSubexpressionEliminator.h new file mode 100644 index 000000000..0dbb47b29 --- /dev/null +++ b/libevmcore/CommonSubexpressionEliminator.h @@ -0,0 +1,228 @@ +/* + 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 CommonSubexpressionEliminator.h + * @author Christian + * @date 2015 + * Optimizer step for common subexpression elimination and stack reorganisation. + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace dev +{ +namespace eth +{ + +class AssemblyItem; +using AssemblyItems = std::vector; + +/** + * Optimizer step that performs common subexpression elimination and stack reorganisation, + * i.e. it tries to infer equality among expressions and compute the values of two expressions + * known to be equal only once. + * + * The general workings are that for each assembly item that is fed into the eliminator, an + * equivalence class is derived from the operation and the equivalence class of its arguments. + * DUPi, SWAPi and some arithmetic instructions are used to infer equivalences while these + * classes are determined. + * + * When the list of optimized items is requested, they are generated in a bottom-up fashion, + * adding code for equivalence classes that were not yet computed. + */ +class CommonSubexpressionEliminator +{ +public: + struct StoreOperation + { + enum Target { Memory, Storage }; + StoreOperation( + Target _target, + ExpressionClasses::Id _slot, + unsigned _sequenceNumber, + ExpressionClasses::Id _expression + ): target(_target), slot(_slot), sequenceNumber(_sequenceNumber), expression(_expression) {} + Target target; + ExpressionClasses::Id slot; + unsigned sequenceNumber; + ExpressionClasses::Id expression; + }; + + /// Feeds AssemblyItems into the eliminator and @returns the iterator pointing at the first + /// item that must be fed into a new instance of the eliminator. + template + _AssemblyItemIterator feedItems(_AssemblyItemIterator _iterator, _AssemblyItemIterator _end); + + /// @returns the resulting items after optimization. + AssemblyItems getOptimizedItems(); + + /// Streams debugging information to @a _out. + std::ostream& stream( + std::ostream& _out, + std::map _initialStack = std::map(), + std::map _targetStack = std::map() + ) const; + +private: + /// Feeds the item into the system for analysis. + void feedItem(AssemblyItem const& _item, bool _copyItem = false); + + /// Tries to optimize the item that breaks the basic block at the end. + void optimizeBreakingItem(); + + /// Simplifies the given item using + /// Assigns a new equivalence class to the next sequence number of the given stack element. + void setStackElement(int _stackHeight, ExpressionClasses::Id _class); + /// Swaps the given stack elements in their next sequence number. + void swapStackElements(int _stackHeightA, int _stackHeightB); + /// Retrieves the current equivalence class fo the given stack element (or generates a new + /// one if it does not exist yet). + ExpressionClasses::Id stackElement(int _stackHeight); + /// @returns the equivalence class id of the special initial stack element at the given height + /// (must not be positive). + ExpressionClasses::Id initialStackElement(int _stackHeight); + + /// Increments the sequence number, deletes all storage information that might be overwritten + /// and stores the new value at the given slot. + void storeInStorage(ExpressionClasses::Id _slot, ExpressionClasses::Id _value); + /// Retrieves the current value at the given slot in storage or creates a new special sload class. + ExpressionClasses::Id loadFromStorage(ExpressionClasses::Id _slot); + /// Increments the sequence number, deletes all memory information that might be overwritten + /// and stores the new value at the given slot. + void storeInMemory(ExpressionClasses::Id _slot, ExpressionClasses::Id _value); + /// Retrieves the current value at the given slot in memory or creates a new special mload class. + ExpressionClasses::Id loadFromMemory(ExpressionClasses::Id _slot); + + + /// Current stack height, can be negative. + int m_stackHeight = 0; + /// Current stack layout, mapping stack height -> equivalence class + std::map m_stackElements; + /// Current sequence number, this is incremented with each modification to storage or memory. + unsigned m_sequenceNumber = 1; + /// Knowledge about storage content. + std::map m_storageContent; + /// Knowledge about memory content. Keys are memory addresses, note that the values overlap + /// and are not contained here if they are not completely known. + std::map m_memoryContent; + /// Keeps information about which storage or memory slots were written to at which sequence + /// number with what instruction. + std::vector m_storeOperations; + /// Structure containing the classes of equivalent expressions. + ExpressionClasses m_expressionClasses; + + /// The item that breaks the basic block, can be nullptr. + /// It is usually appended to the block but can be optimized in some cases. + AssemblyItem const* m_breakingItem = nullptr; +}; + +/** + * Unit that generates code from current stack layout, target stack layout and information about + * the equivalence classes. + */ +class CSECodeGenerator +{ +public: + using StoreOperation = CommonSubexpressionEliminator::StoreOperation; + using StoreOperations = std::vector; + + /// Initializes the code generator with the given classes and store operations. + /// The store operations have to be sorted by sequence number in ascending order. + CSECodeGenerator(ExpressionClasses& _expressionClasses, StoreOperations const& _storeOperations); + + /// @returns the assembly items generated from the given requirements + /// @param _initialStack current contents of the stack (up to stack height of zero) + /// @param _targetStackContents final contents of the stack, by stack height relative to initial + /// @note should only be called once on each object. + AssemblyItems generateCode( + std::map const& _initialStack, + std::map const& _targetStackContents + ); + +private: + /// Recursively discovers all dependencies to @a m_requests. + void addDependencies(ExpressionClasses::Id _c); + + /// Produce code that generates the given element if it is not yet present. + /// @returns the stack position of the element or c_invalidPosition if it does not actually + /// generate a value on the stack. + /// @param _allowSequenced indicates that sequence-constrained operations are allowed + int generateClassElement(ExpressionClasses::Id _c, bool _allowSequenced = false); + /// @returns the position of the representative of the given id on the stack. + /// @note throws an exception if it is not on the stack. + int classElementPosition(ExpressionClasses::Id _id) const; + + /// @returns true if @a _element can be removed - in general or, if given, while computing @a _result. + bool canBeRemoved(ExpressionClasses::Id _element, ExpressionClasses::Id _result = ExpressionClasses::Id(-1)); + + /// Appends code to remove the topmost stack element if it can be removed. + bool removeStackTopIfPossible(); + + /// Appends a dup instruction to m_generatedItems to retrieve the element at the given stack position. + void appendDup(int _fromPosition); + /// Appends a swap instruction to m_generatedItems to retrieve the element at the given stack position. + /// @note this might also remove the last item if it exactly the same swap instruction. + void appendOrRemoveSwap(int _fromPosition); + /// Appends the given assembly item. + void appendItem(AssemblyItem const& _item); + + static const int c_invalidPosition = -0x7fffffff; + + AssemblyItems m_generatedItems; + /// Current height of the stack relative to the start. + int m_stackHeight = 0; + /// If (b, a) is in m_requests then b is needed to compute a. + std::multimap m_neededBy; + /// Current content of the stack. + std::map m_stack; + /// Current positions of equivalence classes, equal to c_invalidPosition if already deleted. + std::map m_classPositions; + + /// The actual eqivalence class items and how to compute them. + ExpressionClasses& m_expressionClasses; + /// Keeps information about which storage or memory slots were written to by which operations. + /// The operations are sorted ascendingly by sequence number. + std::map, StoreOperations> m_storeOperations; + /// The set of equivalence classes that should be present on the stack at the end. + std::set m_finalClasses; +}; + +template +_AssemblyItemIterator CommonSubexpressionEliminator::feedItems( + _AssemblyItemIterator _iterator, + _AssemblyItemIterator _end +) +{ + for (; _iterator != _end && !SemanticInformation::breaksCSEAnalysisBlock(*_iterator); ++_iterator) + feedItem(*_iterator); + if (_iterator != _end) + m_breakingItem = &(*_iterator++); + return _iterator; +} + +} +} diff --git a/libevmcore/Exceptions.h b/libevmcore/Exceptions.h index 57e1ac9c6..fa3c19f13 100644 --- a/libevmcore/Exceptions.h +++ b/libevmcore/Exceptions.h @@ -31,6 +31,8 @@ namespace eth struct AssemblyException: virtual Exception {}; struct InvalidDeposit: virtual AssemblyException {}; struct InvalidOpcode: virtual AssemblyException {}; +struct OptimizerException: virtual AssemblyException {}; +struct StackTooDeepException: virtual OptimizerException {}; } } diff --git a/libevmcore/ExpressionClasses.cpp b/libevmcore/ExpressionClasses.cpp new file mode 100644 index 000000000..1c7a79e6b --- /dev/null +++ b/libevmcore/ExpressionClasses.cpp @@ -0,0 +1,419 @@ +/* + 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 ExpressionClasses.cpp + * @author Christian + * @date 2015 + * Container for equivalence classes of expressions for use in common subexpression elimination. + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace std; +using namespace dev; +using namespace dev::eth; + + +bool ExpressionClasses::Expression::operator<(ExpressionClasses::Expression const& _other) const +{ + auto type = item->type(); + auto otherType = _other.item->type(); + return std::tie(type, item->data(), arguments, sequenceNumber) < + std::tie(otherType, _other.item->data(), _other.arguments, _other.sequenceNumber); +} + +ExpressionClasses::Id ExpressionClasses::find( + AssemblyItem const& _item, + Ids const& _arguments, + bool _copyItem, + unsigned _sequenceNumber +) +{ + Expression exp; + exp.id = Id(-1); + exp.item = &_item; + exp.arguments = _arguments; + exp.sequenceNumber = _sequenceNumber; + + if (SemanticInformation::isCommutativeOperation(_item)) + sort(exp.arguments.begin(), exp.arguments.end()); + + auto it = m_expressions.find(exp); + if (it != m_expressions.end()) + return it->id; + + if (_copyItem) + { + m_spareAssemblyItem.push_back(make_shared(_item)); + exp.item = m_spareAssemblyItem.back().get(); + } + + ExpressionClasses::Id id = tryToSimplify(exp); + if (id < m_representatives.size()) + exp.id = id; + else + { + exp.id = m_representatives.size(); + m_representatives.push_back(exp); + } + m_expressions.insert(exp); + return exp.id; +} + +bool ExpressionClasses::knownToBeDifferent(ExpressionClasses::Id _a, ExpressionClasses::Id _b) +{ + // Try to simplify "_a - _b" and return true iff the value is a non-zero constant. + map matchGroups; + Pattern constant(Push); + constant.setMatchGroup(1, matchGroups); + Id difference = find(Instruction::SUB, {_a, _b}); + return constant.matches(representative(difference), *this) && constant.d() != u256(0); +} + +bool ExpressionClasses::knownToBeDifferentBy32(ExpressionClasses::Id _a, ExpressionClasses::Id _b) +{ + // Try to simplify "_a - _b" and return true iff the value is at least 32 away from zero. + map matchGroups; + Pattern constant(Push); + constant.setMatchGroup(1, matchGroups); + Id difference = find(Instruction::SUB, {_a, _b}); + if (!constant.matches(representative(difference), *this)) + return false; + // forbidden interval is ["-31", 31] + return constant.d() + 31 > u256(62); +} + +string ExpressionClasses::fullDAGToString(ExpressionClasses::Id _id) const +{ + Expression const& expr = representative(_id); + stringstream str; + str << dec << expr.id << ":" << *expr.item << "("; + for (Id arg: expr.arguments) + str << fullDAGToString(arg) << ","; + str << ")"; + return str.str(); +} + +class Rules: public boost::noncopyable +{ +public: + Rules(); + void resetMatchGroups() { m_matchGroups.clear(); } + vector>> rules() const { return m_rules; } + +private: + using Expression = ExpressionClasses::Expression; + map m_matchGroups; + vector>> m_rules; +}; + +Rules::Rules() +{ + // Multiple occurences of one of these inside one rule must match the same equivalence class. + // Constants. + Pattern A(Push); + Pattern B(Push); + Pattern C(Push); + // Anything. + Pattern X; + Pattern Y; + Pattern Z; + A.setMatchGroup(1, m_matchGroups); + B.setMatchGroup(2, m_matchGroups); + C.setMatchGroup(3, m_matchGroups); + X.setMatchGroup(4, m_matchGroups); + Y.setMatchGroup(5, m_matchGroups); + Z.setMatchGroup(6, m_matchGroups); + + m_rules = vector>>{ + // arithmetics on constants + {{Instruction::ADD, {A, B}}, [=]{ return A.d() + B.d(); }}, + {{Instruction::MUL, {A, B}}, [=]{ return A.d() * B.d(); }}, + {{Instruction::SUB, {A, B}}, [=]{ return A.d() - B.d(); }}, + {{Instruction::DIV, {A, B}}, [=]{ return B.d() == 0 ? 0 : A.d() / B.d(); }}, + {{Instruction::SDIV, {A, B}}, [=]{ return B.d() == 0 ? 0 : s2u(u2s(A.d()) / u2s(B.d())); }}, + {{Instruction::MOD, {A, B}}, [=]{ return B.d() == 0 ? 0 : A.d() % B.d(); }}, + {{Instruction::SMOD, {A, B}}, [=]{ return B.d() == 0 ? 0 : s2u(u2s(A.d()) % u2s(B.d())); }}, + {{Instruction::EXP, {A, B}}, [=]{ return u256(boost::multiprecision::powm(bigint(A.d()), bigint(B.d()), bigint(1) << 256)); }}, + {{Instruction::NOT, {A}}, [=]{ return ~A.d(); }}, + {{Instruction::LT, {A, B}}, [=]() { return A.d() < B.d() ? u256(1) : 0; }}, + {{Instruction::GT, {A, B}}, [=]() -> u256 { return A.d() > B.d() ? 1 : 0; }}, + {{Instruction::SLT, {A, B}}, [=]() -> u256 { return u2s(A.d()) < u2s(B.d()) ? 1 : 0; }}, + {{Instruction::SGT, {A, B}}, [=]() -> u256 { return u2s(A.d()) > u2s(B.d()) ? 1 : 0; }}, + {{Instruction::EQ, {A, B}}, [=]() -> u256 { return A.d() == B.d() ? 1 : 0; }}, + {{Instruction::ISZERO, {A}}, [=]() -> u256 { return A.d() == 0 ? 1 : 0; }}, + {{Instruction::AND, {A, B}}, [=]{ return A.d() & B.d(); }}, + {{Instruction::OR, {A, B}}, [=]{ return A.d() | B.d(); }}, + {{Instruction::XOR, {A, B}}, [=]{ return A.d() ^ B.d(); }}, + {{Instruction::BYTE, {A, B}}, [=]{ return A.d() >= 32 ? 0 : (B.d() >> unsigned(8 * (31 - A.d()))) & 0xff; }}, + {{Instruction::ADDMOD, {A, B, C}}, [=]{ return C.d() == 0 ? 0 : u256((bigint(A.d()) + bigint(B.d())) % C.d()); }}, + {{Instruction::MULMOD, {A, B, C}}, [=]{ return C.d() == 0 ? 0 : u256((bigint(A.d()) * bigint(B.d())) % C.d()); }}, + {{Instruction::MULMOD, {A, B, C}}, [=]{ return A.d() * B.d(); }}, + {{Instruction::SIGNEXTEND, {A, B}}, [=]() -> u256 { + if (A.d() >= 31) + return B.d(); + unsigned testBit = unsigned(A.d()) * 8 + 7; + u256 mask = (u256(1) << testBit) - 1; + return u256(boost::multiprecision::bit_test(B.d(), testBit) ? B.d() | ~mask : B.d() & mask); + }}, + + // invariants involving known constants + {{Instruction::ADD, {X, 0}}, [=]{ return X; }}, + {{Instruction::MUL, {X, 1}}, [=]{ return X; }}, + {{Instruction::DIV, {X, 1}}, [=]{ return X; }}, + {{Instruction::SDIV, {X, 1}}, [=]{ return X; }}, + {{Instruction::OR, {X, 0}}, [=]{ return X; }}, + {{Instruction::XOR, {X, 0}}, [=]{ return X; }}, + {{Instruction::AND, {X, ~u256(0)}}, [=]{ return X; }}, + {{Instruction::MUL, {X, 0}}, [=]{ return u256(0); }}, + {{Instruction::DIV, {X, 0}}, [=]{ return u256(0); }}, + {{Instruction::MOD, {X, 0}}, [=]{ return u256(0); }}, + {{Instruction::MOD, {0, X}}, [=]{ return u256(0); }}, + {{Instruction::AND, {X, 0}}, [=]{ return u256(0); }}, + {{Instruction::OR, {X, ~u256(0)}}, [=]{ return ~u256(0); }}, + // operations involving an expression and itself + {{Instruction::AND, {X, X}}, [=]{ return X; }}, + {{Instruction::OR, {X, X}}, [=]{ return X; }}, + {{Instruction::SUB, {X, X}}, [=]{ return u256(0); }}, + {{Instruction::EQ, {X, X}}, [=]{ return u256(1); }}, + {{Instruction::LT, {X, X}}, [=]{ return u256(0); }}, + {{Instruction::SLT, {X, X}}, [=]{ return u256(0); }}, + {{Instruction::GT, {X, X}}, [=]{ return u256(0); }}, + {{Instruction::SGT, {X, X}}, [=]{ return u256(0); }}, + {{Instruction::MOD, {X, X}}, [=]{ return u256(0); }}, + + {{Instruction::NOT, {{Instruction::NOT, {X}}}}, [=]{ return X; }}, + }; + // Associative operations + for (auto const& opFun: vector>>{ + {Instruction::ADD, plus()}, + {Instruction::MUL, multiplies()}, + {Instruction::AND, bit_and()}, + {Instruction::OR, bit_or()}, + {Instruction::XOR, bit_xor()} + }) + { + auto op = opFun.first; + auto fun = opFun.second; + // Moving constants to the outside, order matters here! + // we need actions that return expressions (or patterns?) here, and we need also reversed rules + // (X+A)+B -> X+(A+B) + m_rules += vector>>{{ + {op, {{op, {X, A}}, B}}, + [=]() -> Pattern { return {op, {X, fun(A.d(), B.d())}}; } + }, { + // X+(Y+A) -> (X+Y)+A + {op, {{op, {X, A}}, Y}}, + [=]() -> Pattern { return {op, {{op, {X, Y}}, A}}; } + }, { + // For now, we still need explicit commutativity for the inner pattern + {op, {{op, {A, X}}, B}}, + [=]() -> Pattern { return {op, {X, fun(A.d(), B.d())}}; } + }, { + {op, {{op, {A, X}}, Y}}, + [=]() -> Pattern { return {op, {{op, {X, Y}}, A}}; } + }}; + } + // move constants across subtractions + m_rules += vector>>{ + { + // X - A -> X + (-A) + {Instruction::SUB, {X, A}}, + [=]() -> Pattern { return {Instruction::ADD, {X, 0 - A.d()}}; } + }, { + // (X + A) - Y -> (X - Y) + A + {Instruction::SUB, {{Instruction::ADD, {X, A}}, Y}}, + [=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, A}}; } + }, { + // (A + X) - Y -> (X - Y) + A + {Instruction::SUB, {{Instruction::ADD, {A, X}}, Y}}, + [=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, A}}; } + }, { + // X - (Y + A) -> (X - Y) + (-A) + {Instruction::SUB, {X, {Instruction::ADD, {Y, A}}}}, + [=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, 0 - A.d()}}; } + }, { + // X - (A + Y) -> (X - Y) + (-A) + {Instruction::SUB, {X, {Instruction::ADD, {A, Y}}}}, + [=]() -> Pattern { return {Instruction::ADD, {{Instruction::SUB, {X, Y}}, 0 - A.d()}}; } + } + }; +} + +ExpressionClasses::Id ExpressionClasses::tryToSimplify(Expression const& _expr, bool _secondRun) +{ + static Rules rules; + + if (_expr.item->type() != Operation) + return -1; + + for (auto const& rule: rules.rules()) + { + rules.resetMatchGroups(); + if (rule.first.matches(_expr, *this)) + { + // Debug info + //cout << "Simplifying " << *_expr.item << "("; + //for (Id arg: _expr.arguments) + // cout << fullDAGToString(arg) << ", "; + //cout << ")" << endl; + //cout << "with rule " << rule.first.toString() << endl; + //ExpressionTemplate t(rule.second()); + //cout << "to " << rule.second().toString() << endl; + return rebuildExpression(ExpressionTemplate(rule.second())); + } + } + + if (!_secondRun && _expr.arguments.size() == 2 && SemanticInformation::isCommutativeOperation(*_expr.item)) + { + Expression expr = _expr; + swap(expr.arguments[0], expr.arguments[1]); + return tryToSimplify(expr, true); + } + + return -1; +} + +ExpressionClasses::Id ExpressionClasses::rebuildExpression(ExpressionTemplate const& _template) +{ + if (_template.hasId) + return _template.id; + + Ids arguments; + for (ExpressionTemplate const& t: _template.arguments) + arguments.push_back(rebuildExpression(t)); + return find(_template.item, arguments); +} + + +Pattern::Pattern(Instruction _instruction, std::vector const& _arguments): + m_type(Operation), + m_requireDataMatch(true), + m_data(_instruction), + m_arguments(_arguments) +{ +} + +void Pattern::setMatchGroup(unsigned _group, map& _matchGroups) +{ + m_matchGroup = _group; + m_matchGroups = &_matchGroups; +} + +bool Pattern::matches(Expression const& _expr, ExpressionClasses const& _classes) const +{ + if (!matchesBaseItem(*_expr.item)) + return false; + if (m_matchGroup) + { + if (!m_matchGroups->count(m_matchGroup)) + (*m_matchGroups)[m_matchGroup] = &_expr; + else if ((*m_matchGroups)[m_matchGroup]->id != _expr.id) + return false; + } + assertThrow(m_arguments.size() == 0 || _expr.arguments.size() == m_arguments.size(), OptimizerException, ""); + for (size_t i = 0; i < m_arguments.size(); ++i) + if (!m_arguments[i].matches(_classes.representative(_expr.arguments[i]), _classes)) + return false; + return true; +} + +string Pattern::toString() const +{ + stringstream s; + switch (m_type) + { + case Operation: + s << instructionInfo(Instruction(unsigned(m_data))).name; + break; + case Push: + s << "PUSH " << hex << m_data; + break; + case UndefinedItem: + s << "ANY"; + break; + default: + s << "t=" << dec << m_type << " d=" << hex << m_data; + break; + } + if (!m_requireDataMatch) + s << " ~"; + if (m_matchGroup) + s << "[" << dec << m_matchGroup << "]"; + s << "("; + for (Pattern const& p: m_arguments) + s << p.toString() << ", "; + s << ")"; + return s.str(); +} + +bool Pattern::matchesBaseItem(AssemblyItem const& _item) const +{ + if (m_type == UndefinedItem) + return true; + if (m_type != _item.type()) + return false; + if (m_requireDataMatch && m_data != _item.data()) + return false; + return true; +} + +Pattern::Expression const& Pattern::matchGroupValue() const +{ + assertThrow(m_matchGroup > 0, OptimizerException, ""); + assertThrow(!!m_matchGroups, OptimizerException, ""); + assertThrow((*m_matchGroups)[m_matchGroup], OptimizerException, ""); + return *(*m_matchGroups)[m_matchGroup]; +} + + +ExpressionTemplate::ExpressionTemplate(Pattern const& _pattern) +{ + if (_pattern.matchGroup()) + { + hasId = true; + id = _pattern.id(); + } + else + { + hasId = false; + item = _pattern.toAssemblyItem(); + } + for (auto const& arg: _pattern.arguments()) + arguments.push_back(ExpressionTemplate(arg)); +} + +string ExpressionTemplate::toString() const +{ + stringstream s; + if (hasId) + s << id; + else + s << item; + s << "("; + for (auto const& arg: arguments) + s << arg.toString(); + s << ")"; + return s.str(); +} diff --git a/libevmcore/ExpressionClasses.h b/libevmcore/ExpressionClasses.h new file mode 100644 index 000000000..5179845f6 --- /dev/null +++ b/libevmcore/ExpressionClasses.h @@ -0,0 +1,168 @@ +/* + 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 ExpressionClasses.h + * @author Christian + * @date 2015 + * Container for equivalence classes of expressions for use in common subexpression elimination. + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace dev +{ +namespace eth +{ + +class Pattern; +struct ExpressionTemplate; + +/** + * Collection of classes of equivalent expressions that can also determine the class of an expression. + * Identifiers are contiguously assigned to new classes starting from zero. + */ +class ExpressionClasses +{ +public: + using Id = unsigned; + using Ids = std::vector; + + struct Expression + { + Id id; + AssemblyItem const* item; + Ids arguments; + unsigned sequenceNumber; ///< Storage modification sequence, only used for SLOAD/SSTORE instructions. + /// Behaves as if this was a tuple of (item->type(), item->data(), arguments, sequenceNumber). + bool operator<(Expression const& _other) const; + }; + + /// Retrieves the id of the expression equivalence class resulting from the given item applied to the + /// given classes, might also create a new one. + /// @param _copyItem if true, copies the assembly item to an internal storage instead of just + /// keeping a pointer. + /// The @a _sequenceNumber indicates the current storage or memory access sequence. + Id find( + AssemblyItem const& _item, + Ids const& _arguments = {}, + bool _copyItem = true, + unsigned _sequenceNumber = 0 + ); + /// @returns the canonical representative of an expression class. + Expression const& representative(Id _id) const { return m_representatives.at(_id); } + /// @returns the number of classes. + Id size() const { return m_representatives.size(); } + + /// @returns true if the values of the given classes are known to be different (on every input). + /// @note that this function might still return false for some different inputs. + bool knownToBeDifferent(Id _a, Id _b); + /// Similar to @a knownToBeDifferent but require that abs(_a - b) >= 32. + bool knownToBeDifferentBy32(Id _a, Id _b); + + std::string fullDAGToString(Id _id) const; + +private: + /// Tries to simplify the given expression. + /// @returns its class if it possible or Id(-1) otherwise. + /// @param _secondRun is set to true for the second run where arguments of commutative expressions are reversed + Id tryToSimplify(Expression const& _expr, bool _secondRun = false); + + /// Rebuilds an expression from a (matched) pattern. + Id rebuildExpression(ExpressionTemplate const& _template); + + std::vector>> createRules() const; + + /// Expression equivalence class representatives - we only store one item of an equivalence. + std::vector m_representatives; + /// All expression ever encountered. + std::set m_expressions; + std::vector> m_spareAssemblyItem; +}; + +/** + * Pattern to match against an expression. + * Also stores matched expressions to retrieve them later, for constructing new expressions using + * ExpressionTemplate. + */ +class Pattern +{ +public: + using Expression = ExpressionClasses::Expression; + using Id = ExpressionClasses::Id; + + // Matches a specific constant value. + Pattern(unsigned _value): Pattern(u256(_value)) {} + // Matches a specific constant value. + Pattern(u256 const& _value): m_type(Push), m_requireDataMatch(true), m_data(_value) {} + // Matches a specific assembly item type or anything if not given. + Pattern(AssemblyItemType _type = UndefinedItem): m_type(_type) {} + // Matches a given instruction with given arguments + Pattern(Instruction _instruction, std::vector const& _arguments = {}); + /// Sets this pattern to be part of the match group with the identifier @a _group. + /// Inside one rule, all patterns in the same match group have to match expressions from the + /// same expression equivalence class. + void setMatchGroup(unsigned _group, std::map& _matchGroups); + unsigned matchGroup() const { return m_matchGroup; } + bool matches(Expression const& _expr, ExpressionClasses const& _classes) const; + + AssemblyItem toAssemblyItem() const { return AssemblyItem(m_type, m_data); } + std::vector arguments() const { return m_arguments; } + + /// @returns the id of the matched expression if this pattern is part of a match group. + Id id() const { return matchGroupValue().id; } + /// @returns the data of the matched expression if this pattern is part of a match group. + u256 d() const { return matchGroupValue().item->data(); } + + std::string toString() const; + +private: + bool matchesBaseItem(AssemblyItem const& _item) const; + Expression const& matchGroupValue() const; + + AssemblyItemType m_type; + bool m_requireDataMatch = false; + u256 m_data = 0; + std::vector m_arguments; + unsigned m_matchGroup = 0; + std::map* m_matchGroups = nullptr; +}; + +/** + * Template for a new expression that can be built from matched patterns. + */ +struct ExpressionTemplate +{ + using Expression = ExpressionClasses::Expression; + using Id = ExpressionClasses::Id; + explicit ExpressionTemplate(Pattern const& _pattern); + std::string toString() const; + bool hasId = false; + /// Id of the matched expression, if available. + Id id = Id(-1); + // Otherwise, assembly item. + AssemblyItem item = UndefinedItem; + std::vector arguments; +}; + +} +} diff --git a/libevmcore/Instruction.h b/libevmcore/Instruction.h index 9eab92713..07c7b52fd 100644 --- a/libevmcore/Instruction.h +++ b/libevmcore/Instruction.h @@ -22,6 +22,7 @@ #pragma once #include +#include #include namespace dev @@ -194,32 +195,28 @@ inline unsigned getSwapNumber(Instruction _inst) /// @returns the PUSH<_number> instruction inline Instruction pushInstruction(unsigned _number) { - if (asserts(1 <= _number && _number <= 32)) - BOOST_THROW_EXCEPTION(InvalidOpcode() << errinfo_comment("Invalid PUSH instruction requested.")); + assertThrow(1 <= _number && _number <= 32, InvalidOpcode, "Invalid PUSH instruction requested."); return Instruction(unsigned(Instruction::PUSH1) + _number - 1); } /// @returns the DUP<_number> instruction inline Instruction dupInstruction(unsigned _number) { - if (asserts(1 <= _number && _number <= 16)) - BOOST_THROW_EXCEPTION(InvalidOpcode() << errinfo_comment("Invalid DUP instruction requested.")); + assertThrow(1 <= _number && _number <= 16, InvalidOpcode, "Invalid DUP instruction requested."); return Instruction(unsigned(Instruction::DUP1) + _number - 1); } /// @returns the SWAP<_number> instruction inline Instruction swapInstruction(unsigned _number) { - if (asserts(1 <= _number && _number <= 16)) - BOOST_THROW_EXCEPTION(InvalidOpcode() << errinfo_comment("Invalid SWAP instruction requested.")); + assertThrow(1 <= _number && _number <= 16, InvalidOpcode, "Invalid SWAP instruction requested."); return Instruction(unsigned(Instruction::SWAP1) + _number - 1); } /// @returns the LOG<_number> instruction inline Instruction logInstruction(unsigned _number) { - if (asserts(_number <= 4)) - BOOST_THROW_EXCEPTION(InvalidOpcode() << errinfo_comment("Invalid LOG instruction requested.")); + assertThrow(_number <= 4, InvalidOpcode, "Invalid LOG instruction requested."); return Instruction(unsigned(Instruction::LOG0) + _number); } diff --git a/libevmcore/SemanticInformation.cpp b/libevmcore/SemanticInformation.cpp new file mode 100644 index 000000000..e561e7554 --- /dev/null +++ b/libevmcore/SemanticInformation.cpp @@ -0,0 +1,107 @@ +/* + 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 SemanticInformation.cpp + * @author Christian + * @date 2015 + * Helper to provide semantic information about assembly items. + */ + +#include +#include + +using namespace std; +using namespace dev; +using namespace dev::eth; + +bool SemanticInformation::breaksCSEAnalysisBlock(AssemblyItem const& _item) +{ + switch (_item.type()) + { + default: + case UndefinedItem: + case Tag: + return true; + case Push: + case PushString: + case PushTag: + case PushSub: + case PushSubSize: + case PushProgramSize: + case PushData: + return false; + case Operation: + { + if (isSwapInstruction(_item) || isDupInstruction(_item)) + return false; + if (_item.instruction() == Instruction::GAS || _item.instruction() == Instruction::PC) + return true; // GAS and PC assume a specific order of opcodes + if (_item.instruction() == Instruction::MSIZE) + return true; // msize is modified already by memory access, avoid that for now + if (_item.instruction() == Instruction::SHA3) + return true; //@todo: we have to compare sha3's not based on their memory addresses but on the memory content. + InstructionInfo info = instructionInfo(_item.instruction()); + if (_item.instruction() == Instruction::SSTORE) + return false; + if (_item.instruction() == Instruction::MSTORE) + return false; + //@todo: We do not handle the following memory instructions for now: + // calldatacopy, codecopy, extcodecopy, mstore8, + // msize (note that msize also depends on memory read access) + + // the second requirement will be lifted once it is implemented + return info.sideEffects || info.args > 2; + } + } +} + +bool SemanticInformation::isCommutativeOperation(AssemblyItem const& _item) +{ + if (_item.type() != Operation) + return false; + switch (_item.instruction()) + { + case Instruction::ADD: + case Instruction::MUL: + case Instruction::EQ: + case Instruction::AND: + case Instruction::OR: + case Instruction::XOR: + return true; + default: + return false; + } +} + +bool SemanticInformation::isDupInstruction(AssemblyItem const& _item) +{ + if (_item.type() != Operation) + return false; + return Instruction::DUP1 <= _item.instruction() && _item.instruction() <= Instruction::DUP16; +} + +bool SemanticInformation::isSwapInstruction(AssemblyItem const& _item) +{ + if (_item.type() != Operation) + return false; + return Instruction::SWAP1 <= _item.instruction() && _item.instruction() <= Instruction::SWAP16; +} + +bool SemanticInformation::isJumpInstruction(AssemblyItem const& _item) +{ + return _item == AssemblyItem(Instruction::JUMP) || _item == AssemblyItem(Instruction::JUMPI); +} diff --git a/libevmcore/SemanticInformation.h b/libevmcore/SemanticInformation.h new file mode 100644 index 000000000..7497dc651 --- /dev/null +++ b/libevmcore/SemanticInformation.h @@ -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 SemanticInformation.h + * @author Christian + * @date 2015 + * Helper to provide semantic information about assembly items. + */ + +#pragma once + + +namespace dev +{ +namespace eth +{ + +class AssemblyItem; + +/** + * Helper functions to provide context-independent information about assembly items. + */ +struct SemanticInformation +{ + /// @returns true if the given items starts a new block for common subexpression analysis. + static bool breaksCSEAnalysisBlock(AssemblyItem const& _item); + /// @returns true if the item is a two-argument operation whose value does not depend on the + /// order of its arguments. + static bool isCommutativeOperation(AssemblyItem const& _item); + static bool isDupInstruction(AssemblyItem const& _item); + static bool isSwapInstruction(AssemblyItem const& _item); + static bool isJumpInstruction(AssemblyItem const& _item); +}; + +} +} diff --git a/libjsqrc/CMakeLists.txt b/libjsqrc/CMakeLists.txt index 68500ada6..4e849de12 100644 --- a/libjsqrc/CMakeLists.txt +++ b/libjsqrc/CMakeLists.txt @@ -12,7 +12,7 @@ qt5_add_resources(JSQRC js.qrc) add_library(jsqrc STATIC ${JSQRC}) target_link_libraries(jsqrc Qt5::Core) -if (ETH_NODE AND ETH_NPM) +if (USENPM) add_custom_target(ethereumjs) add_custom_command(TARGET ethereumjs POST_BUILD @@ -23,3 +23,7 @@ if (ETH_NODE AND ETH_NPM) endif() install( TARGETS jsqrc RUNTIME DESTINATION bin ARCHIVE DESTINATION lib LIBRARY DESTINATION lib ) + +file(GLOB_RECURSE JSFILES "ethereumjs/lib/*.js") +add_custom_target(aux_js SOURCES ${JSFILES}) + diff --git a/libjsqrc/ethereumjs/.travis.yml b/libjsqrc/ethereumjs/.travis.yml index 558fd1537..6dcfc17d1 100644 --- a/libjsqrc/ethereumjs/.travis.yml +++ b/libjsqrc/ethereumjs/.travis.yml @@ -1,13 +1,17 @@ language: node_js node_js: + - "0.12" - "0.11" - "0.10" before_script: - npm install - npm install jshint + - export DISPLAY=:99.0 + - sh -e /etc/init.d/xvfb start script: - "jshint *.js lib" after_script: - npm run-script build + - npm run-script karma - npm run-script test-coveralls diff --git a/libjsqrc/ethereumjs/.versions b/libjsqrc/ethereumjs/.versions new file mode 100644 index 000000000..ed4a55b6a --- /dev/null +++ b/libjsqrc/ethereumjs/.versions @@ -0,0 +1,4 @@ +3stack:bignumber@2.0.0 +ethereum:js@0.0.15-rc12 +meteor@1.1.4 +underscore@1.0.2 diff --git a/libjsqrc/ethereumjs/README.md b/libjsqrc/ethereumjs/README.md index 30c20aafa..a153b89ba 100644 --- a/libjsqrc/ethereumjs/README.md +++ b/libjsqrc/ethereumjs/README.md @@ -1,57 +1,64 @@ # Ethereum JavaScript API This is the Ethereum compatible [JavaScript API](https://github.com/ethereum/wiki/wiki/JavaScript-API) -which implements the [Generic JSON RPC](https://github.com/ethereum/wiki/wiki/JSON-RPC) spec. It's available on npm as a node module and also for bower and component as an embeddable js +which implements the [Generic JSON RPC](https://github.com/ethereum/wiki/wiki/JSON-RPC) spec. It's available on npm as a node module, for bower and component as an embeddable js and as a meteor.js package. [![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![dependency status][dep-image]][dep-url] [![dev dependency status][dep-dev-image]][dep-dev-url][![Coverage Status][coveralls-image]][coveralls-url] +You need to run a local ethrereum node to use this library. + +[Documentation](https://github.com/ethereum/wiki/wiki/JavaScript-API) + ## Installation ### Node.js - npm install ethereum.js + $ npm install ethereum.js + +### Meteor.js -### For browser + $ meteor add ethereum:js + +### As Browser module Bower - bower install ethereum.js + $ bower install ethereum.js Component - component install ethereum/ethereum.js + $ component install ethereum/ethereum.js -* Include `ethereum.min.js` in your html file. -* Include [bignumber.js](https://github.com/MikeMcl/bignumber.js/) +* Include `ethereum.min.js` in your html file. (not required for the meteor package) +* Include [bignumber.js](https://github.com/MikeMcl/bignumber.js/) (not required for the meteor package) ## Usage -Require the library: +Require the library (not required for the meteor package): var web3 = require('web3'); -Set a provider (QtSyncProvider, HttpSyncProvider) +Set a provider (QtSyncProvider, HttpProvider) - web3.setProvider(new web3.providers.HttpSyncProvider()); + web3.setProvider(new web3.providers.HttpProvider('http://localhost:8545')); There you go, now you can use it: ``` var coinbase = web3.eth.coinbase; -var balance = web3.eth.balanceAt(coinbase); +var balance = web3.eth.getBalance(coinbase); ``` For another example see `example/index.html`. + ## Contribute! ### Requirements * Node.js * npm -* gulp (build) -* mocha (tests) ```bash sudo apt-get update @@ -73,6 +80,15 @@ npm run-script build npm test ``` +### Testing (karma) +Karma allows testing within one or several browsers. + +```bash +npm run-script karma # default browsers are Chrome and Firefox +npm run-script karma -- --browsers="Chrome,Safari" # custom browsers +``` + + **Please note this repo is in it's early stage.** If you'd like to run a Http ethereum node check out diff --git a/libjsqrc/ethereumjs/bower.json b/libjsqrc/ethereumjs/bower.json index f0cb33321..5fe92754b 100644 --- a/libjsqrc/ethereumjs/bower.json +++ b/libjsqrc/ethereumjs/bower.json @@ -1,7 +1,7 @@ { "name": "ethereum.js", "namespace": "ethereum", - "version": "0.0.16", + "version": "0.1.2", "description": "Ethereum Compatible JavaScript API", "main": [ "./dist/ethereum.js", @@ -33,6 +33,11 @@ "name": "Marian Oancea", "email": "marian@ethdev.com", "homepage": "https://github.com/cubedro" + }, + { + "name": "Fabian Vogelsteller", + "email": "fabian@ethdev.com", + "homepage": "https://github.com/frozeman" } ], "license": "LGPL-3.0", @@ -41,6 +46,8 @@ "lib", "node_modules", "package.json", + "package.js", + ".versions", ".bowerrc", ".editorconfig", ".gitignore", diff --git a/libjsqrc/ethereumjs/dist/ethereum.js b/libjsqrc/ethereumjs/dist/ethereum.js index 69d393699..8d00981c0 100644 --- a/libjsqrc/ethereumjs/dist/ethereum.js +++ b/libjsqrc/ethereumjs/dist/ethereum.js @@ -22,34 +22,57 @@ require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof requ * @date 2014 */ -var utils = require('./utils'); +var utils = require('../utils/utils'); +var c = require('../utils/config'); var types = require('./types'); -var c = require('./const'); var f = require('./formatters'); -var displayTypeError = function (type) { - console.error('parser does not support type: ' + type); +/** + * throw incorrect type error + * + * @method throwTypeError + * @param {String} type + * @throws incorrect type error + */ +var throwTypeError = function (type) { + throw new Error('parser does not support type: ' + type); }; -/// This method should be called if we want to check if givent type is an array type -/// @returns true if it is, otherwise false -var arrayType = function (type) { +/** This method should be called if we want to check if givent type is an array type + * + * @method isArrayType + * @param {String} type name + * @returns {Boolean} true if it is, otherwise false + */ +var isArrayType = function (type) { return type.slice(-2) === '[]'; }; +/** + * This method should be called to return dynamic type length in hex + * + * @method dynamicTypeBytes + * @param {String} type + * @param {String|Array} dynamic type + * @return {String} length of dynamic type in hex or empty string if type is not dynamic + */ var dynamicTypeBytes = function (type, value) { // TODO: decide what to do with array of strings - if (arrayType(type) || type === 'string') // only string itself that is dynamic; stringX is static length. + if (isArrayType(type) || type === 'bytes') return f.formatInputInt(value.length); return ""; }; var inputTypes = types.inputTypes(); -/// Formats input params to bytes -/// @param abi contract method inputs -/// @param array of params that will be formatted to bytes -/// @returns bytes representation of input params +/** + * Formats input params to bytes + * + * @method formatInput + * @param {Array} abi inputs of method + * @param {Array} params that will be formatted to bytes + * @returns bytes representation of input params + */ var formatInput = function (inputs, params) { var bytes = ""; var toAppendConstant = ""; @@ -67,16 +90,16 @@ var formatInput = function (inputs, params) { typeMatch = inputTypes[j].type(inputs[i].type, params[i]); } if (!typeMatch) { - displayTypeError(inputs[i].type); + throwTypeError(inputs[i].type); } var formatter = inputTypes[j - 1].format; - if (arrayType(inputs[i].type)) + if (isArrayType(inputs[i].type)) toAppendArrayContent += params[i].reduce(function (acc, curr) { return acc + formatter(curr); }, ""); - else if (inputs[i].type === 'string') + else if (inputs[i].type === 'bytes') toAppendArrayContent += formatter(params[i]); else toAppendConstant += formatter(params[i]); @@ -87,18 +110,29 @@ var formatInput = function (inputs, params) { return bytes; }; +/** + * This method should be called to predict the length of dynamic type + * + * @method dynamicBytesLength + * @param {String} type + * @returns {Number} length of dynamic type, 0 or multiplication of ETH_PADDING (32) + */ var dynamicBytesLength = function (type) { - if (arrayType(type) || type === 'string') // only string itself that is dynamic; stringX is static length. + if (isArrayType(type) || type === 'bytes') return c.ETH_PADDING * 2; return 0; }; var outputTypes = types.outputTypes(); -/// Formats output bytes back to param list -/// @param contract abi method outputs -/// @param bytes representtion of output -/// @returns array of output params +/** + * Formats output bytes back to param list + * + * @method formatOutput + * @param {Array} abi outputs of method + * @param {String} bytes represention of output + * @returns {Array} output params + */ var formatOutput = function (outs, output) { output = output.slice(2); @@ -120,11 +154,11 @@ var formatOutput = function (outs, output) { } if (!typeMatch) { - displayTypeError(outs[i].type); + throwTypeError(outs[i].type); } var formatter = outputTypes[j - 1].format; - if (arrayType(outs[i].type)) { + if (isArrayType(outs[i].type)) { var size = f.formatOutputUInt(dynamicPart.slice(0, padding)); dynamicPart = dynamicPart.slice(padding); var array = []; @@ -134,7 +168,7 @@ var formatOutput = function (outs, output) { } result.push(array); } - else if (types.prefixedType('string')(outs[i].type)) { + else if (types.prefixedType('bytes')(outs[i].type)) { dynamicPart = dynamicPart.slice(padding); result.push(formatter(output.slice(0, padding))); output = output.slice(padding); @@ -147,9 +181,14 @@ var formatOutput = function (outs, output) { return result; }; -/// @param json abi for contract -/// @returns input parser object for given json abi -/// TODO: refactor creating the parser, do not double logic from contract +/** + * Should be called to create input parser for contract with given abi + * + * @method inputParser + * @param {Array} contract abi + * @returns {Object} input parser object for given json abi + * TODO: refactor creating the parser, do not double logic from contract + */ var inputParser = function (json) { var parser = {}; json.forEach(function (method) { @@ -171,8 +210,13 @@ var inputParser = function (json) { return parser; }; -/// @param json abi for contract -/// @returns output parser for given json abi +/** + * Should be called to create output parser for contract with given abi + * + * @method outputParser + * @param {Array} contract abi + * @returns {Object} output parser for given json abi + */ var outputParser = function (json) { var parser = {}; json.forEach(function (method) { @@ -201,7 +245,302 @@ module.exports = { formatOutput: formatOutput }; -},{"./const":2,"./formatters":8,"./types":15,"./utils":16}],2:[function(require,module,exports){ +},{"../utils/config":4,"../utils/utils":5,"./formatters":2,"./types":3}],2:[function(require,module,exports){ +/* + This file is part of ethereum.js. + + ethereum.js is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + ethereum.js 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with ethereum.js. If not, see . +*/ +/** @file formatters.js + * @authors: + * Marek Kotewicz + * @date 2015 + */ + +if ("build" !== 'build') {/* + var BigNumber = require('bignumber.js'); // jshint ignore:line +*/} + +var utils = require('../utils/utils'); +var c = require('../utils/config'); + +/** + * Should be called to pad string to expected length + * + * @method padLeft + * @param {String} string to be padded + * @param {Number} characters that result string should have + * @param {String} sign, by default 0 + * @returns {String} right aligned string + */ +var padLeft = function (string, chars, sign) { + return new Array(chars - string.length + 1).join(sign ? sign : "0") + string; +}; + +/** + * Formats input value to byte representation of int + * If value is negative, return it's two's complement + * If the value is floating point, round it down + * + * @method formatInputInt + * @param {String|Number|BigNumber} value that needs to be formatted + * @returns {String} right-aligned byte representation of int + */ +var formatInputInt = function (value) { + var padding = c.ETH_PADDING * 2; + BigNumber.config(c.ETH_BIGNUMBER_ROUNDING_MODE); + return padLeft(utils.toTwosComplement(value).round().toString(16), padding); +}; + +/** + * Formats input value to byte representation of string + * + * @method formatInputString + * @param {String} + * @returns {String} left-algined byte representation of string + */ +var formatInputString = function (value) { + return utils.fromAscii(value, c.ETH_PADDING).substr(2); +}; + +/** + * Formats input value to byte representation of bool + * + * @method formatInputBool + * @param {Boolean} + * @returns {String} right-aligned byte representation bool + */ +var formatInputBool = function (value) { + return '000000000000000000000000000000000000000000000000000000000000000' + (value ? '1' : '0'); +}; + +/** + * Formats input value to byte representation of real + * Values are multiplied by 2^m and encoded as integers + * + * @method formatInputReal + * @param {String|Number|BigNumber} + * @returns {String} byte representation of real + */ +var formatInputReal = function (value) { + return formatInputInt(new BigNumber(value).times(new BigNumber(2).pow(128))); +}; + +/** + * Check if input value is negative + * + * @method signedIsNegative + * @param {String} value is hex format + * @returns {Boolean} true if it is negative, otherwise false + */ +var signedIsNegative = function (value) { + return (new BigNumber(value.substr(0, 1), 16).toString(2).substr(0, 1)) === '1'; +}; + +/** + * Formats right-aligned output bytes to int + * + * @method formatOutputInt + * @param {String} bytes + * @returns {BigNumber} right-aligned output bytes formatted to big number + */ +var formatOutputInt = function (value) { + + value = value || "0"; + + // check if it's negative number + // it it is, return two's complement + if (signedIsNegative(value)) { + return new BigNumber(value, 16).minus(new BigNumber('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 16)).minus(1); + } + return new BigNumber(value, 16); +}; + +/** + * Formats right-aligned output bytes to uint + * + * @method formatOutputUInt + * @param {String} bytes + * @returns {BigNumeber} right-aligned output bytes formatted to uint + */ +var formatOutputUInt = function (value) { + value = value || "0"; + return new BigNumber(value, 16); +}; + +/** + * Formats right-aligned output bytes to real + * + * @method formatOutputReal + * @param {String} + * @returns {BigNumber} input bytes formatted to real + */ +var formatOutputReal = function (value) { + return formatOutputInt(value).dividedBy(new BigNumber(2).pow(128)); +}; + +/** + * Formats right-aligned output bytes to ureal + * + * @method formatOutputUReal + * @param {String} + * @returns {BigNumber} input bytes formatted to ureal + */ +var formatOutputUReal = function (value) { + return formatOutputUInt(value).dividedBy(new BigNumber(2).pow(128)); +}; + +/** + * Should be used to format output hash + * + * @method formatOutputHash + * @param {String} + * @returns {String} right-aligned output bytes formatted to hex + */ +var formatOutputHash = function (value) { + return "0x" + value; +}; + +/** + * Should be used to format output bool + * + * @method formatOutputBool + * @param {String} + * @returns {Boolean} right-aligned input bytes formatted to bool + */ +var formatOutputBool = function (value) { + return value === '0000000000000000000000000000000000000000000000000000000000000001' ? true : false; +}; + +/** + * Should be used to format output string + * + * @method formatOutputString + * @param {Sttring} left-aligned hex representation of string + * @returns {String} ascii string + */ +var formatOutputString = function (value) { + return utils.toAscii(value); +}; + +/** + * Should be used to format output address + * + * @method formatOutputAddress + * @param {String} right-aligned input bytes + * @returns {String} address + */ +var formatOutputAddress = function (value) { + return "0x" + value.slice(value.length - 40, value.length); +}; + +module.exports = { + formatInputInt: formatInputInt, + formatInputString: formatInputString, + formatInputBool: formatInputBool, + formatInputReal: formatInputReal, + formatOutputInt: formatOutputInt, + formatOutputUInt: formatOutputUInt, + formatOutputReal: formatOutputReal, + formatOutputUReal: formatOutputUReal, + formatOutputHash: formatOutputHash, + formatOutputBool: formatOutputBool, + formatOutputString: formatOutputString, + formatOutputAddress: formatOutputAddress +}; + + +},{"../utils/config":4,"../utils/utils":5}],3:[function(require,module,exports){ +/* + This file is part of ethereum.js. + + ethereum.js is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + ethereum.js 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with ethereum.js. If not, see . +*/ +/** @file types.js + * @authors: + * Marek Kotewicz + * @date 2015 + */ + +var f = require('./formatters'); + +/// @param expected type prefix (string) +/// @returns function which checks if type has matching prefix. if yes, returns true, otherwise false +var prefixedType = function (prefix) { + return function (type) { + return type.indexOf(prefix) === 0; + }; +}; + +/// @param expected type name (string) +/// @returns function which checks if type is matching expected one. if yes, returns true, otherwise false +var namedType = function (name) { + return function (type) { + return name === type; + }; +}; + +/// Setups input formatters for solidity types +/// @returns an array of input formatters +var inputTypes = function () { + + return [ + { type: prefixedType('uint'), format: f.formatInputInt }, + { type: prefixedType('int'), format: f.formatInputInt }, + { type: prefixedType('bytes'), format: f.formatInputString }, + { type: prefixedType('real'), format: f.formatInputReal }, + { type: prefixedType('ureal'), format: f.formatInputReal }, + { type: namedType('address'), format: f.formatInputInt }, + { type: namedType('bool'), format: f.formatInputBool } + ]; +}; + +/// Setups output formaters for solidity types +/// @returns an array of output formatters +var outputTypes = function () { + + return [ + { type: prefixedType('uint'), format: f.formatOutputUInt }, + { type: prefixedType('int'), format: f.formatOutputInt }, + { type: prefixedType('bytes'), format: f.formatOutputString }, + { type: prefixedType('real'), format: f.formatOutputReal }, + { type: prefixedType('ureal'), format: f.formatOutputUReal }, + { type: namedType('address'), format: f.formatOutputAddress }, + { type: namedType('bool'), format: f.formatOutputBool } + ]; +}; + +module.exports = { + prefixedType: prefixedType, + namedType: namedType, + inputTypes: inputTypes, + outputTypes: outputTypes +}; + + +},{"./formatters":2}],4:[function(require,module,exports){ /* This file is part of ethereum.js. @@ -218,12 +557,25 @@ module.exports = { You should have received a copy of the GNU Lesser General Public License along with ethereum.js. If not, see . */ -/** @file const.js +/** @file config.js * @authors: * Marek Kotewicz * @date 2015 */ +/** + * Utils + * + * @module utils + */ + +/** + * Utility functions + * + * @class [utils] config + * @constructor + */ + /// required to define ETH_BIGNUMBER_ROUNDING_MODE if ("build" !== 'build') {/* var BigNumber = require('bignumber.js'); // jshint ignore:line @@ -256,11 +608,764 @@ module.exports = { ETH_SIGNATURE_LENGTH: 4, ETH_UNITS: ETH_UNITS, ETH_BIGNUMBER_ROUNDING_MODE: { ROUNDING_MODE: BigNumber.ROUND_DOWN }, - ETH_POLLING_TIMEOUT: 1000 + ETH_POLLING_TIMEOUT: 1000, + ETH_DEFAULTBLOCK: 'latest' }; -},{}],3:[function(require,module,exports){ +},{}],5:[function(require,module,exports){ +/* + This file is part of ethereum.js. + + ethereum.js is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + ethereum.js 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with ethereum.js. If not, see . +*/ +/** @file utils.js + * @authors: + * Marek Kotewicz + * @date 2015 + */ + +/** + * Utils + * + * @module utils + */ + +/** + * Utility functions + * + * @class [utils] utils + * @constructor + */ + +if ("build" !== 'build') {/* + var BigNumber = require('bignumber.js'); // jshint ignore:line +*/} + +var unitMap = { + 'wei': '1', + 'kwei': '1000', + 'ada': '1000', + 'mwei': '1000000', + 'babbage': '1000000', + 'gwei': '1000000000', + 'shannon': '1000000000', + 'szabo': '1000000000000', + 'finney': '1000000000000000', + 'ether': '1000000000000000000', + 'kether': '1000000000000000000000', + 'grand': '1000000000000000000000', + 'einstein': '1000000000000000000000', + 'mether': '1000000000000000000000000', + 'gether': '1000000000000000000000000000', + 'tether': '1000000000000000000000000000000' +}; + + +/** Finds first index of array element matching pattern + * + * @method findIndex + * @param {Array} + * @param {Function} pattern + * @returns {Number} index of element + */ +var findIndex = function (array, callback) { + var end = false; + var i = 0; + for (; i < array.length && !end; i++) { + end = callback(array[i]); + } + return end ? i - 1 : -1; +}; + +/** + * Should be called to get sting from it's hex representation + * + * @method toAscii + * @param {String} string in hex + * @returns {String} ascii string representation of hex value + */ +var toAscii = function(hex) { +// Find termination + var str = ""; + var i = 0, l = hex.length; + if (hex.substring(0, 2) === '0x') { + i = 2; + } + for (; i < l; i+=2) { + var code = parseInt(hex.substr(i, 2), 16); + if (code === 0) { + break; + } + + str += String.fromCharCode(code); + } + + return str; +}; + +/** + * Shold be called to get hex representation (prefixed by 0x) of ascii string + * + * @method fromAscii + * @param {String} string + * @returns {String} hex representation of input string + */ +var toHexNative = function(str) { + var hex = ""; + for(var i = 0; i < str.length; i++) { + var n = str.charCodeAt(i).toString(16); + hex += n.length < 2 ? '0' + n : n; + } + + return hex; +}; + +/** + * Shold be called to get hex representation (prefixed by 0x) of ascii string + * + * @method fromAscii + * @param {String} string + * @param {Number} optional padding + * @returns {String} hex representation of input string + */ +var fromAscii = function(str, pad) { + pad = pad === undefined ? 0 : pad; + var hex = toHexNative(str); + while (hex.length < pad*2) + hex += "00"; + return "0x" + hex; +}; + +/** + * Should be called to get display name of contract function + * + * @method extractDisplayName + * @param {String} name of function/event + * @returns {String} display name for function/event eg. multiply(uint256) -> multiply + */ +var extractDisplayName = function (name) { + var length = name.indexOf('('); + return length !== -1 ? name.substr(0, length) : name; +}; + +/// @returns overloaded part of function/event name +var extractTypeName = function (name) { + /// TODO: make it invulnerable + var length = name.indexOf('('); + return length !== -1 ? name.substr(length + 1, name.length - 1 - (length + 1)).replace(' ', '') : ""; +}; + +/** + * Filters all functions from input abi + * + * @method filterFunctions + * @param {Array} abi + * @returns {Array} abi array with filtered objects of type 'function' + */ +var filterFunctions = function (json) { + return json.filter(function (current) { + return current.type === 'function'; + }); +}; + +/** + * Filters all events from input abi + * + * @method filterEvents + * @param {Array} abi + * @returns {Array} abi array with filtered objects of type 'event' + */ +var filterEvents = function (json) { + return json.filter(function (current) { + return current.type === 'event'; + }); +}; + +/** + * Converts value to it's decimal representation in string + * + * @method toDecimal + * @param {String|Number|BigNumber} + * @return {String} + */ +var toDecimal = function (value) { + return toBigNumber(value).toNumber(); +}; + +/** + * Converts value to it's hex representation + * + * @method fromDecimal + * @param {String|Number|BigNumber} + * @return {String} + */ +var fromDecimal = function (value) { + var number = toBigNumber(value); + var result = number.toString(16); + + return number.lessThan(0) ? '-0x' + result.substr(1) : '0x' + result; +}; + +/** + * Auto converts any given value into it's hex representation. + * + * And even stringifys objects before. + * + * @method toHex + * @param {String|Number|BigNumber|Object} + * @return {String} + */ +var toHex = function (val) { + /*jshint maxcomplexity:7 */ + + if(isBoolean(val)) + return val; + + if(isBigNumber(val)) + return fromDecimal(val); + + if(isObject(val)) + return fromAscii(JSON.stringify(val)); + + // if its a negative number, pass it through fromDecimal + if (isString(val)) { + if (val.indexOf('-0x') === 0) + return fromDecimal(val); + else if (!isFinite(val)) + return fromAscii(val); + } + + return fromDecimal(val); +}; + +/** + * Returns value of unit in Wei + * + * @method getValueOfUnit + * @param {String} unit the unit to convert to, default ether + * @returns {BigNumber} value of the unit (in Wei) + * @throws error if the unit is not correct:w + */ +var getValueOfUnit = function (unit) { + unit = unit ? unit.toLowerCase() : 'ether'; + var unitValue = unitMap[unit]; + if (unitValue === undefined) { + throw new Error('This unit doesn\'t exists, please use the one of the following units' + JSON.stringify(unitMap, null, 2)); + } + return new BigNumber(unitValue, 10); +}; + +/** + * Takes a number of wei and converts it to any other ether unit. + * + * Possible units are: + * - kwei/ada + * - mwei/babbage + * - gwei/shannon + * - szabo + * - finney + * - ether + * - kether/grand/einstein + * - mether + * - gether + * - tether + * + * @method fromWei + * @param {Number|String} number can be a number, number string or a HEX of a decimal + * @param {String} unit the unit to convert to, default ether + * @return {String|Object} When given a BigNumber object it returns one as well, otherwise a number +*/ +var fromWei = function(number, unit) { + var returnValue = toBigNumber(number).dividedBy(getValueOfUnit(unit)); + + return isBigNumber(number) ? returnValue : returnValue.toString(10); +}; + +/** + * Takes a number of a unit and converts it to wei. + * + * Possible units are: + * - kwei/ada + * - mwei/babbage + * - gwei/shannon + * - szabo + * - finney + * - ether + * - kether/grand/einstein + * - mether + * - gether + * - tether + * + * @method toWei + * @param {Number|String|BigNumber} number can be a number, number string or a HEX of a decimal + * @param {String} unit the unit to convert from, default ether + * @return {String|Object} When given a BigNumber object it returns one as well, otherwise a number +*/ +var toWei = function(number, unit) { + var returnValue = toBigNumber(number).times(getValueOfUnit(unit)); + + return isBigNumber(number) ? returnValue : returnValue.toString(10); +}; + +/** + * Takes an input and transforms it into an bignumber + * + * @method toBigNumber + * @param {Number|String|BigNumber} a number, string, HEX string or BigNumber + * @return {BigNumber} BigNumber +*/ +var toBigNumber = function(number) { + /*jshint maxcomplexity:5 */ + number = number || 0; + if (isBigNumber(number)) + return number; + + if (isString(number) && (number.indexOf('0x') === 0 || number.indexOf('-0x') === 0)) { + return new BigNumber(number.replace('0x',''), 16); + } + + return new BigNumber(number.toString(10), 10); +}; + +/** + * Takes and input transforms it into bignumber and if it is negative value, into two's complement + * + * @method toTwosComplement + * @param {Number|String|BigNumber} + * @return {BigNumber} + */ +var toTwosComplement = function (number) { + var bigNumber = toBigNumber(number); + if (bigNumber.lessThan(0)) { + return new BigNumber("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 16).plus(bigNumber).plus(1); + } + return bigNumber; +}; + +/** + * Checks if the given string has proper length + * + * @method isAddress + * @param {String} address the given HEX adress + * @return {Boolean} +*/ +var isAddress = function(address) { + if (!isString(address)) { + return false; + } + + return ((address.indexOf('0x') === 0 && address.length === 42) || + (address.indexOf('0x') === -1 && address.length === 40)); +}; + +/** + * Returns true if object is BigNumber, otherwise false + * + * @method isBigNumber + * @param {Object} + * @return {Boolean} + */ +var isBigNumber = function (object) { + return object instanceof BigNumber || + (object && object.constructor && object.constructor.name === 'BigNumber'); +}; + +/** + * Returns true if object is string, otherwise false + * + * @method isString + * @param {Object} + * @return {Boolean} + */ +var isString = function (object) { + return typeof object === 'string' || + (object && object.constructor && object.constructor.name === 'String'); +}; + +/** + * Returns true if object is function, otherwise false + * + * @method isFunction + * @param {Object} + * @return {Boolean} + */ +var isFunction = function (object) { + return typeof object === 'function'; +}; + +/** + * Returns true if object is Objet, otherwise false + * + * @method isObject + * @param {Object} + * @return {Boolean} + */ +var isObject = function (object) { + return typeof object === 'object'; +}; + +/** + * Returns true if object is boolean, otherwise false + * + * @method isBoolean + * @param {Object} + * @return {Boolean} + */ +var isBoolean = function (object) { + return typeof object === 'boolean'; +}; + +/** + * Returns true if object is array, otherwise false + * + * @method isArray + * @param {Object} + * @return {Boolean} + */ +var isArray = function (object) { + return object instanceof Array; +}; + +module.exports = { + findIndex: findIndex, + toHex: toHex, + toDecimal: toDecimal, + fromDecimal: fromDecimal, + toAscii: toAscii, + fromAscii: fromAscii, + extractDisplayName: extractDisplayName, + extractTypeName: extractTypeName, + filterFunctions: filterFunctions, + filterEvents: filterEvents, + toWei: toWei, + fromWei: fromWei, + toBigNumber: toBigNumber, + toTwosComplement: toTwosComplement, + isBigNumber: isBigNumber, + isAddress: isAddress, + isFunction: isFunction, + isString: isString, + isObject: isObject, + isBoolean: isBoolean, + isArray: isArray +}; + + +},{}],6:[function(require,module,exports){ +/* + This file is part of ethereum.js. + + ethereum.js is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + ethereum.js 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with ethereum.js. If not, see . +*/ +/** @file web3.js + * @authors: + * Jeffrey Wilcke + * Marek Kotewicz + * Marian Oancea + * Fabian Vogelsteller + * Gav Wood + * @date 2014 + */ + +var version = require('../version.json'); +var net = require('./web3/net'); +var eth = require('./web3/eth'); +var db = require('./web3/db'); +var shh = require('./web3/shh'); +var watches = require('./web3/watches'); +var filter = require('./web3/filter'); +var utils = require('./utils/utils'); +var formatters = require('./solidity/formatters'); +var requestManager = require('./web3/requestmanager'); +var c = require('./utils/config'); + +/// @returns an array of objects describing web3 api methods +var web3Methods = [ + { name: 'sha3', call: 'web3_sha3', inputFormatter: utils.toHex }, +]; +var web3Properties = [ + { name: 'version.client', getter: 'web3_clientVersion' }, + { name: 'version.network', getter: 'net_version' } +]; + + +/// creates methods in a given object based on method description on input +/// setups api calls for these methods +var setupMethods = function (obj, methods) { + methods.forEach(function (method) { + // allow for object methods 'myObject.method' + var objectMethods = method.name.split('.'), + callFunction = function () { + /*jshint maxcomplexity:8 */ + + var callback = null, + args = Array.prototype.slice.call(arguments), + call = typeof method.call === 'function' ? method.call(args) : method.call; + + // get the callback if one is available + if(typeof args[args.length-1] === 'function'){ + callback = args[args.length-1]; + Array.prototype.pop.call(args); + } + + // add the defaultBlock if not given + if(method.addDefaultblock) { + if(args.length !== method.addDefaultblock) + Array.prototype.push.call(args, (isFinite(c.ETH_DEFAULTBLOCK) ? utils.fromDecimal(c.ETH_DEFAULTBLOCK) : c.ETH_DEFAULTBLOCK)); + else + args[args.length-1] = isFinite(args[args.length-1]) ? utils.fromDecimal(args[args.length-1]) : args[args.length-1]; + } + + // show deprecated warning + if(method.newMethod) + console.warn('This method is deprecated please use web3.'+ method.newMethod +'() instead.'); + + return web3.manager.send({ + method: call, + params: args, + outputFormatter: method.outputFormatter, + inputFormatter: method.inputFormatter, + addDefaultblock: method.addDefaultblock + }, callback); + }; + + if(objectMethods.length > 1) { + if(!obj[objectMethods[0]]) + obj[objectMethods[0]] = {}; + + obj[objectMethods[0]][objectMethods[1]] = callFunction; + + } else { + + obj[objectMethods[0]] = callFunction; + } + + }); +}; + +/// creates properties in a given object based on properties description on input +/// setups api calls for these properties +var setupProperties = function (obj, properties) { + properties.forEach(function (property) { + var objectProperties = property.name.split('.'), + proto = {}; + + proto.get = function () { + + // show deprecated warning + if(property.newProperty) + console.warn('This property is deprecated please use web3.'+ property.newProperty +' instead.'); + + + return web3.manager.send({ + method: property.getter, + outputFormatter: property.outputFormatter + }); + }; + + if (property.setter) { + proto.set = function (val) { + + // show deprecated warning + if(property.newProperty) + console.warn('This property is deprecated please use web3.'+ property.newProperty +' instead.'); + + return web3.manager.send({ + method: property.setter, + params: [val], + inputFormatter: property.inputFormatter + }); + }; + } + + proto.enumerable = !property.newProperty; + + if(objectProperties.length > 1) { + if(!obj[objectProperties[0]]) + obj[objectProperties[0]] = {}; + + Object.defineProperty(obj[objectProperties[0]], objectProperties[1], proto); + } else + Object.defineProperty(obj, property.name, proto); + + }); +}; + +/*jshint maxparams:4 */ +var startPolling = function (method, id, callback, uninstall) { + web3.manager.startPolling({ + method: method, + params: [id] + }, id, callback, uninstall); +}; +/*jshint maxparams:3 */ + +var stopPolling = function (id) { + web3.manager.stopPolling(id); +}; + +var ethWatch = { + startPolling: startPolling.bind(null, 'eth_getFilterChanges'), + stopPolling: stopPolling +}; + +var shhWatch = { + startPolling: startPolling.bind(null, 'shh_getFilterChanges'), + stopPolling: stopPolling +}; + +/// setups web3 object, and it's in-browser executed methods +var web3 = { + + version: { + api: version.version + }, + + manager: requestManager(), + providers: {}, + + setProvider: function (provider) { + web3.manager.setProvider(provider); + }, + + /// Should be called to reset state of web3 object + /// Resets everything except manager + reset: function () { + web3.manager.reset(); + }, + + /// @returns hex string of the input + toHex: utils.toHex, + + /// @returns ascii string representation of hex value prefixed with 0x + toAscii: utils.toAscii, + + /// @returns hex representation (prefixed by 0x) of ascii string + fromAscii: utils.fromAscii, + + /// @returns decimal representaton of hex value prefixed by 0x + toDecimal: utils.toDecimal, + + /// @returns hex representation (prefixed by 0x) of decimal value + fromDecimal: utils.fromDecimal, + + /// @returns a BigNumber object + toBigNumber: utils.toBigNumber, + + toWei: utils.toWei, + fromWei: utils.fromWei, + isAddress: utils.isAddress, + + // provide network information + net: { + // peerCount: + }, + + + /// eth object prototype + eth: { + // DEPRECATED + contractFromAbi: function (abi) { + console.warn('Initiating a contract like this is deprecated please use var MyContract = eth.contract(abi); new MyContract(address); instead.'); + + return function(addr) { + // Default to address of Config. TODO: rremove prior to genesis. + addr = addr || '0xc6d9d2cd449a754c494264e1809c50e34d64562b'; + var ret = web3.eth.contract(addr, abi); + ret.address = addr; + return ret; + }; + }, + + /// @param filter may be a string, object or event + /// @param eventParams is optional, this is an object with optional event eventParams params + /// @param options is optional, this is an object with optional event options ('max'...) + /*jshint maxparams:4 */ + filter: function (fil, eventParams, options) { + + // if its event, treat it differently + if (fil._isEvent) + return fil(eventParams, options); + + return filter(fil, ethWatch, formatters.outputLogFormatter); + }, + // DEPRECATED + watch: function (fil, eventParams, options) { + console.warn('eth.watch() is deprecated please use eth.filter() instead.'); + return this.filter(fil, eventParams, options); + } + /*jshint maxparams:3 */ + }, + + /// db object prototype + db: {}, + + /// shh object prototype + shh: { + /// @param filter may be a string, object or event + filter: function (fil) { + return filter(fil, shhWatch, formatters.outputPostFormatter); + }, + // DEPRECATED + watch: function (fil) { + console.warn('shh.watch() is deprecated please use shh.filter() instead.'); + return this.filter(fil); + } + } +}; + + +// ADD defaultblock +Object.defineProperty(web3.eth, 'defaultBlock', { + get: function () { + return c.ETH_DEFAULTBLOCK; + }, + set: function (val) { + c.ETH_DEFAULTBLOCK = val; + return c.ETH_DEFAULTBLOCK; + } +}); + + +/// setups all api methods +setupMethods(web3, web3Methods); +setupProperties(web3, web3Properties); +setupMethods(web3.net, net.methods); +setupProperties(web3.net, net.properties); +setupMethods(web3.eth, eth.methods); +setupProperties(web3.eth, eth.properties); +setupMethods(web3.db, db.methods()); +setupMethods(web3.shh, shh.methods()); +setupMethods(ethWatch, watches.eth()); +setupMethods(shhWatch, watches.shh()); + +module.exports = web3; + + +},{"../version.json":21,"./solidity/formatters":2,"./utils/config":4,"./utils/utils":5,"./web3/db":8,"./web3/eth":9,"./web3/filter":11,"./web3/net":15,"./web3/requestmanager":17,"./web3/shh":18,"./web3/watches":20}],7:[function(require,module,exports){ /* This file is part of ethereum.js. @@ -283,9 +1388,9 @@ module.exports = { * @date 2014 */ -var web3 = require('./web3'); -var abi = require('./abi'); -var utils = require('./utils'); +var web3 = require('../web3'); +var abi = require('../solidity/abi'); +var utils = require('../utils/utils'); var eventImpl = require('./event'); var signature = require('./signature'); @@ -301,16 +1406,24 @@ var exportNatspecGlobals = function (vars) { var addFunctionRelatedPropertiesToContract = function (contract) { contract.call = function (options) { - contract._isTransact = false; + contract._isTransaction = false; contract._options = options; return contract; }; - contract.transact = function (options) { - contract._isTransact = true; + + contract.sendTransaction = function (options) { + contract._isTransaction = true; contract._options = options; return contract; }; + // DEPRECATED + contract.transact = function (options) { + + console.warn('myContract.transact() is deprecated please use myContract.sendTransaction() instead.'); + + return contract.sendTransaction(options); + }; contract._options = {}; ['gas', 'gasPrice', 'value', 'from'].forEach(function(p) { @@ -342,14 +1455,14 @@ var addFunctionsToContract = function (contract, desc, address) { options.to = address; options.data = sign + parsed; - var isTransact = contract._isTransact === true || (contract._isTransact !== false && !method.constant); + var isTransaction = contract._isTransaction === true || (contract._isTransaction !== false && !method.constant); var collapse = options.collapse !== false; // reset contract._options = {}; - contract._isTransact = null; + contract._isTransaction = null; - if (isTransact) { + if (isTransaction) { exportNatspecGlobals({ abi: desc, @@ -359,7 +1472,7 @@ var addFunctionsToContract = function (contract, desc, address) { }); // transactions do not have any output, cause we do not know, when they will be processed - web3.eth.transact(options); + web3.eth.sendTransaction(options); return; } @@ -391,7 +1504,7 @@ var addEventRelatedPropertiesToContract = function (contract, desc, address) { return parser(data); }; - Object.defineProperty(contract, 'topic', { + Object.defineProperty(contract, 'topics', { get: function() { return utils.filterEvents(desc).map(function (e) { return signature.eventSignatureFromAscii(e.name); @@ -414,7 +1527,7 @@ var addEventsToContract = function (contract, desc, address) { var parser = eventImpl.outputParser(e); return parser(data); }; - return web3.eth.watch(o, undefined, undefined, outputFormatter); + return web3.eth.filter(o, undefined, undefined, outputFormatter); }; // this property should be used by eth.filter to check if object is an event @@ -444,24 +1557,40 @@ var addEventsToContract = function (contract, desc, address) { * outputs: [{name: 'd', type: 'string' }] * }]; // contract abi * - * var myContract = web3.eth.contract('0x0123123121', abi); // creation of contract object + * var MyContract = web3.eth.contract(abi); // creation of contract prototype + * + * var contractInstance = new MyContract('0x0123123121'); * - * myContract.myMethod('this is test string param for call'); // myMethod call (implicit, default) - * myContract.call().myMethod('this is test string param for call'); // myMethod call (explicit) - * myContract.transact().myMethod('this is test string param for transact'); // myMethod transact + * contractInstance.myMethod('this is test string param for call'); // myMethod call (implicit, default) + * contractInstance.call().myMethod('this is test string param for call'); // myMethod call (explicit) + * contractInstance.sendTransaction().myMethod('this is test string param for transact'); // myMethod sendTransaction * - * @param address - address of the contract, which should be called - * @param desc - abi json description of the contract, which is being created + * @param abi - abi json description of the contract, which is being created * @returns contract object */ +var contract = function (abi) { + + // return prototype + if(abi instanceof Array && arguments.length === 1) { + return Contract.bind(null, abi); + + // deprecated: auto initiate contract + } else { -var contract = function (address, desc) { + console.warn('Initiating a contract like this is deprecated please use var MyContract = eth.contract(abi); new MyContract(address); instead.'); + + return new Contract(arguments[1], arguments[0]); + } + +}; + +function Contract(abi, address) { // workaround for invalid assumption that method.name is the full anonymous prototype of the method. // it's not. it's just the name. the rest of the code assumes it's actually the anonymous // prototype, so we make it so as a workaround. // TODO: we may not want to modify input params, maybe use copy instead? - desc.forEach(function (method) { + abi.forEach(function (method) { if (method.name.indexOf('(') === -1) { var displayName = method.name; var typeName = method.inputs.map(function(i){return i.type; }).join(); @@ -471,17 +1600,17 @@ var contract = function (address, desc) { var result = {}; addFunctionRelatedPropertiesToContract(result); - addFunctionsToContract(result, desc, address); - addEventRelatedPropertiesToContract(result, desc, address); - addEventsToContract(result, desc, address); + addFunctionsToContract(result, abi, address); + addEventRelatedPropertiesToContract(result, abi, address); + addEventsToContract(result, abi, address); return result; -}; +} module.exports = contract; -},{"./abi":1,"./event":6,"./signature":14,"./utils":16,"./web3":18}],4:[function(require,module,exports){ +},{"../solidity/abi":1,"../utils/utils":5,"../web3":6,"./event":10,"./signature":19}],8:[function(require,module,exports){ /* This file is part of ethereum.js. @@ -504,13 +1633,14 @@ module.exports = contract; * @date 2015 */ + /// @returns an array of objects describing web3.db api methods var methods = function () { return [ - { name: 'put', call: 'db_put' }, - { name: 'get', call: 'db_get' }, - { name: 'putString', call: 'db_putString' }, - { name: 'getString', call: 'db_getString' } + { name: 'putString', call: 'db_putString'}, + { name: 'getString', call: 'db_getString'}, + { name: 'putHex', call: 'db_putHex'}, + { name: 'getHex', call: 'db_getHex'} ]; }; @@ -518,7 +1648,7 @@ module.exports = { methods: methods }; -},{}],5:[function(require,module,exports){ +},{}],9:[function(require,module,exports){ /* This file is part of ethereum.js. @@ -538,66 +1668,129 @@ module.exports = { /** @file eth.js * @authors: * Marek Kotewicz + * Fabian Vogelsteller * @date 2015 */ -/// @returns an array of objects describing web3.eth api methods -var methods = function () { - var blockCall = function (args) { - return typeof args[0] === "string" ? "eth_blockByHash" : "eth_blockByNumber"; - }; +/** + * Web3 + * + * @module web3 + */ - var transactionCall = function (args) { - return typeof args[0] === "string" ? 'eth_transactionByHash' : 'eth_transactionByNumber'; - }; +/** + * Eth methods and properties + * + * An example method object can look as follows: + * + * { + * name: 'getBlock', + * call: blockCall, + * outputFormatter: formatters.outputBlockFormatter, + * inputFormatter: [ // can be a formatter funciton or an array of functions. Where each item in the array will be used for one parameter + * utils.toHex, // formats paramter 1 + * function(param){ if(!param) return false; } // formats paramter 2 + * ] + * }, + * + * @class [web3] eth + * @constructor + */ - var uncleCall = function (args) { - return typeof args[0] === "string" ? 'eth_uncleByHash' : 'eth_uncleByNumber'; - }; - var transactionCountCall = function (args) { - return typeof args[0] === "string" ? 'eth_transactionCountByHash' : 'eth_transactionCountByNumber'; - }; +var formatters = require('./formatters'); +var utils = require('../utils/utils'); - var uncleCountCall = function (args) { - return typeof args[0] === "string" ? 'eth_uncleCountByHash' : 'eth_uncleCountByNumber'; - }; - return [ - { name: 'balanceAt', call: 'eth_balanceAt' }, - { name: 'stateAt', call: 'eth_stateAt' }, - { name: 'storageAt', call: 'eth_storageAt' }, - { name: 'countAt', call: 'eth_countAt'}, - { name: 'codeAt', call: 'eth_codeAt' }, - { name: 'transact', call: 'eth_transact' }, - { name: 'call', call: 'eth_call' }, - { name: 'block', call: blockCall }, - { name: 'transaction', call: transactionCall }, - { name: 'uncle', call: uncleCall }, - { name: 'compilers', call: 'eth_compilers' }, - { name: 'flush', call: 'eth_flush' }, - { name: 'lll', call: 'eth_lll' }, - { name: 'solidity', call: 'eth_solidity' }, - { name: 'serpent', call: 'eth_serpent' }, - { name: 'logs', call: 'eth_logs' }, - { name: 'transactionCount', call: transactionCountCall }, - { name: 'uncleCount', call: uncleCountCall } - ]; +var blockCall = function (args) { + return (utils.isString(args[0]) && args[0].indexOf('0x') === 0) ? "eth_getBlockByHash" : "eth_getBlockByNumber"; +}; + +var transactionFromBlockCall = function (args) { + return (utils.isString(args[0]) && args[0].indexOf('0x') === 0) ? 'eth_getTransactionByBlockHashAndIndex' : 'eth_getTransactionByBlockNumberAndIndex'; +}; + +var uncleCall = function (args) { + return (utils.isString(args[0]) && args[0].indexOf('0x') === 0) ? 'eth_getUncleByBlockHashAndIndex' : 'eth_getUncleByBlockNumberAndIndex'; +}; + +var getBlockTransactionCountCall = function (args) { + return (utils.isString(args[0]) && args[0].indexOf('0x') === 0) ? 'eth_getBlockTransactionCountByHash' : 'eth_getBlockTransactionCountByNumber'; }; +var uncleCountCall = function (args) { + return (utils.isString(args[0]) && args[0].indexOf('0x') === 0) ? 'eth_getUncleCountByBlockHash' : 'eth_getUncleCountByBlockNumber'; +}; + +/// @returns an array of objects describing web3.eth api methods +var methods = [ + { name: 'getBalance', call: 'eth_getBalance', addDefaultblock: 2, + outputFormatter: formatters.convertToBigNumber}, + { name: 'getStorageAt', call: 'eth_getStorageAt', addDefaultblock: 3, + inputFormatter: utils.toHex}, + { name: 'getCode', call: 'eth_getCode', addDefaultblock: 2}, + { name: 'getBlock', call: blockCall, + outputFormatter: formatters.outputBlockFormatter, + inputFormatter: [utils.toHex, function(param){ return (!param) ? false : true; }]}, + { name: 'getUncle', call: uncleCall, + outputFormatter: formatters.outputBlockFormatter, + inputFormatter: [utils.toHex, utils.toHex, function(param){ return (!param) ? false : true; }]}, + { name: 'getCompilers', call: 'eth_getCompilers' }, + { name: 'getBlockTransactionCount', call: getBlockTransactionCountCall, + outputFormatter: utils.toDecimal, + inputFormatter: utils.toHex }, + { name: 'getBlockUncleCount', call: uncleCountCall, + outputFormatter: utils.toDecimal, + inputFormatter: utils.toHex }, + { name: 'getTransaction', call: 'eth_getTransactionByHash', + outputFormatter: formatters.outputTransactionFormatter }, + { name: 'getTransactionFromBlock', call: transactionFromBlockCall, + outputFormatter: formatters.outputTransactionFormatter, + inputFormatter: utils.toHex }, + { name: 'getTransactionCount', call: 'eth_getTransactionCount', addDefaultblock: 2, + outputFormatter: utils.toDecimal}, + { name: 'sendTransaction', call: 'eth_sendTransaction', + inputFormatter: formatters.inputTransactionFormatter }, + { name: 'call', call: 'eth_call', addDefaultblock: 2, + inputFormatter: formatters.inputCallFormatter }, + { name: 'compile.solidity', call: 'eth_compileSolidity' }, + { name: 'compile.lll', call: 'eth_compileLLL', inputFormatter: utils.toHex }, + { name: 'compile.serpent', call: 'eth_compileSerpent', inputFormatter: utils.toHex }, + { name: 'flush', call: 'eth_flush' }, + + // deprecated methods + { name: 'balanceAt', call: 'eth_balanceAt', newMethod: 'eth.getBalance' }, + { name: 'stateAt', call: 'eth_stateAt', newMethod: 'eth.getStorageAt' }, + { name: 'storageAt', call: 'eth_storageAt', newMethod: 'eth.getStorage' }, + { name: 'countAt', call: 'eth_countAt', newMethod: 'eth.getTransactionCount' }, + { name: 'codeAt', call: 'eth_codeAt', newMethod: 'eth.getCode' }, + { name: 'transact', call: 'eth_transact', newMethod: 'eth.sendTransaction' }, + { name: 'block', call: blockCall, newMethod: 'eth.getBlock' }, + { name: 'transaction', call: transactionFromBlockCall, newMethod: 'eth.getTransaction' }, + { name: 'uncle', call: uncleCall, newMethod: 'eth.getUncle' }, + { name: 'compilers', call: 'eth_compilers', newMethod: 'eth.getCompilers' }, + { name: 'solidity', call: 'eth_solidity', newMethod: 'eth.compile.solidity' }, + { name: 'lll', call: 'eth_lll', newMethod: 'eth.compile.lll' }, + { name: 'serpent', call: 'eth_serpent', newMethod: 'eth.compile.serpent' }, + { name: 'transactionCount', call: getBlockTransactionCountCall, newMethod: 'eth.getBlockTransactionCount' }, + { name: 'uncleCount', call: uncleCountCall, newMethod: 'eth.getBlockUncleCount' }, + { name: 'logs', call: 'eth_logs' } +]; + /// @returns an array of objects describing web3.eth api properties -var properties = function () { - return [ - { name: 'coinbase', getter: 'eth_coinbase', setter: 'eth_setCoinbase' }, - { name: 'listening', getter: 'eth_listening', setter: 'eth_setListening' }, - { name: 'mining', getter: 'eth_mining', setter: 'eth_setMining' }, - { name: 'gasPrice', getter: 'eth_gasPrice' }, +var properties = [ + { name: 'coinbase', getter: 'eth_coinbase'}, + { name: 'mining', getter: 'eth_mining'}, + { name: 'gasPrice', getter: 'eth_gasPrice', outputFormatter: formatters.convertToBigNumber}, { name: 'accounts', getter: 'eth_accounts' }, - { name: 'peerCount', getter: 'eth_peerCount' }, - { name: 'defaultBlock', getter: 'eth_defaultBlock', setter: 'eth_setDefaultBlock' }, - { name: 'number', getter: 'eth_number'} - ]; -}; + { name: 'blockNumber', getter: 'eth_blockNumber', outputFormatter: utils.toDecimal}, + + // deprecated properties + { name: 'listening', getter: 'net_listening', setter: 'eth_setListening', newProperty: 'net.listening'}, + { name: 'peerCount', getter: 'net_peerCount', newProperty: 'net.peerCount'}, + { name: 'number', getter: 'eth_number', newProperty: 'eth.blockNumber'} +]; + module.exports = { methods: methods, @@ -605,7 +1798,7 @@ module.exports = { }; -},{}],6:[function(require,module,exports){ +},{"../utils/utils":5,"./formatters":12}],10:[function(require,module,exports){ /* This file is part of ethereum.js. @@ -628,8 +1821,8 @@ module.exports = { * @date 2014 */ -var abi = require('./abi'); -var utils = require('./utils'); +var abi = require('../solidity/abi'); +var utils = require('../utils/utils'); var signature = require('./signature'); /// filter inputs array && returns only indexed (or not) inputs @@ -671,14 +1864,14 @@ var indexedParamsToTopics = function (event, indexed) { var inputParser = function (address, sign, event) { - // valid options are 'earliest', 'latest', 'offset' and 'max', as defined for 'eth.watch' + // valid options are 'earliest', 'latest', 'offset' and 'max', as defined for 'eth.filter' return function (indexed, options) { var o = options || {}; o.address = address; - o.topic = []; - o.topic.push(sign); + o.topics = []; + o.topics.push(sign); if (indexed) { - o.topic = o.topic.concat(indexedParamsToTopics(event, indexed)); + o.topics = o.topics.concat(indexedParamsToTopics(event, indexed)); } return o; }; @@ -710,12 +1903,12 @@ var outputParser = function (event) { }; output.topics = output.topic; // fallback for go-ethereum - if (!output.topic) { + if (!output.topics) { return result; } var indexedOutputs = filterInputs(event.inputs, true); - var indexedData = "0x" + output.topic.slice(1, output.topic.length).map(function (topic) { return topic.slice(2); }).join(""); + var indexedData = "0x" + output.topics.slice(1, output.topics.length).map(function (topics) { return topics.slice(2); }).join(""); var indexedRes = abi.formatOutput(indexedOutputs, indexedData); var notIndexedOutputs = filterInputs(event.inputs, false); @@ -730,7 +1923,7 @@ var outputParser = function (event) { var getMatchingEvent = function (events, payload) { for (var i = 0; i < events.length; i++) { var sign = signature.eventSignatureFromAscii(events[i].name); - if (sign === payload.topic[0]) { + if (sign === payload.topics[0]) { return events[i]; } } @@ -745,7 +1938,7 @@ module.exports = { }; -},{"./abi":1,"./signature":14,"./utils":16}],7:[function(require,module,exports){ +},{"../solidity/abi":1,"../utils/utils":5,"./signature":19}],11:[function(require,module,exports){ /* This file is part of ethereum.js. @@ -767,16 +1960,19 @@ module.exports = { * Jeffrey Wilcke * Marek Kotewicz * Marian Oancea + * Fabian Vogelsteller * Gav Wood * @date 2014 */ +var utils = require('../utils/utils'); + /// Should be called to check if filter implementation is valid /// @returns true if it is, otherwise false var implementationIsValid = function (i) { return !!i && typeof i.newFilter === 'function' && - typeof i.getMessages === 'function' && + typeof i.getLogs === 'function' && typeof i.uninstallFilter === 'function' && typeof i.startPolling === 'function' && typeof i.stopPolling === 'function'; @@ -786,25 +1982,51 @@ var implementationIsValid = function (i) { /// @param should be string or object /// @returns options string or object var getOptions = function (options) { + /*jshint maxcomplexity:9 */ + if (typeof options === 'string') { return options; } options = options || {}; - if (options.topics) { - console.warn('"topics" is deprecated, is "topic" instead'); + if (options.topic) { + console.warn('"topic" is deprecated, is "topics" instead'); + options.topics = options.topic; } + if (options.earliest) { + console.warn('"earliest" is deprecated, is "fromBlock" instead'); + options.fromBlock = options.earliest; + } + + if (options.latest) { + console.warn('"latest" is deprecated, is "toBlock" instead'); + options.toBlock = options.latest; + } + + // make sure topics, get converted to hex + if(options.topics instanceof Array) { + options.topics = options.topics.map(function(topic){ + return utils.toHex(topic); + }); + } + + var asBlockNumber = function(n) { + if (n == null) + return null; + if (n == 'latest' || n == 'pending') + return n; + return utils.toHex(n); + }; + // evaluate lazy properties return { + fromBlock: asBlockNumber(options.fromBlock), + toBlock: asBlockNumber(options.toBlock), to: options.to, - topic: options.topic, - earliest: options.earliest, - latest: options.latest, - max: options.max, - skip: options.skip, - address: options.address + address: options.address, + topics: options.topics }; }; @@ -822,45 +2044,74 @@ var filter = function(options, implementation, formatter) { options = getOptions(options); var callbacks = []; var filterId = implementation.newFilter(options); + + // call the callbacks var onMessages = function (messages) { messages.forEach(function (message) { - message = formatter ? formatter(message) : message; + message = formatter ? formatter(message) : message; callbacks.forEach(function (callback) { - callback(message); + callback(message); }); }); }; implementation.startPolling(filterId, onMessages, implementation.uninstallFilter); - var changed = function (callback) { + var watch = function(callback) { callbacks.push(callback); }; - var messages = function () { - return implementation.getMessages(filterId); - }; - - var uninstall = function () { + var stopWatching = function() { implementation.stopPolling(filterId); implementation.uninstallFilter(filterId); callbacks = []; }; + var get = function () { + var results = implementation.getLogs(filterId); + + return utils.isArray(results) ? results.map(function(message){ + return formatter ? formatter(message) : message; + }) : results; + }; + return { - changed: changed, - arrived: changed, - happened: changed, - messages: messages, - logs: messages, - uninstall: uninstall + watch: watch, + stopWatching: stopWatching, + get: get, + + // DEPRECATED methods + changed: function(){ + console.warn('watch().changed() is deprecated please use filter().watch() instead.'); + return watch.apply(this, arguments); + }, + arrived: function(){ + console.warn('watch().arrived() is deprecated please use filter().watch() instead.'); + return watch.apply(this, arguments); + }, + happened: function(){ + console.warn('watch().happened() is deprecated please use filter().watch() instead.'); + return watch.apply(this, arguments); + }, + uninstall: function(){ + console.warn('watch().uninstall() is deprecated please use filter().stopWatching() instead.'); + return stopWatching.apply(this, arguments); + }, + messages: function(){ + console.warn('watch().messages() is deprecated please use filter().get() instead.'); + return get.apply(this, arguments); + }, + logs: function(){ + console.warn('watch().logs() is deprecated please use filter().get() instead.'); + return get.apply(this, arguments); + } }; }; module.exports = filter; -},{}],8:[function(require,module,exports){ +},{"../utils/utils":5}],12:[function(require,module,exports){ /* This file is part of ethereum.js. @@ -880,144 +2131,192 @@ module.exports = filter; /** @file formatters.js * @authors: * Marek Kotewicz + * Fabian Vogelsteller * @date 2015 */ -if ("build" !== 'build') {/* - var BigNumber = require('bignumber.js'); // jshint ignore:line -*/} +var utils = require('../utils/utils'); + +/** + * Should the input to a big number + * + * @method convertToBigNumber + * @param {String|Number|BigNumber} + * @returns {BigNumber} object + */ +var convertToBigNumber = function (value) { + return utils.toBigNumber(value); +}; + +/** + * Formats the input of a transaction and converts all values to HEX + * + * @method inputTransactionFormatter + * @param {Object} transaction options + * @returns object +*/ +var inputTransactionFormatter = function (options){ -var utils = require('./utils'); -var c = require('./const'); + // make code -> data + if (options.code) { + options.data = options.code; + delete options.code; + } + + ['gasPrice', 'gas', 'value'].forEach(function(key){ + options[key] = utils.fromDecimal(options[key]); + }); + + return options; +}; + +/** + * Formats the output of a transaction to its proper values + * + * @method outputTransactionFormatter + * @param {Object} transaction + * @returns {Object} transaction +*/ +var outputTransactionFormatter = function (tx){ + tx.gas = utils.toDecimal(tx.gas); + tx.gasPrice = utils.toBigNumber(tx.gasPrice); + tx.value = utils.toBigNumber(tx.value); + return tx; +}; + +/** + * Formats the input of a call and converts all values to HEX + * + * @method inputCallFormatter + * @param {Object} transaction options + * @returns object +*/ +var inputCallFormatter = function (options){ + + // make code -> data + if (options.code) { + options.data = options.code; + delete options.code; + } -/// @param string string to be padded -/// @param number of characters that result string should have -/// @param sign, by default 0 -/// @returns right aligned string -var padLeft = function (string, chars, sign) { - return new Array(chars - string.length + 1).join(sign ? sign : "0") + string; + return options; }; -/// Formats input value to byte representation of int -/// If value is negative, return it's two's complement -/// If the value is floating point, round it down -/// @returns right-aligned byte representation of int -var formatInputInt = function (value) { - /*jshint maxcomplexity:7 */ - var padding = c.ETH_PADDING * 2; - if (value instanceof BigNumber || typeof value === 'number') { - if (typeof value === 'number') - value = new BigNumber(value); - BigNumber.config(c.ETH_BIGNUMBER_ROUNDING_MODE); - value = value.round(); - - if (value.lessThan(0)) - value = new BigNumber("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff", 16).plus(value).plus(1); - value = value.toString(16); + +/** + * Formats the output of a block to its proper values + * + * @method outputBlockFormatter + * @param {Object} block object + * @returns {Object} block object +*/ +var outputBlockFormatter = function(block){ + + // transform to number + block.gasLimit = utils.toDecimal(block.gasLimit); + block.gasUsed = utils.toDecimal(block.gasUsed); + block.size = utils.toDecimal(block.size); + block.timestamp = utils.toDecimal(block.timestamp); + block.number = utils.toDecimal(block.number); + + block.minGasPrice = utils.toBigNumber(block.minGasPrice); + block.difficulty = utils.toBigNumber(block.difficulty); + block.totalDifficulty = utils.toBigNumber(block.totalDifficulty); + + if(block.transactions instanceof Array) { + block.transactions.forEach(function(item){ + if(!utils.isString(item)) + return outputTransactionFormatter(item); + }); } - else if (value.indexOf('0x') === 0) - value = value.substr(2); - else if (typeof value === 'string') - value = formatInputInt(new BigNumber(value)); - else - value = (+value).toString(16); - return padLeft(value, padding); -}; -/// Formats input value to byte representation of string -/// @returns left-algined byte representation of string -var formatInputString = function (value) { - return utils.fromAscii(value, c.ETH_PADDING).substr(2); + return block; }; -/// Formats input value to byte representation of bool -/// @returns right-aligned byte representation bool -var formatInputBool = function (value) { - return '000000000000000000000000000000000000000000000000000000000000000' + (value ? '1' : '0'); -}; +/** + * Formats the output of a log + * + * @method outputLogFormatter + * @param {Object} log object + * @returns {Object} log +*/ +var outputLogFormatter = function(log){ + log.blockNumber = utils.toDecimal(log.blockNumber); + log.transactionIndex = utils.toDecimal(log.transactionIndex); + log.logIndex = utils.toDecimal(log.logIndex); -/// Formats input value to byte representation of real -/// Values are multiplied by 2^m and encoded as integers -/// @returns byte representation of real -var formatInputReal = function (value) { - return formatInputInt(new BigNumber(value).times(new BigNumber(2).pow(128))); + return log; }; -/// Check if input value is negative -/// @param value is hex format -/// @returns true if it is negative, otherwise false -var signedIsNegative = function (value) { - return (new BigNumber(value.substr(0, 1), 16).toString(2).substr(0, 1)) === '1'; -}; +/** + * Formats the input of a whisper post and converts all values to HEX + * + * @method inputPostFormatter + * @param {Object} transaction object + * @returns {Object} +*/ +var inputPostFormatter = function(post){ -/// Formats input right-aligned input bytes to int -/// @returns right-aligned input bytes formatted to int -var formatOutputInt = function (value) { - value = value || "0"; - // check if it's negative number - // it it is, return two's complement - if (signedIsNegative(value)) { - return new BigNumber(value, 16).minus(new BigNumber('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 16)).minus(1); - } - return new BigNumber(value, 16); -}; + post.payload = utils.toHex(post.payload); + post.ttl = utils.fromDecimal(post.ttl); + post.priority = utils.fromDecimal(post.priority); -/// Formats big right-aligned input bytes to uint -/// @returns right-aligned input bytes formatted to uint -var formatOutputUInt = function (value) { - value = value || "0"; - return new BigNumber(value, 16); -}; + if(!(post.topics instanceof Array)) + post.topics = [post.topics]; -/// @returns input bytes formatted to real -var formatOutputReal = function (value) { - return formatOutputInt(value).dividedBy(new BigNumber(2).pow(128)); -}; -/// @returns input bytes formatted to ureal -var formatOutputUReal = function (value) { - return formatOutputUInt(value).dividedBy(new BigNumber(2).pow(128)); -}; + // format the following options + post.topics = post.topics.map(function(topic){ + return utils.fromAscii(topic); + }); -/// @returns right-aligned input bytes formatted to hex -var formatOutputHash = function (value) { - return "0x" + value; + return post; }; -/// @returns right-aligned input bytes formatted to bool -var formatOutputBool = function (value) { - return value === '0000000000000000000000000000000000000000000000000000000000000001' ? true : false; -}; +/** + * Formats the output of a received post message + * + * @method outputPostFormatter + * @param {Object} + * @returns {Object} + */ +var outputPostFormatter = function(post){ + + post.expiry = utils.toDecimal(post.expiry); + post.sent = utils.toDecimal(post.sent); + post.ttl = utils.toDecimal(post.ttl); + post.workProved = utils.toDecimal(post.workProved); + post.payloadRaw = post.payload; + post.payload = utils.toAscii(post.payload); + + if(post.payload.indexOf('{') === 0 || post.payload.indexOf('[') === 0) { + try { + post.payload = JSON.parse(post.payload); + } catch (e) { } + } -/// @returns left-aligned input bytes formatted to ascii string -var formatOutputString = function (value) { - return utils.toAscii(value); -}; + // format the following options + post.topics = post.topics.map(function(topic){ + return utils.toAscii(topic); + }); -/// @returns right-aligned input bytes formatted to address -var formatOutputAddress = function (value) { - return "0x" + value.slice(value.length - 40, value.length); + return post; }; - module.exports = { - formatInputInt: formatInputInt, - formatInputString: formatInputString, - formatInputBool: formatInputBool, - formatInputReal: formatInputReal, - formatOutputInt: formatOutputInt, - formatOutputUInt: formatOutputUInt, - formatOutputReal: formatOutputReal, - formatOutputUReal: formatOutputUReal, - formatOutputHash: formatOutputHash, - formatOutputBool: formatOutputBool, - formatOutputString: formatOutputString, - formatOutputAddress: formatOutputAddress + convertToBigNumber: convertToBigNumber, + inputTransactionFormatter: inputTransactionFormatter, + outputTransactionFormatter: outputTransactionFormatter, + inputCallFormatter: inputCallFormatter, + outputBlockFormatter: outputBlockFormatter, + outputLogFormatter: outputLogFormatter, + inputPostFormatter: inputPostFormatter, + outputPostFormatter: outputPostFormatter }; -},{"./const":2,"./utils":16}],9:[function(require,module,exports){ +},{"../utils/utils":5}],13:[function(require,module,exports){ /* This file is part of ethereum.js. @@ -1034,10 +2333,11 @@ module.exports = { You should have received a copy of the GNU Lesser General Public License along with ethereum.js. If not, see . */ -/** @file httpsync.js +/** @file httpprovider.js * @authors: * Marek Kotewicz * Marian Oancea + * Fabian Vogelsteller * @date 2014 */ @@ -1045,29 +2345,49 @@ if ("build" !== 'build') {/* var XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest; // jshint ignore:line */} -var HttpSyncProvider = function (host) { +var HttpProvider = function (host) { + this.name = 'HTTP'; this.handlers = []; - this.host = host || 'http://127.0.0.1:8080'; + this.host = host || 'http://localhost:8080'; }; -HttpSyncProvider.prototype.send = function (payload) { - //var data = formatJsonRpcObject(payload); - +HttpProvider.prototype.send = function (payload, callback) { var request = new XMLHttpRequest(); - request.open('POST', this.host, false); - request.send(JSON.stringify(payload)); - var result = request.responseText; - // check request.status - if(request.status !== 200) - return; - return JSON.parse(result); + // ASYNC + if(typeof callback === 'function') { + request.onreadystatechange = function() { + if(request.readyState === 4) { + var result = ''; + try { + result = JSON.parse(request.responseText); + } catch(error) { + result = error; + } + callback(result, request.status); + } + }; + + request.open('POST', this.host, true); + request.send(JSON.stringify(payload)); + + // SYNC + } else { + request.open('POST', this.host, false); + request.send(JSON.stringify(payload)); + + // check request.status + if(request.status !== 200) + return; + return JSON.parse(request.responseText); + + } }; -module.exports = HttpSyncProvider; +module.exports = HttpProvider; -},{}],10:[function(require,module,exports){ +},{}],14:[function(require,module,exports){ /* This file is part of ethereum.js. @@ -1134,7 +2454,50 @@ module.exports = { -},{}],11:[function(require,module,exports){ +},{}],15:[function(require,module,exports){ +/* + This file is part of ethereum.js. + + ethereum.js is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + ethereum.js 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with ethereum.js. If not, see . +*/ +/** @file eth.js + * @authors: + * Marek Kotewicz + * @date 2015 + */ + +var utils = require('../utils/utils'); + +/// @returns an array of objects describing web3.eth api methods +var methods = [ + // { name: 'getBalance', call: 'eth_balanceAt', outputFormatter: formatters.convertToBigNumber}, +]; + +/// @returns an array of objects describing web3.eth api properties +var properties = [ + { name: 'listening', getter: 'net_listening'}, + { name: 'peerCount', getter: 'net_peerCount', outputFormatter: utils.toDecimal }, +]; + + +module.exports = { + methods: methods, + properties: properties +}; + + +},{"../utils/utils":5}],16:[function(require,module,exports){ /* This file is part of ethereum.js. @@ -1169,7 +2532,7 @@ QtSyncProvider.prototype.send = function (payload) { module.exports = QtSyncProvider; -},{}],12:[function(require,module,exports){ +},{}],17:[function(require,module,exports){ /* This file is part of ethereum.js. @@ -1191,12 +2554,13 @@ module.exports = QtSyncProvider; * Jeffrey Wilcke * Marek Kotewicz * Marian Oancea + * Fabian Vogelsteller * Gav Wood * @date 2014 */ var jsonrpc = require('./jsonrpc'); -var c = require('./const'); +var c = require('../utils/config'); /** * It's responsible for passing messages to providers @@ -1208,7 +2572,25 @@ var requestManager = function() { var timeout = null; var provider; - var send = function (data) { + var send = function (data, callback) { + /*jshint maxcomplexity: 8 */ + + // FORMAT BASED ON ONE FORMATTER function + if(typeof data.inputFormatter === 'function') { + data.params = Array.prototype.map.call(data.params, function(item, index){ + // format everything besides the defaultblock, which is already formated + return (!data.addDefaultblock || index+1 < data.addDefaultblock) ? data.inputFormatter(item) : item; + }); + + // FORMAT BASED ON the input FORMATTER ARRAY + } else if(data.inputFormatter instanceof Array) { + data.params = Array.prototype.map.call(data.inputFormatter, function(formatter, index){ + // format everything besides the defaultblock, which is already formated + return (!data.addDefaultblock || index+1 < data.addDefaultblock) ? formatter(data.params[index]) : data.params[index]; + }); + } + + var payload = jsonrpc.toPayload(data.method, data.params); if (!provider) { @@ -1216,14 +2598,42 @@ var requestManager = function() { return null; } - var result = provider.send(payload); + // HTTP ASYNC (only when callback is given, and it a HttpProvidor) + if(typeof callback === 'function' && provider.name === 'HTTP'){ + provider.send(payload, function(result, status){ + + if (!jsonrpc.isValidResponse(result)) { + if(typeof result === 'object' && result.error && result.error.message) { + console.error(result.error.message); + callback(result.error); + } else { + callback(new Error({ + status: status, + error: result, + message: 'Bad Request' + })); + } + return null; + } + + // format the output + callback(null, (typeof data.outputFormatter === 'function') ? data.outputFormatter(result.result) : result.result); + }); - if (!jsonrpc.isValidResponse(result)) { - console.log(result); - return null; + // SYNC + } else { + var result = provider.send(payload); + + if (!jsonrpc.isValidResponse(result)) { + if(typeof result === 'object' && result.error && result.error.message) + console.error(result.error.message); + return null; + } + + // format the output + return (typeof data.outputFormatter === 'function') ? data.outputFormatter(result.result) : result.result; } - return result.result; }; var setProvider = function (p) { @@ -1260,11 +2670,13 @@ var requestManager = function() { var poll = function () { polls.forEach(function (data) { - var result = send(data.data); - if (!(result instanceof Array) || result.length === 0) { - return; - } - data.callback(result); + // send async + send(data.data, function(error, result){ + if (!(result instanceof Array) || result.length === 0) { + return; + } + data.callback(result); + }); }); timeout = setTimeout(poll, c.ETH_POLLING_TIMEOUT); }; @@ -1283,7 +2695,7 @@ var requestManager = function() { module.exports = requestManager; -},{"./const":2,"./jsonrpc":10}],13:[function(require,module,exports){ +},{"../utils/config":4,"./jsonrpc":14}],18:[function(require,module,exports){ /* This file is part of ethereum.js. @@ -1306,148 +2718,28 @@ module.exports = requestManager; * @date 2015 */ +var formatters = require('./formatters'); + /// @returns an array of objects describing web3.shh api methods var methods = function () { - return [ - { name: 'post', call: 'shh_post' }, - { name: 'newIdentity', call: 'shh_newIdentity' }, - { name: 'haveIdentity', call: 'shh_haveIdentity' }, - { name: 'newGroup', call: 'shh_newGroup' }, - { name: 'addToGroup', call: 'shh_addToGroup' } - ]; -}; - -module.exports = { - methods: methods -}; - - -},{}],14:[function(require,module,exports){ -/* - This file is part of ethereum.js. - - ethereum.js is free software: you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ethereum.js 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 Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with ethereum.js. If not, see . -*/ -/** @file signature.js - * @authors: - * Marek Kotewicz - * @date 2015 - */ - -var web3 = require('./web3'); -var c = require('./const'); - -/// @param function name for which we want to get signature -/// @returns signature of function with given name -var functionSignatureFromAscii = function (name) { - return web3.sha3(web3.fromAscii(name)).slice(0, 2 + c.ETH_SIGNATURE_LENGTH * 2); -}; - -/// @param event name for which we want to get signature -/// @returns signature of event with given name -var eventSignatureFromAscii = function (name) { - return web3.sha3(web3.fromAscii(name)); -}; - -module.exports = { - functionSignatureFromAscii: functionSignatureFromAscii, - eventSignatureFromAscii: eventSignatureFromAscii -}; - - -},{"./const":2,"./web3":18}],15:[function(require,module,exports){ -/* - This file is part of ethereum.js. - - ethereum.js is free software: you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ethereum.js 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 Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with ethereum.js. If not, see . -*/ -/** @file types.js - * @authors: - * Marek Kotewicz - * @date 2015 - */ - -var f = require('./formatters'); - -/// @param expected type prefix (string) -/// @returns function which checks if type has matching prefix. if yes, returns true, otherwise false -var prefixedType = function (prefix) { - return function (type) { - return type.indexOf(prefix) === 0; - }; -}; - -/// @param expected type name (string) -/// @returns function which checks if type is matching expected one. if yes, returns true, otherwise false -var namedType = function (name) { - return function (type) { - return name === type; - }; -}; - -/// Setups input formatters for solidity types -/// @returns an array of input formatters -var inputTypes = function () { - - return [ - { type: prefixedType('uint'), format: f.formatInputInt }, - { type: prefixedType('int'), format: f.formatInputInt }, - { type: prefixedType('hash'), format: f.formatInputInt }, - { type: prefixedType('string'), format: f.formatInputString }, - { type: prefixedType('real'), format: f.formatInputReal }, - { type: prefixedType('ureal'), format: f.formatInputReal }, - { type: namedType('address'), format: f.formatInputInt }, - { type: namedType('bool'), format: f.formatInputBool } - ]; -}; - -/// Setups output formaters for solidity types -/// @returns an array of output formatters -var outputTypes = function () { - - return [ - { type: prefixedType('uint'), format: f.formatOutputUInt }, - { type: prefixedType('int'), format: f.formatOutputInt }, - { type: prefixedType('hash'), format: f.formatOutputHash }, - { type: prefixedType('string'), format: f.formatOutputString }, - { type: prefixedType('real'), format: f.formatOutputReal }, - { type: prefixedType('ureal'), format: f.formatOutputUReal }, - { type: namedType('address'), format: f.formatOutputAddress }, - { type: namedType('bool'), format: f.formatOutputBool } + return [ + { name: 'post', call: 'shh_post', inputFormatter: formatters.inputPostFormatter }, + { name: 'newIdentity', call: 'shh_newIdentity' }, + { name: 'hasIdentity', call: 'shh_hasIdentity' }, + { name: 'newGroup', call: 'shh_newGroup' }, + { name: 'addToGroup', call: 'shh_addToGroup' }, + + // deprecated + { name: 'haveIdentity', call: 'shh_haveIdentity', newMethod: 'shh.hasIdentity' }, ]; }; module.exports = { - prefixedType: prefixedType, - namedType: namedType, - inputTypes: inputTypes, - outputTypes: outputTypes + methods: methods }; -},{"./formatters":8}],16:[function(require,module,exports){ +},{"./formatters":12}],19:[function(require,module,exports){ /* This file is part of ethereum.js. @@ -1464,135 +2756,34 @@ module.exports = { You should have received a copy of the GNU Lesser General Public License along with ethereum.js. If not, see . */ -/** @file utils.js +/** @file signature.js * @authors: * Marek Kotewicz * @date 2015 */ -var c = require('./const'); - -/// Finds first index of array element matching pattern -/// @param array -/// @param callback pattern -/// @returns index of element -var findIndex = function (array, callback) { - var end = false; - var i = 0; - for (; i < array.length && !end; i++) { - end = callback(array[i]); - } - return end ? i - 1 : -1; -}; - -/// @returns ascii string representation of hex value prefixed with 0x -var toAscii = function(hex) { -// Find termination - var str = ""; - var i = 0, l = hex.length; - if (hex.substring(0, 2) === '0x') { - i = 2; - } - for (; i < l; i+=2) { - var code = parseInt(hex.substr(i, 2), 16); - if (code === 0) { - break; - } - - str += String.fromCharCode(code); - } - - return str; -}; - -var toHex = function(str) { - var hex = ""; - for(var i = 0; i < str.length; i++) { - var n = str.charCodeAt(i).toString(16); - hex += n.length < 2 ? '0' + n : n; - } - - return hex; -}; - -/// @returns hex representation (prefixed by 0x) of ascii string -var fromAscii = function(str, pad) { - pad = pad === undefined ? 0 : pad; - var hex = toHex(str); - while (hex.length < pad*2) - hex += "00"; - return "0x" + hex; -}; - -/// @returns display name for function/event eg. multiply(uint256) -> multiply -var extractDisplayName = function (name) { - var length = name.indexOf('('); - return length !== -1 ? name.substr(0, length) : name; -}; - -/// @returns overloaded part of function/event name -var extractTypeName = function (name) { - /// TODO: make it invulnerable - var length = name.indexOf('('); - return length !== -1 ? name.substr(length + 1, name.length - 1 - (length + 1)).replace(' ', '') : ""; -}; - -/// Filters all function from input abi -/// @returns abi array with filtered objects of type 'function' -var filterFunctions = function (json) { - return json.filter(function (current) { - return current.type === 'function'; - }); -}; +var web3 = require('../web3'); +var c = require('../utils/config'); -/// Filters all events form input abi -/// @returns abi array with filtered objects of type 'event' -var filterEvents = function (json) { - return json.filter(function (current) { - return current.type === 'event'; - }); +/// @param function name for which we want to get signature +/// @returns signature of function with given name +var functionSignatureFromAscii = function (name) { + return web3.sha3(web3.fromAscii(name)).slice(0, 2 + c.ETH_SIGNATURE_LENGTH * 2); }; -/// used to transform value/string to eth string -/// TODO: use BigNumber.js to parse int -/// TODO: add tests for it! -var toEth = function (str) { - /*jshint maxcomplexity:7 */ - var val = typeof str === "string" ? str.indexOf('0x') === 0 ? parseInt(str.substr(2), 16) : parseInt(str) : str; - var unit = 0; - var units = c.ETH_UNITS; - while (val > 3000 && unit < units.length - 1) - { - val /= 1000; - unit++; - } - var s = val.toString().length < val.toFixed(2).length ? val.toString() : val.toFixed(2); - var replaceFunction = function($0, $1, $2) { - return $1 + ',' + $2; - }; - - while (true) { - var o = s; - s = s.replace(/(\d)(\d\d\d[\.\,])/, replaceFunction); - if (o === s) - break; - } - return s + ' ' + units[unit]; +/// @param event name for which we want to get signature +/// @returns signature of event with given name +var eventSignatureFromAscii = function (name) { + return web3.sha3(web3.fromAscii(name)); }; module.exports = { - findIndex: findIndex, - toAscii: toAscii, - fromAscii: fromAscii, - extractDisplayName: extractDisplayName, - extractTypeName: extractTypeName, - filterFunctions: filterFunctions, - filterEvents: filterEvents, - toEth: toEth + functionSignatureFromAscii: functionSignatureFromAscii, + eventSignatureFromAscii: eventSignatureFromAscii }; -},{"./const":2}],17:[function(require,module,exports){ +},{"../utils/config":4,"../web3":6}],20:[function(require,module,exports){ /* This file is part of ethereum.js. @@ -1615,16 +2806,16 @@ module.exports = { * @date 2015 */ -/// @returns an array of objects describing web3.eth.watch api methods +/// @returns an array of objects describing web3.eth.filter api methods var eth = function () { var newFilter = function (args) { - return typeof args[0] === 'string' ? 'eth_newFilterString' : 'eth_newFilter'; + return typeof args[0] === 'string' ? 'eth_newBlockFilter' : 'eth_newFilter'; }; return [ { name: 'newFilter', call: newFilter }, { name: 'uninstallFilter', call: 'eth_uninstallFilter' }, - { name: 'getMessages', call: 'eth_filterLogs' } + { name: 'getLogs', call: 'eth_getFilterLogs' } ]; }; @@ -1633,7 +2824,7 @@ var shh = function () { return [ { name: 'newFilter', call: 'shh_newFilter' }, { name: 'uninstallFilter', call: 'shh_uninstallFilter' }, - { name: 'getMessages', call: 'shh_getMessages' } + { name: 'getLogs', call: 'shh_getMessages' } ]; }; @@ -1643,215 +2834,20 @@ module.exports = { }; -},{}],18:[function(require,module,exports){ -/* - This file is part of ethereum.js. - - ethereum.js is free software: you can redistribute it and/or modify - it under the terms of the GNU Lesser General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - ethereum.js 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 Lesser General Public License for more details. - - You should have received a copy of the GNU Lesser General Public License - along with ethereum.js. If not, see . -*/ -/** @file web3.js - * @authors: - * Jeffrey Wilcke - * Marek Kotewicz - * Marian Oancea - * Gav Wood - * @date 2014 - */ - -if ("build" !== 'build') {/* - var BigNumber = require('bignumber.js'); -*/} - -var eth = require('./eth'); -var db = require('./db'); -var shh = require('./shh'); -var watches = require('./watches'); -var filter = require('./filter'); -var utils = require('./utils'); -var requestManager = require('./requestmanager'); - -/// @returns an array of objects describing web3 api methods -var web3Methods = function () { - return [ - { name: 'sha3', call: 'web3_sha3' } - ]; -}; - -/// creates methods in a given object based on method description on input -/// setups api calls for these methods -var setupMethods = function (obj, methods) { - methods.forEach(function (method) { - obj[method.name] = function () { - var args = Array.prototype.slice.call(arguments); - var call = typeof method.call === 'function' ? method.call(args) : method.call; - return web3.manager.send({ - method: call, - params: args - }); - }; - }); -}; - -/// creates properties in a given object based on properties description on input -/// setups api calls for these properties -var setupProperties = function (obj, properties) { - properties.forEach(function (property) { - var proto = {}; - proto.get = function () { - return web3.manager.send({ - method: property.getter - }); - }; - - if (property.setter) { - proto.set = function (val) { - return web3.manager.send({ - method: property.setter, - params: [val] - }); - }; - } - Object.defineProperty(obj, property.name, proto); - }); -}; - -/*jshint maxparams:4 */ -var startPolling = function (method, id, callback, uninstall) { - web3.manager.startPolling({ - method: method, - params: [id] - }, id, callback, uninstall); -}; -/*jshint maxparams:3 */ - -var stopPolling = function (id) { - web3.manager.stopPolling(id); -}; - -var ethWatch = { - startPolling: startPolling.bind(null, 'eth_changed'), - stopPolling: stopPolling -}; - -var shhWatch = { - startPolling: startPolling.bind(null, 'shh_changed'), - stopPolling: stopPolling -}; - -/// setups web3 object, and it's in-browser executed methods -var web3 = { - manager: requestManager(), - providers: {}, - - /// @returns ascii string representation of hex value prefixed with 0x - toAscii: utils.toAscii, - - /// @returns hex representation (prefixed by 0x) of ascii string - fromAscii: utils.fromAscii, - - /// @returns decimal representaton of hex value prefixed by 0x - toDecimal: function (val) { - // remove 0x and place 0, if it's required - val = val.length > 2 ? val.substring(2) : "0"; - return (new BigNumber(val, 16).toString(10)); - }, - - /// @returns hex representation (prefixed by 0x) of decimal value - fromDecimal: function (val) { - return "0x" + (new BigNumber(val).toString(16)); - }, - - /// used to transform value/string to eth string - toEth: utils.toEth, - - /// eth object prototype - eth: { - contractFromAbi: function (abi) { - return function(addr) { - // Default to address of Config. TODO: rremove prior to genesis. - addr = addr || '0xc6d9d2cd449a754c494264e1809c50e34d64562b'; - var ret = web3.eth.contract(addr, abi); - ret.address = addr; - return ret; - }; - }, - - canary: function (abi) { - return function(addr) { - // Default to address of Config. TODO: rremove prior to genesis. - addr = addr || '0xc6d9d2cd449a754c494264e1809c50e34d64562b'; - return addr; - }; - }, - - /// @param filter may be a string, object or event - /// @param indexed is optional, this is an object with optional event indexed params - /// @param options is optional, this is an object with optional event options ('max'...) - /// TODO: fix it, 4 params? no way - /*jshint maxparams:4 */ - watch: function (fil, indexed, options, formatter) { - if (fil._isEvent) { - return fil(indexed, options); - } - return filter(fil, ethWatch, formatter); - } - /*jshint maxparams:3 */ - }, - - /// db object prototype - db: {}, - - /// shh object prototype - shh: { - /// @param filter may be a string, object or event - watch: function (fil) { - return filter(fil, shhWatch); - } - }, - setProvider: function (provider) { - web3.manager.setProvider(provider); - }, - - /// Should be called to reset state of web3 object - /// Resets everything except manager - reset: function () { - web3.manager.reset(); - } -}; - -/// setups all api methods -setupMethods(web3, web3Methods()); -setupMethods(web3.eth, eth.methods()); -setupProperties(web3.eth, eth.properties()); -setupMethods(web3.db, db.methods()); -setupMethods(web3.shh, shh.methods()); -setupMethods(ethWatch, watches.eth()); -setupMethods(shhWatch, watches.shh()); - -module.exports = web3; - - -},{"./db":4,"./eth":5,"./filter":7,"./requestmanager":12,"./shh":13,"./utils":16,"./watches":17}],"web3":[function(require,module,exports){ +},{}],21:[function(require,module,exports){ +module.exports={ + "version": "0.1.3" +} +},{}],"web3":[function(require,module,exports){ var web3 = require('./lib/web3'); -web3.providers.HttpSyncProvider = require('./lib/httpsync'); -web3.providers.QtSyncProvider = require('./lib/qtsync'); -web3.eth.contract = require('./lib/contract'); -web3.abi = require('./lib/abi'); +web3.providers.HttpProvider = require('./lib/web3/httpprovider'); +web3.providers.QtSyncProvider = require('./lib/web3/qtsync'); +web3.eth.contract = require('./lib/web3/contract'); +web3.abi = require('./lib/solidity/abi'); module.exports = web3; -},{"./lib/abi":1,"./lib/contract":3,"./lib/httpsync":9,"./lib/qtsync":11,"./lib/web3":18}]},{},["web3"]) +},{"./lib/solidity/abi":1,"./lib/web3":6,"./lib/web3/contract":7,"./lib/web3/httpprovider":13,"./lib/web3/qtsync":16}]},{},["web3"]) //# sourceMappingURL=ethereum.js.map \ No newline at end of file diff --git a/libjsqrc/ethereumjs/dist/ethereum.js.map b/libjsqrc/ethereumjs/dist/ethereum.js.map index 2b788900e..44144743f 100644 --- a/libjsqrc/ethereumjs/dist/ethereum.js.map +++ b/libjsqrc/ethereumjs/dist/ethereum.js.map @@ -2,50 +2,56 @@ "version": 3, "sources": [ "node_modules/browserify/node_modules/browser-pack/_prelude.js", - "lib/abi.js", - "lib/const.js", - "lib/contract.js", - "lib/db.js", - "lib/eth.js", - "lib/event.js", - "lib/filter.js", - "lib/formatters.js", - "lib/httpsync.js", - "lib/jsonrpc.js", - "lib/qtsync.js", - "lib/requestmanager.js", - "lib/shh.js", - "lib/signature.js", - "lib/types.js", - "lib/utils.js", - "lib/watches.js", + "lib/solidity/abi.js", + "lib/solidity/formatters.js", + "lib/solidity/types.js", + "lib/utils/config.js", + "lib/utils/utils.js", "lib/web3.js", + "lib/web3/contract.js", + "lib/web3/db.js", + "lib/web3/eth.js", + "lib/web3/event.js", + "lib/web3/filter.js", + "lib/web3/formatters.js", + "lib/web3/httpprovider.js", + "lib/web3/jsonrpc.js", + "lib/web3/net.js", + "lib/web3/qtsync.js", + "lib/web3/requestmanager.js", + "lib/web3/shh.js", + "lib/web3/signature.js", + "lib/web3/watches.js", + "version.json", "index.js" ], "names": [], - "mappings": "AAAA;ACAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACzMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACzDA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AC3NA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACnCA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACrFA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AC1IA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACjHA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AC3JA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AChDA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACjEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACjCA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AChHA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACrCA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AC1CA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AC/EA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AC/IA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACjDA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACrMA;AACA;AACA;AACA;AACA;AACA;AACA;AACA", + "mappings": "AAAA;ACAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACrPA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACtNA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AC7EA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACvEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AChcA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AC5SA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACnPA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACpCA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACpJA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AC1IA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AC3KA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AC3MA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACrEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACjEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACzCA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACjCA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACjKA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AC1CA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AC1CA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;ACjDA;AACA;AACA;;ACFA;AACA;AACA;AACA;AACA;AACA;AACA;AACA", "file": "generated.js", "sourceRoot": "", "sourcesContent": [ "(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require==\"function\"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error(\"Cannot find module '\"+o+\"'\");throw f.code=\"MODULE_NOT_FOUND\",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require==\"function\"&&require;for(var o=0;o.\n*/\n/** @file abi.js\n * @authors:\n * Marek Kotewicz \n * Gav Wood \n * @date 2014\n */\n\nvar utils = require('./utils');\nvar types = require('./types');\nvar c = require('./const');\nvar f = require('./formatters');\n\nvar displayTypeError = function (type) {\n console.error('parser does not support type: ' + type);\n};\n\n/// This method should be called if we want to check if givent type is an array type\n/// @returns true if it is, otherwise false\nvar arrayType = function (type) {\n return type.slice(-2) === '[]';\n};\n\nvar dynamicTypeBytes = function (type, value) {\n // TODO: decide what to do with array of strings\n if (arrayType(type) || type === 'string') // only string itself that is dynamic; stringX is static length.\n return f.formatInputInt(value.length);\n return \"\";\n};\n\nvar inputTypes = types.inputTypes();\n\n/// Formats input params to bytes\n/// @param abi contract method inputs\n/// @param array of params that will be formatted to bytes\n/// @returns bytes representation of input params\nvar formatInput = function (inputs, params) {\n var bytes = \"\";\n var toAppendConstant = \"\";\n var toAppendArrayContent = \"\";\n\n /// first we iterate in search for dynamic\n inputs.forEach(function (input, index) {\n bytes += dynamicTypeBytes(input.type, params[index]);\n });\n\n inputs.forEach(function (input, i) {\n /*jshint maxcomplexity:5 */\n var typeMatch = false;\n for (var j = 0; j < inputTypes.length && !typeMatch; j++) {\n typeMatch = inputTypes[j].type(inputs[i].type, params[i]);\n }\n if (!typeMatch) {\n displayTypeError(inputs[i].type);\n }\n\n var formatter = inputTypes[j - 1].format;\n\n if (arrayType(inputs[i].type))\n toAppendArrayContent += params[i].reduce(function (acc, curr) {\n return acc + formatter(curr);\n }, \"\");\n else if (inputs[i].type === 'string')\n toAppendArrayContent += formatter(params[i]);\n else\n toAppendConstant += formatter(params[i]);\n });\n\n bytes += toAppendConstant + toAppendArrayContent;\n\n return bytes;\n};\n\nvar dynamicBytesLength = function (type) {\n if (arrayType(type) || type === 'string') // only string itself that is dynamic; stringX is static length.\n return c.ETH_PADDING * 2;\n return 0;\n};\n\nvar outputTypes = types.outputTypes();\n\n/// Formats output bytes back to param list\n/// @param contract abi method outputs\n/// @param bytes representtion of output\n/// @returns array of output params\nvar formatOutput = function (outs, output) {\n\n output = output.slice(2);\n var result = [];\n var padding = c.ETH_PADDING * 2;\n\n var dynamicPartLength = outs.reduce(function (acc, curr) {\n return acc + dynamicBytesLength(curr.type);\n }, 0);\n\n var dynamicPart = output.slice(0, dynamicPartLength);\n output = output.slice(dynamicPartLength);\n\n outs.forEach(function (out, i) {\n /*jshint maxcomplexity:6 */\n var typeMatch = false;\n for (var j = 0; j < outputTypes.length && !typeMatch; j++) {\n typeMatch = outputTypes[j].type(outs[i].type);\n }\n\n if (!typeMatch) {\n displayTypeError(outs[i].type);\n }\n\n var formatter = outputTypes[j - 1].format;\n if (arrayType(outs[i].type)) {\n var size = f.formatOutputUInt(dynamicPart.slice(0, padding));\n dynamicPart = dynamicPart.slice(padding);\n var array = [];\n for (var k = 0; k < size; k++) {\n array.push(formatter(output.slice(0, padding)));\n output = output.slice(padding);\n }\n result.push(array);\n }\n else if (types.prefixedType('string')(outs[i].type)) {\n dynamicPart = dynamicPart.slice(padding);\n result.push(formatter(output.slice(0, padding)));\n output = output.slice(padding);\n } else {\n result.push(formatter(output.slice(0, padding)));\n output = output.slice(padding);\n }\n });\n\n return result;\n};\n\n/// @param json abi for contract\n/// @returns input parser object for given json abi\n/// TODO: refactor creating the parser, do not double logic from contract\nvar inputParser = function (json) {\n var parser = {};\n json.forEach(function (method) {\n var displayName = utils.extractDisplayName(method.name);\n var typeName = utils.extractTypeName(method.name);\n\n var impl = function () {\n var params = Array.prototype.slice.call(arguments);\n return formatInput(method.inputs, params);\n };\n\n if (parser[displayName] === undefined) {\n parser[displayName] = impl;\n }\n\n parser[displayName][typeName] = impl;\n });\n\n return parser;\n};\n\n/// @param json abi for contract\n/// @returns output parser for given json abi\nvar outputParser = function (json) {\n var parser = {};\n json.forEach(function (method) {\n\n var displayName = utils.extractDisplayName(method.name);\n var typeName = utils.extractTypeName(method.name);\n\n var impl = function (output) {\n return formatOutput(method.outputs, output);\n };\n\n if (parser[displayName] === undefined) {\n parser[displayName] = impl;\n }\n\n parser[displayName][typeName] = impl;\n });\n\n return parser;\n};\n\nmodule.exports = {\n inputParser: inputParser,\n outputParser: outputParser,\n formatInput: formatInput,\n formatOutput: formatOutput\n};\n", - "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file const.js\n * @authors:\n * Marek Kotewicz \n * @date 2015\n */\n\n/// required to define ETH_BIGNUMBER_ROUNDING_MODE\nif (\"build\" !== 'build') {/*\n var BigNumber = require('bignumber.js'); // jshint ignore:line\n*/}\n\nvar ETH_UNITS = [ \n 'wei', \n 'Kwei', \n 'Mwei', \n 'Gwei', \n 'szabo', \n 'finney', \n 'ether', \n 'grand', \n 'Mether', \n 'Gether', \n 'Tether', \n 'Pether', \n 'Eether', \n 'Zether', \n 'Yether', \n 'Nether', \n 'Dether', \n 'Vether', \n 'Uether' \n];\n\nmodule.exports = {\n ETH_PADDING: 32,\n ETH_SIGNATURE_LENGTH: 4,\n ETH_UNITS: ETH_UNITS,\n ETH_BIGNUMBER_ROUNDING_MODE: { ROUNDING_MODE: BigNumber.ROUND_DOWN },\n ETH_POLLING_TIMEOUT: 1000\n};\n\n", - "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file contract.js\n * @authors:\n * Marek Kotewicz \n * @date 2014\n */\n\nvar web3 = require('./web3'); \nvar abi = require('./abi');\nvar utils = require('./utils');\nvar eventImpl = require('./event');\nvar signature = require('./signature');\n\nvar exportNatspecGlobals = function (vars) {\n // it's used byt natspec.js\n // TODO: figure out better way to solve this\n web3._currentContractAbi = vars.abi;\n web3._currentContractAddress = vars.address;\n web3._currentContractMethodName = vars.method;\n web3._currentContractMethodParams = vars.params;\n};\n\nvar addFunctionRelatedPropertiesToContract = function (contract) {\n \n contract.call = function (options) {\n contract._isTransact = false;\n contract._options = options;\n return contract;\n };\n\n contract.transact = function (options) {\n contract._isTransact = true;\n contract._options = options;\n return contract;\n };\n\n contract._options = {};\n ['gas', 'gasPrice', 'value', 'from'].forEach(function(p) {\n contract[p] = function (v) {\n contract._options[p] = v;\n return contract;\n };\n });\n\n};\n\nvar addFunctionsToContract = function (contract, desc, address) {\n var inputParser = abi.inputParser(desc);\n var outputParser = abi.outputParser(desc);\n\n // create contract functions\n utils.filterFunctions(desc).forEach(function (method) {\n\n var displayName = utils.extractDisplayName(method.name);\n var typeName = utils.extractTypeName(method.name);\n\n var impl = function () {\n /*jshint maxcomplexity:7 */\n var params = Array.prototype.slice.call(arguments);\n var sign = signature.functionSignatureFromAscii(method.name);\n var parsed = inputParser[displayName][typeName].apply(null, params);\n\n var options = contract._options || {};\n options.to = address;\n options.data = sign + parsed;\n \n var isTransact = contract._isTransact === true || (contract._isTransact !== false && !method.constant);\n var collapse = options.collapse !== false;\n \n // reset\n contract._options = {};\n contract._isTransact = null;\n\n if (isTransact) {\n \n exportNatspecGlobals({\n abi: desc,\n address: address,\n method: method.name,\n params: params\n });\n\n // transactions do not have any output, cause we do not know, when they will be processed\n web3.eth.transact(options);\n return;\n }\n \n var output = web3.eth.call(options);\n var ret = outputParser[displayName][typeName](output);\n if (collapse)\n {\n if (ret.length === 1)\n ret = ret[0];\n else if (ret.length === 0)\n ret = null;\n }\n return ret;\n };\n\n if (contract[displayName] === undefined) {\n contract[displayName] = impl;\n }\n\n contract[displayName][typeName] = impl;\n });\n};\n\nvar addEventRelatedPropertiesToContract = function (contract, desc, address) {\n contract.address = address;\n contract._onWatchEventResult = function (data) {\n var matchingEvent = event.getMatchingEvent(utils.filterEvents(desc));\n var parser = eventImpl.outputParser(matchingEvent);\n return parser(data);\n };\n \n Object.defineProperty(contract, 'topic', {\n get: function() {\n return utils.filterEvents(desc).map(function (e) {\n return signature.eventSignatureFromAscii(e.name);\n });\n }\n });\n\n};\n\nvar addEventsToContract = function (contract, desc, address) {\n // create contract events\n utils.filterEvents(desc).forEach(function (e) {\n\n var impl = function () {\n var params = Array.prototype.slice.call(arguments);\n var sign = signature.eventSignatureFromAscii(e.name);\n var event = eventImpl.inputParser(address, sign, e);\n var o = event.apply(null, params);\n var outputFormatter = function (data) {\n var parser = eventImpl.outputParser(e);\n return parser(data);\n };\n return web3.eth.watch(o, undefined, undefined, outputFormatter);\n };\n \n // this property should be used by eth.filter to check if object is an event\n impl._isEvent = true;\n\n var displayName = utils.extractDisplayName(e.name);\n var typeName = utils.extractTypeName(e.name);\n\n if (contract[displayName] === undefined) {\n contract[displayName] = impl;\n }\n\n contract[displayName][typeName] = impl;\n\n });\n};\n\n\n/**\n * This method should be called when we want to call / transact some solidity method from javascript\n * it returns an object which has same methods available as solidity contract description\n * usage example: \n *\n * var abi = [{\n * name: 'myMethod',\n * inputs: [{ name: 'a', type: 'string' }],\n * outputs: [{name: 'd', type: 'string' }]\n * }]; // contract abi\n *\n * var myContract = web3.eth.contract('0x0123123121', abi); // creation of contract object\n *\n * myContract.myMethod('this is test string param for call'); // myMethod call (implicit, default)\n * myContract.call().myMethod('this is test string param for call'); // myMethod call (explicit)\n * myContract.transact().myMethod('this is test string param for transact'); // myMethod transact\n *\n * @param address - address of the contract, which should be called\n * @param desc - abi json description of the contract, which is being created\n * @returns contract object\n */\n\nvar contract = function (address, desc) {\n\n // workaround for invalid assumption that method.name is the full anonymous prototype of the method.\n // it's not. it's just the name. the rest of the code assumes it's actually the anonymous\n // prototype, so we make it so as a workaround.\n // TODO: we may not want to modify input params, maybe use copy instead?\n desc.forEach(function (method) {\n if (method.name.indexOf('(') === -1) {\n var displayName = method.name;\n var typeName = method.inputs.map(function(i){return i.type; }).join();\n method.name = displayName + '(' + typeName + ')';\n }\n });\n\n var result = {};\n addFunctionRelatedPropertiesToContract(result);\n addFunctionsToContract(result, desc, address);\n addEventRelatedPropertiesToContract(result, desc, address);\n addEventsToContract(result, desc, address);\n\n return result;\n};\n\nmodule.exports = contract;\n\n", - "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file db.js\n * @authors:\n * Marek Kotewicz \n * @date 2015\n */\n\n/// @returns an array of objects describing web3.db api methods\nvar methods = function () {\n return [\n { name: 'put', call: 'db_put' },\n { name: 'get', call: 'db_get' },\n { name: 'putString', call: 'db_putString' },\n { name: 'getString', call: 'db_getString' }\n ];\n};\n\nmodule.exports = {\n methods: methods\n};\n", - "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file eth.js\n * @authors:\n * Marek Kotewicz \n * @date 2015\n */\n\n/// @returns an array of objects describing web3.eth api methods\nvar methods = function () {\n var blockCall = function (args) {\n return typeof args[0] === \"string\" ? \"eth_blockByHash\" : \"eth_blockByNumber\";\n };\n\n var transactionCall = function (args) {\n return typeof args[0] === \"string\" ? 'eth_transactionByHash' : 'eth_transactionByNumber';\n };\n\n var uncleCall = function (args) {\n return typeof args[0] === \"string\" ? 'eth_uncleByHash' : 'eth_uncleByNumber';\n };\n\n var transactionCountCall = function (args) {\n return typeof args[0] === \"string\" ? 'eth_transactionCountByHash' : 'eth_transactionCountByNumber';\n };\n\n var uncleCountCall = function (args) {\n return typeof args[0] === \"string\" ? 'eth_uncleCountByHash' : 'eth_uncleCountByNumber';\n };\n\n return [\n { name: 'balanceAt', call: 'eth_balanceAt' },\n { name: 'stateAt', call: 'eth_stateAt' },\n { name: 'storageAt', call: 'eth_storageAt' },\n { name: 'countAt', call: 'eth_countAt'},\n { name: 'codeAt', call: 'eth_codeAt' },\n { name: 'transact', call: 'eth_transact' },\n { name: 'call', call: 'eth_call' },\n { name: 'block', call: blockCall },\n { name: 'transaction', call: transactionCall },\n { name: 'uncle', call: uncleCall },\n { name: 'compilers', call: 'eth_compilers' },\n { name: 'flush', call: 'eth_flush' },\n { name: 'lll', call: 'eth_lll' },\n { name: 'solidity', call: 'eth_solidity' },\n { name: 'serpent', call: 'eth_serpent' },\n { name: 'logs', call: 'eth_logs' },\n { name: 'transactionCount', call: transactionCountCall },\n { name: 'uncleCount', call: uncleCountCall }\n ];\n};\n\n/// @returns an array of objects describing web3.eth api properties\nvar properties = function () {\n return [\n { name: 'coinbase', getter: 'eth_coinbase', setter: 'eth_setCoinbase' },\n { name: 'listening', getter: 'eth_listening', setter: 'eth_setListening' },\n { name: 'mining', getter: 'eth_mining', setter: 'eth_setMining' },\n { name: 'gasPrice', getter: 'eth_gasPrice' },\n { name: 'accounts', getter: 'eth_accounts' },\n { name: 'peerCount', getter: 'eth_peerCount' },\n { name: 'defaultBlock', getter: 'eth_defaultBlock', setter: 'eth_setDefaultBlock' },\n { name: 'number', getter: 'eth_number'}\n ];\n};\n\nmodule.exports = {\n methods: methods,\n properties: properties\n};\n\n", - "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file event.js\n * @authors:\n * Marek Kotewicz \n * @date 2014\n */\n\nvar abi = require('./abi');\nvar utils = require('./utils');\nvar signature = require('./signature');\n\n/// filter inputs array && returns only indexed (or not) inputs\n/// @param inputs array\n/// @param bool if result should be an array of indexed params on not\n/// @returns array of (not?) indexed params\nvar filterInputs = function (inputs, indexed) {\n return inputs.filter(function (current) {\n return current.indexed === indexed;\n });\n};\n\nvar inputWithName = function (inputs, name) {\n var index = utils.findIndex(inputs, function (input) {\n return input.name === name;\n });\n \n if (index === -1) {\n console.error('indexed param with name ' + name + ' not found');\n return undefined;\n }\n return inputs[index];\n};\n\nvar indexedParamsToTopics = function (event, indexed) {\n // sort keys?\n return Object.keys(indexed).map(function (key) {\n var inputs = [inputWithName(filterInputs(event.inputs, true), key)];\n\n var value = indexed[key];\n if (value instanceof Array) {\n return value.map(function (v) {\n return abi.formatInput(inputs, [v]);\n }); \n }\n return abi.formatInput(inputs, [value]);\n });\n};\n\nvar inputParser = function (address, sign, event) {\n \n // valid options are 'earliest', 'latest', 'offset' and 'max', as defined for 'eth.watch'\n return function (indexed, options) {\n var o = options || {};\n o.address = address;\n o.topic = [];\n o.topic.push(sign);\n if (indexed) {\n o.topic = o.topic.concat(indexedParamsToTopics(event, indexed));\n }\n return o;\n };\n};\n\nvar getArgumentsObject = function (inputs, indexed, notIndexed) {\n var indexedCopy = indexed.slice();\n var notIndexedCopy = notIndexed.slice();\n return inputs.reduce(function (acc, current) {\n var value;\n if (current.indexed)\n value = indexedCopy.splice(0, 1)[0];\n else\n value = notIndexedCopy.splice(0, 1)[0];\n\n acc[current.name] = value;\n return acc;\n }, {}); \n};\n \nvar outputParser = function (event) {\n \n return function (output) {\n var result = {\n event: utils.extractDisplayName(event.name),\n number: output.number,\n hash: output.hash,\n args: {}\n };\n\n output.topics = output.topic; // fallback for go-ethereum\n if (!output.topic) {\n return result;\n }\n \n var indexedOutputs = filterInputs(event.inputs, true);\n var indexedData = \"0x\" + output.topic.slice(1, output.topic.length).map(function (topic) { return topic.slice(2); }).join(\"\");\n var indexedRes = abi.formatOutput(indexedOutputs, indexedData);\n\n var notIndexedOutputs = filterInputs(event.inputs, false);\n var notIndexedRes = abi.formatOutput(notIndexedOutputs, output.data);\n\n result.args = getArgumentsObject(event.inputs, indexedRes, notIndexedRes);\n\n return result;\n };\n};\n\nvar getMatchingEvent = function (events, payload) {\n for (var i = 0; i < events.length; i++) {\n var sign = signature.eventSignatureFromAscii(events[i].name); \n if (sign === payload.topic[0]) {\n return events[i];\n }\n }\n return undefined;\n};\n\n\nmodule.exports = {\n inputParser: inputParser,\n outputParser: outputParser,\n getMatchingEvent: getMatchingEvent\n};\n\n", - "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file filter.js\n * @authors:\n * Jeffrey Wilcke \n * Marek Kotewicz \n * Marian Oancea \n * Gav Wood \n * @date 2014\n */\n\n/// Should be called to check if filter implementation is valid\n/// @returns true if it is, otherwise false\nvar implementationIsValid = function (i) {\n return !!i && \n typeof i.newFilter === 'function' && \n typeof i.getMessages === 'function' && \n typeof i.uninstallFilter === 'function' &&\n typeof i.startPolling === 'function' &&\n typeof i.stopPolling === 'function';\n};\n\n/// This method should be called on options object, to verify deprecated properties && lazy load dynamic ones\n/// @param should be string or object\n/// @returns options string or object\nvar getOptions = function (options) {\n if (typeof options === 'string') {\n return options;\n } \n\n options = options || {};\n\n if (options.topics) {\n console.warn('\"topics\" is deprecated, is \"topic\" instead');\n }\n\n // evaluate lazy properties\n return {\n to: options.to,\n topic: options.topic,\n earliest: options.earliest,\n latest: options.latest,\n max: options.max,\n skip: options.skip,\n address: options.address\n };\n};\n\n/// Should be used when we want to watch something\n/// it's using inner polling mechanism and is notified about changes\n/// @param options are filter options\n/// @param implementation, an abstract polling implementation\n/// @param formatter (optional), callback function which formats output before 'real' callback \nvar filter = function(options, implementation, formatter) {\n if (!implementationIsValid(implementation)) {\n console.error('filter implemenation is invalid');\n return;\n }\n\n options = getOptions(options);\n var callbacks = [];\n var filterId = implementation.newFilter(options);\n var onMessages = function (messages) {\n messages.forEach(function (message) {\n message = formatter ? formatter(message) : message;\n callbacks.forEach(function (callback) {\n callback(message);\n });\n });\n };\n\n implementation.startPolling(filterId, onMessages, implementation.uninstallFilter);\n\n var changed = function (callback) {\n callbacks.push(callback);\n };\n\n var messages = function () {\n return implementation.getMessages(filterId);\n };\n \n var uninstall = function () {\n implementation.stopPolling(filterId);\n implementation.uninstallFilter(filterId);\n callbacks = [];\n };\n\n return {\n changed: changed,\n arrived: changed,\n happened: changed,\n messages: messages,\n logs: messages,\n uninstall: uninstall\n };\n};\n\nmodule.exports = filter;\n\n", - "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file formatters.js\n * @authors:\n * Marek Kotewicz \n * @date 2015\n */\n\nif (\"build\" !== 'build') {/*\n var BigNumber = require('bignumber.js'); // jshint ignore:line\n*/}\n\nvar utils = require('./utils');\nvar c = require('./const');\n\n/// @param string string to be padded\n/// @param number of characters that result string should have\n/// @param sign, by default 0\n/// @returns right aligned string\nvar padLeft = function (string, chars, sign) {\n return new Array(chars - string.length + 1).join(sign ? sign : \"0\") + string;\n};\n\n/// Formats input value to byte representation of int\n/// If value is negative, return it's two's complement\n/// If the value is floating point, round it down\n/// @returns right-aligned byte representation of int\nvar formatInputInt = function (value) {\n /*jshint maxcomplexity:7 */\n var padding = c.ETH_PADDING * 2;\n if (value instanceof BigNumber || typeof value === 'number') {\n if (typeof value === 'number')\n value = new BigNumber(value);\n BigNumber.config(c.ETH_BIGNUMBER_ROUNDING_MODE);\n value = value.round();\n\n if (value.lessThan(0)) \n value = new BigNumber(\"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 16).plus(value).plus(1);\n value = value.toString(16);\n }\n else if (value.indexOf('0x') === 0)\n value = value.substr(2);\n else if (typeof value === 'string')\n value = formatInputInt(new BigNumber(value));\n else\n value = (+value).toString(16);\n return padLeft(value, padding);\n};\n\n/// Formats input value to byte representation of string\n/// @returns left-algined byte representation of string\nvar formatInputString = function (value) {\n return utils.fromAscii(value, c.ETH_PADDING).substr(2);\n};\n\n/// Formats input value to byte representation of bool\n/// @returns right-aligned byte representation bool\nvar formatInputBool = function (value) {\n return '000000000000000000000000000000000000000000000000000000000000000' + (value ? '1' : '0');\n};\n\n/// Formats input value to byte representation of real\n/// Values are multiplied by 2^m and encoded as integers\n/// @returns byte representation of real\nvar formatInputReal = function (value) {\n return formatInputInt(new BigNumber(value).times(new BigNumber(2).pow(128))); \n};\n\n\n/// Check if input value is negative\n/// @param value is hex format\n/// @returns true if it is negative, otherwise false\nvar signedIsNegative = function (value) {\n return (new BigNumber(value.substr(0, 1), 16).toString(2).substr(0, 1)) === '1';\n};\n\n/// Formats input right-aligned input bytes to int\n/// @returns right-aligned input bytes formatted to int\nvar formatOutputInt = function (value) {\n value = value || \"0\";\n // check if it's negative number\n // it it is, return two's complement\n if (signedIsNegative(value)) {\n return new BigNumber(value, 16).minus(new BigNumber('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 16)).minus(1);\n }\n return new BigNumber(value, 16);\n};\n\n/// Formats big right-aligned input bytes to uint\n/// @returns right-aligned input bytes formatted to uint\nvar formatOutputUInt = function (value) {\n value = value || \"0\";\n return new BigNumber(value, 16);\n};\n\n/// @returns input bytes formatted to real\nvar formatOutputReal = function (value) {\n return formatOutputInt(value).dividedBy(new BigNumber(2).pow(128)); \n};\n\n/// @returns input bytes formatted to ureal\nvar formatOutputUReal = function (value) {\n return formatOutputUInt(value).dividedBy(new BigNumber(2).pow(128)); \n};\n\n/// @returns right-aligned input bytes formatted to hex\nvar formatOutputHash = function (value) {\n return \"0x\" + value;\n};\n\n/// @returns right-aligned input bytes formatted to bool\nvar formatOutputBool = function (value) {\n return value === '0000000000000000000000000000000000000000000000000000000000000001' ? true : false;\n};\n\n/// @returns left-aligned input bytes formatted to ascii string\nvar formatOutputString = function (value) {\n return utils.toAscii(value);\n};\n\n/// @returns right-aligned input bytes formatted to address\nvar formatOutputAddress = function (value) {\n return \"0x\" + value.slice(value.length - 40, value.length);\n};\n\n\nmodule.exports = {\n formatInputInt: formatInputInt,\n formatInputString: formatInputString,\n formatInputBool: formatInputBool,\n formatInputReal: formatInputReal,\n formatOutputInt: formatOutputInt,\n formatOutputUInt: formatOutputUInt,\n formatOutputReal: formatOutputReal,\n formatOutputUReal: formatOutputUReal,\n formatOutputHash: formatOutputHash,\n formatOutputBool: formatOutputBool,\n formatOutputString: formatOutputString,\n formatOutputAddress: formatOutputAddress\n};\n\n", - "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file httpsync.js\n * @authors:\n * Marek Kotewicz \n * Marian Oancea \n * @date 2014\n */\n\nif (\"build\" !== 'build') {/*\n var XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest; // jshint ignore:line\n*/}\n\nvar HttpSyncProvider = function (host) {\n this.handlers = [];\n this.host = host || 'http://127.0.0.1:8080';\n};\n\nHttpSyncProvider.prototype.send = function (payload) {\n //var data = formatJsonRpcObject(payload);\n\n var request = new XMLHttpRequest();\n request.open('POST', this.host, false);\n request.send(JSON.stringify(payload));\n\n var result = request.responseText;\n // check request.status\n if(request.status !== 200)\n return;\n return JSON.parse(result);\n};\n\nmodule.exports = HttpSyncProvider;\n\n", + "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file abi.js\n * @authors:\n * Marek Kotewicz \n * Gav Wood \n * @date 2014\n */\n\nvar utils = require('../utils/utils');\nvar c = require('../utils/config');\nvar types = require('./types');\nvar f = require('./formatters');\n\n/**\n * throw incorrect type error\n *\n * @method throwTypeError\n * @param {String} type\n * @throws incorrect type error\n */\nvar throwTypeError = function (type) {\n throw new Error('parser does not support type: ' + type);\n};\n\n/** This method should be called if we want to check if givent type is an array type\n *\n * @method isArrayType\n * @param {String} type name\n * @returns {Boolean} true if it is, otherwise false\n */\nvar isArrayType = function (type) {\n return type.slice(-2) === '[]';\n};\n\n/**\n * This method should be called to return dynamic type length in hex\n *\n * @method dynamicTypeBytes\n * @param {String} type\n * @param {String|Array} dynamic type\n * @return {String} length of dynamic type in hex or empty string if type is not dynamic\n */\nvar dynamicTypeBytes = function (type, value) {\n // TODO: decide what to do with array of strings\n if (isArrayType(type) || type === 'bytes')\n return f.formatInputInt(value.length);\n return \"\";\n};\n\nvar inputTypes = types.inputTypes();\n\n/**\n * Formats input params to bytes\n *\n * @method formatInput\n * @param {Array} abi inputs of method\n * @param {Array} params that will be formatted to bytes\n * @returns bytes representation of input params\n */\nvar formatInput = function (inputs, params) {\n var bytes = \"\";\n var toAppendConstant = \"\";\n var toAppendArrayContent = \"\";\n\n /// first we iterate in search for dynamic\n inputs.forEach(function (input, index) {\n bytes += dynamicTypeBytes(input.type, params[index]);\n });\n\n inputs.forEach(function (input, i) {\n /*jshint maxcomplexity:5 */\n var typeMatch = false;\n for (var j = 0; j < inputTypes.length && !typeMatch; j++) {\n typeMatch = inputTypes[j].type(inputs[i].type, params[i]);\n }\n if (!typeMatch) {\n throwTypeError(inputs[i].type);\n }\n\n var formatter = inputTypes[j - 1].format;\n\n if (isArrayType(inputs[i].type))\n toAppendArrayContent += params[i].reduce(function (acc, curr) {\n return acc + formatter(curr);\n }, \"\");\n else if (inputs[i].type === 'bytes')\n toAppendArrayContent += formatter(params[i]);\n else\n toAppendConstant += formatter(params[i]);\n });\n\n bytes += toAppendConstant + toAppendArrayContent;\n\n return bytes;\n};\n\n/**\n * This method should be called to predict the length of dynamic type\n *\n * @method dynamicBytesLength\n * @param {String} type\n * @returns {Number} length of dynamic type, 0 or multiplication of ETH_PADDING (32)\n */\nvar dynamicBytesLength = function (type) {\n if (isArrayType(type) || type === 'bytes')\n return c.ETH_PADDING * 2;\n return 0;\n};\n\nvar outputTypes = types.outputTypes();\n\n/** \n * Formats output bytes back to param list\n *\n * @method formatOutput\n * @param {Array} abi outputs of method\n * @param {String} bytes represention of output\n * @returns {Array} output params\n */\nvar formatOutput = function (outs, output) {\n\n output = output.slice(2);\n var result = [];\n var padding = c.ETH_PADDING * 2;\n\n var dynamicPartLength = outs.reduce(function (acc, curr) {\n return acc + dynamicBytesLength(curr.type);\n }, 0);\n\n var dynamicPart = output.slice(0, dynamicPartLength);\n output = output.slice(dynamicPartLength);\n\n outs.forEach(function (out, i) {\n /*jshint maxcomplexity:6 */\n var typeMatch = false;\n for (var j = 0; j < outputTypes.length && !typeMatch; j++) {\n typeMatch = outputTypes[j].type(outs[i].type);\n }\n\n if (!typeMatch) {\n throwTypeError(outs[i].type);\n }\n\n var formatter = outputTypes[j - 1].format;\n if (isArrayType(outs[i].type)) {\n var size = f.formatOutputUInt(dynamicPart.slice(0, padding));\n dynamicPart = dynamicPart.slice(padding);\n var array = [];\n for (var k = 0; k < size; k++) {\n array.push(formatter(output.slice(0, padding)));\n output = output.slice(padding);\n }\n result.push(array);\n }\n else if (types.prefixedType('bytes')(outs[i].type)) {\n dynamicPart = dynamicPart.slice(padding);\n result.push(formatter(output.slice(0, padding)));\n output = output.slice(padding);\n } else {\n result.push(formatter(output.slice(0, padding)));\n output = output.slice(padding);\n }\n });\n\n return result;\n};\n\n/**\n * Should be called to create input parser for contract with given abi\n *\n * @method inputParser\n * @param {Array} contract abi\n * @returns {Object} input parser object for given json abi\n * TODO: refactor creating the parser, do not double logic from contract\n */\nvar inputParser = function (json) {\n var parser = {};\n json.forEach(function (method) {\n var displayName = utils.extractDisplayName(method.name);\n var typeName = utils.extractTypeName(method.name);\n\n var impl = function () {\n var params = Array.prototype.slice.call(arguments);\n return formatInput(method.inputs, params);\n };\n\n if (parser[displayName] === undefined) {\n parser[displayName] = impl;\n }\n\n parser[displayName][typeName] = impl;\n });\n\n return parser;\n};\n\n/**\n * Should be called to create output parser for contract with given abi\n *\n * @method outputParser\n * @param {Array} contract abi\n * @returns {Object} output parser for given json abi\n */\nvar outputParser = function (json) {\n var parser = {};\n json.forEach(function (method) {\n\n var displayName = utils.extractDisplayName(method.name);\n var typeName = utils.extractTypeName(method.name);\n\n var impl = function (output) {\n return formatOutput(method.outputs, output);\n };\n\n if (parser[displayName] === undefined) {\n parser[displayName] = impl;\n }\n\n parser[displayName][typeName] = impl;\n });\n\n return parser;\n};\n\nmodule.exports = {\n inputParser: inputParser,\n outputParser: outputParser,\n formatInput: formatInput,\n formatOutput: formatOutput\n};\n", + "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file formatters.js\n * @authors:\n * Marek Kotewicz \n * @date 2015\n */\n\nif (\"build\" !== 'build') {/*\n var BigNumber = require('bignumber.js'); // jshint ignore:line\n*/}\n\nvar utils = require('../utils/utils');\nvar c = require('../utils/config');\n\n/**\n * Should be called to pad string to expected length\n *\n * @method padLeft\n * @param {String} string to be padded\n * @param {Number} characters that result string should have\n * @param {String} sign, by default 0\n * @returns {String} right aligned string\n */\nvar padLeft = function (string, chars, sign) {\n return new Array(chars - string.length + 1).join(sign ? sign : \"0\") + string;\n};\n\n/**\n * Formats input value to byte representation of int\n * If value is negative, return it's two's complement\n * If the value is floating point, round it down\n *\n * @method formatInputInt\n * @param {String|Number|BigNumber} value that needs to be formatted\n * @returns {String} right-aligned byte representation of int\n */\nvar formatInputInt = function (value) {\n var padding = c.ETH_PADDING * 2;\n BigNumber.config(c.ETH_BIGNUMBER_ROUNDING_MODE);\n return padLeft(utils.toTwosComplement(value).round().toString(16), padding);\n};\n\n/**\n * Formats input value to byte representation of string\n *\n * @method formatInputString\n * @param {String}\n * @returns {String} left-algined byte representation of string\n */\nvar formatInputString = function (value) {\n return utils.fromAscii(value, c.ETH_PADDING).substr(2);\n};\n\n/**\n * Formats input value to byte representation of bool\n *\n * @method formatInputBool\n * @param {Boolean}\n * @returns {String} right-aligned byte representation bool\n */\nvar formatInputBool = function (value) {\n return '000000000000000000000000000000000000000000000000000000000000000' + (value ? '1' : '0');\n};\n\n/**\n * Formats input value to byte representation of real\n * Values are multiplied by 2^m and encoded as integers\n *\n * @method formatInputReal\n * @param {String|Number|BigNumber}\n * @returns {String} byte representation of real\n */\nvar formatInputReal = function (value) {\n return formatInputInt(new BigNumber(value).times(new BigNumber(2).pow(128))); \n};\n\n/**\n * Check if input value is negative\n *\n * @method signedIsNegative\n * @param {String} value is hex format\n * @returns {Boolean} true if it is negative, otherwise false\n */\nvar signedIsNegative = function (value) {\n return (new BigNumber(value.substr(0, 1), 16).toString(2).substr(0, 1)) === '1';\n};\n\n/**\n * Formats right-aligned output bytes to int\n *\n * @method formatOutputInt\n * @param {String} bytes\n * @returns {BigNumber} right-aligned output bytes formatted to big number\n */\nvar formatOutputInt = function (value) {\n\n value = value || \"0\";\n\n // check if it's negative number\n // it it is, return two's complement\n if (signedIsNegative(value)) {\n return new BigNumber(value, 16).minus(new BigNumber('ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', 16)).minus(1);\n }\n return new BigNumber(value, 16);\n};\n\n/**\n * Formats right-aligned output bytes to uint\n *\n * @method formatOutputUInt\n * @param {String} bytes\n * @returns {BigNumeber} right-aligned output bytes formatted to uint\n */\nvar formatOutputUInt = function (value) {\n value = value || \"0\";\n return new BigNumber(value, 16);\n};\n\n/**\n * Formats right-aligned output bytes to real\n *\n * @method formatOutputReal\n * @param {String}\n * @returns {BigNumber} input bytes formatted to real\n */\nvar formatOutputReal = function (value) {\n return formatOutputInt(value).dividedBy(new BigNumber(2).pow(128)); \n};\n\n/**\n * Formats right-aligned output bytes to ureal\n *\n * @method formatOutputUReal\n * @param {String}\n * @returns {BigNumber} input bytes formatted to ureal\n */\nvar formatOutputUReal = function (value) {\n return formatOutputUInt(value).dividedBy(new BigNumber(2).pow(128)); \n};\n\n/**\n * Should be used to format output hash\n *\n * @method formatOutputHash\n * @param {String}\n * @returns {String} right-aligned output bytes formatted to hex\n */\nvar formatOutputHash = function (value) {\n return \"0x\" + value;\n};\n\n/**\n * Should be used to format output bool\n *\n * @method formatOutputBool\n * @param {String}\n * @returns {Boolean} right-aligned input bytes formatted to bool\n */\nvar formatOutputBool = function (value) {\n return value === '0000000000000000000000000000000000000000000000000000000000000001' ? true : false;\n};\n\n/**\n * Should be used to format output string\n *\n * @method formatOutputString\n * @param {Sttring} left-aligned hex representation of string\n * @returns {String} ascii string\n */\nvar formatOutputString = function (value) {\n return utils.toAscii(value);\n};\n\n/**\n * Should be used to format output address\n *\n * @method formatOutputAddress\n * @param {String} right-aligned input bytes\n * @returns {String} address\n */\nvar formatOutputAddress = function (value) {\n return \"0x\" + value.slice(value.length - 40, value.length);\n};\n\nmodule.exports = {\n formatInputInt: formatInputInt,\n formatInputString: formatInputString,\n formatInputBool: formatInputBool,\n formatInputReal: formatInputReal,\n formatOutputInt: formatOutputInt,\n formatOutputUInt: formatOutputUInt,\n formatOutputReal: formatOutputReal,\n formatOutputUReal: formatOutputUReal,\n formatOutputHash: formatOutputHash,\n formatOutputBool: formatOutputBool,\n formatOutputString: formatOutputString,\n formatOutputAddress: formatOutputAddress\n};\n\n", + "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file types.js\n * @authors:\n * Marek Kotewicz \n * @date 2015\n */\n\nvar f = require('./formatters');\n\n/// @param expected type prefix (string)\n/// @returns function which checks if type has matching prefix. if yes, returns true, otherwise false\nvar prefixedType = function (prefix) {\n return function (type) {\n return type.indexOf(prefix) === 0;\n };\n};\n\n/// @param expected type name (string)\n/// @returns function which checks if type is matching expected one. if yes, returns true, otherwise false\nvar namedType = function (name) {\n return function (type) {\n return name === type;\n };\n};\n\n/// Setups input formatters for solidity types\n/// @returns an array of input formatters \nvar inputTypes = function () {\n \n return [\n { type: prefixedType('uint'), format: f.formatInputInt },\n { type: prefixedType('int'), format: f.formatInputInt },\n { type: prefixedType('bytes'), format: f.formatInputString }, \n { type: prefixedType('real'), format: f.formatInputReal },\n { type: prefixedType('ureal'), format: f.formatInputReal },\n { type: namedType('address'), format: f.formatInputInt },\n { type: namedType('bool'), format: f.formatInputBool }\n ];\n};\n\n/// Setups output formaters for solidity types\n/// @returns an array of output formatters\nvar outputTypes = function () {\n\n return [\n { type: prefixedType('uint'), format: f.formatOutputUInt },\n { type: prefixedType('int'), format: f.formatOutputInt },\n { type: prefixedType('bytes'), format: f.formatOutputString },\n { type: prefixedType('real'), format: f.formatOutputReal },\n { type: prefixedType('ureal'), format: f.formatOutputUReal },\n { type: namedType('address'), format: f.formatOutputAddress },\n { type: namedType('bool'), format: f.formatOutputBool }\n ];\n};\n\nmodule.exports = {\n prefixedType: prefixedType,\n namedType: namedType,\n inputTypes: inputTypes,\n outputTypes: outputTypes\n};\n\n", + "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file config.js\n * @authors:\n * Marek Kotewicz \n * @date 2015\n */\n\n/**\n * Utils\n * \n * @module utils\n */\n\n/**\n * Utility functions\n * \n * @class [utils] config\n * @constructor\n */\n\n/// required to define ETH_BIGNUMBER_ROUNDING_MODE\nif (\"build\" !== 'build') {/*\n var BigNumber = require('bignumber.js'); // jshint ignore:line\n*/}\n\nvar ETH_UNITS = [ \n 'wei', \n 'Kwei', \n 'Mwei', \n 'Gwei', \n 'szabo', \n 'finney', \n 'ether', \n 'grand', \n 'Mether', \n 'Gether', \n 'Tether', \n 'Pether', \n 'Eether', \n 'Zether', \n 'Yether', \n 'Nether', \n 'Dether', \n 'Vether', \n 'Uether' \n];\n\nmodule.exports = {\n ETH_PADDING: 32,\n ETH_SIGNATURE_LENGTH: 4,\n ETH_UNITS: ETH_UNITS,\n ETH_BIGNUMBER_ROUNDING_MODE: { ROUNDING_MODE: BigNumber.ROUND_DOWN },\n ETH_POLLING_TIMEOUT: 1000,\n ETH_DEFAULTBLOCK: 'latest'\n};\n\n", + "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file utils.js\n * @authors:\n * Marek Kotewicz \n * @date 2015\n */\n\n/**\n * Utils\n * \n * @module utils\n */\n\n/**\n * Utility functions\n * \n * @class [utils] utils\n * @constructor\n */\n\nif (\"build\" !== 'build') {/*\n var BigNumber = require('bignumber.js'); // jshint ignore:line\n*/}\n\nvar unitMap = {\n 'wei': '1',\n 'kwei': '1000',\n 'ada': '1000',\n 'mwei': '1000000',\n 'babbage': '1000000',\n 'gwei': '1000000000',\n 'shannon': '1000000000',\n 'szabo': '1000000000000',\n 'finney': '1000000000000000',\n 'ether': '1000000000000000000',\n 'kether': '1000000000000000000000',\n 'grand': '1000000000000000000000',\n 'einstein': '1000000000000000000000',\n 'mether': '1000000000000000000000000',\n 'gether': '1000000000000000000000000000',\n 'tether': '1000000000000000000000000000000'\n};\n\n\n/** Finds first index of array element matching pattern\n *\n * @method findIndex\n * @param {Array}\n * @param {Function} pattern\n * @returns {Number} index of element\n */\nvar findIndex = function (array, callback) {\n var end = false;\n var i = 0;\n for (; i < array.length && !end; i++) {\n end = callback(array[i]);\n }\n return end ? i - 1 : -1;\n};\n\n/** \n * Should be called to get sting from it's hex representation\n *\n * @method toAscii\n * @param {String} string in hex\n * @returns {String} ascii string representation of hex value\n */\nvar toAscii = function(hex) {\n// Find termination\n var str = \"\";\n var i = 0, l = hex.length;\n if (hex.substring(0, 2) === '0x') {\n i = 2;\n }\n for (; i < l; i+=2) {\n var code = parseInt(hex.substr(i, 2), 16);\n if (code === 0) {\n break;\n }\n\n str += String.fromCharCode(code);\n }\n\n return str;\n};\n \n/**\n * Shold be called to get hex representation (prefixed by 0x) of ascii string \n *\n * @method fromAscii\n * @param {String} string\n * @returns {String} hex representation of input string\n */\nvar toHexNative = function(str) {\n var hex = \"\";\n for(var i = 0; i < str.length; i++) {\n var n = str.charCodeAt(i).toString(16);\n hex += n.length < 2 ? '0' + n : n;\n }\n\n return hex;\n};\n\n/**\n * Shold be called to get hex representation (prefixed by 0x) of ascii string \n *\n * @method fromAscii\n * @param {String} string\n * @param {Number} optional padding\n * @returns {String} hex representation of input string\n */\nvar fromAscii = function(str, pad) {\n pad = pad === undefined ? 0 : pad;\n var hex = toHexNative(str);\n while (hex.length < pad*2)\n hex += \"00\";\n return \"0x\" + hex;\n};\n\n/**\n * Should be called to get display name of contract function\n * \n * @method extractDisplayName\n * @param {String} name of function/event\n * @returns {String} display name for function/event eg. multiply(uint256) -> multiply\n */\nvar extractDisplayName = function (name) {\n var length = name.indexOf('('); \n return length !== -1 ? name.substr(0, length) : name;\n};\n\n/// @returns overloaded part of function/event name\nvar extractTypeName = function (name) {\n /// TODO: make it invulnerable\n var length = name.indexOf('(');\n return length !== -1 ? name.substr(length + 1, name.length - 1 - (length + 1)).replace(' ', '') : \"\";\n};\n\n/**\n * Filters all functions from input abi\n *\n * @method filterFunctions\n * @param {Array} abi\n * @returns {Array} abi array with filtered objects of type 'function'\n */\nvar filterFunctions = function (json) {\n return json.filter(function (current) {\n return current.type === 'function'; \n }); \n};\n\n/**\n * Filters all events from input abi\n *\n * @method filterEvents\n * @param {Array} abi\n * @returns {Array} abi array with filtered objects of type 'event'\n */\nvar filterEvents = function (json) {\n return json.filter(function (current) {\n return current.type === 'event';\n });\n};\n\n/**\n * Converts value to it's decimal representation in string\n *\n * @method toDecimal\n * @param {String|Number|BigNumber}\n * @return {String}\n */\nvar toDecimal = function (value) {\n return toBigNumber(value).toNumber();\n};\n\n/**\n * Converts value to it's hex representation\n *\n * @method fromDecimal\n * @param {String|Number|BigNumber}\n * @return {String}\n */\nvar fromDecimal = function (value) {\n var number = toBigNumber(value);\n var result = number.toString(16);\n\n return number.lessThan(0) ? '-0x' + result.substr(1) : '0x' + result;\n};\n\n/**\n * Auto converts any given value into it's hex representation.\n *\n * And even stringifys objects before.\n *\n * @method toHex\n * @param {String|Number|BigNumber|Object}\n * @return {String}\n */\nvar toHex = function (val) {\n /*jshint maxcomplexity:7 */\n\n if(isBoolean(val))\n return val;\n\n if(isBigNumber(val))\n return fromDecimal(val);\n\n if(isObject(val))\n return fromAscii(JSON.stringify(val));\n\n // if its a negative number, pass it through fromDecimal\n if (isString(val)) {\n if (val.indexOf('-0x') === 0)\n return fromDecimal(val);\n else if (!isFinite(val))\n return fromAscii(val);\n }\n\n return fromDecimal(val);\n};\n\n/**\n * Returns value of unit in Wei\n *\n * @method getValueOfUnit\n * @param {String} unit the unit to convert to, default ether\n * @returns {BigNumber} value of the unit (in Wei)\n * @throws error if the unit is not correct:w\n */\nvar getValueOfUnit = function (unit) {\n unit = unit ? unit.toLowerCase() : 'ether';\n var unitValue = unitMap[unit];\n if (unitValue === undefined) {\n throw new Error('This unit doesn\\'t exists, please use the one of the following units' + JSON.stringify(unitMap, null, 2));\n }\n return new BigNumber(unitValue, 10);\n};\n\n/**\n * Takes a number of wei and converts it to any other ether unit.\n *\n * Possible units are:\n * - kwei/ada\n * - mwei/babbage\n * - gwei/shannon\n * - szabo\n * - finney\n * - ether\n * - kether/grand/einstein\n * - mether\n * - gether\n * - tether\n *\n * @method fromWei\n * @param {Number|String} number can be a number, number string or a HEX of a decimal\n * @param {String} unit the unit to convert to, default ether\n * @return {String|Object} When given a BigNumber object it returns one as well, otherwise a number\n*/\nvar fromWei = function(number, unit) {\n var returnValue = toBigNumber(number).dividedBy(getValueOfUnit(unit));\n\n return isBigNumber(number) ? returnValue : returnValue.toString(10); \n};\n\n/**\n * Takes a number of a unit and converts it to wei.\n *\n * Possible units are:\n * - kwei/ada\n * - mwei/babbage\n * - gwei/shannon\n * - szabo\n * - finney\n * - ether\n * - kether/grand/einstein\n * - mether\n * - gether\n * - tether\n *\n * @method toWei\n * @param {Number|String|BigNumber} number can be a number, number string or a HEX of a decimal\n * @param {String} unit the unit to convert from, default ether\n * @return {String|Object} When given a BigNumber object it returns one as well, otherwise a number\n*/\nvar toWei = function(number, unit) {\n var returnValue = toBigNumber(number).times(getValueOfUnit(unit));\n\n return isBigNumber(number) ? returnValue : returnValue.toString(10); \n};\n\n/**\n * Takes an input and transforms it into an bignumber\n *\n * @method toBigNumber\n * @param {Number|String|BigNumber} a number, string, HEX string or BigNumber\n * @return {BigNumber} BigNumber\n*/\nvar toBigNumber = function(number) {\n /*jshint maxcomplexity:5 */\n number = number || 0;\n if (isBigNumber(number))\n return number;\n\n if (isString(number) && (number.indexOf('0x') === 0 || number.indexOf('-0x') === 0)) {\n return new BigNumber(number.replace('0x',''), 16);\n }\n \n return new BigNumber(number.toString(10), 10);\n};\n\n/**\n * Takes and input transforms it into bignumber and if it is negative value, into two's complement\n *\n * @method toTwosComplement\n * @param {Number|String|BigNumber}\n * @return {BigNumber}\n */\nvar toTwosComplement = function (number) {\n var bigNumber = toBigNumber(number);\n if (bigNumber.lessThan(0)) {\n return new BigNumber(\"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff\", 16).plus(bigNumber).plus(1);\n }\n return bigNumber;\n};\n\n/**\n * Checks if the given string has proper length\n *\n * @method isAddress\n * @param {String} address the given HEX adress\n * @return {Boolean}\n*/\nvar isAddress = function(address) {\n if (!isString(address)) {\n return false;\n }\n\n return ((address.indexOf('0x') === 0 && address.length === 42) ||\n (address.indexOf('0x') === -1 && address.length === 40));\n};\n\n/**\n * Returns true if object is BigNumber, otherwise false\n *\n * @method isBigNumber\n * @param {Object}\n * @return {Boolean} \n */\nvar isBigNumber = function (object) {\n return object instanceof BigNumber ||\n (object && object.constructor && object.constructor.name === 'BigNumber');\n};\n\n/**\n * Returns true if object is string, otherwise false\n * \n * @method isString\n * @param {Object}\n * @return {Boolean}\n */\nvar isString = function (object) {\n return typeof object === 'string' ||\n (object && object.constructor && object.constructor.name === 'String');\n};\n\n/**\n * Returns true if object is function, otherwise false\n *\n * @method isFunction\n * @param {Object}\n * @return {Boolean}\n */\nvar isFunction = function (object) {\n return typeof object === 'function';\n};\n\n/**\n * Returns true if object is Objet, otherwise false\n *\n * @method isObject\n * @param {Object}\n * @return {Boolean}\n */\nvar isObject = function (object) {\n return typeof object === 'object';\n};\n\n/**\n * Returns true if object is boolean, otherwise false\n *\n * @method isBoolean\n * @param {Object}\n * @return {Boolean}\n */\nvar isBoolean = function (object) {\n return typeof object === 'boolean';\n};\n\n/**\n * Returns true if object is array, otherwise false\n *\n * @method isArray\n * @param {Object}\n * @return {Boolean}\n */\nvar isArray = function (object) {\n return object instanceof Array; \n};\n\nmodule.exports = {\n findIndex: findIndex,\n toHex: toHex,\n toDecimal: toDecimal,\n fromDecimal: fromDecimal,\n toAscii: toAscii,\n fromAscii: fromAscii,\n extractDisplayName: extractDisplayName,\n extractTypeName: extractTypeName,\n filterFunctions: filterFunctions,\n filterEvents: filterEvents,\n toWei: toWei,\n fromWei: fromWei,\n toBigNumber: toBigNumber,\n toTwosComplement: toTwosComplement,\n isBigNumber: isBigNumber,\n isAddress: isAddress,\n isFunction: isFunction,\n isString: isString,\n isObject: isObject,\n isBoolean: isBoolean,\n isArray: isArray\n};\n\n", + "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file web3.js\n * @authors:\n * Jeffrey Wilcke \n * Marek Kotewicz \n * Marian Oancea \n * Fabian Vogelsteller \n * Gav Wood \n * @date 2014\n */\n\nvar version = require('../version.json');\nvar net = require('./web3/net');\nvar eth = require('./web3/eth');\nvar db = require('./web3/db');\nvar shh = require('./web3/shh');\nvar watches = require('./web3/watches');\nvar filter = require('./web3/filter');\nvar utils = require('./utils/utils');\nvar formatters = require('./solidity/formatters');\nvar requestManager = require('./web3/requestmanager');\nvar c = require('./utils/config');\n\n/// @returns an array of objects describing web3 api methods\nvar web3Methods = [\n { name: 'sha3', call: 'web3_sha3', inputFormatter: utils.toHex },\n];\nvar web3Properties = [\n { name: 'version.client', getter: 'web3_clientVersion' },\n { name: 'version.network', getter: 'net_version' }\n];\n\n\n/// creates methods in a given object based on method description on input\n/// setups api calls for these methods\nvar setupMethods = function (obj, methods) {\n methods.forEach(function (method) {\n // allow for object methods 'myObject.method'\n var objectMethods = method.name.split('.'),\n callFunction = function () {\n /*jshint maxcomplexity:8 */\n \n var callback = null,\n args = Array.prototype.slice.call(arguments),\n call = typeof method.call === 'function' ? method.call(args) : method.call;\n\n // get the callback if one is available\n if(typeof args[args.length-1] === 'function'){\n callback = args[args.length-1];\n Array.prototype.pop.call(args);\n }\n\n // add the defaultBlock if not given\n if(method.addDefaultblock) {\n if(args.length !== method.addDefaultblock)\n Array.prototype.push.call(args, (isFinite(c.ETH_DEFAULTBLOCK) ? utils.fromDecimal(c.ETH_DEFAULTBLOCK) : c.ETH_DEFAULTBLOCK));\n else\n args[args.length-1] = isFinite(args[args.length-1]) ? utils.fromDecimal(args[args.length-1]) : args[args.length-1];\n }\n\n // show deprecated warning\n if(method.newMethod)\n console.warn('This method is deprecated please use web3.'+ method.newMethod +'() instead.');\n\n return web3.manager.send({\n method: call,\n params: args,\n outputFormatter: method.outputFormatter,\n inputFormatter: method.inputFormatter,\n addDefaultblock: method.addDefaultblock\n }, callback);\n };\n\n if(objectMethods.length > 1) {\n if(!obj[objectMethods[0]])\n obj[objectMethods[0]] = {};\n\n obj[objectMethods[0]][objectMethods[1]] = callFunction;\n \n } else {\n\n obj[objectMethods[0]] = callFunction;\n }\n\n });\n};\n\n/// creates properties in a given object based on properties description on input\n/// setups api calls for these properties\nvar setupProperties = function (obj, properties) {\n properties.forEach(function (property) {\n var objectProperties = property.name.split('.'),\n proto = {};\n\n proto.get = function () {\n\n // show deprecated warning\n if(property.newProperty)\n console.warn('This property is deprecated please use web3.'+ property.newProperty +' instead.');\n\n\n return web3.manager.send({\n method: property.getter,\n outputFormatter: property.outputFormatter\n });\n };\n\n if (property.setter) {\n proto.set = function (val) {\n\n // show deprecated warning\n if(property.newProperty)\n console.warn('This property is deprecated please use web3.'+ property.newProperty +' instead.');\n\n return web3.manager.send({\n method: property.setter,\n params: [val],\n inputFormatter: property.inputFormatter\n });\n };\n }\n\n proto.enumerable = !property.newProperty;\n\n if(objectProperties.length > 1) {\n if(!obj[objectProperties[0]])\n obj[objectProperties[0]] = {};\n\n Object.defineProperty(obj[objectProperties[0]], objectProperties[1], proto); \n } else\n Object.defineProperty(obj, property.name, proto);\n\n });\n};\n\n/*jshint maxparams:4 */\nvar startPolling = function (method, id, callback, uninstall) {\n web3.manager.startPolling({\n method: method, \n params: [id]\n }, id, callback, uninstall); \n};\n/*jshint maxparams:3 */\n\nvar stopPolling = function (id) {\n web3.manager.stopPolling(id);\n};\n\nvar ethWatch = {\n startPolling: startPolling.bind(null, 'eth_getFilterChanges'), \n stopPolling: stopPolling\n};\n\nvar shhWatch = {\n startPolling: startPolling.bind(null, 'shh_getFilterChanges'), \n stopPolling: stopPolling\n};\n\n/// setups web3 object, and it's in-browser executed methods\nvar web3 = {\n\n version: {\n api: version.version\n },\n\n manager: requestManager(),\n providers: {},\n\n setProvider: function (provider) {\n web3.manager.setProvider(provider);\n },\n \n /// Should be called to reset state of web3 object\n /// Resets everything except manager\n reset: function () {\n web3.manager.reset(); \n },\n\n /// @returns hex string of the input\n toHex: utils.toHex,\n\n /// @returns ascii string representation of hex value prefixed with 0x\n toAscii: utils.toAscii,\n\n /// @returns hex representation (prefixed by 0x) of ascii string\n fromAscii: utils.fromAscii,\n\n /// @returns decimal representaton of hex value prefixed by 0x\n toDecimal: utils.toDecimal,\n\n /// @returns hex representation (prefixed by 0x) of decimal value\n fromDecimal: utils.fromDecimal,\n\n /// @returns a BigNumber object\n toBigNumber: utils.toBigNumber,\n\n toWei: utils.toWei,\n fromWei: utils.fromWei,\n isAddress: utils.isAddress,\n\n // provide network information\n net: {\n // peerCount: \n },\n\n\n /// eth object prototype\n eth: {\n // DEPRECATED\n contractFromAbi: function (abi) {\n console.warn('Initiating a contract like this is deprecated please use var MyContract = eth.contract(abi); new MyContract(address); instead.');\n\n return function(addr) {\n // Default to address of Config. TODO: rremove prior to genesis.\n addr = addr || '0xc6d9d2cd449a754c494264e1809c50e34d64562b';\n var ret = web3.eth.contract(addr, abi);\n ret.address = addr;\n return ret;\n };\n },\n\n /// @param filter may be a string, object or event\n /// @param eventParams is optional, this is an object with optional event eventParams params\n /// @param options is optional, this is an object with optional event options ('max'...)\n /*jshint maxparams:4 */\n filter: function (fil, eventParams, options) {\n\n // if its event, treat it differently\n if (fil._isEvent)\n return fil(eventParams, options);\n\n return filter(fil, ethWatch, formatters.outputLogFormatter);\n },\n // DEPRECATED\n watch: function (fil, eventParams, options) {\n console.warn('eth.watch() is deprecated please use eth.filter() instead.');\n return this.filter(fil, eventParams, options);\n }\n /*jshint maxparams:3 */\n },\n\n /// db object prototype\n db: {},\n\n /// shh object prototype\n shh: {\n /// @param filter may be a string, object or event\n filter: function (fil) {\n return filter(fil, shhWatch, formatters.outputPostFormatter);\n },\n // DEPRECATED\n watch: function (fil) {\n console.warn('shh.watch() is deprecated please use shh.filter() instead.');\n return this.filter(fil);\n }\n }\n};\n\n\n// ADD defaultblock\nObject.defineProperty(web3.eth, 'defaultBlock', {\n get: function () {\n return c.ETH_DEFAULTBLOCK;\n },\n set: function (val) {\n c.ETH_DEFAULTBLOCK = val;\n return c.ETH_DEFAULTBLOCK;\n }\n});\n\n\n/// setups all api methods\nsetupMethods(web3, web3Methods);\nsetupProperties(web3, web3Properties);\nsetupMethods(web3.net, net.methods);\nsetupProperties(web3.net, net.properties);\nsetupMethods(web3.eth, eth.methods);\nsetupProperties(web3.eth, eth.properties);\nsetupMethods(web3.db, db.methods());\nsetupMethods(web3.shh, shh.methods());\nsetupMethods(ethWatch, watches.eth());\nsetupMethods(shhWatch, watches.shh());\n\nmodule.exports = web3;\n\n", + "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file contract.js\n * @authors:\n * Marek Kotewicz \n * @date 2014\n */\n\nvar web3 = require('../web3'); \nvar abi = require('../solidity/abi');\nvar utils = require('../utils/utils');\nvar eventImpl = require('./event');\nvar signature = require('./signature');\n\nvar exportNatspecGlobals = function (vars) {\n // it's used byt natspec.js\n // TODO: figure out better way to solve this\n web3._currentContractAbi = vars.abi;\n web3._currentContractAddress = vars.address;\n web3._currentContractMethodName = vars.method;\n web3._currentContractMethodParams = vars.params;\n};\n\nvar addFunctionRelatedPropertiesToContract = function (contract) {\n \n contract.call = function (options) {\n contract._isTransaction = false;\n contract._options = options;\n return contract;\n };\n\n\n contract.sendTransaction = function (options) {\n contract._isTransaction = true;\n contract._options = options;\n return contract;\n };\n // DEPRECATED\n contract.transact = function (options) {\n\n console.warn('myContract.transact() is deprecated please use myContract.sendTransaction() instead.');\n\n return contract.sendTransaction(options);\n };\n\n contract._options = {};\n ['gas', 'gasPrice', 'value', 'from'].forEach(function(p) {\n contract[p] = function (v) {\n contract._options[p] = v;\n return contract;\n };\n });\n\n};\n\nvar addFunctionsToContract = function (contract, desc, address) {\n var inputParser = abi.inputParser(desc);\n var outputParser = abi.outputParser(desc);\n\n // create contract functions\n utils.filterFunctions(desc).forEach(function (method) {\n\n var displayName = utils.extractDisplayName(method.name);\n var typeName = utils.extractTypeName(method.name);\n\n var impl = function () {\n /*jshint maxcomplexity:7 */\n var params = Array.prototype.slice.call(arguments);\n var sign = signature.functionSignatureFromAscii(method.name);\n var parsed = inputParser[displayName][typeName].apply(null, params);\n\n var options = contract._options || {};\n options.to = address;\n options.data = sign + parsed;\n \n var isTransaction = contract._isTransaction === true || (contract._isTransaction !== false && !method.constant);\n var collapse = options.collapse !== false;\n \n // reset\n contract._options = {};\n contract._isTransaction = null;\n\n if (isTransaction) {\n \n exportNatspecGlobals({\n abi: desc,\n address: address,\n method: method.name,\n params: params\n });\n\n // transactions do not have any output, cause we do not know, when they will be processed\n web3.eth.sendTransaction(options);\n return;\n }\n \n var output = web3.eth.call(options);\n var ret = outputParser[displayName][typeName](output);\n if (collapse)\n {\n if (ret.length === 1)\n ret = ret[0];\n else if (ret.length === 0)\n ret = null;\n }\n return ret;\n };\n\n if (contract[displayName] === undefined) {\n contract[displayName] = impl;\n }\n\n contract[displayName][typeName] = impl;\n });\n};\n\nvar addEventRelatedPropertiesToContract = function (contract, desc, address) {\n contract.address = address;\n contract._onWatchEventResult = function (data) {\n var matchingEvent = event.getMatchingEvent(utils.filterEvents(desc));\n var parser = eventImpl.outputParser(matchingEvent);\n return parser(data);\n };\n \n Object.defineProperty(contract, 'topics', {\n get: function() {\n return utils.filterEvents(desc).map(function (e) {\n return signature.eventSignatureFromAscii(e.name);\n });\n }\n });\n\n};\n\nvar addEventsToContract = function (contract, desc, address) {\n // create contract events\n utils.filterEvents(desc).forEach(function (e) {\n\n var impl = function () {\n var params = Array.prototype.slice.call(arguments);\n var sign = signature.eventSignatureFromAscii(e.name);\n var event = eventImpl.inputParser(address, sign, e);\n var o = event.apply(null, params);\n var outputFormatter = function (data) {\n var parser = eventImpl.outputParser(e);\n return parser(data);\n };\n\t\t\treturn web3.eth.filter(o, undefined, undefined, outputFormatter);\n };\n \n // this property should be used by eth.filter to check if object is an event\n impl._isEvent = true;\n\n var displayName = utils.extractDisplayName(e.name);\n var typeName = utils.extractTypeName(e.name);\n\n if (contract[displayName] === undefined) {\n contract[displayName] = impl;\n }\n\n contract[displayName][typeName] = impl;\n\n });\n};\n\n\n/**\n * This method should be called when we want to call / transact some solidity method from javascript\n * it returns an object which has same methods available as solidity contract description\n * usage example: \n *\n * var abi = [{\n * name: 'myMethod',\n * inputs: [{ name: 'a', type: 'string' }],\n * outputs: [{name: 'd', type: 'string' }]\n * }]; // contract abi\n *\n * var MyContract = web3.eth.contract(abi); // creation of contract prototype\n *\n * var contractInstance = new MyContract('0x0123123121');\n *\n * contractInstance.myMethod('this is test string param for call'); // myMethod call (implicit, default)\n * contractInstance.call().myMethod('this is test string param for call'); // myMethod call (explicit)\n * contractInstance.sendTransaction().myMethod('this is test string param for transact'); // myMethod sendTransaction\n *\n * @param abi - abi json description of the contract, which is being created\n * @returns contract object\n */\nvar contract = function (abi) {\n\n // return prototype\n if(abi instanceof Array && arguments.length === 1) {\n return Contract.bind(null, abi);\n\n // deprecated: auto initiate contract\n } else {\n\n console.warn('Initiating a contract like this is deprecated please use var MyContract = eth.contract(abi); new MyContract(address); instead.');\n\n return new Contract(arguments[1], arguments[0]);\n }\n\n};\n\nfunction Contract(abi, address) {\n\n // workaround for invalid assumption that method.name is the full anonymous prototype of the method.\n // it's not. it's just the name. the rest of the code assumes it's actually the anonymous\n // prototype, so we make it so as a workaround.\n // TODO: we may not want to modify input params, maybe use copy instead?\n abi.forEach(function (method) {\n if (method.name.indexOf('(') === -1) {\n var displayName = method.name;\n var typeName = method.inputs.map(function(i){return i.type; }).join();\n method.name = displayName + '(' + typeName + ')';\n }\n });\n\n var result = {};\n addFunctionRelatedPropertiesToContract(result);\n addFunctionsToContract(result, abi, address);\n addEventRelatedPropertiesToContract(result, abi, address);\n addEventsToContract(result, abi, address);\n\n return result;\n}\n\nmodule.exports = contract;\n\n", + "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file db.js\n * @authors:\n * Marek Kotewicz \n * @date 2015\n */\n\n\n/// @returns an array of objects describing web3.db api methods\nvar methods = function () {\n return [\n { name: 'putString', call: 'db_putString'},\n { name: 'getString', call: 'db_getString'},\n { name: 'putHex', call: 'db_putHex'},\n { name: 'getHex', call: 'db_getHex'}\n ];\n};\n\nmodule.exports = {\n methods: methods\n};\n", + "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file eth.js\n * @authors:\n * Marek Kotewicz \n * Fabian Vogelsteller \n * @date 2015\n */\n\n/**\n * Web3\n * \n * @module web3\n */\n\n/**\n * Eth methods and properties\n *\n * An example method object can look as follows:\n *\n * {\n * name: 'getBlock',\n * call: blockCall,\n * outputFormatter: formatters.outputBlockFormatter,\n * inputFormatter: [ // can be a formatter funciton or an array of functions. Where each item in the array will be used for one parameter\n * utils.toHex, // formats paramter 1\n * function(param){ if(!param) return false; } // formats paramter 2\n * ]\n * },\n *\n * @class [web3] eth\n * @constructor\n */\n\n\nvar formatters = require('./formatters');\nvar utils = require('../utils/utils');\n\n\nvar blockCall = function (args) {\n return (utils.isString(args[0]) && args[0].indexOf('0x') === 0) ? \"eth_getBlockByHash\" : \"eth_getBlockByNumber\";\n};\n\nvar transactionFromBlockCall = function (args) {\n return (utils.isString(args[0]) && args[0].indexOf('0x') === 0) ? 'eth_getTransactionByBlockHashAndIndex' : 'eth_getTransactionByBlockNumberAndIndex';\n};\n\nvar uncleCall = function (args) {\n return (utils.isString(args[0]) && args[0].indexOf('0x') === 0) ? 'eth_getUncleByBlockHashAndIndex' : 'eth_getUncleByBlockNumberAndIndex';\n};\n\nvar getBlockTransactionCountCall = function (args) {\n return (utils.isString(args[0]) && args[0].indexOf('0x') === 0) ? 'eth_getBlockTransactionCountByHash' : 'eth_getBlockTransactionCountByNumber';\n};\n\nvar uncleCountCall = function (args) {\n return (utils.isString(args[0]) && args[0].indexOf('0x') === 0) ? 'eth_getUncleCountByBlockHash' : 'eth_getUncleCountByBlockNumber';\n};\n\n/// @returns an array of objects describing web3.eth api methods\nvar methods = [\n { name: 'getBalance', call: 'eth_getBalance', addDefaultblock: 2,\n outputFormatter: formatters.convertToBigNumber},\n { name: 'getStorageAt', call: 'eth_getStorageAt', addDefaultblock: 3,\n inputFormatter: utils.toHex},\n\t{ name: 'getCode', call: 'eth_getCode', addDefaultblock: 2},\n { name: 'getBlock', call: blockCall,\n outputFormatter: formatters.outputBlockFormatter,\n inputFormatter: [utils.toHex, function(param){ return (!param) ? false : true; }]},\n { name: 'getUncle', call: uncleCall,\n outputFormatter: formatters.outputBlockFormatter,\n inputFormatter: [utils.toHex, utils.toHex, function(param){ return (!param) ? false : true; }]},\n { name: 'getCompilers', call: 'eth_getCompilers' },\n { name: 'getBlockTransactionCount', call: getBlockTransactionCountCall,\n outputFormatter: utils.toDecimal,\n inputFormatter: utils.toHex },\n { name: 'getBlockUncleCount', call: uncleCountCall,\n outputFormatter: utils.toDecimal,\n inputFormatter: utils.toHex },\n { name: 'getTransaction', call: 'eth_getTransactionByHash',\n outputFormatter: formatters.outputTransactionFormatter },\n { name: 'getTransactionFromBlock', call: transactionFromBlockCall,\n outputFormatter: formatters.outputTransactionFormatter,\n inputFormatter: utils.toHex },\n { name: 'getTransactionCount', call: 'eth_getTransactionCount', addDefaultblock: 2,\n outputFormatter: utils.toDecimal},\n { name: 'sendTransaction', call: 'eth_sendTransaction',\n inputFormatter: formatters.inputTransactionFormatter },\n { name: 'call', call: 'eth_call', addDefaultblock: 2,\n inputFormatter: formatters.inputCallFormatter },\n { name: 'compile.solidity', call: 'eth_compileSolidity' },\n { name: 'compile.lll', call: 'eth_compileLLL', inputFormatter: utils.toHex },\n { name: 'compile.serpent', call: 'eth_compileSerpent', inputFormatter: utils.toHex },\n { name: 'flush', call: 'eth_flush' },\n\n // deprecated methods\n { name: 'balanceAt', call: 'eth_balanceAt', newMethod: 'eth.getBalance' },\n { name: 'stateAt', call: 'eth_stateAt', newMethod: 'eth.getStorageAt' },\n { name: 'storageAt', call: 'eth_storageAt', newMethod: 'eth.getStorage' },\n { name: 'countAt', call: 'eth_countAt', newMethod: 'eth.getTransactionCount' },\n\t{ name: 'codeAt', call: 'eth_codeAt', newMethod: 'eth.getCode' },\n { name: 'transact', call: 'eth_transact', newMethod: 'eth.sendTransaction' },\n { name: 'block', call: blockCall, newMethod: 'eth.getBlock' },\n { name: 'transaction', call: transactionFromBlockCall, newMethod: 'eth.getTransaction' },\n { name: 'uncle', call: uncleCall, newMethod: 'eth.getUncle' },\n { name: 'compilers', call: 'eth_compilers', newMethod: 'eth.getCompilers' },\n { name: 'solidity', call: 'eth_solidity', newMethod: 'eth.compile.solidity' },\n { name: 'lll', call: 'eth_lll', newMethod: 'eth.compile.lll' },\n { name: 'serpent', call: 'eth_serpent', newMethod: 'eth.compile.serpent' },\n { name: 'transactionCount', call: getBlockTransactionCountCall, newMethod: 'eth.getBlockTransactionCount' },\n { name: 'uncleCount', call: uncleCountCall, newMethod: 'eth.getBlockUncleCount' },\n { name: 'logs', call: 'eth_logs' }\n];\n\n/// @returns an array of objects describing web3.eth api properties\nvar properties = [\n { name: 'coinbase', getter: 'eth_coinbase'},\n { name: 'mining', getter: 'eth_mining'},\n { name: 'gasPrice', getter: 'eth_gasPrice', outputFormatter: formatters.convertToBigNumber},\n { name: 'accounts', getter: 'eth_accounts' },\n { name: 'blockNumber', getter: 'eth_blockNumber', outputFormatter: utils.toDecimal},\n\n // deprecated properties\n { name: 'listening', getter: 'net_listening', setter: 'eth_setListening', newProperty: 'net.listening'},\n { name: 'peerCount', getter: 'net_peerCount', newProperty: 'net.peerCount'},\n { name: 'number', getter: 'eth_number', newProperty: 'eth.blockNumber'}\n];\n\n\nmodule.exports = {\n methods: methods,\n properties: properties\n};\n\n", + "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file event.js\n * @authors:\n * Marek Kotewicz \n * @date 2014\n */\n\nvar abi = require('../solidity/abi');\nvar utils = require('../utils/utils');\nvar signature = require('./signature');\n\n/// filter inputs array && returns only indexed (or not) inputs\n/// @param inputs array\n/// @param bool if result should be an array of indexed params on not\n/// @returns array of (not?) indexed params\nvar filterInputs = function (inputs, indexed) {\n return inputs.filter(function (current) {\n return current.indexed === indexed;\n });\n};\n\nvar inputWithName = function (inputs, name) {\n var index = utils.findIndex(inputs, function (input) {\n return input.name === name;\n });\n \n if (index === -1) {\n console.error('indexed param with name ' + name + ' not found');\n return undefined;\n }\n return inputs[index];\n};\n\nvar indexedParamsToTopics = function (event, indexed) {\n // sort keys?\n return Object.keys(indexed).map(function (key) {\n var inputs = [inputWithName(filterInputs(event.inputs, true), key)];\n\n var value = indexed[key];\n if (value instanceof Array) {\n return value.map(function (v) {\n return abi.formatInput(inputs, [v]);\n }); \n }\n return abi.formatInput(inputs, [value]);\n });\n};\n\nvar inputParser = function (address, sign, event) {\n \n // valid options are 'earliest', 'latest', 'offset' and 'max', as defined for 'eth.filter'\n return function (indexed, options) {\n var o = options || {};\n o.address = address;\n o.topics = [];\n o.topics.push(sign);\n if (indexed) {\n o.topics = o.topics.concat(indexedParamsToTopics(event, indexed));\n }\n return o;\n };\n};\n\nvar getArgumentsObject = function (inputs, indexed, notIndexed) {\n var indexedCopy = indexed.slice();\n var notIndexedCopy = notIndexed.slice();\n return inputs.reduce(function (acc, current) {\n var value;\n if (current.indexed)\n value = indexedCopy.splice(0, 1)[0];\n else\n value = notIndexedCopy.splice(0, 1)[0];\n\n acc[current.name] = value;\n return acc;\n }, {}); \n};\n \nvar outputParser = function (event) {\n \n return function (output) {\n var result = {\n event: utils.extractDisplayName(event.name),\n number: output.number,\n hash: output.hash,\n args: {}\n };\n\n output.topics = output.topic; // fallback for go-ethereum\n if (!output.topics) {\n return result;\n }\n \n var indexedOutputs = filterInputs(event.inputs, true);\n var indexedData = \"0x\" + output.topics.slice(1, output.topics.length).map(function (topics) { return topics.slice(2); }).join(\"\");\n var indexedRes = abi.formatOutput(indexedOutputs, indexedData);\n\n var notIndexedOutputs = filterInputs(event.inputs, false);\n var notIndexedRes = abi.formatOutput(notIndexedOutputs, output.data);\n\n result.args = getArgumentsObject(event.inputs, indexedRes, notIndexedRes);\n\n return result;\n };\n};\n\nvar getMatchingEvent = function (events, payload) {\n for (var i = 0; i < events.length; i++) {\n var sign = signature.eventSignatureFromAscii(events[i].name); \n if (sign === payload.topics[0]) {\n return events[i];\n }\n }\n return undefined;\n};\n\n\nmodule.exports = {\n inputParser: inputParser,\n outputParser: outputParser,\n getMatchingEvent: getMatchingEvent\n};\n\n", + "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file filter.js\n * @authors:\n * Jeffrey Wilcke \n * Marek Kotewicz \n * Marian Oancea \n * Fabian Vogelsteller \n * Gav Wood \n * @date 2014\n */\n\nvar utils = require('../utils/utils');\n\n/// Should be called to check if filter implementation is valid\n/// @returns true if it is, otherwise false\nvar implementationIsValid = function (i) {\n return !!i && \n typeof i.newFilter === 'function' && \n typeof i.getLogs === 'function' && \n typeof i.uninstallFilter === 'function' &&\n typeof i.startPolling === 'function' &&\n typeof i.stopPolling === 'function';\n};\n\n/// This method should be called on options object, to verify deprecated properties && lazy load dynamic ones\n/// @param should be string or object\n/// @returns options string or object\nvar getOptions = function (options) {\n /*jshint maxcomplexity:9 */\n\n if (typeof options === 'string') {\n return options;\n } \n\n options = options || {};\n\n if (options.topic) {\n console.warn('\"topic\" is deprecated, is \"topics\" instead');\n options.topics = options.topic;\n }\n\n if (options.earliest) {\n console.warn('\"earliest\" is deprecated, is \"fromBlock\" instead');\n options.fromBlock = options.earliest;\n }\n\n if (options.latest) {\n console.warn('\"latest\" is deprecated, is \"toBlock\" instead');\n options.toBlock = options.latest;\n }\n\n // make sure topics, get converted to hex\n if(options.topics instanceof Array) {\n options.topics = options.topics.map(function(topic){\n return utils.toHex(topic);\n });\n }\n\n\tvar asBlockNumber = function(n) {\n\t\tif (n == null)\n\t\t\treturn null;\n\t\tif (n == 'latest' || n == 'pending')\n\t\t\treturn n;\n\t\treturn utils.toHex(n);\n\t};\n\n // evaluate lazy properties\n return {\n\t\tfromBlock: asBlockNumber(options.fromBlock),\n\t\ttoBlock: asBlockNumber(options.toBlock),\n to: options.to,\n address: options.address,\n topics: options.topics\n };\n};\n\n/// Should be used when we want to watch something\n/// it's using inner polling mechanism and is notified about changes\n/// @param options are filter options\n/// @param implementation, an abstract polling implementation\n/// @param formatter (optional), callback function which formats output before 'real' callback \nvar filter = function(options, implementation, formatter) {\n if (!implementationIsValid(implementation)) {\n console.error('filter implemenation is invalid');\n return;\n }\n\n options = getOptions(options);\n var callbacks = [];\n var filterId = implementation.newFilter(options);\n\n // call the callbacks\n var onMessages = function (messages) {\n messages.forEach(function (message) {\n\t\t\tmessage = formatter ? formatter(message) : message;\n callbacks.forEach(function (callback) {\n\t\t\t\tcallback(message);\n });\n });\n };\n\n implementation.startPolling(filterId, onMessages, implementation.uninstallFilter);\n\n var watch = function(callback) {\n callbacks.push(callback);\n };\n\n var stopWatching = function() {\n implementation.stopPolling(filterId);\n implementation.uninstallFilter(filterId);\n callbacks = [];\n };\n\n var get = function () {\n var results = implementation.getLogs(filterId);\n\n return utils.isArray(results) ? results.map(function(message){\n return formatter ? formatter(message) : message;\n }) : results;\n };\n \n return {\n watch: watch,\n stopWatching: stopWatching,\n get: get,\n\n // DEPRECATED methods\n changed: function(){\n console.warn('watch().changed() is deprecated please use filter().watch() instead.');\n return watch.apply(this, arguments);\n },\n arrived: function(){\n console.warn('watch().arrived() is deprecated please use filter().watch() instead.');\n return watch.apply(this, arguments);\n },\n happened: function(){\n console.warn('watch().happened() is deprecated please use filter().watch() instead.');\n return watch.apply(this, arguments);\n },\n uninstall: function(){\n console.warn('watch().uninstall() is deprecated please use filter().stopWatching() instead.');\n return stopWatching.apply(this, arguments);\n },\n messages: function(){\n console.warn('watch().messages() is deprecated please use filter().get() instead.');\n return get.apply(this, arguments);\n },\n logs: function(){\n console.warn('watch().logs() is deprecated please use filter().get() instead.');\n return get.apply(this, arguments);\n }\n };\n};\n\nmodule.exports = filter;\n\n", + "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file formatters.js\n * @authors:\n * Marek Kotewicz \n * Fabian Vogelsteller \n * @date 2015\n */\n\nvar utils = require('../utils/utils');\n\n/**\n * Should the input to a big number\n *\n * @method convertToBigNumber\n * @param {String|Number|BigNumber}\n * @returns {BigNumber} object\n */\nvar convertToBigNumber = function (value) {\n return utils.toBigNumber(value);\n};\n\n/**\n * Formats the input of a transaction and converts all values to HEX\n *\n * @method inputTransactionFormatter\n * @param {Object} transaction options\n * @returns object\n*/\nvar inputTransactionFormatter = function (options){\n\n // make code -> data\n if (options.code) {\n options.data = options.code;\n delete options.code;\n }\n\n ['gasPrice', 'gas', 'value'].forEach(function(key){\n options[key] = utils.fromDecimal(options[key]);\n });\n\n return options;\n};\n\n/**\n * Formats the output of a transaction to its proper values\n * \n * @method outputTransactionFormatter\n * @param {Object} transaction\n * @returns {Object} transaction\n*/\nvar outputTransactionFormatter = function (tx){\n tx.gas = utils.toDecimal(tx.gas);\n tx.gasPrice = utils.toBigNumber(tx.gasPrice);\n tx.value = utils.toBigNumber(tx.value);\n return tx;\n};\n\n/**\n * Formats the input of a call and converts all values to HEX\n *\n * @method inputCallFormatter\n * @param {Object} transaction options\n * @returns object\n*/\nvar inputCallFormatter = function (options){\n\n // make code -> data\n if (options.code) {\n options.data = options.code;\n delete options.code;\n }\n\n return options;\n};\n\n\n/**\n * Formats the output of a block to its proper values\n *\n * @method outputBlockFormatter\n * @param {Object} block object \n * @returns {Object} block object\n*/\nvar outputBlockFormatter = function(block){\n\n // transform to number\n block.gasLimit = utils.toDecimal(block.gasLimit);\n block.gasUsed = utils.toDecimal(block.gasUsed);\n block.size = utils.toDecimal(block.size);\n block.timestamp = utils.toDecimal(block.timestamp);\n block.number = utils.toDecimal(block.number);\n\n block.minGasPrice = utils.toBigNumber(block.minGasPrice);\n block.difficulty = utils.toBigNumber(block.difficulty);\n block.totalDifficulty = utils.toBigNumber(block.totalDifficulty);\n\n if(block.transactions instanceof Array) {\n block.transactions.forEach(function(item){\n if(!utils.isString(item))\n return outputTransactionFormatter(item);\n });\n }\n\n return block;\n};\n\n/**\n * Formats the output of a log\n * \n * @method outputLogFormatter\n * @param {Object} log object\n * @returns {Object} log\n*/\nvar outputLogFormatter = function(log){\n log.blockNumber = utils.toDecimal(log.blockNumber);\n log.transactionIndex = utils.toDecimal(log.transactionIndex);\n log.logIndex = utils.toDecimal(log.logIndex);\n\n return log;\n};\n\n\n/**\n * Formats the input of a whisper post and converts all values to HEX\n *\n * @method inputPostFormatter\n * @param {Object} transaction object\n * @returns {Object}\n*/\nvar inputPostFormatter = function(post){\n\n post.payload = utils.toHex(post.payload);\n post.ttl = utils.fromDecimal(post.ttl);\n post.priority = utils.fromDecimal(post.priority);\n\n if(!(post.topics instanceof Array))\n post.topics = [post.topics];\n\n\n // format the following options\n post.topics = post.topics.map(function(topic){\n return utils.fromAscii(topic);\n });\n\n return post;\n};\n\n/**\n * Formats the output of a received post message\n *\n * @method outputPostFormatter\n * @param {Object}\n * @returns {Object}\n */\nvar outputPostFormatter = function(post){\n\n post.expiry = utils.toDecimal(post.expiry);\n post.sent = utils.toDecimal(post.sent);\n post.ttl = utils.toDecimal(post.ttl);\n post.workProved = utils.toDecimal(post.workProved);\n post.payloadRaw = post.payload;\n post.payload = utils.toAscii(post.payload);\n\n if(post.payload.indexOf('{') === 0 || post.payload.indexOf('[') === 0) {\n try {\n post.payload = JSON.parse(post.payload);\n } catch (e) { }\n }\n\n // format the following options\n post.topics = post.topics.map(function(topic){\n return utils.toAscii(topic);\n });\n\n return post;\n};\n\nmodule.exports = {\n convertToBigNumber: convertToBigNumber,\n inputTransactionFormatter: inputTransactionFormatter,\n outputTransactionFormatter: outputTransactionFormatter,\n inputCallFormatter: inputCallFormatter,\n outputBlockFormatter: outputBlockFormatter,\n outputLogFormatter: outputLogFormatter,\n inputPostFormatter: inputPostFormatter,\n outputPostFormatter: outputPostFormatter\n};\n\n", + "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file httpprovider.js\n * @authors:\n * Marek Kotewicz \n * Marian Oancea \n * Fabian Vogelsteller \n * @date 2014\n */\n\nif (\"build\" !== 'build') {/*\n var XMLHttpRequest = require('xmlhttprequest').XMLHttpRequest; // jshint ignore:line\n*/}\n\nvar HttpProvider = function (host) {\n this.name = 'HTTP';\n this.handlers = [];\n this.host = host || 'http://localhost:8080';\n};\n\nHttpProvider.prototype.send = function (payload, callback) {\n var request = new XMLHttpRequest();\n\n // ASYNC\n if(typeof callback === 'function') {\n request.onreadystatechange = function() {\n if(request.readyState === 4) {\n var result = '';\n try {\n result = JSON.parse(request.responseText);\n } catch(error) {\n result = error;\n }\n callback(result, request.status);\n }\n };\n\n request.open('POST', this.host, true);\n request.send(JSON.stringify(payload));\n\n // SYNC\n } else {\n request.open('POST', this.host, false);\n request.send(JSON.stringify(payload));\n\n // check request.status\n if(request.status !== 200)\n return;\n return JSON.parse(request.responseText);\n \n }\n};\n\nmodule.exports = HttpProvider;\n\n", "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file jsonrpc.js\n * @authors:\n * Marek Kotewicz \n * @date 2015\n */\n\nvar messageId = 1;\n\n/// Should be called to valid json create payload object\n/// @param method of jsonrpc call, required\n/// @param params, an array of method params, optional\n/// @returns valid jsonrpc payload object\nvar toPayload = function (method, params) {\n if (!method)\n console.error('jsonrpc method should be specified!');\n\n return {\n jsonrpc: '2.0',\n method: method,\n params: params || [],\n id: messageId++\n }; \n};\n\n/// Should be called to check if jsonrpc response is valid\n/// @returns true if response is valid, otherwise false \nvar isValidResponse = function (response) {\n return !!response &&\n !response.error &&\n response.jsonrpc === '2.0' &&\n typeof response.id === 'number' &&\n response.result !== undefined; // only undefined is not valid json object\n};\n\n/// Should be called to create batch payload object\n/// @param messages, an array of objects with method (required) and params (optional) fields\nvar toBatchPayload = function (messages) {\n return messages.map(function (message) {\n return toPayload(message.method, message.params);\n }); \n};\n\nmodule.exports = {\n toPayload: toPayload,\n isValidResponse: isValidResponse,\n toBatchPayload: toBatchPayload\n};\n\n\n", + "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file eth.js\n * @authors:\n * Marek Kotewicz \n * @date 2015\n */\n\nvar utils = require('../utils/utils');\n\n/// @returns an array of objects describing web3.eth api methods\nvar methods = [\n // { name: 'getBalance', call: 'eth_balanceAt', outputFormatter: formatters.convertToBigNumber},\n];\n\n/// @returns an array of objects describing web3.eth api properties\nvar properties = [\n { name: 'listening', getter: 'net_listening'},\n { name: 'peerCount', getter: 'net_peerCount', outputFormatter: utils.toDecimal },\n];\n\n\nmodule.exports = {\n methods: methods,\n properties: properties\n};\n\n", "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file qtsync.js\n * @authors:\n * Marek Kotewicz \n * Marian Oancea \n * @date 2014\n */\n\nvar QtSyncProvider = function () {\n};\n\nQtSyncProvider.prototype.send = function (payload) {\n var result = navigator.qt.callMethod(JSON.stringify(payload));\n return JSON.parse(result);\n};\n\nmodule.exports = QtSyncProvider;\n\n", - "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file requestmanager.js\n * @authors:\n * Jeffrey Wilcke \n * Marek Kotewicz \n * Marian Oancea \n * Gav Wood \n * @date 2014\n */\n\nvar jsonrpc = require('./jsonrpc');\nvar c = require('./const');\n\n/**\n * It's responsible for passing messages to providers\n * It's also responsible for polling the ethereum node for incoming messages\n * Default poll timeout is 1 second\n */\nvar requestManager = function() {\n var polls = [];\n var timeout = null;\n var provider;\n\n var send = function (data) {\n var payload = jsonrpc.toPayload(data.method, data.params);\n \n if (!provider) {\n console.error('provider is not set');\n return null;\n }\n\n var result = provider.send(payload);\n\n if (!jsonrpc.isValidResponse(result)) {\n console.log(result);\n return null;\n }\n \n return result.result;\n };\n\n var setProvider = function (p) {\n provider = p;\n };\n\n /*jshint maxparams:4 */\n var startPolling = function (data, pollId, callback, uninstall) {\n polls.push({data: data, id: pollId, callback: callback, uninstall: uninstall});\n };\n /*jshint maxparams:3 */\n\n var stopPolling = function (pollId) {\n for (var i = polls.length; i--;) {\n var poll = polls[i];\n if (poll.id === pollId) {\n polls.splice(i, 1);\n }\n }\n };\n\n var reset = function () {\n polls.forEach(function (poll) {\n poll.uninstall(poll.id); \n });\n polls = [];\n\n if (timeout) {\n clearTimeout(timeout);\n timeout = null;\n }\n poll();\n };\n\n var poll = function () {\n polls.forEach(function (data) {\n var result = send(data.data);\n if (!(result instanceof Array) || result.length === 0) {\n return;\n }\n data.callback(result);\n });\n timeout = setTimeout(poll, c.ETH_POLLING_TIMEOUT);\n };\n \n poll();\n\n return {\n send: send,\n setProvider: setProvider,\n startPolling: startPolling,\n stopPolling: stopPolling,\n reset: reset\n };\n};\n\nmodule.exports = requestManager;\n\n", - "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file shh.js\n * @authors:\n * Marek Kotewicz \n * @date 2015\n */\n\n/// @returns an array of objects describing web3.shh api methods\nvar methods = function () {\n return [\n { name: 'post', call: 'shh_post' },\n { name: 'newIdentity', call: 'shh_newIdentity' },\n { name: 'haveIdentity', call: 'shh_haveIdentity' },\n { name: 'newGroup', call: 'shh_newGroup' },\n { name: 'addToGroup', call: 'shh_addToGroup' }\n ];\n};\n\nmodule.exports = {\n methods: methods\n};\n\n", - "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file signature.js\n * @authors:\n * Marek Kotewicz \n * @date 2015\n */\n\nvar web3 = require('./web3'); \nvar c = require('./const');\n\n/// @param function name for which we want to get signature\n/// @returns signature of function with given name\nvar functionSignatureFromAscii = function (name) {\n return web3.sha3(web3.fromAscii(name)).slice(0, 2 + c.ETH_SIGNATURE_LENGTH * 2);\n};\n\n/// @param event name for which we want to get signature\n/// @returns signature of event with given name\nvar eventSignatureFromAscii = function (name) {\n return web3.sha3(web3.fromAscii(name));\n};\n\nmodule.exports = {\n functionSignatureFromAscii: functionSignatureFromAscii,\n eventSignatureFromAscii: eventSignatureFromAscii\n};\n\n", - "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file types.js\n * @authors:\n * Marek Kotewicz \n * @date 2015\n */\n\nvar f = require('./formatters');\n\n/// @param expected type prefix (string)\n/// @returns function which checks if type has matching prefix. if yes, returns true, otherwise false\nvar prefixedType = function (prefix) {\n return function (type) {\n return type.indexOf(prefix) === 0;\n };\n};\n\n/// @param expected type name (string)\n/// @returns function which checks if type is matching expected one. if yes, returns true, otherwise false\nvar namedType = function (name) {\n return function (type) {\n return name === type;\n };\n};\n\n/// Setups input formatters for solidity types\n/// @returns an array of input formatters \nvar inputTypes = function () {\n \n return [\n { type: prefixedType('uint'), format: f.formatInputInt },\n { type: prefixedType('int'), format: f.formatInputInt },\n { type: prefixedType('hash'), format: f.formatInputInt },\n { type: prefixedType('string'), format: f.formatInputString }, \n { type: prefixedType('real'), format: f.formatInputReal },\n { type: prefixedType('ureal'), format: f.formatInputReal },\n { type: namedType('address'), format: f.formatInputInt },\n { type: namedType('bool'), format: f.formatInputBool }\n ];\n};\n\n/// Setups output formaters for solidity types\n/// @returns an array of output formatters\nvar outputTypes = function () {\n\n return [\n { type: prefixedType('uint'), format: f.formatOutputUInt },\n { type: prefixedType('int'), format: f.formatOutputInt },\n { type: prefixedType('hash'), format: f.formatOutputHash },\n { type: prefixedType('string'), format: f.formatOutputString },\n { type: prefixedType('real'), format: f.formatOutputReal },\n { type: prefixedType('ureal'), format: f.formatOutputUReal },\n { type: namedType('address'), format: f.formatOutputAddress },\n { type: namedType('bool'), format: f.formatOutputBool }\n ];\n};\n\nmodule.exports = {\n prefixedType: prefixedType,\n namedType: namedType,\n inputTypes: inputTypes,\n outputTypes: outputTypes\n};\n\n", - "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file utils.js\n * @authors:\n * Marek Kotewicz \n * @date 2015\n */\n\nvar c = require('./const');\n\n/// Finds first index of array element matching pattern\n/// @param array\n/// @param callback pattern\n/// @returns index of element\nvar findIndex = function (array, callback) {\n var end = false;\n var i = 0;\n for (; i < array.length && !end; i++) {\n end = callback(array[i]);\n }\n return end ? i - 1 : -1;\n};\n\n/// @returns ascii string representation of hex value prefixed with 0x\nvar toAscii = function(hex) {\n// Find termination\n var str = \"\";\n var i = 0, l = hex.length;\n if (hex.substring(0, 2) === '0x') {\n i = 2;\n }\n for (; i < l; i+=2) {\n var code = parseInt(hex.substr(i, 2), 16);\n if (code === 0) {\n break;\n }\n\n str += String.fromCharCode(code);\n }\n\n return str;\n};\n \nvar toHex = function(str) {\n var hex = \"\";\n for(var i = 0; i < str.length; i++) {\n var n = str.charCodeAt(i).toString(16);\n hex += n.length < 2 ? '0' + n : n;\n }\n\n return hex;\n};\n\n/// @returns hex representation (prefixed by 0x) of ascii string\nvar fromAscii = function(str, pad) {\n pad = pad === undefined ? 0 : pad;\n var hex = toHex(str);\n while (hex.length < pad*2)\n hex += \"00\";\n return \"0x\" + hex;\n};\n\n/// @returns display name for function/event eg. multiply(uint256) -> multiply\nvar extractDisplayName = function (name) {\n var length = name.indexOf('('); \n return length !== -1 ? name.substr(0, length) : name;\n};\n\n/// @returns overloaded part of function/event name\nvar extractTypeName = function (name) {\n /// TODO: make it invulnerable\n var length = name.indexOf('(');\n return length !== -1 ? name.substr(length + 1, name.length - 1 - (length + 1)).replace(' ', '') : \"\";\n};\n\n/// Filters all function from input abi\n/// @returns abi array with filtered objects of type 'function'\nvar filterFunctions = function (json) {\n return json.filter(function (current) {\n return current.type === 'function'; \n }); \n};\n\n/// Filters all events form input abi\n/// @returns abi array with filtered objects of type 'event'\nvar filterEvents = function (json) {\n return json.filter(function (current) {\n return current.type === 'event';\n });\n};\n\n/// used to transform value/string to eth string\n/// TODO: use BigNumber.js to parse int\n/// TODO: add tests for it!\nvar toEth = function (str) {\n /*jshint maxcomplexity:7 */\n var val = typeof str === \"string\" ? str.indexOf('0x') === 0 ? parseInt(str.substr(2), 16) : parseInt(str) : str;\n var unit = 0;\n var units = c.ETH_UNITS;\n while (val > 3000 && unit < units.length - 1)\n {\n val /= 1000;\n unit++;\n }\n var s = val.toString().length < val.toFixed(2).length ? val.toString() : val.toFixed(2);\n var replaceFunction = function($0, $1, $2) {\n return $1 + ',' + $2;\n };\n\n while (true) {\n var o = s;\n s = s.replace(/(\\d)(\\d\\d\\d[\\.\\,])/, replaceFunction);\n if (o === s)\n break;\n }\n return s + ' ' + units[unit];\n};\n\nmodule.exports = {\n findIndex: findIndex,\n toAscii: toAscii,\n fromAscii: fromAscii,\n extractDisplayName: extractDisplayName,\n extractTypeName: extractTypeName,\n filterFunctions: filterFunctions,\n filterEvents: filterEvents,\n toEth: toEth\n};\n\n", - "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file watches.js\n * @authors:\n * Marek Kotewicz \n * @date 2015\n */\n\n/// @returns an array of objects describing web3.eth.watch api methods\nvar eth = function () {\n var newFilter = function (args) {\n return typeof args[0] === 'string' ? 'eth_newFilterString' : 'eth_newFilter';\n };\n\n return [\n { name: 'newFilter', call: newFilter },\n { name: 'uninstallFilter', call: 'eth_uninstallFilter' },\n { name: 'getMessages', call: 'eth_filterLogs' }\n ];\n};\n\n/// @returns an array of objects describing web3.shh.watch api methods\nvar shh = function () {\n return [\n { name: 'newFilter', call: 'shh_newFilter' },\n { name: 'uninstallFilter', call: 'shh_uninstallFilter' },\n { name: 'getMessages', call: 'shh_getMessages' }\n ];\n};\n\nmodule.exports = {\n eth: eth,\n shh: shh\n};\n\n", - "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file web3.js\n * @authors:\n * Jeffrey Wilcke \n * Marek Kotewicz \n * Marian Oancea \n * Gav Wood \n * @date 2014\n */\n\nif (\"build\" !== 'build') {/*\n var BigNumber = require('bignumber.js');\n*/}\n\nvar eth = require('./eth');\nvar db = require('./db');\nvar shh = require('./shh');\nvar watches = require('./watches');\nvar filter = require('./filter');\nvar utils = require('./utils');\nvar requestManager = require('./requestmanager');\n\n/// @returns an array of objects describing web3 api methods\nvar web3Methods = function () {\n return [\n { name: 'sha3', call: 'web3_sha3' }\n ];\n};\n\n/// creates methods in a given object based on method description on input\n/// setups api calls for these methods\nvar setupMethods = function (obj, methods) {\n methods.forEach(function (method) {\n obj[method.name] = function () {\n var args = Array.prototype.slice.call(arguments);\n var call = typeof method.call === 'function' ? method.call(args) : method.call;\n return web3.manager.send({\n method: call,\n params: args\n });\n };\n });\n};\n\n/// creates properties in a given object based on properties description on input\n/// setups api calls for these properties\nvar setupProperties = function (obj, properties) {\n properties.forEach(function (property) {\n var proto = {};\n proto.get = function () {\n return web3.manager.send({\n method: property.getter\n });\n };\n\n if (property.setter) {\n proto.set = function (val) {\n return web3.manager.send({\n method: property.setter,\n params: [val]\n });\n };\n }\n Object.defineProperty(obj, property.name, proto);\n });\n};\n\n/*jshint maxparams:4 */\nvar startPolling = function (method, id, callback, uninstall) {\n web3.manager.startPolling({\n method: method, \n params: [id]\n }, id, callback, uninstall); \n};\n/*jshint maxparams:3 */\n\nvar stopPolling = function (id) {\n web3.manager.stopPolling(id);\n};\n\nvar ethWatch = {\n startPolling: startPolling.bind(null, 'eth_changed'), \n stopPolling: stopPolling\n};\n\nvar shhWatch = {\n startPolling: startPolling.bind(null, 'shh_changed'), \n stopPolling: stopPolling\n};\n\n/// setups web3 object, and it's in-browser executed methods\nvar web3 = {\n manager: requestManager(),\n providers: {},\n\n /// @returns ascii string representation of hex value prefixed with 0x\n toAscii: utils.toAscii,\n\n /// @returns hex representation (prefixed by 0x) of ascii string\n fromAscii: utils.fromAscii,\n\n /// @returns decimal representaton of hex value prefixed by 0x\n toDecimal: function (val) {\n // remove 0x and place 0, if it's required\n val = val.length > 2 ? val.substring(2) : \"0\";\n return (new BigNumber(val, 16).toString(10));\n },\n\n /// @returns hex representation (prefixed by 0x) of decimal value\n fromDecimal: function (val) {\n return \"0x\" + (new BigNumber(val).toString(16));\n },\n\n /// used to transform value/string to eth string\n toEth: utils.toEth,\n\n /// eth object prototype\n eth: {\n contractFromAbi: function (abi) {\n return function(addr) {\n // Default to address of Config. TODO: rremove prior to genesis.\n addr = addr || '0xc6d9d2cd449a754c494264e1809c50e34d64562b';\n var ret = web3.eth.contract(addr, abi);\n ret.address = addr;\n return ret;\n };\n },\n\n canary: function (abi) {\n return function(addr) {\n // Default to address of Config. TODO: rremove prior to genesis.\n addr = addr || '0xc6d9d2cd449a754c494264e1809c50e34d64562b';\n return addr;\n };\n },\n\n /// @param filter may be a string, object or event\n /// @param indexed is optional, this is an object with optional event indexed params\n /// @param options is optional, this is an object with optional event options ('max'...)\n /// TODO: fix it, 4 params? no way\n /*jshint maxparams:4 */\n watch: function (fil, indexed, options, formatter) {\n if (fil._isEvent) {\n return fil(indexed, options);\n }\n return filter(fil, ethWatch, formatter);\n }\n /*jshint maxparams:3 */\n },\n\n /// db object prototype\n db: {},\n\n /// shh object prototype\n shh: {\n /// @param filter may be a string, object or event\n watch: function (fil) {\n return filter(fil, shhWatch);\n }\n },\n setProvider: function (provider) {\n web3.manager.setProvider(provider);\n },\n \n /// Should be called to reset state of web3 object\n /// Resets everything except manager\n reset: function () {\n web3.manager.reset(); \n }\n};\n\n/// setups all api methods\nsetupMethods(web3, web3Methods());\nsetupMethods(web3.eth, eth.methods());\nsetupProperties(web3.eth, eth.properties());\nsetupMethods(web3.db, db.methods());\nsetupMethods(web3.shh, shh.methods());\nsetupMethods(ethWatch, watches.eth());\nsetupMethods(shhWatch, watches.shh());\n\nmodule.exports = web3;\n\n", - "var web3 = require('./lib/web3');\nweb3.providers.HttpSyncProvider = require('./lib/httpsync');\nweb3.providers.QtSyncProvider = require('./lib/qtsync');\nweb3.eth.contract = require('./lib/contract');\nweb3.abi = require('./lib/abi');\n\nmodule.exports = web3;\n" + "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file requestmanager.js\n * @authors:\n * Jeffrey Wilcke \n * Marek Kotewicz \n * Marian Oancea \n * Fabian Vogelsteller \n * Gav Wood \n * @date 2014\n */\n\nvar jsonrpc = require('./jsonrpc');\nvar c = require('../utils/config');\n\n/**\n * It's responsible for passing messages to providers\n * It's also responsible for polling the ethereum node for incoming messages\n * Default poll timeout is 1 second\n */\nvar requestManager = function() {\n var polls = [];\n var timeout = null;\n var provider;\n\n var send = function (data, callback) {\n /*jshint maxcomplexity: 8 */\n\n // FORMAT BASED ON ONE FORMATTER function\n if(typeof data.inputFormatter === 'function') {\n data.params = Array.prototype.map.call(data.params, function(item, index){\n // format everything besides the defaultblock, which is already formated\n return (!data.addDefaultblock || index+1 < data.addDefaultblock) ? data.inputFormatter(item) : item;\n });\n\n // FORMAT BASED ON the input FORMATTER ARRAY\n } else if(data.inputFormatter instanceof Array) {\n data.params = Array.prototype.map.call(data.inputFormatter, function(formatter, index){\n // format everything besides the defaultblock, which is already formated\n return (!data.addDefaultblock || index+1 < data.addDefaultblock) ? formatter(data.params[index]) : data.params[index];\n });\n }\n\n\n var payload = jsonrpc.toPayload(data.method, data.params);\n \n if (!provider) {\n console.error('provider is not set');\n return null;\n }\n\n // HTTP ASYNC (only when callback is given, and it a HttpProvidor)\n if(typeof callback === 'function' && provider.name === 'HTTP'){\n provider.send(payload, function(result, status){\n\n if (!jsonrpc.isValidResponse(result)) {\n if(typeof result === 'object' && result.error && result.error.message) {\n console.error(result.error.message);\n callback(result.error);\n } else {\n callback(new Error({\n status: status,\n error: result,\n message: 'Bad Request'\n }));\n }\n return null;\n }\n\n // format the output\n callback(null, (typeof data.outputFormatter === 'function') ? data.outputFormatter(result.result) : result.result);\n });\n\n // SYNC\n } else {\n var result = provider.send(payload);\n\n if (!jsonrpc.isValidResponse(result)) {\n if(typeof result === 'object' && result.error && result.error.message)\n console.error(result.error.message);\n return null;\n }\n\n // format the output\n return (typeof data.outputFormatter === 'function') ? data.outputFormatter(result.result) : result.result;\n }\n \n };\n\n var setProvider = function (p) {\n provider = p;\n };\n\n /*jshint maxparams:4 */\n var startPolling = function (data, pollId, callback, uninstall) {\n polls.push({data: data, id: pollId, callback: callback, uninstall: uninstall});\n };\n /*jshint maxparams:3 */\n\n var stopPolling = function (pollId) {\n for (var i = polls.length; i--;) {\n var poll = polls[i];\n if (poll.id === pollId) {\n polls.splice(i, 1);\n }\n }\n };\n\n var reset = function () {\n polls.forEach(function (poll) {\n poll.uninstall(poll.id); \n });\n polls = [];\n\n if (timeout) {\n clearTimeout(timeout);\n timeout = null;\n }\n poll();\n };\n\n var poll = function () {\n polls.forEach(function (data) {\n // send async\n send(data.data, function(error, result){\n if (!(result instanceof Array) || result.length === 0) {\n return;\n }\n data.callback(result);\n });\n });\n timeout = setTimeout(poll, c.ETH_POLLING_TIMEOUT);\n };\n \n poll();\n\n return {\n send: send,\n setProvider: setProvider,\n startPolling: startPolling,\n stopPolling: stopPolling,\n reset: reset\n };\n};\n\nmodule.exports = requestManager;\n\n", + "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file shh.js\n * @authors:\n * Marek Kotewicz \n * @date 2015\n */\n\nvar formatters = require('./formatters');\n\n/// @returns an array of objects describing web3.shh api methods\nvar methods = function () {\n return [\n { name: 'post', call: 'shh_post', inputFormatter: formatters.inputPostFormatter },\n { name: 'newIdentity', call: 'shh_newIdentity' },\n { name: 'hasIdentity', call: 'shh_hasIdentity' },\n { name: 'newGroup', call: 'shh_newGroup' },\n { name: 'addToGroup', call: 'shh_addToGroup' },\n\n // deprecated\n { name: 'haveIdentity', call: 'shh_haveIdentity', newMethod: 'shh.hasIdentity' },\n ];\n};\n\nmodule.exports = {\n methods: methods\n};\n\n", + "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file signature.js\n * @authors:\n * Marek Kotewicz \n * @date 2015\n */\n\nvar web3 = require('../web3'); \nvar c = require('../utils/config');\n\n/// @param function name for which we want to get signature\n/// @returns signature of function with given name\nvar functionSignatureFromAscii = function (name) {\n return web3.sha3(web3.fromAscii(name)).slice(0, 2 + c.ETH_SIGNATURE_LENGTH * 2);\n};\n\n/// @param event name for which we want to get signature\n/// @returns signature of event with given name\nvar eventSignatureFromAscii = function (name) {\n return web3.sha3(web3.fromAscii(name));\n};\n\nmodule.exports = {\n functionSignatureFromAscii: functionSignatureFromAscii,\n eventSignatureFromAscii: eventSignatureFromAscii\n};\n\n", + "/*\n This file is part of ethereum.js.\n\n ethereum.js is free software: you can redistribute it and/or modify\n it under the terms of the GNU Lesser General Public License as published by\n the Free Software Foundation, either version 3 of the License, or\n (at your option) any later version.\n\n ethereum.js is distributed in the hope that it will be useful,\n but WITHOUT ANY WARRANTY; without even the implied warranty of\n MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n GNU Lesser General Public License for more details.\n\n You should have received a copy of the GNU Lesser General Public License\n along with ethereum.js. If not, see .\n*/\n/** @file watches.js\n * @authors:\n * Marek Kotewicz \n * @date 2015\n */\n\n/// @returns an array of objects describing web3.eth.filter api methods\nvar eth = function () {\n var newFilter = function (args) {\n return typeof args[0] === 'string' ? 'eth_newBlockFilter' : 'eth_newFilter';\n };\n\n return [\n { name: 'newFilter', call: newFilter },\n { name: 'uninstallFilter', call: 'eth_uninstallFilter' },\n { name: 'getLogs', call: 'eth_getFilterLogs' }\n ];\n};\n\n/// @returns an array of objects describing web3.shh.watch api methods\nvar shh = function () {\n return [\n { name: 'newFilter', call: 'shh_newFilter' },\n { name: 'uninstallFilter', call: 'shh_uninstallFilter' },\n { name: 'getLogs', call: 'shh_getMessages' }\n ];\n};\n\nmodule.exports = {\n eth: eth,\n shh: shh\n};\n\n", + "module.exports={\n \"version\": \"0.1.3\"\n}", + "var web3 = require('./lib/web3');\nweb3.providers.HttpProvider = require('./lib/web3/httpprovider');\nweb3.providers.QtSyncProvider = require('./lib/web3/qtsync');\nweb3.eth.contract = require('./lib/web3/contract');\nweb3.abi = require('./lib/solidity/abi');\n\nmodule.exports = web3;\n" ] } \ No newline at end of file diff --git a/libjsqrc/ethereumjs/dist/ethereum.min.js b/libjsqrc/ethereumjs/dist/ethereum.min.js index e65ae4f09..4aab986d6 100644 --- a/libjsqrc/ethereumjs/dist/ethereum.min.js +++ b/libjsqrc/ethereumjs/dist/ethereum.min.js @@ -1 +1 @@ -require=function t(n,e,r){function o(i,u){if(!e[i]){if(!n[i]){var f="function"==typeof require&&require;if(!u&&f)return f(i,!0);if(a)return a(i,!0);var s=new Error("Cannot find module '"+i+"'");throw s.code="MODULE_NOT_FOUND",s}var c=e[i]={exports:{}};n[i][0].call(c.exports,function(t){var e=n[i][1][t];return o(e?e:t)},c,c.exports,t,n,e,r)}return e[i].exports}for(var a="function"==typeof require&&require,i=0;iv;v++)g.push(h(n.slice(0,f))),n=n.slice(f);e.push(g)}else r.prefixedType("string")(t[s].type)?(c=c.slice(f),e.push(h(n.slice(0,f))),n=n.slice(f)):(e.push(h(n.slice(0,f))),n=n.slice(f))}),e},h=function(t){var n={};return t.forEach(function(t){var r=e.extractDisplayName(t.name),o=e.extractTypeName(t.name),a=function(){var n=Array.prototype.slice.call(arguments);return c(t.inputs,n)};void 0===n[r]&&(n[r]=a),n[r][o]=a}),n},d=function(t){var n={};return t.forEach(function(t){var r=e.extractDisplayName(t.name),o=e.extractTypeName(t.name),a=function(n){return m(t.outputs,n)};void 0===n[r]&&(n[r]=a),n[r][o]=a}),n};n.exports={inputParser:h,outputParser:d,formatInput:c,formatOutput:m}},{"./const":2,"./formatters":8,"./types":15,"./utils":16}],2:[function(t,n){var e=["wei","Kwei","Mwei","Gwei","szabo","finney","ether","grand","Mether","Gether","Tether","Pether","Eether","Zether","Yether","Nether","Dether","Vether","Uether"];n.exports={ETH_PADDING:32,ETH_SIGNATURE_LENGTH:4,ETH_UNITS:e,ETH_BIGNUMBER_ROUNDING_MODE:{ROUNDING_MODE:BigNumber.ROUND_DOWN},ETH_POLLING_TIMEOUT:1e3}},{}],3:[function(t,n){var e=t("./web3"),r=t("./abi"),o=t("./utils"),a=t("./event"),i=t("./signature"),u=function(t){e._currentContractAbi=t.abi,e._currentContractAddress=t.address,e._currentContractMethodName=t.method,e._currentContractMethodParams=t.params},f=function(t){t.call=function(n){return t._isTransact=!1,t._options=n,t},t.transact=function(n){return t._isTransact=!0,t._options=n,t},t._options={},["gas","gasPrice","value","from"].forEach(function(n){t[n]=function(e){return t._options[n]=e,t}})},s=function(t,n,a){var f=r.inputParser(n),s=r.outputParser(n);o.filterFunctions(n).forEach(function(r){var c=o.extractDisplayName(r.name),l=o.extractTypeName(r.name),p=function(){var o=Array.prototype.slice.call(arguments),p=i.functionSignatureFromAscii(r.name),m=f[c][l].apply(null,o),h=t._options||{};h.to=a,h.data=p+m;var d=t._isTransact===!0||t._isTransact!==!1&&!r.constant,g=h.collapse!==!1;if(t._options={},t._isTransact=null,d)return u({abi:n,address:a,method:r.name,params:o}),void e.eth.transact(h);var v=e.eth.call(h),y=s[c][l](v);return g&&(1===y.length?y=y[0]:0===y.length&&(y=null)),y};void 0===t[c]&&(t[c]=p),t[c][l]=p})},c=function(t,n,e){t.address=e,t._onWatchEventResult=function(t){var e=event.getMatchingEvent(o.filterEvents(n)),r=a.outputParser(e);return r(t)},Object.defineProperty(t,"topic",{get:function(){return o.filterEvents(n).map(function(t){return i.eventSignatureFromAscii(t.name)})}})},l=function(t,n,r){o.filterEvents(n).forEach(function(n){var u=function(){var t=Array.prototype.slice.call(arguments),o=i.eventSignatureFromAscii(n.name),u=a.inputParser(r,o,n),f=u.apply(null,t),s=function(t){var e=a.outputParser(n);return e(t)};return e.eth.watch(f,void 0,void 0,s)};u._isEvent=!0;var f=o.extractDisplayName(n.name),s=o.extractTypeName(n.name);void 0===t[f]&&(t[f]=u),t[f][s]=u})},p=function(t,n){n.forEach(function(t){if(-1===t.name.indexOf("(")){var n=t.name,e=t.inputs.map(function(t){return t.type}).join();t.name=n+"("+e+")"}});var e={};return f(e),s(e,n,t),c(e,n,t),l(e,n,t),e};n.exports=p},{"./abi":1,"./event":6,"./signature":14,"./utils":16,"./web3":18}],4:[function(t,n){var e=function(){return[{name:"put",call:"db_put"},{name:"get",call:"db_get"},{name:"putString",call:"db_putString"},{name:"getString",call:"db_getString"}]};n.exports={methods:e}},{}],5:[function(t,n){var e=function(){var t=function(t){return"string"==typeof t[0]?"eth_blockByHash":"eth_blockByNumber"},n=function(t){return"string"==typeof t[0]?"eth_transactionByHash":"eth_transactionByNumber"},e=function(t){return"string"==typeof t[0]?"eth_uncleByHash":"eth_uncleByNumber"},r=function(t){return"string"==typeof t[0]?"eth_transactionCountByHash":"eth_transactionCountByNumber"},o=function(t){return"string"==typeof t[0]?"eth_uncleCountByHash":"eth_uncleCountByNumber"};return[{name:"balanceAt",call:"eth_balanceAt"},{name:"stateAt",call:"eth_stateAt"},{name:"storageAt",call:"eth_storageAt"},{name:"countAt",call:"eth_countAt"},{name:"codeAt",call:"eth_codeAt"},{name:"transact",call:"eth_transact"},{name:"call",call:"eth_call"},{name:"block",call:t},{name:"transaction",call:n},{name:"uncle",call:e},{name:"compilers",call:"eth_compilers"},{name:"flush",call:"eth_flush"},{name:"lll",call:"eth_lll"},{name:"solidity",call:"eth_solidity"},{name:"serpent",call:"eth_serpent"},{name:"logs",call:"eth_logs"},{name:"transactionCount",call:r},{name:"uncleCount",call:o}]},r=function(){return[{name:"coinbase",getter:"eth_coinbase",setter:"eth_setCoinbase"},{name:"listening",getter:"eth_listening",setter:"eth_setListening"},{name:"mining",getter:"eth_mining",setter:"eth_setMining"},{name:"gasPrice",getter:"eth_gasPrice"},{name:"accounts",getter:"eth_accounts"},{name:"peerCount",getter:"eth_peerCount"},{name:"defaultBlock",getter:"eth_defaultBlock",setter:"eth_setDefaultBlock"},{name:"number",getter:"eth_number"}]};n.exports={methods:e,properties:r}},{}],6:[function(t,n){var e=t("./abi"),r=t("./utils"),o=t("./signature"),a=function(t,n){return t.filter(function(t){return t.indexed===n})},i=function(t,n){var e=r.findIndex(t,function(t){return t.name===n});return-1===e?void console.error("indexed param with name "+n+" not found"):t[e]},u=function(t,n){return Object.keys(n).map(function(r){var o=[i(a(t.inputs,!0),r)],u=n[r];return u instanceof Array?u.map(function(t){return e.formatInput(o,[t])}):e.formatInput(o,[u])})},f=function(t,n,e){return function(r,o){var a=o||{};return a.address=t,a.topic=[],a.topic.push(n),r&&(a.topic=a.topic.concat(u(e,r))),a}},s=function(t,n,e){var r=n.slice(),o=e.slice();return t.reduce(function(t,n){var e;return e=n.indexed?r.splice(0,1)[0]:o.splice(0,1)[0],t[n.name]=e,t},{})},c=function(t){return function(n){var o={event:r.extractDisplayName(t.name),number:n.number,hash:n.hash,args:{}};if(n.topics=n.topic,!n.topic)return o;var i=a(t.inputs,!0),u="0x"+n.topic.slice(1,n.topic.length).map(function(t){return t.slice(2)}).join(""),f=e.formatOutput(i,u),c=a(t.inputs,!1),l=e.formatOutput(c,n.data);return o.args=s(t.inputs,f,l),o}},l=function(t,n){for(var e=0;ee;e+=2){var o=parseInt(t.substr(e,2),16);if(0===o)break;n+=String.fromCharCode(o)}return n},a=function(t){for(var n="",e=0;e3e3&&r2?t.substring(2):"0",new BigNumber(t,16).toString(10)},fromDecimal:function(t){return"0x"+new BigNumber(t).toString(16)},toEth:u.toEth,eth:{contractFromAbi:function(t){return function(n){n=n||"0xc6d9d2cd449a754c494264e1809c50e34d64562b";var e=g.eth.contract(n,t);return e.address=n,e}},canary:function(){return function(t){return t=t||"0xc6d9d2cd449a754c494264e1809c50e34d64562b"}},watch:function(t,n,e,r){return t._isEvent?t(n,e):i(t,h,r)}},db:{},shh:{watch:function(t){return i(t,d)}},setProvider:function(t){g.manager.setProvider(t)},reset:function(){g.manager.reset()}};c(g,s()),c(g.eth,e.methods()),l(g.eth,e.properties()),c(g.db,r.methods()),c(g.shh,o.methods()),c(h,a.eth()),c(d,a.shh()),n.exports=g},{"./db":4,"./eth":5,"./filter":7,"./requestmanager":12,"./shh":13,"./utils":16,"./watches":17}],web3:[function(t,n){var e=t("./lib/web3");e.providers.HttpSyncProvider=t("./lib/httpsync"),e.providers.QtSyncProvider=t("./lib/qtsync"),e.eth.contract=t("./lib/contract"),e.abi=t("./lib/abi"),n.exports=e},{"./lib/abi":1,"./lib/contract":3,"./lib/httpsync":9,"./lib/qtsync":11,"./lib/web3":18}]},{},["web3"]); \ No newline at end of file +require=function t(e,n,r){function o(a,u){if(!n[a]){if(!e[a]){var s="function"==typeof require&&require;if(!u&&s)return s(a,!0);if(i)return i(a,!0);var c=new Error("Cannot find module '"+a+"'");throw c.code="MODULE_NOT_FOUND",c}var l=n[a]={exports:{}};e[a][0].call(l.exports,function(t){var n=e[a][1][t];return o(n?n:t)},l,l.exports,t,e,n,r)}return n[a].exports}for(var i="function"==typeof require&&require,a=0;ay;y++)g.push(d(e.slice(0,s))),e=e.slice(s);n.push(g)}else o.prefixedType("bytes")(t[c].type)?(l=l.slice(s),n.push(d(e.slice(0,s))),e=e.slice(s)):(n.push(d(e.slice(0,s))),e=e.slice(s))}),n},d=function(t){var e={};return t.forEach(function(t){var r=n.extractDisplayName(t.name),o=n.extractTypeName(t.name),i=function(){var e=Array.prototype.slice.call(arguments);return l(t.inputs,e)};void 0===e[r]&&(e[r]=i),e[r][o]=i}),e},h=function(t){var e={};return t.forEach(function(t){var r=n.extractDisplayName(t.name),o=n.extractTypeName(t.name),i=function(e){return m(t.outputs,e)};void 0===e[r]&&(e[r]=i),e[r][o]=i}),e};e.exports={inputParser:d,outputParser:h,formatInput:l,formatOutput:m}},{"../utils/config":4,"../utils/utils":5,"./formatters":2,"./types":3}],2:[function(t,e){var n=t("../utils/utils"),r=t("../utils/config"),o=function(t,e,n){return new Array(e-t.length+1).join(n?n:"0")+t},i=function(t){var e=2*r.ETH_PADDING;return BigNumber.config(r.ETH_BIGNUMBER_ROUNDING_MODE),o(n.toTwosComplement(t).round().toString(16),e)},a=function(t){return n.fromAscii(t,r.ETH_PADDING).substr(2)},u=function(t){return"000000000000000000000000000000000000000000000000000000000000000"+(t?"1":"0")},s=function(t){return i(new BigNumber(t).times(new BigNumber(2).pow(128)))},c=function(t){return"1"===new BigNumber(t.substr(0,1),16).toString(2).substr(0,1)},l=function(t){return t=t||"0",c(t)?new BigNumber(t,16).minus(new BigNumber("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",16)).minus(1):new BigNumber(t,16)},f=function(t){return t=t||"0",new BigNumber(t,16)},p=function(t){return l(t).dividedBy(new BigNumber(2).pow(128))},m=function(t){return f(t).dividedBy(new BigNumber(2).pow(128))},d=function(t){return"0x"+t},h=function(t){return"0000000000000000000000000000000000000000000000000000000000000001"===t?!0:!1},g=function(t){return n.toAscii(t)},y=function(t){return"0x"+t.slice(t.length-40,t.length)};e.exports={formatInputInt:i,formatInputString:a,formatInputBool:u,formatInputReal:s,formatOutputInt:l,formatOutputUInt:f,formatOutputReal:p,formatOutputUReal:m,formatOutputHash:d,formatOutputBool:h,formatOutputString:g,formatOutputAddress:y}},{"../utils/config":4,"../utils/utils":5}],3:[function(t,e){var n=t("./formatters"),r=function(t){return function(e){return 0===e.indexOf(t)}},o=function(t){return function(e){return t===e}},i=function(){return[{type:r("uint"),format:n.formatInputInt},{type:r("int"),format:n.formatInputInt},{type:r("bytes"),format:n.formatInputString},{type:r("real"),format:n.formatInputReal},{type:r("ureal"),format:n.formatInputReal},{type:o("address"),format:n.formatInputInt},{type:o("bool"),format:n.formatInputBool}]},a=function(){return[{type:r("uint"),format:n.formatOutputUInt},{type:r("int"),format:n.formatOutputInt},{type:r("bytes"),format:n.formatOutputString},{type:r("real"),format:n.formatOutputReal},{type:r("ureal"),format:n.formatOutputUReal},{type:o("address"),format:n.formatOutputAddress},{type:o("bool"),format:n.formatOutputBool}]};e.exports={prefixedType:r,namedType:o,inputTypes:i,outputTypes:a}},{"./formatters":2}],4:[function(t,e){var n=["wei","Kwei","Mwei","Gwei","szabo","finney","ether","grand","Mether","Gether","Tether","Pether","Eether","Zether","Yether","Nether","Dether","Vether","Uether"];e.exports={ETH_PADDING:32,ETH_SIGNATURE_LENGTH:4,ETH_UNITS:n,ETH_BIGNUMBER_ROUNDING_MODE:{ROUNDING_MODE:BigNumber.ROUND_DOWN},ETH_POLLING_TIMEOUT:1e3,ETH_DEFAULTBLOCK:"latest"}},{}],5:[function(t,e){var n={wei:"1",kwei:"1000",ada:"1000",mwei:"1000000",babbage:"1000000",gwei:"1000000000",shannon:"1000000000",szabo:"1000000000000",finney:"1000000000000000",ether:"1000000000000000000",kether:"1000000000000000000000",grand:"1000000000000000000000",einstein:"1000000000000000000000",mether:"1000000000000000000000000",gether:"1000000000000000000000000000",tether:"1000000000000000000000000000000"},r=function(t,e){for(var n=!1,r=0;rn;n+=2){var o=parseInt(t.substr(n,2),16);if(0===o)break;e+=String.fromCharCode(o)}return e},i=function(t){for(var e="",n=0;n1?(t[n[0]]||(t[n[0]]={}),t[n[0]][n[1]]=r):t[n[0]]=r})},g=function(t,e){e.forEach(function(e){var n=e.name.split("."),r={};r.get=function(){return e.newProperty&&console.warn("This property is deprecated please use web3."+e.newProperty+" instead."),_.manager.send({method:e.getter,outputFormatter:e.outputFormatter})},e.setter&&(r.set=function(t){return e.newProperty&&console.warn("This property is deprecated please use web3."+e.newProperty+" instead."),_.manager.send({method:e.setter,params:[t],inputFormatter:e.inputFormatter})}),r.enumerable=!e.newProperty,n.length>1?(t[n[0]]||(t[n[0]]={}),Object.defineProperty(t[n[0]],n[1],r)):Object.defineProperty(t,e.name,r)})},y=function(t,e,n,r){_.manager.startPolling({method:t,params:[e]},e,n,r)},b=function(t){_.manager.stopPolling(t)},v={startPolling:y.bind(null,"eth_getFilterChanges"),stopPolling:b},w={startPolling:y.bind(null,"shh_getFilterChanges"),stopPolling:b},_={version:{api:n.version},manager:f(),providers:{},setProvider:function(t){_.manager.setProvider(t)},reset:function(){_.manager.reset()},toHex:c.toHex,toAscii:c.toAscii,fromAscii:c.fromAscii,toDecimal:c.toDecimal,fromDecimal:c.fromDecimal,toBigNumber:c.toBigNumber,toWei:c.toWei,fromWei:c.fromWei,isAddress:c.isAddress,net:{},eth:{contractFromAbi:function(t){return console.warn("Initiating a contract like this is deprecated please use var MyContract = eth.contract(abi); new MyContract(address); instead."),function(e){e=e||"0xc6d9d2cd449a754c494264e1809c50e34d64562b";var n=_.eth.contract(e,t);return n.address=e,n}},filter:function(t,e,n){return t._isEvent?t(e,n):s(t,v,l.outputLogFormatter)},watch:function(t,e,n){return console.warn("eth.watch() is deprecated please use eth.filter() instead."),this.filter(t,e,n)}},db:{},shh:{filter:function(t){return s(t,w,l.outputPostFormatter)},watch:function(t){return console.warn("shh.watch() is deprecated please use shh.filter() instead."),this.filter(t)}}};Object.defineProperty(_.eth,"defaultBlock",{get:function(){return p.ETH_DEFAULTBLOCK},set:function(t){return p.ETH_DEFAULTBLOCK=t,p.ETH_DEFAULTBLOCK}}),h(_,m),g(_,d),h(_.net,r.methods),g(_.net,r.properties),h(_.eth,o.methods),g(_.eth,o.properties),h(_.db,i.methods()),h(_.shh,a.methods()),h(v,u.eth()),h(w,u.shh()),e.exports=_},{"../version.json":21,"./solidity/formatters":2,"./utils/config":4,"./utils/utils":5,"./web3/db":8,"./web3/eth":9,"./web3/filter":11,"./web3/net":15,"./web3/requestmanager":17,"./web3/shh":18,"./web3/watches":20}],7:[function(t,e){function n(t,e){t.forEach(function(t){if(-1===t.name.indexOf("(")){var e=t.name,n=t.inputs.map(function(t){return t.type}).join();t.name=e+"("+n+")"}});var n={};return c(n),l(n,t,e),f(n,t,e),p(n,t,e),n}var r=t("../web3"),o=t("../solidity/abi"),i=t("../utils/utils"),a=t("./event"),u=t("./signature"),s=function(t){r._currentContractAbi=t.abi,r._currentContractAddress=t.address,r._currentContractMethodName=t.method,r._currentContractMethodParams=t.params},c=function(t){t.call=function(e){return t._isTransaction=!1,t._options=e,t},t.sendTransaction=function(e){return t._isTransaction=!0,t._options=e,t},t.transact=function(e){return console.warn("myContract.transact() is deprecated please use myContract.sendTransaction() instead."),t.sendTransaction(e)},t._options={},["gas","gasPrice","value","from"].forEach(function(e){t[e]=function(n){return t._options[e]=n,t}})},l=function(t,e,n){var a=o.inputParser(e),c=o.outputParser(e);i.filterFunctions(e).forEach(function(o){var l=i.extractDisplayName(o.name),f=i.extractTypeName(o.name),p=function(){var i=Array.prototype.slice.call(arguments),p=u.functionSignatureFromAscii(o.name),m=a[l][f].apply(null,i),d=t._options||{};d.to=n,d.data=p+m;var h=t._isTransaction===!0||t._isTransaction!==!1&&!o.constant,g=d.collapse!==!1;if(t._options={},t._isTransaction=null,h)return s({abi:e,address:n,method:o.name,params:i}),void r.eth.sendTransaction(d);var y=r.eth.call(d),b=c[l][f](y);return g&&(1===b.length?b=b[0]:0===b.length&&(b=null)),b};void 0===t[l]&&(t[l]=p),t[l][f]=p})},f=function(t,e,n){t.address=n,t._onWatchEventResult=function(t){var n=event.getMatchingEvent(i.filterEvents(e)),r=a.outputParser(n);return r(t)},Object.defineProperty(t,"topics",{get:function(){return i.filterEvents(e).map(function(t){return u.eventSignatureFromAscii(t.name)})}})},p=function(t,e,n){i.filterEvents(e).forEach(function(e){var o=function(){var t=Array.prototype.slice.call(arguments),o=u.eventSignatureFromAscii(e.name),i=a.inputParser(n,o,e),s=i.apply(null,t),c=function(t){var n=a.outputParser(e);return n(t)};return r.eth.filter(s,void 0,void 0,c)};o._isEvent=!0;var s=i.extractDisplayName(e.name),c=i.extractTypeName(e.name);void 0===t[s]&&(t[s]=o),t[s][c]=o})},m=function(t){return t instanceof Array&&1===arguments.length?n.bind(null,t):(console.warn("Initiating a contract like this is deprecated please use var MyContract = eth.contract(abi); new MyContract(address); instead."),new n(arguments[1],arguments[0]))};e.exports=m},{"../solidity/abi":1,"../utils/utils":5,"../web3":6,"./event":10,"./signature":19}],8:[function(t,e){var n=function(){return[{name:"putString",call:"db_putString"},{name:"getString",call:"db_getString"},{name:"putHex",call:"db_putHex"},{name:"getHex",call:"db_getHex"}]};e.exports={methods:n}},{}],9:[function(t,e){var n=t("./formatters"),r=t("../utils/utils"),o=function(t){return r.isString(t[0])&&0===t[0].indexOf("0x")?"eth_getBlockByHash":"eth_getBlockByNumber"},i=function(t){return r.isString(t[0])&&0===t[0].indexOf("0x")?"eth_getTransactionByBlockHashAndIndex":"eth_getTransactionByBlockNumberAndIndex"},a=function(t){return r.isString(t[0])&&0===t[0].indexOf("0x")?"eth_getUncleByBlockHashAndIndex":"eth_getUncleByBlockNumberAndIndex"},u=function(t){return r.isString(t[0])&&0===t[0].indexOf("0x")?"eth_getBlockTransactionCountByHash":"eth_getBlockTransactionCountByNumber"},s=function(t){return r.isString(t[0])&&0===t[0].indexOf("0x")?"eth_getUncleCountByBlockHash":"eth_getUncleCountByBlockNumber"},c=[{name:"getBalance",call:"eth_getBalance",addDefaultblock:2,outputFormatter:n.convertToBigNumber},{name:"getStorageAt",call:"eth_getStorageAt",addDefaultblock:3,inputFormatter:r.toHex},{name:"getCode",call:"eth_getCode",addDefaultblock:2},{name:"getBlock",call:o,outputFormatter:n.outputBlockFormatter,inputFormatter:[r.toHex,function(t){return t?!0:!1}]},{name:"getUncle",call:a,outputFormatter:n.outputBlockFormatter,inputFormatter:[r.toHex,r.toHex,function(t){return t?!0:!1}]},{name:"getCompilers",call:"eth_getCompilers"},{name:"getBlockTransactionCount",call:u,outputFormatter:r.toDecimal,inputFormatter:r.toHex},{name:"getBlockUncleCount",call:s,outputFormatter:r.toDecimal,inputFormatter:r.toHex},{name:"getTransaction",call:"eth_getTransactionByHash",outputFormatter:n.outputTransactionFormatter},{name:"getTransactionFromBlock",call:i,outputFormatter:n.outputTransactionFormatter,inputFormatter:r.toHex},{name:"getTransactionCount",call:"eth_getTransactionCount",addDefaultblock:2,outputFormatter:r.toDecimal},{name:"sendTransaction",call:"eth_sendTransaction",inputFormatter:n.inputTransactionFormatter},{name:"call",call:"eth_call",addDefaultblock:2,inputFormatter:n.inputCallFormatter},{name:"compile.solidity",call:"eth_compileSolidity"},{name:"compile.lll",call:"eth_compileLLL",inputFormatter:r.toHex},{name:"compile.serpent",call:"eth_compileSerpent",inputFormatter:r.toHex},{name:"flush",call:"eth_flush"},{name:"balanceAt",call:"eth_balanceAt",newMethod:"eth.getBalance"},{name:"stateAt",call:"eth_stateAt",newMethod:"eth.getStorageAt"},{name:"storageAt",call:"eth_storageAt",newMethod:"eth.getStorage"},{name:"countAt",call:"eth_countAt",newMethod:"eth.getTransactionCount"},{name:"codeAt",call:"eth_codeAt",newMethod:"eth.getCode"},{name:"transact",call:"eth_transact",newMethod:"eth.sendTransaction"},{name:"block",call:o,newMethod:"eth.getBlock"},{name:"transaction",call:i,newMethod:"eth.getTransaction"},{name:"uncle",call:a,newMethod:"eth.getUncle"},{name:"compilers",call:"eth_compilers",newMethod:"eth.getCompilers"},{name:"solidity",call:"eth_solidity",newMethod:"eth.compile.solidity"},{name:"lll",call:"eth_lll",newMethod:"eth.compile.lll"},{name:"serpent",call:"eth_serpent",newMethod:"eth.compile.serpent"},{name:"transactionCount",call:u,newMethod:"eth.getBlockTransactionCount"},{name:"uncleCount",call:s,newMethod:"eth.getBlockUncleCount"},{name:"logs",call:"eth_logs"}],l=[{name:"coinbase",getter:"eth_coinbase"},{name:"mining",getter:"eth_mining"},{name:"gasPrice",getter:"eth_gasPrice",outputFormatter:n.convertToBigNumber},{name:"accounts",getter:"eth_accounts"},{name:"blockNumber",getter:"eth_blockNumber",outputFormatter:r.toDecimal},{name:"listening",getter:"net_listening",setter:"eth_setListening",newProperty:"net.listening"},{name:"peerCount",getter:"net_peerCount",newProperty:"net.peerCount"},{name:"number",getter:"eth_number",newProperty:"eth.blockNumber"}];e.exports={methods:c,properties:l}},{"../utils/utils":5,"./formatters":12}],10:[function(t,e){var n=t("../solidity/abi"),r=t("../utils/utils"),o=t("./signature"),i=function(t,e){return t.filter(function(t){return t.indexed===e})},a=function(t,e){var n=r.findIndex(t,function(t){return t.name===e});return-1===n?void console.error("indexed param with name "+e+" not found"):t[n]},u=function(t,e){return Object.keys(e).map(function(r){var o=[a(i(t.inputs,!0),r)],u=e[r];return u instanceof Array?u.map(function(t){return n.formatInput(o,[t])}):n.formatInput(o,[u])})},s=function(t,e,n){return function(r,o){var i=o||{};return i.address=t,i.topics=[],i.topics.push(e),r&&(i.topics=i.topics.concat(u(n,r))),i}},c=function(t,e,n){var r=e.slice(),o=n.slice();return t.reduce(function(t,e){var n;return n=e.indexed?r.splice(0,1)[0]:o.splice(0,1)[0],t[e.name]=n,t},{})},l=function(t){return function(e){var o={event:r.extractDisplayName(t.name),number:e.number,hash:e.hash,args:{}};if(e.topics=e.topic,!e.topics)return o;var a=i(t.inputs,!0),u="0x"+e.topics.slice(1,e.topics.length).map(function(t){return t.slice(2)}).join(""),s=n.formatOutput(a,u),l=i(t.inputs,!1),f=n.formatOutput(l,e.data);return o.args=c(t.inputs,s,f),o}},f=function(t,e){for(var n=0;n var web3 = require('web3'); - web3.setProvider(new web3.providers.HttpSyncProvider('http://localhost:8080')); + web3.setProvider(new web3.providers.HttpProvider()); function watchBalance() { var coinbase = web3.eth.coinbase; - var originalBalance = 0; - var balance = web3.eth.balanceAt(coinbase); - var originalBalance = web3.toDecimal(balance); - document.getElementById('original').innerText = 'original balance: ' + originalBalance + ' watching...'; + var originalBalance = web3.eth.getBalance(coinbase).toNumber(); + document.getElementById('coinbase').innerText = 'coinbase: ' + coinbase; + document.getElementById('original').innerText = ' original balance: ' + originalBalance + ' watching...'; - web3.eth.watch('pending').changed(function() { - balance = web3.eth.balanceAt(coinbase) - var currentBalance = web3.toDecimal(balance); + web3.eth.filter('pending').watch(function() { + var currentBalance = web3.eth.getBalance(coinbase).toNumber(); document.getElementById("current").innerText = 'current: ' + currentBalance; document.getElementById("diff").innerText = 'diff: ' + (currentBalance - originalBalance); }); @@ -31,6 +29,7 @@

coinbase balance

+
diff --git a/libjsqrc/ethereumjs/example/contract.html b/libjsqrc/ethereumjs/example/contract.html index a534f68d8..ce96d8d5c 100644 --- a/libjsqrc/ethereumjs/example/contract.html +++ b/libjsqrc/ethereumjs/example/contract.html @@ -7,7 +7,7 @@ + + + + + + diff --git a/libnatspec/natspecjs/natspec.js b/libnatspec/natspecjs/natspec.js new file mode 100644 index 000000000..277cad2aa --- /dev/null +++ b/libnatspec/natspecjs/natspec.js @@ -0,0 +1,186 @@ +/* + This file is part of natspec.js. + + natspec.js is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + natspec.js 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with natspec.js. If not, see . +*/ +/** @file natspec.js + * @authors: + * Marek Kotewicz + * @date 2015 + */ + +var abi = require('./node_modules/ethereum.js/lib/abi.js'); + +/** + * This object should be used to evaluate natspec expression + * It has one method evaluateExpression which shoul be used + */ +var natspec = (function () { + /** + * Helper method + * Should be called to copy values from object to global context + * + * @method copyToContext + * @param {Object} object from which we want to copy properties + * @param {Object} object to which we copy + */ + var copyToContext = function (obj, context) { + Object.keys(obj).forEach(function (key) { + context[key] = obj[key]; + }); + } + + /** + * Should be used to generate codes, which will be evaluated + * + * @method generateCode + * @param {Object} object from which code will be generated + * @return {String} javascript code which is used to initalized variables + */ + var generateCode = function (obj) { + return Object.keys(obj).reduce(function (acc, key) { + return acc + "var " + key + " = context['" + key + "'];\n"; + }, ""); + }; + + /** + * Helper method + * Should be called to get method with given name from the abi + * + * @method getMethodWithName + * @param {Array} contract's abi + * @param {String} name of the method that we are looking for + * @return {Object} abi for method with name + */ + var getMethodWithName = function(abi, name) { + return abi.filter(function (method) { + return method.name === name; + })[0]; + }; + + /** + * Should be used to get all contract method input variables + * + * @method getMethodInputParams + * @param {Object} abi for certain method + * @param {Object} transaction object + * @return {Object} object with all contract's method input variables + */ + var getMethodInputParams = function (method, transaction) { + // do it with output formatter (cause we have to decode) + var params = abi.formatOutput(method.inputs, '0x' + transaction.params[0].data.slice(10)); + + return method.inputs.reduce(function (acc, current, index) { + acc[current.name] = params[index]; + return acc; + }, {}); + }; + + /** + * Should be called when we want to evaluate natspec expression + * Replaces all natspec 'subexpressions' with evaluated value + * + * @method mapExpressionToEvaluate + * @param {String} expression to evaluate + * @param {Function} callback which is called to evaluate te expression + * @return {String} evaluated expression + */ + var mapExpressionsToEvaluate = function (expression, cb) { + var evaluatedExpression = ""; + + // match everything in `` quotes + var pattern = /\`(?:\\.|[^`\\])*\`/gim + var match; + var lastIndex = 0; + try { + while ((match = pattern.exec(expression)) !== null) { + var startIndex = pattern.lastIndex - match[0].length; + var toEval = match[0].slice(1, match[0].length - 1); + evaluatedExpression += expression.slice(lastIndex, startIndex); + var evaluatedPart = cb(toEval); + evaluatedExpression += evaluatedPart; + lastIndex = pattern.lastIndex; + } + + evaluatedExpression += expression.slice(lastIndex); + } + catch (err) { + throw new Error("Natspec evaluation failed, wrong input params"); + } + + return evaluatedExpression; + }; + + /** + * Should be called to evaluate single expression + * Is internally using javascript's 'eval' method + * + * @method evaluateExpression + * @param {String} expression which should be evaluated + * @param {Object} [call] object containing contract abi, transaction, called method + * @return {String} evaluated expression + * @throws exception if method is not found or we are trying to evaluate input params that does not exists + */ + var evaluateExpression = function (expression, call) { + //var self = this; + var context = {}; + + if (!!call) { + try { + var method = getMethodWithName(call.abi, call.method); + var params = getMethodInputParams(method, call.transaction); + copyToContext(params, context); + } + catch (err) { + throw new Error("Natspec evaluation failed, method does not exist"); + } + } + + var code = generateCode(context); + + var evaluatedExpression = mapExpressionsToEvaluate(expression, function (toEval) { + var fn = new Function("context", code + "return " + toEval + ";"); + return fn(context).toString(); + }); + + return evaluatedExpression; + }; + + /** + * Safe version of evaluateExpression + * Instead of throwing an exception it returns it as a string + * + * @method evaluateExpressionSafe + * @param {String} expression which should be evaluated + * @param {Object} [call] object containing contract abi, transaction, called method + * @return {String} evaluated expression + */ + var evaluateExpressionSafe = function (expression, call) { + try { + return evaluateExpression(expression, call); + } + catch (err) { + return err.message; + } + }; + + return { + evaluateExpression: evaluateExpression, + evaluateExpressionSafe: evaluateExpressionSafe + }; + +})(); + +module.exports = natspec; + diff --git a/libnatspec/natspecjs/package.json b/libnatspec/natspecjs/package.json new file mode 100644 index 000000000..ed2f252db --- /dev/null +++ b/libnatspec/natspecjs/package.json @@ -0,0 +1,24 @@ +{ + "name": "natspec.js", + "version": "0.0.1", + "description": "Javascript Library used to evaluate natspec expressions", + "main": "natspec.js", + "scripts": { + "build": "cd dist && browserify -r ../natspec.js:natspec -i crypto -o natspec.js && uglifyjs natspec.js --source-map natspec.js.map -c -m -o natspec.min.js", + "test": "mocha", + "test-coveralls": "istanbul cover _mocha -- -R spec && cat coverage/lcov.info | coveralls --verbose" + }, + "author": "", + "dependencies": { + "ethereum.js": "ethereum/ethereum.js#master" + }, + "devDependencies": { + "browserify": "^9.0.3", + "chai": "^2.1.0", + "coveralls": "^2.11.2", + "istanbul": "^0.3.6", + "mocha": "^2.1.0", + "uglify-js": "^2.4.16" + }, + "license": "LGPL-3.0" +} diff --git a/libnatspec/natspecjs/test/test.js b/libnatspec/natspecjs/test/test.js new file mode 100644 index 000000000..59f46cdab --- /dev/null +++ b/libnatspec/natspecjs/test/test.js @@ -0,0 +1,149 @@ +var chai = require('chai'); +var natspec = require('../natspec.js'); +var assert = chai.assert; + +describe('natspec', function () { + it('should evaluate simple expression', function () { + // given + var expression = "`x = 1` + `y = 2` will be equal `x + y`"; + + // when + var result = natspec.evaluateExpression(expression); + var result2 = natspec.evaluateExpressionSafe(expression); + + // then + assert.equal(result, "1 + 2 will be equal 3"); + assert.equal(result2, "1 + 2 will be equal 3"); + }); + + it('should evalute expression using input params', function () { + //given + var expression = "Will multiply `a` by 7 and return `a * 7`."; + var method = 'multiply'; + var abi = [{ + "name": "multiply", + "constant": false, + "type": "function", + "inputs": [{ + "name": "a", + "type": "uint256" + }], + "outputs": [{ + "name": "d", + "type": "uint256" + }] + }]; + + var transaction = { + "jsonrpc": "2.0", + "method": "eth_call", + "params": [{ + "to": "0x8521742d3f456bd237e312d6e30724960f72517a", + "data": "0xc6888fa1000000000000000000000000000000000000000000000000000000000000007a" + }], + "id": 6 + }; + + var call = { + method: method, + abi: abi, + transaction: transaction + }; + + // when + var result = natspec.evaluateExpression(expression, call); + var result2 = natspec.evaluateExpressionSafe(expression, call); + + // then + assert.equal(result, "Will multiply 122 by 7 and return 854."); + assert.equal(result2, "Will multiply 122 by 7 and return 854."); + }); + + it('should evalute expression using input params', function () { + //given + var expression = "Will multiply `a` by 7 and return `a * 7`."; + var method = 'multiply'; + var abi = [{ + "name": "multiply", + "constant": false, + "type": "function", + "inputs": [{ + "name": "b", + "type": "uint256" + }], + "outputs": [{ + "name": "d", + "type": "uint256" + }] + }]; + + var transaction = { + "jsonrpc": "2.0", + "method": "eth_call", + "params": [{ + "to": "0x8521742d3f456bd237e312d6e30724960f72517a", + "data": "0xc6888fa1000000000000000000000000000000000000000000000000000000000000007a" + }], + "id": 6 + }; + + var call = { + method: method, + abi: abi, + transaction: transaction + }; + + // when + var exceptionThrow = function () { natspec.evaluateExpression(expression, call);} + var result = natspec.evaluateExpressionSafe(expression, call); + + // then + assert.equal(result, "Natspec evaluation failed, wrong input params"); + assert.throws(exceptionThrow, "Natspec evaluation failed, wrong input params"); + }); + + it('should evalute expression using input params', function () { + //given + var expression = "Will multiply `a` by 7 and return `a * 7`."; + var method = 'multiply2'; + var abi = [{ + "name": "multiply", + "constant": false, + "type": "function", + "inputs": [{ + "name": "a", + "type": "uint256" + }], + "outputs": [{ + "name": "d", + "type": "uint256" + }] + }]; + + var transaction = { + "jsonrpc": "2.0", + "method": "eth_call", + "params": [{ + "to": "0x8521742d3f456bd237e312d6e30724960f72517a", + "data": "0xc6888fa1000000000000000000000000000000000000000000000000000000000000007a" + }], + "id": 6 + }; + + var call = { + method: method, + abi: abi, + transaction: transaction + }; + + // when + var exceptionThrow = function () { natspec.evaluateExpression(expression, call);} + var result = natspec.evaluateExpressionSafe(expression, call); + + // then + assert.equal(result, "Natspec evaluation failed, method does not exist"); + assert.throws(exceptionThrow, "Natspec evaluation failed, method does not exist"); + }); +}); + + diff --git a/libp2p/Capability.cpp b/libp2p/Capability.cpp index 5ef70bbc3..f59fd8cdd 100644 --- a/libp2p/Capability.cpp +++ b/libp2p/Capability.cpp @@ -45,7 +45,7 @@ void Capability::disable(std::string const& _problem) RLPStream& Capability::prep(RLPStream& _s, unsigned _id, unsigned _args) { - return Session::prep(_s).appendList(_args + 1).append(_id + m_idOffset); + return _s.appendRaw(bytes(1, _id + m_idOffset)).appendList(_args); } void Capability::sealAndSend(RLPStream& _s) @@ -53,17 +53,7 @@ void Capability::sealAndSend(RLPStream& _s) m_session->sealAndSend(_s); } -void Capability::send(bytesConstRef _msg) -{ - m_session->send(_msg); -} - -void Capability::send(bytes&& _msg) -{ - m_session->send(move(_msg)); -} - -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 04b116aa8..d09391655 100644 --- a/libp2p/Capability.h +++ b/libp2p/Capability.h @@ -52,10 +52,7 @@ protected: RLPStream& prep(RLPStream& _s, unsigned _id, unsigned _args = 0); void sealAndSend(RLPStream& _s); - void send(bytes&& _msg); - void send(bytesConstRef _msg); - - void addRating(unsigned _r); + void addRating(int _r); private: Session* m_session; diff --git a/libp2p/Common.cpp b/libp2p/Common.cpp index e0f3b5629..95c96e346 100644 --- a/libp2p/Common.cpp +++ b/libp2p/Common.cpp @@ -24,6 +24,19 @@ using namespace std; using namespace dev; using namespace dev::p2p; +const unsigned dev::p2p::c_protocolVersion = 3; +const unsigned dev::p2p::c_defaultIPPort = 30303; + +bool p2p::isPublicAddress(std::string const& _addressToCheck) +{ + return _addressToCheck.empty() ? false : isPublicAddress(bi::address::from_string(_addressToCheck)); +} + +bool p2p::isPublicAddress(bi::address const& _addressToCheck) +{ + return !(isPrivateAddress(_addressToCheck) || isLocalHostAddress(_addressToCheck)); +} + // Helper function to determine if an address falls within one of the reserved ranges // For V4: // Class A "10.*", Class B "172.[16->31].*", Class C "192.168.*" @@ -53,6 +66,11 @@ bool p2p::isPrivateAddress(bi::address const& _addressToCheck) return false; } +bool p2p::isPrivateAddress(std::string const& _addressToCheck) +{ + return _addressToCheck.empty() ? false : isPrivateAddress(bi::address::from_string(_addressToCheck)); +} + // Helper function to determine if an address is localhost bool p2p::isLocalHostAddress(bi::address const& _addressToCheck) { @@ -67,6 +85,11 @@ bool p2p::isLocalHostAddress(bi::address const& _addressToCheck) return find(c_rejectAddresses.begin(), c_rejectAddresses.end(), _addressToCheck) != c_rejectAddresses.end(); } +bool p2p::isLocalHostAddress(std::string const& _addressToCheck) +{ + return _addressToCheck.empty() ? false : isLocalHostAddress(bi::address::from_string(_addressToCheck)); +} + std::string p2p::reasonOf(DisconnectReason _r) { switch (_r) @@ -87,3 +110,9 @@ std::string p2p::reasonOf(DisconnectReason _r) default: return "Unknown reason."; } } + +void Node::cullEndpoint() +{ + if (!isPublicAddress(endpoint.tcp.address()) && isPublicAddress(endpoint.udp.address())) + endpoint.tcp.address(endpoint.udp.address()); +} diff --git a/libp2p/Common.h b/libp2p/Common.h index ddf5f78b8..c9aee9a0e 100644 --- a/libp2p/Common.h +++ b/libp2p/Common.h @@ -48,10 +48,18 @@ class RLPStream; namespace p2p { +/// Peer network protocol version. +extern const unsigned c_protocolVersion; +extern const unsigned c_defaultIPPort; + using NodeId = h512; bool isPrivateAddress(bi::address const& _addressToCheck); +bool isPrivateAddress(std::string const& _addressToCheck); bool isLocalHostAddress(bi::address const& _addressToCheck); +bool isLocalHostAddress(std::string const& _addressToCheck); +bool isPublicAddress(bi::address const& _addressToCheck); +bool isPublicAddress(std::string const& _addressToCheck); class UPnP; class Capability; @@ -59,6 +67,8 @@ class Host; class Session; struct NetworkStartRequired: virtual dev::Exception {}; +struct InvalidPublicIPAddress: virtual dev::Exception {}; +struct InvalidHostIPAddress: virtual dev::Exception {}; struct NetWarn: public LogChannel { static const char* name() { return "!N!"; } static const int verbosity = 0; }; struct NetNote: public LogChannel { static const char* name() { return "*N*"; } static const int verbosity = 1; }; @@ -166,6 +176,9 @@ struct Node virtual NodeId const& address() const { return id; } virtual Public const& publicKey() const { return id; } + /// Adopt UDP address for TCP if TCP isn't public and UDP is. (to be removed when protocol is updated for nat) + void cullEndpoint(); + NodeId id; /// Endpoints by which we expect to reach node. diff --git a/libp2p/Host.cpp b/libp2p/Host.cpp index ed0ee653e..e49baa1be 100644 --- a/libp2p/Host.cpp +++ b/libp2p/Host.cpp @@ -24,10 +24,10 @@ #include #include #include - +#include #include - #include +#include #include #include #include @@ -36,11 +36,18 @@ #include "Common.h" #include "Capability.h" #include "UPnP.h" +#include "RLPxHandshake.h" #include "Host.h" using namespace std; using namespace dev; using namespace dev::p2p; +/// Interval at which Host::run will call keepAlivePeers to ping peers. +std::chrono::seconds const c_keepAliveInterval = std::chrono::seconds(30); + +/// Disconnect timeout after failure to respond to keepAlivePeers ping. +std::chrono::milliseconds const c_keepAliveTimeOut = std::chrono::milliseconds(1000); + HostNodeTableHandler::HostNodeTableHandler(Host& _host): m_host(_host) {} void HostNodeTableHandler::processEvent(NodeId const& _n, NodeTableEventType const& _e) @@ -59,10 +66,6 @@ Host::Host(std::string const& _clientVersion, NetworkPreferences const& _n, byte m_alias(networkAlias(_restoreNetwork)), m_lastPing(chrono::steady_clock::time_point::min()) { - for (auto address: m_ifAddresses) - if (address.is_v4()) - clog(NetNote) << "IP Address: " << address << " = " << (isPrivateAddress(address) ? "[LOCAL]" : "[PEER]"); - clog(NetNote) << "Id:" << id(); } @@ -121,6 +124,23 @@ void Host::doneWorking() for (auto const& h: m_capabilities) h.second->onStopping(); + // disconnect pending handshake, before peers, as a handshake may create a peer + for (unsigned n = 0;; n = 0) + { + { + Guard l(x_connecting); + for (auto i: m_connecting) + if (auto h = i.lock()) + { + h->cancel(); + n++; + } + } + if (!n) + break; + m_ioService.poll(); + } + // disconnect peers for (unsigned n = 0;; n = 0) { @@ -152,37 +172,66 @@ void Host::doneWorking() m_sessions.clear(); } -unsigned Host::protocolVersion() const -{ - return 3; -} - -void Host::registerPeer(std::shared_ptr _s, CapDescs const& _caps) +void Host::startPeerSession(Public const& _id, RLP const& _rlp, RLPXFrameIO* _io, bi::tcp::endpoint _endpoint) { + shared_ptr p; + if (!m_peers.count(_id)) + { + p.reset(new Peer()); + p->id = _id; + } + else + p = m_peers[_id]; + if (p->isOffline()) + p->m_lastConnected = std::chrono::system_clock::now(); + p->endpoint.tcp.address(_endpoint.address()); + + auto protocolVersion = _rlp[0].toInt(); + auto clientVersion = _rlp[1].toString(); + auto caps = _rlp[2].toVector(); + auto listenPort = _rlp[3].toInt(); + + // clang error (previously: ... << hex << caps ...) + // "'operator<<' should be declared prior to the call site or in an associated namespace of one of its arguments" + stringstream capslog; + for (auto cap: caps) + capslog << "(" << cap.first << "," << dec << cap.second << ")"; + clog(NetMessageSummary) << "Hello: " << clientVersion << "V[" << protocolVersion << "]" << _id.abridged() << showbase << capslog.str() << dec << listenPort; + + // create session so disconnects are managed + auto ps = make_shared(this, _io, p, PeerSessionInfo({_id, clientVersion, _endpoint.address().to_string(), listenPort, chrono::steady_clock::duration(), _rlp[2].toSet(), 0, map()})); + if (protocolVersion != dev::p2p::c_protocolVersion) + { + ps->disconnect(IncompatibleProtocol); + return; + } + { - clog(NetNote) << "p2p.host.peer.register" << _s->m_peer->id.abridged(); - StructuredLogger::p2pConnected( - _s->m_peer->id.abridged(), - _s->m_peer->peerEndpoint(), - _s->m_peer->m_lastConnected, - _s->m_info.clientVersion, - peerCount() - ); RecursiveGuard l(x_sessions); - // TODO: temporary loose-coupling; if m_peers already has peer, - // it is same as _s->m_peer. (fixing next PR) - if (!m_peers.count(_s->m_peer->id)) - m_peers[_s->m_peer->id] = _s->m_peer; - m_sessions[_s->m_peer->id] = _s; + if (m_sessions.count(_id) && !!m_sessions[_id].lock()) + if (auto s = m_sessions[_id].lock()) + if(s->isConnected()) + { + // Already connected. + clog(NetWarn) << "Session already exists for peer with id" << _id.abridged(); + 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; } - - unsigned o = (unsigned)UserPacket; - for (auto const& i: _caps) - if (haveCapability(i)) - { - _s->m_capabilities[i] = shared_ptr(m_capabilities[i]->newPeerCapability(_s.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()); } void Host::onNodeTableEvent(NodeId const& _n, NodeTableEventType const& _e) @@ -209,7 +258,7 @@ void Host::onNodeTableEvent(NodeId const& _n, NodeTableEventType const& _e) p->required = n.required; m_peers[_n] = p; - clog(NetNote) << "p2p.host.peers.events.peersAdded " << _n << p->endpoint.tcp.address() << p->endpoint.udp.address(); + clog(NetNote) << "p2p.host.peers.events.peersAdded " << _n << "udp:" << p->endpoint.udp.address() << "tcp:" << p->endpoint.tcp.address(); } p->endpoint.tcp = n.endpoint.tcp; } @@ -226,89 +275,59 @@ void Host::onNodeTableEvent(NodeId const& _n, NodeTableEventType const& _e) connect(p); } } - else if (_e == NodeEntryRemoved) + else if (_e == NodeEntryDropped) { - clog(NetNote) << "p2p.host.nodeTable.events.nodeEntryRemoved " << _n; + clog(NetNote) << "p2p.host.nodeTable.events.NodeEntryDropped " << _n; RecursiveGuard l(x_sessions); m_peers.erase(_n); } } -void Host::seal(bytes& _b) -{ - _b[0] = 0x22; - _b[1] = 0x40; - _b[2] = 0x08; - _b[3] = 0x91; - uint32_t len = (uint32_t)_b.size() - 8; - _b[4] = (len >> 24) & 0xff; - _b[5] = (len >> 16) & 0xff; - _b[6] = (len >> 8) & 0xff; - _b[7] = len & 0xff; -} - -void Host::determinePublic(string const& _publicAddress, bool _upnp) +void Host::determinePublic() { - m_peerAddresses.clear(); - - // no point continuing if there are no interface addresses or valid listen port - if (!m_ifAddresses.size() || m_listenPort < 1) - return; - - // populate interfaces we'll listen on (eth listens on all interfaces); ignores local - for (auto addr: m_ifAddresses) - if ((m_netPrefs.localNetworking || !isPrivateAddress(addr)) && !isLocalHostAddress(addr)) - m_peerAddresses.insert(addr); - - // if user supplied address is a public address then we use it - // if user supplied address is private, and localnetworking is enabled, we use it - bi::address reqPublicAddr(bi::address(_publicAddress.empty() ? bi::address() : bi::address::from_string(_publicAddress))); - bi::tcp::endpoint reqPublic(reqPublicAddr, m_listenPort); - bool isprivate = isPrivateAddress(reqPublicAddr); - bool ispublic = !isprivate && !isLocalHostAddress(reqPublicAddr); - if (!reqPublicAddr.is_unspecified() && (ispublic || (isprivate && m_netPrefs.localNetworking))) + // set m_tcpPublic := listenIP (if public) > public > upnp > unspecified address. + + auto ifAddresses = Network::getInterfaceAddresses(); + auto laddr = m_netPrefs.listenIPAddress.empty() ? bi::address() : bi::address::from_string(m_netPrefs.listenIPAddress); + auto lset = !laddr.is_unspecified(); + auto paddr = m_netPrefs.publicIPAddress.empty() ? bi::address() : bi::address::from_string(m_netPrefs.publicIPAddress); + auto pset = !paddr.is_unspecified(); + + bool listenIsPublic = lset && isPublicAddress(laddr); + bool publicIsHost = !lset && pset && ifAddresses.count(paddr); + + bi::tcp::endpoint ep(bi::address(), m_netPrefs.listenPort); + if (m_netPrefs.traverseNAT && listenIsPublic) { - if (!m_peerAddresses.count(reqPublicAddr)) - m_peerAddresses.insert(reqPublicAddr); - m_tcpPublic = reqPublic; - return; + clog(NetNote) << "Listen address set to Public address:" << laddr << ". UPnP disabled."; + ep.address(laddr); } - - // if address wasn't provided, then use first public ipv4 address found - for (auto addr: m_peerAddresses) - if (addr.is_v4() && !isPrivateAddress(addr)) - { - m_tcpPublic = bi::tcp::endpoint(*m_peerAddresses.begin(), m_listenPort); - return; - } - - // or find address via upnp - if (_upnp) + else if (m_netPrefs.traverseNAT && publicIsHost) { - bi::address upnpifaddr; - bi::tcp::endpoint upnpep = Network::traverseNAT(m_ifAddresses, m_listenPort, upnpifaddr); - if (!upnpep.address().is_unspecified() && !upnpifaddr.is_unspecified()) + clog(NetNote) << "Public address set to Host configured address:" << paddr << ". UPnP disabled."; + ep.address(paddr); + } + else if (m_netPrefs.traverseNAT) + { + bi::address natIFAddr; + ep = Network::traverseNAT(lset && ifAddresses.count(laddr) ? std::set({laddr}) : ifAddresses, m_netPrefs.listenPort, natIFAddr); + + if (lset && natIFAddr != laddr) + // if listen address is set, Host will use it, even if upnp returns different + clog(NetWarn) << "Listen address" << laddr << "differs from local address" << natIFAddr << "returned by UPnP!"; + + if (pset && ep.address() != paddr) { - if (!m_peerAddresses.count(upnpep.address())) - m_peerAddresses.insert(upnpep.address()); - m_tcpPublic = upnpep; - return; + // if public address is set, Host will advertise it, even if upnp returns different + clog(NetWarn) << "Specified public address" << paddr << "differs from external address" << ep.address() << "returned by UPnP!"; + ep.address(paddr); } } + else if (pset) + ep.address(paddr); - // or if no address provided, use private ipv4 address if local networking is enabled - if (reqPublicAddr.is_unspecified()) - if (m_netPrefs.localNetworking) - for (auto addr: m_peerAddresses) - if (addr.is_v4() && isPrivateAddress(addr)) - { - m_tcpPublic = bi::tcp::endpoint(addr, m_listenPort); - return; - } - - // otherwise address is unspecified - m_tcpPublic = bi::tcp::endpoint(bi::address(), m_listenPort); + m_tcpPublic = ep; } void Host::runAcceptor() @@ -320,34 +339,19 @@ void Host::runAcceptor() clog(NetConnect) << "Listening on local port " << m_listenPort << " (public: " << m_tcpPublic << ")"; m_accepting = true; - // socket is created outside of acceptor-callback - // An allocated socket is necessary as asio can use the socket - // until the callback succeeds or fails. - // - // Until callback succeeds or fails, we can't dealloc it. - // - // Callback is guaranteed to be called via asio or when - // m_tcp4Acceptor->stop() is called by Host. - // - // All exceptions are caught so they don't halt asio and so the - // socket is deleted. - // - // It's possible for an accepted connection to return an error in which - // case the socket may be open and must be closed to prevent asio from - // processing socket events after socket is deallocated. - - bi::tcp::socket *s = new bi::tcp::socket(m_ioService); - m_tcp4Acceptor.async_accept(*s, [=](boost::system::error_code ec) + auto socket = make_shared(new bi::tcp::socket(m_ioService)); + m_tcp4Acceptor.async_accept(socket->ref(), [=](boost::system::error_code ec) { - // if no error code, doHandshake takes ownership + // if no error code bool success = false; if (!ec) { try { - // doHandshake takes ownersihp of *s via std::move // incoming connection; we don't yet know nodeid - doHandshake(s, NodeId()); + auto handshake = make_shared(this, socket); + m_connecting.push_back(handshake); + handshake->start(); success = true; } catch (Exception const& _e) @@ -360,41 +364,16 @@ void Host::runAcceptor() } } - // asio doesn't close socket on error - if (!success && s->is_open()) - { - boost::system::error_code ec; - s->shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec); - s->close(); - } - + if (!success) + socket->ref().close(); + m_accepting = false; - delete s; - if (ec.value() < 1) runAcceptor(); }); } } -void Host::doHandshake(bi::tcp::socket* _socket, NodeId _nodeId) -{ - try { - clog(NetConnect) << "Accepting connection for " << _socket->remote_endpoint(); - } catch (...){} - - shared_ptr p; - if (_nodeId) - p = m_peers[_nodeId]; - - if (!p) - p.reset(new Peer()); - p->endpoint.tcp.address(_socket->remote_endpoint().address()); - - auto ps = std::make_shared(this, std::move(*_socket), p); - ps->start(); -} - string Host::pocHost() { vector strs; @@ -402,7 +381,7 @@ string Host::pocHost() return "poc-" + strs[1] + ".ethdev.com"; } -void Host::addNode(NodeId const& _node, std::string const& _addr, unsigned short _tcpPeerPort, unsigned short _udpNodePort) +void Host::addNode(NodeId const& _node, bi::address const& _addr, unsigned short _udpNodePort, unsigned short _tcpPeerPort) { // TODO: p2p clean this up (bring tested acceptor code over from network branch) while (isWorking() && !m_run) @@ -418,37 +397,71 @@ void Host::addNode(NodeId const& _node, std::string const& _addr, unsigned short cwarn << "Private port being recorded - setting to 0"; _tcpPeerPort = 0; } + + if (m_nodeTable) + m_nodeTable->addNode(Node(_node, NodeIPEndpoint(bi::udp::endpoint(_addr, _udpNodePort), bi::tcp::endpoint(_addr, _tcpPeerPort)))); +} - boost::system::error_code ec; - bi::address addr = bi::address::from_string(_addr, ec); - if (ec) +void Host::requirePeer(NodeId const& _n, bi::address const& _udpAddr, unsigned short _udpPort, bi::address const& _tcpAddr, unsigned short _tcpPort) +{ + auto naddr = _udpAddr; + auto paddr = _tcpAddr.is_unspecified() ? naddr : _tcpAddr; + auto udp = bi::udp::endpoint(naddr, _udpPort); + auto tcp = bi::tcp::endpoint(paddr, _tcpPort ? _tcpPort : _udpPort); + Node node(_n, NodeIPEndpoint(udp, tcp)); + if (_n) { - bi::tcp::resolver *r = new bi::tcp::resolver(m_ioService); - r->async_resolve({_addr, toString(_tcpPeerPort)}, [=](boost::system::error_code const& _ec, bi::tcp::resolver::iterator _epIt) + // add or replace peer + shared_ptr p; { - if (!_ec) + RecursiveGuard l(x_sessions); + if (m_peers.count(_n)) + p = m_peers[_n]; + else { - bi::tcp::endpoint tcp = *_epIt; - if (m_nodeTable) m_nodeTable->addNode(Node(_node, NodeIPEndpoint(bi::udp::endpoint(tcp.address(), _udpNodePort), tcp))); + p.reset(new Peer()); + p->id = _n; + p->required = true; + m_peers[_n] = p; } - delete r; + p->endpoint.udp = node.endpoint.udp; + p->endpoint.tcp = node.endpoint.tcp; + } + connect(p); + } + else if (m_nodeTable) + { + shared_ptr t(new boost::asio::deadline_timer(m_ioService)); + m_timers.push_back(t); + + m_nodeTable->addNode(node); + t->expires_from_now(boost::posix_time::milliseconds(600)); + t->async_wait([this, _n](boost::system::error_code const& _ec) + { + if (!_ec && m_nodeTable) + if (auto n = m_nodeTable->node(_n)) + requirePeer(n.id, n.endpoint.udp.address(), n.endpoint.udp.port(), n.endpoint.tcp.address(), n.endpoint.tcp.port()); }); } - else - if (m_nodeTable) m_nodeTable->addNode(Node(_node, NodeIPEndpoint(bi::udp::endpoint(addr, _udpNodePort), bi::tcp::endpoint(addr, _tcpPeerPort)))); +} + +void Host::relinquishPeer(NodeId const& _node) +{ + Guard l(x_requiredPeers); + if (m_requiredPeers.count(_node)) + m_requiredPeers.erase(_node); } void Host::connect(std::shared_ptr const& _p) { - for (unsigned i = 0; i < 200; i++) - if (isWorking() && !m_run) - this_thread::sleep_for(chrono::milliseconds(50)); if (!m_run) return; + _p->m_lastAttempted = std::chrono::system_clock::now(); + if (havePeerSession(_p->id)) { - clog(NetWarn) << "Aborted connect. Node already connected."; + clog(NetConnect) << "Aborted connect. Node already connected."; return; } @@ -469,27 +482,30 @@ void Host::connect(std::shared_ptr const& _p) } clog(NetConnect) << "Attempting connection to node" << _p->id.abridged() << "@" << _p->peerEndpoint() << "from" << id().abridged(); - bi::tcp::socket* s = new bi::tcp::socket(m_ioService); - s->async_connect(_p->peerEndpoint(), [=](boost::system::error_code const& ec) + 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(); } else { - clog(NetConnect) << "Connected to" << _p->id.abridged() << "@" << _p->peerEndpoint(); - _p->m_lastDisconnect = NoDisconnect; - _p->m_lastConnected = std::chrono::system_clock::now(); - _p->m_failedAttempts = 0; - - auto ps = make_shared(this, std::move(*s), _p); - ps->start(); + clog(NetConnect) << "Connecting to" << _p->id.abridged() << "@" << _p->peerEndpoint(); + auto handshake = make_shared(this, socket, _p->id); + { + Guard l(x_connecting); + m_connecting.push_back(handshake); + } + handshake->start(); } - delete s; + Guard l(x_pendingNodeConns); m_pendingPeerConns.erase(nptr); }); @@ -538,26 +554,49 @@ void Host::run(boost::system::error_code const&) m_nodeTable->processEvents(); + // cleanup zombies + { + Guard l(x_connecting); + m_connecting.remove_if([](std::weak_ptr h){ return h.lock(); }); + } + { + Guard l(x_timers); + m_timers.remove_if([](std::shared_ptr t) + { + return t->expires_from_now().total_milliseconds() > 0; + }); + } + for (auto p: m_sessions) if (auto pp = p.second.lock()) pp->serviceNodesRequest(); keepAlivePeers(); - disconnectLatePeers(); - - auto c = peerCount(); - if (m_idealPeerCount && !c) - for (auto p: m_peers) - if (p.second->shouldReconnect()) - { - // TODO p2p: fixme - p.second->m_lastAttempted = std::chrono::system_clock::now(); - connect(p.second); + + // At this time peers will be disconnected based on natural TCP timeout. + // disconnectLatePeers needs to be updated for the assumption that Session + // is always live and to ensure reputation and fallback timers are properly + // updated. // disconnectLatePeers(); + + auto openSlots = m_idealPeerCount - peerCount(); + if (openSlots > 0) + { + list> toConnect; + { + RecursiveGuard l(x_sessions); + for (auto p: m_peers) + if (p.second->shouldReconnect() && !havePeerSession(p.second->id)) + toConnect.push_back(p.second); + } + + for (auto p: toConnect) + if (openSlots--) + connect(p); + else break; - } - - if (c < m_idealPeerCount) + m_nodeTable->discover(); + } auto runcb = [this](boost::system::error_code const& error) { run(error); }; m_timer->expires_from_now(boost::posix_time::milliseconds(c_timerInterval)); @@ -578,27 +617,26 @@ void Host::startedWorking() m_run = true; } - // try to open acceptor (todo: ipv6) - m_listenPort = Network::tcp4Listen(m_tcp4Acceptor, m_netPrefs.listenPort); - - // start capability threads + // start capability threads (ready for incoming connections) for (auto const& h: m_capabilities) h.second->onStarting(); + + // try to open acceptor (todo: ipv6) + m_listenPort = Network::tcp4Listen(m_tcp4Acceptor, m_netPrefs); // determine public IP, but only if we're able to listen for connections // todo: GUI when listen is unavailable in UI if (m_listenPort) { - determinePublic(m_netPrefs.publicIP, m_netPrefs.upnp); + determinePublic(); if (m_listenPort > 0) runAcceptor(); } else - clog(NetNote) << "p2p.start.notice id:" << id().abridged() << "Listen port is invalid or unavailable. Node Table using default port (30303)."; + clog(NetNote) << "p2p.start.notice id:" << id().abridged() << "TCP Listen port is invalid or unavailable."; - // TODO: add m_tcpPublic endpoint; sort out endpoint stuff for nodetable - m_nodeTable.reset(new NodeTable(m_ioService, m_alias, m_listenPort > 0 ? m_listenPort : 30303)); + m_nodeTable.reset(new NodeTable(m_ioService, m_alias, bi::address::from_string(listenAddress()), listenPort())); m_nodeTable->setEventHandler(new HostNodeTableHandler(*this)); restoreNetwork(&m_restoreNetwork); @@ -641,9 +679,6 @@ void Host::disconnectLatePeers() bytes Host::saveNetwork() const { - if (!m_nodeTable) - return bytes(); - std::list peers; { RecursiveGuard l(x_sessions); @@ -655,27 +690,22 @@ bytes Host::saveNetwork() const RLPStream network; int count = 0; + for (auto const& p: peers) { - RecursiveGuard l(x_sessions); - for (auto const& p: peers) + // Only save peers which have connected within 2 days, with properly-advertised port and public IP address + // todo: e2e ipv6 support + bi::tcp::endpoint endpoint(p.peerEndpoint()); + if (!endpoint.address().is_v4()) + continue; + + if (chrono::system_clock::now() - p.m_lastConnected < chrono::seconds(3600 * 48) && endpoint.port() > 0 && endpoint.port() < /*49152*/32768 && p.id != id() && !isPrivateAddress(p.endpoint.udp.address()) && !isPrivateAddress(endpoint.address())) { - // TODO: alpha: Figure out why it ever shares these ports.//p.address.port() >= 30300 && p.address.port() <= 30305 && - // TODO: alpha: if/how to save private addresses - // Only save peers which have connected within 2 days, with properly-advertised port and public IP address - if (chrono::system_clock::now() - p.m_lastConnected < chrono::seconds(3600 * 48) && p.peerEndpoint().port() > 0 && p.peerEndpoint().port() < /*49152*/32768 && p.id != id() && !isPrivateAddress(p.peerEndpoint().address())) - { - network.appendList(10); - if (p.peerEndpoint().address().is_v4()) - network << p.peerEndpoint().address().to_v4().to_bytes(); - else - network << p.peerEndpoint().address().to_v6().to_bytes(); - // TODO: alpha: replace 0 with trust-state of node - network << p.peerEndpoint().port() << p.id << 0 - << chrono::duration_cast(p.m_lastConnected.time_since_epoch()).count() - << chrono::duration_cast(p.m_lastAttempted.time_since_epoch()).count() - << p.m_failedAttempts << (unsigned)p.m_lastDisconnect << p.m_score << p.m_rating; - count++; - } + network.appendList(10); + network << endpoint.port() << p.id << p.required + << chrono::duration_cast(p.m_lastConnected.time_since_epoch()).count() + << chrono::duration_cast(p.m_lastAttempted.time_since_epoch()).count() + << p.m_failedAttempts << (unsigned)p.m_lastDisconnect << p.m_score << p.m_rating; + count++; } } @@ -694,10 +724,13 @@ bytes Host::saveNetwork() const count++; } } + // else: TODO: use previous configuration if available RLPStream ret(3); - ret << 1 << m_alias.secret(); - ret.appendList(count).appendRaw(network.out(), count); + ret << dev::p2p::c_protocolVersion << m_alias.secret(); + ret.appendList(count); + if (!!count) + ret.appendRaw(network.out(), count); return ret.out(); } @@ -707,9 +740,12 @@ void Host::restoreNetwork(bytesConstRef _b) if (!isStarted()) BOOST_THROW_EXCEPTION(NetworkStartRequired()); + if (m_dropPeers) + return; + RecursiveGuard l(x_sessions); RLP r(_b); - if (r.itemCount() > 0 && r[0].isInt() && r[0].toInt() == 1) + if (r.itemCount() > 0 && r[0].isInt() && r[0].toInt() == dev::p2p::c_protocolVersion) { // r[0] = version // r[1] = key @@ -717,18 +753,17 @@ void Host::restoreNetwork(bytesConstRef _b) for (auto i: r[2]) { + // todo: e2e ipv6 support + // bi::tcp::endpoint(bi::address_v6(i[0].toArray()), i[1].toInt()); + if (i[0].itemCount() != 4) + continue; bi::tcp::endpoint tcp; bi::udp::endpoint udp; - if (i[0].itemCount() == 4) - { - tcp = bi::tcp::endpoint(bi::address_v4(i[0].toArray()), i[1].toInt()); - udp = bi::udp::endpoint(bi::address_v4(i[0].toArray()), i[1].toInt()); - } - else - { - tcp = bi::tcp::endpoint(bi::address_v6(i[0].toArray()), i[1].toInt()); - udp = bi::udp::endpoint(bi::address_v6(i[0].toArray()), i[1].toInt()); - } + tcp = bi::tcp::endpoint(bi::address_v4(i[0].toArray()), i[1].toInt()); + udp = bi::udp::endpoint(bi::address_v4(i[0].toArray()), i[1].toInt()); + if (isPrivateAddress(tcp.address()) || isPrivateAddress(udp.address())) + continue; + auto id = (NodeId)i[2]; if (i.itemCount() == 3) m_nodeTable->addNode(id, udp, tcp); @@ -736,6 +771,7 @@ void Host::restoreNetwork(bytesConstRef _b) { shared_ptr p = make_shared(); p->id = id; + p->required = i[3].toInt(); p->m_lastConnected = chrono::system_clock::time_point(chrono::seconds(i[4].toInt())); p->m_lastAttempted = chrono::system_clock::time_point(chrono::seconds(i[5].toInt())); p->m_failedAttempts = i[6].toInt(); @@ -745,7 +781,10 @@ void Host::restoreNetwork(bytesConstRef _b) p->endpoint.tcp = tcp; p->endpoint.udp = udp; m_peers[p->id] = p; - m_nodeTable->addNode(*p.get()); + if (p->required) + requirePeer(p->id, p->endpoint.udp.address(), p->endpoint.udp.port()); + else + m_nodeTable->addNode(*p.get()); } } } @@ -754,7 +793,7 @@ void Host::restoreNetwork(bytesConstRef _b) KeyPair Host::networkAlias(bytesConstRef _b) { RLP r(_b); - if (r.itemCount() == 3 && r[0].isInt() && r[0].toInt() == 1) + if (r.itemCount() == 3 && r[0].isInt() && r[0].toInt() == dev::p2p::c_protocolVersion) return move(KeyPair(move(Secret(r[1].toBytes())))); else return move(KeyPair::create()); diff --git a/libp2p/Host.h b/libp2p/Host.h index ce4dfa50c..753be3150 100644 --- a/libp2p/Host.h +++ b/libp2p/Host.h @@ -35,10 +35,12 @@ #include #include #include +#include #include "NodeTable.h" #include "HostCapability.h" #include "Network.h" #include "Peer.h" +#include "RLPxFrameIO.h" #include "Common.h" namespace ba = boost::asio; namespace bi = ba::ip; @@ -68,19 +70,14 @@ private: * @brief The Host class * Capabilities should be registered prior to startNetwork, since m_capabilities is not thread-safe. * - * @todo exceptions when nodeTable not set (prior to start) - * @todo onNodeTableEvent: move peer-connection logic into ensurePeers - * @todo handshake: gracefully disconnect peer if peer already connected - * @todo abstract socket -> IPConnection * @todo determinePublic: ipv6, udp - * @todo handle conflict if addNode/requireNode called and Node already exists w/conflicting tcp or udp port - * @todo write host identifier to disk w/nodes * @todo per-session keepalive/ping instead of broadcast; set ping-timeout via median-latency - * @todo configuration-management (NetworkPrefs+Keys+Topology) */ class Host: public Worker { friend class HostNodeTableHandler; + friend class RLPXHandshake; + friend class Session; friend class HostCapabilityFace; @@ -95,29 +92,25 @@ public: /// Will block on network process events. virtual ~Host(); - /// Interval at which Host::run will call keepAlivePeers to ping peers. - std::chrono::seconds const c_keepAliveInterval = std::chrono::seconds(30); - - /// Disconnect timeout after failure to respond to keepAlivePeers ping. - std::chrono::milliseconds const c_keepAliveTimeOut = std::chrono::milliseconds(1000); - /// Default host for current version of client. static std::string pocHost(); - /// Basic peer network protocol version. - unsigned protocolVersion() const; - /// Register a peer-capability; all new peer connections will have this capability. - template std::shared_ptr registerCapability(T* _t) { _t->m_host = this; auto ret = std::shared_ptr(_t); m_capabilities[std::make_pair(T::staticName(), T::staticVersion())] = ret; return ret; } + template std::shared_ptr registerCapability(T* _t) { _t->m_host = this; std::shared_ptr ret(_t); m_capabilities[std::make_pair(T::staticName(), T::staticVersion())] = ret; return ret; } bool haveCapability(CapDesc const& _name) const { return m_capabilities.count(_name) != 0; } CapDescs caps() const { CapDescs ret; for (auto const& i: m_capabilities) ret.push_back(i.first); return ret; } template std::shared_ptr cap() const { try { return std::static_pointer_cast(m_capabilities.at(std::make_pair(T::staticName(), T::staticVersion()))); } catch (...) { return nullptr; } } - bool havePeerSession(NodeId _id) { RecursiveGuard l(x_sessions); return m_sessions.count(_id) ? !!m_sessions[_id].lock() : false; } - - void addNode(NodeId const& _node, std::string const& _addr, unsigned short _tcpPort, unsigned short _udpPort); + /// Add node as a peer candidate. Node is added if discovery ping is successful and table has capacity. + void addNode(NodeId const& _node, bi::address const& _addr, unsigned short _udpPort, unsigned short _tcpPort); + + /// Create Peer and attempt keeping peer connected. + void requirePeer(NodeId const& _node, bi::address const& _udpAddr, unsigned short _udpPort, bi::address const& _tcpAddr = bi::address(), unsigned short _tcpPort = 0); + /// Note peer as no longer being required. + void relinquishPeer(NodeId const& _node); + /// Set ideal number of peers. void setIdealPeerCount(unsigned _n) { m_idealPeerCount = _n; } @@ -128,10 +121,10 @@ public: size_t peerCount() const; /// Get the address we're listening on currently. - std::string listenAddress() const { return m_tcpPublic.address().to_string(); } + std::string listenAddress() const { return m_netPrefs.listenIPAddress.empty() ? "0.0.0.0" : m_netPrefs.listenIPAddress; } /// Get the port we're listening on currently. - unsigned short listenPort() const { return m_tcpPublic.port(); } + unsigned short listenPort() const { return m_netPrefs.listenPort; } /// Serialise the set of known peers. bytes saveNetwork() const; @@ -139,7 +132,7 @@ public: // TODO: P2P this should be combined with peers into a HostStat object of some kind; coalesce data, as it's only used for status information. Peers getPeers() const { RecursiveGuard l(x_sessions); Peers ret; for (auto const& i: m_peers) ret.push_back(*i.second); return ret; } - void setNetworkPreferences(NetworkPreferences const& _p) { auto had = isStarted(); if (had) stop(); m_netPrefs = _p; if (had) start(); } + void setNetworkPreferences(NetworkPreferences const& _p, bool _dropPeers = false) { m_dropPeers = _dropPeers; auto had = isStarted(); if (had) stop(); m_netPrefs = _p; if (had) start(); } /// Start network. @threadsafe void start(); @@ -153,7 +146,8 @@ public: NodeId id() const { return m_alias.pub(); } - void registerPeer(std::shared_ptr _s, CapDescs const& _caps); + /// Validates and starts peer session, taking ownership of _io. Disconnects and returns false upon error. + void startPeerSession(Public const& _id, RLP const& _hello, RLPXFrameIO* _io, bi::tcp::endpoint _endpoint); protected: void onNodeTableEvent(NodeId const& _n, NodeTableEventType const& _e); @@ -162,8 +156,10 @@ protected: void restoreNetwork(bytesConstRef _b); private: - /// Populate m_peerAddresses with available public addresses. - void determinePublic(std::string const& _publicAddress, bool _upnp); + bool havePeerSession(NodeId _id) { RecursiveGuard l(x_sessions); return m_sessions.count(_id) ? !!m_sessions[_id].lock() : false; } + + /// Determines and sets m_tcpPublic to publicly advertised address. + void determinePublic(); void connect(std::shared_ptr const& _p); @@ -176,11 +172,6 @@ private: /// Called only from startedWorking(). void runAcceptor(); - /// Handler for verifying handshake siganture before creating session. _nodeId is passed for outbound connections. If successful, socket is moved to Session via std::move. - void doHandshake(bi::tcp::socket* _socket, NodeId _nodeId = NodeId()); - - void seal(bytes& _b); - /// Called by Worker. Not thread-safe; to be called only by worker. virtual void startedWorking(); /// Called by startedWorking. Not thread-safe; to be called only be Worker. @@ -205,7 +196,7 @@ private: NetworkPreferences m_netPrefs; ///< Network settings. /// Interface addresses (private, public) - std::vector m_ifAddresses; ///< Interface addresses. + std::set m_ifAddresses; ///< Interface addresses. int m_listenPort = -1; ///< What port are we listening on. -1 means binding failed or acceptor hasn't been initialized. @@ -224,20 +215,30 @@ private: /// Shared storage of Peer objects. Peers are created or destroyed on demand by the Host. Active sessions maintain a shared_ptr to a Peer; std::map> m_peers; + + /// Peers we try to connect regardless of p2p network. + std::set m_requiredPeers; + Mutex x_requiredPeers; /// The nodes to which we are currently connected. Used by host to service peer requests and keepAlivePeers and for shutdown. (see run()) /// Mutable because we flush zombie entries (null-weakptrs) as regular maintenance from a const method. mutable std::map> m_sessions; mutable RecursiveMutex x_sessions; + + std::list> m_connecting; ///< Pending connections. + Mutex x_connecting; ///< Mutex for m_connecting. unsigned m_idealPeerCount = 5; ///< Ideal number of peers to be connected to. - std::set m_peerAddresses; ///< Public addresses that peers (can) know us by. - std::map> m_capabilities; ///< Each of the capabilities we support. + + /// Deadline timers used for isolated network events. GC'd by run. + std::list> m_timers; + Mutex x_timers; std::chrono::steady_clock::time_point m_lastPing; ///< Time we sent the last ping to all peers. bool m_accepting = false; + bool m_dropPeers = false; }; } diff --git a/libp2p/HostCapability.cpp b/libp2p/HostCapability.cpp index 9437cd45c..b2acdcd1b 100644 --- a/libp2p/HostCapability.cpp +++ b/libp2p/HostCapability.cpp @@ -27,11 +27,6 @@ using namespace std; using namespace dev; using namespace dev::p2p; -void HostCapabilityFace::seal(bytes& _b) -{ - m_host->seal(_b); -} - std::vector,std::shared_ptr>> HostCapabilityFace::peerSessions() const { RecursiveGuard l(m_host->x_sessions); diff --git a/libp2p/HostCapability.h b/libp2p/HostCapability.h index 9122ca1fa..93086b1c9 100644 --- a/libp2p/HostCapability.h +++ b/libp2p/HostCapability.h @@ -57,8 +57,6 @@ protected: virtual void onStarting() {} virtual void onStopping() {} - void seal(bytes& _b); - private: Host* m_host = nullptr; }; diff --git a/libp2p/Network.cpp b/libp2p/Network.cpp index d004f33d2..74bc8bd45 100644 --- a/libp2p/Network.cpp +++ b/libp2p/Network.cpp @@ -27,8 +27,10 @@ #endif #include +#include #include +#include #include #include #include "Common.h" @@ -39,9 +41,9 @@ using namespace std; using namespace dev; using namespace dev::p2p; -std::vector Network::getInterfaceAddresses() +std::set Network::getInterfaceAddresses() { - std::vector addresses; + std::set addresses; #ifdef _WIN32 WSAData wsaData; @@ -71,7 +73,7 @@ std::vector Network::getInterfaceAddresses() char *addrStr = inet_ntoa(addr); bi::address address(bi::address::from_string(addrStr)); if (!isLocalHostAddress(address)) - addresses.push_back(address.to_v4()); + addresses.insert(address.to_v4()); } WSACleanup(); @@ -90,7 +92,7 @@ std::vector Network::getInterfaceAddresses() in_addr addr = ((struct sockaddr_in *)ifa->ifa_addr)->sin_addr; boost::asio::ip::address_v4 address(boost::asio::detail::socket_ops::network_to_host_long(addr.s_addr)); if (!isLocalHostAddress(address)) - addresses.push_back(address); + addresses.insert(address); } else if (ifa->ifa_addr->sa_family == AF_INET6) { @@ -100,7 +102,7 @@ std::vector Network::getInterfaceAddresses() memcpy(&bytes[0], addr.s6_addr, 16); boost::asio::ip::address_v6 address(bytes, sockaddr->sin6_scope_id); if (!isLocalHostAddress(address)) - addresses.push_back(address); + addresses.insert(address); } } @@ -112,13 +114,39 @@ std::vector Network::getInterfaceAddresses() return std::move(addresses); } -int Network::tcp4Listen(bi::tcp::acceptor& _acceptor, unsigned short _listenPort) +int Network::tcp4Listen(bi::tcp::acceptor& _acceptor, NetworkPreferences const& _netPrefs) { int retport = -1; - for (unsigned i = 0; i < 2; ++i) + if (_netPrefs.listenIPAddress.empty()) + for (unsigned i = 0; i < 2; ++i) + { + // try to connect w/listenPort, else attempt net-allocated port + bi::tcp::endpoint endpoint(bi::tcp::v4(), i ? 0 : _netPrefs.listenPort); + try + { + _acceptor.open(endpoint.protocol()); + _acceptor.set_option(ba::socket_base::reuse_address(true)); + _acceptor.bind(endpoint); + _acceptor.listen(); + retport = _acceptor.local_endpoint().port(); + break; + } + catch (...) + { + if (i) + { + // both attempts failed + cwarn << "Couldn't start accepting connections on host. Something very wrong with network?\n" << boost::current_exception_diagnostic_information(); + } + + // first attempt failed + _acceptor.close(); + continue; + } + } + else { - // try to connect w/listenPort, else attempt net-allocated port - bi::tcp::endpoint endpoint(bi::tcp::v4(), i ? 0 : _listenPort); + bi::tcp::endpoint endpoint(bi::address::from_string(_netPrefs.listenIPAddress), _netPrefs.listenPort); try { _acceptor.open(endpoint.protocol()); @@ -126,25 +154,18 @@ int Network::tcp4Listen(bi::tcp::acceptor& _acceptor, unsigned short _listenPort _acceptor.bind(endpoint); _acceptor.listen(); retport = _acceptor.local_endpoint().port(); - break; } catch (...) { - if (i) - { - // both attempts failed - cwarn << "Couldn't start accepting connections on host. Something very wrong with network?\n" << boost::current_exception_diagnostic_information(); - } - - // first attempt failed - _acceptor.close(); - continue; + clog(NetWarn) << "Couldn't start accepting connections on host. Failed to accept socket.\n" << boost::current_exception_diagnostic_information(); } + assert(retport == _netPrefs.listenPort); + return retport; } return retport; } -bi::tcp::endpoint Network::traverseNAT(std::vector const& _ifAddresses, unsigned short _listenPort, bi::address& o_upnpifaddr) +bi::tcp::endpoint Network::traverseNAT(std::set const& _ifAddresses, unsigned short _listenPort, bi::address& o_upnpInterfaceAddr) { asserts(_listenPort != 0); @@ -154,28 +175,28 @@ bi::tcp::endpoint Network::traverseNAT(std::vector const& _ifAddres upnp = new UPnP; } // let m_upnp continue as null - we handle it properly. - catch (NoUPnPDevice) {} + catch (...) {} - bi::tcp::endpoint upnpep; + bi::tcp::endpoint upnpEP; if (upnp && upnp->isValid()) { - bi::address paddr; + bi::address pAddr; int extPort = 0; for (auto const& addr: _ifAddresses) if (addr.is_v4() && isPrivateAddress(addr) && (extPort = upnp->addRedirect(addr.to_string().c_str(), _listenPort))) { - paddr = addr; + pAddr = addr; break; } - auto eip = upnp->externalIP(); - bi::address eipaddr(bi::address::from_string(eip)); - if (extPort && eip != string("0.0.0.0") && !isPrivateAddress(eipaddr)) + auto eIP = upnp->externalIP(); + bi::address eIPAddr(bi::address::from_string(eIP)); + if (extPort && eIP != string("0.0.0.0") && !isPrivateAddress(eIPAddr)) { clog(NetNote) << "Punched through NAT and mapped local port" << _listenPort << "onto external port" << extPort << "."; - clog(NetNote) << "External addr:" << eip; - o_upnpifaddr = paddr; - upnpep = bi::tcp::endpoint(eipaddr, (unsigned short)extPort); + clog(NetNote) << "External addr:" << eIP; + o_upnpInterfaceAddr = pAddr; + upnpEP = bi::tcp::endpoint(eIPAddr, (unsigned short)extPort); } else clog(NetWarn) << "Couldn't punch through NAT (or no NAT in place)."; @@ -184,5 +205,33 @@ bi::tcp::endpoint Network::traverseNAT(std::vector const& _ifAddres delete upnp; } - return upnpep; + return upnpEP; } + +bi::tcp::endpoint Network::resolveHost(string const& _addr) +{ + static boost::asio::io_service s_resolverIoService; + + vector split; + boost::split(split, _addr, boost::is_any_of(":")); + unsigned port = split.size() > 1 ? stoi(split[1]) : dev::p2p::c_defaultIPPort; + + bi::tcp::endpoint ep(bi::address(), port); + boost::system::error_code ec; + bi::address address = bi::address::from_string(split[0], ec); + if (!ec) + ep.address(address); + else + { + boost::system::error_code ec; + // resolve returns an iterator (host can resolve to multiple addresses) + bi::tcp::resolver r(s_resolverIoService); + auto it = r.resolve({split[0], toString(port)}, ec); + if (ec) + clog(NetWarn) << "Error resolving host address " << _addr << ":" << ec.message(); + else + ep = *it; + } + return ep; +} + diff --git a/libp2p/Network.h b/libp2p/Network.h index aeeabf329..d02ce3cbe 100644 --- a/libp2p/Network.h +++ b/libp2p/Network.h @@ -39,12 +39,19 @@ namespace p2p struct NetworkPreferences { - NetworkPreferences(unsigned short p = 30303, std::string i = std::string(), bool u = true, bool l = false): listenPort(p), publicIP(i), upnp(u), localNetworking(l) {} + // Default Network Preferences + NetworkPreferences(unsigned short lp = 30303): listenPort(lp) {} + + // Network Preferences with specific Listen IP + NetworkPreferences(std::string const& l, unsigned short lp = 30303, bool u = true): publicIPAddress(), listenIPAddress(l), listenPort(lp), traverseNAT(u) {} + + // Network Preferences with intended Public IP + NetworkPreferences(std::string const& publicIP, std::string const& l = std::string(), unsigned short lp = 30303, bool u = true): publicIPAddress(publicIP), listenIPAddress(l), listenPort(lp), traverseNAT(u) { if (!publicIPAddress.empty() && !isPublicAddress(publicIPAddress)) BOOST_THROW_EXCEPTION(InvalidPublicIPAddress()); } + std::string publicIPAddress; + std::string listenIPAddress; unsigned short listenPort = 30303; - std::string publicIP; - bool upnp = true; - bool localNetworking = false; + bool traverseNAT = true; }; /** @@ -55,14 +62,17 @@ class Network { public: /// @returns public and private interface addresses - static std::vector getInterfaceAddresses(); + static std::set getInterfaceAddresses(); /// Try to bind and listen on _listenPort, else attempt net-allocated port. - static int tcp4Listen(bi::tcp::acceptor& _acceptor, unsigned short _listenPort); + static int tcp4Listen(bi::tcp::acceptor& _acceptor, NetworkPreferences const& _netPrefs); /// Return public endpoint of upnp interface. If successful o_upnpifaddr will be a private interface address and endpoint will contain public address and port. - static bi::tcp::endpoint traverseNAT(std::vector const& _ifAddresses, unsigned short _listenPort, bi::address& o_upnpifaddr); + static bi::tcp::endpoint traverseNAT(std::set const& _ifAddresses, unsigned short _listenPort, bi::address& o_upnpInterfaceAddr); + + /// Resolve "host:port" string as TCP endpoint. Returns unspecified endpoint on failure. + static bi::tcp::endpoint resolveHost(std::string const& _host); }; - + } } diff --git a/libp2p/NodeTable.cpp b/libp2p/NodeTable.cpp index adeb43c2e..407b99942 100644 --- a/libp2p/NodeTable.cpp +++ b/libp2p/NodeTable.cpp @@ -27,11 +27,11 @@ using namespace dev::p2p; NodeEntry::NodeEntry(Node _src, Public _pubk, NodeIPEndpoint _gw): Node(_pubk, _gw), distance(NodeTable::distance(_src.id,_pubk)) {} NodeEntry::NodeEntry(Node _src, Public _pubk, bi::udp::endpoint _udp): Node(_pubk, NodeIPEndpoint(_udp)), distance(NodeTable::distance(_src.id,_pubk)) {} -NodeTable::NodeTable(ba::io_service& _io, KeyPair _alias, uint16_t _udp): - m_node(Node(_alias.pub(), bi::udp::endpoint())), +NodeTable::NodeTable(ba::io_service& _io, KeyPair _alias, bi::address const& _udpAddress, uint16_t _udp): + m_node(Node(_alias.pub(), bi::udp::endpoint(_udpAddress, _udp))), m_secret(_alias.sec()), m_io(_io), - m_socket(new NodeSocket(m_io, *this, _udp)), + m_socket(new NodeSocket(m_io, *this, m_node.endpoint.udp)), m_socketPointer(m_socket.get()), m_bucketRefreshTimer(m_io), m_evictionCheckTimer(m_io) @@ -70,37 +70,41 @@ shared_ptr NodeTable::addNode(Public const& _pubk, bi::udp::endpoint shared_ptr NodeTable::addNode(Node const& _node) { - // ping address if nodeid is empty + // re-enable tcp checks when NAT hosts are handled by discover + // we handle when tcp endpoint is 0 below + if (_node.endpoint.udp.address().to_string() == "0.0.0.0") + { + clog(NodeTableWarn) << "addNode Failed. Invalid UDP address 0.0.0.0 for" << _node.id.abridged(); + return move(shared_ptr()); + } + + // ping address to recover nodeid if nodeid is empty if (!_node.id) { + clog(NodeTableConnect) << "Sending public key discovery Ping to" << _node.endpoint.udp << "(Advertising:" << m_node.endpoint.udp << ")"; + { + Guard l(x_pubkDiscoverPings); + m_pubkDiscoverPings[_node.endpoint.udp.address()] = std::chrono::steady_clock::now(); + } PingNode p(_node.endpoint.udp, m_node.endpoint.udp.address().to_string(), m_node.endpoint.udp.port()); p.sign(m_secret); m_socketPointer->send(p); - shared_ptr n; - return move(n); + return move(shared_ptr()); } - Guard l(x_nodes); - if (m_nodes.count(_node.id)) { -// // SECURITY: remove this in beta - it's only for lazy connections and presents an easy attack vector. -// if (m_server->m_peers.count(id) && isPrivateAddress(m_server->m_peers.at(id)->address.address()) && ep.port() != 0) -// // Update address if the node if we now have a public IP for it. -// m_server->m_peers[id]->address = ep; - return m_nodes[_node.id]; + Guard ln(x_nodes); + if (m_nodes.count(_node.id)) + return m_nodes[_node.id]; } shared_ptr ret(new NodeEntry(m_node, _node.id, NodeIPEndpoint(_node.endpoint.udp, _node.endpoint.tcp))); m_nodes[_node.id] = ret; + ret->cullEndpoint(); + clog(NodeTableConnect) << "addNode pending for" << m_node.endpoint.udp << m_node.endpoint.tcp; PingNode p(_node.endpoint.udp, m_node.endpoint.udp.address().to_string(), m_node.endpoint.udp.port()); p.sign(m_secret); m_socketPointer->send(p); - - // TODO p2p: rename to p2p.nodes.pending, add p2p.nodes.add event (when pong is received) - clog(NodeTableNote) << "p2p.nodes.add " << _node.id.abridged(); - if (m_nodeEventHandler) - m_nodeEventHandler->appendEvent(_node.id, NodeEntryAdded); - return ret; } @@ -135,7 +139,6 @@ list NodeTable::snapshot() const Node NodeTable::node(NodeId const& _id) { - // TODO p2p: eloquent copy operator Guard l(x_nodes); if (m_nodes.count(_id)) { @@ -149,7 +152,7 @@ Node NodeTable::node(NodeId const& _id) shared_ptr NodeTable::nodeEntry(NodeId _id) { Guard l(x_nodes); - return m_nodes.count(_id) ? move(m_nodes[_id]) : move(shared_ptr()); + return m_nodes.count(_id) ? m_nodes[_id] : shared_ptr(); } void NodeTable::discover(NodeId _node, unsigned _round, shared_ptr>> _tried) @@ -159,7 +162,7 @@ void NodeTable::discover(NodeId _node, unsigned _round, shared_ptr _leastSeen, shared_ptr _n m_evictions.push_back(EvictionTimeout(make_pair(_leastSeen->id,chrono::steady_clock::now()), _new->id)); if (m_evictions.size() == 1) doCheckEvictions(boost::system::error_code()); - - m_evictions.push_back(EvictionTimeout(make_pair(_leastSeen->id,chrono::steady_clock::now()), _new->id)); } ping(_leastSeen.get()); } @@ -307,23 +308,25 @@ void NodeTable::noteActiveNode(Public const& _pubk, bi::udp::endpoint const& _en { if (_pubk == m_node.address()) return; - - clog(NodeTableNote) << "Noting active node:" << _pubk.abridged() << _endpoint.address().to_string() << ":" << _endpoint.port(); - shared_ptr node(addNode(_pubk, _endpoint, bi::tcp::endpoint(_endpoint.address(), _endpoint.port()))); - - // TODO p2p: old bug (maybe gone now) sometimes node is nullptr here - if (!!node) + shared_ptr node = nodeEntry(_pubk); + if (!!node && !node->pending) { + clog(NodeTableConnect) << "Noting active node:" << _pubk.abridged() << _endpoint.address().to_string() << ":" << _endpoint.port(); + node->endpoint.udp.address(_endpoint.address()); + node->endpoint.udp.port(_endpoint.port()); + node->cullEndpoint(); + shared_ptr contested; { Guard l(x_state); NodeBucket& s = bucket_UNSAFE(node.get()); - s.nodes.remove_if([&node](weak_ptr n) + bool removed = false; + s.nodes.remove_if([&node, &removed](weak_ptr const& n) { if (n.lock() == node) - return true; - return false; + removed = true; + return removed; }); if (s.nodes.size() >= s_bucketSize) @@ -335,12 +338,18 @@ void NodeTable::noteActiveNode(Public const& _pubk, bi::udp::endpoint const& _en s.nodes.pop_front(); s.nodes.push_back(node); s.touch(); + + if (!removed && m_nodeEventHandler) + m_nodeEventHandler->appendEvent(node->id, NodeEntryAdded); } } else { s.nodes.push_back(node); s.touch(); + + if (!removed && m_nodeEventHandler) + m_nodeEventHandler->appendEvent(node->id, NodeEntryAdded); } } @@ -351,19 +360,17 @@ void NodeTable::noteActiveNode(Public const& _pubk, bi::udp::endpoint const& _en void NodeTable::dropNode(shared_ptr _n) { + // remove from nodetable { Guard l(x_state); NodeBucket& s = bucket_UNSAFE(_n.get()); s.nodes.remove_if([&_n](weak_ptr n) { return n.lock() == _n; }); } - { - Guard l(x_nodes); - m_nodes.erase(_n->id); - } - clog(NodeTableNote) << "p2p.nodes.drop " << _n->id.abridged(); + // notify host + clog(NodeTableUpdate) << "p2p.nodes.drop " << _n->id.abridged(); if (m_nodeEventHandler) - m_nodeEventHandler->appendEvent(_n->id, NodeEntryRemoved); + m_nodeEventHandler->appendEvent(_n->id, NodeEntryDropped); } NodeTable::NodeBucket& NodeTable::bucket_UNSAFE(NodeEntry const* _n) @@ -376,7 +383,7 @@ void NodeTable::onReceived(UDPSocketFace*, bi::udp::endpoint const& _from, bytes // h256 + Signature + type + RLP (smallest possible packet is empty neighbours packet which is 3 bytes) if (_packet.size() < h256::size + Signature::size + 1 + 3) { - clog(NodeTableMessageSummary) << "Invalid Message size from " << _from.address().to_string() << ":" << _from.port(); + clog(NodeTableWarn) << "Invalid Message size from " << _from.address().to_string() << ":" << _from.port(); return; } @@ -384,26 +391,23 @@ void NodeTable::onReceived(UDPSocketFace*, bi::udp::endpoint const& _from, bytes h256 hashSigned(sha3(hashedBytes)); if (!_packet.cropped(0, h256::size).contentsEqual(hashSigned.asBytes())) { - clog(NodeTableMessageSummary) << "Invalid Message hash from " << _from.address().to_string() << ":" << _from.port(); + clog(NodeTableWarn) << "Invalid Message hash from " << _from.address().to_string() << ":" << _from.port(); return; } bytesConstRef signedBytes(hashedBytes.cropped(Signature::size, hashedBytes.size() - Signature::size)); - // todo: verify sig via known-nodeid and MDC, or, do ping/pong auth if node/endpoint is unknown/untrusted + // todo: verify sig via known-nodeid and MDC bytesConstRef sigBytes(_packet.cropped(h256::size, Signature::size)); Public nodeid(dev::recover(*(Signature const*)sigBytes.data(), sha3(signedBytes))); if (!nodeid) { - clog(NodeTableMessageSummary) << "Invalid Message signature from " << _from.address().to_string() << ":" << _from.port(); + clog(NodeTableWarn) << "Invalid Message signature from " << _from.address().to_string() << ":" << _from.port(); return; } unsigned packetType = signedBytes[0]; - if (packetType && packetType < 4) - noteActiveNode(nodeid, _from); - bytesConstRef rlpBytes(_packet.cropped(h256::size + Signature::size + 1)); RLP rlp(rlpBytes); try { @@ -411,37 +415,56 @@ void NodeTable::onReceived(UDPSocketFace*, bi::udp::endpoint const& _from, bytes { case Pong::type: { -// clog(NodeTableMessageSummary) << "Received Pong from " << _from.address().to_string() << ":" << _from.port(); Pong in = Pong::fromBytesConstRef(_from, rlpBytes); // whenever a pong is received, check if it's in m_evictions Guard le(x_evictions); + bool evictionEntry = false; for (auto it = m_evictions.begin(); it != m_evictions.end(); it++) if (it->first.first == nodeid && it->first.second > std::chrono::steady_clock::now()) { + evictionEntry = true; if (auto n = nodeEntry(it->second)) dropNode(n); - if (auto n = node(it->first.first)) - addNode(n); + if (auto n = nodeEntry(it->first.first)) + n->pending = false; it = m_evictions.erase(it); } + + // if not, check if it's known/pending or a pubk discovery ping + if (!evictionEntry) + { + if (auto n = nodeEntry(nodeid)) + n->pending = false; + else if (m_pubkDiscoverPings.count(_from.address())) + { + { + Guard l(x_pubkDiscoverPings); + m_pubkDiscoverPings.erase(_from.address()); + } + if (!haveNode(nodeid)) + addNode(nodeid, _from, bi::tcp::endpoint(_from.address(), _from.port())); + } + else + return; // unsolicited pong; don't note node as active + } + + clog(NodeTableConnect) << "PONG from " << nodeid.abridged() << _from; break; } case Neighbours::type: { Neighbours in = Neighbours::fromBytesConstRef(_from, rlpBytes); -// clog(NodeTableMessageSummary) << "Received " << in.nodes.size() << " Neighbours from " << _from.address().to_string() << ":" << _from.port(); for (auto n: in.nodes) - noteActiveNode(n.node, bi::udp::endpoint(bi::address::from_string(n.ipAddress), n.port)); + addNode(n.node, bi::udp::endpoint(bi::address::from_string(n.ipAddress), n.port), bi::tcp::endpoint(bi::address::from_string(n.ipAddress), n.port)); break; } case FindNode::type: { -// clog(NodeTableMessageSummary) << "Received FindNode from " << _from.address().to_string() << ":" << _from.port(); FindNode in = FindNode::fromBytesConstRef(_from, rlpBytes); vector> nearest = nearestNodeEntries(in.target); @@ -450,6 +473,8 @@ void NodeTable::onReceived(UDPSocketFace*, bi::udp::endpoint const& _from, bytes { Neighbours out(_from, nearest, offset, nlimit); out.sign(m_secret); + if (out.data.size() > 1280) + clog(NetWarn) << "Sending truncated datagram, size: " << out.data.size(); m_socketPointer->send(out); } break; @@ -457,8 +482,15 @@ void NodeTable::onReceived(UDPSocketFace*, bi::udp::endpoint const& _from, bytes case PingNode::type: { -// clog(NodeTableMessageSummary) << "Received PingNode from " << _from.address().to_string() << ":" << _from.port(); PingNode in = PingNode::fromBytesConstRef(_from, rlpBytes); + if (in.version != dev::p2p::c_protocolVersion) + { + if (auto n = nodeEntry(nodeid)) + dropNode(n); + return; + } + + addNode(nodeid, _from, bi::tcp::endpoint(bi::address::from_string(in.ipAddress), in.port)); Pong p(_from); p.echo = sha3(rlpBytes); @@ -471,6 +503,8 @@ void NodeTable::onReceived(UDPSocketFace*, bi::udp::endpoint const& _from, bytes clog(NodeTableWarn) << "Invalid Message, " << hex << packetType << ", received from " << _from.address().to_string() << ":" << dec << _from.port(); return; } + + noteActiveNode(nodeid, _from); } catch (...) { @@ -516,33 +550,44 @@ void NodeTable::doRefreshBuckets(boost::system::error_code const& _ec) if (_ec) return; - clog(NodeTableNote) << "refreshing buckets"; + clog(NodeTableEvent) << "refreshing buckets"; bool connected = m_socketPointer->isOpen(); - bool refreshed = false; if (connected) { - Guard l(x_state); - for (auto& d: m_state) - if (chrono::steady_clock::now() - d.modified > c_bucketRefresh) - { - d.touch(); - while (!d.nodes.empty()) - { - auto n = d.nodes.front(); - if (auto p = n.lock()) - { - refreshed = true; - ping(p.get()); - break; - } - d.nodes.pop_front(); - } - } + NodeId randNodeId; + crypto::Nonce::get().ref().copyTo(randNodeId.ref().cropped(0, h256::size)); + crypto::Nonce::get().ref().copyTo(randNodeId.ref().cropped(h256::size, h256::size)); + discover(randNodeId); } - unsigned nextRefresh = connected ? (refreshed ? 200 : c_bucketRefresh.count()*1000) : 10000; auto runcb = [this](boost::system::error_code const& error) { doRefreshBuckets(error); }; - m_bucketRefreshTimer.expires_from_now(boost::posix_time::milliseconds(nextRefresh)); + m_bucketRefreshTimer.expires_from_now(boost::posix_time::milliseconds(c_bucketRefresh.count())); m_bucketRefreshTimer.async_wait(runcb); } +void PingNode::streamRLP(RLPStream& _s) const +{ + _s.appendList(4); + _s << dev::p2p::c_protocolVersion << ipAddress << port << expiration; +} + +void PingNode::interpretRLP(bytesConstRef _bytes) +{ + RLP r(_bytes); + if (r.itemCountStrict() == 3) + { + version = 2; + ipAddress = r[0].toString(); + port = r[1].toInt(RLP::Strict); + expiration = r[2].toInt(RLP::Strict); + } + else if (r.itemCountStrict() == 4) + { + version = r[0].toInt(RLP::Strict); + ipAddress = r[1].toString(); + port = r[2].toInt(RLP::Strict); + expiration = r[3].toInt(RLP::Strict); + } + else + BOOST_THROW_EXCEPTION(InvalidRLP()); +} diff --git a/libp2p/NodeTable.h b/libp2p/NodeTable.h index 10fa921e4..9ff28b7f1 100644 --- a/libp2p/NodeTable.h +++ b/libp2p/NodeTable.h @@ -44,11 +44,13 @@ struct NodeEntry: public Node NodeEntry(Node _src, Public _pubk, bi::udp::endpoint _udp); unsigned const distance; ///< Node's distance (xor of _src as integer). + + bool pending = true; ///< Node will be ignored until Pong is received }; enum NodeTableEventType { NodeEntryAdded, - NodeEntryRemoved + NodeEntryDropped }; class NodeTable; class NodeTableEventHandler @@ -101,11 +103,10 @@ inline std::ostream& operator<<(std::ostream& _out, NodeTable const& _nodeTable) * NodeTable accepts a port for UDP and will listen to the port on all available * interfaces. * + * * [Integration] - * @todo restore nodes: affects refreshbuckets * @todo TCP endpoints - * @todo makeRequired: don't try to evict node if node isRequired. - * @todo makeRequired: exclude bucket from refresh if we have node as peer. + * @todo GC uniform 1/32 entires at 112500ms interval * * [Optimization] * @todo serialize evictions per-bucket @@ -134,22 +135,23 @@ class NodeTable: UDPSocketEvents, public std::enable_shared_from_this using EvictionTimeout = std::pair, NodeId>; ///< First NodeId may be evicted and replaced with second NodeId. public: - NodeTable(ba::io_service& _io, KeyPair _alias, uint16_t _udpPort = 30303); + /// Constructor requiring host for I/O, credentials, and IP Address and port to listen on. + NodeTable(ba::io_service& _io, KeyPair _alias, bi::address const& _udpAddress, uint16_t _udpPort = 30303); ~NodeTable(); /// Returns distance based on xor metric two node ids. Used by NodeEntry and NodeTable. static unsigned distance(NodeId const& _a, NodeId const& _b) { u512 d = _a ^ _b; unsigned ret; for (ret = 0; d >>= 1; ++ret) {}; return ret; } - /// Set event handler for NodeEntryAdded and NodeEntryRemoved events. + /// Set event handler for NodeEntryAdded and NodeEntryDropped events. void setEventHandler(NodeTableEventHandler* _handler) { m_nodeEventHandler.reset(_handler); } - /// Called by implementation which provided handler to process NodeEntryAdded/NodeEntryRemoved events. Events are coalesced by type whereby old events are ignored. + /// Called by implementation which provided handler to process NodeEntryAdded/NodeEntryDropped events. Events are coalesced by type whereby old events are ignored. void processEvents(); - /// Add node. Node will be pinged if it's not already known. + /// Add node. Node will be pinged and empty shared_ptr is returned if NodeId is uknown. std::shared_ptr addNode(Public const& _pubk, bi::udp::endpoint const& _udp, bi::tcp::endpoint const& _tcp); - /// Add node. Node will be pinged if it's not already known. + /// Add node. Node will be pinged and empty shared_ptr is returned if node has never been seen. std::shared_ptr addNode(Node const& _node); /// To be called when node table is empty. Runs node discovery with m_node.id as the target in order to populate node-table. @@ -193,7 +195,7 @@ private: /* todo: replace boost::posix_time; change constants to upper camelcase */ boost::posix_time::milliseconds const c_evictionCheckInterval = boost::posix_time::milliseconds(75); ///< Interval at which eviction timeouts are checked. std::chrono::milliseconds const c_reqTimeout = std::chrono::milliseconds(300); ///< How long to wait for requests (evict, find iterations). - std::chrono::seconds const c_bucketRefresh = std::chrono::seconds(3600); ///< Refresh interval prevents bucket from becoming stale. [Kademlia] + std::chrono::milliseconds const c_bucketRefresh = std::chrono::milliseconds(112500); ///< Refresh interval prevents bucket from becoming stale. [Kademlia] struct NodeBucket { @@ -225,7 +227,7 @@ private: /// Asynchronously drops _leastSeen node if it doesn't reply and adds _new node, otherwise _new node is thrown away. void evict(std::shared_ptr _leastSeen, std::shared_ptr _new); - /// Called whenever activity is received from an unknown node in order to maintain node table. + /// Called whenever activity is received from a node in order to maintain node table. void noteActiveNode(Public const& _pubk, bi::udp::endpoint const& _endpoint); /// Used to drop node when timeout occurs or when evict() result is to keep previous node. @@ -265,6 +267,9 @@ private: Mutex x_evictions; ///< LOCK x_nodes first if both x_nodes and x_evictions locks are required. std::deque m_evictions; ///< Eviction timeouts. + + Mutex x_pubkDiscoverPings; ///< LOCK x_nodes first if both x_nodes and x_pubkDiscoverPings locks are required. + std::map m_pubkDiscoverPings; ///< List of pending pings where node entry wasn't created due to unkown pubk. ba::io_service& m_io; ///< Used by bucket refresh timer. std::shared_ptr m_socket; ///< Shared pointer for our UDPSocket; ASIO requires shared_ptr. @@ -283,6 +288,8 @@ inline std::ostream& operator<<(std::ostream& _out, NodeTable const& _nodeTable) return _out; } +struct InvalidRLP: public Exception {}; + /** * Ping packet: Sent to check if node is alive. * PingNode is cached and regenerated after expiration - t, where t is timeout. @@ -311,13 +318,13 @@ struct PingNode: RLPXDatagram static const uint8_t type = 1; - unsigned version = 1; + unsigned version = 0; std::string ipAddress; unsigned port; unsigned expiration; - void streamRLP(RLPStream& _s) const { _s.appendList(3); _s << ipAddress << port << expiration; } - void interpretRLP(bytesConstRef _bytes) { RLP r(_bytes); ipAddress = r[0].toString(); port = r[1].toInt(); expiration = r[2].toInt(); } + void streamRLP(RLPStream& _s) const override; + void interpretRLP(bytesConstRef _bytes) override; }; /** @@ -411,8 +418,11 @@ struct Neighbours: RLPXDatagram struct NodeTableWarn: public LogChannel { static const char* name() { return "!P!"; } static const int verbosity = 0; }; struct NodeTableNote: public LogChannel { static const char* name() { return "*P*"; } static const int verbosity = 1; }; struct NodeTableMessageSummary: public LogChannel { static const char* name() { return "-P-"; } static const int verbosity = 2; }; -struct NodeTableConnect: public LogChannel { static const char* name() { return "+P+"; } static const int verbosity = 10; }; struct NodeTableMessageDetail: public LogChannel { static const char* name() { return "=P="; } static const int verbosity = 5; }; +struct NodeTableConnect: public LogChannel { static const char* name() { return "+P+"; } static const int verbosity = 10; }; +struct NodeTableEvent: public LogChannel { static const char* name() { return "+P+"; } static const int verbosity = 10; }; +struct NodeTableTimer: public LogChannel { static const char* name() { return "+P+"; } static const int verbosity = 10; }; +struct NodeTableUpdate: public LogChannel { static const char* name() { return "+P+"; } static const int verbosity = 10; }; struct NodeTableTriviaSummary: public LogChannel { static const char* name() { return "-P-"; } static const int verbosity = 10; }; struct NodeTableTriviaDetail: public LogChannel { static const char* name() { return "=P="; } static const int verbosity = 11; }; struct NodeTableAllDetail: public LogChannel { static const char* name() { return "=P="; } static const int verbosity = 13; }; 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 704e5c2b4..cb9155bbc 100644 --- a/libp2p/Peer.h +++ b/libp2p/Peer.h @@ -47,7 +47,6 @@ namespace p2p * those peers. Modifying these properties via a storage backend alleviates * Host of the responsibility. (&& remove save/restoreNetwork) * @todo reimplement recording of historical session information on per-transport basis - * @todo rebuild nodetable when localNetworking is enabled/disabled * @todo move attributes into protected */ class Peer: public Node @@ -55,6 +54,8 @@ class Peer: public Node friend class Session; /// Allows Session to update score and rating. friend class Host; /// For Host: saveNetwork(), restoreNetwork() + friend class RLPXHandshake; + public: bool isOffline() const { return !m_session.lock(); } @@ -74,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/RLPxFrameIO.cpp b/libp2p/RLPxFrameIO.cpp new file mode 100644 index 000000000..ac1a27bed --- /dev/null +++ b/libp2p/RLPxFrameIO.cpp @@ -0,0 +1,211 @@ +/* + 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 RLPXFrameIO.cpp + * @author Alex Leverington + * @date 2015 + */ + +#include "RLPxFrameIO.h" +#include +#include "Host.h" +#include "Session.h" +#include "Peer.h" +#include "RLPxHandshake.h" + +using namespace std; +using namespace dev; +using namespace dev::p2p; +using namespace CryptoPP; + +RLPXFrameIO::RLPXFrameIO(RLPXHandshake const& _init): m_socket(_init.m_socket) +{ + // we need: + // originated? + // Secret == output of ecdhe agreement + // authCipher + // ackCipher + + bytes keyMaterialBytes(64); + bytesRef keyMaterial(&keyMaterialBytes); + + // shared-secret = sha3(ecdhe-shared-secret || sha3(nonce || initiator-nonce)) + Secret ephemeralShared; + _init.m_ecdhe.agree(_init.m_remoteEphemeral, ephemeralShared); + ephemeralShared.ref().copyTo(keyMaterial.cropped(0, h256::size)); + h512 nonceMaterial; + h256 const& leftNonce = _init.m_originated ? _init.m_remoteNonce : _init.m_nonce; + h256 const& rightNonce = _init.m_originated ? _init.m_nonce : _init.m_remoteNonce; + leftNonce.ref().copyTo(nonceMaterial.ref().cropped(0, h256::size)); + rightNonce.ref().copyTo(nonceMaterial.ref().cropped(h256::size, h256::size)); + auto outRef(keyMaterial.cropped(h256::size, h256::size)); + sha3(nonceMaterial.ref(), outRef); // output h(nonces) + + sha3(keyMaterial, outRef); // output shared-secret + // token: sha3(outRef, bytesRef(&token)); -> m_host (to be saved to disk) + + // aes-secret = sha3(ecdhe-shared-secret || shared-secret) + sha3(keyMaterial, outRef); // output aes-secret + m_frameEncKey.resize(h256::size); + memcpy(m_frameEncKey.data(), outRef.data(), h256::size); + m_frameDecKey.resize(h256::size); + memcpy(m_frameDecKey.data(), outRef.data(), h256::size); + h128 iv; + m_frameEnc.SetKeyWithIV(m_frameEncKey, h256::size, iv.data()); + m_frameDec.SetKeyWithIV(m_frameDecKey, h256::size, iv.data()); + + // mac-secret = sha3(ecdhe-shared-secret || aes-secret) + sha3(keyMaterial, outRef); // output mac-secret + m_macEncKey.resize(h256::size); + memcpy(m_macEncKey.data(), outRef.data(), h256::size); + m_macEnc.SetKey(m_macEncKey, h256::size); + + // Initiator egress-mac: sha3(mac-secret^recipient-nonce || auth-sent-init) + // ingress-mac: sha3(mac-secret^initiator-nonce || auth-recvd-ack) + // Recipient egress-mac: sha3(mac-secret^initiator-nonce || auth-sent-ack) + // ingress-mac: sha3(mac-secret^recipient-nonce || auth-recvd-init) + + (*(h256*)outRef.data() ^ _init.m_remoteNonce).ref().copyTo(keyMaterial); + bytes const& egressCipher = _init.m_originated ? _init.m_authCipher : _init.m_ackCipher; + keyMaterialBytes.resize(h256::size + egressCipher.size()); + keyMaterial.retarget(keyMaterialBytes.data(), keyMaterialBytes.size()); + bytesConstRef(&egressCipher).copyTo(keyMaterial.cropped(h256::size, egressCipher.size())); + m_egressMac.Update(keyMaterial.data(), keyMaterial.size()); + + // recover mac-secret by re-xoring remoteNonce + (*(h256*)keyMaterial.data() ^ _init.m_remoteNonce ^ _init.m_nonce).ref().copyTo(keyMaterial); + bytes const& ingressCipher = _init.m_originated ? _init.m_ackCipher : _init.m_authCipher; + keyMaterialBytes.resize(h256::size + ingressCipher.size()); + keyMaterial.retarget(keyMaterialBytes.data(), keyMaterialBytes.size()); + bytesConstRef(&ingressCipher).copyTo(keyMaterial.cropped(h256::size, ingressCipher.size())); + m_ingressMac.Update(keyMaterial.data(), keyMaterial.size()); +} + +void RLPXFrameIO::writeSingleFramePacket(bytesConstRef _packet, bytes& o_bytes) +{ + // _packet = type || rlpList() + + RLPStream header; + uint32_t len = (uint32_t)_packet.size(); + header.appendRaw(bytes({byte((len >> 16) & 0xff), byte((len >> 8) & 0xff), byte(len & 0xff)})); + // zeroHeader: []byte{0xC2, 0x80, 0x80}. Should be rlpList(protocolType,seqId,totalPacketSize). + header.appendRaw(bytes({0xc2,0x80,0x80})); + + // TODO: SECURITY check that header is <= 16 bytes + + bytes headerWithMac(h256::size); + bytesConstRef(&header.out()).copyTo(bytesRef(&headerWithMac)); + m_frameEnc.ProcessData(headerWithMac.data(), headerWithMac.data(), 16); + updateEgressMACWithHeader(bytesConstRef(&headerWithMac).cropped(0, 16)); + egressDigest().ref().copyTo(bytesRef(&headerWithMac).cropped(h128::size,h128::size)); + + auto padding = (16 - (_packet.size() % 16)) % 16; + o_bytes.swap(headerWithMac); + o_bytes.resize(32 + _packet.size() + padding + h128::size); + bytesRef packetRef(o_bytes.data() + 32, _packet.size()); + m_frameEnc.ProcessData(packetRef.data(), _packet.data(), _packet.size()); + bytesRef paddingRef(o_bytes.data() + 32 + _packet.size(), padding); + if (padding) + m_frameEnc.ProcessData(paddingRef.data(), paddingRef.data(), padding); + bytesRef packetWithPaddingRef(o_bytes.data() + 32, _packet.size() + padding); + updateEgressMACWithFrame(packetWithPaddingRef); + bytesRef macRef(o_bytes.data() + 32 + _packet.size() + padding, h128::size); + egressDigest().ref().copyTo(macRef); +} + +bool RLPXFrameIO::authAndDecryptHeader(bytesRef io) +{ + asserts(io.size() == h256::size); + updateIngressMACWithHeader(io); + bytesConstRef macRef = io.cropped(h128::size, h128::size); + h128 expected = ingressDigest(); + if (*(h128*)macRef.data() != expected) + return false; + m_frameDec.ProcessData(io.data(), io.data(), h128::size); + return true; +} + +bool RLPXFrameIO::authAndDecryptFrame(bytesRef io) +{ + bytesRef cipherText(io.cropped(0, io.size() - h128::size)); + updateIngressMACWithFrame(cipherText); + bytesConstRef frameMac(io.data() + io.size() - h128::size, h128::size); + if (*(h128*)frameMac.data() != ingressDigest()) + return false; + m_frameDec.ProcessData(io.data(), io.data(), io.size() - h128::size); + return true; +} + +h128 RLPXFrameIO::egressDigest() +{ + SHA3_256 h(m_egressMac); + h128 digest; + h.TruncatedFinal(digest.data(), h128::size); + return move(digest); +} + +h128 RLPXFrameIO::ingressDigest() +{ + SHA3_256 h(m_ingressMac); + h128 digest; + h.TruncatedFinal(digest.data(), h128::size); + return move(digest); +} + +void RLPXFrameIO::updateEgressMACWithHeader(bytesConstRef _headerCipher) +{ + updateMAC(m_egressMac, _headerCipher.cropped(0, 16)); +} + +void RLPXFrameIO::updateEgressMACWithFrame(bytesConstRef _cipher) +{ + m_egressMac.Update(_cipher.data(), _cipher.size()); + updateMAC(m_egressMac); +} + +void RLPXFrameIO::updateIngressMACWithHeader(bytesConstRef _headerCipher) +{ + updateMAC(m_ingressMac, _headerCipher.cropped(0, 16)); +} + +void RLPXFrameIO::updateIngressMACWithFrame(bytesConstRef _cipher) +{ + m_ingressMac.Update(_cipher.data(), _cipher.size()); + updateMAC(m_ingressMac); +} + +void RLPXFrameIO::updateMAC(SHA3_256& _mac, bytesConstRef _seed) +{ + if (_seed.size() && _seed.size() != h128::size) + asserts(false); + + SHA3_256 prevDigest(_mac); + h128 encDigest(h128::size); + prevDigest.TruncatedFinal(encDigest.data(), h128::size); + h128 prevDigestOut = encDigest; + + { + Guard l(x_macEnc); + m_macEnc.ProcessData(encDigest.data(), encDigest.data(), 16); + } + if (_seed.size()) + encDigest ^= *(h128*)_seed.data(); + else + encDigest ^= *(h128*)prevDigestOut.data(); + + // update mac for final digest + _mac.Update(encDigest.data(), h128::size); +} diff --git a/libp2p/RLPxFrameIO.h b/libp2p/RLPxFrameIO.h new file mode 100644 index 000000000..0f0504e48 --- /dev/null +++ b/libp2p/RLPxFrameIO.h @@ -0,0 +1,133 @@ +/* + 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 RLPXFrameIO.h + * @author Alex Leverington + * @date 2015 + */ + + +#pragma once + +#include +#include +#include +#include +#include +#include "Common.h" +namespace ba = boost::asio; +namespace bi = boost::asio::ip; + +namespace dev +{ +namespace p2p +{ + +class RLPXHandshake; + +/** + * @brief Encoder/decoder transport for RLPx connections established by RLPXHandshake. + * Managed (via shared_ptr) socket for use by RLPXHandshake and RLPXFrameIO. + * + * Thread Safety + * Distinct Objects: Safe. + * Shared objects: Unsafe. + * * an instance method must not be called concurrently + * * a writeSingleFramePacket can be called concurrent to authAndDecryptHeader OR authAndDecryptFrame + */ +class RLPXSocket: public std::enable_shared_from_this +{ +public: + RLPXSocket(bi::tcp::socket* _socket): m_socket(std::move(*_socket)) {} + ~RLPXSocket() { close(); } + + bool isConnected() const { return m_socket.is_open(); } + void close() { try { boost::system::error_code ec; m_socket.shutdown(bi::tcp::socket::shutdown_both, ec); if (m_socket.is_open()) m_socket.close(); } catch (...){} } + bi::tcp::endpoint remoteEndpoint() { try { return m_socket.remote_endpoint(); } catch (...){ return bi::tcp::endpoint(); } } + bi::tcp::socket& ref() { return m_socket; } + +protected: + bi::tcp::socket m_socket; +}; + +/** + * @brief Encoder/decoder transport for RLPx connections established by RLPXHandshake. + * + * Thread Safety + * Distinct Objects: Safe. + * Shared objects: Unsafe. + */ +class RLPXFrameIO +{ + friend class Session; +public: + /// Constructor. + /// Requires instance of RLPXHandshake which has completed first two phases of handshake. + RLPXFrameIO(RLPXHandshake const& _init); + ~RLPXFrameIO() {} + + /// Encrypt _packet as RLPx frame. + void writeSingleFramePacket(bytesConstRef _packet, bytes& o_bytes); + + /// Authenticate and decrypt header in-place. + bool authAndDecryptHeader(bytesRef io_cipherWithMac); + + /// Authenticate and decrypt frame in-place. + bool authAndDecryptFrame(bytesRef io_cipherWithMac); + + /// Return first 16 bytes of current digest from egress mac. + h128 egressDigest(); + + /// Return first 16 bytes of current digest from ingress mac. + h128 ingressDigest(); + +protected: + /// Update state of egress MAC with frame header. + void updateEgressMACWithHeader(bytesConstRef _headerCipher); + + /// Update state of egress MAC with frame. + void updateEgressMACWithFrame(bytesConstRef _cipher); + + /// Update state of ingress MAC with frame header. + void updateIngressMACWithHeader(bytesConstRef _headerCipher); + + /// Update state of ingress MAC with frame. + void updateIngressMACWithFrame(bytesConstRef _cipher); + + bi::tcp::socket& socket() { return m_socket->ref(); } + +private: + /// Update state of _mac. + void updateMAC(CryptoPP::SHA3_256& _mac, bytesConstRef _seed = bytesConstRef()); + + CryptoPP::SecByteBlock m_frameEncKey; ///< Key for m_frameEnc + CryptoPP::CTR_Mode::Encryption m_frameEnc; ///< Encoder for egress plaintext. + + CryptoPP::SecByteBlock m_frameDecKey; ///< Key for m_frameDec + CryptoPP::CTR_Mode::Encryption m_frameDec; ///< Decoder for egress plaintext. + + CryptoPP::SecByteBlock m_macEncKey; /// Key for m_macEnd + CryptoPP::ECB_Mode::Encryption m_macEnc; /// One-way coder used by updateMAC for ingress and egress MAC updates. + Mutex x_macEnc; /// Mutex + + CryptoPP::SHA3_256 m_egressMac; ///< State of MAC for egress ciphertext. + CryptoPP::SHA3_256 m_ingressMac; ///< State of MAC for ingress ciphertext. + + std::shared_ptr m_socket; +}; + +} +} \ No newline at end of file diff --git a/libp2p/RLPxHandshake.cpp b/libp2p/RLPxHandshake.cpp new file mode 100644 index 000000000..e311c615a --- /dev/null +++ b/libp2p/RLPxHandshake.cpp @@ -0,0 +1,283 @@ +/* + 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 RLPXHandshake.cpp + * @author Alex Leverington + * @date 2015 + */ + +#include "Host.h" +#include "Session.h" +#include "Peer.h" +#include "RLPxHandshake.h" +using namespace std; +using namespace dev; +using namespace dev::p2p; +using namespace CryptoPP; + +void RLPXHandshake::writeAuth() +{ + clog(NetConnect) << "p2p.connect.egress sending auth to " << m_socket->remoteEndpoint(); + m_auth.resize(Signature::size + h256::size + Public::size + h256::size + 1); + bytesRef sig(&m_auth[0], Signature::size); + bytesRef hepubk(&m_auth[Signature::size], h256::size); + bytesRef pubk(&m_auth[Signature::size + h256::size], Public::size); + bytesRef nonce(&m_auth[Signature::size + h256::size + Public::size], h256::size); + + // E(remote-pubk, S(ecdhe-random, ecdh-shared-secret^nonce) || H(ecdhe-random-pubk) || pubk || nonce || 0x0) + Secret staticShared; + crypto::ecdh::agree(m_host->m_alias.sec(), m_remote, staticShared); + sign(m_ecdhe.seckey(), staticShared ^ m_nonce).ref().copyTo(sig); + sha3(m_ecdhe.pubkey().ref(), hepubk); + m_host->m_alias.pub().ref().copyTo(pubk); + m_nonce.ref().copyTo(nonce); + m_auth[m_auth.size() - 1] = 0x0; + encryptECIES(m_remote, &m_auth, m_authCipher); + + auto self(shared_from_this()); + ba::async_write(m_socket->ref(), ba::buffer(m_authCipher), [this, self](boost::system::error_code ec, std::size_t) + { + transition(ec); + }); +} + +void RLPXHandshake::writeAck() +{ + clog(NetConnect) << "p2p.connect.ingress sending ack to " << m_socket->remoteEndpoint(); + m_ack.resize(Public::size + h256::size + 1); + bytesRef epubk(&m_ack[0], Public::size); + bytesRef nonce(&m_ack[Public::size], h256::size); + m_ecdhe.pubkey().ref().copyTo(epubk); + m_nonce.ref().copyTo(nonce); + m_ack[m_ack.size() - 1] = 0x0; + encryptECIES(m_remote, &m_ack, m_ackCipher); + + auto self(shared_from_this()); + ba::async_write(m_socket->ref(), ba::buffer(m_ackCipher), [this, self](boost::system::error_code ec, std::size_t) + { + transition(ec); + }); +} + +void RLPXHandshake::readAuth() +{ + clog(NetConnect) << "p2p.connect.ingress recving auth from " << m_socket->remoteEndpoint(); + m_authCipher.resize(307); + auto self(shared_from_this()); + ba::async_read(m_socket->ref(), ba::buffer(m_authCipher, 307), [this, self](boost::system::error_code ec, std::size_t) + { + if (ec) + transition(ec); + else if (decryptECIES(m_host->m_alias.sec(), bytesConstRef(&m_authCipher), m_auth)) + { + bytesConstRef sig(&m_auth[0], Signature::size); + bytesConstRef hepubk(&m_auth[Signature::size], h256::size); + bytesConstRef pubk(&m_auth[Signature::size + h256::size], Public::size); + bytesConstRef nonce(&m_auth[Signature::size + h256::size + Public::size], h256::size); + pubk.copyTo(m_remote.ref()); + nonce.copyTo(m_remoteNonce.ref()); + + Secret sharedSecret; + crypto::ecdh::agree(m_host->m_alias.sec(), m_remote, sharedSecret); + m_remoteEphemeral = recover(*(Signature*)sig.data(), sharedSecret ^ m_remoteNonce); + assert(sha3(m_remoteEphemeral) == *(h256*)hepubk.data()); + transition(); + } + else + { + clog(NetConnect) << "p2p.connect.ingress recving auth decrypt failed for" << m_socket->remoteEndpoint(); + m_nextState = Error; + transition(); + } + }); +} + +void RLPXHandshake::readAck() +{ + clog(NetConnect) << "p2p.connect.egress recving ack from " << m_socket->remoteEndpoint(); + m_ackCipher.resize(210); + auto self(shared_from_this()); + ba::async_read(m_socket->ref(), ba::buffer(m_ackCipher, 210), [this, self](boost::system::error_code ec, std::size_t) + { + if (ec) + transition(ec); + else if (decryptECIES(m_host->m_alias.sec(), bytesConstRef(&m_ackCipher), m_ack)) + { + bytesConstRef(&m_ack).cropped(0, Public::size).copyTo(m_remoteEphemeral.ref()); + bytesConstRef(&m_ack).cropped(Public::size, h256::size).copyTo(m_remoteNonce.ref()); + transition(); + } + else + { + clog(NetConnect) << "p2p.connect.egress recving ack decrypt failed for " << m_socket->remoteEndpoint(); + m_nextState = Error; + transition(); + } + }); +} + +void RLPXHandshake::error() +{ + m_idleTimer.cancel(); + + auto connected = m_socket->isConnected(); + if (connected && !m_socket->remoteEndpoint().address().is_unspecified()) + clog(NetConnect) << "Disconnecting " << m_socket->remoteEndpoint() << " (Handshake Failed)"; + else + clog(NetConnect) << "Handshake Failed (Connection reset by peer)"; + + m_socket->close(); + if (m_io != nullptr) + delete m_io; +} + +void RLPXHandshake::transition(boost::system::error_code _ech) +{ + if (_ech || m_nextState == Error || m_cancel) + { + clog(NetConnect) << "Handshake Failed (I/O Error:" << _ech.message() << ")"; + return error(); + } + + auto self(shared_from_this()); + if (m_nextState == New) + { + m_nextState = AckAuth; + if (m_originated) + writeAuth(); + else + readAuth(); + } + else if (m_nextState == AckAuth) + { + m_nextState = WriteHello; + if (m_originated) + readAck(); + else + writeAck(); + } + else if (m_nextState == WriteHello) + { + m_nextState = ReadHello; + clog(NetConnect) << (m_originated ? "p2p.connect.egress" : "p2p.connect.ingress") << "sending capabilities handshake"; + + /// This pointer will be freed if there is an error otherwise + /// it will be passed to Host which will take ownership. + m_io = new RLPXFrameIO(*this); + + // old packet format + // 5 arguments, HelloPacket + RLPStream s; + s.append((unsigned)0).appendList(5) + << dev::p2p::c_protocolVersion + << m_host->m_clientVersion + << m_host->caps() + << m_host->listenPort() + << m_host->id(); + bytes packet; + s.swapOut(packet); + m_io->writeSingleFramePacket(&packet, m_handshakeOutBuffer); + ba::async_write(m_socket->ref(), ba::buffer(m_handshakeOutBuffer), [this, self](boost::system::error_code ec, std::size_t) + { + transition(ec); + }); + } + else if (m_nextState == ReadHello) + { + // Authenticate and decrypt initial hello frame with initial RLPXFrameIO + // and request m_host to start session. + m_nextState = StartSession; + + // read frame header + m_handshakeInBuffer.resize(h256::size); + ba::async_read(m_socket->ref(), boost::asio::buffer(m_handshakeInBuffer, h256::size), [this, self](boost::system::error_code ec, std::size_t) + { + if (ec) + transition(ec); + else + { + /// authenticate and decrypt header + if (!m_io->authAndDecryptHeader(bytesRef(m_handshakeInBuffer.data(), h256::size))) + { + m_nextState = Error; + transition(); + return; + } + + clog(NetNote) << (m_originated ? "p2p.connect.egress" : "p2p.connect.ingress") << "recvd hello header"; + + /// check frame size + bytes& header = m_handshakeInBuffer; + uint32_t frameSize = (uint32_t)(header[2]) | (uint32_t)(header[1])<<8 | (uint32_t)(header[0])<<16; + if (frameSize > 1024) + { + // all future frames: 16777216 + clog(NetWarn) << (m_originated ? "p2p.connect.egress" : "p2p.connect.ingress") << "hello frame is too large" << frameSize; + m_nextState = Error; + transition(); + return; + } + + /// rlp of header has protocol-type, sequence-id[, total-packet-size] + bytes headerRLP(header.size() - 3 - h128::size); + bytesConstRef(&header).cropped(3).copyTo(&headerRLP); + + /// read padded frame and mac + m_handshakeInBuffer.resize(frameSize + ((16 - (frameSize % 16)) % 16) + h128::size); + ba::async_read(m_socket->ref(), boost::asio::buffer(m_handshakeInBuffer, m_handshakeInBuffer.size()), [this, self, headerRLP](boost::system::error_code ec, std::size_t) + { + if (ec) + transition(ec); + else + { + bytesRef frame(&m_handshakeInBuffer); + if (!m_io->authAndDecryptFrame(frame)) + { + clog(NetWarn) << (m_originated ? "p2p.connect.egress" : "p2p.connect.ingress") << "hello frame: decrypt failed"; + m_nextState = Error; + transition(); + return; + } + + PacketType packetType = (PacketType)(frame[0] == 0x80 ? 0x0 : frame[0]); + if (packetType != 0) + { + clog(NetWarn) << (m_originated ? "p2p.connect.egress" : "p2p.connect.ingress") << "hello frame: invalid packet type"; + m_nextState = Error; + transition(); + return; + } + + clog(NetNote) << (m_originated ? "p2p.connect.egress" : "p2p.connect.ingress") << "hello frame: success. starting session."; + RLP rlp(frame.cropped(1), RLP::ThrowOnFail | RLP::FailIfTooSmall); + m_host->startPeerSession(m_remote, rlp, m_io, m_socket->remoteEndpoint()); + } + }); + } + }); + } + + m_idleTimer.expires_from_now(c_timeout); + m_idleTimer.async_wait([this, self](boost::system::error_code const& _ec) + { + if (!_ec) + { + if (!m_socket->remoteEndpoint().address().is_unspecified()) + clog(NetWarn) << "Disconnecting " << m_socket->remoteEndpoint() << " (Handshake Timeout)"; + cancel(); + } + }); +} diff --git a/libp2p/RLPxHandshake.h b/libp2p/RLPxHandshake.h new file mode 100644 index 000000000..47f6afb57 --- /dev/null +++ b/libp2p/RLPxHandshake.h @@ -0,0 +1,132 @@ +/* + 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 RLPXHandshake.h + * @author Alex Leverington + * @date 2015 + */ + + +#pragma once + +#include +#include +#include +#include "RLPxFrameIO.h" +#include "Common.h" +namespace ba = boost::asio; +namespace bi = boost::asio::ip; + +namespace dev +{ +namespace p2p +{ + +/** + * @brief Setup inbound or outbound connection for communication over RLPXFrameIO. + * RLPx Spec: https://github.com/ethereum/devp2p/blob/master/rlpx.md#encrypted-handshake + * + * @todo Implement StartSession transition via lambda which is passed to constructor. + * + * Thread Safety + * Distinct Objects: Safe. + * Shared objects: Unsafe. + */ +class RLPXHandshake: public std::enable_shared_from_this +{ + friend class RLPXFrameIO; + + /// Sequential states of handshake + enum State + { + Error = -1, + New, + AckAuth, + WriteHello, + ReadHello, + StartSession + }; + +public: + /// Setup incoming connection. + RLPXHandshake(Host* _host, std::shared_ptr const& _socket): m_host(_host), m_originated(false), m_socket(_socket), m_idleTimer(m_socket->ref().get_io_service()) { crypto::Nonce::get().ref().copyTo(m_nonce.ref()); } + + /// Setup outbound connection. + RLPXHandshake(Host* _host, std::shared_ptr const& _socket, NodeId _remote): m_host(_host), m_remote(_remote), m_originated(true), m_socket(_socket), m_idleTimer(m_socket->ref().get_io_service()) { crypto::Nonce::get().ref().copyTo(m_nonce.ref()); } + + ~RLPXHandshake() {} + + /// Start handshake. + void start() { transition(); } + + /// Cancels handshake preventing + void cancel() { m_cancel = true; } + +protected: + /// Write Auth message to socket and transitions to AckAuth. + void writeAuth(); + + /// Reads Auth message from socket and transitions to AckAuth. + void readAuth(); + + /// Write Ack message to socket and transitions to WriteHello. + void writeAck(); + + /// Reads Auth message from socket and transitions to WriteHello. + void readAck(); + + /// Closes connection and ends transitions. + void error(); + + /// Performs transition for m_nextState. + void transition(boost::system::error_code _ech = boost::system::error_code()); + + /// Timeout for remote to respond to transition events. Enforced by m_idleTimer and refreshed by transition(). + boost::posix_time::milliseconds const c_timeout = boost::posix_time::milliseconds(1800); + + State m_nextState = New; ///< Current or expected state of transition. + bool m_cancel = false; ///< Will be set to true if connection was canceled. + + Host* m_host; ///< Host which provides m_alias, protocolVersion(), m_clientVersion, caps(), and TCP listenPort(). + + /// Node id of remote host for socket. + NodeId m_remote; ///< Public address of remote host. + bool m_originated = false; ///< True if connection is outbound. + + /// Buffers for encoded and decoded handshake phases + bytes m_auth; ///< Plaintext of egress or ingress Auth message. + bytes m_authCipher; ///< Ciphertext of egress or ingress Auth message. + bytes m_ack; ///< Plaintext of egress or ingress Ack message. + bytes m_ackCipher; ///< Ciphertext of egress or ingress Ack message. + bytes m_handshakeOutBuffer; ///< Frame buffer for egress Hello packet. + bytes m_handshakeInBuffer; ///< Frame buffer for ingress Hello packet. + + crypto::ECDHE m_ecdhe; ///< Ephemeral ECDH secret and agreement. + h256 m_nonce; ///< Nonce generated by this host for handshake. + + Public m_remoteEphemeral; ///< Remote ephemeral public key. + h256 m_remoteNonce; ///< Nonce generated by remote host for handshake. + + /// Used to read and write RLPx encrypted frames for last step of handshake authentication. + /// Passed onto Host which will take ownership. + RLPXFrameIO* m_io = nullptr; + + std::shared_ptr m_socket; ///< Socket. + boost::asio::deadline_timer m_idleTimer; ///< Timer which enforces c_timeout. +}; + +} +} \ No newline at end of file diff --git a/libp2p/Session.cpp b/libp2p/Session.cpp index ec0a2d0d7..e00f56962 100644 --- a/libp2p/Session.cpp +++ b/libp2p/Session.cpp @@ -16,6 +16,7 @@ */ /** @file Session.cpp * @author Gav Wood + * @author Alex Leverington * @date 2014 */ @@ -37,18 +38,21 @@ using namespace dev::p2p; #endif #define clogS(X) dev::LogOutputStream(false) << "| " << std::setw(2) << m_socket.native_handle() << "] " -Session::Session(Host* _s, bi::tcp::socket _socket, std::shared_ptr const& _n): +Session::Session(Host* _s, RLPXFrameIO* _io, std::shared_ptr const& _n, PeerSessionInfo _info): m_server(_s), - m_socket(std::move(_socket)), + m_io(_io), + m_socket(m_io->socket()), m_peer(_n), - m_info({NodeId(), "?", m_socket.remote_endpoint().address().to_string(), 0, chrono::steady_clock::duration(0), CapDescSet(), 0, map()}), + m_info(_info), m_ping(chrono::steady_clock::time_point::max()) { + m_peer->m_lastDisconnect = NoDisconnect; m_lastReceived = m_connect = chrono::steady_clock::now(); } Session::~Session() { + clogS(NetMessageSummary) << "Closing Peer Session :-("; m_peer->m_lastConnected = m_peer->m_lastAttempted - chrono::seconds(1); // Read-chain finished for one reason or another. @@ -65,6 +69,7 @@ Session::~Session() } } catch (...){} + delete m_io; } NodeId Session::id() const @@ -72,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(); } } @@ -142,201 +149,111 @@ void Session::serviceNodesRequest() addNote("peers", "done"); } -bool Session::interpret(RLP const& _r) +bool Session::interpret(PacketType _t, RLP const& _r) { m_lastReceived = chrono::steady_clock::now(); - clogS(NetRight) << _r; + clogS(NetRight) << _t << _r; try // Generic try-catch block designed to capture RLP format errors - TODO: give decent diagnostics, make a bit more specific over what is caught. { - - switch ((PacketType)_r[0].toInt()) - { - case HelloPacket: - { - // TODO: P2P first pass, implement signatures. if signature fails, drop connection. if egress, flag node's endpoint as stale. - // Move auth to Host so we consolidate authentication logic and eschew peer deduplication logic. - // Move all node-lifecycle information into Host. - // Finalize peer-lifecycle properties vs node lifecycle. - - m_protocolVersion = _r[1].toInt(); - auto clientVersion = _r[2].toString(); - auto caps = _r[3].toVector(); - auto listenPort = _r[4].toInt(); - auto id = _r[5].toHash(); - - // clang error (previously: ... << hex << caps ...) - // "'operator<<' should be declared prior to the call site or in an associated namespace of one of its arguments" - stringstream capslog; - for (auto cap: caps) - capslog << "(" << cap.first << "," << dec << cap.second << ")"; - - clogS(NetMessageSummary) << "Hello: " << clientVersion << "V[" << m_protocolVersion << "]" << id.abridged() << showbase << capslog.str() << dec << listenPort; - - if (m_server->id() == id) - { - // Already connected. - clogS(NetWarn) << "Connected to ourself under a false pretext. We were told this peer was id" << id.abridged(); - disconnect(LocalIdentity); - return true; - } - - // if peer and connection have id, check for UnexpectedIdentity - if (!id) + switch (_t) { - disconnect(NullIdentity); - return true; - } - else if (!m_peer->id) - { - m_peer->id = id; - m_peer->endpoint.tcp.port(listenPort); - } - else if (m_peer->id != id) - { - // TODO p2p: FIXME. Host should catch this and reattempt adding node to table. - m_peer->id = id; - m_peer->m_score = 0; - m_peer->m_rating = 0; -// disconnect(UnexpectedIdentity); -// return true; - } - - if (m_server->havePeerSession(id)) + case DisconnectPacket: { - // Already connected. - clogS(NetWarn) << "Already connected to a peer with id" << id.abridged(); - // Possible that two nodes continually connect to each other with exact same timing. - this_thread::sleep_for(chrono::milliseconds(rand() % 100)); - disconnect(DuplicatePeer); - return true; - } - - if (m_peer->isOffline()) - m_peer->m_lastConnected = chrono::system_clock::now(); - - if (m_protocolVersion != m_server->protocolVersion()) - { - disconnect(IncompatibleProtocol); - return true; + string reason = "Unspecified"; + auto r = (DisconnectReason)_r[0].toInt(); + if (!_r[0].isInt()) + drop(BadProtocol); + else + { + reason = reasonOf(r); + clogS(NetMessageSummary) << "Disconnect (reason: " << reason << ")"; + drop(DisconnectRequested); + } + break; } - - m_info.clientVersion = clientVersion; - m_info.host = m_socket.remote_endpoint().address().to_string(); - m_info.port = listenPort; - m_info.lastPing = std::chrono::steady_clock::duration(); - m_info.caps = _r[3].toSet(); - m_info.socket = (unsigned)m_socket.native_handle(); - m_info.notes = map(); - - m_server->registerPeer(shared_from_this(), caps); - break; - } - case DisconnectPacket: - { - string reason = "Unspecified"; - auto r = (DisconnectReason)_r[1].toInt(); - if (!_r[1].isInt()) - drop(BadProtocol); - else + case PingPacket: { - reason = reasonOf(r); - clogS(NetMessageSummary) << "Disconnect (reason: " << reason << ")"; - drop(DisconnectRequested); + clogS(NetTriviaSummary) << "Ping"; + RLPStream s; + sealAndSend(prep(s, PongPacket)); + break; } - break; - } - case PingPacket: - { - clogS(NetTriviaSummary) << "Ping"; - RLPStream s; - sealAndSend(prep(s, PongPacket)); - break; - } - case PongPacket: - m_info.lastPing = std::chrono::steady_clock::now() - m_ping; - clogS(NetTriviaSummary) << "Latency: " << chrono::duration_cast(m_info.lastPing).count() << " ms"; - break; - case GetPeersPacket: - { - // Disabled for interop testing. - // GetPeers/PeersPacket will be modified to only exchange new nodes which it's peers are interested in. - break; - - clogS(NetTriviaSummary) << "GetPeers"; - m_theyRequestedNodes = true; - serviceNodesRequest(); - break; - } - case PeersPacket: - // Disabled for interop testing. - // GetPeers/PeersPacket will be modified to only exchange new nodes which it's peers are interested in. - break; - - clogS(NetTriviaSummary) << "Peers (" << dec << (_r.itemCount() - 1) << " entries)"; - m_weRequestedNodes = false; - for (unsigned i = 1; i < _r.itemCount(); ++i) - { - bi::address peerAddress; - if (_r[i][0].size() == 16) - peerAddress = bi::address_v6(_r[i][0].toHash>().asArray()); - else if (_r[i][0].size() == 4) - peerAddress = bi::address_v4(_r[i][0].toHash>().asArray()); - else + case PongPacket: + m_info.lastPing = std::chrono::steady_clock::now() - m_ping; + clogS(NetTriviaSummary) << "Latency: " << chrono::duration_cast(m_info.lastPing).count() << " ms"; + break; + case GetPeersPacket: + // Disabled for interop testing. + // GetPeers/PeersPacket will be modified to only exchange new nodes which it's peers are interested in. + break; + + clogS(NetTriviaSummary) << "GetPeers"; + m_theyRequestedNodes = true; + serviceNodesRequest(); + break; + case PeersPacket: + // Disabled for interop testing. + // GetPeers/PeersPacket will be modified to only exchange new nodes which it's peers are interested in. + break; + + clogS(NetTriviaSummary) << "Peers (" << dec << (_r.itemCount() - 1) << " entries)"; + m_weRequestedNodes = false; + for (unsigned i = 0; i < _r.itemCount(); ++i) { - cwarn << "Received bad peer packet:" << _r; - disconnect(BadProtocol); - return true; - } - auto ep = bi::tcp::endpoint(peerAddress, _r[i][1].toInt()); - NodeId id = _r[i][2].toHash(); - - clogS(NetAllDetail) << "Checking: " << ep << "(" << id.abridged() << ")"; -// clogS(NetAllDetail) << "Checking: " << ep << "(" << id.abridged() << ")" << isPrivateAddress(peerAddress) << this->id().abridged() << isPrivateAddress(endpoint().address()) << m_server->m_peers.count(id) << (m_server->m_peers.count(id) ? isPrivateAddress(m_server->m_peers.at(id)->address.address()) : -1); + bi::address peerAddress; + if (_r[i][0].size() == 16) + peerAddress = bi::address_v6(_r[i][0].toHash>().asArray()); + else if (_r[i][0].size() == 4) + peerAddress = bi::address_v4(_r[i][0].toHash>().asArray()); + else + { + cwarn << "Received bad peer packet:" << _r; + disconnect(BadProtocol); + return true; + } + auto ep = bi::tcp::endpoint(peerAddress, _r[i][1].toInt()); + NodeId id = _r[i][2].toHash(); - // todo: draft spec: ignore if dist(us,item) - dist(us,them) > 1 - - // TODO: isPrivate - if (!m_server->m_netPrefs.localNetworking && isPrivateAddress(peerAddress)) - goto CONTINUE; // Private address. Ignore. + clogS(NetAllDetail) << "Checking: " << ep << "(" << id.abridged() << ")"; - if (!id) - goto LAMEPEER; // Null identity. Ignore. + if (!isPublicAddress(peerAddress)) + goto CONTINUE; // Private address. Ignore. - if (m_server->id() == id) - goto LAMEPEER; // Just our info - we already have that. + if (!id) + goto LAMEPEER; // Null identity. Ignore. - if (id == this->id()) - goto LAMEPEER; // Just their info - we already have that. + if (m_server->id() == id) + goto LAMEPEER; // Just our info - we already have that. - if (!ep.port()) - goto LAMEPEER; // Zero port? Don't think so. + if (id == this->id()) + goto LAMEPEER; // Just their info - we already have that. - if (ep.port() >= /*49152*/32768) - goto LAMEPEER; // Private port according to IANA. + if (!ep.port()) + goto LAMEPEER; // Zero port? Don't think so. - // OK passed all our checks. Assume it's good. - addRating(1000); - m_server->addNode(id, ep.address().to_string(), ep.port(), ep.port()); - clogS(NetTriviaDetail) << "New peer: " << ep << "(" << id .abridged()<< ")"; - CONTINUE:; - LAMEPEER:; - } - break; - default: - { - auto id = _r[0].toInt(); - for (auto const& i: m_capabilities) - if (id >= i.second->m_idOffset && id - i.second->m_idOffset < i.second->hostCapability()->messageCount()) - { - if (i.second->m_enabled) - return i.second->interpret(id - i.second->m_idOffset, _r); - else - return true; + if (ep.port() >= /*49152*/32768) + goto LAMEPEER; // Private port according to IANA. + + // OK passed all our checks. Assume it's good. + addRating(1000); + m_server->addNode(id, ep.address(), ep.port(), ep.port()); + clogS(NetTriviaDetail) << "New peer: " << ep << "(" << id .abridged()<< ")"; + CONTINUE:; + LAMEPEER:; } - return false; - } - } + break; + default: + for (auto const& i: m_capabilities) + if (_t >= i.second->m_idOffset && _t - i.second->m_idOffset < i.second->hostCapability()->messageCount()) + { + if (i.second->m_enabled) + return i.second->interpret(_t - i.second->m_idOffset, _r); + else + return true; + } + return false; + } } catch (std::exception const& _e) { @@ -356,47 +273,34 @@ void Session::ping() RLPStream& Session::prep(RLPStream& _s, PacketType _id, unsigned _args) { - return prep(_s).appendList(_args + 1).append((unsigned)_id); -} - -RLPStream& Session::prep(RLPStream& _s) -{ - return _s.appendRaw(bytes(8, 0)); + return _s.append((unsigned)_id).appendList(_args); } void Session::sealAndSend(RLPStream& _s) { bytes b; _s.swapOut(b); - m_server->seal(b); send(move(b)); } bool Session::checkPacket(bytesConstRef _msg) { - if (_msg.size() < 8) + if (_msg.size() < 2) return false; - if (!(_msg[0] == 0x22 && _msg[1] == 0x40 && _msg[2] == 0x08 && _msg[3] == 0x91)) + if (_msg[0] > 0x7f) return false; - uint32_t len = ((_msg[4] * 256 + _msg[5]) * 256 + _msg[6]) * 256 + _msg[7]; - if (_msg.size() != len + 8) - return false; - RLP r(_msg.cropped(8)); - if (r.actualSize() != len) + RLP r(_msg.cropped(1)); + if (r.actualSize() + 1 != _msg.size()) return false; return true; } -void Session::send(bytesConstRef _msg) -{ - send(_msg.toBytes()); -} - void Session::send(bytes&& _msg) { - clogS(NetLeft) << RLP(bytesConstRef(&_msg).cropped(8)); + clogS(NetLeft) << RLP(bytesConstRef(&_msg).cropped(1)); - if (!checkPacket(bytesConstRef(&_msg))) + bytesConstRef msg(&_msg); + if (!checkPacket(msg)) clogS(NetWarn) << "INVALID PACKET CONSTRUCTED!"; if (!m_socket.is_open()) @@ -416,6 +320,7 @@ void Session::send(bytes&& _msg) void Session::write() { const bytes& bytes = m_writeQueue[0]; + m_io->writeSingleFramePacket(&bytes, m_writeQueue[0]); auto self(shared_from_this()); ba::async_write(m_socket, ba::buffer(bytes), [this, self](boost::system::error_code ec, std::size_t /*length*/) { @@ -451,16 +356,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; } @@ -484,95 +384,88 @@ void Session::disconnect(DisconnectReason _reason) void Session::start() { - RLPStream s; - prep(s, HelloPacket, 5) - << m_server->protocolVersion() - << m_server->m_clientVersion - << m_server->caps() - << m_server->m_tcpPublic.port() - << m_server->id(); - sealAndSend(s); ping(); doRead(); } void Session::doRead() { - // ignore packets received while waiting to disconnect + // ignore packets received while waiting to disconnect. if (m_dropped) return; - + auto self(shared_from_this()); - m_socket.async_read_some(boost::asio::buffer(m_data), [this,self](boost::system::error_code ec, std::size_t length) + ba::async_read(m_socket, boost::asio::buffer(m_data, h256::size), [this,self](boost::system::error_code ec, std::size_t length) { - // If error is end of file, ignore if (ec && ec.category() != boost::asio::error::get_misc_category() && ec.value() != boost::asio::error::eof) { - // got here with length of 1241... clogS(NetWarn) << "Error reading: " << ec.message(); drop(TCPError); } else if (ec && length == 0) - { return; - } else { - try + /// authenticate and decrypt header + bytesRef header(m_data.data(), h256::size); + if (!m_io->authAndDecryptHeader(header)) + { + clog(NetWarn) << "header decrypt failed"; + drop(BadProtocol); // todo: better error + return; + } + + /// check frame size + uint32_t frameSize = (m_data[0] * 256 + m_data[1]) * 256 + m_data[2]; + if (frameSize >= (uint32_t)1 << 24) { - m_incoming.resize(m_incoming.size() + length); - memcpy(m_incoming.data() + m_incoming.size() - length, m_data.data(), length); - while (m_incoming.size() > 8) + clog(NetWarn) << "frame size too large"; + drop(BadProtocol); + return; + } + + /// rlp of header has protocol-type, sequence-id[, total-packet-size] + bytes headerRLP(13); + bytesConstRef(m_data.data(), h128::size).cropped(3).copyTo(&headerRLP); + + /// read padded frame and mac + auto tlen = frameSize + ((16 - (frameSize % 16)) % 16) + h128::size; + ba::async_read(m_socket, boost::asio::buffer(m_data, tlen), [this, self, headerRLP, frameSize, tlen](boost::system::error_code ec, std::size_t length) + { + if (ec && ec.category() != boost::asio::error::get_misc_category() && ec.value() != boost::asio::error::eof) + { + clogS(NetWarn) << "Error reading: " << ec.message(); + drop(TCPError); + } + else if (ec && length == 0) + return; + else { - if (m_incoming[0] != 0x22 || m_incoming[1] != 0x40 || m_incoming[2] != 0x08 || m_incoming[3] != 0x91) + if (!m_io->authAndDecryptFrame(bytesRef(m_data.data(), tlen))) + { + clog(NetWarn) << "frame decrypt failed"; + drop(BadProtocol); // todo: better error + return; + } + + bytesConstRef frame(m_data.data(), frameSize); + if (!checkPacket(frame)) { - clogS(NetWarn) << "INVALID SYNCHRONISATION TOKEN; expected = 22400891; received = " << toHex(bytesConstRef(m_incoming.data(), 4)); + cerr << "Received " << frame.size() << ": " << toHex(frame) << endl; + clogS(NetWarn) << "INVALID MESSAGE RECEIVED"; disconnect(BadProtocol); return; } else { - uint32_t len = fromBigEndian(bytesConstRef(m_incoming.data() + 4, 4)); - uint32_t tlen = len + 8; - if (m_incoming.size() < tlen) - break; - - // enough has come in. - auto data = bytesConstRef(m_incoming.data(), tlen); - if (!checkPacket(data)) - { - cerr << "Received " << len << ": " << toHex(bytesConstRef(m_incoming.data() + 8, len)) << endl; - clogS(NetWarn) << "INVALID MESSAGE RECEIVED"; - disconnect(BadProtocol); - return; - } - else - { - RLP r(data.cropped(8)); - if (!interpret(r)) - { - // error - bad protocol - clogS(NetWarn) << "Couldn't interpret packet." << RLP(r); - // Just wasting our bandwidth - perhaps reduce rating? - //return; - } - } - memmove(m_incoming.data(), m_incoming.data() + tlen, m_incoming.size() - tlen); - m_incoming.resize(m_incoming.size() - tlen); + auto packetType = (PacketType)RLP(frame.cropped(0, 1)).toInt(); + RLP r(frame.cropped(1)); + if (!interpret(packetType, r)) + clogS(NetWarn) << "Couldn't interpret packet." << RLP(r); } + doRead(); } - doRead(); - } - catch (Exception const& _e) - { - clogS(NetWarn) << "ERROR: " << diagnostic_information(_e); - drop(BadProtocol); - } - catch (std::exception const& _e) - { - clogS(NetWarn) << "ERROR: " << _e.what(); - drop(BadProtocol); - } + }); } }); } diff --git a/libp2p/Session.h b/libp2p/Session.h index a5347cddb..95053d2a9 100644 --- a/libp2p/Session.h +++ b/libp2p/Session.h @@ -16,6 +16,7 @@ */ /** @file Session.h * @author Gav Wood + * @author Alex Leverington * @date 2014 */ @@ -32,6 +33,7 @@ #include #include #include +#include "RLPxHandshake.h" #include "Common.h" namespace dev @@ -52,7 +54,7 @@ class Session: public std::enable_shared_from_this friend class HostCapabilityFace; public: - Session(Host* _server, bi::tcp::socket _socket, std::shared_ptr const& _n); + Session(Host* _server, RLPXFrameIO* _io, std::shared_ptr const& _n, PeerSessionInfo _info); virtual ~Session(); void start(); @@ -63,19 +65,16 @@ public: bool isConnected() const { return m_socket.is_open(); } NodeId id() const; - unsigned socketId() const { return m_socket.native_handle(); } + unsigned socketId() const { return m_info.socket; } template std::shared_ptr cap() const { try { return std::static_pointer_cast(m_capabilities.at(std::make_pair(PeerCap::name(), PeerCap::version()))); } catch (...) { return nullptr; } } static RLPStream& prep(RLPStream& _s, PacketType _t, unsigned _args = 0); - static RLPStream& prep(RLPStream& _s); void sealAndSend(RLPStream& _s); - void send(bytes&& _msg); - void send(bytesConstRef _msg); 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; } @@ -85,6 +84,8 @@ public: void serviceNodesRequest(); private: + void send(bytes&& _msg); + /// Drop the connection for the reason @a _r. void drop(DisconnectReason _r); @@ -95,17 +96,18 @@ private: void write(); /// Interpret an incoming message. - bool interpret(RLP const& _r); + bool interpret(PacketType _t, RLP const& _r); /// @returns true iff the _msg forms a valid message for sending or receiving on the network. static bool checkPacket(bytesConstRef _msg); Host* m_server; ///< The host that owns us. Never null. - mutable bi::tcp::socket m_socket; ///< Socket for the peer's connection. Mutable to ask for native_handle(). + RLPXFrameIO* m_io; ///< Transport over which packets are sent. + bi::tcp::socket& m_socket; ///< Socket for the peer's connection. Mutex x_writeQueue; ///< Mutex for the write queue. std::deque m_writeQueue; ///< The write queue. - std::array m_data; ///< Buffer for ingress packet data. + std::array m_data; ///< Buffer for ingress packet data. bytes m_incoming; ///< Read buffer for ingress bytes. unsigned m_protocolVersion = 0; ///< The protocol version of the peer. diff --git a/libp2p/UDP.h b/libp2p/UDP.h index 3c3399acb..374f986b0 100644 --- a/libp2p/UDP.h +++ b/libp2p/UDP.h @@ -77,7 +77,7 @@ template struct RLPXDatagram: public RLPXDatagramFace { RLPXDatagram(bi::udp::endpoint const& _ep): RLPXDatagramFace(_ep) {} - static T fromBytesConstRef(bi::udp::endpoint const& _ep, bytesConstRef _bytes) { T t(_ep); t.interpretRLP(_bytes); return std::move(t); } + static T fromBytesConstRef(bi::udp::endpoint const& _ep, bytesConstRef _bytes) { try { T t(_ep); t.interpretRLP(_bytes); return std::move(t); } catch(...) { T t(_ep); return std::move(t); } } uint8_t packetType() { return T::type; } }; @@ -113,6 +113,10 @@ public: enum { maxDatagramSize = MaxDatagramSize }; static_assert(maxDatagramSize < 65507, "UDP datagrams cannot be larger than 65507 bytes"); + /// Create socket for specific endpoint. + UDPSocket(ba::io_service& _io, UDPSocketEvents& _host, bi::udp::endpoint _endpoint): m_host(_host), m_endpoint(_endpoint), m_socket(_io) { m_started.store(false); m_closed.store(true); }; + + /// Create socket which listens to all ports. UDPSocket(ba::io_service& _io, UDPSocketEvents& _host, unsigned _port): m_host(_host), m_endpoint(bi::udp::v4(), _port), m_socket(_io) { m_started.store(false); m_closed.store(true); }; virtual ~UDPSocket() { disconnect(); } @@ -159,7 +163,14 @@ void UDPSocket::connect() return; m_socket.open(bi::udp::v4()); - m_socket.bind(m_endpoint); + try + { + m_socket.bind(m_endpoint); + } + catch (...) + { + m_socket.bind(bi::udp::endpoint(bi::udp::v4(), m_endpoint.port())); + } // clear write queue so reconnect doesn't send stale messages Guard l(x_sendQ); @@ -192,7 +203,10 @@ void UDPSocket::doRead() auto self(UDPSocket::shared_from_this()); m_socket.async_receive_from(boost::asio::buffer(m_recvData), m_recvEndpoint, [this, self](boost::system::error_code _ec, size_t _len) { - if (_ec) + // ASIO Safety: It is possible that ASIO will call lambda w/o an error + // and after the socket has been disconnected. Checking m_closed + // guarantees that m_host will not be called after disconnect(). + if (_ec || m_closed) return disconnectWithError(_ec); assert(_len); @@ -211,7 +225,7 @@ void UDPSocket::doWrite() auto self(UDPSocket::shared_from_this()); m_socket.async_send_to(boost::asio::buffer(datagram.data), datagram.endpoint(), [this, self](boost::system::error_code _ec, std::size_t) { - if (_ec) + if (_ec || m_closed) return disconnectWithError(_ec); else { diff --git a/libsolidity/AST.cpp b/libsolidity/AST.cpp index 09c03ef6e..ada4a01d1 100644 --- a/libsolidity/AST.cpp +++ b/libsolidity/AST.cpp @@ -21,6 +21,7 @@ */ #include +#include #include #include #include @@ -52,6 +53,7 @@ void ContractDefinition::checkTypeRequirements() baseSpecifier->checkTypeRequirements(); checkIllegalOverrides(); + checkAbstractFunctions(); FunctionDefinition const* constructor = getConstructor(); if (constructor && !constructor->getReturnParameters().empty()) @@ -60,6 +62,7 @@ void ContractDefinition::checkTypeRequirements() FunctionDefinition const* fallbackFunction = nullptr; for (ASTPointer const& function: getDefinedFunctions()) + { if (function->getName().empty()) { if (fallbackFunction) @@ -71,6 +74,9 @@ void ContractDefinition::checkTypeRequirements() BOOST_THROW_EXCEPTION(fallbackFunction->getParameterList().createTypeError("Fallback function cannot take parameters.")); } } + if (!function->isFullyImplemented()) + setFullyImplemented(false); + } for (ASTPointer const& modifier: getFunctionModifiers()) modifier->checkTypeRequirements(); @@ -98,7 +104,7 @@ void ContractDefinition::checkTypeRequirements() if (hashes.count(hash)) BOOST_THROW_EXCEPTION(createTypeError( std::string("Function signature hash collision for ") + - it.second->getCanonicalSignature())); + it.second->externalSignature())); hashes.insert(hash); } } @@ -134,6 +140,28 @@ FunctionDefinition const* ContractDefinition::getFallbackFunction() const return nullptr; } +void ContractDefinition::checkAbstractFunctions() +{ + map functions; + + // Search from base to derived + for (ContractDefinition const* contract: boost::adaptors::reverse(getLinearizedBaseContracts())) + for (ASTPointer const& function: contract->getDefinedFunctions()) + { + string const& name = function->getName(); + if (!function->isFullyImplemented() && functions.count(name) && functions[name]) + BOOST_THROW_EXCEPTION(function->createTypeError("Redeclaring an already implemented function as abstract")); + functions[name] = function->isFullyImplemented(); + } + + for (auto const& it: functions) + if (!it.second) + { + setFullyImplemented(false); + break; + } +} + void ContractDefinition::checkIllegalOverrides() const { // TODO unify this at a later point. for this we need to put the constness and the access specifier @@ -203,8 +231,8 @@ vector, FunctionTypePointer>> const& ContractDefinition::getIn { for (ASTPointer const& f: contract->getDefinedFunctions()) { - string functionSignature = f->getCanonicalSignature(); - if (f->isPublic() && !f->isConstructor() && !f->getName().empty() && signaturesSeen.count(functionSignature) == 0) + string functionSignature = f->externalSignature(); + if (f->isPartOfExternalInterface() && signaturesSeen.count(functionSignature) == 0) { functionsSeen.insert(f->getName()); signaturesSeen.insert(functionSignature); @@ -214,11 +242,12 @@ vector, FunctionTypePointer>> const& ContractDefinition::getIn } for (ASTPointer const& v: contract->getStateVariables()) - if (v->isPublic() && functionsSeen.count(v->getName()) == 0) + if (functionsSeen.count(v->getName()) == 0 && v->isPartOfExternalInterface()) { FunctionType ftype(*v); + solAssert(v->getType().get(), ""); functionsSeen.insert(v->getName()); - FixedHash<4> hash(dev::sha3(ftype.getCanonicalSignature(v->getName()))); + FixedHash<4> hash(dev::sha3(ftype.externalSignature(v->getName()))); m_interfaceFunctionList->push_back(make_pair(hash, make_shared(*v))); } } @@ -322,25 +351,35 @@ TypePointer FunctionDefinition::getType(ContractDefinition const*) const void FunctionDefinition::checkTypeRequirements() { for (ASTPointer const& var: getParameters() + getReturnParameters()) + { if (!var->getType()->canLiveOutsideStorage()) BOOST_THROW_EXCEPTION(var->createTypeError("Type is required to live outside storage.")); + if (getVisibility() >= Visibility::Public && !(var->getType()->externalType())) + { + // todo delete when will be implemented arrays as parameter type in internal functions + if (getVisibility() == Visibility::Public && var->getType()->getCategory() == Type::Category::Array) + BOOST_THROW_EXCEPTION(var->createTypeError("Arrays only implemented for external functions.")); + else + BOOST_THROW_EXCEPTION(var->createTypeError("Internal type is not allowed for public and external functions.")); + } + } for (ASTPointer const& modifier: m_functionModifiers) modifier->checkTypeRequirements(isConstructor() ? dynamic_cast(*getScope()).getBaseContracts() : vector>()); - - m_body->checkTypeRequirements(); + if (m_body) + m_body->checkTypeRequirements(); } -string FunctionDefinition::getCanonicalSignature() const +string FunctionDefinition::externalSignature() const { - return FunctionType(*this).getCanonicalSignature(getName()); + return FunctionType(*this).externalSignature(getName()); } bool VariableDeclaration::isLValue() const { - // External function parameters are Read-Only - return !isExternalFunctionParameter(); + // External function parameters and constant declared variables are Read-Only + return !isExternalFunctionParameter() && !m_isConstant; } void VariableDeclaration::checkTypeRequirements() @@ -349,10 +388,24 @@ void VariableDeclaration::checkTypeRequirements() // sets the type. // Note that assignments before the first declaration are legal because of the special scoping // rules inherited from JavaScript. + if (m_isConstant) + { + if (!dynamic_cast(getScope())) + BOOST_THROW_EXCEPTION(createTypeError("Illegal use of \"constant\" specifier.")); + if ((m_type && !m_type->isValueType()) || !m_value) + BOOST_THROW_EXCEPTION(createTypeError("Unitialized \"constant\" variable.")); + } if (!m_value) return; if (m_type) + { m_value->expectType(*m_type); + if (m_isStateVariable && !m_type->externalType() && getVisibility() >= Visibility::Public) + BOOST_THROW_EXCEPTION(createTypeError("Internal type is not allowed for state variables.")); + + if (!FunctionType(*this).externalType()) + BOOST_THROW_EXCEPTION(createTypeError("Internal type is not allowed for public state variables.")); + } else { // no type declared and no previous assignment, infer the type @@ -437,6 +490,8 @@ void EventDefinition::checkTypeRequirements() numIndexed++; if (!var->getType()->canLiveOutsideStorage()) BOOST_THROW_EXCEPTION(var->createTypeError("Type is required to live outside storage.")); + if (!var->getType()->externalType()) + BOOST_THROW_EXCEPTION(var->createTypeError("Internal type is not allowed as event parameter type.")); } if (numIndexed > 3) BOOST_THROW_EXCEPTION(createTypeError("More than 3 indexed arguments for event.")); @@ -681,6 +736,8 @@ void NewExpression::checkTypeRequirements() m_contract = dynamic_cast(m_contractName->getReferencedDeclaration()); if (!m_contract) BOOST_THROW_EXCEPTION(createTypeError("Identifier is not a contract.")); + if (!m_contract->isFullyImplemented()) + BOOST_THROW_EXCEPTION(m_contract->createTypeError("Trying to create an instance of an abstract contract.")); shared_ptr contractType = make_shared(*m_contract); TypePointers const& parameterTypes = contractType->getConstructorType()->getParameterTypes(); m_type = make_shared(parameterTypes, TypePointers{contractType}, @@ -720,7 +777,7 @@ void IndexAccess::checkTypeRequirements() BOOST_THROW_EXCEPTION(createTypeError("Index expression cannot be omitted.")); m_index->expectType(IntegerType(256)); if (type.isByteArray()) - m_type = make_shared(8, IntegerType::Modifier::Hash); + m_type = make_shared(1); else m_type = type.getBaseType(); m_isLValue = type.getLocation() != ArrayType::Location::CallData; diff --git a/libsolidity/AST.h b/libsolidity/AST.h index 6cc7f742f..fd50eb956 100644 --- a/libsolidity/AST.h +++ b/libsolidity/AST.h @@ -156,6 +156,7 @@ public: /// contract types. virtual TypePointer getType(ContractDefinition const* m_currentContract = nullptr) const = 0; virtual bool isLValue() const { return false; } + virtual bool isPartOfExternalInterface() const { return false; }; protected: virtual Visibility getDefaultVisibility() const { return Visibility::Public; } @@ -195,6 +196,22 @@ protected: ASTPointer m_documentation; }; +/** + * Abstract class that is added to AST nodes that can be marked as not being fully implemented + */ +class ImplementationOptional +{ +public: + explicit ImplementationOptional(bool _implemented): m_implemented(_implemented) {} + + /// @return whether this node is fully implemented or not + bool isFullyImplemented() const { return m_implemented; } + void setFullyImplemented(bool _implemented) { m_implemented = _implemented; } + +protected: + bool m_implemented; +}; + /// @} /** @@ -202,20 +219,24 @@ protected: * document order. It first visits all struct declarations, then all variable declarations and * finally all function declarations. */ -class ContractDefinition: public Declaration, public Documented +class ContractDefinition: public Declaration, public Documented, public ImplementationOptional { public: - ContractDefinition(SourceLocation const& _location, - ASTPointer const& _name, - ASTPointer const& _documentation, - std::vector> const& _baseContracts, - std::vector> const& _definedStructs, - std::vector> const& _definedEnums, - std::vector> const& _stateVariables, - std::vector> const& _definedFunctions, - std::vector> const& _functionModifiers, - std::vector> const& _events): - Declaration(_location, _name), Documented(_documentation), + ContractDefinition( + SourceLocation const& _location, + ASTPointer const& _name, + ASTPointer const& _documentation, + std::vector> const& _baseContracts, + std::vector> const& _definedStructs, + std::vector> const& _definedEnums, + std::vector> const& _stateVariables, + std::vector> const& _definedFunctions, + std::vector> const& _functionModifiers, + std::vector> const& _events + ): + Declaration(_location, _name), + Documented(_documentation), + ImplementationOptional(true), m_baseContracts(_baseContracts), m_definedStructs(_definedStructs), m_definedEnums(_definedEnums), @@ -262,6 +283,7 @@ public: private: void checkIllegalOverrides() const; + void checkAbstractFunctions(); std::vector, FunctionTypePointer>> const& getInterfaceFunctionList() const; @@ -377,24 +399,29 @@ private: std::vector> m_parameters; }; -class FunctionDefinition: public Declaration, public VariableScope, public Documented +class FunctionDefinition: public Declaration, public VariableScope, public Documented, public ImplementationOptional { public: - FunctionDefinition(SourceLocation const& _location, ASTPointer const& _name, - Declaration::Visibility _visibility, bool _isConstructor, - ASTPointer const& _documentation, - ASTPointer const& _parameters, - bool _isDeclaredConst, - std::vector> const& _modifiers, - ASTPointer const& _returnParameters, - ASTPointer const& _body): - Declaration(_location, _name, _visibility), Documented(_documentation), - m_isConstructor(_isConstructor), - m_parameters(_parameters), - m_isDeclaredConst(_isDeclaredConst), - m_functionModifiers(_modifiers), - m_returnParameters(_returnParameters), - m_body(_body) + FunctionDefinition( + SourceLocation const& _location, + ASTPointer const& _name, + Declaration::Visibility _visibility, bool _isConstructor, + ASTPointer const& _documentation, + ASTPointer const& _parameters, + bool _isDeclaredConst, + std::vector> const& _modifiers, + ASTPointer const& _returnParameters, + ASTPointer const& _body + ): + Declaration(_location, _name, _visibility), + Documented(_documentation), + ImplementationOptional(_body != nullptr), + m_isConstructor(_isConstructor), + m_parameters(_parameters), + m_isDeclaredConst(_isDeclaredConst), + m_functionModifiers(_modifiers), + m_returnParameters(_returnParameters), + m_body(_body) {} virtual void accept(ASTVisitor& _visitor) override; @@ -415,14 +442,15 @@ public: getVisibility() >= Visibility::Internal; } virtual TypePointer getType(ContractDefinition const*) const override; + virtual bool isPartOfExternalInterface() const override { return isPublic() && !m_isConstructor && !getName().empty(); } /// Checks that all parameters have allowed types and calls checkTypeRequirements on the body. void checkTypeRequirements(); - /// @returns the canonical signature of the function + /// @returns the external signature of the function /// That consists of the name of the function followed by the types of the /// arguments separated by commas all enclosed in parentheses without any spaces. - std::string getCanonicalSignature() const; + std::string externalSignature() const; private: bool m_isConstructor; @@ -440,13 +468,23 @@ private: class VariableDeclaration: public Declaration { public: - VariableDeclaration(SourceLocation const& _location, ASTPointer const& _type, - ASTPointer const& _name, ASTPointer _value, - Visibility _visibility, - bool _isStateVar = false, bool _isIndexed = false): - Declaration(_location, _name, _visibility), - m_typeName(_type), m_value(_value), - m_isStateVariable(_isStateVar), m_isIndexed(_isIndexed) {} + VariableDeclaration( + SourceLocation const& _location, + ASTPointer const& _type, + ASTPointer const& _name, + ASTPointer _value, + Visibility _visibility, + bool _isStateVar = false, + bool _isIndexed = false, + bool _isConstant = false + ): + Declaration(_location, _name, _visibility), + m_typeName(_type), + m_value(_value), + m_isStateVariable(_isStateVar), + m_isIndexed(_isIndexed), + m_isConstant(_isConstant){} + virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTConstVisitor& _visitor) const override; @@ -459,21 +497,24 @@ public: void setType(std::shared_ptr const& _type) { m_type = _type; } virtual bool isLValue() const override; + virtual bool isPartOfExternalInterface() const override { return isPublic() && !m_isConstant; } void checkTypeRequirements(); bool isLocalVariable() const { return !!dynamic_cast(getScope()); } bool isExternalFunctionParameter() const; bool isStateVariable() const { return m_isStateVariable; } bool isIndexed() const { return m_isIndexed; } + bool isConstant() const { return m_isConstant; } protected: Visibility getDefaultVisibility() const override { return Visibility::Internal; } private: ASTPointer m_typeName; ///< can be empty ("var") - ASTPointer m_value; ///< the assigned value, can be missing + ASTPointer m_value; ///< the assigned value, can be missing bool m_isStateVariable; ///< Whether or not this is a contract state variable bool m_isIndexed; ///< Whether this is an indexed variable (used by events). + bool m_isConstant; ///< Whether the variable is a compile-time constant. std::shared_ptr m_type; ///< derived type, initially empty }; @@ -538,17 +579,24 @@ private: class EventDefinition: public Declaration, public VariableScope, public Documented { public: - EventDefinition(SourceLocation const& _location, - ASTPointer const& _name, - ASTPointer const& _documentation, - ASTPointer const& _parameters): - Declaration(_location, _name), Documented(_documentation), m_parameters(_parameters) {} + EventDefinition( + SourceLocation const& _location, + ASTPointer const& _name, + ASTPointer const& _documentation, + ASTPointer const& _parameters, + bool _anonymous = false + ): + Declaration(_location, _name), + Documented(_documentation), + m_parameters(_parameters), + m_anonymous(_anonymous){} virtual void accept(ASTVisitor& _visitor) override; virtual void accept(ASTConstVisitor& _visitor) const override; std::vector> const& getParameters() const { return m_parameters->getParameters(); } ParameterList const& getParameterList() const { return *m_parameters; } + bool isAnonymous() const { return m_anonymous; } virtual TypePointer getType(ContractDefinition const* = nullptr) const override { @@ -559,6 +607,7 @@ public: private: ASTPointer m_parameters; + bool m_anonymous = false; }; /** diff --git a/libsolidity/AST_accept.h b/libsolidity/AST_accept.h index 81ede8fc9..3557f8779 100644 --- a/libsolidity/AST_accept.h +++ b/libsolidity/AST_accept.h @@ -175,7 +175,8 @@ void FunctionDefinition::accept(ASTVisitor& _visitor) if (m_returnParameters) m_returnParameters->accept(_visitor); listAccept(m_functionModifiers, _visitor); - m_body->accept(_visitor); + if (m_body) + m_body->accept(_visitor); } _visitor.endVisit(*this); } @@ -188,7 +189,8 @@ void FunctionDefinition::accept(ASTConstVisitor& _visitor) const if (m_returnParameters) m_returnParameters->accept(_visitor); listAccept(m_functionModifiers, _visitor); - m_body->accept(_visitor); + if (m_body) + m_body->accept(_visitor); } _visitor.endVisit(*this); } diff --git a/libsolidity/ArrayUtils.cpp b/libsolidity/ArrayUtils.cpp index f0d7d6a81..58031390f 100644 --- a/libsolidity/ArrayUtils.cpp +++ b/libsolidity/ArrayUtils.cpp @@ -34,8 +34,10 @@ using namespace solidity; void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType const& _sourceType) const { - // stack layout: [source_ref] target_ref (top) - // need to leave target_ref on the stack at the end + // this copies source to target and also clears target if it was larger + // need to leave "target_ref target_byte_off" on the stack at the end + + // stack layout: [source_ref] [source_byte_off] [source length] target_ref target_byte_off (top) solAssert(_targetType.getLocation() == ArrayType::Location::Storage, ""); solAssert( _sourceType.getLocation() == ArrayType::Location::CallData || @@ -47,14 +49,26 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons Type const* targetBaseType = _targetType.isByteArray() ? &uint256 : &(*_targetType.getBaseType()); Type const* sourceBaseType = _sourceType.isByteArray() ? &uint256 : &(*_sourceType.getBaseType()); - // this copies source to target and also clears target if it was larger - // TODO unroll loop for small sizes - // stack: source_ref [source_length] target_ref + bool sourceIsStorage = _sourceType.getLocation() == ArrayType::Location::Storage; + bool directCopy = sourceIsStorage && sourceBaseType->isValueType() && *sourceBaseType == *targetBaseType; + bool haveByteOffsetSource = !directCopy && sourceIsStorage && sourceBaseType->getStorageBytes() <= 16; + bool haveByteOffsetTarget = !directCopy && targetBaseType->getStorageBytes() <= 16; + unsigned byteOffsetSize = (haveByteOffsetSource ? 1 : 0) + (haveByteOffsetTarget ? 1 : 0); + + // stack: source_ref [source_byte_off] [source_length] target_ref target_byte_off // store target_ref + // arrays always start at zero byte offset, pop offset + m_context << eth::Instruction::POP; for (unsigned i = _sourceType.getSizeOnStack(); i > 0; --i) m_context << eth::swapInstruction(i); + // stack: target_ref source_ref [source_byte_off] [source_length] + if (sourceIsStorage) + // arrays always start at zero byte offset, pop offset + m_context << eth::Instruction::POP; + // stack: target_ref source_ref [source_length] + // retrieve source length if (_sourceType.getLocation() != ArrayType::Location::CallData || !_sourceType.isDynamicallySized()) retrieveLength(_sourceType); // otherwise, length is already there // stack: target_ref source_ref source_length @@ -73,6 +87,7 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons m_context << eth::Instruction::POP << eth::Instruction::POP << eth::Instruction::POP << eth::Instruction::POP; + m_context << u256(0); return; } // compute hashes (data positions) @@ -88,8 +103,8 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons // stack: target_ref target_data_end source_length target_data_pos source_ref // skip copying if source length is zero m_context << eth::Instruction::DUP3 << eth::Instruction::ISZERO; - eth::AssemblyItem copyLoopEnd = m_context.newTag(); - m_context.appendConditionalJumpTo(copyLoopEnd); + eth::AssemblyItem copyLoopEndWithoutByteOffset = m_context.newTag(); + m_context.appendConditionalJumpTo(copyLoopEndWithoutByteOffset); if (_sourceType.getLocation() == ArrayType::Location::Storage && _sourceType.isDynamicallySized()) CompilerUtils(m_context).computeHashStatic(); @@ -98,88 +113,172 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons convertLengthToSize(_sourceType); m_context << eth::Instruction::DUP3 << eth::Instruction::ADD; // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end + if (haveByteOffsetTarget) + m_context << u256(0); + if (haveByteOffsetSource) + m_context << u256(0); + // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] eth::AssemblyItem copyLoopStart = m_context.newTag(); m_context << copyLoopStart; // check for loop condition m_context - << eth::Instruction::DUP3 << eth::Instruction::DUP2 + << eth::dupInstruction(3 + byteOffsetSize) << eth::dupInstruction(2 + byteOffsetSize) << eth::Instruction::GT << eth::Instruction::ISZERO; + eth::AssemblyItem copyLoopEnd = m_context.newTag(); m_context.appendConditionalJumpTo(copyLoopEnd); - // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end + // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] // copy if (sourceBaseType->getCategory() == Type::Category::Array) { - m_context << eth::Instruction::DUP3 << eth::Instruction::DUP3; + solAssert(byteOffsetSize == 0, "Byte offset for array as base type."); + m_context << eth::Instruction::DUP3; + if (sourceIsStorage) + m_context << u256(0); + m_context << eth::dupInstruction(sourceIsStorage ? 4 : 3) << u256(0); copyArrayToStorage( dynamic_cast(*targetBaseType), dynamic_cast(*sourceBaseType) ); - m_context << eth::Instruction::POP; + m_context << eth::Instruction::POP << eth::Instruction::POP; + } + else if (directCopy) + { + solAssert(byteOffsetSize == 0, "Byte offset for direct copy."); + m_context + << eth::Instruction::DUP3 << eth::Instruction::SLOAD + << eth::Instruction::DUP3 << eth::Instruction::SSTORE; } else { - m_context << eth::Instruction::DUP3; + // Note that we have to copy each element on its own in case conversion is involved. + // We might copy too much if there is padding at the last element, but this way end + // checking is easier. + // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] + m_context << eth::dupInstruction(3 + byteOffsetSize); if (_sourceType.getLocation() == ArrayType::Location::Storage) + { + if (haveByteOffsetSource) + m_context << eth::Instruction::DUP2; + else + m_context << u256(0); StorageItem(m_context, *sourceBaseType).retrieveValue(SourceLocation(), true); + } else if (sourceBaseType->isValueType()) CompilerUtils(m_context).loadFromMemoryDynamic(*sourceBaseType, true, true, false); else solAssert(false, "Copying of unknown type requested: " + sourceBaseType->toString()); - m_context << eth::dupInstruction(2 + sourceBaseType->getSizeOnStack()); + // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] ... + solAssert(2 + byteOffsetSize + sourceBaseType->getSizeOnStack() <= 16, "Stack too deep."); + // fetch target storage reference + m_context << eth::dupInstruction(2 + byteOffsetSize + sourceBaseType->getSizeOnStack()); + if (haveByteOffsetTarget) + m_context << eth::dupInstruction(1 + byteOffsetSize + sourceBaseType->getSizeOnStack()); + else + m_context << u256(0); StorageItem(m_context, *targetBaseType).storeValue(*sourceBaseType, SourceLocation(), true); } + // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] // increment source - m_context - << eth::Instruction::SWAP2 - << (_sourceType.getLocation() == ArrayType::Location::Storage ? - sourceBaseType->getStorageSize() : - sourceBaseType->getCalldataEncodedSize()) - << eth::Instruction::ADD - << eth::Instruction::SWAP2; + if (haveByteOffsetSource) + incrementByteOffset(sourceBaseType->getStorageBytes(), 1, haveByteOffsetTarget ? 5 : 4); + else + m_context + << eth::swapInstruction(2 + byteOffsetSize) + << (sourceIsStorage ? sourceBaseType->getStorageSize() : sourceBaseType->getCalldataEncodedSize()) + << eth::Instruction::ADD + << eth::swapInstruction(2 + byteOffsetSize); // increment target - m_context - << eth::Instruction::SWAP1 - << targetBaseType->getStorageSize() - << eth::Instruction::ADD - << eth::Instruction::SWAP1; + if (haveByteOffsetTarget) + incrementByteOffset(targetBaseType->getStorageBytes(), byteOffsetSize, byteOffsetSize + 2); + else + m_context + << eth::swapInstruction(1 + byteOffsetSize) + << targetBaseType->getStorageSize() + << eth::Instruction::ADD + << eth::swapInstruction(1 + byteOffsetSize); m_context.appendJumpTo(copyLoopStart); m_context << copyLoopEnd; + if (haveByteOffsetTarget) + { + // clear elements that might be left over in the current slot in target + // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end target_byte_offset [source_byte_offset] + m_context << eth::dupInstruction(byteOffsetSize) << eth::Instruction::ISZERO; + eth::AssemblyItem copyCleanupLoopEnd = m_context.appendConditionalJump(); + m_context << eth::dupInstruction(2 + byteOffsetSize) << eth::dupInstruction(1 + byteOffsetSize); + StorageItem(m_context, *targetBaseType).setToZero(SourceLocation(), true); + incrementByteOffset(targetBaseType->getStorageBytes(), byteOffsetSize, byteOffsetSize + 2); + m_context.appendJumpTo(copyLoopEnd); + + m_context << copyCleanupLoopEnd; + m_context << eth::Instruction::POP; // might pop the source, but then target is popped next + } + if (haveByteOffsetSource) + m_context << eth::Instruction::POP; + m_context << copyLoopEndWithoutByteOffset; // zero-out leftovers in target - // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end + // stack: target_ref target_data_end source_data_pos target_data_pos_updated source_data_end m_context << eth::Instruction::POP << eth::Instruction::SWAP1 << eth::Instruction::POP; // stack: target_ref target_data_end target_data_pos_updated clearStorageLoop(*targetBaseType); m_context << eth::Instruction::POP; + m_context << u256(0); } void ArrayUtils::clearArray(ArrayType const& _type) const { + unsigned stackHeightStart = m_context.getStackHeight(); solAssert(_type.getLocation() == ArrayType::Location::Storage, ""); + if (_type.getBaseType()->getStorageBytes() < 32) + { + solAssert(_type.getBaseType()->isValueType(), "Invalid storage size for non-value type."); + solAssert(_type.getBaseType()->getStorageSize() <= 1, "Invalid storage size for type."); + } + if (_type.getBaseType()->isValueType()) + solAssert(_type.getBaseType()->getStorageSize() <= 1, "Invalid size for value type."); + + m_context << eth::Instruction::POP; // remove byte offset if (_type.isDynamicallySized()) clearDynamicArray(_type); else if (_type.getLength() == 0 || _type.getBaseType()->getCategory() == Type::Category::Mapping) m_context << eth::Instruction::POP; - else if (_type.getLength() < 5) // unroll loop for small arrays @todo choose a good value + else if (_type.getBaseType()->isValueType() && _type.getStorageSize() <= 5) { - solAssert(!_type.isByteArray(), ""); + // unroll loop for small arrays @todo choose a good value + // Note that we loop over storage slots here, not elements. + for (unsigned i = 1; i < _type.getStorageSize(); ++i) + m_context + << u256(0) << eth::Instruction::DUP2 << eth::Instruction::SSTORE + << u256(1) << eth::Instruction::ADD; + m_context << u256(0) << eth::Instruction::SWAP1 << eth::Instruction::SSTORE; + } + else if (!_type.getBaseType()->isValueType() && _type.getLength() <= 4) + { + // unroll loop for small arrays @todo choose a good value + solAssert(_type.getBaseType()->getStorageBytes() >= 32, "Invalid storage size."); for (unsigned i = 1; i < _type.getLength(); ++i) { + m_context << u256(0); StorageItem(m_context, *_type.getBaseType()).setToZero(SourceLocation(), false); - m_context << u256(_type.getBaseType()->getStorageSize()) << eth::Instruction::ADD; + m_context + << eth::Instruction::POP + << u256(_type.getBaseType()->getStorageSize()) << eth::Instruction::ADD; } + m_context << u256(0); StorageItem(m_context, *_type.getBaseType()).setToZero(SourceLocation(), true); } else { - solAssert(!_type.isByteArray(), ""); - m_context - << eth::Instruction::DUP1 << u256(_type.getLength()) - << u256(_type.getBaseType()->getStorageSize()) - << eth::Instruction::MUL << eth::Instruction::ADD << eth::Instruction::SWAP1; - clearStorageLoop(*_type.getBaseType()); + m_context << eth::Instruction::DUP1 << _type.getLength(); + convertLengthToSize(_type); + m_context << eth::Instruction::ADD << eth::Instruction::SWAP1; + if (_type.getBaseType()->getStorageBytes() < 32) + clearStorageLoop(IntegerType(256)); + else + clearStorageLoop(*_type.getBaseType()); m_context << eth::Instruction::POP; } + solAssert(m_context.getStackHeight() == stackHeightStart - 2, ""); } void ArrayUtils::clearDynamicArray(ArrayType const& _type) const @@ -187,6 +286,7 @@ void ArrayUtils::clearDynamicArray(ArrayType const& _type) const solAssert(_type.getLocation() == ArrayType::Location::Storage, ""); solAssert(_type.isDynamicallySized(), ""); + unsigned stackHeightStart = m_context.getStackHeight(); // fetch length m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD; // set length to zero @@ -196,23 +296,27 @@ void ArrayUtils::clearDynamicArray(ArrayType const& _type) const // compute data positions m_context << eth::Instruction::SWAP1; CompilerUtils(m_context).computeHashStatic(); - // stack: len data_pos (len is in slots for byte array and in items for other arrays) + // stack: len data_pos m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2 << eth::Instruction::ADD << eth::Instruction::SWAP1; // stack: data_pos_end data_pos - if (_type.isByteArray()) + if (_type.isByteArray() || _type.getBaseType()->getStorageBytes() < 32) clearStorageLoop(IntegerType(256)); else clearStorageLoop(*_type.getBaseType()); // cleanup m_context << eth::Instruction::POP; + solAssert(m_context.getStackHeight() == stackHeightStart - 1, ""); } void ArrayUtils::resizeDynamicArray(const ArrayType& _type) const { solAssert(_type.getLocation() == ArrayType::Location::Storage, ""); solAssert(_type.isDynamicallySized(), ""); + if (!_type.isByteArray() && _type.getBaseType()->getStorageBytes() < 32) + solAssert(_type.getBaseType()->isValueType(), "Invalid storage size for non-value type."); + unsigned stackHeightStart = m_context.getStackHeight(); eth::AssemblyItem resizeEnd = m_context.newTag(); // stack: ref new_length @@ -240,7 +344,7 @@ void ArrayUtils::resizeDynamicArray(const ArrayType& _type) const // stack: ref new_length data_pos new_size delete_end m_context << eth::Instruction::SWAP2 << eth::Instruction::ADD; // stack: ref new_length delete_end delete_start - if (_type.isByteArray()) + if (_type.isByteArray() || _type.getBaseType()->getStorageBytes() < 32) clearStorageLoop(IntegerType(256)); else clearStorageLoop(*_type.getBaseType()); @@ -248,10 +352,12 @@ void ArrayUtils::resizeDynamicArray(const ArrayType& _type) const m_context << resizeEnd; // cleanup m_context << eth::Instruction::POP << eth::Instruction::POP << eth::Instruction::POP; + solAssert(m_context.getStackHeight() == stackHeightStart - 2, ""); } void ArrayUtils::clearStorageLoop(Type const& _type) const { + unsigned stackHeightStart = m_context.getStackHeight(); if (_type.getCategory() == Type::Category::Mapping) { m_context << eth::Instruction::POP; @@ -266,13 +372,16 @@ void ArrayUtils::clearStorageLoop(Type const& _type) const eth::AssemblyItem zeroLoopEnd = m_context.newTag(); m_context.appendConditionalJumpTo(zeroLoopEnd); // delete + m_context << u256(0); StorageItem(m_context, _type).setToZero(SourceLocation(), false); + m_context << eth::Instruction::POP; // increment m_context << u256(1) << eth::Instruction::ADD; m_context.appendJumpTo(loopStart); // cleanup m_context << zeroLoopEnd; m_context << eth::Instruction::POP; + solAssert(m_context.getStackHeight() == stackHeightStart - 1, ""); } void ArrayUtils::convertLengthToSize(ArrayType const& _arrayType, bool _pad) const @@ -282,7 +391,20 @@ void ArrayUtils::convertLengthToSize(ArrayType const& _arrayType, bool _pad) con if (_arrayType.isByteArray()) m_context << u256(31) << eth::Instruction::ADD << u256(32) << eth::Instruction::SWAP1 << eth::Instruction::DIV; - else if (_arrayType.getBaseType()->getStorageSize() > 1) + else if (_arrayType.getBaseType()->getStorageSize() <= 1) + { + unsigned baseBytes = _arrayType.getBaseType()->getStorageBytes(); + if (baseBytes == 0) + m_context << eth::Instruction::POP << u256(1); + else if (baseBytes <= 16) + { + unsigned itemsPerSlot = 32 / baseBytes; + m_context + << u256(itemsPerSlot - 1) << eth::Instruction::ADD + << u256(itemsPerSlot) << eth::Instruction::SWAP1 << eth::Instruction::DIV; + } + } + else m_context << _arrayType.getBaseType()->getStorageSize() << eth::Instruction::MUL; } else @@ -318,3 +440,143 @@ void ArrayUtils::retrieveLength(ArrayType const& _arrayType) const } } +void ArrayUtils::accessIndex(ArrayType const& _arrayType) const +{ + ArrayType::Location location = _arrayType.getLocation(); + eth::Instruction load = + location == ArrayType::Location::Storage ? eth::Instruction::SLOAD : + location == ArrayType::Location::Memory ? eth::Instruction::MLOAD : + eth::Instruction::CALLDATALOAD; + + // retrieve length + if (!_arrayType.isDynamicallySized()) + m_context << _arrayType.getLength(); + else if (location == ArrayType::Location::CallData) + // length is stored on the stack + m_context << eth::Instruction::SWAP1; + else + m_context << eth::Instruction::DUP2 << load; + // stack: + // check out-of-bounds access + m_context << eth::Instruction::DUP2 << eth::Instruction::LT; + eth::AssemblyItem legalAccess = m_context.appendConditionalJump(); + // out-of-bounds access throws exception (just STOP for now) + m_context << eth::Instruction::STOP; + + m_context << legalAccess; + // stack: + if (_arrayType.isByteArray()) + switch (location) + { + case ArrayType::Location::Storage: + // byte array index storage lvalue on stack (goal): + // = + m_context << u256(32) << eth::Instruction::SWAP2; + CompilerUtils(m_context).computeHashStatic(); + // stack: 32 index data_ref + m_context + << eth::Instruction::DUP3 << eth::Instruction::DUP3 + << eth::Instruction::DIV << eth::Instruction::ADD + // stack: 32 index (data_ref + index / 32) + << eth::Instruction::SWAP2 << eth::Instruction::SWAP1 + << eth::Instruction::MOD; + break; + case ArrayType::Location::CallData: + // no lvalue, just retrieve the value + m_context + << eth::Instruction::ADD << eth::Instruction::CALLDATALOAD + << ((u256(0xff) << (256 - 8))) << eth::Instruction::AND; + break; + case ArrayType::Location::Memory: + solAssert(false, "Memory lvalues not yet implemented."); + } + else + { + // stack: + m_context << eth::Instruction::SWAP1; + if (_arrayType.isDynamicallySized()) + { + if (location == ArrayType::Location::Storage) + CompilerUtils(m_context).computeHashStatic(); + else if (location == ArrayType::Location::Memory) + m_context << u256(32) << eth::Instruction::ADD; + } + // stack: + switch (location) + { + case ArrayType::Location::CallData: + m_context + << eth::Instruction::SWAP1 << _arrayType.getBaseType()->getCalldataEncodedSize() + << eth::Instruction::MUL << eth::Instruction::ADD; + if (_arrayType.getBaseType()->isValueType()) + CompilerUtils(m_context).loadFromMemoryDynamic(*_arrayType.getBaseType(), true, true, false); + break; + case ArrayType::Location::Storage: + m_context << eth::Instruction::SWAP1; + if (_arrayType.getBaseType()->getStorageBytes() <= 16) + { + // stack: + // goal: + // = <(index % itemsPerSlot) * byteSize> + unsigned byteSize = _arrayType.getBaseType()->getStorageBytes(); + solAssert(byteSize != 0, ""); + unsigned itemsPerSlot = 32 / byteSize; + m_context << u256(itemsPerSlot) << eth::Instruction::SWAP2; + // stack: itemsPerSlot index data_ref + m_context + << eth::Instruction::DUP3 << eth::Instruction::DUP3 + << eth::Instruction::DIV << eth::Instruction::ADD + // stack: itemsPerSlot index (data_ref + index / itemsPerSlot) + << eth::Instruction::SWAP2 << eth::Instruction::SWAP1 + << eth::Instruction::MOD + << u256(byteSize) << eth::Instruction::MUL; + } + else + { + if (_arrayType.getBaseType()->getStorageSize() != 1) + m_context << _arrayType.getBaseType()->getStorageSize() << eth::Instruction::MUL; + m_context << eth::Instruction::ADD << u256(0); + } + break; + case ArrayType::Location::Memory: + solAssert(false, "Memory lvalues not yet implemented."); + } + } +} + +void ArrayUtils::incrementByteOffset(unsigned _byteSize, unsigned _byteOffsetPosition, unsigned _storageOffsetPosition) const +{ + solAssert(_byteSize < 32, ""); + solAssert(_byteSize != 0, ""); + // We do the following, but avoiding jumps: + // byteOffset += byteSize + // if (byteOffset + byteSize > 32) + // { + // storageOffset++; + // byteOffset = 0; + // } + if (_byteOffsetPosition > 1) + m_context << eth::swapInstruction(_byteOffsetPosition - 1); + m_context << u256(_byteSize) << eth::Instruction::ADD; + if (_byteOffsetPosition > 1) + m_context << eth::swapInstruction(_byteOffsetPosition - 1); + // compute, X := (byteOffset + byteSize - 1) / 32, should be 1 iff byteOffset + bytesize > 32 + m_context + << u256(32) << eth::dupInstruction(1 + _byteOffsetPosition) << u256(_byteSize - 1) + << eth::Instruction::ADD << eth::Instruction::DIV; + // increment storage offset if X == 1 (just add X to it) + // stack: X + m_context + << eth::swapInstruction(_storageOffsetPosition) << eth::dupInstruction(_storageOffsetPosition + 1) + << eth::Instruction::ADD << eth::swapInstruction(_storageOffsetPosition); + // stack: X + // set source_byte_offset to zero if X == 1 (using source_byte_offset *= 1 - X) + m_context << u256(1) << eth::Instruction::SUB; + // stack: 1 - X + if (_byteOffsetPosition == 1) + m_context << eth::Instruction::MUL; + else + m_context + << eth::dupInstruction(_byteOffsetPosition + 1) << eth::Instruction::MUL + << eth::swapInstruction(_byteOffsetPosition) << eth::Instruction::POP; +} diff --git a/libsolidity/ArrayUtils.h b/libsolidity/ArrayUtils.h index 31cca8173..dab40e2d6 100644 --- a/libsolidity/ArrayUtils.h +++ b/libsolidity/ArrayUtils.h @@ -41,19 +41,19 @@ public: /// Copies an array to an array in storage. The arrays can be of different types only if /// their storage representation is the same. - /// Stack pre: [source_reference] target_reference - /// Stack post: target_reference + /// Stack pre: source_reference [source_byte_offset/source_length] target_reference target_byte_offset + /// Stack post: target_reference target_byte_offset void copyArrayToStorage(ArrayType const& _targetType, ArrayType const& _sourceType) const; /// Clears the given dynamic or static array. - /// Stack pre: reference + /// Stack pre: storage_ref storage_byte_offset /// Stack post: void clearArray(ArrayType const& _type) const; /// Clears the length and data elements of the array referenced on the stack. - /// Stack pre: reference + /// Stack pre: reference (excludes byte offset) /// Stack post: void clearDynamicArray(ArrayType const& _type) const; /// Changes the size of a dynamic array and clears the tail if it is shortened. - /// Stack pre: reference new_length + /// Stack pre: reference (excludes byte offset) new_length /// Stack post: void resizeDynamicArray(ArrayType const& _type) const; /// Appends a loop that clears a sequence of storage slots of the given type (excluding end). @@ -67,11 +67,23 @@ public: void convertLengthToSize(ArrayType const& _arrayType, bool _pad = false) const; /// Retrieves the length (number of elements) of the array ref on the stack. This also /// works for statically-sized arrays. - /// Stack pre: reference + /// Stack pre: reference (excludes byte offset for dynamic storage arrays) /// Stack post: reference length void retrieveLength(ArrayType const& _arrayType) const; + /// Retrieves the value at a specific index. If the location is storage, only retrieves the + /// position. + /// Stack pre: reference [length] index + /// Stack post for storage: slot byte_offset + /// Stack post for calldata: value + void accessIndex(ArrayType const& _arrayType) const; private: + /// Adds the given number of bytes to a storage byte offset counter and also increments + /// the storage offset if adding this number again would increase the counter over 32. + /// @param byteOffsetPosition the stack offset of the storage byte offset + /// @param storageOffsetPosition the stack offset of the storage slot offset + void incrementByteOffset(unsigned _byteSize, unsigned _byteOffsetPosition, unsigned _storageOffsetPosition) const; + CompilerContext& m_context; }; diff --git a/libsolidity/Compiler.cpp b/libsolidity/Compiler.cpp index 5eeb0c3e2..8e2634499 100644 --- a/libsolidity/Compiler.cpp +++ b/libsolidity/Compiler.cpp @@ -20,12 +20,12 @@ * Solidity compiler. */ +#include #include #include #include #include #include -#include #include #include @@ -132,7 +132,7 @@ void Compiler::packIntoContractCreator(ContractDefinition const& _contract, Comp void Compiler::appendBaseConstructor(FunctionDefinition const& _constructor) { - CompilerContext::LocationSetter locationSetter(m_context, &_constructor); + CompilerContext::LocationSetter locationSetter(m_context, _constructor); FunctionType constructorType(_constructor); if (!constructorType.getParameterTypes().empty()) { @@ -146,7 +146,7 @@ void Compiler::appendBaseConstructor(FunctionDefinition const& _constructor) void Compiler::appendConstructor(FunctionDefinition const& _constructor) { - CompilerContext::LocationSetter locationSetter(m_context, &_constructor); + CompilerContext::LocationSetter locationSetter(m_context, _constructor); // copy constructor arguments from code to memory and then to stack, they are supplied after the actual program unsigned argumentSize = 0; for (ASTPointer const& var: _constructor.getParameters()) @@ -192,10 +192,12 @@ void Compiler::appendFunctionSelector(ContractDefinition const& _contract) for (auto const& it: interfaceFunctions) { FunctionTypePointer const& functionType = it.second; + solAssert(functionType->hasDeclaration(), ""); + CompilerContext::LocationSetter locationSetter(m_context, functionType->getDeclaration()); m_context << callDataUnpackerEntryPoints.at(it.first); eth::AssemblyItem returnTag = m_context.pushNewTag(); appendCalldataUnpacker(functionType->getParameterTypes()); - m_context.appendJumpTo(m_context.getFunctionEntryLabel(it.second->getDeclaration())); + m_context.appendJumpTo(m_context.getFunctionEntryLabel(functionType->getDeclaration())); m_context << returnTag; appendReturnValuePacker(functionType->getReturnParameterTypes()); } @@ -226,6 +228,7 @@ void Compiler::appendCalldataUnpacker(TypePointers const& _typeParameters, bool { // Retrieve data start offset by adding length to start offset of previous dynamic type unsigned stackDepth = m_context.getStackHeight() - stackHeightOfPreviousDynamicArgument; + solAssert(stackDepth <= 16, "Stack too deep."); m_context << eth::dupInstruction(stackDepth) << eth::dupInstruction(stackDepth); ArrayUtils(m_context).convertLengthToSize(*previousDynamicType, true); m_context << eth::Instruction::ADD; @@ -271,22 +274,21 @@ void Compiler::appendReturnValuePacker(TypePointers const& _typeParameters) void Compiler::registerStateVariables(ContractDefinition const& _contract) { - for (ContractDefinition const* contract: boost::adaptors::reverse(_contract.getLinearizedBaseContracts())) - for (ASTPointer const& variable: contract->getStateVariables()) - m_context.addStateVariable(*variable); + for (auto const& var: ContractType(_contract).getStateVariables()) + m_context.addStateVariable(*get<0>(var), get<1>(var), get<2>(var)); } void Compiler::initializeStateVariables(ContractDefinition const& _contract) { for (ASTPointer const& variable: _contract.getStateVariables()) - if (variable->getValue()) + if (variable->getValue() && !variable->isConstant()) ExpressionCompiler(m_context, m_optimize).appendStateVariableInitialization(*variable); } bool Compiler::visit(VariableDeclaration const& _variableDeclaration) { solAssert(_variableDeclaration.isStateVariable(), "Compiler visit to non-state variable declaration."); - CompilerContext::LocationSetter locationSetter(m_context, &_variableDeclaration); + CompilerContext::LocationSetter locationSetter(m_context, _variableDeclaration); m_context.startFunction(_variableDeclaration); m_breakTags.clear(); @@ -300,7 +302,7 @@ bool Compiler::visit(VariableDeclaration const& _variableDeclaration) bool Compiler::visit(FunctionDefinition const& _function) { - CompilerContext::LocationSetter locationSetter(m_context, &_function); + CompilerContext::LocationSetter locationSetter(m_context, _function); //@todo to simplify this, the calling convention could by changed such that // caller puts: [retarg0] ... [retargm] [return address] [arg0] ... [argn] // although note that this reduces the size of the visible stack @@ -357,6 +359,7 @@ bool Compiler::visit(FunctionDefinition const& _function) stackLayout.push_back(i); stackLayout += vector(c_localVariablesSize, -1); + solAssert(stackLayout.size() <= 17, "Stack too deep."); while (stackLayout.back() != int(stackLayout.size() - 1)) if (stackLayout.back() < 0) { @@ -376,15 +379,16 @@ bool Compiler::visit(FunctionDefinition const& _function) m_context.removeVariable(*localVariable); m_context.adjustStackOffset(-(int)c_returnValuesSize); + if (!_function.isConstructor()) - m_context << eth::Instruction::JUMP; + m_context.appendJump(eth::AssemblyItem::JumpType::OutOfFunction); return false; } bool Compiler::visit(IfStatement const& _ifStatement) { StackHeightChecker checker(m_context); - CompilerContext::LocationSetter locationSetter(m_context, &_ifStatement); + CompilerContext::LocationSetter locationSetter(m_context, _ifStatement); compileExpression(_ifStatement.getCondition()); eth::AssemblyItem trueTag = m_context.appendConditionalJump(); if (_ifStatement.getFalseStatement()) @@ -401,7 +405,7 @@ bool Compiler::visit(IfStatement const& _ifStatement) bool Compiler::visit(WhileStatement const& _whileStatement) { StackHeightChecker checker(m_context); - CompilerContext::LocationSetter locationSetter(m_context, &_whileStatement); + CompilerContext::LocationSetter locationSetter(m_context, _whileStatement); eth::AssemblyItem loopStart = m_context.newTag(); eth::AssemblyItem loopEnd = m_context.newTag(); m_continueTags.push_back(loopStart); @@ -427,7 +431,7 @@ bool Compiler::visit(WhileStatement const& _whileStatement) bool Compiler::visit(ForStatement const& _forStatement) { StackHeightChecker checker(m_context); - CompilerContext::LocationSetter locationSetter(m_context, &_forStatement); + CompilerContext::LocationSetter locationSetter(m_context, _forStatement); eth::AssemblyItem loopStart = m_context.newTag(); eth::AssemblyItem loopEnd = m_context.newTag(); m_continueTags.push_back(loopStart); @@ -464,7 +468,7 @@ bool Compiler::visit(ForStatement const& _forStatement) bool Compiler::visit(Continue const& _continueStatement) { - CompilerContext::LocationSetter locationSetter(m_context, &_continueStatement); + CompilerContext::LocationSetter locationSetter(m_context, _continueStatement); if (!m_continueTags.empty()) m_context.appendJumpTo(m_continueTags.back()); return false; @@ -472,7 +476,7 @@ bool Compiler::visit(Continue const& _continueStatement) bool Compiler::visit(Break const& _breakStatement) { - CompilerContext::LocationSetter locationSetter(m_context, &_breakStatement); + CompilerContext::LocationSetter locationSetter(m_context, _breakStatement); if (!m_breakTags.empty()) m_context.appendJumpTo(m_breakTags.back()); return false; @@ -480,7 +484,7 @@ bool Compiler::visit(Break const& _breakStatement) bool Compiler::visit(Return const& _return) { - CompilerContext::LocationSetter locationSetter(m_context, &_return); + CompilerContext::LocationSetter locationSetter(m_context, _return); //@todo modifications are needed to make this work with functions returning multiple values if (Expression const* expression = _return.getExpression()) { @@ -499,7 +503,7 @@ bool Compiler::visit(Return const& _return) bool Compiler::visit(VariableDeclarationStatement const& _variableDeclarationStatement) { StackHeightChecker checker(m_context); - CompilerContext::LocationSetter locationSetter(m_context, &_variableDeclarationStatement); + CompilerContext::LocationSetter locationSetter(m_context, _variableDeclarationStatement); if (Expression const* expression = _variableDeclarationStatement.getExpression()) { compileExpression(*expression, _variableDeclarationStatement.getDeclaration().getType()); @@ -512,7 +516,7 @@ bool Compiler::visit(VariableDeclarationStatement const& _variableDeclarationSta bool Compiler::visit(ExpressionStatement const& _expressionStatement) { StackHeightChecker checker(m_context); - CompilerContext::LocationSetter locationSetter(m_context, &_expressionStatement); + CompilerContext::LocationSetter locationSetter(m_context, _expressionStatement); Expression const& expression = _expressionStatement.getExpression(); compileExpression(expression); CompilerUtils(m_context).popStackElement(*expression.getType()); @@ -523,7 +527,7 @@ bool Compiler::visit(ExpressionStatement const& _expressionStatement) bool Compiler::visit(PlaceholderStatement const& _placeholderStatement) { StackHeightChecker checker(m_context); - CompilerContext::LocationSetter locationSetter(m_context, &_placeholderStatement); + CompilerContext::LocationSetter locationSetter(m_context, _placeholderStatement); ++m_modifierDepth; appendModifierOrFunctionCode(); --m_modifierDepth; @@ -550,7 +554,7 @@ void Compiler::appendModifierOrFunctionCode() } ModifierDefinition const& modifier = m_context.getFunctionModifier(modifierInvocation->getName()->getName()); - CompilerContext::LocationSetter locationSetter(m_context, &modifier); + CompilerContext::LocationSetter locationSetter(m_context, modifier); solAssert(modifier.getParameters().size() == modifierInvocation->getArguments().size(), ""); for (unsigned i = 0; i < modifier.getParameters().size(); ++i) { diff --git a/libsolidity/Compiler.h b/libsolidity/Compiler.h index 76f16f3ab..4b1e1b4d6 100644 --- a/libsolidity/Compiler.h +++ b/libsolidity/Compiler.h @@ -94,8 +94,8 @@ private: std::vector m_continueTags; ///< tag to jump to for a "continue" statement eth::AssemblyItem m_returnTag; ///< tag to jump to for a "return" statement unsigned m_modifierDepth = 0; - FunctionDefinition const* m_currentFunction; - unsigned m_stackCleanupForReturn; ///< this number of stack elements need to be removed before jump to m_returnTag + FunctionDefinition const* m_currentFunction = nullptr; + unsigned m_stackCleanupForReturn = 0; ///< this number of stack elements need to be removed before jump to m_returnTag // arguments for base constructors, filled in derived-to-base order std::map> const*> m_baseArguments; }; diff --git a/libsolidity/CompilerContext.cpp b/libsolidity/CompilerContext.cpp index b12e01923..0afda1367 100644 --- a/libsolidity/CompilerContext.cpp +++ b/libsolidity/CompilerContext.cpp @@ -37,15 +37,13 @@ void CompilerContext::addMagicGlobal(MagicVariableDeclaration const& _declaratio m_magicGlobals.insert(&_declaration); } -void CompilerContext::addStateVariable(VariableDeclaration const& _declaration) +void CompilerContext::addStateVariable( + VariableDeclaration const& _declaration, + u256 const& _storageOffset, + unsigned _byteOffset +) { - m_stateVariables[&_declaration] = m_stateVariablesSize; - bigint newSize = bigint(m_stateVariablesSize) + _declaration.getType()->getStorageSize(); - if (newSize >= bigint(1) << 256) - BOOST_THROW_EXCEPTION(TypeError() - << errinfo_comment("State variable does not fit in storage.") - << errinfo_sourceLocation(_declaration.getLocation())); - m_stateVariablesSize = u256(newSize); + m_stateVariables[&_declaration] = make_pair(_storageOffset, _byteOffset); } void CompilerContext::startFunction(Declaration const& _function) @@ -69,7 +67,7 @@ void CompilerContext::removeVariable(VariableDeclaration const& _declaration) void CompilerContext::addAndInitializeVariable(VariableDeclaration const& _declaration) { - LocationSetter locationSetter(*this, &_declaration); + LocationSetter locationSetter(*this, _declaration); addVariable(_declaration); int const size = _declaration.getType()->getSizeOnStack(); for (int i = 0; i < size; ++i) @@ -174,46 +172,26 @@ unsigned CompilerContext::currentToBaseStackOffset(unsigned _offset) const return m_asm.deposit() - _offset - 1; } -u256 CompilerContext::getStorageLocationOfVariable(const Declaration& _declaration) const +pair CompilerContext::getStorageLocationOfVariable(const Declaration& _declaration) const { auto it = m_stateVariables.find(&_declaration); solAssert(it != m_stateVariables.end(), "Variable not found in storage."); return it->second; } +CompilerContext& CompilerContext::appendJump(eth::AssemblyItem::JumpType _jumpType) +{ + eth::AssemblyItem item(eth::Instruction::JUMP); + item.setJumpType(_jumpType); + return *this << item; +} + void CompilerContext::resetVisitedNodes(ASTNode const* _node) { stack newStack; newStack.push(_node); std::swap(m_visitedNodes, newStack); -} - -CompilerContext& CompilerContext::operator<<(eth::AssemblyItem const& _item) -{ - solAssert(!m_visitedNodes.empty(), "No node on the visited stack"); - m_asm.append(_item, m_visitedNodes.top()->getLocation()); - return *this; -} - -CompilerContext& CompilerContext::operator<<(eth::Instruction _instruction) -{ - solAssert(!m_visitedNodes.empty(), "No node on the visited stack"); - m_asm.append(_instruction, m_visitedNodes.top()->getLocation()); - return *this; -} - -CompilerContext& CompilerContext::operator<<(u256 const& _value) -{ - solAssert(!m_visitedNodes.empty(), "No node on the visited stack"); - m_asm.append(_value, m_visitedNodes.top()->getLocation()); - return *this; -} - -CompilerContext& CompilerContext::operator<<(bytes const& _data) -{ - solAssert(!m_visitedNodes.empty(), "No node on the visited stack"); - m_asm.append(_data, m_visitedNodes.top()->getLocation()); - return *this; + updateSourceLocation(); } vector::const_iterator CompilerContext::getSuperContract(ContractDefinition const& _contract) const @@ -224,5 +202,10 @@ vector::const_iterator CompilerContext::getSuperContr return ++it; } +void CompilerContext::updateSourceLocation() +{ + m_asm.setSourceLocation(m_visitedNodes.empty() ? SourceLocation() : m_visitedNodes.top()->getLocation()); +} + } } diff --git a/libsolidity/CompilerContext.h b/libsolidity/CompilerContext.h index e42e7c76c..87f90d4c4 100644 --- a/libsolidity/CompilerContext.h +++ b/libsolidity/CompilerContext.h @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -42,7 +43,7 @@ class CompilerContext { public: void addMagicGlobal(MagicVariableDeclaration const& _declaration); - void addStateVariable(VariableDeclaration const& _declaration); + void addStateVariable(VariableDeclaration const& _declaration, u256 const& _storageOffset, unsigned _byteOffset); void addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent = 0); void removeVariable(VariableDeclaration const& _declaration); void addAndInitializeVariable(VariableDeclaration const& _declaration); @@ -82,7 +83,7 @@ public: /// Converts an offset relative to the current stack height to a value that can be used later /// with baseToCurrentStackOffset to point to the same stack element. unsigned currentToBaseStackOffset(unsigned _offset) const; - u256 getStorageLocationOfVariable(Declaration const& _declaration) const; + std::pair getStorageLocationOfVariable(Declaration const& _declaration) const; /// Appends a JUMPI instruction to a new tag and @returns the tag eth::AssemblyItem appendConditionalJump() { return m_asm.appendJumpI().tag(); } @@ -91,7 +92,7 @@ public: /// Appends a JUMP to a new tag and @returns the tag eth::AssemblyItem appendJumpToNew() { return m_asm.appendJump().tag(); } /// Appends a JUMP to a tag already on the stack - CompilerContext& appendJump() { return *this << eth::Instruction::JUMP; } + CompilerContext& appendJump(eth::AssemblyItem::JumpType _jumpType = eth::AssemblyItem::JumpType::Ordinary); /// Appends a JUMP to a specific tag CompilerContext& appendJumpTo(eth::AssemblyItem const& _tag) { m_asm.appendJump(_tag); return *this; } /// Appends pushing of a new tag and @returns the new tag. @@ -108,19 +109,19 @@ public: /// Resets the stack of visited nodes with a new stack having only @c _node void resetVisitedNodes(ASTNode const* _node); /// Pops the stack of visited nodes - void popVisitedNodes() { m_visitedNodes.pop(); } + void popVisitedNodes() { m_visitedNodes.pop(); updateSourceLocation(); } /// Pushes an ASTNode to the stack of visited nodes - void pushVisitedNodes(ASTNode const* _node) { m_visitedNodes.push(_node); } + void pushVisitedNodes(ASTNode const* _node) { m_visitedNodes.push(_node); updateSourceLocation(); } /// Append elements to the current instruction list and adjust @a m_stackOffset. - CompilerContext& operator<<(eth::AssemblyItem const& _item); - CompilerContext& operator<<(eth::Instruction _instruction); - CompilerContext& operator<<(u256 const& _value); - CompilerContext& operator<<(bytes const& _data); + CompilerContext& operator<<(eth::AssemblyItem const& _item) { m_asm.append(_item); return *this; } + CompilerContext& operator<<(eth::Instruction _instruction) { m_asm.append(_instruction); return *this; } + CompilerContext& operator<<(u256 const& _value) { m_asm.append(_value); return *this; } + CompilerContext& operator<<(bytes const& _data) { m_asm.append(_data); return *this; } eth::Assembly const& getAssembly() const { return m_asm; } /// @arg _sourceCodes is the map of input files to source code strings - void streamAssembly(std::ostream& _stream, StringMap const& _sourceCodes = StringMap()) const { m_asm.streamRLP(_stream, "", _sourceCodes); } + void streamAssembly(std::ostream& _stream, StringMap const& _sourceCodes = StringMap()) const { m_asm.stream(_stream, "", _sourceCodes); } bytes getAssembledBytecode(bool _optimize = false) { return m_asm.optimise(_optimize).assemble(); } @@ -130,22 +131,22 @@ public: class LocationSetter: public ScopeGuard { public: - LocationSetter(CompilerContext& _compilerContext, ASTNode const* _node): - ScopeGuard(std::bind(&CompilerContext::popVisitedNodes, _compilerContext)) { _compilerContext.pushVisitedNodes(_node); } + LocationSetter(CompilerContext& _compilerContext, ASTNode const& _node): + ScopeGuard([&]{ _compilerContext.popVisitedNodes(); }) { _compilerContext.pushVisitedNodes(&_node); } }; private: std::vector::const_iterator getSuperContract(const ContractDefinition &_contract) const; + /// Updates source location set in the assembly. + void updateSourceLocation(); eth::Assembly m_asm; /// Magic global variables like msg, tx or this, distinguished by type. std::set m_magicGlobals; /// Other already compiled contracts to be used in contract creation calls. std::map m_compiledContracts; - /// Size of the state variables, offset of next variable to be added. - u256 m_stateVariablesSize = 0; /// Storage offsets of state variables - std::map m_stateVariables; + std::map> m_stateVariables; /// Offsets of local variables on the stack (relative to stack base). std::map m_localVariables; /// Labels pointing to the entry points of functions. diff --git a/libsolidity/CompilerStack.cpp b/libsolidity/CompilerStack.cpp index a878bb61a..1301bfa5d 100644 --- a/libsolidity/CompilerStack.cpp +++ b/libsolidity/CompilerStack.cpp @@ -41,14 +41,14 @@ namespace solidity { const map StandardSources = map{ - {"coin", R"(import "CoinReg";import "Config";import "configUser";contract coin is configUser{function coin(string3 name, uint denom) {CoinReg(Config(configAddr()).lookup(3)).register(name, denom);}})"}, + {"coin", R"(import "CoinReg";import "Config";import "configUser";contract coin is configUser{function coin(bytes3 name, uint denom) {CoinReg(Config(configAddr()).lookup(3)).register(name, denom);}})"}, {"Coin", R"(contract Coin{function isApprovedFor(address _target,address _proxy)constant returns(bool _r){}function isApproved(address _proxy)constant returns(bool _r){}function sendCoinFrom(address _from,uint256 _val,address _to){}function coinBalanceOf(address _a)constant returns(uint256 _r){}function sendCoin(uint256 _val,address _to){}function coinBalance()constant returns(uint256 _r){}function approve(address _a){}})"}, - {"CoinReg", R"(contract CoinReg{function count()constant returns(uint256 r){}function info(uint256 i)constant returns(address addr,string3 name,uint256 denom){}function register(string3 name,uint256 denom){}function unregister(){}})"}, + {"CoinReg", R"(contract CoinReg{function count()constant returns(uint256 r){}function info(uint256 i)constant returns(address addr,bytes3 name,uint256 denom){}function register(bytes3 name,uint256 denom){}function unregister(){}})"}, {"configUser", R"(contract configUser{function configAddr()constant returns(address a){ return 0xc6d9d2cd449a754c494264e1809c50e34d64562b;}})"}, {"Config", R"(contract Config{function lookup(uint256 service)constant returns(address a){}function kill(){}function unregister(uint256 id){}function register(uint256 id,address service){}})"}, {"mortal", R"(import "owned";contract mortal is owned {function kill() { if (msg.sender == owner) suicide(owner); }})"}, - {"named", R"(import "Config";import "NameReg";import "configUser";contract named is configUser {function named(string32 name) {NameReg(Config(configAddr()).lookup(1)).register(name);}})"}, - {"NameReg", R"(contract NameReg{function register(string32 name){}function addressOf(string32 name)constant returns(address addr){}function unregister(){}function nameOf(address addr)constant returns(string32 name){}})"}, + {"named", R"(import "Config";import "NameReg";import "configUser";contract named is configUser {function named(bytes32 name) {NameReg(Config(configAddr()).lookup(1)).register(name);}})"}, + {"NameReg", R"(contract NameReg{function register(bytes32 name){}function addressOf(bytes32 name)constant returns(address addr){}function unregister(){}function nameOf(address addr)constant returns(bytes32 name){}})"}, {"owned", R"(contract owned{function owned(){owner = msg.sender;}modifier onlyowner(){if(msg.sender==owner)_}address owner;})"}, {"service", R"(import "Config";import "configUser";contract service is configUser{function service(uint _n){Config(configAddr()).register(_n, this);}})"}, {"std", R"(import "owned";import "mortal";import "Config";import "configUser";import "NameReg";import "named";)"} @@ -138,6 +138,8 @@ void CompilerStack::compile(bool _optimize) for (ASTPointer const& node: source->ast->getNodes()) if (ContractDefinition* contract = dynamic_cast(node.get())) { + if (!contract->isFullyImplemented()) + continue; shared_ptr compiler = make_shared(_optimize); compiler->compileContract(*contract, contractBytecode); Contract& compiledContract = m_contracts[contract->getName()]; diff --git a/libsolidity/CompilerUtils.cpp b/libsolidity/CompilerUtils.cpp index 8a26b5d17..511254fa5 100644 --- a/libsolidity/CompilerUtils.cpp +++ b/libsolidity/CompilerUtils.cpp @@ -93,6 +93,7 @@ void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBound else { solAssert(type.getLocation() == ArrayType::Location::Storage, "Memory arrays not yet implemented."); + m_context << eth::Instruction::POP; //@todo m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD; // stack here: memory_offset storage_offset length_bytes // jump to end if length is zero @@ -138,6 +139,7 @@ void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable) { unsigned const stackPosition = m_context.baseToCurrentStackOffset(m_context.getBaseStackOffsetOfVariable(_variable)); unsigned const size = _variable.getType()->getSizeOnStack(); + solAssert(stackPosition >= size, "Variable size and position mismatch."); // move variable starting from its top end in the stack if (stackPosition - size + 1 > 16) BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_variable.getLocation()) @@ -148,8 +150,7 @@ void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable) void CompilerUtils::copyToStackTop(unsigned _stackDepth, unsigned _itemSize) { - if (_stackDepth > 16) - BOOST_THROW_EXCEPTION(CompilerError() << errinfo_comment("Stack too deep.")); + solAssert(_stackDepth <= 16, "Stack too deep."); for (unsigned i = 0; i < _itemSize; ++i) m_context << eth::dupInstruction(_stackDepth); } @@ -178,7 +179,7 @@ void CompilerUtils::computeHashStatic(Type const& _type, bool _padToWordBoundari unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWordBoundaries) { unsigned numBytes = _type.getCalldataEncodedSize(_padToWordBoundaries); - bool leftAligned = _type.getCategory() == Type::Category::String; + bool leftAligned = _type.getCategory() == Type::Category::FixedBytes; if (numBytes == 0) m_context << eth::Instruction::POP << u256(0); else @@ -198,11 +199,10 @@ unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCallda return numBytes; } - unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWordBoundaries) const { unsigned numBytes = _type.getCalldataEncodedSize(_padToWordBoundaries); - bool leftAligned = _type.getCategory() == Type::Category::String; + bool leftAligned = _type.getCategory() == Type::Category::FixedBytes; if (numBytes == 0) m_context << eth::Instruction::POP; else diff --git a/libsolidity/ExpressionCompiler.cpp b/libsolidity/ExpressionCompiler.cpp index 4be461b25..3ca8de89e 100644 --- a/libsolidity/ExpressionCompiler.cpp +++ b/libsolidity/ExpressionCompiler.cpp @@ -48,7 +48,7 @@ void ExpressionCompiler::appendStateVariableInitialization(VariableDeclaration c if (!_varDecl.getValue()) return; solAssert(!!_varDecl.getValue()->getType(), "Type information not available."); - CompilerContext::LocationSetter locationSetter(m_context, &_varDecl); + CompilerContext::LocationSetter locationSetter(m_context, _varDecl); _varDecl.getValue()->accept(*this); appendTypeConversion(*_varDecl.getValue()->getType(), *_varDecl.getType(), true); @@ -57,7 +57,7 @@ void ExpressionCompiler::appendStateVariableInitialization(VariableDeclaration c void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& _varDecl) { - CompilerContext::LocationSetter locationSetter(m_context, &_varDecl); + CompilerContext::LocationSetter locationSetter(m_context, _varDecl); FunctionType accessorType(_varDecl); unsigned length = 0; @@ -67,9 +67,10 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& length += CompilerUtils(m_context).storeInMemory(length, *paramType, true); // retrieve the position of the variable - m_context << m_context.getStorageLocationOfVariable(_varDecl); - TypePointer returnType = _varDecl.getType(); + auto const& location = m_context.getStorageLocationOfVariable(_varDecl); + m_context << location.first; + TypePointer returnType = _varDecl.getType(); for (TypePointer const& paramType: paramTypes) { // move offset to memory @@ -90,9 +91,10 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& // struct for (size_t i = 0; i < names.size(); ++i) { - m_context << eth::Instruction::DUP1 - << structType->getStorageOffsetOfMember(names[i]) - << eth::Instruction::ADD; + if (types[i]->getCategory() == Type::Category::Mapping) + continue; + pair const& offsets = structType->getStorageOffsetsOfMember(names[i]); + m_context << eth::Instruction::DUP1 << u256(offsets.first) << eth::Instruction::ADD << u256(offsets.second); StorageItem(m_context, *types[i]).retrieveValue(SourceLocation(), true); solAssert(types[i]->getSizeOnStack() == 1, "Returning struct elements with stack size != 1 not yet implemented."); m_context << eth::Instruction::SWAP1; @@ -104,11 +106,13 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& { // simple value solAssert(accessorType.getReturnParameterTypes().size() == 1, ""); + m_context << u256(location.second); StorageItem(m_context, *returnType).retrieveValue(SourceLocation(), true); retSizeOnStack = returnType->getSizeOnStack(); } solAssert(retSizeOnStack <= 15, "Stack too deep."); - m_context << eth::dupInstruction(retSizeOnStack + 1) << eth::Instruction::JUMP; + m_context << eth::dupInstruction(retSizeOnStack + 1); + m_context.appendJump(eth::AssemblyItem::JumpType::OutOfFunction); } void ExpressionCompiler::appendTypeConversion(Type const& _typeOnStack, Type const& _targetType, bool _cleanupNeeded) @@ -122,23 +126,25 @@ void ExpressionCompiler::appendTypeConversion(Type const& _typeOnStack, Type con Type::Category stackTypeCategory = _typeOnStack.getCategory(); Type::Category targetTypeCategory = _targetType.getCategory(); - if (stackTypeCategory == Type::Category::String) + switch (stackTypeCategory) { - StaticStringType const& typeOnStack = dynamic_cast(_typeOnStack); + case Type::Category::FixedBytes: + { + FixedBytesType const& typeOnStack = dynamic_cast(_typeOnStack); if (targetTypeCategory == Type::Category::Integer) { - // conversion from string to hash. no need to clean the high bit + // conversion from bytes to integer. no need to clean the high bit // only to shift right because of opposite alignment IntegerType const& targetIntegerType = dynamic_cast(_targetType); - solAssert(targetIntegerType.isHash(), "Only conversion between String and Hash is allowed."); - solAssert(targetIntegerType.getNumBits() == typeOnStack.getNumBytes() * 8, "The size should be the same."); m_context << (u256(1) << (256 - typeOnStack.getNumBytes() * 8)) << eth::Instruction::SWAP1 << eth::Instruction::DIV; + if (targetIntegerType.getNumBits() < typeOnStack.getNumBytes() * 8) + appendTypeConversion(IntegerType(typeOnStack.getNumBytes() * 8), _targetType, _cleanupNeeded); } else { - // clear lower-order bytes for conversion to shorter strings - we always clean - solAssert(targetTypeCategory == Type::Category::String, "Invalid type conversion requested."); - StaticStringType const& targetType = dynamic_cast(_targetType); + // clear lower-order bytes for conversion to shorter bytes - we always clean + solAssert(targetTypeCategory == Type::Category::FixedBytes, "Invalid type conversion requested."); + FixedBytesType const& targetType = dynamic_cast(_targetType); if (targetType.getNumBytes() < typeOnStack.getNumBytes()) { if (targetType.getNumBytes() == 0) @@ -150,22 +156,24 @@ void ExpressionCompiler::appendTypeConversion(Type const& _typeOnStack, Type con } } } - else if (stackTypeCategory == Type::Category::Enum) - solAssert(targetTypeCategory == Type::Category::Integer || - targetTypeCategory == Type::Category::Enum, ""); - else if (stackTypeCategory == Type::Category::Integer || - stackTypeCategory == Type::Category::Contract || - stackTypeCategory == Type::Category::IntegerConstant) - { - if (targetTypeCategory == Type::Category::String && stackTypeCategory == Type::Category::Integer) + break; + case Type::Category::Enum: + solAssert(targetTypeCategory == Type::Category::Integer || targetTypeCategory == Type::Category::Enum, ""); + break; + case Type::Category::Integer: + case Type::Category::Contract: + case Type::Category::IntegerConstant: + if (targetTypeCategory == Type::Category::FixedBytes) { - // conversion from hash to string. no need to clean the high bit + solAssert(stackTypeCategory == Type::Category::Integer || stackTypeCategory == Type::Category::IntegerConstant, + "Invalid conversion to FixedBytesType requested."); + // conversion from bytes to string. no need to clean the high bit // only to shift left because of opposite alignment - StaticStringType const& targetStringType = dynamic_cast(_targetType); - IntegerType const& typeOnStack = dynamic_cast(_typeOnStack); - solAssert(typeOnStack.isHash(), "Only conversion between String and Hash is allowed."); - solAssert(typeOnStack.getNumBits() == targetStringType.getNumBytes() * 8, "The size should be the same."); - m_context << (u256(1) << (256 - typeOnStack.getNumBits())) << eth::Instruction::MUL; + FixedBytesType const& targetBytesType = dynamic_cast(_targetType); + if (auto typeOnStack = dynamic_cast(&_typeOnStack)) + if (targetBytesType.getNumBytes() * 8 > typeOnStack->getNumBits()) + appendHighBitsCleanup(*typeOnStack); + m_context << (u256(1) << (256 - targetBytesType.getNumBytes() * 8)) << eth::Instruction::MUL; } else if (targetTypeCategory == Type::Category::Enum) // just clean @@ -175,7 +183,7 @@ void ExpressionCompiler::appendTypeConversion(Type const& _typeOnStack, Type con solAssert(targetTypeCategory == Type::Category::Integer || targetTypeCategory == Type::Category::Contract, ""); IntegerType addressType(0, IntegerType::Modifier::Address); IntegerType const& targetType = targetTypeCategory == Type::Category::Integer - ? dynamic_cast(_targetType) : addressType; + ? dynamic_cast(_targetType) : addressType; if (stackTypeCategory == Type::Category::IntegerConstant) { IntegerConstantType const& constType = dynamic_cast(_typeOnStack); @@ -187,7 +195,7 @@ void ExpressionCompiler::appendTypeConversion(Type const& _typeOnStack, Type con else { IntegerType const& typeOnStack = stackTypeCategory == Type::Category::Integer - ? dynamic_cast(_typeOnStack) : addressType; + ? dynamic_cast(_typeOnStack) : addressType; // Widening: clean up according to source type width // Non-widening and force: clean up according to target type bits if (targetType.getNumBits() > typeOnStack.getNumBits()) @@ -196,15 +204,17 @@ void ExpressionCompiler::appendTypeConversion(Type const& _typeOnStack, Type con appendHighBitsCleanup(targetType); } } - } - else if (_typeOnStack != _targetType) + break; + default: // All other types should not be convertible to non-equal types. - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid type conversion requested.")); + solAssert(_typeOnStack == _targetType, "Invalid type conversion requested."); + break; + } } bool ExpressionCompiler::visit(Assignment const& _assignment) { - CompilerContext::LocationSetter locationSetter(m_context, &_assignment); + CompilerContext::LocationSetter locationSetter(m_context, _assignment); _assignment.getRightHandSide().accept(*this); if (_assignment.getType()->isValueType()) appendTypeConversion(*_assignment.getRightHandSide().getType(), *_assignment.getType()); @@ -226,9 +236,12 @@ bool ExpressionCompiler::visit(Assignment const& _assignment) m_currentLValue->retrieveValue(_assignment.getLocation(), true); appendOrdinaryBinaryOperatorCode(Token::AssignmentToBinaryOp(op), *_assignment.getType()); if (lvalueSize > 0) + { + solAssert(itemSize + lvalueSize <= 16, "Stack too deep."); // value [lvalue_ref] updated_value for (unsigned i = 0; i < itemSize; ++i) m_context << eth::swapInstruction(itemSize + lvalueSize) << eth::Instruction::POP; + } } m_currentLValue->storeValue(*_assignment.getRightHandSide().getType(), _assignment.getLocation()); m_currentLValue.reset(); @@ -237,7 +250,7 @@ bool ExpressionCompiler::visit(Assignment const& _assignment) bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation) { - CompilerContext::LocationSetter locationSetter(m_context, &_unaryOperation); + CompilerContext::LocationSetter locationSetter(m_context, _unaryOperation); //@todo type checking and creating code for an operator should be in the same place: // the operator should know how to convert itself and to which types it applies, so // put this code together with "Type::acceptsBinary/UnaryOperator" into a class that @@ -270,23 +283,24 @@ bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation) case Token::Dec: // -- (pre- or postfix) solAssert(!!m_currentLValue, "LValue not retrieved."); m_currentLValue->retrieveValue(_unaryOperation.getLocation()); - solAssert(m_currentLValue->sizeOnStack() <= 1, "Not implemented."); if (!_unaryOperation.isPrefixOperation()) { - if (m_currentLValue->sizeOnStack() == 1) - m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2; - else - m_context << eth::Instruction::DUP1; + // store value for later + solAssert(_unaryOperation.getType()->getSizeOnStack() == 1, "Stack size != 1 not implemented."); + m_context << eth::Instruction::DUP1; + if (m_currentLValue->sizeOnStack() > 0) + for (unsigned i = 1 + m_currentLValue->sizeOnStack(); i > 0; --i) + m_context << eth::swapInstruction(i); } m_context << u256(1); if (_unaryOperation.getOperator() == Token::Inc) m_context << eth::Instruction::ADD; else - m_context << eth::Instruction::SWAP1 << eth::Instruction::SUB; // @todo avoid the swap - // Stack for prefix: [ref] (*ref)+-1 - // Stack for postfix: *ref [ref] (*ref)+-1 - if (m_currentLValue->sizeOnStack() == 1) - m_context << eth::Instruction::SWAP1; + m_context << eth::Instruction::SWAP1 << eth::Instruction::SUB; + // Stack for prefix: [ref...] (*ref)+-1 + // Stack for postfix: *ref [ref...] (*ref)+-1 + for (unsigned i = m_currentLValue->sizeOnStack(); i > 0; --i) + m_context << eth::swapInstruction(i); m_currentLValue->storeValue( *_unaryOperation.getType(), _unaryOperation.getLocation(), !_unaryOperation.isPrefixOperation()); @@ -307,7 +321,7 @@ bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation) bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation) { - CompilerContext::LocationSetter locationSetter(m_context, &_binaryOperation); + CompilerContext::LocationSetter locationSetter(m_context, _binaryOperation); Expression const& leftExpression = _binaryOperation.getLeftExpression(); Expression const& rightExpression = _binaryOperation.getRightExpression(); Type const& commonType = _binaryOperation.getCommonType(); @@ -354,7 +368,7 @@ bool ExpressionCompiler::visit(BinaryOperation const& _binaryOperation) bool ExpressionCompiler::visit(FunctionCall const& _functionCall) { - CompilerContext::LocationSetter locationSetter(m_context, &_functionCall); + CompilerContext::LocationSetter locationSetter(m_context, _functionCall); using Location = FunctionType::Location; if (_functionCall.isTypeConversion()) { @@ -405,7 +419,7 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) } _functionCall.getExpression().accept(*this); - m_context.appendJump(); + m_context.appendJump(eth::AssemblyItem::JumpType::IntoFunction); m_context << returnLabel; unsigned returnParametersSize = CompilerUtils::getSizeOnStack(function.getReturnParameterTypes()); @@ -528,8 +542,11 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) appendTypeConversion(*arguments[arg - 1]->getType(), *function.getParameterTypes()[arg - 1], true); } - m_context << u256(h256::Arith(dev::sha3(function.getCanonicalSignature(event.getName())))); - ++numIndexed; + if (!event.isAnonymous()) + { + m_context << u256(h256::Arith(dev::sha3(function.externalSignature(event.getName())))); + ++numIndexed; + } solAssert(numIndexed <= 4, "Too many indexed arguments."); // Copy all non-indexed arguments to memory (data) m_context << u256(0); @@ -550,10 +567,13 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall) case Location::SHA256: case Location::RIPEMD160: { + _functionCall.getExpression().accept(*this); static const map contractAddresses{{Location::ECRecover, 1}, {Location::SHA256, 2}, {Location::RIPEMD160, 3}}; m_context << contractAddresses.find(function.getLocation())->second; + for (unsigned i = function.getSizeOnStack(); i > 0; --i) + m_context << eth::swapInstruction(i); appendExternalFunctionCall(function, arguments, true); break; } @@ -572,7 +592,7 @@ bool ExpressionCompiler::visit(NewExpression const&) void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess) { - CompilerContext::LocationSetter locationSetter(m_context, &_memberAccess); + CompilerContext::LocationSetter locationSetter(m_context, _memberAccess); ASTString const& member = _memberAccess.getMemberName(); switch (_memberAccess.getExpression().getType()->getCategory()) { @@ -639,13 +659,18 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess) m_context << eth::Instruction::GASPRICE; else if (member == "data") m_context << u256(0) << eth::Instruction::CALLDATASIZE; + else if (member == "sig") + m_context << u256(0) << eth::Instruction::CALLDATALOAD + << (u256(0xffffffff) << (256 - 32)) << eth::Instruction::AND; else BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown magic member.")); break; case Type::Category::Struct: { StructType const& type = dynamic_cast(*_memberAccess.getExpression().getType()); - m_context << type.getStorageOffsetOfMember(member) << eth::Instruction::ADD; + m_context << eth::Instruction::POP; // structs always align to new slot + pair const& offsets = type.getStorageOffsetsOfMember(member); + m_context << offsets.first << eth::Instruction::ADD << u256(offsets.second); setLValueToStorageItem(_memberAccess); break; } @@ -707,110 +732,43 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess) bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) { - CompilerContext::LocationSetter locationSetter(m_context, &_indexAccess); + CompilerContext::LocationSetter locationSetter(m_context, _indexAccess); _indexAccess.getBaseExpression().accept(*this); Type const& baseType = *_indexAccess.getBaseExpression().getType(); if (baseType.getCategory() == Type::Category::Mapping) { + // storage byte offset is ignored for mappings, it should be zero. + m_context << eth::Instruction::POP; + // stack: storage_base_ref Type const& keyType = *dynamic_cast(baseType).getKeyType(); - m_context << u256(0); + m_context << u256(0); // memory position solAssert(_indexAccess.getIndexExpression(), "Index expression expected."); appendExpressionCopyToMemory(keyType, *_indexAccess.getIndexExpression()); - solAssert(baseType.getSizeOnStack() == 1, - "Unexpected: Not exactly one stack slot taken by subscriptable expression."); m_context << eth::Instruction::SWAP1; appendTypeMoveToMemory(IntegerType(256)); m_context << u256(0) << eth::Instruction::SHA3; + m_context << u256(0); setLValueToStorageItem( _indexAccess); } else if (baseType.getCategory() == Type::Category::Array) { - // stack layout: [] ArrayType const& arrayType = dynamic_cast(baseType); solAssert(_indexAccess.getIndexExpression(), "Index expression expected."); - ArrayType::Location location = arrayType.getLocation(); - eth::Instruction load = - location == ArrayType::Location::Storage ? eth::Instruction::SLOAD : - location == ArrayType::Location::Memory ? eth::Instruction::MLOAD : - eth::Instruction::CALLDATALOAD; + + // remove storage byte offset + if (arrayType.getLocation() == ArrayType::Location::Storage) + m_context << eth::Instruction::POP; _indexAccess.getIndexExpression()->accept(*this); - // retrieve length - if (!arrayType.isDynamicallySized()) - m_context << arrayType.getLength(); - else if (location == ArrayType::Location::CallData) - // length is stored on the stack - m_context << eth::Instruction::SWAP1; - else - m_context << eth::Instruction::DUP2 << load; - // stack: - // check out-of-bounds access - m_context << eth::Instruction::DUP2 << eth::Instruction::LT; - eth::AssemblyItem legalAccess = m_context.appendConditionalJump(); - // out-of-bounds access throws exception (just STOP for now) - m_context << eth::Instruction::STOP; - - m_context << legalAccess; - // stack: - if (arrayType.isByteArray()) - // byte array is packed differently, especially in storage - switch (location) - { - case ArrayType::Location::Storage: - // byte array index storage lvalue on stack (goal): - // = - m_context << u256(32) << eth::Instruction::SWAP2; - CompilerUtils(m_context).computeHashStatic(); - // stack: 32 index data_ref - m_context - << eth::Instruction::DUP3 << eth::Instruction::DUP3 - << eth::Instruction::DIV << eth::Instruction::ADD - // stack: 32 index (data_ref + index / 32) - << eth::Instruction::SWAP2 << eth::Instruction::SWAP1 << eth::Instruction::MOD; - setLValue(_indexAccess); - break; - case ArrayType::Location::CallData: - // no lvalue, just retrieve the value - m_context - << eth::Instruction::ADD << eth::Instruction::CALLDATALOAD - << u256(0) << eth::Instruction::BYTE; - break; - case ArrayType::Location::Memory: - solAssert(false, "Memory lvalues not yet implemented."); - } - else + // stack layout: [] + ArrayUtils(m_context).accessIndex(arrayType); + if (arrayType.getLocation() == ArrayType::Location::Storage) { - u256 elementSize = - location == ArrayType::Location::Storage ? - arrayType.getBaseType()->getStorageSize() : - arrayType.getBaseType()->getCalldataEncodedSize(); - solAssert(elementSize != 0, "Invalid element size."); - if (elementSize > 1) - m_context << elementSize << eth::Instruction::MUL; - if (arrayType.isDynamicallySized()) - { - if (location == ArrayType::Location::Storage) - { - m_context << eth::Instruction::SWAP1; - CompilerUtils(m_context).computeHashStatic(); - } - else if (location == ArrayType::Location::Memory) - m_context << u256(32) << eth::Instruction::ADD; - } - m_context << eth::Instruction::ADD; - switch (location) - { - case ArrayType::Location::CallData: - if (arrayType.getBaseType()->isValueType()) - CompilerUtils(m_context).loadFromMemoryDynamic(*arrayType.getBaseType(), true, true, false); - break; - case ArrayType::Location::Storage: + if (arrayType.isByteArray()) + setLValue(_indexAccess); + else setLValueToStorageItem(_indexAccess); - break; - case ArrayType::Location::Memory: - solAssert(false, "Memory lvalues not yet implemented."); - } } } else @@ -821,18 +779,34 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess) void ExpressionCompiler::endVisit(Identifier const& _identifier) { + CompilerContext::LocationSetter locationSetter(m_context, _identifier); Declaration const* declaration = _identifier.getReferencedDeclaration(); if (MagicVariableDeclaration const* magicVar = dynamic_cast(declaration)) { - if (magicVar->getType()->getCategory() == Type::Category::Contract) + switch (magicVar->getType()->getCategory()) + { + case Type::Category::Contract: // "this" or "super" if (!dynamic_cast(*magicVar->getType()).isSuper()) m_context << eth::Instruction::ADDRESS; + break; + case Type::Category::Integer: + // "now" + m_context << eth::Instruction::TIMESTAMP; + break; + default: + break; + } } else if (FunctionDefinition const* functionDef = dynamic_cast(declaration)) m_context << m_context.getVirtualFunctionEntryLabel(*functionDef).pushTag(); - else if (dynamic_cast(declaration)) - setLValueFromDeclaration(*declaration, _identifier); + else if (auto variable = dynamic_cast(declaration)) + { + if (!variable->isConstant()) + setLValueFromDeclaration(*declaration, _identifier); + else + variable->getValue()->accept(*this); + } else if (dynamic_cast(declaration)) { // no-op @@ -860,11 +834,12 @@ void ExpressionCompiler::endVisit(Identifier const& _identifier) void ExpressionCompiler::endVisit(Literal const& _literal) { + CompilerContext::LocationSetter locationSetter(m_context, _literal); switch (_literal.getType()->getCategory()) { case Type::Category::IntegerConstant: case Type::Category::Bool: - case Type::Category::String: + case Type::Category::FixedBytes: m_context << _literal.getType()->literalValue(&_literal); break; default: @@ -1115,7 +1090,7 @@ void ExpressionCompiler::setLValueFromDeclaration(Declaration const& _declaratio if (m_context.isLocalVariable(&_declaration)) setLValue(_expression, _declaration); else if (m_context.isStateVariable(&_declaration)) - setLValue(_expression, _declaration); + setLValue(_expression, _declaration); else BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_expression.getLocation()) diff --git a/libsolidity/ExpressionCompiler.h b/libsolidity/ExpressionCompiler.h index 9cab757ea..2577d21b5 100644 --- a/libsolidity/ExpressionCompiler.h +++ b/libsolidity/ExpressionCompiler.h @@ -42,7 +42,6 @@ class CompilerContext; class Type; class IntegerType; class ArrayType; -class StaticStringType; /** * Compiler for expressions, i.e. converts an AST tree whose root is an Expression into a stream @@ -140,7 +139,6 @@ void ExpressionCompiler::setLValue(Expression const& _expression, _Arguments con m_currentLValue = move(lvalue); else lvalue->retrieveValue(_expression.getLocation(), true); - } } diff --git a/libsolidity/GlobalContext.cpp b/libsolidity/GlobalContext.cpp index 60de5105f..80cebd760 100644 --- a/libsolidity/GlobalContext.cpp +++ b/libsolidity/GlobalContext.cpp @@ -37,26 +37,27 @@ GlobalContext::GlobalContext(): m_magicVariables(vector>{make_shared("block", make_shared(MagicType::Kind::Block)), make_shared("msg", make_shared(MagicType::Kind::Message)), make_shared("tx", make_shared(MagicType::Kind::Transaction)), + make_shared("now", make_shared(256)), make_shared("suicide", make_shared(strings{"address"}, strings{}, FunctionType::Location::Suicide)), make_shared("sha3", - make_shared(strings(), strings{"hash"}, FunctionType::Location::SHA3, true)), + make_shared(strings(), strings{"bytes32"}, FunctionType::Location::SHA3, true)), make_shared("log0", - make_shared(strings{"hash"},strings{}, FunctionType::Location::Log0)), + make_shared(strings{"bytes32"}, strings{}, FunctionType::Location::Log0)), make_shared("log1", - make_shared(strings{"hash", "hash"},strings{}, FunctionType::Location::Log1)), + make_shared(strings{"bytes32", "bytes32"}, strings{}, FunctionType::Location::Log1)), make_shared("log2", - make_shared(strings{"hash", "hash", "hash"},strings{}, FunctionType::Location::Log2)), + make_shared(strings{"bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Location::Log2)), make_shared("log3", - make_shared(strings{"hash", "hash", "hash", "hash"},strings{}, FunctionType::Location::Log3)), + make_shared(strings{"bytes32", "bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Location::Log3)), make_shared("log4", - make_shared(strings{"hash", "hash", "hash", "hash", "hash"},strings{}, FunctionType::Location::Log4)), + make_shared(strings{"bytes32", "bytes32", "bytes32", "bytes32", "bytes32"}, strings{}, FunctionType::Location::Log4)), make_shared("sha256", - make_shared(strings(), strings{"hash"}, FunctionType::Location::SHA256, true)), + make_shared(strings(), strings{"bytes32"}, FunctionType::Location::SHA256, true)), make_shared("ecrecover", - make_shared(strings{"hash", "hash8", "hash", "hash"}, strings{"address"}, FunctionType::Location::ECRecover)), + make_shared(strings{"bytes32", "uint8", "bytes32", "bytes32"}, strings{"address"}, FunctionType::Location::ECRecover)), make_shared("ripemd160", - make_shared(strings(), strings{"hash160"}, FunctionType::Location::RIPEMD160, true))}) + make_shared(strings(), strings{"bytes20"}, FunctionType::Location::RIPEMD160, true))}) { } diff --git a/libsolidity/InterfaceHandler.cpp b/libsolidity/InterfaceHandler.cpp index 99a7db96e..aacbbfd72 100644 --- a/libsolidity/InterfaceHandler.cpp +++ b/libsolidity/InterfaceHandler.cpp @@ -70,6 +70,7 @@ std::unique_ptr InterfaceHandler::getABIInterface(ContractDefinitio Json::Value event; event["type"] = "event"; event["name"] = it->getName(); + event["anonymous"] = it->isAnonymous(); Json::Value params(Json::arrayValue); for (auto const& p: it->getParameters()) { @@ -128,7 +129,7 @@ std::unique_ptr InterfaceHandler::getUserDocumentation(ContractDefi if (!m_notice.empty()) {// since @notice is the only user tag if missing function should not appear user["notice"] = Json::Value(m_notice); - methods[it.second->getCanonicalSignature()] = user; + methods[it.second->externalSignature()] = user; } } } @@ -174,8 +175,17 @@ std::unique_ptr InterfaceHandler::getDevDocumentation(ContractDefin method["author"] = m_author; Json::Value params(Json::objectValue); + std::vector paramNames = it.second->getParameterNames(); for (auto const& pair: m_params) + { + if (find(paramNames.begin(), paramNames.end(), pair.first) == paramNames.end()) + // LTODO: mismatching parameter name, throw some form of warning and not just an exception + BOOST_THROW_EXCEPTION( + DocstringParsingError() << + errinfo_comment("documented parameter \"" + pair.first + "\" not found found in the function") + ); params[pair.first] = pair.second; + } if (!m_params.empty()) method["params"] = params; @@ -184,7 +194,7 @@ std::unique_ptr InterfaceHandler::getDevDocumentation(ContractDefin method["return"] = m_return; if (!method.empty()) // add the function, only if we have any documentation to add - methods[it.second->getCanonicalSignature()] = method; + methods[it.second->externalSignature()] = method; } } doc["methods"] = methods; diff --git a/libsolidity/LValue.cpp b/libsolidity/LValue.cpp index a036be80b..234072bce 100644 --- a/libsolidity/LValue.cpp +++ b/libsolidity/LValue.cpp @@ -77,7 +77,8 @@ void StackVariable::setToZero(SourceLocation const& _location, bool) const StorageItem::StorageItem(CompilerContext& _compilerContext, Declaration const& _declaration): StorageItem(_compilerContext, *_declaration.getType()) { - m_context << m_context.getStorageLocationOfVariable(_declaration); + auto const& location = m_context.getStorageLocationOfVariable(_declaration); + m_context << location.first << u256(location.second); } StorageItem::StorageItem(CompilerContext& _compilerContext, Type const& _type): @@ -86,62 +87,78 @@ StorageItem::StorageItem(CompilerContext& _compilerContext, Type const& _type): if (m_dataType.isValueType()) { solAssert(m_dataType.getStorageSize() == m_dataType.getSizeOnStack(), ""); - solAssert(m_dataType.getStorageSize() <= numeric_limits::max(), - "The storage size of " + m_dataType.toString() + " should fit in an unsigned"); - m_size = unsigned(m_dataType.getStorageSize()); + solAssert(m_dataType.getStorageSize() == 1, "Invalid storage size."); } - else - m_size = 0; // unused } void StorageItem::retrieveValue(SourceLocation const&, bool _remove) const { + // stack: storage_key storage_offset if (!m_dataType.isValueType()) return; // no distinction between value and reference for non-value types if (!_remove) - m_context << eth::Instruction::DUP1; - if (m_size == 1) - m_context << eth::Instruction::SLOAD; + CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack()); + if (m_dataType.getStorageBytes() == 32) + m_context << eth::Instruction::POP << eth::Instruction::SLOAD; else - for (unsigned i = 0; i < m_size; ++i) - { - m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD << eth::Instruction::SWAP1; - if (i + 1 < m_size) - m_context << u256(1) << eth::Instruction::ADD; - else - m_context << eth::Instruction::POP; - } + { + m_context + << eth::Instruction::SWAP1 << eth::Instruction::SLOAD << eth::Instruction::SWAP1 + << u256(0x100) << eth::Instruction::EXP << eth::Instruction::SWAP1 << eth::Instruction::DIV; + if (m_dataType.getCategory() == Type::Category::FixedBytes) + m_context << (u256(0x1) << (256 - 8 * m_dataType.getStorageBytes())) << eth::Instruction::MUL; + else + m_context << ((u256(0x1) << (8 * m_dataType.getStorageBytes())) - 1) << eth::Instruction::AND; + } } void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _location, bool _move) const { - // stack layout: value value ... value target_ref + // stack: value storage_key storage_offset if (m_dataType.isValueType()) { - if (!_move) // copy values + solAssert(m_dataType.getStorageBytes() <= 32, "Invalid storage bytes size."); + solAssert(m_dataType.getStorageBytes() > 0, "Invalid storage bytes size."); + if (m_dataType.getStorageBytes() == 32) { - if (m_size + 1 > 16) - BOOST_THROW_EXCEPTION(CompilerError() - << errinfo_sourceLocation(_location) << errinfo_comment("Stack too deep.")); - for (unsigned i = 0; i < m_size; ++i) - m_context << eth::dupInstruction(m_size + 1) << eth::Instruction::SWAP1; + // offset should be zero + m_context << eth::Instruction::POP; + if (!_move) + m_context << eth::Instruction::DUP2 << eth::Instruction::SWAP1; + m_context << eth::Instruction::SSTORE; } - if (m_size > 1) // store high index value first - m_context << u256(m_size - 1) << eth::Instruction::ADD; - for (unsigned i = 0; i < m_size; ++i) + else { - if (i + 1 >= m_size) - m_context << eth::Instruction::SSTORE; - else - // stack here: value value ... value value (target_ref+offset) - m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2 - << eth::Instruction::SSTORE - << u256(1) << eth::Instruction::SWAP1 << eth::Instruction::SUB; + // OR the value into the other values in the storage slot + m_context << u256(0x100) << eth::Instruction::EXP; + // stack: value storage_ref multiplier + // fetch old value + m_context << eth::Instruction::DUP2 << eth::Instruction::SLOAD; + // stack: value storege_ref multiplier old_full_value + // clear bytes in old value + m_context + << eth::Instruction::DUP2 << ((u256(1) << (8 * m_dataType.getStorageBytes())) - 1) + << eth::Instruction::MUL; + m_context << eth::Instruction::NOT << eth::Instruction::AND; + // stack: value storage_ref multiplier cleared_value + m_context + << eth::Instruction::SWAP1 << eth::Instruction::DUP4; + // stack: value storage_ref cleared_value multiplier value + if (m_dataType.getCategory() == Type::Category::FixedBytes) + m_context + << (u256(0x1) << (256 - 8 * dynamic_cast(m_dataType).getNumBytes())) + << eth::Instruction::SWAP1 << eth::Instruction::DIV; + m_context << eth::Instruction::MUL << eth::Instruction::OR; + // stack: value storage_ref updated_value + m_context << eth::Instruction::SWAP1 << eth::Instruction::SSTORE; + if (_move) + m_context << eth::Instruction::POP; } } else { - solAssert(_sourceType.getCategory() == m_dataType.getCategory(), + solAssert( + _sourceType.getCategory() == m_dataType.getCategory(), "Wrong type conversation for assignment."); if (m_dataType.getCategory() == Type::Category::Array) { @@ -149,11 +166,12 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc dynamic_cast(m_dataType), dynamic_cast(_sourceType)); if (_move) - m_context << eth::Instruction::POP; + CompilerUtils(m_context).popStackElement(_sourceType); } else if (m_dataType.getCategory() == Type::Category::Struct) { - // stack layout: source_ref target_ref + // stack layout: source_ref source_offset target_ref target_offset + // note that we have structs, so offsets should be zero and are ignored auto const& structType = dynamic_cast(m_dataType); solAssert(structType == _sourceType, "Struct assignment with conversion."); for (auto const& member: structType.getMembers()) @@ -162,26 +180,37 @@ void StorageItem::storeValue(Type const& _sourceType, SourceLocation const& _loc TypePointer const& memberType = member.second; if (memberType->getCategory() == Type::Category::Mapping) continue; - m_context << structType.getStorageOffsetOfMember(member.first) - << eth::Instruction::DUP3 << eth::Instruction::DUP2 << eth::Instruction::ADD; - // stack: source_ref target_ref member_offset source_member_ref + pair const& offsets = structType.getStorageOffsetsOfMember(member.first); + m_context + << offsets.first << u256(offsets.second) + << eth::Instruction::DUP6 << eth::Instruction::DUP3 + << eth::Instruction::ADD << eth::Instruction::DUP2; + // stack: source_ref source_off target_ref target_off member_slot_offset member_byte_offset source_member_ref source_member_off StorageItem(m_context, *memberType).retrieveValue(_location, true); - // stack: source_ref target_ref member_offset source_value... - m_context << eth::dupInstruction(2 + memberType->getSizeOnStack()) - << eth::dupInstruction(2 + memberType->getSizeOnStack()) << eth::Instruction::ADD; - // stack: source_ref target_ref member_offset source_value... target_member_ref + // stack: source_ref source_off target_ref target_off member_offset source_value... + solAssert(4 + memberType->getSizeOnStack() <= 16, "Stack too deep."); + m_context + << eth::dupInstruction(4 + memberType->getSizeOnStack()) + << eth::dupInstruction(3 + memberType->getSizeOnStack()) << eth::Instruction::ADD + << eth::dupInstruction(2 + memberType->getSizeOnStack()); + // stack: source_ref source_off target_ref target_off member_slot_offset member_byte_offset source_value... target_member_ref target_member_byte_off StorageItem(m_context, *memberType).storeValue(*memberType, _location, true); - m_context << eth::Instruction::POP; + m_context << eth::Instruction::POP << eth::Instruction::POP; } if (_move) - m_context << eth::Instruction::POP; + m_context + << eth::Instruction::POP << eth::Instruction::POP + << eth::Instruction::POP << eth::Instruction::POP; else - m_context << eth::Instruction::SWAP1; - m_context << eth::Instruction::POP; + m_context + << eth::Instruction::SWAP2 << eth::Instruction::POP + << eth::Instruction::SWAP2 << eth::Instruction::POP; } else - BOOST_THROW_EXCEPTION(InternalCompilerError() - << errinfo_sourceLocation(_location) << errinfo_comment("Invalid non-value type for assignment.")); + BOOST_THROW_EXCEPTION( + InternalCompilerError() + << errinfo_sourceLocation(_location) + << errinfo_comment("Invalid non-value type for assignment.")); } } @@ -190,12 +219,13 @@ void StorageItem::setToZero(SourceLocation const&, bool _removeReference) const if (m_dataType.getCategory() == Type::Category::Array) { if (!_removeReference) - m_context << eth::Instruction::DUP1; + CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack()); ArrayUtils(m_context).clearArray(dynamic_cast(m_dataType)); } else if (m_dataType.getCategory() == Type::Category::Struct) { - // stack layout: ref + // stack layout: storage_key storage_offset + // @todo this can be improved for packed types auto const& structType = dynamic_cast(m_dataType); for (auto const& member: structType.getMembers()) { @@ -203,38 +233,48 @@ void StorageItem::setToZero(SourceLocation const&, bool _removeReference) const TypePointer const& memberType = member.second; if (memberType->getCategory() == Type::Category::Mapping) continue; - m_context << structType.getStorageOffsetOfMember(member.first) - << eth::Instruction::DUP2 << eth::Instruction::ADD; + pair const& offsets = structType.getStorageOffsetsOfMember(member.first); + m_context + << offsets.first << eth::Instruction::DUP3 << eth::Instruction::ADD + << u256(offsets.second); StorageItem(m_context, *memberType).setToZero(); } if (_removeReference) - m_context << eth::Instruction::POP; + m_context << eth::Instruction::POP << eth::Instruction::POP; } else { solAssert(m_dataType.isValueType(), "Clearing of unsupported type requested: " + m_dataType.toString()); - if (m_size == 0 && _removeReference) - m_context << eth::Instruction::POP; - else if (m_size == 1) + // @todo actually use offset + if (!_removeReference) + CompilerUtils(m_context).copyToStackTop(sizeOnStack(), sizeOnStack()); + if (m_dataType.getStorageBytes() == 32) + { + // offset should be zero m_context - << u256(0) << (_removeReference ? eth::Instruction::SWAP1 : eth::Instruction::DUP2) - << eth::Instruction::SSTORE; + << eth::Instruction::POP << u256(0) + << eth::Instruction::SWAP1 << eth::Instruction::SSTORE; + } else { - if (!_removeReference) - m_context << eth::Instruction::DUP1; - for (unsigned i = 0; i < m_size; ++i) - if (i + 1 >= m_size) - m_context << u256(0) << eth::Instruction::SWAP1 << eth::Instruction::SSTORE; - else - m_context << u256(0) << eth::Instruction::DUP2 << eth::Instruction::SSTORE - << u256(1) << eth::Instruction::ADD; + m_context << u256(0x100) << eth::Instruction::EXP; + // stack: storage_ref multiplier + // fetch old value + m_context << eth::Instruction::DUP2 << eth::Instruction::SLOAD; + // stack: storege_ref multiplier old_full_value + // clear bytes in old value + m_context + << eth::Instruction::SWAP1 << ((u256(1) << (8 * m_dataType.getStorageBytes())) - 1) + << eth::Instruction::MUL; + m_context << eth::Instruction::NOT << eth::Instruction::AND; + // stack: storage_ref cleared_value + m_context << eth::Instruction::SWAP1 << eth::Instruction::SSTORE; } } } /// Used in StorageByteArrayElement -static IntegerType byteType(8, IntegerType::Modifier::Hash); +static FixedBytesType byteType(1); StorageByteArrayElement::StorageByteArrayElement(CompilerContext& _compilerContext): LValue(_compilerContext, byteType) @@ -250,6 +290,7 @@ void StorageByteArrayElement::retrieveValue(SourceLocation const&, bool _remove) else m_context << eth::Instruction::DUP2 << eth::Instruction::SLOAD << eth::Instruction::DUP2 << eth::Instruction::BYTE; + m_context << (u256(1) << (256 - 8)) << eth::Instruction::MUL; } void StorageByteArrayElement::storeValue(Type const&, SourceLocation const&, bool _move) const @@ -265,8 +306,9 @@ void StorageByteArrayElement::storeValue(Type const&, SourceLocation const&, boo m_context << eth::Instruction::DUP2 << u256(0xff) << eth::Instruction::MUL << eth::Instruction::NOT << eth::Instruction::AND; // stack: value ref (1<<(32-byte_number)) old_full_value_with_cleared_byte - m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP4 << eth::Instruction::MUL - << eth::Instruction::OR; + m_context << eth::Instruction::SWAP1; + m_context << (u256(1) << (256 - 8)) << eth::Instruction::DUP5 << eth::Instruction::DIV + << eth::Instruction::MUL << eth::Instruction::OR; // stack: value ref new_full_value m_context << eth::Instruction::SWAP1 << eth::Instruction::SSTORE; if (_move) @@ -297,6 +339,8 @@ StorageArrayLength::StorageArrayLength(CompilerContext& _compilerContext, const m_arrayType(_arrayType) { solAssert(m_arrayType.isDynamicallySized(), ""); + // storage byte offset must be zero + m_context << eth::Instruction::POP; } void StorageArrayLength::retrieveValue(SourceLocation const&, bool _remove) const diff --git a/libsolidity/LValue.h b/libsolidity/LValue.h index c57c80e37..ad6225162 100644 --- a/libsolidity/LValue.h +++ b/libsolidity/LValue.h @@ -98,7 +98,9 @@ private: }; /** - * Reference to some item in storage. The (starting) position of the item is stored on the stack. + * Reference to some item in storage. On the stack this is , + * where 0 <= offset_inside_value < 32 and an offset of i means that the value is multiplied + * by 2**i before storing it. */ class StorageItem: public LValue { @@ -107,6 +109,7 @@ public: StorageItem(CompilerContext& _compilerContext, Declaration const& _declaration); /// Constructs the LValue and assumes that the storage reference is already on the stack. StorageItem(CompilerContext& _compilerContext, Type const& _type); + virtual unsigned sizeOnStack() const { return 2; } virtual void retrieveValue(SourceLocation const& _location, bool _remove = false) const override; virtual void storeValue( Type const& _sourceType, @@ -117,11 +120,6 @@ public: SourceLocation const& _location = SourceLocation(), bool _removeReference = true ) const override; - -private: - /// Number of stack elements occupied by the value (not the reference). - /// Only used for value types. - unsigned m_size; }; /** diff --git a/libsolidity/Parser.cpp b/libsolidity/Parser.cpp index 44d111591..5c7676df5 100644 --- a/libsolidity/Parser.cpp +++ b/libsolidity/Parser.cpp @@ -164,8 +164,17 @@ ASTPointer Parser::parseContractDefinition() } nodeFactory.markEndPosition(); expectToken(Token::RBrace); - return nodeFactory.createNode(name, docString, baseContracts, structs, enums, - stateVariables, functions, modifiers, events); + return nodeFactory.createNode( + name, + docString, + baseContracts, + structs, + enums, + stateVariables, + functions, + modifiers, + events + ); } ASTPointer Parser::parseInheritanceSpecifier() @@ -247,8 +256,15 @@ ASTPointer Parser::parseFunctionDefinition(ASTString const* } else returnParameters = createEmptyParameterList(); - ASTPointer block = parseBlock(); - nodeFactory.setEndPositionFromNode(block); + ASTPointer block = ASTPointer(); + nodeFactory.markEndPosition(); + if (m_scanner->getCurrentToken() != Token::Semicolon) + { + block = parseBlock(); + nodeFactory.setEndPositionFromNode(block); + } + else + m_scanner->next(); // just consume the ';' bool const c_isConstructor = (_contractName && *name == *_contractName); return nodeFactory.createNode(name, visibility, c_isConstructor, docstring, parameters, isDeclaredConst, modifiers, @@ -317,6 +333,7 @@ ASTPointer Parser::parseVariableDeclaration( nodeFactory.setEndPositionFromNode(type); } bool isIndexed = false; + bool isDeclaredConst = false; ASTPointer identifier; Token::Value token = m_scanner->getCurrentToken(); Declaration::Visibility visibility(Declaration::Visibility::Default); @@ -327,7 +344,13 @@ ASTPointer Parser::parseVariableDeclaration( isIndexed = true; m_scanner->next(); } + if (token == Token::Const) + { + isDeclaredConst = true; + m_scanner->next(); + } nodeFactory.markEndPosition(); + if (_options.allowEmptyName && m_scanner->getCurrentToken() != Token::Identifier) { identifier = make_shared(""); @@ -348,7 +371,7 @@ ASTPointer Parser::parseVariableDeclaration( } return nodeFactory.createNode(type, identifier, value, visibility, _options.isStateVariable, - isIndexed); + isIndexed, isDeclaredConst); } ASTPointer Parser::parseModifierDefinition() @@ -387,9 +410,15 @@ ASTPointer Parser::parseEventDefinition() parameters = parseParameterList(true, true); else parameters = createEmptyParameterList(); + bool anonymous = false; + if (m_scanner->getCurrentToken() == Token::Anonymous) + { + anonymous = true; + m_scanner->next(); + } nodeFactory.markEndPosition(); expectToken(Token::Semicolon); - return nodeFactory.createNode(name, docstring, parameters); + return nodeFactory.createNode(name, docstring, parameters, anonymous); } ASTPointer Parser::parseModifierInvocation() @@ -913,6 +942,7 @@ Parser::LookAheadInfo Parser::peekStatementType() const // In all other cases, we have an expression statement. Token::Value token(m_scanner->getCurrentToken()); bool mightBeTypeName = (Token::isElementaryTypeName(token) || token == Token::Identifier); + if (token == Token::Mapping || token == Token::Var || (mightBeTypeName && m_scanner->peekNextToken() == Token::Identifier)) return LookAheadInfo::VariableDeclarationStatement; diff --git a/libsolidity/Parser.h b/libsolidity/Parser.h index 87eb2f8ff..08c47c252 100644 --- a/libsolidity/Parser.h +++ b/libsolidity/Parser.h @@ -34,6 +34,8 @@ class Scanner; class Parser { public: + Parser() {} + ASTPointer parse(std::shared_ptr const& _scanner); std::shared_ptr const& getSourceName() const; @@ -64,8 +66,7 @@ private: ASTPointer parseStructDefinition(); ASTPointer parseEnumDefinition(); ASTPointer parseEnumValue(); - ASTPointer parseVariableDeclaration( - VarDeclParserOptions const& _options = VarDeclParserOptions(), + ASTPointer parseVariableDeclaration(VarDeclParserOptions const& _options = VarDeclParserOptions(), ASTPointer const& _lookAheadArrayType = ASTPointer()); ASTPointer parseModifierDefinition(); ASTPointer parseEventDefinition(); diff --git a/libsolidity/Token.h b/libsolidity/Token.h index 85979b566..1435dcc57 100644 --- a/libsolidity/Token.h +++ b/libsolidity/Token.h @@ -143,8 +143,8 @@ namespace solidity \ /* Keywords */ \ K(Break, "break", 0) \ - K(Case, "case", 0) \ K(Const, "constant", 0) \ + K(Anonymous, "anonymous", 0) \ K(Continue, "continue", 0) \ K(Contract, "contract", 0) \ K(Default, "default", 0) \ @@ -167,7 +167,6 @@ namespace solidity K(Return, "return", 0) \ K(Returns, "returns", 0) \ K(Struct, "struct", 0) \ - K(Switch, "switch", 0) \ K(Var, "var", 0) \ K(While, "while", 0) \ K(Enum, "enum", 0) \ @@ -252,77 +251,43 @@ namespace solidity K(UInt240, "uint240", 0) \ K(UInt248, "uint248", 0) \ K(UInt256, "uint256", 0) \ - K(Hash, "hash", 0) \ - K(Hash8, "hash8", 0) \ - K(Hash16, "hash16", 0) \ - K(Hash24, "hash24", 0) \ - K(Hash32, "hash32", 0) \ - K(Hash40, "hash40", 0) \ - K(Hash48, "hash48", 0) \ - K(Hash56, "hash56", 0) \ - K(Hash64, "hash64", 0) \ - K(Hash72, "hash72", 0) \ - K(Hash80, "hash80", 0) \ - K(Hash88, "hash88", 0) \ - K(Hash96, "hash96", 0) \ - K(Hash104, "hash104", 0) \ - K(Hash112, "hash112", 0) \ - K(Hash120, "hash120", 0) \ - K(Hash128, "hash128", 0) \ - K(Hash136, "hash136", 0) \ - K(Hash144, "hash144", 0) \ - K(Hash152, "hash152", 0) \ - K(Hash160, "hash160", 0) \ - K(Hash168, "hash168", 0) \ - K(Hash176, "hash178", 0) \ - K(Hash184, "hash184", 0) \ - K(Hash192, "hash192", 0) \ - K(Hash200, "hash200", 0) \ - K(Hash208, "hash208", 0) \ - K(Hash216, "hash216", 0) \ - K(Hash224, "hash224", 0) \ - K(Hash232, "hash232", 0) \ - K(Hash240, "hash240", 0) \ - K(Hash248, "hash248", 0) \ - K(Hash256, "hash256", 0) \ + K(Bytes0, "bytes0", 0) \ + K(Bytes1, "bytes1", 0) \ + K(Bytes2, "bytes2", 0) \ + K(Bytes3, "bytes3", 0) \ + K(Bytes4, "bytes4", 0) \ + K(Bytes5, "bytes5", 0) \ + K(Bytes6, "bytes6", 0) \ + K(Bytes7, "bytes7", 0) \ + K(Bytes8, "bytes8", 0) \ + K(Bytes9, "bytes9", 0) \ + K(Bytes10, "bytes10", 0) \ + K(Bytes11, "bytes11", 0) \ + K(Bytes12, "bytes12", 0) \ + K(Bytes13, "bytes13", 0) \ + K(Bytes14, "bytes14", 0) \ + K(Bytes15, "bytes15", 0) \ + K(Bytes16, "bytes16", 0) \ + K(Bytes17, "bytes17", 0) \ + K(Bytes18, "bytes18", 0) \ + K(Bytes19, "bytes19", 0) \ + K(Bytes20, "bytes20", 0) \ + K(Bytes21, "bytes21", 0) \ + K(Bytes22, "bytes22", 0) \ + K(Bytes23, "bytes23", 0) \ + K(Bytes24, "bytes24", 0) \ + K(Bytes25, "bytes25", 0) \ + K(Bytes26, "bytes26", 0) \ + K(Bytes27, "bytes27", 0) \ + K(Bytes28, "bytes28", 0) \ + K(Bytes29, "bytes29", 0) \ + K(Bytes30, "bytes30", 0) \ + K(Bytes31, "bytes31", 0) \ + K(Bytes32, "bytes32", 0) \ + K(Bytes, "bytes", 0) \ + K(Byte, "byte", 0) \ K(Address, "address", 0) \ K(Bool, "bool", 0) \ - K(Bytes, "bytes", 0) \ - K(StringType, "string", 0) \ - K(String0, "string0", 0) \ - K(String1, "string1", 0) \ - K(String2, "string2", 0) \ - K(String3, "string3", 0) \ - K(String4, "string4", 0) \ - K(String5, "string5", 0) \ - K(String6, "string6", 0) \ - K(String7, "string7", 0) \ - K(String8, "string8", 0) \ - K(String9, "string9", 0) \ - K(String10, "string10", 0) \ - K(String11, "string11", 0) \ - K(String12, "string12", 0) \ - K(String13, "string13", 0) \ - K(String14, "string14", 0) \ - K(String15, "string15", 0) \ - K(String16, "string16", 0) \ - K(String17, "string17", 0) \ - K(String18, "string18", 0) \ - K(String19, "string19", 0) \ - K(String20, "string20", 0) \ - K(String21, "string21", 0) \ - K(String22, "string22", 0) \ - K(String23, "string23", 0) \ - K(String24, "string24", 0) \ - K(String25, "string25", 0) \ - K(String26, "string26", 0) \ - K(String27, "string27", 0) \ - K(String28, "string28", 0) \ - K(String29, "string29", 0) \ - K(String30, "string30", 0) \ - K(String31, "string31", 0) \ - K(String32, "string32", 0) \ - K(Text, "text", 0) \ K(Real, "real", 0) \ K(UReal, "ureal", 0) \ T(TypesEnd, NULL, 0) /* used as type enum end marker */ \ @@ -338,6 +303,16 @@ namespace solidity /* Identifiers (not keywords or future reserved words). */ \ T(Identifier, NULL, 0) \ \ + /* Keywords reserved for future. use*/ \ + T(String, "string", 0) \ + K(Case, "case", 0) \ + K(Switch, "switch", 0) \ + K(Throw, "throw", 0) \ + K(Try, "try", 0) \ + K(Catch, "catch", 0) \ + K(Using, "using", 0) \ + K(Type, "type", 0) \ + K(TypeOf, "typeof", 0) \ /* Illegal token - not able to scan. */ \ T(Illegal, "ILLEGAL", 0) \ \ diff --git a/libsolidity/Types.cpp b/libsolidity/Types.cpp index 454d79d9b..78649cc95 100644 --- a/libsolidity/Types.cpp +++ b/libsolidity/Types.cpp @@ -20,14 +20,14 @@ * Solidity data types */ +#include +#include +#include #include #include #include -#include #include -#include - using namespace std; namespace dev @@ -35,28 +35,113 @@ namespace dev namespace solidity { +void StorageOffsets::computeOffsets(TypePointers const& _types) +{ + bigint slotOffset = 0; + unsigned byteOffset = 0; + map> offsets; + for (size_t i = 0; i < _types.size(); ++i) + { + TypePointer const& type = _types[i]; + if (!type->canBeStored()) + continue; + if (byteOffset + type->getStorageBytes() > 32) + { + // would overflow, go to next slot + ++slotOffset; + byteOffset = 0; + } + if (slotOffset >= bigint(1) << 256) + BOOST_THROW_EXCEPTION(TypeError() << errinfo_comment("Object too large for storage.")); + offsets[i] = make_pair(u256(slotOffset), byteOffset); + solAssert(type->getStorageSize() >= 1, "Invalid storage size."); + if (type->getStorageSize() == 1 && byteOffset + type->getStorageBytes() <= 32) + byteOffset += type->getStorageBytes(); + else + { + slotOffset += type->getStorageSize(); + byteOffset = 0; + } + } + if (byteOffset > 0) + ++slotOffset; + if (slotOffset >= bigint(1) << 256) + BOOST_THROW_EXCEPTION(TypeError() << errinfo_comment("Object too large for storage.")); + m_storageSize = u256(slotOffset); + swap(m_offsets, offsets); +} + +pair const* StorageOffsets::getOffset(size_t _index) const +{ + if (m_offsets.count(_index)) + return &m_offsets.at(_index); + else + return nullptr; +} + +MemberList& MemberList::operator=(MemberList&& _other) +{ + m_memberTypes = std::move(_other.m_memberTypes); + m_storageOffsets = std::move(_other.m_storageOffsets); + return *this; +} + +std::pair const* MemberList::getMemberStorageOffset(string const& _name) const +{ + if (!m_storageOffsets) + { + TypePointers memberTypes; + memberTypes.reserve(m_memberTypes.size()); + for (auto const& nameAndType: m_memberTypes) + memberTypes.push_back(nameAndType.second); + m_storageOffsets.reset(new StorageOffsets()); + m_storageOffsets->computeOffsets(memberTypes); + } + for (size_t index = 0; index < m_memberTypes.size(); ++index) + if (m_memberTypes[index].first == _name) + return m_storageOffsets->getOffset(index); + return nullptr; +} + +u256 const& MemberList::getStorageSize() const +{ + // trigger lazy computation + getMemberStorageOffset(""); + return m_storageOffsets->getStorageSize(); +} + TypePointer Type::fromElementaryTypeName(Token::Value _typeToken) { - solAssert(Token::isElementaryTypeName(_typeToken), "Elementary type name expected."); + char const* tokenCstr = Token::toString(_typeToken); + solAssert(Token::isElementaryTypeName(_typeToken), + "Expected an elementary type name but got " + ((tokenCstr) ? std::string(Token::toString(_typeToken)) : "")); - if (Token::Int <= _typeToken && _typeToken <= Token::Hash256) + if (Token::Int <= _typeToken && _typeToken <= Token::Bytes32) { int offset = _typeToken - Token::Int; int bytes = offset % 33; - if (bytes == 0) + if (bytes == 0 && _typeToken != Token::Bytes0) bytes = 32; int modifier = offset / 33; - return make_shared(bytes * 8, - modifier == 0 ? IntegerType::Modifier::Signed : - modifier == 1 ? IntegerType::Modifier::Unsigned : - IntegerType::Modifier::Hash); + switch(modifier) + { + case 0: + return make_shared(bytes * 8, IntegerType::Modifier::Signed); + case 1: + return make_shared(bytes * 8, IntegerType::Modifier::Unsigned); + case 2: + return make_shared(bytes); + default: + solAssert(false, "Unexpected modifier value. Should never happen"); + return TypePointer(); + } } + else if (_typeToken == Token::Byte) + return make_shared(1); else if (_typeToken == Token::Address) return make_shared(0, IntegerType::Modifier::Address); else if (_typeToken == Token::Bool) return make_shared(); - else if (Token::String0 <= _typeToken && _typeToken <= Token::String32) - return make_shared(int(_typeToken) - int(Token::String0)); else if (_typeToken == Token::Bytes) return make_shared(ArrayType::Location::Storage); else @@ -99,6 +184,8 @@ TypePointer Type::fromArrayTypeName(TypeName& _baseTypeName, Expression* _length TypePointer baseType = _baseTypeName.toType(); if (!baseType) BOOST_THROW_EXCEPTION(_baseTypeName.createTypeError("Invalid type name.")); + if (baseType->getStorageBytes() == 0) + BOOST_THROW_EXCEPTION(_baseTypeName.createTypeError("Illegal base type of storage size zero for array.")); if (_length) { if (!_length->getType()) @@ -123,7 +210,7 @@ TypePointer Type::forLiteral(Literal const& _literal) return make_shared(_literal); case Token::StringLiteral: //@todo put larger strings into dynamic strings - return StaticStringType::smallestTypeForLiteral(_literal.getValue()); + return FixedBytesType::smallestTypeForLiteral(_literal.getValue()); default: return shared_ptr(); } @@ -139,7 +226,7 @@ TypePointer Type::commonType(TypePointer const& _a, TypePointer const& _b) return TypePointer(); } -const MemberList Type::EmptyMemberList = MemberList(); +const MemberList Type::EmptyMemberList; IntegerType::IntegerType(int _bits, IntegerType::Modifier _modifier): m_bits(_bits), m_modifier(_modifier) @@ -159,8 +246,6 @@ bool IntegerType::isImplicitlyConvertibleTo(Type const& _convertTo) const return false; if (isAddress()) return convertTo.isAddress(); - else if (isHash()) - return convertTo.isHash(); else if (isSigned()) return convertTo.isSigned(); else @@ -169,14 +254,10 @@ bool IntegerType::isImplicitlyConvertibleTo(Type const& _convertTo) const bool IntegerType::isExplicitlyConvertibleTo(Type const& _convertTo) const { - if (_convertTo.getCategory() == Category::String) - { - StaticStringType const& convertTo = dynamic_cast(_convertTo); - return isHash() && (m_bits == convertTo.getNumBytes() * 8); - } return _convertTo.getCategory() == getCategory() || - _convertTo.getCategory() == Category::Contract || - _convertTo.getCategory() == Category::Enum; + _convertTo.getCategory() == Category::Contract || + _convertTo.getCategory() == Category::Enum || + _convertTo.getCategory() == Category::FixedBytes; } TypePointer IntegerType::unaryOperatorResult(Token::Value _operator) const @@ -187,16 +268,10 @@ TypePointer IntegerType::unaryOperatorResult(Token::Value _operator) const // no further unary operators for addresses else if (isAddress()) return TypePointer(); - // "~" is ok for all other types - else if (_operator == Token::BitNot) - return shared_from_this(); - // nothing else for hashes - else if (isHash()) - return TypePointer(); - // for non-hash integers, we allow +, -, ++ and -- + // for non-address integers, we allow +, -, ++ and -- else if (_operator == Token::Add || _operator == Token::Sub || _operator == Token::Inc || _operator == Token::Dec || - _operator == Token::After) + _operator == Token::After || _operator == Token::BitNot) return shared_from_this(); else return TypePointer(); @@ -214,7 +289,7 @@ string IntegerType::toString() const { if (isAddress()) return "address"; - string prefix = isHash() ? "hash" : (isSigned() ? "int" : "uint"); + string prefix = isSigned() ? "int" : "uint"; return prefix + dev::toString(m_bits); } @@ -230,20 +305,18 @@ TypePointer IntegerType::binaryOperatorResult(Token::Value _operator, TypePointe // All integer types can be compared if (Token::isCompareOp(_operator)) return commonType; - - // Nothing else can be done with addresses, but hashes can receive bit operators + // Nothing else can be done with addresses if (commonType->isAddress()) return TypePointer(); - else if (commonType->isHash() && !Token::isBitOp(_operator)) - return TypePointer(); - else - return commonType; + + return commonType; } -const MemberList IntegerType::AddressMemberList = - MemberList({{"balance", make_shared(256)}, - {"call", make_shared(strings(), strings(), FunctionType::Location::Bare, true)}, - {"send", make_shared(strings{"uint"}, strings{}, FunctionType::Location::Send)}}); +const MemberList IntegerType::AddressMemberList({ + {"balance", make_shared(256)}, + {"call", make_shared(strings(), strings(), FunctionType::Location::Bare, true)}, + {"send", make_shared(strings{"uint"}, strings{}, FunctionType::Location::Send)} +}); IntegerConstantType::IntegerConstantType(Literal const& _literal) { @@ -284,8 +357,17 @@ IntegerConstantType::IntegerConstantType(Literal const& _literal) bool IntegerConstantType::isImplicitlyConvertibleTo(Type const& _convertTo) const { - TypePointer integerType = getIntegerType(); - return integerType && integerType->isImplicitlyConvertibleTo(_convertTo); + shared_ptr integerType = getIntegerType(); + if (!integerType) + return false; + + if (_convertTo.getCategory() == Category::FixedBytes) + { + FixedBytesType const& convertTo = dynamic_cast(_convertTo); + return convertTo.getNumBytes() * 8 >= integerType->getNumBits(); + } + + return integerType->isImplicitlyConvertibleTo(_convertTo); } bool IntegerConstantType::isExplicitlyConvertibleTo(Type const& _convertTo) const @@ -433,50 +515,67 @@ shared_ptr IntegerConstantType::getIntegerType() const : IntegerType::Modifier::Unsigned); } -shared_ptr StaticStringType::smallestTypeForLiteral(string const& _literal) +shared_ptr FixedBytesType::smallestTypeForLiteral(string const& _literal) { if (_literal.length() <= 32) - return make_shared(_literal.length()); - return shared_ptr(); + return make_shared(_literal.length()); + return shared_ptr(); } -StaticStringType::StaticStringType(int _bytes): m_bytes(_bytes) +FixedBytesType::FixedBytesType(int _bytes): m_bytes(_bytes) { solAssert(m_bytes >= 0 && m_bytes <= 32, - "Invalid byte number for static string type: " + dev::toString(m_bytes)); + "Invalid byte number for fixed bytes type: " + dev::toString(m_bytes)); } -bool StaticStringType::isImplicitlyConvertibleTo(Type const& _convertTo) const +bool FixedBytesType::isImplicitlyConvertibleTo(Type const& _convertTo) const { if (_convertTo.getCategory() != getCategory()) return false; - StaticStringType const& convertTo = dynamic_cast(_convertTo); + FixedBytesType const& convertTo = dynamic_cast(_convertTo); return convertTo.m_bytes >= m_bytes; } -bool StaticStringType::isExplicitlyConvertibleTo(Type const& _convertTo) const +bool FixedBytesType::isExplicitlyConvertibleTo(Type const& _convertTo) const { - if (_convertTo.getCategory() == getCategory()) - return true; - if (_convertTo.getCategory() == Category::Integer) - { - IntegerType const& convertTo = dynamic_cast(_convertTo); - if (convertTo.isHash() && (m_bytes * 8 == convertTo.getNumBits())) - return true; - } + return _convertTo.getCategory() == Category::Integer || + _convertTo.getCategory() == Category::Contract || + _convertTo.getCategory() == getCategory(); +} - return false; +TypePointer FixedBytesType::unaryOperatorResult(Token::Value _operator) const +{ + // "delete" and "~" is okay for FixedBytesType + if (_operator == Token::Delete) + return make_shared(); + else if (_operator == Token::BitNot) + return shared_from_this(); + + return TypePointer(); +} + +TypePointer FixedBytesType::binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const +{ + auto commonType = dynamic_pointer_cast(Type::commonType(shared_from_this(), _other)); + if (!commonType) + return TypePointer(); + + // FixedBytes can be compared and have bitwise operators applied to them + if (Token::isCompareOp(_operator) || Token::isBitOp(_operator)) + return commonType; + + return TypePointer(); } -bool StaticStringType::operator==(Type const& _other) const +bool FixedBytesType::operator==(Type const& _other) const { if (_other.getCategory() != getCategory()) return false; - StaticStringType const& other = dynamic_cast(_other); + FixedBytesType const& other = dynamic_cast(_other); return other.m_bytes == m_bytes; } -u256 StaticStringType::literalValue(const Literal* _literal) const +u256 FixedBytesType::literalValue(const Literal* _literal) const { solAssert(_literal, ""); u256 value = 0; @@ -603,13 +702,21 @@ u256 ArrayType::getStorageSize() const { if (isDynamicallySized()) return 1; - else + + bigint size; + unsigned baseBytes = getBaseType()->getStorageBytes(); + if (baseBytes == 0) + size = 1; + else if (baseBytes < 32) { - bigint size = bigint(getLength()) * getBaseType()->getStorageSize(); - if (size >= bigint(1) << 256) - BOOST_THROW_EXCEPTION(TypeError() << errinfo_comment("Array too large for storage.")); - return max(1, u256(size)); + unsigned itemsPerSlot = 32 / baseBytes; + size = (bigint(getLength()) + (itemsPerSlot - 1)) / itemsPerSlot; } + else + size = bigint(getLength()) * getBaseType()->getStorageSize(); + if (size >= bigint(1) << 256) + BOOST_THROW_EXCEPTION(TypeError() << errinfo_comment("Array too large for storage.")); + return max(1, u256(size)); } unsigned ArrayType::getSizeOnStack() const @@ -617,6 +724,9 @@ unsigned ArrayType::getSizeOnStack() const if (m_location == Location::CallData) // offset [length] (stack top) return 1 + (isDynamicallySized() ? 1 : 0); + else if (m_location == Location::Storage) + // storage_key storage_offset + return 2; else // offset return 1; @@ -632,6 +742,23 @@ string ArrayType::toString() const return ret + "]"; } +TypePointer ArrayType::externalType() const +{ + if (m_location != Location::CallData) + return TypePointer(); + if (m_isByteArray) + return shared_from_this(); + if (!m_baseType->externalType()) + return TypePointer(); + if (m_baseType->getCategory() == Category::Array && m_baseType->isDynamicallySized()) + return TypePointer(); + + if (isDynamicallySized()) + return std::make_shared(Location::CallData, m_baseType->externalType()); + else + return std::make_shared(Location::CallData, m_baseType->externalType(), m_length); +} + shared_ptr ArrayType::copyForLocation(ArrayType::Location _location) const { auto copy = make_shared(_location); @@ -645,7 +772,7 @@ shared_ptr ArrayType::copyForLocation(ArrayType::Location _location) return copy; } -const MemberList ArrayType::s_arrayTypeMemberList = MemberList({{"length", make_shared(256)}}); +const MemberList ArrayType::s_arrayTypeMemberList({{"length", make_shared(256)}}); bool ContractType::operator==(Type const& _other) const { @@ -706,6 +833,26 @@ u256 ContractType::getFunctionIdentifier(string const& _functionName) const return Invalid256; } +vector> ContractType::getStateVariables() const +{ + vector variables; + for (ContractDefinition const* contract: boost::adaptors::reverse(m_contract.getLinearizedBaseContracts())) + for (ASTPointer const& variable: contract->getStateVariables()) + if (!variable->isConstant()) + variables.push_back(variable.get()); + TypePointers types; + for (auto variable: variables) + types.push_back(variable->getType()); + StorageOffsets offsets; + offsets.computeOffsets(types); + + vector> variablesAndOffsets; + for (size_t index = 0; index < variables.size(); ++index) + if (auto const* offset = offsets.getOffset(index)) + variablesAndOffsets.push_back(make_tuple(variables[index], offset->first, offset->second)); + return variablesAndOffsets; +} + TypePointer StructType::unaryOperatorResult(Token::Value _operator) const { return _operator == Token::Delete ? make_shared() : TypePointer(); @@ -721,12 +868,7 @@ bool StructType::operator==(Type const& _other) const u256 StructType::getStorageSize() const { - bigint size = 0; - for (pair const& member: getMembers()) - size += member.second->getStorageSize(); - if (size >= bigint(1) << 256) - BOOST_THROW_EXCEPTION(TypeError() << errinfo_comment("Struct too large for storage.")); - return max(1, u256(size)); + return max(1, getMembers().getStorageSize()); } bool StructType::canLiveOutsideStorage() const @@ -755,17 +897,11 @@ MemberList const& StructType::getMembers() const return *m_members; } -u256 StructType::getStorageOffsetOfMember(string const& _name) const +pair const& StructType::getStorageOffsetsOfMember(string const& _name) const { - //@todo cache member offset? - u256 offset; - for (ASTPointer const& variable: m_struct.getMembers()) - { - if (variable->getName() == _name) - return offset; - offset += variable->getType()->getStorageSize(); - } - BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Storage offset of non-existing member requested.")); + auto const* offsets = getMembers().getMemberStorageOffset(_name); + solAssert(offsets, "Storage offset of non-existing member requested."); + return *offsets; } TypePointer EnumType::unaryOperatorResult(Token::Value _operator) const @@ -781,6 +917,15 @@ bool EnumType::operator==(Type const& _other) const return other.m_enum == m_enum; } +unsigned EnumType::getStorageBytes() const +{ + size_t elements = m_enum.getMembers().size(); + if (elements <= 1) + return 1; + else + return dev::bytesRequired(elements - 1); +} + string EnumType::toString() const { return string("enum ") + m_enum.getName(); @@ -925,6 +1070,13 @@ string FunctionType::toString() const return name + ")"; } +u256 FunctionType::getStorageSize() const +{ + BOOST_THROW_EXCEPTION( + InternalCompilerError() + << errinfo_comment("Storage size of non-storable function type requested.")); +} + unsigned FunctionType::getSizeOnStack() const { Location location = m_location; @@ -946,6 +1098,26 @@ unsigned FunctionType::getSizeOnStack() const return size; } +TypePointer FunctionType::externalType() const +{ + TypePointers paramTypes; + TypePointers retParamTypes; + + for (auto type: m_parameterTypes) + { + if (!type->externalType()) + return TypePointer(); + paramTypes.push_back(type->externalType()); + } + for (auto type: m_returnParameterTypes) + { + if (!type->externalType()) + return TypePointer(); + retParamTypes.push_back(type->externalType()); + } + return make_shared(paramTypes, retParamTypes, m_location, m_arbitraryParameters); +} + MemberList const& FunctionType::getMembers() const { switch (m_location) @@ -975,7 +1147,7 @@ MemberList const& FunctionType::getMembers() const } } -string FunctionType::getCanonicalSignature(std::string const& _name) const +string FunctionType::externalSignature(std::string const& _name) const { std::string funcName = _name; if (_name == "") @@ -985,8 +1157,12 @@ string FunctionType::getCanonicalSignature(std::string const& _name) const } string ret = funcName + "("; - for (auto it = m_parameterTypes.cbegin(); it != m_parameterTypes.cend(); ++it) - ret += (*it)->toString() + (it + 1 == m_parameterTypes.cend() ? "" : ","); + TypePointers externalParameterTypes = dynamic_cast(*externalType()).getParameterTypes(); + for (auto it = externalParameterTypes.cbegin(); it != externalParameterTypes.cend(); ++it) + { + solAssert(!!(*it), "Parameter should have external type"); + ret += (*it)->toString() + (it + 1 == externalParameterTypes.cend() ? "" : ","); + } return ret + ")"; } @@ -1047,6 +1223,13 @@ string MappingType::toString() const return "mapping(" + getKeyType()->toString() + " => " + getValueType()->toString() + ")"; } +u256 VoidType::getStorageSize() const +{ + BOOST_THROW_EXCEPTION( + InternalCompilerError() + << errinfo_comment("Storage size of non-storable void type requested.")); +} + bool TypeType::operator==(Type const& _other) const { if (_other.getCategory() != getCategory()) @@ -1055,6 +1238,13 @@ bool TypeType::operator==(Type const& _other) const return *getActualType() == *other.getActualType(); } +u256 TypeType::getStorageSize() const +{ + BOOST_THROW_EXCEPTION( + InternalCompilerError() + << errinfo_comment("Storage size of non-storable type type requested.")); +} + MemberList const& TypeType::getMembers() const { // We need to lazy-initialize it because of recursive references. @@ -1092,6 +1282,13 @@ ModifierType::ModifierType(const ModifierDefinition& _modifier) swap(params, m_parameterTypes); } +u256 ModifierType::getStorageSize() const +{ + BOOST_THROW_EXCEPTION( + InternalCompilerError() + << errinfo_comment("Storage size of non-storable type type requested.")); +} + bool ModifierType::operator==(Type const& _other) const { if (_other.getCategory() != getCategory()) @@ -1122,22 +1319,29 @@ MagicType::MagicType(MagicType::Kind _kind): switch (m_kind) { case Kind::Block: - m_members = MemberList({{"coinbase", make_shared(0, IntegerType::Modifier::Address)}, - {"timestamp", make_shared(256)}, - {"blockhash", make_shared(strings{"uint"}, strings{"hash"}, FunctionType::Location::BlockHash)}, - {"difficulty", make_shared(256)}, - {"number", make_shared(256)}, - {"gaslimit", make_shared(256)}}); + m_members = move(MemberList({ + {"coinbase", make_shared(0, IntegerType::Modifier::Address)}, + {"timestamp", make_shared(256)}, + {"blockhash", make_shared(strings{"uint"}, strings{"bytes32"}, FunctionType::Location::BlockHash)}, + {"difficulty", make_shared(256)}, + {"number", make_shared(256)}, + {"gaslimit", make_shared(256)} + })); break; case Kind::Message: - m_members = MemberList({{"sender", make_shared(0, IntegerType::Modifier::Address)}, - {"gas", make_shared(256)}, - {"value", make_shared(256)}, - {"data", make_shared(ArrayType::Location::CallData)}}); + m_members = move(MemberList({ + {"sender", make_shared(0, IntegerType::Modifier::Address)}, + {"gas", make_shared(256)}, + {"value", make_shared(256)}, + {"data", make_shared(ArrayType::Location::CallData)}, + {"sig", make_shared(4)} + })); break; case Kind::Transaction: - m_members = MemberList({{"origin", make_shared(0, IntegerType::Modifier::Address)}, - {"gasprice", make_shared(256)}}); + m_members = move(MemberList({ + {"origin", make_shared(0, IntegerType::Modifier::Address)}, + {"gasprice", make_shared(256)} + })); break; default: BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown kind of magic.")); diff --git a/libsolidity/Types.h b/libsolidity/Types.h index b19f75406..5fa09f690 100644 --- a/libsolidity/Types.h +++ b/libsolidity/Types.h @@ -43,6 +43,26 @@ using TypePointer = std::shared_ptr; using FunctionTypePointer = std::shared_ptr; using TypePointers = std::vector; + +/** + * Helper class to compute storage offsets of members of structs and contracts. + */ +class StorageOffsets +{ +public: + /// Resets the StorageOffsets objects and determines the position in storage for each + /// of the elements of @a _types. + void computeOffsets(TypePointers const& _types); + /// @returns the offset of the given member, might be null if the member is not part of storage. + std::pair const* getOffset(size_t _index) const; + /// @returns the total number of slots occupied by all members. + u256 const& getStorageSize() const { return m_storageSize; } + +private: + u256 m_storageSize; + std::map> m_offsets; +}; + /** * List of members of a type. */ @@ -53,6 +73,7 @@ public: MemberList() {} explicit MemberList(MemberMap const& _members): m_memberTypes(_members) {} + MemberList& operator=(MemberList&& _other); TypePointer getMemberType(std::string const& _name) const { for (auto const& it: m_memberTypes) @@ -60,12 +81,18 @@ public: return it.second; return TypePointer(); } + /// @returns the offset of the given member in storage slots and bytes inside a slot or + /// a nullptr if the member is not part of storage. + std::pair const* getMemberStorageOffset(std::string const& _name) const; + /// @returns the number of storage slots occupied by the members. + u256 const& getStorageSize() const; MemberMap::const_iterator begin() const { return m_memberTypes.begin(); } MemberMap::const_iterator end() const { return m_memberTypes.end(); } private: MemberMap m_memberTypes; + mutable std::unique_ptr m_storageOffsets; }; /** @@ -77,12 +104,12 @@ public: enum class Category { Integer, IntegerConstant, Bool, Real, Array, - String, Contract, Struct, Function, OverloadedFunctions, Enum, + FixedBytes, Contract, Struct, Function, OverloadedFunctions, Enum, Mapping, Void, TypeType, Modifier, Magic }; - ///@{ - ///@name Factory functions + /// @{ + /// @name Factory functions /// Factory functions that convert an AST @ref TypeName to a Type. static TypePointer fromElementaryTypeName(Token::Value _typeToken); static TypePointer fromElementaryTypeName(std::string const& _name); @@ -97,6 +124,8 @@ public: /// @returns a pointer to _a or _b if the other is implicitly convertible to it or nullptr otherwise static TypePointer commonType(TypePointer const& _a, TypePointer const& _b); + /// Calculates the + virtual Category getCategory() const = 0; virtual bool isImplicitlyConvertibleTo(Type const& _other) const { return *this == _other; } virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const @@ -126,9 +155,15 @@ public: unsigned getCalldataEncodedSize() const { return getCalldataEncodedSize(true); } /// @returns true if the type is dynamically encoded in calldata virtual bool isDynamicallySized() const { return false; } - /// @returns number of bytes required to hold this value in storage. + /// @returns the number of storage slots required to hold this value in storage. /// For dynamically "allocated" types, it returns the size of the statically allocated head, virtual u256 getStorageSize() const { return 1; } + /// Multiple small types can be packed into a single storage slot. If such a packing is possible + /// this function @returns the size in bytes smaller than 32. Data is moved to the next slot if + /// it does not fit. + /// In order to avoid computation at runtime of whether such moving is necessary, structs and + /// array data (not each element) always start a new slot. + virtual unsigned getStorageBytes() const { return 32; } /// Returns true if the type can be stored in storage. virtual bool canBeStored() const { return true; } /// Returns false if the type cannot live outside the storage, i.e. if it includes some mapping. @@ -152,20 +187,24 @@ public: "for type without literals.")); } + /// @returns a type suitable for outside of Solidity, i.e. for contract types it returns address. + /// If there is no such type, returns an empty shared pointer. + virtual TypePointer externalType() const { return TypePointer(); } + protected: /// Convenience object used when returning an empty member list. static const MemberList EmptyMemberList; }; /** - * Any kind of integer type including hash and address. + * Any kind of integer type (signed, unsigned, address). */ class IntegerType: public Type { public: enum class Modifier { - Unsigned, Signed, Hash, Address + Unsigned, Signed, Address }; virtual Category getCategory() const override { return Category::Integer; } @@ -179,14 +218,16 @@ public: virtual bool operator==(Type const& _other) const override; virtual unsigned getCalldataEncodedSize(bool _padded = true) const override { return _padded ? 32 : m_bits / 8; } + virtual unsigned getStorageBytes() const override { return m_bits / 8; } virtual bool isValueType() const override { return true; } - virtual MemberList const& getMembers() const { return isAddress() ? AddressMemberList : EmptyMemberList; } + virtual MemberList const& getMembers() const override { return isAddress() ? AddressMemberList : EmptyMemberList; } virtual std::string toString() const override; + virtual TypePointer externalType() const override { return shared_from_this(); } + int getNumBits() const { return m_bits; } - bool isHash() const { return m_modifier == Modifier::Hash || m_modifier == Modifier::Address; } bool isAddress() const { return m_modifier == Modifier::Address; } bool isSigned() const { return m_modifier == Modifier::Signed; } @@ -232,28 +273,32 @@ private: }; /** - * String type with fixed length, up to 32 bytes. + * Bytes type with fixed length of up to 32 bytes. */ -class StaticStringType: public Type +class FixedBytesType: public Type { public: - virtual Category getCategory() const override { return Category::String; } + virtual Category getCategory() const override { return Category::FixedBytes; } - /// @returns the smallest string type for the given literal or an empty pointer + /// @returns the smallest bytes type for the given literal or an empty pointer /// if no type fits. - static std::shared_ptr smallestTypeForLiteral(std::string const& _literal); + static std::shared_ptr smallestTypeForLiteral(std::string const& _literal); - explicit StaticStringType(int _bytes); + explicit FixedBytesType(int _bytes); virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; virtual bool operator==(Type const& _other) const override; + virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; + virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const override; virtual unsigned getCalldataEncodedSize(bool _padded) const override { return _padded && m_bytes > 0 ? 32 : m_bytes; } + virtual unsigned getStorageBytes() const override { return m_bytes; } virtual bool isValueType() const override { return true; } - virtual std::string toString() const override { return "string" + dev::toString(m_bytes); } + virtual std::string toString() const override { return "bytes" + dev::toString(m_bytes); } virtual u256 literalValue(Literal const* _literal) const override; + virtual TypePointer externalType() const override { return shared_from_this(); } int getNumBytes() const { return m_bytes; } @@ -273,16 +318,21 @@ public: virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const override; - virtual unsigned getCalldataEncodedSize(bool _padded) const { return _padded ? 32 : 1; } + virtual unsigned getCalldataEncodedSize(bool _padded) const override{ return _padded ? 32 : 1; } + virtual unsigned getStorageBytes() const override { return 1; } virtual bool isValueType() const override { return true; } virtual std::string toString() const override { return "bool"; } virtual u256 literalValue(Literal const* _literal) const override; + virtual TypePointer externalType() const override { return shared_from_this(); } }; /** * The type of an array. The flavours are byte array (bytes), statically- ([]) * and dynamically-sized array ([]). + * In storage, all arrays are packed tightly (as long as more than one elementary type fits in + * one slot). Dynamically sized arrays (including byte arrays) start with their size as a uint and + * thus start on their own slot. */ class ArrayType: public Type { @@ -293,13 +343,22 @@ public: /// Constructor for a byte array ("bytes") explicit ArrayType(Location _location): - m_location(_location), m_isByteArray(true), m_baseType(std::make_shared(8)) {} + m_location(_location), + m_isByteArray(true), + m_baseType(std::make_shared(8)) + {} /// Constructor for a dynamically sized array type ("type[]") ArrayType(Location _location, const TypePointer &_baseType): - m_location(_location), m_baseType(_baseType) {} + m_location(_location), + m_baseType(_baseType) + {} /// Constructor for a fixed-size array type ("type[20]") ArrayType(Location _location, const TypePointer &_baseType, u256 const& _length): - m_location(_location), m_baseType(_baseType), m_hasDynamicLength(false), m_length(_length) {} + m_location(_location), + m_baseType(_baseType), + m_hasDynamicLength(false), + m_length(_length) + {} virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override; virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; @@ -310,6 +369,7 @@ public: virtual unsigned getSizeOnStack() const override; virtual std::string toString() const override; virtual MemberList const& getMembers() const override { return s_arrayTypeMemberList; } + virtual TypePointer externalType() const override; Location getLocation() const { return m_location; } bool isByteArray() const { return m_isByteArray; } @@ -344,10 +404,12 @@ public: virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; virtual bool operator==(Type const& _other) const override; + virtual unsigned getStorageBytes() const override { return 20; } virtual bool isValueType() const override { return true; } virtual std::string toString() const override; virtual MemberList const& getMembers() const override; + virtual TypePointer externalType() const override { return std::make_shared(160, IntegerType::Modifier::Address); } bool isSuper() const { return m_super; } ContractDefinition const& getContractDefinition() const { return m_contract; } @@ -360,6 +422,10 @@ public: /// not exist. u256 getFunctionIdentifier(std::string const& _functionName) const; + /// @returns a list of all state variables (including inherited) of the contract and their + /// offsets in storage. + std::vector> getStateVariables() const; + private: ContractDefinition const& m_contract; /// If true, it is the "super" type of the current contract, i.e. it contains only inherited @@ -383,12 +449,12 @@ public: virtual bool operator==(Type const& _other) const override; virtual u256 getStorageSize() const override; virtual bool canLiveOutsideStorage() const override; - virtual unsigned getSizeOnStack() const override { return 1; /*@todo*/ } + virtual unsigned getSizeOnStack() const override { return 2; } virtual std::string toString() const override; virtual MemberList const& getMembers() const override; - u256 getStorageOffsetOfMember(std::string const& _name) const; + std::pair const& getStorageOffsetsOfMember(std::string const& _name) const; private: StructDefinition const& m_struct; @@ -407,10 +473,12 @@ public: virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; virtual bool operator==(Type const& _other) const override; virtual unsigned getSizeOnStack() const override { return 1; } + virtual unsigned getStorageBytes() const override; virtual std::string toString() const override; virtual bool isValueType() const override { return true; } virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override; + virtual TypePointer externalType() const override { return std::make_shared(8 * int(getStorageBytes())); } EnumDefinition const& getEnumDefinition() const { return m_enum; } /// @returns the value that the string has in the Enum @@ -443,6 +511,11 @@ public: Bare }; virtual Category getCategory() const override { return Category::Function; } + + /// @returns TypePointer of a new FunctionType object. All input/return parameters are an appropriate external types of input/return parameters of current function. + /// Returns an empty shared pointer if one of the input/return parameters does not have an externaltype. + virtual TypePointer externalType() const override; + explicit FunctionType(FunctionDefinition const& _function, bool _isInternal = true); explicit FunctionType(VariableDeclaration const& _varDecl); explicit FunctionType(EventDefinition const& _event); @@ -476,16 +549,16 @@ public: virtual bool operator==(Type const& _other) const override; virtual std::string toString() const override; virtual bool canBeStored() const override { return false; } - virtual u256 getStorageSize() const override { BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Storage size of non-storable function type requested.")); } + virtual u256 getStorageSize() const override; virtual bool canLiveOutsideStorage() const override { return false; } virtual unsigned getSizeOnStack() const override; virtual MemberList const& getMembers() const override; Location const& getLocation() const { return m_location; } - /// @returns the canonical signature of this function type given the function name + /// @returns the external signature of this function type given the function name /// If @a _name is not provided (empty string) then the @c m_declaration member of the /// function type is used - std::string getCanonicalSignature(std::string const& _name = "") const; + std::string externalSignature(std::string const& _name = "") const; Declaration const& getDeclaration() const { solAssert(m_declaration, "Requested declaration from a FunctionType that has none"); @@ -515,7 +588,7 @@ private: std::vector m_parameterNames; std::vector m_returnParameterNames; Location const m_location; - /// true iff the function takes an arbitrary number of arguments of arbitrary types + /// true if the function takes an arbitrary number of arguments of arbitrary types bool const m_arbitraryParameters = false; bool const m_gasSet = false; ///< true iff the gas value to be used is on the stack bool const m_valueSet = false; ///< true iff the value to be sent is on the stack @@ -540,6 +613,7 @@ private: /** * The type of a mapping, there is one distinct type per key/value type pair. + * Mappings always occupy their own storage slot, but do not actually use it. */ class MappingType: public Type { @@ -550,6 +624,7 @@ public: virtual bool operator==(Type const& _other) const override; virtual std::string toString() const override; + virtual unsigned getSizeOnStack() const override { return 2; } virtual bool canLiveOutsideStorage() const override { return false; } TypePointer const& getKeyType() const { return m_keyType; } @@ -573,7 +648,7 @@ public: virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); } virtual std::string toString() const override { return "void"; } virtual bool canBeStored() const override { return false; } - virtual u256 getStorageSize() const override { BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Storage size of non-storable void type requested.")); } + virtual u256 getStorageSize() const override; virtual bool canLiveOutsideStorage() const override { return false; } virtual unsigned getSizeOnStack() const override { return 0; } }; @@ -593,7 +668,7 @@ public: virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); } virtual bool operator==(Type const& _other) const override; virtual bool canBeStored() const override { return false; } - virtual u256 getStorageSize() const override { BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Storage size of non-storable type type requested.")); } + virtual u256 getStorageSize() const override; virtual bool canLiveOutsideStorage() const override { return false; } virtual unsigned getSizeOnStack() const override { return 0; } virtual std::string toString() const override { return "type(" + m_actualType->toString() + ")"; } @@ -619,7 +694,7 @@ public: virtual TypePointer binaryOperatorResult(Token::Value, TypePointer const&) const override { return TypePointer(); } virtual bool canBeStored() const override { return false; } - virtual u256 getStorageSize() const override { BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Storage size of non-storable type type requested.")); } + virtual u256 getStorageSize() const override; virtual bool canLiveOutsideStorage() const override { return false; } virtual unsigned getSizeOnStack() const override { return 0; } virtual bool operator==(Type const& _other) const override; diff --git a/libsolidity/Utils.h b/libsolidity/Utils.h index 1411f66b4..05c5fa6f0 100644 --- a/libsolidity/Utils.h +++ b/libsolidity/Utils.h @@ -22,34 +22,9 @@ #pragma once -#include -#include - -namespace dev -{ -namespace solidity -{ +#include /// Assertion that throws an InternalCompilerError containing the given description if it is not met. #define solAssert(CONDITION, DESCRIPTION) \ - ::dev::solidity::solAssertAux(CONDITION, DESCRIPTION, __LINE__, __FILE__, ETH_FUNC) - -inline void solAssertAux(bool _condition, std::string const& _errorDescription, unsigned _line, - char const* _file, char const* _function) -{ - if (!_condition) - ::boost::throw_exception( InternalCompilerError() - << errinfo_comment(_errorDescription) - << ::boost::throw_function(_function) - << ::boost::throw_file(_file) - << ::boost::throw_line(_line)); -} - -inline void solAssertAux(void const* _pointer, std::string const& _errorDescription, unsigned _line, - char const* _file, char const* _function) -{ - solAssertAux(_pointer != nullptr, _errorDescription, _line, _file, _function); -} + assertThrow(CONDITION, ::dev::solidity::InternalCompilerError, DESCRIPTION) -} -} diff --git a/libtestutils/BlockChainLoader.cpp b/libtestutils/BlockChainLoader.cpp new file mode 100644 index 000000000..ba0def59e --- /dev/null +++ b/libtestutils/BlockChainLoader.cpp @@ -0,0 +1,49 @@ +/* + 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 BlockChainLoader.cpp + * @author Marek Kotewicz + * @date 2015 + */ + +#include "BlockChainLoader.h" +#include "StateLoader.h" +#include "Common.h" + +using namespace std; +using namespace dev; +using namespace dev::test; +using namespace dev::eth; + +BlockChainLoader::BlockChainLoader(Json::Value const& _json) +{ + // load pre state + StateLoader sl(_json["pre"]); + m_state = sl.state(); + + // load genesisBlock + m_bc.reset(new BlockChain(fromHex(_json["genesisRLP"].asString()), m_dir.path(), WithExisting::Kill)); + + // load blocks + for (auto const& block: _json["blocks"]) + { + bytes rlp = fromHex(block["rlp"].asString()); + m_bc->import(rlp, m_state.db()); + } + + // sync state + m_state.sync(*m_bc); +} diff --git a/libtestutils/BlockChainLoader.h b/libtestutils/BlockChainLoader.h new file mode 100644 index 000000000..6cb04c53c --- /dev/null +++ b/libtestutils/BlockChainLoader.h @@ -0,0 +1,52 @@ +/* + 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 BlockChainLoader.h + * @author Marek Kotewicz + * @date 2015 + */ + +#pragma once +#include +#include +#include +#include +#include "TransientDirectory.h" + +namespace dev +{ +namespace test +{ + +/** + * @brief Should be used to load test blockchain from json file + * Loads the blockchain from json, creates temporary directory to store it, removes the directory on dealloc + */ +class BlockChainLoader +{ +public: + BlockChainLoader(Json::Value const& _json); + eth::BlockChain const& bc() const { return *m_bc; } + eth::State const& state() const { return m_state; } + +private: + TransientDirectory m_dir; + std::auto_ptr m_bc; + eth::State m_state; +}; + +} +} diff --git a/libtestutils/CMakeLists.txt b/libtestutils/CMakeLists.txt new file mode 100644 index 000000000..3ac4f34f8 --- /dev/null +++ b/libtestutils/CMakeLists.txt @@ -0,0 +1,42 @@ +cmake_policy(SET CMP0015 NEW) +# this policy was introduced in cmake 3.0 +# remove if, once 3.0 will be used on unix +if (${CMAKE_MAJOR_VERSION} GREATER 2) + # old policy do not use MACOSX_RPATH + cmake_policy(SET CMP0042 OLD) +endif() +set(CMAKE_AUTOMOC OFF) + +aux_source_directory(. SRC_LIST) + +include_directories(BEFORE ${JSONCPP_INCLUDE_DIRS}) +include_directories(BEFORE ..) +include_directories(${JSON_RPC_CPP_INCLUDE_DIRS}) +include_directories(${Boost_INCLUDE_DIRS}) + +set(EXECUTABLE testutils) + +file(GLOB HEADERS "*.h") + +if (NOT JSONRPC) + list(REMOVE_ITEM SRC_LIST "./FixedWebThreeServer.cpp") + list(REMOVE_ITEM HEADERS "./FixedWebThreeServer.h") +endif() + +if (ETH_STATIC) + add_library(${EXECUTABLE} STATIC ${SRC_LIST} ${HEADERS}) +else() + add_library(${EXECUTABLE} SHARED ${SRC_LIST} ${HEADERS}) +endif() + +target_link_libraries(${EXECUTABLE} ${Boost_FILESYSTEM_LIBRARIES}) +target_link_libraries(${EXECUTABLE} ${JSONCPP_LIBRARIES}) +target_link_libraries(${EXECUTABLE} ethereum) + +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/Common.cpp b/libtestutils/Common.cpp new file mode 100644 index 000000000..86f96f667 --- /dev/null +++ b/libtestutils/Common.cpp @@ -0,0 +1,80 @@ +/* + 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 Common.cpp + * @author Marek Kotewicz + * @date 2015 + */ + +#include +#include +#include +#include +#include "Common.h" + +using namespace std; +using namespace dev; +using namespace dev::test; + +std::string dev::test::getTestPath() +{ + string testPath; + const char* ptestPath = getenv("ETHEREUM_TEST_PATH"); + + if (ptestPath == NULL) + { + ctest << " could not find environment variable ETHEREUM_TEST_PATH \n"; + testPath = "../../../tests"; + } + else + testPath = ptestPath; + + return testPath; +} + +int dev::test::randomNumber() +{ + static std::mt19937 randomGenerator(time(0)); + randomGenerator.seed(std::random_device()()); + return std::uniform_int_distribution(1)(randomGenerator); +} + +Json::Value dev::test::loadJsonFromFile(std::string const& _path) +{ + Json::Reader reader; + Json::Value result; + string s = asString(dev::contents(_path)); + if (!s.length()) + ctest << "Contents of " + _path + " is empty. Have you cloned the 'tests' repo branch develop and set ETHEREUM_TEST_PATH to its path?"; + else + ctest << "FIXTURE: loaded test from file: " << _path; + + reader.parse(s, result); + return result; +} + +std::string dev::test::toTestFilePath(std::string const& _filename) +{ + return getTestPath() + "/" + _filename + ".json"; +} + +std::string dev::test::getRandomPath() +{ + std::stringstream stream; + stream << getDataDir("EthereumTests") << "/" << randomNumber(); + return stream.str(); +} + diff --git a/libtestutils/Common.h b/libtestutils/Common.h new file mode 100644 index 000000000..4757a3b7a --- /dev/null +++ b/libtestutils/Common.h @@ -0,0 +1,44 @@ +/* + 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 Common.h + * @author Marek Kotewicz + * @date 2015 + */ + +#pragma once + +#include +#include +#include + +namespace dev +{ +namespace test +{ + +struct TestChannel: public LogChannel { static const char* name() { return "TEST"; } }; +#define ctest dev::LogOutputStream() + +std::string getTestPath(); +int randomNumber(); +Json::Value loadJsonFromFile(std::string const& _path); +std::string toTestFilePath(std::string const& _filename); +std::string getRandomPath(); + +} + +} diff --git a/libtestutils/FixedClient.cpp b/libtestutils/FixedClient.cpp new file mode 100644 index 000000000..052141039 --- /dev/null +++ b/libtestutils/FixedClient.cpp @@ -0,0 +1,32 @@ +/* + 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 FixedClient.cpp + * @author Marek Kotewicz + * @date 2015 + */ + +#include "FixedClient.h" + +using namespace dev; +using namespace dev::eth; +using namespace dev::test; + +eth::State FixedClient::asOf(h256 const& _h) const +{ + ReadGuard l(x_stateDB); + return State(m_state.db(), bc(), _h); +} diff --git a/libtestutils/FixedClient.h b/libtestutils/FixedClient.h new file mode 100644 index 000000000..95acc3edb --- /dev/null +++ b/libtestutils/FixedClient.h @@ -0,0 +1,60 @@ +/* + 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 FixedClient.h + * @author Marek Kotewicz + * @date 2015 + */ + +#pragma once + +#include +#include +#include + +namespace dev +{ +namespace test +{ + +/** + * @brief mvp implementation of ClientBase + * Doesn't support mining interface + */ +class FixedClient: public dev::eth::ClientBase +{ +public: + FixedClient(eth::BlockChain const& _bc, eth::State _state) : m_bc(_bc), m_state(_state) {} + virtual ~FixedClient() {} + + // stub + virtual void flushTransactions() override {} + virtual eth::BlockChain const& bc() const override { return m_bc; } + using ClientBase::asOf; + virtual eth::State asOf(h256 const& _h) const override; + virtual eth::State preMine() const override { ReadGuard l(x_stateDB); return m_state; } + virtual eth::State postMine() const override { ReadGuard l(x_stateDB); return m_state; } + virtual void setAddress(Address _us) { WriteGuard l(x_stateDB); m_state.setAddress(_us); } + virtual void prepareForTransaction() override {} + +private: + eth::BlockChain const& m_bc; + eth::State m_state; + mutable SharedMutex x_stateDB; ///< Lock on the state DB, effectively a lock on m_postMine. +}; + +} +} diff --git a/libtestutils/FixedWebThreeServer.cpp b/libtestutils/FixedWebThreeServer.cpp new file mode 100644 index 000000000..c72a106c6 --- /dev/null +++ b/libtestutils/FixedWebThreeServer.cpp @@ -0,0 +1,22 @@ +/* + 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 FixedWebThreeStubServer.cpp + * @author Marek Kotewicz + * @date 2015 + */ + +#include "FixedWebThreeServer.h" diff --git a/libtestutils/FixedWebThreeServer.h b/libtestutils/FixedWebThreeServer.h new file mode 100644 index 000000000..33ccbf71e --- /dev/null +++ b/libtestutils/FixedWebThreeServer.h @@ -0,0 +1,57 @@ +/* + 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 FixedWebThreeStubServer.h + * @author Marek Kotewicz + * @date 2015 + */ + +#pragma once + +#include +#include + +/** + * @brief dummy JSON-RPC api implementation + * Should be used for test purposes only + * Supports eth && db interfaces + * Doesn't support shh && net interfaces + */ +class FixedWebThreeServer: public dev::WebThreeStubServerBase, public dev::WebThreeStubDatabaseFace +{ +public: + FixedWebThreeServer(jsonrpc::AbstractServerConnector& _conn, std::vector const& _accounts, dev::eth::Interface* _client): WebThreeStubServerBase(_conn, _accounts), m_client(_client) {}; + +private: + dev::eth::Interface* client() override { return m_client; } + std::shared_ptr face() override { BOOST_THROW_EXCEPTION(dev::InterfaceNotSupported("dev::shh::Interface")); } + dev::WebThreeNetworkFace* network() override { BOOST_THROW_EXCEPTION(dev::InterfaceNotSupported("dev::WebThreeNetworkFace")); } + dev::WebThreeStubDatabaseFace* db() override { return this; } + std::string get(std::string const& _name, std::string const& _key) override + { + std::string k(_name + "/" + _key); + return m_db[k]; + } + void put(std::string const& _name, std::string const& _key, std::string const& _value) override + { + std::string k(_name + "/" + _key); + m_db[k] = _value; + } + +private: + dev::eth::Interface* m_client; + std::map m_db; +}; diff --git a/libtestutils/StateLoader.cpp b/libtestutils/StateLoader.cpp new file mode 100644 index 000000000..464df0ec5 --- /dev/null +++ b/libtestutils/StateLoader.cpp @@ -0,0 +1,56 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . + */ +/** @file StateLoader.cpp + * @author Marek Kotewicz + * @date 2015 + */ + +#include "StateLoader.h" + +using namespace std; +using namespace dev; +using namespace dev::eth; +using namespace dev::test; + +StateLoader::StateLoader(Json::Value const& _json) +{ + for (string const& name: _json.getMemberNames()) + { + Json::Value o = _json[name]; + + Address address = Address(name); + bytes code = fromHex(o["code"].asString().substr(2)); + + if (code.size()) + { + m_state.m_cache[address] = Account(u256(o["balance"].asString()), Account::ContractConception); + m_state.m_cache[address].setCode(code); + } + else + m_state.m_cache[address] = Account(u256(o["balance"].asString()), Account::NormalCreation); + + for (string const& j: o["storage"].getMemberNames()) + m_state.setStorage(address, u256(j), u256(o["storage"][j].asString())); + + for (auto i = 0; i < u256(o["nonce"].asString()); ++i) + m_state.noteSending(address); + + m_state.ensureCached(address, false, false); + } + + m_state.commit(); +} diff --git a/libtestutils/StateLoader.h b/libtestutils/StateLoader.h new file mode 100644 index 000000000..e5843d0b4 --- /dev/null +++ b/libtestutils/StateLoader.h @@ -0,0 +1,45 @@ +/* + 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 StateLoader.h + * @author Marek Kotewicz + * @date 2015 + */ + +#pragma once + +#include +#include + +namespace dev +{ +namespace test +{ + +/** + * @brief Friend of State, loads State from given JSON object + */ +class StateLoader +{ +public: + StateLoader(Json::Value const& _json); + eth::State const& state() const { return m_state; } + +private: + eth::State m_state; +}; +} +} diff --git a/libtestutils/TransientDirectory.cpp b/libtestutils/TransientDirectory.cpp new file mode 100644 index 000000000..694784e25 --- /dev/null +++ b/libtestutils/TransientDirectory.cpp @@ -0,0 +1,46 @@ +/* + 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 TransientDirectory.cpp + * @author Marek Kotewicz + * @date 2015 + */ + +#include +#include +#include "TransientDirectory.h" +using namespace std; +using namespace dev; +using namespace dev::test; + +TransientDirectory::TransientDirectory(): + TransientDirectory((boost::filesystem::temp_directory_path() / "eth_transient" / toString(FixedHash<4>::random())).string()) +{} + +TransientDirectory::TransientDirectory(std::string const& _path): + m_path(_path) +{ + // we never ever want to delete a directory (including all its contents) that we did not create ourselves. + if (boost::filesystem::exists(m_path)) + BOOST_THROW_EXCEPTION(FileError()); + + boost::filesystem::create_directories(m_path); +} + +TransientDirectory::~TransientDirectory() +{ + boost::filesystem::remove_all(m_path); +} diff --git a/libtestutils/TransientDirectory.h b/libtestutils/TransientDirectory.h new file mode 100644 index 000000000..21a338e59 --- /dev/null +++ b/libtestutils/TransientDirectory.h @@ -0,0 +1,51 @@ +/* + 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 TransientDirectory.h + * @author Marek Kotewicz + * @date 2015 + */ + +#pragma once + +#include +#include "Common.h" + +namespace dev +{ +namespace test +{ + +/** + * @brief temporary directory implementation + * It creates temporary directory in the given path. On dealloc it removes the directory + * @throws if the given path already exists, throws an exception + */ +class TransientDirectory +{ +public: + TransientDirectory(); + TransientDirectory(std::string const& _path); + ~TransientDirectory(); + + std::string const& path() const { return m_path; } + +private: + std::string m_path; +}; + +} +} diff --git a/libweb3jsonrpc/CMakeLists.txt b/libweb3jsonrpc/CMakeLists.txt index b445f77ff..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,9 +53,10 @@ 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} ) +add_custom_target(aux_json SOURCES "spec.json") 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 70c8ca9de..79fd75d77 100644 --- a/libweb3jsonrpc/WebThreeStubServerBase.cpp +++ b/libweb3jsonrpc/WebThreeStubServerBase.cpp @@ -24,9 +24,13 @@ // Make sure boost/asio.hpp is included before windows.h. #include +#include +#include +#if ETH_SOLIDITY #include #include #include +#endif #include #include #include @@ -34,75 +38,123 @@ #include #include #include -#ifndef _MSC_VER +#if ETH_SERPENT #include #endif #include "WebThreeStubServerBase.h" #include "AccountHolder.h" using namespace std; +using namespace jsonrpc; using namespace dev; using namespace dev::eth; +#if ETH_DEBUG +const unsigned dev::SensibleHttpThreads = 1; +#else +const unsigned dev::SensibleHttpThreads = 4; +#endif +const unsigned dev::SensibleHttpPort = 8080; static Json::Value toJson(dev::eth::BlockInfo const& _bi) { Json::Value res; - res["hash"] = boost::lexical_cast(_bi.hash); - res["parentHash"] = toJS(_bi.parentHash); - res["sha3Uncles"] = toJS(_bi.sha3Uncles); - res["miner"] = toJS(_bi.coinbaseAddress); - res["stateRoot"] = toJS(_bi.stateRoot); - res["transactionsRoot"] = toJS(_bi.transactionsRoot); - res["difficulty"] = toJS(_bi.difficulty); - res["number"] = (int)_bi.number; - res["gasLimit"] = (int)_bi.gasLimit; - res["timestamp"] = (int)_bi.timestamp; - res["extraData"] = jsFromBinary(_bi.extraData); - res["nonce"] = toJS(_bi.nonce); + 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"] = jsFromBinary(_t.data()); - res["to"] = toJS(_t.receiveAddress()); - res["from"] = toJS(_t.safeSender()); - res["gas"] = (int)_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); + 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); + 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; } static Json::Value toJson(dev::eth::TransactionSkeleton const& _t) { Json::Value res; - res["to"] = toJS(_t.to); + res["to"] = _t.creation ? Json::Value() : toJS(_t.to); res["from"] = toJS(_t.from); res["gas"] = toJS(_t.gas); res["gasPrice"] = toJS(_t.gasPrice); res["value"] = toJS(_t.value); - res["data"] = jsFromBinary(_t.data); + res["data"] = toJS(_t.data, 32); return res; } static Json::Value toJson(dev::eth::LocalisedLogEntry const& _e) { Json::Value res; - - res["data"] = jsFromBinary(_e.data); - res["address"] = toJS(_e.address); - for (auto const& t: _e.topics) - res["topic"].append(toJS(t)); - res["number"] = _e.number; - res["hash"] = toJS(_e.sha3); + if (_e.transactionHash) + { + res["data"] = toJS(_e.data); + res["address"] = toJS(_e.address); + res["topics"] = Json::Value(Json::arrayValue); + for (auto const& t: _e.topics) + res["topics"].append(toJS(t)); + res["number"] = _e.number; + res["hash"] = toJS(_e.transactionHash); + } return res; } -static Json::Value toJson(dev::eth::LocalisedLogEntries const& _es) // commented to avoid warning. Uncomment once in use @ poC-7. +static Json::Value toJson(dev::eth::LocalisedLogEntries const& _es) { Json::Value res(Json::arrayValue); for (dev::eth::LocalisedLogEntry const& e: _es) @@ -110,7 +162,7 @@ static Json::Value toJson(dev::eth::LocalisedLogEntries const& _es) // commented return res; } -static Json::Value toJson(std::map const& _storage) +static Json::Value toJson(map const& _storage) { Json::Value res(Json::objectValue); for (auto i: _storage) @@ -118,55 +170,51 @@ static Json::Value toJson(std::map const& _storage) return res; } +static unsigned toBlockNumber(std::string const& _js) +{ + if (_js == "latest") + return LatestBlock; + else if (_js == "earliest") + return 0; + else if (_js == "pending") + return PendingBlock; + else + return (unsigned)jsToInt(_js); +} + static dev::eth::LogFilter toLogFilter(Json::Value const& _json) // commented to avoid warning. Uncomment once in use @ PoC-7. { dev::eth::LogFilter filter; if (!_json.isObject() || _json.empty()) return filter; - if (_json["earliest"].isInt()) - filter.withEarliest(_json["earliest"].asInt()); - if (_json["latest"].isInt()) - filter.withLatest(_json["lastest"].asInt()); - if (_json["max"].isInt()) - filter.withMax(_json["max"].asInt()); - if (_json["skip"].isInt()) - filter.withSkip(_json["skip"].asInt()); + // check only !empty. it should throw exceptions if input params are incorrect + if (!_json["fromBlock"].empty()) + filter.withEarliest(toBlockNumber(_json["fromBlock"].asString())); + if (!_json["toBlock"].empty()) + filter.withLatest(toBlockNumber(_json["toBlock"].asString())); if (!_json["address"].empty()) { if (_json["address"].isArray()) - { for (auto i : _json["address"]) - if (i.isString()) - filter.address(jsToAddress(i.asString())); - } - else if (_json["address"].isString()) + filter.address(jsToAddress(i.asString())); + else filter.address(jsToAddress(_json["address"].asString())); } - if (!_json["topic"].empty() && _json["topic"].isArray()) - { - unsigned i = 0; - for (auto t: _json["topic"]) - { - if (t.isArray()) - for (auto tt: t) - filter.topic(i, jsToFixed<32>(tt.asString())); - else if (t.isString()) - filter.topic(i, jsToFixed<32>(t.asString())); - i++; - } - } + if (!_json["topics"].empty()) + for (unsigned i = 0; i < _json["topics"].size(); i++) + filter.topic(i, jsToFixed<32>(_json["topics"][i].asString())); return filter; } static shh::Message toMessage(Json::Value const& _json) { shh::Message ret; - if (_json["from"].isString()) + if (!_json["from"].empty()) ret.setFrom(jsToPublic(_json["from"].asString())); - if (_json["to"].isString()) + if (!_json["to"].empty()) ret.setTo(jsToPublic(_json["to"].asString())); - if (_json["payload"].isString()) + if (!_json["payload"].empty()) ret.setPayload(jsToBytes(_json["payload"].asString())); return ret; } @@ -177,19 +225,16 @@ static shh::Envelope toSealed(Json::Value const& _json, shh::Message const& _m, unsigned workToProve = 50; shh::BuildTopic bt; - if (_json["ttl"].isInt()) - ttl = _json["ttl"].asInt(); - if (_json["workToProve"].isInt()) - workToProve = _json["workToProve"].asInt(); - if (!_json["topic"].empty()) - { - if (_json["topic"].isString()) - bt.shift(jsToBytes(_json["topic"].asString())); - else if (_json["topic"].isArray()) - for (auto i: _json["topic"]) - if (i.isString()) - bt.shift(jsToBytes(i.asString())); - } + if (!_json["ttl"].empty()) + ttl = jsToInt(_json["ttl"].asString()); + + if (!_json["workToProve"].empty()) + workToProve = jsToInt(_json["workToProve"].asString()); + + if (!_json["topics"].empty()) + for (auto i: _json["topics"]) + bt.shift(jsToBytes(i.asString())); + return _m.seal(_from, bt, ttl, workToProve); } @@ -198,18 +243,13 @@ static pair toWatch(Json::Value const& _json) shh::BuildTopic bt; Public to; - if (_json["to"].isString()) + if (!_json["to"].empty()) to = jsToPublic(_json["to"].asString()); - if (!_json["topic"].empty()) - { - if (_json["topic"].isString()) - bt.shift(jsToBytes(_json["topic"].asString())); - else if (_json["topic"].isArray()) - for (auto i: _json["topic"]) - if (i.isString()) - bt.shift(jsToBytes(i.asString())); - } + if (!_json["topics"].empty()) + for (auto i: _json["topics"]) + bt.shift(jsToBytes(i.asString())); + return make_pair(bt, to); } @@ -217,302 +257,384 @@ static Json::Value toJson(h256 const& _h, shh::Envelope const& _e, shh::Message { Json::Value res; res["hash"] = toJS(_h); - res["expiry"] = (int)_e.expiry(); - res["sent"] = (int)_e.sent(); - res["ttl"] = (int)_e.ttl(); - res["workProved"] = (int)_e.workProved(); + res["expiry"] = toJS(_e.expiry()); + res["sent"] = toJS(_e.sent()); + res["ttl"] = toJS(_e.ttl()); + res["workProved"] = toJS(_e.workProved()); + res["topics"] = Json::Value(Json::arrayValue); for (auto const& t: _e.topic()) - res["topic"].append(toJS(t)); + res["topics"].append(toJS(t)); res["payload"] = toJS(_m.payload()); res["from"] = toJS(_m.from()); res["to"] = toJS(_m.to()); return res; } -WebThreeStubServerBase::WebThreeStubServerBase(jsonrpc::AbstractServerConnector& _conn, std::vector const& _accounts): - AbstractWebThreeStubServer(_conn), m_accounts(make_shared(std::bind(&WebThreeStubServerBase::client, this))) +WebThreeStubServerBase::WebThreeStubServerBase(AbstractServerConnector& _conn, vector const& _accounts): + AbstractWebThreeStubServer(_conn), m_accounts(make_shared(bind(&WebThreeStubServerBase::client, this))) { m_accounts->setAccounts(_accounts); } -void WebThreeStubServerBase::setIdentities(std::vector const& _ids) +void WebThreeStubServerBase::setIdentities(vector const& _ids) { m_ids.clear(); for (auto i: _ids) m_ids[i.pub()] = i.secret(); } -std::string WebThreeStubServerBase::web3_sha3(std::string const& _param1) +string WebThreeStubServerBase::web3_sha3(string const& _param1) { return toJS(sha3(jsToBytes(_param1))); } -Json::Value WebThreeStubServerBase::eth_accounts() +string WebThreeStubServerBase::net_peerCount() { - Json::Value ret(Json::arrayValue); - for (auto const& i: m_accounts->getAllAccounts()) - ret.append(toJS(i)); - return ret; + return toJS(network()->peerCount()); } -std::string WebThreeStubServerBase::shh_addToGroup(std::string const& _group, std::string const& _who) +bool WebThreeStubServerBase::net_listening() { - (void)_group; - (void)_who; - return ""; -} - -std::string WebThreeStubServerBase::eth_balanceAt(string const& _address) -{ - return toJS(client()->balanceAt(jsToAddress(_address), client()->getDefault())); + return network()->isNetworkStarted(); } -Json::Value WebThreeStubServerBase::eth_blockByHash(std::string const& _hash) +string WebThreeStubServerBase::eth_protocolVersion() { - return toJson(client()->blockInfo(jsToFixed<32>(_hash))); + return toJS(eth::c_protocolVersion); } -Json::Value WebThreeStubServerBase::eth_blockByNumber(int _number) +string WebThreeStubServerBase::eth_coinbase() { - return toJson(client()->blockInfo(client()->hashFromNumber(_number))); + return toJS(client()->address()); } -static TransactionSkeleton toTransaction(Json::Value const& _json) +bool WebThreeStubServerBase::eth_mining() { - TransactionSkeleton ret; - if (!_json.isObject() || _json.empty()) - return ret; - - if (_json["from"].isString()) - ret.from = jsToAddress(_json["from"].asString()); - if (_json["to"].isString()) - ret.to = jsToAddress(_json["to"].asString()); - else - ret.creation = true; - if (!_json["value"].empty()) - { - if (_json["value"].isString()) - ret.value = jsToU256(_json["value"].asString()); - else if (_json["value"].isInt()) - ret.value = u256(_json["value"].asInt()); - } - if (!_json["gas"].empty()) - { - if (_json["gas"].isString()) - ret.gas = jsToU256(_json["gas"].asString()); - else if (_json["gas"].isInt()) - ret.gas = u256(_json["gas"].asInt()); - } - if (!_json["gasPrice"].empty()) - { - if (_json["gasPrice"].isString()) - ret.gasPrice = jsToU256(_json["gasPrice"].asString()); - else if (_json["gasPrice"].isInt()) - ret.gas = u256(_json["gas"].asInt()); - } - if (!_json["data"].empty()) - { - if (_json["data"].isString()) // ethereum.js has preconstructed the data array - ret.data = jsToBytes(_json["data"].asString()); - else if (_json["data"].isArray()) // old style: array of 32-byte-padded values. TODO: remove PoC-8 - for (auto i: _json["data"]) - dev::operator +=(ret.data, padded(jsToBytes(i.asString()), 32)); - } - - if (_json["code"].isString()) - ret.data = jsToBytes(_json["code"].asString()); - return ret; + return client()->isMining(); } -bool WebThreeStubServerBase::eth_flush() +string WebThreeStubServerBase::eth_gasPrice() { - client()->flushTransactions(); - return true; + return toJS(10 * dev::eth::szabo); } -std::string WebThreeStubServerBase::eth_call(Json::Value const& _json) +Json::Value WebThreeStubServerBase::eth_accounts() { - std::string ret; - TransactionSkeleton t = toTransaction(_json); - if (!t.from) - t.from = m_accounts->getDefaultTransactAccount(); - if (!m_accounts->isRealAccount(t.from)) - return ret; - if (!t.gasPrice) - t.gasPrice = 10 * dev::eth::szabo; - if (!t.gas) - t.gas = min(client()->gasLimitRemaining(), client()->balanceAt(t.from) / t.gasPrice); - ret = toJS(client()->call(m_accounts->secretKey(t.from), t.value, t.to, t.data, t.gas, t.gasPrice)); + Json::Value ret(Json::arrayValue); + for (auto const& i: m_accounts->getAllAccounts()) + ret.append(toJS(i)); return ret; } -Json::Value WebThreeStubServerBase::eth_changed(int _id) +string WebThreeStubServerBase::eth_blockNumber() { - auto entries = client()->checkWatch(_id); - if (entries.size()) - cnote << "FIRING WATCH" << _id << entries.size(); - return toJson(entries); + return toJS(client()->number()); } -std::string WebThreeStubServerBase::eth_codeAt(string const& _address) -{ - return jsFromBinary(client()->codeAt(jsToAddress(_address), client()->getDefault())); -} -std::string WebThreeStubServerBase::eth_coinbase() +string WebThreeStubServerBase::eth_getBalance(string const& _address, string const& _blockNumber) { - return toJS(client()->address()); + try + { + return toJS(client()->balanceAt(jsToAddress(_address), toBlockNumber(_blockNumber))); + } + catch (...) + { + BOOST_THROW_EXCEPTION(JsonRpcException(Errors::ERROR_RPC_INVALID_PARAMS)); + } } -double WebThreeStubServerBase::eth_countAt(string const& _address) +string WebThreeStubServerBase::eth_getStorageAt(string const& _address, string const& _position, string const& _blockNumber) { - return (double)(uint64_t)client()->countAt(jsToAddress(_address), client()->getDefault()); + try + { + return toJS(client()->stateAt(jsToAddress(_address), jsToU256(_position), toBlockNumber(_blockNumber))); + } + catch (...) + { + BOOST_THROW_EXCEPTION(JsonRpcException(Errors::ERROR_RPC_INVALID_PARAMS)); + } } -double WebThreeStubServerBase::eth_transactionCountByHash(std::string const& _hash) +string WebThreeStubServerBase::eth_getTransactionCount(string const& _address, string const& _blockNumber) { - return client()->transactionCount(jsToFixed<32>(_hash)); + try + { + return toJS(client()->countAt(jsToAddress(_address), toBlockNumber(_blockNumber))); + } + catch (...) + { + BOOST_THROW_EXCEPTION(JsonRpcException(Errors::ERROR_RPC_INVALID_PARAMS)); + } } -double WebThreeStubServerBase::eth_transactionCountByNumber(int _number) +string WebThreeStubServerBase::eth_getBlockTransactionCountByHash(string const& _blockHash) { - return client()->transactionCount(client()->hashFromNumber(_number)); + try + { + return toJS(client()->transactionCount(jsToFixed<32>(_blockHash))); + } + catch (...) + { + BOOST_THROW_EXCEPTION(JsonRpcException(Errors::ERROR_RPC_INVALID_PARAMS)); + } } -double WebThreeStubServerBase::eth_uncleCountByHash(std::string const& _hash) -{ - return client()->transactionCount(jsToFixed<32>(_hash)); -} -double WebThreeStubServerBase::eth_uncleCountByNumber(int _number) +string WebThreeStubServerBase::eth_getBlockTransactionCountByNumber(string const& _blockNumber) { - return client()->transactionCount(client()->hashFromNumber(_number)); + try + { + return toJS(_blockNumber == "pending" ? client()->pending().size() : client()->transactionCount(client()->hashFromNumber(toBlockNumber(_blockNumber)))); + } + catch (...) + { + BOOST_THROW_EXCEPTION(JsonRpcException(Errors::ERROR_RPC_INVALID_PARAMS)); + } } -int WebThreeStubServerBase::eth_defaultBlock() +string WebThreeStubServerBase::eth_getUncleCountByBlockHash(string const& _blockHash) { - return client()->getDefault(); + try + { + return toJS(client()->uncleCount(jsToFixed<32>(_blockHash))); + } + catch (...) + { + BOOST_THROW_EXCEPTION(JsonRpcException(Errors::ERROR_RPC_INVALID_PARAMS)); + } } -std::string WebThreeStubServerBase::eth_gasPrice() +string WebThreeStubServerBase::eth_getUncleCountByBlockNumber(string const& _blockNumber) { - return toJS(10 * dev::eth::szabo); + try + { + return toJS(client()->uncleCount(client()->hashFromNumber(toBlockNumber(_blockNumber)))); + } + catch (...) + { + BOOST_THROW_EXCEPTION(JsonRpcException(Errors::ERROR_RPC_INVALID_PARAMS)); + } } -std::string WebThreeStubServerBase::db_get(std::string const& _name, std::string const& _key) +string WebThreeStubServerBase::eth_getCode(string const& _address, string const& _blockNumber) { - string ret = db()->get(_name, _key); - return toJS(dev::asBytes(ret)); + try + { + return toJS(client()->codeAt(jsToAddress(_address), toBlockNumber(_blockNumber))); + } + catch (...) + { + BOOST_THROW_EXCEPTION(JsonRpcException(Errors::ERROR_RPC_INVALID_PARAMS)); + } } -Json::Value WebThreeStubServerBase::eth_filterLogs(int _id) +static TransactionSkeleton toTransaction(Json::Value const& _json) { - return toJson(client()->logs(_id)); -} + TransactionSkeleton ret; + if (!_json.isObject() || _json.empty()) + return ret; + + if (!_json["from"].empty()) + ret.from = jsToAddress(_json["from"].asString()); + if (!_json["to"].empty() && _json["to"].asString() != "0x") + ret.to = jsToAddress(_json["to"].asString()); + else + ret.creation = true; + + if (!_json["value"].empty()) + ret.value = jsToU256(_json["value"].asString()); -Json::Value WebThreeStubServerBase::eth_logs(Json::Value const& _json) -{ - return toJson(client()->logs(toLogFilter(_json))); -} + if (!_json["gas"].empty()) + ret.gas = jsToU256(_json["gas"].asString()); -std::string WebThreeStubServerBase::db_getString(std::string const& _name, std::string const& _key) -{ - return db()->get(_name, _key);; -} + if (!_json["gasPrice"].empty()) + ret.gasPrice = jsToU256(_json["gasPrice"].asString()); -bool WebThreeStubServerBase::shh_haveIdentity(std::string const& _id) -{ - return m_ids.count(jsToPublic(_id)) > 0; + if (!_json["data"].empty()) // ethereum.js has preconstructed the data array + ret.data = jsToBytes(_json["data"].asString()); + + if (!_json["code"].empty()) + ret.data = jsToBytes(_json["code"].asString()); + return ret; } -bool WebThreeStubServerBase::eth_listening() +string WebThreeStubServerBase::eth_sendTransaction(Json::Value const& _json) { - return network()->isNetworkStarted(); + TransactionSkeleton t; + + try + { + t = toTransaction(_json); + } + catch (...) + { + BOOST_THROW_EXCEPTION(JsonRpcException(Errors::ERROR_RPC_INVALID_PARAMS)); + } + + string ret; + if (!t.from) + t.from = m_accounts->getDefaultTransactAccount(); + if (t.creation) + ret = toJS(right160(sha3(rlpList(t.from, client()->countAt(t.from)))));; + if (!t.gasPrice) + t.gasPrice = 10 * dev::eth::szabo; // TODO: should be determined by user somehow. + if (!t.gas) + t.gas = min(client()->gasLimitRemaining(), client()->balanceAt(t.from) / t.gasPrice); + + if (m_accounts->isRealAccount(t.from)) + authenticate(t, false); + else if (m_accounts->isProxyAccount(t.from)) + authenticate(t, true); + + return ret; } -bool WebThreeStubServerBase::eth_mining() -{ - return client()->isMining(); -} -int WebThreeStubServerBase::eth_newFilter(Json::Value const& _json) +string WebThreeStubServerBase::eth_call(Json::Value const& _json, string const& _blockNumber) { - unsigned ret = -1; - ret = client()->installWatch(toLogFilter(_json)); + TransactionSkeleton t; + int number; + + try + { + t = toTransaction(_json); + number = toBlockNumber(_blockNumber); + } + catch (...) + { + BOOST_THROW_EXCEPTION(JsonRpcException(Errors::ERROR_RPC_INVALID_PARAMS)); + } + + string ret; + if (!t.from) + t.from = m_accounts->getDefaultTransactAccount(); + if (!m_accounts->isRealAccount(t.from)) + return ret; + if (!t.gasPrice) + t.gasPrice = 10 * dev::eth::szabo; + if (!t.gas) + t.gas = min(client()->gasLimitRemaining(), client()->balanceAt(t.from) / t.gasPrice); + ret = toJS(client()->call(m_accounts->secretKey(t.from), t.value, t.to, t.data, t.gas, t.gasPrice, number).output); + return ret; } -int WebThreeStubServerBase::eth_newFilterString(std::string const& _filter) +bool WebThreeStubServerBase::eth_flush() { - unsigned ret = -1; - if (_filter.compare("chain") == 0) - ret = client()->installWatch(dev::eth::ChainChangedFilter); - else if (_filter.compare("pending") == 0) - ret = client()->installWatch(dev::eth::PendingChangedFilter); - return ret; + client()->flushTransactions(); + return true; } -Json::Value WebThreeStubServerBase::eth_getWork() +Json::Value WebThreeStubServerBase::eth_getBlockByHash(string const& _blockHash, bool _includeTransactions) { - Json::Value ret(Json::arrayValue); - auto r = client()->getWork(); - ret.append(toJS(r.first)); - ret.append(toJS(r.second)); - return ret; + try + { + auto h = jsToFixed<32>(_blockHash); + if (_includeTransactions) + return toJson(client()->blockInfo(h), client()->uncleHashes(h), client()->transactions(h)); + else + return toJson(client()->blockInfo(h), client()->uncleHashes(h), client()->transactionHashes(h)); + } + catch (...) + { + BOOST_THROW_EXCEPTION(JsonRpcException(Errors::ERROR_RPC_INVALID_PARAMS)); + } } -bool WebThreeStubServerBase::eth_submitWork(std::string const& _nonce, std::string const& _mixHash) +Json::Value WebThreeStubServerBase::eth_getBlockByNumber(string const& _blockNumber, bool _includeTransactions) { - return client()->submitWork(ProofOfWork::Proof{jsToFixed(_nonce), jsToFixed<32>(_mixHash)}); + try + { + auto h = client()->hashFromNumber(jsToInt(_blockNumber)); + if (_includeTransactions) + return toJson(client()->blockInfo(h), client()->uncleHashes(h), client()->transactions(h)); + else + return toJson(client()->blockInfo(h), client()->uncleHashes(h), client()->transactionHashes(h)); + } + catch (...) + { + BOOST_THROW_EXCEPTION(JsonRpcException(Errors::ERROR_RPC_INVALID_PARAMS)); + } } -int WebThreeStubServerBase::eth_register(std::string const& _address) +Json::Value WebThreeStubServerBase::eth_getTransactionByHash(string const& _transactionHash) { - return m_accounts->addProxyAccount(jsToAddress(_address)); + try + { + return toJson(client()->transaction(jsToFixed<32>(_transactionHash))); + } + catch (...) + { + BOOST_THROW_EXCEPTION(JsonRpcException(Errors::ERROR_RPC_INVALID_PARAMS)); + } } -bool WebThreeStubServerBase::eth_unregister(int _id) +Json::Value WebThreeStubServerBase::eth_getTransactionByBlockHashAndIndex(string const& _blockHash, string const& _transactionIndex) { - return m_accounts->removeProxyAccount(_id); + try + { + return toJson(client()->transaction(jsToFixed<32>(_blockHash), jsToInt(_transactionIndex))); + } + catch (...) + { + BOOST_THROW_EXCEPTION(JsonRpcException(Errors::ERROR_RPC_INVALID_PARAMS)); + } } -Json::Value WebThreeStubServerBase::eth_queuedTransactions(int _id) +Json::Value WebThreeStubServerBase::eth_getTransactionByBlockNumberAndIndex(string const& _blockNumber, string const& _transactionIndex) { - Json::Value ret(Json::arrayValue); - for (TransactionSkeleton const& t: m_accounts->getQueuedTransactions(_id)) - ret.append(toJson(t)); - m_accounts->clearQueue(_id); - return ret; + try + { + return toJson(client()->transaction(client()->hashFromNumber(jsToInt(_blockNumber)), jsToInt(_transactionIndex))); + } + catch (...) + { + BOOST_THROW_EXCEPTION(JsonRpcException(Errors::ERROR_RPC_INVALID_PARAMS)); + } } -std::string WebThreeStubServerBase::shh_newGroup(std::string const& _id, std::string const& _who) +Json::Value WebThreeStubServerBase::eth_getUncleByBlockHashAndIndex(string const& _blockHash, string const& _uncleIndex) { - (void)_id; - (void)_who; - return ""; + try + { + return toJson(client()->uncle(jsToFixed<32>(_blockHash), jsToInt(_uncleIndex))); + } + catch (...) + { + BOOST_THROW_EXCEPTION(JsonRpcException(Errors::ERROR_RPC_INVALID_PARAMS)); + } } -std::string WebThreeStubServerBase::shh_newIdentity() +Json::Value WebThreeStubServerBase::eth_getUncleByBlockNumberAndIndex(string const& _blockNumber, string const& _uncleIndex) { -// cnote << this << m_ids; - KeyPair kp = KeyPair::create(); - m_ids[kp.pub()] = kp.secret(); - return toJS(kp.pub()); + try + { + return toJson(client()->uncle(client()->hashFromNumber(toBlockNumber(_blockNumber)), jsToInt(_uncleIndex))); + } + catch (...) + { + BOOST_THROW_EXCEPTION(JsonRpcException(Errors::ERROR_RPC_INVALID_PARAMS)); + } } -Json::Value WebThreeStubServerBase::eth_compilers() +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; } -std::string WebThreeStubServerBase::eth_lll(std::string const& _code) + +string WebThreeStubServerBase::eth_compileLLL(string const& _code) { + // TODO throw here jsonrpc errors string res; vector errors; res = toJS(dev::eth::compileLLL(_code, true, &errors)); @@ -520,17 +642,19 @@ std::string WebThreeStubServerBase::eth_lll(std::string const& _code) return res; } -std::string WebThreeStubServerBase::eth_serpent(std::string const& _code) +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))); } catch (string err) { - cwarn << "Solidity compilation error: " << err; + cwarn << "Serpent compilation error: " << err; } catch (...) { @@ -540,9 +664,12 @@ std::string WebThreeStubServerBase::eth_serpent(std::string const& _code) return res; } -std::string WebThreeStubServerBase::eth_solidity(std::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 { @@ -558,207 +685,325 @@ std::string WebThreeStubServerBase::eth_solidity(std::string const& _code) { cwarn << "Uncought solidity compilation exception"; } +#endif return res; } -int WebThreeStubServerBase::eth_number() +string WebThreeStubServerBase::eth_newFilter(Json::Value const& _json) { - return client()->number(); + try + { + return toJS(client()->installWatch(toLogFilter(_json))); + } + catch (...) + { + BOOST_THROW_EXCEPTION(JsonRpcException(Errors::ERROR_RPC_INVALID_PARAMS)); + } } -int WebThreeStubServerBase::eth_peerCount() +string WebThreeStubServerBase::eth_newBlockFilter(string const& _filter) { - return network()->peerCount(); + h256 filter; + + if (_filter.compare("chain") == 0 || _filter.compare("latest") == 0) + filter = dev::eth::ChainChangedFilter; + else if (_filter.compare("pending") == 0) + filter = dev::eth::PendingChangedFilter; + else + BOOST_THROW_EXCEPTION(JsonRpcException(Errors::ERROR_RPC_INVALID_PARAMS)); + + return toJS(client()->installWatch(filter)); } -bool WebThreeStubServerBase::shh_post(Json::Value const& _json) +bool WebThreeStubServerBase::eth_uninstallFilter(string const& _filterId) { - shh::Message m = toMessage(_json); - Secret from; - - if (m.from() && m_ids.count(m.from())) + try + { + return client()->uninstallWatch(jsToInt(_filterId)); + } + catch (...) { - cwarn << "Silently signing message from identity" << m.from().abridged() << ": User validation hook goes here."; - // TODO: insert validification hook here. - from = m_ids[m.from()]; + BOOST_THROW_EXCEPTION(JsonRpcException(Errors::ERROR_RPC_INVALID_PARAMS)); } + - face()->inject(toSealed(_json, m, from)); - return true; } -bool WebThreeStubServerBase::db_put(std::string const& _name, std::string const& _key, std::string const& _value) +Json::Value WebThreeStubServerBase::eth_getFilterChanges(string const& _filterId) { - string v = asString(jsToBytes(_value)); - db()->put(_name, _key, v); - return true; + try + { + int id = jsToInt(_filterId); + auto entries = client()->checkWatch(id); + if (entries.size()) + cnote << "FIRING WATCH" << id << entries.size(); + return toJson(entries); + } + catch (...) + { + BOOST_THROW_EXCEPTION(JsonRpcException(Errors::ERROR_RPC_INVALID_PARAMS)); + } } -bool WebThreeStubServerBase::db_putString(std::string const& _name, std::string const& _key, std::string const& _value) +Json::Value WebThreeStubServerBase::eth_getFilterLogs(string const& _filterId) { - db()->put(_name, _key,_value); - return true; + try + { + return toJson(client()->logs(jsToInt(_filterId))); + } + catch (...) + { + BOOST_THROW_EXCEPTION(JsonRpcException(Errors::ERROR_RPC_INVALID_PARAMS)); + } } -bool WebThreeStubServerBase::eth_setCoinbase(std::string const& _address) +Json::Value WebThreeStubServerBase::eth_getLogs(Json::Value const& _json) { - client()->setAddress(jsToAddress(_address)); - return true; + try + { + return toJson(client()->logs(toLogFilter(_json))); + } + catch (...) + { + BOOST_THROW_EXCEPTION(JsonRpcException(Errors::ERROR_RPC_INVALID_PARAMS)); + } } -bool WebThreeStubServerBase::eth_setDefaultBlock(int _block) +Json::Value WebThreeStubServerBase::eth_getWork() { - client()->setDefault(_block); - return true; + Json::Value ret(Json::arrayValue); + auto r = client()->getWork(); + ret.append(toJS(r.first)); + ret.append(toJS(r.second)); + return ret; } -bool WebThreeStubServerBase::eth_setListening(bool _listening) +bool WebThreeStubServerBase::eth_submitWork(string const& _nonce, string const& _mixHash) { - if (_listening) - network()->startNetwork(); - else - network()->stopNetwork(); - return true; + try + { + return client()->submitWork(ProofOfWork::Proof{jsToFixed(_nonce), jsToFixed<32>(_mixHash)}); + } + catch (...) + { + BOOST_THROW_EXCEPTION(JsonRpcException(Errors::ERROR_RPC_INVALID_PARAMS)); + } } -bool WebThreeStubServerBase::eth_setMining(bool _mining) +string WebThreeStubServerBase::eth_register(string const& _address) { - if (_mining) - client()->startMining(); - else - client()->stopMining(); - return true; + try + { + return toJS(m_accounts->addProxyAccount(jsToAddress(_address))); + } + catch (...) + { + BOOST_THROW_EXCEPTION(JsonRpcException(Errors::ERROR_RPC_INVALID_PARAMS)); + } } -Json::Value WebThreeStubServerBase::shh_changed(int _id) +bool WebThreeStubServerBase::eth_unregister(string const& _accountId) { - Json::Value ret(Json::arrayValue); - auto pub = m_shhWatches[_id]; - if (!pub || m_ids.count(pub)) - for (h256 const& h: face()->checkWatch(_id)) - { - auto e = face()->envelope(h); - shh::Message m; - if (pub) - { - cwarn << "Silently decrypting message from identity" << pub.abridged() << ": User validation hook goes here."; - m = e.open(face()->fullTopic(_id), m_ids[pub]); - } - else - m = e.open(face()->fullTopic(_id)); - if (!m) - continue; - ret.append(toJson(h, e, m)); - } - - return ret; + try + { + return m_accounts->removeProxyAccount(jsToInt(_accountId)); + } + catch (...) + { + BOOST_THROW_EXCEPTION(JsonRpcException(Errors::ERROR_RPC_INVALID_PARAMS)); + } } -Json::Value WebThreeStubServerBase::shh_getMessages(int _id) +Json::Value WebThreeStubServerBase::eth_fetchQueuedTransactions(string const& _accountId) { - Json::Value ret(Json::arrayValue); - auto pub = m_shhWatches[_id]; - if (!pub || m_ids.count(pub)) - for (h256 const& h: face()->watchMessages(_id)) - { - auto e = face()->envelope(h); - shh::Message m; - if (pub) - { - cwarn << "Silently decrypting message from identity" << pub.abridged() << ": User validation hook goes here."; - m = e.open(face()->fullTopic(_id), m_ids[pub]); - } - else - m = e.open(face()->fullTopic(_id)); - if (!m) - continue; - ret.append(toJson(h, e, m)); - } - return ret; + try + { + auto id = jsToInt(_accountId); + Json::Value ret(Json::arrayValue); + // TODO: throw an error on no account with given id + for (TransactionSkeleton const& t: m_accounts->getQueuedTransactions(id)) + ret.append(toJson(t)); + m_accounts->clearQueue(id); + return ret; + } + catch (...) + { + BOOST_THROW_EXCEPTION(JsonRpcException(Errors::ERROR_RPC_INVALID_PARAMS)); + } } -int WebThreeStubServerBase::shh_newFilter(Json::Value const& _json) +bool WebThreeStubServerBase::db_put(string const& _name, string const& _key, string const& _value) { - auto w = toWatch(_json); - auto ret = face()->installWatch(w.first); - m_shhWatches.insert(make_pair(ret, w.second)); - return ret; + db()->put(_name, _key,_value); + return true; } -bool WebThreeStubServerBase::shh_uninstallFilter(int _id) +string WebThreeStubServerBase::db_get(string const& _name, string const& _key) { - face()->uninstallWatch(_id); - return true; + return db()->get(_name, _key);; } -std::string WebThreeStubServerBase::eth_stateAt(string const& _address, string const& _storage) +bool WebThreeStubServerBase::shh_post(Json::Value const& _json) { - return toJS(client()->stateAt(jsToAddress(_address), jsToU256(_storage), client()->getDefault())); + try + { + shh::Message m = toMessage(_json); + Secret from; + if (m.from() && m_ids.count(m.from())) + { + cwarn << "Silently signing message from identity" << m.from().abridged() << ": User validation hook goes here."; + // TODO: insert validification hook here. + from = m_ids[m.from()]; + } + + face()->inject(toSealed(_json, m, from)); + return true; + } + catch (...) + { + BOOST_THROW_EXCEPTION(JsonRpcException(Errors::ERROR_RPC_INVALID_PARAMS)); + } } -Json::Value WebThreeStubServerBase::eth_storageAt(string const& _address) +string WebThreeStubServerBase::shh_newIdentity() { - return toJson(client()->storageAt(jsToAddress(_address))); + KeyPair kp = KeyPair::create(); + m_ids[kp.pub()] = kp.secret(); + return toJS(kp.pub()); } -std::string WebThreeStubServerBase::eth_transact(Json::Value const& _json) +bool WebThreeStubServerBase::shh_hasIdentity(string const& _identity) { - std::string ret; - TransactionSkeleton t = toTransaction(_json); - if (!t.from) - t.from = m_accounts->getDefaultTransactAccount(); - if (t.creation) - ret = toJS(right160(sha3(rlpList(t.from, client()->countAt(t.from)))));; - if (!t.gasPrice) - t.gasPrice = 10 * dev::eth::szabo; // TODO: should be determined by user somehow. - if (!t.gas) - t.gas = min(client()->gasLimitRemaining(), client()->balanceAt(t.from) / t.gasPrice); + try + { + return m_ids.count(jsToPublic(_identity)) > 0; + } + catch (...) + { + BOOST_THROW_EXCEPTION(JsonRpcException(Errors::ERROR_RPC_INVALID_PARAMS)); + } +} - if (m_accounts->isRealAccount(t.from)) - authenticate(t, false); - else if (m_accounts->isProxyAccount(t.from)) - authenticate(t, true); - return ret; +string WebThreeStubServerBase::shh_newGroup(string const& _id, string const& _who) +{ + (void)_id; + (void)_who; + return ""; } -void WebThreeStubServerBase::authenticate(TransactionSkeleton const& _t, bool _toProxy) +string WebThreeStubServerBase::shh_addToGroup(string const& _group, string const& _who) { - if (_toProxy) - m_accounts->queueTransaction(_t); - else if (_t.to) - client()->transact(m_accounts->secretKey(_t.from), _t.value, _t.to, _t.data, _t.gas, _t.gasPrice); - else - client()->transact(m_accounts->secretKey(_t.from), _t.value, _t.data, _t.gas, _t.gasPrice); + (void)_group; + (void)_who; + return ""; } -Json::Value WebThreeStubServerBase::eth_transactionByHash(std::string const& _hash, int _i) +string WebThreeStubServerBase::shh_newFilter(Json::Value const& _json) { - return toJson(client()->transaction(jsToFixed<32>(_hash), _i)); + + try + { + pair w = toWatch(_json); + auto ret = face()->installWatch(w.first); + m_shhWatches.insert(make_pair(ret, w.second)); + return toJS(ret); + } + catch (...) + { + BOOST_THROW_EXCEPTION(JsonRpcException(Errors::ERROR_RPC_INVALID_PARAMS)); + } } -Json::Value WebThreeStubServerBase::eth_transactionByNumber(int _number, int _i) +bool WebThreeStubServerBase::shh_uninstallFilter(string const& _filterId) { - return toJson(client()->transaction(client()->hashFromNumber(_number), _i)); + try + { + face()->uninstallWatch(jsToInt(_filterId)); + return true; + } + catch (...) + { + BOOST_THROW_EXCEPTION(JsonRpcException(Errors::ERROR_RPC_INVALID_PARAMS)); + } } -Json::Value WebThreeStubServerBase::eth_uncleByHash(std::string const& _hash, int _i) +Json::Value WebThreeStubServerBase::shh_getFilterChanges(string const& _filterId) { - return toJson(client()->uncle(jsToFixed<32>(_hash), _i)); + try + { + Json::Value ret(Json::arrayValue); + + int id = jsToInt(_filterId); + auto pub = m_shhWatches[id]; + if (!pub || m_ids.count(pub)) + for (h256 const& h: face()->checkWatch(id)) + { + auto e = face()->envelope(h); + shh::Message m; + if (pub) + { + cwarn << "Silently decrypting message from identity" << pub.abridged() << ": User validation hook goes here."; + m = e.open(face()->fullTopic(id), m_ids[pub]); + } + else + m = e.open(face()->fullTopic(id)); + if (!m) + continue; + ret.append(toJson(h, e, m)); + } + + return ret; + } + catch (...) + { + BOOST_THROW_EXCEPTION(JsonRpcException(Errors::ERROR_RPC_INVALID_PARAMS)); + } } -Json::Value WebThreeStubServerBase::eth_uncleByNumber(int _number, int _i) +Json::Value WebThreeStubServerBase::shh_getMessages(string const& _filterId) { - return toJson(client()->uncle(client()->hashFromNumber(_number), _i)); + try + { + Json::Value ret(Json::arrayValue); + + int id = jsToInt(_filterId); + auto pub = m_shhWatches[id]; + if (!pub || m_ids.count(pub)) + for (h256 const& h: face()->watchMessages(id)) + { + auto e = face()->envelope(h); + shh::Message m; + if (pub) + { + cwarn << "Silently decrypting message from identity" << pub.abridged() << ": User validation hook goes here."; + m = e.open(face()->fullTopic(id), m_ids[pub]); + } + else + m = e.open(face()->fullTopic(id)); + if (!m) + continue; + ret.append(toJson(h, e, m)); + } + return ret; + } + catch (...) + { + BOOST_THROW_EXCEPTION(JsonRpcException(Errors::ERROR_RPC_INVALID_PARAMS)); + } } -bool WebThreeStubServerBase::eth_uninstallFilter(int _id) +void WebThreeStubServerBase::authenticate(TransactionSkeleton const& _t, bool _toProxy) { - client()->uninstallWatch(_id); - return true; + if (_toProxy) + m_accounts->queueTransaction(_t); + else if (_t.to) + client()->submitTransaction(m_accounts->secretKey(_t.from), _t.value, _t.to, _t.data, _t.gas, _t.gasPrice); + else + client()->submitTransaction(m_accounts->secretKey(_t.from), _t.value, _t.data, _t.gas, _t.gasPrice); } -void WebThreeStubServerBase::setAccounts(const std::vector& _accounts) +void WebThreeStubServerBase::setAccounts(const vector& _accounts) { m_accounts->setAccounts(_accounts); } diff --git a/libweb3jsonrpc/WebThreeStubServerBase.h b/libweb3jsonrpc/WebThreeStubServerBase.h index 993c82c5c..b57a54c87 100644 --- a/libweb3jsonrpc/WebThreeStubServerBase.h +++ b/libweb3jsonrpc/WebThreeStubServerBase.h @@ -48,6 +48,9 @@ namespace shh class Interface; } +extern const unsigned SensibleHttpThreads; +extern const unsigned SensibleHttpPort; + class WebThreeStubDatabaseFace { public: @@ -68,69 +71,65 @@ public: WebThreeStubServerBase(jsonrpc::AbstractServerConnector& _conn, std::vector const& _accounts); virtual std::string web3_sha3(std::string const& _param1); - virtual Json::Value eth_accounts(); - virtual std::string eth_balanceAt(std::string const& _address); - virtual Json::Value eth_blockByHash(std::string const& _hash); - virtual Json::Value eth_blockByNumber(int _number); - virtual std::string eth_call(Json::Value const& _json); - virtual Json::Value eth_changed(int _id); - virtual std::string eth_codeAt(std::string const& _address); + virtual std::string web3_clientVersion() { return "C++ (ethereum-cpp)"; } + + virtual std::string net_version() { return ""; } + virtual std::string net_peerCount(); + virtual bool net_listening(); + + virtual std::string eth_protocolVersion(); virtual std::string eth_coinbase(); - virtual Json::Value eth_compilers(); - virtual double eth_countAt(std::string const& _address); - virtual double eth_transactionCountByHash(std::string const& _hash); - virtual double eth_transactionCountByNumber(int _number); - virtual double eth_uncleCountByHash(std::string const& _hash); - virtual double eth_uncleCountByNumber(int _number); - virtual int eth_defaultBlock(); + virtual bool eth_mining(); virtual std::string eth_gasPrice(); - virtual Json::Value eth_filterLogs(int _id); + virtual Json::Value eth_accounts(); + virtual std::string eth_blockNumber(); + virtual std::string eth_getBalance(std::string const& _address, std::string const& _blockNumber); + virtual std::string eth_getStorageAt(std::string const& _address, std::string const& _position, std::string const& _blockNumber); + virtual std::string eth_getTransactionCount(std::string const& _address, std::string const& _blockNumber); + virtual std::string eth_getBlockTransactionCountByHash(std::string const& _blockHash); + virtual std::string eth_getBlockTransactionCountByNumber(std::string const& _blockNumber); + virtual std::string eth_getUncleCountByBlockHash(std::string const& _blockHash); + virtual std::string eth_getUncleCountByBlockNumber(std::string const& _blockNumber); + virtual std::string eth_getCode(std::string const& _address, std::string const& _blockNumber); + virtual std::string eth_sendTransaction(Json::Value const& _json); + virtual std::string eth_call(Json::Value const& _json, std::string const& _blockNumber); virtual bool eth_flush(); - virtual Json::Value eth_logs(Json::Value const& _json); - virtual bool eth_listening(); - virtual bool eth_mining(); - virtual int eth_newFilter(Json::Value const& _json); - virtual int eth_newFilterString(std::string const& _filter); - virtual int eth_number(); - virtual int eth_peerCount(); - virtual bool eth_setCoinbase(std::string const& _address); - virtual bool eth_setDefaultBlock(int _block); - virtual bool eth_setListening(bool _listening); - virtual std::string eth_lll(std::string const& _s); - virtual std::string eth_serpent(std::string const& _s); - virtual bool eth_setMining(bool _mining); - virtual std::string eth_solidity(std::string const& _code); - virtual std::string eth_stateAt(std::string const& _address, std::string const& _storage); - virtual Json::Value eth_storageAt(std::string const& _address); - virtual std::string eth_transact(Json::Value const& _json); - virtual Json::Value eth_transactionByHash(std::string const& _hash, int _i); - virtual Json::Value eth_transactionByNumber(int _number, int _i); - virtual Json::Value eth_uncleByHash(std::string const& _hash, int _i); - virtual Json::Value eth_uncleByNumber(int _number, int _i); - virtual bool eth_uninstallFilter(int _id); - + virtual Json::Value eth_getBlockByHash(std::string const& _blockHash, bool _includeTransactions); + virtual Json::Value eth_getBlockByNumber(std::string const& _blockNumber, bool _includeTransactions); + virtual Json::Value eth_getTransactionByHash(std::string const& _transactionHash); + virtual Json::Value eth_getTransactionByBlockHashAndIndex(std::string const& _blockHash, std::string const& _transactionIndex); + virtual Json::Value eth_getTransactionByBlockNumberAndIndex(std::string const& _blockNumber, std::string const& _transactionIndex); + virtual Json::Value eth_getUncleByBlockHashAndIndex(std::string const& _blockHash, std::string const& _uncleIndex); + virtual Json::Value eth_getUncleByBlockNumberAndIndex(std::string const& _blockNumber, std::string const& _uncleIndex); + virtual Json::Value eth_getCompilers(); + virtual std::string eth_compileLLL(std::string const& _s); + virtual std::string eth_compileSerpent(std::string const& _s); + virtual std::string eth_compileSolidity(std::string const& _code); + virtual std::string eth_newFilter(Json::Value const& _json); + virtual std::string eth_newBlockFilter(std::string const& _filter); + virtual bool eth_uninstallFilter(std::string const& _filterId); + virtual Json::Value eth_getFilterChanges(std::string const& _filterId); + virtual Json::Value eth_getFilterLogs(std::string const& _filterId); + virtual Json::Value eth_getLogs(Json::Value const& _json); virtual Json::Value eth_getWork(); virtual bool eth_submitWork(std::string const& _nonce, std::string const& _mixHash); - - virtual int eth_register(std::string const& _address); - virtual bool eth_unregister(int _id); - virtual Json::Value eth_queuedTransactions(int _id); - - virtual std::string db_get(std::string const& _name, std::string const& _key); - virtual std::string db_getString(std::string const& _name, std::string const& _key); + virtual std::string eth_register(std::string const& _address); + virtual bool eth_unregister(std::string const& _accountId); + virtual Json::Value eth_fetchQueuedTransactions(std::string const& _accountId); + virtual bool db_put(std::string const& _name, std::string const& _key, std::string const& _value); - virtual bool db_putString(std::string const& _name, std::string const& _key, std::string const& _value); + virtual std::string db_get(std::string const& _name, std::string const& _key); - virtual std::string shh_addToGroup(std::string const& _group, std::string const& _who); - virtual Json::Value shh_changed(int _id); - virtual Json::Value shh_getMessages(int _id); - virtual bool shh_haveIdentity(std::string const& _id); - virtual int shh_newFilter(Json::Value const& _json); - virtual std::string shh_newGroup(std::string const& _id, std::string const& _who); - virtual std::string shh_newIdentity(); virtual bool shh_post(Json::Value const& _json); - virtual bool shh_uninstallFilter(int _id); - + virtual std::string shh_newIdentity(); + virtual bool shh_hasIdentity(std::string const& _identity); + virtual std::string shh_newGroup(std::string const& _id, std::string const& _who); + virtual std::string shh_addToGroup(std::string const& _group, std::string const& _who); + virtual std::string shh_newFilter(Json::Value const& _json); + virtual bool shh_uninstallFilter(std::string const& _filterId); + virtual Json::Value shh_getFilterChanges(std::string const& _filterId); + virtual Json::Value shh_getMessages(std::string const& _filterId); + void setAccounts(std::vector const& _accounts); void setIdentities(std::vector const& _ids); std::map const& ids() const { return m_ids; } diff --git a/libweb3jsonrpc/abstractwebthreestubserver.h b/libweb3jsonrpc/abstractwebthreestubserver.h index bb84afc9b..6cc5de3e6 100644 --- a/libweb3jsonrpc/abstractwebthreestubserver.h +++ b/libweb3jsonrpc/abstractwebthreestubserver.h @@ -13,239 +13,229 @@ class AbstractWebThreeStubServer : public jsonrpc::AbstractServer(conn, type) { this->bindAndAddMethod(jsonrpc::Procedure("web3_sha3", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_STRING, "param1",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::web3_sha3I); + this->bindAndAddMethod(jsonrpc::Procedure("web3_clientVersion", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::web3_clientVersionI); + this->bindAndAddMethod(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_setCoinbase", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_BOOLEAN, "param1",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_setCoinbaseI); - this->bindAndAddMethod(jsonrpc::Procedure("eth_listening", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_BOOLEAN, NULL), &AbstractWebThreeStubServer::eth_listeningI); - this->bindAndAddMethod(jsonrpc::Procedure("eth_setListening", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_BOOLEAN, "param1",jsonrpc::JSON_BOOLEAN, NULL), &AbstractWebThreeStubServer::eth_setListeningI); this->bindAndAddMethod(jsonrpc::Procedure("eth_mining", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_BOOLEAN, NULL), &AbstractWebThreeStubServer::eth_miningI); - this->bindAndAddMethod(jsonrpc::Procedure("eth_setMining", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_BOOLEAN, "param1",jsonrpc::JSON_BOOLEAN, NULL), &AbstractWebThreeStubServer::eth_setMiningI); this->bindAndAddMethod(jsonrpc::Procedure("eth_gasPrice", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_gasPriceI); this->bindAndAddMethod(jsonrpc::Procedure("eth_accounts", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_ARRAY, NULL), &AbstractWebThreeStubServer::eth_accountsI); - this->bindAndAddMethod(jsonrpc::Procedure("eth_peerCount", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_INTEGER, NULL), &AbstractWebThreeStubServer::eth_peerCountI); - this->bindAndAddMethod(jsonrpc::Procedure("eth_defaultBlock", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_INTEGER, NULL), &AbstractWebThreeStubServer::eth_defaultBlockI); - this->bindAndAddMethod(jsonrpc::Procedure("eth_setDefaultBlock", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_BOOLEAN, "param1",jsonrpc::JSON_INTEGER, NULL), &AbstractWebThreeStubServer::eth_setDefaultBlockI); - this->bindAndAddMethod(jsonrpc::Procedure("eth_number", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_INTEGER, NULL), &AbstractWebThreeStubServer::eth_numberI); - this->bindAndAddMethod(jsonrpc::Procedure("eth_balanceAt", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_STRING, "param1",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_balanceAtI); - this->bindAndAddMethod(jsonrpc::Procedure("eth_stateAt", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_STRING, "param1",jsonrpc::JSON_STRING,"param2",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_stateAtI); - this->bindAndAddMethod(jsonrpc::Procedure("eth_storageAt", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_OBJECT, "param1",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_storageAtI); - this->bindAndAddMethod(jsonrpc::Procedure("eth_countAt", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_REAL, "param1",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_countAtI); - this->bindAndAddMethod(jsonrpc::Procedure("eth_transactionCountByHash", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_REAL, "param1",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_transactionCountByHashI); - this->bindAndAddMethod(jsonrpc::Procedure("eth_transactionCountByNumber", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_REAL, "param1",jsonrpc::JSON_INTEGER, NULL), &AbstractWebThreeStubServer::eth_transactionCountByNumberI); - this->bindAndAddMethod(jsonrpc::Procedure("eth_uncleCountByHash", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_REAL, "param1",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_uncleCountByHashI); - this->bindAndAddMethod(jsonrpc::Procedure("eth_uncleCountByNumber", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_REAL, "param1",jsonrpc::JSON_INTEGER, NULL), &AbstractWebThreeStubServer::eth_uncleCountByNumberI); - this->bindAndAddMethod(jsonrpc::Procedure("eth_codeAt", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_STRING, "param1",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_codeAtI); - this->bindAndAddMethod(jsonrpc::Procedure("eth_transact", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_STRING, "param1",jsonrpc::JSON_OBJECT, NULL), &AbstractWebThreeStubServer::eth_transactI); - this->bindAndAddMethod(jsonrpc::Procedure("eth_call", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_STRING, "param1",jsonrpc::JSON_OBJECT, NULL), &AbstractWebThreeStubServer::eth_callI); + this->bindAndAddMethod(jsonrpc::Procedure("eth_blockNumber", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_blockNumberI); + this->bindAndAddMethod(jsonrpc::Procedure("eth_getBalance", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_STRING, "param1",jsonrpc::JSON_STRING,"param2",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_getBalanceI); + this->bindAndAddMethod(jsonrpc::Procedure("eth_getStorageAt", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_STRING, "param1",jsonrpc::JSON_STRING,"param2",jsonrpc::JSON_STRING,"param3",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_getStorageAtI); + this->bindAndAddMethod(jsonrpc::Procedure("eth_getTransactionCount", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_STRING, "param1",jsonrpc::JSON_STRING,"param2",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_getTransactionCountI); + this->bindAndAddMethod(jsonrpc::Procedure("eth_getBlockTransactionCountByHash", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_STRING, "param1",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_getBlockTransactionCountByHashI); + this->bindAndAddMethod(jsonrpc::Procedure("eth_getBlockTransactionCountByNumber", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_STRING, "param1",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_getBlockTransactionCountByNumberI); + this->bindAndAddMethod(jsonrpc::Procedure("eth_getUncleCountByBlockHash", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_STRING, "param1",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_getUncleCountByBlockHashI); + this->bindAndAddMethod(jsonrpc::Procedure("eth_getUncleCountByBlockNumber", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_STRING, "param1",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_getUncleCountByBlockNumberI); + this->bindAndAddMethod(jsonrpc::Procedure("eth_getCode", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_STRING, "param1",jsonrpc::JSON_STRING,"param2",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_getCodeI); + this->bindAndAddMethod(jsonrpc::Procedure("eth_sendTransaction", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_STRING, "param1",jsonrpc::JSON_OBJECT, NULL), &AbstractWebThreeStubServer::eth_sendTransactionI); + this->bindAndAddMethod(jsonrpc::Procedure("eth_call", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_STRING, "param1",jsonrpc::JSON_OBJECT,"param2",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_callI); this->bindAndAddMethod(jsonrpc::Procedure("eth_flush", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_BOOLEAN, NULL), &AbstractWebThreeStubServer::eth_flushI); - this->bindAndAddMethod(jsonrpc::Procedure("eth_blockByHash", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_OBJECT, "param1",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_blockByHashI); - this->bindAndAddMethod(jsonrpc::Procedure("eth_blockByNumber", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_OBJECT, "param1",jsonrpc::JSON_INTEGER, NULL), &AbstractWebThreeStubServer::eth_blockByNumberI); - this->bindAndAddMethod(jsonrpc::Procedure("eth_transactionByHash", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_OBJECT, "param1",jsonrpc::JSON_STRING,"param2",jsonrpc::JSON_INTEGER, NULL), &AbstractWebThreeStubServer::eth_transactionByHashI); - this->bindAndAddMethod(jsonrpc::Procedure("eth_transactionByNumber", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_OBJECT, "param1",jsonrpc::JSON_INTEGER,"param2",jsonrpc::JSON_INTEGER, NULL), &AbstractWebThreeStubServer::eth_transactionByNumberI); - this->bindAndAddMethod(jsonrpc::Procedure("eth_uncleByHash", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_OBJECT, "param1",jsonrpc::JSON_STRING,"param2",jsonrpc::JSON_INTEGER, NULL), &AbstractWebThreeStubServer::eth_uncleByHashI); - this->bindAndAddMethod(jsonrpc::Procedure("eth_uncleByNumber", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_OBJECT, "param1",jsonrpc::JSON_INTEGER,"param2",jsonrpc::JSON_INTEGER, NULL), &AbstractWebThreeStubServer::eth_uncleByNumberI); - this->bindAndAddMethod(jsonrpc::Procedure("eth_compilers", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_ARRAY, NULL), &AbstractWebThreeStubServer::eth_compilersI); - this->bindAndAddMethod(jsonrpc::Procedure("eth_lll", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_STRING, "param1",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_lllI); - this->bindAndAddMethod(jsonrpc::Procedure("eth_solidity", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_STRING, "param1",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_solidityI); - this->bindAndAddMethod(jsonrpc::Procedure("eth_serpent", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_STRING, "param1",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_serpentI); - this->bindAndAddMethod(jsonrpc::Procedure("eth_newFilter", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_INTEGER, "param1",jsonrpc::JSON_OBJECT, NULL), &AbstractWebThreeStubServer::eth_newFilterI); - this->bindAndAddMethod(jsonrpc::Procedure("eth_newFilterString", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_INTEGER, "param1",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_newFilterStringI); - this->bindAndAddMethod(jsonrpc::Procedure("eth_uninstallFilter", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_BOOLEAN, "param1",jsonrpc::JSON_INTEGER, NULL), &AbstractWebThreeStubServer::eth_uninstallFilterI); - this->bindAndAddMethod(jsonrpc::Procedure("eth_changed", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_ARRAY, "param1",jsonrpc::JSON_INTEGER, NULL), &AbstractWebThreeStubServer::eth_changedI); - this->bindAndAddMethod(jsonrpc::Procedure("eth_filterLogs", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_ARRAY, "param1",jsonrpc::JSON_INTEGER, NULL), &AbstractWebThreeStubServer::eth_filterLogsI); - this->bindAndAddMethod(jsonrpc::Procedure("eth_logs", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_ARRAY, "param1",jsonrpc::JSON_OBJECT, NULL), &AbstractWebThreeStubServer::eth_logsI); + this->bindAndAddMethod(jsonrpc::Procedure("eth_getBlockByHash", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_OBJECT, "param1",jsonrpc::JSON_STRING,"param2",jsonrpc::JSON_BOOLEAN, NULL), &AbstractWebThreeStubServer::eth_getBlockByHashI); + this->bindAndAddMethod(jsonrpc::Procedure("eth_getBlockByNumber", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_OBJECT, "param1",jsonrpc::JSON_STRING,"param2",jsonrpc::JSON_BOOLEAN, NULL), &AbstractWebThreeStubServer::eth_getBlockByNumberI); + this->bindAndAddMethod(jsonrpc::Procedure("eth_getTransactionByHash", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_OBJECT, "param1",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_getTransactionByHashI); + this->bindAndAddMethod(jsonrpc::Procedure("eth_getTransactionByBlockHashAndIndex", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_OBJECT, "param1",jsonrpc::JSON_STRING,"param2",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_getTransactionByBlockHashAndIndexI); + this->bindAndAddMethod(jsonrpc::Procedure("eth_getTransactionByBlockNumberAndIndex", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_OBJECT, "param1",jsonrpc::JSON_STRING,"param2",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_getTransactionByBlockNumberAndIndexI); + this->bindAndAddMethod(jsonrpc::Procedure("eth_getUncleByBlockHashAndIndex", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_OBJECT, "param1",jsonrpc::JSON_STRING,"param2",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_getUncleByBlockHashAndIndexI); + this->bindAndAddMethod(jsonrpc::Procedure("eth_getUncleByBlockNumberAndIndex", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_OBJECT, "param1",jsonrpc::JSON_STRING,"param2",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_getUncleByBlockNumberAndIndexI); + this->bindAndAddMethod(jsonrpc::Procedure("eth_getCompilers", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_ARRAY, NULL), &AbstractWebThreeStubServer::eth_getCompilersI); + this->bindAndAddMethod(jsonrpc::Procedure("eth_compileLLL", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_STRING, "param1",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_compileLLLI); + this->bindAndAddMethod(jsonrpc::Procedure("eth_compileSerpent", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_STRING, "param1",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_compileSerpentI); + this->bindAndAddMethod(jsonrpc::Procedure("eth_compileSolidity", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_STRING, "param1",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_compileSolidityI); + this->bindAndAddMethod(jsonrpc::Procedure("eth_newFilter", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_STRING, "param1",jsonrpc::JSON_OBJECT, NULL), &AbstractWebThreeStubServer::eth_newFilterI); + this->bindAndAddMethod(jsonrpc::Procedure("eth_newBlockFilter", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_STRING, "param1",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_newBlockFilterI); + this->bindAndAddMethod(jsonrpc::Procedure("eth_uninstallFilter", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_BOOLEAN, "param1",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_uninstallFilterI); + this->bindAndAddMethod(jsonrpc::Procedure("eth_getFilterChanges", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_ARRAY, "param1",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_getFilterChangesI); + this->bindAndAddMethod(jsonrpc::Procedure("eth_getFilterLogs", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_ARRAY, "param1",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_getFilterLogsI); + this->bindAndAddMethod(jsonrpc::Procedure("eth_getLogs", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_ARRAY, "param1",jsonrpc::JSON_OBJECT, NULL), &AbstractWebThreeStubServer::eth_getLogsI); this->bindAndAddMethod(jsonrpc::Procedure("eth_getWork", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_ARRAY, NULL), &AbstractWebThreeStubServer::eth_getWorkI); this->bindAndAddMethod(jsonrpc::Procedure("eth_submitWork", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_BOOLEAN, "param1",jsonrpc::JSON_STRING,"param2",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_submitWorkI); - this->bindAndAddMethod(jsonrpc::Procedure("eth_register", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_INTEGER, "param1",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_registerI); - this->bindAndAddMethod(jsonrpc::Procedure("eth_unregister", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_BOOLEAN, "param1",jsonrpc::JSON_INTEGER, NULL), &AbstractWebThreeStubServer::eth_unregisterI); - this->bindAndAddMethod(jsonrpc::Procedure("eth_queuedTransactions", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_ARRAY, "param1",jsonrpc::JSON_INTEGER, NULL), &AbstractWebThreeStubServer::eth_queuedTransactionsI); + this->bindAndAddMethod(jsonrpc::Procedure("eth_register", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_STRING, "param1",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_registerI); + this->bindAndAddMethod(jsonrpc::Procedure("eth_unregister", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_BOOLEAN, "param1",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_unregisterI); + this->bindAndAddMethod(jsonrpc::Procedure("eth_fetchQueuedTransactions", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_ARRAY, "param1",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::eth_fetchQueuedTransactionsI); this->bindAndAddMethod(jsonrpc::Procedure("db_put", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_BOOLEAN, "param1",jsonrpc::JSON_STRING,"param2",jsonrpc::JSON_STRING,"param3",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::db_putI); this->bindAndAddMethod(jsonrpc::Procedure("db_get", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_STRING, "param1",jsonrpc::JSON_STRING,"param2",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::db_getI); - this->bindAndAddMethod(jsonrpc::Procedure("db_putString", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_BOOLEAN, "param1",jsonrpc::JSON_STRING,"param2",jsonrpc::JSON_STRING,"param3",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::db_putStringI); - this->bindAndAddMethod(jsonrpc::Procedure("db_getString", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_STRING, "param1",jsonrpc::JSON_STRING,"param2",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::db_getStringI); this->bindAndAddMethod(jsonrpc::Procedure("shh_post", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_BOOLEAN, "param1",jsonrpc::JSON_OBJECT, NULL), &AbstractWebThreeStubServer::shh_postI); this->bindAndAddMethod(jsonrpc::Procedure("shh_newIdentity", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::shh_newIdentityI); - this->bindAndAddMethod(jsonrpc::Procedure("shh_haveIdentity", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_BOOLEAN, "param1",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::shh_haveIdentityI); + this->bindAndAddMethod(jsonrpc::Procedure("shh_hasIdentity", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_BOOLEAN, "param1",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::shh_hasIdentityI); this->bindAndAddMethod(jsonrpc::Procedure("shh_newGroup", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_STRING, "param1",jsonrpc::JSON_STRING,"param2",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::shh_newGroupI); this->bindAndAddMethod(jsonrpc::Procedure("shh_addToGroup", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_STRING, "param1",jsonrpc::JSON_STRING,"param2",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::shh_addToGroupI); - this->bindAndAddMethod(jsonrpc::Procedure("shh_newFilter", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_INTEGER, "param1",jsonrpc::JSON_OBJECT, NULL), &AbstractWebThreeStubServer::shh_newFilterI); - this->bindAndAddMethod(jsonrpc::Procedure("shh_uninstallFilter", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_BOOLEAN, "param1",jsonrpc::JSON_INTEGER, NULL), &AbstractWebThreeStubServer::shh_uninstallFilterI); - this->bindAndAddMethod(jsonrpc::Procedure("shh_changed", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_ARRAY, "param1",jsonrpc::JSON_INTEGER, NULL), &AbstractWebThreeStubServer::shh_changedI); - this->bindAndAddMethod(jsonrpc::Procedure("shh_getMessages", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_ARRAY, "param1",jsonrpc::JSON_INTEGER, NULL), &AbstractWebThreeStubServer::shh_getMessagesI); + this->bindAndAddMethod(jsonrpc::Procedure("shh_newFilter", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_STRING, "param1",jsonrpc::JSON_OBJECT, NULL), &AbstractWebThreeStubServer::shh_newFilterI); + this->bindAndAddMethod(jsonrpc::Procedure("shh_uninstallFilter", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_BOOLEAN, "param1",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::shh_uninstallFilterI); + this->bindAndAddMethod(jsonrpc::Procedure("shh_getFilterChanges", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_ARRAY, "param1",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::shh_getFilterChangesI); + this->bindAndAddMethod(jsonrpc::Procedure("shh_getMessages", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_ARRAY, "param1",jsonrpc::JSON_STRING, NULL), &AbstractWebThreeStubServer::shh_getMessagesI); } inline virtual void web3_sha3I(const Json::Value &request, Json::Value &response) { response = this->web3_sha3(request[0u].asString()); } - inline virtual void eth_coinbaseI(const Json::Value &request, Json::Value &response) + inline virtual void web3_clientVersionI(const Json::Value &request, Json::Value &response) { (void)request; - response = this->eth_coinbase(); - } - inline virtual void eth_setCoinbaseI(const Json::Value &request, Json::Value &response) - { - response = this->eth_setCoinbase(request[0u].asString()); + response = this->web3_clientVersion(); } - inline virtual void eth_listeningI(const Json::Value &request, Json::Value &response) + inline virtual void net_versionI(const Json::Value &request, Json::Value &response) { (void)request; - response = this->eth_listening(); + response = this->net_version(); } - inline virtual void eth_setListeningI(const Json::Value &request, Json::Value &response) - { - response = this->eth_setListening(request[0u].asBool()); - } - inline virtual void eth_miningI(const Json::Value &request, Json::Value &response) + inline virtual void net_peerCountI(const Json::Value &request, Json::Value &response) { (void)request; - response = this->eth_mining(); + response = this->net_peerCount(); } - inline virtual void eth_setMiningI(const Json::Value &request, Json::Value &response) - { - response = this->eth_setMining(request[0u].asBool()); - } - inline virtual void eth_gasPriceI(const Json::Value &request, Json::Value &response) + inline virtual void net_listeningI(const Json::Value &request, Json::Value &response) { (void)request; - response = this->eth_gasPrice(); + response = this->net_listening(); } - inline virtual void eth_accountsI(const Json::Value &request, Json::Value &response) + inline virtual void eth_protocolVersionI(const Json::Value &request, Json::Value &response) { (void)request; - response = this->eth_accounts(); + response = this->eth_protocolVersion(); } - inline virtual void eth_peerCountI(const Json::Value &request, Json::Value &response) + inline virtual void eth_coinbaseI(const Json::Value &request, Json::Value &response) { (void)request; - response = this->eth_peerCount(); + response = this->eth_coinbase(); } - inline virtual void eth_defaultBlockI(const Json::Value &request, Json::Value &response) + inline virtual void eth_miningI(const Json::Value &request, Json::Value &response) { (void)request; - response = this->eth_defaultBlock(); + response = this->eth_mining(); } - inline virtual void eth_setDefaultBlockI(const Json::Value &request, Json::Value &response) + inline virtual void eth_gasPriceI(const Json::Value &request, Json::Value &response) { - response = this->eth_setDefaultBlock(request[0u].asInt()); + (void)request; + response = this->eth_gasPrice(); } - inline virtual void eth_numberI(const Json::Value &request, Json::Value &response) + inline virtual void eth_accountsI(const Json::Value &request, Json::Value &response) { (void)request; - response = this->eth_number(); + response = this->eth_accounts(); } - inline virtual void eth_balanceAtI(const Json::Value &request, Json::Value &response) + inline virtual void eth_blockNumberI(const Json::Value &request, Json::Value &response) { - response = this->eth_balanceAt(request[0u].asString()); + (void)request; + response = this->eth_blockNumber(); } - inline virtual void eth_stateAtI(const Json::Value &request, Json::Value &response) + inline virtual void eth_getBalanceI(const Json::Value &request, Json::Value &response) { - response = this->eth_stateAt(request[0u].asString(), request[1u].asString()); + response = this->eth_getBalance(request[0u].asString(), request[1u].asString()); } - inline virtual void eth_storageAtI(const Json::Value &request, Json::Value &response) + inline virtual void eth_getStorageAtI(const Json::Value &request, Json::Value &response) { - response = this->eth_storageAt(request[0u].asString()); + response = this->eth_getStorageAt(request[0u].asString(), request[1u].asString(), request[2u].asString()); } - inline virtual void eth_countAtI(const Json::Value &request, Json::Value &response) + inline virtual void eth_getTransactionCountI(const Json::Value &request, Json::Value &response) { - response = this->eth_countAt(request[0u].asString()); + response = this->eth_getTransactionCount(request[0u].asString(), request[1u].asString()); } - inline virtual void eth_transactionCountByHashI(const Json::Value &request, Json::Value &response) + inline virtual void eth_getBlockTransactionCountByHashI(const Json::Value &request, Json::Value &response) { - response = this->eth_transactionCountByHash(request[0u].asString()); + response = this->eth_getBlockTransactionCountByHash(request[0u].asString()); } - inline virtual void eth_transactionCountByNumberI(const Json::Value &request, Json::Value &response) + inline virtual void eth_getBlockTransactionCountByNumberI(const Json::Value &request, Json::Value &response) { - response = this->eth_transactionCountByNumber(request[0u].asInt()); + response = this->eth_getBlockTransactionCountByNumber(request[0u].asString()); } - inline virtual void eth_uncleCountByHashI(const Json::Value &request, Json::Value &response) + inline virtual void eth_getUncleCountByBlockHashI(const Json::Value &request, Json::Value &response) { - response = this->eth_uncleCountByHash(request[0u].asString()); + response = this->eth_getUncleCountByBlockHash(request[0u].asString()); } - inline virtual void eth_uncleCountByNumberI(const Json::Value &request, Json::Value &response) + inline virtual void eth_getUncleCountByBlockNumberI(const Json::Value &request, Json::Value &response) { - response = this->eth_uncleCountByNumber(request[0u].asInt()); + response = this->eth_getUncleCountByBlockNumber(request[0u].asString()); } - inline virtual void eth_codeAtI(const Json::Value &request, Json::Value &response) + inline virtual void eth_getCodeI(const Json::Value &request, Json::Value &response) { - response = this->eth_codeAt(request[0u].asString()); + response = this->eth_getCode(request[0u].asString(), request[1u].asString()); } - inline virtual void eth_transactI(const Json::Value &request, Json::Value &response) + inline virtual void eth_sendTransactionI(const Json::Value &request, Json::Value &response) { - response = this->eth_transact(request[0u]); + response = this->eth_sendTransaction(request[0u]); } inline virtual void eth_callI(const Json::Value &request, Json::Value &response) { - response = this->eth_call(request[0u]); + response = this->eth_call(request[0u], request[1u].asString()); } inline virtual void eth_flushI(const Json::Value &request, Json::Value &response) { (void)request; response = this->eth_flush(); } - inline virtual void eth_blockByHashI(const Json::Value &request, Json::Value &response) + inline virtual void eth_getBlockByHashI(const Json::Value &request, Json::Value &response) + { + response = this->eth_getBlockByHash(request[0u].asString(), request[1u].asBool()); + } + inline virtual void eth_getBlockByNumberI(const Json::Value &request, Json::Value &response) { - response = this->eth_blockByHash(request[0u].asString()); + response = this->eth_getBlockByNumber(request[0u].asString(), request[1u].asBool()); } - inline virtual void eth_blockByNumberI(const Json::Value &request, Json::Value &response) + inline virtual void eth_getTransactionByHashI(const Json::Value &request, Json::Value &response) { - response = this->eth_blockByNumber(request[0u].asInt()); + response = this->eth_getTransactionByHash(request[0u].asString()); } - inline virtual void eth_transactionByHashI(const Json::Value &request, Json::Value &response) + inline virtual void eth_getTransactionByBlockHashAndIndexI(const Json::Value &request, Json::Value &response) { - response = this->eth_transactionByHash(request[0u].asString(), request[1u].asInt()); + response = this->eth_getTransactionByBlockHashAndIndex(request[0u].asString(), request[1u].asString()); } - inline virtual void eth_transactionByNumberI(const Json::Value &request, Json::Value &response) + inline virtual void eth_getTransactionByBlockNumberAndIndexI(const Json::Value &request, Json::Value &response) { - response = this->eth_transactionByNumber(request[0u].asInt(), request[1u].asInt()); + response = this->eth_getTransactionByBlockNumberAndIndex(request[0u].asString(), request[1u].asString()); } - inline virtual void eth_uncleByHashI(const Json::Value &request, Json::Value &response) + inline virtual void eth_getUncleByBlockHashAndIndexI(const Json::Value &request, Json::Value &response) { - response = this->eth_uncleByHash(request[0u].asString(), request[1u].asInt()); + response = this->eth_getUncleByBlockHashAndIndex(request[0u].asString(), request[1u].asString()); } - inline virtual void eth_uncleByNumberI(const Json::Value &request, Json::Value &response) + inline virtual void eth_getUncleByBlockNumberAndIndexI(const Json::Value &request, Json::Value &response) { - response = this->eth_uncleByNumber(request[0u].asInt(), request[1u].asInt()); + response = this->eth_getUncleByBlockNumberAndIndex(request[0u].asString(), request[1u].asString()); } - inline virtual void eth_compilersI(const Json::Value &request, Json::Value &response) + inline virtual void eth_getCompilersI(const Json::Value &request, Json::Value &response) { (void)request; - response = this->eth_compilers(); + response = this->eth_getCompilers(); } - inline virtual void eth_lllI(const Json::Value &request, Json::Value &response) + inline virtual void eth_compileLLLI(const Json::Value &request, Json::Value &response) { - response = this->eth_lll(request[0u].asString()); + response = this->eth_compileLLL(request[0u].asString()); } - inline virtual void eth_solidityI(const Json::Value &request, Json::Value &response) + inline virtual void eth_compileSerpentI(const Json::Value &request, Json::Value &response) { - response = this->eth_solidity(request[0u].asString()); + response = this->eth_compileSerpent(request[0u].asString()); } - inline virtual void eth_serpentI(const Json::Value &request, Json::Value &response) + inline virtual void eth_compileSolidityI(const Json::Value &request, Json::Value &response) { - response = this->eth_serpent(request[0u].asString()); + response = this->eth_compileSolidity(request[0u].asString()); } inline virtual void eth_newFilterI(const Json::Value &request, Json::Value &response) { response = this->eth_newFilter(request[0u]); } - inline virtual void eth_newFilterStringI(const Json::Value &request, Json::Value &response) + inline virtual void eth_newBlockFilterI(const Json::Value &request, Json::Value &response) { - response = this->eth_newFilterString(request[0u].asString()); + response = this->eth_newBlockFilter(request[0u].asString()); } inline virtual void eth_uninstallFilterI(const Json::Value &request, Json::Value &response) { - response = this->eth_uninstallFilter(request[0u].asInt()); + response = this->eth_uninstallFilter(request[0u].asString()); } - inline virtual void eth_changedI(const Json::Value &request, Json::Value &response) + inline virtual void eth_getFilterChangesI(const Json::Value &request, Json::Value &response) { - response = this->eth_changed(request[0u].asInt()); + response = this->eth_getFilterChanges(request[0u].asString()); } - inline virtual void eth_filterLogsI(const Json::Value &request, Json::Value &response) + inline virtual void eth_getFilterLogsI(const Json::Value &request, Json::Value &response) { - response = this->eth_filterLogs(request[0u].asInt()); + response = this->eth_getFilterLogs(request[0u].asString()); } - inline virtual void eth_logsI(const Json::Value &request, Json::Value &response) + inline virtual void eth_getLogsI(const Json::Value &request, Json::Value &response) { - response = this->eth_logs(request[0u]); + response = this->eth_getLogs(request[0u]); } inline virtual void eth_getWorkI(const Json::Value &request, Json::Value &response) { @@ -262,11 +252,11 @@ class AbstractWebThreeStubServer : public jsonrpc::AbstractServereth_unregister(request[0u].asInt()); + response = this->eth_unregister(request[0u].asString()); } - inline virtual void eth_queuedTransactionsI(const Json::Value &request, Json::Value &response) + inline virtual void eth_fetchQueuedTransactionsI(const Json::Value &request, Json::Value &response) { - response = this->eth_queuedTransactions(request[0u].asInt()); + response = this->eth_fetchQueuedTransactions(request[0u].asString()); } inline virtual void db_putI(const Json::Value &request, Json::Value &response) { @@ -276,14 +266,6 @@ class AbstractWebThreeStubServer : public jsonrpc::AbstractServerdb_get(request[0u].asString(), request[1u].asString()); } - inline virtual void db_putStringI(const Json::Value &request, Json::Value &response) - { - response = this->db_putString(request[0u].asString(), request[1u].asString(), request[2u].asString()); - } - inline virtual void db_getStringI(const Json::Value &request, Json::Value &response) - { - response = this->db_getString(request[0u].asString(), request[1u].asString()); - } inline virtual void shh_postI(const Json::Value &request, Json::Value &response) { response = this->shh_post(request[0u]); @@ -293,9 +275,9 @@ class AbstractWebThreeStubServer : public jsonrpc::AbstractServershh_newIdentity(); } - inline virtual void shh_haveIdentityI(const Json::Value &request, Json::Value &response) + inline virtual void shh_hasIdentityI(const Json::Value &request, Json::Value &response) { - response = this->shh_haveIdentity(request[0u].asString()); + response = this->shh_hasIdentity(request[0u].asString()); } inline virtual void shh_newGroupI(const Json::Value &request, Json::Value &response) { @@ -311,75 +293,71 @@ class AbstractWebThreeStubServer : public jsonrpc::AbstractServershh_uninstallFilter(request[0u].asInt()); + response = this->shh_uninstallFilter(request[0u].asString()); } - inline virtual void shh_changedI(const Json::Value &request, Json::Value &response) + inline virtual void shh_getFilterChangesI(const Json::Value &request, Json::Value &response) { - response = this->shh_changed(request[0u].asInt()); + response = this->shh_getFilterChanges(request[0u].asString()); } inline virtual void shh_getMessagesI(const Json::Value &request, Json::Value &response) { - response = this->shh_getMessages(request[0u].asInt()); + response = this->shh_getMessages(request[0u].asString()); } virtual std::string web3_sha3(const std::string& param1) = 0; + virtual std::string web3_clientVersion() = 0; + virtual std::string net_version() = 0; + virtual std::string net_peerCount() = 0; + virtual bool net_listening() = 0; + virtual std::string eth_protocolVersion() = 0; virtual std::string eth_coinbase() = 0; - virtual bool eth_setCoinbase(const std::string& param1) = 0; - virtual bool eth_listening() = 0; - virtual bool eth_setListening(bool param1) = 0; virtual bool eth_mining() = 0; - virtual bool eth_setMining(bool param1) = 0; virtual std::string eth_gasPrice() = 0; virtual Json::Value eth_accounts() = 0; - virtual int eth_peerCount() = 0; - virtual int eth_defaultBlock() = 0; - virtual bool eth_setDefaultBlock(int param1) = 0; - virtual int eth_number() = 0; - virtual std::string eth_balanceAt(const std::string& param1) = 0; - virtual std::string eth_stateAt(const std::string& param1, const std::string& param2) = 0; - virtual Json::Value eth_storageAt(const std::string& param1) = 0; - virtual double eth_countAt(const std::string& param1) = 0; - virtual double eth_transactionCountByHash(const std::string& param1) = 0; - virtual double eth_transactionCountByNumber(int param1) = 0; - virtual double eth_uncleCountByHash(const std::string& param1) = 0; - virtual double eth_uncleCountByNumber(int param1) = 0; - virtual std::string eth_codeAt(const std::string& param1) = 0; - virtual std::string eth_transact(const Json::Value& param1) = 0; - virtual std::string eth_call(const Json::Value& param1) = 0; + virtual std::string eth_blockNumber() = 0; + virtual std::string eth_getBalance(const std::string& param1, const std::string& param2) = 0; + virtual std::string eth_getStorageAt(const std::string& param1, const std::string& param2, const std::string& param3) = 0; + virtual std::string eth_getTransactionCount(const std::string& param1, const std::string& param2) = 0; + virtual std::string eth_getBlockTransactionCountByHash(const std::string& param1) = 0; + virtual std::string eth_getBlockTransactionCountByNumber(const std::string& param1) = 0; + virtual std::string eth_getUncleCountByBlockHash(const std::string& param1) = 0; + virtual std::string eth_getUncleCountByBlockNumber(const std::string& param1) = 0; + virtual std::string eth_getCode(const std::string& param1, const std::string& param2) = 0; + virtual std::string eth_sendTransaction(const Json::Value& param1) = 0; + virtual std::string eth_call(const Json::Value& param1, const std::string& param2) = 0; virtual bool eth_flush() = 0; - virtual Json::Value eth_blockByHash(const std::string& param1) = 0; - virtual Json::Value eth_blockByNumber(int param1) = 0; - virtual Json::Value eth_transactionByHash(const std::string& param1, int param2) = 0; - virtual Json::Value eth_transactionByNumber(int param1, int param2) = 0; - virtual Json::Value eth_uncleByHash(const std::string& param1, int param2) = 0; - virtual Json::Value eth_uncleByNumber(int param1, int param2) = 0; - virtual Json::Value eth_compilers() = 0; - virtual std::string eth_lll(const std::string& param1) = 0; - virtual std::string eth_solidity(const std::string& param1) = 0; - virtual std::string eth_serpent(const std::string& param1) = 0; - virtual int eth_newFilter(const Json::Value& param1) = 0; - virtual int eth_newFilterString(const std::string& param1) = 0; - virtual bool eth_uninstallFilter(int param1) = 0; - virtual Json::Value eth_changed(int param1) = 0; - virtual Json::Value eth_filterLogs(int param1) = 0; - virtual Json::Value eth_logs(const Json::Value& param1) = 0; + virtual Json::Value eth_getBlockByHash(const std::string& param1, bool param2) = 0; + virtual Json::Value eth_getBlockByNumber(const std::string& param1, bool param2) = 0; + virtual Json::Value eth_getTransactionByHash(const std::string& param1) = 0; + virtual Json::Value eth_getTransactionByBlockHashAndIndex(const std::string& param1, const std::string& param2) = 0; + virtual Json::Value eth_getTransactionByBlockNumberAndIndex(const std::string& param1, const std::string& param2) = 0; + virtual Json::Value eth_getUncleByBlockHashAndIndex(const std::string& param1, const std::string& param2) = 0; + virtual Json::Value eth_getUncleByBlockNumberAndIndex(const std::string& param1, const std::string& param2) = 0; + virtual Json::Value eth_getCompilers() = 0; + virtual std::string eth_compileLLL(const std::string& param1) = 0; + virtual std::string eth_compileSerpent(const std::string& param1) = 0; + virtual std::string eth_compileSolidity(const std::string& param1) = 0; + virtual std::string eth_newFilter(const Json::Value& param1) = 0; + virtual std::string eth_newBlockFilter(const std::string& param1) = 0; + virtual bool eth_uninstallFilter(const std::string& param1) = 0; + virtual Json::Value eth_getFilterChanges(const std::string& param1) = 0; + virtual Json::Value eth_getFilterLogs(const std::string& param1) = 0; + virtual Json::Value eth_getLogs(const Json::Value& param1) = 0; virtual Json::Value eth_getWork() = 0; virtual bool eth_submitWork(const std::string& param1, const std::string& param2) = 0; - virtual int eth_register(const std::string& param1) = 0; - virtual bool eth_unregister(int param1) = 0; - virtual Json::Value eth_queuedTransactions(int param1) = 0; + virtual std::string eth_register(const std::string& param1) = 0; + virtual bool eth_unregister(const std::string& param1) = 0; + virtual Json::Value eth_fetchQueuedTransactions(const std::string& param1) = 0; virtual bool db_put(const std::string& param1, const std::string& param2, const std::string& param3) = 0; virtual std::string db_get(const std::string& param1, const std::string& param2) = 0; - virtual bool db_putString(const std::string& param1, const std::string& param2, const std::string& param3) = 0; - virtual std::string db_getString(const std::string& param1, const std::string& param2) = 0; virtual bool shh_post(const Json::Value& param1) = 0; virtual std::string shh_newIdentity() = 0; - virtual bool shh_haveIdentity(const std::string& param1) = 0; + virtual bool shh_hasIdentity(const std::string& param1) = 0; virtual std::string shh_newGroup(const std::string& param1, const std::string& param2) = 0; virtual std::string shh_addToGroup(const std::string& param1, const std::string& param2) = 0; - virtual int shh_newFilter(const Json::Value& param1) = 0; - virtual bool shh_uninstallFilter(int param1) = 0; - virtual Json::Value shh_changed(int param1) = 0; - virtual Json::Value shh_getMessages(int param1) = 0; + virtual std::string shh_newFilter(const Json::Value& param1) = 0; + virtual bool shh_uninstallFilter(const std::string& param1) = 0; + virtual Json::Value shh_getFilterChanges(const std::string& param1) = 0; + virtual Json::Value shh_getMessages(const std::string& param1) = 0; }; #endif //JSONRPC_CPP_STUB_ABSTRACTWEBTHREESTUBSERVER_H_ diff --git a/libweb3jsonrpc/spec.json b/libweb3jsonrpc/spec.json index 3f4227421..39c482605 100644 --- a/libweb3jsonrpc/spec.json +++ b/libweb3jsonrpc/spec.json @@ -1,73 +1,62 @@ [ { "name": "web3_sha3", "params": [""], "order": [], "returns" : "" }, + { "name": "web3_clientVersion", "params": [], "order": [], "returns" : "" }, + { "name": "net_version", "params": [], "order": [], "returns" : "" }, + { "name": "net_peerCount", "params": [], "order": [], "returns" : "" }, + { "name": "net_listening", "params": [], "order": [], "returns" : false }, + + { "name": "eth_protocolVersion", "params": [], "order": [], "returns" : "" }, { "name": "eth_coinbase", "params": [], "order": [], "returns" : "" }, - { "name": "eth_setCoinbase", "params": [""], "order": [], "returns" : true }, - { "name": "eth_listening", "params": [], "order": [], "returns" : false }, - { "name": "eth_setListening", "params": [false], "order" : [], "returns" : true }, { "name": "eth_mining", "params": [], "order": [], "returns" : false }, - { "name": "eth_setMining", "params": [false], "order" : [], "returns" : true }, { "name": "eth_gasPrice", "params": [], "order": [], "returns" : "" }, { "name": "eth_accounts", "params": [], "order": [], "returns" : [] }, - { "name": "eth_peerCount", "params": [], "order": [], "returns" : 0 }, - { "name": "eth_defaultBlock", "params": [], "order": [], "returns" : 0}, - { "name": "eth_setDefaultBlock", "params": [0], "order": [], "returns" : true}, - { "name": "eth_number", "params": [], "order": [], "returns" : 0}, - - { "name": "eth_balanceAt", "params": [""], "order": [], "returns" : ""}, - { "name": "eth_stateAt", "params": ["", ""], "order": [], "returns": ""}, - { "name": "eth_storageAt", "params": [""], "order": [], "returns": {}}, - { "name": "eth_countAt", "params": [""], "order": [], "returns" : 0.0}, - { "name": "eth_transactionCountByHash", "params": [""], "order": [], "returns" : 0.0}, - { "name": "eth_transactionCountByNumber", "params": [0], "order": [], "returns" : 0.0}, - { "name": "eth_uncleCountByHash", "params": [""], "order": [], "returns" : 0.0}, - { "name": "eth_uncleCountByNumber", "params": [0], "order": [], "returns" : 0.0}, - { "name": "eth_codeAt", "params": [""], "order": [], "returns": ""}, - - { "name": "eth_transact", "params": [{}], "order": [], "returns": ""}, - { "name": "eth_call", "params": [{}], "order": [], "returns": ""}, + { "name": "eth_blockNumber", "params": [], "order": [], "returns" : ""}, + { "name": "eth_getBalance", "params": ["", ""], "order": [], "returns" : ""}, + { "name": "eth_getStorageAt", "params": ["", "", ""], "order": [], "returns": ""}, + { "name": "eth_getTransactionCount", "params": ["", ""], "order": [], "returns" : ""}, + { "name": "eth_getBlockTransactionCountByHash", "params": [""], "order": [], "returns" : ""}, + { "name": "eth_getBlockTransactionCountByNumber", "params": [""], "order": [], "returns" : ""}, + { "name": "eth_getUncleCountByBlockHash", "params": [""], "order": [], "returns" : ""}, + { "name": "eth_getUncleCountByBlockNumber", "params": [""], "order": [], "returns" : ""}, + { "name": "eth_getCode", "params": ["", ""], "order": [], "returns": ""}, + { "name": "eth_sendTransaction", "params": [{}], "order": [], "returns": ""}, + { "name": "eth_call", "params": [{}, ""], "order": [], "returns": ""}, { "name": "eth_flush", "params": [], "order": [], "returns" : true}, - - { "name": "eth_blockByHash", "params": [""],"order": [], "returns": {}}, - { "name": "eth_blockByNumber", "params": [0],"order": [], "returns": {}}, - { "name": "eth_transactionByHash", "params": ["", 0], "order": [], "returns": {}}, - { "name": "eth_transactionByNumber", "params": [0, 0], "order": [], "returns": {}}, - { "name": "eth_uncleByHash", "params": ["", 0], "order": [], "returns": {}}, - { "name": "eth_uncleByNumber", "params": [0, 0], "order": [], "returns": {}}, - - { "name": "eth_compilers", "params": [], "order": [], "returns": []}, - { "name": "eth_lll", "params": [""], "order": [], "returns": ""}, - { "name": "eth_solidity", "params": [""], "order": [], "returns": ""}, - { "name": "eth_serpent", "params": [""], "order": [], "returns": ""}, - - { "name": "eth_newFilter", "params": [{}], "order": [], "returns": 0}, - { "name": "eth_newFilterString", "params": [""], "order": [], "returns": 0}, - { "name": "eth_uninstallFilter", "params": [0], "order": [], "returns": true}, - { "name": "eth_changed", "params": [0], "order": [], "returns": []}, - { "name": "eth_filterLogs", "params": [0], "order": [], "returns": []}, - { "name": "eth_logs", "params": [{}], "order": [], "returns": []}, - + { "name": "eth_getBlockByHash", "params": ["", false],"order": [], "returns": {}}, + { "name": "eth_getBlockByNumber", "params": ["", false],"order": [], "returns": {}}, + { "name": "eth_getTransactionByHash", "params": [""], "order": [], "returns": {}}, + { "name": "eth_getTransactionByBlockHashAndIndex", "params": ["", ""], "order": [], "returns": {}}, + { "name": "eth_getTransactionByBlockNumberAndIndex", "params": ["", ""], "order": [], "returns": {}}, + { "name": "eth_getUncleByBlockHashAndIndex", "params": ["", ""], "order": [], "returns": {}}, + { "name": "eth_getUncleByBlockNumberAndIndex", "params": ["", ""], "order": [], "returns": {}}, + { "name": "eth_getCompilers", "params": [], "order": [], "returns": []}, + { "name": "eth_compileLLL", "params": [""], "order": [], "returns": ""}, + { "name": "eth_compileSerpent", "params": [""], "order": [], "returns": ""}, + { "name": "eth_compileSolidity", "params": [""], "order": [], "returns": ""}, + { "name": "eth_newFilter", "params": [{}], "order": [], "returns": ""}, + { "name": "eth_newBlockFilter", "params": [""], "order": [], "returns": ""}, + { "name": "eth_uninstallFilter", "params": [""], "order": [], "returns": true}, + { "name": "eth_getFilterChanges", "params": [""], "order": [], "returns": []}, + { "name": "eth_getFilterLogs", "params": [""], "order": [], "returns": []}, + { "name": "eth_getLogs", "params": [{}], "order": [], "returns": []}, { "name": "eth_getWork", "params": [], "order": [], "returns": []}, { "name": "eth_submitWork", "params": ["", ""], "order": [], "returns": true}, - - { "name": "eth_register", "params": [""], "order": [], "returns": 0}, - { "name": "eth_unregister", "params": [0], "order": [], "returns": true}, - { "name": "eth_queuedTransactions", "params": [0], "order": [], "returns": []}, + { "name": "eth_register", "params": [""], "order": [], "returns": ""}, + { "name": "eth_unregister", "params": [""], "order": [], "returns": true}, + { "name": "eth_fetchQueuedTransactions", "params": [""], "order": [], "returns": []}, { "name": "db_put", "params": ["", "", ""], "order": [], "returns": true}, { "name": "db_get", "params": ["", ""], "order": [], "returns": ""}, - { "name": "db_putString", "params": ["", "", ""], "order": [], "returns": true}, - { "name": "db_getString", "params": ["", ""], "order": [], "returns": ""}, { "name": "shh_post", "params": [{}], "order": [], "returns": true}, { "name": "shh_newIdentity", "params": [], "order": [], "returns": ""}, - { "name": "shh_haveIdentity", "params": [""], "order": [], "returns": false}, + { "name": "shh_hasIdentity", "params": [""], "order": [], "returns": false}, { "name": "shh_newGroup", "params": ["", ""], "order": [], "returns": ""}, { "name": "shh_addToGroup", "params": ["", ""], "order": [], "returns": ""}, - - { "name": "shh_newFilter", "params": [{}], "order": [], "returns": 0}, - { "name": "shh_uninstallFilter", "params": [0], "order": [], "returns": true}, - { "name": "shh_changed", "params": [0], "order": [], "returns": []}, - { "name": "shh_getMessages", "params": [0], "order": [], "returns": []} + { "name": "shh_newFilter", "params": [{}], "order": [], "returns": ""}, + { "name": "shh_uninstallFilter", "params": [""], "order": [], "returns": true}, + { "name": "shh_getFilterChanges", "params": [""], "order": [], "returns": []}, + { "name": "shh_getMessages", "params": [""], "order": [], "returns": []} ] diff --git a/libwebthree/WebThree.cpp b/libwebthree/WebThree.cpp index 6c6414741..a74d7fa55 100644 --- a/libwebthree/WebThree.cpp +++ b/libwebthree/WebThree.cpp @@ -27,7 +27,6 @@ #include #include -#include #include #include #include @@ -40,7 +39,7 @@ using namespace dev::shh; WebThreeDirect::WebThreeDirect( std::string const& _clientVersion, std::string const& _dbPath, - bool _forceClean, + WithExisting _we, std::set const& _interfaces, NetworkPreferences const& _n, bytesConstRef _network, int _miners @@ -51,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); @@ -73,12 +72,12 @@ WebThreeDirect::~WebThreeDirect() m_ethereum.reset(); } -void WebThreeDirect::setNetworkPreferences(p2p::NetworkPreferences const& _n) +void WebThreeDirect::setNetworkPreferences(p2p::NetworkPreferences const& _n, bool _dropPeers) { auto had = haveNetwork(); if (had) stopNetwork(); - m_net.setNetworkPreferences(_n); + m_net.setNetworkPreferences(_n, _dropPeers); if (had) startNetwork(); } @@ -103,7 +102,14 @@ bytes WebThreeDirect::saveNetwork() return m_net.saveNetwork(); } -void WebThreeDirect::connect(std::string const& _seedHost, unsigned short _port) +void WebThreeDirect::addNode(NodeId const& _node, bi::tcp::endpoint const& _host) { - m_net.addNode(NodeId(), _seedHost, _port, _port); + m_net.addNode(_node, _host.address(), _host.port(), _host.port()); } + +void WebThreeDirect::requirePeer(NodeId const& _node, bi::tcp::endpoint const& _host) +{ + m_net.requirePeer(_node, _host.address(), _host.port()); +} + + diff --git a/libwebthree/WebThree.h b/libwebthree/WebThree.h index 242639af4..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 @@ -63,9 +59,12 @@ public: /// Same as peers().size(), but more efficient. virtual size_t peerCount() const = 0; - /// Connect to a particular peer. - virtual void connect(std::string const& _seedHost, unsigned short _port) = 0; - + /// Add node to connect to. + virtual void addNode(p2p::NodeId const& _node, bi::tcp::endpoint const& _hostEndpoint) = 0; + + /// Require connection to peer. + virtual void requirePeer(p2p::NodeId const& _node, bi::tcp::endpoint const& _endpoint) = 0; + /// Save peers virtual dev::bytes saveNetwork() = 0; @@ -74,7 +73,7 @@ public: virtual bool haveNetwork() const = 0; - virtual void setNetworkPreferences(p2p::NetworkPreferences const& _n) = 0; + virtual void setNetworkPreferences(p2p::NetworkPreferences const& _n, bool _dropPeers) = 0; virtual p2p::NodeId id() const = 0; @@ -110,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(), @@ -128,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: @@ -137,22 +138,34 @@ public: /// Same as peers().size(), but more efficient. size_t peerCount() const override; - - /// Connect to a particular peer. - void connect(std::string const& _seedHost, unsigned short _port = 30303) override; + + /// Add node to connect to. + virtual void addNode(p2p::NodeId const& _node, bi::tcp::endpoint const& _hostEndpoint) override; + + /// Add node to connect to. + void addNode(p2p::NodeId const& _node, std::string const& _hostString) { addNode(_node, p2p::Network::resolveHost(_hostString)); } + + /// Add node to connect to. + void addNode(bi::tcp::endpoint const& _endpoint) { addNode(p2p::NodeId(), _endpoint); } + + /// Add node to connect to. + void addNode(std::string const& _hostString) { addNode(p2p::NodeId(), _hostString); } + + /// Require connection to peer. + void requirePeer(p2p::NodeId const& _node, bi::tcp::endpoint const& _endpoint) override; + + /// Require connection to peer. + void requirePeer(p2p::NodeId const& _node, std::string const& _hostString) { requirePeer(_node, p2p::Network::resolveHost(_hostString)); } /// Save peers dev::bytes saveNetwork() override; -// /// Restore peers -// void restoreNetwork(bytesConstRef _saved) override; - /// Sets the ideal number of peers. void setIdealPeerCount(size_t _n) override; bool haveNetwork() const override { return m_net.isStarted(); } - void setNetworkPreferences(p2p::NetworkPreferences const& _n) override; + void setNetworkPreferences(p2p::NetworkPreferences const& _n, bool _dropPeers = false) override; p2p::NodeId id() const override { return m_net.id(); } diff --git a/libwhisper/Message.cpp b/libwhisper/Message.cpp index 07bcea0c1..4375e0727 100644 --- a/libwhisper/Message.cpp +++ b/libwhisper/Message.cpp @@ -58,10 +58,10 @@ Message::Message(Envelope const& _e, FullTopic const& _fk, Secret const& _s) // get key from decrypted topic key: just xor h256 tk = h256(bytesConstRef(&(_e.data())).cropped(32 * topicIndex, 32)); bytesConstRef cipherText = bytesConstRef(&(_e.data())).cropped(32 * _e.topic().size()); - cnote << "Decrypting(" << topicIndex << "): " << topicSecret << tk << (topicSecret ^ tk) << toHex(cipherText); +// cdebug << "Decrypting(" << topicIndex << "): " << topicSecret << tk << (topicSecret ^ tk) << toHex(cipherText); if (!decryptSym(topicSecret ^ tk, cipherText, b)) return; - cnote << "Got: " << toHex(b); +// cdebug << "Got: " << toHex(b); } if (populate(b)) 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/libwhisper/WhisperPeer.cpp b/libwhisper/WhisperPeer.cpp index 7480a104e..53ea91a9e 100644 --- a/libwhisper/WhisperPeer.cpp +++ b/libwhisper/WhisperPeer.cpp @@ -55,7 +55,7 @@ bool WhisperPeer::interpret(unsigned _id, RLP const& _r) { case StatusPacket: { - auto protocolVersion = _r[1].toInt(); + auto protocolVersion = _r[0].toInt(); clogS(NetMessageSummary) << "Status: " << protocolVersion; diff --git a/mix/AppContext.cpp b/mix/AppContext.cpp deleted file mode 100644 index 29124a39a..000000000 --- a/mix/AppContext.cpp +++ /dev/null @@ -1,123 +0,0 @@ -/* - This file is part of cpp-ethereum. - cpp-ethereum is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - cpp-ethereum is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with cpp-ethereum. If not, see . -*/ -/** @file AppContext.cpp - * @author Yann yann@ethdev.com - * @date 2014 - * Provides access to the current QQmlApplicationEngine which is used to add QML file on the fly. - * In the future this class can be extended to add more variable related to the context of the application. - * For now AppContext provides reference to: - * - QQmlApplicationEngine - * - dev::WebThreeDirect (and dev::eth::Client) - * - KeyEventManager - */ - -#include -#include -#include -#include -#include -#include -#include "CodeModel.h" -#include "FileIo.h" -#include "ClientModel.h" -#include "CodeEditorExtensionManager.h" -#include "Exceptions.h" -#include "QEther.h" -#include "QVariableDefinition.h" -#include "HttpServer.h" -#include "AppContext.h" - -using namespace dev; -using namespace dev::eth; -using namespace dev::mix; - -const QString c_projectFileName = "project.mix"; - -AppContext::AppContext(QQmlApplicationEngine* _engine) -{ - m_applicationEngine = _engine; - m_codeModel.reset(new CodeModel(this)); - m_clientModel.reset(new ClientModel(this)); - m_fileIo.reset(new FileIo()); - connect(QApplication::clipboard(), &QClipboard::dataChanged, [this] { emit clipboardChanged();}); -} - -AppContext::~AppContext() -{ -} - -void AppContext::load() -{ - m_applicationEngine->rootContext()->setContextProperty("appContext", this); - QFont f; - m_applicationEngine->rootContext()->setContextProperty("systemPointSize", f.pointSize()); - qmlRegisterType("org.ethereum.qml", 1, 0, "FileIo"); - m_applicationEngine->rootContext()->setContextProperty("codeModel", m_codeModel.get()); - m_applicationEngine->rootContext()->setContextProperty("fileIo", m_fileIo.get()); - qmlRegisterType("org.ethereum.qml.QEther", 1, 0, "QEther"); - qmlRegisterType("org.ethereum.qml.QBigInt", 1, 0, "QBigInt"); - qmlRegisterType("org.ethereum.qml.QIntType", 1, 0, "QIntType"); - qmlRegisterType("org.ethereum.qml.QRealType", 1, 0, "QRealType"); - qmlRegisterType("org.ethereum.qml.QStringType", 1, 0, "QStringType"); - qmlRegisterType("org.ethereum.qml.QHashType", 1, 0, "QHashType"); - qmlRegisterType("org.ethereum.qml.QBoolType", 1, 0, "QBoolType"); - qmlRegisterType("org.ethereum.qml.QVariableDeclaration", 1, 0, "QVariableDeclaration"); - qmlRegisterType("org.ethereum.qml.RecordLogEntry", 1, 0, "RecordLogEntry"); - QQmlComponent projectModelComponent(m_applicationEngine, QUrl("qrc:/qml/ProjectModel.qml")); - QObject* projectModel = projectModelComponent.create(); - if (projectModelComponent.isError()) - { - QmlLoadException exception; - for (auto const& e : projectModelComponent.errors()) - exception << QmlErrorInfo(e); - BOOST_THROW_EXCEPTION(exception); - } - m_applicationEngine->rootContext()->setContextProperty("projectModel", projectModel); - qmlRegisterType("CodeEditorExtensionManager", 1, 0, "CodeEditorExtensionManager"); - qmlRegisterType("HttpServer", 1, 0, "HttpServer"); - m_applicationEngine->load(QUrl("qrc:/qml/main.qml")); - QWindow *window = qobject_cast(m_applicationEngine->rootObjects().at(0)); - window->setIcon(QIcon(":/res/mix_256x256x32.png")); - appLoaded(); -} - -QQmlApplicationEngine* AppContext::appEngine() -{ - return m_applicationEngine; -} - -void AppContext::displayMessageDialog(QString _title, QString _message) -{ - // TODO : move to a UI dedicated layer. - QObject* dialogWin = m_applicationEngine->rootObjects().at(0)->findChild("alertMessageDialog", Qt::FindChildrenRecursively); - QObject* dialogWinComponent = m_applicationEngine->rootObjects().at(0)->findChild("alertMessageDialogContent", Qt::FindChildrenRecursively); - dialogWinComponent->setProperty("source", QString("qrc:/qml/BasicMessage.qml")); - dialogWin->setProperty("title", _title); - dialogWin->setProperty("width", "250"); - dialogWin->setProperty("height", "100"); - dialogWin->findChild("messageContent", Qt::FindChildrenRecursively)->setProperty("text", _message); - QMetaObject::invokeMethod(dialogWin, "open"); -} - -QString AppContext::clipboard() const -{ - QClipboard *clipboard = QApplication::clipboard(); - return clipboard->text(); -} - -void AppContext::toClipboard(QString _text) -{ - QClipboard *clipboard = QApplication::clipboard(); - clipboard->setText(_text); -} diff --git a/mix/AppContext.h b/mix/AppContext.h deleted file mode 100644 index 268771207..000000000 --- a/mix/AppContext.h +++ /dev/null @@ -1,88 +0,0 @@ -/* - This file is part of cpp-ethereum. - - cpp-ethereum is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - cpp-ethereum is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with cpp-ethereum. If not, see . -*/ -/** @file AppContext.h - * @author Yann yann@ethdev.com - * @date 2014 - * Provides access to the current QQmlApplicationEngine which is used to add QML file on the fly. - * In the future this class can be extended to add more variable related to the context of the application. - * For now AppContext provides reference to: - * - QQmlApplicationEngine - * - dev::WebThreeDirect (and dev::eth::Client) - * - KeyEventManager - */ - -#pragma once - -#include -#include -#include - -class QQmlApplicationEngine; - -namespace dev -{ -namespace mix -{ - -class CodeModel; -class ClientModel; -class FileIo; -/** - * @brief Provides access to application scope variable. - */ - -class AppContext: public QObject -{ - Q_OBJECT - Q_PROPERTY(QString clipboard READ clipboard WRITE toClipboard NOTIFY clipboardChanged) - -public: - AppContext(QQmlApplicationEngine* _engine); - virtual ~AppContext(); - /// Load the UI from qml files - void load(); - /// Get the current QQMLApplicationEngine instance. - QQmlApplicationEngine* appEngine(); - /// Get code model - CodeModel* codeModel() { return m_codeModel.get(); } - /// Get client model - ClientModel* clientModel() { return m_clientModel.get(); } - /// Display an alert message. - void displayMessageDialog(QString _title, QString _message); - /// Copy text to clipboard - Q_INVOKABLE void toClipboard(QString _text); - /// Get text from clipboard - QString clipboard() const; - -signals: - /// Triggered once components have been loaded - void appLoaded(); - void clipboardChanged(); - -private: - QQmlApplicationEngine* m_applicationEngine; //owned by app - std::unique_ptr m_codeModel; - std::unique_ptr m_clientModel; - std::unique_ptr m_fileIo; - -public slots: - /// Delete the current instance when application quit. - void quitApplication() {} -}; - -} -} diff --git a/mix/CMakeLists.txt b/mix/CMakeLists.txt index 0ef9c6274..6a434534f 100644 --- a/mix/CMakeLists.txt +++ b/mix/CMakeLists.txt @@ -16,7 +16,7 @@ include_directories(${Boost_INCLUDE_DIRS}) include_directories(BEFORE ..) find_package (Qt5WebEngine QUIET) -qt5_add_resources(UI_RESOURCES res.qrc) +qt5_add_resources(UI_RESOURCES res.qrc qml.qrc) file(GLOB HEADERS "*.h") @@ -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 809d2dfd6..731a5cb7e 100644 --- a/mix/ClientModel.cpp +++ b/mix/ClientModel.cpp @@ -21,6 +21,7 @@ // Make sure boost/asio.hpp is included before windows.h. #include +#include "ClientModel.h" #include #include #include @@ -29,7 +30,6 @@ #include #include #include -#include "AppContext.h" #include "DebuggingStateWrapper.h" #include "Exceptions.h" #include "QContractDefinition.h" @@ -37,14 +37,13 @@ #include "QVariableDefinition.h" #include "ContractCallDataEncoder.h" #include "CodeModel.h" -#include "ClientModel.h" #include "QEther.h" #include "Web3Server.h" -#include "ClientModel.h" #include "MixClient.h" using namespace dev; using namespace dev::eth; +using namespace std; namespace dev { @@ -56,7 +55,7 @@ class RpcConnector: public jsonrpc::AbstractServerConnector public: virtual bool StartListening() override { return true; } virtual bool StopListening() override { return true; } - virtual bool SendResponse(std::string const& _response, void*) override + virtual bool SendResponse(string const& _response, void*) override { m_response = QString::fromStdString(_response); return true; @@ -68,20 +67,15 @@ private: }; -ClientModel::ClientModel(AppContext* _context): - m_context(_context), m_running(false), m_rpcConnector(new RpcConnector()) +ClientModel::ClientModel(): + m_running(false), m_rpcConnector(new RpcConnector()) { qRegisterMetaType("QBigInt*"); - qRegisterMetaType("QIntType*"); - qRegisterMetaType("QStringType*"); - qRegisterMetaType("QRealType*"); - qRegisterMetaType("QHashType*"); - qRegisterMetaType("QEther*"); qRegisterMetaType("QVariableDefinition*"); - qRegisterMetaType("QVariableDefinitionList*"); qRegisterMetaType>("QList"); qRegisterMetaType>("QList"); qRegisterMetaType("QVariableDeclaration*"); + qRegisterMetaType("QSolidityType*"); qRegisterMetaType("QMachineState"); qRegisterMetaType("QInstruction"); qRegisterMetaType("QCode"); @@ -93,11 +87,11 @@ ClientModel::ClientModel(AppContext* _context): m_web3Server.reset(new Web3Server(*m_rpcConnector.get(), m_client->userAccounts(), m_client.get())); connect(m_web3Server.get(), &Web3Server::newTransaction, this, &ClientModel::onNewTransaction, Qt::DirectConnection); - _context->appEngine()->rootContext()->setContextProperty("clientModel", this); } ClientModel::~ClientModel() { + m_runFuture.waitForFinished(); } QString ClientModel::apiCall(QString const& _message) @@ -109,7 +103,7 @@ QString ClientModel::apiCall(QString const& _message) } catch (...) { - std::cerr << boost::current_exception_diagnostic_information(); + cerr << boost::current_exception_diagnostic_information(); return QString(); } } @@ -121,7 +115,7 @@ void ClientModel::mine() m_mining = true; emit miningStarted(); emit miningStateChanged(); - QtConcurrent::run([=]() + m_runFuture = QtConcurrent::run([=]() { try { @@ -133,7 +127,7 @@ void ClientModel::mine() catch (...) { m_mining = false; - std::cerr << boost::current_exception_diagnostic_information(); + cerr << boost::current_exception_diagnostic_information(); emit runFailed(QString::fromStdString(boost::current_exception_diagnostic_information())); } emit miningStateChanged(); @@ -146,11 +140,17 @@ 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; for (auto const& c: m_contractAddresses) - res.insert(c.first, QString::fromStdString(dev::toJS(c.second))); + res.insert(c.first, QString::fromStdString(toJS(c.second))); return res; } @@ -159,14 +159,14 @@ void ClientModel::setupState(QVariantMap _state) QVariantList balances = _state.value("accounts").toList(); QVariantList transactions = _state.value("transactions").toList(); - std::map accounts; + map accounts; for (auto const& b: balances) { QVariantMap address = b.toMap(); - accounts.insert(std::make_pair(Secret(address.value("secret").toString().toStdString()), (qvariant_cast(address.value("balance")))->toU256Wei())); + accounts.insert(make_pair(Secret(address.value("secret").toString().toStdString()), (qvariant_cast(address.value("balance")))->toU256Wei())); } - std::vector transactionSequence; + vector transactionSequence; for (auto const& t: transactions) { QVariantMap transaction = t.toMap(); @@ -190,16 +190,10 @@ void ClientModel::setupState(QVariantMap _state) } else { - if (contractId.isEmpty() && m_context->codeModel()->hasContract()) //TODO: This is to support old project files, remove later - contractId = m_context->codeModel()->contracts().keys()[0]; - QVariantList qParams = transaction.value("qType").toList(); + if (contractId.isEmpty() && m_codeModel->hasContract()) //TODO: This is to support old project files, remove later + contractId = m_codeModel->contracts().keys()[0]; TransactionSettings transactionSettings(contractId, functionId, value, gas, gasPrice, Secret(sender.toStdString())); - - for (QVariant const& variant: qParams) - { - QVariableDefinition* param = qvariant_cast(variant); - transactionSettings.parameterValues.push_back(param); - } + transactionSettings.parameterValues = transaction.value("parameters").toMap(); if (contractId == functionId || functionId == "Constructor") transactionSettings.functionId.clear(); @@ -210,7 +204,7 @@ void ClientModel::setupState(QVariantMap _state) executeSequence(transactionSequence, accounts); } -void ClientModel::executeSequence(std::vector const& _sequence, std::map const& _balances) +void ClientModel::executeSequence(vector const& _sequence, map const& _balances) { if (m_running) BOOST_THROW_EXCEPTION(ExecutionStateException()); @@ -220,7 +214,7 @@ void ClientModel::executeSequence(std::vector const& _seque emit runStateChanged(); //run sequence - QtConcurrent::run([=]() + m_runFuture = QtConcurrent::run([=]() { try { @@ -232,18 +226,20 @@ void ClientModel::executeSequence(std::vector const& _seque if (!transaction.stdContractUrl.isEmpty()) { //std contract - dev::bytes const& stdContractCode = m_context->codeModel()->getStdContractCode(transaction.contractId, transaction.stdContractUrl); - Address address = deployContract(stdContractCode, transaction); - m_stdContractAddresses[transaction.contractId] = address; - m_stdContractNames[address] = transaction.contractId; + bytes const& stdContractCode = m_codeModel->getStdContractCode(transaction.contractId, transaction.stdContractUrl); + TransactionSettings stdTransaction = transaction; + stdTransaction.gas = 500000;// TODO: get this from std contracts library + Address address = deployContract(stdContractCode, stdTransaction); + m_stdContractAddresses[stdTransaction.contractId] = address; + m_stdContractNames[address] = stdTransaction.contractId; } else { //encode data - CompiledContract const& compilerRes = m_context->codeModel()->contract(transaction.contractId); + CompiledContract const& compilerRes = m_codeModel->contract(transaction.contractId); QFunctionDefinition const* f = nullptr; bytes contractCode = compilerRes.bytes(); - std::shared_ptr contractDef = compilerRes.sharedContract(); + shared_ptr contractDef = compilerRes.sharedContract(); if (transaction.functionId.isEmpty()) f = contractDef->constructor(); else @@ -254,14 +250,19 @@ void ClientModel::executeSequence(std::vector const& _seque break; } if (!f) - BOOST_THROW_EXCEPTION(FunctionNotFoundException() << FunctionName(transaction.functionId.toStdString())); + { + emit runFailed("Function '" + transaction.functionId + tr("' not found. Please check transactions or the contract code.")); + m_running = false; + emit runStateChanged(); + return; + } if (!transaction.functionId.isEmpty()) encoder.encode(f); - for (int p = 0; p < transaction.parameterValues.size(); p++) + for (QVariableDeclaration const* p: f->parametersList()) { - if (f->parametersList().size() <= p || f->parametersList().at(p)->type() != transaction.parameterValues.at(p)->declaration()->type()) - BOOST_THROW_EXCEPTION(ParameterChangedException() << FunctionName(transaction.functionId.toStdString())); - encoder.push(transaction.parameterValues.at(p)->encodeValue()); + QSolidityType const* type = p->type(); + QVariant value = transaction.parameterValues.value(p->name()); + encoder.encode(value, type->type()); } if (transaction.functionId.isEmpty() || transaction.functionId == transaction.contractId) @@ -281,7 +282,12 @@ void ClientModel::executeSequence(std::vector const& _seque { auto contractAddressIter = m_contractAddresses.find(transaction.contractId); if (contractAddressIter == m_contractAddresses.end()) - BOOST_THROW_EXCEPTION(dev::Exception() << dev::errinfo_comment("Contract not deployed: " + transaction.contractId.toStdString())); + { + emit runFailed("Contract '" + transaction.contractId + tr(" not deployed.") + "' " + tr(" Cannot call ") + transaction.functionId); + m_running = false; + emit runStateChanged(); + return; + } callContract(contractAddressIter->second, encoder.encodedData(), transaction); } } @@ -292,13 +298,12 @@ void ClientModel::executeSequence(std::vector const& _seque } catch(boost::exception const&) { - std::cerr << boost::current_exception_diagnostic_information(); + cerr << boost::current_exception_diagnostic_information(); emit runFailed(QString::fromStdString(boost::current_exception_diagnostic_information())); } - - catch(std::exception const& e) + catch(exception const& e) { - std::cerr << boost::current_exception_diagnostic_information(); + cerr << boost::current_exception_diagnostic_information(); emit runFailed(e.what()); } m_running = false; @@ -308,7 +313,7 @@ void ClientModel::executeSequence(std::vector const& _seque void ClientModel::showDebugger() { - ExecutionResult const& last = m_client->lastExecution(); + ExecutionResult last = m_client->lastExecution(); showDebuggerForTransaction(last); } @@ -318,19 +323,28 @@ void ClientModel::showDebuggerForTransaction(ExecutionResult const& _t) QDebugData* debugData = new QDebugData(); QQmlEngine::setObjectOwnership(debugData, QQmlEngine::JavaScriptOwnership); QList codes; + QList> codeMaps; + QList codeItems; + QList contracts; for (MachineCode const& code: _t.executionCode) { - codes.push_back(QMachineState::getHumanReadableCode(debugData, code.address, code.code)); + QHash codeMap; + codes.push_back(QMachineState::getHumanReadableCode(debugData, code.address, code.code, codeMap)); + codeMaps.push_back(move(codeMap)); //try to resolve contract for source level debugging auto nameIter = m_contractNames.find(code.address); - if (nameIter != m_contractNames.end()) + CompiledContract const* compilerRes = nullptr; + if (nameIter != m_contractNames.end() && (compilerRes = m_codeModel->tryGetContract(nameIter->second))) { - CompiledContract const& compilerRes = m_context->codeModel()->contract(nameIter->second); - eth::AssemblyItems assemblyItems = !_t.isConstructor() ? compilerRes.assemblyItems() : compilerRes.constructorAssemblyItems(); - QVariantList locations; - for (eth::AssemblyItem const& item: assemblyItems) - locations.push_back(QVariant::fromValue(new QSourceLocation(debugData, item.getLocation().start, item.getLocation().end))); - codes.back()->setLocations(compilerRes.documentId(), std::move(locations)); + eth::AssemblyItems assemblyItems = !_t.isConstructor() ? compilerRes->assemblyItems() : compilerRes->constructorAssemblyItems(); + codes.back()->setDocument(compilerRes->documentId()); + codeItems.push_back(move(assemblyItems)); + contracts.push_back(compilerRes); + } + else + { + codeItems.push_back(AssemblyItems()); + contracts.push_back(nullptr); } } @@ -339,18 +353,147 @@ void ClientModel::showDebuggerForTransaction(ExecutionResult const& _t) data.push_back(QMachineState::getDebugCallData(debugData, d)); QVariantList states; + QVariantList solCallStack; + map solLocals; // + map storageDeclarations; // + + unsigned prevInstructionIndex = 0; for (MachineState const& s: _t.machineStates) - states.append(QVariant::fromValue(new QMachineState(debugData, s, codes[s.codeIndex], data[s.dataIndex]))); + { + int instructionIndex = codeMaps[s.codeIndex][static_cast(s.curPC)]; + QSolState* solState = nullptr; + if (!codeItems[s.codeIndex].empty() && contracts[s.codeIndex]) + { + CompiledContract const* contract = contracts[s.codeIndex]; + AssemblyItem const& instruction = codeItems[s.codeIndex][instructionIndex]; - debugData->setStates(std::move(states)); + if (instruction.type() == eth::Push && !instruction.data()) + { + //register new local variable initialization + auto localIter = contract->locals().find(LocationPair(instruction.getLocation().start, instruction.getLocation().end)); + if (localIter != contract->locals().end()) + solLocals[s.stack.size()] = new QVariableDeclaration(debugData, localIter.value().name.toStdString(), localIter.value().type); + } - //QList returnParameters; - //returnParameters = encoder.decode(f->returnParameters(), debuggingContent.returnValue); + if (instruction.type() == eth::Tag) + { + //track calls into functions + AssemblyItem const& prevInstruction = codeItems[s.codeIndex][prevInstructionIndex]; + auto functionIter = contract->functions().find(LocationPair(instruction.getLocation().start, instruction.getLocation().end)); + if (functionIter != contract->functions().end() && ((prevInstruction.getJumpType() == AssemblyItem::JumpType::IntoFunction) || solCallStack.empty())) + solCallStack.push_back(QVariant::fromValue(functionIter.value())); + else if (prevInstruction.getJumpType() == AssemblyItem::JumpType::OutOfFunction && !solCallStack.empty()) + solCallStack.pop_back(); + } - //collect states for last transaction + //format solidity context values + QVariantMap locals; + QVariantList localDeclarations; + QVariantMap localValues; + for (auto l: solLocals) + if (l.first < (int)s.stack.size()) + { + if (l.second->type()->name().startsWith("mapping")) + break; //mapping type not yet managed + localDeclarations.push_back(QVariant::fromValue(l.second)); + localValues[l.second->name()] = formatValue(l.second->type()->type(), s.stack[l.first]); + } + locals["variables"] = localDeclarations; + locals["values"] = localValues; + + QVariantMap storage; + QVariantList storageDeclarationList; + QVariantMap storageValues; + for (auto st: s.storage) + if (st.first < numeric_limits::max()) + { + auto storageIter = contract->storage().find(static_cast(st.first)); + if (storageIter != contract->storage().end()) + { + QVariableDeclaration* storageDec = nullptr; + for (SolidityDeclaration const& codeDec : storageIter.value()) + { + if (codeDec.type.name.startsWith("mapping")) + continue; //mapping type not yet managed + auto decIter = storageDeclarations.find(codeDec.name); + if (decIter != storageDeclarations.end()) + storageDec = decIter->second; + else + { + storageDec = new QVariableDeclaration(debugData, codeDec.name.toStdString(), codeDec.type); + storageDeclarations[storageDec->name()] = storageDec; + } + storageDeclarationList.push_back(QVariant::fromValue(storageDec)); + storageValues[storageDec->name()] = formatStorageValue(storageDec->type()->type(), s.storage, codeDec.offset, codeDec.slot); + } + } + } + storage["variables"] = storageDeclarationList; + storage["values"] = storageValues; + + prevInstructionIndex = instructionIndex; + + SourceLocation location = instruction.getLocation(); + if (contract->contract()->location() == location || contract->functions().contains(LocationPair(location.start, location.end))) + location = dev::SourceLocation(-1, -1, location.sourceName); + + solState = new QSolState(debugData, move(storage), move(solCallStack), move(locals), location.start, location.end, QString::fromUtf8(location.sourceName->c_str())); + } + + states.append(QVariant::fromValue(new QMachineState(debugData, instructionIndex, s, codes[s.codeIndex], data[s.dataIndex], solState))); + } + + debugData->setStates(move(states)); debugDataReady(debugData); } +QVariant ClientModel::formatValue(SolidityType const& _type, u256 const& _value) +{ + ContractCallDataEncoder decoder; + bytes val = toBigEndian(_value); + QVariant res = decoder.decode(_type, val); + return res; +} + +QVariant ClientModel::formatStorageValue(SolidityType const& _type, map const& _storage, unsigned _offset, u256 const& _slot) +{ + u256 slot = _slot; + QVariantList values; + ContractCallDataEncoder decoder; + u256 count = 1; + if (_type.dynamicSize) + { + count = _storage.at(slot); + slot = fromBigEndian(sha3(toBigEndian(slot)).asBytes()); + } + else if (_type.array) + count = _type.count; + + unsigned offset = _offset; + while (count--) + { + + auto slotIter = _storage.find(slot); + u256 slotValue = slotIter != _storage.end() ? slotIter->second : u256(); + bytes slotBytes = toBigEndian(slotValue); + auto start = slotBytes.end() - _type.size - offset; + bytes val(32 - _type.size); //prepend with zeroes + val.insert(val.end(), start, start + _type.size); + values.append(decoder.decode(_type, val)); + offset += _type.size; + if ((offset + _type.size) > 32) + { + slot++; + offset = 0; + } + } + + if (!_type.array) + return values[0]; + + return QVariant::fromValue(values); +} + void ClientModel::emptyRecord() { debugDataReady(new QDebugData()); @@ -358,35 +501,29 @@ void ClientModel::emptyRecord() void ClientModel::debugRecord(unsigned _index) { - ExecutionResult const& e = m_client->executions().at(_index); + ExecutionResult e = m_client->execution(_index); showDebuggerForTransaction(e); } -void ClientModel::showDebugError(QString const& _error) -{ - //TODO: change that to a signal - m_context->displayMessageDialog(tr("Debugger"), _error); -} - Address ClientModel::deployContract(bytes const& _code, TransactionSettings const& _ctrTransaction) { - Address newAddress = m_client->transact(_ctrTransaction.sender, _ctrTransaction.value, _code, _ctrTransaction.gas, _ctrTransaction.gasPrice); + Address newAddress = m_client->submitTransaction(_ctrTransaction.sender, _ctrTransaction.value, _code, _ctrTransaction.gas, _ctrTransaction.gasPrice); return newAddress; } void ClientModel::callContract(Address const& _contract, bytes const& _data, TransactionSettings const& _tr) { - m_client->transact(_tr.sender, _tr.value, _contract, _data, _tr.gas, _tr.gasPrice); + m_client->submitTransaction(_tr.sender, _tr.value, _contract, _data, _tr.gas, _tr.gasPrice); } RecordLogEntry* ClientModel::lastBlock() const { eth::BlockInfo blockInfo = m_client->blockInfo(); - std::stringstream strGas; + stringstream strGas; strGas << blockInfo.gasUsed; - std::stringstream strNumber; + 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; } @@ -404,10 +541,10 @@ void ClientModel::onNewTransaction() { ExecutionResult const& tr = m_client->lastExecution(); unsigned block = m_client->number() + 1; - unsigned recordIndex = m_client->executions().size() - 1; + unsigned recordIndex = tr.executonIndex; QString transactionIndex = tr.isCall() ? QObject::tr("Call") : QString("%1:%2").arg(block).arg(tr.transactionIndex); QString address = QString::fromStdString(toJS(tr.address)); - QString value = QString::fromStdString(dev::toString(tr.value)); + QString value = QString::fromStdString(toString(tr.value)); QString contract = address; QString function; QString returned; @@ -428,6 +565,7 @@ void ClientModel::onNewTransaction() } else function = QObject::tr("Constructor"); + address = QObject::tr("(Create contract)"); } else { @@ -449,7 +587,7 @@ void ClientModel::onNewTransaction() auto contractAddressIter = m_contractNames.find(contractAddress); if (contractAddressIter != m_contractNames.end()) { - CompiledContract const& compilerRes = m_context->codeModel()->contract(contractAddressIter->second); + CompiledContract const& compilerRes = m_codeModel->contract(contractAddressIter->second); const QContractDefinition* def = compilerRes.contract(); contract = def->name(); if (abi) @@ -459,9 +597,10 @@ void ClientModel::onNewTransaction() { function = funcDef->name(); ContractCallDataEncoder encoder; - QList returnValues = encoder.decode(funcDef->returnParameters(), tr.returnValue); - for (auto const& var: returnValues) - returned += var->value() + " | "; + QStringList returnValues = encoder.decode(funcDef->returnParameters(), tr.result.output); + returned += "("; + returned += returnValues.join(", "); + returned += ")"; } } } diff --git a/mix/ClientModel.h b/mix/ClientModel.h index 8810502bd..4b597a1ea 100644 --- a/mix/ClientModel.h +++ b/mix/ClientModel.h @@ -27,6 +27,7 @@ #include #include #include +#include #include "MachineStates.h" namespace dev @@ -34,13 +35,14 @@ namespace dev namespace mix { -class AppContext; class Web3Server; class RpcConnector; class QEther; class QDebugData; class MixClient; class QVariableDefinition; +class CodeModel; +struct SolidityType; /// Backend transaction config class struct TransactionSettings @@ -62,7 +64,7 @@ struct TransactionSettings /// Gas price u256 gasPrice; /// Mapping from contract function parameter name to value - QList parameterValues; + QVariantMap parameterValues; /// Standard contract url QString stdContractUrl; /// Sender @@ -126,7 +128,7 @@ class ClientModel: public QObject Q_OBJECT public: - ClientModel(AppContext* _context); + ClientModel(); ~ClientModel(); /// @returns true if currently executing contract code Q_PROPERTY(bool running MEMBER m_running NOTIFY runStateChanged) @@ -142,6 +144,8 @@ public: Q_INVOKABLE QString apiCall(QString const& _message); /// Simulate mining. Creates a new block Q_INVOKABLE void mine(); + /// Get/set code model. Should be set from qml + Q_PROPERTY(CodeModel* codeModel MEMBER m_codeModel) public slots: /// Setup state, run transaction sequence, show debugger for the last transaction @@ -151,13 +155,14 @@ 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. void showDebugger(); - /// Update UI with transaction run error. - void showDebugError(QString const& _error); signals: /// Transaction execution started @@ -198,10 +203,12 @@ private: void onNewTransaction(); void onStateReset(); void showDebuggerForTransaction(ExecutionResult const& _t); + QVariant formatValue(SolidityType const& _type, dev::u256 const& _value); + QVariant formatStorageValue(SolidityType const& _type, std::map const& _storage, unsigned _offset, dev::u256 const& _slot); - AppContext* m_context; 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; @@ -209,6 +216,7 @@ private: std::map m_contractNames; std::map m_stdContractAddresses; std::map m_stdContractNames; + CodeModel* m_codeModel = nullptr; }; } diff --git a/mix/Clipboard.cpp b/mix/Clipboard.cpp new file mode 100644 index 000000000..b364a6b31 --- /dev/null +++ b/mix/Clipboard.cpp @@ -0,0 +1,40 @@ +/* + 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 Clipboard.cpp + * @author Yann yann@ethdev.com + * @date 2015 + */ + +#include "Clipboard.h" +#include +#include + +using namespace dev::mix; + +Clipboard::Clipboard() +{ + connect(QApplication::clipboard(), &QClipboard::dataChanged, [this] { emit clipboardChanged();}); +} + +QString Clipboard::text() const +{ + QClipboard *clipboard = QApplication::clipboard(); + return clipboard->text(); +} + +void Clipboard::setText(QString _text) +{ + QClipboard *clipboard = QApplication::clipboard(); + clipboard->setText(_text); +} diff --git a/mix/StatusPane.h b/mix/Clipboard.h similarity index 67% rename from mix/StatusPane.h rename to mix/Clipboard.h index 28b5b449b..317698e41 100644 --- a/mix/StatusPane.h +++ b/mix/Clipboard.h @@ -1,25 +1,27 @@ /* 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 ConstantCompilationControl.h +/** @file Clipboard.h * @author Yann yann@ethdev.com - * @date 2014 - * Ethereum IDE client. + * @date 2015 */ #pragma once -#include "Extension.h" +#include namespace dev { @@ -27,20 +29,23 @@ namespace mix { /** - * @brief Extension which display assembly code of the contract being edited. + * @brief Provides access to system clipboard */ -class StatusPane: public Extension + +class Clipboard: public QObject { Q_OBJECT + Q_PROPERTY(QString text READ text WRITE setText NOTIFY clipboardChanged) public: - StatusPane(AppContext* _appContext); - ~StatusPane() {} - void start() const override; - QString title() const override; - QString contentUrl() const override; + Clipboard(); + /// Copy text to clipboard + void setText(QString _text); + /// Get text from clipboard + QString text() const; -public slots: +signals: + void clipboardChanged(); }; } diff --git a/mix/CodeEditorExtensionManager.cpp b/mix/CodeEditorExtensionManager.cpp deleted file mode 100644 index 97b808eb2..000000000 --- a/mix/CodeEditorExtensionManager.cpp +++ /dev/null @@ -1,97 +0,0 @@ -/* - This file is part of cpp-ethereum. - - cpp-ethereum is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - cpp-ethereum is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with cpp-ethereum. If not, see . -*/ -/** @file CodeEditorExtensionManager.cpp - * @author Yann yann@ethdev.com - * @date 2014 - * Ethereum IDE client. - */ - -#include -#include -#include -#include -#include -#include "StatusPane.h" -#include "AppContext.h" -#include "MixApplication.h" -#include "CodeModel.h" -#include "ClientModel.h" -#include "CodeHighlighter.h" -#include "CodeEditorExtensionManager.h" - -using namespace dev::mix; - -CodeEditorExtensionManager::CodeEditorExtensionManager(): - m_appContext(static_cast(QApplication::instance())->context()) -{ -} - -CodeEditorExtensionManager::~CodeEditorExtensionManager() -{ - m_features.clear(); -} - -void CodeEditorExtensionManager::loadEditor(QQuickItem* _editor) -{ - if (!_editor) - return; -} - -void CodeEditorExtensionManager::initExtensions() -{ - std::shared_ptr output = std::make_shared(m_appContext); - QObject::connect(m_appContext->codeModel(), &CodeModel::compilationComplete, this, &CodeEditorExtensionManager::applyCodeHighlight); - - initExtension(output); -} - -void CodeEditorExtensionManager::initExtension(std::shared_ptr _ext) -{ - if (!_ext->contentUrl().isEmpty()) - { - try - { - if (_ext->getDisplayBehavior() == ExtensionDisplayBehavior::RightView) - _ext->addTabOn(m_rightView); - if (_ext->getDisplayBehavior() == ExtensionDisplayBehavior::HeaderView) - _ext->addTabOn(m_headerView); - } - catch (...) - { - qDebug() << "Exception when adding tab into view."; - return; - } - } - _ext->start(); - m_features.append(_ext); -} - -void CodeEditorExtensionManager::applyCodeHighlight() -{ - //TODO: reimplement -} - -void CodeEditorExtensionManager::setRightView(QQuickItem* _rightView) -{ - m_rightView = _rightView; -} - -void CodeEditorExtensionManager::setHeaderView(QQuickItem* _headerView) -{ - m_headerView = _headerView; - initExtensions(); //TODO: move this to a proper place -} diff --git a/mix/CodeEditorExtensionManager.h b/mix/CodeEditorExtensionManager.h deleted file mode 100644 index fe6fbb33a..000000000 --- a/mix/CodeEditorExtensionManager.h +++ /dev/null @@ -1,73 +0,0 @@ -/* - This file is part of cpp-ethereum. - - cpp-ethereum is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - cpp-ethereum is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with cpp-ethereum. If not, see . -*/ -/** @file CodeEditorExtensionManager.h - * @author Yann yann@ethdev.com - * @date 2014 - * Ethereum IDE client. - */ - -#pragma once - -#include -#include -#include -#include -#include "StatusPane.h" - -namespace dev -{ -namespace mix -{ - - -class AppContext; - -/** - * @brief Init and provides connection between extensions. - */ -class CodeEditorExtensionManager: public QObject -{ - Q_OBJECT - - Q_PROPERTY(QQuickItem* headerView MEMBER m_headerView WRITE setHeaderView) - Q_PROPERTY(QQuickItem* rightView MEMBER m_rightView WRITE setRightView) - -public: - CodeEditorExtensionManager(); - ~CodeEditorExtensionManager(); - /// Initialize all extensions. - void initExtensions(); - /// Initialize extension. - void initExtension(std::shared_ptr); - /// Set current tab view - void setHeaderView(QQuickItem*); - /// Set current right tab view. - void setRightView(QQuickItem*); - -private slots: - void applyCodeHighlight(); - -private: - QVector> m_features; - QQuickItem* m_headerView; - QQuickItem* m_rightView; - AppContext* m_appContext; - void loadEditor(QQuickItem* _editor); -}; - -} -} diff --git a/mix/CodeModel.cpp b/mix/CodeModel.cpp index cc2a29b42..3c4972fcf 100644 --- a/mix/CodeModel.cpp +++ b/mix/CodeModel.cpp @@ -25,7 +25,11 @@ #include #include #include +#include #include +#include +#include +#include #include #include #include @@ -43,6 +47,68 @@ using namespace dev::mix; const std::set c_predefinedContracts = { "Config", "Coin", "CoinReg", "coin", "service", "owned", "mortal", "NameReg", "named", "std", "configUser" }; + +namespace +{ +using namespace dev::solidity; +class CollectDeclarationsVisitor: public ASTConstVisitor +{ +public: + CollectDeclarationsVisitor(QHash* _functions, QHash* _locals): + m_functions(_functions), m_locals(_locals), m_functionScope(false) {} +private: + LocationPair nodeLocation(ASTNode const& _node) + { + return LocationPair(_node.getLocation().start, _node.getLocation().end); + } + + virtual bool visit(FunctionDefinition const& _node) + { + m_functions->insert(nodeLocation(_node), QString::fromStdString(_node.getName())); + m_functionScope = true; + return true; + } + + virtual void endVisit(FunctionDefinition const&) + { + m_functionScope = false; + } + + virtual bool visit(VariableDeclaration const& _node) + { + SolidityDeclaration decl; + decl.type = CodeModel::nodeType(_node.getType().get()); + decl.name = QString::fromStdString(_node.getName()); + decl.slot = 0; + decl.offset = 0; + if (m_functionScope) + m_locals->insert(nodeLocation(_node), decl); + return true; + } + +private: + QHash* m_functions; + QHash* m_locals; + bool m_functionScope; +}; + +QHash collectStorage(dev::solidity::ContractDefinition const& _contract) +{ + QHash result; + dev::solidity::ContractType contractType(_contract); + + for (auto v : contractType.getStateVariables()) + { + dev::solidity::VariableDeclaration const* declaration = std::get<0>(v); + dev::u256 slot = std::get<1>(v); + unsigned offset = std::get<2>(v); + result[static_cast(slot)].push_back(SolidityDeclaration { QString::fromStdString(declaration->getName()), CodeModel::nodeType(declaration->getType().get()), slot, offset }); + } + return result; +} + +} //namespace + void BackgroundWorker::queueCodeChange(int _jobId) { m_model->runCompilationJob(_jobId); @@ -52,18 +118,25 @@ CompiledContract::CompiledContract(const dev::solidity::CompilerStack& _compiler QObject(nullptr), m_sourceHash(qHash(_source)) { - auto const& contractDefinition = _compiler.getContractDefinition(_contractName.toStdString()); - m_contract.reset(new QContractDefinition(&contractDefinition)); + std::string name = _contractName.toStdString(); + ContractDefinition const& contractDefinition = _compiler.getContractDefinition(name); + m_contract.reset(new QContractDefinition(nullptr, &contractDefinition)); QQmlEngine::setObjectOwnership(m_contract.get(), QQmlEngine::CppOwnership); + m_contract->moveToThread(QApplication::instance()->thread()); m_bytes = _compiler.getBytecode(_contractName.toStdString()); - m_assemblyItems = _compiler.getRuntimeAssemblyItems(_contractName.toStdString()); - m_constructorAssemblyItems = _compiler.getAssemblyItems(_contractName.toStdString()); + dev::solidity::InterfaceHandler interfaceHandler; m_contractInterface = QString::fromStdString(*interfaceHandler.getABIInterface(contractDefinition)); if (m_contractInterface.isEmpty()) m_contractInterface = "[]"; if (contractDefinition.getLocation().sourceName.get()) m_documentId = QString::fromStdString(*contractDefinition.getLocation().sourceName); + + CollectDeclarationsVisitor visitor(&m_functions, &m_locals); + m_storage = collectStorage(contractDefinition); + contractDefinition.accept(visitor); + m_assemblyItems = _compiler.getRuntimeAssemblyItems(name); + m_constructorAssemblyItems = _compiler.getAssemblyItems(name); } QString CompiledContract::codeHex() const @@ -71,8 +144,7 @@ QString CompiledContract::codeHex() const return QString::fromStdString(toJS(m_bytes)); } -CodeModel::CodeModel(QObject* _parent): - QObject(_parent), +CodeModel::CodeModel(): m_compiling(false), m_codeHighlighterSettings(new CodeHighlighterSettings()), m_backgroundWorker(this), @@ -121,12 +193,14 @@ void CodeModel::reset(QVariantMap const& _documents) void CodeModel::registerCodeChange(QString const& _documentId, QString const& _code) { + CompiledContract* contract = contractByDocumentId(_documentId); + if (contract != nullptr && contract->m_sourceHash == qHash(_code)) { - Guard l(x_contractMap); - CompiledContract* contract = m_contractMap.value(_documentId); - if (contract != nullptr && contract->m_sourceHash == qHash(_code)) - return; + emit compilationComplete(); + return; + } + { Guard pl(x_pendingContracts); m_pendingContracts[_documentId] = _code; } @@ -146,7 +220,7 @@ QVariantMap CodeModel::contracts() const return result; } -CompiledContract* CodeModel::contractByDocumentId(QString _documentId) const +CompiledContract* CodeModel::contractByDocumentId(QString const& _documentId) const { Guard l(x_contractMap); for (ContractMap::const_iterator c = m_contractMap.cbegin(); c != m_contractMap.cend(); ++c) @@ -155,7 +229,7 @@ CompiledContract* CodeModel::contractByDocumentId(QString _documentId) const return nullptr; } -CompiledContract const& CodeModel::contract(QString _name) const +CompiledContract const& CodeModel::contract(QString const& _name) const { Guard l(x_contractMap); CompiledContract* res = m_contractMap.value(_name); @@ -164,6 +238,13 @@ CompiledContract const& CodeModel::contract(QString _name) const return *res; } +CompiledContract const* CodeModel::tryGetContract(QString const& _name) const +{ + Guard l(x_contractMap); + CompiledContract* res = m_contractMap.value(_name); + return res; +} + void CodeModel::releaseContracts() { for (ContractMap::iterator c = m_contractMap.begin(); c != m_contractMap.end(); ++c) @@ -196,14 +277,22 @@ void CodeModel::runCompilationJob(int _jobId) if (c_predefinedContracts.count(n) != 0) continue; QString name = QString::fromStdString(n); - auto sourceIter = m_pendingContracts.find(name); + QString sourceName = QString::fromStdString(*cs.getContractDefinition(n).getLocation().sourceName); + auto sourceIter = m_pendingContracts.find(sourceName); QString source = sourceIter != m_pendingContracts.end() ? sourceIter->second : QString(); CompiledContract* contract = new CompiledContract(cs, name, source); QQmlEngine::setObjectOwnership(contract, QQmlEngine::CppOwnership); result[name] = contract; - CompiledContract* prevContract = m_contractMap.value(name); + CompiledContract* prevContract = nullptr; + for (ContractMap::const_iterator c = m_contractMap.cbegin(); c != m_contractMap.cend(); ++c) + if (c.value()->documentId() == contract->documentId()) + prevContract = c.value(); if (prevContract != nullptr && prevContract->contractInterface() != result[name]->contractInterface()) emit contractInterfaceChanged(name); + if (prevContract == nullptr) + emit newContractCompiled(name); + else if (prevContract->contract()->name() != name) + emit contractRenamed(contract->documentId(), prevContract->contract()->name(), name); } releaseContracts(); m_contractMap.swap(result); @@ -251,3 +340,79 @@ dev::bytes const& CodeModel::getStdContractCode(const QString& _contractName, co return m_compiledContracts.at(_contractName); } +SolidityType CodeModel::nodeType(dev::solidity::Type const* _type) +{ + SolidityType r { SolidityType::Type::UnsignedInteger, 32, 1, false, false, QString::fromStdString(_type->toString()), std::vector(), std::vector() }; + if (!_type) + return r; + switch (_type->getCategory()) + { + case Type::Category::Integer: + { + IntegerType const* it = dynamic_cast(_type); + r.size = it->getNumBits() / 8; + r.type = it->isAddress() ? SolidityType::Type::Address : it->isSigned() ? SolidityType::Type::SignedInteger : SolidityType::Type::UnsignedInteger; + } + break; + case Type::Category::Bool: + r.type = SolidityType::Type::Bool; + break; + case Type::Category::FixedBytes: + { + FixedBytesType const* b = dynamic_cast(_type); + r.type = SolidityType::Type::Bytes; + r.size = static_cast(b->getNumBytes()); + } + break; + case Type::Category::Contract: + r.type = SolidityType::Type::Address; + break; + case Type::Category::Array: + { + ArrayType const* array = dynamic_cast(_type); + if (array->isByteArray()) + r.type = SolidityType::Type::Bytes; + else + { + SolidityType elementType = nodeType(array->getBaseType().get()); + elementType.name = r.name; + r = elementType; + } + r.count = static_cast(array->getLength()); + r.dynamicSize = _type->isDynamicallySized(); + r.array = true; + } + break; + case Type::Category::Enum: + { + r.type = SolidityType::Type::Enum; + EnumType const* e = dynamic_cast(_type); + for(auto const& enumValue: e->getEnumDefinition().getMembers()) + r.enumNames.push_back(QString::fromStdString(enumValue->getName())); + } + break; + case Type::Category::Struct: + { + r.type = SolidityType::Type::Struct; + StructType const* s = dynamic_cast(_type); + for(auto const& structMember: s->getMembers()) + { + auto slotAndOffset = s->getStorageOffsetsOfMember(structMember.first); + r.members.push_back(SolidityDeclaration { QString::fromStdString(structMember.first), nodeType(structMember.second.get()), slotAndOffset.first, slotAndOffset.second }); + } + } + break; + case Type::Category::Function: + case Type::Category::IntegerConstant: + case Type::Category::Magic: + case Type::Category::Mapping: + case Type::Category::Modifier: + case Type::Category::Real: + case Type::Category::TypeType: + case Type::Category::Void: + default: + break; + } + return r; +} + diff --git a/mix/CodeModel.h b/mix/CodeModel.h index 511d2e01f..ea3694642 100644 --- a/mix/CodeModel.h +++ b/mix/CodeModel.h @@ -31,6 +31,7 @@ #include #include #include +#include "SolidityType.h" class QTextDocument; @@ -39,7 +40,8 @@ namespace dev namespace solidity { - class CompilerStack; +class CompilerStack; +class Type; } namespace mix @@ -64,6 +66,8 @@ private: CodeModel* m_model; }; +using LocationPair = QPair; + ///Compilation result model. Contains all the compiled contract data required by UI class CompiledContract: public QObject { @@ -93,6 +97,10 @@ public: /// @returns contract source Id QString documentId() const { return m_documentId; } + QHash const& functions() const { return m_functions; } + QHash const& locals() const { return m_locals; } + QHash const& storage() const { return m_storage; } + private: uint m_sourceHash; std::shared_ptr m_contract; @@ -102,11 +110,13 @@ private: QString m_documentId; eth::AssemblyItems m_assemblyItems; eth::AssemblyItems m_constructorAssemblyItems; + QHash m_functions; + QHash m_locals; + QHash m_storage; friend class CodeModel; }; - using ContractMap = QHash; /// Code compilation model. Compiles contracts in background an provides compiled contract data @@ -115,7 +125,7 @@ class CodeModel: public QObject Q_OBJECT public: - CodeModel(QObject* _parent); + CodeModel(); ~CodeModel(); Q_PROPERTY(QVariantMap contracts READ contracts NOTIFY codeChanged) @@ -131,10 +141,18 @@ public: /// Get contract code by url. Contract is compiled on first access and cached dev::bytes const& getStdContractCode(QString const& _contractName, QString const& _url); /// Get contract by name - CompiledContract const& contract(QString _name) const; + /// Throws if not found + CompiledContract const& contract(QString const& _name) const; + /// Get contract by name + /// @returns nullptr if not found + CompiledContract const* tryGetContract(QString const& _name) const; /// Find a contract by document id /// @returns CompiledContract object or null if not found - Q_INVOKABLE CompiledContract* contractByDocumentId(QString _documentId) const; + Q_INVOKABLE CompiledContract* contractByDocumentId(QString const& _documentId) const; + /// Reset code model + Q_INVOKABLE void reset() { reset(QVariantMap()); } + /// Convert solidity type info to mix type + static SolidityType nodeType(dev::solidity::Type const* _type); signals: /// Emited on compilation state change @@ -149,6 +167,10 @@ signals: void codeChanged(); /// Emitted if there are any changes in the contract interface void contractInterfaceChanged(QString _documentId); + /// Emitted if there is a new contract compiled for the first time + void newContractCompiled(QString _documentId); + /// Emitted if a contract name has been changed + void contractRenamed(QString _documentId, QString _oldName, QString _newName); public slots: /// Update code model on source code change diff --git a/mix/ContractCallDataEncoder.cpp b/mix/ContractCallDataEncoder.cpp index e31f79e9f..56aeb1d34 100644 --- a/mix/ContractCallDataEncoder.cpp +++ b/mix/ContractCallDataEncoder.cpp @@ -35,7 +35,9 @@ using namespace dev::mix; bytes ContractCallDataEncoder::encodedData() { - return m_encodedData; + bytes r(m_encodedData); + r.insert(r.end(), m_dynamicData.begin(), m_dynamicData.end()); + return r; } void ContractCallDataEncoder::encode(QFunctionDefinition const* _function) @@ -44,38 +46,194 @@ void ContractCallDataEncoder::encode(QFunctionDefinition const* _function) m_encodedData.insert(m_encodedData.end(), hash.begin(), hash.end()); } -void ContractCallDataEncoder::push(bytes const& _b) +void ContractCallDataEncoder::encode(QVariant const& _data, SolidityType const& _type) +{ + u256 count = 1; + QStringList strList; + if (_type.array) + { + if (_data.type() == QVariant::String) + strList = _data.toString().split(",", QString::SkipEmptyParts); //TODO: proper parsing + else + strList = _data.toStringList(); + count = strList.count(); + + } + else + strList.append(_data.toString()); + + if (_type.dynamicSize) + { + if (_type.type == SolidityType::Type::Bytes) + count = encodeSingleItem(_data.toString(), _type, m_dynamicData); + else + { + count = strList.count(); + for (auto const& item: strList) + encodeSingleItem(item, _type, m_dynamicData); + } + bytes sizeEnc(32); + toBigEndian(count, sizeEnc); + m_encodedData.insert(m_encodedData.end(), sizeEnc.begin(), sizeEnc.end()); + } + else + { + if (_type.array) + count = _type.count; + int c = static_cast(count); + if (strList.size() > c) + strList.erase(strList.begin() + c, strList.end()); + else + while (strList.size() < c) + strList.append(QString()); + + for (auto const& item: strList) + encodeSingleItem(item, _type, m_encodedData); + } +} + +unsigned ContractCallDataEncoder::encodeSingleItem(QString const& _data, SolidityType const& _type, bytes& _dest) +{ + if (_type.type == SolidityType::Type::Struct) + BOOST_THROW_EXCEPTION(dev::Exception() << dev::errinfo_comment("Struct parameters are not supported yet")); + + unsigned const alignSize = 32; + QString src = _data; + bytes result; + + if ((src.startsWith("\"") && src.endsWith("\"")) || (src.startsWith("\'") && src.endsWith("\'"))) + src = src.remove(src.length() - 1, 1).remove(0, 1); + + if (src.startsWith("0x")) + { + result = fromHex(src.toStdString().substr(2)); + if (_type.type != SolidityType::Type::Bytes) + result = padded(result, alignSize); + } + else + { + try + { + bigint i(src.toStdString()); + result = bytes(alignSize); + toBigEndian((u256)i, result); + } + catch (std::exception const& ex) + { + // manage input as a string. + QByteArray bytesAr = src.toLocal8Bit(); + result = bytes(bytesAr.begin(), bytesAr.end()); + result = paddedRight(result, alignSize); + } + } + + unsigned dataSize = _type.dynamicSize ? result.size() : alignSize; + if (result.size() % alignSize != 0) + result.resize((result.size() & ~(alignSize - 1)) + alignSize); + _dest.insert(_dest.end(), result.begin(), result.end()); + return dataSize; +} + +bigint ContractCallDataEncoder::decodeInt(dev::bytes const& _rawValue) +{ + dev::u256 un = dev::fromBigEndian(_rawValue); + if (un >> 255) + return (-s256(~un + 1)); + return un; +} + +QString ContractCallDataEncoder::toString(dev::bigint const& _int) +{ + std::stringstream str; + str << std::dec << _int; + return QString::fromStdString(str.str()); +} + +dev::bytes ContractCallDataEncoder::encodeBool(QString const& _str) { - m_encodedData.insert(m_encodedData.end(), _b.begin(), _b.end()); + bytes b(1); + b[0] = _str == "1" || _str.toLower() == "true " ? 1 : 0; + return padded(b, 32); } -QList ContractCallDataEncoder::decode(QList const& _returnParameters, bytes _value) +bool ContractCallDataEncoder::decodeBool(dev::bytes const& _rawValue) +{ + byte ret = _rawValue.at(_rawValue.size() - 1); + return (ret != 0); +} + +QString ContractCallDataEncoder::toString(bool _b) +{ + return _b ? "true" : "false"; +} + +dev::bytes ContractCallDataEncoder::encodeBytes(QString const& _str) +{ + QByteArray bytesAr = _str.toLocal8Bit(); + bytes r = bytes(bytesAr.begin(), bytesAr.end()); + return padded(r, 32); +} + +dev::bytes ContractCallDataEncoder::decodeBytes(dev::bytes const& _rawValue) +{ + return _rawValue; +} + +QString ContractCallDataEncoder::toString(dev::bytes const& _b) +{ + QString str; + if (asString(_b, str)) + return "\"" + str + "\" " + QString::fromStdString(dev::toJS(_b)); + else + return QString::fromStdString(dev::toJS(_b)); +} + + +QVariant ContractCallDataEncoder::decode(SolidityType const& _type, bytes const& _value) { bytesConstRef value(&_value); bytes rawParam(32); - QList r; + value.populate(&rawParam); + QSolidityType::Type type = _type.type; + if (type == QSolidityType::Type::SignedInteger || type == QSolidityType::Type::UnsignedInteger || type == QSolidityType::Type::Address) + return QVariant::fromValue(toString(decodeInt(rawParam))); + else if (type == QSolidityType::Type::Bool) + return QVariant::fromValue(toString(decodeBool(rawParam))); + else if (type == QSolidityType::Type::Bytes || type == QSolidityType::Type::Hash) + return QVariant::fromValue(toString(decodeBytes(rawParam))); + else if (type == QSolidityType::Type::Struct) + return QVariant::fromValue(QString("struct")); //TODO + else + BOOST_THROW_EXCEPTION(Exception() << errinfo_comment("Parameter declaration not found")); +} + +QStringList ContractCallDataEncoder::decode(QList const& _returnParameters, bytes _value) +{ + bytesConstRef value(&_value); + bytes rawParam(32); + QStringList r; + for (int k = 0; k <_returnParameters.length(); k++) { - QVariableDeclaration* dec = static_cast(_returnParameters.at(k)); - QVariableDefinition* def = nullptr; - if (dec->type().contains("int")) - def = new QIntType(dec, QString()); - else if (dec->type().contains("real")) - def = new QRealType(dec, QString()); - else if (dec->type().contains("bool")) - def = new QBoolType(dec, QString()); - else if (dec->type().contains("string") || dec->type().contains("text")) - def = new QStringType(dec, QString()); - else if (dec->type().contains("hash") || dec->type().contains("address")) - def = new QHashType(dec, QString()); - else - BOOST_THROW_EXCEPTION(Exception() << errinfo_comment("Parameter declaration not found")); - value.populate(&rawParam); - def->decodeValue(rawParam); - r.push_back(def); - value = value.cropped(32); - qDebug() << "decoded return value : " << dec->type() << " " << def->value(); + value = value.cropped(32); + QVariableDeclaration* dec = static_cast(_returnParameters.at(k)); + SolidityType const& type = dec->type()->type(); + r.append(decode(type, rawParam).toString()); } return r; } + + +bool ContractCallDataEncoder::asString(dev::bytes const& _b, QString& _str) +{ + dev::bytes bunPad = unpadded(_b); + for (unsigned i = 0; i < bunPad.size(); i++) + { + if (bunPad.at(i) < 9 || bunPad.at(i) > 127) + return false; + else + _str += QString::fromStdString(dev::toJS(bunPad.at(i))).replace("0x", ""); + } + return true; +} diff --git a/mix/ContractCallDataEncoder.h b/mix/ContractCallDataEncoder.h index 718beb8e0..805f26691 100644 --- a/mix/ContractCallDataEncoder.h +++ b/mix/ContractCallDataEncoder.h @@ -33,6 +33,7 @@ namespace mix class QFunctionDefinition; class QVariableDeclaration; class QVariableDefinition; +class QSolidityType; /** * @brief Encode/Decode data to be sent to a transaction or to be displayed in a view. @@ -43,15 +44,33 @@ public: ContractCallDataEncoder() {} /// Encode hash of the function to call. void encode(QFunctionDefinition const* _function); + /// Encode data for corresponding type + void encode(QVariant const& _data, SolidityType const& _type); /// Decode variable in order to be sent to QML view. - QList decode(QList const& _dec, bytes _value); + QStringList decode(QList const& _dec, bytes _value); + /// Decode single variable + 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); + bigint decodeInt(dev::bytes const& _rawValue); + dev::bytes encodeInt(QString const& _str); + QString toString(dev::bigint const& _int); + dev::bytes encodeBool(QString const& _str); + bool decodeBool(dev::bytes const& _rawValue); + QString toString(bool _b); + QString toString(dev::bytes const& _b); + bool asString(dev::bytes const& _b, QString& _str); private: bytes m_encodedData; + bytes m_dynamicData; }; } diff --git a/mix/DebuggingStateWrapper.cpp b/mix/DebuggingStateWrapper.cpp index 6cb29bbae..b8fbdef30 100644 --- a/mix/DebuggingStateWrapper.cpp +++ b/mix/DebuggingStateWrapper.cpp @@ -69,7 +69,7 @@ namespace } } -QCode* QMachineState::getHumanReadableCode(QObject* _owner, const Address& _address, const bytes& _code) +QCode* QMachineState::getHumanReadableCode(QObject* _owner, const Address& _address, const bytes& _code, QHash& o_codeMap) { QVariantList codeStr; for (unsigned i = 0; i <= _code.size(); ++i) @@ -80,14 +80,15 @@ QCode* QMachineState::getHumanReadableCode(QObject* _owner, const Address& _addr QString s = QString::fromStdString(instructionInfo((Instruction)b).name); std::ostringstream out; out << std::hex << std::setw(4) << std::setfill('0') << i; - int line = i; + int offset = i; if (b >= (byte)Instruction::PUSH1 && b <= (byte)Instruction::PUSH32) { unsigned bc = getPushNumber((Instruction)b); s = "PUSH 0x" + QString::fromStdString(toHex(bytesConstRef(&_code[i + 1], bc))); i += bc; } - codeStr.append(QVariant::fromValue(new QInstruction(_owner, QString::fromStdString(out.str()) + " " + s, line))); + o_codeMap[offset] = codeStr.size(); + codeStr.append(QVariant::fromValue(new QInstruction(_owner, QString::fromStdString(out.str()) + " " + s))); } catch (...) { diff --git a/mix/DebuggingStateWrapper.h b/mix/DebuggingStateWrapper.h index 7a34d6493..37bc194fb 100644 --- a/mix/DebuggingStateWrapper.h +++ b/mix/DebuggingStateWrapper.h @@ -26,12 +26,12 @@ #include #include -#include +#include #include #include #include +#include "MachineStates.h" #include "QVariableDefinition.h" -#include "MixClient.h" #include "QBigInt.h" namespace dev @@ -46,32 +46,41 @@ class QInstruction: public QObject { Q_OBJECT Q_PROPERTY(QString line MEMBER m_line CONSTANT) - Q_PROPERTY(int processIndex MEMBER m_processIndex CONSTANT) public: - QInstruction(QObject* _owner, QString _line, int _processIndex): QObject(_owner), m_line(_line), m_processIndex(_processIndex) {} + QInstruction(QObject* _owner, QString _line): QObject(_owner), m_line(_line) {} private: QString m_line; - int m_processIndex; }; - -class QSourceLocation: public QObject +/** + * @brief Solidity state + */ +class QSolState: public QObject { Q_OBJECT + Q_PROPERTY(QVariantMap storage MEMBER m_storage CONSTANT) + Q_PROPERTY(QVariantList callStack MEMBER m_callStack CONSTANT) + Q_PROPERTY(QVariantMap locals MEMBER m_locals CONSTANT) Q_PROPERTY(int start MEMBER m_start CONSTANT) Q_PROPERTY(int end MEMBER m_end CONSTANT) + Q_PROPERTY(QString sourceName MEMBER m_sourceName CONSTANT) public: - QSourceLocation(QObject* _owner, int _start, int _end): QObject(_owner), m_start(_start), m_end(_end) {} + QSolState(QObject* _parent, QVariantMap&& _storage, QVariantList&& _callStack, QVariantMap&& _locals, int _start, int _end, QString _sourceName): + QObject(_parent), m_storage(_storage), m_callStack(_callStack), m_locals(_locals), m_start(_start), m_end(_end), m_sourceName(_sourceName) + { } private: + QVariantMap m_storage; + QVariantList m_callStack; + QVariantMap m_locals; int m_start; int m_end; + QString m_sourceName; }; - /** * @brief Shared container for lines */ @@ -79,19 +88,17 @@ class QCode: public QObject { Q_OBJECT Q_PROPERTY(QVariantList instructions MEMBER m_instructions CONSTANT) - Q_PROPERTY(QVariantList locations MEMBER m_locations CONSTANT) Q_PROPERTY(QString address MEMBER m_address CONSTANT) Q_PROPERTY(QString documentId MEMBER m_document CONSTANT) public: QCode(QObject* _owner, QString const& _address, QVariantList&& _instrunctions): QObject(_owner), m_instructions(_instrunctions), m_address(_address) {} - void setLocations(QString const& _document, QVariantList&& _locations) { m_document = _document; m_locations = _locations; } + void setDocument(QString const& _documentId) { m_document = _documentId; } private: QVariantList m_instructions; QString m_address; QString m_document; - QVariantList m_locations; }; /** @@ -133,6 +140,7 @@ class QMachineState: public QObject Q_OBJECT Q_PROPERTY(int step READ step CONSTANT) Q_PROPERTY(int curPC READ curPC CONSTANT) + Q_PROPERTY(int instructionIndex MEMBER m_instructionIndex CONSTANT) Q_PROPERTY(QBigInt* gasCost READ gasCost CONSTANT) Q_PROPERTY(QBigInt* gas READ gas CONSTANT) Q_PROPERTY(QString instruction READ instruction CONSTANT) @@ -146,10 +154,11 @@ class QMachineState: public QObject Q_PROPERTY(QVariantList levels READ levels CONSTANT) Q_PROPERTY(unsigned codeIndex READ codeIndex CONSTANT) Q_PROPERTY(unsigned dataIndex READ dataIndex CONSTANT) + Q_PROPERTY(QObject* solidity MEMBER m_solState CONSTANT) public: - QMachineState(QObject* _owner, MachineState const& _state, QCode* _code, QCallData* _callData): - QObject(_owner), m_state(_state), m_code(_code), m_callData(_callData) {} + QMachineState(QObject* _owner, int _instructionIndex, MachineState const& _state, QCode* _code, QCallData* _callData, QSolState* _solState): + QObject(_owner), m_instructionIndex(_instructionIndex), m_state(_state), m_code(_code), m_callData(_callData), m_solState(_solState) { } /// Get the step of this machine states. int step() { return (int)m_state.steps; } /// Get the proccessed code index. @@ -168,7 +177,7 @@ public: QStringList debugStorage(); /// Get memory. QVariantList debugMemory(); - /// get end of debug information. + /// Get end of debug information. QString endOfDebug(); /// Get the new memory size. QBigInt* newMemSize(); @@ -177,18 +186,18 @@ public: /// Get all previous steps. QVariantList levels(); /// Get the current processed machine state. - MachineState state() { return m_state; } - /// Set the current processed machine state. - void setState(MachineState _state) { m_state = _state; } + MachineState const& state() const { return m_state; } /// Convert all machine states in human readable code. - static QCode* getHumanReadableCode(QObject* _owner, const Address& _address, const bytes& _code); + static QCode* getHumanReadableCode(QObject* _owner, const Address& _address, const bytes& _code, QHash& o_codeMap); /// Convert call data into human readable form static QCallData* getDebugCallData(QObject* _owner, bytes const& _data); private: + int m_instructionIndex; MachineState m_state; QCode* m_code; QCallData* m_callData; + QSolState* m_solState; }; } diff --git a/mix/Exceptions.h b/mix/Exceptions.h index 46178bf96..02e31760f 100644 --- a/mix/Exceptions.h +++ b/mix/Exceptions.h @@ -39,6 +39,7 @@ struct InvalidBlockException: virtual Exception {}; struct FunctionNotFoundException: virtual Exception {}; struct ExecutionStateException: virtual Exception {}; struct ParameterChangedException: virtual Exception {}; +struct OutOfGasException: virtual Exception {}; using QmlErrorInfo = boost::error_info; using FileError = boost::error_info; diff --git a/mix/Extension.cpp b/mix/Extension.cpp deleted file mode 100644 index eae842279..000000000 --- a/mix/Extension.cpp +++ /dev/null @@ -1,82 +0,0 @@ -/* - This file is part of cpp-ethereum. - cpp-ethereum is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - cpp-ethereum is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with cpp-ethereum. If not, see . -*/ -/** @file Extension.cpp - * @author Yann yann@ethdev.com - * @date 2014 - * Ethereum IDE client. - */ - - -#include -#include -#include - -#include -#include -#include "Extension.h" -#include "AppContext.h" - -using namespace dev; -using namespace dev::mix; - -Extension::Extension(AppContext* _context) -{ - init(_context); -} - -Extension::Extension(AppContext* _context, ExtensionDisplayBehavior _displayBehavior) -{ - init(_context); - m_displayBehavior = _displayBehavior; -} - -void Extension::init(AppContext* _context) -{ - m_ctx = _context; - m_appEngine = m_ctx->appEngine(); -} - -void Extension::addTabOn(QObject* _view) -{ - if (contentUrl() == "") - return; - - QVariant returnValue; - QQmlComponent* component = new QQmlComponent( - m_appEngine, - QUrl(contentUrl()), _view); - - QMetaObject::invokeMethod(_view, "addTab", - Q_RETURN_ARG(QVariant, returnValue), - Q_ARG(QVariant, this->title()), - Q_ARG(QVariant, QVariant::fromValue(component))); - - m_view = qvariant_cast(returnValue); -} - -void Extension::addContentOn(QObject* _view) -{ - Q_UNUSED(_view); - if (m_displayBehavior == ExtensionDisplayBehavior::ModalDialog) - { - QQmlComponent* component = new QQmlComponent(m_appEngine, QUrl(contentUrl()), _view); - QObject* dialogWin = m_appEngine->rootObjects().at(0)->findChild("dialog", Qt::FindChildrenRecursively); - QObject* dialogWinComponent = m_appEngine->rootObjects().at(0)->findChild("modalDialogContent", Qt::FindChildrenRecursively); - dialogWinComponent->setProperty("sourceComponent", QVariant::fromValue(component)); - dialogWin->setProperty("title", title()); - QMetaObject::invokeMethod(dialogWin, "open"); - } - //TODO add more view type. -} - diff --git a/mix/Extension.h b/mix/Extension.h deleted file mode 100644 index 98daf2918..000000000 --- a/mix/Extension.h +++ /dev/null @@ -1,75 +0,0 @@ -/* - This file is part of cpp-ethereum. - cpp-ethereum is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - cpp-ethereum is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - You should have received a copy of the GNU General Public License - along with cpp-ethereum. If not, see . -*/ -/** @file Extension.h - * @author Yann yann@ethdev.com - * @date 2014 - * Ethereum IDE client. - */ - -#pragma once - -#include -#include - -class QQmlApplicationEngine; - -namespace dev -{ -namespace mix -{ - -class AppContext; - -enum ExtensionDisplayBehavior -{ - HeaderView, - RightView, - ModalDialog -}; - - -class Extension: public QObject -{ - Q_OBJECT - -public: - Extension(AppContext* _context); - Extension(AppContext* _context, ExtensionDisplayBehavior _displayBehavior); - /// Return the QML url of the view to display. - virtual QString contentUrl() const { return ""; } - /// Return the title of this extension. - virtual QString title() const { return ""; } - /// Initialize extension. - virtual void start() const {} - /// Add the view define in contentUrl() in the _view QObject. - void addContentOn(QObject* _view); - /// Add the view define in contentUrl() in the _view QObject (_view has to be a tab). - void addTabOn(QObject* _view); - /// Modify the display behavior of this extension. - void setDisplayBehavior(ExtensionDisplayBehavior _displayBehavior) { m_displayBehavior = _displayBehavior; } - /// Get the display behavior of thi extension. - ExtensionDisplayBehavior getDisplayBehavior() { return m_displayBehavior; } - -protected: - QObject* m_view; - ExtensionDisplayBehavior m_displayBehavior; - AppContext* m_ctx; - QQmlApplicationEngine* m_appEngine; - -private: - void init(AppContext* _context); -}; - -} -} diff --git a/mix/FileIo.cpp b/mix/FileIo.cpp index 449512975..6d3d9c1c3 100644 --- a/mix/FileIo.cpp +++ b/mix/FileIo.cpp @@ -20,6 +20,7 @@ * Ethereum IDE client. */ +#include #include #include #include @@ -40,6 +41,10 @@ using namespace dev; using namespace dev::crypto; using namespace dev::mix; +FileIo::FileIo(): m_watcher(new QFileSystemWatcher(this)) +{ + connect(m_watcher, &QFileSystemWatcher::fileChanged, this, &FileIo::fileChanged); +} void FileIo::openFileBrowser(QString const& _dir) { @@ -87,7 +92,9 @@ QString FileIo::readFile(QString const& _url) void FileIo::writeFile(QString const& _url, QString const& _data) { - QFile file(pathFromUrl(_url)); + QString path = pathFromUrl(_url); + m_watcher->removePath(path); + QFile file(path); if (file.open(QIODevice::WriteOnly | QIODevice::Text)) { QTextStream stream(&file); @@ -95,6 +102,7 @@ void FileIo::writeFile(QString const& _url, QString const& _data) } else error(tr("Error writing file %1").arg(_url)); + m_watcher->addPath(path); } void FileIo::copyFile(QString const& _sourceUrl, QString const& _destUrl) @@ -158,6 +166,7 @@ QStringList FileIo::makePackage(QString const& _deploymentFolder) } rlpStr.appendList(k); + manifest["entries"] = entries; std::stringstream jsonStr; jsonStr << manifest; QByteArray b = QString::fromStdString(jsonStr.str()).toUtf8(); @@ -166,7 +175,6 @@ QStringList FileIo::makePackage(QString const& _deploymentFolder) for (unsigned int k = 0; k < files.size(); k++) rlpStr.append(files.at(k)); - manifest["entries"] = entries; bytes dapp = rlpStr.out(); dev::h256 dappHash = dev::sha3(dapp); //encrypt @@ -188,6 +196,16 @@ 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; } +void FileIo::watchFileChanged(QString const& _path) +{ + m_watcher->addPath(pathFromUrl(_path)); +} + +void FileIo::stopWatching(QString const& _path) +{ + m_watcher->removePath(pathFromUrl(_path)); +} diff --git a/mix/FileIo.h b/mix/FileIo.h index 400995435..33c2bd5fd 100644 --- a/mix/FileIo.h +++ b/mix/FileIo.h @@ -25,6 +25,8 @@ #include #include +class QFileSystemWatcher; + namespace dev { namespace mix @@ -39,8 +41,11 @@ class FileIo: public QObject signals: /// Signalled in case of IO error void error(QString const& _errorText); + /// Signnalled when a file is changed. + void fileChanged(QString const& _filePath); public: + FileIo(); /// Create a directory if it does not exist. Signals on failure. Q_INVOKABLE void makeDir(QString const& _url); /// Read file contents to a string. Signals on failure. @@ -55,12 +60,17 @@ public: Q_INVOKABLE bool fileExists(QString const& _url); /// Compress a folder, @returns sha3 of the compressed file. Q_INVOKABLE QStringList makePackage(QString const& _deploymentFolder); - /// Open a file browser + /// Open a file browser. Q_INVOKABLE void openFileBrowser(QString const& _dir); + /// Listen for files change in @arg _path. + Q_INVOKABLE void watchFileChanged(QString const& _path); + /// Stop Listenning for files change in @arg _path. + Q_INVOKABLE void stopWatching(QString const& _path); private: QString getHomePath() const; QString pathFromUrl(QString const& _url); + QFileSystemWatcher* m_watcher; }; } diff --git a/mix/MachineStates.h b/mix/MachineStates.h index 310d5cacd..f0346ba1e 100644 --- a/mix/MachineStates.h +++ b/mix/MachineStates.h @@ -29,6 +29,7 @@ along with cpp-ethereum. If not, see . #include #include #include +#include #include namespace dev @@ -74,12 +75,13 @@ namespace mix std::vector machineStates; std::vector transactionData; std::vector executionCode; - bytes returnValue; + dev::eth::ExecutionResult result; dev::Address address; dev::Address sender; dev::Address contractAddress; dev::u256 value; unsigned transactionIndex; + unsigned executonIndex = 0; bool isCall() const { return transactionIndex == std::numeric_limits::max(); } bool isConstructor() const { return !isCall() && !address; } diff --git a/mix/MixApplication.cpp b/mix/MixApplication.cpp index 821167a3f..54c860a8d 100644 --- a/mix/MixApplication.cpp +++ b/mix/MixApplication.cpp @@ -19,34 +19,99 @@ * @date 2014 */ -#include +#include "MixApplication.h" +#include #include - +#include +#include +#include #ifdef ETH_HAVE_WEBENGINE #include #endif +#include "CodeModel.h" +#include "ClientModel.h" +#include "FileIo.h" +#include "QEther.h" +#include "QVariableDeclaration.h" +#include "SortFilterProxyModel.h" +#include "Clipboard.h" +#include "HttpServer.h" -#include "MixApplication.h" -#include "AppContext.h" - -#include - +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_appContext(new AppContext(m_engine.get())) + 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 - QObject::connect(this, SIGNAL(lastWindowClosed()), context(), SLOT(quitApplication())); //use to kill ApplicationContext and other stuff - m_appContext->load(); + + 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"); + qmlRegisterType("org.ethereum.qml.QVariableDeclaration", 1, 0, "QVariableDeclaration"); + qmlRegisterType("org.ethereum.qml.RecordLogEntry", 1, 0, "RecordLogEntry"); + qmlRegisterType("org.ethereum.qml.SortFilterProxyModel", 1, 0, "SortFilterProxyModel"); + qmlRegisterType("org.ethereum.qml.QSolidityType", 1, 0, "QSolidityType"); + qmlRegisterType("org.ethereum.qml.Clipboard", 1, 0, "Clipboard"); + qmlRegisterType("HttpServer", 1, 0, "HttpServer"); + qRegisterMetaType("CodeModel*"); + qRegisterMetaType("ClientModel*"); + qRegisterMetaType("ClientModel*"); } MixApplication::~MixApplication() { } + +bool MixApplication::notify(QObject * receiver, QEvent * event) +{ + try + { + return QApplication::notify(receiver, event); + } + catch (...) + { + std::cerr << boost::current_exception_diagnostic_information(); + } + return false; +} diff --git a/mix/MixApplication.h b/mix/MixApplication.h index acfe4e547..c1fd73d66 100644 --- a/mix/MixApplication.h +++ b/mix/MixApplication.h @@ -33,7 +33,25 @@ namespace dev namespace mix { -class AppContext; +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 { @@ -41,13 +59,13 @@ class MixApplication: public QApplication public: MixApplication(int& _argc, char* _argv[]); + static void initialize(); virtual ~MixApplication(); - AppContext* context() { return m_appContext.get(); } QQmlApplicationEngine* engine() { return m_engine.get(); } + bool notify(QObject* _receiver, QEvent* _event) override; private: std::unique_ptr m_engine; - std::unique_ptr m_appContext; }; } diff --git a/mix/MixClient.cpp b/mix/MixClient.cpp index 781924db7..58cab151d 100644 --- a/mix/MixClient.cpp +++ b/mix/MixClient.cpp @@ -40,32 +40,25 @@ namespace dev namespace mix { +// TODO: merge as much as possible with the Client.cpp into a mutually inherited base class. + const Secret c_defaultUserAccountSecret = Secret("cb73d9408c4720e230387d956eb0f829d8a4dd2c1055f96257167e14e7169074"); -const u256 c_mixGenesisDifficulty = (u256) 1 << 4; +const u256 c_mixGenesisDifficulty = c_minimumDifficulty; //TODO: make it lower for Mix somehow -class MixBlockChain: public dev::eth::BlockChain +bytes MixBlockChain::createGenesisBlock(h256 _stateRoot) { -public: - MixBlockChain(std::string const& _path, h256 _stateRoot): - BlockChain(createGenesisBlock(_stateRoot), _path, true) - { - } - - static bytes createGenesisBlock(h256 _stateRoot) - { - RLPStream block(3); - block.appendList(16) - << h256() << EmptyListSHA3 << h160() << _stateRoot << EmptyTrie << EmptyTrie - << LogBloom() << c_mixGenesisDifficulty << 0 << 1000000 << 0 << (unsigned)0 - << std::string() << h256() << h256() << h64(u64(42)); - block.appendRaw(RLPEmptyList); - block.appendRaw(RLPEmptyList); - return block.out(); - } -}; + RLPStream block(3); + block.appendList(15) + << h256() << EmptyListSHA3 << h160() << _stateRoot << EmptyTrie << EmptyTrie + << LogBloom() << c_mixGenesisDifficulty << 0 << c_genesisGasLimit << 0 << (unsigned)0 + << std::string() << h256() << h64(u64(42)); + block.appendRaw(RLPEmptyList); + block.appendRaw(RLPEmptyList); + return block.out(); +} MixClient::MixClient(std::string const& _dbPath): - m_dbPath(_dbPath), m_minigThreads(0) + m_dbPath(_dbPath), m_miningThreads(0) { std::map account; account.insert(std::make_pair(c_defaultUserAccountSecret, 1000000 * ether)); @@ -79,7 +72,7 @@ MixClient::~MixClient() void MixClient::resetState(std::map _accounts) { WriteGuard l(x_state); - Guard fl(m_filterLock); + Guard fl(x_filtersWatches); m_filters.clear(); m_watches.clear(); @@ -87,6 +80,7 @@ void MixClient::resetState(std::map _accounts) SecureTrieDB accountState(&m_stateDB); accountState.init(); + m_userAccounts.clear(); std::map genesisState; for (auto account: _accounts) { @@ -99,9 +93,10 @@ 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); m_executions.clear(); } @@ -117,7 +112,8 @@ void MixClient::executeTransaction(Transaction const& _t, State& _state, bool _c State execState = _state; Executive execution(execState, lastHashes, 0); - execution.setup(&rlp); + execution.initialize(&rlp); + execution.execute(); std::vector machineStates; std::vector levels; std::vector codes; @@ -173,7 +169,7 @@ void MixClient::executeTransaction(Transaction const& _t, State& _state, bool _c execution.finalize(); ExecutionResult d; - d.returnValue = execution.out().toVector(); + d.result = execution.executionResult(); d.machineStates = machineStates; d.executionCode = std::move(codes); d.transactionData = std::move(data); @@ -184,15 +180,17 @@ void MixClient::executeTransaction(Transaction const& _t, State& _state, bool _c d.contractAddress = right160(sha3(rlpList(_t.sender(), _t.nonce()))); if (!_call) d.transactionIndex = m_state.pending().size(); - m_executions.emplace_back(std::move(d)); + d.executonIndex = m_executions.size(); // execute on a state if (!_call) { - _state.execute(lastHashes, rlp, nullptr, true); + dev::eth::ExecutionResult er =_state.execute(lastHashes, _t); + if (_t.isCreation() && _state.code(d.contractAddress).empty()) + BOOST_THROW_EXCEPTION(OutOfGas() << errinfo_comment("Not enough gas for contract deployment")); // collect watches h256Set changed; - Guard l(m_filterLock); + Guard l(x_filtersWatches); for (std::pair& i: m_filters) if ((unsigned)i.second.filter.latest() > bc().number()) { @@ -209,6 +207,8 @@ void MixClient::executeTransaction(Transaction const& _t, State& _state, bool _c changed.insert(dev::eth::PendingChangedFilter); noteChanged(changed); } + WriteGuard l(x_executions); + m_executions.emplace_back(std::move(d)); } void MixClient::mine() @@ -224,28 +224,25 @@ void MixClient::mine() noteChanged(changed); } -ExecutionResult const& MixClient::lastExecution() const +ExecutionResult MixClient::lastExecution() const { - return m_executions.back(); + ReadGuard l(x_executions); + return m_executions.empty() ? ExecutionResult() : m_executions.back(); } -ExecutionResults const& MixClient::executions() const +ExecutionResult MixClient::execution(unsigned _index) const { - return m_executions; + ReadGuard l(x_executions); + return m_executions.at(_index); } -State MixClient::asOf(int _block) const +State MixClient::asOf(h256 const& _block) const { ReadGuard l(x_state); - if (_block == 0) - return m_state; - else if (_block == -1) - return m_startState; - else - return State(m_stateDB, bc(), bc().numberHash(_block)); + return State(m_stateDB, bc(), _block); } -void MixClient::transact(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice) +void MixClient::submitTransaction(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice) { WriteGuard l(x_state); u256 n = m_state.transactionsFrom(toAddress(_secret)); @@ -253,7 +250,7 @@ void MixClient::transact(Secret _secret, u256 _value, Address _dest, bytes const executeTransaction(t, m_state, false); } -Address MixClient::transact(Secret _secret, u256 _endowment, bytes const& _init, u256 _gas, u256 _gasPrice) +Address MixClient::submitTransaction(Secret _secret, u256 _endowment, bytes const& _init, u256 _gas, u256 _gasPrice) { WriteGuard l(x_state); u256 n = m_state.transactionsFrom(toAddress(_secret)); @@ -263,151 +260,33 @@ Address MixClient::transact(Secret _secret, u256 _endowment, bytes const& _init, return address; } -void MixClient::inject(bytesConstRef _rlp) -{ - WriteGuard l(x_state); - eth::Transaction t(_rlp, CheckSignature::None); - executeTransaction(t, m_state, false); -} - -void MixClient::flushTransactions() +dev::eth::ExecutionResult MixClient::call(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice, BlockNumber _blockNumber) { + (void)_blockNumber; + State temp = asOf(eth::PendingBlock); + u256 n = temp.transactionsFrom(toAddress(_secret)); + Transaction t(_value, _gasPrice, _gas, _dest, _data, n, _secret); + bytes rlp = t.rlp(); + WriteGuard lw(x_state); //TODO: lock is required only for last execution state + executeTransaction(t, temp, true); + return lastExecution().result; } -bytes MixClient::call(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice) +dev::eth::ExecutionResult MixClient::create(Secret _secret, u256 _value, bytes const& _data, u256 _gas, u256 _gasPrice, BlockNumber _blockNumber) { + (void)_blockNumber; u256 n; State temp; { ReadGuard lr(x_state); - temp = m_state; + temp = asOf(eth::PendingBlock); n = temp.transactionsFrom(toAddress(_secret)); } - Transaction t(_value, _gasPrice, _gas, _dest, _data, n, _secret); + Transaction t(_value, _gasPrice, _gas, _data, n, _secret); bytes rlp = t.rlp(); WriteGuard lw(x_state); //TODO: lock is required only for last execution state executeTransaction(t, temp, true); - return lastExecution().returnValue; -} - -u256 MixClient::balanceAt(Address _a, int _block) const -{ - return asOf(_block).balance(_a); -} - -u256 MixClient::countAt(Address _a, int _block) const -{ - return asOf(_block).transactionsFrom(_a); -} - -u256 MixClient::stateAt(Address _a, u256 _l, int _block) const -{ - return asOf(_block).storage(_a, _l); -} - -bytes MixClient::codeAt(Address _a, int _block) const -{ - return asOf(_block).code(_a); -} - -std::map MixClient::storageAt(Address _a, int _block) const -{ - return asOf(_block).storage(_a); -} - -eth::LocalisedLogEntries MixClient::logs(unsigned _watchId) const -{ - Guard l(m_filterLock); - h256 h = m_watches.at(_watchId).id; - auto filterIter = m_filters.find(h); - if (filterIter != m_filters.end()) - return logs(filterIter->second.filter); - return eth::LocalisedLogEntries(); -} - -eth::LocalisedLogEntries MixClient::logs(eth::LogFilter const& _f) const -{ - LocalisedLogEntries ret; - unsigned lastBlock = bc().number(); - unsigned block = std::min(lastBlock, (unsigned)_f.latest()); - unsigned end = std::min(lastBlock, std::min(block, (unsigned)_f.earliest())); - unsigned skip = _f.skip(); - // Pending transactions - if (block > bc().number()) - { - ReadGuard l(x_state); - for (unsigned i = 0; i < m_state.pending().size(); ++i) - { - // Might have a transaction that contains a matching log. - TransactionReceipt const& tr = m_state.receipt(i); - LogEntries logEntries = _f.matches(tr); - for (unsigned entry = 0; entry < logEntries.size() && ret.size() != _f.max(); ++entry) - ret.insert(ret.begin(), LocalisedLogEntry(logEntries[entry], block)); - skip -= std::min(skip, static_cast(logEntries.size())); - } - block = bc().number(); - } - - // The rest - auto h = bc().numberHash(block); - for (; ret.size() != block && block != end; block--) - { - if (_f.matches(bc().info(h).logBloom)) - for (TransactionReceipt receipt: bc().receipts(h).receipts) - if (_f.matches(receipt.bloom())) - { - LogEntries logEntries = _f.matches(receipt); - for (unsigned entry = skip; entry < logEntries.size() && ret.size() != _f.max(); ++entry) - ret.insert(ret.begin(), LocalisedLogEntry(logEntries[entry], block)); - skip -= std::min(skip, static_cast(logEntries.size())); - } - h = bc().details(h).parent; - } - return ret; -} - -unsigned MixClient::installWatch(h256 _h) -{ - unsigned ret; - { - Guard l(m_filterLock); - ret = m_watches.size() ? m_watches.rbegin()->first + 1 : 0; - m_watches[ret] = ClientWatch(_h); - } - auto ch = logs(ret); - if (ch.empty()) - ch.push_back(eth::InitialChange); - { - Guard l(m_filterLock); - swap(m_watches[ret].changes, ch); - } - return ret; -} - -unsigned MixClient::installWatch(eth::LogFilter const& _f) -{ - h256 h = _f.sha3(); - { - Guard l(m_filterLock); - m_filters.insert(std::make_pair(h, _f)); - } - return installWatch(h); -} - -void MixClient::uninstallWatch(unsigned _i) -{ - Guard l(m_filterLock); - - auto it = m_watches.find(_i); - if (it == m_watches.end()) - return; - auto id = it->second.id; - m_watches.erase(it); - - auto fit = m_filters.find(id); - if (fit != m_filters.end()) - if (!--fit->second.refCount) - m_filters.erase(fit); + return lastExecution().result; } void MixClient::noteChanged(h256Set const& _filters) @@ -424,112 +303,10 @@ void MixClient::noteChanged(h256Set const& _filters) i.second.changes.clear(); } -LocalisedLogEntries MixClient::peekWatch(unsigned _watchId) const -{ - Guard l(m_filterLock); - if (_watchId < m_watches.size()) - return m_watches.at(_watchId).changes; - return LocalisedLogEntries(); -} - -LocalisedLogEntries MixClient::checkWatch(unsigned _watchId) -{ - Guard l(m_filterLock); - LocalisedLogEntries ret; - if (_watchId < m_watches.size()) - std::swap(ret, m_watches.at(_watchId).changes); - return ret; -} - -h256 MixClient::hashFromNumber(unsigned _number) const -{ - return bc().numberHash(_number); -} - -eth::BlockInfo MixClient::blockInfo(h256 _hash) const -{ - return BlockInfo(bc().block(_hash)); - -} - eth::BlockInfo MixClient::blockInfo() const -{ - return BlockInfo(bc().block()); -} - -eth::BlockDetails MixClient::blockDetails(h256 _hash) const -{ - return bc().details(_hash); -} - -eth::Transaction MixClient::transaction(h256 _blockHash, unsigned _i) const -{ - auto bl = bc().block(_blockHash); - RLP b(bl); - if (_i < b[1].itemCount()) - return Transaction(b[1][_i].data(), CheckSignature::Range); - else - return Transaction(); -} - -eth::BlockInfo MixClient::uncle(h256 _blockHash, unsigned _i) const -{ - auto bl = bc().block(_blockHash); - RLP b(bl); - if (_i < b[2].itemCount()) - return BlockInfo::fromHeader(b[2][_i].data()); - else - return BlockInfo(); -} - -unsigned MixClient::transactionCount(h256 _blockHash) const -{ - auto bl = bc().block(_blockHash); - RLP b(bl); - return b[1].itemCount(); -} - -unsigned MixClient::uncleCount(h256 _blockHash) const -{ - auto bl = bc().block(_blockHash); - RLP b(bl); - return b[2].itemCount(); -} - -unsigned MixClient::number() const -{ - return bc().number(); -} - -eth::Transactions MixClient::pending() const -{ - return m_state.pending(); -} - -eth::StateDiff MixClient::diff(unsigned _txi, h256 _block) const -{ - State st(m_stateDB, bc(), _block); - return st.fromPending(_txi).diff(st.fromPending(_txi + 1)); -} - -eth::StateDiff MixClient::diff(unsigned _txi, int _block) const -{ - State st = asOf(_block); - return st.fromPending(_txi).diff(st.fromPending(_txi + 1)); -} - -Addresses MixClient::addresses(int _block) const -{ - Addresses ret; - for (auto const& i: asOf(_block).addresses()) - ret.push_back(i.first); - return ret; -} - -u256 MixClient::gasLimitRemaining() const { ReadGuard l(x_state); - return m_state.gasLimitRemaining(); + return BlockInfo(bc().block()); } void MixClient::setAddress(Address _us) @@ -538,20 +315,14 @@ void MixClient::setAddress(Address _us) m_state.setAddress(_us); } -Address MixClient::address() const -{ - ReadGuard l(x_state); - return m_state.address(); -} - void MixClient::setMiningThreads(unsigned _threads) { - m_minigThreads = _threads; + m_miningThreads = _threads; } unsigned MixClient::miningThreads() const { - return m_minigThreads; + return m_miningThreads; } void MixClient::startMining() diff --git a/mix/MixClient.h b/mix/MixClient.h index 958d9d8bd..abd9445c7 100644 --- a/mix/MixClient.h +++ b/mix/MixClient.h @@ -25,7 +25,7 @@ #include #include -#include +#include #include #include "MachineStates.h" @@ -34,9 +34,15 @@ namespace dev namespace mix { -class MixBlockChain; +class MixBlockChain: public dev::eth::BlockChain +{ +public: + MixBlockChain(std::string const& _path, h256 _stateRoot): BlockChain(createGenesisBlock(_stateRoot), _path, WithExisting::Kill) {} -class MixClient: public dev::eth::Interface + static bytes createGenesisBlock(h256 _stateRoot); +}; + +class MixClient: public dev::eth::ClientBase { public: MixClient(std::string const& _dbPath); @@ -44,42 +50,15 @@ public: /// Reset state to the empty state with given balance. void resetState(std::map _accounts); void mine(); - ExecutionResult const& lastExecution() const; - ExecutionResults const& executions() const; - - //dev::eth::Interface - void transact(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice) override; - Address transact(Secret _secret, u256 _endowment, bytes const& _init, u256 _gas, u256 _gasPrice) override; - void inject(bytesConstRef _rlp) override; - void flushTransactions() override; - bytes call(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice) override; - u256 balanceAt(Address _a, int _block) const override; - u256 countAt(Address _a, int _block) const override; - u256 stateAt(Address _a, u256 _l, int _block) const override; - bytes codeAt(Address _a, int _block) const override; - std::map storageAt(Address _a, int _block) const override; - eth::LocalisedLogEntries logs(unsigned _watchId) const override; - eth::LocalisedLogEntries logs(eth::LogFilter const& _filter) const override; - unsigned installWatch(eth::LogFilter const& _filter) override; - unsigned installWatch(h256 _filterId) override; - void uninstallWatch(unsigned _watchId) override; - eth::LocalisedLogEntries peekWatch(unsigned _watchId) const override; - eth::LocalisedLogEntries checkWatch(unsigned _watchId) override; - h256 hashFromNumber(unsigned _number) const override; - eth::BlockInfo blockInfo(h256 _hash) const override; - eth::BlockDetails blockDetails(h256 _hash) const override; - eth::Transaction transaction(h256 _blockHash, unsigned _i) const override; - eth::BlockInfo uncle(h256 _blockHash, unsigned _i) const override; - unsigned transactionCount(h256 _blockHash) const override; - unsigned uncleCount(h256 _blockHash) const override; - unsigned number() const override; - eth::Transactions pending() const override; - eth::StateDiff diff(unsigned _txi, h256 _block) const override; - eth::StateDiff diff(unsigned _txi, int _block) const override; - Addresses addresses(int _block) const override; - u256 gasLimitRemaining() const override; + ExecutionResult lastExecution() const; + ExecutionResult execution(unsigned _index) const; + + void submitTransaction(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice) override; + Address submitTransaction(Secret _secret, u256 _endowment, bytes const& _init, u256 _gas, u256 _gasPrice) override; + dev::eth::ExecutionResult call(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice, eth::BlockNumber _blockNumber = eth::PendingBlock) override; + dev::eth::ExecutionResult create(Secret _secret, u256 _value, bytes const& _data = bytes(), u256 _gas = 10000, u256 _gasPrice = 10 * eth::szabo, eth::BlockNumber _blockNumber = eth::PendingBlock) override; + void setAddress(Address _us) override; - Address address() const override; void setMiningThreads(unsigned _threads) override; unsigned miningThreads() const override; void startMining() override; @@ -88,16 +67,27 @@ public: eth::MineProgress miningProgress() const override; std::pair getWork() override { return std::pair(); } bool submitWork(eth::ProofOfWork::Proof const&) override { return false; } + virtual void flushTransactions() override {} + /// @returns the last mined block information + using Interface::blockInfo; // to remove warning about hiding virtual function eth::BlockInfo blockInfo() const; std::vector userAccounts() { return m_userAccounts; } +protected: + virtual dev::eth::BlockChain& bc() { return *m_bc; } + + /// InterfaceStub methods + virtual dev::eth::State asOf(h256 const& _block) const override; + using ClientBase::asOf; + virtual dev::eth::BlockChain const& bc() const override { return *m_bc; } + virtual dev::eth::State preMine() const override { ReadGuard l(x_state); return m_startState; } + virtual dev::eth::State postMine() const override { ReadGuard l(x_state); return m_state; } + virtual void prepareForTransaction() override {} + private: void executeTransaction(dev::eth::Transaction const& _t, eth::State& _state, bool _call); void noteChanged(h256Set const& _filters); - dev::eth::State asOf(int _block) const; - MixBlockChain& bc() { return *m_bc; } - MixBlockChain const& bc() const { return *m_bc; } std::vector m_userAccounts; eth::State m_state; @@ -105,12 +95,10 @@ private: OverlayDB m_stateDB; std::auto_ptr m_bc; mutable boost::shared_mutex x_state; - mutable std::mutex m_filterLock; - std::map m_filters; - std::map m_watches; + mutable boost::shared_mutex x_executions; ExecutionResults m_executions; std::string m_dbPath; - unsigned m_minigThreads; + unsigned m_miningThreads; }; } diff --git a/mix/QBasicNodeDefinition.cpp b/mix/QBasicNodeDefinition.cpp new file mode 100644 index 000000000..a38fc3c8c --- /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())), m_location(_d->getLocation()) +{ +} + +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 d276d0eb2..9179905eb 100644 --- a/mix/QBasicNodeDefinition.h +++ b/mix/QBasicNodeDefinition.h @@ -21,11 +21,18 @@ #pragma once +#include #include -#include +#include namespace dev { + +namespace solidity +{ +class Declaration; +} + namespace mix { @@ -35,15 +42,17 @@ class QBasicNodeDefinition: public QObject Q_PROPERTY(QString name READ name CONSTANT) public: - QBasicNodeDefinition(): QObject() {} + QBasicNodeDefinition(QObject* _parent = nullptr): QObject(_parent) {} ~QBasicNodeDefinition() {} - QBasicNodeDefinition(solidity::Declaration const* _d): QObject(), m_name(QString::fromStdString(_d->getName())) {} - QBasicNodeDefinition(std::string const& _name): QObject(), 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; } + dev::SourceLocation const& location() { return m_location; } private: QString m_name; + dev::SourceLocation m_location; }; } diff --git a/mix/QContractDefinition.cpp b/mix/QContractDefinition.cpp index 27779ce11..51b37e399 100644 --- a/mix/QContractDefinition.cpp +++ b/mix/QContractDefinition.cpp @@ -21,27 +21,27 @@ #include +#include "QContractDefinition.h" #include #include #include #include #include #include -#include "AppContext.h" -#include "QContractDefinition.h" using namespace dev::solidity; using namespace dev::mix; -QContractDefinition::QContractDefinition(dev::solidity::ContractDefinition const* _contract): QBasicNodeDefinition(_contract) +QContractDefinition::QContractDefinition(QObject* _parent, dev::solidity::ContractDefinition const* _contract): QBasicNodeDefinition(_parent, _contract) { + QObject* parent = _parent ? _parent : this; if (_contract->getConstructor() != nullptr) - m_constructor = new QFunctionDefinition(ContractType(*_contract).getConstructorType()); + m_constructor = new QFunctionDefinition(parent, ContractType(*_contract).getConstructorType()); else - m_constructor = new QFunctionDefinition(); + m_constructor = new QFunctionDefinition(parent); for (auto const& it: _contract->getInterfaceFunctions()) - m_functions.append(new QFunctionDefinition(it.second));} - + m_functions.append(new QFunctionDefinition(parent, it.second)); +} QFunctionDefinition const* QContractDefinition::getFunction(dev::FixedHash<4> _hash) const { diff --git a/mix/QContractDefinition.h b/mix/QContractDefinition.h index 4d586a239..ff0df1f15 100644 --- a/mix/QContractDefinition.h +++ b/mix/QContractDefinition.h @@ -39,8 +39,7 @@ class QContractDefinition: public QBasicNodeDefinition Q_PROPERTY(dev::mix::QFunctionDefinition* constructor READ constructor CONSTANT) public: - QContractDefinition() {} - QContractDefinition(solidity::ContractDefinition const* _contract); + QContractDefinition(QObject* _parent, solidity::ContractDefinition const* _contract); /// Get all the functions of the contract. QQmlListProperty functions() const { return QQmlListProperty(const_cast(this), const_cast(this)->m_functions); } /// Get the constructor of the contract. diff --git a/mix/QFunctionDefinition.cpp b/mix/QFunctionDefinition.cpp index 20dbe070b..13dbd4821 100644 --- a/mix/QFunctionDefinition.cpp +++ b/mix/QFunctionDefinition.cpp @@ -28,15 +28,15 @@ using namespace dev::solidity; using namespace dev::mix; -QFunctionDefinition::QFunctionDefinition(dev::solidity::FunctionTypePointer const& _f): QBasicNodeDefinition(&_f->getDeclaration()), m_hash(dev::sha3(_f->getCanonicalSignature())) +QFunctionDefinition::QFunctionDefinition(QObject* _parent, dev::solidity::FunctionTypePointer const& _f): QBasicNodeDefinition(_parent, &_f->getDeclaration()), m_hash(dev::sha3(_f->externalSignature())) { auto paramNames = _f->getParameterNames(); - auto paramTypes = _f->getParameterTypeNames(); + auto paramTypes = _f->getParameterTypes(); auto returnNames = _f->getReturnParameterNames(); - auto returnTypes = _f->getReturnParameterTypeNames(); + auto returnTypes = _f->getReturnParameterTypes(); for (unsigned i = 0; i < paramNames.size(); ++i) - m_parameters.append(new QVariableDeclaration(paramNames[i], paramTypes[i])); + m_parameters.append(new QVariableDeclaration(_parent, paramNames[i], paramTypes[i].get())); for (unsigned i = 0; i < returnNames.size(); ++i) - m_returnParameters.append(new QVariableDeclaration(returnNames[i], returnTypes[i])); + m_returnParameters.append(new QVariableDeclaration(_parent, returnNames[i], returnTypes[i].get())); } diff --git a/mix/QFunctionDefinition.h b/mix/QFunctionDefinition.h index 0bbf093b5..18f2d911b 100644 --- a/mix/QFunctionDefinition.h +++ b/mix/QFunctionDefinition.h @@ -38,8 +38,9 @@ class QFunctionDefinition: public QBasicNodeDefinition Q_PROPERTY(QQmlListProperty parameters READ parameters) public: - QFunctionDefinition() {} - QFunctionDefinition(solidity::FunctionTypePointer const& _f); + QFunctionDefinition(){} + QFunctionDefinition(QObject* _parent): QBasicNodeDefinition(_parent) {} + QFunctionDefinition(QObject* _parent, solidity::FunctionTypePointer const& _f); /// Get all input parameters of this function. QList const& parametersList() const { return m_parameters; } /// Get all input parameters of this function as QML property. diff --git a/mix/QVariableDeclaration.cpp b/mix/QVariableDeclaration.cpp new file mode 100644 index 000000000..3bbb0d523 --- /dev/null +++ b/mix/QVariableDeclaration.cpp @@ -0,0 +1,70 @@ +/* + 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 QVariableDeclaration.app + * @author Yann yann@ethdev.com + * @author Arkadiy Paronyan arkadiy@ethdev.com + * @date 2015 + */ + +#include "QVariableDeclaration.h" +#include +#include "CodeModel.h" + +namespace dev +{ +namespace mix +{ + +QVariableDeclaration::QVariableDeclaration(QObject* _parent, solidity::VariableDeclaration const* _v): + QBasicNodeDefinition(_parent, _v), + m_type(new QSolidityType(this, CodeModel::nodeType(_v->getType().get()))) +{ +} + +QVariableDeclaration::QVariableDeclaration(QObject* _parent, std::string const& _name, SolidityType const& _type): + QBasicNodeDefinition(_parent, _name), + m_type(new QSolidityType(_parent, _type)) +{ +} + +QVariableDeclaration::QVariableDeclaration(QObject* _parent, std::string const& _name, solidity::Type const* _type): + QBasicNodeDefinition(_parent, _name), + m_type(new QSolidityType(this, CodeModel::nodeType(_type))) +{ +} + +QSolidityType::QSolidityType(QObject* _parent, SolidityType const& _type): + QObject(_parent), + m_type(_type) +{ +} + +QVariantList QSolidityType::members() const +{ + QVariantList members; + if (m_type.type == Type::Struct) + for (auto const& structMember: m_type.members) + members.push_back(QVariant::fromValue(new QVariableDeclaration(parent(), structMember.name.toStdString(), structMember.type))); + if (m_type.type == Type::Enum) + for (auto const& enumName: m_type.enumNames) + members.push_back(QVariant::fromValue(enumName)); + return members; +} + +} +} + diff --git a/mix/QVariableDeclaration.h b/mix/QVariableDeclaration.h index fcd83cb30..4309550b2 100644 --- a/mix/QVariableDeclaration.h +++ b/mix/QVariableDeclaration.h @@ -20,34 +20,78 @@ */ #include -#include -#include +#include #include "QBasicNodeDefinition.h" +#include "SolidityType.h" #pragma once namespace dev { + +namespace solidity +{ +class Type; +class VariableDeclaration; +} + namespace mix { +/// UI wrapper around solidity type +class QSolidityType: public QObject +{ + Q_OBJECT + Q_PROPERTY(int category READ category CONSTANT) //qml does not support enum properties + Q_PROPERTY(int size READ size CONSTANT) + Q_PROPERTY(QString name READ name CONSTANT) + Q_PROPERTY(QVariantList members READ members CONSTANT) + +public: + QSolidityType() {} + QSolidityType(QObject* _parent, SolidityType const& _type); + using Type = SolidityType::Type; + enum QmlType //TODO: Q_ENUMS does not support enum forwarding. Keep in sync with SolidityType::Type + { + SignedInteger, + UnsignedInteger, + Hash, + Bool, + Address, + Bytes, + Enum, + Struct + }; + + Q_ENUMS(QmlType) + SolidityType const& type() const { return m_type; } + Type category() const { return m_type.type; } + int size() const { return m_type.size; } + QString name() const { return m_type.name; } + QVariantList members() const; + +private: + SolidityType m_type; +}; + +/// UI wrapper around declaration (name + type) class QVariableDeclaration: public QBasicNodeDefinition { Q_OBJECT - Q_PROPERTY(QString type READ type WRITE setType) + Q_PROPERTY(QSolidityType* type READ type CONSTANT) public: QVariableDeclaration() {} - QVariableDeclaration(solidity::VariableDeclaration const* _v): QBasicNodeDefinition(_v), m_type(QString::fromStdString(_v->getType()->toString())) {} - QVariableDeclaration(std::string const& _name, std::string const& _type): QBasicNodeDefinition(_name), m_type(QString::fromStdString(_type)) {} - QString type() const { return m_type; } - void setType(QString _type) { m_type = _type; } + QVariableDeclaration(QObject* _parent, solidity::VariableDeclaration const* _v); + QVariableDeclaration(QObject* _parent, std::string const& _name, SolidityType const& _type); + QVariableDeclaration(QObject* _parent, std::string const& _name, solidity::Type const* _type); + QSolidityType* type() const { return m_type; } + void setType(QSolidityType* _type) { m_type = _type; } private: - QString m_type; + QSolidityType* m_type; }; + } } - -Q_DECLARE_METATYPE(dev::mix::QVariableDeclaration*) diff --git a/mix/QVariableDefinition.cpp b/mix/QVariableDefinition.cpp index c78273f99..4553ffad6 100644 --- a/mix/QVariableDefinition.cpp +++ b/mix/QVariableDefinition.cpp @@ -14,132 +14,24 @@ You should have received a copy of the GNU General Public License along with cpp-ethereum. If not, see . */ -/** @file QVariableDefinition.h +/** @file QVariableDefinition.cpp * @author Yann yann@ethdev.com * @date 2014 */ -#include -#include #include "QVariableDefinition.h" +#include -using namespace dev::mix; -int QVariableDefinitionList::rowCount(const QModelIndex& _parent) const +namespace dev { - Q_UNUSED(_parent); - return m_def.size(); -} - -QVariant QVariableDefinitionList::data(const QModelIndex& _index, int _role) const -{ - if (_role != Qt::DisplayRole) - return QVariant(); - - int i = _index.row(); - if (i < 0 || i >= m_def.size()) - return QVariant(QVariant::Invalid); - - return QVariant::fromValue(m_def.at(i)); -} - -QHash QVariableDefinitionList::roleNames() const -{ - QHash roles; - roles[Qt::DisplayRole] = "variable"; - return roles; -} - -QVariableDefinition* QVariableDefinitionList::val(int _idx) -{ - if (_idx < 0 || _idx >= m_def.size()) - return nullptr; - return m_def.at(_idx); -} - -/* - * QIntType - */ -void QIntType::setValue(dev::bigint _value) -{ - m_bigIntvalue = _value; - std::stringstream str; - str << std::dec << m_bigIntvalue; - m_value = QString::fromStdString(str.str()); -} - -dev::bytes QIntType::encodeValue() +namespace mix { - dev::bigint i(value().toStdString()); - bytes ret(32); - toBigEndian((u256)i, ret); - return ret; -} -void QIntType::decodeValue(dev::bytes const& _rawValue) +QString QVariableDefinition::encodeValueAsString() { - dev::u256 un = dev::fromBigEndian(_rawValue); - if (un >> 255) - setValue(-s256(~un + 1)); - else - setValue(un); + return QString::fromStdString(dev::toHex(encodeValue())); } -/* - * QHashType - */ -dev::bytes QHashType::encodeValue() -{ - QByteArray bytesAr = value().toLocal8Bit(); - bytes r = bytes(bytesAr.begin(), bytesAr.end()); - return padded(r, 32); } - -void QHashType::decodeValue(dev::bytes const& _rawValue) -{ - std::string _ret = asString(unpadLeft(_rawValue)); - setValue(QString::fromStdString(_ret)); } -/* - * QRealType - */ -dev::bytes QRealType::encodeValue() -{ - return bytes(); -} - -void QRealType::decodeValue(dev::bytes const& _rawValue) -{ - Q_UNUSED(_rawValue); -} - -/* - * QStringType - */ -dev::bytes QStringType::encodeValue() -{ - QByteArray b = value().toUtf8(); - bytes r = bytes(b.begin(), b.end()); - return paddedRight(r, 32); -} - -void QStringType::decodeValue(dev::bytes const& _rawValue) -{ - setValue(QString::fromUtf8((char*)_rawValue.data())); -} - -/* - * QBoolType - */ -dev::bytes QBoolType::encodeValue() -{ - return padded(jsToBytes(value().toStdString()), 32); -} - -void QBoolType::decodeValue(dev::bytes const& _rawValue) -{ - byte ret = _rawValue.at(_rawValue.size() - 1); - bool boolRet = (ret == byte(1)); - m_boolValue = boolRet; - m_value = m_boolValue ? "1" : "0"; -} diff --git a/mix/QVariableDefinition.h b/mix/QVariableDefinition.h index 8d890539b..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; @@ -63,95 +63,9 @@ private: QVariableDeclaration* m_dec; }; -class QVariableDefinitionList: public QAbstractListModel -{ - Q_OBJECT - -public: - QVariableDefinitionList(QList _def): m_def(_def) {} - int rowCount(const QModelIndex& parent = QModelIndex()) const override; - QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; - QHash roleNames() const override; - /// Return the variable definition at index _idx. - QVariableDefinition* val(int _idx); - /// Return the list of variables. - QList def() { return m_def; } - -private: - QList m_def; -}; - -class QIntType: public QVariableDefinition -{ - Q_OBJECT -public: - QIntType() {} - QIntType(QVariableDeclaration* _def, QString _value): QVariableDefinition(_def, _value) {} - dev::bytes encodeValue() override; - void decodeValue(dev::bytes const& _rawValue) override; - /// @returns an instance of QBigInt for the current value. - QBigInt* toBigInt() { return new QBigInt(m_bigIntvalue); } - dev::bigint bigInt() { return m_bigIntvalue; } - void setValue(dev::bigint _value); - -private: - dev::bigint m_bigIntvalue; -}; - -class QRealType: public QVariableDefinition -{ - Q_OBJECT - -public: - QRealType() {} - QRealType(QVariableDeclaration* _def, QString _value): QVariableDefinition(_def, _value) {} - dev::bytes encodeValue() override; - void decodeValue(dev::bytes const& _rawValue) override; -}; -class QStringType: public QVariableDefinition -{ - Q_OBJECT - -public: - QStringType() {} - QStringType(QVariableDeclaration* _def, QString _value): QVariableDefinition(_def, _value) {} - dev::bytes encodeValue() override; - void decodeValue(dev::bytes const& _rawValue) override; -}; - -class QHashType: public QVariableDefinition -{ - Q_OBJECT - -public: - QHashType() {} - QHashType(QVariableDeclaration* _def, QString _value): QVariableDefinition(_def, _value) {} - dev::bytes encodeValue() override; - void decodeValue(dev::bytes const& _rawValue) override; -}; - -class QBoolType: public QVariableDefinition -{ - Q_OBJECT - -public: - QBoolType(): m_boolValue(false) {} - QBoolType(QVariableDeclaration* _def, QString _value): QVariableDefinition(_def, _value), m_boolValue(false) {} - dev::bytes encodeValue() override; - void decodeValue(dev::bytes const& _rawValue) override; - /// @returns the boolean value for the current definition. - bool toBool() { return m_boolValue; } - -private: - bool m_boolValue; -}; } } -Q_DECLARE_METATYPE(dev::mix::QIntType*) -Q_DECLARE_METATYPE(dev::mix::QStringType*) -Q_DECLARE_METATYPE(dev::mix::QHashType*) -Q_DECLARE_METATYPE(dev::mix::QBoolType*) diff --git a/mix/SolidityType.h b/mix/SolidityType.h new file mode 100644 index 000000000..accdb14b4 --- /dev/null +++ b/mix/SolidityType.h @@ -0,0 +1,72 @@ +/* +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 SolidityType.h +* @author Yann yann@ethdev.com +* @author Arkadiy Paronyan arkadiy@ethdev.com +* @date 2015 +* Ethereum IDE client. +*/ + +#pragma once + +#include +#include +#include + +namespace dev +{ +namespace mix +{ + +struct SolidityDeclaration; + +//Type info extracted from solidity AST +struct SolidityType +{ + enum Type //keep in sync with QSolidity::Type + { + SignedInteger, + UnsignedInteger, + Hash, //TODO: remove + Bool, + Address, + Bytes, + Enum, + Struct + }; + Type type; + unsigned size; //in bytes, + unsigned count; + bool array; + bool dynamicSize; + QString name; + std::vector members; //for struct + std::vector enumNames; //for enum +}; + +struct SolidityDeclaration +{ + QString name; + SolidityType type; + dev::u256 slot; + unsigned offset; +}; + +using SolidityDeclarations = std::vector; + +} +} diff --git a/mix/SortFilterProxyModel.cpp b/mix/SortFilterProxyModel.cpp new file mode 100644 index 000000000..6fb2cca0c --- /dev/null +++ b/mix/SortFilterProxyModel.cpp @@ -0,0 +1,156 @@ +/* + 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 . +*/ +/** + * @author Yann + * @date 2015 + * Proxy used to filter a QML TableView. + */ + + +#include "SortFilterProxyModel.h" +#include +#include + +using namespace dev::mix; + +SortFilterProxyModel::SortFilterProxyModel(QObject* _parent) : QSortFilterProxyModel(_parent) +{ + connect(this, &SortFilterProxyModel::rowsInserted, this, &SortFilterProxyModel::countChanged); + connect(this, &SortFilterProxyModel::rowsRemoved, this, &SortFilterProxyModel::countChanged); +} + +int SortFilterProxyModel::count() const +{ + return rowCount(); +} + +QObject* SortFilterProxyModel::source() const +{ + return sourceModel(); +} + +void SortFilterProxyModel::setSource(QObject* _source) +{ + setSourceModel(qobject_cast(_source)); +} + +QByteArray SortFilterProxyModel::sortRole() const +{ + return roleNames().value(QSortFilterProxyModel::sortRole()); +} + +void SortFilterProxyModel::setSortRole(QByteArray const& _role) +{ + QSortFilterProxyModel::setSortRole(roleKey(_role)); +} + +void SortFilterProxyModel::setSortOrder(Qt::SortOrder _order) +{ + QSortFilterProxyModel::sort(0, _order); +} + +QString SortFilterProxyModel::filterString() const +{ + return filterRegExp().pattern(); +} + +void SortFilterProxyModel::setFilterString(QString const& _filter) +{ + setFilterRegExp(QRegExp(_filter, filterCaseSensitivity(), static_cast(filterSyntax()))); +} + +SortFilterProxyModel::FilterSyntax SortFilterProxyModel::filterSyntax() const +{ + return static_cast(filterRegExp().patternSyntax()); +} + +void SortFilterProxyModel::setFilterSyntax(SortFilterProxyModel::FilterSyntax _syntax) +{ + setFilterRegExp(QRegExp(filterString(), filterCaseSensitivity(), static_cast(_syntax))); +} + +QJSValue SortFilterProxyModel::get(int _idx) const +{ + QJSEngine *engine = qmlEngine(this); + QJSValue value = engine->newObject(); + if (_idx >= 0 && _idx < count()) + { + QHash roles = roleNames(); + QHashIterator it(roles); + while (it.hasNext()) + { + it.next(); + value.setProperty(QString::fromUtf8(it.value()), data(index(_idx, 0), it.key()).toString()); + } + } + return value; +} + +int SortFilterProxyModel::roleKey(QByteArray const& _role) const +{ + QHash roles = roleNames(); + QHashIterator it(roles); + while (it.hasNext()) + { + it.next(); + if (it.value() == _role) + return it.key(); + } + return -1; +} + +QHash SortFilterProxyModel::roleNames() const +{ + if (QAbstractItemModel* source = sourceModel()) + return source->roleNames(); + return QHash(); +} + +bool SortFilterProxyModel::filterAcceptsRow(int _sourceRow, QModelIndex const& _sourceParent) const +{ + QAbstractItemModel* model = sourceModel(); + QModelIndex sourceIndex = model->index(_sourceRow, 0, _sourceParent); + if (!sourceIndex.isValid()) + return true; + + QString keyType = model->data(sourceIndex, roleKey(type.toUtf8())).toString(); + QString keyContent = model->data(sourceIndex, roleKey(content.toUtf8())).toString(); + return keyType.contains(m_filterType) && keyContent.contains(m_filterContent); +} + +void SortFilterProxyModel::setFilterType(QString const& _type) +{ + m_filterType = QRegExp(_type, filterCaseSensitivity(), static_cast(filterSyntax())); + setFilterRegExp(_type); +} + +QString SortFilterProxyModel::filterType() const +{ + return m_filterType.pattern(); +} + +void SortFilterProxyModel::setFilterContent(QString const& _content) +{ + m_filterContent = QRegExp(_content, filterCaseSensitivity(), static_cast(filterSyntax())); + setFilterRegExp(_content); +} + +QString SortFilterProxyModel::filterContent() const +{ + return m_filterContent.pattern(); +} + diff --git a/mix/SortFilterProxyModel.h b/mix/SortFilterProxyModel.h new file mode 100644 index 000000000..de9a2005f --- /dev/null +++ b/mix/SortFilterProxyModel.h @@ -0,0 +1,97 @@ +/* + 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 . +*/ +/** + * @author Yann + * @date 2015 + * Proxy used to filter a QML TableView. + */ + + +#pragma once + +#include +#include + +namespace dev +{ +namespace mix +{ + +class SortFilterProxyModel: public QSortFilterProxyModel +{ + Q_OBJECT + Q_PROPERTY(int count READ count NOTIFY countChanged) + Q_PROPERTY(QObject* source READ source WRITE setSource) + + Q_PROPERTY(QByteArray sortRole READ sortRole WRITE setSortRole) + Q_PROPERTY(Qt::SortOrder sortOrder READ sortOrder WRITE setSortOrder) + + Q_PROPERTY(QString filterContent READ filterContent WRITE setFilterContent) + Q_PROPERTY(QString filterType READ filterType WRITE setFilterType) + Q_PROPERTY(QString filterString READ filterString WRITE setFilterString) + Q_PROPERTY(FilterSyntax filterSyntax READ filterSyntax WRITE setFilterSyntax) + + Q_ENUMS(FilterSyntax) + +public: + explicit SortFilterProxyModel(QObject* _parent = 0); + + QObject* source() const; + void setSource(QObject* _source); + + QByteArray sortRole() const; + void setSortRole(QByteArray const& _role); + + void setSortOrder(Qt::SortOrder _order); + + QString filterContent() const; + void setFilterContent(QString const& _content); + QString filterType() const; + void setFilterType(QString const& _type); + + QString filterString() const; + void setFilterString(QString const& _filter); + + enum FilterSyntax { + RegExp, + Wildcard, + FixedString + }; + + FilterSyntax filterSyntax() const; + void setFilterSyntax(FilterSyntax _syntax); + + int count() const; + Q_INVOKABLE QJSValue get(int _index) const; + +signals: + void countChanged(); + +protected: + int roleKey(QByteArray const& _role) const; + QHash roleNames() const; + bool filterAcceptsRow(int _sourceRow, QModelIndex const& _sourceParent) const; + +private: + QRegExp m_filterType; + QRegExp m_filterContent; + const QString type = "type"; + const QString content = "content"; +}; + +} +} diff --git a/mix/Web3Server.cpp b/mix/Web3Server.cpp index 11cb56b1d..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) @@ -55,21 +135,21 @@ void Web3Server::put(std::string const& _name, std::string const& _key, std::str m_db[k] = _value; } -Json::Value Web3Server::eth_changed(int _id) +Json::Value Web3Server::eth_getFilterChanges(std::string const& _filterId) { - return WebThreeStubServerBase::eth_changed(_id); + return WebThreeStubServerBase::eth_getFilterChanges(_filterId); } -std::string Web3Server::eth_transact(Json::Value const& _json) +std::string Web3Server::eth_sendTransaction(Json::Value const& _json) { - std::string ret = WebThreeStubServerBase::eth_transact(_json); + std::string ret = WebThreeStubServerBase::eth_sendTransaction(_json); emit newTransaction(); return ret; } -std::string Web3Server::eth_call(Json::Value const& _json) +std::string Web3Server::eth_call(Json::Value const& _json, std::string const& _blockNumber) { - std::string ret = WebThreeStubServerBase::eth_call(_json); + std::string ret = WebThreeStubServerBase::eth_call(_json, _blockNumber); emit newTransaction(); return ret; } diff --git a/mix/Web3Server.h b/mix/Web3Server.h index 01a187ff7..cfe5c889e 100644 --- a/mix/Web3Server.h +++ b/mix/Web3Server.h @@ -39,14 +39,15 @@ 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(); protected: - virtual Json::Value eth_changed(int _id) override; - virtual std::string eth_transact(Json::Value const& _json) override; - virtual std::string eth_call(Json::Value const& _json) override; + virtual Json::Value eth_getFilterChanges(std::string const& _filterId) override; + virtual std::string eth_sendTransaction(Json::Value const& _json) override; + virtual std::string eth_call(Json::Value const& _json, std::string const& _blockNumber) override; private: dev::eth::Interface* client() override { return m_client; } @@ -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 new file mode 100644 index 000000000..01074edb3 --- /dev/null +++ b/mix/qml.qrc @@ -0,0 +1,67 @@ + + + qml/AlertMessageDialog.qml + qml/Application.qml + qml/BasicMessage.qml + qml/BigIntValue.qml + qml/CallStack.qml + qml/CodeEditorStyle.qml + qml/CodeEditorView.qml + qml/CommonSeparator.qml + qml/ContractLibrary.qml + qml/DebugBasicInfo.qml + qml/DebugInfoList.qml + qml/Debugger.qml + qml/DebuggerPaneStyle.qml + qml/DefaultLabel.qml + qml/DefaultTextField.qml + qml/DeploymentDialog.qml + qml/Ether.qml + qml/EtherValue.qml + qml/FilesSection.qml + qml/ItemDelegateDataDump.qml + qml/LogsPane.qml + qml/LogsPaneStyle.qml + qml/MainContent.qml + qml/ModalDialog.qml + qml/NewProjectDialog.qml + qml/ProjectFilesStyle.qml + qml/ProjectList.qml + qml/ProjectModel.qml + qml/QBoolTypeView.qml + qml/QHashTypeView.qml + qml/QIntTypeView.qml + qml/QRealTypeView.qml + qml/QStringTypeView.qml + qml/QVariableDeclaration.qml + qml/QVariableDefinition.qml + qml/SourceSansProBold.qml + qml/SourceSansProLight.qml + qml/SourceSansProRegular.qml + qml/Splitter.qml + qml/StateDialog.qml + qml/StateDialogStyle.qml + qml/StateList.qml + qml/StateListModel.qml + qml/StateStyle.qml + qml/StatusPane.qml + qml/StatusPaneStyle.qml + qml/StepActionImage.qml + qml/StorageView.qml + qml/StatesComboBox.qml + qml/StructView.qml + qml/Style.qml + qml/TabStyle.qml + qml/TransactionDialog.qml + qml/TransactionLog.qml + qml/VariablesView.qml + qml/WebPreviewStyle.qml + qml/js/Debugger.js + qml/js/ErrorLocationFormater.js + qml/js/ProjectModel.js + qml/js/QEtherHelper.js + qml/js/TransactionHelper.js + qml/js/Printer.js + qml/js/ansi2html.js + + diff --git a/mix/qml/main.qml b/mix/qml/Application.qml similarity index 84% rename from mix/qml/main.qml rename to mix/qml/Application.qml index 75a913155..3cbe847c7 100644 --- a/mix/qml/main.qml +++ b/mix/qml/Application.qml @@ -7,15 +7,78 @@ import QtQuick.Window 2.1 import QtQuick.PrivateWidgets 1.1 import Qt.labs.settings 1.0 import org.ethereum.qml.QEther 1.0 +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 { + id: mainApplication + signal loaded; visible: true width: 1200 height: 800 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 + } + + ClientModel { + id: clientModel + codeModel: codeModel + } + + ProjectModel { + id: projectModel + } + + FileIo { + id: fileIo + } + + Clipboard { + id: clipboard + } + + Style { + id: appStyle + } + + Connections { + target: mainApplication + onClosing: + { + mainApplication.close(); + close.accepted = false; + } + } + + Component.onCompleted: { + loaded(); + } + + function close() { + projectModel.appIsClosing = true; + if (projectModel.projectPath !== "") + projectModel.closeProject(function() { Qt.quit(); }) + else + Qt.quit(); + } menuBar: MenuBar { Menu { @@ -24,6 +87,7 @@ ApplicationWindow { MenuItem { action: openProjectAction } MenuSeparator {} MenuItem { action: saveAllFilesAction } + MenuItem { action: saveCurrentDocument } MenuSeparator {} MenuItem { action: addExistingFileAction } MenuItem { action: addNewJsFileAction } @@ -92,7 +156,10 @@ ApplicationWindow { id: exitAppAction text: qsTr("Exit") shortcut: "Ctrl+Q" - onTriggered: Qt.quit(); + onTriggered: + { + mainApplication.close(); + } } Action { @@ -111,7 +178,7 @@ ApplicationWindow { id: editStatesAction text: qsTr("Edit States") shortcut: "Ctrl+Alt+E" - onTriggered: stateList.show(); + onTriggered: stateList.open(); } Connections { @@ -279,11 +346,19 @@ ApplicationWindow { Action { id: saveAllFilesAction text: qsTr("Save All") - shortcut: "Ctrl+S" + shortcut: "Ctrl+Shift+A" enabled: !projectModel.isEmpty onTriggered: projectModel.saveAll(); } + Action { + id: saveCurrentDocument + text: qsTr("Save Current Document") + shortcut: "Ctrl+S" + enabled: !projectModel.isEmpty + onTriggered: projectModel.saveCurrentDocument(); + } + Action { id: closeProjectAction text: qsTr("Close Project") diff --git a/mix/qml/CallStack.qml b/mix/qml/CallStack.qml new file mode 100644 index 000000000..9a938078c --- /dev/null +++ b/mix/qml/CallStack.qml @@ -0,0 +1,73 @@ +import QtQuick 2.2 +import QtQuick.Controls 1.1 +import QtQuick.Controls.Styles 1.1 +import QtQuick.Layouts 1.1 + +DebugInfoList +{ + id: callStack + collapsible: true + title : qsTr("Call Stack") + enableSelection: true + itemDelegate: + Item { + anchors.fill: parent + + Rectangle { + anchors.fill: parent + color: "#4A90E2" + visible: styleData.selected; + } + + RowLayout + { + id: row + anchors.fill: parent + Rectangle + { + color: "#f7f7f7" + Layout.fillWidth: true + Layout.minimumWidth: 30 + Layout.maximumWidth: 30 + Text { + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + font.family: "monospace" + anchors.leftMargin: 5 + color: "#4a4a4a" + text: styleData.row; + font.pointSize: dbgStyle.general.basicFontSize + width: parent.width - 5 + elide: Text.ElideRight + } + } + Rectangle + { + color: "transparent" + Layout.fillWidth: true + Layout.minimumWidth: parent.width - 30 + Layout.maximumWidth: parent.width - 30 + Text { + anchors.leftMargin: 5 + width: parent.width - 5 + wrapMode: Text.NoWrap + anchors.left: parent.left + font.family: "monospace" + anchors.verticalCenter: parent.verticalCenter + color: "#4a4a4a" + text: styleData.value; + elide: Text.ElideRight + font.pointSize: dbgStyle.general.basicFontSize + } + } + } + + Rectangle { + anchors.top: row.bottom + width: parent.width; + height: 1; + color: "#cccccc" + anchors.bottom: parent.bottom + } + } +} 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 13ce1e220..c04776c25 100644 --- a/mix/qml/CodeEditorView.qml +++ b/mix/qml/CodeEditorView.qml @@ -2,15 +2,20 @@ import QtQuick 2.0 import QtQuick.Window 2.0 import QtQuick.Layouts 1.0 import QtQuick.Controls 1.0 +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(); } @@ -19,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; @@ -32,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) @@ -51,23 +68,44 @@ Item { breakpointsChanged(document.documentId); }); editor.setText(data, document.syntaxMode); + editor.onIsCleanChanged.connect(function() { + isCleanChanged(editor.isClean, document.documentId); + }); } 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; + } return null; } - function highlightExecution(documentId, location) { + function highlightExecution(documentId, location) + { var editor = getEditor(documentId); if (editor) - editor.highlightExecution(location); + { + if (documentId !== location.sourceName) + findAndHightlight(location.start, location.end, location.sourceName) + else + editor.highlightExecution(location); + } + } + + // Execution is not in the current document. Try: + // Open targeted document and hightlight (TODO) or + // Warn user that file is not available + function findAndHightlight(start, end, sourceName) + { + var editor = getEditor(currentDocumentId); + if (editor) + editor.showWarning(qsTr("Currently debugging in " + sourceName + ". Source not available.")); } 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; @@ -75,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) { @@ -91,6 +129,12 @@ Item { editor.toggleBreakpoint(); } + function resetEditStatus(docId) { + var editor = getEditor(docId); + if (editor) + editor.changeGeneration(); + } + Component.onCompleted: projectModel.codeEditor = codeEditorView; Connections { @@ -98,33 +142,102 @@ Item { onDocumentOpened: { openDocument(document); } + onProjectSaving: { - for (var i = 0; i < editorListModel.count; i++) - fileIo.writeFile(editorListModel.get(i).path, editors.itemAt(i).item.getText()); + for (var i = 0; i < openDocCount; i++) + { + var doc = editorListModel.get(i); + var editor = editors.itemAt(i).item; + if (editor) + fileIo.writeFile(doc.path, editor.getText()); + } } + + onProjectSaved: { + if (projectModel.appIsClosing || projectModel.projectIsClosing) + return; + for (var i = 0; i < openDocCount; i++) + { + var doc = editorListModel.get(i); + resetEditStatus(doc.documentId); + } + } + onProjectClosed: { - for (var i = 0; i < editorListModel.count; i++) { + for (var i = 0; i < editorListModel.count; i++) editors.itemAt(i).visible = false; - } - editorListModel.clear(); + //editorListModel.clear(); currentDocumentId = ""; + openDocCount = 0; + } + + onDocumentSaved: { + resetEditStatus(documentId); + } + + onContractSaved: { + resetEditStatus(documentId); + } + + onDocumentSaving: { + for (var i = 0; i < editorListModel.count; i++) + { + var doc = editorListModel.get(i); + if (doc.path === document.path) + { + fileIo.writeFile(document.path, editors.itemAt(i).item.getText()); + break; + } + } + } + } + + CodeEditorStyle + { + id: style; + } + + MessageDialog + { + id: messageDialog + title: qsTr("File Changed") + text: qsTr("This file has been changed outside of the editor. Do you want to reload it?") + standardButtons: StandardButton.Yes | StandardButton.No + property variant item + property variant doc + onYes: { + doLoadDocument(item, doc); + resetEditStatus(doc.documentId); } } 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: { loadIfNotLoaded() if (visible && item) + { loader.item.setFocus(); + if (changed) + { + changed = false; + messageDialog.item = loader.item; + messageDialog.doc = editorListModel.get(index); + messageDialog.open(); + } + } } Component.onCompleted: { loadIfNotLoaded() @@ -133,8 +246,39 @@ Item { doLoadDocument(loader.item, editorListModel.get(index)) } + Connections + { + target: projectModel + onDocumentChanged: { + if (!item) + return; + var current = editorListModel.get(index); + if (documentId === current.documentId) + { + if (currentDocumentId === current.documentId) + { + messageDialog.item = loader.item; + messageDialog.doc = editorListModel.get(index); + messageDialog.open(); + } + else + changed = true + } + } + + onDocumentUpdated: { + var document = projectModel.getDocument(documentId); + for (var i = 0; i < editorListModel.count; i++) + if (editorListModel.get(i).documentId === documentId) + { + editorListModel.set(i, document); + break; + } + } + } + function loadIfNotLoaded () { - if(visible && !active) { + if (visible && !active) { active = true; } } 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/ContractLibrary.qml b/mix/qml/ContractLibrary.qml index 557f1cc53..4f3afafc6 100644 --- a/mix/qml/ContractLibrary.qml +++ b/mix/qml/ContractLibrary.qml @@ -5,8 +5,8 @@ Item { property alias model: contractListModel; Connections { - target: appContext - Component.onCompleted: { + target: mainApplication + onLoaded: { //TODO: load a list, dependencies, ets, from external files contractListModel.append({ diff --git a/mix/qml/DebugInfoList.qml b/mix/qml/DebugInfoList.qml index ae7e6fabe..5b1a67519 100644 --- a/mix/qml/DebugInfoList.qml +++ b/mix/qml/DebugInfoList.qml @@ -8,27 +8,32 @@ ColumnLayout { property string title property variant listModel; property bool collapsible; + property bool collapsed; property bool enableSelection: false; property real storedHeight: 0; property Component itemDelegate + property Component componentDelegate + property alias item: loader.item signal rowActivated(int index) spacing: 0 function collapse() { storedHeight = childrenRect.height; - storageContainer.state = "collapsed"; + storageContainer.collapse(); } function show() { - storageContainer.state = ""; + storageContainer.expand(); } Component.onCompleted: { if (storageContainer.parent.parent.height === 25) - storageContainer.state = "collapsed"; + storageContainer.collapse(); + else + storageContainer.expand(); } RowLayout { @@ -57,15 +62,15 @@ ColumnLayout { onClicked: { if (collapsible) { - if (storageContainer.state == "collapsed") + if (collapsed) { - storageContainer.state = ""; + storageContainer.expand(); storageContainer.parent.parent.height = storedHeight; } else { storedHeight = root.childrenRect.height; - storageContainer.state = "collapsed"; + storageContainer.collapse(); } } } @@ -78,56 +83,67 @@ ColumnLayout { border.color: "#deddd9" Layout.fillWidth: true Layout.fillHeight: true - states: [ - State { - name: "collapsed" - PropertyChanges { - target: storageImgArrow - source: "qrc:/qml/img/closedtriangleindicator.png" - } - PropertyChanges { - target: storageContainer.parent.parent - height: 25 - } - } - ] - TableView { - clip: true; - alternatingRowColors: false + + function collapse() { + storageImgArrow.source = "qrc:/qml/img/closedtriangleindicator.png"; + if (storageContainer.parent.parent.height > 25) + storageContainer.parent.parent.height = 25; + collapsed = true; + } + + function expand() { + storageImgArrow.source = "qrc:/qml/img/opentriangleindicator.png"; + collapsed = false; + } + + Loader + { + id: loader anchors.top: parent.top anchors.left: parent.left anchors.topMargin: 3 anchors.leftMargin: 3 width: parent.width - 3 height: parent.height - 6 - model: listModel - selectionMode: enableSelection ? SelectionMode.SingleSelection : SelectionMode.NoSelection - headerDelegate: null - itemDelegate: root.itemDelegate onHeightChanged: { if (height <= 0 && collapsible) { if (storedHeight <= 0) storedHeight = 200; - storageContainer.state = "collapsed"; + storageContainer.collapse(); } - else if (height > 0 && storageContainer.state == "collapsed") { - //TODO: fix increasing size - //storageContainer.state = ""; + else if (height > 0 && collapsed) { + storageContainer.expand(); } } - onActivated: rowActivated(row); - Keys.onPressed: { - if ((event.modifiers & Qt.ControlModifier) && event.key === Qt.Key_C && currentRow >=0 && currentRow < listModel.length) { - var str = ""; - for (var i = 0; i < listModel.length; i++) - str += listModel[i] + "\n"; - appContext.toClipboard(str); + + sourceComponent: componentDelegate ? componentDelegate : table + } + Component + { + id: table + TableView + { + clip: true; + alternatingRowColors: false + anchors.fill: parent + model: listModel + selectionMode: enableSelection ? SelectionMode.SingleSelection : SelectionMode.NoSelection + headerDelegate: null + itemDelegate: root.itemDelegate + onActivated: rowActivated(row); + Keys.onPressed: { + if ((event.modifiers & Qt.ControlModifier) && event.key === Qt.Key_C && currentRow >=0 && currentRow < listModel.length) { + var str = ""; + for (var i = 0; i < listModel.length; i++) + str += listModel[i] + "\n"; + clipboard.text = str; + } } - } - TableViewColumn { - role: "modelData" - width: parent.width + TableViewColumn { + role: "modelData" + width: parent.width + } } } } diff --git a/mix/qml/Debugger.qml b/mix/qml/Debugger.qml index af46bee1c..e5c7e576d 100644 --- a/mix/qml/Debugger.qml +++ b/mix/qml/Debugger.qml @@ -12,6 +12,10 @@ Rectangle { id: debugPanel property alias transactionLog: transactionLog + property alias debugSlider: statesSlider + property alias solLocals: solLocals + property alias solStorage: solStorage + property alias solCallStack: solCallStack signal debugExecuteLocation(string documentId, var location) property string compilationErrorMessage property bool assemblyMode: false @@ -29,6 +33,7 @@ Rectangle { onAssemblyModeChanged: { Debugger.updateMode(); + machineStates.updateHeight(); } function displayCompilationErrorIfAny() @@ -39,7 +44,7 @@ Rectangle { var errorInfo = ErrorLocationFormater.extractErrorInfo(compilationErrorMessage, false); errorLocation.text = errorInfo.errorLocation; errorDetail.text = errorInfo.errorDetail; - errorLine.text = errorInfo.errorLine; + errorLine.text = errorInfo.line; } function update(data, giveFocus) @@ -64,10 +69,14 @@ Rectangle { Debugger.setBreakpoints(bp); } + DebuggerPaneStyle { + id: dbgStyle + } + Connections { target: clientModel onDebugDataReady: { - update(_debugData, true); + update(_debugData, false); } } @@ -90,6 +99,9 @@ Rectangle { property alias memoryDumpHeightSettings: memoryRect.height property alias callDataHeightSettings: callDataRect.height property alias transactionLogVisible: transactionLog.visible + property alias solCallStackHeightSettings: solStackRect.height + property alias solStorageHeightSettings: solStorageRect.height + property alias solLocalsHeightSettings: solLocalsRect.height } Rectangle @@ -154,19 +166,15 @@ Rectangle { } } - SplitView { + Splitter { id: debugScrollArea anchors.fill: parent orientation: Qt.Vertical - handleDelegate: Rectangle { - height: machineStates.sideMargin - color: "transparent" - } TransactionLog { id: transactionLog Layout.fillWidth: true - Layout.minimumHeight: 60 + Layout.minimumHeight: 130 height: 250 anchors.top: parent.top anchors.left: parent.left @@ -183,8 +191,12 @@ Rectangle { Layout.fillWidth: true Layout.fillHeight: true function updateHeight() { - statesLayout.height = buttonRow.childrenRect.height + assemblyCodeRow.childrenRect.height + - callStackRect.childrenRect.height + storageRect.childrenRect.height + memoryRect.childrenRect.height + callDataRect.childrenRect.height + 120; + var h = buttonRow.childrenRect.height; + if (assemblyMode) + h += assemblyCodeRow.childrenRect.height + callStackRect.childrenRect.height + storageRect.childrenRect.height + memoryRect.childrenRect.height + callDataRect.childrenRect.height; + else + h += solStackRect.childrenRect.height + solLocalsRect.childrenRect.height + solStorageRect.childrenRect.height; + statesLayout.height = h + 120; } Component.onCompleted: updateHeight(); @@ -215,6 +227,33 @@ Rectangle { anchors.horizontalCenter: parent.horizontalCenter id: jumpButtons spacing: 3 + + StepActionImage + { + id: playAction + enabledStateImg: "qrc:/qml/img/play_button.png" + disableStateImg: "qrc:/qml/img/play_button.png" + onClicked: projectModel.stateListModel.runState(transactionLog.selectedStateIndex) + width: 30 + height: 30 + buttonShortcut: "Ctrl+Shift+F8" + buttonTooltip: qsTr("Start Debugging") + visible: true + } + + StepActionImage + { + id: pauseAction + enabledStateImg: "qrc:/qml/img/stop_button2x.png" + disableStateImg: "qrc:/qml/img/stop_button2x.png" + onClicked: Debugger.init(null); + width: 30 + height: 30 + buttonShortcut: "Ctrl+Shift+F9" + buttonTooltip: qsTr("Stop Debugging") + visible: true + } + StepActionImage { id: runBackAction; @@ -225,6 +264,7 @@ Rectangle { height: 30 buttonShortcut: "Ctrl+Shift+F5" buttonTooltip: qsTr("Run Back") + visible: false } StepActionImage @@ -309,9 +349,8 @@ Rectangle { height: 30 buttonShortcut: "Ctrl+F5" buttonTooltip: qsTr("Run Forward") + visible: false } - - } } @@ -418,7 +457,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 } @@ -428,7 +467,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 } } } @@ -501,7 +540,7 @@ Rectangle { font.family: "monospace" color: "#4a4a4a" text: styleData.row; - font.pointSize: DebuggerPaneStyle.general.basicFontSize + font.pointSize: dbgStyle.general.basicFontSize } } @@ -519,7 +558,7 @@ Rectangle { anchors.verticalCenter: parent.verticalCenter color: "#4a4a4a" text: styleData.value - font.pointSize: DebuggerPaneStyle.general.basicFontSize + font.pointSize: dbgStyle.general.basicFontSize } } } @@ -546,83 +585,66 @@ Rectangle { Rectangle { - id: callStackRect; + id: solStackRect; color: "transparent" Layout.minimumHeight: 25 Layout.maximumHeight: 800 onHeightChanged: machineStates.updateHeight(); - DebugInfoList - { - id: callStack - collapsible: true + visible: !assemblyMode + CallStack { anchors.fill: parent - title : qsTr("Call Stack") - enableSelection: true - onRowActivated: Debugger.displayFrame(index); - itemDelegate: - Item { - anchors.fill: parent + id: solCallStack + } + } - Rectangle { - anchors.fill: parent - color: "#4A90E2" - visible: styleData.selected; - } + Rectangle + { + id: solLocalsRect; + color: "transparent" + Layout.minimumHeight: 25 + Layout.maximumHeight: 800 + onHeightChanged: machineStates.updateHeight(); + visible: !assemblyMode + VariablesView { + title : qsTr("Locals") + anchors.fill: parent + id: solLocals + } + } - RowLayout - { - id: row - anchors.fill: parent - Rectangle - { - color: "#f7f7f7" - Layout.fillWidth: true - Layout.minimumWidth: 30 - Layout.maximumWidth: 30 - Text { - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - font.family: "monospace" - anchors.leftMargin: 5 - color: "#4a4a4a" - text: styleData.row; - font.pointSize: DebuggerPaneStyle.general.basicFontSize - width: parent.width - 5 - elide: Text.ElideRight - } - } - Rectangle - { - color: "transparent" - Layout.fillWidth: true - Layout.minimumWidth: parent.width - 30 - Layout.maximumWidth: parent.width - 30 - Text { - anchors.leftMargin: 5 - width: parent.width - 5 - wrapMode: Text.NoWrap - anchors.left: parent.left - font.family: "monospace" - anchors.verticalCenter: parent.verticalCenter - color: "#4a4a4a" - text: styleData.value; - elide: Text.ElideRight - font.pointSize: DebuggerPaneStyle.general.basicFontSize - } - } - } + Rectangle + { + id: solStorageRect; + color: "transparent" + Layout.minimumHeight: 25 + Layout.maximumHeight: 800 + onHeightChanged: machineStates.updateHeight(); + visible: !assemblyMode + VariablesView { + title : qsTr("Members") + anchors.fill: parent + id: solStorage + } + } - Rectangle { - anchors.top: row.bottom - width: parent.width; - height: 1; - color: "#cccccc" - anchors.bottom: parent.bottom - } - } + Rectangle + { + id: callStackRect; + color: "transparent" + Layout.minimumHeight: 25 + Layout.maximumHeight: 800 + onHeightChanged: machineStates.updateHeight(); + visible: assemblyMode + CallStack { + anchors.fill: parent + id: callStack + onRowActivated: Debugger.displayFrame(index); } } + + + Rectangle { id: storageRect @@ -631,68 +653,10 @@ Rectangle { Layout.minimumHeight: 25 Layout.maximumHeight: 800 onHeightChanged: machineStates.updateHeight(); - DebugInfoList - { - id: storage + visible: assemblyMode + StorageView { anchors.fill: parent - collapsible: true - title : qsTr("Storage") - itemDelegate: - Item { - anchors.fill: parent - RowLayout - { - id: row - anchors.fill: parent - Rectangle - { - color: "#f7f7f7" - Layout.fillWidth: true - Layout.minimumWidth: parent.width / 2 - Layout.maximumWidth: parent.width / 2 - Text { - anchors.verticalCenter: parent.verticalCenter - anchors.left: parent.left - font.family: "monospace" - anchors.leftMargin: 5 - color: "#4a4a4a" - text: styleData.value.split('\t')[0]; - font.pointSize: DebuggerPaneStyle.general.basicFontSize - width: parent.width - 5 - elide: Text.ElideRight - } - } - Rectangle - { - color: "transparent" - Layout.fillWidth: true - Layout.minimumWidth: parent.width / 2 - Layout.maximumWidth: parent.width / 2 - Text { - maximumLineCount: 1 - clip: true - anchors.leftMargin: 5 - width: parent.width - 5 - wrapMode: Text.WrapAnywhere - anchors.left: parent.left - font.family: "monospace" - anchors.verticalCenter: parent.verticalCenter - color: "#4a4a4a" - text: styleData.value.split('\t')[1]; - elide: Text.ElideRight - font.pointSize: DebuggerPaneStyle.general.basicFontSize - } - } - } - - Rectangle { - anchors.top: row.bottom - width: parent.width; - height: 1; - color: "#cccccc" - anchors.bottom: parent.bottom - } - } + id: storage } } @@ -704,6 +668,7 @@ Rectangle { Layout.minimumHeight: 25 Layout.maximumHeight: 800 onHeightChanged: machineStates.updateHeight(); + visible: assemblyMode DebugInfoList { id: memoryDump anchors.fill: parent @@ -726,6 +691,7 @@ Rectangle { Layout.minimumHeight: 25 Layout.maximumHeight: 800 onHeightChanged: machineStates.updateHeight(); + visible: assemblyMode DebugInfoList { id: callDataDump anchors.fill: parent 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 046dd1689..2e7e1601b 100644 --- a/mix/qml/DeploymentDialog.qml +++ b/mix/qml/DeploymentDialog.qml @@ -2,7 +2,7 @@ import QtQuick 2.2 import QtQuick.Controls 1.1 import QtQuick.Layouts 1.1 import QtQuick.Window 2.0 -import QtQuick.Dialogs 1.1 +import QtQuick.Dialogs 1.2 import QtQuick.Controls.Styles 1.3 import org.ethereum.qml.QEther 1.0 import "js/TransactionHelper.js" as TransactionHelper @@ -11,28 +11,22 @@ import "js/QEtherHelper.js" as QEtherHelper import "." -Window { - +Dialog { id: modalDeploymentDialog modality: Qt.ApplicationModal width: 735 - height: 320 - maximumWidth: width - minimumWidth: width - maximumHeight: height - minimumHeight: height + height: 400 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 - function close() { visible = false; @@ -40,10 +34,7 @@ Window { function open() { - modalDeploymentDialog.setX((Screen.width - width) / 2); - modalDeploymentDialog.setY((Screen.height - height) / 2); visible = true; - var requests = [{ //accounts jsonrpc: "2.0", @@ -131,8 +122,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){ @@ -159,136 +150,140 @@ Window { id: lightFont } - Column - { - spacing: 5 + contentItem: Rectangle { + color: appStyle.generic.layout.backgroundColor anchors.fill: parent - anchors.margins: 10 - ColumnLayout + Column { - id: containerDeploy - Layout.fillWidth: true - Layout.preferredHeight: 500 - RowLayout + spacing: 5 + anchors.fill: parent + anchors.margins: 10 + ColumnLayout { - Rectangle + id: containerDeploy + Layout.fillWidth: true + Layout.preferredHeight: 500 + RowLayout { - Layout.preferredWidth: 357 - DefaultLabel + Rectangle { - text: qsTr("Deployment") - font.family: lightFont.name - font.underline: true - anchors.centerIn: parent + Layout.preferredWidth: 357 + DefaultLabel + { + text: qsTr("Deployment") + font.family: lightFont.name + font.underline: true + anchors.centerIn: parent + } } - } - Button - { - action: displayHelpAction - iconSource: "qrc:/qml/img/help.png" - } - - Action { - id: displayHelpAction - tooltip: qsTr("Help") - onTriggered: { - Qt.openUrlExternally("https://github.com/ethereum/wiki/wiki/Mix:-The-DApp-IDE#deployment-to-network") + Button + { + action: displayHelpAction + iconSource: "qrc:/qml/img/help.png" } - } - Button - { - action: openFolderAction - iconSource: "qrc:/qml/img/openedfolder.png" - } - - Action { - id: openFolderAction - enabled: deploymentDialog.packageBase64 !== "" - tooltip: qsTr("Open Package Folder") - onTriggered: { - fileIo.openFileBrowser(projectModel.deploymentDir); + Action { + id: displayHelpAction + tooltip: qsTr("Help") + onTriggered: { + Qt.openUrlExternally("https://github.com/ethereum/wiki/wiki/Mix:-The-DApp-IDE#deployment-to-network") + } } - } - Button - { - action: b64Action - iconSource: "qrc:/qml/img/b64.png" - } + Button + { + action: openFolderAction + iconSource: "qrc:/qml/img/openedfolder.png" + } - Action { - id: b64Action - enabled: deploymentDialog.packageBase64 !== "" - tooltip: qsTr("Copy Base64 conversion to ClipBoard") - onTriggered: { - appContext.toClipboard(deploymentDialog.packageBase64); + Action { + id: openFolderAction + enabled: deploymentDialog.packageBase64 !== "" + tooltip: qsTr("Open Package Folder") + onTriggered: { + fileIo.openFileBrowser(projectModel.deploymentDir); + } } - } - Button - { - action: exitAction - iconSource: "qrc:/qml/img/exit.png" - } + Button + { + action: b64Action + iconSource: "qrc:/qml/img/b64.png" + } - Action { - id: exitAction - tooltip: qsTr("Exit") - onTriggered: { - close() + Action { + id: b64Action + enabled: deploymentDialog.packageBase64 !== "" + tooltip: qsTr("Copy Base64 conversion to ClipBoard") + onTriggered: { + clipboard.text = deploymentDialog.packageBase64; + } } - } - } - GridLayout - { - columns: 2 - width: parent.width + Button + { + action: exitAction + iconSource: "qrc:/qml/img/exit.png" + } - DefaultLabel - { - text: qsTr("Root Registrar address:") + Action { + id: exitAction + tooltip: qsTr("Exit") + onTriggered: { + close() + } + } } - DefaultTextField + GridLayout { - Layout.preferredWidth: 350 - id: registrarAddr - } + columns: 2 + width: parent.width - DefaultLabel - { - text: qsTr("Account used to deploy:") - } + DefaultLabel + { + text: qsTr("Root Registrar address:") + } - Rectangle - { - width: 300 - height: 25 - color: "transparent" - ComboBox { - id: comboAccounts - property var balances: [] - onCurrentIndexChanged : { - if (modelAccounts.count > 0) - { - currentAccount = modelAccounts.get(currentIndex).id; - balance.text = balances[currentIndex]; - } - } - model: ListModel { - id: modelAccounts - } + DefaultTextField + { + Layout.preferredWidth: 350 + id: registrarAddr } DefaultLabel { - anchors.verticalCenter: parent.verticalCenter - anchors.left: comboAccounts.right - anchors.leftMargin: 20 - id: balance; + text: qsTr("Account used to deploy:") + } + + Rectangle + { + width: 300 + height: 25 + color: "transparent" + ComboBox { + id: comboAccounts + property var balances: [] + onCurrentIndexChanged : { + if (modelAccounts.count > 0) + { + currentAccount = modelAccounts.get(currentIndex).id; + balance.text = balances[currentIndex]; + } + } + model: ListModel { + id: modelAccounts + } + } + + DefaultLabel + { + anchors.verticalCenter: parent.verticalCenter + anchors.left: comboAccounts.right + anchors.leftMargin: 20 + id: balance; + } } } @@ -299,7 +294,7 @@ Window { DefaultTextField { - text: "20000" + text: "1000000" Layout.preferredWidth: 350 id: gasToUseInput } @@ -329,158 +324,124 @@ Window { anchors.verticalCenter: parent.verticalCenter; anchors.left: applicationUrlEth.right font.italic: true - font.pointSize: Style.absoluteSize(-1) + font.pointSize: appStyle.absoluteSize(-1) } } } - RowLayout + Rectangle { - Layout.fillWidth: true - Rectangle - { - Layout.preferredWidth: 357 - color: "transparent" - } + width: parent.width + height: 1 + color: "#5891d3" + } - Button + ColumnLayout + { + id: containerRegister + Layout.fillWidth: true + Layout.preferredHeight: 500 + RowLayout { - id: deployButton - action: runAction - iconSource: "qrc:/qml/img/run.png" - } - - Action { - id: runAction - tooltip: qsTr("Deploy contract(s) and Package resources files.") - onTriggered: { - var inError = []; - var ethUrl = ProjectModelCode.formatAppUrl(applicationUrlEth.text); - for (var k in ethUrl) - { - if (ethUrl[k].length > 32) - inError.push(qsTr("Member too long: " + ethUrl[k]) + "\n"); - } - if (!stopForInputError(inError)) + Layout.preferredHeight: 25 + Rectangle + { + Layout.preferredWidth: 356 + DefaultLabel { - if (contractRedeploy.checked) - deployWarningDialog.open(); - else - ProjectModelCode.startDeployProject(false); + text: qsTr("Registration") + font.family: lightFont.name + font.underline: true + anchors.centerIn: parent } } } - CheckBox + GridLayout { - anchors.left: deployButton.right - id: contractRedeploy - enabled: Object.keys(projectModel.deploymentAddresses).length > 0 - checked: Object.keys(projectModel.deploymentAddresses).length == 0 - text: qsTr("Deploy Contract(s)") - anchors.verticalCenter: parent.verticalCenter - } - } - } - - Rectangle - { - width: parent.width - height: 1 - color: "#5891d3" - } + columns: 2 + Layout.fillWidth: true - ColumnLayout - { - id: containerRegister - Layout.fillWidth: true - Layout.preferredHeight: 500 - RowLayout - { - Layout.preferredHeight: 25 - Rectangle - { - Layout.preferredWidth: 356 DefaultLabel { - text: qsTr("Registration") - font.family: lightFont.name - font.underline: true - anchors.centerIn: parent + Layout.preferredWidth: 355 + text: qsTr("Local package URL") } - } - } - GridLayout - { - columns: 2 - Layout.fillWidth: true + DefaultTextField + { + Layout.preferredWidth: 350 + id: localPackageUrl + readOnly: true + enabled: rowRegister.isOkToRegister() + } - DefaultLabel - { - Layout.preferredWidth: 355 - text: qsTr("URL Hint contract address:") - } + DefaultLabel + { + Layout.preferredWidth: 355 + text: qsTr("URL Hint contract address:") + } - DefaultTextField - { - Layout.preferredWidth: 350 - id: urlHintAddr - enabled: rowRegister.isOkToRegister() - } + DefaultTextField + { + Layout.preferredWidth: 350 + id: urlHintAddr + enabled: rowRegister.isOkToRegister() + } - DefaultLabel - { - Layout.preferredWidth: 355 - text: qsTr("Web Application Resources URL: ") - } + DefaultLabel + { + Layout.preferredWidth: 355 + text: qsTr("Web Application Resources URL: ") + } - DefaultTextField - { - Layout.preferredWidth: 350 - id: applicationUrlHttp - enabled: rowRegister.isOkToRegister() + DefaultTextField + { + Layout.preferredWidth: 350 + id: applicationUrlHttp + enabled: rowRegister.isOkToRegister() + } } - } - - RowLayout - { - id: rowRegister - Layout.fillWidth: true - Rectangle + RowLayout { - Layout.preferredWidth: 357 - color: "transparent" - } + id: rowRegister + Layout.fillWidth: true - function isOkToRegister() - { - return Object.keys(projectModel.deploymentAddresses).length > 0 && deploymentDialog.packageHash !== ""; - } + Rectangle + { + Layout.preferredWidth: 357 + color: "transparent" + } - Button { - action: registerAction - iconSource: "qrc:/qml/img/note.png" - } + function isOkToRegister() + { + return Object.keys(projectModel.deploymentAddresses).length > 0 && deploymentDialog.packageHash !== ""; + } - Action { - id: registerAction - enabled: rowRegister.isOkToRegister() - tooltip: qsTr("Register hosted Web Application.") - onTriggered: { - if (applicationUrlHttp.text === "" || deploymentDialog.packageHash === "") - { - deployDialog.title = text; - deployDialog.text = qsTr("Please provide the link where the resources are stored and ensure the package is aleary built using the deployment step.") - deployDialog.open(); - return; + Button { + action: registerAction + iconSource: "qrc:/qml/img/note.png" + } + + Action { + id: registerAction + enabled: rowRegister.isOkToRegister() + tooltip: qsTr("Register hosted Web Application.") + onTriggered: { + if (applicationUrlHttp.text === "" || deploymentDialog.packageHash === "") + { + deployDialog.title = text; + deployDialog.text = qsTr("Please provide the link where the resources are stored and ensure the package is aleary built using the deployment step.") + deployDialog.open(); + return; + } + var inError = []; + if (applicationUrlHttp.text.length > 32) + inError.push(qsTr(applicationUrlHttp.text)); + if (!stopForInputError(inError)) + ProjectModelCode.registerToUrlHint(); } - var inError = []; - if (applicationUrlHttp.text.length > 32) - inError.push(qsTr(applicationUrlHttp.text)); - if (!stopForInputError(inError)) - ProjectModelCode.registerToUrlHint(); } } } diff --git a/mix/qml/FilesSection.qml b/mix/qml/FilesSection.qml index cc5a67741..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,37 +138,56 @@ 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 - Text { - id: nameText - height: parent.height - visible: !renameMode - color: rootItem.isSelected ? ProjectFilesStyle.documentsList.selectedColor : ProjectFilesStyle.documentsList.color - text: name; - font.family: fileNameFont.name - font.pointSize: ProjectFilesStyle.documentsList.fontSize + + Row { + spacing: 3 anchors.verticalCenter: parent.verticalCenter - verticalAlignment: Text.AlignVCenter + anchors.fill: parent anchors.left: parent.left - anchors.leftMargin: ProjectFilesStyle.general.leftMargin + 2 - width: parent.width - Connections - { - target: selManager - onSelected: { - if (groupName != sectionName) - rootItem.isSelected = false; - else if (doc === documentId) - rootItem.isSelected = true; - else - rootItem.isSelected = false; + anchors.leftMargin: projectFilesStyle.general.leftMargin + 2 + Text { + id: nameText + height: parent.height + visible: !renameMode + color: rootItem.isSelected ? projectFilesStyle.documentsList.selectedColor : projectFilesStyle.documentsList.color + text: name; + font.family: fileNameFont.name + font.pointSize: projectFilesStyle.documentsList.fontSize + verticalAlignment: Text.AlignVCenter + + Connections + { + target: selManager + onSelected: { + if (groupName != sectionName) + rootItem.isSelected = false; + else if (doc === documentId) + rootItem.isSelected = true; + else + rootItem.isSelected = false; - if (rootItem.isSelected && section.state === "hidden") - section.state = ""; + if (rootItem.isSelected && section.state === "hidden") + section.state = ""; + } + onIsCleanChanged: { + if (groupName === sectionName && doc === documentId) + editStatusLabel.visible = !isClean; + } } } + + DefaultLabel { + id: editStatusLabel + visible: false + color: rootItem.isSelected ? projectFilesStyle.documentsList.selectedColor : projectFilesStyle.documentsList.color + verticalAlignment: Text.AlignVCenter + text: "*" + width: 10 + height: parent.height + } } TextInput { @@ -177,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 new file mode 100644 index 000000000..082c55386 --- /dev/null +++ b/mix/qml/LogsPane.qml @@ -0,0 +1,589 @@ +import QtQuick 2.0 +import QtQuick.Layouts 1.0 +import QtQuick.Controls 1.1 +import QtQuick.Controls.Styles 1.3 +import org.ethereum.qml.SortFilterProxyModel 1.0 + +Rectangle +{ + property variant currentStatus; + function clear() + { + logsModel.clear(); + } + + function push(_level, _type, _content) + { + _content = _content.replace(/\n/g, " ") + logsModel.insert(0, { "type": _type, "date": Qt.formatDateTime(new Date(), "hh:mm:ss"), "content": _content, "level": _level }); + } + + onVisibleChanged: + { + if (visible && (logsModel.count === 0 || (logsModel.get(0).date !== currentStatus.date && logsModel.get(0).content !== currentStatus.content))) + logsModel.insert(0, { "type": currentStatus.type, "date": currentStatus.date, "content": currentStatus.content, "level": currentStatus.level }); + else if (!visible) + { + for (var k = 0; k < logsModel.count; k++) + { + if (logsModel.get(k).type === "Comp") //do not keep compilation logs. + logsModel.remove(k); + } + } + } + + anchors.fill: parent + radius: 10 + color: "transparent" + id: logsPane + Column { + z: 2 + height: parent.height - rowAction.height + width: parent.width + spacing: 0 + ListModel { + id: logsModel + } + + ScrollView + { + id: scrollView + height: parent.height + width: parent.width + horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff + Column + { + id: logsRect + spacing: 0 + Repeater { + id: logsRepeater + clip: true + property string frontColor: "transparent" + model: SortFilterProxyModel { + id: proxyModel + source: logsModel + property var roles: ["-", "javascript", "run", "state", "comp"] + + Component.onCompleted: { + filterType = regEx(proxyModel.roles); + } + + function search(_value) + { + filterContent = _value; + } + + function toogleFilter(_value) + { + var count = roles.length; + for (var i in roles) + { + if (roles[i] === _value) + { + roles.splice(i, 1); + break; + } + } + if (count === roles.length) + roles.push(_value); + + filterType = regEx(proxyModel.roles); + } + + function regEx(_value) + { + return "(?:" + roles.join('|') + ")"; + } + + filterType: "(?:javascript|run|state|comp)" + filterContent: "" + filterSyntax: SortFilterProxyModel.RegExp + filterCaseSensitivity: Qt.CaseInsensitive + } + + Rectangle + { + width: logStyle.generic.layout.dateWidth + logStyle.generic.layout.contentWidth + logStyle.generic.layout.typeWidth + height: 30 + color: + { + var cl; + if (level === "warning" || level === "error") + cl = logStyle.generic.layout.errorColor; + else + cl = index % 2 === 0 ? "transparent" : logStyle.generic.layout.logAlternateColor; + if (index === 0) + logsRepeater.frontColor = cl; + return cl; + } + + + MouseArea + { + anchors.fill: parent + onClicked: + { + if (logContent.elide === Text.ElideNone) + { + logContent.elide = Text.ElideRight; + logContent.wrapMode = Text.NoWrap + parent.height = 30; + } + else + { + logContent.elide = Text.ElideNone; + logContent.wrapMode = Text.WordWrap; + parent.height = logContent.lineCount * 30; + } + } + } + + + DefaultLabel { + text: date; + 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 + color: { + parent.getColor(level); + } + } + + DefaultLabel { + text: type; + 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 + color: { + parent.getColor(level); + } + } + + Text { + id: logContent + text: content; + 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 + anchors.leftMargin: 230 + color: { + parent.getColor(level); + } + } + + function getColor() + { + if (level === "error") + return "red"; + else if (level === "warning") + return "orange"; + else + return "#808080"; + } + } + } + } + } + + Component { + id: itemDelegate + DefaultLabel { + text: styleData.value; + font.family: logStyle.generic.layout.logLabelFont + font.pointSize: appStyle.absoluteSize(-1) + color: { + if (proxyModel.get(styleData.row).level === "error") + return "red"; + else if (proxyModel.get(styleData.row).level === "warning") + return "orange"; + else + return "#808080"; + } + } + } + } + + Rectangle + { + gradient: Gradient { + GradientStop { position: 0.0; color: "#f1f1f1" } + GradientStop { position: 1.0; color: "#d9d7da" } + } + Layout.preferredHeight: logStyle.generic.layout.headerHeight + height: logStyle.generic.layout.headerHeight + width: logsPane.width + anchors.bottom: parent.bottom + Row + { + id: rowAction + anchors.leftMargin: logStyle.generic.layout.leftMargin + anchors.left: parent.left + spacing: logStyle.generic.layout.headerButtonSpacing + height: parent.height + Rectangle + { + color: "transparent" + height: parent.height + width: 40 + DefaultLabel + { + anchors.verticalCenter: parent.verticalCenter + color: logStyle.generic.layout.logLabelColor + font.pointSize: appStyle.absoluteSize(-3) + font.family: logStyle.generic.layout.logLabelFont + text: qsTr("Show:") + } + } + + Rectangle { + anchors.verticalCenter: parent.verticalCenter + width: 1; + height: parent.height + color: logStyle.generic.layout.buttonSeparatorColor1 + } + + Rectangle { + anchors.verticalCenter: parent.verticalCenter + width: 2; + height: parent.height + color: logStyle.generic.layout.buttonSeparatorColor2 + } + + ToolButton { + id: javascriptButton + checkable: true + height: logStyle.generic.layout.headerButtonHeight + width: 20 + anchors.verticalCenter: parent.verticalCenter + checked: true + onCheckedChanged: { + proxyModel.toogleFilter("javascript") + } + tooltip: qsTr("JavaScript") + style: + ButtonStyle { + label: + Item { + DefaultLabel { + 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 ? logStyle.generic.layout.buttonSelected : "transparent" + } + } + } + + Rectangle { + anchors.verticalCenter: parent.verticalCenter + width: 1; + height: parent.height + color: logStyle.generic.layout.buttonSeparatorColor1 + } + + Rectangle { + anchors.verticalCenter: parent.verticalCenter + width: 2; + height: parent.height + color: logStyle.generic.layout.buttonSeparatorColor2 + } + + ToolButton { + id: runButton + checkable: true + height: logStyle.generic.layout.headerButtonHeight + width: 30 + anchors.verticalCenter: parent.verticalCenter + checked: true + onCheckedChanged: { + proxyModel.toogleFilter("run") + } + tooltip: qsTr("Run") + style: + ButtonStyle { + label: + Item { + DefaultLabel { + 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 ? logStyle.generic.layout.buttonSelected : "transparent" + } + } + } + + Rectangle { + anchors.verticalCenter: parent.verticalCenter + width: 1; + height: parent.height + color: logStyle.generic.layout.buttonSeparatorColor1 + } + + Rectangle { + anchors.verticalCenter: parent.verticalCenter + width: 2; + height: parent.height + color: logStyle.generic.layout.buttonSeparatorColor2 + } + + ToolButton { + id: stateButton + checkable: true + height: logStyle.generic.layout.headerButtonHeight + anchors.verticalCenter: parent.verticalCenter + width: 35 + checked: true + onCheckedChanged: { + proxyModel.toogleFilter("state") + } + tooltip: qsTr("State") + style: + ButtonStyle { + label: + Item { + DefaultLabel { + 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 ? logStyle.generic.layout.buttonSelected : "transparent" + } + } + } + + Rectangle { + anchors.verticalCenter: parent.verticalCenter + width: 1; + height: parent.height + color: logStyle.generic.layout.buttonSeparatorColor1 + } + + Rectangle { + anchors.verticalCenter: parent.verticalCenter + width: 2; + height: parent.height + color: logStyle.generic.layout.buttonSeparatorColor2 + } + } + + Row + { + height: parent.height + anchors.right: parent.right + anchors.rightMargin: 10 + spacing: 10 + Rectangle + { + height: logStyle.generic.layout.headerButtonHeight + anchors.verticalCenter: parent.verticalCenter + color: "transparent" + width: 20 + Button + { + id: clearButton + action: clearAction + anchors.fill: parent + anchors.verticalCenter: parent.verticalCenter + height: 25 + style: + ButtonStyle { + background: + Rectangle { + height: logStyle.generic.layout.headerButtonHeight + implicitHeight: logStyle.generic.layout.headerButtonHeight + color: "transparent" + } + } + } + + Image { + id: clearImage + source: clearAction.enabled ? "qrc:/qml/img/cleariconactive.png" : "qrc:/qml/img/clearicon.png" + anchors.centerIn: parent + fillMode: Image.PreserveAspectFit + width: 20 + height: 25 + } + + Action { + id: clearAction + enabled: logsModel.count > 0 + tooltip: qsTr("Clear") + onTriggered: { + logsModel.clear(); + } + } + } + + Rectangle + { + height: logStyle.generic.layout.headerButtonHeight + anchors.verticalCenter: parent.verticalCenter + color: "transparent" + width: 20 + Button + { + id: copyButton + action: copyAction + anchors.fill: parent + anchors.verticalCenter: parent.verticalCenter + height: 25 + style: + ButtonStyle { + background: + Rectangle { + height: logStyle.generic.layout.headerButtonHeight + implicitHeight: logStyle.generic.layout.headerButtonHeight + color: "transparent" + } + } + } + + Image { + id: copyImage + source: copyAction.enabled ? "qrc:/qml/img/copyiconactive.png" : "qrc:/qml/img/copyicon.png" + anchors.centerIn: parent + fillMode: Image.PreserveAspectFit + width: 20 + height: 25 + } + + Action { + id: copyAction + enabled: logsModel.count > 0 + tooltip: qsTr("Copy to Clipboard") + onTriggered: { + var content = ""; + for (var k = 0; k < logsModel.count; k++) + { + var log = logsModel.get(k); + content += log.type + "\t" + log.level + "\t" + log.date + "\t" + log.content + "\n"; + } + clipboard.text = content; + } + } + } + + Rectangle + { + width: 120 + radius: 10 + height: 25 + color: "white" + anchors.verticalCenter: parent.verticalCenter + + Image + { + id: searchImg + source: "qrc:/qml/img/searchicon.png" + fillMode: Image.PreserveAspectFit + width: 20 + height: 25 + z: 3 + } + + DefaultTextField + { + id: searchBox + z: 2 + width: 100 + anchors.left: searchImg.right + anchors.leftMargin: -7 + font.family: logStyle.generic.layout.logLabelFont + font.pointSize: appStyle.absoluteSize(-3) + font.italic: true + text: qsTr(" - Search - ") + onFocusChanged: + { + if (!focus && text === "") + text = qsTr(" - Search - "); + else if (focus && text === qsTr(" - Search - ")) + text = ""; + } + + onTextChanged: { + if (text === qsTr(" - Search - ")) + proxyModel.search(""); + else + proxyModel.search(text); + } + + style: + TextFieldStyle { + background: Rectangle { + radius: 10 + } + } + } + } + + Rectangle + { + height: logStyle.generic.layout.headerButtonHeight + anchors.verticalCenter: parent.verticalCenter + color: "transparent" + width: 20 + Button + { + id: hideButton + action: hideAction + anchors.fill: parent + anchors.verticalCenter: parent.verticalCenter + height: 25 + style: + ButtonStyle { + background: + Rectangle { + height: logStyle.generic.layout.headerButtonHeight + implicitHeight: logStyle.generic.layout.headerButtonHeight + color: "transparent" + } + } + } + + Image { + id: hideImage + source: "qrc:/qml/img/exit.png" + anchors.centerIn: parent + fillMode: Image.PreserveAspectFit + width: 20 + height: 25 + } + + Action { + id: hideAction + tooltip: qsTr("Exit") + onTriggered: { + logsPane.parent.toggle(); + } + } + } + } + } + +} diff --git a/mix/qml/LogsPaneStyle.qml b/mix/qml/LogsPaneStyle.qml new file mode 100644 index 000000000..fe50610c8 --- /dev/null +++ b/mix/qml/LogsPaneStyle.qml @@ -0,0 +1,30 @@ +import QtQuick 2.0 + +QtObject { + + function absoluteSize(rel) + { + return systemPointSize + rel; + } + + property QtObject generic: QtObject { + property QtObject layout: QtObject { + property string backgroundColor: "#f7f7f7" + property int headerHeight: 30 + property int headerButtonSpacing: 0 + property int leftMargin: 10 + property int headerButtonHeight: 30 + property string logLabelColor: "#4a4a4a" + property string logLabelFont: "sans serif" + property int headerInputWidth: 200 + property int dateWidth: 150 + property int typeWidth: 150 + property int contentWidth: 560 + property string logAlternateColor: "#f6f5f6" + property string errorColor: "#fffcd5" + property string buttonSeparatorColor1: "#d3d0d0" + property string buttonSeparatorColor2: "#f2f1f2" + property string buttonSelected: "#dcdcdc" + } + } +} diff --git a/mix/qml/MainContent.qml b/mix/qml/MainContent.qml index 081e0cd95..ca08485c3 100644 --- a/mix/qml/MainContent.qml +++ b/mix/qml/MainContent.qml @@ -2,7 +2,6 @@ import QtQuick 2.2 import QtQuick.Controls 1.1 import QtQuick.Layouts 1.0 import QtQuick.Controls.Styles 1.1 -import CodeEditorExtensionManager 1.0 import Qt.labs.settings 1.0 import org.ethereum.qml.QEther 1.0 import "js/QEtherHelper.js" as QEtherHelper @@ -23,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 @@ -35,8 +35,8 @@ Rectangle { onCompilationComplete: { if (firstCompile) { firstCompile = false; - if (runOnProjectLoad) - startQuickDebugging(); + if (runOnProjectLoad) + startQuickDebugging(); } } } @@ -101,10 +101,6 @@ Rectangle { rightView.displayCompilationErrorIfAny(); } - CodeEditorExtensionManager { - headerView: headerPaneTabs; - } - Settings { id: mainSettings property alias codeWebOrientation: codeWebSplitter.orientation @@ -116,6 +112,7 @@ Rectangle { ColumnLayout { + id: mainColumn anchors.fill: parent spacing: 0 Rectangle { @@ -133,21 +130,15 @@ Rectangle { } id: headerPaneContainer anchors.fill: parent - TabView { - id: headerPaneTabs - tabsVisible: false - antialiasing: true + StatusPane + { anchors.fill: parent - style: TabViewStyle { - frameOverlap: 1 - tab: Rectangle {} - frame: Rectangle { color: "transparent" } - } + webPreview: webPreview } } } - Rectangle{ + Rectangle { Layout.fillWidth: true height: 1 color: "#8c8c8c" @@ -164,14 +155,9 @@ Rectangle { property alias rightViewWidth: rightView.width } - SplitView + Splitter { anchors.fill: parent - handleDelegate: Rectangle { - width: 1 - height: 1 - color: "#8c8c8c" - } orientation: Qt.Horizontal ProjectList { @@ -179,17 +165,17 @@ Rectangle { width: 350 Layout.minimumWidth: 250 Layout.fillHeight: true + Connections { + target: projectModel.codeEditor + } } + Rectangle { id: contentView Layout.fillHeight: true Layout.fillWidth: true - SplitView { - handleDelegate: Rectangle { - width: 1 - height: 1 - color: "#8c8c8c" - } + + Splitter { id: codeWebSplitter anchors.fill: parent orientation: Qt.Vertical diff --git a/mix/qml/NewProjectDialog.qml b/mix/qml/NewProjectDialog.qml index 1ec53e1d9..3c3c4e649 100644 --- a/mix/qml/NewProjectDialog.qml +++ b/mix/qml/NewProjectDialog.qml @@ -1,10 +1,11 @@ import QtQuick 2.2 import QtQuick.Controls 1.1 import QtQuick.Layouts 1.1 +import QtQuick.Dialogs 1.2 import QtQuick.Window 2.0 import QtQuick.Dialogs 1.1 -Window { +Dialog { id: newProjectWin modality: Qt.ApplicationModal @@ -15,6 +16,7 @@ Window { property alias projectTitle: titleField.text readonly property string projectPath: "file://" + pathField.text + property alias pathFieldText: pathField.text signal accepted function open() { @@ -32,62 +34,65 @@ Window { close(); accepted(); } - - GridLayout { - id: dialogContent - columns: 2 + contentItem: Rectangle { anchors.fill: parent - anchors.margins: 10 - rowSpacing: 10 - columnSpacing: 10 + GridLayout + { + id: dialogContent + columns: 2 + anchors.fill: parent + anchors.margins: 10 + rowSpacing: 10 + columnSpacing: 10 - Label { - text: qsTr("Title") - } - TextField { - id: titleField - focus: true - Layout.fillWidth: true - Keys.onReturnPressed: { - if (okButton.enabled) - acceptAndClose(); + Label { + text: qsTr("Title") } - } - - Label { - text: qsTr("Path") - } - RowLayout { TextField { - id: pathField + id: titleField + focus: true Layout.fillWidth: true Keys.onReturnPressed: { if (okButton.enabled) acceptAndClose(); } } - Button { - text: qsTr("Browse") - onClicked: createProjectFileDialog.open() + + Label { + text: qsTr("Path") + } + RowLayout { + TextField { + id: pathField + Layout.fillWidth: true + Keys.onReturnPressed: { + if (okButton.enabled) + acceptAndClose(); + } + } + Button { + text: qsTr("Browse") + onClicked: createProjectFileDialog.open() + } } - } - RowLayout - { - anchors.bottom: parent.bottom - anchors.right: parent.right; + RowLayout + { + anchors.bottom: parent.bottom + anchors.right: parent.right; - Button { - id: okButton; - enabled: titleField.text != "" && pathField.text != "" - text: qsTr("OK"); - onClicked: { - acceptAndClose(); + Button { + id: okButton; + enabled: titleField.text != "" && pathField.text != "" + text: qsTr("OK"); + onClicked: { + acceptAndClose(); + } + } + Button { + text: qsTr("Cancel"); + onClicked: close(); } - } - Button { - text: qsTr("Cancel"); - onClicked: close(); } } } @@ -101,8 +106,8 @@ Window { var u = createProjectFileDialog.fileUrl.toString(); if (u.indexOf("file://") == 0) u = u.substring(7, u.length) - if (Qt.platform.os == "windows" && u.indexOf("/") == 0) - u = u.substring(1, u.length); + if (Qt.platform.os == "windows" && u.indexOf("/") == 0) + u = u.substring(1, u.length); pathField.text = u; } } 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 18e7a0d5b..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,16 +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 { @@ -83,6 +86,7 @@ Item { Repeater { model: [qsTr("Contracts"), qsTr("Javascript"), qsTr("Web Pages"), qsTr("Styles"), qsTr("Images"), qsTr("Misc")]; signal selected(string doc, string groupName) + signal isCleanChanged(string doc, string groupName, var isClean) property int incr: -1; id: sectionRepeater FilesSection @@ -112,6 +116,25 @@ Item { Connections { target: codeModel + onContractRenamed: { + if (modelData === "Contracts") + { + var ci = 0; + for (var si = 0; si < projectModel.listModel.count; si++) { + var document = projectModel.listModel.get(si); + if (document.isContract) { + var compiledDoc = codeModel.contractByDocumentId(document.documentId); + if (_documentId === document.documentId && _newName !== document.name) { + document.name = _newName; + projectModel.listModel.set(si, document); + sectionModel.set(ci, document); + } + ci++; + } + } + } + } + onCompilationComplete: { if (modelData === "Contracts") { var ci = 0; @@ -145,6 +168,17 @@ Item { } } + onIsCleanChanged: { + for (var si = 0; si < sectionModel.count; si++) { + var document = sectionModel.get(si); + if (documentId === document.documentId && document.groupName === modelData) + { + selManager.isCleanChanged(documentId, modelData, isClean); + break; + } + } + } + onDocumentOpened: { if (document.groupName === modelData) sectionRepeater.selected(document.documentId, modelData); diff --git a/mix/qml/ProjectModel.qml b/mix/qml/ProjectModel.qml index d3b4070aa..58eb18a16 100644 --- a/mix/qml/ProjectModel.qml +++ b/mix/qml/ProjectModel.qml @@ -7,29 +7,35 @@ import Qt.labs.settings 1.0 import "js/ProjectModel.js" as ProjectModelCode Item { - id: projectModel signal projectClosed signal projectLoading(var projectData) signal projectLoaded() + signal documentSaving(var document) + signal documentChanged(var documentId) signal documentOpened(var document) signal documentRemoved(var documentId) signal documentUpdated(var documentId) //renamed signal documentAdded(var documentId) - signal projectSaving(var projectData) + signal projectSaving() + signal projectFileSaving(var projectData) signal projectSaved() + signal projectFileSaved() signal newProject(var projectData) signal documentSaved(var documentId) + signal contractSaved(var documentId) signal deploymentStarted() signal deploymentStepChanged(string message) signal deploymentComplete() signal deploymentError(string error) + signal isCleanChanged(var isClean, string documentId) property bool isEmpty: (projectPath === "") readonly property string projectFileName: ".mix" - property bool haveUnsavedChanges: false + property bool appIsClosing: false + property bool projectIsClosing: false property string projectPath: "" property string projectTitle: "" property string currentDocumentId: "" @@ -37,13 +43,18 @@ 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(); } + function saveCurrentDocument() { ProjectModelCode.saveCurrentDocument(); } function createProject() { ProjectModelCode.createProject(); } - function closeProject() { ProjectModelCode.closeProject(); } + function closeProject(callBack) { ProjectModelCode.closeProject(callBack); } function saveProject() { ProjectModelCode.saveProject(); } + function saveProjectFile() { ProjectModelCode.saveProjectFile(); } function loadProject(path) { ProjectModelCode.loadProject(path); } function newHtmlFile() { ProjectModelCode.newHtmlFile(); } function newJsFile() { ProjectModelCode.newJsFile(); } @@ -55,6 +66,7 @@ Item { function renameDocument(documentId, newName) { ProjectModelCode.renameDocument(documentId, newName); } function removeDocument(documentId) { ProjectModelCode.removeDocument(documentId); } function getDocument(documentId) { return ProjectModelCode.getDocument(documentId); } + function getDocumentIdByName(documentName) { return ProjectModelCode.getDocumentIdByName(documentName); } function getDocumentIndex(documentId) { return ProjectModelCode.getDocumentIndex(documentId); } function addExistingFiles(paths) { ProjectModelCode.doAddExistingFiles(paths); } function deployProject() { ProjectModelCode.deployProject(false); } @@ -62,13 +74,27 @@ Item { function formatAppUrl() { ProjectModelCode.formatAppUrl(url); } Connections { - target: appContext - onAppLoaded: { - if (projectSettings.lastProjectPath) + target: mainApplication + onLoaded: { + if (projectSettings.lastProjectPath && projectSettings.lastProjectPath !== "") projectModel.loadProject(projectSettings.lastProjectPath) } } + Connections { + target: codeEditor + onIsCleanChanged: { + for (var i in unsavedFiles) + { + if (unsavedFiles[i] === documentId && isClean) + unsavedFiles.splice(i, 1); + } + if (!isClean) + unsavedFiles.push(documentId); + isCleanChanged(isClean, documentId); + } + } + NewProjectDialog { id: newProjectDialog visible: false @@ -79,18 +105,40 @@ Item { } } + Connections + { + target: fileIo + property bool saving: false + onFileChanged: + { + fileIo.watchFileChanged(_filePath); + var documentId = ProjectModelCode.getDocumentByPath(_filePath); + documentChanged(documentId); + } + } + MessageDialog { id: saveMessageDialog title: qsTr("Project") - text: qsTr("Do you want to save changes?") - standardButtons: StandardButton.Ok | StandardButton.Cancel + text: qsTr("Some files require to be saved. Do you want to save changes?"); + standardButtons: StandardButton.Yes | StandardButton.No | StandardButton.Cancel icon: StandardIcon.Question - onAccepted: { + property var callBack; + onYes: { + projectIsClosing = true; projectModel.saveAll(); + unsavedFiles = []; ProjectModelCode.doCloseProject(); + if (callBack) + callBack(); } - onRejected: { + onRejected: {} + onNo: { + projectIsClosing = true; + unsavedFiles = []; ProjectModelCode.doCloseProject(); + if (callBack) + callBack(); } } @@ -112,7 +160,7 @@ Item { } MessageDialog { - id: deployRessourcesDialog + id: deployResourcesDialog title: qsTr("Project") standardButtons: StandardButton.Ok } @@ -130,6 +178,14 @@ Item { id: projectStateListModel } + Connections + { + target: projectModel + onProjectClosed: { + projectPath = ""; + } + } + Settings { id: projectSettings property string lastProjectPath; diff --git a/mix/qml/QBoolType.qml b/mix/qml/QBoolType.qml deleted file mode 100644 index 9f5fe6fd7..000000000 --- a/mix/qml/QBoolType.qml +++ /dev/null @@ -1,7 +0,0 @@ -import QtQuick 2.0 -import org.ethereum.qml.QBoolType 1.0 - -QBoolType -{ -} - diff --git a/mix/qml/QBoolTypeView.qml b/mix/qml/QBoolTypeView.qml index a52601bdb..9911d4549 100644 --- a/mix/qml/QBoolTypeView.qml +++ b/mix/qml/QBoolTypeView.qml @@ -4,8 +4,10 @@ import QtQuick.Controls 1.3 Item { id: editRoot - property string text + property string value property string defaultValue + height: 20 + width: 150 Rectangle { anchors.fill: parent @@ -14,10 +16,10 @@ Item property bool inited: false Component.onCompleted: { - if (text === "") + if (value === "") currentIndex = parseInt(defaultValue); else - currentIndex = parseInt(text); + currentIndex = parseInt(value); inited = true } @@ -26,7 +28,7 @@ Item onCurrentIndexChanged: { if (inited) - text = comboModel.get(currentIndex).value; + value = comboModel.get(currentIndex).value; } model: ListModel { diff --git a/mix/qml/QHashType.qml b/mix/qml/QHashType.qml deleted file mode 100644 index cbd2618cf..000000000 --- a/mix/qml/QHashType.qml +++ /dev/null @@ -1,7 +0,0 @@ -import QtQuick 2.0 -import org.ethereum.qml.QHashType 1.0 - -QHashType -{ -} - diff --git a/mix/qml/QHashTypeView.qml b/mix/qml/QHashTypeView.qml index 73678f953..a097c22dd 100644 --- a/mix/qml/QHashTypeView.qml +++ b/mix/qml/QHashTypeView.qml @@ -2,8 +2,10 @@ import QtQuick 2.0 Item { - property alias text: textinput.text + property alias value: textinput.text id: editRoot + height: 20 + width: 150 SourceSansProBold { @@ -13,10 +15,9 @@ Item Rectangle { anchors.fill: parent radius: 4 - color: "#f7f7f7" TextInput { id: textinput - text: text + text: value anchors.fill: parent wrapMode: Text.WrapAnywhere clip: true diff --git a/mix/qml/QIntType.qml b/mix/qml/QIntType.qml deleted file mode 100644 index 241bd4a12..000000000 --- a/mix/qml/QIntType.qml +++ /dev/null @@ -1,7 +0,0 @@ -import QtQuick 2.0 -import org.ethereum.qml.QIntType 1.0 - -QIntType -{ -} - diff --git a/mix/qml/QIntTypeView.qml b/mix/qml/QIntTypeView.qml index 98344dd8b..8adb46846 100644 --- a/mix/qml/QIntTypeView.qml +++ b/mix/qml/QIntTypeView.qml @@ -1,9 +1,12 @@ import QtQuick 2.0 +import QtQuick.Layouts 1.1 Item { - property alias text: textinput.text + property alias value: textinput.text id: editRoot + height: 20 + width: 150 SourceSansProBold { @@ -13,10 +16,9 @@ Item Rectangle { anchors.fill: parent radius: 4 - color: "#f7f7f7" TextInput { id: textinput - text: text + text: value anchors.fill: parent font.family: boldFont.name clip: true diff --git a/mix/qml/QRealType.qml b/mix/qml/QRealType.qml deleted file mode 100644 index 9a015b1c7..000000000 --- a/mix/qml/QRealType.qml +++ /dev/null @@ -1,7 +0,0 @@ -import QtQuick 2.0 -import org.ethereum.qml.QRealType 1.0 - -QRealType -{ -} - diff --git a/mix/qml/QStringType.qml b/mix/qml/QStringType.qml deleted file mode 100644 index 4113fec20..000000000 --- a/mix/qml/QStringType.qml +++ /dev/null @@ -1,7 +0,0 @@ -import QtQuick 2.0 -import org.ethereum.qml.QStringType 1.0 - -QStringType -{ -} - diff --git a/mix/qml/QStringTypeView.qml b/mix/qml/QStringTypeView.qml index 016206e6d..ffbde734c 100644 --- a/mix/qml/QStringTypeView.qml +++ b/mix/qml/QStringTypeView.qml @@ -2,8 +2,10 @@ import QtQuick 2.0 Item { - property alias text: textinput.text + property alias value: textinput.text id: editRoot + height: 20 + width: 150 SourceSansProBold { @@ -13,10 +15,9 @@ Item Rectangle { anchors.fill: parent radius: 4 - color: "#f7f7f7" TextInput { id: textinput - text: text + text: value clip: true anchors.fill: parent wrapMode: Text.WrapAnywhere diff --git a/mix/qml/Splitter.qml b/mix/qml/Splitter.qml new file mode 100644 index 000000000..012379700 --- /dev/null +++ b/mix/qml/Splitter.qml @@ -0,0 +1,11 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.3 + +SplitView +{ + handleDelegate: Rectangle { + width: 4 + height: 4 + color: "#cccccc" + } +} diff --git a/mix/qml/StateDialog.qml b/mix/qml/StateDialog.qml index 4d890fe18..0af27f055 100644 --- a/mix/qml/StateDialog.qml +++ b/mix/qml/StateDialog.qml @@ -1,6 +1,6 @@ import QtQuick 2.2 import QtQuick.Controls 1.1 -import QtQuick.Dialogs 1.1 +import QtQuick.Dialogs 1.2 import QtQuick.Layouts 1.1 import QtQuick.Window 2.0 import QtQuick.Controls.Styles 1.3 @@ -9,7 +9,7 @@ import "js/QEtherHelper.js" as QEtherHelper import "js/TransactionHelper.js" as TransactionHelper import "." -Window { +Dialog { id: modalStateDialog modality: Qt.ApplicationModal @@ -17,15 +17,20 @@ Window { height: 480 title: qsTr("Edit State") visible: false - 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: [] signal accepted + StateDialogStyle { + id: stateDialogStyle + } + function open(index, item, setDefault) { stateIndex = index; stateTitle = item.title; @@ -46,9 +51,6 @@ Window { stateAccounts.push(item.accounts[k]); } - modalStateDialog.setX((Screen.width - width) / 2); - modalStateDialog.setY((Screen.height - height) / 2); - visible = true; isDefault = setDefault; titleField.focus = true; @@ -56,6 +58,11 @@ Window { forceActiveFocus(); } + function acceptAndClose() { + close(); + accepted(); + } + function close() { visible = false; } @@ -70,335 +77,334 @@ Window { item.accounts = stateAccounts; return item; } - - ColumnLayout { - anchors.fill: parent - anchors.margins: 10 + contentItem: Rectangle { + color: stateDialogStyle.generic.backgroundColor ColumnLayout { - id: dialogContent - anchors.top: parent.top - - RowLayout - { - Layout.fillWidth: true - DefaultLabel { - Layout.preferredWidth: 75 - text: qsTr("Title") - } - DefaultTextField - { - id: titleField - Layout.fillWidth: true - } - } - - CommonSeparator - { - Layout.fillWidth: true - } - - RowLayout - { - Layout.fillWidth: true - - Rectangle - { - Layout.preferredWidth: 75 - DefaultLabel { - id: accountsLabel - Layout.preferredWidth: 75 - text: qsTr("Accounts") + anchors.fill: parent + ColumnLayout { + anchors.fill: parent + anchors.margins: 10 + ColumnLayout { + id: dialogContent + anchors.top: parent.top + RowLayout + { + Layout.fillWidth: true + DefaultLabel { + Layout.preferredWidth: 75 + text: qsTr("Title") + } + DefaultTextField + { + id: titleField + Layout.fillWidth: true + } } - Button + CommonSeparator { - anchors.top: accountsLabel.bottom - anchors.topMargin: 10 - iconSource: "qrc:/qml/img/plus.png" - action: newAccountAction + Layout.fillWidth: true } - Action { - id: newAccountAction - tooltip: qsTr("Add new Account") - onTriggered: - { - var account = stateListModel.newAccount("1000000", QEther.Ether); - stateAccounts.push(account); - accountsModel.append(account); - } - } - } + RowLayout + { + Layout.fillWidth: true - MessageDialog - { - id: alertAlreadyUsed - text: qsTr("This account is in use. You cannot remove it. The first account is used to deploy config contract and cannot be removed.") - icon: StandardIcon.Warning - standardButtons: StandardButton.Ok - } + Rectangle + { + Layout.preferredWidth: 75 + DefaultLabel { + id: accountsLabel + Layout.preferredWidth: 75 + text: qsTr("Accounts") + } - TableView - { - id: accountsView - Layout.fillWidth: true - model: accountsModel - headerVisible: false - TableViewColumn { - role: "name" - title: qsTr("Name") - width: 150 - delegate: Item { - RowLayout + Button { - height: 25 - width: parent.width - Button + anchors.top: accountsLabel.bottom + anchors.topMargin: 10 + iconSource: "qrc:/qml/img/plus.png" + action: newAccountAction + } + + Action { + id: newAccountAction + tooltip: qsTr("Add new Account") + onTriggered: { - iconSource: "qrc:/qml/img/delete_sign.png" - action: deleteAccountAction + var account = stateListModel.newAccount("1000000", QEther.Ether); + stateAccounts.push(account); + accountsModel.append(account); } + } + } + + MessageDialog + { + id: alertAlreadyUsed + text: qsTr("This account is in use. You cannot remove it. The first account is used to deploy config contract and cannot be removed.") + icon: StandardIcon.Warning + standardButtons: StandardButton.Ok + } - Action { - id: deleteAccountAction - tooltip: qsTr("Delete Account") - onTriggered: + TableView + { + id: accountsView + Layout.fillWidth: true + model: accountsModel + headerVisible: false + TableViewColumn { + role: "name" + title: qsTr("Name") + width: 150 + delegate: Item { + RowLayout { - if (transactionsModel.isUsed(stateAccounts[styleData.row].secret)) - alertAlreadyUsed.open(); - else + height: 25 + width: parent.width + Button { - stateAccounts.splice(styleData.row, 1); - accountsModel.remove(styleData.row); + iconSource: "qrc:/qml/img/delete_sign.png" + action: deleteAccountAction + } + + Action { + id: deleteAccountAction + tooltip: qsTr("Delete Account") + onTriggered: + { + if (transactionsModel.isUsed(stateAccounts[styleData.row].secret)) + alertAlreadyUsed.open(); + else + { + stateAccounts.splice(styleData.row, 1); + accountsModel.remove(styleData.row); + } + } + } + + DefaultTextField { + anchors.verticalCenter: parent.verticalCenter + onTextChanged: { + if (styleData.row > -1) + stateAccounts[styleData.row].name = text; + } + text: { + return styleData.value + } } } } + } - DefaultTextField { - anchors.verticalCenter: parent.verticalCenter - onTextChanged: { - if (styleData.row > -1) - stateAccounts[styleData.row].name = text; - } - text: { - return styleData.value + TableViewColumn { + role: "balance" + title: qsTr("Balance") + width: 200 + delegate: Item { + Ether { + id: balanceField + edit: true + displayFormattedValue: false + value: styleData.value } } } + rowDelegate: + Rectangle { + color: styleData.alternate ? "transparent" : "#f0f0f0" + height: 30; + } } } - TableViewColumn { - role: "balance" - title: qsTr("Balance") - width: 200 - delegate: Item { - Ether { - id: balanceField - edit: true - displayFormattedValue: false - value: styleData.value - } + CommonSeparator + { + Layout.fillWidth: true + } + + RowLayout + { + Layout.fillWidth: true + DefaultLabel { + Layout.preferredWidth: 75 + text: qsTr("Default") + } + CheckBox { + id: defaultCheckBox + Layout.fillWidth: true } } - rowDelegate: - Rectangle { - color: styleData.alternate ? "transparent" : "#f0f0f0" - height: 30; + + CommonSeparator + { + Layout.fillWidth: true } } - } - CommonSeparator - { - Layout.fillWidth: true - } + ColumnLayout { + anchors.top: dialogContent.bottom + anchors.topMargin: 5 + spacing: 0 + RowLayout + { + Layout.preferredWidth: 150 + DefaultLabel { + text: qsTr("Transactions: ") + } - RowLayout - { - Layout.fillWidth: true - DefaultLabel { - Layout.preferredWidth: 75 - text: qsTr("Default") - } - CheckBox { - id: defaultCheckBox - Layout.fillWidth: true - } - } + Button + { + iconSource: "qrc:/qml/img/plus.png" + action: newTrAction + width: 10 + height: 10 + anchors.right: parent.right + } - CommonSeparator - { - Layout.fillWidth: true - } - } + Action { + id: newTrAction + tooltip: qsTr("Create a new transaction") + onTriggered: transactionsModel.addTransaction() + } + } - ColumnLayout { - anchors.top: dialogContent.bottom - anchors.topMargin: 5 - spacing: 0 - RowLayout - { - Layout.preferredWidth: 150 - DefaultLabel { - text: qsTr("Transactions: ") + ScrollView + { + Layout.fillHeight: true + Layout.preferredWidth: 300 + Column + { + Layout.fillHeight: true + Repeater + { + id: trRepeater + model: transactionsModel + delegate: transactionRenderDelegate + visible: transactionsModel.count > 0 + height: 20 * transactionsModel.count + } + } + } } - Button + RowLayout { - iconSource: "qrc:/qml/img/plus.png" - action: newTrAction - width: 10 - height: 10 - anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.right: parent.right; + + Button { + text: qsTr("OK"); + onClicked: { + close(); + accepted(); + } + } + Button { + text: qsTr("Cancel"); + onClicked: close(); + } } - Action { - id: newTrAction - tooltip: qsTr("Create a new transaction") - onTriggered: transactionsModel.addTransaction() - } - } + ListModel { + id: accountsModel - ScrollView - { - Layout.fillHeight: true - Layout.preferredWidth: 300 - Column - { - Layout.fillHeight: true - Repeater + function removeAccount(_i) { - id: trRepeater - model: transactionsModel - delegate: transactionRenderDelegate - visible: transactionsModel.count > 0 - height: 20 * transactionsModel.count + accountsModel.remove(_i); + stateAccounts.splice(_i, 1); } } - } - - CommonSeparator - { - Layout.fillWidth: true - } - } - - RowLayout - { - anchors.bottom: parent.bottom - anchors.right: parent.right; - - Button { - text: qsTr("OK"); - onClicked: { - close(); - accepted(); - } - } - Button { - text: qsTr("Cancel"); - onClicked: close(); - } - } - } - - ListModel { - id: accountsModel - function removeAccount(_i) - { - accountsModel.remove(_i); - stateAccounts.splice(_i, 1); - } - } + ListModel { + id: transactionsModel - ListModel { - id: transactionsModel + function editTransaction(index) { + transactionDialog.stateAccounts = stateAccounts; + transactionDialog.open(index, transactionsModel.get(index)); + } - function editTransaction(index) { - transactionDialog.stateAccounts = stateAccounts; - transactionDialog.open(index, transactionsModel.get(index)); - } + function addTransaction() { - function addTransaction() { + // Set next id here to work around Qt bug + // https://bugreports.qt-project.org/browse/QTBUG-41327 + // Second call to signal handler would just edit the item that was just created, no harm done + var item = TransactionHelper.defaultTransaction(); + transactionDialog.stateAccounts = stateAccounts; + transactionDialog.open(transactionsModel.count, item); + } - // Set next id here to work around Qt bug - // https://bugreports.qt-project.org/browse/QTBUG-41327 - // Second call to signal handler would just edit the item that was just created, no harm done - var item = TransactionHelper.defaultTransaction(); - transactionDialog.stateAccounts = stateAccounts; - transactionDialog.open(transactionsModel.count, item); - } + function deleteTransaction(index) { + stateTransactions.splice(index, 1); + transactionsModel.remove(index); + } - function deleteTransaction(index) { - stateTransactions.splice(index, 1); - transactionsModel.remove(index); - } + function isUsed(secret) + { + for (var i in stateTransactions) + { + if (stateTransactions[i].sender === secret) + return true; + } + return false; + } + } - function isUsed(secret) - { - for (var i in stateTransactions) - { - if (stateTransactions[i].sender === secret) - return true; - } - return false; - } - } + Component { + id: transactionRenderDelegate + RowLayout { + DefaultLabel { + Layout.preferredWidth: 150 + text: functionId + } - Component { - id: transactionRenderDelegate - RowLayout { - DefaultLabel { - Layout.preferredWidth: 150 - text: functionId - } + Button + { + id: deleteBtn + iconSource: "qrc:/qml/img/delete_sign.png" + action: deleteAction + width: 10 + height: 10 + Action { + id: deleteAction + tooltip: qsTr("Delete") + onTriggered: transactionsModel.deleteTransaction(index) + } + } - Button - { - id: deleteBtn - iconSource: "qrc:/qml/img/delete_sign.png" - action: deleteAction - width: 10 - height: 10 - Action { - id: deleteAction - tooltip: qsTr("Delete") - onTriggered: transactionsModel.deleteTransaction(index) + Button + { + iconSource: "qrc:/qml/img/edit.png" + action: editAction + visible: stdContract === false + width: 10 + height: 10 + Action { + id: editAction + tooltip: qsTr("Edit") + onTriggered: transactionsModel.editTransaction(index) + } + } + } } - } - Button - { - iconSource: "qrc:/qml/img/edit.png" - action: editAction - visible: !stdContract - width: 10 - height: 10 - Action { - id: editAction - tooltip: qsTr("Edit") - onTriggered: transactionsModel.editTransaction(index) + TransactionDialog + { + id: transactionDialog + onAccepted: + { + var item = transactionDialog.getItem(); + + if (transactionDialog.transactionIndex < transactionsModel.count) { + transactionsModel.set(transactionDialog.transactionIndex, item); + stateTransactions[transactionDialog.transactionIndex] = item; + } else { + transactionsModel.append(item); + stateTransactions.push(item); + } + } } } } } - - TransactionDialog - { - id: transactionDialog - onAccepted: - { - var item = transactionDialog.getItem(); - - if (transactionDialog.transactionIndex < transactionsModel.count) { - transactionsModel.set(transactionDialog.transactionIndex, item); - stateTransactions[transactionDialog.transactionIndex] = item; - } else { - transactionsModel.append(item); - stateTransactions.push(item); - } - } - } } 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/StateList.qml b/mix/qml/StateList.qml index 2e1bb4a06..39567feac 100644 --- a/mix/qml/StateList.qml +++ b/mix/qml/StateList.qml @@ -1,39 +1,50 @@ import QtQuick 2.2 import QtQuick.Controls 1.1 import QtQuick.Controls.Styles 1.1 -import QtQuick.Dialogs 1.1 +import QtQuick.Dialogs 1.2 import QtQuick.Layouts 1.1 import QtQuick.Window 2.0 import "." -Window { +Dialog { id: stateListContainer modality: Qt.WindowModal - width: 640 height: 480 - visible: false - ColumnLayout - { + contentItem: Rectangle { anchors.fill: parent - TableView { - id: list - Layout.fillHeight: true - Layout.fillWidth: true - model: projectModel.stateListModel - itemDelegate: renderDelegate - headerDelegate: null - TableViewColumn { - role: "title" - title: qsTr("State") - width: list.width + ColumnLayout + { + anchors.fill: parent + TableView { + id: list + Layout.fillHeight: true + Layout.fillWidth: true + model: projectModel.stateListModel + itemDelegate: renderDelegate + headerDelegate: null + frameVisible: false + TableViewColumn { + role: "title" + title: qsTr("State") + width: list.width + } } - } - Button { - anchors.bottom: parent.bottom - action: addStateAction + Row{ + spacing: 5 + anchors.bottom: parent.bottom + anchors.right: parent.right + anchors.rightMargin: 10 + Button { + action: addStateAction + } + + Button { + action: closeAction + } + } } } @@ -69,12 +80,21 @@ Window { } } - Action { - id: addStateAction - text: "&Add State" - shortcut: "Ctrl+T" - enabled: codeModel.hasContract && !clientModel.running; - onTriggered: list.model.addState(); + Row + { + Action { + id: addStateAction + text: qsTr("Add State") + shortcut: "Ctrl+T" + enabled: codeModel.hasContract && !clientModel.running; + onTriggered: list.model.addState(); + } + + Action { + id: closeAction + text: qsTr("Close") + onTriggered: stateListContainer.close(); + } } } diff --git a/mix/qml/StateListModel.qml b/mix/qml/StateListModel.qml index 61307826c..c90692572 100644 --- a/mix/qml/StateListModel.qml +++ b/mix/qml/StateListModel.qml @@ -6,12 +6,15 @@ import QtQuick.Window 2.2 import QtQuick.Layouts 1.1 import org.ethereum.qml.QEther 1.0 import "js/QEtherHelper.js" as QEtherHelper +import "js/TransactionHelper.js" as TransactionHelper Item { property alias model: stateListModel property var stateList: [] + property alias stateDialog: stateDialog property string defaultAccount: "cb73d9408c4720e230387d956eb0f829d8a4dd2c1055f96257167e14e7169074" //support for old project + function fromPlainStateItem(s) { if (!s.accounts) s.accounts = [stateListModel.newAccount("1000000", QEther.Ether, defaultAccount)]; //support for old project @@ -41,38 +44,13 @@ Item { value: QEtherHelper.createEther(t.value.value, t.value.unit), gas: QEtherHelper.createBigInt(t.gas.value), gasPrice: QEtherHelper.createEther(t.gasPrice.value, t.gasPrice.unit), - stdContract: t.stdContract, + stdContract: t.stdContract ? true : false, parameters: {}, sender: t.sender }; - var qType = []; for (var key in t.parameters) - { - r.parameters[key] = t.parameters[key].value; - var type = t.parameters[key].type; - var varComponent; - if (type.indexOf("int") !== -1) - varComponent = Qt.createComponent("qrc:/qml/QIntType.qml"); - else if (type.indexOf("real") !== -1) - varComponent = Qt.createComponent("qrc:/qml/QRealType.qml"); - else if (type.indexOf("string") !== -1 || type.indexOf("text") !== -1) - varComponent = Qt.createComponent("qrc:/qml/QStringType.qml"); - else if (type.indexOf("hash") !== -1 || type.indexOf("address") !== -1) - varComponent = Qt.createComponent("qrc:/qml/QHashType.qml"); - else if (type.indexOf("bool") !== -1) - varComponent = Qt.createComponent("qrc:/qml/QBoolType.qml"); - else { - console.log("Unknown parameter type: " + type); - continue; - } + r.parameters[key] = t.parameters[key]; - var param = varComponent.createObject(stateListModel); - var dec = Qt.createComponent("qrc:/qml/QVariableDeclaration.qml"); - param.setDeclaration(dec.createObject(stateListModel, { "type": type })); - param.setValue(r.parameters[key]); - qType.push(param); - } - r.qType = qType; return r; } @@ -118,14 +96,7 @@ Item { parameters: {} }; for (var key in t.parameters) - { - var param = { - name: key, - value: t.parameters[key], - type: getParamType(key, t.qType) - } - r.parameters[key] = param; - } + r.parameters[key] = t.parameters[key]; return r; } @@ -134,9 +105,10 @@ Item { onProjectClosed: { stateListModel.clear(); stateList = []; + codeModel.reset(); } onProjectLoading: stateListModel.loadStatesFromProject(projectData); - onProjectSaving: { + onProjectFileSaving: { projectData.states = [] for(var i = 0; i < stateListModel.count; i++) { projectData.states.push(toPlainStateItem(stateList[i])); @@ -148,6 +120,17 @@ Item { state.title = qsTr("Default"); projectData.states = [ state ]; projectData.defaultStateIndex = 0; + stateListModel.loadStatesFromProject(projectData); + } + } + + Connections { + target: codeModel + onNewContractCompiled: { + stateListModel.addNewContracts(); + } + onContractRenamed: { + stateListModel.renameContracts(_oldName, _newName); } } @@ -184,12 +167,7 @@ Item { signal stateRun(int index) function defaultTransactionItem() { - return { - value: QEtherHelper.createEther("100", QEther.Wei), - gas: QEtherHelper.createBigInt("125000"), - gasPrice: QEtherHelper.createEther("10000000000000", QEther.Wei), - stdContract: false - }; + return TransactionHelper.defaultTransaction(); } function newAccount(_balance, _unit, _secret) @@ -232,6 +210,57 @@ Item { return item; } + function renameContracts(oldName, newName) { + var changed = false; + for(var c in codeModel.contracts) { + for (var s = 0; s < stateListModel.count; s++) { + var state = stateList[s]; + for (var t = 0; t < state.transactions.length; t++) { + var transaction = state.transactions[t]; + if (transaction.contractId === oldName) { + transaction.contractId = newName; + if (transaction.functionId === oldName) + transaction.functionId = newName; + changed = true; + state.transactions[t] = transaction; + } + } + stateListModel.set(s, state); + stateList[s] = state; + } + } + if (changed) + save(); + } + + function addNewContracts() { + //add new contracts for all states + var changed = false; + for(var c in codeModel.contracts) { + for (var s = 0; s < stateListModel.count; s++) { + var state = stateList[s]; + for (var t = 0; t < state.transactions.length; t++) { + var transaction = state.transactions[t]; + if (transaction.functionId === c && transaction.contractId === c) + break; + } + if (t === state.transactions.length) { + //append this contract + var ctorTr = defaultTransactionItem(); + ctorTr.functionId = c; + ctorTr.contractId = c; + ctorTr.sender = state.accounts[0].secret; + state.transactions.push(ctorTr); + changed = true; + stateListModel.set(s, state); + stateList[s] = state; + } + } + } + if (changed) + save(); + } + function addState() { var item = createDefaultState(); stateDialog.open(stateListModel.count, item, false); @@ -264,6 +293,7 @@ Item { defaultStateIndex--; save(); + } function save() { @@ -284,6 +314,8 @@ Item { else defaultStateIndex = 0; var items = projectData.states; + stateListModel.clear(); + stateList = []; for(var i = 0; i < items.length; i++) { var item = fromPlainStateItem(items[i]); stateListModel.append(item); diff --git a/mix/qml/StatesComboBox.qml b/mix/qml/StatesComboBox.qml new file mode 100644 index 000000000..bc7a4853d --- /dev/null +++ b/mix/qml/StatesComboBox.qml @@ -0,0 +1,260 @@ +/* + 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 StatesComboBox.qml + * @author Ali Mashatan ali@ethdev.com + * @date 2015 + * Ethereum IDE client. + */ + +import QtQuick 2.0 +import QtQuick.Controls 1.0 +import QtQuick.Layouts 1.1 +import QtGraphicalEffects 1.0 + +Rectangle { + id: statesComboBox + + width: 200 + height: 23 + + Component.onCompleted: { + var top = dropDownList + while (top.parent) { + top = top.parent + if (top.objectName == "debugPanel") + break + } + var coordinates = dropDownList.mapToItem(top, 0, 0) + //the order is important + dropDownShowdowList.parent = top + dropDownList.parent = top + + dropDownShowdowList.x = coordinates.x + dropDownShowdowList.y = coordinates.y + + dropDownList.x = coordinates.x + dropDownList.y = coordinates.y + } + + signal selectItem(real item) + signal editItem(real item) + signal selectCreate + property int rowHeight: 25 + property variant items + property alias selectedItem: chosenItemText.text + property alias selectedIndex: listView.currentRow + function setSelectedIndex(index) { + listView.currentRow = index + chosenItemText.text = statesComboBox.items.get(index).title + } + + signal comboClicked + + property variant colorItem + property variant colorSelect + + SourceSansProRegular + { + id: regularFont + } + + SourceSansProBold + { + id: boldFont + } + + smooth: true + Rectangle { + id: chosenItem + width: parent.width + height: statesComboBox.height + color: statesComboBox.color + + Text { + id: chosenItemText + anchors.left: parent.left + anchors.leftMargin: 10 + anchors.verticalCenter: parent.verticalCenter + color: statesComboBox.colorItem + text: "" + font.family: regularFont.name + } + + MouseArea { + anchors.fill: parent + onClicked: { + statesComboBox.state = statesComboBox.state === "dropDown" ? "" : "dropDown" + } + } + } + + Rectangle { + id: dropDownShowdowList + width: statesComboBox.width + opacity: 0.3 + height: 0 + clip: true + radius: 4 + anchors.top: chosenItem.top + anchors.margins: 2 + color: "gray" + } + //ToDo: We need scrollbar for items + Rectangle { + id: dropDownList + width: statesComboBox.width + height: 0 + clip: true + radius: 4 + anchors.top: chosenItem.top + anchors.topMargin: 23 + color: statesComboBox.color + + ColumnLayout { + spacing: 2 + TableView { + id: listView + height: 20 + implicitHeight: 0 + width: statesComboBox.width + model: statesComboBox.items + horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff + currentRow: -1 + headerVisible: false + backgroundVisible: false + alternatingRowColors: false + frameVisible: false + + TableViewColumn { + role: "title" + title: "" + width: statesComboBox.width + delegate: mainItemDelegate + } + rowDelegate: Rectangle { + width: statesComboBox.width + height: statesComboBox.rowHeight + } + Component { + id: mainItemDelegate + Rectangle { + id: itemDelegate + width: statesComboBox.width + height: statesComboBox.height + Text { + id: textItemid + text: styleData.value + color: statesComboBox.colorItem + anchors.top: parent.top + anchors.left: parent.left + anchors.leftMargin: 10 + anchors.topMargin: 5 + font.family: regularFont.name + } + Image { + id: imageItemid + height: 20 + width: 20 + anchors.right: parent.right + anchors.top: parent.top + anchors.margins: 5 + visible: false + fillMode: Image.PreserveAspectFit + source: "img/edit_combox.png" + } + + MouseArea { + anchors.fill: parent + hoverEnabled: true + + onEntered: { + imageItemid.visible = true + textItemid.color = statesComboBox.colorSelect + } + onExited: { + imageItemid.visible = false + textItemid.color = statesComboBox.colorItem + } + onClicked: { + if (mouseX > imageItemid.x + && mouseX < imageItemid.x + imageItemid.width + && mouseY > imageItemid.y + && mouseY < imageItemid.y + imageItemid.height) + statesComboBox.editItem(styleData.row) + else { + statesComboBox.state = "" + var prevSelection = chosenItemText.text + chosenItemText.text = styleData.value + listView.currentRow = styleData.row + statesComboBox.selectItem(styleData.row) + } + } + } + } //Item + } //Component + } //Table View + + RowLayout { + anchors.top: listView.bottom + anchors.topMargin: 4 + anchors.left: parent.left + anchors.leftMargin: 10 + Text { + id: createStateText + width: statesComboBox.width + height: statesComboBox.height + font.family: boldFont.name + color: "#808080" + text: qsTr("Create State ...") + font.weight: Font.DemiBold + MouseArea { + anchors.fill: parent + hoverEnabled: true + + onEntered: { + createStateText.color = statesComboBox.colorSelect + } + onExited: { + createStateText.color = statesComboBox.colorItem + } + onClicked: { + statesComboBox.state = "" + statesComboBox.selectCreate() + } + } + } + } + } + } + states: State { + name: "dropDown" + PropertyChanges { + target: dropDownList + height: (statesComboBox.rowHeight * (statesComboBox.items.count + 1)) + } + PropertyChanges { + target: dropDownShowdowList + width: statesComboBox.width + 3 + height: (statesComboBox.rowHeight * (statesComboBox.items.count + 1)) + 3 + } + PropertyChanges { + target: listView + height: 20 + implicitHeight: (statesComboBox.rowHeight * (statesComboBox.items.count)) + } + } +} diff --git a/mix/qml/StatusPane.qml b/mix/qml/StatusPane.qml index e526d65bd..72d9ded9b 100644 --- a/mix/qml/StatusPane.qml +++ b/mix/qml/StatusPane.qml @@ -8,54 +8,107 @@ import "." Rectangle { id: statusHeader objectName: "statusPane" - + property variant webPreview + property alias currentStatus: logPane.currentStatus function updateStatus(message) { if (!message) { status.state = ""; status.text = qsTr("Compile successfully."); - logslink.visible = false; debugImg.state = "active"; + currentStatus = { "type": "Comp", "date": Qt.formatDateTime(new Date(), "hh:mm:ss"), "content": status.text, "level": "info" } } else { status.state = "error"; var errorInfo = ErrorLocationFormater.extractErrorInfo(message, true); status.text = errorInfo.errorLocation + " " + errorInfo.errorDetail; - logslink.visible = true; debugImg.state = ""; + currentStatus = { "type": "Comp", "date": Qt.formatDateTime(new Date(), "hh:mm:ss"), "content": status.text, "level": "error" } } - debugRunActionIcon.enabled = codeModel.hasContract; } - function infoMessage(text) + function infoMessage(text, type) { status.state = ""; status.text = text - logslink.visible = false; + logPane.push("info", type, text); + currentStatus = { "type": type, "date": Qt.formatDateTime(new Date(), "hh:mm:ss"), "content": text, "level": "info" } } - function errorMessage(text) + function warningMessage(text, type) { - status.state = "error"; + status.state = "warning"; status.text = text - logslink.visible = false; + logPane.push("warning", type, text); + currentStatus = { "type": type, "date": Qt.formatDateTime(new Date(), "hh:mm:ss"), "content": text, "level": "warning" } + } + + function errorMessage(text, type) + { + status.state = "error"; + status.text = text; + logPane.push("error", type, text); + currentStatus = { "type": type, "date": Qt.formatDateTime(new Date(), "hh:mm:ss"), "content": text, "level": "error" } + } + + StatusPaneStyle { + id: statusPaneStyle + } + + Connections { + target: webPreview + onJavaScriptMessage: + { + if (_level === 0) + infoMessage(_content, "JavaScript") + else + { + var message = _sourceId.substring(_sourceId.lastIndexOf("/") + 1) + " - " + qsTr("line") + " " + _lineNb + " - " + _content; + if (_level === 1) + warningMessage(message, "JavaScript") + else + errorMessage(message, "JavaScript") + } + } } Connections { target:clientModel - onRunStarted: infoMessage(qsTr("Running transactions...")); - onRunFailed: errorMessage(qsTr("Error running transactions: " + _message)); - onRunComplete: infoMessage(qsTr("Run complete")); - onNewBlock: infoMessage(qsTr("New block created")); + onRunStarted: + { + logPane.clear() + infoMessage(qsTr("Running transactions..."), "Run"); + } + onRunFailed: errorMessage(format(_message), "Run"); + onRunComplete: infoMessage(qsTr("Run complete"), "Run"); + onNewBlock: infoMessage(qsTr("New block created"), "State"); + + function format(_message) + { + var formatted = _message.match(/(?:)/); + if (!formatted) + formatted = _message.match(/(?:)/); + if (formatted && formatted.length > 1) + formatted = formatted[1]; + else + return _message; + var exceptionInfos = _message.match(/(?:tag_)(.+)/g); + if (exceptionInfos !== null && exceptionInfos.length > 0) + formatted += ": " + for (var k in exceptionInfos) + formatted += " " + exceptionInfos[k].replace("*]", "").replace("tag_", "").replace("=", ""); + return formatted; + } } + Connections { target:projectModel - onDeploymentStarted: infoMessage(qsTr("Running deployment...")); - onDeploymentError: errorMessage(error); - onDeploymentComplete: infoMessage(qsTr("Deployment complete")); - onDeploymentStepChanged: infoMessage(message); + onDeploymentStarted: infoMessage(qsTr("Running deployment..."), "Deployment"); + onDeploymentError: errorMessage(error, "Deployment"); + onDeploymentComplete: infoMessage(qsTr("Deployment complete"), "Deployment"); + onDeploymentStepChanged: infoMessage(message, "Deployment"); } Connections { target: codeModel @@ -71,14 +124,13 @@ Rectangle { anchors.horizontalCenter: parent.horizontalCenter anchors.verticalCenter: parent.verticalCenter radius: 3 - width: 500 + width: 600 height: 30 color: "#fcfbfc" - Text { anchors.verticalCenter: parent.verticalCenter anchors.horizontalCenter: parent.horizontalCenter - font.pointSize: StatusPaneStyle.general.statusFontSize + font.pointSize: appStyle.absoluteSize(-1) height: 15 font.family: "sans serif" objectName: "status" @@ -98,6 +150,17 @@ Rectangle { target: statusContainer color: "#fffcd5" } + }, + State { + name: "warning" + PropertyChanges { + target: status + color: "orange" + } + PropertyChanges { + target: statusContainer + color: "#fffcd5" + } } ] onTextChanged: @@ -127,30 +190,77 @@ Rectangle { color: "transparent" } } + MouseArea { + anchors.fill: parent + onClicked: { + logsContainer.toggle(); + } + } } Action { id: toolTipInfo tooltip: "" } - } - Button - { - id: logslink - anchors.left: statusContainer.right - anchors.leftMargin: 9 - visible: false - anchors.verticalCenter: parent.verticalCenter - action: displayLogAction - iconSource: "qrc:/qml/img/search_filled.png" - } + Rectangle + { + function toggle() + { + if (logsContainer.state === "opened") + { + logsContainer.state = "closed" + } + else + { + logsContainer.state = "opened"; + logsContainer.focus = true; + forceActiveFocus(); + calCoord(); + } + } + + id: logsContainer + width: 750 + anchors.top: statusContainer.bottom + anchors.topMargin: 4 + visible: false + radius: 10 + + function calCoord() + { + var top = logsContainer; + while (top.parent) + top = top.parent + var coordinates = logsContainer.mapToItem(top, 0, 0); + logsContainer.parent = top; + logsContainer.x = status.x + statusContainer.x - logStyle.generic.layout.dateWidth - logStyle.generic.layout.typeWidth + 70 + } + + LogsPaneStyle { + id: logStyle + } - Action { - id: displayLogAction - tooltip: qsTr("Display Log") - onTriggered: { - mainContent.displayCompilationErrorIfAny(); + LogsPane + { + id: logPane; + } + + states: [ + State { + name: "opened"; + PropertyChanges { target: logsContainer; height: 500; visible: true } + }, + State { + name: "closed"; + PropertyChanges { target: logsContainer; height: 0; visible: false } + PropertyChanges { target: statusContainer; width: 600; height: 30 } + } + ] + transitions: Transition { + NumberAnimation { properties: "height"; easing.type: Easing.InOutQuad; duration: 200 } + NumberAnimation { target: logsContainer; properties: "visible"; easing.type: Easing.InOutQuad; duration: 200 } + } } } @@ -159,8 +269,8 @@ Rectangle { color: "transparent" width: 100 height: parent.height - anchors.top: statusHeader.top - anchors.right: statusHeader.right + anchors.top: parent.top + anchors.right: parent.right RowLayout { anchors.fill: parent @@ -168,31 +278,15 @@ Rectangle { { color: "transparent" anchors.fill: parent - Button { anchors.right: parent.right anchors.rightMargin: 9 anchors.verticalCenter: parent.verticalCenter id: debugImg - iconSource: "qrc:/qml/img/bugiconinactive.png" - action: debugRunActionIcon - states: [ - State{ - name: "active" - PropertyChanges { target: debugImg; iconSource: "qrc:/qml/img/bugiconactive.png"} - } - ] - } - Action { - id: debugRunActionIcon - onTriggered: { - if (mainContent.rightViewIsVisible()) - mainContent.hideRightView() - else - mainContent.startQuickDebugging(); - } - enabled: false + text: "" + iconSource: "qrc:/qml/img/bugiconactive.png" + action: showHideRightPanelAction } } } 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/StepActionImage.qml b/mix/qml/StepActionImage.qml index e5129e379..262c99def 100644 --- a/mix/qml/StepActionImage.qml +++ b/mix/qml/StepActionImage.qml @@ -6,6 +6,7 @@ import QtQuick.Controls.Styles 1.1 Rectangle { id: buttonActionContainer + color: "transparent" property string disableStateImg property string enabledStateImg property string buttonTooltip diff --git a/mix/qml/StorageView.qml b/mix/qml/StorageView.qml new file mode 100644 index 000000000..ecd64ccf8 --- /dev/null +++ b/mix/qml/StorageView.qml @@ -0,0 +1,69 @@ +import QtQuick 2.2 +import QtQuick.Controls 1.1 +import QtQuick.Controls.Styles 1.1 +import QtQuick.Layouts 1.1 +import "." + +DebugInfoList +{ + id: storage + collapsible: true + title : qsTr("Storage") + itemDelegate: + Item { + anchors.fill: parent + RowLayout + { + id: row + anchors.fill: parent + Rectangle + { + color: "#f7f7f7" + Layout.fillWidth: true + Layout.minimumWidth: parent.width / 2 + Layout.maximumWidth: parent.width / 2 + Text { + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + font.family: "monospace" + anchors.leftMargin: 5 + color: "#4a4a4a" + text: styleData.value.split('\t')[0]; + font.pointSize: dbgStyle.general.basicFontSize + width: parent.width - 5 + elide: Text.ElideRight + } + } + Rectangle + { + color: "transparent" + Layout.fillWidth: true + Layout.minimumWidth: parent.width / 2 + Layout.maximumWidth: parent.width / 2 + Text { + maximumLineCount: 1 + clip: true + anchors.leftMargin: 5 + width: parent.width - 5 + wrapMode: Text.WrapAnywhere + anchors.left: parent.left + font.family: "monospace" + anchors.verticalCenter: parent.verticalCenter + color: "#4a4a4a" + text: styleData.value.split('\t')[1]; + elide: Text.ElideRight + font.pointSize: dbgStyle.general.basicFontSize + } + } + } + + Rectangle { + anchors.top: row.bottom + width: parent.width; + height: 1; + color: "#cccccc" + anchors.bottom: parent.bottom + } + } +} + diff --git a/mix/qml/StructView.qml b/mix/qml/StructView.qml new file mode 100644 index 000000000..2a0c5c68a --- /dev/null +++ b/mix/qml/StructView.qml @@ -0,0 +1,94 @@ +import QtQuick 2.0 +import QtQuick.Controls 1.1 +import QtQuick.Layouts 1.1 +import org.ethereum.qml.QSolidityType 1.0 + +Column +{ + id: root + property alias members: repeater.model //js array + property var value: ({}) + Layout.fillWidth: true + + Repeater + { + id: repeater + visible: model.length > 0 + Layout.fillWidth: true + RowLayout + { + id: row + height: 20 + (members[index].type.category === QSolidityType.Struct ? (20 * members[index].type.members.length) : 0) + Layout.fillWidth: true + DefaultLabel { + height: 20 + id: typeLabel + text: modelData.type.name + anchors.verticalCenter: parent.verticalCenter + } + + DefaultLabel { + id: nameLabel + text: modelData.name + anchors.verticalCenter: parent.verticalCenter + } + + DefaultLabel { + id: equalLabel + text: "=" + anchors.verticalCenter: parent.verticalCenter + } + Loader + { + id: typeLoader + anchors.verticalCenter: parent.verticalCenter + sourceComponent: + { + var t = modelData.type.category; + if (t === QSolidityType.SignedInteger || t === QSolidityType.UnsignedInteger) + return Qt.createComponent("qrc:/qml/QIntTypeView.qml"); + else if (t === QSolidityType.Bool) + return Qt.createComponent("qrc:/qml/QBoolTypeView.qml"); + else if (t === QSolidityType.Bytes) + return Qt.createComponent("qrc:/qml/QStringTypeView.qml"); + else if (t === QSolidityType.Hash || t === QSolidityType.Address) + return Qt.createComponent("qrc:/qml/QHashTypeView.qml"); + else if (t === QSolidityType.Struct) + return Qt.createComponent("qrc:/qml/StructView.qml"); + else + return undefined; + } + onLoaded: + { + var ptype = members[index].type; + var pname = members[index].name; + var vals = value; + if (ptype.category === QSolidityType.Struct && !item.members) + { + item.value = getValue(); + item.members = ptype.members; + } + else + item.value = getValue(); + + item.onValueChanged.connect(function() { + vals[pname] = item.value; + valueChanged(); + }); + } + + function getValue() + { + var r = ""; + if (value && value[modelData.name] !== undefined) + r = value[modelData.name]; + else if (modelData.type.category === QSolidityType.Struct) + r = {}; + if (Array.isArray(r)) + r = r.join(", "); + return r; + } + } + } + } +} 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 997f78779..f53e16a06 100644 --- a/mix/qml/TransactionDialog.qml +++ b/mix/qml/TransactionDialog.qml @@ -1,36 +1,37 @@ import QtQuick 2.2 import QtQuick.Controls 1.1 import QtQuick.Layouts 1.1 +import QtQuick.Dialogs 1.2 import QtQuick.Window 2.0 import QtQuick.Controls.Styles 1.3 import org.ethereum.qml.QEther 1.0 import "js/TransactionHelper.js" as TransactionHelper import "." -Window { +Dialog { id: modalTransactionDialog modality: Qt.ApplicationModal width: 520 - height: (paramsModel.count > 0 ? 500 : 300) + height: 500 visible: false - color: StateDialogStyle.generic.backgroundColor title: qsTr("Edit Transaction") property int transactionIndex - property alias transactionParams: paramsModel; property alias gas: gasValueEdit.gasValue; property alias gasPrice: gasPriceField.value; property alias transactionValue: valueField.value; property string contractId: contractComboBox.currentValue(); property alias functionId: functionComboBox.currentText; - property var itemParams; + property var paramValues; + property var paramsModel: []; property bool useTransactionDefaultValue: false - property var qType; property alias stateAccounts: senderComboBox.model - signal accepted; + StateDialogStyle { + id: transactionDialogStyle + } + function open(index, item) { - qType = []; rowFunction.visible = !useTransactionDefaultValue; rowValue.visible = !useTransactionDefaultValue; rowGas.visible = !useTransactionDefaultValue; @@ -44,7 +45,7 @@ Window { var functionId = item.functionId; rowFunction.visible = true; - itemParams = item.parameters !== undefined ? item.parameters : {}; + paramValues = item.parameters !== undefined ? item.parameters : {}; if (item.sender) senderComboBox.select(item.sender); @@ -62,30 +63,20 @@ Window { contractComboBox.currentIndex = contractIndex; loadFunctions(contractComboBox.currentValue()); + selectFunction(functionId); - 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; - - paramsModel.clear(); + paramsModel = []; if (functionId !== contractComboBox.currentValue()) loadParameters(); else { var contract = codeModel.contracts[contractId]; if (contract) { - var parameters = contract.contract.constructor.parameters; - for (var p = 0; p < parameters.length; p++) - loadParameter(parameters[p]); + var params = contract.contract.constructor.parameters; + for (var p = 0; p < params.length; p++) + loadParameter(params[p]); } } - modalTransactionDialog.setX((Screen.width - width) / 2); - modalTransactionDialog.setY((Screen.height - height) / 2); + initTypeLoader(); visible = true; valueField.focus = true; @@ -106,36 +97,28 @@ 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; var pname = parameter.name; - var varComponent; - - if (type.indexOf("int") !== -1) - varComponent = Qt.createComponent("qrc:/qml/QIntType.qml"); - else if (type.indexOf("real") !== -1) - varComponent = Qt.createComponent("qrc:/qml/QRealType.qml"); - else if (type.indexOf("string") !== -1 || type.indexOf("text") !== -1) - varComponent = Qt.createComponent("qrc:/qml/QStringType.qml"); - else if (type.indexOf("hash") !== -1 || type.indexOf("address") !== -1) - varComponent = Qt.createComponent("qrc:/qml/QHashType.qml"); - else if (type.indexOf("bool") !== -1) - varComponent = Qt.createComponent("qrc:/qml/QBoolType.qml"); - - var param = varComponent.createObject(modalTransactionDialog); - var value = itemParams[pname] !== undefined ? itemParams[pname] : ""; - - param.setValue(value); - param.setDeclaration(parameter); - qType.push({ name: pname, value: param }); - paramsModel.append({ name: pname, type: type, value: value }); + paramsModel.push({ name: pname, type: type }); } function loadParameters() { - paramsModel.clear(); - if (!paramsModel) - return; + paramsModel = [] if (functionComboBox.currentIndex >= 0 && functionComboBox.currentIndex < functionsModel.count) { var contract = codeModel.contracts[contractComboBox.currentValue()]; if (contract) { @@ -147,29 +130,29 @@ Window { } } } + initTypeLoader(); } - function param(name) + function initTypeLoader() { - for (var k = 0; k < paramsModel.count; k++) - { - if (paramsModel.get(k).name === name) - return paramsModel.get(k); - } + typeLoader.value = {} + 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 close() + function acceptAndClose() { - visible = false; + close(); + accepted(); } - function qTypeParam(name) + function close() { - for (var k in qType) - { - if (qType[k].name === name) - return qType[k].value; - } + visible = false; } function getItem() @@ -194,344 +177,222 @@ Window { } item.sender = senderComboBox.model[senderComboBox.currentIndex].secret; - var orderedQType = []; - for (var p = 0; p < transactionDialog.transactionParams.count; p++) { - var parameter = transactionDialog.transactionParams.get(p); - var qtypeParam = qTypeParam(parameter.name); - qtypeParam.setValue(parameter.value); - orderedQType.push(qtypeParam); - item.parameters[parameter.name] = parameter.value; - } - item.qType = orderedQType; + item.parameters = paramValues; return item; } - - ColumnLayout { - anchors.fill: parent - anchors.margins: 10 - + contentItem: Rectangle { + color: transactionDialogStyle.generic.backgroundColor ColumnLayout { - id: dialogContent - anchors.top: parent.top - spacing: 10 - RowLayout - { - id: rowSender - Layout.fillWidth: true - height: 150 - DefaultLabel { - Layout.preferredWidth: 75 - text: qsTr("Sender") - } - ComboBox { - - function select(secret) + anchors.fill: parent + ColumnLayout { + anchors.fill: parent + anchors.margins: 10 + + ColumnLayout { + id: dialogContent + anchors.top: parent.top + spacing: 10 + RowLayout { - for (var i in model) - if (model[i].secret === secret) + id: rowSender + Layout.fillWidth: true + height: 150 + DefaultLabel { + Layout.preferredWidth: 75 + text: qsTr("Sender") + } + ComboBox { + + function select(secret) { - currentIndex = i; - break; + for (var i in model) + if (model[i].secret === secret) + { + currentIndex = i; + break; + } } - } - - id: senderComboBox - Layout.preferredWidth: 350 - currentIndex: 0 - textRole: "name" - editable: false - } - } - RowLayout - { - id: rowContract - Layout.fillWidth: true - height: 150 - DefaultLabel { - Layout.preferredWidth: 75 - text: qsTr("Contract") - } - ComboBox { - id: contractComboBox - function currentValue() { - return (currentIndex >=0 && currentIndex < contractsModel.count) ? contractsModel.get(currentIndex).cid : ""; - } - Layout.preferredWidth: 350 - currentIndex: -1 - textRole: "text" - editable: false - model: ListModel { - id: contractsModel - } - onCurrentIndexChanged: { - loadFunctions(currentValue()); + id: senderComboBox + Layout.preferredWidth: 350 + currentIndex: 0 + textRole: "name" + editable: false + } } - } - } - RowLayout - { - id: rowFunction - Layout.fillWidth: true - height: 150 - DefaultLabel { - Layout.preferredWidth: 75 - text: qsTr("Function") - } - ComboBox { - id: functionComboBox - Layout.preferredWidth: 350 - currentIndex: -1 - textRole: "text" - editable: false - model: ListModel { - id: functionsModel + RowLayout + { + id: rowContract + Layout.fillWidth: true + height: 150 + DefaultLabel { + Layout.preferredWidth: 75 + text: qsTr("Contract") + } + ComboBox { + id: contractComboBox + function currentValue() { + return (currentIndex >=0 && currentIndex < contractsModel.count) ? contractsModel.get(currentIndex).cid : ""; + } + Layout.preferredWidth: 350 + currentIndex: -1 + textRole: "text" + editable: false + model: ListModel { + id: contractsModel + } + onCurrentIndexChanged: { + loadFunctions(currentValue()); + } + } } - onCurrentIndexChanged: { - loadParameters(); + + RowLayout + { + id: rowFunction + Layout.fillWidth: true + height: 150 + DefaultLabel { + Layout.preferredWidth: 75 + text: qsTr("Function") + } + ComboBox { + id: functionComboBox + Layout.preferredWidth: 350 + currentIndex: -1 + textRole: "text" + editable: false + model: ListModel { + id: functionsModel + } + onCurrentIndexChanged: { + loadParameters(); + } + } } - } - } - CommonSeparator - { - Layout.fillWidth: true - } + CommonSeparator + { + Layout.fillWidth: true + } - RowLayout - { - id: rowValue - Layout.fillWidth: true - height: 150 - DefaultLabel { - Layout.preferredWidth: 75 - text: qsTr("Value") - } - Ether { - id: valueField - edit: true - displayFormattedValue: true - } - } + RowLayout + { + id: rowValue + Layout.fillWidth: true + height: 150 + DefaultLabel { + Layout.preferredWidth: 75 + text: qsTr("Value") + } + Ether { + id: valueField + edit: true + displayFormattedValue: true + } + } - CommonSeparator - { - Layout.fillWidth: true - } + CommonSeparator + { + Layout.fillWidth: true + } - RowLayout - { - id: rowGas - Layout.fillWidth: true - height: 150 - DefaultLabel { - Layout.preferredWidth: 75 - text: qsTr("Gas") - } + RowLayout + { + id: rowGas + Layout.fillWidth: true + height: 150 + DefaultLabel { + Layout.preferredWidth: 75 + text: qsTr("Gas") + } - DefaultTextField - { - property variant gasValue - onGasValueChanged: text = gasValue.value(); - onTextChanged: gasValue.setValue(text); - implicitWidth: 200 - id: gasValueEdit; - } - } + DefaultTextField + { + property variant gasValue + onGasValueChanged: text = gasValue.value(); + onTextChanged: gasValue.setValue(text); + implicitWidth: 200 + id: gasValueEdit; + } + } - CommonSeparator - { - Layout.fillWidth: true - } + CommonSeparator + { + Layout.fillWidth: true + } - RowLayout - { - id: rowGasPrice - Layout.fillWidth: true - height: 150 - DefaultLabel { - Layout.preferredWidth: 75 - text: qsTr("Gas Price") - } - Ether { - id: gasPriceField - edit: true - displayFormattedValue: true - } - } + RowLayout + { + id: rowGasPrice + Layout.fillWidth: true + height: 150 + DefaultLabel { + Layout.preferredWidth: 75 + text: qsTr("Gas Price") + } + Ether { + id: gasPriceField + edit: true + displayFormattedValue: true + } + } - CommonSeparator - { - Layout.fillWidth: true - } + CommonSeparator + { + Layout.fillWidth: true + } - DefaultLabel { - id: paramLabel - text: qsTr("Parameters:") - Layout.preferredWidth: 75 - visible: paramsModel.count > 0 - } + DefaultLabel { + id: paramLabel + text: qsTr("Parameters:") + Layout.preferredWidth: 75 + } - ScrollView - { - anchors.top: paramLabel.bottom - anchors.topMargin: 10 - Layout.preferredWidth: 350 - Layout.fillHeight: true - visible: paramsModel.count > 0 - Column - { - id: paramRepeater - Layout.fillWidth: true - Layout.fillHeight: true - spacing: 3 - Repeater + ScrollView { - height: 20 * paramsModel.count - model: paramsModel - visible: paramsModel.count > 0 - RowLayout + id: paramScroll + anchors.top: paramLabel.bottom + anchors.topMargin: 10 + Layout.fillWidth: true + Layout.fillHeight: true + StructView { - id: row - Layout.fillWidth: true - height: 20 - DefaultLabel { - id: typeLabel - text: type - Layout.preferredWidth: 50 - } - - DefaultLabel { - id: nameLabel - text: name - Layout.preferredWidth: 80 - } - - DefaultLabel { - id: equalLabel - text: "=" - Layout.preferredWidth: 15 - } - - Loader - { - id: typeLoader - Layout.preferredWidth: 150 - function getCurrent() - { - return modalTransactionDialog.param(name); - } - - Connections { - target: typeLoader.item - onTextChanged: { - typeLoader.getCurrent().value = typeLoader.item.text; - } - } - - sourceComponent: - { - if (type.indexOf("int") !== -1) - return intViewComp; - else if (type.indexOf("bool") !== -1) - return boolViewComp; - else if (type.indexOf("string") !== -1) - return stringViewComp; - else if (type.indexOf("hash") !== -1 || type.indexOf("address") !== -1) - return hashViewComp; - else - return null; - } - - Component - { - id: intViewComp - QIntTypeView - { - height: 20 - width: 150 - id: intView - text: typeLoader.getCurrent().value - } - } - - Component - { - id: boolViewComp - QBoolTypeView - { - height: 20 - width: 150 - id: boolView - defaultValue: "1" - Component.onCompleted: - { - var current = typeLoader.getCurrent().value; - (current === "" ? text = defaultValue : text = current); - } - } - } - - Component - { - id: stringViewComp - QStringTypeView - { - height: 20 - width: 150 - id: stringView - text: - { - return typeLoader.getCurrent().value - } - } - } - - Component - { - id: hashViewComp - QHashTypeView - { - height: 20 - width: 150 - id: hashView - text: typeLoader.getCurrent().value - } - } - } + id: typeLoader + Layout.preferredWidth: 150 + members: paramsModel; } } + + CommonSeparator + { + Layout.fillWidth: true + visible: paramsModel.length > 0 + } } } - CommonSeparator + RowLayout { - Layout.fillWidth: true - visible: paramsModel.count > 0 - } - } + anchors.bottom: parent.bottom + anchors.right: parent.right; + + Button { + text: qsTr("OK"); + onClicked: { + close(); + accepted(); + } + } - RowLayout - { - anchors.bottom: parent.bottom - anchors.right: parent.right; - - Button { - text: qsTr("OK"); - onClicked: { - close(); - accepted(); + Button { + text: qsTr("Cancel"); + onClicked: close(); + Layout.fillWidth: true } } - Button { - text: qsTr("Cancel"); - onClicked: close(); - } } } - - ListModel { - id: paramsModel - } } + diff --git a/mix/qml/TransactionLog.qml b/mix/qml/TransactionLog.qml index 8e25b0812..5668c6e05 100644 --- a/mix/qml/TransactionLog.qml +++ b/mix/qml/TransactionLog.qml @@ -6,36 +6,33 @@ import QtQuick.Layouts 1.1 import org.ethereum.qml.RecordLogEntry 1.0 Item { - property ListModel fullModel: ListModel{} property ListModel transactionModel: ListModel{} property ListModel callModel: ListModel{} - - Action { - id: addStateAction - text: "Add State" - shortcut: "Ctrl+Alt+T" - enabled: codeModel.hasContract && !clientModel.running; - onTriggered: projectModel.stateListModel.addState(); - } - Action { - id: editStateAction - text: "Edit State" - shortcut: "Ctrl+Alt+T" - enabled: codeModel.hasContract && !clientModel.running && statesCombo.currentIndex >= 0 && projectModel.stateListModel.count > 0; - onTriggered: projectModel.stateListModel.editState(statesCombo.currentIndex); - } + property int selectedStateIndex: statesCombo.selectedIndex ColumnLayout { anchors.fill: parent RowLayout { + anchors.right: parent.right + anchors.left: parent.left + Connections + { + id: compilationStatus + target: codeModel + property bool compilationComplete: false + onCompilationComplete: compilationComplete = true + onCompilationError: compilationComplete = false + } Connections { target: projectModel onProjectSaved: { - if (codeModel.hasContract && !clientModel.running) + if (projectModel.appIsClosing || projectModel.projectIsClosing) + return; + if (compilationStatus.compilationComplete && codeModel.hasContract && !clientModel.running) projectModel.stateListModel.debugDefaultState(); } onProjectClosed: @@ -44,38 +41,30 @@ Item { transactionModel.clear(); callModel.clear(); } + onContractSaved: { + if (compilationStatus.compilationComplete && codeModel.hasContract && !clientModel.running) + projectModel.stateListModel.debugDefaultState(); + } } - ComboBox { + StatesComboBox + { id: statesCombo - model: projectModel.stateListModel - width: 150 - editable: false - textRole: "title" - onActivated: { - model.runState(index); - } + items: projectModel.stateListModel + onSelectCreate: projectModel.stateListModel.addState(); + onEditItem: projectModel.stateListModel.editState(item) + colorItem: "#808080" + colorSelect: "#4a90e2" + color: "white" Connections { target: projectModel.stateListModel onStateRun: { - if (statesCombo.currentIndex !== index) - statesCombo.currentIndex = index; + if (statesCombo.selectedIndex !== index) + statesCombo.setSelectedIndex( index ); } } } Button - { - anchors.rightMargin: 9 - anchors.verticalCenter: parent.verticalCenter - action: editStateAction - } - Button - { - anchors.rightMargin: 9 - anchors.verticalCenter: parent.verticalCenter - action: addStateAction - } - Button { anchors.rightMargin: 9 anchors.verticalCenter: parent.verticalCenter @@ -110,7 +99,7 @@ Item { TableViewColumn { role: "transactionIndex" - title: qsTr("Index") + title: qsTr("#") width: 40 } TableViewColumn { @@ -130,8 +119,8 @@ Item { } TableViewColumn { role: "address" - title: qsTr("Address") - width: 120 + title: qsTr("Destination") + width: 130 } TableViewColumn { role: "returned" @@ -153,7 +142,6 @@ Item { } } } - Connections { target: clientModel onStateCleared: { diff --git a/mix/qml/VariablesView.qml b/mix/qml/VariablesView.qml new file mode 100644 index 000000000..2670a5cb0 --- /dev/null +++ b/mix/qml/VariablesView.qml @@ -0,0 +1,38 @@ +import QtQuick 2.2 +import QtQuick.Controls 1.1 +import QtQuick.Controls.Styles 1.1 +import QtQuick.Layouts 1.1 + +DebugInfoList +{ + id: storage + collapsible: true + title : qsTr("Storage") + componentDelegate: structComp + + Component + { + id: structComp + ScrollView + { + property alias members: typeLoader.members; + property alias value: typeLoader.value; + anchors.fill: parent + StructView + { + id: typeLoader + members: [] + value: {} + Layout.preferredWidth: parent.width + } + } + } + + function setData(members, values) { + storage.item.value = {}; + storage.item.members = []; + storage.item.value = values; //TODO: use a signal for this? + storage.item.members = members; + } +} + diff --git a/mix/qml/WebCodeEditor.qml b/mix/qml/WebCodeEditor.qml index 165bad9ee..6ab0b398a 100644 --- a/mix/qml/WebCodeEditor.qml +++ b/mix/qml/WebCodeEditor.qml @@ -4,27 +4,34 @@ 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 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 (initialized) { + if (mode !== undefined) + currentMode = mode; + if (initialized && editorBrowser) { editorBrowser.runJavaScript("setTextBase64(\"" + Qt.btoa(text) + "\")"); - editorBrowser.runJavaScript("setMode(\"" + mode + "\")"); + editorBrowser.runJavaScript("setMode(\"" + currentMode + "\")"); } setFocus(); } function setFocus() { - editorBrowser.forceActiveFocus(); + if (editorBrowser) + editorBrowser.forceActiveFocus(); } function getText() { @@ -32,14 +39,20 @@ Item { } function syncClipboard() { - if (Qt.platform.os == "osx") { - var text = appContext.clipboard; + if (Qt.platform.os == "osx" && editorBrowser) { + var text = clipboard.text; editorBrowser.runJavaScript("setClipboardBase64(\"" + Qt.btoa(text) + "\")"); } } function highlightExecution(location) { - editorBrowser.runJavaScript("highlightExecution(" + location.start + "," + location.end + ")"); + if (initialized && editorBrowser) + editorBrowser.runJavaScript("highlightExecution(" + location.start + "," + location.end + ")"); + } + + function showWarning(content) { + if (initialized && editorBrowser) + editorBrowser.runJavaScript("showWarning('" + content + "')"); } function getBreakpoints() { @@ -47,11 +60,22 @@ Item { } function toggleBreakpoint() { - editorBrowser.runJavaScript("toggleBreakpoint()"); + if (initialized && editorBrowser) + editorBrowser.runJavaScript("toggleBreakpoint()"); + } + + function changeGeneration() { + if (initialized && editorBrowser) + editorBrowser.runJavaScript("changeGeneration()", function(result) {}); + } + + Clipboard + { + id: clipboard } Connections { - target: appContext + target: clipboard onClipboardChanged: syncClipboard() } @@ -67,17 +91,47 @@ Item { console.log("editor: " + sourceID + ":" + lineNumber + ":" + message); } + Component.onDestruction: + { + codeModel.onCompilationComplete.disconnect(compilationComplete); + codeModel.onCompilationError.disconnect(compilationError); + } + onLoadingChanged: { - if (!loading) { + if (!loading && editorBrowser) { initialized = true; setText(currentText, currentMode); runJavaScript("getTextChanged()", function(result) { }); pollTimer.running = true; syncClipboard(); + if (currentMode === "solidity") + { + codeModel.onCompilationComplete.connect(compilationComplete); + codeModel.onCompilationError.connect(compilationError); + } + parent.changeGeneration(); } } + + function compilationComplete() + { + if (editorBrowser) + editorBrowser.runJavaScript("compilationComplete()", function(result) { }); + } + + function compilationError(error) + { + if (!editorBrowser || !error) + return; + var errorInfo = ErrorLocationFormater.extractErrorInfo(error, false); + if (errorInfo.line && errorInfo.column) + editorBrowser.runJavaScript("compilationError('" + errorInfo.line + "', '" + errorInfo.column + "', '" + errorInfo.errorDetail + "')", function(result) { }); + else + editorBrowser.runJavaScript("compilationComplete()", function(result) { }); + } + Timer { id: pollTimer @@ -85,8 +139,10 @@ Item { running: false repeat: true onTriggered: { + if (!editorBrowser) + return; editorBrowser.runJavaScript("getTextChanged()", function(result) { - if (result === true) { + if (result === true && editorBrowser) { editorBrowser.runJavaScript("getText()" , function(textValue) { currentText = textValue; editorTextChanged(); @@ -94,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; @@ -103,7 +159,9 @@ Item { }); } }); - + editorBrowser.runJavaScript("isClean()", function(result) { + isClean = result; + }); } } } diff --git a/mix/qml/WebPreview.qml b/mix/qml/WebPreview.qml index 6f03088a4..58bb3c64d 100644 --- a/mix/qml/WebPreview.qml +++ b/mix/qml/WebPreview.qml @@ -12,6 +12,11 @@ Item { id: webPreview property string pendingPageUrl: "" property bool initialized: false + property alias urlInput: urlInput + property alias webView: webView + property string webContent; //for testing + signal javaScriptMessage(var _level, string _sourceId, var _lineNb, string _content) + signal webContentReady function setPreviewUrl(url) { if (!initialized) @@ -55,18 +60,24 @@ Item { action(i); } + function getContent() { + webView.runJavaScript("getContent()", function(result) { + webContent = result; + webContentReady(); + }); + } + function changePage() { setPreviewUrl(urlInput.text); - /*if (pageCombo.currentIndex >= 0 && pageCombo.currentIndex < pageListModel.count) { - urlInput.text = httpServer.url + "/" + pageListModel.get(pageCombo.currentIndex).documentId; - setPreviewUrl(httpServer.url + "/" + pageListModel.get(pageCombo.currentIndex).documentId); - } else { - setPreviewUrl(""); - }*/ } + + WebPreviewStyle { + id: webPreviewStyle + } + Connections { - target: appContext - onAppLoaded: { + target: mainApplication + onLoaded: { //We need to load the container using file scheme so that web security would allow loading local files in iframe var containerPage = fileIo.readFile("qrc:///qml/html/WebContainer.html"); webView.loadHtml(containerPage, httpServer.url + "/WebContainer.html") @@ -86,8 +97,7 @@ Item { Connections { target: projectModel - //onProjectSaved : reloadOnSave(); - //onDocumentSaved: reloadOnSave(); + onDocumentAdded: { var document = projectModel.getDocument(documentId) if (document.isHtml) @@ -98,7 +108,13 @@ Item { } onDocumentUpdated: { - updateDocument(documentId, function(i) { pageListModel.set(i, projectModel.getDocument(documentId)) } ) + var document = projectModel.getDocument(documentId); + for (var i = 0; i < pageListModel.count; i++) + if (pageListModel.get(i).documentId === documentId) + { + pageListModel.set(i, document); + break; + } } onProjectLoading: { @@ -115,6 +131,12 @@ Item { } } + onDocumentSaved: + { + if (!projectModel.getDocument(documentId).isContract) + reloadOnSave(); + } + onProjectClosed: { pageListModel.clear(); } @@ -148,18 +170,19 @@ Item { //document request if (urlPath === "/") urlPath = "/index.html"; - var documentId = urlPath.substr(urlPath.lastIndexOf("/") + 1); + var documentName = urlPath.substr(urlPath.lastIndexOf("/") + 1); + var documentId = projectModel.getDocumentIdByName(documentName); var content = ""; if (projectModel.codeEditor.isDocumentOpen(documentId)) content = projectModel.codeEditor.getDocumentText(documentId); else { var doc = projectModel.getDocument(documentId); - if (doc !== undefined) + if (doc) content = fileIo.readFile(doc.path); } - if (documentId === urlInput.text.replace(httpServer.url + "/", "")) { + if (documentName === urlInput.text.replace(httpServer.url + "/", "")) { //root page, inject deployment script content = "\n" + content; _request.setResponseContentType("text/html"); @@ -175,7 +198,7 @@ Item { Rectangle { anchors.leftMargin: 4 - color: WebPreviewStyle.general.headerBackgroundColor + color: webPreviewStyle.general.headerBackgroundColor Layout.preferredWidth: parent.width Layout.preferredHeight: 32 Row { @@ -198,7 +221,6 @@ Item { { setPreviewUrl(text); } - focus: true } @@ -216,7 +238,17 @@ Item { anchors.verticalCenter: parent.verticalCenter width: 21 height: 21 + focus: true } + + Rectangle + { + width: 1 + height: parent.height - 10 + color: webPreviewStyle.general.separatorColor + anchors.verticalCenter: parent.verticalCenter + } + CheckBox { id: autoReloadOnSave checked: true @@ -227,20 +259,63 @@ Item { text: qsTr("Auto reload on save") } } + focus: true + } + + Rectangle + { + width: 1 + height: parent.height - 10 + color: webPreviewStyle.general.separatorColor + anchors.verticalCenter: parent.verticalCenter + } + + Button + { + height: 28 + anchors.verticalCenter: parent.verticalCenter + action: expressionAction + iconSource: "qrc:/qml/img/console.png" + } + + Action { + id: expressionAction + tooltip: qsTr("Expressions") + onTriggered: + { + expressionPanel.visible = !expressionPanel.visible; + if (expressionPanel.visible) + { + webView.width = webView.parent.width - 350 + expressionInput.forceActiveFocus(); + } + else + webView.width = webView.parent.width + } } } } Rectangle + { + Layout.preferredHeight: 1 + Layout.preferredWidth: parent.width + color: webPreviewStyle.general.separatorColor + } + + Splitter { Layout.preferredWidth: parent.width Layout.fillHeight: true WebEngineView { - anchors.fill: parent + Layout.fillHeight: true + width: parent.width + Layout.preferredWidth: parent.width id: webView experimental.settings.localContentCanAccessRemoteUrls: true onJavaScriptConsoleMessage: { - console.log(sourceID + ":" + lineNumber + ":" + message); + console.log(sourceID + ":" + lineNumber + ": " + message); + webPreview.javaScriptMessage(level, sourceID, lineNumber, message); } onLoadingChanged: { if (!loading) { @@ -251,6 +326,124 @@ Item { } } } + + Column { + id: expressionPanel + width: 350 + Layout.preferredWidth: 350 + Layout.fillHeight: true + spacing: 0 + visible: false + function addExpression() + { + if (expressionInput.text === "") + return; + expressionInput.history.unshift(expressionInput.text); + expressionInput.index = -1; + webView.runJavaScript("executeJavaScript(\"" + expressionInput.text.replace(/"/g, '\\"') + "\")", function(result) { + resultTextArea.text = "> " + result + "\n\n" + resultTextArea.text; + expressionInput.text = ""; + }); + } + + Row + { + id: rowConsole + width: parent.width + Button + { + height: 22 + width: 22 + action: clearAction + iconSource: "qrc:/qml/img/cleariconactive.png" + } + + Action { + id: clearAction + enabled: resultTextArea.text !== "" + tooltip: qsTr("Clear") + onTriggered: { + resultTextArea.text = ""; + } + } + + DefaultTextField { + id: expressionInput + width: parent.width - 15 + height: 20 + font.family: webPreviewStyle.general.fontName + font.italic: true + font.pointSize: appStyle.absoluteSize(-3) + anchors.verticalCenter: parent.verticalCenter + + property var history: [] + property int index: -1 + + function displayCache(incr) + { + index = index + incr; + if (history.length - 1 < index || index < 0) + { + if (incr === 1) + index = 0; + else + index = history.length - 1; + } + expressionInput.text = history[index]; + } + + Keys.onDownPressed: { + displayCache(1); + } + + Keys.onUpPressed: { + displayCache(-1); + } + + Keys.onEnterPressed: + { + expressionPanel.addExpression(); + } + + Keys.onReturnPressed: + { + expressionPanel.addExpression(); + } + + onFocusChanged: + { + if (!focus && text == "") + text = qsTr("Expression"); + if (focus && text === qsTr("Expression")) + text = ""; + } + + style: TextFieldStyle { + background: Rectangle { + color: "transparent" + } + } + } + } + + TextArea { + Layout.fillHeight: true + height: parent.height - rowConsole.height + readOnly: true + id: resultTextArea + width: expressionPanel.width + wrapMode: Text.Wrap + textFormat: Text.RichText + 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 551f5a446..1fc0c8b99 100644 --- a/mix/qml/WebPreviewStyle.qml +++ b/mix/qml/WebPreviewStyle.qml @@ -1,4 +1,3 @@ -pragma Singleton import QtQuick 2.0 QtObject { @@ -10,5 +9,7 @@ QtObject { property QtObject general: QtObject { property string headerBackgroundColor: "#f0f0f0" + property string separatorColor: "#808080" + property string fontName: "sans serif" } } diff --git a/mix/qml/html/WebContainer.html b/mix/qml/html/WebContainer.html index e2f97fd86..30e203bed 100644 --- a/mix/qml/html/WebContainer.html +++ b/mix/qml/html/WebContainer.html @@ -3,6 +3,8 @@ + +