From c860ce3d1e6c44886df29070c63d60f30530f60b Mon Sep 17 00:00:00 2001 From: ton Date: Wed, 23 Oct 2019 17:43:50 +0400 Subject: [PATCH] updated smartcontracts - updated smartcontracts - updated fullnode database layout - fixed memory leak in blockchain-explorer - updated tonlib --- CMakeLists.txt | 10 +- adnl/adnl-ext-server.cpp | 4 +- adnl/adnl-ext-server.hpp | 65 +- blockchain-explorer/blockchain-explorer.cpp | 4 +- common/int-to-string.hpp | 24 + crypto/CMakeLists.txt | 33 + crypto/block/block.tlb | 3 + crypto/block/check-proof.cpp | 2 - crypto/block/mc-config.cpp | 77 +- crypto/block/mc-config.h | 44 +- crypto/block/transaction.cpp | 16 +- crypto/block/transaction.h | 27 +- crypto/common/bitstring.h | 2 + crypto/fift/lib/Asm.fif | 1 + crypto/fift/lib/TonUtil.fif | 2 +- crypto/smartcont/CreateState.fif | 19 +- crypto/smartcont/config-code.fc | 124 +- crypto/smartcont/elector-code.fc | 19 +- crypto/smartcont/gen-zerostate.fif | 16 + crypto/smartcont/multisig-code.fc | 265 +++ crypto/smartcont/simple-wallet-ext-code.fc | 67 + crypto/smartcont/stdlib.fc | 16 + crypto/smc-envelope/GenericAccount.cpp | 99 ++ crypto/smc-envelope/GenericAccount.h | 31 + crypto/smc-envelope/MultisigWallet.cpp | 171 ++ crypto/smc-envelope/MultisigWallet.h | 64 + crypto/smc-envelope/SmartContract.cpp | 188 ++ crypto/smc-envelope/SmartContract.h | 116 ++ crypto/smc-envelope/SmartContractCode.cpp | 72 + crypto/smc-envelope/SmartContractCode.h | 30 + crypto/smc-envelope/TestGiver.cpp | 62 + crypto/smc-envelope/TestGiver.h | 39 + crypto/smc-envelope/TestWallet.cpp | 91 + crypto/smc-envelope/TestWallet.h | 48 + crypto/smc-envelope/Wallet.cpp | 100 ++ crypto/smc-envelope/Wallet.h | 48 + crypto/test/Ed25519.cpp | 75 + crypto/test/test-db.cpp | 32 +- crypto/test/test-smartcont.cpp | 524 ++++++ crypto/test/wycheproof.h | 1160 +++++++++++++ crypto/vm/cells/CellString.cpp | 64 + crypto/vm/cells/CellString.h | 22 + crypto/vm/continuation.cpp | 3 + crypto/vm/continuation.h | 52 +- crypto/vm/excno.hpp | 17 + crypto/vm/tonops.cpp | 11 +- doc/tvm.tex | 3 +- .../android/src/drinkless/org/ton/Client.java | 28 +- .../main/java/drinkless/org/ton/Client.java | 28 +- example/cpp/tonjson_example.cpp | 4 +- lite-client/lite-client-common.cpp | 22 + lite-client/lite-client-common.h | 5 +- lite-client/lite-client.cpp | 24 +- lite-client/lite-client.h | 2 - tdactor/benchmark/benchmark.cpp | 279 +++ tdactor/td/actor/PromiseFuture.h | 23 + tdactor/td/actor/actor.h | 25 + tdnet/td/net/TcpListener.cpp | 52 + tdnet/td/net/TcpListener.h | 19 + tdnet/td/net/UdpServer.cpp | 64 - tdutils/td/utils/Status.h | 14 + tdutils/td/utils/invoke.h | 4 +- tdutils/td/utils/tests.h | 28 + .../JavadocTlDocumentationGenerator.php | 6 +- tl/generate/scheme/ton_api.tl | 11 +- tl/generate/scheme/ton_api.tlo | Bin 54392 -> 55652 bytes tl/generate/scheme/tonlib_api.tl | 73 +- tl/generate/scheme/tonlib_api.tlo | Bin 13984 -> 16460 bytes tonlib/CMakeLists.txt | 16 +- tonlib/test/offline.cpp | 206 +-- tonlib/test/online.cpp | 565 ++++-- tonlib/tonlib/Client.cpp | 2 +- tonlib/tonlib/ExtClient.cpp | 15 + tonlib/tonlib/ExtClient.h | 5 + tonlib/tonlib/ExtClientLazy.cpp | 7 + tonlib/tonlib/KeyStorage.cpp | 35 +- tonlib/tonlib/KeyStorage.h | 4 + tonlib/tonlib/LastBlock.cpp | 9 +- tonlib/tonlib/LastConfig.cpp | 152 ++ tonlib/tonlib/LastConfig.h | 70 + tonlib/tonlib/Logging.cpp | 44 +- tonlib/tonlib/TonlibClient.cpp | 1507 ++++++++++++----- tonlib/tonlib/TonlibClient.h | 108 +- tonlib/tonlib/TonlibError.h | 11 + tonlib/tonlib/tonlib-cli.cpp | 316 +++- tonlib/tonlib/utils.cpp | 19 +- tonlib/tonlib/utils.h | 13 +- validator/CMakeLists.txt | 5 + validator/block-handle.hpp | 13 + validator/db/archive-db.cpp | 332 ++++ validator/db/archive-db.hpp | 97 ++ validator/db/archiver.cpp | 51 +- validator/db/archiver.hpp | 5 +- validator/db/filedb.cpp | 3 + validator/db/filedb.hpp | 14 +- validator/db/files-async.hpp | 2 +- validator/db/package.cpp | 143 ++ validator/db/package.hpp | 40 + validator/db/rootdb.cpp | 81 +- validator/db/rootdb.hpp | 4 +- validator/interfaces/block-handle.h | 2 + validator/manager.cpp | 21 + validator/manager.hpp | 22 +- validator/net/download-state.cpp | 2 +- 104 files changed, 7294 insertions(+), 1320 deletions(-) create mode 100644 common/int-to-string.hpp create mode 100644 crypto/smartcont/multisig-code.fc create mode 100644 crypto/smartcont/simple-wallet-ext-code.fc create mode 100644 crypto/smc-envelope/GenericAccount.cpp create mode 100644 crypto/smc-envelope/GenericAccount.h create mode 100644 crypto/smc-envelope/MultisigWallet.cpp create mode 100644 crypto/smc-envelope/MultisigWallet.h create mode 100644 crypto/smc-envelope/SmartContract.cpp create mode 100644 crypto/smc-envelope/SmartContract.h create mode 100644 crypto/smc-envelope/SmartContractCode.cpp create mode 100644 crypto/smc-envelope/SmartContractCode.h create mode 100644 crypto/smc-envelope/TestGiver.cpp create mode 100644 crypto/smc-envelope/TestGiver.h create mode 100644 crypto/smc-envelope/TestWallet.cpp create mode 100644 crypto/smc-envelope/TestWallet.h create mode 100644 crypto/smc-envelope/Wallet.cpp create mode 100644 crypto/smc-envelope/Wallet.h create mode 100644 crypto/test/test-smartcont.cpp create mode 100644 crypto/test/wycheproof.h create mode 100644 crypto/vm/cells/CellString.cpp create mode 100644 crypto/vm/cells/CellString.h create mode 100644 tonlib/tonlib/LastConfig.cpp create mode 100644 tonlib/tonlib/LastConfig.h create mode 100644 validator/db/archive-db.cpp create mode 100644 validator/db/archive-db.hpp create mode 100644 validator/db/package.cpp create mode 100644 validator/db/package.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index d71959b55..583578c6b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -9,7 +9,7 @@ get_filename_component(TON_REAL_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}" REALPAT get_filename_component(TON_REAL_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}" REALPATH) if (TON_REAL_BINARY_DIR STREQUAL TON_REAL_SOURCE_DIR) - message(" Out-of-source build should be used to build TDLib.") + message(" Out-of-source build should be used to build TON.") message(" You need to remove the files already created by CMake and") message(" rerun CMake from a new directory:") message(" rm -rf CMakeFiles CMakeCache.txt") @@ -190,6 +190,7 @@ endif() set(CMAKE_THREAD_PREFER_PTHREAD ON) set(THREADS_PREFER_PTHREAD_FLAG ON) find_package(Threads REQUIRED) +find_package(ZLIB REQUIRED) if (TON_ARCH AND NOT MSVC) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=${TON_ARCH}") @@ -380,7 +381,7 @@ if (NOT CMAKE_CROSSCOMPILING) if (TDUTILS_MIME_TYPE) set(TDMIME_AUTO tdmime_auto) endif() - add_custom_target(prepare_cross_compiling DEPENDS tl_generate_common tlb_generate_block ${TDMIME_AUTO}) + add_custom_target(prepare_cross_compiling DEPENDS tl_generate_common tlb_generate_block gen_fif ${TDMIME_AUTO}) endif() #TESTS @@ -390,6 +391,9 @@ target_link_libraries(test-ed25519 PRIVATE ton_crypto) add_executable(test-vm test/test-td-main.cpp ${TONVM_TEST_SOURCE}) target_link_libraries(test-vm PRIVATE ton_crypto fift-lib) +add_executable(test-smartcont test/test-td-main.cpp ${SMARTCONT_TEST_SOURCE}) +target_link_libraries(test-smartcont PRIVATE smc-envelope fift-lib ton_db) + add_executable(test-cells test/test-td-main.cpp ${CELLS_TEST_SOURCE}) target_link_libraries(test-cells PRIVATE ton_crypto) @@ -490,10 +494,12 @@ endif() enable_testing() set(TEST_OPTIONS "--regression ${CMAKE_CURRENT_SOURCE_DIR}/test/regression-tests.ans --filter -Bench") separate_arguments(TEST_OPTIONS) +add_test(test-ed25519-crypto crypto/test-ed25519-crypto) add_test(test-ed25519 test-ed25519) add_test(test-vm test-vm ${TEST_OPTIONS}) add_test(test-fift test-fift ${TEST_OPTIONS}) add_test(test-cells test-cells ${TEST_OPTIONS}) +add_test(test-smartcont test-smartcont) add_test(test-net test-net) add_test(test-actors test-tdactor) diff --git a/adnl/adnl-ext-server.cpp b/adnl/adnl-ext-server.cpp index 168e9839c..a23d8610d 100644 --- a/adnl/adnl-ext-server.cpp +++ b/adnl/adnl-ext-server.cpp @@ -146,8 +146,8 @@ void AdnlExtServerImpl::add_tcp_port(td::uint16 port) { } }; - auto act = td::actor::create_actor(td::actor::ActorOptions().with_name("listener").with_poll(), - port, std::make_unique(actor_id(this))); + auto act = td::actor::create_actor( + td::actor::ActorOptions().with_name("listener").with_poll(), port, std::make_unique(actor_id(this))); listeners_.emplace(port, std::move(act)); } diff --git a/adnl/adnl-ext-server.hpp b/adnl/adnl-ext-server.hpp index f7912c851..7ffa9e69d 100644 --- a/adnl/adnl-ext-server.hpp +++ b/adnl/adnl-ext-server.hpp @@ -32,69 +32,6 @@ namespace ton { namespace adnl { -class TcpInfiniteListener : public td::actor::Actor { - public: - TcpInfiniteListener(td::int32 port, std::unique_ptr callback) - : port_(port), callback_(std::move(callback)) { - } - - private: - td::int32 port_; - std::unique_ptr callback_; - td::actor::ActorOwn tcp_listener_; - td::int32 refcnt_{0}; - bool close_flag_{false}; - - void start_up() override { - loop(); - } - - void hangup() override { - close_flag_ = true; - tcp_listener_.reset(); - if (refcnt_ == 0) { - stop(); - } - } - - void loop() override { - if (!tcp_listener_.empty()) { - return; - } - class Callback : public td::TcpListener::Callback { - public: - Callback(td::actor::ActorShared parent) : parent_(std::move(parent)) { - } - void accept(td::SocketFd fd) override { - td::actor::send_closure(parent_, &TcpInfiniteListener::accept, std::move(fd)); - } - - private: - td::actor::ActorShared parent_; - }; - refcnt_++; - tcp_listener_ = td::actor::create_actor( - td::actor::ActorOptions().with_name(PSLICE() << "TcpListener" << td::tag("port", port_)).with_poll(), port_, - std::make_unique(actor_shared(this))); - } - - void accept(td::SocketFd fd) { - callback_->accept(std::move(fd)); - } - - void hangup_shared() override { - refcnt_--; - tcp_listener_.reset(); - if (close_flag_) { - if (refcnt_ == 0) { - stop(); - } - } else { - alarm_timestamp() = td::Timestamp::in(5 /*5 seconds*/); - } - } -}; - class AdnlExtServerImpl; class AdnlInboundConnection : public AdnlExtConnection { @@ -150,7 +87,7 @@ class AdnlExtServerImpl : public AdnlExtServer { td::actor::ActorId peer_table_; std::set local_ids_; std::set ports_; - std::map> listeners_; + std::map> listeners_; }; } // namespace adnl diff --git a/blockchain-explorer/blockchain-explorer.cpp b/blockchain-explorer/blockchain-explorer.cpp index 01e4f9546..2fa1bf75c 100644 --- a/blockchain-explorer/blockchain-explorer.cpp +++ b/blockchain-explorer/blockchain-explorer.cpp @@ -393,8 +393,8 @@ class CoreActor : public CoreActorInterface { clients_.emplace_back(ton::adnl::AdnlExtClient::create(ton::adnl::AdnlNodeIdFull{remote_public_key_}, remote_addr_, make_callback(0))); } - daemon_ = MHD_start_daemon(MHD_USE_THREAD_PER_CONNECTION, static_cast(http_port_), nullptr, nullptr, - &process_http_request, nullptr, MHD_OPTION_END); + daemon_ = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY, static_cast(http_port_), nullptr, nullptr, + &process_http_request, nullptr, MHD_OPTION_THREAD_POOL_SIZE, 16, MHD_OPTION_END); CHECK(daemon_ != nullptr); } }; diff --git a/common/int-to-string.hpp b/common/int-to-string.hpp new file mode 100644 index 000000000..414342cd0 --- /dev/null +++ b/common/int-to-string.hpp @@ -0,0 +1,24 @@ +#pragma once + +#include "td/utils/int_types.h" +#include "td/utils/Slice.h" + +namespace ton { + +template +typename std::enable_if_t::value, td::MutableSlice> store_int_to_slice(td::MutableSlice S, + const T &v) { + CHECK(S.size() >= sizeof(T)); + S.copy_from(td::Slice(reinterpret_cast(&v), sizeof(T))); + return S.remove_prefix(sizeof(T)); +} + +template +typename std::enable_if_t::value, T> fetch_int_from_slice(td::Slice S) { + CHECK(S.size() >= sizeof(T)); + T v; + td::MutableSlice(reinterpret_cast(&v), sizeof(T)).copy_from(S.truncate(sizeof(T))); + return v; +} + +} // namespace ton diff --git a/crypto/CMakeLists.txt b/crypto/CMakeLists.txt index dc7d7cf14..8b8f147ab 100644 --- a/crypto/CMakeLists.txt +++ b/crypto/CMakeLists.txt @@ -88,6 +88,7 @@ set(TON_CRYPTO_SOURCE vm/cells/CellBuilder.cpp vm/cells/CellHash.cpp vm/cells/CellSlice.cpp + vm/cells/CellString.cpp vm/cells/CellTraits.cpp vm/cells/CellUsageTree.cpp vm/cells/DataCell.cpp @@ -99,6 +100,7 @@ set(TON_CRYPTO_SOURCE vm/cells/CellBuilder.h vm/cells/CellHash.h vm/cells/CellSlice.h + vm/cells/CellString.h vm/cells/CellTraits.h vm/cells/CellUsageTree.h vm/cells/CellWithStorage.h @@ -197,6 +199,24 @@ set(BLOCK_SOURCE block/transaction.h ) +set(SMC_ENVELOPE_SOURCE + smc-envelope/GenericAccount.cpp + smc-envelope/MultisigWallet.cpp + smc-envelope/SmartContract.cpp + smc-envelope/SmartContractCode.cpp + smc-envelope/TestGiver.cpp + smc-envelope/TestWallet.cpp + smc-envelope/Wallet.cpp + + smc-envelope/GenericAccount.h + smc-envelope/MultisigWallet.h + smc-envelope/SmartContract.h + smc-envelope/SmartContractCode.h + smc-envelope/TestGiver.h + smc-envelope/TestWallet.h + smc-envelope/Wallet.h +) + set(ED25519_TEST_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/test/Ed25519.cpp PARENT_SCOPE @@ -217,6 +237,11 @@ set(TONVM_TEST_SOURCE PARENT_SCOPE ) +set(SMARTCONT_TEST_SOURCE + ${CMAKE_CURRENT_SOURCE_DIR}/test/test-smartcont.cpp + PARENT_SCOPE +) + set(FIFT_TEST_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}/test/fift.cpp PARENT_SCOPE @@ -329,10 +354,18 @@ if (NOT CMAKE_CROSSCOMPILING) GenFif(DEST smartcont/auto/highload-wallet-code SOURCE smartcont/highload-wallet-code.fc NAME highload-wallet) GenFif(DEST smartcont/auto/highload-wallet-v2-code SOURCE smartcont/highload-wallet-v2-code.fc NAME highoad-wallet-v2) GenFif(DEST smartcont/auto/elector-code SOURCE smartcont/elector-code.fc NAME elector-code) + GenFif(DEST smartcont/auto/multisig-code SOURCE smartcont/multisig-code.fc NAME multisig) GenFif(DEST smartcont/auto/restricted-wallet-code SOURCE smartcont/restricted-wallet-code.fc NAME restricted-wallet) GenFif(DEST smartcont/auto/restricted-wallet2-code SOURCE smartcont/restricted-wallet2-code.fc NAME restricted-wallet2) + + GenFif(DEST smartcont/auto/simple-wallet-ext-code SOURCE smartcont/simple-wallet-ext-code.fc NAME simple-wallet-ext) endif() +add_library(smc-envelope ${SMC_ENVELOPE_SOURCE}) +target_include_directories(smc-envelope PUBLIC $) +target_link_libraries(smc-envelope PUBLIC ton_crypto PRIVATE tdutils ton_block) +add_dependencies(smc-envelope gen_fif) + add_executable(create-state block/create-state.cpp) target_include_directories(create-state PUBLIC $ $) diff --git a/crypto/block/block.tlb b/crypto/block/block.tlb index d4bba1a18..ec91ef575 100644 --- a/crypto/block/block.tlb +++ b/crypto/block/block.tlb @@ -551,6 +551,9 @@ validator_addr#73 public_key:SigPubKey weight:uint64 adnl_addr:bits256 = Validat validators#11 utime_since:uint32 utime_until:uint32 total:(## 16) main:(## 16) { main <= total } { main >= 1 } list:(Hashmap 16 ValidatorDescr) = ValidatorSet; +validators_ext#12 utime_since:uint32 utime_until:uint32 + total:(## 16) main:(## 16) { main <= total } { main >= 1 } + total_weight:uint64 list:(HashmapE 16 ValidatorDescr) = ValidatorSet; _ config_addr:bits256 = ConfigParam 0; _ elector_addr:bits256 = ConfigParam 1; diff --git a/crypto/block/check-proof.cpp b/crypto/block/check-proof.cpp index 7bc21eb3e..7db7a97c6 100644 --- a/crypto/block/check-proof.cpp +++ b/crypto/block/check-proof.cpp @@ -221,8 +221,6 @@ td::Status check_account_proof(td::Slice proof, ton::BlockIdExt shard_blk, const td::Result AccountState::validate(ton::BlockIdExt ref_blk, block::StdAddress addr) const { TRY_RESULT_PREFIX(root, vm::std_boc_deserialize(state.as_slice(), true), "cannot deserialize account state"); - LOG(INFO) << "got account state for " << addr << " with respect to blocks " << blk.to_str() - << (shard_blk == blk ? "" : std::string{" and "} + shard_blk.to_str()); if (blk != ref_blk && ref_blk.id.seqno != ~0U) { return td::Status::Error(PSLICE() << "obtained getAccountState() for a different reference block " << blk.to_str() << " instead of requested " << ref_blk.to_str()); diff --git a/crypto/block/mc-config.cpp b/crypto/block/mc-config.cpp index ceefca79e..80e399eed 100644 --- a/crypto/block/mc-config.cpp +++ b/crypto/block/mc-config.cpp @@ -387,11 +387,25 @@ td::Result> Config::unpack_validator_set(Ref dict_root; + if (!tlb::unpack_cell(vset_root, rec)) { + gen::ValidatorSet::Record_validators rec0; + if (!tlb::unpack_cell(std::move(vset_root), rec0)) { + return td::Status::Error("validator set is invalid"); + } + rec.utime_since = rec0.utime_since; + rec.utime_until = rec0.utime_until; + rec.total = rec0.total; + rec.main = rec0.main; + dict_root = vm::Dictionary::construct_root_from(*rec0.list); + rec.total_weight = 0; + } else if (rec.total_weight) { + dict_root = rec.list->prefetch_ref(); + } else { + return td::Status::Error("validator set cannot have zero total weight"); } - vm::Dictionary dict{vm::Dictionary::construct_root_from(*rec.list), 16}; + vm::Dictionary dict{std::move(dict_root), 16}; td::BitArray<16> key_buffer; auto last = dict.get_minmax_key(key_buffer.bits(), 16, true); if (last.is_null() || (int)key_buffer.to_ulong() != rec.total - 1) { @@ -428,6 +442,9 @@ td::Result> Config::unpack_validator_set(Reflist.emplace_back(sig_pubkey.pubkey, descr.weight, ptr->total_weight, descr.adnl_addr); ptr->total_weight += descr.weight; } + if (rec.total_weight && rec.total_weight != ptr->total_weight) { + return td::Status::Error("validator set declares incorrect total weight"); + } return std::move(ptr); } @@ -517,6 +534,58 @@ td::Result> Config::get_storage_prices() const { return std::move(res); } +td::Result Config::get_gas_limits_prices(bool is_masterchain) const { + GasLimitsPrices res; + auto id = is_masterchain ? 20 : 21; + auto cell = get_config_param(id); + if (cell.is_null()) { + return td::Status::Error(PSLICE() << "configuration parameter " << id << " with gas prices is absent"); + } + auto cs = vm::load_cell_slice(std::move(cell)); + block::gen::GasLimitsPrices::Record_gas_flat_pfx flat; + if (tlb::unpack(cs, flat)) { + cs = *flat.other; + res.flat_gas_limit = flat.flat_gas_limit; + res.flat_gas_price = flat.flat_gas_price; + } + auto f = [&](const auto& r, td::uint64 spec_limit) { + res.gas_limit = r.gas_limit; + res.special_gas_limit = spec_limit; + res.gas_credit = r.gas_credit; + res.gas_price = r.gas_price; + res.freeze_due_limit = r.freeze_due_limit; + res.delete_due_limit = r.delete_due_limit; + }; + block::gen::GasLimitsPrices::Record_gas_prices_ext rec; + if (tlb::unpack(cs, rec)) { + f(rec, rec.special_gas_limit); + } else { + block::gen::GasLimitsPrices::Record_gas_prices rec0; + if (tlb::unpack(cs, rec0)) { + f(rec0, rec0.gas_limit); + } else { + return td::Status::Error(PSLICE() << "configuration parameter " << id + << " with gas prices is invalid - can't parse"); + } + } + return res; +} + +td::Result Config::get_msg_prices(bool is_masterchain) const { + auto id = is_masterchain ? 24 : 25; + auto cell = get_config_param(id); + if (cell.is_null()) { + return td::Status::Error(PSLICE() << "configuration parameter " << id << " with msg prices is absent"); + } + auto cs = vm::load_cell_slice(std::move(cell)); + block::gen::MsgForwardPrices::Record rec; + if (!tlb::unpack(cs, rec)) { + return td::Status::Error(PSLICE() << "configuration parameter " << id + << " with msg prices is invalid - can't parse"); + } + return MsgPrices(rec.lump_price, rec.bit_price, rec.cell_price, rec.ihr_price_factor, rec.first_frac, rec.next_frac); +} + CatchainValidatorsConfig Config::unpack_catchain_validators_config(Ref cell) { block::gen::CatchainConfig::Record cfg; if (cell.is_null() || !tlb::unpack_cell(std::move(cell), cfg)) { diff --git a/crypto/block/mc-config.h b/crypto/block/mc-config.h index 24351c0c7..018a47adb 100644 --- a/crypto/block/mc-config.h +++ b/crypto/block/mc-config.h @@ -50,7 +50,7 @@ struct ValidatorDescr { : pubkey(_pubkey), weight(_weight), cum_weight(_cum_weight) { adnl_addr.set_zero(); } - bool operator<(td::uint64 wt_pos) const & { + bool operator<(td::uint64 wt_pos) const& { return cum_weight < wt_pos; } }; @@ -327,6 +327,46 @@ struct StoragePrices { , mc_bit_price(_mc_bprice) , mc_cell_price(_mc_cprice) { } + static td::RefInt256 compute_storage_fees(ton::UnixTime now, const std::vector& pricing, + const vm::CellStorageStat& storage_stat, ton::UnixTime last_paid, + bool is_special, bool is_masterchain); +}; + +struct GasLimitsPrices { + td::uint64 flat_gas_limit{0}; + td::uint64 flat_gas_price{0}; + td::uint64 gas_price{0}; + td::uint64 special_gas_limit{0}; + td::uint64 gas_limit{0}; + td::uint64 gas_credit{0}; + td::uint64 block_gas_limit{0}; + td::uint64 freeze_due_limit{0}; + td::uint64 delete_due_limit{0}; + + td::RefInt256 compute_gas_price(td::uint64 gas_used) const; +}; + +// msg_fwd_fees = (lump_price + ceil((bit_price * msg.bits + cell_price * msg.cells)/2^16)) nanograms +// ihr_fwd_fees = ceil((msg_fwd_fees * ihr_price_factor)/2^16) nanograms +// bits in the root cell of a message are not included in msg.bits (lump_price pays for them) + +struct MsgPrices { + td::uint64 lump_price; + td::uint64 bit_price; + td::uint64 cell_price; + td::uint32 ihr_factor; + td::uint32 first_frac; + td::uint32 next_frac; + td::uint64 compute_fwd_fees(td::uint64 cells, td::uint64 bits) const; + std::pair compute_fwd_ihr_fees(td::uint64 cells, td::uint64 bits, + bool ihr_disabled = false) const; + MsgPrices() = default; + MsgPrices(td::uint64 lump, td::uint64 bitp, td::uint64 cellp, td::uint32 ihrf, td::uint32 firstf, td::uint32 nextf) + : lump_price(lump), bit_price(bitp), cell_price(cellp), ihr_factor(ihrf), first_frac(firstf), next_frac(nextf) { + } + td::RefInt256 get_first_part(td::RefInt256 total) const; + td::uint64 get_first_part(td::uint64 total) const; + td::RefInt256 get_next_part(td::RefInt256 total) const; }; struct CatchainValidatorsConfig { @@ -499,6 +539,8 @@ class Config { bool is_special_smartcontract(const ton::StdSmcAddress& addr) const; static td::Result> unpack_validator_set(Ref valset_root); td::Result> get_storage_prices() const; + td::Result get_gas_limits_prices(bool is_masterchain = false) const; + td::Result get_msg_prices(bool is_masterchain = false) const; static CatchainValidatorsConfig unpack_catchain_validators_config(Ref cell); CatchainValidatorsConfig get_catchain_validators_config() const; td::Status visit_validator_params() const; diff --git a/crypto/block/transaction.cpp b/crypto/block/transaction.cpp index bc6d8e220..6667cf877 100644 --- a/crypto/block/transaction.cpp +++ b/crypto/block/transaction.cpp @@ -421,7 +421,9 @@ void add_partial_storage_payment(td::BigInt256& payment, ton::UnixTime delta, co payment += b; } -td::RefInt256 Account::compute_storage_fees(ton::UnixTime now, const std::vector& pricing) const { +td::RefInt256 StoragePrices::compute_storage_fees(ton::UnixTime now, const std::vector& pricing, + const vm::CellStorageStat& storage_stat, ton::UnixTime last_paid, + bool is_special, bool is_masterchain) { if (now <= last_paid || !last_paid || is_special || pricing.empty() || now <= pricing[0].valid_since) { return {}; } @@ -438,7 +440,7 @@ td::RefInt256 Account::compute_storage_fees(ton::UnixTime now, const std::vector ton::UnixTime valid_until = (i < n - 1 ? std::min(now, pricing[i + 1].valid_since) : now); if (upto < valid_until) { assert(upto >= pricing[i].valid_since); - add_partial_storage_payment(total.unique_write(), valid_until - upto, pricing[i], storage_stat, is_masterchain()); + add_partial_storage_payment(total.unique_write(), valid_until - upto, pricing[i], storage_stat, is_masterchain); } upto = valid_until; } @@ -446,6 +448,10 @@ td::RefInt256 Account::compute_storage_fees(ton::UnixTime now, const std::vector return total; } +td::RefInt256 Account::compute_storage_fees(ton::UnixTime now, const std::vector& pricing) const { + return StoragePrices::compute_storage_fees(now, pricing, storage_stat, last_paid, is_special, is_masterchain()); +} + Transaction::Transaction(const Account& _account, int ttype, ton::LogicalTime req_start_lt, ton::UnixTime _now, Ref _inmsg) : trans_type(ttype) @@ -969,7 +975,7 @@ bool Transaction::prepare_compute_phase(const ComputePhaseConfig& cfg) { gas = vm.get_gas_limits(); cp.gas_used = std::min(gas.gas_consumed(), gas.gas_limit); cp.accepted = (gas.gas_credit == 0); - cp.success = (cp.accepted && (unsigned)cp.exit_code <= 1); + cp.success = (cp.accepted && vm.committed()); if (cp.accepted & use_msg_state) { was_activated = true; acc_status = Account::acc_active; @@ -978,8 +984,8 @@ bool Transaction::prepare_compute_phase(const ComputePhaseConfig& cfg) { << ", limit=" << gas.gas_limit << ", credit=" << gas.gas_credit; LOG(INFO) << "out_of_gas=" << cp.out_of_gas << ", accepted=" << cp.accepted << ", success=" << cp.success; if (cp.success) { - cp.new_data = vm.get_c4(); // c4 -> persistent data - cp.actions = vm.get_d(5); // c5 -> action list + cp.new_data = vm.get_committed_state().c4; // c4 -> persistent data + cp.actions = vm.get_committed_state().c5; // c5 -> action list int out_act_num = output_actions_count(cp.actions); if (verbosity > 2) { std::cerr << "new smart contract data: "; diff --git a/crypto/block/transaction.h b/crypto/block/transaction.h index 015dc3f39..25f805956 100644 --- a/crypto/block/transaction.h +++ b/crypto/block/transaction.h @@ -65,10 +65,10 @@ struct NewOutMsg { NewOutMsg(ton::LogicalTime _lt, Ref _msg, Ref _trans) : lt(_lt), msg(std::move(_msg)), trans(std::move(_trans)) { } - bool operator<(const NewOutMsg& other) const & { + bool operator<(const NewOutMsg& other) const& { return lt < other.lt || (lt == other.lt && msg->get_hash() < other.msg->get_hash()); } - bool operator>(const NewOutMsg& other) const & { + bool operator>(const NewOutMsg& other) const& { return lt > other.lt || (lt == other.lt && other.msg->get_hash() < msg->get_hash()); } }; @@ -132,29 +132,6 @@ struct ComputePhaseConfig { bool parse_GasLimitsPrices(Ref cell, td::RefInt256& freeze_due_limit, td::RefInt256& delete_due_limit); }; -// msg_fwd_fees = (lump_price + ceil((bit_price * msg.bits + cell_price * msg.cells)/2^16)) nanograms -// ihr_fwd_fees = ceil((msg_fwd_fees * ihr_price_factor)/2^16) nanograms -// bits in the root cell of a message are not included in msg.bits (lump_price pays for them) - -struct MsgPrices { - td::uint64 lump_price; - td::uint64 bit_price; - td::uint64 cell_price; - td::uint32 ihr_factor; - td::uint32 first_frac; - td::uint32 next_frac; - td::uint64 compute_fwd_fees(td::uint64 cells, td::uint64 bits) const; - std::pair compute_fwd_ihr_fees(td::uint64 cells, td::uint64 bits, - bool ihr_disabled = false) const; - MsgPrices() = default; - MsgPrices(td::uint64 lump, td::uint64 bitp, td::uint64 cellp, td::uint32 ihrf, td::uint32 firstf, td::uint32 nextf) - : lump_price(lump), bit_price(bitp), cell_price(cellp), ihr_factor(ihrf), first_frac(firstf), next_frac(nextf) { - } - td::RefInt256 get_first_part(td::RefInt256 total) const; - td::uint64 get_first_part(td::uint64 total) const; - td::RefInt256 get_next_part(td::RefInt256 total) const; -}; - struct ActionPhaseConfig { int max_actions{255}; MsgPrices fwd_std; diff --git a/crypto/common/bitstring.h b/crypto/common/bitstring.h index b2a37c72f..901ff709b 100644 --- a/crypto/common/bitstring.h +++ b/crypto/common/bitstring.h @@ -224,6 +224,8 @@ class BitSliceGen { BitSliceGen(BitSliceGen&& bs, unsigned _offs, unsigned _len); BitSliceGen(Pt* _ptr, unsigned _len) : ref(), ptr(_ptr), offs(0), len(_len) { } + explicit BitSliceGen(Slice slice) : BitSliceGen(slice.data(), slice.size() * 8) { + } ~BitSliceGen() { } Pt* get_ptr() const { diff --git a/crypto/fift/lib/Asm.fif b/crypto/fift/lib/Asm.fif index 685471737..eb9034ec1 100644 --- a/crypto/fift/lib/Asm.fif +++ b/crypto/fift/lib/Asm.fif @@ -970,6 +970,7 @@ x{F4B7} @Defop SUBDICTURPGET x{F800} @Defop ACCEPT x{F801} @Defop SETGASLIMIT +x{F80F} @Defop COMMIT x{F82} @Defop(4u) GETPARAM x{F823} @Defop NOW diff --git a/crypto/fift/lib/TonUtil.fif b/crypto/fift/lib/TonUtil.fif index 44f1ff1cf..7f1a376bb 100644 --- a/crypto/fift/lib/TonUtil.fif +++ b/crypto/fift/lib/TonUtil.fif @@ -139,7 +139,7 @@ recursive append-long-bytes { // ( S -- x ) parse public key { dup $len 48 <> abort"public key must be 48 characters long" - base64>B dup Blen 36 <> abort"public key must be 48 characters long" + base64url>B dup Blen 36 <> abort"public key must be 48 characters long" 34 B| 16 B>u@ over crc16 <> abort"crc16 mismatch in public key" 16 B>u@+ 0x3ee6 <> abort"invalid tag in public key" 256 B>u@ diff --git a/crypto/smartcont/CreateState.fif b/crypto/smartcont/CreateState.fif index d3c05c00c..ed82499b5 100644 --- a/crypto/smartcont/CreateState.fif +++ b/crypto/smartcont/CreateState.fif @@ -1,4 +1,5 @@ "Asm.fif" include +"TonUtil.fif" include 31 -1<< constant wc_undef 0 constant wc_base @@ -187,6 +188,7 @@ dictnew constant special-dict // restricted wallet creation +"auto/wallet-code.fif" include =: WCode0 "auto/restricted-wallet-code.fif" include =: RWCode1 "auto/restricted-wallet2-code.fif" include =: RWCode2 @@ -200,7 +202,7 @@ dictnew constant special-dict 0 // ticktock 2 // mode: create register_smc - Masterchain 6 .Addr cr + Masterchain swap 6 .Addr cr } : create-wallet1 // D x t -- D' @@ -225,5 +227,18 @@ dictnew constant special-dict 0 // ticktock 2 // mode: create register_smc - Masterchain 6 .Addr cr + Masterchain swap 6 .Addr cr } : create-wallet2 + +// pubkey amount +{ over ."Key " pubkey>$ type ." -> " + WCode0 // code + // data + empty_cell // libs + 3 roll // balance + 0 // split_depth + 0 // ticktock + 2 // mode: create + register_smc + Masterchain swap 6 .Addr cr +} : create-wallet0 diff --git a/crypto/smartcont/config-code.fc b/crypto/smartcont/config-code.fc index 5e0508f53..a49ad1378 100644 --- a/crypto/smartcont/config-code.fc +++ b/crypto/smartcont/config-code.fc @@ -1,15 +1,32 @@ ;; Simple configuration smart contract () set_conf_param(int index, cell value) impure { - var cs = begin_parse(get_data()); + var cs = get_data().begin_parse(); var cfg_dict = cs~load_ref(); cfg_dict~idict_set_ref(32, index, value); set_data(begin_cell().store_ref(cfg_dict).store_slice(cs).end_cell()); } +(cell, int, int, cell) load_data() inline { + var cs = get_data().begin_parse(); + var (cfg_dict, stored_seqno, public_key) = (cs~load_ref(), cs~load_uint(32), cs~load_uint(256)); + var vote_dict = cs.slice_empty?() ? new_dict() : cs~load_dict(); + cs.end_parse(); + return (cfg_dict, stored_seqno, public_key, vote_dict); +} + +() store_data(cfg_dict, stored_seqno, public_key, vote_dict) impure inline { + set_data(begin_cell() + .store_ref(cfg_dict) + .store_uint(stored_seqno, 32) + .store_uint(public_key, 256) + .store_dict(vote_dict) + .end_cell()); +} + (int, int) check_validator_set(cell vset) { var cs = vset.begin_parse(); - throw_unless(9, cs~load_uint(8) == 0x11); ;; validators#11 + throw_if(9, (cs~load_uint(8) - 0x11) & -2); ;; validators#11 or validators_ext#12 int utime_since = cs~load_uint(32); int utime_until = cs~load_uint(32); int total = cs~load_uint(16); @@ -86,43 +103,112 @@ .end_cell(), 0); } -() recv_external(slice in_msg) impure { - var signature = in_msg~load_bits(512); - var cs = in_msg; - int action = cs~load_uint(32); - int msg_seqno = cs~load_uint(32); - var valid_until = cs~load_uint(32); - throw_if(35, valid_until < now()); - var cs2 = begin_parse(get_data()); - var cfg_dict = cs2~load_ref(); - var stored_seqno = cs2~load_uint(32); - var public_key = cs2~load_uint(256); - cs2.end_parse(); - throw_unless(33, msg_seqno == stored_seqno); - throw_unless(34, check_signature(slice_hash(in_msg), signature, public_key)); - accept_message(); +() after_code_upgrade(slice param, cell old_code) impure method_id(1666) { +} + +_ perform_action(cfg_dict, public_key, action, cs) { if (action == 0x43665021) { ;; change one configuration parameter var param_index = cs~load_uint(32); var param_value = cs~load_ref(); cs.end_parse(); cfg_dict~idict_set_ref(32, param_index, param_value); + return (cfg_dict, public_key); } elseif (action == 0x4e436f64) { ;; change configuration smart contract code var new_code = cs~load_ref(); - cs.end_parse(); set_code(new_code); + var old_code = get_c3(); + set_c3(new_code); + after_code_upgrade(cs, old_code); + throw(0); + return (cfg_dict, public_key); } elseif (action == 0x50624b21) { ;; change configuration master public key public_key = cs~load_uint(256); cs.end_parse(); + return (cfg_dict, public_key); } elseif (action == 0x4e43ef05) { ;; change election smart contract code change_elector_code(cs); + return (cfg_dict, public_key); } else { throw_if(32, action); + return (cfg_dict, public_key); + } +} + +slice get_validator_descr(int idx) inline_ref { + var vset = config_param(34); + if (vset.null?()) { + return null(); + } + var cs = begin_parse(vset); + cs~skip_bits(8 + 32 + 32 + 16 + 16); + var dict = begin_cell().store_slice(cs).end_cell(); + var (value, _) = dict.udict_get?(16, idx); + return value; +} + +(int, int) unpack_validator_descr(slice cs) inline { + ;; ed25519_pubkey#8e81278a pubkey:bits256 = SigPubKey; + ;; validator#53 public_key:SigPubKey weight:uint64 = ValidatorDescr; + ;; validator_addr#73 public_key:SigPubKey weight:uint64 adnl_addr:bits256 = ValidatorDescr; + throw_unless(41, (cs~load_uint(8) & ~ 0x20) == 0x53); + throw_unless(41, cs~load_uint(32) == 0x8e81278a); + return (cs~load_uint(256), cs~load_uint(64)); +} + +slice create_new_entry(cs) inline { + return begin_cell().store_int(false, 1).store_uint(0, 64).store_uint(0, 256).store_slice(cs).end_cell().begin_parse(); +} + +cell register_vote(vote_dict, action, cs, idx, weight) { + int hash = 0; + var entry = null(); + if (action & 1) { + hash = slice_hash(cs); + (entry, var found?) = vote_dict.udict_get?(256, hash); + ifnot (found?) { + entry = create_new_entry(cs); + } + } else { + hash = cs.preload_uint(256); + (entry, var found?) = vote_dict.udict_get?(256, hash); + throw_unless(42, found?); + } + return vote_dict; +} + +() recv_external(slice in_msg) impure { + var signature = in_msg~load_bits(512); + var cs = in_msg; + int action = cs~load_uint(32); + int msg_seqno = cs~load_uint(32); + var valid_until = cs~load_uint(32); + throw_if(35, valid_until < now()); + var (cfg_dict, stored_seqno, public_key, vote_dict) = load_data(); + throw_unless(33, msg_seqno == stored_seqno); + ifnot ((action - 0x566f7465) & -2) { + var idx = cs~load_uint(16); + var vdescr = get_validator_descr(idx); + var (val_pubkey, weight) = unpack_validator_descr(vdescr); + throw_unless(34, check_signature(slice_hash(in_msg), signature, val_pubkey)); + accept_message(); + stored_seqno += 1; + store_data(cfg_dict, stored_seqno, public_key, vote_dict); + commit(); + vote_dict = register_vote(vote_dict, action, cs, idx, weight); + store_data(cfg_dict, stored_seqno, public_key, vote_dict); + return (); } - set_data(begin_cell().store_ref(cfg_dict).store_uint(stored_seqno + 1, 32).store_uint(public_key, 256).end_cell()); + throw_unless(34, check_signature(slice_hash(in_msg), signature, public_key)); + accept_message(); + stored_seqno += 1; + store_data(cfg_dict, stored_seqno, public_key, vote_dict); + commit(); + (cfg_dict, public_key) = perform_action(cfg_dict, public_key, action, cs); + store_data(cfg_dict, stored_seqno, public_key, vote_dict); } () run_ticktock(int is_tock) impure { diff --git a/crypto/smartcont/elector-code.fc b/crypto/smartcont/elector-code.fc index b97b0b3ce..fe000c14d 100644 --- a/crypto/smartcont/elector-code.fc +++ b/crypto/smartcont/elector-code.fc @@ -408,7 +408,7 @@ _ compute_total_stake(l, n, m_stake) { return tot_stake; } -(cell, cell, cell, int, int) try_elect(credits, members, min_stake, max_stake, min_total_stake, max_stake_factor) { +(cell, cell, int, cell, int, int) try_elect(credits, members, min_stake, max_stake, min_total_stake, max_stake_factor) { var cs = 16.config_param().begin_parse(); var (max_validators, _, min_validators) = (cs~load_uint(16), cs~load_uint(16), cs~load_uint(16)); cs.end_parse(); @@ -435,7 +435,7 @@ _ compute_total_stake(l, n, m_stake) { } until (~ f); n = min(n, max_validators); if (n < min_validators) { - return (credits, new_dict(), new_dict(), 0, 0); + return (credits, new_dict(), 0, new_dict(), 0, 0); } var l = nil; do { @@ -464,7 +464,7 @@ _ compute_total_stake(l, n, m_stake) { } } until (i >= n); if ((m == 0) | (best_stake < min_total_stake)) { - return (credits, new_dict(), new_dict(), 0, 0); + return (credits, new_dict(), 0, new_dict(), 0, 0); } ;; we have to select first m validators from list l l1 = touch(l); @@ -476,6 +476,7 @@ _ compute_total_stake(l, n, m_stake) { ;; create both the new validator set and the refund set int i = 0; var tot_stake = 0; + var tot_weight = 0; var vset = new_dict(); var frozen = new_dict(); do { @@ -492,6 +493,7 @@ _ compute_total_stake(l, n, m_stake) { ;; validator_addr#73 public_key:SigPubKey weight:uint64 adnl_addr:bits256 = ValidatorDescr; var weight = (true_stake << 60) / best_stake; tot_stake += true_stake; + tot_weight += weight; var vinfo = begin_cell() .store_uint(adnl_addr ? 0x73 : 0x53, 8) ;; validator_addr#73 or validator#53 .store_uint(0x8e81278a, 32) ;; ed25519_pubkey#8e81278a @@ -514,7 +516,7 @@ _ compute_total_stake(l, n, m_stake) { i += 1; } until (l.null?()); throw_unless(49, tot_stake == best_stake); - return (credits, vset, frozen, tot_stake, m); + return (credits, vset, tot_weight, frozen, tot_stake, m); } int conduct_elections(ds, elect, credits) impure { @@ -545,7 +547,7 @@ int conduct_elections(ds, elect, credits) impure { ;; elections finished return false; } - (credits, var vdict, var frozen, var total_stakes, var cnt) = try_elect(credits, members, min_stake, max_stake, min_total_stake, max_stake_factor); + (credits, var vdict, var total_weight, var frozen, var total_stakes, var cnt) = try_elect(credits, members, min_stake, max_stake, min_total_stake, max_stake_factor); ;; pack elections; if cnt==0, set failed=true, finished=false. failed = (cnt == 0); finished = ~ failed; @@ -561,12 +563,13 @@ int conduct_elections(ds, elect, credits) impure { var start = max(now() + elect_end_before - 60, elect_at); var main_validators = config_param(16).begin_parse().skip_bits(16).preload_uint(16); var vset = begin_cell() - .store_uint(0x11, 8) ;; validators#11 + .store_uint(0x12, 8) ;; validators_ext#12 .store_uint(start, 32) ;; utime_since:uint32 .store_uint(start + elect_for, 32) ;; utime_until:uint32 .store_uint(cnt, 16) ;; total:(## 16) - .store_uint(min(cnt, main_validators), 16) ;; main:(## 16) - .store_slice(vdict.begin_parse()) ;; list:(Hashmap 16 ValidatorDescr) + .store_uint(min(cnt, main_validators), 16) ;; main:(## 16) + .store_uint(total_weight, 64) ;; total_weight:uint64 + .store_dict(vdict) ;; list:(HashmapE 16 ValidatorDescr) .end_cell(); var config_addr = config_param(0).begin_parse().preload_uint(256); send_validator_set_to_config(config_addr, vset, elect_at); diff --git a/crypto/smartcont/gen-zerostate.fif b/crypto/smartcont/gen-zerostate.fif index 70abe412e..66d686241 100644 --- a/crypto/smartcont/gen-zerostate.fif +++ b/crypto/smartcont/gen-zerostate.fif @@ -225,6 +225,22 @@ Masterchain swap "config-master" +suffix +".addr" save-address-verbose // Other data +/* + * + * Initial wallets (test) + * + */ + +// pubkey amount `create-wallet1` or pubkey amount `create-wallet2` +PK'PuZPPXK5Rff9SvtoS7Y9lUuEixvy-J6aishYFj3Qn6P0pJMb GR$100000000 create-wallet1 +PK'PuYiB1zAWzr4p8j6I681+sGUrRGcn6Ylf7vXl0xaUl/w6Xfg GR$1700000000 create-wallet0 + +/* + * + * Create state + * + */ + create_state cr cr ."new state is:" cr dup B dup Bx. cr diff --git a/crypto/smartcont/multisig-code.fc b/crypto/smartcont/multisig-code.fc new file mode 100644 index 000000000..cbe2d30fe --- /dev/null +++ b/crypto/smartcont/multisig-code.fc @@ -0,0 +1,265 @@ +;; Simple wallet smart contract + +_ unpack_state() inline_ref { + var ds = begin_parse(get_data()); + var res = (ds~load_uint(8), ds~load_uint(8), ds~load_uint(64), ds~load_dict(), ds~load_dict()); + ds.end_parse(); + return res; +} + +_ pack_state(cell pending_queries, cell public_keys, int last_cleaned, int k, int n) inline_ref { + return begin_cell() + .store_uint(n, 8) + .store_uint(k, 8) + .store_uint(last_cleaned, 64) + .store_dict(public_keys) + .store_dict(pending_queries) + .end_cell(); +} + +(int, int) check_signatures(cell public_keys, cell signatures, int hash, int cnt_bits) inline_ref { + int cnt = 0; + + do { + slice cs = signatures.begin_parse(); + slice signature = cs~load_bits(512); + + int i = cs~load_uint(8); + signatures = cs~load_dict(); + + (slice public_key, var found?) = public_keys.udict_get?(8, i); + throw_unless(37, found?); + throw_unless(38, check_signature(hash, signature, public_key.preload_uint(256))); + + int mask = (1 << i); + int old_cnt_bits = cnt_bits; + cnt_bits |= mask; + int should_check = cnt_bits != old_cnt_bits; + cnt -= should_check; + } until (cell_null?(signatures)); + + return (cnt, cnt_bits); +} + + +() recv_internal(slice in_msg) impure { + ;; do nothing for internal messages +} + +(int, int, slice) unpack_query_data(slice in_msg, int n, slice query, var found?) inline_ref { + if (found?) { + throw_unless(35, query~load_int(1)); + (int cnt, int cnt_bits, slice msg) = (query~load_uint(8), query~load_uint(n), query); + throw_unless(36, slice_hash(msg) == slice_hash(in_msg)); + return (cnt, cnt_bits, msg); + } + return (0, 0, in_msg); +} + +() try_init() impure inline_ref { + ;; first query without signatures is always accepted + (int n, int k, int last_cleaned, cell public_keys, cell pending_queries) = unpack_state(); + throw_if(37, last_cleaned); + accept_message(); + set_data(pack_state(pending_queries, public_keys, 1, k, n)); +} + +cell update_pending_queries(cell pending_queries, slice msg, int query_id, int cnt, int cnt_bits, int n, int k) impure inline_ref { + if (cnt >= k) { + while (msg.slice_refs()) { + var mode = msg~load_uint(8); + send_raw_message(msg~load_ref(), mode); + } + pending_queries~udict_set_builder(64, query_id, begin_cell().store_int(0, 1)); + } else { + pending_queries~udict_set_builder(64, query_id, begin_cell() + .store_uint(1, 1) + .store_uint(cnt, 8) + .store_uint(cnt_bits, n) + .store_slice(msg)); + } + return pending_queries; +} + +() recv_external(slice in_msg) impure { + ;; empty message triggers init + if (slice_empty?(in_msg)) { + return try_init(); + } + + ;; Check root signature + slice root_signature = in_msg~load_bits(512); + int root_hash = slice_hash(in_msg); + int root_i = in_msg~load_uint(8); + + (int n, int k, int last_cleaned, cell public_keys, cell pending_queries) = unpack_state(); + last_cleaned -= last_cleaned == 0; + + (slice public_key, var found?) = public_keys.udict_get?(8, root_i); + throw_unless(31, found?); + throw_unless(32, check_signature(root_hash, root_signature, public_key.preload_uint(256))); + + cell signatures = in_msg~load_dict(); + + var hash = slice_hash(in_msg); + int query_id = in_msg~load_uint(64); + + var bound = (now() << 32); + throw_if(33, query_id < bound); + + (slice query, var found?) = pending_queries.udict_get?(64, query_id); + (int cnt, int cnt_bits, slice msg) = unpack_query_data(in_msg, n, query, found?); + int mask = 1 << root_i; + throw_if(34, cnt_bits & mask); + cnt_bits |= mask; + cnt += 1; + + ;; TODO: reserve some gas or FAIL + accept_message(); + + pending_queries = update_pending_queries(pending_queries, msg, query_id, cnt, cnt_bits, n, k); + set_data(pack_state(pending_queries, public_keys, last_cleaned, k, n)); + + commit(); + + int need_save = 0; + ifnot (cell_null?(signatures) | (cnt >= k)) { + (int new_cnt, cnt_bits) = check_signatures(public_keys, signatures, hash, cnt_bits); + cnt += new_cnt; + pending_queries = update_pending_queries(pending_queries, msg, query_id, cnt, cnt_bits, n, k); + need_save = -1; + } + + bound -= (64 << 32); ;; clean up records expired more than 64 seconds ago + int old_last_cleaned = last_cleaned; + do { + var (pending_queries', i, _, f) = pending_queries.udict_delete_get_min(64); + f~touch(); + if (f) { + f = (i < bound); + } + if (f) { + pending_queries = pending_queries'; + last_cleaned = i; + need_save = -1; + } + } until (~ f); + + if (need_save) { + set_data(pack_state(pending_queries, public_keys, last_cleaned, k, n)); + } +} + +;; Get methods +;; returns -1 for processed queries, 0 for unprocessed, 1 for unknown (forgotten) +(int, int) get_query_state(int query_id) method_id { + (int n, _, int last_cleaned, _, cell pending_queries) = unpack_state(); + (slice cs, var found) = pending_queries.udict_get?(64, query_id); + if (found) { + if (cs~load_int(1)) { + cs~load_uint(8); + return (0, cs~load_uint(n)); + } else { + return (-1, 0); + } + } else { + return (-(query_id <= last_cleaned), 0); + } +} + +int processed?(int query_id) method_id { + (int x, _) = get_query_state(query_id); + return x; +} + +cell create_init_state(int n, int k, cell public_keys) method_id { + return pack_state(new_dict(), public_keys, 0, k, n); +} + +cell merge_list(cell a, cell b) { + if (cell_null?(a)) { + return b; + } + if (cell_null?(b)) { + return a; + } + slice as = a.begin_parse(); + if (as.slice_refs() != 0) { + cell tail = merge_list(as~load_ref(), b); + return begin_cell().store_slice(as).store_ref(tail).end_cell(); + } + + as~skip_last_bits(1); + ;; as~skip_bits(1); + return begin_cell().store_slice(as).store_dict(b).end_cell(); + +} + +cell get_public_keys() method_id { + (_, _, _, cell public_keys, _) = unpack_state(); + return public_keys; +} + +(int, int) check_query_signatures(cell query) method_id { + slice cs = query.begin_parse(); + slice root_signature = cs~load_bits(512); + int root_hash = slice_hash(cs); + int root_i = cs~load_uint(8); + + cell public_keys = get_public_keys(); + (slice public_key, var found?) = public_keys.udict_get?(8, root_i); + throw_unless(31, found?); + throw_unless(32, check_signature(root_hash, root_signature, public_key.preload_uint(256))); + + int mask = 1 << root_i; + + cell signatures = cs~load_dict(); + if (cell_null?(signatures)) { + return (1, mask); + } + (int cnt, mask) = check_signatures(public_keys, signatures, slice_hash(cs), mask); + return (cnt + 1, mask); +} + +cell messages_by_mask(int mask) method_id { + (int n, _, _, _, cell pending_queries) = unpack_state(); + int i = -1; + cell a = new_dict(); + do { + (i, var cs, var f) = pending_queries.udict_get_next?(64, i); + if (f) { + if (cs~load_int(1)) { + int cnt_bits = cs.skip_bits(8).preload_uint(n); + if (cnt_bits & mask) { + a~udict_set_builder(64, i, begin_cell().store_slice(cs)); + } + } + } + } until (~ f); + return a; +} + +cell get_messages_unsigned_by_id(int id) method_id { + return messages_by_mask(1 << id); +} + +cell get_messages_unsigned() method_id { + return messages_by_mask(~ 0); +} + +(int, int) get_n_k() method_id { + (int n, int k, _, _, _) = unpack_state(); + return (n, k); +} + +cell merge_inner_queries(cell a, cell b) method_id { + slice ca = a.begin_parse(); + slice cb = b.begin_parse(); + cell list_a = ca~load_dict(); + cell list_b = cb~load_dict(); + throw_unless(31, slice_hash(ca) == slice_hash(cb)); + return begin_cell() + .store_dict(merge_list(list_a, list_b)) + .store_slice(ca) + .end_cell(); +} diff --git a/crypto/smartcont/simple-wallet-ext-code.fc b/crypto/smartcont/simple-wallet-ext-code.fc new file mode 100644 index 000000000..a20b96a86 --- /dev/null +++ b/crypto/smartcont/simple-wallet-ext-code.fc @@ -0,0 +1,67 @@ +;; Simple wallet smart contract + +cell create_state(int seqno, int public_key) { + return begin_cell().store_uint(seqno, 32).store_uint(public_key, 256).end_cell(); +} + +(int, int) load_state() { + var cs2 = begin_parse(get_data()); + return (cs2~load_uint(32), cs2~load_uint(256)); +} + +() save_state(int seqno, int public_key) impure { + set_data(create_state(seqno, public_key)); +} + +() recv_internal(slice in_msg) impure { + ;; do nothing for internal messages +} + +slice do_verify_message(slice in_msg, int seqno, int public_key) { + var signature = in_msg~load_bits(512); + var cs = in_msg; + int msg_seqno = cs~load_uint(32); + throw_unless(33, msg_seqno == seqno); + throw_unless(34, check_signature(slice_hash(in_msg), signature, public_key)); + return cs; +} + +() recv_external(slice in_msg) impure { + (int stored_seqno, int public_key) = load_state(); + var cs = do_verify_message(in_msg, stored_seqno, public_key); + accept_message(); + cs~touch_slice(); + if (cs.slice_refs()) { + var mode = cs~load_uint(8); + send_raw_message(cs~load_ref(), mode); + } + cs.end_parse(); + save_state(stored_seqno + 1, public_key); +} + +;; Get methods + +int seqno() method_id { + return get_data().begin_parse().preload_uint(32); +} + +cell create_init_state(int public_key) method_id { + return create_state(0, public_key); +} + +cell prepare_send_message_with_seqno(int mode, cell msg, int seqno) method_id { + return begin_cell().store_uint(seqno, 32).store_uint(mode, 8).store_ref(msg).end_cell(); +} + +cell prepare_send_message(int mode, cell msg) method_id { + return prepare_send_message_with_seqno(mode, msg, seqno()); +} + + +slice verify_message(slice msg) method_id { + var (stored_seqno, public_key) = load_state(); + return do_verify_message(msg, stored_seqno, public_key); +} + + + diff --git a/crypto/smartcont/stdlib.fc b/crypto/smartcont/stdlib.fc index c60d96c92..0798a0a1b 100644 --- a/crypto/smartcont/stdlib.fc +++ b/crypto/smartcont/stdlib.fc @@ -16,6 +16,7 @@ forall X -> X first(tuple t) asm "FIRST"; forall X -> X second(tuple t) asm "SECOND"; forall X -> X third(tuple t) asm "THIRD"; forall X -> X fourth(tuple t) asm "3 INDEX"; +forall X -> X null() asm "PUSHNULL"; int now() asm "NOW"; slice my_address() asm "MYADDR"; @@ -34,8 +35,10 @@ int check_data_signature(slice data, slice signature, int public_key) asm "CHKSI cell get_data() asm "c4 PUSH"; () set_data(cell c) impure asm "c4 POP"; +cell get_c3() impure asm "c3 PUSH"; () set_c3(cell c) impure asm "c3 POP"; () accept_message() impure asm "ACCEPT"; +() commit() impure asm "COMMIT"; int min(int x, int y) asm "MIN"; int max(int x, int y) asm "MAX"; @@ -52,7 +55,10 @@ cell preload_ref(slice s) asm "PLDREF"; ;; slice preload_bits(slice s, int len) asm "PLDSLICEX"; (slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS"; slice skip_bits(slice s, int len) asm "SDSKIPFIRST"; +(slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST"; slice first_bits(slice s, int len) asm "SDCUTFIRST"; +slice skip_last_bits(slice s, int len) asm "SDSKIPLAST"; +(slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST"; (slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT"; cell preload_dict(slice s) asm "PLDDICT"; slice skip_dict(slice s) asm "SKIPDICT"; @@ -94,6 +100,16 @@ cell udict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "D (cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; (cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT"; (cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT"; +cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET"; +cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +(cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET"; +cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET"; +(cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD"; +(cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE"; +(cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD"; +(cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE"; cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; (cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB"; cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB"; diff --git a/crypto/smc-envelope/GenericAccount.cpp b/crypto/smc-envelope/GenericAccount.cpp new file mode 100644 index 000000000..20750fcba --- /dev/null +++ b/crypto/smc-envelope/GenericAccount.cpp @@ -0,0 +1,99 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2017-2019 Telegram Systems LLP +*/ +#include "GenericAccount.h" + +#include "block/block-auto.h" +#include "block/block-parse.h" +namespace ton { +td::Ref GenericAccount::get_init_state(td::Ref code, td::Ref data) noexcept { + return vm::CellBuilder() + .store_zeroes(2) + .store_ones(2) + .store_zeroes(1) + .store_ref(std::move(code)) + .store_ref(std::move(data)) + .finalize(); +} +block::StdAddress GenericAccount::get_address(ton::WorkchainId workchain_id, + const td::Ref& init_state) noexcept { + return block::StdAddress(workchain_id, init_state->get_hash().bits(), true /*bounce*/); +} + +void GenericAccount::store_int_message(vm::CellBuilder& cb, const block::StdAddress& dest_address, td::int64 gramms) { + td::BigInt256 dest_addr; + dest_addr.import_bits(dest_address.addr.as_bitslice()); + cb.store_zeroes(1) + .store_ones(1) + .store_long(dest_address.bounceable, 1) + .store_zeroes(3) + .store_ones(1) + .store_zeroes(2) + .store_long(dest_address.workchain, 8) + .store_int256(dest_addr, 256); + block::tlb::t_Grams.store_integer_value(cb, td::BigInt256(gramms)); + cb.store_zeroes(9 + 64 + 32 + 1 + 1); +} + +td::Ref GenericAccount::create_ext_message(const block::StdAddress& address, td::Ref new_state, + td::Ref body) noexcept { + block::gen::Message::Record message; + /*info*/ { + block::gen::CommonMsgInfo::Record_ext_in_msg_info info; + /* src */ + tlb::csr_pack(info.src, block::gen::MsgAddressExt::Record_addr_none{}); + /* dest */ { + block::gen::MsgAddressInt::Record_addr_std dest; + dest.anycast = vm::CellBuilder().store_zeroes(1).as_cellslice_ref(); + dest.workchain_id = address.workchain; + dest.address = address.addr; + + tlb::csr_pack(info.dest, dest); + } + /* import_fee */ { + vm::CellBuilder cb; + block::tlb::t_Grams.store_integer_value(cb, td::BigInt256(0)); + info.import_fee = cb.as_cellslice_ref(); + } + + tlb::csr_pack(message.info, info); + } + /* init */ { + if (new_state.not_null()) { + // Just(Left(new_state)) + message.init = vm::CellBuilder() + .store_ones(1) + .store_zeroes(1) + .append_cellslice(vm::load_cell_slice(new_state)) + .as_cellslice_ref(); + } else { + message.init = vm::CellBuilder().store_zeroes(1).as_cellslice_ref(); + CHECK(message.init.not_null()); + } + } + /* body */ { + message.body = vm::CellBuilder().store_zeroes(1).append_cellslice(vm::load_cell_slice_ref(body)).as_cellslice_ref(); + } + + td::Ref res; + tlb::type_pack_cell(res, block::gen::t_Message_Any, message); + CHECK(res.not_null()); + + return res; +} +} // namespace ton diff --git a/crypto/smc-envelope/GenericAccount.h b/crypto/smc-envelope/GenericAccount.h new file mode 100644 index 000000000..003b8e1b7 --- /dev/null +++ b/crypto/smc-envelope/GenericAccount.h @@ -0,0 +1,31 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2017-2019 Telegram Systems LLP +*/ +#pragma once +#include "vm/cells.h" +#include "block/block.h" +namespace ton { +class GenericAccount { + public: + static td::Ref get_init_state(td::Ref code, td::Ref data) noexcept; + static block::StdAddress get_address(ton::WorkchainId workchain_id, const td::Ref& init_state) noexcept; + static td::Ref create_ext_message(const block::StdAddress& address, td::Ref new_state, + td::Ref body) noexcept; + static void store_int_message(vm::CellBuilder& cb, const block::StdAddress& dest_address, td::int64 gramms); +}; +} // namespace ton diff --git a/crypto/smc-envelope/MultisigWallet.cpp b/crypto/smc-envelope/MultisigWallet.cpp new file mode 100644 index 000000000..36830179c --- /dev/null +++ b/crypto/smc-envelope/MultisigWallet.cpp @@ -0,0 +1,171 @@ +#include "MultisigWallet.h" + +#include "SmartContractCode.h" + +#include "vm/dict.h" + +#include "td/utils/misc.h" + +namespace ton { + +MultisigWallet::QueryBuilder::QueryBuilder(td::int64 query_id, td::Ref msg, int mode) { + msg_ = vm::CellBuilder().store_long(query_id, 64).store_long(mode, 8).store_ref(std::move(msg)).finalize(); +} +void MultisigWallet::QueryBuilder::sign(td::int32 id, td::Ed25519::PrivateKey& pk) { + CHECK(id < td::narrow_cast(mask_.size())); + auto signature = pk.sign(msg_->get_hash().as_slice()).move_as_ok(); + mask_.set(id); + vm::CellBuilder cb; + cb.store_bytes(signature.as_slice()); + cb.store_long(id, 8); + cb.ensure_throw(cb.store_maybe_ref(std::move(dict_))); + dict_ = cb.finalize(); +} + +td::Ref MultisigWallet::QueryBuilder::create_inner() const { + vm::CellBuilder cb; + cb.ensure_throw(cb.store_maybe_ref(dict_)); + return cb.append_cellslice(vm::load_cell_slice(msg_)).finalize(); +} + +td::Ref MultisigWallet::QueryBuilder::create(td::int32 id, td::Ed25519::PrivateKey& pk) const { + auto cell = create_inner(); + vm::CellBuilder cb; + cb.store_long(id, 8); + cb.append_cellslice(vm::load_cell_slice(cell)); + cell = cb.finalize(); + + auto signature = pk.sign(cell->get_hash().as_slice()).move_as_ok(); + vm::CellBuilder cb2; + cb2.store_bytes(signature.as_slice()); + cb2.append_cellslice(vm::load_cell_slice(cell)); + return cb2.finalize(); +} + +td::Ref MultisigWallet::create(td::Ref data) { + return td::Ref(true, State{ton::SmartContractCode::multisig(), std::move(data)}); +} + +int MultisigWallet::processed(td::uint64 query_id) const { + auto res = run_get_method("processed?", {td::make_refint(query_id)}); + return res.stack.write().pop_smallint_range(1, -1); +} + +MultisigWallet::QueryState MultisigWallet::get_query_state(td::uint64 query_id) const { + auto ans = run_get_method("get_query_state", {td::make_refint(query_id)}); + + auto mask = ans.stack.write().pop_int(); + auto state = ans.stack.write().pop_smallint_range(1, -1); + + QueryState res; + if (state == 1) { + res.state = QueryState::Unknown; + } else if (state == 0) { + res.state = QueryState::NotReady; + for (size_t i = 0; i < res.mask.size(); i++) { + if (mask->get_bit(static_cast(i))) { + res.mask.set(i); + } + } + } else { + res.state = QueryState::Sent; + } + return res; +} + +std::vector MultisigWallet::get_public_keys() const { + auto ans = run_get_method("get_public_keys"); + auto dict_root = ans.stack.write().pop_cell(); + vm::Dictionary dict(std::move(dict_root), 8); + std::vector res; + dict.check_for_each([&](auto cs, auto x, auto y) { + td::SecureString key(32); + cs->prefetch_bytes(key.as_mutable_slice().ubegin(), td::narrow_cast(key.size())); + res.push_back(std::move(key)); + return true; + }); + return res; +} + +td::Ref MultisigWallet::create_init_data(std::vector public_keys, int k) const { + vm::Dictionary pk(8); + for (size_t i = 0; i < public_keys.size(); i++) { + auto key = pk.integer_key(td::make_refint(i), 8, false); + pk.set_builder(key.bits(), 8, vm::CellBuilder().store_bytes(public_keys[i].as_slice())); + } + auto res = run_get_method("create_init_state", + {td::make_refint(public_keys.size()), td::make_refint(k), pk.get_root_cell()}); + CHECK(res.code == 0); + return res.stack.write().pop_cell(); +} + +td::Ref MultisigWallet::create_init_data_fast(std::vector public_keys, int k) { + vm::Dictionary pk(8); + for (size_t i = 0; i < public_keys.size(); i++) { + auto key = pk.integer_key(td::make_refint(i), 8, false); + pk.set_builder(key.bits(), 8, vm::CellBuilder().store_bytes(public_keys[i].as_slice())); + } + + vm::CellBuilder cb; + cb.store_long(public_keys.size(), 8).store_long(k, 8).store_long(0, 64); + cb.ensure_throw(cb.store_maybe_ref(pk.get_root_cell())); + cb.ensure_throw(cb.store_maybe_ref({})); + return cb.finalize(); +} + +td::Ref MultisigWallet::merge_queries(td::Ref a, td::Ref b) const { + auto res = run_get_method("merge_queries", {a, b}); + return res.stack.write().pop_cell(); +} + +MultisigWallet::Mask MultisigWallet::to_mask(td::RefInt256 mask) const { + Mask res_mask; + for (size_t i = 0; i < res_mask.size(); i++) { + if (mask->get_bit(static_cast(i))) { + res_mask.set(i); + } + } + return res_mask; +} + +std::pair MultisigWallet::check_query_signatures(td::Ref a) const { + auto ans = run_get_method("check_query_signatures", {a}); + + auto mask = ans.stack.write().pop_int(); + auto cnt = ans.stack.write().pop_smallint_range(128); + return std::make_pair(cnt, to_mask(mask)); +} + +std::pair MultisigWallet::get_n_k() const { + auto ans = run_get_method("get_n_k"); + auto k = ans.stack.write().pop_smallint_range(128); + auto n = ans.stack.write().pop_smallint_range(128); + return std::make_pair(n, k); +} + +std::vector MultisigWallet::get_unsigned_messaged(int id) const { + SmartContract::Answer ans; + if (id == -1) { + ans = run_get_method("get_messages_unsigned"); + } else { + ans = run_get_method("get_messages_unsigned_by_id", {td::make_refint(id)}); + } + auto n_k = get_n_k(); + + auto cell = ans.stack.write().pop_maybe_cell(); + vm::Dictionary dict(std::move(cell), 64); + std::vector res; + dict.check_for_each([&](auto cs, auto ptr, auto ptr_bits) { + cs.write().skip_first(8); + Message message; + td::BigInt256 query_id; + query_id.import_bits(ptr, ptr_bits, false); + message.query_id = static_cast(query_id.to_long()); + message.signed_by = to_mask(cs.write().fetch_int256(n_k.first, false)); + message.message = cs.write().fetch_ref(); + res.push_back(std::move(message)); + return true; + }); + return res; +} +} // namespace ton diff --git a/crypto/smc-envelope/MultisigWallet.h b/crypto/smc-envelope/MultisigWallet.h new file mode 100644 index 000000000..10c6d0760 --- /dev/null +++ b/crypto/smc-envelope/MultisigWallet.h @@ -0,0 +1,64 @@ +#pragma once +#include "vm/cells.h" + +#include "SmartContract.h" +#include "Ed25519.h" + +#include + +namespace ton { +class MultisigWallet : public ton::SmartContract { + public: + MultisigWallet(State state) : SmartContract(std::move(state)) { + } + + using Mask = std::bitset<128>; + struct QueryState { + enum State { Unknown, NotReady, Sent } state = Unknown; + Mask mask; + }; + + class QueryBuilder { + public: + QueryBuilder(td::int64 query_id, td::Ref msg, int mode = 3); + void sign(td::int32 id, td::Ed25519::PrivateKey& pk); + + td::Ref create_inner() const; + td::Ref create(td::int32 id, td::Ed25519::PrivateKey& pk) const; + Mask get_mask() const { + return mask_; + } + + private: + vm::Ref dict_; + td::Ref msg_; + Mask mask_; + }; + + MultisigWallet* make_copy() const override { + return new MultisigWallet{state_}; + } + + // creation + static td::Ref create(td::Ref data = {}); + + td::Ref create_init_data(std::vector public_keys, int k) const; + static td::Ref create_init_data_fast(std::vector public_keys, int k); + + // get methods + int processed(td::uint64 query_id) const; + QueryState get_query_state(td::uint64 query_id) const; + std::vector get_public_keys() const; + td::Ref merge_queries(td::Ref a, td::Ref b) const; + std::pair check_query_signatures(td::Ref a) const; + std::pair get_n_k() const; + Mask to_mask(td::RefInt256 mask) const; + + struct Message { + td::uint64 query_id; + Mask signed_by; + td::Ref message; + }; + std::vector get_unsigned_messaged(int id = -1) const; +}; +} // namespace ton diff --git a/crypto/smc-envelope/SmartContract.cpp b/crypto/smc-envelope/SmartContract.cpp new file mode 100644 index 000000000..ea7642376 --- /dev/null +++ b/crypto/smc-envelope/SmartContract.cpp @@ -0,0 +1,188 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2017-2019 Telegram Systems LLP +*/ +#include "SmartContract.h" + +#include "GenericAccount.h" + +#include "block/block.h" +#include "block/block-auto.h" +#include "vm/cellslice.h" +#include "vm/cp0.h" +#include "vm/continuation.h" + +#include "td/utils/crypto.h" + +namespace ton { +namespace { +td::int32 get_method_id(td::Slice method_name) { + unsigned crc = td::crc16(method_name); + return (crc & 0xffff) | 0x10000; +} +td::Ref prepare_vm_stack(td::Ref body) { + td::Ref stack_ref{true}; + td::RefInt256 acc_addr{true}; + //CHECK(acc_addr.write().import_bits(account.addr.cbits(), 256)); + vm::Stack& stack = stack_ref.write(); + stack.push_int(td::RefInt256{true, 10000000000}); + stack.push_int(td::RefInt256{true, 10000000000}); + stack.push_cell(vm::CellBuilder().finalize()); + stack.push_cellslice(std::move(body)); + return stack_ref; +} + +td::Ref prepare_vm_c7() { + // TODO: fix initialization of c7 + td::BitArray<256> rand_seed; + rand_seed.as_slice().fill(0); + td::RefInt256 rand_seed_int{true}; + rand_seed_int.unique_write().import_bits(rand_seed.cbits(), 256, false); + auto tuple = vm::make_tuple_ref( + td::make_refint(0x076ef1ea), // [ magic:0x076ef1ea + td::make_refint(0), // actions:Integer + td::make_refint(0), // msgs_sent:Integer + td::make_refint(0), // unixtime:Integer + td::make_refint(0), // block_lt:Integer + td::make_refint(0), // trans_lt:Integer + std::move(rand_seed_int), // rand_seed:Integer + block::CurrencyCollection(1000000000).as_vm_tuple(), // balance_remaining:[Integer (Maybe Cell)] + vm::load_cell_slice_ref(vm::CellBuilder().finalize()) // myself:MsgAddressInt + //vm::StackEntry::maybe(td::Ref()) + ); // global_config:(Maybe Cell) ] = SmartContractInfo; + //LOG(DEBUG) << "SmartContractInfo initialized with " << vm::StackEntry(tuple).to_string(); + return vm::make_tuple_ref(std::move(tuple)); +} + +SmartContract::Answer run_smartcont(SmartContract::State state, td::Ref stack, td::Ref c7, + vm::GasLimits gas, bool ignore_chksig) { + auto gas_credit = gas.gas_credit; + vm::init_op_cp0(); + vm::DictionaryBase::get_empty_dictionary(); + + class Logger : public td::LogInterface { + public: + void append(td::CSlice slice) override { + res.append(slice.data(), slice.size()); + } + std::string res; + }; + Logger logger; + vm::VmLog log{&logger, td::LogOptions::plain()}; + + if (GET_VERBOSITY_LEVEL() >= VERBOSITY_NAME(DEBUG)) { + log.log_options.level = 4; + log.log_options.fix_newlines = true; + log.log_mask |= vm::VmLog::DumpStack; + } else { + log.log_options.level = 0; + log.log_mask = 0; + } + + SmartContract::Answer res; + vm::VmState vm{state.code, std::move(stack), gas, 1, state.data, log}; + vm.set_c7(std::move(c7)); + vm.set_chksig_always_succeed(ignore_chksig); + try { + res.code = ~vm.run(); + } catch (...) { + LOG(FATAL) << "catch unhandled exception"; + } + res.new_state = std::move(state); + res.stack = vm.get_stack_ref(); + gas = vm.get_gas_limits(); + res.gas_used = gas.gas_consumed(); + res.accepted = gas.gas_credit == 0; + res.success = (res.accepted && (unsigned)res.code <= 1); + if (GET_VERBOSITY_LEVEL() >= VERBOSITY_NAME(DEBUG)) { + LOG(DEBUG) << "VM log\n" << logger.res; + std::ostringstream os; + res.stack->dump(os); + LOG(DEBUG) << "VM stack:\n" << os.str(); + LOG(DEBUG) << "VM exit code: " << res.code; + LOG(DEBUG) << "VM accepted: " << res.accepted; + LOG(DEBUG) << "VM success: " << res.success; + } + if (res.success) { + res.new_state.data = vm.get_c4(); + res.actions = vm.get_d(5); + } + LOG_IF(ERROR, gas_credit != 0 && (res.accepted && !res.success)) + << "Accepted but failed with code " << res.code << "\n" + << res.gas_used << "\n"; + return res; +} +} // namespace + +td::Ref SmartContract::empty_slice() { + return vm::load_cell_slice_ref(vm::CellBuilder().finalize()); +} + +size_t SmartContract::code_size() const { + return vm::std_boc_serialize(state_.code).ok().size(); +} +size_t SmartContract::data_size() const { + return vm::std_boc_serialize(state_.data).ok().size(); +} + +block::StdAddress SmartContract::get_address(WorkchainId workchain_id) const { + return GenericAccount::get_address(workchain_id, get_init_state()); +} + +td::Ref SmartContract::get_init_state() const { + return GenericAccount::get_init_state(get_state().code, get_state().data); +} + +SmartContract::Answer SmartContract::run_method(Args args) { + if (!args.c7) { + args.c7 = prepare_vm_c7(); + } + if (!args.limits) { + args.limits = vm::GasLimits{(long long)0, (long long)1000000, (long long)10000}; + } + CHECK(args.stack); + CHECK(args.method_id); + args.stack.value().write().push_smallint(args.method_id.unwrap()); + auto res = + run_smartcont(get_state(), args.stack.unwrap(), args.c7.unwrap(), args.limits.unwrap(), args.ignore_chksig); + state_ = res.new_state; + return res; +} + +SmartContract::Answer SmartContract::run_get_method(Args args) const { + if (!args.c7) { + args.c7 = prepare_vm_c7(); + } + if (!args.limits) { + args.limits = vm::GasLimits{1000000}; + } + if (!args.stack) { + args.stack = td::Ref(true); + } + CHECK(args.method_id); + args.stack.value().write().push_smallint(args.method_id.unwrap()); + return run_smartcont(get_state(), args.stack.unwrap(), args.c7.unwrap(), args.limits.unwrap(), args.ignore_chksig); +} + +SmartContract::Answer SmartContract::run_get_method(td::Slice method, Args args) const { + return run_get_method(args.set_method_id(method)); +} + +SmartContract::Answer SmartContract::send_external_message(td::Ref cell, Args args) { + return run_method(args.set_stack(prepare_vm_stack(vm::load_cell_slice_ref(cell))).set_method_id(-1)); +} +} // namespace ton diff --git a/crypto/smc-envelope/SmartContract.h b/crypto/smc-envelope/SmartContract.h new file mode 100644 index 000000000..d58643619 --- /dev/null +++ b/crypto/smc-envelope/SmartContract.h @@ -0,0 +1,116 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2017-2019 Telegram Systems LLP +*/ +#pragma once + +#include "vm/cells.h" +#include "vm/stack.hpp" +#include "vm/continuation.h" + +#include "td/utils/optional.h" +#include "td/utils/crypto.h" + +#include "block/block.h" + +namespace ton { +class SmartContract : public td::CntObject { + static td::Ref empty_slice(); + + public: + struct State { + td::Ref code; + td::Ref data; + }; + + SmartContract(State state) : state_(std::move(state)) { + } + + struct Answer { + SmartContract::State new_state; + bool accepted; + bool success; + td::Ref stack; + td::Ref actions; + td::int32 code; + td::int64 gas_used; + }; + + struct Args { + td::optional method_id; + td::optional limits; + td::optional> c7; + td::optional> stack; + bool ignore_chksig{false}; + + Args() { + } + Args(std::initializer_list stack) + : stack(td::Ref(true, std::vector(std::move(stack)))) { + } + Args&& set_method_id(td::Slice method_name) { + unsigned crc = td::crc16(method_name); + return set_method_id((crc & 0xffff) | 0x10000); + } + Args&& set_method_id(td::int32 method_id) { + this->method_id = method_id; + return std::move(*this); + } + Args&& set_limits(vm::GasLimits limits) { + this->limits = std::move(limits); + return std::move(*this); + } + Args&& set_c7(td::Ref c7) { + this->c7 = std::move(c7); + return std::move(*this); + } + Args&& set_stack(std::vector stack) { + this->stack = td::Ref(true, std::move(stack)); + return std::move(*this); + } + Args&& set_stack(td::Ref stack) { + this->stack = std::move(stack); + return std::move(*this); + } + Args&& set_ignore_chksig(bool ignore_chksig) { + this->ignore_chksig = ignore_chksig; + return std::move(*this); + } + }; + + Answer run_method(Args args = {}); + Answer run_get_method(Args args = {}) const; + Answer run_get_method(td::Slice method, Args args = {}) const; + Answer send_external_message(td::Ref cell, Args args = {}); + + size_t code_size() const; + size_t data_size() const; + static td::Ref create(State state) { + return td::Ref{true, std::move(state)}; + } + + block::StdAddress get_address(WorkchainId workchain_id = basechainId) const; + td::Ref get_init_state() const; + + const State& get_state() const { + return state_; + } + + protected: + State state_; +}; +} // namespace ton diff --git a/crypto/smc-envelope/SmartContractCode.cpp b/crypto/smc-envelope/SmartContractCode.cpp new file mode 100644 index 000000000..c187131d7 --- /dev/null +++ b/crypto/smc-envelope/SmartContractCode.cpp @@ -0,0 +1,72 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2017-2019 Telegram Systems LLP +*/ +#include "SmartContractCode.h" + +#include "vm/boc.h" +#include + +#include "td/utils/base64.h" + +namespace ton { +namespace { +const auto& get_map() { + static auto map = [] { + class Cmp : public std::less<> { + public: + using is_transparent = void; + }; + std::map, Cmp> map; + auto with_tvm_code = [&](auto name, td::Slice code_str) { + map[name] = vm::std_boc_deserialize(td::base64_decode(code_str).move_as_ok()).move_as_ok(); + }; +#include "smartcont/auto/multisig-code.cpp" +#include "smartcont/auto/simple-wallet-ext-code.cpp" +#include "smartcont/auto/simple-wallet-code.cpp" +#include "smartcont/auto/wallet-code.cpp" + return map; + }(); + return map; +} +} // namespace + +td::Result> SmartContractCode::load(td::Slice name) { + auto& map = get_map(); + auto it = map.find(name); + if (it == map.end()) { + return td::Status::Error(PSLICE() << "Can't load td::refsecond; +} +td::Ref SmartContractCode::multisig() { + auto res = load("multisig").move_as_ok(); + return res; +} +td::Ref SmartContractCode::wallet() { + auto res = load("wallet").move_as_ok(); + return res; +} +td::Ref SmartContractCode::simple_wallet() { + auto res = load("simple-wallet").move_as_ok(); + return res; +} +td::Ref SmartContractCode::simple_wallet_ext() { + static auto res = load("simple-wallet-ext").move_as_ok(); + return res; +} +} // namespace ton diff --git a/crypto/smc-envelope/SmartContractCode.h b/crypto/smc-envelope/SmartContractCode.h new file mode 100644 index 000000000..059215e44 --- /dev/null +++ b/crypto/smc-envelope/SmartContractCode.h @@ -0,0 +1,30 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2017-2019 Telegram Systems LLP +*/ +#include "vm/cells.h" + +namespace ton { +class SmartContractCode { + public: + static td::Result> load(td::Slice name); + static td::Ref multisig(); + static td::Ref wallet(); + static td::Ref simple_wallet(); + static td::Ref simple_wallet_ext(); +}; +} // namespace ton diff --git a/crypto/smc-envelope/TestGiver.cpp b/crypto/smc-envelope/TestGiver.cpp new file mode 100644 index 000000000..2d44d7307 --- /dev/null +++ b/crypto/smc-envelope/TestGiver.cpp @@ -0,0 +1,62 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2017-2019 Telegram Systems LLP +*/ +#include "TestGiver.h" +#include "GenericAccount.h" + +#include "td/utils/base64.h" + +namespace ton { +const block::StdAddress& TestGiver::address() noexcept { + static block::StdAddress res = + block::StdAddress::parse("kf_8uRo6OBbQ97jCx2EIuKm8Wmt6Vb15-KsQHFLbKSMiYIny").move_as_ok(); + //static block::StdAddress res = + //block::StdAddress::parse("kf9tswzQaryeJ4aAYLy_phLhx4afF1aEvpUVak-2BuA0CmZi").move_as_ok(); + return res; +} + +vm::CellHash TestGiver::get_init_code_hash() noexcept { + return vm::CellHash::from_slice(td::base64_decode("wDkZp0yR4xo+9+BnuAPfGVjBzK6FPzqdv2DwRq3z3KE=").move_as_ok()); + //return vm::CellHash::from_slice(td::base64_decode("YV/IANhoI22HVeatFh6S5LbCHp+5OilARfzW+VQPZgQ=").move_as_ok()); +} + +td::Ref TestGiver::make_a_gift_message(td::uint32 seqno, td::uint64 gramms, td::Slice message, + const block::StdAddress& dest_address) noexcept { + vm::CellBuilder cb; + GenericAccount::store_int_message(cb, dest_address, gramms); + cb.store_bytes("\0\0\0\0", 4); + vm::CellString::store(cb, message, 35 * 8).ensure(); + auto message_inner = cb.finalize(); + return vm::CellBuilder().store_long(seqno, 32).store_long(1, 8).store_ref(message_inner).finalize(); +} + +td::Result TestGiver::get_seqno() const { + return TRY_VM(get_seqno_or_throw()); +} + +td::Result TestGiver::get_seqno_or_throw() const { + if (state_.data.is_null()) { + return 0; + } + auto seqno = vm::load_cell_slice(state_.data).fetch_ulong(32); + if (seqno == vm::CellSlice::fetch_ulong_eof) { + return td::Status::Error("Failed to parse seq_no"); + } + return static_cast(seqno); +} +} // namespace ton diff --git a/crypto/smc-envelope/TestGiver.h b/crypto/smc-envelope/TestGiver.h new file mode 100644 index 000000000..210b69124 --- /dev/null +++ b/crypto/smc-envelope/TestGiver.h @@ -0,0 +1,39 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2017-2019 Telegram Systems LLP +*/ +#pragma once +#include "SmartContract.h" +#include "block/block.h" +#include "vm/cells/CellString.h" +namespace ton { +class TestGiver : public SmartContract { + public: + explicit TestGiver(State state) : ton::SmartContract(std::move(state)) { + } + static constexpr unsigned max_message_size = vm::CellString::max_bytes; + static const block::StdAddress& address() noexcept; + static vm::CellHash get_init_code_hash() noexcept; + static td::Ref make_a_gift_message(td::uint32 seqno, td::uint64 gramms, td::Slice message, + const block::StdAddress& dest_address) noexcept; + + td::Result get_seqno() const; + + private: + td::Result get_seqno_or_throw() const; +}; +} // namespace ton diff --git a/crypto/smc-envelope/TestWallet.cpp b/crypto/smc-envelope/TestWallet.cpp new file mode 100644 index 000000000..1edf59f91 --- /dev/null +++ b/crypto/smc-envelope/TestWallet.cpp @@ -0,0 +1,91 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . +Copyright 2017-2019 Telegram Systems LLP +*/ +#include "TestWallet.h" +#include "GenericAccount.h" + +#include "vm/boc.h" +#include "td/utils/base64.h" + +namespace ton { +td::Ref TestWallet::get_init_state(const td::Ed25519::PublicKey& public_key) noexcept { + auto code = get_init_code(); + auto data = get_init_data(public_key); + return GenericAccount::get_init_state(std::move(code), std::move(data)); +} + +td::Ref TestWallet::get_init_message(const td::Ed25519::PrivateKey& private_key) noexcept { + std::string seq_no(4, 0); + auto signature = + private_key.sign(vm::CellBuilder().store_bytes(seq_no).finalize()->get_hash().as_slice()).move_as_ok(); + return vm::CellBuilder().store_bytes(signature).store_bytes(seq_no).finalize(); +} + +td::Ref TestWallet::make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 seqno, + td::int64 gramms, td::Slice message, + const block::StdAddress& dest_address) noexcept { + td::int32 send_mode = 3; + if (gramms == -1) { + gramms = 0; + send_mode += 128; + } + vm::CellBuilder cb; + GenericAccount::store_int_message(cb, dest_address, gramms); + cb.store_bytes("\0\0\0\0", 4); + vm::CellString::store(cb, message, 35 * 8).ensure(); + auto message_inner = cb.finalize(); + auto message_outer = + vm::CellBuilder().store_long(seqno, 32).store_long(send_mode, 8).store_ref(message_inner).finalize(); + auto signature = private_key.sign(message_outer->get_hash().as_slice()).move_as_ok(); + return vm::CellBuilder().store_bytes(signature).append_cellslice(vm::load_cell_slice(message_outer)).finalize(); +} + +td::Ref TestWallet::get_init_code() noexcept { + static auto res = [] { + auto serialized_code = td::base64_decode( + "te6ccgEEAQEAAAAAUwAAov8AIN0gggFMl7qXMO1E0NcLH+Ck8mCBAgDXGCDXCx/tRNDTH9P/" + "0VESuvKhIvkBVBBE+RDyovgAAdMfMSDXSpbTB9QC+wDe0aTIyx/L/8ntVA==") + .move_as_ok(); + return vm::std_boc_deserialize(serialized_code).move_as_ok(); + }(); + return res; +} + +vm::CellHash TestWallet::get_init_code_hash() noexcept { + return get_init_code()->get_hash(); +} + +td::Ref TestWallet::get_init_data(const td::Ed25519::PublicKey& public_key) noexcept { + return vm::CellBuilder().store_long(0, 32).store_bytes(public_key.as_octet_string()).finalize(); +} + +td::Result TestWallet::get_seqno() const { + return TRY_VM(get_seqno_or_throw()); +} + +td::Result TestWallet::get_seqno_or_throw() const { + if (state_.data.is_null()) { + return 0; + } + auto seqno = vm::load_cell_slice(state_.data).fetch_ulong(32); + if (seqno == vm::CellSlice::fetch_ulong_eof) { + return td::Status::Error("Failed to parse seq_no"); + } + return static_cast(seqno); +} + +} // namespace ton diff --git a/crypto/smc-envelope/TestWallet.h b/crypto/smc-envelope/TestWallet.h new file mode 100644 index 000000000..161aef58b --- /dev/null +++ b/crypto/smc-envelope/TestWallet.h @@ -0,0 +1,48 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2017-2019 Telegram Systems LLP +*/ +#pragma once + +#include "smc-envelope/SmartContract.h" +#include "vm/cells.h" +#include "Ed25519.h" +#include "block/block.h" +#include "vm/cells/CellString.h" + +namespace ton { +class TestWallet : public ton::SmartContract { + public: + explicit TestWallet(State state) : ton::SmartContract(std::move(state)) { + } + static constexpr unsigned max_message_size = vm::CellString::max_bytes; + static td::Ref get_init_state(const td::Ed25519::PublicKey& public_key) noexcept; + static td::Ref get_init_message(const td::Ed25519::PrivateKey& private_key) noexcept; + static td::Ref make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 seqno, + td::int64 gramms, td::Slice message, + const block::StdAddress& dest_address) noexcept; + + static td::Ref get_init_code() noexcept; + static vm::CellHash get_init_code_hash() noexcept; + static td::Ref get_init_data(const td::Ed25519::PublicKey& public_key) noexcept; + + td::Result get_seqno() const; + + private: + td::Result get_seqno_or_throw() const; +}; +} // namespace ton diff --git a/crypto/smc-envelope/Wallet.cpp b/crypto/smc-envelope/Wallet.cpp new file mode 100644 index 000000000..61958ed95 --- /dev/null +++ b/crypto/smc-envelope/Wallet.cpp @@ -0,0 +1,100 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2017-2019 Telegram Systems LLP +*/ +#include "Wallet.h" +#include "GenericAccount.h" + +#include "vm/boc.h" +#include "vm/cells/CellString.h" +#include "td/utils/base64.h" + +#include + +namespace ton { +td::Ref Wallet::get_init_state(const td::Ed25519::PublicKey& public_key) noexcept { + auto code = get_init_code(); + auto data = get_init_data(public_key); + return GenericAccount::get_init_state(std::move(code), std::move(data)); +} + +td::Ref Wallet::get_init_message(const td::Ed25519::PrivateKey& private_key) noexcept { + td::uint32 seqno = 0; + td::uint32 valid_until = std::numeric_limits::max(); + auto signature = + private_key + .sign(vm::CellBuilder().store_long(seqno, 32).store_long(valid_until, 32).finalize()->get_hash().as_slice()) + .move_as_ok(); + return vm::CellBuilder().store_bytes(signature).store_long(seqno, 32).store_long(valid_until, 32).finalize(); +} + +td::Ref Wallet::make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 seqno, + td::uint32 valid_until, td::int64 gramms, td::Slice message, + const block::StdAddress& dest_address) noexcept { + td::int32 send_mode = 3; + if (gramms == -1) { + gramms = 0; + send_mode += 128; + } + vm::CellBuilder cb; + GenericAccount::store_int_message(cb, dest_address, gramms); + cb.store_bytes("\0\0\0\0", 4); + vm::CellString::store(cb, message, 35 * 8).ensure(); + auto message_inner = cb.finalize(); + + auto message_outer = vm::CellBuilder() + .store_long(seqno, 32) + .store_long(valid_until, 32) + .store_long(send_mode, 8) + .store_ref(message_inner) + .finalize(); + auto signature = private_key.sign(message_outer->get_hash().as_slice()).move_as_ok(); + return vm::CellBuilder().store_bytes(signature).append_cellslice(vm::load_cell_slice(message_outer)).finalize(); +} + +td::Ref Wallet::get_init_code() noexcept { + static auto res = [] { + auto serialized_code = td::base64_decode( + "te6ccgEEAQEAAAAAVwAAqv8AIN0gggFMl7qXMO1E0NcLH+Ck8mCDCNcYINMf0x8B+CO78mPtRNDTH9P/" + "0VExuvKhA/kBVBBC+RDyovgAApMg10qW0wfUAvsA6NGkyMsfy//J7VQ=") + .move_as_ok(); + return vm::std_boc_deserialize(serialized_code).move_as_ok(); + }(); + return res; +} + +vm::CellHash Wallet::get_init_code_hash() noexcept { + return get_init_code()->get_hash(); +} + +td::Ref Wallet::get_init_data(const td::Ed25519::PublicKey& public_key) noexcept { + return vm::CellBuilder().store_long(0, 32).store_bytes(public_key.as_octet_string()).finalize(); +} + +td::Result Wallet::get_seqno() const { + return TRY_VM(get_seqno_or_throw()); +} + +td::Result Wallet::get_seqno_or_throw() const { + if (state_.data.is_null()) { + return 0; + } + //FIXME use get method + return static_cast(vm::load_cell_slice(state_.data).fetch_ulong(32)); +} + +} // namespace ton diff --git a/crypto/smc-envelope/Wallet.h b/crypto/smc-envelope/Wallet.h new file mode 100644 index 000000000..7cd33c81d --- /dev/null +++ b/crypto/smc-envelope/Wallet.h @@ -0,0 +1,48 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2017-2019 Telegram Systems LLP +*/ +#pragma once + +#include "smc-envelope/SmartContract.h" +#include "vm/cells.h" +#include "Ed25519.h" +#include "block/block.h" +#include "vm/cells/CellString.h" + +namespace ton { +class Wallet : ton::SmartContract { + public: + explicit Wallet(State state) : ton::SmartContract(std::move(state)) { + } + static constexpr unsigned max_message_size = vm::CellString::max_bytes; + static td::Ref get_init_state(const td::Ed25519::PublicKey& public_key) noexcept; + static td::Ref get_init_message(const td::Ed25519::PrivateKey& private_key) noexcept; + static td::Ref make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 seqno, + td::uint32 valid_until, td::int64 gramms, td::Slice message, + const block::StdAddress& dest_address) noexcept; + + static td::Ref get_init_code() noexcept; + static vm::CellHash get_init_code_hash() noexcept; + static td::Ref get_init_data(const td::Ed25519::PublicKey& public_key) noexcept; + + td::Result get_seqno() const; + + private: + td::Result get_seqno_or_throw() const; +}; +} // namespace ton diff --git a/crypto/test/Ed25519.cpp b/crypto/test/Ed25519.cpp index 4d943abbf..5c263acf6 100644 --- a/crypto/test/Ed25519.cpp +++ b/crypto/test/Ed25519.cpp @@ -21,8 +21,12 @@ #include "td/utils/misc.h" #include "td/utils/Slice.h" #include "td/utils/tests.h" +#include "td/utils/JsonBuilder.h" + +#include "wycheproof.h" #include +#include unsigned char fixed_privkey[32] = "abacabadabacabaeabacabadabacaba"; unsigned char fixed_pubkey[32] = {0x6f, 0x9e, 0x5b, 0xde, 0xce, 0x87, 0x21, 0xeb, 0x57, 0x37, 0xfb, @@ -142,3 +146,74 @@ TEST(Crypto, ed25519) { assert(!std::memcmp(secret12, secret21, 32)); */ } + +TEST(Crypto, wycheproof) { + std::vector> bad_tests; + auto json_str = wycheproof_ed25519(); + auto value = td::json_decode(json_str).move_as_ok(); + auto &root = value.get_object(); + auto test_groups_o = get_json_object_field(root, "testGroups", td::JsonValue::Type::Array, false).move_as_ok(); + auto &test_groups = test_groups_o.get_array(); + auto from_hexc = [](char c) { + if (c >= '0' && c <= '9') { + return c - '0'; + } + return c - 'a' + 10; + }; + auto from_hex = [&](td::Slice s) { + CHECK(s.size() % 2 == 0); + std::string res(s.size() / 2, 0); + for (size_t i = 0; i < s.size(); i += 2) { + res[i / 2] = char(from_hexc(s[i]) * 16 + from_hexc(s[i + 1])); + } + return res; + }; + for (auto &test_o : test_groups) { + auto &test = test_o.get_object(); + auto key_o = get_json_object_field(test, "key", td::JsonValue::Type::Object, false).move_as_ok(); + auto sk_str = td::get_json_object_string_field(key_o.get_object(), "sk", false).move_as_ok(); + auto pk_str = td::get_json_object_string_field(key_o.get_object(), "pk", false).move_as_ok(); + auto pk = td::Ed25519::PublicKey(td::SecureString(from_hex(pk_str))); + auto sk = td::Ed25519::PrivateKey(td::SecureString(from_hex(sk_str))); + CHECK(sk.get_public_key().move_as_ok().as_octet_string().as_slice() == pk.as_octet_string().as_slice()); + + //auto key = + //td::Ed25519::PrivateKey::from_pem( + //td::SecureString(td::get_json_object_string_field(test, "keyPem", false).move_as_ok()), td::SecureString()) + //.move_as_ok(); + + auto tests_o = get_json_object_field(test, "tests", td::JsonValue::Type::Array, false).move_as_ok(); + auto &tests = tests_o.get_array(); + for (auto &test_o : tests) { + auto &test = test_o.get_object(); + auto id = td::get_json_object_string_field(test, "tcId", false).move_as_ok(); + auto comment = td::get_json_object_string_field(test, "comment", false).move_as_ok(); + auto sig = from_hex(td::get_json_object_string_field(test, "sig", false).move_as_ok()); + auto msg = from_hex(td::get_json_object_string_field(test, "msg", false).move_as_ok()); + auto result = td::get_json_object_string_field(test, "result", false).move_as_ok(); + auto has_result = pk.verify_signature(msg, sig).is_ok() ? "valid" : "invalid"; + if (result != has_result) { + bad_tests.push_back({id, comment}); + } + } + } + if (bad_tests.empty()) { + return; + } + LOG(ERROR) << "FAILED: " << td::format::as_array(bad_tests); +} + +TEST(Crypto, almost_zero) { + td::SecureString pub(32); + td::SecureString sig(64); + td::SecureString msg(1); + + pub.as_mutable_slice().ubegin()[31] = static_cast(128); + for (td::int32 j = 0; j < 256; j++) { + msg.as_mutable_slice()[0] = (char)j; + if (td::Ed25519::PublicKey(pub.copy()).verify_signature(msg, sig).is_ok()) { + LOG(ERROR) << "FAILED: " << j; + break; + } + } +} diff --git a/crypto/test/test-db.cpp b/crypto/test/test-db.cpp index 28f05ff8e..148a18549 100644 --- a/crypto/test/test-db.cpp +++ b/crypto/test/test-db.cpp @@ -51,34 +51,6 @@ #include -struct Step { - std::function func; - td::uint32 weight; -}; -class RandomSteps { - public: - RandomSteps(std::vector steps) : steps_(std::move(steps)) { - for (auto &step : steps_) { - steps_sum_ += step.weight; - } - } - template - void step(Random &rnd) { - auto w = rnd() % steps_sum_; - for (auto &step : steps_) { - if (w < step.weight) { - step.func(); - break; - } - w -= step.weight; - } - } - - private: - std::vector steps_; - td::int32 steps_sum_ = 0; -}; - namespace vm { std::vector do_get_serialization_modes() { @@ -879,7 +851,7 @@ TEST(TonDb, DynamicBoc2) { VLOG(boc) << " OK"; }; - RandomSteps steps({{new_root, 10}, {delete_root, 9}, {commit, 2}, {reset, 1}}); + td::RandomSteps steps({{new_root, 10}, {delete_root, 9}, {commit, 2}, {reset, 1}}); while (first_root_id != total_roots) { VLOG(boc) << first_root_id << " " << last_root_id << " " << kv->count("").ok(); steps.step(rnd); @@ -1732,7 +1704,7 @@ TEST(TonDb, CompactArray) { fast_array = vm::FastCompactArray(size); }; - RandomSteps steps({{reset_array, 1}, {set_value, 1000}, {validate, 10}, {validate_full, 2}, {flush_to_db, 1}}); + td::RandomSteps steps({{reset_array, 1}, {set_value, 1000}, {validate, 10}, {validate_full, 2}, {flush_to_db, 1}}); for (size_t t = 0; t < 100000; t++) { if (t % 10000 == 0) { LOG(ERROR) << t; diff --git a/crypto/test/test-smartcont.cpp b/crypto/test/test-smartcont.cpp new file mode 100644 index 000000000..f9dd86522 --- /dev/null +++ b/crypto/test/test-smartcont.cpp @@ -0,0 +1,524 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2017-2019 Telegram Systems LLP +*/ +#include "vm/dict.h" +#include "common/bigint.hpp" + +#include "Ed25519.h" + +#include "block/block.h" + +#include "fift/Fift.h" +#include "fift/words.h" +#include "fift/utils.h" + +#include "smc-envelope/GenericAccount.h" +#include "smc-envelope/MultisigWallet.h" +#include "smc-envelope/SmartContract.h" +#include "smc-envelope/SmartContractCode.h" +#include "smc-envelope/TestGiver.h" +#include "smc-envelope/TestWallet.h" +#include "smc-envelope/Wallet.h" + +#include "td/utils/base64.h" +#include "td/utils/crypto.h" +#include "td/utils/Random.h" +#include "td/utils/tests.h" +#include "td/utils/ScopeGuard.h" +#include "td/utils/StringBuilder.h" +#include "td/utils/Timer.h" +#include "td/utils/PathView.h" +#include "td/utils/filesystem.h" +#include "td/utils/port/path.h" + +#include +#include +#include + +std::string current_dir() { + return td::PathView(td::realpath(__FILE__).move_as_ok()).parent_dir().str(); +} + +std::string load_source(std::string name) { + return td::read_file_str(current_dir() + "../../crypto/" + name).move_as_ok(); +} + +td::Ref get_test_wallet_source() { + std::string code = R"ABCD( +SETCP0 DUP IFNOTRET // return if recv_internal +DUP 85143 INT EQUAL IFJMP:<{ // "seqno" get-method + DROP c4 PUSHCTR CTOS 32 PLDU // cnt +}> +INC 32 THROWIF // fail unless recv_external +512 INT LDSLICEX DUP 32 PLDU // sign cs cnt +c4 PUSHCTR CTOS 32 LDU 256 LDU ENDS // sign cs cnt cnt' pubk +s1 s2 XCPU // sign cs cnt pubk cnt' cnt +EQUAL 33 THROWIFNOT // ( seqno mismatch? ) +s2 PUSH HASHSU // sign cs cnt pubk hash +s0 s4 s4 XC2PU // pubk cs cnt hash sign pubk +CHKSIGNU // pubk cs cnt ? +34 THROWIFNOT // signature mismatch +ACCEPT +SWAP 32 LDU NIP +DUP SREFS IF:<{ + // 3 INT 35 LSHIFT# 3 INT RAWRESERVE // reserve all but 103 Grams from the balance + 8 LDU LDREF // pubk cnt mode msg cs + s0 s2 XCHG SENDRAWMSG // pubk cnt cs ; ( message sent ) +}> +ENDS +INC NEWC 32 STU 256 STU ENDC c4 POPCTR +)ABCD"; + return fift::compile_asm(code).move_as_ok(); +} + +td::Ref get_wallet_source() { + std::string code = R"ABCD( +SETCP0 DUP IFNOTRET // return if recv_internal + DUP 85143 INT EQUAL IFJMP:<{ // "seqno" get-method + DROP c4 PUSHCTR CTOS 32 PLDU // cnt + }> + INC 32 THROWIF // fail unless recv_external + 9 PUSHPOW2 LDSLICEX DUP 32 LDU 32 LDU // signature in_msg msg_seqno valid_until cs + SWAP NOW LEQ 35 THROWIF // signature in_msg msg_seqno cs + c4 PUSH CTOS 32 LDU 256 LDU ENDS // signature in_msg msg_seqno cs stored_seqno public_key + s3 s1 XCPU // signature in_msg public_key cs stored_seqno msg_seqno stored_seqno + EQUAL 33 THROWIFNOT // signature in_msg public_key cs stored_seqno + s0 s3 XCHG HASHSU // signature stored_seqno public_key cs hash + s0 s4 s2 XC2PU CHKSIGNU 34 THROWIFNOT // cs stored_seqno public_key + ACCEPT + s0 s2 XCHG // public_key stored_seqno cs + WHILE:<{ + DUP SREFS // public_key stored_seqno cs _40 + }>DO<{ // public_key stored_seqno cs + // 3 INT 35 LSHIFT# 3 INT RAWRESERVE // reserve all but 103 Grams from the balance + 8 LDU LDREF s0 s2 XCHG // public_key stored_seqno cs _45 mode + SENDRAWMSG // public_key stored_seqno cs + }> + ENDS INC // public_key seqno' + NEWC 32 STU 256 STU ENDC c4 POP +)ABCD"; + return fift::compile_asm(code).move_as_ok(); +} + +TEST(Tonlib, TestWallet) { + LOG(ERROR) << td::base64_encode(std_boc_serialize(get_test_wallet_source()).move_as_ok()); + CHECK(get_test_wallet_source()->get_hash() == ton::TestWallet::get_init_code()->get_hash()); + auto fift_output = fift::mem_run_fift(load_source("smartcont/new-wallet.fif"), {"aba", "0"}).move_as_ok(); + + auto new_wallet_pk = fift_output.source_lookup.read_file("new-wallet.pk").move_as_ok().data; + auto new_wallet_query = fift_output.source_lookup.read_file("new-wallet-query.boc").move_as_ok().data; + auto new_wallet_addr = fift_output.source_lookup.read_file("new-wallet.addr").move_as_ok().data; + + td::Ed25519::PrivateKey priv_key{td::SecureString{new_wallet_pk}}; + auto pub_key = priv_key.get_public_key().move_as_ok(); + auto init_state = ton::TestWallet::get_init_state(pub_key); + auto init_message = ton::TestWallet::get_init_message(priv_key); + auto address = ton::GenericAccount::get_address(0, init_state); + + CHECK(address.addr.as_slice() == td::Slice(new_wallet_addr).substr(0, 32)); + + td::Ref res = ton::GenericAccount::create_ext_message(address, init_state, init_message); + + LOG(ERROR) << "-------"; + vm::load_cell_slice(res).print_rec(std::cerr); + LOG(ERROR) << "-------"; + vm::load_cell_slice(vm::std_boc_deserialize(new_wallet_query).move_as_ok()).print_rec(std::cerr); + CHECK(vm::std_boc_deserialize(new_wallet_query).move_as_ok()->get_hash() == res->get_hash()); + + fift_output.source_lookup.write_file("/main.fif", load_source("smartcont/wallet.fif")).ensure(); + auto dest = block::StdAddress::parse("Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX").move_as_ok(); + fift_output = fift::mem_run_fift(std::move(fift_output.source_lookup), + {"aba", "new-wallet", "Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX", "123", + "321", "-C", "TEST"}) + .move_as_ok(); + auto wallet_query = fift_output.source_lookup.read_file("wallet-query.boc").move_as_ok().data; + auto gift_message = ton::GenericAccount::create_ext_message( + address, {}, ton::TestWallet::make_a_gift_message(priv_key, 123, 321000000000ll, "TEST", dest)); + LOG(ERROR) << "-------"; + vm::load_cell_slice(gift_message).print_rec(std::cerr); + LOG(ERROR) << "-------"; + vm::load_cell_slice(vm::std_boc_deserialize(wallet_query).move_as_ok()).print_rec(std::cerr); + CHECK(vm::std_boc_deserialize(wallet_query).move_as_ok()->get_hash() == gift_message->get_hash()); +} + +td::Ref get_wallet_source_fc() { + return fift::compile_asm(load_source("smartcont/wallet-code.fif"), "", false).move_as_ok(); +} + +TEST(Tonlib, Wallet) { + LOG(ERROR) << td::base64_encode(std_boc_serialize(get_wallet_source()).move_as_ok()); + CHECK(get_wallet_source()->get_hash() == ton::Wallet::get_init_code()->get_hash()); + + auto fift_output = fift::mem_run_fift(load_source("smartcont/new-wallet-v2.fif"), {"aba", "0"}).move_as_ok(); + + auto new_wallet_pk = fift_output.source_lookup.read_file("new-wallet.pk").move_as_ok().data; + auto new_wallet_query = fift_output.source_lookup.read_file("new-wallet-query.boc").move_as_ok().data; + auto new_wallet_addr = fift_output.source_lookup.read_file("new-wallet.addr").move_as_ok().data; + + td::Ed25519::PrivateKey priv_key{td::SecureString{new_wallet_pk}}; + auto pub_key = priv_key.get_public_key().move_as_ok(); + auto init_state = ton::Wallet::get_init_state(pub_key); + auto init_message = ton::Wallet::get_init_message(priv_key); + auto address = ton::GenericAccount::get_address(0, init_state); + + CHECK(address.addr.as_slice() == td::Slice(new_wallet_addr).substr(0, 32)); + + td::Ref res = ton::GenericAccount::create_ext_message(address, init_state, init_message); + + LOG(ERROR) << "-------"; + vm::load_cell_slice(res).print_rec(std::cerr); + LOG(ERROR) << "-------"; + vm::load_cell_slice(vm::std_boc_deserialize(new_wallet_query).move_as_ok()).print_rec(std::cerr); + CHECK(vm::std_boc_deserialize(new_wallet_query).move_as_ok()->get_hash() == res->get_hash()); + + fift_output.source_lookup.write_file("/main.fif", load_source("smartcont/wallet-v2.fif")).ensure(); + class ZeroOsTime : public fift::OsTime { + public: + td::uint32 now() override { + return 0; + } + }; + fift_output.source_lookup.set_os_time(std::make_unique()); + auto dest = block::StdAddress::parse("Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX").move_as_ok(); + fift_output = + fift::mem_run_fift(std::move(fift_output.source_lookup), + {"aba", "new-wallet", "Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX", "123", "321"}) + .move_as_ok(); + auto wallet_query = fift_output.source_lookup.read_file("wallet-query.boc").move_as_ok().data; + auto gift_message = ton::GenericAccount::create_ext_message( + address, {}, ton::Wallet::make_a_gift_message(priv_key, 123, 60, 321000000000ll, "TESTv2", dest)); + LOG(ERROR) << "-------"; + vm::load_cell_slice(gift_message).print_rec(std::cerr); + LOG(ERROR) << "-------"; + vm::load_cell_slice(vm::std_boc_deserialize(wallet_query).move_as_ok()).print_rec(std::cerr); + CHECK(vm::std_boc_deserialize(wallet_query).move_as_ok()->get_hash() == gift_message->get_hash()); +} + +TEST(Tonlib, TestGiver) { + auto address = + block::StdAddress::parse("-1:60c04141c6a7b96d68615e7a91d265ad0f3a9a922e9ae9c901d4fa83f5d3c0d0").move_as_ok(); + LOG(ERROR) << address.bounceable; + auto fift_output = fift::mem_run_fift(load_source("smartcont/testgiver.fif"), + {"aba", address.rserialize(), "0", "6.666", "wallet-query"}) + .move_as_ok(); + LOG(ERROR) << fift_output.output; + + auto wallet_query = fift_output.source_lookup.read_file("wallet-query.boc").move_as_ok().data; + + auto res = ton::GenericAccount::create_ext_message( + ton::TestGiver::address(), {}, + ton::TestGiver::make_a_gift_message(0, 1000000000ll * 6666 / 1000, "GIFT", address)); + vm::CellSlice(vm::NoVm(), res).print_rec(std::cerr); + CHECK(vm::std_boc_deserialize(wallet_query).move_as_ok()->get_hash() == res->get_hash()); +} + +class SimpleWallet : public ton::SmartContract { + public: + SimpleWallet(State state) : SmartContract(std::move(state)) { + } + + const State& get_state() const { + return state_; + } + SimpleWallet* make_copy() const override { + return new SimpleWallet{state_}; + } + + static td::Ref create_empty() { + return td::Ref(true, State{ton::SmartContractCode::simple_wallet_ext(), {}}); + } + static td::Ref create(td::Ref data) { + return td::Ref(true, State{ton::SmartContractCode::simple_wallet_ext(), std::move(data)}); + } + static td::Ref create_fast(td::Ref data) { + return td::Ref(true, State{ton::SmartContractCode::simple_wallet(), std::move(data)}); + } + + td::int32 seqno() const { + auto res = run_get_method("seqno"); + return res.stack.write().pop_smallint_range(1000000000); + } + + td::Ref create_init_state(td::Slice public_key) const { + td::RefInt256 pk{true}; + pk.write().import_bytes(public_key.ubegin(), public_key.size(), false); + auto res = run_get_method("create_init_state", {pk}); + return res.stack.write().pop_cell(); + } + + td::Ref prepare_send_message(td::Ref msg, td::int8 mode = 3) const { + auto res = run_get_method("prepare_send_message", {td::make_refint(mode), msg}); + return res.stack.write().pop_cell(); + } + + static td::Ref sign_message(vm::Ref body, const td::Ed25519::PrivateKey& pk) { + auto signature = pk.sign(body->get_hash().as_slice()).move_as_ok(); + return vm::CellBuilder().store_bytes(signature.as_slice()).append_cellslice(vm::load_cell_slice(body)).finalize(); + } +}; + +TEST(Smartcon, Simple) { + auto private_key = td::Ed25519::generate_private_key().move_as_ok(); + auto public_key = private_key.get_public_key().move_as_ok().as_octet_string(); + + auto w_lib = SimpleWallet::create_empty(); + auto init_data = w_lib->create_init_state(public_key); + + auto w = SimpleWallet::create(init_data); + LOG(ERROR) << w->code_size(); + auto fw = SimpleWallet::create_fast(init_data); + LOG(ERROR) << fw->code_size(); + LOG(ERROR) << w->seqno(); + + for (int i = 0; i < 20; i++) { + auto msg = w->sign_message(w->prepare_send_message(vm::CellBuilder().finalize()), private_key); + w.write().send_external_message(msg); + fw.write().send_external_message(msg); + } + ASSERT_EQ(20, w->seqno()); + CHECK(w->get_state().data->get_hash() == fw->get_state().data->get_hash()); +} + +namespace std { // ouch +bool operator<(const ton::MultisigWallet::Mask& a, const ton::MultisigWallet::Mask& b) { + for (size_t i = 0; i < a.size(); i++) { + if (a[i] != b[i]) { + return a[i] < b[i]; + } + } + return false; +} +} // namespace std + +TEST(Smartcon, Multisig) { + auto ms_lib = ton::MultisigWallet::create(); + + int n = 100; + int k = 99; + std::vector keys; + for (int i = 0; i < n; i++) { + keys.push_back(td::Ed25519::generate_private_key().move_as_ok()); + } + auto init_state = ms_lib->create_init_data( + td::transform(keys, [](auto& key) { return key.get_public_key().ok().as_octet_string(); }), k); + auto ms = ton::MultisigWallet::create(init_state); + + td::uint64 query_id = 123; + ton::MultisigWallet::QueryBuilder qb(query_id, vm::CellBuilder().finalize()); + // first empty query (init) + CHECK(ms.write().send_external_message(vm::CellBuilder().finalize()).code == 0); + // first empty query + CHECK(ms.write().send_external_message(vm::CellBuilder().finalize()).code > 0); + + for (int i = 0; i < 10; i++) { + auto query = qb.create(i, keys[i]); + auto ans = ms.write().send_external_message(query); + LOG(INFO) << "CODE: " << ans.code; + LOG(INFO) << "GAS: " << ans.gas_used; + } + for (int i = 0; i + 1 < 50; i++) { + qb.sign(i, keys[i]); + } + auto query = qb.create(49, keys[49]); + + CHECK(ms->get_n_k() == std::make_pair(n, k)); + auto ans = ms.write().send_external_message(query); + LOG(INFO) << "CODE: " << ans.code; + LOG(INFO) << "GAS: " << ans.gas_used; + CHECK(ans.success); + ASSERT_EQ(0, ms->processed(query_id)); + CHECK(ms.write().send_external_message(query).code > 0); + ASSERT_EQ(0, ms->processed(query_id)); + + { + ton::MultisigWallet::QueryBuilder qb(query_id, vm::CellBuilder().finalize()); + for (int i = 50; i + 1 < 100; i++) { + qb.sign(i, keys[i]); + } + query = qb.create(99, keys[99]); + } + + ans = ms.write().send_external_message(query); + LOG(INFO) << "CODE: " << ans.code; + LOG(INFO) << "GAS: " << ans.gas_used; + ASSERT_EQ(-1, ms->processed(query_id)); +} + +TEST(Smartcont, MultisigStress) { + int n = 10; + int k = 5; + + std::vector keys; + for (int i = 0; i < n; i++) { + keys.push_back(td::Ed25519::generate_private_key().move_as_ok()); + } + auto public_keys = td::transform(keys, [](auto& key) { return key.get_public_key().ok().as_octet_string(); }); + auto ms_lib = ton::MultisigWallet::create(); + auto init_state_old = + ms_lib->create_init_data_fast(td::transform(public_keys, [](auto& key) { return key.copy(); }), k); + auto init_state = ms_lib->create_init_data(td::transform(public_keys, [](auto& key) { return key.copy(); }), k); + CHECK(init_state_old->get_hash() == init_state->get_hash()); + auto ms = ton::MultisigWallet::create(init_state); + CHECK(ms->get_public_keys() == public_keys); + + td::int32 now = 0; + td::int32 qid = 1; + using Mask = std::bitset<128>; + struct Query { + td::int64 id; + td::Ref message; + Mask signed_mask; + }; + + std::vector queries; + int max_queries = 300; + + td::Random::Xorshift128plus rnd(123); + + auto new_query = [&] { + if (qid > max_queries) { + return; + } + Query query; + query.id = (static_cast(now) << 32) | qid++; + query.message = vm::CellBuilder().store_bytes(td::rand_string('a', 'z', rnd.fast(0, 100))).finalize(); + queries.push_back(std::move(query)); + }; + + auto verify = [&] { + auto messages = ms->get_unsigned_messaged(); + std::set> s; + std::set> t; + + for (auto& m : messages) { + auto x = std::make_tuple(m.query_id, m.signed_by, m.message->get_hash().as_slice().str()); + s.insert(std::move(x)); + } + + for (auto& q : queries) { + if (q.signed_mask.none()) { + continue; + } + t.insert(std::make_tuple(q.id, q.signed_mask, q.message->get_hash().as_slice().str())); + } + ASSERT_EQ(t.size(), s.size()); + CHECK(s == t); + }; + + auto sign_query = [&](Query& query, Mask mask) { + auto qb = ton::MultisigWallet::QueryBuilder(query.id, query.message); + int first_i = -1; + for (int i = 0; i < (int)mask.size(); i++) { + if (mask.test(i)) { + if (first_i == -1) { + first_i = i; + } else { + qb.sign(i, keys[i]); + } + } + } + return qb.create(first_i, keys[first_i]); + }; + + auto send_signature = [&](td::Ref query) { + auto ans = ms.write().send_external_message(query); + LOG(ERROR) << "GAS: " << ans.gas_used; + return ans.code == 0; + }; + + auto is_ready = [&](Query& query) { return ms->processed(query.id) == -1; }; + + auto gen_query = [&](Query& query) { + auto x = rnd.fast(1, n); + Mask mask; + for (int t = 0; t < x; t++) { + mask.set(rnd() % n); + } + + auto signature = sign_query(query, mask); + return std::make_pair(signature, mask); + }; + + auto rand_sign = [&] { + if (queries.empty()) { + return; + } + + size_t query_i = rnd() % queries.size(); + auto& query = queries[query_i]; + + Mask mask; + td::Ref signature; + std::tie(signature, mask) = gen_query(query); + if (false && rnd() % 6 == 0) { + Mask mask2; + td::Ref signature2; + std::tie(signature2, mask2) = gen_query(query); + for (int i = 0; i < (int)keys.size(); i++) { + if (mask[i]) { + signature = ms->merge_queries(std::move(signature), std::move(signature2)); + break; + } + if (mask2[i]) { + signature = ms->merge_queries(std::move(signature2), std::move(signature)); + break; + } + } + //signature = ms->merge_queries(std::move(signature), std::move(signature2)); + mask |= mask2; + } + + int got_cnt; + Mask got_cnt_bits; + std::tie(got_cnt, got_cnt_bits) = ms->check_query_signatures(signature); + CHECK(mask == got_cnt_bits); + + bool expect_ok = true; + { + auto new_mask = mask & ~query.signed_mask; + expect_ok &= new_mask.any(); + for (size_t i = 0; i < mask.size(); i++) { + if (mask[i]) { + expect_ok &= new_mask[i]; + break; + } + } + } + + ASSERT_EQ(expect_ok, send_signature(std::move(signature))); + if (expect_ok) { + query.signed_mask |= mask; + } + auto expect_is_ready = query.signed_mask.count() >= (size_t)k; + auto state = ms->get_query_state(query.id); + ASSERT_EQ(expect_is_ready, (state.state == ton::MultisigWallet::QueryState::Sent)); + CHECK(expect_is_ready || state.mask == query.signed_mask); + ASSERT_EQ(expect_is_ready, is_ready(query)); + if (expect_is_ready) { + queries.erase(queries.begin() + query_i); + } + verify(); + }; + td::RandomSteps steps({{rand_sign, 2}, {new_query, 1}}); + while (!queries.empty() || qid <= max_queries) { + steps.step(rnd); + //LOG(ERROR) << ms->data_size(); + } + LOG(INFO) << "Final code size: " << ms->code_size(); + LOG(INFO) << "Final data size: " << ms->data_size(); +} diff --git a/crypto/test/wycheproof.h b/crypto/test/wycheproof.h new file mode 100644 index 000000000..182760acb --- /dev/null +++ b/crypto/test/wycheproof.h @@ -0,0 +1,1160 @@ +#pragma once + +#include + +std::string wycheproof_ed25519() { + return + R"abcd({ + "algorithm" : "EDDSA", + "generatorVersion" : "0.4.12", + "notes" : { + "SignatureMalleability" : "EdDSA signatures are non-malleable, if implemented accordingly. Failing to check the range of S allows to modify signatures. See RFC 8032, Section 5.2.7 and Section 8.4." + }, + "numberOfTests" : 111, + "header" : [], + "testGroups" : [ + { + "key" : { + "curve" : "edwards25519", + "keySize" : 255, + "pk" : "7d4d0e7f6153a69b6242b522abbee685fda4420f8834b108c3bdae369ef549fa", + "sk" : "add4bb8103785baf9ac534258e8aaf65f5f1adb5ef5f3df19bb80ab989c4d64b", + "type" : "EDDSAKeyPair" + }, + "keyDer" : "302a300506032b65700321007d4d0e7f6153a69b6242b522abbee685fda4420f8834b108c3bdae369ef549fa", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAfU0Of2FTpptiQrUiq77mhf2kQg+INLEIw72uNp71Sfo=\n-----END PUBLIC KEY-----\n", + "type" : "EDDSAVer", + "tests" : [ + { + "tcId" : 1, + "comment" : "", + "msg" : "", + "sig" : "d4fbdb52bfa726b44d1786a8c0d171c3e62ca83c9e5bbe63de0bb2483f8fd6cc1429ab72cafc41ab56af02ff8fcc43b99bfe4c7ae940f60f38ebaa9d311c4007", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 2, + "comment" : "", + "msg" : "78", + "sig" : "d80737358ede548acb173ef7e0399f83392fe8125b2ce877de7975d8b726ef5b1e76632280ee38afad12125ea44b961bf92f1178c9fa819d020869975bcbe109", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 3, + "comment" : "", + "msg" : "54657374", + "sig" : "7c38e026f29e14aabd059a0f2db8b0cd783040609a8be684db12f82a27774ab07a9155711ecfaf7f99f277bad0c6ae7e39d4eef676573336a5c51eb6f946b30d", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 4, + "comment" : "", + "msg" : "48656c6c6f", + "sig" : "1c1ad976cbaae3b31dee07971cf92c928ce2091a85f5899f5e11ecec90fc9f8e93df18c5037ec9b29c07195ad284e63d548cd0a6fe358cc775bd6c1608d2c905", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 5, + "comment" : "", + "msg" : "313233343030", + "sig" : "657c1492402ab5ce03e2c3a7f0384d051b9cf3570f1207fc78c1bcc98c281c2bf0cf5b3a289976458a1be6277a5055545253b45b07dcc1abd96c8b989c00f301", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 6, + "comment" : "", + "msg" : "000000000000000000000000", + "sig" : "d46543bfb892f84ec124dcdfc847034c19363bf3fc2fa89b1267833a14856e52e60736918783f950b6f1dd8d40dc343247cd43ce054c2d68ef974f7ed0f3c60f", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 7, + "comment" : "", + "msg" : "6161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161", + "sig" : "879350045543bc14ed2c08939b68c30d22251d83e018cacbaf0c9d7a48db577e80bdf76ce99e5926762bc13b7b3483260a5ef63d07e34b58eb9c14621ac92f00", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 8, + "comment" : "", + "msg" : "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f60", + "sig" : "7bdc3f9919a05f1d5db4a3ada896094f6871c1f37afc75db82ec3147d84d6f237b7e5ecc26b59cfea0c7eaf1052dc427b0f724615be9c3d3e01356c65b9b5109", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 9, + "comment" : "", + "msg" : "ffffffffffffffffffffffffffffffff", + "sig" : "5dbd7360e55aa38e855d6ad48c34bd35b7871628508906861a7c4776765ed7d1e13d910faabd689ec8618b78295c8ab8f0e19c8b4b43eb8685778499e943ae04", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 10, + "comment" : "special values for r and s", + "msg" : "3f", + "sig" : "00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 11, + "comment" : "special values for r and s", + "msg" : "3f", + "sig" : "00000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 12, + "comment" : "special values for r and s", + "msg" : "3f", + "sig" : "0000000000000000000000000000000000000000000000000000000000000000ecd3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 13, + "comment" : "special values for r and s", + "msg" : "3f", + "sig" : "0000000000000000000000000000000000000000000000000000000000000000edd3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 14, + "comment" : "special values for r and s", + "msg" : "3f", + "sig" : "0000000000000000000000000000000000000000000000000000000000000000edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 15, + "comment" : "special values for r and s", + "msg" : "3f", + "sig" : "01000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 16, + "comment" : "special values for r and s", + "msg" : "3f", + "sig" : "01000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 17, + "comment" : "special values for r and s", + "msg" : "3f", + "sig" : "0100000000000000000000000000000000000000000000000000000000000000ecd3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 18, + "comment" : "special values for r and s", + "msg" : "3f", + "sig" : "0100000000000000000000000000000000000000000000000000000000000000edd3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 19, + "comment" : "special values for r and s", + "msg" : "3f", + "sig" : "0100000000000000000000000000000000000000000000000000000000000000edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 20, + "comment" : "special values for r and s", + "msg" : "3f", + "sig" : "edd3f55c1a631258d69cf7a2def9de14000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 21, + "comment" : "special values for r and s", + "msg" : "3f", + "sig" : "edd3f55c1a631258d69cf7a2def9de14000000000000000000000000000000100100000000000000000000000000000000000000000000000000000000000000", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 22, + "comment" : "special values for r and s", + "msg" : "3f", + "sig" : "edd3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010ecd3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 23, + "comment" : "special values for r and s", + "msg" : "3f", + "sig" : "edd3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010edd3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 24, + "comment" : "special values for r and s", + "msg" : "3f", + "sig" : "edd3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 25, + "comment" : "special values for r and s", + "msg" : "3f", + "sig" : "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0000000000000000000000000000000000000000000000000000000000000000", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 26, + "comment" : "special values for r and s", + "msg" : "3f", + "sig" : "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f0100000000000000000000000000000000000000000000000000000000000000", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 27, + "comment" : "special values for r and s", + "msg" : "3f", + "sig" : "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fecd3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 28, + "comment" : "special values for r and s", + "msg" : "3f", + "sig" : "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fedd3f55c1a631258d69cf7a2def9de1400000000000000000000000000000010", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 29, + "comment" : "special values for r and s", + "msg" : "3f", + "sig" : "edffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7fedffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff7f", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 30, + "comment" : "empty signature", + "msg" : "54657374", + "sig" : "", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 31, + "comment" : "s missing", + "msg" : "54657374", + "sig" : "7c38e026f29e14aabd059a0f2db8b0cd783040609a8be684db12f82a27774ab0", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 32, + "comment" : "signature too short", + "msg" : "54657374", + "sig" : "7c38e026f29e14aabd059a0f2db8b0cd783040609a8be684db12f82a27774ab07a9155711ecfaf7f99f277bad0c6ae7e39d4eef676573336a5c51eb6f946", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 33, + "comment" : "signature too long", + "msg" : "54657374", + "sig" : "7c38e026f29e14aabd059a0f2db8b0cd783040609a8be684db12f82a27774ab07a9155711ecfaf7f99f277bad0c6ae7e39d4eef676573336a5c51eb6f946b30d2020", + "result" : "invalid", + "flags" : [] + },)abcd" R"abcd( + { + "tcId" : 34, + "comment" : "include pk in signature", + "msg" : "54657374", + "sig" : "7c38e026f29e14aabd059a0f2db8b0cd783040609a8be684db12f82a27774ab07a9155711ecfaf7f99f277bad0c6ae7e39d4eef676573336a5c51eb6f946b30d7d4d0e7f6153a69b6242b522abbee685fda4420f8834b108c3bdae369ef549fa", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 35, + "comment" : "prepending 0 byte to signature", + "msg" : "54657374", + "sig" : "007c38e026f29e14aabd059a0f2db8b0cd783040609a8be684db12f82a27774ab07a9155711ecfaf7f99f277bad0c6ae7e39d4eef676573336a5c51eb6f946b30d", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 36, + "comment" : "prepending 0 byte to s", + "msg" : "54657374", + "sig" : "7c38e026f29e14aabd059a0f2db8b0cd783040609a8be684db12f82a27774ab0007a9155711ecfaf7f99f277bad0c6ae7e39d4eef676573336a5c51eb6f946b30d", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 37, + "comment" : "appending 0 byte to signature", + "msg" : "54657374", + "sig" : "7c38e026f29e14aabd059a0f2db8b0cd783040609a8be684db12f82a27774ab07a9155711ecfaf7f99f277bad0c6ae7e39d4eef676573336a5c51eb6f946b30d00", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 38, + "comment" : "removing 0 byte from signature", + "msg" : "546573743137", + "sig" : "7c38e026f29e14aabd059a0f2db8b0cd783040609a8be684db12f82a27774ab07a9155711ecfaf7f99f277bad0c6ae7e39d4eef676573336a5c51eb6f946b3", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 39, + "comment" : "removing 0 byte from signature", + "msg" : "54657374313236", + "sig" : "7c38e026f29e14aabd059a0f2db8b0cd783040609a8be684db12f82a27774ab09155711ecfaf7f99f277bad0c6ae7e39d4eef676573336a5c51eb6f946b30d", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 40, + "comment" : "removing leading 0 byte from signature", + "msg" : "546573743530", + "sig" : "38e026f29e14aabd059a0f2db8b0cd783040609a8be684db12f82a27774ab07a9155711ecfaf7f99f277bad0c6ae7e39d4eef676573336a5c51eb6f946b30d", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 41, + "comment" : "dropping byte from signature", + "msg" : "54657374333437", + "sig" : "7c38e026f29e14aabd059a0f2db8b0cd783040609a8be684db12f82a27774ab09155711ecfaf7f99f277bad0c6ae7e39d4eef676573336a5c51eb6f946b30d", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 42, + "comment" : "modified bit 0 in R", + "msg" : "313233343030", + "sig" : "647c1492402ab5ce03e2c3a7f0384d051b9cf3570f1207fc78c1bcc98c281c2b1d125e5538f38afbcc1c84e489521083041d24bc6240767029da063271a1ff0c", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 43, + "comment" : "modified bit 1 in R", + "msg" : "313233343030", + "sig" : "677c1492402ab5ce03e2c3a7f0384d051b9cf3570f1207fc78c1bcc98c281c2bc108ca4b87a49c9ed2cf383aecad8f54a962b2899da891e12004d7993a627e01", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 44, + "comment" : "modified bit 2 in R", + "msg" : "313233343030", + "sig" : "617c1492402ab5ce03e2c3a7f0384d051b9cf3570f1207fc78c1bcc98c281c2b9ce23fc6213ed5b87912e9bbf92f5e2c780eae26d15c50a112d1e97d2ea33c06", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 45, + "comment" : "modified bit 7 in R", + "msg" : "313233343030", + "sig" : "e57c1492402ab5ce03e2c3a7f0384d051b9cf3570f1207fc78c1bcc98c281c2bbb3eb51cd98dddb235a5f46f2bded6af184a58d09cce928bda43f41d69118a03", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 46, + "comment" : "modified bit 8 in R", + "msg" : "313233343030", + "sig" : "657d1492402ab5ce03e2c3a7f0384d051b9cf3570f1207fc78c1bcc98c281c2bcd237dda9a116501f67a5705a854b9adc304f34720803a91b324f2c13e0f5a09", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 47, + "comment" : "modified bit 16 in R", + "msg" : "313233343030", + "sig" : "657c1592402ab5ce03e2c3a7f0384d051b9cf3570f1207fc78c1bcc98c281c2b6b167bbdc0d881cc04d28905552c1876f3709851abc5007376940cc8a435c300", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 48, + "comment" : "modified bit 31 in R", + "msg" : "313233343030", + "sig" : "657c1412402ab5ce03e2c3a7f0384d051b9cf3570f1207fc78c1bcc98c281c2b7fd2ac7da14afffcceeb13f2a0d6b887941cb1a5eb57a52f3cb131a16cce7b0e", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 49, + "comment" : "modified bit 32 in R", + "msg" : "313233343030", + "sig" : "657c1492412ab5ce03e2c3a7f0384d051b9cf3570f1207fc78c1bcc98c281c2b7373ba13ebbef99cd2a8ead55ce735c987d85a35320925a8e871702dc7c5c40d", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 50, + "comment" : "modified bit 63 in R", + "msg" : "313233343030", + "sig" : "657c1492402ab54e03e2c3a7f0384d051b9cf3570f1207fc78c1bcc98c281c2bd35bd331c03f0855504ca1cab87b83c36a028425a3cf007ede4f4254c261cb00", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 51, + "comment" : "modified bit 64 in R", + "msg" : "313233343030", + "sig" : "657c1492402ab5ce02e2c3a7f0384d051b9cf3570f1207fc78c1bcc98c281c2bcb35101f73cf467deac8c1a03b6c3dc35af544132734b7e57ab20c89b2e4750d", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 52, + "comment" : "modified bit 97 in R", + "msg" : "313233343030", + "sig" : "657c1492402ab5ce03e2c3a7f2384d051b9cf3570f1207fc78c1bcc98c281c2bb58d2e8878290bff8d3355fdd4ea381924ee578752354eb6dee678ab4011c301", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 53, + "comment" : "modified bit 127 in R", + "msg" : "313233343030", + "sig" : "657c1492402ab5ce03e2c3a7f0384d851b9cf3570f1207fc78c1bcc98c281c2bb978c866187ffb1cc7b29a0b4045aefc08768df65717194ff0c6e63f4dea0d02", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 54, + "comment" : "modified bit 240 in R", + "msg" : "313233343030", + "sig" : "657c1492402ab5ce03e2c3a7f0384d051b9cf3570f1207fc78c1bcc98c281d2b0576ecf8eaf675f00f3dfbe19f75b83b7607a6c96414f6821af920a2498d0305", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 55, + "comment" : "modified bit 247 in R", + "msg" : "313233343030", + "sig" : "657c1492402ab5ce03e2c3a7f0384d051b9cf3570f1207fc78c1bcc98c289c2be5241a345c7b5428054c74b7c382fa10d4a5f1e8f8b79a71d3fdea2254f1ff0e", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 56, + "comment" : "modified bit 248 in R", + "msg" : "313233343030", + "sig" : "657c1492402ab5ce03e2c3a7f0384d051b9cf3570f1207fc78c1bcc98c281c2a63950c85cd6dc96364e768de50ff7732b538f8a0b1615d799190ab600849230e", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 57, + "comment" : "modified bit 253 in R", + "msg" : "313233343030", + "sig" : "657c1492402ab5ce03e2c3a7f0384d051b9cf3570f1207fc78c1bcc98c281c0b543bd3da0a56a8c9c152f59c9fec12f31fa66434d48b817b30d90cb4efa8b501", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 58, + "comment" : "modified bit 254 in R", + "msg" : "313233343030", + "sig" : "657c1492402ab5ce03e2c3a7f0384d051b9cf3570f1207fc78c1bcc98c281c6b8da07efd07a6dafb015ed6a32fe136319a972ffbc341f3a0beae97ccf8136505", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 59, + "comment" : "modified bit 255 in R", + "msg" : "313233343030", + "sig" : "657c1492402ab5ce03e2c3a7f0384d051b9cf3570f1207fc78c1bcc98c281cab227aedf259f910f0f3a759a335062665217925d019173b88917eae294f75d40f", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 60, + "comment" : "R==0", + "msg" : "313233343030", + "sig" : "0000000000000000000000000000000000000000000000000000000000000000e0b8e7770d51c7a36375d006c5bffd6af43ff54aaf47e4330dc118c71d61ec02", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 61, + "comment" : "invalid R", + "msg" : "313233343030", + "sig" : "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff463a1908382e7eb7693acef9884f7cf931a215e0791876be22c631a59881fd0e", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 62, + "comment" : "all bits flipped in R", + "msg" : "313233343030", + "sig" : "9a83eb6dbfd54a31fc1d3c580fc7b2fae4630ca8f0edf803873e433673d7e3d40e94254586cb6188c5386c3febed477cb9a6cb29e3979adc4cb27cf5278fb70a", + "result" : "invalid", + "flags" : [] + }, + { + "tcId" : 63, + "comment" : "checking malleability ", + "msg" : "54657374", + "sig" : "7c38e026f29e14aabd059a0f2db8b0cd783040609a8be684db12f82a27774ab067654bce3832c2d76f8f6f5dafc08d9339d4eef676573336a5c51eb6f946b31d", + "result" : "invalid", + "flags" : [ + "SignatureMalleability" + ] + }, + { + "tcId" : 64, + "comment" : "checking malleability ", + "msg" : "54657374", + "sig" : "7c38e026f29e14aabd059a0f2db8b0cd783040609a8be684db12f82a27774ab05439412b5395d42f462c67008eba6ca839d4eef676573336a5c51eb6f946b32d", + "result" : "invalid", + "flags" : [ + "SignatureMalleability" + ] + }, + { + "tcId" : 65, + "comment" : "checking malleability ", + "msg" : "54657374", + "sig" : "7c38e026f29e14aabd059a0f2db8b0cd783040609a8be684db12f82a27774ab02ee12ce5875bf9dff26556464bae2ad239d4eef676573336a5c51eb6f946b34d", + "result" : "invalid", + "flags" : [ + "SignatureMalleability" + ] + }, + { + "tcId" : 66, + "comment" : "checking malleability ", + "msg" : "54657374", + "sig" : "7c38e026f29e14aabd059a0f2db8b0cd783040609a8be684db12f82a27774ab0e2300459f1e742404cd934d2c595a6253ad4eef676573336a5c51eb6f946b38d", + "result" : "invalid", + "flags" : [ + "SignatureMalleability" + ] + }, + { + "tcId" : 67, + "comment" : "checking malleability ", + "msg" : "54657374", + "sig" : "7c38e026f29e14aabd059a0f2db8b0cd783040609a8be684db12f82a27774ab07a9155711ecfaf7f99f277bad0c6ae7e39d4eef676573336a5c51eb6f946b32d", + "result" : "invalid", + "flags" : [ + "SignatureMalleability" + ] + },)abcd" R"abcd( + { + "tcId" : 68, + "comment" : "checking malleability ", + "msg" : "54657374", + "sig" : "7c38e026f29e14aabd059a0f2db8b0cd783040609a8be684db12f82a27774ab07a9155711ecfaf7f99f277bad0c6ae7e39d4eef676573336a5c51eb6f946b34d", + "result" : "invalid", + "flags" : [ + "SignatureMalleability" + ] + }, + { + "tcId" : 69, + "comment" : "checking malleability ", + "msg" : "54657374", + "sig" : "7c38e026f29e14aabd059a0f2db8b0cd783040609a8be684db12f82a27774ab07a9155711ecfaf7f99f277bad0c6ae7e39d4eef676573336a5c51eb6f946b38d", + "result" : "invalid", + "flags" : [ + "SignatureMalleability" + ] + }, + { + "tcId" : 70, + "comment" : "checking malleability ", + "msg" : "54657374", + "sig" : "7c38e026f29e14aabd059a0f2db8b0cd783040609a8be684db12f82a27774ab0679155711ecfaf7f99f277bad0c6ae7e39d4eef676573336a5c51eb6f946b38d", + "result" : "invalid", + "flags" : [ + "SignatureMalleability" + ] + } + ] + }, + { + "key" : { + "curve" : "edwards25519", + "keySize" : 255, + "pk" : "a12c2beb77265f2aac953b5009349d94155a03ada416aad451319480e983ca4c", + "sk" : "0a23a20072891237aa0864b5765139514908787878cd77135a0059881d313f00", + "type" : "EDDSAKeyPair" + }, + "keyDer" : "302a300506032b6570032100a12c2beb77265f2aac953b5009349d94155a03ada416aad451319480e983ca4c", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAoSwr63cmXyqslTtQCTSdlBVaA62kFqrUUTGUgOmDykw=\n-----END PUBLIC KEY-----\n", + "type" : "EDDSAVer", + "tests" : [ + { + "tcId" : 71, + "comment" : "", + "msg" : "", + "sig" : "5056325d2ab440bf30bbf0f7173199aa8b4e6fbc091cf3eb6bc6cf87cd73d992ffc216c85e4ab5b8a0bbc7e9a6e9f8d33b7f6e5ac0ffdc22d9fcaf784af84302", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 72, + "comment" : "", + "msg" : "78", + "sig" : "481fafbf4364d7b682475282f517a3ac0538c9a6b6a562e99a3d8e5afb4f90a559b056b9f07af023905753b02d95eb329a35c77f154b79abbcd291615ce42f02", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 73, + "comment" : "", + "msg" : "54657374", + "sig" : "8a9bb4c465a3863abc9fd0dd35d80bb28f7d33d37d74679802d63f82b20da114b8d765a1206b3e9ad7cf2b2d8d778bb8651f1fa992db293c0039eacb6161480f", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 74, + "comment" : "", + "msg" : "48656c6c6f", + "sig" : "d839c20abfda1fd429531831c64f813f84b913e9928540310cf060b44c3dbf9457d44a7721fdc0d67724ff81cb450dd39b10cfb65db15dda4b8bf09d26bd3801", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 75, + "comment" : "", + "msg" : "313233343030", + "sig" : "9bbb1052dcfa8ad2715c2eb716ae4f1902dea353d42ee09fd4c0b4fcb8b52b5219e2200016e1199d0061891c263e31b0bc3b55673c19610c4e0fa5408004160b", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 76, + "comment" : "", + "msg" : "000000000000000000000000", + "sig" : "f63b5c0667c7897fc283296416f7f60e84bbde9cbd832e56be463ed9f568069702b17a2f7c341ebf590706a6388ac76ac613c1675ec0f2c7118f2573422a500b", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 77, + "comment" : "", + "msg" : "6161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161616161", + "sig" : "1bc44d7001e6b5b9090fef34b2ca480f9786bbefa7d279353e5881e8dfb91b803ccd46500e270ef0109bfd741037558832120bc2a4f20fbe7b5fb3c3aaf23e08", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 78, + "comment" : "", + "msg" : "202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f404142434445464748494a4b4c4d4e4f505152535455565758595a5b5c5d5e5f60", + "sig" : "ea8e22143b02372e76e99aece3ed36aec529768a27e2bb49bdc135d44378061e1f62d1ac518f33ebf37b2ee8cc6dde68a4bd7d4a2f4d6cb77f015f71ca9fc30d", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 79, + "comment" : "", + "msg" : "ffffffffffffffffffffffffffffffff", + "sig" : "8acd679e1a914fc45d5fa83d3021f0509c805c8d271df54e52f43cfbd00cb6222bf81d58fe1de2de378df67ee9f453786626961fe50a9b05f12b6f0899ebdd0a", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "edwards25519", + "keySize" : 255, + "pk" : "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a", + "sk" : "9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60", + "type" : "EDDSAKeyPair" + }, + "keyDer" : "302a300506032b6570032100d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEA11qYAYKxCrfVS/7TyWQHOg7hcvPapiMlrwIaaPcHURo=\n-----END PUBLIC KEY-----\n", + "type" : "EDDSAVer", + "tests" : [ + { + "tcId" : 80, + "comment" : "draft-josefsson-eddsa-ed25519-02: Test 1", + "msg" : "", + "sig" : "e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "edwards25519", + "keySize" : 255, + "pk" : "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c", + "sk" : "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb", + "type" : "EDDSAKeyPair" + }, + "keyDer" : "302a300506032b65700321003d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAPUAXw+hDiVqStwqnTRt+vJyYLM8uxJaMwM1V8Sr0Zgw=\n-----END PUBLIC KEY-----\n", + "type" : "EDDSAVer", + "tests" : [ + { + "tcId" : 81, + "comment" : "draft-josefsson-eddsa-ed25519-02: Test 2", + "msg" : "72", + "sig" : "92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "edwards25519", + "keySize" : 255, + "pk" : "fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025", + "sk" : "c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7", + "type" : "EDDSAKeyPair" + }, + "keyDer" : "302a300506032b6570032100fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEA/FHNjmIYoaONpH7QAjDwWAgW7RO6MwOsXeuRFUiQgCU=\n-----END PUBLIC KEY-----\n", + "type" : "EDDSAVer", + "tests" : [ + { + "tcId" : 82, + "comment" : "draft-josefsson-eddsa-ed25519-02: Test 3", + "msg" : "af82", + "sig" : "6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40a", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "edwards25519", + "keySize" : 255, + "pk" : "278117fc144c72340f67d0f2316e8386ceffbf2b2428c9c51fef7c597f1d426e", + "sk" : "f5e5767cf153319517630f226876b86c8160cc583bc013744c6bf255f5cc0ee5", + "type" : "EDDSAKeyPair" + }, + "keyDer" : "302a300506032b6570032100278117fc144c72340f67d0f2316e8386ceffbf2b2428c9c51fef7c597f1d426e", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAJ4EX/BRMcjQPZ9DyMW6Dhs7/vyskKMnFH+98WX8dQm4=\n-----END PUBLIC KEY-----\n", + "type" : "EDDSAVer", + "tests" : [ + { + "tcId" : 83, + "comment" : "draft-josefsson-eddsa-ed25519-02: Test 1024", + "msg" : "08b8b2b733424243760fe426a4b54908632110a66c2f6591eabd3345e3e4eb98fa6e264bf09efe12ee50f8f54e9f77b1e355f6c50544e23fb1433ddf73be84d879de7c0046dc4996d9e773f4bc9efe5738829adb26c81b37c93a1b270b20329d658675fc6ea534e0810a4432826bf58c941efb65d57a338bbd2e26640f89ffbc1a858efcb8550ee3a5e1998bd177e93a7363c344fe6b199ee5d02e82d522c4feba15452f80288a821a579116ec6dad2b3b310da903401aa62100ab5d1a36553e06203b33890cc9b832f79ef80560ccb9a39ce767967ed628c6ad573cb116dbefefd75499da96bd68a8a97b928a8bbc103b6621fcde2beca1231d206be6cd9ec7aff6f6c94fcd7204ed3455c68c83f4a41da4af2b74ef5c53f1d8ac70bdcb7ed185ce81bd84359d44254d95629e9855a94a7c1958d1f8ada5d0532ed8a5aa3fb2d17ba70eb6248e594e1a2297acbbb39d502f1a8c6eb6f1ce22b3de1a1f40cc24554119a831a9aad6079cad88425de6bde1a9187ebb6092cf67bf2b13fd65f27088d78b7e883c8759d2c4f5c65adb7553878ad575f9fad878e80a0c9ba63bcbcc2732e69485bbc9c90bfbd62481d9089beccf80cfe2df16a2cf65bd92dd597b0707e0917af48bbb75fed413d238f5555a7a569d80c3414a8d0859dc65a46128bab27af87a71314f318c782b23ebfe808b82b0ce26401d2e22f04d83d1255dc51addd3b75a2b1ae0784504df543af8969be3ea7082ff7fc9888c144da2af58429ec96031dbcad3dad9af0dcbaaaf268cb8fcffead94f3c7ca495e056a9b47acdb751fb73e666c6c655ade8297297d07ad1ba5e43f1bca32301651339e22904cc8c42f58c30c04aafdb038dda0847dd988dcda6f3bfd15c4b4c4525004aa06eeff8ca61783aacec57fb3d1f92b0fe2fd1a85f6724517b65e614ad6808d6f6ee34dff7310fdc82aebfd904b01e1dc54b2927094b2db68d6f903b68401adebf5a7e08d78ff4ef5d63653a65040cf9bfd4aca7984a74d37145986780fc0b16ac451649de6188a7dbdf191f64b5fc5e2ab47b57f7f7276cd419c17a3ca8e1b939ae49e488acba6b965610b5480109c8b17b80e1b7b750dfc7598d5d5011fd2dcc5600a32ef5b52a1ecc820e308aa342721aac0943bf6686b64b2579376504ccc493d97e6aed3fb0f9cd71a43dd497f01f17c0e2cb3797aa2a2f256656168e6c496afc5fb93246f6b1116398a346f1a641f3b041e989f7914f90cc2c7fff357876e506b50d334ba77c225bc307ba537152f3f1610e4eafe595f6d9d90d11faa933a15ef1369546868a7f3a45a96768d40fd9d03412c091c6315cf4fde7cb68606937380db2eaaa707b4c4185c32eddcdd306705e4dc1ffc872eeee475a64dfac86aba41c0618983f8741c5ef68d3a101e8a3b8cac60c905c15fc910840b94c00a0b9d0", + "sig" : "0aab4c900501b3e24d7cdf4663326a3a87df5e4843b2cbdb67cbf6e460fec350aa5371b1508f9f4528ecea23c436d94b5e8fcd4f681e30a6ac00a9704a188a03", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "edwards25519", + "keySize" : 255, + "pk" : "8fd659b77b558ed93882c1157438450ac86ec62d421d568e98ee236f3810295a", + "sk" : "d7ad3f1f6bbe0477c3c357a806a19eb41ae3f94025035bc87f281f8ee9fc0e34", + "type" : "EDDSAKeyPair" + }, + "keyDer" : "302a300506032b65700321008fd659b77b558ed93882c1157438450ac86ec62d421d568e98ee236f3810295a", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAj9ZZt3tVjtk4gsEVdDhFCshuxi1CHVaOmO4jbzgQKVo=\n-----END PUBLIC KEY-----\n", + "type" : "EDDSAVer", + "tests" : [ + { + "tcId" : 84, + "comment" : "Random test failure 1", + "msg" : "b0729a713593a92e46b56eaa66b9e435f7a09a8e7de03b078f6f282285276635f301e7aaafe42187c45d6f5b13f9f16b11195cc125c05b90d24dfe4c", + "sig" : "7db17557ac470c0eda4eedaabce99197ab62565653cf911f632ee8be0e5ffcfc88fb94276b42e0798fd3aa2f0318be7fc6a29fae75f70c3dcdc414a0ad866601", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "edwards25519", + "keySize" : 255, + "pk" : "2a606bf67ac770c607038b004101b325edb569efd3413d2d1f2c3e6b4e6e3082", + "sk" : "ad9b22793336fcdac10e136c4deea599be187a38eef91c1cf7c7a4ec884dda08", + "type" : "EDDSAKeyPair" + }, + "keyDer" : "302a300506032b65700321002a606bf67ac770c607038b004101b325edb569efd3413d2d1f2c3e6b4e6e3082", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAKmBr9nrHcMYHA4sAQQGzJe21ae/TQT0tHyw+a05uMII=\n-----END PUBLIC KEY-----\n", + "type" : "EDDSAVer", + "tests" : [ + { + "tcId" : 85, + "comment" : "Random test failure 2", + "msg" : "a8546e50ba31cae3234310d32672447be213fad91a227a19669c53d309b959782b0e6b71f8791fdb470043b58122003157d2d96a43a6cbd7d3a8d86bf4c97391883e268d50af80e1e6e12939c2bd50ca746cdadfad4edf1bda875299740724148efb1ebe73fb60088cda890317658627a5f7ab5a0c075d9d8f3f97b6492b35519e50ff6b38377432a7081f9176bb1c29a862deac1336ca20b097a47829cec10a6a7cec178eda2d12f6dc6c87f910454af0123555ba184e68804d9cced60fd5c8c90943e56599c8f0ba59a38491ba5e5a53460682474c07e40ca142983314fd762856bb1093f359da6eb0a756bd93a3160c10dd8feea6b97e7c6a17cb54bd5d7649c05c66d7bdee056671dfdaf689fa3945bb8e29a429f4bd5d355dce9687b06f01d5e33e3999f0e8", + "sig" : "67d84d4c3945aaf06e06d524be63acbfb5dbb1988c4aea96a5ee9f7a9b9eecc29df4f66b8aa1d9e8607a58fb1ef0c2ad69aac005b4f58e34103344a9c8871a09", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 86, + "comment" : "Random test failure 24", + "msg" : "b477b0480bb84642608b908d29a51cf2fce63f24ee95", + "sig" : "28fafbb62b4d688fa79e1ac92851f46e319b161f801d4dc09acc21fdd6780a2c4292b8c1003c61c2bcebe7f3f88ccc4bb26d407387c5f27cb8c94cf6ce810405", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "edwards25519", + "keySize" : 255, + "pk" : "c9c946cbc5544ac74eef491f07c5881c16faf7ec31ce4aa91bb60ae7b4539051", + "sk" : "04a6553d68a9baef78a2175af375458eaa01cdb77350c61e282ef5f0c7116599", + "type" : "EDDSAKeyPair" + }, + "keyDer" : "302a300506032b6570032100c9c946cbc5544ac74eef491f07c5881c16faf7ec31ce4aa91bb60ae7b4539051", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAyclGy8VUSsdO70kfB8WIHBb69+wxzkqpG7YK57RTkFE=\n-----END PUBLIC KEY-----\n", + "type" : "EDDSAVer", + "tests" : [ + { + "tcId" : 87, + "comment" : "Random test failure 3", + "msg" : "cd2212eddb0706f62c995cef958634f0cb7793444cbf4d30e81c27c41ebea6cb02607510131f9c015692dfd521b148841e9a2d3564d20ac401f6cb8e40f520fe0cafbeaa88840b83013369d879f013463fe52a13267aa0c8c59c45cde9399cd1e6be8cc64cf48315ac2eb31a1c567a4fb7d601746d1f63b5ac020712adbbe07519bded6f", + "sig" : "24087d47f3e20af51b9668ae0a88ce76586802d0ec75d8c0f28fc30962b5e1d1a1d509571a1624ed125a8df92a6e963728d6b5de99200b8e285f70feb6f05207", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 88, + "comment" : "Random test failure 20", + "msg" : "27d465bc632743522aefa23c", + "sig" : "c2656951e2a0285585a51ff0eda7e9a23c2dfd2ffa273aee7808f4604e8f9a8c8ea49e9fce4eb2d8d75d36b7238fe6fc13b6c5d9427dd58f8c6615d033c0bd0f", + "result" : "valid", + "flags" : [] + } + ] + },)abcd" R"abcd( + { + "key" : { + "curve" : "edwards25519", + "keySize" : 255, + "pk" : "32ad026f693d0d2afe7f4388d91c4c964426fcb9e3665c3ebd8650009b815c8e", + "sk" : "c367c8d2ebeeecd70c1e8985b70c3808b75657f243b21ba4f322792540e92257", + "type" : "EDDSAKeyPair" + }, + "keyDer" : "302a300506032b657003210032ad026f693d0d2afe7f4388d91c4c964426fcb9e3665c3ebd8650009b815c8e", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAMq0Cb2k9DSr+f0OI2RxMlkQm/LnjZlw+vYZQAJuBXI4=\n-----END PUBLIC KEY-----\n", + "type" : "EDDSAVer", + "tests" : [ + { + "tcId" : 89, + "comment" : "Random test failure 4", + "msg" : "ec5c7cb078", + "sig" : "d920d421a5956b69bfe1ba834c025e2babb6c7a6d78c97de1d9bb1116dfdd1185147b2887e34e15578172e150774275ea2aad9e02106f7e8ca1caa669a066f0c", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 90, + "comment" : "Random test failure 5", + "msg" : "4668c6a76f0e482190a7175b9f3806a5fe4314a004fa69f988373f7a", + "sig" : "4f62daf7f7c162038552ad7d306e195baa37ecf6ca7604142679d7d1128e1f8af52e4cb3545748c44ef1ff1c64e877e4f4d248259b7f6eb56e3ef72097dc8e0c", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 91, + "comment" : "Random test failure 8", + "msg" : "5dc9bb87eb11621a93f92abe53515697d2611b2eef73", + "sig" : "deecafb6f2ede73fec91a6f10e45b9c1c61c4b9bfbe6b6147e2de0b1df6938971f7896c3ab83851fb5d9e537037bff0fca0ccb4a3cc38f056f91f7d7a0557e08", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 92, + "comment" : "Random test failure 10", + "msg" : "7dcfe60f881e1285676f35b68a1b2dbcdd7be6f719a288ababc28d36e3a42ac3010a1ca54b32760e74", + "sig" : "7f8663cf98cbd39d5ff553f00bcf3d0d520605794f8866ce75714d77cc51e66c91818b657d7b0dae430a68353506edc4a714c345f5ddb5c8b958ba3d035f7a01", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 93, + "comment" : "Random test failure 12", + "msg" : "58e456064dff471109def4ca27fa8310a1df32739655b624f27e6418d34b7f007173f3faa5", + "sig" : "6aab49e5c0bc309b783378ee03ffda282f0185cdf94c847701ff307a6ee8d0865411c44e0a8206f6a5f606107451940c2593af790ce1860f4c14ab25b2deae08", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 94, + "comment" : "Random test failure 15", + "msg" : "a1", + "sig" : "1a74ed2cbdc7d8f3827014e8e6ecf8fd2698ac8f86833acccdd400df710fe0d6b0543c9cfa00d52bf024ab7ce0d91981944097233ec134d5c7abbd44bfd32d0d", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 95, + "comment" : "Random test failure 19", + "msg" : "11cb1eafa4c42a8402c4193c4696f7b2e6d4585e4b42dcf1a8b67a80b2da80bc9d4b649fb2f35eaf1f56c426fd0b", + "sig" : "14ceb2eaf4688d995d482f44852d71ad878cd7c77b41e60b0065fd01a59b054ee74759224187dbde9e59a763a70277c960892ef89fba997aba2576b2c54ba608", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 96, + "comment" : "Random test failure 25", + "msg" : "aa365b442d12b7f3c925", + "sig" : "83c40ce13d483cc58ff65844875862d93df4bd367af77efa469ec06a8ed9e6d7905a04879535708ddf225567a815c9b941d405c98e918fd0c151165cea7fb101", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 97, + "comment" : "Random test failure 28", + "msg" : "475f", + "sig" : "71a4a06a34075f2fd47bc3abf4714d46db7e97b08cb6180d3f1539ac50b18ce51f8af8ae95ed21d4fa0daab7235925631ecea1fd9d0d8a2ba7a7583fd04b900c", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "edwards25519", + "keySize" : 255, + "pk" : "c29ec1894e06d27b4e40486b4fa5063d66a746c7f9c323b12203c03b72b8b78a", + "sk" : "56c1e22d616cbb6dea869288b4b1c02bb98696583c2f6e650013a03e17049c62", + "type" : "EDDSAKeyPair" + }, + "keyDer" : "302a300506032b6570032100c29ec1894e06d27b4e40486b4fa5063d66a746c7f9c323b12203c03b72b8b78a", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAwp7BiU4G0ntOQEhrT6UGPWanRsf5wyOxIgPAO3K4t4o=\n-----END PUBLIC KEY-----\n", + "type" : "EDDSAVer", + "tests" : [ + { + "tcId" : 98, + "comment" : "Random test failure 6", + "msg" : "0f325ffd87e58131ffa23c05ea4579513b287fdba87b44", + "sig" : "6669acf94667c5b541afe5307bde9476b13ae7e0e6058a772101ac8eb0a94331428eb4db0a2c68a9b6c1763b8624dab259b0876cdcfaeacc17b21a18e3fc010a", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 99, + "comment" : "Random test failure 21", + "msg" : "5ffa", + "sig" : "931e5152fcef078c22cc5d6a3a65f06e396289f6f5f2d1efa6340254a53526ef5dc6874eeddf35c3f50991c53cd02bf06313e37d93ee1f7022128ffa3b8f300b", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "edwards25519", + "keySize" : 255, + "pk" : "cfda5b899e35764c5229e59295fe1222b7ddce176643697c29e46ecbba10cf10", + "sk" : "b7d2f64276df417fed27d8e15b4e90f6fd93dace707294c338bd32bc4bbd8fdb", + "type" : "EDDSAKeyPair" + }, + "keyDer" : "302a300506032b6570032100cfda5b899e35764c5229e59295fe1222b7ddce176643697c29e46ecbba10cf10", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAz9pbiZ41dkxSKeWSlf4SIrfdzhdmQ2l8KeRuy7oQzxA=\n-----END PUBLIC KEY-----\n", + "type" : "EDDSAVer", + "tests" : [ + { + "tcId" : 100, + "comment" : "Random test failure 7", + "msg" : "ec5c7cb078", + "sig" : "30490c28f806298225df62103521dcee047153912c33ab8ab8bbdd1ffabd70fd4fdb360f05be535b067d1cf4e78c2cb432206bf280aab3bd21aaa1cb894c5b06", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 101, + "comment" : "Random test failure 9", + "msg" : "67484059b2490b1a0a4f8dee77979e26", + "sig" : "4cd4f77ed473a6647387f3163541c67a1708a3c3bd1673247cb87f0cb68b3c56f04bfa72970c8a483efe659c87009ab4020b590b6641316b3deddb5450544e02", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 102, + "comment" : "Random test failure 11", + "msg" : "a020a4381dc9141f47ee508871ab7a8b5a3648727c4281ae9932376f23a8e1bcda0626b7129197d864178631ec89c4332dbb18", + "sig" : "1e41a24fe732bd7cab14c2a2f5134ee8c87fcbd2e987e60957ed9239e5c32404d56977e1b4282871896cb10625a1937468e4dc266e16a9c1b8e9891177eca802", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 103, + "comment" : "Random test failure 14", + "msg" : "a25176b3afea318b2ec11ddacb10caf7179c0b3f8eabbfa2895581138d3c1e0e", + "sig" : "2a833aadecd9f28235cb5896bf3781521dc71f28af2e91dbe1735a61dce3e31ac15ca24b3fc47817a59d386bbbb2ce60a6adc0a2703bb2bdea8f70f91051f706", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 104, + "comment" : "Random test failure 18", + "msg" : "a9e6d94870a67a9fe1cf13b1e6f9150cdd407bf6480ec841ea586ae3935e9787163cf419c1", + "sig" : "c97e3190f83bae7729ba473ad46b420b8aad735f0808ea42c0f898ccfe6addd4fd9d9fa3355d5e67ee21ab7e1f805cd07f1fce980e307f4d7ad36cc924eef00c", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "edwards25519", + "keySize" : 255, + "pk" : "529919c9c780985a841c42ba6c180ff2d67a276ccfbe281080e47ab71a758f56", + "sk" : "7d597c3b7283929d07ed8f01f31d2596823e5e46ab226c7be4234d1a9dcaef37", + "type" : "EDDSAKeyPair" + }, + "keyDer" : "302a300506032b6570032100529919c9c780985a841c42ba6c180ff2d67a276ccfbe281080e47ab71a758f56", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAUpkZyceAmFqEHEK6bBgP8tZ6J2zPvigQgOR6txp1j1Y=\n-----END PUBLIC KEY-----\n", + "type" : "EDDSAVer", + "tests" : [ + { + "tcId" : 105, + "comment" : "Random test failure 13", + "msg" : "e1cbf2d86827825613fb7a85811d", + "sig" : "01abfa4d6bbc726b196928ec84fd03f0c953a4fa2b228249562ff1442a4f63a7150b064f3712b51c2af768d2c2711a71aabf8d186833e941a0301b82f0502905", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 106, + "comment" : "Random test failure 22", + "msg" : "25", + "sig" : "e4ae21f7a8f4b3b325c161a8c6e53e2edd7005b9c2f8a2e3b0ac4ba94aa80be6f2ee22ac8d4a96b9a3eb73a825e7bb5aff4a3393bf5b4a38119e9c9b1b041106", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "edwards25519", + "keySize" : 255, + "pk" : "2252b3d57c74cbf8bc460dc2e082847926bc022f09ab6ae95756362bfd1167c1", + "sk" : "f401cee4bfb1732f0e9b8d8ba79469565c3115296141dbdf7e9c311a0ac1823b", + "type" : "EDDSAKeyPair" + }, + "keyDer" : "302a300506032b65700321002252b3d57c74cbf8bc460dc2e082847926bc022f09ab6ae95756362bfd1167c1", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAIlKz1Xx0y/i8Rg3C4IKEeSa8Ai8Jq2rpV1Y2K/0RZ8E=\n-----END PUBLIC KEY-----\n", + "type" : "EDDSAVer", + "tests" : [ + { + "tcId" : 107, + "comment" : "Random test failure 16", + "msg" : "975ef941710071a9e1e6325a0c860becd7c695b5117c3107b686e330e5", + "sig" : "af0fd9dda7e03e12313410d8d8844ebb6fe6b7f65141f22d7bcba5695a25414a9e54326fb44d59fb14707899a8aae70857b23d4080d7ab2c396ef3a36d45ce02", + "result" : "valid", + "flags" : [] + }, + { + "tcId" : 108, + "comment" : "Random test failure 23", + "msg" : "80fdd6218f29c8c8f6bd820945f9b0854e3a8824", + "sig" : "e097e0bd0370bff5bde359175a11b728ee9639095d5df8eda496395565616edfe079977f7d4dc8c75d6113a83d6a55e6e1676408c0967a2906339b43337dcb01", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "edwards25519", + "keySize" : 255, + "pk" : "c0a773110f975de3732355bb7ec7f0c41c091c0252966070205516693b992a4a", + "sk" : "3d658956410377d0644676d2599542412a4f3b0e4eadfb7f3f836615f42b18bc", + "type" : "EDDSAKeyPair" + }, + "keyDer" : "302a300506032b6570032100c0a773110f975de3732355bb7ec7f0c41c091c0252966070205516693b992a4a", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAwKdzEQ+XXeNzI1W7fsfwxBwJHAJSlmBwIFUWaTuZKko=\n-----END PUBLIC KEY-----\n", + "type" : "EDDSAVer", + "tests" : [ + { + "tcId" : 109, + "comment" : "Random test failure 17", + "msg" : "", + "sig" : "0280427e713378f49d478df6373c6cac847b622b567daa2376c839e7ac10e22c380ab0fa8617c9dcfe76c4d9db5459b21dc1413726e46cc8f387d359e344f407", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "edwards25519", + "keySize" : 255, + "pk" : "54cda623245759ad6d43e620a606908befc633d60792bc7798447a0ef38e7311", + "sk" : "bccb61323840c2a96fc36f7e54ea6c8e55f9d221f7f05791ed60025e06064439", + "type" : "EDDSAKeyPair" + }, + "keyDer" : "302a300506032b657003210054cda623245759ad6d43e620a606908befc633d60792bc7798447a0ef38e7311", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAVM2mIyRXWa1tQ+YgpgaQi+/GM9YHkrx3mER6DvOOcxE=\n-----END PUBLIC KEY-----\n", + "type" : "EDDSAVer", + "tests" : [ + { + "tcId" : 110, + "comment" : "Random test failure 26", + "msg" : "27e792b28b2f1702", + "sig" : "14d9b497c19b91d43481c55bb6f5056de252d9ecb637575c807e58e9b4c5eac8b284089d97e2192dc242014363208e2c9a3435edf8928fb1d893553e9be4c703", + "result" : "valid", + "flags" : [] + } + ] + }, + { + "key" : { + "curve" : "edwards25519", + "keySize" : 255, + "pk" : "2362bac514d5fad33802642e979a1e82de6eb6f1bcbf6a5b304f2bb02b9e57fe", + "sk" : "f2d3023b9c19e241748bc4039a7a43c595701f23675505015213a8a2a0274c1b", + "type" : "EDDSAKeyPair" + }, + "keyDer" : "302a300506032b65700321002362bac514d5fad33802642e979a1e82de6eb6f1bcbf6a5b304f2bb02b9e57fe", + "keyPem" : "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAI2K6xRTV+tM4AmQul5oegt5utvG8v2pbME8rsCueV/4=\n-----END PUBLIC KEY-----\n", + "type" : "EDDSAVer", + "tests" : [ + { + "tcId" : 111, + "comment" : "Random test failure 27", + "msg" : "eef3bb0f617c17d0420c115c21c28e3762edc7b7fb048529b84a9c2bc6", + "sig" : "242ddb3a5d938d07af690b1b0ef0fa75842c5f9549bf39c8750f75614c712e7cbaf2e37cc0799db38b858d41aec5b9dd2fca6a3c8e082c10408e2cf3932b9d08", + "result" : "valid", + "flags" : [] + } + ] + } + ] +})abcd"; +} diff --git a/crypto/vm/cells/CellString.cpp b/crypto/vm/cells/CellString.cpp new file mode 100644 index 000000000..ad2cbf5f8 --- /dev/null +++ b/crypto/vm/cells/CellString.cpp @@ -0,0 +1,64 @@ +#include "CellString.h" +#include "td/utils/misc.h" + +#include "vm/cells/CellSlice.h" + +namespace vm { +td::Status CellString::store(CellBuilder &cb, td::Slice slice, unsigned int top_bits) { + td::uint32 size = td::narrow_cast(slice.size() * 8); + return store(cb, td::BitSlice(slice.ubegin(), size), top_bits); +} + +td::Status CellString::store(CellBuilder &cb, td::BitSlice slice, unsigned int top_bits) { + if (slice.size() > max_bytes * 8) { + return td::Status::Error("String is too long (1)"); + } + unsigned int head = td::min(slice.size(), td::min(cb.remaining_bits(), top_bits)) / 8 * 8; + auto max_bits = vm::Cell::max_bits / 8 * 8; + auto depth = 1 + (slice.size() - head + max_bits - 1) / max_bits; + if (depth > max_chain_length) { + return td::Status::Error("String is too long (2)"); + } + cb.append_bitslice(slice.subslice(0, head)); + slice.advance(head); + if (slice.size() == 0) { + return td::Status::OK(); + } + CellBuilder child_cb; + store(child_cb, std::move(slice)); + cb.store_ref(child_cb.finalize()); + return td::Status::OK(); +} + +template +void CellString::for_each(F &&f, CellSlice &cs, unsigned int top_bits) { + unsigned int head = td::min(cs.size(), top_bits); + f(cs.prefetch_bits(head)); + if (!cs.have_refs()) { + return; + } + auto ref = cs.prefetch_ref(); + while (true) { + auto cs = vm::load_cell_slice(ref); + f(cs.prefetch_bits(cs.size())); + if (!cs.have_refs()) { + return; + } + ref = cs.prefetch_ref(); + } +} + +td::Result CellString::load(CellSlice &cs, unsigned int top_bits) { + unsigned int size = 0; + for_each([&](auto slice) { size += slice.size(); }, cs, top_bits); + if (size % 8 != 0) { + return td::Status::Error("Size is not divisible by 8"); + } + std::string res(size / 8, 0); + + td::BitPtr to(td::MutableSlice(res).ubegin()); + for_each([&](auto slice) { to.concat(slice); }, cs, top_bits); + CHECK(to.offs == (int)size); + return res; +} +} // namespace vm diff --git a/crypto/vm/cells/CellString.h b/crypto/vm/cells/CellString.h new file mode 100644 index 000000000..89c933d87 --- /dev/null +++ b/crypto/vm/cells/CellString.h @@ -0,0 +1,22 @@ +#pragma once + +#include "td/utils/Status.h" + +#include "vm/cells/CellBuilder.h" + +namespace vm { +class CellString { + public: + static constexpr unsigned int max_bytes = 1024; + static constexpr unsigned int max_chain_length = 16; + + static td::Status store(CellBuilder &cb, td::Slice slice, unsigned int top_bits = Cell::max_bits); + static td::Status store(CellBuilder &cb, td::BitSlice slice, unsigned int top_bits = Cell::max_bits); + static td::Result load(CellSlice &cs, unsigned int top_bits = Cell::max_bits); + + private: + template + static void for_each(F &&f, CellSlice &cs, unsigned int top_bits = Cell::max_bits); +}; + +} // namespace vm diff --git a/crypto/vm/continuation.cpp b/crypto/vm/continuation.cpp index 7b059dcb5..d4952769a 100644 --- a/crypto/vm/continuation.cpp +++ b/crypto/vm/continuation.cpp @@ -744,6 +744,9 @@ int VmState::run() { } } while (!res); // LOG(INFO) << "[EN] data cells: " << DataCell::get_total_data_cells(); + if ((res | 1) == -1) { + commit(); + } return res; } diff --git a/crypto/vm/continuation.h b/crypto/vm/continuation.h index 1c5f8cd56..c6c9d320e 100644 --- a/crypto/vm/continuation.h +++ b/crypto/vm/continuation.h @@ -154,7 +154,7 @@ struct ControlData { class Continuation : public td::CntObject { public: - virtual int jump(VmState* st) const & = 0; + virtual int jump(VmState* st) const& = 0; virtual int jump_w(VmState* st) &; virtual ControlData* get_cdata() { return 0; @@ -184,7 +184,7 @@ class QuitCont : public Continuation { QuitCont(int _code = 0) : exit_code(_code) { } ~QuitCont() override = default; - int jump(VmState* st) const & override { + int jump(VmState* st) const& override { return ~exit_code; } }; @@ -193,7 +193,7 @@ class ExcQuitCont : public Continuation { public: ExcQuitCont() = default; ~ExcQuitCont() override = default; - int jump(VmState* st) const & override; + int jump(VmState* st) const& override; }; class PushIntCont : public Continuation { @@ -204,7 +204,7 @@ class PushIntCont : public Continuation { PushIntCont(int val, Ref _next) : push_val(val), next(_next) { } ~PushIntCont() override = default; - int jump(VmState* st) const & override; + int jump(VmState* st) const& override; int jump_w(VmState* st) & override; }; @@ -217,7 +217,7 @@ class RepeatCont : public Continuation { : body(std::move(_body)), after(std::move(_after)), count(_count) { } ~RepeatCont() override = default; - int jump(VmState* st) const & override; + int jump(VmState* st) const& override; int jump_w(VmState* st) & override; }; @@ -228,7 +228,7 @@ class AgainCont : public Continuation { AgainCont(Ref _body) : body(std::move(_body)) { } ~AgainCont() override = default; - int jump(VmState* st) const & override; + int jump(VmState* st) const& override; int jump_w(VmState* st) & override; }; @@ -239,7 +239,7 @@ class UntilCont : public Continuation { UntilCont(Ref _body, Ref _after) : body(std::move(_body)), after(std::move(_after)) { } ~UntilCont() override = default; - int jump(VmState* st) const & override; + int jump(VmState* st) const& override; int jump_w(VmState* st) & override; }; @@ -252,7 +252,7 @@ class WhileCont : public Continuation { : cond(std::move(_cond)), body(std::move(_body)), after(std::move(_after)), chkcond(_chk) { } ~WhileCont() override = default; - int jump(VmState* st) const & override; + int jump(VmState* st) const& override; int jump_w(VmState* st) & override; }; @@ -268,7 +268,7 @@ class ArgContExt : public Continuation { ArgContExt(const ArgContExt&) = default; ArgContExt(ArgContExt&&) = default; ~ArgContExt() override = default; - int jump(VmState* st) const & override; + int jump(VmState* st) const& override; int jump_w(VmState* st) & override; ControlData* get_cdata() override { return &data; @@ -303,7 +303,7 @@ class OrdCont : public Continuation { td::CntObject* make_copy() const override { return new OrdCont{*this}; } - int jump(VmState* st) const & override; + int jump(VmState* st) const& override; int jump_w(VmState* st) & override; ControlData* get_cdata() override { @@ -321,7 +321,7 @@ class OrdCont : public Continuation { Ref get_stack_ref() const { return data.stack; } - Ref copy_ord() const & { + Ref copy_ord() const& { return Ref{true, *this}; } Ref copy_ord() && { @@ -375,10 +375,16 @@ struct GasLimits { } }; +struct CommittedState { + Ref c4, c5; + bool committed{false}; +}; + class VmState final : public VmStateInterface { Ref code; Ref stack; ControlRegs cr; + CommittedState cstate; int cp; long long steps{0}; const DispatchTable* dispatch; @@ -388,6 +394,8 @@ class VmState final : public VmStateInterface { std::vector> libraries; int stack_trace{0}, debug_off{0}; + bool chksig_always_succeed{false}; + public: static constexpr unsigned cell_load_gas_price = 100, cell_create_gas_price = 500, exception_gas_price = 50, tuple_entry_gas_price = 1; @@ -401,6 +409,10 @@ class VmState final : public VmStateInterface { VmState(Ref code_cell, Args&&... args) : VmState(convert_code_cell(std::move(code_cell)), std::forward(args)...) { } + VmState(const VmState&) = delete; + VmState(VmState&&) = delete; + VmState& operator=(const VmState&) = delete; + VmState& operator=(VmState&&) = delete; bool set_gas_limits(long long _max, long long _limit, long long _credit = 0); bool final_gas_ok() const { return gas.final_ok(); @@ -408,6 +420,12 @@ class VmState final : public VmStateInterface { long long gas_consumed() const { return gas.gas_consumed(); } + bool committed() const { + return cstate.committed; + } + const CommittedState& get_committed_state() const { + return cstate; + } void consume_gas(long long amount) { gas.consume(amount); } @@ -567,6 +585,18 @@ class VmState final : public VmStateInterface { return cont->is_unique() ? cont.unique_write().jump_w(this) : cont->jump(this); } static Ref convert_code_cell(Ref code_cell); + void commit() { + cstate.c4 = cr.d[0]; + cstate.c5 = cr.d[1]; + cstate.committed = true; + } + + void set_chksig_always_succeed(bool flag) { + chksig_always_succeed = flag; + } + bool get_chksig_always_succeed() const { + return chksig_always_succeed; + } private: void init_cregs(bool same_c3 = false, bool push_0 = true); diff --git a/crypto/vm/excno.hpp b/crypto/vm/excno.hpp index f67d8a4e9..a18f890a3 100644 --- a/crypto/vm/excno.hpp +++ b/crypto/vm/excno.hpp @@ -18,6 +18,8 @@ */ #pragma once +#include "td/utils/Status.h" + namespace vm { enum class Excno : int { @@ -95,4 +97,19 @@ struct VmVirtError { struct VmFatal {}; +template +auto try_f(F&& f) noexcept -> decltype(f()) { + try { + return f(); + } catch (vm::VmError error) { + return td::Status::Error(PSLICE() << "Got a vm exception: " << error.get_msg()); + } catch (vm::VmVirtError error) { + return td::Status::Error(PSLICE() << "Got a vm virtualization exception: " << error.get_msg()); + } catch (vm::VmNoGas error) { + return td::Status::Error(PSLICE() << "Got a vm no gas exception: " << error.get_msg()); + } +} + +#define TRY_VM(f) ::vm::try_f([&] { return f; }) + } // namespace vm diff --git a/crypto/vm/tonops.cpp b/crypto/vm/tonops.cpp index 30d3d14b8..e2142f072 100644 --- a/crypto/vm/tonops.cpp +++ b/crypto/vm/tonops.cpp @@ -75,10 +75,17 @@ int exec_set_gas_limit(VmState* st) { return exec_set_gas_generic(st, gas); } +int exec_commit(VmState* st) { + VM_LOG(st) << "execute COMMIT"; + st->commit(); + return 0; +} + void register_basic_gas_ops(OpcodeTable& cp0) { using namespace std::placeholders; cp0.insert(OpcodeInstr::mksimple(0xf800, 16, "ACCEPT", exec_accept)) - .insert(OpcodeInstr::mksimple(0xf801, 16, "SETGASLIMIT", exec_set_gas_limit)); + .insert(OpcodeInstr::mksimple(0xf801, 16, "SETGASLIMIT", exec_set_gas_limit)) + .insert(OpcodeInstr::mksimple(0xf80f, 16, "COMMIT", exec_commit)); } void register_ton_gas_ops(OpcodeTable& cp0) { @@ -268,7 +275,7 @@ int exec_ed25519_check_signature(VmState* st, bool from_slice) { } td::Ed25519::PublicKey pub_key{td::SecureString(td::Slice{key, 32})}; auto res = pub_key.verify_signature(td::Slice{data, data_len}, td::Slice{signature, 64}); - stack.push_bool(res.is_ok()); + stack.push_bool(res.is_ok() || st->get_chksig_always_succeed()); return 0; } diff --git a/doc/tvm.tex b/doc/tvm.tex index f103b343c..26f7df247 100644 --- a/doc/tvm.tex +++ b/doc/tvm.tex @@ -2160,7 +2160,8 @@ \section*{Introduction} \item {\tt F802} --- {\tt BUYGAS} ($x$ -- ), computes the amount of gas that can be bought for $x$ nanograms, and sets $g_l$ accordingly in the same way as {\tt SETGASLIMIT}. \item {\tt F804} --- {\tt GRAMTOGAS} ($x$ -- $g$), computes the amount of gas that can be bought for $x$ nanograms. If $x$ is negative, returns 0. If $g$ exceeds $2^{63}-1$, it is replaced with this value. \item {\tt F805} --- {\tt GASTOGRAM} ($g$ -- $x$), computes the price of $g$ gas in nanograms. -\item {\tt F806}--{\tt F80F} --- Reserved for gas-related primitives. +\item {\tt F806}--{\tt F80E} --- Reserved for gas-related primitives. +\item {\tt F80F} --- {\tt COMMIT} ( -- ), commits the current state of registers {\tt c4} (``persistent data'') and {\tt c5} (``actions'') so that the current execution is considered ``successful'' with the saved values even if an exception is thrown later. \end{itemize} \nxsubpoint\emb{Pseudo-random number generator primitives} diff --git a/example/android/src/drinkless/org/ton/Client.java b/example/android/src/drinkless/org/ton/Client.java index 79534c5b5..fca82119a 100644 --- a/example/android/src/drinkless/org/ton/Client.java +++ b/example/android/src/drinkless/org/ton/Client.java @@ -13,18 +13,18 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; /** - * Main class for interaction with the TDLib. + * Main class for interaction with the tonlib. */ public final class Client implements Runnable { static { System.loadLibrary("native-lib"); } /** - * Interface for handler for results of queries to TDLib and incoming updates from TDLib. + * Interface for handler for results of queries to tonlib and incoming updates from tonlib. */ public interface ResultHandler { /** - * Callback called on result of query to TDLib or incoming update from TDLib. + * Callback called on result of query to tonlib or incoming update from tonlib. * * @param object Result of query or update of type TonApi.Update about new events. */ @@ -46,9 +46,9 @@ public interface ExceptionHandler { } /** - * Sends a request to the TDLib. + * Sends a request to the tonlib. * - * @param query Object representing a query to the TDLib. + * @param query Object representing a query to the tonlib. * @param resultHandler Result handler with onResult method which will be called with result * of the query or with TonApi.Error as parameter. If it is null, nothing * will be called. @@ -80,9 +80,9 @@ public void send(TonApi.Function query, ResultHandler resultHandler, ExceptionHa } /** - * Sends a request to the TDLib with an empty ExceptionHandler. + * Sends a request to the tonlib with an empty ExceptionHandler. * - * @param query Object representing a query to the TDLib. + * @param query Object representing a query to the tonlib. * @param resultHandler Result handler with onResult method which will be called with result * of the query or with TonApi.Error as parameter. If it is null, then * defaultExceptionHandler will be called. @@ -93,9 +93,9 @@ public void send(TonApi.Function query, ResultHandler resultHandler) { } /** - * Synchronously executes a TDLib request. Only a few marked accordingly requests can be executed synchronously. + * Synchronously executes a tonlib request. Only a few marked accordingly requests can be executed synchronously. * - * @param query Object representing a query to the TDLib. + * @param query Object representing a query to the tonlib. * @return request result. * @throws NullPointerException if query is null. */ @@ -107,10 +107,10 @@ public static TonApi.Object execute(TonApi.Function query) { } /** - * Replaces handler for incoming updates from the TDLib. + * Replaces handler for incoming updates from the tonlib. * * @param updatesHandler Handler with onResult method which will be called for every incoming - * update from the TDLib. + * update from the tonlib. * @param exceptionHandler Exception handler with onException method which will be called on * exception thrown from updatesHandler, if it is null, defaultExceptionHandler will be invoked. */ @@ -119,10 +119,10 @@ public void setUpdatesHandler(ResultHandler updatesHandler, ExceptionHandler exc } /** - * Replaces handler for incoming updates from the TDLib. Sets empty ExceptionHandler. + * Replaces handler for incoming updates from the tonlib. Sets empty ExceptionHandler. * * @param updatesHandler Handler with onResult method which will be called for every incoming - * update from the TDLib. + * update from the tonlib. */ public void setUpdatesHandler(ResultHandler updatesHandler) { setUpdatesHandler(updatesHandler, null); @@ -157,7 +157,7 @@ public void run() { */ public static Client create(ResultHandler updatesHandler, ExceptionHandler updatesExceptionHandler, ExceptionHandler defaultExceptionHandler) { Client client = new Client(updatesHandler, updatesExceptionHandler, defaultExceptionHandler); - new Thread(client, "TDLib thread").start(); + new Thread(client, "tonlib thread").start(); return client; } diff --git a/example/android/test/ton/src/main/java/drinkless/org/ton/Client.java b/example/android/test/ton/src/main/java/drinkless/org/ton/Client.java index 79534c5b5..fca82119a 100644 --- a/example/android/test/ton/src/main/java/drinkless/org/ton/Client.java +++ b/example/android/test/ton/src/main/java/drinkless/org/ton/Client.java @@ -13,18 +13,18 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; /** - * Main class for interaction with the TDLib. + * Main class for interaction with the tonlib. */ public final class Client implements Runnable { static { System.loadLibrary("native-lib"); } /** - * Interface for handler for results of queries to TDLib and incoming updates from TDLib. + * Interface for handler for results of queries to tonlib and incoming updates from tonlib. */ public interface ResultHandler { /** - * Callback called on result of query to TDLib or incoming update from TDLib. + * Callback called on result of query to tonlib or incoming update from tonlib. * * @param object Result of query or update of type TonApi.Update about new events. */ @@ -46,9 +46,9 @@ public interface ExceptionHandler { } /** - * Sends a request to the TDLib. + * Sends a request to the tonlib. * - * @param query Object representing a query to the TDLib. + * @param query Object representing a query to the tonlib. * @param resultHandler Result handler with onResult method which will be called with result * of the query or with TonApi.Error as parameter. If it is null, nothing * will be called. @@ -80,9 +80,9 @@ public void send(TonApi.Function query, ResultHandler resultHandler, ExceptionHa } /** - * Sends a request to the TDLib with an empty ExceptionHandler. + * Sends a request to the tonlib with an empty ExceptionHandler. * - * @param query Object representing a query to the TDLib. + * @param query Object representing a query to the tonlib. * @param resultHandler Result handler with onResult method which will be called with result * of the query or with TonApi.Error as parameter. If it is null, then * defaultExceptionHandler will be called. @@ -93,9 +93,9 @@ public void send(TonApi.Function query, ResultHandler resultHandler) { } /** - * Synchronously executes a TDLib request. Only a few marked accordingly requests can be executed synchronously. + * Synchronously executes a tonlib request. Only a few marked accordingly requests can be executed synchronously. * - * @param query Object representing a query to the TDLib. + * @param query Object representing a query to the tonlib. * @return request result. * @throws NullPointerException if query is null. */ @@ -107,10 +107,10 @@ public static TonApi.Object execute(TonApi.Function query) { } /** - * Replaces handler for incoming updates from the TDLib. + * Replaces handler for incoming updates from the tonlib. * * @param updatesHandler Handler with onResult method which will be called for every incoming - * update from the TDLib. + * update from the tonlib. * @param exceptionHandler Exception handler with onException method which will be called on * exception thrown from updatesHandler, if it is null, defaultExceptionHandler will be invoked. */ @@ -119,10 +119,10 @@ public void setUpdatesHandler(ResultHandler updatesHandler, ExceptionHandler exc } /** - * Replaces handler for incoming updates from the TDLib. Sets empty ExceptionHandler. + * Replaces handler for incoming updates from the tonlib. Sets empty ExceptionHandler. * * @param updatesHandler Handler with onResult method which will be called for every incoming - * update from the TDLib. + * update from the tonlib. */ public void setUpdatesHandler(ResultHandler updatesHandler) { setUpdatesHandler(updatesHandler, null); @@ -157,7 +157,7 @@ public void run() { */ public static Client create(ResultHandler updatesHandler, ExceptionHandler updatesExceptionHandler, ExceptionHandler defaultExceptionHandler) { Client client = new Client(updatesHandler, updatesExceptionHandler, defaultExceptionHandler); - new Thread(client, "TDLib thread").start(); + new Thread(client, "tonlib thread").start(); return client; } diff --git a/example/cpp/tonjson_example.cpp b/example/cpp/tonjson_example.cpp index d00dec9fd..c60d9fd08 100644 --- a/example/cpp/tonjson_example.cpp +++ b/example/cpp/tonjson_example.cpp @@ -28,12 +28,12 @@ #include #include -// Basic example of TONLib JSON interface usage. +// Basic example of tonlib JSON interface usage. // Native interface should be preferred instead in C++, so here is only an example of // the main event cycle, which should be essentially the same for all languages. int main() { - // disable TDLib logging + // disable tonlib logging void *client = tonlib_client_json_create(); // somehow share the client with other threads, which will be able to send requests via tonlib_client_json_send diff --git a/lite-client/lite-client-common.cpp b/lite-client/lite-client-common.cpp index cc6949dea..82503faf3 100644 --- a/lite-client/lite-client-common.cpp +++ b/lite-client/lite-client-common.cpp @@ -4,6 +4,7 @@ #include "tl-utils/lite-utils.hpp" #include "ton/lite-tl.hpp" #include "td/utils/overloaded.h" +#include "td/utils/Random.h" using namespace std::literals::string_literals; @@ -74,4 +75,25 @@ td::Result> deserialize_proof_chain( LOG(DEBUG) << "deserialized a BlkProofChain of " << chain->link_count() << " links"; return std::move(chain); } +td::Ref prepare_vm_c7(ton::UnixTime now, ton::LogicalTime lt, td::Ref my_addr, + const block::CurrencyCollection& balance) { + td::BitArray<256> rand_seed; + td::RefInt256 rand_seed_int{true}; + td::Random::secure_bytes(rand_seed.as_slice()); + if (!rand_seed_int.unique_write().import_bits(rand_seed.cbits(), 256, false)) { + return {}; + } + auto tuple = vm::make_tuple_ref(td::make_refint(0x076ef1ea), // [ magic:0x076ef1ea + td::make_refint(0), // actions:Integer + td::make_refint(0), // msgs_sent:Integer + td::make_refint(now), // unixtime:Integer + td::make_refint(lt), // block_lt:Integer + td::make_refint(lt), // trans_lt:Integer + std::move(rand_seed_int), // rand_seed:Integer + balance.as_vm_tuple(), // balance_remaining:[Integer (Maybe Cell)] + my_addr, // myself:MsgAddressInt + vm::StackEntry()); // global_config:(Maybe Cell) ] = SmartContractInfo; + LOG(DEBUG) << "SmartContractInfo initialized with " << vm::StackEntry(tuple).to_string(); + return vm::make_tuple_ref(std::move(tuple)); +} } // namespace liteclient diff --git a/lite-client/lite-client-common.h b/lite-client/lite-client-common.h index cbadd2b42..bca2c3079 100644 --- a/lite-client/lite-client-common.h +++ b/lite-client/lite-client-common.h @@ -7,4 +7,7 @@ namespace liteclient { td::Result> deserialize_proof_chain( ton::lite_api::object_ptr f); -} + +td::Ref prepare_vm_c7(ton::UnixTime now, ton::LogicalTime lt, td::Ref my_addr, + const block::CurrencyCollection& balance); +} // namespace liteclient diff --git a/lite-client/lite-client.cpp b/lite-client/lite-client.cpp index 552979de8..54eb61b6a 100644 --- a/lite-client/lite-client.cpp +++ b/lite-client/lite-client.cpp @@ -1223,28 +1223,6 @@ void TestNode::got_account_state(ton::BlockIdExt ref_blk, ton::BlockIdExt blk, t } } -Ref TestNode::prepare_vm_c7(ton::UnixTime now, ton::LogicalTime lt, Ref my_addr, - const block::CurrencyCollection& balance) const { - td::BitArray<256> rand_seed; - td::RefInt256 rand_seed_int{true}; - prng::rand_gen().rand_bytes(rand_seed.data(), 32); - if (!rand_seed_int.unique_write().import_bits(rand_seed.cbits(), 256, false)) { - return {}; - } - auto tuple = vm::make_tuple_ref(td::make_refint(0x076ef1ea), // [ magic:0x076ef1ea - td::make_refint(0), // actions:Integer - td::make_refint(0), // msgs_sent:Integer - td::make_refint(now), // unixtime:Integer - td::make_refint(lt), // block_lt:Integer - td::make_refint(lt), // trans_lt:Integer - std::move(rand_seed_int), // rand_seed:Integer - balance.as_vm_tuple(), // balance_remaining:[Integer (Maybe Cell)] - my_addr, // myself:MsgAddressInt - vm::StackEntry()); // global_config:(Maybe Cell) ] = SmartContractInfo; - LOG(DEBUG) << "SmartContractInfo initialized with " << vm::StackEntry(tuple).to_string(); - return vm::make_tuple_ref(std::move(tuple)); -} - void TestNode::run_smc_method(ton::BlockIdExt ref_blk, ton::BlockIdExt blk, ton::BlockIdExt shard_blk, td::BufferSlice shard_proof, td::BufferSlice proof, td::BufferSlice state, ton::WorkchainId workchain, ton::StdSmcAddress addr, std::string method, @@ -1309,7 +1287,7 @@ void TestNode::run_smc_method(ton::BlockIdExt ref_blk, ton::BlockIdExt blk, ton: vm::GasLimits gas{gas_limit}; LOG(DEBUG) << "creating VM"; vm::VmState vm{code, std::move(stack), gas, 1, data, vm::VmLog()}; - vm.set_c7(prepare_vm_c7(info.gen_utime, info.gen_lt, acc.addr, balance)); // tuple with SmartContractInfo + vm.set_c7(liteclient::prepare_vm_c7(info.gen_utime, info.gen_lt, acc.addr, balance)); // tuple with SmartContractInfo // vm.incr_stack_trace(1); // enable stack dump after each step LOG(INFO) << "starting VM to run method `" << method << "` (" << method_id << ") of smart contract " << workchain << ":" << addr.to_hex(); diff --git a/lite-client/lite-client.h b/lite-client/lite-client.h index 17cc29555..36830e8d5 100644 --- a/lite-client/lite-client.h +++ b/lite-client/lite-client.h @@ -119,8 +119,6 @@ class TestNode : public td::actor::Actor { td::BufferSlice shard_proof, td::BufferSlice proof, td::BufferSlice state, ton::WorkchainId workchain, ton::StdSmcAddress addr, std::string method, std::vector params); - Ref prepare_vm_c7(ton::UnixTime now, ton::LogicalTime lt, Ref my_addr, - const block::CurrencyCollection& balance) const; bool get_all_shards(bool use_last = true, ton::BlockIdExt blkid = {}); void got_all_shards(ton::BlockIdExt blk, td::BufferSlice proof, td::BufferSlice data); bool get_config_params(ton::BlockIdExt blkid, int mode = 0, std::string filename = ""); diff --git a/tdactor/benchmark/benchmark.cpp b/tdactor/benchmark/benchmark.cpp index 09986294b..442bb84c6 100644 --- a/tdactor/benchmark/benchmark.cpp +++ b/tdactor/benchmark/benchmark.cpp @@ -1145,6 +1145,273 @@ void run_queue_bench(int n, int m) { #endif } +struct Sem { + public: + void post() { + if (++cnt_ == 0) { + { + std::unique_lock lk(mutex_); + } + cnd_.notify_one(); + } + } + void wait(int cnt = 1) { + auto was = cnt_.fetch_sub(cnt); + if (was >= cnt) { + return; + } + std::unique_lock lk(mutex_); + cnd_.wait(lk, [&] { return cnt_ >= 0; }); + } + + private: + std::mutex mutex_; + std::condition_variable cnd_; + std::atomic cnt_{0}; +}; + +class ChainedSpawn : public td::Benchmark { + public: + ChainedSpawn(bool use_io) : use_io_(use_io) { + } + std::string get_description() const { + return PSTRING() << "Chained create_actor use_io(" << use_io_ << ")"; + } + + void run(int n) { + class Task : public td::actor::Actor { + public: + Task(int n, Sem *sem) : n_(n), sem_(sem) { + } + void start_up() override { + if (n_ == 0) { + sem_->post(); + } else { + td::actor::create_actor("Task", n_ - 1, sem_).release(); + } + stop(); + }; + + private: + int n_; + Sem *sem_{nullptr}; + }; + td::actor::Scheduler scheduler{{8}}; + auto sch = td::thread([&] { scheduler.run(); }); + + Sem sem; + scheduler.run_in_context_external([&] { + for (int i = 0; i < n; i++) { + td::actor::create_actor(td::actor::ActorOptions().with_name("Task").with_poll(use_io_), 1000, &sem) + .release(); + sem.wait(); + } + td::actor::SchedulerContext::get()->stop(); + }); + + sch.join(); + } + + private: + bool use_io_{false}; +}; + +class ChainedSpawnInplace : public td::Benchmark { + public: + ChainedSpawnInplace(bool use_io) : use_io_(use_io) { + } + std::string get_description() const { + return PSTRING() << "Chained send_signal(self) use_io(" << use_io_ << ")"; + } + + void run(int n) { + class Task : public td::actor::Actor { + public: + Task(int n, Sem *sem) : n_(n), sem_(sem) { + } + void loop() override { + if (n_ == 0) { + sem_->post(); + stop(); + } else { + n_--; + send_signals(actor_id(this), td::actor::ActorSignals::wakeup()); + } + }; + + private: + int n_; + Sem *sem_; + }; + td::actor::Scheduler scheduler{{8}}; + auto sch = td::thread([&] { scheduler.run(); }); + + Sem sem; + scheduler.run_in_context_external([&] { + for (int i = 0; i < n; i++) { + td::actor::create_actor(td::actor::ActorOptions().with_name("Task").with_poll(use_io_), 1000, &sem) + .release(); + sem.wait(); + } + td::actor::SchedulerContext::get()->stop(); + }); + + sch.join(); + } + + private: + bool use_io_{false}; +}; + +class PingPong : public td::Benchmark { + public: + PingPong(bool use_io) : use_io_(use_io) { + } + std::string get_description() const { + return PSTRING() << "PingPong use_io(" << use_io_ << ")"; + } + + void run(int n) { + if (n < 3) { + n = 3; + } + class Task : public td::actor::Actor { + public: + explicit Task(Sem *sem) : sem_(sem) { + } + void set_peer(td::actor::ActorId peer) { + peer_ = peer; + } + void ping(int n) { + if (n < 0) { + sem_->post(); + stop(); + } + send_closure(peer_, &Task::ping, n - 1); + } + + private: + td::actor::ActorId peer_; + Sem *sem_; + }; + td::actor::Scheduler scheduler{{8}}; + auto sch = td::thread([&] { scheduler.run(); }); + + Sem sem; + scheduler.run_in_context_external([&] { + for (int i = 0; i < n; i++) { + auto a = td::actor::create_actor(td::actor::ActorOptions().with_name("Task").with_poll(use_io_), &sem) + .release(); + auto b = td::actor::create_actor(td::actor::ActorOptions().with_name("Task").with_poll(use_io_), &sem) + .release(); + send_closure(a, &Task::set_peer, b); + send_closure(b, &Task::set_peer, a); + send_closure(a, &Task::ping, 1000); + sem.wait(2); + } + td::actor::SchedulerContext::get()->stop(); + }); + + sch.join(); + } + + private: + bool use_io_{false}; +}; + +class SpawnMany : public td::Benchmark { + public: + SpawnMany(bool use_io) : use_io_(use_io) { + } + std::string get_description() const { + return PSTRING() << "Spawn many use_io(" << use_io_ << ")"; + } + + void run(int n) { + class Task : public td::actor::Actor { + public: + Task(Sem *sem) : sem_(sem) { + } + void start_up() override { + sem_->post(); + stop(); + }; + + private: + Sem *sem_; + }; + td::actor::Scheduler scheduler{{8}}; + Sem sem; + auto sch = td::thread([&] { scheduler.run(); }); + scheduler.run_in_context_external([&] { + for (int i = 0; i < n; i++) { + int spawn_cnt = 10000; + for (int j = 0; j < spawn_cnt; j++) { + td::actor::create_actor(td::actor::ActorOptions().with_name("Task").with_poll(use_io_), &sem).release(); + } + sem.wait(spawn_cnt); + } + td::actor::SchedulerContext::get()->stop(); + }); + sch.join(); + } + + private: + bool use_io_{false}; +}; + +class YieldMany : public td::Benchmark { + public: + YieldMany(bool use_io) : use_io_(use_io) { + } + std::string get_description() const { + return PSTRING() << "Yield many use_io(" << use_io_ << ")"; + } + + void run(int n) { + int num_yield = 1000; + unsigned tasks_per_cpu = 50; + unsigned cpu_n = td::thread::hardware_concurrency(); + class Task : public td::actor::Actor { + public: + explicit Task(int n, Sem *sem) : n_(n), sem_(sem) { + } + void loop() override { + if (n_ == 0) { + sem_->post(); + stop(); + } else { + n_--; + yield(); + } + }; + + private: + int n_; + Sem *sem_; + }; + td::actor::Scheduler scheduler{{cpu_n}}; + auto sch = td::thread([&] { scheduler.run(); }); + unsigned tasks = tasks_per_cpu * cpu_n; + Sem sem; + scheduler.run_in_context_external([&] { + for (int i = 0; i < n; i++) { + for (unsigned j = 0; j < tasks; j++) { + td::actor::create_actor(td::actor::ActorOptions().with_name("Task").with_poll(use_io_), num_yield, &sem) + .release(); + } + sem.wait(tasks); + } + }); + + scheduler.run_in_context_external([&] { td::actor::SchedulerContext::get()->stop(); }); + sch.join(); + } + + private: + bool use_io_{false}; +}; + int main(int argc, char **argv) { if (argc > 1) { if (argv[1][0] == 'a') { @@ -1159,6 +1426,18 @@ int main(int argc, char **argv) { return 0; } + bench(YieldMany(false)); + bench(YieldMany(true)); + bench(SpawnMany(false)); + bench(SpawnMany(true)); + bench(PingPong(false)); + bench(PingPong(true)); + bench(ChainedSpawnInplace(false)); + bench(ChainedSpawnInplace(true)); + bench(ChainedSpawn(false)); + bench(ChainedSpawn(true)); + return 0; + bench(ActorDummyQuery()); bench(ActorExecutorBenchmark()); bench(ActorSignalQuery()); diff --git a/tdactor/td/actor/PromiseFuture.h b/tdactor/td/actor/PromiseFuture.h index 1070e482d..9805a76cb 100644 --- a/tdactor/td/actor/PromiseFuture.h +++ b/tdactor/td/actor/PromiseFuture.h @@ -225,6 +225,7 @@ class Promise { promise_->set_error(std::move(error)); promise_.reset(); } + void set_result(Result &&result) { if (!promise_) { return; @@ -260,6 +261,28 @@ class Promise { explicit operator bool() { return static_cast(promise_); } + template + auto do_wrap(V &&value, F &&func) { + if (value.is_ok()) { + set_result(func(value.move_as_ok())); + } else { + set_error(value.move_as_error()); + } + } + + template + auto do_wrap(td::Status status, F &&func) { + set_error(std::move(status)); + } + + template + auto wrap(F &&func) { + return [promise = std::move(*this), func = std::move(func)](auto &&res) mutable { + promise.do_wrap(std::move(res), std::move(func)); + }; + } + template + auto send_closure(ArgsT &&... args); private: std::unique_ptr> promise_; diff --git a/tdactor/td/actor/actor.h b/tdactor/td/actor/actor.h index 85685de72..b4eb6f6d6 100644 --- a/tdactor/td/actor/actor.h +++ b/tdactor/td/actor/actor.h @@ -162,4 +162,29 @@ void send_signals_later(ActorIdT &&actor_id, ActorSignals signals) { detail::send_signals_later(id.as_actor_ref(), signals); } } // namespace actor + +class SendClosure { + public: + template + void operator()(ArgsT &&... args) const { + td::actor::send_closure(std::forward(args)...); + } +}; + +template +template +auto Promise::send_closure(ArgsT &&... args) { + return [promise = std::move(*this), t = std::make_tuple(std::forward(args)...)](auto &&r_res) mutable { + TRY_RESULT_PROMISE(promise, res, std::move(r_res)); + td::call_tuple(SendClosure(), std::tuple_cat(std::move(t), std::make_tuple(std::move(res), std::move(promise)))); + }; +} + +template +auto promise_send_closure(ArgsT &&... args) { + return [t = std::make_tuple(std::forward(args)...)](auto &&res) mutable { + td::call_tuple(SendClosure(), std::tuple_cat(std::move(t), std::make_tuple(std::move(res)))); + }; +} + } // namespace td diff --git a/tdnet/td/net/TcpListener.cpp b/tdnet/td/net/TcpListener.cpp index dfc97e608..5e187342b 100644 --- a/tdnet/td/net/TcpListener.cpp +++ b/tdnet/td/net/TcpListener.cpp @@ -73,5 +73,57 @@ void TcpListener::loop() { return stop(); } } +TcpInfiniteListener::TcpInfiniteListener(int32 port, std::unique_ptr callback) + : port_(port), callback_(std::move(callback)) { +} + +void TcpInfiniteListener::start_up() { + loop(); +} + +void TcpInfiniteListener::hangup() { + close_flag_ = true; + tcp_listener_.reset(); + if (refcnt_ == 0) { + stop(); + } +} + +void TcpInfiniteListener::loop() { + if (!tcp_listener_.empty()) { + return; + } + class Callback : public TcpListener::Callback { + public: + Callback(actor::ActorShared parent) : parent_(std::move(parent)) { + } + void accept(SocketFd fd) override { + actor::send_closure(parent_, &TcpInfiniteListener::accept, std::move(fd)); + } + + private: + actor::ActorShared parent_; + }; + refcnt_++; + tcp_listener_ = actor::create_actor( + actor::ActorOptions().with_name(PSLICE() << "TcpListener" << tag("port", port_)).with_poll(), port_, + std::make_unique(actor_shared(this))); +} + +void TcpInfiniteListener::accept(SocketFd fd) { + callback_->accept(std::move(fd)); +} + +void TcpInfiniteListener::hangup_shared() { + refcnt_--; + tcp_listener_.reset(); + if (close_flag_) { + if (refcnt_ == 0) { + stop(); + } + } else { + alarm_timestamp() = Timestamp::in(5 /*5 seconds*/); + } +} } // namespace td diff --git a/tdnet/td/net/TcpListener.h b/tdnet/td/net/TcpListener.h index bccf9e52f..d83030eb0 100644 --- a/tdnet/td/net/TcpListener.h +++ b/tdnet/td/net/TcpListener.h @@ -49,4 +49,23 @@ class TcpListener : public td::actor::Actor, private td::ObserverBase { void loop() override; }; + +class TcpInfiniteListener : public actor::Actor { + public: + TcpInfiniteListener(int32 port, std::unique_ptr callback); + + private: + int32 port_; + std::unique_ptr callback_; + actor::ActorOwn tcp_listener_; + int32 refcnt_{0}; + bool close_flag_{false}; + + void start_up() override; + + void hangup() override; + void loop() override; + void accept(SocketFd fd); + void hangup_shared() override; +}; } // namespace td diff --git a/tdnet/td/net/UdpServer.cpp b/tdnet/td/net/UdpServer.cpp index 1a88d17f1..7dff5e761 100644 --- a/tdnet/td/net/UdpServer.cpp +++ b/tdnet/td/net/UdpServer.cpp @@ -130,70 +130,6 @@ void UdpServerImpl::hangup_shared() { stop(); } -class TcpInfiniteListener : public actor::Actor { - public: - TcpInfiniteListener(int32 port, std::unique_ptr callback) - : port_(port), callback_(std::move(callback)) { - } - - private: - int32 port_; - std::unique_ptr callback_; - actor::ActorOwn tcp_listener_; - int32 refcnt_{0}; - bool close_flag_{false}; - - void start_up() override { - loop(); - } - - void hangup() override { - close_flag_ = true; - tcp_listener_.reset(); - if (refcnt_ == 0) { - stop(); - } - } - - void loop() override { - if (!tcp_listener_.empty()) { - return; - } - class Callback : public TcpListener::Callback { - public: - Callback(actor::ActorShared parent) : parent_(std::move(parent)) { - } - void accept(SocketFd fd) override { - actor::send_closure(parent_, &TcpInfiniteListener::accept, std::move(fd)); - } - - private: - actor::ActorShared parent_; - }; - VLOG(udp_server) << "Create listener"; - refcnt_++; - tcp_listener_ = actor::create_actor( - actor::ActorOptions().with_name(PSLICE() << "TcpListener" << tag("port", port_)).with_poll(), port_, - std::make_unique(actor_shared(this))); - } - - void accept(SocketFd fd) { - callback_->accept(std::move(fd)); - } - - void hangup_shared() override { - refcnt_--; - tcp_listener_.reset(); - if (close_flag_) { - if (refcnt_ == 0) { - stop(); - } - } else { - alarm_timestamp() = Timestamp::in(5 /*5 seconds*/); - } - } -}; - class TcpClient : public td::actor::Actor, td::ObserverBase { public: class Callback { diff --git a/tdutils/td/utils/Status.h b/tdutils/td/utils/Status.h index 1759c0abe..6ac921005 100644 --- a/tdutils/td/utils/Status.h +++ b/tdutils/td/utils/Status.h @@ -48,6 +48,15 @@ } \ } +#define TRY_STATUS_PROMISE(promise_name, status) \ + { \ + auto try_status = (status); \ + if (try_status.is_error()) { \ + promise_name.set_error(std::move(try_status)); \ + return; \ + } \ + } + #define TRY_RESULT(name, result) TRY_RESULT_IMPL(TD_CONCAT(TD_CONCAT(r_, name), __LINE__), auto name, result) #define TRY_RESULT_PROMISE(promise_name, name, result) \ @@ -309,6 +318,11 @@ class Status { return std::move(*this); } + Auto move_as_ok() { + UNREACHABLE(); + return {}; + } + Status move_as_error_prefix(const Status &status) const TD_WARN_UNUSED_RESULT { return status.move_as_error_suffix(message()); } diff --git a/tdutils/td/utils/invoke.h b/tdutils/td/utils/invoke.h index dad0c238d..63b30ba25 100644 --- a/tdutils/td/utils/invoke.h +++ b/tdutils/td/utils/invoke.h @@ -128,7 +128,7 @@ auto invoke(F &&f, } template -auto call_tuple_impl(F &func, std::tuple &&tuple, IntSeq) { +auto call_tuple_impl(F &&func, std::tuple &&tuple, IntSeq) { return func(std::forward(std::get(tuple))...); } @@ -163,7 +163,7 @@ class LogicAnd { }; template -auto call_tuple(F &func, std::tuple &&tuple) { +auto call_tuple(F &&func, std::tuple &&tuple) { return detail::call_tuple_impl(func, std::move(tuple), detail::IntRange()); } diff --git a/tdutils/td/utils/tests.h b/tdutils/td/utils/tests.h index eb1fc3a82..464fa0ea8 100644 --- a/tdutils/td/utils/tests.h +++ b/tdutils/td/utils/tests.h @@ -38,6 +38,34 @@ namespace td { +class RandomSteps { + public: + struct Step { + std::function func; + td::uint32 weight; + }; + RandomSteps(std::vector steps) : steps_(std::move(steps)) { + for (auto &step : steps_) { + steps_sum_ += step.weight; + } + } + template + void step(Random &rnd) { + auto w = rnd() % steps_sum_; + for (auto &step : steps_) { + if (w < step.weight) { + step.func(); + break; + } + w -= step.weight; + } + } + + private: + std::vector steps_; + td::int32 steps_sum_ = 0; +}; + class RegressionTester { public: virtual ~RegressionTester() = default; diff --git a/tl/generate/JavadocTlDocumentationGenerator.php b/tl/generate/JavadocTlDocumentationGenerator.php index f33c9e99a..155d97246 100644 --- a/tl/generate/JavadocTlDocumentationGenerator.php +++ b/tl/generate/JavadocTlDocumentationGenerator.php @@ -128,7 +128,7 @@ protected function addGlobalDocumentation() $this->addDocumentation('public class TdApi {', << * It has no inner classes, functions or public members. @@ -138,7 +138,7 @@ protected function addGlobalDocumentation() $this->addDocumentation(' public abstract static class Object {', <<addDocumentation(' public abstract static class Function extends Object {', <<85o!+I*M;L;SgXHRnJ&@6eOFLnUe~nfG%QS-~`DQ zaF&QMN^E{AAFWfru2QRB1j!Px1NE{&Y8gO&x_L~<2jtNrXEq%U+qQ!sF{sBGuF7%6 zM9u8og6u(n& zHy>*N>Xm_lfB_^2av$7$sO7~B43qz7>hkdb9T5)~o4n7-$QGmp+4;C)Aq^A@PM~0A zU;wEBS;I2<;pFVeJ0c_|e^BJ`1c@S>ge$fnPGw*K$%9Qn4~)qVm879TAi&EFwiq0r z#SD`ft)wMD_Ao*%0tF&_N@`AONovYu12qv)h_QeSo^0ZyzzIqXAR9O*E6!Az>}#pK zd5Mn-qYg+I*`bpr?sgM~CmK-jq^5w94oKbP2@x8c@MxNBSRga`=ox8{e_6oNXM8mv z44}Hn3Vs1#&gK%o70eQ#)CRI0WECv2fW#)71Zl8<83{oq5XOuk9aND?!6uwA{Y;Y$ zL)0ff@Mf8u5e!woAXo)WeF(adDIsV^PCf%uGa+4KvOuVa0muU6aQJg&<`Hp3_5h|8 WNIn9E1*&^BLT$kIZ_Wr6s0RQ5$6#Fm delta 313 zcmaE|iTTG0X5L4$^{p77V9iF}-|V7gb7yAprX=Yl<>V)4>v`s-vQ E05Umo{r~^~ diff --git a/tl/generate/scheme/tonlib_api.tl b/tl/generate/scheme/tonlib_api.tl index 3fad6c36a..2a3f0f52c 100644 --- a/tl/generate/scheme/tonlib_api.tl +++ b/tl/generate/scheme/tonlib_api.tl @@ -24,7 +24,8 @@ config config:string blockchain_name:string use_callbacks_for_network:Bool ignor options config:config keystore_type:KeyStoreType = Options; key public_key:string secret:secureBytes = Key; -inputKey key:key local_password:secureBytes = InputKey; +inputKeyRegular key:key local_password:secureBytes = InputKey; +inputKeyFake = InputKey; exportedKey word_list:vector = ExportedKey; exportedPemKey pem:secureString = ExportedPemKey; exportedEncryptedKey data:secureBytes = ExportedEncryptedKey; @@ -68,29 +69,39 @@ sendGramsResult sent_until:int53 body_hash:bytes = SendGramsResult; syncStateDone = SyncState; syncStateInProgress from_seqno:int32 to_seqno:int32 current_seqno:int32 = SyncState; +fees in_fwd_fee:int53 storage_fee:int53 gas_fee:int53 fwd_fee:int53= Fees; +query.fees source_fees:fees destination_fees:fees = query.Fees; +query.info id:int53 valid_until:int53 body_hash:bytes = query.Info; + +smc.info id:int53 = smc.Info; + updateSendLiteServerQuery id:int64 data:bytes = Update; updateSyncState sync_state:SyncState = Update; -//@class LogStream @description Describes a stream to which TDLib internal log is written +//@class LogStream @description Describes a stream to which tonlib internal log is written //@description The log is written to stderr or an OS specific log logStreamDefault = LogStream; -//@description The log is written to a file @path Path to the file to where the internal TDLib log will be written @max_file_size Maximum size of the file to where the internal TDLib log is written before the file will be auto-rotated +//@description The log is written to a file @path Path to the file to where the internal tonlib log will be written @max_file_size Maximum size of the file to where the internal tonlib log is written before the file will be auto-rotated logStreamFile path:string max_file_size:int53 = LogStream; //@description The log is written nowhere logStreamEmpty = LogStream; -//@description Contains a TDLib internal log verbosity level @verbosity_level Log verbosity level +//@description Contains a tonlib internal log verbosity level @verbosity_level Log verbosity level logVerbosityLevel verbosity_level:int32 = LogVerbosityLevel; -//@description Contains a list of available TDLib internal log tags @tags List of log tags +//@description Contains a list of available tonlib internal log tags @tags List of log tags logTags tags:vector = LogTags; data bytes:secureBytes = Data; +tvm.cell bytes:string = tvm.Cell; + +liteServer.info now:int53 version:int32 capabilities:int64 = liteServer.Info; + ---functions--- init options:options = Ok; @@ -101,13 +112,13 @@ options.setConfig config:config = Ok; createNewKey local_password:secureBytes mnemonic_password:secureBytes random_extra_seed:secureBytes = Key; deleteKey key:key = Ok; deleteAllKeys = Ok; -exportKey input_key:inputKey = ExportedKey; -exportPemKey input_key:inputKey key_password:secureBytes = ExportedPemKey; -exportEncryptedKey input_key:inputKey key_password:secureBytes = ExportedEncryptedKey; +exportKey input_key:InputKey = ExportedKey; +exportPemKey input_key:InputKey key_password:secureBytes = ExportedPemKey; +exportEncryptedKey input_key:InputKey key_password:secureBytes = ExportedEncryptedKey; importKey local_password:secureBytes mnemonic_password:secureBytes exported_key:exportedKey = Key; importPemKey local_password:secureBytes key_password:secureBytes exported_key:exportedPemKey = Key; importEncryptedKey local_password:secureBytes key_password:secureBytes exported_encrypted_key:exportedEncryptedKey = Key; -changeLocalPassword input_key:inputKey new_local_password:secureBytes = Key; +changeLocalPassword input_key:InputKey new_local_password:secureBytes = Key; encrypt decrypted_data:secureBytes secret:secureBytes = Data; decrypt encrypted_data:secureBytes secret:secureBytes = Data; @@ -121,18 +132,20 @@ getBip39Hints prefix:string = Bip39Hints; //raw.init initial_account_state:raw.initialAccountState = Ok; raw.getAccountAddress initital_account_state:raw.initialAccountState = AccountAddress; raw.getAccountState account_address:accountAddress = raw.AccountState; -raw.sendMessage destination:accountAddress initial_account_state:bytes data:bytes = Ok; raw.getTransactions account_address:accountAddress from_transaction_id:internal.transactionId = raw.Transactions; +raw.sendMessage body:bytes = Ok; +raw.createAndSendMessage destination:accountAddress initial_account_state:bytes data:bytes = Ok; +raw.createQuery destination:accountAddress init_code:bytes init_data:bytes body:bytes = query.Info; -testWallet.init private_key:inputKey = Ok; +testWallet.init private_key:InputKey = Ok; testWallet.getAccountAddress initital_account_state:testWallet.initialAccountState = AccountAddress; testWallet.getAccountState account_address:accountAddress = testWallet.AccountState; -testWallet.sendGrams private_key:inputKey destination:accountAddress seqno:int32 amount:int64 message:bytes = SendGramsResult; +testWallet.sendGrams private_key:InputKey destination:accountAddress seqno:int32 amount:int64 message:bytes = SendGramsResult; -wallet.init private_key:inputKey = Ok; +wallet.init private_key:InputKey = Ok; wallet.getAccountAddress initital_account_state:wallet.initialAccountState = AccountAddress; wallet.getAccountState account_address:accountAddress = wallet.AccountState; -wallet.sendGrams private_key:inputKey destination:accountAddress seqno:int32 valid_until:int53 amount:int64 message:bytes = SendGramsResult; +wallet.sendGrams private_key:InputKey destination:accountAddress seqno:int32 valid_until:int53 amount:int64 message:bytes = SendGramsResult; testGiver.getAccountState = testGiver.AccountState; testGiver.getAccountAddress = AccountAddress; @@ -142,36 +155,50 @@ sync = Ok; //generic.getAccountAddress initital_account_state:generic.InitialAccountState = AccountAddress; generic.getAccountState account_address:accountAddress = generic.AccountState; -generic.sendGrams private_key:inputKey source:accountAddress destination:accountAddress amount:int64 timeout:int32 allow_send_to_uninited:Bool message:bytes = SendGramsResult; +generic.sendGrams private_key:InputKey source:accountAddress destination:accountAddress amount:int64 timeout:int32 allow_send_to_uninited:Bool message:bytes = SendGramsResult; + +generic.createSendGramsQuery private_key:InputKey source:accountAddress destination:accountAddress amount:int64 timeout:int32 allow_send_to_uninited:Bool message:bytes = query.Info; + +query.send id:int53 = Ok; +query.forget id:int53 = Ok; +query.estimateFees id:int53 ignore_chksig:Bool = query.Fees; +query.getInfo id:int53 = query.Info; + +smc.load account_address:accountAddress = smc.Info; +smc.getCode id:int53 = tvm.Cell; +smc.getData id:int53 = tvm.Cell; +smc.getState id:int53 = tvm.Cell; onLiteServerQueryResult id:int64 bytes:bytes = Ok; onLiteServerQueryError id:int64 error:error = Ok; runTests dir:string = Ok; -//@description Sets new log stream for internal logging of TDLib. This is an offline method. Can be called before authorization. Can be called synchronously @log_stream New log stream +liteServer.getInfo = liteServer.Info; + +//@description Sets new log stream for internal logging of tonlib. This is an offline method. Can be called before authorization. Can be called synchronously @log_stream New log stream setLogStream log_stream:LogStream = Ok; -//@description Returns information about currently used log stream for internal logging of TDLib. This is an offline method. Can be called before authorization. Can be called synchronously +//@description Returns information about currently used log stream for internal logging of tonlib. This is an offline method. Can be called before authorization. Can be called synchronously getLogStream = LogStream; -//@description Sets the verbosity level of the internal logging of TDLib. This is an offline method. Can be called before authorization. Can be called synchronously +//@description Sets the verbosity level of the internal logging of tonlib. This is an offline method. Can be called before authorization. Can be called synchronously //@new_verbosity_level New value of the verbosity level for logging. Value 0 corresponds to fatal errors, value 1 corresponds to errors, value 2 corresponds to warnings and debug warnings, value 3 corresponds to informational, value 4 corresponds to debug, value 5 corresponds to verbose debug, value greater than 5 and up to 1023 can be used to enable even more logging setLogVerbosityLevel new_verbosity_level:int32 = Ok; -//@description Returns current verbosity level of the internal logging of TDLib. This is an offline method. Can be called before authorization. Can be called synchronously +//@description Returns current verbosity level of the internal logging of tonlib. This is an offline method. Can be called before authorization. Can be called synchronously getLogVerbosityLevel = LogVerbosityLevel; -//@description Returns list of available TDLib internal log tags, for example, ["actor", "binlog", "connections", "notifications", "proxy"]. This is an offline method. Can be called before authorization. Can be called synchronously +//@description Returns list of available tonlib internal log tags, for example, ["actor", "binlog", "connections", "notifications", "proxy"]. This is an offline method. Can be called before authorization. Can be called synchronously getLogTags = LogTags; -//@description Sets the verbosity level for a specified TDLib internal log tag. This is an offline method. Can be called before authorization. Can be called synchronously +//@description Sets the verbosity level for a specified tonlib internal log tag. This is an offline method. Can be called before authorization. Can be called synchronously //@tag Logging tag to change verbosity level @new_verbosity_level New verbosity level; 1-1024 setLogTagVerbosityLevel tag:string new_verbosity_level:int32 = Ok; -//@description Returns current verbosity level for a specified TDLib internal log tag. This is an offline method. Can be called before authorization. Can be called synchronously @tag Logging tag to change verbosity level +//@description Returns current verbosity level for a specified tonlib internal log tag. This is an offline method. Can be called before authorization. Can be called synchronously @tag Logging tag to change verbosity level getLogTagVerbosityLevel tag:string = LogVerbosityLevel; -//@description Adds a message to TDLib internal log. This is an offline method. Can be called before authorization. Can be called synchronously +//@description Adds a message to tonlib internal log. This is an offline method. Can be called before authorization. Can be called synchronously //@verbosity_level Minimum verbosity level needed for the message to be logged, 0-1023 @text Text of a message to log addLogMessage verbosity_level:int32 text:string = Ok; diff --git a/tl/generate/scheme/tonlib_api.tlo b/tl/generate/scheme/tonlib_api.tlo index c48574b2d510507701d3edaa452b167321fe7267..d7c0e04957ea94a8765b1d6349812bb11a8c80ec 100644 GIT binary patch delta 1675 zcmaJ>Z)h837|$iw_VRDJwkb`Q*k(&&Ggs2OwCkK1+vw<4tb(>8vT42M-lpf0yYB9~ zQWV;usE8ZHhe@HLg{|P|mq|JtBAe4OPzr9KAIj7g#Ts#wfeeO%_&%4r7=qjT;kftt z`+I)R^SleWm08C7(|qS{n|YESdv7w%&o+Kbv;BlJ`Fyaz z+Xo$f?VndJ__C5thKPEcsEGl2Ovz6KHg#Uuz1?+elBm-O8X+%l_PNiUf6@6GpdS}tvP!VRV|Kg+>^+Oyu}`Kv3Oems}xBiXE_DAvv~slti6gKWEjZj7R% znDKx0TEieq|AQNA+gy@7GBzcSP&KUaEpTzOABsHB)nx?D5U%q97;fRI&;u(WE{*}L z;=gNk9r>XDmmhtSS?n_;Gno}sBmlm552x^_()iM2e_!@k#{I%kLX~KPh|o^$tL@H|>9j|6hqO73|Jsi{nYjDWQ6i}R89pzP%6d7I$2YjBiFd>XelyM0whrNwW zGgf?zfcfxX5fwc!c)~kouv0x|aBIq>nkGmHl4wJWNCblydeDX!iKa`kpi=;4L#nH= z-G{d2A~A^}pdSQL(RQrThWE-c?UHD;sAFdo?(uOJ^@Z-{bv9C-48zZ6!#jE+JcL&A zY{LGrs4H-xfZbmnn=~C8YKeDPafaZnu%E?C!QaZ;nH0prZ?czr`68ST@2W9cTDTov zva>TzyBFS%m@?QC?}R(90IRJIehATMSIHEfkG8R&p804MUX6B}qLL*Cx1#m!e~o)W z86p!^N+;4P5pX|b@t`W?w5M??C@?G(W@BN~dolL2Q9Im;>g-OReGSHA7OutEP)*k# zhfhA+P_bzlqT?Rfthu2f%S}L8;npKpnb7{H?p|vs?IAWYN0IbnyRl8s)fm9F*yNQm zvZ9ia^!QOt%D^dS>z3*^j=cNDC!SKAF-4`hVM}MDqoM`CNBH)eTMu9NmaHsz<%a)H z+mgru;>Lr+qBT9-YrYX0<;x~ry8Z2aT8{a`i(lQl5m1FGljHreh%`S*G)>6h7D7BR z_t4VYo|C@+j12y*DP-Z%#CE4sk|lj4t%z_e5pfjI)xN)<+x4f%%sZsh&eNC=@Q+se@4X)T+IX6?iXc*7Tryawi^9H`zqE0~UE_B4 z9q;Q)@c9mF4VXw9JG#7MD}<6MbFW@V{>reA+g>ihp&o02(CqH=#d=uiu~zMeo-h9e DqA@+m delta 375 zcmX@pz_=in_t9*9D+Vag-N<{Jk$0=h1!E4+yn@ma@6^i49~q@Ln=tt>PyWODVlof= zlF2vNXE2&>F63}$5do{rgsKFZ0pd^Y;0<8`F*Zx^@o;axBXxyklL8OZ<^&}cCPstF zsVeO(%7vY?Cx1|}UZ+f@+=I;KekTZqhW%qkYlorrr_jSt#lTFxkoQeexU_5dUB`c8X=hOlEABF<-#Ana3)IiKWu* zm&D{88>m$d>Iy84H=g=TK4D`6v3!Ay!X)dK&1-C37+KDFi$qWUVrKx60jbUUQ9Nm~ Qk3G~!V6_I5Z`hXr0I~mv<^TWy diff --git a/tonlib/CMakeLists.txt b/tonlib/CMakeLists.txt index 42b909ca2..26b14c3d5 100644 --- a/tonlib/CMakeLists.txt +++ b/tonlib/CMakeLists.txt @@ -5,42 +5,34 @@ if (NOT OPENSSL_FOUND) endif() set(TONLIB_SOURCE - tonlib/CellString.cpp tonlib/Client.cpp tonlib/Config.cpp tonlib/ExtClient.cpp tonlib/ExtClientLazy.cpp tonlib/ExtClientOutbound.cpp - tonlib/GenericAccount.cpp tonlib/KeyStorage.cpp tonlib/KeyValue.cpp tonlib/LastBlock.cpp tonlib/LastBlockStorage.cpp + tonlib/LastConfig.cpp tonlib/Logging.cpp - tonlib/TestGiver.cpp - tonlib/TestWallet.cpp tonlib/TonlibClient.cpp tonlib/utils.cpp - tonlib/Wallet.cpp - tonlib/CellString.h tonlib/Client.h tonlib/Config.h tonlib/ExtClient.h tonlib/ExtClientLazy.h tonlib/ExtClientOutbound.h - tonlib/GenericAccount.h tonlib/KeyStorage.h tonlib/KeyValue.h tonlib/LastBlock.h tonlib/LastBlockStorage.h + tonlib/LastConfig.h tonlib/Logging.h - tonlib/TestGiver.h - tonlib/TestWallet.h tonlib/TonlibCallback.h tonlib/TonlibClient.h tonlib/utils.h - tonlib/Wallet.h tonlib/keys/bip39.cpp tonlib/keys/DecryptedKey.cpp @@ -64,7 +56,7 @@ target_include_directories(tonlib PUBLIC $/.. $ ) -target_link_libraries(tonlib PRIVATE tdactor adnllite tl_lite_api tl-lite-utils ton_crypto ton_block lite-client-common) +target_link_libraries(tonlib PRIVATE tdactor adnllite tl_lite_api tl-lite-utils ton_crypto ton_block lite-client-common smc-envelope) target_link_libraries(tonlib PUBLIC tdutils tl_tonlib_api) if (TONLIB_ENABLE_JNI AND NOT ANDROID) # jni is available by default on Android @@ -134,7 +126,7 @@ if (NOT TON_USE_ABSEIL) if (WIN32) set(WINGETOPT_TARGET wingetopt) endif() -install(TARGETS tdnet keys crc32c tdactor adnllite tl_api tl-utils tl_lite_api tl-lite-utils ton_crypto ton_block ${WINGETOPT_TARGET} +install(TARGETS tdnet keys crc32c tdactor adnllite tl_api tl-utils tl_lite_api tl-lite-utils ton_crypto ton_block smc-envelope ${WINGETOPT_TARGET} tdutils tl_tonlib_api tonlib lite-client-common Tonlib EXPORT Tonlib LIBRARY DESTINATION lib ARCHIVE DESTINATION lib diff --git a/tonlib/test/offline.cpp b/tonlib/test/offline.cpp index 209b01d23..53d08bdbc 100644 --- a/tonlib/test/offline.cpp +++ b/tonlib/test/offline.cpp @@ -17,23 +17,14 @@ Copyright 2017-2019 Telegram Systems LLP */ -#include "fift/Fift.h" -#include "fift/words.h" -#include "fift/utils.h" - #include "block/block.h" #include "block/block-auto.h" #include "vm/cells.h" #include "vm/boc.h" -#include "vm/cells/MerkleProof.h" +#include "vm/cells/CellString.h" -#include "tonlib/CellString.h" #include "tonlib/utils.h" -#include "tonlib/TestGiver.h" -#include "tonlib/TestWallet.h" -#include "tonlib/Wallet.h" -#include "tonlib/GenericAccount.h" #include "tonlib/TonlibClient.h" #include "tonlib/Client.h" @@ -70,181 +61,6 @@ TEST(Tonlib, CellString) { using namespace tonlib; -std::string current_dir() { - return td::PathView(td::realpath(__FILE__).move_as_ok()).parent_dir().str(); -} - -std::string load_source(std::string name) { - return td::read_file_str(current_dir() + "../../crypto/" + name).move_as_ok(); -} - -td::Ref get_test_wallet_source() { - std::string code = R"ABCD( -SETCP0 DUP IFNOTRET // return if recv_internal -DUP 85143 INT EQUAL IFJMP:<{ // "seqno" get-method - DROP c4 PUSHCTR CTOS 32 PLDU // cnt -}> -INC 32 THROWIF // fail unless recv_external -512 INT LDSLICEX DUP 32 PLDU // sign cs cnt -c4 PUSHCTR CTOS 32 LDU 256 LDU ENDS // sign cs cnt cnt' pubk -s1 s2 XCPU // sign cs cnt pubk cnt' cnt -EQUAL 33 THROWIFNOT // ( seqno mismatch? ) -s2 PUSH HASHSU // sign cs cnt pubk hash -s0 s4 s4 XC2PU // pubk cs cnt hash sign pubk -CHKSIGNU // pubk cs cnt ? -34 THROWIFNOT // signature mismatch -ACCEPT -SWAP 32 LDU NIP -DUP SREFS IF:<{ - // 3 INT 35 LSHIFT# 3 INT RAWRESERVE // reserve all but 103 Grams from the balance - 8 LDU LDREF // pubk cnt mode msg cs - s0 s2 XCHG SENDRAWMSG // pubk cnt cs ; ( message sent ) -}> -ENDS -INC NEWC 32 STU 256 STU ENDC c4 POPCTR -)ABCD"; - return fift::compile_asm(code).move_as_ok(); -} - -td::Ref get_wallet_source() { - std::string code = R"ABCD( -SETCP0 DUP IFNOTRET // return if recv_internal - DUP 85143 INT EQUAL IFJMP:<{ // "seqno" get-method - DROP c4 PUSHCTR CTOS 32 PLDU // cnt - }> - INC 32 THROWIF // fail unless recv_external - 9 PUSHPOW2 LDSLICEX DUP 32 LDU 32 LDU // signature in_msg msg_seqno valid_until cs - SWAP NOW LEQ 35 THROWIF // signature in_msg msg_seqno cs - c4 PUSH CTOS 32 LDU 256 LDU ENDS // signature in_msg msg_seqno cs stored_seqno public_key - s3 s1 XCPU // signature in_msg public_key cs stored_seqno msg_seqno stored_seqno - EQUAL 33 THROWIFNOT // signature in_msg public_key cs stored_seqno - s0 s3 XCHG HASHSU // signature stored_seqno public_key cs hash - s0 s4 s2 XC2PU CHKSIGNU 34 THROWIFNOT // cs stored_seqno public_key - ACCEPT - s0 s2 XCHG // public_key stored_seqno cs - WHILE:<{ - DUP SREFS // public_key stored_seqno cs _40 - }>DO<{ // public_key stored_seqno cs - // 3 INT 35 LSHIFT# 3 INT RAWRESERVE // reserve all but 103 Grams from the balance - 8 LDU LDREF s0 s2 XCHG // public_key stored_seqno cs _45 mode - SENDRAWMSG // public_key stored_seqno cs - }> - ENDS INC // public_key seqno' - NEWC 32 STU 256 STU ENDC c4 POP -)ABCD"; - return fift::compile_asm(code).move_as_ok(); -} - -TEST(Tonlib, TestWallet) { - LOG(ERROR) << td::base64_encode(std_boc_serialize(get_test_wallet_source()).move_as_ok()); - CHECK(get_test_wallet_source()->get_hash() == TestWallet::get_init_code()->get_hash()); - auto fift_output = fift::mem_run_fift(load_source("smartcont/new-wallet.fif"), {"aba", "0"}).move_as_ok(); - - auto new_wallet_pk = fift_output.source_lookup.read_file("new-wallet.pk").move_as_ok().data; - auto new_wallet_query = fift_output.source_lookup.read_file("new-wallet-query.boc").move_as_ok().data; - auto new_wallet_addr = fift_output.source_lookup.read_file("new-wallet.addr").move_as_ok().data; - - td::Ed25519::PrivateKey priv_key{td::SecureString{new_wallet_pk}}; - auto pub_key = priv_key.get_public_key().move_as_ok(); - auto init_state = TestWallet::get_init_state(pub_key); - auto init_message = TestWallet::get_init_message(priv_key); - auto address = GenericAccount::get_address(0, init_state); - - CHECK(address.addr.as_slice() == td::Slice(new_wallet_addr).substr(0, 32)); - - td::Ref res = GenericAccount::create_ext_message(address, init_state, init_message); - - LOG(ERROR) << "-------"; - vm::load_cell_slice(res).print_rec(std::cerr); - LOG(ERROR) << "-------"; - vm::load_cell_slice(vm::std_boc_deserialize(new_wallet_query).move_as_ok()).print_rec(std::cerr); - CHECK(vm::std_boc_deserialize(new_wallet_query).move_as_ok()->get_hash() == res->get_hash()); - - fift_output.source_lookup.write_file("/main.fif", load_source("smartcont/wallet.fif")).ensure(); - auto dest = block::StdAddress::parse("Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX").move_as_ok(); - fift_output = fift::mem_run_fift(std::move(fift_output.source_lookup), - {"aba", "new-wallet", "Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX", "123", - "321", "-C", "TEST"}) - .move_as_ok(); - auto wallet_query = fift_output.source_lookup.read_file("wallet-query.boc").move_as_ok().data; - auto gift_message = GenericAccount::create_ext_message( - address, {}, TestWallet::make_a_gift_message(priv_key, 123, 321000000000ll, "TEST", dest)); - LOG(ERROR) << "-------"; - vm::load_cell_slice(gift_message).print_rec(std::cerr); - LOG(ERROR) << "-------"; - vm::load_cell_slice(vm::std_boc_deserialize(wallet_query).move_as_ok()).print_rec(std::cerr); - CHECK(vm::std_boc_deserialize(wallet_query).move_as_ok()->get_hash() == gift_message->get_hash()); -} - -td::Ref get_wallet_source_fc() { - return fift::compile_asm(load_source("smartcont/wallet-code.fif"), "", false).move_as_ok(); -} - -TEST(Tonlib, Wallet) { - LOG(ERROR) << td::base64_encode(std_boc_serialize(get_wallet_source()).move_as_ok()); - CHECK(get_wallet_source()->get_hash() == Wallet::get_init_code()->get_hash()); - - auto fift_output = fift::mem_run_fift(load_source("smartcont/new-wallet-v2.fif"), {"aba", "0"}).move_as_ok(); - - auto new_wallet_pk = fift_output.source_lookup.read_file("new-wallet.pk").move_as_ok().data; - auto new_wallet_query = fift_output.source_lookup.read_file("new-wallet-query.boc").move_as_ok().data; - auto new_wallet_addr = fift_output.source_lookup.read_file("new-wallet.addr").move_as_ok().data; - - td::Ed25519::PrivateKey priv_key{td::SecureString{new_wallet_pk}}; - auto pub_key = priv_key.get_public_key().move_as_ok(); - auto init_state = Wallet::get_init_state(pub_key); - auto init_message = Wallet::get_init_message(priv_key); - auto address = GenericAccount::get_address(0, init_state); - - CHECK(address.addr.as_slice() == td::Slice(new_wallet_addr).substr(0, 32)); - - td::Ref res = GenericAccount::create_ext_message(address, init_state, init_message); - - LOG(ERROR) << "-------"; - vm::load_cell_slice(res).print_rec(std::cerr); - LOG(ERROR) << "-------"; - vm::load_cell_slice(vm::std_boc_deserialize(new_wallet_query).move_as_ok()).print_rec(std::cerr); - CHECK(vm::std_boc_deserialize(new_wallet_query).move_as_ok()->get_hash() == res->get_hash()); - - fift_output.source_lookup.write_file("/main.fif", load_source("smartcont/wallet-v2.fif")).ensure(); - class ZeroOsTime : public fift::OsTime { - public: - td::uint32 now() override { - return 0; - } - }; - fift_output.source_lookup.set_os_time(std::make_unique()); - auto dest = block::StdAddress::parse("Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX").move_as_ok(); - fift_output = - fift::mem_run_fift(std::move(fift_output.source_lookup), - {"aba", "new-wallet", "Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX", "123", "321"}) - .move_as_ok(); - auto wallet_query = fift_output.source_lookup.read_file("wallet-query.boc").move_as_ok().data; - auto gift_message = GenericAccount::create_ext_message( - address, {}, Wallet::make_a_gift_message(priv_key, 123, 60, 321000000000ll, "TESTv2", dest)); - LOG(ERROR) << "-------"; - vm::load_cell_slice(gift_message).print_rec(std::cerr); - LOG(ERROR) << "-------"; - vm::load_cell_slice(vm::std_boc_deserialize(wallet_query).move_as_ok()).print_rec(std::cerr); - CHECK(vm::std_boc_deserialize(wallet_query).move_as_ok()->get_hash() == gift_message->get_hash()); -} - -TEST(Tonlib, TestGiver) { - auto address = - block::StdAddress::parse("-1:60c04141c6a7b96d68615e7a91d265ad0f3a9a922e9ae9c901d4fa83f5d3c0d0").move_as_ok(); - LOG(ERROR) << address.bounceable; - auto fift_output = fift::mem_run_fift(load_source("smartcont/testgiver.fif"), - {"aba", address.rserialize(), "0", "6.666", "wallet-query"}) - .move_as_ok(); - LOG(ERROR) << fift_output.output; - - auto wallet_query = fift_output.source_lookup.read_file("wallet-query.boc").move_as_ok().data; - - auto res = GenericAccount::create_ext_message( - TestGiver::address(), {}, TestGiver::make_a_gift_message(0, 1000000000ll * 6666 / 1000, "GIFT", address)); - vm::CellSlice(vm::NoVm(), res).print_rec(std::cerr); - CHECK(vm::std_boc_deserialize(wallet_query).move_as_ok()->get_hash() == res->get_hash()); -} TEST(Tonlib, PublicKey) { block::PublicKey::parse("pubjns2gp7DGCnEH7EOWeCnb6Lw1akm538YYaz6sdLVHfRB2").ensure_error(); auto key = block::PublicKey::parse("Pubjns2gp7DGCnEH7EOWeCnb6Lw1akm538YYaz6sdLVHfRB2").move_as_ok(); @@ -480,15 +296,15 @@ TEST(Tonlib, KeysApi) { td::SecureString{})) .move_as_ok(); - sync_send(client, make_object(make_object( + sync_send(client, make_object(make_object( make_object(key->public_key_, key->secret_.copy()), td::SecureString("wrong password")))) .ensure_error(); - //exportKey input_key:inputKey = ExportedKey; + //exportKey input_key:inputKeyRegular = ExportedKey; auto exported_key = sync_send(client, - make_object(make_object( + make_object(make_object( make_object(key->public_key_, key->secret_.copy()), local_password.copy()))) .move_as_ok(); LOG(ERROR) << to_string(exported_key); @@ -500,20 +316,20 @@ TEST(Tonlib, KeysApi) { return word_list_copy; }; - //changeLocalPassword input_key:inputKey new_local_password:bytes = Key; + //changeLocalPassword input_key:inputKeyRegular new_local_password:bytes = Key; auto new_key = sync_send(client, make_object( - make_object( + make_object( make_object(key->public_key_, key->secret_.copy()), local_password.copy()), td::SecureString("tmp local password"))) .move_as_ok(); sync_send(client, - make_object(make_object( + make_object(make_object( make_object(key->public_key_, new_key->secret_.copy()), local_password.copy()))) .ensure_error(); - auto exported_key2 = sync_send(client, make_object(make_object( + auto exported_key2 = sync_send(client, make_object(make_object( make_object(key->public_key_, new_key->secret_.copy()), td::SecureString("tmp local password")))) .move_as_ok(); @@ -531,7 +347,7 @@ TEST(Tonlib, KeysApi) { auto wrong_export_password = td::SecureString("wrong_export password"); auto exported_encrypted_key = sync_send(client, make_object( - make_object( + make_object( make_object(key->public_key_, new_key->secret_.copy()), td::SecureString("tmp local password")), export_password.copy())) @@ -576,12 +392,12 @@ TEST(Tonlib, KeysApi) { CHECK(imported_key->public_key_ == key->public_key_); CHECK(imported_key->secret_ != key->secret_); - //exportPemKey input_key:inputKey key_password:bytes = ExportedPemKey; + //exportPemKey input_key:inputKeyRegular key_password:bytes = ExportedPemKey; auto pem_password = td::SecureString("pem password"); auto r_exported_pem_key = sync_send( client, make_object( - make_object( + make_object( make_object(key->public_key_, imported_key->secret_.copy()), new_local_password.copy()), pem_password.copy())); if (r_exported_pem_key.is_error() && r_exported_pem_key.error().message() == "INTERNAL Not supported") { diff --git a/tonlib/test/online.cpp b/tonlib/test/online.cpp index 6ac47921d..08a3c36af 100644 --- a/tonlib/test/online.cpp +++ b/tonlib/test/online.cpp @@ -34,10 +34,12 @@ #include "ton/ton-tl.hpp" #include "block/block.h" #include "block/block-auto.h" +#include "Ed25519.h" -#include "tonlib/GenericAccount.h" -#include "tonlib/TestGiver.h" -#include "tonlib/TestWallet.h" +#include "smc-envelope/GenericAccount.h" +#include "smc-envelope/MultisigWallet.h" +#include "smc-envelope/TestGiver.h" +#include "smc-envelope/TestWallet.h" #include "tonlib/LastBlock.h" #include "tonlib/ExtClient.h" #include "tonlib/utils.h" @@ -57,18 +59,21 @@ #include "td/utils/optional.h" #include "td/utils/overloaded.h" #include "td/utils/MpscPollableQueue.h" +#include "td/utils/port/path.h" #include "td/utils/port/signals.h" using namespace tonlib; +constexpr td::int64 Gramm = 1000000000; + auto sync_send = [](auto& client, auto query) { using ReturnTypePtr = typename std::decay_t::ReturnType; using ReturnType = typename ReturnTypePtr::element_type; client.send({1, std::move(query)}); while (true) { auto response = client.receive(100); - if (response.object) { + if (response.object && response.id != 0) { CHECK(response.id == 1); if (response.object->get_id() == tonlib_api::error::ID) { auto error = tonlib_api::move_object_as(response.object); @@ -78,87 +83,295 @@ auto sync_send = [](auto& client, auto query) { } } }; +auto static_send = [](auto query) { + using ReturnTypePtr = typename std::decay_t::ReturnType; + using ReturnType = typename ReturnTypePtr::element_type; + auto response = Client::execute({1, std::move(query)}); + if (response.object->get_id() == tonlib_api::error::ID) { + auto error = tonlib_api::move_object_as(response.object); + return td::Result(td::Status::Error(error->code_, error->message_)); + } + return td::Result(tonlib_api::move_object_as(response.object)); +}; struct Key { std::string public_key; td::SecureString secret; - tonlib_api::object_ptr get_input_key() const { - return tonlib_api::make_object( + tonlib_api::object_ptr get_input_key() const { + return tonlib_api::make_object( tonlib_api::make_object(public_key, secret.copy()), td::SecureString("local")); } + tonlib_api::object_ptr get_fake_input_key() const { + return tonlib_api::make_object(); + } }; struct Wallet { std::string address; Key key; }; +struct TransactionId { + td::int64 lt{0}; + std::string hash; +}; + +struct AccountState { + enum Type { Empty, Wallet, Unknown } type{Empty}; + td::int64 sync_utime{-1}; + td::int64 balance{-1}; + TransactionId last_transaction_id; + std::string address; + + bool is_inited() const { + return type != Empty; + } +}; + +using tonlib_api::make_object; + +void sync(Client& client) { + sync_send(client, make_object()).ensure(); +} + +std::string wallet_address(Client& client, const Key& key) { + return sync_send(client, make_object( + make_object(key.public_key))) + .move_as_ok() + ->account_address_; +} + +Wallet import_wallet_from_pkey(Client& client, std::string pkey, std::string password) { + auto key = sync_send(client, make_object( + td::SecureString("local"), td::SecureString(password), + make_object(td::SecureString(pkey)))) + .move_as_ok(); + Wallet wallet{"", {key->public_key_, std::move(key->secret_)}}; + wallet.address = wallet_address(client, wallet.key); + return wallet; +} + std::string test_giver_address(Client& client) { using tonlib_api::make_object; return sync_send(client, make_object()).move_as_ok()->account_address_; } -td::int64 get_balance(Client& client, std::string address) { +AccountState get_account_state(Client& client, std::string address) { auto generic_state = sync_send(client, tonlib_api::make_object( tonlib_api::make_object(address))) .move_as_ok(); - td::int64 res = 0; - tonlib_api::downcast_call(*generic_state, [&](auto& state) { res = state.account_state_->balance_; }); + AccountState res; + tonlib_api::downcast_call(*generic_state, [&](auto& state) { + res.balance = state.account_state_->balance_; + res.sync_utime = state.account_state_->sync_utime_; + res.last_transaction_id.lt = state.account_state_->last_transaction_id_->lt_; + res.last_transaction_id.hash = state.account_state_->last_transaction_id_->hash_; + }); + res.address = address; + switch (generic_state->get_id()) { + case tonlib_api::generic_accountStateUninited::ID: + res.type = AccountState::Empty; + break; + case tonlib_api::generic_accountStateWallet::ID: + res.type = AccountState::Wallet; + break; + default: + res.type = AccountState::Unknown; + break; + } return res; } -bool is_inited(Client& client, std::string address) { - auto generic_state = sync_send(client, tonlib_api::make_object( - tonlib_api::make_object(address))) - .move_as_ok(); - return generic_state->get_id() != tonlib_api::generic_accountStateUninited::ID; +struct QueryId { + td::int64 id; +}; + +struct Fee { + td::int64 in_fwd_fee{0}; + td::int64 storage_fee{0}; + td::int64 gas_fee{0}; + td::int64 fwd_fee{0}; + td::int64 sum() const { + return in_fwd_fee + storage_fee + gas_fee + fwd_fee; + } +}; + +template +auto to_fee(const T& fee) { + Fee res; + res.in_fwd_fee = fee->in_fwd_fee_; + res.storage_fee = fee->storage_fee_; + res.gas_fee = fee->gas_fee_; + res.fwd_fee = fee->fwd_fee_; + return res; } -void transfer_grams(Client& client, std::string from, std::string to, td::int64 amount, - tonlib_api::object_ptr input_key) { - auto balance = get_balance(client, to); - sync_send(client, tonlib_api::make_object( - std::move(input_key), tonlib_api::make_object(from), - tonlib_api::make_object(to), amount, 0, true, "GIFT")) - .ensure(); - while (balance == get_balance(client, to)) { +td::StringBuilder& operator<<(td::StringBuilder& sb, const Fee& fees) { + return sb << td::tag("in_fwd_fee", fees.in_fwd_fee) << td::tag("storage_fee", fees.storage_fee) + << td::tag("gas_fee", fees.gas_fee) << td::tag("fwd_fee", fees.fwd_fee); +} + +struct QueryInfo { + td::int64 valid_until; + std::string body_hash; +}; + +td::Result create_send_grams_query(Client& client, const Wallet& source, std::string destination, + td::int64 amount, std::string message, bool force = false, int timeout = 0, + bool fake = false) { + auto r_id = sync_send(client, tonlib_api::make_object( + fake ? source.key.get_fake_input_key() : source.key.get_input_key(), + tonlib_api::make_object(source.address), + tonlib_api::make_object(destination), amount, timeout, + force, std::move(message))); + TRY_RESULT(id, std::move(r_id)); + return QueryId{id->id_}; +} + +td::Result create_raw_query(Client& client, std::string source, std::string init_code, std::string init_data, + std::string body) { + auto r_id = + sync_send(client, tonlib_api::make_object( + tonlib_api::make_object(source), init_code, init_data, body)); + TRY_RESULT(id, std::move(r_id)); + return QueryId{id->id_}; +} + +std::pair query_estimate_fees(Client& client, QueryId query_id, bool ignore_chksig = false) { + auto fees = sync_send(client, tonlib_api::make_object(query_id.id, ignore_chksig)) + .move_as_ok(); + return std::make_pair(to_fee(fees->source_fees_), to_fee(fees->destination_fees_)); +} + +void query_send(Client& client, QueryId query_id) { + sync_send(client, tonlib_api::make_object(query_id.id)).ensure(); +} +QueryInfo query_get_info(Client& client, QueryId query_id) { + auto info = sync_send(client, tonlib_api::make_object(query_id.id)).move_as_ok(); + return QueryInfo{info->valid_until_, info->body_hash_}; +} + +td::Result wait_state_change(Client& client, const AccountState& old_state, td::int64 valid_until) { + while (true) { + auto new_state = get_account_state(client, old_state.address); + if (new_state.last_transaction_id.lt != old_state.last_transaction_id.lt) { + return new_state; + } + if (valid_until != 0 && new_state.sync_utime >= valid_until) { + return td::Status::Error("valid_until expired"); + } client.receive(1); } +}; + +td::Result> get_transactions(Client& client, std::string address, + const TransactionId& from) { + auto got_transactions = sync_send(client, make_object( + make_object(address), + make_object(from.lt, from.hash))) + .move_as_ok(); + return std::move(got_transactions); +} + +td::Status transfer_grams(Client& client, const Wallet& wallet, std::string address, td::int64 amount) { + auto src_state = get_account_state(client, wallet.address); + auto dst_state = get_account_state(client, address); + auto message = td::rand_string('a', 'z', 500); + + LOG(INFO) << "Transfer: create query " << (double)amount / Gramm << " from " << wallet.address << " to " << address; + auto r_query_id = create_send_grams_query(client, wallet, address, amount, message); + if (r_query_id.is_error() && td::begins_with(r_query_id.error().message(), "DANGEROUS_TRANSACTION")) { + ASSERT_TRUE(dst_state.type == AccountState::Empty); + LOG(INFO) << "Transfer: recreate query due to DANGEROUS_TRANSACTION error"; + r_query_id = create_send_grams_query(client, wallet, address, amount, message, true); + } + + r_query_id.ensure(); + QueryId query_id = r_query_id.move_as_ok(); + auto query_info = query_get_info(client, query_id); + auto fees = query_estimate_fees(client, query_id); + + LOG(INFO) << "Expected src fees: " << fees.first; + LOG(INFO) << "Expected dst fees: " << fees.second; + + bool transfer_all = amount == src_state.balance; + if (!transfer_all && amount + fees.first.sum() + 10 > src_state.balance) { + return td::Status::Error("Not enough balance for query"); + } + + LOG(INFO) << "Transfer: send query"; + + query_send(client, query_id); + td::Timer timer; + TRY_RESULT(new_src_state, wait_state_change(client, src_state, query_info.valid_until)); + LOG(INFO) << "Transfer: reached source in " << timer; + + td::int64 lt; + td::int64 first_fee; + { + auto tr = get_transactions(client, src_state.address, new_src_state.last_transaction_id).move_as_ok(); + CHECK(tr->transactions_.size() > 0); + const auto& txn = tr->transactions_[0]; + CHECK(txn->in_msg_->body_hash_ == query_info.body_hash); + ASSERT_EQ(1u, txn->out_msgs_.size()); + ASSERT_EQ(message, txn->out_msgs_[0]->message_); + lt = txn->out_msgs_[0]->created_lt_; + auto fee_difference = fees.first.sum() - txn->fee_; + first_fee = txn->fee_; + auto desc = PSTRING() << fee_difference << " storage:[" << fees.first.storage_fee << " vs " << txn->storage_fee_ + << "] other:[" << fees.first.sum() - fees.first.storage_fee << " vs " << txn->other_fee_ + << "]"; + LOG(INFO) << "Source fee difference " << desc; + LOG_IF(ERROR, std::abs(fee_difference) > 1) << "Too big source fee difference " << desc; + } + + TRY_RESULT(new_dst_state, wait_state_change(client, dst_state, new_src_state.sync_utime + 30)); + LOG(INFO) << "Transfer: reached destination in " << timer; + + { + auto tr = get_transactions(client, dst_state.address, new_dst_state.last_transaction_id).move_as_ok(); + CHECK(tr->transactions_.size() > 0); + const auto& txn = tr->transactions_[0]; + ASSERT_EQ(lt, txn->in_msg_->created_lt_); + if (transfer_all) { + ASSERT_EQ(amount - first_fee, txn->in_msg_->value_); + } else { + ASSERT_EQ(new_src_state.address, txn->in_msg_->source_); + } + ASSERT_EQ(new_src_state.address, txn->in_msg_->source_); + ASSERT_EQ(message, txn->in_msg_->message_); + auto fee_difference = fees.second.sum() - txn->fee_; + auto desc = PSTRING() << fee_difference << " storage:[" << fees.second.storage_fee << " vs " << txn->storage_fee_ + << "] other:[" << fees.second.sum() - fees.second.storage_fee << " vs " << txn->other_fee_ + << "]"; + LOG(INFO) << "Destination fee difference " << desc; + LOG_IF(ERROR, std::abs(fee_difference) > 1) << "Too big destination fee difference " << desc; + } + + return td::Status::OK(); } + Wallet create_empty_wallet(Client& client) { using tonlib_api::make_object; - auto key = sync_send(client, make_object(td::SecureString("local"), - td::SecureString("mnemonic"), td::SecureString())) + auto key = sync_send(client, make_object(td::SecureString("local"), td::SecureString(), + td::SecureString())) .move_as_ok(); Wallet wallet{"", {key->public_key_, std::move(key->secret_)}}; auto account_address = - sync_send(client, make_object( - make_object(wallet.key.public_key))) + sync_send(client, make_object( + make_object(wallet.key.public_key))) .move_as_ok(); wallet.address = account_address->account_address_; - return wallet; -} -Wallet create_wallet(Client& client) { - using tonlib_api::make_object; - auto wallet = create_empty_wallet(client); + // get state of empty account + auto state = get_account_state(client, wallet.address); + ASSERT_EQ(-1, state.balance); + ASSERT_EQ(AccountState::Empty, state.type); - transfer_grams(client, test_giver_address(client), wallet.address, 6000000000, {}); - sync_send(client, make_object(wallet.key.get_input_key())).ensure(); - while (!is_inited(client, wallet.address)) { - client.receive(1); - } - LOG(ERROR) << get_balance(client, wallet.address); return wallet; } -std::string get_test_giver_address(Client& client) { - return sync_send(client, tonlib_api::make_object()) - .move_as_ok() - ->account_address_; -} - void dump_transaction_history(Client& client, std::string address) { using tonlib_api::make_object; auto state = sync_send(client, make_object()).move_as_ok(); @@ -180,166 +393,156 @@ void dump_transaction_history(Client& client, std::string address) { LOG(ERROR) << cnt; } +void test_estimate_fees_without_key(Client& client, const Wallet& wallet_a, const Wallet& wallet_b) { + LOG(ERROR) << " SUBTEST: estimate fees without key"; + { + auto query_id = create_send_grams_query(client, wallet_a, wallet_b.address, 0, "???", true, 0, true).move_as_ok(); + auto fees1 = query_estimate_fees(client, query_id, false); + auto fees2 = query_estimate_fees(client, query_id, true); + LOG(INFO) << "Fee without ignore_chksig\t" << fees1; + LOG(INFO) << "Fee with ignore_chksig\t" << fees2; + CHECK(fees1.first.gas_fee == 0); + CHECK(fees2.first.gas_fee != 0); + } +} + +void test_back_and_forth_transfer(Client& client, const Wallet& giver_wallet, bool flag) { + LOG(ERROR) << "TEST: back and forth transfer"; + // just generate private key and address + auto wallet_a = create_empty_wallet(client); + LOG(INFO) << wallet_a.address; + + // get state of empty account + auto state = get_account_state(client, wallet_a.address); + ASSERT_EQ(-1, state.balance); + ASSERT_EQ(AccountState::Empty, state.type); + + test_estimate_fees_without_key(client, giver_wallet, wallet_a); + + // transfer from giver to a + transfer_grams(client, giver_wallet, wallet_a.address, 1 * Gramm).ensure(); + state = get_account_state(client, wallet_a.address); + ASSERT_EQ(1 * Gramm, state.balance); + ASSERT_EQ(AccountState::Empty, state.type); + + test_estimate_fees_without_key(client, wallet_a, giver_wallet); + + if (flag) { + // transfer from a to giver + transfer_grams(client, wallet_a, giver_wallet.address, 5 * Gramm / 10).ensure(); + state = get_account_state(client, wallet_a.address); + ASSERT_TRUE(state.balance < 5 * Gramm / 10); + ASSERT_EQ(AccountState::Wallet, state.type); + } + + // transfer all remaining balance (test flag 128) + transfer_grams(client, wallet_a, giver_wallet.address, state.balance).ensure(); + state = get_account_state(client, wallet_a.address); + ASSERT_TRUE(state.balance == 0); + ASSERT_EQ(AccountState::Wallet, state.type); +} + +void test_multisig(Client& client, const Wallet& giver_wallet) { + LOG(ERROR) << "TEST: multisig"; + + int n = 16; + int k = 10; + std::vector private_keys; + for (int i = 0; i < n; i++) { + private_keys.push_back(td::Ed25519::generate_private_key().move_as_ok()); + } + + auto ms = ton::MultisigWallet::create(); + auto init_data = ms->create_init_data( + td::transform(private_keys, [](const auto& pk) { return pk.get_public_key().move_as_ok().as_octet_string(); }), + k); + ms = ton::MultisigWallet::create(init_data); + auto raw_address = ms->get_address(ton::basechainId); + auto address = raw_address.rserialize(); + transfer_grams(client, giver_wallet, address, 1 * Gramm).ensure(); + auto init_state = ms->get_init_state(); + + // Just transfer all (some) money back in one query + vm::CellBuilder icb; + ton::GenericAccount::store_int_message(icb, block::StdAddress::parse(giver_wallet.address).move_as_ok(), + 5 * Gramm / 10); + icb.store_bytes("\0\0\0\0", 4); + vm::CellString::store(icb, "Greatings from multisig", 35 * 8).ensure(); + ton::MultisigWallet::QueryBuilder qb(-1, icb.finalize()); + for (int i = 0; i < k - 1; i++) { + qb.sign(i, private_keys[i]); + } + + auto query_id = + create_raw_query(client, address, vm::std_boc_serialize(ms->get_state().code).move_as_ok().as_slice().str(), + vm::std_boc_serialize(ms->get_state().data).move_as_ok().as_slice().str(), + vm::std_boc_serialize(qb.create(k - 1, private_keys[k - 1])).move_as_ok().as_slice().str()) + .move_as_ok(); + auto fees = query_estimate_fees(client, query_id); + + LOG(INFO) << "Expected src fees: " << fees.first; + LOG(INFO) << "Expected dst fees: " << fees.second; + auto a_state = get_account_state(client, address); + query_send(client, query_id); + auto new_a_state = wait_state_change(client, a_state, a_state.sync_utime + 30).move_as_ok(); +} + int main(int argc, char* argv[]) { td::set_default_failure_signal_handler(); using tonlib_api::make_object; td::OptionsParser p; std::string global_config_str; + std::string giver_key_str; + std::string giver_key_pwd = "cucumber"; + std::string keystore_dir = "test-keystore"; + bool reset_keystore_dir = false; p.add_option('C', "global-config", "file to read global config", [&](td::Slice fname) { TRY_RESULT(str, td::read_file_str(fname.str())); global_config_str = std::move(str); - LOG(ERROR) << global_config_str; + return td::Status::OK(); + }); + p.add_option('G', "giver-key", "file with a wallet key that should be used as a giver", [&](td::Slice fname) { + TRY_RESULT(str, td::read_file_str(fname.str())); + giver_key_str = std::move(str); + return td::Status::OK(); + }); + p.add_option('f', "force", "reser keystore dir", [&]() { + reset_keystore_dir = true; return td::Status::OK(); }); p.run(argc, argv).ensure(); + if (reset_keystore_dir) { + td::rmrf(keystore_dir).ignore(); + td::mkdir(keystore_dir).ensure(); + } + + SET_VERBOSITY_LEVEL(VERBOSITY_NAME(INFO)); + static_send(make_object("tonlib_query", 4)).ensure(); + auto tags = static_send(make_object()).move_as_ok()->tags_; + for (auto& tag : tags) { + static_send(make_object(tag, 4)).ensure(); + } + Client client; { sync_send(client, make_object(make_object( make_object(global_config_str, "", false, false), - make_object(".")))) + make_object(keystore_dir)))) .ensure(); } - //dump_transaction_history(client, get_test_giver_address(client)); - auto wallet_a = create_wallet(client); - auto wallet_b = create_empty_wallet(client); - transfer_grams(client, wallet_a.address, wallet_b.address, 3000000000, wallet_a.key.get_input_key()); - auto a = get_balance(client, wallet_a.address); - auto b = get_balance(client, wallet_b.address); - LOG(ERROR) << a << " " << b; - return 0; - { - // init - sync_send(client, make_object(make_object( - make_object(global_config_str, "", false, false), - make_object(".")))) - .ensure(); - - auto key = sync_send(client, make_object( - td::SecureString("local"), td::SecureString("mnemonic"), td::SecureString())) - .move_as_ok(); - - auto create_input_key = [&] { - return make_object(make_object(key->public_key_, key->secret_.copy()), - td::SecureString("local")); - }; - - auto public_key_raw = key->public_key_; - td::Ed25519::PublicKey public_key_std(td::SecureString{public_key_raw}); - - sync_send(client, make_object( - make_object(global_config_str, "", false, false))) - .ensure(); - auto wallet_addr = GenericAccount::get_address(0, TestWallet::get_init_state(public_key_std)); - { - auto account_address = - sync_send(client, make_object( - make_object(public_key_raw))) - .move_as_ok(); - ASSERT_EQ(wallet_addr.rserialize(), account_address->account_address_); - } + // wait till client is synchronized with blockchain. + // not necessary, but synchronized will be trigged anyway later + sync(client); - std::string test_giver_address; - { - auto account_address = sync_send(client, make_object()).move_as_ok(); - test_giver_address = account_address->account_address_; - ASSERT_EQ(TestGiver::address().rserialize(), test_giver_address); - } - - { - auto account_address = - sync_send( - client, - make_object(make_object( - vm::std_boc_serialize(TestWallet::get_init_code()).move_as_ok().as_slice().str(), - vm::std_boc_serialize(TestWallet::get_init_data(public_key_std)).move_as_ok().as_slice().str()))) - .move_as_ok(); - ASSERT_EQ(wallet_addr.rserialize(), account_address->account_address_); - } - - { - auto state = sync_send(client, make_object( - make_object(wallet_addr.rserialize()))) - .move_as_ok(); - LOG(ERROR) << to_string(state); - } - - td::int32 seqno = 0; - { - auto state = sync_send(client, make_object()).move_as_ok(); - LOG(ERROR) << to_string(state); - seqno = state->seqno_; - } + // give wallet with some test grams to run test + auto giver_wallet = import_wallet_from_pkey(client, giver_key_str, giver_key_pwd); - { - sync_send(client, make_object( - make_object(wallet_addr.rserialize()), seqno, - 1000000000ll * 6666 / 1000, "GIFT")) - .ensure(); - } - - while (true) { - auto state = sync_send(client, make_object()).move_as_ok(); - if (state->seqno_ > seqno) { - break; - } - client.receive(1); - } - - while (true) { - auto state = sync_send(client, make_object( - make_object(wallet_addr.rserialize()))) - .move_as_ok(); - td::int64 grams_count = state->balance_; - if (grams_count > 0) { - LOG(ERROR) << "GOT " << grams_count; - break; - } - client.receive(1); - } - - { sync_send(client, make_object(create_input_key())).ensure(); } - - while (true) { - auto r_state = sync_send(client, make_object( - make_object(wallet_addr.rserialize()))); - if (r_state.is_ok()) { - LOG(ERROR) << to_string(r_state.ok()); - break; - } - client.receive(1); - } - - { - sync_send(client, make_object( - create_input_key(), make_object(wallet_addr.rserialize()), - make_object(test_giver_address), 1000000000ll * 3333 / 1000, 0, - true, "GIFT")) - .ensure(); - } - while (true) { - auto generic_state = sync_send(client, make_object( - make_object(wallet_addr.rserialize()))) - .move_as_ok(); - if (generic_state->get_id() == tonlib_api::generic_accountStateTestWallet::ID) { - auto state = tonlib_api::move_object_as(generic_state); - if (state->account_state_->balance_ < 5617007000) { - LOG(ERROR) << to_string(state); - break; - } - } - client.receive(1); - } - { - auto generic_state = sync_send(client, make_object( - make_object(test_giver_address))) - .move_as_ok(); - CHECK(generic_state->get_id() == tonlib_api::generic_accountStateTestGiver::ID); - LOG(ERROR) << to_string(generic_state); - } - } + test_back_and_forth_transfer(client, giver_wallet, false); + test_back_and_forth_transfer(client, giver_wallet, true); + test_multisig(client, giver_wallet); return 0; } diff --git a/tonlib/tonlib/Client.cpp b/tonlib/tonlib/Client.cpp index d22e7ac79..83b41ad2f 100644 --- a/tonlib/tonlib/Client.cpp +++ b/tonlib/tonlib/Client.cpp @@ -134,7 +134,7 @@ class Client::Impl final { }; Client::Client() : impl_(std::make_unique()) { - // At least it should be enough for everybody who uses TDLib + // At least it should be enough for everybody who uses tonlib // FIXME //td::init_openssl_threads(); } diff --git a/tonlib/tonlib/ExtClient.cpp b/tonlib/tonlib/ExtClient.cpp index 1ce77337c..6981078ef 100644 --- a/tonlib/tonlib/ExtClient.cpp +++ b/tonlib/tonlib/ExtClient.cpp @@ -19,12 +19,27 @@ #include "tonlib/ExtClient.h" #include "tonlib/LastBlock.h" +#include "tonlib/LastConfig.h" namespace tonlib { ExtClient::~ExtClient() { + last_config_queries_.for_each([](auto id, auto &promise) { promise.set_error(TonlibError::Cancelled()); }); last_block_queries_.for_each([](auto id, auto &promise) { promise.set_error(TonlibError::Cancelled()); }); queries_.for_each([](auto id, auto &promise) { promise.set_error(TonlibError::Cancelled()); }); } +void ExtClient::with_last_config(td::Promise promise) { + auto query_id = last_config_queries_.create(std::move(promise)); + td::Promise P = [query_id, self = this, + actor_id = td::actor::actor_id()](td::Result result) { + send_lambda(actor_id, [self, query_id, result = std::move(result)]() mutable { + self->last_config_queries_.extract(query_id).set_result(std::move(result)); + }); + }; + if (client_.last_block_actor_.empty()) { + return P.set_error(TonlibError::NoLiteServers()); + } + td::actor::send_closure(client_.last_config_actor_, &LastConfig::get_last_config, std::move(P)); +} void ExtClient::with_last_block(td::Promise promise) { auto query_id = last_block_queries_.create(std::move(promise)); td::Promise P = [query_id, self = this, diff --git a/tonlib/tonlib/ExtClient.h b/tonlib/tonlib/ExtClient.h index beb0671fb..b3cbbaf09 100644 --- a/tonlib/tonlib/ExtClient.h +++ b/tonlib/tonlib/ExtClient.h @@ -33,10 +33,13 @@ namespace tonlib { class LastBlock; +class LastConfig; struct LastBlockState; +struct LastConfigState; struct ExtClientRef { td::actor::ActorId andl_ext_client_; td::actor::ActorId last_block_actor_; + td::actor::ActorId last_config_actor_; }; class ExtClient { @@ -56,6 +59,7 @@ class ExtClient { ~ExtClient(); void with_last_block(td::Promise promise); + void with_last_config(td::Promise promise); template void send_query(QueryT query, td::Promise promise, td::int32 seq_no = -1) { @@ -94,6 +98,7 @@ class ExtClient { ExtClientRef client_; td::Container> queries_; td::Container> last_block_queries_; + td::Container> last_config_queries_; void send_raw_query(td::BufferSlice query, td::Promise promise); }; diff --git a/tonlib/tonlib/ExtClientLazy.cpp b/tonlib/tonlib/ExtClientLazy.cpp index d5adb770e..cd83e3abd 100644 --- a/tonlib/tonlib/ExtClientLazy.cpp +++ b/tonlib/tonlib/ExtClientLazy.cpp @@ -17,6 +17,7 @@ Copyright 2017-2019 Telegram Systems LLP */ #include "ExtClientLazy.h" +#include "TonlibError.h" namespace tonlib { class ExtClientLazyImp : public ton::adnl::AdnlExtClient { @@ -28,12 +29,18 @@ class ExtClientLazyImp : public ton::adnl::AdnlExtClient { void check_ready(td::Promise promise) override { before_query(); + if (client_.empty()) { + return promise.set_error(TonlibError::Cancelled()); + } send_closure(client_, &ton::adnl::AdnlExtClient::check_ready, std::move(promise)); } void send_query(std::string name, td::BufferSlice data, td::Timestamp timeout, td::Promise promise) override { before_query(); + if (client_.empty()) { + return promise.set_error(TonlibError::Cancelled()); + } send_closure(client_, &ton::adnl::AdnlExtClient::send_query, std::move(name), std::move(data), timeout, std::move(promise)); } diff --git a/tonlib/tonlib/KeyStorage.cpp b/tonlib/tonlib/KeyStorage.cpp index 6e01f2f84..f1ac82715 100644 --- a/tonlib/tonlib/KeyStorage.cpp +++ b/tonlib/tonlib/KeyStorage.cpp @@ -106,6 +106,9 @@ td::Result KeyStorage::export_key(InputKey input_key) { } td::Result KeyStorage::load_private_key(InputKey input_key) { + if (is_fake_input_key(input_key)) { + return fake_private_key(); + } TRY_RESULT(decrypted_key, export_decrypted_key(std::move(input_key))); PrivateKey private_key; private_key.private_key = decrypted_key.private_key.as_octet_string(); @@ -166,20 +169,46 @@ td::Result KeyStorage::import_pem_key(td::Slice local_password, return save_key(DecryptedKey({}, std::move(key)), local_password); } -static std::string dummy_secret = "dummy secret of 32 bytes length!"; +td::SecureString get_dummy_secret() { + return td::SecureString("dummy secret of 32 bytes length!"); +} td::Result KeyStorage::export_encrypted_key(InputKey input_key, td::Slice key_password) { TRY_RESULT(decrypted_key, export_decrypted_key(std::move(input_key))); - auto res = decrypted_key.encrypt(key_password, dummy_secret); + auto res = decrypted_key.encrypt(key_password, get_dummy_secret()); return ExportedEncryptedKey{std::move(res.encrypted_data)}; } td::Result KeyStorage::import_encrypted_key(td::Slice local_password, td::Slice key_password, ExportedEncryptedKey exported_key) { EncryptedKey encrypted_key{std::move(exported_key.data), td::Ed25519::PublicKey(td::SecureString()), - td::SecureString(dummy_secret)}; + get_dummy_secret()}; TRY_RESULT_PREFIX(decrypted_key, encrypted_key.decrypt(key_password, false), TonlibError::KeyDecrypt()); return save_key(std::move(decrypted_key), local_password); } +KeyStorage::PrivateKey KeyStorage::fake_private_key() { + return PrivateKey{td::SecureString(32, 0)}; +} + +KeyStorage::InputKey KeyStorage::fake_input_key() { + return InputKey{{td::SecureString(32, 0), td::SecureString(32, 0)}, {}}; +} + +bool KeyStorage::is_fake_input_key(InputKey &input_key) { + auto is_zero = [](td::Slice slice, size_t size) { + if (slice.size() != size) { + return false; + } + for (auto c : slice) { + if (c != 0) { + return false; + } + } + return true; + }; + return is_zero(input_key.local_password, 0) && is_zero(input_key.key.secret, 32) && + is_zero(input_key.key.public_key, 32); +} + } // namespace tonlib diff --git a/tonlib/tonlib/KeyStorage.h b/tonlib/tonlib/KeyStorage.h index 1e3708288..38478bf1f 100644 --- a/tonlib/tonlib/KeyStorage.h +++ b/tonlib/tonlib/KeyStorage.h @@ -69,6 +69,10 @@ class KeyStorage { td::Result load_private_key(InputKey input_key); + static PrivateKey fake_private_key(); + static InputKey fake_input_key(); + static bool is_fake_input_key(InputKey& input_key); + private: std::shared_ptr kv_; diff --git a/tonlib/tonlib/LastBlock.cpp b/tonlib/tonlib/LastBlock.cpp index 9740a24ab..234af9971 100644 --- a/tonlib/tonlib/LastBlock.cpp +++ b/tonlib/tonlib/LastBlock.cpp @@ -17,6 +17,7 @@ Copyright 2017-2019 Telegram Systems LLP */ #include "tonlib/LastBlock.h" +#include "tonlib/LastConfig.h" #include "tonlib/utils.h" @@ -271,7 +272,7 @@ void LastBlock::update_zero_state(ton::ZeroStateIdExt zero_state_id, td::Slice s } if (!state_.zero_state_id.is_valid()) { - LOG(INFO) << "Init zerostate from " << source << ": " << zero_state_id.to_str(); + VLOG(last_block) << "Init zerostate from " << source << ": " << zero_state_id.to_str(); state_.zero_state_id = std::move(zero_state_id); return; } @@ -295,7 +296,7 @@ bool LastBlock::update_mc_last_block(ton::BlockIdExt mc_block_id) { } if (!state_.last_block_id.is_valid() || state_.last_block_id.id.seqno < mc_block_id.id.seqno) { state_.last_block_id = mc_block_id; - LOG(INFO) << "Update masterchain block id: " << state_.last_block_id.to_str(); + VLOG(last_block) << "Update masterchain block id: " << state_.last_block_id.to_str(); return true; } return false; @@ -311,7 +312,7 @@ bool LastBlock::update_mc_last_key_block(ton::BlockIdExt mc_key_block_id) { } if (!state_.last_key_block_id.is_valid() || state_.last_key_block_id.id.seqno < mc_key_block_id.id.seqno) { state_.last_key_block_id = mc_key_block_id; - LOG(INFO) << "Update masterchain key block id: " << state_.last_key_block_id.to_str(); + VLOG(last_block) << "Update masterchain key block id: " << state_.last_key_block_id.to_str(); //LOG(ERROR) << td::int64(state_.last_key_block_id.id.shard) << " " //<< td::base64_encode(state_.last_key_block_id.file_hash.as_slice()) << " " //<< td::base64_encode(state_.last_key_block_id.root_hash.as_slice()); @@ -330,7 +331,7 @@ bool LastBlock::update_init_block(ton::BlockIdExt init_block_id) { } if (state_.init_block_id != init_block_id) { state_.init_block_id = init_block_id; - LOG(INFO) << "Update init block id: " << state_.init_block_id.to_str(); + VLOG(last_block) << "Update init block id: " << state_.init_block_id.to_str(); return true; } return false; diff --git a/tonlib/tonlib/LastConfig.cpp b/tonlib/tonlib/LastConfig.cpp new file mode 100644 index 000000000..7207b6f0e --- /dev/null +++ b/tonlib/tonlib/LastConfig.cpp @@ -0,0 +1,152 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2017-2019 Telegram Systems LLP +*/ +#include "tonlib/LastConfig.h" + +#include "tonlib/utils.h" + +#include "ton/lite-tl.hpp" +#include "block/check-proof.h" +#include "block/mc-config.h" +#include "block/block-auto.h" + +#include "lite-client/lite-client-common.h" + +#include "LastBlock.h" + +namespace tonlib { + +// init_state <-> last_key_block +// state.valitated_init_state +// last_key_block -> +// +td::StringBuilder& operator<<(td::StringBuilder& sb, const LastConfigState& state) { + return sb; +} + +LastConfig::LastConfig(ExtClientRef client, td::unique_ptr callback) : callback_(std::move(callback)) { + client_.set_client(client); + VLOG(last_block) << "State: " << state_; +} + +void LastConfig::get_last_config(td::Promise promise) { + if (promises_.empty() && get_config_state_ == QueryState::Done) { + VLOG(last_config) << "start"; + VLOG(last_config) << "get_config: reset"; + get_config_state_ = QueryState::Empty; + } + + promises_.push_back(std::move(promise)); + loop(); +} + +void LastConfig::with_last_block(td::Result r_last_block) { + if (r_last_block.is_error()) { + on_error(r_last_block.move_as_error()); + return; + } + + auto last_block = r_last_block.move_as_ok(); + auto params = params_; + client_.send_query(ton::lite_api::liteServer_getConfigParams(0, create_tl_lite_block_id(last_block.last_block_id), + std::move(params)), + [this](auto r_config) { this->on_config(std::move(r_config)); }); +} + +void LastConfig::on_config(td::Result> r_config) { + auto status = process_config(std::move(r_config)); + if (status.is_ok()) { + on_ok(); + get_config_state_ = QueryState::Done; + } else { + on_error(std::move(status)); + get_config_state_ = QueryState::Empty; + } +} + +td::Status LastConfig::process_config( + td::Result> r_config) { + TRY_RESULT(raw_config, std::move(r_config)); + TRY_STATUS_PREFIX(TRY_VM(process_config_proof(std::move(raw_config))), TonlibError::ValidateConfig()); + return td::Status::OK(); +} + +td::Status LastConfig::process_config_proof(ton::ton_api::object_ptr raw_config) { + auto blkid = create_block_id(raw_config->id_); + if (!blkid.is_masterchain_ext()) { + return td::Status::Error(PSLICE() << "reference block " << blkid.to_str() + << " for the configuration is not a valid masterchain block"); + } + TRY_RESULT(state, block::check_extract_state_proof(blkid, raw_config->state_proof_.as_slice(), + raw_config->config_proof_.as_slice())); + TRY_RESULT(config, block::Config::extract_from_state(std::move(state), 0)); + + for (auto i : params_) { + VLOG(last_config) << "ConfigParam(" << i << ") = "; + auto value = config->get_config_param(i); + if (value.is_null()) { + VLOG(last_config) << "(null)\n"; + } else { + std::ostringstream os; + if (i >= 0) { + block::gen::ConfigParam{i}.print_ref(os, value); + os << std::endl; + } + vm::load_cell_slice(value).print_rec(os); + VLOG(last_config) << os.str(); + } + } + state_.config.reset(config.release()); + return td::Status::OK(); +} + +void LastConfig::loop() { + if (promises_.empty()) { + return; + } + + if (get_config_state_ == QueryState::Empty) { + VLOG(last_block) << "get_config: start"; + get_config_state_ = QueryState::Active; + client_.with_last_block( + [self = this](td::Result r_last_block) { self->with_last_block(std::move(r_last_block)); }); + } +} + +void LastConfig::on_ok() { + VLOG(last_block) << "ok " << state_; + for (auto& promise : promises_) { + auto state = state_; + promise.set_value(std::move(state)); + } + promises_.clear(); +} + +void LastConfig::on_error(td::Status status) { + VLOG(last_config) << "error " << status; + for (auto& promise : promises_) { + promise.set_error(status.clone()); + } + promises_.clear(); +} + +void LastConfig::tear_down() { + on_error(TonlibError::Cancelled()); +} + +} // namespace tonlib diff --git a/tonlib/tonlib/LastConfig.h b/tonlib/tonlib/LastConfig.h new file mode 100644 index 000000000..2e0cf2a39 --- /dev/null +++ b/tonlib/tonlib/LastConfig.h @@ -0,0 +1,70 @@ +/* + This file is part of TON Blockchain Library. + + TON Blockchain Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + TON Blockchain Library 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 Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with TON Blockchain Library. If not, see . + + Copyright 2017-2019 Telegram Systems LLP +*/ +#pragma once +#include "td/actor/actor.h" + +#include "tonlib/Config.h" +#include "tonlib/ExtClient.h" + +#include "td/utils/CancellationToken.h" +#include "td/utils/tl_helpers.h" + +#include "block/mc-config.h" + +namespace tonlib { +struct LastConfigState { + std::shared_ptr config; +}; + +td::StringBuilder& operator<<(td::StringBuilder& sb, const LastConfigState& state); + +class LastConfig : public td::actor::Actor { + public: + class Callback { + public: + virtual ~Callback() { + } + }; + + explicit LastConfig(ExtClientRef client, td::unique_ptr callback); + void get_last_config(td::Promise promise); + + private: + td::unique_ptr callback_; + ExtClient client_; + LastConfigState state_; + + enum class QueryState { Empty, Active, Done }; + QueryState get_config_state_{QueryState::Empty}; + + std::vector> promises_; + std::vector params_{18, 20, 21, 24, 25}; + + void with_last_block(td::Result r_last_block); + void on_config(td::Result> r_config); + td::Status process_config(td::Result> r_config); + td::Status process_config_proof(ton::ton_api::object_ptr config); + + void on_ok(); + void on_error(td::Status status); + + void loop() override; + void tear_down() override; +}; +} // namespace tonlib diff --git a/tonlib/tonlib/Logging.cpp b/tonlib/tonlib/Logging.cpp index 31dc275c0..a17b03ebe 100644 --- a/tonlib/tonlib/Logging.cpp +++ b/tonlib/tonlib/Logging.cpp @@ -32,14 +32,22 @@ namespace tonlib { -static std::mutex logging_mutex; -static td::FileLog file_log; -static td::TsLog ts_log(&file_log); -static td::NullLog null_log; +struct LogData { + std::mutex logging_mutex; + td::FileLog file_log; + td::TsLog ts_log{&file_log}; + td::NullLog null_log; +}; + +auto &log_data() { + static LogData data; + return data; +} #define ADD_TAG(tag) \ { #tag, &VERBOSITY_NAME(tag) } -static const std::map log_tags{ADD_TAG(tonlib_query), ADD_TAG(last_block)}; +static const std::map log_tags{ADD_TAG(tonlib_query), ADD_TAG(last_block), ADD_TAG(last_config), + ADD_TAG(lite_server)}; #undef ADD_TAG td::Status Logging::set_current_stream(tonlib_api::object_ptr stream) { @@ -47,7 +55,7 @@ td::Status Logging::set_current_stream(tonlib_api::object_ptr lock(logging_mutex); + std::lock_guard lock(log_data().logging_mutex); switch (stream->get_id()) { case tonlib_api::logStreamDefault::ID: td::log_interface = td::default_log_interface; @@ -59,13 +67,13 @@ td::Status Logging::set_current_stream(tonlib_api::object_ptrpath_, max_log_file_size)); + TRY_STATUS(log_data().file_log.init(file_stream->path_, max_log_file_size)); std::atomic_thread_fence(std::memory_order_release); // better than nothing - td::log_interface = &ts_log; + td::log_interface = &log_data().ts_log; return td::Status::OK(); } case tonlib_api::logStreamEmpty::ID: - td::log_interface = &null_log; + td::log_interface = &log_data().null_log; return td::Status::OK(); default: UNREACHABLE(); @@ -74,22 +82,22 @@ td::Status Logging::set_current_stream(tonlib_api::object_ptr> Logging::get_current_stream() { - std::lock_guard lock(logging_mutex); + std::lock_guard lock(log_data().logging_mutex); if (td::log_interface == td::default_log_interface) { return tonlib_api::make_object(); } - if (td::log_interface == &null_log) { + if (td::log_interface == &log_data().null_log) { return tonlib_api::make_object(); } - if (td::log_interface == &ts_log) { - return tonlib_api::make_object(file_log.get_path().str(), - file_log.get_rotate_threshold()); + if (td::log_interface == &log_data().ts_log) { + return tonlib_api::make_object(log_data().file_log.get_path().str(), + log_data().file_log.get_rotate_threshold()); } return td::Status::Error("Log stream is unrecognized"); } td::Status Logging::set_verbosity_level(int new_verbosity_level) { - std::lock_guard lock(logging_mutex); + std::lock_guard lock(log_data().logging_mutex); if (0 <= new_verbosity_level && new_verbosity_level <= VERBOSITY_NAME(NEVER)) { SET_VERBOSITY_LEVEL(VERBOSITY_NAME(FATAL) + new_verbosity_level); return td::Status::OK(); @@ -99,7 +107,7 @@ td::Status Logging::set_verbosity_level(int new_verbosity_level) { } int Logging::get_verbosity_level() { - std::lock_guard lock(logging_mutex); + std::lock_guard lock(log_data().logging_mutex); return GET_VERBOSITY_LEVEL(); } @@ -113,7 +121,7 @@ td::Status Logging::set_tag_verbosity_level(td::Slice tag, int new_verbosity_lev return td::Status::Error("Log tag is not found"); } - std::lock_guard lock(logging_mutex); + std::lock_guard lock(log_data().logging_mutex); *it->second = td::clamp(new_verbosity_level, 1, VERBOSITY_NAME(NEVER)); return td::Status::OK(); } @@ -124,7 +132,7 @@ td::Result Logging::get_tag_verbosity_level(td::Slice tag) { return td::Status::Error("Log tag is not found"); } - std::lock_guard lock(logging_mutex); + std::lock_guard lock(log_data().logging_mutex); return *it->second; } diff --git a/tonlib/tonlib/TonlibClient.cpp b/tonlib/tonlib/TonlibClient.cpp index 070630e73..edd8d6e75 100644 --- a/tonlib/tonlib/TonlibClient.cpp +++ b/tonlib/tonlib/TonlibClient.cpp @@ -20,18 +20,19 @@ #include "tonlib/ExtClientLazy.h" #include "tonlib/ExtClientOutbound.h" -#include "tonlib/GenericAccount.h" #include "tonlib/LastBlock.h" +#include "tonlib/LastConfig.h" #include "tonlib/Logging.h" -#include "tonlib/TestWallet.h" -#include "tonlib/Wallet.h" -#include "tonlib/TestGiver.h" #include "tonlib/utils.h" #include "tonlib/keys/Mnemonic.h" #include "tonlib/keys/SimpleEncryption.h" - #include "tonlib/TonlibError.h" +#include "smc-envelope/GenericAccount.h" +#include "smc-envelope/TestWallet.h" +#include "smc-envelope/Wallet.h" +#include "smc-envelope/TestGiver.h" + #include "auto/tl/tonlib_api.hpp" #include "block/block-auto.h" #include "block/check-proof.h" @@ -41,12 +42,43 @@ #include "vm/boc.h" #include "td/utils/Random.h" +#include "td/utils/optional.h" #include "td/utils/overloaded.h" #include "td/utils/tests.h" #include "td/utils/port/path.h" namespace tonlib { +namespace int_api { +struct GetAccountState { + block::StdAddress address; + using ReturnType = td::unique_ptr; +}; +struct GetPrivateKey { + KeyStorage::InputKey input_key; + using ReturnType = KeyStorage::PrivateKey; +}; +struct SendMessage { + td::Ref message; + using ReturnType = td::Unit; +}; +} // namespace int_api + +class TonlibQueryActor : public td::actor::Actor { + public: + TonlibQueryActor(td::actor::ActorShared client) : client_(std::move(client)) { + } + template + void send_query(QueryT query, td::Promise promise) { + td::actor::send_lambda(client_, + [self = client_.get(), query = std::move(query), promise = std::move(promise)]() mutable { + self.get_actor_unsafe().make_request(std::move(query), std::move(promise)); + }); + } + + private: + td::actor::ActorShared client_; +}; tonlib_api::object_ptr status_to_tonlib_api(const td::Status& status) { return tonlib_api::make_object(status.code(), status.message().str()); @@ -63,12 +95,427 @@ static block::AccountState create_account_state(ton::tl_object_ptr code; - td::Ref data; + + ton::UnixTime storage_last_paid{0}; + vm::CellStorageStat storage_stat; + + td::Ref code; + td::Ref data; + td::Ref state; std::string frozen_hash; block::AccountState::Info info; }; +tonlib_api::object_ptr empty_transaction_id() { + return tonlib_api::make_object(0, std::string(32, 0)); +} + +tonlib_api::object_ptr to_transaction_id(const block::AccountState::Info& info) { + return tonlib_api::make_object(info.last_trans_lt, + info.last_trans_hash.as_slice().str()); +} + +std::string to_bytes(td::Ref cell) { + return vm::std_boc_serialize(cell, vm::BagOfCells::Mode::WithCRC32C).move_as_ok().as_slice().str(); +} + +class AccountState { + public: + AccountState(block::StdAddress address, RawAccountState&& raw) : address_(std::move(address)), raw_(std::move(raw)) { + wallet_type_ = guess_type(); + } + + auto to_uninited_accountState() const { + return tonlib_api::make_object(get_balance(), to_transaction_id(raw().info), + raw().frozen_hash, get_sync_time()); + } + + td::Result> to_raw_accountState() const { + auto state = get_smc_state(); + std::string code; + if (state.code.not_null()) { + code = to_bytes(state.code); + } + std::string data; + if (state.data.not_null()) { + data = to_bytes(state.data); + } + return tonlib_api::make_object(get_balance(), std::move(code), std::move(data), + to_transaction_id(raw().info), raw().frozen_hash, + get_sync_time()); + } + + td::Result> to_testWallet_accountState() const { + if (wallet_type_ != SimpleWallet) { + return TonlibError::AccountTypeUnexpected("TestWallet"); + } + TRY_RESULT(seqno, ton::TestWallet(get_smc_state()).get_seqno()); + return tonlib_api::make_object(get_balance(), static_cast(seqno), + to_transaction_id(raw().info), get_sync_time()); + } + + td::Result> to_wallet_accountState() const { + if (wallet_type_ != Wallet) { + return TonlibError::AccountTypeUnexpected("Wallet"); + } + TRY_RESULT(seqno, ton::Wallet(get_smc_state()).get_seqno()); + return tonlib_api::make_object(get_balance(), static_cast(seqno), + to_transaction_id(raw().info), get_sync_time()); + } + + td::Result> to_testGiver_accountState() const { + if (wallet_type_ != Giver) { + return TonlibError::AccountTypeUnexpected("TestGiver"); + } + TRY_RESULT(seqno, ton::TestGiver(get_smc_state()).get_seqno()); + return tonlib_api::make_object(get_balance(), static_cast(seqno), + to_transaction_id(raw().info), get_sync_time()); + } + td::Result> to_generic_accountState() const { + switch (wallet_type_) { + case Empty: + return tonlib_api::make_object(to_uninited_accountState()); + case Unknown: { + TRY_RESULT(res, to_raw_accountState()); + return tonlib_api::make_object(std::move(res)); + } + case Giver: { + TRY_RESULT(res, to_testGiver_accountState()); + return tonlib_api::make_object(std::move(res)); + } + case SimpleWallet: { + TRY_RESULT(res, to_testWallet_accountState()); + return tonlib_api::make_object(std::move(res)); + } + case Wallet: { + TRY_RESULT(res, to_wallet_accountState()); + return tonlib_api::make_object(std::move(res)); + } + } + UNREACHABLE(); + } + + enum WalletType { Empty, Unknown, Giver, SimpleWallet, Wallet }; + WalletType get_wallet_type() const { + return wallet_type_; + } + bool is_frozen() const { + return !raw_.frozen_hash.empty(); + } + + const block::StdAddress& get_address() const { + return address_; + } + + void make_non_bounceable() { + address_.bounceable = false; + } + + td::uint32 get_sync_time() const { + return raw_.info.gen_utime; + } + + td::int64 get_balance() const { + return raw_.balance; + } + + const RawAccountState& raw() const { + return raw_; + } + + WalletType guess_type_by_public_key(td::Ed25519::PublicKey& key) { + if (wallet_type_ != WalletType::Empty) { + return wallet_type_; + } + if (ton::GenericAccount::get_address(address_.workchain, ton::TestWallet::get_init_state(key)).addr == + address_.addr) { + set_new_state({ton::TestWallet::get_init_code(), ton::TestWallet::get_init_data(key)}); + wallet_type_ = WalletType::SimpleWallet; + } else if (ton::GenericAccount::get_address(address_.workchain, ton::Wallet::get_init_state(key)).addr == + address_.addr) { + set_new_state({ton::Wallet::get_init_code(), ton::Wallet::get_init_data(key)}); + wallet_type_ = WalletType::Wallet; + } + return wallet_type_; + } + + WalletType guess_type_default(td::Ed25519::PublicKey& key) { + if (wallet_type_ != WalletType::Empty) { + return wallet_type_; + } + set_new_state({ton::Wallet::get_init_code(), ton::Wallet::get_init_data(key)}); + wallet_type_ = WalletType::Wallet; + return wallet_type_; + } + + ton::SmartContract::State get_smc_state() const { + return {raw_.code, raw_.data}; + } + + td::Ref get_raw_state() { + return raw_.state; + } + + void set_new_state(ton::SmartContract::State state) { + raw_.code = std::move(state.code); + raw_.data = std::move(state.data); + raw_.state = ton::GenericAccount::get_init_state(raw_.code, raw_.data); + has_new_state_ = true; + } + + td::Ref get_new_state() const { + if (!has_new_state_) { + return {}; + } + return raw_.state; + } + + private: + block::StdAddress address_; + RawAccountState raw_; + WalletType wallet_type_{Unknown}; + bool has_new_state_{false}; + + WalletType guess_type() const { + if (raw_.code.is_null()) { + return WalletType::Empty; + } + auto code_hash = raw_.code->get_hash(); + if (code_hash == ton::TestGiver::get_init_code_hash()) { + return WalletType::Giver; + } + if (code_hash == ton::TestWallet::get_init_code_hash()) { + return WalletType::SimpleWallet; + } + if (code_hash == ton::Wallet::get_init_code_hash()) { + return WalletType::Wallet; + } + LOG(WARNING) << "Unknown code hash: " << td::base64_encode(code_hash.as_slice()); + return WalletType::Unknown; + } +}; + +class Query { + public: + struct Raw { + td::unique_ptr source; + td::unique_ptr destination; + + td::uint32 valid_until{std::numeric_limits::max()}; + + td::Ref message; + td::Ref new_state; + td::Ref message_body; + }; + + Query(Raw&& raw) : raw_(std::move(raw)) { + } + + td::Ref get_message() const { + return raw_.message; + } + + vm::CellHash get_body_hash() const { + return raw_.message_body->get_hash(); + } + + td::uint32 get_valid_until() const { + return raw_.valid_until; + } + + // ported from block/transaction.cpp + // TODO: reuse code + static td::RefInt256 compute_threshold(const block::GasLimitsPrices& cfg) { + auto gas_price256 = td::RefInt256{true, cfg.gas_price}; + if (cfg.gas_limit > cfg.flat_gas_limit) { + return td::rshift(gas_price256 * (cfg.gas_limit - cfg.flat_gas_limit), 16, 1) + + td::make_refint(cfg.flat_gas_price); + } else { + return td::make_refint(cfg.flat_gas_price); + } + } + + static td::uint64 gas_bought_for(td::RefInt256 nanograms, td::RefInt256 max_gas_threshold, + const block::GasLimitsPrices& cfg) { + if (nanograms.is_null() || sgn(nanograms) < 0) { + return 0; + } + if (nanograms >= max_gas_threshold) { + return cfg.gas_limit; + } + if (nanograms < cfg.flat_gas_price) { + return 0; + } + auto gas_price256 = td::RefInt256{true, cfg.gas_price}; + auto res = td::div((std::move(nanograms) - cfg.flat_gas_price) << 16, gas_price256); + return res->to_long() + cfg.flat_gas_limit; + } + + static td::RefInt256 compute_gas_price(td::uint64 gas_used, const block::GasLimitsPrices& cfg) { + auto gas_price256 = td::RefInt256{true, cfg.gas_price}; + return gas_used <= cfg.flat_gas_limit + ? td::make_refint(cfg.flat_gas_price) + : td::rshift(gas_price256 * (gas_used - cfg.flat_gas_limit), 16, 1) + cfg.flat_gas_price; + } + + static vm::GasLimits compute_gas_limits(td::RefInt256 balance, const block::GasLimitsPrices& cfg) { + vm::GasLimits res; + // Compute gas limits + if (false /*account.is_special*/) { + res.gas_max = cfg.special_gas_limit; + } else { + res.gas_max = gas_bought_for(balance, compute_threshold(cfg), cfg); + } + res.gas_credit = 0; + if (false /*trans_type != tr_ord*/) { + // may use all gas that can be bought using remaining balance + res.gas_limit = res.gas_max; + } else { + // originally use only gas bought using remaining message balance + // if the message is "accepted" by the smart contract, the gas limit will be set to gas_max + res.gas_limit = gas_bought_for(td::make_refint(0) /*msg balance remaining*/, compute_threshold(cfg), cfg); + if (true /*!block::tlb::t_Message.is_internal(in_msg)*/) { + // external messages carry no balance, give them some credit to check whether they are accepted + res.gas_credit = std::min(static_cast(cfg.gas_credit), static_cast(res.gas_max)); + } + } + LOG(DEBUG) << "gas limits: max=" << res.gas_max << ", limit=" << res.gas_limit << ", credit=" << res.gas_credit; + return res; + } + + struct Fee { + td::int64 in_fwd_fee{0}; + td::int64 storage_fee{0}; + td::int64 gas_fee{0}; + td::int64 fwd_fee{0}; + auto to_tonlib_api() const { + return tonlib_api::make_object(in_fwd_fee, storage_fee, gas_fee, fwd_fee); + } + }; + + td::Result calc_fwd_fees(td::Ref list, const block::MsgPrices& msg_prices) { + td::int64 res = 0; + std::vector> actions; + int n{0}; + int max_actions = 20; + while (true) { + actions.push_back(list); + auto cs = load_cell_slice(std::move(list)); + if (!cs.size_ext()) { + break; + } + if (!cs.have_refs()) { + return td::Status::Error("action list invalid: entry found with data but no next reference"); + } + list = cs.prefetch_ref(); + n++; + if (n > max_actions) { + return td::Status::Error(PSLICE() << "action list too long: more than " << max_actions << " actions"); + } + } + for (int i = n - 1; i >= 0; --i) { + vm::CellSlice cs = load_cell_slice(actions[i]); + CHECK(cs.fetch_ref().not_null()); + int tag = block::gen::t_OutAction.get_tag(cs); + CHECK(tag >= 0); + switch (tag) { + case block::gen::OutAction::action_set_code: + return td::Status::Error("estimate_fee: action_set_code unsupported"); + case block::gen::OutAction::action_send_msg: { + block::gen::OutAction::Record_action_send_msg act_rec; + // mode: +128 = attach all remaining balance, +64 = attach all remaining balance of the inbound message, +1 = pay message fees, +2 = skip if message cannot be sent + if (!tlb::unpack_exact(cs, act_rec) || (act_rec.mode & ~0xe3) || (act_rec.mode & 0xc0) == 0xc0) { + return td::Status::Error("estimate_fee: can't parse send_msg"); + } + block::gen::MessageRelaxed::Record msg; + if (!tlb::type_unpack_cell(act_rec.out_msg, block::gen::t_MessageRelaxed_Any, msg)) { + return td::Status::Error("estimate_fee: can't parse send_msg"); + } + vm::CellStorageStat sstat; // for message size + sstat.add_used_storage(msg.init, true, 3); // message init + sstat.add_used_storage(msg.body, true, 3); // message body (the root cell itself is not counted) + res += msg_prices.compute_fwd_fees(sstat.cells, sstat.bits); + break; + } + case block::gen::OutAction::action_reserve_currency: + return td::Status::Error("estimate_fee: action_reserve_currency unsupported"); + } + } + return res; + } + td::Result> estimate_fees(bool ignore_chksig, const block::Config& cfg) { + // gas fees + bool is_masterchain = raw_.source->get_address().workchain == ton::masterchainId; + bool dest_is_masterchain = raw_.destination && raw_.destination->get_address().workchain == ton::masterchainId; + TRY_RESULT(gas_limits_prices, cfg.get_gas_limits_prices(is_masterchain)); + TRY_RESULT(dest_gas_limits_prices, cfg.get_gas_limits_prices(dest_is_masterchain)); + TRY_RESULT(msg_prices, cfg.get_msg_prices(is_masterchain || dest_is_masterchain)); + TRY_RESULT(storage_prices, cfg.get_storage_prices()); + + auto storage_fee_256 = block::StoragePrices::compute_storage_fees( + raw_.source->get_sync_time(), storage_prices, raw_.source->raw().storage_stat, + raw_.source->raw().storage_last_paid, false, is_masterchain); + auto storage_fee = storage_fee_256.is_null() ? 0 : storage_fee_256->to_long(); + + auto dest_storage_fee_256 = + raw_.destination ? block::StoragePrices::compute_storage_fees( + raw_.destination->get_sync_time(), storage_prices, raw_.destination->raw().storage_stat, + raw_.destination->raw().storage_last_paid, false, is_masterchain) + : td::make_refint(0); + auto dest_storage_fee = dest_storage_fee_256.is_null() ? 0 : dest_storage_fee_256->to_long(); + + auto smc = ton::SmartContract::create(raw_.source->get_smc_state()); + + td::int64 in_fwd_fee = 0; + { + vm::CellStorageStat sstat; // for message size + sstat.add_used_storage(raw_.message, true, 3); // message init + in_fwd_fee += msg_prices.compute_fwd_fees(sstat.cells, sstat.bits); + } + + vm::GasLimits gas_limits = compute_gas_limits(td::make_refint(raw_.source->get_balance()), gas_limits_prices); + auto res = smc.write().send_external_message( + raw_.message_body, ton::SmartContract::Args().set_limits(gas_limits).set_ignore_chksig(ignore_chksig)); + td::int64 fwd_fee = 0; + if (res.success) { + //std::cerr << "new smart contract data: "; + //load_cell_slice(res.new_state.data).print_rec(std::cerr); + //std::cerr << "output actions: "; + //int out_act_num = output_actions_count(res.actions); + //block::gen::OutList{out_act_num}.print_ref(std::cerr, res.actions); + + TRY_RESULT_ASSIGN(fwd_fee, calc_fwd_fees(res.actions, msg_prices)); + } + + auto gas_fee = res.accepted ? compute_gas_price(res.gas_used, gas_limits_prices)->to_long() : 0; + LOG(ERROR) << storage_fee << " " << in_fwd_fee << " " << gas_fee << " " << fwd_fee; + + Fee fee; + fee.in_fwd_fee = in_fwd_fee; + fee.storage_fee = storage_fee; + fee.gas_fee = gas_fee; + fee.fwd_fee = fwd_fee; + + Fee dst_fee; + if (raw_.destination && raw_.destination->get_wallet_type() != AccountState::WalletType::Empty) { + dst_fee.gas_fee = dest_gas_limits_prices.flat_gas_price; + dst_fee.storage_fee = dest_storage_fee; + } + return std::make_pair(fee, dst_fee); + } + + private: + Raw raw_; + static int output_actions_count(td::Ref list) { + int i = -1; + do { + ++i; + list = load_cell_slice(std::move(list)).prefetch_ref(); + } while (list.not_null()); + return i; + } +}; // namespace tonlib + td::Result to_balance_or_throw(td::Ref balance_ref) { vm::CellSlice balance_slice = *balance_ref; auto balance = block::tlb::t_Grams.as_integer_skip(balance_slice); @@ -139,8 +586,8 @@ class GetTransactionHistory : public td::actor::Actor { td::Result do_with_transactions(std::vector blkids, td::BufferSlice transactions) { - LOG(INFO) << "got up to " << count_ << " transactions for " << address_ << " from last transaction " << lt_ << ":" - << hash_.to_hex(); + //LOG(INFO) << "got up to " << count_ << " transactions for " << address_ << " from last transaction " << lt_ << ":" + //<< hash_.to_hex(); block::TransactionList list; list.blkids = std::move(blkids); list.hash = hash_; @@ -211,7 +658,7 @@ class GetRawAccountState : public td::actor::Actor { auto cell = res.info.root; std::ostringstream outp; block::gen::t_Account.print_ref(outp, cell); - LOG(INFO) << outp.str(); + //LOG(INFO) << outp.str(); if (cell.is_null()) { return res; } @@ -219,6 +666,41 @@ class GetRawAccountState : public td::actor::Actor { if (!tlb::unpack_cell(cell, account)) { return td::Status::Error("Failed to unpack Account"); } + { + block::gen::StorageInfo::Record storage_info; + if (!tlb::csr_unpack(account.storage_stat, storage_info)) { + return td::Status::Error("Failed to unpack StorageInfo"); + } + res.storage_last_paid = storage_info.last_paid; + td::RefInt256 due_payment; + if (storage_info.due_payment->prefetch_ulong(1) == 1) { + vm::CellSlice& cs2 = storage_info.due_payment.write(); + cs2.advance(1); + due_payment = block::tlb::t_Grams.as_integer_skip(cs2); + if (due_payment.is_null() || !cs2.empty_ext()) { + return td::Status::Error("Failed to upack due_payment"); + } + } else { + due_payment = td::RefInt256{true, 0}; + } + block::gen::StorageUsed::Record storage_used; + if (!tlb::csr_unpack(storage_info.used, storage_used)) { + return td::Status::Error("Failed to unpack StorageInfo"); + } + unsigned long long u = 0; + vm::CellStorageStat storage_stat; + u |= storage_stat.cells = block::tlb::t_VarUInteger_7.as_uint(*storage_used.cells); + u |= storage_stat.bits = block::tlb::t_VarUInteger_7.as_uint(*storage_used.bits); + u |= storage_stat.public_cells = block::tlb::t_VarUInteger_7.as_uint(*storage_used.public_cells); + //LOG(DEBUG) << "last_paid=" << res.storage_last_paid << "; cells=" << storage_stat.cells + //<< " bits=" << storage_stat.bits << " public_cells=" << storage_stat.public_cells; + if (u == std::numeric_limits::max()) { + return td::Status::Error("Failed to unpack StorageStat"); + } + + res.storage_stat = storage_stat; + } + block::gen::AccountStorage::Record storage; if (!tlb::csr_unpack(account.storage, storage)) { return td::Status::Error("Failed to unpack AccountStorage"); @@ -245,12 +727,12 @@ class GetRawAccountState : public td::actor::Actor { return td::Status::Error("Failed to parse AccountState"); } block::gen::StateInit::Record state_init; + res.state = vm::CellBuilder().append_cellslice(state.x).finalize(); if (!tlb::csr_unpack(state.x, state_init)) { return td::Status::Error("Failed to parse StateInit"); } - res.code = std::move(state_init.code); - res.data = std::move(state_init.data); - + state_init.code->prefetch_maybe_ref(res.code); + state_init.data->prefetch_maybe_ref(res.data); return res; } @@ -295,6 +777,7 @@ void TonlibClient::hangup() { ref_cnt_--; raw_client_ = {}; raw_last_block_ = {}; + raw_last_config_ = {}; try_stop(); } @@ -302,6 +785,7 @@ ExtClientRef TonlibClient::get_client_ref() { ExtClientRef ref; ref.andl_ext_client_ = raw_client_.get(); ref.last_block_actor_ = raw_last_block_.get(); + ref.last_config_actor_ = raw_last_config_.get(); return ref; } @@ -413,6 +897,20 @@ void TonlibClient::init_last_block() { td::actor::ActorOptions().with_name("LastBlock").with_poll(false), get_client_ref(), std::move(state), config_, source_.get_cancellation_token(), td::make_unique(td::actor::actor_shared(this), config_generation_)); } +void TonlibClient::init_last_config() { + ref_cnt_++; + class Callback : public LastConfig::Callback { + public: + Callback(td::actor::ActorShared client) : client_(std::move(client)) { + } + + private: + td::actor::ActorShared client_; + }; + raw_last_config_ = + td::actor::create_actor(td::actor::ActorOptions().with_name("LastConfig").with_poll(false), + get_client_ref(), td::make_unique(td::actor::actor_shared(this))); +} void TonlibClient::on_result(td::uint64 id, tonlib_api::object_ptr response) { VLOG_IF(tonlib_query, id != 0) << "Tonlib answer query " << td::tag("id", id) << " " << to_string(response); @@ -461,11 +959,7 @@ void TonlibClient::request(td::uint64 id, tonlib_api::object_ptrdo_request(request, std::move(promise)); - if (status.is_error()) { - CHECK(promise); - promise.set_error(std::move(status)); - } + this->make_request(request, std::move(promise)); }); } void TonlibClient::close() { @@ -538,19 +1032,20 @@ td::Result get_public_key(td::Slice public_key) { td::Result get_account_address(const tonlib_api::raw_initialAccountState& raw_state) { TRY_RESULT_PREFIX(code, vm::std_boc_deserialize(raw_state.code_), TonlibError::InvalidBagOfCells("raw_state.code")); TRY_RESULT_PREFIX(data, vm::std_boc_deserialize(raw_state.data_), TonlibError::InvalidBagOfCells("raw_state.data")); - return GenericAccount::get_address(0 /*zerochain*/, GenericAccount::get_init_state(std::move(code), std::move(data))); + return ton::GenericAccount::get_address(0 /*zerochain*/, + ton::GenericAccount::get_init_state(std::move(code), std::move(data))); } td::Result get_account_address(const tonlib_api::testWallet_initialAccountState& test_wallet_state) { TRY_RESULT(key_bytes, get_public_key(test_wallet_state.public_key_)); auto key = td::Ed25519::PublicKey(td::SecureString(key_bytes.key)); - return GenericAccount::get_address(0 /*zerochain*/, TestWallet::get_init_state(key)); + return ton::GenericAccount::get_address(0 /*zerochain*/, ton::TestWallet::get_init_state(key)); } td::Result get_account_address(const tonlib_api::wallet_initialAccountState& test_wallet_state) { TRY_RESULT(key_bytes, get_public_key(test_wallet_state.public_key_)); auto key = td::Ed25519::PublicKey(td::SecureString(key_bytes.key)); - return GenericAccount::get_address(0 /*zerochain*/, Wallet::get_init_state(key)); + return ton::GenericAccount::get_address(0 /*zerochain*/, ton::Wallet::get_init_state(key)); } td::Result get_account_address(td::Slice account_address) { @@ -587,7 +1082,7 @@ tonlib_api::object_ptr TonlibClient::do_static_request( tonlib_api::object_ptr TonlibClient::do_static_request( const tonlib_api::testGiver_getAccountAddress& request) { - return tonlib_api::make_object(TestGiver::address().rserialize(true)); + return tonlib_api::make_object(ton::TestGiver::address().rserialize(true)); } tonlib_api::object_ptr TonlibClient::do_static_request( @@ -675,6 +1170,7 @@ td::Status TonlibClient::set_config(object_ptr config) { ignore_cache_ = config->ignore_cache_; init_ext_client(); init_last_block(); + init_last_config(); client_.set_client(get_client_ref()); return td::Status::OK(); } @@ -698,35 +1194,6 @@ td::Status TonlibClient::do_request(tonlib_api::options_setConfig& request, return td::Status::OK(); } -tonlib_api::object_ptr empty_transaction_id() { - return tonlib_api::make_object(0, std::string(32, 0)); -} - -tonlib_api::object_ptr to_transaction_id(const block::AccountState::Info& info) { - return tonlib_api::make_object(info.last_trans_lt, - info.last_trans_hash.as_slice().str()); -} - -td::Result> to_raw_accountState(RawAccountState&& raw_state) { - std::string code; - if (raw_state.code.not_null()) { - code = vm::std_boc_serialize(vm::CellBuilder().append_cellslice(std::move(raw_state.code)).finalize()) - .move_as_ok() - .as_slice() - .str(); - } - std::string data; - if (raw_state.data.not_null()) { - data = vm::std_boc_serialize(vm::CellBuilder().append_cellslice(std::move(raw_state.data)).finalize()) - .move_as_ok() - .as_slice() - .str(); - } - return tonlib_api::make_object(raw_state.balance, std::move(code), std::move(data), - to_transaction_id(raw_state.info), raw_state.frozen_hash, - raw_state.info.gen_utime); -} - td::Result to_std_address_or_throw(td::Ref cs) { auto tag = block::gen::MsgAddressInt().get_tag(*cs); if (tag < 0) { @@ -833,8 +1300,7 @@ td::Result> to_raw_transacti td::int64 fees = 0; td::int64 storage_fee = 0; if (info.transaction.not_null()) { - TRY_RESULT(copy_data, vm::std_boc_serialize(info.transaction)); - data = copy_data.as_slice().str(); + data = to_bytes(info.transaction); block::gen::Transaction::Record trans; if (!tlb::unpack_cell(info.transaction, trans)) { return td::Status::Error("Failed to unpack Transaction"); @@ -842,9 +1308,9 @@ td::Result> to_raw_transacti TRY_RESULT_ASSIGN(fees, to_balance(trans.total_fees)); - std::ostringstream outp; - block::gen::t_Transaction.print_ref(outp, info.transaction); - LOG(INFO) << outp.str(); + //std::ostringstream outp; + //block::gen::t_Transaction.print_ref(outp, info.transaction); + //LOG(INFO) << outp.str(); auto is_just = trans.r1.in_msg->prefetch_long(1); if (is_just == trans.r1.in_msg->fetch_long_eof) { @@ -899,88 +1365,26 @@ td::Result> to_raw_transact return tonlib_api::make_object(std::move(transactions), std::move(transaction_id)); } -td::Result> to_testWallet_accountState( - RawAccountState&& raw_state) { - if (raw_state.code.is_null()) { - return TonlibError::AccountNotInited(); - } - if (raw_state.code->prefetch_ref()->get_hash() != TestWallet::get_init_code_hash()) { - return TonlibError::AccountTypeUnexpected("TestWallet"); - } - auto ref = raw_state.data->prefetch_ref(); - auto cs = vm::load_cell_slice(std::move(ref)); - auto seqno = cs.fetch_ulong(32); - if (seqno == cs.fetch_ulong_eof) { - return td::Status::Error("Failed to parse seq_no"); - } - return tonlib_api::make_object( - raw_state.balance, static_cast(seqno), to_transaction_id(raw_state.info), raw_state.info.gen_utime); -} +// Raw -td::Result> to_wallet_accountState( - RawAccountState&& raw_state) { - if (raw_state.code.is_null()) { - return TonlibError::AccountNotInited(); - } - if (raw_state.code->prefetch_ref()->get_hash() != Wallet::get_init_code_hash()) { - return TonlibError::AccountTypeUnexpected("Wallet"); - } - auto ref = raw_state.data->prefetch_ref(); - auto cs = vm::load_cell_slice(std::move(ref)); - auto seqno = cs.fetch_ulong(32); - if (seqno == cs.fetch_ulong_eof) { - return td::Status::Error("Failed to parse seq_no"); - } - return tonlib_api::make_object( - raw_state.balance, static_cast(seqno), to_transaction_id(raw_state.info), raw_state.info.gen_utime); +auto to_any_promise(td::Promise>&& promise) { + return promise.wrap([](auto x) { return tonlib_api::make_object(); }); } - -td::Result> to_testGiver_accountState( - RawAccountState&& raw_state) { - if (raw_state.code.is_null()) { - return TonlibError::AccountNotInited(); - } - if (raw_state.code->prefetch_ref()->get_hash() != TestGiver::get_init_code_hash()) { - return TonlibError::AccountTypeUnexpected("TestGiver"); - } - auto ref = raw_state.data->prefetch_ref(); - auto cs = vm::load_cell_slice(std::move(ref)); - auto seqno = cs.fetch_ulong(32); - if (seqno == cs.fetch_ulong_eof) { - return td::Status::Error("Failed to parse seq_no"); - } - return tonlib_api::make_object( - raw_state.balance, static_cast(seqno), to_transaction_id(raw_state.info), raw_state.info.gen_utime); +auto to_any_promise(td::Promise&& promise) { + return promise.wrap([](auto x) { return td::Unit(); }); } -td::Result> to_generic_accountState( - RawAccountState&& raw_state) { - if (raw_state.code.is_null()) { - return tonlib_api::make_object( - tonlib_api::make_object(raw_state.balance, to_transaction_id(raw_state.info), - raw_state.frozen_hash, raw_state.info.gen_utime)); - } - - auto code_hash = raw_state.code->prefetch_ref()->get_hash(); - if (code_hash == TestWallet::get_init_code_hash()) { - TRY_RESULT(test_wallet, to_testWallet_accountState(std::move(raw_state))); - return tonlib_api::make_object(std::move(test_wallet)); - } - if (code_hash == Wallet::get_init_code_hash()) { - TRY_RESULT(wallet, to_wallet_accountState(std::move(raw_state))); - return tonlib_api::make_object(std::move(wallet)); - } - if (code_hash == TestGiver::get_init_code_hash()) { - TRY_RESULT(test_wallet, to_testGiver_accountState(std::move(raw_state))); - return tonlib_api::make_object(std::move(test_wallet)); - } - TRY_RESULT(raw, to_raw_accountState(std::move(raw_state))); - return tonlib_api::make_object(std::move(raw)); +td::Status TonlibClient::do_request(const tonlib_api::raw_sendMessage& request, + td::Promise>&& promise) { + TRY_RESULT_PREFIX(body, vm::std_boc_deserialize(request.body_), TonlibError::InvalidBagOfCells("body")); + std::ostringstream os; + block::gen::t_Message_Any.print_ref(os, body); + LOG(ERROR) << os.str(); + make_request(int_api::SendMessage{std::move(body)}, to_any_promise(std::move(promise))); + return td::Status::OK(); } -// Raw - -td::Status TonlibClient::do_request(const tonlib_api::raw_sendMessage& request, +td::Status TonlibClient::do_request(const tonlib_api::raw_createAndSendMessage& request, td::Promise>&& promise) { td::Ref init_state; if (!request.initial_account_state_.empty()) { @@ -990,12 +1394,9 @@ td::Status TonlibClient::do_request(const tonlib_api::raw_sendMessage& request, } TRY_RESULT_PREFIX(data, vm::std_boc_deserialize(request.data_), TonlibError::InvalidBagOfCells("data")); TRY_RESULT(account_address, get_account_address(request.destination_->account_address_)); - auto message = GenericAccount::create_ext_message(account_address, std::move(init_state), std::move(data)); - client_.send_query(ton::lite_api::liteServer_sendMessage(vm::std_boc_serialize(message).move_as_ok()), - [promise = std::move(promise)](auto r_info) mutable { - TRY_RESULT_PROMISE(promise, info, std::move(r_info)); - promise.set_value(tonlib_api::make_object()); - }); + auto message = ton::GenericAccount::create_ext_message(account_address, std::move(init_state), std::move(data)); + + make_request(int_api::SendMessage{std::move(message)}, to_any_promise(std::move(promise))); return td::Status::OK(); } @@ -1005,13 +1406,8 @@ td::Status TonlibClient::do_request(tonlib_api::raw_getAccountState& request, return TonlibError::EmptyField("account_address"); } TRY_RESULT(account_address, get_account_address(request.account_address_->account_address_)); - auto actor_id = actor_id_++; - actors_[actor_id] = td::actor::create_actor( - "GetAccountState", client_.get_client(), std::move(account_address), actor_shared(this, actor_id), - [promise = std::move(promise)](td::Result r_state) mutable { - TRY_RESULT_PROMISE(promise, state, std::move(r_state)); - promise.set_result(to_raw_accountState(std::move(state))); - }); + make_request(int_api::GetAccountState{std::move(account_address)}, + promise.wrap([](auto&& res) { return res->to_raw_accountState(); })); return td::Status::OK(); } @@ -1035,14 +1431,10 @@ td::Status TonlibClient::do_request(tonlib_api::raw_getTransactions& request, auto actor_id = actor_id_++; actors_[actor_id] = td::actor::create_actor( "GetTransactionHistory", client_.get_client(), account_address, lt, hash, actor_shared(this, actor_id), - [promise = std::move(promise)](td::Result r_info) mutable { - TRY_RESULT_PROMISE(promise, info, std::move(r_info)); - promise.set_result(to_raw_transactions(std::move(info))); - }); + promise.wrap(to_raw_transactions)); return td::Status::OK(); } - -td::Result from_tonlib(tonlib_api::inputKey& input_key) { +td::Result from_tonlib(tonlib_api::inputKeyRegular& input_key) { if (!input_key.key_) { return TonlibError::EmptyField("key"); } @@ -1052,22 +1444,28 @@ td::Result from_tonlib(tonlib_api::inputKey& input_key) { std::move(input_key.local_password_)}; } -// TestWallet +td::Result from_tonlib(tonlib_api::InputKey& input_key) { + td::Result r_key; + tonlib_api::downcast_call( + input_key, td::overloaded([&](tonlib_api::inputKeyRegular& input_key) { r_key = from_tonlib(input_key); }, + [&](tonlib_api::inputKeyFake&) { r_key = KeyStorage::fake_input_key(); })); + return r_key; +} + +// ton::TestWallet td::Status TonlibClient::do_request(const tonlib_api::testWallet_init& request, td::Promise>&& promise) { if (!request.private_key_) { return td::Status::Error(400, "Field private_key must not be empty"); } TRY_RESULT(input_key, from_tonlib(*request.private_key_)); - auto init_state = TestWallet::get_init_state(td::Ed25519::PublicKey(input_key.key.public_key.copy())); - auto address = GenericAccount::get_address(0 /*zerochain*/, init_state); + auto init_state = ton::TestWallet::get_init_state(td::Ed25519::PublicKey(input_key.key.public_key.copy())); + auto address = ton::GenericAccount::get_address(0 /*zerochain*/, init_state); TRY_RESULT(private_key, key_storage_.load_private_key(std::move(input_key))); - auto init_message = TestWallet::get_init_message(td::Ed25519::PrivateKey(std::move(private_key.private_key))); - return do_request( - tonlib_api::raw_sendMessage(tonlib_api::make_object(address.rserialize(true)), - vm::std_boc_serialize(init_state).move_as_ok().as_slice().str(), - vm::std_boc_serialize(init_message).move_as_ok().as_slice().str()), - std::move(promise)); + auto init_message = ton::TestWallet::get_init_message(td::Ed25519::PrivateKey(std::move(private_key.private_key))); + auto message = ton::GenericAccount::create_ext_message(address, std::move(init_state), std::move(init_message)); + make_request(int_api::SendMessage{std::move(message)}, to_any_promise(std::move(promise))); + return td::Status::OK(); } td::Status TonlibClient::do_request(const tonlib_api::testWallet_sendGrams& request, @@ -1078,33 +1476,30 @@ td::Status TonlibClient::do_request(const tonlib_api::testWallet_sendGrams& requ if (!request.private_key_) { return TonlibError::EmptyField("private_key"); } - if (request.message_.size() > TestWallet::max_message_size) { + if (request.message_.size() > ton::TestWallet::max_message_size) { return TonlibError::MessageTooLong(); } TRY_RESULT(account_address, get_account_address(request.destination_->account_address_)); TRY_RESULT(input_key, from_tonlib(*request.private_key_)); - auto address = GenericAccount::get_address( - 0 /*zerochain*/, TestWallet::get_init_state(td::Ed25519::PublicKey(input_key.key.public_key.copy()))); + auto address = ton::GenericAccount::get_address( + 0 /*zerochain*/, ton::TestWallet::get_init_state(td::Ed25519::PublicKey(input_key.key.public_key.copy()))); TRY_RESULT(private_key_str, key_storage_.load_private_key(std::move(input_key))); auto private_key = td::Ed25519::PrivateKey(std::move(private_key_str.private_key)); - std::string init_state; + td::Ref init_state; if (request.seqno_ == 0) { TRY_RESULT_PREFIX(public_key, private_key.get_public_key(), TonlibError::Internal()); - init_state = vm::std_boc_serialize(TestWallet::get_init_state(public_key)).move_as_ok().as_slice().str(); + init_state = ton::TestWallet::get_init_state(public_key); } - auto message = - TestWallet::make_a_gift_message(private_key, request.seqno_, request.amount_, request.message_, account_address); + auto message = ton::TestWallet::make_a_gift_message(private_key, request.seqno_, request.amount_, request.message_, + account_address); auto message_hash = message->get_hash().as_slice().str(); - td::Promise> new_promise = - [promise = std::move(promise), - message_hash = std::move(message_hash)](td::Result> res) mutable { - TRY_RESULT_PROMISE(promise, ok, std::move(res)); - promise.set_value(tonlib_api::make_object(0, std::move(message_hash))); - }; - return do_request(tonlib_api::raw_sendMessage( - tonlib_api::make_object(address.rserialize(true)), - std::move(init_state), vm::std_boc_serialize(std::move(message)).move_as_ok().as_slice().str()), - std::move(new_promise)); + auto new_promise = promise.wrap([message_hash = std::move(message_hash)](auto&&) { + return tonlib_api::make_object(0, std::move(message_hash)); + }); + + auto ext_message = ton::GenericAccount::create_ext_message(address, std::move(init_state), std::move(message)); + make_request(int_api::SendMessage{std::move(message)}, std::move(new_promise)); + return td::Status::OK(); } td::Status TonlibClient::do_request(tonlib_api::testWallet_getAccountState& request, @@ -1113,32 +1508,27 @@ td::Status TonlibClient::do_request(tonlib_api::testWallet_getAccountState& requ return TonlibError::EmptyField("account_address"); } TRY_RESULT(account_address, get_account_address(request.account_address_->account_address_)); - auto actor_id = actor_id_++; - actors_[actor_id] = td::actor::create_actor( - "GetAccountState", client_.get_client(), std::move(account_address), actor_shared(this, actor_id), - [promise = std::move(promise)](td::Result r_state) mutable { - TRY_RESULT_PROMISE(promise, state, std::move(r_state)); - promise.set_result(to_testWallet_accountState(std::move(state))); - }); + make_request(int_api::GetAccountState{std::move(account_address)}, + promise.wrap([](auto&& res) { return res->to_testWallet_accountState(); })); return td::Status::OK(); } -// Wallet +// ton::Wallet td::Status TonlibClient::do_request(const tonlib_api::wallet_init& request, td::Promise>&& promise) { if (!request.private_key_) { return TonlibError::EmptyField("private_key"); } TRY_RESULT(input_key, from_tonlib(*request.private_key_)); - auto init_state = Wallet::get_init_state(td::Ed25519::PublicKey(input_key.key.public_key.copy())); - auto address = GenericAccount::get_address(0 /*zerochain*/, init_state); + auto init_state = ton::Wallet::get_init_state(td::Ed25519::PublicKey(input_key.key.public_key.copy())); + auto address = ton::GenericAccount::get_address(0 /*zerochain*/, init_state); TRY_RESULT(private_key, key_storage_.load_private_key(std::move(input_key))); - auto init_message = Wallet::get_init_message(td::Ed25519::PrivateKey(std::move(private_key.private_key))); - return do_request( - tonlib_api::raw_sendMessage(tonlib_api::make_object(address.rserialize(true)), - vm::std_boc_serialize(init_state).move_as_ok().as_slice().str(), - vm::std_boc_serialize(init_message).move_as_ok().as_slice().str()), - std::move(promise)); + auto init_message = ton::Wallet::get_init_message(td::Ed25519::PrivateKey(std::move(private_key.private_key))); + auto message = + ton::GenericAccount::create_ext_message(std::move(address), std::move(init_state), std::move(init_message)); + + make_request(int_api::SendMessage{std::move(message)}, to_any_promise(std::move(promise))); + return td::Status::OK(); } td::Status TonlibClient::do_request(const tonlib_api::wallet_sendGrams& request, @@ -1149,35 +1539,31 @@ td::Status TonlibClient::do_request(const tonlib_api::wallet_sendGrams& request, if (!request.private_key_) { return TonlibError::EmptyField("private_key"); } - if (request.message_.size() > Wallet::max_message_size) { + if (request.message_.size() > ton::Wallet::max_message_size) { return TonlibError::MessageTooLong(); } TRY_RESULT_PREFIX(valid_until, td::narrow_cast_safe(request.valid_until_), TonlibError::InvalidField("valid_until", "overflow")); TRY_RESULT(account_address, get_account_address(request.destination_->account_address_)); TRY_RESULT(input_key, from_tonlib(*request.private_key_)); - auto address = GenericAccount::get_address( - 0 /*zerochain*/, Wallet::get_init_state(td::Ed25519::PublicKey(input_key.key.public_key.copy()))); + auto address = ton::GenericAccount::get_address( + 0 /*zerochain*/, ton::Wallet::get_init_state(td::Ed25519::PublicKey(input_key.key.public_key.copy()))); TRY_RESULT(private_key_str, key_storage_.load_private_key(std::move(input_key))); auto private_key = td::Ed25519::PrivateKey(std::move(private_key_str.private_key)); - std::string init_state; + td::Ref init_state; if (request.seqno_ == 0) { TRY_RESULT_PREFIX(public_key, private_key.get_public_key(), TonlibError::Internal()); - init_state = vm::std_boc_serialize(Wallet::get_init_state(public_key)).move_as_ok().as_slice().str(); + init_state = ton::Wallet::get_init_state(public_key); } - auto message = Wallet::make_a_gift_message(private_key, request.seqno_, valid_until, request.amount_, - request.message_, account_address); + auto message = ton::Wallet::make_a_gift_message(private_key, request.seqno_, valid_until, request.amount_, + request.message_, account_address); auto message_hash = message->get_hash().as_slice().str(); - td::Promise> new_promise = - [promise = std::move(promise), valid_until, - message_hash = std::move(message_hash)](td::Result> res) mutable { - TRY_RESULT_PROMISE(promise, ok, std::move(res)); - promise.set_value(tonlib_api::make_object(valid_until, std::move(message_hash))); - }; - return do_request(tonlib_api::raw_sendMessage( - tonlib_api::make_object(address.rserialize(true)), - std::move(init_state), vm::std_boc_serialize(std::move(message)).move_as_ok().as_slice().str()), - std::move(new_promise)); + auto new_promise = promise.wrap([valid_until, message_hash = std::move(message_hash)](auto&&) { + return tonlib_api::make_object(valid_until, std::move(message_hash)); + }); + auto ext_message = ton::GenericAccount::create_ext_message(address, std::move(init_state), std::move(message)); + make_request(int_api::SendMessage{std::move(message)}, std::move(new_promise)); + return td::Status::OK(); } td::Status TonlibClient::do_request(tonlib_api::wallet_getAccountState& request, @@ -1186,49 +1572,37 @@ td::Status TonlibClient::do_request(tonlib_api::wallet_getAccountState& request, return TonlibError::EmptyField("account_address"); } TRY_RESULT(account_address, get_account_address(request.account_address_->account_address_)); - auto actor_id = actor_id_++; - actors_[actor_id] = td::actor::create_actor( - "GetAccountState", client_.get_client(), std::move(account_address), actor_shared(this, actor_id), - [promise = std::move(promise)](td::Result r_state) mutable { - TRY_RESULT_PROMISE(promise, state, std::move(r_state)); - promise.set_result(to_wallet_accountState(std::move(state))); - }); + make_request(int_api::GetAccountState{std::move(account_address)}, + promise.wrap([](auto&& res) { return res->to_wallet_accountState(); })); return td::Status::OK(); } -// TestGiver +// ton::TestGiver td::Status TonlibClient::do_request(const tonlib_api::testGiver_sendGrams& request, td::Promise>&& promise) { if (!request.destination_) { return TonlibError::EmptyField("destination"); } - if (request.message_.size() > TestGiver::max_message_size) { + if (request.message_.size() > ton::TestGiver::max_message_size) { return TonlibError::MessageTooLong(); } TRY_RESULT(account_address, get_account_address(request.destination_->account_address_)); - auto message = TestGiver::make_a_gift_message(request.seqno_, request.amount_, request.message_, account_address); + auto message = + ton::TestGiver::make_a_gift_message(request.seqno_, request.amount_, request.message_, account_address); auto message_hash = message->get_hash().as_slice().str(); - td::Promise> new_promise = - [promise = std::move(promise), - message_hash = std::move(message_hash)](td::Result> res) mutable { - TRY_RESULT_PROMISE(promise, ok, std::move(res)); - promise.set_value(tonlib_api::make_object(0, std::move(message_hash))); - }; - return do_request(tonlib_api::raw_sendMessage( - tonlib_api::make_object(TestGiver::address().rserialize(true)), "", - vm::std_boc_serialize(std::move(message)).move_as_ok().as_slice().str()), - std::move(new_promise)); + auto new_promise = promise.wrap([message_hash = std::move(message_hash)](auto&&) { + return tonlib_api::make_object(0, std::move(message_hash)); + }); + + auto ext_message = ton::GenericAccount::create_ext_message(ton::TestGiver::address(), {}, std::move(message)); + make_request(int_api::SendMessage{std::move(message)}, std::move(new_promise)); + return td::Status::OK(); } td::Status TonlibClient::do_request(const tonlib_api::testGiver_getAccountState& request, td::Promise>&& promise) { - auto actor_id = actor_id_++; - actors_[actor_id] = td::actor::create_actor( - "GetAccountState", client_.get_client(), TestGiver::address(), actor_shared(this, actor_id), - [promise = std::move(promise)](td::Result r_state) mutable { - TRY_RESULT_PROMISE(promise, state, std::move(r_state)); - promise.set_result(to_testGiver_accountState(std::move(state))); - }); + make_request(int_api::GetAccountState{ton::TestGiver::address()}, + promise.wrap([](auto&& res) { return res->to_testGiver_accountState(); })); return td::Status::OK(); } @@ -1238,51 +1612,29 @@ td::Status TonlibClient::do_request(const tonlib_api::generic_getAccountState& r return TonlibError::EmptyField("account_address"); } TRY_RESULT(account_address, get_account_address(request.account_address_->account_address_)); - auto actor_id = actor_id_++; - actors_[actor_id] = td::actor::create_actor( - "GetAccountState", client_.get_client(), std::move(account_address), actor_shared(this, actor_id), - [promise = std::move(promise)](td::Result r_state) mutable { - TRY_RESULT_PROMISE(promise, state, std::move(r_state)); - promise.set_result(to_generic_accountState(std::move(state))); - }); + make_request(int_api::GetAccountState{std::move(account_address)}, + promise.wrap([](auto&& res) { return res->to_generic_accountState(); })); return td::Status::OK(); } -class TonlibQueryActor : public td::actor::Actor { +class GenericCreateSendGrams : public TonlibQueryActor { public: - TonlibQueryActor(td::actor::ActorShared client) : client_(std::move(client)) { - } - template - void send_query(QueryT query, td::Promise promise) { - td::actor::send_lambda(client_, - [self = client_.get(), query = std::move(query), promise = std::move(promise)]() mutable { - auto status = self.get_actor_unsafe().do_request(query, std::move(promise)); - if (status.is_error()) { - promise.set_error(std::move(status)); - } - }); - } - - private: - td::actor::ActorShared client_; -}; - -class GenericSendGrams : public TonlibQueryActor { - public: - GenericSendGrams(td::actor::ActorShared client, tonlib_api::generic_sendGrams send_grams, - td::Promise>&& promise) + GenericCreateSendGrams(td::actor::ActorShared client, + tonlib_api::generic_createSendGramsQuery send_grams, + td::Promise>&& promise) : TonlibQueryActor(std::move(client)), send_grams_(std::move(send_grams)), promise_(std::move(promise)) { } private: - tonlib_api::generic_sendGrams send_grams_; - td::Promise> promise_; + tonlib_api::generic_createSendGramsQuery send_grams_; + td::Promise> promise_; - tonlib_api::object_ptr source_state_; - block::StdAddress source_address_; - - tonlib_api::object_ptr destination_state_; - bool is_destination_bounceable_{false}; + td::unique_ptr source_; + td::unique_ptr destination_; + bool has_private_key_{false}; + bool is_fake_key_{false}; + td::optional private_key_; + td::optional public_key_; void check(td::Status status) { if (status.is_error()) { @@ -1299,182 +1651,380 @@ class GenericSendGrams : public TonlibQueryActor { } td::Status do_start_up() { + if (send_grams_.timeout_ < 0 || send_grams_.timeout_ > 300) { + return TonlibError::InvalidField("timeout", "must be between 0 and 300"); + } if (!send_grams_.destination_) { return TonlibError::EmptyField("destination"); } - TRY_RESULT(destination_address, get_account_address(send_grams_.destination_->account_address_)); - is_destination_bounceable_ = destination_address.bounceable; - if (!send_grams_.source_) { - return TonlibError::EmptyField("destination"); + return TonlibError::EmptyField("source"); + } + if (send_grams_.amount_ < 0) { + return TonlibError::InvalidField("amount", "can't be negative"); } + // Use this limit as a preventive check + if (send_grams_.message_.size() > ton::Wallet::max_message_size) { + return TonlibError::MessageTooLong(); + } + TRY_RESULT(destination_address, get_account_address(send_grams_.destination_->account_address_)); TRY_RESULT(source_address, get_account_address(send_grams_.source_->account_address_)); - source_address_ = std::move(source_address); - - send_query(tonlib_api::generic_getAccountState( - tonlib_api::make_object(send_grams_.source_->account_address_)), - [actor_id = actor_id(this)](auto r_res) { - send_closure(actor_id, &GenericSendGrams::on_source_state, std::move(r_res)); - }); - send_query(tonlib_api::generic_getAccountState( - tonlib_api::make_object(send_grams_.destination_->account_address_)), - [actor_id = actor_id(this)](auto r_res) { - send_closure(actor_id, &GenericSendGrams::on_destination_state, std::move(r_res)); - }); + + has_private_key_ = bool(send_grams_.private_key_); + if (has_private_key_) { + TRY_RESULT(input_key, from_tonlib(*send_grams_.private_key_)); + is_fake_key_ = send_grams_.private_key_->get_id() == tonlib_api::inputKeyFake::ID; + public_key_ = td::Ed25519::PublicKey(input_key.key.public_key.copy()); + send_query(int_api::GetPrivateKey{std::move(input_key)}, + promise_send_closure(actor_id(this), &GenericCreateSendGrams::on_private_key)); + } + + send_query(int_api::GetAccountState{source_address}, + promise_send_closure(actor_id(this), &GenericCreateSendGrams::on_source_state)); + + send_query(int_api::GetAccountState{destination_address}, + promise_send_closure(actor_id(this), &GenericCreateSendGrams::on_destination_state)); + return do_loop(); } - static tonlib_api::object_ptr clone(const tonlib_api::object_ptr& ptr) { - if (!ptr) { - return nullptr; - } - return tonlib_api::make_object(ptr->public_key_, ptr->secret_.copy()); + void on_private_key(td::Result r_key) { + check(do_on_private_key(std::move(r_key))); } - static tonlib_api::object_ptr clone(const tonlib_api::object_ptr& ptr) { - if (!ptr) { - return nullptr; - } - return tonlib_api::make_object(clone(ptr->key_), ptr->local_password_.copy()); + td::Status do_on_private_key(td::Result r_key) { + TRY_RESULT(key, std::move(r_key)); + private_key_ = td::Ed25519::PrivateKey(std::move(key.private_key)); + return do_loop(); } - void on_source_state(td::Result> r_state) { + void on_source_state(td::Result> r_state) { check(do_on_source_state(std::move(r_state))); } - td::Status do_on_source_state(td::Result> r_state) { + td::Status do_on_source_state(td::Result> r_state) { TRY_RESULT(state, std::move(r_state)); - source_state_ = std::move(state); - if (source_state_->get_id() == tonlib_api::generic_accountStateUninited::ID && send_grams_.private_key_ && - send_grams_.private_key_->key_) { - TRY_RESULT(key_bytes, get_public_key(send_grams_.private_key_->key_->public_key_)); - auto key = td::Ed25519::PublicKey(td::SecureString(key_bytes.key)); - - if (GenericAccount::get_address(0 /*zerochain*/, TestWallet::get_init_state(key)).addr == source_address_.addr) { - auto state = ton::move_tl_object_as(source_state_); - source_state_ = tonlib_api::make_object( - tonlib_api::make_object(state->account_state_->balance_, 0, nullptr, - state->account_state_->sync_utime_)); - } else if (GenericAccount::get_address(0 /*zerochain*/, Wallet::get_init_state(key)).addr == - source_address_.addr) { - auto state = ton::move_tl_object_as(source_state_); - source_state_ = tonlib_api::make_object( - tonlib_api::make_object(state->account_state_->balance_, 0, nullptr, - state->account_state_->sync_utime_)); - } + source_ = std::move(state); + if (source_->get_wallet_type() == AccountState::Empty && public_key_) { + source_->guess_type_by_public_key(public_key_.value()); + } + + //TODO: pass default type through api + if (source_->get_wallet_type() == AccountState::Empty && public_key_ && is_fake_key_) { + source_->guess_type_default(public_key_.value()); } + return do_loop(); } - void on_destination_state(td::Result> r_state) { + void on_destination_state(td::Result> r_state) { check(do_on_destination_state(std::move(r_state))); } - td::Status do_on_destination_state(td::Result> r_state) { + td::Status do_on_destination_state(td::Result> r_state) { TRY_RESULT(state, std::move(r_state)); - destination_state_ = std::move(state); - if (destination_state_->get_id() == tonlib_api::generic_accountStateUninited::ID && is_destination_bounceable_) { + destination_ = std::move(state); + if (destination_->is_frozen()) { //FIXME: after restoration of frozen accounts will be supported - if (!static_cast(*destination_state_) - .account_state_->frozen_hash_.empty()) { - return TonlibError::TransferToFrozen(); - //return TonlibError::DangerousTransaction("Transfer to frozen wallet"); - } - if (send_grams_.allow_send_to_uninited_) { - TRY_RESULT(destination_address, get_account_address(send_grams_.destination_->account_address_)); - destination_address.bounceable = false; - auto new_destination_address = destination_address.rserialize(true); - LOG(INFO) << "Change destination address from bounceable to non-bounceable " - << send_grams_.destination_->account_address_ << " -> " << new_destination_address; - send_grams_.destination_->account_address_ = std::move(new_destination_address); - } else { + return TonlibError::TransferToFrozen(); + } + if (destination_->get_wallet_type() == AccountState::Empty && destination_->get_address().bounceable) { + if (!send_grams_.allow_send_to_uninited_) { return TonlibError::DangerousTransaction("Transfer to uninited wallet"); } + destination_->make_non_bounceable(); + LOG(INFO) << "Change destination address from bounceable to non-bounceable "; } return do_loop(); } td::Status do_loop() { - if (!source_state_ || !destination_state_) { + if (!source_ || !destination_) { + return td::Status::OK(); + } + if (has_private_key_ && !private_key_) { return td::Status::OK(); } - downcast_call( - *source_state_, - td::overloaded( - [&](tonlib_api::generic_accountStateTestGiver& test_giver_state) { - auto amount = send_grams_.amount_; - send_query(tonlib_api::testGiver_sendGrams(std::move(send_grams_.destination_), - test_giver_state.account_state_->seqno_, amount, - std::move(send_grams_.message_)), - std::move(promise_)); - stop(); - }, - [&](tonlib_api::generic_accountStateTestWallet& test_wallet_state) { - auto amount = send_grams_.amount_; - auto balance = test_wallet_state.account_state_->balance_; - if (false && amount == balance) { - amount = -1; - } else if (amount >= balance) { - promise_.set_error(TonlibError::NotEnoughFunds()); - return stop(); - } - send_query(tonlib_api::testWallet_sendGrams( - std::move(send_grams_.private_key_), std::move(send_grams_.destination_), - test_wallet_state.account_state_->seqno_, amount, std::move(send_grams_.message_)), - std::move(promise_)); - stop(); - }, - [&](tonlib_api::generic_accountStateWallet& wallet_state) { - auto amount = send_grams_.amount_; - auto balance = wallet_state.account_state_->balance_; - if (false && amount == balance) { - amount = -1; - } else if (amount >= balance) { - promise_.set_error(TonlibError::NotEnoughFunds()); - return stop(); - } - send_query( - tonlib_api::wallet_sendGrams(std::move(send_grams_.private_key_), std::move(send_grams_.destination_), - wallet_state.account_state_->seqno_, - send_grams_.timeout_ == 0 - ? 60 + wallet_state.account_state_->sync_utime_ - : send_grams_.timeout_ + wallet_state.account_state_->sync_utime_, - amount, std::move(send_grams_.message_)), - std::move(promise_)); - stop(); - }, - [&](tonlib_api::generic_accountStateUninited&) { - promise_.set_error(TonlibError::AccountNotInited()); - stop(); - }, - [&](tonlib_api::generic_accountStateRaw&) { - promise_.set_error(TonlibError::AccountTypeUnknown()); - stop(); - })); + + Query::Raw raw; + + auto amount = send_grams_.amount_; + if (amount > source_->get_balance()) { + return TonlibError::NotEnoughFunds(); + } + if (amount == source_->get_balance()) { + amount = -1; + } + auto message = send_grams_.message_; + switch (source_->get_wallet_type()) { + case AccountState::Empty: + return TonlibError::AccountNotInited(); + case AccountState::Unknown: + return TonlibError::AccountTypeUnknown(); + case AccountState::Giver: { + raw.message_body = ton::TestGiver::make_a_gift_message(0, amount, message, destination_->get_address()); + break; + } + + case AccountState::SimpleWallet: { + if (!private_key_) { + return TonlibError::EmptyField("private_key"); + } + if (message.size() > ton::TestWallet::max_message_size) { + return TonlibError::MessageTooLong(); + } + TRY_RESULT(seqno, ton::TestWallet(source_->get_smc_state()).get_seqno()); + raw.message_body = ton::TestWallet::make_a_gift_message(private_key_.unwrap(), seqno, amount, message, + destination_->get_address()); + break; + } + case AccountState::Wallet: { + if (!private_key_) { + return TonlibError::EmptyField("private_key"); + } + if (message.size() > ton::Wallet::max_message_size) { + return TonlibError::MessageTooLong(); + } + TRY_RESULT(seqno, ton::Wallet(source_->get_smc_state()).get_seqno()); + auto valid_until = source_->get_sync_time(); + valid_until += send_grams_.timeout_ == 0 ? 60 : send_grams_.timeout_; + raw.valid_until = valid_until; + raw.message_body = ton::Wallet::make_a_gift_message(private_key_.unwrap(), seqno, valid_until, amount, message, + destination_->get_address()); + break; + } + } + + raw.new_state = source_->get_new_state(); + raw.message = ton::GenericAccount::create_ext_message(source_->get_address(), raw.new_state, raw.message_body); + raw.source = std::move(source_); + raw.destination = std::move(destination_); + + promise_.set_value(td::make_unique(std::move(raw))); + stop(); return td::Status::OK(); } }; +td::int64 TonlibClient::register_query(td::unique_ptr query) { + auto query_id = ++next_query_id_; + queries_[query_id] = std::move(query); + return query_id; +} + +td::Result> TonlibClient::get_query_info(td::int64 id) { + auto it = queries_.find(id); + if (it == queries_.end()) { + return TonlibError::InvalidQueryId(); + } + return tonlib_api::make_object(id, it->second->get_valid_until(), + it->second->get_body_hash().as_slice().str()); +} + +void TonlibClient::finish_create_query(td::Result> r_query, + td::Promise>&& promise) { + TRY_RESULT_PROMISE(promise, query, std::move(r_query)); + auto id = register_query(std::move(query)); + promise.set_result(get_query_info(id)); +} +void TonlibClient::finish_send_query(td::Result> r_query, + td::Promise>&& promise) { + TRY_RESULT_PROMISE(promise, query, std::move(r_query)); + auto result = tonlib_api::make_object(query->get_valid_until(), + query->get_body_hash().as_slice().str()); + auto id = register_query(std::move(query)); + make_request(tonlib_api::query_send(id), + promise.wrap([result = std::move(result)](auto&&) mutable { return std::move(result); })); +} +td::Status TonlibClient::do_request(tonlib_api::generic_createSendGramsQuery& request, + td::Promise>&& promise) { + auto id = actor_id_++; + actors_[id] = td::actor::create_actor( + "GenericSendGrams", actor_shared(this, id), std::move(request), + promise.send_closure(actor_id(this), &TonlibClient::finish_create_query)); + return td::Status::OK(); +} + +td::Status TonlibClient::do_request(const tonlib_api::raw_createQuery& request, + td::Promise>&& promise) { + if (!request.destination_) { + return TonlibError::EmptyField("destination"); + } + TRY_RESULT(account_address, get_account_address(request.destination_->account_address_)); + + td::optional smc_state; + if (!request.init_code_.empty()) { + TRY_RESULT_PREFIX(code, vm::std_boc_deserialize(request.init_code_), TonlibError::InvalidBagOfCells("init_code")); + TRY_RESULT_PREFIX(data, vm::std_boc_deserialize(request.init_data_), TonlibError::InvalidBagOfCells("init_data")); + smc_state = ton::SmartContract::State{std::move(code), std::move(data)}; + } + TRY_RESULT_PREFIX(body, vm::std_boc_deserialize(request.body_), TonlibError::InvalidBagOfCells("body")); + + td::Promise> new_promise = + promise.send_closure(actor_id(this), &TonlibClient::finish_create_query); + + make_request(int_api::GetAccountState{account_address}, + new_promise.wrap([smc_state = std::move(smc_state), body = std::move(body)](auto&& source) mutable { + Query::Raw raw; + if (smc_state) { + source->set_new_state(smc_state.unwrap()); + } + raw.new_state = source->get_new_state(); + raw.message_body = std::move(body); + raw.message = + ton::GenericAccount::create_ext_message(source->get_address(), raw.new_state, raw.message_body); + raw.source = std::move(source); + raw.destination = nullptr; + return td::make_unique(std::move(raw)); + })); + return td::Status::OK(); +} + td::Status TonlibClient::do_request(tonlib_api::generic_sendGrams& request, td::Promise>&& promise) { - if (request.timeout_ < 0 || request.timeout_ > 300) { - return TonlibError::InvalidField("timeout", "must be between 0 and 300"); - } auto id = actor_id_++; - actors_[id] = td::actor::create_actor("GenericSendGrams", actor_shared(this, id), - std::move(request), std::move(promise)); + actors_[id] = td::actor::create_actor( + "GenericSendGrams", actor_shared(this, id), + tonlib_api::generic_createSendGramsQuery(std::move(request.private_key_), std::move(request.source_), + std::move(request.destination_), request.amount_, request.timeout_, + request.allow_send_to_uninited_, std::move(request.message_)), + promise.send_closure(actor_id(this), &TonlibClient::finish_send_query)); return td::Status::OK(); } -td::Status TonlibClient::do_request(tonlib_api::sync& request, td::Promise>&& promise) { - client_.with_last_block([promise = std::move(promise)](td::Result r_last_block) mutable { - TRY_RESULT_PROMISE(promise, last_block, std::move(r_last_block)); - (void)last_block; - promise.set_value(tonlib_api::make_object()); +td::Status TonlibClient::do_request(const tonlib_api::query_getInfo& request, + td::Promise>&& promise) { + promise.set_result(get_query_info(request.id_)); + return td::Status::OK(); +} + +void TonlibClient::query_estimate_fees(td::int64 id, bool ignore_chksig, td::Result r_state, + td::Promise>&& promise) { + auto it = queries_.find(id); + if (it == queries_.end()) { + promise.set_error(TonlibError::InvalidQueryId()); + return; + } + TRY_RESULT_PROMISE(promise, state, std::move(r_state)); + TRY_RESULT_PROMISE_PREFIX(promise, fees, TRY_VM(it->second->estimate_fees(ignore_chksig, *state.config)), + TonlibError::Internal()); + promise.set_value( + tonlib_api::make_object(fees.first.to_tonlib_api(), fees.second.to_tonlib_api())); +} + +td::Status TonlibClient::do_request(const tonlib_api::query_estimateFees& request, + td::Promise>&& promise) { + auto it = queries_.find(request.id_); + if (it == queries_.end()) { + return TonlibError::InvalidQueryId(); + } + + client_.with_last_config([this, id = request.id_, ignore_chksig = request.ignore_chksig_, + promise = std::move(promise)](td::Result r_state) mutable { + this->query_estimate_fees(id, ignore_chksig, std::move(r_state), std::move(promise)); }); return td::Status::OK(); } +td::Status TonlibClient::do_request(const tonlib_api::query_send& request, + td::Promise>&& promise) { + auto it = queries_.find(request.id_); + if (it == queries_.end()) { + return TonlibError::InvalidQueryId(); + } + + auto message = it->second->get_message(); + if (GET_VERBOSITY_LEVEL() >= VERBOSITY_NAME(DEBUG)) { + std::ostringstream ss; + block::gen::t_Message_Any.print_ref(ss, message); + LOG(DEBUG) << ss.str(); + } + make_request(int_api::SendMessage{std::move(message)}, to_any_promise(std::move(promise))); + return td::Status::OK(); +} + +td::Status TonlibClient::do_request(tonlib_api::query_forget& request, + td::Promise>&& promise) { + auto it = queries_.find(request.id_); + if (it == queries_.end()) { + return TonlibError::InvalidQueryId(); + } + promise.set_value(tonlib_api::make_object()); + return td::Status::OK(); +} + +td::int64 TonlibClient::register_smc(td::unique_ptr smc) { + auto smc_id = ++next_smc_id_; + smcs_[smc_id] = std::move(smc); + return smc_id; +} + +td::Result> TonlibClient::get_smc_info(td::int64 id) { + auto it = smcs_.find(id); + if (it == smcs_.end()) { + return TonlibError::InvalidSmcId(); + } + return tonlib_api::make_object(id); +} + +void TonlibClient::finish_load_smc(td::unique_ptr smc, + td::Promise>&& promise) { + auto id = register_smc(std::move(smc)); + promise.set_result(get_smc_info(id)); +} + +td::Status TonlibClient::do_request(const tonlib_api::smc_load& request, + td::Promise>&& promise) { + if (!request.account_address_) { + return TonlibError::EmptyField("account_address"); + } + TRY_RESULT(account_address, get_account_address(request.account_address_->account_address_)); + make_request(int_api::GetAccountState{std::move(account_address)}, + promise.send_closure(actor_id(this), &TonlibClient::finish_load_smc)); + return td::Status::OK(); +} + +td::Status TonlibClient::do_request(const tonlib_api::smc_getCode& request, + td::Promise>&& promise) { + auto it = smcs_.find(request.id_); + if (it == smcs_.end()) { + return TonlibError::InvalidSmcId(); + } + auto& acc = it->second; + auto code = acc->get_smc_state().code; + promise.set_value(tonlib_api::make_object(to_bytes(code))); + return td::Status::OK(); +} + +td::Status TonlibClient::do_request(const tonlib_api::smc_getData& request, + td::Promise>&& promise) { + auto it = smcs_.find(request.id_); + if (it == smcs_.end()) { + return TonlibError::InvalidSmcId(); + } + auto& acc = it->second; + auto data = acc->get_smc_state().data; + promise.set_value(tonlib_api::make_object(to_bytes(data))); + return td::Status::OK(); +} + +td::Status TonlibClient::do_request(const tonlib_api::smc_getState& request, + td::Promise>&& promise) { + auto it = smcs_.find(request.id_); + if (it == smcs_.end()) { + return TonlibError::InvalidSmcId(); + } + auto& acc = it->second; + auto data = acc->get_raw_state(); + promise.set_value(tonlib_api::make_object(to_bytes(data))); + return td::Status::OK(); +} + +td::Status TonlibClient::do_request(tonlib_api::sync& request, td::Promise>&& promise) { + client_.with_last_block(to_any_promise(std::move(promise))); + return td::Status::OK(); +} + td::Result public_key_from_bytes(td::Slice bytes) { TRY_RESULT_PREFIX(key_bytes, block::PublicKey::from_bytes(bytes), TonlibError::Internal()); return key_bytes; @@ -1541,13 +2091,7 @@ td::Status TonlibClient::do_request(const tonlib_api::exportPemKey& request, if (!request.input_key_) { return TonlibError::EmptyField("input_key"); } - if (!request.input_key_->key_) { - return TonlibError::EmptyField("key"); - } - - TRY_RESULT(key_bytes, get_public_key(request.input_key_->key_->public_key_)); - KeyStorage::InputKey input_key{{td::SecureString(key_bytes.key), std::move(request.input_key_->key_->secret_)}, - std::move(request.input_key_->local_password_)}; + TRY_RESULT(input_key, from_tonlib(*request.input_key_)); TRY_RESULT(exported_pem_key, key_storage_.export_pem_key(std::move(input_key), std::move(request.key_password_))); promise.set_value(tonlib_api::make_object(std::move(exported_pem_key.pem))); return td::Status::OK(); @@ -1594,26 +2138,16 @@ td::Status TonlibClient::do_request(const tonlib_api::changeLocalPassword& reque if (!request.input_key_) { return TonlibError::EmptyField("input_key"); } - if (!request.input_key_->key_) { - return TonlibError::EmptyField("key"); - } TRY_RESULT(input_key, from_tonlib(*request.input_key_)); TRY_RESULT(key, key_storage_.change_local_password(std::move(input_key), std::move(request.new_local_password_))); - promise.set_value( - tonlib_api::make_object(request.input_key_->key_->public_key_, std::move(key.secret))); + promise.set_value(tonlib_api::make_object(key.public_key.as_slice().str(), std::move(key.secret))); return td::Status::OK(); } td::Status TonlibClient::do_request(const tonlib_api::onLiteServerQueryResult& request, td::Promise>&& promise) { send_closure(ext_client_outbound_, &ExtClientOutbound::on_query_result, request.id_, td::BufferSlice(request.bytes_), - [promise = std::move(promise)](td::Result res) mutable { - if (res.is_ok()) { - promise.set_value(tonlib_api::make_object()); - } else { - promise.set_error(res.move_as_error()); - } - }); + to_any_promise(std::move(promise))); return td::Status::OK(); } td::Status TonlibClient::do_request(const tonlib_api::onLiteServerQueryError& request, @@ -1621,13 +2155,7 @@ td::Status TonlibClient::do_request(const tonlib_api::onLiteServerQueryError& re send_closure(ext_client_outbound_, &ExtClientOutbound::on_query_result, request.id_, td::Status::Error(request.error_->code_, request.error_->message_) .move_as_error_prefix(TonlibError::LiteServerNetwork()), - [promise = std::move(promise)](td::Result res) mutable { - if (res.is_ok()) { - promise.set_value(tonlib_api::make_object()); - } else { - promise.set_error(res.move_as_error()); - } - }); + to_any_promise(std::move(promise))); return td::Status::OK(); } @@ -1710,4 +2238,131 @@ tonlib_api::object_ptr TonlibClient::do_static_request(const SimpleEncryption::kdf(request.password_, request.salt_, request.iterations_)); } +td::Status TonlibClient::do_request(int_api::GetAccountState request, + td::Promise>&& promise) { + auto actor_id = actor_id_++; + actors_[actor_id] = td::actor::create_actor( + "GetAccountState", client_.get_client(), request.address, actor_shared(this, actor_id), + promise.wrap([address = request.address](auto&& state) mutable { + return td::make_unique(std::move(address), std::move(state)); + })); + return td::Status::OK(); +} + +td::Status TonlibClient::do_request(int_api::GetPrivateKey request, td::Promise&& promise) { + TRY_RESULT(pk, key_storage_.load_private_key(std::move(request.input_key))); + promise.set_value(std::move(pk)); + return td::Status::OK(); +} + +td::Status TonlibClient::do_request(int_api::SendMessage request, td::Promise&& promise) { + client_.send_query(ton::lite_api::liteServer_sendMessage(vm::std_boc_serialize(request.message).move_as_ok()), + to_any_promise(std::move(promise))); + return td::Status::OK(); +} + +td::Status TonlibClient::do_request(const tonlib_api::liteServer_getInfo& request, + td::Promise>&& promise) { + client_.send_query(ton::lite_api::liteServer_getVersion(), promise.wrap([](auto&& version) { + return tonlib_api::make_object(version->now_, version->version_, + version->capabilities_); + })); + return td::Status::OK(); +} + +template +td::Status TonlibClient::do_request(const tonlib_api::runTests& request, P&&) { + UNREACHABLE(); + return TonlibError::Internal(); +} +template +td::Status TonlibClient::do_request(const tonlib_api::raw_getAccountAddress& request, P&&) { + UNREACHABLE(); + return TonlibError::Internal(); +} +template +td::Status TonlibClient::do_request(const tonlib_api::testWallet_getAccountAddress& request, P&&) { + UNREACHABLE(); + return TonlibError::Internal(); +} +template +td::Status TonlibClient::do_request(const tonlib_api::wallet_getAccountAddress& request, P&&) { + UNREACHABLE(); + return TonlibError::Internal(); +} +template +td::Status TonlibClient::do_request(const tonlib_api::testGiver_getAccountAddress& request, P&&) { + UNREACHABLE(); + return TonlibError::Internal(); +} +template +td::Status TonlibClient::do_request(const tonlib_api::packAccountAddress& request, P&&) { + UNREACHABLE(); + return TonlibError::Internal(); +} +template +td::Status TonlibClient::do_request(const tonlib_api::unpackAccountAddress& request, P&&) { + UNREACHABLE(); + return TonlibError::Internal(); +} +template +td::Status TonlibClient::do_request(tonlib_api::getBip39Hints& request, P&&) { + UNREACHABLE(); + return TonlibError::Internal(); +} +template +td::Status TonlibClient::do_request(tonlib_api::setLogStream& request, P&&) { + UNREACHABLE(); + return TonlibError::Internal(); +} +template +td::Status TonlibClient::do_request(const tonlib_api::getLogStream& request, P&&) { + UNREACHABLE(); + return TonlibError::Internal(); +} +template +td::Status TonlibClient::do_request(const tonlib_api::setLogVerbosityLevel& request, P&&) { + UNREACHABLE(); + return TonlibError::Internal(); +} +template +td::Status TonlibClient::do_request(const tonlib_api::setLogTagVerbosityLevel& request, P&&) { + UNREACHABLE(); + return TonlibError::Internal(); +} +template +td::Status TonlibClient::do_request(const tonlib_api::getLogVerbosityLevel& request, P&&) { + UNREACHABLE(); + return TonlibError::Internal(); +} +template +td::Status TonlibClient::do_request(const tonlib_api::getLogTagVerbosityLevel& request, P&&) { + UNREACHABLE(); + return TonlibError::Internal(); +} +template +td::Status TonlibClient::do_request(const tonlib_api::getLogTags& request, P&&) { + UNREACHABLE(); + return TonlibError::Internal(); +} +template +td::Status TonlibClient::do_request(const tonlib_api::addLogMessage& request, P&&) { + UNREACHABLE(); + return TonlibError::Internal(); +} +template +td::Status TonlibClient::do_request(const tonlib_api::encrypt& request, P&&) { + UNREACHABLE(); + return TonlibError::Internal(); +} +template +td::Status TonlibClient::do_request(const tonlib_api::decrypt& request, P&&) { + UNREACHABLE(); + return TonlibError::Internal(); +} +template +td::Status TonlibClient::do_request(const tonlib_api::kdf& request, P&&) { + UNREACHABLE(); + return TonlibError::Internal(); +} } // namespace tonlib diff --git a/tonlib/tonlib/TonlibClient.h b/tonlib/tonlib/TonlibClient.h index 148f8bae7..9a844fa36 100644 --- a/tonlib/tonlib/TonlibClient.h +++ b/tonlib/tonlib/TonlibClient.h @@ -34,6 +34,17 @@ #include namespace tonlib { +namespace int_api { +struct GetAccountState; +struct GetPrivateKey; +struct SendMessage; +inline std::string to_string(const int_api::SendMessage&) { + return "Send message"; +} +} // namespace int_api +class AccountState; +class Query; + class TonlibClient : public td::actor::Actor { public: template @@ -66,6 +77,7 @@ class TonlibClient : public td::actor::Actor { td::actor::ActorOwn raw_client_; td::actor::ActorId ext_client_outbound_; td::actor::ActorOwn raw_last_block_; + td::actor::ActorOwn raw_last_config_; ExtClient client_; td::CancellationTokenSource source_; @@ -76,6 +88,7 @@ class TonlibClient : public td::actor::Actor { ExtClientRef get_client_ref(); void init_ext_client(); void init_last_block(); + void init_last_config(); bool is_closing_{false}; td::uint32 ref_cnt_{1}; @@ -127,9 +140,53 @@ class TonlibClient : public td::actor::Actor { static object_ptr do_static_request(const tonlib_api::decrypt& request); static object_ptr do_static_request(const tonlib_api::kdf& request); + template + td::Status do_request(const tonlib_api::runTests& request, P&&); + template + td::Status do_request(const tonlib_api::raw_getAccountAddress& request, P&&); + template + td::Status do_request(const tonlib_api::testWallet_getAccountAddress& request, P&&); + template + td::Status do_request(const tonlib_api::wallet_getAccountAddress& request, P&&); + template + td::Status do_request(const tonlib_api::testGiver_getAccountAddress& request, P&&); + template + td::Status do_request(const tonlib_api::packAccountAddress& request, P&&); + template + td::Status do_request(const tonlib_api::unpackAccountAddress& request, P&&); + template + td::Status do_request(tonlib_api::getBip39Hints& request, P&&); + + template + td::Status do_request(tonlib_api::setLogStream& request, P&&); + template + td::Status do_request(const tonlib_api::getLogStream& request, P&&); + template + td::Status do_request(const tonlib_api::setLogVerbosityLevel& request, P&&); + template + td::Status do_request(const tonlib_api::setLogTagVerbosityLevel& request, P&&); + template + td::Status do_request(const tonlib_api::getLogVerbosityLevel& request, P&&); + template + td::Status do_request(const tonlib_api::getLogTagVerbosityLevel& request, P&&); + template + td::Status do_request(const tonlib_api::getLogTags& request, P&&); + template + td::Status do_request(const tonlib_api::addLogMessage& request, P&&); + + template + td::Status do_request(const tonlib_api::encrypt& request, P&&); + template + td::Status do_request(const tonlib_api::decrypt& request, P&&); + template + td::Status do_request(const tonlib_api::kdf& request, P&&); + template - td::Status do_request(const T& request, P&& promise) { - return td::Status::Error(400, "Function is unsupported"); + void make_request(T&& request, P&& promise) { + auto status = do_request(std::forward(request), std::move(promise)); + if (status.is_error()) { + promise.operator()(std::move(status)); + } } td::Status set_config(object_ptr config); @@ -138,6 +195,11 @@ class TonlibClient : public td::actor::Actor { td::Status do_request(tonlib_api::options_setConfig& request, td::Promise>&& promise); td::Status do_request(const tonlib_api::raw_sendMessage& request, td::Promise>&& promise); + td::Status do_request(const tonlib_api::raw_createAndSendMessage& request, + td::Promise>&& promise); + td::Status do_request(const tonlib_api::raw_createQuery& request, + td::Promise>&& promise); + td::Status do_request(tonlib_api::raw_getAccountState& request, td::Promise>&& promise); td::Status do_request(tonlib_api::raw_getTransactions& request, @@ -191,6 +253,48 @@ class TonlibClient : public td::actor::Actor { td::Status do_request(const tonlib_api::onLiteServerQueryError& request, td::Promise>&& promise); + td::int64 next_query_id_{0}; + std::map> queries_; + td::int64 register_query(td::unique_ptr query); + td::Result> get_query_info(td::int64 id); + void finish_create_query(td::Result> r_query, + td::Promise>&& promise); + void finish_send_query(td::Result> r_query, + td::Promise>&& promise); + void query_estimate_fees(td::int64 id, bool ignore_chksig, td::Result r_state, + td::Promise>&& promise); + + td::Status do_request(const tonlib_api::query_getInfo& request, + td::Promise>&& promise); + td::Status do_request(const tonlib_api::query_estimateFees& request, + td::Promise>&& promise); + td::Status do_request(const tonlib_api::query_send& request, td::Promise>&& promise); + td::Status do_request(tonlib_api::query_forget& request, td::Promise>&& promise); + + td::Status do_request(tonlib_api::generic_createSendGramsQuery& request, + td::Promise>&& promise); + + td::int64 next_smc_id_{0}; + std::map> smcs_; + + td::int64 register_smc(td::unique_ptr smc); + td::Result> get_smc_info(td::int64 id); + void finish_load_smc(td::unique_ptr query, td::Promise>&& promise); + td::Status do_request(const tonlib_api::smc_load& request, td::Promise>&& promise); + td::Status do_request(const tonlib_api::smc_getCode& request, + td::Promise>&& promise); + td::Status do_request(const tonlib_api::smc_getData& request, + td::Promise>&& promise); + td::Status do_request(const tonlib_api::smc_getState& request, + td::Promise>&& promise); + + td::Status do_request(int_api::GetAccountState request, td::Promise>&&); + td::Status do_request(int_api::GetPrivateKey request, td::Promise&&); + td::Status do_request(int_api::SendMessage request, td::Promise&& promise); + + td::Status do_request(const tonlib_api::liteServer_getInfo& request, + td::Promise>&& promise); + void proxy_request(td::int64 query_id, std::string data); friend class TonlibQueryActor; diff --git a/tonlib/tonlib/TonlibError.h b/tonlib/tonlib/TonlibError.h index b521c1368..656811230 100644 --- a/tonlib/tonlib/TonlibError.h +++ b/tonlib/tonlib/TonlibError.h @@ -27,6 +27,8 @@ // INVALID_MNEMONIC // INVALID_BAG_OF_CELLS // INVALID_PUBLIC_KEY +// INVALID_QUERY_ID +// INVALID_SMC_ID // INVALID_ACCOUNT_ADDRESS // INVALID_CONFIG // INVALID_PEM_KEY @@ -65,6 +67,12 @@ struct TonlibError { static td::Status InvalidAccountAddress() { return td::Status::Error(400, "INVALID_ACCOUNT_ADDRESS"); } + static td::Status InvalidQueryId() { + return td::Status::Error(400, "INVALID_QUERY_ID"); + } + static td::Status InvalidSmcId() { + return td::Status::Error(400, "INVALID_SMC_ID"); + } static td::Status InvalidConfig(td::Slice reason) { return td::Status::Error(400, PSLICE() << "INVALID_CONFIG: " << reason); } @@ -110,6 +118,9 @@ struct TonlibError { static td::Status ValidateTransactions() { return td::Status::Error(500, "VALIDATE_TRANSACTION"); } + static td::Status ValidateConfig() { + return td::Status::Error(500, "VALIDATE_CONFIG"); + } static td::Status ValidateZeroState(td::Slice message) { return td::Status::Error(500, PSLICE() << "VALIDATE_ZERO_STATE: " << message); } diff --git a/tonlib/tonlib/tonlib-cli.cpp b/tonlib/tonlib/tonlib-cli.cpp index 21f68b4b6..b401a027f 100644 --- a/tonlib/tonlib/tonlib-cli.cpp +++ b/tonlib/tonlib/tonlib-cli.cpp @@ -20,6 +20,57 @@ #include #include +// Consider this as a TODO list: +// +// (from lite-client) +// SUPPORTED +// "time\tGet server time\n" +// "remote-version\tShows server time, version and capabilities\n" +// "help []\tThis help\n" // TODO: support [] +// "quit\tExit\n"; +// "sendfile \tLoad a serialized message from and send it to server\n" +// +// "saveaccount[code|data] []\tSaves into specified file the most recent state " +// "runmethod ...\tRuns GET method of account " +// "with specified parameters\n" +// +// WONTSUPPORT +// +// UNSUPPORTED +//"last\tGet last block and state info from server\n" +//"status\tShow connection and local database status\n" +//"getaccount []\tLoads the most recent state of specified account; is in " +//"[:] format\n" +//"(StateInit) or just the code or data of specified account; is in " +//"[:] format\n" +//"allshards []\tShows shard configuration from the most recent masterchain " +//"state or from masterchain state corresponding to \n" +//"getconfig [...]\tShows specified or all configuration parameters from the latest masterchain state\n" +//"getconfigfrom [...]\tShows specified or all configuration parameters from the " +//"masterchain state of \n" +//"saveconfig []\tSaves all configuration parameters into specified file\n" +//"gethead \tShows block header for \n" +//"getblock \tDownloads block\n" +//"dumpblock \tDownloads and dumps specified block\n" +//"getstate \tDownloads state corresponding to specified block\n" +//"dumpstate \tDownloads and dumps state corresponding to specified block\n" +//"dumptrans \tDumps one transaction of specified account\n" +//"lasttrans[dump] []\tShows or dumps specified transaction and " +//"several preceding " +//"ones\n" +//"listblocktrans[rev] [ ]\tLists block transactions, " +//"starting immediately after or before the specified one\n" +//"blkproofchain[step] []\tDownloads and checks proof of validity of the /"second " +//"indicated block (or the last known masterchain block) starting from given block\n" +//"byseqno \tLooks up a block by workchain, shard and seqno, and shows its " +//"header\n" +//"bylt \tLooks up a block by workchain, shard and logical time, and shows its " +//"header\n" +//"byutime \tLooks up a block by workchain, shard and creation time, and " +//"shows its header\n" +//"known\tShows the list of all known block ids\n" +//"privkey \tLoads a private key from file\n" + class TonlibCli : public td::actor::Actor { public: struct Options { @@ -201,8 +252,25 @@ class TonlibCli : public td::actor::Actor { } return true; }; + + td::Promise cmd_promise = [line = line.clone()](td::Result res) { + if (res.is_ok()) { + // on_ok + } else { + td::TerminalIO::out() << "Query {" << line.as_slice() << "} FAILED: \n\t" << res.error() << "\n"; + } + }; + if (cmd == "help") { - td::TerminalIO::out() << "help - show this help\n"; + td::TerminalIO::out() << "help\tThis help\n"; + td::TerminalIO::out() << "time\tGet server time\n"; + td::TerminalIO::out() << "remote-version\tShows server time, version and capabilities\n"; + td::TerminalIO::out() << "sendfile \tLoad a serialized message from and send it to server\n"; + td::TerminalIO::out() << "exit\tExit\n"; + td::TerminalIO::out() << "quit\tExit\n"; + td::TerminalIO::out() + << "saveaccount[code|data] \tSaves into specified file the most recent state\n"; + td::TerminalIO::out() << "genkey - generate new secret key\n"; td::TerminalIO::out() << "keys - show all stored keys\n"; td::TerminalIO::out() << "unpackaddress
- validate and parse address\n"; @@ -210,6 +278,7 @@ class TonlibCli : public td::actor::Actor { td::TerminalIO::out() << "importkey - import key\n"; td::TerminalIO::out() << "deletekeys - delete ALL PRIVATE KEYS\n"; td::TerminalIO::out() << "exportkey [] - export key\n"; + td::TerminalIO::out() << "exportkeypem [] - export key\n"; td::TerminalIO::out() << "setconfig [] [] [] - set lite server config\n"; td::TerminalIO::out() << "getstate - get state of simple wallet with requested key\n"; td::TerminalIO::out() @@ -219,10 +288,9 @@ class TonlibCli : public td::actor::Actor { " to .\n" << "\t could also be 'giver'\n" << "\t could also be 'giver' or smartcontract address\n"; - td::TerminalIO::out() << "exit - exit from this programm\n"; } else if (cmd == "genkey") { generate_key(); - } else if (cmd == "exit") { + } else if (cmd == "exit" || cmd == "quit") { is_closing_ = true; client_.reset(); ref_cnt_--; @@ -233,8 +301,8 @@ class TonlibCli : public td::actor::Actor { //delete_key(parser.read_word()); } else if (cmd == "deletekeys") { delete_all_keys(); - } else if (cmd == "exportkey") { - export_key(parser.read_word()); + } else if (cmd == "exportkey" || cmd == "exportkeypem") { + export_key(cmd.str(), parser.read_word()); } else if (cmd == "importkey") { import_key(parser.read_all()); } else if (cmd == "setconfig") { @@ -265,20 +333,93 @@ class TonlibCli : public td::actor::Actor { set_bounceable(addr, to_bool(bounceable, true)); } else if (cmd == "netstats") { dump_netstats(); + // reviewed from here } else if (cmd == "sync") { - sync(); + sync(std::move(cmd_promise)); + } else if (cmd == "time") { + remote_time(std::move(cmd_promise)); + } else if (cmd == "remote-version") { + remote_version(std::move(cmd_promise)); + } else if (cmd == "sendfile") { + send_file(parser.read_word(), std::move(cmd_promise)); + } else if (cmd == "saveaccount" || cmd == "saveaccountdata" || cmd == "saveaccountcode") { + auto path = parser.read_word(); + auto address = parser.read_word(); + save_account(cmd, path, address, std::move(cmd_promise)); + } else { + cmd_promise.set_error(td::Status::Error(PSLICE() << "Unkwnown query `" << cmd << "`")); } + if (cmd_promise) { + cmd_promise.set_value(td::Unit()); + } + } + + void remote_time(td::Promise promise) { + send_query(tonlib_api::make_object(), promise.wrap([](auto&& info) { + td::TerminalIO::out() << "Lite server time is: " << info->now_ << "\n"; + return td::Unit(); + })); + } + + void remote_version(td::Promise promise) { + send_query(tonlib_api::make_object(), promise.wrap([](auto&& info) { + td::TerminalIO::out() << "Lite server time is: " << info->now_ << "\n"; + td::TerminalIO::out() << "Lite server version is: " << info->version_ << "\n"; + td::TerminalIO::out() << "Lite server capabilities are: " << info->capabilities_ << "\n"; + return td::Unit(); + })); + } + + void send_file(td::Slice name, td::Promise promise) { + TRY_RESULT_PROMISE(promise, data, td::read_file_str(name.str())); + send_query(tonlib_api::make_object(std::move(data)), promise.wrap([](auto&& info) { + td::TerminalIO::out() << "Query was sent\n"; + return td::Unit(); + })); + } + + void save_account(td::Slice cmd, td::Slice path, td::Slice address, td::Promise promise) { + TRY_RESULT_PROMISE(promise, addr, to_account_address(address, false)); + send_query(tonlib_api::make_object(std::move(addr.address)), + promise.send_closure(actor_id(this), &TonlibCli::save_account_2, cmd.str(), path.str(), address.str())); } - void sync() { + void save_account_2(std::string cmd, std::string path, std::string address, + tonlib_api::object_ptr info, td::Promise promise) { + auto with_query = [&, self = this](auto query, auto log) { + send_query(std::move(query), + promise.send_closure(actor_id(self), &TonlibCli::save_account_3, std::move(path), std::move(log))); + }; + if (cmd == "saveaccount") { + with_query(tonlib_api::make_object(info->id_), + PSTRING() << "StateInit of account " << address); + } else if (cmd == "saveaccountcode") { + with_query(tonlib_api::make_object(info->id_), PSTRING() + << "Code of account " << address); + } else if (cmd == "saveaccountdata") { + with_query(tonlib_api::make_object(info->id_), PSTRING() + << "Data of account " << address); + } else { + promise.set_error(td::Status::Error("Unknown query")); + } + } + + void save_account_3(std::string path, std::string log, tonlib_api::object_ptr cell, + td::Promise promise) { + TRY_STATUS_PROMISE(promise, td::write_file(path, cell->bytes_)); + td::TerminalIO::out() << log << " was successfully written to the disk(" << td::format::as_size(cell->bytes_.size()) + << ")\n"; + promise.set_value(td::Unit()); + } + + void sync(td::Promise promise) { using tonlib_api::make_object; - send_query(make_object(), [](auto r_ok) { - LOG_IF(ERROR, r_ok.is_error()) << r_ok.error(); - if (r_ok.is_ok()) { - td::TerminalIO::out() << "synchronized\n"; - } - }); + send_query(make_object(), promise.wrap([](auto&&) { + td::TerminalIO::out() << "synchronized\n"; + return td::Unit(); + })); } + void dump_netstats() { td::TerminalIO::out() << td::tag("snd", td::format::as_size(snd_bytes_)) << "\n"; td::TerminalIO::out() << td::tag("rcv", td::format::as_size(rcv_bytes_)) << "\n"; @@ -298,15 +439,41 @@ class TonlibCli : public td::actor::Actor { void on_tonlib_result(std::uint64_t id, tonlib_api::object_ptr result) { if (id == 0) { - if (result->get_id() == tonlib_api::updateSendLiteServerQuery::ID) { - auto update = tonlib_api::move_object_as(std::move(result)); - CHECK(!raw_client_.empty()); - snd_bytes_ += update->data_.size(); - send_closure(raw_client_, &ton::adnl::AdnlExtClient::send_query, "query", td::BufferSlice(update->data_), - td::Timestamp::in(5), - [actor_id = actor_id(this), id = update->id_](td::Result res) { - send_closure(actor_id, &TonlibCli::on_adnl_result, id, std::move(res)); - }); + switch (result->get_id()) { + case tonlib_api::updateSendLiteServerQuery::ID: { + auto update = tonlib_api::move_object_as(std::move(result)); + CHECK(!raw_client_.empty()); + snd_bytes_ += update->data_.size(); + send_closure(raw_client_, &ton::adnl::AdnlExtClient::send_query, "query", td::BufferSlice(update->data_), + td::Timestamp::in(5), + [actor_id = actor_id(this), id = update->id_](td::Result res) { + send_closure(actor_id, &TonlibCli::on_adnl_result, id, std::move(res)); + }); + return; + } + case tonlib_api::updateSyncState::ID: { + auto update = tonlib_api::move_object_as(std::move(result)); + switch (update->sync_state_->get_id()) { + case tonlib_api::syncStateDone::ID: { + td::TerminalIO::out() << "synchronization: DONE\n"; + break; + } + case tonlib_api::syncStateInProgress::ID: { + auto progress = tonlib_api::move_object_as(update->sync_state_); + auto from = progress->from_seqno_; + auto to = progress->to_seqno_; + auto at = progress->current_seqno_; + auto d = to - from; + if (d <= 0) { + td::TerminalIO::out() << "synchronization: ???\n"; + } else { + td::TerminalIO::out() << "synchronization: " << 100 * (at - from) / d << "%\n"; + } + break; + } + } + return; + } } } auto it = query_handlers_.find(id); @@ -407,7 +574,7 @@ class TonlibCli : public td::actor::Actor { info.public_key = key->public_key_; info.secret = std::move(key->secret_); keys_.push_back(std::move(info)); - export_key(key->public_key_, keys_.size() - 1, std::move(password)); + export_key("exportkey", key->public_key_, keys_.size() - 1, std::move(password)); store_keys(); }); } @@ -596,11 +763,11 @@ class TonlibCli : public td::actor::Actor { td::TerminalIO::out() << "Ok\n"; }); } - void export_key(td::Slice key) { + void export_key(std::string cmd, td::Slice key) { if (key.empty()) { dump_keys(); td::TerminalIO::out() << "Choose public key (hex prefix or #N)"; - cont_ = [this](td::Slice key) { this->export_key(key); }; + cont_ = [this, cmd](td::Slice key) { this->export_key(cmd, key); }; return; } auto r_key_i = to_key_i(key); @@ -614,24 +781,40 @@ class TonlibCli : public td::actor::Actor { << "public key: " << td::buffer_to_hex(keys_[key_i].public_key) << "\n"; td::TerminalIO::out() << "Enter password (could be empty)"; - cont_ = [this, key = key.str(), key_i](td::Slice password) { this->export_key(key, key_i, password); }; + cont_ = [this, cmd, key = key.str(), key_i](td::Slice password) { this->export_key(cmd, key, key_i, password); }; } - void export_key(std::string key, size_t key_i, td::Slice password) { + void export_key(std::string cmd, std::string key, size_t key_i, td::Slice password) { using tonlib_api::make_object; - send_query(make_object(make_object( - make_object(keys_[key_i].public_key, keys_[key_i].secret.copy()), - td::SecureString(password))), - [this, key = std::move(key), key_i](auto r_res) { - if (r_res.is_error()) { - td::TerminalIO::out() << "Can't export key id: [" << key << "] " << r_res.error() << "\n"; - return; - } - dump_key(key_i); - for (auto& word : r_res.ok()->word_list_) { - td::TerminalIO::out() << " " << word.as_slice() << "\n"; - } - }); + if (cmd == "exportkey") { + send_query(make_object(make_object( + make_object(keys_[key_i].public_key, keys_[key_i].secret.copy()), + td::SecureString(password))), + [this, key = std::move(key), key_i](auto r_res) { + if (r_res.is_error()) { + td::TerminalIO::out() << "Can't export key id: [" << key << "] " << r_res.error() << "\n"; + return; + } + dump_key(key_i); + for (auto& word : r_res.ok()->word_list_) { + td::TerminalIO::out() << " " << word.as_slice() << "\n"; + } + }); + } else { + send_query(make_object( + make_object( + make_object(keys_[key_i].public_key, keys_[key_i].secret.copy()), + td::SecureString(password)), + td::SecureString("cucumber")), + [this, key = std::move(key), key_i](auto r_res) { + if (r_res.is_error()) { + td::TerminalIO::out() << "Can't export key id: [" << key << "] " << r_res.error() << "\n"; + return; + } + dump_key(key_i); + td::TerminalIO::out() << "\n" << r_res.ok()->pem_.as_slice() << "\n"; + }); + } } void import_key(td::Slice slice, std::vector words = {}) { @@ -669,7 +852,7 @@ class TonlibCli : public td::actor::Actor { info.public_key = key->public_key_; info.secret = std::move(key->secret_); keys_.push_back(std::move(info)); - export_key(key->public_key_, keys_.size() - 1, std::move(password)); + export_key("exportkey", key->public_key_, keys_.size() - 1, std::move(password)); store_keys(); }); } @@ -830,21 +1013,42 @@ class TonlibCli : public td::actor::Actor { } using tonlib_api::make_object; auto key = !from.secret.empty() - ? make_object( + ? make_object( make_object(from.public_key, from.secret.copy()), td::SecureString(password)) : nullptr; - send_query( - make_object(std::move(key), std::move(from.address), std::move(to.address), - grams, 60, allow_send_to_uninited, std::move(msg)), - [this](auto r_res) { - if (r_res.is_error()) { - td::TerminalIO::out() << "Can't transfer: " << r_res.error() << "\n"; - on_error(); - return; - } - td::TerminalIO::out() << to_string(r_res.ok()); - on_ok(); - }); + send_query(make_object(std::move(key), std::move(from.address), + std::move(to.address), grams, 60, + allow_send_to_uninited, std::move(msg)), + [self = this](auto r_res) { + if (r_res.is_error()) { + td::TerminalIO::out() << "Can't transfer: " << r_res.error() << "\n"; + self->on_error(); + return; + } + td::TerminalIO::out() << to_string(r_res.ok()); + self->send_query(make_object(r_res.ok()->id_, false), + [self](auto r_res) { + if (r_res.is_error()) { + td::TerminalIO::out() << "Can't transfer: " << r_res.error() << "\n"; + self->on_error(); + return; + } + td::TerminalIO::out() << to_string(r_res.ok()); + self->on_ok(); + }); + + self->send_query(make_object(r_res.ok()->id_), [self](auto r_res) { + if (r_res.is_error()) { + td::TerminalIO::out() << "Can't transfer: " << r_res.error() << "\n"; + self->on_error(); + return; + } + td::TerminalIO::out() << to_string(r_res.ok()); + self->on_ok(); + }); + + self->on_ok(); + }); } void init_simple_wallet(td::Slice key) { @@ -871,7 +1075,7 @@ class TonlibCli : public td::actor::Actor { void init_simple_wallet(std::string key, size_t key_i, td::Slice password) { using tonlib_api::make_object; if (options_.use_simple_wallet) { - send_query(make_object(make_object( + send_query(make_object(make_object( make_object(keys_[key_i].public_key, keys_[key_i].secret.copy()), td::SecureString(password))), [key = std::move(key)](auto r_res) { @@ -882,7 +1086,7 @@ class TonlibCli : public td::actor::Actor { td::TerminalIO::out() << to_string(r_res.ok()); }); } else { - send_query(make_object(make_object( + send_query(make_object(make_object( make_object(keys_[key_i].public_key, keys_[key_i].secret.copy()), td::SecureString(password))), [key = std::move(key)](auto r_res) { diff --git a/tonlib/tonlib/utils.cpp b/tonlib/tonlib/utils.cpp index 5cef21f56..fec914431 100644 --- a/tonlib/tonlib/utils.cpp +++ b/tonlib/tonlib/utils.cpp @@ -20,20 +20,9 @@ #include "td/utils/misc.h" #include "vm/cellslice.h" namespace tonlib { -int VERBOSITY_NAME(tonlib_query) = VERBOSITY_NAME(INFO); -int VERBOSITY_NAME(last_block) = VERBOSITY_NAME(INFO); -int VERBOSITY_NAME(lite_server) = VERBOSITY_NAME(INFO); +int VERBOSITY_NAME(tonlib_query) = VERBOSITY_NAME(DEBUG); +int VERBOSITY_NAME(last_block) = VERBOSITY_NAME(DEBUG); +int VERBOSITY_NAME(last_config) = VERBOSITY_NAME(DEBUG); +int VERBOSITY_NAME(lite_server) = VERBOSITY_NAME(DEBUG); -td::Result> binary_bitstring_to_cellslice(td::Slice literal) { - unsigned char buff[128]; - if (!begins_with(literal, "b{") || !ends_with(literal, "}")) { - return td::Status::Error("Invalid binary bitstring constant"); - } - int bits = - (int)td::bitstring::parse_bitstring_binary_literal(buff, sizeof(buff), literal.begin() + 2, literal.end() - 1); - if (bits < 0) { - return td::Status::Error("Invalid binary bitstring constant"); - } - return td::Ref{true, vm::CellBuilder().store_bits(td::ConstBitPtr{buff}, bits).finalize()}; -} } // namespace tonlib diff --git a/tonlib/tonlib/utils.h b/tonlib/tonlib/utils.h index 840bc10d2..8bcf9ab9b 100644 --- a/tonlib/tonlib/utils.h +++ b/tonlib/tonlib/utils.h @@ -23,19 +23,8 @@ #include "block/block-parse.h" namespace tonlib { -template -auto try_f(F&& f) noexcept -> decltype(f()) { - try { - return f(); - } catch (vm::VmError error) { - return td::Status::Error(PSLICE() << "Got a vm exception: " << error.get_msg()); - } -} - -#define TRY_VM(f) try_f([&] { return f; }) - extern int VERBOSITY_NAME(tonlib_query); extern int VERBOSITY_NAME(last_block); +extern int VERBOSITY_NAME(last_config); extern int VERBOSITY_NAME(lite_server); -td::Result> binary_bitstring_to_cellslice(td::Slice literal); } // namespace tonlib diff --git a/validator/CMakeLists.txt b/validator/CMakeLists.txt index e66b4bfe6..73cf0c99e 100644 --- a/validator/CMakeLists.txt +++ b/validator/CMakeLists.txt @@ -10,6 +10,8 @@ add_subdirectory(impl) set(VALIDATOR_DB_SOURCE db/archiver.cpp db/archiver.hpp + db/archive-db.cpp + db/archive-db.hpp db/blockdb.cpp db/blockdb.hpp db/celldb.cpp @@ -25,6 +27,9 @@ set(VALIDATOR_DB_SOURCE db/statedb.cpp db/staticfilesdb.cpp db/staticfilesdb.hpp + + db/package.hpp + db/package.cpp ) set(VALIDATOR_HEADERS diff --git a/validator/block-handle.hpp b/validator/block-handle.hpp index 42eddccb2..3cbaf2e10 100644 --- a/validator/block-handle.hpp +++ b/validator/block-handle.hpp @@ -59,6 +59,7 @@ struct BlockHandleImpl : public BlockHandleInterface { dbf_moved = 0x1000000, dbf_deleted = 0x2000000, dbf_deleted_boc = 0x4000000, + dbf_moved_new = 0x8000000, dbf_processed = 0x10000000, }; @@ -95,6 +96,9 @@ struct BlockHandleImpl : public BlockHandleInterface { bool moved_to_storage() const override { return flags_.load(std::memory_order_consume) & Flags::dbf_moved; } + bool moved_to_archive() const override { + return flags_.load(std::memory_order_consume) & Flags::dbf_moved_new; + } bool deleted() const override { return flags_.load(std::memory_order_consume) & Flags::dbf_deleted; } @@ -393,6 +397,15 @@ struct BlockHandleImpl : public BlockHandleInterface { flags_ |= Flags::dbf_moved; unlock(); } + void set_moved_to_archive() override { + if (flags_.load(std::memory_order_consume) & Flags::dbf_moved_new) { + return; + } + lock(); + flags_ |= Flags::dbf_moved_new; + flags_ &= ~Flags::dbf_moved; + unlock(); + } void set_deleted() override { if (flags_.load(std::memory_order_consume) & Flags::dbf_deleted) { return; diff --git a/validator/db/archive-db.cpp b/validator/db/archive-db.cpp new file mode 100644 index 000000000..cf0dbed1c --- /dev/null +++ b/validator/db/archive-db.cpp @@ -0,0 +1,332 @@ +#include "archive-db.hpp" +#include "common/errorcode.h" + +#include "common/int-to-string.hpp" +#include "files-async.hpp" + +#include "td/db/RocksDb.h" +#include "validator/fabric.h" + +namespace ton { + +namespace validator { + +void PackageWriter::append(std::string filename, td::BufferSlice data, + td::Promise> promise) { + auto offset = package_->append(std::move(filename), std::move(data)); + auto size = package_->size(); + + promise.set_value(std::pair{offset, size}); +} + +class PackageReader : public td::actor::Actor { + public: + PackageReader(std::shared_ptr package, td::uint64 offset, + td::Promise> promise) + : package_(std::move(package)), offset_(offset), promise_(std::move(promise)) { + } + void start_up() { + promise_.set_result(package_->read(offset_)); + } + + private: + std::shared_ptr package_; + td::uint64 offset_; + td::Promise> promise_; +}; + +void ArchiveFile::start_up() { + auto R = Package::open(path_, false, true); + if (R.is_error()) { + LOG(FATAL) << "failed to open/create archive '" << path_ << "': " << R.move_as_error(); + return; + } + package_ = std::make_shared(R.move_as_ok()); + index_ = std::make_shared(td::RocksDb::open(path_ + ".index").move_as_ok()); + + std::string value; + auto R2 = index_->get("status", value); + R2.ensure(); + + if (R2.move_as_ok() == td::KeyValue::GetStatus::Ok) { + auto len = td::to_integer(value); + package_->truncate(len); + } else { + package_->truncate(0); + } + + writer_ = td::actor::create_actor("writer", package_); +} + +void ArchiveFile::write(FileDb::RefId ref_id, td::BufferSlice data, td::Promise promise) { + auto P = td::PromiseCreator::lambda([SelfId = actor_id(this), ref_id, promise = std::move(promise)]( + td::Result> R) mutable { + if (R.is_error()) { + promise.set_error(R.move_as_error()); + return; + } + auto v = R.move_as_ok(); + td::actor::send_closure(SelfId, &ArchiveFile::completed_write, std::move(ref_id), v.first, v.second, + std::move(promise)); + }); + + FileHash hash; + ref_id.visit([&](const auto &obj) { hash = obj.hash(); }); + + td::actor::send_closure(writer_, &PackageWriter::append, hash.to_hex(), std::move(data), std::move(P)); +} + +void ArchiveFile::write_handle(BlockHandle handle, td::Promise promise) { + FileHash hash = fileref::BlockInfo{handle->id()}.hash(); + index_->begin_transaction().ensure(); + do { + auto version = handle->version(); + index_->set(hash.to_hex(), handle->serialize().as_slice().str()).ensure(); + handle->flushed_upto(version); + } while (handle->need_flush()); + index_->commit_transaction().ensure(); + promise.set_value(td::Unit()); +} + +void ArchiveFile::completed_write(FileDb::RefId ref_id, td::uint64 offset, td::uint64 new_size, + td::Promise promise) { + FileHash hash; + ref_id.visit([&](const auto &obj) { hash = obj.hash(); }); + index_->begin_transaction().ensure(); + index_->set("status", td::to_string(new_size)).ensure(); + index_->set(hash.to_hex(), td::to_string(offset)).ensure(); + index_->commit_transaction().ensure(); + promise.set_value(td::Unit()); +} + +void ArchiveFile::read(FileDb::RefId ref_id, td::Promise promise) { + FileHash hash; + ref_id.visit([&](const auto &obj) { hash = obj.hash(); }); + std::string value; + auto R = index_->get(hash.to_hex(), value); + R.ensure(); + if (R.move_as_ok() == td::KeyValue::GetStatus::NotFound) { + promise.set_error(td::Status::Error(ErrorCode::notready, "not in db (archive)")); + return; + } + auto offset = td::to_integer(value); + auto P = td::PromiseCreator::lambda( + [promise = std::move(promise)](td::Result> R) mutable { + if (R.is_error()) { + promise.set_error(R.move_as_error()); + } else { + promise.set_value(std::move(R.move_as_ok().second)); + } + }); + td::actor::create_actor("reader", package_, offset, std::move(P)).release(); +} + +void ArchiveFile::read_handle(BlockIdExt block_id, td::Promise promise) { + FileHash hash = fileref::BlockInfo{block_id}.hash(); + + std::string value; + auto R = index_->get(hash.to_hex(), value); + R.ensure(); + + if (R.move_as_ok() == td::KeyValue::GetStatus::NotFound) { + promise.set_error(td::Status::Error(ErrorCode::notready, "not in archive db")); + return; + } + promise.set_result(create_block_handle(td::BufferSlice{value})); +} + +ArchiveManager::ArchiveManager(std::string db_root) : db_root_(db_root) { +} + +void ArchiveManager::write(UnixTime ts, bool key_block, FileDb::RefId ref_id, td::BufferSlice data, + td::Promise promise) { + auto f = get_file(ts, key_block); + td::actor::send_closure(f->file_actor_id(), &ArchiveFile::write, std::move(ref_id), std::move(data), + std::move(promise)); +} + +void ArchiveManager::write_handle(BlockHandle handle, td::Promise promise) { + auto f = get_file(handle->unix_time(), handle->is_key_block()); + update_desc(*f, handle->id().shard_full(), handle->id().seqno(), handle->logical_time()); + td::actor::send_closure(f->file_actor_id(), &ArchiveFile::write_handle, std::move(handle), std::move(promise)); +} + +void ArchiveManager::read(UnixTime ts, bool key_block, FileDb::RefId ref_id, td::Promise promise) { + auto f = get_file(ts, key_block); + td::actor::send_closure(f->file_actor_id(), &ArchiveFile::read, std::move(ref_id), std::move(promise)); +} + +void ArchiveManager::read_handle(BlockIdExt block_id, td::Promise promise) { + if (block_id.is_masterchain()) { + auto f = get_file_by_seqno(block_id.shard_full(), block_id.seqno(), true); + if (!f) { + read_handle_cont(block_id, std::move(promise)); + return; + } + auto P = td::PromiseCreator::lambda( + [SelfId = actor_id(this), block_id, promise = std::move(promise)](td::Result R) mutable { + if (R.is_error()) { + td::actor::send_closure(SelfId, &ArchiveManager::read_handle_cont, block_id, std::move(promise)); + } else { + promise.set_value(R.move_as_ok()); + } + }); + td::actor::send_closure(f->file_actor_id(), &ArchiveFile::read_handle, block_id, std::move(P)); + } else { + read_handle_cont(block_id, std::move(promise)); + } +} + +void ArchiveManager::read_handle_cont(BlockIdExt block_id, td::Promise promise) { + auto f = get_file_by_seqno(block_id.shard_full(), block_id.seqno(), false); + if (!f) { + promise.set_error(td::Status::Error(ErrorCode::notready, "not in archive db")); + return; + } + td::actor::send_closure(f->file_actor_id(), &ArchiveFile::read_handle, block_id, std::move(promise)); +} + +UnixTime ArchiveManager::convert_ts(UnixTime ts, bool key_block) { + if (!key_block) { + return ts - (ts % (1 << 17)); + } else { + return ts - (ts % (1 << 22)); + } +} + +ArchiveManager::FileDescription *ArchiveManager::get_file(UnixTime ts, bool key_block, bool force) { + ts = convert_ts(ts, key_block); + auto &f = key_block ? key_files_ : files_; + auto it = f.find(ts); + if (it != f.end()) { + return &it->second; + } + if (!force) { + return nullptr; + } + + return add_file(ts, key_block); +} + +ArchiveManager::FileDescription *ArchiveManager::add_file(UnixTime ts, bool key_block) { + CHECK((key_block ? key_files_ : files_).count(ts) == 0); + index_->begin_transaction().ensure(); + { + std::vector t; + std::vector tk; + for (auto &e : files_) { + t.push_back(e.first); + } + for (auto &e : key_files_) { + tk.push_back(e.first); + } + (key_block ? tk : t).push_back(ts); + index_ + ->set(create_serialize_tl_object().as_slice(), + create_serialize_tl_object(std::move(t), std::move(tk)).as_slice()) + .ensure(); + } + { + index_ + ->set(create_serialize_tl_object(ts, key_block).as_slice(), + create_serialize_tl_object( + ts, key_block, std::vector>(), false) + .as_slice()) + .ensure(); + } + index_->commit_transaction().ensure(); + FileDescription desc{ts, key_block}; + auto w = td::actor::create_actor( + PSTRING() << "archivefile" << ts, + PSTRING() << db_root_ << "/packed/" << (key_block ? "key" : "") << ts << ".pack", ts); + desc.file = std::move(w); + + return &(key_block ? key_files_ : files_).emplace(ts, std::move(desc)).first->second; +} + +void ArchiveManager::load_package(UnixTime ts, bool key_block) { + auto key = create_serialize_tl_object(ts, key_block); + + std::string value; + auto v = index_->get(key.as_slice(), value); + v.ensure(); + CHECK(v.move_as_ok() == td::KeyValue::GetStatus::Ok); + + auto R = fetch_tl_object(value, true); + R.ensure(); + auto x = R.move_as_ok(); + + if (x->deleted_) { + return; + } + + FileDescription desc{ts, key_block}; + for (auto &e : x->firstblocks_) { + desc.first_blocks[ShardIdFull{e->workchain_, static_cast(e->shard_)}] = + FileDescription::Desc{static_cast(e->seqno_), static_cast(e->lt_)}; + } + desc.file = td::actor::create_actor( + PSTRING() << "archivefile" << ts, + PSTRING() << db_root_ << "/packed/" << (key_block ? "key" : "") << ts << ".pack", ts); + + (key_block ? key_files_ : files_).emplace(ts, std::move(desc)); +} + +void ArchiveManager::update_desc(FileDescription &desc, ShardIdFull shard, BlockSeqno seqno, LogicalTime lt) { + auto it = desc.first_blocks.find(shard); + if (it != desc.first_blocks.end() && it->second.seqno <= seqno) { + return; + } + desc.first_blocks[shard] = FileDescription::Desc{seqno, lt}; + std::vector> vec; + for (auto &e : desc.first_blocks) { + vec.push_back(create_tl_object(e.first.workchain, e.first.shard, + e.second.seqno, e.second.lt)); + } + index_->begin_transaction().ensure(); + index_ + ->set(create_serialize_tl_object(desc.unix_time, desc.key_block).as_slice(), + create_serialize_tl_object(desc.unix_time, desc.key_block, + std::move(vec), false) + .as_slice()) + .ensure(); + index_->commit_transaction().ensure(); +} + +ArchiveManager::FileDescription *ArchiveManager::get_file_by_seqno(ShardIdFull shard, BlockSeqno seqno, + bool key_block) { + auto &f = (key_block ? key_files_ : files_); + + for (auto it = f.rbegin(); it != f.rend(); it++) { + auto i = it->second.first_blocks.find(shard); + if (i != it->second.first_blocks.end() && i->second.seqno <= seqno) { + return &it->second; + } + } + return nullptr; +} + +void ArchiveManager::start_up() { + td::mkdir(db_root_).ensure(); + td::mkdir(db_root_ + "/packed").ensure(); + index_ = std::make_shared(td::RocksDb::open(db_root_ + "/packed/globalindex").move_as_ok()); + std::string value; + auto v = index_->get(create_serialize_tl_object().as_slice(), value); + v.ensure(); + if (v.move_as_ok() == td::KeyValue::GetStatus::Ok) { + auto R = fetch_tl_object(value, true); + R.ensure(); + auto x = R.move_as_ok(); + + for (auto &d : x->packages_) { + load_package(d, false); + } + for (auto &d : x->key_packages_) { + load_package(d, true); + } + } +} + +} // namespace validator + +} // namespace ton diff --git a/validator/db/archive-db.hpp b/validator/db/archive-db.hpp new file mode 100644 index 000000000..d842d9f47 --- /dev/null +++ b/validator/db/archive-db.hpp @@ -0,0 +1,97 @@ +#pragma once + +#include "td/actor/actor.h" +#include "td/utils/buffer.h" +#include "ton/ton-types.h" +#include "td/utils/port/FileFd.h" +#include "package.hpp" +#include "filedb.hpp" +#include "validator/interfaces/block-handle.h" + +#include +#include + +namespace ton { + +namespace validator { + +class PackageWriter : public td::actor::Actor { + public: + PackageWriter(std::shared_ptr package) : package_(std::move(package)) { + } + + void append(std::string filename, td::BufferSlice data, td::Promise> promise); + + private: + std::shared_ptr package_; +}; + +class ArchiveFile : public td::actor::Actor { + public: + ArchiveFile(std::string path, UnixTime ts) : path_(std::move(path)), ts_(ts) { + } + void start_up() override; + void write(FileDb::RefId ref_id, td::BufferSlice data, td::Promise promise); + void write_handle(BlockHandle handle, td::Promise promise); + void read(FileDb::RefId ref_id, td::Promise promise); + void read_handle(BlockIdExt block_id, td::Promise promise); + + private: + void completed_write(FileDb::RefId ref_id, td::uint64 offset, td::uint64 new_size, td::Promise promise); + + std::shared_ptr package_; + std::shared_ptr index_; + + td::actor::ActorOwn writer_; + + std::string path_; + UnixTime ts_; +}; + +class ArchiveManager : public td::actor::Actor { + public: + ArchiveManager(std::string db_root); + void write(UnixTime ts, bool key_block, FileDb::RefId ref_id, td::BufferSlice data, td::Promise promise); + void write_handle(BlockHandle handle, td::Promise promise); + void read(UnixTime ts, bool key_block, FileDb::RefId ref_id, td::Promise promise); + void read_handle(BlockIdExt block_id, td::Promise promise); + + void start_up() override; + + private: + void read_handle_cont(BlockIdExt block_id, td::Promise promise); + struct FileDescription { + struct Desc { + BlockSeqno seqno; + LogicalTime lt; + }; + FileDescription(UnixTime unix_time, bool key_block) : unix_time(unix_time), key_block(key_block) { + } + auto file_actor_id() const { + return file.get(); + } + UnixTime unix_time; + bool key_block; + + std::map first_blocks; + td::actor::ActorOwn file; + }; + + void load_package(UnixTime ts, bool key_block); + + UnixTime convert_ts(UnixTime ts, bool key_block); + FileDescription *get_file(UnixTime ts, bool key_block, bool force = true); + FileDescription *add_file(UnixTime ts, bool key_block); + void update_desc(FileDescription &desc, ShardIdFull shard, BlockSeqno seqno, LogicalTime lt); + FileDescription *get_file_by_seqno(ShardIdFull shard, BlockSeqno seqno, bool key_block); + + std::string db_root_; + std::map files_; + std::map key_files_; + + std::shared_ptr index_; +}; + +} // namespace validator + +} // namespace ton diff --git a/validator/db/archiver.cpp b/validator/db/archiver.cpp index 284eea79a..f0f1692c5 100644 --- a/validator/db/archiver.cpp +++ b/validator/db/archiver.cpp @@ -26,8 +26,13 @@ namespace validator { BlockArchiver::BlockArchiver(BlockIdExt block_id, td::actor::ActorId root_db, td::actor::ActorId file_db, td::actor::ActorId archive_db, - td::Promise promise) - : block_id_(block_id), root_db_(root_db), file_db_(file_db), archive_db_(archive_db), promise_(std::move(promise)) { + td::actor::ActorId archive, td::Promise promise) + : block_id_(block_id) + , root_db_(root_db) + , file_db_(file_db) + , archive_db_(archive_db) + , archive_(archive) + , promise_(std::move(promise)) { } void BlockArchiver::start_up() { @@ -40,7 +45,7 @@ void BlockArchiver::start_up() { void BlockArchiver::got_block_handle(BlockHandle handle) { handle_ = std::move(handle); - if (handle_->moved_to_storage()) { + if (handle_->moved_to_archive()) { finish_query(); return; } @@ -63,16 +68,21 @@ void BlockArchiver::got_block_handle(BlockHandle handle) { R.ensure(); td::actor::send_closure(SelfId, &BlockArchiver::got_proof, R.move_as_ok()); }); - td::actor::send_closure(file_db_, &FileDb::load_file, FileDb::RefId{fileref::Proof{block_id_}}, std::move(P)); + + if (handle_->moved_to_storage()) { + td::actor::send_closure(archive_db_, &FileDb::load_file, FileDb::RefId{fileref::Proof{block_id_}}, std::move(P)); + } else { + td::actor::send_closure(file_db_, &FileDb::load_file, FileDb::RefId{fileref::Proof{block_id_}}, std::move(P)); + } } void BlockArchiver::got_proof(td::BufferSlice data) { - auto P = td::PromiseCreator::lambda([SelfId = actor_id(this)](td::Result R) { + auto P = td::PromiseCreator::lambda([SelfId = actor_id(this)](td::Result R) { R.ensure(); td::actor::send_closure(SelfId, &BlockArchiver::written_proof); }); - td::actor::send_closure(archive_db_, &FileDb::store_file, FileDb::RefId{fileref::Proof{block_id_}}, std::move(data), - std::move(P)); + td::actor::send_closure(archive_, &ArchiveManager::write, handle_->unix_time(), handle_->is_key_block(), + FileDb::RefId{fileref::Proof{block_id_}}, std::move(data), std::move(P)); } void BlockArchiver::written_proof() { @@ -85,16 +95,21 @@ void BlockArchiver::written_proof() { R.ensure(); td::actor::send_closure(SelfId, &BlockArchiver::got_proof_link, R.move_as_ok()); }); - td::actor::send_closure(file_db_, &FileDb::load_file, FileDb::RefId{fileref::ProofLink{block_id_}}, std::move(P)); + if (handle_->moved_to_storage()) { + td::actor::send_closure(archive_db_, &FileDb::load_file, FileDb::RefId{fileref::ProofLink{block_id_}}, + std::move(P)); + } else { + td::actor::send_closure(file_db_, &FileDb::load_file, FileDb::RefId{fileref::ProofLink{block_id_}}, std::move(P)); + } } void BlockArchiver::got_proof_link(td::BufferSlice data) { - auto P = td::PromiseCreator::lambda([SelfId = actor_id(this)](td::Result R) { + auto P = td::PromiseCreator::lambda([SelfId = actor_id(this)](td::Result R) { R.ensure(); td::actor::send_closure(SelfId, &BlockArchiver::written_proof_link); }); - td::actor::send_closure(archive_db_, &FileDb::store_file, FileDb::RefId{fileref::ProofLink{block_id_}}, - std::move(data), std::move(P)); + td::actor::send_closure(archive_, &ArchiveManager::write, handle_->unix_time(), handle_->is_key_block(), + FileDb::RefId{fileref::ProofLink{block_id_}}, std::move(data), std::move(P)); } void BlockArchiver::written_proof_link() { @@ -106,20 +121,24 @@ void BlockArchiver::written_proof_link() { R.ensure(); td::actor::send_closure(SelfId, &BlockArchiver::got_block_data, R.move_as_ok()); }); - td::actor::send_closure(file_db_, &FileDb::load_file, FileDb::RefId{fileref::Block{block_id_}}, std::move(P)); + if (handle_->moved_to_storage()) { + td::actor::send_closure(archive_db_, &FileDb::load_file, FileDb::RefId{fileref::Block{block_id_}}, std::move(P)); + } else { + td::actor::send_closure(file_db_, &FileDb::load_file, FileDb::RefId{fileref::Block{block_id_}}, std::move(P)); + } } void BlockArchiver::got_block_data(td::BufferSlice data) { - auto P = td::PromiseCreator::lambda([SelfId = actor_id(this)](td::Result R) { + auto P = td::PromiseCreator::lambda([SelfId = actor_id(this)](td::Result R) { R.ensure(); td::actor::send_closure(SelfId, &BlockArchiver::written_block_data); }); - td::actor::send_closure(archive_db_, &FileDb::store_file, FileDb::RefId{fileref::Block{block_id_}}, std::move(data), - std::move(P)); + td::actor::send_closure(archive_, &ArchiveManager::write, handle_->unix_time(), handle_->is_key_block(), + FileDb::RefId{fileref::Block{block_id_}}, std::move(data), std::move(P)); } void BlockArchiver::written_block_data() { - handle_->set_moved_to_storage(); + handle_->set_moved_to_archive(); auto P = td::PromiseCreator::lambda([SelfId = actor_id(this)](td::Result R) { R.ensure(); diff --git a/validator/db/archiver.hpp b/validator/db/archiver.hpp index 01ff87527..3335d3aa0 100644 --- a/validator/db/archiver.hpp +++ b/validator/db/archiver.hpp @@ -22,6 +22,7 @@ #include "td/actor/actor.h" #include "validator/interfaces/block-handle.h" #include "ton/ton-io.hpp" +#include "archive-db.hpp" namespace ton { @@ -33,7 +34,8 @@ class FileDb; class BlockArchiver : public td::actor::Actor { public: BlockArchiver(BlockIdExt block_id, td::actor::ActorId root_db, td::actor::ActorId file_db, - td::actor::ActorId archive_db, td::Promise promise); + td::actor::ActorId archive_db, td::actor::ActorId archive, + td::Promise promise); void abort_query(td::Status error); @@ -52,6 +54,7 @@ class BlockArchiver : public td::actor::Actor { td::actor::ActorId root_db_; td::actor::ActorId file_db_; td::actor::ActorId archive_db_; + td::actor::ActorId archive_; td::Promise promise_; BlockHandle handle_; diff --git a/validator/db/filedb.cpp b/validator/db/filedb.cpp index 4532641ea..371c08fc3 100644 --- a/validator/db/filedb.cpp +++ b/validator/db/filedb.cpp @@ -331,6 +331,9 @@ FileDb::RefId FileDb::get_ref_from_tl(const ton_api::db_filedb_Key& from) { [&](const ton_api::db_filedb_key_candidate& key) { ref_id = fileref::Candidate{PublicKey{key.id_->source_}, create_block_id(key.id_->id_), key.id_->collated_data_file_hash_}; + }, + [&](const ton_api::db_filedb_key_blockInfo& key) { + ref_id = fileref::BlockInfo{create_block_id(key.block_id_)}; })); return ref_id; } diff --git a/validator/db/filedb.hpp b/validator/db/filedb.hpp index 5bb1ea8f0..3adc43478 100644 --- a/validator/db/filedb.hpp +++ b/validator/db/filedb.hpp @@ -130,6 +130,18 @@ class Candidate { BlockIdExt block_id; FileHash collated_data_file_hash; }; + +class BlockInfo { + public: + tl_object_ptr tl() const { + return create_tl_object(create_tl_block_id(block_id)); + } + FileHash hash() const { + return create_hash_tl_object(create_tl_block_id(block_id)); + } + + BlockIdExt block_id; +}; }; // namespace fileref class RootDb; @@ -138,7 +150,7 @@ class FileDb : public td::actor::Actor { public: using RefId = td::Variant; + fileref::Proof, fileref::ProofLink, fileref::Signatures, fileref::Candidate, fileref::BlockInfo>; using RefIdHash = td::Bits256; void store_file(RefId ref_id, td::BufferSlice data, td::Promise promise); diff --git a/validator/db/files-async.hpp b/validator/db/files-async.hpp index 4f4207762..f2009fcd5 100644 --- a/validator/db/files-async.hpp +++ b/validator/db/files-async.hpp @@ -52,7 +52,7 @@ class WriteFile : public td::actor::Actor { auto res = R.move_as_ok(); auto file = std::move(res.first); auto old_name = res.second; - size_t offset = 0; + td::uint64 offset = 0; while (data_.size() > 0) { auto R = file.pwrite(data_.as_slice(), offset); auto s = R.move_as_ok(); diff --git a/validator/db/package.cpp b/validator/db/package.cpp new file mode 100644 index 000000000..76f504a36 --- /dev/null +++ b/validator/db/package.cpp @@ -0,0 +1,143 @@ +#include "package.hpp" +#include "common/errorcode.h" + +namespace ton { + +namespace { + +constexpr td::uint32 header_size() { + return 4; +} + +constexpr td::uint32 max_data_size() { + return (1u << 31) - 1; +} + +constexpr td::uint32 max_filename_size() { + return (1u << 16) - 1; +} + +constexpr td::uint16 entry_header_magic() { + return 0x1e8b; +} + +constexpr td::uint32 package_header_magic() { + return 0xae8fdd01; +} +} // namespace + +Package::Package(td::FileFd fd) : fd_(std::move(fd)) { +} + +td::Status Package::truncate(td::uint64 size) { + TRY_STATUS(fd_.seek(size + header_size())); + return fd_.truncate_to_current_position(size + header_size()); +} + +td::uint64 Package::append(std::string filename, td::Slice data) { + CHECK(data.size() <= max_data_size()); + CHECK(filename.size() <= max_filename_size()); + auto size = fd_.get_size().move_as_ok(); + auto orig_size = size; + td::uint32 header[2]; + header[0] = entry_header_magic() + (td::narrow_cast(filename.size()) << 16); + header[1] = td::narrow_cast(data.size()); + CHECK(fd_.pwrite(td::Slice(reinterpret_cast(header), 8), size).move_as_ok() == 8); + size += 8; + CHECK(fd_.pwrite(filename, size).move_as_ok() == filename.size()); + size += filename.size(); + CHECK(fd_.pwrite(data, size).move_as_ok() == data.size()); + size += data.size(); + fd_.sync().ensure(); + return orig_size - header_size(); +} + +td::uint64 Package::size() const { + return fd_.get_size().move_as_ok() - header_size(); +} + +td::Result> Package::read(td::uint64 offset) const { + offset += header_size(); + + td::uint32 header[2]; + TRY_RESULT(s1, fd_.pread(td::MutableSlice(reinterpret_cast(header), 8), offset)); + if (s1 != 8) { + return td::Status::Error(ErrorCode::notready, "too short read"); + } + if ((header[0] & 0xffff) != entry_header_magic()) { + return td::Status::Error(ErrorCode::notready, "bad entry magic"); + } + offset += 8; + auto fname_size = header[0] >> 16; + auto data_size = header[1]; + + std::string fname(fname_size, '\0'); + TRY_RESULT(s2, fd_.pread(fname, offset)); + if (s2 != fname_size) { + return td::Status::Error(ErrorCode::notready, "too short read (filename)"); + } + offset += fname_size; + + td::BufferSlice data{data_size}; + TRY_RESULT(s3, fd_.pread(data.as_slice(), offset)); + if (s3 != data_size) { + return td::Status::Error(ErrorCode::notready, "too short read (data)"); + } + return std::pair{std::move(fname), std::move(data)}; +} + +td::Result Package::advance(td::uint64 offset) { + offset += header_size(); + + td::uint32 header[2]; + TRY_RESULT(s1, fd_.pread(td::MutableSlice(reinterpret_cast(header), 8), offset)); + if (s1 != 8) { + return td::Status::Error(ErrorCode::notready, "too short read"); + } + if ((header[0] & 0xffff) != entry_header_magic()) { + return td::Status::Error(ErrorCode::notready, "bad entry magic"); + } + + offset += 8 + (header[0] >> 16) + header[1]; + if (offset > static_cast(fd_.get_size().move_as_ok())) { + return td::Status::Error(ErrorCode::notready, "truncated read"); + } + return offset - header_size(); +} + +td::Result Package::open(std::string path, bool read_only, bool create) { + td::uint32 flags = td::FileFd::Flags::Read; + if (!read_only) { + flags |= td::FileFd::Write; + } + if (create) { + flags |= td::FileFd::Create; + } + + TRY_RESULT(fd, td::FileFd::open(path, flags)); + TRY_RESULT(size, fd.get_size()); + + if (size < header_size()) { + if (!create) { + return td::Status::Error(ErrorCode::notready, "db is too short"); + } + td::uint32 header[1]; + header[0] = package_header_magic(); + TRY_RESULT(s, fd.pwrite(td::Slice(reinterpret_cast(header), header_size()), size)); + if (s != header_size()) { + return td::Status::Error(ErrorCode::notready, "db write is short"); + } + } else { + td::uint32 header[1]; + TRY_RESULT(s, fd.pread(td::MutableSlice(reinterpret_cast(header), header_size()), 0)); + if (s != header_size()) { + return td::Status::Error(ErrorCode::notready, "db read failed"); + } + if (header[0] != package_header_magic()) { + return td::Status::Error(ErrorCode::notready, "magic mismatch"); + } + } + return Package{std::move(fd)}; +} + +} // namespace ton diff --git a/validator/db/package.hpp b/validator/db/package.hpp new file mode 100644 index 000000000..50d6ad0a3 --- /dev/null +++ b/validator/db/package.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include "td/actor/actor.h" +#include "td/utils/port/FileFd.h" +#include "td/utils/buffer.h" + +namespace ton { + +class Package { + public: + static td::Result open(std::string path, bool read_only = false, bool create = false); + + Package(td::FileFd fd); + + td::Status truncate(td::uint64 size); + + td::uint64 append(std::string filename, td::Slice data); + td::uint64 size() const; + td::Result> read(td::uint64 offset) const; + td::Result advance(td::uint64 offset); + + struct Iterator { + td::uint64 offset; + Package &package; + + Iterator operator++(int); + const Iterator operator++(int) const; + td::Result> read() const; + }; + + Iterator begin(); + const Iterator begin() const; + Iterator end(); + const Iterator end() const; + + private: + td::FileFd fd_; +}; + +} // namespace ton diff --git a/validator/db/rootdb.cpp b/validator/db/rootdb.cpp index 2a21d0c27..a0e4d5217 100644 --- a/validator/db/rootdb.cpp +++ b/validator/db/rootdb.cpp @@ -32,7 +32,7 @@ namespace ton { namespace validator { void RootDb::store_block_data(BlockHandle handle, td::Ref block, td::Promise promise) { - if (handle->moved_to_storage()) { + if (handle->moved_to_storage() || handle->moved_to_archive()) { promise.set_value(td::Unit()); return; } @@ -64,14 +64,19 @@ void RootDb::get_block_data(BlockHandle handle, td::Promise> } }); - td::actor::send_closure(handle->moved_to_storage() ? archive_db_.get() : file_db_.get(), &FileDb::load_file, - FileDb::RefId{fileref::Block{handle->id()}}, std::move(P)); + if (handle->moved_to_archive()) { + td::actor::send_closure(new_archive_db_, &ArchiveManager::read, handle->unix_time(), handle->is_key_block(), + FileDb::RefId{fileref::Block{handle->id()}}, std::move(P)); + } else { + td::actor::send_closure(handle->moved_to_storage() ? old_archive_db_.get() : file_db_.get(), &FileDb::load_file, + FileDb::RefId{fileref::Block{handle->id()}}, std::move(P)); + } } } void RootDb::store_block_signatures(BlockHandle handle, td::Ref data, td::Promise promise) { - if (handle->moved_to_storage()) { + if (handle->moved_to_storage() || handle->moved_to_archive()) { promise.set_value(td::Unit()); return; } @@ -94,7 +99,7 @@ void RootDb::get_block_signatures(BlockHandle handle, td::Promiseinited_signatures()) { promise.set_error(td::Status::Error(ErrorCode::notready, "not in db")); } else { - if (handle->moved_to_storage()) { + if (handle->moved_to_storage() || handle->moved_to_archive()) { promise.set_error(td::Status::Error(ErrorCode::error, "signatures already gc'd")); return; } @@ -111,7 +116,7 @@ void RootDb::get_block_signatures(BlockHandle handle, td::Promise proof, td::Promise promise) { - if (handle->moved_to_storage()) { + if (handle->moved_to_storage() || handle->moved_to_archive()) { promise.set_value(td::Unit()); return; } @@ -142,13 +147,18 @@ void RootDb::get_block_proof(BlockHandle handle, td::Promise> pro promise.set_result(create_proof(id, R.move_as_ok())); } }); - td::actor::send_closure(handle->moved_to_storage() ? archive_db_.get() : file_db_.get(), &FileDb::load_file, - FileDb::RefId{fileref::Proof{handle->id()}}, std::move(P)); + if (handle->moved_to_archive()) { + td::actor::send_closure(new_archive_db_, &ArchiveManager::read, handle->unix_time(), handle->is_key_block(), + FileDb::RefId{fileref::Proof{handle->id()}}, std::move(P)); + } else { + td::actor::send_closure(handle->moved_to_storage() ? old_archive_db_.get() : file_db_.get(), &FileDb::load_file, + FileDb::RefId{fileref::Proof{handle->id()}}, std::move(P)); + } } } void RootDb::store_block_proof_link(BlockHandle handle, td::Ref proof, td::Promise promise) { - if (handle->moved_to_storage()) { + if (handle->moved_to_storage() || handle->moved_to_archive()) { promise.set_value(td::Unit()); return; } @@ -179,8 +189,13 @@ void RootDb::get_block_proof_link(BlockHandle handle, td::Promisemoved_to_storage() ? archive_db_.get() : file_db_.get(), &FileDb::load_file, - FileDb::RefId{fileref::ProofLink{handle->id()}}, std::move(P)); + if (handle->moved_to_archive()) { + td::actor::send_closure(new_archive_db_, &ArchiveManager::read, handle->unix_time(), handle->is_key_block(), + FileDb::RefId{fileref::ProofLink{handle->id()}}, std::move(P)); + } else { + td::actor::send_closure(handle->moved_to_storage() ? old_archive_db_.get() : file_db_.get(), &FileDb::load_file, + FileDb::RefId{fileref::ProofLink{handle->id()}}, std::move(P)); + } } } @@ -225,7 +240,7 @@ void RootDb::get_block_candidate(PublicKey source, BlockIdExt id, FileHash colla void RootDb::store_block_state(BlockHandle handle, td::Ref state, td::Promise> promise) { - if (handle->moved_to_storage()) { + if (handle->moved_to_storage() || handle->moved_to_archive()) { promise.set_value(std::move(state)); return; } @@ -290,27 +305,27 @@ void RootDb::store_persistent_state_file(BlockIdExt block_id, BlockIdExt masterc } }); - td::actor::send_closure(archive_db_, &FileDb::store_file, + td::actor::send_closure(old_archive_db_, &FileDb::store_file, FileDb::RefId{fileref::PersistentState{block_id, masterchain_block_id}}, std::move(state), std::move(P)); } void RootDb::get_persistent_state_file(BlockIdExt block_id, BlockIdExt masterchain_block_id, td::Promise promise) { - td::actor::send_closure(archive_db_, &FileDb::load_file, + td::actor::send_closure(old_archive_db_, &FileDb::load_file, FileDb::RefId{fileref::PersistentState{block_id, masterchain_block_id}}, std::move(promise)); } void RootDb::get_persistent_state_file_slice(BlockIdExt block_id, BlockIdExt masterchain_block_id, td::int64 offset, td::int64 max_size, td::Promise promise) { - td::actor::send_closure(archive_db_, &FileDb::load_file_slice, + td::actor::send_closure(old_archive_db_, &FileDb::load_file_slice, FileDb::RefId{fileref::PersistentState{block_id, masterchain_block_id}}, offset, max_size, std::move(promise)); } void RootDb::check_persistent_state_file_exists(BlockIdExt block_id, BlockIdExt masterchain_block_id, td::Promise promise) { - td::actor::send_closure(archive_db_, &FileDb::check_file, + td::actor::send_closure(old_archive_db_, &FileDb::check_file, FileDb::RefId{fileref::PersistentState{block_id, masterchain_block_id}}, std::move(promise)); } @@ -325,26 +340,38 @@ void RootDb::store_zero_state_file(BlockIdExt block_id, td::BufferSlice state, t } }); - td::actor::send_closure(archive_db_, &FileDb::store_file, FileDb::RefId{fileref::ZeroState{block_id}}, + td::actor::send_closure(old_archive_db_, &FileDb::store_file, FileDb::RefId{fileref::ZeroState{block_id}}, std::move(state), std::move(P)); } void RootDb::get_zero_state_file(BlockIdExt block_id, td::Promise promise) { - td::actor::send_closure(archive_db_, &FileDb::load_file, FileDb::RefId{fileref::ZeroState{block_id}}, + td::actor::send_closure(old_archive_db_, &FileDb::load_file, FileDb::RefId{fileref::ZeroState{block_id}}, std::move(promise)); } void RootDb::check_zero_state_file_exists(BlockIdExt block_id, td::Promise promise) { - td::actor::send_closure(archive_db_, &FileDb::check_file, FileDb::RefId{fileref::ZeroState{block_id}}, + td::actor::send_closure(old_archive_db_, &FileDb::check_file, FileDb::RefId{fileref::ZeroState{block_id}}, std::move(promise)); } void RootDb::store_block_handle(BlockHandle handle, td::Promise promise) { - td::actor::send_closure(block_db_, &BlockDb::store_block_handle, std::move(handle), std::move(promise)); + if (handle->moved_to_archive()) { + td::actor::send_closure(new_archive_db_, &ArchiveManager::write_handle, std::move(handle), std::move(promise)); + } else { + td::actor::send_closure(block_db_, &BlockDb::store_block_handle, std::move(handle), std::move(promise)); + } } void RootDb::get_block_handle(BlockIdExt id, td::Promise promise) { - td::actor::send_closure(block_db_, &BlockDb::get_block_handle, id, std::move(promise)); + auto P = td::PromiseCreator::lambda( + [db = block_db_.get(), id, promise = std::move(promise)](td::Result R) mutable { + if (R.is_error()) { + td::actor::send_closure(db, &BlockDb::get_block_handle, id, std::move(promise)); + } else { + promise.set_value(R.move_as_ok()); + } + }); + td::actor::send_closure(new_archive_db_, &ArchiveManager::read_handle, id, std::move(P)); } void RootDb::try_get_static_file(FileHash file_hash, td::Promise promise) { @@ -426,16 +453,17 @@ void RootDb::start_up() { cell_db_ = td::actor::create_actor("celldb", actor_id(this), root_path_ + "/celldb/"); block_db_ = td::actor::create_actor("blockdb", actor_id(this), root_path_ + "/blockdb/"); file_db_ = td::actor::create_actor("filedb", actor_id(this), root_path_ + "/files/", depth_, false); - archive_db_ = + old_archive_db_ = td::actor::create_actor("filedbarchive", actor_id(this), root_path_ + "/archive/", depth_, true); lt_db_ = td::actor::create_actor("ltdb", actor_id(this), root_path_ + "/ltdb/"); state_db_ = td::actor::create_actor("statedb", actor_id(this), root_path_ + "/state/"); static_files_db_ = td::actor::create_actor("staticfilesdb", actor_id(this), root_path_ + "/static/"); + new_archive_db_ = td::actor::create_actor("archivemanager", root_path_ + "/archive/"); } void RootDb::archive(BlockIdExt block_id, td::Promise promise) { - td::actor::create_actor("archiveblock", block_id, actor_id(this), file_db_.get(), archive_db_.get(), - std::move(promise)) + td::actor::create_actor("archiveblock", block_id, actor_id(this), file_db_.get(), + old_archive_db_.get(), new_archive_db_.get(), std::move(promise)) .release(); } @@ -480,14 +508,15 @@ void RootDb::allow_gc(FileDb::RefId ref_id, bool is_archive, td::Promise p CHECK(!is_archive); td::actor::send_closure(validator_manager_, &ValidatorManager::allow_block_candidate_gc, key.block_id, std::move(promise)); - })); + }, + [&](const fileref::BlockInfo &key) { UNREACHABLE(); })); } void RootDb::prepare_stats(td::Promise>> promise) { auto merger = StatsMerger::create(std::move(promise)); td::actor::send_closure(file_db_, &FileDb::prepare_stats, merger.make_promise("filedb.")); - td::actor::send_closure(archive_db_, &FileDb::prepare_stats, merger.make_promise("archivedb.")); + td::actor::send_closure(old_archive_db_, &FileDb::prepare_stats, merger.make_promise("archivedb.")); } void RootDb::truncate(td::Ref state, td::Promise promise) { diff --git a/validator/db/rootdb.hpp b/validator/db/rootdb.hpp index a584ac974..47cd31be1 100644 --- a/validator/db/rootdb.hpp +++ b/validator/db/rootdb.hpp @@ -28,6 +28,7 @@ #include "ltdb.hpp" #include "statedb.hpp" #include "staticfilesdb.hpp" +#include "archive-db.hpp" namespace ton { @@ -126,10 +127,11 @@ class RootDb : public Db { td::actor::ActorOwn cell_db_; td::actor::ActorOwn block_db_; td::actor::ActorOwn file_db_; - td::actor::ActorOwn archive_db_; + td::actor::ActorOwn old_archive_db_; td::actor::ActorOwn lt_db_; td::actor::ActorOwn state_db_; td::actor::ActorOwn static_files_db_; + td::actor::ActorOwn new_archive_db_; }; } // namespace validator diff --git a/validator/interfaces/block-handle.h b/validator/interfaces/block-handle.h index 1f6ad877f..b30269cf7 100644 --- a/validator/interfaces/block-handle.h +++ b/validator/interfaces/block-handle.h @@ -33,6 +33,7 @@ struct BlockHandleInterface { virtual BlockIdExt id() const = 0; virtual bool received() const = 0; virtual bool moved_to_storage() const = 0; + virtual bool moved_to_archive() const = 0; virtual bool deleted() const = 0; virtual bool inited_next_left() const = 0; virtual bool inited_next_right() const = 0; @@ -83,6 +84,7 @@ struct BlockHandleInterface { virtual void set_prev(BlockIdExt prev) = 0; virtual void set_received() = 0; virtual void set_moved_to_storage() = 0; + virtual void set_moved_to_archive() = 0; virtual void set_deleted() = 0; virtual void set_split(bool value) = 0; virtual void set_merge(bool value) = 0; diff --git a/validator/manager.cpp b/validator/manager.cpp index eba3f2436..fc1edd988 100644 --- a/validator/manager.cpp +++ b/validator/manager.cpp @@ -1842,6 +1842,27 @@ void ValidatorManagerImpl::allow_block_state_gc(BlockIdExt block_id, td::Promise UNREACHABLE(); } +void ValidatorManagerImpl::allow_block_info_gc(BlockIdExt block_id, td::Promise promise) { + auto P = + td::PromiseCreator::lambda([db = db_.get(), promise = std::move(promise)](td::Result R) mutable { + if (R.is_error()) { + promise.set_result(false); + } else { + auto handle = R.move_as_ok(); + if (!handle->moved_to_archive()) { + promise.set_result(false); + } else { + auto P = td::PromiseCreator::lambda([promise = std::move(promise)](td::Result R) mutable { + R.ensure(); + promise.set_result(true); + }); + td::actor::send_closure(db, &Db::store_block_handle, handle, std::move(P)); + } + } + }); + get_block_handle(block_id, false, std::move(P)); +} + void ValidatorManagerImpl::got_next_gc_masterchain_handle(BlockHandle handle) { CHECK(gc_advancing_); auto P = td::PromiseCreator::lambda([SelfId = actor_id(this), handle](td::Result> R) { diff --git a/validator/manager.hpp b/validator/manager.hpp index 6ce036ca7..669f1c669 100644 --- a/validator/manager.hpp +++ b/validator/manager.hpp @@ -463,11 +463,7 @@ class ValidatorManagerImpl : public ValidatorManager { void allow_delete(BlockIdExt block_id, td::Promise promise); void allow_archive(BlockIdExt block_id, td::Promise promise); void allow_block_data_gc(BlockIdExt block_id, bool is_archive, td::Promise promise) override { - if (!is_archive) { - allow_archive(block_id, std::move(promise)); - } else { - allow_delete(block_id, std::move(promise)); - } + allow_archive(block_id, std::move(promise)); } void allow_block_state_gc(BlockIdExt block_id, td::Promise promise) override; void allow_zero_state_file_gc(BlockIdExt block_id, td::Promise promise) override { @@ -479,25 +475,15 @@ class ValidatorManagerImpl : public ValidatorManager { allow_archive(block_id, std::move(promise)); } void allow_block_proof_gc(BlockIdExt block_id, bool is_archive, td::Promise promise) override { - if (!is_archive) { - allow_archive(block_id, std::move(promise)); - } else { - allow_delete(block_id, std::move(promise)); - } + allow_archive(block_id, std::move(promise)); } void allow_block_proof_link_gc(BlockIdExt block_id, bool is_archive, td::Promise promise) override { - if (!is_archive) { - allow_archive(block_id, std::move(promise)); - } else { - allow_delete(block_id, std::move(promise)); - } + allow_archive(block_id, std::move(promise)); } void allow_block_candidate_gc(BlockIdExt block_id, td::Promise promise) override { allow_block_state_gc(block_id, std::move(promise)); } - void allow_block_info_gc(BlockIdExt block_id, td::Promise promise) override { - allow_delete(block_id, std::move(promise)); - } + void allow_block_info_gc(BlockIdExt block_id, td::Promise promise) override; void send_peek_key_block_request(); void got_next_key_blocks(std::vector vec); diff --git a/validator/net/download-state.cpp b/validator/net/download-state.cpp index 6fc8c366f..abda77a94 100644 --- a/validator/net/download-state.cpp +++ b/validator/net/download-state.cpp @@ -188,7 +188,7 @@ void DownloadState::got_block_state_part(td::BufferSlice data, td::uint32 reques parts_.push_back(std::move(data)); if (last_part) { - td::BufferSlice res{sum_}; + td::BufferSlice res{td::narrow_cast(sum_)}; auto S = res.as_slice(); for (auto &p : parts_) { S.copy_from(p.as_slice());