Browse Source

Merge remote-tracking branch 'upstream/develop' into pr/evmjit-v0.2

Conflicts:
	evmjit/libevmjit/Arith256.cpp
cl-refactor
Paweł Bylica 10 years ago
parent
commit
7b001b42ca
  1. 7
      BuildInfo.h.in
  2. 55
      BuildInfo.sh
  3. 16
      CMakeLists.txt
  4. 52
      CodingStandards.txt
  5. 18
      alethzero/CMakeLists.txt
  6. 59
      alethzero/Context.cpp
  7. 68
      alethzero/Context.h
  8. 380
      alethzero/Debugger.cpp
  9. 103
      alethzero/Debugger.h
  10. 300
      alethzero/Debugger.ui
  11. 528
      alethzero/Main.ui
  12. 781
      alethzero/MainWin.cpp
  13. 188
      alethzero/MainWin.h
  14. 13
      alethzero/NatspecHandler.cpp
  15. 7
      alethzero/NatspecHandler.h
  16. 71
      alethzero/OurWebThreeStubServer.cpp
  17. 4
      alethzero/OurWebThreeStubServer.h
  18. 336
      alethzero/Transact.cpp
  19. 82
      alethzero/Transact.h
  20. 244
      alethzero/Transact.ui
  21. 3
      cmake/EthCompilerSettings.cmake
  22. 18
      cmake/EthDependencies.cmake
  23. 24
      cmake/EthUtils.cmake
  24. 3
      cmake/FindJsoncpp.cmake
  25. 47
      cmake/FindMHD.cmake
  26. 48
      cmake/scripts/buildinfo.cmake
  27. 14
      cmake/scripts/configure.cmake
  28. 45
      cmake/scripts/jsonrpcstub.cmake
  29. 3
      eth/CMakeLists.txt
  30. 104
      eth/main.cpp
  31. 2
      exp/CMakeLists.txt
  32. 8
      extdep/CMakeLists.txt
  33. 9
      libdevcore/CMakeLists.txt
  34. 6
      libdevcore/CommonData.cpp
  35. 9
      libdevcore/CommonData.h
  36. 22
      libdevcore/CommonIO.h
  37. 2
      libdevcore/RLP.cpp
  38. 5
      libdevcore/vector_ref.h
  39. 2
      libdevcrypto/CMakeLists.txt
  40. 6
      libethcore/BlockInfo.cpp
  41. 2
      libethcore/CMakeLists.txt
  42. 10
      libethcore/CommonEth.cpp
  43. 33
      libethcore/CommonEth.h
  44. 13
      libethcore/CommonJS.cpp
  45. 3
      libethcore/CommonJS.h
  46. 1
      libethcore/Exceptions.h
  47. 4
      libethereum/BlockChain.h
  48. 2
      libethereum/CMakeLists.txt
  49. 21
      libethereum/Client.cpp
  50. 4
      libethereum/Client.h
  51. 25
      libethereum/EthereumHost.cpp
  52. 6
      libethereum/Executive.cpp
  53. 5
      libethereum/Executive.h
  54. 2
      libethereum/Interface.h
  55. 9
      libethereum/State.cpp
  56. 2
      libethereum/State.h
  57. 3
      libethereumx/CMakeLists.txt
  58. 2
      libevm/CMakeLists.txt
  59. 2
      libevmcore/CMakeLists.txt
  60. 4
      libjsqrc/setup.js
  61. 2
      liblll/CMakeLists.txt
  62. 2
      libnatspec/CMakeLists.txt
  63. 2
      libnatspec/NatspecExpressionEvaluator.h
  64. 2
      libp2p/CMakeLists.txt
  65. 55
      libp2p/Common.h
  66. 733
      libp2p/Host.cpp
  67. 192
      libp2p/Host.h
  68. 14
      libp2p/HostCapability.cpp
  69. 3
      libp2p/HostCapability.h
  70. 339
      libp2p/NodeTable.cpp
  71. 304
      libp2p/NodeTable.h
  72. 83
      libp2p/Peer.cpp
  73. 97
      libp2p/Peer.h
  74. 218
      libp2p/Session.cpp
  75. 20
      libp2p/Session.h
  76. 35
      libp2p/UDP.cpp
  77. 14
      libp2p/UDP.h
  78. 2
      libp2p/UPnP.cpp
  79. 41
      libqwebthree/CMakeLists.txt
  80. 107
      libqwebthree/QWebThree.cpp
  81. 89
      libqwebthree/QWebThree.h
  82. 2
      libserpent/CMakeLists.txt
  83. 54
      libsolidity/AST.cpp
  84. 62
      libsolidity/AST.h
  85. 2
      libsolidity/ASTForward.h
  86. 17
      libsolidity/ASTJsonConverter.cpp
  87. 2
      libsolidity/ASTJsonConverter.h
  88. 22
      libsolidity/ASTPrinter.cpp
  89. 4
      libsolidity/ASTPrinter.h
  90. 8
      libsolidity/ASTVisitor.h
  91. 28
      libsolidity/AST_accept.h
  92. 4
      libsolidity/CMakeLists.txt
  93. 60
      libsolidity/Compiler.cpp
  94. 6
      libsolidity/Compiler.h
  95. 2
      libsolidity/CompilerContext.cpp
  96. 5
      libsolidity/CompilerStack.cpp
  97. 1
      libsolidity/CompilerStack.h
  98. 332
      libsolidity/CompilerUtils.cpp
  99. 49
      libsolidity/CompilerUtils.h
  100. 13
      libsolidity/DeclarationContainer.cpp

7
BuildInfo.h.in

@ -0,0 +1,7 @@
#pragma once
#define ETH_COMMIT_HASH @ETH_COMMIT_HASH@
#define ETH_CLEAN_REPO @ETH_CLEAN_REPO@
#define ETH_BUILD_TYPE @ETH_BUILD_TYPE@
#define ETH_BUILD_PLATFORM @ETH_BUILD_PLATFORM@

55
BuildInfo.sh

@ -1,55 +0,0 @@
CURRENT_SOURCE_DIR=$1
CURRENT_BINARY_DIR=$2
BUILD_TYPE=$3
BUILD_PLATFORM=$4
echo "Current source dir: $CURRENT_SOURCE_DIR"
echo "Current binary dir: $CURRENT_BINARY_DIR"
echo "Build type: $BUILD_TYPE"
echo "Build platform: $BUILD_PLATFORM"
if [[ -e "$CURRENT_SOURCE_DIR/BuildInfo.h" ]]
then
echo "Using existing BuildInfo.h"
cp $CURRENT_SOURCE_DIR/BuildInfo.h $CURRENT_BINARY_DIR/BuildInfo.h.tmp
else
if [[ -e "$CURRENT_SOURCE_DIR/.git" ]]
then
ETH_COMMIT_HASH=$(git --git-dir=$CURRENT_SOURCE_DIR/.git --work-tree=$CURRENT_SOURCE_DIR rev-parse HEAD)
ETH_LOCAL_CHANGES=$(git --git-dir=$CURRENT_SOURCE_DIR/.git --work-tree=$CURRENT_SOURCE_DIR diff --shortstat)
if [[ -z "$ETH_LOCAL_CHANGES" ]]
then
ETH_CLEAN_REPO=1
else
ETH_CLEAN_REPO=0
fi
echo "Commit hash: ${ETH_COMMIT_HASH} (Clean: ${ETH_CLEAN_REPO} - ${ETH_LOCAL_CHANGES})"
else
echo "Unknown repo."
ETH_COMMIT_HASH=0
ETH_CLEAN_REPO=1
fi
echo "// This file was automatically generated by cmake" > $CURRENT_BINARY_DIR/BuildInfo.h.tmp
echo "" >> $CURRENT_BINARY_DIR/BuildInfo.h.tmp
echo "#pragma once" >> $CURRENT_BINARY_DIR/BuildInfo.h.tmp
echo "" >> $CURRENT_BINARY_DIR/BuildInfo.h.tmp
echo "#define ETH_COMMIT_HASH $ETH_COMMIT_HASH" >> $CURRENT_BINARY_DIR/BuildInfo.h.tmp
echo "#define ETH_CLEAN_REPO $ETH_CLEAN_REPO" >> $CURRENT_BINARY_DIR/BuildInfo.h.tmp
echo "#define ETH_BUILD_TYPE $BUILD_TYPE" >> $CURRENT_BINARY_DIR/BuildInfo.h.tmp
echo "#define ETH_BUILD_PLATFORM $BUILD_PLATFORM" >> $CURRENT_BINARY_DIR/BuildInfo.h.tmp
fi
if [[ -e "$CURRENT_BINARY_DIR/BuildInfo.h" ]]
then
DIFF=$(diff $CURRENT_BINARY_DIR/BuildInfo.h $CURRENT_BINARY_DIR/BuildInfo.h.tmp)
if [[ -z "$DIFF" ]]
then
rm $CURRENT_BINARY_DIR/BuildInfo.h.tmp
else
mv $CURRENT_BINARY_DIR/BuildInfo.h.tmp $CURRENT_BINARY_DIR/BuildInfo.h
fi
else
mv $CURRENT_BINARY_DIR/BuildInfo.h.tmp $CURRENT_BINARY_DIR/BuildInfo.h
fi

16
CMakeLists.txt

