mirror of
https://github.com/ton-blockchain/ton
synced 2025-03-09 15:40:10 +00:00
updated smartcontracts
- updated smartcontracts - updated fullnode database layout - fixed memory leak in blockchain-explorer - updated tonlib
This commit is contained in:
parent
9c9248a9ae
commit
c860ce3d1e
104 changed files with 7309 additions and 1335 deletions
|
@ -5,42 +5,34 @@ if (NOT OPENSSL_FOUND)
|
|||
endif()
|
||||
|
||||
set(TONLIB_SOURCE
|
||||
tonlib/CellString.cpp
|
||||
tonlib/Client.cpp
|
||||
tonlib/Config.cpp
|
||||
tonlib/ExtClient.cpp
|
||||
tonlib/ExtClientLazy.cpp
|
||||
tonlib/ExtClientOutbound.cpp
|
||||
tonlib/GenericAccount.cpp
|
||||
tonlib/KeyStorage.cpp
|
||||
tonlib/KeyValue.cpp
|
||||
tonlib/LastBlock.cpp
|
||||
tonlib/LastBlockStorage.cpp
|
||||
tonlib/LastConfig.cpp
|
||||
tonlib/Logging.cpp
|
||||
tonlib/TestGiver.cpp
|
||||
tonlib/TestWallet.cpp
|
||||
tonlib/TonlibClient.cpp
|
||||
tonlib/utils.cpp
|
||||
tonlib/Wallet.cpp
|
||||
|
||||
tonlib/CellString.h
|
||||
tonlib/Client.h
|
||||
tonlib/Config.h
|
||||
tonlib/ExtClient.h
|
||||
tonlib/ExtClientLazy.h
|
||||
tonlib/ExtClientOutbound.h
|
||||
tonlib/GenericAccount.h
|
||||
tonlib/KeyStorage.h
|
||||
tonlib/KeyValue.h
|
||||
tonlib/LastBlock.h
|
||||
tonlib/LastBlockStorage.h
|
||||
tonlib/LastConfig.h
|
||||
tonlib/Logging.h
|
||||
tonlib/TestGiver.h
|
||||
tonlib/TestWallet.h
|
||||
tonlib/TonlibCallback.h
|
||||
tonlib/TonlibClient.h
|
||||
tonlib/utils.h
|
||||
tonlib/Wallet.h
|
||||
|
||||
tonlib/keys/bip39.cpp
|
||||
tonlib/keys/DecryptedKey.cpp
|
||||
|
@ -64,7 +56,7 @@ target_include_directories(tonlib PUBLIC
|
|||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>/..
|
||||
$<BUILD_INTERFACE:${OPENSSL_INCLUDE_DIR}>
|
||||
)
|
||||
target_link_libraries(tonlib PRIVATE tdactor adnllite tl_lite_api tl-lite-utils ton_crypto ton_block lite-client-common)
|
||||
target_link_libraries(tonlib PRIVATE tdactor adnllite tl_lite_api tl-lite-utils ton_crypto ton_block lite-client-common smc-envelope)
|
||||
target_link_libraries(tonlib PUBLIC tdutils tl_tonlib_api)
|
||||
|
||||
if (TONLIB_ENABLE_JNI AND NOT ANDROID) # jni is available by default on Android
|
||||
|
@ -134,7 +126,7 @@ if (NOT TON_USE_ABSEIL)
|
|||
if (WIN32)
|
||||
set(WINGETOPT_TARGET wingetopt)
|
||||
endif()
|
||||
install(TARGETS tdnet keys crc32c tdactor adnllite tl_api tl-utils tl_lite_api tl-lite-utils ton_crypto ton_block ${WINGETOPT_TARGET}
|
||||
install(TARGETS tdnet keys crc32c tdactor adnllite tl_api tl-utils tl_lite_api tl-lite-utils ton_crypto ton_block smc-envelope ${WINGETOPT_TARGET}
|
||||
tdutils tl_tonlib_api tonlib lite-client-common Tonlib EXPORT Tonlib
|
||||
LIBRARY DESTINATION lib
|
||||
ARCHIVE DESTINATION lib
|
||||
|
|
|
@ -17,23 +17,14 @@
|
|||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
|
||||
#include "fift/Fift.h"
|
||||
#include "fift/words.h"
|
||||
#include "fift/utils.h"
|
||||
|
||||
#include "block/block.h"
|
||||
#include "block/block-auto.h"
|
||||
|
||||
#include "vm/cells.h"
|
||||
#include "vm/boc.h"
|
||||
#include "vm/cells/MerkleProof.h"
|
||||
#include "vm/cells/CellString.h"
|
||||
|
||||
#include "tonlib/CellString.h"
|
||||
#include "tonlib/utils.h"
|
||||
#include "tonlib/TestGiver.h"
|
||||
#include "tonlib/TestWallet.h"
|
||||
#include "tonlib/Wallet.h"
|
||||
#include "tonlib/GenericAccount.h"
|
||||
#include "tonlib/TonlibClient.h"
|
||||
#include "tonlib/Client.h"
|
||||
|
||||
|
@ -70,181 +61,6 @@ TEST(Tonlib, CellString) {
|
|||
|
||||
using namespace tonlib;
|
||||
|
||||
std::string current_dir() {
|
||||
return td::PathView(td::realpath(__FILE__).move_as_ok()).parent_dir().str();
|
||||
}
|
||||
|
||||
std::string load_source(std::string name) {
|
||||
return td::read_file_str(current_dir() + "../../crypto/" + name).move_as_ok();
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> get_test_wallet_source() {
|
||||
std::string code = R"ABCD(
|
||||
SETCP0 DUP IFNOTRET // return if recv_internal
|
||||
DUP 85143 INT EQUAL IFJMP:<{ // "seqno" get-method
|
||||
DROP c4 PUSHCTR CTOS 32 PLDU // cnt
|
||||
}>
|
||||
INC 32 THROWIF // fail unless recv_external
|
||||
512 INT LDSLICEX DUP 32 PLDU // sign cs cnt
|
||||
c4 PUSHCTR CTOS 32 LDU 256 LDU ENDS // sign cs cnt cnt' pubk
|
||||
s1 s2 XCPU // sign cs cnt pubk cnt' cnt
|
||||
EQUAL 33 THROWIFNOT // ( seqno mismatch? )
|
||||
s2 PUSH HASHSU // sign cs cnt pubk hash
|
||||
s0 s4 s4 XC2PU // pubk cs cnt hash sign pubk
|
||||
CHKSIGNU // pubk cs cnt ?
|
||||
34 THROWIFNOT // signature mismatch
|
||||
ACCEPT
|
||||
SWAP 32 LDU NIP
|
||||
DUP SREFS IF:<{
|
||||
// 3 INT 35 LSHIFT# 3 INT RAWRESERVE // reserve all but 103 Grams from the balance
|
||||
8 LDU LDREF // pubk cnt mode msg cs
|
||||
s0 s2 XCHG SENDRAWMSG // pubk cnt cs ; ( message sent )
|
||||
}>
|
||||
ENDS
|
||||
INC NEWC 32 STU 256 STU ENDC c4 POPCTR
|
||||
)ABCD";
|
||||
return fift::compile_asm(code).move_as_ok();
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> get_wallet_source() {
|
||||
std::string code = R"ABCD(
|
||||
SETCP0 DUP IFNOTRET // return if recv_internal
|
||||
DUP 85143 INT EQUAL IFJMP:<{ // "seqno" get-method
|
||||
DROP c4 PUSHCTR CTOS 32 PLDU // cnt
|
||||
}>
|
||||
INC 32 THROWIF // fail unless recv_external
|
||||
9 PUSHPOW2 LDSLICEX DUP 32 LDU 32 LDU // signature in_msg msg_seqno valid_until cs
|
||||
SWAP NOW LEQ 35 THROWIF // signature in_msg msg_seqno cs
|
||||
c4 PUSH CTOS 32 LDU 256 LDU ENDS // signature in_msg msg_seqno cs stored_seqno public_key
|
||||
s3 s1 XCPU // signature in_msg public_key cs stored_seqno msg_seqno stored_seqno
|
||||
EQUAL 33 THROWIFNOT // signature in_msg public_key cs stored_seqno
|
||||
s0 s3 XCHG HASHSU // signature stored_seqno public_key cs hash
|
||||
s0 s4 s2 XC2PU CHKSIGNU 34 THROWIFNOT // cs stored_seqno public_key
|
||||
ACCEPT
|
||||
s0 s2 XCHG // public_key stored_seqno cs
|
||||
WHILE:<{
|
||||
DUP SREFS // public_key stored_seqno cs _40
|
||||
}>DO<{ // public_key stored_seqno cs
|
||||
// 3 INT 35 LSHIFT# 3 INT RAWRESERVE // reserve all but 103 Grams from the balance
|
||||
8 LDU LDREF s0 s2 XCHG // public_key stored_seqno cs _45 mode
|
||||
SENDRAWMSG // public_key stored_seqno cs
|
||||
}>
|
||||
ENDS INC // public_key seqno'
|
||||
NEWC 32 STU 256 STU ENDC c4 POP
|
||||
)ABCD";
|
||||
return fift::compile_asm(code).move_as_ok();
|
||||
}
|
||||
|
||||
TEST(Tonlib, TestWallet) {
|
||||
LOG(ERROR) << td::base64_encode(std_boc_serialize(get_test_wallet_source()).move_as_ok());
|
||||
CHECK(get_test_wallet_source()->get_hash() == TestWallet::get_init_code()->get_hash());
|
||||
auto fift_output = fift::mem_run_fift(load_source("smartcont/new-wallet.fif"), {"aba", "0"}).move_as_ok();
|
||||
|
||||
auto new_wallet_pk = fift_output.source_lookup.read_file("new-wallet.pk").move_as_ok().data;
|
||||
auto new_wallet_query = fift_output.source_lookup.read_file("new-wallet-query.boc").move_as_ok().data;
|
||||
auto new_wallet_addr = fift_output.source_lookup.read_file("new-wallet.addr").move_as_ok().data;
|
||||
|
||||
td::Ed25519::PrivateKey priv_key{td::SecureString{new_wallet_pk}};
|
||||
auto pub_key = priv_key.get_public_key().move_as_ok();
|
||||
auto init_state = TestWallet::get_init_state(pub_key);
|
||||
auto init_message = TestWallet::get_init_message(priv_key);
|
||||
auto address = GenericAccount::get_address(0, init_state);
|
||||
|
||||
CHECK(address.addr.as_slice() == td::Slice(new_wallet_addr).substr(0, 32));
|
||||
|
||||
td::Ref<vm::Cell> res = GenericAccount::create_ext_message(address, init_state, init_message);
|
||||
|
||||
LOG(ERROR) << "-------";
|
||||
vm::load_cell_slice(res).print_rec(std::cerr);
|
||||
LOG(ERROR) << "-------";
|
||||
vm::load_cell_slice(vm::std_boc_deserialize(new_wallet_query).move_as_ok()).print_rec(std::cerr);
|
||||
CHECK(vm::std_boc_deserialize(new_wallet_query).move_as_ok()->get_hash() == res->get_hash());
|
||||
|
||||
fift_output.source_lookup.write_file("/main.fif", load_source("smartcont/wallet.fif")).ensure();
|
||||
auto dest = block::StdAddress::parse("Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX").move_as_ok();
|
||||
fift_output = fift::mem_run_fift(std::move(fift_output.source_lookup),
|
||||
{"aba", "new-wallet", "Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX", "123",
|
||||
"321", "-C", "TEST"})
|
||||
.move_as_ok();
|
||||
auto wallet_query = fift_output.source_lookup.read_file("wallet-query.boc").move_as_ok().data;
|
||||
auto gift_message = GenericAccount::create_ext_message(
|
||||
address, {}, TestWallet::make_a_gift_message(priv_key, 123, 321000000000ll, "TEST", dest));
|
||||
LOG(ERROR) << "-------";
|
||||
vm::load_cell_slice(gift_message).print_rec(std::cerr);
|
||||
LOG(ERROR) << "-------";
|
||||
vm::load_cell_slice(vm::std_boc_deserialize(wallet_query).move_as_ok()).print_rec(std::cerr);
|
||||
CHECK(vm::std_boc_deserialize(wallet_query).move_as_ok()->get_hash() == gift_message->get_hash());
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> get_wallet_source_fc() {
|
||||
return fift::compile_asm(load_source("smartcont/wallet-code.fif"), "", false).move_as_ok();
|
||||
}
|
||||
|
||||
TEST(Tonlib, Wallet) {
|
||||
LOG(ERROR) << td::base64_encode(std_boc_serialize(get_wallet_source()).move_as_ok());
|
||||
CHECK(get_wallet_source()->get_hash() == Wallet::get_init_code()->get_hash());
|
||||
|
||||
auto fift_output = fift::mem_run_fift(load_source("smartcont/new-wallet-v2.fif"), {"aba", "0"}).move_as_ok();
|
||||
|
||||
auto new_wallet_pk = fift_output.source_lookup.read_file("new-wallet.pk").move_as_ok().data;
|
||||
auto new_wallet_query = fift_output.source_lookup.read_file("new-wallet-query.boc").move_as_ok().data;
|
||||
auto new_wallet_addr = fift_output.source_lookup.read_file("new-wallet.addr").move_as_ok().data;
|
||||
|
||||
td::Ed25519::PrivateKey priv_key{td::SecureString{new_wallet_pk}};
|
||||
auto pub_key = priv_key.get_public_key().move_as_ok();
|
||||
auto init_state = Wallet::get_init_state(pub_key);
|
||||
auto init_message = Wallet::get_init_message(priv_key);
|
||||
auto address = GenericAccount::get_address(0, init_state);
|
||||
|
||||
CHECK(address.addr.as_slice() == td::Slice(new_wallet_addr).substr(0, 32));
|
||||
|
||||
td::Ref<vm::Cell> res = GenericAccount::create_ext_message(address, init_state, init_message);
|
||||
|
||||
LOG(ERROR) << "-------";
|
||||
vm::load_cell_slice(res).print_rec(std::cerr);
|
||||
LOG(ERROR) << "-------";
|
||||
vm::load_cell_slice(vm::std_boc_deserialize(new_wallet_query).move_as_ok()).print_rec(std::cerr);
|
||||
CHECK(vm::std_boc_deserialize(new_wallet_query).move_as_ok()->get_hash() == res->get_hash());
|
||||
|
||||
fift_output.source_lookup.write_file("/main.fif", load_source("smartcont/wallet-v2.fif")).ensure();
|
||||
class ZeroOsTime : public fift::OsTime {
|
||||
public:
|
||||
td::uint32 now() override {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
fift_output.source_lookup.set_os_time(std::make_unique<ZeroOsTime>());
|
||||
auto dest = block::StdAddress::parse("Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX").move_as_ok();
|
||||
fift_output =
|
||||
fift::mem_run_fift(std::move(fift_output.source_lookup),
|
||||
{"aba", "new-wallet", "Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX", "123", "321"})
|
||||
.move_as_ok();
|
||||
auto wallet_query = fift_output.source_lookup.read_file("wallet-query.boc").move_as_ok().data;
|
||||
auto gift_message = GenericAccount::create_ext_message(
|
||||
address, {}, Wallet::make_a_gift_message(priv_key, 123, 60, 321000000000ll, "TESTv2", dest));
|
||||
LOG(ERROR) << "-------";
|
||||
vm::load_cell_slice(gift_message).print_rec(std::cerr);
|
||||
LOG(ERROR) << "-------";
|
||||
vm::load_cell_slice(vm::std_boc_deserialize(wallet_query).move_as_ok()).print_rec(std::cerr);
|
||||
CHECK(vm::std_boc_deserialize(wallet_query).move_as_ok()->get_hash() == gift_message->get_hash());
|
||||
}
|
||||
|
||||
TEST(Tonlib, TestGiver) {
|
||||
auto address =
|
||||
block::StdAddress::parse("-1:60c04141c6a7b96d68615e7a91d265ad0f3a9a922e9ae9c901d4fa83f5d3c0d0").move_as_ok();
|
||||
LOG(ERROR) << address.bounceable;
|
||||
auto fift_output = fift::mem_run_fift(load_source("smartcont/testgiver.fif"),
|
||||
{"aba", address.rserialize(), "0", "6.666", "wallet-query"})
|
||||
.move_as_ok();
|
||||
LOG(ERROR) << fift_output.output;
|
||||
|
||||
auto wallet_query = fift_output.source_lookup.read_file("wallet-query.boc").move_as_ok().data;
|
||||
|
||||
auto res = GenericAccount::create_ext_message(
|
||||
TestGiver::address(), {}, TestGiver::make_a_gift_message(0, 1000000000ll * 6666 / 1000, "GIFT", address));
|
||||
vm::CellSlice(vm::NoVm(), res).print_rec(std::cerr);
|
||||
CHECK(vm::std_boc_deserialize(wallet_query).move_as_ok()->get_hash() == res->get_hash());
|
||||
}
|
||||
TEST(Tonlib, PublicKey) {
|
||||
block::PublicKey::parse("pubjns2gp7DGCnEH7EOWeCnb6Lw1akm538YYaz6sdLVHfRB2").ensure_error();
|
||||
auto key = block::PublicKey::parse("Pubjns2gp7DGCnEH7EOWeCnb6Lw1akm538YYaz6sdLVHfRB2").move_as_ok();
|
||||
|
@ -480,15 +296,15 @@ TEST(Tonlib, KeysApi) {
|
|||
td::SecureString{}))
|
||||
.move_as_ok();
|
||||
|
||||
sync_send(client, make_object<tonlib_api::exportKey>(make_object<tonlib_api::inputKey>(
|
||||
sync_send(client, make_object<tonlib_api::exportKey>(make_object<tonlib_api::inputKeyRegular>(
|
||||
make_object<tonlib_api::key>(key->public_key_, key->secret_.copy()),
|
||||
td::SecureString("wrong password"))))
|
||||
.ensure_error();
|
||||
|
||||
//exportKey input_key:inputKey = ExportedKey;
|
||||
//exportKey input_key:inputKeyRegular = ExportedKey;
|
||||
auto exported_key =
|
||||
sync_send(client,
|
||||
make_object<tonlib_api::exportKey>(make_object<tonlib_api::inputKey>(
|
||||
make_object<tonlib_api::exportKey>(make_object<tonlib_api::inputKeyRegular>(
|
||||
make_object<tonlib_api::key>(key->public_key_, key->secret_.copy()), local_password.copy())))
|
||||
.move_as_ok();
|
||||
LOG(ERROR) << to_string(exported_key);
|
||||
|
@ -500,20 +316,20 @@ TEST(Tonlib, KeysApi) {
|
|||
return word_list_copy;
|
||||
};
|
||||
|
||||
//changeLocalPassword input_key:inputKey new_local_password:bytes = Key;
|
||||
//changeLocalPassword input_key:inputKeyRegular new_local_password:bytes = Key;
|
||||
auto new_key =
|
||||
sync_send(client,
|
||||
make_object<tonlib_api::changeLocalPassword>(
|
||||
make_object<tonlib_api::inputKey>(
|
||||
make_object<tonlib_api::inputKeyRegular>(
|
||||
make_object<tonlib_api::key>(key->public_key_, key->secret_.copy()), local_password.copy()),
|
||||
td::SecureString("tmp local password")))
|
||||
.move_as_ok();
|
||||
sync_send(client,
|
||||
make_object<tonlib_api::exportKey>(make_object<tonlib_api::inputKey>(
|
||||
make_object<tonlib_api::exportKey>(make_object<tonlib_api::inputKeyRegular>(
|
||||
make_object<tonlib_api::key>(key->public_key_, new_key->secret_.copy()), local_password.copy())))
|
||||
.ensure_error();
|
||||
|
||||
auto exported_key2 = sync_send(client, make_object<tonlib_api::exportKey>(make_object<tonlib_api::inputKey>(
|
||||
auto exported_key2 = sync_send(client, make_object<tonlib_api::exportKey>(make_object<tonlib_api::inputKeyRegular>(
|
||||
make_object<tonlib_api::key>(key->public_key_, new_key->secret_.copy()),
|
||||
td::SecureString("tmp local password"))))
|
||||
.move_as_ok();
|
||||
|
@ -531,7 +347,7 @@ TEST(Tonlib, KeysApi) {
|
|||
auto wrong_export_password = td::SecureString("wrong_export password");
|
||||
auto exported_encrypted_key =
|
||||
sync_send(client, make_object<tonlib_api::exportEncryptedKey>(
|
||||
make_object<tonlib_api::inputKey>(
|
||||
make_object<tonlib_api::inputKeyRegular>(
|
||||
make_object<tonlib_api::key>(key->public_key_, new_key->secret_.copy()),
|
||||
td::SecureString("tmp local password")),
|
||||
export_password.copy()))
|
||||
|
@ -576,12 +392,12 @@ TEST(Tonlib, KeysApi) {
|
|||
CHECK(imported_key->public_key_ == key->public_key_);
|
||||
CHECK(imported_key->secret_ != key->secret_);
|
||||
|
||||
//exportPemKey input_key:inputKey key_password:bytes = ExportedPemKey;
|
||||
//exportPemKey input_key:inputKeyRegular key_password:bytes = ExportedPemKey;
|
||||
auto pem_password = td::SecureString("pem password");
|
||||
auto r_exported_pem_key = sync_send(
|
||||
client,
|
||||
make_object<tonlib_api::exportPemKey>(
|
||||
make_object<tonlib_api::inputKey>(
|
||||
make_object<tonlib_api::inputKeyRegular>(
|
||||
make_object<tonlib_api::key>(key->public_key_, imported_key->secret_.copy()), new_local_password.copy()),
|
||||
pem_password.copy()));
|
||||
if (r_exported_pem_key.is_error() && r_exported_pem_key.error().message() == "INTERNAL Not supported") {
|
||||
|
|
|
@ -34,10 +34,12 @@
|
|||
#include "ton/ton-tl.hpp"
|
||||
#include "block/block.h"
|
||||
#include "block/block-auto.h"
|
||||
#include "Ed25519.h"
|
||||
|
||||
#include "tonlib/GenericAccount.h"
|
||||
#include "tonlib/TestGiver.h"
|
||||
#include "tonlib/TestWallet.h"
|
||||
#include "smc-envelope/GenericAccount.h"
|
||||
#include "smc-envelope/MultisigWallet.h"
|
||||
#include "smc-envelope/TestGiver.h"
|
||||
#include "smc-envelope/TestWallet.h"
|
||||
#include "tonlib/LastBlock.h"
|
||||
#include "tonlib/ExtClient.h"
|
||||
#include "tonlib/utils.h"
|
||||
|
@ -57,18 +59,21 @@
|
|||
#include "td/utils/optional.h"
|
||||
#include "td/utils/overloaded.h"
|
||||
#include "td/utils/MpscPollableQueue.h"
|
||||
#include "td/utils/port/path.h"
|
||||
|
||||
#include "td/utils/port/signals.h"
|
||||
|
||||
using namespace tonlib;
|
||||
|
||||
constexpr td::int64 Gramm = 1000000000;
|
||||
|
||||
auto sync_send = [](auto& client, auto query) {
|
||||
using ReturnTypePtr = typename std::decay_t<decltype(*query)>::ReturnType;
|
||||
using ReturnType = typename ReturnTypePtr::element_type;
|
||||
client.send({1, std::move(query)});
|
||||
while (true) {
|
||||
auto response = client.receive(100);
|
||||
if (response.object) {
|
||||
if (response.object && response.id != 0) {
|
||||
CHECK(response.id == 1);
|
||||
if (response.object->get_id() == tonlib_api::error::ID) {
|
||||
auto error = tonlib_api::move_object_as<tonlib_api::error>(response.object);
|
||||
|
@ -78,87 +83,295 @@ auto sync_send = [](auto& client, auto query) {
|
|||
}
|
||||
}
|
||||
};
|
||||
auto static_send = [](auto query) {
|
||||
using ReturnTypePtr = typename std::decay_t<decltype(*query)>::ReturnType;
|
||||
using ReturnType = typename ReturnTypePtr::element_type;
|
||||
auto response = Client::execute({1, std::move(query)});
|
||||
if (response.object->get_id() == tonlib_api::error::ID) {
|
||||
auto error = tonlib_api::move_object_as<tonlib_api::error>(response.object);
|
||||
return td::Result<ReturnTypePtr>(td::Status::Error(error->code_, error->message_));
|
||||
}
|
||||
return td::Result<ReturnTypePtr>(tonlib_api::move_object_as<ReturnType>(response.object));
|
||||
};
|
||||
|
||||
struct Key {
|
||||
std::string public_key;
|
||||
td::SecureString secret;
|
||||
tonlib_api::object_ptr<tonlib_api::inputKey> get_input_key() const {
|
||||
return tonlib_api::make_object<tonlib_api::inputKey>(
|
||||
tonlib_api::object_ptr<tonlib_api::InputKey> get_input_key() const {
|
||||
return tonlib_api::make_object<tonlib_api::inputKeyRegular>(
|
||||
tonlib_api::make_object<tonlib_api::key>(public_key, secret.copy()), td::SecureString("local"));
|
||||
}
|
||||
tonlib_api::object_ptr<tonlib_api::InputKey> get_fake_input_key() const {
|
||||
return tonlib_api::make_object<tonlib_api::inputKeyFake>();
|
||||
}
|
||||
};
|
||||
struct Wallet {
|
||||
std::string address;
|
||||
Key key;
|
||||
};
|
||||
|
||||
struct TransactionId {
|
||||
td::int64 lt{0};
|
||||
std::string hash;
|
||||
};
|
||||
|
||||
struct AccountState {
|
||||
enum Type { Empty, Wallet, Unknown } type{Empty};
|
||||
td::int64 sync_utime{-1};
|
||||
td::int64 balance{-1};
|
||||
TransactionId last_transaction_id;
|
||||
std::string address;
|
||||
|
||||
bool is_inited() const {
|
||||
return type != Empty;
|
||||
}
|
||||
};
|
||||
|
||||
using tonlib_api::make_object;
|
||||
|
||||
void sync(Client& client) {
|
||||
sync_send(client, make_object<tonlib_api::sync>()).ensure();
|
||||
}
|
||||
|
||||
std::string wallet_address(Client& client, const Key& key) {
|
||||
return sync_send(client, make_object<tonlib_api::wallet_getAccountAddress>(
|
||||
make_object<tonlib_api::wallet_initialAccountState>(key.public_key)))
|
||||
.move_as_ok()
|
||||
->account_address_;
|
||||
}
|
||||
|
||||
Wallet import_wallet_from_pkey(Client& client, std::string pkey, std::string password) {
|
||||
auto key = sync_send(client, make_object<tonlib_api::importPemKey>(
|
||||
td::SecureString("local"), td::SecureString(password),
|
||||
make_object<tonlib_api::exportedPemKey>(td::SecureString(pkey))))
|
||||
.move_as_ok();
|
||||
Wallet wallet{"", {key->public_key_, std::move(key->secret_)}};
|
||||
wallet.address = wallet_address(client, wallet.key);
|
||||
return wallet;
|
||||
}
|
||||
|
||||
std::string test_giver_address(Client& client) {
|
||||
using tonlib_api::make_object;
|
||||
return sync_send(client, make_object<tonlib_api::testGiver_getAccountAddress>()).move_as_ok()->account_address_;
|
||||
}
|
||||
|
||||
td::int64 get_balance(Client& client, std::string address) {
|
||||
AccountState get_account_state(Client& client, std::string address) {
|
||||
auto generic_state = sync_send(client, tonlib_api::make_object<tonlib_api::generic_getAccountState>(
|
||||
tonlib_api::make_object<tonlib_api::accountAddress>(address)))
|
||||
.move_as_ok();
|
||||
td::int64 res = 0;
|
||||
tonlib_api::downcast_call(*generic_state, [&](auto& state) { res = state.account_state_->balance_; });
|
||||
AccountState res;
|
||||
tonlib_api::downcast_call(*generic_state, [&](auto& state) {
|
||||
res.balance = state.account_state_->balance_;
|
||||
res.sync_utime = state.account_state_->sync_utime_;
|
||||
res.last_transaction_id.lt = state.account_state_->last_transaction_id_->lt_;
|
||||
res.last_transaction_id.hash = state.account_state_->last_transaction_id_->hash_;
|
||||
});
|
||||
res.address = address;
|
||||
switch (generic_state->get_id()) {
|
||||
case tonlib_api::generic_accountStateUninited::ID:
|
||||
res.type = AccountState::Empty;
|
||||
break;
|
||||
case tonlib_api::generic_accountStateWallet::ID:
|
||||
res.type = AccountState::Wallet;
|
||||
break;
|
||||
default:
|
||||
res.type = AccountState::Unknown;
|
||||
break;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
bool is_inited(Client& client, std::string address) {
|
||||
auto generic_state = sync_send(client, tonlib_api::make_object<tonlib_api::generic_getAccountState>(
|
||||
tonlib_api::make_object<tonlib_api::accountAddress>(address)))
|
||||
.move_as_ok();
|
||||
return generic_state->get_id() != tonlib_api::generic_accountStateUninited::ID;
|
||||
struct QueryId {
|
||||
td::int64 id;
|
||||
};
|
||||
|
||||
struct Fee {
|
||||
td::int64 in_fwd_fee{0};
|
||||
td::int64 storage_fee{0};
|
||||
td::int64 gas_fee{0};
|
||||
td::int64 fwd_fee{0};
|
||||
td::int64 sum() const {
|
||||
return in_fwd_fee + storage_fee + gas_fee + fwd_fee;
|
||||
}
|
||||
};
|
||||
|
||||
template <class T>
|
||||
auto to_fee(const T& fee) {
|
||||
Fee res;
|
||||
res.in_fwd_fee = fee->in_fwd_fee_;
|
||||
res.storage_fee = fee->storage_fee_;
|
||||
res.gas_fee = fee->gas_fee_;
|
||||
res.fwd_fee = fee->fwd_fee_;
|
||||
return res;
|
||||
}
|
||||
|
||||
void transfer_grams(Client& client, std::string from, std::string to, td::int64 amount,
|
||||
tonlib_api::object_ptr<tonlib_api::inputKey> input_key) {
|
||||
auto balance = get_balance(client, to);
|
||||
sync_send(client, tonlib_api::make_object<tonlib_api::generic_sendGrams>(
|
||||
std::move(input_key), tonlib_api::make_object<tonlib_api::accountAddress>(from),
|
||||
tonlib_api::make_object<tonlib_api::accountAddress>(to), amount, 0, true, "GIFT"))
|
||||
.ensure();
|
||||
while (balance == get_balance(client, to)) {
|
||||
td::StringBuilder& operator<<(td::StringBuilder& sb, const Fee& fees) {
|
||||
return sb << td::tag("in_fwd_fee", fees.in_fwd_fee) << td::tag("storage_fee", fees.storage_fee)
|
||||
<< td::tag("gas_fee", fees.gas_fee) << td::tag("fwd_fee", fees.fwd_fee);
|
||||
}
|
||||
|
||||
struct QueryInfo {
|
||||
td::int64 valid_until;
|
||||
std::string body_hash;
|
||||
};
|
||||
|
||||
td::Result<QueryId> 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<tonlib_api::generic_createSendGramsQuery>(
|
||||
fake ? source.key.get_fake_input_key() : source.key.get_input_key(),
|
||||
tonlib_api::make_object<tonlib_api::accountAddress>(source.address),
|
||||
tonlib_api::make_object<tonlib_api::accountAddress>(destination), amount, timeout,
|
||||
force, std::move(message)));
|
||||
TRY_RESULT(id, std::move(r_id));
|
||||
return QueryId{id->id_};
|
||||
}
|
||||
|
||||
td::Result<QueryId> create_raw_query(Client& client, std::string source, std::string init_code, std::string init_data,
|
||||
std::string body) {
|
||||
auto r_id =
|
||||
sync_send(client, tonlib_api::make_object<tonlib_api::raw_createQuery>(
|
||||
tonlib_api::make_object<tonlib_api::accountAddress>(source), init_code, init_data, body));
|
||||
TRY_RESULT(id, std::move(r_id));
|
||||
return QueryId{id->id_};
|
||||
}
|
||||
|
||||
std::pair<Fee, Fee> query_estimate_fees(Client& client, QueryId query_id, bool ignore_chksig = false) {
|
||||
auto fees = sync_send(client, tonlib_api::make_object<tonlib_api::query_estimateFees>(query_id.id, ignore_chksig))
|
||||
.move_as_ok();
|
||||
return std::make_pair(to_fee(fees->source_fees_), to_fee(fees->destination_fees_));
|
||||
}
|
||||
|
||||
void query_send(Client& client, QueryId query_id) {
|
||||
sync_send(client, tonlib_api::make_object<tonlib_api::query_send>(query_id.id)).ensure();
|
||||
}
|
||||
QueryInfo query_get_info(Client& client, QueryId query_id) {
|
||||
auto info = sync_send(client, tonlib_api::make_object<tonlib_api::query_getInfo>(query_id.id)).move_as_ok();
|
||||
return QueryInfo{info->valid_until_, info->body_hash_};
|
||||
}
|
||||
|
||||
td::Result<AccountState> wait_state_change(Client& client, const AccountState& old_state, td::int64 valid_until) {
|
||||
while (true) {
|
||||
auto new_state = get_account_state(client, old_state.address);
|
||||
if (new_state.last_transaction_id.lt != old_state.last_transaction_id.lt) {
|
||||
return new_state;
|
||||
}
|
||||
if (valid_until != 0 && new_state.sync_utime >= valid_until) {
|
||||
return td::Status::Error("valid_until expired");
|
||||
}
|
||||
client.receive(1);
|
||||
}
|
||||
};
|
||||
|
||||
td::Result<tonlib_api::object_ptr<tonlib_api::raw_transactions>> get_transactions(Client& client, std::string address,
|
||||
const TransactionId& from) {
|
||||
auto got_transactions = sync_send(client, make_object<tonlib_api::raw_getTransactions>(
|
||||
make_object<tonlib_api::accountAddress>(address),
|
||||
make_object<tonlib_api::internal_transactionId>(from.lt, from.hash)))
|
||||
.move_as_ok();
|
||||
return std::move(got_transactions);
|
||||
}
|
||||
|
||||
td::Status transfer_grams(Client& client, const Wallet& wallet, std::string address, td::int64 amount) {
|
||||
auto src_state = get_account_state(client, wallet.address);
|
||||
auto dst_state = get_account_state(client, address);
|
||||
auto message = td::rand_string('a', 'z', 500);
|
||||
|
||||
LOG(INFO) << "Transfer: create query " << (double)amount / Gramm << " from " << wallet.address << " to " << address;
|
||||
auto r_query_id = create_send_grams_query(client, wallet, address, amount, message);
|
||||
if (r_query_id.is_error() && td::begins_with(r_query_id.error().message(), "DANGEROUS_TRANSACTION")) {
|
||||
ASSERT_TRUE(dst_state.type == AccountState::Empty);
|
||||
LOG(INFO) << "Transfer: recreate query due to DANGEROUS_TRANSACTION error";
|
||||
r_query_id = create_send_grams_query(client, wallet, address, amount, message, true);
|
||||
}
|
||||
|
||||
r_query_id.ensure();
|
||||
QueryId query_id = r_query_id.move_as_ok();
|
||||
auto query_info = query_get_info(client, query_id);
|
||||
auto fees = query_estimate_fees(client, query_id);
|
||||
|
||||
LOG(INFO) << "Expected src fees: " << fees.first;
|
||||
LOG(INFO) << "Expected dst fees: " << fees.second;
|
||||
|
||||
bool transfer_all = amount == src_state.balance;
|
||||
if (!transfer_all && amount + fees.first.sum() + 10 > src_state.balance) {
|
||||
return td::Status::Error("Not enough balance for query");
|
||||
}
|
||||
|
||||
LOG(INFO) << "Transfer: send query";
|
||||
|
||||
query_send(client, query_id);
|
||||
td::Timer timer;
|
||||
TRY_RESULT(new_src_state, wait_state_change(client, src_state, query_info.valid_until));
|
||||
LOG(INFO) << "Transfer: reached source in " << timer;
|
||||
|
||||
td::int64 lt;
|
||||
td::int64 first_fee;
|
||||
{
|
||||
auto tr = get_transactions(client, src_state.address, new_src_state.last_transaction_id).move_as_ok();
|
||||
CHECK(tr->transactions_.size() > 0);
|
||||
const auto& txn = tr->transactions_[0];
|
||||
CHECK(txn->in_msg_->body_hash_ == query_info.body_hash);
|
||||
ASSERT_EQ(1u, txn->out_msgs_.size());
|
||||
ASSERT_EQ(message, txn->out_msgs_[0]->message_);
|
||||
lt = txn->out_msgs_[0]->created_lt_;
|
||||
auto fee_difference = fees.first.sum() - txn->fee_;
|
||||
first_fee = txn->fee_;
|
||||
auto desc = PSTRING() << fee_difference << " storage:[" << fees.first.storage_fee << " vs " << txn->storage_fee_
|
||||
<< "] other:[" << fees.first.sum() - fees.first.storage_fee << " vs " << txn->other_fee_
|
||||
<< "]";
|
||||
LOG(INFO) << "Source fee difference " << desc;
|
||||
LOG_IF(ERROR, std::abs(fee_difference) > 1) << "Too big source fee difference " << desc;
|
||||
}
|
||||
|
||||
TRY_RESULT(new_dst_state, wait_state_change(client, dst_state, new_src_state.sync_utime + 30));
|
||||
LOG(INFO) << "Transfer: reached destination in " << timer;
|
||||
|
||||
{
|
||||
auto tr = get_transactions(client, dst_state.address, new_dst_state.last_transaction_id).move_as_ok();
|
||||
CHECK(tr->transactions_.size() > 0);
|
||||
const auto& txn = tr->transactions_[0];
|
||||
ASSERT_EQ(lt, txn->in_msg_->created_lt_);
|
||||
if (transfer_all) {
|
||||
ASSERT_EQ(amount - first_fee, txn->in_msg_->value_);
|
||||
} else {
|
||||
ASSERT_EQ(new_src_state.address, txn->in_msg_->source_);
|
||||
}
|
||||
ASSERT_EQ(new_src_state.address, txn->in_msg_->source_);
|
||||
ASSERT_EQ(message, txn->in_msg_->message_);
|
||||
auto fee_difference = fees.second.sum() - txn->fee_;
|
||||
auto desc = PSTRING() << fee_difference << " storage:[" << fees.second.storage_fee << " vs " << txn->storage_fee_
|
||||
<< "] other:[" << fees.second.sum() - fees.second.storage_fee << " vs " << txn->other_fee_
|
||||
<< "]";
|
||||
LOG(INFO) << "Destination fee difference " << desc;
|
||||
LOG_IF(ERROR, std::abs(fee_difference) > 1) << "Too big destination fee difference " << desc;
|
||||
}
|
||||
|
||||
return td::Status::OK();
|
||||
}
|
||||
|
||||
Wallet create_empty_wallet(Client& client) {
|
||||
using tonlib_api::make_object;
|
||||
auto key = sync_send(client, make_object<tonlib_api::createNewKey>(td::SecureString("local"),
|
||||
td::SecureString("mnemonic"), td::SecureString()))
|
||||
auto key = sync_send(client, make_object<tonlib_api::createNewKey>(td::SecureString("local"), td::SecureString(),
|
||||
td::SecureString()))
|
||||
.move_as_ok();
|
||||
Wallet wallet{"", {key->public_key_, std::move(key->secret_)}};
|
||||
|
||||
auto account_address =
|
||||
sync_send(client, make_object<tonlib_api::testWallet_getAccountAddress>(
|
||||
make_object<tonlib_api::testWallet_initialAccountState>(wallet.key.public_key)))
|
||||
sync_send(client, make_object<tonlib_api::wallet_getAccountAddress>(
|
||||
make_object<tonlib_api::wallet_initialAccountState>(wallet.key.public_key)))
|
||||
.move_as_ok();
|
||||
|
||||
wallet.address = account_address->account_address_;
|
||||
|
||||
// get state of empty account
|
||||
auto state = get_account_state(client, wallet.address);
|
||||
ASSERT_EQ(-1, state.balance);
|
||||
ASSERT_EQ(AccountState::Empty, state.type);
|
||||
|
||||
return wallet;
|
||||
}
|
||||
|
||||
Wallet create_wallet(Client& client) {
|
||||
using tonlib_api::make_object;
|
||||
auto wallet = create_empty_wallet(client);
|
||||
|
||||
transfer_grams(client, test_giver_address(client), wallet.address, 6000000000, {});
|
||||
sync_send(client, make_object<tonlib_api::testWallet_init>(wallet.key.get_input_key())).ensure();
|
||||
while (!is_inited(client, wallet.address)) {
|
||||
client.receive(1);
|
||||
}
|
||||
LOG(ERROR) << get_balance(client, wallet.address);
|
||||
return wallet;
|
||||
}
|
||||
|
||||
std::string get_test_giver_address(Client& client) {
|
||||
return sync_send(client, tonlib_api::make_object<tonlib_api::testGiver_getAccountAddress>())
|
||||
.move_as_ok()
|
||||
->account_address_;
|
||||
}
|
||||
|
||||
void dump_transaction_history(Client& client, std::string address) {
|
||||
using tonlib_api::make_object;
|
||||
auto state = sync_send(client, make_object<tonlib_api::testGiver_getAccountState>()).move_as_ok();
|
||||
|
@ -180,166 +393,156 @@ void dump_transaction_history(Client& client, std::string address) {
|
|||
LOG(ERROR) << cnt;
|
||||
}
|
||||
|
||||
void test_estimate_fees_without_key(Client& client, const Wallet& wallet_a, const Wallet& wallet_b) {
|
||||
LOG(ERROR) << " SUBTEST: estimate fees without key";
|
||||
{
|
||||
auto query_id = create_send_grams_query(client, wallet_a, wallet_b.address, 0, "???", true, 0, true).move_as_ok();
|
||||
auto fees1 = query_estimate_fees(client, query_id, false);
|
||||
auto fees2 = query_estimate_fees(client, query_id, true);
|
||||
LOG(INFO) << "Fee without ignore_chksig\t" << fees1;
|
||||
LOG(INFO) << "Fee with ignore_chksig\t" << fees2;
|
||||
CHECK(fees1.first.gas_fee == 0);
|
||||
CHECK(fees2.first.gas_fee != 0);
|
||||
}
|
||||
}
|
||||
|
||||
void test_back_and_forth_transfer(Client& client, const Wallet& giver_wallet, bool flag) {
|
||||
LOG(ERROR) << "TEST: back and forth transfer";
|
||||
// just generate private key and address
|
||||
auto wallet_a = create_empty_wallet(client);
|
||||
LOG(INFO) << wallet_a.address;
|
||||
|
||||
// get state of empty account
|
||||
auto state = get_account_state(client, wallet_a.address);
|
||||
ASSERT_EQ(-1, state.balance);
|
||||
ASSERT_EQ(AccountState::Empty, state.type);
|
||||
|
||||
test_estimate_fees_without_key(client, giver_wallet, wallet_a);
|
||||
|
||||
// transfer from giver to a
|
||||
transfer_grams(client, giver_wallet, wallet_a.address, 1 * Gramm).ensure();
|
||||
state = get_account_state(client, wallet_a.address);
|
||||
ASSERT_EQ(1 * Gramm, state.balance);
|
||||
ASSERT_EQ(AccountState::Empty, state.type);
|
||||
|
||||
test_estimate_fees_without_key(client, wallet_a, giver_wallet);
|
||||
|
||||
if (flag) {
|
||||
// transfer from a to giver
|
||||
transfer_grams(client, wallet_a, giver_wallet.address, 5 * Gramm / 10).ensure();
|
||||
state = get_account_state(client, wallet_a.address);
|
||||
ASSERT_TRUE(state.balance < 5 * Gramm / 10);
|
||||
ASSERT_EQ(AccountState::Wallet, state.type);
|
||||
}
|
||||
|
||||
// transfer all remaining balance (test flag 128)
|
||||
transfer_grams(client, wallet_a, giver_wallet.address, state.balance).ensure();
|
||||
state = get_account_state(client, wallet_a.address);
|
||||
ASSERT_TRUE(state.balance == 0);
|
||||
ASSERT_EQ(AccountState::Wallet, state.type);
|
||||
}
|
||||
|
||||
void test_multisig(Client& client, const Wallet& giver_wallet) {
|
||||
LOG(ERROR) << "TEST: multisig";
|
||||
|
||||
int n = 16;
|
||||
int k = 10;
|
||||
std::vector<td::Ed25519::PrivateKey> private_keys;
|
||||
for (int i = 0; i < n; i++) {
|
||||
private_keys.push_back(td::Ed25519::generate_private_key().move_as_ok());
|
||||
}
|
||||
|
||||
auto ms = ton::MultisigWallet::create();
|
||||
auto init_data = ms->create_init_data(
|
||||
td::transform(private_keys, [](const auto& pk) { return pk.get_public_key().move_as_ok().as_octet_string(); }),
|
||||
k);
|
||||
ms = ton::MultisigWallet::create(init_data);
|
||||
auto raw_address = ms->get_address(ton::basechainId);
|
||||
auto address = raw_address.rserialize();
|
||||
transfer_grams(client, giver_wallet, address, 1 * Gramm).ensure();
|
||||
auto init_state = ms->get_init_state();
|
||||
|
||||
// Just transfer all (some) money back in one query
|
||||
vm::CellBuilder icb;
|
||||
ton::GenericAccount::store_int_message(icb, block::StdAddress::parse(giver_wallet.address).move_as_ok(),
|
||||
5 * Gramm / 10);
|
||||
icb.store_bytes("\0\0\0\0", 4);
|
||||
vm::CellString::store(icb, "Greatings from multisig", 35 * 8).ensure();
|
||||
ton::MultisigWallet::QueryBuilder qb(-1, icb.finalize());
|
||||
for (int i = 0; i < k - 1; i++) {
|
||||
qb.sign(i, private_keys[i]);
|
||||
}
|
||||
|
||||
auto query_id =
|
||||
create_raw_query(client, address, vm::std_boc_serialize(ms->get_state().code).move_as_ok().as_slice().str(),
|
||||
vm::std_boc_serialize(ms->get_state().data).move_as_ok().as_slice().str(),
|
||||
vm::std_boc_serialize(qb.create(k - 1, private_keys[k - 1])).move_as_ok().as_slice().str())
|
||||
.move_as_ok();
|
||||
auto fees = query_estimate_fees(client, query_id);
|
||||
|
||||
LOG(INFO) << "Expected src fees: " << fees.first;
|
||||
LOG(INFO) << "Expected dst fees: " << fees.second;
|
||||
auto a_state = get_account_state(client, address);
|
||||
query_send(client, query_id);
|
||||
auto new_a_state = wait_state_change(client, a_state, a_state.sync_utime + 30).move_as_ok();
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[]) {
|
||||
td::set_default_failure_signal_handler();
|
||||
using tonlib_api::make_object;
|
||||
|
||||
td::OptionsParser p;
|
||||
std::string global_config_str;
|
||||
std::string giver_key_str;
|
||||
std::string giver_key_pwd = "cucumber";
|
||||
std::string keystore_dir = "test-keystore";
|
||||
bool reset_keystore_dir = false;
|
||||
p.add_option('C', "global-config", "file to read global config", [&](td::Slice fname) {
|
||||
TRY_RESULT(str, td::read_file_str(fname.str()));
|
||||
global_config_str = std::move(str);
|
||||
LOG(ERROR) << global_config_str;
|
||||
return td::Status::OK();
|
||||
});
|
||||
p.add_option('G', "giver-key", "file with a wallet key that should be used as a giver", [&](td::Slice fname) {
|
||||
TRY_RESULT(str, td::read_file_str(fname.str()));
|
||||
giver_key_str = std::move(str);
|
||||
return td::Status::OK();
|
||||
});
|
||||
p.add_option('f', "force", "reser keystore dir", [&]() {
|
||||
reset_keystore_dir = true;
|
||||
return td::Status::OK();
|
||||
});
|
||||
p.run(argc, argv).ensure();
|
||||
|
||||
if (reset_keystore_dir) {
|
||||
td::rmrf(keystore_dir).ignore();
|
||||
td::mkdir(keystore_dir).ensure();
|
||||
}
|
||||
|
||||
SET_VERBOSITY_LEVEL(VERBOSITY_NAME(INFO));
|
||||
static_send(make_object<tonlib_api::setLogTagVerbosityLevel>("tonlib_query", 4)).ensure();
|
||||
auto tags = static_send(make_object<tonlib_api::getLogTags>()).move_as_ok()->tags_;
|
||||
for (auto& tag : tags) {
|
||||
static_send(make_object<tonlib_api::setLogTagVerbosityLevel>(tag, 4)).ensure();
|
||||
}
|
||||
|
||||
Client client;
|
||||
{
|
||||
sync_send(client, make_object<tonlib_api::init>(make_object<tonlib_api::options>(
|
||||
make_object<tonlib_api::config>(global_config_str, "", false, false),
|
||||
make_object<tonlib_api::keyStoreTypeDirectory>("."))))
|
||||
make_object<tonlib_api::keyStoreTypeDirectory>(keystore_dir))))
|
||||
.ensure();
|
||||
}
|
||||
//dump_transaction_history(client, get_test_giver_address(client));
|
||||
auto wallet_a = create_wallet(client);
|
||||
auto wallet_b = create_empty_wallet(client);
|
||||
transfer_grams(client, wallet_a.address, wallet_b.address, 3000000000, wallet_a.key.get_input_key());
|
||||
auto a = get_balance(client, wallet_a.address);
|
||||
auto b = get_balance(client, wallet_b.address);
|
||||
LOG(ERROR) << a << " " << b;
|
||||
return 0;
|
||||
{
|
||||
// init
|
||||
sync_send(client, make_object<tonlib_api::init>(make_object<tonlib_api::options>(
|
||||
make_object<tonlib_api::config>(global_config_str, "", false, false),
|
||||
make_object<tonlib_api::keyStoreTypeDirectory>("."))))
|
||||
.ensure();
|
||||
|
||||
auto key = sync_send(client, make_object<tonlib_api::createNewKey>(
|
||||
td::SecureString("local"), td::SecureString("mnemonic"), td::SecureString()))
|
||||
.move_as_ok();
|
||||
// wait till client is synchronized with blockchain.
|
||||
// not necessary, but synchronized will be trigged anyway later
|
||||
sync(client);
|
||||
|
||||
auto create_input_key = [&] {
|
||||
return make_object<tonlib_api::inputKey>(make_object<tonlib_api::key>(key->public_key_, key->secret_.copy()),
|
||||
td::SecureString("local"));
|
||||
};
|
||||
// give wallet with some test grams to run test
|
||||
auto giver_wallet = import_wallet_from_pkey(client, giver_key_str, giver_key_pwd);
|
||||
|
||||
auto public_key_raw = key->public_key_;
|
||||
td::Ed25519::PublicKey public_key_std(td::SecureString{public_key_raw});
|
||||
|
||||
sync_send(client, make_object<tonlib_api::options_setConfig>(
|
||||
make_object<tonlib_api::config>(global_config_str, "", false, false)))
|
||||
.ensure();
|
||||
|
||||
auto wallet_addr = GenericAccount::get_address(0, TestWallet::get_init_state(public_key_std));
|
||||
{
|
||||
auto account_address =
|
||||
sync_send(client, make_object<tonlib_api::testWallet_getAccountAddress>(
|
||||
make_object<tonlib_api::testWallet_initialAccountState>(public_key_raw)))
|
||||
.move_as_ok();
|
||||
ASSERT_EQ(wallet_addr.rserialize(), account_address->account_address_);
|
||||
}
|
||||
|
||||
std::string test_giver_address;
|
||||
{
|
||||
auto account_address = sync_send(client, make_object<tonlib_api::testGiver_getAccountAddress>()).move_as_ok();
|
||||
test_giver_address = account_address->account_address_;
|
||||
ASSERT_EQ(TestGiver::address().rserialize(), test_giver_address);
|
||||
}
|
||||
|
||||
{
|
||||
auto account_address =
|
||||
sync_send(
|
||||
client,
|
||||
make_object<tonlib_api::raw_getAccountAddress>(make_object<tonlib_api::raw_initialAccountState>(
|
||||
vm::std_boc_serialize(TestWallet::get_init_code()).move_as_ok().as_slice().str(),
|
||||
vm::std_boc_serialize(TestWallet::get_init_data(public_key_std)).move_as_ok().as_slice().str())))
|
||||
.move_as_ok();
|
||||
ASSERT_EQ(wallet_addr.rserialize(), account_address->account_address_);
|
||||
}
|
||||
|
||||
{
|
||||
auto state = sync_send(client, make_object<tonlib_api::raw_getAccountState>(
|
||||
make_object<tonlib_api::accountAddress>(wallet_addr.rserialize())))
|
||||
.move_as_ok();
|
||||
LOG(ERROR) << to_string(state);
|
||||
}
|
||||
|
||||
td::int32 seqno = 0;
|
||||
{
|
||||
auto state = sync_send(client, make_object<tonlib_api::testGiver_getAccountState>()).move_as_ok();
|
||||
LOG(ERROR) << to_string(state);
|
||||
seqno = state->seqno_;
|
||||
}
|
||||
|
||||
{
|
||||
sync_send(client, make_object<tonlib_api::testGiver_sendGrams>(
|
||||
make_object<tonlib_api::accountAddress>(wallet_addr.rserialize()), seqno,
|
||||
1000000000ll * 6666 / 1000, "GIFT"))
|
||||
.ensure();
|
||||
}
|
||||
|
||||
while (true) {
|
||||
auto state = sync_send(client, make_object<tonlib_api::testGiver_getAccountState>()).move_as_ok();
|
||||
if (state->seqno_ > seqno) {
|
||||
break;
|
||||
}
|
||||
client.receive(1);
|
||||
}
|
||||
|
||||
while (true) {
|
||||
auto state = sync_send(client, make_object<tonlib_api::raw_getAccountState>(
|
||||
make_object<tonlib_api::accountAddress>(wallet_addr.rserialize())))
|
||||
.move_as_ok();
|
||||
td::int64 grams_count = state->balance_;
|
||||
if (grams_count > 0) {
|
||||
LOG(ERROR) << "GOT " << grams_count;
|
||||
break;
|
||||
}
|
||||
client.receive(1);
|
||||
}
|
||||
|
||||
{ sync_send(client, make_object<tonlib_api::testWallet_init>(create_input_key())).ensure(); }
|
||||
|
||||
while (true) {
|
||||
auto r_state = sync_send(client, make_object<tonlib_api::testWallet_getAccountState>(
|
||||
make_object<tonlib_api::accountAddress>(wallet_addr.rserialize())));
|
||||
if (r_state.is_ok()) {
|
||||
LOG(ERROR) << to_string(r_state.ok());
|
||||
break;
|
||||
}
|
||||
client.receive(1);
|
||||
}
|
||||
|
||||
{
|
||||
sync_send(client, make_object<tonlib_api::generic_sendGrams>(
|
||||
create_input_key(), make_object<tonlib_api::accountAddress>(wallet_addr.rserialize()),
|
||||
make_object<tonlib_api::accountAddress>(test_giver_address), 1000000000ll * 3333 / 1000, 0,
|
||||
true, "GIFT"))
|
||||
.ensure();
|
||||
}
|
||||
while (true) {
|
||||
auto generic_state = sync_send(client, make_object<tonlib_api::generic_getAccountState>(
|
||||
make_object<tonlib_api::accountAddress>(wallet_addr.rserialize())))
|
||||
.move_as_ok();
|
||||
if (generic_state->get_id() == tonlib_api::generic_accountStateTestWallet::ID) {
|
||||
auto state = tonlib_api::move_object_as<tonlib_api::generic_accountStateTestWallet>(generic_state);
|
||||
if (state->account_state_->balance_ < 5617007000) {
|
||||
LOG(ERROR) << to_string(state);
|
||||
break;
|
||||
}
|
||||
}
|
||||
client.receive(1);
|
||||
}
|
||||
{
|
||||
auto generic_state = sync_send(client, make_object<tonlib_api::generic_getAccountState>(
|
||||
make_object<tonlib_api::accountAddress>(test_giver_address)))
|
||||
.move_as_ok();
|
||||
CHECK(generic_state->get_id() == tonlib_api::generic_accountStateTestGiver::ID);
|
||||
LOG(ERROR) << to_string(generic_state);
|
||||
}
|
||||
}
|
||||
test_back_and_forth_transfer(client, giver_wallet, false);
|
||||
test_back_and_forth_transfer(client, giver_wallet, true);
|
||||
test_multisig(client, giver_wallet);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -134,7 +134,7 @@ class Client::Impl final {
|
|||
};
|
||||
|
||||
Client::Client() : impl_(std::make_unique<Impl>()) {
|
||||
// At least it should be enough for everybody who uses TDLib
|
||||
// At least it should be enough for everybody who uses tonlib
|
||||
// FIXME
|
||||
//td::init_openssl_threads();
|
||||
}
|
||||
|
|
|
@ -19,12 +19,27 @@
|
|||
#include "tonlib/ExtClient.h"
|
||||
|
||||
#include "tonlib/LastBlock.h"
|
||||
#include "tonlib/LastConfig.h"
|
||||
|
||||
namespace tonlib {
|
||||
ExtClient::~ExtClient() {
|
||||
last_config_queries_.for_each([](auto id, auto &promise) { promise.set_error(TonlibError::Cancelled()); });
|
||||
last_block_queries_.for_each([](auto id, auto &promise) { promise.set_error(TonlibError::Cancelled()); });
|
||||
queries_.for_each([](auto id, auto &promise) { promise.set_error(TonlibError::Cancelled()); });
|
||||
}
|
||||
void ExtClient::with_last_config(td::Promise<LastConfigState> promise) {
|
||||
auto query_id = last_config_queries_.create(std::move(promise));
|
||||
td::Promise<LastConfigState> P = [query_id, self = this,
|
||||
actor_id = td::actor::actor_id()](td::Result<LastConfigState> result) {
|
||||
send_lambda(actor_id, [self, query_id, result = std::move(result)]() mutable {
|
||||
self->last_config_queries_.extract(query_id).set_result(std::move(result));
|
||||
});
|
||||
};
|
||||
if (client_.last_block_actor_.empty()) {
|
||||
return P.set_error(TonlibError::NoLiteServers());
|
||||
}
|
||||
td::actor::send_closure(client_.last_config_actor_, &LastConfig::get_last_config, std::move(P));
|
||||
}
|
||||
void ExtClient::with_last_block(td::Promise<LastBlockState> promise) {
|
||||
auto query_id = last_block_queries_.create(std::move(promise));
|
||||
td::Promise<LastBlockState> P = [query_id, self = this,
|
||||
|
|
|
@ -33,10 +33,13 @@
|
|||
|
||||
namespace tonlib {
|
||||
class LastBlock;
|
||||
class LastConfig;
|
||||
struct LastBlockState;
|
||||
struct LastConfigState;
|
||||
struct ExtClientRef {
|
||||
td::actor::ActorId<ton::adnl::AdnlExtClient> andl_ext_client_;
|
||||
td::actor::ActorId<LastBlock> last_block_actor_;
|
||||
td::actor::ActorId<LastConfig> last_config_actor_;
|
||||
};
|
||||
|
||||
class ExtClient {
|
||||
|
@ -56,6 +59,7 @@ class ExtClient {
|
|||
~ExtClient();
|
||||
|
||||
void with_last_block(td::Promise<LastBlockState> promise);
|
||||
void with_last_config(td::Promise<LastConfigState> promise);
|
||||
|
||||
template <class QueryT>
|
||||
void send_query(QueryT query, td::Promise<typename QueryT::ReturnType> promise, td::int32 seq_no = -1) {
|
||||
|
@ -94,6 +98,7 @@ class ExtClient {
|
|||
ExtClientRef client_;
|
||||
td::Container<td::Promise<td::BufferSlice>> queries_;
|
||||
td::Container<td::Promise<LastBlockState>> last_block_queries_;
|
||||
td::Container<td::Promise<LastConfigState>> last_config_queries_;
|
||||
|
||||
void send_raw_query(td::BufferSlice query, td::Promise<td::BufferSlice> promise);
|
||||
};
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#include "ExtClientLazy.h"
|
||||
#include "TonlibError.h"
|
||||
namespace tonlib {
|
||||
|
||||
class ExtClientLazyImp : public ton::adnl::AdnlExtClient {
|
||||
|
@ -28,12 +29,18 @@ class ExtClientLazyImp : public ton::adnl::AdnlExtClient {
|
|||
|
||||
void check_ready(td::Promise<td::Unit> promise) override {
|
||||
before_query();
|
||||
if (client_.empty()) {
|
||||
return promise.set_error(TonlibError::Cancelled());
|
||||
}
|
||||
send_closure(client_, &ton::adnl::AdnlExtClient::check_ready, std::move(promise));
|
||||
}
|
||||
|
||||
void send_query(std::string name, td::BufferSlice data, td::Timestamp timeout,
|
||||
td::Promise<td::BufferSlice> promise) override {
|
||||
before_query();
|
||||
if (client_.empty()) {
|
||||
return promise.set_error(TonlibError::Cancelled());
|
||||
}
|
||||
send_closure(client_, &ton::adnl::AdnlExtClient::send_query, std::move(name), std::move(data), timeout,
|
||||
std::move(promise));
|
||||
}
|
||||
|
|
|
@ -106,6 +106,9 @@ td::Result<KeyStorage::ExportedKey> KeyStorage::export_key(InputKey input_key) {
|
|||
}
|
||||
|
||||
td::Result<KeyStorage::PrivateKey> KeyStorage::load_private_key(InputKey input_key) {
|
||||
if (is_fake_input_key(input_key)) {
|
||||
return fake_private_key();
|
||||
}
|
||||
TRY_RESULT(decrypted_key, export_decrypted_key(std::move(input_key)));
|
||||
PrivateKey private_key;
|
||||
private_key.private_key = decrypted_key.private_key.as_octet_string();
|
||||
|
@ -166,20 +169,46 @@ td::Result<KeyStorage::Key> KeyStorage::import_pem_key(td::Slice local_password,
|
|||
return save_key(DecryptedKey({}, std::move(key)), local_password);
|
||||
}
|
||||
|
||||
static std::string dummy_secret = "dummy secret of 32 bytes length!";
|
||||
td::SecureString get_dummy_secret() {
|
||||
return td::SecureString("dummy secret of 32 bytes length!");
|
||||
}
|
||||
td::Result<KeyStorage::ExportedEncryptedKey> KeyStorage::export_encrypted_key(InputKey input_key,
|
||||
td::Slice key_password) {
|
||||
TRY_RESULT(decrypted_key, export_decrypted_key(std::move(input_key)));
|
||||
auto res = decrypted_key.encrypt(key_password, dummy_secret);
|
||||
auto res = decrypted_key.encrypt(key_password, get_dummy_secret());
|
||||
return ExportedEncryptedKey{std::move(res.encrypted_data)};
|
||||
}
|
||||
|
||||
td::Result<KeyStorage::Key> KeyStorage::import_encrypted_key(td::Slice local_password, td::Slice key_password,
|
||||
ExportedEncryptedKey exported_key) {
|
||||
EncryptedKey encrypted_key{std::move(exported_key.data), td::Ed25519::PublicKey(td::SecureString()),
|
||||
td::SecureString(dummy_secret)};
|
||||
get_dummy_secret()};
|
||||
TRY_RESULT_PREFIX(decrypted_key, encrypted_key.decrypt(key_password, false), TonlibError::KeyDecrypt());
|
||||
return save_key(std::move(decrypted_key), local_password);
|
||||
}
|
||||
|
||||
KeyStorage::PrivateKey KeyStorage::fake_private_key() {
|
||||
return PrivateKey{td::SecureString(32, 0)};
|
||||
}
|
||||
|
||||
KeyStorage::InputKey KeyStorage::fake_input_key() {
|
||||
return InputKey{{td::SecureString(32, 0), td::SecureString(32, 0)}, {}};
|
||||
}
|
||||
|
||||
bool KeyStorage::is_fake_input_key(InputKey &input_key) {
|
||||
auto is_zero = [](td::Slice slice, size_t size) {
|
||||
if (slice.size() != size) {
|
||||
return false;
|
||||
}
|
||||
for (auto c : slice) {
|
||||
if (c != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
};
|
||||
return is_zero(input_key.local_password, 0) && is_zero(input_key.key.secret, 32) &&
|
||||
is_zero(input_key.key.public_key, 32);
|
||||
}
|
||||
|
||||
} // namespace tonlib
|
||||
|
|
|
@ -69,6 +69,10 @@ class KeyStorage {
|
|||
|
||||
td::Result<PrivateKey> load_private_key(InputKey input_key);
|
||||
|
||||
static PrivateKey fake_private_key();
|
||||
static InputKey fake_input_key();
|
||||
static bool is_fake_input_key(InputKey& input_key);
|
||||
|
||||
private:
|
||||
std::shared_ptr<KeyValue> kv_;
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#include "tonlib/LastBlock.h"
|
||||
#include "tonlib/LastConfig.h"
|
||||
|
||||
#include "tonlib/utils.h"
|
||||
|
||||
|
@ -271,7 +272,7 @@ void LastBlock::update_zero_state(ton::ZeroStateIdExt zero_state_id, td::Slice s
|
|||
}
|
||||
|
||||
if (!state_.zero_state_id.is_valid()) {
|
||||
LOG(INFO) << "Init zerostate from " << source << ": " << zero_state_id.to_str();
|
||||
VLOG(last_block) << "Init zerostate from " << source << ": " << zero_state_id.to_str();
|
||||
state_.zero_state_id = std::move(zero_state_id);
|
||||
return;
|
||||
}
|
||||
|
@ -295,7 +296,7 @@ bool LastBlock::update_mc_last_block(ton::BlockIdExt mc_block_id) {
|
|||
}
|
||||
if (!state_.last_block_id.is_valid() || state_.last_block_id.id.seqno < mc_block_id.id.seqno) {
|
||||
state_.last_block_id = mc_block_id;
|
||||
LOG(INFO) << "Update masterchain block id: " << state_.last_block_id.to_str();
|
||||
VLOG(last_block) << "Update masterchain block id: " << state_.last_block_id.to_str();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
@ -311,7 +312,7 @@ bool LastBlock::update_mc_last_key_block(ton::BlockIdExt mc_key_block_id) {
|
|||
}
|
||||
if (!state_.last_key_block_id.is_valid() || state_.last_key_block_id.id.seqno < mc_key_block_id.id.seqno) {
|
||||
state_.last_key_block_id = mc_key_block_id;
|
||||
LOG(INFO) << "Update masterchain key block id: " << state_.last_key_block_id.to_str();
|
||||
VLOG(last_block) << "Update masterchain key block id: " << state_.last_key_block_id.to_str();
|
||||
//LOG(ERROR) << td::int64(state_.last_key_block_id.id.shard) << " "
|
||||
//<< td::base64_encode(state_.last_key_block_id.file_hash.as_slice()) << " "
|
||||
//<< td::base64_encode(state_.last_key_block_id.root_hash.as_slice());
|
||||
|
@ -330,7 +331,7 @@ bool LastBlock::update_init_block(ton::BlockIdExt init_block_id) {
|
|||
}
|
||||
if (state_.init_block_id != init_block_id) {
|
||||
state_.init_block_id = init_block_id;
|
||||
LOG(INFO) << "Update init block id: " << state_.init_block_id.to_str();
|
||||
VLOG(last_block) << "Update init block id: " << state_.init_block_id.to_str();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
|
152
tonlib/tonlib/LastConfig.cpp
Normal file
152
tonlib/tonlib/LastConfig.cpp
Normal file
|
@ -0,0 +1,152 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#include "tonlib/LastConfig.h"
|
||||
|
||||
#include "tonlib/utils.h"
|
||||
|
||||
#include "ton/lite-tl.hpp"
|
||||
#include "block/check-proof.h"
|
||||
#include "block/mc-config.h"
|
||||
#include "block/block-auto.h"
|
||||
|
||||
#include "lite-client/lite-client-common.h"
|
||||
|
||||
#include "LastBlock.h"
|
||||
|
||||
namespace tonlib {
|
||||
|
||||
// init_state <-> last_key_block
|
||||
// state.valitated_init_state
|
||||
// last_key_block ->
|
||||
//
|
||||
td::StringBuilder& operator<<(td::StringBuilder& sb, const LastConfigState& state) {
|
||||
return sb;
|
||||
}
|
||||
|
||||
LastConfig::LastConfig(ExtClientRef client, td::unique_ptr<Callback> callback) : callback_(std::move(callback)) {
|
||||
client_.set_client(client);
|
||||
VLOG(last_block) << "State: " << state_;
|
||||
}
|
||||
|
||||
void LastConfig::get_last_config(td::Promise<LastConfigState> promise) {
|
||||
if (promises_.empty() && get_config_state_ == QueryState::Done) {
|
||||
VLOG(last_config) << "start";
|
||||
VLOG(last_config) << "get_config: reset";
|
||||
get_config_state_ = QueryState::Empty;
|
||||
}
|
||||
|
||||
promises_.push_back(std::move(promise));
|
||||
loop();
|
||||
}
|
||||
|
||||
void LastConfig::with_last_block(td::Result<LastBlockState> r_last_block) {
|
||||
if (r_last_block.is_error()) {
|
||||
on_error(r_last_block.move_as_error());
|
||||
return;
|
||||
}
|
||||
|
||||
auto last_block = r_last_block.move_as_ok();
|
||||
auto params = params_;
|
||||
client_.send_query(ton::lite_api::liteServer_getConfigParams(0, create_tl_lite_block_id(last_block.last_block_id),
|
||||
std::move(params)),
|
||||
[this](auto r_config) { this->on_config(std::move(r_config)); });
|
||||
}
|
||||
|
||||
void LastConfig::on_config(td::Result<ton::ton_api::object_ptr<ton::lite_api::liteServer_configInfo>> r_config) {
|
||||
auto status = process_config(std::move(r_config));
|
||||
if (status.is_ok()) {
|
||||
on_ok();
|
||||
get_config_state_ = QueryState::Done;
|
||||
} else {
|
||||
on_error(std::move(status));
|
||||
get_config_state_ = QueryState::Empty;
|
||||
}
|
||||
}
|
||||
|
||||
td::Status LastConfig::process_config(
|
||||
td::Result<ton::ton_api::object_ptr<ton::lite_api::liteServer_configInfo>> r_config) {
|
||||
TRY_RESULT(raw_config, std::move(r_config));
|
||||
TRY_STATUS_PREFIX(TRY_VM(process_config_proof(std::move(raw_config))), TonlibError::ValidateConfig());
|
||||
return td::Status::OK();
|
||||
}
|
||||
|
||||
td::Status LastConfig::process_config_proof(ton::ton_api::object_ptr<ton::lite_api::liteServer_configInfo> raw_config) {
|
||||
auto blkid = create_block_id(raw_config->id_);
|
||||
if (!blkid.is_masterchain_ext()) {
|
||||
return td::Status::Error(PSLICE() << "reference block " << blkid.to_str()
|
||||
<< " for the configuration is not a valid masterchain block");
|
||||
}
|
||||
TRY_RESULT(state, block::check_extract_state_proof(blkid, raw_config->state_proof_.as_slice(),
|
||||
raw_config->config_proof_.as_slice()));
|
||||
TRY_RESULT(config, block::Config::extract_from_state(std::move(state), 0));
|
||||
|
||||
for (auto i : params_) {
|
||||
VLOG(last_config) << "ConfigParam(" << i << ") = ";
|
||||
auto value = config->get_config_param(i);
|
||||
if (value.is_null()) {
|
||||
VLOG(last_config) << "(null)\n";
|
||||
} else {
|
||||
std::ostringstream os;
|
||||
if (i >= 0) {
|
||||
block::gen::ConfigParam{i}.print_ref(os, value);
|
||||
os << std::endl;
|
||||
}
|
||||
vm::load_cell_slice(value).print_rec(os);
|
||||
VLOG(last_config) << os.str();
|
||||
}
|
||||
}
|
||||
state_.config.reset(config.release());
|
||||
return td::Status::OK();
|
||||
}
|
||||
|
||||
void LastConfig::loop() {
|
||||
if (promises_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (get_config_state_ == QueryState::Empty) {
|
||||
VLOG(last_block) << "get_config: start";
|
||||
get_config_state_ = QueryState::Active;
|
||||
client_.with_last_block(
|
||||
[self = this](td::Result<LastBlockState> r_last_block) { self->with_last_block(std::move(r_last_block)); });
|
||||
}
|
||||
}
|
||||
|
||||
void LastConfig::on_ok() {
|
||||
VLOG(last_block) << "ok " << state_;
|
||||
for (auto& promise : promises_) {
|
||||
auto state = state_;
|
||||
promise.set_value(std::move(state));
|
||||
}
|
||||
promises_.clear();
|
||||
}
|
||||
|
||||
void LastConfig::on_error(td::Status status) {
|
||||
VLOG(last_config) << "error " << status;
|
||||
for (auto& promise : promises_) {
|
||||
promise.set_error(status.clone());
|
||||
}
|
||||
promises_.clear();
|
||||
}
|
||||
|
||||
void LastConfig::tear_down() {
|
||||
on_error(TonlibError::Cancelled());
|
||||
}
|
||||
|
||||
} // namespace tonlib
|
70
tonlib/tonlib/LastConfig.h
Normal file
70
tonlib/tonlib/LastConfig.h
Normal file
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
This file is part of TON Blockchain Library.
|
||||
|
||||
TON Blockchain Library is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Lesser General Public License as published by
|
||||
the Free Software Foundation, either version 2 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
TON Blockchain Library is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Lesser General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Lesser General Public License
|
||||
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
Copyright 2017-2019 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
#include "td/actor/actor.h"
|
||||
|
||||
#include "tonlib/Config.h"
|
||||
#include "tonlib/ExtClient.h"
|
||||
|
||||
#include "td/utils/CancellationToken.h"
|
||||
#include "td/utils/tl_helpers.h"
|
||||
|
||||
#include "block/mc-config.h"
|
||||
|
||||
namespace tonlib {
|
||||
struct LastConfigState {
|
||||
std::shared_ptr<const block::Config> config;
|
||||
};
|
||||
|
||||
td::StringBuilder& operator<<(td::StringBuilder& sb, const LastConfigState& state);
|
||||
|
||||
class LastConfig : public td::actor::Actor {
|
||||
public:
|
||||
class Callback {
|
||||
public:
|
||||
virtual ~Callback() {
|
||||
}
|
||||
};
|
||||
|
||||
explicit LastConfig(ExtClientRef client, td::unique_ptr<Callback> callback);
|
||||
void get_last_config(td::Promise<LastConfigState> promise);
|
||||
|
||||
private:
|
||||
td::unique_ptr<Callback> callback_;
|
||||
ExtClient client_;
|
||||
LastConfigState state_;
|
||||
|
||||
enum class QueryState { Empty, Active, Done };
|
||||
QueryState get_config_state_{QueryState::Empty};
|
||||
|
||||
std::vector<td::Promise<LastConfigState>> promises_;
|
||||
std::vector<td::int32> params_{18, 20, 21, 24, 25};
|
||||
|
||||
void with_last_block(td::Result<LastBlockState> r_last_block);
|
||||
void on_config(td::Result<ton::ton_api::object_ptr<ton::lite_api::liteServer_configInfo>> r_config);
|
||||
td::Status process_config(td::Result<ton::ton_api::object_ptr<ton::lite_api::liteServer_configInfo>> r_config);
|
||||
td::Status process_config_proof(ton::ton_api::object_ptr<ton::lite_api::liteServer_configInfo> config);
|
||||
|
||||
void on_ok();
|
||||
void on_error(td::Status status);
|
||||
|
||||
void loop() override;
|
||||
void tear_down() override;
|
||||
};
|
||||
} // namespace tonlib
|
|
@ -32,14 +32,22 @@
|
|||
|
||||
namespace tonlib {
|
||||
|
||||
static std::mutex logging_mutex;
|
||||
static td::FileLog file_log;
|
||||
static td::TsLog ts_log(&file_log);
|
||||
static td::NullLog null_log;
|
||||
struct LogData {
|
||||
std::mutex logging_mutex;
|
||||
td::FileLog file_log;
|
||||
td::TsLog ts_log{&file_log};
|
||||
td::NullLog null_log;
|
||||
};
|
||||
|
||||
auto &log_data() {
|
||||
static LogData data;
|
||||
return data;
|
||||
}
|
||||
|
||||
#define ADD_TAG(tag) \
|
||||
{ #tag, &VERBOSITY_NAME(tag) }
|
||||
static const std::map<td::Slice, int *> log_tags{ADD_TAG(tonlib_query), ADD_TAG(last_block)};
|
||||
static const std::map<td::Slice, int *> log_tags{ADD_TAG(tonlib_query), ADD_TAG(last_block), ADD_TAG(last_config),
|
||||
ADD_TAG(lite_server)};
|
||||
#undef ADD_TAG
|
||||
|
||||
td::Status Logging::set_current_stream(tonlib_api::object_ptr<tonlib_api::LogStream> stream) {
|
||||
|
@ -47,7 +55,7 @@ td::Status Logging::set_current_stream(tonlib_api::object_ptr<tonlib_api::LogStr
|
|||
return td::Status::Error("Log stream must not be empty");
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(logging_mutex);
|
||||
std::lock_guard<std::mutex> lock(log_data().logging_mutex);
|
||||
switch (stream->get_id()) {
|
||||
case tonlib_api::logStreamDefault::ID:
|
||||
td::log_interface = td::default_log_interface;
|
||||
|
@ -59,13 +67,13 @@ td::Status Logging::set_current_stream(tonlib_api::object_ptr<tonlib_api::LogStr
|
|||
return td::Status::Error("Max log file size should be positive");
|
||||
}
|
||||
|
||||
TRY_STATUS(file_log.init(file_stream->path_, max_log_file_size));
|
||||
TRY_STATUS(log_data().file_log.init(file_stream->path_, max_log_file_size));
|
||||
std::atomic_thread_fence(std::memory_order_release); // better than nothing
|
||||
td::log_interface = &ts_log;
|
||||
td::log_interface = &log_data().ts_log;
|
||||
return td::Status::OK();
|
||||
}
|
||||
case tonlib_api::logStreamEmpty::ID:
|
||||
td::log_interface = &null_log;
|
||||
td::log_interface = &log_data().null_log;
|
||||
return td::Status::OK();
|
||||
default:
|
||||
UNREACHABLE();
|
||||
|
@ -74,22 +82,22 @@ td::Status Logging::set_current_stream(tonlib_api::object_ptr<tonlib_api::LogStr
|
|||
}
|
||||
|
||||
td::Result<tonlib_api::object_ptr<tonlib_api::LogStream>> Logging::get_current_stream() {
|
||||
std::lock_guard<std::mutex> lock(logging_mutex);
|
||||
std::lock_guard<std::mutex> lock(log_data().logging_mutex);
|
||||
if (td::log_interface == td::default_log_interface) {
|
||||
return tonlib_api::make_object<tonlib_api::logStreamDefault>();
|
||||
}
|
||||
if (td::log_interface == &null_log) {
|
||||
if (td::log_interface == &log_data().null_log) {
|
||||
return tonlib_api::make_object<tonlib_api::logStreamEmpty>();
|
||||
}
|
||||
if (td::log_interface == &ts_log) {
|
||||
return tonlib_api::make_object<tonlib_api::logStreamFile>(file_log.get_path().str(),
|
||||
file_log.get_rotate_threshold());
|
||||
if (td::log_interface == &log_data().ts_log) {
|
||||
return tonlib_api::make_object<tonlib_api::logStreamFile>(log_data().file_log.get_path().str(),
|
||||
log_data().file_log.get_rotate_threshold());
|
||||
}
|
||||
return td::Status::Error("Log stream is unrecognized");
|
||||
}
|
||||
|
||||
td::Status Logging::set_verbosity_level(int new_verbosity_level) {
|
||||
std::lock_guard<std::mutex> lock(logging_mutex);
|
||||
std::lock_guard<std::mutex> lock(log_data().logging_mutex);
|
||||
if (0 <= new_verbosity_level && new_verbosity_level <= VERBOSITY_NAME(NEVER)) {
|
||||
SET_VERBOSITY_LEVEL(VERBOSITY_NAME(FATAL) + new_verbosity_level);
|
||||
return td::Status::OK();
|
||||
|
@ -99,7 +107,7 @@ td::Status Logging::set_verbosity_level(int new_verbosity_level) {
|
|||
}
|
||||
|
||||
int Logging::get_verbosity_level() {
|
||||
std::lock_guard<std::mutex> lock(logging_mutex);
|
||||
std::lock_guard<std::mutex> lock(log_data().logging_mutex);
|
||||
return GET_VERBOSITY_LEVEL();
|
||||
}
|
||||
|
||||
|
@ -113,7 +121,7 @@ td::Status Logging::set_tag_verbosity_level(td::Slice tag, int new_verbosity_lev
|
|||
return td::Status::Error("Log tag is not found");
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(logging_mutex);
|
||||
std::lock_guard<std::mutex> lock(log_data().logging_mutex);
|
||||
*it->second = td::clamp(new_verbosity_level, 1, VERBOSITY_NAME(NEVER));
|
||||
return td::Status::OK();
|
||||
}
|
||||
|
@ -124,7 +132,7 @@ td::Result<int> Logging::get_tag_verbosity_level(td::Slice tag) {
|
|||
return td::Status::Error("Log tag is not found");
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(logging_mutex);
|
||||
std::lock_guard<std::mutex> lock(log_data().logging_mutex);
|
||||
return *it->second;
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -34,6 +34,17 @@
|
|||
#include <map>
|
||||
|
||||
namespace tonlib {
|
||||
namespace int_api {
|
||||
struct GetAccountState;
|
||||
struct GetPrivateKey;
|
||||
struct SendMessage;
|
||||
inline std::string to_string(const int_api::SendMessage&) {
|
||||
return "Send message";
|
||||
}
|
||||
} // namespace int_api
|
||||
class AccountState;
|
||||
class Query;
|
||||
|
||||
class TonlibClient : public td::actor::Actor {
|
||||
public:
|
||||
template <class T>
|
||||
|
@ -66,6 +77,7 @@ class TonlibClient : public td::actor::Actor {
|
|||
td::actor::ActorOwn<ton::adnl::AdnlExtClient> raw_client_;
|
||||
td::actor::ActorId<ExtClientOutbound> ext_client_outbound_;
|
||||
td::actor::ActorOwn<LastBlock> raw_last_block_;
|
||||
td::actor::ActorOwn<LastConfig> raw_last_config_;
|
||||
ExtClient client_;
|
||||
|
||||
td::CancellationTokenSource source_;
|
||||
|
@ -76,6 +88,7 @@ class TonlibClient : public td::actor::Actor {
|
|||
ExtClientRef get_client_ref();
|
||||
void init_ext_client();
|
||||
void init_last_block();
|
||||
void init_last_config();
|
||||
|
||||
bool is_closing_{false};
|
||||
td::uint32 ref_cnt_{1};
|
||||
|
@ -127,9 +140,53 @@ class TonlibClient : public td::actor::Actor {
|
|||
static object_ptr<tonlib_api::Object> do_static_request(const tonlib_api::decrypt& request);
|
||||
static object_ptr<tonlib_api::Object> do_static_request(const tonlib_api::kdf& request);
|
||||
|
||||
template <class P>
|
||||
td::Status do_request(const tonlib_api::runTests& request, P&&);
|
||||
template <class P>
|
||||
td::Status do_request(const tonlib_api::raw_getAccountAddress& request, P&&);
|
||||
template <class P>
|
||||
td::Status do_request(const tonlib_api::testWallet_getAccountAddress& request, P&&);
|
||||
template <class P>
|
||||
td::Status do_request(const tonlib_api::wallet_getAccountAddress& request, P&&);
|
||||
template <class P>
|
||||
td::Status do_request(const tonlib_api::testGiver_getAccountAddress& request, P&&);
|
||||
template <class P>
|
||||
td::Status do_request(const tonlib_api::packAccountAddress& request, P&&);
|
||||
template <class P>
|
||||
td::Status do_request(const tonlib_api::unpackAccountAddress& request, P&&);
|
||||
template <class P>
|
||||
td::Status do_request(tonlib_api::getBip39Hints& request, P&&);
|
||||
|
||||
template <class P>
|
||||
td::Status do_request(tonlib_api::setLogStream& request, P&&);
|
||||
template <class P>
|
||||
td::Status do_request(const tonlib_api::getLogStream& request, P&&);
|
||||
template <class P>
|
||||
td::Status do_request(const tonlib_api::setLogVerbosityLevel& request, P&&);
|
||||
template <class P>
|
||||
td::Status do_request(const tonlib_api::setLogTagVerbosityLevel& request, P&&);
|
||||
template <class P>
|
||||
td::Status do_request(const tonlib_api::getLogVerbosityLevel& request, P&&);
|
||||
template <class P>
|
||||
td::Status do_request(const tonlib_api::getLogTagVerbosityLevel& request, P&&);
|
||||
template <class P>
|
||||
td::Status do_request(const tonlib_api::getLogTags& request, P&&);
|
||||
template <class P>
|
||||
td::Status do_request(const tonlib_api::addLogMessage& request, P&&);
|
||||
|
||||
template <class P>
|
||||
td::Status do_request(const tonlib_api::encrypt& request, P&&);
|
||||
template <class P>
|
||||
td::Status do_request(const tonlib_api::decrypt& request, P&&);
|
||||
template <class P>
|
||||
td::Status do_request(const tonlib_api::kdf& request, P&&);
|
||||
|
||||
template <class T, class P>
|
||||
td::Status do_request(const T& request, P&& promise) {
|
||||
return td::Status::Error(400, "Function is unsupported");
|
||||
void make_request(T&& request, P&& promise) {
|
||||
auto status = do_request(std::forward<T>(request), std::move(promise));
|
||||
if (status.is_error()) {
|
||||
promise.operator()(std::move(status));
|
||||
}
|
||||
}
|
||||
|
||||
td::Status set_config(object_ptr<tonlib_api::config> config);
|
||||
|
@ -138,6 +195,11 @@ class TonlibClient : public td::actor::Actor {
|
|||
td::Status do_request(tonlib_api::options_setConfig& request, td::Promise<object_ptr<tonlib_api::ok>>&& promise);
|
||||
|
||||
td::Status do_request(const tonlib_api::raw_sendMessage& request, td::Promise<object_ptr<tonlib_api::ok>>&& promise);
|
||||
td::Status do_request(const tonlib_api::raw_createAndSendMessage& request,
|
||||
td::Promise<object_ptr<tonlib_api::ok>>&& promise);
|
||||
td::Status do_request(const tonlib_api::raw_createQuery& request,
|
||||
td::Promise<object_ptr<tonlib_api::query_info>>&& promise);
|
||||
|
||||
td::Status do_request(tonlib_api::raw_getAccountState& request,
|
||||
td::Promise<object_ptr<tonlib_api::raw_accountState>>&& promise);
|
||||
td::Status do_request(tonlib_api::raw_getTransactions& request,
|
||||
|
@ -191,6 +253,48 @@ class TonlibClient : public td::actor::Actor {
|
|||
td::Status do_request(const tonlib_api::onLiteServerQueryError& request,
|
||||
td::Promise<object_ptr<tonlib_api::ok>>&& promise);
|
||||
|
||||
td::int64 next_query_id_{0};
|
||||
std::map<td::int64, td::unique_ptr<Query>> queries_;
|
||||
td::int64 register_query(td::unique_ptr<Query> query);
|
||||
td::Result<tonlib_api::object_ptr<tonlib_api::query_info>> get_query_info(td::int64 id);
|
||||
void finish_create_query(td::Result<td::unique_ptr<Query>> r_query,
|
||||
td::Promise<object_ptr<tonlib_api::query_info>>&& promise);
|
||||
void finish_send_query(td::Result<td::unique_ptr<Query>> r_query,
|
||||
td::Promise<object_ptr<tonlib_api::sendGramsResult>>&& promise);
|
||||
void query_estimate_fees(td::int64 id, bool ignore_chksig, td::Result<LastConfigState> r_state,
|
||||
td::Promise<object_ptr<tonlib_api::query_fees>>&& promise);
|
||||
|
||||
td::Status do_request(const tonlib_api::query_getInfo& request,
|
||||
td::Promise<object_ptr<tonlib_api::query_info>>&& promise);
|
||||
td::Status do_request(const tonlib_api::query_estimateFees& request,
|
||||
td::Promise<object_ptr<tonlib_api::query_fees>>&& promise);
|
||||
td::Status do_request(const tonlib_api::query_send& request, td::Promise<object_ptr<tonlib_api::ok>>&& promise);
|
||||
td::Status do_request(tonlib_api::query_forget& request, td::Promise<object_ptr<tonlib_api::ok>>&& promise);
|
||||
|
||||
td::Status do_request(tonlib_api::generic_createSendGramsQuery& request,
|
||||
td::Promise<object_ptr<tonlib_api::query_info>>&& promise);
|
||||
|
||||
td::int64 next_smc_id_{0};
|
||||
std::map<td::int64, td::unique_ptr<AccountState>> smcs_;
|
||||
|
||||
td::int64 register_smc(td::unique_ptr<AccountState> smc);
|
||||
td::Result<tonlib_api::object_ptr<tonlib_api::smc_info>> get_smc_info(td::int64 id);
|
||||
void finish_load_smc(td::unique_ptr<AccountState> query, td::Promise<object_ptr<tonlib_api::smc_info>>&& promise);
|
||||
td::Status do_request(const tonlib_api::smc_load& request, td::Promise<object_ptr<tonlib_api::smc_info>>&& promise);
|
||||
td::Status do_request(const tonlib_api::smc_getCode& request,
|
||||
td::Promise<object_ptr<tonlib_api::tvm_cell>>&& promise);
|
||||
td::Status do_request(const tonlib_api::smc_getData& request,
|
||||
td::Promise<object_ptr<tonlib_api::tvm_cell>>&& promise);
|
||||
td::Status do_request(const tonlib_api::smc_getState& request,
|
||||
td::Promise<object_ptr<tonlib_api::tvm_cell>>&& promise);
|
||||
|
||||
td::Status do_request(int_api::GetAccountState request, td::Promise<td::unique_ptr<AccountState>>&&);
|
||||
td::Status do_request(int_api::GetPrivateKey request, td::Promise<KeyStorage::PrivateKey>&&);
|
||||
td::Status do_request(int_api::SendMessage request, td::Promise<td::Unit>&& promise);
|
||||
|
||||
td::Status do_request(const tonlib_api::liteServer_getInfo& request,
|
||||
td::Promise<object_ptr<tonlib_api::liteServer_info>>&& promise);
|
||||
|
||||
void proxy_request(td::int64 query_id, std::string data);
|
||||
|
||||
friend class TonlibQueryActor;
|
||||
|
|
|
@ -27,6 +27,8 @@
|
|||
// INVALID_MNEMONIC
|
||||
// INVALID_BAG_OF_CELLS
|
||||
// INVALID_PUBLIC_KEY
|
||||
// INVALID_QUERY_ID
|
||||
// INVALID_SMC_ID
|
||||
// INVALID_ACCOUNT_ADDRESS
|
||||
// INVALID_CONFIG
|
||||
// INVALID_PEM_KEY
|
||||
|
@ -65,6 +67,12 @@ struct TonlibError {
|
|||
static td::Status InvalidAccountAddress() {
|
||||
return td::Status::Error(400, "INVALID_ACCOUNT_ADDRESS");
|
||||
}
|
||||
static td::Status InvalidQueryId() {
|
||||
return td::Status::Error(400, "INVALID_QUERY_ID");
|
||||
}
|
||||
static td::Status InvalidSmcId() {
|
||||
return td::Status::Error(400, "INVALID_SMC_ID");
|
||||
}
|
||||
static td::Status InvalidConfig(td::Slice reason) {
|
||||
return td::Status::Error(400, PSLICE() << "INVALID_CONFIG: " << reason);
|
||||
}
|
||||
|
@ -110,6 +118,9 @@ struct TonlibError {
|
|||
static td::Status ValidateTransactions() {
|
||||
return td::Status::Error(500, "VALIDATE_TRANSACTION");
|
||||
}
|
||||
static td::Status ValidateConfig() {
|
||||
return td::Status::Error(500, "VALIDATE_CONFIG");
|
||||
}
|
||||
static td::Status ValidateZeroState(td::Slice message) {
|
||||
return td::Status::Error(500, PSLICE() << "VALIDATE_ZERO_STATE: " << message);
|
||||
}
|
||||
|
|
|
@ -20,6 +20,57 @@
|
|||
#include <iostream>
|
||||
#include <map>
|
||||
|
||||
// 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 [<command>]\tThis help\n" // TODO: support [<command>]
|
||||
// "quit\tExit\n";
|
||||
// "sendfile <filename>\tLoad a serialized message from <filename> and send it to server\n"
|
||||
//
|
||||
// "saveaccount[code|data] <filename> <addr> [<block-id-ext>]\tSaves into specified file the most recent state "
|
||||
// "runmethod <addr> <method-id> <params>...\tRuns GET method <method-id> of account <addr> "
|
||||
// "with specified parameters\n"
|
||||
//
|
||||
// WONTSUPPORT
|
||||
//
|
||||
// UNSUPPORTED
|
||||
//"last\tGet last block and state info from server\n"
|
||||
//"status\tShow connection and local database status\n"
|
||||
//"getaccount <addr> [<block-id-ext>]\tLoads the most recent state of specified account; <addr> is in "
|
||||
//"[<workchain>:]<hex-or-base64-addr> format\n"
|
||||
//"(StateInit) or just the code or data of specified account; <addr> is in "
|
||||
//"[<workchain>:]<hex-or-base64-addr> format\n"
|
||||
//"allshards [<block-id-ext>]\tShows shard configuration from the most recent masterchain "
|
||||
//"state or from masterchain state corresponding to <block-id-ext>\n"
|
||||
//"getconfig [<param>...]\tShows specified or all configuration parameters from the latest masterchain state\n"
|
||||
//"getconfigfrom <block-id-ext> [<param>...]\tShows specified or all configuration parameters from the "
|
||||
//"masterchain state of <block-id-ext>\n"
|
||||
//"saveconfig <filename> [<block-id-ext>]\tSaves all configuration parameters into specified file\n"
|
||||
//"gethead <block-id-ext>\tShows block header for <block-id-ext>\n"
|
||||
//"getblock <block-id-ext>\tDownloads block\n"
|
||||
//"dumpblock <block-id-ext>\tDownloads and dumps specified block\n"
|
||||
//"getstate <block-id-ext>\tDownloads state corresponding to specified block\n"
|
||||
//"dumpstate <block-id-ext>\tDownloads and dumps state corresponding to specified block\n"
|
||||
//"dumptrans <block-id-ext> <account-id> <trans-lt>\tDumps one transaction of specified account\n"
|
||||
//"lasttrans[dump] <account-id> <trans-lt> <trans-hash> [<count>]\tShows or dumps specified transaction and "
|
||||
//"several preceding "
|
||||
//"ones\n"
|
||||
//"listblocktrans[rev] <block-id-ext> <count> [<start-account-id> <start-trans-lt>]\tLists block transactions, "
|
||||
//"starting immediately after or before the specified one\n"
|
||||
//"blkproofchain[step] <from-block-id-ext> [<to-block-id-ext>]\tDownloads and checks proof of validity of the /"second "
|
||||
//"indicated block (or the last known masterchain block) starting from given block\n"
|
||||
//"byseqno <workchain> <shard-prefix> <seqno>\tLooks up a block by workchain, shard and seqno, and shows its "
|
||||
//"header\n"
|
||||
//"bylt <workchain> <shard-prefix> <lt>\tLooks up a block by workchain, shard and logical time, and shows its "
|
||||
//"header\n"
|
||||
//"byutime <workchain> <shard-prefix> <utime>\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 <filename>\tLoads a private key from file\n"
|
||||
|
||||
class TonlibCli : public td::actor::Actor {
|
||||
public:
|
||||
struct Options {
|
||||
|
@ -201,8 +252,25 @@ class TonlibCli : public td::actor::Actor {
|
|||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
td::Promise<td::Unit> cmd_promise = [line = line.clone()](td::Result<td::Unit> res) {
|
||||
if (res.is_ok()) {
|
||||
// on_ok
|
||||
} else {
|
||||
td::TerminalIO::out() << "Query {" << line.as_slice() << "} FAILED: \n\t" << res.error() << "\n";
|
||||
}
|
||||
};
|
||||
|
||||
if (cmd == "help") {
|
||||
td::TerminalIO::out() << "help - show this help\n";
|
||||
td::TerminalIO::out() << "help\tThis help\n";
|
||||
td::TerminalIO::out() << "time\tGet server time\n";
|
||||
td::TerminalIO::out() << "remote-version\tShows server time, version and capabilities\n";
|
||||
td::TerminalIO::out() << "sendfile <filename>\tLoad a serialized message from <filename> and send it to server\n";
|
||||
td::TerminalIO::out() << "exit\tExit\n";
|
||||
td::TerminalIO::out() << "quit\tExit\n";
|
||||
td::TerminalIO::out()
|
||||
<< "saveaccount[code|data] <filename> <addr>\tSaves into specified file the most recent state\n";
|
||||
|
||||
td::TerminalIO::out() << "genkey - generate new secret key\n";
|
||||
td::TerminalIO::out() << "keys - show all stored keys\n";
|
||||
td::TerminalIO::out() << "unpackaddress <address> - validate and parse address\n";
|
||||
|
@ -210,6 +278,7 @@ class TonlibCli : public td::actor::Actor {
|
|||
td::TerminalIO::out() << "importkey - import key\n";
|
||||
td::TerminalIO::out() << "deletekeys - delete ALL PRIVATE KEYS\n";
|
||||
td::TerminalIO::out() << "exportkey [<key_id>] - export key\n";
|
||||
td::TerminalIO::out() << "exportkeypem [<key_id>] - export key\n";
|
||||
td::TerminalIO::out() << "setconfig <path> [<name>] [<use_callback>] [<force>] - set lite server config\n";
|
||||
td::TerminalIO::out() << "getstate <key_id> - get state of simple wallet with requested key\n";
|
||||
td::TerminalIO::out()
|
||||
|
@ -219,10 +288,9 @@ class TonlibCli : public td::actor::Actor {
|
|||
"<from_key_id> to <to_key_id>.\n"
|
||||
<< "\t<from_key_id> could also be 'giver'\n"
|
||||
<< "\t<to_key_id> could also be 'giver' or smartcontract address\n";
|
||||
td::TerminalIO::out() << "exit - exit from this programm\n";
|
||||
} else if (cmd == "genkey") {
|
||||
generate_key();
|
||||
} else if (cmd == "exit") {
|
||||
} else if (cmd == "exit" || cmd == "quit") {
|
||||
is_closing_ = true;
|
||||
client_.reset();
|
||||
ref_cnt_--;
|
||||
|
@ -233,8 +301,8 @@ class TonlibCli : public td::actor::Actor {
|
|||
//delete_key(parser.read_word());
|
||||
} else if (cmd == "deletekeys") {
|
||||
delete_all_keys();
|
||||
} else if (cmd == "exportkey") {
|
||||
export_key(parser.read_word());
|
||||
} else if (cmd == "exportkey" || cmd == "exportkeypem") {
|
||||
export_key(cmd.str(), parser.read_word());
|
||||
} else if (cmd == "importkey") {
|
||||
import_key(parser.read_all());
|
||||
} else if (cmd == "setconfig") {
|
||||
|
@ -265,20 +333,93 @@ class TonlibCli : public td::actor::Actor {
|
|||
set_bounceable(addr, to_bool(bounceable, true));
|
||||
} else if (cmd == "netstats") {
|
||||
dump_netstats();
|
||||
// reviewed from here
|
||||
} else if (cmd == "sync") {
|
||||
sync();
|
||||
sync(std::move(cmd_promise));
|
||||
} else if (cmd == "time") {
|
||||
remote_time(std::move(cmd_promise));
|
||||
} else if (cmd == "remote-version") {
|
||||
remote_version(std::move(cmd_promise));
|
||||
} else if (cmd == "sendfile") {
|
||||
send_file(parser.read_word(), std::move(cmd_promise));
|
||||
} else if (cmd == "saveaccount" || cmd == "saveaccountdata" || cmd == "saveaccountcode") {
|
||||
auto path = parser.read_word();
|
||||
auto address = parser.read_word();
|
||||
save_account(cmd, path, address, std::move(cmd_promise));
|
||||
} else {
|
||||
cmd_promise.set_error(td::Status::Error(PSLICE() << "Unkwnown query `" << cmd << "`"));
|
||||
}
|
||||
if (cmd_promise) {
|
||||
cmd_promise.set_value(td::Unit());
|
||||
}
|
||||
}
|
||||
|
||||
void sync() {
|
||||
using tonlib_api::make_object;
|
||||
send_query(make_object<tonlib_api::sync>(), [](auto r_ok) {
|
||||
LOG_IF(ERROR, r_ok.is_error()) << r_ok.error();
|
||||
if (r_ok.is_ok()) {
|
||||
td::TerminalIO::out() << "synchronized\n";
|
||||
}
|
||||
});
|
||||
void remote_time(td::Promise<td::Unit> promise) {
|
||||
send_query(tonlib_api::make_object<tonlib_api::liteServer_getInfo>(), promise.wrap([](auto&& info) {
|
||||
td::TerminalIO::out() << "Lite server time is: " << info->now_ << "\n";
|
||||
return td::Unit();
|
||||
}));
|
||||
}
|
||||
|
||||
void remote_version(td::Promise<td::Unit> promise) {
|
||||
send_query(tonlib_api::make_object<tonlib_api::liteServer_getInfo>(), promise.wrap([](auto&& info) {
|
||||
td::TerminalIO::out() << "Lite server time is: " << info->now_ << "\n";
|
||||
td::TerminalIO::out() << "Lite server version is: " << info->version_ << "\n";
|
||||
td::TerminalIO::out() << "Lite server capabilities are: " << info->capabilities_ << "\n";
|
||||
return td::Unit();
|
||||
}));
|
||||
}
|
||||
|
||||
void send_file(td::Slice name, td::Promise<td::Unit> promise) {
|
||||
TRY_RESULT_PROMISE(promise, data, td::read_file_str(name.str()));
|
||||
send_query(tonlib_api::make_object<tonlib_api::raw_sendMessage>(std::move(data)), promise.wrap([](auto&& info) {
|
||||
td::TerminalIO::out() << "Query was sent\n";
|
||||
return td::Unit();
|
||||
}));
|
||||
}
|
||||
|
||||
void save_account(td::Slice cmd, td::Slice path, td::Slice address, td::Promise<td::Unit> promise) {
|
||||
TRY_RESULT_PROMISE(promise, addr, to_account_address(address, false));
|
||||
send_query(tonlib_api::make_object<tonlib_api::smc_load>(std::move(addr.address)),
|
||||
promise.send_closure(actor_id(this), &TonlibCli::save_account_2, cmd.str(), path.str(), address.str()));
|
||||
}
|
||||
|
||||
void save_account_2(std::string cmd, std::string path, std::string address,
|
||||
tonlib_api::object_ptr<tonlib_api::smc_info> info, td::Promise<td::Unit> promise) {
|
||||
auto with_query = [&, self = this](auto query, auto log) {
|
||||
send_query(std::move(query),
|
||||
promise.send_closure(actor_id(self), &TonlibCli::save_account_3, std::move(path), std::move(log)));
|
||||
};
|
||||
if (cmd == "saveaccount") {
|
||||
with_query(tonlib_api::make_object<tonlib_api::smc_getState>(info->id_),
|
||||
PSTRING() << "StateInit of account " << address);
|
||||
} else if (cmd == "saveaccountcode") {
|
||||
with_query(tonlib_api::make_object<tonlib_api::smc_getCode>(info->id_), PSTRING()
|
||||
<< "Code of account " << address);
|
||||
} else if (cmd == "saveaccountdata") {
|
||||
with_query(tonlib_api::make_object<tonlib_api::smc_getData>(info->id_), PSTRING()
|
||||
<< "Data of account " << address);
|
||||
} else {
|
||||
promise.set_error(td::Status::Error("Unknown query"));
|
||||
}
|
||||
}
|
||||
|
||||
void save_account_3(std::string path, std::string log, tonlib_api::object_ptr<tonlib_api::tvm_cell> cell,
|
||||
td::Promise<td::Unit> promise) {
|
||||
TRY_STATUS_PROMISE(promise, td::write_file(path, cell->bytes_));
|
||||
td::TerminalIO::out() << log << " was successfully written to the disk(" << td::format::as_size(cell->bytes_.size())
|
||||
<< ")\n";
|
||||
promise.set_value(td::Unit());
|
||||
}
|
||||
|
||||
void sync(td::Promise<td::Unit> promise) {
|
||||
using tonlib_api::make_object;
|
||||
send_query(make_object<tonlib_api::sync>(), promise.wrap([](auto&&) {
|
||||
td::TerminalIO::out() << "synchronized\n";
|
||||
return td::Unit();
|
||||
}));
|
||||
}
|
||||
|
||||
void dump_netstats() {
|
||||
td::TerminalIO::out() << td::tag("snd", td::format::as_size(snd_bytes_)) << "\n";
|
||||
td::TerminalIO::out() << td::tag("rcv", td::format::as_size(rcv_bytes_)) << "\n";
|
||||
|
@ -298,15 +439,41 @@ class TonlibCli : public td::actor::Actor {
|
|||
|
||||
void on_tonlib_result(std::uint64_t id, tonlib_api::object_ptr<tonlib_api::Object> result) {
|
||||
if (id == 0) {
|
||||
if (result->get_id() == tonlib_api::updateSendLiteServerQuery::ID) {
|
||||
auto update = tonlib_api::move_object_as<tonlib_api::updateSendLiteServerQuery>(std::move(result));
|
||||
CHECK(!raw_client_.empty());
|
||||
snd_bytes_ += update->data_.size();
|
||||
send_closure(raw_client_, &ton::adnl::AdnlExtClient::send_query, "query", td::BufferSlice(update->data_),
|
||||
td::Timestamp::in(5),
|
||||
[actor_id = actor_id(this), id = update->id_](td::Result<td::BufferSlice> res) {
|
||||
send_closure(actor_id, &TonlibCli::on_adnl_result, id, std::move(res));
|
||||
});
|
||||
switch (result->get_id()) {
|
||||
case tonlib_api::updateSendLiteServerQuery::ID: {
|
||||
auto update = tonlib_api::move_object_as<tonlib_api::updateSendLiteServerQuery>(std::move(result));
|
||||
CHECK(!raw_client_.empty());
|
||||
snd_bytes_ += update->data_.size();
|
||||
send_closure(raw_client_, &ton::adnl::AdnlExtClient::send_query, "query", td::BufferSlice(update->data_),
|
||||
td::Timestamp::in(5),
|
||||
[actor_id = actor_id(this), id = update->id_](td::Result<td::BufferSlice> res) {
|
||||
send_closure(actor_id, &TonlibCli::on_adnl_result, id, std::move(res));
|
||||
});
|
||||
return;
|
||||
}
|
||||
case tonlib_api::updateSyncState::ID: {
|
||||
auto update = tonlib_api::move_object_as<tonlib_api::updateSyncState>(std::move(result));
|
||||
switch (update->sync_state_->get_id()) {
|
||||
case tonlib_api::syncStateDone::ID: {
|
||||
td::TerminalIO::out() << "synchronization: DONE\n";
|
||||
break;
|
||||
}
|
||||
case tonlib_api::syncStateInProgress::ID: {
|
||||
auto progress = tonlib_api::move_object_as<tonlib_api::syncStateInProgress>(update->sync_state_);
|
||||
auto from = progress->from_seqno_;
|
||||
auto to = progress->to_seqno_;
|
||||
auto at = progress->current_seqno_;
|
||||
auto d = to - from;
|
||||
if (d <= 0) {
|
||||
td::TerminalIO::out() << "synchronization: ???\n";
|
||||
} else {
|
||||
td::TerminalIO::out() << "synchronization: " << 100 * (at - from) / d << "%\n";
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
auto it = query_handlers_.find(id);
|
||||
|
@ -407,7 +574,7 @@ class TonlibCli : public td::actor::Actor {
|
|||
info.public_key = key->public_key_;
|
||||
info.secret = std::move(key->secret_);
|
||||
keys_.push_back(std::move(info));
|
||||
export_key(key->public_key_, keys_.size() - 1, std::move(password));
|
||||
export_key("exportkey", key->public_key_, keys_.size() - 1, std::move(password));
|
||||
store_keys();
|
||||
});
|
||||
}
|
||||
|
@ -596,11 +763,11 @@ class TonlibCli : public td::actor::Actor {
|
|||
td::TerminalIO::out() << "Ok\n";
|
||||
});
|
||||
}
|
||||
void export_key(td::Slice key) {
|
||||
void export_key(std::string cmd, td::Slice key) {
|
||||
if (key.empty()) {
|
||||
dump_keys();
|
||||
td::TerminalIO::out() << "Choose public key (hex prefix or #N)";
|
||||
cont_ = [this](td::Slice key) { this->export_key(key); };
|
||||
cont_ = [this, cmd](td::Slice key) { this->export_key(cmd, key); };
|
||||
return;
|
||||
}
|
||||
auto r_key_i = to_key_i(key);
|
||||
|
@ -614,24 +781,40 @@ class TonlibCli : public td::actor::Actor {
|
|||
<< "public key: " << td::buffer_to_hex(keys_[key_i].public_key) << "\n";
|
||||
|
||||
td::TerminalIO::out() << "Enter password (could be empty)";
|
||||
cont_ = [this, key = key.str(), key_i](td::Slice password) { this->export_key(key, key_i, password); };
|
||||
cont_ = [this, cmd, key = key.str(), key_i](td::Slice password) { this->export_key(cmd, key, key_i, password); };
|
||||
}
|
||||
|
||||
void export_key(std::string key, size_t key_i, td::Slice password) {
|
||||
void export_key(std::string cmd, std::string key, size_t key_i, td::Slice password) {
|
||||
using tonlib_api::make_object;
|
||||
send_query(make_object<tonlib_api::exportKey>(make_object<tonlib_api::inputKey>(
|
||||
make_object<tonlib_api::key>(keys_[key_i].public_key, keys_[key_i].secret.copy()),
|
||||
td::SecureString(password))),
|
||||
[this, key = std::move(key), key_i](auto r_res) {
|
||||
if (r_res.is_error()) {
|
||||
td::TerminalIO::out() << "Can't export key id: [" << key << "] " << r_res.error() << "\n";
|
||||
return;
|
||||
}
|
||||
dump_key(key_i);
|
||||
for (auto& word : r_res.ok()->word_list_) {
|
||||
td::TerminalIO::out() << " " << word.as_slice() << "\n";
|
||||
}
|
||||
});
|
||||
if (cmd == "exportkey") {
|
||||
send_query(make_object<tonlib_api::exportKey>(make_object<tonlib_api::inputKeyRegular>(
|
||||
make_object<tonlib_api::key>(keys_[key_i].public_key, keys_[key_i].secret.copy()),
|
||||
td::SecureString(password))),
|
||||
[this, key = std::move(key), key_i](auto r_res) {
|
||||
if (r_res.is_error()) {
|
||||
td::TerminalIO::out() << "Can't export key id: [" << key << "] " << r_res.error() << "\n";
|
||||
return;
|
||||
}
|
||||
dump_key(key_i);
|
||||
for (auto& word : r_res.ok()->word_list_) {
|
||||
td::TerminalIO::out() << " " << word.as_slice() << "\n";
|
||||
}
|
||||
});
|
||||
} else {
|
||||
send_query(make_object<tonlib_api::exportPemKey>(
|
||||
make_object<tonlib_api::inputKeyRegular>(
|
||||
make_object<tonlib_api::key>(keys_[key_i].public_key, keys_[key_i].secret.copy()),
|
||||
td::SecureString(password)),
|
||||
td::SecureString("cucumber")),
|
||||
[this, key = std::move(key), key_i](auto r_res) {
|
||||
if (r_res.is_error()) {
|
||||
td::TerminalIO::out() << "Can't export key id: [" << key << "] " << r_res.error() << "\n";
|
||||
return;
|
||||
}
|
||||
dump_key(key_i);
|
||||
td::TerminalIO::out() << "\n" << r_res.ok()->pem_.as_slice() << "\n";
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void import_key(td::Slice slice, std::vector<td::SecureString> words = {}) {
|
||||
|
@ -669,7 +852,7 @@ class TonlibCli : public td::actor::Actor {
|
|||
info.public_key = key->public_key_;
|
||||
info.secret = std::move(key->secret_);
|
||||
keys_.push_back(std::move(info));
|
||||
export_key(key->public_key_, keys_.size() - 1, std::move(password));
|
||||
export_key("exportkey", key->public_key_, keys_.size() - 1, std::move(password));
|
||||
store_keys();
|
||||
});
|
||||
}
|
||||
|
@ -830,21 +1013,42 @@ class TonlibCli : public td::actor::Actor {
|
|||
}
|
||||
using tonlib_api::make_object;
|
||||
auto key = !from.secret.empty()
|
||||
? make_object<tonlib_api::inputKey>(
|
||||
? make_object<tonlib_api::inputKeyRegular>(
|
||||
make_object<tonlib_api::key>(from.public_key, from.secret.copy()), td::SecureString(password))
|
||||
: nullptr;
|
||||
send_query(
|
||||
make_object<tonlib_api::generic_sendGrams>(std::move(key), std::move(from.address), std::move(to.address),
|
||||
grams, 60, allow_send_to_uninited, std::move(msg)),
|
||||
[this](auto r_res) {
|
||||
if (r_res.is_error()) {
|
||||
td::TerminalIO::out() << "Can't transfer: " << r_res.error() << "\n";
|
||||
on_error();
|
||||
return;
|
||||
}
|
||||
td::TerminalIO::out() << to_string(r_res.ok());
|
||||
on_ok();
|
||||
});
|
||||
send_query(make_object<tonlib_api::generic_createSendGramsQuery>(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<tonlib_api::query_estimateFees>(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<tonlib_api::query_send>(r_res.ok()->id_), [self](auto r_res) {
|
||||
if (r_res.is_error()) {
|
||||
td::TerminalIO::out() << "Can't transfer: " << r_res.error() << "\n";
|
||||
self->on_error();
|
||||
return;
|
||||
}
|
||||
td::TerminalIO::out() << to_string(r_res.ok());
|
||||
self->on_ok();
|
||||
});
|
||||
|
||||
self->on_ok();
|
||||
});
|
||||
}
|
||||
|
||||
void init_simple_wallet(td::Slice key) {
|
||||
|
@ -871,7 +1075,7 @@ class TonlibCli : public td::actor::Actor {
|
|||
void init_simple_wallet(std::string key, size_t key_i, td::Slice password) {
|
||||
using tonlib_api::make_object;
|
||||
if (options_.use_simple_wallet) {
|
||||
send_query(make_object<tonlib_api::testWallet_init>(make_object<tonlib_api::inputKey>(
|
||||
send_query(make_object<tonlib_api::testWallet_init>(make_object<tonlib_api::inputKeyRegular>(
|
||||
make_object<tonlib_api::key>(keys_[key_i].public_key, keys_[key_i].secret.copy()),
|
||||
td::SecureString(password))),
|
||||
[key = std::move(key)](auto r_res) {
|
||||
|
@ -882,7 +1086,7 @@ class TonlibCli : public td::actor::Actor {
|
|||
td::TerminalIO::out() << to_string(r_res.ok());
|
||||
});
|
||||
} else {
|
||||
send_query(make_object<tonlib_api::wallet_init>(make_object<tonlib_api::inputKey>(
|
||||
send_query(make_object<tonlib_api::wallet_init>(make_object<tonlib_api::inputKeyRegular>(
|
||||
make_object<tonlib_api::key>(keys_[key_i].public_key, keys_[key_i].secret.copy()),
|
||||
td::SecureString(password))),
|
||||
[key = std::move(key)](auto r_res) {
|
||||
|
|
|
@ -20,20 +20,9 @@
|
|||
#include "td/utils/misc.h"
|
||||
#include "vm/cellslice.h"
|
||||
namespace tonlib {
|
||||
int VERBOSITY_NAME(tonlib_query) = VERBOSITY_NAME(INFO);
|
||||
int VERBOSITY_NAME(last_block) = VERBOSITY_NAME(INFO);
|
||||
int VERBOSITY_NAME(lite_server) = VERBOSITY_NAME(INFO);
|
||||
int VERBOSITY_NAME(tonlib_query) = VERBOSITY_NAME(DEBUG);
|
||||
int VERBOSITY_NAME(last_block) = VERBOSITY_NAME(DEBUG);
|
||||
int VERBOSITY_NAME(last_config) = VERBOSITY_NAME(DEBUG);
|
||||
int VERBOSITY_NAME(lite_server) = VERBOSITY_NAME(DEBUG);
|
||||
|
||||
td::Result<td::Ref<vm::CellSlice>> binary_bitstring_to_cellslice(td::Slice literal) {
|
||||
unsigned char buff[128];
|
||||
if (!begins_with(literal, "b{") || !ends_with(literal, "}")) {
|
||||
return td::Status::Error("Invalid binary bitstring constant");
|
||||
}
|
||||
int bits =
|
||||
(int)td::bitstring::parse_bitstring_binary_literal(buff, sizeof(buff), literal.begin() + 2, literal.end() - 1);
|
||||
if (bits < 0) {
|
||||
return td::Status::Error("Invalid binary bitstring constant");
|
||||
}
|
||||
return td::Ref<vm::CellSlice>{true, vm::CellBuilder().store_bits(td::ConstBitPtr{buff}, bits).finalize()};
|
||||
}
|
||||
} // namespace tonlib
|
||||
|
|
|
@ -23,19 +23,8 @@
|
|||
#include "block/block-parse.h"
|
||||
|
||||
namespace tonlib {
|
||||
template <class F>
|
||||
auto try_f(F&& f) noexcept -> decltype(f()) {
|
||||
try {
|
||||
return f();
|
||||
} catch (vm::VmError error) {
|
||||
return td::Status::Error(PSLICE() << "Got a vm exception: " << error.get_msg());
|
||||
}
|
||||
}
|
||||
|
||||
#define TRY_VM(f) try_f([&] { return f; })
|
||||
|
||||
extern int VERBOSITY_NAME(tonlib_query);
|
||||
extern int VERBOSITY_NAME(last_block);
|
||||
extern int VERBOSITY_NAME(last_config);
|
||||
extern int VERBOSITY_NAME(lite_server);
|
||||
td::Result<td::Ref<vm::CellSlice>> binary_bitstring_to_cellslice(td::Slice literal);
|
||||
} // namespace tonlib
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue