Browse Source

Merge remote-tracking branch 'upstream/develop' into develop

Conflicts:
	mix/qml/TransactionLog.qml
	mix/res.qrc
cl-refactor
Ali Mashatan 10 years ago
parent
commit
a400c9aadd
  1. 8
      CMakeLists.txt
  2. 16
      abi/CMakeLists.txt
  3. 769
      abi/main.cpp
  4. 5
      alethzero/CMakeLists.txt
  5. 63
      alethzero/Connect.cpp
  6. 55
      alethzero/Connect.h
  7. 123
      alethzero/Connect.ui
  8. 3
      alethzero/Debugger.cpp
  9. 78
      alethzero/Main.ui
  10. 129
      alethzero/MainWin.cpp
  11. 3
      alethzero/MainWin.h
  12. 397
      alethzero/Transact.cpp
  13. 16
      alethzero/Transact.h
  14. 6
      cmake/EthCompilerSettings.cmake
  15. 32
      cmake/EthDependencies.cmake
  16. 40
      cmake/EthUtils.cmake
  17. 29
      cmake/Findjson_rpc_cpp.cmake
  18. 15
      cmake/scripts/runtest.cmake
  19. 102
      eth/main.cpp
  20. 35
      ethrpctest/CMakeLists.txt
  21. 129
      ethrpctest/CommandLineInterface.cpp
  22. 46
      ethrpctest/CommandLineInterface.h
  23. 34
      ethrpctest/main.cpp
  24. 4
      evmjit/libevmjit-cpp/Env.cpp
  25. 46
      evmjit/libevmjit/Arith256.cpp
  26. 17
      evmjit/libevmjit/ExecutionEngine.cpp
  27. 6
      libdevcore/CommonData.cpp
  28. 20
      libdevcore/CommonData.h
  29. 2
      libdevcore/CommonIO.cpp
  30. 2
      libdevcore/Exceptions.h
  31. 1
      libdevcore/FixedHash.h
  32. 31
      libdevcore/RLP.cpp
  33. 34
      libdevcore/RLP.h
  34. 3
      libdevcore/vector_ref.h
  35. 9
      libdevcrypto/FileSystem.cpp
  36. 2
      libdevcrypto/FileSystem.h
  37. 2
      libethcore/Common.cpp
  38. 11
      libethcore/Common.h
  39. 30
      libethcore/Ethasher.cpp
  40. 2
      libethcore/Exceptions.h
  41. 1
      libethcore/Params.cpp
  42. 1
      libethcore/Params.h
  43. 27
      libethereum/ABI.cpp
  44. 64
      libethereum/ABI.h
  45. 69
      libethereum/BlockChain.cpp
  46. 51
      libethereum/BlockChain.h
  47. 417
      libethereum/Client.cpp
  48. 173
      libethereum/Client.h
  49. 399
      libethereum/ClientBase.cpp
  50. 170
      libethereum/ClientBase.h
  51. 2
      libethereum/EthereumHost.cpp
  52. 1
      libethereum/EthereumPeer.cpp
  53. 106
      libethereum/Executive.cpp
  54. 43
      libethereum/Executive.h
  55. 6
      libethereum/ExtVM.h
  56. 30
      libethereum/Interface.h
  57. 27
      libethereum/LogFilter.cpp
  58. 11
      libethereum/LogFilter.h
  59. 91
      libethereum/State.cpp
  60. 20
      libethereum/State.h
  61. 6
      libethereum/Transaction.cpp
  62. 8
      libethereum/Transaction.h
  63. 10
      libethereum/TransactionQueue.cpp
  64. 11
      libethereum/TransactionQueue.h
  65. 4
      libevm/ExtVMFace.h
  66. 12
      libevm/VM.cpp
  67. 2
      libevm/VM.h
  68. 1
      libevm/VMFace.h
  69. 278
      libevmcore/Assembly.cpp
  70. 55
      libevmcore/Assembly.h
  71. 135
      libevmcore/AssemblyItem.cpp
  72. 92
      libevmcore/AssemblyItem.h
  73. 440
      libevmcore/CommonSubexpressionEliminator.cpp
  74. 183
      libevmcore/CommonSubexpressionEliminator.h
  75. 2
      libevmcore/Exceptions.h
  76. 371
      libevmcore/ExpressionClasses.cpp
  77. 150
      libevmcore/ExpressionClasses.h
  78. 31
      libjsqrc/ethereumjs/dist/ethereum.js
  79. 8
      libjsqrc/ethereumjs/dist/ethereum.js.map
  80. 2
      libjsqrc/ethereumjs/dist/ethereum.min.js
  81. 2
      libjsqrc/ethereumjs/lib/web3/contract.js
  82. 27
      libjsqrc/ethereumjs/lib/web3/filter.js
  83. 1
      libnatspec/NatspecExpressionEvaluator.h
  84. 29
      libp2p/Common.cpp
  85. 13
      libp2p/Common.h
  86. 211
      libp2p/Host.cpp
  87. 42
      libp2p/Host.h
  88. 108
      libp2p/Network.cpp
  89. 26
      libp2p/Network.h
  90. 110
      libp2p/NodeTable.cpp
  91. 13
      libp2p/NodeTable.h
  92. 1
      libp2p/Peer.h
  93. 29
      libp2p/RLPxHandshake.cpp
  94. 14
      libp2p/RLPxHandshake.h
  95. 9
      libp2p/Session.cpp
  96. 22
      libp2p/UDP.h
  97. 22
      libsolidity/AST.cpp
  98. 4
      libsolidity/AST.h
  99. 207
      libsolidity/ArrayUtils.cpp
  100. 6
      libsolidity/ArrayUtils.h

8
CMakeLists.txt

@ -160,7 +160,6 @@ if (EVMJIT)
endif() endif()
add_subdirectory(libdevcore) add_subdirectory(libdevcore)
add_subdirectory(rlp)
add_subdirectory(libevmcore) add_subdirectory(libevmcore)
add_subdirectory(liblll) add_subdirectory(liblll)
@ -178,6 +177,7 @@ endif()
if (JSONRPC) if (JSONRPC)
add_subdirectory(libweb3jsonrpc) add_subdirectory(libweb3jsonrpc)
add_subdirectory(ethrpctest)
endif() endif()
add_subdirectory(secp256k1) add_subdirectory(secp256k1)
@ -195,10 +195,13 @@ add_subdirectory(libevm)
add_subdirectory(libethereum) add_subdirectory(libethereum)
add_subdirectory(libwebthree) add_subdirectory(libwebthree)
add_subdirectory(libtestutils)
add_subdirectory(test) add_subdirectory(test)
if (NOT JUSTTESTS) if (NOT JUSTTESTS)
add_subdirectory(rlp)
add_subdirectory(abi)
add_subdirectory(eth) add_subdirectory(eth)
if("x${CMAKE_BUILD_TYPE}" STREQUAL "xDebug") if("x${CMAKE_BUILD_TYPE}" STREQUAL "xDebug")
@ -226,9 +229,6 @@ if (NOT JUSTTESTS)
endif() endif()
enable_testing()
add_test(NAME alltests WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/test COMMAND testeth)
#unset(TARGET_PLATFORM CACHE) #unset(TARGET_PLATFORM CACHE)
if (WIN32) if (WIN32)

16
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)