@ -86,7 +86,12 @@ function(createBuildInfo)
endif()
# Generate header file containing useful build information
add_custom_target(BuildInfo.h ALL COMMAND bash ${CMAKE_CURRENT_SOURCE_DIR}/BuildInfo.sh ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} ${_cmake_build_type} ${ETH_BUILD_PLATFORM})
add_custom_target(BuildInfo.h ALL
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
COMMAND ${CMAKE_COMMAND} -DETH_SOURCE_DIR="${CMAKE_SOURCE_DIR}" -DETH_DST_DIR="${CMAKE_BINARY_DIR}"
-DETH_BUILD_TYPE="${_cmake_build_type}" -DETH_BUILD_PLATFORM="${ETH_BUILD_PLATFORM}"
-P "${ETH_SCRIPTS_DIR}/buildinfo.cmake"
)
include_directories(${CMAKE_CURRENT_BINARY_DIR})
set(CMAKE_INCLUDE_CURRENT_DIR ON)
@ -104,6 +109,7 @@ cmake_policy(SET CMP0015 NEW)
createDefaultCacheConfig()
configureProject()
message(STATUS "CMAKE_VERSION: ${CMAKE_VERSION}")
message("-- VMTRACE: ${VMTRACE}; PARANOIA: ${PARANOIA}; HEADLESS: ${HEADLESS}; JSONRPC: ${JSONRPC}; EVMJIT: ${EVMJIT}")
@ -135,15 +141,15 @@ endif()
add_subdirectory(libdevcore)
add_subdirectory(libevmcore)
add_subdirectory(liblll)
if (NOT ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC"))
add_subdirectory(libserpent)
add_subdirectory(sc)
endif ()
add_subdirectory(libsolidity)
add_subdirectory(lllc)
add_subdirectory(solc)
if (NOT ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC"))
add_subdirectory(sc)
endif()
if (JSONRPC)
add_subdirectory(libweb3jsonrpc)
@ -161,6 +167,7 @@ add_subdirectory(libethereum)
add_subdirectory(libwebthree)
add_subdirectory(test)
add_subdirectory(eth)
if("x${CMAKE_BUILD_TYPE}" STREQUAL "xDebug")
add_subdirectory(exp)
endif ()
@ -174,7 +181,6 @@ if (NOT HEADLESS)
add_subdirectory(libnatspec)
add_subdirectory(libjsqrc)
add_subdirectory(libqwebthree)
add_subdirectory(alethzero)
add_subdirectory(third)
add_subdirectory(mix)

52
CodingStandards.txt

@ -1,8 +1,11 @@
0. Formatting
GOLDEN RULE: Never *ever* use spaces for formatting.
a. Use tabs for indentation!
- tab stops are every 4 characters.
- One indentation level -> exactly one byte (i.e. a tab character) in the source file.
- Never use spaces to line up sequential lines: If you have run-on lines, indent as you would for a block.
b. Line widths:
- Don't worry about having lines of code > 80-char wide.
- Lines of comments should be formatted according to ease of viewing, but simplicity is to be prefered over beauty.
@ -17,10 +20,22 @@ j. Braces, when used, always have their own lines and are at same indentation le
(WRONG)
if( a==b[ i ] ) { printf ("Hello\n"); }
foo->bar(someLongVariableName,
anotherLongVariableName,
anotherLongVariableName,
anotherLongVariableName,
anotherLongVariableName);
(RIGHT)
if (a == b[i])
printf("Hello\n"); // NOTE spaces used instead of tab here for clarify - first byte should be '\t'.
printf("Hello\n"); // NOTE spaces used instead of tab here for clarity - first byte should be '\t'.
foo->bar(
someLongVariableName,
anotherLongVariableName,
anotherLongVariableName,
anotherLongVariableName,
anotherLongVariableName
);
@ -57,6 +72,8 @@ e. Split complex macro on multiple lines with '\'.
3. Capitalization;
GOLDEN RULE: Preprocessor: ALL_CAPS; C++: camelCase.
a. Use camelCase for splitting words in names, except where obviously extending STL/boost functionality in which case follow those naming conventions.
b. The following entities' first alpha is upper case:
- Type names.
@ -65,6 +82,7 @@ b. The following entities' first alpha is upper case:
- static const variables that form an external API.
c. All preprocessor symbols (macros, macro argments) in full uppercase with underscore word separation.
All other entities' first alpha is lower case.
@ -93,15 +111,21 @@ b. Only one per line.
c. Associate */& with type, not variable (at ends with parser, but more readable, and safe if in conjunction with (b)).
d. Favour declarations close to use; don't habitually declare at top of scope ala C.
e. Always pass non-trivial parameters with a const& suffix.
f. If a func tion returns multiple values, use std::tuple (std::pair acceptable). Prefer not using */& arguments, except where efficiency requires.
f. If a function returns multiple values, use std::tuple (std::pair acceptable). Prefer not using */& arguments, except where efficiency requires.
g. Never use a macro where adequate non-preprocessor C++ can be written.
h. Prefer "using NewType = OldType" to "typedef OldType NewType".
i. Make use of auto whenever type is clear or unimportant:
- Always avoid doubly-stating the type.
- Use to avoid vast and unimportant type declarations.
(WRONG)
const double d = 0;
int i, j;
char *s;
float meanAndSigma(std::vector<float> _v, float* _sigma);
Derived* x(dynamic_cast<Derived*>(base));
for (map<ComplexTypeOne, ComplexTypeTwo>::iterator i = l.begin(); i != l.end(); ++l) {}
(CORRECT)
double const d = 0;
@ -109,12 +133,14 @@ int i;
int j;
char* s;
std::tuple<float, float> meanAndSigma(std::vector<float> const& _v);
auto x = dynamic_cast<Derived*>(base);
for (auto i = x.begin(); i != x.end(); ++i) {}
7. Structs & classes
a. Structs to be used when all members public and no virtual functions.
- In this case, members should be named naturally and not prefixed with 'm_'
b. Classes to be used in all other circumstances.
@ -124,7 +150,7 @@ b. Classes to be used in all other circumstances.
a. One member per line only.
b. Private, non-static, non-const fields prefixed with m_.
c. Avoid public fields, except in structs.
d. Use override, final and const judiciously.
d. Use override, final and const as much as possible.
e. No implementations with the class declaration, except:
- template or force-inline method (though prefer implementation at bottom of header file).
- one-line implementation (in which case include it in same line as declaration).
@ -147,14 +173,18 @@ c. Avoid unpronouncable names;
- If you need to shorten a name favour a pronouncable slice of the original to a scatterred set of consonants.
- e.g. Manager shortens to Man rather than Mgr.
d. Avoid prefixes of initials (e.g. DON'T use IMyInterface, CMyImplementation)
e. A dictionary and thesaurus are your friends.
e. Find short, memorable & (at least semi-) descriptive names for commonly used classes or name-fragments.
- A dictionary and thesaurus are your friends.
- Spell correctly.
- Find short, memorable & (at least semi-) descriptive names for commonly used classes or name-fragments.
- Think carefully about the class's purpose.
- Imagine it as an isolated component to try to decontextualise it when considering its name.
- Don't be trapped into naming it (purely) in terms of its implementation.
10. Type-definitions
a. Prefer using to typedef. e.g. using ints = std::vector<int>; rather than typedef std::vector<int> ints;
a. Prefer 'using' to 'typedef'. e.g. using ints = std::vector<int>; rather than typedef std::vector<int> ints;
b. Generally avoid shortening a standard form that already includes all important information:
- e.g. stick to shared_ptr<X> rather than shortening to ptr<X>.
c. Where there are exceptions to this (due to excessive use and clear meaning), note the change prominently and use it consistently.
@ -162,9 +192,17 @@ c. Where there are exceptions to this (due to excessive use and clear meaning),
d. In general expressions should be roughly as important/semantically meaningful as the space they occupy.
11. Commenting
a. Comments should be doxygen-compilable, using @notation rather than \notation.
b. Document the interface, not the implementation.
- Documentation should be able to remain completely unchanged, even if the method is reimplemented.
- Comment in terms of the method properties and intended alteration to class state (or what aspects of the state it reports).
- Be careful to scrutinise documentation that extends only to intended purpose and usage.
- Reject documentation that is simply an English transaction of the implementation.
12. Include Headers

18
alethzero/CMakeLists.txt

@ -10,10 +10,13 @@ endif()
set(CMAKE_INCLUDE_CURRENT_DIR ON)
aux_source_directory(. SRC_LIST)
include_directories(BEFORE ${JSONCPP_INCLUDE_DIRS})
include_directories(BEFORE ..)
include_directories(${JSON_RPC_CPP_INCLUDE_DIRS})
include_directories(..)
qt5_wrap_ui(ui_Main.h Main.ui)
qt5_wrap_ui(ui_Debugger.h Debugger.ui)
qt5_wrap_ui(ui_Transact.h Transact.ui)
file(GLOB HEADERS "*.h")
@ -26,23 +29,22 @@ endif ()
# eth_add_executable is defined in cmake/EthExecutableHelper.cmake
eth_add_executable(${EXECUTABLE}
ICON alethzero
UI_RESOURCES alethzero.icns Main.ui
UI_RESOURCES alethzero.icns Main.ui Debugger.ui Transact.ui
WIN_RESOURCES alethzero.rc
)
add_dependencies(${EXECUTABLE} BuildInfo.h)
target_link_libraries(${EXECUTABLE} Qt5::Core)
target_link_libraries(${EXECUTABLE} Qt5::Widgets)
target_link_libraries(${EXECUTABLE} Qt5::WebKit)
target_link_libraries(${EXECUTABLE} Qt5::WebKitWidgets)
target_link_libraries(${EXECUTABLE} webthree)
target_link_libraries(${EXECUTABLE} qwebthree)
target_link_libraries(${EXECUTABLE} ethereum)
target_link_libraries(${EXECUTABLE} evm)
target_link_libraries(${EXECUTABLE} ethcore)
target_link_libraries(${EXECUTABLE} devcrypto)
target_link_libraries(${EXECUTABLE} secp256k1)
if (NOT ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC"))
target_link_libraries(${EXECUTABLE} serpent)
endif()
target_link_libraries(${EXECUTABLE} lll)
target_link_libraries(${EXECUTABLE} solidity)
target_link_libraries(${EXECUTABLE} evmcore)
@ -51,6 +53,10 @@ target_link_libraries(${EXECUTABLE} web3jsonrpc)
target_link_libraries(${EXECUTABLE} jsqrc)
target_link_libraries(${EXECUTABLE} natspec)
if (NOT ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC"))
target_link_libraries(${EXECUTABLE} serpent)
endif()
# eth_install_executable is defined in cmake/EthExecutableHelper.cmake
eth_install_executable(${EXECUTABLE})

59
alethzero/Context.cpp

@ -0,0 +1,59 @@
/*
This file is part of cpp-ethereum.
cpp-ethereum is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
cpp-ethereum is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file Debugger.h
* @author Gav Wood <i@gavwood.com>
* @date 2015
*/
#include "Context.h"
#include <QComboBox>
#include <libethcore/CommonEth.h>
using namespace std;
using namespace dev;
using namespace dev::eth;
NatSpecFace::~NatSpecFace()
{
}
Context::~Context()
{
}
void initUnits(QComboBox* _b)
{
for (auto n = (unsigned)units().size(); n-- != 0; )
_b->addItem(QString::fromStdString(units()[n].second), n);
}
vector<KeyPair> keysAsVector(QList<KeyPair> const& keys)
{
auto list = keys.toStdList();
return {begin(list), end(list)};
}
bool sourceIsSolidity(string const& _source)
{
// TODO: Improve this heuristic
return (_source.substr(0, 8) == "contract" || _source.substr(0, 5) == "//sol");
}
bool sourceIsSerpent(string const& _source)
{
// TODO: Improve this heuristic
return (_source.substr(0, 5) == "//ser");
}

68
alethzero/Context.h

@ -0,0 +1,68 @@
/*
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 Debugger.h
* @author Gav Wood <i@gavwood.com>
* @date 2015
*/
#pragma once
#include <string>
#include <vector>
#include <QString>
#include <QList>
#include <libethcore/CommonEth.h>
class QComboBox;
namespace dev { namespace eth { struct StateDiff; } }
#define Small "font-size: small; "
#define Mono "font-family: Ubuntu Mono, Monospace, Lucida Console, Courier New; font-weight: bold; "
#define Div(S) "<div style=\"" S "\">"
#define Span(S) "<span style=\"" S "\">"
void initUnits(QComboBox* _b);
std::vector<dev::KeyPair> keysAsVector(QList<dev::KeyPair> const& _keys);
bool sourceIsSolidity(std::string const& _source);
bool sourceIsSerpent(std::string const& _source);
class NatSpecFace
{
public:
virtual ~NatSpecFace();
virtual void add(dev::h256 const& _contractHash, std::string const& _doc) = 0;
virtual std::string retrieve(dev::h256 const& _contractHash) const = 0;
virtual std::string getUserNotice(std::string const& json, const dev::bytes& _transactionData) = 0;
virtual std::string getUserNotice(dev::h256 const& _contractHash, dev::bytes const& _transactionDacta) = 0;
};
class Context
{
public:
virtual ~Context();
virtual QString pretty(dev::Address _a) const = 0;
virtual QString prettyU256(dev::u256 _n) const = 0;
virtual QString render(dev::Address _a) const = 0;
virtual dev::Address fromString(QString const& _a) const = 0;
virtual std::string renderDiff(dev::eth::StateDiff const& _d) const = 0;
};

380
alethzero/Debugger.cpp

@ -0,0 +1,380 @@
/*
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 Debugger.cpp
* @author Gav Wood <i@gavwood.com>
* @date 2015
*/
#include "Debugger.h"
#include <fstream>
#include <QFileDialog>
#include <libevm/VM.h>
#include <libethereum/ExtVM.h>
#include <libethereum/Executive.h>
#include "ui_Debugger.h"
using namespace std;
using namespace dev;
using namespace dev::eth;
Debugger::Debugger(Context* _c, QWidget* _parent):
QDialog(_parent),
ui(new Ui::Debugger),
m_context(_c)
{
ui->setupUi(this);
}
Debugger::~Debugger()
{
delete ui;
}
void Debugger::init()
{
if (m_session.history.size())
{
alterDebugStateGroup(true);
ui->debugCode->setEnabled(false);
ui->debugTimeline->setMinimum(0);
ui->debugTimeline->setMaximum(m_session.history.size());
ui->debugTimeline->setValue(0);
}
}
void Debugger::populate(dev::eth::Executive& _executive, dev::eth::Transaction const& _transaction)
{
finished();
if (m_session.populate(_executive, _transaction))
init();
update();
}
bool DebugSession::populate(dev::eth::Executive& _executive, dev::eth::Transaction const& _transaction)
{
try {
if (_executive.setup(_transaction))
return false;
}
catch (...)
{
// Invalid transaction
return false;
}
vector<WorldState const*> levels;
bytes lastExtCode;
bytesConstRef lastData;
h256 lastHash;
h256 lastDataHash;
auto onOp = [&](uint64_t steps, Instruction inst, dev::bigint newMemSize, dev::bigint gasCost, VM* voidVM, ExtVMFace const* voidExt)
{
VM& vm = *voidVM;
ExtVM const& ext = *static_cast<ExtVM const*>(voidExt);
if (ext.code != lastExtCode)
{
lastExtCode = ext.code;
lastHash = sha3(lastExtCode);
if (!codes.count(lastHash))
codes[lastHash] = ext.code;
}
if (ext.data != lastData)
{
lastData = ext.data;
lastDataHash = sha3(lastData);
if (!codes.count(lastDataHash))
codes[lastDataHash] = ext.data.toBytes();
}
if (levels.size() < ext.depth)
levels.push_back(&history.back());
else
levels.resize(ext.depth);
history.append(WorldState({steps, ext.myAddress, vm.curPC(), inst, newMemSize, vm.gas(), lastHash, lastDataHash, vm.stack(), vm.memory(), gasCost, ext.state().storage(ext.myAddress), levels}));
};
_executive.go(onOp);
_executive.finalize();
return true;
}
void Debugger::finished()
{
m_session = DebugSession();
ui->callStack->clear();
ui->debugCode->clear();
ui->debugStack->clear();
ui->debugMemory->setHtml("");
ui->debugStorage->setHtml("");
ui->debugStateInfo->setText("");
alterDebugStateGroup(false);
}
void Debugger::update()
{
if (m_session.history.size())
{
WorldState const& nws = m_session.history[min((int)m_session.history.size() - 1, ui->debugTimeline->value())];
WorldState const& ws = ui->callStack->currentRow() > 0 ? *nws.levels[nws.levels.size() - ui->callStack->currentRow()] : nws;
if (ui->debugTimeline->value() >= m_session.history.size())
{
if (ws.gasCost > ws.gas)
ui->debugMemory->setHtml("<h3>OUT-OF-GAS</h3>");
else if (ws.inst == Instruction::RETURN && ws.stack.size() >= 2)
{
unsigned from = (unsigned)ws.stack.back();
unsigned size = (unsigned)ws.stack[ws.stack.size() - 2];
unsigned o = 0;
bytes out(size, 0);
for (; o < size && from + o < ws.memory.size(); ++o)
out[o] = ws.memory[from + o];
ui->debugMemory->setHtml("<h3>RETURN</h3>" + QString::fromStdString(dev::memDump(out, 16, true)));
}
else if (ws.inst == Instruction::STOP)
ui->debugMemory->setHtml("<h3>STOP</h3>");
else if (ws.inst == Instruction::SUICIDE && ws.stack.size() >= 1)
ui->debugMemory->setHtml("<h3>SUICIDE</h3>0x" + QString::fromStdString(toString(right160(ws.stack.back()))));
else
ui->debugMemory->setHtml("<h3>EXCEPTION</h3>");
ostringstream ss;
ss << dec << "EXIT | GAS: " << dec << max<dev::bigint>(0, (dev::bigint)ws.gas - ws.gasCost);
ui->debugStateInfo->setText(QString::fromStdString(ss.str()));
ui->debugStorage->setHtml("");
ui->debugCallData->setHtml("");
m_session.currentData = h256();
ui->callStack->clear();
m_session.currentLevels.clear();
ui->debugCode->clear();
m_session.currentCode = h256();
ui->debugStack->setHtml("");
}
else
{
if (m_session.currentLevels != nws.levels || !ui->callStack->count())
{
m_session.currentLevels = nws.levels;
ui->callStack->clear();
for (unsigned i = 0; i <= nws.levels.size(); ++i)
{
WorldState const& s = i ? *nws.levels[nws.levels.size() - i] : nws;
ostringstream out;
out << s.cur.abridged();
if (i)
out << " " << instructionInfo(s.inst).name << " @0x" << hex << s.curPC;
ui->callStack->addItem(QString::fromStdString(out.str()));
}
}
if (ws.code != m_session.currentCode)
{
m_session.currentCode = ws.code;
bytes const& code = m_session.codes[ws.code];
QListWidget* dc = ui->debugCode;
dc->clear();
m_session.pcWarp.clear();
for (unsigned i = 0; i <= code.size(); ++i)
{
byte b = i < code.size() ? code[i] : 0;
try
{
QString s = QString::fromStdString(instructionInfo((Instruction)b).name);
ostringstream out;
out << hex << setw(4) << setfill('0') << i;
m_session.pcWarp[i] = dc->count();
if (b >= (byte)Instruction::PUSH1 && b <= (byte)Instruction::PUSH32)
{
unsigned bc = b - (byte)Instruction::PUSH1 + 1;
s = "PUSH 0x" + QString::fromStdString(toHex(bytesConstRef(&code[i + 1], bc)));
i += bc;
}
dc->addItem(QString::fromStdString(out.str()) + " " + s);
}
catch (...)
{
cerr << "Unhandled exception!" << endl << boost::current_exception_diagnostic_information();
break; // probably hit data segment
}
}
}
if (ws.callData != m_session.currentData)
{
m_session.currentData = ws.callData;
if (ws.callData)
{
assert(m_session.codes.count(ws.callData));
ui->debugCallData->setHtml(QString::fromStdString(dev::memDump(m_session.codes[ws.callData], 16, true)));
}
else
ui->debugCallData->setHtml("");
}
QString stack;
for (auto i: ws.stack)
stack.prepend("<div>" + m_context->prettyU256(i) + "</div>");
ui->debugStack->setHtml(stack);
ui->debugMemory->setHtml(QString::fromStdString(dev::memDump(ws.memory, 16, true)));
assert(m_session.codes.count(ws.code));
if (m_session.codes[ws.code].size() >= (unsigned)ws.curPC)
{
int l = m_session.pcWarp[(unsigned)ws.curPC];
ui->debugCode->setCurrentRow(max(0, l - 5));
ui->debugCode->setCurrentRow(min(ui->debugCode->count() - 1, l + 5));
ui->debugCode->setCurrentRow(l);
}
else
cwarn << "PC (" << (unsigned)ws.curPC << ") is after code range (" << m_session.codes[ws.code].size() << ")";
ostringstream ss;
ss << dec << "STEP: " << ws.steps << " | PC: 0x" << hex << ws.curPC << " : " << instructionInfo(ws.inst).name << " | ADDMEM: " << dec << ws.newMemSize << " words | COST: " << dec << ws.gasCost << " | GAS: " << dec << ws.gas;
ui->debugStateInfo->setText(QString::fromStdString(ss.str()));
stringstream s;
for (auto const& i: ws.storage)
s << "@" << m_context->prettyU256(i.first).toStdString() << "&nbsp;&nbsp;&nbsp;&nbsp;" << m_context->prettyU256(i.second).toStdString() << "<br/>";
ui->debugStorage->setHtml(QString::fromStdString(s.str()));
}
}
}
void Debugger::on_callStack_currentItemChanged()
{
update();
}
void Debugger::alterDebugStateGroup(bool _enable) const
{
ui->stepOver->setEnabled(_enable);
ui->stepInto->setEnabled(_enable);
ui->stepOut->setEnabled(_enable);
ui->backOver->setEnabled(_enable);
ui->backInto->setEnabled(_enable);
ui->backOut->setEnabled(_enable);
ui->dump->setEnabled(_enable);
ui->dumpStorage->setEnabled(_enable);
ui->dumpPretty->setEnabled(_enable);
}
void Debugger::on_debugTimeline_valueChanged()
{
update();
}
void Debugger::on_stepOver_clicked()
{
if (ui->debugTimeline->value() < m_session.history.size()) {
auto l = m_session.history[ui->debugTimeline->value()].levels.size();
if ((ui->debugTimeline->value() + 1) < m_session.history.size() && m_session.history[ui->debugTimeline->value() + 1].levels.size() > l)
{
on_stepInto_clicked();
if (m_session.history[ui->debugTimeline->value()].levels.size() > l)
on_stepOut_clicked();
}
else
on_stepInto_clicked();
}
}
void Debugger::on_stepInto_clicked()
{
ui->debugTimeline->setValue(ui->debugTimeline->value() + 1);
ui->callStack->setCurrentRow(0);
}
void Debugger::on_stepOut_clicked()
{
if (ui->debugTimeline->value() < m_session.history.size())
{
auto ls = m_session.history[ui->debugTimeline->value()].levels.size();
auto l = ui->debugTimeline->value();
for (; l < m_session.history.size() && m_session.history[l].levels.size() >= ls; ++l) {}
ui->debugTimeline->setValue(l);
ui->callStack->setCurrentRow(0);
}
}
void Debugger::on_backInto_clicked()
{
ui->debugTimeline->setValue(ui->debugTimeline->value() - 1);
ui->callStack->setCurrentRow(0);
}
void Debugger::on_backOver_clicked()
{
auto l = m_session.history[ui->debugTimeline->value()].levels.size();
if (ui->debugTimeline->value() > 0 && m_session.history[ui->debugTimeline->value() - 1].levels.size() > l)
{
on_backInto_clicked();
if (m_session.history[ui->debugTimeline->value()].levels.size() > l)
on_backOut_clicked();
}
else
on_backInto_clicked();
}
void Debugger::on_backOut_clicked()
{
if (ui->debugTimeline->value() > 0 && m_session.history.size() > 0)
{
auto ls = m_session.history[min(ui->debugTimeline->value(), m_session.history.size() - 1)].levels.size();
int l = ui->debugTimeline->value();
for (; l > 0 && m_session.history[l].levels.size() >= ls; --l) {}
ui->debugTimeline->setValue(l);
ui->callStack->setCurrentRow(0);
}
}
void Debugger::on_dump_clicked()
{
QString fn = QFileDialog::getSaveFileName(this, "Select file to output EVM trace");
ofstream f(fn.toStdString());
if (f.is_open())
for (WorldState const& ws: m_session.history)
f << ws.cur << " " << hex << toHex(dev::toCompactBigEndian(ws.curPC, 1)) << " " << hex << toHex(dev::toCompactBigEndian((int)(byte)ws.inst, 1)) << " " << hex << toHex(dev::toCompactBigEndian((uint64_t)ws.gas, 1)) << endl;
}
void Debugger::on_dumpPretty_clicked()
{
QString fn = QFileDialog::getSaveFileName(this, "Select file to output EVM trace");
ofstream f(fn.toStdString());
if (f.is_open())
for (WorldState const& ws: m_session.history)
{
f << endl << " STACK" << endl;
for (auto i: ws.stack)
f << (h256)i << endl;
f << " MEMORY" << endl << dev::memDump(ws.memory);
f << " STORAGE" << endl;
for (auto const& i: ws.storage)
f << showbase << hex << i.first << ": " << i.second << endl;
f << dec << ws.levels.size() << " | " << ws.cur << " | #" << ws.steps << " | " << hex << setw(4) << setfill('0') << ws.curPC << " : " << instructionInfo(ws.inst).name << " | " << dec << ws.gas << " | -" << dec << ws.gasCost << " | " << ws.newMemSize << "x32";
}
}
void Debugger::on_dumpStorage_clicked()
{
QString fn = QFileDialog::getSaveFileName(this, "Select file to output EVM trace");
ofstream f(fn.toStdString());
if (f.is_open())
for (WorldState const& ws: m_session.history)
{
if (ws.inst == Instruction::STOP || ws.inst == Instruction::RETURN || ws.inst == Instruction::SUICIDE)
for (auto i: ws.storage)
f << toHex(dev::toCompactBigEndian(i.first, 1)) << " " << toHex(dev::toCompactBigEndian(i.second, 1)) << endl;
f << ws.cur << " " << hex << toHex(dev::toCompactBigEndian(ws.curPC, 1)) << " " << hex << toHex(dev::toCompactBigEndian((int)(byte)ws.inst, 1)) << " " << hex << toHex(dev::toCompactBigEndian((uint64_t)ws.gas, 1)) << endl;
}
}

103
alethzero/Debugger.h

@ -0,0 +1,103 @@
/*
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 Debugger.h
* @author Gav Wood <i@gavwood.com>
* @date 2015
*/
#pragma once
#include <libdevcore/RLP.h>
#include <libethcore/CommonEth.h>
#include <libethereum/State.h>
#include <libethereum/Executive.h>
#include <QDialog>
#include <QMap>
#include <QList>
#include "Context.h"
namespace Ui { class Debugger; }
struct WorldState
{
uint64_t steps;
dev::Address cur;
dev::u256 curPC;
dev::eth::Instruction inst;
dev::bigint newMemSize;
dev::u256 gas;
dev::h256 code;
dev::h256 callData;
dev::u256s stack;
dev::bytes memory;
dev::bigint gasCost;
std::map<dev::u256, dev::u256> storage;
std::vector<WorldState const*> levels;
};
struct DebugSession
{
DebugSession() {}
bool populate(dev::eth::Executive& _executive, dev::eth::Transaction const& _transaction);
dev::h256 currentCode;
dev::h256 currentData;
std::vector<WorldState const*> currentLevels;
QMap<unsigned, unsigned> pcWarp;
QList<WorldState> history;
std::map<dev::u256, dev::bytes> codes;
};
class Debugger: public QDialog
{
Q_OBJECT
public:
explicit Debugger(Context* _context, QWidget* _parent = 0);
~Debugger();
void populate(dev::eth::Executive& _executive, dev::eth::Transaction const& _transaction);
protected slots:
void on_callStack_currentItemChanged();
void on_debugTimeline_valueChanged();
void on_stepOver_clicked();
void on_stepInto_clicked();
void on_stepOut_clicked();
void on_backOver_clicked();
void on_backInto_clicked();
void on_backOut_clicked();
void on_dump_clicked();
void on_dumpPretty_clicked();
void on_dumpStorage_clicked();
void on_close_clicked() { close(); }
private:
void init();
void update();
void finished();
void alterDebugStateGroup(bool _enable) const;
Ui::Debugger* ui;
DebugSession m_session;
Context* m_context;
};

300
alethzero/Debugger.ui

@ -0,0 +1,300 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Debugger</class>
<widget class="QDialog" name="Debugger">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>989</width>
<height>690</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QFrame" name="frame">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QToolButton" name="stepOver">
<property name="text">
<string>Step Over</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="stepInto">
<property name="text">
<string>Step Into</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="stepOut">
<property name="text">
<string>Step Out</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="backOver">
<property name="text">
<string>Back Over</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="backInto">
<property name="text">
<string>Back Into</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="backOut">
<property name="text">
<string>Back Out</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QSplitter" name="splitter_6">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QSplitter" name="splitter_42">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="QListWidget" name="debugCode">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
</widget>
<widget class="QListWidget" name="callStack">
<property name="font">
<font>
<family>Ubuntu Mono</family>
</font>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
</widget>
</widget>
<widget class="QSplitter" name="splitter_4">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="QTextEdit" name="debugStack">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
<widget class="QTextEdit" name="debugMemory">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
<widget class="QTextEdit" name="debugStorage">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
<widget class="QTextEdit" name="debugCallData">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</widget>
</widget>
</item>
<item>
<widget class="QLabel" name="debugStateInfo">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="debugTimeline">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
<item>
<widget class="QFrame" name="frame_2">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QToolButton" name="dump">
<property name="text">
<string>Dump</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="dumpStorage">
<property name="text">
<string>Dump Storage</string>
</property>
</widget>
</item>
<item>
<widget class="QToolButton" name="dumpPretty">
<property name="text">
<string>Dump Pretty</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>577</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QToolButton" name="close">
<property name="text">
<string>Close</string>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

528
alethzero/Main.ui

@ -117,7 +117,7 @@
<x>0</x>
<y>0</y>
<width>1617</width>
<height>25</height>
<height>24</height>
</rect>
</property>
<widget class="QMenu" name="menu_File">
@ -140,11 +140,12 @@
</widget>
<widget class="QMenu" name="menu_Tools">
<property name="title">
<string>T&amp;ools</string>
<string>&amp;Tools</string>
</property>
<addaction name="mine"/>
<addaction name="separator"/>
<addaction name="create"/>
<addaction name="newTransaction"/>
<addaction name="newAccount"/>
<addaction name="importKey"/>
<addaction name="importKeyFile"/>
<addaction name="exportKey"/>
@ -160,18 +161,14 @@
</widget>
<widget class="QMenu" name="menu_Debug">
<property name="title">
<string>Deb&amp;ug</string>
<string>&amp;Special</string>
</property>
<addaction name="debugDumpState"/>
<addaction name="debugDumpStatePre"/>
<addaction name="separator"/>
<addaction name="paranoia"/>
<addaction name="clearPending"/>
<addaction name="killBlockchain"/>
<addaction name="inject"/>
<addaction name="forceMining"/>
<addaction name="turboMining"/>
<addaction name="enableOptimizer"/>
<addaction name="separator"/>
<addaction name="usePrivate"/>
<addaction name="jitvm"/>
@ -185,42 +182,28 @@
<addaction name="separator"/>
<addaction name="preview"/>
</widget>
<widget class="QMenu" name="menuDebugger">
<property name="title">
<string>D&amp;ebugger</string>
</property>
<widget class="QMenu" name="menu_Dump_Trace">
<property name="title">
<string>&amp;Dump Trace</string>
</property>
<addaction name="dumpTrace"/>
<addaction name="dumpTraceStorage"/>
<addaction name="dumpTracePretty"/>
</widget>
<addaction name="debugCurrent"/>
<addaction name="menu_Dump_Trace"/>
<addaction name="separator"/>
<addaction name="debugStep"/>
<addaction name="debugStepInto"/>
<addaction name="debugStepOut"/>
<addaction name="debugStepBack"/>
<addaction name="debugStepBackInto"/>
<addaction name="debugStepBackOut"/>
</widget>
<widget class="QMenu" name="menuWhispe">
<property name="title">
<string>&amp;Whisper</string>
</property>
<addaction name="newIdentity"/>
</widget>
<widget class="QMenu" name="menuDebug">
<property name="title">
<string>&amp;Debug</string>
</property>
<addaction name="debugCurrent"/>
<addaction name="debugDumpState"/>
<addaction name="debugDumpStatePre"/>
</widget>
<addaction name="menu_File"/>
<addaction name="menu_View"/>
<addaction name="menu_Network"/>
<addaction name="menu_Tools"/>
<addaction name="menuDebugger"/>
<addaction name="menuWhispe"/>
<addaction name="menu_Debug"/>
<addaction name="menuDebug"/>
<addaction name="menu_Help"/>
<addaction name="menu_Debug"/>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<widget class="QDockWidget" name="dockWidget_2">
@ -533,237 +516,11 @@
<bool>false</bool>
</attribute>
<addaction name="go"/>
<addaction name="newTransaction"/>
<addaction name="preview"/>
<addaction name="mine"/>
<addaction name="refresh"/>
</widget>
<widget class="QDockWidget" name="dockWidget_5">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>510</width>
<height>386</height>
</size>
</property>
<property name="features">
<set>QDockWidget::DockWidgetFeatureMask</set>
</property>
<property name="windowTitle">
<string>Transact</string>
</property>
<attribute name="dockWidgetArea">
<number>1</number>
</attribute>
<widget class="QWidget" name="dockWidgetContents_5">
<layout class="QGridLayout" name="gridLayout">
<item row="4" column="0">
<widget class="QLabel" name="label_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&amp;Data</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>data</cstring>
</property>
</widget>
</item>
<item row="5" column="0" colspan="4">
<widget class="QSplitter" name="splitter_5">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="QPlainTextEdit" name="data">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
</widget>
<widget class="QTextEdit" name="code">
<property name="focusPolicy">
<enum>Qt::ClickFocus</enum>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>&amp;Gas</string>
</property>
<property name="buddy">
<cstring>gas</cstring>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label5">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&amp;To</string>
</property>
<property name="buddy">
<cstring>destination</cstring>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QSpinBox" name="gas">
<property name="suffix">
<string> gas</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>430000000</number>
</property>
<property name="value">
<number>10000</number>
</property>
</widget>
</item>
<item row="2" column="3">
<widget class="QComboBox" name="valueUnits"/>
</item>
<item row="3" column="3">
<widget class="QComboBox" name="gasPriceUnits"/>
</item>
<item row="3" column="2">
<widget class="QSpinBox" name="gasPrice">
<property name="prefix">
<string>@ </string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>430000000</number>
</property>
</widget>
</item>
<item row="1" column="1" colspan="3">
<widget class="QLineEdit" name="calculatedName">
<property name="enabled">
<bool>false</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
<property name="placeholderText">
<string/>
</property>
</widget>
</item>
<item row="2" column="1" colspan="2">
<widget class="QSpinBox" name="value">
<property name="suffix">
<string/>
</property>
<property name="maximum">
<number>430000000</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label5_2">
<property name="text">
<string>&amp;Amount</string>
</property>
<property name="buddy">
<cstring>value</cstring>
</property>
</widget>
</item>
<item row="4" column="1" colspan="3">
<widget class="QLabel" name="fee">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="1" colspan="3">
<widget class="QComboBox" name="destination">
<property name="editable">
<bool>true</bool>
</property>
<item>
<property name="text">
<string>(Create Contract)</string>
</property>
</item>
</widget>
</item>
<item row="6" column="3">
<widget class="QPushButton" name="send">
<property name="text">
<string>&amp;Execute</string>
</property>
</widget>
</item>
<item row="6" column="0" colspan="2">
<widget class="QLabel" name="total">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="6" column="2">
<widget class="QPushButton" name="debug">
<property name="text">
<string>De&amp;bug</string>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<widget class="QDockWidget" name="dockWidget_7">
<property name="features">
<set>QDockWidget::DockWidgetFeatureMask</set>
@ -987,171 +744,6 @@
</layout>
</widget>
</widget>
<widget class="QDockWidget" name="dockWidget_9">
<property name="floating">
<bool>false</bool>
</property>
<property name="features">
<set>QDockWidget::DockWidgetFeatureMask</set>
</property>
<property name="windowTitle">
<string>Debugger</string>
</property>
<attribute name="dockWidgetArea">
<number>1</number>
</attribute>
<widget class="QWidget" name="debugPanel">
<property name="enabled">
<bool>false</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="spacing">
<number>6</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QSplitter" name="splitter_6">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<widget class="QSplitter" name="splitter_42">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="QListWidget" name="debugCode">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
</widget>
<widget class="QListWidget" name="callStack">
<property name="font">
<font>
<family>Ubuntu Mono</family>
</font>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
</widget>
</widget>
<widget class="QSplitter" name="splitter_4">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="QTextEdit" name="debugStack">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
<widget class="QTextEdit" name="debugMemory">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
<widget class="QTextEdit" name="debugStorage">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
</property>
</widget>
<widget class="QTextEdit" name="debugCallData">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</widget>
</widget>
</item>
<item>
<widget class="QLabel" name="debugStateInfo">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QSlider" name="debugTimeline">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
</widget>
</item>
</layout>
</widget>
</widget>
<widget class="QDockWidget" name="dockWidget_10">
<property name="features">
<set>QDockWidget::DockWidgetFeatureMask</set>
@ -1566,9 +1158,6 @@ font-size: 14pt</string>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>data</cstring>
</property>
</widget>
</item>
<item row="3" column="1" colspan="4">
@ -1621,9 +1210,6 @@ font-size: 14pt</string>
<property name="text">
<string>TTL</string>
</property>
<property name="buddy">
<cstring>destination</cstring>
</property>
</widget>
</item>
<item row="1" column="0">
@ -1637,9 +1223,6 @@ font-size: 14pt</string>
<property name="text">
<string>From</string>
</property>
<property name="buddy">
<cstring>destination</cstring>
</property>
</widget>
</item>
<item row="0" column="0">
@ -1653,9 +1236,6 @@ font-size: 14pt</string>
<property name="text">
<string>To</string>
</property>
<property name="buddy">
<cstring>destination</cstring>
</property>
</widget>
</item>
<item row="1" column="1" colspan="4">
@ -1686,9 +1266,6 @@ font-size: 14pt</string>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>data</cstring>
</property>
</widget>
</item>
<item row="2" column="1">
@ -1715,9 +1292,6 @@ font-size: 14pt</string>
<property name="text">
<string>Work to Prove</string>
</property>
<property name="buddy">
<cstring>destination</cstring>
</property>
</widget>
</item>
</layout>
@ -1776,7 +1350,7 @@ font-size: 14pt</string>
<bool>true</bool>
</property>
<property name="text">
<string>Use &amp;UPnP</string>
<string>&amp;Use UPnP</string>
</property>
</action>
<action name="connect">
@ -1800,9 +1374,9 @@ font-size: 14pt</string>
<string>&amp;Mine</string>
</property>
</action>
<action name="create">
<action name="newAccount">
<property name="text">
<string>&amp;New Address</string>
<string>&amp;New Address...</string>
</property>
</action>
<action name="about">
@ -1834,7 +1408,7 @@ font-size: 14pt</string>
<bool>true</bool>
</property>
<property name="text">
<string>Mining &amp;Paranoia</string>
<string>&amp;Mining Paranoia</string>
</property>
</action>
<action name="killBlockchain">
@ -1862,7 +1436,7 @@ font-size: 14pt</string>
<bool>true</bool>
</property>
<property name="text">
<string>Show Ancient &amp;Blocks</string>
<string>&amp;Show Ancient Blocks</string>
</property>
</action>
<action name="showAllAccounts">
@ -1870,7 +1444,7 @@ font-size: 14pt</string>
<bool>true</bool>
</property>
<property name="text">
<string>Show Anonymous &amp;Accounts</string>
<string>Show &amp;Anonymous Accounts</string>
</property>
</action>
<action name="usePast">
@ -1970,7 +1544,7 @@ font-size: 14pt</string>
<bool>false</bool>
</property>
<property name="text">
<string>Debu&amp;g Current Transaction</string>
<string>&amp;Debug Current Transaction</string>
</property>
</action>
<action name="debugDumpState">
@ -1978,7 +1552,7 @@ font-size: 14pt</string>
<bool>false</bool>
</property>
<property name="text">
<string>D&amp;ump Current Transaction State (post)</string>
<string>Dump &amp;Current Transaction State (post)</string>
</property>
</action>
<action name="debugDumpStatePre">
@ -1986,7 +1560,7 @@ font-size: 14pt</string>
<bool>false</bool>
</property>
<property name="text">
<string>D&amp;ump Current Transaction State (pre)</string>
<string>Dump Current &amp;Transaction State (pre)</string>
</property>
</action>
<action name="dumpTracePretty">
@ -2007,7 +1581,7 @@ font-size: 14pt</string>
<bool>true</bool>
</property>
<property name="text">
<string>&amp;Use Private Chain...</string>
<string>Use &amp;Private Chain...</string>
</property>
</action>
<action name="enableOptimizer">
@ -2015,7 +1589,7 @@ font-size: 14pt</string>
<bool>true</bool>
</property>
<property name="text">
<string>&amp;Enable LLL &amp;Optimizer</string>
<string>&amp;Enable LLL Optimizer</string>
</property>
</action>
<action name="turboMining">
@ -2023,7 +1597,7 @@ font-size: 14pt</string>
<bool>true</bool>
</property>
<property name="text">
<string>Reserved Debug 1</string>
<string>&amp;Reserved Debug 1</string>
</property>
</action>
<action name="localNetworking">
@ -2031,7 +1605,7 @@ font-size: 14pt</string>
<bool>true</bool>
</property>
<property name="text">
<string>Enable Local Addresses</string>
<string>&amp;Enable Local Addresses</string>
</property>
</action>
<action name="importKeyFile">
@ -2044,7 +1618,7 @@ font-size: 14pt</string>
</action>
<action name="go">
<property name="text">
<string>Go!</string>
<string>&amp;Go!</string>
</property>
</action>
<action name="newIdentity">
@ -2054,7 +1628,7 @@ font-size: 14pt</string>
</action>
<action name="clearPending">
<property name="text">
<string>Clear Pe&amp;nd&amp;ing</string>
<string>&amp;Clear Pending</string>
</property>
</action>
<action name="jitvm">
@ -2073,6 +1647,11 @@ font-size: 14pt</string>
<string>&amp;Kill Account</string>
</property>
</action>
<action name="newTransaction">
<property name="text">
<string>New &amp;Transaction...</string>
</property>
</action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<customwidgets>
@ -2095,27 +1674,30 @@ font-size: 14pt</string>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>destination</tabstop>
<tabstop>calculatedName</tabstop>
<tabstop>value</tabstop>
<tabstop>valueUnits</tabstop>
<tabstop>gas</tabstop>
<tabstop>gasPrice</tabstop>
<tabstop>gasPriceUnits</tabstop>
<tabstop>data</tabstop>
<tabstop>send</tabstop>
<tabstop>idealPeers</tabstop>
<tabstop>port</tabstop>
<tabstop>clientName</tabstop>
<tabstop>shhTo</tabstop>
<tabstop>shhFrom</tabstop>
<tabstop>shhTtl</tabstop>
<tabstop>shhTopic</tabstop>
<tabstop>shhWork</tabstop>
<tabstop>shhData</tabstop>
<tabstop>log</tabstop>
<tabstop>post</tabstop>
<tabstop>verbosity</tabstop>
<tabstop>jsConsole</tabstop>
<tabstop>tabWidget</tabstop>
<tabstop>urlEdit</tabstop>
<tabstop>webView</tabstop>
<tabstop>idealPeers</tabstop>
<tabstop>forceAddress</tabstop>
<tabstop>port</tabstop>
<tabstop>clientName</tabstop>
<tabstop>transactionQueue</tabstop>
<tabstop>pendingInfo</tabstop>
<tabstop>blockChainFilter</tabstop>
<tabstop>nameReg</tabstop>
<tabstop>debugCode</tabstop>
<tabstop>debugStack</tabstop>
<tabstop>debugMemory</tabstop>
<tabstop>debugStorage</tabstop>
<tabstop>nodes</tabstop>
<tabstop>whispers</tabstop>
<tabstop>jsInput</tabstop>
</tabstops>
<resources/>
<connections/>

781
alethzero/MainWin.cpp

File diff suppressed because it is too large

188
alethzero/MainWin.h

@ -34,10 +34,10 @@
#include <libethcore/CommonEth.h>
#include <libethereum/State.h>
#include <libethereum/Executive.h>
#include <libqwebthree/QWebThree.h>
#include <libwebthree/WebThree.h>
#include <libsolidity/CompilerStack.h>
#include "Context.h"
#include "Transact.h"
#include "NatspecHandler.h"
namespace Ui {
@ -49,31 +49,18 @@ class Client;
class State;
}}
namespace jsonrpc {
class HttpServer;
}
class QQuickView;
class OurWebThreeStubServer;
struct WorldState
{
uint64_t steps;
dev::Address cur;
dev::u256 curPC;
dev::eth::Instruction inst;
dev::bigint newMemSize;
dev::u256 gas;
dev::h256 code;
dev::h256 callData;
dev::u256s stack;
dev::bytes memory;
dev::bigint gasCost;
std::map<dev::u256, dev::u256> storage;
std::vector<WorldState const*> levels;
};
using WatchHandler = std::function<void(dev::eth::LocalisedLogEntries const&)>;
QString contentsOfQResource(std::string const& res);
class Main : public QMainWindow
class Main: public QMainWindow, public Context
{
Q_OBJECT
@ -85,13 +72,20 @@ public:
dev::eth::Client* ethereum() const { return m_webThree->ethereum(); }
std::shared_ptr<dev::shh::WhisperHost> whisper() const { return m_webThree->whisper(); }
std::string lookupNatSpec(dev::h256 const& _contractHash) const;
std::string lookupNatSpecUserNotice(dev::h256 const& _contractHash, dev::bytes const& _transactionData);
NatSpecFace* natSpec() { return &m_natSpecDB; }
QList<dev::KeyPair> owned() const { return m_myIdentities + m_myKeys; }
QVariant evalRaw(QString const& _js);
QString pretty(dev::Address _a) const override;
QString prettyU256(dev::u256 _n) const override;
QString render(dev::Address _a) const override;
dev::Address fromString(QString const& _a) const override;
std::string renderDiff(dev::eth::StateDiff const& _d) const override;
QList<dev::KeyPair> owned() const { return m_myIdentities + m_myKeys; }
dev::u256 gasPrice() const { return 10 * dev::eth::szabo; }
public slots:
void load(QString _file);
void note(QString _entry);
@ -99,75 +93,80 @@ public slots:
void warn(QString _entry);
QString contents(QString _file);
int authenticate(QString _title, QString _text);
void onKeysChanged();
private slots:
void eval(QString const& _js);
// Application
void on_about_triggered();
void on_quit_triggered() { close(); }
// Network
void on_go_triggered();
void on_net_triggered();
void on_connect_triggered();
void on_idealPeers_valueChanged();
// Mining
void on_mine_triggered();
void on_send_clicked();
void on_create_triggered();
// View
void on_refresh_triggered();
void on_showAll_triggered() { refreshBlockChain(); }
void on_showAllAccounts_triggered() { refreshAccounts(); }
void on_preview_triggered();
// Account management
void on_newAccount_triggered();
void on_killAccount_triggered();
void on_net_triggered();
void on_verbosity_valueChanged();
void on_ourAccounts_doubleClicked();
void on_importKey_triggered();
void on_importKeyFile_triggered();
void on_exportKey_triggered();
// Tools
void on_newTransaction_triggered();
void on_loadJS_triggered();
// Stuff concerning the blocks/transactions/accounts panels
void ourAccountsRowsMoved();
void on_ourAccounts_doubleClicked();
void on_accounts_doubleClicked();
void on_destination_currentTextChanged();
void on_data_textChanged();
void on_idealPeers_valueChanged();
void on_value_valueChanged() { updateFee(); }
void on_gas_valueChanged() { updateFee(); }
void on_valueUnits_currentIndexChanged() { updateFee(); }
void on_gasPriceUnits_currentIndexChanged() { updateFee(); }
void on_gasPrice_valueChanged() { updateFee(); }
void on_log_doubleClicked();
void on_blocks_currentItemChanged();
void on_contracts_doubleClicked();
void on_contracts_currentItemChanged();
void on_transactionQueue_currentItemChanged();
void on_about_triggered();
void on_paranoia_triggered();
void on_nameReg_textChanged();
void on_preview_triggered();
void on_quit_triggered() { close(); }
void on_blockChainFilter_textChanged();
void on_blocks_currentItemChanged();
// Logging
void on_log_doubleClicked();
void on_verbosity_valueChanged();
// Misc
void on_urlEdit_returnPressed();
void on_debugStep_triggered();
void on_debugStepBack_triggered();
void on_debug_clicked();
void on_debugTimeline_valueChanged();
void on_jsInput_returnPressed();
void on_nameReg_textChanged();
// Special (debug) stuff
void on_paranoia_triggered();
void on_killBlockchain_triggered();
void on_clearPending_triggered();
void on_importKey_triggered();
void on_exportKey_triggered();
void on_inject_triggered();
void on_showAll_triggered() { refreshBlockChain(); }
void on_showAllAccounts_triggered() { refreshAccounts(); }
void on_loadJS_triggered();
void on_blockChainFilter_textChanged();
void on_forceMining_triggered();
void on_dumpTrace_triggered();
void on_dumpTraceStorage_triggered();
void on_dumpTracePretty_triggered();
void on_debugStepInto_triggered();
void on_debugStepOut_triggered();
void on_debugStepBackOut_triggered();
void on_debugStepBackInto_triggered();
void on_callStack_currentItemChanged();
void on_usePrivate_triggered();
void on_turboMining_triggered();
void on_jitvm_triggered();
// Debugger
void on_debugCurrent_triggered();
void on_debugDumpState_triggered(int _add = 1);
void on_debugDumpStatePre_triggered();
void on_refresh_triggered();
void on_usePrivate_triggered();
void on_enableOptimizer_triggered();
void on_turboMining_triggered();
void on_go_triggered();
void on_importKeyFile_triggered();
void on_post_clicked();
// Whisper
void on_newIdentity_triggered();
void on_jitvm_triggered();
void on_post_clicked();
void refreshWhisper();
void refreshBlockChain();
@ -179,33 +178,14 @@ signals:
private:
dev::p2p::NetworkPreferences netPrefs() const;
QString pretty(dev::Address _a) const;
QString prettyU256(dev::u256 _n) const;
QString lookup(QString const& _n) const;
dev::Address getNameReg() const;
dev::Address getCurrencies() const;
void populateDebugger(dev::bytesConstRef r);
void initDebugger();
void updateDebugger();
void debugFinished();
QString render(dev::Address _a) const;
dev::Address fromString(QString const& _a) const;
std::string renderDiff(dev::eth::StateDiff const& _d) const;
void alterDebugStateGroup(bool _enable) const;
void updateFee();
void readSettings(bool _skipGeometry = false);
void writeSettings();
bool isCreation() const;
dev::u256 fee() const;
dev::u256 total() const;
dev::u256 value() const;
dev::u256 gasPrice() const;
unsigned installWatch(dev::eth::LogFilter const& _tf, WatchHandler const& _f);
unsigned installWatch(dev::h256 _tf, WatchHandler const& _f);
void uninstallWatch(unsigned _w);
@ -232,16 +212,9 @@ private:
void refreshAll();
void refreshPending();
void refreshAccounts();
void refreshDestination();
void refreshBlockCount();
void refreshBalances();
/// Attempts to infer that @c _source contains Solidity code
bool sourceIsSolidity(std::string const& _source);
/// @eturns all method hashes of a Solidity contract in a string
std::string const getFunctionHashes(dev::solidity::CompilerStack const &_compiler,
std::string const& _contractName = "");
std::unique_ptr<Ui::Main> ui;
std::unique_ptr<dev::WebThreeDirect> m_webThree;
@ -251,27 +224,13 @@ private:
unsigned m_currenciesFilter = (unsigned)-1;
unsigned m_balancesFilter = (unsigned)-1;
QByteArray m_peers;
QByteArray m_networkConfig;
QStringList m_servers;
QList<dev::KeyPair> m_myKeys;
QList<dev::KeyPair> m_myIdentities;
QString m_privateChain;
dev::bytes m_data;
dev::Address m_nameReg;
unsigned m_backupGas;
dev::eth::State m_executiveState;
std::unique_ptr<dev::eth::Executive> m_currentExecution;
dev::h256 m_lastCode;
dev::h256 m_lastData;
std::vector<WorldState const*> m_lastLevels;
QMap<unsigned, unsigned> m_pcWarp;
QList<WorldState> m_history;
std::map<dev::u256, dev::bytes> m_codes; // and pcWarps
bool m_enableOptimizer = true;
QNetworkAccessManager m_webCtrl;
QList<QPair<QString, QString>> m_consoleHistory;
@ -279,10 +238,11 @@ private:
QString m_logHistory;
bool m_logChanged = true;
std::unique_ptr<QWebThreeConnector> m_qwebConnector;
std::unique_ptr<jsonrpc::HttpServer> m_httpConnector;
std::unique_ptr<OurWebThreeStubServer> m_server;
QWebThree* m_qweb = nullptr;
static QString fromRaw(dev::h256 _n, unsigned* _inc = nullptr);
NatspecHandler m_natspecDB;
NatspecHandler m_natSpecDB;
Transact m_transact;
};

13
alethzero/NatspecHandler.cpp

@ -50,16 +50,15 @@ NatspecHandler::~NatspecHandler()
void NatspecHandler::add(dev::h256 const& _contractHash, string const& _doc)
{
bytes k = _contractHash.asBytes();
string v = _doc;
m_db->Put(m_writeOptions, ldb::Slice((char const*)k.data(), k.size()), ldb::Slice((char const*)v.data(), v.size()));
m_db->Put(m_writeOptions, _contractHash.ref(), _doc);
cdebug << "Registering NatSpec: " << _contractHash.abridged() << _doc;
}
string NatspecHandler::retrieve(dev::h256 const& _contractHash) const
{
bytes k = _contractHash.asBytes();
string ret;
m_db->Get(m_readOptions, ldb::Slice((char const*)k.data(), k.size()), &ret);
m_db->Get(m_readOptions, _contractHash.ref(), &ret);
cdebug << "Looking up NatSpec: " << _contractHash.abridged() << ret;
return ret;
}
@ -69,8 +68,8 @@ string NatspecHandler::getUserNotice(string const& json, dev::bytes const& _tran
Json::Value userNotice;
string retStr;
m_reader.parse(json, natspec);
bytes transactionFunctionPart(_transactionData.begin(), _transactionData.begin() + 4);
FixedHash<4> transactionFunctionHash(transactionFunctionPart);
FixedHash<4> transactionFunctionHash((bytesConstRef(&_transactionData).cropped(0, 4).toBytes()));
Json::Value methods = natspec["methods"];
for (Json::ValueIterator it = methods.begin(); it != methods.end(); ++it)

7
alethzero/NatspecHandler.h

@ -26,12 +26,13 @@
#pragma warning(disable: 4100 4267)
#include <leveldb/db.h>
#pragma warning(pop)
#include <jsoncpp/json/json.h>
#include <json/json.h>
#include <libdevcore/FixedHash.h>
#include "Context.h"
namespace ldb = leveldb;
class NatspecHandler
class NatspecHandler: public NatSpecFace
{
public:
NatspecHandler();
@ -40,7 +41,7 @@ class NatspecHandler
/// Stores locally in a levelDB a key value pair of contract code hash to natspec documentation
void add(dev::h256 const& _contractHash, std::string const& _doc);
/// Retrieves the natspec documentation as a string given a contract code hash
std::string retrieve(dev::h256 const& _contractHash) const;
std::string retrieve(dev::h256 const& _contractHash) const override;
/// Given a json natspec string and the transaction data return the user notice
std::string getUserNotice(std::string const& json, const dev::bytes& _transactionData);

71
alethzero/OurWebThreeStubServer.cpp

@ -46,48 +46,71 @@ string OurWebThreeStubServer::shh_newIdentity()
bool OurWebThreeStubServer::showAuthenticationPopup(string const& _title, string const& _text) const
{
QMessageBox userInput;
userInput.setText(QString::fromStdString(_title));
userInput.setInformativeText(QString::fromStdString(_text + "\n Do you wish to allow this?"));
userInput.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel);
userInput.button(QMessageBox::Ok)->setText("Allow");
userInput.button(QMessageBox::Cancel)->setText("Reject");
userInput.setDefaultButton(QMessageBox::Cancel);
return userInput.exec() == QMessageBox::Ok;
int button;
QMetaObject::invokeMethod(m_main, "authenticate", Qt::BlockingQueuedConnection, Q_RETURN_ARG(int, button), Q_ARG(QString, QString::fromStdString(_title)), Q_ARG(QString, QString::fromStdString(_text)));
return button == QMessageBox::Ok;
}
void OurWebThreeStubServer::showBasicValueTransferNotice(u256 _value) const
bool OurWebThreeStubServer::showCreationNotice(TransactionSkeleton const& _t) const
{
QMessageBox notice;
notice.setText("Basic Value Transfer Transaction");
notice.setInformativeText(QString::fromStdString("Value is " + toString(_value)));
notice.setStandardButtons(QMessageBox::Ok);
notice.exec();
return showAuthenticationPopup("Contract Creation Transaction", "ÐApp is attemping to create a contract; to be endowed with " + formatBalance(_t.value) + ", with additional network fees of up to " + formatBalance(_t.gas * _t.gasPrice) + ".\n\nMaximum total cost is <b>" + formatBalance(_t.value + _t.gas * _t.gasPrice) + "</b>.");
}
bool OurWebThreeStubServer::showSendNotice(TransactionSkeleton const& _t) const
{
return showAuthenticationPopup("Fund Transfer Transaction", "ÐApp is attempting to send " + formatBalance(_t.value) + " to a recipient " + m_main->pretty(_t.to).toStdString() + ", with additional network fees of up to " + formatBalance(_t.gas * _t.gasPrice) + ".\n\nMaximum total cost is <b>" + formatBalance(_t.value + _t.gas * _t.gasPrice) + "</b>.");
}
bool OurWebThreeStubServer::showUnknownCallNotice(TransactionSkeleton const& _t) const
{
return showAuthenticationPopup("DANGEROUS! Unknown Contract Transaction!",
"ÐApp is attempting to call into an unknown contract at address " +
m_main->pretty(_t.to).toStdString() +
".\n\nCall involves sending " +
formatBalance(_t.value) + " to the recipient, with additional network fees of up to " +
formatBalance(_t.gas * _t.gasPrice) +
"However, this also does other stuff which we don't understand, and does so in your name.\n\n" +
"WARNING: This is probably going to cost you at least " +
formatBalance(_t.value + _t.gas * _t.gasPrice) +
", however this doesn't include any side-effects, which could be of far greater importance.\n\n" +
"REJECT UNLESS YOU REALLY KNOW WHAT YOU ARE DOING!");
}
bool OurWebThreeStubServer::authenticate(TransactionSkeleton const& _t)
{
if (_t.creation)
{
// recipient has no code - nothing special about this transaction, show basic value transfer info
return showCreationNotice(_t);
}
h256 contractCodeHash = m_web3->ethereum()->postState().codeHash(_t.to);
if (contractCodeHash == EmptySHA3)
// contract creation
return true;
if (false) //TODO: When is is just a value transfer?
{
// recipient has no code - nothing special about this transaction, show basic value transfer info
showBasicValueTransferNotice(_t.value);
return true;
return showSendNotice(_t);
}
string userNotice = m_main->lookupNatSpecUserNotice(contractCodeHash, _t.data);
string userNotice = m_main->natSpec()->getUserNotice(contractCodeHash, _t.data);
if (userNotice.empty())
return showAuthenticationPopup("Unverified Pending Transaction",
"An undocumented transaction is about to be executed.");
return showUnknownCallNotice(_t);
NatspecExpressionEvaluator evaluator;
userNotice = evaluator.evalExpression(QString::fromStdString(userNotice)).toStdString();
// otherwise it's a transaction to a contract for which we have the natspec
return showAuthenticationPopup("Pending Transaction", userNotice);
return showAuthenticationPopup("Contract Transaction",
"ÐApp attempting to conduct contract interaction with " +
m_main->pretty(_t.to).toStdString() +
": <b>" + userNotice + "</b>.\n\n" +
(_t.value > 0 ?
"In addition, ÐApp is attempting to send " +
formatBalance(_t.value) + " to said recipient, with additional network fees of up to " +
formatBalance(_t.gas * _t.gasPrice) + " = <b>" +
formatBalance(_t.value + _t.gas * _t.gasPrice) + "</b>."
:
"Additional network fees are at most" +
formatBalance(_t.gas * _t.gasPrice) + ".")
);
}

4
alethzero/OurWebThreeStubServer.h

@ -42,7 +42,9 @@ signals:
private:
bool showAuthenticationPopup(std::string const& _title, std::string const& _text) const;
void showBasicValueTransferNotice(dev::u256 _value) const;
bool showCreationNotice(dev::eth::TransactionSkeleton const& _t) const;
bool showSendNotice(dev::eth::TransactionSkeleton const& _t) const;
bool showUnknownCallNotice(dev::eth::TransactionSkeleton const& _t) const;
dev::WebThreeDirect* m_web3;
Main* m_main;

336
alethzero/Transact.cpp

@ -0,0 +1,336 @@
/*
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 Transact.cpp
* @author Gav Wood <i@gavwood.com>
* @date 2015
*/
#include "Transact.h"
#include <fstream>
#include <QFileDialog>
#include <QMessageBox>
#include <liblll/Compiler.h>
#include <liblll/CodeFragment.h>
#include <libsolidity/CompilerStack.h>
#include <libsolidity/Scanner.h>
#include <libsolidity/AST.h>
#include <libsolidity/SourceReferenceFormatter.h>
#include <libnatspec/NatspecExpressionEvaluator.h>
#include <libethereum/Client.h>
#include <libethereum/Utility.h>
#ifndef _MSC_VER
#include <libserpent/funcs.h>
#include <libserpent/util.h>
#endif
#include "Debugger.h"
#include "ui_Transact.h"
using namespace std;
using namespace dev;
using namespace dev::eth;
Transact::Transact(Context* _c, QWidget* _parent):
QDialog(_parent),
ui(new Ui::Transact),
m_context(_c)
{
ui->setupUi(this);
initUnits(ui->gasPriceUnits);
initUnits(ui->valueUnits);
ui->valueUnits->setCurrentIndex(6);
ui->gasPriceUnits->setCurrentIndex(4);
ui->gasPrice->setValue(10);
on_destination_currentTextChanged();
}
Transact::~Transact()
{
delete ui;
}
void Transact::setEnvironment(QList<dev::KeyPair> _myKeys, dev::eth::Client* _eth, NatSpecFace* _natSpecDB)
{
m_myKeys = _myKeys;
m_ethereum = _eth;
m_natSpecDB = _natSpecDB;
}
bool Transact::isCreation() const
{
return ui->destination->currentText().isEmpty() || ui->destination->currentText() == "(Create Contract)";
}
u256 Transact::fee() const
{
return ui->gas->value() * gasPrice();
}
u256 Transact::value() const
{
if (ui->valueUnits->currentIndex() == -1)
return 0;
return ui->value->value() * units()[units().size() - 1 - ui->valueUnits->currentIndex()].first;
}
u256 Transact::gasPrice() const
{
if (ui->gasPriceUnits->currentIndex() == -1)
return 0;
return ui->gasPrice->value() * units()[units().size() - 1 - ui->gasPriceUnits->currentIndex()].first;
}
u256 Transact::total() const
{
return value() + fee();
}
void Transact::updateDestination()
{
cwatch << "updateDestination()";
QString s;
for (auto i: ethereum()->addresses())
if ((s = m_context->pretty(i)).size())
// A namereg address
if (ui->destination->findText(s, Qt::MatchExactly | Qt::MatchCaseSensitive) == -1)
ui->destination->addItem(s);
for (int i = 0; i < ui->destination->count(); ++i)
if (ui->destination->itemText(i) != "(Create Contract)" && !m_context->fromString(ui->destination->itemText(i)))
ui->destination->removeItem(i--);
}
void Transact::updateFee()
{
ui->fee->setText(QString("(gas sub-total: %1)").arg(formatBalance(fee()).c_str()));
auto totalReq = total();
ui->total->setText(QString("Total: %1").arg(formatBalance(totalReq).c_str()));
bool ok = false;
for (auto i: m_myKeys)
if (ethereum()->balanceAt(i.address()) >= totalReq)
{
ok = true;
break;
}
ui->send->setEnabled(ok);
QPalette p = ui->total->palette();
p.setColor(QPalette::WindowText, QColor(ok ? 0x00 : 0x80, 0x00, 0x00));
ui->total->setPalette(p);
}
string Transact::getFunctionHashes(dev::solidity::CompilerStack const& _compiler, string const& _contractName)
{
string ret = "";
auto const& contract = _compiler.getContractDefinition(_contractName);
auto interfaceFunctions = contract.getInterfaceFunctions();
for (auto const& it: interfaceFunctions)
{
ret += it.first.abridged();
ret += " :";
ret += it.second->getDeclaration().getName() + "\n";
}
return ret;
}
void Transact::on_destination_currentTextChanged()
{
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 src = ui->data->toPlainText().toStdString();
vector<string> errors;
QString lll;
QString solidity;
if (src.find_first_not_of("1234567890abcdefABCDEF") == string::npos && src.size() % 2 == 0)
{
m_data = fromHex(src);
}
else if (sourceIsSolidity(src))
{
dev::solidity::CompilerStack compiler;
try
{
// compiler.addSources(dev::solidity::StandardSources);
m_data = compiler.compile(src, ui->optimize->isChecked());
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>" + QString::fromStdString(compiler.getSolidityInterface()).toHtmlEscaped() + "</pre>";
solidity += "<pre>" + QString::fromStdString(getFunctionHashes(compiler)).toHtmlEscaped() + "</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>";
}
}
#ifndef _MSC_VER
else if (sourceIsSerpent(src))
{
try
{
m_data = dev::asBytes(::compile(src));
for (auto& i: errors)
i = "(LLL " + i + ")";
}
catch (string err)
{
errors.push_back("Serpent " + err);
}
}
#endif
else
{
m_data = compileLLL(src, ui->optimize->isChecked(), &errors);
if (errors.empty())
{
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;
if (errors.size())
{
errs = "<h4>Errors</h4>";
for (auto const& i: errors)
errs.append("<div style=\"border-left: 6px solid #c00; margin-top: 2px\">" + QString::fromStdString(i).toHtmlEscaped() + "</div>");
}
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>");
ui->gas->setMinimum((qint64)Interface::txGas(m_data, 0));
if (!ui->gas->isEnabled())
ui->gas->setValue(m_backupGas);
ui->gas->setEnabled(true);
}
else
{
m_data = parseData(ui->data->toPlainText().toStdString());
auto to = m_context->fromString(ui->destination->currentText());
QString natspec;
if (ethereum()->codeAt(to, 0).size())
{
string userNotice = m_natSpecDB->getUserNotice(ethereum()->postState().codeHash(to), m_data);
if (userNotice.empty())
natspec = "Destination contract unknown.";
else
{
NatspecExpressionEvaluator evaluator;
natspec = evaluator.evalExpression(QString::fromStdString(userNotice));
}
ui->gas->setMinimum((qint64)Interface::txGas(m_data, 1));
if (!ui->gas->isEnabled())
ui->gas->setValue(m_backupGas);
ui->gas->setEnabled(true);
}
else
{
natspec += "Destination not a contract.";
if (ui->gas->isEnabled())
m_backupGas = ui->gas->value();
ui->gas->setValue((qint64)Interface::txGas(m_data));
ui->gas->setEnabled(false);
}
ui->code->setHtml("<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>");
}
updateFee();
}
void Transact::on_send_clicked()
{
u256 totalReq = value() + fee();
for (auto const& i: m_myKeys)
if (ethereum()->balanceAt(i.address(), 0) >= totalReq)
{
Secret s = i.secret();
if (isCreation())
{
// If execution is a contract creation, add Natspec to
// a local Natspec LEVELDB
ethereum()->transact(s, value(), m_data, ui->gas->value(), gasPrice());
string src = ui->data->toPlainText().toStdString();
if (sourceIsSolidity(src))
try
{
dev::solidity::CompilerStack compiler;
m_data = compiler.compile(src, ui->optimize->isChecked());
for (string const& s: compiler.getContractNames())
{
h256 contractHash = compiler.getContractCodeHash(s);
m_natSpecDB->add(contractHash, compiler.getMetadata(s, dev::solidity::DocumentationType::NatspecUser));
}
}
catch (...)
{
}
close();
return;
}
else
ethereum()->transact(s, value(), m_context->fromString(ui->destination->currentText()), m_data, ui->gas->value(), gasPrice());
return;
}
QMessageBox::critical(this, "Transaction Failed", "Couldn't make transaction: no single account contains at least the required amount.");
}
void Transact::on_debug_clicked()
{
try
{
u256 totalReq = value() + fee();
for (auto i: m_myKeys)
if (ethereum()->balanceAt(i.address()) >= totalReq)
{
State st(ethereum()->postState());
Secret s = i.secret();
Transaction t = isCreation() ?
Transaction(value(), gasPrice(), ui->gas->value(), m_data, st.transactionsFrom(dev::toAddress(s)), s) :
Transaction(value(), gasPrice(), ui->gas->value(), m_context->fromString(ui->destination->currentText()), m_data, st.transactionsFrom(dev::toAddress(s)), s);
Debugger dw(m_context, this);
Executive e(st, ethereum()->blockChain(), 0);
dw.populate(e, t);
dw.exec();
return;
}
QMessageBox::critical(this, "Transaction Failed", "Couldn't make transaction: no single account contains at least the required amount.");
}
catch (dev::Exception const& _e)
{
QMessageBox::critical(this, "Transaction Failed", "Couldn't make transaction. Low-level error: " + QString::fromStdString(diagnostic_information(_e)));
// this output is aimed at developers, reconsider using _e.what for more user friendly output.
}
}

82
alethzero/Transact.h

@ -0,0 +1,82 @@
/*
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 Transact.h
* @author Gav Wood <i@gavwood.com>
* @date 2015
*/
#pragma once
#include <libdevcore/RLP.h>
#include <libethcore/CommonEth.h>
#include <libethereum/Transaction.h>
#include <QDialog>
#include <QMap>
#include <QList>
#include "Context.h"
namespace Ui { class Transact; }
namespace dev { namespace eth { class Client; } }
namespace dev { namespace solidity { class CompilerStack; } }
class Transact: public QDialog
{
Q_OBJECT
public:
explicit Transact(Context* _context, QWidget* _parent = 0);
~Transact();
void setEnvironment(QList<dev::KeyPair> _myKeys, dev::eth::Client* _eth, NatSpecFace* _natSpecDB);
private slots:
void on_destination_currentTextChanged();
void on_value_valueChanged() { updateFee(); }
void on_gas_valueChanged() { updateFee(); }
void on_valueUnits_currentIndexChanged() { updateFee(); }
void on_gasPriceUnits_currentIndexChanged() { updateFee(); }
void on_gasPrice_valueChanged() { updateFee(); }
void on_data_textChanged() { rejigData(); }
void on_optimize_clicked() { rejigData(); }
void on_send_clicked();
void on_debug_clicked();
void on_cancel_clicked() { close(); }
private:
dev::eth::Client* ethereum() { return m_ethereum; }
void rejigData();
void updateDestination();
void updateFee();
bool isCreation() const;
dev::u256 fee() const;
dev::u256 total() const;
dev::u256 value() const;
dev::u256 gasPrice() const;
std::string getFunctionHashes(dev::solidity::CompilerStack const& _compiler, std::string const& _contractName = std::string());
Ui::Transact* ui;
unsigned m_backupGas;
dev::bytes m_data;
QList<dev::KeyPair> m_myKeys;
dev::eth::Client* m_ethereum;
Context* m_context;
NatSpecFace* m_natSpecDB;
};

244
alethzero/Transact.ui

@ -0,0 +1,244 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Transact</class>
<widget class="QDialog" name="Transact">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>543</width>
<height>695</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="2" column="1" colspan="2">
<widget class="QSpinBox" name="value">
<property name="suffix">
<string/>
</property>
<property name="maximum">
<number>430000000</number>
</property>
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label5_2">
<property name="text">
<string>&amp;Amount</string>
</property>
<property name="buddy">
<cstring>value</cstring>
</property>
</widget>
</item>
<item row="1" column="1" colspan="3">
<widget class="QLineEdit" name="calculatedName">
<property name="enabled">
<bool>false</bool>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
<property name="placeholderText">
<string/>
</property>
</widget>
</item>
<item row="5" column="0" colspan="4">
<widget class="QSplitter" name="splitter_5">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="QPlainTextEdit" name="data">
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
</widget>
<widget class="QTextEdit" name="code">
<property name="focusPolicy">
<enum>Qt::ClickFocus</enum>
</property>
<property name="frameShape">
<enum>QFrame::NoFrame</enum>
</property>
<property name="lineWidth">
<number>0</number>
</property>
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</widget>
</item>
<item row="3" column="3">
<widget class="QComboBox" name="gasPriceUnits"/>
</item>
<item row="4" column="1" colspan="2">
<widget class="QLabel" name="fee">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label5">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>&amp;To</string>
</property>
<property name="buddy">
<cstring>destination</cstring>
</property>
</widget>
</item>
<item row="2" column="3">
<widget class="QComboBox" name="valueUnits"/>
</item>
<item row="7" column="2">
<widget class="QPushButton" name="debug">
<property name="text">
<string>&amp;Debug</string>
</property>
</widget>
</item>
<item row="7" column="3">
<widget class="QPushButton" name="send">
<property name="text">
<string>&amp;Execute</string>
</property>
<property name="default">
<bool>false</bool>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>&amp;Gas</string>
</property>
<property name="buddy">
<cstring>gas</cstring>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QSpinBox" name="gas">
<property name="suffix">
<string> gas</string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>430000000</number>
</property>
<property name="value">
<number>10000</number>
</property>
</widget>
</item>
<item row="3" column="2">
<widget class="QSpinBox" name="gasPrice">
<property name="prefix">
<string>@ </string>
</property>
<property name="minimum">
<number>1</number>
</property>
<property name="maximum">
<number>430000000</number>
</property>
</widget>
</item>
<item row="4" column="3">
<widget class="QCheckBox" name="optimize">
<property name="text">
<string>&amp;Optimise</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Maximum">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>D&amp;ata</string>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>data</cstring>
</property>
</widget>
</item>
<item row="0" column="1" colspan="3">
<widget class="QComboBox" name="destination">
<property name="editable">
<bool>true</bool>
</property>
<item>
<property name="text">
<string>(Create Contract)</string>
</property>
</item>
</widget>
</item>
<item row="6" column="0" colspan="4">
<widget class="QLabel" name="total">
<property name="sizePolicy">
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string/>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QPushButton" name="cancel">
<property name="text">
<string>&amp;Cancel</string>
</property>
<property name="shortcut">
<string>Esc</string>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections/>
</ui>

3
cmake/EthCompilerSettings.cmake

@ -27,13 +27,14 @@ elseif ("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")
elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
# enable parallel compilation
# specify Exception Handling Model in msvc
# disable unknown pragma warning (4068)
# disable unsafe function warning (4996)
# disable decorated name length exceeded, name was truncated (4503)
# disable warning C4535: calling _set_se_translator() requires /EHa (for boost tests)
# declare Windows XP requirement
add_compile_options(/EHsc /wd4068 /wd4996 /wd4503 -D_WIN32_WINNT=0x0501)
add_compile_options(/MP /EHsc /wd4068 /wd4996 /wd4503 -D_WIN32_WINNT=0x0501)
# disable empty object file warning
set(CMAKE_STATIC_LINKER_FLAGS "${CMAKE_STATIC_LINKER_FLAGS} /ignore:4221")
# warning LNK4075: ignoring '/EDITANDCONTINUE' due to '/SAFESEH' specification

18
cmake/EthDependencies.cmake

@ -7,6 +7,14 @@ string(TOLOWER ${CMAKE_SYSTEM_NAME} _system_name)
set (ETH_DEPENDENCY_INSTALL_DIR "${CMAKE_CURRENT_SOURCE_DIR}/extdep/install/${_system_name}")
set (CMAKE_PREFIX_PATH ${ETH_DEPENDENCY_INSTALL_DIR})
# setup directory for cmake generated files and include it globally
# it's not used yet, but if we have more generated files, consider moving them to ETH_GENERATED_DIR
set(ETH_GENERATED_DIR "${PROJECT_BINARY_DIR}/gen")
include_directories(${ETH_GENERATED_DIR})
# custom cmake scripts
set(ETH_SCRIPTS_DIR ${CMAKE_SOURCE_DIR}/cmake/scripts)
# Qt5 requires opengl
# TODO use proper version of windows SDK (32 vs 64)
# TODO make it possible to use older versions of windows SDK (7.0+ should also work)
@ -51,6 +59,10 @@ if (JSONRPC)
message (" - json-rpc-cpp lib : ${JSON_RPC_CPP_LIBRARIES}")
add_definitions(-DETH_JSONRPC)
find_package(MHD)
message(" - microhttpd header: ${MHD_INCLUDE_DIRS}")
message(" - microhttpd lib : ${MHD_LIBRARIES}")
endif() #JSONRPC
# TODO readline package does not yet check for correct version number
@ -87,6 +99,10 @@ find_package (CURL)
message(" - curl header: ${CURL_INCLUDE_DIRS}")
message(" - curl lib : ${CURL_LIBRARIES}")
# find location of jsonrpcstub
find_program(ETH_JSON_RPC_STUB jsonrpcstub)
message(" - jsonrpcstub location : ${ETH_JSON_RPC_STUB}")
# do not compile GUI
if (NOT HEADLESS)
@ -122,8 +138,6 @@ if (NOT HEADLESS)
string(REGEX REPLACE "npm" "" ETH_NPM_DIRECTORY ${ETH_NPM})
message(" - npm location : ${ETH_NPM}")
find_program(ETH_JSON_RPC_STUB jsonrpcstub)
endif() #HEADLESS
# use multithreaded boost libraries, with -mt suffix

24
cmake/EthUtils.cmake

@ -0,0 +1,24 @@
#
# renames the file if it is different from its destination
include(CMakeParseArguments)
#
macro(replace_if_different SOURCE DST)
set(extra_macro_args ${ARGN})
set(options CREATE)
set(one_value_args)
set(multi_value_args)
cmake_parse_arguments(REPLACE_IF_DIFFERENT "${options}" "${one_value_args}" "${multi_value_args}" "${extra_macro_args}")
if (REPLACE_IF_DIFFERENT_CREATE AND (NOT (EXISTS "${DST}")))
file(WRITE "${DST}" "")
endif()
execute_process(COMMAND ${CMAKE_COMMAND} -E compare_files "${SOURCE}" "${DST}" RESULT_VARIABLE DIFFERENT)
if (DIFFERENT)
execute_process(COMMAND ${CMAKE_COMMAND} -E rename "${SOURCE}" "${DST}")
else()
execute_process(COMMAND ${CMAKE_COMMAND} -E remove "${SOURCE}")
endif()
endmacro()

3
cmake/FindJsoncpp.cmake

@ -12,7 +12,8 @@
# only look in default directories
find_path(
JSONCPP_INCLUDE_DIR
NAMES jsoncpp/json/json.h
NAMES json/json.h
PATH_SUFFIXES jsoncpp
DOC "jsoncpp include dir"
)

47
cmake/FindMHD.cmake

@ -0,0 +1,47 @@
# Find microhttpd
#
# Find the microhttpd includes and library
#
# if you need to add a custom library search path, do it via via CMAKE_PREFIX_PATH
#
# This module defines
# MHD_INCLUDE_DIRS, where to find header, etc.
# MHD_LIBRARIES, the libraries needed to use jsoncpp.
# MHD_FOUND, If false, do not try to use jsoncpp.
find_path(
MHD_INCLUDE_DIR
NAMES microhttpd.h
DOC "microhttpd include dir"
)
find_library(
MHD_LIBRARY
NAMES microhttpd microhttpd-10 libmicrohttpd libmicrohttpd-dll
DOC "microhttpd library"
)
set(MHD_INCLUDE_DIRS ${MHD_INCLUDE_DIR})
set(MHD_LIBRARIES ${MHD_LIBRARY})
# debug library on windows
# same naming convention as in QT (appending debug library with d)
# boost is using the same "hack" as us with "optimized" and "debug"
# official MHD project actually uses _d suffix
if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
find_library(
MHD_LIBRARY_DEBUG
NAMES microhttpd_d microhttpd-10_d libmicrohttpd_d libmicrohttpd-dll_d
DOC "mhd debug library"
)
set(MHD_LIBRARIES optimized ${MHD_LIBRARIES} debug ${MHD_LIBRARY_DEBUG})
endif()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(mhd DEFAULT_MSG
MHD_INCLUDE_DIR MHD_LIBRARY)
mark_as_advanced(MHD_INCLUDE_DIR MHD_LIBRARY)

48
cmake/scripts/buildinfo.cmake

@ -0,0 +1,48 @@
# generates BuildInfo.h
#
# this module expects
# ETH_SOURCE_DIR - main CMAKE_SOURCE_DIR
# ETH_DST_DIR - main CMAKE_BINARY_DIR
# ETH_BUILD_TYPE
# ETH_BUILD_PLATFORM
#
# example usage:
# cmake -DETH_SOURCE_DIR=. -DETH_DST_DIR=build -DETH_BUILD_TYPE=Debug -DETH_BUILD_PLATFORM=mac -P scripts/buildinfo.cmake
if (NOT ETH_BUILD_TYPE)
set(ETH_BUILD_TYPE "unknown")
endif()
if (NOT ETH_BUILD_PLATFORM)
set(ETH_BUILD_PLATFORM "unknown")
endif()
execute_process(
COMMAND git --git-dir=${ETH_SOURCE_DIR}/.git --work-tree=${ETH_SOURCE_DIR} rev-parse HEAD
OUTPUT_VARIABLE ETH_COMMIT_HASH OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET
)
if (NOT ETH_COMMIT_HASH)
set(ETH_COMMIT_HASH 0)
endif()
execute_process(
COMMAND git --git-dir=${ETH_SOURCE_DIR}/.git --work-tree=${ETH_SOURCE_DIR} diff --shortstat
OUTPUT_VARIABLE ETH_LOCAL_CHANGES OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET
)
if (ETH_LOCAL_CHANGES)
set(ETH_CLEAN_REPO 0)
else()
set(ETH_CLEAN_REPO 1)
endif()
set(INFILE "${ETH_SOURCE_DIR}/BuildInfo.h.in")
set(TMPFILE "${ETH_DST_DIR}/BuildInfo.h.tmp")
set(OUTFILE "${ETH_DST_DIR}/BuildInfo.h")
configure_file("${INFILE}" "${TMPFILE}")
include("${ETH_SOURCE_DIR}/cmake/EthUtils.cmake")
replace_if_different("${TMPFILE}" "${OUTFILE}" CREATE)

14
cmake/scripts/configure.cmake

@ -0,0 +1,14 @@
# adds possibility to run configure_file as buildstep
# reference:
# http://www.cmake.org/pipermail/cmake/2012-May/050227.html
#
# This module expects
# INFILE
# OUTFILE
# other custom vars
#
# example usage:
# cmake -DINFILE=blah.in -DOUTFILE=blah.out -Dvar1=value1 -Dvar2=value2 -P scripts/configure.cmake
configure_file(${INFILE} ${OUTFILE})

45
cmake/scripts/jsonrpcstub.cmake

@ -0,0 +1,45 @@
# generates JSONRPC Stub Server && Client
#
# this script expects
# ETH_SOURCE_DIR - main CMAKE_SOURCE_DIR
# ETH_SPEC_PATH
# ETH_SERVER_DIR
# ETH_CLIENT_DIR
# ETH_SERVER_NAME
# ETH_CLIENT_NAME
# ETH_JSON_RPC_STUB
#
# example usage:
# cmake -DETH_SPEC_PATH=spec.json -DETH_SERVER_DIR=libweb3jsonrpc -DETH_CLIENT_DIR=test
# -DETH_SERVER_NAME=AbstractWebThreeStubServer -DETH_CLIENT_NAME=WebThreeStubClient -DETH_JSON_RPC_STUB=/usr/local/bin/jsonrpcstub
# by default jsonrpcstub produces files in lowercase, we want to stick to this
string(TOLOWER ${ETH_SERVER_NAME} ETH_SERVER_NAME_LOWER)
string(TOLOWER ${ETH_CLIENT_NAME} ETH_CLIENT_NAME_LOWER)
# setup names
set(SERVER_TMPFILE "${ETH_SERVER_DIR}/${ETH_SERVER_NAME_LOWER}.h.tmp")
set(SERVER_OUTFILE "${ETH_SERVER_DIR}/${ETH_SERVER_NAME_LOWER}.h")
set(CLIENT_TMPFILE "${ETH_CLIENT_DIR}/${ETH_CLIENT_NAME_LOWER}.h.tmp")
set(CLIENT_OUTFILE "${ETH_CLIENT_DIR}/${ETH_CLIENT_NAME_LOWER}.h")
# create tmp files
execute_process(
COMMAND ${ETH_JSON_RPC_STUB} ${ETH_SPEC_PATH}
--cpp-server=${ETH_SERVER_NAME} --cpp-server-file=${SERVER_TMPFILE}
--cpp-client=${ETH_CLIENT_NAME} --cpp-client-file=${CLIENT_TMPFILE}
OUTPUT_VARIABLE ERR ERROR_QUIET
)
# don't throw fatal error on jsonrpcstub error, someone might have old version of jsonrpcstub,
# he does not need to upgrade it if he is not working on JSON RPC
# show him warning instead
if (ERR)
message(WARNING "Your version of jsonrcpstub tool is not supported. Please upgrade it.")
message(WARNING "${ERR}")
else()
include("${ETH_SOURCE_DIR}/cmake/EthUtils.cmake")
replace_if_different("${SERVER_TMPFILE}" "${SERVER_OUTFILE}")
replace_if_different("${CLIENT_TMPFILE}" "${CLIENT_OUTFILE}")
endif()

3
eth/CMakeLists.txt

@ -3,9 +3,9 @@ set(CMAKE_AUTOMOC OFF)
aux_source_directory(. SRC_LIST)
include_directories(BEFORE ..)
include_directories(${Boost_INCLUDE_DIRS})
include_directories(${JSON_RPC_CPP_INCLUDE_DIRS})
include_directories(..)
set(EXECUTABLE eth)
@ -16,7 +16,6 @@ add_executable(${EXECUTABLE} ${SRC_LIST} ${HEADERS})
add_dependencies(${EXECUTABLE} BuildInfo.h)
target_link_libraries(${EXECUTABLE} ${Boost_REGEX_LIBRARIES})
target_link_libraries(${EXECUTABLE} ${Boost_DATE_TIME_LIBRARIES})
if (READLINE_FOUND)
target_link_libraries(${EXECUTABLE} ${READLINE_LIBRARIES})

104
eth/main.cpp

@ -119,6 +119,7 @@ void help()
<< " -p,--port <port> Connect to remote port (default: 30303)." << 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
<< " -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
<< " -x,--peers <number> Attempt to connect to given number of peers (Default: 5)." << endl
@ -195,6 +196,7 @@ int main(int argc, char** argv)
unsigned mining = ~(unsigned)0;
NodeMode mode = NodeMode::Full;
unsigned peers = 5;
int miners = -1;
bool interactive = false;
#if ETH_JSONRPC
int jsonrpc = -1;
@ -258,7 +260,23 @@ int main(int argc, char** argv)
else if ((arg == "-c" || arg == "--client-name") && i + 1 < argc)
clientName = argv[++i];
else if ((arg == "-a" || arg == "--address" || arg == "--coinbase-address") && i + 1 < argc)
coinbase = h160(fromHex(argv[++i]));
{
try
{
coinbase = h160(fromHex(argv[++i], ThrowType::Throw));
}
catch (BadHexCharacter& _e)
{
cwarn << "invalid hex character, coinbase rejected";
cwarn << boost::diagnostic_information(_e);
break;
}
catch (...)
{
cwarn << "coinbase rejected";
break;
}
}
else if ((arg == "-s" || arg == "--secret") && i + 1 < argc)
us = KeyPair(h256(fromHex(argv[++i])));
else if ((arg == "-d" || arg == "--path" || arg == "--db-path") && i + 1 < argc)
@ -295,6 +313,8 @@ int main(int argc, char** argv)
g_logVerbosity = atoi(argv[++i]);
else if ((arg == "-x" || arg == "--peers") && i + 1 < argc)
peers = atoi(argv[++i]);
else if ((arg == "-t" || arg == "--miners") && i + 1 < argc)
miners = atoi(argv[++i]);
else if ((arg == "-o" || arg == "--mode") && i + 1 < argc)
{
string m = argv[++i];
@ -332,12 +352,15 @@ int main(int argc, char** argv)
VMFactory::setKind(jit ? VMKind::JIT : VMKind::Interpreter);
NetworkPreferences netPrefs(listenPort, publicIP, upnp, useLocal);
auto nodesState = contents((dbPath.size() ? dbPath : getDataDir()) + "/network.rlp");
dev::WebThreeDirect web3(
"Ethereum(++)/" + clientName + "v" + dev::Version + "/" DEV_QUOTED(ETH_BUILD_TYPE) "/" DEV_QUOTED(ETH_BUILD_PLATFORM) + (jit ? "/JIT" : ""),
dbPath,
false,
mode == NodeMode::Full ? set<string>{"eth", "shh"} : set<string>(),
netPrefs
netPrefs,
&nodesState,
miners
);
web3.setIdealPeerCount(peers);
eth::Client* c = mode == NodeMode::Full ? web3.ethereum() : nullptr;
@ -348,9 +371,6 @@ int main(int argc, char** argv)
c->setAddress(coinbase);
}
auto nodesState = contents((dbPath.size() ? dbPath : getDataDir()) + "/nodeState.rlp");
web3.restoreNodes(&nodesState);
cout << "Address: " << endl << toHex(us.address().asArray()) << endl;
web3.startNetwork();
@ -532,9 +552,21 @@ int main(int argc, char** argv)
}
else
{
Secret secret = h256(fromHex(sechex));
Address dest = h160(fromHex(hexAddr));
c->transact(secret, amount, dest, data, gas, gasPrice);
try
{
Secret secret = h256(fromHex(sechex));
Address dest = h160(fromHex(hexAddr));
c->transact(secret, amount, dest, data, gas, gasPrice);
}
catch (BadHexCharacter& _e)
{
cwarn << "invalid hex character, transaction rejected";
cwarn << boost::diagnostic_information(_e);
}
catch (...)
{
cwarn << "transaction rejected";
}
}
}
else
@ -583,8 +615,20 @@ int main(int argc, char** argv)
auto blockData = bc.block(h);
BlockInfo info(blockData);
u256 minGas = (u256)Client::txGas(bytes(), 0);
Address dest = h160(fromHex(hexAddr));
c->transact(us.secret(), amount, dest, bytes(), minGas);
try
{
Address dest = h160(fromHex(hexAddr, ThrowType::Throw));
c->transact(us.secret(), amount, dest, bytes(), minGas);
}
catch (BadHexCharacter& _e)
{
cwarn << "invalid hex character, transaction rejected";
cwarn << boost::diagnostic_information(_e);
}
catch (...)
{
cwarn << "transaction rejected";
}
}
}
else
@ -615,14 +659,30 @@ int main(int argc, char** argv)
{
cnote << "Assembled:";
stringstream ssc;
init = fromHex(sinit);
try
{
init = fromHex(sinit, ThrowType::Throw);
}
catch (BadHexCharacter& _e)
{
cwarn << "invalid hex character, code rejected";
cwarn << boost::diagnostic_information(_e);
init = bytes();
}
catch (...)
{
cwarn << "code rejected";
init = bytes();
}
ssc.str(string());
ssc << disassemble(init);
cnote << "Init:";
cnote << ssc.str();
}
u256 minGas = (u256)Client::txGas(init, 0);
if (endowment < 0)
if (!init.size())
cwarn << "Contract creation aborted, no init code.";
else if (endowment < 0)
cwarn << "Invalid endowment";
else if (gas < minGas)
cwarn << "Minimum gas amount is" << minGas;
@ -759,8 +819,22 @@ int main(int argc, char** argv)
if (hexAddr.length() != 40)
cwarn << "Invalid address length: " << hexAddr.length();
else
coinbase = h160(fromHex(hexAddr));
}
{
try
{
coinbase = h160(fromHex(hexAddr, ThrowType::Throw));
}
catch (BadHexCharacter& _e)
{
cwarn << "invalid hex character, coinbase rejected";
cwarn << boost::diagnostic_information(_e);
}
catch (...)
{
cwarn << "coinbase rejected";
}
}
}
else
cwarn << "Require parameter: setAddress HEXADDRESS";
}
@ -824,7 +898,7 @@ int main(int argc, char** argv)
while (!g_exit)
this_thread::sleep_for(chrono::milliseconds(1000));
writeFile((dbPath.size() ? dbPath : getDataDir()) + "/nodeState.rlp", web3.saveNodes());
writeFile((dbPath.size() ? dbPath : getDataDir()) + "/network.rlp", web3.saveNetwork());
return 0;
}

