Browse Source

Merge branch 'develop' of https://github.com/ethereum/cpp-ethereum into develop

cl-refactor
winsvega 10 years ago
parent
commit
a6aea27c9d
  1. 8
      CMakeLists.txt
  2. 2
      CodingStandards.txt
  3. 8
      alethzero/CMakeLists.txt
  4. 81
      alethzero/MainWin.cpp
  5. 2
      alethzero/MainWin.h
  6. 2
      alethzero/NatspecHandler.h
  7. 3
      cmake/FindJsoncpp.cmake
  8. 17
      cmake/scripts/jsonrpcstub.cmake
  9. 1
      eth/CMakeLists.txt
  10. 104
      eth/main.cpp
  11. 7
      libdevcore/CMakeLists.txt
  12. 6
      libdevcore/CommonData.cpp
  13. 9
      libdevcore/CommonData.h
  14. 22
      libdevcore/CommonIO.h
  15. 7
      libethereum/Client.cpp
  16. 2
      libethereum/Client.h
  17. 25
      libethereum/EthereumHost.cpp
  18. 2
      libethereum/State.cpp
  19. 55
      libp2p/Common.h
  20. 733
      libp2p/Host.cpp
  21. 192
      libp2p/Host.h
  22. 14
      libp2p/HostCapability.cpp
  23. 3
      libp2p/HostCapability.h
  24. 339
      libp2p/NodeTable.cpp
  25. 302
      libp2p/NodeTable.h
  26. 83
      libp2p/Peer.cpp
  27. 97
      libp2p/Peer.h
  28. 216
      libp2p/Session.cpp
  29. 20
      libp2p/Session.h
  30. 35
      libp2p/UDP.cpp
  31. 14
      libp2p/UDP.h
  32. 2
      libp2p/UPnP.cpp
  33. 3
      libsolidity/AST.cpp
  34. 2
      libsolidity/ASTJsonConverter.h
  35. 7
      libsolidity/Compiler.cpp
  36. 2
      libsolidity/CompilerContext.cpp
  37. 247
      libsolidity/CompilerUtils.cpp
  38. 33
      libsolidity/CompilerUtils.h
  39. 209
      libsolidity/ExpressionCompiler.cpp
  40. 34
      libsolidity/ExpressionCompiler.h
  41. 2
      libsolidity/InterfaceHandler.h
  42. 6
      libsolidity/Token.h
  43. 66
      libsolidity/Types.cpp
  44. 34
      libsolidity/Types.h
  45. 5
      libweb3jsonrpc/CMakeLists.txt
  46. 23
      libwebthree/WebThree.cpp
  47. 23
      libwebthree/WebThree.h
  48. 16
      libwhisper/WhisperHost.cpp
  49. 1
      lllc/CMakeLists.txt
  50. 4
      mix/AppContext.cpp
  51. 1
      mix/CMakeLists.txt
  52. 3
      mix/ClientModel.cpp
  53. 8
      mix/CodeModel.cpp
  54. 6
      mix/CodeModel.h
  55. 6
      mix/FileIo.cpp
  56. 12
      mix/HttpServer.cpp
  57. 4
      mix/HttpServer.h
  58. 4
      mix/QEther.cpp
  59. 23
      mix/qml/CodeEditorView.qml
  60. 9
      mix/qml/CommonSeparator.qml
  61. 2
      mix/qml/Debugger.qml
  62. 17
      mix/qml/DefaultLabel.qml
  63. 13
      mix/qml/DefaultTextField.qml
  64. 13
      mix/qml/Ether.qml
  65. 6
      mix/qml/FilesSection.qml
  66. 2
      mix/qml/MainContent.qml
  67. 6
      mix/qml/NewProjectDialog.qml
  68. 4
      mix/qml/ProjectList.qml
  69. 19
      mix/qml/ProjectModel.qml
  70. 10
      mix/qml/QHashTypeView.qml
  71. 10
      mix/qml/QIntTypeView.qml
  72. 10
      mix/qml/QStringTypeView.qml
  73. 6
      mix/qml/SourceSansProBold.qml
  74. 7
      mix/qml/SourceSansProLight.qml
  75. 8
      mix/qml/SourceSansProRegular.qml
  76. 247
      mix/qml/StateDialog.qml
  77. 17
      mix/qml/StateDialogStyle.qml
  78. 10
      mix/qml/StateListModel.qml
  79. 8
      mix/qml/StatusPane.qml
  80. 19
      mix/qml/Style.qml
  81. 467
      mix/qml/TransactionDialog.qml
  82. 10
      mix/qml/TransactionLog.qml
  83. 51
      mix/qml/WebPreview.qml
  84. 8
      mix/qml/html/WebContainer.html
  85. BIN
      mix/qml/img/delete_sign.png
  86. BIN
      mix/qml/img/edit.png
  87. BIN
      mix/qml/img/plus.png
  88. 100
      mix/qml/js/ProjectModel.js
  89. 9
      mix/qml/js/QEtherHelper.js
  90. 2
      mix/qml/js/TransactionHelper.js
  91. 11
      mix/qml/main.qml
  92. 2
      mix/qml/qmldir
  93. 11
      mix/res.qrc
  94. 88
      neth/main.cpp
  95. 1
      solc/CMakeLists.txt
  96. 2
      test/SolidityABIJSON.cpp
  97. 184
      test/SolidityEndToEndTest.cpp
  98. 2
      test/SolidityNatspecJSON.cpp
  99. 19
      test/net.cpp
  100. 78
      test/peer.cpp

8
CMakeLists.txt

@ -109,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}")
@ -140,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)
@ -166,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 ()

2
CodingStandards.txt

@ -93,7 +93,7 @@ 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".

8
alethzero/CMakeLists.txt

@ -11,6 +11,7 @@ set(CMAKE_INCLUDE_CURRENT_DIR ON)
aux_source_directory(. SRC_LIST)
include_directories(${JSON_RPC_CPP_INCLUDE_DIRS})
include_directories(${JSONCPP_INCLUDE_DIRS})
include_directories(..)
qt5_wrap_ui(ui_Main.h Main.ui)
@ -40,9 +41,6 @@ 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 +49,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})

81
alethzero/MainWin.cpp

@ -161,8 +161,11 @@ Main::Main(QWidget *parent) :
statusBar()->addPermanentWidget(ui->blockCount);
connect(ui->ourAccounts->model(), SIGNAL(rowsMoved(const QModelIndex &, int, int, const QModelIndex &, int)), SLOT(ourAccountsRowsMoved()));
m_webThree.reset(new WebThreeDirect(string("AlethZero/v") + dev::Version + "/" DEV_QUOTED(ETH_BUILD_TYPE) "/" DEV_QUOTED(ETH_BUILD_PLATFORM), getDataDir() + "/AlethZero", false, {"eth", "shh"}));
QSettings s("ethereum", "alethzero");
m_networkConfig = s.value("peers").toByteArray();
bytesConstRef network((byte*)m_networkConfig.data(), m_networkConfig.size());
m_webThree.reset(new WebThreeDirect(string("AlethZero/v") + dev::Version + "/" DEV_QUOTED(ETH_BUILD_TYPE) "/" DEV_QUOTED(ETH_BUILD_PLATFORM), getDataDir() + "/AlethZero", false, {"eth", "shh"}, p2p::NetworkPreferences(), network));
m_qwebConnector.reset(new QWebThreeConnector());
m_server.reset(new OurWebThreeStubServer(*m_qwebConnector, *web3(), keysAsVector(m_myKeys), this));
@ -210,11 +213,11 @@ Main::Main(QWidget *parent) :
Main::~Main()
{
writeSettings();
// Must do this here since otherwise m_ethereum'll be deleted (and therefore clearWatches() called by the destructor)
// *after* the client is dead.
m_qweb->clientDieing();
g_logPost = simpleDebugOut;
writeSettings();
}
void Main::on_newIdentity_triggered()
@ -569,7 +572,23 @@ Address Main::fromString(QString const& _n) const
}*/
if (_n.size() == 40)
return Address(fromHex(_n.toStdString()));
{
try
{
return Address(fromHex(_n.toStdString(), ThrowType::Throw));
}
catch (BadHexCharacter& _e)
{
cwarn << "invalid hex character, address rejected";
cwarn << boost::diagnostic_information(_e);
return Address();
}
catch (...)
{
cwarn << "address rejected";
return Address();
}
}
else
return Address();
}
@ -662,10 +681,10 @@ void Main::writeSettings()
s.setValue("verbosity", ui->verbosity->value());
s.setValue("jitvm", ui->jitvm->isChecked());
bytes d = m_webThree->saveNodes();
bytes d = m_webThree->saveNetwork();
if (d.size())
m_peers = QByteArray((char*)d.data(), (int)d.size());
s.setValue("peers", m_peers);
m_networkConfig = QByteArray((char*)d.data(), (int)d.size());
s.setValue("peers", m_networkConfig);
s.setValue("nameReg", ui->nameReg->text());
s.setValue("geometry", saveGeometry());
@ -714,7 +733,6 @@ void Main::readSettings(bool _skipGeometry)
}
}
m_peers = s.value("peers").toByteArray();
ui->upnp->setChecked(s.value("upnp", true).toBool());
ui->forceAddress->setText(s.value("forceAddress", "").toString());
ui->usePast->setChecked(s.value("usePast", true).toBool());
@ -946,31 +964,27 @@ void Main::refreshNetwork()
if (web3()->haveNetwork())
{
map<h512, QString> clients;
for (PeerInfo const& i: ps)
map<h512, QString> sessions;
for (PeerSessionInfo const& i: ps)
ui->peers->addItem(QString("[%8 %7] %3 ms - %1:%2 - %4 %5 %6")
.arg(QString::fromStdString(i.host))
.arg(i.port)
.arg(chrono::duration_cast<chrono::milliseconds>(i.lastPing).count())
.arg(clients[i.id] = QString::fromStdString(i.clientVersion))
.arg(sessions[i.id] = QString::fromStdString(i.clientVersion))
.arg(QString::fromStdString(toString(i.caps)))
.arg(QString::fromStdString(toString(i.notes)))
.arg(i.socket)
.arg(QString::fromStdString(i.id.abridged())));
auto ns = web3()->nodes();
for (p2p::Node const& i: ns)
if (!i.dead)
ui->nodes->insertItem(clients.count(i.id) ? 0 : ui->nodes->count(), QString("[%1 %3] %2 - ( =%5s | /%4s%6 ) - *%7 $%8")
.arg(QString::fromStdString(i.id.abridged()))
.arg(QString::fromStdString(toString(i.address)))
.arg(i.id == web3()->id() ? "self" : clients.count(i.id) ? clients[i.id] : i.secondsSinceLastAttempted() == -1 ? "session-fail" : i.secondsSinceLastAttempted() >= (int)i.fallbackSeconds() ? "retrying..." : "retry-" + QString::number(i.fallbackSeconds() - i.secondsSinceLastAttempted()) + "s")
.arg(i.secondsSinceLastAttempted())
.arg(i.secondsSinceLastConnected())
.arg(i.isOffline() ? " | " + QString::fromStdString(reasonOf(i.lastDisconnect)) + " | " + QString::number(i.failedAttempts) + "x" : "")
.arg(i.rating)
.arg((int)i.idOrigin)
);
for (p2p::Peer const& i: ns)
ui->nodes->insertItem(sessions.count(i.id) ? 0 : ui->nodes->count(), QString("[%1 %3] %2 - ( =%5s | /%4s%6 ) - *%7 $%8")
.arg(QString::fromStdString(i.id.abridged()))
.arg(QString::fromStdString(i.peerEndpoint().address().to_string()))
.arg(i.id == web3()->id() ? "self" : sessions.count(i.id) ? sessions[i.id] : "disconnected")
.arg(i.isOffline() ? " | " + QString::fromStdString(reasonOf(i.lastDisconnect())) + " | " + QString::number(i.failedAttempts()) + "x" : "")
.arg(i.rating())
);
}
}
@ -1327,8 +1341,20 @@ void Main::ourAccountsRowsMoved()
void Main::on_inject_triggered()
{
QString s = QInputDialog::getText(this, "Inject Transaction", "Enter transaction dump in hex");
bytes b = fromHex(s.toStdString());
ethereum()->inject(&b);
try
{
bytes b = fromHex(s.toStdString(), ThrowType::Throw);
ethereum()->inject(&b);
}
catch (BadHexCharacter& _e)
{
cwarn << "invalid hex character, transaction rejected";
cwarn << boost::diagnostic_information(_e);
}
catch (...)
{
cwarn << "transaction rejected";
}
}
void Main::on_blocks_currentItemChanged()
@ -1871,8 +1897,9 @@ void Main::on_net_triggered()
web3()->setIdealPeerCount(ui->idealPeers->value());
web3()->setNetworkPreferences(netPrefs());
ethereum()->setNetworkId(m_privateChain.size() ? sha3(m_privateChain.toStdString()) : 0);
if (m_peers.size()/* && ui->usePast->isChecked()*/)
web3()->restoreNodes(bytesConstRef((byte*)m_peers.data(), m_peers.size()));
// TODO: p2p
// if (m_networkConfig.size()/* && ui->usePast->isChecked()*/)
// web3()->restoreNetwork(bytesConstRef((byte*)m_networkConfig.data(), m_networkConfig.size()));
web3()->startNetwork();
ui->downloadView->setDownloadMan(ethereum()->downloadMan());
}

2
alethzero/MainWin.h

@ -251,7 +251,7 @@ 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;

2
alethzero/NatspecHandler.h

@ -26,7 +26,7 @@
#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>
namespace ldb = leveldb;

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

17
cmake/scripts/jsonrpcstub.cmake

@ -27,10 +27,19 @@ set(CLIENT_OUTFILE "${ETH_CLIENT_DIR}/${ETH_CLIENT_NAME_LOWER}.h")
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}
--cpp-client=${ETH_CLIENT_NAME} --cpp-client-file=${CLIENT_TMPFILE}
OUTPUT_VARIABLE ERR ERROR_QUIET
)
include("${ETH_SOURCE_DIR}/cmake/EthUtils.cmake")
replace_if_different("${SERVER_TMPFILE}" "${SERVER_OUTFILE}")
replace_if_different("${CLIENT_TMPFILE}" "${CLIENT_OUTFILE}")
# 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()

1
eth/CMakeLists.txt

@ -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;
}

7
libdevcore/CMakeLists.txt

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

7
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();

2
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();

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

2
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);

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::time_point<chrono::steady_clock>::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);
}

302
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(); }
/// 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); }
/// Returns the Node to the corresponding node id or the empty Node if that id is not found.
Node node(NodeId const& _id);
#ifndef BOOST_AUTO_TEST_SUITE
private:
#else
protected:
#endif
/// Constants for Kademlia, mostly derived from address space.
/// 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))) {}
h256 replyTo; // hash of rlp of PingNode
static const uint8_t type = 2;
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; }

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>;
}
}

216
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, std::chrono::steady_clock::duration(0), CapDescSet(), 0, map<string, string>()}),
m_ping(chrono::time_point<chrono::steady_clock>::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>()});
}
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;

3
libsolidity/AST.cpp