769
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 <http://www.gnu.org/licenses/>.
*/
/** @file main.cpp
* @author Gav Wood <i@gavwood.com>
* @date 2014
* RLP tool.
*/
#include <fstream>
#include <iostream>
#include <boost/regex.hpp>
#include <boost/algorithm/string.hpp>
#include "../test/JsonSpiritHeaders.h"
#include <libdevcore/CommonIO.h>
#include <libdevcore/RLP.h>
#include <libdevcrypto/SHA3.h>
#include <libethereum/Client.h>
using namespace std;
using namespace dev;
namespace js = json_spirit;
void help()
{
cout
<< "Usage abi enc <method_name> (<arg1>, (<arg2>, ... ))" << endl
<< " abi enc -a <abi.json> <method_name> (<arg1>, (<arg2>, ... ))" << endl
<< " abi dec -a <abi.json> [ <signature> | <unique_method_name> ]" << endl
<< "Options:" << endl
<< " -a,--abi-file <filename> 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 <n> 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<Base, string> 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<int> 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<bigint>(bytesConstRef(&_data).cropped(32 - size / 8))));
else
{
bigint i = fromBigEndian<bigint>(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<bytes, ABIType, Format> 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<ABIType> ins;
vector<ABIType> 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<ABIType> 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<pair<bytes, Format>> 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<void(vector<int>, unsigned)> putDim = [&](vector<int> addr, unsigned q) {
if (addr.size() == a.dims.size())
put();
else
{
if (_params[pi].second != Format::Open)
throw ExpectedOpen();
++pi;
int l = a.dims[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<int>(), 1);
}
return ret + suffix;
}
string decode(bytes const& _data, int _index, EncodingPrefs _ep)
{
stringstream out;
unsigned di = 0;
vector<unsigned> catDims;
for (ABIType const& a: outs)
{
auto put = [&]() {
if (a.isBytes())
{
catDims.push_back(fromBigEndian<unsigned>(bytesConstRef(&_data).cropped(di, 32)));
di += 32;
}
};
function<void(vector<int>, unsigned)> putDim = [&](vector<int> addr, unsigned q) {
if (addr.size() == a.dims.size())
put();
else
{
int l = a.dims[addr.size()];
if (l == -1)
{
l = fromBigEndian<unsigned>(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<int>(), 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<void(vector<int>)> putDim = [&](vector<int> addr) {
if (addr.size() == a.dims.size())
put();
else
{
out << "[";
addr.push_back(0);
int l = a.dims[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<int>());
}
return out.str();
}
};
string canonSig(string const& _name, vector<ABIType> 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<ABIType> 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<FixedHash<4>, 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 <unsigned n, class T> vector<typename std::remove_reference<decltype(get<n>(T()))>::type> retrieve(vector<T> const& _t)
{
vector<typename std::remove_reference<decltype(get<n>(T()))>::type> ret;
for (T const& i: _t)
ret.push_back(get<n>(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<pair<bytes, Format>> params;
vector<ABIType> 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;
}

5
alethzero/CMakeLists.txt

@ -19,6 +19,7 @@ find_package (Qt5WebEngine QUIET)
find_package (Qt5WebEngineWidgets QUIET) find_package (Qt5WebEngineWidgets QUIET)
qt5_wrap_ui(ui_Main.h Main.ui) 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_Debugger.h Debugger.ui)
qt5_wrap_ui(ui_Transact.h Transact.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 is defined in cmake/EthExecutableHelper.cmake
eth_add_executable(${EXECUTABLE} eth_add_executable(${EXECUTABLE}
ICON alethzero ICON alethzero
UI_RESOURCES alethzero.icns Main.ui Debugger.ui Transact.ui UI_RESOURCES alethzero.icns Main.ui Connect.ui Debugger.ui Transact.ui
WIN_RESOURCES alethzero.rc WIN_RESOURCES alethzero.rc
) )

63
alethzero/Connect.cpp

@ -0,0 +1,63 @@
/*
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 <http://www.gnu.org/licenses/>.
*/
/** @file Connect.cpp
* @author Alex Leverington <nessence@gmail.com>
* @date 2015
*/
#include "Connect.h"
#include <libp2p/Host.h>
#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)
{
ui->host->addItems(_nodes);
}
void Connect::reset()
{
ui->nodeId->clear();
ui->required->setChecked(false);
}
QString Connect::host()
{
return ui->host->currentText();
}
QString Connect::nodeId()
{
return ui->nodeId->text();
}
bool Connect::required()
{
return ui->required->isChecked();
}

55
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 <http://www.gnu.org/licenses/>.
*/
/** @file Connect.h
* @author Alex Leverington <nessence@gmail.com>
* @date 2015
*/
#pragma once
#include <QDialog>
#include <QList>
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;
};

123
alethzero/Connect.ui

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Connect</class>
<widget class="QDialog" name="Connect">
<property name="windowModality">
<enum>Qt::WindowModal</enum>
</property>
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>343</width>
<height>178</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="maximumSize">
<size>
<width>343</width>
<height>178</height>
</size>
</property>
<property name="windowTitle">
<string>Connect to Peer</string>
</property>
<property name="modal">
<bool>true</bool>
</property>
<layout class="QFormLayout" name="formLayout_2">
<item row="1" column="0">
<layout class="QFormLayout" name="formLayout">
<item row="1" column="0" colspan="2">
<widget class="QComboBox" name="host">
<property name="minimumSize">
<size>
<width>311</width>
<height>0</height>
</size>
</property>
<property name="editable">
<bool>true</bool>
</property>
</widget>
</item>
<item row="2" column="0" colspan="2">
<widget class="QLineEdit" name="nodeId">
<property name="placeholderText">
<string>Node Id</string>
</property>
</widget>
</item>
<item row="3" column="0" colspan="2">
<widget class="QCheckBox" name="required">
<property name="text">
<string>Required (Always Connect to this Peer)</string>
</property>
<property name="tristate">
<bool>false</bool>
</property>
</widget>
</item>
<item row="4" column="0" colspan="2">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="0">
<widget class="QLabel" name="formLabel">
<property name="text">
<string>Enter a peer to which a connection may be made:</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>Connect</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>Connect</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

3
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) bool DebugSession::populate(dev::eth::Executive& _executive, dev::eth::Transaction const& _transaction)
{ {
try { try {
if (_executive.setup(_transaction)) _executive.initialize(_transaction);
if (_executive.execute())
return false; return false;
} }
catch (...) catch (...)

78
alethzero/Main.ui

@ -118,7 +118,7 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>1617</width> <width>1617</width>
<height>24</height> <height>22</height>
</rect> </rect>
</property> </property>
<widget class="QMenu" name="menu_File"> <widget class="QMenu" name="menu_File">
@ -134,8 +134,7 @@
<addaction name="go"/> <addaction name="go"/>
<addaction name="separator"/> <addaction name="separator"/>
<addaction name="upnp"/> <addaction name="upnp"/>
<addaction name="usePast"/> <addaction name="dropPeers"/>
<addaction name="localNetworking"/>
<addaction name="net"/> <addaction name="net"/>
<addaction name="connect"/> <addaction name="connect"/>
</widget> </widget>
@ -208,7 +207,7 @@
<addaction name="menu_Debug"/> <addaction name="menu_Debug"/>
</widget> </widget>
<widget class="QStatusBar" name="statusbar"/> <widget class="QStatusBar" name="statusbar"/>
<widget class="QDockWidget" name="dockWidget_2"> <widget class="QDockWidget" name="dockWidget_accounts">
<property name="features"> <property name="features">
<set>QDockWidget::DockWidgetFeatureMask</set> <set>QDockWidget::DockWidgetFeatureMask</set>
</property> </property>
@ -284,7 +283,27 @@
</widget> </widget>
<widget class="QWidget" name="layoutWidget"> <widget class="QWidget" name="layoutWidget">
<layout class="QGridLayout" name="gridLayout_2"> <layout class="QGridLayout" name="gridLayout_2">
<item row="1" column="2"> <item row="3" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>&amp;Listen on</string>
</property>
<property name="buddy">
<cstring>port</cstring>
</property>
</widget>
</item>
<item row="0" column="1" colspan="2">
<widget class="QSpinBox" name="idealPeers">
<property name="minimum">
<number>1</number>
</property>
<property name="value">
<number>5</number>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QSpinBox" name="port"> <widget class="QSpinBox" name="port">
<property name="minimum"> <property name="minimum">
<number>1024</number> <number>1024</number>
@ -297,8 +316,8 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="1"> <item row="3" column="1">
<widget class="QLineEdit" name="forceAddress"> <widget class="QLineEdit" name="listenIP">
<property name="inputMask"> <property name="inputMask">
<string/> <string/>
</property> </property>
@ -310,16 +329,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>&amp;Listen on</string>
</property>
<property name="buddy">
<cstring>port</cstring>
</property>
</widget>
</item>
<item row="0" column="0"> <item row="0" column="0">
<widget class="QLabel" name="label_3"> <widget class="QLabel" name="label_3">
<property name="text"> <property name="text">
@ -330,20 +339,17 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="0" column="1" colspan="2"> <item row="5" column="1">
<widget class="QSpinBox" name="idealPeers"> <widget class="QLineEdit" name="forcePublicIP">
<property name="minimum"> <property name="placeholderText">
<number>1</number> <string>Automatic</string>
</property>
<property name="value">
<number>5</number>
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="1" colspan="2"> <item row="5" column="0">
<widget class="QLineEdit" name="clientName"> <widget class="QLabel" name="label_2">
<property name="placeholderText"> <property name="text">
<string>Anonymous</string> <string>Public IP</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -357,6 +363,13 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="1">
<widget class="QLineEdit" name="clientName">
<property name="placeholderText">
<string>Anonymous</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
</widget> </widget>
@ -566,7 +579,7 @@
</layout> </layout>
</widget> </widget>
</widget> </widget>
<widget class="QDockWidget" name="dockWidget_6"> <widget class="QDockWidget" name="dockWidget_contracts">
<property name="features"> <property name="features">
<set>QDockWidget::DockWidgetFeatureMask</set> <set>QDockWidget::DockWidgetFeatureMask</set>
</property> </property>
@ -1443,12 +1456,12 @@ font-size: 14pt</string>
<string>Show &amp;Anonymous Accounts</string> <string>Show &amp;Anonymous Accounts</string>
</property> </property>
</action> </action>
<action name="usePast"> <action name="dropPeers">
<property name="checkable"> <property name="checkable">
<bool>true</bool> <bool>true</bool>
</property> </property>
<property name="text"> <property name="text">
<string>Use &amp;Past Peers</string> <string>&amp;Drop Past Peers</string>
</property> </property>
</action> </action>
<action name="loadJS"> <action name="loadJS">
@ -1694,9 +1707,8 @@ font-size: 14pt</string>
<tabstop>tabWidget</tabstop> <tabstop>tabWidget</tabstop>
<tabstop>urlEdit</tabstop> <tabstop>urlEdit</tabstop>
<tabstop>idealPeers</tabstop> <tabstop>idealPeers</tabstop>
<tabstop>forceAddress</tabstop> <tabstop>listenIP</tabstop>
<tabstop>port</tabstop> <tabstop>port</tabstop>
<tabstop>clientName</tabstop>
<tabstop>transactionQueue</tabstop> <tabstop>transactionQueue</tabstop>
<tabstop>pendingInfo</tabstop> <tabstop>pendingInfo</tabstop>
<tabstop>blockChainFilter</tabstop> <tabstop>blockChainFilter</tabstop>

129
alethzero/MainWin.cpp

@ -28,6 +28,7 @@
//pragma GCC diagnostic ignored "-Werror=pedantic" //pragma GCC diagnostic ignored "-Werror=pedantic"
#include <QtNetwork/QNetworkReply> #include <QtNetwork/QNetworkReply>
#include <QtWidgets/QFileDialog> #include <QtWidgets/QFileDialog>
#include <QtWidgets/QDialog>
#include <QtWidgets/QMessageBox> #include <QtWidgets/QMessageBox>
#include <QtWidgets/QInputDialog> #include <QtWidgets/QInputDialog>
#include <QtWebEngine/QtWebEngine> #include <QtWebEngine/QtWebEngine>
@ -135,8 +136,13 @@ Main::Main(QWidget *parent) :
// ui->log->addItem(QString::fromStdString(s)); // ui->log->addItem(QString::fromStdString(s));
}; };
#if !ETH_FATDB
delete ui->dockWidget_accounts;
delete ui->dockWidget_contracts;
#endif
#if ETH_DEBUG #if ETH_DEBUG
m_servers.append("localhost:30300"); m_servers.append("127.0.0.1:30300");
#endif #endif
m_servers.append(QString::fromStdString(Host::pocHost() + ":30303")); m_servers.append(QString::fromStdString(Host::pocHost() + ":30303"));
@ -145,7 +151,7 @@ Main::Main(QWidget *parent) :
cerr << "Block Hash: " << CanonBlockChain::genesis().hash << endl; cerr << "Block Hash: " << CanonBlockChain::genesis().hash << endl;
cerr << "Block RLP: " << RLP(block) << endl; cerr << "Block RLP: " << RLP(block) << endl;
cerr << "Block Hex: " << toHex(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; cerr << "Client database version: " << c_databaseVersion << endl;
ui->configDock->close(); ui->configDock->close();
@ -162,9 +168,9 @@ Main::Main(QWidget *parent) :
QSettings s("ethereum", "alethzero"); QSettings s("ethereum", "alethzero");
m_networkConfig = s.value("peers").toByteArray(); m_networkConfig = s.value("peers").toByteArray();
bytesConstRef network((byte*)m_networkConfig.data(), m_networkConfig.size()); 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(), false, {"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)); m_server.reset(new OurWebThreeStubServer(*m_httpConnector, *web3(), keysAsVector(m_myKeys), this));
connect(&*m_server, SIGNAL(onNewId(QString)), SLOT(addNewId(QString))); connect(&*m_server, SIGNAL(onNewId(QString)), SLOT(addNewId(QString)));
m_server->setIdentities(keysAsVector(owned())); m_server->setIdentities(keysAsVector(owned()));
@ -246,7 +252,30 @@ void Main::addNewId(QString _ids)
NetworkPreferences Main::netPrefs() const 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() void Main::onKeysChanged()
@ -258,6 +287,7 @@ unsigned Main::installWatch(LogFilter const& _tf, WatchHandler const& _f)
{ {
auto ret = ethereum()->installWatch(_tf); auto ret = ethereum()->installWatch(_tf);
m_handlers[ret] = _f; m_handlers[ret] = _f;
_f(LocalisedLogEntries());
return ret; return ret;
} }
@ -265,6 +295,7 @@ unsigned Main::installWatch(dev::h256 _tf, WatchHandler const& _f)
{ {
auto ret = ethereum()->installWatch(_tf, Reaping::Manual); auto ret = ethereum()->installWatch(_tf, Reaping::Manual);
m_handlers[ret] = _f; m_handlers[ret] = _f;
_f(LocalisedLogEntries());
return ret; return ret;
} }
@ -312,7 +343,7 @@ void Main::installBalancesWatch()
Address coinsAddr = getCurrencies(); Address coinsAddr = getCurrencies();
// TODO: Update for new currencies reg. // 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))); altCoins.push_back(right160(ethereum()->stateAt(coinsAddr, i + 1)));
for (auto i: m_myKeys) for (auto i: m_myKeys)
for (auto c: altCoins) for (auto c: altCoins)
@ -583,7 +614,7 @@ Address Main::fromString(QString const& _n) const
{ {
try try
{ {
return Address(fromHex(_n.toStdString(), ThrowType::Throw)); return Address(fromHex(_n.toStdString(), WhenError::Throw));
} }
catch (BadHexCharacter& _e) catch (BadHexCharacter& _e)
{ {
@ -616,10 +647,10 @@ QString Main::lookup(QString const& _a) const
h256 ret; h256 ret;
// TODO: fix with the new DNSreg contract // 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); // ret = ethereum()->stateAt(dnsReg, n);
/* if (!ret) /* 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); ret = ethereum()->stateAt(nameReg, n2);
*/ */
if (ret && !((u256)ret >> 32)) if (ret && !((u256)ret >> 32))
@ -668,9 +699,7 @@ void Main::writeSettings()
} }
s.setValue("upnp", ui->upnp->isChecked()); s.setValue("upnp", ui->upnp->isChecked());
s.setValue("forceAddress", ui->forceAddress->text()); s.setValue("forceAddress", ui->forcePublicIP->text());
s.setValue("usePast", ui->usePast->isChecked());
s.setValue("localNetworking", ui->localNetworking->isChecked());
s.setValue("forceMining", ui->forceMining->isChecked()); s.setValue("forceMining", ui->forceMining->isChecked());
s.setValue("paranoia", ui->paranoia->isChecked()); s.setValue("paranoia", ui->paranoia->isChecked());
s.setValue("natSpec", ui->natSpec->isChecked()); s.setValue("natSpec", ui->natSpec->isChecked());
@ -678,6 +707,7 @@ void Main::writeSettings()
s.setValue("showAllAccounts", ui->showAllAccounts->isChecked()); s.setValue("showAllAccounts", ui->showAllAccounts->isChecked());
s.setValue("clientName", ui->clientName->text()); s.setValue("clientName", ui->clientName->text());
s.setValue("idealPeers", ui->idealPeers->value()); s.setValue("idealPeers", ui->idealPeers->value());
s.setValue("listenIP", ui->listenIP->text());
s.setValue("port", ui->port->value()); s.setValue("port", ui->port->value());
s.setValue("url", ui->urlEdit->text()); s.setValue("url", ui->urlEdit->text());
s.setValue("privateChain", m_privateChain); s.setValue("privateChain", m_privateChain);
@ -737,9 +767,8 @@ void Main::readSettings(bool _skipGeometry)
} }
ui->upnp->setChecked(s.value("upnp", true).toBool()); ui->upnp->setChecked(s.value("upnp", true).toBool());
ui->forceAddress->setText(s.value("forceAddress", "").toString()); ui->forcePublicIP->setText(s.value("forceAddress", "").toString());
ui->usePast->setChecked(s.value("usePast", true).toBool()); ui->dropPeers->setChecked(false);
ui->localNetworking->setChecked(s.value("localNetworking", true).toBool());
ui->forceMining->setChecked(s.value("forceMining", false).toBool()); ui->forceMining->setChecked(s.value("forceMining", false).toBool());
on_forceMining_triggered(); on_forceMining_triggered();
ui->paranoia->setChecked(s.value("paranoia", false).toBool()); ui->paranoia->setChecked(s.value("paranoia", false).toBool());
@ -750,6 +779,7 @@ void Main::readSettings(bool _skipGeometry)
if (ui->clientName->text().isEmpty()) 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->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->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->port->setValue(s.value("port", ui->port->value()).toInt());
ui->nameReg->setText(s.value("nameReg", "").toString()); ui->nameReg->setText(s.value("nameReg", "").toString());
m_privateChain = s.value("privateChain", "").toString(); m_privateChain = s.value("privateChain", "").toString();
@ -907,7 +937,7 @@ void Main::on_nameReg_textChanged()
void Main::on_preview_triggered() void Main::on_preview_triggered()
{ {
ethereum()->setDefault(ui->preview->isChecked() ? 0 : -1); ethereum()->setDefault(ui->preview->isChecked() ? PendingBlock : LatestBlock);
refreshAll(); refreshAll();
} }
@ -937,7 +967,7 @@ void Main::refreshBalances()
u256 totalBalance = 0; u256 totalBalance = 0;
/* map<Address, tuple<QString, u256, u256>> altCoins; /* map<Address, tuple<QString, u256, u256>> altCoins;
Address coinsAddr = getCurrencies(); 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 n = ethereum()->stateAt(coinsAddr, i + 1);
auto addr = right160(ethereum()->stateAt(coinsAddr, n)); auto addr = right160(ethereum()->stateAt(coinsAddr, n));
@ -1036,6 +1066,7 @@ void Main::refreshPending()
void Main::refreshAccounts() void Main::refreshAccounts()
{ {
#if ETH_FATDB
cwatch << "refreshAccounts()"; cwatch << "refreshAccounts()";
ui->accounts->clear(); ui->accounts->clear();
ui->contracts->clear(); ui->contracts->clear();
@ -1053,13 +1084,14 @@ void Main::refreshAccounts()
->setData(Qt::UserRole, QByteArray((char const*)i.data(), Address::size)); ->setData(Qt::UserRole, QByteArray((char const*)i.data(), Address::size));
} }
} }
#endif
} }
void Main::refreshBlockCount() void Main::refreshBlockCount()
{ {
cwatch << "refreshBlockCount()"; cwatch << "refreshBlockCount()";
auto d = ethereum()->blockChain().details(); auto d = ethereum()->blockChain().details();
ui->blockCount->setText(QString("%4 #%1 PV%2 D%3 H%5").arg(d.number).arg(c_protocolVersion).arg(c_databaseVersion).arg(m_privateChain.size() ? "[" + m_privateChain + "] " : "testnet").arg(c_ethashVersion)); ui->blockCount->setText(QString("%4 #%1 PV%2 D%3 H%5").arg(d.number).arg(eth::c_protocolVersion).arg(c_databaseVersion).arg(m_privateChain.size() ? "[" + m_privateChain + "] " : "testnet").arg(c_ethashVersion));
} }
void Main::on_turboMining_triggered() void Main::on_turboMining_triggered()
@ -1090,7 +1122,7 @@ void Main::refreshBlockChain()
blocks.insert(bc.numberHash(b)); blocks.insert(bc.numberHash(b));
} }
else if (f.toLongLong() <= bc.number()) else if (f.toLongLong() <= bc.number())
blocks.insert(bc.numberHash(u256(f.toLongLong()))); blocks.insert(bc.numberHash((unsigned)f.toLongLong()));
else if (f.size() == 40) else if (f.size() == 40)
{ {
Address h(f.toStdString()); Address h(f.toStdString());
@ -1353,11 +1385,11 @@ void Main::on_transactionQueue_currentItemChanged()
if (!!receipt.bloom()) if (!!receipt.bloom())
s << "<div>Log Bloom: " << receipt.bloom() << "</div>"; s << "<div>Log Bloom: " << receipt.bloom() << "</div>";
else else
s << "<div>Log Bloom: <i>Uneventful</i></div>"; s << "<div>Log Bloom: <b><i>Uneventful</i></b></div>";
auto r = receipt.rlp(); auto r = receipt.rlp();
s << "<div>Receipt: " << toString(RLP(r)) << "</div>"; s << "<div>Receipt: " << toString(RLP(r)) << "</div>";
s << "<div>Receipt-Hex: " Span(Mono) << toHex(receipt.rlp()) << "</span></div>"; s << "<div>Receipt-Hex: " Span(Mono) << toHex(receipt.rlp()) << "</span></div>";
s << renderDiff(ethereum()->diff(i, 0)); s << renderDiff(ethereum()->diff(i, PendingBlock));
// s << "Pre: " << fs.rootHash() << "<br/>"; // s << "Pre: " << fs.rootHash() << "<br/>";
// s << "Post: <b>" << ts.rootHash() << "</b>"; // s << "Post: <b>" << ts.rootHash() << "</b>";
} }
@ -1388,7 +1420,7 @@ void Main::on_inject_triggered()
QString s = QInputDialog::getText(this, "Inject Transaction", "Enter transaction dump in hex"); QString s = QInputDialog::getText(this, "Inject Transaction", "Enter transaction dump in hex");
try try
{ {
bytes b = fromHex(s.toStdString(), ThrowType::Throw); bytes b = fromHex(s.toStdString(), WhenError::Throw);
ethereum()->inject(&b); ethereum()->inject(&b);
} }
catch (BadHexCharacter& _e) catch (BadHexCharacter& _e)
@ -1452,7 +1484,7 @@ void Main::on_blocks_currentItemChanged()
if (!!info.logBloom) if (!!info.logBloom)
s << "<div>Log Bloom: " << info.logBloom << "</div>"; s << "<div>Log Bloom: " << info.logBloom << "</div>";
else else
s << "<div>Log Bloom: <i>Uneventful</i></div>"; s << "<div>Log Bloom: <b><i>Uneventful</i></b></div>";
s << "<div>Transactions: <b>" << block[1].itemCount() << "</b> @<b>" << info.transactionsRoot << "</b>" << "</div>"; s << "<div>Transactions: <b>" << block[1].itemCount() << "</b> @<b>" << info.transactionsRoot << "</b>" << "</div>";
s << "<div>Uncles: <b>" << block[2].itemCount() << "</b> @<b>" << info.sha3Uncles << "</b>" << "</div>"; s << "<div>Uncles: <b>" << block[2].itemCount() << "</b> @<b>" << info.sha3Uncles << "</b>" << "</div>";
for (auto u: block[2]) for (auto u: block[2])
@ -1550,7 +1582,7 @@ void Main::on_debugCurrent_triggered()
unsigned txi = item->data(Qt::UserRole + 1).toInt(); unsigned txi = item->data(Qt::UserRole + 1).toInt();
bytes t = ethereum()->blockChain().transaction(h, txi); bytes t = ethereum()->blockChain().transaction(h, txi);
State s(ethereum()->state(txi, h)); State s(ethereum()->state(txi, h));
Executive e(s, ethereum()->blockChain(), 0); Executive e(s, ethereum()->blockChain(), PendingBlock);
Debugger dw(this, this); Debugger dw(this, this);
dw.populate(e, Transaction(t, CheckSignature::Sender)); dw.populate(e, Transaction(t, CheckSignature::Sender));
dw.exec(); dw.exec();
@ -1626,16 +1658,22 @@ void Main::on_ourAccounts_doubleClicked()
void Main::on_accounts_doubleClicked() void Main::on_accounts_doubleClicked()
{ {
auto hba = ui->accounts->currentItem()->data(Qt::UserRole).toByteArray(); if (ui->accounts->count())
auto h = Address((byte const*)hba.data(), Address::ConstructFromPointer); {
qApp->clipboard()->setText(QString::fromStdString(toHex(h.asArray()))); 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() void Main::on_contracts_doubleClicked()
{ {
auto hba = ui->contracts->currentItem()->data(Qt::UserRole).toByteArray(); if (ui->contracts->count())
auto h = Address((byte const*)hba.data(), Address::ConstructFromPointer); {
qApp->clipboard()->setText(QString::fromStdString(toHex(h.asArray()))); 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) static shh::FullTopic topicFromText(QString _s)
@ -1728,11 +1766,8 @@ void Main::on_net_triggered()
if (ui->net->isChecked()) if (ui->net->isChecked())
{ {
web3()->setIdealPeerCount(ui->idealPeers->value()); web3()->setIdealPeerCount(ui->idealPeers->value());
web3()->setNetworkPreferences(netPrefs()); web3()->setNetworkPreferences(netPrefs(), ui->dropPeers->isChecked());
ethereum()->setNetworkId(m_privateChain.size() ? sha3(m_privateChain.toStdString()) : h256()); 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(); web3()->startNetwork();
ui->downloadView->setDownloadMan(ethereum()->downloadMan()); ui->downloadView->setDownloadMan(ethereum()->downloadMan());
} }
@ -1750,13 +1785,25 @@ void Main::on_connect_triggered()
ui->net->setChecked(true); ui->net->setChecked(true);
on_net_triggered(); 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); m_connect.setEnvironment(m_servers);
if (ok && s.contains(":")) if (m_connect.exec() == QDialog::Accepted)
{ {
string host = s.section(":", 0, 0).toStdString(); bool required = m_connect.required();
unsigned short port = s.section(":", 1).toInt(); string host(m_connect.host().toStdString());
web3()->connect(host, port); 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);
} }
} }
@ -1867,7 +1914,7 @@ void Main::on_go_triggered()
ui->net->setChecked(true); ui->net->setChecked(true);
on_net_triggered(); on_net_triggered();
} }
web3()->connect(Host::pocHost()); web3()->addNode(p2p::NodeId(), Host::pocHost());
} }
QString Main::prettyU256(dev::u256 _n) const QString Main::prettyU256(dev::u256 _n) const

3
alethzero/MainWin.h

@ -40,6 +40,7 @@
#include "Context.h" #include "Context.h"
#include "Transact.h" #include "Transact.h"
#include "NatspecHandler.h" #include "NatspecHandler.h"
#include "Connect.h"
namespace Ui { namespace Ui {
class Main; class Main;
@ -256,4 +257,6 @@ private:
std::unique_ptr<DappHost> m_dappHost; std::unique_ptr<DappHost> m_dappHost;
DappLoader* m_dappLoader; DappLoader* m_dappLoader;
QWebEnginePage* m_webPage; QWebEnginePage* m_webPage;
Connect m_connect;
}; };

397
alethzero/Transact.cpp

@ -25,6 +25,7 @@
#include "Transact.h" #include "Transact.h"
#include <fstream> #include <fstream>
#include <boost/algorithm/string.hpp>
#include <QFileDialog> #include <QFileDialog>
#include <QMessageBox> #include <QMessageBox>
#include <liblll/Compiler.h> #include <liblll/Compiler.h>
@ -135,7 +136,39 @@ void Transact::updateFee()
ui->total->setPalette(p); 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 = ""; string ret = "";
auto const& contract = _compiler.getContractDefinition(_contractName); auto const& contract = _compiler.getContractDefinition(_contractName);
@ -150,186 +183,256 @@ string Transact::getFunctionHashes(dev::solidity::CompilerStack const& _compiler
return ret; return ret;
} }
void Transact::on_destination_currentTextChanged(QString) static tuple<vector<string>, bytes, string> userInputToCode(string const& _user, bool _opt)
{
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()
{ {
if (isCreation()) string lll;
string solidity;
bytes data;
vector<string> errors;
if (_user.find_first_not_of("1234567890abcdefABCDEF\n\t ") == string::npos && _user.size() % 2 == 0)
{ {
string src = ui->data->toPlainText().toStdString(); std::string u = _user;
vector<string> errors; boost::replace_all_copy(u, "\n", "");
QString lll; boost::replace_all_copy(u, "\t", "");
QString solidity; boost::replace_all_copy(u, " ", "");
if (src.find_first_not_of("1234567890abcdefABCDEF") == string::npos && src.size() % 2 == 0) data = fromHex(u);
m_data = fromHex(src); }
else if (sourceIsSolidity(src)) else if (sourceIsSolidity(_user))
{
dev::solidity::CompilerStack compiler(true);
try
{ {
dev::solidity::CompilerStack compiler(true);
try
{
// compiler.addSources(dev::solidity::StandardSources); // compiler.addSources(dev::solidity::StandardSources);
m_data = compiler.compile(src, ui->optimize->isChecked()); data = compiler.compile(_user, _opt);
solidity = "<h4>Solidity</h4>"; solidity = "<h4>Solidity</h4>";
solidity += "<pre>var " + QString::fromStdString(compiler.defaultContractName()) + " = web3.eth.contractFromAbi(" + QString::fromStdString(compiler.getInterface()).replace(QRegExp("\\s"), "").toHtmlEscaped() + ");</pre>"; solidity += "<pre>var " + compiler.defaultContractName() + " = web3.eth.contract(" + QString::fromStdString(compiler.getInterface()).replace(QRegExp("\\s"), "").toHtmlEscaped().toStdString() + ");</pre>";
solidity += "<pre>" + QString::fromStdString(compiler.getSolidityInterface()).toHtmlEscaped() + "</pre>"; solidity += "<pre>" + QString::fromStdString(compiler.getSolidityInterface()).toHtmlEscaped().toStdString() + "</pre>";
solidity += "<pre>" + QString::fromStdString(getFunctionHashes(compiler)).toHtmlEscaped() + "</pre>"; solidity += "<pre>" + QString::fromStdString(getFunctionHashes(compiler, "")).toHtmlEscaped().toStdString() + "</pre>";
}
catch (dev::Exception const& exception)
{
ostringstream error;
solidity::SourceReferenceFormatter::printExceptionInformation(error, exception, "Error", compiler);
solidity = "<h4>Solidity</h4><pre>" + QString::fromStdString(error.str()).toHtmlEscaped() + "</pre>";
}
catch (...)
{
solidity = "<h4>Solidity</h4><pre>Uncaught exception.</pre>";
}
} }
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 #ifndef _MSC_VER
else if (sourceIsSerpent(src)) else if (sourceIsSerpent(_user))
{
try
{ {
try data = dev::asBytes(::compile(_user));
{
m_data = dev::asBytes(::compile(src));
for (auto& i: errors)
i = "(LLL " + i + ")";
}
catch (string const& err)
{
errors.push_back("Serpent " + err);
}
} }
catch (string const& err)
{
errors.push_back("Serpent " + err);
}
}
#endif #endif
else
{
data = compileLLL(_user, _opt, &errors);
if (errors.empty())
{
auto asmcode = compileLLLToAsm(_user, _opt);
lll = "<h4>LLL</h4><pre>" + QString::fromStdString(asmcode).toHtmlEscaped().toStdString() + "</pre>";
}
}
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 else
{ {
m_data = compileLLL(src, ui->optimize->isChecked(), &errors); NatspecExpressionEvaluator evaluator;
if (errors.empty()) return evaluator.evalExpression(QString::fromStdString(userNotice)).toStdString();
{
auto asmcode = compileLLLToAsm(src, false);
lll = "<h4>Pre</h4><pre>" + QString::fromStdString(asmcode).toHtmlEscaped() + "</pre>";
if (ui->optimize->isChecked())
{
asmcode = compileLLLToAsm(src, true);
lll = "<h4>Opt</h4><pre>" + QString::fromStdString(asmcode).toHtmlEscaped() + "</pre>" + lll;
}
}
} }
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<string> errors;
tie(errors, m_data, info) = userInputToCode(ui->data->toPlainText().toStdString(), ui->optimize->isChecked());
if (errors.size()) if (errors.size())
{ {
errs = "<h4>Errors</h4>"; // Errors determining transaction data (i.e. init code). Bail.
QString htmlErrors;
for (auto const& i: errors) for (auto const& i: errors)
errs.append("<div style=\"border-left: 6px solid #c00; margin-top: 2px\">" + QString::fromStdString(i).toHtmlEscaped() + "</div>"); htmlErrors.append("<div class=\"error\"><span class=\"icon\">ERROR</span> " + QString::fromStdString(i).toHtmlEscaped() + "</div>");
bail(htmlErrors);
return;
} }
ui->code->setHtml(errs + lll + solidity + "<h4>Code</h4>" + QString::fromStdString(disassemble(m_data)).toHtmlEscaped() + "<h4>Hex</h4>" Div(Mono) + QString::fromStdString(toHex(m_data)) + "</div>"); htmlInfo = QString::fromStdString(info) + "<h4>Code</h4>" + QString::fromStdString(disassemble(m_data)).toHtmlEscaped();
ui->gas->setMinimum((qint64)Interface::txGas(m_data, 0));
if (!ui->gas->isEnabled())
ui->gas->setValue(m_backupGas);
ui->gas->setEnabled(true);
if (ui->gas->value() == ui->gas->minimum() && !src.empty())
ui->gas->setValue((int)(m_ethereum->postState().gasLimitRemaining() / 10));
} }
else else
{ {
m_data = parseData(ui->data->toPlainText().toStdString()); m_data = parseData(ui->data->toPlainText().toStdString());
auto to = m_context->fromString(ui->destination->currentText()); htmlInfo = "<h4>Dump</h4>" + QString::fromStdString(dev::memDump(m_data, 8, true));
QString natspec; }
if (ethereum()->codeAt(to, 0).size())
{ htmlInfo += "<h4>Hex</h4>" + QString(Div(Mono)) + QString::fromStdString(toHex(m_data)) + "</div>";
string userNotice = m_natSpecDB->getUserNotice(ethereum()->postState().codeHash(to), m_data);
if (userNotice.empty()) // Determine the minimum amount of gas we need to play...
natspec = "Destination contract unknown."; qint64 baseGas = (qint64)Interface::txGas(m_data, 0);
else qint64 gasNeeded = 0;
{
NatspecExpressionEvaluator evaluator; if (b < value() + baseGas * gasPrice())
natspec = evaluator.evalExpression(QString::fromStdString(userNotice)); {
} // Not enough - bail.
ui->gas->setMinimum((qint64)Interface::txGas(m_data, 1)); bail("<div class=\"error\"><span class=\"icon\">ERROR</span> No single account contains enough for paying even the basic amount of gas required.</div>");
if (!ui->gas->isEnabled()) return;
ui->gas->setValue(m_backupGas); }
ui->gas->setEnabled(true); else
} gasNeeded = (qint64)min<bigint>(ethereum()->gasLimitRemaining(), ((b - value()) / gasPrice()));
else
{ // Dry-run execution to determine gas requirement and any execution errors
natspec += "Destination not a contract."; Address to;
if (ui->gas->isEnabled()) ExecutionResult er;
m_backupGas = ui->gas->value(); if (isCreation())
ui->gas->setValue((qint64)Interface::txGas(m_data)); er = ethereum()->create(s, value(), m_data, gasNeeded, gasPrice());
ui->gas->setEnabled(false); else
} {
ui->code->setHtml("<h3>NatSpec</h3>" + natspec + "<h3>Dump</h3>" + QString::fromStdString(dev::memDump(m_data, 8, true)) + "<h3>Hex</h3>" + Div(Mono) + QString::fromStdString(toHex(m_data)) + "</div>"); 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("<div class=\"info\"><span class=\"icon\">INFO</span> Gas required: %1 total = %2 base, %3 exec [%4 refunded later]</div>").arg(gasNeeded).arg(baseGas).arg(gasNeeded - baseGas).arg((qint64)er.gasRefunded) + htmlInfo;
if (er.excepted != TransactionException::None)
{
bail("<div class=\"error\"><span class=\"icon\">ERROR</span> " + QString::fromStdString(toString(er.excepted)) + "</div>");
return;
}
if (er.codeDeposit == CodeDeposit::Failed)
{
bail("<div class=\"error\"><span class=\"icon\">ERROR</span> Code deposit failed due to insufficient gas</div>");
return;
}
// Add Natspec information
if (!isCreation())
htmlInfo = "<div class=\"info\"><span class=\"icon\">INFO</span> " + QString::fromStdString(natspecNotice(to, m_data)).toHtmlEscaped() + "</div>" + htmlInfo;
// Update gas
if (ui->gas->value() == ui->gas->minimum())
{
ui->gas->setMinimum(gasNeeded);
ui->gas->setValue(gasNeeded);
} }
else
ui->gas->setMinimum(gasNeeded);
updateFee(); 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) for (auto const& i: m_myKeys)
if (ethereum()->balanceAt(i.address(), 0) >= totalReq) {
{ auto b = ethereum()->balanceAt(i.address(), PendingBlock);
Secret s = i.secret(); if (b >= _totalReq)
if (isCreation()) 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 dev::solidity::CompilerStack compiler(true);
// a local Natspec LEVELDB m_data = compiler.compile(src, ui->optimize->isChecked());
ethereum()->submitTransaction(s, value(), m_data, ui->gas->value(), gasPrice()); for (string const& s: compiler.getContractNames())
string src = ui->data->toPlainText().toStdString(); {
if (sourceIsSolidity(src)) h256 contractHash = compiler.getContractCodeHash(s);
try m_natSpecDB->add(contractHash, compiler.getMetadata(s, dev::solidity::DocumentationType::NatspecUser));
{ }
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));
}
}
catch (...)
{
}
close();
return;
} }
else catch (...) {}
ethereum()->submitTransaction(s, value(), m_context->fromString(ui->destination->currentText()), m_data, ui->gas->value(), gasPrice()); }
return; else
} ethereum()->submitTransaction(s, value(), m_context->fromString(ui->destination->currentText()), m_data, ui->gas->value(), gasPrice());
QMessageBox::critical(this, "Transaction Failed", "Couldn't make transaction: no single account contains at least the required amount."); close();
} }
void Transact::on_debug_clicked() 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 try
{ {
u256 totalReq = value() + fee(); State st(ethereum()->postState());
for (auto i: m_myKeys) Transaction t = isCreation() ?
if (ethereum()->balanceAt(i.address()) >= totalReq) 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);
State st(ethereum()->postState()); Debugger dw(m_context, this);
Secret s = i.secret(); Executive e(st, ethereum()->blockChain(), 0);
Transaction t = isCreation() ? dw.populate(e, t);
Transaction(value(), gasPrice(), ui->gas->value(), m_data, st.transactionsFrom(dev::toAddress(s)), s) : dw.exec();
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.");
} }
catch (dev::Exception const& _e) catch (dev::Exception const& _e)
{ {

16
alethzero/Transact.h

@ -57,7 +57,7 @@ private slots:
void on_cancel_clicked() { close(); } void on_cancel_clicked() { close(); }
private: private:
dev::eth::Client* ethereum() { return m_ethereum; } dev::eth::Client* ethereum() const { return m_ethereum; }
void rejigData(); void rejigData();
void updateDestination(); void updateDestination();
@ -68,15 +68,17 @@ private:
dev::u256 value() const; dev::u256 value() const;
dev::u256 gasPrice() 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; dev::bytes m_data;
QList<dev::KeyPair> m_myKeys; QList<dev::KeyPair> m_myKeys;
dev::eth::Client* m_ethereum; dev::eth::Client* m_ethereum = nullptr;
Context* m_context; Context* m_context = nullptr;
NatSpecFace* m_natSpecDB; NatSpecFace* m_natSpecDB = nullptr;
bool m_allGood = false;
}; };

6
cmake/EthCompilerSettings.cmake

@ -38,12 +38,14 @@ elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
# disable decorated name length exceeded, name was truncated (4503) # disable decorated name length exceeded, name was truncated (4503)
# disable warning C4535: calling _set_se_translator() requires /EHa (for boost tests) # disable warning C4535: calling _set_se_translator() requires /EHa (for boost tests)
# declare Windows XP requirement # 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 # disable empty object file warning
set(CMAKE_STATIC_LINKER_FLAGS "${CMAKE_STATIC_LINKER_FLAGS} /ignore:4221") set(CMAKE_STATIC_LINKER_FLAGS "${CMAKE_STATIC_LINKER_FLAGS} /ignore:4221")
# warning LNK4075: ignoring '/EDITANDCONTINUE' due to '/SAFESEH' specification # warning LNK4075: ignoring '/EDITANDCONTINUE' due to '/SAFESEH' specification
# warning LNK4099: pdb was not found with lib # 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 # windows likes static
set(ETH_STATIC 1) set(ETH_STATIC 1)

32
cmake/EthDependencies.cmake

@ -30,6 +30,9 @@ if (APPLE)
set (CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} "/usr/local/opt/qt5") set (CMAKE_PREFIX_PATH ${CMAKE_PREFIX_PATH} "/usr/local/opt/qt5")
endif() 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. # 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) 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 header: ${JSONCPP_INCLUDE_DIRS}")
message(" - Jsoncpp lib : ${JSONCPP_LIBRARIES}") 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 get rid of -DETH_JSONRPC
# TODO add EXACT once we commit ourselves to cmake 3.x
if (JSONRPC) if (JSONRPC)
find_package (json_rpc_cpp 0.4 REQUIRED)
find_package (JsonRpcCpp 0.3.2)
if (NOT JSON_RPC_CPP_FOUND)
message (FATAL_ERROR "JSONRPC 0.3.2. not found")
endif()
message (" - json-rpc-cpp header: ${JSON_RPC_CPP_INCLUDE_DIRS}") message (" - json-rpc-cpp header: ${JSON_RPC_CPP_INCLUDE_DIRS}")
message (" - json-rpc-cpp lib : ${JSON_RPC_CPP_LIBRARIES}") message (" - json-rpc-cpp lib : ${JSON_RPC_CPP_LIBRARIES}")
add_definitions(-DETH_JSONRPC) add_definitions(-DETH_JSONRPC)
@ -114,14 +111,17 @@ if (NOT HEADLESS)
# find all of the Qt packages # find all of the Qt packages
# remember to use 'Qt' instead of 'QT', cause unix is case sensitive # remember to use 'Qt' instead of 'QT', cause unix is case sensitive
# TODO make headless client optional # TODO make headless client optional
find_package (Qt5Core REQUIRED)
find_package (Qt5Gui REQUIRED) set (ETH_QT_VERSION 5.4)
find_package (Qt5Quick REQUIRED)
find_package (Qt5Qml REQUIRED) find_package (Qt5Core ${ETH_QT_VERSION} REQUIRED)
find_package (Qt5Network REQUIRED) find_package (Qt5Gui ${ETH_QT_VERSION} REQUIRED)
find_package (Qt5Widgets REQUIRED) find_package (Qt5Quick ${ETH_QT_VERSION} REQUIRED)
find_package (Qt5WebEngine REQUIRED) find_package (Qt5Qml ${ETH_QT_VERSION} REQUIRED)
find_package (Qt5WebEngineWidgets 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 # we need to find path to macdeployqt on mac
if (APPLE) if (APPLE)

40
cmake/EthUtils.cmake

@ -22,3 +22,43 @@ macro(replace_if_different SOURCE DST)
endif() endif()
endmacro() 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()

29
cmake/FindJsonRpcCpp.cmake → 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_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_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_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 # only look in default directories
find_path( find_path(
@ -90,10 +95,28 @@ if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
endif() 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 # 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 # if all listed variables are TRUE, hide their existence from configuration view
include(FindPackageHandleStandardArgs) 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) find_package_handle_standard_args(
mark_as_advanced (JSON_RPC_CPP_COMMON_LIBRARY JSON_RPC_CPP_SERVER_LIBRARY JSON_RPC_CPP_CLIENT_LIBRARY JSON_RPC_CPP_INCLUDE_DIR) 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)

15
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}[.].*")

102
eth/main.cpp

@ -77,7 +77,6 @@ void interactiveHelp()
<< " setetherprice <p> Resets the ether price." << endl << " setetherprice <p> Resets the ether price." << endl
<< " setpriority <p> Resets the transaction priority." << endl << " setpriority <p> Resets the transaction priority." << endl
<< " minestart Starts mining." << endl << " minestart Starts mining." << endl
<< " minestart Starts mining." << endl
<< " minestop Stops mining." << endl << " minestop Stops mining." << endl
<< " mineforce <enable> Forces mining, even when there are no transactions." << endl << " mineforce <enable> Forces mining, even when there are no transactions." << endl
<< " address Gives the current address." << endl << " address Gives the current address." << endl
@ -88,12 +87,14 @@ void interactiveHelp()
<< " send Execute a given transaction with current secret." << endl << " send Execute a given transaction with current secret." << endl
<< " contract Create a new contract with current secret." << endl << " contract Create a new contract with current secret." << endl
<< " peers List the peers that are connected" << endl << " peers List the peers that are connected" << endl
#if ETH_FATDB
<< " listaccounts List the accounts on the network." << endl << " listaccounts List the accounts on the network." << endl
<< " listcontracts List the contracts on the network." << endl << " listcontracts List the contracts on the network." << endl
<< " setsecret <secret> Set the secret to the hex secret key." <<endl #endif
<< " setaddress <addr> Set the coinbase (mining payout) address." <<endl << " setsecret <secret> Set the secret to the hex secret key." << endl
<< " exportconfig <path> Export the config (.RLP) to the path provided." <<endl << " setaddress <addr> Set the coinbase (mining payout) address." << endl
<< " importconfig <path> Import the config (.RLP) from the path provided." <<endl << " exportconfig <path> Export the config (.RLP) to the path provided." << endl
<< " importconfig <path> Import the config (.RLP) from the path provided." << endl
<< " inspect <contract> Dumps a contract to <APPDATA>/<contract>.evm." << endl << " inspect <contract> Dumps a contract to <APPDATA>/<contract>.evm." << endl
<< " dumptrace <block> <index> <filename> <format> Dumps a transaction trace" << endl << "to <filename>. <format> should be one of pretty, standard, standard+." << endl << " dumptrace <block> <index> <filename> <format> Dumps a transaction trace" << endl << "to <filename>. <format> should be one of pretty, standard, standard+." << endl
<< " dumpreceipt <block> <index> Dumps a transation receipt." << endl << " dumpreceipt <block> <index> Dumps a transation receipt." << endl
@ -103,7 +104,7 @@ void interactiveHelp()
void help() void help()
{ {
cout cout
<< "Usage eth [OPTIONS] <remote-host>" << endl << "Usage eth [OPTIONS]" << endl
<< "Options:" << endl << "Options:" << endl
<< " -a,--address <addr> Set the coinbase (mining payout) address to addr (default: auto)." << endl << " -a,--address <addr> Set the coinbase (mining payout) address to addr (default: auto)." << endl
<< " -b,--bootstrap Connect to the default Ethereum peerserver." << endl << " -b,--bootstrap Connect to the default Ethereum peerserver." << endl
@ -117,10 +118,12 @@ void help()
<< " -i,--interactive Enter interactive mode (default: non-interactive)." << endl << " -i,--interactive Enter interactive mode (default: non-interactive)." << endl
#if ETH_JSONRPC #if ETH_JSONRPC
<< " -j,--json-rpc Enable JSON-RPC server (default: off)." << endl << " -j,--json-rpc Enable JSON-RPC server (default: off)." << endl
<< " --json-rpc-port Specify JSON-RPC server port (implies '-j', default: 8080)." << endl << " --json-rpc-port Specify JSON-RPC server port (implies '-j', default: " << SensibleHttpPort << ")." << endl
#endif #endif
<< " -l,--listen <port> Listen on the given port for incoming connected (default: 30303)." << endl << " -K,--kill-blockchain First kill the blockchain." << endl
<< " -L,--local-networking Use peers whose addresses are local." << endl << " --listen-ip <port> Listen on the given port for incoming connections (default: 30303)." << endl
<< " -l,--listen <ip> Listen on the given IP for incoming connections (default: 0.0.0.0)." << endl
<< " -u,--public-ip <ip> Force public ip to given (default: auto)." << endl
<< " -m,--mining <on/off/number> Enable mining, optionally for a specified number of blocks (Default: off)" << endl << " -m,--mining <on/off/number> Enable mining, optionally for a specified number of blocks (Default: off)" << endl
<< " -n,--upnp <on/off> Use upnp for NAT (default: on)." << endl << " -n,--upnp <on/off> Use upnp for NAT (default: on)." << endl
<< " -o,--mode <full/peer> Start a full node or a peer node (Default: full)." << endl << " -o,--mode <full/peer> Start a full node or a peer node (Default: full)." << endl
@ -129,7 +132,6 @@ void help()
<< " -r,--remote <host> Connect to remote host (default: none)." << endl << " -r,--remote <host> Connect to remote host (default: none)." << endl
<< " -s,--secret <secretkeyhex> Set the secret key for use with send command (default: auto)." << endl << " -s,--secret <secretkeyhex> Set the secret key for use with send command (default: auto)." << endl
<< " -t,--miners <number> Number of mining threads to start (Default: " << thread::hardware_concurrency() << ")" << endl << " -t,--miners <number> Number of mining threads to start (Default: " << thread::hardware_concurrency() << ")" << endl
<< " -u,--public-ip <ip> Force public ip to given (default; auto)." << endl
<< " -v,--verbosity <0 - 9> Set the log verbosity from 0 to 9 (Default: 8)." << endl << " -v,--verbosity <0 - 9> Set the log verbosity from 0 to 9 (Default: 8)." << endl
<< " -x,--peers <number> Attempt to connect to given number of peers (Default: 5)." << endl << " -x,--peers <number> Attempt to connect to given number of peers (Default: 5)." << endl
<< " -V,--version Show the version and exit." << endl << " -V,--version Show the version and exit." << endl
@ -159,7 +161,7 @@ string credits(bool _interactive = false)
void version() void version()
{ {
cout << "eth version " << dev::Version << endl; 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 << "Client database version: " << dev::eth::c_databaseVersion << endl;
cout << "Build: " << DEV_QUOTED(ETH_BUILD_PLATFORM) << "/" << DEV_QUOTED(ETH_BUILD_TYPE) << endl; cout << "Build: " << DEV_QUOTED(ETH_BUILD_PLATFORM) << "/" << DEV_QUOTED(ETH_BUILD_TYPE) << endl;
exit(0); exit(0);
@ -197,7 +199,9 @@ enum class NodeMode
int main(int argc, char** argv) int main(int argc, char** argv)
{ {
string listenIP;
unsigned short listenPort = 30303; unsigned short listenPort = 30303;
string publicIP;
string remoteHost; string remoteHost;
unsigned short remotePort = 30303; unsigned short remotePort = 30303;
string dbPath; string dbPath;
@ -209,11 +213,10 @@ int main(int argc, char** argv)
#if ETH_JSONRPC #if ETH_JSONRPC
int jsonrpc = -1; int jsonrpc = -1;
#endif #endif
string publicIP;
bool bootstrap = false; bool bootstrap = false;
bool upnp = true; bool upnp = true;
bool useLocal = false;
bool forceMining = false; bool forceMining = false;
bool killChain = false;
bool jit = false; bool jit = false;
bool structuredLogging = false; bool structuredLogging = false;
string structuredLoggingFormat = "%Y-%m-%dT%H:%M:%S"; string structuredLoggingFormat = "%Y-%m-%dT%H:%M:%S";
@ -247,7 +250,9 @@ int main(int argc, char** argv)
for (int i = 1; i < argc; ++i) for (int i = 1; i < argc; ++i)
{ {
string arg = argv[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]); listenPort = (short)atoi(argv[++i]);
else if ((arg == "-u" || arg == "--public-ip" || arg == "--public") && i + 1 < argc) else if ((arg == "-u" || arg == "--public-ip" || arg == "--public") && i + 1 < argc)
publicIP = argv[++i]; publicIP = argv[++i];
@ -268,15 +273,14 @@ int main(int argc, char** argv)
return -1; return -1;
} }
} }
else if (arg == "-L" || arg == "--local-networking") else if (arg == "-K" || arg == "--kill-blockchain")
useLocal = true; killChain = true;
else if ((arg == "-c" || arg == "--client-name") && i + 1 < argc) else if ((arg == "-c" || arg == "--client-name") && i + 1 < argc)
clientName = argv[++i]; clientName = argv[++i];
else if ((arg == "-a" || arg == "--address" || arg == "--coinbase-address") && i + 1 < argc) else if ((arg == "-a" || arg == "--address" || arg == "--coinbase-address") && i + 1 < argc)
{
try try
{ {
coinbase = h160(fromHex(argv[++i], ThrowType::Throw)); coinbase = h160(fromHex(argv[++i], WhenError::Throw));
} }
catch (BadHexCharacter& _e) catch (BadHexCharacter& _e)
{ {
@ -289,7 +293,6 @@ int main(int argc, char** argv)
cwarn << "coinbase rejected"; cwarn << "coinbase rejected";
break; break;
} }
}
else if ((arg == "-s" || arg == "--secret") && i + 1 < argc) else if ((arg == "-s" || arg == "--secret") && i + 1 < argc)
us = KeyPair(h256(fromHex(argv[++i]))); us = KeyPair(h256(fromHex(argv[++i])));
else if (arg == "--structured-logging-format" && i + 1 < argc) else if (arg == "--structured-logging-format" && i + 1 < argc)
@ -300,20 +303,24 @@ int main(int argc, char** argv)
dbPath = argv[++i]; dbPath = argv[++i];
else if ((arg == "-B" || arg == "--block-fees") && i + 1 < argc) else if ((arg == "-B" || arg == "--block-fees") && i + 1 < argc)
{ {
try { try
{
blockFees = stof(argv[++i]); blockFees = stof(argv[++i]);
} }
catch (...) { catch (...)
{
cerr << "Bad " << arg << " option: " << argv[++i] << endl; cerr << "Bad " << arg << " option: " << argv[++i] << endl;
return -1; return -1;
} }
} }
else if ((arg == "-e" || arg == "--ether-price") && i + 1 < argc) else if ((arg == "-e" || arg == "--ether-price") && i + 1 < argc)
{ {
try { try
{
etherPrice = stof(argv[++i]); etherPrice = stof(argv[++i]);
} }
catch (...) { catch (...)
{
cerr << "Bad " << arg << " option: " << argv[++i] << endl; cerr << "Bad " << arg << " option: " << argv[++i] << endl;
return -1; return -1;
} }
@ -364,7 +371,7 @@ int main(int argc, char** argv)
interactive = true; interactive = true;
#if ETH_JSONRPC #if ETH_JSONRPC
else if ((arg == "-j" || arg == "--json-rpc")) 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) else if (arg == "--json-rpc-port" && i + 1 < argc)
jsonrpc = atoi(argv[++i]); jsonrpc = atoi(argv[++i]);
#endif #endif
@ -401,7 +408,10 @@ int main(int argc, char** argv)
else if (arg == "-V" || arg == "--version") else if (arg == "-V" || arg == "--version")
version(); version();
else else
remoteHost = argv[i]; {
cerr << "Invalid argument: " << arg << endl;
exit(-1);
}
} }
if (!clientName.empty()) if (!clientName.empty())
@ -411,13 +421,13 @@ int main(int argc, char** argv)
StructuredLogger::get().initialize(structuredLogging, structuredLoggingFormat); StructuredLogger::get().initialize(structuredLogging, structuredLoggingFormat);
VMFactory::setKind(jit ? VMKind::JIT : VMKind::Interpreter); 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"); 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" : ""); std::string clientImplString = "Ethereum(++)/" + clientName + "v" + dev::Version + "/" DEV_QUOTED(ETH_BUILD_TYPE) "/" DEV_QUOTED(ETH_BUILD_PLATFORM) + (jit ? "/JIT" : "");
dev::WebThreeDirect web3( dev::WebThreeDirect web3(
clientImplString, clientImplString,
dbPath, dbPath,
false, killChain,
mode == NodeMode::Full ? set<string>{"eth", "shh"} : set<string>(), mode == NodeMode::Full ? set<string>{"eth", "shh"} : set<string>(),
netPrefs, netPrefs,
&nodesState, &nodesState,
@ -434,20 +444,21 @@ int main(int argc, char** argv)
c->setAddress(coinbase); c->setAddress(coinbase);
} }
cout << "Address: " << endl << toHex(us.address().asArray()) << endl; cout << "Transaction Signer: " << us.address() << endl;
cout << "Mining Benefactor: " << coinbase << endl;
web3.startNetwork(); web3.startNetwork();
if (bootstrap) if (bootstrap)
web3.connect(Host::pocHost()); web3.addNode(p2p::NodeId(), Host::pocHost());
if (remoteHost.size()) if (remoteHost.size())
web3.connect(remoteHost, remotePort); web3.addNode(p2p::NodeId(), remoteHost + ":" + toString(remotePort));
#if ETH_JSONRPC #if ETH_JSONRPC
shared_ptr<WebThreeStubServer> jsonrpcServer; shared_ptr<WebThreeStubServer> jsonrpcServer;
unique_ptr<jsonrpc::AbstractServerConnector> jsonrpcConnector; unique_ptr<jsonrpc::AbstractServerConnector> jsonrpcConnector;
if (jsonrpc > -1) if (jsonrpc > -1)
{ {
jsonrpcConnector = unique_ptr<jsonrpc::AbstractServerConnector>(new jsonrpc::HttpServer(jsonrpc)); jsonrpcConnector = unique_ptr<jsonrpc::AbstractServerConnector>(new jsonrpc::HttpServer(jsonrpc, "", "", SensibleHttpThreads));
jsonrpcServer = shared_ptr<WebThreeStubServer>(new WebThreeStubServer(*jsonrpcConnector.get(), web3, vector<KeyPair>({us}))); jsonrpcServer = shared_ptr<WebThreeStubServer>(new WebThreeStubServer(*jsonrpcConnector.get(), web3, vector<KeyPair>({us})));
jsonrpcServer->setIdentities({us}); jsonrpcServer->setIdentities({us});
jsonrpcServer->StartListening(); jsonrpcServer->StartListening();
@ -500,7 +511,7 @@ int main(int argc, char** argv)
string addr; string addr;
unsigned port; unsigned port;
iss >> addr >> port; iss >> addr >> port;
web3.connect(addr, (short)port); web3.addNode(p2p::NodeId(), addr + ":" + toString(port ? port : p2p::c_defaultIPPort));
} }
else if (cmd == "netstop") else if (cmd == "netstop")
{ {
@ -572,8 +583,8 @@ int main(int argc, char** argv)
else if (cmd == "jsonstart") else if (cmd == "jsonstart")
{ {
if (jsonrpc < 0) if (jsonrpc < 0)
jsonrpc = 8080; jsonrpc = SensibleHttpPort;
jsonrpcConnector = unique_ptr<jsonrpc::AbstractServerConnector>(new jsonrpc::HttpServer(jsonrpc)); jsonrpcConnector = unique_ptr<jsonrpc::AbstractServerConnector>(new jsonrpc::HttpServer(jsonrpc, "", "", SensibleHttpThreads));
jsonrpcServer = shared_ptr<WebThreeStubServer>(new WebThreeStubServer(*jsonrpcConnector.get(), web3, vector<KeyPair>({us}))); jsonrpcServer = shared_ptr<WebThreeStubServer>(new WebThreeStubServer(*jsonrpcConnector.get(), web3, vector<KeyPair>({us})));
jsonrpcServer->setIdentities({us}); jsonrpcServer->setIdentities({us});
jsonrpcServer->StartListening(); jsonrpcServer->StartListening();
@ -675,12 +686,13 @@ int main(int argc, char** argv)
else else
cwarn << "Require parameters: submitTransaction ADDRESS AMOUNT GASPRICE GAS SECRET DATA"; cwarn << "Require parameters: submitTransaction ADDRESS AMOUNT GASPRICE GAS SECRET DATA";
} }
#if ETH_FATDB
else if (c && cmd == "listcontracts") else if (c && cmd == "listcontracts")
{ {
auto acs =c->addresses(); auto acs =c->addresses();
string ss; string ss;
for (auto const& i: acs) 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)) + "]"; ss = toString(i) + " : " + toString( c->balanceAt(i)) + " [" + toString((unsigned) c->countAt(i)) + "]";
cout << ss << endl; cout << ss << endl;
@ -691,12 +703,13 @@ int main(int argc, char** argv)
auto acs =c->addresses(); auto acs =c->addresses();
string ss; string ss;
for (auto const& i: acs) 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)) + "]"; ss = toString(i) + " : " + toString( c->balanceAt(i)) + " [" + toString((unsigned) c->countAt(i)) + "]";
cout << ss << endl; cout << ss << endl;
} }
} }
#endif
else if (c && cmd == "send") else if (c && cmd == "send")
{ {
if (iss.peek() != -1) if (iss.peek() != -1)
@ -720,7 +733,7 @@ int main(int argc, char** argv)
u256 minGas = (u256)Client::txGas(bytes(), 0); u256 minGas = (u256)Client::txGas(bytes(), 0);
try try
{ {
Address dest = h160(fromHex(hexAddr, ThrowType::Throw)); Address dest = h160(fromHex(hexAddr, WhenError::Throw));
c->submitTransaction(us.secret(), amount, dest, bytes(), minGas); c->submitTransaction(us.secret(), amount, dest, bytes(), minGas);
} }
catch (BadHexCharacter& _e) catch (BadHexCharacter& _e)
@ -764,7 +777,7 @@ int main(int argc, char** argv)
stringstream ssc; stringstream ssc;
try try
{ {
init = fromHex(sinit, ThrowType::Throw); init = fromHex(sinit, WhenError::Throw);
} }
catch (BadHexCharacter& _e) catch (BadHexCharacter& _e)
{ {
@ -822,11 +835,8 @@ int main(int argc, char** argv)
Executive e(state, c->blockChain(), 0); Executive e(state, c->blockChain(), 0);
Transaction t = state.pending()[index]; Transaction t = state.pending()[index];
state = state.fromPending(index); state = state.fromPending(index);
bytes r = t.rlp();
try try
{ {
e.setup(&r);
OnOpFunc oof; OnOpFunc oof;
if (format == "pretty") if (format == "pretty")
oof = [&](uint64_t steps, Instruction instr, bigint newMemSize, bigint gasCost, dev::eth::VM* vvm, dev::eth::ExtVMFace const* vextVM) oof = [&](uint64_t steps, Instruction instr, bigint newMemSize, bigint gasCost, dev::eth::VM* vvm, dev::eth::ExtVMFace const* vextVM)
@ -859,7 +869,9 @@ int main(int argc, char** argv)
f << toHex(dev::toCompactBigEndian(i.first, 1)) << " " << toHex(dev::toCompactBigEndian(i.second, 1)) << endl; 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; 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(); e.finalize();
} }
catch(Exception const& _e) catch(Exception const& _e)
@ -883,10 +895,10 @@ int main(int argc, char** argv)
try try
{ {
auto storage =c->storageAt(h, 0); auto storage =c->storageAt(h, PendingBlock);
for (auto const& i: storage) for (auto const& i: storage)
s << "@" << showbase << hex << i.first << " " << showbase << hex << i.second << endl; 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"; string outFile = getDataDir() + "/" + rechex + ".evm";
ofstream ofs; ofstream ofs;
@ -925,7 +937,7 @@ int main(int argc, char** argv)
{ {
try try
{ {
coinbase = h160(fromHex(hexAddr, ThrowType::Throw)); coinbase = h160(fromHex(hexAddr, WhenError::Throw));
} }
catch (BadHexCharacter& _e) catch (BadHexCharacter& _e)
{ {

35
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 )

129
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 <http://www.gnu.org/licenses/>.
*/
/** @file CommandLineInterface.cpp
* @author Marek Kotewicz <marek@ethdev.com>
* @date 2015
*/
#include <string>
#include <iostream>
#include <fstream>
#include <csignal>
#include <thread>
#include <boost/filesystem.hpp>
#include <jsonrpccpp/server/connectors/httpserver.h>
#include <libtestutils/Common.h>
#include <libtestutils/BlockChainLoader.h>
#include <libtestutils/FixedClient.h>
#include <libtestutils/FixedWebThreeServer.h>
#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<vector<string>>()->required(), "input file")
("test", po::value<vector<string>>()->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<vector<string>>()[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<vector<string>>()[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<FixedWebThreeServer> 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));
}

46
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 <http://www.gnu.org/licenses/>.
*/
/** CommandLineInterface.h
* @author Marek Kotewicz <marek@ethdev.com>
* @date 2015
*/
#pragma once
#include <json/json.h>
#include <boost/program_options.hpp>
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;
};

34
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 <http://www.gnu.org/licenses/>.
*/
/** main.cpp
* @author Marek Kotewicz <c@ethdev.com>
* @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;
}

4
evmjit/libevmjit-cpp/Env.cpp

@ -52,7 +52,6 @@ extern "C"
auto endowment = llvm2eth(*_endowment); auto endowment = llvm2eth(*_endowment);
if (_env->balance(_env->myAddress) >= endowment && _env->depth < 1024) if (_env->balance(_env->myAddress) >= endowment && _env->depth < 1024)
{ {
_env->subBalance(endowment);
u256 gas = *io_gas; u256 gas = *io_gas;
h256 address(_env->create(endowment, gas, {_initBeg, _initSize}, {}), h256::AlignRight); h256 address(_env->create(endowment, gas, {_initBeg, _initSize}, {}), h256::AlignRight);
*io_gas = static_cast<int64_t>(gas); *io_gas = static_cast<int64_t>(gas);
@ -89,10 +88,7 @@ extern "C"
auto ret = false; auto ret = false;
auto callGas = u256{_callGas}; auto callGas = u256{_callGas};
if (_env->balance(_env->myAddress) >= value && _env->depth < 1024) if (_env->balance(_env->myAddress) >= value && _env->depth < 1024)
{
_env->subBalance(value);
ret = _env->call(receiveAddress, value, {_inBeg, _inSize}, callGas, {_outBeg, _outSize}, {}, {}, codeAddress); ret = _env->call(receiveAddress, value, {_inBeg, _inSize}, callGas, {_outBeg, _outSize}, {}, {}, codeAddress);
}
*io_gas += static_cast<int64_t>(callGas); // it is never more than initial _callGas *io_gas += static_cast<int64_t>(callGas); // it is never more than initial _callGas
return ret; return ret;

46
evmjit/libevmjit/Arith256.cpp

@ -371,17 +371,18 @@ llvm::Value* Arith256::mul(llvm::Value* _arg1, llvm::Value* _arg2)
std::pair<llvm::Value*, llvm::Value*> Arith256::div(llvm::Value* _arg1, llvm::Value* _arg2) std::pair<llvm::Value*, llvm::Value*> Arith256::div(llvm::Value* _arg1, llvm::Value* _arg2)
{ {
if (auto c1 = llvm::dyn_cast<llvm::ConstantInt>(_arg1)) // FIXME: Disabled because of llvm::APInt::urem bug
{ // if (auto c1 = llvm::dyn_cast<llvm::ConstantInt>(_arg1))
if (auto c2 = llvm::dyn_cast<llvm::ConstantInt>(_arg2)) // {
{ // if (auto c2 = llvm::dyn_cast<llvm::ConstantInt>(_arg2))
if (!c2->getValue()) // {
return std::make_pair(Constant::get(0), Constant::get(0)); // if (!c2->getValue())
auto div = Constant::get(c1->getValue().udiv(c2->getValue())); // return std::make_pair(Constant::get(0), Constant::get(0));
auto mod = Constant::get(c1->getValue().urem(c2->getValue())); // auto div = Constant::get(c1->getValue().udiv(c2->getValue()));
return std::make_pair(div, mod); // auto mod = Constant::get(c1->getValue().urem(c2->getValue()));
} // return std::make_pair(div, mod);
} // }
// }
auto r = createCall(getDivFunc(Type::Word), {_arg1, _arg2}); auto r = createCall(getDivFunc(Type::Word), {_arg1, _arg2});
auto div = m_builder.CreateExtractValue(r, 0, "div"); auto div = m_builder.CreateExtractValue(r, 0, "div");
@ -391,17 +392,18 @@ std::pair<llvm::Value*, llvm::Value*> Arith256::div(llvm::Value* _arg1, llvm::Va
std::pair<llvm::Value*, llvm::Value*> Arith256::sdiv(llvm::Value* _x, llvm::Value* _y) std::pair<llvm::Value*, llvm::Value*> Arith256::sdiv(llvm::Value* _x, llvm::Value* _y)
{ {
if (auto c1 = llvm::dyn_cast<llvm::ConstantInt>(_x)) // FIXME: Disabled because of llvm::APInt::urem bug
{ // if (auto c1 = llvm::dyn_cast<llvm::ConstantInt>(_x))
if (auto c2 = llvm::dyn_cast<llvm::ConstantInt>(_y)) // {
{ // if (auto c2 = llvm::dyn_cast<llvm::ConstantInt>(_y))
if (!c2->getValue()) // {
return std::make_pair(Constant::get(0), Constant::get(0)); // if (!c2->getValue())
auto div = Constant::get(c1->getValue().sdiv(c2->getValue())); // return std::make_pair(Constant::get(0), Constant::get(0));
auto mod = Constant::get(c1->getValue().srem(c2->getValue())); // auto div = Constant::get(c1->getValue().sdiv(c2->getValue()));
return std::make_pair(div, mod); // 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 xIsNeg = m_builder.CreateICmpSLT(_x, Constant::get(0));
auto xNeg = m_builder.CreateSub(Constant::get(0), _x); auto xNeg = m_builder.CreateSub(Constant::get(0), _x);

17
evmjit/libevmjit/ExecutionEngine.cpp

@ -4,6 +4,8 @@
#include <mutex> #include <mutex>
#include <iostream> #include <iostream>
#include <unordered_map> #include <unordered_map>
#include <cstdlib>
#include <cstring>
#include "preprocessor/llvm_includes_start.h" #include "preprocessor/llvm_includes_start.h"
#include <llvm/IR/Module.h> #include <llvm/IR/Module.h>
@ -82,7 +84,20 @@ void parseOptions()
{ {
static llvm::llvm_shutdown_obj shutdownObj{}; static llvm::llvm_shutdown_obj shutdownObj{};
cl::AddExtraVersionPrinter(printVersion); cl::AddExtraVersionPrinter(printVersion);
cl::ParseEnvironmentOptions("evmjit", "EVMJIT", "Ethereum EVM JIT Compiler"); //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");
} }
} }

6
libdevcore/CommonData.cpp

@ -78,7 +78,7 @@ int dev::fromHex(char _i)
BOOST_THROW_EXCEPTION(BadHexCharacter() << errinfo_invalidSymbol(_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; unsigned s = (_s[0] == '0' && _s[1] == 'x') ? 2 : 0;
std::vector<uint8_t> ret; std::vector<uint8_t> ret;
@ -96,7 +96,7 @@ bytes dev::fromHex(std::string const& _s, ThrowType _throw)
#ifndef BOOST_NO_EXCEPTIONS #ifndef BOOST_NO_EXCEPTIONS
cwarn << boost::current_exception_diagnostic_information(); cwarn << boost::current_exception_diagnostic_information();
#endif #endif
if (_throw == ThrowType::Throw) if (_throw == WhenError::Throw)
throw; throw;
} }
for (unsigned i = s; i < _s.size(); i += 2) 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 #ifndef BOOST_NO_EXCEPTIONS
cwarn << boost::current_exception_diagnostic_information(); cwarn << boost::current_exception_diagnostic_information();
#endif #endif
if (_throw == ThrowType::Throw) if (_throw == WhenError::Throw)
throw; throw;
} }
return ret; return ret;

20
libdevcore/CommonData.h

@ -35,21 +35,22 @@ namespace dev
// String conversion functions, mainly to/from hex/nibble/byte representations. // String conversion functions, mainly to/from hex/nibble/byte representations.
enum class ThrowType enum class WhenError
{ {
NoThrow = 0, DontThrow = 0,
Throw = 1, Throw = 1,
}; };
/// Convert a series of bytes to the corresponding string of hex duplets. /// 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" /// @example toHex("A\x69") == "4169"
template <class _T> template <class _T>
std::string toHex(_T const& _data, int _w = 2) std::string toHex(_T const& _data, int _w = 2)
{ {
std::ostringstream ret; std::ostringstream ret;
unsigned ii = 0;
for (auto i: _data) for (auto i: _data)
ret << std::hex << std::setfill('0') << std::setw(_w) << (int)(typename std::make_unsigned<decltype(i)>::type)i; ret << std::hex << std::setfill('0') << std::setw(ii++ ? 2 : _w) << (int)(typename std::make_unsigned<decltype(i)>::type)i;
return ret.str(); return ret.str();
} }
@ -59,8 +60,8 @@ int fromHex(char _i);
/// Converts a (printable) ASCII hex string into the corresponding byte stream. /// Converts a (printable) ASCII hex string into the corresponding byte stream.
/// @example fromHex("41626261") == asBytes("Abba") /// @example fromHex("41626261") == asBytes("Abba")
/// If _throw = ThrowType::NoThrow, it replaces bad hex characters with 0's, otherwise it will throw an exception. /// If _throw = ThrowType::DontThrow, it replaces bad hex characters with 0's, otherwise it will throw an exception.
bytes fromHex(std::string const& _s, ThrowType _throw = ThrowType::NoThrow); bytes fromHex(std::string const& _s, WhenError _throw = WhenError::DontThrow);
#if 0 #if 0
std::string toBase58(bytesConstRef _data); 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())); 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. /// Converts a string to a byte array containing the string's (byte) data.
inline bytes asBytes(std::string const& _b) inline bytes asBytes(std::string const& _b)
{ {

2
libdevcore/CommonIO.cpp

@ -112,6 +112,6 @@ string dev::contentsString(std::string const& _file)
void dev::writeFile(std::string const& _file, bytesConstRef _data) 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());
} }

2
libdevcore/Exceptions.h

@ -44,6 +44,8 @@ struct BadHexCharacter: virtual Exception {};
struct RLPException: virtual Exception {}; struct RLPException: virtual Exception {};
struct BadCast: virtual RLPException {}; struct BadCast: virtual RLPException {};
struct BadRLP: virtual RLPException {}; struct BadRLP: virtual RLPException {};
struct OversizeRLP: virtual RLPException {};
struct UndersizeRLP: virtual RLPException {};
struct NoNetworking: virtual Exception {}; struct NoNetworking: virtual Exception {};
struct NoUPnPDevice: virtual Exception {}; struct NoUPnPDevice: virtual Exception {};
struct RootNotFound: virtual Exception {}; struct RootNotFound: virtual Exception {};

1
libdevcore/FixedHash.h

@ -141,6 +141,7 @@ public:
return ret; return ret;
} }
/// @returns a random valued object.
static FixedHash random() { return random(s_fixedHashEngine); } static FixedHash random() { return random(s_fixedHashEngine); }
/// A generic std::hash compatible function object. /// A generic std::hash compatible function object.

31
libdevcore/RLP.cpp

@ -26,12 +26,31 @@ using namespace dev;
bytes dev::RLPNull = rlp(""); bytes dev::RLPNull = rlp("");
bytes dev::RLPEmptyList = rlpList(); 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++() RLP::iterator& RLP::iterator::operator++()
{ {
if (m_remaining) if (m_remaining)
{ {
m_lastItem.retarget(m_lastItem.next().data(), 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<unsigned>(m_remaining, m_lastItem.size()); m_remaining -= std::min<unsigned>(m_remaining, m_lastItem.size());
} }
else else
@ -44,7 +63,7 @@ RLP::iterator::iterator(RLP const& _parent, bool _begin)
if (_begin && _parent.isList()) if (_begin && _parent.isList())
{ {
auto pl = _parent.payload(); 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(); m_remaining = pl.size() - m_lastItem.size();
} }
else else
@ -58,17 +77,17 @@ RLP RLP::operator[](unsigned _i) const
{ {
if (_i < m_lastIndex) if (_i < m_lastIndex)
{ {
m_lastEnd = RLP(payload()).actualSize(); m_lastEnd = RLP(payload(), ThrowOnFail | FailIfTooSmall).actualSize();
m_lastItem = payload().cropped(0, m_lastEnd); m_lastItem = payload().cropped(0, m_lastEnd);
m_lastIndex = 0; m_lastIndex = 0;
} }
for (; m_lastIndex < _i && m_lastItem.size(); ++m_lastIndex) for (; m_lastIndex < _i && m_lastItem.size(); ++m_lastIndex)
{ {
m_lastItem = payload().cropped(m_lastEnd); 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(); m_lastEnd += m_lastItem.size();
} }
return RLP(m_lastItem); return RLP(m_lastItem, ThrowOnFail | FailIfTooSmall);
} }
RLPs RLP::toList() const RLPs RLP::toList() const
@ -154,7 +173,7 @@ unsigned RLP::items() const
bytesConstRef d = payload().cropped(0, length()); bytesConstRef d = payload().cropped(0, length());
unsigned i = 0; unsigned i = 0;
for (; d.size(); ++i) for (; d.size(); ++i)
d = d.cropped(RLP(d).actualSize()); d = d.cropped(RLP(d, ThrowOnFail | FailIfTooSmall).actualSize());
return i; return i;
} }
return 0; return 0;

34
libdevcore/RLP.h

@ -62,20 +62,34 @@ static const byte c_rlpListIndLenZero = c_rlpListStart + c_rlpListImmLenCount -
class RLP class RLP
{ {
public: 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. /// Construct a null node.
RLP() {} RLP() {}
/// Construct a node of value given in the bytes. /// 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. /// 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. /// 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. /// 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. /// The bare data of the RLP.
bytesConstRef data() const { return m_data; } bytesConstRef data() const { return m_data; }
@ -236,18 +250,6 @@ public:
return ret; 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. /// Converts to int of type given; if isString(), decodes as big-endian bytestream. @returns 0 if not an int or string.
template <class _T = unsigned> _T toInt(int _flags = Strict) const template <class _T = unsigned> _T toInt(int _flags = Strict) const
{ {

3
libdevcore/vector_ref.h

@ -39,7 +39,8 @@ public:
size_t size() const { return m_count; } size_t size() const { return m_count; }
bool empty() 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> 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>(); } 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(_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 retarget(std::vector<_T> const& _t) { m_data = _t.data(); m_count = _t.size(); }
void copyTo(vector_ref<typename std::remove_const<_T>::type> _t) const { memcpy(_t.data(), m_data, std::min(_t.size(), m_count) * sizeof(_T)); } void copyTo(vector_ref<typename std::remove_const<_T>::type> _t) const { memcpy(_t.data(), m_data, std::min(_t.size(), m_count) * sizeof(_T)); }

9
libdevcrypto/FileSystem.cpp

@ -32,12 +32,15 @@
using namespace std; using namespace std;
using namespace dev; using namespace dev;
std::string dev::getDataDir() std::string dev::getDataDir(std::string _prefix)
{ {
if (_prefix.empty())
_prefix = "ethereum";
#ifdef _WIN32 #ifdef _WIN32
_prefix[0] = toupper(_prefix[0]);
char path[1024] = ""; char path[1024] = "";
if (SHGetSpecialFolderPathA(NULL, path, CSIDL_APPDATA, true)) if (SHGetSpecialFolderPathA(NULL, path, CSIDL_APPDATA, true))
return (boost::filesystem::path(path) / "Ethereum").string(); return (boost::filesystem::path(path) / _prefix).string();
else else
{ {
#ifndef _MSC_VER // todo? #ifndef _MSC_VER // todo?
@ -57,7 +60,7 @@ std::string dev::getDataDir()
// This eventually needs to be put in proper wrapper (to support sandboxing) // This eventually needs to be put in proper wrapper (to support sandboxing)
return (dataDirPath / "Library/Application Support/Ethereum").string(); return (dataDirPath / "Library/Application Support/Ethereum").string();
#else #else
return (dataDirPath / ".ethereum").string(); return (dataDirPath / ("." + _prefix)).string();
#endif #endif
#endif #endif
} }

2
libdevcrypto/FileSystem.h

@ -30,6 +30,6 @@ namespace dev
{ {
/// @returns the path for user data. /// @returns the path for user data.
std::string getDataDir(); std::string getDataDir(std::string _prefix = "ethereum");
} }

2
libethcore/Common.cpp

@ -34,7 +34,7 @@ namespace eth
{ {
const unsigned c_ethashVersion = c_ethashRevision; const unsigned c_ethashVersion = c_ethashRevision;
const unsigned c_protocolVersion = 58; const unsigned c_protocolVersion = 59;
const unsigned c_databaseBaseVersion = 8; const unsigned c_databaseBaseVersion = 8;
#if ETH_FATDB #if ETH_FATDB
const unsigned c_databaseVersionModifier = 1; const unsigned c_databaseVersionModifier = 1;

11
libethcore/Common.h

@ -71,5 +71,16 @@ static const u256 wei = exp10<0>();
using Nonce = h64; 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
};
} }
} }

30
libethcore/Ethasher.cpp

@ -71,10 +71,10 @@ bytesConstRef Ethasher::full(BlockInfo const& _header)
m_fulls.erase(m_fulls.begin()); m_fulls.erase(m_fulls.begin());
} }
try { try {
boost::filesystem::create_directories(getDataDir() + "/ethashcache"); boost::filesystem::create_directories(getDataDir("ethash"));
} catch (...) {} } catch (...) {}
std::string memoFile = getDataDir() + "/ethashcache/full"; std::string memoFile = getDataDir("ethash") + "/full";
auto info = rlpList(c_ethashRevision, _header.seedHash()); auto info = rlpList(c_ethashRevision, _header.seedHash());
if (boost::filesystem::exists(memoFile) && contents(memoFile + ".info") != info) if (boost::filesystem::exists(memoFile) && contents(memoFile + ".info") != info)
boost::filesystem::remove(memoFile); boost::filesystem::remove(memoFile);
@ -112,15 +112,31 @@ bool Ethasher::verify(BlockInfo const& _header)
h256 boundary = u256((bigint(1) << 256) / _header.difficulty); h256 boundary = u256((bigint(1) << 256) / _header.difficulty);
// should be equivalent to: bool ret = ethash_quick_check_difficulty(
auto r = eval(_header);
return r.mixHash == _header.mixHash && r.value <= boundary;
return ethash_quick_check_difficulty(
_header.headerHash(WithoutNonce).data(), _header.headerHash(WithoutNonce).data(),
(uint64_t)(u64)_header.nonce, (uint64_t)(u64)_header.nonce,
_header.mixHash.data(), _header.mixHash.data(),
boundary.data()); boundary.data());
#if ETH_DEBUG
auto result = eval(_header);
if ((result.value <= boundary && result.mixHash == _header.mixHash) != ret)
{
cwarn << "Assertion failure coming: evaluated result gives different outcome to ethash_quick_check_difficulty";
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;
}
assert((result.value <= boundary) == ret);
if (result.value <= boundary)
assert(result.mixHash == _header.mixHash);
#endif
return ret;
} }
Ethasher::Result Ethasher::eval(BlockInfo const& _header, Nonce const& _nonce) Ethasher::Result Ethasher::eval(BlockInfo const& _header, Nonce const& _nonce)

2
libethcore/Exceptions.h

@ -38,6 +38,7 @@ using errinfo_difficulty = boost::error_info<struct tag_difficulty, u256>;
using BadFieldError = boost::tuple<errinfo_field, errinfo_data>; using BadFieldError = boost::tuple<errinfo_field, errinfo_data>;
struct DatabaseAlreadyOpen: virtual dev::Exception {}; struct DatabaseAlreadyOpen: virtual dev::Exception {};
struct NotEnoughAvailableSpace: virtual dev::Exception {};
struct NotEnoughCash: virtual dev::Exception {}; struct NotEnoughCash: virtual dev::Exception {};
struct GasPriceTooLow: virtual dev::Exception {}; struct GasPriceTooLow: virtual dev::Exception {};
struct BlockGasLimitReached: virtual dev::Exception {}; struct BlockGasLimitReached: virtual dev::Exception {};
@ -59,7 +60,6 @@ struct InvalidGasUsed: virtual dev::Exception {};
class InvalidTransactionsHash: virtual public dev::Exception {}; class InvalidTransactionsHash: virtual public dev::Exception {};
struct InvalidTransaction: virtual dev::Exception {}; struct InvalidTransaction: virtual dev::Exception {};
struct InvalidDifficulty: virtual dev::Exception {}; struct InvalidDifficulty: virtual dev::Exception {};
struct InvalidSeedHash: virtual dev::Exception {};
class InvalidGasLimit: virtual public dev::Exception {}; class InvalidGasLimit: virtual public dev::Exception {};
struct InvalidTransactionGasUsed: virtual dev::Exception {}; struct InvalidTransactionGasUsed: virtual dev::Exception {};
struct InvalidTransactionsStateRoot: virtual dev::Exception {}; struct InvalidTransactionsStateRoot: virtual dev::Exception {};

1
libethcore/Params.cpp

@ -37,6 +37,7 @@ u256 const c_gasLimitBoundDivisor = 1024;
u256 const c_minimumDifficulty = 131072; u256 const c_minimumDifficulty = 131072;
u256 const c_difficultyBoundDivisor = 2048; u256 const c_difficultyBoundDivisor = 2048;
u256 const c_durationLimit = 8; 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_tierStepGas[] = {0, 2, 3, 5, 8, 10, 20, 0};
u256 const c_expGas = 10; u256 const c_expGas = 10;
u256 const c_expByteGas = 10; u256 const c_expByteGas = 10;

1
libethcore/Params.h

@ -38,6 +38,7 @@ extern u256 const c_gasLimitBoundDivisor;
extern u256 const c_minimumDifficulty; extern u256 const c_minimumDifficulty;
extern u256 const c_difficultyBoundDivisor; extern u256 const c_difficultyBoundDivisor;
extern u256 const c_durationLimit; extern u256 const c_durationLimit;
extern u256 const c_stackLimit;
extern u256 const c_tierStepGas[8]; ///< Once per operation, for a selection of them. extern u256 const c_tierStepGas[8]; ///< Once per operation, for a selection of them.
extern u256 const c_expGas; ///< Once per EXP instuction. extern u256 const c_expGas; ///< Once per EXP instuction.

27
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 <http://www.gnu.org/licenses/>.
*/
/** @file ABI.cpp
* @author Gav Wood <i@gavwood.com>
* @date 2014
*/
#include "ABI.h"
using namespace std;
using namespace dev;
using namespace dev::eth;

64
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 <http://www.gnu.org/licenses/>.
*/
/** @file ABI.h
* @author Gav Wood <i@gavwood.com>
* @date 2014
*/
#pragma once
#include <libdevcore/Common.h>
#include <libdevcore/FixedHash.h>
#include <libdevcore/CommonData.h>
#include <libdevcrypto/SHA3.h>
namespace dev
{
namespace eth
{
template <class T> struct ABISerialiser {};
template <unsigned N> struct ABISerialiser<FixedHash<N>> { static bytes serialise(FixedHash<N> 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<u256> { static bytes serialise(u256 const& _t) { return h256(_t).asBytes(); } };
template <> struct ABISerialiser<u160> { static bytes serialise(u160 const& _t) { return bytes(12, 0) + h160(_t).asBytes(); } };
template <> struct ABISerialiser<string32> { static bytes serialise(string32 const& _t) { return bytesConstRef((byte const*)_t.data(), 32).toBytes(); } };
inline bytes abiInAux() { return {}; }
template <class T, class ... U> bytes abiInAux(T const& _t, U const& ... _u)
{
return ABISerialiser<T>::serialise(_t) + abiInAux(_u ...);
}
template <class ... T> bytes abiIn(std::string _id, T const& ... _t)
{
return sha3(_id).ref().cropped(0, 4).toBytes() + abiInAux(_t ...);
}
template <class T> struct ABIDeserialiser {};
template <unsigned N> struct ABIDeserialiser<FixedHash<N>> { static FixedHash<N> deserialise(bytesConstRef& io_t) { static_assert(N <= 32, "Parameter sizes must be at most 32 bytes."); FixedHash<N> ret; io_t.cropped(32 - N, N).populate(ret.ref()); io_t = io_t.cropped(32); return ret; } };
template <> struct ABIDeserialiser<u256> { static u256 deserialise(bytesConstRef& io_t) { u256 ret = fromBigEndian<u256>(io_t.cropped(0, 32)); io_t = io_t.cropped(32); return ret; } };
template <> struct ABIDeserialiser<u160> { static u160 deserialise(bytesConstRef& io_t) { u160 ret = fromBigEndian<u160>(io_t.cropped(12, 20)); io_t = io_t.cropped(32); return ret; } };
template <> struct ABIDeserialiser<string32> { 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 <class T> T abiOut(bytes const& _data)
{
bytesConstRef o(&_data);
return ABIDeserialiser<T>::deserialise(o);
}
}
}

69
libethereum/BlockChain.cpp

@ -63,7 +63,7 @@ std::ostream& dev::eth::operator<<(std::ostream& _out, BlockChain const& _bc)
return _out; return _out;
} }
ldb::Slice dev::eth::toSlice(h256 _h, unsigned _sub) ldb::Slice dev::eth::toSlice(h256 const& _h, unsigned _sub)
{ {
#if ALL_COMPILERS_ARE_CPP11_COMPLIANT #if ALL_COMPILERS_ARE_CPP11_COMPLIANT
static thread_local h256 h = _h ^ sha3(h256(u256(_sub))); static thread_local h256 h = _h ^ sha3(h256(u256(_sub)));
@ -131,10 +131,19 @@ void BlockChain::open(std::string _path, bool _killExisting)
o.create_if_missing = true; o.create_if_missing = true;
ldb::DB::Open(o, _path + "/blocks", &m_blocksDB); ldb::DB::Open(o, _path + "/blocks", &m_blocksDB);
ldb::DB::Open(o, _path + "/details", &m_extrasDB); ldb::DB::Open(o, _path + "/details", &m_extrasDB);
if (!m_blocksDB) if (!m_blocksDB || !m_extrasDB)
BOOST_THROW_EXCEPTION(DatabaseAlreadyOpen()); {
if (!m_extrasDB) if (boost::filesystem::space(_path + "/blocks").available < 1024)
BOOST_THROW_EXCEPTION(DatabaseAlreadyOpen()); {
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 (!details(m_genesisHash))
{ {
@ -184,6 +193,19 @@ inline string toString(h256s const& _bs)
return out.str(); return out.str();
} }
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;
}
h256s BlockChain::sync(BlockQueue& _bq, OverlayDB const& _stateDB, unsigned _max) h256s BlockChain::sync(BlockQueue& _bq, OverlayDB const& _stateDB, unsigned _max)
{ {
_bq.tick(*this); _bq.tick(*this);
@ -412,6 +434,9 @@ h256s BlockChain::import(bytes const& _block, OverlayDB const& _db)
WriteGuard l(x_lastBlockHash); WriteGuard l(x_lastBlockHash);
m_lastBlockHash = newHash; m_lastBlockHash = newHash;
} }
noteCanonChanged();
m_extrasDB->Put(m_writeOptions, ldb::Slice("best"), ldb::Slice((char const*)&newHash, 32)); 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); clog(BlockChainNote) << " Imported and best" << td << ". Has" << (details(bi.parentHash).children.size() - 1) << "siblings. Route:" << toString(ret);
StructuredLogger::chainNewHead( StructuredLogger::chainNewHead(
@ -428,7 +453,7 @@ h256s BlockChain::import(bytes const& _block, OverlayDB const& _db)
return ret; return ret;
} }
h256s BlockChain::treeRoute(h256 _from, h256 _to, h256* o_common, bool _pre, bool _post) const h256s BlockChain::treeRoute(h256 const& _from, h256 const& _to, h256* o_common, bool _pre, bool _post) const
{ {
// cdebug << "treeRoute" << _from.abridged() << "..." << _to.abridged(); // cdebug << "treeRoute" << _from.abridged() << "..." << _to.abridged();
if (!_from || !_to) if (!_from || !_to)
@ -438,38 +463,40 @@ h256s BlockChain::treeRoute(h256 _from, h256 _to, h256* o_common, bool _pre, boo
unsigned fn = details(_from).number; unsigned fn = details(_from).number;
unsigned tn = details(_to).number; unsigned tn = details(_to).number;
// cdebug << "treeRoute" << fn << "..." << tn; // cdebug << "treeRoute" << fn << "..." << tn;
h256 from = _from;
while (fn > tn) while (fn > tn)
{ {
if (_pre) if (_pre)
ret.push_back(_from); ret.push_back(from);
_from = details(_from).parent; from = details(from).parent;
fn--; fn--;
// cdebug << "from:" << fn << _from.abridged(); // cdebug << "from:" << fn << _from.abridged();
} }
h256 to = _to;
while (fn < tn) while (fn < tn)
{ {
if (_post) if (_post)
back.push_back(_to); back.push_back(to);
_to = details(_to).parent; to = details(to).parent;
tn--; tn--;
// cdebug << "to:" << tn << _to.abridged(); // cdebug << "to:" << tn << _to.abridged();
} }
while (_from != _to) while (from != to)
{ {
assert(_from); assert(from);
assert(_to); assert(to);
_from = details(_from).parent; from = details(from).parent;
_to = details(_to).parent; to = details(to).parent;
if (_pre) if (_pre)
ret.push_back(_from); ret.push_back(from);
if (_post) if (_post)
back.push_back(_to); back.push_back(to);
fn--; fn--;
tn--; tn--;
// cdebug << "from:" << fn << _from.abridged() << "; to:" << tn << _to.abridged(); // cdebug << "from:" << fn << _from.abridged() << "; to:" << tn << _to.abridged();
} }
if (o_common) if (o_common)
*o_common = _from; *o_common = from;
ret.reserve(ret.size() + back.size()); ret.reserve(ret.size() + back.size());
for (auto it = back.cbegin(); it != back.cend(); ++it) for (auto it = back.cbegin(); it != back.cend(); ++it)
ret.push_back(*it); ret.push_back(*it);
@ -677,7 +704,7 @@ vector<unsigned> BlockChain::withBlockBloom(LogBloom const& _b, unsigned _earlie
return ret; return ret;
} }
h256Set BlockChain::allUnclesFrom(h256 _parent) const 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). // Get all uncles cited given a parent (i.e. featured as uncles/main in parent, parent + 1, ... parent + 5).
h256Set ret; h256Set ret;
@ -692,7 +719,7 @@ h256Set BlockChain::allUnclesFrom(h256 _parent) const
return ret; return ret;
} }
bool BlockChain::isKnown(h256 _hash) const bool BlockChain::isKnown(h256 const& _hash) const
{ {
if (_hash == m_genesisHash) if (_hash == m_genesisHash)
return true; return true;
@ -706,7 +733,7 @@ bool BlockChain::isKnown(h256 _hash) const
return !!d.size(); return !!d.size();
} }
bytes BlockChain::block(h256 _hash) const bytes BlockChain::block(h256 const& _hash) const
{ {
if (_hash == m_genesisHash) if (_hash == m_genesisHash)
return m_genesisBlock; return m_genesisBlock;

51
libethereum/BlockChain.h

@ -30,9 +30,10 @@
#include <chrono> #include <chrono>
#include <libdevcore/Log.h> #include <libdevcore/Log.h>
#include <libdevcore/Exceptions.h> #include <libdevcore/Exceptions.h>
#include <libdevcore/Guards.h>
#include <libethcore/Common.h> #include <libethcore/Common.h>
#include <libethcore/BlockInfo.h> #include <libethcore/BlockInfo.h>
#include <libdevcore/Guards.h> #include <libevm/ExtVMFace.h>
#include "BlockDetails.h" #include "BlockDetails.h"
#include "Account.h" #include "Account.h"
#include "Transaction.h" #include "Transaction.h"
@ -61,10 +62,11 @@ struct BlockChainNote: public LogChannel { static const char* name() { return "=
// TODO: Move all this Genesis stuff into Genesis.h/.cpp // TODO: Move all this Genesis stuff into Genesis.h/.cpp
std::map<Address, Account> const& genesisState(); std::map<Address, Account> const& genesisState();
ldb::Slice toSlice(h256 _h, unsigned _sub = 0); ldb::Slice toSlice(h256 const& _h, unsigned _sub = 0);
using BlocksHash = std::map<h256, bytes>; using BlocksHash = std::map<h256, bytes>;
using TransactionHashes = h256s; using TransactionHashes = h256s;
using UncleHashes = h256s;
enum { enum {
ExtraDetails = 0, ExtraDetails = 0,
@ -104,34 +106,42 @@ public:
h256s import(bytes const& _block, OverlayDB const& _stateDB); h256s import(bytes const& _block, OverlayDB const& _stateDB);
/// Returns true if the given block is known (though not necessarily a part of the canon chain). /// 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. /// 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(h256 const& _hash) const { return BlockInfo(block(_hash)); }
BlockInfo info() const { return BlockInfo(block()); } BlockInfo info() const { return BlockInfo(block()); }
/// Get a block (RLP format) for the given hash (or the most recent mined if none given). Thread-safe. /// 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 block() const { return block(currentHash()); }
/// Get the familial details concerning a block (or the most recent mined if none given). Thread-safe. /// Get the familial details concerning a block (or the most recent mined if none given). Thread-safe.
BlockDetails details(h256 _hash) const { return queryExtras<BlockDetails, ExtraDetails>(_hash, m_details, x_details, NullBlockDetails); } BlockDetails details(h256 const& _hash) const { return queryExtras<BlockDetails, ExtraDetails>(_hash, m_details, x_details, NullBlockDetails); }
BlockDetails details() const { return details(currentHash()); } BlockDetails details() const { return details(currentHash()); }
/// Get the transactions' log blooms of a block (or the most recent mined if none given). Thread-safe. /// 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<BlockLogBlooms, ExtraLogBlooms>(_hash, m_logBlooms, x_logBlooms, NullBlockLogBlooms); } BlockLogBlooms logBlooms(h256 const& _hash) const { return queryExtras<BlockLogBlooms, ExtraLogBlooms>(_hash, m_logBlooms, x_logBlooms, NullBlockLogBlooms); }
BlockLogBlooms logBlooms() const { return logBlooms(currentHash()); } BlockLogBlooms logBlooms() const { return logBlooms(currentHash()); }
/// Get the transactions' receipts of a block (or the most recent mined if none given). Thread-safe. /// Get the transactions' receipts of a block (or the most recent mined if none given). Thread-safe.
BlockReceipts receipts(h256 _hash) const { return queryExtras<BlockReceipts, ExtraReceipts>(_hash, m_receipts, x_receipts, NullBlockReceipts); } BlockReceipts receipts(h256 const& _hash) const { return queryExtras<BlockReceipts, ExtraReceipts>(_hash, m_receipts, x_receipts, NullBlockReceipts); }
BlockReceipts receipts() const { return receipts(currentHash()); } BlockReceipts receipts() const { return receipts(currentHash()); }
/// Get a list of transaction hashes for a given block. Thread-safe. /// 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()); } TransactionHashes transactionHashes() const { return transactionHashes(currentHash()); }
/// Get a list of transaction hashes for a given block. Thread-safe. /// Get a list of uncle hashes for a given block. Thread-safe.
h256 numberHash(u256 _index) const { if (!_index) return genesisHash(); return queryExtras<BlockHash, ExtraBlockHash>(h256(_index), m_blockHashes, x_blockHashes, NullBlockHash).value; } 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<BlockHash, ExtraBlockHash>(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. /** Get the block blooms for a number of blocks. Thread-safe.
* @returns the object pertaining to the blocks: * @returns the object pertaining to the blocks:
@ -154,15 +164,15 @@ public:
std::vector<unsigned> withBlockBloom(LogBloom const& _b, unsigned _earliest, unsigned _latest, unsigned _topLevel, unsigned _index) const; std::vector<unsigned> withBlockBloom(LogBloom const& _b, unsigned _earliest, unsigned _latest, unsigned _topLevel, unsigned _index) const;
/// Get a transaction from its hash. Thread-safe. /// Get a transaction from its hash. Thread-safe.
bytes transaction(h256 _transactionHash) const { TransactionAddress ta = queryExtras<TransactionAddress, ExtraTransactionAddress>(_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<TransactionAddress, ExtraTransactionAddress>(_transactionHash, m_transactionAddresses, x_transactionAddresses, NullTransactionAddress); if (!ta) return bytes(); return transaction(ta.blockHash, ta.index); }
std::pair<h256, unsigned> transactionLocation(h256 _transactionHash) const { TransactionAddress ta = queryExtras<TransactionAddress, ExtraTransactionAddress>(_transactionHash, m_transactionAddresses, x_transactionAddresses, NullTransactionAddress); if (!ta) return std::pair<h256, unsigned>(h256(), 0); return std::make_pair(ta.blockHash, ta.index); } std::pair<h256, unsigned> transactionLocation(h256 const& _transactionHash) const { TransactionAddress ta = queryExtras<TransactionAddress, ExtraTransactionAddress>(_transactionHash, m_transactionAddresses, x_transactionAddresses, NullTransactionAddress); if (!ta) return std::pair<h256, unsigned>(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. /// 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); } bytes transaction(unsigned _i) const { return transaction(currentHash(), _i); }
/// Get a number for the given hash (or the most recent mined if none given). Thread-safe. /// 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()); } unsigned number() const { return number(currentHash()); }
/// Get a given block (RLP format). Thread-safe. /// Get a given block (RLP format). Thread-safe.
@ -174,7 +184,7 @@ public:
/// Get all blocks not allowed as uncles given a parent (i.e. featured as uncles/main in parent, parent + 1, ... parent + 5). /// 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 /// @returns set including the header-hash of every parent (including @a _parent) up to and including generation +5
/// togther with all their quoted uncles. /// togther with all their quoted uncles.
h256Set allUnclesFrom(h256 _parent) const; h256Set allUnclesFrom(h256 const& _parent) const;
/** @returns the hash of all blocks between @a _from and @a _to, all blocks are ordered first by a number of /** @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. * blocks that are parent-to-child, then two sibling blocks, then a number of blocks that are child-to-parent.
@ -190,7 +200,7 @@ public:
* treeRoute(1b, 2a) == { 1b, 1a, 2a }; // *o_common == g * treeRoute(1b, 2a) == { 1b, 1a, 2a }; // *o_common == g
* @endcode * @endcode
*/ */
h256s treeRoute(h256 _from, h256 _to, h256* o_common = nullptr, bool _pre = true, bool _post = true) const; h256s treeRoute(h256 const& _from, h256 const& _to, h256* o_common = nullptr, bool _pre = true, bool _post = true) const;
struct Statistics struct Statistics
{ {
@ -215,7 +225,7 @@ private:
void open(std::string _path, bool _killExisting = false); void open(std::string _path, bool _killExisting = false);
void close(); void close();
template<class T, unsigned N> T queryExtras(h256 _h, std::map<h256, T>& _m, boost::shared_mutex& _x, T const& _n) const template<class T, unsigned N> T queryExtras(h256 const& _h, std::map<h256, T>& _m, boost::shared_mutex& _x, T const& _n) const
{ {
{ {
ReadGuard l(_x); ReadGuard l(_x);
@ -264,6 +274,11 @@ private:
void noteUsed(h256 const& _h, unsigned _extra = (unsigned)-1) const; void noteUsed(h256 const& _h, unsigned _extra = (unsigned)-1) const;
std::chrono::system_clock::time_point m_lastCollection; 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; void updateStats() const;
mutable Statistics m_lastStats; mutable Statistics m_lastStats;

417
libethereum/Client.cpp

@ -40,7 +40,7 @@ VersionChecker::VersionChecker(string const& _dbPath):
{ {
auto protocolContents = contents(m_path + "/protocol"); auto protocolContents = contents(m_path + "/protocol");
auto databaseContents = contents(m_path + "/database"); auto databaseContents = contents(m_path + "/database");
m_ok = RLP(protocolContents).toInt<unsigned>(RLP::LaisezFaire) == c_protocolVersion && RLP(databaseContents).toInt<unsigned>(RLP::LaisezFaire) == c_databaseVersion; m_ok = RLP(protocolContents).toInt<unsigned>(RLP::LaisezFaire) == eth::c_protocolVersion && RLP(databaseContents).toInt<unsigned>(RLP::LaisezFaire) == c_databaseVersion;
} }
void VersionChecker::setOk() void VersionChecker::setOk()
@ -55,7 +55,7 @@ void VersionChecker::setOk()
{ {
cwarn << "Unhandled exception! Failed to create directory: " << m_path << "\n" << boost::current_exception_diagnostic_information(); 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 + "/protocol", rlp(eth::c_protocolVersion));
writeFile(m_path + "/database", rlp(c_databaseVersion)); writeFile(m_path + "/database", rlp(c_databaseVersion));
} }
} }
@ -186,11 +186,6 @@ void Client::doneWorking()
m_postMine = m_preMine; m_postMine = m_preMine;
} }
void Client::flushTransactions()
{
doWork();
}
void Client::killChain() void Client::killChain()
{ {
bool wasMining = isMining(); bool wasMining = isMining();
@ -249,119 +244,31 @@ void Client::clearPending()
noteChanged(changeds); noteChanged(changeds);
} }
unsigned Client::installWatch(h256 _h, Reaping _r)
{
unsigned ret;
{
Guard l(m_filterLock);
ret = m_watches.size() ? m_watches.rbegin()->first + 1 : 0;
m_watches[ret] = ClientWatch(_h, _r);
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, Reaping _r)
{
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, _r);
}
bool Client::uninstallWatch(unsigned _i)
{
cwatch << "XXX" << _i;
Guard l(m_filterLock);
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;
}
void Client::noteChanged(h256Set const& _filters) void Client::noteChanged(h256Set const& _filters)
{ {
Guard l(m_filterLock); Guard l(x_filtersWatches);
if (_filters.size()) if (_filters.size())
cnote << "noteChanged(" << _filters << ")"; cnote << "noteChanged(" << _filters << ")";
// accrue all changes left in each filter into the watches. // accrue all changes left in each filter into the watches.
for (auto& i: m_watches) for (auto& w: m_watches)
if (_filters.count(i.second.id)) if (_filters.count(w.second.id))
{ {
cwatch << "!!!" << i.first << i.second.id; cwatch << "!!!" << w.first << w.second.id;
if (m_filters.count(i.second.id)) if (m_filters.count(w.second.id)) // Normal filtering watch
i.second.changes += m_filters.at(i.second.id).changes; w.second.changes += m_filters.at(w.second.id).changes;
else else // Special ('pending'/'latest') watch
i.second.changes.push_back(LocalisedLogEntry(SpecialLogEntry, 0)); w.second.changes.push_back(LocalisedLogEntry(SpecialLogEntry, 0));
} }
// clear the filters now. // clear the filters now.
for (auto& i: m_filters) for (auto& i: m_filters)
i.second.changes.clear(); 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);
#if ETH_DEBUG
cdebug << "peekWatch" << _watchId;
#endif
auto& w = m_watches.at(_watchId);
#if ETH_DEBUG
cdebug << "lastPoll updated to " << chrono::duration_cast<chrono::seconds>(chrono::system_clock::now().time_since_epoch()).count();
#endif
w.lastPoll = chrono::system_clock::now();
return w.changes;
}
LocalisedLogEntries Client::checkWatch(unsigned _watchId)
{ {
Guard l(m_filterLock); Guard l(x_filtersWatches);
LocalisedLogEntries ret;
#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::seconds>(chrono::system_clock::now().time_since_epoch()).count();
#endif
std::swap(ret, w.changes);
w.lastPoll = chrono::system_clock::now();
return ret;
}
void Client::appendFromNewPending(TransactionReceipt const& _receipt, h256Set& io_changed, h256 _sha3)
{
Guard l(m_filterLock);
for (pair<h256 const, InstalledFilter>& i: m_filters) for (pair<h256 const, InstalledFilter>& 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. // acceptable number.
auto m = i.second.filter.matches(_receipt); auto m = i.second.filter.matches(_receipt);
@ -369,7 +276,7 @@ void Client::appendFromNewPending(TransactionReceipt const& _receipt, h256Set& i
{ {
// filter catches them // filter catches them
for (LogEntry const& l: m) 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); io_changed.insert(i.first);
} }
} }
@ -381,9 +288,9 @@ void Client::appendFromNewBlock(h256 const& _block, h256Set& io_changed)
auto d = m_bc.info(_block); auto d = m_bc.info(_block);
auto br = m_bc.receipts(_block); auto br = m_bc.receipts(_block);
Guard l(m_filterLock); Guard l(x_filtersWatches);
for (pair<h256 const, InstalledFilter>& i: m_filters) for (pair<h256 const, InstalledFilter>& 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. // acceptable number & looks like block may contain a matching log entry.
for (size_t j = 0; j < br.receipts.size(); j++) for (size_t j = 0; j < br.receipts.size(); j++)
{ {
@ -391,10 +298,10 @@ void Client::appendFromNewBlock(h256 const& _block, h256Set& io_changed)
auto m = i.second.filter.matches(tr); auto m = i.second.filter.matches(tr);
if (m.size()) if (m.size())
{ {
auto sha3 = transaction(d.hash, j).sha3(); auto transactionHash = transaction(d.hash, j).sha3();
// filter catches them // filter catches them
for (LogEntry const& l: m) 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); io_changed.insert(i.first);
} }
} }
@ -475,68 +382,6 @@ void Client::setupState(State& _s)
_s.commitToMine(m_bc); _s.commitToMine(m_bc);
} }
void Client::submitTransaction(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());
}
ExecutionResult Client::call(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice, int _blockNumber)
{
ExecutionResult ret;
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 = asOf(_blockNumber);
n = temp.transactionsFrom(toAddress(_secret));
}
Transaction t(_value, _gasPrice, _gas, _dest, _data, n, _secret);
ret = temp.execute(m_bc, t.rlp(), Permanence::Reverted);
}
catch (...)
{
// TODO: Some sort of notification of failure.
}
return ret;
}
ExecutionResult Client::create(Secret _secret, u256 _value, bytes const& _data, u256 _gas, u256 _gasPrice, int _blockNumber)
{
ExecutionResult ret;
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 = asOf(_blockNumber);
n = temp.transactionsFrom(toAddress(_secret));
}
Transaction t(_value, _gasPrice, _gas, _data, n, _secret);
ret = temp.execute(m_bc, t.rlp(), Permanence::Reverted);
}
catch (...)
{
// TODO: Some sort of notification of failure.
}
return ret;
}
ExecutionResult 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; ExecutionResult ret;
@ -547,6 +392,7 @@ ExecutionResult Client::call(Address _dest, bytes const& _data, u256 _gas, u256
{ {
ReadGuard l(x_stateDB); ReadGuard l(x_stateDB);
temp = m_postMine; temp = m_postMine;
temp.addBalance(Address(), _value + _gasPrice * _gas);
} }
Executive e(temp, LastHashes(), 0); Executive e(temp, LastHashes(), 0);
if (!e.call(_dest, _dest, Address(), _value, _gasPrice, &_data, _gas, Address())) if (!e.call(_dest, _dest, Address(), _value, _gasPrice, &_data, _gas, Address()))
@ -560,28 +406,6 @@ ExecutionResult Client::call(Address _dest, bytes const& _data, u256 _gas, u256
return ret; return ret;
} }
Address Client::submitTransaction(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);
}
pair<h256, u256> Client::getWork() pair<h256, u256> Client::getWork()
{ {
Guard l(x_remoteMiner); Guard l(x_remoteMiner);
@ -711,7 +535,7 @@ void Client::doWork()
// watches garbage collection // watches garbage collection
vector<unsigned> toUninstall; vector<unsigned> toUninstall;
{ {
Guard l(m_filterLock); Guard l(x_filtersWatches);
for (auto key: keysOf(m_watches)) for (auto key: keysOf(m_watches))
if (m_watches[key].lastPoll != chrono::system_clock::time_point::max() && 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))
{ {
@ -729,25 +553,15 @@ void Client::doWork()
} }
} }
unsigned Client::numberOf(int _n) const State Client::asOf(h256 const& _block) const
{ {
if (_n > 0) ReadGuard l(x_stateDB);
return _n; return State(m_stateDB, bc(), _block);
else if (_n == GenesisBlock)
return 0;
else
return m_bc.details().number + max(-(int)m_bc.details().number, 1 + _n);
} }
State Client::asOf(int _h) const void Client::prepareForTransaction()
{ {
ReadGuard l(x_stateDB); startWorking();
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)));
} }
State Client::state(unsigned _txi, h256 _block) const State Client::state(unsigned _txi, h256 _block) const
@ -768,183 +582,14 @@ eth::State Client::state(unsigned _txi) const
return m_postMine.fromPending(_txi); return m_postMine.fromPending(_txi);
} }
StateDiff Client::diff(unsigned _txi, int _block) const void Client::inject(bytesConstRef _rlp)
{
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<Address> Client::addresses(int _block) const
{
vector<Address> 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<u256, u256> 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 _transactionHash) const
{
return Transaction(m_bc.transaction(_transactionHash), CheckSignature::Range);
}
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
{
auto bl = m_bc.block(_blockHash);
RLP b(bl);
return b[2].itemCount();
}
Transactions Client::transactions(h256 _blockHash) const
{
auto bl = m_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 Client::transactionHashes(h256 _blockHash) const
{
return m_bc.transactionHashes(_blockHash);
}
LocalisedLogEntries Client::logs(unsigned _watchId) const
{ {
LogFilter f; startWorking();
try
{ m_tq.attemptImport(_rlp);
Guard l(m_filterLock);
f = m_filters.at(m_watches.at(_watchId).id).filter;
}
catch (...)
{
return LocalisedLogEntries();
}
return logs(f);
} }
LocalisedLogEntries Client::logs(LogFilter const& _f) const void Client::flushTransactions()
{ {
LocalisedLogEntries ret; doWork();
unsigned begin = min<unsigned>(m_bc.number() + 1, (unsigned)_f.latest());
unsigned end = min(m_bc.number(), min(begin, (unsigned)_f.earliest()));
// 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(); ++j)
ret.insert(ret.begin(), LocalisedLogEntry(le[j], begin, sha3));
}
begin = m_bc.number();
}
set<unsigned> matchingBlocks;
for (auto const& i: _f.bloomPossibilities())
for (auto u: m_bc.withBlockBloom(i, end, begin))
matchingBlocks.insert(u);
#if ETH_DEBUG
unsigned falsePos = 0;
#endif
for (auto n: matchingBlocks)
{
#if ETH_DEBUG
int total = 0;
#endif
auto h = m_bc.numberHash(n);
auto receipts = m_bc.receipts(h).receipts;
for (size_t i = 0; i < receipts.size(); i++)
{
TransactionReceipt receipt = receipts[i];
if (_f.matches(receipt.bloom()))
{
auto info = m_bc.info(h);
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(); ++j)
ret.insert(ret.begin(), LocalisedLogEntry(le[j], n, h));
}
}
#if ETH_DEBUG
if (!total)
falsePos++;
#endif
}
}
#if ETH_DEBUG
cdebug << matchingBlocks.size() << "searched from" << (end - begin) << "skipped; " << falsePos << "false +ves";
#endif
return ret;
} }

173
libethereum/Client.h

@ -40,9 +40,9 @@
#include "TransactionQueue.h" #include "TransactionQueue.h"
#include "State.h" #include "State.h"
#include "CommonNet.h" #include "CommonNet.h"
#include "LogFilter.h"
#include "Miner.h" #include "Miner.h"
#include "Interface.h" #include "ABI.h"
#include "ClientBase.h"
namespace dev namespace dev
{ {
@ -72,71 +72,6 @@ private:
std::string m_path; 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, Reaping _r): id(_id), lastPoll(_r == Reaping::Automatic ? std::chrono::system_clock::now() : std::chrono::system_clock::time_point::max()) {}
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<dev::eth::WatchChannel, true>()
struct WorkInChannel: public LogChannel { static const char* name() { return ">W>"; } static const int verbosity = 16; };
struct WorkOutChannel: public LogChannel { static const char* name() { return "<W<"; } static const int verbosity = 16; };
struct WorkChannel: public LogChannel { static const char* name() { return "-W-"; } static const int verbosity = 16; };
#define cwork dev::LogOutputStream<dev::eth::WorkChannel, true>()
#define cworkin dev::LogOutputStream<dev::eth::WorkInChannel, true>()
#define cworkout dev::LogOutputStream<dev::eth::WorkOutChannel, true>()
template <class T> struct ABISerialiser {};
template <unsigned N> struct ABISerialiser<FixedHash<N>> { static bytes serialise(FixedHash<N> 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<u256> { static bytes serialise(u256 const& _t) { return h256(_t).asBytes(); } };
template <> struct ABISerialiser<u160> { static bytes serialise(u160 const& _t) { return bytes(12, 0) + h160(_t).asBytes(); } };
template <> struct ABISerialiser<string32> { static bytes serialise(string32 const& _t) { return bytesConstRef((byte const*)_t.data(), 32).toBytes(); } };
inline bytes abiInAux() { return {}; }
template <class T, class ... U> bytes abiInAux(T const& _t, U const& ... _u)
{
return ABISerialiser<T>::serialise(_t) + abiInAux(_u ...);
}
template <class ... T> bytes abiIn(std::string _id, T const& ... _t)
{
return sha3(_id).ref().cropped(0, 4).toBytes() + abiInAux(_t ...);
}
template <class T> struct ABIDeserialiser {};
template <unsigned N> struct ABIDeserialiser<FixedHash<N>> { static FixedHash<N> deserialise(bytesConstRef& io_t) { static_assert(N <= 32, "Parameter sizes must be at most 32 bytes."); FixedHash<N> ret; io_t.cropped(32 - N, N).populate(ret.ref()); io_t = io_t.cropped(32); return ret; } };
template <> struct ABIDeserialiser<u256> { static u256 deserialise(bytesConstRef& io_t) { u256 ret = fromBigEndian<u256>(io_t.cropped(0, 32)); io_t = io_t.cropped(32); return ret; } };
template <> struct ABIDeserialiser<u160> { static u160 deserialise(bytesConstRef& io_t) { u160 ret = fromBigEndian<u160>(io_t.cropped(12, 20)); io_t = io_t.cropped(32); return ret; } };
template <> struct ABIDeserialiser<string32> { 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 <class T> T abiOut(bytes const& _data)
{
bytesConstRef o(&_data);
return ABIDeserialiser<T>::deserialise(o);
}
class RemoteMiner: public Miner class RemoteMiner: public Miner
{ {
public: public:
@ -175,14 +110,14 @@ public:
private: private:
u256 m_weiPerRef; u256 m_weiPerRef;
u256 m_refsPerBlock; u256 m_refsPerBlock;
u256 m_gasPerBlock = 1000000; u256 m_gasPerBlock = 3141592;
std::array<u256, 9> m_octiles; std::array<u256, 9> m_octiles;
}; };
/** /**
* @brief Main API hub for interfacing with Ethereum. * @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; friend class Miner;
@ -211,88 +146,20 @@ public:
/// Resets the gas pricer to some other object. /// Resets the gas pricer to some other object.
void setGasPricer(std::shared_ptr<GasPricer> _gp) { m_gp = _gp; } void setGasPricer(std::shared_ptr<GasPricer> _gp) { m_gp = _gp; }
/// 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;
/// Injects the RLP-encoded transaction given by the _rlp into the transaction queue directly. /// Injects the RLP-encoded transaction given by the _rlp into the transaction queue directly.
virtual void inject(bytesConstRef _rlp) override; virtual void inject(bytesConstRef _rlp);
/// Blocks until all pending transactions have been processed. /// Blocks until all pending transactions have been processed.
virtual void flushTransactions() override; virtual void flushTransactions() override;
/// Makes the given call. Nothing is recorded into the state. using Interface::call; // to remove warning about hiding virtual function
virtual ExecutionResult call(Secret _secret, u256 _value, Address _dest, bytes const& _data = bytes(), u256 _gas = 10000, u256 _gasPrice = 10 * szabo, int _blockNumber = 0) override;
/// 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 = bytes(), u256 _gas = 10000, u256 _gasPrice = 10 * szabo, int _blockNumber = 0) override;
/// 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. /// 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.
ExecutionResult call(Address _dest, bytes const& _data = bytes(), u256 _gas = 125000, u256 _value = 0, u256 _gasPrice = 1 * ether); ExecutionResult 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<u256, u256> storageAt(Address _a, int _block) const;
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;
virtual LocalisedLogEntries checkWatch(unsigned _watchId);
virtual LocalisedLogEntries logs(unsigned _watchId) const;
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 _transactionHash) const;
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;
virtual Transactions transactions(h256 _blockHash) const;
virtual TransactionHashes transactionHashes(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<Address> addresses(int _block) const;
/// Get the remaining gas limit in this block. /// Get the remaining gas limit in this block.
virtual u256 gasLimitRemaining() const { return m_postMine.gasLimitRemaining(); } virtual u256 gasLimitRemaining() const { return m_postMine.gasLimitRemaining(); }
// [PRIVATE API - only relevant for base clients, not available in general] // [PRIVATE API - only relevant for base clients, not available in general]
dev::eth::State state(unsigned _txi, h256 _block) const; dev::eth::State state(unsigned _txi, h256 _block) const;
dev::eth::State state(h256 _block) const; dev::eth::State state(h256 _block) const;
dev::eth::State state(unsigned _txi) const; dev::eth::State state(unsigned _txi) const;
@ -304,6 +171,8 @@ public:
// Mining stuff: // Mining stuff:
void setAddress(Address _us) { WriteGuard l(x_stateDB); m_preMine.setAddress(_us); }
/// Check block validity prior to mining. /// Check block validity prior to mining.
bool miningParanoia() const { return m_paranoia; } bool miningParanoia() const { return m_paranoia; }
/// Change whether we check block validity prior to mining. /// Change whether we check block validity prior to mining.
@ -317,10 +186,6 @@ public:
/// Enable/disable fast mining. /// Enable/disable fast mining.
void setTurboMining(bool _enable = true) { m_turboMining = _enable; } 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). /// Stops mining and sets the number of mining threads (0 for automatic).
virtual void setMiningThreads(unsigned _threads = 0); virtual void setMiningThreads(unsigned _threads = 0);
/// Get the effective number of mining threads. /// Get the effective number of mining threads.
@ -356,6 +221,17 @@ public:
void killChain(); void killChain();
protected: 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. /// Collate the changed filters for the bloom filter of the given pending transaction.
/// Insert any filters that are activated into @a o_changed. /// Insert any filters that are activated into @a o_changed.
void appendFromNewPending(TransactionReceipt const& _receipt, h256Set& io_changed, h256 _sha3); void appendFromNewPending(TransactionReceipt const& _receipt, h256Set& io_changed, h256 _sha3);
@ -380,15 +256,8 @@ private:
virtual bool turbo() const { return m_turboMining; } virtual bool turbo() const { return m_turboMining; }
virtual bool force() const { return m_forceMining; } 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. VersionChecker m_vc; ///< Dummy object to check & update the protocol version.
CanonBlockChain m_bc; ///< Maintains block database. 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). BlockQueue m_bq; ///< Maintains a list of incoming blocks not yet on the blockchain (to be imported).
std::shared_ptr<GasPricer> m_gp; ///< The gas pricer. std::shared_ptr<GasPricer> m_gp; ///< The gas pricer.
@ -409,10 +278,6 @@ private:
bool m_forceMining = false; ///< Mine even when there are no transactions pending? bool m_forceMining = false; ///< Mine even when there are no transactions pending?
bool m_verifyOwnBlocks = true; ///< Should be verify blocks that we mined? bool m_verifyOwnBlocks = true; ///< Should be verify blocks that we mined?
mutable Mutex m_filterLock;
std::map<h256, InstalledFilter> m_filters;
std::map<unsigned, ClientWatch> m_watches;
mutable std::chrono::system_clock::time_point m_lastGarbageCollection; mutable std::chrono::system_clock::time_point m_lastGarbageCollection;
}; };

399
libethereum/ClientBase.cpp

@ -0,0 +1,399 @@
/*
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 <http://www.gnu.org/licenses/>.
*/
/** @file ClientBase.cpp
* @author Gav Wood <i@gavwood.com>
* @author Marek Kotewicz <marek@ethdev.com>
* @date 2015
*/
#include <libdevcore/StructuredLogger.h>
#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.attemptImport(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.attemptImport(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<u256, u256> 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<unsigned>(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<unsigned> 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::seconds>(chrono::system_clock::now().time_since_epoch()).count();
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::seconds>(chrono::system_clock::now().time_since_epoch()).count();
std::swap(ret, w.changes);
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();
}

170
libethereum/ClientBase.h

@ -0,0 +1,170 @@
/*
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 <http://www.gnu.org/licenses/>.
*/
/** @file ClientBase.h
* @author Gav Wood <i@gavwood.com>
* @author Marek Kotewicz <marek@ethdev.com>
* @date 2015
*/
#pragma once
#include <chrono>
#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<dev::eth::WatchChannel, true>()
struct WorkInChannel: public LogChannel { static const char* name() { return ">W>"; } static const int verbosity = 16; };
struct WorkOutChannel: public LogChannel { static const char* name() { return "<W<"; } static const int verbosity = 16; };
struct WorkChannel: public LogChannel { static const char* name() { return "-W-"; } static const int verbosity = 16; };
#define cwork dev::LogOutputStream<dev::eth::WorkChannel, true>()
#define cworkin dev::LogOutputStream<dev::eth::WorkInChannel, true>()
#define cworkout dev::LogOutputStream<dev::eth::WorkOutChannel, true>()
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<u256, u256> 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;
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<h256, u256> 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<h256, InstalledFilter> m_filters; ///< The dictionary of filters that are active.
std::map<unsigned, ClientWatch> m_watches; ///< Each and every watch - these reference a filter.
};
}}

2
libethereum/EthereumHost.cpp

@ -178,7 +178,7 @@ void EthereumHost::maintainTransactions()
for (auto const& i: m_tq.transactions()) for (auto const& i: m_tq.transactions())
if (ep->m_requireTransactions || (!m_transactionsSent.count(i.first) && !ep->m_knownTransactions.count(i.first))) if (ep->m_requireTransactions || (!m_transactionsSent.count(i.first) && !ep->m_knownTransactions.count(i.first)))
{ {
b += i.second; b += i.second.rlp();
++n; ++n;
m_transactionsSent.insert(i.first); m_transactionsSent.insert(i.first);
} }

1
libethereum/EthereumPeer.cpp

@ -48,6 +48,7 @@ EthereumPeer::EthereumPeer(Session* _s, HostCapabilityFace* _h, unsigned _i):
EthereumPeer::~EthereumPeer() EthereumPeer::~EthereumPeer()
{ {
clogS(NetMessageSummary) << "Aborting Sync :-(";
abortSync(); abortSync();
} }

106
libethereum/Executive.cpp

@ -35,7 +35,7 @@ using namespace dev::eth;
Executive::Executive(State& _s, BlockChain const& _bc, unsigned _level): Executive::Executive(State& _s, BlockChain const& _bc, unsigned _level):
m_s(_s), 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) m_depth(_level)
{} {}
@ -44,18 +44,44 @@ u256 Executive::gasUsed() const
return m_t.gas() - m_endGas; return m_t.gas() - m_endGas;
} }
ExecutionResult Executive::executionResult() const
{
return ExecutionResult(gasUsed(), m_excepted, m_newAddress, m_out, m_codeDeposit, m_ext ? m_ext->sub.refunds : 0);
}
void Executive::accrueSubState(SubState& _parentContext) void Executive::accrueSubState(SubState& _parentContext)
{ {
if (m_ext) if (m_ext)
_parentContext += m_ext->sub; _parentContext += m_ext->sub;
} }
bool Executive::setup(bytesConstRef _rlp) void Executive::initialize(Transaction const& _transaction)
{ {
// Entry point for a user-executed transaction. m_t = _transaction;
// 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) << "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.
m_gasRequired = Interface::txGas(m_t.data());
if (m_t.gas() < m_gasRequired)
{
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()));
}
// Avoid invalid transactions.
u256 nonceReq;
try try
{ {
m_t = Transaction(_rlp, CheckSignature::Sender); nonceReq = m_s.transactionsFrom(m_t.sender());
} }
catch (...) catch (...)
{ {
@ -63,15 +89,6 @@ bool Executive::setup(bytesConstRef _rlp)
m_excepted = TransactionException::InvalidSignature; m_excepted = TransactionException::InvalidSignature;
throw; throw;
} }
return setup();
}
bool Executive::setup()
{
// Entry point for a user-executed transaction.
// Avoid invalid transactions.
auto nonceReq = m_s.transactionsFrom(m_t.sender());
if (m_t.nonce() != nonceReq) if (m_t.nonce() != nonceReq)
{ {
clog(StateDetail) << "Invalid Nonce: Require" << nonceReq << " Got" << m_t.nonce(); clog(StateDetail) << "Invalid Nonce: Require" << nonceReq << " Got" << m_t.nonce();
@ -79,50 +96,38 @@ bool Executive::setup()
BOOST_THROW_EXCEPTION(InvalidNonce() << RequirementError((bigint)nonceReq, (bigint)m_t.nonce())); BOOST_THROW_EXCEPTION(InvalidNonce() << RequirementError((bigint)nonceReq, (bigint)m_t.nonce()));
} }
// Check gas cost is enough.
auto gasCost = Interface::txGas(m_t.data());
if (m_t.gas() < gasCost)
{
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()));
}
bigint cost = m_t.value() + (bigint)m_t.gas() * m_t.gasPrice();
// Avoid unaffordable transactions. // Avoid unaffordable transactions.
if (m_s.balance(m_t.sender()) < cost) 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) << "Not enough cash: Require >" << cost << " Got" << m_s.balance(m_t.sender()); clog(StateDetail) << "Not enough cash: Require >" << m_totalCost << " Got" << m_s.balance(m_t.sender());
BOOST_THROW_EXCEPTION(NotEnoughCash() << RequirementError(cost, (bigint)m_s.balance(m_t.sender()))); m_excepted = TransactionException::NotEnoughCash;
BOOST_THROW_EXCEPTION(NotEnoughCash() << RequirementError(m_totalCost, (bigint)m_s.balance(m_t.sender())));
} }
}
u256 startGasUsed = m_s.gasUsed(); bool Executive::execute()
if (startGasUsed + (bigint)m_t.gas() > m_s.m_currentBlock.gasLimit) {
{ // Entry point for a user-executed transaction.
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()));
}
// Increment associated nonce for sender. // Increment associated nonce for sender.
m_s.noteSending(m_t.sender()); m_s.noteSending(m_t.sender());
// Pay... // Pay...
clog(StateDetail) << "Paying" << formatBalance(u256(cost)) << "from sender (includes" << m_t.gas() << "gas at" << formatBalance(m_t.gasPrice()) << ")"; 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(), cost); m_s.subBalance(m_t.sender(), m_gasCost);
if (m_t.isCreation()) 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 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) bool Executive::call(Address _receiveAddress, Address _codeAddress, Address _senderAddress, u256 _value, u256 _gasPrice, bytesConstRef _data, u256 _gas, Address _originAddress)
{ {
m_isCreation = false; m_isCreation = false;
// cnote << "Transferring" << formatBalance(_value) << "to receiver."; // cnote << "Transferring" << formatBalance(_value) << "to receiver.";
m_s.addBalance(_receiveAddress, _value);
auto it = !(_codeAddress & ~h160(0xffffffff)) ? precompiled().find((unsigned)(u160)_codeAddress) : precompiled().end(); auto it = !(_codeAddress & ~h160(0xffffffff)) ? precompiled().find((unsigned)(u160)_codeAddress) : precompiled().end();
if (it != precompiled().end()) if (it != precompiled().end())
{ {
@ -131,6 +136,8 @@ bool Executive::call(Address _receiveAddress, Address _codeAddress, Address _sen
{ {
m_endGas = 0; m_endGas = 0;
m_excepted = TransactionException::OutOfGasBase; m_excepted = TransactionException::OutOfGasBase;
// Bail from exception.
return true; // true actually means "all finished - nothing more to be done regarding go().
} }
else else
{ {
@ -147,6 +154,9 @@ bool Executive::call(Address _receiveAddress, Address _codeAddress, Address _sen
} }
else else
m_endGas = _gas; m_endGas = _gas;
m_s.transferBalance(_senderAddress, _receiveAddress, _value);
return !m_ext; return !m_ext;
} }
@ -158,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. // we delete it explicitly if we decide we need to revert.
m_newAddress = right160(sha3(rlpList(_sender, m_s.transactionsFrom(_sender) - 1))); 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. // Execute _init.
if (!_init.empty())
{
m_vm = VMFactory::create(_gas);
m_ext = make_shared<ExtVM>(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()) if (_init.empty())
{ {
m_s.m_cache[m_newAddress].setCode({}); m_s.m_cache[m_newAddress].setCode({});
m_endGas = _gas; m_endGas = _gas;
} }
else
{
m_vm = VMFactory::create(_gas);
m_ext = make_shared<ExtVM>(m_s, m_lastHashes, m_newAddress, _sender, _origin, _endowment, _gasPrice, bytesConstRef(), _init, m_depth);
}
return !m_ext; return !m_ext;
} }

43
libethereum/Executive.h

@ -41,10 +41,21 @@ struct VMTraceChannel: public LogChannel { static const char* name() { return "E
* @brief Message-call/contract-creation executor; useful for executing transactions. * @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. * 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 * 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. * 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 class Executive
{ {
@ -59,17 +70,17 @@ public:
Executive(Executive const&) = delete; Executive(Executive const&) = delete;
void operator=(Executive) = delete; void operator=(Executive) = delete;
/// Set up the executive for evaluating a transaction. You must call finalize() following this. /// Initializes the executive for evaluating a transaction. You must call finalize() at some point following this.
/// @returns true iff go() must be called (and thus a VM execution in required). void initialize(bytesConstRef _transaction) { initialize(Transaction(_transaction, CheckSignature::None)); }
bool setup(bytesConstRef _transaction); void initialize(Transaction const& _transaction);
/// Set up the executive for evaluating a transaction. You must call finalize() following this. /// Finalise a transaction previously set up with initialize().
/// @returns true iff go() must be called (and thus a VM execution in required). /// @warning Only valid after initialize() and execute(), and possibly go().
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().
void finalize(); void finalize();
/// @returns the transaction from setup(). /// Begins execution of a transaction. You must call finalize() following this.
/// @warning Only valid after setup(). /// @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; } Transaction const& t() const { return m_t; }
/// @returns the log entries created by this operation. /// @returns the log entries created by this operation.
/// @warning Only valid after finalise(). /// @warning Only valid after finalise().
@ -104,11 +115,9 @@ public:
bool excepted() const { return m_excepted != TransactionException::None; } bool excepted() const { return m_excepted != TransactionException::None; }
/// Get the above in an amalgamated fashion. /// Get the above in an amalgamated fashion.
ExecutionResult executionResult() const { return ExecutionResult(gasUsed(), m_excepted, m_newAddress, m_out, m_codeDeposit); } ExecutionResult executionResult() const;
private: private:
bool setup();
State& m_s; ///< The state to which this operation/transaction is applied. State& m_s; ///< The state to which this operation/transaction is applied.
LastHashes m_lastHashes; LastHashes m_lastHashes;
std::shared_ptr<ExtVM> m_ext; ///< The VM externality object for the VM execution or null if no VM is required. std::shared_ptr<ExtVM> m_ext; ///< The VM externality object for the VM execution or null if no VM is required.
@ -125,6 +134,10 @@ private:
Transaction m_t; ///< The original transaction. Set by setup(). Transaction m_t; ///< The original transaction. Set by setup().
LogEntries m_logs; ///< The log entries created by this transaction. Set by finalize(). LogEntries m_logs; ///< The log entries created by this transaction. Set by finalize().
bigint m_gasRequired;
bigint m_gasCost;
bigint m_totalCost;
}; };
} }

6
libethereum/ExtVM.h

@ -82,7 +82,11 @@ public:
/// Revert any changes made (by any of the other calls). /// Revert any changes made (by any of the other calls).
/// @TODO check call site for the parent manifest being discarded. /// @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; } State& state() const { return m_s; }

30
libethereum/Interface.h

@ -38,6 +38,7 @@ namespace eth
{ {
using TransactionHashes = h256s; using TransactionHashes = h256s;
using UncleHashes = h256s;
enum class Reaping enum class Reaping
{ {
@ -66,23 +67,22 @@ public:
/// @returns the new contract's address (assuming it all goes through). /// @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) = 0; virtual Address submitTransaction(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;
/// Blocks until all pending transactions have been processed. /// Blocks until all pending transactions have been processed.
virtual void flushTransactions() = 0; virtual void flushTransactions() = 0;
/// Makes the given call. Nothing is recorded into the state. /// 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, int _blockNumber = 0) = 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. /// Does the given creation. Nothing is recorded into the state.
/// @returns the pair of the Address of the created contract together with its code. /// @returns the pair of the Address of the created contract together with its code.
virtual ExecutionResult create(Secret _secret, u256 _value, bytes const& _data = bytes(), u256 _gas = 10000, u256 _gasPrice = 10 * szabo, int _blockNumber = 0) = 0; 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] // [STATE-QUERY API]
int getDefault() const { return m_default; } 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 balanceAt(Address _a) const { return balanceAt(_a, m_default); }
u256 countAt(Address _a) const { return countAt(_a, m_default); } u256 countAt(Address _a) const { return countAt(_a, m_default); }
@ -90,11 +90,11 @@ public:
bytes codeAt(Address _a) const { return codeAt(_a, m_default); } bytes codeAt(Address _a) const { return codeAt(_a, m_default); }
std::map<u256, u256> storageAt(Address _a) const { return storageAt(_a, m_default); } std::map<u256, u256> storageAt(Address _a) const { return storageAt(_a, m_default); }
virtual u256 balanceAt(Address _a, int _block) const = 0; virtual u256 balanceAt(Address _a, BlockNumber _block) const = 0;
virtual u256 countAt(Address _a, int _block) const = 0; virtual u256 countAt(Address _a, BlockNumber _block) const = 0;
virtual u256 stateAt(Address _a, u256 _l, int _block) const = 0; virtual u256 stateAt(Address _a, u256 _l, BlockNumber _block) const = 0;
virtual bytes codeAt(Address _a, int _block) const = 0; virtual bytes codeAt(Address _a, BlockNumber _block) const = 0;
virtual std::map<u256, u256> storageAt(Address _a, int _block) const = 0; virtual std::map<u256, u256> storageAt(Address _a, BlockNumber _block) const = 0;
// [LOGS API] // [LOGS API]
@ -118,6 +118,7 @@ public:
virtual Transaction transaction(h256 _transactionHash) const = 0; virtual Transaction transaction(h256 _transactionHash) const = 0;
virtual Transaction transaction(h256 _blockHash, unsigned _i) const = 0; virtual Transaction transaction(h256 _blockHash, unsigned _i) const = 0;
virtual BlockInfo uncle(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 transactionCount(h256 _blockHash) const = 0;
virtual unsigned uncleCount(h256 _blockHash) const = 0; virtual unsigned uncleCount(h256 _blockHash) const = 0;
virtual Transactions transactions(h256 _blockHash) const = 0; virtual Transactions transactions(h256 _blockHash) const = 0;
@ -135,11 +136,12 @@ public:
/// Differences between transactions. /// Differences between transactions.
StateDiff diff(unsigned _txi) const { return diff(_txi, m_default); } StateDiff diff(unsigned _txi) const { return diff(_txi, m_default); }
virtual StateDiff diff(unsigned _txi, h256 _block) const = 0; 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. /// 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() 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. /// Get the fee associated for a transaction with the given data.
template <class T> 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; } template <class T> 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; }
@ -177,7 +179,7 @@ public:
virtual MineProgress miningProgress() const = 0; virtual MineProgress miningProgress() const = 0;
protected: protected:
int m_default = -1; int m_default = PendingBlock;
}; };
class Watch; class Watch;

27
libethereum/LogFilter.cpp

@ -46,6 +46,33 @@ h256 LogFilter::sha3() const
return dev::sha3(s.out()); 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 bool LogFilter::matches(LogBloom _bloom) const
{ {
if (m_addresses.size()) if (m_addresses.size())

11
libethereum/LogFilter.h

@ -45,14 +45,15 @@ class State;
class LogFilter class LogFilter
{ {
public: public:
LogFilter(int _earliest = 0, int _latest = -1): m_earliest(_earliest), m_latest(_latest) {} LogFilter(unsigned _earliest = 0, unsigned _latest = PendingBlock): m_earliest(_earliest), m_latest(_latest) {}
void streamRLP(RLPStream& _s) const; void streamRLP(RLPStream& _s) const;
h256 sha3() const; h256 sha3() const;
int earliest() const { return m_earliest; } unsigned earliest() const { return m_earliest; }
int latest() const { return m_latest; } unsigned latest() const { return m_latest; }
bool envelops(RelativeBlock _logBlockRelation, u256 _logBlockNumber) const;
std::vector<LogBloom> bloomPossibilities() const; std::vector<LogBloom> bloomPossibilities() const;
bool matches(LogBloom _bloom) const; bool matches(LogBloom _bloom) const;
bool matches(State const& _s, unsigned _i) const; bool matches(State const& _s, unsigned _i) const;
@ -68,8 +69,8 @@ public:
private: private:
AddressSet m_addresses; AddressSet m_addresses;
std::array<h256Set, 4> m_topics; std::array<h256Set, 4> m_topics;
int m_earliest = 0; unsigned m_earliest = 0;
int m_latest = -1; unsigned m_latest = LatestBlock;
}; };
} }

91
libethereum/State.cpp

@ -60,7 +60,18 @@ OverlayDB State::openDB(std::string _path, bool _killExisting)
ldb::DB* db = nullptr; ldb::DB* db = nullptr;
ldb::DB::Open(o, _path + "/state", &db); ldb::DB::Open(o, _path + "/state", &db);
if (!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."; cnote << "Opened state DB.";
return OverlayDB(db); return OverlayDB(db);
@ -342,6 +353,7 @@ u256 State::enactOn(bytesConstRef _block, BlockInfo const& _bi, BlockChain const
map<Address, u256> State::addresses() const map<Address, u256> State::addresses() const
{ {
#if ETH_FATDB
map<Address, u256> ret; map<Address, u256> ret;
for (auto i: m_cache) for (auto i: m_cache)
if (i.second.isAlive()) if (i.second.isAlive())
@ -350,6 +362,9 @@ map<Address, u256> State::addresses() const
if (m_cache.find(i.first) == m_cache.end()) if (m_cache.find(i.first) == m_cache.end())
ret[i.first] = RLP(i.second)[1].toInt<u256>(); ret[i.first] = RLP(i.second)[1].toInt<u256>();
return ret; return ret;
#else
throw InterfaceNotSupported("State::addresses()");
#endif
} }
void State::resetCurrent() void State::resetCurrent()
@ -384,8 +399,7 @@ bool State::cull(TransactionQueue& _tq) const
{ {
try try
{ {
Transaction t(i.second, CheckSignature::Sender); if (i.second.nonce() <= transactionsFrom(i.second.sender()))
if (t.nonce() <= transactionsFrom(t.sender()))
{ {
_tq.drop(i.first); _tq.drop(i.first);
ret = true; ret = true;
@ -407,7 +421,7 @@ TransactionReceipts State::sync(BlockChain const& _bc, TransactionQueue& _tq, Ga
TransactionReceipts ret; TransactionReceipts ret;
auto ts = _tq.transactions(); auto ts = _tq.transactions();
auto lh = getLastHashes(_bc, _bc.number()); LastHashes lh;
for (int goodTxs = 1; goodTxs;) for (int goodTxs = 1; goodTxs;)
{ {
@ -417,12 +431,11 @@ TransactionReceipts State::sync(BlockChain const& _bc, TransactionQueue& _tq, Ga
{ {
try try
{ {
Transaction t(i.second, CheckSignature::Sender); if (i.second.gasPrice() >= _gp.ask(*this))
if (t.gasPrice() >= _gp.ask(*this))
{ {
// don't have it yet! Execute it now.
uncommitToMine();
// boost::timer t; // boost::timer t;
if (lh.empty())
lh = _bc.lastHashes();
execute(lh, i.second); execute(lh, i.second);
ret.push_back(m_receipts.back()); ret.push_back(m_receipts.back());
_tq.noteGood(i); _tq.noteGood(i);
@ -430,6 +443,7 @@ TransactionReceipts State::sync(BlockChain const& _bc, TransactionQueue& _tq, Ga
// cnote << "TX took:" << t.elapsed() * 1000; // cnote << "TX took:" << t.elapsed() * 1000;
} }
} }
#if ETH_DEBUG
catch (InvalidNonce const& in) catch (InvalidNonce const& in)
{ {
bigint const* req = boost::get_error_info<errinfo_required>(in); bigint const* req = boost::get_error_info<errinfo_required>(in);
@ -445,13 +459,19 @@ TransactionReceipts State::sync(BlockChain const& _bc, TransactionQueue& _tq, Ga
else else
_tq.setFuture(i); _tq.setFuture(i);
} }
catch (BlockGasLimitReached const& e)
{
_tq.setFuture(i);
}
#endif
catch (Exception const& _e) catch (Exception const& _e)
{ {
// Something else went wrong - drop it. // Something else went wrong - drop it.
_tq.drop(i.first); _tq.drop(i.first);
if (o_transactionQueueChanged) if (o_transactionQueueChanged)
*o_transactionQueueChanged = true; *o_transactionQueueChanged = true;
cwarn << "Sync went wrong\n" << diagnostic_information(_e); cnote << "Dropping invalid transaction:";
cnote << diagnostic_information(_e);
} }
catch (std::exception const&) catch (std::exception const&)
{ {
@ -459,6 +479,7 @@ TransactionReceipts State::sync(BlockChain const& _bc, TransactionQueue& _tq, Ga
_tq.drop(i.first); _tq.drop(i.first);
if (o_transactionQueueChanged) if (o_transactionQueueChanged)
*o_transactionQueueChanged = true; *o_transactionQueueChanged = true;
cnote << "Transaction caused low-level exception :(";
} }
} }
} }
@ -494,7 +515,7 @@ u256 State::enact(bytesConstRef _block, BlockChain const& _bc, bool _checkNonce)
GenericTrieDB<MemoryDB> receiptsTrie(&rm); GenericTrieDB<MemoryDB> receiptsTrie(&rm);
receiptsTrie.init(); receiptsTrie.init();
LastHashes lh = getLastHashes(_bc, (unsigned)m_previousBlock.number); LastHashes lh = _bc.lastHashes((unsigned)m_previousBlock.number);
RLP rlp(_block); RLP rlp(_block);
// All ok with the block generally. Play back the transactions now... // All ok with the block generally. Play back the transactions now...
@ -505,7 +526,7 @@ u256 State::enact(bytesConstRef _block, BlockChain const& _bc, bool _checkNonce)
k << i; k << i;
transactionsTrie.insert(&k.out(), tr.data()); transactionsTrie.insert(&k.out(), tr.data());
execute(lh, tr.data()); execute(lh, Transaction(tr.data(), CheckSignature::Sender));
RLPStream receiptrlp; RLPStream receiptrlp;
m_receipts.back().streamRLP(receiptrlp); m_receipts.back().streamRLP(receiptrlp);
@ -1036,56 +1057,34 @@ bool State::isTrieGood(bool _enforceRefs, bool _requireNoLeftOvers) const
return true; return true;
} }
LastHashes State::getLastHashes(BlockChain const& _bc, unsigned _n) const ExecutionResult State::execute(LastHashes const& _lh, Transaction const& _t, Permanence _p)
{
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;
}
ExecutionResult State::execute(BlockChain const& _bc, bytes const& _rlp, Permanence _p)
{
return execute(getLastHashes(_bc, _bc.number()), &_rlp, _p);
}
ExecutionResult State::execute(BlockChain const& _bc, bytesConstRef _rlp, Permanence _p)
{
return execute(getLastHashes(_bc, _bc.number()), _rlp, _p);
}
// TODO: maintain node overlay revisions for stateroots -> each commit gives a stateroot + OverlayDB; allow overlay copying for rewind operations.
ExecutionResult State::execute(LastHashes const& _lh, bytesConstRef _rlp, Permanence _p)
{ {
#ifndef ETH_RELEASE #if ETH_PARANOIA
commit(); // get an updated hash
#endif
paranoia("start of execution.", true); paranoia("start of execution.", true);
State old(*this); State old(*this);
#if ETH_PARANOIA
auto h = rootHash(); auto h = rootHash();
#endif #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); 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 #if ETH_PARANOIA
ctrace << "Executing" << e.t() << "on" << h; ctrace << "Executing" << e.t() << "on" << h;
ctrace << toHex(e.t().rlp()); ctrace << toHex(e.t().rlp());
#endif #endif
if (!e.execute())
#if ETH_VMTRACE #if ETH_VMTRACE
e.go(e.simpleTrace()); e.go(e.simpleTrace());
#else #else
e.go(); e.go();
#endif #endif
e.finalize(); e.finalize();

20
libethereum/State.h

@ -41,7 +41,7 @@
namespace dev namespace dev
{ {
namespace test { class ImportTest; } namespace test { class ImportTest; class StateLoader; }
namespace eth namespace eth
{ {
@ -99,6 +99,7 @@ class State
{ {
friend class ExtVM; friend class ExtVM;
friend class dev::test::ImportTest; friend class dev::test::ImportTest;
friend class dev::test::StateLoader;
friend class Executive; friend class Executive;
public: public:
@ -127,6 +128,7 @@ public:
OverlayDB const& db() const { return m_db; } OverlayDB const& db() const { return m_db; }
/// @returns the set containing all addresses currently in use in Ethereum. /// @returns the set containing all addresses currently in use in Ethereum.
/// @throws InterfaceNotSupported if compiled without ETH_FATDB.
std::map<Address, u256> addresses() const; std::map<Address, u256> addresses() const;
/// Get the header information on the present block. /// Get the header information on the present block.
@ -186,15 +188,9 @@ public:
/// Like sync but only operate on _tq, killing the invalid/old ones. /// Like sync but only operate on _tq, killing the invalid/old ones.
bool cull(TransactionQueue& _tq) const; 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. /// Execute a given transaction.
/// This will append @a _t to the transaction list and change the state accordingly. /// This will append @a _t to the transaction list and change the state accordingly.
ExecutionResult execute(BlockChain const& _bc, bytes const& _rlp, Permanence _p = Permanence::Committed); ExecutionResult execute(LastHashes const& _lh, Transaction const& _t, Permanence _p = Permanence::Committed);
ExecutionResult execute(BlockChain const& _bc, bytesConstRef _rlp, Permanence _p = Permanence::Committed);
ExecutionResult execute(LastHashes const& _lh, bytes const& _rlp, Permanence _p = Permanence::Committed) { return execute(_lh, &_rlp, _p); }
ExecutionResult execute(LastHashes const& _lh, bytesConstRef _rlp, Permanence _p = Permanence::Committed);
/// Get the remaining gas limit in this block. /// Get the remaining gas limit in this block.
u256 gasLimitRemaining() const { return m_currentBlock.gasLimit - gasUsed(); } u256 gasLimitRemaining() const { return m_currentBlock.gasLimit - gasUsed(); }
@ -219,6 +215,14 @@ public:
*/ */
void subBalance(Address _id, bigint _value); 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. /// Get the root of the storage of an account.
h256 storageRoot(Address _contract) const; h256 storageRoot(Address _contract) const;

6
libethereum/Transaction.cpp

@ -46,6 +46,8 @@ TransactionException dev::eth::toTransactionException(VMException const& _e)
return TransactionException::BadJumpDestination; return TransactionException::BadJumpDestination;
if (!!dynamic_cast<OutOfGas const*>(&_e)) if (!!dynamic_cast<OutOfGas const*>(&_e))
return TransactionException::OutOfGas; return TransactionException::OutOfGas;
if (!!dynamic_cast<OutOfStack const*>(&_e))
return TransactionException::OutOfStack;
if (!!dynamic_cast<StackUnderflow const*>(&_e)) if (!!dynamic_cast<StackUnderflow const*>(&_e))
return TransactionException::StackUnderflow; return TransactionException::StackUnderflow;
return TransactionException::Unknown; return TransactionException::Unknown;
@ -66,6 +68,10 @@ Transaction::Transaction(bytesConstRef _rlpData, CheckSignature _checkSig)
m_type = rlp[field = 3].isEmpty() ? ContractCreation : MessageCall; m_type = rlp[field = 3].isEmpty() ? ContractCreation : MessageCall;
m_receiveAddress = rlp[field = 3].isEmpty() ? Address() : rlp[field = 3].toHash<Address>(RLP::VeryStrict); m_receiveAddress = rlp[field = 3].isEmpty() ? Address() : rlp[field = 3].toHash<Address>(RLP::VeryStrict);
m_value = rlp[field = 4].toInt<u256>(); m_value = rlp[field = 4].toInt<u256>();
if (!rlp[field = 5].isData())
BOOST_THROW_EXCEPTION(BadRLP() << errinfo_comment("transaction data RLP must be an array"));
m_data = rlp[field = 5].toBytes(); m_data = rlp[field = 5].toBytes();
byte v = rlp[field = 6].toInt<byte>() - 27; byte v = rlp[field = 6].toInt<byte>() - 27;
h256 r = rlp[field = 7].toInt<u256>(); h256 r = rlp[field = 7].toInt<u256>();

8
libethereum/Transaction.h

@ -56,6 +56,7 @@ enum class TransactionException
BadInstruction, BadInstruction,
BadJumpDestination, BadJumpDestination,
OutOfGas, ///< Ran out of gas executing code of the transaction. OutOfGas, ///< Ran out of gas executing code of the transaction.
OutOfStack, ///< Ran out of stack executing code of the transaction.
StackUnderflow StackUnderflow
}; };
@ -66,7 +67,7 @@ enum class CodeDeposit
Success Success
}; };
class VMException; struct VMException;
TransactionException toTransactionException(VMException const& _e); TransactionException toTransactionException(VMException const& _e);
@ -74,12 +75,13 @@ TransactionException toTransactionException(VMException const& _e);
struct ExecutionResult struct ExecutionResult
{ {
ExecutionResult() = default; ExecutionResult() = default;
ExecutionResult(u256 _gasUsed, TransactionException _excepted, Address _newAddress, bytesConstRef _output, CodeDeposit _codeDeposit): gasUsed(_gasUsed), excepted(_excepted), newAddress(_newAddress), output(_output.toBytes()), codeDeposit(_codeDeposit) {} ExecutionResult(u256 _gasUsed, TransactionException _excepted, Address _newAddress, bytesConstRef _output, CodeDeposit _codeDeposit, u256 _gasRefund): gasUsed(_gasUsed), excepted(_excepted), newAddress(_newAddress), output(_output.toBytes()), codeDeposit(_codeDeposit), gasRefunded(_gasRefund) {}
u256 gasUsed; u256 gasUsed = 0;
TransactionException excepted = TransactionException::Unknown; TransactionException excepted = TransactionException::Unknown;
Address newAddress; Address newAddress;
bytes output; bytes output;
CodeDeposit codeDeposit = CodeDeposit::None; CodeDeposit codeDeposit = CodeDeposit::None;
u256 gasRefunded = 0;
}; };
std::ostream& operator<<(std::ostream& _out, ExecutionResult const& _er); std::ostream& operator<<(std::ostream& _out, ExecutionResult const& _er);

10
libethereum/TransactionQueue.cpp

@ -46,7 +46,7 @@ bool TransactionQueue::import(bytesConstRef _transactionRLP)
UpgradeGuard ul(l); UpgradeGuard ul(l);
// If valid, append to blocks. // If valid, append to blocks.
m_current[h] = _transactionRLP.toBytes(); m_current[h] = t;
m_known.insert(h); m_known.insert(h);
} }
catch (Exception const& _e) catch (Exception const& _e)
@ -63,20 +63,20 @@ bool TransactionQueue::import(bytesConstRef _transactionRLP)
return true; return true;
} }
void TransactionQueue::setFuture(std::pair<h256, bytes> const& _t) void TransactionQueue::setFuture(std::pair<h256, Transaction> const& _t)
{ {
WriteGuard l(m_lock); WriteGuard l(m_lock);
if (m_current.count(_t.first)) if (m_current.count(_t.first))
{ {
m_current.erase(_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<h256, bytes> const& _t) void TransactionQueue::noteGood(std::pair<h256, Transaction> const& _t)
{ {
WriteGuard l(m_lock); 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) for (auto it = r.first; it != r.second; ++it)
m_current.insert(it->second); m_current.insert(it->second);
m_unknown.erase(r.first, r.second); m_unknown.erase(r.first, r.second);

11
libethereum/TransactionQueue.h

@ -25,6 +25,7 @@
#include <libdevcore/Common.h> #include <libdevcore/Common.h>
#include "libethcore/Common.h" #include "libethcore/Common.h"
#include <libdevcore/Guards.h> #include <libdevcore/Guards.h>
#include "Transaction.h"
namespace dev namespace dev
{ {
@ -46,19 +47,19 @@ public:
void drop(h256 _txHash); void drop(h256 _txHash);
std::map<h256, bytes> transactions() const { ReadGuard l(m_lock); return m_current; } std::map<h256, Transaction> transactions() const { ReadGuard l(m_lock); return m_current; }
std::pair<unsigned, unsigned> items() const { ReadGuard l(m_lock); return std::make_pair(m_current.size(), m_unknown.size()); } std::pair<unsigned, unsigned> items() const { ReadGuard l(m_lock); return std::make_pair(m_current.size(), m_unknown.size()); }
void setFuture(std::pair<h256, bytes> const& _t); void setFuture(std::pair<h256, Transaction> const& _t);
void noteGood(std::pair<h256, bytes> const& _t); void noteGood(std::pair<h256, Transaction> const& _t);
void clear() { WriteGuard l(m_lock); m_known.clear(); m_current.clear(); m_unknown.clear(); } void clear() { WriteGuard l(m_lock); m_known.clear(); m_current.clear(); m_unknown.clear(); }
private: private:
mutable boost::shared_mutex m_lock; ///< General lock. mutable boost::shared_mutex m_lock; ///< General lock.
std::set<h256> m_known; ///< Hashes of transactions in both sets. std::set<h256> m_known; ///< Hashes of transactions in both sets.
std::map<h256, bytes> m_current; ///< Map of SHA3(tx) to tx. std::map<h256, Transaction> m_current; ///< Map of SHA3(tx) to tx.
std::multimap<Address, std::pair<h256, bytes>> 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::multimap<Address, std::pair<h256, Transaction>> 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.
}; };
} }

4
libevm/ExtVMFace.h

@ -63,10 +63,10 @@ using LogEntries = std::vector<LogEntry>;
struct LocalisedLogEntry: public LogEntry struct LocalisedLogEntry: public LogEntry
{ {
LocalisedLogEntry() {} 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; unsigned number = 0;
h256 sha3; h256 transactionHash;
}; };
using LocalisedLogEntries = std::vector<LocalisedLogEntry>; using LocalisedLogEntries = std::vector<LocalisedLogEntry>;

12
libevm/VM.cpp

@ -36,6 +36,7 @@ struct InstructionMetric
{ {
int gasPriceTier; int gasPriceTier;
int args; int args;
int ret;
}; };
static array<InstructionMetric, 256> metrics() static array<InstructionMetric, 256> metrics()
@ -46,12 +47,15 @@ static array<InstructionMetric, 256> metrics()
InstructionInfo inst = instructionInfo((Instruction)i); InstructionInfo inst = instructionInfo((Instruction)i);
s_ret[i].gasPriceTier = inst.gasPriceTier; s_ret[i].gasPriceTier = inst.gasPriceTier;
s_ret[i].args = inst.args; s_ret[i].args = inst.args;
s_ret[i].ret = inst.ret;
} }
return s_ret; return s_ret;
} }
bytesConstRef VM::go(ExtVMFace& _ext, OnOpFunc const& _onOp, uint64_t _steps) bytesConstRef VM::go(ExtVMFace& _ext, OnOpFunc const& _onOp, uint64_t _steps)
{ {
m_stack.reserve((unsigned)c_stackLimit);
static const array<InstructionMetric, 256> c_metrics = metrics(); static const array<InstructionMetric, 256> c_metrics = metrics();
auto memNeed = [](u256 _offset, dev::u256 _size) { return _size ? (bigint)_offset + _size : (bigint)0; }; 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. // should work, but just seems to result in immediate errorless exit on initial execution. yeah. weird.
//m_onFail = std::function<void()>(onOperation); //m_onFail = std::function<void()>(onOperation);
require(metric.args); require(metric.args, metric.ret);
auto onOperation = [&]() auto onOperation = [&]()
{ {
@ -610,10 +614,7 @@ bytesConstRef VM::go(ExtVMFace& _ext, OnOpFunc const& _onOp, uint64_t _steps)
m_stack.pop_back(); m_stack.pop_back();
if (_ext.balance(_ext.myAddress) >= endowment && _ext.depth < 1024) 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)); m_stack.push_back((u160)_ext.create(endowment, m_gas, bytesConstRef(m_temp.data() + initOff, initSize), _onOp));
}
else else
m_stack.push_back(0); m_stack.push_back(0);
break; break;
@ -640,10 +641,7 @@ bytesConstRef VM::go(ExtVMFace& _ext, OnOpFunc const& _onOp, uint64_t _steps)
m_stack.pop_back(); m_stack.pop_back();
if (_ext.balance(_ext.myAddress) >= value && _ext.depth < 1024) 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)); 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 else
m_stack.push_back(0); m_stack.push_back(0);

2
libevm/VM.h

@ -56,7 +56,7 @@ public:
virtual bytesConstRef go(ExtVMFace& _ext, OnOpFunc const& _onOp = {}, uint64_t _steps = (uint64_t)-1) override final; 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(StackUnderflow() << 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); } } void requireMem(unsigned _n) { if (m_temp.size() < _n) { m_temp.resize(_n); } }
u256 curPC() const { return m_curPC; } u256 curPC() const { return m_curPC; }

1
libevm/VMFace.h

@ -31,6 +31,7 @@ struct BreakPointHit: virtual VMException {};
struct BadInstruction: virtual VMException {}; struct BadInstruction: virtual VMException {};
struct BadJumpDestination: virtual VMException {}; struct BadJumpDestination: virtual VMException {};
struct OutOfGas: virtual VMException {}; struct OutOfGas: virtual VMException {};
struct OutOfStack: virtual VMException {};
struct StackUnderflow: virtual VMException {}; struct StackUnderflow: virtual VMException {};
/// EVM Virtual Machine interface /// EVM Virtual Machine interface

278
libevmcore/Assembly.cpp

@ -20,131 +20,14 @@
*/ */
#include "Assembly.h" #include "Assembly.h"
#include <fstream> #include <fstream>
#include <libdevcore/Log.h> #include <libdevcore/Log.h>
#include <libevmcore/CommonSubexpressionEliminator.h>
using namespace std; using namespace std;
using namespace dev; using namespace dev;
using namespace dev::eth; 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<unsigned>(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;
}
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) void Assembly::append(Assembly const& _a)
{ {
auto newDeposit = m_deposit + _a.deposit(); auto newDeposit = m_deposit + _a.deposit();
@ -181,11 +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) for (unsigned br = 1;; ++br)
_out << i; {
return _out; 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 string Assembly::getLocationFromSources(StringMap const& _sourceCodes, SourceLocation const& _location) const
@ -289,24 +180,6 @@ inline bool matches(AssemblyItemsConstRef _a, AssemblyItemsConstRef _b)
return true; 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<class Iterator>
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; }; struct OptimiserChannel: public LogChannel { static const char* name() { return "OPT"; } static const int verbosity = 12; };
#define copt dev::LogOutputStream<OptimiserChannel, true>() #define copt dev::LogOutputStream<OptimiserChannel, true>()
@ -314,39 +187,6 @@ Assembly& Assembly::optimise(bool _enable)
{ {
if (!_enable) if (!_enable)
return *this; 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<Instruction, function<u256(u256, u256)>> 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<Instruction, function<u256(u256, u256)>> 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<pair<AssemblyItem, u256>> const c_identities =
{ { Instruction::ADD, 0}, { Instruction::MUL, 1}, { Instruction::MOD, 0}, { Instruction::OR, 0}, { Instruction::XOR, 0} };
std::vector<pair<AssemblyItems, function<AssemblyItems(AssemblyItemsConstRef)>>> rules = std::vector<pair<AssemblyItems, function<AssemblyItems(AssemblyItemsConstRef)>>> rules =
{ {
{ { Push, Instruction::POP }, [](AssemblyItemsConstRef) -> AssemblyItems { return {}; } }, { { Push, Instruction::POP }, [](AssemblyItemsConstRef) -> AssemblyItems { return {}; } },
@ -359,62 +199,46 @@ Assembly& Assembly::optimise(bool _enable)
{ { Instruction::ISZERO, Instruction::ISZERO }, [](AssemblyItemsConstRef) -> AssemblyItems { 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(); }});
// jump to next instruction // 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(); }}); 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 = m[0].instruction();
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 <bits> 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;
unsigned total = 0; unsigned total = 0;
for (unsigned count = 1; count > 0; total += count) for (unsigned count = 1; count > 0; total += count)
{ {
copt << *this;
count = 0; count = 0;
copt << "Performing common subexpression elimination...";
for (auto iter = m_items.begin(); iter != m_items.end();)
{
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)
{
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);
}
if (iter != m_items.end())
++iter;
}
for (unsigned i = 0; i < m_items.size(); ++i) for (unsigned i = 0; i < m_items.size(); ++i)
{ {
for (auto const& r: rules) for (auto const& r: rules)
@ -423,12 +247,10 @@ Assembly& Assembly::optimise(bool _enable)
if (matches(vr, &r.first)) if (matches(vr, &r.first))
{ {
auto rw = r.second(vr); auto rw = r.second(vr);
unsigned const vrSizeInBytes = bytesRequiredBySlice(vr.begin(), vr.end()); if (rw.size() < vr.size())
unsigned const rwSizeInBytes = bytesRequiredBySlice(rw.begin(), rw.end());
if (rwSizeInBytes < vrSizeInBytes || (rwSizeInBytes == vrSizeInBytes && popCountIncreased(vr, rw)))
{ {
copt << vr << "matches" << AssemblyItemsConstRef(&r.first) << "becomes..."; copt << "Rule " << vr << " matches " << AssemblyItemsConstRef(&r.first) << " becomes...";
copt << AssemblyItemsConstRef(&rw); copt << AssemblyItemsConstRef(&rw) << "\n";
if (rw.size() > vr.size()) if (rw.size() > vr.size())
{ {
// create hole in the vector // create hole in the vector
@ -442,7 +264,7 @@ Assembly& Assembly::optimise(bool _enable)
copy(rw.begin(), rw.end(), m_items.begin() + i); copy(rw.begin(), rw.end(), m_items.begin() + i);
count++; count++;
copt << "Now:\n" << m_items; copt << "Now:" << m_items;
} }
} }
} }

55
libevmcore/Assembly.h

@ -27,6 +27,7 @@
#include <libdevcore/Assertions.h> #include <libdevcore/Assertions.h>
#include <libevmcore/SourceLocation.h> #include <libevmcore/SourceLocation.h>
#include <libevmcore/Instruction.h> #include <libevmcore/Instruction.h>
#include <libevmcore/AssemblyItem.h>
#include "Exceptions.h" #include "Exceptions.h"
namespace dev namespace dev
@ -34,60 +35,6 @@ namespace dev
namespace eth namespace eth
{ {
enum AssemblyItemType { UndefinedItem, Operation, Push, PushString, PushTag, PushSub, PushSubSize, PushProgramSize, Tag, PushData };
class Assembly;
class AssemblyItem
{
friend class Assembly;
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; }
/// @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<AssemblyItem>;
using AssemblyItemsConstRef = vector_ref<AssemblyItem const>;
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)); }
class Assembly class Assembly
{ {
public: public:

135
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 <http://www.gnu.org/licenses/>.
*/
/** @file Assembly.cpp
* @author Gav Wood <i@gavwood.com>
* @date 2014
*/
#include "AssemblyItem.h"
#include <fstream>
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<unsigned>(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;
}

92
libevmcore/AssemblyItem.h

@ -0,0 +1,92 @@
/*
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 <http://www.gnu.org/licenses/>.
*/
/** @file Assembly.h
* @author Gav Wood <i@gavwood.com>
* @date 2014
*/
#pragma once
#include <iostream>
#include <sstream>
#include <libdevcore/Common.h>
#include <libdevcore/Assertions.h>
#include <libevmcore/SourceLocation.h>
#include <libevmcore/Instruction.h>
#include "Exceptions.h"
namespace dev
{
namespace eth
{
enum AssemblyItemType { UndefinedItem, Operation, Push, PushString, PushTag, PushSub, PushSubSize, PushProgramSize, Tag, PushData };
class Assembly;
class AssemblyItem
{
friend class Assembly;
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; }
/// @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<AssemblyItem>;
using AssemblyItemsConstRef = vector_ref<AssemblyItem const>;
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)); }
}
}

440
libevmcore/CommonSubexpressionEliminator.cpp

@ -0,0 +1,440 @@
/*
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 <http://www.gnu.org/licenses/>.
*/
/**
* @file CommonSubexpressionEliminator.cpp
* @author Christian <c@ethdev.com>
* @date 2015
* Optimizer step for common subexpression elimination and stack reorganisation.
*/
#include <functional>
#include <boost/range/adaptor/reversed.hpp>
#include <libevmcore/CommonSubexpressionEliminator.h>
#include <libevmcore/Assembly.h>
using namespace std;
using namespace dev;
using namespace dev::eth;
vector<AssemblyItem> CommonSubexpressionEliminator::getOptimizedItems()
{
map<int, ExpressionClasses::Id> initialStackContents;
map<int, ExpressionClasses::Id> 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);
return CSECodeGenerator(m_expressionClasses).generateCode(initialStackContents, targetStackContents);
}
ostream& CommonSubexpressionEliminator::stream(
ostream& _out,
map<int, ExpressionClasses::Id> _currentStack,
map<int, ExpressionClasses::Id> _targetStack
) const
{
auto streamExpressionClass = [this](ostream& _out, ExpressionClasses::Id _id)
{
auto const& expr = m_expressionClasses.representative(_id);
_out << " " << _id << ": " << *expr.item;
_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 << "Stack elements: " << endl;
for (auto const& it: m_stackElements)
{
_out << " " << dec << it.first << " = ";
streamExpressionClass(_out, it.second);
}
_out << "Equivalence classes: " << endl;
for (ExpressionClasses::Id eqClass = 0; eqClass < m_expressionClasses.size(); ++eqClass)
streamExpressionClass(_out, eqClass);
_out << "Current stack: " << endl;
for (auto const& it: _currentStack)
{
_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)
{
if (_item.type() != Operation)
{
if (_item.deposit() != 1)
BOOST_THROW_EXCEPTION(InvalidDeposit());
setStackElement(++m_stackHeight, m_expressionClasses.find(_item, {}));
}
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<ExpressionClasses::Id> arguments(info.args);
for (int i = 0; i < info.args; ++i)
arguments[i] = stackElement(m_stackHeight - i);
setStackElement(m_stackHeight + _item.deposit(), m_expressionClasses.find(_item, arguments));
}
m_stackHeight += _item.deposit();
}
}
void CommonSubexpressionEliminator::setStackElement(int _stackHeight, ExpressionClasses::Id _class)
{
m_stackElements[_stackHeight] = _class;
}
void CommonSubexpressionEliminator::swapStackElements(int _stackHeightA, int _stackHeightB)
{
if (_stackHeightA == _stackHeightB)
BOOST_THROW_EXCEPTION(OptimizerException() << errinfo_comment("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)));
}
bool SemanticInformation::breaksBasicBlock(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
InstructionInfo info = instructionInfo(_item.instruction());
// 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;
}
AssemblyItems CSECodeGenerator::generateCode(
map<int, ExpressionClasses::Id> const& _initialStack,
map<int, ExpressionClasses::Id> 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
for (auto const& targetItem: _targetStackContents)
{
m_finalClasses.insert(targetItem.second);
addDependencies(targetItem.second);
}
// generate the actual elements
for (auto const& targetItem: _targetStackContents)
{
removeStackTopIfPossible();
int position = generateClassElement(targetItem.second);
if (position == targetItem.first)
continue;
if (position < targetItem.first)
// it is already at its target, we need another copy
appendDup(position);
else
appendSwapOrRemove(position);
appendSwapOrRemove(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;
for (ExpressionClasses::Id argument: m_expressionClasses.representative(_c).arguments)
{
addDependencies(argument);
m_neededBy.insert(make_pair(argument, _c));
}
}
int CSECodeGenerator::generateClassElement(ExpressionClasses::Id _c)
{
if (m_classPositions.count(_c))
{
assertThrow(
m_classPositions[_c] != c_invalidPosition,
OptimizerException,
"Element already removed but still needed."
);
return m_classPositions[_c];
}
ExpressionClasses::Ids const& arguments = m_expressionClasses.representative(_c).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))
appendSwapOrRemove(generateClassElement(arguments[0]));
else
appendDup(generateClassElement(arguments[0]));
}
else if (arguments.size() == 2)
{
if (canBeRemoved(arguments[1], _c))
{
appendSwapOrRemove(generateClassElement(arguments[1]));
if (arguments[0] == arguments[1])
appendDup(m_stackHeight);
else if (canBeRemoved(arguments[0], _c))
{
appendSwapOrRemove(m_stackHeight - 1);
appendSwapOrRemove(generateClassElement(arguments[0]));
}
else
appendDup(generateClassElement(arguments[0]));
}
else
{
if (arguments[0] == arguments[1])
{
appendDup(generateClassElement(arguments[0]));
appendDup(m_stackHeight);
}
else if (canBeRemoved(arguments[0], _c))
{
appendSwapOrRemove(generateClassElement(arguments[0]));
appendDup(generateClassElement(arguments[1]));
appendSwapOrRemove(m_stackHeight - 1);
}
else
{
appendDup(generateClassElement(arguments[1]));
appendDup(generateClassElement(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." );
AssemblyItem const& item = *m_expressionClasses.representative(_c).item;
while (SemanticInformation::isCommutativeOperation(item) &&
!m_generatedItems.empty() &&
m_generatedItems.back() == AssemblyItem(Instruction::SWAP1))
// this will not append a swap but remove the one that is already there
appendSwapOrRemove(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(*m_expressionClasses.representative(_c).item);
m_stack[m_stackHeight] = _c;
return m_classPositions[_c] = m_stackHeight;
}
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)
{
int nr = 1 + m_stackHeight - _fromPosition;
assertThrow(nr <= 16, StackTooDeepException, "Stack too deep.");
assertThrow(1 <= nr, OptimizerException, "Invalid stack access.");
m_generatedItems.push_back(AssemblyItem(dupInstruction(nr)));
m_stackHeight++;
m_stack[m_stackHeight] = m_stack[_fromPosition];
}
void CSECodeGenerator::appendSwapOrRemove(int _fromPosition)
{
if (_fromPosition == m_stackHeight)
return;
int nr = m_stackHeight - _fromPosition;
assertThrow(nr <= 16, StackTooDeepException, "Stack too deep.");
assertThrow(1 <= nr, OptimizerException, "Invalid stack access.");
m_generatedItems.push_back(AssemblyItem(swapInstruction(nr)));
// 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();
}

183
libevmcore/CommonSubexpressionEliminator.h

@ -0,0 +1,183 @@
/*
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 <http://www.gnu.org/licenses/>.
*/
/**
* @file CommonSubexpressionEliminator.h
* @author Christian <c@ethdev.com>
* @date 2015
* Optimizer step for common subexpression elimination and stack reorganisation.
*/
#pragma once
#include <vector>
#include <map>
#include <ostream>
#include <libdevcore/CommonIO.h>
#include <libdevcore/Exceptions.h>
#include <libevmcore/ExpressionClasses.h>
namespace dev
{
namespace eth
{
class AssemblyItem;
using AssemblyItems = std::vector<AssemblyItem>;
/**
* 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 and
* it is assigned to the next sequence number of a stack item. 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:
/// 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 <class _AssemblyItemIterator>
_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<int, ExpressionClasses::Id> _currentStack = std::map<int, ExpressionClasses::Id>(),
std::map<int, ExpressionClasses::Id> _targetStack = std::map<int, ExpressionClasses::Id>()
) const;
private:
/// Feeds the item into the system for analysis.
void feedItem(AssemblyItem const& _item);
/// 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);
/// Current stack height, can be negative.
int m_stackHeight = 0;
/// Current stack layout, mapping stack height -> equivalence class
std::map<int, ExpressionClasses::Id> m_stackElements;
/// Structure containing the classes of equivalent expressions.
ExpressionClasses m_expressionClasses;
};
/**
* Helper functions to provide context-independent information about assembly items.
*/
struct SemanticInformation
{
/// @returns true if the given items starts a new basic block
static bool breaksBasicBlock(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);
};
/**
* Unit that generates code from current stack layout, target stack layout and information about
* the equivalence classes.
*/
class CSECodeGenerator
{
public:
CSECodeGenerator(ExpressionClasses const& _expressionClasses):
m_expressionClasses(_expressionClasses)
{}
/// @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
/// @param _equivalenceClasses equivalence classes as expressions of how to compute them
/// @note should only be called once on each object.
AssemblyItems generateCode(
std::map<int, ExpressionClasses::Id> const& _initialStack,
std::map<int, ExpressionClasses::Id> 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.
int generateClassElement(ExpressionClasses::Id _c);
/// @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 appendSwapOrRemove(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<ExpressionClasses::Id, ExpressionClasses::Id> m_neededBy;
/// Current content of the stack.
std::map<int, ExpressionClasses::Id> m_stack;
/// Current positions of equivalence classes, equal to c_invalidPosition if already deleted.
std::map<ExpressionClasses::Id, int> m_classPositions;
/// The actual eqivalence class items and how to compute them.
ExpressionClasses const& m_expressionClasses;
/// The set of equivalence classes that should be present on the stack at the end.
std::set<ExpressionClasses::Id> m_finalClasses;
};
template <class _AssemblyItemIterator>
_AssemblyItemIterator CommonSubexpressionEliminator::feedItems(
_AssemblyItemIterator _iterator,
_AssemblyItemIterator _end
)
{
for (; _iterator != _end && !SemanticInformation::breaksBasicBlock(*_iterator); ++_iterator)
feedItem(*_iterator);
return _iterator;
}
}
}

2
libevmcore/Exceptions.h

@ -31,6 +31,8 @@ namespace eth
struct AssemblyException: virtual Exception {}; struct AssemblyException: virtual Exception {};
struct InvalidDeposit: virtual AssemblyException {}; struct InvalidDeposit: virtual AssemblyException {};
struct InvalidOpcode: virtual AssemblyException {}; struct InvalidOpcode: virtual AssemblyException {};
struct OptimizerException: virtual AssemblyException {};
struct StackTooDeepException: virtual OptimizerException {};
} }
} }

371
libevmcore/ExpressionClasses.cpp

@ -0,0 +1,371 @@
/*
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 <http://www.gnu.org/licenses/>.
*/
/**
* @file ExpressionClasses.cpp
* @author Christian <c@ethdev.com>
* @date 2015
* Container for equivalence classes of expressions for use in common subexpression elimination.
*/
#include <libevmcore/ExpressionClasses.h>
#include <utility>
#include <tuple>
#include <functional>
#include <boost/range/adaptor/reversed.hpp>
#include <boost/noncopyable.hpp>
#include <libevmcore/Assembly.h>
#include <libevmcore/CommonSubexpressionEliminator.h>
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) <
std::tie(otherType, _other.item->data(), _other.arguments);
}
ExpressionClasses::Id ExpressionClasses::find(AssemblyItem const& _item, Ids const& _arguments)
{
Expression exp;
exp.id = Id(-1);
exp.item = &_item;
exp.arguments = _arguments;
if (SemanticInformation::isCommutativeOperation(_item))
sort(exp.arguments.begin(), exp.arguments.end());
//@todo store all class members (not only the representatives) in an efficient data structure to search here
for (Expression const& e: m_representatives)
if (!(e < exp || exp < e))
return e.id;
if (SemanticInformation::isDupInstruction(_item))
{
// Special item that refers to values pre-existing on the stack
m_spareAssemblyItem.push_back(make_shared<AssemblyItem>(_item));
exp.item = m_spareAssemblyItem.back().get();
}
ExpressionClasses::Id id = tryToSimplify(exp);
if (id < m_representatives.size())
return id;
exp.id = m_representatives.size();
m_representatives.push_back(exp);
return exp.id;
}
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<pair<Pattern, function<Pattern()>>> rules() const { return m_rules; }
private:
using Expression = ExpressionClasses::Expression;
map<unsigned, Expression const*> m_matchGroups;
vector<pair<Pattern, function<Pattern()>>> 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<pair<Pattern, function<Pattern()>>>{
// 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<pair<Instruction,function<u256(u256 const&,u256 const&)>>>{
{Instruction::ADD, plus<u256>()},
{Instruction::MUL, multiplies<u256>()},
{Instruction::AND, bit_and<u256>()},
{Instruction::OR, bit_or<u256>()},
{Instruction::XOR, bit_xor<u256>()}
})
{
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.push_back({
{op, {{op, {X, A}}, B}},
[=]() -> Pattern { return {op, {X, fun(A.d(), B.d())}}; }
});
// X+(Y+A) -> (X+Y)+A
m_rules.push_back({
{op, {{op, {X, A}}, Y}},
[=]() -> Pattern { return {op, {{op, {X, Y}}, A}}; }
});
// For now, we still need explicit commutativity for the inner pattern
m_rules.push_back({
{op, {{op, {A, X}}, B}},
[=]() -> Pattern { return {op, {X, fun(A.d(), B.d())}}; }
});
m_rules.push_back({
{op, {{op, {A, X}}, Y}},
[=]() -> Pattern { return {op, {{op, {X, Y}}, A}}; }
});
};
//@todo: (x+8)-3 and other things
}
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));
m_spareAssemblyItem.push_back(make_shared<AssemblyItem>(_template.item));
return find(*m_spareAssemblyItem.back(), arguments);
}
Pattern::Pattern(Instruction _instruction, std::vector<Pattern> const& _arguments):
m_type(Operation),
m_requireDataMatch(true),
m_data(_instruction),
m_arguments(_arguments)
{
}
void Pattern::setMatchGroup(unsigned _group, map<unsigned, Expression const*>& _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();
}

150
libevmcore/ExpressionClasses.h

@ -0,0 +1,150 @@
/*
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 <http://www.gnu.org/licenses/>.
*/
/**
* @file ExpressionClasses.h
* @author Christian <c@ethdev.com>
* @date 2015
* Container for equivalence classes of expressions for use in common subexpression elimination.
*/
#pragma once
#include <vector>
#include <map>
#include <memory>
#include <libdevcore/Common.h>
#include <libevmcore/AssemblyItem.h>
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<Id>;
struct Expression
{
Id id;
AssemblyItem const* item;
Ids arguments;
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.
Id find(AssemblyItem const& _item, Ids const& _arguments = {});
/// @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(); }
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<std::pair<Pattern, std::function<Pattern()>>> createRules() const;
/// Expression equivalence class representatives - we only store one item of an equivalence.
std::vector<Expression> m_representatives;
std::vector<std::shared_ptr<AssemblyItem>> 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<Pattern> 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<unsigned, Expression const*>& _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<Pattern> 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<Pattern> m_arguments;
unsigned m_matchGroup = 0;
std::map<unsigned, Expression const*>* 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<ExpressionTemplate> arguments;
};
}
}

31
libjsqrc/ethereumjs/dist/ethereum.js

@ -1527,7 +1527,7 @@ var addEventsToContract = function (contract, desc, address) {
var parser = eventImpl.outputParser(e); var parser = eventImpl.outputParser(e);
return parser(data); return parser(data);
}; };
return web3.eth.filter(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 // this property should be used by eth.filter to check if object is an event
@ -2005,16 +2005,6 @@ var getOptions = function (options) {
options.toBlock = options.latest; options.toBlock = options.latest;
} }
if (options.skip) {
console.warn('"skip" is deprecated, is "offset" instead');
options.offset = options.skip;
}
if (options.max) {
console.warn('"max" is deprecated, is "limit" instead');
options.limit = options.max;
}
// make sure topics, get converted to hex // make sure topics, get converted to hex
if(options.topics instanceof Array) { if(options.topics instanceof Array) {
options.topics = options.topics.map(function(topic){ options.topics = options.topics.map(function(topic){
@ -2022,13 +2012,18 @@ var getOptions = function (options) {
}); });
} }
var asBlockNumber = function(n) {
if (n == null)
return null;
if (n == 'latest' || n == 'pending')
return n;
return utils.toHex(n);
};
// evaluate lazy properties // evaluate lazy properties
return { return {
fromBlock: utils.toHex(options.fromBlock), fromBlock: asBlockNumber(options.fromBlock),
toBlock: utils.toHex(options.toBlock), toBlock: asBlockNumber(options.toBlock),
limit: utils.toHex(options.limit),
offset: utils.toHex(options.offset),
to: options.to, to: options.to,
address: options.address, address: options.address,
topics: options.topics topics: options.topics
@ -2053,9 +2048,9 @@ var filter = function(options, implementation, formatter) {
// call the callbacks // call the callbacks
var onMessages = function (messages) { var onMessages = function (messages) {
messages.forEach(function (message) { messages.forEach(function (message) {
message = formatter ? formatter(message) : message; message = formatter ? formatter(message) : message;
callbacks.forEach(function (callback) { callbacks.forEach(function (callback) {
callback(message); callback(message);
}); });
}); });
}; };
@ -2855,4 +2850,4 @@ module.exports = web3;
},{"./lib/solidity/abi":1,"./lib/web3":6,"./lib/web3/contract":7,"./lib/web3/httpprovider":13,"./lib/web3/qtsync":16}]},{},["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 //# sourceMappingURL=ethereum.js.map

8
libjsqrc/ethereumjs/dist/ethereum.js.map

File diff suppressed because one or more lines are too long

2
libjsqrc/ethereumjs/dist/ethereum.min.js

File diff suppressed because one or more lines are too long

2
libjsqrc/ethereumjs/lib/web3/contract.js

@ -159,7 +159,7 @@ var addEventsToContract = function (contract, desc, address) {
var parser = eventImpl.outputParser(e); var parser = eventImpl.outputParser(e);
return parser(data); return parser(data);
}; };
return web3.eth.filter(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 // this property should be used by eth.filter to check if object is an event

27
libjsqrc/ethereumjs/lib/web3/filter.js

@ -64,16 +64,6 @@ var getOptions = function (options) {
options.toBlock = options.latest; options.toBlock = options.latest;
} }
if (options.skip) {
console.warn('"skip" is deprecated, is "offset" instead');
options.offset = options.skip;
}
if (options.max) {
console.warn('"max" is deprecated, is "limit" instead');
options.limit = options.max;
}
// make sure topics, get converted to hex // make sure topics, get converted to hex
if(options.topics instanceof Array) { if(options.topics instanceof Array) {
options.topics = options.topics.map(function(topic){ options.topics = options.topics.map(function(topic){
@ -81,13 +71,18 @@ var getOptions = function (options) {
}); });
} }
var asBlockNumber = function(n) {
if (n == null)
return null;
if (n == 'latest' || n == 'pending')
return n;
return utils.toHex(n);
};
// evaluate lazy properties // evaluate lazy properties
return { return {
fromBlock: utils.toHex(options.fromBlock), fromBlock: asBlockNumber(options.fromBlock),
toBlock: utils.toHex(options.toBlock), toBlock: asBlockNumber(options.toBlock),
limit: utils.toHex(options.limit),
offset: utils.toHex(options.offset),
to: options.to, to: options.to,
address: options.address, address: options.address,
topics: options.topics topics: options.topics
@ -112,9 +107,9 @@ var filter = function(options, implementation, formatter) {
// call the callbacks // call the callbacks
var onMessages = function (messages) { var onMessages = function (messages) {
messages.forEach(function (message) { messages.forEach(function (message) {
message = formatter ? formatter(message) : message; message = formatter ? formatter(message) : message;
callbacks.forEach(function (callback) { callbacks.forEach(function (callback) {
callback(message); callback(message);
}); });
}); });
}; };

1
libnatspec/NatspecExpressionEvaluator.h

@ -21,7 +21,6 @@
#pragma once #pragma once
#include <QtCore/QObject>
#include <QtCore/QtCore> #include <QtCore/QtCore>
#include <QtQml/QJSEngine> #include <QtQml/QJSEngine>

29
libp2p/Common.cpp

@ -24,6 +24,19 @@ using namespace std;
using namespace dev; using namespace dev;
using namespace dev::p2p; 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 // Helper function to determine if an address falls within one of the reserved ranges
// For V4: // For V4:
// Class A "10.*", Class B "172.[16->31].*", Class C "192.168.*" // 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; 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 // Helper function to determine if an address is localhost
bool p2p::isLocalHostAddress(bi::address const& _addressToCheck) 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(); 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) std::string p2p::reasonOf(DisconnectReason _r)
{ {
switch (_r) switch (_r)
@ -87,3 +110,9 @@ std::string p2p::reasonOf(DisconnectReason _r)
default: return "Unknown reason."; default: return "Unknown reason.";
} }
} }
void Node::cullEndpoint()
{
if (!isPublicAddress(endpoint.tcp.address()) && isPublicAddress(endpoint.udp.address()))
endpoint.tcp.address(endpoint.udp.address());
}

13
libp2p/Common.h

@ -48,10 +48,18 @@ class RLPStream;
namespace p2p namespace p2p
{ {
/// Peer network protocol version.
extern const unsigned c_protocolVersion;
extern const unsigned c_defaultIPPort;
using NodeId = h512; using NodeId = h512;
bool isPrivateAddress(bi::address const& _addressToCheck); bool isPrivateAddress(bi::address const& _addressToCheck);
bool isPrivateAddress(std::string const& _addressToCheck);
bool isLocalHostAddress(bi::address 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 UPnP;
class Capability; class Capability;
@ -59,6 +67,8 @@ class Host;
class Session; class Session;
struct NetworkStartRequired: virtual dev::Exception {}; 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 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; }; 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 NodeId const& address() const { return id; }
virtual Public const& publicKey() 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; NodeId id;
/// Endpoints by which we expect to reach node. /// Endpoints by which we expect to reach node.

211
libp2p/Host.cpp

@ -42,6 +42,12 @@ using namespace std;
using namespace dev; using namespace dev;
using namespace dev::p2p; 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) {} HostNodeTableHandler::HostNodeTableHandler(Host& _host): m_host(_host) {}
void HostNodeTableHandler::processEvent(NodeId const& _n, NodeTableEventType const& _e) void HostNodeTableHandler::processEvent(NodeId const& _n, NodeTableEventType const& _e)
@ -60,10 +66,6 @@ Host::Host(std::string const& _clientVersion, NetworkPreferences const& _n, byte
m_alias(networkAlias(_restoreNetwork)), m_alias(networkAlias(_restoreNetwork)),
m_lastPing(chrono::steady_clock::time_point::min()) 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(); clog(NetNote) << "Id:" << id();
} }
@ -170,11 +172,6 @@ void Host::doneWorking()
m_sessions.clear(); m_sessions.clear();
} }
unsigned Host::protocolVersion() const
{
return 3;
}
void Host::startPeerSession(Public const& _id, RLP const& _rlp, RLPXFrameIO* _io, bi::tcp::endpoint _endpoint) void Host::startPeerSession(Public const& _id, RLP const& _rlp, RLPXFrameIO* _io, bi::tcp::endpoint _endpoint)
{ {
shared_ptr<Peer> p; shared_ptr<Peer> p;
@ -205,7 +202,7 @@ void Host::startPeerSession(Public const& _id, RLP const& _rlp, RLPXFrameIO* _io
// create session so disconnects are managed // create session so disconnects are managed
auto ps = make_shared<Session>(this, _io, p, PeerSessionInfo({_id, clientVersion, _endpoint.address().to_string(), listenPort, chrono::steady_clock::duration(), _rlp[2].toSet<CapDesc>(), 0, map<string, string>()})); auto ps = make_shared<Session>(this, _io, p, PeerSessionInfo({_id, clientVersion, _endpoint.address().to_string(), listenPort, chrono::steady_clock::duration(), _rlp[2].toSet<CapDesc>(), 0, map<string, string>()}));
if (protocolVersion != this->protocolVersion()) if (protocolVersion != dev::p2p::c_protocolVersion)
{ {
ps->disconnect(IncompatibleProtocol); ps->disconnect(IncompatibleProtocol);
return; return;
@ -260,7 +257,7 @@ void Host::onNodeTableEvent(NodeId const& _n, NodeTableEventType const& _e)
p->required = n.required; p->required = n.required;
m_peers[_n] = p; 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; p->endpoint.tcp = n.endpoint.tcp;
} }
@ -286,67 +283,50 @@ void Host::onNodeTableEvent(NodeId const& _n, NodeTableEventType const& _e)
} }
} }
void Host::determinePublic(string const& _publicAddress, bool _upnp) void Host::determinePublic()
{ {
m_peerAddresses.clear(); // set m_tcpPublic := listenIP (if public) > public > upnp > unspecified address.
// no point continuing if there are no interface addresses or valid listen port auto ifAddresses = Network::getInterfaceAddresses();
if (!m_ifAddresses.size() || m_listenPort < 1) auto laddr = m_netPrefs.listenIPAddress.empty() ? bi::address() : bi::address::from_string(m_netPrefs.listenIPAddress);
return; auto lset = !laddr.is_unspecified();
auto paddr = m_netPrefs.publicIPAddress.empty() ? bi::address() : bi::address::from_string(m_netPrefs.publicIPAddress);
// populate interfaces we'll listen on (eth listens on all interfaces); ignores local auto pset = !paddr.is_unspecified();
for (auto addr: m_ifAddresses)
if ((m_netPrefs.localNetworking || !isPrivateAddress(addr)) && !isLocalHostAddress(addr)) bool listenIsPublic = lset && isPublicAddress(laddr);
m_peerAddresses.insert(addr); bool publicIsHost = !lset && pset && ifAddresses.count(paddr);
// if user supplied address is a public address then we use it bi::tcp::endpoint ep(bi::address(), m_netPrefs.listenPort);
// if user supplied address is private, and localnetworking is enabled, we use it if (m_netPrefs.traverseNAT && listenIsPublic)
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)))
{ {
if (!m_peerAddresses.count(reqPublicAddr)) clog(NetNote) << "Listen address set to Public address:" << laddr << ". UPnP disabled.";
m_peerAddresses.insert(reqPublicAddr); ep.address(laddr);
m_tcpPublic = reqPublic;
return;
} }
else if (m_netPrefs.traverseNAT && publicIsHost)
// 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)
{ {
bi::address upnpifaddr; clog(NetNote) << "Public address set to Host configured address:" << paddr << ". UPnP disabled.";
bi::tcp::endpoint upnpep = Network::traverseNAT(m_ifAddresses, m_listenPort, upnpifaddr); ep.address(paddr);
if (!upnpep.address().is_unspecified() && !upnpifaddr.is_unspecified()) }
else if (m_netPrefs.traverseNAT)
{
bi::address natIFAddr;
ep = Network::traverseNAT(lset && ifAddresses.count(laddr) ? std::set<bi::address>({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())) // if public address is set, Host will advertise it, even if upnp returns different
m_peerAddresses.insert(upnpep.address()); clog(NetWarn) << "Specified public address" << paddr << "differs from external address" << ep.address() << "returned by UPnP!";
m_tcpPublic = upnpep; ep.address(paddr);
return;
} }
} }
else if (pset)
ep.address(paddr);
// or if no address provided, use private ipv4 address if local networking is enabled m_tcpPublic = ep;
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);
} }
void Host::runAcceptor() void Host::runAcceptor()
@ -400,7 +380,7 @@ string Host::pocHost()
return "poc-" + strs[1] + ".ethdev.com"; 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) // TODO: p2p clean this up (bring tested acceptor code over from network branch)
while (isWorking() && !m_run) while (isWorking() && !m_run)
@ -416,24 +396,59 @@ void Host::addNode(NodeId const& _node, std::string const& _addr, unsigned short
cwarn << "Private port being recorded - setting to 0"; cwarn << "Private port being recorded - setting to 0";
_tcpPeerPort = 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; void Host::requirePeer(NodeId const& _n, bi::address const& _udpAddr, unsigned short _udpPort, bi::address const& _tcpAddr, unsigned short _tcpPort)
bi::address addr = bi::address::from_string(_addr, ec); {
if (ec) 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); // add or replace peer
r->async_resolve({_addr, toString(_tcpPeerPort)}, [=](boost::system::error_code const& _ec, bi::tcp::resolver::iterator _epIt) shared_ptr<Peer> p;
{ {
if (!_ec) RecursiveGuard l(x_sessions);
if (m_peers.count(_n))
p = m_peers[_n];
else
{ {
bi::tcp::endpoint tcp = *_epIt; p.reset(new Peer());
if (m_nodeTable) m_nodeTable->addNode(Node(_node, NodeIPEndpoint(bi::udp::endpoint(tcp.address(), _udpNodePort), tcp))); 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<boost::asio::deadline_timer> 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<Peer> const& _p) void Host::connect(std::shared_ptr<Peer> const& _p)
@ -474,6 +489,7 @@ void Host::connect(std::shared_ptr<Peer> const& _p)
clog(NetConnect) << "Connection refused to node" << _p->id.abridged() << "@" << _p->peerEndpoint() << "(" << ec.message() << ")"; clog(NetConnect) << "Connection refused to node" << _p->id.abridged() << "@" << _p->peerEndpoint() << "(" << ec.message() << ")";
_p->m_lastDisconnect = TCPError; _p->m_lastDisconnect = TCPError;
_p->m_lastAttempted = std::chrono::system_clock::now(); _p->m_lastAttempted = std::chrono::system_clock::now();
_p->m_failedAttempts++;
} }
else else
{ {
@ -483,6 +499,9 @@ void Host::connect(std::shared_ptr<Peer> const& _p)
Guard l(x_connecting); Guard l(x_connecting);
m_connecting.push_back(handshake); m_connecting.push_back(handshake);
} }
// preempt setting failedAttempts; this value is cleared upon success
_p->m_failedAttempts++;
handshake->start(); handshake->start();
} }
@ -539,6 +558,13 @@ void Host::run(boost::system::error_code const&)
Guard l(x_connecting); Guard l(x_connecting);
m_connecting.remove_if([](std::weak_ptr<RLPXHandshake> h){ return h.lock(); }); m_connecting.remove_if([](std::weak_ptr<RLPXHandshake> h){ return h.lock(); });
} }
{
Guard l(x_timers);
m_timers.remove_if([](std::shared_ptr<boost::asio::deadline_timer> t)
{
return t->expires_from_now().total_milliseconds() > 0;
});
}
for (auto p: m_sessions) for (auto p: m_sessions)
if (auto pp = p.second.lock()) if (auto pp = p.second.lock())
@ -558,7 +584,7 @@ void Host::run(boost::system::error_code const&)
{ {
RecursiveGuard l(x_sessions); RecursiveGuard l(x_sessions);
for (auto p: m_peers) for (auto p: m_peers)
if (p.second->shouldReconnect()) if (p.second->shouldReconnect() && !havePeerSession(p.second->id))
toConnect.push_back(p.second); toConnect.push_back(p.second);
} }
@ -590,27 +616,26 @@ void Host::startedWorking()
m_run = true; m_run = true;
} }
// try to open acceptor (todo: ipv6) // start capability threads (ready for incoming connections)
m_listenPort = Network::tcp4Listen(m_tcp4Acceptor, m_netPrefs.listenPort);
// start capability threads
for (auto const& h: m_capabilities) for (auto const& h: m_capabilities)
h.second->onStarting(); 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 // determine public IP, but only if we're able to listen for connections
// todo: GUI when listen is unavailable in UI // todo: GUI when listen is unavailable in UI
if (m_listenPort) if (m_listenPort)
{ {
determinePublic(m_netPrefs.publicIP, m_netPrefs.upnp); determinePublic();
if (m_listenPort > 0) if (m_listenPort > 0)
runAcceptor(); runAcceptor();
} }
else 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, bi::address::from_string(listenAddress()), listenPort()));
m_nodeTable.reset(new NodeTable(m_ioService, m_alias, m_listenPort > 0 ? m_listenPort : 30303));
m_nodeTable->setEventHandler(new HostNodeTableHandler(*this)); m_nodeTable->setEventHandler(new HostNodeTableHandler(*this));
restoreNetwork(&m_restoreNetwork); restoreNetwork(&m_restoreNetwork);
@ -674,7 +699,7 @@ bytes Host::saveNetwork() const
// TODO: alpha: Figure out why it ever shares these ports.//p.address.port() >= 30300 && p.address.port() <= 30305 && // 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 // 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 // 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())) 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.endpoint.udp.address()) && !isPrivateAddress(p.endpoint.tcp.address()))
{ {
network.appendList(10); network.appendList(10);
if (p.peerEndpoint().address().is_v4()) if (p.peerEndpoint().address().is_v4())
@ -708,7 +733,7 @@ bytes Host::saveNetwork() const
} }
RLPStream ret(3); RLPStream ret(3);
ret << 1 << m_alias.secret(); ret << dev::p2p::c_protocolVersion << m_alias.secret();
ret.appendList(count).appendRaw(network.out(), count); ret.appendList(count).appendRaw(network.out(), count);
return ret.out(); return ret.out();
} }
@ -719,9 +744,12 @@ void Host::restoreNetwork(bytesConstRef _b)
if (!isStarted()) if (!isStarted())
BOOST_THROW_EXCEPTION(NetworkStartRequired()); BOOST_THROW_EXCEPTION(NetworkStartRequired());
if (m_dropPeers)
return;
RecursiveGuard l(x_sessions); RecursiveGuard l(x_sessions);
RLP r(_b); RLP r(_b);
if (r.itemCount() > 0 && r[0].isInt() && r[0].toInt<int>() == 1) if (r.itemCount() > 0 && r[0].isInt() && r[0].toInt<unsigned>() == dev::p2p::c_protocolVersion)
{ {
// r[0] = version // r[0] = version
// r[1] = key // r[1] = key
@ -741,6 +769,13 @@ void Host::restoreNetwork(bytesConstRef _b)
tcp = bi::tcp::endpoint(bi::address_v6(i[0].toArray<byte, 16>()), i[1].toInt<short>()); tcp = bi::tcp::endpoint(bi::address_v6(i[0].toArray<byte, 16>()), i[1].toInt<short>());
udp = bi::udp::endpoint(bi::address_v6(i[0].toArray<byte, 16>()), i[1].toInt<short>()); udp = bi::udp::endpoint(bi::address_v6(i[0].toArray<byte, 16>()), i[1].toInt<short>());
} }
// skip private addresses
// todo: to support private addresseses entries must be stored
// and managed externally by host rather than nodetable.
if (isPrivateAddress(tcp.address()) || isPrivateAddress(udp.address()))
continue;
auto id = (NodeId)i[2]; auto id = (NodeId)i[2];
if (i.itemCount() == 3) if (i.itemCount() == 3)
m_nodeTable->addNode(id, udp, tcp); m_nodeTable->addNode(id, udp, tcp);