2
exp/CMakeLists.txt

@ -3,8 +3,8 @@ set(CMAKE_AUTOMOC OFF)
aux_source_directory(. SRC_LIST)
include_directories(BEFORE ..)
include_directories(${LEVELDB_INCLUDE_DIRS})
include_directories(..)
set(EXECUTABLE exp)

8
extdep/CMakeLists.txt

@ -35,14 +35,18 @@ if (ETH_COMPILE)
include(compile/boost.cmake)
else()
eth_download(jsoncpp)
eth_download(json-rpc-cpp OSX_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/scripts/json-rpc-cpp_osx.sh)
eth_download(microhttpd)
eth_download(json-rpc-cpp
VERSION 4.2
OSX_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/scripts/json-rpc-cpp_osx.sh
)
if (APPLE)
eth_download(snappy OSX_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/scripts/snappy_osx.sh)
endif()
eth_download(leveldb OSX_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/scripts/leveldb_osx.sh)
eth_download(qt)
eth_download(qt VERSION 5.4)
eth_download(cryptopp)
eth_download(boost)
eth_download(curl)

9
libdevcore/CMakeLists.txt

@ -12,8 +12,8 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DSTATICLIB")
aux_source_directory(. SRC_LIST)
include_directories(BEFORE ..)
include_directories(${Boost_INCLUDE_DIRS})
include_directories(..)
set(EXECUTABLE devcore)
@ -26,9 +26,14 @@ else()
endif()
target_link_libraries(${EXECUTABLE} ${Boost_THREAD_LIBRARIES})
#target_link_libraries(${EXECUTABLE} ${Boost_DATE_TIME_LIBRARIES})
target_link_libraries(${EXECUTABLE} ${Boost_SYSTEM_LIBRARIES})
# transitive dependencies for windows executables
if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC")
target_link_libraries(${EXECUTABLE} ${Boost_CHRONO_LIBRARIES})
target_link_libraries(${EXECUTABLE} ${Boost_DATE_TIME_LIBRARIES})
endif()
if (APPLE)
find_package(Threads REQUIRED)
target_link_libraries(${EXECUTABLE} ${CMAKE_THREAD_LIBS_INIT})

6
libdevcore/CommonData.cpp

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

9
libdevcore/CommonData.h

@ -35,6 +35,12 @@ namespace dev
// String conversion functions, mainly to/from hex/nibble/byte representations.
enum class ThrowType
{
NoThrow = 0,
Throw = 1,
};
/// Convert a series of bytes to the corresponding string of hex duplets.
/// @param _w specifies the width of each of the elements. Defaults to two - enough to represent a byte.
/// @example toHex("A\x69") == "4169"
@ -53,7 +59,8 @@ int fromHex(char _i);
/// Converts a (printable) ASCII hex string into the corresponding byte stream.
/// @example fromHex("41626261") == asBytes("Abba")
bytes fromHex(std::string const& _s);
/// If _throw = ThrowType::NoThrow, it replaces bad hex characters with 0's, otherwise it will throw an exception.
bytes fromHex(std::string const& _s, ThrowType _throw = ThrowType::NoThrow);
#if 0
std::string toBase58(bytesConstRef _data);

22
libdevcore/CommonIO.h

