diff --git a/CMakeLists.txt b/CMakeLists.txt index 4d59081b..cbb858e1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -323,6 +323,7 @@ if (LATEX_FOUND) add_latex_document(doc/tvm.tex TARGET_NAME ton_vm_description) add_latex_document(doc/tblkch.tex TARGET_NAME ton_blockchain_description) add_latex_document(doc/fiftbase.tex TARGET_NAME fift_basic_description) + add_latex_document(doc/catchain.tex TARGET_NAME catchain_consensus_description) endif() #END internal @@ -374,6 +375,8 @@ add_subdirectory(validator-engine) add_subdirectory(validator-engine-console) add_subdirectory(dht-server) add_subdirectory(utils) +add_subdirectory(http) +add_subdirectory(rldp-http-proxy) endif() #END internal @@ -473,6 +476,9 @@ target_link_libraries(test-ton-collator overlay tdutils tdactor adnl tl_api dht #add_executable(test-ext-client test/test-ext-client.cpp) #target_link_libraries(test-ext-client tdutils tdactor adnl tl_api tl-lite-utils) +add_executable(test-http test/test-http.cpp) +target_link_libraries(test-http PRIVATE tonhttp) + get_directory_property(HAS_PARENT PARENT_DIRECTORY) if (HAS_PARENT) set(ALL_TEST_SOURCE diff --git a/GPLv2 b/GPLv2 index c1e0c158..69910f2e 100644 --- a/GPLv2 +++ b/GPLv2 @@ -1,4 +1,4 @@ -/* +/* This file is part of TON Blockchain source code. TON Blockchain is free software; you can redistribute it and/or @@ -14,13 +14,13 @@ You should have received a copy of the GNU General Public License along with TON Blockchain. If not, see . - In addition, as a special exception, the copyright holders give permission - to link the code of portions of this program with the OpenSSL library. - You must obey the GNU General Public License in all respects for all - of the code used other than OpenSSL. If you modify file(s) with this - exception, you may extend this exception to your version of the file(s), - but you are not obligated to do so. If you do not wish to do so, delete this - exception statement from your version. If you delete this exception statement + In addition, as a special exception, the copyright holders give permission + to link the code of portions of this program with the OpenSSL library. + You must obey the GNU General Public License in all respects for all + of the code used other than OpenSSL. If you modify file(s) with this + exception, you may extend this exception to your version of the file(s), + but you are not obligated to do so. If you do not wish to do so, delete this + exception statement from your version. If you delete this exception statement from all source files in the program, then also delete it here. Copyright 2017-2020 Telegram Systems LLP diff --git a/adnl/adnl-address-list.cpp b/adnl/adnl-address-list.cpp index 8b92d925..fc3d81eb 100644 --- a/adnl/adnl-address-list.cpp +++ b/adnl/adnl-address-list.cpp @@ -14,7 +14,7 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #include "adnl-address-list.hpp" #include "adnl-peer-table.h" diff --git a/adnl/adnl-address-list.h b/adnl/adnl-address-list.h index 686ced65..d9dada69 100644 --- a/adnl/adnl-address-list.h +++ b/adnl/adnl-address-list.h @@ -14,7 +14,7 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/adnl/adnl-channel.cpp b/adnl/adnl-channel.cpp index f4e09cb0..5c8229ca 100644 --- a/adnl/adnl-channel.cpp +++ b/adnl/adnl-channel.cpp @@ -14,7 +14,7 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #include "adnl-channel.hpp" #include "adnl-peer.h" diff --git a/adnl/adnl-channel.h b/adnl/adnl-channel.h index a8e2c448..94b5877d 100644 --- a/adnl/adnl-channel.h +++ b/adnl/adnl-channel.h @@ -14,7 +14,7 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/adnl/adnl-channel.hpp b/adnl/adnl-channel.hpp index d4356f93..0c30fbd5 100644 --- a/adnl/adnl-channel.hpp +++ b/adnl/adnl-channel.hpp @@ -14,7 +14,7 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/adnl/adnl-local-id.cpp b/adnl/adnl-local-id.cpp index cd9d384e..714464be 100644 --- a/adnl/adnl-local-id.cpp +++ b/adnl/adnl-local-id.cpp @@ -14,7 +14,7 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #include "td/utils/crypto.h" #include "td/utils/Random.h" diff --git a/adnl/adnl-local-id.h b/adnl/adnl-local-id.h index 03998f7a..678adabd 100644 --- a/adnl/adnl-local-id.h +++ b/adnl/adnl-local-id.h @@ -14,7 +14,7 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/adnl/adnl-network-manager.cpp b/adnl/adnl-network-manager.cpp index 02e73ce0..d6882d85 100644 --- a/adnl/adnl-network-manager.cpp +++ b/adnl/adnl-network-manager.cpp @@ -14,7 +14,7 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #include "adnl-network-manager.hpp" #include "adnl-peer-table.h" diff --git a/adnl/adnl-packet.h b/adnl/adnl-packet.h index 107ed7e5..363a74c2 100644 --- a/adnl/adnl-packet.h +++ b/adnl/adnl-packet.h @@ -14,7 +14,7 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/adnl/adnl-peer-table.cpp b/adnl/adnl-peer-table.cpp index b4880de2..555ff47b 100644 --- a/adnl/adnl-peer-table.cpp +++ b/adnl/adnl-peer-table.cpp @@ -14,7 +14,7 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #include "adnl-peer-table.hpp" #include "adnl-peer.h" diff --git a/adnl/adnl-peer-table.h b/adnl/adnl-peer-table.h index 42447a44..536ed60e 100644 --- a/adnl/adnl-peer-table.h +++ b/adnl/adnl-peer-table.h @@ -14,7 +14,7 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/adnl/adnl-peer-table.hpp b/adnl/adnl-peer-table.hpp index c5c6acd8..1ef4f971 100644 --- a/adnl/adnl-peer-table.hpp +++ b/adnl/adnl-peer-table.hpp @@ -14,7 +14,7 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/adnl/adnl-peer.cpp b/adnl/adnl-peer.cpp index 4ff8044a..5e3e9700 100644 --- a/adnl/adnl-peer.cpp +++ b/adnl/adnl-peer.cpp @@ -14,7 +14,7 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #include "adnl-peer.h" #include "adnl-peer.hpp" diff --git a/blockchain-explorer/blockchain-explorer-http.hpp b/blockchain-explorer/blockchain-explorer-http.hpp index 130a3a99..e21f1b7e 100644 --- a/blockchain-explorer/blockchain-explorer-http.hpp +++ b/blockchain-explorer/blockchain-explorer-http.hpp @@ -23,7 +23,7 @@ exception statement from your version. If you delete this exception statement from all source files in the program, then also delete it here. - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/blockchain-explorer/blockchain-explorer-query.cpp b/blockchain-explorer/blockchain-explorer-query.cpp index db731f14..4c8ed7b1 100644 --- a/blockchain-explorer/blockchain-explorer-query.cpp +++ b/blockchain-explorer/blockchain-explorer-query.cpp @@ -24,7 +24,7 @@ from all source files in the program, then also delete it here. along with TON Blockchain. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "blockchain-explorer-query.hpp" #include "blockchain-explorer-http.hpp" @@ -47,7 +47,7 @@ #include "vm/boc.h" #include "vm/cellops.h" #include "vm/cells/MerkleProof.h" -#include "vm/continuation.h" +#include "vm/vm.h" #include "vm/cp0.h" namespace { @@ -198,8 +198,9 @@ void HttpQueryCommon::abort_query(td::Status error) { HttpAnswer A{"error", prefix_}; A.abort(std::move(error)); auto page = A.finish(); - promise_.set_value( - MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY)); + auto R = MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY); + MHD_add_response_header(R, "Content-Type", "text/html"); + promise_.set_value(std::move(R)); } stop(); } @@ -291,8 +292,9 @@ void HttpQueryBlockView::finish_query() { A << HttpAnswer::RawData{root}; return A.finish(); }(); - promise_.set_value( - MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY)); + auto R = MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY); + MHD_add_response_header(R, "Content-Type", "text/html"); + promise_.set_value(std::move(R)); } stop(); } @@ -488,8 +490,9 @@ void HttpQueryBlockInfo::finish_query() { return A.finish(); }(); - promise_.set_value( - MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY)); + auto R = MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY); + MHD_add_response_header(R, "Content-Type", "text/html"); + promise_.set_value(std::move(R)); } stop(); } @@ -709,8 +712,9 @@ void HttpQueryBlockSearch::finish_query() { return A.finish(); }(); - promise_.set_value( - MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY)); + auto R = MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY); + MHD_add_response_header(R, "Content-Type", "text/html"); + promise_.set_value(std::move(R)); } stop(); } @@ -795,8 +799,9 @@ void HttpQueryViewAccount::finish_query() { A << HttpAnswer::AccountCell{addr_, res_block_id_, root, Q_roots}; return A.finish(); }(); - promise_.set_value( - MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY)); + auto R = MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY); + MHD_add_response_header(R, "Content-Type", "text/html"); + promise_.set_value(std::move(R)); } stop(); } @@ -893,8 +898,9 @@ void HttpQueryViewTransaction::finish_query() { } return A.finish(); }(); - promise_.set_value( - MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY)); + auto R = MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY); + MHD_add_response_header(R, "Content-Type", "text/html"); + promise_.set_value(std::move(R)); } stop(); } @@ -974,8 +980,9 @@ void HttpQueryViewTransaction2::finish_query() { A << HttpAnswer::TransactionCell{addr_, block_id_, list}; return A.finish(); }(); - promise_.set_value( - MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY)); + auto R = MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY); + MHD_add_response_header(R, "Content-Type", "text/html"); + promise_.set_value(std::move(R)); } stop(); } @@ -1178,8 +1185,9 @@ void HttpQueryConfig::finish_query() { } return A.finish(); }(); - promise_.set_value( - MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY)); + auto R = MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY); + MHD_add_response_header(R, "Content-Type", "text/html"); + promise_.set_value(std::move(R)); } stop(); } @@ -1209,8 +1217,9 @@ void HttpQuerySendForm::finish_query() { << ""; return A.finish(); }(); - promise_.set_value( - MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY)); + auto R = MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY); + MHD_add_response_header(R, "Content-Type", "text/html"); + promise_.set_value(std::move(R)); } stop(); } @@ -1270,8 +1279,9 @@ void HttpQuerySend::finish_query() { } return A.finish(); }(); - promise_.set_value( - MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY)); + auto R = MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY); + MHD_add_response_header(R, "Content-Type", "text/html"); + promise_.set_value(std::move(R)); } stop(); } @@ -1434,8 +1444,9 @@ void HttpQueryRunMethod::finish_query() { return A.finish(); }(); - promise_.set_value( - MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY)); + auto R = MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY); + MHD_add_response_header(R, "Content-Type", "text/html"); + promise_.set_value(std::move(R)); } stop(); } @@ -1511,8 +1522,9 @@ void HttpQueryStatus::finish_query() { A << ""; return A.finish(); }(); - promise_.set_value( - MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY)); + auto R = MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY); + MHD_add_response_header(R, "Content-Type", "text/html"); + promise_.set_value(std::move(R)); } stop(); } diff --git a/blockchain-explorer/blockchain-explorer.cpp b/blockchain-explorer/blockchain-explorer.cpp index adaf4084..47ce7378 100644 --- a/blockchain-explorer/blockchain-explorer.cpp +++ b/blockchain-explorer/blockchain-explorer.cpp @@ -23,7 +23,7 @@ from all source files in the program, then also delete it here. along with TON Blockchain. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "adnl/adnl-ext-client.h" #include "adnl/utils.hpp" @@ -52,7 +52,6 @@ #include "vm/boc.h" #include "vm/cellops.h" #include "vm/cells/MerkleProof.h" -#include "vm/continuation.h" #include "vm/cp0.h" #include "auto/tl/lite_api.h" diff --git a/blockchain-explorer/blockchain-explorer.hpp b/blockchain-explorer/blockchain-explorer.hpp index 8e2ae0e6..b6bc15f1 100644 --- a/blockchain-explorer/blockchain-explorer.hpp +++ b/blockchain-explorer/blockchain-explorer.hpp @@ -23,7 +23,7 @@ exception statement from your version. If you delete this exception statement from all source files in the program, then also delete it here. - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/crypto/CMakeLists.txt b/crypto/CMakeLists.txt index ff7d9227..0897166f 100644 --- a/crypto/CMakeLists.txt +++ b/crypto/CMakeLists.txt @@ -36,6 +36,7 @@ set(TON_CRYPTO_SOURCE vm/tonops.cpp vm/boc.cpp vm/utils.cpp + vm/vm.cpp tl/tlblib.cpp Ed25519.h @@ -82,6 +83,7 @@ set(TON_CRYPTO_SOURCE vm/tonops.h vm/vmstate.h vm/utils.h + vm/vm.h vm/cells.h vm/cellslice.h @@ -204,6 +206,8 @@ set(BLOCK_SOURCE set(SMC_ENVELOPE_SOURCE smc-envelope/GenericAccount.cpp smc-envelope/HighloadWallet.cpp + smc-envelope/HighloadWalletV2.cpp + smc-envelope/ManualDns.cpp smc-envelope/MultisigWallet.cpp smc-envelope/SmartContract.cpp smc-envelope/SmartContractCode.cpp @@ -214,12 +218,15 @@ set(SMC_ENVELOPE_SOURCE smc-envelope/GenericAccount.h smc-envelope/HighloadWallet.h + smc-envelope/HighloadWalletV2.h + smc-envelope/ManualDns.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 + smc-envelope/WalletInterface.h smc-envelope/WalletV3.h ) @@ -359,12 +366,14 @@ if (NOT CMAKE_CROSSCOMPILING) GenFif(DEST smartcont/auto/wallet3-code SOURCE smartcont/wallet3-code.fc NAME wallet3) GenFif(DEST smartcont/auto/simple-wallet-code SOURCE smartcont/simple-wallet-code.fc NAME simple-wallet) 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/highload-wallet-v2-code SOURCE smartcont/highload-wallet-v2-code.fc NAME highload-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/dns-manual-code SOURCE smartcont/dns-manual-code.fc NAME dns-manual) + GenFif(DEST smartcont/auto/simple-wallet-ext-code SOURCE smartcont/simple-wallet-ext-code.fc NAME simple-wallet-ext) endif() diff --git a/crypto/block/mc-config.cpp b/crypto/block/mc-config.cpp index 46186caa..719d76b0 100644 --- a/crypto/block/mc-config.cpp +++ b/crypto/block/mc-config.cpp @@ -567,6 +567,21 @@ td::Result Config::do_get_gas_limits_prices(td::Ref c } return res; } + +td::Result Config::get_dns_root_addr() const { + auto cell = get_config_param(4); + if (cell.is_null()) { + return td::Status::Error(PSLICE() << "configuration parameter " << 4 << " with dns root address is absent"); + } + auto cs = vm::load_cell_slice(std::move(cell)); + if (cs.size() != 0x100) { + return td::Status::Error(PSLICE() << "configuration parameter " << 4 << " with dns root address has wrong size"); + } + ton::StdSmcAddress res; + CHECK(cs.fetch_bits_to(res)); + return res; +} + td::Result Config::get_gas_limits_prices(bool is_masterchain) const { auto id = is_masterchain ? 20 : 21; auto cell = get_config_param(id); diff --git a/crypto/block/mc-config.h b/crypto/block/mc-config.h index 55bf1122..8541110f 100644 --- a/crypto/block/mc-config.h +++ b/crypto/block/mc-config.h @@ -14,7 +14,7 @@ You should have received a copy of the GNU General Public License along with TON Blockchain. If not, see . - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "common/refcnt.hpp" @@ -534,6 +534,7 @@ class Config { bool create_stats_enabled() const { return has_capability(ton::capCreateStatsEnabled); } + td::Result get_dns_root_addr() const; bool set_block_id_ext(const ton::BlockIdExt& block_id_ext); td::Result> get_special_smartcontracts(bool without_config = false) const; bool is_special_smartcontract(const ton::StdSmcAddress& addr) const; diff --git a/crypto/block/transaction.cpp b/crypto/block/transaction.cpp index df4cf883..2c5f4488 100644 --- a/crypto/block/transaction.cpp +++ b/crypto/block/transaction.cpp @@ -14,7 +14,7 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #include "block/transaction.h" #include "block/block.h" @@ -23,7 +23,7 @@ #include "td/utils/bits.h" #include "td/utils/uint128.h" #include "ton/ton-shard.h" -#include "vm/continuation.h" +#include "vm/vm.h" namespace block { using td::Ref; @@ -691,12 +691,16 @@ bool ComputePhaseConfig::parse_GasLimitsPrices(Ref cs, td::RefInt } block::gen::GasLimitsPrices::Record_gas_flat_pfx flat; if (tlb::csr_unpack(cs, flat)) { - bool ok = parse_GasLimitsPrices(std::move(flat.other), freeze_due_limit, delete_due_limit); - flat_gas_limit = flat.flat_gas_limit; - flat_gas_price = flat.flat_gas_price; - return ok; + return parse_GasLimitsPrices_internal(std::move(flat.other), freeze_due_limit, delete_due_limit, + flat.flat_gas_limit, flat.flat_gas_price); + } else { + return parse_GasLimitsPrices_internal(std::move(cs), freeze_due_limit, delete_due_limit); } - flat_gas_limit = flat_gas_price = 0; +} + +bool ComputePhaseConfig::parse_GasLimitsPrices_internal(Ref cs, td::RefInt256& freeze_due_limit, + td::RefInt256& delete_due_limit, td::uint64 _flat_gas_limit, + td::uint64 _flat_gas_price) { auto f = [&](const auto& r, td::uint64 spec_limit) { gas_limit = r.gas_limit; special_gas_limit = spec_limit; @@ -716,6 +720,8 @@ bool ComputePhaseConfig::parse_GasLimitsPrices(Ref cs, td::RefInt return false; } } + flat_gas_limit = _flat_gas_limit; + flat_gas_price = _flat_gas_price; compute_threshold(); return true; } @@ -1010,10 +1016,9 @@ bool Transaction::prepare_compute_phase(const ComputePhaseConfig& cfg) { total_fees += cp.gas_fees; balance -= cp.gas_fees; } - if (verbosity > 2) { - std::cerr << "gas fees: " << cp.gas_fees << " = " << cfg.gas_price256 << " * " << cp.gas_used - << " /2^16 ; price=" << cfg.gas_price << "; remaining balance=" << balance << std::endl; - } + LOG(DEBUG) << "gas fees: " << cp.gas_fees->to_dec_string() << " = " << cfg.gas_price256->to_dec_string() << " * " + << cp.gas_used << " /2^16 ; price=" << cfg.gas_price << "; flat rate=[" << cfg.flat_gas_price << " for " + << cfg.flat_gas_limit << "]; remaining balance=" << balance.to_str(); CHECK(td::sgn(balance.grams) >= 0); } return true; diff --git a/crypto/block/transaction.h b/crypto/block/transaction.h index 10150f1c..8b3a424e 100644 --- a/crypto/block/transaction.h +++ b/crypto/block/transaction.h @@ -14,7 +14,7 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "common/refcnt.hpp" @@ -130,6 +130,11 @@ struct ComputePhaseConfig { } bool parse_GasLimitsPrices(Ref cs, td::RefInt256& freeze_due_limit, td::RefInt256& delete_due_limit); bool parse_GasLimitsPrices(Ref cell, td::RefInt256& freeze_due_limit, td::RefInt256& delete_due_limit); + + private: + bool parse_GasLimitsPrices_internal(Ref cs, td::RefInt256& freeze_due_limit, + td::RefInt256& delete_due_limit, td::uint64 flat_gas_limit = 0, + td::uint64 flat_gas_price = 0); }; struct ActionPhaseConfig { diff --git a/crypto/fift/lib/Asm.fif b/crypto/fift/lib/Asm.fif index 46fb9564..7c09e82d 100644 --- a/crypto/fift/lib/Asm.fif +++ b/crypto/fift/lib/Asm.fif @@ -1005,6 +1005,11 @@ x{F902} @Defop SHA256U x{F910} @Defop CHKSIGNU x{F911} @Defop CHKSIGNS +x{F940} @Defop CDATASIZEQ +x{F941} @Defop CDATASIZE +x{F942} @Defop SDATASIZEQ +x{F943} @Defop SDATASIZE + x{FA00} dup @Defop LDGRAMS @Defop LDVARUINT16 x{FA01} @Defop LDVARINT16 x{FA02} dup @Defop STGRAMS @Defop STVARUINT16 diff --git a/crypto/fift/lib/TonUtil.fif b/crypto/fift/lib/TonUtil.fif index 71624798..796390f9 100644 --- a/crypto/fift/lib/TonUtil.fif +++ b/crypto/fift/lib/TonUtil.fif @@ -34,8 +34,10 @@ library TonUtil // TON Blockchain Fift Library 1 and 0= } : parse-smc-addr +// ( x -- ) Displays a 64-digit hex number +{ 64 0x. } : 64x. // ( wc addr -- ) Show address in : form -{ swap ._ .":" 64 0x. } : .addr +{ swap ._ .":" 64x. } : .addr // ( wc addr flags -- ) Show address in base64url form { smca>$ type } : .Addr // ( wc addr fname -- ) Save address to file in 36-byte format diff --git a/crypto/fift/words.cpp b/crypto/fift/words.cpp index 2a36cf8f..a4067eb9 100644 --- a/crypto/fift/words.cpp +++ b/crypto/fift/words.cpp @@ -32,7 +32,7 @@ #include "vm/cells.h" #include "vm/cellslice.h" -#include "vm/continuation.h" +#include "vm/vm.h" #include "vm/cp0.h" #include "vm/dict.h" #include "vm/boc.h" @@ -1534,7 +1534,7 @@ void interpret_dict_add(vm::Stack& stack, vm::Dictionary::SetMode mode, bool add stack.push_bool(res); } -void interpret_dict_get(vm::Stack& stack, int sgnd) { +void interpret_dict_get(vm::Stack& stack, int sgnd, int mode) { int n = stack.pop_smallint_range(vm::Dictionary::max_key_bits); vm::Dictionary dict{stack.pop_maybe_cell(), n}; unsigned char buffer[vm::Dictionary::max_key_bytes]; @@ -1543,12 +1543,16 @@ void interpret_dict_get(vm::Stack& stack, int sgnd) { if (!key.is_valid()) { throw IntError{"not enough bits for a dictionary key"}; } - auto res = dict.lookup(std::move(key)); - if (res.not_null()) { + auto res = (mode & 4 ? dict.lookup_delete(std::move(key)) : dict.lookup(std::move(key))); + if (mode & 4) { + stack.push_maybe_cell(std::move(dict).extract_root_cell()); + } + bool found = res.not_null(); + if (found && (mode & 2)) { stack.push_cellslice(std::move(res)); - stack.push_bool(true); - } else { - stack.push_bool(false); + } + if (mode & 1) { + stack.push_bool(found); } } @@ -1572,15 +1576,15 @@ void interpret_dict_map(IntCtx& ctx) { ctx.stack.push_maybe_cell(std::move(dict).extract_root_cell()); } -void interpret_dict_map_ext(IntCtx& ctx) { +void interpret_dict_map_ext(IntCtx& ctx, bool sgnd) { auto func = pop_exec_token(ctx); int n = ctx.stack.pop_smallint_range(vm::Dictionary::max_key_bits); vm::Dictionary dict{ctx.stack.pop_maybe_cell(), n}; - vm::Dictionary::map_func_t map_func = [&ctx, func](vm::CellBuilder& cb, Ref cs_ref, - td::ConstBitPtr key, int key_len) -> bool { + vm::Dictionary::map_func_t map_func = [&ctx, func, sgnd](vm::CellBuilder& cb, Ref cs_ref, + td::ConstBitPtr key, int key_len) -> bool { ctx.stack.push_builder(Ref(cb)); td::RefInt256 x{true}; - x.unique_write().import_bits(key, key_len, false); + x.unique_write().import_bits(key, key_len, sgnd); ctx.stack.push_int(std::move(x)); ctx.stack.push_cellslice(std::move(cs_ref)); func->run(ctx); @@ -1596,20 +1600,20 @@ void interpret_dict_map_ext(IntCtx& ctx) { ctx.stack.push_maybe_cell(std::move(dict).extract_root_cell()); } -void interpret_dict_foreach(IntCtx& ctx) { +void interpret_dict_foreach(IntCtx& ctx, bool sgnd) { auto func = pop_exec_token(ctx); int n = ctx.stack.pop_smallint_range(vm::Dictionary::max_key_bits); vm::Dictionary dict{ctx.stack.pop_maybe_cell(), n}; - vm::Dictionary::foreach_func_t foreach_func = [&ctx, func](Ref cs_ref, td::ConstBitPtr key, - int key_len) -> bool { + vm::Dictionary::foreach_func_t foreach_func = [&ctx, func, sgnd](Ref cs_ref, td::ConstBitPtr key, + int key_len) -> bool { td::RefInt256 x{true}; - x.unique_write().import_bits(key, key_len, false); + x.unique_write().import_bits(key, key_len, sgnd); ctx.stack.push_int(std::move(x)); ctx.stack.push_cellslice(std::move(cs_ref)); func->run(ctx); return ctx.stack.pop_bool(); }; - ctx.stack.push_bool(dict.check_for_each(std::move(foreach_func))); + ctx.stack.push_bool(dict.check_for_each(std::move(foreach_func), sgnd)); } void interpret_dict_merge(IntCtx& ctx) { @@ -2752,23 +2756,31 @@ void init_words_common(Dictionary& d) { d.def_stack_word("sdict! ", std::bind(interpret_dict_add, _1, vm::Dictionary::SetMode::Set, false, -1)); d.def_stack_word("b>sdict!+ ", std::bind(interpret_dict_add, _1, vm::Dictionary::SetMode::Add, true, -1)); d.def_stack_word("b>sdict! ", std::bind(interpret_dict_add, _1, vm::Dictionary::SetMode::Set, true, -1)); - d.def_stack_word("sdict@ ", std::bind(interpret_dict_get, _1, -1)); + d.def_stack_word("sdict@ ", std::bind(interpret_dict_get, _1, -1, 3)); + d.def_stack_word("sdict@- ", std::bind(interpret_dict_get, _1, -1, 7)); + d.def_stack_word("sdict- ", std::bind(interpret_dict_get, _1, -1, 5)); d.def_stack_word("udict!+ ", std::bind(interpret_dict_add, _1, vm::Dictionary::SetMode::Add, false, 0)); d.def_stack_word("udict! ", std::bind(interpret_dict_add, _1, vm::Dictionary::SetMode::Set, false, 0)); d.def_stack_word("b>udict!+ ", std::bind(interpret_dict_add, _1, vm::Dictionary::SetMode::Add, true, 0)); d.def_stack_word("b>udict! ", std::bind(interpret_dict_add, _1, vm::Dictionary::SetMode::Set, true, 0)); - d.def_stack_word("udict@ ", std::bind(interpret_dict_get, _1, 0)); + d.def_stack_word("udict@ ", std::bind(interpret_dict_get, _1, 0, 3)); + d.def_stack_word("udict@- ", std::bind(interpret_dict_get, _1, 0, 7)); + d.def_stack_word("udict- ", std::bind(interpret_dict_get, _1, 0, 5)); d.def_stack_word("idict!+ ", std::bind(interpret_dict_add, _1, vm::Dictionary::SetMode::Add, false, 1)); d.def_stack_word("idict! ", std::bind(interpret_dict_add, _1, vm::Dictionary::SetMode::Set, false, 1)); d.def_stack_word("b>idict!+ ", std::bind(interpret_dict_add, _1, vm::Dictionary::SetMode::Add, true, 1)); d.def_stack_word("b>idict! ", std::bind(interpret_dict_add, _1, vm::Dictionary::SetMode::Set, true, 1)); - d.def_stack_word("idict@ ", std::bind(interpret_dict_get, _1, 1)); + d.def_stack_word("idict@ ", std::bind(interpret_dict_get, _1, 1, 3)); + d.def_stack_word("idict@- ", std::bind(interpret_dict_get, _1, 1, 7)); + d.def_stack_word("idict- ", std::bind(interpret_dict_get, _1, 1, 5)); d.def_stack_word("pfxdict!+ ", std::bind(interpret_pfx_dict_add, _1, vm::Dictionary::SetMode::Add, false)); d.def_stack_word("pfxdict! ", std::bind(interpret_pfx_dict_add, _1, vm::Dictionary::SetMode::Set, false)); d.def_stack_word("pfxdict@ ", interpret_pfx_dict_get); d.def_ctx_word("dictmap ", interpret_dict_map); - d.def_ctx_word("dictmapext ", interpret_dict_map_ext); - d.def_ctx_word("dictforeach ", interpret_dict_foreach); + d.def_ctx_word("dictmapext ", std::bind(interpret_dict_map_ext, _1, false)); + d.def_ctx_word("idictmapext ", std::bind(interpret_dict_map_ext, _1, true)); + d.def_ctx_word("dictforeach ", std::bind(interpret_dict_foreach, _1, false)); + d.def_ctx_word("idictforeach ", std::bind(interpret_dict_foreach, _1, true)); d.def_ctx_word("dictmerge ", interpret_dict_merge); d.def_ctx_word("dictdiff ", interpret_dict_diff); // slice/bitstring constants diff --git a/crypto/smartcont/dns-manual-code.fc b/crypto/smartcont/dns-manual-code.fc new file mode 100644 index 00000000..7e34a1cc --- /dev/null +++ b/crypto/smartcont/dns-manual-code.fc @@ -0,0 +1,381 @@ +{- + Originally created by: + /------------------------------------------------------------------------\ + | Created for: Telegram (Open Network) Blockchain Contest | + | Task 3: DNS Resolver (Manually controlled) | + >------------------------------------------------------------------------< + | Author: Oleksandr Murzin (tg: @skydev / em: alexhacker64@gmail.com) | + | October 2019 | + \------------------------------------------------------------------------/ +-} + +;;===========================================================================;; +;; Custom ASM instructions ;; +;;===========================================================================;; + +;; Args: s D n | Success: s' x s'' -1 | Failure: s 0 -> s N N 0 +(slice, slice, slice, int) pfxdict_get?(cell dict, int key_len, slice key) + asm(key dict key_len) "PFXDICTGETQ" "NULLSWAPIFNOT" "NULLSWAPIFNOT"; + +;; Args: x k D n | Success: D' -1 | Failure: D 0 +(cell, int) pfxdict_set?(cell dict, int key_len, slice key, slice value) + asm(value key dict key_len) "PFXDICTSET"; + +;; Args: k D n | Success: D' -1 | Failure: D 0 +(cell, int) pfxdict_delete?(cell dict, int key_len, slice key) + asm(key dict key_len) "PFXDICTDEL"; + +slice slice_last(slice s, int len) asm "SDCUTLAST"; + +;; Actually, equivalent to dictionaries, provided for clarity +(slice, cell) load_maybe_ref(slice s) asm( -> 1 0) "LDOPTREF"; +builder store_maybe_ref(builder b, cell c) asm(c b) "STOPTREF"; + +(cell, ()) pfxdict_set_ref(cell dict, int key_len, slice key, cell value) { + throw_unless(33, dict~pfxdict_set?(key_len, key, begin_cell().store_maybe_ref(value).end_cell().begin_parse())); + return (dict, ()); +} + +(slice, cell, slice, int) pfxdict_get_ref(cell dict, int key_len, slice key) { + (slice pfx, slice val, slice tail, int succ) = dict.pfxdict_get?(key_len, key); + cell res = null(); + if (succ) { + res = val~load_maybe_ref(); + } + return (pfx, res, tail, succ); +} + +;;===========================================================================;; +;; Utility functions ;; +;;===========================================================================;; + +(int, int, int, cell, cell) load_data() { + slice cs = get_data().begin_parse(); + var res = (cs~load_uint(32), cs~load_uint(64), cs~load_uint(256), cs~load_dict(), cs~load_dict()); + cs.end_parse(); + return res; +} + +() store_data(int subwallet, int last_cleaned, int public_key, cell root, old_queries) impure { + set_data(begin_cell() + .store_uint(subwallet, 32) + .store_uint(last_cleaned, 64) + .store_uint(public_key, 256) + .store_dict(root) + .store_dict(old_queries) + .end_cell()); +} + +;;===========================================================================;; +;; Internal message handler (Code 0) ;; +;;===========================================================================;; + +() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure { + ;; not interested at all +} + +;;===========================================================================;; +;; External message handler (Code -1) ;; +;;===========================================================================;; + +{- + External message structure: + [Bytes<512b>:signature] [UInt<32b>:seqno] [UInt<6b>:operation] + [Either b0: inline name (<= 58-x Bytes) or b1: reference-stored name) + x depends on operation + Use of 6-bit op instead of 32-bit allows to save 4 bytes for inline name + Inline [Name] structure: [UInt<6b>:length] [Bytes:data] + Operations (continuation of message): + 00 Contract initialization message (only if seqno = 0) (x=-) + 11 VSet: set specified value to specified subdomain->category (x=2) + [Int<16b>:category] [Name:subdomain] [Cell<1r>:value] + 12 VDel: delete specified subdomain->category (x=2) + [Int<16b>:category] [Name:subdomain] + 21 DSet: replace entire category dictionary of domain with provided (x=0) + [Name:subdomain] [Cell<1r>:new_cat_table] + 22 DDel: delete entire category dictionary of specified domain (x=0) + [Name:subdomain] + 31 TSet: replace ENTIRE DOMAIN TABLE with the provided tree root cell (x=-) + [Cell<1r>:new_domains_table] + 32 TDel: nullify ENTIRE DOMAIN TABLE (x=-) + 51 OSet: replace owner public key with a new one (x=-) + [UInt<256b>:new_public_key] +-} + +(cell, slice) process_op(cell root, slice ops) { + int op = ops~load_uint(6); + int is_name_ref = (ops~load_uint(1) == 1); + + ;; lets assume at this point that special operations 00..09 are handled + throw_if(45, op < 10); + slice name = ops; ;; anything! better do not begin or it costs much gas + cell cat_table = null(); + int cat = 0; + if (op < 20) { + ;; for operations with codes 10..19 category is required + cat = ops~load_int(16); + } + int zeros = 0; + if (op < 30) { + ;; for operations with codes 10..29 name is required + if (is_name_ref) { + ;; name is stored in separate referenced cell + name = ops~load_ref().begin_parse(); + } else { + ;; name is stored inline + int name_len = ops~load_uint(6) * 8; + name = ops~load_bits(name_len); + } + ;; at least one character not counting \0 + throw_unless(38, name.slice_bits() >= 16); + ;; name shall end with \0 + (_, int name_last_byte) = name.slice_last(8).load_uint(8); + throw_unless(40, name_last_byte == 0); + + ;; Multiple zero characters seem to be allowed as per github issue response + ;; Lets change the tactics! + + int loop = -1; + slice cname = name; + ;; better safe then sorry, dont want to catch any of loop bugs + while (loop) { + int lval = cname~load_uint(8); + if (lval == 0) { zeros += 1; } + if (cname.slice_bits() == 0) { loop = 0; } + } + ;; throw_unless(39, zeros == 1); + } + ;; operation with codes 10..19 manipulate category dict + ;; lets try to find it and store into a variable + ;; operations with codes 20..29 replace / delete dict, no need + name = begin_cell().store_uint(zeros, 7).store_slice(name).end_cell().begin_parse(); + if (op < 20) { + ;; lets resolve the name here so as not to duplicate the code + (slice pfx, cell val, slice tail, int succ) = + root.pfxdict_get_ref(1023, name); + if (succ) { + ;; must match EXACTLY to prevent accident changes + throw_unless(35, tail.slice_empty?()); + cat_table = val; + } + ;; otherwise cat_table is null which is reasonable for actions + } + ;; 11 VSet: set specified value to specified subdomain->category + if (op == 11) { + cell new_value = ops~load_maybe_ref(); + if (new_value.cell_null?()) { + cat_table~idict_delete?(16, cat); + } else { + cat_table~idict_set_ref(16, cat, new_value); + } + root~pfxdict_set_ref(1023, name, cat_table); + return (root, ops); + } + ;; 12 VDel: delete specified subdomain->category value + if (op == 12) { + ifnot (cat_table.dict_empty?()) { + cat_table~idict_delete?(16, cat); + root~pfxdict_set_ref(1023, name, cat_table); + } + return (root, ops); + } + ;; 21 DSet: replace entire category dictionary of domain with provided + if (op == 21) { + cell new_cat_table = ops~load_maybe_ref(); + root~pfxdict_set_ref(1023, name, new_cat_table); + return (root, ops); + } + ;; 22 DDel: delete entire category dictionary of specified domain + if (op == 22) { + root~pfxdict_delete?(1023, name); + return (root, ops); + } + ;; 31 TSet: replace ENTIRE DOMAIN TABLE with the provided tree root cell + if (op == 31) { + cell new_tree_root = ops~load_maybe_ref(); + ;; no sanity checks cause they would cost immense gas + return (new_tree_root, ops); + } + ;; 32 TDel: nullify ENTIRE DOMAIN TABLE + if (op == 32) { + return (null(), ops); + } + throw(44); ;; invalid operation + return (root, ops); +} + +cell process_ops(cell root, slice ops) { + int f = -1; + while (f) { + (root, ops) = process_op(root, ops); + if (ops.slice_refs_empty?()) { + f = 0; + } else { + ops = ops~load_ref().begin_parse(); + } + } + return root; +} + +() recv_external(slice in_msg) impure { + ;; Load data + (int stored_subwalet, int last_cleaned, int public_key, cell root, cell old_queries) = load_data(); + + ;; validate signature and seqno + slice signature = in_msg~load_bits(512); + int shash = slice_hash(in_msg); + var (subwallet_id, query_id) = (in_msg~load_uint(32), in_msg~load_uint(64)); + var bound = (now() << 32); + throw_if(35, query_id < bound); + (_, var found?) = old_queries.udict_get?(64, query_id); + throw_if(32, found?); + throw_unless(34, check_signature(shash, signature, public_key)); + accept_message(); ;; message is signed by owner, sanity not guaranteed yet + + + int op = in_msg.preload_uint(6); + ;; 00 Contract initialization message (only if seqno = 0) + if (op == 0) { + ;; noop + } else { if (op == 51) { + in_msg~skip_bits(6); + public_key = in_msg~load_uint(256); + } else { + root = process_ops(root, in_msg); + } } + + bound -= (64 << 32); ;; clean up records expired more than 64 seconds ago + old_queries~udict_set_builder(64, query_id, begin_cell()); + var queries = old_queries; + do { + var (old_queries', i, _, f) = old_queries.udict_delete_get_min(64); + f~touch(); + if (f) { + f = (i < bound); + } + if (f) { + old_queries = old_queries'; + last_cleaned = i; + } + } until (~ f); + + store_data(subwallet_id, last_cleaned, public_key, root, old_queries); +} + +{- + Data structure: + Root cell: [UInt<32b>:seqno] [UInt<256b>:owner_public_key] + [OptRef<1b+1r?>:HashmapCatTable>:domains] + := HashmapE 16 ^DNSRecord + := arbitary? not defined anywhere in documentation or internet! + + STORED DOMAIN NAME SLICE FORMAT: (#ZeroChars<7b>) (Domain name value) + #Zeros allows to simultaneously store, for example, com\0 and com\0google\0 + That will be stored as \1com\0 and \2com\0google\0 (pfx tree has restricitons) + This will allow to resolve more specific requests to subdomains, and resort + to parent domain next resolver lookup if subdomain is not found + com\0goo\0 lookup will, for example look up \2com\0goo\0 and then + \1com\0goo\0 which will return \1com\0 (as per pfx tree) with -1 cat +-} + +;;===========================================================================;; +;; Getter methods ;; +;;===========================================================================;; + +int get_seqno() method_id { ;; Retrieve sequence number + return get_data().begin_parse().preload_uint(32); +} + +;;8m dns-record-value +(int, cell) dnsresolve(slice subdomain, int category) method_id { + cell Null = null(); ;; pseudo-alias + throw_if(30, subdomain.slice_bits() % 8 != 0); ;; malformed input (~ 8n-bit) + if (subdomain.slice_bits() == 0) { return (0, Null); } ;; zero-length input + {- ;; Logic thrown away: return only first ZB-delimited subdomain, + appends \0 if neccessary (not required for pfx, \0 can be ap more eff) + builder b_name = begin_cell(); + slice remaining = subdomain; + int char = remaining~load_uint(8); ;; seems to be the most optimal way + do { + b_name~store_uint(char, 8); + char = remaining~load_uint(8); + } until ((remaining.slice_bits() == 0) | (char == 0)); + if (char == 0) { category = -1; } + if ((remaining.slice_bits() == 0) & (char != 0)) { + b_name~store_uint(0, 8); ;; string was not terminated with zero byte + } + cell c_name = b_name.end_cell(); + slice s_name = c_name.begin_parse(); + -} + (_, int name_last_byte) = subdomain.slice_last(8).load_uint(8); + if ((name_last_byte == 0) & (subdomain.slice_bits() == 8)) { + return (0, Null); ;; zero-length input, but with zero byte + } + slice s_name = subdomain; + if (name_last_byte != 0) { + s_name = begin_cell().store_slice(subdomain) ;; append zero byte + .store_uint(0, 8).end_cell().begin_parse(); + } + (_, _, _, cell root, _) = load_data(); + + ;; Multiple zero characters seem to be allowed as per github issue response + ;; Lets change the tactics! + + int zeros = 0; + int loop = -1; + slice cname = s_name; + ;; better safe then sorry, dont want to catch any of loop bugs + while (loop) { + int lval = cname~load_uint(8); + if (lval == 0) { zeros += 1; } + if (cname.slice_bits() == 0) { loop = 0; } + } + + ;; can't move below, will cause errors! + slice pfx = cname; cell val = null(); + slice tail = cname; int succ = 0; + + while (zeros > 0) { + slice pfname = begin_cell().store_uint(zeros, 7) + .store_slice(s_name).end_cell().begin_parse(); + (pfx, val, tail, succ) = root.pfxdict_get_ref(1023, pfname); + if (succ) { zeros = 1; } ;; break + zeros -= 1; + } + + + zeros = pfx.preload_uint(7); + + if (~ succ) { + return (0, Null); ;; failed to find entry in prefix dictionary + } + if (~ tail.slice_empty?()) { ;; if we have tail then len(pfx) < len(subdomain) + category = -1; ;; incomplete subdomain found, must return next resolver (-1) + } + int pfx_bits = pfx.slice_bits() - 7; + cell cat_table = val; + ;; pfx.slice_bits() will contain 8m, where m is number of bytes in subdomain + ;; COUNTING for the zero byte (if structurally correct: no multiple-ZB keys) + ;; which corresponds to "8m, m=one plus the number of bytes in the subdomain found) + if (category == 0) { + return (pfx_bits, cat_table); ;; return cell with entire dictionary for 0 + } else { + cell cat_found = cat_table.idict_get_ref(16, category); + {- it seems that if subdomain is found but cat is not need to return (8m, Null) + if (cat_found.cell_null?()) { + pfx_bits = 0; ;; to return (0, Null) instead of (8m, Null) + ;; my thoughts about this requirement are in next block comment + } -} + return (pfx_bits, cat_found); ;; no need to unslice and cellize the poor cat now + {- Old logic garbage, replaced with ref functions discovered + ;; dictionary category lookup + (slice cat_value, int cat_found) = cat_table.idict_get?(16, category); + if (~ cat_found) { + ;; we have failed to find the cat :( + return (0, Null); + } + ;; cat is found, turn it's slices into cells + return (pfx.slice_bits(), begin_cell().store_slice(cat_value).end_cell()); + -} + } +} diff --git a/crypto/smartcont/gen-zerostate.fif b/crypto/smartcont/gen-zerostate.fif index 5c4de564..e0a86469 100644 --- a/crypto/smartcont/gen-zerostate.fif +++ b/crypto/smartcont/gen-zerostate.fif @@ -17,10 +17,10 @@ dup dup 31 boc+>B dup Bx. cr dup "basestate0" +suffix +".boc" tuck B>file ."(Initial basechain state saved to file " type .")" cr Bhashu dup =: basestate0_fhash -."file hash=" dup x. space 256 u>B dup B>base64url type cr +."file hash=" dup 64x. space 256 u>B dup B>base64url type cr "basestate0" +suffix +".fhash" B>file hashu dup =: basestate0_rhash -."root hash=" dup x. space 256 u>B dup B>base64url type cr +."root hash=" dup 64x. space 256 u>B dup B>base64url type cr "basestate0" +suffix +".rhash" B>file basestate0_rhash basestate0_fhash now 0 2 32 0 add-std-workchain @@ -128,7 +128,7 @@ GR$666 // balance 2 // mode: create register_smc dup make_special dup constant smc3_addr -."address = " x. cr +."address = " 64x. cr /* * @@ -160,7 +160,7 @@ Masterchain swap // 9 4 1 config.validator_num! 1000 100 13 config.validator_num! // min-stake max-stake min-total-stake max-factor -GR$10000 GR$10000000 GR$500000 sg~10 config.validator_stake_limits! +GR$10000 GR$10000000 GR$500000 sg~3 config.validator_stake_limits! // elected-for elect-start-before elect-end-before stakes-frozen-for // 400000 200000 4000 400000 config.election_params! // 4000 2000 500 1000 config.election_params! // DEBUG diff --git a/crypto/smartcont/show-addr.fif b/crypto/smartcont/show-addr.fif index 506aa492..86b31463 100755 --- a/crypto/smartcont/show-addr.fif +++ b/crypto/smartcont/show-addr.fif @@ -12,9 +12,9 @@ $1 "new-wallet" replace-if-null =: file-base file-base +".addr" dup ."Loading wallet address from " type cr file>B 32 B| dup Blen { 32 B>i@ } { drop Basechain } cond constant wallet_wc 256 B>u@ dup constant wallet_addr -."Source wallet address = " wallet_wc ._ .":" x. cr -wallet_wc wallet_addr 2dup 7 smca>$ ."Non-bounceable address (for init only): " type cr -6 smca>$ ."Bounceable address (for later access): " type cr +."Source wallet address = " wallet_wc swap 2dup .addr cr +."Non-bounceable address (for init only): " 2dup 7 .Addr cr +."Bounceable address (for later access): " 6 .Addr cr file-base +".pk" dup file-exists? { dup file>B dup Blen 32 <> abort"Private key must be exactly 32 bytes long" diff --git a/crypto/smartcont/stdlib.fc b/crypto/smartcont/stdlib.fc index 9041bafe..2dc5c137 100644 --- a/crypto/smartcont/stdlib.fc +++ b/crypto/smartcont/stdlib.fc @@ -31,6 +31,9 @@ int string_hash(slice s) asm "SHA256U"; int check_signature(int hash, slice signature, int public_key) asm "CHKSIGNU"; int check_data_signature(slice data, slice signature, int public_key) asm "CHKSIGNS"; +(int, int, int) compute_data_size(cell c, int max_cells) asm "CDATASIZE"; +(int, int, int) slice_compute_data_size(slice s, int max_cells) asm "SDATASIZE"; + ;; () throw_if(int excno, int cond) impure asm "THROWARGIF"; () dump_stack() impure asm "DUMPSTK"; diff --git a/crypto/smartcont/validator-elect-req.fif b/crypto/smartcont/validator-elect-req.fif index 215fe9c1..a220b421 100755 --- a/crypto/smartcont/validator-elect-req.fif +++ b/crypto/smartcont/validator-elect-req.fif @@ -22,7 +22,7 @@ def? $5 { @' $5 } { "validator-to-sign.bin" } cond constant output_fname ."Creating a request to participate in validator elections at time " elect_time . ."from smart contract " -1 src_addr 2dup 1 .Addr ." = " .addr ." with maximal stake factor with respect to the minimal stake " max_factor ._ -."/65536 and validator ADNL address " adnl_addr x. cr +."/65536 and validator ADNL address " adnl_addr 64x. cr B{654c5074} elect_time 32 u>B B+ max_factor 32 u>B B+ src_addr 256 u>B B+ adnl_addr 256 u>B B+ dup Bx. cr diff --git a/crypto/smartcont/validator-elect-signed.fif b/crypto/smartcont/validator-elect-signed.fif index 55e3d9dd..4e681f07 100755 --- a/crypto/smartcont/validator-elect-signed.fif +++ b/crypto/smartcont/validator-elect-signed.fif @@ -27,7 +27,7 @@ def? $7 { @' $7 } { "validator-query.boc" } cond constant output_fname ."Creating a request to participate in validator elections at time " elect_time . ."from smart contract " -1 src_addr 2dup 1 .Addr ." = " .addr ." with maximal stake factor with respect to the minimal stake " max_factor ._ -."/65536 and validator ADNL address " adnl_addr x. cr +."/65536 and validator ADNL address " adnl_addr 64x. cr B{654c5074} elect_time 32 u>B B+ max_factor 32 u>B B+ src_addr 256 u>B B+ adnl_addr 256 u>B B+ ."String to sign is: " dup Bx. cr constant to_sign diff --git a/crypto/smc-envelope/HighloadWallet.cpp b/crypto/smc-envelope/HighloadWallet.cpp index 5f540780..e44ce97b 100644 --- a/crypto/smc-envelope/HighloadWallet.cpp +++ b/crypto/smc-envelope/HighloadWallet.cpp @@ -14,7 +14,7 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #include "HighloadWallet.h" #include "GenericAccount.h" @@ -51,7 +51,7 @@ td::Ref HighloadWallet::get_init_message(const td::Ed25519::PrivateKey td::Ref HighloadWallet::make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 wallet_id, td::uint32 seqno, td::uint32 valid_until, td::Span gifts) noexcept { - CHECK(gifts.size() <= 254); + CHECK(gifts.size() <= max_gifts_size); vm::Dictionary messages(16); for (size_t i = 0; i < gifts.size(); i++) { auto& gift = gifts[i]; @@ -64,7 +64,7 @@ td::Ref HighloadWallet::make_a_gift_message(const td::Ed25519::Private vm::CellBuilder cb; GenericAccount::store_int_message(cb, gift.destination, gramms); cb.store_bytes("\0\0\0\0", 4); - //vm::CellString::store(cb, gift.message, 35 * 8).ensure(); + vm::CellString::store(cb, gift.message, 35 * 8).ensure(); auto message_inner = cb.finalize(); cb = {}; cb.store_long(send_mode, 8).store_ref(message_inner); @@ -123,4 +123,20 @@ td::Result HighloadWallet::get_wallet_id_or_throw() const { return static_cast(cs.fetch_ulong(32)); } +td::Result HighloadWallet::get_public_key() const { + return TRY_VM(get_public_key_or_throw()); +} + +td::Result HighloadWallet::get_public_key_or_throw() const { + if (state_.data.is_null()) { + return td::Status::Error("data is null"); + } + //FIXME use get method + auto cs = vm::load_cell_slice(state_.data); + cs.skip_first(64); + td::SecureString res(td::Ed25519::PublicKey::LENGTH); + cs.fetch_bytes(res.as_mutable_slice().ubegin(), td::narrow_cast(res.size())); + return td::Ed25519::PublicKey(std::move(res)); +} + } // namespace ton diff --git a/crypto/smc-envelope/HighloadWallet.h b/crypto/smc-envelope/HighloadWallet.h index aa1e3a4b..e1db23ef 100644 --- a/crypto/smc-envelope/HighloadWallet.h +++ b/crypto/smc-envelope/HighloadWallet.h @@ -14,29 +14,26 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "smc-envelope/SmartContract.h" +#include "smc-envelope/WalletInterface.h" #include "vm/cells.h" #include "Ed25519.h" #include "block/block.h" #include "vm/cells/CellString.h" namespace ton { -class HighloadWallet : ton::SmartContract { +class HighloadWallet : ton::SmartContract, public WalletInterface { public: explicit HighloadWallet(State state) : ton::SmartContract(std::move(state)) { } static constexpr unsigned max_message_size = vm::CellString::max_bytes; + static constexpr unsigned max_gifts_size = 254; static td::Ref get_init_state(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id) noexcept; static td::Ref get_init_message(const td::Ed25519::PrivateKey& private_key, td::uint32 wallet_id) noexcept; - struct Gift { - block::StdAddress destination; - td::int64 gramms; - std::string message; - }; static td::Ref make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 wallet_id, td::uint32 seqno, td::uint32 valid_until, td::Span gifts) noexcept; @@ -47,8 +44,20 @@ class HighloadWallet : ton::SmartContract { td::Result get_seqno() const; td::Result get_wallet_id() const; + td::Result> make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 valid_until, + td::Span gifts) const override { + TRY_RESULT(seqno, get_seqno()); + TRY_RESULT(wallet_id, get_wallet_id()); + return make_a_gift_message(private_key, wallet_id, seqno, valid_until, gifts); + } + size_t get_max_gifts_size() const override { + return max_gifts_size; + } + td::Result get_public_key() const override; + private: td::Result get_seqno_or_throw() const; td::Result get_wallet_id_or_throw() const; + td::Result get_public_key_or_throw() const; }; } // namespace ton diff --git a/crypto/smc-envelope/HighloadWalletV2.cpp b/crypto/smc-envelope/HighloadWalletV2.cpp new file mode 100644 index 00000000..823161b9 --- /dev/null +++ b/crypto/smc-envelope/HighloadWalletV2.cpp @@ -0,0 +1,151 @@ +/* + 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-2020 Telegram Systems LLP +*/ +#include "HighloadWalletV2.h" +#include "GenericAccount.h" +#include "SmartContractCode.h" + +#include "vm/boc.h" +#include "vm/cells/CellString.h" +#include "td/utils/base64.h" + +#include + +namespace ton { +td::optional HighloadWalletV2::guess_revision(const vm::Cell::Hash& code_hash) { + for (td::int32 i = 1; i <= 2; i++) { + if (get_init_code(i)->get_hash() == code_hash) { + return i; + } + } + return {}; +} +td::optional HighloadWalletV2::guess_revision(const block::StdAddress& address, + const td::Ed25519::PublicKey& public_key, + td::uint32 wallet_id) { + for (td::int32 i = 1; i <= 2; i++) { + if (GenericAccount::get_address(address.workchain, get_init_state(public_key, wallet_id, i)) == address) { + return i; + } + } + return {}; +} +td::Ref HighloadWalletV2::get_init_state(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id, + td::int32 revision) noexcept { + auto code = get_init_code(revision); + auto data = get_init_data(public_key, wallet_id); + return GenericAccount::get_init_state(std::move(code), std::move(data)); +} + +td::Ref HighloadWalletV2::get_init_message(const td::Ed25519::PrivateKey& private_key, td::uint32 wallet_id, + td::uint32 valid_until) noexcept { + td::uint32 id = -1; + auto append_message = [&](auto&& cb) -> vm::CellBuilder& { + cb.store_long(wallet_id, 32).store_long(valid_until, 32).store_long(id, 32); + CHECK(cb.store_maybe_ref({})); + return cb; + }; + auto signature = private_key.sign(append_message(vm::CellBuilder()).finalize()->get_hash().as_slice()).move_as_ok(); + + return append_message(vm::CellBuilder().store_bytes(signature)).finalize(); +} + +td::Ref HighloadWalletV2::make_a_gift_message(const td::Ed25519::PrivateKey& private_key, + td::uint32 wallet_id, td::uint32 valid_until, + td::Span gifts) noexcept { + CHECK(gifts.size() <= max_gifts_size); + vm::Dictionary messages(16); + for (size_t i = 0; i < gifts.size(); i++) { + auto& gift = gifts[i]; + td::int32 send_mode = 3; + auto gramms = gift.gramms; + if (gramms == -1) { + gramms = 0; + send_mode += 128; + } + vm::CellBuilder cb; + GenericAccount::store_int_message(cb, gift.destination, gramms); + cb.store_bytes("\0\0\0\0", 4); + vm::CellString::store(cb, gift.message, 35 * 8).ensure(); + auto message_inner = cb.finalize(); + cb = {}; + cb.store_long(send_mode, 8).store_ref(message_inner); + auto key = messages.integer_key(td::make_refint(i), 16, false); + messages.set_builder(key.bits(), 16, cb); + } + std::string hash; + { + vm::CellBuilder cb; + CHECK(cb.store_maybe_ref(messages.get_root_cell())); + hash = cb.finalize()->get_hash().as_slice().substr(28, 4).str(); + } + + vm::CellBuilder cb; + cb.store_long(wallet_id, 32).store_long(valid_until, 32).store_bytes(hash); + CHECK(cb.store_maybe_ref(messages.get_root_cell())); + auto message_outer = cb.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 HighloadWalletV2::get_init_code(td::int32 revision) noexcept { + return SmartContractCode::highload_wallet_v2(revision); +} + +vm::CellHash HighloadWalletV2::get_init_code_hash() noexcept { + return get_init_code(0)->get_hash(); +} + +td::Ref HighloadWalletV2::get_init_data(const td::Ed25519::PublicKey& public_key, + td::uint32 wallet_id) noexcept { + vm::CellBuilder cb; + cb.store_long(wallet_id, 32).store_long(0, 64).store_bytes(public_key.as_octet_string()); + CHECK(cb.store_maybe_ref({})); + return cb.finalize(); +} + +td::Result HighloadWalletV2::get_wallet_id() const { + return TRY_VM(get_wallet_id_or_throw()); +} + +td::Result HighloadWalletV2::get_wallet_id_or_throw() const { + if (state_.data.is_null()) { + return 0; + } + //FIXME use get method + auto cs = vm::load_cell_slice(state_.data); + return static_cast(cs.fetch_ulong(32)); +} + +td::Result HighloadWalletV2::get_public_key() const { + return TRY_VM(get_public_key_or_throw()); +} + +td::Result HighloadWalletV2::get_public_key_or_throw() const { + if (state_.data.is_null()) { + return td::Status::Error("data is null"); + } + //FIXME use get method + auto cs = vm::load_cell_slice(state_.data); + cs.skip_first(96); + td::SecureString res(td::Ed25519::PublicKey::LENGTH); + cs.fetch_bytes(res.as_mutable_slice().ubegin(), td::narrow_cast(res.size())); + return td::Ed25519::PublicKey(std::move(res)); +} + +} // namespace ton diff --git a/crypto/smc-envelope/HighloadWalletV2.h b/crypto/smc-envelope/HighloadWalletV2.h new file mode 100644 index 00000000..2b6f99e7 --- /dev/null +++ b/crypto/smc-envelope/HighloadWalletV2.h @@ -0,0 +1,66 @@ +/* + 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-2020 Telegram Systems LLP +*/ +#pragma once + +#include "smc-envelope/SmartContract.h" +#include "smc-envelope/WalletInterface.h" +#include "vm/cells.h" +#include "Ed25519.h" +#include "block/block.h" +#include "vm/cells/CellString.h" + +namespace ton { +class HighloadWalletV2 : ton::SmartContract, public WalletInterface { + public: + explicit HighloadWalletV2(State state) : ton::SmartContract(std::move(state)) { + } + static constexpr unsigned max_message_size = vm::CellString::max_bytes; + static constexpr unsigned max_gifts_size = 254; + static td::Ref get_init_state(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id, + td::int32 revision) noexcept; + static td::Ref get_init_message(const td::Ed25519::PrivateKey& private_key, td::uint32 wallet_id, + td::uint32 valid_until) noexcept; + + static td::Ref make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 wallet_id, + td::uint32 valid_until, td::Span gifts) noexcept; + + static td::Ref get_init_code(td::int32 revision) noexcept; + static vm::CellHash get_init_code_hash() noexcept; + static td::Ref get_init_data(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id) noexcept; + static td::optional guess_revision(const vm::Cell::Hash& code_hash); + static td::optional guess_revision(const block::StdAddress& address, + const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id); + + td::Result get_wallet_id() const; + + td::Result> make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 valid_until, + td::Span gifts) const override { + TRY_RESULT(wallet_id, get_wallet_id()); + return make_a_gift_message(private_key, wallet_id, valid_until, gifts); + } + size_t get_max_gifts_size() const override { + return max_gifts_size; + } + td::Result get_public_key() const override; + + private: + td::Result get_wallet_id_or_throw() const; + td::Result get_public_key_or_throw() const; +}; +} // namespace ton diff --git a/crypto/smc-envelope/ManualDns.cpp b/crypto/smc-envelope/ManualDns.cpp new file mode 100644 index 00000000..c932f49d --- /dev/null +++ b/crypto/smc-envelope/ManualDns.cpp @@ -0,0 +1,542 @@ +/* + 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 2019-2020 Telegram Systems LLP +*/ +#include "ManualDns.h" + +#include "smc-envelope/SmartContractCode.h" + +#include "vm/dict.h" + +#include "td/utils/format.h" +#include "td/utils/overloaded.h" +#include "td/utils/Parser.h" +#include "td/utils/Random.h" + +#include "block/block-auto.h" +#include "block/block-parse.h" + +namespace ton { + +//proto_list_nil$0 = ProtoList; +//proto_list_next$1 head:Protocol tail:ProtoList = ProtoList; +//proto_http#4854 = Protocol; + +//cap_list_nil$0 = SmcCapList; +//cap_list_next$1 head:SmcCapability tail:SmcCapList = SmcCapList; +//cap_method_seqno#5371 = SmcCapability; +//cap_method_pubkey#71f4 = SmcCapability; +//cap_is_wallet#2177 = SmcCapability; +//cap_name#ff name:Text = SmcCapability; +// +td::Result> DnsInterface::EntryData::as_cell() const { + td::Ref res; + td::Status error; + data.visit(td::overloaded( + [&](const EntryDataText& text) { + block::gen::DNSRecord::Record_dns_text dns; + vm::CellBuilder cb; + vm::CellText::store(cb, text.text); + dns.x = vm::load_cell_slice_ref(cb.finalize()); + tlb::pack_cell(res, dns); + }, + [&](const EntryDataNextResolver& resolver) { + block::gen::DNSRecord::Record_dns_next_resolver dns; + vm::CellBuilder cb; + block::tlb::t_MsgAddressInt.store_std_address(cb, resolver.resolver.workchain, resolver.resolver.addr); + dns.resolver = vm::load_cell_slice_ref(cb.finalize()); + tlb::pack_cell(res, dns); + }, + [&](const EntryDataAdnlAddress& adnl_address) { + block::gen::DNSRecord::Record_dns_adnl_address dns; + dns.adnl_addr = adnl_address.adnl_address; + dns.flags = 0; + tlb::pack_cell(res, dns); + }, + [&](const EntryDataSmcAddress& smc_address) { + block::gen::DNSRecord::Record_dns_smc_address dns; + vm::CellBuilder cb; + block::tlb::t_MsgAddressInt.store_std_address(cb, smc_address.smc_address.workchain, + smc_address.smc_address.addr); + dns.smc_addr = vm::load_cell_slice_ref(cb.finalize()); + tlb::pack_cell(res, dns); + })); + if (error.is_error()) { + return error; + } + if (res.is_null()) { + return td::Status::Error("Entry data is emtpy"); + } + return res; + //dns_text#1eda _:Text = DNSRecord; + + //dns_next_resolver#ba93 resolver:MsgAddressInt = DNSRecord; // usually in record #-1 + //dns_adnl_address#ad01 adnl_addr:bits256 flags:(## 8) { flags <= 1 } proto_list:flags . 0?ProtoList = DNSRecord; // often in record #2 + + //dns_smc_address#9fd3 smc_addr:MsgAddressInt flags:(## 8) { flags <= 1 } cap_list:flags . 0?SmcCapList = DNSRecord; // often in record #1 +} + +td::Result DnsInterface::EntryData::from_cellslice(vm::CellSlice& cs) { + switch (block::gen::t_DNSRecord.get_tag(cs)) { + case block::gen::DNSRecord::dns_text: { + block::gen::DNSRecord::Record_dns_text dns; + tlb::unpack(cs, dns); + TRY_RESULT(text, vm::CellText::load(dns.x.write())); + return EntryData::text(std::move(text)); + } + case block::gen::DNSRecord::dns_next_resolver: { + block::gen::DNSRecord::Record_dns_next_resolver dns; + tlb::unpack(cs, dns); + ton::WorkchainId wc; + ton::StdSmcAddress addr; + if (!block::tlb::t_MsgAddressInt.extract_std_address(dns.resolver, wc, addr)) { + return td::Status::Error("Invalid address"); + } + return EntryData::next_resolver(block::StdAddress(wc, addr)); + } + case block::gen::DNSRecord::dns_adnl_address: { + block::gen::DNSRecord::Record_dns_adnl_address dns; + tlb::unpack(cs, dns); + return EntryData::adnl_address(dns.adnl_addr); + } + case block::gen::DNSRecord::dns_smc_address: { + block::gen::DNSRecord::Record_dns_smc_address dns; + tlb::unpack(cs, dns); + ton::WorkchainId wc; + ton::StdSmcAddress addr; + if (!block::tlb::t_MsgAddressInt.extract_std_address(dns.smc_addr, wc, addr)) { + return td::Status::Error("Invalid address"); + } + return EntryData::smc_address(block::StdAddress(wc, addr)); + } + } + return td::Status::Error("Unknown entry data"); +} + +td::Result> DnsInterface::resolve(td::Slice name, td::int32 category) const { + TRY_RESULT(raw_entries, resolve_raw(name, category)); + std::vector entries; + entries.reserve(raw_entries.size()); + for (auto& raw_entry : raw_entries) { + Entry entry; + entry.name = std::move(raw_entry.name); + entry.category = raw_entry.category; + auto cs = vm::load_cell_slice(raw_entry.data); + TRY_RESULT(data, EntryData::from_cellslice(cs)); + entry.data = std::move(data); + entries.push_back(std::move(entry)); + } + return entries; +} + +/* + External message structure: + [Bytes<512b>:signature] [UInt<32b>:seqno] [UInt<6b>:operation] + [Either b0: inline name (<= 58-x Bytes) or b1: reference-stored name) + x depends on operation + Use of 6-bit op instead of 32-bit allows to save 4 bytes for inline name + Inline [Name] structure: [UInt<6b>:length] [Bytes:data] + Operations (continuation of message): + 00 Contract initialization message (only if seqno = 0) (x=-) + 31 TSet: replace ENTIRE DOMAIN TABLE with the provided tree root cell (x=-) + [Cell<1r>:new_domains_table] + 51 OSet: replace owner public key with a new one (x=-) + [UInt<256b>:new_public_key] +*/ +// creation +td::Ref ManualDns::create(td::Ref data) { + return td::Ref(true, State{ton::SmartContractCode::dns_manual(), std::move(data)}); +} +td::Ref ManualDns::create(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id) { + return create(create_init_data_fast(public_key, wallet_id)); +} + +td::Result ManualDns::get_wallet_id() const { + return TRY_VM(get_wallet_id_or_throw()); +} +td::Result ManualDns::get_wallet_id_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)); +} + +td::Result> ManualDns::create_set_value_unsigned(td::int16 category, td::Slice name, + td::Ref data) const { + //11 VSet: set specified value to specified subdomain->category (x=2) + //[Int<16b>:category] [Name:subdomain] [Cell<1r>:value] + vm::CellBuilder cb; + cb.store_long(11, 6); + if (name.size() <= 58 - 2) { + cb.store_long(0, 1); + cb.store_long(category, 16); + cb.store_long(name.size(), 6); + cb.store_bytes(name); + } else { + cb.store_long(1, 1); + cb.store_long(category, 16); + cb.store_ref(vm::CellBuilder().store_bytes(name).finalize()); + } + cb.store_maybe_ref(std::move(data)); + return cb.finalize(); +} +td::Result> ManualDns::create_delete_value_unsigned(td::int16 category, td::Slice name) const { + //12 VDel: delete specified subdomain->category (x=2) + //[Int<16b>:category] [Name:subdomain] + vm::CellBuilder cb; + cb.store_long(12, 6); + if (name.size() <= 58 - 2) { + cb.store_long(0, 1); + cb.store_long(category, 16); + cb.store_long(name.size(), 6); + cb.store_bytes(name); + } else { + cb.store_long(1, 1); + cb.store_long(category, 16); + cb.store_ref(vm::CellBuilder().store_bytes(name).finalize()); + } + cb.store_long(0, 1); + return cb.finalize(); +} + +td::Result> ManualDns::create_delete_all_unsigned() const { + // 32 TDel: nullify ENTIRE DOMAIN TABLE (x=-) + vm::CellBuilder cb; + cb.store_long(32, 6); + cb.store_long(0, 1); + return cb.finalize(); +} + +td::Result> ManualDns::create_set_all_unsigned(td::Span entries) const { + vm::PrefixDictionary pdict(1023); + for (auto& action : entries) { + auto name_key = encode_name(action.name); + int zero_cnt = 0; + for (auto c : name_key) { + if (c == 0) { + zero_cnt++; + } + } + auto new_name_key = vm::load_cell_slice(vm::CellBuilder().store_long(zero_cnt, 7).store_bytes(name_key).finalize()); + auto ptr = new_name_key.data_bits(); + auto ptr_size = new_name_key.size(); + auto o_dict = pdict.lookup(ptr, ptr_size); + td::Ref dict_root; + if (o_dict.not_null()) { + o_dict->prefetch_maybe_ref(dict_root); + } + vm::Dictionary dict(dict_root, 16); + if (!action.data.value().is_null()) { + auto key = dict.integer_key(td::make_refint(action.category), 16); + dict.set_ref(key.bits(), 16, action.data.value()); + } + pdict.set(ptr, ptr_size, dict.get_root()); + } + + vm::CellBuilder cb; + cb.store_long(31, 6); + cb.store_long(1, 1); + + cb.store_maybe_ref(pdict.get_root_cell()); + + return cb.finalize(); +} + +//21 DSet: replace entire category dictionary of domain with provided (x=0) +//[Name:subdomain] [Cell<1r>:new_cat_table] +//22 DDel: delete entire category dictionary of specified domain (x=0) +//[Name:subdomain] +td::Result> ManualDns::create_delete_name_unsigned(td::Slice name) const { + vm::CellBuilder cb; + cb.store_long(22, 6); + if (name.size() <= 58) { + cb.store_long(0, 1); + cb.store_long(name.size(), 6); + cb.store_bytes(name); + } else { + cb.store_long(1, 1); + cb.store_ref(vm::CellBuilder().store_bytes(name).finalize()); + } + cb.store_long(0, 1); + return cb.finalize(); +} +td::Result> ManualDns::create_set_name_unsigned(td::Slice name, td::Span entries) const { + vm::CellBuilder cb; + cb.store_long(21, 6); + if (name.size() <= 58) { + cb.store_long(0, 1); + cb.store_long(name.size(), 6); + cb.store_bytes(name); + } else { + cb.store_long(1, 1); + cb.store_ref(vm::CellBuilder().store_bytes(name).finalize()); + } + + vm::Dictionary dict(16); + + for (auto& action : entries) { + if (action.data.value().is_null()) { + continue; + } + auto key = dict.integer_key(td::make_refint(action.category), 16); + dict.set_ref(key.bits(), 16, action.data.value()); + } + cb.store_maybe_ref(dict.get_root_cell()); + + return cb.finalize(); +} + +td::Result> ManualDns::prepare(td::Ref data, td::uint32 valid_until) const { + TRY_RESULT(wallet_id, get_wallet_id()); + auto hash = data->get_hash().as_slice().substr(28, 4).str(); + + vm::CellBuilder cb; + cb.store_long(wallet_id, 32).store_long(valid_until, 32); + //cb.store_bytes(hash); + cb.store_long(td::Random::secure_uint32(), 32); + cb.append_cellslice(vm::load_cell_slice(data)); + return cb.finalize(); +} + +td::Result> ManualDns::sign(const td::Ed25519::PrivateKey& private_key, td::Ref data) { + auto signature = private_key.sign(data->get_hash().as_slice()).move_as_ok(); + vm::CellBuilder cb; + cb.store_bytes(signature.as_slice()); + cb.append_cellslice(vm::load_cell_slice(data)); + return cb.finalize(); +} + +td::Result> ManualDns::create_init_query(const td::Ed25519::PrivateKey& private_key, + td::uint32 valid_until) const { + vm::CellBuilder cb; + cb.store_long(0, 6); + cb.store_long(0, 1); + + TRY_RESULT(prepared, prepare(cb.finalize(), valid_until)); + return sign(private_key, std::move(prepared)); +} + +td::Ref ManualDns::create_init_data_fast(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id) { + vm::CellBuilder cb; + cb.store_long(wallet_id, 32).store_long(0, 64).store_bytes(public_key.as_octet_string()); + CHECK(cb.store_maybe_ref({})); + CHECK(cb.store_maybe_ref({})); + return cb.finalize(); +} + +size_t ManualDns::get_max_name_size() const { + return 128; +} + +td::Result> ManualDns::resolve_raw(td::Slice name, td::int32 category_big) const { + return TRY_VM(resolve_raw_or_throw(name, category_big)); +} +td::Result> ManualDns::resolve_raw_or_throw(td::Slice name, + td::int32 category_big) const { + TRY_RESULT(category, td::narrow_cast_safe(category_big)); + if (name.size() > get_max_name_size()) { + return td::Status::Error("Name is too long"); + } + auto encoded_name = encode_name(name); + auto res = run_get_method( + "dnsresolve", + {vm::load_cell_slice_ref(vm::CellBuilder().store_bytes(encoded_name).finalize()), td::make_refint(category)}); + if (!res.success) { + return td::Status::Error("get method failed"); + } + std::vector vec; + auto data = res.stack.write().pop_maybe_cell(); + if (data.is_null()) { + return vec; + } + size_t prefix_size = res.stack.write().pop_smallint_range((int)encoded_name.size() * 8); + if (prefix_size % 8 != 0) { + return td::Status::Error("Prefix size is not divisible by 8"); + } + prefix_size /= 8; + if (prefix_size < encoded_name.size()) { + vec.push_back({decode_name(td::Slice(encoded_name).substr(0, prefix_size)), -1, data}); + } else { + if (category == 0) { + vm::Dictionary dict(std::move(data), 16); + dict.check_for_each([&](auto cs, auto x, auto y) { + td::BigInt256 cat; + cat.import_bits(x, y, true); + vec.push_back({name.str(), td::narrow_cast(cat.to_long()), cs->prefetch_ref()}); + return true; + }); + } else { + vec.push_back({name.str(), category, data}); + } + } + + return vec; +} + +td::Result> ManualDns::create_update_query(CombinedActions& combined) const { + if (combined.name.empty()) { + if (combined.actions.value().empty()) { + return create_delete_all_unsigned(); + } + return create_set_all_unsigned(combined.actions.value()); + } + if (combined.category == 0) { + if (!combined.actions) { + return create_delete_name_unsigned(encode_name(combined.name)); + } + return create_set_name_unsigned(encode_name(combined.name), combined.actions.value()); + } + CHECK(combined.actions.value().size() == 1); + auto& action = combined.actions.value()[0]; + if (action.data) { + return create_set_value_unsigned(action.category, encode_name(action.name), action.data.value()); + } else { + return create_delete_value_unsigned(action.category, encode_name(action.name)); + } +} + +td::Result> ManualDns::create_update_query(td::Ed25519::PrivateKey& pk, td::Span actions, + td::uint32 valid_until) const { + auto combined = combine_actions(actions); + std::vector> queries; + for (auto& c : combined) { + TRY_RESULT(q, create_update_query(c)); + queries.push_back(std::move(q)); + } + + td::Ref combined_query; + for (auto& query : td::reversed(queries)) { + if (combined_query.is_null()) { + combined_query = std::move(query); + } else { + auto next = vm::load_cell_slice(combined_query); + combined_query = vm::CellBuilder() + .append_cellslice(vm::load_cell_slice(query)) + .store_ref(vm::CellBuilder().append_cellslice(next).finalize()) + .finalize(); + } + } + + TRY_RESULT(prepared, prepare(std::move(combined_query), valid_until)); + return sign(pk, std::move(prepared)); +} + +std::string ManualDns::encode_name(td::Slice name) { + std::string res; + while (!name.empty()) { + auto pos = name.rfind('.'); + if (pos == name.npos) { + res += name.str(); + name = td::Slice(); + } else { + res += name.substr(pos + 1).str(); + name.truncate(pos); + } + res += '\0'; + } + return res; +} + +std::string ManualDns::decode_name(td::Slice name) { + std::string res; + if (!name.empty() && name.back() == 0) { + name.remove_suffix(1); + } + while (!name.empty()) { + auto pos = name.rfind('\0'); + if (!res.empty()) { + res += '.'; + } + if (pos == name.npos) { + res += name.str(); + name = td::Slice(); + } else { + res += name.substr(pos + 1).str(); + name.truncate(pos); + } + } + return res; +} + +std::string ManualDns::serialize_data(const EntryData& data) { + std::string res; + data.data.visit(td::overloaded([&](const ton::ManualDns::EntryDataText& text) { res = "UNSUPPORTED"; }, + [&](const ton::ManualDns::EntryDataNextResolver& resolver) { res = "UNSUPPORTED"; }, + [&](const ton::ManualDns::EntryDataAdnlAddress& adnl_address) { res = "UNSUPPORTED"; }, + [&](const ton::ManualDns::EntryDataSmcAddress& text) { res = "UNSUPPORTED"; })); + return res; +} + +td::Result> ManualDns::parse_data(td::Slice cmd) { + td::ConstParser parser(cmd); + parser.skip_whitespaces(); + auto type = parser.read_till(':'); + parser.advance(1); + if (type == "TEXT") { + return ManualDns::EntryData::text(parser.read_all().str()); + } else if (type == "DELETED") { + return {}; + } + return td::Status::Error(PSLICE() << "Unknown entry type: " << type); +} + +td::Result ManualDns::parse_line(td::Slice cmd) { + // Cmd = + // set name category data | + // delete.name name | + // delete.all + // data = + // TEXT: | + // DELETED + td::ConstParser parser(cmd); + auto type = parser.read_word(); + if (type == "set") { + auto name = parser.read_word(); + auto category_str = parser.read_word(); + TRY_RESULT(category, td::to_integer_safe(category_str)); + TRY_RESULT(data, parse_data(parser.read_all())); + return ManualDns::ActionExt{name.str(), category, std::move(data)}; + } else if (type == "delete.name") { + auto name = parser.read_word(); + if (name.empty()) { + return td::Status::Error("name is empty"); + } + return ManualDns::ActionExt{name.str(), 0, {}}; + } else if (type == "delete.all") { + return ManualDns::ActionExt{"", 0, {}}; + } + return td::Status::Error(PSLICE() << "Unknown command: " << type); +} + +td::Result> ManualDns::parse(td::Slice cmd) { + auto lines = td::full_split(cmd, '\n'); + std::vector res; + res.reserve(lines.size()); + for (auto& line : lines) { + td::ConstParser parser(line); + parser.skip_whitespaces(); + if (parser.empty()) { + continue; + } + TRY_RESULT(action, parse_line(parser.read_all())); + res.push_back(std::move(action)); + } + return res; +} + +} // namespace ton diff --git a/crypto/smc-envelope/ManualDns.h b/crypto/smc-envelope/ManualDns.h new file mode 100644 index 00000000..08db188e --- /dev/null +++ b/crypto/smc-envelope/ManualDns.h @@ -0,0 +1,339 @@ +/* + 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 2019-2020 Telegram Systems LLP +*/ +#pragma once +#include "td/utils/Variant.h" +#include "td/utils/Status.h" +#include "vm/cells/Cell.h" +#include "vm/cells/CellSlice.h" +#include "vm/cells/CellString.h" + +#include "smc-envelope/SmartContract.h" + +#include "Ed25519.h" + +#include + +namespace ton { +class DnsInterface { + public: + struct EntryDataText { + std::string text; + bool operator==(const EntryDataText& other) const { + return text == other.text; + } + }; + + struct EntryDataNextResolver { + block::StdAddress resolver; + bool operator==(const EntryDataNextResolver& other) const { + return resolver == other.resolver; + } + }; + + struct EntryDataAdnlAddress { + ton::Bits256 adnl_address; + // TODO: proto + bool operator==(const EntryDataAdnlAddress& other) const { + return adnl_address == other.adnl_address; + } + }; + + struct EntryDataSmcAddress { + block::StdAddress smc_address; + bool operator==(const EntryDataSmcAddress& other) const { + return smc_address == other.smc_address; + } + // TODO: capability + }; + + struct EntryData { + enum Type { Empty, Text, NextResolver, AdnlAddress, SmcAddress } type{Empty}; + td::Variant data; + + static EntryData text(std::string text) { + return {Text, EntryDataText{text}}; + } + static EntryData next_resolver(block::StdAddress resolver) { + return {NextResolver, EntryDataNextResolver{resolver}}; + } + static EntryData adnl_address(ton::Bits256 adnl_address) { + return {AdnlAddress, EntryDataAdnlAddress{adnl_address}}; + } + static EntryData smc_address(block::StdAddress smc_address) { + return {SmcAddress, EntryDataSmcAddress{smc_address}}; + } + + bool operator==(const EntryData& other) const { + return data == other.data; + } + friend td::StringBuilder& operator<<(td::StringBuilder& sb, const EntryData& data) { + switch (data.type) { + case Type::Empty: + return sb << ""; + case Type::Text: + return sb << "text{" << data.data.get().text << "}"; + case Type::NextResolver: + return sb << "next{" << data.data.get().resolver.rserialize() << "}"; + case Type::AdnlAddress: + return sb << "adnl{" << data.data.get().adnl_address.to_hex() << "}"; + case Type::SmcAddress: + return sb << "smc{" << data.data.get().smc_address.rserialize() << "}"; + } + return sb << ""; + } + + td::Result> as_cell() const; + static td::Result from_cellslice(vm::CellSlice& cs); + }; + + struct Entry { + std::string name; + td::int16 category; + EntryData data; + auto key() const { + return std::tie(name, category); + } + bool operator<(const Entry& other) const { + return key() < other.key(); + } + bool operator==(const Entry& other) const { + return key() == other.key() && data == other.data; + } + friend td::StringBuilder& operator<<(td::StringBuilder& sb, const Entry& entry) { + sb << entry.name << ":" << entry.category << ":" << entry.data; + return sb; + } + }; + struct RawEntry { + std::string name; + td::int16 category; + td::Ref data; + }; + + struct ActionExt { + std::string name; + td::int16 category; + td::optional data; + static td::Result parse(td::Slice); + }; + + struct Action { + std::string name; + td::int16 category; + td::optional> data; + + bool does_create_category() const { + CHECK(!name.empty()); + CHECK(category != 0); + return static_cast(data); + } + bool does_change_empty() const { + CHECK(!name.empty()); + CHECK(category != 0); + return static_cast(data) && data.value().not_null(); + } + void make_non_empty() { + CHECK(!name.empty()); + CHECK(category != 0); + if (!data) { + data = td::Ref(); + } + } + friend td::StringBuilder& operator<<(td::StringBuilder& sb, const Action& action) { + sb << action.name << ":" << action.category << ":"; + if (action.data) { + if (action.data.value().is_null()) { + sb << ""; + } else { + sb << ""; + } + } else { + sb << ""; + } + return sb; + } + }; + + virtual ~DnsInterface() { + } + virtual size_t get_max_name_size() const = 0; + virtual td::Result> resolve_raw(td::Slice name, td::int32 category) const = 0; + virtual td::Result> create_update_query( + td::Ed25519::PrivateKey& pk, td::Span actions, + td::uint32 valid_until = std::numeric_limits::max()) const = 0; + + td::Result> resolve(td::Slice name, td::int32 category) const; +}; + +class ManualDns : public ton::SmartContract, public DnsInterface { + public: + ManualDns(State state) : SmartContract(std::move(state)) { + } + + ManualDns* make_copy() const override { + return new ManualDns{state_}; + } + + // creation + static td::Ref create(State state) { + return td::Ref(true, std::move(state)); + } + static td::Ref create(td::Ref data = {}); + static td::Ref create(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id); + + static std::string serialize_data(const EntryData& data); + static td::Result> parse_data(td::Slice cmd); + static td::Result parse_line(td::Slice cmd); + static td::Result> parse(td::Slice cmd); + + td::Ref create_init_data(const td::Ed25519::PublicKey& public_key, td::uint32 valid_until) const { + return create_init_data_fast(public_key, valid_until); + } + + td::Result get_wallet_id() const; + td::Result get_wallet_id_or_throw() const; + + td::Result> create_set_value_unsigned(td::int16 category, td::Slice name, + td::Ref data) const; + td::Result> create_delete_value_unsigned(td::int16 category, td::Slice name) const; + td::Result> create_delete_all_unsigned() const; + td::Result> create_set_all_unsigned(td::Span entries) const; + td::Result> create_delete_name_unsigned(td::Slice name) const; + td::Result> create_set_name_unsigned(td::Slice name, td::Span entries) const; + + td::Result> prepare(td::Ref data, td::uint32 valid_until) const; + + static td::Result> sign(const td::Ed25519::PrivateKey& private_key, td::Ref data); + static td::Ref create_init_data_fast(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id); + + size_t get_max_name_size() const override; + td::Result> resolve_raw(td::Slice name, td::int32 category_big) const override; + td::Result> resolve_raw_or_throw(td::Slice name, td::int32 category_big) const; + + td::Result> create_init_query( + const td::Ed25519::PrivateKey& private_key, + td::uint32 valid_until = std::numeric_limits::max()) const; + td::Result> create_update_query( + td::Ed25519::PrivateKey& pk, td::Span actions, + td::uint32 valid_until = std::numeric_limits::max()) const override; + + static std::string encode_name(td::Slice name); + static std::string decode_name(td::Slice name); + + template + struct CombinedActions { + std::string name; + td::int16 category{0}; + td::optional> actions; + friend td::StringBuilder& operator<<(td::StringBuilder& sb, const CombinedActions& action) { + sb << action.name << ":" << action.category << ":"; + if (action.actions) { + sb << "" << action.actions.value().size(); + } else { + sb << ""; + } + return sb; + } + }; + + template + static std::vector> combine_actions(td::Span actions) { + struct Info { + std::set known_category; + std::vector actions; + bool closed{false}; + bool non_empty{false}; + }; + + std::map mp; + std::vector> res; + for (auto& action : td::reversed(actions)) { + if (action.name.empty()) { + CombinedActions set_all; + set_all.actions = std::vector(); + for (auto& it : mp) { + for (auto& e : it.second.actions) { + if (e.does_create_category()) { + set_all.actions.value().push_back(std::move(e)); + } + } + } + res.push_back(std::move(set_all)); + return res; + } + + Info& info = mp[action.name]; + if (info.closed) { + continue; + } + if (action.category != 0 && action.does_create_category()) { + info.non_empty = true; + } + if (!info.known_category.insert(action.category).second) { + continue; + } + if (action.category == 0) { + info.closed = true; + auto old_actions = std::move(info.actions); + bool is_empty = true; + for (auto& action : old_actions) { + if (is_empty && action.does_create_category()) { + info.actions.push_back(std::move(action)); + is_empty = false; + } else if (!is_empty && action.does_change_empty()) { + info.actions.push_back(std::move(action)); + } + } + } else { + info.actions.push_back(std::move(action)); + } + } + + for (auto& it : mp) { + auto& info = it.second; + if (info.closed) { + CombinedActions ca; + ca.name = it.first; + ca.category = 0; + if (!info.actions.empty() || info.non_empty) { + ca.actions = std::move(info.actions); + } + res.push_back(std::move(ca)); + } else { + bool need_non_empty = info.non_empty; + for (auto& a : info.actions) { + if (need_non_empty) { + a.make_non_empty(); + need_non_empty = false; + } + CombinedActions ca; + ca.name = a.name; + ca.category = a.category; + ca.actions = std::vector(); + ca.actions.value().push_back(std::move(a)); + res.push_back(ca); + } + } + } + return res; + } + td::Result> create_update_query(CombinedActions& combined) const; +}; + +} // namespace ton diff --git a/crypto/smc-envelope/SmartContract.cpp b/crypto/smc-envelope/SmartContract.cpp index 02964a7c..17477de9 100644 --- a/crypto/smc-envelope/SmartContract.cpp +++ b/crypto/smc-envelope/SmartContract.cpp @@ -14,7 +14,7 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #include "SmartContract.h" @@ -24,7 +24,7 @@ #include "block/block-auto.h" #include "vm/cellslice.h" #include "vm/cp0.h" -#include "vm/continuation.h" +#include "vm/vm.h" #include "td/utils/crypto.h" diff --git a/crypto/smc-envelope/SmartContract.h b/crypto/smc-envelope/SmartContract.h index d5864361..14b5b947 100644 --- a/crypto/smc-envelope/SmartContract.h +++ b/crypto/smc-envelope/SmartContract.h @@ -14,13 +14,13 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "vm/cells.h" #include "vm/stack.hpp" -#include "vm/continuation.h" +#include "vm/vm.h" #include "td/utils/optional.h" #include "td/utils/crypto.h" diff --git a/crypto/smc-envelope/SmartContractCode.cpp b/crypto/smc-envelope/SmartContractCode.cpp index e61cab24..c97a689c 100644 --- a/crypto/smc-envelope/SmartContractCode.cpp +++ b/crypto/smc-envelope/SmartContractCode.cpp @@ -14,7 +14,7 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #include "SmartContractCode.h" @@ -25,6 +25,11 @@ namespace ton { namespace { +constexpr static int WALLET_REVISION = 2; +constexpr static int WALLET2_REVISION = 2; +constexpr static int WALLET3_REVISION = 2; +constexpr static int HIGHLOAD_WALLET_REVISION = 2; +constexpr static int HIGHLOAD_WALLET2_REVISION = 2; const auto& get_map() { static auto map = [] { std::map, std::less<>> map; @@ -36,6 +41,58 @@ const auto& get_map() { #include "smartcont/auto/simple-wallet-code.cpp" #include "smartcont/auto/wallet-code.cpp" #include "smartcont/auto/highload-wallet-code.cpp" +#include "smartcont/auto/highload-wallet-v2-code.cpp" +#include "smartcont/auto/dns-manual-code.cpp" + + with_tvm_code("highload-wallet-r1", + "te6ccgEBBgEAhgABFP8A9KQT9KDyyAsBAgEgAgMCAUgEBQC88oMI1xgg0x/TH9Mf+CMTu/Jj7UTQ0x/TH9P/" + "0VEyuvKhUUS68qIE+QFUEFX5EPKj9ATR+AB/jhghgBD0eG+hb6EgmALTB9QwAfsAkTLiAbPmWwGkyMsfyx/L/" + "8ntVAAE0DAAEaCZL9qJoa4WPw=="); + with_tvm_code("highload-wallet-r2", + "te6ccgEBCAEAmQABFP8A9KQT9LzyyAsBAgEgAgMCAUgEBQC88oMI1xgg0x/TH9Mf+CMTu/Jj7UTQ0x/TH9P/" + "0VEyuvKhUUS68qIE+QFUEFX5EPKj9ATR+AB/jhghgBD0eG+hb6EgmALTB9QwAfsAkTLiAbPmWwGkyMsfyx/L/" + "8ntVAAE0DACAUgGBwAXuznO1E0NM/MdcL/4ABG4yX7UTQ1wsfg="); + with_tvm_code("highload-wallet-v2-r1", + "te6ccgEBBwEA1gABFP8A9KQT9KDyyAsBAgEgAgMCAUgEBQHu8oMI1xgg0x/TP/gjqh9TILnyY+1E0NMf0z/T//" + "QE0VNggED0Dm+hMfJgUXO68qIH+QFUEIf5EPKjAvQE0fgAf44YIYAQ9HhvoW+" + "hIJgC0wfUMAH7AJEy4gGz5luDJaHIQDSAQPRDiuYxyBLLHxPLP8v/9ADJ7VQGAATQMABBoZfl2omhpj5jpn+n/" + "mPoCaKkQQCB6BzfQmMktv8ld0fFADgggED0lm+hb6EyURCUMFMDud4gkzM2AZIyMOKz"); + with_tvm_code("highload-wallet-v2-r2", + "te6ccgEBCQEA6QABFP8A9KQT9LzyyAsBAgEgAgMCAUgEBQHu8oMI1xgg0x/TP/gjqh9TILnyY+1E0NMf0z/T//" + "QE0VNggED0Dm+hMfJgUXO68qIH+QFUEIf5EPKjAvQE0fgAf44YIYAQ9HhvoW+" + "hIJgC0wfUMAH7AJEy4gGz5luDJaHIQDSAQPRDiuYxyBLLHxPLP8v/9ADJ7VQIAATQMAIBIAYHABe9nOdqJoaa+Y64X/" + "wAQb5fl2omhpj5jpn+n/mPoCaKkQQCB6BzfQmMktv8ld0fFAA4IIBA9JZvoW+hMlEQlDBTA7neIJMzNgGSMjDisw=="); + with_tvm_code("simple-wallet-r1", + "te6ccgEEAQEAAAAAUwAAov8AIN0gggFMl7qXMO1E0NcLH+Ck8mCBAgDXGCDXCx/tRNDTH9P/" + "0VESuvKhIvkBVBBE+RDyovgAAdMfMSDXSpbTB9QC+wDe0aTIyx/L/8ntVA=="); + with_tvm_code("simple-wallet-r2", + "te6ccgEBAQEAXwAAuv8AIN0gggFMl7ohggEznLqxnHGw7UTQ0x/XC//jBOCk8mCBAgDXGCDXCx/tRNDTH9P/" + "0VESuvKhIvkBVBBE+RDyovgAAdMfMSDXSpbTB9QC+wDe0aTIyx/L/8ntVA=="); + with_tvm_code("wallet-r1", + "te6ccgEBAQEAVwAAqv8AIN0gggFMl7qXMO1E0NcLH+Ck8mCDCNcYINMf0x8B+CO78mPtRNDTH9P/0VExuvKhA/" + "kBVBBC+RDyovgAApMg10qW0wfUAvsA6NGkyMsfy//J7VQ="); + with_tvm_code("wallet-r2", + "te6ccgEBAQEAYwAAwv8AIN0gggFMl7ohggEznLqxnHGw7UTQ0x/XC//jBOCk8mCDCNcYINMf0x8B+CO78mPtRNDTH9P/" + "0VExuvKhA/kBVBBC+RDyovgAApMg10qW0wfUAvsA6NGkyMsfy//J7VQ="); + with_tvm_code("wallet3-r1", + "te6ccgEBAQEAYgAAwP8AIN0gggFMl7qXMO1E0NcLH+Ck8mCDCNcYINMf0x/TH/gjE7vyY+1E0NMf0x/T/" + "9FRMrryoVFEuvKiBPkBVBBV+RDyo/gAkyDXSpbTB9QC+wDo0QGkyMsfyx/L/8ntVA=="); + with_tvm_code("wallet3-r2", + "te6ccgEBAQEAcQAA3v8AIN0gggFMl7ohggEznLqxn3Gw7UTQ0x/THzHXC//jBOCk8mCDCNcYINMf0x/TH/gjE7vyY+1E0NMf0x/" + "T/9FRMrryoVFEuvKiBPkBVBBV+RDyo/gAkyDXSpbTB9QC+wDo0QGkyMsfyx/L/8ntVA=="); + auto check_revision = [&](td::Slice name, td::int32 default_revision) { + auto it = map.find(name); + CHECK(it != map.end()); + auto other_it = map.find(PSLICE() << name << "-r" << default_revision); + CHECK(other_it != map.end()); + CHECK(it->second->get_hash() == other_it->second->get_hash()); + }; + check_revision("highload-wallet", HIGHLOAD_WALLET_REVISION); + check_revision("highload-wallet-v2", HIGHLOAD_WALLET2_REVISION); + + //check_revision("simple-wallet", WALLET_REVISION); + //check_revision("wallet", WALLET2_REVISION); + //check_revision("wallet3", WALLET3_REVISION); return map; }(); return map; @@ -46,7 +103,7 @@ 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::ref " << name); } return it->second; } @@ -54,20 +111,47 @@ td::Ref SmartContractCode::multisig() { auto res = load("multisig").move_as_ok(); return res; } -td::Ref SmartContractCode::wallet() { - auto res = load("wallet").move_as_ok(); +td::Ref SmartContractCode::wallet3(int revision) { + if (revision == 0) { + revision = WALLET3_REVISION; + } + auto res = load(PSLICE() << "wallet3-r" << revision).move_as_ok(); return res; } -td::Ref SmartContractCode::simple_wallet() { - auto res = load("simple-wallet").move_as_ok(); +td::Ref SmartContractCode::wallet(int revision) { + if (revision == 0) { + revision = WALLET2_REVISION; + } + auto res = load(PSLICE() << "wallet-r" << revision).move_as_ok(); + return res; +} +td::Ref SmartContractCode::simple_wallet(int revision) { + if (revision == 0) { + revision = WALLET_REVISION; + } + auto res = load(PSLICE() << "simple-wallet-r" << revision).move_as_ok(); return res; } td::Ref SmartContractCode::simple_wallet_ext() { static auto res = load("simple-wallet-ext").move_as_ok(); return res; } -td::Ref SmartContractCode::highload_wallet() { - static auto res = load("highload-wallet").move_as_ok(); +td::Ref SmartContractCode::highload_wallet(int revision) { + if (revision == 0) { + revision = HIGHLOAD_WALLET_REVISION; + } + auto res = load(PSLICE() << "highload-wallet-r" << revision).move_as_ok(); + return res; +} +td::Ref SmartContractCode::highload_wallet_v2(int revision) { + if (revision == 0) { + revision = HIGHLOAD_WALLET2_REVISION; + } + auto res = load(PSLICE() << "highload-wallet-v2-r" << revision).move_as_ok(); + return res; +} +td::Ref SmartContractCode::dns_manual() { + static auto res = load("dns-manual").move_as_ok(); return res; } } // namespace ton diff --git a/crypto/smc-envelope/SmartContractCode.h b/crypto/smc-envelope/SmartContractCode.h index 0c9e4764..439dc868 100644 --- a/crypto/smc-envelope/SmartContractCode.h +++ b/crypto/smc-envelope/SmartContractCode.h @@ -14,7 +14,7 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #include "vm/cells.h" @@ -23,9 +23,12 @@ 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 wallet3(int revision = 0); + static td::Ref wallet(int revision = 0); + static td::Ref simple_wallet(int revision = 0); static td::Ref simple_wallet_ext(); - static td::Ref highload_wallet(); + static td::Ref highload_wallet(int revision = 0); + static td::Ref highload_wallet_v2(int revision = 0); + static td::Ref dns_manual(); }; } // namespace ton diff --git a/crypto/smc-envelope/TestGiver.cpp b/crypto/smc-envelope/TestGiver.cpp index 2d44d730..f9ed60bf 100644 --- a/crypto/smc-envelope/TestGiver.cpp +++ b/crypto/smc-envelope/TestGiver.cpp @@ -14,7 +14,7 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #include "TestGiver.h" #include "GenericAccount.h" @@ -35,14 +35,23 @@ vm::CellHash TestGiver::get_init_code_hash() noexcept { //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 { +td::Ref TestGiver::make_a_gift_message_static(td::uint32 seqno, td::Span gifts) noexcept { + CHECK(gifts.size() <= max_gifts_size); + 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(); + cb.store_long(seqno, 32); + + for (auto& gift : gifts) { + td::int32 send_mode = 1; + auto gramms = gift.gramms; + vm::CellBuilder cbi; + GenericAccount::store_int_message(cbi, gift.destination, gramms); + store_gift_message(cbi, gift); + auto message_inner = cbi.finalize(); + cb.store_long(send_mode, 8).store_ref(std::move(message_inner)); + } + + return cb.finalize(); } td::Result TestGiver::get_seqno() const { diff --git a/crypto/smc-envelope/TestGiver.h b/crypto/smc-envelope/TestGiver.h index 210b6912..b51ac7db 100644 --- a/crypto/smc-envelope/TestGiver.h +++ b/crypto/smc-envelope/TestGiver.h @@ -14,25 +14,38 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "SmartContract.h" +#include "smc-envelope/WalletInterface.h" #include "block/block.h" #include "vm/cells/CellString.h" namespace ton { -class TestGiver : public SmartContract { +class TestGiver : public SmartContract, public WalletInterface { public: explicit TestGiver(State state) : ton::SmartContract(std::move(state)) { } + TestGiver() : ton::SmartContract({}) { + } static constexpr unsigned max_message_size = vm::CellString::max_bytes; + static constexpr unsigned max_gifts_size = 1; 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; + static td::Ref make_a_gift_message_static(td::uint32 seqno, td::Span) noexcept; td::Result get_seqno() const; + using WalletInterface::get_init_message; + td::Result> make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 valid_until, + td::Span gifts) const override { + TRY_RESULT(seqno, get_seqno()); + return make_a_gift_message_static(seqno, gifts); + } + size_t get_max_gifts_size() const override { + return max_gifts_size; + } + private: td::Result get_seqno_or_throw() const; }; diff --git a/crypto/smc-envelope/TestWallet.cpp b/crypto/smc-envelope/TestWallet.cpp index 1edf59f9..6249da09 100644 --- a/crypto/smc-envelope/TestWallet.cpp +++ b/crypto/smc-envelope/TestWallet.cpp @@ -13,17 +13,19 @@ 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 +Copyright 2017-2020 Telegram Systems LLP */ #include "TestWallet.h" #include "GenericAccount.h" +#include "SmartContractCode.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(); +td::Ref TestWallet::get_init_state(const td::Ed25519::PublicKey& public_key, td::int32 revision) noexcept { + auto code = get_init_code(revision); auto data = get_init_data(public_key); return GenericAccount::get_init_state(std::move(code), std::move(data)); } @@ -35,42 +37,46 @@ td::Ref TestWallet::get_init_message(const td::Ed25519::PrivateKey& pr 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; - } +td::Ref TestWallet::make_a_gift_message_static(const td::Ed25519::PrivateKey& private_key, td::uint32 seqno, + td::Span gifts) noexcept { + CHECK(gifts.size() <= max_gifts_size); + 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(); + cb.store_long(seqno, 32); + + for (auto& gift : gifts) { + td::int32 send_mode = 3; + auto gramms = gift.gramms; + if (gramms == -1) { + gramms = 0; + send_mode += 128; + } + vm::CellBuilder cbi; + GenericAccount::store_int_message(cbi, gift.destination, gramms); + store_gift_message(cbi, gift); + auto message_inner = cbi.finalize(); + cb.store_long(send_mode, 8).store_ref(std::move(message_inner)); + } + + auto message_outer = cb.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; +td::Ref TestWallet::get_init_code(td::int32 revision) noexcept { + return ton::SmartContractCode::simple_wallet(revision); } vm::CellHash TestWallet::get_init_code_hash() noexcept { return get_init_code()->get_hash(); } +td::Ref TestWallet::get_data(const td::Ed25519::PublicKey& public_key, td::uint32 seqno) noexcept { + return vm::CellBuilder().store_long(seqno, 32).store_bytes(public_key.as_octet_string()).finalize(); +} + 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(); + return get_data(public_key, 0); } td::Result TestWallet::get_seqno() const { @@ -88,4 +94,20 @@ td::Result TestWallet::get_seqno_or_throw() const { return static_cast(seqno); } +td::Result TestWallet::get_public_key() const { + return TRY_VM(get_public_key_or_throw()); +} + +td::Result TestWallet::get_public_key_or_throw() const { + if (state_.data.is_null()) { + return td::Status::Error("data is null"); + } + //FIXME use get method + auto cs = vm::load_cell_slice(state_.data); + cs.skip_first(32); + td::SecureString res(td::Ed25519::PublicKey::LENGTH); + cs.fetch_bytes(res.as_mutable_slice().ubegin(), td::narrow_cast(res.size())); + return td::Ed25519::PublicKey(std::move(res)); +} + } // namespace ton diff --git a/crypto/smc-envelope/TestWallet.h b/crypto/smc-envelope/TestWallet.h index 161aef58..46891aa9 100644 --- a/crypto/smc-envelope/TestWallet.h +++ b/crypto/smc-envelope/TestWallet.h @@ -14,35 +14,53 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "smc-envelope/SmartContract.h" +#include "smc-envelope/WalletInterface.h" #include "vm/cells.h" #include "Ed25519.h" #include "block/block.h" #include "vm/cells/CellString.h" namespace ton { -class TestWallet : public ton::SmartContract { +class TestWallet : public ton::SmartContract, public WalletInterface { public: explicit TestWallet(State state) : ton::SmartContract(std::move(state)) { } + explicit TestWallet(const td::Ed25519::PublicKey& public_key, td::uint32 seqno) + : TestWallet(State{get_init_code(), get_data(public_key, seqno)}) { + } static constexpr unsigned max_message_size = vm::CellString::max_bytes; - static td::Ref get_init_state(const td::Ed25519::PublicKey& public_key) noexcept; + static constexpr unsigned max_gifts_size = 1; + static td::Ref get_init_state(const td::Ed25519::PublicKey& public_key, td::int32 revision = 0) 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 make_a_gift_message_static(const td::Ed25519::PrivateKey& private_key, td::uint32 seqno, + td::Span gifts) noexcept; - static td::Ref get_init_code() noexcept; + static td::Ref get_init_code(td::int32 revision = 0) noexcept; static vm::CellHash get_init_code_hash() noexcept; + static td::Ref get_data(const td::Ed25519::PublicKey& public_key, td::uint32 seqno) noexcept; static td::Ref get_init_data(const td::Ed25519::PublicKey& public_key) noexcept; td::Result get_seqno() const; + using WalletInterface::get_init_message; + td::Result> make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 valid_until, + td::Span gifts) const override { + TRY_RESULT(seqno, get_seqno()); + return make_a_gift_message_static(private_key, seqno, gifts); + } + size_t get_max_gifts_size() const override { + return max_gifts_size; + } + + td::Result get_public_key() const override; + private: td::Result get_seqno_or_throw() const; + td::Result get_public_key_or_throw() const; }; } // namespace ton diff --git a/crypto/smc-envelope/Wallet.cpp b/crypto/smc-envelope/Wallet.cpp index 61958ed9..e354a7a0 100644 --- a/crypto/smc-envelope/Wallet.cpp +++ b/crypto/smc-envelope/Wallet.cpp @@ -14,10 +14,11 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #include "Wallet.h" #include "GenericAccount.h" +#include "SmartContractCode.h" #include "vm/boc.h" #include "vm/cells/CellString.h" @@ -26,8 +27,8 @@ #include namespace ton { -td::Ref Wallet::get_init_state(const td::Ed25519::PublicKey& public_key) noexcept { - auto code = get_init_code(); +td::Ref Wallet::get_init_state(const td::Ed25519::PublicKey& public_key, td::int32 revision) noexcept { + auto code = get_init_code(revision); auto data = get_init_data(public_key); return GenericAccount::get_init_state(std::move(code), std::move(data)); } @@ -43,46 +44,45 @@ td::Ref Wallet::get_init_message(const td::Ed25519::PrivateKey& privat } 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(); + td::uint32 valid_until, td::Span gifts) noexcept { + CHECK(gifts.size() <= max_gifts_size); - auto message_outer = vm::CellBuilder() - .store_long(seqno, 32) - .store_long(valid_until, 32) - .store_long(send_mode, 8) - .store_ref(message_inner) - .finalize(); + vm::CellBuilder cb; + cb.store_long(seqno, 32).store_long(valid_until, 32); + + for (auto& gift : gifts) { + td::int32 send_mode = 3; + auto gramms = gift.gramms; + if (gramms == -1) { + gramms = 0; + send_mode += 128; + } + vm::CellBuilder cbi; + GenericAccount::store_int_message(cbi, gift.destination, gramms); + store_gift_message(cbi, gift); + auto message_inner = cbi.finalize(); + cb.store_long(send_mode, 8).store_ref(std::move(message_inner)); + } + + auto message_outer = cb.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; +td::Ref Wallet::get_init_code(td::int32 revision) noexcept { + return SmartContractCode::wallet(revision); } vm::CellHash Wallet::get_init_code_hash() noexcept { return get_init_code()->get_hash(); } +td::Ref Wallet::get_data(const td::Ed25519::PublicKey& public_key, td::uint32 seqno) noexcept { + return vm::CellBuilder().store_long(seqno, 32).store_bytes(public_key.as_octet_string()).finalize(); +} + 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(); + return get_data(public_key, 0); } td::Result Wallet::get_seqno() const { @@ -97,4 +97,20 @@ td::Result Wallet::get_seqno_or_throw() const { return static_cast(vm::load_cell_slice(state_.data).fetch_ulong(32)); } +td::Result Wallet::get_public_key() const { + return TRY_VM(get_public_key_or_throw()); +} + +td::Result Wallet::get_public_key_or_throw() const { + if (state_.data.is_null()) { + return td::Status::Error("data is null"); + } + //FIXME use get method + auto cs = vm::load_cell_slice(state_.data); + cs.skip_first(32); + td::SecureString res(td::Ed25519::PublicKey::LENGTH); + cs.fetch_bytes(res.as_mutable_slice().ubegin(), td::narrow_cast(res.size())); + return td::Ed25519::PublicKey(std::move(res)); +} + } // namespace ton diff --git a/crypto/smc-envelope/Wallet.h b/crypto/smc-envelope/Wallet.h index 7cd33c81..2299be7c 100644 --- a/crypto/smc-envelope/Wallet.h +++ b/crypto/smc-envelope/Wallet.h @@ -14,35 +14,53 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "smc-envelope/SmartContract.h" +#include "smc-envelope/WalletInterface.h" #include "vm/cells.h" #include "Ed25519.h" #include "block/block.h" #include "vm/cells/CellString.h" namespace ton { -class Wallet : ton::SmartContract { +class Wallet : ton::SmartContract, public WalletInterface { public: explicit Wallet(State state) : ton::SmartContract(std::move(state)) { } + explicit Wallet(const td::Ed25519::PublicKey& public_key, td::uint32 seqno) + : Wallet(State{get_init_code(), get_data(public_key, seqno)}) { + } static constexpr unsigned max_message_size = vm::CellString::max_bytes; - static td::Ref get_init_state(const td::Ed25519::PublicKey& public_key) noexcept; + static constexpr unsigned max_gifts_size = 4; + static td::Ref get_init_state(const td::Ed25519::PublicKey& public_key, td::int32 revision = 0) 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; + td::uint32 valid_until, td::Span gifts) noexcept; - static td::Ref get_init_code() noexcept; + static td::Ref get_init_code(td::int32 revision = 0) noexcept; static vm::CellHash get_init_code_hash() noexcept; static td::Ref get_init_data(const td::Ed25519::PublicKey& public_key) noexcept; + static td::Ref get_data(const td::Ed25519::PublicKey& public_key, td::uint32 seqno) noexcept; td::Result get_seqno() const; + using WalletInterface::get_init_message; + td::Result> make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 valid_until, + td::Span gifts) const override { + TRY_RESULT(seqno, get_seqno()); + return make_a_gift_message(private_key, seqno, valid_until, gifts); + } + size_t get_max_gifts_size() const override { + return max_gifts_size; + } + + td::Result get_public_key() const override; + private: td::Result get_seqno_or_throw() const; + td::Result get_public_key_or_throw() const; }; } // namespace ton diff --git a/crypto/smc-envelope/WalletInterface.h b/crypto/smc-envelope/WalletInterface.h new file mode 100644 index 00000000..e0e439c3 --- /dev/null +++ b/crypto/smc-envelope/WalletInterface.h @@ -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-2020 Telegram Systems LLP +*/ +#pragma once + +#include "td/utils/common.h" +#include "Ed25519.h" +#include "block/block.h" +#include "vm/cells/CellString.h" + +#include "SmartContract.h" + +namespace ton { +class WalletInterface { + public: + struct Gift { + block::StdAddress destination; + td::int64 gramms; + bool is_encrypted{false}; + std::string message; + }; + + virtual ~WalletInterface() { + } + + virtual size_t get_max_gifts_size() const = 0; + virtual td::Result> make_a_gift_message(const td::Ed25519::PrivateKey &private_key, + td::uint32 valid_until, td::Span gifts) const = 0; + virtual td::Result get_public_key() const { + return td::Status::Error("TODO"); + } + + td::Result> get_init_message(const td::Ed25519::PrivateKey &private_key, + td::uint32 valid_until = std::numeric_limits::max()) { + return make_a_gift_message(private_key, valid_until, {}); + } + static void store_gift_message(vm::CellBuilder &cb, const Gift &gift) { + if (gift.is_encrypted) { + cb.store_long(1, 32); + } else { + cb.store_long(0, 32); + } + vm::CellString::store(cb, gift.message, 35 * 8).ensure(); + } +}; + +} // namespace ton diff --git a/crypto/smc-envelope/WalletV3.cpp b/crypto/smc-envelope/WalletV3.cpp index db39c725..39b9b4a9 100644 --- a/crypto/smc-envelope/WalletV3.cpp +++ b/crypto/smc-envelope/WalletV3.cpp @@ -14,10 +14,11 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #include "WalletV3.h" #include "GenericAccount.h" +#include "SmartContractCode.h" #include "vm/boc.h" #include "vm/cells/CellString.h" @@ -26,76 +27,70 @@ #include namespace ton { -td::Ref WalletV3::get_init_state(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id) noexcept { - auto code = get_init_code(); +td::Ref WalletV3::get_init_state(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id, + td::int32 revision) noexcept { + auto code = get_init_code(revision); auto data = get_init_data(public_key, wallet_id); return GenericAccount::get_init_state(std::move(code), std::move(data)); } -td::Ref WalletV3::get_init_message(const td::Ed25519::PrivateKey& private_key, - td::uint32 wallet_id) noexcept { - td::uint32 seqno = 0; - td::uint32 valid_until = std::numeric_limits::max(); - auto signature = private_key - .sign(vm::CellBuilder() - .store_long(wallet_id, 32) - .store_long(valid_until, 32) - .store_long(seqno, 32) - .finalize() - ->get_hash() - .as_slice()) - .move_as_ok(); - return vm::CellBuilder() - .store_bytes(signature) - .store_long(wallet_id, 32) - .store_long(valid_until, 32) - .store_long(seqno, 32) - .finalize(); +td::optional WalletV3::guess_revision(const vm::Cell::Hash& code_hash) { + for (td::int32 i = 1; i <= 2; i++) { + if (get_init_code(i)->get_hash() == code_hash) { + return i; + } + } + return {}; +} +td::optional WalletV3::guess_revision(const block::StdAddress& address, + const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id) { + for (td::int32 i = 1; i <= 2; i++) { + if (GenericAccount::get_address(address.workchain, get_init_state(public_key, wallet_id, i)) == address) { + return i; + } + } + return {}; } td::Ref WalletV3::make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 wallet_id, - 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(); + td::uint32 seqno, td::uint32 valid_until, + td::Span gifts) noexcept { + CHECK(gifts.size() <= max_gifts_size); - auto message_outer = vm::CellBuilder() - .store_long(wallet_id, 32) - .store_long(valid_until, 32) - .store_long(seqno, 32) - .store_long(send_mode, 8) - .store_ref(message_inner) - .finalize(); + vm::CellBuilder cb; + cb.store_long(wallet_id, 32).store_long(valid_until, 32).store_long(seqno, 32); + + for (auto& gift : gifts) { + td::int32 send_mode = 3; + auto gramms = gift.gramms; + if (gramms == -1) { + gramms = 0; + send_mode += 128; + } + vm::CellBuilder cbi; + GenericAccount::store_int_message(cbi, gift.destination, gramms); + store_gift_message(cbi, gift); + auto message_inner = cbi.finalize(); + cb.store_long(send_mode, 8).store_ref(std::move(message_inner)); + } + + auto message_outer = cb.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 WalletV3::get_init_code() noexcept { - static auto res = [] { - auto serialized_code = td::base64_decode( - "te6ccgEBAQEAYgAAwP8AIN0gggFMl7qXMO1E0NcLH+Ck8mCDCNcYINMf0x/TH/gjE7vyY+1E0NMf0x/T/" - "9FRMrryoVFEuvKiBPkBVBBV+RDyo/gAkyDXSpbTB9QC+wDo0QGkyMsfyx/L/8ntVA==") - .move_as_ok(); - return vm::std_boc_deserialize(serialized_code).move_as_ok(); - }(); - return res; +td::Ref WalletV3::get_init_code(td::int32 revision) noexcept { + return SmartContractCode::wallet3(revision); } vm::CellHash WalletV3::get_init_code_hash() noexcept { return get_init_code()->get_hash(); } -td::Ref WalletV3::get_init_data(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id) noexcept { +td::Ref WalletV3::get_init_data(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id, + td::uint32 seqno) noexcept { return vm::CellBuilder() - .store_long(0, 32) + .store_long(seqno, 32) .store_long(wallet_id, 32) .store_bytes(public_key.as_octet_string()) .finalize(); @@ -127,4 +122,20 @@ td::Result WalletV3::get_wallet_id_or_throw() const { return static_cast(cs.fetch_ulong(32)); } +td::Result WalletV3::get_public_key() const { + return TRY_VM(get_public_key_or_throw()); +} + +td::Result WalletV3::get_public_key_or_throw() const { + if (state_.data.is_null()) { + return td::Status::Error("data is null"); + } + //FIXME use get method + auto cs = vm::load_cell_slice(state_.data); + cs.skip_first(64); + td::SecureString res(td::Ed25519::PublicKey::LENGTH); + cs.fetch_bytes(res.as_mutable_slice().ubegin(), td::narrow_cast(res.size())); + return td::Ed25519::PublicKey(std::move(res)); +} + } // namespace ton diff --git a/crypto/smc-envelope/WalletV3.h b/crypto/smc-envelope/WalletV3.h index a6e4162d..b7c211ca 100644 --- a/crypto/smc-envelope/WalletV3.h +++ b/crypto/smc-envelope/WalletV3.h @@ -14,37 +14,59 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "smc-envelope/SmartContract.h" +#include "smc-envelope/WalletInterface.h" #include "vm/cells.h" #include "Ed25519.h" #include "block/block.h" #include "vm/cells/CellString.h" namespace ton { -class WalletV3 : ton::SmartContract { +class WalletV3 : ton::SmartContract, public WalletInterface { public: explicit WalletV3(State state) : ton::SmartContract(std::move(state)) { } + explicit WalletV3(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id, td::uint32 seqno = 0) + : WalletV3(State{get_init_code(), get_init_data(public_key, wallet_id, seqno)}) { + } static constexpr unsigned max_message_size = vm::CellString::max_bytes; - static td::Ref get_init_state(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id) noexcept; - static td::Ref get_init_message(const td::Ed25519::PrivateKey& private_key, td::uint32 wallet_id) noexcept; - static td::Ref make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 wallet_id, - td::uint32 seqno, td::uint32 valid_until, td::int64 gramms, - td::Slice message, const block::StdAddress& dest_address) noexcept; + static constexpr unsigned max_gifts_size = 4; - static td::Ref get_init_code() noexcept; + static td::optional guess_revision(const vm::Cell::Hash& code_hash); + static td::optional guess_revision(const block::StdAddress& address, + const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id); + static td::Ref get_init_state(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id, + td::int32 revision = 0) noexcept; + static td::Ref make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 wallet_id, + td::uint32 seqno, td::uint32 valid_until, td::Span gifts) noexcept; + + static td::Ref get_init_code(td::int32 revision = 0) noexcept; static vm::CellHash get_init_code_hash() noexcept; - static td::Ref get_init_data(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id) noexcept; + static td::Ref get_init_data(const td::Ed25519::PublicKey& public_key, td::uint32 wallet_id, + td::uint32 seqno = 0) noexcept; td::Result get_seqno() const; td::Result get_wallet_id() const; + using WalletInterface::get_init_message; + td::Result> make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 valid_until, + td::Span gifts) const override { + TRY_RESULT(seqno, get_seqno()); + TRY_RESULT(wallet_id, get_wallet_id()); + return make_a_gift_message(private_key, wallet_id, seqno, valid_until, gifts); + } + size_t get_max_gifts_size() const override { + return max_gifts_size; + } + td::Result get_public_key() const override; + private: td::Result get_seqno_or_throw() const; td::Result get_wallet_id_or_throw() const; + td::Result get_public_key_or_throw() const; }; } // namespace ton diff --git a/crypto/test/fift/testdict.fif b/crypto/test/fift/testdict.fif index 307bd2e8..b9491ea8 100644 --- a/crypto/test/fift/testdict.fif +++ b/crypto/test/fift/testdict.fif @@ -1,4 +1,4 @@ -"Lisp.fif" include +"Lists.fif" include 16 constant key-bits 16 constant val-bits { val-bits u, } : val, diff --git a/crypto/test/test-smartcont.cpp b/crypto/test/test-smartcont.cpp index f129e0b3..f6f66b80 100644 --- a/crypto/test/test-smartcont.cpp +++ b/crypto/test/test-smartcont.cpp @@ -14,7 +14,7 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #include "vm/dict.h" #include "common/bigint.hpp" @@ -28,6 +28,7 @@ #include "fift/utils.h" #include "smc-envelope/GenericAccount.h" +#include "smc-envelope/ManualDns.h" #include "smc-envelope/MultisigWallet.h" #include "smc-envelope/SmartContract.h" #include "smc-envelope/SmartContractCode.h" @@ -36,6 +37,7 @@ #include "smc-envelope/Wallet.h" #include "smc-envelope/WalletV3.h" #include "smc-envelope/HighloadWallet.h" +#include "smc-envelope/HighloadWalletV2.h" #include "td/utils/base64.h" #include "td/utils/crypto.h" @@ -47,6 +49,7 @@ #include "td/utils/PathView.h" #include "td/utils/filesystem.h" #include "td/utils/port/path.h" +#include "td/utils/Variant.h" #include #include @@ -63,8 +66,8 @@ std::string load_source(std::string name) { 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 +DUP 85143 INT EQUAL OVER 78748 INT EQUAL OR IFJMP:<{ // "seqno" and "get_public_key" get-methods + 1 INT AND c4 PUSHCTR CTOS 32 LDU 256 PLDU CONDSEL // cnt or pubk }> INC 32 THROWIF // fail unless recv_external 512 INT LDSLICEX DUP 32 PLDU // sign cs cnt @@ -91,8 +94,8 @@ INC NEWC 32 STU 256 STU ENDC c4 POPCTR 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 + DUP 85143 INT EQUAL OVER 78748 INT EQUAL OR IFJMP:<{ // "seqno" and "get_public_key" get-methods + 1 INT AND c4 PUSHCTR CTOS 32 LDU 256 PLDU CONDSEL // cnt or pubk }> INC 32 THROWIF // fail unless recv_external 9 PUSHPOW2 LDSLICEX DUP 32 LDU 32 LDU // signature in_msg msg_seqno valid_until cs @@ -119,8 +122,8 @@ SETCP0 DUP IFNOTRET // return if recv_internal td::Ref get_wallet_v3_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 + DUP 85143 INT EQUAL OVER 78748 INT EQUAL OR IFJMP:<{ // "seqno" and "get_public_key" get-methods + 1 INT AND c4 PUSHCTR CTOS 32 LDU 32 LDU NIP 256 PLDU CONDSEL // cnt or pubk }> INC 32 THROWIF // fail unless recv_external 9 PUSHPOW2 LDSLICEX DUP 32 LDU 32 LDU 32 LDU // signature in_msg subwallet_id valid_until msg_seqno cs @@ -176,8 +179,15 @@ TEST(Tonlib, TestWallet) { "321", "-C", "TEST"}) .move_as_ok(); auto wallet_query = fift_output.source_lookup.read_file("wallet-query.boc").move_as_ok().data; + ton::TestWallet::Gift gift; + gift.destination = dest; + gift.message = "TEST"; + gift.gramms = 321000000000ll; + ton::TestWallet wallet(priv_key.get_public_key().move_as_ok(), 123); + ASSERT_EQ(123u, wallet.get_seqno().ok()); + CHECK(priv_key.get_public_key().ok().as_octet_string() == wallet.get_public_key().ok().as_octet_string()); auto gift_message = ton::GenericAccount::create_ext_message( - address, {}, ton::TestWallet::make_a_gift_message(priv_key, 123, 321000000000ll, "TEST", dest)); + address, {}, wallet.make_a_gift_message(priv_key, 0, {gift}).move_as_ok()); LOG(ERROR) << "-------"; vm::load_cell_slice(gift_message).print_rec(std::cerr); LOG(ERROR) << "-------"; @@ -224,13 +234,20 @@ TEST(Tonlib, Wallet) { }; 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", "-C", "TESTv2", "Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX", "123", "321"}) - .move_as_ok(); + fift_output = fift::mem_run_fift(std::move(fift_output.source_lookup), + {"aba", "new-wallet", "-C", "TESTv2", + "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; + ton::TestWallet::Gift gift; + gift.destination = dest; + gift.message = "TESTv2"; + gift.gramms = 321000000000ll; + ton::Wallet wallet(priv_key.get_public_key().move_as_ok(), 123); + ASSERT_EQ(123u, wallet.get_seqno().ok()); + CHECK(priv_key.get_public_key().ok().as_octet_string() == wallet.get_public_key().ok().as_octet_string()); auto gift_message = ton::GenericAccount::create_ext_message( - address, {}, ton::Wallet::make_a_gift_message(priv_key, 123, 60, 321000000000ll, "TESTv2", dest)); + address, {}, wallet.make_a_gift_message(priv_key, 60, {gift}).move_as_ok()); LOG(ERROR) << "-------"; vm::load_cell_slice(gift_message).print_rec(std::cerr); LOG(ERROR) << "-------"; @@ -251,7 +268,8 @@ TEST(Tonlib, WalletV3) { 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::WalletV3::get_init_state(pub_key, 239); - auto init_message = ton::WalletV3::get_init_message(priv_key, 239); + auto init_message = + ton::WalletV3(priv_key.get_public_key().move_as_ok(), 239).get_init_message(priv_key).move_as_ok(); auto address = ton::GenericAccount::get_address(0, init_state); CHECK(address.addr.as_slice() == td::Slice(new_wallet_addr).substr(0, 32)); @@ -273,13 +291,24 @@ TEST(Tonlib, WalletV3) { }; 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", "-C", "TESTv3", "Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX", "239", "123", "321"}) - .move_as_ok(); + fift_output = fift::mem_run_fift(std::move(fift_output.source_lookup), + {"aba", "new-wallet", "-C", "TESTv3", + "Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX", "239", "123", "321"}) + .move_as_ok(); auto wallet_query = fift_output.source_lookup.read_file("wallet-query.boc").move_as_ok().data; + + ton::WalletV3::Gift gift; + gift.destination = dest; + gift.message = "TESTv3"; + gift.gramms = 321000000000ll; + + ton::WalletV3 wallet(priv_key.get_public_key().move_as_ok(), 239, 123); + ASSERT_EQ(239u, wallet.get_wallet_id().ok()); + ASSERT_EQ(123u, wallet.get_seqno().ok()); + CHECK(priv_key.get_public_key().ok().as_octet_string() == wallet.get_public_key().ok().as_octet_string()); + auto gift_message = ton::GenericAccount::create_ext_message( - address, {}, ton::WalletV3::make_a_gift_message(priv_key, 239, 123, 60, 321000000000ll, "TESTv3", dest)); + address, {}, wallet.make_a_gift_message(priv_key, 60, {gift}).move_as_ok()); LOG(ERROR) << "-------"; vm::load_cell_slice(gift_message).print_rec(std::cerr); LOG(ERROR) << "-------"; @@ -304,6 +333,11 @@ TEST(Tonlib, HighloadWallet) { auto init_message = ton::HighloadWallet::get_init_message(priv_key, 239); auto address = ton::GenericAccount::get_address(0, init_state); + ton::HighloadWallet wallet({ton::HighloadWallet::get_init_code(), ton::HighloadWallet::get_init_data(pub_key, 239)}); + ASSERT_EQ(239u, wallet.get_wallet_id().ok()); + ASSERT_EQ(0u, wallet.get_seqno().ok()); + CHECK(pub_key.as_octet_string() == wallet.get_public_key().ok().as_octet_string()); + 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); @@ -354,6 +388,80 @@ TEST(Tonlib, HighloadWallet) { CHECK(vm::std_boc_deserialize(wallet_query).move_as_ok()->get_hash() == gift_message->get_hash()); } +TEST(Tonlib, HighloadWalletV2) { + auto source_lookup = fift::create_mem_source_lookup(load_source("smartcont/new-highload-wallet-v2.fif")).move_as_ok(); + source_lookup + .write_file("/auto/highload-wallet-v2-code.fif", load_source("smartcont/auto/highload-wallet-v2-code.fif")) + .ensure(); + class ZeroOsTime : public fift::OsTime { + public: + td::uint32 now() override { + return 0; + } + }; + source_lookup.set_os_time(std::make_unique()); + auto fift_output = fift::mem_run_fift(std::move(source_lookup), {"aba", "0", "239"}).move_as_ok(); + + LOG(ERROR) << fift_output.output; + 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-wallet239-query.boc").move_as_ok().data; + auto new_wallet_addr = fift_output.source_lookup.read_file("new-wallet239.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::HighloadWalletV2::get_init_state(pub_key, 239, 0); + auto init_message = ton::HighloadWalletV2::get_init_message(priv_key, 239, 65535); + auto address = ton::GenericAccount::get_address(0, init_state); + + ton::HighloadWalletV2 wallet( + {ton::HighloadWalletV2::get_init_code(0), ton::HighloadWalletV2::get_init_data(pub_key, 239)}); + ASSERT_EQ(239u, wallet.get_wallet_id().ok()); + CHECK(pub_key.as_octet_string() == wallet.get_public_key().ok().as_octet_string()); + + 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) << "---smc-envelope----"; + vm::load_cell_slice(res).print_rec(std::cerr); + LOG(ERROR) << "---fift scripts----"; + 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/highload-wallet-v2.fif")).ensure(); + std::string order; + std::vector gifts; + auto add_order = [&](td::Slice dest_str, td::int64 gramms) { + auto g = td::to_string(gramms); + if (g.size() < 10) { + g = std::string(10 - g.size(), '0') + g; + } + order += PSTRING() << "SEND " << dest_str << " " << g.substr(0, g.size() - 9) << "." << g.substr(g.size() - 9) + << "\n"; + + ton::HighloadWalletV2::Gift gift; + gift.destination = block::StdAddress::parse(dest_str).move_as_ok(); + gift.gramms = gramms; + gifts.push_back(gift); + }; + std::string dest_str = "Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX"; + add_order(dest_str, 0); + add_order(dest_str, 321000000000ll); + add_order(dest_str, 321ll); + fift_output.source_lookup.write_file("/order", order).ensure(); + fift_output.source_lookup.set_os_time(std::make_unique()); + fift_output = + fift::mem_run_fift(std::move(fift_output.source_lookup), {"aba", "new-wallet", "239", "order"}).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::HighloadWalletV2::make_a_gift_message(priv_key, 239, 60, gifts)); + LOG(ERROR) << "---smc-envelope----"; + vm::load_cell_slice(gift_message).print_rec(std::cerr); + LOG(ERROR) << "---fift scripts----"; + 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(); @@ -365,9 +473,13 @@ TEST(Tonlib, TestGiver) { 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)); + ton::TestGiver::Gift gift; + gift.gramms = 1000000000ll * 6666 / 1000; + gift.message = "GIFT"; + gift.destination = address; + td::Ed25519::PrivateKey key{td::SecureString()}; + auto res = ton::GenericAccount::create_ext_message(ton::TestGiver::address(), {}, + ton::TestGiver().make_a_gift_message(key, 0, {gift}).move_as_ok()); vm::CellSlice(vm::NoVm(), res).print_rec(std::cerr); CHECK(vm::std_boc_deserialize(wallet_query).move_as_ok()->get_hash() == res->get_hash()); } @@ -670,3 +782,457 @@ TEST(Smartcont, MultisigStress) { LOG(INFO) << "Final code size: " << ms->code_size(); LOG(INFO) << "Final data size: " << ms->data_size(); } + +class MapDns { + public: + using ManualDns = ton::ManualDns; + struct Entry { + std::string name; + td::int16 category{0}; + std::string text; + + auto key() const { + return std::tie(name, category); + } + bool operator<(const Entry& other) const { + return key() < other.key(); + } + bool operator==(const ton::DnsInterface::Entry& other) const { + return key() == other.key() && other.data.type == ManualDns::EntryData::Type::Text && + other.data.data.get().text == text; + } + bool operator==(const Entry& other) const { + return key() == other.key() && text == other.text; + } + friend td::StringBuilder& operator<<(td::StringBuilder& sb, const Entry& entry) { + return sb << "[" << entry.name << ":" << entry.category << ":" << entry.text << "]"; + } + }; + struct Action { + std::string name; + td::int16 category{0}; + td::optional text; + + bool does_create_category() const { + CHECK(!name.empty()); + CHECK(category != 0); + return static_cast(text); + } + bool does_change_empty() const { + CHECK(!name.empty()); + CHECK(category != 0); + return static_cast(text) && !text.value().empty(); + } + void make_non_empty() { + CHECK(!name.empty()); + CHECK(category != 0); + if (!text) { + text = ""; + } + } + friend td::StringBuilder& operator<<(td::StringBuilder& sb, const Action& entry) { + return sb << "[" << entry.name << ":" << entry.category << ":" << (entry.text ? entry.text.value() : "") + << "]"; + } + }; + void update(td::Span actions) { + for (auto& action : actions) { + do_update(action); + } + } + using CombinedActions = ton::ManualDns::CombinedActions; + void update_combined(td::Span actions) { + LOG(ERROR) << "BEGIN"; + LOG(ERROR) << td::format::as_array(actions); + auto combined_actions = ton::ManualDns::combine_actions(actions); + for (auto& c : combined_actions) { + LOG(ERROR) << c.name << ":" << c.category; + if (c.actions) { + LOG(ERROR) << td::format::as_array(c.actions.value()); + } + } + LOG(ERROR) << "END"; + for (auto& combined_action : combined_actions) { + do_update(combined_action); + } + } + + std::vector resolve(td::Slice name, td::int16 category) { + std::vector res; + if (name.empty()) { + for (auto& a : entries_) { + for (auto& b : a.second) { + res.push_back({a.first, b.first, b.second}); + } + } + } else { + auto it = entries_.find(name); + while (it == entries_.end()) { + auto sz = name.find('.'); + category = -1; + if (sz != td::Slice::npos) { + name = name.substr(sz + 1); + } else { + break; + } + it = entries_.find(name); + } + if (it != entries_.end()) { + for (auto& b : it->second) { + if (category == 0 || category == b.first) { + res.push_back({name.str(), b.first, b.second}); + } + } + } + } + + std::sort(res.begin(), res.end()); + return res; + } + + private: + std::map, std::less<>> entries_; + void do_update(const Action& action) { + if (action.name.empty()) { + entries_.clear(); + return; + } + if (action.category == 0) { + entries_.erase(action.name); + return; + } + if (action.text) { + if (action.text.value().empty()) { + entries_[action.name].erase(action.category); + } else { + entries_[action.name][action.category] = action.text.value(); + } + } else { + auto it = entries_.find(action.name); + if (it != entries_.end()) { + it->second.erase(action.category); + } + } + } + + void do_update(const CombinedActions& actions) { + if (actions.name.empty()) { + entries_.clear(); + LOG(ERROR) << "CLEAR"; + if (!actions.actions) { + return; + } + for (auto& action : actions.actions.value()) { + CHECK(!action.name.empty()); + CHECK(action.category != 0); + CHECK(action.text); + if (action.text.value().empty()) { + entries_[action.name]; + } else { + entries_[action.name][action.category] = action.text.value(); + } + } + return; + } + if (actions.category == 0) { + entries_.erase(actions.name); + LOG(ERROR) << "CLEAR " << actions.name; + if (!actions.actions) { + return; + } + entries_[actions.name]; + for (auto& action : actions.actions.value()) { + CHECK(action.name == actions.name); + CHECK(action.category != 0); + CHECK(action.text); + if (action.text.value().empty()) { + entries_[action.name]; + } else { + entries_[action.name][action.category] = action.text.value(); + } + } + return; + } + CHECK(actions.actions); + CHECK(actions.actions.value().size() == 1); + for (auto& action : actions.actions.value()) { + CHECK(action.name == actions.name); + CHECK(action.category != 0); + if (action.text) { + if (action.text.value().empty()) { + entries_[action.name].erase(action.category); + } else { + entries_[action.name][action.category] = action.text.value(); + } + } else { + auto it = entries_.find(action.name); + if (it != entries_.end()) { + it->second.erase(action.category); + } + } + } + } +}; + +class CheckedDns { + public: + explicit CheckedDns(bool check_smc = true, bool check_combine = true) { + if (check_smc) { + key_ = td::Ed25519::generate_private_key().move_as_ok(); + dns_ = ManualDns::create(ManualDns::create_init_data_fast(key_.value().get_public_key().move_as_ok(), 123)); + } + if (check_combine) { + combined_map_dns_ = MapDns(); + } + } + using Action = MapDns::Action; + using Entry = MapDns::Entry; + void update(td::Span entries) { + if (dns_.not_null()) { + auto smc_actions = td::transform(entries, [](auto& entry) { + ton::DnsInterface::Action action; + action.name = entry.name; + action.category = entry.category; + if (entry.text) { + if (entry.text.value().empty()) { + action.data = td::Ref(); + } else { + action.data = ManualDns::EntryData::text(entry.text.value()).as_cell().move_as_ok(); + } + } + return action; + }); + auto query = dns_->create_update_query(key_.value(), smc_actions).move_as_ok(); + CHECK(dns_.write().send_external_message(std::move(query)).code == 0); + } + map_dns_.update(entries); + if (combined_map_dns_) { + combined_map_dns_.value().update_combined(entries); + } + } + void update(const Action& action) { + return update(td::Span(&action, 1)); + } + + std::vector resolve(td::Slice name, td::int16 category) { + LOG(ERROR) << "RESOLVE: " << name << " " << category; + auto res = map_dns_.resolve(name, category); + LOG(ERROR) << td::format::as_array(res); + + if (dns_.not_null()) { + auto other_res = dns_->resolve(name, category).move_as_ok(); + + std::sort(other_res.begin(), other_res.end()); + if (res.size() != other_res.size()) { + LOG(ERROR) << td::format::as_array(res); + LOG(FATAL) << td::format::as_array(other_res); + } + for (size_t i = 0; i < res.size(); i++) { + if (!(res[i] == other_res[i])) { + LOG(ERROR) << td::format::as_array(res); + LOG(FATAL) << td::format::as_array(other_res); + } + } + } + if (combined_map_dns_) { + auto other_res = combined_map_dns_.value().resolve(name, category); + + std::sort(other_res.begin(), other_res.end()); + if (res.size() != other_res.size()) { + LOG(ERROR) << td::format::as_array(res); + LOG(FATAL) << td::format::as_array(other_res); + } + for (size_t i = 0; i < res.size(); i++) { + if (!(res[i] == other_res[i])) { + LOG(ERROR) << td::format::as_array(res); + LOG(FATAL) << td::format::as_array(other_res); + } + } + } + + return res; + } + + private: + using ManualDns = ton::ManualDns; + td::optional key_; + td::Ref dns_; + + MapDns map_dns_; + td::optional combined_map_dns_; + + void do_update_smc(const Action& entry) { + LOG(ERROR) << td::format::escaped(ManualDns::encode_name(entry.name)); + ton::DnsInterface::Action action; + action.name = entry.name; + action.category = entry.category; + action.data = ManualDns::EntryData::text(entry.text.value()).as_cell().move_as_ok(); + } +}; + +void do_dns_test(CheckedDns&& dns) { + using Action = CheckedDns::Action; + std::vector actions; + + td::Random::Xorshift128plus rnd(123); + + auto gen_name = [&] { + auto cnt = rnd.fast(1, 2); + std::string res; + for (int i = 0; i < cnt; i++) { + if (i != 0) { + res += '.'; + } + auto len = rnd.fast(1, 1); + for (int j = 0; j < len; j++) { + res += static_cast(rnd.fast('a', 'b')); + } + } + return res; + }; + auto gen_text = [&] { + std::string res; + int len = 5; + for (int j = 0; j < len; j++) { + res += static_cast(rnd.fast('a', 'b')); + } + return res; + }; + + auto gen_action = [&] { + Action action; + if (rnd.fast(0, 1000) == 0) { + return action; + } + action.name = gen_name(); + if (rnd.fast(0, 20) == 0) { + return action; + } + action.category = td::narrow_cast(rnd.fast(1, 5)); + if (rnd.fast(0, 4) == 0) { + return action; + } + if (rnd.fast(0, 4) == 0) { + action.text = ""; + return action; + } + action.text = gen_text(); + return action; + }; + + SET_VERBOSITY_LEVEL(VERBOSITY_NAME(ERROR)); + for (int i = 0; i < 100000; i++) { + actions.push_back(gen_action()); + if (rnd.fast(0, 10) == 0) { + dns.update(actions); + actions.clear(); + } + dns.resolve(gen_name(), td::narrow_cast(rnd.fast(0, 5))); + } +}; + +TEST(Smartcont, DnsManual) { + using ManualDns = ton::ManualDns; + auto test_entry_data = [](auto&& entry_data) { + auto cell = entry_data.as_cell().move_as_ok(); + auto cs = vm::load_cell_slice(cell); + auto new_entry_data = ManualDns::EntryData::from_cellslice(cs).move_as_ok(); + ASSERT_EQ(entry_data, new_entry_data); + }; + test_entry_data(ManualDns::EntryData::text("abcd")); + test_entry_data(ManualDns::EntryData::adnl_address(ton::Bits256{})); + + CHECK(td::Slice("a\0b\0") == ManualDns::encode_name("b.a")); + CHECK(td::Slice("a\0b\0") == ManualDns::encode_name(".b.a")); + ASSERT_EQ("b.a", ManualDns::decode_name("a\0b\0")); + ASSERT_EQ("b.a", ManualDns::decode_name("a\0b")); + ASSERT_EQ("", ManualDns::decode_name("")); + + auto key = td::Ed25519::generate_private_key().move_as_ok(); + + auto manual = ManualDns::create(ManualDns::create_init_data_fast(key.get_public_key().move_as_ok(), 123)); + CHECK(manual->get_wallet_id().move_as_ok() == 123); + auto init_query = manual->create_init_query(key).move_as_ok(); + LOG(ERROR) << "A"; + CHECK(manual.write().send_external_message(init_query).code == 0); + LOG(ERROR) << "B"; + CHECK(manual.write().send_external_message(init_query).code != 0); + + auto value = vm::CellBuilder().store_bytes("hello world").finalize(); + auto set_query = + manual + ->sign(key, + manual->prepare(manual->create_set_value_unsigned(1, "a\0b\0", value).move_as_ok(), 1).move_as_ok()) + .move_as_ok(); + CHECK(manual.write().send_external_message(set_query).code == 0); + + auto res = manual->run_get_method( + "dnsresolve", {vm::load_cell_slice_ref(vm::CellBuilder().store_bytes("a\0b\0").finalize()), td::make_refint(1)}); + CHECK(res.code == 0); + CHECK(res.stack.write().pop_cell()->get_hash() == value->get_hash()); + + CheckedDns dns; + dns.update(CheckedDns::Action{"a.b.c", 1, "hello"}); + CHECK(dns.resolve("a.b.c", 1).at(0).text == "hello"); + dns.resolve("a", 1); + dns.resolve("a.b", 1); + CHECK(dns.resolve("a.b.c", 2).empty()); + dns.update(CheckedDns::Action{"a.b.c", 2, "test"}); + CHECK(dns.resolve("a.b.c", 2).at(0).text == "test"); + dns.resolve("a.b.c", 1); + dns.resolve("a.b.c", 2); + LOG(ERROR) << "Test zero category"; + dns.resolve("a.b.c", 0); + dns.update(CheckedDns::Action{"", 0, ""}); + CHECK(dns.resolve("a.b.c", 2).empty()); + + LOG(ERROR) << "Test multipe update"; + { + CheckedDns::Action e[4] = {CheckedDns::Action{"", 0, ""}, CheckedDns::Action{"a.b.c", 1, "hello"}, + CheckedDns::Action{"a.b.c", 2, "world"}, CheckedDns::Action{"x.y.z", 3, "abc"}}; + dns.update(td::Span(e, 4)); + } + dns.resolve("a.b.c", 1); + dns.resolve("a.b.c", 2); + dns.resolve("x.y.z", 3); + + { + CheckedDns::Action e[1] = {CheckedDns::Action{"x.y.z", 0, ""}}; + dns.update(td::Span(e, 1)); + } + + dns.resolve("a.b.c", 1); + dns.resolve("a.b.c", 2); + dns.resolve("x.y.z", 3); + + { + CheckedDns::Action e[3] = {CheckedDns::Action{"x.y.z", 0, ""}, CheckedDns::Action{"x.y.z", 1, "xxx"}, + CheckedDns::Action{"x.y.z", 2, "yyy"}}; + dns.update(td::Span(e, 3)); + } + dns.resolve("a.b.c", 1); + dns.resolve("a.b.c", 2); + dns.resolve("x.y.z", 1); + dns.resolve("x.y.z", 2); + dns.resolve("x.y.z", 3); + + { + auto actions_ext = + ton::ManualDns::parse("delete.name one\nset one 1 TEXT:one\ndelete.name two\nset two 2 TEXT:two").move_as_ok(); + + auto actions = td::transform(actions_ext, [](auto& action) { + td::optional data; + if (action.data) { + data = action.data.value().data.template get().text; + } + return CheckedDns::Action{action.name, action.category, std::move(data)}; + }); + + dns.update(actions); + } + dns.resolve("one", 1); + dns.resolve("two", 2); + + // TODO: rethink semantic of creating an empty dictionary + do_dns_test(CheckedDns(true, true)); +} diff --git a/crypto/test/vm.cpp b/crypto/test/vm.cpp index 72e2f8c6..77157f6d 100644 --- a/crypto/test/vm.cpp +++ b/crypto/test/vm.cpp @@ -14,9 +14,9 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ -#include "vm/continuation.h" +#include "vm/vm.h" #include "vm/cp0.h" #include "vm/dict.h" #include "fift/utils.h" diff --git a/crypto/vm/arithops.cpp b/crypto/vm/arithops.cpp index 19279cf1..69e6f083 100644 --- a/crypto/vm/arithops.cpp +++ b/crypto/vm/arithops.cpp @@ -14,15 +14,15 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #include #include "vm/arithops.h" #include "vm/log.h" #include "vm/opctable.h" #include "vm/stack.hpp" -#include "vm/continuation.h" #include "vm/excno.hpp" +#include "vm/vm.h" #include "common/bigint.hpp" #include "common/refint.h" diff --git a/crypto/vm/cellops.cpp b/crypto/vm/cellops.cpp index 5346e168..0cd7d5e1 100644 --- a/crypto/vm/cellops.cpp +++ b/crypto/vm/cellops.cpp @@ -14,16 +14,16 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #include #include "vm/cellops.h" #include "vm/log.h" #include "vm/opctable.h" #include "vm/stack.hpp" -#include "vm/continuation.h" #include "vm/excno.hpp" #include "vm/vmstate.h" +#include "vm/vm.h" #include "common/bigint.hpp" #include "common/refint.h" diff --git a/crypto/vm/cells/CellSlice.cpp b/crypto/vm/cells/CellSlice.cpp index 74a78970..8e46bb95 100644 --- a/crypto/vm/cells/CellSlice.cpp +++ b/crypto/vm/cells/CellSlice.cpp @@ -14,7 +14,7 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #include "vm/cells/CellSlice.h" #include "vm/excno.hpp" @@ -1026,7 +1026,7 @@ std::ostream& operator<<(std::ostream& os, Ref cs_ref) { VirtualCell::LoadedCell load_cell_slice_impl(const Ref& cell, bool* can_be_special) { auto* vm_state_interface = VmStateInterface::get(); if (vm_state_interface) { - vm_state_interface->register_cell_load(); + vm_state_interface->register_cell_load(cell->get_hash()); } auto r_loaded_cell = cell->load_cell(); if (r_loaded_cell.is_error()) { diff --git a/crypto/vm/cells/CellString.cpp b/crypto/vm/cells/CellString.cpp index ad2cbf5f..9889dc8e 100644 --- a/crypto/vm/cells/CellString.cpp +++ b/crypto/vm/cells/CellString.cpp @@ -1,3 +1,21 @@ +/* + 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 2019-2020 Telegram Systems LLP +*/ #include "CellString.h" #include "td/utils/misc.h" @@ -61,4 +79,79 @@ td::Result CellString::load(CellSlice &cs, unsigned int top_bits) { CHECK(to.offs == (int)size); return res; } + +td::Status CellText::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 CellText::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)"); + } + if (cb.remaining_bits() < 16) { + return td::Status::Error("Not enough space in a builder"); + } + if (top_bits < 16) { + return td::Status::Error("Need at least 16 top bits"); + } + if (slice.size() == 0) { + cb.store_long(0, 8); + return td::Status::OK(); + } + unsigned int head = td::min(slice.size(), td::min(cb.remaining_bits(), top_bits) - 16) / 8 * 8; + auto max_bits = vm::Cell::max_bits / 8 * 8; + auto depth = 1 + (slice.size() - head + max_bits - 8 - 1) / (max_bits - 8); + if (depth > max_chain_length) { + return td::Status::Error("String is too long (2)"); + } + cb.store_long(depth, 8); + cb.store_long(head / 8, 8); + cb.append_bitslice(slice.subslice(0, head)); + slice.advance(head); + if (slice.size() == 0) { + return td::Status::OK(); + } + cb.store_ref(do_store(std::move(slice))); + return td::Status::OK(); +} + +td::Ref CellText::do_store(td::BitSlice slice) { + vm::CellBuilder cb; + unsigned int head = td::min(slice.size(), cb.remaining_bits() - 8) / 8 * 8; + cb.store_long(head / 8, 8); + cb.append_bitslice(slice.subslice(0, head)); + slice.advance(head); + if (slice.size() != 0) { + cb.store_ref(do_store(std::move(slice))); + } + return cb.finalize(); +} + +template +void CellText::for_each(F &&f, CellSlice cs) { + auto depth = cs.fetch_ulong(8); + + for (td::uint32 i = 0; i < depth; i++) { + auto size = cs.fetch_ulong(8); + f(cs.fetch_bits(td::narrow_cast(size) * 8)); + if (i + 1 < depth) { + cs = vm::load_cell_slice(cs.prefetch_ref()); + } + } +} + +td::Result CellText::load(CellSlice &cs) { + unsigned int size = 0; + for_each([&](auto slice) { size += slice.size(); }, cs); + 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); + CHECK(to.offs == (int)size); + return res; +} } // namespace vm diff --git a/crypto/vm/cells/CellString.h b/crypto/vm/cells/CellString.h index 89c933d8..ab93203f 100644 --- a/crypto/vm/cells/CellString.h +++ b/crypto/vm/cells/CellString.h @@ -1,3 +1,21 @@ +/* + 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 2019-2020 Telegram Systems LLP +*/ #pragma once #include "td/utils/Status.h" @@ -13,10 +31,35 @@ class CellString { 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); + static td::Result> create(td::Slice slice, unsigned int top_bits = Cell::max_bits) { + vm::CellBuilder cb; + TRY_STATUS(store(cb, slice, top_bits)); + return cb.finalize(); + } private: template static void for_each(F &&f, CellSlice &cs, unsigned int top_bits = Cell::max_bits); }; +class CellText { + 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); + static td::Result> create(td::Slice slice, unsigned int top_bits = Cell::max_bits) { + vm::CellBuilder cb; + TRY_STATUS(store(cb, slice, top_bits)); + return cb.finalize(); + } + + private: + template + static void for_each(F &&f, CellSlice cs); + static td::Ref do_store(td::BitSlice slice); +}; + } // namespace vm diff --git a/crypto/vm/continuation.cpp b/crypto/vm/continuation.cpp index f75aff75..fdd27ce6 100644 --- a/crypto/vm/continuation.cpp +++ b/crypto/vm/continuation.cpp @@ -14,12 +14,13 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #include "vm/dispatch.h" #include "vm/continuation.h" #include "vm/dict.h" #include "vm/log.h" +#include "vm/vm.h" namespace vm { @@ -625,562 +626,4 @@ void VmState::init_cregs(bool same_c3, bool push_0) { } } -VmState::VmState() : cp(-1), dispatch(&dummy_dispatch_table), quit0(true, 0), quit1(true, 1) { - ensure_throw(init_cp(0)); - init_cregs(); -} - -VmState::VmState(Ref _code) - : code(std::move(_code)), cp(-1), dispatch(&dummy_dispatch_table), quit0(true, 0), quit1(true, 1) { - ensure_throw(init_cp(0)); - init_cregs(); -} - -VmState::VmState(Ref _code, Ref _stack, int flags, Ref _data, VmLog log, - std::vector> _libraries, Ref init_c7) - : code(std::move(_code)) - , stack(std::move(_stack)) - , cp(-1) - , dispatch(&dummy_dispatch_table) - , quit0(true, 0) - , quit1(true, 1) - , log(log) - , libraries(std::move(_libraries)) { - ensure_throw(init_cp(0)); - set_c4(std::move(_data)); - if (init_c7.not_null()) { - set_c7(std::move(init_c7)); - } - init_cregs(flags & 1, flags & 2); -} - -VmState::VmState(Ref _code, Ref _stack, const GasLimits& gas, int flags, Ref _data, VmLog log, - std::vector> _libraries, Ref init_c7) - : code(std::move(_code)) - , stack(std::move(_stack)) - , cp(-1) - , dispatch(&dummy_dispatch_table) - , quit0(true, 0) - , quit1(true, 1) - , log(log) - , gas(gas) - , libraries(std::move(_libraries)) { - ensure_throw(init_cp(0)); - set_c4(std::move(_data)); - if (init_c7.not_null()) { - set_c7(std::move(init_c7)); - } - init_cregs(flags & 1, flags & 2); -} - -Ref VmState::convert_code_cell(Ref code_cell) { - if (code_cell.is_null()) { - return {}; - } - Ref csr{true, NoVmOrd(), code_cell}; - if (csr->is_valid()) { - return csr; - } - return load_cell_slice_ref(CellBuilder{}.store_ref(std::move(code_cell)).finalize()); -} - -bool VmState::init_cp(int new_cp) { - const DispatchTable* dt = DispatchTable::get_table(new_cp); - if (dt) { - cp = new_cp; - dispatch = dt; - return true; - } else { - return false; - } -} - -bool VmState::set_cp(int new_cp) { - return new_cp == cp || init_cp(new_cp); -} - -void VmState::force_cp(int new_cp) { - if (!set_cp(new_cp)) { - throw VmError{Excno::inv_opcode, "unsupported codepage"}; - } -} - -// simple call to a continuation cont -int VmState::call(Ref cont) { - const ControlData* cont_data = cont->get_cdata(); - if (cont_data) { - if (cont_data->save.c[0].not_null()) { - // call reduces to a jump - return jump(std::move(cont)); - } - if (cont_data->stack.not_null() || cont_data->nargs >= 0) { - // if cont has non-empty stack or expects fixed number of arguments, call is not simple - return call(std::move(cont), -1, -1); - } - // create return continuation, to be stored into new c0 - Ref ret = Ref{true, std::move(code), cp}; - ret.unique_write().get_cdata()->save.set_c0(std::move(cr.c[0])); - cr.set_c0( - std::move(ret)); // set c0 to its final value before switching to cont; notice that cont.save.c0 is not set - return jump_to(std::move(cont)); - } - // create return continuation, to be stored into new c0 - Ref ret = Ref{true, std::move(code), cp}; - ret.unique_write().get_cdata()->save.set_c0(std::move(cr.c[0])); - // general implementation of a simple call - cr.set_c0(std::move(ret)); // set c0 to its final value before switching to cont; notice that cont.save.c0 is not set - return jump_to(std::move(cont)); -} - -// call with parameters to continuation cont -int VmState::call(Ref cont, int pass_args, int ret_args) { - const ControlData* cont_data = cont->get_cdata(); - if (cont_data) { - if (cont_data->save.c[0].not_null()) { - // call reduces to a jump - return jump(std::move(cont), pass_args); - } - int depth = stack->depth(); - if (pass_args > depth || cont_data->nargs > depth) { - throw VmError{Excno::stk_und, "stack underflow while calling a continuation: not enough arguments on stack"}; - } - if (cont_data->nargs > pass_args && pass_args >= 0) { - throw VmError{Excno::stk_und, - "stack underflow while calling a closure continuation: not enough arguments passed"}; - } - auto old_c0 = std::move(cr.c[0]); - // optimization(?): decrease refcnts of unused continuations in c[i] as early as possible - preclear_cr(cont_data->save); - // no exceptions should be thrown after this point - int copy = cont_data->nargs, skip = 0; - if (pass_args >= 0) { - if (copy >= 0) { - skip = pass_args - copy; - } else { - copy = pass_args; - } - } - // copy=-1 : pass whole stack, else pass top `copy` elements, drop next `skip` elements. - Ref new_stk; - if (cont_data->stack.not_null() && !cont_data->stack->is_empty()) { - // `cont` already has a stack, create resulting stack from it - if (copy < 0) { - copy = stack->depth(); - } - if (cont->is_unique()) { - // optimization: avoid copying stack if we hold the only copy of `cont` - new_stk = std::move(cont.unique_write().get_cdata()->stack); - } else { - new_stk = cont_data->stack; - } - new_stk.write().move_from_stack(get_stack(), copy); - if (skip > 0) { - get_stack().pop_many(skip); - } - } else if (copy >= 0) { - new_stk = get_stack().split_top(copy, skip); - } else { - new_stk = std::move(stack); - stack.clear(); - } - // create return continuation using the remainder of current stack - Ref ret = Ref{true, std::move(code), cp, std::move(stack), ret_args}; - ret.unique_write().get_cdata()->save.set_c0(std::move(old_c0)); - Ref ord_cont = static_cast>(cont); - set_stack(std::move(new_stk)); - cr.set_c0(std::move(ret)); // ??? if codepage of code in ord_cont is unknown, will end up with incorrect c0 - return jump_to(std::move(cont)); - } else { - // have no continuation data, situation is somewhat simpler - int depth = stack->depth(); - if (pass_args > depth) { - throw VmError{Excno::stk_und, "stack underflow while calling a continuation: not enough arguments on stack"}; - } - // create new stack from the top `pass_args` elements of the current stack - Ref new_stk = (pass_args >= 0 ? get_stack().split_top(pass_args) : std::move(stack)); - // create return continuation using the remainder of the current stack - Ref ret = Ref{true, std::move(code), cp, std::move(stack), ret_args}; - ret.unique_write().get_cdata()->save.set_c0(std::move(cr.c[0])); - set_stack(std::move(new_stk)); - cr.set_c0(std::move(ret)); // ??? if codepage of code in ord_cont is unknown, will end up with incorrect c0 - return jump_to(std::move(cont)); - } -} - -// simple jump to continuation cont -int VmState::jump(Ref cont) { - const ControlData* cont_data = cont->get_cdata(); - if (cont_data && (cont_data->stack.not_null() || cont_data->nargs >= 0)) { - // if cont has non-empty stack or expects fixed number of arguments, jump is not simple - return jump(std::move(cont), -1); - } else { - return jump_to(std::move(cont)); - } -} - -// general jump to continuation cont -int VmState::jump(Ref cont, int pass_args) { - const ControlData* cont_data = cont->get_cdata(); - if (cont_data) { - // first do the checks - int depth = stack->depth(); - if (pass_args > depth || cont_data->nargs > depth) { - throw VmError{Excno::stk_und, "stack underflow while jumping to a continuation: not enough arguments on stack"}; - } - if (cont_data->nargs > pass_args && pass_args >= 0) { - throw VmError{Excno::stk_und, - "stack underflow while jumping to closure continuation: not enough arguments passed"}; - } - // optimization(?): decrease refcnts of unused continuations in c[i] as early as possible - preclear_cr(cont_data->save); - // no exceptions should be thrown after this point - int copy = cont_data->nargs; - if (pass_args >= 0 && copy < 0) { - copy = pass_args; - } - // copy=-1 : pass whole stack, else pass top `copy` elements, drop the remainder. - if (cont_data->stack.not_null() && !cont_data->stack->is_empty()) { - // `cont` already has a stack, create resulting stack from it - if (copy < 0) { - copy = get_stack().depth(); - } - Ref new_stk; - if (cont->is_unique()) { - // optimization: avoid copying the stack if we hold the only copy of `cont` - new_stk = std::move(cont.unique_write().get_cdata()->stack); - } else { - new_stk = cont_data->stack; - } - new_stk.write().move_from_stack(get_stack(), copy); - set_stack(std::move(new_stk)); - } else { - if (copy >= 0) { - get_stack().drop_bottom(stack->depth() - copy); - } - } - return jump_to(std::move(cont)); - } else { - // have no continuation data, situation is somewhat simpler - if (pass_args >= 0) { - int depth = get_stack().depth(); - if (pass_args > depth) { - throw VmError{Excno::stk_und, "stack underflow while jumping to a continuation: not enough arguments on stack"}; - } - get_stack().drop_bottom(depth - pass_args); - } - return jump_to(std::move(cont)); - } -} - -int VmState::ret() { - Ref cont = quit0; - cont.swap(cr.c[0]); - return jump(std::move(cont)); -} - -int VmState::ret(int ret_args) { - Ref cont = quit0; - cont.swap(cr.c[0]); - return jump(std::move(cont), ret_args); -} - -int VmState::ret_alt() { - Ref cont = quit1; - cont.swap(cr.c[1]); - return jump(std::move(cont)); -} - -int VmState::ret_alt(int ret_args) { - Ref cont = quit1; - cont.swap(cr.c[1]); - return jump(std::move(cont), ret_args); -} - -Ref VmState::extract_cc(int save_cr, int stack_copy, int cc_args) { - Ref new_stk; - if (stack_copy < 0 || stack_copy == stack->depth()) { - new_stk = std::move(stack); - stack.clear(); - } else if (stack_copy > 0) { - stack->check_underflow(stack_copy); - new_stk = get_stack().split_top(stack_copy); - } else { - new_stk = Ref{true}; - } - Ref cc = Ref{true, std::move(code), cp, std::move(stack), cc_args}; - stack = std::move(new_stk); - if (save_cr & 7) { - ControlData* cdata = cc.unique_write().get_cdata(); - if (save_cr & 1) { - cdata->save.set_c0(std::move(cr.c[0])); - cr.set_c0(quit0); - } - if (save_cr & 2) { - cdata->save.set_c1(std::move(cr.c[1])); - cr.set_c1(quit1); - } - if (save_cr & 4) { - cdata->save.set_c2(std::move(cr.c[2])); - // cr.set_c2(Ref{true}); - } - } - return cc; -} - -int VmState::throw_exception(int excno) { - Stack& stack_ref = get_stack(); - stack_ref.clear(); - stack_ref.push_smallint(0); - stack_ref.push_smallint(excno); - code.clear(); - consume_gas(exception_gas_price); - return jump(get_c2()); -} - -int VmState::throw_exception(int excno, StackEntry&& arg) { - Stack& stack_ref = get_stack(); - stack_ref.clear(); - stack_ref.push(std::move(arg)); - stack_ref.push_smallint(excno); - code.clear(); - consume_gas(exception_gas_price); - return jump(get_c2()); -} - -void GasLimits::gas_exception() const { - throw VmNoGas{}; -} - -void GasLimits::set_limits(long long _max, long long _limit, long long _credit) { - gas_max = _max; - gas_limit = _limit; - gas_credit = _credit; - change_base(_limit + _credit); -} - -void GasLimits::change_limit(long long _limit) { - _limit = std::min(std::max(_limit, 0LL), gas_max); - gas_credit = 0; - gas_limit = _limit; - change_base(_limit); -} - -bool VmState::set_gas_limits(long long _max, long long _limit, long long _credit) { - gas.set_limits(_max, _limit, _credit); - return true; -} - -void VmState::change_gas_limit(long long new_limit) { - VM_LOG(this) << "changing gas limit to " << std::min(new_limit, gas.gas_max); - gas.change_limit(new_limit); -} - -int VmState::step() { - assert(!code.is_null()); - //VM_LOG(st) << "stack:"; stack->dump(VM_LOG(st)); - //VM_LOG(st) << "; cr0.refcnt = " << get_c0()->get_refcnt() - 1 << std::endl; - if (stack_trace) { - stack->dump(std::cerr, 3); - } - ++steps; - if (code->size()) { - return dispatch->dispatch(this, code.write()); - } else if (code->size_refs()) { - VM_LOG(this) << "execute implicit JMPREF\n"; - Ref cont = Ref{true, load_cell_slice_ref(code->prefetch_ref()), get_cp()}; - return jump(std::move(cont)); - } else { - VM_LOG(this) << "execute implicit RET\n"; - return ret(); - } -} - -int VmState::run() { - if (code.is_null()) { - throw VmError{Excno::fatal, "cannot run an uninitialized VM"}; - } - int res; - Guard guard(this); - do { - // LOG(INFO) << "[BS] data cells: " << DataCell::get_total_data_cells(); - try { - try { - res = step(); - gas.check(); - } catch (vm::CellBuilder::CellWriteError) { - throw VmError{Excno::cell_ov}; - } catch (vm::CellBuilder::CellCreateError) { - throw VmError{Excno::cell_ov}; - } catch (vm::CellSlice::CellReadError) { - throw VmError{Excno::cell_und}; - } - } catch (const VmError& vme) { - VM_LOG(this) << "handling exception code " << vme.get_errno() << ": " << vme.get_msg(); - try { - // LOG(INFO) << "[EX] data cells: " << DataCell::get_total_data_cells(); - ++steps; - res = throw_exception(vme.get_errno()); - } catch (const VmError& vme2) { - VM_LOG(this) << "exception " << vme2.get_errno() << " while handling exception: " << vme.get_msg(); - // LOG(INFO) << "[EXX] data cells: " << DataCell::get_total_data_cells(); - return ~vme2.get_errno(); - } - } catch (VmNoGas vmoog) { - ++steps; - VM_LOG(this) << "unhandled out-of-gas exception: gas consumed=" << gas.gas_consumed() - << ", limit=" << gas.gas_limit; - get_stack().clear(); - get_stack().push_smallint(gas.gas_consumed()); - return vmoog.get_errno(); // no ~ for unhandled exceptions (to make their faking impossible) - } - } while (!res); - // LOG(INFO) << "[EN] data cells: " << DataCell::get_total_data_cells(); - if ((res | 1) == -1) { - commit(); - } - return res; -} - -ControlData* force_cdata(Ref& cont) { - if (!cont->get_cdata()) { - cont = Ref{true, cont}; - return cont.unique_write().get_cdata(); - } else { - return cont.write().get_cdata(); - } -} - -ControlRegs* force_cregs(Ref& cont) { - return &force_cdata(cont)->save; -} - -int run_vm_code(Ref code, Ref& stack, int flags, Ref* data_ptr, VmLog log, long long* steps, - GasLimits* gas_limits, std::vector> libraries, Ref init_c7, Ref* actions_ptr) { - VmState vm{code, - std::move(stack), - gas_limits ? *gas_limits : GasLimits{}, - flags, - data_ptr ? *data_ptr : Ref{}, - log, - std::move(libraries), - std::move(init_c7)}; - int res = vm.run(); - stack = vm.get_stack_ref(); - if (vm.committed() && data_ptr) { - *data_ptr = vm.get_committed_state().c4; - } - if (vm.committed() && actions_ptr) { - *actions_ptr = vm.get_committed_state().c5; - } - if (steps) { - *steps = vm.get_steps_count(); - } - if (gas_limits) { - *gas_limits = vm.get_gas_limits(); - LOG(INFO) << "steps: " << vm.get_steps_count() << " gas: used=" << gas_limits->gas_consumed() - << ", max=" << gas_limits->gas_max << ", limit=" << gas_limits->gas_limit - << ", credit=" << gas_limits->gas_credit; - } - if ((vm.get_log().log_mask & vm::VmLog::DumpStack) != 0) { - VM_LOG(&vm) << "BEGIN_STACK_DUMP"; - for (int i = stack->depth(); i > 0; i--) { - VM_LOG(&vm) << (*stack)[i - 1].to_string(); - } - VM_LOG(&vm) << "END_STACK_DUMP"; - } - - return ~res; -} - -int run_vm_code(Ref code, Stack& stack, int flags, Ref* data_ptr, VmLog log, long long* steps, - GasLimits* gas_limits, std::vector> libraries, Ref init_c7, Ref* actions_ptr) { - Ref stk{true}; - stk.unique_write().set_contents(std::move(stack)); - stack.clear(); - int res = run_vm_code(code, stk, flags, data_ptr, log, steps, gas_limits, std::move(libraries), std::move(init_c7), - actions_ptr); - CHECK(stack.is_unique()); - if (stk.is_null()) { - stack.clear(); - } else if (&(*stk) != &stack) { - VmState* st = nullptr; - if (stk->is_unique()) { - VM_LOG(st) << "move resulting stack (" << stk->depth() << " entries)"; - stack.set_contents(std::move(stk.unique_write())); - } else { - VM_LOG(st) << "copying resulting stack (" << stk->depth() << " entries)"; - stack.set_contents(*stk); - } - } - return res; -} - -// may throw a dictionary exception; returns nullptr if library is not found in context -Ref VmState::load_library(td::ConstBitPtr hash) { - std::unique_ptr tmp_ctx; - // install temporary dummy vm state interface to prevent charging for cell load operations during library lookup - VmStateInterface::Guard(tmp_ctx.get()); - for (const auto& lib_collection : libraries) { - auto lib = lookup_library_in(hash, lib_collection); - if (lib.not_null()) { - return lib; - } - } - return {}; -} - -bool VmState::register_library_collection(Ref lib) { - if (lib.is_null()) { - return true; - } - libraries.push_back(std::move(lib)); - return true; -} - -void VmState::register_cell_load() { - consume_gas(cell_load_gas_price); -} - -void VmState::register_cell_create() { - consume_gas(cell_create_gas_price); -} - -td::BitArray<256> VmState::get_state_hash() const { - // TODO: implement properly, by serializing the stack etc, and computing the Merkle hash - td::BitArray<256> res; - res.clear(); - return res; -} - -td::BitArray<256> VmState::get_final_state_hash(int exit_code) const { - // TODO: implement properly, by serializing the stack etc, and computing the Merkle hash - td::BitArray<256> res; - res.clear(); - return res; -} - -Ref lookup_library_in(td::ConstBitPtr key, vm::Dictionary& dict) { - try { - auto val = dict.lookup(key, 256); - if (val.is_null() || !val->have_refs()) { - return {}; - } - auto root = val->prefetch_ref(); - if (root.not_null() && !root->get_hash().bits().compare(key, 256)) { - return root; - } - return {}; - } catch (vm::VmError) { - return {}; - } -} - -Ref lookup_library_in(td::ConstBitPtr key, Ref lib_root) { - if (lib_root.is_null()) { - return lib_root; - } - vm::Dictionary dict{std::move(lib_root), 256}; - return lookup_library_in(key, dict); -} - } // namespace vm diff --git a/crypto/vm/continuation.h b/crypto/vm/continuation.h index 1c24c4cd..37abe869 100644 --- a/crypto/vm/continuation.h +++ b/crypto/vm/continuation.h @@ -14,7 +14,7 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -371,289 +371,7 @@ class OrdCont : public Continuation { static Ref deserialize(CellSlice& cs, int mode = 0); }; -struct GasLimits { - static constexpr long long infty = (1ULL << 63) - 1; - long long gas_max, gas_limit, gas_credit, gas_remaining, gas_base; - GasLimits() : gas_max(infty), gas_limit(infty), gas_credit(0), gas_remaining(infty), gas_base(infty) { - } - GasLimits(long long _limit, long long _max = infty, long long _credit = 0) - : gas_max(_max) - , gas_limit(_limit) - , gas_credit(_credit) - , gas_remaining(_limit + _credit) - , gas_base(gas_remaining) { - } - long long gas_consumed() const { - return gas_base - gas_remaining; - } - void set_limits(long long _max, long long _limit, long long _credit = 0); - void change_base(long long _base) { - gas_remaining += _base - gas_base; - gas_base = _base; - } - void change_limit(long long _limit); - void consume(long long amount) { - // LOG(DEBUG) << "consume " << amount << " gas (" << gas_remaining << " remaining)"; - gas_remaining -= amount; - } - bool try_consume(long long amount) { - // LOG(DEBUG) << "try consume " << amount << " gas (" << gas_remaining << " remaining)"; - return (gas_remaining -= amount) >= 0; - } - void gas_exception() const; - void gas_exception(bool cond) const { - if (!cond) { - gas_exception(); - } - } - void consume_chk(long long amount) { - gas_exception(try_consume(amount)); - } - void check() const { - gas_exception(gas_remaining >= 0); - } - bool final_ok() const { - return gas_remaining >= gas_credit; - } -}; - -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; - Ref quit0, quit1; - VmLog log; - GasLimits gas; - 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; - VmState(); - VmState(Ref _code); - VmState(Ref _code, Ref _stack, int flags = 0, Ref _data = {}, VmLog log = {}, - std::vector> _libraries = {}, Ref init_c7 = {}); - VmState(Ref _code, Ref _stack, const GasLimits& _gas, int flags = 0, Ref _data = {}, - VmLog log = {}, std::vector> _libraries = {}, Ref init_c7 = {}); - template - 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(); - } - 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); - } - void consume_tuple_gas(unsigned tuple_len) { - consume_gas(tuple_len * tuple_entry_gas_price); - } - void consume_tuple_gas(const Ref& tup) { - if (tup.not_null()) { - consume_tuple_gas((unsigned)tup->size()); - } - } - GasLimits get_gas_limits() const { - return gas; - } - void change_gas_limit(long long new_limit); - template - void check_underflow(Args... args) { - stack->check_underflow(args...); - } - bool register_library_collection(Ref lib); - Ref load_library( - td::ConstBitPtr hash) override; // may throw a dictionary exception; returns nullptr if library is not found - void register_cell_load() override; - void register_cell_create() override; - bool init_cp(int new_cp); - bool set_cp(int new_cp); - void force_cp(int new_cp); - int get_cp() const { - return cp; - } - int incr_stack_trace(int v) { - return stack_trace += v; - } - long long get_steps_count() const { - return steps; - } - td::BitArray<256> get_state_hash() const; - td::BitArray<256> get_final_state_hash(int exit_code) const; - int step(); - int run(); - Stack& get_stack() { - return stack.write(); - } - const Stack& get_stack_const() const { - return *stack; - } - Ref get_stack_ref() const { - return stack; - } - Ref get_c0() const { - return cr.c[0]; - } - Ref get_c1() const { - return cr.c[1]; - } - Ref get_c2() const { - return cr.c[2]; - } - Ref get_c3() const { - return cr.c[3]; - } - Ref get_c4() const { - return cr.d[0]; - } - Ref get_c7() const { - return cr.c7; - } - Ref get_c(unsigned idx) const { - return cr.get_c(idx); - } - Ref get_d(unsigned idx) const { - return cr.get_d(idx); - } - StackEntry get(unsigned idx) const { - return cr.get(idx); - } - const VmLog& get_log() const { - return log; - } - void define_c0(Ref cont) { - cr.define_c0(std::move(cont)); - } - void set_c0(Ref cont) { - cr.set_c0(std::move(cont)); - } - void set_c1(Ref cont) { - cr.set_c1(std::move(cont)); - } - void set_c2(Ref cont) { - cr.set_c2(std::move(cont)); - } - bool set_c(unsigned idx, Ref val) { - return cr.set_c(idx, std::move(val)); - } - bool set_d(unsigned idx, Ref val) { - return cr.set_d(idx, std::move(val)); - } - void set_c4(Ref val) { - cr.set_c4(std::move(val)); - } - bool set_c7(Ref val) { - return cr.set_c7(std::move(val)); - } - bool set(unsigned idx, StackEntry val) { - return cr.set(idx, std::move(val)); - } - void set_stack(Ref new_stk) { - stack = std::move(new_stk); - } - Ref swap_stack(Ref new_stk) { - stack.swap(new_stk); - return new_stk; - } - void ensure_throw(bool cond) const { - if (!cond) { - fatal(); - } - } - void set_code(Ref _code, int _cp) { - code = std::move(_code); - force_cp(_cp); - } - Ref get_code() const { - return code; - } - void push_code() { - get_stack().push_cellslice(get_code()); - } - void adjust_cr(const ControlRegs& save) { - cr ^= save; - } - void adjust_cr(ControlRegs&& save) { - cr ^= save; - } - void preclear_cr(const ControlRegs& save) { - cr &= save; - } - int call(Ref cont); - int call(Ref cont, int pass_args, int ret_args = -1); - int jump(Ref cont); - int jump(Ref cont, int pass_args); - int ret(); - int ret(int ret_args); - int ret_alt(); - int ret_alt(int ret_args); - int repeat(Ref body, Ref after, long long count); - int again(Ref body); - int until(Ref body, Ref after); - int loop_while(Ref cond, Ref body, Ref after); - int throw_exception(int excno, StackEntry&& arg); - int throw_exception(int excno); - Ref extract_cc(int save_cr = 1, int stack_copy = -1, int cc_args = -1); - void fatal(void) const { - throw VmFatal{}; - } - int jump_to(Ref cont) { - 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); -}; - -int run_vm_code(Ref _code, Ref& _stack, int flags = 0, Ref* data_ptr = nullptr, VmLog log = {}, - long long* steps = nullptr, GasLimits* gas_limits = nullptr, std::vector> libraries = {}, - Ref init_c7 = {}, Ref* actions_ptr = nullptr); -int run_vm_code(Ref _code, Stack& _stack, int flags = 0, Ref* data_ptr = nullptr, VmLog log = {}, - long long* steps = nullptr, GasLimits* gas_limits = nullptr, std::vector> libraries = {}, - Ref init_c7 = {}, Ref* actions_ptr = nullptr); - ControlData* force_cdata(Ref& cont); ControlRegs* force_cregs(Ref& cont); -Ref lookup_library_in(td::ConstBitPtr key, Ref lib_root); - } // namespace vm diff --git a/crypto/vm/contops.cpp b/crypto/vm/contops.cpp index 6fcda8de..63d55705 100644 --- a/crypto/vm/contops.cpp +++ b/crypto/vm/contops.cpp @@ -14,7 +14,7 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #include #include "vm/contops.h" @@ -24,6 +24,7 @@ #include "vm/continuation.h" #include "vm/cellops.h" #include "vm/excno.hpp" +#include "vm/vm.h" namespace vm { diff --git a/crypto/vm/debugops.cpp b/crypto/vm/debugops.cpp index 5c22740a..f8dc4396 100644 --- a/crypto/vm/debugops.cpp +++ b/crypto/vm/debugops.cpp @@ -14,15 +14,15 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #include #include "vm/debugops.h" #include "vm/log.h" #include "vm/opctable.h" #include "vm/stack.hpp" -#include "vm/continuation.h" #include "vm/excno.hpp" +#include "vm/vm.h" namespace vm { diff --git a/crypto/vm/dictops.cpp b/crypto/vm/dictops.cpp index 820e1e97..c798b333 100644 --- a/crypto/vm/dictops.cpp +++ b/crypto/vm/dictops.cpp @@ -20,8 +20,8 @@ #include "vm/log.h" #include "vm/opctable.h" #include "vm/stack.hpp" -#include "vm/continuation.h" #include "vm/excno.hpp" +#include "vm/vm.h" #include "common/bigint.hpp" #include "common/refint.h" #include "vm/dictops.h" diff --git a/crypto/vm/opctable.cpp b/crypto/vm/opctable.cpp index fbdc2578..d4f0f3e9 100644 --- a/crypto/vm/opctable.cpp +++ b/crypto/vm/opctable.cpp @@ -14,14 +14,14 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #include #include #include "vm/opctable.h" #include "vm/cellslice.h" #include "vm/excno.hpp" -#include "vm/continuation.h" +#include "vm/vm.h" #include #include #include diff --git a/crypto/vm/stackops.cpp b/crypto/vm/stackops.cpp index 7fdbcba0..ac7d9f88 100644 --- a/crypto/vm/stackops.cpp +++ b/crypto/vm/stackops.cpp @@ -14,14 +14,14 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #include "vm/log.h" #include "vm/stackops.h" #include "vm/opctable.h" #include "vm/stack.hpp" -#include "vm/continuation.h" #include "vm/excno.hpp" +#include "vm/vm.h" namespace vm { diff --git a/crypto/vm/tonops.cpp b/crypto/vm/tonops.cpp index 5dcd0d54..5fd8bd64 100644 --- a/crypto/vm/tonops.cpp +++ b/crypto/vm/tonops.cpp @@ -14,15 +14,15 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #include #include "vm/tonops.h" #include "vm/log.h" #include "vm/opctable.h" #include "vm/stack.hpp" -#include "vm/continuation.h" #include "vm/excno.hpp" +#include "vm/vm.h" #include "vm/dict.h" #include "Ed25519.h" @@ -397,6 +397,83 @@ void register_ton_crypto_ops(OpcodeTable& cp0) { .insert(OpcodeInstr::mksimple(0xf911, 16, "CHKSIGNS", std::bind(exec_ed25519_check_signature, _1, true))); } +struct VmStorageStat { + td::uint64 cells{0}, bits{0}, refs{0}, limit; + td::HashSet visited; + VmStorageStat(td::uint64 _limit) : limit(_limit) { + } + bool add_storage(Ref cell); + bool add_storage(const CellSlice& cs); + bool check_visited(const CellHash& cell_hash) { + return visited.insert(cell_hash).second; + } + bool check_visited(const Ref& cell) { + return check_visited(cell->get_hash()); + } +}; + +bool VmStorageStat::add_storage(Ref cell) { + if (cell.is_null() || !check_visited(cell)) { + return true; + } + if (cells >= limit) { + return false; + } + ++cells; + bool special; + auto cs = load_cell_slice_special(std::move(cell), special); + return cs.is_valid() && add_storage(std::move(cs)); +} + +bool VmStorageStat::add_storage(const CellSlice& cs) { + bits += cs.size(); + refs += cs.size_refs(); + for (unsigned i = 0; i < cs.size_refs(); i++) { + if (!add_storage(cs.prefetch_ref(i))) { + return false; + } + } + return true; +} + +int exec_compute_data_size(VmState* st, int mode) { + VM_LOG(st) << (mode & 2 ? 'S' : 'C') << "DATASIZE" << (mode & 1 ? "Q" : ""); + Stack& stack = st->get_stack(); + stack.check_underflow(2); + auto bound = stack.pop_int(); + Ref cell; + Ref cs; + if (mode & 2) { + cs = stack.pop_cellslice(); + } else { + cell = stack.pop_maybe_cell(); + } + if (!bound->is_valid() || bound->sgn() < 0) { + throw VmError{Excno::range_chk, "finite non-negative integer expected"}; + } + VmStorageStat stat{bound->unsigned_fits_bits(63) ? bound->to_long() : (1ULL << 63) - 1}; + bool ok = (mode & 2 ? stat.add_storage(cs.write()) : stat.add_storage(std::move(cell))); + if (ok) { + stack.push_smallint(stat.cells); + stack.push_smallint(stat.bits); + stack.push_smallint(stat.refs); + } else if (!(mode & 1)) { + throw VmError{Excno::cell_ov, "scanned too many cells"}; + } + if (mode & 1) { + stack.push_bool(ok); + } + return 0; +} + +void register_ton_misc_ops(OpcodeTable& cp0) { + using namespace std::placeholders; + cp0.insert(OpcodeInstr::mksimple(0xf940, 16, "CDATASIZEQ", std::bind(exec_compute_data_size, _1, 1))) + .insert(OpcodeInstr::mksimple(0xf941, 16, "CDATASIZE", std::bind(exec_compute_data_size, _1, 0))) + .insert(OpcodeInstr::mksimple(0xf942, 16, "SDATASIZEQ", std::bind(exec_compute_data_size, _1, 3))) + .insert(OpcodeInstr::mksimple(0xf943, 16, "SDATASIZE", std::bind(exec_compute_data_size, _1, 2))); +} + int exec_load_var_integer(VmState* st, int len_bits, bool sgnd, bool quiet) { if (len_bits == 4 && !sgnd) { VM_LOG(st) << "execute LDGRAMS" << (quiet ? "Q" : ""); @@ -822,6 +899,7 @@ void register_ton_ops(OpcodeTable& cp0) { register_prng_ops(cp0); register_ton_config_ops(cp0); register_ton_crypto_ops(cp0); + register_ton_misc_ops(cp0); register_ton_currency_address_ops(cp0); register_ton_message_ops(cp0); } diff --git a/crypto/vm/tupleops.cpp b/crypto/vm/tupleops.cpp index de4de834..96f78fe3 100644 --- a/crypto/vm/tupleops.cpp +++ b/crypto/vm/tupleops.cpp @@ -14,14 +14,14 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #include "vm/log.h" #include "vm/stackops.h" #include "vm/opctable.h" #include "vm/stack.hpp" -#include "vm/continuation.h" #include "vm/excno.hpp" +#include "vm/vm.h" namespace vm { diff --git a/crypto/vm/vm.cpp b/crypto/vm/vm.cpp new file mode 100644 index 00000000..cba973bc --- /dev/null +++ b/crypto/vm/vm.cpp @@ -0,0 +1,593 @@ +/* + 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-2020 Telegram Systems LLP +*/ +#include "vm/dispatch.h" +#include "vm/continuation.h" +#include "vm/dict.h" +#include "vm/log.h" +#include "vm/vm.h" + +namespace vm { + +VmState::VmState() : cp(-1), dispatch(&dummy_dispatch_table), quit0(true, 0), quit1(true, 1) { + ensure_throw(init_cp(0)); + init_cregs(); +} + +VmState::VmState(Ref _code) + : code(std::move(_code)), cp(-1), dispatch(&dummy_dispatch_table), quit0(true, 0), quit1(true, 1) { + ensure_throw(init_cp(0)); + init_cregs(); +} + +VmState::VmState(Ref _code, Ref _stack, int flags, Ref _data, VmLog log, + std::vector> _libraries, Ref init_c7) + : code(std::move(_code)) + , stack(std::move(_stack)) + , cp(-1) + , dispatch(&dummy_dispatch_table) + , quit0(true, 0) + , quit1(true, 1) + , log(log) + , libraries(std::move(_libraries)) { + ensure_throw(init_cp(0)); + set_c4(std::move(_data)); + if (init_c7.not_null()) { + set_c7(std::move(init_c7)); + } + init_cregs(flags & 1, flags & 2); +} + +VmState::VmState(Ref _code, Ref _stack, const GasLimits& gas, int flags, Ref _data, VmLog log, + std::vector> _libraries, Ref init_c7) + : code(std::move(_code)) + , stack(std::move(_stack)) + , cp(-1) + , dispatch(&dummy_dispatch_table) + , quit0(true, 0) + , quit1(true, 1) + , log(log) + , gas(gas) + , libraries(std::move(_libraries)) { + ensure_throw(init_cp(0)); + set_c4(std::move(_data)); + if (init_c7.not_null()) { + set_c7(std::move(init_c7)); + } + init_cregs(flags & 1, flags & 2); +} + +Ref VmState::convert_code_cell(Ref code_cell) { + if (code_cell.is_null()) { + return {}; + } + Ref csr{true, NoVmOrd(), code_cell}; + if (csr->is_valid()) { + return csr; + } + return load_cell_slice_ref(CellBuilder{}.store_ref(std::move(code_cell)).finalize()); +} + +bool VmState::init_cp(int new_cp) { + const DispatchTable* dt = DispatchTable::get_table(new_cp); + if (dt) { + cp = new_cp; + dispatch = dt; + return true; + } else { + return false; + } +} + +bool VmState::set_cp(int new_cp) { + return new_cp == cp || init_cp(new_cp); +} + +void VmState::force_cp(int new_cp) { + if (!set_cp(new_cp)) { + throw VmError{Excno::inv_opcode, "unsupported codepage"}; + } +} + +// simple call to a continuation cont +int VmState::call(Ref cont) { + const ControlData* cont_data = cont->get_cdata(); + if (cont_data) { + if (cont_data->save.c[0].not_null()) { + // call reduces to a jump + return jump(std::move(cont)); + } + if (cont_data->stack.not_null() || cont_data->nargs >= 0) { + // if cont has non-empty stack or expects fixed number of arguments, call is not simple + return call(std::move(cont), -1, -1); + } + // create return continuation, to be stored into new c0 + Ref ret = Ref{true, std::move(code), cp}; + ret.unique_write().get_cdata()->save.set_c0(std::move(cr.c[0])); + cr.set_c0( + std::move(ret)); // set c0 to its final value before switching to cont; notice that cont.save.c0 is not set + return jump_to(std::move(cont)); + } + // create return continuation, to be stored into new c0 + Ref ret = Ref{true, std::move(code), cp}; + ret.unique_write().get_cdata()->save.set_c0(std::move(cr.c[0])); + // general implementation of a simple call + cr.set_c0(std::move(ret)); // set c0 to its final value before switching to cont; notice that cont.save.c0 is not set + return jump_to(std::move(cont)); +} + +// call with parameters to continuation cont +int VmState::call(Ref cont, int pass_args, int ret_args) { + const ControlData* cont_data = cont->get_cdata(); + if (cont_data) { + if (cont_data->save.c[0].not_null()) { + // call reduces to a jump + return jump(std::move(cont), pass_args); + } + int depth = stack->depth(); + if (pass_args > depth || cont_data->nargs > depth) { + throw VmError{Excno::stk_und, "stack underflow while calling a continuation: not enough arguments on stack"}; + } + if (cont_data->nargs > pass_args && pass_args >= 0) { + throw VmError{Excno::stk_und, + "stack underflow while calling a closure continuation: not enough arguments passed"}; + } + auto old_c0 = std::move(cr.c[0]); + // optimization(?): decrease refcnts of unused continuations in c[i] as early as possible + preclear_cr(cont_data->save); + // no exceptions should be thrown after this point + int copy = cont_data->nargs, skip = 0; + if (pass_args >= 0) { + if (copy >= 0) { + skip = pass_args - copy; + } else { + copy = pass_args; + } + } + // copy=-1 : pass whole stack, else pass top `copy` elements, drop next `skip` elements. + Ref new_stk; + if (cont_data->stack.not_null() && !cont_data->stack->is_empty()) { + // `cont` already has a stack, create resulting stack from it + if (copy < 0) { + copy = stack->depth(); + } + if (cont->is_unique()) { + // optimization: avoid copying stack if we hold the only copy of `cont` + new_stk = std::move(cont.unique_write().get_cdata()->stack); + } else { + new_stk = cont_data->stack; + } + new_stk.write().move_from_stack(get_stack(), copy); + if (skip > 0) { + get_stack().pop_many(skip); + } + } else if (copy >= 0) { + new_stk = get_stack().split_top(copy, skip); + } else { + new_stk = std::move(stack); + stack.clear(); + } + // create return continuation using the remainder of current stack + Ref ret = Ref{true, std::move(code), cp, std::move(stack), ret_args}; + ret.unique_write().get_cdata()->save.set_c0(std::move(old_c0)); + Ref ord_cont = static_cast>(cont); + set_stack(std::move(new_stk)); + cr.set_c0(std::move(ret)); // ??? if codepage of code in ord_cont is unknown, will end up with incorrect c0 + return jump_to(std::move(cont)); + } else { + // have no continuation data, situation is somewhat simpler + int depth = stack->depth(); + if (pass_args > depth) { + throw VmError{Excno::stk_und, "stack underflow while calling a continuation: not enough arguments on stack"}; + } + // create new stack from the top `pass_args` elements of the current stack + Ref new_stk = (pass_args >= 0 ? get_stack().split_top(pass_args) : std::move(stack)); + // create return continuation using the remainder of the current stack + Ref ret = Ref{true, std::move(code), cp, std::move(stack), ret_args}; + ret.unique_write().get_cdata()->save.set_c0(std::move(cr.c[0])); + set_stack(std::move(new_stk)); + cr.set_c0(std::move(ret)); // ??? if codepage of code in ord_cont is unknown, will end up with incorrect c0 + return jump_to(std::move(cont)); + } +} + +// simple jump to continuation cont +int VmState::jump(Ref cont) { + const ControlData* cont_data = cont->get_cdata(); + if (cont_data && (cont_data->stack.not_null() || cont_data->nargs >= 0)) { + // if cont has non-empty stack or expects fixed number of arguments, jump is not simple + return jump(std::move(cont), -1); + } else { + return jump_to(std::move(cont)); + } +} + +// general jump to continuation cont +int VmState::jump(Ref cont, int pass_args) { + const ControlData* cont_data = cont->get_cdata(); + if (cont_data) { + // first do the checks + int depth = stack->depth(); + if (pass_args > depth || cont_data->nargs > depth) { + throw VmError{Excno::stk_und, "stack underflow while jumping to a continuation: not enough arguments on stack"}; + } + if (cont_data->nargs > pass_args && pass_args >= 0) { + throw VmError{Excno::stk_und, + "stack underflow while jumping to closure continuation: not enough arguments passed"}; + } + // optimization(?): decrease refcnts of unused continuations in c[i] as early as possible + preclear_cr(cont_data->save); + // no exceptions should be thrown after this point + int copy = cont_data->nargs; + if (pass_args >= 0 && copy < 0) { + copy = pass_args; + } + // copy=-1 : pass whole stack, else pass top `copy` elements, drop the remainder. + if (cont_data->stack.not_null() && !cont_data->stack->is_empty()) { + // `cont` already has a stack, create resulting stack from it + if (copy < 0) { + copy = get_stack().depth(); + } + Ref new_stk; + if (cont->is_unique()) { + // optimization: avoid copying the stack if we hold the only copy of `cont` + new_stk = std::move(cont.unique_write().get_cdata()->stack); + } else { + new_stk = cont_data->stack; + } + new_stk.write().move_from_stack(get_stack(), copy); + set_stack(std::move(new_stk)); + } else { + if (copy >= 0) { + get_stack().drop_bottom(stack->depth() - copy); + } + } + return jump_to(std::move(cont)); + } else { + // have no continuation data, situation is somewhat simpler + if (pass_args >= 0) { + int depth = get_stack().depth(); + if (pass_args > depth) { + throw VmError{Excno::stk_und, "stack underflow while jumping to a continuation: not enough arguments on stack"}; + } + get_stack().drop_bottom(depth - pass_args); + } + return jump_to(std::move(cont)); + } +} + +int VmState::ret() { + Ref cont = quit0; + cont.swap(cr.c[0]); + return jump(std::move(cont)); +} + +int VmState::ret(int ret_args) { + Ref cont = quit0; + cont.swap(cr.c[0]); + return jump(std::move(cont), ret_args); +} + +int VmState::ret_alt() { + Ref cont = quit1; + cont.swap(cr.c[1]); + return jump(std::move(cont)); +} + +int VmState::ret_alt(int ret_args) { + Ref cont = quit1; + cont.swap(cr.c[1]); + return jump(std::move(cont), ret_args); +} + +Ref VmState::extract_cc(int save_cr, int stack_copy, int cc_args) { + Ref new_stk; + if (stack_copy < 0 || stack_copy == stack->depth()) { + new_stk = std::move(stack); + stack.clear(); + } else if (stack_copy > 0) { + stack->check_underflow(stack_copy); + new_stk = get_stack().split_top(stack_copy); + } else { + new_stk = Ref{true}; + } + Ref cc = Ref{true, std::move(code), cp, std::move(stack), cc_args}; + stack = std::move(new_stk); + if (save_cr & 7) { + ControlData* cdata = cc.unique_write().get_cdata(); + if (save_cr & 1) { + cdata->save.set_c0(std::move(cr.c[0])); + cr.set_c0(quit0); + } + if (save_cr & 2) { + cdata->save.set_c1(std::move(cr.c[1])); + cr.set_c1(quit1); + } + if (save_cr & 4) { + cdata->save.set_c2(std::move(cr.c[2])); + // cr.set_c2(Ref{true}); + } + } + return cc; +} + +int VmState::throw_exception(int excno) { + Stack& stack_ref = get_stack(); + stack_ref.clear(); + stack_ref.push_smallint(0); + stack_ref.push_smallint(excno); + code.clear(); + consume_gas(exception_gas_price); + return jump(get_c2()); +} + +int VmState::throw_exception(int excno, StackEntry&& arg) { + Stack& stack_ref = get_stack(); + stack_ref.clear(); + stack_ref.push(std::move(arg)); + stack_ref.push_smallint(excno); + code.clear(); + consume_gas(exception_gas_price); + return jump(get_c2()); +} + +void GasLimits::gas_exception() const { + throw VmNoGas{}; +} + +void GasLimits::set_limits(long long _max, long long _limit, long long _credit) { + gas_max = _max; + gas_limit = _limit; + gas_credit = _credit; + change_base(_limit + _credit); +} + +void GasLimits::change_limit(long long _limit) { + _limit = std::min(std::max(_limit, 0LL), gas_max); + gas_credit = 0; + gas_limit = _limit; + change_base(_limit); +} + +bool VmState::set_gas_limits(long long _max, long long _limit, long long _credit) { + gas.set_limits(_max, _limit, _credit); + return true; +} + +void VmState::change_gas_limit(long long new_limit) { + VM_LOG(this) << "changing gas limit to " << std::min(new_limit, gas.gas_max); + gas.change_limit(new_limit); +} + +int VmState::step() { + assert(!code.is_null()); + //VM_LOG(st) << "stack:"; stack->dump(VM_LOG(st)); + //VM_LOG(st) << "; cr0.refcnt = " << get_c0()->get_refcnt() - 1 << std::endl; + if (stack_trace) { + stack->dump(std::cerr, 3); + } + ++steps; + if (code->size()) { + return dispatch->dispatch(this, code.write()); + } else if (code->size_refs()) { + VM_LOG(this) << "execute implicit JMPREF\n"; + Ref cont = Ref{true, load_cell_slice_ref(code->prefetch_ref()), get_cp()}; + return jump(std::move(cont)); + } else { + VM_LOG(this) << "execute implicit RET\n"; + return ret(); + } +} + +int VmState::run() { + if (code.is_null()) { + throw VmError{Excno::fatal, "cannot run an uninitialized VM"}; + } + int res; + Guard guard(this); + do { + // LOG(INFO) << "[BS] data cells: " << DataCell::get_total_data_cells(); + try { + try { + res = step(); + gas.check(); + } catch (vm::CellBuilder::CellWriteError) { + throw VmError{Excno::cell_ov}; + } catch (vm::CellBuilder::CellCreateError) { + throw VmError{Excno::cell_ov}; + } catch (vm::CellSlice::CellReadError) { + throw VmError{Excno::cell_und}; + } + } catch (const VmError& vme) { + VM_LOG(this) << "handling exception code " << vme.get_errno() << ": " << vme.get_msg(); + try { + // LOG(INFO) << "[EX] data cells: " << DataCell::get_total_data_cells(); + ++steps; + res = throw_exception(vme.get_errno()); + } catch (const VmError& vme2) { + VM_LOG(this) << "exception " << vme2.get_errno() << " while handling exception: " << vme.get_msg(); + // LOG(INFO) << "[EXX] data cells: " << DataCell::get_total_data_cells(); + return ~vme2.get_errno(); + } + } catch (VmNoGas vmoog) { + ++steps; + VM_LOG(this) << "unhandled out-of-gas exception: gas consumed=" << gas.gas_consumed() + << ", limit=" << gas.gas_limit; + get_stack().clear(); + get_stack().push_smallint(gas.gas_consumed()); + return vmoog.get_errno(); // no ~ for unhandled exceptions (to make their faking impossible) + } + } while (!res); + // LOG(INFO) << "[EN] data cells: " << DataCell::get_total_data_cells(); + if ((res | 1) == -1) { + commit(); + } + return res; +} + +ControlData* force_cdata(Ref& cont) { + if (!cont->get_cdata()) { + cont = Ref{true, cont}; + return cont.unique_write().get_cdata(); + } else { + return cont.write().get_cdata(); + } +} + +ControlRegs* force_cregs(Ref& cont) { + return &force_cdata(cont)->save; +} + +int run_vm_code(Ref code, Ref& stack, int flags, Ref* data_ptr, VmLog log, long long* steps, + GasLimits* gas_limits, std::vector> libraries, Ref init_c7, Ref* actions_ptr) { + VmState vm{code, + std::move(stack), + gas_limits ? *gas_limits : GasLimits{}, + flags, + data_ptr ? *data_ptr : Ref{}, + log, + std::move(libraries), + std::move(init_c7)}; + int res = vm.run(); + stack = vm.get_stack_ref(); + if (vm.committed() && data_ptr) { + *data_ptr = vm.get_committed_state().c4; + } + if (vm.committed() && actions_ptr) { + *actions_ptr = vm.get_committed_state().c5; + } + if (steps) { + *steps = vm.get_steps_count(); + } + if (gas_limits) { + *gas_limits = vm.get_gas_limits(); + LOG(INFO) << "steps: " << vm.get_steps_count() << " gas: used=" << gas_limits->gas_consumed() + << ", max=" << gas_limits->gas_max << ", limit=" << gas_limits->gas_limit + << ", credit=" << gas_limits->gas_credit; + } + if ((vm.get_log().log_mask & vm::VmLog::DumpStack) != 0) { + VM_LOG(&vm) << "BEGIN_STACK_DUMP"; + for (int i = stack->depth(); i > 0; i--) { + VM_LOG(&vm) << (*stack)[i - 1].to_string(); + } + VM_LOG(&vm) << "END_STACK_DUMP"; + } + + return ~res; +} + +int run_vm_code(Ref code, Stack& stack, int flags, Ref* data_ptr, VmLog log, long long* steps, + GasLimits* gas_limits, std::vector> libraries, Ref init_c7, Ref* actions_ptr) { + Ref stk{true}; + stk.unique_write().set_contents(std::move(stack)); + stack.clear(); + int res = run_vm_code(code, stk, flags, data_ptr, log, steps, gas_limits, std::move(libraries), std::move(init_c7), + actions_ptr); + CHECK(stack.is_unique()); + if (stk.is_null()) { + stack.clear(); + } else if (&(*stk) != &stack) { + VmState* st = nullptr; + if (stk->is_unique()) { + VM_LOG(st) << "move resulting stack (" << stk->depth() << " entries)"; + stack.set_contents(std::move(stk.unique_write())); + } else { + VM_LOG(st) << "copying resulting stack (" << stk->depth() << " entries)"; + stack.set_contents(*stk); + } + } + return res; +} + +// may throw a dictionary exception; returns nullptr if library is not found in context +Ref VmState::load_library(td::ConstBitPtr hash) { + std::unique_ptr tmp_ctx; + // install temporary dummy vm state interface to prevent charging for cell load operations during library lookup + VmStateInterface::Guard(tmp_ctx.get()); + for (const auto& lib_collection : libraries) { + auto lib = lookup_library_in(hash, lib_collection); + if (lib.not_null()) { + return lib; + } + } + return {}; +} + +bool VmState::register_library_collection(Ref lib) { + if (lib.is_null()) { + return true; + } + libraries.push_back(std::move(lib)); + return true; +} + +void VmState::register_cell_load(const CellHash& cell_hash) { + if (cell_load_gas_price == cell_reload_gas_price) { + consume_gas(cell_load_gas_price); + } else { + auto ok = loaded_cells.insert(cell_hash); // check whether this is the first time this cell is loaded + if (ok.second) { + loaded_cells_count++; + } + consume_gas(ok.second ? cell_load_gas_price : cell_reload_gas_price); + } +} + +void VmState::register_cell_create() { + consume_gas(cell_create_gas_price); +} + +td::BitArray<256> VmState::get_state_hash() const { + // TODO: implement properly, by serializing the stack etc, and computing the Merkle hash + td::BitArray<256> res; + res.clear(); + return res; +} + +td::BitArray<256> VmState::get_final_state_hash(int exit_code) const { + // TODO: implement properly, by serializing the stack etc, and computing the Merkle hash + td::BitArray<256> res; + res.clear(); + return res; +} + +Ref lookup_library_in(td::ConstBitPtr key, vm::Dictionary& dict) { + try { + auto val = dict.lookup(key, 256); + if (val.is_null() || !val->have_refs()) { + return {}; + } + auto root = val->prefetch_ref(); + if (root.not_null() && !root->get_hash().bits().compare(key, 256)) { + return root; + } + return {}; + } catch (vm::VmError) { + return {}; + } +} + +Ref lookup_library_in(td::ConstBitPtr key, Ref lib_root) { + if (lib_root.is_null()) { + return lib_root; + } + vm::Dictionary dict{std::move(lib_root), 256}; + return lookup_library_in(key, dict); +} + +} // namespace vm diff --git a/crypto/vm/vm.h b/crypto/vm/vm.h new file mode 100644 index 00000000..eac5f2fd --- /dev/null +++ b/crypto/vm/vm.h @@ -0,0 +1,320 @@ +/* + 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-2020 Telegram Systems LLP +*/ +#pragma once + +#include "common/refcnt.hpp" +#include "vm/cellslice.h" +#include "vm/stack.hpp" +#include "vm/vmstate.h" +#include "vm/log.h" +#include "vm/continuation.h" +#include "td/utils/HashSet.h" + +namespace vm { + +using td::Ref; +struct GasLimits { + static constexpr long long infty = (1ULL << 63) - 1; + long long gas_max, gas_limit, gas_credit, gas_remaining, gas_base; + GasLimits() : gas_max(infty), gas_limit(infty), gas_credit(0), gas_remaining(infty), gas_base(infty) { + } + GasLimits(long long _limit, long long _max = infty, long long _credit = 0) + : gas_max(_max) + , gas_limit(_limit) + , gas_credit(_credit) + , gas_remaining(_limit + _credit) + , gas_base(gas_remaining) { + } + long long gas_consumed() const { + return gas_base - gas_remaining; + } + void set_limits(long long _max, long long _limit, long long _credit = 0); + void change_base(long long _base) { + gas_remaining += _base - gas_base; + gas_base = _base; + } + void change_limit(long long _limit); + void consume(long long amount) { + // LOG(DEBUG) << "consume " << amount << " gas (" << gas_remaining << " remaining)"; + gas_remaining -= amount; + } + bool try_consume(long long amount) { + // LOG(DEBUG) << "try consume " << amount << " gas (" << gas_remaining << " remaining)"; + return (gas_remaining -= amount) >= 0; + } + void gas_exception() const; + void gas_exception(bool cond) const { + if (!cond) { + gas_exception(); + } + } + void consume_chk(long long amount) { + gas_exception(try_consume(amount)); + } + void check() const { + gas_exception(gas_remaining >= 0); + } + bool final_ok() const { + return gas_remaining >= gas_credit; + } +}; + +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; + Ref quit0, quit1; + VmLog log; + GasLimits gas; + std::vector> libraries; + td::HashSet loaded_cells; + td::int64 loaded_cells_count{0}; + int stack_trace{0}, debug_off{0}; + bool chksig_always_succeed{false}; + + public: + enum { + cell_load_gas_price = 100, + cell_reload_gas_price = 25, + cell_create_gas_price = 500, + exception_gas_price = 50, + tuple_entry_gas_price = 1 + }; + VmState(); + VmState(Ref _code); + VmState(Ref _code, Ref _stack, int flags = 0, Ref _data = {}, VmLog log = {}, + std::vector> _libraries = {}, Ref init_c7 = {}); + VmState(Ref _code, Ref _stack, const GasLimits& _gas, int flags = 0, Ref _data = {}, + VmLog log = {}, std::vector> _libraries = {}, Ref init_c7 = {}); + template + 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(); + } + 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); + } + void consume_tuple_gas(unsigned tuple_len) { + consume_gas(tuple_len * tuple_entry_gas_price); + } + void consume_tuple_gas(const Ref& tup) { + if (tup.not_null()) { + consume_tuple_gas((unsigned)tup->size()); + } + } + GasLimits get_gas_limits() const { + return gas; + } + void change_gas_limit(long long new_limit); + template + void check_underflow(Args... args) { + stack->check_underflow(args...); + } + bool register_library_collection(Ref lib); + Ref load_library( + td::ConstBitPtr hash) override; // may throw a dictionary exception; returns nullptr if library is not found + void register_cell_load(const CellHash& cell_hash) override; + void register_cell_create() override; + bool init_cp(int new_cp); + bool set_cp(int new_cp); + void force_cp(int new_cp); + int get_cp() const { + return cp; + } + int incr_stack_trace(int v) { + return stack_trace += v; + } + long long get_steps_count() const { + return steps; + } + td::BitArray<256> get_state_hash() const; + td::BitArray<256> get_final_state_hash(int exit_code) const; + int step(); + int run(); + Stack& get_stack() { + return stack.write(); + } + const Stack& get_stack_const() const { + return *stack; + } + Ref get_stack_ref() const { + return stack; + } + Ref get_c0() const { + return cr.c[0]; + } + Ref get_c1() const { + return cr.c[1]; + } + Ref get_c2() const { + return cr.c[2]; + } + Ref get_c3() const { + return cr.c[3]; + } + Ref get_c4() const { + return cr.d[0]; + } + Ref get_c7() const { + return cr.c7; + } + Ref get_c(unsigned idx) const { + return cr.get_c(idx); + } + Ref get_d(unsigned idx) const { + return cr.get_d(idx); + } + StackEntry get(unsigned idx) const { + return cr.get(idx); + } + const VmLog& get_log() const { + return log; + } + void define_c0(Ref cont) { + cr.define_c0(std::move(cont)); + } + void set_c0(Ref cont) { + cr.set_c0(std::move(cont)); + } + void set_c1(Ref cont) { + cr.set_c1(std::move(cont)); + } + void set_c2(Ref cont) { + cr.set_c2(std::move(cont)); + } + bool set_c(unsigned idx, Ref val) { + return cr.set_c(idx, std::move(val)); + } + bool set_d(unsigned idx, Ref val) { + return cr.set_d(idx, std::move(val)); + } + void set_c4(Ref val) { + cr.set_c4(std::move(val)); + } + bool set_c7(Ref val) { + return cr.set_c7(std::move(val)); + } + bool set(unsigned idx, StackEntry val) { + return cr.set(idx, std::move(val)); + } + void set_stack(Ref new_stk) { + stack = std::move(new_stk); + } + Ref swap_stack(Ref new_stk) { + stack.swap(new_stk); + return new_stk; + } + void ensure_throw(bool cond) const { + if (!cond) { + fatal(); + } + } + void set_code(Ref _code, int _cp) { + code = std::move(_code); + force_cp(_cp); + } + Ref get_code() const { + return code; + } + void push_code() { + get_stack().push_cellslice(get_code()); + } + void adjust_cr(const ControlRegs& save) { + cr ^= save; + } + void adjust_cr(ControlRegs&& save) { + cr ^= save; + } + void preclear_cr(const ControlRegs& save) { + cr &= save; + } + int call(Ref cont); + int call(Ref cont, int pass_args, int ret_args = -1); + int jump(Ref cont); + int jump(Ref cont, int pass_args); + int ret(); + int ret(int ret_args); + int ret_alt(); + int ret_alt(int ret_args); + int repeat(Ref body, Ref after, long long count); + int again(Ref body); + int until(Ref body, Ref after); + int loop_while(Ref cond, Ref body, Ref after); + int throw_exception(int excno, StackEntry&& arg); + int throw_exception(int excno); + Ref extract_cc(int save_cr = 1, int stack_copy = -1, int cc_args = -1); + void fatal(void) const { + throw VmFatal{}; + } + int jump_to(Ref cont) { + 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); +}; + +int run_vm_code(Ref _code, Ref& _stack, int flags = 0, Ref* data_ptr = nullptr, VmLog log = {}, + long long* steps = nullptr, GasLimits* gas_limits = nullptr, std::vector> libraries = {}, + Ref init_c7 = {}, Ref* actions_ptr = nullptr); +int run_vm_code(Ref _code, Stack& _stack, int flags = 0, Ref* data_ptr = nullptr, VmLog log = {}, + long long* steps = nullptr, GasLimits* gas_limits = nullptr, std::vector> libraries = {}, + Ref init_c7 = {}, Ref* actions_ptr = nullptr); + +Ref lookup_library_in(td::ConstBitPtr key, Ref lib_root); + +} // namespace vm diff --git a/crypto/vm/vmstate.h b/crypto/vm/vmstate.h index 312b6626..39e5d48a 100644 --- a/crypto/vm/vmstate.h +++ b/crypto/vm/vmstate.h @@ -14,7 +14,7 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "common/refcnt.hpp" @@ -32,7 +32,7 @@ class VmStateInterface : public td::Context { td::ConstBitPtr hash) { // may throw a dictionary exception; returns nullptr if library is not found return {}; } - virtual void register_cell_load(){}; + virtual void register_cell_load(const CellHash& cell_hash){}; virtual void register_cell_create(){}; }; diff --git a/dht/dht-bucket.cpp b/dht/dht-bucket.cpp index bc30a45a..4f9d75eb 100644 --- a/dht/dht-bucket.cpp +++ b/dht/dht-bucket.cpp @@ -14,7 +14,7 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #include "td/utils/tl_storers.h" #include "td/utils/crypto.h" diff --git a/dht/dht-bucket.hpp b/dht/dht-bucket.hpp index 3d16a5d8..812f670d 100644 --- a/dht/dht-bucket.hpp +++ b/dht/dht-bucket.hpp @@ -14,7 +14,7 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/dht/dht-in.hpp b/dht/dht-in.hpp index 5407c824..c4f67819 100644 --- a/dht/dht-in.hpp +++ b/dht/dht-in.hpp @@ -14,7 +14,7 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/dht/dht-query.cpp b/dht/dht-query.cpp index beefbc5c..7e1f1b92 100644 --- a/dht/dht-query.cpp +++ b/dht/dht-query.cpp @@ -14,7 +14,7 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #include "dht.hpp" diff --git a/dht/dht-query.hpp b/dht/dht-query.hpp index af39f7cf..aa607f56 100644 --- a/dht/dht-query.hpp +++ b/dht/dht-query.hpp @@ -14,7 +14,7 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/dht/dht-remote-node.cpp b/dht/dht-remote-node.cpp index 381a332e..f1ea2197 100644 --- a/dht/dht-remote-node.cpp +++ b/dht/dht-remote-node.cpp @@ -14,7 +14,7 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #include "dht.hpp" diff --git a/dht/dht-remote-node.hpp b/dht/dht-remote-node.hpp index ed37415c..e65c0429 100644 --- a/dht/dht-remote-node.hpp +++ b/dht/dht-remote-node.hpp @@ -14,7 +14,7 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/dht/dht.h b/dht/dht.h index c21a2e85..eacb2e4b 100644 --- a/dht/dht.h +++ b/dht/dht.h @@ -14,7 +14,7 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/dht/dht.hpp b/dht/dht.hpp index 5e83e548..b8d73c8e 100644 --- a/dht/dht.hpp +++ b/dht/dht.hpp @@ -14,7 +14,7 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #pragma once diff --git a/doc/TonSites-HOWTO b/doc/TonSites-HOWTO new file mode 100644 index 00000000..9d4005f0 --- /dev/null +++ b/doc/TonSites-HOWTO @@ -0,0 +1,78 @@ +The aim of this document is to provide a gentle introduction into TON Sites, which are (TON) Web sites accessed through the TON Network. TON Sites may be used as a convenient entry point for other TON Services. In particular, HTML pages downloaded from TON Sites may contain links to ton://... URIs representing payments that can be performed by the user by clicking to the link, provided a TON Wallet is installed on the user's device. + +From the technical perspective, TON Sites are very much like the usual Web sites, but they are accessed through the TON Network (which is an overlay network inside the Internet) instead of the Internet. More specifically, they have an ADNL address (instead of a more customary IPv4 or IPv6 address), and they accept HTTP queries via RLDP protocol (which is a higher-level RPC protocol built upon ADNL, the main protocol of TON Network) instead of the usual TCP/IP. All encryption is handled by ADNL, so there is no need to use HTTPS (i.e., TLS). + +In order to access existing and create new TON Sites one needs special gateways between the "ordinary" internet and the TON Network. Essentially, TON Sites are accessed with the aid of a HTTP->RLDP proxy running locally on the client's machine, and they are created by means of a reverse RLDP->HTTP proxy running on a remote web server. + +1. Compiling RLDP-HTTP Proxy +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The RLDP-HTTP Proxy is a special utility specially designed for accessing and creating TON Sites. Its current (alpha) version is a part of the general TON Blockchain source tree, available at GitHub repository ton-blockchain/ton. In order to compile the RLDP-HTTP Proxy, follow the instructions outlined in README and Validator-HOWTO. The Proxy binary will be located as + + rldp-http-proxy/rldp-http-proxy + +in the build directory. Alternatively, you may want to build just the Proxy instead of building all TON Blockchain projects. This can be done by invoking + + cmake --build . --target rldp-http-proxy + +in the build directory. + +2. Running RLDP-HTTP Proxy to access TON Sites +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +In order to access existing TON Sites, you need a running instance of RLDP-HTTP Proxy on your computer. It can be invoked as follows: + + rldp-http-proxy/rldp-http-proxy -p 8080 -c 3333 -C ton-global.config.json + +or + + rldp-http-proxy/rldp-http-proxy -p 8080 -a :3333 -C ton-global.config.json + +where is your public IPv4 address, provided you have one on your home computer. The TON Network global configuration file `ton-global.config.json` can be downloaded at https://test.ton.org/ton-global.config.json : + + wget https://test.ton.org/ton-global.config.json + +In the above example, 8080 is the TCP port that will be listened to at localhost for incoming HTTP queries, and 3333 is the UDP port that will be used for all outbound and inbound RLDP and ADNL activity, i.e., for connecting to the TON Sites via the TON Network. + +If you have done everything correctly, the Proxy will not terminate, but it will continue running in the terminal. It can be used now for accessing TON Sites. When you don't need it anymore, you can terminate it by pressing Ctrl-C, or simply by closing the terminal window. + +3. Accessing TON Sites +~~~~~~~~~~~~~~~~~~~~~~ + +Now suppose that you have a running instance of the RLDP-HTTP Proxy running on your computer and listening on localhost:8080 for inbound TCP connections, as explained above in Section 2. + +A simple test that everything is working property may be performed using programs such as Curl or WGet. For example, + + curl -x 127.0.0.1:8080 http://test.ton + +attempts to download the main page of (TON) Site `test.ton` using the proxy at `127.0.0.1:8080`. If the proxy is up and running, you'll see something like + + +

TON Blockchain Test Network — files and resources

+

News

+
    +... + + +because TON Site `test.ton` is currently set up to be a mirror of Web Site https://test.ton.org. + +Alternatively, you can set up `localhost:8080` as a HTTP proxy in your browser. For example, if you use Firefox, visit [Setup] -> General -> Network Settings -> Settings -> Configure Proxy Access -> Manual Proxy configuration, and type "127.0.0.1" into the field "HTTP Proxy", and "8080" into the field "Port". If you don't have Firefox yet, visit https://www.getfirefox.com first. + +Once you have set up `localhost:8080` as the HTTP proxy to be used in your browser, you can simply type the required URI, such as `http://test.ton`, in the navigation bar of your browser, and interact with the TON Site in the same way as with the usual Web Sites. + +4. Creating TON Sites +~~~~~~~~~~~~~~~~~~~~~ + +Most people will need just to access existing TON Sites, not to create new ones. However, if you want to create one, you'll need to run RLDP-HTTP Proxy on your server, along with the usual web server software such as Apache or Nginx. + +We suppose that you know already how to set up an ordinary web site, and that you have already configured one on your server, accepting incoming HTTP connections on TCP port :80, and defining the required TON Network domain name, say, `example.ton`, as the main domain name or an alias for your web site in the configuration of your web server. + +After that, you execute + + rldp-http-proxy -a :3333 -L example.ton -C ton-global.config.json + +in the background (you can try this in a terminal at first, but if you want your TON Site to run permanently, you'll have to use options `-d` and `-l ` as well). + +If all works properly, the RLDP-HTTP proxy will accept incoming HTTP queries from the TON Network via RLDP/ADNL running on UDP port 3333 (of course, you can use any other UDP port if you want to) of IPv4 address (in particular, if you are using a firewall, don't forget to allow `rldp-http-proxy` to receive and send UDP packets from this port), and it will forward these HTTP queries addressed to host `example.ton` to TCP port 80 at 127.0.0.1, i.e., to your ordinary Web server. + +You can visit TON Site `http://example.ton` from a browser running on a client machine as explained in Sections 2 and 3 and check whether your TON Site is actually available to the public. diff --git a/doc/catchain.tex b/doc/catchain.tex new file mode 100644 index 00000000..214ffd41 --- /dev/null +++ b/doc/catchain.tex @@ -0,0 +1,558 @@ +\documentclass[12pt,oneside]{article} +\usepackage[T1]{fontenc} +%\usepackage{euler} +\usepackage{amssymb, amsmath, amsfonts, stmaryrd} +\usepackage[mathscr]{euscript} +\usepackage{mathrsfs} +\usepackage{theorem} +\usepackage[english]{babel} +\usepackage{bm} +\usepackage[all]{xy} +\usepackage{array} +\usepackage{multirow} +%\usepackage{chngcntr} +%\CompileMatrices +\usepackage[bookmarks=false,pdfauthor={Nikolai Durov},pdftitle={Catchain Consensus: An Outline}]{hyperref} +\usepackage{fancyhdr} +\usepackage{caption} +% +\setlength{\headheight}{15.2pt} +\pagestyle{fancy} +\renewcommand{\headrulewidth}{0.5pt} +% +\def\makepoint#1{\medbreak\noindent{\bf #1.\ }} +\def\zeropoint{\setcounter{subsection}{-1}} +\def\zerosubpoint{\setcounter{subsubsection}{-1}} +\def\nxpoint{\refstepcounter{subsection}% + \smallbreak\makepoint{\thesubsection}} +\def\nxsubpoint{\refstepcounter{subsubsection}% + \smallbreak\makepoint{\thesubsubsection}} +\def\nxsubsubpoint{\refstepcounter{paragraph}% + \makepoint{\paragraph}} +%\setcounter{secnumdepth}{4} +%\counterwithin{paragraph}{subsubsection} +\def\refpoint#1{{\rm\textbf{\ref{#1}}}} +\let\ptref=\refpoint +\def\embt(#1.){\textbf{#1.}} +\def\embtx(#1){\textbf{#1}} +\def\emb#1{\textbf{#1.}} +\long\def\nodo#1{} +% +%\def\markbothsame#1{\markboth{#1}{#1}} +\fancyhf{} +\fancyfoot[C]{\thepage} +\def\markbothsame#1{\fancyhead[C]{#1}} +\def\mysection#1{\section{#1}\fancyhead[C]{\textsc{Chapter \textbf{\thesection.} #1}}} +\def\mysubsection#1{\subsection{#1}\fancyhead[C]{\small{\textsc{\textrm{\thesubsection.} #1}}}} +\def\myappendix#1{\section{#1}\fancyhead[C]{\textsc{Appendix \textbf{\thesection.} #1}}} +% +\let\tp=\textit +\let\vr=\textit +\def\opsc#1{\operatorname{\textsc{#1}}} +\def\Max{\operatorname{Max}} +\def\Sha{\opsc{sha256}} +\def\SHA#1{\opsc{sha#1}} +\def\Int{\opsc{int}} +\def\height{\opsc{height}} +\def\LT{\opsc{Lt}} +\def\VT{\opsc{Vt}} +\def\Submit{\opsc{Submit}} +\def\Approve{\opsc{Approve}} +\def\Reject{\opsc{Reject}} +\def\PreCommit{\opsc{PreCommit}} +\def\CommitSign{\opsc{CommitSign}} +\def\Vote{\opsc{Vote}} +\def\VoteFor{\opsc{VoteFor}} +\def\wround{\vr{round}} +\def\wattempt{\vr{attempt}} +\def\wcandidate{\vr{candidate}} +\def\wsignature{\vr{signature}} +\def\bbB{{\mathbb{B}}} +\def\bbP{{\mathbb{P}}} +\def\bbF{{\mathbb{F}}} +\def\bbZ{{\mathbb{Z}}} +\def\bbN{{\mathbb{N}}} +\def\st#1{{\mathbf{#1}}} +\def\sgn{\operatorname{sgn}} +\def\caret{\^{}} +\def\cF{\mathscr{F}} +% +\hfuzz=0.8pt + +\title{Catchain Consensus: An Outline} +\author{Nikolai Durov} +\begin{document} + +%\pagestyle{myheadings} +\maketitle + +\begin{abstract} + The aim of this text is to provide an outline of the Catchain Consensus Protocol, a Byzantine Fault Tolerant (BFT) protocol specifically crafted for block generation and validation in the TON Blockchain~\cite{TON}. This protocol can potentially be used for purposes other than block generation in a proof-of-stake (PoS) blockchain; however, the current implementation uses some optimizations valid only for this specific problem. +\end{abstract} + +\tableofcontents +\clearpage + +\mysection{Overview}\label{sect:overview} +The Catchain Consensus protocol builds upon the overlay network construction protocol and the overlay network broadcast protocol of TON Network (\cite{TON}). The catchain protocol itself can be decomposed into two separate protocols, one more low-level and general-purpose (the {\em Catchain protocol\/}\footnote{The original name of this protocol used during the initial stage of the research and development phase was {\em catch-chain} or {\em catchchain}, because it essentially is a special block{\em chain} dedicated to {\em catch\/}ing all events important for the consensus protocol; after saying and writing this name a lot of times it gradually got contracted to ``catchain''.}), and the other the high-level {\em (TON) block consensus protocol}, which makes use of the Catchain protocol. Higher levels in the TON protocol stack are occupied by the block generation and validation levels; however, all of them are executed essentially locally on one (logical) machine, with the problem of achieving consensus on the newly-generated block delegated to the Catchain protocol level. + +Here is an approximate diagram of the protocol stack employed by TON for block generation and distribution, showing the correct place of the Catchain Consensus protocol (or rather its two component protocols): +\begin{itemize} +\item {\it Top-level:} Block generation and block validation software, logically running on a stand-alone logical machine, with all the inputs provided and outputs handled by the lower-level protocols. The job of this software is to either generate a new valid block for a blockchain (a shardchain or the masterchain of the TON Blockchain; cf.~\cite{TON} for a discussion of the shardchains and the masterchain), or to check the validity of a block generated by somebody else. +\item {\it (TON) Block consensus protocol:\/} Achieves (byzantine fault tolerant) consensus on the block to be accepted as the next one in the current validator group for the masterchain or a shardchain. This level makes use of (the abstract interface of) the block generation and validation software, and builds upon the lower-level Catchain protocol. This protocol is explained in more detail in Section~\ptref{sect:blk.consensus}. +\item {\it Catchain protocol:\/} Provides secure persistent broadcasts in an overlay network (e.g., the task group of validators for a specific shardchain or the masterchain dedicated to generation, validation, and propagation of new blocks in this shardchain or masterchain), and detects attempts of ``cheating'' (protocol violation) on the part of some participants. This protocol is explained in more detail in Section~\ptref{sect:catchain}. +\item {\it (TON Network) overlay broadcast protocol:\/} A simple best-effort broadcast protocol for overlay networks in the TON Network as described in \cite{TON}. Simply broadcasts received broadcast messages to all neighbors in the same overlay network that did not receive a copy of these messages before, with minimal effort dedicated to keeping copies of undelivered broadcast messages for a short interval of time. +\item {\it (TON Network) overlay protocol:\/} Creates overlay networks (cf.~\cite{TON}) inside the ADNL protocol network, manages neighbor lists for these overlay networks. Each participant of an overlay network tracks several neighbors in the same overlay network and keeps dedicated ADNL connections (called {\em ``channels''\/}) to them, so that incoming messages can be efficiently broadcasted to all neighbors with minimal overhead. +\item {\it Abstract Datagram Network Layer (ADNL) protocol\/}: The basic protocol of the TON Network, that delivers packets (datagrams) between network nodes identified only by 256-bit abstract (ADNL) addresses, which effectively are cryptographic keys (or their hashes). +\end{itemize} + +This text aims to describe only the second and the third protocol in this suite, namely, the (TON) block consensus protocol and the (TON) Catchain protocol. + +A few words on the efficiency of the combined Catchain Consensus protocol. Firstly, it is a true Byzantine Fault Tolerant (BFT) protocol, in the sense that it eventually achieves consensus on a valid next block of the blockchain even if some participants (validators) exhibit arbitrarily malicious behavior, provided these malicious participants are less than one third of the total number of the validators. It is well-known that achieving BFT consensus is impossible if at least one third of participants are malicious (cf.~\cite{Byzantine}), so the Catchain Consensus protocol is as good as theoretically possible in this respect. Secondly, when the Catchain Consensus was first implemented (in December 2018) and tested on up to 300 nodes distributed all over the world, it achieved consensus on a new block in 6 seconds for 300 nodes and in 4--5 seconds for 100 nodes (and in 3 seconds for 10 nodes), even if some of these nodes fail to participate or exhibit incorrect behavior.\footnote{When the ratio of the malicious or non-participating or very slow validators grows up to one third, the protocol exhibits graceful degradation, with the block consensus time growing very slowly---say, by at most half a second---until the critical value of one third is almost achieved.} Since the TON Blockchain task groups are not expected to consist of more than a hundred validators (even if a total of a thousand or ten thousand validators are running, only a hundred of them with the largest stakes will generate new masterchain blocks, and the others will participate only in the creation of new shardchain blocks, each shardchain block generated and validated by 10--30 validators; of course, all numbers given here are configuration parameters (cf.\ \cite{TON} and \cite{TBC}) and can be adjusted later by a consensus vote of validators if necessary), this means that the TON Blockchain is able to generate new blocks once every 4--5 seconds, as originally planned. This promise has been further tested and found out to be fulfilled with the launch of the Test Network of the TON Blockchain a couple of months later (in March 2019). Therefore, we see that the Catchain Consensus protocol is a new member of the ever growing family of the practical BFT protocols (cf.~\cite{PBFT}), even though it is based on slightly different principles. + +\clearpage +\mysection{Catchain Protocol}\label{sect:catchain} +We have already explained in the Overview (cf.~\ptref{sect:overview}) that the BFT consensus protocol used by TON Blockchain for achieving consensus on new blockchain blocks consists of two protocols. We provide here a brief description of the {\em Catchain protocol}, the lower-lever of these two protocols that could be potentially used for purposes other than BFT consensus for blocks. The source code for the Catchcain protocol resides in subdirectory {\tt catchain} of the source tree. + +\nxpoint\emb{Prerequisites for running the Catchain protocol}\label{p:cc.prereq} +The main prerequisite for running (an instance of) the Catchain protocol is the ordered list of all nodes that are participating (or allowed to participate) in this specific instance of the protocol. This list consists of public keys and ADNL addresses of all participating nodes. It has to be provided from the outside when an instance of the Catchain protocol is created. + +\nxpoint\emb{Nodes participating in the block consensus protocol}\label{p:cc.nodes} +For the specific task of creating new blocks for one of blockchains (i.e., the masterchain or one of the active shardchains) of the TON Blockchain, a special task group consisting of several validators is created. The list of members of this task group is used both to create a private overlay network inside ADNL (this means that the only nodes that can join this overlay network are those explicitly listed during its creation) and to run the corresponding instance of the Catchain protocol. + +The construction of this list of members is the responsibility of the higher levels of the overall protocol stack (the block creation and validation software) and therefore is not the topic of this text (\cite{TBC} would be a more appropriate reference). It is sufficient to know at this point that this list is a deterministic function of the current (most recent) masterchain state (and especially of the current value of the configuration parameters, such as the active list of all validators elected for creating new blocks along with their respective weights). Since the list is computed deterministically, all validators compute the same lists and in particular each validator knows in which task groups (i.e., instances of the Catchain protocol) it participates without any further need for network communication or negotiation.\footnote{If some validators have outdated masterchain state, they may fail to compute correct task group lists and to participate in the corresponding catchains; in this respect, they are treated as if they were malicious or malfunctioning and do not affect the overall validity of the BFT protocol as long as less than one third of all validators fail in this fashion.} + +\nxsubpoint\emb{Catchains are created in advance} +In fact, not only the current values of the lists alluded to above are computed, but also their immediately subsequent (future) values are computed as well, so that the Catchain is usually created in advance. In this way it is already in place when the first block has to be created by the new instance of the validator task group. + +\nxpoint\emb{The genesis block and the identifier of a catchain}\label{sp:cc.ident} +A {\em catchain\/} (i.e., an instance of the Catchain protocol) is characterized by its {\em genesis block} or {\em genesis message}. It is a simple data structure containing some magic numbers, the purpose of the catchain (e.g., the identifier of the shardchain, for which the blocks will be generated, and the so-called {\em catchain sequence number}, also obtained from the masterchain configuration and used to distinguish subsequent instances of the catchain generating ``the same'' shardchain, but possibly with different participating validators), and, most importantly, the list of all participating nodes (their ADNL addresses and Ed25519 public keys as explained in~\ptref{p:cc.prereq}). The Catchain protocol itself uses only this list and the $\Sha$ hash of the overall data structure; this hash is used as an internal identifier of the catchain, i.e., of the instance of the Catchain protocol. + +\nxsubpoint\emb{Distribution of the genesis block} +Note that the genesis block is not distributed among the participating nodes; it is rather computed independently by each participating node as explained in \ptref{p:cc.nodes}. Since the hash of the genesis block is used as the catchain identifier (i.e., identifier of the specific instance of the Catchain protocol; cf.~\ptref{sp:cc.ident}), if a node (accidentally or intentionally) computes a different genesis block, it will be effectively locked out from participating in the ``correct'' instance of the protocol. + +\nxsubpoint\emb{List of nodes participating in a catchain} +Note that the (ordered) list of nodes participating in a catchain is fixed in the genesis block and hence it is known to all the participants and it is unambiguously determined by the hash of the genesis block (i.e., the catchain identifier), provided there are no (known) collisions for $\Sha$. Therefore, we fix the number of participating nodes $N$ in the discussion of one specific catchain below, and assume that the nodes are numbered from $1$ to $N$ (their real identities may be looked up in the list of participants using this index in range $1\ldots N$). The set of all participants will be denoted by $I$; we assume that $I=\{1\ldots N\}$. + +\nxpoint\emb{Messages in a catchain. Catchain as a process group} +One perspective is that a catchain is a {\em (distributed) process group\/} consisting of $N$ known and fixed {\em (communicating) processes\/} (or {\em nodes\/} in the preceding terminology), and these processes generate {\em broadcast messages}, that are eventually broadcasted to all members of the process group. The set of all processes is denoted by $I$; we usually assume that $I=\{1\ldots N\}$. The broadcasts generated by each process are numbered starting from one, so the $n$-th broadcast of process $i$ will receive {\em sequence number\/} or {\em height\/} $n$; each broadcast should be uniquely determined by the identity or the index~$i$ of the originating process and its height~$n$, so we can think of the pair $(i,n)$ as the natural identifier of a broadcast message inside a process group.\footnote{In the Byzantine environment of a catchain this is not necessarily true in all situations.} The broadcasts generated by the same process $i$ are expected to be delivered to every other process in exactly the same order they have been created, i.e., in the increasing order of their height. In this respect a catchain is very similar to a process group in the sense of \cite{Birman} or \cite{DistrSys}. The principal difference is that a catchain is a ``hardened'' version of a process group tolerant to possible Byzantine (arbitrarily malicious) behavior of some participants. + +\nxsubpoint\emb{Dependence relation on messages} +One can introduce a {\em dependence relation\/} on all messages broadcasted in a process group. This relation must be a strict partial order $\prec$, with the property that $m_{i,k}\prec m_{i,k+1}$, where $m_{i,k}$ denotes the $k$-th message broadcasted by group member process with index~$i$. The meaning of $m\prec m'$ is that {\em $m'$ depends on $m$}, so that the (broadcast) message $m'$ can be processed (by a member of the process group) only if $m$ has been processed before. For instance, if the message $m'$ represents the reaction of a group member to another message $m$, then it is natural to set $m\prec m'$. If a member of the process group receives a message $m'$ before all its dependencies, i.e., messages $m\prec m'$, have been processed (or {\em delivered\/} to the higher-level protocol), then its processing (or {\em delivery\/}) is delayed until all its dependencies are delivered. + +We have defined the dependence relation to be a strict partial order, so it must be transitive ($m''\prec m'$ and $m'\prec m$ imply $m''\prec m$), antisymmetric (at most one of $m'\prec m$ and $m\prec m'$ can hold for any two messages $m$ and $m'$) and antireflexive ($m\prec m$ never holds). If we have a smaller set of ``basic dependencies'' $m'\to m$, we can construct its transitive closure $\to^+$ and put $\prec:=\to^+$. The only other requirement is that every broadcast of a sender depends on all previous broadcasts of the same sender. It is not strictly necessary to assume this; however, this assumption is quite natural and considerably simplifies the design of a messaging system inside a process group, so the Catchain protocol makes this assumption. + +\nxsubpoint\label{sp:dep.cone}\emb{Dependence set or cone of a message} +Let $m$ be a (broadcast) message inside a process group as above. We say that the set $D_m:=\{m'\,:\,m'\prec m\}$ is the {\em dependence set\/} or {\em dependence cone\/} of message~$m$. In other words, $D_m$ is the {\em principal ideal\/} generated by $m$ in the partially ordered finite set of all messages. It is precisely the set of all messages that must be delivered before $m$ is delivered. + +\nxsubpoint\label{sp:ext.dep.cone}\emb{Extended dependence cone of a message} +We also define $D^+_m$, the {\em extended dependence cone of $m$,} by $D^+_m:=D_m\cup\{m\}$. + +\nxsubpoint\emb{Cones, or ideals with respect to $\prec$} +More generally, we say that a subset $D$ of messages is a {\em cone\/} if it is an ideal with respect to the dependence relation $\prec$, i.e., if $m\in D$ and $m'\prec m$ imply $m'\in D$. Of course, the dependence cone $D_m$ and the extended dependence cone $D^+_m$ of any message $m$ are cones (because any principal ideal in a partially ordered set is an ideal). + +\nxsubpoint\emb{Identification of cones with the aid of vector time} +Recall that we have assumed that any message depends on all preceding messages of the same sender, i.e.\ $m_{i,s}\prec m_{i,s+1}$ for any $i\in I$ and any $s>0$, such that $m_{i,s+1}$ exists. This implies that any cone $D$ is completely characterized by $N$ values $\VT(D)_i$ indexed by $i\in I$: +\begin{equation} + \VT(D)_i:=\sup\{s\in\bbN\,:\,m_{i,s}\in D\}=\inf\{s\in\bbN_0\,:\,m_{i,s+1}\not\in D\} +\end{equation} +(if no message $m_{i,s}$ is in $D$, we set $\VT(D)_i:=0$.) Indeed, it is clear that +\begin{equation} + m_{i,s}\in D\Leftrightarrow s\leq\VT(D)_i +\end{equation} +We say that the vector $\VT(D)=(\VT(D)_i)_{i\in I}\in\bbN_0^I$ with non-negative components $\VT(D)_i$ is the {\em vector time\/} or {\em vector timestamp\/} corresponding to cone~$D$ (cf.~\cite{Birman} or \cite{DistrSys} for a more detailed discussion of vector time). + +\nxsubpoint\emb{Partial order on vector timestamps} +We introduce a partial order $\leq$ on the set of all possible vector times $\bbN_0^I$, which is the product of the usual orders on $\bbN_0$: +\begin{equation} + {\bm x}=(x_i)_{i\in I}\leq{\bm y}=(y_i)_{i\in I}\quad\text{iff}\quad x_i\leq y_i\quad\text{for all $i\in I$} +\end{equation} +It is immediate that $D\subset D'$ iff $\VT(D)\leq\VT(D')$; therefore, $\VT$ is a strict order-preserving embedding of the set of all cones contained in the set of all messages into $\bbN_0^I$. + +\nxsubpoint\emb{Vector timestamp $\VT(m)$ of a message $m$} +Given any message $m$, we define its {\em vector timestamp\/} $\VT(m)$ as $\VT(D_m)$. In other words, message $m$ can be delivered only after the first $\VT(m)_j$ messages generated by process $j$ are delivered, and this is true for all $j\in I$. + +If $i$ is the sender of message $m$, and $s$ is the height of message $m$, so that $m=m_{i,s}$, then $\VT(m)_i=s-1$. We can define the {\em adjusted vector timestamp\/} $\VT^+(m)$ of message $m$ by setting $VT^+(m)_j=VT(m)_j$ for $j\neq i$, $VT^+(m)_i=\VT(m)_i+1=s$. Alternatively, $\VT^+(m)=\VT(D^+_m)$, where $D^+_m:=D_m\cup\{m\}$ is the {\em extended dependence cone of $m$} (cf.~\ptref{sp:ext.dep.cone}). + +Note that $m'\preceq m$ iff $D^+_{m'}\subset D^+_m$ iff $\VT^+(m')\leq\VT^+(m)$ in $\bbN_0^I$, where $m'\preceq m$ means ``$m'\prec m$ or $m'=m$''. Similarly, $m'\prec m$ iff $D^+_{m'}\subset D_m$ iff $\VT^+(m')\leq\VT(m)$. In other words, {\em the dependence relation $\prec$ on (some or all) messages is completely determined by the adjusted vector timestamps of these messages.} + +\nxsubpoint\emb{Using vector timestamps to correctly deliver broadcast messages} +Vector timestamps can be used (in non-byzantine settings) to correctly deliver messages broadcast in a process group.\footnote{We assume that all broadcast messages in the process group are ``causal broadcasts'' or ``cbcast'' in the terminology of \cite{Birman}, because we need only cbcasts for the implementation of Catchain protocol and Catchain consensus.} Namely, suppose that every broadcast message $m=m_{i,s}$ contains the index of its sender $i$ and the vector timestamp of this message $\VT(m)$. Then each receiver $j$ knows whether the message can be delivered or not. For this, $j$ keeps track of the cone $C_j$ of all messages delivered so far, for example by maintaining a {\em current timestamp} $\VT(j)$ equal to $\VT(C_j)$. In other words, $\VT(j)_k$ is the count of messages of sender $k$ processed by $j$ so far. If $\VT(m)\leq\VT(j)$, then the message $m$ is delivered immediately and $\VT(j)$ is updated to $\sup(\VT(j),\VT^+(m))$ afterwards; this is equivalent to increasing $\VT(j)_i$ by one, where $i$ is the original sender of message~$m$. If this condition is not met, then $m$ may be put into a waiting queue until $\VT(j)$ becomes large enough. Instead of passively waiting for the required broadcasts, $j$ can construct the list of message indices $(i',s')$ that are implicitly mentioned in $\VT(m)$ of some received but not delivered message $m$, and request messages with these indices from the neighbors from which $j$ learned about $m$ and $\VT(m)$; an alternative strategy (actually employed by the current implementation of the Catchain protocol) is to request these messages from randomly chosen neighbors from time to time. The latter strategy is simpler because it does not require remembering the immediate sources of all received messages (which may become unavailable anyway). + +\nxpoint\emb{Message structure in a catchain. Catchain as a multi-blockchain} +The message structure in a catchain is a bit more complicated than described above because of the necessity to support a BFT protocol. In particular, vector timestamps are not sufficient in a Byzantine setting. They have to be complemented by descriptions based on maximal elements of a dependence cone (such descriptions are typically used in non-byzantine settings only when the process group is very large, so that vector timestamp sizes become prohibitive). + +\nxsubpoint\emb{Describing cones by means of their maximal elements} +An alternative way (to using a vector timestamp) of describing a message cone $D$ is by listing all its {\em maximal elements\/} $\Max(D)$, i.e.\ elements $m\in D$, such that $m\prec m'$ does not hold for any $m'\in D$. Of course, one needs a suitable way of referring to messages without including them completely in order for this representation to be practical. + +\nxsubpoint\emb{Message identifiers inside a catchain} +Catchain protocol uses {\em $\Sha$} hashes of (suitably serialized) messages as their unique identifiers. If we assume that there are no collisions for $\Sha$ (computable in reasonable, e.g., polynomial time), then a message $m$ is completely identified within the process group by its hash $\Sha(m)$. + +\nxsubpoint\emb{Message headers}\label{sp:msg.hdr} +The header of a message $m=m_{i,s}$ inside a catchain (i.e., an instance of the Catchain protocol) always contains the index $i$ of its sender, the height $s$, the catchain identifier (i.e., the hash of the genesis message, cf.~\ptref{sp:cc.ident})) and the set of hashes of maximal elements of the dependence cone of $m$, i.e., the set $\{\Sha(m')\,:\,m'\in\Max(D_m)\}$. In particular, the hash $\Sha(m_{i,s-1})$ of the previous message of the same sender is always included since $m_{i,s-1}\in\Max(D_m)$ if $s>1$; for performance reasons, there is a separate field in the message header containing $\Sha(m_{i,s-1})$. If $s=1$, then there is no previous message, so the hash of the genesis message (i.e., the catchain identifier, cf.~\ptref{sp:cc.ident}) is used instead. + +The vector timestamp $\VT(m)$ is not included into the message header; however, the header implicitly determines $\VT(m)$ since +\begin{equation} + \VT(m)=\sup_{m'\in D_m}\VT^+(m')=\sup_{m'\in\Max(D_m)}\VT^+(m') +\end{equation} + +Note that the message header is a part of the message, and in particular the hash of a message (i.e., the message identifier) depends on all data listed in the header. Therefore, we assume that the message identifier implicitly determines all the dependencies of the corresponding message (if there are no known collisions for $\Sha$). + +\nxsubpoint\emb{Message signatures} +Apart from that, every message in a catchain is signed by its creator. Since the list of participating nodes (processes) in a catchain is known in advance, and this list includes the public keys of all processes, these message signatures can be checked by a receiving process immediately after a message is received. If the signature is invalid, the message is discarded without any further processing. + +\nxsubpoint\emb{Message encryption} +All messages in a catchain are also encrypted before being transferred from a node to its neighbor in the private overlay network underlying the catchain. However, this encryption is performed by lower-level network protocols (such as ADNL) and is not relevant to the discussion here. We would like to mention that correct encryption is possible here only because the list of participating processes includes not only the public keys of all processes, but also their ADNL addresses (which effectively are public encryption keys for network transmission). + +Notice that even if the encryption had been absent, this would not violate the BFT properties of the protocol, because faking a message from another sender would not be possible because of the signatures. However, this might lead to a leak of information to outside observers, which is often undesirable. + +\nxsubpoint\emb{Alternative perspective: a catchain as a multi-blockchain} +Note that all messages created by the same sender $i$ in a catchain turn out to have a simple ``blockchain structure'', because the header of $m_{i,s+1}$ contains the hash $\Sha(m_{i,s})$ (among other hashes of messages from $\Max(D_{m_{i,s+1}})$) of the previous message of sender~$i$. In this way each process $i$ generates a simple blockchain consisting of its messages, with each ``block'' of this blockchain corresponding to one message and referring to the previous block by its hash, and sometimes includes references to blocks (i.e., messages) of other processes by mentioning the hashes of these blocks in its blocks. Each block is signed by its creator. The resulting structure is very similar to that of an ``asynchronous payment channel'' considered in \cite[5]{TON}, but with $N$ participants instead of 2. + +\nxpoint\emb{Message propagation in a catchain}\label{sp:cc.msg.prop} +Now we are ready to describe message propagation in a catchain. Namely: +\begin{itemize} +\item The (lower-level) overlay network protocol maintains a list of neighbors in the private overlay network underlying the catchain and provides ADNL channels to each of these neighbors. This private overlay network has the same list of members (processes, nodes) as the catchain, and the neighbors of each node form an (oriented) subgraph on the set of all partipating nodes. This (essentially random) subgraph is strongly connected with probability very close to one. +\item Each process generates some new messages from time to time (as needed by the higher-level protocol). These messages are augmented by catchain message headers as outlined in~\ptref{sp:msg.hdr}, signed, and propagated to all known neighbors using the ADNL channels established by the overlay protocol. +\item In contrast with the usual simple overlay broadcast protocol, the messages received from neighbors are not immediately re-broadcasted to all other neighbors that are not known yet to have a copy of them. Instead, the signature is checked first, and invalid messages are discarded. Then the message is either delivered (if all its dependent messages have been already delivered), or put into a waiting queue. In the latter case, all the required messages mentioned in its header (i.e., the set $\Max(D_m)$) are pulled from the neighbor that sent this message (apart from that, attempts to download these missing messages from random neighbors are performed from time to time). If necessary, this process is repeated recursively until some messages can be delivered. Once a message is ready for local delivery (i.e., all its dependencies are already present), it is also re-broadcasted to all neighbors in the overlay network. +\item Apart from the recursive ``pull'' mechanism described above, a faster vector timestamp-based mechanism is also used, so that messages can be queried from neighbors by their senders and heights (learned from vector timestamps of received messages). Namely, each process sends a special query containing the current vector timestamp to a randomly chosen neighbor from time to time. This peer-to-peer query leads to its receiver sending back all or some messages unknown to the sender (judging by their vector timestamps). +\item This faster vector timestamp-based mechanism can be disabled for messages of some sender as soon as a ``fork'' is detected, i.e., a second message with the same sender $i$ and height $s$, but with a different hash, is learned from a neighbor, for example, during the fast or slow ``pull'' process. Once a fork created by $i$ is detected, the corresponding component $\VT_i$ of all subsequent vector timestamps is set to a special value $\infty$ to indicate that comparing the values of these components does not make sense anymore. +\item When a message is delivered (to the higher-level protocol), this message is added into the cone $C$ of processed messages of the current process (and the current vector timestamp is updated accordingly), and all subsequent messages generated by the current process will be assumed to depend on all the messages delivered so far (even if this is not logically necessary from the perspective of the higher-level protocol). +\item If the set $\Max(C)$ of the maximal elements of the cone of processed messages becomes too large (contains more elements than a certain amount fixed in advance by the genesis message of the catchain), then the Catchain protocol asks the higher-level protocol to generate a new message (empty if no useful payload is available). After this new message is generated (and immediately delivered to the current process), $C$ is updated and $\Max(C)$ consists of only one element (the new message). In this way the size of $\Max(C)$ and therefore the size of the message header always remain bounded. +\item Once a message~$m$ is delivered and the set $C$ is modified to include this message, a timer is set, and after some small delay the higher-level protocol is asked to create a new message (empty if necessary), so that this new message $m^*$ would refer to the new~$C$, similarly to the procedure described in the previous item. This new message $m^*$ is pushed to all neighbors; since its header contains $\Max(C)$ for the new~$C$, and $m\in C$, the neighbors learn not only about the newly-generated message $m^*$, but also about the original received message $m$. If some neighbors do not have a copy of $m$ yet, they would require one (from the current process or not). +\item All (broadcast) messages received and especially created in a catchain are stored into a special local database. This is especially important for the newly-created messages (cf.~\ptref{sp:new.msg.flush}): if a message is created and sent to neighbors, but not saved into the database (and flushed to disk) before the creating process crashes and is restarted, then another message with the same sender and height can be created after restart, thus effectively leading to an involuntary ``fork''. +\end{itemize} + +\nxpoint\emb{Forks and their prevention} +One can see that the multi-blockchain structure of a catchain outlined above (with references to other blocks by their hashes and with signatures) leaves not too much possibilities for ``cheating'' in a consensus protocol built upon a catchain (i.e., using the catchain as a means for broadcasting messages inside a process group). The only possibility that is not detected immediately consists of creating two (or more) different versions of the same message $m_{i,s}$ (say, $m'_{i,s}$ and $m''_{i,s}$), and sending one version of this message $m'_{i,s}$ to some peers and a different version $m''_{i,s}$ to others. If $s$ is minimal (for a fixed $i$), then this corresponds to a {\em fork\/} in the blockchain terminology: two different next blocks $m'_{i,s}$ and $m''_{i,s}$ for the same previous block $m_{i,s-1}$. + +Therefore, the catchain protocol takes care to detect forks as soon as possible and prevent their propagation. + +\nxsubpoint\emb{Detection of forks} +The detection of forks is simple: if there are two different blocks $m'_{i,s}$ and $m''_{i,s}$ with the same creator $i\in I$ and the same height $s\geq1$, and with valid signatures of~$i$, then this is a fork. + +\nxsubpoint\emb{Fork proofs}\label{sp:fork.proofs} +Block signatures in the catchain protocol are created in such a way that creating {\em fork proofs\/} (i.e., the proof that a process~$i$ has intentionally created a fork) is especially simple since it is the hash of a very small structure (containing a magic number, the values of $i$ and $s$, and the hash of the remainder of the message) that is actually signed. Therefore, only two such small structures and two signatures are required in a fork proof. + +\nxsubpoint\emb{External punishment for creating forks} +Notice that an external punishment for creating catchain forks may be used in the proof-of-stake blockchain generation context. Namely, the fork proofs may be submitted to a special smart contract (such as the elector smart contract of the TON Blockchain), checked automatically, and some part or all of the stake of the offending party may be confiscated. + +\nxsubpoint\emb{Internal processing of forks} +Once a fork (created by~$i$) is detected (by another process~$j$), i.e.\ $j$ learns about two different messages $m_{i,s}$ and $m'_{i,s}$ created by $i$ and having same height $s$ (usually this happens while recursively downloading dependencies of some other messages), $j$ starts ignoring~$i$ and all of its subsequent messages. They are not accepted and not broadcasted further. However, messages created by~$i$ prior to the fork detection may be still downloaded if they are referred to in messages (blocks) created by processes that did not see this fork before referring to such messages created by~$i$. + +\nxsubpoint\emb{Accepting messages from a ``bad'' process is bad}\label{sp:no.bad.accept} +Furthermore, if process $i$ learns about a fork created by process $j$, then $i$ shows this to its neighbors by creating a new service broadcast message that contains the corresponding fork proof (cf.~\ptref{sp:fork.proofs}). Afterwards this and all subsequent messages of $j$ cannot directly depend on any messages by the known ``bad'' producer $i$ (but they still can refer to messages from another party $k$ that directly or indirectly refer to messages of~$i$ if no fork by~$i$ was known to $k$ at the time when the referring message was created). If $j$ violates this restriction and creates messages with such invalid references, these messages will be discarded by all (honest) processes in the group. + +\nxsubpoint\emb{The set of ``bad'' group members is a part of the intrinsic state}\label{sp:bad.proc.set} +Each process~$i$ keeps its own copy of the set of known ``bad'' processes in the group, i.e., those processes that have created at least one fork or have violated \ptref{sp:no.bad.accept}. This set is updated by adding~$j$ into it as soon as $i$ learns about a fork created by~$j$ (or about a violation of~\ptref{sp:no.bad.accept} by $j$); after that, a callback provided by the higher-level protocol is invoked. This set is used when a new broadcast message arrives: if the sender is bad, then the message is ignored and discarded. + +\clearpage +\mysection{Block Consensus Protocol}\label{sect:blk.consensus} +We explain in this section the basic workings of the TON Block Consensus Protocol (cf.~\ptref{sect:overview}), which builds upon the generic Catchain protocol (cf.~\ptref{sect:catchain}) to provide the BFT protocol employed for generating and validating new blocks of TON Blockchain. The source code for the TON Block Consensus protocol resides in subdirectory {\tt validator-session} of the source tree. + +\nxpoint\emb{Internal state of the Block Consensus Protocol}\label{p:cc.state} +The higher-level Block Consensus Protocol introduces a new notion to the catchain: that of an {\em internal state\/} of the Block Consensus Protocol (BCP), sometimes also (not quite correctly) called ``the internal state of the catchain'' or simply {\em catchain state}. Namely, each process $i\in I$ has a well-determined internal state $\sigma_{C_i}$ after a subset of messages (actually always a dependence cone) $C_i$ is delivered by the Catchain protocol to the higher-level protocol (i.e., to the Block Consensus Protocol in this case). Furthermore, this state $\sigma_{C_i}=\sigma(C_i)$ depends only on cone~$C_i$, but not on the identity of the process $i\in I$, and can be defined for any dependence cone~$S$ (not necessarily a cone $C_i$ of delivered messages for some process $i$ at some point). + +\nxsubpoint\emb{Abstract structure of the internal state} +We start with an abstract structure of the internal state employed by BCP; more specific details will be provided later. + +\nxsubpoint\emb{Updating the internal state} +The Catchain protocol knows nothing about the internal state; it simply invokes appropriate callbacks supplied by the higher-level protocol (i.e., the BCP) whenever a message $m$ is delivered. It is the job of the higher-level protocol to compute the new state $\sigma_{S'}$ starting from the previously computed state $\sigma_S$ and the message $m$, where $S'=S\cup\{m\}$ (and necessarily $S\supset D_m$, otherwise $m$ could not have been delivered at this point). + +\nxsubpoint\emb{Recursive formula for updating the internal state}\label{sp:abs.state.upd} +The abstract setup for computing $\sigma_S$ for all cones $S$ consists of three components: +\begin{itemize} +\item A value $\sigma_\emptyset$ for the initial state (this value actually depends on the genesis block of the catchain; we ignore this dependence here because we consider only one catchain at this point). +\item A function $f$ that computes the state $\sigma_{D^+_m}$ from the previous state $\sigma_{D_m}$ and the newly-delivered message $m$: +\begin{equation}\label{eq:state.rec} + \sigma_{D^+_m}=f(\sigma_{D_m},m) +\end{equation} +where $D_m$ is the dependence cone of message $m$ and $D^+_m=D_m\cup\{m\}$ its extended dependence cone (cf.~\ptref{sp:ext.dep.cone}). In most cases, $f$ will actually satisfy the stronger condition +\begin{equation}\label{eq:state.rec.x} + \sigma_{S\cup\{m\}}=f(\sigma_S,m)\quad\text{if $S$ and $S\cup\{m\}$ are cones and $m\not\in S$} +\end{equation} +However, this stronger condition is not required by the update algorithm. +\item A ``merge function'' $g$ that computes $\sigma_{S\cup T}$ from $\sigma_S$ and $\sigma_T$: +\begin{equation}\label{eq:state.merge} + \sigma_{S\cup T}=g(\sigma_S,\sigma_T)\quad\text{for any cones $S$ and $T$} +\end{equation} +(the union of two cones always is a cone.) +This function $\sigma$ is applied by the update algorithm only in the specific case $T=D^+_m$ and $m\not\in S$. +\end{itemize} + +\nxsubpoint\emb{Commutativity and associativity of $g$}\label{sp:g.assoc} +Note that \eqref{eq:state.merge} (for arbitrary cones $S$ and $T$) implies associativity and commutativity of $g$, at least when $g$ is applied to possible states (values of form $\sigma_S$ for some cone $S$). In this respect $g$ defines a commutative monoid structure on the set $\Sigma=\{\sigma_S\,:\,S$ is a cone$\}$. Usually $g$ is defined or partially defined on a larger set $\tilde\Sigma$ of state-like values, and it may be commutative and associative on this larger set $\tilde\Sigma$, i.e., +$g(x,y)=g(y,x)$ and $g(x,g(y,z))=g(g(x,y),z)$ for $x$, $y$, $z\in\tilde\Sigma$ (whenever both sides the equality are defined), with $\sigma_\emptyset$ as an unit, i.e., $g(x,\sigma_\emptyset)=x=g(\sigma_\emptyset,x)$ for $x\in\tilde S$ (under the same condition). However, this property, useful for the formal analysis of the consensus algorithm, is not strictly required by the state update algorithm, because this algorithm uses $g$ in a deterministic fashion to compute $\sigma_S$. + +\nxsubpoint\emb{Commutativity of $f$} +Note that $f$, if it satisfies the stronger condition \eqref{eq:state.rec.x}, must also exhibit a commutativity property +\begin{equation}\label{eq:step.upd.comm} + f\bigl(f(\sigma_S,m),m'\bigr)=f\bigl(f(\sigma_S,m'),m\bigr) +\end{equation} +whenever $S$ is a cone and $m$ and $m'$ are two messages with $D_m\subset S$, $D_{m'}\subset S$, $m\not\in S$ and $m'\not\in S$, because in this case $S\cup\{m\}$, $S\cup\{m'\}$ and $S\cup\{m,m'\}$ are also cones, and \eqref{eq:state.rec.x} implies that both sides of \eqref{eq:step.upd.comm} are equal to $\sigma_{S\cup\{m,m'\}}$. Similarly to \ptref{sp:g.assoc}, $f$ is usually defined or partially defined on the product of a larger set $\tilde\Sigma$ of state-like values and of a set of message-like values; it may exhibit the ``commutativity'' property \eqref{eq:step.upd.comm} or not on this larger set. If it does, this might be useful for formal analysis of the algorithms relying on $\sigma_S$, but this property is not strictly necessary. + +\nxsubpoint\emb{The state update algorithm} +The state update algorithm (independently executed by each process $i$) employed by the catchain (actually by the higher-level BCP) uses $\sigma_\emptyset$, $f$ and $g$ as follows: +\begin{itemize} +\item The algorithm keeps track of all $\sigma_{D^+_m}$ for all messages $m$ delivered so far. +\item The algorithm keeps track of $\sigma_{C_i}$, where $C_i$ is the current dependence cone, i.e., the set of all messages $m$ delivered (to the current process $i$). The initial value of $\sigma_{C_i}$ is $\sigma_\emptyset$. +\item When a new message $m$ is delivered, the value of $\sigma_{D^m}$ is computed by a repeated application of $g$ since $D_m=\bigcup_{m'\in D_m}D^+_{m'}=\bigcup_{m'\in\Max(D_m)}D^+_{m'}$; therefore, if $\Max(D_m)=\{m'_1,\ldots,m'_k\}$, then + \begin{equation}\label{eq:merge.many} + \sigma_{D_m}=g\Bigl(\ldots g\bigl(g(\sigma_{D^+_{m'_1}},\sigma_{D^+_{m'_2}}),\sigma_{D^+_{m'_3}}\bigr),\ldots \sigma_{D^+_{m'_k}}\Bigr)\quad. + \end{equation} +The set $\Max(D_m)$ is explicitly listed in the header of message $m$ in some fixed order $m'_1$, \dots, $m'_k$; the above formula is applied with respect to this order (so the computation of $D_m$ is deterministic). The first element in this list always is the previous message of the sender of $m$, i.e., if $m=m_{i,s+1}$, then $m'_1=m_{i,s}$. +\item After that, the value of $\sigma_{D^+_m}$ is computed by an application of $f$: $\sigma_{D^+_m}=f(\sigma_{D_m},m)$. This value is memorized for future use. +\item Finally, when a new message $m$ is delivered to the current process $i$, thus updating $C_i$ to $C'_i:=C_i\cup\{m\}$, the algorithm uses the computed value $\sigma_{D^+_m}$ to update the current state + \begin{equation} + \sigma_{C'_i}=g(\sigma_{C_i},\sigma_{D^+_m}) + \end{equation} + This state, however, is ``virtual'' in the sense that it can be slightly changed later (especially if $g$ is not commutative). Nevertheless, it is used to make some important decisions by the higher-level algorithm (BCP). +\item Once a new message $m$ is generated and locally delivered, so that $C_i$ becomes equal to $D^+_m$, the previously computed value of $\sigma_{C_i}$ is discarded and replaced with $\sigma_{D^+_m}$ computed according to the general algorithm described above. If $g$ is not commutative or not associative (for example, it may happen that $g(x,y)$ and $g(y,x)$ are different but equivalent representatations of the same state), then this might lead to a slight change of the current ``virtual'' state of process $i$. +\item If the lower-level (catchain) protocol reports to the higher-level protocol that a certain process $j\not\in i$ is ``bad'' (i.e., $j$ is found out to have created a fork, cf.~\ptref{sp:bad.proc.set}, or to have knowingly endorsed a fork by another process, cf.~\ptref{sp:no.bad.accept}), then the current (virtual) state $\sigma_{C_i}$ is recomputed from scratch using the new set $C'_i=\bigcup_{\text{$m\in C_i$, $m$ was created by ``good'' process $k$}}D^+_m$ and the ``merge'' function $g$ applied to the set of $\sigma_{D^+_m}$ where $m$ runs through the set of last messages of the processes known to be good (or through the set of maximal elements of this set). The next created outbound message will depend only on the messages from $C'_i$. +\end{itemize} + +\nxsubpoint\emb{Necessity to know the internal state of the other processes} +Formula \eqref{eq:merge.many} implies that process~$i$ must also keep track of $\sigma_{D^+_m}$ for all messages $m$, created by this process or not. However, this is possible since these internal states are also computed by appropriate applications of the update algorithm. Therefore, BCP computes and remembers all $\sigma_{D^+_m}$ as well. + +\nxsubpoint\emb{Function $f$ would suffice} +Notice that the update algorithm applies $g$ only to compute $\sigma_{S\cup D^+_m}=g(\sigma_S,\sigma_{D^+_m})$ when $S$ is a cone containing $D_m$, but not containing~$m$. Therefore, every actual application of $g$ could have been replaced by an application of~$f$ satisfying the extended property \eqref{eq:state.rec.x}: +\begin{equation} + \sigma_{S\cup D^+_m}=g(\sigma_S,\sigma_{D^+_m})=f(\sigma_S,m) +\end{equation} +However, the update algorithm does not use this ``optimization'', because it would disable the more important optimizations described below in \ptref{sp:share.substr} and \ptref{sp:memoize}. + +\nxpoint\emb{The structure of the internal state} +The structure of the internal state is optimized to make the {\em transition function\/ $f$} of~\eqref{eq:state.rec} and the {\em merge function\/ $g$} of~\eqref{eq:state.merge} as efficiently computable as possible, preferably without the need of potentially unbounded recursion (just some loops). This motivates the inclusion of additional components into the internal state (even if these components are computable from the remainder of the internal state), which have to be stored and updated as well. This process of including additional components is similar to that employed while solving problems using dynamic programming, or to that used while proving statements by mathematical (or structural) induction. + +\nxsubpoint\emb{The internal state is a representation of a value of an abstract algebraic data type}\label{sp:state.node.tree} +The internal representation of the internal state is essentially a (directed) tree (or rather a directed acyclic graph) or a collection of nodes; each node contains some immediate (usually integer) values and several pointers to other (previously constructed) nodes. If necessary, an extra {\em constructor tag\/} (a small integer) is added at the beginning of a node to distinguish between several possibilities. This structure is very similar to that used to represent values of abstract algebraic data types in functional programming languages such as Haskell. + +\nxsubpoint\emb{The internal state is persistent} +The internal state is {\em persistent}, in the sense that the memory used to allocate the nodes which are part of the internal state is never freed up while the catchain is active. Furthermore, the internal state of a catchain is actually allocated inside a huge contiguous memory buffer, and new nodes are always allocated at the end of the used portion of this buffer by advancing a pointer. In this way the references to other nodes from a node inside this buffer may be represented by an integer offset from the start of the buffer. Every internal state is represented by a pointer to its root node inside this buffer; this pointer can be also represented by an integer offset from the start of the buffer. + +\nxsubpoint\emb{The internal state of a catchain is flushed to an append-only file}\label{sp:state.apponly.file} +The consequence of the structure of the buffer used to store the internal states of a catchain explained above is that it is updated only by appending some new data at its end. This means that the internal state (or rather the buffer containing all the required internal states) of a catchain can be flushed to an append-only file, and easily recovered after a restart. The only other data that needs to be stored before restarts is the offset (from the start of the buffer, i.e., of this file) of the current state of the catchain. A simple key-value database can be used for this purpose. + +\nxsubpoint\label{sp:share.substr}\emb{Sharing data between different states} +It turns out that the tree (or rather the dag) representing the new state $\sigma_{S\cup\{m\}}=f(\sigma_S,m)$ shares large subtrees with the previous state $\sigma_S$, and, similarly, $\sigma_{S\cup T}=g(\sigma_S,\sigma_T)$ shares large subtrees with $\sigma_S$ and $\sigma_T$. The persistent structure used for representing the states in BCP makes possible reusing the same pointers inside the buffer for representing such shared data structures instead of duplicating them. + +\nxsubpoint\label{sp:memoize}\emb{Memoizing nodes} +Another technique employed while computing new states (i.e., the values of function~$f$) is that of {\em memoizing new nodes}, also borrowed from functional programming languages. Namely, whenever a new node is constructed (inside the huge buffer containing all states for a specific catchain), its hash is computed, and a simple hash table is used to look up the latest node with the same hash. If a node with this hash is found, and it has the same contents, then the newly-constructed node is discarded and a reference to the old node with the same contents is returned instead. On the other hand, if no copy of the new node is found, then the hash table is updated, the end-of-buffer (allocation) pointer is advanced, and the pointer to the new node is returned to the caller. + +In this way if different processes end up making similar computations and having similar states, large portions of these states will be shared even if they are not directly related by application of function~$f$ as explained in~\ptref{sp:share.substr}. + +\nxsubpoint\emb{Importance of optimization techniques} +The optimization techniques \ptref{sp:share.substr} and \ptref{sp:memoize} used for sharing parts of different internal states inside the same catchain are drastically important for improving the memory profile and the performance of BCM in a large process group. The improvement is several orders of magnitude in groups of $N\approx100$ processes. Without these optimizations BCM would not be fit for its intended purpose (BFT consensus on new blocks generated by validators in TON Blockchain). + +\nxsubpoint\emb{Message $m$ contains a hash of state $\sigma_{D^+_m}$} +Every message $m$ contains a (Merkle) hash of (the abstract representation of) the corresponding state $\sigma_{D^+_m}$. Very roughly, this hash is computed recursively using the tree of nodes representation of~\ptref{sp:state.node.tree}: all node references inside a node are replaced with (recursively computed) hashes of the referred nodes, and a simple 64-bit hash of the resulting byte sequence is computed. This hash is also used for memoization as described in \ptref{sp:memoize}. + +The purpose of this field in messages is to provide a sanity check for the computations of $\sigma_{D^+_m}$ performed by different processes (and possibly by different implementations of the state update algorithm): once $\sigma_{D^+_m}$ is computed for a newly-delivered message $m$, the hash of computed $\sigma_{D^+_m}$ is compared to the value stored in the header of~$m$. If these values are not equal, an error message is output into an error log (and no further actions are taken by the software). These error logs can be examined to detect bugs or incompatibilities between different versions of BCP. + +\nxpoint\emb{State recovery after restart or crashes} +A catchain is typically used by the BCP for several minutes; during this period, the program (the validator software) running the catchain protocol may be terminated and restarted, either deliberately (e.g., because of a scheduled software update) or unintensionally (the program might crash because of a bug in this or some other subsystem, and be restarted afterwards). One way of dealing with this situation would be to ignore all catchains not created after the last restart. However, this would lead to some validators not participating in creating any blocks for several minutes (until next catchain instances are created), which is undesirable. Therefore, a catchain state recovery protocol is run instead after every restart, so that the validator could continue participating in the same catchain. + +\nxsubpoint\emb{Database of all delivered messages}\label{sp:msg.db} +To this end, a special database is created for each active catchain. This database contains all known and delivered messages, indexed by their identifiers (hashes). A simple key-value storage suffices for this purpose. The hash of the most recent outbound message $m=m_{i,s}$ generated by the current process $i$ is also stored in this database. After restart, all messages up to $m$ are recursively delivered in proper order (in the same way as if all these messages had been just received from the network in an arbitrary order) and processed by the higher-level protocol, until $m$ finally is delivered, thus recovering the current state. + +\nxsubpoint\emb{Flushing new messages to disk}\label{sp:new.msg.flush} +We have already explained in~\ptref{sp:cc.msg.prop} that newly-created messages are stored in the database of all delivered messages (cf.~\ptref{sp:msg.db}) and the database is flushed to disk before the new message is sent to all network neighbors. In this way we can be sure that the message cannot be lost if the system crashes and is restarted, thus avoiding the creation of involuntary forks. + +\nxsubpoint\emb{Avoiding the recomputation of states $\sigma_{D^+_m}$} +An implementation might use an append-only file containing all previously computed states as described in~\ptref{sp:state.apponly.file} to avoid recomputing all states after restart, trading off disk space for computational power. However, the current implementation does not use this optimization. + +\nxpoint\emb{High-level description of Block Consensus Protocol}\label{p:bcp.descr} +Now we are ready to present a high-level description of the Block Consensus Protocol employed by TON Blockchain validators to generate and achieve consensus on new blockchain blocks. Essentially, it is a three-phase commit protocol that runs over a catchain (an instance of the Catchain protocol), which is used as a ``hardened'' message broadcast system in a process group. + +\nxsubpoint\emb{Creation of new catchain messages} +Recall that the lower-level Catchain protocol does not create broadcast messages on its own (with the only exception of service broadcasts with fork proofs, cf.~\ptref{sp:no.bad.accept}). Instead, when a new message needs to be created, the higher-level protocol (BCP) is asked to do this by invoking a callback. Apart from that, the creation of new messages may be triggered by changes in the current virtual state and by timer alarms. + +\nxsubpoint\emb{Payload of catchain messages}\label{sp:payload} +In this way the payload of catchain messages is always determined by the higher level protocol, such as BCP. For BCP, this payload consists of +\begin{itemize} +\item Current Unix time. It must be non-decreasing on subsequent messages of the same process. (If this restriction is violated, all processes processing this message will tacitly replace this Unix time by the maximum Unix time seen in previous messages of the same sender.) +\item Several (zero or more) {\em BCP events\/} of one of the admissible types listed below. +\end{itemize} + +\nxsubpoint\emb{BCP events} +We have just explained that the payload of a catchain message contains several (zero or more) BCP events. Now we list all admissible BCP event types. +\begin{itemize} +\item $\Submit(\wround,\wcandidate)$ --- suggest a new block candidate +\item $\Approve(\wround,\wcandidate,\wsignature)$ --- a block candidate has passed local validation +\item $\Reject(\wround,\wcandidate)$ --- a block candidate failed local validation +\item $\CommitSign(\wround,\wcandidate,\wsignature)$ --- block candidate has been accepted and signed +\item $\Vote(\wround,\wcandidate)$ --- vote for a block candidate +\item $\VoteFor(\wround,\wcandidate)$ --- this block candidate must be voted for in this round (even if the current process has another opinion) +\item $\PreCommit(\wround,\wcandidate)$ --- a preliminary committment to a block candidate (used in three-phase commit scheme) +\end{itemize} + +\nxsubpoint\emb{Protocol parameters} +Several parameters of BCP must be fixed in advance (in the genesis message of the catchain, where they are initialized from the current values of configuration parameters of masterchain state): +\begin{itemize} +\item $K$ --- duration of one attempt (in seconds). It is an integer amount of seconds in the current implementation; however, this is an implementation detail, not a restriction of the protocol +\item $Y$ --- number of {\em fast\/} attempts to accept a candidate +\item $C$ --- block candidates suggested during one round +\item $\Delta_i$ for $1\leq i\leq C$ --- delay before suggesting the block candidate with priority $i$ +\item $\Delta_\infty$ --- delay before approving the null candidate +\end{itemize} +Possible values for these parameters are $K=8$, $Y=3$, $C=2$, $\Delta_i=2(i-1)$, $\Delta_\infty=2C$. + +\nxsubpoint\emb{Protocol overview} +The BCP consist of several {\em rounds\/} that are executed inside the same catchain. More than one round may be active at one point of time, because some phases of a round may overlap with other phases of other rounds. Therefore, all BCP events contain an explicit round identifier $\wround$ (a small integer starting from zero). Every round is terminated either by (collectively) accepting a {\em block candidate\/} suggested by one of participating processes, or by accepting a special {\em null candidate\/}---a dummy value indicating that no real block candidate was accepted, for example because no block candidates were suggested at all. After a round is terminated (from the perspective of a participating process), i.e., once a block candidate collects $\CommitSign$ signatures of more that $2/3$ of all validators, only $\CommitSign$ events may be added to that round; the process automatically starts participating in the next round (with the next identifier) and ignores all BCP events with different values of $\wround$.\footnote{This also means that each process implicitly determines the Unixtime of the start of the next round, and computes all delays, e.g., the block candidate submission delays, starting from this time.} + +Each round is subdivided into several {\em attempts}. Each attempt lasts a predetermined time period of $K$ seconds (BCP uses clocks to measure time and time intervals and assumes that clocks of ``good'' processes are more or less in agreement with each other; therefore, BCP is not an asynchronous BFT protocol). Each attempt starts at Unixtime exactly divisible by $K$ and lasts for $K$ seconds. The attempt identifier $\wattempt$ is the Unixtime of its start divided by $K$. Therefore, the attempts are numbered more or less consecutively by 32-bit integers, but not starting from zero. The first $Y$ attempts of a round are {\em fast\/}; the remaining attempts are {\em slow}. + +\nxsubpoint\emb{Attempt identification. Fast and slow attempts} +In contrast with rounds, BCP events do not have a parameter to indicate the attempt they belong to. Instead, this attempt is implicitly determined by the Unix time indicated in the payload of the catchain message containing the BCP event (cf.~\ptref{sp:payload}). Furthermore, the attempts are subdivided into {\em fast\/} (the first $Y$ attempts of a round in which a process takes part) and {\em slow\/} (the subsequent attempts of the same round). This subdivision is also implicit: the first BCP event sent by a process in a round belongs to a certain attempt, and $Y$ attempts starting from this one are considered fast by this process. + +\nxsubpoint\emb{Block producers and block candidates} +There are $C$ designated block producers (member processes) in each round. The (ordered) list of these block producers is computed by a deterministic algorithm (in the simplest case, processes $i$, $i+1$, \dots, $i+C-1$ are used in the $i$-th round, with the indices taken modulo $N$, the total number of processes in the catchain) and is known to all participants without any extra communication or negotiation. The processes are ordered in this list by decreasing priority, so the first member of the list has the highest priority (i.e., if it offers a block candidate not too late, this block candidate has very high chances to be accepted by the protocol). + +The first block producer may suggest a block candidate immediately after the round starts. Other block producers can suggest block candidates only after some delay $\Delta_i$, where $i$ is the index of the producer in the list of designated block producers, with $0=\Delta_1\leq\Delta_2\leq\ldots$. After some predetermined period of time $\Delta_\infty$ elapses from the round start, a special {\em null candidate\/} is assumed automatically suggested (even if there are no explicit BCP events to indicate this). Therefore, at most $C+1$ block candidates (including the null candidate) are suggested in a round. + +\nxsubpoint\emb{Suggesting a block candidate} +A block candidate for TON Block\-chain consists of two large ``files'' --- the block and the collated data, along with a small header containing the description of the block being generated (most importantly, the complete {\em block identifier\/} for the block candidate, containing the workchain and the shard identifier, the block sequence number, its file hash and its root hash) and the $\Sha$ hashes of the two large files. Only a part of this small header (including the hashes of the two files and other important data) is used as $\wcandidate$ in BCP events such as $\Submit$ or $\CommitSign$ to refer to a specific block candidate. The bulk of the data (most importantly, the two large files) is propagated in the overlay network associated with the catchain by the streaming broadcast protocol implemented over ADNL for this purpose (cf.~\cite[5]{TON}). This bulk data propagation mechanism is unimportant for the validity of the consensus protocol (the only important point is that the hashes of the large files are part of BCP events and hence of the catchain messages, where they are signed by the sender, and these hashes are checked after the large files are received by any participating nodes; therefore, nobody can replace or corrupt these files). A $\Submit(\wround,\wcandidate)$ BCP event is created in the catchain by the block producer in parallel with the propagation of the block candidate, indicating the committment of this block producer to this specific block candidate. + +\nxsubpoint\emb{Processing block candidates} +Once a process observes a $\Submit$ BCP event in a delivered catchain message, it checks the validity of this event (for instance, its originating process must be in the list of designated producers, and current Unixtime must be at least the start of the round plus the minimum delay $\Delta_i$, where $i$ is the index of this producer in the list of designated producers), and if it is valid, remembers it in the current catchain state (cf.~\ptref{p:cc.state}). After that, when a streaming broadcast containing the files with this block candidates (with correct hash values) is received (or immediately, if these files are already present), the process invokes a validator instance to validate the new block candidate (even if this block candidate was suggested by this process itself!). Depending on the result of this validation, either an $\Approve(\wround,\wcandidate,\wsignature)$ or a $\Reject(\wround,\wcandidate)$ BCP event is created (and embedded into a new catchain message). Note that the $\wsignature$ used in $\Approve$ events uses the same private key that will be ultimately used to sign the accepted block, but the signature itself is different from that used in $\CommitSign$ (the hash of a structure with different magic number is actually signed). Therefore, this interim signature cannot be used to fake the acceptance of this block by this particular validator process to an outside observer. + +\nxsubpoint\emb{Overview of one round} +Each round of BCP proceeds as follows: +\begin{itemize} +\item At the beginning of a round several processes (from the predetermined list of designated producers) submit their block candidates (with certain delays depending on the producer priority) and reflect this fact by means of $\Submit$ events (incorporated into catchain messages). +\item Once a process receives a submitted block candidate (i.e., observes a $\Submit$ events and receives all necessary files by means external with respect to the consensus protocol), it starts the validation of this candidate and eventually creates either an $\Approve$ or a $\Reject$ event for this block candidate. +\item During each {\em fast attempt\/} (i.e., one of the first $Y$ attempts) every process votes either for a block candidate that has collected votes of more than $2/3$ of all processes, or, if there are no such candidates yet, for the valid (i.e., $\Approve$d by more than $2/3$ of all processes) block candidate with the highest priority. The voting is performed by means of creating $\Vote$ events (embedded into new catchain messages). +\item During each {\em slow attempt\/} (i.e., any attempt except $Y$ first ones) every process votes either for a candidate that was $\PreCommit$ted before (by the same process), or for a candidate that was suggested by $\VoteFor$. +\item If a block candidate has received votes from more than $2/3$ of all processes during the current attempt, and the current process observes these votes (which are collected in the catchain state), a $\PreCommit$ event is created, indicating that the process will vote only for this candidate in the future. +\item If a block candidate collects $\PreCommit$s from more than $2/3$ of all processes inside an attempt, then it is assumed to be accepted (by the group), and each process that observes these $\PreCommit$s creates a $\CommitSign$ event with a valid block signature. These block signatures are registered in the catchain, and are ultimately collected to create a ``block proof'' (containing signatures of more than $2/3$ of validators for this block). This block proof is the external output of the consensus protocol (along with the block itself, but without its collated data); it is ultimately propagated in the overlay network of all full nodes that have subscribed to new blocks of this shard (or of the masterchain). +\item Once a block candidate collects $\CommitSign$ signatures from more than $2/3$ of all validators, the round is considered finished (at least from the perspective of a process that observes all these signatures). After that, only a $\CommitSign$ can be added to that round by this process, and the process automatically starts participating in the next round (and ignores all events related to all other rounds). +\end{itemize} + +Note that the above protocol may lead to a validator signing (in a $\CommitSign$ event) a block candidate that was $\Reject$ed by the same validator before (this is a kind of ``submitting to the will of majority''). + +\nxsubpoint\emb{$\Vote$ and $\PreCommit$ messages are created deterministically}\label{sp:force.vote} +Note that each process can create at most one $\Vote$ and at most one $\PreCommit$ event in each attempt. Furthermore, these events are completely determined by the state $\sigma_{D_m}$ of the sender of catchain message~$m$ containing such an event. Therefore, the receiver can detect invalid $\Vote$ or $\PreCommit$ events and ignore them (thus mitigating byzantine behavior of other participants). On the other hand, a message $m$ that should contain a $\Vote$ or a $\PreCommit$ event according to the corresponding state $\sigma_{D_m}$ but does not contain one can be received. In this case, the current implementation automatically creates missing events and proceeds as if $m$ had contained them from the very beginning. However, such instances of byzantine behavior are either corrected or ignored (and a message is output into the error log), but the offending processes are not punished otherwise (because this would require very large misbehavior proofs for outside observers that do not have access to the internal state of the catchain). + +\nxsubpoint\emb{Multiple $\Vote$s and $\PreCommit$s of the same process}\label{sp:vote.fork} +Note that a process usually ignores subsequent $\Vote$s and $\PreCommit$s generated by the same originating process inside the same attempt, so normally a process can vote for at most one block candidate. However, it may happen that a ``good'' process indirectly observes a fork created by a byzantine process, with $\Vote$s for different block candidates in different branches of this fork (this can happen if the ``good'' process learns about these two branches from two other ``good'' processes that did not see this fork before). In this case, both $\Vote$s (for different candidates) are taken into account (added into the merged state of the current process). A similar logic applies to $\PreCommit$s. + +\nxsubpoint\emb{Approving or rejecting block candidates} +Notice that a block candidate cannot be $\Approve$d or $\Reject$ed before it has been $\Submit$ted (i.e., an $\Approve$ event that was not preceded by a corresponding $\Submit$ event will be ignored), and that a candidate cannot be approved before the minimum time of its submission (the round start time plus the priority-dependent delay $\Delta_i$) is reached, i.e., any ``good'' process will postpone the creation of its $\Approve$ until this time. Furthermore, one cannot $\Approve$ more than one candidate of the same producer in the same round (i.e., even if a process $\Submit$s several candidates, only one of them---presumably the first one---will be $\Approve$d by other ``good'' processes; as usual, this means that subsequent $\Approve$ events will be ignored by ``good'' processes on receival). + +\nxsubpoint\emb{Approving the null block candidate} +The implicit null block candidate is also explicitly approved (by creating an $\Approve$ event) by all (good) processes, once the delay $\Delta_\infty$ from the start of the round expires. + +\nxsubpoint\emb{Choosing a block candidate for voting}\label{sp:vote.rules} +Each process chooses one of the available block candidates (including the implicit null candidate) and votes for this candidate (by creating a $\Vote$ event) by applying the following rules (in the order they are presented): +\begin{itemize} +\item If the current process created a $\PreCommit$ event for a candidate during one of the previous attempts, and no other candidate has collected votes of more than $2/3$ of all processes since (i.e., inside one of the subsequent attempts, including the current one so far; we say that the $\PreCommit$ event is still {\em active\/} in this case), then the current process votes for this candidate again. +\item If the current attempt is fast (i.e., one of the first $Y$ attempts of a round from the perspective of the current process), and a candidate has collected votes of more than $2/3$ of all processes during the current or one of the previous attempts, the current process votes for this candidate. In the case of a tie, the candidate from the latest of all such attempts is chosen. +\item If the current attempt is fast, and the previous rules do not apply, then the process votes for the candidate with the highest priority among all {\em eligible candidates}, i.e., candidates that have collected $\Approve$s (observable by the current process) from more than $2/3$ of all processes. +\item If the current attempt is slow, then the process votes only after it receives a valid $\VoteFor$ event in the same attempt. If the first rule is applicable, the process votes according to it (i.e., for the previously $\PreCommit$ed candidate). Otherwise it votes for the block candidate that is mentioned in the $\VoteFor$ event. If there are several such valid events (during the current attempt), the candidate with the smallest hash is selected (this may happen in rare situations related to different $\VoteFor$ events created in different branches of a fork, cf.~\ptref{sp:vote.fork}). +\end{itemize} +The ``null candidate'' is considered to have the least priority. It also requires an explicit $\Approve$ before being voted for (with the exception of the first two rules). + +\nxsubpoint\emb{Creating $\VoteFor$ events during slow attempts} +A $\VoteFor$ event is created at the beginning of a slow attempt by the {\em coordinator\/} --- the process with index $\wattempt\bmod N$ in the ordered list of all processes participating in the catchain (as usual, this means that $\VoteFor$ created by another process will be ignored by all ``good'' processes). This $\VoteFor$ event refers to one of the block candidates (including the null candidate) that have collected $\Approve$s from more than $2/3$ of all processes, usually randomly chosen among all such candidates. Essentially, this is a suggestion to vote for this block candidate directed to all other processes that do not have an active $\PreCommit$. + +\nxpoint\emb{Validity of BCP} +Now we present a sketch of the proof of validity of TON Block Consensus Protocol (BCP) described above in~\ptref{p:bcp.descr}, assuming that less than one third of all processes exhibit byzantine (arbitrarily malicious protocol-violating) behavior, as it is customary for Byzantine Fault Tolerant protocols. During this subsection, we consider only one round of BCP protocol, subdivided into several attempts. + +\nxsubpoint\emb{Fundamental assumption}\label{sp:fund.ass} +Let us emphasize once again that we assume that {\em less than one third of all processes are byzantine}. All other processes are assumed to be {\em good}, i.e., they follow the protocol. + +\nxsubpoint\emb{Weighted BCP} +The reasoning in this subsection is valid for the {\em weighted variant of BCP} as well. In this variant, each process $i\in I$ is pre-assigned a positive weight $w_i>0$ (fixed in the genesis message of the catchain), and statements about ``more than $2/3$ of all processes'' and ``less than one third of all processes'' are understood as ``more than $2/3$ of all processes by weight'', i.e., ``a subset $J\subset I$ of processes with total weight $\sum_{j\in J}w_j>\frac{2}{3}\sum_{i\in I} w_i$'', and similarly for the second property. In particular, our ``fundamental assumption'' \ptref{sp:fund.ass} is to be understood in the sense that ``the total weight of all byzantine processes is less than one third of the total weight of all processes''. + +\nxsubpoint\emb{Useful invariants} +We collect here some useful invariants obeyed by all BCP events during one round of BCP (inside a catchain). These invariants are enforced in two ways. Firstly, any ``good'' (non-byzantine) process will not create events violating these invariants. Secondly, even if a ``bad'' process creates an event violating these invariants, all ``good'' processes will detect this when a catchain message containing this event is delivered to BCP and ignore such events. Some possible issues related to forks (cf.~~\ptref{sp:vote.fork}) remain even after these precautions; we indicate how these issues are resolved separately, and ignore them in this list. So: +\begin{itemize} +\item There is at most one $\Submit$ event by each process (inside one round of BCP). +\item There is at most one $\Approve$ or $\Reject$ event by each process related to one candidate (and even to all candidates created by the same designated block producer).\footnote{In fact, $\Reject$s appear only in this restriction, and do not affect anything else. Therefore, any process can abstain from sending $\Reject$s without violating the protocol, and $\Reject$ events could have been removed from the protocol altogether. Instead, the current implementation of the protocol still generates $\Reject$s, but does not check anything on their receipt and does not remember them in the catchain state. Only a message is output into the error log, and the offending candidate is stored into a special directory for future study, because $\Reject$s usually indicate either the presence of a byzantine adversary, or a bug in the collator (block generation) or validator (block verification) software either on the node that suggested the block or on the node that created the $\Reject$ event.} This is achieved by requiring all ``good'' processes to ignore (i.e., not to create $\Approve$s or $\Reject$s for) all candidates suggested by the same producer but the very first one they have learned about. +\item There is at most one $\Vote$ and at most one $\PreCommit$ event by each process during each attempt. +\item There is at most one $\VoteFor$ event during each (slow) attempt. +\item There is at most one $\CommitSign$ event by each process. +\item During a slow attempt, each process votes either for its previously $\PreCommit$ted candidate, or for the candidate indicated in the $\VoteFor$ event of this attempt. +\end{itemize} +One might somewhat improve the above statements by adding word ``valid'' where appropriate (e.g., there is at most one {\em valid\/} $\Submit$ event\dots). + +\nxsubpoint\emb{More invariants}\label{sp:more.inv} +\begin{itemize} +\item There is at most one eligible candidate (i.e., candidate that has received $\Approve$s from more than $2/3$ of all processes) from each designated producer, and no eligible candidates from other producers. +\item There are at most $C+1$ eligible candidates in total (at most $C$ candidates from $C$ designated producers, plus the empty candidate). +\item A candidate may be accepted only if it has collected more than $2/3$ $\PreCommit$s during the same attempt (more precisely, a candidate is accepted only if there are $\PreCommit$ events created by more than $2/3$ of all processes for this candidate and belonging to the same attempt). +\item A candidate may be $\Vote$d for, $\PreCommit$ted, or mentioned in a $\VoteFor$ only if it is an {\em eligible candidate}, meaning that has previously collected $\Approve$s from more than $2/3$ of all validators (i.e., a valid $\Vote$ event may be created for a candidate only if $\Approve$ events for this candidate have been previously created by more than $2/3$ of all processes and registered in catchain messages observable from the message containing the $\Vote$ event, and similarly for $\PreCommit$ and $\VoteFor$ events). +\end{itemize} + +\nxsubpoint\emb{At most one block candidate is accepted}\label{sp:acc.unique} +Now we claim that {\em at most one block candidate can be accepted (in a round of BCP)}. Indeed, a candidate can be accepted only if it collects $\PreCommit$s from more than $2/3$ of all processes inside the same attempt. Therefore, two different candidates cannot achieve this during the same attempt (otherwise more than one third of all validators must have created $\PreCommit$s for two different candidates inside an attempt, thus violating the above invariants; but we have assumed that less than one third of all validators exhibit byzantine behavior). Now suppose that two different candidates $c_1$ and $c_2$ have collected $\PreCommit$s from more than $2/3$ of all processes in two different attempts $a_1$ and $a_2$. We may assume that $a_1a_1$, or at least cannot vote for any other candidate, unless another candidate $c'$ collects $\Vote$s of more than $2/3$ of all processes during a subsequent attempt (and this invariant is enforced even if some processes attempt not to create these new $\Vote$ events for $c_1$, cf.~\ptref{sp:force.vote}). Therefore, if $c_2\neq c_1$ has collected the necessary amount of $\PreCommit$s during attempt $a_2>a_1$, there is at least one attempt $a'$, $a_1a$, and did not observe votes of more than $2/3$ of all validators for any candidate $\neq c$ during any attempts $a'>a$. Any process has at most one active $\PreCommit$, and if it has one, it must vote only for the precommitted candidate. + +Now we see that if a process with an active $\PreCommit$ for a candidate $c$ since attempt $a$ observes a valid $\PreCommit$ (usually by another process) for a candidate $c'$ created during some later attempt $a'>a$, then the first process must also observe all dependencies of the message that contains the newer $\PreCommit$; these dependencies necessarily include valid $\Vote$s from more than $2/3$ of all validators for the same candidate $c'\neq c$ created during the same attempt $a'>a$ (because otherwise the newer $\PreCommit$ would not be valid, and would be ignored by the first process); by definition, the observation of all these $\Vote$s deactivates the original $\PreCommit$. + +\nxsubpoint\emb{Assumptions for proving the convergence of the protocol}\label{sp:conv.ass} +Now we are going to prove that the protocol described above {\em converges\/} (i.e., terminates after accepting a block candidate) with probability one under some assumptions, which essentially tell us that there are enough ``good'' processes (i.e., processes that diligently follow the protocol and do not introduce arbitrary delays before sending their new messages), and that these good processes enjoy good network connectivity at least from time to time. More precisely, these assumptions are as follows: +\begin{itemize} +\item There is a subset $I^+\subset I$ consisting of ``good'' processes and containing more than $2/3$ of all processes. +\item All processes from $I^+$ have well-synchronized clocks (differing by at most $\tau$, where $\tau$ is a bound for network latency described below). +\item If there are infinitely many attempts, then infinitely many attempts are ``good'' with respect to network connectivity between processes from~$I^+$, meaning that all messages created by a process from $I^+$ during this attempt or earlier are delivered to any other process from $I^+$ within at most $\tau>0$ seconds after being created with probability at least $q>0$, where $\tau>0$ and $00$ is a fixed parameter. +\item A process from $I^+$, when it is its turn to be the coordinator of a slow attempt, chooses a candidate for $\VoteFor$ uniformly at random among all eligible candidates (i.e., those candidates that have collected $\Approve$s from more than $2/3$ of all validators). +\end{itemize} + +\nxsubpoint\emb{The protocol terminates under these assumptions} +Now we claim that {\em (each round of) the BCP protocol as described above terminates with probability one under the assumptions listed in~\ptref{sp:conv.ass}}. The proof goes as follows. +\begin{itemize} +\item Let us assume that the protocol does not converge. Then it continues running forever. We are going to ignore the first several attempts, and consider only attempts $a_0$, $a_0+1$, $a_0+2$, \dots\ starting from some $a_0$, to be chosen later. +\item Since all processes from $I^+$ continue participating in the protocol, they will create at least one message not much later than the start of the round (which may be perceived slightly differently by different processes). For instance, they will create an $\Approve$ for the null candidate not later than $\Delta_\infty$ seconds from the start of the round. Therefore, they will consider all attempts slow at most $KY$ seconds afterwards. By choosing $a_0$ appropriately, we can assume that all attempts we consider are slow from the perspective of all processes from~$I^+$. +\item After a ``good'' attempt $a\geq a_0$ all processes from $I^+$ will see the $\Approve$s for the null candidate created by all other processes from~$I^+$, and will deem the null candidate eligible henceforth. Since there are infinitely many ``good'' attempts, this will happen sooner or later with probability one. Therefore, we can assume (increasing $a_0$ if necessary) that there is at least one eligible candidate from the perspective of all processes from $I^+$, namely, the null candidate. +\item Furthermore, there will be infinitely many attempts $a\geq a_0$ that are perceived slow by all processes from $I^+$, that have a coordinator from $I^+$, and that are ``good'' (with respect to the network connectivity) as defined in~\ptref{sp:conv.ass}. Let us call such attempts ``very good''. +\item Consider one of ``very good'' slow attempts $a$. With probability $q'>0$, its coordinator (which belongs to $I^+$) will wait for $\tau'\in(\tau,K-3\tau)$ seconds before creating its $\VoteFor$ event. Consider the most recent $\PreCommit$ event created by any process from~$I^+$; let us suppose it was created during attempt $a'0$, the catchain message carrying this $\PreCommit$ will be already delivered to the coordinator at the time of generation of its $\VoteFor$ event. In that case, the catchain message carrying this $\VoteFor$ will depend on this $\PreCommit(c')$ event, and all ``good'' processes that observe this $\VoteFor$ will also observe its dependencies, including this $\PreCommit(c')$. We see that {\em with probability at least $qq'$, all processes from $I^+$ that receive the $\VoteFor$ event during a ``very good'' slow attempt receive also the most recent $\PreCommit$ (if any).} +\item Next, consider any process from $I^+$ that receives this $\VoteFor$, for a randomly chosen eligible candidate $c$, and suppose that there are already some $\PreCommit$s, and that the previous statement holds. Since there are at most $C+1$ eligible candidates (cf.~\ptref{sp:more.inv}), with probability at least $1/(C+1)>0$ we'll have $c=c'$, where $c'$ is the most recently $\PreCommit$ted candidate (there is at most one such candidate by~\ptref{sp:all.precomm.same}). In this case, all processes from $I^+$ will vote for $c=c'$ during this attempt immediately after they receive this $\VoteFor$ (which will be delivered to any process $j\in I^+$ less than $K-2\tau$ seconds after the beginning of the attempt with probability $qq'$). Indeed, if a process $j$ from $I^+$ did not have an active $\PreCommit$, it will vote for the value indicated in $\VoteFor$, which is $c$. If $j$ had an active $\PreCommit$, and it is as recent as possible, i.e., also created during attempt $a'$, then it must have been a $\PreCommit$ for the same value $c'=c$ (because we know about at least one valid $\PreCommit$ for $c'$ during attempt $a'$, and all other valid $\PreCommit$s during attempt $a'$ must be for the same $c'$ by~\ptref{sp:all.precomm.same}). Finally, if $j$ had an active $\PreCommit$ from an attempt $0$ for some fixed value of $p$, the protocol will terminate successfully with probability one. +\end{itemize} + +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% +% bibliography +% +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +\clearpage +\markbothsame{\textsc{References}} + +\begin{thebibliography}{2} +\bibitem{Birman} + {\sc K.~Birman}, {\sl Reliable Distributed Systems: Technologies, Web Services and Applications}, Springer, 2005. + +\bibitem{PBFT} + {\sc M.~Castro, B.~Liskov, et al.}, {\sl Practical byzantine fault tolerance}, {\it Proceedings of the Third Symposium on Operating Systems Design and Implementation\/} (1999), p.~173--186, available at \url{http://pmg.csail.mit.edu/papers/osdi99.pdf}. + +\bibitem{TON} + {\sc N.~Durov}, {\sl Telegram Open Network}, 2017. + +\bibitem{TBC} + {\sc N.~Durov}, {\sl Telegram Open Network Blockchain}, 2018. + +\bibitem{Byzantine} + {\sc L.~Lamport, R.~Shostak, M.~Pease}, {\sl The byzantine generals problem}, {\it ACM Transactions on Programming Languages and Systems}, {\bf 4/3} (1982), p.~382--401. + +\bibitem{HoneyBadger} + {\sc A.~Miller, Yu Xia, et al.}, {\sl The honey badger of BFT protocols}, Cryptology e-print archive 2016/99, \url{https://eprint.iacr.org/2016/199.pdf}, 2016. + +\bibitem{DistrSys} + {\sc M.~van Steen, A.~Tanenbaum}, {\sl Distributed Systems, 3rd ed.}, 2017. +\end{thebibliography} + +\end{document} diff --git a/doc/fiftbase.tex b/doc/fiftbase.tex index 162f6784..e20c2490 100644 --- a/doc/fiftbase.tex +++ b/doc/fiftbase.tex @@ -1340,8 +1340,10 @@ Fift has several words for {\em hashmap\/} or {\em (TVM) dictionary\/} manipulat \item {\tt udict!}, {\tt udict!+}, {\tt b>udict!}, {\tt b>udict!+}, variants of {\tt idict!}, {\tt idict!+}, {\tt b>idict!}, {\tt b>idict!+}, but with an unsigned $n$-bit integer $x$ used as a key. \item {\tt sdict!}, {\tt sdict!+}, {\tt b>sdict!}, {\tt b>sdict!+}, variants of {\tt idict!}, {\tt idict!+}, {\tt b>idict!}, {\tt b>idict!+}, but with the first $n$ data bits of {\em Slice\/}~$x$ used as a key. \item {\tt idict@} ($x$ $D$ $n$ -- $v$ $-1$ or $0$), looks up the key represented by signed big-endian $n$-bit {\em Integer\/}~$x$ in the dictionary represented by {\em Cell\/}~$D$. If the key is found, returns the corresponding value as a {\em Slice\/}~$v$ and $-1$. Otherwise returns $0$. -\item {\tt udict@} ($x$ $D$ $n$ -- $v$ $-1$ or $0$), similar to {\tt idict@}, but with an {\em un}signed big-endian $n$-bit {\em Integer\/}~$x$ used as a key. -\item {\tt sdict@} ($k$ $D$ $n$ -- $v$ $-1$ or $0$), similar to {\tt idict@}, but with the key provided in the first $n$ bits of {\em Slice\/}~$k$. +\item {\tt idict@-} ($x$ $D$ $n$ -- $D'$ $v$ $-1$ or $D$ $0$), looks up the key represented by signed big-endian $n$-bit {\em Integer\/}~$x$ in the dictionary represented by {\em Cell\/}~$D$. If the key is found, deletes it from the dictionary and returns the modified dictionary $D'$, the corresponding value as a {\em Slice\/}~$v$, and $-1$. Otherwise returns the unmodified dictionary $D$ and $0$. +\item {\tt idict-} ($x$ $D$ $n$ -- $D'$ $-1$ or $D$ $0$), deletes integer key $x$ from dictionary $D$ similarly to {\tt idict@-}, but does not return the value corresponding to $x$ in the old dictionary~$D$. +\item {\tt udict@}, {\tt udict@-}, {\tt udict-}, variants of {\tt idict@}, {\tt idict@-}, {\tt idict-}, but with an {\em un}signed big-endian $n$-bit {\em Integer\/}~$x$ used as a key. +\item {\tt sdict@}, {\tt sdict@-}, {\tt sdict-}, variants of {\tt idict@}, {\tt idict@-}, {\tt idict-}, but with the key provided in the first $n$ bits of {\em Slice\/}~$k$. \item {\tt dictmap} ($D$ $n$ $e$ -- $s'$), applies execution token $e$ (i.e., an anonymous function) to each of the key-value pairs stored in a dictionary $D$ with $n$-bit keys. The execution token is executed once for each key-value pair, with a {\em Builder\/} $b$ and a {\em Slice\/} $v$ (containing the value) pushed into the stack before executing $e$. After the execution $e$ must leave in the stack either a modified {\em Builder\/} $b'$ (containing all data from~$b$ along with the new value $v'$) and $-1$, or $0$ indicating failure. In the latter case, the corresponding key is omitted from the new dictionary. \item {\tt dictmerge} ($D$ $D'$ $n$ $e$ -- $D''$), combines two dictionaries $D$ and $D'$ with $n$-bit keys into one dictionary $D''$ with the same keys. If a key is present in only one of the dictionaries $D$ and $D'$, this key and the corresponding value are copied verbatim to the new dictionary $D''$. Otherwise the execution token (anonymous function) $e$ is invoked to merge the two values $v$ and $v'$ corresponding to the same key $k$ in $D$ and $D'$, respectively. Before $e$ is invoked, a {\em Builder\/}~$b$ and two {\em Slice}s $v$ and $v'$ representing the two values to be merged are pushed. After the execution $e$ leaves either a modified {\em Builder\/}~$b'$ (containing the original data from $b$ along with the combined value) and $-1$, or $0$ on failure. In the latter case, the corresponding key is omitted from the new dictionary. \end{itemize} @@ -2087,7 +2089,9 @@ Typical values of $x$ are $x=0$ or $x=2$ for very small bags of cells (e.g., TON \item {\tt i@?+} ($s$ $x$ -- $y$ $s'$ $-1$ or $s$ $0$), fetches a signed big-endian integer from {\em Slice\/}~$s$ and computes the remainder of this {\em Slice\/} similarly to {\tt i@+}, but pushes $-1$ afterwards to indicate success, cf.~\ptref{p:slice.ops}. On failure, pushes the unchanged {\em Slice\/}~$s$ and $0$ to indicate failure. \item {\tt idict!} ($v$ $x$ $D$ $n$ -- $D'$ $-1$ or $D$ $0$), adds a new value $v$ (represented by a {\em Slice\/}) with key given by signed big-endian $n$-bit integer $x$ into dictionary $D$ with $n$-bit keys, and returns the new dictionary $D'$ and $-1$ on success, cf.~\ptref{p:hashmap.ops}. Otherwise the unchanged dictionary $D$ and $0$ are returned. \item {\tt idict!+} ($v$ $x$ $D$ $n$ -- $D'$ $-1$ or $D$ $0$), adds a new key-value pair $(x,v)$ into dictionary $D$ similarly to {\tt idict!}, but fails if the key already exists by returning the unchanged dictionary $D$ and $0$, cf.~\ptref{p:hashmap.ops}. +\item {\tt idict-} ($x$ $D$ $n$ -- $D'$ $-1$ or $D$ $0$), deletes the key represented by signed big-endian $n$-bit {\em Integer\/}~$x$ from the dictionary represented by {\em Cell\/}~$D$, cf.~\ptref{p:hashmap.ops}. If the key is found, deletes it from the dictionary and returns the modified dictionary $D'$ and $-1$. Otherwise returns the unmodified dictionary $D$ and $0$. \item {\tt idict@} ($x$ $D$ $n$ -- $v$ $-1$ or $0$), looks up key represented by signed big-endian $n$-bit {\em Integer\/}~$x$ in the dictionary represented by {\em Cell\/} or {\em Null\/}~$D$, cf.~\ptref{p:hashmap.ops}. If the key is found, returns the corresponding value as a {\em Slice\/}~$v$ and $-1$. Otherwise returns $0$. +\item {\tt idict@-} ($x$ $D$ $n$ -- $D'$ $v$ $-1$ or $D$ $0$), looks up the key represented by signed big-endian $n$-bit {\em Integer\/}~$x$ in the dictionary represented by {\em Cell\/}~$D$, cf.~\ptref{p:hashmap.ops}. If the key is found, deletes it from the dictionary and returns the modified dictionary $D'$, the corresponding value as a {\em Slice\/}~$v$, and $-1$. Otherwise returns the unmodified dictionary $D$ and $0$. \item {\tt if} ($x$ $e$ -- ), executes execution token (i.e., a {\em WordDef\/}) $e$, but only if {\em Integer\/} $x$ is non-zero, cf.~\ptref{p:cond.ops}. \item {\tt ifnot} ($x$ $e$ -- ), executes execution token $e$, but only if {\em Integer\/} $x$ is zero, cf.~\ptref{p:cond.ops}. \item {\tt include} ($S$ -- ), loads and interprets a Fift source file from the path given by {\em String\/}~$S$, cf.~\ptref{p:asm.load}. If the filename $S$ does not begin with a slash, the Fift include search path, typically taken from the {\tt FIFTPATH} environment variable or the {\tt -I} command-line argument of the Fift interpreter (and equal to {\tt /usr/lib/fift} if both are absent), is used to locate~$S$. @@ -2135,7 +2139,9 @@ Typical values of $x$ are $x=0$ or $x=2$ for very small bags of cells (e.g., TON \item {\tt sbits} ($s$ -- $x$), returns the number of data bits $x$ remaining in {\em Slice}~$s$, cf.~\ptref{p:slice.ops}. \item {\tt sdict!} ($v$ $k$ $D$ $n$ -- $D'$ $-1$ or $D$ $0$), adds a new value $v$ (represented by a {\em Slice\/}) with key given by the first $n$ bits of {\em Slice\/}~$k$ into dictionary $D$ with $n$-bit keys, and returns the new dictionary $D'$ and $-1$ on success, cf.~\ptref{p:hashmap.ops}. Otherwise the unchanged dictionary $D$ and $0$ are returned. \item {\tt sdict!+} ($v$ $k$ $D$ $n$ -- $D'$ $-1$ or $D$ $0$), adds a new key-value pair $(k,v)$ into dictionary $D$ similarly to {\tt sdict!}, but fails if the key already exists by returning the unchanged dictionary $D$ and $0$, cf.~\ptref{p:hashmap.ops}. +\item {\tt sdict-} ($x$ $D$ $n$ -- $D'$ $-1$ or $D$ $0$), deletes the key given by the first $n$ data bits of {\em Slice\/}~$x$ from the dictionary represented by {\em Cell\/}~$D$, cf.~\ptref{p:hashmap.ops}. If the key is found, deletes it from the dictionary and returns the modified dictionary $D'$ and $-1$. Otherwise returns the unmodified dictionary $D$ and $0$. \item {\tt sdict@} ($k$ $D$ $n$ -- $v$ $-1$ or $0$), looks up the key given by the first $n$ data bits of {\em Slice\/}~$x$ in the dictionary represented by {\em Cell\/} or {\em Null\/}~$D$, cf.~\ptref{p:hashmap.ops}. If the key is found, returns the corresponding value as a {\em Slice\/}~$v$ and $-1$. Otherwise returns $0$. +\item {\tt sdict@-} ($x$ $D$ $n$ -- $D'$ $v$ $-1$ or $D$ $0$), looks up the key given by the first $n$ data bits of {\em Slice\/}~$x$ in the dictionary represented by {\em Cell\/}~$D$, cf.~\ptref{p:hashmap.ops}. If the key is found, deletes it from the dictionary and returns the modified dictionary $D'$, the corresponding value as a {\em Slice\/}~$v$, and $-1$. Otherwise returns the unmodified dictionary $D$ and $0$. \item {\tt second} ($t$ -- $x$), returns the second component of a {\em Tuple}, cf.~\ptref{p:tuples}. Equivalent to {\tt 1 []}. \item {\tt sgn} ($x$ -- $y$), computes the sign of an {\em Integer\/} $x$ (i.e., pushes $1$ if $x>0$, $-1$ if $x<0$, and $0$ if $x=0$), cf.~\ptref{p:int.comp}. Equivalent to {\tt 0 cmp}. \item {\tt shash} ($s$ -- $B$), computes the $\Sha$-based representation hash of a {\em Slice\/} by first transforming it into a cell, cf.~\ptref{p:hash.ops}. Equivalent to {\tt s>c hashB}. @@ -2164,7 +2170,9 @@ Typical values of $x$ are $x=0$ or $x=2$ for very small bags of cells (e.g., TON \item {\tt u@?+} ($s$ $x$ -- $y$ $s'$ $-1$ or $s$ $0$), fetches an unsigned big-endian integer from {\em Slice\/}~$s$ and computes the remainder of this {\em Slice\/} similarly to {\tt u@+}, but pushes $-1$ afterwards to indicate success, cf.~\ptref{p:slice.ops}. On failure, pushes the unchanged {\em Slice\/}~$s$ and $0$ to indicate failure. \item {\tt udict!} ($v$ $x$ $D$ $n$ -- $D'$ $-1$ or $D$ $0$), adds a new value $v$ (represented by a {\em Slice\/}) with key given by big-endian unsigned $n$-bit integer $x$ into dictionary $D$ with $n$-bit keys, and returns the new dictionary $D'$ and $-1$ on success, cf.~\ptref{p:hashmap.ops}. Otherwise the unchanged dictionary $D$ and $0$ are returned. \item {\tt udict!+} ($v$ $x$ $D$ $n$ -- $D'$ $-1$ or $D$ $0$), adds a new key-value pair $(x,v)$ into dictionary $D$ similarly to {\tt udict!}, but fails if the key already exists by returning the unchanged dictionary $D$ and $0$, cf.~\ptref{p:hashmap.ops}. +\item {\tt udict-} ($x$ $D$ $n$ -- $D'$ $-1$ or $D$ $0$), deletes the key represented by unsigned big-endian $n$-bit {\em Integer\/}~$x$ from the dictionary represented by {\em Cell\/}~$D$, cf.~\ptref{p:hashmap.ops}. If the key is found, deletes it from the dictionary and returns the modified dictionary $D'$ and $-1$. Otherwise returns the unmodified dictionary $D$ and $0$. \item {\tt udict@} ($x$ $D$ $n$ -- $v$ $-1$ or $0$), looks up key represented by unsigned big-endian $n$-bit {\em Integer\/}~$x$ in the dictionary represented by {\em Cell\/} or {\em Null\/}~$D$, cf.~\ptref{p:hashmap.ops}. If the key is found, returns the corresponding value as a {\em Slice\/}~$v$ and $-1$. Otherwise returns $0$. +\item {\tt udict@-} ($x$ $D$ $n$ -- $D'$ $v$ $-1$ or $D$ $0$), looks up the key represented by unsigned big-endian $n$-bit {\em Integer\/}~$x$ in the dictionary represented by {\em Cell\/}~$D$, cf.~\ptref{p:hashmap.ops}. If the key is found, deletes it from the dictionary and returns the modified dictionary $D'$, the corresponding value as a {\em Slice\/}~$v$, and $-1$. Otherwise returns the unmodified dictionary $D$ and $0$. \item {\tt ufits} ($x$ $y$ -- $?$), checks whether {\em Integer\/}~$x$ is an unsigned $y$-bit integer (i.e., whether $0\leq x<2^y$ for $0\leq y\leq 1023$), and returns $-1$ or $0$ accordingly. \item {\tt uncons} ($l$ -- $h$ $t$), decomposes a non-empty list into its head and its tail, cf.~\ptref{p:lists}. Equivalent to {\tt unpair}. \item {\tt \underline{undef?} $\langle\textit{word-name\/}\rangle$} ( -- $?$), checks whether the word $\langle\textit{word-name\/}\rangle$ is undefined at execution time, and returns $-1$ or $0$ accordingly. diff --git a/doc/tvm.tex b/doc/tvm.tex index 3a9f92a4..61f5a0da 100644 --- a/doc/tvm.tex +++ b/doc/tvm.tex @@ -1295,15 +1295,18 @@ We list the instructions in lexicographical opcode order. However, the opcode sp We use hexadecimal notation (cf. ~\ptref{p:bitstring.hex}) for bitstrings. Stack registers {\tt s$(i)$} usually have $0\leq i\leq 15$, and $i$ is encoded in a 4-bit field (or, on a few rare occasions, in an 8-bit field). Other immediate parameters are usually 4-bit, 8-bit, or variable length. The stack notation described in~\ptref{sp:stack.notat} is extensively used throughout this appendix. + \mysubsection{Gas prices} \def\gas#1{{\em ($#1$)}} The gas price for most primitives equals the {\em basic gas price}, computed as $P_b:=10+b+5r$, where $b$ is the instruction length in bits and $r$ is the number of cell references included in the instruction. When the gas price of an instruction differs from this basic price, it is indicated in parentheses after its mnemonics, either as \gas{x}, meaning that the total gas price equals $x$, or as \gas{+x}, meaning $P_b+x$. Apart from integer constants, the following expressions may appear: \begin{itemize} -\item $C_r$ --- The total price of ``reading'' cells (i.e., transforming cell references into cell slices). Currently equal to 20 gas units per cell. +\item $C_r$ --- The total price of ``reading'' cells (i.e., transforming cell references into cell slices). Currently equal to 100 or 25 gas units per cell depending on whether it is the first time a cell with this hash is being ``read'' during the current run of the VM or not. \item $L$ --- The total price of loading cells. Depends on the loading action required. -\item $B_w$ --- The total price of creating new {\em Builder}s. Currently equal to 100 gas units per builder. -\item $C_w$ --- The total price of creating new {\em Cell}s from {\em Builder}s). Currently equal to 100 gas units per cell. +\item $B_w$ --- The total price of creating new {\em Builder\/}s. Currently equal to 0 gas units per builder. +\item $C_w$ --- The total price of creating new {\em Cell\/}s from {\em Builder\/}s. Currently equal to 500 gas units per cell. \end{itemize} +By default, the gas price of an instruction equals $P:=P_b+C_r+L+B_w+C_w$. + \mysubsection{Stack manipulation primitives} This section includes both the basic (cf.~\ptref{sp:stack.basic}) and the compound (cf.~\ptref{sp:stack.comp}) stack manipulation primitives, as well as some ``unsystematic'' ones. Some compound stack manipulation primitives, such as {\tt XCPU} or {\tt XCHG2}, turn out to have the same length as an equivalent sequence of simpler operations. We have included these primitives regardless, so that they can easily be allocated shorter opcodes in a future revision of TVM---or removed for good. @@ -2220,7 +2223,16 @@ The ``global variables'' may be helpful in implementing some high-level smart-co \item {\tt F902} --- {\tt SHA256U} ($s$ -- $x$), computes $\Sha$ of the data bits of~{\em Slice\/}~$s$. If the bit length of $s$ is not divisible by eight, throws a cell underflow exception. The hash value is returned as a 256-bit unsigned integer~$x$. \item {\tt F910} --- {\tt CHKSIGNU} ($h$ $s$ $k$ -- $?$), checks the Ed25519-signature $s$ of a hash $h$ (a 256-bit unsigned integer, usually computed as the hash of some data) using public key $k$ (also represented by a 256-bit unsigned integer). The signature $s$ must be a {\em Slice\/} containing at least 512 data bits; only the first 512 bits are used. The result is $-1$ if the signature is valid, $0$ otherwise. Notice that {\tt CHKSIGNU} is equivalent to {\tt ROT}; {\tt NEWB}; {\tt STU 256}; {\tt ENDB}; {\tt NEWC}; {\tt ROTREV}; {\tt CHKSIGNS}, i.e., to {\tt CHKSIGNS} with the first argument $d$ set to 256-bit {\em Slice} containing~$h$. Therefore, if $h$ is computed as the hash of some data, these data are hashed {\em twice}, the second hashing occurring inside {\tt CHKSIGNS}. \item {\tt F911} --- {\tt CHKSIGNS} ($d$ $s$ $k$ -- $?$), checks whether $s$ is a valid Ed25519-signature of the data portion of {\em Slice\/}~$d$ using public key~$k$, similarly to {\tt CHKSIGNU}. If the bit length of {\em Slice\/}~$d$ is not divisible by eight, throws a cell underflow exception. The verification of Ed25519 signatures is the standard one, with $\Sha$ used to reduce $d$ to the 256-bit number that is actually signed. -\item {\tt F902}--{\tt F93F} --- Reserved for hashing and cryptography primitives. +\item {\tt F912}--{\tt F93F} --- Reserved for hashing and cryptography primitives. +\end{itemize} + +\nxsubpoint\emb{Miscellaneous primitives} +\begin{itemize} +\item {\tt F940} --- {\tt CDATASIZEQ} ($c$ $n$ -- $x$ $y$ $z$ $-1$ or $0$), recursively computes the count of distinct cells $x$, data bits $y$, and cell references $z$ in the dag rooted at {\em Cell\/} $c$, effectively returning the total storage used by this dag taking into account the identification of equal cells. The values of $x$, $y$, and $z$ are computed by a depth-first traversal of this dag, with a hash table of visited cell hashes used to prevent visits of already-visited cells. The total count of visited cells $x$ cannot exceed non-negative {\em Integer\/}~$n$; otherwise the computation is aborted before visiting the $(n+1)$-st cell and a zero is returned to indicate failure. If $c$ is {\em Null}, returns $x=y=z=0$. +\item {\tt F941} --- {\tt CDATASIZE} ($c$ $n$ -- $x$ $y$ $z$), a non-quiet version of {\tt CDATASIZEQ} that throws a cell overflow exception (8) on failure. +\item {\tt F942} --- {\tt SDATASIZEQ} ($s$ $n$ -- $x$ $y$ $z$ $-1$ or $0$), similar to {\tt CDATASIZEQ}, but accepting a {\em Slice\/}~$s$ instead of a {\em Cell\/}. The returned value of $x$ does not take into account the cell that contains the slice~$s$ itself; however, the data bits and the cell references of $s$ are accounted for in $y$ and~$z$. +\item {\tt F943} --- {\tt SDATASIZE} ($s$ $n$ -- $x$ $y$ $z$), a non-quiet version of {\tt SDATASIZEQ} that throws a cell overflow exception (8) on failure. +\item {\tt F944}--{\tt F97F} --- Reserved for miscellaneous TON-specific primitives that do not fall into any other specific category. \end{itemize} \nxsubpoint\emb{Currency manipulation primitives} diff --git a/http/CMakeLists.txt b/http/CMakeLists.txt new file mode 100644 index 00000000..5badeaa1 --- /dev/null +++ b/http/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR) + +set(HTTP_SOURCE + http.h + http.cpp + http-connection.h + http-connection.cpp + http-inbound-connection.h + http-inbound-connection.cpp + http-outbound-connection.h + http-outbound-connection.cpp + http-server.h + http-server.cpp + http-client.h + http-client.hpp + http-client.cpp +) + +add_library(tonhttp STATIC ${HTTP_SOURCE}) +target_include_directories(tonhttp PUBLIC $) +target_link_libraries(tonhttp PUBLIC tdactor ton_crypto tl_api tdnet ) + +add_executable(http-proxy http-proxy.cpp) +target_include_directories(http-proxy PUBLIC $) +target_link_libraries(http-proxy PRIVATE tonhttp) diff --git a/http/http-client.cpp b/http/http-client.cpp new file mode 100644 index 00000000..6c43e3eb --- /dev/null +++ b/http/http-client.cpp @@ -0,0 +1,124 @@ +/* + 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 2019-2020 Telegram Systems LLP +*/ +#include "http-client.hpp" + +#include "td/utils/Random.h" + +namespace ton { + +namespace http { + +void HttpClientImpl::create_connection() { + alarm_timestamp().relax(td::Timestamp::in(td::Random::fast(10.0, 20.0))); + + if (domain_.size() > 0) { + auto S = addr_.init_host_port(domain_); + if (S.is_error()) { + LOG(INFO) << "failed domain '" << domain_ << "': " << S; + return; + } + } + + auto fd = td::SocketFd::open(addr_); + if (fd.is_error()) { + LOG(INFO) << "failed to connect to " << addr_ << ": " << fd.move_as_error(); + return; + } + + class Cb : public HttpClient::Callback { + public: + Cb(td::actor::ActorId id) : id_(id) { + } + + void on_ready() override { + td::actor::send_closure(id_, &HttpClientImpl::client_ready, true); + } + + void on_stop_ready() override { + td::actor::send_closure(id_, &HttpClientImpl::client_ready, false); + } + + private: + td::actor::ActorId id_; + }; + conn_ = td::actor::create_actor(td::actor::ActorOptions().with_name("outconn").with_poll(), + fd.move_as_ok(), std::make_shared(actor_id(this))); +} + +void HttpClientImpl::send_request( + std::unique_ptr request, std::shared_ptr payload, td::Timestamp timeout, + td::Promise, std::shared_ptr>> promise) { + td::actor::send_closure(conn_, &HttpOutboundConnection::send_query, std::move(request), std::move(payload), timeout, + std::move(promise)); +} + +void HttpMultiClientImpl::send_request( + std::unique_ptr request, std::shared_ptr payload, td::Timestamp timeout, + td::Promise, std::shared_ptr>> promise) { + if (domain_.size() > 0) { + auto S = addr_.init_host_port(domain_); + if (S.is_error()) { + return answer_error(HttpStatusCode::status_bad_gateway, "", std::move(promise)); + } + } + + auto fd = td::SocketFd::open(addr_); + if (fd.is_error()) { + return answer_error(HttpStatusCode::status_bad_gateway, "", std::move(promise)); + } + + class Cb : public HttpClient::Callback { + public: + Cb(td::actor::ActorId id) : id_(id) { + } + + void on_ready() override { + } + + void on_stop_ready() override { + } + + private: + td::actor::ActorId id_; + }; + auto conn = + td::actor::create_actor(td::actor::ActorOptions().with_name("outconn").with_poll(), + fd.move_as_ok(), std::make_shared(actor_id(this))) + .release(); + request->set_keep_alive(false); + td::actor::send_closure(conn, &HttpOutboundConnection::send_query, std::move(request), std::move(payload), timeout, + std::move(promise)); +} + +td::actor::ActorOwn HttpClient::create(std::string domain, td::IPAddress addr, + std::shared_ptr callback) { + return td::actor::create_actor("httpclient", std::move(domain), addr, std::move(callback)); +} + +td::actor::ActorOwn HttpClient::create_multi(std::string domain, td::IPAddress addr, + td::uint32 max_connections, + td::uint32 max_requests_per_connect, + std::shared_ptr callback) { + return td::actor::create_actor("httpmclient", std::move(domain), addr, max_connections, + max_requests_per_connect, std::move(callback)); +} + +} // namespace http + +} // namespace ton diff --git a/http/http-client.h b/http/http-client.h new file mode 100644 index 00000000..9ec80be4 --- /dev/null +++ b/http/http-client.h @@ -0,0 +1,56 @@ +/* + 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 2019-2020 Telegram Systems LLP +*/ +#pragma once + +#include "http.h" + +#include "td/utils/port/IPAddress.h" +#include "td/actor/actor.h" + +namespace ton { + +namespace http { + +class HttpOutboundConnection; + +class HttpClient : public td::actor::Actor { + public: + class Callback { + public: + virtual ~Callback() = default; + virtual void on_ready() = 0; + virtual void on_stop_ready() = 0; + }; + + virtual void check_ready(td::Promise promise) = 0; + + virtual void send_request( + std::unique_ptr request, std::shared_ptr payload, td::Timestamp timeout, + td::Promise, std::shared_ptr>> promise) = 0; + + static td::actor::ActorOwn create(std::string domain, td::IPAddress addr, + std::shared_ptr callback); + static td::actor::ActorOwn create_multi(std::string domain, td::IPAddress addr, + td::uint32 max_connections, td::uint32 max_requests_per_connect, + std::shared_ptr callback); +}; + +} // namespace http + +} // namespace ton diff --git a/http/http-client.hpp b/http/http-client.hpp new file mode 100644 index 00000000..abb48867 --- /dev/null +++ b/http/http-client.hpp @@ -0,0 +1,118 @@ +/* + 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 2019-2020 Telegram Systems LLP +*/ +#include "http-client.h" +#include "http-outbound-connection.h" + +#include "td/utils/Random.h" + +namespace ton { + +namespace http { + +class HttpClientImpl : public HttpClient { + public: + HttpClientImpl(std::string domain, td::IPAddress addr, std::shared_ptr callback) + : domain_(std::move(domain)), addr_(addr), callback_(std::move(callback)) { + } + + void start_up() override { + create_connection(); + } + void check_ready(td::Promise promise) override { + if (ready_) { + promise.set_value(td::Unit()); + } else { + promise.set_error(td::Status::Error(ErrorCode::notready, "connection not ready")); + } + } + + void client_ready(bool value) { + if (ready_ == value) { + return; + } + ready_ = value; + if (ready_) { + callback_->on_ready(); + } else { + callback_->on_stop_ready(); + conn_.reset(); + if (next_create_at_.is_in_past()) { + create_connection(); + } else { + alarm_timestamp().relax(next_create_at_); + } + } + } + + void alarm() override { + create_connection(); + } + void send_request( + std::unique_ptr request, std::shared_ptr payload, td::Timestamp timeout, + td::Promise, std::shared_ptr>> promise) override; + + void create_connection(); + + private: + bool ready_ = false; + std::string domain_; + td::IPAddress addr_; + td::Timestamp next_create_at_; + + std::shared_ptr callback_; + td::actor::ActorOwn conn_; +}; + +class HttpMultiClientImpl : public HttpClient { + public: + HttpMultiClientImpl(std::string domain, td::IPAddress addr, td::uint32 max_connections, + td::uint32 max_requests_per_connect, std::shared_ptr callback) + : domain_(std::move(domain)) + , addr_(addr) + , max_connections_(max_connections) + , max_requests_per_connect_(max_requests_per_connect) + , callback_(std::move(callback)) { + } + + void start_up() override { + callback_->on_ready(); + } + void check_ready(td::Promise promise) override { + promise.set_value(td::Unit()); + } + + void send_request( + std::unique_ptr request, std::shared_ptr payload, td::Timestamp timeout, + td::Promise, std::shared_ptr>> promise) override; + + private: + std::string domain_; + td::IPAddress addr_; + + size_t max_connections_; + td::uint32 max_requests_per_connect_; + + td::Timestamp next_create_at_; + + std::shared_ptr callback_; +}; + +} // namespace http + +} // namespace ton diff --git a/http/http-connection.cpp b/http/http-connection.cpp new file mode 100644 index 00000000..cf7b9a66 --- /dev/null +++ b/http/http-connection.cpp @@ -0,0 +1,256 @@ +/* + 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 2019-2020 Telegram Systems LLP +*/ +#include "http-connection.h" + +namespace ton { + +namespace http { + +void HttpConnection::loop() { + if (in_loop_) { + return; + } + in_loop_ = true; + auto status = [&] { + while (true) { + LOG(DEBUG) << "loop(): in=" << buffered_fd_.left_unread() << " out=" << buffered_fd_.left_unwritten(); + bool is_eof = td::can_close(buffered_fd_); + bool read_eof = false; + + bool written = false; + bool read = false; + if (is_eof || buffered_fd_.left_unread() <= fd_low_watermark()) { + allow_read_ = true; + } + if (allow_read_ && buffered_fd_.left_unread() < fd_high_watermark()) { + TRY_RESULT(r, buffered_fd_.flush_read(fd_high_watermark() - buffered_fd_.left_unread())); + if (r == 0 && is_eof) { + read_eof = true; + } + } + if (buffered_fd_.left_unread() >= fd_high_watermark()) { + allow_read_ = false; + } + { + auto &input = buffered_fd_.input_buffer(); + auto s = input.size(); + TRY_STATUS(receive(input)); + read = input.size() < s; + } + if (buffered_fd_.left_unread() == 0 && read_eof) { + TRY_STATUS(receive_eof()); + } + TRY_STATUS(buffered_fd_.flush_write()); + if (writing_payload_ && buffered_fd_.left_unwritten() < fd_high_watermark()) { + auto w = buffered_fd_.left_unwritten(); + continue_payload_write(); + written = buffered_fd_.left_unwritten() > w; + } + if (close_after_write_ && !writing_payload_ && !buffered_fd_.left_unwritten()) { + LOG(INFO) << "close after write"; + stop(); + break; + } + if (close_after_read_ && !reading_payload_ && !buffered_fd_.left_unread()) { + LOG(INFO) << "close after read"; + stop(); + break; + } + if (!written && !read) { + break; + } + } + return td::Status::OK(); + }(); + in_loop_ = false; + if (status.is_error()) { + LOG(ERROR) << "loop() failed: " << status; + stop(); + } else { + send_ready(); + } +} + +void HttpConnection::send_error(std::unique_ptr response) { + CHECK(!writing_payload_); + auto payload = response->create_empty_payload().move_as_ok(); + CHECK(payload->parse_completed()); + send_response(std::move(response), std::move(payload)); +} + +void HttpConnection::send_request(std::unique_ptr request, std::shared_ptr payload) { + CHECK(!writing_payload_); + request->store_http(buffered_fd_.output_buffer()); + + write_payload(std::move(payload)); +} + +void HttpConnection::send_response(std::unique_ptr response, std::shared_ptr payload) { + CHECK(!writing_payload_); + response->store_http(buffered_fd_.output_buffer()); + + write_payload(std::move(payload)); +} + +void HttpConnection::write_payload(std::shared_ptr payload) { + CHECK(!writing_payload_); + + writing_payload_ = std::move(payload); + + if (writing_payload_->parse_completed()) { + continue_payload_write(); + return; + } + + class Cb : public HttpPayload::Callback { + public: + Cb(td::actor::ActorId conn) : conn_(conn) { + } + void run(size_t ready_bytes) override { + if (!reached_ && ready_bytes >= watermark_) { + td::actor::send_closure(conn_, &HttpConnection::loop); + reached_ = true; + } else if (reached_ && ready_bytes < watermark_) { + reached_ = false; + } + } + void completed() override { + td::actor::send_closure(conn_, &HttpConnection::loop); + } + + private: + size_t watermark_ = chunk_size(); + bool reached_ = false; + + td::actor::ActorId conn_; + }; + + writing_payload_->add_callback(std::make_unique(actor_id(this))); + continue_payload_write(); +} + +void HttpConnection::continue_payload_write() { + if (!writing_payload_) { + return; + } + + auto t = writing_payload_->payload_type(); + if (t == HttpPayload::PayloadType::pt_eof) { + t = HttpPayload::PayloadType::pt_chunked; + } + + while (!writing_payload_->written()) { + if (buffered_fd_.left_unwritten() > fd_high_watermark()) { + return; + } + if (!writing_payload_->parse_completed() && writing_payload_->ready_bytes() < chunk_size()) { + return; + } + writing_payload_->store_http(buffered_fd_.output_buffer(), chunk_size(), t); + } + if (writing_payload_->parse_completed() && writing_payload_->written()) { + payload_written(); + return; + } +} + +td::Status HttpConnection::read_payload(HttpResponse *response) { + CHECK(!reading_payload_); + + if (!response->keep_alive()) { + close_after_read_ = true; + } + + return read_payload(response->create_empty_payload().move_as_ok()); +} + +td::Status HttpConnection::read_payload(HttpRequest *request) { + CHECK(!reading_payload_); + + return read_payload(request->create_empty_payload().move_as_ok()); +} + +td::Status HttpConnection::read_payload(std::shared_ptr payload) { + CHECK(!reading_payload_); + + reading_payload_ = std::move(payload); + + if (reading_payload_->parse_completed()) { + payload_read(); + return td::Status::OK(); + } + + class Cb : public HttpPayload::Callback { + public: + Cb(td::actor::ActorId conn) : conn_(conn) { + } + void run(size_t ready_bytes) override { + if (!reached_ && ready_bytes < watermark_) { + reached_ = true; + td::actor::send_closure(conn_, &HttpConnection::loop); + } else if (reached_ && ready_bytes >= watermark_) { + reached_ = false; + } + } + void completed() override { + td::actor::send_closure(conn_, &HttpConnection::loop); + } + + private: + size_t watermark_ = HttpRequest::low_watermark(); + bool reached_ = false; + + td::actor::ActorId conn_; + }; + + reading_payload_->add_callback(std::make_unique(actor_id(this))); + auto &input = buffered_fd_.input_buffer(); + return continue_payload_read(input); +} + +td::Status HttpConnection::continue_payload_read(td::ChainBufferReader &input) { + if (!reading_payload_) { + return td::Status::OK(); + } + while (!reading_payload_->parse_completed()) { + if (reading_payload_->ready_bytes() > fd_high_watermark()) { + return td::Status::OK(); + } + auto s = input.size(); + TRY_STATUS(reading_payload_->parse(input)); + if (input.size() == s) { + return td::Status::OK(); + } + } + if (reading_payload_->parse_completed()) { + payload_read(); + return td::Status::OK(); + } + return td::Status::OK(); +} + +td::Status HttpConnection::receive_payload(td::ChainBufferReader &input) { + CHECK(reading_payload_); + continue_payload_read(input); + return td::Status::OK(); +} + +} // namespace http + +} // namespace ton diff --git a/http/http-connection.h b/http/http-connection.h new file mode 100644 index 00000000..6d20b8b2 --- /dev/null +++ b/http/http-connection.h @@ -0,0 +1,141 @@ +/* + 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 2019-2020 Telegram Systems LLP +*/ +#pragma once + +#include "td/actor/actor.h" +#include "td/utils/port/SocketFd.h" +#include "td/utils/buffer.h" +#include "td/utils/BufferedFd.h" +#include "common/errorcode.h" + +#include "http.h" + +namespace ton { + +namespace http { + +class HttpConnection : public td::actor::Actor, public td::ObserverBase { + public: + class Callback { + public: + virtual ~Callback() = default; + virtual void on_close(td::actor::ActorId conn) = 0; + virtual void on_ready(td::actor::ActorId conn) = 0; + }; + + HttpConnection(td::SocketFd fd, std::unique_ptr callback, bool is_client) + : buffered_fd_(std::move(fd)), callback_(std::move(callback)), is_client_(is_client) { + } + virtual td::Status receive(td::ChainBufferReader &input) = 0; + virtual td::Status receive_eof() = 0; + td::Status receive_payload(td::ChainBufferReader &input); + bool check_ready() const { + return !td::can_close(buffered_fd_); + } + void check_ready_async(td::Promise promise) { + if (check_ready()) { + promise.set_value(td::Unit()); + } else { + promise.set_error(td::Status::Error(ErrorCode::notready, "not ready")); + } + } + void send_ready() { + if (check_ready() && !sent_ready_ && callback_) { + callback_->on_ready(actor_id(this)); + sent_ready_ = true; + } + } + void send_error(std::unique_ptr response); + void send_request(std::unique_ptr request, std::shared_ptr payload); + void send_response(std::unique_ptr response, std::shared_ptr payload); + void write_payload(std::shared_ptr payload); + void continue_payload_write(); + td::Status receive_request(); + td::Status receive_response(); + td::Status read_payload(HttpRequest *request); + td::Status read_payload(HttpResponse *response); + td::Status read_payload(std::shared_ptr payload); + td::Status continue_payload_read(td::ChainBufferReader &input); + + virtual void payload_read() = 0; + virtual void payload_written() = 0; + + virtual ~HttpConnection() = default; + + protected: + td::BufferedFd buffered_fd_; + td::actor::ActorId self_; + std::unique_ptr callback_; + bool sent_ready_ = false; + + bool is_client_; + bool close_after_write_ = false; + bool close_after_read_ = false; + bool found_eof_ = false; + bool in_loop_ = false; + bool allow_read_ = true; + + std::shared_ptr reading_payload_; + std::shared_ptr writing_payload_; + + void notify() override { + // NB: Interface will be changed + td::actor::send_closure_later(self_, &HttpConnection::on_net); + } + + void start_up() override { + self_ = actor_id(this); + // Subscribe for socket updates + // NB: Interface will be changed + td::actor::SchedulerContext::get()->get_poll().subscribe(buffered_fd_.get_poll_info().extract_pollable_fd(this), + td::PollFlags::ReadWrite() | td::PollFlags::Close()); + notify(); + } + + void loop() override; + + private: + static constexpr size_t fd_low_watermark() { + return 1 << 14; + } + static constexpr size_t fd_high_watermark() { + return 1 << 16; + } + static constexpr size_t chunk_size() { + return 1 << 10; + } + + void on_net() { + loop(); + } + + void tear_down() override { + if (callback_) { + callback_->on_close(actor_id(this)); + callback_ = nullptr; + } + // unsubscribe from socket updates + // nb: interface will be changed + td::actor::SchedulerContext::get()->get_poll().unsubscribe(buffered_fd_.get_poll_info().get_pollable_fd_ref()); + } +}; + +} // namespace http + +} // namespace ton diff --git a/http/http-inbound-connection.cpp b/http/http-inbound-connection.cpp new file mode 100644 index 00000000..0180ec1c --- /dev/null +++ b/http/http-inbound-connection.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 2019-2020 Telegram Systems LLP +*/ +#include "http-inbound-connection.h" +#include "td/utils/misc.h" + +namespace ton { + +namespace http { + +void HttpInboundConnection::send_client_error() { + static const auto s = + "HTTP/1.0 400 Bad Request\r\n" + "Connection: Close\r\n" + "\r\n"; + buffered_fd_.output_buffer().append(td::Slice(s, strlen(s))); + close_after_write_ = true; +} + +void HttpInboundConnection::send_server_error() { + static const auto s = + "HTTP/1.1 502 Bad Gateway\r\n" + "Connection: keep-alive\r\n" + "\r\n"; + buffered_fd_.output_buffer().append(td::Slice(s, strlen(s))); +} + +void HttpInboundConnection::send_proxy_error() { + static const auto s = + "HTTP/1.1 502 Bad Gateway\r\n" + "Connection: keep-alive\r\n" + "\r\n"; + buffered_fd_.output_buffer().append(td::Slice(s, strlen(s))); +} + +td::Status HttpInboundConnection::receive(td::ChainBufferReader &input) { + if (reading_payload_) { + return receive_payload(input); + } + + if (!cur_request_ && !read_next_request_) { + return td::Status::OK(); + } + + while (!cur_request_ || !cur_request_->check_parse_header_completed()) { + bool exit_loop; + auto R = HttpRequest::parse(std::move(cur_request_), cur_line_, exit_loop, input); + if (R.is_error()) { + send_client_error(); + return td::Status::OK(); + } + if (exit_loop) { + return td::Status::OK(); + } + cur_request_ = R.move_as_ok(); + } + + auto payload = cur_request_->create_empty_payload().move_as_ok(); + auto P = td::PromiseCreator::lambda( + [SelfId = actor_id(this)](td::Result, std::shared_ptr>> R) { + if (R.is_ok()) { + auto a = R.move_as_ok(); + td::actor::send_closure(SelfId, &HttpInboundConnection::send_answer, std::move(a.first), std::move(a.second)); + } else { + td::actor::send_closure(SelfId, &HttpInboundConnection::send_proxy_error); + } + }); + http_callback_->receive_request(std::move(cur_request_), payload, std::move(P)); + read_payload(std::move(payload)); + + return td::Status::OK(); +} + +void HttpInboundConnection::send_answer(std::unique_ptr response, std::shared_ptr payload) { + CHECK(payload); + response->store_http(buffered_fd_.output_buffer()); + + write_payload(std::move(payload)); + loop(); +} + +} // namespace http + +} // namespace ton diff --git a/http/http-inbound-connection.h b/http/http-inbound-connection.h new file mode 100644 index 00000000..88b7384b --- /dev/null +++ b/http/http-inbound-connection.h @@ -0,0 +1,81 @@ +/* + 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 2019-2020 Telegram Systems LLP +*/ +#pragma once + +#include "http.h" +#include "http-connection.h" +#include "http-server.h" + +namespace ton { + +namespace http { + +class HttpInboundConnection : public HttpConnection { + public: + HttpInboundConnection(td::SocketFd fd, std::shared_ptr http_callback) + : HttpConnection(std::move(fd), nullptr, false), http_callback_(std::move(http_callback)) { + } + + td::Status receive_eof() override { + if (reading_payload_) { + if (reading_payload_->payload_type() != HttpPayload::PayloadType::pt_eof) { + return td::Status::Error("unexpected EOF"); + } else { + reading_payload_->complete_parse(); + payload_read(); + return td::Status::OK(); + } + } else { + return td::Status::OK(); + } + } + + void send_client_error(); + void send_server_error(); + void send_proxy_error(); + + void payload_written() override { + writing_payload_ = nullptr; + if (!close_after_write_) { + read_next_request_ = true; + } + } + void payload_read() override { + reading_payload_ = nullptr; + read_next_request_ = false; + } + + td::Status receive(td::ChainBufferReader &input) override; + void send_answer(std::unique_ptr response, std::shared_ptr payload); + + private: + static constexpr size_t chunk_size() { + return 1 << 14; + } + + bool read_next_request_ = true; + + std::shared_ptr http_callback_; + std::unique_ptr cur_request_; + std::string cur_line_; +}; + +} // namespace http + +} // namespace ton diff --git a/http/http-outbound-connection.cpp b/http/http-outbound-connection.cpp new file mode 100644 index 00000000..a98efbc9 --- /dev/null +++ b/http/http-outbound-connection.cpp @@ -0,0 +1,110 @@ +/* + 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 2019-2020 Telegram Systems LLP +*/ +#include "http-outbound-connection.h" + +#include "td/utils/port/StdStreams.h" + +namespace ton { + +namespace http { + +td::Status HttpOutboundConnection::receive(td::ChainBufferReader &input) { + if (input.size() == 0) { + return td::Status::OK(); + } + if (reading_payload_) { + return receive_payload(input); + } + if (!promise_) { + return td::Status::Error("unexpected data"); + } + + while (!cur_response_ || !cur_response_->check_parse_header_completed()) { + bool exit_loop; + auto R = HttpResponse::parse(std::move(cur_response_), cur_line_, force_no_payload_, keep_alive_, exit_loop, input); + if (R.is_error()) { + answer_error(HttpStatusCode::status_bad_request, "", std::move(promise_)); + return td::Status::OK(); + } + if (exit_loop) { + return td::Status::OK(); + } + cur_response_ = R.move_as_ok(); + } + + if (cur_response_->code() == 100) { + cur_response_ = nullptr; + return td::Status::OK(); + } + + close_after_read_ = !cur_response_->keep_alive() || !keep_alive_; + + auto payload = cur_response_->create_empty_payload().move_as_ok(); + promise_.set_value(std::make_pair(std::move(cur_response_), payload)); + read_payload(std::move(payload)); + + if (!reading_payload_) { + return td::Status::OK(); + } + return receive_payload(input); +} + +void HttpOutboundConnection::send_query( + std::unique_ptr request, std::shared_ptr payload, td::Timestamp timeout, + td::Promise, std::shared_ptr>> promise) { + CHECK(request); + CHECK(payload); + if (promise_) { + LOG(INFO) << "delaying send of HTTP request"; + next_.push_back(Query{std::move(request), std::move(payload), timeout, std::move(promise)}); + return; + } + LOG(INFO) << "sending HTTP request"; + keep_alive_ = request->keep_alive(); + force_no_payload_ = request->no_payload_in_answer(); + request->store_http(buffered_fd_.output_buffer()); + write_payload(std::move(payload)); + promise_ = std::move(promise); + alarm_timestamp() = timeout; + + loop(); +} + +void HttpOutboundConnection::send_next_query() { + if (next_.size() == 0) { + return; + } + + LOG(INFO) << "sending delayed HTTP request"; + auto p = std::move(next_.front()); + next_.pop_front(); + keep_alive_ = p.request->keep_alive(); + force_no_payload_ = p.request->no_payload_in_answer(); + + p.request->store_http(buffered_fd_.output_buffer()); + write_payload(std::move(p.payload)); + alarm_timestamp() = p.timeout; + promise_ = std::move(p.promise); + + loop(); +} + +} // namespace http + +} // namespace ton diff --git a/http/http-outbound-connection.h b/http/http-outbound-connection.h new file mode 100644 index 00000000..5a938538 --- /dev/null +++ b/http/http-outbound-connection.h @@ -0,0 +1,125 @@ +/* + 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 2019-2020 Telegram Systems LLP +*/ +#pragma once + +#include "http.h" +#include "http-connection.h" +#include "http-client.h" + +#include + +namespace ton { + +namespace http { + +class HttpOutboundConnection : public HttpConnection { + public: + struct Query { + std::unique_ptr request; + std::shared_ptr payload; + td::Timestamp timeout; + td::Promise, std::shared_ptr>> promise; + }; + + HttpOutboundConnection(td::SocketFd fd, std::shared_ptr http_callback) + : HttpConnection(std::move(fd), nullptr, false), http_callback_(std::move(http_callback)) { + } + + td::Status receive_eof() override { + if (reading_payload_) { + if (reading_payload_->payload_type() != HttpPayload::PayloadType::pt_eof) { + return td::Status::Error("unexpected EOF"); + } else { + LOG(INFO) << "stopping (EOF payload)"; + reading_payload_->complete_parse(); + stop(); + return td::Status::OK(); + } + } else { + LOG(INFO) << "stopping (no req)"; + stop(); + return td::Status::OK(); + } + } + + void alarm() override { + LOG(INFO) << "closing outbound HTTP connection because of request timeout"; + if (promise_) { + answer_error(HttpStatusCode::status_gateway_timeout, "", std::move(promise_)); + } + stop(); + } + + void start_up() override { + class Cb : public HttpConnection::Callback { + public: + Cb(std::shared_ptr callback) : callback_(std::move(callback)) { + } + void on_ready(td::actor::ActorId conn) { + callback_->on_ready(); + } + void on_close(td::actor::ActorId conn) { + callback_->on_stop_ready(); + } + + private: + std::shared_ptr callback_; + }; + + callback_ = std::make_unique(std::move(http_callback_)); + + HttpConnection::start_up(); + } + + td::Status receive(td::ChainBufferReader &input) override; + void send_query(std::unique_ptr request, std::shared_ptr payload, td::Timestamp timeout, + td::Promise, std::shared_ptr>> promise); + + void send_next_query(); + + void payload_read() override { + reading_payload_ = nullptr; + + if (!close_after_read_) { + alarm_timestamp() = td::Timestamp::never(); + send_next_query(); + } else { + stop(); + } + } + void payload_written() override { + writing_payload_ = nullptr; + } + + private: + std::shared_ptr http_callback_; + + td::Promise, std::shared_ptr>> promise_; + bool force_no_payload_; + bool keep_alive_; + + std::unique_ptr cur_response_; + std::string cur_line_; + + std::list next_; +}; + +} // namespace http + +} // namespace ton diff --git a/http/http-proxy.cpp b/http/http-proxy.cpp new file mode 100644 index 00000000..15538056 --- /dev/null +++ b/http/http-proxy.cpp @@ -0,0 +1,308 @@ +/* + This file is part of TON Blockchain source code. + + TON Blockchain is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + TON Blockchain is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with TON Blockchain. If not, see . + + In addition, as a special exception, the copyright holders give permission + to link the code of portions of this program with the OpenSSL library. + You must obey the GNU General Public License in all respects for all + of the code used other than OpenSSL. If you modify file(s) with this + exception, you may extend this exception to your version of the file(s), + but you are not obligated to do so. If you do not wish to do so, delete this + exception statement from your version. If you delete this exception statement + from all source files in the program, then also delete it here. + + Copyright 2019-2020 Telegram Systems LLP +*/ +#include "http/http-server.h" +#include "http/http-client.h" + +#include "td/utils/port/signals.h" +#include "td/utils/OptionsParser.h" +#include "td/utils/FileLog.h" + +#include +#include + +#if TD_DARWIN || TD_LINUX +#include +#endif + +class HttpProxy; + +class HttpRemote : public td::actor::Actor { + public: + struct Query { + std::unique_ptr request; + std::shared_ptr payload; + td::Timestamp timeout; + td::Promise, std::shared_ptr>> promise; + }; + HttpRemote(std::string domain, td::actor::ActorId proxy) : domain_(std::move(domain)), proxy_(proxy) { + } + void start_up() override { + class Cb : public ton::http::HttpClient::Callback { + public: + Cb(td::actor::ActorId id) : id_(id) { + } + void on_ready() override { + td::actor::send_closure(id_, &HttpRemote::set_ready, true); + } + void on_stop_ready() override { + td::actor::send_closure(id_, &HttpRemote::set_ready, false); + } + + private: + td::actor::ActorId id_; + }; + client_ = ton::http::HttpClient::create_multi(domain_, td::IPAddress(), 1, 1, std::make_shared(actor_id(this))); + fail_at_ = td::Timestamp::in(10.0); + close_at_ = td::Timestamp::in(60.0); + } + void set_ready(bool ready) { + if (ready == ready_) { + return; + } + ready_ = ready; + if (!ready) { + fail_at_ = td::Timestamp::in(10.0); + alarm_timestamp().relax(fail_at_); + } else { + fail_at_ = td::Timestamp::never(); + while (list_.size() > 0) { + auto q = std::move(list_.front()); + list_.pop_front(); + td::actor::send_closure(client_, &ton::http::HttpClient::send_request, std::move(q.request), + std::move(q.payload), q.timeout, std::move(q.promise)); + close_at_ = td::Timestamp::in(60.0); + } + } + } + void receive_request( + std::unique_ptr request, std::shared_ptr payload, + td::Promise, std::shared_ptr>> + promise) { + bool keep = request->keep_alive(); + auto P = td::PromiseCreator::lambda( + [promise = std::move(promise), + keep](td::Result, std::shared_ptr>> + R) mutable { + if (R.is_error()) { + promise.set_error(R.move_as_error()); + } else { + auto v = R.move_as_ok(); + v.first->set_keep_alive(keep); + if (v.second->payload_type() != ton::http::HttpPayload::PayloadType::pt_empty && + !v.first->found_content_length() && !v.first->found_transfer_encoding()) { + v.first->add_header(ton::http::HttpHeader{"Transfer-Encoding", "Chunked"}); + } + promise.set_value(std::move(v)); + } + }); + if (ready_) { + td::actor::send_closure(client_, &ton::http::HttpClient::send_request, std::move(request), std::move(payload), + td::Timestamp::in(3.0), std::move(P)); + close_at_ = td::Timestamp::in(60.0); + } else { + list_.push_back(Query{std::move(request), std::move(payload), td::Timestamp::in(3.0), std::move(P)}); + } + } + + void alarm() override; + + private: + std::string domain_; + bool ready_ = false; + td::Timestamp fail_at_; + td::Timestamp close_at_; + td::actor::ActorOwn client_; + + std::list list_; + + td::actor::ActorId proxy_; +}; + +class HttpProxy : public td::actor::Actor { + public: + HttpProxy() { + } + + void set_port(td::uint16 port) { + if (port_ != 0) { + LOG(ERROR) << "duplicate port"; + std::_Exit(2); + } + port_ = port; + } + + void run() { + if (port_ == 0) { + LOG(ERROR) << "no port specified"; + std::_Exit(2); + } + + class Cb : public ton::http::HttpServer::Callback { + public: + Cb(td::actor::ActorId proxy) : proxy_(proxy) { + } + void receive_request( + std::unique_ptr request, std::shared_ptr payload, + td::Promise, std::shared_ptr>> + promise) override { + td::actor::send_closure(proxy_, &HttpProxy::receive_request, std::move(request), std::move(payload), + std::move(promise)); + } + + private: + td::actor::ActorId proxy_; + }; + + server_ = ton::http::HttpServer::create(port_, std::make_shared(actor_id(this))); + } + + void receive_request( + std::unique_ptr request, std::shared_ptr payload, + td::Promise, std::shared_ptr>> + promise) { + auto host = request->host(); + if (host.size() == 0) { + host = request->url(); + if (host.size() >= 7 && host.substr(0, 7) == "http://") { + host = host.substr(7); + } else if (host.size() >= 8 && host.substr(0, 8) == "https://") { + host = host.substr(7); + } + auto p = host.find('/'); + if (p != std::string::npos) { + host = host.substr(0, p); + } + } else { + if (host.size() >= 7 && host.substr(0, 7) == "http://") { + host = host.substr(7); + } else if (host.size() >= 8 && host.substr(0, 8) == "https://") { + host = host.substr(7); + } + auto p = host.find('/'); + if (p != std::string::npos) { + host = host.substr(0, p); + } + } + if (host.find(':') == std::string::npos) { + host = host + ":80"; + } + + std::transform(host.begin(), host.end(), host.begin(), [](unsigned char c) { return std::tolower(c); }); + auto it = clients_.find(host); + + if (it == clients_.end()) { + auto id = td::actor::create_actor("remote", host, actor_id(this)); + it = clients_.emplace(host, std::move(id)).first; + } + + td::actor::send_closure(it->second, &HttpRemote::receive_request, std::move(request), std::move(payload), + std::move(promise)); + } + + void close_client(std::string host) { + auto it = clients_.find(host); + CHECK(it != clients_.end()); + clients_.erase(it); + } + + private: + td::uint16 port_; + + td::actor::ActorOwn server_; + std::map> clients_; +}; + +void HttpRemote::alarm() { + if (!ready_) { + if (fail_at_ && fail_at_.is_in_past()) { + LOG(INFO) << "closing outbound HTTP connection because of upper level request timeout"; + td::actor::send_closure(proxy_, &HttpProxy::close_client, domain_); + stop(); + return; + } else { + alarm_timestamp().relax(fail_at_); + } + } + if (close_at_ && close_at_.is_in_past()) { + LOG(INFO) << "closing outbound HTTP connection because of idle timeout"; + td::actor::send_closure(proxy_, &HttpProxy::close_client, domain_); + stop(); + return; + } + alarm_timestamp().relax(close_at_); +} + +int main(int argc, char *argv[]) { + SET_VERBOSITY_LEVEL(verbosity_DEBUG); + + td::set_default_failure_signal_handler().ensure(); + + td::actor::ActorOwn x; + td::unique_ptr logger_; + SCOPE_EXIT { + td::log_interface = td::default_log_interface; + }; + + td::OptionsParser p; + p.set_description("simple http proxy"); + p.add_option('v', "verbosity", "set verbosity level", [&](td::Slice arg) { + int v = VERBOSITY_NAME(FATAL) + (td::to_integer(arg)); + SET_VERBOSITY_LEVEL(v); + return td::Status::OK(); + }); + p.add_option('h', "help", "prints_help", [&]() { + char b[10240]; + td::StringBuilder sb(td::MutableSlice{b, 10000}); + sb << p; + std::cout << sb.as_cslice().c_str(); + std::exit(2); + return td::Status::OK(); + }); + p.add_option('p', "port", "sets listening port", [&](td::Slice arg) -> td::Status { + TRY_RESULT(port, td::to_integer_safe(arg)); + td::actor::send_closure(x, &HttpProxy::set_port, port); + return td::Status::OK(); + }); + p.add_option('d', "daemonize", "set SIGHUP", [&]() { + td::set_signal_handler(td::SignalType::HangUp, [](int sig) { +#if TD_DARWIN || TD_LINUX + close(0); + setsid(); +#endif + }).ensure(); + return td::Status::OK(); + }); +#if TD_DARWIN || TD_LINUX + p.add_option('l', "logname", "log to file", [&](td::Slice fname) { + logger_ = td::FileLog::create(fname.str()).move_as_ok(); + td::log_interface = logger_.get(); + return td::Status::OK(); + }); +#endif + + td::actor::Scheduler scheduler({7}); + + scheduler.run_in_context([&] { x = td::actor::create_actor("proxymain"); }); + + scheduler.run_in_context([&] { p.run(argc, argv).ensure(); }); + scheduler.run_in_context([&] { td::actor::send_closure(x, &HttpProxy::run); }); + while (scheduler.run(1)) { + } + + return 0; +} diff --git a/http/http-server.cpp b/http/http-server.cpp new file mode 100644 index 00000000..ac4be1b3 --- /dev/null +++ b/http/http-server.cpp @@ -0,0 +1,51 @@ +/* + 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 2019-2020 Telegram Systems LLP +*/ +#include "http-server.h" +#include "http-inbound-connection.h" + +namespace ton { + +namespace http { + +void HttpServer::start_up() { + class Callback : public td::TcpListener::Callback { + private: + td::actor::ActorId id_; + + public: + Callback(td::actor::ActorId id) : id_(id) { + } + void accept(td::SocketFd fd) override { + td::actor::send_closure(id_, &HttpServer::accepted, std::move(fd)); + } + }; + + listener_ = td::actor::create_actor( + td::actor::ActorOptions().with_name("listener").with_poll(), port_, std::make_unique(actor_id(this))); +} + +void HttpServer::accepted(td::SocketFd fd) { + td::actor::create_actor(td::actor::ActorOptions().with_name("inhttpconn").with_poll(), + std::move(fd), callback_) + .release(); +} + +} // namespace http + +} // namespace ton diff --git a/http/http-server.h b/http/http-server.h new file mode 100644 index 00000000..25730845 --- /dev/null +++ b/http/http-server.h @@ -0,0 +1,61 @@ +/* + 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 2019-2020 Telegram Systems LLP +*/ +#pragma once + +#include "td/actor/actor.h" +#include "http.h" + +#include "td/net/TcpListener.h" + +namespace ton { + +namespace http { + +class HttpInboundConnection; + +class HttpServer : public td::actor::Actor { + public: + class Callback { + public: + virtual ~Callback() = default; + virtual void receive_request( + std::unique_ptr request, std::shared_ptr payload, + td::Promise, std::shared_ptr>> promise) = 0; + }; + + HttpServer(td::uint16 port, std::shared_ptr callback) : port_(port), callback_(std::move(callback)) { + } + + void start_up() override; + void accepted(td::SocketFd fd); + + static td::actor::ActorOwn create(td::uint16 port, std::shared_ptr callback) { + return td::actor::create_actor("httpserver", port, std::move(callback)); + } + + private: + td::uint16 port_; + std::shared_ptr callback_; + + td::actor::ActorOwn listener_; +}; + +} // namespace http + +} // namespace ton diff --git a/http/http.cpp b/http/http.cpp new file mode 100644 index 00000000..c69ce3ee --- /dev/null +++ b/http/http.cpp @@ -0,0 +1,901 @@ +/* + 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 2019-2020 Telegram Systems LLP +*/ +#include "http.h" + +#include + +namespace ton { + +namespace http { + +namespace util { + +td::Result get_line(td::ChainBufferReader &input, std::string &cur_line, bool &read, + size_t max_line_size) { + while (true) { + if (input.size() == 0) { + read = false; + return ""; + } + auto S = input.prepare_read(); + auto f = S.find('\n'); + if (f == td::Slice::npos) { + if (cur_line.size() + S.size() > max_line_size) { + return td::Status::Error("too big http header"); + } + cur_line += S.str(); + input.confirm_read(S.size()); + continue; + } + if (f > 0) { + if (S[f - 1] == '\r') { + cur_line += S.truncate(f - 1).str(); + } else { + cur_line += S.truncate(f).str(); + } + } else { + if (cur_line.size() > 0 && cur_line[cur_line.size() - 1] == '\r') { + cur_line = cur_line.substr(0, cur_line.size() - 1); + } + } + input.confirm_read(f + 1); + auto s = std::move(cur_line); + cur_line = ""; + read = true; + return s; + } +} + +td::Result get_header(std::string line) { + auto p = line.find(':'); + if (p == std::string::npos) { + return td::Status::Error("failed to parse header"); + } + return HttpHeader{line.substr(0, p), td::trim(line.substr(p + 1))}; +} + +} // namespace util + +void HttpHeader::store_http(td::ChainBufferWriter &output) { + output.append(name); + output.append(": "); + output.append(value); + output.append("\r\n"); +} + +tl_object_ptr HttpHeader::store_tl() { + return create_tl_object(name, value); +} + +td::Result> HttpRequest::parse(std::unique_ptr request, std::string &cur_line, + bool &exit_loop, td::ChainBufferReader &input) { + exit_loop = false; + CHECK(!request || !request->check_parse_header_completed()); + while (true) { + bool read; + TRY_RESULT(line, util::get_line(input, cur_line, read, HttpRequest::max_one_header_size())); + if (!read) { + exit_loop = true; + break; + } + + if (!request) { + auto v = td::full_split(line); + if (v.size() != 3) { + return td::Status::Error("expected http header in form "); + } + TRY_RESULT_ASSIGN(request, HttpRequest::create(v[0], v[1], v[2])); + } else { + if (line.size() == 0) { + TRY_STATUS(request->complete_parse_header()); + break; + } else { + TRY_RESULT(h, util::get_header(std::move(line))); + TRY_STATUS(request->add_header(std::move(h))); + } + } + } + + return std::move(request); +} + +HttpRequest::HttpRequest(std::string method, std::string url, std::string proto_version) + : method_(std::move(method)), url_(std::move(url)), proto_version_(std::move(proto_version)) { + if (proto_version_ == "HTTP/1.1") { + keep_alive_ = true; + } else { + keep_alive_ = false; + } +} + +td::Result> HttpRequest::create(std::string method, std::string url, + std::string proto_version) { + if (proto_version != "HTTP/1.0" && proto_version != "HTTP/1.1") { + return td::Status::Error(PSTRING() << "unsupported http version '" << proto_version << "'"); + } + + static const std::vector supported_methods{"GET", "HEAD", "POST", "PUT", + "DELETE", "CONNECT", "OPTIONS", "TRACE"}; + bool found = false; + for (const auto &e : supported_methods) { + if (e == method) { + found = true; + break; + } + } + + if (!found) { + return td::Status::Error(PSTRING() << "unsupported http method '" << method << "'"); + } + + return std::make_unique(std::move(method), std::move(url), std::move(proto_version)); +} + +bool HttpRequest::check_parse_header_completed() const { + return parse_header_completed_; +} + +td::Status HttpRequest::complete_parse_header() { + CHECK(!parse_header_completed_); + parse_header_completed_ = true; + return td::Status::OK(); +} + +td::Result> HttpRequest::create_empty_payload() { + CHECK(check_parse_header_completed()); + + if (!need_payload()) { + return std::make_shared(HttpPayload::PayloadType::pt_empty); + } else if (found_content_length_) { + return std::make_shared(HttpPayload::PayloadType::pt_content_length, low_watermark(), high_watermark(), + content_length_); + } else if (found_transfer_encoding_) { + return std::make_shared(HttpPayload::PayloadType::pt_chunked, low_watermark(), high_watermark()); + } else { + return td::Status::Error("expected Content-Length/Transfer-Encoding header"); + } +} + +bool HttpRequest::need_payload() const { + return found_content_length_ || found_transfer_encoding_; +} + +td::Status HttpRequest::add_header(HttpHeader header) { + auto lc_name = header.name; + auto lc_value = header.value; + std::transform(lc_name.begin(), lc_name.end(), lc_name.begin(), [](unsigned char c) { return std::tolower(c); }); + std::transform(lc_value.begin(), lc_value.end(), lc_value.begin(), [](unsigned char c) { return std::tolower(c); }); + + auto S = td::trim(td::Slice(lc_value)); + + if (lc_name == "content-length") { + TRY_RESULT(len, td::to_integer_safe(S)); + if (found_transfer_encoding_ || found_content_length_) { + return td::Status::Error("duplicate Content-Length/Transfer-Encoding"); + } + if (len > HttpRequest::max_payload_size()) { + return td::Status::Error("too big Content-Length"); + } + content_length_ = len; + found_content_length_ = true; + } else if (lc_name == "transfer-encoding") { + // expect chunked, don't event check + if (found_transfer_encoding_ || found_content_length_) { + return td::Status::Error("duplicate Content-Length/Transfer-Encoding"); + } + found_transfer_encoding_ = true; + } else if (lc_name == "host") { + if (host_.size() > 0) { + return td::Status::Error("duplicate Host"); + } + host_ = S.str(); + } else if (lc_name == "connection" && S == "keep-alive") { + keep_alive_ = true; + return td::Status::OK(); + } else if (lc_name == "connection" && S == "close") { + keep_alive_ = false; + return td::Status::OK(); + } else if (lc_name == "proxy-connection" && S == "keep-alive") { + keep_alive_ = true; + return td::Status::OK(); + } else if (lc_name == "proxy-connection" && S == "close") { + keep_alive_ = false; + return td::Status::OK(); + } + options_.emplace_back(std::move(header)); + return td::Status::OK(); +} + +void HttpRequest::store_http(td::ChainBufferWriter &output) { + std::string line = method_ + " " + url_ + " " + proto_version_ + "\r\n"; + output.append(line); + for (auto &x : options_) { + x.store_http(output); + } + if (keep_alive_) { + HttpHeader{"Connection", "Keep-Alive"}.store_http(output); + } else { + HttpHeader{"Connection", "Close"}.store_http(output); + } + output.append(td::Slice("\r\n", 2)); +} + +tl_object_ptr HttpRequest::store_tl(td::Bits256 req_id) { + std::vector> headers; + headers.reserve(options_.size()); + for (auto &h : options_) { + headers.push_back(h.store_tl()); + } + if (keep_alive_) { + headers.push_back(HttpHeader{"Connection", "Keep-Alive"}.store_tl()); + } else { + headers.push_back(HttpHeader{"Connection", "Close"}.store_tl()); + } + return create_tl_object(req_id, method_, url_, proto_version_, std::move(headers)); +} + +td::Status HttpPayload::parse(td::ChainBufferReader &input) { + CHECK(!parse_completed()); + while (true) { + if (high_watermark_reached()) { + return td::Status::OK(); + } + switch (state_) { + case ParseState::reading_chunk_header: { + bool read; + TRY_RESULT(l, util::get_line(input, tmp_, read, HttpRequest::max_one_header_size())); + if (!read) { + return td::Status::OK(); + } + if (l.size() == 0) { + return td::Status::Error("expected chunk, found empty line"); + } + auto v = td::split(l); + + TRY_RESULT(size, td::hex_to_integer_safe(v.first)); + if (size == 0) { + state_ = ParseState::reading_trailer; + break; + } + cur_chunk_size_ = size; + state_ = ParseState::reading_chunk_data; + } break; + case ParseState::reading_chunk_data: { + if (cur_chunk_size_ == 0) { + switch (type_) { + case PayloadType::pt_empty: + UNREACHABLE(); + case PayloadType::pt_eof: + cur_chunk_size_ = 1 << 30; + break; + case PayloadType::pt_chunked: + state_ = ParseState::reading_crlf; + break; + case PayloadType::pt_content_length: { + LOG(INFO) << "payload parse success"; + const std::lock_guard lock{mutex_}; + state_ = ParseState::completed; + run_callbacks(); + return td::Status::OK(); + } break; + } + break; + } + if (input.size() == 0) { + return td::Status::OK(); + } + auto S = get_read_slice(); + auto s = input.size(); + if (S.size() > s) { + S.truncate(s); + } + CHECK(input.advance(S.size(), S) == S.size()); + confirm_read(S.size()); + } break; + case ParseState::reading_trailer: { + bool read; + TRY_RESULT(l, util::get_line(input, tmp_, read, HttpRequest::max_one_header_size())); + if (!read) { + return td::Status::OK(); + } + if (!l.size()) { + LOG(INFO) << "payload parse success"; + const std::lock_guard lock{mutex_}; + state_ = ParseState::completed; + run_callbacks(); + return td::Status::OK(); + } + TRY_RESULT(h, util::get_header(std::move(l))); + add_trailer(std::move(h)); + if (trailer_size_ > HttpRequest::max_header_size()) { + return td::Status::Error("too big trailer part"); + } + } break; + case ParseState::reading_crlf: { + if (input.size() < 2) { + return td::Status::OK(); + } + td::uint8 buf[2]; + CHECK(input.advance(2, td::MutableSlice(buf, 2)) == 2); + if (buf[0] != '\r' || buf[1] != '\n') { + return td::Status::Error(PSTRING() + << "expected CRLF " << static_cast(buf[0]) << " " << static_cast(buf[1])); + } + state_ = ParseState::reading_chunk_header; + } break; + case ParseState::completed: + return td::Status::OK(); + } + } +} + +bool HttpPayload::parse_completed() const { + return state_.load(std::memory_order_consume) == ParseState::completed; +} + +td::MutableSlice HttpPayload::get_read_slice() { + const std::lock_guard lock{mutex_}; + if (last_chunk_free_ == 0) { + auto B = td::BufferSlice{chunk_size_}; + last_chunk_free_ = B.size(); + chunks_.push_back(std::move(B)); + } + auto b = chunks_.back().as_slice(); + b.remove_prefix(b.size() - last_chunk_free_); + if (b.size() > cur_chunk_size_) { + b.truncate(cur_chunk_size_); + } + return b; +} + +void HttpPayload::confirm_read(size_t s) { + const std::lock_guard lock{mutex_}; + last_chunk_free_ -= s; + cur_chunk_size_ -= s; + ready_bytes_ += s; + run_callbacks(); +} + +void HttpPayload::add_trailer(HttpHeader header) { + const std::lock_guard lock{mutex_}; + ready_bytes_ += header.size(); + trailer_size_ += header.size(); + run_callbacks(); + trailer_.push_back(std::move(header)); +} + +void HttpPayload::add_chunk(td::BufferSlice data) { + //LOG(INFO) << "payload: added " << data.size() << " bytes"; + while (data.size() > 0) { + if (!cur_chunk_size_) { + cur_chunk_size_ = data.size(); + } + auto S = get_read_slice(); + CHECK(S.size() > 0); + if (S.size() > data.size()) { + S.truncate(data.size()); + } + S.copy_from(data.as_slice().truncate(S.size())); + data.confirm_read(S.size()); + confirm_read(S.size()); + } +} + +void HttpPayload::slice_gc() { + const std::lock_guard lock{mutex_}; + while (chunks_.size() > 0) { + auto &x = chunks_.front(); + if (state_ == ParseState::completed || state_ == ParseState::reading_trailer) { + if (chunks_.size() == 1) { + x.truncate(x.size() - last_chunk_free_); + last_chunk_free_ = 0; + } + } + if (x.size() == 0) { + CHECK(chunks_.size() > 1 || !last_chunk_free_); + chunks_.pop_front(); + continue; + } + break; + } +} + +td::BufferSlice HttpPayload::get_slice(size_t max_size) { + const std::lock_guard lock{mutex_}; + while (chunks_.size() > 0) { + auto &x = chunks_.front(); + if (x.size() == 0) { + CHECK(chunks_.size() > 1 || !last_chunk_free_); + chunks_.pop_front(); + continue; + } + td::BufferSlice b; + if (chunks_.size() > 1 || !last_chunk_free_) { + if (x.size() <= max_size) { + b = std::move(x); + chunks_.pop_front(); + } else { + b = x.clone(); + b.truncate(max_size); + x.confirm_read(max_size); + } + } else { + b = x.clone(); + CHECK(b.size() >= last_chunk_free_); + if (b.size() == last_chunk_free_) { + return td::BufferSlice{}; + } + b.truncate(b.size() - last_chunk_free_); + if (b.size() > max_size) { + b.truncate(max_size); + } + x.confirm_read(b.size()); + } + ready_bytes_ -= b.size(); + run_callbacks(); + return b; + } + return td::BufferSlice{}; +} + +HttpHeader HttpPayload::get_header() { + const std::lock_guard lock{mutex_}; + if (trailer_.size() == 0) { + return HttpHeader{}; + } else { + auto h = std::move(trailer_.front()); + auto s = h.size(); + trailer_.pop_front(); + ready_bytes_ -= s; + run_callbacks(); + return h; + } +} + +void HttpPayload::run_callbacks() { + for (auto &x : callbacks_) { + if (state_.load(std::memory_order_relaxed) == ParseState::completed) { + x->completed(); + } else { + x->run(ready_bytes_); + } + } +} + +void HttpPayload::store_http(td::ChainBufferWriter &output, size_t max_size, HttpPayload::PayloadType store_type) { + if (store_type == PayloadType::pt_empty) { + return; + } + slice_gc(); + while (chunks_.size() > 0 && max_size > 0) { + auto cur_state = state_.load(std::memory_order_consume); + auto s = get_slice(max_size); + if (s.size() == 0) { + if (cur_state != ParseState::reading_trailer && cur_state != ParseState::completed) { + return; + } else { + break; + } + } + CHECK(s.size() <= max_size); + max_size -= s.size(); + if (store_type == PayloadType::pt_chunked) { + char buf[64]; + ::sprintf(buf, "%lx\r\n", s.size()); + output.append(td::Slice(buf, strlen(buf))); + } + + output.append(std::move(s)); + + if (store_type == PayloadType::pt_chunked) { + output.append(td::Slice("\r\n", 2)); + } + } + if (chunks_.size() != 0) { + return; + } + if (!written_zero_chunk_) { + if (store_type == PayloadType::pt_chunked) { + output.append(td::Slice("0\r\n", 3)); + } + written_zero_chunk_ = true; + } + + if (store_type != PayloadType::pt_chunked) { + written_trailer_ = true; + return; + } + + while (max_size > 0) { + auto cur_state = state_.load(std::memory_order_consume); + HttpHeader h = get_header(); + if (h.empty()) { + if (cur_state != ParseState::completed) { + return; + } else { + break; + } + } + auto s = h.size(); + h.store_http(output); + if (max_size <= s) { + return; + } + max_size -= s; + } + + if (!written_trailer_) { + output.append(td::Slice("\r\n", 2)); + written_trailer_ = true; + } +} + +tl_object_ptr HttpPayload::store_tl(size_t max_size) { + auto b = ready_bytes(); + if (b > max_size) { + b = max_size; + } + max_size = b; + td::BufferSlice x{b}; + auto S = x.as_slice(); + auto obj = create_tl_object(std::move(x), + std::vector>(), false); + + slice_gc(); + while (chunks_.size() > 0 && max_size > 0) { + auto cur_state = state_.load(std::memory_order_consume); + auto s = get_slice(max_size); + if (s.size() == 0) { + if (cur_state != ParseState::reading_trailer && cur_state != ParseState::completed) { + LOG(INFO) << "state not trailer/completed"; + obj->data_.truncate(obj->data_.size() - S.size()); + return obj; + } else { + break; + } + } + CHECK(s.size() <= max_size); + S.copy_from(s); + S.remove_prefix(s.size()); + max_size -= s.size(); + } + obj->data_.truncate(obj->data_.size() - S.size()); + if (chunks_.size() != 0) { + return obj; + } + if (!written_zero_chunk_) { + written_zero_chunk_ = true; + } + + LOG(INFO) << "data completed"; + + while (max_size > 0) { + auto cur_state = state_.load(std::memory_order_consume); + HttpHeader h = get_header(); + if (h.empty()) { + if (cur_state != ParseState::completed) { + LOG(INFO) << "state not completed"; + return obj; + } else { + break; + } + } + auto s = h.size(); + obj->trailer_.push_back(h.store_tl()); + if (max_size <= s) { + return obj; + } + max_size -= s; + } + + written_trailer_ = true; + obj->last_ = true; + return obj; +} + +/*tl_object_ptr HttpPayload::store_tl(size_t max_size) { + auto obj = create_tl_object(std::vector(), + std::vector>(), false); + if (type_ == PayloadType::pt_empty) { + return obj; + } + size_t sum = 0; + while (chunks_.size() > 0) { + auto &p = chunks_.front(); + size_t s = p.size(); + bool m = true; + if (chunks_.size() == 1) { + s -= last_chunk_free_; + m = false; + } + sum += s; + + if (m) { + obj->data_.push_back(std::move(p)); + } else { + auto B = p.clone(); + B.truncate(s); + obj->data_.push_back(std::move(B)); + p.confirm_read(s); + } + CHECK(ready_bytes_ >= s); + ready_bytes_ -= s; + if (!m) { + return obj; + } + chunks_.pop_front(); + if (sum > max_size) { + return obj; + } + } + if (state_ != ParseState::reading_trailer && state_ != ParseState::completed) { + return obj; + } + if (!written_zero_chunk_) { + written_zero_chunk_ = true; + } + while (true) { + if (trailer_.size() == 0) { + break; + } + auto &p = trailer_.front(); + sum += p.name.size() + p.value.size() + 2; + ready_bytes_ -= p.name.size() + p.value.size() + 2; + obj->trailer_.push_back(p.store_tl()); + trailer_.pop_front(); + if (sum > max_size) { + return obj; + } + } + if (state_ != ParseState::completed) { + return obj; + } + obj->last_ = true; + return obj; +} + +tl_object_ptr HttpPayload::store_info(size_t max_size) { + if (type_ == PayloadType::pt_empty) { + return create_tl_object(create_tl_object()); + } + if (!parse_completed()) { + return create_tl_object(); + } + if (ready_bytes_ > max_size) { + return create_tl_object(ready_bytes_); + } + auto obj = store_tl(max_size); + CHECK(obj->last_); + return create_tl_object( + create_tl_object(std::move(obj->data_), std::move(obj->trailer_))); +}*/ + +void HttpPayload::add_callback(std::unique_ptr callback) { + const std::lock_guard lock{mutex_}; + callbacks_.push_back(std::move(callback)); +} + +td::Result> HttpResponse::parse(std::unique_ptr response, + std::string &cur_line, bool force_no_payload, + bool keep_alive, bool &exit_loop, + td::ChainBufferReader &input) { + exit_loop = false; + CHECK(!response || !response->check_parse_header_completed()); + while (true) { + bool read; + TRY_RESULT(line, util::get_line(input, cur_line, read, HttpRequest::max_one_header_size())); + if (!read) { + exit_loop = true; + break; + } + + if (!response) { + auto v = td::full_split(line, ' ', 3); + if (v.size() != 3) { + return td::Status::Error("expected http header in form "); + } + TRY_RESULT(code, td::to_integer_safe(std::move(v[1]))); + TRY_RESULT_ASSIGN(response, HttpResponse::create(v[0], code, v[2], force_no_payload, keep_alive)); + } else { + if (line.size() == 0) { + TRY_STATUS(response->complete_parse_header()); + break; + } else { + TRY_RESULT(h, util::get_header(std::move(line))); + TRY_STATUS(response->add_header(std::move(h))); + } + } + } + + return std::move(response); +} + +HttpResponse::HttpResponse(std::string proto_version, td::uint32 code, std::string reason, bool force_no_payload, + bool keep_alive) + : proto_version_(std::move(proto_version)) + , code_(code) + , reason_(std::move(reason)) + , force_no_payload_(force_no_payload) + , force_no_keep_alive_(!keep_alive) { +} + +td::Result> HttpResponse::create(std::string proto_version, td::uint32 code, + std::string reason, bool force_no_payload, + bool keep_alive) { + if (proto_version != "HTTP/1.0" && proto_version != "HTTP/1.1") { + return td::Status::Error(PSTRING() << "unsupported http version '" << proto_version << "'"); + } + + if (code < 100 || code > 999) { + return td::Status::Error(PSTRING() << "bad status code '" << code << "'"); + } + + return std::make_unique(std::move(proto_version), code, std::move(reason), force_no_payload, + keep_alive); +} + +td::Status HttpResponse::complete_parse_header() { + CHECK(!parse_header_completed_); + parse_header_completed_ = true; + return td::Status::OK(); +} + +bool HttpResponse::check_parse_header_completed() const { + return parse_header_completed_; +} + +td::Result> HttpResponse::create_empty_payload() { + CHECK(check_parse_header_completed()); + + if (!need_payload()) { + return std::make_shared(HttpPayload::PayloadType::pt_empty); + } else if (found_content_length_) { + return std::make_shared(HttpPayload::PayloadType::pt_content_length, low_watermark(), high_watermark(), + content_length_); + } else if (found_transfer_encoding_) { + return std::make_shared(HttpPayload::PayloadType::pt_chunked, low_watermark(), high_watermark()); + } else { + return std::make_shared(HttpPayload::PayloadType::pt_eof, low_watermark(), high_watermark()); + } +} + +bool HttpResponse::need_payload() const { + return !force_no_payload_ && (code_ >= 200) && code_ != 204 && code_ != 304; +} + +td::Status HttpResponse::add_header(HttpHeader header) { + auto lc_name = header.name; + auto lc_value = header.value; + std::transform(lc_name.begin(), lc_name.end(), lc_name.begin(), [](unsigned char c) { return std::tolower(c); }); + std::transform(lc_value.begin(), lc_value.end(), lc_value.begin(), [](unsigned char c) { return std::tolower(c); }); + + auto S = td::trim(td::Slice(lc_value)); + + if (lc_name == "content-length") { + TRY_RESULT(len, td::to_integer_safe(S)); + if (found_transfer_encoding_ || found_content_length_) { + return td::Status::Error("duplicate Content-Length/Transfer-Encoding"); + } + if (len > HttpRequest::max_payload_size()) { + return td::Status::Error("too big Content-Length"); + } + content_length_ = len; + found_content_length_ = true; + } else if (lc_name == "transfer-encoding") { + // expect chunked, don't event check + if (found_transfer_encoding_ || found_content_length_) { + return td::Status::Error("duplicate Content-Length/Transfer-Encoding"); + } + found_transfer_encoding_ = true; + } else if (lc_name == "connection" && S == "keep-alive") { + keep_alive_ = true; + return td::Status::OK(); + } else if (lc_name == "connection" && S == "close") { + keep_alive_ = false; + return td::Status::OK(); + } else if (lc_name == "proxy-connection" && S == "keep-alive") { + keep_alive_ = true; + return td::Status::OK(); + } else if (lc_name == "proxy-connection" && S == "close") { + keep_alive_ = false; + return td::Status::OK(); + } + options_.emplace_back(std::move(header)); + return td::Status::OK(); +} + +void HttpResponse::store_http(td::ChainBufferWriter &output) { + std::string line = proto_version_ + " " + std::to_string(code_) + " " + reason_ + "\r\n"; + output.append(line); + for (auto &x : options_) { + x.store_http(output); + } + if (keep_alive_) { + HttpHeader{"Connection", "Keep-Alive"}.store_http(output); + } else { + HttpHeader{"Connection", "Close"}.store_http(output); + } + output.append(td::Slice("\r\n", 2)); +} + +tl_object_ptr HttpResponse::store_tl() { + std::vector> headers; + headers.reserve(options_.size()); + for (auto &h : options_) { + headers.push_back(h.store_tl()); + } + if (keep_alive_) { + headers.push_back(HttpHeader{"Connection", "Keep-Alive"}.store_tl()); + } else { + headers.push_back(HttpHeader{"Connection", "Close"}.store_tl()); + } + return create_tl_object(proto_version_, code_, reason_, std::move(headers)); +} + +td::Status HttpHeader::basic_check() { + for (auto &c : name) { + if (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == ':') { + return td::Status::Error("bad character in header name"); + } + } + for (auto &c : value) { + if (c == '\r' || c == '\n') { + return td::Status::Error("bad character in header name"); + } + } + return td::Status::OK(); +} + +void answer_error(HttpStatusCode code, std::string reason, + td::Promise, std::shared_ptr>> promise) { + if (reason.empty()) { + switch (code) { + case status_ok: + reason = "OK"; + break; + case status_bad_request: + reason = "Bad Request"; + break; + case status_method_not_allowed: + reason = "Method Not Allowed"; + break; + case status_internal_server_error: + reason = "Internal Server Error"; + break; + case status_bad_gateway: + reason = "Bad Gateway"; + break; + case status_gateway_timeout: + reason = "Gateway Timeout"; + break; + default: + reason = "Unknown"; + break; + } + } + auto response = HttpResponse::create("HTTP/1.0", code, reason, false, false).move_as_ok(); + response->add_header(HttpHeader{"Content-Length", "0"}); + auto payload = response->create_empty_payload().move_as_ok(); + CHECK(payload->parse_completed()); + promise.set_value(std::make_pair(std::move(response), std::move(payload))); +} + +} // namespace http + +} // namespace ton diff --git a/http/http.h b/http/http.h new file mode 100644 index 00000000..4f3b6517 --- /dev/null +++ b/http/http.h @@ -0,0 +1,326 @@ +/* + 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 2019-2020 Telegram Systems LLP +*/ +#pragma once + +#include "td/utils/buffer.h" +#include "auto/tl/ton_api.h" +#include "td/actor/PromiseFuture.h" + +#include +#include +#include + +namespace ton { + +namespace http { + +enum HttpStatusCode : td::uint32 { + status_ok = 200, + status_bad_request = 400, + status_method_not_allowed = 405, + status_internal_server_error = 500, + status_bad_gateway = 502, + status_gateway_timeout = 504 +}; + +struct HttpHeader { + std::string name; + std::string value; + void store_http(td::ChainBufferWriter &output); + tl_object_ptr store_tl(); + + size_t size() const { + return 2 + name.size() + value.size(); + } + bool empty() const { + return name.size() == 0; + } + + td::Status basic_check(); +}; + +namespace util { + +td::Result get_line(td::ChainBufferReader &input, std::string &cur_line, bool &read, size_t max_line_size); +td::Result get_header(std::string line); + +} // namespace util + +class HttpPayload { + public: + enum class PayloadType { pt_empty, pt_eof, pt_chunked, pt_content_length }; + HttpPayload(PayloadType t, size_t low_watermark, size_t high_watermark, td::uint64 size) + : type_(t), low_watermark_(low_watermark), high_watermark_(high_watermark), cur_chunk_size_(size) { + CHECK(t == PayloadType::pt_content_length); + state_ = ParseState::reading_chunk_data; + } + HttpPayload(PayloadType t, size_t low_watermark, size_t high_watermark) + : type_(t), low_watermark_(low_watermark), high_watermark_(high_watermark) { + CHECK(t != PayloadType::pt_content_length); + CHECK(t != PayloadType::pt_empty); + switch (t) { + case PayloadType::pt_empty: + UNREACHABLE(); + case PayloadType::pt_eof: + state_ = ParseState::reading_chunk_data; + break; + case PayloadType::pt_chunked: + state_ = ParseState::reading_chunk_header; + break; + case PayloadType::pt_content_length: + state_ = ParseState::reading_chunk_data; + break; + } + } + HttpPayload(PayloadType t) : type_(t) { + CHECK(t == PayloadType::pt_empty); + state_ = ParseState::completed; + written_zero_chunk_ = true; + written_trailer_ = true; + } + + class Callback { + public: + virtual void run(size_t ready_bytes) = 0; + virtual void completed() = 0; + virtual ~Callback() = default; + }; + void add_callback(std::unique_ptr callback); + void run_callbacks(); + + td::Status parse(td::ChainBufferReader &input); + bool parse_completed() const; + void complete_parse() { + state_ = ParseState::completed; + run_callbacks(); + } + size_t ready_bytes() const { + return ready_bytes_; + } + bool low_watermark_reached() const { + return ready_bytes_ <= low_watermark_; + } + bool high_watermark_reached() const { + return ready_bytes_ > high_watermark_; + } + PayloadType payload_type() const { + return type_; + } + td::MutableSlice get_read_slice(); + void confirm_read(size_t s); + void add_trailer(HttpHeader header); + void add_chunk(td::BufferSlice data); + td::BufferSlice get_slice(size_t max_size); + void slice_gc(); + HttpHeader get_header(); + + void store_http(td::ChainBufferWriter &output, size_t max_size, HttpPayload::PayloadType store_type); + tl_object_ptr store_tl(size_t max_size); + + bool written() const { + return ready_bytes_ == 0 && parse_completed() && written_zero_chunk_ && written_trailer_; + } + + private: + enum class ParseState { reading_chunk_header, reading_chunk_data, reading_trailer, reading_crlf, completed }; + PayloadType type_{PayloadType::pt_chunked}; + size_t low_watermark_; + size_t high_watermark_; + std::string tmp_; + std::list chunks_; + std::list trailer_; + size_t trailer_size_ = 0; + size_t ready_bytes_ = 0; + td::uint64 cur_chunk_size_ = 0; + size_t last_chunk_free_ = 0; + size_t chunk_size_ = 1 << 14; + bool written_zero_chunk_ = false; + bool written_trailer_ = false; + + std::list> callbacks_; + + std::atomic state_{ParseState::reading_chunk_header}; + std::mutex mutex_; +}; + +class HttpRequest { + public: + static constexpr size_t max_header_size() { + return 16 << 10; + } + + static constexpr size_t max_one_header_size() { + return 16 << 10; + } + + static constexpr size_t max_payload_size() { + return 1 << 20; + } + + static constexpr size_t low_watermark() { + return 1 << 14; + } + static constexpr size_t high_watermark() { + return 1 << 17; + } + + static td::Result> create(std::string method, std::string url, + std::string proto_version); + + HttpRequest(std::string method, std::string url, std::string proto_version); + + bool check_parse_header_completed() const; + bool keep_alive() const { + return keep_alive_; + } + + td::Status complete_parse_header(); + td::Status add_header(HttpHeader header); + td::Result> create_empty_payload(); + bool need_payload() const; + + const auto &method() const { + return method_; + } + const auto &url() const { + return url_; + } + const auto &proto_version() const { + return proto_version_; + } + const auto &host() const { + return host_; + } + + bool no_payload_in_answer() const { + return method_ == "HEAD"; + } + + void set_keep_alive(bool value) { + keep_alive_ = value; + } + + void store_http(td::ChainBufferWriter &output); + tl_object_ptr store_tl(td::Bits256 req_id); + + static td::Result> parse(std::unique_ptr request, std::string &cur_line, + bool &exit_loop, td::ChainBufferReader &input); + + private: + std::string method_; + std::string url_; + std::string proto_version_; + + std::string host_; + size_t content_length_ = 0; + bool found_content_length_ = false; + bool found_transfer_encoding_ = false; + + bool parse_header_completed_ = false; + bool keep_alive_ = false; + + std::vector options_; +}; + +class HttpResponse { + public: + static constexpr size_t max_header_size() { + return 16 << 10; + } + + static constexpr size_t max_one_header_size() { + return 16 << 10; + } + + static constexpr size_t max_payload_size() { + return 1 << 20; + } + + static constexpr size_t low_watermark() { + return 1 << 14; + } + static constexpr size_t high_watermark() { + return 1 << 17; + } + + static td::Result> create(std::string proto_version, td::uint32 code, + std::string reason, bool force_no_payload, bool keep_alive); + + HttpResponse(std::string proto_version, td::uint32 code, std::string reason, bool force_no_payload, bool keep_alive); + + bool check_parse_header_completed() const; + bool keep_alive() const { + return !force_no_payload_ && keep_alive_; + } + + td::Status complete_parse_header(); + td::Status add_header(HttpHeader header); + td::Result> create_empty_payload(); + bool need_payload() const; + + auto code() const { + return code_; + } + const auto &proto_version() const { + return proto_version_; + } + void set_keep_alive(bool value) { + keep_alive_ = value; + } + + void store_http(td::ChainBufferWriter &output); + tl_object_ptr store_tl(); + + static td::Result> parse(std::unique_ptr request, std::string &cur_line, + bool force_no_payload, bool keep_alive, bool &exit_loop, + td::ChainBufferReader &input); + + static std::unique_ptr create_error(HttpStatusCode code, std::string reason); + + bool found_transfer_encoding() const { + return found_transfer_encoding_; + } + bool found_content_length() const { + return found_content_length_; + } + + private: + std::string proto_version_; + td::uint32 code_; + std::string reason_; + + bool force_no_payload_ = false; + bool force_no_keep_alive_ = false; + + size_t content_length_ = 0; + bool found_content_length_ = false; + bool found_transfer_encoding_ = false; + + bool parse_header_completed_ = false; + bool keep_alive_ = false; + + std::vector options_; +}; + +void answer_error(HttpStatusCode code, std::string reason, + td::Promise, std::shared_ptr>> promise); + +} // namespace http + +} // namespace ton diff --git a/lite-client/lite-client.cpp b/lite-client/lite-client.cpp index e273d593..42ce655b 100644 --- a/lite-client/lite-client.cpp +++ b/lite-client/lite-client.cpp @@ -55,7 +55,7 @@ #include "vm/boc.h" #include "vm/cellops.h" #include "vm/cells/MerkleProof.h" -#include "vm/continuation.h" +#include "vm/vm.h" #include "vm/cp0.h" #include "ton/ton-shard.h" #include "openssl/rand.hpp" diff --git a/rldp-http-proxy/CMakeLists.txt b/rldp-http-proxy/CMakeLists.txt new file mode 100644 index 00000000..cb856ca3 --- /dev/null +++ b/rldp-http-proxy/CMakeLists.txt @@ -0,0 +1,5 @@ +cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR) + +add_executable(rldp-http-proxy rldp-http-proxy.cpp) +target_include_directories(rldp-http-proxy PUBLIC $) +target_link_libraries(rldp-http-proxy PRIVATE tonhttp rldp dht) diff --git a/rldp-http-proxy/rldp-http-proxy.cpp b/rldp-http-proxy/rldp-http-proxy.cpp new file mode 100644 index 00000000..71ea2213 --- /dev/null +++ b/rldp-http-proxy/rldp-http-proxy.cpp @@ -0,0 +1,1014 @@ +/* + This file is part of TON Blockchain source code. + + TON Blockchain is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + TON Blockchain is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with TON Blockchain. If not, see . + + In addition, as a special exception, the copyright holders give permission + to link the code of portions of this program with the OpenSSL library. + You must obey the GNU General Public License in all respects for all + of the code used other than OpenSSL. If you modify file(s) with this + exception, you may extend this exception to your version of the file(s), + but you are not obligated to do so. If you do not wish to do so, delete this + exception statement from your version. If you delete this exception statement + from all source files in the program, then also delete it here. + + Copyright 2019-2020 Telegram Systems LLP +*/ +#include "http/http-server.h" +#include "http/http-client.h" + +#include "td/utils/port/signals.h" +#include "td/utils/OptionsParser.h" +#include "td/utils/FileLog.h" +#include "td/utils/Random.h" +#include "td/utils/filesystem.h" + +#include "auto/tl/ton_api_json.h" + +#include "common/errorcode.h" + +#include "adnl/adnl.h" +#include "rldp/rldp.h" +#include "dht/dht.h" + +#include +#include + +#if TD_DARWIN || TD_LINUX +#include +#endif + +class RldpHttpProxy; + +class HttpRemote : public td::actor::Actor { + public: + struct Query { + std::unique_ptr request; + std::shared_ptr payload; + td::Timestamp timeout; + td::Promise, std::shared_ptr>> promise; + }; + HttpRemote(td::IPAddress addr) : addr_(addr) { + } + void start_up() override { + class Cb : public ton::http::HttpClient::Callback { + public: + Cb(td::actor::ActorId id) : id_(id) { + } + void on_ready() override { + td::actor::send_closure(id_, &HttpRemote::set_ready, true); + } + void on_stop_ready() override { + td::actor::send_closure(id_, &HttpRemote::set_ready, false); + } + + private: + td::actor::ActorId id_; + }; + client_ = ton::http::HttpClient::create_multi("", addr_, 1000, 100, std::make_shared(actor_id(this))); + } + void set_ready(bool ready) { + ready_ = ready; + } + void receive_request( + std::unique_ptr request, std::shared_ptr payload, + td::Promise, std::shared_ptr>> + promise) { + if (ready_) { + bool keep = request->keep_alive(); + auto P = td::PromiseCreator::lambda( + [promise = std::move(promise), keep]( + td::Result, std::shared_ptr>> + R) mutable { + if (R.is_error()) { + promise.set_error(R.move_as_error()); + } else { + auto v = R.move_as_ok(); + v.first->set_keep_alive(keep); + if (v.second->payload_type() != ton::http::HttpPayload::PayloadType::pt_empty && + !v.first->found_content_length() && !v.first->found_transfer_encoding()) { + v.first->add_header(ton::http::HttpHeader{"Transfer-Encoding", "Chunked"}); + } + promise.set_value(std::move(v)); + } + }); + td::actor::send_closure(client_, &ton::http::HttpClient::send_request, std::move(request), std::move(payload), + td::Timestamp::in(30.0), std::move(P)); + } else { + ton::http::answer_error(ton::http::HttpStatusCode::status_bad_request, "", std::move(promise)); + } + } + + private: + td::IPAddress addr_; + bool ready_ = false; + td::actor::ActorOwn client_; +}; + +class HttpRldpPayloadReceiver : public td::actor::Actor { + public: + HttpRldpPayloadReceiver(std::shared_ptr payload, td::Bits256 transfer_id, + ton::adnl::AdnlNodeIdShort src, ton::adnl::AdnlNodeIdShort local_id, + td::actor::ActorId adnl, td::actor::ActorId rldp) + : payload_(std::move(payload)), id_(transfer_id), src_(src), local_id_(local_id), adnl_(adnl), rldp_(rldp) { + } + + void start_up() override { + class Cb : public ton::http::HttpPayload::Callback { + public: + Cb(td::actor::ActorId id) : self_id_(id) { + } + void run(size_t ready_bytes) override { + if (!reached_ && ready_bytes < watermark_) { + reached_ = true; + td::actor::send_closure(self_id_, &HttpRldpPayloadReceiver::request_more_data); + } else if (reached_ && ready_bytes >= watermark_) { + reached_ = false; + } + } + void completed() override { + } + + private: + size_t watermark_ = watermark(); + bool reached_ = false; + td::actor::ActorId self_id_; + }; + + payload_->add_callback(std::make_unique(actor_id(this))); + request_more_data(); + } + + void request_more_data() { + LOG(INFO) << "HttpPayloadReceiver: sent=" << sent_ << " completed=" << payload_->parse_completed() + << " ready=" << payload_->ready_bytes() << " watermark=" << watermark(); + if (sent_ || payload_->parse_completed()) { + return; + } + if (payload_->ready_bytes() >= watermark()) { + return; + } + sent_ = true; + auto P = td::PromiseCreator::lambda([SelfId = actor_id(this)](td::Result R) { + if (R.is_error()) { + td::actor::send_closure(SelfId, &HttpRldpPayloadReceiver::abort_query, R.move_as_error()); + } else { + td::actor::send_closure(SelfId, &HttpRldpPayloadReceiver::add_data, R.move_as_ok()); + } + }); + + auto f = ton::create_serialize_tl_object( + id_, seqno_++, static_cast(chunk_size())); + td::actor::send_closure(rldp_, &ton::rldp::Rldp::send_query_ex, local_id_, src_, "payload part", std::move(P), + td::Timestamp::in(15.0), std::move(f), 2 * chunk_size() + 1024); + } + + void add_data(td::BufferSlice data) { + LOG(INFO) << "HttpPayloadReceiver: received answer (size " << data.size() << ")"; + auto F = ton::fetch_tl_object(std::move(data), true); + if (F.is_error()) { + abort_query(F.move_as_error()); + return; + } + auto f = F.move_as_ok(); + LOG(INFO) << "HttpPayloadReceiver: received answer datasize=" << f->data_.size() + << " trailers_cnt=" << f->trailer_.size() << " last=" << f->last_; + if (f->data_.size() != 0) { + payload_->add_chunk(std::move(f->data_)); + } + for (auto &x : f->trailer_) { + ton::http::HttpHeader h{x->name_, x->value_}; + auto S = h.basic_check(); + if (S.is_error()) { + abort_query(S.move_as_error()); + return; + } + payload_->add_trailer(std::move(h)); + } + sent_ = false; + if (f->last_) { + payload_->complete_parse(); + LOG(INFO) << "received HTTP payload"; + stop(); + } else { + if (payload_->ready_bytes() < watermark()) { + request_more_data(); + } + } + } + + void abort_query(td::Status error) { + LOG(INFO) << "failed to receive HTTP payload: " << error; + stop(); + } + + private: + static constexpr size_t watermark() { + return 1 << 15; + } + static constexpr size_t chunk_size() { + return 1 << 17; + } + + std::shared_ptr payload_; + + td::Bits256 id_; + + ton::adnl::AdnlNodeIdShort src_; + ton::adnl::AdnlNodeIdShort local_id_; + td::actor::ActorId adnl_; + td::actor::ActorId rldp_; + + bool sent_ = false; + td::int32 seqno_ = 0; +}; + +class HttpRldpPayloadSender : public td::actor::Actor { + public: + HttpRldpPayloadSender(std::shared_ptr payload, td::Bits256 transfer_id, + ton::adnl::AdnlNodeIdShort local_id, td::actor::ActorId adnl, + td::actor::ActorId rldp) + : payload_(std::move(payload)), id_(transfer_id), local_id_(local_id), adnl_(adnl), rldp_(rldp) { + } + + std::string generate_prefix() const { + std::string x(static_cast(36), '\0'); + auto S = td::MutableSlice{x}; + CHECK(S.size() == 36); + + auto id = ton::ton_api::http_getNextPayloadPart::ID; + S.copy_from(td::Slice(reinterpret_cast(&id), 4)); + S.remove_prefix(4); + S.copy_from(id_.as_slice()); + return x; + } + + void start_up() override { + class AdnlCb : public ton::adnl::Adnl::Callback { + public: + AdnlCb(td::actor::ActorId id) : self_id_(id) { + } + void receive_message(ton::adnl::AdnlNodeIdShort src, ton::adnl::AdnlNodeIdShort dst, + td::BufferSlice data) override { + LOG(INFO) << "http payload sender: dropping message"; + } + void receive_query(ton::adnl::AdnlNodeIdShort src, ton::adnl::AdnlNodeIdShort dst, td::BufferSlice data, + td::Promise promise) override { + td::actor::send_closure(self_id_, &HttpRldpPayloadSender::receive_query, std::move(data), std::move(promise)); + } + + private: + td::actor::ActorId self_id_; + }; + td::actor::send_closure(adnl_, &ton::adnl::Adnl::subscribe, local_id_, generate_prefix(), + std::make_unique(actor_id(this))); + + class Cb : public ton::http::HttpPayload::Callback { + public: + Cb(td::actor::ActorId id) : self_id_(id) { + } + void run(size_t ready_bytes) override { + if (!reached_ && ready_bytes >= watermark_) { + reached_ = true; + td::actor::send_closure(self_id_, &HttpRldpPayloadSender::try_answer_query); + } else if (reached_ && ready_bytes < watermark_) { + reached_ = false; + } + } + void completed() override { + td::actor::send_closure(self_id_, &HttpRldpPayloadSender::try_answer_query); + } + + private: + size_t watermark_ = ton::http::HttpRequest::low_watermark(); + bool reached_ = false; + td::actor::ActorId self_id_; + }; + + payload_->add_callback(std::make_unique(actor_id(this))); + + alarm_timestamp() = td::Timestamp::in(10.0); + } + + void try_answer_query() { + if (!cur_query_promise_) { + return; + } + if (payload_->parse_completed() || payload_->ready_bytes() >= ton::http::HttpRequest::low_watermark()) { + answer_query(); + } + } + + void send_data(ton::tl_object_ptr query, + td::Promise promise) { + CHECK(query->id_ == id_); + if (query->seqno_ != seqno_) { + LOG(INFO) << "seqno mismatch. closing http transfer"; + stop(); + return; + } + + if (cur_query_promise_) { + LOG(INFO) << "duplicate http query. closing http transfer"; + stop(); + return; + } + + cur_query_size_ = query->max_chunk_size_; + if (cur_query_size_ > watermark()) { + cur_query_size_ = watermark(); + } + cur_query_promise_ = std::move(promise); + + LOG(INFO) << "received request. size=" << cur_query_size_ << " parse_completed=" << payload_->parse_completed() + << " ready_bytes=" << payload_->ready_bytes(); + + if (payload_->parse_completed() || payload_->ready_bytes() >= ton::http::HttpRequest::low_watermark()) { + answer_query(); + return; + } + + alarm_timestamp() = td::Timestamp::in(10.0); + } + + void receive_query(td::BufferSlice data, td::Promise promise) { + auto F = ton::fetch_tl_object(std::move(data), true); + if (F.is_error()) { + LOG(INFO) << "failed to parse query: " << F.move_as_error(); + return; + } + send_data(F.move_as_ok(), std::move(promise)); + } + + void alarm() override { + if (cur_query_promise_) { + LOG(INFO) << "timeout on inbound connection. closing http transfer"; + } else { + LOG(INFO) << "timeout on RLDP connection. closing http transfer"; + } + stop(); + } + + void answer_query() { + cur_query_promise_.set_value(ton::serialize_tl_object(payload_->store_tl(cur_query_size_), true)); + if (payload_->written()) { + LOG(INFO) << "sent HTTP payload"; + stop(); + } + seqno_++; + + alarm_timestamp() = td::Timestamp::in(30.0); + } + + void abort_query(td::Status error) { + LOG(INFO) << error; + stop(); + } + + void tear_down() override { + td::actor::send_closure(adnl_, &ton::adnl::Adnl::unsubscribe, local_id_, generate_prefix()); + } + + private: + static constexpr size_t watermark() { + return 1 << 15; + } + + std::shared_ptr payload_; + + td::Bits256 id_; + + bool sent_ = false; + td::int32 seqno_ = 0; + + ton::adnl::AdnlNodeIdShort local_id_; + td::actor::ActorId adnl_; + td::actor::ActorId rldp_; + + size_t cur_query_size_; + td::Promise cur_query_promise_; +}; + +class TcpToRldpRequestSender : public td::actor::Actor { + public: + TcpToRldpRequestSender( + ton::adnl::AdnlNodeIdShort local_id, std::string host, std::unique_ptr request, + std::shared_ptr request_payload, + td::Promise, std::shared_ptr>> promise, + td::actor::ActorId adnl, td::actor::ActorId dht, + td::actor::ActorId rldp) + : local_id_(local_id) + , host_(std::move(host)) + , request_(std::move(request)) + , request_payload_(std::move(request_payload)) + , promise_(std::move(promise)) + , adnl_(adnl) + , dht_(dht) + , rldp_(rldp) { + } + void start_up() override { + auto P = td::PromiseCreator::lambda([SelfId = actor_id(this)](td::Result R) { + if (R.is_error()) { + td::actor::send_closure(SelfId, &TcpToRldpRequestSender::abort_query, R.move_as_error()); + return; + } + auto value = R.move_as_ok(); + if (value.value().size() != 32) { + td::actor::send_closure(SelfId, &TcpToRldpRequestSender::abort_query, td::Status::Error("bad value in dht")); + return; + } + + ton::PublicKeyHash h{value.value().as_slice()}; + td::actor::send_closure(SelfId, &TcpToRldpRequestSender::resolved, ton::adnl::AdnlNodeIdShort{h}); + }); + + ton::PublicKey key = ton::pubkeys::Unenc{"http." + host_}; + ton::dht::DhtKey dht_key{key.compute_short_id(), "http." + host_, 0}; + td::actor::send_closure(dht_, &ton::dht::Dht::get_value, std::move(dht_key), std::move(P)); + } + + void resolved(ton::adnl::AdnlNodeIdShort id) { + dst_ = id; + td::Random::secure_bytes(id_.as_slice()); + + auto P = td::PromiseCreator::lambda([SelfId = actor_id(this)](td::Result R) { + if (R.is_error()) { + td::actor::send_closure(SelfId, &TcpToRldpRequestSender::abort_query, R.move_as_error()); + } else { + td::actor::send_closure(SelfId, &TcpToRldpRequestSender::got_result, R.move_as_ok()); + } + }); + + td::actor::create_actor("HttpPayloadSender", request_payload_, id_, local_id_, adnl_, rldp_) + .release(); + + auto f = ton::serialize_tl_object(request_->store_tl(id_), true); + td::actor::send_closure(rldp_, &ton::rldp::Rldp::send_query_ex, local_id_, dst_, "http request over rldp", + std::move(P), td::Timestamp::in(30.0), std::move(f), 16 << 10); + } + + void got_result(td::BufferSlice data) { + auto F = ton::fetch_tl_object(std::move(data), true); + if (F.is_error()) { + abort_query(F.move_as_error()); + return; + } + auto f = F.move_as_ok(); + auto R = ton::http::HttpResponse::create(f->http_version_, f->status_code_, f->reason_, false, true); + if (R.is_error()) { + abort_query(R.move_as_error()); + return; + } + response_ = R.move_as_ok(); + for (auto &e : f->headers_) { + ton::http::HttpHeader h{e->name_, e->value_}; + auto S = h.basic_check(); + if (S.is_error()) { + abort_query(S.move_as_error()); + return; + } + response_->add_header(std::move(h)); + } + auto S = response_->complete_parse_header(); + if (S.is_error()) { + abort_query(S.move_as_error()); + return; + } + + response_payload_ = response_->create_empty_payload().move_as_ok(); + + auto P = td::PromiseCreator::lambda([SelfId = actor_id(this)](td::Result R) { + if (R.is_error()) { + td::actor::send_closure(SelfId, &TcpToRldpRequestSender::abort_query, R.move_as_error()); + } else { + td::actor::send_closure(SelfId, &TcpToRldpRequestSender::finished_payload_transfer); + } + }); + td::actor::create_actor("HttpPayloadReceiver", response_payload_, id_, dst_, local_id_, + adnl_, rldp_) + .release(); + + promise_.set_value(std::make_pair(std::move(response_), std::move(response_payload_))); + stop(); + }; + + void finished_payload_transfer() { + stop(); + } + + void abort_query(td::Status error) { + LOG(INFO) << "aborting http over rldp query: " << error; + stop(); + } + + protected: + td::Bits256 id_; + + ton::adnl::AdnlNodeIdShort local_id_; + std::string host_; + ton::adnl::AdnlNodeIdShort dst_; + + std::unique_ptr request_; + std::shared_ptr request_payload_; + + td::Promise, std::shared_ptr>> promise_; + + td::actor::ActorId adnl_; + td::actor::ActorId dht_; + td::actor::ActorId rldp_; + + std::unique_ptr response_; + std::shared_ptr response_payload_; +}; + +class RldpToTcpRequestSender : public td::actor::Actor { + public: + RldpToTcpRequestSender(td::Bits256 id, ton::adnl::AdnlNodeIdShort local_id, ton::adnl::AdnlNodeIdShort dst, + std::unique_ptr request, + std::shared_ptr request_payload, td::Promise promise, + td::actor::ActorId adnl, td::actor::ActorId rldp, + td::actor::ActorId remote) + : id_(id) + , local_id_(local_id) + , dst_(dst) + , request_(std::move(request)) + , request_payload_(std::move(request_payload)) + , promise_(std::move(promise)) + , adnl_(adnl) + , rldp_(rldp) + , remote_(std::move(remote)) { + } + void start_up() override { + auto P = td::PromiseCreator::lambda( + [SelfId = actor_id(this)]( + td::Result, std::shared_ptr>> + R) { + if (R.is_error()) { + td::actor::send_closure(SelfId, &RldpToTcpRequestSender::abort_query, R.move_as_error()); + } else { + td::actor::send_closure(SelfId, &RldpToTcpRequestSender::got_result, R.move_as_ok()); + } + }); + td::actor::send_closure(remote_, &HttpRemote::receive_request, std::move(request_), request_payload_, std::move(P)); + td::actor::create_actor("HttpPayloadReceiver(R)", std::move(request_payload_), id_, dst_, + local_id_, adnl_, rldp_) + .release(); + } + + void got_result(std::pair, std::shared_ptr> R) { + td::actor::create_actor("HttpPayloadSender(R)", std::move(R.second), id_, local_id_, adnl_, + rldp_) + .release(); + auto f = ton::serialize_tl_object(R.first->store_tl(), true); + promise_.set_value(std::move(f)); + stop(); + } + + void abort_query(td::Status error) { + LOG(INFO) << "aborting http over rldp query: " << error; + promise_.set_error(std::move(error)); + stop(); + } + + protected: + td::Bits256 id_; + + ton::adnl::AdnlNodeIdShort local_id_; + ton::adnl::AdnlNodeIdShort dst_; + + std::unique_ptr request_; + std::shared_ptr request_payload_; + + td::Promise promise_; + + td::actor::ActorId adnl_; + td::actor::ActorId rldp_; + + td::actor::ActorId remote_; +}; + +class RldpHttpProxy : public td::actor::Actor { + public: + RldpHttpProxy() { + } + + void set_port(td::uint16 port) { + if (port_) { + LOG(ERROR) << "duplicate listening port"; + std::_Exit(2); + } + port_ = port; + } + + void set_global_config(std::string path) { + global_config_ = std::move(path); + } + + void set_addr(td::IPAddress addr) { + addr_ = addr; + } + + void set_client_port(td::uint16 port) { + is_client_ = true; + client_port_ = port; + } + + void set_local_host(std::string name, td::IPAddress remote) { + local_hosts_.emplace_back(std::move(name), std::move(remote)); + } + + td::Status load_global_config() { + TRY_RESULT_PREFIX(conf_data, td::read_file(global_config_), "failed to read: "); + TRY_RESULT_PREFIX(conf_json, td::json_decode(conf_data.as_slice()), "failed to parse json: "); + + ton::ton_api::config_global conf; + TRY_STATUS_PREFIX(ton::ton_api::from_json(conf, conf_json.get_object()), "json does not fit TL scheme: "); + + if (!conf.dht_) { + return td::Status::Error(ton::ErrorCode::error, "does not contain [dht] section"); + } + + TRY_RESULT_PREFIX(dht, ton::dht::Dht::create_global_config(std::move(conf.dht_)), "bad [dht] section: "); + dht_config_ = std::move(dht); + + return td::Status::OK(); + } + + void store_dht() { + for (auto &serv : local_hosts_) { + ton::PublicKey key = ton::pubkeys::Unenc{"http." + serv.first}; + ton::dht::DhtKey dht_key{key.compute_short_id(), "http." + serv.first, 0}; + auto dht_update_rule = ton::dht::DhtUpdateRuleAnybody::create().move_as_ok(); + ton::dht::DhtKeyDescription dht_key_description{std::move(dht_key), key, std::move(dht_update_rule), + td::BufferSlice()}; + dht_key_description.check().ensure(); + + auto ttl = static_cast(td::Clocks::system() + 3600); + ton::dht::DhtValue dht_value{std::move(dht_key_description), td::BufferSlice{local_id_.as_slice()}, ttl, + td::BufferSlice("")}; + + td::actor::send_closure(dht_, &ton::dht::Dht::set_value, std::move(dht_value), [](td::Unit) {}); + } + alarm_timestamp() = td::Timestamp::in(60.0); + } + + void alarm() override { + store_dht(); + } + + void run() { + if (is_client_ && local_hosts_.size() > 0) { + LOG(ERROR) << "client-only node cannot be server"; + std::_Exit(2); + } + if (is_client_ && client_port_ == 0) { + LOG(ERROR) << "client-only expects client port"; + std::_Exit(2); + } + { + auto S = load_global_config(); + if (S.is_error()) { + LOG(INFO) << S; + std::_Exit(2); + } + } + keyring_ = ton::keyring::Keyring::create(""); + { + adnl_network_manager_ = + ton::adnl::AdnlNetworkManager::create(is_client_ ? client_port_ : static_cast(addr_.get_port())); + adnl_ = ton::adnl::Adnl::create("", keyring_.get()); + td::actor::send_closure(adnl_, &ton::adnl::Adnl::register_network_manager, adnl_network_manager_.get()); + if (is_client_) { + td::IPAddress addr; + addr.init_host_port("127.0.0.1", client_port_).ensure(); + td::actor::send_closure(adnl_network_manager_, &ton::adnl::AdnlNetworkManager::add_self_addr, addr, 0); + } else { + td::actor::send_closure(adnl_network_manager_, &ton::adnl::AdnlNetworkManager::add_self_addr, addr_, 0); + } + + ton::adnl::AdnlAddressList addr_list; + if (!is_client_) { + ton::adnl::AdnlAddress x = ton::adnl::AdnlAddressImpl::create( + ton::create_tl_object(addr_.get_ipv4(), addr_.get_port())); + addr_list.add_addr(std::move(x)); + } + addr_list.set_version(static_cast(td::Clocks::system())); + addr_list.set_reinit_date(ton::adnl::Adnl::adnl_start_time()); + { + auto pk = ton::PrivateKey{ton::privkeys::Ed25519::random()}; + auto pub = pk.compute_public_key(); + td::actor::send_closure(keyring_, &ton::keyring::Keyring::add_key, std::move(pk), true, [](td::Unit) {}); + local_id_ = ton::adnl::AdnlNodeIdShort{pub.compute_short_id()}; + td::actor::send_closure(adnl_, &ton::adnl::Adnl::add_id, ton::adnl::AdnlNodeIdFull{pub}, addr_list); + } + { + auto pk = ton::PrivateKey{ton::privkeys::Ed25519::random()}; + auto pub = pk.compute_public_key(); + td::actor::send_closure(keyring_, &ton::keyring::Keyring::add_key, std::move(pk), true, [](td::Unit) {}); + dht_id_ = ton::adnl::AdnlNodeIdShort{pub.compute_short_id()}; + td::actor::send_closure(adnl_, &ton::adnl::Adnl::add_id, ton::adnl::AdnlNodeIdFull{pub}, addr_list); + } + } + { + if (is_client_) { + auto D = ton::dht::Dht::create_client(dht_id_, "", dht_config_, keyring_.get(), adnl_.get()); + D.ensure(); + dht_ = D.move_as_ok(); + } else { + auto D = ton::dht::Dht::create(dht_id_, "", dht_config_, keyring_.get(), adnl_.get()); + D.ensure(); + dht_ = D.move_as_ok(); + } + td::actor::send_closure(adnl_, &ton::adnl::Adnl::register_dht_node, dht_.get()); + } + if (port_) { + class Cb : public ton::http::HttpServer::Callback { + public: + Cb(td::actor::ActorId proxy) : proxy_(proxy) { + } + void receive_request( + std::unique_ptr request, std::shared_ptr payload, + td::Promise, std::shared_ptr>> + promise) override { + td::actor::send_closure(proxy_, &RldpHttpProxy::receive_http_request, std::move(request), std::move(payload), + std::move(promise)); + } + + private: + td::actor::ActorId proxy_; + }; + + server_ = ton::http::HttpServer::create(port_, std::make_shared(actor_id(this))); + } + + class AdnlCb : public ton::adnl::Adnl::Callback { + public: + AdnlCb(td::actor::ActorId id) : self_id_(id) { + } + void receive_message(ton::adnl::AdnlNodeIdShort src, ton::adnl::AdnlNodeIdShort dst, + td::BufferSlice data) override { + } + void receive_query(ton::adnl::AdnlNodeIdShort src, ton::adnl::AdnlNodeIdShort dst, td::BufferSlice data, + td::Promise promise) override { + td::actor::send_closure(self_id_, &RldpHttpProxy::receive_rldp_request, src, std::move(data), + std::move(promise)); + } + + private: + td::actor::ActorId self_id_; + }; + td::actor::send_closure(adnl_, &ton::adnl::Adnl::subscribe, local_id_, + ton::adnl::Adnl::int_to_bytestring(ton::ton_api::http_request::ID), + std::make_unique(actor_id(this))); + for (auto &serv : local_hosts_) { + servers_.emplace(serv.first, td::actor::create_actor("remote", serv.second)); + } + + rldp_ = ton::rldp::Rldp::create(adnl_.get()); + td::actor::send_closure(rldp_, &ton::rldp::Rldp::add_id, local_id_); + + store_dht(); + } + + void receive_http_request( + std::unique_ptr request, std::shared_ptr payload, + td::Promise, std::shared_ptr>> + promise) { + auto host = request->host(); + if (host.size() == 0) { + host = request->url(); + if (host.size() >= 7 && host.substr(0, 7) == "http://") { + host = host.substr(7); + } else if (host.size() >= 8 && host.substr(0, 8) == "https://") { + host = host.substr(8); + } + auto p = host.find('/'); + if (p != std::string::npos) { + host = host.substr(0, p); + } + } else { + if (host.size() >= 7 && host.substr(0, 7) == "http://") { + host = host.substr(7); + } else if (host.size() >= 8 && host.substr(0, 8) == "https://") { + host = host.substr(8); + } + auto p = host.find('/'); + if (p != std::string::npos) { + host = host.substr(0, p); + } + } + { + auto p = host.find(':'); + if (p != std::string::npos) { + host = host.substr(0, p); + } + } + std::transform(host.begin(), host.end(), host.begin(), [](unsigned char c) { return std::tolower(c); }); + if (host.size() < 5 || host.substr(host.size() - 4) != ".ton") { + promise.set_error(td::Status::Error(ton::ErrorCode::error, "bad server name")); + return; + } + + td::actor::create_actor("outboundreq", local_id_, host, std::move(request), + std::move(payload), std::move(promise), adnl_.get(), dht_.get(), + rldp_.get()) + .release(); + } + + void receive_rldp_request(ton::adnl::AdnlNodeIdShort src, td::BufferSlice data, + td::Promise promise) { + LOG(INFO) << "got HTTP request over rldp from " << src; + TRY_RESULT_PROMISE(promise, f, ton::fetch_tl_object(std::move(data), true)); + TRY_RESULT_PROMISE(promise, request, ton::http::HttpRequest::create(f->method_, f->url_, f->http_version_)); + for (auto &x : f->headers_) { + ton::http::HttpHeader h{x->name_, x->value_}; + TRY_STATUS_PROMISE(promise, h.basic_check()); + request->add_header(std::move(h)); + } + TRY_STATUS_PROMISE(promise, request->complete_parse_header()); + auto host = request->host(); + if (host.size() == 0) { + host = request->url(); + if (host.size() >= 7 && host.substr(0, 7) == "http://") { + host = host.substr(7); + } else if (host.size() >= 8 && host.substr(0, 8) == "https://") { + host = host.substr(8); + } + auto p = host.find('/'); + if (p != std::string::npos) { + host = host.substr(0, p); + } + } else { + if (host.size() >= 7 && host.substr(0, 7) == "http://") { + host = host.substr(7); + } else if (host.size() >= 8 && host.substr(0, 8) == "https://") { + host = host.substr(8); + } + auto p = host.find('/'); + if (p != std::string::npos) { + host = host.substr(0, p); + } + } + { + auto p = host.find(':'); + if (p != std::string::npos) { + host = host.substr(0, p); + } + } + std::transform(host.begin(), host.end(), host.begin(), [](unsigned char c) { return std::tolower(c); }); + if (host.size() < 5 || host.substr(host.size() - 4) != ".ton") { + promise.set_error(td::Status::Error(ton::ErrorCode::error, "bad server name")); + return; + } + + auto it = servers_.find(host); + if (it == servers_.end()) { + promise.set_error(td::Status::Error(ton::ErrorCode::error, "unknown server name")); + return; + } + + TRY_RESULT_PROMISE(promise, payload, request->create_empty_payload()); + + LOG(INFO) << "starting HTTP over RLDP request"; + td::actor::create_actor("inboundreq", f->id_, local_id_, src, std::move(request), + std::move(payload), std::move(promise), adnl_.get(), rldp_.get(), + it->second.get()) + .release(); + } + + private: + td::uint16 port_; + td::IPAddress addr_; + std::string global_config_; + std::vector> local_hosts_; + + bool is_client_{false}; + td::uint16 client_port_{0}; + + ton::adnl::AdnlNodeIdShort local_id_; + ton::adnl::AdnlNodeIdShort dht_id_; + + td::actor::ActorOwn server_; + std::map dns_; + std::map> servers_; + + td::actor::ActorOwn keyring_; + td::actor::ActorOwn adnl_network_manager_; + td::actor::ActorOwn adnl_; + td::actor::ActorOwn dht_; + td::actor::ActorOwn rldp_; + + std::shared_ptr dht_config_; +}; + +int main(int argc, char *argv[]) { + SET_VERBOSITY_LEVEL(verbosity_WARNING); + + td::set_default_failure_signal_handler().ensure(); + + td::actor::ActorOwn x; + td::unique_ptr logger_; + SCOPE_EXIT { + td::log_interface = td::default_log_interface; + }; + + td::OptionsParser p; + p.set_description( + "A simple rldp-to-http and http-to-rldp proxy for running and accessing ton sites\n" + "Example:\n\trldp-http-proxy -p 8080 -c 3333 -C ton-global.config.json\tRuns a local HTTP->RLDP proxy that " + "accepts HTTP proxy queries at localhost:8080\n" + "Example:\n\trldp-http-proxy -a :3333 -L example.ton -C ton-global.config.json\tRuns a local " + "RLDP->HTTP proxy on UDP port :3333 that forwards all queries for http://example.ton to HTTP server " + "at localhost:80\n"); + p.add_option('v', "verbosity", "set verbosity level", [&](td::Slice arg) { + int v = VERBOSITY_NAME(FATAL) + (td::to_integer(arg)); + SET_VERBOSITY_LEVEL(v); + return td::Status::OK(); + }); + p.add_option('h', "help", "prints a help message", [&]() { + char b[10240]; + td::StringBuilder sb(td::MutableSlice{b, 10000}); + sb << p; + std::cout << sb.as_cslice().c_str(); + std::exit(2); + return td::Status::OK(); + }); + p.add_option('p', "port", "sets http listening port", [&](td::Slice arg) -> td::Status { + TRY_RESULT(port, td::to_integer_safe(arg)); + td::actor::send_closure(x, &RldpHttpProxy::set_port, port); + return td::Status::OK(); + }); + p.add_option('a', "address", "local : to use for adnl queries", [&](td::Slice arg) -> td::Status { + td::IPAddress addr; + TRY_STATUS(addr.init_host_port(arg.str())); + td::actor::send_closure(x, &RldpHttpProxy::set_addr, addr); + return td::Status::OK(); + }); + p.add_option('c', "client-port", "local to use for client adnl queries", [&](td::Slice arg) -> td::Status { + TRY_RESULT(port, td::to_integer_safe(arg)); + td::actor::send_closure(x, &RldpHttpProxy::set_client_port, port); + return td::Status::OK(); + }); + p.add_option('C', "global-config", "global TON configuration file", [&](td::Slice arg) -> td::Status { + td::actor::send_closure(x, &RldpHttpProxy::set_global_config, arg.str()); + return td::Status::OK(); + }); + p.add_option('L', "local", "http hostname that will be proxied to http server at localhost:80", + [&](td::Slice arg) -> td::Status { + td::IPAddress addr; + TRY_STATUS(addr.init_ipv4_port("127.0.0.1", 80)); + td::actor::send_closure(x, &RldpHttpProxy::set_local_host, arg.str(), addr); + return td::Status::OK(); + }); + p.add_option( + 'R', "remote", + "@:, indicates a http hostname that will be proxied to remote http server at :", + [&](td::Slice arg) -> td::Status { + auto ch = arg.find('@'); + if (ch == td::Slice::npos) { + return td::Status::Error("bad format for --remote"); + } + td::IPAddress addr; + TRY_STATUS(addr.init_host_port(arg.substr(ch + 1).str())); + td::actor::send_closure(x, &RldpHttpProxy::set_local_host, arg.substr(0, ch).str(), addr); + return td::Status::OK(); + }); + p.add_option('d', "daemonize", "set SIGHUP", [&]() { + td::set_signal_handler(td::SignalType::HangUp, [](int sig) { +#if TD_DARWIN || TD_LINUX + close(0); + setsid(); +#endif + }).ensure(); + return td::Status::OK(); + }); +#if TD_DARWIN || TD_LINUX + p.add_option('l', "logname", "log to file", [&](td::Slice fname) { + logger_ = td::FileLog::create(fname.str()).move_as_ok(); + td::log_interface = logger_.get(); + return td::Status::OK(); + }); +#endif + + td::actor::Scheduler scheduler({7}); + + scheduler.run_in_context([&] { x = td::actor::create_actor("proxymain"); }); + + scheduler.run_in_context([&] { p.run(argc, argv).ensure(); }); + scheduler.run_in_context([&] { td::actor::send_closure(x, &RldpHttpProxy::run); }); + while (scheduler.run(1)) { + } + + return 0; +} diff --git a/tdutils/td/utils/Span.h b/tdutils/td/utils/Span.h index 8c8ee110..a4a8edff 100644 --- a/tdutils/td/utils/Span.h +++ b/tdutils/td/utils/Span.h @@ -14,7 +14,7 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -95,6 +95,12 @@ class SpanImpl { InnerT *end() const { return data_ + size_; } + auto rbegin() const { + return std::reverse_iterator(end()); + } + auto rend() const { + return std::reverse_iterator(begin()); + } size_t size() const { return size_; } diff --git a/tdutils/td/utils/Status.h b/tdutils/td/utils/Status.h index 6ac92100..8016099f 100644 --- a/tdutils/td/utils/Status.h +++ b/tdutils/td/utils/Status.h @@ -14,7 +14,7 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -64,6 +64,9 @@ #define TRY_RESULT_ASSIGN(name, result) TRY_RESULT_IMPL(TD_CONCAT(TD_CONCAT(r_, name), __LINE__), name, result) +#define TRY_RESULT_PROMISE_ASSIGN(promise_name, name, result) \ + TRY_RESULT_PROMISE_IMPL(promise_name, TD_CONCAT(TD_CONCAT(r_, name), __LINE__), name, result) + #define TRY_RESULT_PREFIX(name, result, prefix) \ TRY_RESULT_PREFIX_IMPL(TD_CONCAT(TD_CONCAT(r_, name), __LINE__), auto name, result, prefix) diff --git a/tdutils/td/utils/Variant.h b/tdutils/td/utils/Variant.h index 30777a16..708fc1cb 100644 --- a/tdutils/td/utils/Variant.h +++ b/tdutils/td/utils/Variant.h @@ -14,7 +14,7 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -253,6 +253,10 @@ class Variant { return offset_; } + bool empty() const { + return offset_ == npos; + } + private: union { int64 align_; diff --git a/tdutils/td/utils/optional.h b/tdutils/td/utils/optional.h index 7da14b62..3eb2d518 100644 --- a/tdutils/td/utils/optional.h +++ b/tdutils/td/utils/optional.h @@ -14,7 +14,7 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -74,6 +74,13 @@ class optional { return res; } + td::optional copy() const { + if (*this) { + return value(); + } + return {}; + } + template void emplace(ArgsT &&... args) { impl_.emplace(std::forward(args)...); diff --git a/test/regression-tests.ans b/test/regression-tests.ans index fd58341a..2224b9bb 100644 --- a/test/regression-tests.ans +++ b/test/regression-tests.ans @@ -7,7 +7,7 @@ Test_Fift_bug_newlize_default e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca4 Test_Fift_bug_ufits_default 51bf5a9f1ed7633a193f6fdd17a7a3af8e032dfe72a9669c85e8639aa8a7c195 Test_Fift_contfrac_default 09ebce5c91bcb70696c6fb6981d82dc3b9e3444dab608a7a1b044c0ddd778a96 Test_Fift_test_default 4e44b3382963ec89f7b5c8f2ebd85da3bc8aebad5b49f5b11b14075061477b4d -Test_Fift_test_dict_default 1879f03e5fb25dcdd33f40120a6d352c246481895c9f8e45975bf3e101d9ee70 +Test_Fift_test_dict_default 85b2ed797bab084f391179ff9c8c83f0ed9b5f4c53180c72e37379fdf95356b8 Test_Fift_test_fixed_default 278a19d56b773102caf5c1fe2997ea6c8d0d9e720eff8503feede6398a197eec Test_Fift_test_sort2_default 9b57d47e6a10e7d1bbb565db35400debf2f963031f434742a702ec76555a5d3a Test_Fift_test_sort_default 9b57d47e6a10e7d1bbb565db35400debf2f963031f434742a702ec76555a5d3a diff --git a/test/test-http.cpp b/test/test-http.cpp new file mode 100644 index 00000000..9738dfe8 --- /dev/null +++ b/test/test-http.cpp @@ -0,0 +1,308 @@ +/* + This file is part of TON Blockchain source code. + + TON Blockchain is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + TON Blockchain is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with TON Blockchain. If not, see . + + In addition, as a special exception, the copyright holders give permission + to link the code of portions of this program with the OpenSSL library. + You must obey the GNU General Public License in all respects for all + of the code used other than OpenSSL. If you modify file(s) with this + exception, you may extend this exception to your version of the file(s), + but you are not obligated to do so. If you do not wish to do so, delete this + exception statement from your version. If you delete this exception statement + from all source files in the program, then also delete it here. + + Copyright 2017-2020 Telegram Systems LLP +*/ +#include "td/utils/OptionsParser.h" +#include "td/utils/Time.h" +#include "td/utils/filesystem.h" +#include "td/utils/format.h" +#include "td/utils/port/path.h" +#include "td/utils/Random.h" +#include "td/utils/port/signals.h" +#include "td/utils/port/FileFd.h" +#include "td/utils/overloaded.h" +#include "common/errorlog.h" +#include "http/http.h" + +#if TD_DARWIN || TD_LINUX +#include +#endif +#include +#include + +#include + +void dump_reader(td::ChainBufferReader &reader) { + auto b = reader.move_as_buffer_slice(); + LOG(INFO) << b.as_slice(); +} + +int main(int argc, char *argv[]) { + SET_VERBOSITY_LEVEL(verbosity_INFO); + td::set_default_failure_signal_handler().ensure(); + + { + const auto request = + "GET /pub/WWW/TheProject.html HTTP/1.1\r\n" + "Host: www.example.org:8080\r\n" + "xopt: opt12345\r\n" + "\r\n"; + td::ChainBufferWriter w; + w.init(0); + w.append(td::Slice(request, std::strlen(request))); + auto r = w.extract_reader(); + + bool exit_loop = false; + std::string cur_line = ""; + auto R = ton::http::HttpRequest::parse(nullptr, cur_line, exit_loop, r); + R.ensure(); + auto req = R.move_as_ok(); + + td::ChainBufferWriter wo; + wo.init(0); + CHECK(req); + CHECK(!exit_loop); + req->store_http(wo); + auto ro = wo.extract_reader(); + dump_reader(ro); + + CHECK(req->method() == "GET"); + CHECK(req->url() == "/pub/WWW/TheProject.html"); + CHECK(req->proto_version() == "HTTP/1.1"); + CHECK(req->host() == "www.example.org:8080"); + CHECK(req->check_parse_header_completed()); + CHECK(!req->need_payload()); + } + + { + const auto request = + "GET /pub/WWW/TheProject.html HTTP/1.1\r\n" + "Host: www.example.org:8080\r\n" + "xopt: opt12345\r\n" + "Content-Length: opt12345\r\n" + "\r\n"; + td::ChainBufferWriter w; + w.init(0); + w.append(td::Slice(request, std::strlen(request))); + auto r = w.extract_reader(); + + bool exit_loop = false; + std::string cur_line = ""; + auto R = ton::http::HttpRequest::parse(nullptr, cur_line, exit_loop, r); + R.ensure_error(); + } + + { + const auto request = + "GET /pub/WWW/TheProject.html HTTP/1.1\r\n" + "Host: www.example.org:8080\r\n" + "Content-Length: 123456789\r\n" + "\r\n"; + td::ChainBufferWriter w; + w.init(0); + w.append(td::Slice(request, std::strlen(request))); + auto r = w.extract_reader(); + + bool exit_loop = false; + std::string cur_line = ""; + auto R = ton::http::HttpRequest::parse(nullptr, cur_line, exit_loop, r); + R.ensure_error(); + } + + { + const auto request = + "GET /pub/WWW/TheProject.html HTTP/1.1\r\n" + "Host: www.example.org:8080\r\n" + "Content-Length: 16\r\n" + "\r\n"; + td::ChainBufferWriter w; + w.init(0); + w.append(td::Slice(request, std::strlen(request))); + auto r = w.extract_reader(); + + bool exit_loop = false; + std::string cur_line = ""; + auto R = ton::http::HttpRequest::parse(nullptr, cur_line, exit_loop, r); + R.ensure(); + auto req = R.move_as_ok(); + + td::ChainBufferWriter wo; + wo.init(0); + CHECK(req); + CHECK(!exit_loop); + req->store_http(wo); + auto ro = wo.extract_reader(); + dump_reader(ro); + + CHECK(req->method() == "GET"); + CHECK(req->url() == "/pub/WWW/TheProject.html"); + CHECK(req->proto_version() == "HTTP/1.1"); + CHECK(req->host() == "www.example.org:8080"); + CHECK(req->check_parse_header_completed()); + CHECK(req->need_payload()); + CHECK(req->keep_alive()); + + auto payload = req->create_empty_payload().move_as_ok(); + CHECK(payload); + CHECK(!payload->parse_completed()); + payload->parse(r).ensure(); + CHECK(!payload->parse_completed()); + w.append("1234567890abcdef"); + r.advance_end(16); + payload->parse(r).ensure(); + CHECK(payload->parse_completed()); + + wo.init(0); + payload->store_http(wo, 1 << 20, payload->payload_type()); + ro = wo.extract_reader(); + dump_reader(ro); + } + + { + const auto request = + "GET /pub/WWW/TheProject.html HTTP/1.1\r\n" + "Host: www.example.org:8080\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n"; + td::ChainBufferWriter w; + w.init(0); + w.append(td::Slice(request, std::strlen(request))); + auto r = w.extract_reader(); + + bool exit_loop = false; + std::string cur_line = ""; + auto R = ton::http::HttpRequest::parse(nullptr, cur_line, exit_loop, r); + R.ensure(); + auto req = R.move_as_ok(); + + td::ChainBufferWriter wo; + wo.init(0); + CHECK(req); + CHECK(!exit_loop); + req->store_http(wo); + auto ro = wo.extract_reader(); + dump_reader(ro); + + CHECK(req->method() == "GET"); + CHECK(req->url() == "/pub/WWW/TheProject.html"); + CHECK(req->proto_version() == "HTTP/1.1"); + CHECK(req->host() == "www.example.org:8080"); + CHECK(req->check_parse_header_completed()); + CHECK(req->need_payload()); + CHECK(req->keep_alive()); + + auto payload = req->create_empty_payload().move_as_ok(); + CHECK(payload); + CHECK(!payload->parse_completed()); + payload->parse(r).ensure(); + CHECK(!payload->parse_completed()); + w.append("10\r\n1234567890abcdef\r\n"); + r.advance_end(22); + payload->parse(r).ensure(); + CHECK(!payload->parse_completed()); + w.append("10\r\n1234567890ABCDEF\r\n"); + r.advance_end(22); + payload->parse(r).ensure(); + CHECK(!payload->parse_completed()); + w.append("0\r\n"); + r.advance_end(5); + payload->parse(r).ensure(); + CHECK(!payload->parse_completed()); + + w.append("X-tail: value\r\n\r\n"); + r.advance_end(17); + payload->parse(r).ensure(); + CHECK(payload->parse_completed()); + + wo.init(0); + payload->store_http(wo, 1 << 20, payload->payload_type()); + ro = wo.extract_reader(); + dump_reader(ro); + } + + { + const auto request = + "GET /pub/WWW/TheProject.html HTTP/1.1\r\n" + "Host: www.example.org:8080\r\n" + "Transfer-Encoding: chunked\r\n" + "\r\n" + "10\r\n" + "0123456789abcdef\r\n" + "10\r\n" + "0123456789ABCDEF\r\n" + "0\r\n" + "x-1: a\r\n" + "x-2: b\r\n" + "\r\n"; + td::ChainBufferWriter w; + w.init(0); + auto r = w.extract_reader(); + w.append(td::Slice(request, std::strlen(request))); + + std::unique_ptr req; + std::shared_ptr payload; + + auto l = strlen(request); + std::string cur_line; + bool exit_loop; + for (size_t i = 0; i < l; i++) { + r.advance_end(1); + if (!req || !req->check_parse_header_completed()) { + req = ton::http::HttpRequest::parse(std::move(req), cur_line, exit_loop, r).move_as_ok(); + } else { + if (!payload) { + payload = req->create_empty_payload().move_as_ok(); + } + CHECK(!payload->parse_completed()); + payload->parse(r).ensure(); + } + } + CHECK(payload->parse_completed()); + td::ChainBufferWriter wo; + wo.init(0); + req->store_http(wo); + payload->store_http(wo, 1 << 20, payload->payload_type()); + auto ro = wo.extract_reader(); + dump_reader(ro); + } + + { + const auto response = + "HTTP/1.1 200 Ok\r\n" + "\r\n"; + td::ChainBufferWriter w; + w.init(0); + w.append(td::Slice(response, std::strlen(response))); + auto r = w.extract_reader(); + + bool exit_loop = false; + std::string cur_line = ""; + auto R = ton::http::HttpResponse::parse(nullptr, cur_line, false, false, exit_loop, r); + R.ensure(); + auto res = R.move_as_ok(); + CHECK(res); + CHECK(res->check_parse_header_completed()); + + td::ChainBufferWriter wo; + wo.init(0); + res->store_http(wo); + auto ro = wo.extract_reader(); + dump_reader(ro); + } + + std::_Exit(0); + return 0; +} diff --git a/tl/generate/scheme/tonlib_api.tl b/tl/generate/scheme/tonlib_api.tl index da23bcdc..a7385eb3 100644 --- a/tl/generate/scheme/tonlib_api.tl +++ b/tl/generate/scheme/tonlib_api.tl @@ -8,6 +8,9 @@ bytes = Bytes; secureString = SecureString; secureBytes = SecureBytes; +object ? = Object; +function ? = Function; + boolFalse = Bool; boolTrue = Bool; @@ -22,7 +25,7 @@ keyStoreTypeInMemory = KeyStoreType; config config:string blockchain_name:string use_callbacks_for_network:Bool ignore_cache:Bool = Config; options config:config keystore_type:KeyStoreType = Options; -options.configInfo default_wallet_id:int53 = options.ConfigInfo; +options.configInfo default_wallet_id:int64 = options.ConfigInfo; options.info config_info:options.configInfo = options.Info; key public_key:string secret:secureBytes = Key; @@ -35,48 +38,83 @@ exportedEncryptedKey data:secureBytes = ExportedEncryptedKey; bip39Hints words:vector = Bip39Hints; accountAddress account_address:string = AccountAddress; +accountRevisionList revisions:vector = AccountRevisionList; unpackedAccountAddress workchain_id:int32 bounceable:Bool testnet:Bool addr:bytes = UnpackedAccountAddress; internal.transactionId lt:int64 hash:bytes = internal.TransactionId; -raw.initialAccountState code:bytes data:bytes = raw.InitialAccountState; -raw.accountState balance:int64 code:bytes data:bytes last_transaction_id:internal.transactionId frozen_hash:bytes sync_utime:int53 = raw.AccountState; +ton.blockId workchain:int32 shard:int64 seqno:int32 = internal.BlockId; +ton.blockIdExt workchain:int32 shard:int64 seqno:int32 root_hash:bytes file_hash:bytes = ton.BlockIdExt; + +raw.fullAccountState balance:int64 code:bytes data:bytes last_transaction_id:internal.transactionId block_id:ton.blockIdExt frozen_hash:bytes sync_utime:int53 = raw.FullAccountState; raw.message source:string destination:string value:int64 fwd_fee:int64 ihr_fee:int64 created_lt:int64 body_hash:bytes message:bytes = raw.Message; raw.transaction utime:int53 data:bytes transaction_id:internal.transactionId fee:int64 storage_fee:int64 other_fee:int64 in_msg:raw.message out_msgs:vector = raw.Transaction; raw.transactions transactions:vector previous_transaction_id:internal.transactionId = raw.Transactions; -testWallet.initialAccountState public_key:string = testWallet.InitialAccountState; -testWallet.accountState balance:int64 seqno:int32 last_transaction_id:internal.transactionId sync_utime:int53 = testWallet.AccountState; +raw.initialAccountState code:bytes data:bytes = InitialAccountState; +testGiver.initialAccountState = InitialAccountState; +testWallet.initialAccountState public_key:string = InitialAccountState; +wallet.initialAccountState public_key:string = InitialAccountState; +wallet.v3.initialAccountState public_key:string wallet_id:int64 = InitialAccountState; +wallet.highload.v1.initialAccountState public_key:string wallet_id:int64 = InitialAccountState; +wallet.highload.v2.initialAccountState public_key:string wallet_id:int64 = InitialAccountState; +dns.initialAccountState public_key:string wallet_id:int64 = InitialAccountState; -wallet.initialAccountState public_key:string = wallet.InitialAccountState; -wallet.accountState balance:int64 seqno:int32 last_transaction_id:internal.transactionId sync_utime:int53 = wallet.AccountState; +raw.accountState code:bytes data:bytes frozen_hash:bytes = AccountState; +testWallet.accountState seqno:int32 = AccountState; +wallet.accountState seqno:int32 = AccountState; +wallet.v3.accountState wallet_id:int64 seqno:int32 = AccountState; +wallet.highload.v1.accountState wallet_id:int64 seqno:int32 = AccountState; +wallet.highload.v2.accountState wallet_id:int64 = AccountState; +testGiver.accountState seqno:int32 = AccountState; +dns.accountState wallet_id:int64 = AccountState; +uninited.accountState frozen_hash:bytes = AccountState; -wallet.v3.initialAccountState public_key:string wallet_id:int53 = wallet.v3.InitialAccountState; -wallet.v3.accountState balance:int64 wallet_id:int53 seqno:int32 last_transaction_id:internal.transactionId sync_utime:int53 = wallet.v3.AccountState; - -testGiver.accountState balance:int64 seqno:int32 last_transaction_id:internal.transactionId sync_utime:int53= testGiver.AccountState; - -uninited.accountState balance:int64 last_transaction_id:internal.transactionId frozen_hash:bytes sync_utime:int53 = uninited.AccountState; - -//generic.initialAccountStateRaw initital_account_state:raw.initialAccountState = generic.InitialAccountState; -//generic.initialAccountStateTestWallet initital_account_state:testWallet.initialAccountState = generic.InitialAccountState; -//generic.initialAccountStateWallet initital_account_state:wallet.initialAccountState = generic.InitialAccountState; - -generic.accountStateRaw account_state:raw.accountState = generic.AccountState; -generic.accountStateTestWallet account_state:testWallet.accountState = generic.AccountState; -generic.accountStateWallet account_state:wallet.accountState = generic.AccountState; -generic.accountStateWalletV3 account_state:wallet.v3.accountState = generic.AccountState; -generic.accountStateTestGiver account_state:testGiver.accountState = generic.AccountState; -generic.accountStateUninited account_state:uninited.accountState = generic.AccountState; - -sendGramsResult sent_until:int53 body_hash:bytes = SendGramsResult; +fullAccountState balance:int64 last_transaction_id:internal.transactionId block_id:ton.blockIdExt sync_utime:int53 account_state:AccountState = FullAccountState; 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; +// +// MSG +// + +msg.dataText text:string = msg.Data; +msg.dataEncryptedText text:string = msg.Data; +msg.message destination:accountAddress amount:int64 data:msg.Data = msg.Message; + +// +// DNS +// + +dns.entryDataUnknown bytes:bytes = dns.EntryData; +dns.entryDataText text:string = dns.EntryData; +dns.entryDataNextResolver resolver:AccountAddress = dns.EntryData; +dns.entryDataSmcAddress smc_address:AccountAddress = dns.EntryData; +//TODO: other entry types + +dns.entry name:string category:int32 entry:dns.EntryData = dns.Entry; + +dns.actionDeleteAll = dns.Action; +// use category = 0 to delete all entries +dns.actionDelete name:string category:int32 = dns.Action; +dns.actionSet entry:dns.entry = dns.Action; + +dns.resolved entries:vector = dns.Resolved; + +// +// Actions +// + +actionNoop = Action; +actionMsg messages:vector allow_send_to_uninited:Bool = Action; +actionDns actions:vector = Action; +//actionMultisig actions:vector = Action; + +fees in_fwd_fee:int53 storage_fee:int53 gas_fee:int53 fwd_fee:int53 = Fees; +query.fees source_fees:fees destination_fees:vector = query.Fees; +// query.emulationResult exit_code:int32 fees:fees = query.EmulationResult; query.info id:int53 valid_until:int53 body_hash:bytes = query.Info; tvm.slice bytes:bytes = tvm.Slice; @@ -152,51 +190,41 @@ packAccountAddress account_address:unpackedAccountAddress = AccountAddress; 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.getTransactions account_address:accountAddress from_transaction_id:internal.transactionId = raw.Transactions; +raw.getAccountState account_address:accountAddress = raw.FullAccountState; +raw.getTransactions private_key:InputKey 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.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; +sync = ton.BlockIdExt; -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.v3.getAccountAddress initital_account_state:wallet.v3.initialAccountState = AccountAddress; - -testGiver.getAccountState = testGiver.AccountState; -testGiver.getAccountAddress = AccountAddress; -testGiver.sendGrams destination:accountAddress seqno:int32 amount:int64 message:bytes = SendGramsResult; - -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.createSendGramsQuery private_key:InputKey source:accountAddress destination:accountAddress amount:int64 timeout:int32 allow_send_to_uninited:Bool message:bytes = query.Info; +// revision = 0 -- use default revision +// revision = x (x > 0) -- use revision x +getAccountAddress initial_account_state:InitialAccountState revision:int32 = AccountAddress; +// guessAccountRevision initial_account_state:InitialAccountState = AccountRevisionList; +getAccountState account_address:accountAddress = FullAccountState; +createQuery private_key:InputKey address:accountAddress timeout:int32 action:Action = query.Info; query.send id:int53 = Ok; query.forget id:int53 = Ok; query.estimateFees id:int53 ignore_chksig:Bool = query.Fees; +// query.emulate id:int53 ignore_chksig:Bool = query.EmulationResult; query.getInfo id:int53 = query.Info; smc.load account_address:accountAddress = smc.Info; +//smc.forget id:int53 = Ok; smc.getCode id:int53 = tvm.Cell; smc.getData id:int53 = tvm.Cell; smc.getState id:int53 = tvm.Cell; smc.runGetMethod id:int53 method:smc.MethodId stack:vector = smc.RunResult; +dns.resolve account_address:accountAddress name:string category:int32 = dns.Resolved; + onLiteServerQueryResult id:int64 bytes:bytes = Ok; onLiteServerQueryError id:int64 error:error = Ok; +withBlock id:ton.blockIdExt function:Function = Object; + runTests dir:string = Ok; liteServer.getInfo = liteServer.Info; diff --git a/tl/generate/scheme/tonlib_api.tlo b/tl/generate/scheme/tonlib_api.tlo index 5c9d506e..209dc1c2 100644 Binary files a/tl/generate/scheme/tonlib_api.tlo and b/tl/generate/scheme/tonlib_api.tlo differ diff --git a/tonlib/test/offline.cpp b/tonlib/test/offline.cpp index 4153e269..897682cc 100644 --- a/tonlib/test/offline.cpp +++ b/tonlib/test/offline.cpp @@ -14,7 +14,7 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #include "block/block.h" @@ -60,6 +60,23 @@ TEST(Tonlib, CellString) { } }; +TEST(Tonlib, Text) { + for (unsigned size : + {0, 1, 7, 8, 35, 127, 128, 255, 256, (int)vm::CellText::max_bytes - 1, (int)vm::CellText::max_bytes}) { + auto str = td::rand_string('a', 'z', size); + for (unsigned head : {16, 17, 127, 35 * 8, 127 * 8, 1023, 1024}) { + vm::CellBuilder cb; + vm::CellText::store(cb, str, head).ensure(); + auto cs = vm::load_cell_slice(cb.finalize()); + auto cs2 = cs; + cs.print_rec(std::cerr); + CHECK(block::gen::t_Text.validate_exact(cs2)); + auto got_str = vm::CellText::load(cs).move_as_ok(); + ASSERT_EQ(str, got_str); + } + } +}; + using namespace tonlib; TEST(Tonlib, PublicKey) { @@ -126,7 +143,11 @@ TEST(Tonlib, InitClose) { )abc"; sync_send(client, make_object(cfg(bad_config.str()))).ensure_error(); - sync_send(client, make_object()).ensure_error(); + auto address = + sync_send(client, + make_object(make_object(), 0)) + .move_as_ok(); + sync_send(client, make_object(std::move(address))).ensure_error(); sync_send(client, make_object()).ensure(); sync_send(client, make_object()).ensure_error(); sync_send(client, make_object(make_object(nullptr, dir(".")))) @@ -159,6 +180,38 @@ TEST(Tonlib, SimpleEncryption) { } } +TEST(Tonlib, SimpleEncryptionAsym) { + auto private_key = td::Ed25519::generate_private_key().move_as_ok(); + auto public_key = private_key.get_public_key().move_as_ok(); + auto other_private_key = td::Ed25519::generate_private_key().move_as_ok(); + auto other_public_key = private_key.get_public_key().move_as_ok(); + auto wrong_private_key = td::Ed25519::generate_private_key().move_as_ok(); + { + std::string data = "some private data"; + auto encrypted_data = SimpleEncryption::encrypt_data(data, public_key, other_private_key).move_as_ok(); + LOG(ERROR) << encrypted_data.size(); + auto decrypted_data = SimpleEncryption::decrypt_data(encrypted_data, private_key).move_as_ok(); + CHECK(data == decrypted_data); + auto decrypted_data2 = SimpleEncryption::decrypt_data(encrypted_data, other_private_key).move_as_ok(); + CHECK(data == decrypted_data2); + SimpleEncryption::decrypt_data(encrypted_data, wrong_private_key).ensure_error(); + SimpleEncryption::decrypt_data("", private_key).ensure_error(); + SimpleEncryption::decrypt_data(std::string(32, 'a'), private_key).ensure_error(); + SimpleEncryption::decrypt_data(std::string(33, 'a'), private_key).ensure_error(); + SimpleEncryption::decrypt_data(std::string(64, 'a'), private_key).ensure_error(); + SimpleEncryption::decrypt_data(std::string(128, 'a'), private_key).ensure_error(); + } + + for (size_t i = 0; i < 255; i++) { + auto data = td::rand_string('a', 'z', static_cast(i)); + auto encrypted_data = SimpleEncryption::encrypt_data(data, public_key, other_private_key).move_as_ok(); + auto decrypted_data = SimpleEncryption::decrypt_data(encrypted_data, private_key).move_as_ok(); + CHECK(data == decrypted_data); + auto decrypted_data2 = SimpleEncryption::decrypt_data(encrypted_data, other_private_key).move_as_ok(); + CHECK(data == decrypted_data2); + } +} + class MnemonicBench : public td::Benchmark { public: std::string get_description() const override { diff --git a/tonlib/test/online.cpp b/tonlib/test/online.cpp index 0e25dd66..9cdee740 100644 --- a/tonlib/test/online.cpp +++ b/tonlib/test/online.cpp @@ -23,7 +23,7 @@ exception statement from your version. If you delete this exception statement from all source files in the program, then also delete it here. - Copyright 2017-2019 Telegram Systems LLP + Copyright 2017-2020 Telegram Systems LLP */ #include "adnl/adnl-ext-client.h" #include "tl-utils/tl-utils.hpp" @@ -37,6 +37,7 @@ #include "Ed25519.h" #include "smc-envelope/GenericAccount.h" +#include "smc-envelope/ManualDns.h" #include "smc-envelope/MultisigWallet.h" #include "smc-envelope/TestGiver.h" #include "smc-envelope/TestWallet.h" @@ -108,6 +109,10 @@ struct Key { struct Wallet { std::string address; Key key; + + auto get_address() const { + return tonlib_api::make_object(address); + } }; struct TransactionId { @@ -116,7 +121,7 @@ struct TransactionId { }; struct AccountState { - enum Type { Empty, Wallet, Unknown } type{Empty}; + enum Type { Empty, Wallet, Dns, Unknown } type{Empty}; td::int64 sync_utime{-1}; td::int64 balance{-1}; TransactionId last_transaction_id; @@ -136,8 +141,17 @@ void sync(Client& client) { static td::uint32 default_wallet_id{0}; std::string wallet_address(Client& client, const Key& key) { return sync_send(client, - make_object( - make_object(key.public_key, default_wallet_id))) + make_object( + make_object(key.public_key, default_wallet_id), 0)) + .move_as_ok() + ->account_address_; +} + +std::string highload_wallet_address(Client& client, const Key& key) { + return sync_send(client, make_object( + make_object(key.public_key, + default_wallet_id), + 1 /*TODO: guess revision!*/)) .move_as_ok() ->account_address_; } @@ -148,35 +162,31 @@ Wallet import_wallet_from_pkey(Client& client, std::string pkey, std::string pas make_object(td::SecureString(pkey)))) .move_as_ok(); Wallet wallet{"", {key->public_key_, std::move(key->secret_)}}; - wallet.address = wallet_address(client, wallet.key); + wallet.address = highload_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_; -} - 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(); + auto state = sync_send(client, tonlib_api::make_object( + tonlib_api::make_object(address))) + .move_as_ok(); 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.balance = state->balance_; + res.sync_utime = state->sync_utime_; + res.last_transaction_id.lt = state->last_transaction_id_->lt_; + res.last_transaction_id.hash = state->last_transaction_id_->hash_; res.address = address; - switch (generic_state->get_id()) { - case tonlib_api::generic_accountStateUninited::ID: + switch (state->account_state_->get_id()) { + case tonlib_api::uninited_accountState::ID: res.type = AccountState::Empty; break; - case tonlib_api::generic_accountStateWalletV3::ID: - case tonlib_api::generic_accountStateWallet::ID: + case tonlib_api::wallet_v3_accountState::ID: + case tonlib_api::wallet_accountState::ID: res.type = AccountState::Wallet; break; + case tonlib_api::dns_accountState::ID: + res.type = AccountState::Dns; + break; default: res.type = AccountState::Unknown; break; @@ -219,13 +229,31 @@ struct QueryInfo { }; 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))); + td::int64 amount, bool encrypted, std::string message, bool force = false, + int timeout = 0, bool fake = false) { + std::vector> msgs; + tonlib_api::object_ptr data; + if (encrypted) { + data = tonlib_api::make_object(std::move(message)); + } else { + data = tonlib_api::make_object(std::move(message)); + } + msgs.push_back(tonlib_api::make_object( + tonlib_api::make_object(destination), amount, std::move(data))); + + auto r_id = + sync_send(client, tonlib_api::make_object( + fake ? source.key.get_fake_input_key() : source.key.get_input_key(), source.get_address(), + timeout, tonlib_api::make_object(std::move(msgs), force))); + TRY_RESULT(id, std::move(r_id)); + return QueryId{id->id_}; +} + +td::Result create_update_dns_query(Client& client, const Wallet& dns, + std::vector> entries) { + using namespace ton::tonlib_api; + auto r_id = sync_send(client, make_object(dns.key.get_input_key(), dns.get_address(), 60, + make_object(std::move(entries)))); TRY_RESULT(id, std::move(r_id)); return QueryId{id->id_}; } @@ -242,7 +270,11 @@ td::Result create_raw_query(Client& client, std::string source, std::st 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_)); + Fee second; + if (!fees->destination_fees_.empty()) { + second = to_fee(fees->destination_fees_[0]); + } + return std::make_pair(to_fee(fees->source_fees_), second); } void query_send(Client& client, QueryId query_id) { @@ -266,26 +298,38 @@ td::Result wait_state_change(Client& client, const AccountState& o } }; -td::Result> get_transactions(Client& client, std::string address, +td::Result> get_transactions(Client& client, + td::optional wallet, + std::string address, const TransactionId& from) { auto got_transactions = sync_send(client, make_object( + wallet ? wallet.value()->key.get_input_key() : nullptr, 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) { +td::Status transfer_grams(Client& client, const Wallet& wallet, std::string address, td::int64 amount, + bool fast = false) { 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); + bool encrypt = true; + auto r_query_id = create_send_grams_query(client, wallet, address, amount, encrypt, message, fast); + if (r_query_id.is_error()) { + LOG(INFO) << "Send query WITHOUT message encryption"; + encrypt = false; + r_query_id = create_send_grams_query(client, wallet, address, amount, encrypt, message, fast); + } else { + LOG(INFO) << "Send query WITH message encryption"; + } 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 = create_send_grams_query(client, wallet, address, amount, false, message, true); } r_query_id.ensure(); @@ -304,6 +348,9 @@ td::Status transfer_grams(Client& client, const Wallet& wallet, std::string addr LOG(INFO) << "Transfer: send query"; query_send(client, query_id); + if (fast) { + return td::Status::OK(); + } 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; @@ -311,7 +358,7 @@ td::Status transfer_grams(Client& client, const Wallet& wallet, std::string addr td::int64 lt; td::int64 first_fee; { - auto tr = get_transactions(client, src_state.address, new_src_state.last_transaction_id).move_as_ok(); + auto tr = get_transactions(client, &wallet, 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); @@ -331,7 +378,7 @@ td::Status transfer_grams(Client& client, const Wallet& wallet, std::string addr LOG(INFO) << "Transfer: reached destination in " << timer; { - auto tr = get_transactions(client, dst_state.address, new_dst_state.last_transaction_id).move_as_ok(); + 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_); @@ -341,7 +388,9 @@ td::Status transfer_grams(Client& client, const Wallet& wallet, std::string addr 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_); + if (!encrypt) { + 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_ @@ -361,9 +410,10 @@ Wallet create_empty_wallet(Client& client) { Wallet wallet{"", {key->public_key_, std::move(key->secret_)}}; auto account_address = - sync_send(client, - make_object( - make_object(wallet.key.public_key, default_wallet_id))) + sync_send( + client, + make_object( + make_object(wallet.key.public_key, default_wallet_id), 0)) .move_as_ok(); wallet.address = account_address->account_address_; @@ -376,31 +426,33 @@ Wallet create_empty_wallet(Client& client) { return wallet; } -void dump_transaction_history(Client& client, std::string address) { +Wallet create_empty_dns(Client& client) { using tonlib_api::make_object; - auto state = sync_send(client, make_object()).move_as_ok(); - auto tid = std::move(state->last_transaction_id_); - int cnt = 0; - while (tid->lt_ != 0) { - auto lt = tid->lt_; - auto got_transactions = sync_send(client, make_object( - make_object(address), std::move(tid))) - .move_as_ok(); - CHECK(got_transactions->transactions_.size() > 0); - CHECK(got_transactions->previous_transaction_id_->lt_ < lt); - for (auto& txn : got_transactions->transactions_) { - LOG(ERROR) << to_string(txn); - cnt++; - } - tid = std::move(got_transactions->previous_transaction_id_); - } - LOG(ERROR) << cnt; + auto key = sync_send(client, make_object(td::SecureString("local"), td::SecureString(), + td::SecureString())) + .move_as_ok(); + Wallet dns{"", {key->public_key_, std::move(key->secret_)}}; + + auto account_address = + sync_send(client, make_object( + make_object(dns.key.public_key, default_wallet_id), 0)) + .move_as_ok(); + + dns.address = account_address->account_address_; + + // get state of empty account + auto state = get_account_state(client, dns.address); + ASSERT_EQ(-1, state.balance); + ASSERT_EQ(AccountState::Empty, state.type); + + return dns; } 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 query_id = + create_send_grams_query(client, wallet_a, wallet_b.address, 0, false, "???", 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; @@ -495,6 +547,74 @@ void test_multisig(Client& client, const Wallet& giver_wallet) { } } +void dns_resolve(Client& client, const Wallet& dns, std::string name) { + using namespace ton::tonlib_api; + auto address = dns.get_address(); + while (true) { + auto resolved = + sync_send(client, make_object<::ton::tonlib_api::dns_resolve>(std::move(address), name, 1)).move_as_ok(); + CHECK(resolved->entries_.size() == 1); + LOG(INFO) << to_string(resolved); + if (resolved->entries_[0]->category_ == -1) { + auto entry = ton::move_tl_object_as(resolved->entries_[0]->entry_); + address = std::move(entry->resolver_); + continue; + } + LOG(INFO) << "OK"; + break; + } +} + +void test_dns(Client& client, const Wallet& giver_wallet) { + auto A = create_empty_dns(client); + auto A_B = create_empty_dns(client); + auto A_B_C = create_empty_dns(client); + transfer_grams(client, giver_wallet, A.address, 1 * Gramm, true).ensure(); + transfer_grams(client, giver_wallet, A_B.address, 1 * Gramm, true).ensure(); + transfer_grams(client, giver_wallet, A_B_C.address, 1 * Gramm, true).ensure(); + + using namespace ton::tonlib_api; + std::vector> actions; + + actions.push_back(make_object( + make_object("A", -1, make_object(A_B.get_address())))); + auto init_A = create_update_dns_query(client, A, std::move(actions)).move_as_ok(); + actions.push_back(make_object( + make_object("B.A", -1, make_object(A_B_C.get_address())))); + auto init_A_B = create_update_dns_query(client, A_B, std::move(actions)).move_as_ok(); + actions.push_back( + make_object(make_object("C.B.A", 1, make_object("Hello dns")))); + auto init_A_B_C = create_update_dns_query(client, A_B_C, std::move(actions)).move_as_ok(); + + LOG(INFO) << "Send dns init queries"; + ::query_send(client, init_A); + ::query_send(client, init_A_B); + ::query_send(client, init_A_B_C); + + auto wait = [&](auto& query, auto& dns) { + auto info = query_get_info(client, query); + LOG(INFO) << "Wait till dns " << dns.address << " is inited " << info.valid_until; + while (true) { + auto state = get_account_state(client, dns.address); + LOG(INFO) << "Account type: " << state.type << " " << state.sync_utime; + if (state.type == ::AccountState::Dns) { + break; + } + if (state.sync_utime >= info.valid_until) { + LOG(FATAL) << "Query expired"; + } + LOG(INFO) << "time left to wait: " << td::format::as_time(info.valid_until - state.sync_utime); + client.receive(1); + } + }; + wait(init_A, A); + wait(init_A_B, A_B); + wait(init_A_B_C, A_B_C); + + // search "C.B.A" + ::dns_resolve(client, A, "C.B.A"); +} + int main(int argc, char* argv[]) { td::set_default_failure_signal_handler(); using tonlib_api::make_object; @@ -550,6 +670,7 @@ int main(int argc, char* argv[]) { // give wallet with some test grams to run test auto giver_wallet = import_wallet_from_pkey(client, giver_key_str, giver_key_pwd); + test_dns(client, giver_wallet); test_back_and_forth_transfer(client, giver_wallet, false); test_back_and_forth_transfer(client, giver_wallet, true); test_multisig(client, giver_wallet); diff --git a/tonlib/tonlib/LastConfig.h b/tonlib/tonlib/LastConfig.h index 2e0cf2a3..514b4a59 100644 --- a/tonlib/tonlib/LastConfig.h +++ b/tonlib/tonlib/LastConfig.h @@ -14,7 +14,7 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "td/actor/actor.h" @@ -54,7 +54,7 @@ class LastConfig : public td::actor::Actor { QueryState get_config_state_{QueryState::Empty}; std::vector> promises_; - std::vector params_{18, 20, 21, 24, 25}; + std::vector params_{4, 18, 20, 21, 24, 25}; void with_last_block(td::Result r_last_block); void on_config(td::Result> r_config); diff --git a/tonlib/tonlib/TonlibClient.cpp b/tonlib/tonlib/TonlibClient.cpp index a2639210..108bc037 100644 --- a/tonlib/tonlib/TonlibClient.cpp +++ b/tonlib/tonlib/TonlibClient.cpp @@ -14,7 +14,7 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #include "TonlibClient.h" @@ -29,10 +29,14 @@ #include "tonlib/TonlibError.h" #include "smc-envelope/GenericAccount.h" +#include "smc-envelope/ManualDns.h" #include "smc-envelope/TestWallet.h" #include "smc-envelope/Wallet.h" #include "smc-envelope/WalletV3.h" +#include "smc-envelope/HighloadWallet.h" +#include "smc-envelope/HighloadWalletV2.h" #include "smc-envelope/TestGiver.h" +#include "smc-envelope/SmartContractCode.h" #include "auto/tl/tonlib_api.hpp" #include "block/block-auto.h" @@ -54,12 +58,16 @@ namespace tonlib { namespace int_api { struct GetAccountState { block::StdAddress address; + td::optional block_id; using ReturnType = td::unique_ptr; }; struct GetPrivateKey { KeyStorage::InputKey input_key; using ReturnType = KeyStorage::PrivateKey; }; +struct GetDnsResolver { + using ReturnType = block::StdAddress; +}; struct SendMessage { td::Ref message; using ReturnType = td::Unit; @@ -72,6 +80,11 @@ R downcast_call2(O&& o, F&& f, R res = {}) { return res; } +auto to_tonlib_api(const ton::BlockIdExt& blk) { + return tonlib_api::make_object( + blk.id.workchain, blk.id.shard, blk.id.seqno, blk.root_hash.as_slice().str(), blk.file_hash.as_slice().str()); +} + tonlib_api::object_ptr to_tonlib_api(const TonlibClient::FullConfig& full_config) { return tonlib_api::make_object(full_config.wallet_id); } @@ -116,6 +129,7 @@ struct RawAccountState { td::Ref state; std::string frozen_hash; block::AccountState::Info info; + ton::BlockIdExt block_id; }; tonlib_api::object_ptr empty_transaction_id() { @@ -135,12 +149,11 @@ class AccountState { public: AccountState(block::StdAddress address, RawAccountState&& raw, td::uint32 wallet_id) : address_(std::move(address)), raw_(std::move(raw)), wallet_id_(wallet_id) { - wallet_type_ = guess_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()); + return tonlib_api::make_object(raw().frozen_hash); } td::Result> to_raw_accountState() const { @@ -153,9 +166,22 @@ class AccountState { 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()); + return tonlib_api::make_object(std::move(code), std::move(data), raw().frozen_hash); + } + + td::Result> to_raw_fullAccountState() 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), to_tonlib_api(raw().block_id), + raw().frozen_hash, get_sync_time()); } td::Result> to_testWallet_accountState() const { @@ -163,8 +189,7 @@ class AccountState { 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()); + return tonlib_api::make_object(static_cast(seqno)); } td::Result> to_wallet_accountState() const { @@ -172,8 +197,7 @@ class AccountState { 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()); + return tonlib_api::make_object(static_cast(seqno)); } td::Result> to_wallet_v3_accountState() const { if (wallet_type_ != WalletV3) { @@ -182,9 +206,28 @@ class AccountState { auto wallet = ton::WalletV3(get_smc_state()); TRY_RESULT(seqno, wallet.get_seqno()); TRY_RESULT(wallet_id, wallet.get_wallet_id()); - return tonlib_api::make_object( - get_balance(), static_cast(wallet_id), static_cast(seqno), - to_transaction_id(raw().info), get_sync_time()); + return tonlib_api::make_object(static_cast(wallet_id), + static_cast(seqno)); + } + td::Result> to_wallet_highload_v1_accountState() + const { + if (wallet_type_ != HighloadWalletV1) { + return TonlibError::AccountTypeUnexpected("HighloadWalletV1"); + } + auto wallet = ton::HighloadWallet(get_smc_state()); + TRY_RESULT(seqno, wallet.get_seqno()); + TRY_RESULT(wallet_id, wallet.get_wallet_id()); + return tonlib_api::make_object(static_cast(wallet_id), + static_cast(seqno)); + } + td::Result> to_wallet_highload_v2_accountState() + const { + if (wallet_type_ != HighloadWalletV2) { + return TonlibError::AccountTypeUnexpected("HighloadWalletV2"); + } + auto wallet = ton::HighloadWalletV2(get_smc_state()); + TRY_RESULT(wallet_id, wallet.get_wallet_id()); + return tonlib_api::make_object(static_cast(wallet_id)); } td::Result> to_testGiver_accountState() const { @@ -192,41 +235,106 @@ class AccountState { 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()); + return tonlib_api::make_object(static_cast(seqno)); } - 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)); - } - case WalletV3: { - TRY_RESULT(res, to_wallet_v3_accountState()); - return tonlib_api::make_object(std::move(res)); - } + td::Result> to_dns_accountState() const { + if (wallet_type_ != ManualDns) { + return TonlibError::AccountTypeUnexpected("ManualDns"); } - UNREACHABLE(); + TRY_RESULT(wallet_id, ton::ManualDns(get_smc_state()).get_wallet_id()); + return tonlib_api::make_object(static_cast(wallet_id)); } - enum WalletType { Empty, Unknown, Giver, SimpleWallet, Wallet, WalletV3 }; + td::Result> to_accountState() const { + auto f = [](auto&& r_x) -> td::Result> { + TRY_RESULT(x, std::move(r_x)); + return std::move(x); + }; + + switch (wallet_type_) { + case Empty: + return to_uninited_accountState(); + case Unknown: + return f(to_raw_accountState()); + case Giver: + return f(to_testGiver_accountState()); + case SimpleWallet: + return f(to_testWallet_accountState()); + case Wallet: + return f(to_wallet_accountState()); + case WalletV3: + return f(to_wallet_v3_accountState()); + case HighloadWalletV1: + return f(to_wallet_highload_v1_accountState()); + case HighloadWalletV2: + return f(to_wallet_highload_v2_accountState()); + case ManualDns: + return f(to_dns_accountState()); + default: + UNREACHABLE(); + } + } + + td::Result> to_fullAccountState() const { + TRY_RESULT(account_state, to_accountState()); + return tonlib_api::make_object(get_balance(), to_transaction_id(raw().info), + to_tonlib_api(raw().block_id), get_sync_time(), + std::move(account_state)); + } + + enum WalletType { + Empty, + Unknown, + Giver, + SimpleWallet, + Wallet, + WalletV3, + HighloadWalletV1, + HighloadWalletV2, + ManualDns + }; WalletType get_wallet_type() const { return wallet_type_; } + bool is_wallet() const { + switch (get_wallet_type()) { + case AccountState::Empty: + case AccountState::Unknown: + case AccountState::ManualDns: + return false; + case AccountState::Giver: + case AccountState::SimpleWallet: + case AccountState::Wallet: + case AccountState::WalletV3: + case AccountState::HighloadWalletV1: + case AccountState::HighloadWalletV2: + return true; + } + UNREACHABLE(); + return false; + } + td::unique_ptr get_wallet() const { + switch (get_wallet_type()) { + case AccountState::Empty: + case AccountState::Unknown: + case AccountState::ManualDns: + return {}; + case AccountState::Giver: + return td::make_unique(get_smc_state()); + case AccountState::SimpleWallet: + return td::make_unique(get_smc_state()); + case AccountState::Wallet: + return td::make_unique(get_smc_state()); + case AccountState::WalletV3: + return td::make_unique(get_smc_state()); + case AccountState::HighloadWalletV1: + return td::make_unique(get_smc_state()); + case AccountState::HighloadWalletV2: + return td::make_unique(get_smc_state()); + } + UNREACHABLE(); + return {}; + } bool is_frozen() const { return !raw_.frozen_hash.empty(); } @@ -255,6 +363,23 @@ class AccountState { if (wallet_type_ != WalletType::Empty) { return wallet_type_; } + auto o_revision = ton::WalletV3::guess_revision(address_, key, wallet_id_); + if (o_revision) { + wallet_type_ = WalletType::WalletV3; + wallet_revision_ = o_revision.value(); + LOG(ERROR) << "!!!" << wallet_revision_; + set_new_state({ton::WalletV3::get_init_code(wallet_revision_), ton::WalletV3::get_init_data(key, wallet_id_)}); + return wallet_type_; + } + o_revision = ton::HighloadWalletV2::guess_revision(address_, key, wallet_id_); + if (o_revision) { + wallet_type_ = WalletType::HighloadWalletV2; + wallet_revision_ = o_revision.value(); + LOG(ERROR) << "!!!" << wallet_revision_; + set_new_state( + {ton::HighloadWalletV2::get_init_code(wallet_revision_), ton::WalletV3::get_init_data(key, wallet_id_)}); + 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)}); @@ -263,10 +388,17 @@ class AccountState { address_.addr) { set_new_state({ton::Wallet::get_init_code(), ton::Wallet::get_init_data(key)}); wallet_type_ = WalletType::Wallet; - } else if (ton::GenericAccount::get_address(address_.workchain, ton::WalletV3::get_init_state(key, wallet_id_)) + } else if (ton::GenericAccount::get_address(address_.workchain, + ton::HighloadWallet::get_init_state(key, wallet_id_)) .addr == address_.addr) { - set_new_state({ton::WalletV3::get_init_code(), ton::WalletV3::get_init_data(key, wallet_id_)}); - wallet_type_ = WalletType::WalletV3; + set_new_state({ton::HighloadWallet::get_init_code(), ton::HighloadWallet::get_init_data(key, wallet_id_)}); + wallet_type_ = WalletType::HighloadWalletV1; + } else { + auto dns = ton::ManualDns::create(key, wallet_id_); + if (dns->get_address().addr == address_.addr) { + set_new_state(dns->get_state()); + wallet_type_ = WalletType::ManualDns; + } } return wallet_type_; } @@ -306,28 +438,44 @@ class AccountState { block::StdAddress address_; RawAccountState raw_; WalletType wallet_type_{Unknown}; + td::int32 wallet_revision_{0}; td::uint32 wallet_id_{0}; bool has_new_state_{false}; - WalletType guess_type() const { + WalletType guess_type() { if (raw_.code.is_null()) { - return WalletType::Empty; + wallet_type_ = WalletType::Empty; + return wallet_type_; } auto code_hash = raw_.code->get_hash(); + auto o_revision = ton::WalletV3::guess_revision(code_hash); + if (o_revision) { + wallet_type_ = WalletType::WalletV3; + wallet_revision_ = o_revision.value(); + return wallet_type_; + } + o_revision = ton::HighloadWalletV2::guess_revision(code_hash); + if (o_revision) { + wallet_type_ = WalletType::HighloadWalletV2; + wallet_revision_ = o_revision.value(); + return wallet_type_; + } + if (code_hash == ton::TestGiver::get_init_code_hash()) { - return WalletType::Giver; + wallet_type_ = WalletType::Giver; + } else if (code_hash == ton::TestWallet::get_init_code_hash()) { + wallet_type_ = WalletType::SimpleWallet; + } else if (code_hash == ton::Wallet::get_init_code_hash()) { + wallet_type_ = WalletType::Wallet; + } else if (code_hash == ton::HighloadWallet::get_init_code_hash()) { + wallet_type_ = WalletType::HighloadWalletV1; + } else if (code_hash == ton::SmartContractCode::dns_manual()->get_hash()) { + wallet_type_ = WalletType::ManualDns; + } else { + LOG(WARNING) << "Unknown code hash: " << td::base64_encode(code_hash.as_slice()); + wallet_type_ = WalletType::Unknown; } - if (code_hash == ton::TestWallet::get_init_code_hash()) { - return WalletType::SimpleWallet; - } - if (code_hash == ton::Wallet::get_init_code_hash()) { - return WalletType::Wallet; - } - if (code_hash == ton::WalletV3::get_init_code_hash()) { - return WalletType::WalletV3; - } - LOG(WARNING) << "Unknown code hash: " << td::base64_encode(code_hash.as_slice()); - return WalletType::Unknown; + return wallet_type_; } }; @@ -335,7 +483,7 @@ class Query { public: struct Raw { td::unique_ptr source; - td::unique_ptr destination; + std::vector> destinations; td::uint32 valid_until{std::numeric_limits::max()}; @@ -429,7 +577,7 @@ class Query { } }; - td::Result calc_fwd_fees(td::Ref list, const block::MsgPrices& msg_prices) { + td::Result calc_fwd_fees(td::Ref list, block::MsgPrices** msg_prices, bool is_masterchain) { td::int64 res = 0; std::vector> actions; int n{0}; @@ -467,10 +615,31 @@ class Query { 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"); } + + bool dest_is_masterchain = false; + if (block::gen::t_CommonMsgInfoRelaxed.get_tag(*msg.info) == block::gen::CommonMsgInfoRelaxed::int_msg_info) { + block::gen::CommonMsgInfoRelaxed::Record_int_msg_info info; + if (!tlb::csr_unpack(msg.info, info)) { + return td::Status::Error("estimate_fee: can't parse send_msg"); + } + auto dest_addr = info.dest; + if (!dest_addr->prefetch_ulong(1)) { + return td::Status::Error("estimate_fee: messages with external addresses are unsupported"); + } + int tag = block::gen::t_MsgAddressInt.get_tag(*dest_addr); + + if (tag == block::gen::MsgAddressInt::addr_std) { + block::gen::MsgAddressInt::Record_addr_std recs; + if (!tlb::csr_unpack(dest_addr, recs)) { + return td::Status::Error("estimate_fee: can't parse send_msg"); + } + dest_is_masterchain = recs.workchain_id == ton::masterchainId; + } + } 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); + res += msg_prices[is_masterchain || dest_is_masterchain]->compute_fwd_fees(sstat.cells, sstat.bits); break; } case block::gen::OutAction::action_reserve_currency: @@ -479,34 +648,26 @@ class Query { } return res; } - td::Result> estimate_fees(bool ignore_chksig, const block::Config& cfg) { + 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()); - + TRY_RESULT(masterchain_msg_prices, cfg.get_msg_prices(true)); + TRY_RESULT(basechain_msg_prices, cfg.get_msg_prices(false)); + block::MsgPrices* msg_prices[2] = {&basechain_msg_prices, &masterchain_msg_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); + in_fwd_fee += msg_prices[is_masterchain]->compute_fwd_fees(sstat.cells, sstat.bits); } vm::GasLimits gas_limits = compute_gas_limits(td::make_refint(raw_.source->get_balance()), gas_limits_prices); @@ -520,7 +681,7 @@ class Query { //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)); + TRY_RESULT_ASSIGN(fwd_fee, calc_fwd_fees(res.actions, msg_prices, is_masterchain)); } auto gas_fee = res.accepted ? compute_gas_price(res.gas_used, gas_limits_prices)->to_long() : 0; @@ -532,12 +693,25 @@ class Query { 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; + std::vector dst_fees; + + for (auto& destination : raw_.destinations) { + bool dest_is_masterchain = destination && destination->get_address().workchain == ton::masterchainId; + TRY_RESULT(dest_gas_limits_prices, cfg.get_gas_limits_prices(dest_is_masterchain)); + auto dest_storage_fee_256 = + destination ? block::StoragePrices::compute_storage_fees( + destination->get_sync_time(), storage_prices, destination->raw().storage_stat, + destination->raw().storage_last_paid, false, is_masterchain) + : td::make_refint(0); + Fee dst_fee; + auto dest_storage_fee = dest_storage_fee_256.is_null() ? 0 : dest_storage_fee_256->to_long(); + if (destination && 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; + } + dst_fees.push_back(dst_fee); } - return std::make_pair(fee, dst_fee); + return std::make_pair(fee, dst_fees); } private: @@ -656,18 +830,21 @@ class GetTransactionHistory : public td::actor::Actor { class GetRawAccountState : public td::actor::Actor { public: - GetRawAccountState(ExtClientRef ext_client_ref, block::StdAddress address, td::actor::ActorShared<> parent, - td::Promise&& promise) - : address_(std::move(address)), promise_(std::move(promise)), parent_(std::move(parent)) { + GetRawAccountState(ExtClientRef ext_client_ref, block::StdAddress address, td::optional block_id, + td::actor::ActorShared<> parent, td::Promise&& promise) + : address_(std::move(address)) + , block_id_(std::move(block_id)) + , promise_(std::move(promise)) + , parent_(std::move(parent)) { client_.set_client(ext_client_ref); } private: block::StdAddress address_; + td::optional block_id_; td::Promise promise_; td::actor::ActorShared<> parent_; ExtClient client_; - LastBlockState last_block_; void with_account_state(td::Result> r_account_state) { check(do_with_account_state(std::move(r_account_state))); @@ -686,11 +863,11 @@ class GetRawAccountState : public td::actor::Actor { td::Result do_with_account_state( ton::tl_object_ptr raw_account_state) { auto account_state = create_account_state(std::move(raw_account_state)); - TRY_RESULT(info, account_state.validate(last_block_.last_block_id, address_)); + TRY_RESULT(info, account_state.validate(block_id_.value(), address_)); auto serialized_state = account_state.state.clone(); RawAccountState res; + res.block_id = block_id_.value(); res.info = std::move(info); - LOG_IF(ERROR, res.info.gen_utime > last_block_.utime) << res.info.gen_utime << " " << last_block_.utime; auto cell = res.info.root; std::ostringstream outp; block::gen::t_Account.print_ref(outp, cell); @@ -776,20 +953,28 @@ class GetRawAccountState : public td::actor::Actor { check(do_with_last_block(std::move(r_last_block))); } - td::Status do_with_last_block(td::Result r_last_block) { - TRY_RESULT_ASSIGN(last_block_, std::move(r_last_block)); + void with_block_id() { client_.send_query( ton::lite_api::liteServer_getAccountState( - ton::create_tl_lite_block_id(last_block_.last_block_id), + ton::create_tl_lite_block_id(block_id_.value()), ton::create_tl_object(address_.workchain, address_.addr)), - [self = this](auto r_state) { self->with_account_state(std::move(r_state)); }, - last_block_.last_block_id.id.seqno); + [self = this](auto r_state) { self->with_account_state(std::move(r_state)); }, block_id_.value().id.seqno); + } + + td::Status do_with_last_block(td::Result r_last_block) { + TRY_RESULT(last_block, std::move(r_last_block)); + block_id_ = std::move(last_block.last_block_id); + with_block_id(); return td::Status::OK(); } void start_up() override { - client_.with_last_block( - [self = this](td::Result r_last_block) { self->with_last_block(std::move(r_last_block)); }); + if (block_id_) { + with_block_id(); + } else { + client_.with_last_block( + [self = this](td::Result r_last_block) { self->with_last_block(std::move(r_last_block)); }); + } } void check(td::Status status) { @@ -952,6 +1137,16 @@ void TonlibClient::on_update(object_ptr response) { on_result(0, std::move(response)); } +void TonlibClient::make_any_request(tonlib_api::Function& function, QueryContext query_context, + td::Promise>&& promise) { + auto old_context = std::move(query_context_); + SCOPE_EXIT { + query_context_ = std::move(old_context); + }; + query_context_ = std::move(query_context); + downcast_call(function, [&](auto& request) { this->make_request(request, promise.wrap([](auto x) { return x; })); }); +} + void TonlibClient::request(td::uint64 id, tonlib_api::object_ptr function) { VLOG(tonlib_query) << "Tonlib got query " << td::tag("id", id) << " " << to_string(function); if (function == nullptr) { @@ -972,22 +1167,20 @@ void TonlibClient::request(td::uint64 id, tonlib_api::object_ptr::ReturnType; - ref_cnt_++; - td::Promise promise = [actor_id = actor_id(self), id, - tmp = actor_shared(self)](td::Result r_result) { - tonlib_api::object_ptr result; - if (r_result.is_error()) { - result = status_to_tonlib_api(r_result.error()); - } else { - result = r_result.move_as_ok(); - } + ref_cnt_++; + using Object = tonlib_api::object_ptr; + td::Promise promise = [actor_id = actor_id(this), id, tmp = actor_shared(this)](td::Result r_result) { + tonlib_api::object_ptr result; + if (r_result.is_error()) { + result = status_to_tonlib_api(r_result.error()); + } else { + result = r_result.move_as_ok(); + } - send_closure(actor_id, &TonlibClient::on_result, id, std::move(result)); - }; - this->make_request(request, std::move(promise)); - }); + send_closure(actor_id, &TonlibClient::on_result, id, std::move(result)); + }; + + make_any_request(*function, {}, std::move(promise)); } void TonlibClient::close() { stop(); @@ -1009,11 +1202,7 @@ tonlib_api::object_ptr TonlibClient::static_request( bool TonlibClient::is_static_request(td::int32 id) { switch (id) { case tonlib_api::runTests::ID: - case tonlib_api::raw_getAccountAddress::ID: - case tonlib_api::testWallet_getAccountAddress::ID: - case tonlib_api::wallet_getAccountAddress::ID: - case tonlib_api::wallet_v3_getAccountAddress::ID: - case tonlib_api::testGiver_getAccountAddress::ID: + case tonlib_api::getAccountAddress::ID: case tonlib_api::packAccountAddress::ID: case tonlib_api::unpackAccountAddress::ID: case tonlib_api::getBip39Hints::ID: @@ -1057,29 +1246,63 @@ td::Result get_public_key(td::Slice public_key) { return address; } -td::Result get_account_address(const tonlib_api::raw_initialAccountState& raw_state) { +td::Result get_account_address(const tonlib_api::raw_initialAccountState& raw_state, + td::int32 revision) { 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 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 ton::GenericAccount::get_address(0 /*zerochain*/, ton::TestWallet::get_init_state(key)); +td::Result get_account_address(const tonlib_api::testGiver_initialAccountState& test_wallet_state, + td::int32 revision) { + return ton::TestGiver::address(); } -td::Result get_account_address(const tonlib_api::wallet_initialAccountState& test_wallet_state) { +td::Result get_account_address(const tonlib_api::testWallet_initialAccountState& test_wallet_state, + td::int32 revision) { TRY_RESULT(key_bytes, get_public_key(test_wallet_state.public_key_)); auto key = td::Ed25519::PublicKey(td::SecureString(key_bytes.key)); - return ton::GenericAccount::get_address(0 /*zerochain*/, ton::Wallet::get_init_state(key)); + return ton::GenericAccount::get_address(0 /*zerochain*/, ton::TestWallet::get_init_state(key, revision)); } -td::Result get_account_address(const tonlib_api::wallet_v3_initialAccountState& test_wallet_state) { + +td::Result get_account_address(const tonlib_api::wallet_initialAccountState& wallet_state, + td::int32 revision) { + TRY_RESULT(key_bytes, get_public_key(wallet_state.public_key_)); + auto key = td::Ed25519::PublicKey(td::SecureString(key_bytes.key)); + return ton::GenericAccount::get_address(0 /*zerochain*/, ton::Wallet::get_init_state(key, revision)); +} +td::Result get_account_address(const tonlib_api::wallet_v3_initialAccountState& test_wallet_state, + td::int32 revision) { TRY_RESULT(key_bytes, get_public_key(test_wallet_state.public_key_)); auto key = td::Ed25519::PublicKey(td::SecureString(key_bytes.key)); return ton::GenericAccount::get_address( - 0 /*zerochain*/, ton::WalletV3::get_init_state(key, static_cast(test_wallet_state.wallet_id_))); + 0 /*zerochain*/, + ton::WalletV3::get_init_state(key, static_cast(test_wallet_state.wallet_id_), revision)); +} + +td::Result get_account_address( + const tonlib_api::wallet_highload_v1_initialAccountState& test_wallet_state, td::int32 revision) { + TRY_RESULT(key_bytes, get_public_key(test_wallet_state.public_key_)); + auto key = td::Ed25519::PublicKey(td::SecureString(key_bytes.key)); + return ton::GenericAccount::get_address( + 0 /*zerochain*/, ton::HighloadWallet::get_init_state(key, static_cast(test_wallet_state.wallet_id_))); +} + +td::Result get_account_address( + const tonlib_api::wallet_highload_v2_initialAccountState& test_wallet_state, td::int32 revision) { + TRY_RESULT(key_bytes, get_public_key(test_wallet_state.public_key_)); + auto key = td::Ed25519::PublicKey(td::SecureString(key_bytes.key)); + return ton::GenericAccount::get_address( + 0 /*zerochain*/, + ton::HighloadWalletV2::get_init_state(key, static_cast(test_wallet_state.wallet_id_), revision)); +} + +td::Result get_account_address(const tonlib_api::dns_initialAccountState& dns_state, + td::int32 revision) { + TRY_RESULT(key_bytes, get_public_key(dns_state.public_key_)); + auto key = td::Ed25519::PublicKey(td::SecureString(key_bytes.key)); + return ton::ManualDns::create(key, static_cast(dns_state.wallet_id_))->get_address(); } td::Result get_account_address(td::Slice account_address) { @@ -1088,60 +1311,19 @@ td::Result get_account_address(td::Slice account_address) { } tonlib_api::object_ptr TonlibClient::do_static_request( - const tonlib_api::raw_getAccountAddress& request) { - if (!request.initital_account_state_) { + const tonlib_api::getAccountAddress& request) { + if (!request.initial_account_state_) { return status_to_tonlib_api(TonlibError::EmptyField("initial_account_state")); } - auto r_account_address = get_account_address(*request.initital_account_state_); + auto r_account_address = downcast_call2>( + *request.initial_account_state_, + [&request](auto&& state) { return get_account_address(state, request.revision_); }); if (r_account_address.is_error()) { return status_to_tonlib_api(r_account_address.error()); } return tonlib_api::make_object(r_account_address.ok().rserialize(true)); } -tonlib_api::object_ptr TonlibClient::do_static_request( - const tonlib_api::testWallet_getAccountAddress& request) { - if (!request.initital_account_state_) { - return status_to_tonlib_api(TonlibError::EmptyField("initial_account_state")); - } - if (!request.initital_account_state_) { - return status_to_tonlib_api(TonlibError::EmptyField("initial_account_state")); - } - auto r_account_address = get_account_address(*request.initital_account_state_); - if (r_account_address.is_error()) { - return status_to_tonlib_api(r_account_address.error()); - } - return tonlib_api::make_object(r_account_address.ok().rserialize(true)); -} - -tonlib_api::object_ptr TonlibClient::do_static_request( - const tonlib_api::wallet_getAccountAddress& request) { - if (!request.initital_account_state_) { - return status_to_tonlib_api(TonlibError::EmptyField("initial_account_state")); - } - auto r_account_address = get_account_address(*request.initital_account_state_); - if (r_account_address.is_error()) { - return status_to_tonlib_api(r_account_address.error()); - } - return tonlib_api::make_object(r_account_address.ok().rserialize(true)); -} -tonlib_api::object_ptr TonlibClient::do_static_request( - const tonlib_api::wallet_v3_getAccountAddress& request) { - if (!request.initital_account_state_) { - return status_to_tonlib_api(TonlibError::EmptyField("initial_account_state")); - } - auto r_account_address = get_account_address(*request.initital_account_state_); - if (r_account_address.is_error()) { - return status_to_tonlib_api(r_account_address.error()); - } - return tonlib_api::make_object(r_account_address.ok().rserialize(true)); -} - -tonlib_api::object_ptr TonlibClient::do_static_request( - const tonlib_api::testGiver_getAccountAddress& request) { - return tonlib_api::make_object(ton::TestGiver::address().rserialize(true)); -} - tonlib_api::object_ptr TonlibClient::do_static_request( const tonlib_api::unpackAccountAddress& request) { auto r_account_address = get_account_address(request.account_address_); @@ -1390,158 +1572,177 @@ td::Result to_std_address_or_throw(td::Ref cs) { td::Result to_std_address(td::Ref cs) { return TRY_VM(to_std_address_or_throw(std::move(cs))); } - -td::Result> to_raw_message_or_throw(td::Ref cell) { - block::gen::Message::Record message; - if (!tlb::type_unpack_cell(cell, block::gen::t_Message_Any, message)) { - return td::Status::Error("Failed to unpack Message"); +struct ToRawTransactions { + explicit ToRawTransactions(td::optional private_key) : private_key_(std::move(private_key)) { } - auto tag = block::gen::CommonMsgInfo().get_tag(*message.info); - if (tag < 0) { - return td::Status::Error("Failed to read CommonMsgInfo tag"); + td::optional private_key_; + td::Result> to_raw_message_or_throw(td::Ref cell) { + block::gen::Message::Record message; + if (!tlb::type_unpack_cell(cell, block::gen::t_Message_Any, message)) { + return td::Status::Error("Failed to unpack Message"); + } + + auto tag = block::gen::CommonMsgInfo().get_tag(*message.info); + if (tag < 0) { + return td::Status::Error("Failed to read CommonMsgInfo tag"); + } + switch (tag) { + case block::gen::CommonMsgInfo::int_msg_info: { + block::gen::CommonMsgInfo::Record_int_msg_info msg_info; + if (!tlb::csr_unpack(message.info, msg_info)) { + return td::Status::Error("Failed to unpack CommonMsgInfo::int_msg_info"); + } + + TRY_RESULT(balance, to_balance(msg_info.value)); + TRY_RESULT(src, to_std_address(msg_info.src)); + TRY_RESULT(dest, to_std_address(msg_info.dest)); + TRY_RESULT(fwd_fee, to_balance(msg_info.fwd_fee)); + TRY_RESULT(ihr_fee, to_balance(msg_info.ihr_fee)); + auto created_lt = static_cast(msg_info.created_lt); + td::Ref body; + if (message.body->prefetch_long(1) == 0) { + body = std::move(message.body); + body.write().advance(1); + } else { + body = vm::load_cell_slice_ref(message.body->prefetch_ref()); + } + auto body_hash = vm::CellBuilder().append_cellslice(*body).finalize()->get_hash().as_slice().str(); + std::string body_message; + if (body->size() >= 32 && body->prefetch_long(32) <= 1) { + auto type = body.write().fetch_long(32); + auto r_body_message = vm::CellString::load(body.write()); + if (type == 1) { + LOG(ERROR) << "TRY DECRYPT"; + r_body_message = [&]() -> td::Result { + TRY_RESULT(message, std::move(r_body_message)); + if (!private_key_) { + return TonlibError::EmptyField("private_key"); + } + TRY_RESULT(decrypted, SimpleEncryption::decrypt_data(message, private_key_.value())); + return decrypted.as_slice().str(); + }(); + } + if (r_body_message.is_ok()) { + body_message = r_body_message.move_as_ok(); + } else { + LOG(WARNING) << "Failed to parse a message: " << r_body_message.error(); + } + } + + return tonlib_api::make_object(std::move(src), std::move(dest), balance, fwd_fee, + ihr_fee, created_lt, std::move(body_hash), + std::move(body_message)); + } + case block::gen::CommonMsgInfo::ext_in_msg_info: { + block::gen::CommonMsgInfo::Record_ext_in_msg_info msg_info; + if (!tlb::csr_unpack(message.info, msg_info)) { + return td::Status::Error("Failed to unpack CommonMsgInfo::ext_in_msg_info"); + } + TRY_RESULT(dest, to_std_address(msg_info.dest)); + td::Ref body; + if (message.body->prefetch_long(1) == 0) { + body = std::move(message.body); + body.write().advance(1); + } else { + body = vm::load_cell_slice_ref(message.body->prefetch_ref()); + } + auto body_hash = vm::CellBuilder().append_cellslice(*body).finalize()->get_hash().as_slice().str(); + return tonlib_api::make_object("", std::move(dest), 0, 0, 0, 0, std::move(body_hash), + ""); + } + case block::gen::CommonMsgInfo::ext_out_msg_info: { + block::gen::CommonMsgInfo::Record_ext_out_msg_info msg_info; + if (!tlb::csr_unpack(message.info, msg_info)) { + return td::Status::Error("Failed to unpack CommonMsgInfo::ext_out_msg_info"); + } + TRY_RESULT(src, to_std_address(msg_info.src)); + return tonlib_api::make_object(std::move(src), "", 0, 0, 0, 0, "", ""); + } + } + + return td::Status::Error("Unknown CommonMsgInfo tag"); } - switch (tag) { - case block::gen::CommonMsgInfo::int_msg_info: { - block::gen::CommonMsgInfo::Record_int_msg_info msg_info; - if (!tlb::csr_unpack(message.info, msg_info)) { - return td::Status::Error("Failed to unpack CommonMsgInfo::int_msg_info"); + + td::Result> to_raw_message(td::Ref cell) { + return TRY_VM(to_raw_message_or_throw(std::move(cell))); + } + + td::Result> to_raw_transaction_or_throw( + block::Transaction::Info&& info) { + std::string data; + + tonlib_api::object_ptr in_msg; + std::vector> out_msgs; + td::int64 fees = 0; + td::int64 storage_fee = 0; + if (info.transaction.not_null()) { + 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"); } - TRY_RESULT(balance, to_balance(msg_info.value)); - TRY_RESULT(src, to_std_address(msg_info.src)); - TRY_RESULT(dest, to_std_address(msg_info.dest)); - TRY_RESULT(fwd_fee, to_balance(msg_info.fwd_fee)); - TRY_RESULT(ihr_fee, to_balance(msg_info.ihr_fee)); - auto created_lt = static_cast(msg_info.created_lt); - td::Ref body; - if (message.body->prefetch_long(1) == 0) { - body = std::move(message.body); - body.write().advance(1); - } else { - body = vm::load_cell_slice_ref(message.body->prefetch_ref()); + TRY_RESULT_ASSIGN(fees, to_balance(trans.total_fees)); + //LOG(ERROR) << fees; + + //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) { + return td::Status::Error("Failed to parse long"); } - auto body_hash = vm::CellBuilder().append_cellslice(*body).finalize()->get_hash().as_slice().str(); - std::string body_message; - if (body->size() >= 32 && body->prefetch_long(32) == 0) { - body.write().fetch_long(32); - auto r_body_message = vm::CellString::load(body.write()); - if (r_body_message.is_ok()) { - body_message = r_body_message.move_as_ok(); + if (is_just == -1) { + auto msg = trans.r1.in_msg->prefetch_ref(); + TRY_RESULT(in_msg_copy, to_raw_message(trans.r1.in_msg->prefetch_ref())); + in_msg = std::move(in_msg_copy); + } + + if (trans.outmsg_cnt != 0) { + vm::Dictionary dict{trans.r1.out_msgs, 15}; + for (int x = 0; x < trans.outmsg_cnt && x < 100; x++) { + TRY_RESULT(out_msg, to_raw_message(dict.lookup_ref(td::BitArray<15>{x}))); + fees += out_msg->fwd_fee_; + fees += out_msg->ihr_fee_; + out_msgs.push_back(std::move(out_msg)); } } - - return tonlib_api::make_object(std::move(src), std::move(dest), balance, fwd_fee, - ihr_fee, created_lt, std::move(body_hash), - std::move(body_message)); - } - case block::gen::CommonMsgInfo::ext_in_msg_info: { - block::gen::CommonMsgInfo::Record_ext_in_msg_info msg_info; - if (!tlb::csr_unpack(message.info, msg_info)) { - return td::Status::Error("Failed to unpack CommonMsgInfo::ext_in_msg_info"); + td::RefInt256 storage_fees; + if (!block::tlb::t_TransactionDescr.get_storage_fees(trans.description, storage_fees)) { + return td::Status::Error("Failed to fetch storage fee from transaction"); } - TRY_RESULT(dest, to_std_address(msg_info.dest)); - td::Ref body; - if (message.body->prefetch_long(1) == 0) { - body = std::move(message.body); - body.write().advance(1); - } else { - body = vm::load_cell_slice_ref(message.body->prefetch_ref()); - } - auto body_hash = vm::CellBuilder().append_cellslice(*body).finalize()->get_hash().as_slice().str(); - return tonlib_api::make_object("", std::move(dest), 0, 0, 0, 0, std::move(body_hash), - ""); - } - case block::gen::CommonMsgInfo::ext_out_msg_info: { - block::gen::CommonMsgInfo::Record_ext_out_msg_info msg_info; - if (!tlb::csr_unpack(message.info, msg_info)) { - return td::Status::Error("Failed to unpack CommonMsgInfo::ext_out_msg_info"); - } - TRY_RESULT(src, to_std_address(msg_info.src)); - return tonlib_api::make_object(std::move(src), "", 0, 0, 0, 0, "", ""); + storage_fee = storage_fees->to_long(); } + return tonlib_api::make_object( + info.now, data, + tonlib_api::make_object(info.prev_trans_lt, + info.prev_trans_hash.as_slice().str()), + fees, storage_fee, fees - storage_fee, std::move(in_msg), std::move(out_msgs)); } - return td::Status::Error("Unknown CommonMsgInfo tag"); -} - -td::Result> to_raw_message(td::Ref cell) { - return TRY_VM(to_raw_message_or_throw(std::move(cell))); -} - -td::Result> to_raw_transaction_or_throw( - block::Transaction::Info&& info) { - std::string data; - - tonlib_api::object_ptr in_msg; - std::vector> out_msgs; - td::int64 fees = 0; - td::int64 storage_fee = 0; - if (info.transaction.not_null()) { - 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"); - } - - 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(); - - auto is_just = trans.r1.in_msg->prefetch_long(1); - if (is_just == trans.r1.in_msg->fetch_long_eof) { - return td::Status::Error("Failed to parse long"); - } - if (is_just == -1) { - auto msg = trans.r1.in_msg->prefetch_ref(); - TRY_RESULT(in_msg_copy, to_raw_message(trans.r1.in_msg->prefetch_ref())); - in_msg = std::move(in_msg_copy); - } - - if (trans.outmsg_cnt != 0) { - vm::Dictionary dict{trans.r1.out_msgs, 15}; - for (int x = 0; x < trans.outmsg_cnt && x < 100; x++) { - TRY_RESULT(out_msg, to_raw_message(dict.lookup_ref(td::BitArray<15>{x}))); - fees += out_msg->fwd_fee_; - fees += out_msg->ihr_fee_; - out_msgs.push_back(std::move(out_msg)); - } - } - td::RefInt256 storage_fees; - if (!block::tlb::t_TransactionDescr.get_storage_fees(trans.description, storage_fees)) { - return td::Status::Error("Failed to fetch storage fee from transaction"); - } - storage_fee = storage_fees->to_long(); - } - return tonlib_api::make_object( - info.now, data, - tonlib_api::make_object(info.prev_trans_lt, - info.prev_trans_hash.as_slice().str()), - fees, storage_fee, fees - storage_fee, std::move(in_msg), std::move(out_msgs)); -} - -td::Result> to_raw_transaction(block::Transaction::Info&& info) { - return TRY_VM(to_raw_transaction_or_throw(std::move(info))); -} - -td::Result> to_raw_transactions( - block::TransactionList::Info&& info) { - std::vector> transactions; - for (auto& transaction : info.transactions) { - TRY_RESULT(raw_transaction, to_raw_transaction(std::move(transaction))); - transactions.push_back(std::move(raw_transaction)); + td::Result> to_raw_transaction(block::Transaction::Info&& info) { + return TRY_VM(to_raw_transaction_or_throw(std::move(info))); } - auto transaction_id = - tonlib_api::make_object(info.lt, info.hash.as_slice().str()); - for (auto& transaction : transactions) { - std::swap(transaction->transaction_id_, transaction_id); - } + td::Result> to_raw_transactions( + block::TransactionList::Info&& info) { + std::vector> transactions; + for (auto& transaction : info.transactions) { + TRY_RESULT(raw_transaction, to_raw_transaction(std::move(transaction))); + transactions.push_back(std::move(raw_transaction)); + } - return tonlib_api::make_object(std::move(transactions), std::move(transaction_id)); -} + auto transaction_id = + tonlib_api::make_object(info.lt, info.hash.as_slice().str()); + for (auto& transaction : transactions) { + std::swap(transaction->transaction_id_, transaction_id); + } + + return tonlib_api::make_object(std::move(transactions), std::move(transaction_id)); + } +}; // Raw @@ -1579,39 +1780,16 @@ td::Status TonlibClient::do_request(const tonlib_api::raw_createAndSendMessage& } td::Status TonlibClient::do_request(tonlib_api::raw_getAccountState& request, - td::Promise>&& promise) { + 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.wrap([](auto&& res) { return res->to_raw_accountState(); })); + make_request(int_api::GetAccountState{std::move(account_address), query_context_.block_id.copy()}, + promise.wrap([](auto&& res) { return res->to_raw_fullAccountState(); })); return td::Status::OK(); } -td::Status TonlibClient::do_request(tonlib_api::raw_getTransactions& request, - td::Promise>&& promise) { - if (!request.account_address_) { - return TonlibError::EmptyField("account_address"); - } - if (!request.from_transaction_id_) { - return TonlibError::EmptyField("from_transaction_id"); - } - TRY_RESULT(account_address, get_account_address(request.account_address_->account_address_)); - auto lt = request.from_transaction_id_->lt_; - auto hash_str = request.from_transaction_id_->hash_; - if (hash_str.size() != 32) { - return td::Status::Error(400, "Invalid transaction id hash size"); - } - td::Bits256 hash; - hash.as_slice().copy_from(hash_str); - - 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.wrap(to_raw_transactions)); - return td::Status::OK(); -} td::Result from_tonlib(tonlib_api::inputKeyRegular& input_key) { if (!input_key.key_) { return TonlibError::EmptyField("key"); @@ -1628,189 +1806,94 @@ td::Result from_tonlib(tonlib_api::InputKey& input_key) { [&](tonlib_api::inputKeyFake&) { return KeyStorage::fake_input_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"); +td::Status TonlibClient::do_request(tonlib_api::raw_getTransactions& request, + td::Promise>&& promise) { + if (!request.account_address_) { + return TonlibError::EmptyField("account_address"); } - TRY_RESULT(input_key, from_tonlib(*request.private_key_)); - 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 = 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))); + if (!request.from_transaction_id_) { + return TonlibError::EmptyField("from_transaction_id"); + } + TRY_RESULT(account_address, get_account_address(request.account_address_->account_address_)); + td::optional private_key; + if (request.private_key_) { + TRY_RESULT(input_key, from_tonlib(*request.private_key_)); + //NB: options has lot of problems. We use emplace to migitate them + td::optional o_status; + //NB: rely on (and assert) that GetPrivateKey is a synchonous request + make_request(int_api::GetPrivateKey{std::move(input_key)}, [&](auto&& r_key) { + if (r_key.is_error()) { + o_status.emplace(r_key.move_as_error()); + return; + } + o_status.emplace(td::Status::OK()); + private_key = td::Ed25519::PrivateKey(std::move(r_key.move_as_ok().private_key)); + }); + TRY_STATUS(o_status.unwrap()); + } + auto lt = request.from_transaction_id_->lt_; + auto hash_str = request.from_transaction_id_->hash_; + if (hash_str.size() != 32) { + return td::Status::Error(400, "Invalid transaction id hash size"); + } + td::Bits256 hash; + hash.as_slice().copy_from(hash_str); + + 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.wrap([private_key = std::move(private_key)](auto&& x) mutable { + return ToRawTransactions(std::move(private_key)).to_raw_transactions(std::move(x)); + })); return td::Status::OK(); } -td::Status TonlibClient::do_request(const tonlib_api::testWallet_sendGrams& request, - td::Promise>&& promise) { - if (!request.destination_) { - return TonlibError::EmptyField("destination"); - } - if (!request.private_key_) { - return TonlibError::EmptyField("private_key"); - } - 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 = 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)); - td::Ref init_state; - if (request.seqno_ == 0) { - TRY_RESULT_PREFIX(public_key, private_key.get_public_key(), TonlibError::Internal()); - init_state = ton::TestWallet::get_init_state(public_key); - } - 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(); - 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, - td::Promise>&& promise) { +td::Status TonlibClient::do_request(const tonlib_api::getAccountState& 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.wrap([](auto&& res) { return res->to_testWallet_accountState(); })); - return td::Status::OK(); -} - -// 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 = 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 = 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, - td::Promise>&& promise) { - if (!request.destination_) { - return TonlibError::EmptyField("destination"); - } - if (!request.private_key_) { - return TonlibError::EmptyField("private_key"); - } - 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 = 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)); - td::Ref init_state; - if (request.seqno_ == 0) { - TRY_RESULT_PREFIX(public_key, private_key.get_public_key(), TonlibError::Internal()); - init_state = ton::Wallet::get_init_state(public_key); - } - 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(); - 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, - 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.wrap([](auto&& res) { return res->to_wallet_accountState(); })); - return td::Status::OK(); -} - -// 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() > ton::TestGiver::max_message_size) { - return TonlibError::MessageTooLong(); - } - TRY_RESULT(account_address, get_account_address(request.destination_->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(); - 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) { - make_request(int_api::GetAccountState{ton::TestGiver::address()}, - promise.wrap([](auto&& res) { return res->to_testGiver_accountState(); })); - return td::Status::OK(); -} - -td::Status TonlibClient::do_request(const tonlib_api::generic_getAccountState& 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.wrap([](auto&& res) { return res->to_generic_accountState(); })); + make_request(int_api::GetAccountState{std::move(account_address), query_context_.block_id.copy()}, + promise.wrap([](auto&& res) { return res->to_fullAccountState(); })); return td::Status::OK(); } class GenericCreateSendGrams : public TonlibQueryActor { public: - 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)) { + GenericCreateSendGrams(td::actor::ActorShared client, tonlib_api::createQuery query, + td::optional block_id, td::Promise>&& promise) + : TonlibQueryActor(std::move(client)) + , query_(std::move(query)) + , promise_(std::move(promise)) + , block_id_(std::move(block_id)) { } private: - tonlib_api::generic_createSendGramsQuery send_grams_; + tonlib_api::createQuery query_; td::Promise> promise_; td::unique_ptr source_; - td::unique_ptr destination_; + std::vector> destinations_; + size_t destinations_left_ = 0; bool has_private_key_{false}; bool is_fake_key_{false}; td::optional private_key_; td::optional public_key_; + td::optional block_id_; + + struct Action { + block::StdAddress destination; + td::int64 amount; + bool should_encrypt; + std::string message; + }; + bool allow_send_to_uninited_{false}; + std::vector actions_; + + // We combine compelty different actions in one actor + // Should be splitted eventually + std::vector dns_actions_; void check(td::Status status) { if (status.is_error()) { @@ -1826,40 +1909,150 @@ class GenericCreateSendGrams : public TonlibQueryActor { check(TonlibError::Cancelled()); } - td::Status do_start_up() { - if (send_grams_.timeout_ < 0 || send_grams_.timeout_ > 300) { - return TonlibError::InvalidField("timeout", "must be between 0 and 300"); + td::Result to_action(const tonlib_api::msg_message& message) { + if (!message.destination_) { + return TonlibError::EmptyField("message.destination"); } - if (!send_grams_.destination_) { - return TonlibError::EmptyField("destination"); - } - if (!send_grams_.source_) { - return TonlibError::EmptyField("source"); - } - if (send_grams_.amount_ < 0) { + Action res; + TRY_RESULT(destination, get_account_address(message.destination_->account_address_)); + res.destination = destination; + if (message.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_)); + res.amount = message.amount_; + auto status = + downcast_call2(*message.data_, td::overloaded( + [&](tonlib_api::msg_dataText& text) { + // Use this limit as a preventive check + if (text.text_.size() > ton::Wallet::max_message_size) { + return TonlibError::MessageTooLong(); + } + res.message = text.text_; + res.should_encrypt = false; + return td::Status::OK(); + }, + [&](tonlib_api::msg_dataEncryptedText& text) { + // Use this limit as a preventive check + if (text.text_.size() > ton::Wallet::max_message_size) { + return TonlibError::MessageTooLong(); + } + res.message = text.text_; + if (!has_private_key_) { + return TonlibError::EmptyField("input_key"); + } + res.should_encrypt = true; + return td::Status::OK(); + })); + TRY_STATUS(std::move(status)); + return res; + } - has_private_key_ = bool(send_grams_.private_key_); + td::Result to_dns_entry_data(tonlib_api::dns_EntryData& entry_data) { + using R = td::Result; + return downcast_call2( + entry_data, + td::overloaded( + [&](tonlib_api::dns_entryDataUnknown& unknown) -> R { return ton::ManualDns::EntryData(); }, + [&](tonlib_api::dns_entryDataNextResolver& next_resolver) -> R { + if (!next_resolver.resolver_) { + return TonlibError::EmptyField("resolver"); + } + TRY_RESULT(resolver, get_account_address(next_resolver.resolver_->account_address_)); + return ton::ManualDns::EntryData::next_resolver(std::move(resolver)); + }, + [&](tonlib_api::dns_entryDataSmcAddress& smc_address) -> R { + if (!smc_address.smc_address_) { + return TonlibError::EmptyField("smc_address"); + } + TRY_RESULT(address, get_account_address(smc_address.smc_address_->account_address_)); + return ton::ManualDns::EntryData::smc_address(std::move(address)); + }, + [&](tonlib_api::dns_entryDataText& text) -> R { return ton::ManualDns::EntryData::text(text.text_); })); + } + + td::Result to_dns_action(tonlib_api::dns_Action& action) { + using R = td::Result; + return downcast_call2(action, + td::overloaded( + [&](tonlib_api::dns_actionDeleteAll& del_all) -> R { + return ton::ManualDns::Action{"", 0, {}}; + }, + [&](tonlib_api::dns_actionDelete& del) -> R { + TRY_RESULT(category, td::narrow_cast_safe(del.category_)); + return ton::ManualDns::Action{del.name_, category, {}}; + }, + [&](tonlib_api::dns_actionSet& set) -> R { + if (!set.entry_) { + return TonlibError::EmptyField("entry"); + } + if (!set.entry_->entry_) { + return TonlibError::EmptyField("entry.entry"); + } + TRY_RESULT(category, td::narrow_cast_safe(set.entry_->category_)); + TRY_RESULT(entry_data, to_dns_entry_data(*set.entry_->entry_)); + TRY_RESULT(data_cell, entry_data.as_cell()); + return ton::ManualDns::Action{set.entry_->name_, category, std::move(data_cell)}; + })); + } + + td::Status parse_action(tonlib_api::Action& action) { + return downcast_call2(action, + td::overloaded([&](tonlib_api::actionNoop& cell) { return td::Status::OK(); }, + [&](tonlib_api::actionMsg& cell) { + allow_send_to_uninited_ = cell.allow_send_to_uninited_; + for (auto& from_action : cell.messages_) { + if (!from_action) { + return TonlibError::EmptyField("message"); + } + TRY_RESULT(action, to_action(*from_action)); + actions_.push_back(std::move(action)); + } + return td::Status::OK(); + }, + [&](tonlib_api::actionDns& cell) { + for (auto& from_action : cell.actions_) { + if (!from_action) { + return TonlibError::EmptyField("action"); + } + TRY_RESULT(action, to_dns_action(*from_action)); + dns_actions_.push_back(std::move(action)); + } + return td::Status::OK(); + })); + } + + td::Status do_start_up() { + if (query_.timeout_ < 0 || query_.timeout_ > 300) { + return TonlibError::InvalidField("timeout", "must be between 0 and 300"); + } + if (!query_.address_) { + return TonlibError::EmptyField("address"); + } + if (!query_.action_) { + return TonlibError::EmptyField("action"); + } + + TRY_RESULT(source_address, get_account_address(query_.address_->account_address_)); + + has_private_key_ = bool(query_.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; + TRY_RESULT(input_key, from_tonlib(*query_.private_key_)); + is_fake_key_ = query_.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)); } + TRY_STATUS(parse_action(*query_.action_)); - send_query(int_api::GetAccountState{source_address}, + send_query(int_api::GetAccountState{source_address, block_id_.copy()}, 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)); + destinations_.resize(actions_.size()); + destinations_left_ = destinations_.size(); + for (size_t i = 0; i < actions_.size(); i++) { + send_query(int_api::GetAccountState{actions_[i].destination, block_id_.copy()}, + promise_send_closure(actor_id(this), &GenericCreateSendGrams::on_destination_state, i)); + } return do_loop(); } @@ -1893,109 +2086,154 @@ class GenericCreateSendGrams : public TonlibQueryActor { return do_loop(); } - void on_destination_state(td::Result> r_state) { - check(do_on_destination_state(std::move(r_state))); + void on_destination_state(size_t i, td::Result> state) { + check(do_on_destination_state(i, std::move(state))); } - td::Status do_on_destination_state(td::Result> r_state) { + td::Status do_on_destination_state(size_t i, td::Result> r_state) { TRY_RESULT(state, std::move(r_state)); - destination_ = std::move(state); - if (destination_->is_frozen()) { + CHECK(destinations_left_ > 0); + destinations_left_--; + destinations_[i] = std::move(state); + auto& destination = *destinations_[i]; + if (destination.is_frozen()) { //FIXME: after restoration of frozen accounts will be supported return TonlibError::TransferToFrozen(); } - if (destination_->get_wallet_type() == AccountState::Empty && destination_->get_address().bounceable) { - if (!send_grams_.allow_send_to_uninited_) { + if (destination.get_wallet_type() == AccountState::Empty && destination.get_address().bounceable) { + if (!allow_send_to_uninited_) { return TonlibError::DangerousTransaction("Transfer to uninited wallet"); } - destination_->make_non_bounceable(); + destination.make_non_bounceable(); LOG(INFO) << "Change destination address from bounceable to non-bounceable "; } return do_loop(); } + td::Status do_dns_loop() { + if (!private_key_) { + return TonlibError::EmptyField("private_key"); + } + + Query::Raw raw; + auto valid_until = source_->get_sync_time(); + valid_until += query_.timeout_ == 0 ? 60 : query_.timeout_; + raw.valid_until = valid_until; + auto dns = ton::ManualDns::create(source_->get_smc_state()); + if (dns_actions_.empty()) { + TRY_RESULT(message_body, dns->create_init_query(private_key_.value(), valid_until)); + raw.message_body = std::move(message_body); + } else { + TRY_RESULT(message_body, dns->create_update_query(private_key_.value(), dns_actions_, valid_until)); + raw.message_body = std::move(message_body); + } + 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.destinations = std::move(destinations_); + promise_.set_value(td::make_unique(std::move(raw))); + stop(); + return td::Status::OK(); + } + td::Status do_loop() { - if (!source_ || !destination_) { + if (!source_ || destinations_left_ != 0) { return td::Status::OK(); } if (has_private_key_ && !private_key_) { return td::Status::OK(); } - Query::Raw raw; + if (source_->get_wallet_type() == AccountState::ManualDns) { + return do_dns_loop(); + } - 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()); + default: 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; - } - case AccountState::WalletV3: { - if (!private_key_) { - return TonlibError::EmptyField("private_key"); - } - if (message.size() > ton::WalletV3::max_message_size) { - return TonlibError::MessageTooLong(); - } - auto wallet = ton::WalletV3(source_->get_smc_state()); - TRY_RESULT(seqno, wallet.get_seqno()); - TRY_RESULT(wallet_id, wallet.get_wallet_id()); - 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::WalletV3::make_a_gift_message(private_key_.unwrap(), wallet_id, 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_); + if (!source_->is_wallet()) { + return TonlibError::AccountActionUnsupported("wallet action"); + } - promise_.set_value(td::make_unique(std::move(raw))); - stop(); - return td::Status::OK(); + td::int64 amount = 0; + for (auto& action : actions_) { + amount += action.amount; + } + + if (amount > source_->get_balance()) { + return TonlibError::NotEnoughFunds(); + } + + auto valid_until = source_->get_sync_time(); + valid_until += query_.timeout_ == 0 ? 60 : query_.timeout_; + std::vector gifts; + size_t i = 0; + for (auto& action : actions_) { + ton::HighloadWalletV2::Gift gift; + auto& destination = destinations_[i]; + gift.destination = destinations_[i]->get_address(); + gift.gramms = action.amount; + if (action.amount == source_->get_balance()) { + gift.gramms = -1; + } + if (action.should_encrypt) { + LOG(ERROR) << "TRY ENCRYPT"; + if (!private_key_) { + return TonlibError::EmptyField("private_key"); + } + if (!destination->is_wallet()) { + return TonlibError::AccountActionUnsupported("Get public key (in destination)"); + } + auto wallet = destination->get_wallet(); + TRY_RESULT_PREFIX(public_key, wallet->get_public_key(), + TonlibError::AccountActionUnsupported("Get public key (in destination)")); + TRY_RESULT_PREFIX(encrypted_message, + SimpleEncryption::encrypt_data(action.message, public_key, private_key_.value()), + TonlibError::Internal()); + gift.message = encrypted_message.as_slice().str(); + gift.is_encrypted = true; + } else { + gift.message = action.message; + gift.is_encrypted = false; + } + i++; + gifts.push_back(gift); + } + + Query::Raw raw; + auto with_wallet = [&](auto&& wallet) { + if (!private_key_) { + return TonlibError::EmptyField("private_key"); + } + if (wallet.get_max_gifts_size() < gifts.size()) { + return TonlibError::MessageTooLong(); // TODO: other error + } + + raw.valid_until = valid_until; + TRY_RESULT(message_body, wallet.make_a_gift_message(private_key_.unwrap(), valid_until, gifts)); + raw.message_body = std::move(message_body); + 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.destinations = std::move(destinations_); + + promise_.set_value(td::make_unique(std::move(raw))); + stop(); + return td::Status::OK(); + }; + + if (source_->get_wallet_type() == AccountState::Giver) { + valid_until = 0; + private_key_ = td::Ed25519::PrivateKey(td::SecureString(std::string('\0', 32))); + } + + return with_wallet(*source_->get_wallet()); } }; @@ -2020,20 +2258,12 @@ void TonlibClient::finish_create_query(td::Result> 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::Status TonlibClient::do_request(tonlib_api::createQuery& request, td::Promise>&& promise) { auto id = actor_id_++; actors_[id] = td::actor::create_actor( - "GenericSendGrams", actor_shared(this, id), std::move(request), + "GenericSendGrams", actor_shared(this, id), std::move(request), query_context_.block_id.copy(), promise.send_closure(actor_id(this), &TonlibClient::finish_create_query)); return td::Status::OK(); } @@ -2056,7 +2286,7 @@ td::Status TonlibClient::do_request(const tonlib_api::raw_createQuery& request, td::Promise> new_promise = promise.send_closure(actor_id(this), &TonlibClient::finish_create_query); - make_request(int_api::GetAccountState{account_address}, + make_request(int_api::GetAccountState{account_address, query_context_.block_id.copy()}, new_promise.wrap([smc_state = std::move(smc_state), body = std::move(body)](auto&& source) mutable { Query::Raw raw; if (smc_state) { @@ -2067,24 +2297,11 @@ td::Status TonlibClient::do_request(const tonlib_api::raw_createQuery& request, 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) { - auto id = actor_id_++; - 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(const tonlib_api::query_getInfo& request, td::Promise>&& promise) { promise.set_result(get_query_info(request.id_)); @@ -2101,8 +2318,8 @@ void TonlibClient::query_estimate_fees(td::int64 id, bool ignore_chksig, td::Res 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())); + promise.set_value(tonlib_api::make_object( + fees.first.to_tonlib_api(), td::transform(fees.second, [](auto& x) { return x.to_tonlib_api(); }))); } td::Status TonlibClient::do_request(const tonlib_api::query_estimateFees& request, @@ -2172,7 +2389,7 @@ td::Status TonlibClient::do_request(const tonlib_api::smc_load& request, 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)}, + make_request(int_api::GetAccountState{std::move(account_address), query_context_.block_id.copy()}, promise.send_closure(actor_id(this), &TonlibClient::finish_load_smc)); return td::Status::OK(); } @@ -2335,8 +2552,70 @@ td::Status TonlibClient::do_request(const tonlib_api::smc_runGetMethod& request, 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))); +td::Result> to_tonlib_api( + const ton::ManualDns::EntryData& entry_data) { + td::Result> res; + if (entry_data.data.empty()) { + return TonlibError::Internal("Unexpected empty EntryData"); + } + entry_data.data.visit(td::overloaded( + [&](const ton::ManualDns::EntryDataText& text) { + res = tonlib_api::make_object(text.text); + }, + [&](const ton::ManualDns::EntryDataNextResolver& resolver) { + res = tonlib_api::make_object( + tonlib_api::make_object(resolver.resolver.rserialize(true))); + }, + [&](const ton::ManualDns::EntryDataAdnlAddress& adnl_address) { res = td::Status::Error("TODO"); }, + [&](const ton::ManualDns::EntryDataSmcAddress& smc_address) { + res = tonlib_api::make_object( + tonlib_api::make_object(smc_address.smc_address.rserialize(true))); + })); + return res; +} + +void TonlibClient::finish_dns_resolve(std::string name, td::int32 category, td::unique_ptr smc, + td::Promise>&& promise) { + if (smc->get_wallet_type() != AccountState::WalletType::ManualDns) { + return promise.set_error(TonlibError::AccountTypeUnexpected("ManualDns")); + } + auto dns = ton::ManualDns::create(smc->get_smc_state()); + TRY_RESULT_PROMISE(promise, entries, dns->resolve(name, category)); + + std::vector> api_entries; + for (auto& entry : entries) { + TRY_RESULT_PROMISE(promise, entry_data, to_tonlib_api(entry.data)); + api_entries.push_back( + tonlib_api::make_object(entry.name, entry.category, std::move(entry_data))); + } + promise.set_value(tonlib_api::make_object(std::move(api_entries))); +} + +void TonlibClient::do_dns_request(std::string name, td::int32 category, block::StdAddress address, + td::Promise>&& promise) { + make_request(int_api::GetAccountState{address, query_context_.block_id.copy()}, + promise.send_closure(actor_id(this), &TonlibClient::finish_dns_resolve, name, category)); +} + +td::Status TonlibClient::do_request(const tonlib_api::dns_resolve& request, + td::Promise>&& promise) { + if (!request.account_address_) { + make_request(int_api::GetDnsResolver{}, + promise.send_closure(actor_id(this), &TonlibClient::do_dns_request, request.name_, request.category_)); + return td::Status::OK(); + } + TRY_RESULT(account_address, get_account_address(request.account_address_->account_address_)); + do_dns_request(request.name_, request.category_, account_address, std::move(promise)); + return td::Status::OK(); +} + +td::Status TonlibClient::do_request(tonlib_api::sync& request, + td::Promise>&& promise) { + // ton.blockIdExt workchain:int32 shard:int64 seqno:int32 root_hash:bytes file_hash:bytes = ton.BlockIdExt; + client_.with_last_block( + std::move(promise).wrap([](auto last_block) -> td::Result> { + return to_tonlib_api(last_block.last_block_id); + })); return td::Status::OK(); } @@ -2569,7 +2848,8 @@ 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), + "GetAccountState", client_.get_client(), request.address, std::move(request.block_id), + actor_shared(this, actor_id), promise.wrap([address = request.address, wallet_id = wallet_id_](auto&& state) mutable { return td::make_unique(std::move(address), std::move(state), wallet_id); })); @@ -2582,6 +2862,15 @@ td::Status TonlibClient::do_request(int_api::GetPrivateKey request, td::Promise< return td::Status::OK(); } +td::Status TonlibClient::do_request(int_api::GetDnsResolver request, td::Promise&& promise) { + client_.with_last_config(promise.wrap([](auto&& state) mutable -> td::Result { + TRY_RESULT_PREFIX(addr, TRY_VM(state.config->get_dns_root_addr()), + TonlibError::Internal("get dns root addr from config: ")); + return block::StdAddress(ton::masterchainId, addr); + })); + 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))); @@ -2597,33 +2886,31 @@ td::Status TonlibClient::do_request(const tonlib_api::liteServer_getInfo& reques return td::Status::OK(); } +td::Status TonlibClient::do_request(tonlib_api::withBlock& request, + td::Promise>&& promise) { + if (!request.id_) { + return TonlibError::EmptyField("id"); + } + auto to_bits256 = [](td::Slice data, td::Slice name) -> td::Result { + if (data.size() != 32) { + return TonlibError::InvalidField(name, "wrong length (not 32 bytes)"); + } + return td::Bits256(data.ubegin()); + }; + TRY_RESULT(root_hash, to_bits256(request.id_->root_hash_, "root_hash")); + TRY_RESULT(file_hash, to_bits256(request.id_->file_hash_, "file_hash")); + ton::BlockIdExt block_id(request.id_->workchain_, request.id_->shard_, request.id_->seqno_, root_hash, file_hash); + make_any_request(*request.function_, {std::move(block_id)}, std::move(promise)); + 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::wallet_v3_getAccountAddress& request, P&&) { - UNREACHABLE(); - return TonlibError::Internal(); -} -template -td::Status TonlibClient::do_request(const tonlib_api::testGiver_getAccountAddress& request, P&&) { +td::Status TonlibClient::do_request(const tonlib_api::getAccountAddress& request, P&&) { UNREACHABLE(); return TonlibError::Internal(); } diff --git a/tonlib/tonlib/TonlibClient.h b/tonlib/tonlib/TonlibClient.h index f624440a..36560d38 100644 --- a/tonlib/tonlib/TonlibClient.h +++ b/tonlib/tonlib/TonlibClient.h @@ -14,7 +14,7 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -38,6 +38,7 @@ namespace tonlib { namespace int_api { struct GetAccountState; struct GetPrivateKey; +struct GetDnsResolver; struct SendMessage; inline std::string to_string(const int_api::SendMessage&) { return "Send message"; @@ -81,6 +82,10 @@ class TonlibClient : public td::actor::Actor { std::shared_ptr kv_; KeyStorage key_storage_; LastBlockStorage last_block_storage_; + struct QueryContext { + td::optional block_id; + }; + QueryContext query_context_; // network td::actor::ActorOwn raw_client_; @@ -128,11 +133,7 @@ class TonlibClient : public td::actor::Actor { return tonlib_api::make_object(400, "Function can't be executed synchronously"); } static object_ptr do_static_request(const tonlib_api::runTests& request); - static object_ptr do_static_request(const tonlib_api::raw_getAccountAddress& request); - static object_ptr do_static_request(const tonlib_api::testWallet_getAccountAddress& request); - static object_ptr do_static_request(const tonlib_api::wallet_getAccountAddress& request); - static object_ptr do_static_request(const tonlib_api::wallet_v3_getAccountAddress& request); - static object_ptr do_static_request(const tonlib_api::testGiver_getAccountAddress& request); + static object_ptr do_static_request(const tonlib_api::getAccountAddress& request); static object_ptr do_static_request(const tonlib_api::packAccountAddress& request); static object_ptr do_static_request(const tonlib_api::unpackAccountAddress& request); static object_ptr do_static_request(tonlib_api::getBip39Hints& request); @@ -153,15 +154,7 @@ class TonlibClient : public td::actor::Actor { 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::wallet_v3_getAccountAddress& request, P&&); - template - td::Status do_request(const tonlib_api::testGiver_getAccountAddress& request, P&&); + td::Status do_request(const tonlib_api::getAccountAddress& request, P&&); template td::Status do_request(const tonlib_api::packAccountAddress& request, P&&); template @@ -193,11 +186,15 @@ class TonlibClient : public td::actor::Actor { template td::Status do_request(const tonlib_api::kdf& request, P&&); + void make_any_request(tonlib_api::Function& function, QueryContext query_context, + td::Promise>&& promise); template void make_request(T&& request, P&& promise) { - auto status = do_request(std::forward(request), std::move(promise)); + td::Promise::ReturnType> new_promise = std::move(promise); + + auto status = do_request(std::forward(request), std::move(new_promise)); if (status.is_error()) { - promise.operator()(std::move(status)); + new_promise.operator()(std::move(status)); } } @@ -217,33 +214,14 @@ class TonlibClient : public td::actor::Actor { td::Promise>&& promise); td::Status do_request(tonlib_api::raw_getAccountState& request, - td::Promise>&& promise); + td::Promise>&& promise); td::Status do_request(tonlib_api::raw_getTransactions& request, td::Promise>&& promise); - td::Status do_request(const tonlib_api::testWallet_init& request, td::Promise>&& promise); - td::Status do_request(const tonlib_api::testWallet_sendGrams& request, - td::Promise>&& promise); - td::Status do_request(tonlib_api::testWallet_getAccountState& request, - td::Promise>&& promise); + td::Status do_request(const tonlib_api::getAccountState& request, + td::Promise>&& promise); - td::Status do_request(const tonlib_api::wallet_init& request, td::Promise>&& promise); - td::Status do_request(const tonlib_api::wallet_sendGrams& request, - td::Promise>&& promise); - td::Status do_request(tonlib_api::wallet_getAccountState& request, - td::Promise>&& promise); - - td::Status do_request(const tonlib_api::testGiver_getAccountState& request, - td::Promise>&& promise); - td::Status do_request(const tonlib_api::testGiver_sendGrams& request, - td::Promise>&& promise); - - td::Status do_request(const tonlib_api::generic_getAccountState& request, - td::Promise>&& promise); - td::Status do_request(tonlib_api::generic_sendGrams& request, - td::Promise>&& promise); - - td::Status do_request(tonlib_api::sync& request, td::Promise>&& promise); + td::Status do_request(tonlib_api::sync& request, td::Promise>&& promise); td::Status do_request(const tonlib_api::createNewKey& request, td::Promise>&& promise); td::Status do_request(const tonlib_api::exportKey& request, @@ -275,8 +253,6 @@ class TonlibClient : public td::actor::Actor { 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); @@ -287,8 +263,7 @@ class TonlibClient : public td::actor::Actor { 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::Status do_request(tonlib_api::createQuery& request, td::Promise>&& promise); td::int64 next_smc_id_{0}; std::map> smcs_; @@ -307,13 +282,23 @@ class TonlibClient : public td::actor::Actor { td::Status do_request(const tonlib_api::smc_runGetMethod& request, td::Promise>&& promise); + td::Status do_request(const tonlib_api::dns_resolve& request, + td::Promise>&& promise); + void do_dns_request(std::string name, td::int32 category, block::StdAddress address, + td::Promise>&& promise); + void finish_dns_resolve(std::string name, td::int32 category, td::unique_ptr smc, + 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::GetDnsResolver 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); + td::Status do_request(tonlib_api::withBlock& 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 dca5eecf..c26b3162 100644 --- a/tonlib/tonlib/TonlibError.h +++ b/tonlib/tonlib/TonlibError.h @@ -14,7 +14,7 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -39,6 +39,7 @@ // ACCOUNT_NOT_INITED // ACCOUNT_TYPE_UNKNOWN // ACCOUNT_TYPE_UNEXPECTED +// ACCOUNT_ACTION_UNSUPPORTED // VALIDATE_ACCOUNT_STATE // VALIDATE_TRANSACTION // VALIDATE_ZERO_STATE @@ -86,7 +87,7 @@ struct TonlibError { return td::Status::Error(400, "MESSAGE_TOO_LONG"); } static td::Status EmptyField(td::Slice field_name) { - return td::Status::Error(400, PSLICE() << "EMPTY_FIELD: Field " << field_name << " must not be emtpy"); + return td::Status::Error(400, PSLICE() << "EMPTY_FIELD: Field " << field_name << " must not be empty"); } static td::Status InvalidField(td::Slice field_name, td::Slice reason) { return td::Status::Error(400, PSLICE() << "INVALID_FIELD: Field " << field_name << " has invalid value " << reason); @@ -103,6 +104,9 @@ struct TonlibError { static td::Status AccountTypeUnexpected(td::Slice expected) { return td::Status::Error(400, PSLICE() << "ACCOUNT_TYPE_UNEXPECTED: not a " << expected); } + static td::Status AccountActionUnsupported(td::Slice action) { + return td::Status::Error(400, PSLICE() << "ACCOUNT_ACTION_UNSUPPORTED: " << action); + } static td::Status Internal() { return td::Status::Error(500, "INTERNAL"); } diff --git a/tonlib/tonlib/keys/SimpleEncryption.cpp b/tonlib/tonlib/keys/SimpleEncryption.cpp index 586070b0..bbf9d573 100644 --- a/tonlib/tonlib/keys/SimpleEncryption.cpp +++ b/tonlib/tonlib/keys/SimpleEncryption.cpp @@ -14,7 +14,7 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #include "SimpleEncryption.h" @@ -36,6 +36,7 @@ td::AesCbcState SimpleEncryption::calc_aes_cbc_state_sha512(td::Slice seed) { sha512(seed, hash.as_mutable_slice()); return calc_aes_cbc_state_hash(hash.as_slice()); } + td::SecureString SimpleEncryption::gen_random_prefix(td::int64 data_size) { td::SecureString buff(td::narrow_cast(((32 + 15 + data_size) & -16) - data_size), 0); td::Random::secure_bytes(buff.as_mutable_slice()); @@ -104,4 +105,46 @@ td::Result SimpleEncryption::decrypt_data(td::Slice encrypted_ return td::SecureString(decrypted_data.as_slice().substr(prefix_size)); } + +td::Result SimpleEncryption::encrypt_data(td::Slice data, const td::Ed25519::PublicKey &public_key) { + TRY_RESULT(tmp_private_key, td::Ed25519::generate_private_key()); + return encrypt_data(data, public_key, tmp_private_key); +} + +namespace { +td::SecureString secure_xor(td::Slice a, td::Slice b) { + CHECK(a.size() == b.size()); + td::SecureString res(a.size()); + for (size_t i = 0; i < res.size(); i++) { + res.as_mutable_slice()[i] = a[i] ^ b[i]; + } + return res; +} +} // namespace + +td::Result SimpleEncryption::encrypt_data(td::Slice data, const td::Ed25519::PublicKey &public_key, + const td::Ed25519::PrivateKey &private_key) { + TRY_RESULT(shared_secret, td::Ed25519::compute_shared_secret(public_key, private_key)); + auto encrypted = encrypt_data(data, shared_secret.as_slice()); + TRY_RESULT(tmp_public_key, private_key.get_public_key()); + td::SecureString prefixed_encrypted(tmp_public_key.LENGTH + encrypted.size()); + prefixed_encrypted.as_mutable_slice().copy_from(tmp_public_key.as_octet_string()); + auto xored_keys = secure_xor(public_key.as_octet_string().as_slice(), tmp_public_key.as_octet_string().as_slice()); + prefixed_encrypted.as_mutable_slice().copy_from(xored_keys.as_slice()); + prefixed_encrypted.as_mutable_slice().substr(xored_keys.size()).copy_from(encrypted); + return std::move(prefixed_encrypted); +} + +td::Result SimpleEncryption::decrypt_data(td::Slice data, + const td::Ed25519::PrivateKey &private_key) { + if (data.size() < td::Ed25519::PublicKey::LENGTH) { + return td::Status::Error("Failed to decrypte: data is too small"); + } + TRY_RESULT(public_key, private_key.get_public_key()); + auto tmp_public_key = + td::Ed25519::PublicKey(secure_xor(data.substr(0, td::Ed25519::PublicKey::LENGTH), public_key.as_octet_string())); + TRY_RESULT(shared_secret, td::Ed25519::compute_shared_secret(tmp_public_key, private_key)); + TRY_RESULT(decrypted, decrypt_data(data.substr(td::Ed25519::PublicKey::LENGTH), shared_secret.as_slice())); + return std::move(decrypted); +} } // namespace tonlib diff --git a/tonlib/tonlib/keys/SimpleEncryption.h b/tonlib/tonlib/keys/SimpleEncryption.h index 69d5629a..a3b0ae90 100644 --- a/tonlib/tonlib/keys/SimpleEncryption.h +++ b/tonlib/tonlib/keys/SimpleEncryption.h @@ -14,13 +14,14 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #pragma once #include "td/utils/crypto.h" #include "td/utils/Slice.h" #include "td/utils/SharedSlice.h" +#include "crypto/Ed25519.h" namespace tonlib { class SimpleEncryption { @@ -30,6 +31,11 @@ class SimpleEncryption { static td::SecureString combine_secrets(td::Slice a, td::Slice b); static td::SecureString kdf(td::Slice secret, td::Slice password, int iterations); + static td::Result encrypt_data(td::Slice data, const td::Ed25519::PublicKey &public_key); + static td::Result decrypt_data(td::Slice data, const td::Ed25519::PrivateKey &private_key); + static td::Result encrypt_data(td::Slice data, const td::Ed25519::PublicKey &public_key, + const td::Ed25519::PrivateKey &private_key); + private: static td::AesCbcState calc_aes_cbc_state_hash(td::Slice hash); static td::AesCbcState calc_aes_cbc_state_sha512(td::Slice seed); diff --git a/tonlib/tonlib/tonlib-cli.cpp b/tonlib/tonlib/tonlib-cli.cpp index fec9f86c..35a0b7e1 100644 --- a/tonlib/tonlib/tonlib-cli.cpp +++ b/tonlib/tonlib/tonlib-cli.cpp @@ -1,3 +1,30 @@ +/* + This file is part of TON Blockchain source code. + + TON Blockchain is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License + as published by the Free Software Foundation; either version 2 + of the License, or (at your option) any later version. + + TON Blockchain is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with TON Blockchain. If not, see . + + In addition, as a special exception, the copyright holders give permission + to link the code of portions of this program with the OpenSSL library. + You must obey the GNU General Public License in all respects for all + of the code used other than OpenSSL. If you modify file(s) with this + exception, you may extend this exception to your version of the file(s), + but you are not obligated to do so. If you do not wish to do so, delete this + exception statement from your version. If you delete this exception statement + from all source files in the program, then also delete it here. + + Copyright 2019-2020 Telegram Systems LLP +*/ #include "td/actor/actor.h" #include "td/utils/filesystem.h" @@ -16,62 +43,65 @@ #include "tonlib/ExtClientLazy.h" +#include "smc-envelope/ManualDns.h" + #include "auto/tl/tonlib_api.hpp" +#include #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 " -// "(StateInit) or just the code or data of specified account; is in " -// "[:] format\n" -// -// "runmethod ...\tRuns GET method of account " -// "with specified parameters\n" -// -// "getaccount []\tLoads the most recent state of specified account; is in " -// "[:] format\n" -// -// WONTSUPPORT -// -// UNSUPPORTED -//"last\tGet last block and state info from server\n" -//"status\tShow connection and local database status\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" +// GR$ +struct Grams { + td::uint64 nano; +}; + +td::StringBuilder& operator<<(td::StringBuilder& sb, const Grams& grams) { + auto b = grams.nano % 1000000000; + auto a = grams.nano / 1000000000; + sb << "GR$" << a; + if (b != 0) { + size_t sz = 9; + while (b % 10 == 0) { + sz--; + b /= 10; + } + sb << '.'; + [&](auto b_str) { + for (size_t i = b_str.size(); i < sz; i++) { + sb << '0'; + } + sb << b_str; + }(PSLICE() << b); + } + return sb; +} + +td::Result parse_grams(td::Slice grams) { + td::ConstParser parser(grams); + if (parser.skip_start_with("GR$")) { + TRY_RESULT(a, td::to_integer_safe(parser.read_till_nofail('.'))); + td::uint64 res = a; + if (parser.try_skip('.')) { + for (int i = 0; i < 9; i++) { + res *= 10; + if (parser.peek_char() >= '0' && parser.peek_char() <= '9') { + res += parser.peek_char() - '0'; + parser.advance(1); + } + } + } else { + res *= 1000000000; + } + if (!parser.empty()) { + return td::Status::Error(PSLICE() << "Failed to parse grams \"" << grams << "\", left \"" << parser.read_all() + << "\""); + } + return Grams{res}; + } + TRY_RESULT(value, td::to_integer_safe(grams)); + return Grams{value}; +} class TonlibCli : public td::actor::Actor { public: @@ -83,6 +113,7 @@ class TonlibCli : public td::actor::Actor { bool in_memory{false}; bool use_callbacks_for_network{false}; td::int32 wallet_version = 2; + td::int32 wallet_revision = 0; bool ignore_cache{false}; bool one_shot{false}; @@ -98,6 +129,8 @@ class TonlibCli : public td::actor::Actor { std::uint64_t next_query_id_{1}; td::Promise cont_; td::uint32 wallet_id_; + ton::tonlib_api::object_ptr current_block_; + enum class BlockMode { Auto, Manual } block_mode_ = BlockMode::Auto; struct KeyInfo { std::string public_key; @@ -189,13 +222,15 @@ class TonlibCli : public td::actor::Actor { [&](auto r_ok) { LOG_IF(ERROR, r_ok.is_error()) << r_ok.error(); if (r_ok.is_ok()) { - wallet_id_ = static_cast(r_ok.ok()->config_info_->default_wallet_id_); + if (r_ok.ok()->config_info_) { + wallet_id_ = static_cast(r_ok.ok()->config_info_->default_wallet_id_); + } td::TerminalIO::out() << "Tonlib is inited\n"; + if (options_.one_shot) { + td::actor::send_closure(actor_id(this), &TonlibCli::parse_line, td::BufferSlice(options_.cmd)); + } } }); - if (options_.one_shot) { - td::actor::send_closure(actor_id(this), &TonlibCli::parse_line, td::BufferSlice(options_.cmd)); - } } void hangup_shared() override { @@ -222,19 +257,6 @@ class TonlibCli : public td::actor::Actor { } } - void on_error() { - if (options_.one_shot) { - LOG(ERROR) << "FAILED"; - std::_Exit(1); - } - } - void on_ok() { - if (options_.one_shot) { - LOG(INFO) << "OK"; - std::_Exit(0); - } - } - void parse_line(td::BufferSlice line) { if (is_closing_) { return; @@ -259,11 +281,18 @@ class TonlibCli : public td::actor::Actor { return true; }; - td::Promise cmd_promise = [line = line.clone()](td::Result res) { + td::Promise cmd_promise = [line = line.clone(), one_shot = options_.one_shot](td::Result res) { if (res.is_ok()) { - // on_ok + if (one_shot) { + LOG(DEBUG) << "OK"; + std::_Exit(0); + } } else { td::TerminalIO::out() << "Query {" << line.as_slice() << "} FAILED: \n\t" << res.error() << "\n"; + if (one_shot) { + LOG(ERROR) << "FAILED"; + std::_Exit(1); + } } }; @@ -274,6 +303,23 @@ class TonlibCli : public td::actor::Actor { td::TerminalIO::out() << "sendfile \tLoad a serialized message from and send it to server\n"; td::TerminalIO::out() << "setconfig|validateconfig [] [] [] - set or validate " "lite server config\n"; + td::TerminalIO::out() << "runmethod ...\tRuns GET method of account " + " with specified parameters\n"; + td::TerminalIO::out() << "getstate \tget state of wallet with requested key\n"; + td::TerminalIO::out() << "getaddress \tget address of wallet with requested key\n"; + td::TerminalIO::out() << "dns resove \n"; + td::TerminalIO::out() << "dns cmd \n"; + //td::TerminalIO::out() << "dns cmdlist {\\n} end\n"; + td::TerminalIO::out() << "dns cmdfile \n"; + td::TerminalIO::out() << "\t = set | delete.name | delete.all\n"; + td::TerminalIO::out() << "\t = DELETED | EMPTY | TEXT:\n"; + + td::TerminalIO::out() + << "blockmode auto|manual\tWith auto mode, all queries will be executed with respect to the latest block. " + "With manual mode, user must update current block explicitly: with last or setblock\n"; + td::TerminalIO::out() << "last\tUpdate current block to the most recent one\n"; + td::TerminalIO::out() << "setblock \tSet current block\n"; + td::TerminalIO::out() << "exit\tExit\n"; td::TerminalIO::out() << "quit\tExit\n"; td::TerminalIO::out() @@ -284,10 +330,10 @@ class TonlibCli : public td::actor::Actor { td::TerminalIO::out() << "unpackaddress
    - validate and parse address\n"; td::TerminalIO::out() << "setbounceble
    [] - change bounceble flag in address\n"; td::TerminalIO::out() << "importkey - import key\n"; + td::TerminalIO::out() << "importkeypem - 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() << "getstate - get state of simple wallet with requested key\n"; td::TerminalIO::out() << "gethistory - get history fo simple wallet with requested key (last 10 transactions)\n"; td::TerminalIO::out() << "init - init simple wallet with requested key\n"; @@ -312,18 +358,6 @@ class TonlibCli : public td::actor::Actor { export_key(cmd.str(), parser.read_word()); } else if (cmd == "importkey") { import_key(parser.read_all()); - } else if (cmd == "getstate") { - get_state(parser.read_word()); - } else if (cmd == "gethistory") { - get_history(parser.read_word()); - } else if (cmd == "init") { - init_simple_wallet(parser.read_word()); - } else if (cmd == "transfer" || cmd == "transferf") { - auto from = parser.read_word(); - auto to = parser.read_word(); - auto grams = parser.read_word(); - auto message = parser.read_word(); - transfer(from, to, grams, message, cmd == "transferf"); } else if (cmd == "hint") { get_hints(parser.read_word()); } else if (cmd == "unpackaddress") { @@ -335,8 +369,10 @@ class TonlibCli : public td::actor::Actor { } else if (cmd == "netstats") { dump_netstats(); // reviewed from here - } else if (cmd == "sync") { - sync(std::move(cmd_promise)); + } else if (cmd == "blockmode") { + set_block_mode(parser.read_word(), std::move(cmd_promise)); + } else if (cmd == "sync" || cmd == "last") { + sync(std::move(cmd_promise), cmd == "last"); } else if (cmd == "time") { remote_time(std::move(cmd_promise)); } else if (cmd == "remote-version") { @@ -355,6 +391,22 @@ class TonlibCli : public td::actor::Actor { auto use_callback = parser.read_word(); auto force = parser.read_word(); set_validate_config(cmd, config, name, to_bool(use_callback), to_bool(force), std::move(cmd_promise)); + } else if (td::begins_with(cmd, "transfer") || cmd == "init") { + // transfer[f][F] + // f - force + // F from file - SEND
    + // use @empty for empty message + transfer(parser, cmd, std::move(cmd_promise)); + } else if (cmd == "getstate") { + get_state(parser.read_word(), std::move(cmd_promise)); + } else if (cmd == "getaddress") { + get_address(parser.read_word(), std::move(cmd_promise)); + } else if (cmd == "importkeypem") { + import_key_pem(parser.read_word(), std::move(cmd_promise)); + } else if (cmd == "dns") { + run_dns_cmd(parser, std::move(cmd_promise)); + } else if (cmd == "gethistory") { + get_history(parser.read_word(), std::move(cmd_promise)); } else { cmd_promise.set_error(td::Status::Error(PSLICE() << "Unkwnown query `" << cmd << "`")); } @@ -363,6 +415,112 @@ class TonlibCli : public td::actor::Actor { } } + void run_dns_cmd(td::ConstParser& parser, td::Promise promise) { + auto cmd = parser.read_word(); + if (cmd == "cmd" || cmd == "cmdlist" || cmd == "cmdfile") { + return dns_cmd(cmd, parser, std::move(promise)); + } + if (cmd == "resolve") { + return dns_resolve(parser, std::move(promise)); + } + promise.set_error(td::Status::Error("Unknown cmd")); + } + + void do_dns_resolve(std::string name, td::int16 category, td::int32 ttl, + tonlib_api::object_ptr resolved, td::Promise promise) { + if (resolved->entries_.empty()) { + td::TerminalIO::out() << "No dns entries found\n"; + promise.set_value(td::Unit()); + return; + } + if (resolved->entries_[0]->name_ == name) { + td::TerminalIO::out() << "Done: " << to_string(resolved); + promise.set_value(td::Unit()); + return; + } + if (resolved->entries_[0]->entry_->get_id() == tonlib_api::dns_entryDataNextResolver::ID) { + auto entry = tonlib_api::move_object_as(resolved->entries_[0]->entry_); + send_query(tonlib_api::make_object(std::move(entry->resolver_), name, category), + promise.send_closure(actor_id(this), &TonlibCli::do_dns_resolve, name, category, ttl)); + } + promise.set_error(td::Status::Error("Failed to resolve")); + } + + void dns_resolve(td::ConstParser& parser, td::Promise promise) { + auto key_id = parser.read_word(); + TRY_RESULT_PROMISE(promise, address, to_account_address(key_id, false)); + auto name = parser.read_word(); + auto category_str = parser.read_word(); + TRY_RESULT_PROMISE(promise, category, td::to_integer_safe(category_str)); + + std::vector> entries; + entries.push_back(tonlib_api::make_object( + "", -1, tonlib_api::make_object(std::move(address.address)))); + do_dns_resolve(name.str(), category, 10, tonlib_api::make_object(std::move(entries)), + std::move(promise)); + } + void dns_cmd(td::Slice cmd, td::ConstParser& parser, td::Promise promise) { + auto key_id = parser.read_word(); + TRY_RESULT_PROMISE(promise, address, to_account_address(key_id, true)); + + std::vector actions_ext; + if (cmd == "cmd") { + TRY_RESULT_PROMISE_ASSIGN(promise, actions_ext, ton::ManualDns::parse(parser.read_all())); + } else if (cmd == "cmdfile") { + TRY_RESULT_PROMISE(promise, file_data, td::read_file(parser.read_word().str())); + TRY_RESULT_PROMISE_ASSIGN(promise, actions_ext, ton::ManualDns::parse(file_data)); + } + + std::vector> actions; + for (auto& action : actions_ext) { + if (action.name.empty()) { + actions.push_back(tonlib_api::make_object()); + td::TerminalIO::out() << "Delete all dns entries\n"; + } else if (action.category == 0) { + actions.push_back(tonlib_api::make_object(action.name, 0)); + td::TerminalIO::out() << "Delete all dns enties with name: " << action.name << "\n"; + } else if (!action.data) { + actions.push_back(tonlib_api::make_object(action.name, action.category)); + td::TerminalIO::out() << "Delete all dns enties with name and category: " << action.name << ":" + << action.category << "\n"; + } else { + tonlib_api::object_ptr data; + td::StringBuilder sb; + + td::Status error; + if (action.data.value().data.empty()) { + TRY_STATUS_PROMISE(promise, td::Status::Error("Empty entry data is not supported")); + } + action.data.value().data.visit(td::overloaded( + [&](const ton::ManualDns::EntryDataText& text) { + data = tonlib_api::make_object(text.text); + sb << "TEXT:" << text.text; + }, + [&](const ton::ManualDns::EntryDataNextResolver& resolver) { error = td::Status::Error("TODO"); }, + [&](const ton::ManualDns::EntryDataAdnlAddress& adnl_address) { error = td::Status::Error("TODO"); }, + [&](const ton::ManualDns::EntryDataSmcAddress& text) { error = td::Status::Error("TODO"); })); + ; + TRY_STATUS_PROMISE(promise, std::move(error)); + td::TerminalIO::out() << "Set dns entry: " << action.name << ":" << action.category << " " << sb.as_cslice() + << "\n"; + actions.push_back(tonlib_api::make_object( + tonlib_api::make_object(action.name, action.category, std::move(data)))); + } + } + + auto action = tonlib_api::make_object(std::move(actions)); + + td::Slice password; // empty by default + using tonlib_api::make_object; + auto key = !address.secret.empty() ? make_object( + make_object(address.public_key, address.secret.copy()), + td::SecureString(password)) + : nullptr; + send_query(tonlib_api::make_object(std::move(key), std::move(address.address), 60, + std::move(action)), + promise.send_closure(actor_id(this), &TonlibCli::transfer2)); + } + 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"; @@ -421,13 +579,29 @@ class TonlibCli : public td::actor::Actor { promise.set_value(td::Unit()); } - void sync(td::Promise promise) { + void sync(td::Promise promise, bool update_last) { using tonlib_api::make_object; - send_query(make_object(), promise.wrap([](auto&&) { + send_query(make_object(), promise.wrap([&, update_last](auto&& block) { td::TerminalIO::out() << "synchronized\n"; + td::TerminalIO::out() << to_string(block) << "\n"; + if (update_last) { + current_block_ = std::move(block); + td::TerminalIO::out() << "Update current block\n"; + } return td::Unit(); })); } + void set_block_mode(td::Slice mode, td::Promise promise) { + if (mode == "auto") { + block_mode_ = BlockMode::Auto; + promise.set_value(td::Unit()); + } else if (mode == "manual") { + block_mode_ = BlockMode::Manual; + promise.set_value(td::Unit()); + } else { + promise.set_error(td::Status::Error("Invalid block mode")); + } + } td::Result> parse_stack_entry(td::Slice str) { if (str.empty() || str.size() > 65535) { return td::Status::Error("String is or empty or too big"); @@ -691,8 +865,20 @@ class TonlibCli : public td::actor::Actor { if (is_closing_) { return; } + tonlib_api::object_ptr func = std::move(query); + if (block_mode_ == BlockMode::Manual && func->get_id() != tonlib_api::sync::ID) { + if (!current_block_) { + promise.set_error(td::Status::Error("empty current block")); + return; + } + func = tonlib_api::make_object( + tonlib_api::make_object(current_block_->workchain_, current_block_->shard_, + current_block_->seqno_, current_block_->root_hash_, + current_block_->file_hash_), + std::move(func)); + } auto query_id = next_query_id_++; - td::actor::send_closure(client_, &tonlib::TonlibClient::request, query_id, std::move(query)); + td::actor::send_closure(client_, &tonlib::TonlibClient::request, query_id, std::move(func)); query_handlers_[query_id] = [promise = std::move(promise)](td::Result> r_obj) mutable { if (r_obj.is_error()) { @@ -808,7 +994,6 @@ class TonlibCli : public td::actor::Actor { KeyInfo info; info.public_key = public_key; info.secret = r_secret.move_as_ok(); - LOG(INFO) << info.public_key; keys_.push_back(std::move(info)); } @@ -900,22 +1085,39 @@ class TonlibCli : public td::actor::Actor { if (key.empty()) { return td::Status::Error("account address is empty"); } + if (key == "none" && !need_private_key) { + return Address{}; + } auto r_key_i = to_key_i(key); using tonlib_api::make_object; if (r_key_i.is_ok()) { - auto obj = [&](td::int32 version) { + auto obj = [&](td::int32 version, td::int32 revision) { + auto do_request = [revision](auto x) { + return tonlib::TonlibClient::static_request( + make_object(std::move(x), revision)); + }; if (version == 1) { - return tonlib::TonlibClient::static_request(make_object( - make_object(keys_[r_key_i.ok()].public_key))); + return do_request(make_object(keys_[r_key_i.ok()].public_key)); } if (version == 2) { - return tonlib::TonlibClient::static_request(make_object( - make_object(keys_[r_key_i.ok()].public_key))); + return do_request(make_object(keys_[r_key_i.ok()].public_key)); } - return tonlib::TonlibClient::static_request(make_object( - make_object(keys_[r_key_i.ok()].public_key, wallet_id_))); + if (version == 4) { + return do_request(make_object( + keys_[r_key_i.ok()].public_key, wallet_id_)); + } + if (version == 5) { + return do_request(make_object( + keys_[r_key_i.ok()].public_key, wallet_id_)); + } + if (version == 6) { + return do_request( + make_object(keys_[r_key_i.ok()].public_key, wallet_id_)); + } + return do_request( + make_object(keys_[r_key_i.ok()].public_key, wallet_id_)); UNREACHABLE(); - }(options_.wallet_version); + }(options_.wallet_version, options_.wallet_revision); if (obj->get_id() != tonlib_api::error::ID) { Address res; res.address = ton::move_tl_object_as(obj); @@ -925,7 +1127,8 @@ class TonlibCli : public td::actor::Actor { } } if (key == "giver") { - auto obj = tonlib::TonlibClient::static_request(make_object()); + auto obj = tonlib::TonlibClient::static_request( + make_object(make_object(), 0)); if (obj->get_id() != tonlib_api::error::ID) { Address res; res.address = ton::move_tl_object_as(obj); @@ -983,6 +1186,23 @@ class TonlibCli : public td::actor::Actor { cont_ = [this, cmd, key = key.str(), key_i](td::Slice password) { this->export_key(cmd, key, key_i, password); }; } + void import_key_pem(td::Slice filename, td::Promise promise) { + TRY_RESULT_PROMISE(promise, data, td::read_file_secure(filename.str())); + using tonlib_api::make_object; + send_query(make_object(td::SecureString(), td::SecureString("cucumber"), + make_object(std::move(data))), + promise.wrap([&](auto&& key) { + LOG(ERROR) << to_string(key); + KeyInfo info; + info.public_key = key->public_key_; + info.secret = std::move(key->secret_); + keys_.push_back(std::move(info)); + export_key("exportkey", key->public_key_, keys_.size() - 1, td::SecureString()); + store_keys(); + return td::Unit(); + })); + } + void export_key(std::string cmd, std::string key, size_t key_i, td::Slice password) { using tonlib_api::make_object; if (cmd == "exportkey") { @@ -1038,7 +1258,7 @@ class TonlibCli : public td::actor::Actor { void import_key(std::vector words, td::Slice password) { using tonlib_api::make_object; - send_query(make_object(td::SecureString(password), td::SecureString(" test mnemonic"), + send_query(make_object(td::SecureString(password), td::SecureString(""), make_object(std::move(words))), [this, password = td::SecureString(password)](auto r_res) { if (r_res.is_error()) { @@ -1056,226 +1276,149 @@ class TonlibCli : public td::actor::Actor { }); } - void get_state(td::Slice key) { - if (key.empty()) { - dump_keys(); - td::TerminalIO::out() << "Choose public key (hex prefix or #N)"; - cont_ = [this](td::Slice key) { this->get_state(key); }; - on_wait(); - return; - } - auto r_address = to_account_address(key, false); - if (r_address.is_error()) { - td::TerminalIO::out() << "Unknown key id: [" << key << "]\n"; - on_error(); - return; - } - auto address = r_address.move_as_ok(); + void get_state(td::Slice key, td::Promise promise) { + TRY_RESULT_PROMISE(promise, address, to_account_address(key, false)); using tonlib_api::make_object; - send_query(make_object( + auto address_str = address.address->account_address_; + send_query(make_object( ton::move_tl_object_as(std::move(address.address))), - [this](auto r_res) { - if (r_res.is_error()) { - td::TerminalIO::out() << "Can't get state: " << r_res.error() << "\n"; - on_error(); - return; - } - td::TerminalIO::out() << to_string(r_res.ok()); - on_ok(); - }); + promise.wrap([address_str](auto&& state) { + td::TerminalIO::out() << "Address: " << address_str << "\n"; + td::TerminalIO::out() << "Balance: " + << Grams{td::narrow_cast(state->balance_ * (state->balance_ > 0))} + << "\n"; + td::TerminalIO::out() << "Sync utime: " << state->sync_utime_ << "\n"; + td::TerminalIO::out() << "transaction.LT: " << state->last_transaction_id_->lt_ << "\n"; + td::TerminalIO::out() << "transaction.Hash: " << td::base64_encode(state->last_transaction_id_->hash_) + << "\n"; + td::TerminalIO::out() << to_string(state->account_state_); + return td::Unit(); + })); } - void get_history(td::Slice key) { - if (key.empty()) { - dump_keys(); - td::TerminalIO::out() << "Choose public key (hex prefix or #N)"; - cont_ = [this](td::Slice key) { this->get_state(key); }; - return; - } - auto r_address = to_account_address(key, false); - if (r_address.is_error()) { - td::TerminalIO::out() << "Unknown key id: [" << key << "]\n"; - return; - } - auto address = r_address.move_as_ok(); + + void get_address(td::Slice key, td::Promise promise) { + TRY_RESULT_PROMISE(promise, address, to_account_address(key, false)); + promise.set_value(td::Unit()); + td::TerminalIO::out() << address.address->account_address_ << "\n"; + } + + void get_history(td::Slice key, td::Promise promise) { + TRY_RESULT_PROMISE(promise, address, to_account_address(key, false)); using tonlib_api::make_object; - send_query(make_object( + send_query(make_object( ton::move_tl_object_as(std::move(address.address))), - [this, key = key.str()](auto r_res) { - if (r_res.is_error()) { - td::TerminalIO::out() << "Can't get state: " << r_res.error() << "\n"; - return; - } - this->get_history(key, *r_res.move_as_ok()); - }); + promise.send_closure(td::actor::actor_id(this), &TonlibCli::get_history2, key.str())); } - void get_history(td::Slice key, tonlib_api::generic_AccountState& state) { - auto r_address = to_account_address(key, false); + void get_history2(td::Slice key, td::Result> r_state, + td::Promise promise) { + TRY_RESULT_PROMISE(promise, state, std::move(r_state)); + auto r_address = to_account_address(key, true); if (r_address.is_error()) { - td::TerminalIO::out() << "Unknown key id: [" << key << "]\n"; - return; + r_address = to_account_address(key, false); } - auto address = r_address.move_as_ok(); + TRY_RESULT_PROMISE(promise, address, std::move(r_address)); + td::Slice password; + using tonlib_api::make_object; + auto input_key = + !address.secret.empty() + ? make_object( + make_object(address.public_key, address.secret.copy()), td::SecureString(password)) + : nullptr; - tonlib_api::object_ptr transaction_id; - downcast_call(state, [&](auto& state) { transaction_id = std::move(state.account_state_->last_transaction_id_); }); - - send_query( - tonlib_api::make_object( - ton::move_tl_object_as(std::move(address.address)), std::move(transaction_id)), - [](auto r_res) { - if (r_res.is_error()) { - td::TerminalIO::out() << "Can't get transactions: " << r_res.error() << "\n"; - return; - } - td::TerminalIO::out() << to_string(r_res.move_as_ok()) << "\n"; - }); + send_query(tonlib_api::make_object( + std::move(input_key), ton::move_tl_object_as(std::move(address.address)), + std::move(state->last_transaction_id_)), + promise.wrap([](auto res) { + td::TerminalIO::out() << to_string(res) << "\n"; + return td::Unit(); + })); } - void transfer(td::Slice from, td::Slice to, td::Slice grams, td::Slice message, bool allow_send_to_uninited) { - auto r_from_address = to_account_address(from, true); - if (r_from_address.is_error()) { - td::TerminalIO::out() << "Unknown key id: [" << from << "] : " << r_from_address.error() << "\n"; - on_error(); - return; + void transfer(td::ConstParser& parser, td::Slice cmd, td::Promise cmd_promise) { + bool from_file = false; + bool force = false; + if (cmd != "init") { + td::ConstParser cmd_parser(cmd); + cmd_parser.advance(td::Slice("transfer").size()); + while (!cmd_parser.empty()) { + auto c = cmd_parser.peek_char(); + cmd_parser.advance(1); + if (c == 'F') { + from_file = true; + } else if (c == 'f') { + force = true; + } else { + cmd_promise.set_error(td::Status::Error(PSLICE() << "Unknown suffix '" << c << "'")); + return; + } + } } - auto r_to_address = to_account_address(to, false); - if (r_to_address.is_error()) { - td::TerminalIO::out() << "Unknown key id: [" << to << "] : " << r_to_address.error() << "\n"; - on_error(); - return; - } - auto r_grams = td::to_integer_safe(grams); - if (r_grams.is_error()) { - td::TerminalIO::out() << "Invalid grams amount: [" << grams << "]\n"; - on_error(); - return; - } - if (options_.one_shot) { - transfer(r_from_address.move_as_ok(), r_to_address.move_as_ok(), r_grams.move_as_ok(), "", "", - allow_send_to_uninited); - return; - } - if (from != "giver" && message.empty()) { - td::TerminalIO::out() << "Enter password (could be empty)"; - cont_ = [this, from = r_from_address.move_as_ok(), to = r_to_address.move_as_ok(), grams = r_grams.move_as_ok(), - allow_send_to_uninited](td::Slice password) mutable { - this->transfer(std::move(from), std::move(to), grams, password, allow_send_to_uninited); - }; - on_wait(); - return; - } - if (message.empty()) { - transfer(r_from_address.move_as_ok(), r_to_address.move_as_ok(), r_grams.move_as_ok(), "", - allow_send_to_uninited); - } else { - transfer(r_from_address.move_as_ok(), r_to_address.move_as_ok(), r_grams.move_as_ok(), "", message, - allow_send_to_uninited); - } - } - void transfer(Address from, Address to, td::uint64 grams, td::Slice password, bool allow_send_to_uninited) { - td::TerminalIO::out() << "Enter message (could be empty)"; - cont_ = [this, from = std::move(from), to = std::move(to), grams, password = password.str(), - allow_send_to_uninited](td::Slice message) mutable { - this->transfer(std::move(from), std::move(to), grams, password, message, allow_send_to_uninited); + auto from = parser.read_word(); + TRY_RESULT_PROMISE(cmd_promise, from_address, to_account_address(from, true)); + + struct Message { + Address to; + td::int64 amount; + std::string message; }; - on_wait(); - return; - } - void transfer(Address from, Address to, td::uint64 grams, td::Slice password, td::Slice message, - bool allow_send_to_uninited) { - auto r_sz = td::to_integer_safe(message); - auto msg = message.str(); - if (r_sz.is_ok()) { - msg = std::string(r_sz.ok(), 'Z'); - } - using tonlib_api::make_object; - auto key = !from.secret.empty() - ? 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)), - [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(); - }); + std::vector> messages; + auto add_message = [&](td::ConstParser& parser) { + auto to = parser.read_word(); + auto grams = parser.read_word(); + auto message = parser.read_word(); - //self->on_ok(); - }); - } + Message res; + TRY_RESULT(address, to_account_address(to, false)); + TRY_RESULT(amount, parse_grams(grams)); + messages.push_back(tonlib_api::make_object( + std::move(address.address), amount.nano, tonlib_api::make_object(message.str()))); + return td::Status::OK(); + }; - void init_simple_wallet(td::Slice key) { - if (key.empty()) { - dump_keys(); - td::TerminalIO::out() << "Choose public key (hex prefix or #N)"; - cont_ = [this](td::Slice key) { this->init_simple_wallet(key); }; - return; - } - auto r_key_i = to_key_i(key); - if (r_key_i.is_error()) { - td::TerminalIO::out() << "Unknown key id: [" << key << "]\n"; - return; - } - auto key_i = r_key_i.move_as_ok(); - - td::TerminalIO::out() << "Key #" << key_i << "\n" - << "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->init_simple_wallet(key, key_i, password); }; - } - - void init_simple_wallet(std::string key, size_t key_i, td::Slice password) { - using tonlib_api::make_object; - if (options_.wallet_version == 1) { - 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) { - if (r_res.is_error()) { - td::TerminalIO::out() << "Can't init wallet with key: [" << key << "] " << r_res.error() << "\n"; - return; - } - td::TerminalIO::out() << to_string(r_res.ok()); - }); + if (from_file) { + TRY_RESULT_PROMISE(cmd_promise, data, td::read_file(parser.read_word().str())); + auto lines = td::full_split(data.as_slice(), '\n'); + for (auto& line : lines) { + td::ConstParser parser(line); + parser.skip_whitespaces(); + if (parser.empty()) { + continue; + } + if (parser.read_word() != "SEND") { + TRY_STATUS_PROMISE(cmd_promise, td::Status::Error("Expected `SEND` in file")); + } + TRY_STATUS_PROMISE(cmd_promise, add_message(parser)); + } } else { - 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) { - if (r_res.is_error()) { - td::TerminalIO::out() << "Can't init wallet with key: [" << key << "] " << r_res.error() << "\n"; - return; - } - td::TerminalIO::out() << to_string(r_res.ok()); - }); + while (parser.skip_whitespaces(), !parser.empty()) { + TRY_STATUS_PROMISE(cmd_promise, add_message(parser)); + } } + + td::Slice password; // empty by default + using tonlib_api::make_object; + auto key = !from_address.secret.empty() + ? make_object( + make_object(from_address.public_key, from_address.secret.copy()), + td::SecureString(password)) + : nullptr; + + bool allow_send_to_uninited = force; + + send_query(make_object( + std::move(key), std::move(from_address.address), 60, + make_object(std::move(messages), allow_send_to_uninited)), + cmd_promise.send_closure(actor_id(this), &TonlibCli::transfer2)); + } + + void transfer2(td::Result> r_info, td::Promise cmd_promise) { + send_query(tonlib_api::make_object(r_info.ok()->id_), cmd_promise.wrap([](auto&& info) { + td::TerminalIO::out() << "Transfer sent!\n"; + return td::Unit(); + })); } void get_hints(td::Slice prefix) { @@ -1346,8 +1489,17 @@ int main(int argc, char* argv[]) { options.use_callbacks_for_network = true; return td::Status::OK(); }); - p.add_option('W', "wallet-version", "do not use this", [&](td::Slice arg) { - options.wallet_version = td::to_integer(arg); + p.add_option('W', "wallet-version", "do not use this (version[.revision])", [&](td::Slice arg) { + td::ConstParser parser(arg); + TRY_RESULT(version, td::to_integer_safe((parser.read_till_nofail('.')))); + options.wallet_version = version; + LOG(INFO) << "Use wallet version = " << version; + if (parser.peek_char() == '.') { + parser.skip('.'); + TRY_RESULT(revision, td::to_integer_safe((parser.read_all()))); + options.wallet_revision = revision; + LOG(INFO) << "Use wallet revision = " << revision; + } return td::Status::OK(); }); diff --git a/validator-session/validator-session-state.cpp b/validator-session/validator-session-state.cpp index 2230c4cf..463ced3a 100644 --- a/validator-session/validator-session-state.cpp +++ b/validator-session/validator-session-state.cpp @@ -14,7 +14,7 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #include "validator-session-state.h" #include "td/utils/overloaded.h" @@ -37,7 +37,7 @@ td::StringBuilder& operator<<(td::StringBuilder& sb, const ton::ton_api::validat sb << "APPROVE(" << obj.round_ << "," << obj.candidate_ << ")"; }, [&](const ton::ton_api::validatorSession_message_rejectedBlock& obj) { - sb << "COMMIT(" << obj.round_ << "," << obj.candidate_ << ")"; + sb << "REJECT(" << obj.round_ << "," << obj.candidate_ << ")"; }, [&](const ton::ton_api::validatorSession_message_commit& obj) { sb << "COMMIT(" << obj.round_ << "," << obj.candidate_ << ")"; @@ -1419,6 +1419,10 @@ const ValidatorSessionState* ValidatorSessionState::action(ValidatorSessionDescr const ValidatorSessionState* state, td::uint32 src_idx, td::uint32 att, const ton_api::validatorSession_round_Message* action) { + if (action) { + VLOG(VALIDATOR_SESSION_DEBUG) << "[validator session][node " << desc.get_source_id(src_idx) << "][" << action + << "]: applying message " << *action; + } if (att < state->att_->at(src_idx)) { VLOG(VALIDATOR_SESSION_WARNING) << "[validator session][node " << desc.get_source_id(src_idx) << "][" << action << "]: invalid message: bad ts: times goes back: " << state->att_->at(src_idx) diff --git a/validator/db/archive-manager.cpp b/validator/db/archive-manager.cpp index 0f529d66..c5f5ba9d 100644 --- a/validator/db/archive-manager.cpp +++ b/validator/db/archive-manager.cpp @@ -1,3 +1,21 @@ +/* + 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 2019-2020 Telegram Systems LLP +*/ #include "archive-manager.hpp" #include "td/actor/MultiPromise.h" #include "td/utils/overloaded.h" diff --git a/validator/db/archive-manager.hpp b/validator/db/archive-manager.hpp index 2f6e4752..34e7ca7d 100644 --- a/validator/db/archive-manager.hpp +++ b/validator/db/archive-manager.hpp @@ -1,3 +1,21 @@ +/* + 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 2019-2020 Telegram Systems LLP +*/ #pragma once #include "archive-slice.hpp" diff --git a/validator/db/archive-slice.cpp b/validator/db/archive-slice.cpp index ae2809f7..aad9c8fa 100644 --- a/validator/db/archive-slice.cpp +++ b/validator/db/archive-slice.cpp @@ -1,3 +1,21 @@ +/* + 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 2019-2020 Telegram Systems LLP +*/ #include "archive-slice.hpp" #include "td/actor/MultiPromise.h" diff --git a/validator/db/package.cpp b/validator/db/package.cpp index f76887c6..87090738 100644 --- a/validator/db/package.cpp +++ b/validator/db/package.cpp @@ -1,3 +1,21 @@ +/* + 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 2019-2020 Telegram Systems LLP +*/ #include "package.hpp" #include "common/errorcode.h" diff --git a/validator/impl/liteserver.cpp b/validator/impl/liteserver.cpp index 161e089b..fdd91620 100644 --- a/validator/impl/liteserver.cpp +++ b/validator/impl/liteserver.cpp @@ -34,7 +34,7 @@ #include "block/block-auto.h" #include "vm/dict.h" #include "vm/cells/MerkleProof.h" -#include "vm/continuation.h" +#include "vm/vm.h" #include "shard.hpp" #include "validator-set.hpp" #include "signature-set.hpp" diff --git a/validator/manager-disk.cpp b/validator/manager-disk.cpp index 564dc6b8..24135198 100644 --- a/validator/manager-disk.cpp +++ b/validator/manager-disk.cpp @@ -14,7 +14,7 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #include "manager-disk.hpp" #include "validator-group.hpp" @@ -765,7 +765,8 @@ void ValidatorManagerImpl::write_handle(BlockHandle handle, td::Promise state, td::Promise promise) { +void ValidatorManagerImpl::new_block_cont(BlockHandle handle, td::Ref state, + td::Promise promise) { handle->set_processed(); if (state->get_shard().is_masterchain() && handle->id().id.seqno > last_masterchain_seqno_) { CHECK(handle->id().id.seqno == last_masterchain_seqno_ + 1); @@ -783,6 +784,23 @@ void ValidatorManagerImpl::new_block(BlockHandle handle, td::Ref sta } } +void ValidatorManagerImpl::new_block(BlockHandle handle, td::Ref state, td::Promise promise) { + if (handle->is_applied()) { + return new_block_cont(std::move(handle), std::move(state), std::move(promise)); + } else { + auto P = td::PromiseCreator::lambda([SelfId = actor_id(this), handle, state = std::move(state), + promise = std::move(promise)](td::Result R) mutable { + if (R.is_error()) { + promise.set_error(R.move_as_error()); + } else { + td::actor::send_closure(SelfId, &ValidatorManagerImpl::new_block_cont, std::move(handle), std::move(state), + std::move(promise)); + } + }); + td::actor::send_closure(db_, &Db::apply_block, handle, std::move(P)); + } +} + void ValidatorManagerImpl::get_block_handle(BlockIdExt id, bool force, td::Promise promise) { auto it = handles_.find(id); if (it != handles_.end()) { diff --git a/validator/manager-disk.hpp b/validator/manager-disk.hpp index f65617f4..1520cbbb 100644 --- a/validator/manager-disk.hpp +++ b/validator/manager-disk.hpp @@ -14,7 +14,7 @@ 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 + Copyright 2017-2020 Telegram Systems LLP */ #pragma once @@ -213,6 +213,7 @@ class ValidatorManagerImpl : public ValidatorManager { void write_handle(BlockHandle handle, td::Promise promise) override; void new_block(BlockHandle handle, td::Ref state, td::Promise promise) override; + void new_block_cont(BlockHandle handle, td::Ref state, td::Promise promise); void get_top_masterchain_state(td::Promise> promise) override; void get_top_masterchain_block(td::Promise promise) override; void get_top_masterchain_state_block(td::Promise, BlockIdExt>> promise) override;