42
libp2p/Host.h

@ -70,9 +70,7 @@ private:
* @brief The Host class * @brief The Host class
* Capabilities should be registered prior to startNetwork, since m_capabilities is not thread-safe. * Capabilities should be registered prior to startNetwork, since m_capabilities is not thread-safe.
* *
* @todo cleanup startPeerSession
* @todo determinePublic: ipv6, udp * @todo determinePublic: ipv6, udp
* @todo handle conflict if addNode/requireNode called and Node already exists w/conflicting tcp or udp port
* @todo per-session keepalive/ping instead of broadcast; set ping-timeout via median-latency * @todo per-session keepalive/ping instead of broadcast; set ping-timeout via median-latency
*/ */
class Host: public Worker class Host: public Worker
@ -94,18 +92,9 @@ public:
/// Will block on network process events. /// Will block on network process events.
virtual ~Host(); 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. /// Default host for current version of client.
static std::string pocHost(); static std::string pocHost();
/// Basic peer network protocol version.
unsigned protocolVersion() const;
/// Register a peer-capability; all new peer connections will have this capability. /// Register a peer-capability; all new peer connections will have this capability.
template <class T> std::shared_ptr<T> registerCapability(T* _t) { _t->m_host = this; auto ret = std::shared_ptr<T>(_t); m_capabilities[std::make_pair(T::staticName(), T::staticVersion())] = ret; return ret; } template <class T> std::shared_ptr<T> registerCapability(T* _t) { _t->m_host = this; auto ret = std::shared_ptr<T>(_t); m_capabilities[std::make_pair(T::staticName(), T::staticVersion())] = ret; return ret; }
@ -114,8 +103,14 @@ public:
template <class T> std::shared_ptr<T> cap() const { try { return std::static_pointer_cast<T>(m_capabilities.at(std::make_pair(T::staticName(), T::staticVersion()))); } catch (...) { return nullptr; } } template <class T> std::shared_ptr<T> cap() const { try { return std::static_pointer_cast<T>(m_capabilities.at(std::make_pair(T::staticName(), T::staticVersion()))); } catch (...) { return nullptr; } }
/// Add node as a peer candidate. Node is added if discovery ping is successful and table has capacity. /// Add node as a peer candidate. Node is added if discovery ping is successful and table has capacity.
void addNode(NodeId const& _node, std::string const& _addr, unsigned short _tcpPort, unsigned short _udpPort); 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. /// Set ideal number of peers.
void setIdealPeerCount(unsigned _n) { m_idealPeerCount = _n; } void setIdealPeerCount(unsigned _n) { m_idealPeerCount = _n; }
@ -126,10 +121,10 @@ public:
size_t peerCount() const; size_t peerCount() const;
/// Get the address we're listening on currently. /// 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. /// 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. /// Serialise the set of known peers.
bytes saveNetwork() const; bytes saveNetwork() const;
@ -137,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. // 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; } 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 /// Start network. @threadsafe
void start(); void start();
@ -163,8 +158,8 @@ protected:
private: private:
bool havePeerSession(NodeId _id) { RecursiveGuard l(x_sessions); return m_sessions.count(_id) ? !!m_sessions[_id].lock() : false; } bool havePeerSession(NodeId _id) { RecursiveGuard l(x_sessions); return m_sessions.count(_id) ? !!m_sessions[_id].lock() : false; }
/// Populate m_peerAddresses with available public addresses. /// Determines and sets m_tcpPublic to publicly advertised address.
void determinePublic(std::string const& _publicAddress, bool _upnp); void determinePublic();
void connect(std::shared_ptr<Peer> const& _p); void connect(std::shared_ptr<Peer> const& _p);
@ -201,7 +196,7 @@ private:
NetworkPreferences m_netPrefs; ///< Network settings. NetworkPreferences m_netPrefs; ///< Network settings.
/// Interface addresses (private, public) /// Interface addresses (private, public)
std::vector<bi::address> m_ifAddresses; ///< Interface addresses. std::set<bi::address> m_ifAddresses; ///< Interface addresses.
int m_listenPort = -1; ///< What port are we listening on. -1 means binding failed or acceptor hasn't been initialized. int m_listenPort = -1; ///< What port are we listening on. -1 means binding failed or acceptor hasn't been initialized.
@ -220,6 +215,10 @@ 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; /// 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<NodeId, std::shared_ptr<Peer>> m_peers; std::map<NodeId, std::shared_ptr<Peer>> m_peers;
/// Peers we try to connect regardless of p2p network.
std::set<NodeId> 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()) /// 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 because we flush zombie entries (null-weakptrs) as regular maintenance from a const method.
@ -231,12 +230,15 @@ private:
unsigned m_idealPeerCount = 5; ///< Ideal number of peers to be connected to. unsigned m_idealPeerCount = 5; ///< Ideal number of peers to be connected to.
std::set<bi::address> m_peerAddresses; ///< Public addresses that peers (can) know us by.
std::map<CapDesc, std::shared_ptr<HostCapabilityFace>> m_capabilities; ///< Each of the capabilities we support. std::map<CapDesc, std::shared_ptr<HostCapabilityFace>> m_capabilities; ///< Each of the capabilities we support.
/// Deadline timers used for isolated network events. GC'd by run.
std::list<std::shared_ptr<boost::asio::deadline_timer>> m_timers;
Mutex x_timers;
std::chrono::steady_clock::time_point m_lastPing; ///< Time we sent the last ping to all peers. std::chrono::steady_clock::time_point m_lastPing; ///< Time we sent the last ping to all peers.
bool m_accepting = false; bool m_accepting = false;
bool m_dropPeers = false;
}; };
} }