@ -57,7 +57,7 @@ template <class S, class T> struct StreamOut { static S& bypass(S& _out, T const
template <class S> struct StreamOut<S, uint8_t> { static S& bypass(S& _out, uint8_t const& _t) { _out << (int)_t; return _out; } };
template <class T> inline std::ostream& operator<<(std::ostream& _out, std::vector<T> const& _e);
template <class T, unsigned Z> inline std::ostream& operator<<(std::ostream& _out, std::array<T, Z> const& _e);
template <class T, std::size_t Z> inline std::ostream& operator<<(std::ostream& _out, std::array<T, Z> const& _e);
template <class T, class U> inline std::ostream& operator<<(std::ostream& _out, std::pair<T, U> const& _e);
template <class T> inline std::ostream& operator<<(std::ostream& _out, std::list<T> const& _e);
template <class T1, class T2, class T3> inline std::ostream& operator<<(std::ostream& _out, std::tuple<T1, T2, T3> const& _e);
@ -84,7 +84,7 @@ inline S& streamout(S& _out, std::vector<T> const& _e)
template <class T> inline std::ostream& operator<<(std::ostream& _out, std::vector<T> const& _e) { streamout(_out, _e); return _out; }
template <class S, class T, unsigned Z>
template <class S, class T, std::size_t Z>
inline S& streamout(S& _out, std::array<T, Z> const& _e)
{
_out << "[";
@ -98,23 +98,7 @@ inline S& streamout(S& _out, std::array<T, Z> const& _e)
_out << "]";
return _out;
}
template <class T, unsigned Z> inline std::ostream& operator<<(std::ostream& _out, std::array<T, Z> const& _e) { streamout(_out, _e); return _out; }
template <class S, class T, unsigned long Z>
inline S& streamout(S& _out, std::array<T, Z> const& _e)
{
_out << "[";
if (!_e.empty())
{
StreamOut<S, T>::bypass(_out, _e.front());
auto i = _e.begin();
for (++i; i != _e.end(); ++i)
StreamOut<S, T>::bypass(_out << ",", *i);
}
_out << "]";
return _out;
}
template <class T, unsigned long Z> inline std::ostream& operator<<(std::ostream& _out, std::array<T, Z> const& _e) { streamout(_out, _e); return _out; }
template <class T, std::size_t Z> inline std::ostream& operator<<(std::ostream& _out, std::array<T, Z> const& _e) { streamout(_out, _e); return _out; }
template <class S, class T>
inline S& streamout(S& _out, std::list<T> const& _e)

2
libdevcore/RLP.cpp

@ -177,7 +177,7 @@ void RLPStream::noteAppended(unsigned _itemCount)
while (m_listStack.size())
{
if (m_listStack.back().first < _itemCount)
BOOST_THROW_EXCEPTION(RLPException() << errinfo_comment("itemCount too large"));
BOOST_THROW_EXCEPTION(RLPException() << errinfo_comment("itemCount too large") << RequirementError((bigint)m_listStack.back().first, (bigint)_itemCount));
m_listStack.back().first -= _itemCount;
if (m_listStack.back().first)
break;

5
libdevcore/vector_ref.h

@ -1,7 +1,8 @@
#pragma once
#include <type_traits>
#include <cstring>
#include <cassert>
#include <type_traits>
#include <vector>
#include <string>
@ -18,7 +19,7 @@ public:
vector_ref(): m_data(nullptr), m_count(0) {}
vector_ref(_T* _data, size_t _count): m_data(_data), m_count(_count) {}
vector_ref(std::string* _data): m_data((_T*)_data->data()), m_count(_data->size() / sizeof(_T)) {}
vector_ref(typename std::conditional<std::is_const<_T>::value, std::string const*, std::string*>::type _data): m_data((_T*)_data->data()), m_count(_data->size() / sizeof(_T)) {}
vector_ref(typename std::conditional<std::is_const<_T>::value, std::vector<typename std::remove_const<_T>::type> const*, std::vector<_T>*>::type _data): m_data(_data->data()), m_count(_data->size()) {}
vector_ref(typename std::conditional<std::is_const<_T>::value, std::string const&, std::string&>::type _data): m_data((_T*)_data.data()), m_count(_data.size() / sizeof(_T)) {}
#ifdef STORAGE_LEVELDB_INCLUDE_DB_H_

2
libdevcrypto/CMakeLists.txt

@ -9,10 +9,10 @@ set(CMAKE_AUTOMOC OFF)
aux_source_directory(. SRC_LIST)
include_directories(BEFORE ..)
include_directories(${Boost_INCLUDE_DIRS})
include_directories(${CRYPTOPP_INCLUDE_DIRS})
include_directories(${LEVELDB_INCLUDE_DIRS})
include_directories(..)
set(EXECUTABLE devcrypto)

6
libethcore/BlockInfo.cpp

@ -134,7 +134,7 @@ void BlockInfo::populate(bytesConstRef _block, bool _checkNonce)
RLP header = root[0];
if (!header.isList())
BOOST_THROW_EXCEPTION(InvalidBlockFormat(0,header.data()));
BOOST_THROW_EXCEPTION(InvalidBlockFormat(0,header.data()) << errinfo_comment("block header needs to be a list"));
populateFromHeader(header, _checkNonce);
if (!root[1].isList())
@ -157,8 +157,8 @@ void BlockInfo::verifyInternals(bytesConstRef _block) const
{
bytes k = rlp(i);
t.insert(&k, tr.data());
u256 gp = tr[1].toInt<u256>();
mgp = min(mgp, gp);
u256 gasprice = tr[1].toInt<u256>();
mgp = min(mgp, gasprice); // the minimum gas price is not used for anything //TODO delete?
++i;
}
if (transactionsRoot != t.root())

2
libethcore/CMakeLists.txt

@ -9,8 +9,8 @@ set(CMAKE_AUTOMOC OFF)
aux_source_directory(. SRC_LIST)
include_directories(BEFORE ..)
include_directories(${Boost_INCLUDE_DIRS})
include_directories(..)
set(EXECUTABLE ethcore)

10
libethcore/CommonEth.cpp

@ -35,16 +35,6 @@ namespace eth
const unsigned c_protocolVersion = 53;
const unsigned c_databaseVersion = 5;
template <size_t n> constexpr u256 exp10()
{
return exp10<n - 1>() * u256(10);
}
template <> constexpr u256 exp10<0>()
{
return u256(1);
}
vector<pair<u256, string>> const& units()
{
static const vector<pair<u256, string>> s_units =

33
libethcore/CommonEth.h

@ -47,26 +47,21 @@ std::vector<std::pair<u256, std::string>> const& units();
/// The log bloom's size (512 bit).
using LogBloom = h512;
template <size_t n> inline u256 exp10()
{
return exp10<n - 1>() * u256(10);
}
template <> inline u256 exp10<0>()
{
return u256(1);
}
// The various denominations; here for ease of use where needed within code.
/*static const u256 Uether = ((((u256(1000000000) * 1000000000) * 1000000000) * 1000000000) * 1000000000) * 1000000000;
static const u256 Vether = ((((u256(1000000000) * 1000000000) * 1000000000) * 1000000000) * 1000000000) * 1000000;
static const u256 Dether = ((((u256(1000000000) * 1000000000) * 1000000000) * 1000000000) * 1000000000) * 1000;
static const u256 Nether = (((u256(1000000000) * 1000000000) * 1000000000) * 1000000000) * 1000000000;
static const u256 Yether = (((u256(1000000000) * 1000000000) * 1000000000) * 1000000000) * 1000000;
static const u256 Zether = (((u256(1000000000) * 1000000000) * 1000000000) * 1000000000) * 1000;
static const u256 Eether = ((u256(1000000000) * 1000000000) * 1000000000) * 1000000000;
static const u256 Pether = ((u256(1000000000) * 1000000000) * 1000000000) * 1000000;
static const u256 Tether = ((u256(1000000000) * 1000000000) * 1000000000) * 1000;
static const u256 Gether = (u256(1000000000) * 1000000000) * 1000000000;
static const u256 Mether = (u256(1000000000) * 1000000000) * 1000000;
static const u256 grand = (u256(1000000000) * 1000000000) * 1000;*/
static const u256 ether = u256(1000000000) * 1000000000;
static const u256 finney = u256(1000000000) * 1000000;
static const u256 szabo = u256(1000000000) * 1000;
/*static const u256 Gwei = u256(1000000000);
static const u256 Mwei = u256(1000000);
static const u256 Kwei = u256(1000);*/
static const u256 wei = u256(1);
static const u256 ether = exp10<18>();
static const u256 finney = exp10<15>();
static const u256 szabo = exp10<12>();
static const u256 wei = exp10<0>();
}
}

13
libethcore/CommonJS.cpp

@ -97,7 +97,7 @@ std::string fromRaw(h256 _n, unsigned* _inc)
return "";
}
std::string prettyU256(u256 _n)
std::string prettyU256(u256 _n, bool _abridged)
{
unsigned inc = 0;
std::string raw;
@ -110,11 +110,16 @@ std::string prettyU256(u256 _n)
{
Address a = right160(_n);
std::string n = a.abridged();
std::string n;
if (_abridged)
n = a.abridged();
else
n = toHex(a.ref());
if (n.empty())
s << "0x" << a;
s << "0";
else
s << n << "(0x" << a.abridged() << ")";
s << _n << "(0x" << n << ")";
}
else if ((raw = fromRaw((h256)_n, &inc)).size())
return "\"" + raw + "\"" + (inc ? " + " + std::to_string(inc) : "");

3
libethcore/CommonJS.h

@ -59,7 +59,7 @@ bytes unpadded(bytes _s);
/// Remove all 0 byte on the head of @a _s.
bytes unpadLeft(bytes _s);
/// Convert u256 into user-readable string. Returns int/hex value of 64 bits int, hex of 160 bits FixedHash. As a fallback try to handle input as h256.
std::string prettyU256(u256 _n);
std::string prettyU256(u256 _n, bool _abridged = true);
/// Convert h256 into user-readable string (by directly using std::string constructor).
std::string fromRaw(h256 _n, unsigned* _inc = nullptr);
/// Convert string to Address (h160), returns empty address if (_a.size != 40).
@ -144,6 +144,7 @@ inline Address jsToAddress(std::string const& _s) { return jsToFixed<sizeof(dev:
struct TransactionSkeleton
{
bool creation = false;
Address from;
Address to;
u256 value;

1
libethcore/Exceptions.h

@ -51,6 +51,7 @@ struct UncleTooOld: virtual dev::Exception {};
class UncleInChain: virtual public dev::Exception { public: UncleInChain(h256Set _uncles, h256 _block): m_uncles(_uncles), m_block(_block) {} h256Set m_uncles; h256 m_block; virtual const char* what() const noexcept; };
struct DuplicateUncleNonce: virtual dev::Exception {};
struct InvalidStateRoot: virtual dev::Exception {};
struct InvalidGasUsed: virtual dev::Exception {};
class InvalidTransactionsHash: virtual public dev::Exception { public: InvalidTransactionsHash(h256 _head, h256 _real): m_head(_head), m_real(_real) {} h256 m_head; h256 m_real; virtual const char* what() const noexcept; };
struct InvalidTransaction: virtual dev::Exception {};
struct InvalidDifficulty: virtual dev::Exception {};

4
libethereum/BlockChain.h

@ -112,6 +112,10 @@ public:
bytes block(h256 _hash) const;
bytes block() const { return block(currentHash()); }
/// 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 _hash, unsigned _i) const { bytes b = block(_hash); return RLP(b)[1][_i].data().toBytes(); }
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.
unsigned number(h256 _hash) const { return details(_hash).number; }
unsigned number() const { return number(currentHash()); }

2
libethereum/CMakeLists.txt

@ -11,8 +11,8 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DSTATICLIB")
aux_source_directory(. SRC_LIST)
include_directories(BEFORE ..)
include_directories(${LEVELDB_INCLUDE_DIRS})
include_directories(..)
set(EXECUTABLE ethereum)

21
libethereum/Client.cpp

@ -59,7 +59,7 @@ void VersionChecker::setOk()
}
}
Client::Client(p2p::Host* _extNet, std::string const& _dbPath, bool _forceClean, u256 _networkId):
Client::Client(p2p::Host* _extNet, std::string const& _dbPath, bool _forceClean, u256 _networkId, int miners):
Worker("eth"),
m_vc(_dbPath),
m_bc(_dbPath, !m_vc.ok() || _forceClean),
@ -69,7 +69,10 @@ Client::Client(p2p::Host* _extNet, std::string const& _dbPath, bool _forceClean,
{
m_host = _extNet->registerCapability(new EthereumHost(m_bc, m_tq, m_bq, _networkId));
setMiningThreads();
if (miners > -1)
setMiningThreads(miners);
else
setMiningThreads();
if (_dbPath.size())
Defaults::setDBPath(_dbPath);
m_vc.setOk();
@ -711,6 +714,20 @@ BlockInfo Client::uncle(h256 _blockHash, unsigned _i) const
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();
}
LocalisedLogEntries Client::logs(LogFilter const& _f) const
{
LocalisedLogEntries ret;

4
libethereum/Client.h

@ -166,7 +166,7 @@ class Client: public MinerHost, public Interface, Worker
public:
/// New-style Constructor.
explicit Client(p2p::Host* _host, std::string const& _dbPath = std::string(), bool _forceClean = false, u256 _networkId = 0);
explicit Client(p2p::Host* _host, std::string const& _dbPath = std::string(), bool _forceClean = false, u256 _networkId = 0, int miners = -1);
/// Destructor.
virtual ~Client();
@ -229,6 +229,8 @@ public:
virtual BlockDetails blockDetails(h256 _hash) const { return m_bc.details(_hash); }
virtual Transaction transaction(h256 _blockHash, unsigned _i) const;
virtual BlockInfo uncle(h256 _blockHash, unsigned _i) const;
virtual unsigned transactionCount(h256 _blockHash) const;
virtual unsigned uncleCount(h256 _blockHash) const;
/// Differences between transactions.
using Interface::diff;

25
libethereum/EthereumHost.cpp

@ -51,8 +51,8 @@ EthereumHost::EthereumHost(BlockChain const& _ch, TransactionQueue& _tq, BlockQu
EthereumHost::~EthereumHost()
{
for (auto const& i: peers())
i->cap<EthereumPeer>()->abortSync();
for (auto i: peerSessions())
i.first->cap<EthereumPeer>().get()->abortSync();
}
bool EthereumHost::ensureInitialised()
@ -95,16 +95,19 @@ void EthereumHost::changeSyncer(EthereumPeer* _syncer)
if (isSyncing())
{
if (_syncer->m_asking == Asking::Blocks)
for (auto j: peers())
if (j->cap<EthereumPeer>().get() != _syncer && j->cap<EthereumPeer>()->m_asking == Asking::Nothing)
j->cap<EthereumPeer>()->transition(Asking::Blocks);
for (auto j: peerSessions())
{
auto e = j.first->cap<EthereumPeer>().get();
if (e != _syncer && e->m_asking == Asking::Nothing)
e->transition(Asking::Blocks);
}
}
else
{
// start grabbing next hash chain if there is one.
for (auto j: peers())
for (auto j: peerSessions())
{
j->cap<EthereumPeer>()->attemptSync();
j.first->cap<EthereumPeer>()->attemptSync();
if (isSyncing())
return;
}
@ -167,8 +170,8 @@ void EthereumHost::doWork()
void EthereumHost::maintainTransactions()
{
// Send any new transactions.
for (auto const& p: peers())
if (auto ep = p->cap<EthereumPeer>())
for (auto p: peerSessions())
if (auto ep = p.first->cap<EthereumPeer>().get())
{
bytes b;
unsigned n = 0;
@ -198,9 +201,9 @@ void EthereumHost::maintainBlocks(h256 _currentHash)
{
clog(NetMessageSummary) << "Sending a new block (current is" << _currentHash << ", was" << m_latestBlockSent << ")";
for (auto j: peers())
for (auto j: peerSessions())
{
auto p = j->cap<EthereumPeer>();
auto p = j.first->cap<EthereumPeer>().get();
RLPStream ts;
p->prep(ts, NewBlockPacket, 2).appendRaw(m_chain.block(), 1).append(m_chain.details().totalDifficulty);

6
libethereum/Executive.cpp

@ -54,6 +54,12 @@ bool Executive::setup(bytesConstRef _rlp)
{
// Entry point for a user-executed transaction.
m_t = Transaction(_rlp, CheckSignature::Sender);
return setup();
}
bool Executive::setup()
{
// Entry point for a user-executed transaction.
// Avoid invalid transactions.
auto nonceReq = m_s.transactionsFrom(m_t.sender());

5
libethereum/Executive.h

@ -62,6 +62,9 @@ public:
/// Set up the executive for evaluating a transaction. You must call finalize() following this.
/// @returns true iff go() must be called (and thus a VM execution in required).
bool setup(bytesConstRef _transaction);
/// Set up the executive for evaluating a transaction. You must call finalize() following this.
/// @returns true iff go() must be called (and thus a VM execution in required).
bool setup(Transaction const& _transaction) { m_t = _transaction; return setup(); }
/// Finalise a transaction previously set up with setup().
/// @warning Only valid after setup(), and possibly go().
void finalize();
@ -101,6 +104,8 @@ public:
bool excepted() const { return m_excepted; }
private:
bool setup();
State& m_s; ///< The state to which this operation/transaction is applied.
LastHashes m_lastHashes;
std::shared_ptr<ExtVM> m_ext; ///< The VM externality object for the VM execution or null if no VM is required.

2
libethereum/Interface.h

@ -103,6 +103,8 @@ public:
virtual BlockDetails blockDetails(h256 _hash) const = 0;
virtual Transaction transaction(h256 _blockHash, unsigned _i) const = 0;
virtual BlockInfo uncle(h256 _blockHash, unsigned _i) const = 0;
virtual unsigned transactionCount(h256 _blockHash) const = 0;
virtual unsigned uncleCount(h256 _blockHash) const = 0;
// [EXTRA API]:

9
libethereum/State.cpp

@ -370,7 +370,7 @@ void State::resetCurrent()
m_cache.clear();
m_currentBlock = BlockInfo();
m_currentBlock.coinbaseAddress = m_ourAddress;
m_currentBlock.timestamp = time(0);
m_currentBlock.timestamp = max(m_previousBlock.timestamp + 1, (u256)time(0));
m_currentBlock.transactionsRoot = h256();
m_currentBlock.sha3Uncles = h256();
m_currentBlock.populateFromParent(m_previousBlock);
@ -600,6 +600,13 @@ u256 State::enact(bytesConstRef _block, BlockChain const& _bc, bool _checkNonce)
BOOST_THROW_EXCEPTION(InvalidStateRoot());
}
if (m_currentBlock.gasUsed != gasUsed())
{
// Rollback the trie.
m_db.rollback();
BOOST_THROW_EXCEPTION(InvalidGasUsed() << RequirementError(bigint(gasUsed()), bigint(m_currentBlock.gasUsed)));
}
return tdIncrease;
}

2
libethereum/State.h

@ -132,7 +132,7 @@ public:
* commitToMine(_blockChain); // will call uncommitToMine if a repeat.
* // unlock
* MineInfo info;
* for (info.complete = false; !info.complete; info = mine()) {}
* for (info.completed = false; !info.completed; info = mine()) {}
* }
* // lock
* completeMine();

3
libethereumx/CMakeLists.txt

@ -4,7 +4,8 @@ set(CMAKE_AUTOMOC OFF)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DSTATICLIB")
aux_source_directory(. SRC_LIST)
include_directories(..)
include_directories(BEFORE ..)
set(EXECUTABLE ethereumx)

2
libevm/CMakeLists.txt

@ -13,8 +13,8 @@ aux_source_directory(. SRC_LIST)
# we may not use it in libevm, but one of our dependecies is including boost in header file
# and windows is failing to build without that
include_directories(BEFORE ..)
include_directories(${Boost_INCLUDE_DIRS})
include_directories(..)
set(EXECUTABLE evm)

2
libevmcore/CMakeLists.txt

@ -11,8 +11,8 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DSTATICLIB")
aux_source_directory(. SRC_LIST)
include_directories(BEFORE ..)
include_directories(${Boost_INCLUDE_DIRS})
include_directories(..)
set(EXECUTABLE evmcore)

4
libjsqrc/setup.js

@ -20,8 +20,6 @@
* @date 2014
*/
navigator.qt = _web3;
var web3 = require('web3');
web3.setProvider(new web3.providers.QtSyncProvider());
web3.setProvider(new web3.providers.HttpSyncProvider());

2
liblll/CMakeLists.txt

@ -11,8 +11,8 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DSTATICLIB")
aux_source_directory(. SRC_LIST)
include_directories(BEFORE ..)
include_directories(${Boost_INCLUDE_DIRS})
include_directories(..)
set(EXECUTABLE lll)

2
libnatspec/CMakeLists.txt

@ -13,7 +13,7 @@ set(CMAKE_AUTOMOC OFF)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
aux_source_directory(. SRC_LIST)
include_directories(..)
include_directories(BEFORE ..)
set(EXECUTABLE natspec)

2
libnatspec/NatspecExpressionEvaluator.h

@ -19,6 +19,8 @@
* @date 2015
*/
#pragma once
#include <QtCore/QObject>
#include <QtCore/QtCore>
#include <QtQml/QJSEngine>

2
libp2p/CMakeLists.txt

@ -11,6 +11,7 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DSTATICLIB")
aux_source_directory(. SRC_LIST)
include_directories(BEFORE ..)
# we may not use it in libp2p, but one of our dependecies is including leveldb in header file
# and windows is failing to build without that
include_directories(${LEVELDB_INCLUDE_DIRS})
@ -18,7 +19,6 @@ include_directories(${LEVELDB_INCLUDE_DIRS})
if (MINIUPNPC_FOUND)
include_directories(${MINIUPNPC_INCLUDE_DIRS})
endif()
include_directories(..)
set(EXECUTABLE p2p)

55
libp2p/Common.h

@ -16,9 +16,10 @@
*/
/** @file Common.h
* @author Gav Wood <i@gavwood.com>
* @author Alex Leverington <nessence@gmail.com>
* @date 2014
*
* Miscellanea required for the Host/Session classes.
* Miscellanea required for the Host/Session/NodeTable classes.
*/
#pragma once
@ -29,9 +30,9 @@
#include <boost/asio.hpp>
#include <boost/asio/ip/tcp.hpp>
#include <chrono>
#include <libdevcore/Common.h>
#include <libdevcrypto/Common.h>
#include <libdevcore/Log.h>
#include <libdevcore/FixedHash.h>
#include <libdevcore/Exceptions.h>
namespace ba = boost::asio;
namespace bi = boost::asio::ip;
@ -54,6 +55,8 @@ class Capability;
class Host;
class Session;
struct NetworkStartRequired: virtual dev::Exception {};
struct NetWarn: public LogChannel { static const char* name() { return "!N!"; } static const int verbosity = 0; };
struct NetNote: public LogChannel { static const char* name() { return "*N*"; } static const int verbosity = 1; };
struct NetMessageSummary: public LogChannel { static const char* name() { return "-N-"; } static const int verbosity = 2; };
@ -116,7 +119,12 @@ using CapDesc = std::pair<std::string, u256>;
using CapDescSet = std::set<CapDesc>;
using CapDescs = std::vector<CapDesc>;
struct PeerInfo
/*
* Used by Host to pass negotiated information about a connection to a
* new Peer Session; PeerSessionInfo is then maintained by Session and can
* be queried for point-in-time status information via Host.
*/
struct PeerSessionInfo
{
NodeId id;
std::string clientVersion;
@ -128,7 +136,44 @@ struct PeerInfo
std::map<std::string, std::string> notes;
};
using PeerInfos = std::vector<PeerInfo>;
using PeerSessionInfos = std::vector<PeerSessionInfo>;
/**
* @brief IPv4,UDP/TCP endpoints.
*/
struct NodeIPEndpoint
{
NodeIPEndpoint(): udp(bi::udp::endpoint()), tcp(bi::tcp::endpoint()) {}
NodeIPEndpoint(bi::udp::endpoint _udp): udp(_udp) {}
NodeIPEndpoint(bi::tcp::endpoint _tcp): tcp(_tcp) {}
NodeIPEndpoint(bi::udp::endpoint _udp, bi::tcp::endpoint _tcp): udp(_udp), tcp(_tcp) {}
bi::udp::endpoint udp;
bi::tcp::endpoint tcp;
operator bool() const { return !udp.address().is_unspecified() || !tcp.address().is_unspecified(); }
};
struct Node
{
Node(): endpoint(NodeIPEndpoint()) {};
Node(Public _pubk, NodeIPEndpoint _ip, bool _required = false): id(_pubk), endpoint(_ip), required(_required) {}
Node(Public _pubk, bi::udp::endpoint _udp, bool _required = false): Node(_pubk, NodeIPEndpoint(_udp), _required) {}
virtual NodeId const& address() const { return id; }
virtual Public const& publicKey() const { return id; }
NodeId id;
/// Endpoints by which we expect to reach node.
NodeIPEndpoint endpoint;
/// If true, node will not be removed from Node list.
// TODO: p2p implement
bool required = false;
virtual operator bool() const { return (bool)id; }
};
}
}

733
libp2p/Host.cpp

@ -28,6 +28,7 @@
#include <libdevcore/Common.h>
#include <libdevcore/CommonIO.h>
#include <libethcore/Exceptions.h>
#include <libdevcrypto/FileSystem.h>
#include "Session.h"
#include "Common.h"
#include "Capability.h"
@ -37,22 +38,29 @@ using namespace std;
using namespace dev;
using namespace dev::p2p;
Host::Host(std::string const& _clientVersion, NetworkPreferences const& _n, bool _start):
HostNodeTableHandler::HostNodeTableHandler(Host& _host): m_host(_host) {}
void HostNodeTableHandler::processEvent(NodeId const& _n, NodeTableEventType const& _e)
{
m_host.onNodeTableEvent(_n, _e);
}
Host::Host(std::string const& _clientVersion, NetworkPreferences const& _n, bytesConstRef _restoreNetwork):
Worker("p2p", 0),
m_restoreNetwork(_restoreNetwork.toBytes()),
m_clientVersion(_clientVersion),
m_netPrefs(_n),
m_ifAddresses(Network::getInterfaceAddresses()),
m_ioService(2),
m_tcp4Acceptor(m_ioService),
m_key(KeyPair::create())
m_alias(networkAlias(_restoreNetwork)),
m_lastPing(chrono::steady_clock::time_point::min())
{
for (auto address: m_ifAddresses)
if (address.is_v4())
clog(NetNote) << "IP Address: " << address << " = " << (isPrivateAddress(address) ? "[LOCAL]" : "[PEER]");
clog(NetNote) << "Id:" << id().abridged();
if (_start)
start();
clog(NetNote) << "Id:" << id();
}
Host::~Host()
@ -114,10 +122,10 @@ void Host::doneWorking()
for (unsigned n = 0;; n = 0)
{
{
RecursiveGuard l(x_peers);
for (auto i: m_peers)
RecursiveGuard l(x_sessions);
for (auto i: m_sessions)
if (auto p = i.second.lock())
if (p->isOpen())
if (p->isConnected())
{
p->disconnect(ClientQuit);
n++;
@ -137,27 +145,27 @@ void Host::doneWorking()
m_ioService.reset();
// finally, clear out peers (in case they're lingering)
RecursiveGuard l(x_peers);
m_peers.clear();
RecursiveGuard l(x_sessions);
m_sessions.clear();
}
unsigned Host::protocolVersion() const
{
return 2;
return 3;
}
void Host::registerPeer(std::shared_ptr<Session> _s, CapDescs const& _caps)
{
if (!_s->m_node || !_s->m_node->id)
{
cwarn << "Attempting to register a peer without node information!";
return;
}
{
RecursiveGuard l(x_peers);
m_peers[_s->m_node->id] = _s;
clog(NetNote) << "p2p.host.peer.register" << _s->m_peer->id.abridged();
RecursiveGuard l(x_sessions);
// TODO: temporary loose-coupling; if m_peers already has peer,
// it is same as _s->m_peer. (fixing next PR)
if (!m_peers.count(_s->m_peer->id))
m_peers[_s->m_peer->id] = _s->m_peer;
m_sessions[_s->m_peer->id] = _s;
}
unsigned o = (unsigned)UserPacket;
for (auto const& i: _caps)
if (haveCapability(i))
@ -167,94 +175,67 @@ void Host::registerPeer(std::shared_ptr<Session> _s, CapDescs const& _caps)
}
}
void Host::seal(bytes& _b)
void Host::onNodeTableEvent(NodeId const& _n, NodeTableEventType const& _e)
{
_b[0] = 0x22;
_b[1] = 0x40;
_b[2] = 0x08;
_b[3] = 0x91;
uint32_t len = (uint32_t)_b.size() - 8;
_b[4] = (len >> 24) & 0xff;
_b[5] = (len >> 16) & 0xff;
_b[6] = (len >> 8) & 0xff;
_b[7] = len & 0xff;
}
shared_ptr<Node> Host::noteNode(NodeId _id, bi::tcp::endpoint _a, Origin _o, bool _ready, NodeId _oldId)
{
RecursiveGuard l(x_peers);
if (_a.port() < 30300 || _a.port() > 30305)
cwarn << "Non-standard port being recorded: " << _a.port();
if (_a.port() >= /*49152*/32768)
if (_e == NodeEntryAdded)
{
cwarn << "Private port being recorded - setting to 0";
_a = bi::tcp::endpoint(_a.address(), 0);
}
// cnote << "Node:" << _id.abridged() << _a << (_ready ? "ready" : "used") << _oldId.abridged() << (m_nodes.count(_id) ? "[have]" : "[NEW]");
// First check for another node with the same connection credentials, and put it in oldId if found.
if (!_oldId)
for (pair<h512, shared_ptr<Node>> const& n: m_nodes)
if (n.second->address == _a && n.second->id != _id)
clog(NetNote) << "p2p.host.nodeTable.events.nodeEntryAdded " << _n;
auto n = m_nodeTable->node(_n);
if (n)
{
shared_ptr<Peer> p;
{
_oldId = n.second->id;
break;
RecursiveGuard l(x_sessions);
if (m_peers.count(_n))
p = m_peers[_n];
else
{
// TODO p2p: construct peer from node
p.reset(new Peer());
p->id = _n;
p->endpoint = NodeIPEndpoint(n.endpoint.udp, n.endpoint.tcp);
p->required = n.required;
m_peers[_n] = p;
clog(NetNote) << "p2p.host.peers.events.peersAdded " << _n << p->endpoint.tcp.address() << p->endpoint.udp.address();
}
p->endpoint.tcp = n.endpoint.tcp;
}
unsigned i;
if (!m_nodes.count(_id))
{
if (m_nodes.count(_oldId))
{
i = m_nodes[_oldId]->index;
m_nodes.erase(_oldId);
m_nodesList[i] = _id;
}
else
{
i = m_nodesList.size();
m_nodesList.push_back(_id);
// TODO: Implement similar to discover. Attempt connecting to nodes
// until ideal peer count is reached; if all nodes are tried,
// repeat. Notably, this is an integrated process such that
// when onNodeTableEvent occurs we should also update +/-
// the list of nodes to be tried. Thus:
// 1) externalize connection attempts
// 2) attempt copies potentialPeer list
// 3) replace this logic w/maintenance of potentialPeers
if (peerCount() < m_idealPeerCount)
connect(p);
}
m_nodes[_id] = make_shared<Node>();
m_nodes[_id]->id = _id;
m_nodes[_id]->index = i;
m_nodes[_id]->idOrigin = _o;
}
else
else if (_e == NodeEntryRemoved)
{
i = m_nodes[_id]->index;
m_nodes[_id]->idOrigin = max(m_nodes[_id]->idOrigin, _o);
clog(NetNote) << "p2p.host.nodeTable.events.nodeEntryRemoved " << _n;
RecursiveGuard l(x_sessions);
m_peers.erase(_n);
}
m_nodes[_id]->address = _a;
m_ready.extendAll(i);
m_private.extendAll(i);
if (_ready)
m_ready += i;
else
m_ready -= i;
if (!_a.port() || (isPrivateAddress(_a.address()) && !m_netPrefs.localNetworking))
m_private += i;
else
m_private -= i;
// cnote << m_nodes[_id]->index << ":" << m_ready;
m_hadNewNodes = true;
return m_nodes[_id];
}
Nodes Host::potentialPeers(RangeMask<unsigned> const& _known)
void Host::seal(bytes& _b)
{
RecursiveGuard l(x_peers);
Nodes ret;
auto ns = (m_netPrefs.localNetworking ? _known : (m_private + _known)).inverted();
for (auto i: ns)
ret.push_back(*m_nodes[m_nodesList[i]]);
return ret;
_b[0] = 0x22;
_b[1] = 0x40;
_b[2] = 0x08;
_b[3] = 0x91;
uint32_t len = (uint32_t)_b.size() - 8;
_b[4] = (len >> 24) & 0xff;
_b[5] = (len >> 16) & 0xff;
_b[6] = (len >> 8) & 0xff;
_b[7] = len & 0xff;
}
void Host::determinePublic(string const& _publicAddress, bool _upnp)
@ -328,21 +309,35 @@ void Host::runAcceptor()
{
clog(NetConnect) << "Listening on local port " << m_listenPort << " (public: " << m_tcpPublic << ")";
m_accepting = true;
m_socket.reset(new bi::tcp::socket(m_ioService));
m_tcp4Acceptor.async_accept(*m_socket, [=](boost::system::error_code ec)
// socket is created outside of acceptor-callback
// An allocated socket is necessary as asio can use the socket
// until the callback succeeds or fails.
//
// Until callback succeeds or fails, we can't dealloc it.
//
// Callback is guaranteed to be called via asio or when
// m_tcp4Acceptor->stop() is called by Host.
//
// All exceptions are caught so they don't halt asio and so the
// socket is deleted.
//
// It's possible for an accepted connection to return an error in which
// case the socket may be open and must be closed to prevent asio from
// processing socket events after socket is deallocated.
bi::tcp::socket *s = new bi::tcp::socket(m_ioService);
m_tcp4Acceptor.async_accept(*s, [=](boost::system::error_code ec)
{
// if no error code, doHandshake takes ownership
bool success = false;
if (!ec)
{
try
{
try {
clog(NetConnect) << "Accepted connection from " << m_socket->remote_endpoint();
} catch (...){}
bi::address remoteAddress = m_socket->remote_endpoint().address();
// Port defaults to 0 - we let the hello tell us which port the peer listens to
auto p = std::make_shared<Session>(this, std::move(*m_socket.release()), bi::tcp::endpoint(remoteAddress, 0));
p->start();
// doHandshake takes ownersihp of *s via std::move
// incoming connection; we don't yet know nodeid
doHandshake(s, NodeId());
success = true;
}
catch (Exception const& _e)
@ -355,20 +350,41 @@ void Host::runAcceptor()
}
}
if (!success && m_socket->is_open())
// asio doesn't close socket on error
if (!success && s->is_open())
{
boost::system::error_code ec;
m_socket->shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
m_socket->close();
s->shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec);
s->close();
}
m_accepting = false;
delete s;
if (ec.value() < 1)
runAcceptor();
});
}
}
void Host::doHandshake(bi::tcp::socket* _socket, NodeId _nodeId)
{
try {
clog(NetConnect) << "Accepting connection for " << _socket->remote_endpoint();
} catch (...){}
shared_ptr<Peer> p;
if (_nodeId)
p = m_peers[_nodeId];
if (!p)
p.reset(new Peer());
p->endpoint.tcp.address(_socket->remote_endpoint().address());
auto ps = std::make_shared<Session>(this, std::move(*_socket), p);
ps->start();
}
string Host::pocHost()
{
vector<string> strs;
@ -376,237 +392,130 @@ string Host::pocHost()
return "poc-" + strs[1] + ".ethdev.com";
}
void Host::connect(std::string const& _addr, unsigned short _port) noexcept
void Host::addNode(NodeId const& _node, std::string const& _addr, unsigned short _tcpPeerPort, unsigned short _udpNodePort)
{
// TODO: p2p clean this up (bring tested acceptor code over from network branch)
while (isWorking() && !m_run)
this_thread::sleep_for(chrono::milliseconds(50));
if (!m_run)
return;
for (auto first: {true, false})
if (_tcpPeerPort < 30300 || _tcpPeerPort > 30305)
cwarn << "Non-standard port being recorded: " << _tcpPeerPort;
if (_tcpPeerPort >= /*49152*/32768)
{
cwarn << "Private port being recorded - setting to 0";
_tcpPeerPort = 0;
}
boost::system::error_code ec;
bi::address addr = bi::address::from_string(_addr, ec);
if (ec)
{
try
bi::tcp::resolver *r = new bi::tcp::resolver(m_ioService);
r->async_resolve({_addr, toString(_tcpPeerPort)}, [=](boost::system::error_code const& _ec, bi::tcp::resolver::iterator _epIt)
{
if (first)
if (!_ec)
{
bi::tcp::resolver r(m_ioService);
connect(r.resolve({_addr, toString(_port)})->endpoint());
bi::tcp::endpoint tcp = *_epIt;
if (m_nodeTable) m_nodeTable->addNode(Node(_node, NodeIPEndpoint(bi::udp::endpoint(tcp.address(), _udpNodePort), tcp)));
}
else
connect(bi::tcp::endpoint(bi::address::from_string(_addr), _port));
break;
}
catch (Exception const& _e)
{
// Couldn't connect
clog(NetConnect) << "Bad host " << _addr << "\n" << diagnostic_information(_e);
}
catch (exception const& e)
{
// Couldn't connect
clog(NetConnect) << "Bad host " << _addr << " (" << e.what() << ")";
}
delete r;
});
}
else
if (m_nodeTable) m_nodeTable->addNode(Node(_node, NodeIPEndpoint(bi::udp::endpoint(addr, _udpNodePort), bi::tcp::endpoint(addr, _tcpPeerPort))));
}
void Host::connect(bi::tcp::endpoint const& _ep)
void Host::connect(std::shared_ptr<Peer> const& _p)
{
while (isWorking() && !m_run)
this_thread::sleep_for(chrono::milliseconds(50));
for (unsigned i = 0; i < 200; i++)
if (isWorking() && !m_run)
this_thread::sleep_for(chrono::milliseconds(50));
if (!m_run)
return;
clog(NetConnect) << "Attempting single-shot connection to " << _ep;
bi::tcp::socket* s = new bi::tcp::socket(m_ioService);
s->async_connect(_ep, [=](boost::system::error_code const& ec)
if (havePeerSession(_p->id))
{
if (ec)
clog(NetConnect) << "Connection refused to " << _ep << " (" << ec.message() << ")";
else
{
auto p = make_shared<Session>(this, std::move(*s), _ep);
clog(NetConnect) << "Connected to " << _ep;
p->start();
}
delete s;
});
}
void Host::connect(std::shared_ptr<Node> const& _n)
{
while (isWorking() && !m_run)
this_thread::sleep_for(chrono::milliseconds(50));
if (!m_run)
clog(NetWarn) << "Aborted connect. Node already connected.";
return;
}
if (!m_nodeTable->haveNode(_p->id))
{
clog(NetWarn) << "Aborted connect. Node not in node table.";
m_nodeTable->addNode(*_p.get());
return;
}
// prevent concurrently connecting to a node; todo: better abstraction
Node *nptr = _n.get();
// prevent concurrently connecting to a node
Peer *nptr = _p.get();
{
Guard l(x_pendingNodeConns);
if (m_pendingNodeConns.count(nptr))
if (m_pendingPeerConns.count(nptr))
return;
m_pendingNodeConns.insert(nptr);
m_pendingPeerConns.insert(nptr);
}
clog(NetConnect) << "Attempting connection to node" << _n->id.abridged() << "@" << _n->address << "from" << id().abridged();
_n->lastAttempted = std::chrono::system_clock::now();
_n->failedAttempts++;
m_ready -= _n->index;
clog(NetConnect) << "Attempting connection to node" << _p->id.abridged() << "@" << _p->peerEndpoint() << "from" << id().abridged();
bi::tcp::socket* s = new bi::tcp::socket(m_ioService);
auto n = node(_n->id);
if (n)
s->async_connect(_n->address, [=](boost::system::error_code const& ec)
{
if (ec)
{
clog(NetConnect) << "Connection refused to node" << _n->id.abridged() << "@" << _n->address << "(" << ec.message() << ")";
_n->lastDisconnect = TCPError;
_n->lastAttempted = std::chrono::system_clock::now();
m_ready += _n->index;
}
else
{
clog(NetConnect) << "Connected to" << _n->id.abridged() << "@" << _n->address;
_n->lastConnected = std::chrono::system_clock::now();
auto p = make_shared<Session>(this, std::move(*s), n, true); // true because we don't care about ids matched for now. Once we have permenant IDs this will matter a lot more and we can institute a safer mechanism.
p->start();
}
delete s;
Guard l(x_pendingNodeConns);
m_pendingNodeConns.erase(nptr);
});
else
clog(NetWarn) << "Trying to connect to node not in node table.";
}
bool Host::havePeer(NodeId _id) const
{
RecursiveGuard l(x_peers);
// Remove dead peers from list.
for (auto i = m_peers.begin(); i != m_peers.end();)
if (i->second.lock().get())
++i;
else
i = m_peers.erase(i);
return !!m_peers.count(_id);
}
unsigned Node::fallbackSeconds() const
{
switch (lastDisconnect)
{
case BadProtocol:
return 30 * (failedAttempts + 1);
case UselessPeer:
case TooManyPeers:
case ClientQuit:
return 15 * (failedAttempts + 1);
case NoDisconnect:
return 0;
default:
if (failedAttempts < 5)
return failedAttempts * 5;
else if (failedAttempts < 15)
return 25 + (failedAttempts - 5) * 10;
else
return 25 + 100 + (failedAttempts - 15) * 20;
}
}
bool Node::shouldReconnect() const
{
return chrono::system_clock::now() > lastAttempted + chrono::seconds(fallbackSeconds());
}
void Host::growPeers()
{
RecursiveGuard l(x_peers);
int morePeers = (int)m_idealPeerCount - m_peers.size();
if (morePeers > 0)
s->async_connect(_p->peerEndpoint(), [=](boost::system::error_code const& ec)
{
auto toTry = m_ready;
if (!m_netPrefs.localNetworking)
toTry -= m_private;
set<Node> ns;
for (auto i: toTry)
if (m_nodes[m_nodesList[i]]->shouldReconnect())
ns.insert(*m_nodes[m_nodesList[i]]);
if (ns.size())
for (Node const& i: ns)
{
connect(m_nodes[i.id]);
if (!--morePeers)
return;
}
else
for (auto const& i: m_peers)
if (auto p = i.second.lock())
p->ensureNodesRequested();
}
}
void Host::prunePeers()
{
RecursiveGuard l(x_peers);
// We'll keep at most twice as many as is ideal, halfing what counts as "too young to kill" until we get there.
set<NodeId> dc;
for (unsigned old = 15000; m_peers.size() - dc.size() > m_idealPeerCount * 2 && old > 100; old /= 2)
if (m_peers.size() - dc.size() > m_idealPeerCount)
if (ec)
{
// look for worst peer to kick off
// first work out how many are old enough to kick off.
shared_ptr<Session> worst;
unsigned agedPeers = 0;
for (auto i: m_peers)
if (!dc.count(i.first))
if (auto p = i.second.lock())
if (chrono::steady_clock::now() > p->m_connect + chrono::milliseconds(old)) // don't throw off new peers; peer-servers should never kick off other peer-servers.
{
++agedPeers;
if ((!worst || p->rating() < worst->rating() || (p->rating() == worst->rating() && p->m_connect > worst->m_connect))) // kill older ones
worst = p;
}
if (!worst || agedPeers <= m_idealPeerCount)
break;
dc.insert(worst->id());
worst->disconnect(TooManyPeers);
clog(NetConnect) << "Connection refused to node" << _p->id.abridged() << "@" << _p->peerEndpoint() << "(" << ec.message() << ")";
_p->m_lastDisconnect = TCPError;
_p->m_lastAttempted = std::chrono::system_clock::now();
}
// Remove dead peers from list.
for (auto i = m_peers.begin(); i != m_peers.end();)
if (i->second.lock().get())
++i;
else
i = m_peers.erase(i);
{
clog(NetConnect) << "Connected to" << _p->id.abridged() << "@" << _p->peerEndpoint();
_p->m_lastDisconnect = NoDisconnect;
_p->m_lastConnected = std::chrono::system_clock::now();
_p->m_failedAttempts = 0;
auto ps = make_shared<Session>(this, std::move(*s), _p);
ps->start();
}
delete s;
Guard l(x_pendingNodeConns);
m_pendingPeerConns.erase(nptr);
});
}
PeerInfos Host::peers(bool _updatePing) const
PeerSessionInfos Host::peerSessionInfo() const
{
if (!m_run)
return PeerInfos();
return PeerSessionInfos();
RecursiveGuard l(x_peers);
if (_updatePing)
{
const_cast<Host*>(this)->pingAll();
this_thread::sleep_for(chrono::milliseconds(200));
}
std::vector<PeerInfo> ret;
for (auto& i: m_peers)
std::vector<PeerSessionInfo> ret;
RecursiveGuard l(x_sessions);
for (auto& i: m_sessions)
if (auto j = i.second.lock())
if (j->m_socket.is_open())
if (j->isConnected())
ret.push_back(j->m_info);
return ret;
}
size_t Host::peerCount() const
{
unsigned retCount = 0;
RecursiveGuard l(x_sessions);
for (auto& i: m_sessions)
if (std::shared_ptr<Session> j = i.second.lock())
if (j->isConnected())
retCount++;
return retCount;
}
void Host::run(boost::system::error_code const&)
{
if (!m_run)
{
// reset NodeTable
m_nodeTable.reset();
// stopping io service allows running manual network operations for shutdown
// and also stops blocking worker thread, allowing worker thread to exit
m_ioService.stop();
@ -615,34 +524,31 @@ void Host::run(boost::system::error_code const&)
m_timer.reset();
return;
}
m_lastTick += c_timerInterval;
if (m_lastTick >= c_timerInterval * 10)
{
growPeers();
prunePeers();
m_lastTick = 0;
}
if (m_hadNewNodes)
{
for (auto p: m_peers)
if (auto pp = p.second.lock())
pp->serviceNodesRequest();
m_hadNewNodes = false;
}
m_nodeTable->processEvents();
if (chrono::steady_clock::now() - m_lastPing > chrono::seconds(30)) // ping every 30s.
{
for (auto p: m_sessions)
if (auto pp = p.second.lock())
pp->serviceNodesRequest();
keepAlivePeers();
disconnectLatePeers();
auto c = peerCount();
if (m_idealPeerCount && !c)
for (auto p: m_peers)
if (auto pp = p.second.lock())
if (chrono::steady_clock::now() - pp->m_lastReceived > chrono::seconds(60))
pp->disconnect(PingTimeout);
pingAll();
}
if (p.second->shouldReconnect())
{
// TODO p2p: fixme
p.second->m_lastAttempted = std::chrono::system_clock::now();
connect(p.second);
break;
}
auto runcb = [this](boost::system::error_code const& error) -> void { run(error); };
if (c < m_idealPeerCount)
m_nodeTable->discover();
auto runcb = [this](boost::system::error_code const& error) { run(error); };
m_timer->expires_from_now(boost::posix_time::milliseconds(c_timerInterval));
m_timer->async_wait(runcb);
}
@ -677,13 +583,15 @@ void Host::startedWorking()
if (m_listenPort > 0)
runAcceptor();
}
else
clog(NetNote) << "p2p.start.notice id:" << id().abridged() << "Listen port is invalid or unavailable. Node Table using default port (30303).";
// if m_public address is valid then add us to node list
// todo: abstract empty() and emplace logic
if (!m_tcpPublic.address().is_unspecified() && (m_nodes.empty() || m_nodes[m_nodesList[0]]->id != id()))
noteNode(id(), m_tcpPublic, Origin::Perfect, false);
// TODO: add m_tcpPublic endpoint; sort out endpoint stuff for nodetable
m_nodeTable.reset(new NodeTable(m_ioService, m_alias, m_listenPort > 0 ? m_listenPort : 30303));
m_nodeTable->setEventHandler(new HostNodeTableHandler(*this));
restoreNetwork(&m_restoreNetwork);
clog(NetNote) << "Id:" << id().abridged();
clog(NetNote) << "p2p.started id:" << id().abridged();
run(boost::system::error_code());
}
@ -694,94 +602,143 @@ void Host::doWork()
m_ioService.run();
}
void Host::pingAll()
void Host::keepAlivePeers()
{
RecursiveGuard l(x_peers);
for (auto& i: m_peers)
if (auto j = i.second.lock())
j->ping();
if (chrono::steady_clock::now() - c_keepAliveInterval < m_lastPing)
return;
RecursiveGuard l(x_sessions);
for (auto p: m_sessions)
if (auto pp = p.second.lock())
pp->ping();
m_lastPing = chrono::steady_clock::now();
}
bytes Host::saveNodes() const
void Host::disconnectLatePeers()
{
RLPStream nodes;
auto now = chrono::steady_clock::now();
if (now - c_keepAliveTimeOut < m_lastPing)
return;
RecursiveGuard l(x_sessions);
for (auto p: m_sessions)
if (auto pp = p.second.lock())
if (now - c_keepAliveTimeOut > m_lastPing && pp->m_lastReceived < m_lastPing)
pp->disconnect(PingTimeout);
}
bytes Host::saveNetwork() const
{
std::list<Peer> peers;
{
RecursiveGuard l(x_sessions);
for (auto p: m_peers)
if (p.second)
peers.push_back(*p.second);
}
peers.sort();
RLPStream network;
int count = 0;
{
RecursiveGuard l(x_peers);
for (auto const& i: m_nodes)
RecursiveGuard l(x_sessions);
for (auto const& p: peers)
{
Node const& n = *(i.second);
// TODO: PoC-7: Figure out why it ever shares these ports.//n.address.port() >= 30300 && n.address.port() <= 30305 &&
if (!n.dead && chrono::system_clock::now() - n.lastConnected < chrono::seconds(3600 * 48) && n.address.port() > 0 && n.address.port() < /*49152*/32768 && n.id != id() && !isPrivateAddress(n.address.address()))
// TODO: alpha: Figure out why it ever shares these ports.//p.address.port() >= 30300 && p.address.port() <= 30305 &&
// TODO: alpha: if/how to save private addresses
// Only save peers which have connected within 2 days, with properly-advertised port and public IP address
if (chrono::system_clock::now() - p.m_lastConnected < chrono::seconds(3600 * 48) && p.peerEndpoint().port() > 0 && p.peerEndpoint().port() < /*49152*/32768 && p.id != id() && !isPrivateAddress(p.peerEndpoint().address()))
{
nodes.appendList(10);
if (n.address.address().is_v4())
nodes << n.address.address().to_v4().to_bytes();
network.appendList(10);
if (p.peerEndpoint().address().is_v4())
network << p.peerEndpoint().address().to_v4().to_bytes();
else
nodes << n.address.address().to_v6().to_bytes();
nodes << n.address.port() << n.id << (int)n.idOrigin
<< chrono::duration_cast<chrono::seconds>(n.lastConnected.time_since_epoch()).count()
<< chrono::duration_cast<chrono::seconds>(n.lastAttempted.time_since_epoch()).count()
<< n.failedAttempts << (unsigned)n.lastDisconnect << n.score << n.rating;
network << p.peerEndpoint().address().to_v6().to_bytes();
// TODO: alpha: replace 0 with trust-state of node
network << p.peerEndpoint().port() << p.id << 0
<< chrono::duration_cast<chrono::seconds>(p.m_lastConnected.time_since_epoch()).count()
<< chrono::duration_cast<chrono::seconds>(p.m_lastAttempted.time_since_epoch()).count()
<< p.m_failedAttempts << (unsigned)p.m_lastDisconnect << p.m_score << p.m_rating;
count++;
}
}
}
auto state = m_nodeTable->snapshot();
state.sort();
for (auto const& s: state)
{
network.appendList(3);
if (s.endpoint.tcp.address().is_v4())
network << s.endpoint.tcp.address().to_v4().to_bytes();
else
network << s.endpoint.tcp.address().to_v6().to_bytes();
network << s.endpoint.tcp.port() << s.id;
count++;
}
RLPStream ret(3);
ret << 0 << m_key.secret();
ret.appendList(count).appendRaw(nodes.out(), count);
ret << 1 << m_alias.secret();
ret.appendList(count).appendRaw(network.out(), count);
return ret.out();
}
void Host::restoreNodes(bytesConstRef _b)
void Host::restoreNetwork(bytesConstRef _b)
{
RecursiveGuard l(x_peers);
// nodes can only be added if network is added
if (!isStarted())
BOOST_THROW_EXCEPTION(NetworkStartRequired());
RecursiveGuard l(x_sessions);
RLP r(_b);
if (r.itemCount() > 0 && r[0].isInt())
switch (r[0].toInt<int>())
{
case 0:
{
auto oldId = id();
m_key = KeyPair(r[1].toHash<Secret>());
noteNode(id(), m_tcpPublic, Origin::Perfect, false, oldId);
if (r.itemCount() > 0 && r[0].isInt() && r[0].toInt<int>() == 1)
{
// r[0] = version
// r[1] = key
// r[2] = nodes
for (auto i: r[2])
for (auto i: r[2])
{
bi::tcp::endpoint tcp;
bi::udp::endpoint udp;
if (i[0].itemCount() == 4)
{
bi::tcp::endpoint ep;
if (i[0].itemCount() == 4)
ep = bi::tcp::endpoint(bi::address_v4(i[0].toArray<byte, 4>()), i[1].toInt<short>());
else
ep = bi::tcp::endpoint(bi::address_v6(i[0].toArray<byte, 16>()), i[1].toInt<short>());
auto id = (NodeId)i[2];
if (!m_nodes.count(id))
{
auto o = (Origin)i[3].toInt<int>();
auto n = noteNode(id, ep, o, true);
n->lastConnected = chrono::system_clock::time_point(chrono::seconds(i[4].toInt<unsigned>()));
n->lastAttempted = chrono::system_clock::time_point(chrono::seconds(i[5].toInt<unsigned>()));
n->failedAttempts = i[6].toInt<unsigned>();
n->lastDisconnect = (DisconnectReason)i[7].toInt<unsigned>();
n->score = (int)i[8].toInt<unsigned>();
n->rating = (int)i[9].toInt<unsigned>();
}
tcp = bi::tcp::endpoint(bi::address_v4(i[0].toArray<byte, 4>()), i[1].toInt<short>());
udp = bi::udp::endpoint(bi::address_v4(i[0].toArray<byte, 4>()), i[1].toInt<short>());
}
else
{
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>());
}
}
default:;
}
else
for (auto i: r)
{
auto id = (NodeId)i[2];
if (!m_nodes.count(id))
if (i.itemCount() == 3)
m_nodeTable->addNode(id, udp, tcp);
else if (i.itemCount() == 10)
{
bi::tcp::endpoint ep;
if (i[0].itemCount() == 4)
ep = bi::tcp::endpoint(bi::address_v4(i[0].toArray<byte, 4>()), i[1].toInt<short>());
else
ep = bi::tcp::endpoint(bi::address_v6(i[0].toArray<byte, 16>()), i[1].toInt<short>());
auto n = noteNode(id, ep, Origin::Self, true);
shared_ptr<Peer> p = make_shared<Peer>();
p->id = id;
p->m_lastConnected = chrono::system_clock::time_point(chrono::seconds(i[4].toInt<unsigned>()));
p->m_lastAttempted = chrono::system_clock::time_point(chrono::seconds(i[5].toInt<unsigned>()));
p->m_failedAttempts = i[6].toInt<unsigned>();
p->m_lastDisconnect = (DisconnectReason)i[7].toInt<unsigned>();
p->m_score = (int)i[8].toInt<unsigned>();
p->m_rating = (int)i[9].toInt<unsigned>();
p->endpoint.tcp = tcp;
p->endpoint.udp = udp;
m_peers[p->id] = p;
m_nodeTable->addNode(*p.get());
}
}
}
}
KeyPair Host::networkAlias(bytesConstRef _b)
{
RLP r(_b);
if (r.itemCount() == 3 && r[0].isInt() && r[0].toInt<int>() == 1)
return move(KeyPair(move(Secret(r[1].toBytes()))));
else
return move(KeyPair::create());
}

192
libp2p/Host.h

@ -34,8 +34,10 @@
#include <libdevcore/Worker.h>
#include <libdevcore/RangeMask.h>
#include <libdevcrypto/Common.h>
#include "NodeTable.h"
#include "HostCapability.h"
#include "Network.h"
#include "Peer.h"
#include "Common.h"
namespace ba = boost::asio;
namespace bi = ba::ip;
@ -43,83 +45,60 @@ namespace bi = ba::ip;
namespace dev
{
class RLPStream;
namespace p2p
{
class Host;
enum class Origin
class HostNodeTableHandler: public NodeTableEventHandler
{
Unknown,
Self,
SelfThird,
PerfectThird,
Perfect,
};
public:
HostNodeTableHandler(Host& _host);
struct Node
{
NodeId id; ///< Their id/public key.
unsigned index; ///< Index into m_nodesList
bi::tcp::endpoint address; ///< As reported from the node itself.
int score = 0; ///< All time cumulative.
int rating = 0; ///< Trending.
bool dead = false; ///< If true, we believe this node is permanently dead - forget all about it.
std::chrono::system_clock::time_point lastConnected;
std::chrono::system_clock::time_point lastAttempted;
unsigned failedAttempts = 0;
DisconnectReason lastDisconnect = NoDisconnect; ///< Reason for disconnect that happened last.
Origin idOrigin = Origin::Unknown; ///< How did we get to know this node's id?
int secondsSinceLastConnected() const { return lastConnected == std::chrono::system_clock::time_point() ? -1 : (int)std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now() - lastConnected).count(); }
int secondsSinceLastAttempted() const { return lastAttempted == std::chrono::system_clock::time_point() ? -1 : (int)std::chrono::duration_cast<std::chrono::seconds>(std::chrono::system_clock::now() - lastAttempted).count(); }
unsigned fallbackSeconds() const;
bool shouldReconnect() const;
bool isOffline() const { return lastAttempted > lastConnected; }
bool operator<(Node const& _n) const
{
if (isOffline() != _n.isOffline())
return isOffline();
else if (isOffline())
if (lastAttempted == _n.lastAttempted)
return failedAttempts < _n.failedAttempts;
else
return lastAttempted < _n.lastAttempted;
else
if (score == _n.score)
if (rating == _n.rating)
return failedAttempts < _n.failedAttempts;
else
return rating < _n.rating;
else
return score < _n.score;
}
};
Host const& host() const { return m_host; }
private:
virtual void processEvent(NodeId const& _n, NodeTableEventType const& _e);
using Nodes = std::vector<Node>;
Host& m_host;
};
/**
* @brief The Host class
* Capabilities should be registered prior to startNetwork, since m_capabilities is not thread-safe.
*
* @todo exceptions when nodeTable not set (prior to start)
* @todo onNodeTableEvent: move peer-connection logic into ensurePeers
* @todo handshake: gracefully disconnect peer if peer already connected
* @todo abstract socket -> IPConnection
* @todo determinePublic: ipv6, udp
* @todo handle conflict if addNode/requireNode called and Node already exists w/conflicting tcp or udp port
* @todo write host identifier to disk w/nodes
* @todo per-session keepalive/ping instead of broadcast; set ping-timeout via median-latency
* @todo configuration-management (NetworkPrefs+Keys+Topology)
*/
class Host: public Worker
{
friend class HostNodeTableHandler;
friend class Session;
friend class HostCapabilityFace;
friend struct Node;
public:
/// Start server, listening for connections on the given port.
Host(std::string const& _clientVersion, NetworkPreferences const& _n = NetworkPreferences(), bool _start = false);
Host(std::string const& _clientVersion, NetworkPreferences const& _n = NetworkPreferences(), bytesConstRef _restoreNetwork = bytesConstRef());
/// Will block on network process events.
virtual ~Host();
/// Interval at which Host::run will call keepAlivePeers to ping peers.
std::chrono::seconds const c_keepAliveInterval = std::chrono::seconds(30);
/// Disconnect timeout after failure to respond to keepAlivePeers ping.
std::chrono::milliseconds const c_keepAliveTimeOut = std::chrono::milliseconds(1000);
/// Default host for current version of client.
static std::string pocHost();
/// Basic peer network protocol version.
unsigned protocolVersion() const;
@ -129,38 +108,31 @@ public:
bool haveCapability(CapDesc const& _name) const { return m_capabilities.count(_name) != 0; }
CapDescs caps() const { CapDescs ret; for (auto const& i: m_capabilities) ret.push_back(i.first); return ret; }
template <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; } }
/// Connect to a peer explicitly.
static std::string pocHost();
void connect(std::string const& _addr, unsigned short _port = 30303) noexcept;
void connect(bi::tcp::endpoint const& _ep);
void connect(std::shared_ptr<Node> const& _n);
/// @returns true iff we have a peer of the given id.
bool havePeer(NodeId _id) const;
bool havePeerSession(NodeId _id) { RecursiveGuard l(x_sessions); return m_sessions.count(_id) ? !!m_sessions[_id].lock() : false; }
void addNode(NodeId const& _node, std::string const& _addr, unsigned short _tcpPort, unsigned short _udpPort);
/// Set ideal number of peers.
void setIdealPeerCount(unsigned _n) { m_idealPeerCount = _n; }
/// Get peer information.
PeerInfos peers(bool _updatePing = false) const;
/// Get number of peers connected; equivalent to, but faster than, peers().size().
size_t peerCount() const { RecursiveGuard l(x_peers); return m_peers.size(); }
/// Ping the peers, to update the latency information.
void pingAll();
PeerSessionInfos peerSessionInfo() const;
/// Get number of peers connected.
size_t peerCount() const;
/// Get the address we're listening on currently.
std::string listenAddress() const { return m_tcpPublic.address().to_string(); }
/// Get the port we're listening on currently.
unsigned short listenPort() const { return m_tcpPublic.port(); }
/// Serialise the set of known peers.
bytes saveNodes() const;
bytes saveNetwork() const;
/// Deserialise the data and populate the set of known peers.
void restoreNodes(bytesConstRef _b);
Nodes nodes() const { RecursiveGuard l(x_peers); Nodes ret; for (auto const& i: m_nodes) ret.push_back(*i.second); return ret; }
// TODO: P2P this should be combined with peers into a HostStat object of some kind; coalesce data, as it's only used for status information.
Peers getPeers() const { RecursiveGuard l(x_sessions); Peers ret; for (auto const& i: m_peers) ret.push_back(*i.second); return ret; }
void setNetworkPreferences(NetworkPreferences const& _p) { auto had = isStarted(); if (had) stop(); m_netPrefs = _p; if (had) start(); }
@ -174,24 +146,36 @@ public:
/// @returns if network is running.
bool isStarted() const { return m_run; }
NodeId id() const { return m_key.pub(); }
NodeId id() const { return m_alias.pub(); }
void registerPeer(std::shared_ptr<Session> _s, CapDescs const& _caps);
std::shared_ptr<Node> node(NodeId _id) const { if (m_nodes.count(_id)) return m_nodes.at(_id); return std::shared_ptr<Node>(); }
protected:
void onNodeTableEvent(NodeId const& _n, NodeTableEventType const& _e);
/// Deserialise the data and populate the set of known peers.
void restoreNetwork(bytesConstRef _b);
private:
/// Populate m_peerAddresses with available public addresses.
void determinePublic(std::string const& _publicAddress, bool _upnp);
void connect(std::shared_ptr<Peer> const& _p);
/// Ping the peers to update the latency information and disconnect peers which have timed out.
void keepAlivePeers();
/// Disconnect peers which didn't respond to keepAlivePeers ping prior to c_keepAliveTimeOut.
void disconnectLatePeers();
/// Called only from startedWorking().
void runAcceptor();
/// Handler for verifying handshake siganture before creating session. _nodeId is passed for outbound connections. If successful, socket is moved to Session via std::move.
void doHandshake(bi::tcp::socket* _socket, NodeId _nodeId = NodeId());
void seal(bytes& _b);
void growPeers();
void prunePeers();
/// Called by Worker. Not thread-safe; to be called only by worker.
virtual void startedWorking();
/// Called by startedWorking. Not thread-safe; to be called only be Worker.
@ -203,64 +187,54 @@ private:
/// Shutdown network. Not thread-safe; to be called only by worker.
virtual void doneWorking();
std::shared_ptr<Node> noteNode(NodeId _id, bi::tcp::endpoint _a, Origin _o, bool _ready, NodeId _oldId = NodeId());
Nodes potentialPeers(RangeMask<unsigned> const& _known);
/// Get or create host identifier (KeyPair).
static KeyPair networkAlias(bytesConstRef _b);
bytes m_restoreNetwork; ///< Set by constructor and used to set Host key and restore network peers & nodes.
bool m_run = false; ///< Whether network is running.
std::mutex x_runTimer; ///< Start/stop mutex.
std::mutex x_runTimer; ///< Start/stop mutex.
std::string m_clientVersion; ///< Our version string.
NetworkPreferences m_netPrefs; ///< Network settings.
/// Interface addresses (private, public)
std::vector<bi::address> m_ifAddresses; ///< Interface addresses.
std::vector<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.
ba::io_service m_ioService; ///< IOService for network stuff.
bi::tcp::acceptor m_tcp4Acceptor; ///< Listening acceptor.
std::unique_ptr<bi::tcp::socket> m_socket; ///< Listening socket.
ba::io_service m_ioService; ///< IOService for network stuff.
bi::tcp::acceptor m_tcp4Acceptor; ///< Listening acceptor.
std::unique_ptr<boost::asio::deadline_timer> m_timer; ///< Timer which, when network is running, calls scheduler() every c_timerInterval ms.
static const unsigned c_timerInterval = 100; ///< Interval which m_timer is run when network is connected.
unsigned m_lastTick = 0; ///< Used by run() for scheduling; must not be mutated outside of run().
std::set<Node*> m_pendingNodeConns; /// Used only by connect(Node&) to limit concurrently connecting to same node. See connect(shared_ptr<Node>const&).
std::set<Peer*> m_pendingPeerConns; /// Used only by connect(Peer&) to limit concurrently connecting to same node. See connect(shared_ptr<Peer>const&).
Mutex x_pendingNodeConns;
bi::tcp::endpoint m_tcpPublic; ///< Our public listening endpoint.
KeyPair m_key; ///< Our unique ID.
bool m_hadNewNodes = false;
KeyPair m_alias; ///< Alias for network communication. Network address is k*G. k is key material. TODO: Replace KeyPair.
std::shared_ptr<NodeTable> m_nodeTable; ///< Node table (uses kademlia-like discovery).
mutable RecursiveMutex x_peers;
/// The nodes to which we are currently connected.
/// 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;
/// The nodes to which we are currently connected. Used by host to service peer requests and keepAlivePeers and for shutdown. (see run())
/// Mutable because we flush zombie entries (null-weakptrs) as regular maintenance from a const method.
mutable std::map<NodeId, std::weak_ptr<Session>> m_peers;
/// Nodes to which we may connect (or to which we have connected).
/// TODO: does this need a lock?
std::map<NodeId, std::shared_ptr<Node> > m_nodes;
/// A list of node IDs. This contains every index from m_nodes; the order is guaranteed to remain the same.
std::vector<NodeId> m_nodesList;
RangeMask<unsigned> m_ready; ///< Indices into m_nodesList over to which nodes we are not currently connected, connecting or otherwise ignoring.
RangeMask<unsigned> m_private; ///< Indices into m_nodesList over to which nodes are private.
mutable std::map<NodeId, std::weak_ptr<Session>> m_sessions;
mutable RecursiveMutex x_sessions;
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.
// Our capabilities.
std::map<CapDesc, std::shared_ptr<HostCapabilityFace>> m_capabilities; ///< Each of the capabilities we support.
std::chrono::steady_clock::time_point m_lastPing; ///< Time we sent the last ping to all peers.
bool m_accepting = false;
};
}
}

