diff --git a/CMakeLists.txt b/CMakeLists.txt index 5aa1f5fcd..682ba5a14 100644 --- a/CMakeLists.txt +++ b/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 () diff --git a/CodingStandards.txt b/CodingStandards.txt index 78885ce25..f883c9147 100644 --- a/CodingStandards.txt +++ b/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". diff --git a/alethzero/CMakeLists.txt b/alethzero/CMakeLists.txt index b58446935..ed2d8fa5e 100644 --- a/alethzero/CMakeLists.txt +++ b/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}) diff --git a/alethzero/MainWin.cpp b/alethzero/MainWin.cpp index 9a29aff40..397b2330c 100644 --- a/alethzero/MainWin.cpp +++ b/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 clients; - for (PeerInfo const& i: ps) + map 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(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()); } diff --git a/alethzero/MainWin.h b/alethzero/MainWin.h index b5639fc68..4e21d493f 100644 --- a/alethzero/MainWin.h +++ b/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 m_myKeys; QList m_myIdentities; diff --git a/alethzero/NatspecHandler.h b/alethzero/NatspecHandler.h index edd9281d7..98677dbc2 100644 --- a/alethzero/NatspecHandler.h +++ b/alethzero/NatspecHandler.h @@ -26,7 +26,7 @@ #pragma warning(disable: 4100 4267) #include #pragma warning(pop) -#include +#include #include namespace ldb = leveldb; diff --git a/cmake/FindJsoncpp.cmake b/cmake/FindJsoncpp.cmake index 36ba12a3e..c5b9c87d8 100644 --- a/cmake/FindJsoncpp.cmake +++ b/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" ) diff --git a/cmake/scripts/jsonrpcstub.cmake b/cmake/scripts/jsonrpcstub.cmake index a9b2c44ab..39f850e53 100644 --- a/cmake/scripts/jsonrpcstub.cmake +++ b/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() diff --git a/eth/CMakeLists.txt b/eth/CMakeLists.txt index c98f3cbec..31aa8df96 100644 --- a/eth/CMakeLists.txt +++ b/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}) diff --git a/eth/main.cpp b/eth/main.cpp index 7a128298a..f17817fbf 100644 --- a/eth/main.cpp +++ b/eth/main.cpp @@ -119,6 +119,7 @@ void help() << " -p,--port Connect to remote port (default: 30303)." << endl << " -r,--remote Connect to remote host (default: none)." << endl << " -s,--secret Set the secret key for use with send command (default: auto)." << endl + << " -t,--miners Number of mining threads to start (Default: " << thread::hardware_concurrency() << ")" << endl << " -u,--public-ip Force public ip to given (default; auto)." << endl << " -v,--verbosity <0 - 9> Set the log verbosity from 0 to 9 (Default: 8)." << endl << " -x,--peers Attempt to connect to given number of peers (Default: 5)." << endl @@ -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{"eth", "shh"} : set(), - 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; } diff --git a/libdevcore/CMakeLists.txt b/libdevcore/CMakeLists.txt index 51caee3d3..40aa7ce24 100644 --- a/libdevcore/CMakeLists.txt +++ b/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}) diff --git a/libdevcore/CommonData.cpp b/libdevcore/CommonData.cpp index b3abe4300..09b525d59 100644 --- a/libdevcore/CommonData.cpp +++ b/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 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; } diff --git a/libdevcore/CommonData.h b/libdevcore/CommonData.h index 883e76dd9..3cdf3b449 100644 --- a/libdevcore/CommonData.h +++ b/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); diff --git a/libdevcore/CommonIO.h b/libdevcore/CommonIO.h index d2a67921b..23092b702 100644 --- a/libdevcore/CommonIO.h +++ b/libdevcore/CommonIO.h @@ -57,7 +57,7 @@ template struct StreamOut { static S& bypass(S& _out, T const template struct StreamOut { static S& bypass(S& _out, uint8_t const& _t) { _out << (int)_t; return _out; } }; template inline std::ostream& operator<<(std::ostream& _out, std::vector const& _e); -template inline std::ostream& operator<<(std::ostream& _out, std::array const& _e); +template inline std::ostream& operator<<(std::ostream& _out, std::array const& _e); template inline std::ostream& operator<<(std::ostream& _out, std::pair const& _e); template inline std::ostream& operator<<(std::ostream& _out, std::list const& _e); template inline std::ostream& operator<<(std::ostream& _out, std::tuple const& _e); @@ -84,7 +84,7 @@ inline S& streamout(S& _out, std::vector const& _e) template inline std::ostream& operator<<(std::ostream& _out, std::vector const& _e) { streamout(_out, _e); return _out; } -template +template inline S& streamout(S& _out, std::array const& _e) { _out << "["; @@ -98,23 +98,7 @@ inline S& streamout(S& _out, std::array const& _e) _out << "]"; return _out; } -template inline std::ostream& operator<<(std::ostream& _out, std::array const& _e) { streamout(_out, _e); return _out; } - -template -inline S& streamout(S& _out, std::array const& _e) -{ - _out << "["; - if (!_e.empty()) - { - StreamOut::bypass(_out, _e.front()); - auto i = _e.begin(); - for (++i; i != _e.end(); ++i) - StreamOut::bypass(_out << ",", *i); - } - _out << "]"; - return _out; -} -template inline std::ostream& operator<<(std::ostream& _out, std::array const& _e) { streamout(_out, _e); return _out; } +template inline std::ostream& operator<<(std::ostream& _out, std::array const& _e) { streamout(_out, _e); return _out; } template inline S& streamout(S& _out, std::list const& _e) diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index a42d1df99..4a49812df 100644 --- a/libethereum/Client.cpp +++ b/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(); diff --git a/libethereum/Client.h b/libethereum/Client.h index ce3506bde..cb4488b17 100644 --- a/libethereum/Client.h +++ b/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(); diff --git a/libethereum/EthereumHost.cpp b/libethereum/EthereumHost.cpp index 94ef9d35c..7dfc51b47 100644 --- a/libethereum/EthereumHost.cpp +++ b/libethereum/EthereumHost.cpp @@ -51,8 +51,8 @@ EthereumHost::EthereumHost(BlockChain const& _ch, TransactionQueue& _tq, BlockQu EthereumHost::~EthereumHost() { - for (auto const& i: peers()) - i->cap()->abortSync(); + for (auto i: peerSessions()) + i.first->cap().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().get() != _syncer && j->cap()->m_asking == Asking::Nothing) - j->cap()->transition(Asking::Blocks); + for (auto j: peerSessions()) + { + auto e = j.first->cap().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()->attemptSync(); + j.first->cap()->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()) + for (auto p: peerSessions()) + if (auto ep = p.first->cap().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(); + auto p = j.first->cap().get(); RLPStream ts; p->prep(ts, NewBlockPacket, 2).appendRaw(m_chain.block(), 1).append(m_chain.details().totalDifficulty); diff --git a/libethereum/State.cpp b/libethereum/State.cpp index e44b81c83..beab32abb 100644 --- a/libethereum/State.cpp +++ b/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); diff --git a/libp2p/Common.h b/libp2p/Common.h index 61df1350f..ff7ceb215 100644 --- a/libp2p/Common.h +++ b/libp2p/Common.h @@ -16,9 +16,10 @@ */ /** @file Common.h * @author Gav Wood + * @author Alex Leverington * @date 2014 * - * Miscellanea required for the Host/Session classes. + * Miscellanea required for the Host/Session/NodeTable classes. */ #pragma once @@ -29,9 +30,9 @@ #include #include #include -#include +#include #include -#include +#include 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; using CapDescSet = std::set; using CapDescs = std::vector; -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 notes; }; -using PeerInfos = std::vector; +using PeerSessionInfos = std::vector; + +/** + * @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; } +}; } } diff --git a/libp2p/Host.cpp b/libp2p/Host.cpp index a6267b7e9..f09468e0d 100644 --- a/libp2p/Host.cpp +++ b/libp2p/Host.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #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::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 _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 _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 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> 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 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(); - 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 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(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 p; + if (_nodeId) + p = m_peers[_nodeId]; + + if (!p) + p.reset(new Peer()); + p->endpoint.tcp.address(_socket->remote_endpoint().address()); + + auto ps = std::make_shared(this, std::move(*_socket), p); + ps->start(); +} + string Host::pocHost() { vector strs; @@ -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 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(this, std::move(*s), _ep); - clog(NetConnect) << "Connected to " << _ep; - p->start(); - } - delete s; - }); -} - -void Host::connect(std::shared_ptr 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(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 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 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 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(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(this)->pingAll(); - this_thread::sleep_for(chrono::milliseconds(200)); - } - std::vector ret; - for (auto& i: m_peers) + std::vector 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 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 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(n.lastConnected.time_since_epoch()).count() - << chrono::duration_cast(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(p.m_lastConnected.time_since_epoch()).count() + << chrono::duration_cast(p.m_lastAttempted.time_since_epoch()).count() + << p.m_failedAttempts << (unsigned)p.m_lastDisconnect << p.m_score << p.m_rating; count++; } } } + + 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()) - { - case 0: - { - auto oldId = id(); - m_key = KeyPair(r[1].toHash()); - noteNode(id(), m_tcpPublic, Origin::Perfect, false, oldId); + if (r.itemCount() > 0 && r[0].isInt() && r[0].toInt() == 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()), i[1].toInt()); - else - ep = bi::tcp::endpoint(bi::address_v6(i[0].toArray()), i[1].toInt()); - auto id = (NodeId)i[2]; - if (!m_nodes.count(id)) - { - auto o = (Origin)i[3].toInt(); - auto n = noteNode(id, ep, o, true); - n->lastConnected = chrono::system_clock::time_point(chrono::seconds(i[4].toInt())); - n->lastAttempted = chrono::system_clock::time_point(chrono::seconds(i[5].toInt())); - n->failedAttempts = i[6].toInt(); - n->lastDisconnect = (DisconnectReason)i[7].toInt(); - n->score = (int)i[8].toInt(); - n->rating = (int)i[9].toInt(); - } + tcp = bi::tcp::endpoint(bi::address_v4(i[0].toArray()), i[1].toInt()); + udp = bi::udp::endpoint(bi::address_v4(i[0].toArray()), i[1].toInt()); + } + else + { + tcp = bi::tcp::endpoint(bi::address_v6(i[0].toArray()), i[1].toInt()); + udp = bi::udp::endpoint(bi::address_v6(i[0].toArray()), i[1].toInt()); } - } - 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()), i[1].toInt()); - else - ep = bi::tcp::endpoint(bi::address_v6(i[0].toArray()), i[1].toInt()); - auto n = noteNode(id, ep, Origin::Self, true); + shared_ptr p = make_shared(); + p->id = id; + p->m_lastConnected = chrono::system_clock::time_point(chrono::seconds(i[4].toInt())); + p->m_lastAttempted = chrono::system_clock::time_point(chrono::seconds(i[5].toInt())); + p->m_failedAttempts = i[6].toInt(); + p->m_lastDisconnect = (DisconnectReason)i[7].toInt(); + p->m_score = (int)i[8].toInt(); + p->m_rating = (int)i[9].toInt(); + 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() == 1) + return move(KeyPair(move(Secret(r[1].toBytes())))); + else + return move(KeyPair::create()); } diff --git a/libp2p/Host.h b/libp2p/Host.h index 8ed25f2ae..baf8f0585 100644 --- a/libp2p/Host.h +++ b/libp2p/Host.h @@ -34,8 +34,10 @@ #include #include #include +#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::system_clock::now() - lastConnected).count(); } - int secondsSinceLastAttempted() const { return lastAttempted == std::chrono::system_clock::time_point() ? -1 : (int)std::chrono::duration_cast(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; + 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 std::shared_ptr cap() const { try { return std::static_pointer_cast(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 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 _s, CapDescs const& _caps); - std::shared_ptr node(NodeId _id) const { if (m_nodes.count(_id)) return m_nodes.at(_id); return std::shared_ptr(); } +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 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 noteNode(NodeId _id, bi::tcp::endpoint _a, Origin _o, bool _ready, NodeId _oldId = NodeId()); - Nodes potentialPeers(RangeMask 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 m_ifAddresses; ///< Interface addresses. + std::vector 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 m_socket; ///< Listening socket. + ba::io_service m_ioService; ///< IOService for network stuff. + bi::tcp::acceptor m_tcp4Acceptor; ///< Listening acceptor. std::unique_ptr 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 m_pendingNodeConns; /// Used only by connect(Node&) to limit concurrently connecting to same node. See connect(shared_ptrconst&). + std::set m_pendingPeerConns; /// Used only by connect(Peer&) to limit concurrently connecting to same node. See connect(shared_ptrconst&). 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 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> 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> m_peers; - - /// Nodes to which we may connect (or to which we have connected). - /// TODO: does this need a lock? - std::map > m_nodes; - - /// A list of node IDs. This contains every index from m_nodes; the order is guaranteed to remain the same. - std::vector m_nodesList; - - RangeMask m_ready; ///< Indices into m_nodesList over to which nodes we are not currently connected, connecting or otherwise ignoring. - RangeMask m_private; ///< Indices into m_nodesList over to which nodes are private. + mutable std::map> m_sessions; + mutable RecursiveMutex x_sessions; unsigned m_idealPeerCount = 5; ///< Ideal number of peers to be connected to. std::set m_peerAddresses; ///< Public addresses that peers (can) know us by. - // Our capabilities. std::map> 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; }; - + } } diff --git a/libp2p/HostCapability.cpp b/libp2p/HostCapability.cpp index 0728bef2c..9437cd45c 100644 --- a/libp2p/HostCapability.cpp +++ b/libp2p/HostCapability.cpp @@ -32,13 +32,13 @@ void HostCapabilityFace::seal(bytes& _b) m_host->seal(_b); } -std::vector > HostCapabilityFace::peers() const +std::vector,std::shared_ptr>> HostCapabilityFace::peerSessions() const { - RecursiveGuard l(m_host->x_peers); - std::vector > ret; - for (auto const& i: m_host->m_peers) - if (std::shared_ptr p = i.second.lock()) - if (p->m_capabilities.count(capDesc())) - ret.push_back(p); + RecursiveGuard l(m_host->x_sessions); + std::vector,std::shared_ptr>> ret; + for (auto const& i: m_host->m_sessions) + if (std::shared_ptr s = i.second.lock()) + if (s->m_capabilities.count(capDesc())) + ret.push_back(make_pair(s,s->m_peer)); return ret; } diff --git a/libp2p/HostCapability.h b/libp2p/HostCapability.h index 9666ef65a..9122ca1fa 100644 --- a/libp2p/HostCapability.h +++ b/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 > peers() const; + std::vector,std::shared_ptr>> peerSessions() const; protected: virtual std::string name() const = 0; diff --git a/libp2p/NodeTable.cpp b/libp2p/NodeTable.cpp index f6ab0f949..adeb43c2e 100644 --- a/libp2p/NodeTable.cpp +++ b/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 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 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 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 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 NodeTable::nodes() const { list nodes; @@ -64,7 +123,7 @@ list NodeTable::nodes() const return move(nodes); } -list NodeTable::state() const +list NodeTable::snapshot() const { list ret; Guard l(x_state); @@ -74,34 +133,40 @@ list 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 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()); } -void NodeTable::doFindNode(NodeId _node, unsigned _round, shared_ptr>> _tried) +void NodeTable::discover(NodeId _node, unsigned _round, shared_ptr>> _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>()); - auto nearest = findNearest(_node); + auto nearest = nearestNodeEntries(_node); list> 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_ptrendpoint.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> NodeTable::findNearest(NodeId _target) +vector> 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>> found; @@ -154,7 +219,7 @@ vector> 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> 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> 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> 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 _leastSeen, shared_ptr _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 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 _n) -{ - shared_ptr contested; - { - NodeBucket& s = bucket(_n.get()); - Guard l(x_state); - s.nodes.remove_if([&_n](weak_ptr n) - { - if (n.lock() == _n) - return true; - return false; - }); + shared_ptr 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 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 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 _n) { - NodeBucket &s = bucket(_n.get()); { Guard l(x_state); + NodeBucket& s = bucket_UNSAFE(_n.get()); s.nodes.remove_if([&_n](weak_ptr n) { return n.lock() == _n; }); } - Guard l(x_nodes); - m_nodes.erase(_n->id); + { + 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> 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> 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> 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); } diff --git a/libp2p/NodeTable.h b/libp2p/NodeTable.h index e8385998e..04e8d009c 100644 --- a/libp2p/NodeTable.h +++ b/libp2p/NodeTable.h @@ -22,9 +22,10 @@ #pragma once #include +#include #include -#include #include +#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> 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 m_nodeEventHandler; + std::map 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 m_cachedPingPacket; - * @todo std::shared_ptr 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 { - friend struct Neighbours; + friend std::ostream& operator<<(std::ostream& _out, NodeTable const& _nodeTable); using NodeSocket = UDPSocket; using TimePoint = std::chrono::steady_clock::time_point; - using EvictionTimeout = std::pair,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, 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 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 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> nodes; - }; + /// Returns list of node ids active in node table. + std::list 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 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::value; ///< Max iterations of discovery. (doFindNode) + static unsigned const s_maxSteps = boost::static_log2::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> 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 nodes() const; - std::list 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(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>> _tried = std::shared_ptr>>()); + /// 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>> _tried = std::shared_ptr>>()); - /// Returns nodes nearest to target. - std::vector> 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> 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 _leastSeen, std::shared_ptr _new); - void noteNode(Public const& _pubk, bi::udp::endpoint const& _endpoint); - - void noteNode(std::shared_ptr _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 _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 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> 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> m_nodes; ///< Nodes - mutable Mutex x_state; - std::array 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 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 m_evictions; ///< Eviction timeouts. - - std::shared_ptr 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 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(bi::udp::endpoint _ep): RLPXDatagram(_ep) {} PingNode(bi::udp::endpoint _ep, std::string _src, uint16_t _srcPort, std::chrono::seconds _expiration = std::chrono::seconds(60)): RLPXDatagram(_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 }; /** - * 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(bi::udp::endpoint _ep): RLPXDatagram(_ep) {} + Pong(bi::udp::endpoint _ep): RLPXDatagram(_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(); } }; /** @@ -284,6 +355,8 @@ struct FindNode: RLPXDatagram { FindNode(bi::udp::endpoint _ep): RLPXDatagram(_ep) {} FindNode(bi::udp::endpoint _ep, NodeId _target, std::chrono::seconds _expiration = std::chrono::seconds(30)): RLPXDatagram(_ep), target(_target), expiration(futureFromEpoch(_expiration)) {} + + static const uint8_t type = 3; h512 target; unsigned expiration; @@ -297,8 +370,6 @@ struct FindNode: RLPXDatagram * * RLP Encoded Items: 2 (first item is list) * Minimum Encoded Size: 10 bytes - * - * @todo nonce: Should be replaced with expiration. */ struct Neighbours: RLPXDatagram { @@ -313,8 +384,8 @@ struct Neighbours: RLPXDatagram void interpretRLP(RLP const& _r) { ipAddress = _r[0].toString(); port = _r[1].toInt(); node = h512(_r[2].toBytes()); } }; - Neighbours(bi::udp::endpoint _ep): RLPXDatagram(_ep) {} - Neighbours(bi::udp::endpoint _to, std::vector> const& _nearest, unsigned _offset = 0, unsigned _limit = 0): RLPXDatagram(_to) + Neighbours(bi::udp::endpoint _ep): RLPXDatagram(_ep), expiration(futureFromEpoch(std::chrono::seconds(30))) {} + Neighbours(bi::udp::endpoint _to, std::vector> const& _nearest, unsigned _offset = 0, unsigned _limit = 0): RLPXDatagram(_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 } } - std::list nodes; + static const uint8_t type = 4; + std::vector 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; } diff --git a/libp2p/Peer.cpp b/libp2p/Peer.cpp new file mode 100644 index 000000000..4be0fd799 --- /dev/null +++ b/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 . +*/ +/** @file Peer.cpp + * @author Alex Leverington + * @author Gav Wood + * @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; +} + +} +} diff --git a/libp2p/Peer.h b/libp2p/Peer.h new file mode 100644 index 000000000..704e5c2b4 --- /dev/null +++ b/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 . +*/ +/** @file Peer.h + * @author Alex Leverington + * @author Gav Wood + * @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 m_session; +}; +using Peers = std::vector; + +} +} diff --git a/libp2p/Session.cpp b/libp2p/Session.cpp index d4342c37a..cb7225f0f 100644 --- a/libp2p/Session.cpp +++ b/libp2p/Session.cpp @@ -36,37 +36,19 @@ using namespace dev::p2p; #endif #define clogS(X) dev::LogOutputStream(false) << "| " << std::setw(2) << m_socket.native_handle() << "] " -Session::Session(Host* _s, bi::tcp::socket _socket, bi::tcp::endpoint const& _manual): +Session::Session(Host* _s, bi::tcp::socket _socket, std::shared_ptr 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()}), + m_ping(chrono::time_point::max()) { m_lastReceived = m_connect = std::chrono::steady_clock::now(); - - m_info = PeerInfo({NodeId(), "?", m_manualEndpoint.address().to_string(), 0, std::chrono::steady_clock::duration(0), CapDescSet(), 0, map()}); -} - -Session::Session(Host* _s, bi::tcp::socket _socket, std::shared_ptr 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()}); } 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 vector randomSelection(vector const& _t, unsigned _n) @@ -132,9 +99,10 @@ template vector randomSelection(vector 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(); auto clientVersion = _r[2].toString(); auto caps = _r[3].toVector(); @@ -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(), (unsigned)m_socket.native_handle(), map() }); + + m_info.clientVersion = clientVersion; + m_info.host = m_socket.remote_endpoint().address().to_string(); + m_info.port = listenPort; + m_info.lastPing = std::chrono::steady_clock::duration(); + m_info.caps = _r[3].toSet(); + m_info.socket = (unsigned)m_socket.native_handle(); + m_info.notes = map(); m_server->registerPeer(shared_from_this(), caps); break; @@ -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()); NodeId id = _r[i][2].toHash(); - 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; diff --git a/libp2p/Session.h b/libp2p/Session.h index cabef2cbf..4d55e7d9d 100644 --- a/libp2p/Session.h +++ b/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 friend class HostCapabilityFace; public: - Session(Host* _server, bi::tcp::socket _socket, std::shared_ptr 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 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 std::shared_ptr cap() const { try { return std::static_pointer_cast(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 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 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 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> m_capabilities; ///< The peer's capability set. - RangeMask m_knownNodes; ///< Nodes we already know about as indices into Host's nodesList. These shouldn't be resent to peer. }; } diff --git a/libp2p/UDP.cpp b/libp2p/UDP.cpp index b1f87e409..e27b6e57a 100644 --- a/libp2p/UDP.cpp +++ b/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) diff --git a/libp2p/UDP.h b/libp2p/UDP.h index 6de783509..5c3b9362f 100644 --- a/libp2p/UDP.h +++ b/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::system_clock::now() + _ms).time_since_epoch()).count(); } - static uint64_t futureFromEpoch(std::chrono::seconds _sec) { return std::chrono::duration_cast((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::system_clock::now() + _ms).time_since_epoch()).count(); } + static uint64_t futureFromEpoch(std::chrono::seconds _sec) { return std::chrono::duration_cast((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 @@ -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; } }; /** diff --git a/libp2p/UPnP.cpp b/libp2p/UPnP.cpp index 42868d67a..36795c540 100644 --- a/libp2p/UPnP.cpp +++ b/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; diff --git a/libsolidity/AST.cpp b/libsolidity/AST.cpp index 041b6277d..33cf8c12d 100644 --- a/libsolidity/AST.cpp +++ b/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) diff --git a/libsolidity/ASTJsonConverter.h b/libsolidity/ASTJsonConverter.h index cb43c2d9e..466801e9c 100644 --- a/libsolidity/ASTJsonConverter.h +++ b/libsolidity/ASTJsonConverter.h @@ -27,7 +27,7 @@ #include #include #include -#include +#include namespace dev { diff --git a/libsolidity/Compiler.cpp b/libsolidity/Compiler.cpp index b36153677..dad79bb06 100644 --- a/libsolidity/Compiler.cpp +++ b/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 diff --git a/libsolidity/CompilerContext.cpp b/libsolidity/CompilerContext.cpp index 52910a556..01a71d7c9 100644 --- a/libsolidity/CompilerContext.cpp +++ b/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) diff --git a/libsolidity/CompilerUtils.cpp b/libsolidity/CompilerUtils.cpp index 3101c1b44..73be38176 100644 --- a/libsolidity/CompilerUtils.cpp +++ b/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(_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> 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; +} + } } diff --git a/libsolidity/CompilerUtils.h b/libsolidity/CompilerUtils.h index b5a695282..fe28ceadf 100644 --- a/libsolidity/CompilerUtils.h +++ b/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 const& _variables); static unsigned getSizeOnStack(std::vector> 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; }; diff --git a/libsolidity/ExpressionCompiler.cpp b/libsolidity/ExpressionCompiler.cpp index beda01322..3dbb4012d 100644 --- a/libsolidity/ExpressionCompiler.cpp +++ b/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(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(*_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(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> const& _arguments, - TypePointers const& _types, - unsigned _memoryOffset, - bool _padToWordBoundaries, - bool _padExceptionIfFourBytes) +void ExpressionCompiler::appendArgumentsCopyToMemory(vector> 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(vectorgetCalldataEncodedSize() == 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(*_expression.getType()), + dynamic_cast(_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(_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.")); } + } } diff --git a/libsolidity/ExpressionCompiler.h b/libsolidity/ExpressionCompiler.h index 3c94d74c2..e0cc75ce8 100644 --- a/libsolidity/ExpressionCompiler.h +++ b/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> 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> 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> 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; diff --git a/libsolidity/InterfaceHandler.h b/libsolidity/InterfaceHandler.h index c6da63de0..6aa3f72d6 100644 --- a/libsolidity/InterfaceHandler.h +++ b/libsolidity/InterfaceHandler.h @@ -28,7 +28,7 @@ #include #include -#include +#include namespace dev { diff --git a/libsolidity/Token.h b/libsolidity/Token.h index ac8c618fa..5167c1a77 100644 --- a/libsolidity/Token.h +++ b/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) \ diff --git a/libsolidity/Types.cpp b/libsolidity/Types.cpp index 5f573a6da..33cc8a1ec 100644 --- a/libsolidity/Types.cpp +++ b/libsolidity/Types.cpp @@ -35,7 +35,7 @@ namespace dev namespace solidity { -shared_ptr 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::fromElementaryTypeName(Token::Value _typeToken) return make_shared(); else if (Token::String0 <= _typeToken && _typeToken <= Token::String32) return make_shared(int(_typeToken) - int(Token::String0)); + else if (_typeToken == Token::Bytes) + return make_shared(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::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(declaration)) @@ -71,21 +78,21 @@ shared_ptr Type::fromUserDefinedTypeName(UserDefinedTypeName const& return make_shared(*function); else if (ContractDefinition const* contract = dynamic_cast(declaration)) return make_shared(*contract); - return shared_ptr(); + return TypePointer(); } -shared_ptr Type::fromMapping(Mapping const& _typeName) +TypePointer Type::fromMapping(Mapping const& _typeName) { - shared_ptr keyType = _typeName.getKeyType().toType(); + TypePointer keyType = _typeName.getKeyType().toType(); if (!keyType) BOOST_THROW_EXCEPTION(InternalCompilerError() << errinfo_comment("Error resolving type name.")); - shared_ptr valueType = _typeName.getValueType().toType(); + TypePointer valueType = _typeName.getValueType().toType(); if (!valueType) BOOST_THROW_EXCEPTION(_typeName.getValueType().createTypeError("Invalid type name")); return make_shared(keyType, valueType); } -shared_ptr 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() : 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(); + return TypePointer(); +} + +bool ByteArrayType::operator==(Type const& _other) const +{ + if (_other.getCategory() != getCategory()) + return false; + ByteArrayType const& other = dynamic_cast(_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(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> members(IntegerType::AddressMemberList.begin(), - IntegerType::AddressMemberList.end()); + map 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> const& member: getMembers()) + for (pair const& member: getMembers()) size += member.second->getStorageSize(); return max(1, size); } bool StructType::canLiveOutsideStorage() const { - for (pair> const& member: getMembers()) + for (pair 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> members; + map members; for (ASTPointer 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(0, IntegerType::Modifier::Address)}, {"gas", make_shared(256)}, - {"value", make_shared(256)}}); + {"value", make_shared(256)}, + {"data", make_shared(ByteArrayType::Location::CallData)}}); break; case Kind::Transaction: m_members = MemberList({{"origin", make_shared(0, IntegerType::Modifier::Address)}, diff --git a/libsolidity/Types.h b/libsolidity/Types.h index 05090c7a2..5a0e2c429 100644 --- a/libsolidity/Types.h +++ b/libsolidity/Types.h @@ -76,15 +76,17 @@ class Type: private boost::noncopyable, public std::enable_shared_from_this const& _interfaces, NetworkPreferences const& _n): +WebThreeDirect::WebThreeDirect(std::string const& _clientVersion, std::string const& _dbPath, bool _forceClean, std::set 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(new WhisperHost); @@ -75,9 +75,9 @@ void WebThreeDirect::setNetworkPreferences(p2p::NetworkPreferences const& _n) startNetwork(); } -std::vector WebThreeDirect::peers() +std::vector 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); } diff --git a/libwebthree/WebThree.h b/libwebthree/WebThree.h index 682fdc0b6..4fa1d1fe5 100644 --- a/libwebthree/WebThree.h +++ b/libwebthree/WebThree.h @@ -54,7 +54,7 @@ class WebThreeNetworkFace { public: /// Get information on the current peer set. - virtual std::vector peers() = 0; + virtual std::vector 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 const& _interfaces = {"eth", "shh"}, p2p::NetworkPreferences const& _n = p2p::NetworkPreferences()); + WebThreeDirect(std::string const& _clientVersion, std::string const& _dbPath, bool _forceClean = false, std::set 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 peers() override; + std::vector 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 peers(); + std::vector peers(); /// Same as peers().size(), but more efficient. size_t peerCount() const; diff --git a/libwhisper/WhisperHost.cpp b/libwhisper/WhisperHost.cpp index 213134db9..22a6a56fe 100644 --- a/libwhisper/WhisperHost.cpp +++ b/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().get() == _p) - i->addRating(1); + // TODO p2p: capability-based rating + for (auto i: peerSessions()) + { + auto w = i.first->cap().get(); + if (w == _p) + w->addRating(1); else - i->cap()->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()->sendMessages(); + for (auto& i: peerSessions()) + i.first->cap().get()->sendMessages(); cleanup(); } diff --git a/lllc/CMakeLists.txt b/lllc/CMakeLists.txt index 2e76aa6ff..5aaca0ccc 100644 --- a/lllc/CMakeLists.txt +++ b/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) diff --git a/mix/AppContext.cpp b/mix/AppContext.cpp index 7fe22106f..64bc59ff8 100644 --- a/mix/AppContext.cpp +++ b/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("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", 1, 0, "CodeEditorExtensionManager"); qmlRegisterType("HttpServer", 1, 0, "HttpServer"); m_applicationEngine->load(QUrl("qrc:/qml/main.qml")); diff --git a/mix/CMakeLists.txt b/mix/CMakeLists.txt index db9452061..755f3df04 100644 --- a/mix/CMakeLists.txt +++ b/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) diff --git a/mix/ClientModel.cpp b/mix/ClientModel.cpp index b7be8988a..57caf573c 100644 --- a/mix/ClientModel.cpp +++ b/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(transaction.value("gas")))->toU256Wei(); + + u256 gas = boost::get(qvariant_cast(transaction.value("gas"))->internalValue()); u256 value = (qvariant_cast(transaction.value("value")))->toU256Wei(); u256 gasPrice = (qvariant_cast(transaction.value("gasPrice")))->toU256Wei(); diff --git a/mix/CodeModel.cpp b/mix/CodeModel.cpp index a9cfcc336..aae9dac86 100644 --- a/mix/CodeModel.cpp +++ b/mix/CodeModel.cpp @@ -28,6 +28,7 @@ #include #include #include +#include #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), diff --git a/mix/CodeModel.h b/mix/CodeModel.h index 5f2add874..0262aa094 100644 --- a/mix/CodeModel.h +++ b/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 m_contract; QString m_compilerMessage; ///< @todo: use some structure here dev::bytes m_bytes; - QString m_assemblyCode; QString m_contractInterface; std::shared_ptr m_codeHighlighter; diff --git a/mix/FileIo.cpp b/mix/FileIo.cpp index 52fd57902..818d8c887 100644 --- a/mix/FileIo.cpp +++ b/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())) diff --git a/mix/HttpServer.cpp b/mix/HttpServer.cpp index cfe5c37f4..bf210444b 100644 --- a/mix/HttpServer.cpp +++ b/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 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; } } diff --git a/mix/HttpServer.h b/mix/HttpServer.h index 00d63a073..add83238b 100644 --- a/mix/HttpServer.h +++ b/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; }; diff --git a/mix/QEther.cpp b/mix/QEther.cpp index 46562d440..4eef1fbdb 100644 --- a/mix/QEther.cpp +++ b/mix/QEther.cpp @@ -35,7 +35,7 @@ QBigInt* QEther::toWei() const const char* key = units.valueToKey(m_currentUnit); for (std::pair 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(units.keysToValue(units.key(k))); return; diff --git a/mix/qml/CodeEditorView.qml b/mix/qml/CodeEditorView.qml index 36fc586b3..4d54994fe 100644 --- a/mix/qml/CodeEditorView.qml +++ b/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: { diff --git a/mix/qml/CommonSeparator.qml b/mix/qml/CommonSeparator.qml new file mode 100644 index 000000000..c81a81f63 --- /dev/null +++ b/mix/qml/CommonSeparator.qml @@ -0,0 +1,9 @@ +import QtQuick 2.0 +import "." + +Rectangle +{ + height: 1 + color: Style.generic.layout.separatorColor +} + diff --git a/mix/qml/Debugger.qml b/mix/qml/Debugger.qml index 2bf23ccef..61bf5e8cc 100644 --- a/mix/qml/Debugger.qml +++ b/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; diff --git a/mix/qml/DefaultLabel.qml b/mix/qml/DefaultLabel.qml new file mode 100644 index 000000000..a1304e673 --- /dev/null +++ b/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 + } +} + + + + diff --git a/mix/qml/DefaultTextField.qml b/mix/qml/DefaultTextField.qml new file mode 100644 index 000000000..6705273db --- /dev/null +++ b/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; + } +} diff --git a/mix/qml/Ether.qml b/mix/qml/Ether.qml index a5fd5dba2..be41fced9 100644 --- a/mix/qml/Ether.qml +++ b/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 } } diff --git a/mix/qml/FilesSection.qml b/mix/qml/FilesSection.qml index 76e235e7d..518e85595 100644 --- a/mix/qml/FilesSection.qml +++ b/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 diff --git a/mix/qml/MainContent.qml b/mix/qml/MainContent.qml index d984e2753..6c6781878 100644 --- a/mix/qml/MainContent.qml +++ b/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 diff --git a/mix/qml/NewProjectDialog.qml b/mix/qml/NewProjectDialog.qml index 8d5afc7ac..4fcb524b2 100644 --- a/mix/qml/NewProjectDialog.qml +++ b/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; } diff --git a/mix/qml/ProjectList.qml b/mix/qml/ProjectList.qml index 0297c2441..925bb0bab 100644 --- a/mix/qml/ProjectList.qml +++ b/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 diff --git a/mix/qml/ProjectModel.qml b/mix/qml/ProjectModel.qml index 10dde5b41..e74be7a9b 100644 --- a/mix/qml/ProjectModel.qml +++ b/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 } diff --git a/mix/qml/QHashTypeView.qml b/mix/qml/QHashTypeView.qml index e36514fab..73678f953 100644 --- a/mix/qml/QHashTypeView.qml +++ b/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 diff --git a/mix/qml/QIntTypeView.qml b/mix/qml/QIntTypeView.qml index f794a3b2d..98344dd8b 100644 --- a/mix/qml/QIntTypeView.qml +++ b/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 diff --git a/mix/qml/QStringTypeView.qml b/mix/qml/QStringTypeView.qml index a78fc1d26..016206e6d 100644 --- a/mix/qml/QStringTypeView.qml +++ b/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 diff --git a/mix/qml/SourceSansProBold.qml b/mix/qml/SourceSansProBold.qml new file mode 100644 index 000000000..39c99cc8d --- /dev/null +++ b/mix/qml/SourceSansProBold.qml @@ -0,0 +1,6 @@ +import QtQuick 2.0 + +FontLoader +{ + source: "qrc:/qml/fonts/SourceSansPro-Bold.ttf" +} diff --git a/mix/qml/SourceSansProLight.qml b/mix/qml/SourceSansProLight.qml new file mode 100644 index 000000000..f46abedd5 --- /dev/null +++ b/mix/qml/SourceSansProLight.qml @@ -0,0 +1,7 @@ +import QtQuick 2.0 + +FontLoader +{ + source: "qrc:/qml/fonts/SourceSansPro-Light.ttf" +} + diff --git a/mix/qml/SourceSansProRegular.qml b/mix/qml/SourceSansProRegular.qml new file mode 100644 index 000000000..d9ce908e0 --- /dev/null +++ b/mix/qml/SourceSansProRegular.qml @@ -0,0 +1,8 @@ +import QtQuick 2.0 + +FontLoader +{ + source: "qrc:/qml/fonts/SourceSansPro-Regular.ttf" +} + + diff --git a/mix/qml/StateDialog.qml b/mix/qml/StateDialog.qml index 4384b81a6..eeda2ae22 100644 --- a/mix/qml/StateDialog.qml +++ b/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 { } } } - } diff --git a/mix/qml/StateDialogStyle.qml b/mix/qml/StateDialogStyle.qml new file mode 100644 index 000000000..39214312a --- /dev/null +++ b/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 + { + } +} diff --git a/mix/qml/StateListModel.qml b/mix/qml/StateListModel.qml index e87a96f76..67372421c 100644 --- a/mix/qml/StateListModel.qml +++ b/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, diff --git a/mix/qml/StatusPane.qml b/mix/qml/StatusPane.qml index 956d3f2ec..ddadb9953 100644 --- a/mix/qml/StatusPane.qml +++ b/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 diff --git a/mix/qml/Style.qml b/mix/qml/Style.qml new file mode 100644 index 000000000..9e4b6f268 --- /dev/null +++ b/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) + } + } +} diff --git a/mix/qml/TransactionDialog.qml b/mix/qml/TransactionDialog.qml index 3e6cf0236..a38886354 100644 --- a/mix/qml/TransactionDialog.qml +++ b/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 + } } diff --git a/mix/qml/TransactionLog.qml b/mix/qml/TransactionLog.qml index b31956898..61d2e0920 100644 --- a/mix/qml/TransactionLog.qml +++ b/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 diff --git a/mix/qml/WebPreview.qml b/mix/qml/WebPreview.qml index f4ddca84e..08f25b3df 100644 --- a/mix/qml/WebPreview.qml +++ b/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 = "\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); } diff --git a/mix/qml/html/WebContainer.html b/mix/qml/html/WebContainer.html index 04ba8ab73..372860209 100644 --- a/mix/qml/html/WebContainer.html +++ b/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)); }; diff --git a/mix/qml/img/delete_sign.png b/mix/qml/img/delete_sign.png new file mode 100644 index 000000000..5c00a20ba Binary files /dev/null and b/mix/qml/img/delete_sign.png differ diff --git a/mix/qml/img/edit.png b/mix/qml/img/edit.png new file mode 100644 index 000000000..0530fd192 Binary files /dev/null and b/mix/qml/img/edit.png differ diff --git a/mix/qml/img/plus.png b/mix/qml/img/plus.png new file mode 100644 index 000000000..fa8f5b9df Binary files /dev/null and b/mix/qml/img/plus.png differ diff --git a/mix/qml/js/ProjectModel.js b/mix/qml/js/ProjectModel.js index 7c7a8ae4c..fb6872bd4 100644 --- a/mix/qml/js/ProjectModel.js +++ b/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("") + if (insertAt < 0) + insertAt = 0; + else + insertAt += 6; + html = html.substr(0, insertAt) + + "" + + "" + + "" + + 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(); +} diff --git a/mix/qml/js/QEtherHelper.js b/mix/qml/js/QEtherHelper.js index 9761b2f45..7563941d2 100644 --- a/mix/qml/js/QEtherHelper.js +++ b/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; +} + diff --git a/mix/qml/js/TransactionHelper.js b/mix/qml/js/TransactionHelper.js index f404685cf..87dd74beb 100644 --- a/mix/qml/js/TransactionHelper.js +++ b/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: {} diff --git a/mix/qml/main.qml b/mix/qml/main.qml index 79430eb59..71d8c24bf 100644 --- a/mix/qml/main.qml +++ b/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(); + } } diff --git a/mix/qml/qmldir b/mix/qml/qmldir index 9eb0effd0..ca8e494fe 100644 --- a/mix/qml/qmldir +++ b/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 diff --git a/mix/res.qrc b/mix/res.qrc index cc37b1d32..6d9ce3d79 100644 --- a/mix/res.qrc +++ b/mix/res.qrc @@ -81,10 +81,21 @@ qml/img/closedtriangleindicator_filesproject.png qml/img/opentriangleindicator_filesproject.png qml/img/projecticon.png + qml/SourceSansProRegular.qml + qml/SourceSansProBold.qml + qml/SourceSansProLight.qml + qml/StateDialogStyle.qml qml/ProjectFilesStyle.qml qml/DebuggerPaneStyle.qml qml/CodeEditorStyle.qml qml/StatusPaneStyle.qml qml/StateStyle.qml + qml/img/plus.png + qml/img/delete_sign.png + qml/img/edit.png + qml/DefaultLabel.qml + qml/DefaultTextField.qml + qml/CommonSeparator.qml + qml/Style.qml diff --git a/neth/main.cpp b/neth/main.cpp index 2c1a85241..27da7e89b 100644 --- a/neth/main.cpp +++ b/neth/main.cpp @@ -81,6 +81,7 @@ void help() << " -p,--port Connect to remote port (default: 30303)." << endl << " -r,--remote Connect to remote host (default: none)." << endl << " -s,--secret Set the secret key for use with send command (default: auto)." << endl + << " -t,--miners Number of mining threads to start (Default: " << thread::hardware_concurrency() << ")" << endl << " -u,--public-ip Force public ip to given (default; auto)." << endl << " -v,--verbosity <0..9> Set the log verbosity from 0 to 9 (tmp forced to 1)." << endl << " -x,--peers 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{"eth", "shh"} : set(), - 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(i.lastPing).count()) % diff --git a/solc/CMakeLists.txt b/solc/CMakeLists.txt index 8c0ece27e..2a7bd7b6d 100644 --- a/solc/CMakeLists.txt +++ b/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) diff --git a/test/SolidityABIJSON.cpp b/test/SolidityABIJSON.cpp index 242a88e77..10873b5ab 100644 --- a/test/SolidityABIJSON.cpp +++ b/test/SolidityABIJSON.cpp @@ -22,7 +22,7 @@ #include #include -#include +#include #include namespace dev diff --git a/test/SolidityEndToEndTest.cpp b/test/SolidityEndToEndTest.cpp index 13a666fbf..587d4193f 100644 --- a/test/SolidityEndToEndTest.cpp +++ b/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() } diff --git a/test/SolidityNatspecJSON.cpp b/test/SolidityNatspecJSON.cpp index b652ad10e..d1a443c21 100644 --- a/test/SolidityNatspecJSON.cpp +++ b/test/SolidityNatspecJSON.cpp @@ -21,7 +21,7 @@ */ #include -#include +#include #include #include #include diff --git a/test/net.cpp b/test/net.cpp index 67c50dae8..5039c5436 100644 --- a/test/net.cpp +++ b/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) diff --git a/test/peer.cpp b/test/peer.cpp index a99ce7201..7f3c19e1e 100644 --- a/test/peer.cpp +++ b/test/peer.cpp @@ -20,6 +20,7 @@ * Peer Network test functions. */ +#include #include #include #include @@ -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 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() == 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; } diff --git a/test/solidityExecutionFramework.h b/test/solidityExecutionFramework.h index 5a6935365..4ef9bfdc8 100644 --- a/test/solidityExecutionFramework.h +++ b/test/solidityExecutionFramework.h @@ -139,6 +139,7 @@ private: return encode(_cppFunction(_arguments...)); } +protected: void sendMessage(bytes const& _data, bool _isCreation, u256 const& _value = 0) { m_state.addBalance(m_sender, _value); // just in case @@ -171,7 +172,6 @@ private: m_logs = executive.logs(); } -protected: bool m_optimize = false; bool m_addStandardSources = false; Address m_sender; diff --git a/test/whisperTopic.cpp b/test/whisperTopic.cpp index 119373361..be93174ec 100644 --- a/test/whisperTopic.cpp +++ b/test/whisperTopic.cpp @@ -36,27 +36,31 @@ BOOST_AUTO_TEST_CASE(topic) auto oldLogVerbosity = g_logVerbosity; g_logVerbosity = 0; + Host host1("Test", NetworkPreferences(30303, "127.0.0.1", false, true)); + auto whost1 = host1.registerCapability(new WhisperHost()); + host1.start(); + + while (!host1.isStarted()) + this_thread::sleep_for(chrono::milliseconds(2)); + bool started = false; unsigned result = 0; std::thread listener([&]() { setThreadName("other"); - - Host ph("Test", NetworkPreferences(50303, "", false, true)); - auto wh = ph.registerCapability(new WhisperHost()); - ph.start(); + started = true; /// Only interested in odd packets - auto w = wh->installWatch(BuildTopicMask("odd")); + auto w = whost1->installWatch(BuildTopicMask("odd")); started = true; set received; for (int iterout = 0, last = 0; iterout < 200 && last < 81; ++iterout) { - for (auto i: wh->checkWatch(w)) + for (auto i: whost1->checkWatch(w)) { - Message msg = wh->envelope(i).open(wh->fullTopic(w)); + Message msg = whost1->envelope(i).open(whost1->fullTopic(w)); last = RLP(msg.payload()).toInt(); if (received.count(last)) continue; @@ -66,22 +70,28 @@ BOOST_AUTO_TEST_CASE(topic) } this_thread::sleep_for(chrono::milliseconds(50)); } + }); + + Host host2("Test", NetworkPreferences(30300, "127.0.0.1", false, true)); + auto whost2 = host2.registerCapability(new WhisperHost()); + host2.start(); + + while (!host2.isStarted()) + this_thread::sleep_for(chrono::milliseconds(2)); + + this_thread::sleep_for(chrono::milliseconds(100)); + host2.addNode(host1.id(), "127.0.0.1", 30303, 30303); + + this_thread::sleep_for(chrono::milliseconds(500)); while (!started) - this_thread::sleep_for(chrono::milliseconds(50)); - - Host ph("Test", NetworkPreferences(50300, "", false, true)); - shared_ptr wh = ph.registerCapability(new WhisperHost()); - this_thread::sleep_for(chrono::milliseconds(500)); - ph.start(); - this_thread::sleep_for(chrono::milliseconds(500)); - ph.connect("127.0.0.1", 50303); + this_thread::sleep_for(chrono::milliseconds(2)); KeyPair us = KeyPair::create(); for (int i = 0; i < 10; ++i) { - wh->post(us.sec(), RLPStream().append(i * i).out(), BuildTopic(i)(i % 2 ? "odd" : "even")); + whost2->post(us.sec(), RLPStream().append(i * i).out(), BuildTopic(i)(i % 2 ? "odd" : "even")); this_thread::sleep_for(chrono::milliseconds(250)); } @@ -96,7 +106,15 @@ BOOST_AUTO_TEST_CASE(forwarding) cnote << "Testing Whisper forwarding..."; auto oldLogVerbosity = g_logVerbosity; g_logVerbosity = 0; - + + // Host must be configured not to share peers. + Host host1("Listner", NetworkPreferences(30303, "", false, true)); + host1.setIdealPeerCount(0); + auto whost1 = host1.registerCapability(new WhisperHost()); + host1.start(); + while (!host1.isStarted()) + this_thread::sleep_for(chrono::milliseconds(2)); + unsigned result = 0; bool done = false; @@ -105,22 +123,16 @@ BOOST_AUTO_TEST_CASE(forwarding) { setThreadName("listener"); - // Host must be configured not to share peers. - Host ph("Listner", NetworkPreferences(50303, "", false, true)); - ph.setIdealPeerCount(0); - auto wh = ph.registerCapability(new WhisperHost()); - ph.start(); - startedListener = true; /// Only interested in odd packets - auto w = wh->installWatch(BuildTopicMask("test")); + auto w = whost1->installWatch(BuildTopicMask("test")); for (int i = 0; i < 200 && !result; ++i) { - for (auto i: wh->checkWatch(w)) + for (auto i: whost1->checkWatch(w)) { - Message msg = wh->envelope(i).open(wh->fullTopic(w)); + Message msg = whost1->envelope(i).open(whost1->fullTopic(w)); unsigned last = RLP(msg.payload()).toInt(); cnote << "New message from:" << msg.from().abridged() << RLP(msg.payload()).toInt(); result = last; @@ -129,6 +141,16 @@ BOOST_AUTO_TEST_CASE(forwarding) } }); + + // Host must be configured not to share peers. + Host host2("Forwarder", NetworkPreferences(30305, "", false, true)); + host2.setIdealPeerCount(1); + auto whost2 = host2.registerCapability(new WhisperHost()); + host2.start(); + while (!host2.isStarted()) + this_thread::sleep_for(chrono::milliseconds(2)); + + Public fwderid; bool startedForwarder = false; std::thread forwarder([&]() { @@ -137,26 +159,19 @@ BOOST_AUTO_TEST_CASE(forwarding) while (!startedListener) this_thread::sleep_for(chrono::milliseconds(50)); - // Host must be configured not to share peers. - Host ph("Forwarder", NetworkPreferences(50305, "", false, true)); - ph.setIdealPeerCount(0); - auto wh = ph.registerCapability(new WhisperHost()); - this_thread::sleep_for(chrono::milliseconds(500)); - ph.start(); - this_thread::sleep_for(chrono::milliseconds(500)); - ph.connect("127.0.0.1", 50303); + host2.addNode(host1.id(), "127.0.0.1", 30303, 30303); startedForwarder = true; /// Only interested in odd packets - auto w = wh->installWatch(BuildTopicMask("test")); + auto w = whost2->installWatch(BuildTopicMask("test")); while (!done) { - for (auto i: wh->checkWatch(w)) + for (auto i: whost2->checkWatch(w)) { - Message msg = wh->envelope(i).open(wh->fullTopic(w)); + Message msg = whost2->envelope(i).open(whost2->fullTopic(w)); cnote << "New message from:" << msg.from().abridged() << RLP(msg.payload()).toInt(); } this_thread::sleep_for(chrono::milliseconds(50)); @@ -166,13 +181,13 @@ BOOST_AUTO_TEST_CASE(forwarding) while (!startedForwarder) this_thread::sleep_for(chrono::milliseconds(50)); - Host ph("Sender", NetworkPreferences(50300, "", false, true)); - ph.setIdealPeerCount(0); + Host ph("Sender", NetworkPreferences(30300, "", false, true)); + ph.setIdealPeerCount(1); shared_ptr wh = ph.registerCapability(new WhisperHost()); - this_thread::sleep_for(chrono::milliseconds(500)); ph.start(); - this_thread::sleep_for(chrono::milliseconds(500)); - ph.connect("127.0.0.1", 50305); + ph.addNode(host2.id(), "127.0.0.1", 30305, 30305); + while (!ph.isStarted()) + this_thread::sleep_for(chrono::milliseconds(10)); KeyPair us = KeyPair::create(); wh->post(us.sec(), RLPStream().append(1).out(), BuildTopic("test")); @@ -194,32 +209,33 @@ BOOST_AUTO_TEST_CASE(asyncforwarding) unsigned result = 0; bool done = false; + + // Host must be configured not to share peers. + Host host1("Forwarder", NetworkPreferences(30305, "", false, true)); + host1.setIdealPeerCount(1); + auto whost1 = host1.registerCapability(new WhisperHost()); + host1.start(); + while (!host1.isStarted()) + this_thread::sleep_for(chrono::milliseconds(2)); bool startedForwarder = false; std::thread forwarder([&]() { setThreadName("forwarder"); - - // Host must be configured not to share peers. - Host ph("Forwarder", NetworkPreferences(50305, "", false, true)); - ph.setIdealPeerCount(0); - auto wh = ph.registerCapability(new WhisperHost()); + this_thread::sleep_for(chrono::milliseconds(500)); - ph.start(); - - this_thread::sleep_for(chrono::milliseconds(500)); - ph.connect("127.0.0.1", 50303); +// ph.addNode("127.0.0.1", 30303, 30303); startedForwarder = true; /// Only interested in odd packets - auto w = wh->installWatch(BuildTopicMask("test")); + auto w = whost1->installWatch(BuildTopicMask("test")); while (!done) { - for (auto i: wh->checkWatch(w)) + for (auto i: whost1->checkWatch(w)) { - Message msg = wh->envelope(i).open(wh->fullTopic(w)); + Message msg = whost1->envelope(i).open(whost1->fullTopic(w)); cnote << "New message from:" << msg.from().abridged() << RLP(msg.payload()).toInt(); } this_thread::sleep_for(chrono::milliseconds(50)); @@ -227,30 +243,33 @@ BOOST_AUTO_TEST_CASE(asyncforwarding) }); while (!startedForwarder) - this_thread::sleep_for(chrono::milliseconds(50)); - + this_thread::sleep_for(chrono::milliseconds(2)); + { - Host ph("Sender", NetworkPreferences(50300, "", false, true)); - ph.setIdealPeerCount(0); - shared_ptr wh = ph.registerCapability(new WhisperHost()); - this_thread::sleep_for(chrono::milliseconds(500)); - ph.start(); - this_thread::sleep_for(chrono::milliseconds(500)); - ph.connect("127.0.0.1", 50305); - + Host host2("Sender", NetworkPreferences(30300, "", false, true)); + host2.setIdealPeerCount(1); + shared_ptr whost2 = host2.registerCapability(new WhisperHost()); + host2.start(); + while (!host2.isStarted()) + this_thread::sleep_for(chrono::milliseconds(2)); + host2.addNode(host1.id(), "127.0.0.1", 30305, 30305); + + while (!host2.peerCount()) + this_thread::sleep_for(chrono::milliseconds(5)); + KeyPair us = KeyPair::create(); - wh->post(us.sec(), RLPStream().append(1).out(), BuildTopic("test")); + whost2->post(us.sec(), RLPStream().append(1).out(), BuildTopic("test")); this_thread::sleep_for(chrono::milliseconds(250)); } { - Host ph("Listener", NetworkPreferences(50300, "", false, true)); - ph.setIdealPeerCount(0); + Host ph("Listener", NetworkPreferences(30300, "", false, true)); + ph.setIdealPeerCount(1); shared_ptr wh = ph.registerCapability(new WhisperHost()); - this_thread::sleep_for(chrono::milliseconds(500)); ph.start(); - this_thread::sleep_for(chrono::milliseconds(500)); - ph.connect("127.0.0.1", 50305); + while (!ph.isStarted()) + this_thread::sleep_for(chrono::milliseconds(2)); + ph.addNode(host1.id(), "127.0.0.1", 30305, 30305); /// Only interested in odd packets auto w = wh->installWatch(BuildTopicMask("test")); diff --git a/third/MainWin.cpp b/third/MainWin.cpp index 64f90a761..f1b7bc826 100644 --- a/third/MainWin.cpp +++ b/third/MainWin.cpp @@ -114,7 +114,8 @@ Main::Main(QWidget *parent) : connect(ui->ourAccounts->model(), SIGNAL(rowsMoved(const QModelIndex &, int, int, const QModelIndex &, int)), SLOT(ourAccountsRowsMoved())); - m_web3.reset(new WebThreeDirect("Third", getDataDir() + "/Third", false, {"eth", "shh"})); + bytesConstRef networkConfig((byte*)m_networkConfig.data(), m_networkConfig.size()); + m_web3.reset(new WebThreeDirect("Third", getDataDir() + "/Third", false, {"eth", "shh"}, NetworkPreferences(), networkConfig)); m_web3->connect(Host::pocHost()); m_server = unique_ptr(new WebThreeStubServer(m_qwebConnector, *web3(), keysAsVector(m_myKeys))); @@ -377,10 +378,10 @@ void Main::writeSettings() s.setValue("address", b); s.setValue("url", ui->urlEdit->text()); - bytes d = m_web3->saveNodes(); + bytes d = m_web3->saveNetwork(); if (d.size()) - m_nodes = QByteArray((char*)d.data(), (int)d.size()); - s.setValue("peers", m_nodes); + m_networkConfig = QByteArray((char*)d.data(), (int)d.size()); + s.setValue("peers", m_networkConfig); s.setValue("geometry", saveGeometry()); s.setValue("windowState", saveState()); @@ -409,7 +410,7 @@ void Main::readSettings(bool _skipGeometry) } } ethereum()->setAddress(m_myKeys.back().address()); - m_nodes = s.value("peers").toByteArray(); + m_networkConfig = s.value("peers").toByteArray(); ui->urlEdit->setText(s.value("url", "about:blank").toString()); //http://gavwood.com/gavcoin.html on_urlEdit_returnPressed(); } @@ -581,11 +582,9 @@ void Main::ensureNetwork() web3()->startNetwork(); web3()->connect(defPeer); } - else - if (!m_web3->peerCount()) - m_web3->connect(defPeer); - if (m_nodes.size()) - m_web3->restoreNodes(bytesConstRef((byte*)m_nodes.data(), m_nodes.size())); +// else +// if (!m_web3->peerCount()) +// m_web3->connect(defPeer); } void Main::on_connect_triggered() diff --git a/third/MainWin.h b/third/MainWin.h index 0bd75d5de..924fc057e 100644 --- a/third/MainWin.h +++ b/third/MainWin.h @@ -131,7 +131,7 @@ private: unsigned m_currenciesFilter = (unsigned)-1; unsigned m_balancesFilter = (unsigned)-1; - QByteArray m_nodes; + QByteArray m_networkConfig; QStringList m_servers; QNetworkAccessManager m_webCtrl;