@ -403,7 +403,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())
if (m_leftHandSide->getType()->getCategory() != Type::Category::ByteArray &&
!m_leftHandSide->getType()->isValueType() && !m_leftHandSide->isLocalLValue())
BOOST_THROW_EXCEPTION(createTypeError("Assignment to non-local non-value lvalue."));
m_type = m_leftHandSide->getType();
if (m_assigmentOperator == Token::Assign)

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
{

7
libsolidity/Compiler.cpp

@ -207,15 +207,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

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)

247
libsolidity/CompilerUtils.cpp

@ -62,20 +62,59 @@ unsigned CompilerUtils::loadFromMemory(unsigned _offset, unsigned _bytes, bool _
}
}
unsigned CompilerUtils::storeInMemory(unsigned _offset, unsigned _bytes, bool _leftAligned,
bool _padToWordBoundaries)
unsigned CompilerUtils::storeInMemory(unsigned _offset, Type const& _type, bool _padToWordBoundaries)
{
if (_bytes == 0)
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);
solAssert(type.getLocation() == ByteArrayType::Location::Storage, "Non-storage 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)
{
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 +153,191 @@ 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:
{
// @todo this does not take length into account. It also assumes that after "CALLDATALENGTH" we only have zeros.
// 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: 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: target_ref target_data_end target_data_ref
// store length (in bytes)
m_context << eth::Instruction::CALLDATASIZE;
m_context << 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 << u256(0);
// stack now: 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::CALLDATASIZE << 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: target_ref target_data_end target_data_ref
clearStorageLoop();
m_context << 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.");
}
}
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;
}
}
}

33
libsolidity/CompilerUtils.h

@ -47,13 +47,16 @@ public:
bool _fromCalldata = false, bool _padToWordBoundaries = false);
/// 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 +72,32 @@ 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;
/// 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;
};

209
libsolidity/ExpressionCompiler.cpp

@ -59,13 +59,15 @@ void ExpressionCompiler::appendStateVariableAccessor(CompilerContext& _context,
bool ExpressionCompiler::visit(Assignment const& _assignment)
{
_assignment.getRightHandSide().accept(*this);
appendTypeConversion(*_assignment.getRightHandSide().getType(), *_assignment.getType());
if (_assignment.getType()->isValueType())
appendTypeConversion(*_assignment.getRightHandSide().getType(), *_assignment.getType());
_assignment.getLeftHandSide().accept(*this);
solAssert(m_currentLValue.isValid(), "LValue not retrieved.");
Token::Value op = _assignment.getAssignmentOperator();
if (op != Token::Assign) // compound assignment
{
solAssert(_assignment.getType()->isValueType(), "Compound operators not implemented for non-value types.");
if (m_currentLValue.storesReferenceOnStack())
m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2;
m_currentLValue.retrieveValue(_assignment.getType(), _assignment.getLocation(), true);
@ -73,7 +75,7 @@ bool ExpressionCompiler::visit(Assignment const& _assignment)
if (m_currentLValue.storesReferenceOnStack())
m_context << eth::Instruction::SWAP1;
}
m_currentLValue.storeValue(_assignment);
m_currentLValue.storeValue(_assignment, *_assignment.getRightHandSide().getType());
m_currentLValue.reset();
return false;
@ -103,7 +105,7 @@ bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation)
break;
case Token::Delete: // delete
solAssert(m_currentLValue.isValid(), "LValue not retrieved.");
m_currentLValue.setToZero(_unaryOperation);
m_currentLValue.setToZero(_unaryOperation, *_unaryOperation.getSubExpression().getType());
m_currentLValue.reset();
break;
case Token::Inc: // ++ (pre- or postfix)
@ -126,7 +128,7 @@ bool ExpressionCompiler::visit(UnaryOperation const& _unaryOperation)
// Stack for postfix: *ref [ref] (*ref)+-1
if (m_currentLValue.storesReferenceOnStack())
m_context << eth::Instruction::SWAP1;
m_currentLValue.storeValue(_unaryOperation, !_unaryOperation.isPrefixOperation());
m_currentLValue.storeValue(_unaryOperation, *_unaryOperation.getType(), !_unaryOperation.isPrefixOperation());
m_currentLValue.reset();
break;
case Token::Add: // +
@ -274,10 +276,10 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
//@todo copy to memory position 0, shift as soon as we use memory
m_context << u256(0) << eth::Instruction::CODECOPY;
unsigned length = bytecode.size();
length += appendArgumentsCopyToMemory(arguments, function.getParameterTypes(), length);
m_context << u256(bytecode.size());
appendArgumentsCopyToMemory(arguments, function.getParameterTypes());
// size, offset, endowment
m_context << u256(length) << u256(0);
m_context << u256(0);
if (function.valueSet())
m_context << eth::dupInstruction(3);
else
@ -327,8 +329,9 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
break;
case Location::SHA3:
{
unsigned length = appendArgumentsCopyToMemory(arguments, TypePointers(), 0, function.padArguments());
m_context << u256(length) << u256(0) << eth::Instruction::SHA3;
m_context << u256(0);
appendArgumentsCopyToMemory(arguments, TypePointers(), function.padArguments());
m_context << u256(0) << eth::Instruction::SHA3;
break;
}
case Location::Log0:
@ -343,23 +346,16 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
arguments[arg]->accept(*this);
appendTypeConversion(*arguments[arg]->getType(), *function.getParameterTypes()[arg], true);
}
unsigned length = appendExpressionCopyToMemory(*function.getParameterTypes().front(),
*arguments.front());
solAssert(length == 32, "Log data should be 32 bytes long (for now).");
m_context << u256(length) << u256(0) << eth::logInstruction(logNumber);
m_context << u256(0);
appendExpressionCopyToMemory(*function.getParameterTypes().front(), *arguments.front());
m_context << u256(0) << eth::logInstruction(logNumber);
break;
}
case Location::Event:
{
_functionCall.getExpression().accept(*this);
auto const& event = dynamic_cast<EventDefinition const&>(function.getDeclaration());
// Copy all non-indexed arguments to memory (data)
unsigned numIndexed = 0;
unsigned memLength = 0;
for (unsigned arg = 0; arg < arguments.size(); ++arg)
if (!event.getParameters()[arg]->isIndexed())
memLength += appendExpressionCopyToMemory(*function.getParameterTypes()[arg],
*arguments[arg], memLength);
// All indexed arguments go to the stack
for (unsigned arg = arguments.size(); arg > 0; --arg)
if (event.getParameters()[arg - 1]->isIndexed())
@ -372,7 +368,12 @@ bool ExpressionCompiler::visit(FunctionCall const& _functionCall)
m_context << u256(h256::Arith(dev::sha3(function.getCanonicalSignature(event.getName()))));
++numIndexed;
solAssert(numIndexed <= 4, "Too many indexed arguments.");
m_context << u256(memLength) << u256(0) << eth::logInstruction(numIndexed);
// Copy all non-indexed arguments to memory (data)
m_context << u256(0);
for (unsigned arg = 0; arg < arguments.size(); ++arg)
if (!event.getParameters()[arg]->isIndexed())
appendExpressionCopyToMemory(*function.getParameterTypes()[arg], *arguments[arg]);
m_context << u256(0) << eth::logInstruction(numIndexed);
break;
}
case Location::BlockHash:
@ -472,6 +473,10 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess)
m_context << eth::Instruction::GAS;
else if (member == "gasprice")
m_context << eth::Instruction::GASPRICE;
else if (member == "data")
{
// nothing to store on the stack
}
else
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unknown magic member."));
break;
@ -499,6 +504,12 @@ void ExpressionCompiler::endVisit(MemberAccess const& _memberAccess)
}
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Invalid member access to " + type.toString()));
}
case Type::Category::ByteArray:
{
solAssert(member == "length", "Illegal bytearray member.");
m_context << eth::Instruction::SLOAD;
break;
}
default:
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Member access to unknown type."));
}
@ -508,12 +519,16 @@ bool ExpressionCompiler::visit(IndexAccess const& _indexAccess)
{
_indexAccess.getBaseExpression().accept(*this);
TypePointer const& keyType = dynamic_cast<MappingType const&>(*_indexAccess.getBaseExpression().getType()).getKeyType();
unsigned length = appendExpressionCopyToMemory(*keyType, _indexAccess.getIndexExpression());
solAssert(length == 32, "Mapping key has to take 32 bytes in memory (for now).");
// @todo move this once we actually use memory
length += CompilerUtils(m_context).storeInMemory(length);
m_context << u256(length) << u256(0) << eth::Instruction::SHA3;
Type const& baseType = *_indexAccess.getBaseExpression().getType();
solAssert(baseType.getCategory() == Type::Category::Mapping, "");
Type const& keyType = *dynamic_cast<MappingType const&>(baseType).getKeyType();
m_context << u256(0);
appendExpressionCopyToMemory(keyType, _indexAccess.getIndexExpression());
solAssert(baseType.getSizeOnStack() == 1,
"Unexpected: Not exactly one stack slot taken by subscriptable expression.");
m_context << eth::Instruction::SWAP1;
appendTypeMoveToMemory(IntegerType(256));
m_context << u256(0) << eth::Instruction::SHA3;
m_currentLValue = LValue(m_context, LValue::LValueType::Storage, *_indexAccess.getType());
m_currentLValue.retrieveValueIfLValueNotRequested(_indexAccess);
@ -804,26 +819,30 @@ void ExpressionCompiler::appendExternalFunctionCall(FunctionType const& _functio
unsigned gasStackPos = m_context.currentToBaseStackOffset(gasValueSize);
unsigned valueStackPos = m_context.currentToBaseStackOffset(1);
if (!bare)
//@todo only return the first return value for now
Type const* firstType = _functionType.getReturnParameterTypes().empty() ? nullptr :
_functionType.getReturnParameterTypes().front().get();
unsigned retSize = firstType ? CompilerUtils::getPaddedSize(firstType->getCalldataEncodedSize()) : 0;
m_context << u256(retSize) << u256(0);
if (bare)
m_context << u256(0);
else
{
// copy function identifier
m_context << eth::dupInstruction(gasValueSize + 1);
CompilerUtils(m_context).storeInMemory(0, CompilerUtils::dataStartOffset);
m_context << eth::dupInstruction(gasValueSize + 3);
CompilerUtils(m_context).storeInMemory(0, IntegerType(CompilerUtils::dataStartOffset * 8));
m_context << u256(CompilerUtils::dataStartOffset);
}
// reserve space for the function identifier
unsigned dataOffset = bare ? 0 : CompilerUtils::dataStartOffset;
// For bare call, activate "4 byte pad exception": If the first argument has exactly 4 bytes,
// do not pad it to 32 bytes.
dataOffset += appendArgumentsCopyToMemory(_arguments, _functionType.getParameterTypes(), dataOffset,
_functionType.padArguments(), bare);
appendArgumentsCopyToMemory(_arguments, _functionType.getParameterTypes(),
_functionType.padArguments(), bare);
//@todo only return the first return value for now
Type const* firstType = _functionType.getReturnParameterTypes().empty() ? nullptr :
_functionType.getReturnParameterTypes().front().get();
unsigned retSize = firstType ? CompilerUtils::getPaddedSize(firstType->getCalldataEncodedSize()) : 0;
// CALL arguments: outSize, outOff, inSize, inOff, value, addr, gas (stack top)
m_context << u256(retSize) << u256(0) << u256(dataOffset) << u256(0);
// CALL arguments: outSize, outOff, inSize, (already present up to here)
// inOff, value, addr, gas (stack top)
m_context << u256(0);
if (_functionType.valueSet())
m_context << eth::dupInstruction(m_context.baseToCurrentStackOffset(valueStackPos));
else
@ -852,14 +871,12 @@ void ExpressionCompiler::appendExternalFunctionCall(FunctionType const& _functio
}
}
unsigned ExpressionCompiler::appendArgumentsCopyToMemory(vector<ASTPointer<Expression const>> const& _arguments,
TypePointers const& _types,
unsigned _memoryOffset,
bool _padToWordBoundaries,
bool _padExceptionIfFourBytes)
void ExpressionCompiler::appendArgumentsCopyToMemory(vector<ASTPointer<Expression const>> const& _arguments,
TypePointers const& _types,
bool _padToWordBoundaries,
bool _padExceptionIfFourBytes)
{
solAssert(_types.empty() || _types.size() == _arguments.size(), "");
unsigned length = 0;
for (size_t i = 0; i < _arguments.size(); ++i)
{
_arguments[i]->accept(*this);
@ -869,31 +886,20 @@ unsigned ExpressionCompiler::appendArgumentsCopyToMemory(vector<ASTPointer<Expre
// Do not pad if the first argument has exactly four bytes
if (i == 0 && pad && _padExceptionIfFourBytes && expectedType->getCalldataEncodedSize() == 4)
pad = false;
length += appendTypeMoveToMemory(*expectedType, _arguments[i]->getLocation(),
_memoryOffset + length, pad);
appendTypeMoveToMemory(*expectedType, pad);
}
return length;
}
unsigned ExpressionCompiler::appendTypeMoveToMemory(Type const& _type, Location const& _location, unsigned _memoryOffset, bool _padToWordBoundaries)
void ExpressionCompiler::appendTypeMoveToMemory(Type const& _type, bool _padToWordBoundaries)
{
unsigned const c_encodedSize = _type.getCalldataEncodedSize();
unsigned const c_numBytes = _padToWordBoundaries ? CompilerUtils::getPaddedSize(c_encodedSize) : c_encodedSize;
if (c_numBytes == 0 || c_numBytes > 32)
BOOST_THROW_EXCEPTION(CompilerError()
<< errinfo_sourceLocation(_location)
<< errinfo_comment("Type " + _type.toString() + " not yet supported."));
bool const c_leftAligned = _type.getCategory() == Type::Category::String;
return CompilerUtils(m_context).storeInMemory(_memoryOffset, c_numBytes, c_leftAligned, _padToWordBoundaries);
CompilerUtils(m_context).storeInMemoryDynamic(_type, _padToWordBoundaries);
}
unsigned ExpressionCompiler::appendExpressionCopyToMemory(Type const& _expectedType,
Expression const& _expression,
unsigned _memoryOffset)
void ExpressionCompiler::appendExpressionCopyToMemory(Type const& _expectedType, Expression const& _expression)
{
_expression.accept(*this);
appendTypeConversion(*_expression.getType(), _expectedType, true);
return appendTypeMoveToMemory(_expectedType, _expression.getLocation(), _memoryOffset);
appendTypeMoveToMemory(_expectedType);
}
void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& _varDecl)
@ -904,7 +910,7 @@ void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const&
TypePointers const& paramTypes = accessorType.getParameterTypes();
// move arguments to memory
for (TypePointer const& paramType: boost::adaptors::reverse(paramTypes))
length += appendTypeMoveToMemory(*paramType, Location(), length);
length += CompilerUtils(m_context).storeInMemory(length, *paramType, true);
// retrieve the position of the variable
m_context << m_context.getStorageLocationOfVariable(_varDecl);
@ -1014,7 +1020,7 @@ void ExpressionCompiler::LValue::retrieveValueFromStorage(TypePointer const& _ty
}
}
void ExpressionCompiler::LValue::storeValue(Expression const& _expression, bool _move) const
void ExpressionCompiler::LValue::storeValue(Expression const& _expression, Type const& _sourceType, bool _move) const
{
switch (m_type)
{
@ -1032,28 +1038,46 @@ void ExpressionCompiler::LValue::storeValue(Expression const& _expression, bool
break;
}
case LValueType::Storage:
if (!_expression.getType()->isValueType())
break; // no distinction between value and reference for non-value types
// stack layout: value value ... value ref
if (!_move) // copy values
// stack layout: value value ... value target_ref
if (_expression.getType()->isValueType())
{
if (m_size + 1 > 16)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_expression.getLocation())
<< errinfo_comment("Stack too deep."));
if (!_move) // copy values
{
if (m_size + 1 > 16)
BOOST_THROW_EXCEPTION(CompilerError() << errinfo_sourceLocation(_expression.getLocation())
<< errinfo_comment("Stack too deep."));
for (unsigned i = 0; i < m_size; ++i)
*m_context << eth::dupInstruction(m_size + 1) << eth::Instruction::SWAP1;
}
if (m_size > 0) // store high index value first
*m_context << u256(m_size - 1) << eth::Instruction::ADD;
for (unsigned i = 0; i < m_size; ++i)
*m_context << eth::dupInstruction(m_size + 1) << eth::Instruction::SWAP1;
{
if (i + 1 >= m_size)
*m_context << eth::Instruction::SSTORE;
else
// stack here: value value ... value value (target_ref+offset)
*m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2
<< eth::Instruction::SSTORE
<< u256(1) << eth::Instruction::SWAP1 << eth::Instruction::SUB;
}
}
if (m_size > 0) // store high index value first
*m_context << u256(m_size - 1) << eth::Instruction::ADD;
for (unsigned i = 0; i < m_size; ++i)
else
{
if (i + 1 >= m_size)
*m_context << eth::Instruction::SSTORE;
solAssert(!_move, "Move assign for non-value types not implemented.");
solAssert(_sourceType.getCategory() == _expression.getType()->getCategory(), "");
if (_expression.getType()->getCategory() == Type::Category::ByteArray)
CompilerUtils(*m_context).copyByteArrayToStorage(
dynamic_cast<ByteArrayType const&>(*_expression.getType()),
dynamic_cast<ByteArrayType const&>(_sourceType));
else if (_expression.getType()->getCategory() == Type::Category::Struct)
{
//@todo
solAssert(false, "Struct copy not yet implemented.");
}
else
// v v ... v v r+x
*m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2
<< eth::Instruction::SSTORE
<< u256(1) << eth::Instruction::SWAP1 << eth::Instruction::SUB;
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_expression.getLocation())
<< errinfo_comment("Invalid non-value type for assignment."));
}
break;
case LValueType::Memory:
@ -1069,7 +1093,7 @@ void ExpressionCompiler::LValue::storeValue(Expression const& _expression, bool
}
}
void ExpressionCompiler::LValue::setToZero(Expression const& _expression) const
void ExpressionCompiler::LValue::setToZero(Expression const& _expression, Type const& _type) const
{
switch (m_type)
{
@ -1086,20 +1110,21 @@ void ExpressionCompiler::LValue::setToZero(Expression const& _expression) const
break;
}
case LValueType::Storage:
if (m_size == 0)
*m_context << eth::Instruction::POP;
for (unsigned i = 0; i < m_size; ++i)
if (_type.getCategory() == Type::Category::ByteArray)
CompilerUtils(*m_context).clearByteArray(dynamic_cast<ByteArrayType const&>(_type));
else
{
if (i + 1 >= m_size)
*m_context << u256(0) << eth::Instruction::SWAP1 << eth::Instruction::SSTORE;
else
*m_context << u256(0) << eth::Instruction::DUP2 << eth::Instruction::SSTORE
<< u256(1) << eth::Instruction::ADD;
if (m_size == 0)
*m_context << eth::Instruction::POP;
for (unsigned i = 0; i < m_size; ++i)
if (i + 1 >= m_size)
*m_context << u256(0) << eth::Instruction::SWAP1 << eth::Instruction::SSTORE;
else
*m_context << u256(0) << eth::Instruction::DUP2 << eth::Instruction::SSTORE
<< u256(1) << eth::Instruction::ADD;
}
break;
case LValueType::Memory:
if (!_expression.getType()->isValueType())
break; // no distinction between value and reference for non-value types
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_sourceLocation(_expression.getLocation())
<< errinfo_comment("Location type not yet implemented."));
break;
@ -1108,7 +1133,6 @@ void ExpressionCompiler::LValue::setToZero(Expression const& _expression) const
<< errinfo_comment("Unsupported location type."));
break;
}
}
void ExpressionCompiler::LValue::retrieveValueIfLValueNotRequested(Expression const& _expression)
@ -1145,5 +1169,6 @@ void ExpressionCompiler::LValue::fromIdentifier(Identifier const& _identifier, D
<< errinfo_comment("Identifier type not supported or identifier not found."));
}
}
}