108
libp2p/Network.cpp

@ -27,6 +27,7 @@
#endif #endif
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/split.hpp>
#include <libdevcore/Common.h> #include <libdevcore/Common.h>
#include <libdevcore/Assertions.h> #include <libdevcore/Assertions.h>
@ -40,9 +41,9 @@ using namespace std;
using namespace dev; using namespace dev;
using namespace dev::p2p; using namespace dev::p2p;
std::vector<bi::address> Network::getInterfaceAddresses() std::set<bi::address> Network::getInterfaceAddresses()
{ {
std::vector<bi::address> addresses; std::set<bi::address> addresses;
#ifdef _WIN32 #ifdef _WIN32
WSAData wsaData; WSAData wsaData;
@ -72,7 +73,7 @@ std::vector<bi::address> Network::getInterfaceAddresses()
char *addrStr = inet_ntoa(addr); char *addrStr = inet_ntoa(addr);
bi::address address(bi::address::from_string(addrStr)); bi::address address(bi::address::from_string(addrStr));
if (!isLocalHostAddress(address)) if (!isLocalHostAddress(address))
addresses.push_back(address.to_v4()); addresses.insert(address.to_v4());
} }
WSACleanup(); WSACleanup();
@ -91,7 +92,7 @@ std::vector<bi::address> Network::getInterfaceAddresses()
in_addr addr = ((struct sockaddr_in *)ifa->ifa_addr)->sin_addr; 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)); boost::asio::ip::address_v4 address(boost::asio::detail::socket_ops::network_to_host_long(addr.s_addr));
if (!isLocalHostAddress(address)) if (!isLocalHostAddress(address))
addresses.push_back(address); addresses.insert(address);
} }
else if (ifa->ifa_addr->sa_family == AF_INET6) else if (ifa->ifa_addr->sa_family == AF_INET6)
{ {
@ -101,7 +102,7 @@ std::vector<bi::address> Network::getInterfaceAddresses()
memcpy(&bytes[0], addr.s6_addr, 16); memcpy(&bytes[0], addr.s6_addr, 16);
boost::asio::ip::address_v6 address(bytes, sockaddr->sin6_scope_id); boost::asio::ip::address_v6 address(bytes, sockaddr->sin6_scope_id);
if (!isLocalHostAddress(address)) if (!isLocalHostAddress(address))
addresses.push_back(address); addresses.insert(address);
} }
} }
@ -113,13 +114,39 @@ std::vector<bi::address> Network::getInterfaceAddresses()
return std::move(addresses); 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; 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::address::from_string(_netPrefs.listenIPAddress), _netPrefs.listenPort);
bi::tcp::endpoint endpoint(bi::tcp::v4(), i ? 0 : _listenPort);
try try
{ {
_acceptor.open(endpoint.protocol()); _acceptor.open(endpoint.protocol());
@ -127,25 +154,18 @@ int Network::tcp4Listen(bi::tcp::acceptor& _acceptor, unsigned short _listenPort
_acceptor.bind(endpoint); _acceptor.bind(endpoint);
_acceptor.listen(); _acceptor.listen();
retport = _acceptor.local_endpoint().port(); retport = _acceptor.local_endpoint().port();
break;
} }
catch (...) catch (...)
{ {
if (i) clog(NetWarn) << "Couldn't start accepting connections on host. Failed to accept socket.\n" << boost::current_exception_diagnostic_information();
{
// 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;
} }
assert(retport == _netPrefs.listenPort);
return retport;
} }
return retport; return retport;
} }
bi::tcp::endpoint Network::traverseNAT(std::vector<bi::address> const& _ifAddresses, unsigned short _listenPort, bi::address& o_upnpifaddr) bi::tcp::endpoint Network::traverseNAT(std::set<bi::address> const& _ifAddresses, unsigned short _listenPort, bi::address& o_upnpInterfaceAddr)
{ {
asserts(_listenPort != 0); asserts(_listenPort != 0);
@ -157,26 +177,26 @@ bi::tcp::endpoint Network::traverseNAT(std::vector<bi::address> const& _ifAddres
// let m_upnp continue as null - we handle it properly. // let m_upnp continue as null - we handle it properly.
catch (...) {} catch (...) {}
bi::tcp::endpoint upnpep; bi::tcp::endpoint upnpEP;
if (upnp && upnp->isValid()) if (upnp && upnp->isValid())
{ {
bi::address paddr; bi::address pAddr;
int extPort = 0; int extPort = 0;
for (auto const& addr: _ifAddresses) for (auto const& addr: _ifAddresses)
if (addr.is_v4() && isPrivateAddress(addr) && (extPort = upnp->addRedirect(addr.to_string().c_str(), _listenPort))) if (addr.is_v4() && isPrivateAddress(addr) && (extPort = upnp->addRedirect(addr.to_string().c_str(), _listenPort)))
{ {
paddr = addr; pAddr = addr;
break; break;
} }
auto eip = upnp->externalIP(); auto eIP = upnp->externalIP();
bi::address eipaddr(bi::address::from_string(eip)); bi::address eIPAddr(bi::address::from_string(eIP));
if (extPort && eip != string("0.0.0.0") && !isPrivateAddress(eipaddr)) 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) << "Punched through NAT and mapped local port" << _listenPort << "onto external port" << extPort << ".";
clog(NetNote) << "External addr:" << eip; clog(NetNote) << "External addr:" << eIP;
o_upnpifaddr = paddr; o_upnpInterfaceAddr = pAddr;
upnpep = bi::tcp::endpoint(eipaddr, (unsigned short)extPort); upnpEP = bi::tcp::endpoint(eIPAddr, (unsigned short)extPort);
} }
else else
clog(NetWarn) << "Couldn't punch through NAT (or no NAT in place)."; clog(NetWarn) << "Couldn't punch through NAT (or no NAT in place).";
@ -185,5 +205,33 @@ bi::tcp::endpoint Network::traverseNAT(std::vector<bi::address> const& _ifAddres
delete upnp; delete upnp;
} }
return upnpep; return upnpEP;
} }
bi::tcp::endpoint Network::resolveHost(string const& _addr)
{
static boost::asio::io_service s_resolverIoService;
vector<string> 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;
}

