diff --git a/.github/workflows/build-ton-wasm-emscripten.yml b/.github/workflows/build-ton-wasm-emscripten.yml index 92107ffdf..534d312fd 100644 --- a/.github/workflows/build-ton-wasm-emscripten.yml +++ b/.github/workflows/build-ton-wasm-emscripten.yml @@ -19,7 +19,7 @@ jobs: - name: Build TON WASM artifacts run: | - cd assembly/wasm + cp assembly/wasm/fift-func-wasm-build-ubuntu.sh . chmod +x fift-func-wasm-build-ubuntu.sh ./fift-func-wasm-build-ubuntu.sh -a diff --git a/assembly/cicd/jenkins/test-builds.groovy b/assembly/cicd/jenkins/test-builds.groovy index 47ce52e53..0b5ab7a38 100644 --- a/assembly/cicd/jenkins/test-builds.groovy +++ b/assembly/cicd/jenkins/test-builds.groovy @@ -219,7 +219,7 @@ pipeline { steps { timeout(time: 180, unit: 'MINUTES') { sh ''' - cd assembly/wasm + cp assembly/wasm/fift-func-wasm-build-ubuntu.sh . chmod +x fift-func-wasm-build-ubuntu.sh ./fift-func-wasm-build-ubuntu.sh -a ''' diff --git a/assembly/wasm/fift-func-wasm-build-ubuntu.sh b/assembly/wasm/fift-func-wasm-build-ubuntu.sh index 7687a63f5..e7a54d16f 100644 --- a/assembly/wasm/fift-func-wasm-build-ubuntu.sh +++ b/assembly/wasm/fift-func-wasm-build-ubuntu.sh @@ -1,5 +1,3 @@ -# The script builds funcfift compiler to WASM - # Execute these prerequisites first # sudo apt update # sudo apt install -y build-essential git make cmake ninja-build clang libgflags-dev zlib1g-dev libssl-dev \ @@ -11,10 +9,12 @@ # sudo ./llvm.sh 16 all with_artifacts=false +scratch_new=false -while getopts 'a' flag; do +while getopts 'af' flag; do case "${flag}" in a) with_artifacts=true ;; + f) scratch_new=true ;; *) break ;; esac @@ -24,112 +24,139 @@ export CC=$(which clang-16) export CXX=$(which clang++-16) export CCACHE_DISABLE=1 -cd ../.. -rm -rf openssl zlib emsdk secp256k1 libsodium build echo `pwd` +if [ "$scratch_new" = true ]; then + echo Compiling openssl zlib lz4 emsdk secp256k1 libsodium emsdk ton + rm -rf openssl zlib lz4 emsdk secp256k1 libsodium build +fi -git clone https://github.com/openssl/openssl.git -cd openssl -git checkout checkout openssl-3.1.4 -./config -make -j16 -OPENSSL_DIR=`pwd` -cd .. - -git clone https://github.com/madler/zlib.git -cd zlib -git checkout v1.3.1 -ZLIB_DIR=`pwd` -cd .. - -git clone https://github.com/lz4/lz4.git -cd lz4 -git checkout v1.9.4 -LZ4_DIR=`pwd` -cd .. - -git clone https://github.com/bitcoin-core/secp256k1.git -cd secp256k1 -git checkout v0.3.2 -./autogen.sh -SECP256K1_DIR=`pwd` -cd .. - -git clone https://github.com/jedisct1/libsodium -cd libsodium -git checkout 1.0.18-RELEASE -SODIUM_DIR=`pwd` -cd .. - -mkdir build -cd build -cmake -GNinja -DCMAKE_BUILD_TYPE=Release \ --DCMAKE_CXX_STANDARD=17 \ --DOPENSSL_FOUND=1 \ --DOPENSSL_ROOT_DIR=$OPENSSL_DIR \ --DOPENSSL_INCLUDE_DIR=$OPENSSL_DIR/include \ --DOPENSSL_CRYPTO_LIBRARY=$OPENSSL_DIR/libcrypto.so \ --DOPENSSL_SSL_LIBRARY=$OPENSSL_DIR/libssl.so \ --DTON_USE_ABSEIL=OFF .. - -test $? -eq 0 || { echo "Can't configure TON build"; exit 1; } - -ninja fift smc-envelope -test $? -eq 0 || { echo "Can't compile fift "; exit 1; } +if [ ! -d "openssl" ]; then + git clone https://github.com/openssl/openssl.git + cd openssl + git checkout openssl-3.1.4 + ./config + make -j16 + OPENSSL_DIR=`pwd` + cd .. +else + OPENSSL_DIR=`pwd`/openssl + echo Using compiled openssl at $OPENSSL_DIR +fi -rm -rf * +if [ ! -d "build" ]; then + mkdir build + cd build + cmake -GNinja -DCMAKE_BUILD_TYPE=Release \ + -DCMAKE_CXX_STANDARD=17 \ + -DOPENSSL_FOUND=1 \ + -DOPENSSL_INCLUDE_DIR=$OPENSSL_DIR/include \ + -DOPENSSL_CRYPTO_LIBRARY=$OPENSSL_DIR/libcrypto.so \ + -DTON_USE_ABSEIL=OFF .. + + test $? -eq 0 || { echo "Can't configure TON build"; exit 1; } + ninja fift smc-envelope + test $? -eq 0 || { echo "Can't compile fift "; exit 1; } + rm -rf * + cd .. +else + echo cleaning build... + rm -rf build/* +fi -cd .. +if [ ! -d "emsdk" ]; then + git clone https://github.com/emscripten-core/emsdk.git +echo + echo Using cloned emsdk +fi -git clone https://github.com/emscripten-core/emsdk.git cd emsdk ./emsdk install 3.1.19 ./emsdk activate 3.1.19 EMSDK_DIR=`pwd` -ls $EMSDK_DIR . $EMSDK_DIR/emsdk_env.sh export CC=$(which emcc) export CXX=$(which em++) export CCACHE_DISABLE=1 -cd ../openssl - -make clean -emconfigure ./Configure linux-generic32 no-shared no-dso no-engine no-unit-test -sed -i 's/CROSS_COMPILE=.*/CROSS_COMPILE=/g' Makefile -sed -i 's/-ldl//g' Makefile -sed -i 's/-O3/-Os/g' Makefile -emmake make depend -emmake make -j16 -test $? -eq 0 || { echo "Can't compile OpenSSL with emmake "; exit 1; } - -cd ../zlib - -emconfigure ./configure --static -emmake make -j16 -test $? -eq 0 || { echo "Can't compile zlib with emmake "; exit 1; } +cd .. -cd ../lz4 -emmake make -j16 -test $? -eq 0 || { echo "Can't compile lz4 with emmake "; exit 1; } +if [ ! -f "openssl/openssl_em" ]; then + cd openssl + make clean + emconfigure ./Configure linux-generic32 no-shared no-dso no-engine no-unit-test + sed -i 's/CROSS_COMPILE=.*/CROSS_COMPILE=/g' Makefile + sed -i 's/-ldl//g' Makefile + sed -i 's/-O3/-Os/g' Makefile + emmake make depend + emmake make -j16 + test $? -eq 0 || { echo "Can't compile OpenSSL with emmake "; exit 1; } + touch openssl_em + cd .. +else + echo Using compiled openssl with emscripten +fi -cd ../secp256k1 +if [ ! -d "zlib" ]; then + git clone https://github.com/madler/zlib.git + cd zlib + git checkout v1.3.1 + ZLIB_DIR=`pwd` + emconfigure ./configure --static + emmake make -j16 + test $? -eq 0 || { echo "Can't compile zlib with emmake "; exit 1; } + cd .. +else + ZLIB_DIR=`pwd`/zlib + echo Using compiled zlib with emscripten at $ZLIB_DIR +fi -emconfigure ./configure --enable-module-recovery -emmake make -j16 -test $? -eq 0 || { echo "Can't compile secp256k1 with emmake "; exit 1; } +if [ ! -d "lz4" ]; then + git clone https://github.com/lz4/lz4.git + cd lz4 + git checkout v1.9.4 + LZ4_DIR=`pwd` + emmake make -j16 + test $? -eq 0 || { echo "Can't compile lz4 with emmake "; exit 1; } + cd .. +else + LZ4_DIR=`pwd`/lz4 + echo Using compiled lz4 with emscripten at $LZ4_DIR +fi -cd ../libsodium +if [ ! -d "secp256k1" ]; then + git clone https://github.com/bitcoin-core/secp256k1.git + cd secp256k1 + git checkout v0.3.2 + ./autogen.sh + SECP256K1_DIR=`pwd` + emconfigure ./configure --enable-module-recovery + emmake make -j16 + test $? -eq 0 || { echo "Can't compile secp256k1 with emmake "; exit 1; } + cd .. +else + SECP256K1_DIR=`pwd`/secp256k1 + echo Using compiled secp256k1 with emscripten at $SECP256K1_DIR +fi -emconfigure ./configure --disable-ssp -emmake make -j16 -test $? -eq 0 || { echo "Can't compile libsodium with emmake "; exit 1; } +if [ ! -d "libsodium" ]; then + git clone https://github.com/jedisct1/libsodium + cd libsodium + git checkout 1.0.18-RELEASE + SODIUM_DIR=`pwd` + emconfigure ./configure --disable-ssp + emmake make -j16 + test $? -eq 0 || { echo "Can't compile libsodium with emmake "; exit 1; } + cd .. +else + SODIUM_DIR=`pwd`/libsodium + echo Using compiled libsodium with emscripten at $SODIUM_DIR +fi -cd ../build +cd build -emcmake cmake -DUSE_EMSCRIPTEN=ON -DCMAKE_BUILD_TYPE=Release \ +emcmake cmake -DUSE_EMSCRIPTEN=ON -DCMAKE_BUILD_TYPE=Release -DCMAKE_VERBOSE_MAKEFILE:BOOL=ON \ -DZLIB_FOUND=1 \ -DZLIB_LIBRARIES=$ZLIB_DIR/libz.a \ -DZLIB_INCLUDE_DIR=$ZLIB_DIR \ @@ -137,18 +164,15 @@ emcmake cmake -DUSE_EMSCRIPTEN=ON -DCMAKE_BUILD_TYPE=Release \ -DLZ4_LIBRARIES=$LZ4_DIR/lib/liblz4.a \ -DLZ4_INCLUDE_DIRS=$LZ4_DIR/lib \ -DOPENSSL_FOUND=1 \ --DOPENSSL_ROOT_DIR=$OPENSSL_DIR \ -DOPENSSL_INCLUDE_DIR=$OPENSSL_DIR/include \ -DOPENSSL_CRYPTO_LIBRARY=$OPENSSL_DIR/libcrypto.a \ --DOPENSSL_SSL_LIBRARY=$OPENSSL_DIR/libssl.a \ -DCMAKE_TOOLCHAIN_FILE=$EMSDK_DIR/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake \ -DCMAKE_CXX_FLAGS="-sUSE_ZLIB=1" \ --DSECP256K1_FOUND=1 \ -DSECP256K1_INCLUDE_DIR=$SECP256K1_DIR/include \ -DSECP256K1_LIBRARY=$SECP256K1_DIR/.libs/libsecp256k1.a \ -DSODIUM_INCLUDE_DIR=$SODIUM_DIR/src/libsodium/include \ -DSODIUM_LIBRARY_RELEASE=$SODIUM_DIR/src/libsodium/.libs/libsodium.a \ --DSODIUM_USE_STATIC_LIBS=ON .. +.. test $? -eq 0 || { echo "Can't configure TON with emmake "; exit 1; } cp -R ../crypto/smartcont ../crypto/fift/lib crypto diff --git a/crypto/CMakeLists.txt b/crypto/CMakeLists.txt index 306194408..e21f18cbe 100644 --- a/crypto/CMakeLists.txt +++ b/crypto/CMakeLists.txt @@ -358,7 +358,8 @@ target_link_libraries(test-ed25519-crypto PUBLIC ton_crypto) add_library(fift-lib STATIC ${FIFT_SOURCE}) target_include_directories(fift-lib PUBLIC $) -target_link_libraries(fift-lib PUBLIC ton_crypto ton_db tdutils ton_block) +target_link_libraries(fift-lib PUBLIC ton_crypto tdutils ton_block) + if (USE_EMSCRIPTEN) target_link_options(fift-lib PRIVATE -fexceptions) target_compile_options(fift-lib PRIVATE -fexceptions) diff --git a/crypto/block/transaction.cpp b/crypto/block/transaction.cpp index 379d899ed..dbf0199e7 100644 --- a/crypto/block/transaction.cpp +++ b/crypto/block/transaction.cpp @@ -1661,7 +1661,12 @@ bool Transaction::prepare_compute_phase(const ComputePhaseConfig& cfg) { std::unique_ptr logger; auto vm_log = vm::VmLog(); if (cfg.with_vm_log) { - size_t log_max_size = cfg.vm_log_verbosity > 0 ? 1024 * 1024 : 256; + size_t log_max_size = 256; + if (cfg.vm_log_verbosity > 4) { + log_max_size = 32 << 20; + } else if (cfg.vm_log_verbosity > 0) { + log_max_size = 1 << 20; + } logger = std::make_unique(log_max_size); vm_log.log_interface = logger.get(); vm_log.log_options = td::LogOptions(VERBOSITY_NAME(DEBUG), true, false); @@ -1673,6 +1678,7 @@ bool Transaction::prepare_compute_phase(const ComputePhaseConfig& cfg) { vm_log.log_mask |= vm::VmLog::DumpStack; if (cfg.vm_log_verbosity > 4) { vm_log.log_mask |= vm::VmLog::DumpStackVerbose; + vm_log.log_mask |= vm::VmLog::DumpC5; } } } diff --git a/crypto/common/bitstring.cpp b/crypto/common/bitstring.cpp index 5135cdf0d..52e57c9a8 100644 --- a/crypto/common/bitstring.cpp +++ b/crypto/common/bitstring.cpp @@ -640,11 +640,11 @@ long parse_bitstring_hex_literal(unsigned char* buff, std::size_t buff_size, con return bits; } -long parse_bitstring_binary_literal(BitPtr buff, std::size_t buff_size, const char* str, const char* str_end) { +long parse_bitstring_binary_literal(BitPtr buff, std::size_t buff_size_bits, const char* str, const char* str_end) { const char* ptr = str; - while (ptr < str_end && buff_size && (*ptr == '0' || *ptr == '1')) { + while (ptr < str_end && buff_size_bits && (*ptr == '0' || *ptr == '1')) { *buff++ = (bool)(*ptr++ & 1); - --buff_size; + --buff_size_bits; } return td::narrow_cast(ptr == str_end ? ptr - str : str - ptr - 1); } diff --git a/crypto/common/bitstring.h b/crypto/common/bitstring.h index dc3a2fa5b..257764788 100644 --- a/crypto/common/bitstring.h +++ b/crypto/common/bitstring.h @@ -58,7 +58,7 @@ unsigned long long bits_load_long_top(ConstBitPtr from, unsigned top_bits); long long bits_load_long(ConstBitPtr from, unsigned bits); unsigned long long bits_load_ulong(ConstBitPtr from, unsigned bits); long parse_bitstring_hex_literal(unsigned char* buff, std::size_t buff_size, const char* str, const char* str_end); -long parse_bitstring_binary_literal(BitPtr buff, std::size_t buff_size, const char* str, const char* str_end); +long parse_bitstring_binary_literal(BitPtr buff, std::size_t buff_size_bits, const char* str, const char* str_end); void bits_sha256(BitPtr to, ConstBitPtr from, std::size_t size); diff --git a/crypto/fift/Fift.cpp b/crypto/fift/Fift.cpp index 85511a38f..e0faca57c 100644 --- a/crypto/fift/Fift.cpp +++ b/crypto/fift/Fift.cpp @@ -49,7 +49,6 @@ td::Result Fift::interpret_istream(std::istream& stream, std::string curren } td::Result Fift::do_interpret(IntCtx& ctx, bool is_interactive) { - ctx.ton_db = &config_.ton_db; ctx.source_lookup = &config_.source_lookup; ctx.dictionary = ctx.main_dictionary = ctx.context = config_.dictionary; ctx.output_stream = config_.output_stream; diff --git a/crypto/fift/Fift.h b/crypto/fift/Fift.h index ebcf2ef4d..a17727993 100644 --- a/crypto/fift/Fift.h +++ b/crypto/fift/Fift.h @@ -19,7 +19,6 @@ #pragma once #include "SourceLookup.h" -#include "vm/db/TonDb.h" #include "Dictionary.h" #include "td/utils/Status.h" @@ -31,13 +30,11 @@ struct Fift { public: struct Config { fift::SourceLookup source_lookup; - vm::TonDb ton_db; fift::Dictionary dictionary; std::ostream* output_stream{&std::cout}; std::ostream* error_stream{&std::cerr}; bool show_backtrace{true}; }; - // Fift must own ton_db and dictionary, no concurrent access is allowed explicit Fift(Config config); td::Result interpret_file(std::string fname, std::string current_dir, bool interactive = false); diff --git a/crypto/fift/fift-main.cpp b/crypto/fift/fift-main.cpp index fd424e8cf..cdc36fc07 100644 --- a/crypto/fift/fift-main.cpp +++ b/crypto/fift/fift-main.cpp @@ -46,8 +46,6 @@ #include "SourceLookup.h" #include "words.h" -#include "vm/db/TonDb.h" - #include "td/utils/logging.h" #include "td/utils/misc.h" #include "td/utils/Parser.h" @@ -65,7 +63,6 @@ void usage(const char* progname) { "\t-I\tSets colon-separated (unix) or at-separated (windows) library source include path. If not indicated, " "$FIFTPATH is used instead.\n" "\t-L\tPre-loads a library source file\n" - "\t-d\tUse a ton database\n" "\t-s\tScript mode: use first argument as a fift source file and import remaining arguments as $n)\n" "\t-v\tSet verbosity level\n" "\t-V\tShow fift build information\n"; @@ -94,13 +91,12 @@ int main(int argc, char* const argv[]) { bool script_mode = false; std::vector library_source_files, source_list; std::vector source_include_path; - std::string ton_db_path; fift::Fift::Config config; int i; int new_verbosity_level = VERBOSITY_NAME(INFO); - while (!script_mode && (i = getopt(argc, argv, "hinI:L:d:sv:V")) != -1) { + while (!script_mode && (i = getopt(argc, argv, "hinI:L:sv:V")) != -1) { switch (i) { case 'i': interactive = true; @@ -115,9 +111,6 @@ int main(int argc, char* const argv[]) { case 'L': library_source_files.emplace_back(optarg); break; - case 'd': - ton_db_path = optarg; - break; case 's': script_mode = true; break; @@ -158,16 +151,6 @@ int main(int argc, char* const argv[]) { config.source_lookup.add_include_path(path); } - if (!ton_db_path.empty()) { - auto r_ton_db = vm::TonDbImpl::open(ton_db_path); - if (r_ton_db.is_error()) { - LOG(ERROR) << "Error opening ton database: " << r_ton_db.error().to_string(); - std::exit(2); - } - config.ton_db = r_ton_db.move_as_ok(); - // FIXME //std::atexit([&] { config.ton_db.reset(); }); - } - fift::init_words_common(config.dictionary); fift::init_words_vm(config.dictionary, true); // enable vm debug fift::init_words_ton(config.dictionary); diff --git a/crypto/fift/utils.cpp b/crypto/fift/utils.cpp index 68fc18c03..f37766a72 100644 --- a/crypto/fift/utils.cpp +++ b/crypto/fift/utils.cpp @@ -22,6 +22,8 @@ #include "td/utils/filesystem.h" #include "td/utils/misc.h" #include "td/utils/port/path.h" +#include "vm/boc.h" +#include namespace fift { namespace { diff --git a/crypto/fift/words.cpp b/crypto/fift/words.cpp index 8d652afcc..324f492c0 100644 --- a/crypto/fift/words.cpp +++ b/crypto/fift/words.cpp @@ -43,8 +43,6 @@ #include "vm/box.hpp" #include "vm/atom.h" -#include "vm/db/TonDb.h" // only for interpret_db_run_vm{,_parallel} - #include "block/block.h" #include "common/global-version.h" @@ -2077,23 +2075,23 @@ void interpret_bitstring_hex_literal(IntCtx& ctx) { auto s = ctx.parser->scan_word_to('}'); unsigned char buff[128]; int bits = (int)td::bitstring::parse_bitstring_hex_literal(buff, sizeof(buff), s.begin(), s.end()); - if (bits < 0) { + vm::CellBuilder cb; + if (bits < 0 || !cb.store_bits_bool(td::ConstBitPtr{buff}, bits)) { throw IntError{"Invalid hex bitstring constant"}; } - auto cs = Ref{true, vm::CellBuilder().store_bits(td::ConstBitPtr{buff}, bits).finalize()}; - ctx.stack.push(std::move(cs)); + ctx.stack.push(cb.as_cellslice_ref()); push_argcount(ctx, 1); } void interpret_bitstring_binary_literal(IntCtx& ctx) { auto s = ctx.parser->scan_word_to('}'); unsigned char buff[128]; - int bits = (int)td::bitstring::parse_bitstring_binary_literal(buff, sizeof(buff), s.begin(), s.end()); - if (bits < 0) { + int bits = (int)td::bitstring::parse_bitstring_binary_literal(buff, sizeof(buff) * 8, s.begin(), s.end()); + vm::CellBuilder cb; + if (bits < 0 || !cb.store_bits_bool(td::ConstBitPtr{buff}, bits)) { throw IntError{"Invalid binary bitstring constant"}; } - auto cs = Ref{true, vm::CellBuilder().store_bits(td::ConstBitPtr{buff}, bits).finalize()}; - ctx.stack.push(std::move(cs)); + ctx.stack.push(cb.as_cellslice_ref()); push_argcount(ctx, 1); } @@ -2721,114 +2719,6 @@ void interpret_vmop_dump(vm::Stack& stack) { stack.push_string(std::move(dump)); } -void do_interpret_db_run_vm_parallel(std::ostream* stream, vm::Stack& stack, vm::TonDb* ton_db_ptr, int threads_n, - int tasks_n) { - if (!ton_db_ptr || !*ton_db_ptr) { - throw vm::VmError{vm::Excno::fatal, "Ton database is not available"}; - } - auto& ton_db = *ton_db_ptr; - auto txn = ton_db->begin_transaction(); - auto txn_abort = td::ScopeExit() + [&] { ton_db->abort_transaction(std::move(txn)); }; - - struct Task { - vm::Ref code; - vm::SmartContractDb smart; - td::optional diff; - td::unique_ptr guard; - Ref stack; - int res{0}; - Ref data; - std::string log; - }; - std::vector tasks(tasks_n); - std::vector threads(threads_n); - - for (auto& task : tasks) { - task.code = stack.pop_cellslice(); - auto smart_hash = td::serialize(stack.pop_smallint_range(1000000000)); - task.smart = txn->begin_smartcontract(smart_hash); - task.guard = td::create_lambda_guard([&] { txn->abort_smartcontract(std::move(task.smart)); }); - auto argsn = stack.pop_smallint_range(100); - task.stack = stack.split_top(argsn); - } - - std::atomic next_task_i{0}; - auto run_tasks = [&] { - while (true) { - auto task_i = next_task_i++; - if (task_i >= tasks_n) { - break; - } - auto& task = tasks[task_i]; - auto data = task.smart->get_root(); - - StringLogger logger; - vm::VmLog log = create_vm_log(stream ? &logger : nullptr); - - task.res = vm::run_vm_code(task.code, task.stack, 3, &data, std::move(log)); - task.smart->set_root(data); - task.diff = vm::SmartContractDiff(std::move(task.smart)); - task.data = std::move(data); - task.log = std::move(logger.res); - } - }; - - td::Timer timer; - for (auto& thread : threads) { - thread = td::thread(run_tasks); - } - run_tasks(); - for (auto& thread : threads) { - thread.join(); - } - - if (stream) { - int id = 0; - for (auto& task : tasks) { - id++; - *stream << "Task #" << id << " vm_log begin" << std::endl; - *stream << task.log; - *stream << "Task #" << id << " vm_log end" << std::endl; - } - } - - LOG(ERROR) << timer; - timer = {}; - - for (auto& task : tasks) { - auto retn = task.stack.write().pop_smallint_range(100, -1); - if (retn == -1) { - retn = task.stack->depth(); - } - stack.push_from_stack(std::move(*task.stack), retn); - stack.push_smallint(task.res); - stack.push_cell(std::move(task.data)); - task.guard->dismiss(); - if (task.diff) { - txn->commit_smartcontract(std::move(task.diff.value())); - } else { - txn->commit_smartcontract(std::move(task.smart)); - } - } - LOG(ERROR) << timer; - timer = {}; - - txn_abort.dismiss(); - ton_db->commit_transaction(std::move(txn)); - timer = {}; - LOG(INFO) << "TonDB stats: \n" << ton_db->stats(); -} - -void interpret_db_run_vm(IntCtx& ctx) { - do_interpret_db_run_vm_parallel(ctx.error_stream, ctx.stack, ctx.ton_db, 0, 1); -} - -void interpret_db_run_vm_parallel(IntCtx& ctx) { - auto threads_n = ctx.stack.pop_smallint_range(32, 0); - auto tasks_n = ctx.stack.pop_smallint_range(1000000000); - do_interpret_db_run_vm_parallel(ctx.error_stream, ctx.stack, ctx.ton_db, threads_n, tasks_n); -} - void interpret_store_vm_cont(vm::Stack& stack) { auto vmcont = stack.pop_cont(); auto cb = stack.pop_builder(); @@ -3518,8 +3408,6 @@ void init_words_vm(Dictionary& d, bool enable_debug) { // d.def_ctx_word("runvmcode ", std::bind(interpret_run_vm, _1, 0x40)); // d.def_ctx_word("runvm ", std::bind(interpret_run_vm, _1, 0x45)); d.def_ctx_word("runvmx ", std::bind(interpret_run_vm, _1, -1)); - d.def_ctx_word("dbrunvm ", interpret_db_run_vm); - d.def_ctx_word("dbrunvm-parallel ", interpret_db_run_vm_parallel); d.def_stack_word("vmcont, ", interpret_store_vm_cont); d.def_stack_word("vmcont@ ", interpret_fetch_vm_cont); d.def_stack_word("(vmoplen) ", interpret_vmop_len); diff --git a/crypto/func/analyzer.cpp b/crypto/func/analyzer.cpp index ec6931af0..fb05bbb4b 100644 --- a/crypto/func/analyzer.cpp +++ b/crypto/func/analyzer.cpp @@ -17,6 +17,7 @@ Copyright 2017-2020 Telegram Systems LLP */ #include "func.h" +#include "vm/boc.h" namespace funC { diff --git a/crypto/funcfiftlib/funcfiftlib.cpp b/crypto/funcfiftlib/funcfiftlib.cpp index a041c25dd..0bef9eac7 100644 --- a/crypto/funcfiftlib/funcfiftlib.cpp +++ b/crypto/funcfiftlib/funcfiftlib.cpp @@ -33,6 +33,7 @@ #include "td/utils/Status.h" #include #include +#include "vm/boc.h" td::Result compile_internal(char *config_json) { TRY_RESULT(input_json, td::json_decode(td::MutableSlice(config_json))) diff --git a/crypto/test/fift/testdb.fif b/crypto/test/fift/testdb.fif deleted file mode 100644 index cbaa98178..000000000 --- a/crypto/test/fift/testdb.fif +++ /dev/null @@ -1,102 +0,0 @@ -"Asm.fif" include - -PROGRAM{ - -NEWPROC load_dict -NEWPROC generate_dict -NEWPROC save_dict - -NEWPROC do_get -NEWPROC do_set -NEWPROC do_erase - -main PROC:<{ - DUP 1 INT EQUAL IF:<{ - DROP - do_get CALL - }>ELSE<{ - DUP 2 INT EQUAL IF:<{ - DROP - do_set CALL - }>ELSE<{ - DUP 3 INT EQUAL IF:<{ - DROP - do_erase CALL - }> }> }> - -1 INT -}> - -do_get PROC:<{ - load_dict CALL - 32 INT - DICTIGET -}> - -do_set PROC:<{ - load_dict CALL - 32 INT - DICTISET - save_dict CALL -}> - -do_erase PROC:<{ - load_dict CALL - 32 INT - DICTIDEL - DROP - save_dict CALL -}> - -generate_dict PROC:<{ - 4 INT 100 INT REPEAT:<{ - DUP 2DUP MUL ROT 617 INT ADD 1000 INT MOD - }> - DROP 100 INT - NEWDICT - SWAP REPEAT:<{ - s0 s2 XCHG - NEWC - 16 STU - s0 s2 XCHG - 32 INT - DICTISETB - }> -}> - -load_dict PROC:<{ - PUSHROOT - CTOS DUP SEMPTY IF:<{ - DROP - generate_dict CALL - }> -}> - -save_dict PROC:<{ - NEWC - STSLICE - ENDC - POPROOT -}> - -}END>s constant pmc_prog - -{ 1 2 rot pmc_prog } : task_pmc_get -{ 2 3 rot pmc_prog } : task_pmc_set -{ 3 2 rot pmc_prog } : task_pmc_erase - -{ task_pmc_get dbrunvm 2drop } : pmc_get -{ task_pmc_set dbrunvm 2drop } : pmc_set -{ task_pmc_erase dbrunvm 2drop } : pmc_erase - - &cell) { + auto boc = vm::std_boc_serialize(cell); + if (boc.is_ok()) { + return td::buffer_to_hex(boc.move_as_ok().as_slice()); + } + return "???"; +} + void StackEntry::dump(std::ostream& os, bool verbose) const { switch (tp) { case t_null: @@ -94,12 +102,7 @@ void StackEntry::dump(std::ostream& os, bool verbose) const { case t_cell: if (ref.not_null()) { if (verbose) { - std::string serialized = "???"; - auto boc = vm::std_boc_serialize(as_cell()); - if (boc.is_ok()) { - serialized = td::buffer_to_hex(boc.move_as_ok().as_slice()); - } - os << "C{" << serialized << "}"; + os << "C{" << cell_to_hex(as_cell()) << "}"; } else { os << "C{" << *as_cell() << "}"; } @@ -109,7 +112,12 @@ void StackEntry::dump(std::ostream& os, bool verbose) const { break; case t_builder: if (ref.not_null()) { - os << "BC{" << *as_builder() << "}"; + if (verbose) { + Ref cb = as_builder(); + os << "BC{" << cell_to_hex(cb.write().finalize_novm()) << "}"; + } else { + os << "BC{" << *as_builder() << "}"; + } } else { os << "BC{null}"; } @@ -117,7 +125,13 @@ void StackEntry::dump(std::ostream& os, bool verbose) const { case t_slice: { if (ref.not_null()) { os << "CS{"; - static_cast>(ref)->dump(os, 1, false); + if (verbose) { + CellBuilder cb; + cb.append_cellslice(as_slice()); + os << cell_to_hex(cb.finalize_novm()); + } else { + static_cast>(ref)->dump(os, 1, false); + } os << '}'; } else { os << "CS{null}"; diff --git a/crypto/vm/utils.cpp b/crypto/vm/utils.cpp index 783bf1327..52bfb0d43 100644 --- a/crypto/vm/utils.cpp +++ b/crypto/vm/utils.cpp @@ -96,10 +96,10 @@ td::Result convert_stack_entry(td::Slice str) { } if (l >= 3 && (str[0] == 'x' || str[0] == 'b') && str[1] == '{' && str.back() == '}') { unsigned char buff[128]; - int bits = - (str[0] == 'x') - ? (int)td::bitstring::parse_bitstring_hex_literal(buff, sizeof(buff), str.begin() + 2, str.end() - 1) - : (int)td::bitstring::parse_bitstring_binary_literal(buff, sizeof(buff), str.begin() + 2, str.end() - 1); + int bits = (str[0] == 'x') + ? (int)td::bitstring::parse_bitstring_hex_literal(buff, sizeof(buff), str.begin() + 2, str.end() - 1) + : (int)td::bitstring::parse_bitstring_binary_literal(buff, sizeof(buff) * 8, str.begin() + 2, + str.end() - 1); if (bits < 0) { return td::Status::Error("failed to parse raw b{...}/x{...} number"); } diff --git a/crypto/vm/vm.cpp b/crypto/vm/vm.cpp index 3f595a00e..fb774f80a 100644 --- a/crypto/vm/vm.cpp +++ b/crypto/vm/vm.cpp @@ -441,10 +441,16 @@ int VmState::step() { if (log.log_mask & vm::VmLog::DumpStackVerbose) { mode += 4; } + std::unique_ptr tmp_ctx; + // install temporary dummy vm state interface to prevent charging for cell load operations during dump + VmStateInterface::Guard guard(tmp_ctx.get()); stack->dump(ss, mode); VM_LOG(this) << "stack:" << ss.str(); } if (stack_trace) { + std::unique_ptr tmp_ctx; + // install temporary dummy vm state interface to prevent charging for cell load operations during dump + VmStateInterface::Guard guard(tmp_ctx.get()); stack->dump(std::cerr, 3); } ++steps; @@ -523,6 +529,13 @@ int VmState::run() { res = vmoog.get_errno(); // no ~ for unhandled exceptions (to make their faking impossible) } if (!parent) { + if ((log.log_mask & VmLog::DumpC5) && cstate.committed) { + std::stringstream ss; + ss << "final c5: "; + StackEntry::maybe(cstate.c5).dump(ss, true); + ss << "\n"; + VM_LOG(this) << ss.str(); + } return res; } restore_parent = true; diff --git a/emulator/CMakeLists.txt b/emulator/CMakeLists.txt index 7a4b7676b..dc8cbf62b 100644 --- a/emulator/CMakeLists.txt +++ b/emulator/CMakeLists.txt @@ -35,7 +35,7 @@ else() add_library(emulator STATIC ${EMULATOR_SOURCE} ${EMULATOR_HEADERS}) endif() -target_link_libraries(emulator PUBLIC emulator_static) +target_link_libraries(emulator PUBLIC emulator_static git) generate_export_header(emulator EXPORT_FILE_NAME ${CMAKE_CURRENT_BINARY_DIR}/emulator_export.h) target_include_directories(emulator PUBLIC $ @@ -48,7 +48,7 @@ if (USE_EMSCRIPTEN) add_executable(emulator-emscripten ${EMULATOR_EMSCRIPTEN_SOURCE}) target_link_libraries(emulator-emscripten PUBLIC emulator) target_link_options(emulator-emscripten PRIVATE -sEXPORTED_RUNTIME_METHODS=_malloc,free,UTF8ToString,stringToUTF8,allocate,ALLOC_NORMAL,lengthBytesUTF8) - target_link_options(emulator-emscripten PRIVATE -sEXPORTED_FUNCTIONS=_emulate,_free,_run_get_method,_create_emulator,_destroy_emulator,_emulate_with_emulator) + target_link_options(emulator-emscripten PRIVATE -sEXPORTED_FUNCTIONS=_emulate,_free,_run_get_method,_create_emulator,_destroy_emulator,_emulate_with_emulator,_version) target_link_options(emulator-emscripten PRIVATE -sEXPORT_NAME=EmulatorModule) target_link_options(emulator-emscripten PRIVATE -sERROR_ON_UNDEFINED_SYMBOLS=0) target_link_options(emulator-emscripten PRIVATE -Oz) diff --git a/emulator/emulator-emscripten.cpp b/emulator/emulator-emscripten.cpp index e76607c62..17639d280 100644 --- a/emulator/emulator-emscripten.cpp +++ b/emulator/emulator-emscripten.cpp @@ -254,4 +254,8 @@ const char *run_get_method(const char *params, const char* stack, const char* co return output; } +const char *version() { + return emulator_version(); +} + } \ No newline at end of file diff --git a/emulator/emulator-extern.cpp b/emulator/emulator-extern.cpp index 5710d4c8a..52c374edb 100644 --- a/emulator/emulator-extern.cpp +++ b/emulator/emulator-extern.cpp @@ -9,6 +9,7 @@ #include "tvm-emulator.hpp" #include "crypto/vm/stack.hpp" #include "crypto/vm/memo.h" +#include "git.h" td::Result> boc_b64_to_cell(const char *boc) { TRY_RESULT_PREFIX(boc_decoded, td::base64_decode(td::Slice(boc)), "Can't decode base64 boc: "); @@ -717,3 +718,12 @@ void tvm_emulator_destroy(void *tvm_emulator) { void emulator_config_destroy(void *config) { delete static_cast(config); } + +const char* emulator_version() { + auto version_json = td::JsonBuilder(); + auto obj = version_json.enter_object(); + obj("emulatorLibCommitHash", GitMetadata::CommitSHA1()); + obj("emulatorLibCommitDate", GitMetadata::CommitDate()); + obj.leave(); + return strdup(version_json.string_builder().as_cslice().c_str()); +} diff --git a/emulator/emulator-extern.h b/emulator/emulator-extern.h index 28d38d78d..e69a9cb0b 100644 --- a/emulator/emulator-extern.h +++ b/emulator/emulator-extern.h @@ -307,6 +307,10 @@ EMULATOR_EXPORT void tvm_emulator_destroy(void *tvm_emulator); */ EMULATOR_EXPORT void emulator_config_destroy(void *config); +/** + * @brief Get git commit hash and date of the library + */ +EMULATOR_EXPORT const char* emulator_version(); #ifdef __cplusplus } // extern "C" diff --git a/emulator/emulator_export_list b/emulator/emulator_export_list index c719a393d..feb653e2f 100644 --- a/emulator/emulator_export_list +++ b/emulator/emulator_export_list @@ -26,3 +26,4 @@ _tvm_emulator_send_external_message _tvm_emulator_send_internal_message _tvm_emulator_destroy _tvm_emulator_emulate_run_method +_emulator_version diff --git a/emulator/test/emulator-tests.cpp b/emulator/test/emulator-tests.cpp index 0780be611..24394b498 100644 --- a/emulator/test/emulator-tests.cpp +++ b/emulator/test/emulator-tests.cpp @@ -9,7 +9,6 @@ #include "td/utils/base64.h" #include "td/utils/crypto.h" #include "td/utils/JsonBuilder.h" -#include "td/utils/Time.h" #include "smc-envelope/WalletV3.h" @@ -225,7 +224,7 @@ TEST(Emulator, wallet_int_and_ext_msg) { msg_info.ihr_fee = cb.as_cellslice_ref(); } msg_info.created_lt = 0; - msg_info.created_at = static_cast(td::Time::now()); + msg_info.created_at = static_cast(utime); tlb::csr_pack(message.info, msg_info); message.init = vm::CellBuilder() .store_ones(1) @@ -242,6 +241,7 @@ TEST(Emulator, wallet_int_and_ext_msg) { auto int_msg_boc = td::base64_encode(std_boc_serialize(int_msg).move_as_ok()); std::string int_emu_res = transaction_emulator_emulate_transaction(emulator, none_shard_account_boc.c_str(), int_msg_boc.c_str()); + LOG(ERROR) << "int_emu_res = " << int_emu_res; auto int_result_json = td::json_decode(td::MutableSlice(int_emu_res)); CHECK(int_result_json.is_ok()); @@ -293,11 +293,12 @@ TEST(Emulator, wallet_int_and_ext_msg) { // emulate external message { - auto ext_body = wallet->make_a_gift_message(priv_key, static_cast(td::Time::now()) + 60, {ton::WalletV3::Gift{block::StdAddress(0, ton::StdSmcAddress()), 1 * Ton}}); + auto ext_body = wallet->make_a_gift_message(priv_key, utime + 60, {ton::WalletV3::Gift{block::StdAddress(0, ton::StdSmcAddress()), 1 * Ton}}); CHECK(ext_body.is_ok()); auto ext_msg = ton::GenericAccount::create_ext_message(address, {}, ext_body.move_as_ok()); auto ext_msg_boc = td::base64_encode(std_boc_serialize(ext_msg).move_as_ok()); std::string ext_emu_res = transaction_emulator_emulate_transaction(emulator, shard_account_after_boc_b64.c_str(), ext_msg_boc.c_str()); + LOG(ERROR) << "ext_emu_res = " << ext_emu_res; auto ext_result_json = td::json_decode(td::MutableSlice(ext_emu_res)); CHECK(ext_result_json.is_ok()); @@ -371,9 +372,10 @@ TEST(Emulator, tvm_emulator) { CHECK(wallet->get_address().rserialize_to(addr_buffer)); auto rand_seed = std::string(64, 'F'); - CHECK(tvm_emulator_set_c7(tvm_emulator, addr_buffer, static_cast(td::Time::now()), 10 * Ton, rand_seed.c_str(), config_boc)); + CHECK(tvm_emulator_set_c7(tvm_emulator, addr_buffer, 1337, 10 * Ton, rand_seed.c_str(), config_boc)); std::string tvm_res = tvm_emulator_run_get_method(tvm_emulator, method_id, stack_boc.c_str()); - + LOG(ERROR) << "tvm_res = " << tvm_res; + auto result_json = td::json_decode(td::MutableSlice(tvm_res)); CHECK(result_json.is_ok()); auto result = result_json.move_as_ok(); diff --git a/tddb/td/db/KeyValue.h b/tddb/td/db/KeyValue.h index 4f30a272b..980e367c6 100644 --- a/tddb/td/db/KeyValue.h +++ b/tddb/td/db/KeyValue.h @@ -18,6 +18,7 @@ */ #pragma once #include "td/utils/Status.h" +#include "td/utils/Time.h" #include "td/utils/logging.h" #include namespace td { diff --git a/tddb/td/db/RocksDb.cpp b/tddb/td/db/RocksDb.cpp index f8688c006..a46d04b13 100644 --- a/tddb/td/db/RocksDb.cpp +++ b/tddb/td/db/RocksDb.cpp @@ -258,11 +258,17 @@ Status RocksDb::flush() { Status RocksDb::begin_snapshot() { snapshot_.reset(db_->GetSnapshot()); + if (options_.snapshot_statistics) { + options_.snapshot_statistics->begin_snapshot(snapshot_.get()); + } return td::Status::OK(); } Status RocksDb::end_snapshot() { if (snapshot_) { + if (options_.snapshot_statistics) { + options_.snapshot_statistics->end_snapshot(snapshot_.get()); + } db_->ReleaseSnapshot(snapshot_.release()); } return td::Status::OK(); @@ -271,4 +277,41 @@ Status RocksDb::end_snapshot() { RocksDb::RocksDb(std::shared_ptr db, RocksDbOptions options) : db_(std::move(db)), options_(options) { } + +void RocksDbSnapshotStatistics::begin_snapshot(const rocksdb::Snapshot *snapshot) { + auto lock = std::unique_lock(mutex_); + auto id = reinterpret_cast(snapshot); + auto ts = td::Timestamp::now().at(); + CHECK(id_to_ts_.emplace(id, ts).second); + CHECK(by_ts_.emplace(ts, id).second); +} + +void RocksDbSnapshotStatistics::end_snapshot(const rocksdb::Snapshot *snapshot) { + auto id = reinterpret_cast(snapshot); + auto it = id_to_ts_.find(id); + CHECK(it != id_to_ts_.end()); + auto ts = it->second; + CHECK(by_ts_.erase(std::make_pair(ts, id)) == 1u); + CHECK(id_to_ts_.erase(id) == 1u); +} + +td::Timestamp RocksDbSnapshotStatistics::oldest_snapshot_timestamp() const { + auto lock = std::unique_lock(mutex_); + if (by_ts_.empty()) { + return {}; + } + return td::Timestamp::at(by_ts_.begin()->first); +} + +std::string RocksDbSnapshotStatistics::to_string() const { + td::Timestamp oldest_snapshot = oldest_snapshot_timestamp(); + double value; + if (oldest_snapshot) { + value = td::Timestamp::now().at() - oldest_snapshot.at(); + } else { + value = -1; + } + return PSTRING() << "td.rocksdb.snapshot.oldest_snapshot_ago.seconds : " << value << "\n"; +} + } // namespace td diff --git a/tddb/td/db/RocksDb.h b/tddb/td/db/RocksDb.h index 5efcd0f48..32c53a529 100644 --- a/tddb/td/db/RocksDb.h +++ b/tddb/td/db/RocksDb.h @@ -26,6 +26,12 @@ #include "td/utils/Status.h" #include "td/utils/optional.h" +#include "td/utils/Time.h" + +#include +#include +#include + namespace rocksdb { class Cache; class OptimisticTransactionDB; @@ -36,10 +42,22 @@ class Statistics; } // namespace rocksdb namespace td { +struct RocksDbSnapshotStatistics { + void begin_snapshot(const rocksdb::Snapshot *snapshot); + void end_snapshot(const rocksdb::Snapshot *snapshot); + td::Timestamp oldest_snapshot_timestamp() const; + std::string to_string() const; + + private: + mutable std::mutex mutex_; + std::map id_to_ts_; + std::set> by_ts_; +}; struct RocksDbOptions { std::shared_ptr statistics = nullptr; std::shared_ptr block_cache; // Default - one 1G cache for all RocksDb + std::shared_ptr snapshot_statistics = nullptr; bool use_direct_reads = false; }; diff --git a/tddb/test/key_value.cpp b/tddb/test/key_value.cpp index e04e7ee90..921ad059a 100644 --- a/tddb/test/key_value.cpp +++ b/tddb/test/key_value.cpp @@ -60,9 +60,20 @@ TEST(KeyValue, simple) { ensure_value(as_slice(x), as_slice(x)); kv.reset(); - kv = std::make_unique(td::RocksDb::open(db_name.str()).move_as_ok()); + td::RocksDbOptions options; + options.snapshot_statistics = std::make_shared(); + kv = std::make_unique(td::RocksDb::open(db_name.str(), options).move_as_ok()); ensure_value("A", "HELLO"); ensure_value(as_slice(x), as_slice(x)); + + CHECK(!options.snapshot_statistics->oldest_snapshot_timestamp()); + auto snapshot = kv->snapshot(); + CHECK(options.snapshot_statistics->oldest_snapshot_timestamp()); + auto snapshot2 = kv->snapshot(); + snapshot.reset(); + CHECK(options.snapshot_statistics->oldest_snapshot_timestamp()); + snapshot2.reset(); + CHECK(!options.snapshot_statistics->oldest_snapshot_timestamp()); }; TEST(KeyValue, async_simple) { diff --git a/tonlib/tonlib/tonlib-cli.cpp b/tonlib/tonlib/tonlib-cli.cpp index eb0f5af4f..f12e88d41 100644 --- a/tonlib/tonlib/tonlib-cli.cpp +++ b/tonlib/tonlib/tonlib-cli.cpp @@ -1356,10 +1356,10 @@ class TonlibCli : public td::actor::Actor { } if (l >= 3 && (str[0] == 'x' || str[0] == 'b') && str[1] == '{' && str.back() == '}') { unsigned char buff[128]; - int bits = - (str[0] == 'x') - ? (int)td::bitstring::parse_bitstring_hex_literal(buff, sizeof(buff), str.begin() + 2, str.end() - 1) - : (int)td::bitstring::parse_bitstring_binary_literal(buff, sizeof(buff), str.begin() + 2, str.end() - 1); + int bits = (str[0] == 'x') ? (int)td::bitstring::parse_bitstring_hex_literal(buff, sizeof(buff), str.begin() + 2, + str.end() - 1) + : (int)td::bitstring::parse_bitstring_binary_literal(buff, sizeof(buff) * 8, + str.begin() + 2, str.end() - 1); if (bits < 0) { return td::Status::Error("Failed to parse slice"); } diff --git a/validator/db/celldb.cpp b/validator/db/celldb.cpp index dfbee0a1a..463e6e34a 100644 --- a/validator/db/celldb.cpp +++ b/validator/db/celldb.cpp @@ -84,11 +84,13 @@ void CellDbIn::start_up() { }; CellDbBase::start_up(); + td::RocksDbOptions db_options; if (!opts_->get_disable_rocksdb_stats()) { statistics_ = td::RocksDb::create_statistics(); statistics_flush_at_ = td::Timestamp::in(60.0); + snapshot_statistics_ = std::make_shared(); + db_options.snapshot_statistics = snapshot_statistics_; } - td::RocksDbOptions db_options; db_options.statistics = statistics_; if (opts_->get_celldb_cache_size()) { db_options.block_cache = td::RocksDb::create_cache(opts_->get_celldb_cache_size().value()); @@ -193,7 +195,11 @@ void CellDbIn::get_cell_db_reader(td::Promise> } void CellDbIn::flush_db_stats() { - auto stats = td::RocksDb::statistics_to_string(statistics_) + cell_db_statistics_.to_string(); + if (opts_->get_disable_rocksdb_stats()) { + return; + } + auto stats = td::RocksDb::statistics_to_string(statistics_) + snapshot_statistics_->to_string() + + cell_db_statistics_.to_string(); auto to_file_r = td::FileFd::open(path_ + "/db_stats.txt", td::FileFd::Truncate | td::FileFd::Create | td::FileFd::Write, 0644); if (to_file_r.is_error()) { diff --git a/validator/db/celldb.hpp b/validator/db/celldb.hpp index 7dc1fa781..b3857971c 100644 --- a/validator/db/celldb.hpp +++ b/validator/db/celldb.hpp @@ -27,6 +27,7 @@ #include "auto/tl/ton_api.h" #include "validator.h" #include "db-utils.h" +#include "td/db/RocksDb.h" namespace rocksdb { class Statistics; @@ -139,6 +140,7 @@ class CellDbIn : public CellDbBase { }; std::shared_ptr statistics_; + std::shared_ptr snapshot_statistics_; CellDbStatistics cell_db_statistics_; td::Timestamp statistics_flush_at_ = td::Timestamp::never();