34
libsolidity/ExpressionCompiler.h

@ -92,21 +92,18 @@ private:
/// Appends code to call a function of the given type with the given arguments.
void appendExternalFunctionCall(FunctionType const& _functionType, std::vector<ASTPointer<Expression const>> const& _arguments,
bool bare = false);
/// Appends code that evaluates the given arguments and moves the result to memory (with optional offset).
/// @returns the number of bytes moved to memory
unsigned appendArgumentsCopyToMemory(std::vector<ASTPointer<Expression const>> const& _arguments,
TypePointers const& _types = {},
unsigned _memoryOffset = 0,
bool _padToWordBoundaries = true,
bool _padExceptionIfFourBytes = false);
/// Appends code that moves a stack element of the given type to memory
/// @returns the number of bytes moved to memory
unsigned appendTypeMoveToMemory(Type const& _type, Location const& _location, unsigned _memoryOffset,
bool _padToWordBoundaries = true);
/// Appends code that evaluates a single expression and moves the result to memory (with optional offset).
/// @returns the number of bytes moved to memory
unsigned appendExpressionCopyToMemory(Type const& _expectedType, Expression const& _expression,
unsigned _memoryOffset = 0);
/// Appends code that evaluates the given arguments and moves the result to memory. The memory offset is
/// expected to be on the stack and is updated by this call.
void appendArgumentsCopyToMemory(std::vector<ASTPointer<Expression const>> const& _arguments,
TypePointers const& _types = {},
bool _padToWordBoundaries = true,
bool _padExceptionIfFourBytes = false);
/// Appends code that moves a stack element of the given type to memory. The memory offset is
/// expected below the stack element and is updated by this call.
void appendTypeMoveToMemory(Type const& _type, bool _padToWordBoundaries = true);
/// Appends code that evaluates a single expression and moves the result to memory. The memory offset is
/// expected to be on the stack and is updated by this call.
void appendExpressionCopyToMemory(Type const& _expectedType, Expression const& _expression);
/// Appends code for a State Variable accessor function
void appendStateVariableAccessor(VariableDeclaration const& _varDecl);
@ -148,10 +145,11 @@ private:
/// be on the top of the stack, if any) in the lvalue and removes the reference.
/// Also removes the stored value from the stack if @a _move is
/// true. @a _expression is the current expression, used for error reporting.
void storeValue(Expression const& _expression, bool _move = false) const;
/// @a _sourceType is the type of the expression that is assigned.
void storeValue(Expression const& _expression, Type const& _sourceType, bool _move = false) const;
/// Stores zero in the lvalue.
/// @a _expression is the current expression, used for error reporting.
void setToZero(Expression const& _expression) const;
void setToZero(Expression const& _expression, Type const& _type) const;
/// Convenience function to convert the stored reference to a value and reset type to NONE if
/// the reference was not requested by @a _expression.
void retrieveValueIfLValueNotRequested(Expression const& _expression);
@ -159,6 +157,8 @@ private:
private:
/// Convenience function to retrieve Value from Storage. Specific version of @ref retrieveValue
void retrieveValueFromStorage(TypePointer const& _type, bool _remove = false) const;
/// Copies from a byte array to a byte array in storage, both references on the stack.
void copyByteArrayToStorage(ByteArrayType const& _targetType, ByteArrayType const& _sourceType) const;
CompilerContext* m_context;
LValueType m_type = LValueType::None;

2
libsolidity/InterfaceHandler.h