26
libp2p/Network.h

@ -39,12 +39,19 @@ namespace p2p
struct NetworkPreferences 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; unsigned short listenPort = 30303;
std::string publicIP; bool traverseNAT = true;
bool upnp = true;
bool localNetworking = false;
}; };
/** /**
@ -55,14 +62,17 @@ class Network
{ {
public: public:
/// @returns public and private interface addresses /// @returns public and private interface addresses
static std::vector<bi::address> getInterfaceAddresses(); static std::set<bi::address> getInterfaceAddresses();
/// Try to bind and listen on _listenPort, else attempt net-allocated port. /// 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. /// 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<bi::address> const& _ifAddresses, unsigned short _listenPort, bi::address& o_upnpifaddr); static bi::tcp::endpoint traverseNAT(std::set<bi::address> 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);
}; };
} }
} }

110
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, 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)) {} 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): NodeTable::NodeTable(ba::io_service& _io, KeyPair _alias, bi::address const& _udpAddress, uint16_t _udp):
m_node(Node(_alias.pub(), bi::udp::endpoint())), m_node(Node(_alias.pub(), bi::udp::endpoint(_udpAddress, _udp))),
m_secret(_alias.sec()), m_secret(_alias.sec()),
m_io(_io), 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_socketPointer(m_socket.get()),
m_bucketRefreshTimer(m_io), m_bucketRefreshTimer(m_io),
m_evictionCheckTimer(m_io) m_evictionCheckTimer(m_io)
@ -70,10 +70,22 @@ shared_ptr<NodeEntry> NodeTable::addNode(Public const& _pubk, bi::udp::endpoint
shared_ptr<NodeEntry> NodeTable::addNode(Node const& _node) shared_ptr<NodeEntry> 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<NodeEntry>());
}
// ping address to recover nodeid if nodeid is empty
if (!_node.id) if (!_node.id)
{ {
m_pubkDiscoverPings[m_node.endpoint.udp.address()] = std::chrono::steady_clock::now(); 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()); PingNode p(_node.endpoint.udp, m_node.endpoint.udp.address().to_string(), m_node.endpoint.udp.port());
p.sign(m_secret); p.sign(m_secret);
m_socketPointer->send(p); m_socketPointer->send(p);
@ -88,6 +100,8 @@ shared_ptr<NodeEntry> NodeTable::addNode(Node const& _node)
shared_ptr<NodeEntry> ret(new NodeEntry(m_node, _node.id, NodeIPEndpoint(_node.endpoint.udp, _node.endpoint.tcp))); shared_ptr<NodeEntry> ret(new NodeEntry(m_node, _node.id, NodeIPEndpoint(_node.endpoint.udp, _node.endpoint.tcp)));
m_nodes[_node.id] = ret; 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()); PingNode p(_node.endpoint.udp, m_node.endpoint.udp.address().to_string(), m_node.endpoint.udp.port());
p.sign(m_secret); p.sign(m_secret);
m_socketPointer->send(p); m_socketPointer->send(p);
@ -299,10 +313,9 @@ void NodeTable::noteActiveNode(Public const& _pubk, bi::udp::endpoint const& _en
if (!!node && !node->pending) if (!!node && !node->pending)
{ {
clog(NodeTableConnect) << "Noting active node:" << _pubk.abridged() << _endpoint.address().to_string() << ":" << _endpoint.port(); clog(NodeTableConnect) << "Noting active node:" << _pubk.abridged() << _endpoint.address().to_string() << ":" << _endpoint.port();
// update udp endpoint
node->endpoint.udp.address(_endpoint.address()); node->endpoint.udp.address(_endpoint.address());
node->endpoint.udp.port(_endpoint.port()); node->endpoint.udp.port(_endpoint.port());
node->cullEndpoint();
shared_ptr<NodeEntry> contested; shared_ptr<NodeEntry> contested;
{ {
@ -326,7 +339,7 @@ void NodeTable::noteActiveNode(Public const& _pubk, bi::udp::endpoint const& _en
s.nodes.push_back(node); s.nodes.push_back(node);
s.touch(); s.touch();
if (!removed) if (!removed && m_nodeEventHandler)
m_nodeEventHandler->appendEvent(node->id, NodeEntryAdded); m_nodeEventHandler->appendEvent(node->id, NodeEntryAdded);
} }
} }
@ -335,7 +348,7 @@ void NodeTable::noteActiveNode(Public const& _pubk, bi::udp::endpoint const& _en
s.nodes.push_back(node); s.nodes.push_back(node);
s.touch(); s.touch();
if (!removed) if (!removed && m_nodeEventHandler)
m_nodeEventHandler->appendEvent(node->id, NodeEntryAdded); m_nodeEventHandler->appendEvent(node->id, NodeEntryAdded);
} }
} }
@ -384,7 +397,7 @@ void NodeTable::onReceived(UDPSocketFace*, bi::udp::endpoint const& _from, bytes
bytesConstRef signedBytes(hashedBytes.cropped(Signature::size, hashedBytes.size() - Signature::size)); 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)); bytesConstRef sigBytes(_packet.cropped(h256::size, Signature::size));
Public nodeid(dev::recover(*(Signature const*)sigBytes.data(), sha3(signedBytes))); Public nodeid(dev::recover(*(Signature const*)sigBytes.data(), sha3(signedBytes)));
@ -415,8 +428,7 @@ void NodeTable::onReceived(UDPSocketFace*, bi::udp::endpoint const& _from, bytes
dropNode(n); dropNode(n);
if (auto n = nodeEntry(it->first.first)) if (auto n = nodeEntry(it->first.first))
if (m_nodeEventHandler && n->pending) n->pending = false;
n->pending = false;
it = m_evictions.erase(it); it = m_evictions.erase(it);
} }
@ -426,12 +438,20 @@ void NodeTable::onReceived(UDPSocketFace*, bi::udp::endpoint const& _from, bytes
{ {
if (auto n = nodeEntry(nodeid)) if (auto n = nodeEntry(nodeid))
n->pending = false; 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
} }
else if (m_pubkDiscoverPings.count(_from.address()))
m_pubkDiscoverPings.erase(_from.address());
else
return; // unsolicited pong; don't note node as active
clog(NodeTableConnect) << "PONG from " << nodeid.abridged() << _from;
break; break;
} }
@ -463,6 +483,13 @@ void NodeTable::onReceived(UDPSocketFace*, bi::udp::endpoint const& _from, bytes
case PingNode::type: case PingNode::type:
{ {
PingNode in = PingNode::fromBytesConstRef(_from, rlpBytes); 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)); addNode(nodeid, _from, bi::tcp::endpoint(bi::address::from_string(in.ipAddress), in.port));
Pong p(_from); Pong p(_from);
@ -525,31 +552,42 @@ void NodeTable::doRefreshBuckets(boost::system::error_code const& _ec)
clog(NodeTableEvent) << "refreshing buckets"; clog(NodeTableEvent) << "refreshing buckets";
bool connected = m_socketPointer->isOpen(); bool connected = m_socketPointer->isOpen();
bool refreshed = false;
if (connected) if (connected)
{ {
Guard l(x_state); NodeId randNodeId;
for (auto& d: m_state) crypto::Nonce::get().ref().copyTo(randNodeId.ref().cropped(0, h256::size));
if (chrono::steady_clock::now() - d.modified > c_bucketRefresh) crypto::Nonce::get().ref().copyTo(randNodeId.ref().cropped(h256::size, h256::size));
{ discover(randNodeId);
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();
}
}
} }
unsigned nextRefresh = connected ? (refreshed ? 200 : c_bucketRefresh.count()*1000) : 10000;
auto runcb = [this](boost::system::error_code const& error) { doRefreshBuckets(error); }; 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); 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<unsigned>(RLP::Strict);
expiration = r[2].toInt<unsigned>(RLP::Strict);
}
else if (r.itemCountStrict() == 4)
{
version = r[0].toInt<unsigned>(RLP::Strict);
ipAddress = r[1].toString();
port = r[2].toInt<unsigned>(RLP::Strict);
expiration = r[3].toInt<unsigned>(RLP::Strict);
}
else
BOOST_THROW_EXCEPTION(InvalidRLP());
}

13
libp2p/NodeTable.h

@ -135,7 +135,8 @@ class NodeTable: UDPSocketEvents, public std::enable_shared_from_this<NodeTable>
using EvictionTimeout = std::pair<std::pair<NodeId, TimePoint>, NodeId>; ///< First NodeId may be evicted and replaced with second NodeId. using EvictionTimeout = std::pair<std::pair<NodeId, TimePoint>, NodeId>; ///< First NodeId may be evicted and replaced with second NodeId.
public: 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(); ~NodeTable();
/// Returns distance based on xor metric two node ids. Used by NodeEntry and NodeTable. /// Returns distance based on xor metric two node ids. Used by NodeEntry and NodeTable.
@ -194,7 +195,7 @@ private:
/* todo: replace boost::posix_time; change constants to upper camelcase */ /* 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. 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::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 struct NodeBucket
{ {
@ -287,6 +288,8 @@ inline std::ostream& operator<<(std::ostream& _out, NodeTable const& _nodeTable)
return _out; return _out;
} }
struct InvalidRLP: public Exception {};
/** /**
* Ping packet: Sent to check if node is alive. * Ping packet: Sent to check if node is alive.
* PingNode is cached and regenerated after expiration - t, where t is timeout. * PingNode is cached and regenerated after expiration - t, where t is timeout.
@ -315,13 +318,13 @@ struct PingNode: RLPXDatagram<PingNode>
static const uint8_t type = 1; static const uint8_t type = 1;
unsigned version = 1; unsigned version = 0;
std::string ipAddress; std::string ipAddress;
unsigned port; unsigned port;
unsigned expiration; unsigned expiration;
void streamRLP(RLPStream& _s) const { _s.appendList(3); _s << ipAddress << port << expiration; } void streamRLP(RLPStream& _s) const override;
void interpretRLP(bytesConstRef _bytes) { RLP r(_bytes); ipAddress = r[0].toString(); port = r[1].toInt<unsigned>(); expiration = r[2].toInt<unsigned>(); } void interpretRLP(bytesConstRef _bytes) override;
}; };
/** /**

1
libp2p/Peer.h

@ -47,7 +47,6 @@ namespace p2p
* those peers. Modifying these properties via a storage backend alleviates * those peers. Modifying these properties via a storage backend alleviates
* Host of the responsibility. (&& remove save/restoreNetwork) * Host of the responsibility. (&& remove save/restoreNetwork)
* @todo reimplement recording of historical session information on per-transport basis * @todo reimplement recording of historical session information on per-transport basis
* @todo rebuild nodetable when localNetworking is enabled/disabled
* @todo move attributes into protected * @todo move attributes into protected
*/ */
class Peer: public Node class Peer: public Node