14
libp2p/HostCapability.cpp

@ -32,13 +32,13 @@ void HostCapabilityFace::seal(bytes& _b)
m_host->seal(_b);
}
std::vector<std::shared_ptr<Session> > HostCapabilityFace::peers() const
std::vector<std::pair<std::shared_ptr<Session>,std::shared_ptr<Peer>>> HostCapabilityFace::peerSessions() const
{
RecursiveGuard l(m_host->x_peers);
std::vector<std::shared_ptr<Session> > ret;
for (auto const& i: m_host->m_peers)
if (std::shared_ptr<Session> p = i.second.lock())
if (p->m_capabilities.count(capDesc()))
ret.push_back(p);
RecursiveGuard l(m_host->x_sessions);
std::vector<std::pair<std::shared_ptr<Session>,std::shared_ptr<Peer>>> ret;
for (auto const& i: m_host->m_sessions)
if (std::shared_ptr<Session> s = i.second.lock())
if (s->m_capabilities.count(capDesc()))
ret.push_back(make_pair(s,s->m_peer));
return ret;
}

3
libp2p/HostCapability.h

@ -23,6 +23,7 @@
#pragma once
#include "Peer.h"
#include "Common.h"
namespace dev
@ -44,7 +45,7 @@ public:
Host* host() const { return m_host; }
std::vector<std::shared_ptr<Session> > peers() const;
std::vector<std::pair<std::shared_ptr<Session>,std::shared_ptr<Peer>>> peerSessions() const;
protected:
virtual std::string name() const = 0;

339
libp2p/NodeTable.cpp

