diff --git a/CMakeLists.txt b/CMakeLists.txt index 4adac7060..7e3b6a53c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -394,6 +394,7 @@ endif () if (JSCONSOLE) add_subdirectory(libjsengine) add_subdirectory(libjsconsole) + add_subdirectory(ethconsole) endif () add_subdirectory(secp256k1) diff --git a/alethzero/Transact.cpp b/alethzero/Transact.cpp index 7a26f56f2..594133ba4 100644 --- a/alethzero/Transact.cpp +++ b/alethzero/Transact.cpp @@ -139,7 +139,7 @@ void Transact::updateDestination() void Transact::updateFee() { - ui->fee->setText(QString("(gas sub-total: %1)").arg(formatBalance(fee()).c_str())); +// ui->fee->setText(QString("(gas sub-total: %1)").arg(formatBalance(fee()).c_str())); auto totalReq = total(); ui->total->setText(QString("Total: %1").arg(formatBalance(totalReq).c_str())); @@ -435,9 +435,18 @@ Address Transact::fromAccount() return *it; } +void Transact::updateNonce() +{ + u256 n = ethereum()->countAt(fromAccount(), PendingBlock); + ui->nonce->setMaximum((unsigned)n); + ui->nonce->setMinimum(0); + ui->nonce->setValue((unsigned)n); +} + void Transact::on_send_clicked() { // Secret s = findSecret(value() + fee()); + u256 nonce = ui->autoNonce->isChecked() ? ethereum()->countAt(fromAccount(), PendingBlock) : ui->nonce->value(); auto a = fromAccount(); auto b = ethereum()->balanceAt(a, PendingBlock); @@ -455,7 +464,7 @@ void Transact::on_send_clicked() { // If execution is a contract creation, add Natspec to // a local Natspec LEVELDB - ethereum()->submitTransaction(s, value(), m_data, ui->gas->value(), gasPrice()); + ethereum()->submitTransaction(s, value(), m_data, ui->gas->value(), gasPrice(), nonce); #if ETH_SOLIDITY string src = ui->data->toPlainText().toStdString(); if (sourceIsSolidity(src)) @@ -474,7 +483,7 @@ void Transact::on_send_clicked() } else // TODO: cache like m_data. - ethereum()->submitTransaction(s, value(), m_context->fromString(ui->destination->currentText().toStdString()).first, m_data, ui->gas->value(), gasPrice()); + ethereum()->submitTransaction(s, value(), m_context->fromString(ui->destination->currentText().toStdString()).first, m_data, ui->gas->value(), gasPrice(), nonce); close(); } diff --git a/alethzero/Transact.h b/alethzero/Transact.h index b8b5134a4..e2c1a6662 100644 --- a/alethzero/Transact.h +++ b/alethzero/Transact.h @@ -45,7 +45,7 @@ public: void setEnvironment(dev::AddressHash const& _accounts, dev::eth::Client* _eth, NatSpecFace* _natSpecDB); private slots: - void on_from_currentIndexChanged(int) { rejigData(); rejigData(); } + void on_from_currentIndexChanged(int) { updateNonce(); rejigData(); } void on_destination_currentTextChanged(QString); void on_value_valueChanged(int) { updateFee(); rejigData(); } void on_gas_valueChanged(int) { updateFee(); rejigData(); } @@ -61,6 +61,7 @@ private slots: private: dev::eth::Client* ethereum() const { return m_ethereum; } void rejigData(); + void updateNonce(); dev::Address fromAccount(); void updateDestination(); diff --git a/alethzero/Transact.ui b/alethzero/Transact.ui index 87a8ebd99..fe1fc4c3e 100644 --- a/alethzero/Transact.ui +++ b/alethzero/Transact.ui @@ -14,35 +14,6 @@ Transact - - - - - 0 - 0 - - - - D&ata - - - Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter - - - data - - - - - - - &Optimise - - - true - - - @@ -92,6 +63,9 @@ + + + @@ -105,22 +79,6 @@ - - - - - - - 430000000 - - - 0 - - - - - - @@ -166,6 +124,54 @@ + + + + + 0 + 0 + + + + D&ata + + + Qt::AlignLeading|Qt::AlignLeft|Qt::AlignVCenter + + + data + + + + + + + + + + + + + 430000000 + + + 0 + + + + + + + false + + + true + + + + + + @@ -205,53 +211,68 @@ - - - - false - - - true + + + + &From - - + + from - - + + - - - - - 0 - 0 - - + + - + &Optimise - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + true - - + + + + false + + + + + - &From + Auto Nonce - - from + + true + + + false - - - - + + + autoNonce + toggled(bool) + nonce + setDisabled(bool) + + + 374 + 196 + + + 451 + 190 + + + + diff --git a/eth/main.cpp b/eth/main.cpp index c5a15e5fd..235183ef6 100644 --- a/eth/main.cpp +++ b/eth/main.cpp @@ -41,7 +41,7 @@ #include #if ETH_JSCONSOLE || !ETH_TRUE -#include +#include #endif #if ETH_READLINE || !ETH_TRUE #include @@ -141,6 +141,7 @@ void help() << " --master Give the master password for the key store." << endl << " --password Give a password for a private key." << endl << " --sentinel Set the sentinel for reporting bad blocks or chain issues." << endl + << " --prime Specify n as the 6 digit prime number to start Frontier." << endl << endl << "Client transacting:" << endl /*<< " -B,--block-fees Set the block fee profit in the reference unit e.g. ¢ (default: 15)." << endl @@ -187,6 +188,7 @@ void help() << " --from Export only from block n; n may be a decimal, a '0x' prefixed hash, or 'latest'." << endl << " --to Export only to block n (inclusive); n may be a decimal, a '0x' prefixed hash, or 'latest'." << endl << " --only Equivalent to --export-from n --export-to n." << endl + << " --dont-check Avoids checking some of the aspects of blocks. Faster importing, but only do if you know the data is valid." << endl << endl << "General Options:" << endl << " -d,--db-path Load database from path (default: " << getDataDir() << ")" << endl @@ -240,6 +242,16 @@ string pretty(h160 _a, dev::eth::State const& _st) bool g_exit = false; +inline bool isPrime(unsigned _number) +{ + if (((!(_number & 1)) && _number != 2 ) || (_number < 2) || (_number % 3 == 0 && _number != 3)) + return false; + for(unsigned k = 1; 36 * k * k - 12 * k < _number; ++k) + if ((_number % (6 * k + 1) == 0) || (_number % (6 * k - 1) == 0)) + return false; + return true; +} + void sighandler(int) { g_exit = true; @@ -280,9 +292,12 @@ int main(int argc, char** argv) /// Operating mode. OperationMode mode = OperationMode::Node; string dbPath; + unsigned prime = 0; + bool yesIReallyKnowWhatImDoing = false; /// File name for import/export. string filename; + bool safeImport = false; /// Hashes/numbers for export range. string exportFrom = "1"; @@ -395,11 +410,25 @@ int main(int argc, char** argv) mode = OperationMode::Import; filename = argv[++i]; } + else if (arg == "--dont-check") + safeImport = true; else if ((arg == "-E" || arg == "--export") && i + 1 < argc) { mode = OperationMode::Export; filename = argv[++i]; } + else if (arg == "--prime" && i + 1 < argc) + try + { + prime = stoi(argv[++i]); + } + catch (...) + { + cerr << "Bad " << arg << " option: " << argv[i] << endl; + return -1; + } + else if (arg == "--yes-i-really-know-what-im-doing") + yesIReallyKnowWhatImDoing = true; else if (arg == "--sentinel" && i + 1 < argc) sentinel = argv[++i]; else if (arg == "--mine-on-wrong-chain") @@ -753,13 +782,18 @@ int main(int argc, char** argv) unsigned futureTime = 0; unsigned unknownParent = 0; unsigned bad = 0; + chrono::steady_clock::time_point t = chrono::steady_clock::now(); + double last = 0; + unsigned lastImported = 0; + unsigned imported = 0; while (in.peek() != -1) { bytes block(8); in.read((char*)block.data(), 8); block.resize(RLP(block, RLP::LaisezFaire).actualSize()); in.read((char*)block.data() + 8, block.size() - 8); - switch (web3.ethereum()->injectBlock(block)) + + switch (web3.ethereum()->queueBlock(block, safeImport)) { case ImportResult::Success: good++; break; case ImportResult::AlreadyKnown: alreadyHave++; break; @@ -768,11 +802,52 @@ int main(int argc, char** argv) case ImportResult::FutureTimeKnown: futureTime++; break; default: bad++; break; } + + // sync chain with queue + tuple r = web3.ethereum()->syncQueue(10); + imported += get<2>(r); + + double e = chrono::duration_cast(chrono::steady_clock::now() - t).count() / 1000.0; + if ((unsigned)e >= last + 10) + { + auto i = imported - lastImported; + auto d = e - last; + cout << i << " more imported at " << (round(i * 10 / d) / 10) << " blocks/s. " << imported << " imported in " << e << " seconds at " << (round(imported * 10 / e) / 10) << " blocks/s (#" << web3.ethereum()->number() << ")" << endl; + last = (unsigned)e; + lastImported = imported; + } } - cout << (good + bad + futureTime + unknownParent + alreadyHave) << " total: " << good << " ok, " << alreadyHave << " got, " << futureTime << " future, " << unknownParent << " unknown parent, " << bad << " malformed." << endl; + + while (web3.ethereum()->blockQueue().items().first + web3.ethereum()->blockQueue().items().second > 0) + { + sleep(1); + web3.ethereum()->syncQueue(100000); + } + double e = chrono::duration_cast(chrono::steady_clock::now() - t).count() / 1000.0; + cout << imported << " imported in " << e << " seconds at " << (round(imported * 10 / e) / 10) << " blocks/s (#" << web3.ethereum()->number() << ")" << endl; return 0; } + if (c_network == eth::Network::Frontier && !yesIReallyKnowWhatImDoing) + { + auto pd = contents(getDataDir() + "primes"); + unordered_set primes = RLP(pd).toUnorderedSet(); + while (true) + { + if (!prime) + try + { + prime = stoi(getPassword("To enter the Frontier, enter a 6 digit prime that you have not entered before: ")); + } + catch (...) {} + if (isPrime(prime) && !primes.count(prime)) + break; + prime = 0; + } + primes.insert(prime); + writeFile(getDataDir() + "primes", rlp(primes)); + } + if (keyManager.exists()) { if (masterPassword.empty() || !keyManager.load(masterPassword)) @@ -1742,12 +1817,24 @@ int main(int argc, char** argv) if (useConsole) { #if ETH_JSCONSOLE - JSConsole console(web3, make_shared([&](){return web3.ethereum();}, getAccountPassword, keyManager)); + JSLocalConsole console; + + jsonrpcServer = shared_ptr(new dev::WebThreeStubServer(*jsonrpcConnector.get(), web3, make_shared([&](){ return web3.ethereum(); }, getAccountPassword, keyManager), vector(), keyManager, *gasPricer)); + jsonrpcServer->setMiningBenefactorChanger([&](Address const& a) { beneficiary = a; }); + jsonrpcServer->StartListening(); + if (jsonAdmin.empty()) + jsonAdmin = jsonrpcServer->newSession(SessionPermissions{{Priviledge::Admin}}); + else + jsonrpcServer->addSession(jsonAdmin, SessionPermissions{{Priviledge::Admin}}); + cout << "JSONRPC Admin Session Key: " << jsonAdmin << endl; + while (!g_exit) { console.readExpression(); stopMiningAfterXBlocks(c, n, mining); } + + jsonrpcServer->StopListening(); #endif } else diff --git a/ethconsole/CMakeLists.txt b/ethconsole/CMakeLists.txt new file mode 100644 index 000000000..08fa7ca83 --- /dev/null +++ b/ethconsole/CMakeLists.txt @@ -0,0 +1,31 @@ +cmake_policy(SET CMP0015 NEW) +set(CMAKE_AUTOMOC OFF) + +aux_source_directory(. SRC_LIST) + +include_directories(BEFORE ..) +include_directories(${JSON_RPC_CPP_INCLUDE_DIRS}) +include_directories(${CURL_INCLUDE_DIRS}) +include_directories(${V8_INCLUDE_DIRS}) + +set(EXECUTABLE ethconsole) + +file(GLOB HEADERS "*.h") + +add_executable(${EXECUTABLE} ${SRC_LIST} ${HEADERS}) + +target_link_libraries(${EXECUTABLE} ${Boost_REGEX_LIBRARIES}) +target_link_libraries(${EXECUTABLE} ${READLINE_LIBRARIES}) +target_link_libraries(${EXECUTABLE} ${CURL_LIBRARIES}) + +if (DEFINED WIN32 AND NOT DEFINED CMAKE_COMPILER_IS_MINGW) + eth_copy_dlls(${EXECUTABLE} CURL_DLLS) +endif() +target_link_libraries(${EXECUTABLE} jsconsole) + +if (APPLE) + install(TARGETS ${EXECUTABLE} DESTINATION bin) +else() + eth_install_executable(${EXECUTABLE}) +endif() + diff --git a/ethconsole/main.cpp b/ethconsole/main.cpp new file mode 100644 index 000000000..5df3444eb --- /dev/null +++ b/ethconsole/main.cpp @@ -0,0 +1,41 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file main.cpp + * @author Marek + * @date 2014 + */ + +#include +#include +using namespace std; +using namespace dev; +using namespace dev::eth; + +int main(int argc, char** argv) +{ + string remote; + if (argc == 1) + remote = "http://localhost:8545"; + else if (argc == 2) + remote = argv[1]; + + JSRemoteConsole console(remote); + while (true) + console.readExpression(); + + return 0; +} diff --git a/ethvm/main.cpp b/ethvm/main.cpp index 08a1b4508..4ca733ed0 100644 --- a/ethvm/main.cpp +++ b/ethvm/main.cpp @@ -165,7 +165,7 @@ int main(int argc, char** argv) executive.initialize(t); executive.create(sender, value, gasPrice, gas, &data, origin); - boost::timer timer; + Timer timer; executive.go(onOp); double execTime = timer.elapsed(); executive.finalize(); diff --git a/exp/main.cpp b/exp/main.cpp index f0574fa7c..1db9b4267 100644 --- a/exp/main.cpp +++ b/exp/main.cpp @@ -88,7 +88,7 @@ int main() data.push_back(rlp(i)); h256 ret; - DEV_TIMED(triedb) + DEV_TIMED("triedb") { MemoryDB mdb; GenericTrieDB t(&mdb); @@ -99,7 +99,7 @@ int main() ret = t.root(); } cdebug << ret; - DEV_TIMED(hash256) + DEV_TIMED("hash256") ret = orderedTrieRoot(data); cdebug << ret; } diff --git a/libdevcore/Common.cpp b/libdevcore/Common.cpp index 17ccae6b1..e33936102 100644 --- a/libdevcore/Common.cpp +++ b/libdevcore/Common.cpp @@ -48,9 +48,9 @@ const char* TimerChannel::name() { return EthRed " ⚡ "; } TimerHelper::~TimerHelper() { - auto e = m_t.elapsed(); - if (!m_ms || e * 1000 > m_ms) - clog(TimerChannel) << m_id << e << "s"; + auto e = std::chrono::high_resolution_clock::now() - m_t; + if (!m_ms || e > chrono::milliseconds(m_ms)) + clog(TimerChannel) << m_id << chrono::duration_cast(e).count() << "ms"; } } diff --git a/libdevcore/Common.h b/libdevcore/Common.h index c6ed25223..f1d35bbc7 100644 --- a/libdevcore/Common.h +++ b/libdevcore/Common.h @@ -40,7 +40,7 @@ #include #include #include -#include +#include #include #pragma warning(push) #pragma GCC diagnostic push @@ -193,16 +193,29 @@ private: class TimerHelper { public: - TimerHelper(char const* _id, unsigned _msReportWhenGreater = 0): m_id(_id), m_ms(_msReportWhenGreater) {} + TimerHelper(std::string const& _id, unsigned _msReportWhenGreater = 0): m_t(std::chrono::high_resolution_clock::now()), m_id(_id), m_ms(_msReportWhenGreater) {} ~TimerHelper(); private: - boost::timer m_t; - char const* m_id; + std::chrono::high_resolution_clock::time_point m_t; + std::string m_id; unsigned m_ms; }; -#define DEV_TIMED(S) for (::std::pair<::dev::TimerHelper, bool> __eth_t(#S, true); __eth_t.second; __eth_t.second = false) +class Timer +{ +public: + Timer() { restart(); } + + std::chrono::high_resolution_clock::duration duration() const { return std::chrono::high_resolution_clock::now() - m_t; } + double elapsed() const { return std::chrono::duration_cast(duration()).count() / 1000000.0; } + void restart() { m_t = std::chrono::high_resolution_clock::now(); } + +private: + std::chrono::high_resolution_clock::time_point m_t; +}; + +#define DEV_TIMED(S) for (::std::pair<::dev::TimerHelper, bool> __eth_t(S, true); __eth_t.second; __eth_t.second = false) #define DEV_TIMED_SCOPE(S) ::dev::TimerHelper __eth_t(S) #if WIN32 #define DEV_TIMED_FUNCTION DEV_TIMED_SCOPE(__FUNCSIG__) @@ -210,7 +223,7 @@ private: #define DEV_TIMED_FUNCTION DEV_TIMED_SCOPE(__PRETTY_FUNCTION__) #endif -#define DEV_TIMED_ABOVE(S, MS) for (::std::pair<::dev::TimerHelper, bool> __eth_t(::dev::TimerHelper(#S, MS), true); __eth_t.second; __eth_t.second = false) +#define DEV_TIMED_ABOVE(S, MS) for (::std::pair<::dev::TimerHelper, bool> __eth_t(::dev::TimerHelper(S, MS), true); __eth_t.second; __eth_t.second = false) #define DEV_TIMED_SCOPE_ABOVE(S, MS) ::dev::TimerHelper __eth_t(S, MS) #if WIN32 #define DEV_TIMED_FUNCTION_ABOVE(MS) DEV_TIMED_SCOPE_ABOVE(__FUNCSIG__, MS) diff --git a/libdevcore/FixedHash.h b/libdevcore/FixedHash.h index 922a6ec30..c7c551c2b 100644 --- a/libdevcore/FixedHash.h +++ b/libdevcore/FixedHash.h @@ -113,6 +113,9 @@ public: /// @returns an abridged version of the hash as a user-readable hex string. std::string abridged() const { return toHex(ref().cropped(0, 4)) + "\342\200\246"; } + /// @returns a version of the hash as a user-readable hex string that leaves out the middle part. + std::string abridgedMiddle() const { return toHex(ref().cropped(0, 4)) + "\342\200\246" + toHex(ref().cropped(N - 4)); } + /// @returns the hash as a user-readable hex string. std::string hex() const { return toHex(ref()); } diff --git a/libdevcore/RLP.h b/libdevcore/RLP.h index 6ae9c165b..dee438be4 100644 --- a/libdevcore/RLP.h +++ b/libdevcore/RLP.h @@ -204,9 +204,7 @@ public: { ret.reserve(itemCount()); for (auto const& i: *this) - { ret.push_back((T)i); - } } return ret; } @@ -216,15 +214,21 @@ public: { std::set ret; if (isList()) - { for (auto const& i: *this) - { ret.insert((T)i); - } - } return ret; } - + + template + std::unordered_set toUnorderedSet() const + { + std::unordered_set ret; + if (isList()) + for (auto const& i: *this) + ret.insert((T)i); + return ret; + } + template std::pair toPair() const { diff --git a/libdevcore/TrieCommon.cpp b/libdevcore/TrieCommon.cpp index ff44906b1..5bf6070ee 100644 --- a/libdevcore/TrieCommon.cpp +++ b/libdevcore/TrieCommon.cpp @@ -60,7 +60,7 @@ std::string hexPrefixEncode(bytes const& _hexVector, bool _leaf, int _begin, int std::string hexPrefixEncode(bytesConstRef _data, bool _leaf, int _beginNibble, int _endNibble, unsigned _offset) { unsigned begin = _beginNibble + _offset; - unsigned end = (_endNibble < 0 ? (_data.size() * 2 - _offset) + 1 + _endNibble : _endNibble) + _offset; + unsigned end = (_endNibble < 0 ? ((int)(_data.size() * 2 - _offset) + 1) + _endNibble : _endNibble) + _offset; bool odd = (end - begin) & 1; std::string ret(1, ((_leaf ? 2 : 0) | (odd ? 1 : 0)) * 16); diff --git a/libdevcore/Worker.cpp b/libdevcore/Worker.cpp index ab19b2f74..d47955753 100644 --- a/libdevcore/Worker.cpp +++ b/libdevcore/Worker.cpp @@ -65,14 +65,14 @@ void Worker::startWorking() m_state.exchange(ex); // cnote << "Waiting until not Stopped..."; - DEV_TIMED_ABOVE(Worker stopping, 100) + DEV_TIMED_ABOVE("Worker stopping", 100) while (m_state == WorkerState::Stopped) this_thread::sleep_for(chrono::milliseconds(20)); } })); // cnote << "Spawning" << m_name; } - DEV_TIMED_ABOVE(Start worker, 100) + DEV_TIMED_ABOVE("Start worker", 100) while (m_state == WorkerState::Starting) this_thread::sleep_for(chrono::microseconds(20)); } @@ -85,7 +85,7 @@ void Worker::stopWorking() WorkerState ex = WorkerState::Started; m_state.compare_exchange_strong(ex, WorkerState::Stopping); - DEV_TIMED_ABOVE(Stop worker, 100) + DEV_TIMED_ABOVE("Stop worker", 100) while (m_state != WorkerState::Stopped) this_thread::sleep_for(chrono::microseconds(20)); } @@ -99,7 +99,7 @@ void Worker::terminate() { m_state.exchange(WorkerState::Killing); - DEV_TIMED_ABOVE(Terminate worker, 100) + DEV_TIMED_ABOVE("Terminate worker", 100) m_work->join(); m_work.reset(); diff --git a/libethash-cl/ethash_cl_miner.cpp b/libethash-cl/ethash_cl_miner.cpp index 315f29685..79024e2ad 100644 --- a/libethash-cl/ethash_cl_miner.cpp +++ b/libethash-cl/ethash_cl_miner.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -64,7 +65,7 @@ static void addDefinition(string& _source, char const* _id, unsigned _value) ethash_cl_miner::search_hook::~search_hook() {} ethash_cl_miner::ethash_cl_miner() -: m_opencl_1_1() +: m_openclOnePointOne() { } @@ -252,7 +253,7 @@ void ethash_cl_miner::finish() bool ethash_cl_miner::init( uint8_t const* _dag, uint64_t _dagSize, - unsigned workgroup_size, + unsigned _workgroupSize, unsigned _platformId, unsigned _deviceId ) @@ -291,23 +292,23 @@ bool ethash_cl_miner::init( return false; } if (strncmp("OpenCL 1.1", device_version.c_str(), 10) == 0) - m_opencl_1_1 = true; + m_openclOnePointOne = true; // create context m_context = cl::Context(vector(&device, &device + 1)); m_queue = cl::CommandQueue(m_context, device); // use requested workgroup size, but we require multiple of 8 - m_workgroup_size = ((workgroup_size + 7) / 8) * 8; + m_workgroupSize = ((_workgroupSize + 7) / 8) * 8; // patch source code // note: ETHASH_CL_MINER_KERNEL is simply ethash_cl_miner_kernel.cl compiled // into a byte array by bin2h.cmake. There is no need to load the file by hand in runtime string code(ETHASH_CL_MINER_KERNEL, ETHASH_CL_MINER_KERNEL + ETHASH_CL_MINER_KERNEL_SIZE); - addDefinition(code, "GROUP_SIZE", m_workgroup_size); + addDefinition(code, "GROUP_SIZE", m_workgroupSize); addDefinition(code, "DAG_SIZE", (unsigned)(_dagSize / ETHASH_MIX_BYTES)); addDefinition(code, "ACCESSES", ETHASH_ACCESSES); - addDefinition(code, "MAX_OUTPUTS", c_max_search_results); + addDefinition(code, "MAX_OUTPUTS", c_maxSearchResults); //debugf("%s", code.c_str()); // create miner OpenCL program @@ -330,7 +331,7 @@ bool ethash_cl_miner::init( // create buffer for dag try { - m_dagChunksNum = 1; + m_dagChunksCount = 1; m_dagChunks.push_back(cl::Buffer(m_context, CL_MEM_READ_ONLY, _dagSize)); ETHCL_LOG("Created one big buffer for the DAG"); } @@ -346,8 +347,8 @@ bool ethash_cl_miner::init( << result << ". Trying to allocate 4 chunks." ); // The OpenCL kernel has a hard coded number of 4 chunks at the moment - m_dagChunksNum = 4; - for (unsigned i = 0; i < m_dagChunksNum; i++) + m_dagChunksCount = 4; + for (unsigned i = 0; i < m_dagChunksCount; i++) { // TODO Note: If we ever change to _dagChunksNum other than 4, then the size would need recalculation ETHCL_LOG("Creating buffer for chunk " << i); @@ -359,24 +360,24 @@ bool ethash_cl_miner::init( } } - if (m_dagChunksNum == 1) + if (m_dagChunksCount == 1) { ETHCL_LOG("Loading single big chunk kernels"); - m_hash_kernel = cl::Kernel(program, "ethash_hash"); - m_search_kernel = cl::Kernel(program, "ethash_search"); + m_hashKernel = cl::Kernel(program, "ethash_hash"); + m_searchKernel = cl::Kernel(program, "ethash_search"); } else { ETHCL_LOG("Loading chunk kernels"); - m_hash_kernel = cl::Kernel(program, "ethash_hash_chunks"); - m_search_kernel = cl::Kernel(program, "ethash_search_chunks"); + m_hashKernel = cl::Kernel(program, "ethash_hash_chunks"); + m_searchKernel = cl::Kernel(program, "ethash_search_chunks"); } // create buffer for header ETHCL_LOG("Creating buffer for header."); m_header = cl::Buffer(m_context, CL_MEM_READ_ONLY, 32); - if (m_dagChunksNum == 1) + if (m_dagChunksCount == 1) { ETHCL_LOG("Mapping one big chunk."); m_queue.enqueueWriteBuffer(m_dagChunks[0], CL_TRUE, 0, _dagSize, _dag); @@ -385,12 +386,12 @@ bool ethash_cl_miner::init( { // TODO Note: If we ever change to _dagChunksNum other than 4, then the size would need recalculation void* dag_ptr[4]; - for (unsigned i = 0; i < m_dagChunksNum; i++) + for (unsigned i = 0; i < m_dagChunksCount; i++) { ETHCL_LOG("Mapping chunk " << i); - dag_ptr[i] = m_queue.enqueueMapBuffer(m_dagChunks[i], true, m_opencl_1_1 ? CL_MAP_WRITE : CL_MAP_WRITE_INVALIDATE_REGION, 0, (i == 3) ? (_dagSize - 3 * ((_dagSize >> 9) << 7)) : (_dagSize >> 9) << 7); + dag_ptr[i] = m_queue.enqueueMapBuffer(m_dagChunks[i], true, m_openclOnePointOne ? CL_MAP_WRITE : CL_MAP_WRITE_INVALIDATE_REGION, 0, (i == 3) ? (_dagSize - 3 * ((_dagSize >> 9) << 7)) : (_dagSize >> 9) << 7); } - for (unsigned i = 0; i < m_dagChunksNum; i++) + for (unsigned i = 0; i < m_dagChunksCount; i++) { memcpy(dag_ptr[i], (char *)_dag + i*((_dagSize >> 9) << 7), (i == 3) ? (_dagSize - 3 * ((_dagSize >> 9) << 7)) : (_dagSize >> 9) << 7); m_queue.enqueueUnmapMemObject(m_dagChunks[i], dag_ptr[i]); @@ -398,11 +399,11 @@ bool ethash_cl_miner::init( } // create mining buffers - for (unsigned i = 0; i != c_num_buffers; ++i) + for (unsigned i = 0; i != c_bufferCount; ++i) { ETHCL_LOG("Creating mining buffer " << i); - m_hash_buf[i] = cl::Buffer(m_context, CL_MEM_WRITE_ONLY | (!m_opencl_1_1 ? CL_MEM_HOST_READ_ONLY : 0), 32 * c_hash_batch_size); - m_search_buf[i] = cl::Buffer(m_context, CL_MEM_WRITE_ONLY, (c_max_search_results + 1) * sizeof(uint32_t)); + m_hashBuffer[i] = cl::Buffer(m_context, CL_MEM_WRITE_ONLY | (!m_openclOnePointOne ? CL_MEM_HOST_READ_ONLY : 0), 32 * c_hashBatchSize); + m_searchBuffer[i] = cl::Buffer(m_context, CL_MEM_WRITE_ONLY, (c_maxSearchResults + 1) * sizeof(uint32_t)); } } catch (cl::Error const& err) @@ -413,7 +414,7 @@ bool ethash_cl_miner::init( return true; } -void ethash_cl_miner::search(uint8_t const* header, uint64_t target, search_hook& hook) +void ethash_cl_miner::search(uint8_t const* header, uint64_t target, search_hook& hook, unsigned _msPerBatch) { try { @@ -429,8 +430,8 @@ void ethash_cl_miner::search(uint8_t const* header, uint64_t target, search_hook // update header constant buffer m_queue.enqueueWriteBuffer(m_header, false, 0, 32, header); - for (unsigned i = 0; i != c_num_buffers; ++i) - m_queue.enqueueWriteBuffer(m_search_buf[i], false, 0, 4, &c_zero); + for (unsigned i = 0; i != c_bufferCount; ++i) + m_queue.enqueueWriteBuffer(m_searchBuffer[i], false, 0, 4, &c_zero); #if CL_VERSION_1_2 && 0 cl::Event pre_return_event; @@ -441,53 +442,59 @@ void ethash_cl_miner::search(uint8_t const* header, uint64_t target, search_hook m_queue.finish(); unsigned argPos = 2; - m_search_kernel.setArg(1, m_header); - for (unsigned i = 0; i < m_dagChunksNum; ++i, ++argPos) - m_search_kernel.setArg(argPos, m_dagChunks[i]); + m_searchKernel.setArg(1, m_header); + for (unsigned i = 0; i < m_dagChunksCount; ++i, ++argPos) + m_searchKernel.setArg(argPos, m_dagChunks[i]); // pass these to stop the compiler unrolling the loops - m_search_kernel.setArg(argPos + 1, target); - m_search_kernel.setArg(argPos + 2, ~0u); + m_searchKernel.setArg(argPos + 1, target); + m_searchKernel.setArg(argPos + 2, ~0u); unsigned buf = 0; random_device engine; uint64_t start_nonce = uniform_int_distribution()(engine); - for (;; start_nonce += c_search_batch_size) + for (;; start_nonce += m_batchSize) { // supply output buffer to kernel - m_search_kernel.setArg(0, m_search_buf[buf]); - if (m_dagChunksNum == 1) - m_search_kernel.setArg(3, start_nonce); + m_searchKernel.setArg(0, m_searchBuffer[buf]); + if (m_dagChunksCount == 1) + m_searchKernel.setArg(3, start_nonce); else - m_search_kernel.setArg(6, start_nonce); + m_searchKernel.setArg(6, start_nonce); // execute it! - m_queue.enqueueNDRangeKernel(m_search_kernel, cl::NullRange, c_search_batch_size, m_workgroup_size); + boost::timer t; + m_queue.enqueueNDRangeKernel(m_searchKernel, cl::NullRange, m_batchSize, m_workgroupSize); + unsigned ms = t.elapsed() * 1000; + if (ms > _msPerBatch * 1.1) + m_batchSize = max(128, m_batchSize * 9 / 10); + else if (ms < _msPerBatch * 0.9) + m_batchSize = m_batchSize * 10 / 9; pending.push({ start_nonce, buf }); - buf = (buf + 1) % c_num_buffers; + buf = (buf + 1) % c_bufferCount; // read results - if (pending.size() == c_num_buffers) + if (pending.size() == c_bufferCount) { pending_batch const& batch = pending.front(); // could use pinned host pointer instead - uint32_t* results = (uint32_t*)m_queue.enqueueMapBuffer(m_search_buf[batch.buf], true, CL_MAP_READ, 0, (1 + c_max_search_results) * sizeof(uint32_t)); - unsigned num_found = min(results[0], c_max_search_results); + uint32_t* results = (uint32_t*)m_queue.enqueueMapBuffer(m_searchBuffer[batch.buf], true, CL_MAP_READ, 0, (1 + c_maxSearchResults) * sizeof(uint32_t)); + unsigned num_found = min(results[0], c_maxSearchResults); - uint64_t nonces[c_max_search_results]; + uint64_t nonces[c_maxSearchResults]; for (unsigned i = 0; i != num_found; ++i) nonces[i] = batch.start_nonce + results[i + 1]; - m_queue.enqueueUnmapMemObject(m_search_buf[batch.buf], results); + m_queue.enqueueUnmapMemObject(m_searchBuffer[batch.buf], results); bool exit = num_found && hook.found(nonces, num_found); - exit |= hook.searched(batch.start_nonce, c_search_batch_size); // always report searched before exit + exit |= hook.searched(batch.start_nonce, m_batchSize); // always report searched before exit if (exit) break; // reset search buffer if we're still going if (num_found) - m_queue.enqueueWriteBuffer(m_search_buf[batch.buf], true, 0, 4, &c_zero); + m_queue.enqueueWriteBuffer(m_searchBuffer[batch.buf], true, 0, 4, &c_zero); pending.pop(); } diff --git a/libethash-cl/ethash_cl_miner.h b/libethash-cl/ethash_cl_miner.h index f36082a5a..996453c00 100644 --- a/libethash-cl/ethash_cl_miner.h +++ b/libethash-cl/ethash_cl_miner.h @@ -19,6 +19,9 @@ class ethash_cl_miner { +private: + enum { c_maxSearchResults = 63, c_bufferCount = 2, c_hashBatchSize = 1024, c_searchBatchSize = 1024 * 16 }; + public: struct search_hook { @@ -29,7 +32,6 @@ public: virtual bool searched(uint64_t start_nonce, uint32_t count) = 0; }; -public: ethash_cl_miner(); ~ethash_cl_miner(); @@ -50,33 +52,32 @@ public: bool init( uint8_t const* _dag, uint64_t _dagSize, - unsigned workgroup_size = 64, + unsigned _workgroupSize = 64, unsigned _platformId = 0, unsigned _deviceId = 0 ); void finish(); - void search(uint8_t const* header, uint64_t target, search_hook& hook); + void search(uint8_t const* _header, uint64_t _target, search_hook& _hook, unsigned _msPerBatch = 100); - void hash_chunk(uint8_t* ret, uint8_t const* header, uint64_t nonce, unsigned count); - void search_chunk(uint8_t const* header, uint64_t target, search_hook& hook); + void hash_chunk(uint8_t* _ret, uint8_t const* _header, uint64_t _nonce, unsigned _count); + void search_chunk(uint8_t const*_header, uint64_t _target, search_hook& _hook); private: static std::vector getDevices(std::vector const& _platforms, unsigned _platformId); - - enum { c_max_search_results = 63, c_num_buffers = 2, c_hash_batch_size = 1024, c_search_batch_size = 1024*256 }; cl::Context m_context; cl::CommandQueue m_queue; - cl::Kernel m_hash_kernel; - cl::Kernel m_search_kernel; - unsigned int m_dagChunksNum; + cl::Kernel m_hashKernel; + cl::Kernel m_searchKernel; + unsigned int m_dagChunksCount; std::vector m_dagChunks; cl::Buffer m_header; - cl::Buffer m_hash_buf[c_num_buffers]; - cl::Buffer m_search_buf[c_num_buffers]; - unsigned m_workgroup_size; - bool m_opencl_1_1; + cl::Buffer m_hashBuffer[c_bufferCount]; + cl::Buffer m_searchBuffer[c_bufferCount]; + unsigned m_workgroupSize; + unsigned m_batchSize = c_searchBatchSize; + bool m_openclOnePointOne; /// Allow CPU to appear as an OpenCL device or not. Default is false static bool s_allowCPU; diff --git a/libethcore/Common.h b/libethcore/Common.h index 19ca600b9..25a6a8e1d 100644 --- a/libethcore/Common.h +++ b/libethcore/Common.h @@ -97,6 +97,12 @@ enum class RelativeBlock: BlockNumber Pending = PendingBlock }; +struct ImportRoute +{ + h256s deadBlocks; + h256s liveBlocks; +}; + enum class ImportResult { Success = 0, diff --git a/libethcore/Ethash.cpp b/libethcore/Ethash.cpp index b277e3c1c..70908ee44 100644 --- a/libethcore/Ethash.cpp +++ b/libethcore/Ethash.cpp @@ -225,26 +225,58 @@ std::string Ethash::CPUMiner::platformInfo() #if ETH_ETHASHCL || !ETH_TRUE +using UniqueGuard = std::unique_lock; + +template +class Notified +{ +public: + Notified() {} + Notified(N const& _v): m_value(_v) {} + Notified(Notified const&) = delete; + Notified& operator=(N const& _v) { UniqueGuard l(m_mutex); m_value = _v; m_cv.notify_all(); return *this; } + + operator N() const { UniqueGuard l(m_mutex); return m_value; } + + void wait() const { UniqueGuard l(m_mutex); m_cv.wait(l); } + void wait(N const& _v) const { UniqueGuard l(m_mutex); m_cv.wait(l, [&](){return m_value == _v;}); } + template void wait(F const& _f) const { UniqueGuard l(m_mutex); m_cv.wait(l, _f); } + +private: + mutable Mutex m_mutex; + mutable std::condition_variable m_cv; + N m_value; +}; + class EthashCLHook: public ethash_cl_miner::search_hook { public: EthashCLHook(Ethash::GPUMiner* _owner): m_owner(_owner) {} + EthashCLHook(EthashCLHook const&) = delete; void abort() { - Guard l(x_all); - if (m_aborted) - return; + { + UniqueGuard l(x_all); + if (m_aborted) + return; // cdebug << "Attempting to abort"; - m_abort = true; - for (unsigned timeout = 0; timeout < 100 && !m_aborted; ++timeout) - std::this_thread::sleep_for(chrono::milliseconds(30)); + + m_abort = true; + } + // m_abort is true so now searched()/found() will return true to abort the search. + // we hang around on this thread waiting for them to point out that they have aborted since + // otherwise we may end up deleting this object prior to searched()/found() being called. + m_aborted.wait(true); +// for (unsigned timeout = 0; timeout < 100 && !m_aborted; ++timeout) +// std::this_thread::sleep_for(chrono::milliseconds(30)); // if (!m_aborted) // cwarn << "Couldn't abort. Abandoning OpenCL process."; } void reset() { + UniqueGuard l(x_all); m_aborted = m_abort = false; } @@ -253,27 +285,19 @@ protected: { // dev::operator <<(std::cerr << "Found nonces: ", vector(_nonces, _nonces + _count)) << std::endl; for (uint32_t i = 0; i < _count; ++i) - { if (m_owner->report(_nonces[i])) - { - m_aborted = true; - return true; - } - } + return (m_aborted = true); return m_owner->shouldStop(); } virtual bool searched(uint64_t _startNonce, uint32_t _count) override { - Guard l(x_all); + UniqueGuard l(x_all); // std::cerr << "Searched " << _count << " from " << _startNonce << std::endl; m_owner->accumulateHashes(_count); m_last = _startNonce + _count; if (m_abort || m_owner->shouldStop()) - { - m_aborted = true; - return true; - } + return (m_aborted = true); return false; } @@ -281,7 +305,7 @@ private: Mutex x_all; uint64_t m_last; bool m_abort = false; - bool m_aborted = true; + Notified m_aborted = {true}; Ethash::GPUMiner* m_owner = nullptr; }; diff --git a/libethcore/EthashAux.cpp b/libethcore/EthashAux.cpp index 14a44a812..efb6066f7 100644 --- a/libethcore/EthashAux.cpp +++ b/libethcore/EthashAux.cpp @@ -119,9 +119,11 @@ void EthashAux::killCache(h256 const& _s) EthashAux::LightType EthashAux::light(h256 const& _seedHash) { - ReadGuard l(get()->x_lights); - LightType ret = get()->m_lights[_seedHash]; - return ret ? ret : (get()->m_lights[_seedHash] = make_shared(_seedHash)); + UpgradableGuard l(get()->x_lights); + if (get()->m_lights.count(_seedHash)) + return get()->m_lights.at(_seedHash); + UpgradeGuard l2(l); + return (get()->m_lights[_seedHash] = make_shared(_seedHash)); } EthashAux::LightAllocation::LightAllocation(h256 const& _seedHash) diff --git a/libethcore/Exceptions.h b/libethcore/Exceptions.h index b411ea416..0ac9df5b2 100644 --- a/libethcore/Exceptions.h +++ b/libethcore/Exceptions.h @@ -51,6 +51,7 @@ DEV_SIMPLE_EXCEPTION(FeeTooSmall); DEV_SIMPLE_EXCEPTION(TooMuchGasUsed); DEV_SIMPLE_EXCEPTION(ExtraDataTooBig); DEV_SIMPLE_EXCEPTION(InvalidSignature); +DEV_SIMPLE_EXCEPTION(InvalidTransactionFormat); DEV_SIMPLE_EXCEPTION(InvalidBlockFormat); DEV_SIMPLE_EXCEPTION(InvalidUnclesHash); DEV_SIMPLE_EXCEPTION(TooManyUncles); diff --git a/libethcore/Miner.h b/libethcore/Miner.h index 11b9ae140..415da9878 100644 --- a/libethcore/Miner.h +++ b/libethcore/Miner.h @@ -107,9 +107,9 @@ public: } if (!!_work) { - DEV_TIMED_ABOVE(pause, 250) + DEV_TIMED_ABOVE("pause", 250) pause(); - DEV_TIMED_ABOVE(kickOff, 250) + DEV_TIMED_ABOVE("kickOff", 250) kickOff(); } else if (!_work && !!old) diff --git a/libethereum/BlockChain.cpp b/libethereum/BlockChain.cpp index 640fd2df4..7abf9316e 100644 --- a/libethereum/BlockChain.cpp +++ b/libethereum/BlockChain.cpp @@ -149,6 +149,7 @@ void BlockChain::open(std::string const& _path, WithExisting _we) ldb::Options o; o.create_if_missing = true; + o.max_open_files = 256; ldb::DB::Open(o, path + "/blocks", &m_blocksDB); ldb::DB::Open(o, path + "/details", &m_extrasDB); if (!m_blocksDB || !m_extrasDB) @@ -239,7 +240,7 @@ void BlockChain::rebuild(std::string const& _path, std::functionPut(m_writeOptions, toSlice(m_lastBlockHash, ExtraDetails), (ldb::Slice)dev::ref(m_details[m_lastBlockHash].rlp())); h256 lastHash = m_lastBlockHash; - boost::timer t; + Timer t; for (unsigned d = 1; d < originalNumber; ++d) { if (!(d % 1000)) @@ -323,7 +324,7 @@ tuple BlockChain::sync(BlockQueue& _bq, OverlayDB c { // Nonce & uncle nonces already verified in verification thread at this point. ImportRoute r; - DEV_TIMED_ABOVE(Block import, 500) + DEV_TIMED_ABOVE("Block import", 500) r = import(block.verified, _stateDB, ImportRequirements::Default & ~ImportRequirements::ValidNonce & ~ImportRequirements::CheckUncles); fresh += r.liveBlocks; dead += r.deadBlocks; @@ -397,6 +398,7 @@ ImportRoute BlockChain::import(bytes const& _block, OverlayDB const& _db, Import catch (Exception& ex) { // clog(BlockChainNote) << " Malformed block: " << diagnostic_information(ex); + ex << errinfo_phase(2); ex << errinfo_now(time(0)); ex << errinfo_block(_block); throw; @@ -411,13 +413,13 @@ ImportRoute BlockChain::import(VerifiedBlockRef const& _block, OverlayDB const& //@tidy This is a behemoth of a method - could do to be split into a few smaller ones. #if ETH_TIMED_IMPORTS - boost::timer total; + Timer total; double preliminaryChecks; double enactment; double collation; double writing; double checkBest; - boost::timer t; + Timer t; #endif // Check block doesn't already exist first! @@ -469,6 +471,9 @@ ImportRoute BlockChain::import(VerifiedBlockRef const& _block, OverlayDB const& h256 newLastBlockHash = currentHash(); unsigned newLastBlockNumber = number(); + BlockLogBlooms blb; + BlockReceipts br; + u256 td; #if ETH_CATCH try @@ -479,8 +484,6 @@ ImportRoute BlockChain::import(VerifiedBlockRef const& _block, OverlayDB const& State s(_db); auto tdIncrease = s.enactOn(_block, *this, _ir); - BlockLogBlooms blb; - BlockReceipts br; for (unsigned i = 0; i < s.pending().size(); ++i) { blb.blooms.push_back(s.receipt(i).bloom()); @@ -674,15 +677,17 @@ ImportRoute BlockChain::import(VerifiedBlockRef const& _block, OverlayDB const& #if ETH_TIMED_IMPORTS checkBest = t.elapsed(); - if (total.elapsed() > 1.0) + if (total.elapsed() > 0.5) { - cnote << "SLOW IMPORT:" << _block.info.hash(); + cnote << "SLOW IMPORT:" << _block.info.hash() << " #" << _block.info.number; cnote << " Import took:" << total.elapsed(); cnote << " preliminaryChecks:" << preliminaryChecks; cnote << " enactment:" << enactment; cnote << " collation:" << collation; cnote << " writing:" << writing; cnote << " checkBest:" << checkBest; + cnote << " " << _block.transactions.size() << " transactions"; + cnote << " " << _block.info.gasUsed << " gas used"; } #endif @@ -1080,6 +1085,7 @@ VerifiedBlockRef BlockChain::verifyBlock(bytes const& _block, function; using TransactionHashes = h256s; using UncleHashes = h256s; -struct ImportRoute -{ - h256s deadBlocks; - h256s liveBlocks; -}; - enum { ExtraDetails = 0, ExtraBlockHash, diff --git a/libethereum/Client.cpp b/libethereum/Client.cpp index d4cc23aaf..b45620e3c 100644 --- a/libethereum/Client.cpp +++ b/libethereum/Client.cpp @@ -87,6 +87,18 @@ void VersionChecker::setOk() } } +ImportResult Client::queueBlock(bytes const& _block, bool _isSafe) +{ + if (m_bq.status().verified + m_bq.status().verifying + m_bq.status().unverified > 30000) + this_thread::sleep_for(std::chrono::milliseconds(500)); + return m_bq.import(&_block, bc(), _isSafe); +} + +tuple Client::syncQueue(unsigned _max) +{ + return m_bc.sync(m_bq, m_stateDB, _max); +} + void Client::onBadBlock(Exception& _ex) const { // BAD BLOCK!!! @@ -159,7 +171,9 @@ void Client::onBadBlock(Exception& _ex) const DEV_HINT_ERRINFO(max); DEV_HINT_ERRINFO(name); DEV_HINT_ERRINFO(field); + DEV_HINT_ERRINFO(transaction); DEV_HINT_ERRINFO(data); + DEV_HINT_ERRINFO(phase); DEV_HINT_ERRINFO_HASH(nonce); DEV_HINT_ERRINFO(difficulty); DEV_HINT_ERRINFO(target); @@ -367,7 +381,7 @@ void Client::startedWorking() { // Synchronise the state according to the head of the block chain. // TODO: currently it contains keys for *all* blocks. Make it remove old ones. - cdebug << "startedWorking()"; + clog(ClientTrace) << "startedWorking()"; DEV_WRITE_GUARDED(x_preMine) m_preMine.sync(m_bc); @@ -563,7 +577,7 @@ ExecutionResult Client::call(Address _dest, bytes const& _data, u256 _gas, u256 try { State temp; -// cdebug << "Nonce at " << toAddress(_secret) << " pre:" << m_preMine.transactionsFrom(toAddress(_secret)) << " post:" << m_postMine.transactionsFrom(toAddress(_secret)); +// clog(ClientTrace) << "Nonce at " << toAddress(_secret) << " pre:" << m_preMine.transactionsFrom(toAddress(_secret)) << " post:" << m_postMine.transactionsFrom(toAddress(_secret)); DEV_READ_GUARDED(x_postMine) temp = m_postMine; temp.addBalance(_from, _value + _gasPrice * _gas); @@ -628,12 +642,12 @@ void Client::syncBlockQueue() cwork << "BQ ==> CHAIN ==> STATE"; ImportRoute ir; unsigned count; - boost::timer t; + Timer t; tie(ir, m_syncBlockQueue, count) = m_bc.sync(m_bq, m_stateDB, m_syncAmount); double elapsed = t.elapsed(); if (count) - cnote << count << "blocks imported in" << unsigned(elapsed * 1000) << "ms (" << (count / elapsed) << "blocks/s)"; + clog(ClientNote) << count << "blocks imported in" << unsigned(elapsed * 1000) << "ms (" << (count / elapsed) << "blocks/s)"; if (elapsed > c_targetDuration * 1.1 && count > c_syncMin) m_syncAmount = max(c_syncMin, count * 9 / 10); @@ -666,7 +680,6 @@ void Client::syncTransactionQueue() for (size_t i = 0; i < newPendingReceipts.size(); i++) appendFromNewPending(newPendingReceipts[i], changeds, m_postMine.pending()[i].sha3()); - // Tell farm about new transaction (i.e. restartProofOfWork mining). onPostStateChanged(); @@ -732,7 +745,7 @@ void Client::restartMining() if (preChanged || m_postMine.address() != m_preMine.address()) { if (isMining()) - cnote << "New block on chain."; + clog(ClientTrace) << "New block on chain."; DEV_WRITE_GUARDED(x_preMine) m_preMine = newPreMine; @@ -774,7 +787,7 @@ bool Client::remoteActive() const void Client::onPostStateChanged() { - cnote << "Post state changed."; + clog(ClientTrace) << "Post state changed."; rejigMining(); m_remoteWorking = false; } @@ -789,7 +802,7 @@ void Client::rejigMining() { if ((wouldMine() || remoteActive()) && !isMajorSyncing() && (!isChainBad() || mineOnBadChain()) /*&& (forceMining() || transactionsWaiting())*/) { - cnote << "Rejigging mining..."; + clog(ClientTrace) << "Rejigging mining..."; DEV_WRITE_GUARDED(x_working) m_working.commitToMine(m_bc); DEV_READ_GUARDED(x_working) @@ -886,7 +899,7 @@ void Client::checkWatchGarbage() if (m_watches[key].lastPoll != chrono::system_clock::time_point::max() && chrono::system_clock::now() - m_watches[key].lastPoll > chrono::seconds(20)) { toUninstall.push_back(key); - cnote << "GC: Uninstall" << key << "(" << chrono::duration_cast(chrono::system_clock::now() - m_watches[key].lastPoll).count() << "s old)"; + clog(ClientTrace) << "GC: Uninstall" << key << "(" << chrono::duration_cast(chrono::system_clock::now() - m_watches[key].lastPoll).count() << "s old)"; } for (auto i: toUninstall) uninstallWatch(i); diff --git a/libethereum/Client.h b/libethereum/Client.h index 596c382bd..aa118054f 100644 --- a/libethereum/Client.h +++ b/libethereum/Client.h @@ -138,6 +138,9 @@ public: /// Blocks until all pending transactions have been processed. virtual void flushTransactions() override; + /// Queues a block for import. + ImportResult queueBlock(bytes const& _block, bool _isSafe = false); + using Interface::call; // to remove warning about hiding virtual function /// Makes the given call. Nothing is recorded into the state. This cheats by creating a null address and endowing it with a lot of ETH. ExecutionResult call(Address _dest, bytes const& _data = bytes(), u256 _gas = 125000, u256 _value = 0, u256 _gasPrice = 1 * ether, Address const& _from = Address()); @@ -160,6 +163,11 @@ public: SyncStatus syncStatus() const; /// Get the block queue. BlockQueue const& blockQueue() const { return m_bq; } + /// Get the block queue. + OverlayDB const& stateDB() const { return m_stateDB; } + + /// Freeze worker thread and sync some of the block queue. + std::tuple syncQueue(unsigned _max = 1); // Mining stuff: diff --git a/libethereum/ClientBase.cpp b/libethereum/ClientBase.cpp index 5f10267a6..4f50234e6 100644 --- a/libethereum/ClientBase.cpp +++ b/libethereum/ClientBase.cpp @@ -56,6 +56,19 @@ void ClientBase::submitTransaction(Secret _secret, u256 _value, Address _dest, b cnote << "New transaction " << t; } +Address ClientBase::submitTransaction(Secret _secret, u256 _value, bytes const& _data, u256 _gas, u256 _gasPrice, u256 _nonce) +{ + prepareForTransaction(); + + Transaction t(_value, _gasPrice, _gas, _data, _nonce, _secret); + m_tq.import(t.rlp()); + + StructuredLogger::transactionReceived(t.sha3().abridged(), t.sender().abridged()); + cnote << "New transaction " << t; + + return right160(sha3(rlpList(t.sender(), t.nonce()))); +} + void ClientBase::submitTransaction(Secret _secret, u256 _value, Address _dest, bytes const& _data, u256 _gas, u256 _gasPrice) { auto a = toAddress(_secret); diff --git a/libethereum/ClientBase.h b/libethereum/ClientBase.h index e46bfe24d..aac10ae60 100644 --- a/libethereum/ClientBase.h +++ b/libethereum/ClientBase.h @@ -81,6 +81,7 @@ public: /// Submits a new contract-creation transaction. /// @returns the new contract's address (assuming it all goes through). + virtual Address submitTransaction(Secret _secret, u256 _endowment, bytes const& _init, u256 _gas, u256 _gasPrice, u256 _nonce); virtual Address submitTransaction(Secret _secret, u256 _endowment, bytes const& _init, u256 _gas = 10000, u256 _gasPrice = 10 * szabo) override; using Interface::submitTransaction; diff --git a/libethereum/EthereumHost.cpp b/libethereum/EthereumHost.cpp index afe12639a..9980f4339 100644 --- a/libethereum/EthereumHost.cpp +++ b/libethereum/EthereumHost.cpp @@ -166,20 +166,21 @@ tuple>, vector>, vector vector> allowed; vector> sessions; - auto const& ps = peerSessions(); - allowed.reserve(ps.size()); - for (auto const& j: ps) + size_t peerCount = 0; + foreachPeer([&](std::shared_ptr _p) { - auto pp = j.first->cap(); - if (_allow(pp.get())) + if (_allow(_p.get())) { - allowed.push_back(move(pp)); - sessions.push_back(move(j.first)); + allowed.push_back(_p); + sessions.push_back(_p->session()); } - } + ++peerCount; + return true; + }); - chosen.reserve((ps.size() * _percent + 99) / 100); - for (unsigned i = (ps.size() * _percent + 99) / 100; i-- && allowed.size();) + size_t chosenSize = (peerCount * _percent + 99) / 100; + chosen.reserve(chosenSize); + for (unsigned i = chosenSize; i && allowed.size(); i--) { unsigned n = rand() % allowed.size(); chosen.push_back(std::move(allowed[n])); diff --git a/libethereum/Executive.cpp b/libethereum/Executive.cpp index 9bf0cc74f..02e263ab1 100644 --- a/libethereum/Executive.cpp +++ b/libethereum/Executive.cpp @@ -316,7 +316,7 @@ bool Executive::go(OnOpFunc const& _onOp) if (m_ext) { #if ETH_TIMED_EXECUTIONS - boost::timer t; + Timer t; #endif try { diff --git a/libethereum/State.cpp b/libethereum/State.cpp index 37978bedb..ead72f496 100644 --- a/libethereum/State.cpp +++ b/libethereum/State.cpp @@ -139,7 +139,7 @@ PopulationStatistics State::populateFromChain(BlockChain const& _bc, h256 const& // 2. Enact the block's transactions onto this state. m_ourAddress = bi.coinbaseAddress; - boost::timer t; + Timer t; auto vb = BlockChain::verifyBlock(b); ret.verify = t.elapsed(); t.restart(); @@ -401,7 +401,7 @@ bool State::sync(BlockChain const& _bc, h256 _block, BlockInfo const& _bi, Impor u256 State::enactOn(VerifiedBlockRef const& _block, BlockChain const& _bc, ImportRequirements::value _ir) { #if ETH_TIMED_ENACTMENTS - boost::timer t; + Timer t; double populateVerify; double populateGrand; double syncReset; @@ -507,7 +507,7 @@ pair State::sync(BlockChain const& _bc, TransactionQu { if (i.second.gasPrice() >= _gp.ask(*this)) { - // boost::timer t; + // Timer t; if (lh.empty()) lh = _bc.lastHashes(); execute(lh, i.second); @@ -531,8 +531,20 @@ pair State::sync(BlockChain const& _bc, TransactionQu if (req > got) { // too old - cnote << i.first << "Dropping old transaction (nonce too low)"; - _tq.drop(i.first); + for (Transaction const& t: m_transactions) + if (t.from() == i.second.from()) + { + if (t.nonce() < i.second.nonce()) + { + cnote << i.first << "Dropping old transaction (nonce too low)"; + _tq.drop(i.first); + } + else if (t.nonce() == i.second.nonce() && t.gasPrice() <= i.second.gasPrice()) + { + cnote << i.first << "Dropping old transaction (gas price lower)"; + _tq.drop(i.first); + } + } } else if (got > req + _tq.waiting(i.second.sender())) { @@ -634,7 +646,7 @@ u256 State::enact(VerifiedBlockRef const& _block, BlockChain const& _bc, ImportR // cnote << m_state; LastHashes lh; - DEV_TIMED_ABOVE(lastHashes, 500) + DEV_TIMED_ABOVE("lastHashes", 500) lh = _bc.lastHashes((unsigned)m_previousBlock.number); RLP rlp(_block.block); @@ -643,7 +655,7 @@ u256 State::enact(VerifiedBlockRef const& _block, BlockChain const& _bc, ImportR // All ok with the block generally. Play back the transactions now... unsigned i = 0; - DEV_TIMED_ABOVE(txEcec, 500) + DEV_TIMED_ABOVE("txExec", 500) for (auto const& tr: _block.transactions) { try @@ -664,7 +676,7 @@ u256 State::enact(VerifiedBlockRef const& _block, BlockChain const& _bc, ImportR } h256 receiptsRoot; - DEV_TIMED_ABOVE(receiptsRoot, 500) + DEV_TIMED_ABOVE("receiptsRoot", 500) receiptsRoot = orderedTrieRoot(receipts); if (receiptsRoot != m_currentBlock.receiptsRoot) @@ -698,12 +710,12 @@ u256 State::enact(VerifiedBlockRef const& _block, BlockChain const& _bc, ImportR vector rewarded; h256Hash excluded; - DEV_TIMED_ABOVE(allKin, 500) + DEV_TIMED_ABOVE("allKin", 500) excluded = _bc.allKinFrom(m_currentBlock.parentHash, 6); excluded.insert(m_currentBlock.hash()); unsigned ii = 0; - DEV_TIMED_ABOVE(uncleCheck, 500) + DEV_TIMED_ABOVE("uncleCheck", 500) for (auto const& i: rlp[2]) { try @@ -752,11 +764,11 @@ u256 State::enact(VerifiedBlockRef const& _block, BlockChain const& _bc, ImportR } } - DEV_TIMED_ABOVE(applyRewards, 500) + DEV_TIMED_ABOVE("applyRewards", 500) applyRewards(rewarded); // Commit all cached state changes to the state trie. - DEV_TIMED_ABOVE(commit, 500) + DEV_TIMED_ABOVE("commit", 500) commit(); // Hash the state trie and check against the state_root hash in m_currentBlock. diff --git a/libethereum/State.h b/libethereum/State.h index 93a4f4ade..6ad8fed09 100644 --- a/libethereum/State.h +++ b/libethereum/State.h @@ -58,6 +58,8 @@ using errinfo_transactionIndex = boost::error_info; using errinfo_receipts = boost::error_info>; +using errinfo_transaction = boost::error_info; +using errinfo_phase = boost::error_info; using errinfo_required_LogBloom = boost::error_info; using errinfo_got_LogBloom = boost::error_info; using LogBloomRequirementError = boost::tuple; diff --git a/libethereum/Transaction.cpp b/libethereum/Transaction.cpp index 58ecc44fa..40a7914d3 100644 --- a/libethereum/Transaction.cpp +++ b/libethereum/Transaction.cpp @@ -77,6 +77,7 @@ std::ostream& dev::eth::operator<<(std::ostream& _out, TransactionException cons { case TransactionException::None: _out << "None"; break; case TransactionException::BadRLP: _out << "BadRLP"; break; + case TransactionException::InvalidFormat: _out << "InvalidFormat"; break; case TransactionException::OutOfGasIntrinsic: _out << "OutOfGasIntrinsic"; break; case TransactionException::InvalidSignature: _out << "InvalidSignature"; break; case TransactionException::InvalidNonce: _out << "InvalidNonce"; break; @@ -100,7 +101,7 @@ Transaction::Transaction(bytesConstRef _rlpData, CheckTransaction _checkSig) try { if (!rlp.isList()) - BOOST_THROW_EXCEPTION(BadRLP() << errinfo_comment("transaction RLP must be a list")); + BOOST_THROW_EXCEPTION(InvalidTransactionFormat() << errinfo_comment("transaction RLP must be a list")); m_nonce = rlp[field = 0].toInt(); m_gasPrice = rlp[field = 1].toInt(); @@ -110,7 +111,7 @@ Transaction::Transaction(bytesConstRef _rlpData, CheckTransaction _checkSig) m_value = rlp[field = 4].toInt(); if (!rlp[field = 5].isData()) - BOOST_THROW_EXCEPTION(BadRLP() << errinfo_comment("transaction data RLP must be an array")); + BOOST_THROW_EXCEPTION(InvalidTransactionFormat() << errinfo_comment("transaction data RLP must be an array")); m_data = rlp[field = 5].toBytes(); byte v = rlp[field = 6].toInt() - 27; @@ -118,7 +119,7 @@ Transaction::Transaction(bytesConstRef _rlpData, CheckTransaction _checkSig) h256 s = rlp[field = 8].toInt(); if (rlp.itemCount() > 9) - BOOST_THROW_EXCEPTION(BadRLP() << errinfo_comment("to many fields in the transaction RLP")); + BOOST_THROW_EXCEPTION(InvalidTransactionFormat() << errinfo_comment("to many fields in the transaction RLP")); m_vrs = SignatureStruct{ r, s, v }; if (_checkSig >= CheckTransaction::Cheap && !m_vrs.isValid()) diff --git a/libethereum/Transaction.h b/libethereum/Transaction.h index e9b1cbf80..4de9d7e92 100644 --- a/libethereum/Transaction.h +++ b/libethereum/Transaction.h @@ -50,6 +50,7 @@ enum class TransactionException None = 0, Unknown, BadRLP, + InvalidFormat, OutOfGasIntrinsic, ///< Too little gas to pay for the base transaction cost. InvalidSignature, InvalidNonce, diff --git a/libethereum/TransactionQueue.cpp b/libethereum/TransactionQueue.cpp index b4fa215ad..a86f6abf3 100644 --- a/libethereum/TransactionQueue.cpp +++ b/libethereum/TransactionQueue.cpp @@ -39,20 +39,22 @@ ImportResult TransactionQueue::import(bytesConstRef _transactionRLP, ImportCallb Transaction t; ImportResult ir; { - UpgradableGuard l(m_lock); + UpgradableGuard l(m_lock); - ir = check_WITH_LOCK(h, _ik); - if (ir != ImportResult::Success) - return ir; + ir = check_WITH_LOCK(h, _ik); + if (ir != ImportResult::Success) + return ir; - try { - t = Transaction(_transactionRLP, CheckTransaction::Everything); - UpgradeGuard ul(l); - ir = manageImport_WITH_LOCK(h, t, _cb); - } - catch (...) { - return ImportResult::Malformed; - } + try + { + t = Transaction(_transactionRLP, CheckTransaction::Everything); + UpgradeGuard ul(l); + ir = manageImport_WITH_LOCK(h, t, _cb); + } + catch (...) + { + return ImportResult::Malformed; + } } // cdebug << "import-END: Nonce of" << t.sender() << "now" << maxNonce(t.sender()); return ir; diff --git a/libevmasm/Assembly.cpp b/libevmasm/Assembly.cpp index 34ee05966..c96b6f40d 100644 --- a/libevmasm/Assembly.cpp +++ b/libevmasm/Assembly.cpp @@ -41,7 +41,7 @@ void Assembly::append(Assembly const& _a) if (i.type() == Tag || i.type() == PushTag) i.setData(i.data() + m_usedTags); else if (i.type() == PushSub || i.type() == PushSubSize) - i.setData(i.data() + m_usedTags); + i.setData(i.data() + m_subs.size()); append(i); } m_deposit = newDeposit; @@ -136,10 +136,10 @@ ostream& Assembly::streamAsm(ostream& _out, string const& _prefix, StringMap con _out << " PUSH [tag" << dec << i.data() << "]"; break; case PushSub: - _out << " PUSH [$" << h256(i.data()).abridged() << "]"; + _out << " PUSH [$" << h256(i.data()).abridgedMiddle() << "]"; break; case PushSubSize: - _out << " PUSH #[$" << h256(i.data()).abridged() << "]"; + _out << " PUSH #[$" << h256(i.data()).abridgedMiddle() << "]"; break; case PushProgramSize: _out << " PUSHSIZE"; diff --git a/libevmasm/AssemblyItem.cpp b/libevmasm/AssemblyItem.cpp index a0c5e19a6..e005ece18 100644 --- a/libevmasm/AssemblyItem.cpp +++ b/libevmasm/AssemblyItem.cpp @@ -110,10 +110,10 @@ ostream& dev::eth::operator<<(ostream& _out, AssemblyItem const& _item) _out << " PushData " << hex << (unsigned)_item.data(); break; case PushSub: - _out << " PushSub " << hex << h256(_item.data()).abridged(); + _out << " PushSub " << hex << h256(_item.data()).abridgedMiddle(); break; case PushSubSize: - _out << " PushSubSize " << hex << h256(_item.data()).abridged(); + _out << " PushSubSize " << hex << h256(_item.data()).abridgedMiddle(); break; case PushProgramSize: _out << " PushProgramSize"; diff --git a/libjsconsole/CMakeLists.txt b/libjsconsole/CMakeLists.txt index e8f98de88..761435fe1 100644 --- a/libjsconsole/CMakeLists.txt +++ b/libjsconsole/CMakeLists.txt @@ -14,6 +14,7 @@ include_directories(BEFORE ${V8_INCLUDE_DIRS}) include_directories(BEFORE ..) include_directories(${READLINE_INCLUDE_DIRS}) include_directories(${JSON_RPC_CPP_INCLUDE_DIRS}) +include_directories(${CURL_INCLUDE_DIRS}) set(EXECUTABLE jsconsole) @@ -24,7 +25,12 @@ add_library(${EXECUTABLE} ${SRC_LIST} ${HEADERS}) target_link_libraries(${EXECUTABLE} jsengine) target_link_libraries(${EXECUTABLE} devcore) target_link_libraries(${EXECUTABLE} ${READLINE_LIBRARIES}) -target_link_libraries(${EXECUTABLE} web3jsonrpc) +target_link_libraries(${EXECUTABLE} ${JSON_RPC_CPP_SERVER_LIBRARIES}) + +target_link_libraries(${EXECUTABLE} ${CURL_LIBRARIES}) +if (DEFINED WIN32 AND NOT DEFINED CMAKE_COMPILER_IS_MINGW) + eth_copy_dlls(${EXECUTABLE} CURL_DLLS) +endif() install( TARGETS ${EXECUTABLE} RUNTIME DESTINATION bin ARCHIVE DESTINATION lib LIBRARY DESTINATION lib ) install( FILES ${HEADERS} DESTINATION include/${EXECUTABLE} ) diff --git a/libjsconsole/CURLRequest.cpp b/libjsconsole/CURLRequest.cpp new file mode 100644 index 000000000..c07059372 --- /dev/null +++ b/libjsconsole/CURLRequest.cpp @@ -0,0 +1,66 @@ +/* + 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 CURLRequest.cpp + * @author Marek Kotewicz + * @date 2015 + * Ethereum client. + */ + +#include "CURLRequest.h" + +using namespace std; + +static size_t write_data(void *buffer, size_t elementSize, size_t numberOfElements, void *userp) +{ + static_cast(userp)->write((const char *)buffer, elementSize * numberOfElements); + return elementSize * numberOfElements; +} + +void CURLRequest::commonCURLPreparation() +{ + m_resultBuffer.str(""); + curl_easy_setopt(m_curl, CURLOPT_URL, (m_url + "?").c_str()); + curl_easy_setopt(m_curl, CURLOPT_FOLLOWLOCATION, 1L); + curl_easy_setopt(m_curl, CURLOPT_WRITEFUNCTION, write_data); + curl_easy_setopt(m_curl, CURLOPT_WRITEDATA, &m_resultBuffer); +} + +std::tuple CURLRequest::commonCURLPerform() +{ + CURLcode res = curl_easy_perform(m_curl); + if (res != CURLE_OK) { + throw runtime_error(curl_easy_strerror(res)); + } + long httpCode = 0; + curl_easy_getinfo(m_curl, CURLINFO_RESPONSE_CODE, &httpCode); + return make_tuple(httpCode, m_resultBuffer.str()); +} + +std::tuple CURLRequest::post() +{ + commonCURLPreparation(); + curl_easy_setopt(m_curl, CURLOPT_POSTFIELDS, m_body.c_str()); + + struct curl_slist *headerList = NULL; + headerList = curl_slist_append(headerList, "Content-Type: application/json"); + curl_easy_setopt(m_curl, CURLOPT_HTTPHEADER, headerList); + + auto result = commonCURLPerform(); + + curl_slist_free_all(headerList); + return result; +} diff --git a/libjsconsole/CURLRequest.h b/libjsconsole/CURLRequest.h new file mode 100644 index 000000000..e025d1eb9 --- /dev/null +++ b/libjsconsole/CURLRequest.h @@ -0,0 +1,58 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file CURLRequest.h + * @author Marek Kotewicz + * @date 2015 + * Ethereum client. + */ + +// based on http://stackoverflow.com/questions/1011339/how-do-you-make-a-http-request-with-c/27026683#27026683 + +#pragma once + +#include +#include +#include +#include + +class CURLRequest +{ +public: + CURLRequest(): m_curl(curl_easy_init()) {} + ~CURLRequest() + { + if (m_curl) + curl_easy_cleanup(m_curl); + } + + void setUrl(std::string _url) { m_url = _url; } + void setBody(std::string _body) { m_body = _body; } + + std::tuple post(); + +private: + std::string m_url; + std::string m_body; + + CURL* m_curl; + std::stringstream m_resultBuffer; + + void commonCURLPreparation(); + std::tuple commonCURLPerform(); +}; + + diff --git a/libjsconsole/JSConsole.cpp b/libjsconsole/JSConsole.cpp index 29d547242..61376de79 100644 --- a/libjsconsole/JSConsole.cpp +++ b/libjsconsole/JSConsole.cpp @@ -20,65 +20,5 @@ * Ethereum client. */ -#include -#include -#include -#include "JSConsole.h" -#include "JSV8Connector.h" - -// TODO! make readline optional! -#include -#include - -using namespace std; -using namespace dev; -using namespace dev::eth; - -JSConsole::JSConsole(WebThreeDirect& _web3, shared_ptr const& _accounts): - m_engine(), - m_printer(m_engine) -{ - m_jsonrpcConnector.reset(new JSV8Connector(m_engine)); - (void)_web3; (void)_accounts; -// m_jsonrpcServer.reset(new WebThreeStubServer(*m_jsonrpcConnector.get(), _web3, _accounts, vector())); -} - -void JSConsole::readExpression() const -{ - string cmd = ""; - g_logPost = [](std::string const& a, char const*) { cout << "\r \r" << a << endl << flush; rl_forced_update_display(); }; - bool isEmpty = true; - int openBrackets = 0; - do { - char* buff = readline(promptForIndentionLevel(openBrackets).c_str()); - isEmpty = !(buff && *buff); - if (!isEmpty) - { - cmd += string(buff); - cmd += " "; - free(buff); - int open = count(cmd.begin(), cmd.end(), '{'); - open += count(cmd.begin(), cmd.end(), '('); - int closed = count(cmd.begin(), cmd.end(), '}'); - closed += count(cmd.begin(), cmd.end(), ')'); - openBrackets = open - closed; - } - } while (openBrackets > 0); - - if (!isEmpty) - { - add_history(cmd.c_str()); - auto value = m_engine.eval(cmd.c_str()); - string result = m_printer.prettyPrint(value).cstr(); - cout << result << endl; - } -} - -std::string JSConsole::promptForIndentionLevel(int _i) const -{ - if (_i == 0) - return "> "; - - return string((_i + 1) * 2, ' '); -} +#include "JSConsole.h" diff --git a/libjsconsole/JSConsole.h b/libjsconsole/JSConsole.h index 2e5144a5d..50f6d6ae5 100644 --- a/libjsconsole/JSConsole.h +++ b/libjsconsole/JSConsole.h @@ -22,32 +22,66 @@ #pragma once -#include -#include - -namespace dev { class WebThreeStubServer; } -namespace jsonrpc { class AbstractServerConnector; } +#include +// TODO! make readline optional! +#include +#include namespace dev { namespace eth { -class AccountHolder; - +template class JSConsole { public: - JSConsole(WebThreeDirect& _web3, std::shared_ptr const& _accounts); - void readExpression() const; + JSConsole(): m_engine(Engine()), m_printer(Printer(m_engine)) {} + ~JSConsole() {} + + void readExpression() const + { + std::string cmd = ""; + g_logPost = [](std::string const& a, char const*) { std::cout << "\r \r" << a << std::endl << std::flush; rl_forced_update_display(); }; + + bool isEmpty = true; + int openBrackets = 0; + do { + char* buff = readline(promptForIndentionLevel(openBrackets).c_str()); + isEmpty = !(buff && *buff); + if (!isEmpty) + { + cmd += std::string(buff); + cmd += " "; + free(buff); + int open = std::count(cmd.begin(), cmd.end(), '{'); + open += std::count(cmd.begin(), cmd.end(), '('); + int closed = std::count(cmd.begin(), cmd.end(), '}'); + closed += std::count(cmd.begin(), cmd.end(), ')'); + openBrackets = open - closed; + } + } while (openBrackets > 0); + + if (!isEmpty) + { + add_history(cmd.c_str()); + auto value = m_engine.eval(cmd.c_str()); + std::string result = m_printer.prettyPrint(value).cstr(); + std::cout << result << std::endl; + } + } + +protected: + Engine m_engine; + Printer m_printer; -private: - std::string promptForIndentionLevel(int _i) const; + virtual std::string promptForIndentionLevel(int _i) const + { + if (_i == 0) + return "> "; - JSV8Engine m_engine; - JSV8Printer m_printer; - std::unique_ptr m_jsonrpcServer; - std::unique_ptr m_jsonrpcConnector; + return std::string((_i + 1) * 2, ' '); + } }; } diff --git a/libjsconsole/JSLocalConsole.cpp b/libjsconsole/JSLocalConsole.cpp new file mode 100644 index 000000000..04c6104a6 --- /dev/null +++ b/libjsconsole/JSLocalConsole.cpp @@ -0,0 +1,34 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file JSLocalConsole.cpp + * @author Marek Kotewicz + * @date 2015 + * Ethereum client. + */ + +#include +#include "JSLocalConsole.h" +#include "JSV8Connector.h" + +using namespace std; +using namespace dev; +using namespace dev::eth; + +JSLocalConsole::JSLocalConsole() +{ + m_jsonrpcConnector.reset(new JSV8Connector(m_engine)); +} diff --git a/libjsconsole/JSLocalConsole.h b/libjsconsole/JSLocalConsole.h new file mode 100644 index 000000000..48922faee --- /dev/null +++ b/libjsconsole/JSLocalConsole.h @@ -0,0 +1,50 @@ +/* + This file is part of cpp-ethereum. + + cpp-ethereum is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + cpp-ethereum is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with cpp-ethereum. If not, see . +*/ +/** @file JSLocalConsole.h + * @author Marek Kotewicz + * @date 2015 + * Ethereum client. + */ + +#pragma once + +#include +#include +#include "JSConsole.h" + +class WebThreeStubServer; +namespace jsonrpc { class AbstractServerConnector; } + +namespace dev +{ +namespace eth +{ + +class JSLocalConsole: public JSConsole +{ +public: + JSLocalConsole(); + virtual ~JSLocalConsole() {} + + jsonrpc::AbstractServerConnector* connector() { return m_jsonrpcConnector.get(); } + +private: + std::unique_ptr m_jsonrpcConnector; +}; + +} +} diff --git a/libjsconsole/JSRemoteConsole.cpp b/libjsconsole/JSRemoteConsole.cpp new file mode 100644 index 000000000..b42c5b340 --- /dev/null +++ b/libjsconsole/JSRemoteConsole.cpp @@ -0,0 +1,23 @@ +/* + 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 JSRemoteConsole.cpp + * @author Marek Kotewicz + * @date 2015 + * Ethereum client. + */ + +#include "JSRemoteConsole.h" diff --git a/libjsconsole/JSRemoteConsole.h b/libjsconsole/JSRemoteConsole.h new file mode 100644 index 000000000..2baf516f6 --- /dev/null +++ b/libjsconsole/JSRemoteConsole.h @@ -0,0 +1,48 @@ +/* + 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 JSRemoteConsole.h + * @author Marek Kotewicz + * @date 2015 + * Ethereum client. + */ + +#pragma once + +#include +#include +#include "JSV8RemoteConnector.h" +#include "JSConsole.h" + +namespace dev +{ +namespace eth +{ + +class JSRemoteConsole: public JSConsole +{ + +public: + JSRemoteConsole(std::string _url): m_connector(m_engine, _url) {} + virtual ~JSRemoteConsole() {} + +private: + JSV8RemoteConnector m_connector; + +}; + +} +} diff --git a/libjsconsole/JSV8Connector.h b/libjsconsole/JSV8Connector.h index 98cef4c2c..34c38fed1 100644 --- a/libjsconsole/JSV8Connector.h +++ b/libjsconsole/JSV8Connector.h @@ -43,7 +43,7 @@ public: bool SendResponse(std::string const& _response, void* _addInfo = nullptr); // implement JSV8RPC interface - void onSend(char const* payload); + void onSend(char const* _payload); }; } diff --git a/libjsconsole/JSV8RemoteConnector.cpp b/libjsconsole/JSV8RemoteConnector.cpp new file mode 100644 index 000000000..72e64faae --- /dev/null +++ b/libjsconsole/JSV8RemoteConnector.cpp @@ -0,0 +1,20 @@ +// +// Created by Marek Kotewicz on 15/06/15. +// + +#include "JSV8RemoteConnector.h" + +using namespace std; +using namespace dev; +using namespace dev::eth; + +void JSV8RemoteConnector::onSend(char const* _payload) +{ + m_request.setUrl(m_url); + m_request.setBody(_payload); + long code; + string response; + tie(code, response) = m_request.post(); + (void)code; + m_lastResponse = response.c_str(); +} diff --git a/libjsconsole/JSV8RemoteConnector.h b/libjsconsole/JSV8RemoteConnector.h new file mode 100644 index 000000000..5d28094ad --- /dev/null +++ b/libjsconsole/JSV8RemoteConnector.h @@ -0,0 +1,32 @@ +// +// Created by Marek Kotewicz on 15/06/15. +// + +#pragma once + +#include +#include +#include "CURLRequest.h" + +namespace dev +{ +namespace eth +{ + +class JSV8RemoteConnector : public JSV8RPC +{ + +public: + JSV8RemoteConnector(JSV8Engine const& _engine, std::string _url): JSV8RPC(_engine), m_url(_url) {} + virtual ~JSV8RemoteConnector() {} + + // implement JSV8RPC interface + void onSend(char const* _payload); + +private: + std::string m_url; + CURLRequest m_request; +}; + +} +} diff --git a/libjsengine/JSResources.cmake b/libjsengine/JSResources.cmake index d4370a8da..15e788778 100644 --- a/libjsengine/JSResources.cmake +++ b/libjsengine/JSResources.cmake @@ -1,8 +1,9 @@ set(web3 "${CMAKE_CURRENT_LIST_DIR}/../libjsqrc/ethereumjs/dist/web3.js") +set(admin "${CMAKE_CURRENT_LIST_DIR}/../libjsqrc/admin.js") set(pretty_print "${CMAKE_CURRENT_LIST_DIR}/PrettyPrint.js") set(common "${CMAKE_CURRENT_LIST_DIR}/Common.js") set(ETH_RESOURCE_NAME "JSEngineResources") set(ETH_RESOURCE_LOCATION "${CMAKE_CURRENT_BINARY_DIR}") -set(ETH_RESOURCES "web3" "pretty_print" "common") +set(ETH_RESOURCES "web3" "pretty_print" "common" "admin") diff --git a/libjsengine/JSV8Engine.cpp b/libjsengine/JSV8Engine.cpp index 4e06f0f65..ebf0a0e72 100644 --- a/libjsengine/JSV8Engine.cpp +++ b/libjsengine/JSV8Engine.cpp @@ -143,9 +143,11 @@ JSV8Engine::JSV8Engine(): m_scope(new JSV8Scope()) JSEngineResources resources; string common = resources.loadResourceAsString("common"); string web3 = resources.loadResourceAsString("web3"); + string admin = resources.loadResourceAsString("admin"); eval(common.c_str()); eval(web3.c_str()); eval("web3 = require('web3');"); + eval(admin.c_str()); } JSV8Engine::~JSV8Engine() diff --git a/libjsengine/JSV8Engine.h b/libjsengine/JSV8Engine.h index 56459c5d0..563642d73 100644 --- a/libjsengine/JSV8Engine.h +++ b/libjsengine/JSV8Engine.h @@ -22,7 +22,10 @@ #pragma once +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-parameter" #include +#pragma clang diagnostic pop #include "JSEngine.h" namespace dev diff --git a/libsolidity/ArrayUtils.cpp b/libsolidity/ArrayUtils.cpp index e138e9519..3be12af72 100644 --- a/libsolidity/ArrayUtils.cpp +++ b/libsolidity/ArrayUtils.cpp @@ -231,6 +231,181 @@ void ArrayUtils::copyArrayToStorage(ArrayType const& _targetType, ArrayType cons m_context << u256(0); } +void ArrayUtils::copyArrayToMemory(const ArrayType& _sourceType, bool _padToWordBoundaries) const +{ + solAssert( + _sourceType.getBaseType()->getCalldataEncodedSize() > 0, + "Nested arrays not yet implemented here." + ); + unsigned baseSize = 1; + if (!_sourceType.isByteArray()) + // We always pad the elements, regardless of _padToWordBoundaries. + baseSize = _sourceType.getBaseType()->getCalldataEncodedSize(); + + if (_sourceType.location() == DataLocation::CallData) + { + if (!_sourceType.isDynamicallySized()) + m_context << _sourceType.getLength(); + if (_sourceType.getBaseType()->getCalldataEncodedSize() > 1) + m_context << u256(baseSize) << eth::Instruction::MUL; + // stack: target source_offset source_len + m_context << eth::Instruction::DUP1 << eth::Instruction::DUP3 << eth::Instruction::DUP5; + // stack: target source_offset source_len source_len source_offset target + m_context << eth::Instruction::CALLDATACOPY; + m_context << eth::Instruction::DUP3 << eth::Instruction::ADD; + m_context << eth::Instruction::SWAP2 << eth::Instruction::POP << eth::Instruction::POP; + } + else if (_sourceType.location() == DataLocation::Memory) + { + // memcpy using the built-in contract + retrieveLength(_sourceType); + if (_sourceType.isDynamicallySized()) + { + // change pointer to data part + m_context << eth::Instruction::SWAP1 << u256(32) << eth::Instruction::ADD; + m_context << eth::Instruction::SWAP1; + } + // convert length to size + if (baseSize > 1) + m_context << u256(baseSize) << eth::Instruction::MUL; + // stack: + //@TODO do not use ::CALL if less than 32 bytes? + m_context << eth::Instruction::DUP1 << eth::Instruction::DUP4 << eth::Instruction::DUP4; + CompilerUtils(m_context).memoryCopy(); + + m_context << eth::Instruction::SWAP1 << eth::Instruction::POP; + // stack: + + bool paddingNeeded = false; + if (_sourceType.isDynamicallySized()) + paddingNeeded = _padToWordBoundaries && ((baseSize % 32) != 0); + else + paddingNeeded = _padToWordBoundaries && (((_sourceType.getLength() * baseSize) % 32) != 0); + if (paddingNeeded) + { + // stack: + m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2 << eth::Instruction::ADD; + // stack: + m_context << eth::Instruction::SWAP1 << u256(31) << eth::Instruction::AND; + // stack: + eth::AssemblyItem skip = m_context.newTag(); + if (_sourceType.isDynamicallySized()) + { + m_context << eth::Instruction::DUP1 << eth::Instruction::ISZERO; + m_context.appendConditionalJumpTo(skip); + } + // round off, load from there. + // stack + m_context << eth::Instruction::DUP1 << eth::Instruction::DUP3; + m_context << eth::Instruction::SUB; + // stack: target+size remainder + m_context << eth::Instruction::DUP1 << eth::Instruction::MLOAD; + // Now we AND it with ~(2**(8 * (32 - remainder)) - 1) + m_context << u256(1); + m_context << eth::Instruction::DUP4 << u256(32) << eth::Instruction::SUB; + // stack: ... 1 <32 - remainder> + m_context << u256(0x100) << eth::Instruction::EXP << eth::Instruction::SUB; + m_context << eth::Instruction::NOT << eth::Instruction::AND; + // stack: target+size remainder target+size-remainder + m_context << eth::Instruction::DUP2 << eth::Instruction::MSTORE; + // stack: target+size remainder target+size-remainder + m_context << u256(32) << eth::Instruction::ADD; + // stack: target+size remainder + m_context << eth::Instruction::SWAP2 << eth::Instruction::POP; + + if (_sourceType.isDynamicallySized()) + m_context << skip.tag(); + // stack + m_context << eth::Instruction::POP; + } + else + // stack: + m_context << eth::Instruction::ADD; + } + else + { + solAssert(_sourceType.location() == DataLocation::Storage, ""); + unsigned storageBytes = _sourceType.getBaseType()->getStorageBytes(); + u256 storageSize = _sourceType.getBaseType()->getStorageSize(); + solAssert(storageSize > 1 || (storageSize == 1 && storageBytes > 0), ""); + + m_context << eth::Instruction::POP; // remove offset, arrays always start new slot + retrieveLength(_sourceType); + // stack here: memory_offset storage_offset length + // 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 + if (baseSize > 1) + // convert length to memory size + m_context << u256(baseSize) << eth::Instruction::MUL; + m_context << eth::Instruction::DUP3 << eth::Instruction::ADD << eth::Instruction::SWAP2; + if (_sourceType.isDynamicallySized()) + { + // 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 + bool haveByteOffset = !_sourceType.isByteArray() && storageBytes <= 16; + if (haveByteOffset) + m_context << u256(0) << eth::Instruction::SWAP1; + // stack here: memory_end_offset storage_data_offset [storage_byte_offset] memory_offset + eth::AssemblyItem loopStart = m_context.newTag(); + m_context << loopStart; + // load and store + if (_sourceType.isByteArray()) + { + // Packed both in storage and memory. + m_context << eth::Instruction::DUP2 << eth::Instruction::SLOAD; + m_context << eth::Instruction::DUP2 << eth::Instruction::MSTORE; + // increment storage_data_offset by 1 + m_context << eth::Instruction::SWAP1 << u256(1) << eth::Instruction::ADD; + // increment memory offset by 32 + m_context << eth::Instruction::SWAP1 << u256(32) << eth::Instruction::ADD; + } + else + { + // stack here: memory_end_offset storage_data_offset [storage_byte_offset] memory_offset + if (haveByteOffset) + m_context << eth::Instruction::DUP3 << eth::Instruction::DUP3; + else + m_context << eth::Instruction::DUP2 << u256(0); + StorageItem(m_context, *_sourceType.getBaseType()).retrieveValue(SourceLocation(), true); + CompilerUtils(m_context).storeInMemoryDynamic(*_sourceType.getBaseType()); + // increment storage_data_offset and byte offset + if (haveByteOffset) + incrementByteOffset(storageBytes, 2, 3); + else + { + m_context << eth::Instruction::SWAP1; + m_context << storageSize << eth::Instruction::ADD; + m_context << eth::Instruction::SWAP1; + } + } + // check for loop condition + m_context << eth::Instruction::DUP1 << eth::dupInstruction(haveByteOffset ? 5 : 4) << eth::Instruction::GT; + m_context.appendConditionalJumpTo(loopStart); + // stack here: memory_end_offset storage_data_offset [storage_byte_offset] memory_offset + if (haveByteOffset) + m_context << eth::Instruction::SWAP1 << eth::Instruction::POP; + if (_padToWordBoundaries && baseSize % 32 != 0) + { + // memory_end_offset - start is the actual length (we want to compute the ceil of). + // memory_offset - start is its next multiple of 32, but it might be off by 32. + // so we compute: memory_end_offset += (memory_offset - memory_end_offest) & 31 + m_context << eth::Instruction::DUP3 << eth::Instruction::SWAP1 << eth::Instruction::SUB; + m_context << u256(31) << eth::Instruction::AND; + m_context << eth::Instruction::DUP3 << eth::Instruction::ADD; + m_context << eth::Instruction::SWAP2; + } + m_context << loopEnd << eth::Instruction::POP << eth::Instruction::POP; + } +} + void ArrayUtils::clearArray(ArrayType const& _type) const { unsigned stackHeightStart = m_context.getStackHeight(); @@ -499,6 +674,8 @@ void ArrayUtils::accessIndex(ArrayType const& _arrayType) const m_context << _arrayType.getBaseType()->getCalldataEncodedSize() << eth::Instruction::MUL; } m_context << eth::Instruction::ADD; + //@todo we should also load if it is a reference type of dynamic length + // but we should apply special logic if we load from calldata. if (_arrayType.getBaseType()->isValueType()) CompilerUtils(m_context).loadFromMemoryDynamic( *_arrayType.getBaseType(), diff --git a/libsolidity/ArrayUtils.h b/libsolidity/ArrayUtils.h index dab40e2d6..8d56f3c8f 100644 --- a/libsolidity/ArrayUtils.h +++ b/libsolidity/ArrayUtils.h @@ -44,6 +44,10 @@ public: /// Stack pre: source_reference [source_byte_offset/source_length] target_reference target_byte_offset /// Stack post: target_reference target_byte_offset void copyArrayToStorage(ArrayType const& _targetType, ArrayType const& _sourceType) const; + /// Copies an array (which cannot be dynamically nested) from anywhere to memory. + /// Stack pre: memory_offset source_item + /// Stack post: memory_offest + length(padded) + void copyArrayToMemory(ArrayType const& _sourceType, bool _padToWordBoundaries = true) const; /// Clears the given dynamic or static array. /// Stack pre: storage_ref storage_byte_offset /// Stack post: diff --git a/libsolidity/Compiler.cpp b/libsolidity/Compiler.cpp index f5570b98f..b05a7a9b1 100644 --- a/libsolidity/Compiler.cpp +++ b/libsolidity/Compiler.cpp @@ -392,9 +392,9 @@ bool Compiler::visit(FunctionDefinition const& _function) } for (ASTPointer const& variable: _function.getReturnParameters()) - m_context.addAndInitializeVariable(*variable); + appendStackVariableInitialisation(*variable); for (VariableDeclaration const* localVariable: _function.getLocalVariables()) - m_context.addAndInitializeVariable(*localVariable); + appendStackVariableInitialisation(*localVariable); if (_function.isConstructor()) if (auto c = m_context.getNextConstructor(dynamic_cast(*_function.getScope()))) @@ -639,7 +639,7 @@ void Compiler::appendModifierOrFunctionCode() modifier.getParameters()[i]->getType()); } for (VariableDeclaration const* localVariable: modifier.getLocalVariables()) - m_context.addAndInitializeVariable(*localVariable); + appendStackVariableInitialisation(*localVariable); unsigned const c_stackSurplus = CompilerUtils::getSizeOnStack(modifier.getParameters()) + CompilerUtils::getSizeOnStack(modifier.getLocalVariables()); @@ -653,6 +653,13 @@ void Compiler::appendModifierOrFunctionCode() } } +void Compiler::appendStackVariableInitialisation(VariableDeclaration const& _variable) +{ + CompilerContext::LocationSetter location(m_context, _variable); + m_context.addVariable(_variable); + ExpressionCompiler(m_context).appendStackVariableInitialisation(*_variable.getType()); +} + void Compiler::compileExpression(Expression const& _expression, TypePointer const& _targetType) { ExpressionCompiler expressionCompiler(m_context, m_optimize); diff --git a/libsolidity/Compiler.h b/libsolidity/Compiler.h index 60ca00e83..ac794f89e 100644 --- a/libsolidity/Compiler.h +++ b/libsolidity/Compiler.h @@ -84,6 +84,13 @@ private: void registerStateVariables(ContractDefinition const& _contract); void initializeStateVariables(ContractDefinition const& _contract); + /// Initialises all memory arrays in the local variables to point to an empty location. + void initialiseMemoryArrays(std::vector _variables); + /// Pushes the initialised value of the given type to the stack. If the type is a memory + /// reference type, allocates memory and pushes the memory pointer. + /// Not to be used for storage references. + void initialiseInMemory(Type const& _type); + virtual bool visit(VariableDeclaration const& _variableDeclaration) override; virtual bool visit(FunctionDefinition const& _function) override; virtual bool visit(IfStatement const& _ifStatement) override; @@ -100,6 +107,7 @@ private: /// body itself if the last modifier was reached. void appendModifierOrFunctionCode(); + void appendStackVariableInitialisation(VariableDeclaration const& _variable); void compileExpression(Expression const& _expression, TypePointer const& _targetType = TypePointer()); bool const m_optimize; diff --git a/libsolidity/CompilerContext.cpp b/libsolidity/CompilerContext.cpp index fde6adacc..0f6f5fe76 100644 --- a/libsolidity/CompilerContext.cpp +++ b/libsolidity/CompilerContext.cpp @@ -65,15 +65,6 @@ void CompilerContext::removeVariable(VariableDeclaration const& _declaration) m_localVariables.erase(&_declaration); } -void CompilerContext::addAndInitializeVariable(VariableDeclaration const& _declaration) -{ - LocationSetter locationSetter(*this, _declaration); - addVariable(_declaration); - int const size = _declaration.getType()->getSizeOnStack(); - for (int i = 0; i < size; ++i) - *this << u256(0); -} - bytes const& CompilerContext::getCompiledContract(const ContractDefinition& _contract) const { auto ret = m_compiledContracts.find(&_contract); diff --git a/libsolidity/CompilerContext.h b/libsolidity/CompilerContext.h index 998b0a2f7..3f97d900b 100644 --- a/libsolidity/CompilerContext.h +++ b/libsolidity/CompilerContext.h @@ -46,7 +46,6 @@ public: void addStateVariable(VariableDeclaration const& _declaration, u256 const& _storageOffset, unsigned _byteOffset); void addVariable(VariableDeclaration const& _declaration, unsigned _offsetToCurrent = 0); void removeVariable(VariableDeclaration const& _declaration); - void addAndInitializeVariable(VariableDeclaration const& _declaration); void setCompiledContracts(std::map const& _contracts) { m_compiledContracts = _contracts; } bytes const& getCompiledContract(ContractDefinition const& _contract) const; diff --git a/libsolidity/CompilerUtils.cpp b/libsolidity/CompilerUtils.cpp index 5bd6de13b..47a9a3542 100644 --- a/libsolidity/CompilerUtils.cpp +++ b/libsolidity/CompilerUtils.cpp @@ -25,6 +25,7 @@ #include #include #include +#include using namespace std; @@ -91,142 +92,20 @@ void CompilerUtils::loadFromMemoryDynamic( } } -unsigned CompilerUtils::storeInMemory(unsigned _offset, Type const& _type, bool _padToWordBoundaries) +void CompilerUtils::storeInMemory(unsigned _offset) { - solAssert(_type.getCategory() != Type::Category::Array, "Unable to statically store dynamic type."); - unsigned numBytes = prepareMemoryStore(_type, _padToWordBoundaries); + unsigned numBytes = prepareMemoryStore(IntegerType(256), true); 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::Array) - { - auto const& type = dynamic_cast(_type); - solAssert(type.isByteArray(), "Non byte arrays not yet implemented here."); - - if (type.location() == DataLocation::CallData) - { - if (!type.isDynamicallySized()) - m_context << type.getLength(); - // stack: target source_offset source_len - m_context << eth::Instruction::DUP1 << eth::Instruction::DUP3 << eth::Instruction::DUP5; - // stack: target source_offset source_len source_len source_offset target - m_context << eth::Instruction::CALLDATACOPY; - m_context << eth::Instruction::DUP3 << eth::Instruction::ADD; - m_context << eth::Instruction::SWAP2 << eth::Instruction::POP << eth::Instruction::POP; - } - else if (type.location() == DataLocation::Memory) - { - // memcpy using the built-in contract - ArrayUtils(m_context).retrieveLength(type); - if (type.isDynamicallySized()) - { - // change pointer to data part - m_context << eth::Instruction::SWAP1 << u256(32) << eth::Instruction::ADD; - m_context << eth::Instruction::SWAP1; - } - // stack: - // stack for call: outsize target size source value contract gas - m_context << eth::Instruction::DUP1 << eth::Instruction::DUP4; - m_context << eth::Instruction::DUP2 << eth::Instruction::DUP5; - m_context << u256(0) << u256(identityContractAddress); - //@TODO do not use ::CALL if less than 32 bytes? - //@todo in production, we should not have to pair c_callNewAccountGas. - m_context << u256(eth::c_callGas + 15 + eth::c_callNewAccountGas) << eth::Instruction::GAS; - m_context << eth::Instruction::SUB << eth::Instruction::CALL; - m_context << eth::Instruction::POP; // ignore return value - - m_context << eth::Instruction::SWAP1 << eth::Instruction::POP; - // stack: - - if (_padToWordBoundaries && (type.isDynamicallySized() || (type.getLength()) % 32 != 0)) - { - // stack: - m_context << eth::Instruction::SWAP1 << eth::Instruction::DUP2 << eth::Instruction::ADD; - // stack: - m_context << eth::Instruction::SWAP1 << u256(31) << eth::Instruction::AND; - // stack: - eth::AssemblyItem skip = m_context.newTag(); - if (type.isDynamicallySized()) - { - m_context << eth::Instruction::DUP1 << eth::Instruction::ISZERO; - m_context.appendConditionalJumpTo(skip); - } - // round off, load from there. - // stack - m_context << eth::Instruction::DUP1 << eth::Instruction::DUP3; - m_context << eth::Instruction::SUB; - // stack: target+length remainder - m_context << eth::Instruction::DUP1 << eth::Instruction::MLOAD; - // Now we AND it with ~(2**(8 * (32 - remainder)) - 1) - m_context << u256(1); - m_context << eth::Instruction::DUP4 << u256(32) << eth::Instruction::SUB; - // stack: ... 1 <32 - remainder> - m_context << u256(0x100) << eth::Instruction::EXP << eth::Instruction::SUB; - m_context << eth::Instruction::NOT << eth::Instruction::AND; - // stack: target+length remainder target+length-remainder - m_context << eth::Instruction::DUP2 << eth::Instruction::MSTORE; - // stack: target+length remainder target+length-remainder - m_context << u256(32) << eth::Instruction::ADD; - // stack: target+length remainder - m_context << eth::Instruction::SWAP2 << eth::Instruction::POP; - - if (type.isDynamicallySized()) - m_context << skip.tag(); - // stack - m_context << eth::Instruction::POP; - } - else - // stack: - m_context << eth::Instruction::ADD; - } - else - { - solAssert(type.location() == DataLocation::Storage, ""); - m_context << eth::Instruction::POP; // remove offset, arrays always start new slot - 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 - m_context << eth::Instruction::DUP2 << eth::Instruction::SLOAD; - m_context << eth::Instruction::DUP2 << eth::Instruction::MSTORE; - // increment storage_data_offset by 1 - m_context << eth::Instruction::SWAP1 << u256(1) << eth::Instruction::ADD; - // increment memory offset by 32 - m_context << eth::Instruction::SWAP1 << u256(32) << eth::Instruction::ADD; - // check for loop condition - m_context << eth::Instruction::DUP1 << eth::Instruction::DUP4 << eth::Instruction::GT; - m_context.appendConditionalJumpTo(loopStart); - // stack here: memory_end_offset storage_data_offset memory_offset - if (_padToWordBoundaries) - { - // memory_end_offset - start is the actual length (we want to compute the ceil of). - // memory_offset - start is its next multiple of 32, but it might be off by 32. - // so we compute: memory_end_offset += (memory_offset - memory_end_offest) & 31 - m_context << eth::Instruction::DUP3 << eth::Instruction::SWAP1 << eth::Instruction::SUB; - m_context << u256(31) << eth::Instruction::AND; - m_context << eth::Instruction::DUP3 << eth::Instruction::ADD; - m_context << eth::Instruction::SWAP2; - } - m_context << loopEnd << eth::Instruction::POP << eth::Instruction::POP; - } - } + ArrayUtils(m_context).copyArrayToMemory( + dynamic_cast(_type), + _padToWordBoundaries + ); else { unsigned numBytes = prepareMemoryStore(_type, _padToWordBoundaries); @@ -341,6 +220,21 @@ void CompilerUtils::encodeToMemory( popStackSlots(argSize + dynPointers + 1); } +void CompilerUtils::memoryCopy() +{ + // Stack here: size target source + // stack for call: outsize target size source value contract gas + //@TODO do not use ::CALL if less than 32 bytes? + m_context << eth::Instruction::DUP3 << eth::Instruction::SWAP1; + m_context << u256(0) << u256(identityContractAddress); + // compute gas costs + m_context << u256(32) << eth::Instruction::DUP5 << u256(31) << eth::Instruction::ADD; + m_context << eth::Instruction::DIV << u256(eth::c_identityWordGas) << eth::Instruction::MUL; + m_context << u256(eth::c_identityGas) << eth::Instruction::ADD; + m_context << eth::Instruction::CALL; + m_context << eth::Instruction::POP; // ignore return value +} + void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetType, bool _cleanupNeeded) { // For a type extension, we need to remove all higher-order bits that we might have ignored in @@ -515,7 +409,14 @@ void CompilerUtils::convertType(Type const& _typeOnStack, Type const& _targetTyp break; } default: - solAssert(false, "Invalid type conversion requested."); + solAssert( + false, + "Invalid type conversion " + + _typeOnStack.toString(false) + + " to " + + _targetType.toString(false) + + " requested." + ); } break; } @@ -599,11 +500,10 @@ unsigned CompilerUtils::getSizeOnStack(vector> const& _va return size; } -void CompilerUtils::computeHashStatic(Type const& _type, bool _padToWordBoundaries) +void CompilerUtils::computeHashStatic() { - unsigned length = storeInMemory(0, _type, _padToWordBoundaries); - solAssert(length <= CompilerUtils::freeMemoryPointer, ""); - m_context << u256(length) << u256(0) << eth::Instruction::SHA3; + storeInMemory(0); + m_context << u256(32) << u256(0) << eth::Instruction::SHA3; } unsigned CompilerUtils::loadFromMemoryHelper(Type const& _type, bool _fromCalldata, bool _padToWordBoundaries) diff --git a/libsolidity/CompilerUtils.h b/libsolidity/CompilerUtils.h index a880f9ee4..a9e07f74f 100644 --- a/libsolidity/CompilerUtils.h +++ b/libsolidity/CompilerUtils.h @@ -65,18 +65,14 @@ public: bool _padToWordBoundaries = true, bool _keepUpdatedMemoryOffset = true ); - /// Stores data from stack in memory. + /// Stores a 256 bit integer from stack in memory. /// @param _offset offset in memory /// @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, - Type const& _type = IntegerType(256), - bool _padToWordBoundaries = false - ); + void storeInMemory(unsigned _offset); /// Dynamic version of @see storeInMemory, expects the memory offset below the value on the stack /// and also updates that. For arrays, only copies the data part. + /// @param _padToWordBoundaries if true, adds zeros to pad to multiple of 32 bytes. Array elements + /// are always padded (except for byte arrays), regardless of this parameter. /// Stack pre: memory_offset value... /// Stack post: (memory_offset+length) void storeInMemoryDynamic(Type const& _type, bool _padToWordBoundaries = true); @@ -99,6 +95,11 @@ public: bool _copyDynamicDataInPlace = false ); + /// Uses a CALL to the identity contract to perform a memory-to-memory copy. + /// Stack pre: + /// Stack post: + void memoryCopy(); + /// Appends code for an implicit or explicit type conversion. This includes erasing higher /// order bits (@see appendHighBitCleanup) when widening integer but also copy to memory /// if a reference type is converted from calldata or storage to memory. @@ -124,10 +125,8 @@ 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); + /// Appends code that computes tha SHA3 hash of the topmost stack element of 32 byte type. + void computeHashStatic(); /// Bytes we need to the start of call data. /// - The size in bytes of the function (hash) identifier. diff --git a/libsolidity/ExpressionCompiler.cpp b/libsolidity/ExpressionCompiler.cpp index 7d6ed346a..fb10eb83b 100644 --- a/libsolidity/ExpressionCompiler.cpp +++ b/libsolidity/ExpressionCompiler.cpp @@ -56,6 +56,62 @@ void ExpressionCompiler::appendStateVariableInitialization(VariableDeclaration c StorageItem(m_context, _varDecl).storeValue(*_varDecl.getType(), _varDecl.getLocation(), true); } +void ExpressionCompiler::appendStackVariableInitialisation(Type const& _type, bool _toMemory) +{ + CompilerUtils utils(m_context); + auto const* referenceType = dynamic_cast(&_type); + if (!referenceType || referenceType->location() == DataLocation::Storage) + { + for (size_t i = 0; i < _type.getSizeOnStack(); ++i) + m_context << u256(0); + if (_toMemory) + utils.storeInMemoryDynamic(_type); + return; + } + solAssert(referenceType->location() == DataLocation::Memory, ""); + if (!_toMemory) + { + // allocate memory + utils.fetchFreeMemoryPointer(); + m_context << eth::Instruction::DUP1 << u256(max(32u, _type.getCalldataEncodedSize())); + m_context << eth::Instruction::ADD; + utils.storeFreeMemoryPointer(); + m_context << eth::Instruction::DUP1; + } + + if (auto structType = dynamic_cast(&_type)) + for (auto const& member: structType->getMembers()) + appendStackVariableInitialisation(*member.type, true); + else if (auto arrayType = dynamic_cast(&_type)) + { + if (arrayType->isDynamicallySized()) + { + // zero length + m_context << u256(0); + CompilerUtils(m_context).storeInMemoryDynamic(IntegerType(256)); + } + else if (arrayType->getLength() > 0) + { + m_context << arrayType->getLength() << eth::Instruction::SWAP1; + // stack: items_to_do memory_pos + auto repeat = m_context.newTag(); + m_context << repeat; + appendStackVariableInitialisation(*arrayType->getBaseType(), true); + m_context << eth::Instruction::SWAP1 << u256(1) << eth::Instruction::SWAP1; + m_context << eth::Instruction::SUB << eth::Instruction::SWAP1; + m_context << eth::Instruction::DUP2; + m_context.appendConditionalJumpTo(repeat); + m_context << eth::Instruction::SWAP1 << eth::Instruction::POP; + } + } + else + solAssert(false, "Requested initialisation for unknown type: " + _type.toString()); + + if (!_toMemory) + // remove the updated memory pointer + m_context << eth::Instruction::POP; +} + void ExpressionCompiler::appendStateVariableAccessor(VariableDeclaration const& _varDecl) { CompilerContext::LocationSetter locationSetter(m_context, _varDecl); diff --git a/libsolidity/ExpressionCompiler.h b/libsolidity/ExpressionCompiler.h index 642560c64..747e241ef 100644 --- a/libsolidity/ExpressionCompiler.h +++ b/libsolidity/ExpressionCompiler.h @@ -64,6 +64,13 @@ public: /// Appends code to set a state variable to its initial value/expression. void appendStateVariableInitialization(VariableDeclaration const& _varDecl); + /// Appends code to initialise a local variable. + /// If @a _toMemory is false, leaves the value on the stack. For memory references, this + /// allocates new memory. + /// If @a _toMemory is true, directly stores the data in the memory pos on the stack and + /// updates it. + void appendStackVariableInitialisation(Type const& _type, bool _toMemory = false); + /// Appends code for a State Variable accessor function void appendStateVariableAccessor(VariableDeclaration const& _varDecl); diff --git a/libsolidity/Types.cpp b/libsolidity/Types.cpp index 10a598266..01876b5a7 100644 --- a/libsolidity/Types.cpp +++ b/libsolidity/Types.cpp @@ -721,9 +721,13 @@ bool ArrayType::isImplicitlyConvertibleTo(const Type& _convertTo) const } else { - // Require that the base type is the same, not only convertible. - // This disallows assignment of nested arrays from storage to memory for now. - if (*getBaseType() != *convertTo.getBaseType()) + // Conversion to storage pointer or to memory, we de not copy element-for-element here, so + // require that the base type is the same, not only convertible. + // This disallows assignment of nested dynamic arrays from storage to memory for now. + if ( + *copyForLocationIfReference(location(), getBaseType()) != + *copyForLocationIfReference(location(), convertTo.getBaseType()) + ) return false; if (isDynamicallySized() != convertTo.isDynamicallySized()) return false; @@ -822,16 +826,16 @@ string ArrayType::toString(bool _short) const TypePointer ArrayType::externalType() const { if (m_arrayKind != ArrayKind::Ordinary) - return this->copyForLocation(DataLocation::CallData, true); + return this->copyForLocation(DataLocation::Memory, true); if (!m_baseType->externalType()) return TypePointer(); if (m_baseType->getCategory() == Category::Array && m_baseType->isDynamicallySized()) return TypePointer(); if (isDynamicallySized()) - return std::make_shared(DataLocation::CallData, m_baseType->externalType()); + return std::make_shared(DataLocation::Memory, m_baseType->externalType()); else - return std::make_shared(DataLocation::CallData, m_baseType->externalType(), m_length); + return std::make_shared(DataLocation::Memory, m_baseType->externalType(), m_length); } TypePointer ArrayType::copyForLocation(DataLocation _location, bool _isPointer) const @@ -970,6 +974,22 @@ bool StructType::operator==(Type const& _other) const return ReferenceType::operator==(other) && other.m_struct == m_struct; } +unsigned StructType::getCalldataEncodedSize(bool _padded) const +{ + unsigned size = 0; + for (auto const& member: getMembers()) + if (!member.type->canLiveOutsideStorage()) + return 0; + else + { + unsigned memberSize = member.type->getCalldataEncodedSize(_padded); + if (memberSize == 0) + return 0; + size += memberSize; + } + return size; +} + u256 StructType::getStorageSize() const { return max(1, getMembers().getStorageSize()); diff --git a/libsolidity/Types.h b/libsolidity/Types.h index 7f66b5b0c..9d412cd68 100644 --- a/libsolidity/Types.h +++ b/libsolidity/Types.h @@ -542,6 +542,7 @@ public: virtual bool isImplicitlyConvertibleTo(const Type& _convertTo) const override; virtual TypePointer unaryOperatorResult(Token::Value _operator) const override; virtual bool operator==(Type const& _other) const override; + virtual unsigned getCalldataEncodedSize(bool _padded) const override; virtual u256 getStorageSize() const override; virtual bool canLiveOutsideStorage() const override; virtual unsigned getSizeOnStack() const override { return 2; } diff --git a/libweb3jsonrpc/CMakeLists.txt b/libweb3jsonrpc/CMakeLists.txt index a28d51a06..c65efd39b 100644 --- a/libweb3jsonrpc/CMakeLists.txt +++ b/libweb3jsonrpc/CMakeLists.txt @@ -54,4 +54,3 @@ install( TARGETS ${EXECUTABLE} RUNTIME DESTINATION bin ARCHIVE DESTINATION lib L install( FILES ${HEADERS} DESTINATION include/${EXECUTABLE} ) add_custom_target(aux_json SOURCES "spec.json") - diff --git a/libwebthree/WebThree.cpp b/libwebthree/WebThree.cpp index d7fb5de7c..513581841 100644 --- a/libwebthree/WebThree.cpp +++ b/libwebthree/WebThree.cpp @@ -80,7 +80,7 @@ std::string WebThreeDirect::composeClientVersion(std::string const& _client, std #else char const* jit = ""; #endif - return _client + "v" + dev::Version + "-" + string(DEV_QUOTED(ETH_COMMIT_HASH)).substr(0, 8) + (ETH_CLEAN_REPO ? "" : "*") + "/" + _clientName + "/" DEV_QUOTED(ETH_BUILD_TYPE) "-" DEV_QUOTED(ETH_BUILD_PLATFORM) + jit; + return _client + "-" + "v" + dev::Version + "-" + string(DEV_QUOTED(ETH_COMMIT_HASH)).substr(0, 8) + (ETH_CLEAN_REPO ? "" : "*") + "/" + _clientName + "/" DEV_QUOTED(ETH_BUILD_TYPE) "-" DEV_QUOTED(ETH_BUILD_PLATFORM) + jit; } p2p::NetworkPreferences const& WebThreeDirect::networkPreferences() const diff --git a/mix/CodeModel.cpp b/mix/CodeModel.cpp index 5ff1da8eb..655be1c08 100644 --- a/mix/CodeModel.cpp +++ b/mix/CodeModel.cpp @@ -392,6 +392,7 @@ void CodeModel::gasEstimation(solidity::CompilerStack const& _cs) } eth::AssemblyItems const& runtimeAssembly = *_cs.getRuntimeAssemblyItems(n); + QString contractName = QString::fromStdString(contractDefinition.getName()); // Functional gas costs (per function, but also for accessors) for (auto it: contractDefinition.getInterfaceFunctions()) { @@ -399,13 +400,15 @@ void CodeModel::gasEstimation(solidity::CompilerStack const& _cs) continue; SourceLocation loc = it.second->getDeclaration().getLocation(); GasMeter::GasConsumption cost = GasEstimator::functionalEstimation(runtimeAssembly, it.second->externalSignature()); - m_gasCostsMaps->push(sourceName, loc.start, loc.end, gasToString(cost), cost.isInfinite, GasMap::type::Function); + m_gasCostsMaps->push(sourceName, loc.start, loc.end, gasToString(cost), cost.isInfinite, GasMap::type::Function, + contractName, QString::fromStdString(it.second->getDeclaration().getName())); } if (auto const* fallback = contractDefinition.getFallbackFunction()) { SourceLocation loc = fallback->getLocation(); GasMeter::GasConsumption cost = GasEstimator::functionalEstimation(runtimeAssembly, "INVALID"); - m_gasCostsMaps->push(sourceName, loc.start, loc.end, gasToString(cost), cost.isInfinite, GasMap::type::Function); + m_gasCostsMaps->push(sourceName, loc.start, loc.end, gasToString(cost), cost.isInfinite, GasMap::type::Function, + contractName, "fallback"); } for (auto const& it: contractDefinition.getDefinedFunctions()) { @@ -416,13 +419,15 @@ void CodeModel::gasEstimation(solidity::CompilerStack const& _cs) GasEstimator::GasConsumption cost = GasEstimator::GasConsumption::infinite(); if (entry > 0) cost = GasEstimator::functionalEstimation(runtimeAssembly, entry, *it); - m_gasCostsMaps->push(sourceName, loc.start, loc.end, gasToString(cost), cost.isInfinite, GasMap::type::Function); + m_gasCostsMaps->push(sourceName, loc.start, loc.end, gasToString(cost), cost.isInfinite, GasMap::type::Function, + contractName, QString::fromStdString(it->getName())); } if (auto const* constructor = contractDefinition.getConstructor()) { SourceLocation loc = constructor->getLocation(); GasMeter::GasConsumption cost = GasEstimator::functionalEstimation(*_cs.getAssemblyItems(n)); - m_gasCostsMaps->push(sourceName, loc.start, loc.end, gasToString(cost), cost.isInfinite, GasMap::type::Constructor); + m_gasCostsMaps->push(sourceName, loc.start, loc.end, gasToString(cost), cost.isInfinite, GasMap::type::Constructor, + contractName, contractName); } } } @@ -435,6 +440,14 @@ QVariantList CodeModel::gasCostByDocumentId(QString const& _documentId) const return QVariantList(); } +QVariantList CodeModel::gasCostBy(QString const& _contractName, QString const& _functionName) const +{ + if (m_gasCostsMaps) + return m_gasCostsMaps->gasCostsBy(_contractName, _functionName); + else + return QVariantList(); +} + void CodeModel::collectContracts(dev::solidity::CompilerStack const& _cs, std::vector const& _sourceNames) { Guard pl(x_pendingContracts); @@ -643,9 +656,9 @@ void CodeModel::setOptimizeCode(bool _value) emit scheduleCompilationJob(++m_backgroundJobId); } -void GasMapWrapper::push(QString _source, int _start, int _end, QString _value, bool _isInfinite, GasMap::type _type) +void GasMapWrapper::push(QString _source, int _start, int _end, QString _value, bool _isInfinite, GasMap::type _type, QString _contractName, QString _functionName) { - GasMap* gas = new GasMap(_start, _end, _value, _isInfinite, _type, this); + GasMap* gas = new GasMap(_start, _end, _value, _isInfinite, _type, _contractName, _functionName, this); m_gasMaps.find(_source).value().push_back(QVariant::fromValue(gas)); } @@ -668,3 +681,17 @@ QVariantList GasMapWrapper::gasCostsByDocId(QString _source) return QVariantList(); } +QVariantList GasMapWrapper::gasCostsBy(QString _contractName, QString _functionName) +{ + QVariantList gasMap; + for (auto const& map: m_gasMaps) + { + for (auto const& gas: map) + { + if (gas.value()->contractName() == _contractName && (_functionName.isEmpty() || gas.value()->functionName() == _functionName)) + gasMap.push_back(gas); + } + } + return gasMap; +} + diff --git a/mix/CodeModel.h b/mix/CodeModel.h index 7841c7142..e51eabf51 100644 --- a/mix/CodeModel.h +++ b/mix/CodeModel.h @@ -31,6 +31,7 @@ #include #include #include +#include #include #include "SolidityType.h" #include "QBigInt.h" @@ -140,6 +141,8 @@ class GasMap: public QObject Q_PROPERTY(QString gas MEMBER m_gas CONSTANT) Q_PROPERTY(bool isInfinite MEMBER m_isInfinite CONSTANT) Q_PROPERTY(QString codeBlockType READ codeBlockType CONSTANT) + Q_PROPERTY(QString contractName MEMBER m_contractName CONSTANT) + Q_PROPERTY(QString functionName MEMBER m_functionName CONSTANT) public: @@ -150,13 +153,19 @@ public: Constructor }; - GasMap(int _start, int _end, QString _gas, bool _isInfinite, type _type, QObject* _parent): QObject(_parent), m_start(_start), m_end(_end), m_gas(_gas), m_isInfinite(_isInfinite), m_type(_type) {} + GasMap(int _start, int _end, QString _gas, bool _isInfinite, type _type, QString _contractName, QString _functionName, QObject* _parent): QObject(_parent), + m_start(_start), m_end(_end), m_gas(_gas), m_isInfinite(_isInfinite), m_type(_type), m_contractName(_contractName), m_functionName(_functionName) {} + QString contractName() { return m_contractName; } + QString functionName() { return m_functionName; } +private: int m_start; int m_end; QString m_gas; bool m_isInfinite; type m_type; + QString m_contractName; + QString m_functionName; QString codeBlockType() const { @@ -178,10 +187,11 @@ class GasMapWrapper: public QObject public: GasMapWrapper(QObject* _parent = nullptr): QObject(_parent){} - void push(QString _source, int _start, int _end, QString _value, bool _isInfinite, GasMap::type _type); + void push(QString _source, int _start, int _end, QString _value, bool _isInfinite, GasMap::type _type, QString _contractName = "", QString _functionName = ""); bool contains(QString _key); void insert(QString _source, QVariantList _variantList); QVariantList gasCostsByDocId(QString _source); + QVariantList gasCostsBy(QString _contractName, QString _functionName = ""); private: GasCostsMaps m_gasMaps; @@ -200,6 +210,8 @@ public: Q_PROPERTY(bool compiling READ isCompiling NOTIFY stateChanged) Q_PROPERTY(bool hasContract READ hasContract NOTIFY codeChanged) Q_PROPERTY(bool optimizeCode MEMBER m_optimizeCode WRITE setOptimizeCode) + Q_PROPERTY(int callStipend READ callStipend) + Q_PROPERTY(int txGas READ txGas) /// @returns latest compilation results for contracts QVariantMap contracts() const; @@ -234,7 +246,10 @@ public: void gasEstimation(solidity::CompilerStack const& _cs); /// Gas cost by doc id Q_INVOKABLE QVariantList gasCostByDocumentId(QString const& _documentId) const; + Q_INVOKABLE QVariantList gasCostBy(QString const& _contractName, QString const& _functionName) const; Q_INVOKABLE void setOptimizeCode(bool _value); + int txGas() { return static_cast(dev::eth::c_txGas); } + int callStipend() { return static_cast(dev::eth::c_callStipend); } signals: /// Emited on compilation state change diff --git a/mix/MixClient.cpp b/mix/MixClient.cpp index befd1d056..0b1c7c44f 100644 --- a/mix/MixClient.cpp +++ b/mix/MixClient.cpp @@ -229,6 +229,7 @@ void MixClient::executeTransaction(Transaction const& _t, State& _state, bool _c case TransactionException::BadInstruction: case TransactionException::InvalidSignature: case TransactionException::InvalidNonce: + case TransactionException::InvalidFormat: case TransactionException::BadRLP: BOOST_THROW_EXCEPTION(Exception() << errinfo_comment("Internal execution error")); } diff --git a/mix/qml/Block.qml b/mix/qml/Block.qml index 31d3e924c..75d34a766 100644 --- a/mix/qml/Block.qml +++ b/mix/qml/Block.qml @@ -243,7 +243,6 @@ ColumnLayout } } - Rectangle { Layout.preferredWidth: toWidth @@ -266,8 +265,6 @@ ColumnLayout } } - - function userFrienldyToken(value) { if (value && value.indexOf("<") === 0) @@ -293,7 +290,7 @@ ColumnLayout color: labelColor font.bold: true font.pointSize: dbgStyle.absoluteSize(1) - width: parent.width -30 + width: parent.width - 30 text: { if (index >= 0 && transactions.get(index).returned) return transactions.get(index).returned @@ -402,18 +399,5 @@ ColumnLayout } } } - - Rectangle - { - id: right - Layout.preferredWidth: blockWidth - height: 10 - anchors.top: parent.bottom - anchors.topMargin: 5 - color: "#DEDCDC" - radius: 15 - anchors.left: parent.left - anchors.leftMargin: statusWidth - } } diff --git a/mix/qml/Ether.qml b/mix/qml/Ether.qml index 7a059e04d..29323642a 100644 --- a/mix/qml/Ether.qml +++ b/mix/qml/Ether.qml @@ -54,6 +54,7 @@ RowLayout { { id: units visible: displayUnitSelection; + implicitWidth: 145 onCurrentTextChanged: { if (value) diff --git a/mix/qml/QAddressView.qml b/mix/qml/QAddressView.qml index 4781d092b..56aefcf3e 100644 --- a/mix/qml/QAddressView.qml +++ b/mix/qml/QAddressView.qml @@ -2,14 +2,14 @@ import QtQuick 2.0 import QtQuick.Controls 1.3 import QtQuick.Controls.Styles 1.3 -Item +Row { property alias value: textinput.text property alias accountRef: ctrModel property string subType property bool readOnly property alias currentIndex: trCombobox.currentIndex - property alias currentText: textinput.text + property alias displayInput: textInputRect.visible property variant accounts signal indexChanged() id: editRoot @@ -22,7 +22,7 @@ Item } function currentValue() { - return currentText; + return value; } function currentType() @@ -38,7 +38,6 @@ Item function load() { accountRef.clear(); - accountRef.append({"itemid": " - "}); if (subType === "contract" || subType === "address") { var trCr = 0; @@ -52,7 +51,7 @@ Item if (i > transactionIndex) break; var tr = blockChainPanel.model.blocks[k].transactions[i] - if (tr.functionId === tr.contractId /*&& (dec[1] === tr.contractId || item.subType === "address")*/) + if (tr.functionId === tr.contractId) { accountRef.append({ "itemid": tr.contractId + " - " + trCr, "value": "<" + tr.contractId + " - " + trCr + ">", "type": "contract" }); trCr++; @@ -87,6 +86,7 @@ Item } trCombobox.currentIndex = 0; } + trCombobox.update() } function select(address) @@ -102,10 +102,10 @@ Item } Rectangle { - anchors.fill: parent radius: 4 anchors.verticalCenter: parent.verticalCenter height: 20 + id: textInputRect TextInput { id: textinput text: value @@ -141,12 +141,12 @@ Item property bool selected: false id: trCombobox model: ctrModel + width: 350 textRole: "itemid" - height: 20 anchors.verticalCenter: parent.verticalCenter - anchors.left: textinput.parent.right - anchors.leftMargin: 3 - onCurrentIndexChanged: { + + function update() + { trCombobox.selected = false; if (currentText === "") return; @@ -164,5 +164,9 @@ Item } indexChanged(); } + + onCurrentIndexChanged: { + update() + } } } diff --git a/mix/qml/QBoolTypeView.qml b/mix/qml/QBoolTypeView.qml index a95c12040..12c932762 100644 --- a/mix/qml/QBoolTypeView.qml +++ b/mix/qml/QBoolTypeView.qml @@ -21,16 +21,19 @@ Item value = value === "true" ? "1" : value value = value === "false" ? "0" : value; + var setValue = "1" if (value === "") - boolCombo.currentIndex = parseInt(defaultValue); + setValue = parseInt(defaultValue); else - boolCombo.currentIndex = parseInt(value); + setValue = parseInt(value); + boolCombo.checked = setValue === "1" ? true: false boolCombo.enabled = !readOnly; } Rectangle { + color: "transparent" anchors.fill: parent - ComboBox + CheckBox { property bool inited; Component.onCompleted: @@ -41,17 +44,13 @@ Item id: boolCombo anchors.fill: parent - onCurrentIndexChanged: + onCheckedChanged: { if (inited) - value = comboModel.get(currentIndex).value; - } - model: ListModel - { - id: comboModel - ListElement { text: qsTr("False"); value: "0" } - ListElement { text: qsTr("True"); value: "1" } + value = checked ? "1" : "0" + } + text: qsTr("True") } } } diff --git a/mix/qml/QHashTypeView.qml b/mix/qml/QHashTypeView.qml index ee5848b3a..6811dd43f 100644 --- a/mix/qml/QHashTypeView.qml +++ b/mix/qml/QHashTypeView.qml @@ -13,22 +13,15 @@ Item id: boldFont } - Rectangle { - anchors.fill: parent - radius: 4 - TextInput { - id: textinput - text: value + TextInput { + id: textinput + text: value + wrapMode: Text.WrapAnywhere + MouseArea { + id: mouseArea anchors.fill: parent - wrapMode: Text.WrapAnywhere - clip: true - font.family: boldFont.name - MouseArea { - id: mouseArea - anchors.fill: parent - hoverEnabled: true - onClicked: textinput.forceActiveFocus() - } + hoverEnabled: true + onClicked: textinput.forceActiveFocus() } } } diff --git a/mix/qml/QIntTypeView.qml b/mix/qml/QIntTypeView.qml index ec54d6409..6bee54a1f 100644 --- a/mix/qml/QIntTypeView.qml +++ b/mix/qml/QIntTypeView.qml @@ -1,35 +1,26 @@ import QtQuick 2.0 - +import QtQuick.Controls 1.1 Item { property alias value: textinput.text property alias readOnly: textinput.readOnly id: editRoot - width: readOnly ? textinput.implicitWidth : 150 - + width: 200 DebuggerPaneStyle { id: dbgStyle } - Rectangle { - anchors.fill: parent - radius: 4 - TextInput { - anchors.verticalCenter: parent.verticalCenter - id: textinput - font.family: dbgStyle.general.basicFont - clip: true - selectByMouse: true - text: value + TextField { + anchors.verticalCenter: parent.verticalCenter + id: textinput + selectByMouse: true + text: value + implicitWidth: 200 + MouseArea { + id: mouseArea anchors.fill: parent - font.pointSize: dbgStyle.general.basicFontSize - color: dbgStyle.general.basicColor - MouseArea { - id: mouseArea - anchors.fill: parent - hoverEnabled: true - onClicked: textinput.forceActiveFocus() - } + hoverEnabled: true + onClicked: textinput.forceActiveFocus() } } } diff --git a/mix/qml/QStringTypeView.qml b/mix/qml/QStringTypeView.qml index c42e65654..25831cb00 100644 --- a/mix/qml/QStringTypeView.qml +++ b/mix/qml/QStringTypeView.qml @@ -1,4 +1,5 @@ import QtQuick 2.0 +import QtQuick.Controls 1.1 Item { @@ -11,25 +12,16 @@ Item id: dbgStyle } - Rectangle { - anchors.fill: parent - radius: 4 - TextInput { - anchors.verticalCenter: parent.verticalCenter - id: textinput - font.family: dbgStyle.general.basicFont - clip: true - selectByMouse: true - text: value + TextField { + anchors.verticalCenter: parent.verticalCenter + id: textinput + selectByMouse: true + text: value + MouseArea { + id: mouseArea anchors.fill: parent - font.pointSize: dbgStyle.general.basicFontSize - color: dbgStyle.general.basicColor - MouseArea { - id: mouseArea - anchors.fill: parent - hoverEnabled: true - onClicked: textinput.forceActiveFocus() - } + hoverEnabled: true + onClicked: textinput.forceActiveFocus() } } } diff --git a/mix/qml/StructView.qml b/mix/qml/StructView.qml index 6b9edc755..bd2cbf598 100644 --- a/mix/qml/StructView.qml +++ b/mix/qml/StructView.qml @@ -13,58 +13,40 @@ Column property int transactionIndex property string context Layout.fillWidth: true - spacing: 0 - - DebuggerPaneStyle { - id: dbgStyle - } - + spacing: 5 Repeater { id: repeater visible: model.length > 0 - Layout.fillWidth: true - RowLayout { id: row - height: 20 + (members[index].type.category === QSolidityType.Struct ? (20 * members[index].type.members.length) : 0) + height: 30 + (members[index].type.category === QSolidityType.Struct ? (30 * members[index].type.members.length) : 0) Layout.fillWidth: true - DefaultLabel { - height: 20 - id: typeLabel - text: modelData.type.name - anchors.verticalCenter: parent.verticalCenter - font.family: dbgStyle.general.basicFont - color: dbgStyle.general.basicColor - font.pointSize: dbgStyle.general.basicFontSize - } - - DefaultLabel { - height: 20 - id: nameLabel - text: modelData.name - anchors.verticalCenter: parent.verticalCenter - font.family: dbgStyle.general.basicFont - color: dbgStyle.general.basicColor - font.pointSize: dbgStyle.general.basicFontSize - } + Rectangle + { + Layout.preferredWidth: 150 + Row + { + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + Label { + id: nameLabel + text: modelData.name + } - DefaultLabel { - height: 20 - id: equalLabel - text: "=" - anchors.verticalCenter: parent.verticalCenter - font.family: dbgStyle.general.basicFont - color: dbgStyle.general.basicColor - font.pointSize: dbgStyle.general.basicFontSize + Label { + id: typeLabel + text: " (" + modelData.type.name + ")" + font.italic: true + font.weight: Font.Light + } + } } Loader { id: typeLoader - height: 20 - anchors.verticalCenter: parent.verticalCenter sourceComponent: { var t = modelData.type.category; diff --git a/mix/qml/TransactionDialog.qml b/mix/qml/TransactionDialog.qml index 287ba08c2..f4aa9a5f0 100644 --- a/mix/qml/TransactionDialog.qml +++ b/mix/qml/TransactionDialog.qml @@ -7,12 +7,13 @@ import QtQuick.Controls.Styles 1.3 import org.ethereum.qml.QEther 1.0 import "js/TransactionHelper.js" as TransactionHelper import "js/InputValidator.js" as InputValidator +import "js/NetworkDeployment.js" as NetworkDeployment import "." Dialog { id: modalTransactionDialog modality: Qt.ApplicationModal - width: 570 + width: 580 height: 500 visible: false title: qsTr("Edit Transaction") @@ -22,7 +23,7 @@ Dialog { property alias gasAuto: gasAutoCheck.checked; property alias gasPrice: gasPriceField.value; property alias transactionValue: valueField.value; - property string contractId: contractComboBox.currentValue(); + property string contractId: contractCreationComboBox.currentValue(); property alias functionId: functionComboBox.currentText; property var paramValues; property var paramsModel: []; @@ -30,21 +31,16 @@ Dialog { property alias stateAccounts: senderComboBox.model property bool saveStatus signal accepted; - + property int rowWidth: 500 StateDialogStyle { id: transactionDialogStyle } function open(index, blockIdx, item) { - rowFunction.visible = !useTransactionDefaultValue; - rowValue.visible = !useTransactionDefaultValue; - rowGas.visible = !useTransactionDefaultValue; - rowGasPrice.visible = !useTransactionDefaultValue; - transactionIndex = index blockIndex = blockIdx - typeLoader.transactionIndex = index - typeLoader.blockIndex = blockIdx + paramScroll.transactionIndex = index + paramScroll.blockIndex = blockIdx saveStatus = item.saveStatus gasValueEdit.gasValue = item.gas; gasAutoCheck.checked = item.gasAuto ? true : false; @@ -52,48 +48,20 @@ Dialog { valueField.value = item.value; var contractId = item.contractId; var functionId = item.functionId; - rowFunction.visible = true; paramValues = item.parameters !== undefined ? item.parameters : {}; if (item.sender) senderComboBox.select(item.sender); - contractsModel.clear(); - var contractIndex = -1; - var contracts = codeModel.contracts; - for (var c in contracts) { - contractsModel.append({ cid: c, text: contracts[c].contract.name }); - if (contracts[c].contract.name === contractId) - contractIndex = contractsModel.count - 1; - } - if (contractIndex == -1 && contractsModel.count > 0) - contractIndex = 0; //@todo suggest unused contract - contractComboBox.currentIndex = contractIndex; + trTypeCreate.checked = item.isContractCreation + trTypeSend.checked = !item.isFunctionCall + trTypeExecute.checked = item.isFunctionCall && !item.isContractCreation - recipients.accounts = senderComboBox.model; - recipients.subType = "address"; - recipients.load(); - recipients.init(); - recipients.select(contractId); - - if (item.isContractCreation) - loadFunctions(contractComboBox.currentValue()); - else - loadFunctions(contractFromToken(recipients.currentValue())) - selectFunction(functionId); - - trType.checked = item.isContractCreation - trType.init(); - - paramsModel = []; - if (item.isContractCreation) - loadCtorParameters(); - else - loadParameters(); + load(item.isContractCreation, item.isFunctionCall, functionId, contractId) + estimatedGas.updateView() visible = true; - valueField.focus = true; } function loadCtorParameters(contractId) @@ -111,12 +79,12 @@ Dialog { function loadFunctions(contractId) { functionsModel.clear(); - functionsModel.append({ text: " - " }); var contract = codeModel.contracts[contractId]; if (contract) { var functions = codeModel.contracts[contractId].contract.functions; for (var f = 0; f < functions.length; f++) { - functionsModel.append({ text: functions[f].name }); + if (functions[f].name !== contractId) + functionsModel.append({ text: functions[f].name }); } } } @@ -156,9 +124,9 @@ Dialog { function loadParameters() { paramsModel = [] if (functionComboBox.currentIndex >= 0 && functionComboBox.currentIndex < functionsModel.count) { - var contract = codeModel.contracts[contractFromToken(recipients.currentValue())]; + var contract = codeModel.contracts[contractFromToken(contractCreationComboBox.currentValue())]; if (contract) { - var func = contract.contract.functions[functionComboBox.currentIndex - 1]; + var func = contract.contract.functions[functionComboBox.currentIndex + 1]; if (func) { var parameters = func.parameters; for (var p = 0; p < parameters.length; p++) @@ -171,13 +139,11 @@ Dialog { function initTypeLoader() { - typeLoader.value = {} - typeLoader.members = [] - typeLoader.value = paramValues; - typeLoader.members = paramsModel; - paramLabel.visible = paramsModel.length > 0; - paramScroll.visible = paramsModel.length > 0; - modalTransactionDialog.height = (paramsModel.length > 0 ? 500 : 300); + paramScroll.value = {} + paramScroll.members = [] + paramScroll.value = paramValues; + paramScroll.members = paramsModel; + paramScroll.updateView() } function acceptAndClose() @@ -213,16 +179,16 @@ Dialog { item.functionId = transactionDialog.functionId; } - item.isContractCreation = trType.checked; + item.isContractCreation = trTypeCreate.checked; if (item.isContractCreation) item.functionId = item.contractId; - item.isFunctionCall = item.functionId !== " - "; + item.isFunctionCall = trTypeExecute.checked if (!item.isContractCreation) { - item.contractId = recipients.currentText; - item.label = item.contractId + " " + item.functionId; - if (recipients.current().type === "address") + item.contractId = recipientsAccount.currentValue(); + item.label = contractFromToken(item.contractId) + "." + item.functionId + "()"; + if (recipientsAccount.current().type === "address") { item.functionId = ""; item.isFunctionCall = false; @@ -230,8 +196,9 @@ Dialog { } else { + item.isFunctionCall = true item.functionId = item.contractId; - item.label = qsTr("Deploy") + " " + item.contractId; + item.label = item.contractId + "." + item.contractId + "()"; } item.saveStatus = saveStatus item.sender = senderComboBox.model[senderComboBox.currentIndex].secret; @@ -246,187 +213,360 @@ Dialog { return token; } + function load(isContractCreation, isFunctionCall, functionId, contractId) + { + if (!isContractCreation) + { + contractCreationComboBox.visible = false + recipientsAccount.visible = true + recipientsAccount.accounts = senderComboBox.model; + amountLabel.text = qsTr("Amount") + if (!isFunctionCall) + recipientsAccount.subType = "address" + else + recipientsAccount.subType = "contract"; + recipientsAccount.load(); + recipientsAccount.init(); + if (contractId) + recipientsAccount.select(contractId); + if (functionId) + selectFunction(functionId); + if (isFunctionCall) + { + labelRecipient.text = qsTr("Recipient Contract") + functionRect.show() + loadFunctions(contractFromToken(recipientsAccount.currentValue())) + loadParameters(); + paramScroll.updateView() + } + else + { + paramsModel = [] + paramScroll.updateView() + labelRecipient.text = qsTr("Recipient Account") + functionRect.hide() + } + } + else + { + //contract creation + contractsModel.clear(); + var contractIndex = -1; + var contracts = codeModel.contracts; + for (var c in contracts) { + contractsModel.append({ cid: c, text: contracts[c].contract.name }); + if (contracts[c].contract.name === contractId) + contractIndex = contractsModel.count - 1; + } + + if (contractIndex == -1 && contractsModel.count > 0) + contractIndex = 0; //@todo suggest unused contract + contractCreationComboBox.currentIndex = contractIndex; + contractCreationComboBox.visible = true + labelRecipient.text = qsTr("Contract") + amountLabel.text = qsTr("Endownment") + functionRect.hide() + recipientsAccount.visible = false + loadCtorParameters(contractCreationComboBox.currentValue()); + paramScroll.updateView() + } + } + contentItem: Rectangle { + id: containerRect color: transactionDialogStyle.generic.backgroundColor - ColumnLayout { + anchors.fill: parent + ScrollView + { + anchors.top: parent.top anchors.fill: parent ColumnLayout { - anchors.fill: parent - anchors.margins: 10 - - ColumnLayout { - id: dialogContent - anchors.top: parent.top - spacing: 10 - RowLayout + Layout.preferredWidth: rowWidth + anchors.top: parent.top + anchors.topMargin: 10 + anchors.left: parent.left + width: 500 + anchors.leftMargin: + { + return (containerRect.width - 530) /2 + } + + RowLayout + { + Rectangle { - id: rowSender - Layout.fillWidth: true - height: 150 - DefaultLabel { - Layout.preferredWidth: 75 - text: qsTr("Sender") + Layout.preferredWidth: 150 + Label { + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + text: qsTr("Sender Account") } - ComboBox { + } - function select(secret) - { - for (var i in model) - if (model[i].secret === secret) - { - currentIndex = i; - break; - } - } + ComboBox { + function select(secret) + { + for (var i in model) + if (model[i].secret === secret) + { + currentIndex = i; + break; + } + } + Layout.preferredWidth: 350 + id: senderComboBox + currentIndex: 0 + textRole: "name" + editable: false + } + } - id: senderComboBox - Layout.preferredWidth: 350 - currentIndex: 0 - textRole: "name" - editable: false + RowLayout + { + Rectangle + { + Layout.preferredWidth: 150 + Layout.preferredHeight: 80 + color: "transparent" + Label + { + anchors.verticalCenter: parent.verticalCenter + anchors.top: parent.top + anchors.right: parent.right + text: qsTr("Type of Transaction") } } - RowLayout + Column { - id: rowIsContract - Layout.fillWidth: true - height: 150 - CheckBox { - id: trType - onCheckedChanged: - { - init(); + Layout.preferredWidth: 350 + Layout.preferredHeight: 90 + ExclusiveGroup { + id: rbbuttonList + onCurrentChanged: { + if (current) + { + if (current.objectName === "trTypeSend") + { + recipientsAccount.visible = true + contractCreationComboBox.visible = false + modalTransactionDialog.load(false, false) + } + else if (current.objectName === "trTypeCreate") + { + contractCreationComboBox.visible = true + recipientsAccount.visible = false + modalTransactionDialog.load(true, true) + } + else if (current.objectName === "trTypeExecute") + { + recipientsAccount.visible = true + contractCreationComboBox.visible = false + modalTransactionDialog.load(false, true) + } + } } + } - function init() - { - rowFunction.visible = !checked; - rowContract.visible = checked; - rowRecipient.visible = !checked; - paramLabel.visible = checked; - paramScroll.visible = checked; - functionComboBox.enabled = !checked; - if (checked) - loadCtorParameters(contractComboBox.currentValue()); - } + RadioButton { + id: trTypeSend + objectName: "trTypeSend" + exclusiveGroup: rbbuttonList + height: 30 + text: qsTr("Send ether to account") - text: qsTr("is contract creation") - checked: true + } + + RadioButton { + id: trTypeCreate + objectName: "trTypeCreate" + exclusiveGroup: rbbuttonList + height: 30 + text: qsTr("Create Contract") + } + + RadioButton { + id: trTypeExecute + objectName: "trTypeExecute" + exclusiveGroup: rbbuttonList + height: 30 + text: qsTr("Execute Contract") } } + } - RowLayout + RowLayout + { + Rectangle { - id: rowRecipient - Layout.fillWidth: true - height: 150 - DefaultLabel { - Layout.preferredWidth: 75 - text: qsTr("Recipient") + Layout.preferredWidth: 150 + Label { + id: labelRecipient + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + text: qsTr("Recipient Account") } + } - QAddressView + QAddressView + { + id: recipientsAccount + displayInput: false + onIndexChanged: { - id: recipients - onIndexChanged: - { - rowFunction.visible = current().type === "contract"; - paramLabel.visible = current().type === "contract"; - paramScroll.visible = current().type === "contract"; - if (!rowIsContract.checked) - loadFunctions(contractFromToken(recipients.currentValue())) - } + if (rbbuttonList.current.objectName === "trTypeExecute") + loadFunctions(contractFromToken(currentValue())) } } - RowLayout - { - id: rowContract - Layout.fillWidth: true - height: 150 - DefaultLabel { - Layout.preferredWidth: 75 - text: qsTr("Contract") + ComboBox { + id: contractCreationComboBox + function currentValue() { + return (currentIndex >=0 && currentIndex < contractsModel.count) ? contractsModel.get(currentIndex).cid : ""; } - ComboBox { - id: contractComboBox - function currentValue() { - return (currentIndex >=0 && currentIndex < contractsModel.count) ? contractsModel.get(currentIndex).cid : ""; - } - Layout.preferredWidth: 350 - currentIndex: -1 - textRole: "text" - editable: false - model: ListModel { - id: contractsModel - } - onCurrentIndexChanged: { - loadCtorParameters(currentValue()); - } + Layout.preferredWidth: 350 + currentIndex: -1 + textRole: "text" + editable: false + model: ListModel { + id: contractsModel + } + onCurrentIndexChanged: { + loadCtorParameters(currentValue()); } } + } - RowLayout + RowLayout + { + Rectangle { - id: rowFunction - Layout.fillWidth: true - height: 150 - DefaultLabel { - Layout.preferredWidth: 75 + Layout.preferredWidth: 150 + id: functionRect + + function hide() + { + parent.visible = false + functionRect.visible = false + functionComboBox.visible = false + } + + function show() + { + parent.visible = true + functionRect.visible = true + functionComboBox.visible = true + } + + Label { + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right text: qsTr("Function") } - ComboBox { - id: functionComboBox - Layout.preferredWidth: 350 - currentIndex: -1 - textRole: "text" - editable: false - model: ListModel { - id: functionsModel - } - onCurrentIndexChanged: { - loadParameters(); - } + } + + ComboBox { + id: functionComboBox + Layout.preferredWidth: 350 + currentIndex: -1 + textRole: "text" + editable: false + model: ListModel { + id: functionsModel + } + onCurrentIndexChanged: { + loadParameters(); } } + } - CommonSeparator + StructView + { + id: paramScroll + members: paramsModel; + accounts: senderComboBox.model + context: "parameter" + Layout.fillWidth: true + function updateView() { - Layout.fillWidth: true + paramScroll.visible = paramsModel.length > 0 + paramScroll.Layout.preferredHeight = paramsModel.length < 6 ? paramsModel.length * 30 : 205 + if (paramsModel.length === 0) + { + paramScroll.height = 0 + } } + } - RowLayout + RowLayout + { + Rectangle { - id: rowValue - Layout.fillWidth: true - height: 150 - DefaultLabel { - Layout.preferredWidth: 75 - text: qsTr("Value") - } - Ether { - id: valueField - edit: true - displayFormattedValue: true + Layout.preferredWidth: 150 + Label { + id: amountLabel + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right + text: qsTr("Amount") } } - CommonSeparator + Ether { + Layout.preferredWidth: 350 + id: valueField + edit: true + displayFormattedValue: false + displayUnitSelection: true + } + } + + Rectangle + { + Layout.preferredHeight: 30 + Layout.fillWidth: true + color: "transparent" + Rectangle + { + color: "#cccccc" + height: 1 + width: parent.width + anchors.verticalCenter: parent.verticalCenter + } + } + + Rectangle + { + height: 20 + color: "transparent" + Layout.preferredWidth: 500 + Rectangle { - Layout.fillWidth: true + + anchors.horizontalCenter: parent.horizontalCenter + Label { + text: qsTr("Transaction fees") + anchors.horizontalCenter: parent.horizontalCenter + } } - RowLayout + } + + RowLayout + { + Layout.preferredHeight: 45 + Rectangle { - id: rowGas - Layout.fillWidth: true - height: 150 - DefaultLabel { - Layout.preferredWidth: 75 + Layout.preferredWidth: 150 + Label { + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right text: qsTr("Gas") } + } + Row + { + Layout.preferredWidth: 350 DefaultTextField { property variant gasValue @@ -435,6 +575,55 @@ Dialog { implicitWidth: 200 enabled: !gasAutoCheck.checked id: gasValueEdit; + + Label + { + id: estimatedGas + anchors.top: parent.bottom + text: "" + Connections + { + target: functionComboBox + onCurrentIndexChanged: + { + estimatedGas.displayGas(contractFromToken(recipientsAccount.currentValue()), functionComboBox.currentText) + } + } + + function displayGas(contractName, functionName) + { + var gasCost = codeModel.gasCostBy(contractName, functionName); + if (gasCost && gasCost.length > 0) + { + var gas = codeModel.txGas + codeModel.callStipend + parseInt(gasCost[0].gas) + estimatedGas.text = qsTr("Estimated cost: ") + gasCost[0].gas + " gas" + } + } + + function updateView() + { + if (rbbuttonList.current.objectName === "trTypeExecute") + estimatedGas.displayGas(contractFromToken(recipientsAccount.currentValue()), functionComboBox.currentText) + else if (rbbuttonList.current.objectName === "trTypeCreate") + { + var contractName = contractCreationComboBox.currentValue() + estimatedGas.displayGas(contractName, contractName) + } + else if (rbbuttonList.current.objectName === "trTypeSend") + { + var gas = codeModel.txGas + codeModel.callStipend + estimatedGas.text = qsTr("Estimated cost: ") + gas + " gas" + } + } + + Connections + { + target: rbbuttonList + onCurrentChanged: { + estimatedGas.updateView() + } + } + } } CheckBox @@ -444,101 +633,92 @@ Dialog { text: qsTr("Auto"); } } + } - CommonSeparator - { - Layout.fillWidth: true - } - - RowLayout + RowLayout + { + Layout.preferredWidth: 500 + Layout.preferredHeight: 45 + Rectangle { - id: rowGasPrice - Layout.fillWidth: true - height: 150 - DefaultLabel { - Layout.preferredWidth: 75 + Layout.preferredWidth: 150 + Label { + id: gasPriceLabel + anchors.verticalCenter: parent.verticalCenter + anchors.right: parent.right text: qsTr("Gas Price") - } - Ether { - id: gasPriceField - edit: true - displayFormattedValue: true - } - } - - CommonSeparator - { - Layout.fillWidth: true - } - DefaultLabel { - id: paramLabel - text: qsTr("Parameters:") - Layout.preferredWidth: 75 - } - - ScrollView - { - id: paramScroll - anchors.top: paramLabel.bottom - anchors.topMargin: 10 - Layout.fillWidth: true - Layout.fillHeight: true - StructView - { - id: typeLoader - Layout.preferredWidth: 150 - members: paramsModel; - accounts: senderComboBox.model - context: "parameter" + Label { + id: gasPriceMarket + anchors.top: gasPriceLabel.bottom + Component.onCompleted: + { + NetworkDeployment.gasPrice(function(result) + { + gasPriceMarket.text = qsTr("Current market: ") + " " + result + " Wei"; + }, function (){}); + } + } } } - CommonSeparator - { - Layout.fillWidth: true - visible: paramsModel.length > 0 + Ether { + Layout.preferredWidth: 350 + id: gasPriceField + edit: true + displayFormattedValue: false + displayUnitSelection: true } } - } - RowLayout - { - anchors.bottom: parent.bottom - anchors.right: parent.right; - Button { + RowLayout + { - text: qsTr("OK"); - onClicked: { - var invalid = InputValidator.validate(paramsModel, paramValues); - if (invalid.length === 0) - { - close(); - accepted(); + Layout.preferredWidth: 500 + Row + { + width: parent.width + anchors.right: parent.right + Button { + id: updateBtn + text: qsTr("Cancel"); + onClicked: close(); } - else - { - errorDialog.text = qsTr("Some parameters are invalid:\n"); - for (var k in invalid) - errorDialog.text += invalid[k].message + "\n"; - errorDialog.open(); + + Button { + text: qsTr("Update"); + onClicked: { + var invalid = InputValidator.validate(paramsModel, paramValues); + if (invalid.length === 0) + { + close(); + accepted(); + } + else + { + errorDialog.text = qsTr("Some parameters are invalid:\n"); + for (var k in invalid) + errorDialog.text += invalid[k].message + "\n"; + errorDialog.open(); + } + } } } - } - Button { - text: qsTr("Cancel"); - onClicked: close(); + MessageDialog { + id: errorDialog + standardButtons: StandardButton.Ok + icon: StandardIcon.Critical + } } - MessageDialog { - id: errorDialog - standardButtons: StandardButton.Ok - icon: StandardIcon.Critical + RowLayout + { + Layout.preferredHeight: 30 + anchors.bottom: parent.bottom } } } } } - diff --git a/mix/qml/js/NetworkDeployment.js b/mix/qml/js/NetworkDeployment.js index 8a833e144..94d83460f 100644 --- a/mix/qml/js/NetworkDeployment.js +++ b/mix/qml/js/NetworkDeployment.js @@ -201,7 +201,7 @@ function executeTrNextStep(trIndex, state, ctrAddresses, callBack) callBack(); } -function gasPrice(callBack) +function gasPrice(callBack, error) { var requests = [{ jsonrpc: "2.0", @@ -210,7 +210,9 @@ function gasPrice(callBack) id: jsonRpcRequestId }]; rpcCall(requests, function (httpCall, response){ - callBack(JSON.parse(response)[0].result); + callBack(JSON.parse(response)[0].result) + }, function(message){ + error(message) }); } diff --git a/mix/qml/js/TransactionHelper.js b/mix/qml/js/TransactionHelper.js index f8bad03ed..ea91a40d7 100644 --- a/mix/qml/js/TransactionHelper.js +++ b/mix/qml/js/TransactionHelper.js @@ -17,7 +17,7 @@ function defaultTransaction() }; } -function rpcCall(requests, callBack) +function rpcCall(requests, callBack, error) { var jsonRpcUrl = "http://localhost:8545"; var rpcRequest = JSON.stringify(requests); @@ -33,7 +33,7 @@ function rpcCall(requests, callBack) { var errorText = qsTr("Unable to initiate request to the live network. Please verify your ethereum node is up.") + qsTr(" Error status: ") + httpRequest.status; console.log(errorText); - deploymentError(errorText); + error(errorText); } else { diff --git a/test/libdevcrypto/trie.cpp b/test/libdevcrypto/trie.cpp index daa3cc181..720001f9c 100644 --- a/test/libdevcrypto/trie.cpp +++ b/test/libdevcrypto/trie.cpp @@ -585,7 +585,7 @@ template void perfTestTrie(char const* _name) d.init(); cnote << "TriePerf " << _name << p; std::vector keys(1000); - boost::timer t; + Timer t; size_t ki = 0; for (size_t i = 0; i < p; ++i) { diff --git a/test/libethcore/keymanager.cpp b/test/libethcore/keymanager.cpp index 69f4743b1..44ee9ae51 100644 --- a/test/libethcore/keymanager.cpp +++ b/test/libethcore/keymanager.cpp @@ -57,6 +57,8 @@ BOOST_AUTO_TEST_CASE(KeyManagerConstructor) BOOST_CHECK_EQUAL(km.keysFile(), km.defaultPath()); BOOST_CHECK_EQUAL(km.defaultPath(), getDataDir("ethereum") + "/keys.info"); BOOST_CHECK(km.store().keys() == SecretStore().keys()); + for (auto a: km.accounts()) + km.kill(a); } BOOST_AUTO_TEST_CASE(KeyManagerKeysFile) @@ -72,13 +74,15 @@ BOOST_AUTO_TEST_CASE(KeyManagerKeysFile) BOOST_CHECK(!km.exists()); BOOST_CHECK_THROW(km.create(password), FileError); km.setKeysFile(tmpDir.path() + "/notExistingDir/keysFile.json"); - BOOST_CHECK_THROW(km.create(password), FileError); - BOOST_CHECK(!km.exists()); + BOOST_CHECK_NO_THROW(km.create(password)); + BOOST_CHECK(km.exists()); km.setKeysFile(tmpDir.path() + "keysFile.json"); BOOST_CHECK_NO_THROW(km.create(password)); - km.save(password); BOOST_CHECK(km.load(password)); + + for (auto a: km.accounts()) + km.kill(a); } BOOST_AUTO_TEST_CASE(KeyManagerHints) @@ -96,6 +100,25 @@ BOOST_AUTO_TEST_CASE(KeyManagerHints) BOOST_CHECK(!km.haveHint(password + "2")); km.notePassword(password); BOOST_CHECK(km.haveHint(password)); + + for (auto a: km.accounts()) + km.kill(a); +} + +BOOST_AUTO_TEST_CASE(KeyManagerAccounts) +{ + KeyManager km; + string password = "hardPassword"; + + TransientDirectory tmpDir; + km.setKeysFile(tmpDir.path()+ "keysFile.json"); + + BOOST_CHECK_NO_THROW(km.create(password)); + BOOST_CHECK(km.accounts().empty()); + BOOST_CHECK(km.load(password)); + + for (auto a: km.accounts()) + km.kill(a); } BOOST_AUTO_TEST_SUITE_END() diff --git a/test/libsolidity/SolidityEndToEndTest.cpp b/test/libsolidity/SolidityEndToEndTest.cpp index d397dc595..75793abf7 100644 --- a/test/libsolidity/SolidityEndToEndTest.cpp +++ b/test/libsolidity/SolidityEndToEndTest.cpp @@ -4517,6 +4517,105 @@ BOOST_AUTO_TEST_CASE(bytes_in_constructors_packer) ); } +BOOST_AUTO_TEST_CASE(arrays_from_and_to_storage) +{ + char const* sourceCode = R"( + contract Test { + uint24[] public data; + function set(uint24[] _data) returns (uint) { + data = _data; + return data.length; + } + function get() returns (uint24[]) { + return data; + } + } + )"; + compileAndRun(sourceCode, 0, "Test"); + + vector data{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18}; + BOOST_REQUIRE( + callContractFunction("set(uint24[])", u256(0x20), u256(data.size()), data) == + encodeArgs(u256(data.size())) + ); + BOOST_CHECK(callContractFunction("data(uint256)", u256(7)) == encodeArgs(u256(8))); + BOOST_CHECK(callContractFunction("data(uint256)", u256(15)) == encodeArgs(u256(16))); + BOOST_CHECK(callContractFunction("data(uint256)", u256(18)) == encodeArgs()); + BOOST_CHECK(callContractFunction("get()") == encodeArgs(u256(0x20), u256(data.size()), data)); +} + +BOOST_AUTO_TEST_CASE(arrays_complex_from_and_to_storage) +{ + char const* sourceCode = R"( + contract Test { + uint24[3][] public data; + function set(uint24[3][] _data) returns (uint) { + data = _data; + return data.length; + } + function get() returns (uint24[3][]) { + return data; + } + } + )"; + compileAndRun(sourceCode, 0, "Test"); + + vector data{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18}; + BOOST_REQUIRE( + callContractFunction("set(uint24[3][])", u256(0x20), u256(data.size() / 3), data) == + encodeArgs(u256(data.size() / 3)) + ); + BOOST_CHECK(callContractFunction("data(uint256,uint256)", u256(2), u256(2)) == encodeArgs(u256(9))); + BOOST_CHECK(callContractFunction("data(uint256,uint256)", u256(5), u256(1)) == encodeArgs(u256(17))); + BOOST_CHECK(callContractFunction("data(uint256,uint256)", u256(6), u256(0)) == encodeArgs()); + BOOST_CHECK(callContractFunction("get()") == encodeArgs(u256(0x20), u256(data.size() / 3), data)); +} + +BOOST_AUTO_TEST_CASE(arrays_complex_memory_index_access) +{ + char const* sourceCode = R"( + contract Test { + function set(uint24[3][] _data, uint a, uint b) returns (uint l, uint e) { + l = _data.length; + e = _data[a][b]; + } + } + )"; + compileAndRun(sourceCode, 0, "Test"); + + vector data{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18}; + BOOST_REQUIRE(callContractFunction( + "set(uint24[3][],uint256,uint256)", + u256(0x60), + u256(3), + u256(2), + u256(data.size() / 3), + data + ) == encodeArgs(u256(data.size() / 3), u256(data[3 * 3 + 2]))); +} + +BOOST_AUTO_TEST_CASE(bytes_memory_index_access) +{ + char const* sourceCode = R"( + contract Test { + function set(bytes _data, uint i) returns (uint l, byte c) { + l = _data.length; + c = _data[i]; + } + } + )"; + compileAndRun(sourceCode, 0, "Test"); + + string data("abcdefgh"); + BOOST_REQUIRE(callContractFunction( + "set(bytes,uint256)", + u256(0x40), + u256(3), + u256(data.size()), + data + ) == encodeArgs(u256(data.size()), string("d"))); +} + BOOST_AUTO_TEST_CASE(storage_array_ref) { char const* sourceCode = R"( @@ -4570,6 +4669,28 @@ BOOST_AUTO_TEST_CASE(storage_array_ref) BOOST_CHECK(callContractFunction("find(uint256)", u256(400)) == encodeArgs(u256(-1))); } +BOOST_AUTO_TEST_CASE(memory_types_initialisation) +{ + char const* sourceCode = R"( + contract Test { + mapping(uint=>uint) data; + function stat() returns (uint[5]) + { + data[2] = 3; // make sure to use some memory + } + function dyn() returns (uint[]) { stat(); } + function nested() returns (uint[3][]) { stat(); } + function nestedStat() returns (uint[3][7]) { stat(); } + } + )"; + compileAndRun(sourceCode, 0, "Test"); + + BOOST_CHECK(callContractFunction("stat()") == encodeArgs(vector(5))); + BOOST_CHECK(callContractFunction("dyn()") == encodeArgs(u256(0x20), u256(0))); + BOOST_CHECK(callContractFunction("nested()") == encodeArgs(u256(0x20), u256(0))); + BOOST_CHECK(callContractFunction("nestedStat()") == encodeArgs(vector(3 * 7))); +} + BOOST_AUTO_TEST_SUITE_END() } diff --git a/test/libsolidity/solidityExecutionFramework.h b/test/libsolidity/solidityExecutionFramework.h index 0079d82b6..200940a43 100644 --- a/test/libsolidity/solidityExecutionFramework.h +++ b/test/libsolidity/solidityExecutionFramework.h @@ -127,6 +127,14 @@ public: return _padLeft ? padding + _value : _value + padding; } static bytes encode(std::string const& _value) { return encode(asBytes(_value), false); } + template + static bytes encode(std::vector<_T> const& _value) + { + bytes ret; + for (auto const& v: _value) + ret += encode(v); + return ret; + } template static bytes encodeArgs(FirstArg const& _firstArg, Args const&... _followingArgs)