29
libp2p/RLPxHandshake.cpp

@ -131,7 +131,14 @@ void RLPXHandshake::readAck()
void RLPXHandshake::error() void RLPXHandshake::error()
{ {
clog(NetConnect) << "Disconnecting " << m_socket->remoteEndpoint() << " (Handshake Failed)"; 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(); m_socket->close();
if (m_io != nullptr) if (m_io != nullptr)
delete m_io; delete m_io;
@ -139,8 +146,11 @@ void RLPXHandshake::error()
void RLPXHandshake::transition(boost::system::error_code _ech) void RLPXHandshake::transition(boost::system::error_code _ech)
{ {
if (_ech || m_nextState == Error) if (_ech || m_nextState == Error || m_cancel)
{
clog(NetConnect) << "Handshake Failed (I/O Error:" << _ech.message() << ")";
return error(); return error();
}
auto self(shared_from_this()); auto self(shared_from_this());
if (m_nextState == New) if (m_nextState == New)
@ -172,7 +182,7 @@ void RLPXHandshake::transition(boost::system::error_code _ech)
// 5 arguments, HelloPacket // 5 arguments, HelloPacket
RLPStream s; RLPStream s;
s.append((unsigned)0).appendList(5) s.append((unsigned)0).appendList(5)
<< m_host->protocolVersion() << dev::p2p::c_protocolVersion
<< m_host->m_clientVersion << m_host->m_clientVersion
<< m_host->caps() << m_host->caps()
<< m_host->listenPort() << m_host->listenPort()
@ -252,11 +262,22 @@ void RLPXHandshake::transition(boost::system::error_code _ech)
} }
clog(NetNote) << (m_originated ? "p2p.connect.egress" : "p2p.connect.ingress") << "hello frame: success. starting session."; clog(NetNote) << (m_originated ? "p2p.connect.egress" : "p2p.connect.ingress") << "hello frame: success. starting session.";
RLP rlp(frame.cropped(1)); RLP rlp(frame.cropped(1), RLP::ThrowOnFail | RLP::FailIfTooSmall);
m_host->startPeerSession(m_remote, rlp, m_io, m_socket->remoteEndpoint()); 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();
}
});
} }