@ -24,12 +24,15 @@ using namespace std;
using namespace dev;
using namespace dev::p2p;
NodeTable::NodeTable(ba::io_service& _io, KeyPair _alias, uint16_t _listenPort):
NodeEntry::NodeEntry(Node _src, Public _pubk, NodeIPEndpoint _gw): Node(_pubk, _gw), distance(NodeTable::distance(_src.id,_pubk)) {}
NodeEntry::NodeEntry(Node _src, Public _pubk, bi::udp::endpoint _udp): Node(_pubk, NodeIPEndpoint(_udp)), distance(NodeTable::distance(_src.id,_pubk)) {}
NodeTable::NodeTable(ba::io_service& _io, KeyPair _alias, uint16_t _udp):
m_node(Node(_alias.pub(), bi::udp::endpoint())),
m_secret(_alias.sec()),
m_socket(new NodeSocket(_io, *this, _listenPort)),
m_socketPtr(m_socket.get()),
m_io(_io),
m_socket(new NodeSocket(m_io, *this, _udp)),
m_socketPointer(m_socket.get()),
m_bucketRefreshTimer(m_io),
m_evictionCheckTimer(m_io)
{
@ -39,22 +42,78 @@ NodeTable::NodeTable(ba::io_service& _io, KeyPair _alias, uint16_t _listenPort):
m_state[i].modified = chrono::steady_clock::now() - chrono::seconds(1);
}
m_socketPtr->connect();
m_socketPointer->connect();
doRefreshBuckets(boost::system::error_code());
}
NodeTable::~NodeTable()
{
// Cancel scheduled tasks to ensure.
m_evictionCheckTimer.cancel();
m_bucketRefreshTimer.cancel();
m_socketPtr->disconnect();
// Disconnect socket so that deallocation is safe.
m_socketPointer->disconnect();
}
void NodeTable::processEvents()
{
if (m_nodeEventHandler)
m_nodeEventHandler->processEvents();
}
void NodeTable::join()
shared_ptr<NodeEntry> NodeTable::addNode(Public const& _pubk, bi::udp::endpoint const& _udp, bi::tcp::endpoint const& _tcp)
{
doFindNode(m_node.id);
auto node = Node(_pubk, NodeIPEndpoint(_udp, _tcp));
return addNode(node);
}
shared_ptr<NodeEntry> NodeTable::addNode(Node const& _node)
{
// ping address if nodeid is empty
if (!_node.id)
{
PingNode p(_node.endpoint.udp, m_node.endpoint.udp.address().to_string(), m_node.endpoint.udp.port());
p.sign(m_secret);
m_socketPointer->send(p);
shared_ptr<NodeEntry> n;
return move(n);
}
Guard l(x_nodes);
if (m_nodes.count(_node.id))
{
// // SECURITY: remove this in beta - it's only for lazy connections and presents an easy attack vector.
// if (m_server->m_peers.count(id) && isPrivateAddress(m_server->m_peers.at(id)->address.address()) && ep.port() != 0)
// // Update address if the node if we now have a public IP for it.
// m_server->m_peers[id]->address = ep;
return m_nodes[_node.id];
}
shared_ptr<NodeEntry> ret(new NodeEntry(m_node, _node.id, NodeIPEndpoint(_node.endpoint.udp, _node.endpoint.tcp)));
m_nodes[_node.id] = ret;
PingNode p(_node.endpoint.udp, m_node.endpoint.udp.address().to_string(), m_node.endpoint.udp.port());
p.sign(m_secret);
m_socketPointer->send(p);
// TODO p2p: rename to p2p.nodes.pending, add p2p.nodes.add event (when pong is received)
clog(NodeTableNote) << "p2p.nodes.add " << _node.id.abridged();
if (m_nodeEventHandler)
m_nodeEventHandler->appendEvent(_node.id, NodeEntryAdded);
return ret;
}
void NodeTable::discover()
{
static chrono::steady_clock::time_point s_lastDiscover = chrono::steady_clock::now() - std::chrono::seconds(30);
if (chrono::steady_clock::now() > s_lastDiscover + std::chrono::seconds(30))
{
s_lastDiscover = chrono::steady_clock::now();
discover(m_node.id);
}
}
list<NodeId> NodeTable::nodes() const
{
list<NodeId> nodes;
@ -64,7 +123,7 @@ list<NodeId> NodeTable::nodes() const
return move(nodes);
}
list<NodeTable::NodeEntry> NodeTable::state() const
list<NodeEntry> NodeTable::snapshot() const
{
list<NodeEntry> ret;
Guard l(x_state);
@ -74,34 +133,40 @@ list<NodeTable::NodeEntry> NodeTable::state() const
return move(ret);
}
NodeTable::NodeEntry NodeTable::operator[](NodeId _id)
Node NodeTable::node(NodeId const& _id)
{
// TODO p2p: eloquent copy operator
Guard l(x_nodes);
return *m_nodes[_id];
if (m_nodes.count(_id))
{
auto entry = m_nodes[_id];
Node n(_id, NodeIPEndpoint(entry->endpoint.udp, entry->endpoint.tcp), entry->required);
return move(n);
}
return move(Node());
}
void NodeTable::requestNeighbours(NodeEntry const& _node, NodeId _target) const
shared_ptr<NodeEntry> NodeTable::nodeEntry(NodeId _id)
{
FindNode p(_node.endpoint.udp, _target);
p.sign(m_secret);
m_socketPtr->send(p);
Guard l(x_nodes);
return m_nodes.count(_id) ? move(m_nodes[_id]) : move(shared_ptr<NodeEntry>());
}
void NodeTable::doFindNode(NodeId _node, unsigned _round, shared_ptr<set<shared_ptr<NodeEntry>>> _tried)
void NodeTable::discover(NodeId _node, unsigned _round, shared_ptr<set<shared_ptr<NodeEntry>>> _tried)
{
if (!m_socketPtr->isOpen() || _round == s_maxSteps)
if (!m_socketPointer->isOpen() || _round == s_maxSteps)
return;
if (_round == s_maxSteps)
{
clog(NodeTableNote) << "Terminating doFindNode after " << _round << " rounds.";
clog(NodeTableNote) << "Terminating discover after " << _round << " rounds.";
return;
}
else if(!_round && !_tried)
// initialized _tried on first round
_tried.reset(new set<shared_ptr<NodeEntry>>());
auto nearest = findNearest(_node);
auto nearest = nearestNodeEntries(_node);
list<shared_ptr<NodeEntry>> tried;
for (unsigned i = 0; i < nearest.size() && tried.size() < s_alpha; i++)
if (!_tried->count(nearest[i]))
@ -110,12 +175,12 @@ void NodeTable::doFindNode(NodeId _node, unsigned _round, shared_ptr<set<shared_
tried.push_back(r);
FindNode p(r->endpoint.udp, _node);
p.sign(m_secret);
m_socketPtr->send(p);
m_socketPointer->send(p);
}
if (tried.empty())
{
clog(NodeTableNote) << "Terminating doFindNode after " << _round << " rounds.";
clog(NodeTableNote) << "Terminating discover after " << _round << " rounds.";
return;
}
@ -131,15 +196,15 @@ void NodeTable::doFindNode(NodeId _node, unsigned _round, shared_ptr<set<shared_
{
if (_ec)
return;
doFindNode(_node, _round + 1, _tried);
discover(_node, _round + 1, _tried);
});
}
vector<shared_ptr<NodeTable::NodeEntry>> NodeTable::findNearest(NodeId _target)
vector<shared_ptr<NodeEntry>> NodeTable::nearestNodeEntries(NodeId _target)
{
// send s_alpha FindNode packets to nodes we know, closest to target
static unsigned lastBin = s_bins - 1;
unsigned head = dist(m_node.id, _target);
unsigned head = distance(m_node.id, _target);
unsigned tail = head == 0 ? lastBin : (head - 1) % s_bins;
map<unsigned, list<shared_ptr<NodeEntry>>> found;
@ -154,7 +219,7 @@ vector<shared_ptr<NodeTable::NodeEntry>> NodeTable::findNearest(NodeId _target)
if (auto p = n.lock())
{
if (count < s_bucketSize)
found[dist(_target, p->id)].push_back(p);
found[distance(_target, p->id)].push_back(p);
else
break;
}
@ -164,7 +229,7 @@ vector<shared_ptr<NodeTable::NodeEntry>> NodeTable::findNearest(NodeId _target)
if (auto p = n.lock())
{
if (count < s_bucketSize)
found[dist(_target, p->id)].push_back(p);
found[distance(_target, p->id)].push_back(p);
else
break;
}
@ -181,7 +246,7 @@ vector<shared_ptr<NodeTable::NodeEntry>> NodeTable::findNearest(NodeId _target)
if (auto p = n.lock())
{
if (count < s_bucketSize)
found[dist(_target, p->id)].push_back(p);
found[distance(_target, p->id)].push_back(p);
else
break;
}
@ -195,7 +260,7 @@ vector<shared_ptr<NodeTable::NodeEntry>> NodeTable::findNearest(NodeId _target)
if (auto p = n.lock())
{
if (count < s_bucketSize)
found[dist(_target, p->id)].push_back(p);
found[distance(_target, p->id)].push_back(p);
else
break;
}
@ -213,7 +278,7 @@ void NodeTable::ping(bi::udp::endpoint _to) const
{
PingNode p(_to, m_node.endpoint.udp.address().to_string(), m_node.endpoint.udp.port());
p.sign(m_secret);
m_socketPtr->send(p);
m_socketPointer->send(p);
}
void NodeTable::ping(NodeEntry* _n) const
@ -224,173 +289,186 @@ void NodeTable::ping(NodeEntry* _n) const
void NodeTable::evict(shared_ptr<NodeEntry> _leastSeen, shared_ptr<NodeEntry> _new)
{
if (!m_socketPtr->isOpen())
if (!m_socketPointer->isOpen())
return;
Guard l(x_evictions);
m_evictions.push_back(EvictionTimeout(make_pair(_leastSeen->id,chrono::steady_clock::now()), _new->id));
if (m_evictions.size() == 1)
doCheckEvictions(boost::system::error_code());
m_evictions.push_back(EvictionTimeout(make_pair(_leastSeen->id,chrono::steady_clock::now()), _new->id));
{
Guard l(x_evictions);
m_evictions.push_back(EvictionTimeout(make_pair(_leastSeen->id,chrono::steady_clock::now()), _new->id));
if (m_evictions.size() == 1)
doCheckEvictions(boost::system::error_code());
m_evictions.push_back(EvictionTimeout(make_pair(_leastSeen->id,chrono::steady_clock::now()), _new->id));
}
ping(_leastSeen.get());
}
void NodeTable::noteNode(Public const& _pubk, bi::udp::endpoint const& _endpoint)
void NodeTable::noteActiveNode(Public const& _pubk, bi::udp::endpoint const& _endpoint)
{
// Don't add ourself
if (_pubk == m_node.address())
return;
shared_ptr<NodeEntry> node;
{
Guard l(x_nodes);
auto n = m_nodes.find(_pubk);
if (n == m_nodes.end())
{
node.reset(new NodeEntry(m_node, _pubk, _endpoint));
m_nodes[_pubk] = node;
// clog(NodeTableMessageSummary) << "Adding node to cache: " << _pubk;
}
else
{
node = n->second;
// clog(NodeTableMessageSummary) << "Found node in cache: " << _pubk;
}
}
// todo: why is this necessary?
if (!!node)
noteNode(node);
}
clog(NodeTableNote) << "Noting active node:" << _pubk.abridged() << _endpoint.address().to_string() << ":" << _endpoint.port();
void NodeTable::noteNode(shared_ptr<NodeEntry> _n)
{
shared_ptr<NodeEntry> contested;
{
NodeBucket& s = bucket(_n.get());
Guard l(x_state);
s.nodes.remove_if([&_n](weak_ptr<NodeEntry> n)
{
if (n.lock() == _n)
return true;
return false;
});
shared_ptr<NodeEntry> node(addNode(_pubk, _endpoint, bi::tcp::endpoint(_endpoint.address(), _endpoint.port())));
if (s.nodes.size() >= s_bucketSize)
// TODO p2p: old bug (maybe gone now) sometimes node is nullptr here
if (!!node)
{
shared_ptr<NodeEntry> contested;
{
contested = s.nodes.front().lock();
if (!contested)
Guard l(x_state);
NodeBucket& s = bucket_UNSAFE(node.get());
s.nodes.remove_if([&node](weak_ptr<NodeEntry> n)
{
if (n.lock() == node)
return true;
return false;
});
if (s.nodes.size() >= s_bucketSize)
{
// It's only contested iff nodeentry exists
contested = s.nodes.front().lock();
if (!contested)
{
s.nodes.pop_front();
s.nodes.push_back(node);
s.touch();
}
}
else
{
s.nodes.pop_front();
s.nodes.push_back(_n);
s.nodes.push_back(node);
s.touch();
}
}
else
s.nodes.push_back(_n);
if (contested)
evict(contested, node);
}
if (contested)
evict(contested, _n);
}
void NodeTable::dropNode(shared_ptr<NodeEntry> _n)
{
NodeBucket &s = bucket(_n.get());
{
Guard l(x_state);
NodeBucket& s = bucket_UNSAFE(_n.get());
s.nodes.remove_if([&_n](weak_ptr<NodeEntry> n) { return n.lock() == _n; });
}
Guard l(x_nodes);
m_nodes.erase(_n->id);
{
Guard l(x_nodes);
m_nodes.erase(_n->id);
}
clog(NodeTableNote) << "p2p.nodes.drop " << _n->id.abridged();
if (m_nodeEventHandler)
m_nodeEventHandler->appendEvent(_n->id, NodeEntryRemoved);
}
NodeTable::NodeBucket& NodeTable::bucket(NodeEntry const* _n)
NodeTable::NodeBucket& NodeTable::bucket_UNSAFE(NodeEntry const* _n)
{
return m_state[_n->distance - 1];
}
void NodeTable::onReceived(UDPSocketFace*, bi::udp::endpoint const& _from, bytesConstRef _packet)
{
// h256 + Signature + RLP (smallest possible packet is empty neighbours packet which is 3 bytes)
if (_packet.size() < h256::size + Signature::size + 3)
// h256 + Signature + type + RLP (smallest possible packet is empty neighbours packet which is 3 bytes)
if (_packet.size() < h256::size + Signature::size + 1 + 3)
{
clog(NodeTableMessageSummary) << "Invalid Message size from " << _from.address().to_string() << ":" << _from.port();
return;
}
bytesConstRef signedBytes(_packet.cropped(h256::size, _packet.size() - h256::size));
h256 hashSigned(sha3(signedBytes));
bytesConstRef hashedBytes(_packet.cropped(h256::size, _packet.size() - h256::size));
h256 hashSigned(sha3(hashedBytes));
if (!_packet.cropped(0, h256::size).contentsEqual(hashSigned.asBytes()))
{
clog(NodeTableMessageSummary) << "Invalid Message hash from " << _from.address().to_string() << ":" << _from.port();
return;
}
bytesConstRef signedBytes(hashedBytes.cropped(Signature::size, hashedBytes.size() - Signature::size));
bytesConstRef rlpBytes(signedBytes.cropped(Signature::size, signedBytes.size() - Signature::size));
RLP rlp(rlpBytes);
unsigned itemCount = rlp.itemCount();
// todo: verify sig via known-nodeid and MDC, or, do ping/pong auth if node/endpoint is unknown/untrusted
bytesConstRef sigBytes(_packet.cropped(h256::size, Signature::size));
Public nodeid(dev::recover(*(Signature const*)sigBytes.data(), sha3(rlpBytes)));
Public nodeid(dev::recover(*(Signature const*)sigBytes.data(), sha3(signedBytes)));
if (!nodeid)
{
clog(NodeTableMessageSummary) << "Invalid Message signature from " << _from.address().to_string() << ":" << _from.port();
return;
}
noteNode(nodeid, _from);
unsigned packetType = signedBytes[0];
if (packetType && packetType < 4)
noteActiveNode(nodeid, _from);
bytesConstRef rlpBytes(_packet.cropped(h256::size + Signature::size + 1));
RLP rlp(rlpBytes);
try {
switch (itemCount)
switch (packetType)
{
case 1:
case Pong::type:
{
// clog(NodeTableMessageSummary) << "Received Pong from " << _from.address().to_string() << ":" << _from.port();
Pong in = Pong::fromBytesConstRef(_from, rlpBytes);
// whenever a pong is received, first check if it's in m_evictions
// whenever a pong is received, check if it's in m_evictions
Guard le(x_evictions);
for (auto it = m_evictions.begin(); it != m_evictions.end(); it++)
if (it->first.first == nodeid && it->first.second > std::chrono::steady_clock::now())
{
if (auto n = nodeEntry(it->second))
dropNode(n);
if (auto n = node(it->first.first))
addNode(n);
it = m_evictions.erase(it);
}
break;
}
case 2:
if (rlp[0].isList())
{
Neighbours in = Neighbours::fromBytesConstRef(_from, rlpBytes);
// clog(NodeTableMessageSummary) << "Received " << in.nodes.size() << " Neighbours from " << _from.address().to_string() << ":" << _from.port();
for (auto n: in.nodes)
noteNode(n.node, bi::udp::endpoint(bi::address::from_string(n.ipAddress), n.port));
}
else
case Neighbours::type:
{
Neighbours in = Neighbours::fromBytesConstRef(_from, rlpBytes);
// clog(NodeTableMessageSummary) << "Received " << in.nodes.size() << " Neighbours from " << _from.address().to_string() << ":" << _from.port();
for (auto n: in.nodes)
noteActiveNode(n.node, bi::udp::endpoint(bi::address::from_string(n.ipAddress), n.port));
break;
}
case FindNode::type:
{
// clog(NodeTableMessageSummary) << "Received FindNode from " << _from.address().to_string() << ":" << _from.port();
FindNode in = FindNode::fromBytesConstRef(_from, rlpBytes);
vector<shared_ptr<NodeEntry>> nearest = nearestNodeEntries(in.target);
static unsigned const nlimit = (m_socketPointer->maxDatagramSize - 11) / 86;
for (unsigned offset = 0; offset < nearest.size(); offset += nlimit)
{
// clog(NodeTableMessageSummary) << "Received FindNode from " << _from.address().to_string() << ":" << _from.port();
FindNode in = FindNode::fromBytesConstRef(_from, rlpBytes);
vector<shared_ptr<NodeTable::NodeEntry>> nearest = findNearest(in.target);
static unsigned const nlimit = (m_socketPtr->maxDatagramSize - 11) / 86;
for (unsigned offset = 0; offset < nearest.size(); offset += nlimit)
{
Neighbours out(_from, nearest, offset, nlimit);
out.sign(m_secret);
m_socketPtr->send(out);
}
Neighbours out(_from, nearest, offset, nlimit);
out.sign(m_secret);
m_socketPointer->send(out);
}
break;
case 3:
}
case PingNode::type:
{
// clog(NodeTableMessageSummary) << "Received PingNode from " << _from.address().to_string() << ":" << _from.port();
PingNode in = PingNode::fromBytesConstRef(_from, rlpBytes);
Pong p(_from);
p.replyTo = sha3(rlpBytes);
p.echo = sha3(rlpBytes);
p.sign(m_secret);
m_socketPtr->send(p);
m_socketPointer->send(p);
break;
}
default:
clog(NodeTableMessageSummary) << "Invalid Message received from " << _from.address().to_string() << ":" << _from.port();
clog(NodeTableWarn) << "Invalid Message, " << hex << packetType << ", received from " << _from.address().to_string() << ":" << dec << _from.port();
return;
}
}
@ -399,10 +477,10 @@ void NodeTable::onReceived(UDPSocketFace*, bi::udp::endpoint const& _from, bytes
clog(NodeTableWarn) << "Exception processing message from " << _from.address().to_string() << ":" << _from.port();
}
}
void NodeTable::doCheckEvictions(boost::system::error_code const& _ec)
{
if (_ec || !m_socketPtr->isOpen())
if (_ec || !m_socketPointer->isOpen())
return;
auto self(shared_from_this());
@ -415,12 +493,12 @@ void NodeTable::doCheckEvictions(boost::system::error_code const& _ec)
bool evictionsRemain = false;
list<shared_ptr<NodeEntry>> drop;
{
Guard le(x_evictions);
Guard ln(x_nodes);
Guard le(x_evictions);
for (auto& e: m_evictions)
if (chrono::steady_clock::now() - e.first.second > c_reqTimeout)
if (auto n = m_nodes[e.second])
drop.push_back(n);
if (m_nodes.count(e.second))
drop.push_back(m_nodes[e.second]);
evictionsRemain = m_evictions.size() - drop.size() > 0;
}
@ -439,13 +517,15 @@ void NodeTable::doRefreshBuckets(boost::system::error_code const& _ec)
return;
clog(NodeTableNote) << "refreshing buckets";
bool connected = m_socketPtr->isOpen();
bool connected = m_socketPointer->isOpen();
bool refreshed = false;
if (connected)
{
Guard l(x_state);
for (auto& d: m_state)
if (chrono::steady_clock::now() - d.modified > c_bucketRefresh)
{
d.touch();
while (!d.nodes.empty())
{
auto n = d.nodes.front();
@ -457,10 +537,11 @@ void NodeTable::doRefreshBuckets(boost::system::error_code const& _ec)
}
d.nodes.pop_front();
}
}
}
unsigned nextRefresh = connected ? (refreshed ? 200 : c_bucketRefresh.count()*1000) : 10000;
auto runcb = [this](boost::system::error_code const& error) -> void { 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.async_wait(runcb);
}

304
libp2p/NodeTable.h

@ -22,9 +22,10 @@
#pragma once
#include <algorithm>
#include <deque>
#include <boost/integer/static_log2.hpp>
#include <libdevcrypto/Common.h>
#include <libp2p/UDP.h>
#include "Common.h"
namespace dev
{
@ -32,189 +33,263 @@ namespace p2p
{
/**
* NodeTable using S/Kademlia system for node discovery and preference.
* untouched buckets are refreshed if they have not been touched within an hour
* NodeEntry
* @brief Entry in Node Table
*/
struct NodeEntry: public Node
{
NodeEntry(Node _src, Public _pubk, NodeIPEndpoint _gw);
NodeEntry(Node _src, Public _pubk, bi::udp::endpoint _udp);
unsigned const distance; ///< Node's distance (xor of _src as integer).
};
enum NodeTableEventType {
NodeEntryAdded,
NodeEntryRemoved
};
class NodeTable;
class NodeTableEventHandler
{
friend class NodeTable;
public:
virtual void processEvent(NodeId const& _n, NodeTableEventType const& _e) = 0;
protected:
/// Called by NodeTable on behalf of an implementation (Host) to process new events without blocking nodetable.
void processEvents()
{
std::list<std::pair<NodeId, NodeTableEventType>> events;
{
Guard l(x_events);
if (!m_nodeEventHandler.size())
return;
m_nodeEventHandler.unique();
for (auto const& n: m_nodeEventHandler)
events.push_back(std::make_pair(n,m_events[n]));
m_nodeEventHandler.clear();
m_events.clear();
}
for (auto const& e: events)
processEvent(e.first, e.second);
}
/// Called by NodeTable to append event.
virtual void appendEvent(NodeId _n, NodeTableEventType _e) { Guard l(x_events); m_nodeEventHandler.push_back(_n); m_events[_n] = _e; }
Mutex x_events;
std::list<NodeId> m_nodeEventHandler;
std::map<NodeId, NodeTableEventType> m_events;
};
class NodeTable;
inline std::ostream& operator<<(std::ostream& _out, NodeTable const& _nodeTable);
/**
* NodeTable using modified kademlia for node discovery and preference.
* Node table requires an IO service, creates a socket for incoming
* UDP messages and implements a kademlia-like protocol. Node requests and
* responses are used to build a node table which can be queried to
* obtain a list of potential nodes to connect to, and, passes events to
* Host whenever a node is added or removed to/from the table.
*
* Thread-safety is ensured by modifying NodeEntry details via
* shared_ptr replacement instead of mutating values.
*
* NodeTable accepts a port for UDP and will listen to the port on all available
* interfaces.
*
* [Integration]
* @todo deadline-timer which maintains tcp/peer connections
* @todo restore nodes: affects refreshbuckets
* @todo TCP endpoints
* @todo makeRequired: don't try to evict node if node isRequired.
* @todo makeRequired: exclude bucket from refresh if we have node as peer.
*
* [Optimization]
* @todo encapsulate doFindNode into NetworkAlgorithm (task)
* @todo serialize evictions per-bucket
* @todo store evictions in map, unit-test eviction logic
* @todo store root node in table
* @todo encapsulate discover into NetworkAlgorithm (task)
* @todo Pong to include ip:port where ping was received
* @todo expiration and sha3(id) 'to' for messages which are replies (prevents replay)
* @todo std::shared_ptr<PingNode> m_cachedPingPacket;
* @todo std::shared_ptr<FindNeighbours> m_cachedFindSelfPacket;
* @todo store root node in table?
* @todo cache Ping and FindSelf
*
* [Networking]
* @todo node-endpoint updates
* @todo TCP endpoints
* @todo eth/upnp/natpmp/stun/ice/etc for public-discovery
* @todo firewall
*
* [Protocol]
* @todo post-eviction pong
* @todo optimize knowledge at opposite edges; eg, s_bitsPerStep lookups. (Can be done via pointers to NodeBucket)
* @todo ^ s_bitsPerStep = 5; // Denoted by b in [Kademlia]. Bits by which address space is divided.
* @todo optimize (use tree for state and/or custom compare for cache)
* @todo reputation (aka universal siblings lists)
* @todo ^ s_bitsPerStep = 8; // Denoted by b in [Kademlia]. Bits by which address space is divided.
*/
class NodeTable: UDPSocketEvents, public std::enable_shared_from_this<NodeTable>
{
friend struct Neighbours;
friend std::ostream& operator<<(std::ostream& _out, NodeTable const& _nodeTable);
using NodeSocket = UDPSocket<NodeTable, 1280>;
using TimePoint = std::chrono::steady_clock::time_point;
using EvictionTimeout = std::pair<std::pair<NodeId,TimePoint>,NodeId>; ///< First NodeId may be evicted and replaced with second NodeId.
struct NodeDefaultEndpoint
{
NodeDefaultEndpoint(bi::udp::endpoint _udp): udp(_udp) {}
bi::udp::endpoint udp;
};
using EvictionTimeout = std::pair<std::pair<NodeId, TimePoint>, NodeId>; ///< First NodeId may be evicted and replaced with second NodeId.
struct Node
{
Node(Public _pubk, NodeDefaultEndpoint _udp): id(_pubk), endpoint(_udp) {}
Node(Public _pubk, bi::udp::endpoint _udp): Node(_pubk, NodeDefaultEndpoint(_udp)) {}
virtual NodeId const& address() const { return id; }
virtual Public const& publicKey() const { return id; }
NodeId id;
NodeDefaultEndpoint endpoint;
};
public:
NodeTable(ba::io_service& _io, KeyPair _alias, uint16_t _udpPort = 30303);
~NodeTable();
/**
* NodeEntry
* @todo Type of id will become template parameter.
*/
struct NodeEntry: public Node
{
NodeEntry(Node _src, Public _pubk, NodeDefaultEndpoint _gw): Node(_pubk, _gw), distance(dist(_src.id,_pubk)) {}
NodeEntry(Node _src, Public _pubk, bi::udp::endpoint _udp): Node(_pubk, NodeDefaultEndpoint(_udp)), distance(dist(_src.id,_pubk)) {}
/// Returns distance based on xor metric two node ids. Used by NodeEntry and NodeTable.
static unsigned distance(NodeId const& _a, NodeId const& _b) { u512 d = _a ^ _b; unsigned ret; for (ret = 0; d >>= 1; ++ret) {}; return ret; }
/// Set event handler for NodeEntryAdded and NodeEntryRemoved events.
void setEventHandler(NodeTableEventHandler* _handler) { m_nodeEventHandler.reset(_handler); }
/// Called by implementation which provided handler to process NodeEntryAdded/NodeEntryRemoved events. Events are coalesced by type whereby old events are ignored.
void processEvents();
/// Add node. Node will be pinged if it's not already known.
std::shared_ptr<NodeEntry> addNode(Public const& _pubk, bi::udp::endpoint const& _udp, bi::tcp::endpoint const& _tcp);
/// Add node. Node will be pinged if it's not already known.
std::shared_ptr<NodeEntry> addNode(Node const& _node);
const unsigned distance; ///< Node's distance from _src (see constructor).
};
/// To be called when node table is empty. Runs node discovery with m_node.id as the target in order to populate node-table.
void discover();
struct NodeBucket
{
unsigned distance;
TimePoint modified;
std::list<std::weak_ptr<NodeEntry>> nodes;
};
/// Returns list of node ids active in node table.
std::list<NodeId> nodes() const;
public:
NodeTable(ba::io_service& _io, KeyPair _alias, uint16_t _port = 30300);
~NodeTable();
/// Returns node count.
unsigned count() const { return m_nodes.size(); }
/// Constants for Kademlia, mostly derived from address space.
/// Returns snapshot of table.
std::list<NodeEntry> snapshot() const;
/// Returns true if node id is in node table.
bool haveNode(NodeId const& _id) { Guard l(x_nodes); return m_nodes.count(_id) > 0; }
/// Returns the Node to the corresponding node id or the empty Node if that id is not found.
Node node(NodeId const& _id);
#if defined(BOOST_AUTO_TEST_SUITE) || defined(_MSC_VER) // MSVC includes access specifier in symbol name
protected:
#else
private:
#endif
/// Constants for Kademlia, derived from address space.
static unsigned const s_addressByteSize = sizeof(NodeId); ///< Size of address type in bytes.
static unsigned const s_bits = 8 * s_addressByteSize; ///< Denoted by n in [Kademlia].
static unsigned const s_bins = s_bits - 1; ///< Size of m_state (excludes root, which is us).
static unsigned const s_maxSteps = boost::static_log2<s_bits>::value; ///< Max iterations of discovery. (doFindNode)
static unsigned const s_maxSteps = boost::static_log2<s_bits>::value; ///< Max iterations of discovery. (discover)
/// Chosen constants
static unsigned const s_bucketSize = 16; ///< Denoted by k in [Kademlia]. Number of nodes stored in each bucket.
static unsigned const s_bucketSize = 16; ///< Denoted by k in [Kademlia]. Number of nodes stored in each bucket.
static unsigned const s_alpha = 3; ///< Denoted by \alpha in [Kademlia]. Number of concurrent FindNode requests.
/// Intervals
/* todo: replace boost::posix_time; change constants to upper camelcase */
boost::posix_time::milliseconds const c_evictionCheckInterval = boost::posix_time::milliseconds(75); ///< Interval at which eviction timeouts are checked.
std::chrono::milliseconds const c_reqTimeout = std::chrono::milliseconds(300); ///< How long to wait for requests (evict, find iterations).
std::chrono::seconds const c_bucketRefresh = std::chrono::seconds(3600); ///< Refresh interval prevents bucket from becoming stale. [Kademlia]
static unsigned dist(NodeId const& _a, NodeId const& _b) { u512 d = _a ^ _b; unsigned ret; for (ret = 0; d >>= 1; ++ret) {}; return ret; }
struct NodeBucket
{
unsigned distance;
TimePoint modified;
std::list<std::weak_ptr<NodeEntry>> nodes;
void touch() { modified = std::chrono::steady_clock::now(); }
};
void join();
/// Used to ping endpoint.
void ping(bi::udp::endpoint _to) const;
NodeEntry root() const { return NodeEntry(m_node, m_node.publicKey(), m_node.endpoint.udp); }
std::list<NodeId> nodes() const;
std::list<NodeEntry> state() const;
/// Used ping known node. Used by node table when refreshing buckets and as part of eviction process (see evict).
void ping(NodeEntry* _n) const;
NodeEntry operator[](NodeId _id);
/// Returns center node entry which describes this node and used with dist() to calculate xor metric for node table nodes.
NodeEntry center() const { return NodeEntry(m_node, m_node.publicKey(), m_node.endpoint.udp); }
/// Used by asynchronous operations to return NodeEntry which is active and managed by node table.
std::shared_ptr<NodeEntry> nodeEntry(NodeId _id);
protected:
/// Repeatedly sends s_alpha concurrent requests to nodes nearest to target, for nodes nearest to target, up to s_maxSteps rounds.
void doFindNode(NodeId _node, unsigned _round = 0, std::shared_ptr<std::set<std::shared_ptr<NodeEntry>>> _tried = std::shared_ptr<std::set<std::shared_ptr<NodeEntry>>>());
/// Used to discovery nodes on network which are close to the given target.
/// Sends s_alpha concurrent requests to nodes nearest to target, for nodes nearest to target, up to s_maxSteps rounds.
void discover(NodeId _target, unsigned _round = 0, std::shared_ptr<std::set<std::shared_ptr<NodeEntry>>> _tried = std::shared_ptr<std::set<std::shared_ptr<NodeEntry>>>());
/// Returns nodes nearest to target.
std::vector<std::shared_ptr<NodeEntry>> findNearest(NodeId _target);
void ping(bi::udp::endpoint _to) const;
void ping(NodeEntry* _n) const;
/// Returns nodes from node table which are closest to target.
std::vector<std::shared_ptr<NodeEntry>> nearestNodeEntries(NodeId _target);
/// Asynchronously drops _leastSeen node if it doesn't reply and adds _new node, otherwise _new node is thrown away.
void evict(std::shared_ptr<NodeEntry> _leastSeen, std::shared_ptr<NodeEntry> _new);
void noteNode(Public const& _pubk, bi::udp::endpoint const& _endpoint);
void noteNode(std::shared_ptr<NodeEntry> _n);
/// Called whenever activity is received from an unknown node in order to maintain node table.
void noteActiveNode(Public const& _pubk, bi::udp::endpoint const& _endpoint);
/// Used to drop node when timeout occurs or when evict() result is to keep previous node.
void dropNode(std::shared_ptr<NodeEntry> _n);
NodeBucket& bucket(NodeEntry const* _n);
/// Returns references to bucket which corresponds to distance of node id.
/// @warning Only use the return reference locked x_state mutex.
// TODO p2p: Remove this method after removing offset-by-one functionality.
NodeBucket& bucket_UNSAFE(NodeEntry const* _n);
/// General Network Events
/// Called by m_socket when packet is received.
void onReceived(UDPSocketFace*, bi::udp::endpoint const& _from, bytesConstRef _packet);
void onDisconnected(UDPSocketFace*) {};
/// Called by m_socket when socket is disconnected.
void onDisconnected(UDPSocketFace*) {}
/// Tasks
/// Called by evict() to ensure eviction check is scheduled to run and terminates when no evictions remain. Asynchronous.
void doCheckEvictions(boost::system::error_code const& _ec);
void doRefreshBuckets(boost::system::error_code const& _ec);
#ifndef BOOST_AUTO_TEST_SUITE
private:
#else
protected:
#endif
/// Sends FindNeighbor packet. See doFindNode.
void requestNeighbours(NodeEntry const& _node, NodeId _target) const;
/// Purges and pings nodes for any buckets which haven't been touched for c_bucketRefresh seconds.
void doRefreshBuckets(boost::system::error_code const& _ec);
std::unique_ptr<NodeTableEventHandler> m_nodeEventHandler; ///< Event handler for node events.
Node m_node; ///< This node.
Secret m_secret; ///< This nodes secret key.
mutable Mutex x_nodes; ///< Mutable for thread-safe copy in nodes() const.
std::map<NodeId, std::shared_ptr<NodeEntry>> m_nodes; ///< NodeId -> Node table (most common lookup path)
mutable Mutex x_nodes; ///< LOCK x_state first if both locks are required. Mutable for thread-safe copy in nodes() const.
std::map<NodeId, std::shared_ptr<NodeEntry>> m_nodes; ///< Nodes
mutable Mutex x_state;
std::array<NodeBucket, s_bins> m_state; ///< State table of binned nodes.
mutable Mutex x_state; ///< LOCK x_state first if both x_nodes and x_state locks are required.
std::array<NodeBucket, s_bins> m_state; ///< State of p2p node network.
Mutex x_evictions;
Mutex x_evictions; ///< LOCK x_nodes first if both x_nodes and x_evictions locks are required.
std::deque<EvictionTimeout> m_evictions; ///< Eviction timeouts.
std::shared_ptr<NodeSocket> m_socket; ///< Shared pointer for our UDPSocket; ASIO requires shared_ptr.
NodeSocket* m_socketPtr; ///< Set to m_socket.get().
ba::io_service& m_io; ///< Used by bucket refresh timer.
std::shared_ptr<NodeSocket> m_socket; ///< Shared pointer for our UDPSocket; ASIO requires shared_ptr.
NodeSocket* m_socketPointer; ///< Set to m_socket.get(). Socket is created in constructor and disconnected in destructor to ensure access to pointer is safe.
boost::asio::deadline_timer m_bucketRefreshTimer; ///< Timer which schedules and enacts bucket refresh.
boost::asio::deadline_timer m_evictionCheckTimer; ///< Timer for handling node evictions.
};
inline std::ostream& operator<<(std::ostream& _out, NodeTable const& _nodeTable)
{
_out << _nodeTable.root().address() << "\t" << "0\t" << _nodeTable.root().endpoint.udp.address() << ":" << _nodeTable.root().endpoint.udp.port() << std::endl;
auto s = _nodeTable.state();
_out << _nodeTable.center().address() << "\t" << "0\t" << _nodeTable.center().endpoint.udp.address() << ":" << _nodeTable.center().endpoint.udp.port() << std::endl;
auto s = _nodeTable.snapshot();
for (auto n: s)
_out << n.address() << "\t" << n.distance << "\t" << n.endpoint.udp.address() << ":" << n.endpoint.udp.port() << std::endl;
return _out;
}
/**
* Ping packet: 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.
*
* Ping is used to implement evict. When a new node is seen for
* a given bucket which is full, the least-responsive node is pinged.
* If the pinged node doesn't respond, then it is removed and the new
* node is inserted.
*
* RLP Encoded Items: 3
* Minimum Encoded Size: 18 bytes
* Maximum Encoded Size: bytes // todo after u128 addresses
@ -224,11 +299,6 @@ inline std::ostream& operator<<(std::ostream& _out, NodeTable const& _nodeTable)
* port: Our port.
* expiration: Triggers regeneration of packet. May also provide control over synchronization.
*
* Ping is used to implement evict. When a new node is seen for
* a given bucket which is full, the least-responsive node is pinged.
* If the pinged node doesn't respond then it is removed and the new
* node is inserted.
*
* @todo uint128_t for ip address (<->integer ipv4/6, asio-address, asio-endpoint)
*
*/
@ -237,6 +307,9 @@ struct PingNode: RLPXDatagram<PingNode>
PingNode(bi::udp::endpoint _ep): RLPXDatagram<PingNode>(_ep) {}
PingNode(bi::udp::endpoint _ep, std::string _src, uint16_t _srcPort, std::chrono::seconds _expiration = std::chrono::seconds(60)): RLPXDatagram<PingNode>(_ep), ipAddress(_src), port(_srcPort), expiration(futureFromEpoch(_expiration)) {}
static const uint8_t type = 1;
unsigned version = 1;
std::string ipAddress;
unsigned port;
unsigned expiration;
@ -246,25 +319,23 @@ struct PingNode: RLPXDatagram<PingNode>
};
/**
* Pong packet: response to ping
* Pong packet: Sent in response to ping
*
* RLP Encoded Items: 1
* RLP Encoded Items: 2
* Minimum Encoded Size: 33 bytes
* Maximum Encoded Size: 33 bytes
*
* @todo expiration
* @todo value of replyTo
* @todo create from PingNode (reqs RLPXDatagram verify flag)
*/
struct Pong: RLPXDatagram<Pong>
{
Pong(bi::udp::endpoint _ep): RLPXDatagram<Pong>(_ep) {}
Pong(bi::udp::endpoint _ep): RLPXDatagram<Pong>(_ep), expiration(futureFromEpoch(std::chrono::seconds(60))) {}
static const uint8_t type = 2;
h256 replyTo; // hash of rlp of PingNode
h256 echo; ///< MCD of PingNode
unsigned expiration;
void streamRLP(RLPStream& _s) const { _s.appendList(1); _s << replyTo; }
void interpretRLP(bytesConstRef _bytes) { RLP r(_bytes); replyTo = (h256)r[0]; }
void streamRLP(RLPStream& _s) const { _s.appendList(2); _s << echo << expiration; }
void interpretRLP(bytesConstRef _bytes) { RLP r(_bytes); echo = (h256)r[0]; expiration = r[1].toInt<unsigned>(); }
};
/**
@ -284,6 +355,8 @@ struct FindNode: RLPXDatagram<FindNode>
{
FindNode(bi::udp::endpoint _ep): RLPXDatagram<FindNode>(_ep) {}
FindNode(bi::udp::endpoint _ep, NodeId _target, std::chrono::seconds _expiration = std::chrono::seconds(30)): RLPXDatagram<FindNode>(_ep), target(_target), expiration(futureFromEpoch(_expiration)) {}
static const uint8_t type = 3;
h512 target;
unsigned expiration;
@ -297,8 +370,6 @@ struct FindNode: RLPXDatagram<FindNode>
*
* RLP Encoded Items: 2 (first item is list)
* Minimum Encoded Size: 10 bytes
*
* @todo nonce: Should be replaced with expiration.
*/
struct Neighbours: RLPXDatagram<Neighbours>
{
@ -313,8 +384,8 @@ struct Neighbours: RLPXDatagram<Neighbours>
void interpretRLP(RLP const& _r) { ipAddress = _r[0].toString(); port = _r[1].toInt<unsigned>(); node = h512(_r[2].toBytes()); }
};
Neighbours(bi::udp::endpoint _ep): RLPXDatagram<Neighbours>(_ep) {}
Neighbours(bi::udp::endpoint _to, std::vector<std::shared_ptr<NodeTable::NodeEntry>> const& _nearest, unsigned _offset = 0, unsigned _limit = 0): RLPXDatagram<Neighbours>(_to)
Neighbours(bi::udp::endpoint _ep): RLPXDatagram<Neighbours>(_ep), expiration(futureFromEpoch(std::chrono::seconds(30))) {}
Neighbours(bi::udp::endpoint _to, std::vector<std::shared_ptr<NodeEntry>> const& _nearest, unsigned _offset = 0, unsigned _limit = 0): RLPXDatagram<Neighbours>(_to), expiration(futureFromEpoch(std::chrono::seconds(30)))
{
auto limit = _limit ? std::min(_nearest.size(), (size_t)(_offset + _limit)) : _nearest.size();
for (auto i = _offset; i < limit; i++)
@ -327,7 +398,8 @@ struct Neighbours: RLPXDatagram<Neighbours>
}
}
std::list<Node> nodes;
static const uint8_t type = 4;
std::vector<Node> nodes;
unsigned expiration = 1;
void streamRLP(RLPStream& _s) const { _s.appendList(2); _s.appendList(nodes.size()); for (auto& n: nodes) n.streamRLP(_s); _s << expiration; }
@ -346,4 +418,4 @@ struct NodeTableEgress: public LogChannel { static const char* name() { return "
struct NodeTableIngress: public LogChannel { static const char* name() { return "<<P"; } static const int verbosity = 15; };
}
}
}

83
libp2p/Peer.cpp

@ -0,0 +1,83 @@
/*
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 Peer.cpp
* @author Alex Leverington <nessence@gmail.com>
* @author Gav Wood <i@gavwood.com>
* @date 2014
*/
#include "Peer.h"
using namespace std;
using namespace dev;
using namespace dev::p2p;
namespace dev
{
namespace p2p
{
bool Peer::shouldReconnect() const
{
return chrono::system_clock::now() > m_lastAttempted + chrono::seconds(fallbackSeconds());
}
unsigned Peer::fallbackSeconds() const
{
switch (m_lastDisconnect)
{
case BadProtocol:
return 30 * (m_failedAttempts + 1);
case UselessPeer:
case TooManyPeers:
case ClientQuit:
return 15 * (m_failedAttempts + 1);
case NoDisconnect:
default:
if (m_failedAttempts < 5)
return m_failedAttempts ? m_failedAttempts * 5 : 5;
else if (m_failedAttempts < 15)
return 25 + (m_failedAttempts - 5) * 10;
else
return 25 + 100 + (m_failedAttempts - 15) * 20;
}
}
bool Peer::operator<(Peer const& _p) const
{
if (isOffline() != _p.isOffline())
return isOffline();
else if (isOffline())
if (m_lastAttempted == _p.m_lastAttempted)
return m_failedAttempts < _p.m_failedAttempts;
else
return m_lastAttempted < _p.m_lastAttempted;
else
if (m_score == _p.m_score)
if (m_rating == _p.m_rating)
if (m_failedAttempts == _p.m_failedAttempts)
return id < _p.id;
else
return m_failedAttempts < _p.m_failedAttempts;
else
return m_rating < _p.m_rating;
else
return m_score < _p.m_score;
}
}
}

97
libp2p/Peer.h

@ -0,0 +1,97 @@
/*
This file is part of cpp-ethereum.
cpp-ethereum is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
cpp-ethereum is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file Peer.h
* @author Alex Leverington <nessence@gmail.com>
* @author Gav Wood <i@gavwood.com>
* @date 2014
*/
#pragma once
#include "Common.h"
namespace dev
{
namespace p2p
{
/**
* @brief Representation of connectivity state and all other pertinent Peer metadata.
* A Peer represents connectivity between two nodes, which in this case, are the host
* and remote nodes.
*
* State information necessary for loading network topology is maintained by NodeTable.
*
* @todo Implement 'bool required'
* @todo reputation: Move score, rating to capability-specific map (&& remove friend class)
* @todo reputation: implement via origin-tagged events
* @todo Populate metadata upon construction; save when destroyed.
* @todo Metadata for peers needs to be handled via a storage backend.
* Specifically, peers can be utilized in a variety of
* many-to-many relationships while also needing to modify shared instances of
* those peers. Modifying these properties via a storage backend alleviates
* Host of the responsibility. (&& remove save/restoreNetwork)
* @todo reimplement recording of historical session information on per-transport basis
* @todo rebuild nodetable when localNetworking is enabled/disabled
* @todo move attributes into protected
*/
class Peer: public Node
{
friend class Session; /// Allows Session to update score and rating.
friend class Host; /// For Host: saveNetwork(), restoreNetwork()
public:
bool isOffline() const { return !m_session.lock(); }
bi::tcp::endpoint const& peerEndpoint() const { return endpoint.tcp; }
virtual bool operator<(Peer const& _p) const;
/// WIP: Returns current peer rating.
int rating() const { return m_rating; }
/// Return true if connection attempt should be made to this peer or false if
bool shouldReconnect() const;
/// Number of times connection has been attempted to peer.
int failedAttempts() const { return m_failedAttempts; }
/// Reason peer was previously disconnected.
DisconnectReason lastDisconnect() const { return m_lastDisconnect; }
protected:
/// Returns number of seconds to wait until attempting connection, based on attempted connection history.
unsigned fallbackSeconds() const;
int m_score = 0; ///< All time cumulative.
int m_rating = 0; ///< Trending.
/// Network Availability
std::chrono::system_clock::time_point m_lastConnected;
std::chrono::system_clock::time_point m_lastAttempted;
unsigned m_failedAttempts = 0;
DisconnectReason m_lastDisconnect = NoDisconnect; ///< Reason for disconnect that happened last.
/// Used by isOffline() and (todo) for peer to emit session information.
std::weak_ptr<Session> m_session;
};
using Peers = std::vector<Peer>;
}
}

218
libp2p/Session.cpp

@ -36,37 +36,19 @@ using namespace dev::p2p;
#endif
#define clogS(X) dev::LogOutputStream<X, true>(false) << "| " << std::setw(2) << m_socket.native_handle() << "] "
Session::Session(Host* _s, bi::tcp::socket _socket, bi::tcp::endpoint const& _manual):
Session::Session(Host* _s, bi::tcp::socket _socket, std::shared_ptr<Peer> const& _n):
m_server(_s),
m_socket(std::move(_socket)),
m_node(nullptr),
m_manualEndpoint(_manual) // NOTE: the port on this shouldn't be used if it's zero.
m_peer(_n),
m_info({NodeId(), "?", m_socket.remote_endpoint().address().to_string(), 0, chrono::steady_clock::duration(0), CapDescSet(), 0, map<string, string>()}),
m_ping(chrono::steady_clock::time_point::max())
{
m_lastReceived = m_connect = std::chrono::steady_clock::now();
m_info = PeerInfo({NodeId(), "?", m_manualEndpoint.address().to_string(), 0, std::chrono::steady_clock::duration(0), CapDescSet(), 0, map<string, string>()});
}
Session::Session(Host* _s, bi::tcp::socket _socket, std::shared_ptr<Node> const& _n, bool _force):
m_server(_s),
m_socket(std::move(_socket)),
m_node(_n),
m_manualEndpoint(_n->address),
m_force(_force)
{
m_lastReceived = m_connect = std::chrono::steady_clock::now();
m_info = PeerInfo({m_node->id, "?", _n->address.address().to_string(), _n->address.port(), std::chrono::steady_clock::duration(0), CapDescSet(), 0, map<string, string>()});
m_lastReceived = m_connect = chrono::steady_clock::now();
}
Session::~Session()
{
if (m_node)
{
if (id() && !isPermanentProblem(m_node->lastDisconnect) && !m_node->dead)
m_server->m_ready += m_node->index;
else
m_node->lastConnected = m_node->lastAttempted - chrono::seconds(1);
}
m_peer->m_lastConnected = m_peer->m_lastAttempted - chrono::seconds(1);
// Read-chain finished for one reason or another.
for (auto& i: m_capabilities)
@ -86,36 +68,21 @@ Session::~Session()
NodeId Session::id() const
{
return m_node ? m_node->id : NodeId();
return m_peer ? m_peer->id : NodeId();
}
void Session::addRating(unsigned _r)
{
if (m_node)
if (m_peer)
{
m_node->rating += _r;
m_node->score += _r;
m_peer->m_rating += _r;
m_peer->m_score += _r;
}
}
int Session::rating() const
{
return m_node->rating;
}
bi::tcp::endpoint Session::endpoint() const
{
if (m_socket.is_open() && m_node)
try
{
return bi::tcp::endpoint(m_socket.remote_endpoint().address(), m_node->address.port());
}
catch (...) {}
if (m_node)
return m_node->address;
return m_manualEndpoint;
return m_peer->m_rating;
}
template <class T> vector<T> randomSelection(vector<T> const& _t, unsigned _n)
@ -132,9 +99,10 @@ template <class T> vector<T> randomSelection(vector<T> const& _t, unsigned _n)
return ret;
}
// TODO: P2P integration: replace w/asio post -> serviceNodesRequest()
void Session::ensureNodesRequested()
{
if (isOpen() && !m_weRequestedNodes)
if (isConnected() && !m_weRequestedNodes)
{
m_weRequestedNodes = true;
RLPStream s;
@ -147,7 +115,9 @@ void Session::serviceNodesRequest()
if (!m_theyRequestedNodes)
return;
auto peers = m_server->potentialPeers(m_knownNodes);
// TODO: P2P reimplement, as per TCP "close nodes" gossip specifications (WiP)
// auto peers = m_server->potentialPeers(m_knownNodes);
Peers peers;
if (peers.empty())
{
addNote("peers", "requested");
@ -160,13 +130,11 @@ void Session::serviceNodesRequest()
auto rs = randomSelection(peers, 10);
for (auto const& i: rs)
{
clogS(NetTriviaDetail) << "Sending peer " << i.id.abridged() << i.address;
if (i.address.address().is_v4())
s.appendList(3) << bytesConstRef(i.address.address().to_v4().to_bytes().data(), 4) << i.address.port() << i.id;
clogS(NetTriviaDetail) << "Sending peer " << i.id.abridged() << i.peerEndpoint();
if (i.peerEndpoint().address().is_v4())
s.appendList(3) << bytesConstRef(i.peerEndpoint().address().to_v4().to_bytes().data(), 4) << i.peerEndpoint().port() << i.id;
else// if (i.second.address().is_v6()) - assumed
s.appendList(3) << bytesConstRef(i.address.address().to_v6().to_bytes().data(), 16) << i.address.port() << i.id;
m_knownNodes.extendAll(i.index);
m_knownNodes.unionWith(i.index);
s.appendList(3) << bytesConstRef(i.peerEndpoint().address().to_v6().to_bytes().data(), 16) << i.peerEndpoint().port() << i.id;
}
sealAndSend(s);
m_theyRequestedNodes = false;
@ -185,6 +153,11 @@ bool Session::interpret(RLP const& _r)
{
case HelloPacket:
{
// TODO: P2P first pass, implement signatures. if signature fails, drop connection. if egress, flag node's endpoint as stale.
// Move auth to Host so we consolidate authentication logic and eschew peer deduplication logic.
// Move all node-lifecycle information into Host.
// Finalize peer-lifecycle properties vs node lifecycle.
m_protocolVersion = _r[1].toInt<unsigned>();
auto clientVersion = _r[2].toString();
auto caps = _r[3].toVector<CapDesc>();
@ -202,57 +175,58 @@ bool Session::interpret(RLP const& _r)
if (m_server->id() == id)
{
// Already connected.
clogS(NetWarn) << "Connected to ourself under a false pretext. We were told this peer was id" << m_info.id.abridged();
clogS(NetWarn) << "Connected to ourself under a false pretext. We were told this peer was id" << id.abridged();
disconnect(LocalIdentity);
return true;
}
if (m_node && m_node->id != id)
// if peer and connection have id, check for UnexpectedIdentity
if (!id)
{
if (m_force || m_node->idOrigin <= Origin::SelfThird)
// SECURITY: We're forcing through the new ID, despite having been told
clogS(NetWarn) << "Connected to node, but their ID has changed since last time. This could indicate a MitM attack. Allowing anyway...";
else
{
clogS(NetWarn) << "Connected to node, but their ID has changed since last time. This could indicate a MitM attack. Disconnecting.";
disconnect(UnexpectedIdentity);
return true;
}
if (m_server->havePeer(id))
{
m_node->dead = true;
disconnect(DuplicatePeer);
return true;
}
disconnect(NullIdentity);
return true;
}
else if (!m_peer->id)
{
m_peer->id = id;
m_peer->endpoint.tcp.port(listenPort);
}
else if (m_peer->id != id)
{
// TODO p2p: FIXME. Host should catch this and reattempt adding node to table.
m_peer->id = id;
m_peer->m_score = 0;
m_peer->m_rating = 0;
// disconnect(UnexpectedIdentity);
// return true;
}
if (m_server->havePeer(id))
if (m_server->havePeerSession(id))
{
// Already connected.
clogS(NetWarn) << "Already connected to a peer with id" << id.abridged();
// Possible that two nodes continually connect to each other with exact same timing.
this_thread::sleep_for(chrono::milliseconds(rand() % 100));
disconnect(DuplicatePeer);
return true;
}
if (!id)
{
disconnect(NullIdentity);
return true;
}
m_node = m_server->noteNode(id, bi::tcp::endpoint(m_socket.remote_endpoint().address(), listenPort), Origin::Self, false, !m_node || m_node->id == id ? NodeId() : m_node->id);
if (m_node->isOffline())
m_node->lastConnected = chrono::system_clock::now();
m_knownNodes.extendAll(m_node->index);
m_knownNodes.unionWith(m_node->index);
if (m_peer->isOffline())
m_peer->m_lastConnected = chrono::system_clock::now();
if (m_protocolVersion != m_server->protocolVersion())
{
disconnect(IncompatibleProtocol);
return true;
}
m_info = PeerInfo({id, clientVersion, m_socket.remote_endpoint().address().to_string(), listenPort, std::chrono::steady_clock::duration(), _r[3].toSet<CapDesc>(), (unsigned)m_socket.native_handle(), map<string, string>() });
m_info.clientVersion = clientVersion;
m_info.host = m_socket.remote_endpoint().address().to_string();
m_info.port = listenPort;
m_info.lastPing = std::chrono::steady_clock::duration();
m_info.caps = _r[3].toSet<CapDesc>();
m_info.socket = (unsigned)m_socket.native_handle();
m_info.notes = map<string, string>();
m_server->registerPeer(shared_from_this(), caps);
break;
@ -284,12 +258,20 @@ bool Session::interpret(RLP const& _r)
break;
case GetPeersPacket:
{
// Disabled for interop testing.
// GetPeers/PeersPacket will be modified to only exchange new nodes which it's peers are interested in.
break;
clogS(NetTriviaSummary) << "GetPeers";
m_theyRequestedNodes = true;
serviceNodesRequest();
break;
}
case PeersPacket:
// Disabled for interop testing.
// GetPeers/PeersPacket will be modified to only exchange new nodes which it's peers are interested in.
break;
clogS(NetTriviaSummary) << "Peers (" << dec << (_r.itemCount() - 1) << " entries)";
m_weRequestedNodes = false;
for (unsigned i = 1; i < _r.itemCount(); ++i)
@ -307,62 +289,37 @@ bool Session::interpret(RLP const& _r)
}
auto ep = bi::tcp::endpoint(peerAddress, _r[i][1].toInt<short>());
NodeId id = _r[i][2].toHash<NodeId>();
clogS(NetAllDetail) << "Checking: " << ep << "(" << id.abridged() << ")" << isPrivateAddress(peerAddress) << this->id().abridged() << isPrivateAddress(endpoint().address()) << m_server->m_nodes.count(id) << (m_server->m_nodes.count(id) ? isPrivateAddress(m_server->m_nodes.at(id)->address.address()) : -1);
if (isPrivateAddress(peerAddress) && !m_server->m_netPrefs.localNetworking)
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
// TODO: isPrivate
if (!m_server->m_netPrefs.localNetworking && isPrivateAddress(peerAddress))
goto CONTINUE; // Private address. Ignore.
if (!id)
goto CONTINUE; // Null identity. Ignore.
goto LAMEPEER; // Null identity. Ignore.
if (m_server->id() == id)
goto CONTINUE; // Just our info - we already have that.
goto LAMEPEER; // Just our info - we already have that.
if (id == this->id())
goto CONTINUE; // Just their info - we already have that.
// check that it's not us or one we already know:
if (m_server->m_nodes.count(id))
{
/* MEH. Far from an ideal solution. Leave alone for now.
// Already got this node.
// See if it's any better that ours or not...
// This could be the public address of a known node.
// SECURITY: remove this in beta - it's only for lazy connections and presents an easy attack vector.
if (m_server->m_nodes.count(id) && isPrivateAddress(m_server->m_nodes.at(id)->address.address()) && ep.port() != 0)
// Update address if the node if we now have a public IP for it.
m_server->m_nodes[id]->address = ep;
*/
goto CONTINUE;
}
goto LAMEPEER; // Just their info - we already have that.
if (!ep.port())
goto CONTINUE; // Zero port? Don't think so.
goto LAMEPEER; // Zero port? Don't think so.
if (ep.port() >= /*49152*/32768)
goto CONTINUE; // Private port according to IANA.
// TODO: PoC-7:
// Technically fine, but ignore for now to avoid peers passing on incoming ports until we can be sure that doesn't happen any more.
// if (ep.port() < 30300 || ep.port() > 30305)
// goto CONTINUE; // Wierd port.
// Avoid our random other addresses that they might end up giving us.
for (auto i: m_server->m_peerAddresses)
if (ep.address() == i && ep.port() == m_server->listenPort())
goto CONTINUE;
// Check that we don't already know about this addr:port combination. If we are, assume the original is best.
// SECURITY: Not a valid assumption in general. Should compare ID origins and pick the best or note uncertainty and weight each equally.
for (auto const& i: m_server->m_nodes)
if (i.second->address == ep)
goto CONTINUE; // Same address but a different node.
goto LAMEPEER; // Private port according to IANA.
// OK passed all our checks. Assume it's good.
addRating(1000);
m_server->noteNode(id, ep, m_node->idOrigin == Origin::Perfect ? Origin::PerfectThird : Origin::SelfThird, true);
m_server->addNode(id, ep.address().to_string(), ep.port(), ep.port());
clogS(NetTriviaDetail) << "New peer: " << ep << "(" << id .abridged()<< ")";
CONTINUE:;
LAMEPEER:;
}
break;
default:
@ -441,7 +398,6 @@ void Session::send(bytes&& _msg)
if (!checkPacket(bytesConstRef(&_msg)))
clogS(NetWarn) << "INVALID PACKET CONSTRUCTED!";
// cerr << (void*)this << " writeImpl" << endl;
if (!m_socket.is_open())
return;
@ -494,15 +450,15 @@ void Session::drop(DisconnectReason _reason)
}
catch (...) {}
if (m_node)
if (m_peer)
{
if (_reason != m_node->lastDisconnect || _reason == NoDisconnect || _reason == ClientQuit || _reason == DisconnectRequested)
m_node->failedAttempts = 0;
m_node->lastDisconnect = _reason;
if (_reason != m_peer->m_lastDisconnect || _reason == NoDisconnect || _reason == ClientQuit || _reason == DisconnectRequested)
m_peer->m_failedAttempts = 0;
m_peer->m_lastDisconnect = _reason;
if (_reason == BadProtocol)
{
m_node->rating /= 2;
m_node->score /= 2;
m_peer->m_rating /= 2;
m_peer->m_score /= 2;
}
}
m_dropped = true;

20
libp2p/Session.h

@ -39,7 +39,7 @@ namespace dev
namespace p2p
{
struct Node;
class Peer;
/**
* @brief The Session class
@ -51,8 +51,7 @@ class Session: public std::enable_shared_from_this<Session>
friend class HostCapabilityFace;
public:
Session(Host* _server, bi::tcp::socket _socket, std::shared_ptr<Node> const& _n, bool _force = false);
Session(Host* _server, bi::tcp::socket _socket, bi::tcp::endpoint const& _manual);
Session(Host* _server, bi::tcp::socket _socket, std::shared_ptr<Peer> const& _n);
virtual ~Session();
void start();
@ -60,13 +59,11 @@ public:
void ping();
bool isOpen() const { return m_socket.is_open(); }
bool isConnected() const { return m_socket.is_open(); }
NodeId id() const;
unsigned socketId() const { return m_socket.native_handle(); }
bi::tcp::endpoint endpoint() const; ///< for other peers to connect to.
template <class PeerCap>
std::shared_ptr<PeerCap> cap() const { try { return std::static_pointer_cast<PeerCap>(m_capabilities.at(std::make_pair(PeerCap::name(), PeerCap::version()))); } catch (...) { return nullptr; } }
@ -81,7 +78,7 @@ public:
void addNote(std::string const& _k, std::string const& _v) { m_info.notes[_k] = _v; }
PeerInfo const& info() const { return m_info; }
PeerSessionInfo const& info() const { return m_info; }
void ensureNodesRequested();
void serviceNodesRequest();
@ -110,14 +107,12 @@ private:
std::array<byte, 65536> m_data; ///< Buffer for ingress packet data.
bytes m_incoming; ///< Read buffer for ingress bytes.
PeerInfo m_info; ///< Dynamic information about this peer.
unsigned m_protocolVersion = 0; ///< The protocol version of the peer.
std::shared_ptr<Node> m_node; ///< The Node object. Might be null if we constructed using a bare address/port.
bi::tcp::endpoint m_manualEndpoint; ///< The endpoint as specified by the constructor.
bool m_force = false; ///< If true, ignore IDs being different. This could open you up to MitM attacks.
std::shared_ptr<Peer> m_peer; ///< The Peer object.
bool m_dropped = false; ///< If true, we've already divested ourselves of this peer. We're just waiting for the reads & writes to fail before the shared_ptr goes OOS and the destructor kicks in.
PeerSessionInfo m_info; ///< Dynamic information about this peer.
bool m_theyRequestedNodes = false; ///< Has the peer requested nodes from us without receiveing an answer from us?
bool m_weRequestedNodes = false; ///< Have we requested nodes from the peer and not received an answer yet?
@ -126,7 +121,6 @@ private:
std::chrono::steady_clock::time_point m_lastReceived; ///< Time point of last message.
std::map<CapDesc, std::shared_ptr<Capability>> m_capabilities; ///< The peer's capability set.
RangeMask<unsigned> m_knownNodes; ///< Nodes we already know about as indices into Host's nodesList. These shouldn't be resent to peer.
};
}

35
libp2p/UDP.cpp

@ -25,25 +25,30 @@ using namespace dev::p2p;
h256 RLPXDatagramFace::sign(Secret const& _k)
{
RLPStream rlpstream;
streamRLP(rlpstream);
bytes rlpBytes(rlpstream.out());
assert(packetType());
bytesConstRef rlp(&rlpBytes);
h256 hash(dev::sha3(rlp));
Signature sig = dev::sign(_k, hash);
RLPStream rlpxstream;
// rlpxstream.appendRaw(toPublic(_k).asBytes()); // for mdc-based signature
rlpxstream.appendRaw(bytes(1, packetType())); // prefix by 1 byte for type
streamRLP(rlpxstream);
bytes rlpxBytes(rlpxstream.out());
data.resize(h256::size + Signature::size + rlp.size());
bytesConstRef packetHash(&data[0], h256::size);
bytesConstRef signedPayload(&data[h256::size], Signature::size + rlp.size());
bytesConstRef payloadSig(&data[h256::size], Signature::size);
bytesConstRef payload(&data[h256::size + Signature::size], rlp.size());
bytesConstRef rlpx(&rlpxBytes);
h256 sighash(dev::sha3(rlpx)); // H(type||data)
Signature sig = dev::sign(_k, sighash); // S(H(type||data))
sig.ref().copyTo(payloadSig);
rlp.copyTo(payload);
dev::sha3(signedPayload).ref().copyTo(packetHash);
data.resize(h256::size + Signature::size + rlpx.size());
bytesConstRef rlpxHash(&data[0], h256::size);
bytesConstRef rlpxSig(&data[h256::size], Signature::size);
bytesConstRef rlpxPayload(&data[h256::size + Signature::size], rlpx.size());
sig.ref().copyTo(rlpxSig);
rlpx.copyTo(rlpxPayload);
bytesConstRef signedRLPx(&data[h256::size], data.size() - h256::size);
dev::sha3(signedRLPx).ref().copyTo(rlpxHash);
return std::move(hash);
return std::move(sighash);
};
Public RLPXDatagramFace::authenticate(bytesConstRef _sig, bytesConstRef _rlp)

14
libp2p/UDP.h

@ -57,20 +57,19 @@ protected:
/**
* @brief RLPX Datagram which can be signed.
* @todo compact templates
* @todo make data private/functional (see UDPDatagram)
*/
struct RLPXDatagramFace: public UDPDatagram
{
static uint64_t futureFromEpoch(std::chrono::milliseconds _ms) { return std::chrono::duration_cast<std::chrono::milliseconds>((std::chrono::system_clock::now() + _ms).time_since_epoch()).count(); }
static uint64_t futureFromEpoch(std::chrono::seconds _sec) { return std::chrono::duration_cast<std::chrono::milliseconds>((std::chrono::system_clock::now() + _sec).time_since_epoch()).count(); }
static uint64_t futureFromEpoch(std::chrono::milliseconds _ms) { return std::chrono::duration_cast<std::chrono::seconds>((std::chrono::system_clock::now() + _ms).time_since_epoch()).count(); }
static uint64_t futureFromEpoch(std::chrono::seconds _sec) { return std::chrono::duration_cast<std::chrono::seconds>((std::chrono::system_clock::now() + _sec).time_since_epoch()).count(); }
static Public authenticate(bytesConstRef _sig, bytesConstRef _rlp);
virtual uint8_t packetType() = 0;
RLPXDatagramFace(bi::udp::endpoint const& _ep): UDPDatagram(_ep) {}
virtual h256 sign(Secret const& _from);
virtual void streamRLP(RLPStream&) const =0;
virtual void interpretRLP(bytesConstRef _bytes) =0;
virtual void streamRLP(RLPStream&) const = 0;
virtual void interpretRLP(bytesConstRef _bytes) = 0;
};
template <class T>
@ -78,6 +77,7 @@ struct RLPXDatagram: public RLPXDatagramFace
{
RLPXDatagram(bi::udp::endpoint const& _ep): RLPXDatagramFace(_ep) {}
static T fromBytesConstRef(bi::udp::endpoint const& _ep, bytesConstRef _bytes) { T t(_ep); t.interpretRLP(_bytes); return std::move(t); }
uint8_t packetType() { return T::type; }
};
/**

2
libp2p/UPnP.cpp

@ -132,7 +132,7 @@ int UPnP::addRedirect(char const* _addr, int _port)
srand(time(NULL));
for (unsigned i = 0; i < 10; ++i)
{
_port = rand() % (65535 - 1024) + 1024;
_port = rand() % (32768 - 1024) + 1024;
sprintf(ext_port_str, "%d", _port);
if (!UPNP_AddPortMapping(m_urls->controlURL, m_data->first.servicetype, ext_port_str, port_str, _addr, "ethereum", "TCP", NULL, NULL))
return _port;

41
libqwebthree/CMakeLists.txt

@ -1,41 +0,0 @@
cmake_policy(SET CMP0015 NEW)
# let cmake autolink dependencies on windows
cmake_policy(SET CMP0020 NEW)
# this policy was introduced in cmake 3.0
# remove if, once 3.0 will be used on unix
if (${CMAKE_MAJOR_VERSION} GREATER 2)
# old policy do not use MACOSX_RPATH
cmake_policy(SET CMP0042 OLD)
cmake_policy(SET CMP0043 OLD)
endif()
set(CMAKE_INCLUDE_CURRENT_DIR ON)
aux_source_directory(. SRC_LIST)
include_directories(${JSON_RPC_CPP_INCLUDE_DIRS})
include_directories(..)
set(EXECUTABLE qwebthree)
file(GLOB HEADERS "*.h")
if(ETH_STATIC)
add_library(${EXECUTABLE} STATIC ${RESOURCE_ADDED} ${SRC_LIST} ${HEADERS})
else()
add_library(${EXECUTABLE} SHARED ${RESOURCE_ADDED} ${SRC_LIST} ${HEADERS})
endif()
target_link_libraries(${EXECUTABLE} Qt5::Core)
target_link_libraries(${EXECUTABLE} Qt5::Gui)
target_link_libraries(${EXECUTABLE} Qt5::WebKit)
target_link_libraries(${EXECUTABLE} Qt5::WebKitWidgets)
target_link_libraries(${EXECUTABLE} Qt5::Widgets)
target_link_libraries(${EXECUTABLE} Qt5::Network)
target_link_libraries(${EXECUTABLE} Qt5::Quick)
target_link_libraries(${EXECUTABLE} Qt5::Qml)
target_link_libraries(${EXECUTABLE} ${JSON_RPC_CPP_SERVER_LIBRARIES})
target_link_libraries(${EXECUTABLE} ethereum)
target_link_libraries(${EXECUTABLE} secp256k1)
install( TARGETS ${EXECUTABLE} ARCHIVE DESTINATION lib LIBRARY DESTINATION lib )
install( FILES ${HEADERS} DESTINATION include/${EXECUTABLE} )

107
libqwebthree/QWebThree.cpp

@ -1,107 +0,0 @@
/*
This file is part of cpp-ethereum.
cpp-ethereum is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
cpp-ethereum is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file QWebThree.cpp
* @authors:
* Gav Wood <i@gavwood.com>
* Marek Kotewicz <marek@ethdev.com>
* @date 2014
*/
#include <QtCore/QtCore>
#include "QWebThree.h"
using namespace std;
QWebThree::QWebThree(QObject* _p): QObject(_p)
{
moveToThread(_p->thread());
}
QWebThree::~QWebThree()
{
clientDieing();
}
void QWebThree::clientDieing()
{
this->disconnect();
}
QString QWebThree::callMethod(QString _json)
{
emit processData(_json, ""); // it's synchronous
return m_response;
}
void QWebThree::onDataProcessed(QString _json, QString)
{
QJsonObject f = QJsonDocument::fromJson(_json.toUtf8()).object();
syncResponse(QString::fromUtf8(QJsonDocument(f).toJson()));
}
void QWebThree::syncResponse(QString _json)
{
m_response = _json;
}
QWebThreeConnector::QWebThreeConnector()
{
}
QWebThreeConnector::~QWebThreeConnector()
{
StopListening();
}
void QWebThreeConnector::setQWeb(QWebThree* _q)
{
m_qweb = _q;
if (m_isListening)
{
StopListening();
StartListening();
}
}
bool QWebThreeConnector::StartListening()
{
m_isListening = true;
if (m_qweb)
{
connect(m_qweb, SIGNAL(processData(QString, QString)), this, SLOT(onProcessData(QString, QString)));
connect(this, SIGNAL(dataProcessed(QString, QString)), m_qweb, SLOT(onDataProcessed(QString, QString)));
}
return true;
}
bool QWebThreeConnector::StopListening()
{
this->disconnect();
return true;
}
bool QWebThreeConnector::SendResponse(std::string const& _response, void* _addInfo)
{
emit dataProcessed(QString::fromStdString(_response), *(QString*)_addInfo);
return true;
}
void QWebThreeConnector::onProcessData(QString const& _json, QString const& _addInfo)
{
OnRequest(_json.toStdString(), (void*)&_addInfo);
}

89
libqwebthree/QWebThree.h

@ -1,89 +0,0 @@
/*
This file is part of cpp-ethereum.
cpp-ethereum is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
cpp-ethereum is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with cpp-ethereum. If not, see <http://www.gnu.org/licenses/>.
*/
/** @file QWebThree.h
* @authors:
* Gav Wood <i@gavwood.com>
* Marek Kotewicz <marek@ethdev.com>
* @date 2014
*/
#pragma once
#include <QtCore/QObject>
#include <QtCore/QString>
#include <jsonrpccpp/server.h>
class QWebThree: public QObject
{
Q_OBJECT
public:
QWebThree(QObject* _p);
virtual ~QWebThree();
void clientDieing();
Q_INVOKABLE QString callMethod(QString _json);
void syncResponse(QString _json);
public slots:
void onDataProcessed(QString _json, QString _addInfo);
signals:
void processData(QString _json, QString _addInfo);
void response(QString _json);
void onNewId(QString _id);
private:
QString m_response;
};
class QWebThreeConnector: public QObject, public jsonrpc::AbstractServerConnector
{
Q_OBJECT
public:
QWebThreeConnector();
virtual ~QWebThreeConnector();
void setQWeb(QWebThree *_q);
virtual bool StartListening();
virtual bool StopListening();
virtual bool SendResponse(std::string const& _response, void* _addInfo = NULL);
public slots:
void onProcessData(QString const& _json, QString const& _addInfo);
signals:
void dataProcessed(QString const& _json, QString const& _addInfo);
private:
QWebThree* m_qweb = nullptr;
bool m_isListening;
};
#define QETH_INSTALL_JS_NAMESPACE(_frame, _env, qweb) [_frame, _env, qweb]() \
{ \
_frame->disconnect(); \
_frame->addToJavaScriptWindowObject("_web3", qweb, QWebFrame::ScriptOwnership); \
_frame->addToJavaScriptWindowObject("env", _env, QWebFrame::QtOwnership); \
_frame->evaluateJavaScript(contentsOfQResource(":/js/bignumber.min.js")); \
_frame->evaluateJavaScript(contentsOfQResource(":/js/webthree.js")); \
_frame->evaluateJavaScript(contentsOfQResource(":/js/setup.js")); \
}

2
libserpent/CMakeLists.txt

@ -11,7 +11,7 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DSTATICLIB")
aux_source_directory(. SRC_LIST)
include_directories(..)
include_directories(BEFORE ..)
set(EXECUTABLE serpent)

54
libsolidity/AST.cpp

@ -206,6 +206,13 @@ vector<pair<FixedHash<4>, FunctionTypePointer>> const& ContractDefinition::getIn
return *m_interfaceFunctionList;
}
TypePointer EnumValue::getType(ContractDefinition const*) const
{
EnumDefinition const* parentDef = dynamic_cast<EnumDefinition const*>(getScope());
solAssert(parentDef, "Enclosing Scope of EnumValue was not set");
return make_shared<EnumType>(*parentDef);
}
void InheritanceSpecifier::checkTypeRequirements()
{
m_baseName->checkTypeRequirements();
@ -255,6 +262,11 @@ void StructDefinition::checkRecursion() const
}
}
TypePointer EnumDefinition::getType(ContractDefinition const*) const
{
return make_shared<TypeType>(make_shared<EnumType>(*this));
}
TypePointer FunctionDefinition::getType(ContractDefinition const*) const
{
return make_shared<FunctionType>(*this);
@ -262,6 +274,15 @@ TypePointer FunctionDefinition::getType(ContractDefinition const*) const
void FunctionDefinition::checkTypeRequirements()
{
// change all byte arrays parameters to point to calldata
if (getVisibility() == Visibility::External)
for (ASTPointer<VariableDeclaration> const& var: getParameters())
{
auto const& type = var->getType();
solAssert(!!type, "");
if (auto const* byteArrayType = dynamic_cast<ByteArrayType const*>(type.get()))
var->setType(byteArrayType->copyForLocation(ByteArrayType::Location::CallData));
}
for (ASTPointer<VariableDeclaration> const& var: getParameters() + getReturnParameters())
if (!var->getType()->canLiveOutsideStorage())
BOOST_THROW_EXCEPTION(var->createTypeError("Type is required to live outside storage."));
@ -276,12 +297,23 @@ string FunctionDefinition::getCanonicalSignature() const
return FunctionType(*this).getCanonicalSignature(getName());
}
Declaration::LValueType VariableDeclaration::getLValueType() const
bool VariableDeclaration::isLValue() const
{
if (dynamic_cast<FunctionDefinition const*>(getScope()) || dynamic_cast<ModifierDefinition const*>(getScope()))
return Declaration::LValueType::Local;
else
return Declaration::LValueType::Storage;
if (auto const* function = dynamic_cast<FunctionDefinition const*>(getScope()))
if (function->getVisibility() == Declaration::Visibility::External && isFunctionParameter())
return false;
return true;
}
bool VariableDeclaration::isFunctionParameter() const
{
auto const* function = dynamic_cast<FunctionDefinition const*>(getScope());
if (!function)
return false;
for (auto const& variable: function->getParameters())
if (variable.get() == this)
return true;
return false;
}
TypePointer ModifierDefinition::getType(ContractDefinition const*) const
@ -402,9 +434,8 @@ void Assignment::checkTypeRequirements()
{
m_leftHandSide->checkTypeRequirements();
m_leftHandSide->requireLValue();
//@todo later, assignments to structs might be possible, but not to mappings
if (!m_leftHandSide->getType()->isValueType() && !m_leftHandSide->isLocalLValue())
BOOST_THROW_EXCEPTION(createTypeError("Assignment to non-local non-value lvalue."));
if (m_leftHandSide->getType()->getCategory() == Type::Category::Mapping)
BOOST_THROW_EXCEPTION(createTypeError("Mappings cannot be assigned to."));
m_type = m_leftHandSide->getType();
if (m_assigmentOperator == Token::Assign)
m_rightHandSide->expectType(*m_type);
@ -575,8 +606,7 @@ void MemberAccess::checkTypeRequirements()
if (!m_type)
BOOST_THROW_EXCEPTION(createTypeError("Member \"" + *m_memberName + "\" not found or not "
"visible in " + type.toString()));
//@todo later, this will not always be STORAGE
m_lvalue = type.getCategory() == Type::Category::Struct ? Declaration::LValueType::Storage : Declaration::LValueType::None;
m_isLValue = (type.getCategory() == Type::Category::Struct);
}
void IndexAccess::checkTypeRequirements()
@ -588,14 +618,14 @@ void IndexAccess::checkTypeRequirements()
MappingType const& type = dynamic_cast<MappingType const&>(*m_base->getType());
m_index->expectType(*type.getKeyType());
m_type = type.getValueType();
m_lvalue = Declaration::LValueType::Storage;
m_isLValue = true;
}
void Identifier::checkTypeRequirements()
{
solAssert(m_referencedDeclaration, "Identifier not resolved.");
m_lvalue = m_referencedDeclaration->getLValueType();
m_isLValue = m_referencedDeclaration->isLValue();
m_type = m_referencedDeclaration->getType(m_currentContract);
if (!m_type)
BOOST_THROW_EXCEPTION(createTypeError("Declaration referenced before type could be determined."));

62
libsolidity/AST.h

@ -132,8 +132,8 @@ private:
class Declaration: public ASTNode
{
public:
enum class LValueType { None, Local, Storage };
enum class Visibility { Default, Public, Protected, Private };
/// Visibility ordered from restricted to unrestricted.
enum class Visibility { Default, Private, Protected, Public, External };
Declaration(Location const& _location, ASTPointer<ASTString> const& _name,
Visibility _visibility = Visibility::Default):
@ -142,7 +142,9 @@ public:
/// @returns the declared name.
ASTString const& getName() const { return *m_name; }
Visibility getVisibility() const { return m_visibility == Visibility::Default ? getDefaultVisibility() : m_visibility; }
bool isPublic() const { return getVisibility() == Visibility::Public; }
bool isPublic() const { return getVisibility() >= Visibility::Public; }
bool isVisibleInContract() const { return getVisibility() != Visibility::External; }
bool isVisibleInDerivedContracts() const { return isVisibleInContract() && getVisibility() >= Visibility::Protected; }
/// @returns the scope this declaration resides in. Can be nullptr if it is the global scope.
/// Available only after name and type resolution step.
@ -153,8 +155,7 @@ public:
/// The current contract has to be given since this context can change the type, especially of
/// contract types.
virtual TypePointer getType(ContractDefinition const* m_currentContract = nullptr) const = 0;
/// @returns the lvalue type of expressions referencing this declaration
virtual LValueType getLValueType() const { return LValueType::None; }
virtual bool isLValue() const { return false; }
protected:
virtual Visibility getDefaultVisibility() const { return Visibility::Public; }
@ -209,6 +210,7 @@ public:
ASTPointer<ASTString> const& _documentation,
std::vector<ASTPointer<InheritanceSpecifier>> const& _baseContracts,
std::vector<ASTPointer<StructDefinition>> const& _definedStructs,
std::vector<ASTPointer<EnumDefinition>> const& _definedEnums,
std::vector<ASTPointer<VariableDeclaration>> const& _stateVariables,
std::vector<ASTPointer<FunctionDefinition>> const& _definedFunctions,
std::vector<ASTPointer<ModifierDefinition>> const& _functionModifiers,
@ -216,6 +218,7 @@ public:
Declaration(_location, _name), Documented(_documentation),
m_baseContracts(_baseContracts),
m_definedStructs(_definedStructs),
m_definedEnums(_definedEnums),
m_stateVariables(_stateVariables),
m_definedFunctions(_definedFunctions),
m_functionModifiers(_functionModifiers),
@ -227,6 +230,7 @@ public:
std::vector<ASTPointer<InheritanceSpecifier>> const& getBaseContracts() const { return m_baseContracts; }
std::vector<ASTPointer<StructDefinition>> const& getDefinedStructs() const { return m_definedStructs; }
std::vector<ASTPointer<EnumDefinition>> const& getDefinedEnums() const { return m_definedEnums; }
std::vector<ASTPointer<VariableDeclaration>> const& getStateVariables() const { return m_stateVariables; }
std::vector<ASTPointer<ModifierDefinition>> const& getFunctionModifiers() const { return m_functionModifiers; }
std::vector<ASTPointer<FunctionDefinition>> const& getDefinedFunctions() const { return m_definedFunctions; }
@ -260,6 +264,7 @@ private:
std::vector<ASTPointer<InheritanceSpecifier>> m_baseContracts;
std::vector<ASTPointer<StructDefinition>> m_definedStructs;
std::vector<ASTPointer<EnumDefinition>> m_definedEnums;
std::vector<ASTPointer<VariableDeclaration>> m_stateVariables;
std::vector<ASTPointer<FunctionDefinition>> m_definedFunctions;
std::vector<ASTPointer<ModifierDefinition>> m_functionModifiers;
@ -315,6 +320,39 @@ private:
std::vector<ASTPointer<VariableDeclaration>> m_members;
};
class EnumDefinition: public Declaration
{
public:
EnumDefinition(Location const& _location,
ASTPointer<ASTString> const& _name,
std::vector<ASTPointer<EnumValue>> const& _members):
Declaration(_location, _name), m_members(_members) {}
virtual void accept(ASTVisitor& _visitor) override;
virtual void accept(ASTConstVisitor& _visitor) const override;
std::vector<ASTPointer<EnumValue>> const& getMembers() const { return m_members; }
virtual TypePointer getType(ContractDefinition const*) const override;
private:
std::vector<ASTPointer<EnumValue>> m_members;
};
/**
* Declaration of an Enum Value
*/
class EnumValue: public Declaration
{
public:
EnumValue(Location const& _location,
ASTPointer<ASTString> const& _name):
Declaration(_location, _name) {}
virtual void accept(ASTVisitor& _visitor) override;
virtual void accept(ASTConstVisitor& _visitor) const override;
TypePointer getType(ContractDefinition const* = nullptr) const;
};
/**
* Parameter list, used as function parameter list and return list.
* None of the parameters is allowed to contain mappings (not even recursively
@ -408,8 +446,9 @@ public:
TypePointer getType(ContractDefinition const* = nullptr) const { return m_type; }
void setType(std::shared_ptr<Type const> const& _type) { m_type = _type; }
virtual LValueType getLValueType() const override;
virtual bool isLValue() const override;
bool isLocalVariable() const { return !!dynamic_cast<FunctionDefinition const*>(getScope()); }
bool isFunctionParameter() const;
bool isStateVariable() const { return m_isStateVariable; }
bool isIndexed() const { return m_isIndexed; }
@ -679,7 +718,7 @@ public:
Expression const& getCondition() const { return *m_condition; }
Statement const& getTrueStatement() const { return *m_trueBody; }
/// @returns the "else" part of the if statement or nullptr if there is no "else" part.
/// @returns the "else" part of the if statement or nullptr if there is no "else" part.
Statement const* getFalseStatement() const { return m_falseBody.get(); }
private:
@ -847,8 +886,7 @@ public:
virtual void checkTypeRequirements() = 0;
std::shared_ptr<Type const> const& getType() const { return m_type; }
bool isLValue() const { return m_lvalue != Declaration::LValueType::None; }
bool isLocalLValue() const { return m_lvalue == Declaration::LValueType::Local; }
bool isLValue() const { return m_isLValue; }
/// Helper function, infer the type via @ref checkTypeRequirements and then check that it
/// is implicitly convertible to @a _expectedType. If not, throw exception.
@ -863,9 +901,9 @@ public:
protected:
//! Inferred type of the expression, only filled after a call to checkTypeRequirements().
std::shared_ptr<Type const> m_type;
//! If this expression is an lvalue (i.e. something that can be assigned to) and is stored
//! locally or in storage. This is set during calls to @a checkTypeRequirements()
Declaration::LValueType m_lvalue = Declaration::LValueType::None;
//! If this expression is an lvalue (i.e. something that can be assigned to).
//! This is set during calls to @a checkTypeRequirements()
bool m_isLValue = false;
//! Whether the outer expression requested the address (true) or the value (false) of this expression.
bool m_lvalueRequested = false;
};

2
libsolidity/ASTForward.h

@ -40,6 +40,8 @@ class Declaration;
class ContractDefinition;
class InheritanceSpecifier;
class StructDefinition;
class EnumDefinition;
class EnumValue;
class ParameterList;
class FunctionDefinition;
class VariableDeclaration;

17
libsolidity/ASTJsonConverter.cpp

@ -118,11 +118,7 @@ bool ASTJsonConverter::visit(FunctionDefinition const& _node)
bool ASTJsonConverter::visit(VariableDeclaration const& _node)
{
bool isLocalVariable = (_node.getLValueType() == VariableDeclaration::LValueType::Local);
addJsonNode("VariableDeclaration",
{ make_pair("name", _node.getName()),
make_pair("local", boost::lexical_cast<std::string>(isLocalVariable))},
true);
addJsonNode("VariableDeclaration", { make_pair("name", _node.getName()) }, true);
return true;
}
@ -216,11 +212,12 @@ bool ASTJsonConverter::visit(ExpressionStatement const&)
bool ASTJsonConverter::visit(Expression const& _node)
{
addJsonNode("Expression",
{ make_pair("type", getType(_node)),
make_pair("lvalue", boost::lexical_cast<std::string>(_node.isLValue())),
make_pair("local_lvalue", boost::lexical_cast<std::string>(_node.isLocalLValue())) },
true);
addJsonNode(
"Expression",
{ make_pair("type", getType(_node)),
make_pair("lvalue", boost::lexical_cast<std::string>(_node.isLValue())) },
true
);
return true;
}

2
libsolidity/ASTJsonConverter.h

@ -27,7 +27,7 @@
#include <libsolidity/ASTVisitor.h>
#include <libsolidity/Exceptions.h>
#include <libsolidity/Utils.h>
#include <jsoncpp/json/json.h>
#include <json/json.h>
namespace dev
{

22
libsolidity/ASTPrinter.cpp

@ -71,6 +71,18 @@ bool ASTPrinter::visit(StructDefinition const& _node)
return goDeeper();
}
bool ASTPrinter::visit(EnumDefinition const& _node)
{
writeLine("EnumDefinition \"" + _node.getName() + "\"");
return goDeeper();
}
bool ASTPrinter::visit(EnumValue const& _node)
{
writeLine("EnumValue \"" + _node.getName() + "\"");
return goDeeper();
}
bool ASTPrinter::visit(ParameterList const& _node)
{
writeLine("ParameterList");
@ -347,6 +359,16 @@ void ASTPrinter::endVisit(StructDefinition const&)
m_indentation--;
}
void ASTPrinter::endVisit(EnumDefinition const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(EnumValue const&)
{
m_indentation--;
}
void ASTPrinter::endVisit(ParameterList const&)
{
m_indentation--;

4
libsolidity/ASTPrinter.h

@ -46,6 +46,8 @@ public:
bool visit(ContractDefinition const& _node) override;
bool visit(InheritanceSpecifier const& _node) override;
bool visit(StructDefinition const& _node) override;
bool visit(EnumDefinition const& _node) override;
bool visit(EnumValue const& _node) override;
bool visit(ParameterList const& _node) override;
bool visit(FunctionDefinition const& _node) override;
bool visit(VariableDeclaration const& _node) override;
@ -85,6 +87,8 @@ public:
void endVisit(ContractDefinition const&) override;
void endVisit(InheritanceSpecifier const&) override;
void endVisit(StructDefinition const&) override;
void endVisit(EnumDefinition const&) override;
void endVisit(EnumValue const&) override;
void endVisit(ParameterList const&) override;
void endVisit(FunctionDefinition const&) override;
void endVisit(VariableDeclaration const&) override;

8
libsolidity/ASTVisitor.h

@ -47,6 +47,8 @@ public:
virtual bool visit(ContractDefinition&) { return true; }
virtual bool visit(InheritanceSpecifier&) { return true; }
virtual bool visit(StructDefinition&) { return true; }
virtual bool visit(EnumDefinition&) { return true; }
virtual bool visit(EnumValue&) { return true; }
virtual bool visit(ParameterList&) { return true; }
virtual bool visit(FunctionDefinition&) { return true; }
virtual bool visit(VariableDeclaration&) { return true; }
@ -88,6 +90,8 @@ public:
virtual void endVisit(ContractDefinition&) { }
virtual void endVisit(InheritanceSpecifier&) { }
virtual void endVisit(StructDefinition&) { }
virtual void endVisit(EnumDefinition&) { }
virtual void endVisit(EnumValue&) { }
virtual void endVisit(ParameterList&) { }
virtual void endVisit(FunctionDefinition&) { }
virtual void endVisit(VariableDeclaration&) { }
@ -133,6 +137,8 @@ public:
virtual bool visit(ContractDefinition const&) { return true; }
virtual bool visit(InheritanceSpecifier const&) { return true; }
virtual bool visit(StructDefinition const&) { return true; }
virtual bool visit(EnumDefinition const&) { return true; }
virtual bool visit(EnumValue const&) { return true; }
virtual bool visit(ParameterList const&) { return true; }
virtual bool visit(FunctionDefinition const&) { return true; }
virtual bool visit(VariableDeclaration const&) { return true; }
@ -174,6 +180,8 @@ public:
virtual void endVisit(ContractDefinition const&) { }
virtual void endVisit(InheritanceSpecifier const&) { }
virtual void endVisit(StructDefinition const&) { }
virtual void endVisit(EnumDefinition const&) { }
virtual void endVisit(EnumValue const&) { }
virtual void endVisit(ParameterList const&) { }
virtual void endVisit(FunctionDefinition const&) { }
virtual void endVisit(VariableDeclaration const&) { }

28
libsolidity/AST_accept.h

@ -63,6 +63,7 @@ void ContractDefinition::accept(ASTVisitor& _visitor)
{
listAccept(m_baseContracts, _visitor);
listAccept(m_definedStructs, _visitor);
listAccept(m_definedEnums, _visitor);
listAccept(m_stateVariables, _visitor);
listAccept(m_events, _visitor);
listAccept(m_functionModifiers, _visitor);
@ -77,6 +78,7 @@ void ContractDefinition::accept(ASTConstVisitor& _visitor) const
{
listAccept(m_baseContracts, _visitor);
listAccept(m_definedStructs, _visitor);
listAccept(m_definedEnums, _visitor);
listAccept(m_stateVariables, _visitor);
listAccept(m_events, _visitor);
listAccept(m_functionModifiers, _visitor);
@ -105,6 +107,32 @@ void InheritanceSpecifier::accept(ASTConstVisitor& _visitor) const
_visitor.endVisit(*this);
}
void EnumDefinition::accept(ASTVisitor& _visitor)
{
if (_visitor.visit(*this))
listAccept(m_members, _visitor);
_visitor.endVisit(*this);
}
void EnumDefinition::accept(ASTConstVisitor& _visitor) const
{
if (_visitor.visit(*this))
listAccept(m_members, _visitor);
_visitor.endVisit(*this);
}
void EnumValue::accept(ASTVisitor& _visitor)
{
_visitor.visit(*this);
_visitor.endVisit(*this);
}
void EnumValue::accept(ASTConstVisitor& _visitor) const
{
_visitor.visit(*this);
_visitor.endVisit(*this);
}
void StructDefinition::accept(ASTVisitor& _visitor)
{
if (_visitor.visit(*this))

4
libsolidity/CMakeLists.txt

@ -11,9 +11,9 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -DSTATICLIB")
aux_source_directory(. SRC_LIST)
include_directories(BEFORE ${JSONCPP_INCLUDE_DIRS})
include_directories(BEFORE ..)
include_directories(${Boost_INCLUDE_DIRS})
include_directories(${JSONCPP_INCLUDE_DIRS})
include_directories(..)
set(EXECUTABLE solidity)

60
libsolidity/Compiler.cpp

@ -147,7 +147,7 @@ void Compiler::appendFunctionSelector(ContractDefinition const& _contract)
// retrieve the function signature hash from the calldata
if (!interfaceFunctions.empty())
CompilerUtils(m_context).loadFromMemory(0, 4, false, true);
CompilerUtils(m_context).loadFromMemory(0, IntegerType(CompilerUtils::dataStartOffset * 8), true);
// stack now is: 1 0 <funhash>
for (auto const& it: interfaceFunctions)
@ -178,23 +178,46 @@ void Compiler::appendFunctionSelector(ContractDefinition const& _contract)
}
}
unsigned Compiler::appendCalldataUnpacker(TypePointers const& _typeParameters, bool _fromMemory)
void Compiler::appendCalldataUnpacker(TypePointers const& _typeParameters, bool _fromMemory)
{
// We do not check the calldata size, everything is zero-padded.
unsigned dataOffset = CompilerUtils::dataStartOffset; // the 4 bytes of the function hash signature
//@todo this can be done more efficiently, saving some CALLDATALOAD calls
unsigned offset(CompilerUtils::dataStartOffset);
bool const c_padToWords = true;
unsigned dynamicParameterCount = 0;
for (TypePointer const& type: _typeParameters)
{
unsigned const c_numBytes = type->getCalldataEncodedSize();
if (c_numBytes > 32)
BOOST_THROW_EXCEPTION(CompilerError()
<< errinfo_comment("Type " + type->toString() + " not yet supported."));
bool const c_leftAligned = type->getCategory() == Type::Category::String;
bool const c_padToWords = true;
dataOffset += CompilerUtils(m_context).loadFromMemory(dataOffset, c_numBytes, c_leftAligned,
!_fromMemory, c_padToWords);
}
return dataOffset;
if (type->isDynamicallySized())
dynamicParameterCount++;
offset += dynamicParameterCount * 32;
unsigned currentDynamicParameter = 0;
for (TypePointer const& type: _typeParameters)
if (type->isDynamicallySized())
{
// value on stack: [calldata_offset] (only if we are already in dynamic mode)
if (currentDynamicParameter == 0)
// switch from static to dynamic
m_context << u256(offset);
// retrieve length
CompilerUtils(m_context).loadFromMemory(
CompilerUtils::dataStartOffset + currentDynamicParameter * 32,
IntegerType(256), !_fromMemory, c_padToWords);
// stack: offset length
// add 32-byte padding to copy of length
m_context << u256(32) << eth::Instruction::DUP1 << u256(31)
<< eth::Instruction::DUP4 << eth::Instruction::ADD
<< eth::Instruction::DIV << eth::Instruction::MUL;
// stack: offset length padded_length
m_context << eth::Instruction::DUP3 << eth::Instruction::ADD;
currentDynamicParameter++;
// stack: offset length next_calldata_offset
}
else if (currentDynamicParameter == 0)
// we can still use static load
offset += CompilerUtils(m_context).loadFromMemory(offset, *type, !_fromMemory, c_padToWords);
else
CompilerUtils(m_context).loadFromMemoryDynamic(*type, !_fromMemory, c_padToWords);
if (dynamicParameterCount > 0)
m_context << eth::Instruction::POP;
}
void Compiler::appendReturnValuePacker(TypePointers const& _typeParameters)
@ -207,15 +230,10 @@ void Compiler::appendReturnValuePacker(TypePointers const& _typeParameters)
for (TypePointer const& type: _typeParameters)
{
unsigned numBytes = type->getCalldataEncodedSize();
if (numBytes > 32)
BOOST_THROW_EXCEPTION(CompilerError()
<< errinfo_comment("Type " + type->toString() + " not yet supported."));
CompilerUtils(m_context).copyToStackTop(stackDepth, *type);
ExpressionCompiler::appendTypeConversion(m_context, *type, *type, true);
bool const c_leftAligned = type->getCategory() == Type::Category::String;
bool const c_padToWords = true;
dataOffset += CompilerUtils(m_context).storeInMemory(dataOffset, numBytes, c_leftAligned, c_padToWords);
dataOffset += CompilerUtils(m_context).storeInMemory(dataOffset, *type, c_padToWords);
stackDepth -= type->getSizeOnStack();
}
// note that the stack is not cleaned up here

6
libsolidity/Compiler.h

@ -20,6 +20,8 @@
* Solidity AST to EVM bytecode compiler.
*/
#pragma once
#include <ostream>
#include <functional>
#include <libsolidity/ASTVisitor.h>
@ -52,8 +54,8 @@ private:
void appendConstructorCall(FunctionDefinition const& _constructor);
void appendFunctionSelector(ContractDefinition const& _contract);
/// Creates code that unpacks the arguments for the given function represented by a vector of TypePointers.
/// From memory if @a _fromMemory is true, otherwise from call data. @returns the size of the data in bytes.
unsigned appendCalldataUnpacker(TypePointers const& _typeParameters, bool _fromMemory = false);
/// From memory if @a _fromMemory is true, otherwise from call data.
void appendCalldataUnpacker(TypePointers const& _typeParameters, bool _fromMemory = false);
void appendReturnValuePacker(TypePointers const& _typeParameters);
void registerStateVariables(ContractDefinition const& _contract);

2
libsolidity/CompilerContext.cpp

@ -76,7 +76,7 @@ bytes const& CompilerContext::getCompiledContract(const ContractDefinition& _con
bool CompilerContext::isLocalVariable(Declaration const* _declaration) const
{
return m_localVariables.count(_declaration);
return !!m_localVariables.count(_declaration);
}
eth::AssemblyItem CompilerContext::getFunctionEntryLabel(Declaration const& _declaration)

5
libsolidity/CompilerStack.cpp

@ -333,6 +333,11 @@ void CompilerStack::resolveImports()
swap(m_sourceOrder, sourceOrder);
}
std::string CompilerStack::defaultContractName() const
{
return getContract("").contract->getName();
}
CompilerStack::Contract const& CompilerStack::getContract(string const& _contractName) const
{
if (m_contracts.empty())

1
libsolidity/CompilerStack.h

@ -73,6 +73,7 @@ public:
void parse(std::string const& _sourceCode);
/// Returns a list of the contract names in the sources.
std::vector<std::string> getContractNames() const;
std::string defaultContractName() const;
/// Compiles the source units that were previously added and parsed.
void compile(bool _optimize = false);

332
libsolidity/CompilerUtils.cpp

@ -33,49 +33,92 @@ namespace solidity
const unsigned int CompilerUtils::dataStartOffset = 4;
unsigned CompilerUtils::loadFromMemory(unsigned _offset, unsigned _bytes, bool _leftAligned,
bool _fromCalldata, bool _padToWordBoundaries)
unsigned CompilerUtils::loadFromMemory(unsigned _offset, Type const& _type,
bool _fromCalldata, bool _padToWordBoundaries)
{
if (_bytes == 0)
{
m_context << u256(0);
return 0;
}
eth::Instruction load = _fromCalldata ? eth::Instruction::CALLDATALOAD : eth::Instruction::MLOAD;
solAssert(_bytes <= 32, "Memory load of more than 32 bytes requested.");
if (_bytes == 32 || _padToWordBoundaries)
{
m_context << u256(_offset) << load;
return 32;
}
else
{
// load data and add leading or trailing zeros by dividing/multiplying depending on alignment
u256 shiftFactor = u256(1) << ((32 - _bytes) * 8);
m_context << shiftFactor;
if (_leftAligned)
m_context << eth::Instruction::DUP1;
m_context << u256(_offset) << load << eth::Instruction::DIV;
if (_leftAligned)
m_context << eth::Instruction::MUL;
return _bytes;
}
solAssert(_type.getCategory() != Type::Category::ByteArray, "Unable to statically load dynamic type.");
m_context << u256(_offset);
return loadFromMemoryHelper(_type, _fromCalldata, _padToWordBoundaries);
}
unsigned CompilerUtils::storeInMemory(unsigned _offset, unsigned _bytes, bool _leftAligned,
bool _padToWordBoundaries)
void CompilerUtils::loadFromMemoryDynamic(Type const& _type, bool _fromCalldata, bool _padToWordBoundaries)
{
if (_bytes == 0)
solAssert(_type.getCategory() != Type::Category::ByteArray, "Byte arrays not yet implemented.");
m_context << eth::Instruction::DUP1;
unsigned numBytes = loadFromMemoryHelper(_type, _fromCalldata, _padToWordBoundaries);
// update memory counter
for (unsigned i = 0; i < _type.getSizeOnStack(); ++i)
m_context << eth::swapInstruction(1 + i);
m_context << u256(numBytes) << eth::Instruction::ADD;
}
unsigned CompilerUtils::storeInMemory(unsigned _offset, Type const& _type, bool _padToWordBoundaries)
{
solAssert(_type.getCategory() != Type::Category::ByteArray, "Unable to statically store dynamic type.");
unsigned numBytes = prepareMemoryStore(_type, _padToWordBoundaries);
if (numBytes > 0)
m_context << u256(_offset) << eth::Instruction::MSTORE;
return numBytes;
}
void CompilerUtils::storeInMemoryDynamic(Type const& _type, bool _padToWordBoundaries)
{
if (_type.getCategory() == Type::Category::ByteArray)
{
m_context << eth::Instruction::POP;
return 0;
auto const& type = dynamic_cast<ByteArrayType const&>(_type);
if (type.getLocation() == ByteArrayType::Location::CallData)
{
// stack: target source_offset source_len
m_context << eth::Instruction::DUP1 << eth::Instruction::DUP3 << eth::Instruction::DUP5
// stack: target source_offset source_len source_len source_offset target
<< eth::Instruction::CALLDATACOPY
<< eth::Instruction::DUP3 << eth::Instruction::ADD
<< eth::Instruction::SWAP2 << eth::Instruction::POP << eth::Instruction::POP;
}
else
{
solAssert(type.getLocation() == ByteArrayType::Location::Storage, "Memory byte arrays not yet implemented.");
m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD;
// stack here: memory_offset storage_offset length_bytes
// jump to end if length is zero
m_context << eth::Instruction::DUP1 << eth::Instruction::ISZERO;
eth::AssemblyItem loopEnd = m_context.newTag();
m_context.appendConditionalJumpTo(loopEnd);
// compute memory end offset
m_context << eth::Instruction::DUP3 << eth::Instruction::ADD << eth::Instruction::SWAP2;
// actual array data is stored at SHA3(storage_offset)
m_context << eth::Instruction::SWAP1;
CompilerUtils(m_context).computeHashStatic();
m_context << eth::Instruction::SWAP1;
// stack here: memory_end_offset storage_data_offset memory_offset
eth::AssemblyItem loopStart = m_context.newTag();
m_context << loopStart
// load and store
<< eth::Instruction::DUP2 << eth::Instruction::SLOAD
<< eth::Instruction::DUP2 << eth::Instruction::MSTORE
// increment storage_data_offset by 1
<< eth::Instruction::SWAP1 << u256(1) << eth::Instruction::ADD
// increment memory offset by 32
<< eth::Instruction::SWAP1 << u256(32) << eth::Instruction::ADD
// check for loop condition
<< eth::Instruction::DUP1 << eth::Instruction::DUP4 << eth::Instruction::GT;
m_context.appendConditionalJumpTo(loopStart);
m_context << loopEnd << eth::Instruction::POP << eth::Instruction::POP;
}
}
else
{
unsigned numBytes = prepareMemoryStore(_type, _padToWordBoundaries);
if (numBytes > 0)
{
solAssert(_type.getSizeOnStack() == 1, "Memory store of types with stack size != 1 not implemented.");
m_context << eth::Instruction::DUP2 << eth::Instruction::MSTORE;
m_context << u256(numBytes) << eth::Instruction::ADD;
}
}
solAssert(_bytes <= 32, "Memory store of more than 32 bytes requested.");
if (_bytes != 32 && !_leftAligned && !_padToWordBoundaries)
// shift the value accordingly before storing
m_context << (u256(1) << ((32 - _bytes) * 8)) << eth::Instruction::MUL;
m_context << u256(_offset) << eth::Instruction::MSTORE;
return _padToWordBoundaries ? 32 : _bytes;
}
void CompilerUtils::moveToStackVariable(VariableDeclaration const& _variable)
@ -114,5 +157,220 @@ unsigned CompilerUtils::getSizeOnStack(vector<shared_ptr<Type const>> const& _va
return size;
}
void CompilerUtils::computeHashStatic(Type const& _type, bool _padToWordBoundaries)
{
unsigned length = storeInMemory(0, _type, _padToWordBoundaries);
m_context << u256(length) << u256(0) << eth::Instruction::SHA3;
}
void CompilerUtils::copyByteArrayToStorage(ByteArrayType const& _targetType,
ByteArrayType const& _sourceType) const
{
// stack layout: [source_ref] target_ref (top)
// need to leave target_ref on the stack at the end
solAssert(_targetType.getLocation() == ByteArrayType::Location::Storage, "");
switch (_sourceType.getLocation())
{
case ByteArrayType::Location::CallData:
{
// This also assumes that after "length" we only have zeros, i.e. it cannot be used to
// slice a byte array from calldata.
// stack: source_offset source_len target_ref
// fetch old length and convert to words
m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD;
m_context << u256(31) << eth::Instruction::ADD
<< u256(32) << eth::Instruction::SWAP1 << eth::Instruction::DIV;
// stack here: source_offset source_len target_ref target_length_words
// actual array data is stored at SHA3(storage_offset)
m_context << eth::Instruction::DUP2;
CompilerUtils(m_context).computeHashStatic();
// compute target_data_end
m_context << eth::Instruction::DUP1 << eth::Instruction::SWAP2 << eth::Instruction::ADD
<< eth::Instruction::SWAP1;
// stack here: source_offset source_len target_ref target_data_end target_data_ref
// store length (in bytes)
m_context << eth::Instruction::DUP4 << eth::Instruction::DUP1 << eth::Instruction::DUP5
<< eth::Instruction::SSTORE;
// jump to end if length is zero
m_context << eth::Instruction::ISZERO;
eth::AssemblyItem copyLoopEnd = m_context.newTag();
m_context.appendConditionalJumpTo(copyLoopEnd);
// store start offset
m_context << eth::Instruction::DUP5;
// stack now: source_offset source_len target_ref target_data_end target_data_ref calldata_offset
eth::AssemblyItem copyLoopStart = m_context.newTag();
m_context << copyLoopStart
// copy from calldata and store
<< eth::Instruction::DUP1 << eth::Instruction::CALLDATALOAD
<< eth::Instruction::DUP3 << eth::Instruction::SSTORE
// increment target_data_ref by 1
<< eth::Instruction::SWAP1 << u256(1) << eth::Instruction::ADD
// increment calldata_offset by 32
<< eth::Instruction::SWAP1 << u256(32) << eth::Instruction::ADD
// check for loop condition
<< eth::Instruction::DUP1 << eth::Instruction::DUP6 << eth::Instruction::GT;
m_context.appendConditionalJumpTo(copyLoopStart);
m_context << eth::Instruction::POP;
m_context << copyLoopEnd;
// now clear leftover bytes of the old value
// stack now: source_offset source_len target_ref target_data_end target_data_ref
clearStorageLoop();
// stack now: source_offset source_len target_ref target_data_end
m_context << eth::Instruction::POP << eth::Instruction::SWAP2
<< eth::Instruction::POP << eth::Instruction::POP;
break;
}
case ByteArrayType::Location::Storage:
{
// this copies source to target and also clears target if it was larger
// stack: source_ref target_ref
// store target_ref
m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2;
// fetch lengthes
m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD << eth::Instruction::SWAP2
<< eth::Instruction::DUP1 << eth::Instruction::SLOAD;
// stack: target_ref target_len_bytes target_ref source_ref source_len_bytes
// store new target length
m_context << eth::Instruction::DUP1 << eth::Instruction::DUP4 << eth::Instruction::SSTORE;
// compute hashes (data positions)
m_context << eth::Instruction::SWAP2;
CompilerUtils(m_context).computeHashStatic();
m_context << eth::Instruction::SWAP1;
CompilerUtils(m_context).computeHashStatic();
// stack: target_ref target_len_bytes source_len_bytes target_data_pos source_data_pos
// convert lengthes from bytes to storage slots
m_context << u256(31) << u256(32) << eth::Instruction::DUP1 << eth::Instruction::DUP3
<< eth::Instruction::DUP8 << eth::Instruction::ADD << eth::Instruction::DIV
<< eth::Instruction::SWAP2
<< eth::Instruction::DUP6 << eth::Instruction::ADD << eth::Instruction::DIV;
// stack: target_ref target_len_bytes source_len_bytes target_data_pos source_data_pos target_len source_len
// @todo we might be able to go without a third counter
m_context << u256(0);
// stack: target_ref target_len_bytes source_len_bytes target_data_pos source_data_pos target_len source_len counter
eth::AssemblyItem copyLoopStart = m_context.newTag();
m_context << copyLoopStart;
// check for loop condition
m_context << eth::Instruction::DUP1 << eth::Instruction::DUP3
<< eth::Instruction::GT << eth::Instruction::ISZERO;
eth::AssemblyItem copyLoopEnd = m_context.newTag();
m_context.appendConditionalJumpTo(copyLoopEnd);
// copy
m_context << eth::Instruction::DUP4 << eth::Instruction::DUP2 << eth::Instruction::ADD
<< eth::Instruction::SLOAD
<< eth::Instruction::DUP6 << eth::Instruction::DUP3 << eth::Instruction::ADD
<< eth::Instruction::SSTORE;
// increment
m_context << u256(1) << eth::Instruction::ADD;
m_context.appendJumpTo(copyLoopStart);
m_context << copyLoopEnd;
// zero-out leftovers in target
// stack: target_ref target_len_bytes source_len_bytes target_data_pos source_data_pos target_len source_len counter
// add counter to target_data_pos
m_context << eth::Instruction::DUP5 << eth::Instruction::ADD
<< eth::Instruction::SWAP5 << eth::Instruction::POP;
// stack: target_ref target_len_bytes target_data_pos_updated target_data_pos source_data_pos target_len source_len
// add length to target_data_pos to get target_data_end
m_context << eth::Instruction::POP << eth::Instruction::DUP3 << eth::Instruction::ADD
<< eth::Instruction::SWAP4
<< eth::Instruction::POP << eth::Instruction::POP << eth::Instruction::POP;
// stack: target_ref target_data_end target_data_pos_updated
clearStorageLoop();
m_context << eth::Instruction::POP;
break;
}
default:
solAssert(false, "Given byte array location not implemented.");
}
}
unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWordBoundaries)
{
unsigned _encodedSize = _type.getCalldataEncodedSize();
unsigned numBytes = _padToWordBoundaries ? getPaddedSize(_encodedSize) : _encodedSize;
bool leftAligned = _type.getCategory() == Type::Category::String;
if (numBytes == 0)
m_context << eth::Instruction::POP << u256(0);
else
{
solAssert(numBytes <= 32, "Static memory load of more than 32 bytes requested.");
m_context << (_fromCalldata ? eth::Instruction::CALLDATALOAD : eth::Instruction::MLOAD);
if (numBytes != 32)
{
// add leading or trailing zeros by dividing/multiplying depending on alignment
u256 shiftFactor = u256(1) << ((32 - numBytes) * 8);
m_context << shiftFactor << eth::Instruction::SWAP1 << eth::Instruction::DIV;
if (leftAligned)
m_context << shiftFactor << eth::Instruction::MUL;
}
}
return numBytes;
}
void CompilerUtils::clearByteArray(ByteArrayType const& _type) const
{
solAssert(_type.getLocation() == ByteArrayType::Location::Storage, "");
// fetch length
m_context << eth::Instruction::DUP1 << eth::Instruction::SLOAD;
// set length to zero
m_context << u256(0) << eth::Instruction::DUP3 << eth::Instruction::SSTORE;
// convert length from bytes to storage slots
m_context << u256(31) << eth::Instruction::ADD
<< u256(32) << eth::Instruction::SWAP1 << eth::Instruction::DIV;
// compute data positions
m_context << eth::Instruction::SWAP1;
CompilerUtils(m_context).computeHashStatic();
// stack: len data_pos
m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2 << eth::Instruction::ADD
<< eth::Instruction::SWAP1;
clearStorageLoop();
// cleanup
m_context << eth::Instruction::POP;
}
unsigned CompilerUtils::prepareMemoryStore(Type const& _type, bool _padToWordBoundaries) const
{
unsigned _encodedSize = _type.getCalldataEncodedSize();
unsigned numBytes = _padToWordBoundaries ? getPaddedSize(_encodedSize) : _encodedSize;
bool leftAligned = _type.getCategory() == Type::Category::String;
if (numBytes == 0)
m_context << eth::Instruction::POP;
else
{
solAssert(numBytes <= 32, "Memory store of more than 32 bytes requested.");
if (numBytes != 32 && !leftAligned && !_padToWordBoundaries)
// shift the value accordingly before storing
m_context << (u256(1) << ((32 - numBytes) * 8)) << eth::Instruction::MUL;
}
return numBytes;
}
void CompilerUtils::clearStorageLoop() const
{
// stack: end_pos pos
eth::AssemblyItem loopStart = m_context.newTag();
m_context << loopStart;
// check for loop condition
m_context << eth::Instruction::DUP1 << eth::Instruction::DUP3
<< eth::Instruction::GT << eth::Instruction::ISZERO;
eth::AssemblyItem zeroLoopEnd = m_context.newTag();
m_context.appendConditionalJumpTo(zeroLoopEnd);
// zero out
m_context << u256(0) << eth::Instruction::DUP2 << eth::Instruction::SSTORE;
// increment
m_context << u256(1) << eth::Instruction::ADD;
m_context.appendJumpTo(loopStart);
// cleanup
m_context << zeroLoopEnd;
m_context << eth::Instruction::POP;
}
}
}

49
libsolidity/CompilerUtils.h

@ -37,23 +37,28 @@ public:
/// Loads data from memory to the stack.
/// @param _offset offset in memory (or calldata)
/// @param _bytes number of bytes to load
/// @param _leftAligned if true, store left aligned on stack (otherwise right aligned)
/// @param _type data type to load
/// @param _fromCalldata if true, load from calldata, not from memory
/// @param _padToWordBoundaries if true, assume the data is padded to word (32 byte) boundaries
/// @returns the number of bytes consumed in memory (can be different from _bytes if
/// _padToWordBoundaries is true)
unsigned loadFromMemory(unsigned _offset, unsigned _bytes = 32, bool _leftAligned = false,
bool _fromCalldata = false, bool _padToWordBoundaries = false);
/// @returns the number of bytes consumed in memory.
unsigned loadFromMemory(unsigned _offset, Type const& _type = IntegerType(256),
bool _fromCalldata = false, bool _padToWordBoundaries = false);
/// Dynamic version of @see loadFromMemory, expects the memory offset on the stack.
/// Stack pre: memory_offset
/// Stack post: value... (memory_offset+length)
void loadFromMemoryDynamic(Type const& _type, bool _fromCalldata = false, bool _padToWordBoundaries = true);
/// Stores data from stack in memory.
/// @param _offset offset in memory
/// @param _bytes number of bytes to store
/// @param _leftAligned if true, data is left aligned on stack (otherwise right aligned)
/// @param _type type of the data on the stack
/// @param _padToWordBoundaries if true, pad the data to word (32 byte) boundaries
/// @returns the number of bytes written to memory (can be different from _bytes if
/// _padToWordBoundaries is true)
unsigned storeInMemory(unsigned _offset, unsigned _bytes = 32, bool _leftAligned = false,
bool _padToWordBoundaries = false);
unsigned storeInMemory(unsigned _offset, Type const& _type = IntegerType(256), bool _padToWordBoundaries = false);
/// Dynamic version of @see storeInMemory, expects the memory offset below the value on the stack
/// and also updates that.
/// Stack pre: memory_offset value...
/// Stack post: (memory_offset+length)
void storeInMemoryDynamic(Type const& _type, bool _padToWordBoundaries = true);
/// @returns _size rounded up to the next multiple of 32 (the number of bytes occupied in the
/// padded calldata)
static unsigned getPaddedSize(unsigned _size) { return ((_size + 31) / 32) * 32; }
@ -69,10 +74,34 @@ public:
static unsigned getSizeOnStack(std::vector<T> const& _variables);
static unsigned getSizeOnStack(std::vector<std::shared_ptr<Type const>> const& _variableTypes);
/// Appends code that computes tha SHA3 hash of the topmost stack element of type @a _type.
/// If @a _pad is set, padds the type to muliples of 32 bytes.
/// @note Only works for types of fixed size.
void computeHashStatic(Type const& _type = IntegerType(256), bool _padToWordBoundaries = false);
/// Copies a byte array to a byte array in storage.
/// Stack pre: [source_reference] target_reference
/// Stack post: target_reference
void copyByteArrayToStorage(ByteArrayType const& _targetType, ByteArrayType const& _sourceType) const;
/// Clears the length and data elements of the byte array referenced on the stack.
/// Stack pre: reference
/// Stack post:
void clearByteArray(ByteArrayType const& _type) const;
/// Bytes we need to the start of call data.
/// - The size in bytes of the function (hash) identifier.
static const unsigned int dataStartOffset;
private:
/// Prepares the given type for storing in memory by shifting it if necessary.
unsigned prepareMemoryStore(Type const& _type, bool _padToWordBoundaries) const;
/// Loads type from memory assuming memory offset is on stack top.
unsigned loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWordBoundaries);
/// Appends a loop that clears a sequence of storage slots (excluding end).
/// Stack pre: end_ref start_ref
/// Stack post: end_ref
void clearStorageLoop() const;
CompilerContext& m_context;
};

13
libsolidity/DeclarationContainer.cpp

@ -28,14 +28,19 @@ namespace dev
namespace solidity
{
bool DeclarationContainer::registerDeclaration(Declaration const& _declaration, bool _update)
bool DeclarationContainer::registerDeclaration(Declaration const& _declaration, bool _invisible, bool _update)
{
if (_declaration.getName().empty())
ASTString const& name(_declaration.getName());
if (name.empty())
return true;
if (!_update && m_declarations.find(_declaration.getName()) != m_declarations.end())
if (!_update && (m_declarations.count(name) || m_invisibleDeclarations.count(name)))
return false;
m_declarations[_declaration.getName()] = &_declaration;
if (_invisible)
m_invisibleDeclarations.insert(name);
else
m_declarations[name] = &_declaration;
return true;
}

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

Loading…
Cancel
Save