@ -28,7 +28,7 @@
#include <string>
#include <memory>
#include <jsoncpp/json/json.h>
#include <json/json.h>
namespace dev
{

6
libsolidity/Token.h

@ -176,8 +176,7 @@ namespace solidity
K(SubFinney, "finney", 0) \
K(SubEther, "ether", 0) \
/* type keywords, keep them in this order, keep int as first keyword
* the implementation in Types.cpp has to be synced to this here
* TODO more to be added */ \
* the implementation in Types.cpp has to be synced to this here */\
K(Int, "int", 0) \
K(Int8, "int8", 0) \
K(Int16, "int16", 0) \
@ -279,7 +278,8 @@ namespace solidity
K(Hash256, "hash256", 0) \
K(Address, "address", 0) \
K(Bool, "bool", 0) \
K(StringType, "string", 0) \
K(Bytes, "bytes", 0) \
K(StringType, "string", 0) \
K(String0, "string0", 0) \
K(String1, "string1", 0) \
K(String2, "string2", 0) \

66
libsolidity/Types.cpp

@ -35,7 +35,7 @@ namespace dev
namespace solidity
{
shared_ptr<Type const> Type::fromElementaryTypeName(Token::Value _typeToken)
TypePointer Type::fromElementaryTypeName(Token::Value _typeToken)
{
solAssert(Token::isElementaryTypeName(_typeToken), "Elementary type name expected.");
@ -57,12 +57,19 @@ shared_ptr<Type const> Type::fromElementaryTypeName(Token::Value _typeToken)
return make_shared<BoolType>();
else if (Token::String0 <= _typeToken && _typeToken <= Token::String32)
return make_shared<StaticStringType>(int(_typeToken) - int(Token::String0));
else if (_typeToken == Token::Bytes)
return make_shared<ByteArrayType>(ByteArrayType::Location::Storage);
else
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Unable to convert elementary typename " +
std::string(Token::toString(_typeToken)) + " to type."));
}
shared_ptr<Type const> Type::fromUserDefinedTypeName(UserDefinedTypeName const& _typeName)
TypePointer Type::fromElementaryTypeName(string const& _name)
{
return fromElementaryTypeName(Token::fromIdentifierOrKeyword(_name));
}
TypePointer Type::fromUserDefinedTypeName(UserDefinedTypeName const& _typeName)
{
Declaration const* declaration = _typeName.getReferencedDeclaration();
if (StructDefinition const* structDef = dynamic_cast<StructDefinition const*>(declaration))
@ -71,21 +78,21 @@ shared_ptr<Type const> Type::fromUserDefinedTypeName(UserDefinedTypeName const&
return make_shared<FunctionType>(*function);
else if (ContractDefinition const* contract = dynamic_cast<ContractDefinition const*>(declaration))
return make_shared<ContractType>(*contract);
return shared_ptr<Type const>();
return TypePointer();
}
shared_ptr<Type const> Type::fromMapping(Mapping const& _typeName)
TypePointer Type::fromMapping(Mapping const& _typeName)
{
shared_ptr<Type const> keyType = _typeName.getKeyType().toType();
TypePointer keyType = _typeName.getKeyType().toType();
if (!keyType)
BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Error resolving type name."));
shared_ptr<Type const> valueType = _typeName.getValueType().toType();
TypePointer valueType = _typeName.getValueType().toType();
if (!valueType)
BOOST_THROW_EXCEPTION(_typeName.getValueType().createTypeError("Invalid type name"));
return make_shared<MappingType>(keyType, valueType);
}
shared_ptr<Type const> Type::forLiteral(Literal const& _literal)
TypePointer Type::forLiteral(Literal const& _literal)
{
switch (_literal.getToken())
{
@ -506,6 +513,36 @@ TypePointer ContractType::unaryOperatorResult(Token::Value _operator) const
return _operator == Token::Delete ? make_shared<VoidType>() : TypePointer();
}
bool ByteArrayType::isImplicitlyConvertibleTo(const Type& _convertTo) const
{
return _convertTo.getCategory() == getCategory();
}
TypePointer ByteArrayType::unaryOperatorResult(Token::Value _operator) const
{
if (_operator == Token::Delete)
return make_shared<VoidType>();
return TypePointer();
}
bool ByteArrayType::operator==(Type const& _other) const
{
if (_other.getCategory() != getCategory())
return false;
ByteArrayType const& other = dynamic_cast<ByteArrayType const&>(_other);
return other.m_location == m_location;
}
unsigned ByteArrayType::getSizeOnStack() const
{
if (m_location == Location::CallData)
return 0;
else
return 1;
}
const MemberList ByteArrayType::s_byteArrayMemberList = MemberList({{"length", make_shared<IntegerType >(256)}});
bool ContractType::operator==(Type const& _other) const
{
if (_other.getCategory() != getCategory())
@ -525,8 +562,8 @@ MemberList const& ContractType::getMembers() const
if (!m_members)
{
// All address members and all interface functions
map<string, shared_ptr<Type const>> members(IntegerType::AddressMemberList.begin(),
IntegerType::AddressMemberList.end());
map<string, TypePointer> members(IntegerType::AddressMemberList.begin(),
IntegerType::AddressMemberList.end());
if (m_super)
{
for (ContractDefinition const* base: m_contract.getLinearizedBaseContracts())
@ -581,14 +618,14 @@ bool StructType::operator==(Type const& _other) const
u256 StructType::getStorageSize() const
{
u256 size = 0;
for (pair<string, shared_ptr<Type const>> const& member: getMembers())
for (pair<string, TypePointer> const& member: getMembers())
size += member.second->getStorageSize();
return max<u256>(1, size);
}
bool StructType::canLiveOutsideStorage() const
{
for (pair<string, shared_ptr<Type const>> const& member: getMembers())
for (pair<string, TypePointer> const& member: getMembers())
if (!member.second->canLiveOutsideStorage())
return false;
return true;
@ -604,7 +641,7 @@ MemberList const& StructType::getMembers() const
// We need to lazy-initialize it because of recursive references.
if (!m_members)
{
map<string, shared_ptr<Type const>> members;
map<string, TypePointer> members;
for (ASTPointer<VariableDeclaration> const& variable: m_struct.getMembers())
members[variable->getName()] = variable->getType();
m_members.reset(new MemberList(members));
@ -811,7 +848,7 @@ TypePointers FunctionType::parseElementaryTypeVector(strings const& _types)
TypePointers pointers;
pointers.reserve(_types.size());
for (string const& type: _types)
pointers.push_back(Type::fromElementaryTypeName(Token::fromIdentifierOrKeyword(type)));
pointers.push_back(Type::fromElementaryTypeName(type));
return pointers;
}
@ -941,7 +978,8 @@ MagicType::MagicType(MagicType::Kind _kind):
case Kind::Message:
m_members = MemberList({{"sender", make_shared<IntegerType>(0, IntegerType::Modifier::Address)},
{"gas", make_shared<IntegerType>(256)},
{"value", make_shared<IntegerType>(256)}});
{"value", make_shared<IntegerType>(256)},
{"data", make_shared<ByteArrayType>(ByteArrayType::Location::CallData)}});
break;
case Kind::Transaction:
m_members = MemberList({{"origin", make_shared<IntegerType>(0, IntegerType::Modifier::Address)},

34
libsolidity/Types.h

@ -76,15 +76,17 @@ class Type: private boost::noncopyable, public std::enable_shared_from_this<Type
public:
enum class Category
{
Integer, IntegerConstant, Bool, Real,
String, Contract, Struct, Function,
Mapping, Void, TypeType, Modifier, Magic
Integer, IntegerConstant, Bool, Real, String,
ByteArray, Mapping,
Contract, Struct, Function,
Void, TypeType, Modifier, Magic
};
///@{
///@name Factory functions
/// Factory functions that convert an AST @ref TypeName to a Type.
static TypePointer fromElementaryTypeName(Token::Value _typeToken);
static TypePointer fromElementaryTypeName(std::string const& _name);
static TypePointer fromUserDefinedTypeName(UserDefinedTypeName const& _typeName);
static TypePointer fromMapping(Mapping const& _typeName);
static TypePointer fromFunction(FunctionDefinition const& _function);
@ -263,7 +265,7 @@ class BoolType: public Type
{
public:
BoolType() {}
virtual Category getCategory() const { return Category::Bool; }
virtual Category getCategory() const override { return Category::Bool; }
virtual bool isExplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
virtual TypePointer binaryOperatorResult(Token::Value _operator, TypePointer const& _other) const override;
@ -275,6 +277,30 @@ public:
virtual u256 literalValue(Literal const* _literal) const override;
};
/**
* The type of a byte array, prototype for a general array.
*/
class ByteArrayType: public Type
{
public:
enum class Location { Storage, CallData, Memory };
virtual Category getCategory() const override { return Category::ByteArray; }
explicit ByteArrayType(Location _location): m_location(_location) {}
virtual bool isImplicitlyConvertibleTo(Type const& _convertTo) const override;
virtual TypePointer unaryOperatorResult(Token::Value _operator) const override;
virtual bool operator==(const Type& _other) const override;
virtual unsigned getSizeOnStack() const override;
virtual std::string toString() const override { return "bytes"; }
virtual MemberList const& getMembers() const override { return s_byteArrayMemberList; }
Location getLocation() const { return m_location; }
private:
Location m_location;
static const MemberList s_byteArrayMemberList;
};
/**
* The type of a contract instance, there is one distinct type for each contract definition.
*/

5
libweb3jsonrpc/CMakeLists.txt

@ -30,7 +30,10 @@ target_link_libraries(${EXECUTABLE} ${JSON_RPC_CPP_SERVER_LIBRARIES})
target_link_libraries(${EXECUTABLE} webthree)
target_link_libraries(${EXECUTABLE} secp256k1)
target_link_libraries(${EXECUTABLE} solidity)
target_link_libraries(${EXECUTABLE} serpent)
if (NOT ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC"))
target_link_libraries(${EXECUTABLE} serpent)
endif()
if (ETH_JSON_RPC_STUB)
add_custom_target(jsonrpcstub)

23
libwebthree/WebThree.cpp

@ -35,15 +35,15 @@ using namespace dev::p2p;
using namespace dev::eth;
using namespace dev::shh;
WebThreeDirect::WebThreeDirect(std::string const& _clientVersion, std::string const& _dbPath, bool _forceClean, std::set<std::string> const& _interfaces, NetworkPreferences const& _n):
WebThreeDirect::WebThreeDirect(std::string const& _clientVersion, std::string const& _dbPath, bool _forceClean, std::set<std::string> const& _interfaces, NetworkPreferences const& _n, bytesConstRef _network, int miners):
m_clientVersion(_clientVersion),
m_net(_clientVersion, _n)
m_net(_clientVersion, _n, _network)
{
if (_dbPath.size())
Defaults::setDBPath(_dbPath);
if (_interfaces.count("eth"))
m_ethereum.reset(new eth::Client(&m_net, _dbPath, _forceClean));
m_ethereum.reset(new eth::Client(&m_net, _dbPath, _forceClean, 0, miners));
if (_interfaces.count("shh"))
m_whisper = m_net.registerCapability<WhisperHost>(new WhisperHost);
@ -75,9 +75,9 @@ void WebThreeDirect::setNetworkPreferences(p2p::NetworkPreferences const& _n)
startNetwork();
}
std::vector<PeerInfo> WebThreeDirect::peers()
std::vector<PeerSessionInfo> WebThreeDirect::peers()
{
return m_net.peers();
return m_net.peerSessionInfo();
}
size_t WebThreeDirect::peerCount() const
@ -90,17 +90,12 @@ void WebThreeDirect::setIdealPeerCount(size_t _n)
return m_net.setIdealPeerCount(_n);
}
bytes WebThreeDirect::saveNodes()
{
return m_net.saveNodes();
}
void WebThreeDirect::restoreNodes(bytesConstRef _saved)
bytes WebThreeDirect::saveNetwork()
{
return m_net.restoreNodes(_saved);
return m_net.saveNetwork();
}
void WebThreeDirect::connect(std::string const& _seedHost, unsigned short _port)
{
m_net.connect(_seedHost, _port);
m_net.addNode(NodeId(), _seedHost, _port, _port);
}

23
libwebthree/WebThree.h

@ -54,7 +54,7 @@ class WebThreeNetworkFace
{
public:
/// Get information on the current peer set.
virtual std::vector<p2p::PeerInfo> peers() = 0;
virtual std::vector<p2p::PeerSessionInfo> peers() = 0;
/// Same as peers().size(), but more efficient.
virtual size_t peerCount() const = 0;
@ -63,10 +63,7 @@ public:
virtual void connect(std::string const& _seedHost, unsigned short _port) = 0;
/// Save peers
virtual dev::bytes saveNodes() = 0;
/// Restore peers
virtual void restoreNodes(bytesConstRef _saved) = 0;
virtual dev::bytes saveNetwork() = 0;
/// Sets the ideal number of peers.
virtual void setIdealPeerCount(size_t _n) = 0;
@ -78,7 +75,7 @@ public:
virtual p2p::NodeId id() const = 0;
/// Gets the nodes.
virtual p2p::Nodes nodes() const = 0;
virtual p2p::Peers nodes() const = 0;
/// Start the network subsystem.
virtual void startNetwork() = 0;
@ -106,7 +103,7 @@ class WebThreeDirect : public WebThreeNetworkFace
public:
/// Constructor for private instance. If there is already another process on the machine using @a _dbPath, then this will throw an exception.
/// ethereum() may be safely static_cast()ed to a eth::Client*.
WebThreeDirect(std::string const& _clientVersion, std::string const& _dbPath, bool _forceClean = false, std::set<std::string> const& _interfaces = {"eth", "shh"}, p2p::NetworkPreferences const& _n = p2p::NetworkPreferences());
WebThreeDirect(std::string const& _clientVersion, std::string const& _dbPath, bool _forceClean = false, std::set<std::string> const& _interfaces = {"eth", "shh"}, p2p::NetworkPreferences const& _n = p2p::NetworkPreferences(), bytesConstRef _network = bytesConstRef(), int miners = -1);
/// Destructor.
~WebThreeDirect();
@ -124,7 +121,7 @@ public:
// Network stuff:
/// Get information on the current peer set.
std::vector<p2p::PeerInfo> peers() override;
std::vector<p2p::PeerSessionInfo> peers() override;
/// Same as peers().size(), but more efficient.
size_t peerCount() const override;
@ -133,10 +130,10 @@ public:
void connect(std::string const& _seedHost, unsigned short _port = 30303) override;
/// Save peers
dev::bytes saveNodes() override;
dev::bytes saveNetwork() override;
/// Restore peers
void restoreNodes(bytesConstRef _saved) override;
// /// Restore peers
// void restoreNetwork(bytesConstRef _saved) override;
/// Sets the ideal number of peers.
void setIdealPeerCount(size_t _n) override;
@ -148,7 +145,7 @@ public:
p2p::NodeId id() const override { return m_net.id(); }
/// Gets the nodes.
p2p::Nodes nodes() const override { return m_net.nodes(); }
p2p::Peers nodes() const override { return m_net.getPeers(); }
/// Start the network subsystem.
void startNetwork() override { m_net.start(); }
@ -235,7 +232,7 @@ public:
// Peer network stuff - forward through RPCSlave, probably with P2PNetworkSlave/Master classes like Whisper & Ethereum.
/// Get information on the current peer set.
std::vector<p2p::PeerInfo> peers();
std::vector<p2p::PeerSessionInfo> peers();
/// Same as peers().size(), but more efficient.
size_t peerCount() const;

16
libwhisper/WhisperHost.cpp

@ -79,11 +79,15 @@ void WhisperHost::inject(Envelope const& _m, WhisperPeer* _p)
noteChanged(h, f.first);
}
for (auto& i: peers())
if (i->cap<WhisperPeer>().get() == _p)
i->addRating(1);
// TODO p2p: capability-based rating
for (auto i: peerSessions())
{
auto w = i.first->cap<WhisperPeer>().get();
if (w == _p)
w->addRating(1);
else
i->cap<WhisperPeer>()->noteNewMessage(h, _m);
w->noteNewMessage(h, _m);
}
}
void WhisperHost::noteChanged(h256 _messageHash, h256 _filter)
@ -158,8 +162,8 @@ void WhisperHost::uninstallWatch(unsigned _i)
void WhisperHost::doWork()
{
for (auto& i: peers())
i->cap<WhisperPeer>()->sendMessages();
for (auto& i: peerSessions())
i.first->cap<WhisperPeer>().get()->sendMessages();
cleanup();
}

1
lllc/CMakeLists.txt

@ -4,6 +4,7 @@ set(CMAKE_AUTOMOC OFF)
aux_source_directory(. SRC_LIST)
include_directories(${Boost_INCLUDE_DIRS})
include_directories(${JSONCPP_INCLUDE_DIRS})
include_directories(..)
set(EXECUTABLE lllc)

4
mix/AppContext.cpp

@ -60,6 +60,8 @@ AppContext::~AppContext()
void AppContext::load()
{
m_applicationEngine->rootContext()->setContextProperty("appContext", this);
QFont f;
m_applicationEngine->rootContext()->setContextProperty("systemPointSize", f.pointSize());
qmlRegisterType<FileIo>("org.ethereum.qml", 1, 0, "FileIo");
m_applicationEngine->rootContext()->setContextProperty("codeModel", m_codeModel.get());
m_applicationEngine->rootContext()->setContextProperty("fileIo", m_fileIo.get());
@ -81,8 +83,6 @@ void AppContext::load()
BOOST_THROW_EXCEPTION(exception);
}
m_applicationEngine->rootContext()->setContextProperty("projectModel", projectModel);
QFont f;
m_applicationEngine->rootContext()->setContextProperty("systemPointSize", f.pointSize());
qmlRegisterType<CodeEditorExtensionManager>("CodeEditorExtensionManager", 1, 0, "CodeEditorExtensionManager");
qmlRegisterType<HttpServer>("HttpServer", 1, 0, "HttpServer");
m_applicationEngine->load(QUrl("qrc:/qml/main.qml"));

1
mix/CMakeLists.txt

@ -10,6 +10,7 @@ endif()
set(CMAKE_INCLUDE_CURRENT_DIR ON)
aux_source_directory(. SRC_LIST)
include_directories(${JSONCPP_INCLUDE_DIRS})
include_directories(..)
find_package (Qt5WebEngine QUIET)

3
mix/ClientModel.cpp

@ -156,7 +156,8 @@ void ClientModel::setupState(QVariantMap _state)
{
QVariantMap transaction = t.toMap();
QString functionId = transaction.value("functionId").toString();
u256 gas = (qvariant_cast<QEther*>(transaction.value("gas")))->toU256Wei();
u256 gas = boost::get<u256>(qvariant_cast<QBigInt*>(transaction.value("gas"))->internalValue());
u256 value = (qvariant_cast<QEther*>(transaction.value("value")))->toU256Wei();
u256 gasPrice = (qvariant_cast<QEther*>(transaction.value("gasPrice")))->toU256Wei();

8
mix/CodeModel.cpp

@ -28,6 +28,7 @@
#include <libsolidity/SourceReferenceFormatter.h>
#include <libsolidity/InterfaceHandler.h>
#include <libevmcore/Instruction.h>
#include <libethcore/CommonJS.h>
#include "QContractDefinition.h"
#include "QFunctionDefinition.h"
#include "QVariableDeclaration.h"
@ -61,7 +62,6 @@ CompilationResult::CompilationResult(const dev::solidity::CompilerStack& _compil
auto const& contractDefinition = _compiler.getContractDefinition(std::string());
m_contract.reset(new QContractDefinition(&contractDefinition));
m_bytes = _compiler.getBytecode();
m_assemblyCode = QString::fromStdString(dev::eth::disassemble(m_bytes));
dev::solidity::InterfaceHandler interfaceHandler;
m_contractInterface = QString::fromStdString(*interfaceHandler.getABIInterface(contractDefinition));
if (m_contractInterface.isEmpty())
@ -78,11 +78,15 @@ CompilationResult::CompilationResult(CompilationResult const& _prev, QString con
m_contract(_prev.m_contract),
m_compilerMessage(_compilerMessage),
m_bytes(_prev.m_bytes),
m_assemblyCode(_prev.m_assemblyCode),
m_contractInterface(_prev.m_contractInterface),
m_codeHighlighter(_prev.m_codeHighlighter)
{}
QString CompilationResult::codeHex() const
{
return QString::fromStdString(toJS(m_bytes));
}
CodeModel::CodeModel(QObject* _parent):
QObject(_parent),
m_compiling(false),

6
mix/CodeModel.h

@ -69,6 +69,7 @@ class CompilationResult: public QObject
Q_PROPERTY(QString compilerMessage READ compilerMessage CONSTANT)
Q_PROPERTY(bool successful READ successful CONSTANT)
Q_PROPERTY(QString contractInterface READ contractInterface CONSTANT)
Q_PROPERTY(QString codeHex READ codeHex CONSTANT)
public:
/// Empty compilation result constructor
@ -88,8 +89,8 @@ public:
QString compilerMessage() const { return m_compilerMessage; }
/// @returns contract bytecode
dev::bytes const& bytes() const { return m_bytes; }
/// @returns contract bytecode in human-readable form
QString assemblyCode() const { return m_assemblyCode; }
/// @returns contract bytecode as hex string
QString codeHex() const;
/// @returns contract definition in JSON format
QString contractInterface() const { return m_contractInterface; }
/// Get code highlighter
@ -101,7 +102,6 @@ private:
std::shared_ptr<QContractDefinition> m_contract;
QString m_compilerMessage; ///< @todo: use some structure here
dev::bytes m_bytes;
QString m_assemblyCode;
QString m_contractInterface;
std::shared_ptr<CodeHighlighter> m_codeHighlighter;

6
mix/FileIo.cpp

@ -70,6 +70,12 @@ void FileIo::writeFile(QString const& _url, QString const& _data)
void FileIo::copyFile(QString const& _sourceUrl, QString const& _destUrl)
{
if (QUrl(_sourceUrl).scheme() == "qrc")
{
writeFile(_destUrl, readFile(_sourceUrl));
return;
}
QUrl sourceUrl(_sourceUrl);
QUrl destUrl(_destUrl);
if (!QFile::copy(sourceUrl.path(), destUrl.path()))

12
mix/HttpServer.cpp

@ -132,23 +132,25 @@ void HttpServer::readClient()
if (socket->canReadLine())
{
QString hdr = QString(socket->readLine());
if (hdr.startsWith("POST"))
if (hdr.startsWith("POST") || hdr.startsWith("GET"))
{
QUrl url(hdr.split(' ')[1]);
QString l;
do
l = socket->readLine();
while (!(l.isEmpty() || l == "\r" || l == "\r\n"));
QString content = socket->readAll();
QUrl url;
std::unique_ptr<HttpRequest> request(new HttpRequest(this, url, content));
clientConnected(request.get());
QTextStream os(socket);
os.setAutoDetectUnicode(true);
QString q;
///@todo: allow setting response content-type, charset, etc
os << "HTTP/1.0 200 Ok\r\n"
"Content-Type: text/plain; charset=\"utf-8\"\r\n"
"\r\n";
os << "HTTP/1.0 200 Ok\r\n";
if (!request->m_responseContentType.isEmpty())
os << "Content-Type: " << request->m_responseContentType << "; ";
os << "charset=\"utf-8\"\r\n\r\n";
os << request->m_response;
}
}

4
mix/HttpServer.h

@ -51,11 +51,15 @@ public:
/// Set response for a request
/// @param _response Response body. If no response is set, server returns status 200 with empty body
Q_INVOKABLE void setResponse(QString const& _response) { m_response = _response; }
/// Set response content type
/// @param _contentType Response content type string. text/plain by default
Q_INVOKABLE void setResponseContentType(QString const& _contentType) { m_responseContentType = _contentType ; }
private:
QUrl m_url;
QString m_content;
QString m_response;
QString m_responseContentType;
friend class HttpServer;
};

4
mix/QEther.cpp

@ -35,7 +35,7 @@ QBigInt* QEther::toWei() const
const char* key = units.valueToKey(m_currentUnit);
for (std::pair<dev::u256, std::string> rawUnit: dev::eth::units())
{
if (rawUnit.second == QString(key).toLower().toStdString())
if (QString::fromStdString(rawUnit.second).toLower() == QString(key).toLower())
return multiply(new QBigInt(rawUnit.first));
}
return new QBigInt(dev::u256(0));
@ -46,7 +46,7 @@ void QEther::setUnit(QString const& _unit)
QMetaEnum units = staticMetaObject.enumerator(staticMetaObject.indexOfEnumerator("EtherUnit"));
for (int k = 0; k < units.keyCount(); k++)
{
if (QString(units.key(k)).toLower() == _unit)
if (QString(units.key(k)).toLower() == _unit.toLower())
{
m_currentUnit = static_cast<EtherUnit>(units.keysToValue(units.key(k)));
return;

23
mix/qml/CodeEditorView.qml

@ -4,18 +4,26 @@ import QtQuick.Layouts 1.0
import QtQuick.Controls 1.0
Item {
id: codeEditorView
property string currentDocumentId: ""
signal documentEdit(string documentId)
function getDocumentText(documentId) {
for (i = 0; i < editorListModel.count; i++) {
for (var i = 0; i < editorListModel.count; i++) {
if (editorListModel.get(i).documentId === documentId) {
return editors.itemAt(i).getText();
return editors.itemAt(i).item.getText();
}
}
return "";
}
function isDocumentOpen(documentId) {
for (var i = 0; i < editorListModel.count; i++)
if (editorListModel.get(i).documentId === documentId)
return true;
return false;
}
function openDocument(document) {
loadDocument(document);
currentDocumentId = document.documentId;
@ -31,13 +39,16 @@ Item {
function doLoadDocument(editor, document) {
var data = fileIo.readFile(document.path);
if (document.isContract)
editor.onEditorTextChanged.connect(function() {
editor.onEditorTextChanged.connect(function() {
documentEdit(document.documentId);
if (document.isContract)
codeModel.registerCodeChange(editor.getText());
});
});
editor.setText(data, document.syntaxMode);
}
Component.onCompleted: projectModel.codeEditor = codeEditorView;
Connections {
target: projectModel
onDocumentOpened: {

9
mix/qml/CommonSeparator.qml

@ -0,0 +1,9 @@
import QtQuick 2.0
import "."
Rectangle
{
height: 1
color: Style.generic.layout.separatorColor
}

2
mix/qml/Debugger.qml

@ -426,7 +426,7 @@ Rectangle {
Layout.minimumHeight: parent.height
Text {
anchors.centerIn: parent
anchors.leftMargin: 5()
anchors.leftMargin: 5
font.family: "monospace"
color: "#4a4a4a"
text: styleData.row;

17
mix/qml/DefaultLabel.qml

@ -0,0 +1,17 @@
import QtQuick 2.0
import QtQuick.Controls 1.1
import "."
Label {
text: text
font.family: regularFont.name
font.pointSize: Style.generic.size.titlePointSize
SourceSansProLight
{
id: regularFont
}
}

13
mix/qml/DefaultTextField.qml

@ -0,0 +1,13 @@
import QtQuick 2.0
import QtQuick.Controls 1.1
TextField {
id: titleField
focus: true
font.family: regularFont.name
SourceSansProRegular
{
id: regularFont;
}
}

13
mix/qml/Ether.qml

@ -32,7 +32,8 @@ RowLayout {
units.currentIndex = unit;
}
TextField
DefaultTextField
{
implicitWidth: 200
onTextChanged:
@ -48,6 +49,10 @@ RowLayout {
id: etherValueEdit;
}
SourceSansProBold {
id: regularFont;
}
ComboBox
{
id: units
@ -59,6 +64,7 @@ RowLayout {
formattedValue.text = value.format();
}
}
model: ListModel {
id: unitsModel
ListElement { text: "Uether"; }
@ -81,10 +87,15 @@ RowLayout {
ListElement { text: "Kwei"; }
ListElement { text: "wei"; }
}
style: ComboBoxStyle {
font: regularFont.name
}
}
Text
{
visible: displayFormattedValue
id: formattedValue
font.family: regularFont.name
}
}

6
mix/qml/FilesSection.qml

@ -48,16 +48,14 @@ ColumnLayout {
model.remove(i);
}
FontLoader
SourceSansProRegular
{
id: fileNameFont
source: "qrc:/qml/fonts/SourceSansPro-Regular.ttf"
}
FontLoader
SourceSansProBold
{
id: boldFont
source: "qrc:/qml/fonts/SourceSansPro-Bold.ttf"
}
RowLayout

2
mix/qml/MainContent.qml

@ -85,7 +85,7 @@ Rectangle {
property alias webWidth: webPreview.width
property alias webHeight: webPreview.height
property alias showProjectView: projectList.visible
property bool runOnProjectLoad: false
property bool runOnProjectLoad: true
}
ColumnLayout

6
mix/qml/NewProjectDialog.qml

@ -5,8 +5,8 @@ import QtQuick.Window 2.0
import QtQuick.Dialogs 1.1
Window {
modality: Qt.WindowModal
id: newProjectWin
modality: Qt.ApplicationModal
width: 640
height: 120
@ -18,6 +18,8 @@ Window {
signal accepted
function open() {
newProjectWin.setX((Screen.width - width) / 2);
newProjectWin.setY((Screen.height - height) / 2);
visible = true;
titleField.focus = true;
}

4
mix/qml/ProjectList.qml

@ -12,10 +12,10 @@ Item {
anchors.fill: parent
id: filesCol
spacing: 0
FontLoader
SourceSansProLight
{
id: srcSansProLight
source: "qrc:/qml/fonts/SourceSansPro-Light.ttf"
}
Rectangle

19
mix/qml/ProjectModel.qml

@ -20,6 +20,9 @@ Item {
signal projectSaved()
signal newProject(var projectData)
signal documentSaved(var documentId)
signal deploymentStarted()
signal deploymentComplete()
signal deploymentError(string error)
property bool isEmpty: (projectPath === "")
readonly property string projectFileName: ".mix"
@ -28,8 +31,10 @@ Item {
property string projectPath: ""
property string projectTitle: ""
property string currentDocumentId: ""
property string deploymentAddress: ""
property var listModel: projectListModel
property var stateListModel: projectStateListModel.model
property CodeEditorView codeEditor: null
//interface
function saveAll() { ProjectModelCode.saveAll(); }
@ -48,7 +53,8 @@ Item {
function removeDocument(documentId) { ProjectModelCode.removeDocument(documentId); }
function getDocument(documentId) { return ProjectModelCode.getDocument(documentId); }
function getDocumentIndex(documentId) { return ProjectModelCode.getDocumentIndex(documentId); }
function doAddExistingFiles(paths) { ProjectModelCode.doAddExistingFiles(paths); }
function addExistingFiles(paths) { ProjectModelCode.doAddExistingFiles(paths); }
function deployProject() { ProjectModelCode.deployProject(false); }
Connections {
target: appContext
@ -83,6 +89,17 @@ Item {
}
}
MessageDialog {
id: deployWarningDialog
title: qsTr("Project")
text: qsTr("This project has been already deployed to the network. Do you want to re-deploy it?")
standardButtons: StandardButton.Ok | StandardButton.Cancel
icon: StandardIcon.Question
onAccepted: {
ProjectModelCode.deployProject(true);
}
}
ListModel {
id: projectListModel
}

10
mix/qml/QHashTypeView.qml

@ -4,13 +4,23 @@ Item
{
property alias text: textinput.text
id: editRoot
SourceSansProBold
{
id: boldFont
}
Rectangle {
anchors.fill: parent
radius: 4
color: "#f7f7f7"
TextInput {
id: textinput
text: text
anchors.fill: parent
wrapMode: Text.WrapAnywhere
clip: true
font.family: boldFont.name
MouseArea {
id: mouseArea
anchors.fill: parent

10
mix/qml/QIntTypeView.qml

@ -4,12 +4,22 @@ Item
{
property alias text: textinput.text
id: editRoot
SourceSansProBold
{
id: boldFont
}
Rectangle {
anchors.fill: parent
radius: 4
color: "#f7f7f7"
TextInput {
id: textinput
text: text
anchors.fill: parent
font.family: boldFont.name
clip: true
MouseArea {
id: mouseArea
anchors.fill: parent

10
mix/qml/QStringTypeView.qml

@ -4,13 +4,23 @@ Item
{
property alias text: textinput.text
id: editRoot
SourceSansProBold
{
id: boldFont
}
Rectangle {
anchors.fill: parent
radius: 4
color: "#f7f7f7"
TextInput {
id: textinput
text: text
clip: true
anchors.fill: parent
wrapMode: Text.WrapAnywhere
font.family: boldFont.name
MouseArea {
id: mouseArea
anchors.fill: parent

6
mix/qml/SourceSansProBold.qml

@ -0,0 +1,6 @@
import QtQuick 2.0
FontLoader
{
source: "qrc:/qml/fonts/SourceSansPro-Bold.ttf"
}

7
mix/qml/SourceSansProLight.qml

@ -0,0 +1,7 @@
import QtQuick 2.0
FontLoader
{
source: "qrc:/qml/fonts/SourceSansPro-Light.ttf"
}

8
mix/qml/SourceSansProRegular.qml

@ -0,0 +1,8 @@
import QtQuick 2.0
FontLoader
{
source: "qrc:/qml/fonts/SourceSansPro-Regular.ttf"
}

247
mix/qml/StateDialog.qml

@ -2,6 +2,7 @@ import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Layouts 1.1
import QtQuick.Window 2.0
import QtQuick.Controls.Styles 1.3
import org.ethereum.qml.QEther 1.0
import "js/QEtherHelper.js" as QEtherHelper
import "js/TransactionHelper.js" as TransactionHelper
@ -9,12 +10,13 @@ import "."
Window {
id: modalStateDialog
modality: Qt.WindowModal
modality: Qt.ApplicationModal
width: 640
width: 450
height: 480
title: qsTr("Edit State")
visible: false
color: StateDialogStyle.generic.backgroundColor
property alias stateTitle: titleField.text
property alias stateBalance: balanceField.value
@ -34,10 +36,15 @@ Window {
transactionsModel.append(item.transactions[t]);
stateTransactions.push(item.transactions[t]);
}
modalStateDialog.setX((Screen.width - width) / 2);
modalStateDialog.setY((Screen.height - height) / 2);
visible = true;
isDefault = setDefault;
titleField.focus = true;
defaultCheckBox.enabled = !isDefault;
forceActiveFocus();
}
function close() {
@ -54,74 +61,138 @@ Window {
return item;
}
GridLayout {
id: dialogContent
columns: 2
ColumnLayout {
anchors.fill: parent
anchors.margins: 10
rowSpacing: 10
columnSpacing: 10
ColumnLayout {
id: dialogContent
anchors.top: parent.top
Label {
text: qsTr("Title")
}
TextField {
id: titleField
focus: true
Layout.fillWidth: true
}
RowLayout
{
Layout.fillWidth: true
DefaultLabel {
Layout.preferredWidth: 75
text: qsTr("Title")
}
DefaultTextField
{
id: titleField
Layout.fillWidth: true
}
}
Label {
text: qsTr("Balance")
}
Ether {
id: balanceField
edit: true
displayFormattedValue: true
Layout.fillWidth: true
}
CommonSeparator
{
Layout.fillWidth: true
}
Label {
text: qsTr("Default")
}
CheckBox {
id: defaultCheckBox
Layout.fillWidth: true
}
RowLayout
{
Layout.fillWidth: true
DefaultLabel {
Layout.preferredWidth: 75
text: qsTr("Balance")
}
Ether {
id: balanceField
edit: true
displayFormattedValue: true
Layout.fillWidth: true
}
}
Label {
text: qsTr("Transactions")
}
ListView {
Layout.fillWidth: true
Layout.fillHeight: true
model: transactionsModel
delegate: transactionRenderDelegate
}
CommonSeparator
{
Layout.fillWidth: true
}
Label {
RowLayout
{
Layout.fillWidth: true
DefaultLabel {
Layout.preferredWidth: 75
text: qsTr("Default")
}
CheckBox {
id: defaultCheckBox
Layout.fillWidth: true
}
}
CommonSeparator
{
Layout.fillWidth: true
}
}
Button {
text: qsTr("Add")
onClicked: transactionsModel.addTransaction()
}
}
RowLayout {
anchors.bottom: parent.bottom
anchors.right: parent.right;
ColumnLayout {
anchors.top: dialogContent.bottom
anchors.topMargin: 5
spacing: 0
RowLayout
{
Layout.preferredWidth: 150
DefaultLabel {
text: qsTr("Transactions: ")
}
Button
{
iconSource: "qrc:/qml/img/plus.png"
action: newTrAction
width: 10
height: 10
anchors.right: parent.right
}
Button {
text: qsTr("OK");
onClicked: {
close();
accepted();
Action {
id: newTrAction
tooltip: qsTr("Create a new transaction")
onTriggered: transactionsModel.addTransaction()
}
}
ScrollView
{
Layout.fillHeight: true
Layout.preferredWidth: 300
Column
{
Layout.fillHeight: true
Repeater
{
id: trRepeater
model: transactionsModel
delegate: transactionRenderDelegate
visible: transactionsModel.count > 0
height: 20 * transactionsModel.count
}
}
}
CommonSeparator
{
Layout.fillWidth: true
}
}
Button {
text: qsTr("Cancel");
onClicked: close();
RowLayout
{
anchors.bottom: parent.bottom
anchors.right: parent.right;
Button {
text: qsTr("OK");
onClicked: {
close();
accepted();
}
}
Button {
text: qsTr("Cancel");
onClicked: close();
}
}
}
@ -149,38 +220,47 @@ Window {
Component {
id: transactionRenderDelegate
Item {
id: wrapperItem
height: 20
width: parent.width
RowLayout {
anchors.fill: parent
Text {
Layout.fillWidth: true
Layout.fillHeight: true
text: functionId
font.pointSize: StateStyle.general.basicFontSize //12
verticalAlignment: Text.AlignBottom
}
ToolButton {
text: qsTr("Edit");
visible: !stdContract
Layout.fillHeight: true
onClicked: transactionsModel.editTransaction(index)
RowLayout {
DefaultLabel {
Layout.preferredWidth: 150
text: functionId
}
Button
{
id: deleteBtn
iconSource: "qrc:/qml/img/delete_sign.png"
action: deleteAction
width: 10
height: 10
Action {
id: deleteAction
tooltip: qsTr("Delete")
onTriggered: transactionsModel.deleteTransaction(index)
}
ToolButton {
visible: index >= 0 ? !transactionsModel.get(index).executeConstructor : false
text: qsTr("Delete");
Layout.fillHeight: true
onClicked: transactionsModel.deleteTransaction(index)
}
Button
{
iconSource: "qrc:/qml/img/edit.png"
action: editAction
visible: !stdContract
width: 10
height: 10
Action {
id: editAction
tooltip: qsTr("Edit")
onTriggered: transactionsModel.editTransaction(index)
}
}
}
}
TransactionDialog {
TransactionDialog
{
id: transactionDialog
onAccepted: {
onAccepted:
{
var item = transactionDialog.getItem();
if (transactionDialog.transactionIndex < transactionsModel.count) {
@ -192,5 +272,4 @@ Window {
}
}
}
}

17
mix/qml/StateDialogStyle.qml

@ -0,0 +1,17 @@
pragma Singleton
import QtQuick 2.0
QtObject {
property QtObject generic: QtObject
{
property string backgroundColor: "#ededed"
}
property QtObject stateDialog: QtObject
{
}
property QtObject transactionDialog: QtObject
{
}
}

10
mix/qml/StateListModel.qml

@ -2,6 +2,7 @@ import QtQuick 2.2
import QtQuick.Controls.Styles 1.1
import QtQuick.Controls 1.1
import QtQuick.Dialogs 1.1
import QtQuick.Window 2.2
import QtQuick.Layouts 1.1
import org.ethereum.qml.QEther 1.0
import "js/QEtherHelper.js" as QEtherHelper
@ -24,7 +25,7 @@ Item {
functionId: t.functionId,
url: t.url,
value: QEtherHelper.createEther(t.value.value, t.value.unit),
gas: QEtherHelper.createEther(t.gas.value, t.gas.unit),
gas: QEtherHelper.createBigInt(t.gas.value), //t.gas,//QEtherHelper.createEther(t.gas.value, t.gas.unit),
gasPrice: QEtherHelper.createEther(t.gasPrice.value, t.gasPrice.unit),
executeConstructor: t.executeConstructor,
stdContract: t.stdContract,
@ -80,7 +81,7 @@ Item {
functionId: t.functionId,
url: t.url,
value: { value: t.value.value, unit: t.value.unit },
gas: { value: t.gas.value, unit: t.gas.unit },
gas: { value: t.gas.value() },
gasPrice: { value: t.gasPrice.value, unit: t.gasPrice.unit },
executeConstructor: t.executeConstructor,
stdContract: t.stdContract,
@ -156,7 +157,7 @@ Item {
function defaultTransactionItem() {
return {
value: QEtherHelper.createEther("100", QEther.Wei),
gas: QEtherHelper.createEther("125000", QEther.Wei),
gas: QEtherHelper.createBigInt("125000"),
gasPrice: QEtherHelper.createEther("10000000000000", QEther.Wei),
executeConstructor: false,
stdContract: false
@ -164,7 +165,8 @@ Item {
}
function createDefaultState() {
var ether = QEtherHelper.createEther("100000000000000000000000000", QEther.Wei);
//var ether = QEtherHelper.createEther("100000000000000000000000000", QEther.Wei);
var ether = QEtherHelper.createEther("1000000", QEther.Ether);
var item = {
title: "",
balance: ether,

8
mix/qml/StatusPane.qml

@ -38,11 +38,17 @@ Rectangle {
Connections {
target:clientModel
onRunStarted: infoMessage(qsTr("Running transactions.."));
onRunStarted: infoMessage(qsTr("Running transactions..."));
onRunFailed: infoMessage(qsTr("Error running transactions"));
onRunComplete: infoMessage(qsTr("Run complete"));
onNewBlock: infoMessage(qsTr("New block created"));
}
Connections {
target:projectModel
onDeploymentStarted: infoMessage(qsTr("Running deployment..."));
onDeploymentError: infoMessage(error);
onDeploymentComplete: infoMessage(qsTr("Deployment complete"));
}
color: "transparent"
anchors.fill: parent

19
mix/qml/Style.qml

@ -0,0 +1,19 @@
pragma Singleton
import QtQuick 2.0
QtObject {
function absoluteSize(rel)
{
return systemPointSize + rel;
}
property QtObject generic: QtObject {
property QtObject layout: QtObject {
property string separatorColor: "#808080"
}
property QtObject size: QtObject {
property string titlePointSize: absoluteSize(0)
}
}
}

467
mix/qml/TransactionDialog.qml

@ -2,19 +2,22 @@ import QtQuick 2.2
import QtQuick.Controls 1.1
import QtQuick.Layouts 1.1
import QtQuick.Window 2.0
import QtQuick.Controls.Styles 1.3
import org.ethereum.qml.QEther 1.0
import "js/TransactionHelper.js" as TransactionHelper
import "."
Window {
id: modalTransactionDialog
modality: Qt.WindowModal
width:640
height:640
modality: Qt.ApplicationModal
width: 450
height: (paramsModel.count > 0 ? 500 : 300)
visible: false
color: StateDialogStyle.generic.backgroundColor
title: qsTr("Edit Transaction")
property int transactionIndex
property alias transactionParams: paramsModel;
property alias gas: gasField.value;
property alias gas: gasValueEdit.gasValue;
property alias gasPrice: gasPriceField.value;
property alias transactionValue: valueField.value;
property alias functionId: functionComboBox.currentText;
@ -33,7 +36,7 @@ Window {
rowGasPrice.visible = !useTransactionDefaultValue;
transactionIndex = index;
gasField.value = item.gas;
gasValueEdit.gasValue = item.gas;
gasPriceField.value = item.gasPrice;
valueField.value = item.value;
var functionId = item.functionId;
@ -63,6 +66,9 @@ Window {
for (var p = 0; p < parameters.length; p++)
loadParameter(parameters[p]);
}
modalTransactionDialog.setX((Screen.width - width) / 2);
modalTransactionDialog.setY((Screen.height - height) / 2);
visible = true;
valueField.focus = true;
}
@ -105,6 +111,15 @@ Window {
}
}
function param(name)
{
for (var k = 0; k < paramsModel.count; k++)
{
if (paramsModel.get(k).name === name)
return paramsModel.get(k);
}
}
function close()
{
visible = false;
@ -156,263 +171,283 @@ Window {
}
ColumnLayout {
id: dialogContent
width: parent.width
anchors.left: parent.left
anchors.right: parent.right
anchors.fill: parent
anchors.margins: 10
spacing: 30
RowLayout
SourceSansProLight
{
id: rowFunction
Layout.fillWidth: true
height: 150
Label {
Layout.preferredWidth: 75
text: qsTr("Function")
}
ComboBox {
id: functionComboBox
id: lightFont
}
ColumnLayout {
id: dialogContent
anchors.top: parent.top
spacing: 10
RowLayout
{
id: rowFunction
Layout.fillWidth: true
currentIndex: -1
textRole: "text"
editable: false
model: ListModel {
id: functionsModel
height: 150
DefaultLabel {
Layout.preferredWidth: 75
text: qsTr("Function")
}
onCurrentIndexChanged: {
loadParameters();
ComboBox {
id: functionComboBox
Layout.preferredWidth: 350
currentIndex: -1
textRole: "text"
editable: false
model: ListModel {
id: functionsModel
}
onCurrentIndexChanged: {
loadParameters();
}
style: ComboBoxStyle {
font: lightFont.name
}
}
}
}
RowLayout
{
id: rowValue
Layout.fillWidth: true
Label {
Layout.preferredWidth: 75
text: qsTr("Value")
CommonSeparator
{
Layout.fillWidth: true
}
Rectangle
RowLayout
{
id: rowValue
Layout.fillWidth: true
height: 150
DefaultLabel {
Layout.preferredWidth: 75
text: qsTr("Value")
}
Ether {
id: valueField
edit: true
displayFormattedValue: true
}
}
}
RowLayout
{
id: rowGas
Layout.fillWidth: true
Label {
Layout.preferredWidth: 75
text: qsTr("Gas")
CommonSeparator
{
Layout.fillWidth: true
}
Rectangle
RowLayout
{
id: rowGas
Layout.fillWidth: true
Ether {
id: gasField
edit: true
displayFormattedValue: true
height: 150
DefaultLabel {
Layout.preferredWidth: 75
text: qsTr("Gas")
}
DefaultTextField
{
property variant gasValue
onGasValueChanged: text = gasValue.value();
onTextChanged: gasValue.setValue(text);
implicitWidth: 200
id: gasValueEdit;
}
}
}
RowLayout
{
id: rowGasPrice
Layout.fillWidth: true
Label {
Layout.preferredWidth: 75
text: qsTr("Gas Price")
CommonSeparator
{
Layout.fillWidth: true
}
Rectangle
RowLayout
{
id: rowGasPrice
Layout.fillWidth: true
height: 150
DefaultLabel {
Layout.preferredWidth: 75
text: qsTr("Gas Price")
}
Ether {
id: gasPriceField
edit: true
displayFormattedValue: true
}
}
}
RowLayout
{
Layout.fillWidth: true
Label {
text: qsTr("Parameters")
Layout.preferredWidth: 75
}
TableView {
model: paramsModel
Layout.preferredWidth: 120 * 2 + 240
Layout.minimumHeight: 150
Layout.preferredHeight: 400
Layout.maximumHeight: 600
TableViewColumn {
role: "name"
title: qsTr("Name")
width: 120
}
TableViewColumn {
role: "type"
title: qsTr("Type")
width: 120
}
TableViewColumn {
role: "value"
title: qsTr("Value")
width: 240
}
rowDelegate: rowDelegate
itemDelegate: editableDelegate
CommonSeparator
{
Layout.fillWidth: true
}
}
}
RowLayout
{
anchors.bottom: parent.bottom
anchors.right: parent.right;
Button {
text: qsTr("OK");
onClicked: {
close();
accepted();
DefaultLabel {
id: paramLabel
text: qsTr("Parameters:")
Layout.preferredWidth: 75
visible: paramsModel.count > 0
}
}
Button {
text: qsTr("Cancel");
onClicked: close();
}
}
ListModel {
id: paramsModel
}
Component {
id: rowDelegate
Item {
height: 100
}
}
Component {
id: editableDelegate
Item {
Loader {
id: loaderEditor
anchors.fill: parent
anchors.margins: 4
Connections {
target: loaderEditor.item
onTextChanged: {
if (styleData.role === "value" && styleData.row < paramsModel.count)
loaderEditor.updateValue(styleData.row, styleData.role, loaderEditor.item.text);
}
}
function updateValue(row, role, value)
{
paramsModel.setProperty(styleData.row, styleData.role, value);
}
sourceComponent:
{
if (styleData.role === "value")
{
if (paramsModel.get(styleData.row) === undefined)
return null;
if (paramsModel.get(styleData.row).type.indexOf("int") !== -1)
return intViewComp;
else if (paramsModel.get(styleData.row).type.indexOf("bool") !== -1)
return boolViewComp;
else if (paramsModel.get(styleData.row).type.indexOf("string") !== -1)
return stringViewComp;
else if (paramsModel.get(styleData.row).type.indexOf("hash") !== -1)
return hashViewComp;
}
else
return editor;
}
Component
{
id: intViewComp
QIntTypeView
{
id: intView
text: styleData.value
}
}
Component
ScrollView
{
anchors.top: paramLabel.bottom
anchors.topMargin: 10
Layout.preferredWidth: 350
Layout.fillHeight: true
visible: paramsModel.count > 0
Column
{
id: boolViewComp
QBoolTypeView
id: paramRepeater
Layout.fillWidth: true
Layout.fillHeight: true
spacing: 3
Repeater
{
id: boolView
defaultValue: "1"
Component.onCompleted:
height: 20 * paramsModel.count
model: paramsModel
visible: paramsModel.count > 0
RowLayout
{
loaderEditor.updateValue(styleData.row, styleData.role,
(paramsModel.get(styleData.row).value === "" ? defaultValue :
paramsModel.get(styleData.row).value));
text = (paramsModel.get(styleData.row).value === "" ? defaultValue : paramsModel.get(styleData.row).value);
id: row
Layout.fillWidth: true
height: 20
DefaultLabel {
id: typeLabel
text: type
Layout.preferredWidth: 50
}
DefaultLabel {
id: nameLabel
text: name
Layout.preferredWidth: 80
}
DefaultLabel {
id: equalLabel
text: "="
Layout.preferredWidth: 15
}
Loader
{
id: typeLoader
Layout.preferredWidth: 150
function getCurrent()
{
return modalTransactionDialog.param(name);
}
Connections {
target: typeLoader.item
onTextChanged: {
typeLoader.getCurrent().value = typeLoader.item.text;
}
}
sourceComponent:
{
if (type.indexOf("int") !== -1)
return intViewComp;
else if (type.indexOf("bool") !== -1)
return boolViewComp;
else if (type.indexOf("string") !== -1)
return stringViewComp;
else if (type.indexOf("hash") !== -1)
return hashViewComp;
else
return null;
}
Component
{
id: intViewComp
QIntTypeView
{
height: 20
width: 150
id: intView
text: typeLoader.getCurrent().value
}
}
Component
{
id: boolViewComp
QBoolTypeView
{
height: 20
width: 150
id: boolView
defaultValue: "1"
Component.onCompleted:
{
var current = typeLoader.getCurrent().value;
(current === "" ? text = defaultValue : text = current);
}
}
}
Component
{
id: stringViewComp
QStringTypeView
{
height: 20
width: 150
id: stringView
text:
{
return typeLoader.getCurrent().value
}
}
}
Component
{
id: hashViewComp
QHashTypeView
{
height: 20
width: 150
id: hashView
text: typeLoader.getCurrent().value
}
}
}
}
}
}
}
Component
{
id: stringViewComp
QStringTypeView
{
id: stringView
text: styleData.value
}
}
Component
{
id: hashViewComp
QHashTypeView
{
id: hashView
text: styleData.value
}
}
CommonSeparator
{
Layout.fillWidth: true
visible: paramsModel.count > 0
}
}
Component {
id: editor
TextInput {
id: textinput
readOnly: true
color: styleData.textColor
text: styleData.value
MouseArea {
id: mouseArea
anchors.fill: parent
hoverEnabled: true
onClicked: textinput.forceActiveFocus()
}
}
RowLayout
{
anchors.bottom: parent.bottom
anchors.right: parent.right;
Button {
text: qsTr("OK");
onClicked: {
close();
accepted();
}
}
Button {
text: qsTr("Cancel");
onClicked: close();
}
}
}
ListModel {
id: paramsModel
}
}

10
mix/qml/TransactionLog.qml

@ -32,6 +32,16 @@ Item {
anchors.fill: parent
RowLayout {
Connections
{
target: projectModel
onProjectSaved:
{
if (codeModel.hasContract && !clientModel.running)
projectModel.stateListModel.debugDefaultState();
}
}
ComboBox {
id: statesCombo
model: projectModel.stateListModel

51
mix/qml/WebPreview.qml

@ -43,8 +43,8 @@ Item {
}
function changePage() {
if (pageCombo.currentIndex >=0 && pageCombo.currentIndex < pageListModel.count) {
setPreviewUrl(pageListModel.get(pageCombo.currentIndex).path);
if (pageCombo.currentIndex >= 0 && pageCombo.currentIndex < pageListModel.count) {
setPreviewUrl(httpServer.url + "/" + pageListModel.get(pageCombo.currentIndex).documentId);
} else {
setPreviewUrl("");
}
@ -54,7 +54,7 @@ Item {
onAppLoaded: {
//We need to load the container using file scheme so that web security would allow loading local files in iframe
var containerPage = fileIo.readFile("qrc:///qml/html/WebContainer.html");
webView.loadHtml(containerPage, "file:///WebContainer.html")
webView.loadHtml(containerPage, httpServer.url + "/WebContainer.html")
}
}
@ -72,8 +72,8 @@ Item {
Connections {
target: projectModel
onProjectSaved : reloadOnSave();
onDocumentSaved: reloadOnSave();
//onProjectSaved : reloadOnSave();
//onDocumentSaved: reloadOnSave();
onDocumentAdded: {
var document = projectModel.getDocument(documentId)
if (document.isHtml)
@ -112,16 +112,35 @@ Item {
accept: true
port: 8893
onClientConnected: {
//filter polling spam
//TODO: do it properly
//var log = _request.content.indexOf("eth_changed") < 0;
var log = true;
if (log)
console.log(_request.content);
var response = clientModel.apiCall(_request.content);
if (log)
console.log(response);
_request.setResponse(response);
var urlPath = _request.url.toString();
if (urlPath.indexOf("/rpc/") === 0)
{
//jsonrpc request
//filter polling requests //TODO: do it properly
var log = _request.content.indexOf("eth_changed") < 0;
if (log)
console.log(_request.content);
var response = clientModel.apiCall(_request.content);
if (log)
console.log(response);
_request.setResponse(response);
}
else
{
//document request
var documentId = urlPath.substr(urlPath.lastIndexOf("/") + 1);
var content = "";
if (projectModel.codeEditor.isDocumentOpen(documentId))
content = projectModel.codeEditor.getDocumentText(documentId);
else
content = fileIo.readFile(projectModel.getDocument(documentId).path);
if (documentId === pageListModel.get(pageCombo.currentIndex).documentId) {
//root page, inject deployment script
content = "<script>deploy=parent.deploy</script>\n" + content;
_request.setResponseContentType("text/html");
}
_request.setResponse(content);
}
}
}
@ -163,7 +182,7 @@ Item {
onLoadingChanged: {
if (!loading) {
initialized = true;
webView.runJavaScript("init(\"" + httpServer.url + "\")");
webView.runJavaScript("init(\"" + httpServer.url + "/rpc/\")");
if (pendingPageUrl)
setPreviewUrl(pendingPageUrl);
}

8
mix/qml/html/WebContainer.html

@ -21,13 +21,19 @@ updateContract = function(address, contractFace) {
window.contractAddress = address;
window.contractInterface = contractFace;
window.contract = window.web3.eth.contract(address, contractFace);
window.deploy = {
contractAddress: address,
contractInterface: contractFace,
contract: window.contract,
web3: window.web3
};
}
};
init = function(url) {
web3 = require('web3');
web3.setProvider(new web3.providers.HttpSyncProvider(url));
window.web3 = web3;
web3.setProvider(new web3.providers.HttpSyncProvider(url));
};
</script>

BIN
mix/qml/img/delete_sign.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 465 B

BIN
mix/qml/img/edit.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 511 B

BIN
mix/qml/img/plus.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 254 B

100
mix/qml/js/ProjectModel.js

@ -39,7 +39,11 @@ function closeProject() {
function saveProject() {
if (!isEmpty) {
var projectData = { files: [] };
var projectData = {
files: [],
title: projectTitle,
deploymentAddress: deploymentAddress
};
for (var i = 0; i < projectListModel.count; i++)
projectData.files.push(projectListModel.get(i).fileName)
projectSaving(projectData);
@ -60,6 +64,7 @@ function loadProject(path) {
var parts = path.split("/");
projectData.title = parts[parts.length - 2];
}
deploymentAddress = projectData.deploymentAddress ? projectData.deploymentAddress : "";
projectTitle = projectData.title;
projectPath = path;
if (!projectData.files)
@ -246,3 +251,96 @@ function generateFileName(name, extension) {
return fileName
}
var jsonRpcRequestId = 1;
function deployProject(force) {
saveAll(); //TODO: ask user
if (!force && deploymentAddress !== "") {
deployWarningDialog.visible = true;
return;
}
var date = new Date();
var deploymentId = date.toLocaleString(Qt.locale(), "ddMMyyHHmmsszzz");
var jsonRpcUrl = "http://localhost:8080";
console.log("Deploying " + deploymentId + " to " + jsonRpcUrl);
deploymentStarted();
var code = codeModel.codeHex
var rpcRequest = JSON.stringify({
jsonrpc: "2.0",
method: "eth_transact",
params: [ {
"code": code
} ],
id: jsonRpcRequestId++
});
var httpRequest = new XMLHttpRequest()
httpRequest.open("POST", jsonRpcUrl, true);
httpRequest.setRequestHeader("Content-type", "application/json");
httpRequest.setRequestHeader("Content-length", rpcRequest.length);
httpRequest.setRequestHeader("Connection", "close");
httpRequest.onreadystatechange = function() {
if (httpRequest.readyState === XMLHttpRequest.DONE) {
if (httpRequest.status === 200) {
var rpcResponse = JSON.parse(httpRequest.responseText);
var address = rpcResponse.result;
console.log("Created contract, address: " + address);
finalizeDeployment(deploymentId, address);
} else {
var errorText = qsTr("Deployment error: RPC server HTTP status ") + httpRequest.status;
console.log(errorText);
deploymentError(errorText);
}
}
}
httpRequest.send(rpcRequest);
}
function finalizeDeployment(deploymentId, address) {
//create a dir for frontend files and copy them
var deploymentDir = projectPath + deploymentId + "/";
fileIo.makeDir(deploymentDir);
for (var i = 0; i < projectListModel.count; i++) {
var doc = projectListModel.get(i);
if (doc.isContract)
continue;
if (doc.isHtml) {
//inject the script to access contract API
//TODO: use a template
var html = fileIo.readFile(doc.path);
var insertAt = html.indexOf("<head>")
if (insertAt < 0)
insertAt = 0;
else
insertAt += 6;
html = html.substr(0, insertAt) +
"<script src=\"bignumber.min.js\"></script>" +
"<script src=\"ethereum.js\"></script>" +
"<script src=\"deployment.js\"></script>" +
html.substr(insertAt);
fileIo.writeFile(deploymentDir + doc.fileName, html);
}
else
fileIo.copyFile(doc.path, deploymentDir + doc.fileName);
}
//write deployment js
var deploymentJs =
"// Autogenerated by Mix\n" +
"var web3 = require(\"web3\");\n" +
"var contractInterface = " + codeModel.code.contractInterface + ";\n" +
"deploy = {\n" +
"\tweb3: web3,\n" +
"\tcontractAddress: \"" + address + "\",\n" +
"\tcontractInterface: contractInterface,\n" +
"};\n" +
"deploy.contract = web3.eth.contract(deploy.contractAddress, deploy.contractInterface);\n";
fileIo.writeFile(deploymentDir + "deployment.js", deploymentJs);
//copy scripts
fileIo.copyFile("qrc:///js/bignumber.min.js", deploymentDir + "bignumber.min.js");
fileIo.copyFile("qrc:///js/webthree.js", deploymentDir + "ethereum.js");
deploymentAddress = address;
saveProject();
deploymentComplete();
}

9
mix/qml/js/QEtherHelper.js

@ -6,3 +6,12 @@ function createEther(_value, _unit, _parent)
ether.setUnit(_unit);
return ether;
}
function createBigInt(_value)
{
var bigintComponent = Qt.createComponent("qrc:/qml/BigIntValue.qml");
var bigint = bigintComponent.createObject();
bigint.setValue(_value);
return bigint;
}

2
mix/qml/js/TransactionHelper.js

@ -5,7 +5,7 @@ function defaultTransaction()
return {
value: createEther("0", QEther.Wei),
functionId: "",
gas: createEther("125000", QEther.Wei),
gas: createBigInt("125000"),
gasPrice: createEther("100000", QEther.Wei),
executeConstructor: false,
parameters: {}

11
mix/qml/main.qml

@ -42,6 +42,8 @@ ApplicationWindow {
MenuSeparator {}
MenuItem { action: editStatesAction }
MenuSeparator {}
MenuItem { action: deployViaRpcAction }
MenuSeparator {}
MenuItem { action: toggleRunOnLoadAction }
}
Menu {
@ -265,7 +267,7 @@ ApplicationWindow {
selectFolder: false
onAccepted: {
var paths = addExistingFileDialog.fileUrls;
projectModel.doAddExistingFiles(paths);
projectModel.addExistingFiles(paths);
}
}
@ -301,4 +303,11 @@ ApplicationWindow {
onTriggered: projectModel.openPrevDocument();
}
Action {
id: deployViaRpcAction
text: qsTr("Deploy to Network")
shortcut: "Ctrl+Shift+D"
enabled: !projectModel.isEmpty && codeModel.hasContract
onTriggered: projectModel.deployProject();
}
}

2
mix/qml/qmldir

@ -1,3 +1,5 @@
singleton Style 1.0 Style.qml
singleton StateDialogStyle 1.0 StateDialogStyle.qml
singleton ProjectFilesStyle 1.0 ProjectFilesStyle.qml
singleton DebuggerPaneStyle 1.0 DebuggerPaneStyle.qml
singleton StateStyle 1.0 StateStyle.qml

11
mix/res.qrc

@ -81,10 +81,21 @@
<file>qml/img/closedtriangleindicator_filesproject.png</file>
<file>qml/img/opentriangleindicator_filesproject.png</file>
<file>qml/img/projecticon.png</file>
<file>qml/SourceSansProRegular.qml</file>
<file>qml/SourceSansProBold.qml</file>
<file>qml/SourceSansProLight.qml</file>
<file>qml/StateDialogStyle.qml</file>
<file>qml/ProjectFilesStyle.qml</file>
<file>qml/DebuggerPaneStyle.qml</file>
<file>qml/CodeEditorStyle.qml</file>
<file>qml/StatusPaneStyle.qml</file>
<file>qml/StateStyle.qml</file>
<file>qml/img/plus.png</file>
<file>qml/img/delete_sign.png</file>
<file>qml/img/edit.png</file>
<file>qml/DefaultLabel.qml</file>
<file>qml/DefaultTextField.qml</file>
<file>qml/CommonSeparator.qml</file>
<file>qml/Style.qml</file>
</qresource>
</RCC>

88
neth/main.cpp

@ -81,6 +81,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 (tmp forced to 1)." << endl
<< " -x,--peers <number> Attempt to connect to given number of peers (default: 5)." << endl
@ -307,6 +308,7 @@ int main(int argc, char** argv)
unsigned mining = ~(unsigned)0;
NodeMode mode = NodeMode::Full;
unsigned peers = 5;
int miners = -1;
#if ETH_JSONRPC
int jsonrpc = 8080;
#endif
@ -366,7 +368,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)
@ -401,6 +419,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 == "-h" || arg == "--help")
help();
else if (arg == "-V" || arg == "--version")
@ -415,12 +435,15 @@ int main(int argc, char** argv)
cout << credits();
NetworkPreferences netPrefs(listenPort, publicIP, upnp, useLocal);
auto nodesState = contents((dbPath.size() ? dbPath : getDataDir()) + "/network.rlp");
dev::WebThreeDirect web3(
"NEthereum(++)/" + clientName + "v" + dev::Version + "/" DEV_QUOTED(ETH_BUILD_TYPE) "/" DEV_QUOTED(ETH_BUILD_PLATFORM),
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;
@ -431,9 +454,7 @@ 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();
if (bootstrap)
@ -709,9 +730,21 @@ int main(int argc, char** argv)
}
else
{
Secret secret = h256(fromHex(sechex));
Address dest = h160(fromHex(fields[0]));
c->transact(secret, amount, dest, data, gas, gasPrice);
try
{
Secret secret = h256(fromHex(sechex, ThrowType::Throw));
Address dest = h160(fromHex(fields[0], ThrowType::Throw));
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";
}
}
}
}
@ -749,8 +782,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(fields[0]));
c->transact(us.secret(), amount, dest, bytes(), minGas);
try
{
Address dest = h160(fromHex(fields[0], 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";
}
}
}
}
@ -803,14 +848,31 @@ 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;
@ -961,7 +1023,7 @@ int main(int argc, char** argv)
// Peers
y = 1;
for (PeerInfo const& i: web3.peers())
for (PeerSessionInfo const& i: web3.peers())
{
auto s = boost::format("%1% ms - %2%:%3% - %4%") %
toString(chrono::duration_cast<chrono::milliseconds>(i.lastPing).count()) %

1
solc/CMakeLists.txt

@ -4,6 +4,7 @@ set(CMAKE_AUTOMOC OFF)
aux_source_directory(. SRC_LIST)
include_directories(${Boost_INCLUDE_DIRS})
include_directories(${JSONCPP_INCLUDE_DIRS})
include_directories(..)
set(EXECUTABLE solc)

2
test/SolidityABIJSON.cpp

@ -22,7 +22,7 @@
#include <boost/test/unit_test.hpp>
#include <libsolidity/CompilerStack.h>
#include <jsoncpp/json/json.h>
#include <json/json.h>
#include <libdevcore/Exceptions.h>
namespace dev

184
test/SolidityEndToEndTest.cpp

@ -668,7 +668,7 @@ BOOST_AUTO_TEST_CASE(mapping_state)
testSolidityAgainstCpp("getVoteCount(address)", getVoteCount, u160(0));
testSolidityAgainstCpp("getVoteCount(address)", getVoteCount, u160(1));
testSolidityAgainstCpp("getVoteCount(address)", getVoteCount, u160(2));
// voting without vote right shourd be rejected
// voting without vote right should be rejected
testSolidityAgainstCpp("vote(address,address)", vote, u160(0), u160(2));
testSolidityAgainstCpp("getVoteCount(address)", getVoteCount, u160(0));
testSolidityAgainstCpp("getVoteCount(address)", getVoteCount, u160(1));
@ -963,7 +963,7 @@ BOOST_AUTO_TEST_CASE(multiple_elementary_accessors)
compileAndRun(sourceCode);
BOOST_CHECK(callContractFunction("data()") == encodeArgs(8));
BOOST_CHECK(callContractFunction("name()") == encodeArgs("Celina"));
BOOST_CHECK(callContractFunction("a_hash()") == encodeArgs(dev::sha3(bytes({0x7b}))));
BOOST_CHECK(callContractFunction("a_hash()") == encodeArgs(dev::sha3(bytes(1, 0x7b))));
BOOST_CHECK(callContractFunction("an_address()") == encodeArgs(toBigEndian(u160(0x1337))));
BOOST_CHECK(callContractFunction("super_secret_data()") == bytes());
}
@ -2202,8 +2202,8 @@ BOOST_AUTO_TEST_CASE(sha3_multiple_arguments_with_numeric_literals)
BOOST_CHECK(callContractFunction("foo(uint256,uint16)", 10, 12) == encodeArgs(
dev::sha3(
toBigEndian(u256(10)) +
bytes({0x0, 0xc}) +
bytes({0x91}))));
bytes{0x0, 0xc} +
bytes(1, 0x91))));
}
BOOST_AUTO_TEST_CASE(sha3_multiple_arguments_with_string_literals)
@ -2226,9 +2226,9 @@ BOOST_AUTO_TEST_CASE(sha3_multiple_arguments_with_string_literals)
BOOST_CHECK(callContractFunction("bar(uint256,uint16)", 10, 12) == encodeArgs(
dev::sha3(
toBigEndian(u256(10)) +
bytes({0x0, 0xc}) +
bytes({0x91}) +
bytes({0x66, 0x6f, 0x6f}))));
bytes{0x0, 0xc} +
bytes(1, 0x91) +
bytes{0x66, 0x6f, 0x6f})));
}
BOOST_AUTO_TEST_CASE(generic_call)
@ -2254,6 +2254,176 @@ BOOST_AUTO_TEST_CASE(generic_call)
BOOST_CHECK_EQUAL(m_state.balance(m_contractAddress), 50 - 2);
}
BOOST_AUTO_TEST_CASE(store_bytes)
{
// this test just checks that the copy loop does not mess up the stack
char const* sourceCode = R"(
contract C {
function save() returns (uint r) {
r = 23;
savedData = msg.data;
r = 24;
}
bytes savedData;
}
)";
compileAndRun(sourceCode);
// empty copy loop
BOOST_CHECK(callContractFunction("save()") == encodeArgs(24));
BOOST_CHECK(callContractFunction("save()", "abcdefg") == encodeArgs(24));
}
BOOST_AUTO_TEST_CASE(call_forward_bytes)
{
char const* sourceCode = R"(
contract receiver {
uint public received;
function receive(uint x) { received += x + 1; }
function() { received = 0x80; }
}
contract sender {
function sender() { rec = new receiver(); }
function() { savedData = msg.data; }
function forward() returns (bool) { rec.call(savedData); return true; }
function clear() returns (bool) { delete savedData; return true; }
function val() returns (uint) { return rec.received(); }
receiver rec;
bytes savedData;
}
)";
compileAndRun(sourceCode, 0, "sender");
BOOST_CHECK(callContractFunction("receive(uint256)", 7) == bytes());
BOOST_CHECK(callContractFunction("val()") == encodeArgs(0));
BOOST_CHECK(callContractFunction("forward()") == encodeArgs(true));
BOOST_CHECK(callContractFunction("val()") == encodeArgs(8));
BOOST_CHECK(callContractFunction("clear()") == encodeArgs(true));
BOOST_CHECK(callContractFunction("val()") == encodeArgs(8));
BOOST_CHECK(callContractFunction("forward()") == encodeArgs(true));
BOOST_CHECK(callContractFunction("val()") == encodeArgs(0x80));
}
BOOST_AUTO_TEST_CASE(copying_bytes_multiassign)
{
char const* sourceCode = R"(
contract receiver {
uint public received;
function receive(uint x) { received += x + 1; }
function() { received = 0x80; }
}
contract sender {
function sender() { rec = new receiver(); }
function() { savedData1 = savedData2 = msg.data; }
function forward(bool selector) returns (bool) {
if (selector) { rec.call(savedData1); delete savedData1; }
else { rec.call(savedData2); delete savedData2; }
return true;
}
function val() returns (uint) { return rec.received(); }
receiver rec;
bytes savedData1;
bytes savedData2;
}
)";
compileAndRun(sourceCode, 0, "sender");
BOOST_CHECK(callContractFunction("receive(uint256)", 7) == bytes());
BOOST_CHECK(callContractFunction("val()") == encodeArgs(0));
BOOST_CHECK(callContractFunction("forward(bool)", true) == encodeArgs(true));
BOOST_CHECK(callContractFunction("val()") == encodeArgs(8));
BOOST_CHECK(callContractFunction("forward(bool)", false) == encodeArgs(true));
BOOST_CHECK(callContractFunction("val()") == encodeArgs(16));
BOOST_CHECK(callContractFunction("forward(bool)", true) == encodeArgs(true));
BOOST_CHECK(callContractFunction("val()") == encodeArgs(0x80));
}
BOOST_AUTO_TEST_CASE(delete_removes_bytes_data)
{
char const* sourceCode = R"(
contract c {
function() { data = msg.data; }
function del() returns (bool) { delete data; return true; }
bytes data;
}
)";
compileAndRun(sourceCode);
BOOST_CHECK(callContractFunction("---", 7) == bytes());
BOOST_CHECK(!m_state.storage(m_contractAddress).empty());
BOOST_CHECK(callContractFunction("del()", 7) == encodeArgs(true));
BOOST_CHECK(m_state.storage(m_contractAddress).empty());
}
BOOST_AUTO_TEST_CASE(copy_from_calldata_removes_bytes_data)
{
char const* sourceCode = R"(
contract c {
function set() returns (bool) { data = msg.data; return true; }
function() { data = msg.data; }
bytes data;
}
)";
compileAndRun(sourceCode);
BOOST_CHECK(callContractFunction("set()", 1, 2, 3, 4, 5) == encodeArgs(true));
BOOST_CHECK(!m_state.storage(m_contractAddress).empty());
sendMessage(bytes(), false);
BOOST_CHECK(m_output == bytes());
BOOST_CHECK(m_state.storage(m_contractAddress).empty());
}
BOOST_AUTO_TEST_CASE(copy_removes_bytes_data)
{
char const* sourceCode = R"(
contract c {
function set() returns (bool) { data1 = msg.data; return true; }
function reset() returns (bool) { data1 = data2; return true; }
bytes data1;
bytes data2;
}
)";
compileAndRun(sourceCode);
BOOST_CHECK(callContractFunction("set()", 1, 2, 3, 4, 5) == encodeArgs(true));
BOOST_CHECK(!m_state.storage(m_contractAddress).empty());
BOOST_CHECK(callContractFunction("reset()") == encodeArgs(true));
BOOST_CHECK(m_state.storage(m_contractAddress).empty());
}
BOOST_AUTO_TEST_CASE(bytes_inside_mappings)
{
char const* sourceCode = R"(
contract c {
function set(uint key) returns (bool) { data[key] = msg.data; return true; }
function copy(uint from, uint to) returns (bool) { data[to] = data[from]; return true; }
mapping(uint => bytes) data;
}
)";
compileAndRun(sourceCode);
// store a short byte array at 1 and a longer one at 2
BOOST_CHECK(callContractFunction("set(uint256)", 1, 2) == encodeArgs(true));
BOOST_CHECK(callContractFunction("set(uint256)", 2, 2, 3, 4, 5) == encodeArgs(true));
BOOST_CHECK(!m_state.storage(m_contractAddress).empty());
// copy shorter to longer
BOOST_CHECK(callContractFunction("copy(uint256,uint256)", 1, 2) == encodeArgs(true));
BOOST_CHECK(!m_state.storage(m_contractAddress).empty());
// copy empty to both
BOOST_CHECK(callContractFunction("copy(uint256,uint256)", 99, 1) == encodeArgs(true));
BOOST_CHECK(!m_state.storage(m_contractAddress).empty());
BOOST_CHECK(callContractFunction("copy(uint256,uint256)", 99, 2) == encodeArgs(true));
BOOST_CHECK(m_state.storage(m_contractAddress).empty());
}
BOOST_AUTO_TEST_CASE(bytes_length_member)
{
char const* sourceCode = R"(
contract c {
function set() returns (bool) { data = msg.data; return true; }
function getLength() returns (uint) { return data.length; }
bytes data;
}
)";
compileAndRun(sourceCode);
BOOST_CHECK(callContractFunction("getLength()") == encodeArgs(0));
BOOST_CHECK(callContractFunction("set()", 1, 2) == encodeArgs(true));
BOOST_CHECK(callContractFunction("getLength()") == encodeArgs(4+32+32));
}
BOOST_AUTO_TEST_SUITE_END()
}

2
test/SolidityNatspecJSON.cpp

@ -21,7 +21,7 @@
*/
#include <boost/test/unit_test.hpp>
#include <jsoncpp/json/json.h>
#include <json/json.h>
#include <libsolidity/CompilerStack.h>
#include <libsolidity/Exceptions.h>
#include <libdevcore/Exceptions.h>

19
test/net.cpp

@ -30,7 +30,7 @@ using namespace dev::p2p;
namespace ba = boost::asio;
namespace bi = ba::ip;
BOOST_AUTO_TEST_SUITE(p2p)
BOOST_AUTO_TEST_SUITE(net)
/**
* Only used for testing. Not useful beyond tests.
@ -87,7 +87,7 @@ struct TestNodeTable: public NodeTable
bi::address ourIp = bi::address::from_string("127.0.0.1");
for (auto& n: _testNodes)
if (_count--)
noteNode(n.first.pub(), bi::udp::endpoint(ourIp, n.second));
noteActiveNode(n.first.pub(), bi::udp::endpoint(ourIp, n.second));
else
break;
}
@ -152,7 +152,7 @@ BOOST_AUTO_TEST_CASE(test_neighbours_packet)
out.sign(k.sec());
bytesConstRef packet(out.data.data(), out.data.size());
bytesConstRef rlpBytes(packet.cropped(97, packet.size() - 97));
bytesConstRef rlpBytes(packet.cropped(h256::size + Signature::size + 1));
Neighbours in = Neighbours::fromBytesConstRef(to, rlpBytes);
int count = 0;
for (auto n: in.nodes)
@ -182,7 +182,7 @@ BOOST_AUTO_TEST_CASE(kademlia)
// Not yet a 'real' test.
TestNodeTableHost node(8);
node.start();
node.nodeTable->join(); // ideally, joining with empty node table logs warning we can check for
node.nodeTable->discover(); // ideally, joining with empty node table logs warning we can check for
node.setup();
node.populate();
clog << "NodeTable:\n" << *node.nodeTable.get() << endl;
@ -190,15 +190,24 @@ BOOST_AUTO_TEST_CASE(kademlia)
node.populateAll();
clog << "NodeTable:\n" << *node.nodeTable.get() << endl;
auto nodes = node.nodeTable->nodes();
nodes.sort();
node.nodeTable->reset();
clog << "NodeTable:\n" << *node.nodeTable.get() << endl;
node.populate(1);
clog << "NodeTable:\n" << *node.nodeTable.get() << endl;
node.nodeTable->join();
node.nodeTable->discover();
this_thread::sleep_for(chrono::milliseconds(2000));
clog << "NodeTable:\n" << *node.nodeTable.get() << endl;
BOOST_REQUIRE_EQUAL(node.nodeTable->count(), 8);
auto netNodes = node.nodeTable->nodes();
netNodes.sort();
}
BOOST_AUTO_TEST_CASE(test_udp_once)

78
test/peer.cpp

@ -20,6 +20,7 @@
* Peer Network test functions.
*/
#include <boost/test/unit_test.hpp>
#include <chrono>
#include <thread>
#include <libp2p/Host.h>
@ -27,12 +28,74 @@ using namespace std;
using namespace dev;
using namespace dev::p2p;
BOOST_AUTO_TEST_SUITE(p2p)
BOOST_AUTO_TEST_CASE(host)
{
NetworkPreferences host1prefs(30301, "127.0.0.1", true, true);
NetworkPreferences host2prefs(30302, "127.0.0.1", true, true);
Host host1("Test", host1prefs);
host1.start();
Host host2("Test", host2prefs);
auto node2 = host2.id();
host2.start();
host1.addNode(node2, "127.0.0.1", host2prefs.listenPort, host2prefs.listenPort);
this_thread::sleep_for(chrono::seconds(1));
BOOST_REQUIRE_EQUAL(host1.peerCount(), 1);
BOOST_REQUIRE_EQUAL(host2.peerCount(), host1.peerCount());
}
BOOST_AUTO_TEST_CASE(save_nodes)
{
std::list<Host*> hosts;
for (auto i:{0,1,2,3,4,5})
{
Host* h = new Host("Test", NetworkPreferences(30300 + i, "127.0.0.1", true, true));
// starting host is required so listenport is available
h->start();
while (!h->isStarted())
this_thread::sleep_for(chrono::milliseconds(2));
hosts.push_back(h);
}
Host& host = *hosts.front();
for (auto const& h: hosts)
host.addNode(h->id(), "127.0.0.1", h->listenPort(), h->listenPort());
Host& host2 = *hosts.back();
for (auto const& h: hosts)
host2.addNode(h->id(), "127.0.0.1", h->listenPort(), h->listenPort());
this_thread::sleep_for(chrono::milliseconds(1000));
bytes firstHostNetwork(host.saveNetwork());
bytes secondHostNetwork(host.saveNetwork());
BOOST_REQUIRE_EQUAL(sha3(firstHostNetwork), sha3(secondHostNetwork));
BOOST_CHECK_EQUAL(host.peerCount(), 5);
BOOST_CHECK_EQUAL(host2.peerCount(), 5);
RLP r(firstHostNetwork);
BOOST_REQUIRE(r.itemCount() == 3);
BOOST_REQUIRE(r[0].toInt<int>() == 1);
BOOST_REQUIRE_EQUAL(r[1].toBytes().size(), 32); // secret
BOOST_REQUIRE_EQUAL(r[2].itemCount(), 5);
}
BOOST_AUTO_TEST_SUITE_END()
int peerTest(int argc, char** argv)
{
Public remoteAlias;
short listenPort = 30303;
string remoteHost;
short remotePort = 30303;
for (int i = 1; i < argc; ++i)
{
string arg = argv[i];
@ -42,21 +105,18 @@ int peerTest(int argc, char** argv)
remoteHost = argv[++i];
else if (arg == "-p" && i + 1 < argc)
remotePort = (short)atoi(argv[++i]);
else if (arg == "-ra" && i + 1 < argc)
remoteAlias = Public(dev::fromHex(argv[++i]));
else
remoteHost = argv[i];
}
Host ph("Test", NetworkPreferences(listenPort));
if (!remoteHost.empty())
ph.connect(remoteHost, remotePort);
if (!remoteHost.empty() && !remoteAlias)
ph.addNode(remoteAlias, remoteHost, remotePort, remotePort);
for (int i = 0; ; ++i)
{
this_thread::sleep_for(chrono::milliseconds(100));
if (!(i % 10))
ph.pingAll();
}
this_thread::sleep_for(chrono::milliseconds(200));
return 0;
}

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

Loading…
Cancel
Save