14
libp2p/RLPxHandshake.h

@ -62,17 +62,18 @@ class RLPXHandshake: public std::enable_shared_from_this<RLPXHandshake>
public: public:
/// Setup incoming connection. /// Setup incoming connection.
RLPXHandshake(Host* _host, std::shared_ptr<RLPXSocket> const& _socket): m_host(_host), m_originated(false), m_socket(_socket) { crypto::Nonce::get().ref().copyTo(m_nonce.ref()); } RLPXHandshake(Host* _host, std::shared_ptr<RLPXSocket> 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. /// Setup outbound connection.
RLPXHandshake(Host* _host, std::shared_ptr<RLPXSocket> const& _socket, NodeId _remote): m_host(_host), m_remote(_remote), m_originated(true), m_socket(_socket) { crypto::Nonce::get().ref().copyTo(m_nonce.ref()); } RLPXHandshake(Host* _host, std::shared_ptr<RLPXSocket> 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() {} ~RLPXHandshake() {}
/// Start handshake. /// Start handshake.
void start() { transition(); } void start() { transition(); }
void cancel() { m_nextState = Error; } /// Cancels handshake preventing
void cancel() { m_cancel = true; }
protected: protected:
/// Write Auth message to socket and transitions to AckAuth. /// Write Auth message to socket and transitions to AckAuth.
@ -93,7 +94,11 @@ protected:
/// Performs transition for m_nextState. /// Performs transition for m_nextState.
void transition(boost::system::error_code _ech = boost::system::error_code()); 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. 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(). Host* m_host; ///< Host which provides m_alias, protocolVersion(), m_clientVersion, caps(), and TCP listenPort().
@ -119,7 +124,8 @@ protected:
/// Passed onto Host which will take ownership. /// Passed onto Host which will take ownership.
RLPXFrameIO* m_io = nullptr; RLPXFrameIO* m_io = nullptr;
std::shared_ptr<RLPXSocket> m_socket; ///< Socket. std::shared_ptr<RLPXSocket> m_socket; ///< Socket.
boost::asio::deadline_timer m_idleTimer; ///< Timer which enforces c_timeout.
}; };
} }

9
libp2p/Session.cpp

@ -51,6 +51,7 @@ Session::Session(Host* _s, RLPXFrameIO* _io, std::shared_ptr<Peer> const& _n, Pe
Session::~Session() Session::~Session()
{ {
clogS(NetMessageSummary) << "Closing Peer Session :-(";
m_peer->m_lastConnected = m_peer->m_lastAttempted - chrono::seconds(1); m_peer->m_lastConnected = m_peer->m_lastAttempted - chrono::seconds(1);
// Read-chain finished for one reason or another. // Read-chain finished for one reason or another.
@ -215,12 +216,8 @@ bool Session::interpret(PacketType _t, RLP const& _r)
NodeId id = _r[i][2].toHash<NodeId>(); NodeId id = _r[i][2].toHash<NodeId>();
clogS(NetAllDetail) << "Checking: " << ep << "(" << id.abridged() << ")"; 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);
// todo: draft spec: ignore if dist(us,item) - dist(us,them) > 1 if (!isPublicAddress(peerAddress))
// TODO: isPrivate
if (!m_server->m_netPrefs.localNetworking && isPrivateAddress(peerAddress))
goto CONTINUE; // Private address. Ignore. goto CONTINUE; // Private address. Ignore.
if (!id) if (!id)
@ -240,7 +237,7 @@ bool Session::interpret(PacketType _t, RLP const& _r)
// OK passed all our checks. Assume it's good. // OK passed all our checks. Assume it's good.
addRating(1000); addRating(1000);
m_server->addNode(id, ep.address().to_string(), ep.port(), ep.port()); m_server->addNode(id, ep.address(), ep.port(), ep.port());
clogS(NetTriviaDetail) << "New peer: " << ep << "(" << id .abridged()<< ")"; clogS(NetTriviaDetail) << "New peer: " << ep << "(" << id .abridged()<< ")";
CONTINUE:; CONTINUE:;
LAMEPEER:; LAMEPEER:;

22
libp2p/UDP.h

@ -77,7 +77,7 @@ template <class T>
struct RLPXDatagram: public RLPXDatagramFace struct RLPXDatagram: public RLPXDatagramFace
{ {
RLPXDatagram(bi::udp::endpoint const& _ep): RLPXDatagramFace(_ep) {} 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; } uint8_t packetType() { return T::type; }
}; };
@ -113,6 +113,10 @@ public:
enum { maxDatagramSize = MaxDatagramSize }; enum { maxDatagramSize = MaxDatagramSize };
static_assert(maxDatagramSize < 65507, "UDP datagrams cannot be larger than 65507 bytes"); 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); }; 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(); } virtual ~UDPSocket() { disconnect(); }
@ -159,7 +163,14 @@ void UDPSocket<Handler, MaxDatagramSize>::connect()
return; return;
m_socket.open(bi::udp::v4()); 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 // clear write queue so reconnect doesn't send stale messages
Guard l(x_sendQ); Guard l(x_sendQ);
@ -192,7 +203,10 @@ void UDPSocket<Handler, MaxDatagramSize>::doRead()
auto self(UDPSocket<Handler, MaxDatagramSize>::shared_from_this()); auto self(UDPSocket<Handler, MaxDatagramSize>::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) 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); return disconnectWithError(_ec);
assert(_len); assert(_len);
@ -211,7 +225,7 @@ void UDPSocket<Handler, MaxDatagramSize>::doWrite()
auto self(UDPSocket<Handler, MaxDatagramSize>::shared_from_this()); auto self(UDPSocket<Handler, MaxDatagramSize>::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) 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); return disconnectWithError(_ec);
else else
{ {

22
libsolidity/AST.cpp

@ -88,7 +88,7 @@ void ContractDefinition::checkTypeRequirements()
if (hashes.count(hash)) if (hashes.count(hash))
BOOST_THROW_EXCEPTION(createTypeError( BOOST_THROW_EXCEPTION(createTypeError(
std::string("Function signature hash collision for ") + std::string("Function signature hash collision for ") +
it.second->getCanonicalSignature())); it.second->externalSignature()));
hashes.insert(hash); hashes.insert(hash);
} }
} }
@ -192,7 +192,7 @@ vector<pair<FixedHash<4>, FunctionTypePointer>> const& ContractDefinition::getIn
if (functionsSeen.count(f->getName()) == 0 && f->isPartOfExternalInterface()) if (functionsSeen.count(f->getName()) == 0 && f->isPartOfExternalInterface())
{ {
functionsSeen.insert(f->getName()); functionsSeen.insert(f->getName());
FixedHash<4> hash(dev::sha3(f->getCanonicalSignature())); FixedHash<4> hash(dev::sha3(f->externalSignature()));
m_interfaceFunctionList->push_back(make_pair(hash, make_shared<FunctionType>(*f, false))); m_interfaceFunctionList->push_back(make_pair(hash, make_shared<FunctionType>(*f, false)));
} }
@ -200,8 +200,9 @@ vector<pair<FixedHash<4>, FunctionTypePointer>> const& ContractDefinition::getIn
if (functionsSeen.count(v->getName()) == 0 && v->isPartOfExternalInterface()) if (functionsSeen.count(v->getName()) == 0 && v->isPartOfExternalInterface())
{ {
FunctionType ftype(*v); FunctionType ftype(*v);
solAssert(v->getType().get(), "");
functionsSeen.insert(v->getName()); 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<FunctionType>(*v))); m_interfaceFunctionList->push_back(make_pair(hash, make_shared<FunctionType>(*v)));
} }
} }
@ -305,8 +306,12 @@ TypePointer FunctionDefinition::getType(ContractDefinition const*) const
void FunctionDefinition::checkTypeRequirements() void FunctionDefinition::checkTypeRequirements()
{ {
for (ASTPointer<VariableDeclaration> const& var: getParameters() + getReturnParameters()) for (ASTPointer<VariableDeclaration> const& var: getParameters() + getReturnParameters())
{
if (!var->getType()->canLiveOutsideStorage()) if (!var->getType()->canLiveOutsideStorage())
BOOST_THROW_EXCEPTION(var->createTypeError("Type is required to live outside storage.")); BOOST_THROW_EXCEPTION(var->createTypeError("Type is required to live outside storage."));
if (!(var->getType()->externalType()) && getVisibility() >= Visibility::Public)
BOOST_THROW_EXCEPTION(var->createTypeError("Internal type is not allowed for function with external visibility"));
}
for (ASTPointer<ModifierInvocation> const& modifier: m_functionModifiers) for (ASTPointer<ModifierInvocation> const& modifier: m_functionModifiers)
modifier->checkTypeRequirements(isConstructor() ? modifier->checkTypeRequirements(isConstructor() ?
dynamic_cast<ContractDefinition const&>(*getScope()).getBaseContracts() : dynamic_cast<ContractDefinition const&>(*getScope()).getBaseContracts() :
@ -315,9 +320,9 @@ void FunctionDefinition::checkTypeRequirements()
m_body->checkTypeRequirements(); 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 bool VariableDeclaration::isLValue() const
@ -342,8 +347,11 @@ void VariableDeclaration::checkTypeRequirements()
if (!m_value) if (!m_value)
return; return;
if (m_type) if (m_type)
{
m_value->expectType(*m_type); m_value->expectType(*m_type);
else if (m_isStateVariable && !m_type->externalType() && getVisibility() >= Visibility::Public)
BOOST_THROW_EXCEPTION(createTypeError("Internal type is not allowed for state variables."));
} else
{ {
// no type declared and no previous assignment, infer the type // no type declared and no previous assignment, infer the type
m_value->checkTypeRequirements(); m_value->checkTypeRequirements();
@ -422,6 +430,8 @@ void EventDefinition::checkTypeRequirements()
numIndexed++; numIndexed++;
if (!var->getType()->canLiveOutsideStorage()) if (!var->getType()->canLiveOutsideStorage())
BOOST_THROW_EXCEPTION(var->createTypeError("Type is required to live outside storage.")); 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) if (numIndexed > 3)
BOOST_THROW_EXCEPTION(createTypeError("More than 3 indexed arguments for event.")); BOOST_THROW_EXCEPTION(createTypeError("More than 3 indexed arguments for event."));

4
libsolidity/AST.h

@ -421,10 +421,10 @@ public:
/// Checks that all parameters have allowed types and calls checkTypeRequirements on the body. /// Checks that all parameters have allowed types and calls checkTypeRequirements on the body.
void checkTypeRequirements(); 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 /// 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. /// arguments separated by commas all enclosed in parentheses without any spaces.
std::string getCanonicalSignature() const; std::string externalSignature() const;
private: private:
bool m_isConstructor; bool m_isConstructor;

207
libsolidity/ArrayUtils.cpp

@ -52,15 +52,21 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
// TODO unroll loop for small sizes // TODO unroll loop for small sizes
bool sourceIsStorage = _sourceType.getLocation() == ArrayType::Location::Storage; 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 // stack: source_ref [source_byte_off] [source_length] target_ref target_byte_off
// store target_ref // store target_ref
m_context << eth::Instruction::POP; //@todo // arrays always start at zero byte offset, pop offset
m_context << eth::Instruction::POP;
for (unsigned i = _sourceType.getSizeOnStack(); i > 0; --i) for (unsigned i = _sourceType.getSizeOnStack(); i > 0; --i)
m_context << eth::swapInstruction(i); m_context << eth::swapInstruction(i);
// stack: target_ref source_ref [source_byte_off] [source_length] // stack: target_ref source_ref [source_byte_off] [source_length]
if (sourceIsStorage) if (sourceIsStorage)
m_context << eth::Instruction::POP; //@todo // arrays always start at zero byte offset, pop offset
m_context << eth::Instruction::POP;
// stack: target_ref source_ref [source_length] // stack: target_ref source_ref [source_length]
// retrieve source length // retrieve source length
if (_sourceType.getLocation() != ArrayType::Location::CallData || !_sourceType.isDynamicallySized()) if (_sourceType.getLocation() != ArrayType::Location::CallData || !_sourceType.isDynamicallySized())
@ -81,7 +87,7 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
m_context m_context
<< eth::Instruction::POP << eth::Instruction::POP << eth::Instruction::POP << eth::Instruction::POP
<< eth::Instruction::POP << eth::Instruction::POP; << eth::Instruction::POP << eth::Instruction::POP;
m_context << u256(0); //@todo m_context << u256(0);
return; return;
} }
// compute hashes (data positions) // compute hashes (data positions)
@ -97,8 +103,8 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
// stack: target_ref target_data_end source_length target_data_pos source_ref // stack: target_ref target_data_end source_length target_data_pos source_ref
// skip copying if source length is zero // skip copying if source length is zero
m_context << eth::Instruction::DUP3 << eth::Instruction::ISZERO; m_context << eth::Instruction::DUP3 << eth::Instruction::ISZERO;
eth::AssemblyItem copyLoopEnd = m_context.newTag(); eth::AssemblyItem copyLoopEndWithoutByteOffset = m_context.newTag();
m_context.appendConditionalJumpTo(copyLoopEnd); m_context.appendConditionalJumpTo(copyLoopEndWithoutByteOffset);
if (_sourceType.getLocation() == ArrayType::Location::Storage && _sourceType.isDynamicallySized()) if (_sourceType.getLocation() == ArrayType::Location::Storage && _sourceType.isDynamicallySized())
CompilerUtils(m_context).computeHashStatic(); CompilerUtils(m_context).computeHashStatic();
@ -107,18 +113,24 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
convertLengthToSize(_sourceType); convertLengthToSize(_sourceType);
m_context << eth::Instruction::DUP3 << eth::Instruction::ADD; m_context << eth::Instruction::DUP3 << eth::Instruction::ADD;
// 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
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(); eth::AssemblyItem copyLoopStart = m_context.newTag();
m_context << copyLoopStart; m_context << copyLoopStart;
// check for loop condition // check for loop condition
m_context m_context
<< eth::Instruction::DUP3 << eth::Instruction::DUP2 << eth::dupInstruction(3 + byteOffsetSize) << eth::dupInstruction(2 + byteOffsetSize)
<< eth::Instruction::GT << eth::Instruction::ISZERO; << eth::Instruction::GT << eth::Instruction::ISZERO;
eth::AssemblyItem copyLoopEnd = m_context.newTag();
m_context.appendConditionalJumpTo(copyLoopEnd); 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 // copy
if (sourceBaseType->getCategory() == Type::Category::Array) if (sourceBaseType->getCategory() == Type::Category::Array)
{ {
//@todo solAssert(byteOffsetSize == 0, "Byte offset for array as base type.");
m_context << eth::Instruction::DUP3; m_context << eth::Instruction::DUP3;
if (sourceIsStorage) if (sourceIsStorage)
m_context << u256(0); m_context << u256(0);
@ -129,36 +141,80 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
); );
m_context << eth::Instruction::POP << 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 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 (_sourceType.getLocation() == ArrayType::Location::Storage)
{ {
m_context << u256(0); if (haveByteOffsetSource)
m_context << eth::Instruction::DUP2;
else
m_context << u256(0);
StorageItem(m_context, *sourceBaseType).retrieveValue(SourceLocation(), true); StorageItem(m_context, *sourceBaseType).retrieveValue(SourceLocation(), true);
} }
else if (sourceBaseType->isValueType()) else if (sourceBaseType->isValueType())
CompilerUtils(m_context).loadFromMemoryDynamic(*sourceBaseType, true, true, false); CompilerUtils(m_context).loadFromMemoryDynamic(*sourceBaseType, true, true, false);
else else
solAssert(false, "Copying of unknown type requested: " + sourceBaseType->toString()); solAssert(false, "Copying of unknown type requested: " + sourceBaseType->toString());
solAssert(2 + sourceBaseType->getSizeOnStack() <= 16, "Stack too deep."); // stack: target_ref target_data_end source_data_pos target_data_pos source_data_end [target_byte_offset] [source_byte_offset] <source_value>...
m_context << eth::dupInstruction(2 + sourceBaseType->getSizeOnStack()) << u256(0); 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); 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 // increment source
m_context if (haveByteOffsetSource)
<< eth::Instruction::SWAP2 incrementByteOffset(sourceBaseType->getStorageBytes(), 1, haveByteOffsetTarget ? 5 : 4);
<< (sourceIsStorage ? sourceBaseType->getStorageSize() : sourceBaseType->getCalldataEncodedSize()) else
<< eth::Instruction::ADD m_context
<< eth::Instruction::SWAP2; << eth::swapInstruction(2 + byteOffsetSize)
<< (sourceIsStorage ? sourceBaseType->getStorageSize() : sourceBaseType->getCalldataEncodedSize())
<< eth::Instruction::ADD
<< eth::swapInstruction(2 + byteOffsetSize);
// increment target // increment target
m_context if (haveByteOffsetTarget)
<< eth::Instruction::SWAP1 incrementByteOffset(targetBaseType->getStorageBytes(), byteOffsetSize, byteOffsetSize + 2);
<< targetBaseType->getStorageSize() else
<< eth::Instruction::ADD m_context
<< eth::Instruction::SWAP1; << eth::swapInstruction(1 + byteOffsetSize)
<< targetBaseType->getStorageSize()
<< eth::Instruction::ADD
<< eth::swapInstruction(1 + byteOffsetSize);
m_context.appendJumpTo(copyLoopStart); m_context.appendJumpTo(copyLoopStart);
m_context << copyLoopEnd; 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 // zero-out leftovers in target
// stack: target_ref target_data_end source_data_pos target_data_pos_updated source_data_end // stack: target_ref target_data_end source_data_pos target_data_pos_updated source_data_end
@ -166,41 +222,61 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons
// stack: target_ref target_data_end target_data_pos_updated // stack: target_ref target_data_end target_data_pos_updated
clearStorageLoop(*targetBaseType); clearStorageLoop(*targetBaseType);
m_context << eth::Instruction::POP; m_context << eth::Instruction::POP;
m_context << u256(0); //@todo m_context << u256(0);
} }
void ArrayUtils::clearArray(ArrayType const& _type) const void ArrayUtils::clearArray(ArrayType const& _type) const
{ {
unsigned stackHeightStart = m_context.getStackHeight(); unsigned stackHeightStart = m_context.getStackHeight();
solAssert(_type.getLocation() == ArrayType::Location::Storage, ""); solAssert(_type.getLocation() == ArrayType::Location::Storage, "");
if (_type.isDynamicallySized()) if (_type.getBaseType()->getStorageBytes() < 32)
{ {
m_context << eth::Instruction::POP; // remove byte offset solAssert(_type.getBaseType()->isValueType(), "Invalid storage size for non-value type.");
clearDynamicArray(_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) else if (_type.getLength() == 0 || _type.getBaseType()->getCategory() == Type::Category::Mapping)
m_context << eth::Instruction::POP << eth::Instruction::POP; 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)
{
// 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)
{ {
solAssert(!_type.isByteArray(), ""); // 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) for (unsigned i = 1; i < _type.getLength(); ++i)
{ {
m_context << u256(0);
StorageItem(m_context, *_type.getBaseType()).setToZero(SourceLocation(), false); StorageItem(m_context, *_type.getBaseType()).setToZero(SourceLocation(), false);
m_context << eth::Instruction::SWAP1; m_context
m_context << u256(_type.getBaseType()->getStorageSize()) << eth::Instruction::ADD; << eth::Instruction::POP
m_context << eth::Instruction::SWAP1; << u256(_type.getBaseType()->getStorageSize()) << eth::Instruction::ADD;
} }
m_context << u256(0);
StorageItem(m_context, *_type.getBaseType()).setToZero(SourceLocation(), true); StorageItem(m_context, *_type.getBaseType()).setToZero(SourceLocation(), true);
} }
else else
{ {
solAssert(!_type.isByteArray(), "");
m_context << eth::Instruction::SWAP1;
m_context << eth::Instruction::DUP1 << _type.getLength(); m_context << eth::Instruction::DUP1 << _type.getLength();
convertLengthToSize(_type); convertLengthToSize(_type);
m_context << eth::Instruction::ADD << eth::Instruction::SWAP1; m_context << eth::Instruction::ADD << eth::Instruction::SWAP1;
clearStorageLoop(*_type.getBaseType()); if (_type.getBaseType()->getStorageBytes() < 32)
m_context << eth::Instruction::POP << eth::Instruction::POP; clearStorageLoop(IntegerType(256));
else
clearStorageLoop(*_type.getBaseType());
m_context << eth::Instruction::POP;
} }
solAssert(m_context.getStackHeight() == stackHeightStart - 2, ""); solAssert(m_context.getStackHeight() == stackHeightStart - 2, "");
} }
@ -224,7 +300,7 @@ void ArrayUtils::clearDynamicArray(ArrayType const& _type) const
m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2 << eth::Instruction::ADD m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2 << eth::Instruction::ADD
<< eth::Instruction::SWAP1; << eth::Instruction::SWAP1;
// stack: data_pos_end data_pos // stack: data_pos_end data_pos
if (_type.isByteArray()) if (_type.isByteArray() || _type.getBaseType()->getStorageBytes() < 32)
clearStorageLoop(IntegerType(256)); clearStorageLoop(IntegerType(256));
else else
clearStorageLoop(*_type.getBaseType()); clearStorageLoop(*_type.getBaseType());
@ -237,6 +313,8 @@ void ArrayUtils::resizeDynamicArray(const ArrayType& _type) const
{ {
solAssert(_type.getLocation() == ArrayType::Location::Storage, ""); solAssert(_type.getLocation() == ArrayType::Location::Storage, "");
solAssert(_type.isDynamicallySized(), ""); 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(); unsigned stackHeightStart = m_context.getStackHeight();
eth::AssemblyItem resizeEnd = m_context.newTag(); eth::AssemblyItem resizeEnd = m_context.newTag();
@ -266,7 +344,7 @@ void ArrayUtils::resizeDynamicArray(const ArrayType& _type) const
// stack: ref new_length data_pos new_size delete_end // stack: ref new_length data_pos new_size delete_end
m_context << eth::Instruction::SWAP2 << eth::Instruction::ADD; m_context << eth::Instruction::SWAP2 << eth::Instruction::ADD;
// stack: ref new_length delete_end delete_start // stack: ref new_length delete_end delete_start
if (_type.isByteArray()) if (_type.isByteArray() || _type.getBaseType()->getStorageBytes() < 32)
clearStorageLoop(IntegerType(256)); clearStorageLoop(IntegerType(256));
else else
clearStorageLoop(*_type.getBaseType()); clearStorageLoop(*_type.getBaseType());
@ -294,7 +372,7 @@ void ArrayUtils::clearStorageLoop(Type const& _type) const
eth::AssemblyItem zeroLoopEnd = m_context.newTag(); eth::AssemblyItem zeroLoopEnd = m_context.newTag();
m_context.appendConditionalJumpTo(zeroLoopEnd); m_context.appendConditionalJumpTo(zeroLoopEnd);
// delete // delete
m_context << u256(0); //@todo m_context << u256(0);
StorageItem(m_context, _type).setToZero(SourceLocation(), false); StorageItem(m_context, _type).setToZero(SourceLocation(), false);
m_context << eth::Instruction::POP; m_context << eth::Instruction::POP;
// increment // increment
@ -313,7 +391,20 @@ void ArrayUtils::convertLengthToSize(ArrayType const& _arrayType, bool _pad) con
if (_arrayType.isByteArray()) if (_arrayType.isByteArray())
m_context << u256(31) << eth::Instruction::ADD m_context << u256(31) << eth::Instruction::ADD
<< u256(32) << eth::Instruction::SWAP1 << eth::Instruction::DIV; << 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; m_context << _arrayType.getBaseType()->getStorageSize() << eth::Instruction::MUL;
} }
else else
@ -349,3 +440,39 @@ void ArrayUtils::retrieveLength(ArrayType const& _arrayType) const
} }
} }
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;
}

6
libsolidity/ArrayUtils.h

@ -72,6 +72,12 @@ public:
void retrieveLength(ArrayType const& _arrayType) const; void retrieveLength(ArrayType const& _arrayType) const;
private: 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; CompilerContext& m_context;
}; };

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save