mirror of
https://github.com/ton-blockchain/ton
synced 2025-02-12 11:12:16 +00:00
updated tonlib, block routing
- upated tonlib - fixed bug in message routing
This commit is contained in:
parent
ac3eb1a7b8
commit
fd7a8de970
33 changed files with 1002 additions and 381 deletions
|
@ -552,7 +552,9 @@ bool MsgProcessedUpto::already_processed(const EnqueuedMsgDescr& msg) const {
|
|||
if (msg.lt_ == last_inmsg_lt && last_inmsg_hash < msg.hash_) {
|
||||
return false;
|
||||
}
|
||||
if (ton::shard_contains(shard, msg.cur_prefix_.account_id_prefix)) {
|
||||
if (msg.same_workchain() && ton::shard_contains(shard, msg.cur_prefix_.account_id_prefix)) {
|
||||
// this branch is needed only for messages generated in the same shard
|
||||
// (such messages could have been processed without a reference from the masterchain)
|
||||
// ? enable this branch only if an extra boolean parameter is set ?
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -145,6 +145,9 @@ struct EnqueuedMsgDescr {
|
|||
return false;
|
||||
}
|
||||
bool unpack(vm::CellSlice& cs);
|
||||
bool same_workchain() const {
|
||||
return cur_prefix_.workchain == next_prefix_.workchain;
|
||||
}
|
||||
};
|
||||
|
||||
using compute_shard_end_lt_func_t = std::function<ton::LogicalTime(ton::AccountIdPrefixFull)>;
|
||||
|
|
|
@ -32,7 +32,7 @@ namespace block {
|
|||
using namespace std::literals::string_literals;
|
||||
|
||||
td::Status check_block_header_proof(td::Ref<vm::Cell> root, ton::BlockIdExt blkid, ton::Bits256* store_shard_hash_to,
|
||||
bool check_state_hash) {
|
||||
bool check_state_hash, td::uint32* save_utime) {
|
||||
ton::RootHash vhash{root->get_hash().bits()};
|
||||
if (vhash != blkid.root_hash) {
|
||||
return td::Status::Error(PSTRING() << " block header for block " << blkid.to_str() << " has incorrect root hash "
|
||||
|
@ -47,6 +47,9 @@ td::Status check_block_header_proof(td::Ref<vm::Cell> root, ton::BlockIdExt blki
|
|||
if (!(tlb::unpack_cell(root, blk) && tlb::unpack_cell(blk.info, info))) {
|
||||
return td::Status::Error(std::string{"cannot unpack header for block "} + blkid.to_str());
|
||||
}
|
||||
if (save_utime) {
|
||||
*save_utime = info.gen_utime;
|
||||
}
|
||||
if (store_shard_hash_to) {
|
||||
vm::CellSlice upd_cs{vm::NoVmSpec(), blk.state_update};
|
||||
if (!(upd_cs.is_special() && upd_cs.prefetch_long(8) == 4 // merkle update
|
||||
|
@ -153,7 +156,8 @@ td::Status check_shard_proof(ton::BlockIdExt blk, ton::BlockIdExt shard_blk, td:
|
|||
}
|
||||
|
||||
td::Status check_account_proof(td::Slice proof, ton::BlockIdExt shard_blk, const block::StdAddress& addr,
|
||||
td::Ref<vm::Cell> root, ton::LogicalTime* last_trans_lt, ton::Bits256* last_trans_hash) {
|
||||
td::Ref<vm::Cell> root, ton::LogicalTime* last_trans_lt, ton::Bits256* last_trans_hash,
|
||||
td::uint32* save_utime) {
|
||||
TRY_RESULT_PREFIX(Q_roots, vm::std_boc_deserialize_multi(std::move(proof)), "cannot deserialize account proof");
|
||||
if (Q_roots.size() != 2) {
|
||||
return td::Status::Error(PSLICE() << "account state proof must have exactly two roots");
|
||||
|
@ -169,8 +173,8 @@ td::Status check_account_proof(td::Slice proof, ton::BlockIdExt shard_blk, const
|
|||
return td::Status::Error("account state proof is invalid");
|
||||
}
|
||||
ton::Bits256 state_hash = state_root->get_hash().bits();
|
||||
TRY_STATUS_PREFIX(
|
||||
check_block_header_proof(vm::MerkleProof::virtualize(std::move(Q_roots[0]), 1), shard_blk, &state_hash, true),
|
||||
TRY_STATUS_PREFIX(check_block_header_proof(vm::MerkleProof::virtualize(std::move(Q_roots[0]), 1), shard_blk,
|
||||
&state_hash, true, save_utime),
|
||||
"error in account shard block header proof : ");
|
||||
block::gen::ShardStateUnsplit::Record sstate;
|
||||
if (!(tlb::unpack_cell(std::move(state_root), sstate))) {
|
||||
|
@ -233,8 +237,8 @@ td::Result<AccountState::Info> AccountState::validate(ton::BlockIdExt ref_blk, b
|
|||
TRY_STATUS(block::check_shard_proof(blk, shard_blk, shard_proof.as_slice()));
|
||||
|
||||
Info res;
|
||||
TRY_STATUS(
|
||||
block::check_account_proof(proof.as_slice(), shard_blk, addr, root, &res.last_trans_lt, &res.last_trans_hash));
|
||||
TRY_STATUS(block::check_account_proof(proof.as_slice(), shard_blk, addr, root, &res.last_trans_lt,
|
||||
&res.last_trans_hash, &res.gen_utime));
|
||||
res.root = std::move(root);
|
||||
|
||||
return res;
|
||||
|
|
|
@ -25,11 +25,12 @@ namespace block {
|
|||
using td::Ref;
|
||||
|
||||
td::Status check_block_header_proof(td::Ref<vm::Cell> root, ton::BlockIdExt blkid,
|
||||
ton::Bits256* store_shard_hash_to = nullptr, bool check_state_hash = false);
|
||||
ton::Bits256* store_shard_hash_to = nullptr, bool check_state_hash = false,
|
||||
td::uint32* save_utime = nullptr);
|
||||
td::Status check_shard_proof(ton::BlockIdExt blk, ton::BlockIdExt shard_blk, td::Slice shard_proof);
|
||||
td::Status check_account_proof(td::Slice proof, ton::BlockIdExt shard_blk, const block::StdAddress& addr,
|
||||
td::Ref<vm::Cell> root, ton::LogicalTime* last_trans_lt = nullptr,
|
||||
ton::Bits256* last_trans_hash = nullptr);
|
||||
ton::Bits256* last_trans_hash = nullptr, td::uint32* save_utime = nullptr);
|
||||
td::Result<td::Bits256> check_state_proof(ton::BlockIdExt blkid, td::Slice proof);
|
||||
td::Result<Ref<vm::Cell>> check_extract_state_proof(ton::BlockIdExt blkid, td::Slice proof, td::Slice data);
|
||||
|
||||
|
@ -47,6 +48,7 @@ struct AccountState {
|
|||
td::Ref<vm::Cell> root;
|
||||
ton::LogicalTime last_trans_lt = 0;
|
||||
ton::Bits256 last_trans_hash;
|
||||
td::uint32 gen_utime{0};
|
||||
};
|
||||
|
||||
td::Result<Info> validate(ton::BlockIdExt ref_blk, block::StdAddress addr) const;
|
||||
|
|
|
@ -1027,6 +1027,12 @@ bool Transaction::prepare_action_phase(const ActionPhaseConfig& cfg) {
|
|||
break;
|
||||
case block::gen::OutAction::action_send_msg:
|
||||
err_code = try_action_send_msg(cs, ap, cfg);
|
||||
if (err_code == -2) {
|
||||
err_code = try_action_send_msg(cs, ap, cfg, 1);
|
||||
if (err_code == -2) {
|
||||
err_code = try_action_send_msg(cs, ap, cfg, 2);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case block::gen::OutAction::action_reserve_currency:
|
||||
err_code = try_action_reserve_currency(cs, ap, cfg);
|
||||
|
@ -1240,22 +1246,60 @@ bool Transaction::check_rewrite_dest_addr(Ref<vm::CellSlice>& dest_addr, const A
|
|||
return true;
|
||||
}
|
||||
|
||||
int Transaction::try_action_send_msg(vm::CellSlice& cs, ActionPhase& ap, const ActionPhaseConfig& cfg) {
|
||||
int Transaction::try_action_send_msg(const vm::CellSlice& cs0, ActionPhase& ap, const ActionPhaseConfig& cfg,
|
||||
int redoing) {
|
||||
block::gen::OutAction::Record_action_send_msg act_rec;
|
||||
// mode: +128 = attach all remaining balance, +64 = attach all remaining balance of the inbound message, +1 = pay message fees, +2 = skip if message cannot be sent
|
||||
vm::CellSlice cs{cs0};
|
||||
if (!tlb::unpack_exact(cs, act_rec) || (act_rec.mode & ~0xc3) || (act_rec.mode & 0xc0) == 0xc0) {
|
||||
return -1;
|
||||
}
|
||||
bool skip_invalid = (act_rec.mode & 2);
|
||||
auto cs2 = vm::load_cell_slice(act_rec.out_msg);
|
||||
// try to parse suggested message in cs2
|
||||
// try to parse suggested message in act_rec.out_msg
|
||||
td::RefInt256 fwd_fee, ihr_fee;
|
||||
bool ext_msg = cs2.prefetch_ulong(1);
|
||||
block::gen::MessageRelaxed::Record msg;
|
||||
if (!tlb::type_unpack_cell(act_rec.out_msg, block::gen::t_MessageRelaxed_Any, msg)) {
|
||||
return -1;
|
||||
}
|
||||
if (redoing >= 1) {
|
||||
if (msg.init->size_refs() >= 2) {
|
||||
LOG(DEBUG) << "moving the StateInit of a suggested outbound message into a separate cell";
|
||||
// init:(Maybe (Either StateInit ^StateInit))
|
||||
// transform (just (left z:StateInit)) into (just (right z:^StateInit))
|
||||
CHECK(msg.init.write().fetch_ulong(2) == 2);
|
||||
vm::CellBuilder cb;
|
||||
Ref<vm::Cell> cell;
|
||||
CHECK(cb.append_cellslice_bool(std::move(msg.init)) // StateInit
|
||||
&& cb.finalize_to(cell) // -> ^StateInit
|
||||
&& cb.store_long_bool(3, 2) // (just (right ... ))
|
||||
&& cb.store_ref_bool(std::move(cell)) // z:^StateInit
|
||||
&& cb.finalize_to(cell));
|
||||
msg.init = vm::load_cell_slice_ref(std::move(cell));
|
||||
} else {
|
||||
redoing = 2;
|
||||
}
|
||||
}
|
||||
if (redoing >= 2 && msg.body->size_ext() > 1 && msg.body->prefetch_ulong(1) == 0) {
|
||||
LOG(DEBUG) << "moving the body of a suggested outbound message into a separate cell";
|
||||
// body:(Either X ^X)
|
||||
// transform (left x:X) into (right x:^X)
|
||||
CHECK(msg.body.write().fetch_ulong(1) == 0);
|
||||
vm::CellBuilder cb;
|
||||
Ref<vm::Cell> cell;
|
||||
CHECK(cb.append_cellslice_bool(std::move(msg.body)) // X
|
||||
&& cb.finalize_to(cell) // -> ^X
|
||||
&& cb.store_long_bool(1, 1) // (right ... )
|
||||
&& cb.store_ref_bool(std::move(cell)) // x:^X
|
||||
&& cb.finalize_to(cell));
|
||||
msg.body = vm::load_cell_slice_ref(std::move(cell));
|
||||
}
|
||||
|
||||
block::gen::CommonMsgInfoRelaxed::Record_int_msg_info info;
|
||||
bool ext_msg = msg.info->prefetch_ulong(1);
|
||||
if (ext_msg) {
|
||||
// ext_out_msg_info$11 constructor of CommonMsgInfoRelaxed
|
||||
block::gen::CommonMsgInfoRelaxed::Record_ext_out_msg_info erec;
|
||||
if (!tlb::unpack(cs2, erec)) {
|
||||
if (!tlb::csr_unpack(msg.info, erec)) {
|
||||
return -1;
|
||||
}
|
||||
info.src = std::move(erec.src);
|
||||
|
@ -1267,7 +1311,7 @@ int Transaction::try_action_send_msg(vm::CellSlice& cs, ActionPhase& ap, const A
|
|||
fwd_fee = ihr_fee = td::RefInt256{true, 0};
|
||||
} else {
|
||||
// int_msg_info$0 constructor
|
||||
if (!tlb::unpack(cs2, info) || !block::tlb::t_CurrencyCollection.validate_csr(info.value)) {
|
||||
if (!tlb::csr_unpack(msg.info, info) || !block::tlb::t_CurrencyCollection.validate_csr(info.value)) {
|
||||
return -1;
|
||||
}
|
||||
fwd_fee = block::tlb::t_Grams.as_integer(info.fwd_fee);
|
||||
|
@ -1295,12 +1339,11 @@ int Transaction::try_action_send_msg(vm::CellSlice& cs, ActionPhase& ap, const A
|
|||
// compute size of message
|
||||
vm::CellStorageStat sstat; // for message size
|
||||
// preliminary storage estimation of the resulting message
|
||||
sstat.compute_used_storage(cs2); // message body
|
||||
sstat.add_used_storage(msg.init, true, 3); // message init
|
||||
sstat.add_used_storage(msg.body, true, 3); // message body (the root cell itself is not counted)
|
||||
if (!ext_msg) {
|
||||
sstat.add_used_storage(info.value->prefetch_ref());
|
||||
}
|
||||
sstat.bits -= cs2.size(); // bits in the root cells are free
|
||||
sstat.cells--; // the root cell itself is not counted as a cell
|
||||
LOG(DEBUG) << "storage paid for a message: " << sstat.cells << " cells, " << sstat.bits << " bits";
|
||||
if (sstat.bits > max_msg_bits || sstat.cells > max_msg_cells) {
|
||||
LOG(DEBUG) << "message too large, invalid";
|
||||
|
@ -1397,17 +1440,15 @@ int Transaction::try_action_send_msg(vm::CellSlice& cs, ActionPhase& ap, const A
|
|||
|
||||
// re-pack message value
|
||||
CHECK(req.pack_to(info.value));
|
||||
vm::CellBuilder cb;
|
||||
CHECK(block::tlb::t_Grams.store_integer_ref(cb, fwd_fee_remain) &&
|
||||
(info.fwd_fee = load_cell_slice_ref(cb.finalize())).not_null());
|
||||
CHECK(block::tlb::t_Grams.store_integer_ref(cb, ihr_fee) &&
|
||||
(info.ihr_fee = load_cell_slice_ref(cb.finalize())).not_null());
|
||||
CHECK(block::tlb::t_Grams.pack_integer(info.fwd_fee, fwd_fee_remain));
|
||||
CHECK(block::tlb::t_Grams.pack_integer(info.ihr_fee, ihr_fee));
|
||||
|
||||
// serialize message
|
||||
CHECK(tlb::pack(cb, info));
|
||||
if (!cb.append_cellslice_bool(cs2)) {
|
||||
CHECK(tlb::csr_pack(msg.info, info));
|
||||
vm::CellBuilder cb;
|
||||
if (!tlb::type_pack(cb, block::gen::t_MessageRelaxed_Any, msg)) {
|
||||
LOG(DEBUG) << "outbound message does not fit into a cell after rewriting";
|
||||
return 39;
|
||||
return redoing < 2 ? -2 : (skip_invalid ? 0 : 39);
|
||||
}
|
||||
|
||||
new_msg_bits = cb.size();
|
||||
|
@ -1438,11 +1479,11 @@ int Transaction::try_action_send_msg(vm::CellSlice& cs, ActionPhase& ap, const A
|
|||
erec.dest = info.dest;
|
||||
erec.created_at = info.created_at;
|
||||
erec.created_lt = info.created_lt;
|
||||
CHECK(tlb::csr_pack(msg.info, erec));
|
||||
vm::CellBuilder cb;
|
||||
CHECK(tlb::pack(cb, erec));
|
||||
if (!cb.append_cellslice_bool(cs2)) {
|
||||
if (!tlb::type_pack(cb, block::gen::t_MessageRelaxed_Any, msg)) {
|
||||
LOG(DEBUG) << "outbound message does not fit into a cell after rewriting";
|
||||
return 39;
|
||||
return redoing < 2 ? -2 : (skip_invalid ? 0 : 39);
|
||||
}
|
||||
|
||||
new_msg_bits = cb.size();
|
||||
|
@ -1461,6 +1502,8 @@ int Transaction::try_action_send_msg(vm::CellSlice& cs, ActionPhase& ap, const A
|
|||
}
|
||||
if (!block::gen::t_Message_Any.validate_ref(new_msg)) {
|
||||
LOG(ERROR) << "generated outbound message is not a valid (Message Any) according to automated check";
|
||||
block::gen::t_Message_Any.print_ref(std::cerr, new_msg);
|
||||
vm::load_cell_slice(new_msg).print_rec(std::cerr);
|
||||
return -1;
|
||||
}
|
||||
if (verbosity > 2) {
|
||||
|
|
|
@ -390,7 +390,7 @@ struct Transaction {
|
|||
Ref<vm::Tuple> prepare_vm_c7(const ComputePhaseConfig& cfg) const;
|
||||
bool prepare_rand_seed(td::BitArray<256>& rand_seed, const ComputePhaseConfig& cfg) const;
|
||||
int try_action_set_code(vm::CellSlice& cs, ActionPhase& ap, const ActionPhaseConfig& cfg);
|
||||
int try_action_send_msg(vm::CellSlice& cs, ActionPhase& ap, const ActionPhaseConfig& cfg);
|
||||
int try_action_send_msg(const vm::CellSlice& cs, ActionPhase& ap, const ActionPhaseConfig& cfg, int redoing = 0);
|
||||
int try_action_reserve_currency(vm::CellSlice& cs, ActionPhase& ap, const ActionPhaseConfig& cfg);
|
||||
bool check_replace_src_addr(Ref<vm::CellSlice>& src_addr) const;
|
||||
bool check_rewrite_dest_addr(Ref<vm::CellSlice>& dest_addr, const ActionPhaseConfig& cfg,
|
||||
|
|
|
@ -77,6 +77,7 @@ struct IntCtx {
|
|||
vm::TonDb* ton_db{nullptr};
|
||||
Dictionary* dictionary{nullptr};
|
||||
SourceLookup* source_lookup{nullptr};
|
||||
int* now{nullptr};
|
||||
|
||||
private:
|
||||
std::string str;
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include <iostream>
|
||||
|
||||
#include "td/utils/Status.h"
|
||||
#include "td/utils/Time.h"
|
||||
|
||||
namespace fift {
|
||||
class FileLoader {
|
||||
|
@ -43,10 +44,21 @@ class OsFileLoader : public FileLoader {
|
|||
bool is_file_exists(td::CSlice filename) override;
|
||||
};
|
||||
|
||||
class OsTime {
|
||||
public:
|
||||
virtual ~OsTime() = default;
|
||||
virtual td::uint32 now() = 0;
|
||||
};
|
||||
|
||||
//TODO: rename SourceLookup
|
||||
class SourceLookup {
|
||||
public:
|
||||
SourceLookup() = default;
|
||||
explicit SourceLookup(std::unique_ptr<FileLoader> file_loader) : file_loader_(std::move(file_loader)) {
|
||||
explicit SourceLookup(std::unique_ptr<FileLoader> file_loader, std::unique_ptr<OsTime> os_time = {})
|
||||
: file_loader_(std::move(file_loader)), os_time_(std::move(os_time)) {
|
||||
}
|
||||
void set_os_time(std::unique_ptr<OsTime> os_time) {
|
||||
os_time_ = std::move(os_time);
|
||||
}
|
||||
void add_include_path(td::string path);
|
||||
td::Result<FileLoader::File> lookup_source(std::string filename, std::string current_dir);
|
||||
|
@ -63,9 +75,16 @@ class SourceLookup {
|
|||
bool is_file_exists(td::CSlice filename) {
|
||||
return file_loader_->is_file_exists(filename);
|
||||
}
|
||||
td::uint32 now() {
|
||||
if (os_time_) {
|
||||
return os_time_->now();
|
||||
}
|
||||
return static_cast<td::uint32>(td::Time::now());
|
||||
}
|
||||
|
||||
protected:
|
||||
std::unique_ptr<FileLoader> file_loader_;
|
||||
std::unique_ptr<OsTime> os_time_;
|
||||
std::vector<std::string> source_include_path_;
|
||||
};
|
||||
} // namespace fift
|
||||
|
|
|
@ -1289,8 +1289,8 @@ void interpret_file_exists(IntCtx& ctx) {
|
|||
|
||||
// custom and crypto
|
||||
|
||||
void interpret_now(vm::Stack& stack) {
|
||||
stack.push_smallint(std::time(nullptr));
|
||||
void interpret_now(IntCtx& ctx) {
|
||||
ctx.stack.push_smallint(ctx.source_lookup->now());
|
||||
}
|
||||
|
||||
void interpret_new_keypair(vm::Stack& stack) {
|
||||
|
@ -2534,7 +2534,7 @@ void init_words_common(Dictionary& d) {
|
|||
d.def_ctx_word("B>file ", interpret_write_file);
|
||||
d.def_ctx_word("file-exists? ", interpret_file_exists);
|
||||
// custom & crypto
|
||||
d.def_stack_word("now ", interpret_now);
|
||||
d.def_ctx_word("now ", interpret_now);
|
||||
d.def_stack_word("newkeypair ", interpret_new_keypair);
|
||||
d.def_stack_word("priv>pub ", interpret_priv_key_to_pub);
|
||||
d.def_stack_word("ed25519_sign ", interpret_ed25519_sign);
|
||||
|
|
62
crypto/smartcont/new-wallet-v2.fif
Normal file
62
crypto/smartcont/new-wallet-v2.fif
Normal file
|
@ -0,0 +1,62 @@
|
|||
#!/usr/bin/env fift -s
|
||||
"TonUtil.fif" include
|
||||
"Asm.fif" include
|
||||
|
||||
{ ."usage: " @' $0 type ." <workchain-id> [<filename-base>]" cr
|
||||
."Creates a new advanced wallet in specified workchain, with private key saved to or loaded from <filename-base>.pk" cr
|
||||
."('new-wallet.pk' by default)" cr 1 halt
|
||||
} : usage
|
||||
$# 1- -2 and ' usage if
|
||||
|
||||
$1 parse-workchain-id =: wc // set workchain id from command line argument
|
||||
def? $2 { @' $2 } { "new-wallet" } cond constant file-base
|
||||
|
||||
."Creating new advanced wallet in workchain " wc . cr
|
||||
|
||||
// Create new advanced wallet; code adapted from `wallet-code.fif`
|
||||
<{ 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
|
||||
}>c // >libref
|
||||
// code
|
||||
<b 0 32 u,
|
||||
file-base +".pk" load-generate-keypair
|
||||
constant wallet_pk
|
||||
B,
|
||||
b> // data
|
||||
null // no libraries
|
||||
<b b{0011} s, 3 roll ref, rot ref, swap dict, b> // create StateInit
|
||||
dup ."StateInit: " <s csr. cr
|
||||
dup hash wc swap 2dup 2constant wallet_addr
|
||||
."new wallet address = " 2dup .addr cr
|
||||
2dup file-base +".addr" save-address-verbose
|
||||
."Non-bounceable address (for init): " 2dup 7 .Addr cr
|
||||
."Bounceable address (for later access): " 6 .Addr cr
|
||||
<b 0 32 u, -1 32 i, b>
|
||||
dup ."signing message: " <s csr. cr
|
||||
dup hash wallet_pk ed25519_sign_uint rot
|
||||
<b b{1000100} s, wallet_addr addr, b{000010} s, swap <s s, b{0} s, swap B, swap <s s, b>
|
||||
dup ."External message for initialization is " <s csr. cr
|
||||
2 boc+>B dup Bx. cr
|
||||
file-base +"-query.boc" tuck B>file
|
||||
."(Saved wallet creating query to file " type .")" cr
|
|
@ -8,7 +8,7 @@
|
|||
var signature = in_msg~load_bits(512);
|
||||
var cs = in_msg;
|
||||
var (msg_seqno, valid_until) = (cs~load_uint(32), cs~load_uint(32));
|
||||
throw_if(35, valid_until < now());
|
||||
throw_if(35, valid_until <= now());
|
||||
var ds = get_data().begin_parse();
|
||||
var (stored_seqno, public_key) = (ds~load_uint(32), ds~load_uint(256));
|
||||
ds.end_parse();
|
||||
|
|
48
crypto/smartcont/wallet-v2.fif
Normal file
48
crypto/smartcont/wallet-v2.fif
Normal file
|
@ -0,0 +1,48 @@
|
|||
#!/usr/bin/env fift -s
|
||||
"TonUtil.fif" include
|
||||
|
||||
{ ."usage: " @' $0 type ." <filename-base> <dest-addr> <seqno> <amount> [-B <body-boc>] [<savefile>]" cr
|
||||
."Creates a request to advanced wallet created by new-wallet-v2.fif, with private key loaded from file <filename-base>.pk "
|
||||
."and address from <filename-base>.addr, and saves it into <savefile>.boc ('wallet-query.boc' by default)" cr 1 halt
|
||||
} : usage
|
||||
def? $6 { @' $5 "-B" $= { @' $6 =: body-boc-file [forget] $6 def? $7 { @' $7 =: $5 [forget] $7 } { [forget] $5 } cond
|
||||
@' $# 2- =: $# } if } if
|
||||
$# dup 4 < swap 5 > or ' usage if
|
||||
|
||||
true constant bounce
|
||||
|
||||
$1 =: file-base
|
||||
$2 bounce parse-load-address =: bounce 2=: dest_addr
|
||||
$3 parse-int =: seqno
|
||||
$4 $>GR =: amount
|
||||
def? $5 { @' $5 } { "wallet-query" } cond constant savefile
|
||||
3 constant send-mode // mode for SENDRAWMSG: +1 - sender pays fees, +2 - ignore errors
|
||||
60 constant timeout // external message expires in 60 seconds
|
||||
|
||||
file-base +".addr" load-address
|
||||
2dup 2constant wallet_addr
|
||||
."Source wallet address = " 2dup .addr cr 6 .Addr cr
|
||||
file-base +".pk" load-keypair nip constant wallet_pk
|
||||
|
||||
def? body-boc-file { @' body-boc-file file>B B>boc } { <b 0 32 u, "TESTv2" $, b> } cond
|
||||
constant body-cell
|
||||
|
||||
."Transferring " amount .GR ."to account "
|
||||
dest_addr 2dup bounce 7 + .Addr ." = " .addr
|
||||
."seqno=0x" seqno x. ."bounce=" bounce . cr
|
||||
."Body of transfer message is " body-cell <s csr. cr
|
||||
|
||||
// create a message
|
||||
<b b{01} s, bounce 1 i, b{000100} s, dest_addr addr, amount Gram, 0 9 64 32 + + 1+ u,
|
||||
body-cell <s 2dup s-fits? not rot over 1 i, -rot { drop body-cell ref, } { s, } cond
|
||||
b>
|
||||
<b seqno 32 u, now timeout + 32 u, send-mode 8 u, swap ref, b>
|
||||
dup ."signing message: " <s csr. cr
|
||||
dup hash wallet_pk ed25519_sign_uint
|
||||
<b b{1000100} s, wallet_addr addr, 0 Gram, b{00} s,
|
||||
swap B, swap <s s, b>
|
||||
dup ."resulting external message: " <s csr. cr
|
||||
2 boc+>B dup Bx. cr
|
||||
savefile +".boc" tuck B>file
|
||||
."Query expires in " timeout . ."seconds" cr
|
||||
."(Saved to file " type .")" cr
|
|
@ -176,6 +176,10 @@ class TLB {
|
|||
virtual bool store_integer_ref(vm::CellBuilder& cb, td::RefInt256 value) const {
|
||||
return value.not_null() && store_integer_value(cb, *value);
|
||||
}
|
||||
bool pack_integer(Ref<vm::CellSlice>& csr, td::RefInt256 value) const {
|
||||
vm::CellBuilder cb;
|
||||
return store_integer_ref(cb, value) && (csr = vm::load_cell_slice_ref(cb.finalize())).not_null();
|
||||
}
|
||||
virtual bool add_values(vm::CellBuilder& cb, vm::CellSlice& cs1, vm::CellSlice& cs2) const {
|
||||
td::RefInt256 x = as_integer_skip(cs1), y = as_integer_skip(cs2);
|
||||
return x.not_null() && y.not_null() && store_integer_ref(cb, x += std::move(y));
|
||||
|
|
|
@ -988,27 +988,27 @@ td::Result<td::BufferSlice> std_boc_serialize_multi(std::vector<Ref<Cell>> roots
|
|||
*
|
||||
*/
|
||||
|
||||
bool CellStorageStat::compute_used_storage(Ref<vm::CellSlice> cs_ref, bool kill_dup, bool skip_count_root) {
|
||||
bool CellStorageStat::compute_used_storage(Ref<vm::CellSlice> cs_ref, bool kill_dup, unsigned skip_count_root) {
|
||||
clear();
|
||||
return add_used_storage(std::move(cs_ref), kill_dup, skip_count_root) && clear_seen();
|
||||
}
|
||||
|
||||
bool CellStorageStat::compute_used_storage(const CellSlice& cs, bool kill_dup, bool skip_count_root) {
|
||||
bool CellStorageStat::compute_used_storage(const CellSlice& cs, bool kill_dup, unsigned skip_count_root) {
|
||||
clear();
|
||||
return add_used_storage(cs, kill_dup, skip_count_root) && clear_seen();
|
||||
}
|
||||
|
||||
bool CellStorageStat::compute_used_storage(CellSlice&& cs, bool kill_dup, bool skip_count_root) {
|
||||
bool CellStorageStat::compute_used_storage(CellSlice&& cs, bool kill_dup, unsigned skip_count_root) {
|
||||
clear();
|
||||
return add_used_storage(std::move(cs), kill_dup, skip_count_root) && clear_seen();
|
||||
}
|
||||
|
||||
bool CellStorageStat::compute_used_storage(Ref<vm::Cell> cell, bool kill_dup, bool skip_count_root) {
|
||||
bool CellStorageStat::compute_used_storage(Ref<vm::Cell> cell, bool kill_dup, unsigned skip_count_root) {
|
||||
clear();
|
||||
return add_used_storage(std::move(cell), kill_dup, skip_count_root) && clear_seen();
|
||||
}
|
||||
|
||||
bool CellStorageStat::add_used_storage(Ref<vm::CellSlice> cs_ref, bool kill_dup, bool skip_count_root) {
|
||||
bool CellStorageStat::add_used_storage(Ref<vm::CellSlice> cs_ref, bool kill_dup, unsigned skip_count_root) {
|
||||
if (cs_ref->is_unique()) {
|
||||
return add_used_storage(std::move(cs_ref.unique_write()), kill_dup, skip_count_root);
|
||||
} else {
|
||||
|
@ -1016,11 +1016,13 @@ bool CellStorageStat::add_used_storage(Ref<vm::CellSlice> cs_ref, bool kill_dup,
|
|||
}
|
||||
}
|
||||
|
||||
bool CellStorageStat::add_used_storage(const CellSlice& cs, bool kill_dup, bool skip_count_root) {
|
||||
if (!skip_count_root) {
|
||||
bool CellStorageStat::add_used_storage(const CellSlice& cs, bool kill_dup, unsigned skip_count_root) {
|
||||
if (!(skip_count_root & 1)) {
|
||||
++cells;
|
||||
}
|
||||
if (!(skip_count_root & 2)) {
|
||||
bits += cs.size();
|
||||
}
|
||||
for (unsigned i = 0; i < cs.size_refs(); i++) {
|
||||
if (!add_used_storage(cs.prefetch_ref(i), kill_dup)) {
|
||||
return false;
|
||||
|
@ -1029,11 +1031,13 @@ bool CellStorageStat::add_used_storage(const CellSlice& cs, bool kill_dup, bool
|
|||
return true;
|
||||
}
|
||||
|
||||
bool CellStorageStat::add_used_storage(CellSlice&& cs, bool kill_dup, bool skip_count_root) {
|
||||
if (!skip_count_root) {
|
||||
bool CellStorageStat::add_used_storage(CellSlice&& cs, bool kill_dup, unsigned skip_count_root) {
|
||||
if (!(skip_count_root & 1)) {
|
||||
++cells;
|
||||
}
|
||||
if (!(skip_count_root & 2)) {
|
||||
bits += cs.size();
|
||||
}
|
||||
while (cs.size_refs()) {
|
||||
if (!add_used_storage(cs.fetch_ref(), kill_dup)) {
|
||||
return false;
|
||||
|
@ -1042,7 +1046,7 @@ bool CellStorageStat::add_used_storage(CellSlice&& cs, bool kill_dup, bool skip_
|
|||
return true;
|
||||
}
|
||||
|
||||
bool CellStorageStat::add_used_storage(Ref<vm::Cell> cell, bool kill_dup, bool skip_count_root) {
|
||||
bool CellStorageStat::add_used_storage(Ref<vm::Cell> cell, bool kill_dup, unsigned skip_count_root) {
|
||||
if (cell.is_null()) {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -122,15 +122,15 @@ struct CellStorageStat {
|
|||
cells = bits = public_cells = 0;
|
||||
clear_seen();
|
||||
}
|
||||
bool compute_used_storage(Ref<vm::CellSlice> cs_ref, bool kill_dup = true, bool skip_count_root = false);
|
||||
bool compute_used_storage(const CellSlice& cs, bool kill_dup = true, bool skip_count_root = false);
|
||||
bool compute_used_storage(CellSlice&& cs, bool kill_dup = true, bool skip_count_root = false);
|
||||
bool compute_used_storage(Ref<vm::Cell> cell, bool kill_dup = true, bool skip_count_root = false);
|
||||
bool compute_used_storage(Ref<vm::CellSlice> cs_ref, bool kill_dup = true, unsigned skip_count_root = 0);
|
||||
bool compute_used_storage(const CellSlice& cs, bool kill_dup = true, unsigned skip_count_root = 0);
|
||||
bool compute_used_storage(CellSlice&& cs, bool kill_dup = true, unsigned skip_count_root = 0);
|
||||
bool compute_used_storage(Ref<vm::Cell> cell, bool kill_dup = true, unsigned skip_count_root = 0);
|
||||
|
||||
bool add_used_storage(Ref<vm::CellSlice> cs_ref, bool kill_dup = true, bool skip_count_root = false);
|
||||
bool add_used_storage(const CellSlice& cs, bool kill_dup = true, bool skip_count_root = false);
|
||||
bool add_used_storage(CellSlice&& cs, bool kill_dup = true, bool skip_count_root = false);
|
||||
bool add_used_storage(Ref<vm::Cell> cell, bool kill_dup = true, bool skip_count_root = false);
|
||||
bool add_used_storage(Ref<vm::CellSlice> cs_ref, bool kill_dup = true, unsigned skip_count_root = 0);
|
||||
bool add_used_storage(const CellSlice& cs, bool kill_dup = true, unsigned skip_count_root = 0);
|
||||
bool add_used_storage(CellSlice&& cs, bool kill_dup = true, unsigned skip_count_root = 0);
|
||||
bool add_used_storage(Ref<vm::Cell> cell, bool kill_dup = true, unsigned skip_count_root = 0);
|
||||
};
|
||||
|
||||
struct CellSerializationInfo {
|
||||
|
|
|
@ -207,9 +207,8 @@ bool CellBuilder::store_bits_bool(const unsigned char* str, std::size_t bit_coun
|
|||
|
||||
CellBuilder& CellBuilder::store_bits(const unsigned char* str, std::size_t bit_count, int bit_offset) {
|
||||
unsigned pos = bits;
|
||||
if (prepare_reserve(bit_count)) {
|
||||
ensure_throw(prepare_reserve(bit_count));
|
||||
td::bitstring::bits_memcpy(data, pos, str, bit_offset, bit_count);
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
|
|
|
@ -5,15 +5,15 @@ This document collects some recommendations and best practices that could be fol
|
|||
|
||||
Smart contracts interact with each other by sending the so-called *internal messages*. When an internal message reaches its intended destination, an ordinary transaction is created on behalf of the destination account, and the internal message is processed as specified by the code and the persistent data of this account (smart contract). In particular, the processing transaction can create one or several outbound internal messages, some of which could be addressed to the source address of the internal message being processed. This can be used to create simple "client-server applications", when a query is incapsulated in an internal message and sent to another smart contract, which processes the query and sends back a response, again as an internal message.
|
||||
|
||||
This approach leads to the necessity of distinguishing whether an internal message is intended as a "query" or as a "response", or just as a "simple money transfer" not requiring any additional processing. Furthermore, when a response is received, there must be a means to understand to which query it corresponds.
|
||||
This approach leads to the necessity of distinguishing whether an internal message is intended as a "query" or as a "response", or doesn't require any additional processing (like a "simple money transfer"). Furthermore, when a response is received, there must be a means to understand to which query it corresponds.
|
||||
|
||||
In order to achieve this goal, the following recommended internal message layout can be used (notice that the TON Blockchain does not enforce any restrictions on the message body, so these are indeed just recommendations):
|
||||
|
||||
0) The body of the message can be embedded into the message itself, or be stored in a separate cell referred to from the message, as indicated by TL-B scheme fragment
|
||||
0) The body of the message can be embedded into the message itself, or be stored in a separate cell referred to from the message, as indicated by the TL-B scheme fragment
|
||||
|
||||
message$_ {X:Type} ... body:(Either X ^X) = Message X;
|
||||
|
||||
The receiving smart contract should accept at least internal messages with embedded message bodies (whenever they fit into the cell containing the message). If it accepts message bodies in separate cells (using the `right` constructor of `(Either X ^X)`), the processing of the inbound message should not depend on the specific embedding option chosen for the message body. On the other side, it is perfectly valid not to support message bodies in separate cells at all for simpler queries and responces.
|
||||
The receiving smart contract should accept at least internal messages with embedded message bodies (whenever they fit into the cell containing the message). If it accepts message bodies in separate cells (using the `right` constructor of `(Either X ^X)`), the processing of the inbound message should not depend on the specific embedding option chosen for the message body. On the other hand, it is perfectly valid not to support message bodies in separate cells at all for simpler queries and responses.
|
||||
|
||||
1) The message body typically begins with the following fields:
|
||||
|
||||
|
@ -21,20 +21,20 @@ The receiving smart contract should accept at least internal messages with embed
|
|||
* A 64-bit (big-endian) unsigned integer *query_id*, used in all query-response internal messages to indicate that a response is related to a query (the *query_id* of a response must be equal to the *query_id* of the corresponding query). If *op* is not a query-response method (e.g., it invokes a method that is not expected to send an answer), then *query_id* may be omitted.
|
||||
* The remainder of the message body is specific for each supported value of *op*.
|
||||
|
||||
2) If *op* is zero, then the message is a "simple transfer message with comment". The comment is contained in the remainder of the message body (without any *query_id* field, i.e., starting from the fifth byte). If it does not begin with the byte 0xff, the comment is a text one; it can be displayed "as is" to the end user of a wallet (after filtering invalid and control characters and checking that it is a valid UTF-8 string). For instance, users may indicate the purpose of a simple transfer from their wallet to the wallet of another user in this text field. On the other hand, if the comment begins with byte 0xff, the remainder is a "binary comment", which should not be displayed to the end user as text (only as hex dump if necessary). The intended use of "binary comments" is that it can be used, e.g., to contain a purchase indentifier for payments in a shop, to be automatically generated and processed by the shop's software.
|
||||
2) If *op* is zero, then the message is a "simple transfer message with comment". The comment is contained in the remainder of the message body (without any *query_id* field, i.e., starting from the fifth byte). If it does not begin with the byte 0xff, the comment is a text one; it can be displayed "as is" to the end user of a wallet (after filtering invalid and control characters and checking that it is a valid UTF-8 string). For instance, users may indicate the purpose of a simple transfer from their wallet to the wallet of another user in this text field. On the other hand, if the comment begins with the byte 0xff, the remainder is a "binary comment", which should not be displayed to the end user as text (only as hex dump if necessary). The intended use of "binary comments" is, e.g., to contain a purchase identifier for payments in a store, to be automatically generated and processed by the store's software.
|
||||
|
||||
Most smart contracts should not perform non-trivial actions or reject the inbound message on receiving a "simple transfer message". In this way, once *op* is found to be zero, the smart contract function for processing inbound internal messages (usually called `recv_internal()`) should immediately terminate with a zero exit code indicating success (e.g., by throwing exception 0, if no custom exception handler has been installed by the smart contract). This will lead to crediting the receiving account with the value transfered by the message without any further effect.
|
||||
Most smart contracts should not perform non-trivial actions or reject the inbound message on receiving a "simple transfer message". In this way, once *op* is found to be zero, the smart contract function for processing inbound internal messages (usually called `recv_internal()`) should immediately terminate with a zero exit code indicating success (e.g., by throwing exception 0, if no custom exception handler has been installed by the smart contract). This will lead to the receiving account being credited with the value transferred by the message without any further effect.
|
||||
|
||||
3) A "simple transfer message without comment" has an empty body (without even an *op* field). The above considerations apply to such messages as well. Note that such messages should have their bodies embedded into the message cell.
|
||||
|
||||
4) We expect "query" messages to have *op* with high-order bit clear, i.e., in the range 1 .. 2^31-1, and "response" messages to have *op* with high-order bit set, i.e., in the range 2^31 .. 2^32-1. If a method is neither a query nor a response (so that the corresponding message body does not contain a *query_id* field), it should use an *op* in the "query" range 1 .. 2^31 - 1.
|
||||
4) We expect "query" messages to have an *op* with the high-order bit clear, i.e., in the range 1 .. 2^31-1, and "response" messages to have an *op* with the high-order bit set, i.e., in the range 2^31 .. 2^32-1. If a method is neither a query nor a response (so that the corresponding message body does not contain a *query_id* field), it should use an *op* in the "query" range 1 .. 2^31 - 1.
|
||||
|
||||
5) There are some "standard" response messages with *op* equal to 0xffffffff and 0xfffffffe. In general, the values of *op* from 0xfffffff0 to 0xffffffff are reserved for such standard responses.
|
||||
5) There are some "standard" response messages with the *op* equal to 0xffffffff and 0xfffffffe. In general, the values of *op* from 0xfffffff0 to 0xffffffff are reserved for such standard responses.
|
||||
|
||||
* op = 0xffffffff means "operation not supported". It is followed by 64-bit *query_id* extracted from the original query, and a 32-bit *op* of the original query. All but the simplest smart contracts should return this error when they receive a query with unknown *op* in the range 1 .. 2^31-1.
|
||||
* op = 0xfffffffe means "operation not allowed". It is followed by 64-bit *query_id* of the original query, followed by 32-bit *op* extracted from the original query.
|
||||
* op = 0xffffffff means "operation not supported". It is followed by the 64-bit *query_id* extracted from the original query, and the 32-bit *op* of the original query. All but the simplest smart contracts should return this error when they receive a query with an unknown *op* in the range 1 .. 2^31-1.
|
||||
* op = 0xfffffffe means "operation not allowed". It is followed by the 64-bit *query_id* of the original query, followed by the 32-bit *op* extracted from the original query.
|
||||
|
||||
Notice that unknown "responses" (with *op* in the range 2^31 .. 2^32-1) should be ignored (in particular, no response with *op* equal to 0xffffffff should be generated in response to them), as should unexpected bounced messages (with "bounced" flag set).
|
||||
Notice that unknown "responses" (with an *op* in the range 2^31 .. 2^32-1) should be ignored (in particular, no response with an *op* equal to 0xffffffff should be generated in response to them), just as unexpected bounced messages (with "bounced" flag set).
|
||||
|
||||
2. Paying for processing queries and sending responses
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
@ -43,33 +43,33 @@ In general, if a smart contract wants to send a query to another smart contract,
|
|||
|
||||
In most cases, the sender will attach a small amount of Grams (e.g., one Gram) to the internal message (sufficient to pay for the processing of this message), and set its "bounce" flag (i.e., send a bounceable internal message); the receiver will return the unused portion of the received value with the answer (deducting message forwarding fees from it). This is normally accomplished by invoking SENDRAWMSG with mode=64 (cf. Appendix A of TON VM documentation).
|
||||
|
||||
If the receiver is unable to parse the received message and terminates with a non-zero exit code (for example, because of an unhandled cell deserialization exception), the message will be automatically "bounced" back to its sender, with the "bounce" flag cleared and the "bounced" flag set. The body of the bounced message will be the same as that of the original message; therefore, it is important to check the "bounced" flag of the incoming internal messages before parsing the *op* field in the smart contract and processing the corresponding query (otherwise there is a risk that the query contained in a bounced message will be processed by its original sender as a new separate query). If this flag is set, special code could find out which query has failed (e.g., by deserializing *op* and *query_id* from the bounced message) and take appropriate action. A simpler smart contract might simply ignore all bounced messages (terminate with zero exit code if "bounced" flag is set).
|
||||
If the receiver is unable to parse the received message and terminates with a non-zero exit code (for example, because of an unhandled cell deserialization exception), the message will be automatically "bounced" back to its sender, with the "bounce" flag cleared and the "bounced" flag set. The body of the bounced message will be the same as that of the original message; therefore, it is important to check the "bounced" flag of incoming internal messages before parsing the *op* field in the smart contract and processing the corresponding query (otherwise there is a risk that the query contained in a bounced message will be processed by its original sender as a new separate query). If the "bounced" flag is set, special code could find out which query has failed (e.g., by deserializing *op* and *query_id* from the bounced message) and take appropriate action. A simpler smart contract might simply ignore all bounced messages (terminate with zero exit code if "bounced" flag is set).
|
||||
|
||||
On the other hand, the receiver might parse the incoming query successfully and find out that the requested method *op* is not supported, or that another error condition is met. Then a response with *op* equal to 0xffffffff or another appropriate value should be sent back, using SENDRAWMSG with mode=64 as mentioned above.
|
||||
|
||||
In some situations, the sender wants both to transfer some value to the sender and to receive an error message or a confirmation. For instance, the validator elections smart contract receives an election participation request along with the stake as the attached value. In such cases, it makes sense to attach, say, one extra Gram to the intended value. If there is an error (e.g., the stake may not be accepted for any reason), the full received amount (minus processing fees) should be returned to the sender along with an error message (e.g., by using SENDRAWMSG with mode=64 as explained before). In the case of success, the confirmation message is created and exactly one Gram is sent back (with the message transferring fees deducted from this value; this is mode=1 of SENDRAWMSG).
|
||||
In some situations, the sender wants both to transfer some value to the sender and to receive either a confirmation or an error message. For instance, the validator elections smart contract receives an election participation request along with the stake as the attached value. In such cases, it makes sense to attach, say, one extra Gram to the intended value. If there is an error (e.g., the stake may not be accepted for any reason), the full received amount (minus the processing fees) should be returned to the sender along with an error message (e.g., by using SENDRAWMSG with mode=64 as explained before). In the case of success, the confirmation message is created and exactly one Gram is sent back (with the message transferring fees deducted from this value; this is mode=1 of SENDRAWMSG).
|
||||
|
||||
3. Using non-bounceable messages
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
Almost all internal messages sent between smart contracts should be bounceable, i.e., should have their "bounce" bit set. Then, if the destination smart contract does not exist, or if it throws an unhandled exception while processing this message, the message will be "bounced" back carrying the remainder of the original value (minus all message transfer and gas fees). The bounced message will have the same body, but with "bounce" flag cleared and "bounced" flag set. Therefore, all smart contracts should check the "bounced" flag of all inbound messages and either silently accept them (by immediately terminating with zero exit code) or perform special processing to detect which outbound query has failed. The query contained in the body of a bounced message should never be executed.
|
||||
Almost all internal messages sent between smart contracts should be bounceable, i.e., should have their "bounce" bit set. Then, if the destination smart contract does not exist, or if it throws an unhandled exception while processing this message, the message will be "bounced" back carrying the remainder of the original value (minus all message transfer and gas fees). The bounced message will have the same body, but with the "bounce" flag cleared and the "bounced" flag set. Therefore, all smart contracts should check the "bounced" flag of all inbound messages and either silently accept them (by immediately terminating with a zero exit code) or perform some special processing to detect which outbound query has failed. The query contained in the body of a bounced message should never be executed.
|
||||
|
||||
On some occasions, non-bounceable internal messages must be used. For instance, new accounts cannot be created without at least one non-bounceable internal message sent to them. Unless this message contains a StateInit with the code and data of the new smart contract, it does not make sense to have a non-empty body in a non-bounceable internal message.
|
||||
|
||||
It is a good idea not to allow the end user (e.g., of a wallet) to send unbounceable messages carrying large value (e.g., more than five Grams), or at least to warn them if they try to. It is a better idea to send a small amount, then initialize the new smart contract, and then send a larger amount.
|
||||
It is a good idea not to allow the end user (e.g., of a wallet) to send unbounceable messages carrying large value (e.g., more than five Grams), or at least to warn them if they try this. It is a better idea to send a small amount first, then initialize the new smart contract, and then send a larger amount.
|
||||
|
||||
4. External messages
|
||||
~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
The external messages are sent from the outside to the smart contracts residing in the TON Blockchain to make them perform certain actions. For instance, a wallet smart contract expects to receive external messages containing orders (e.g., internal messages to be sent from the wallet smart contract) signed by the wallet's owner; when such an external message is received by the wallet smart contract, it first checks the signature, then accepts the message (by running the TVM primitive ACCEPT), and then performs whatever actions are required.
|
||||
External messages are sent from the outside to the smart contracts residing in the TON Blockchain to make them perform certain actions. For instance, a wallet smart contract expects to receive external messages containing orders (e.g., internal messages to be sent from the wallet smart contract) signed by the wallet's owner; when such an external message is received by the wallet smart contract, it first checks the signature, then accepts the message (by running the TVM primitive ACCEPT), and then performs whatever actions are required.
|
||||
|
||||
Notice that all external messages must be protected against replay attacks. The validators normally remove an external message from the pool of suggested external messages (received from network); however, in some situations another validator could process the same external message twice (thus creating a second transaction for the same external message, leading to the duplication of the original action). Even worse, a malicious actor could extract the external message from the block containing the processing transaction and re-send it later. This would force a wallet smart contract to repeat a payment, for example.
|
||||
Notice that all external messages must be protected against replay attacks. The validators normally remove an external message from the pool of suggested external messages (received from network); however, in some situations another validator could process the same external message twice (thus creating a second transaction for the same external message, leading to the duplication of the original action). Even worse, a malicious actor could extract the external message from the block containing the processing transaction and re-send it later. This could force a wallet smart contract to repeat a payment, for example.
|
||||
|
||||
The simplest way to protect smart contracts from replay attacks related to external messages is to store a 32-bit counter *cur-seqno* in the persistent data of the smart contract, and to expect a *req-seqno* value in (the signed part of) any inbound external messages. Then an external message is ACCEPTed if simultaneously the signature is valid and *req-seqno* equals *cur-seqno*. After successful processing, the *cur-seqno* value in the persistent data is increased by one, so the same external message will never be accepted again.
|
||||
The simplest way to protect smart contracts from replay attacks related to external messages is to store a 32-bit counter *cur-seqno* in the persistent data of the smart contract, and to expect a *req-seqno* value in (the signed part of) any inbound external messages. Then an external message is ACCEPTed only if both the signature is valid and *req-seqno* equals *cur-seqno*. After successful processing, the *cur-seqno* value in the persistent data is increased by one, so the same external message will never be accepted again.
|
||||
|
||||
One could also include an *expire-at* field in the external message, and accept an external message only if the current Unix time is less than the value of this field. This approach can be used in conjuction with *seqno*; alternatively, the receiving smart contract could store the set of (the hashes of) all recent (not expired) accepted external messages in its persistent data, and reject a new external message if it is a duplicate of one of the stored messages. Some garbage collecting of expired messages in this set should also be performed to avoid bloating the persistent data.
|
||||
|
||||
In general, an external message begins with a 256-bit signature (if needed), a 32-bit *req-seqno* (if needed), a 32-bit *expire-at* (if needed), and possibly a 32-bit *op* and other required parameters depending on *op*. The layout of external messages does not need to be so standard as that of the internal messages because external messages are not used for interaction between different smart contracts (written by different developers and managed by different owners).
|
||||
In general, an external message begins with a 256-bit signature (if needed), a 32-bit *req-seqno* (if needed), a 32-bit *expire-at* (if needed), and possibly a 32-bit *op* and other required parameters depending on *op*. The layout of external messages does not need to be as standardized as that of the internal messages because external messages are not used for interaction between different smart contracts (written by different developers and managed by different owners).
|
||||
|
||||
5. Get-methods
|
||||
~~~~~~~~~~~~~~
|
||||
|
|
|
@ -59,12 +59,12 @@ class TonTest {
|
|||
val client = ClientKotlin()
|
||||
val dir = getContext().getExternalFilesDir(null).toString() + "/";
|
||||
runBlocking {
|
||||
client.send(TonApi.Init(TonApi.Options(config, dir)))
|
||||
client.send(TonApi.Init(TonApi.Options(TonApi.Config(config, "", false, false), dir)))
|
||||
val key = client.send(TonApi.CreateNewKey("local password".toByteArray(), "mnemonic password".toByteArray(), "".toByteArray())) as TonApi.Key
|
||||
val walletAddress = client.send(TonApi.TestWalletGetAccountAddress(TonApi.TestWalletInitialAccountState(key.publicKey))) as TonApi.AccountAddress;
|
||||
val testGiverState = client.send(TonApi.TestGiverGetAccountState()) as TonApi.TestGiverAccountState
|
||||
|
||||
client.send(TonApi.TestGiverSendGrams(walletAddress, testGiverState.seqno, 6660000000)) as TonApi.Ok
|
||||
client.send(TonApi.TestGiverSendGrams(walletAddress, testGiverState.seqno, 6660000000, "".toByteArray())) as TonApi.Ok
|
||||
|
||||
while ((client.send(TonApi.GenericGetAccountState(walletAddress)) as TonApi.GenericAccountStateUninited).accountState.balance <= 0L) {
|
||||
delay(1000L)
|
||||
|
@ -79,7 +79,7 @@ class TonTest {
|
|||
|
||||
val state = client.send(TonApi.GenericGetAccountState(walletAddress)) as TonApi.GenericAccountStateTestWallet
|
||||
val balance = state.accountState.balance
|
||||
client.send(TonApi.GenericSendGrams(inputKey, walletAddress, walletAddress, 10)) as TonApi.Ok
|
||||
client.send(TonApi.GenericSendGrams(inputKey, walletAddress, walletAddress, 10, 0, true, "hello".toByteArray())) as TonApi.Ok
|
||||
while ((client.send(TonApi.GenericGetAccountState(walletAddress)) as TonApi.GenericAccountStateTestWallet).accountState.balance == balance) {
|
||||
delay(1000L)
|
||||
}
|
||||
|
|
|
@ -86,7 +86,7 @@ public class TonTestJava {
|
|||
appendLog("start...");
|
||||
|
||||
JavaClient client = new JavaClient();
|
||||
Object result = client.send(new TonApi.Init(new TonApi.Options(config, getContext().getExternalFilesDir(null) + "/")));
|
||||
Object result = client.send(new TonApi.Init(new TonApi.Options(new TonApi.Config(config, "", false, false), getContext().getExternalFilesDir(null) + "/")));
|
||||
if (!(result instanceof TonApi.Ok)) {
|
||||
appendLog("failed to set config");
|
||||
return;
|
||||
|
@ -98,7 +98,7 @@ public class TonTestJava {
|
|||
appendLog("got account address");
|
||||
appendLog("sending grams...");
|
||||
TonApi.TestGiverAccountState testGiverState = (TonApi.TestGiverAccountState) client.send(new TonApi.TestGiverGetAccountState());
|
||||
result = client.send(new TonApi.TestGiverSendGrams(walletAddress, testGiverState.seqno, 6660000000L));
|
||||
result = client.send(new TonApi.TestGiverSendGrams(walletAddress, testGiverState.seqno, 6660000000L, "".getBytes()));
|
||||
if (!(result instanceof TonApi.Ok)) {
|
||||
appendLog("failed to send grams");
|
||||
return;
|
||||
|
@ -143,7 +143,7 @@ public class TonTestJava {
|
|||
appendLog("sending grams...");
|
||||
TonApi.GenericAccountStateTestWallet state = (TonApi.GenericAccountStateTestWallet) client.send(new TonApi.GenericGetAccountState(walletAddress));
|
||||
long balance = state.accountState.balance;
|
||||
result = client.send(new TonApi.GenericSendGrams(inputKey, walletAddress, walletAddress, 10));
|
||||
result = client.send(new TonApi.GenericSendGrams(inputKey, walletAddress, walletAddress, 10, 0, true, "hello".getBytes()));
|
||||
if (!(result instanceof TonApi.Ok)) {
|
||||
return;
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include "td/utils/common.h"
|
||||
#include "td/utils/logging.h"
|
||||
#include "td/utils/misc.h"
|
||||
#include "td/utils/SharedSlice.h"
|
||||
#include "td/utils/Slice.h"
|
||||
#include "td/utils/StorerBase.h"
|
||||
#include "td/utils/UInt.h"
|
||||
|
@ -233,6 +234,12 @@ class TlStorerToString {
|
|||
store_field_end();
|
||||
}
|
||||
|
||||
void store_field(const char *name, const SecureString &value) {
|
||||
store_field_begin(name);
|
||||
result.append("<secret>");
|
||||
store_field_end();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
void store_field(const char *name, const T &value) {
|
||||
store_field_begin(name);
|
||||
|
@ -240,6 +247,12 @@ class TlStorerToString {
|
|||
store_field_end();
|
||||
}
|
||||
|
||||
void store_bytes_field(const char *name, const SecureString &value) {
|
||||
store_field_begin(name);
|
||||
result.append("<secret>");
|
||||
store_field_end();
|
||||
}
|
||||
|
||||
template <class BytesT>
|
||||
void store_bytes_field(const char *name, const BytesT &value) {
|
||||
static const char *hex = "0123456789ABCDEF";
|
||||
|
|
|
@ -16,7 +16,8 @@ vector {t:Type} # [ t ] = Vector t;
|
|||
error code:int32 message:string = Error;
|
||||
ok = Ok;
|
||||
|
||||
options config:string keystore_directory:string use_callbacks_for_network:Bool = Options;
|
||||
config config:string blockchain_name:string use_callbacks_for_network:Bool ignore_cache:Bool = Config;
|
||||
options config:config keystore_directory:string = Options;
|
||||
|
||||
key public_key:string secret:secureBytes = Key;
|
||||
inputKey key:key local_password:secureBytes = InputKey;
|
||||
|
@ -28,6 +29,8 @@ bip39Hints words:vector<string> = Bip39Hints;
|
|||
|
||||
accountAddress account_address:string = AccountAddress;
|
||||
|
||||
unpackedAccountAddress workchain_id:int32 bounceable:Bool testnet:Bool addr:bytes = UnpackedAccountAddress;
|
||||
|
||||
internal.transactionId lt:int64 hash:bytes = internal.TransactionId;
|
||||
|
||||
raw.initialAccountState code:bytes data:bytes = raw.InitialAccountState;
|
||||
|
@ -56,7 +59,7 @@ generic.accountStateWallet account_state:wallet.accountState = generic.AccountSt
|
|||
generic.accountStateTestGiver account_state:testGiver.accountState = generic.AccountState;
|
||||
generic.accountStateUninited account_state:uninited.accountState = generic.AccountState;
|
||||
|
||||
//generic.sendGramsResult sent_until:int53 = generic.SendGramsResult;
|
||||
sendGramsResult sent_until:int53 = SendGramsResult;
|
||||
|
||||
updateSendLiteServerQuery id:int64 data:bytes = Update;
|
||||
|
||||
|
@ -83,7 +86,7 @@ logTags tags:vector<string> = LogTags;
|
|||
init options:options = Ok;
|
||||
close = Ok;
|
||||
|
||||
options.setConfig config:string = Ok;
|
||||
options.setConfig config:config = Ok;
|
||||
|
||||
createNewKey local_password:secureBytes mnemonic_password:secureBytes random_extra_seed:secureBytes = Key;
|
||||
deleteKey key:key = Ok;
|
||||
|
@ -95,6 +98,9 @@ importPemKey local_password:secureBytes key_password:secureBytes exported_key:ex
|
|||
importEncryptedKey local_password:secureBytes key_password:secureBytes exported_encrypted_key:exportedEncryptedKey = Key;
|
||||
changeLocalPassword input_key:inputKey new_local_password:secureBytes = Key;
|
||||
|
||||
unpackAccountAddress account_address:string = UnpackedAccountAddress;
|
||||
packAccountAddress account_address:unpackedAccountAddress = AccountAddress;
|
||||
|
||||
getBip39Hints prefix:string = Bip39Hints;
|
||||
|
||||
//raw.init initial_account_state:raw.initialAccountState = Ok;
|
||||
|
@ -106,20 +112,20 @@ raw.getTransactions account_address:accountAddress from_transaction_id:internal.
|
|||
testWallet.init private_key:inputKey = Ok;
|
||||
testWallet.getAccountAddress initital_account_state:testWallet.initialAccountState = AccountAddress;
|
||||
testWallet.getAccountState account_address:accountAddress = testWallet.AccountState;
|
||||
testWallet.sendGrams private_key:inputKey destination:accountAddress seqno:int32 amount:int64 message:bytes = Ok;
|
||||
testWallet.sendGrams private_key:inputKey destination:accountAddress seqno:int32 amount:int64 message:bytes = SendGramsResult;
|
||||
|
||||
wallet.init private_key:inputKey = Ok;
|
||||
wallet.getAccountAddress initital_account_state:wallet.initialAccountState = AccountAddress;
|
||||
wallet.getAccountState account_address:accountAddress = wallet.AccountState;
|
||||
wallet.sendGrams private_key:inputKey destination:accountAddress seqno:int32 valid_until:int53 amount:int64 message:bytes = Ok;
|
||||
wallet.sendGrams private_key:inputKey destination:accountAddress seqno:int32 valid_until:int53 amount:int64 message:bytes = SendGramsResult;
|
||||
|
||||
testGiver.getAccountState = testGiver.AccountState;
|
||||
testGiver.getAccountAddress = AccountAddress;
|
||||
testGiver.sendGrams destination:accountAddress seqno:int32 amount:int64 message:bytes = Ok;
|
||||
testGiver.sendGrams destination:accountAddress seqno:int32 amount:int64 message:bytes = SendGramsResult;
|
||||
|
||||
//generic.getAccountAddress initital_account_state:generic.InitialAccountState = AccountAddress;
|
||||
generic.getAccountState account_address:accountAddress = generic.AccountState;
|
||||
generic.sendGrams private_key:inputKey source:accountAddress destination:accountAddress amount:int64 message:bytes = Ok;
|
||||
generic.sendGrams private_key:inputKey source:accountAddress destination:accountAddress amount:int64 timeout:int32 allow_send_to_uninited:Bool message:bytes = SendGramsResult;
|
||||
|
||||
onLiteServerQueryResult id:int64 bytes:bytes = Ok;
|
||||
onLiteServerQueryError id:int64 error:error = Ok;
|
||||
|
|
Binary file not shown.
|
@ -91,6 +91,35 @@ INC NEWC 32 STU 256 STU ENDC c4 POPCTR
|
|||
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());
|
||||
|
@ -115,37 +144,74 @@ TEST(Tonlib, TestWallet) {
|
|||
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"})
|
||||
.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() {
|
||||
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());
|
||||
// TODO: fix ater new-wallet supports new type of wallet
|
||||
//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;
|
||||
auto fift_output = fift::mem_run_fift(load_source("smartcont/new-wallet-v2.fif"), {"aba", "0"}).move_as_ok();
|
||||
|
||||
//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);
|
||||
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;
|
||||
|
||||
//CHECK(address.addr.as_slice() == td::Slice(new_wallet_addr).substr(0, 32));
|
||||
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);
|
||||
|
||||
//td::Ref<vm::Cell> res = GenericAccount::create_ext_message(address, init_state, init_message);
|
||||
CHECK(address.addr.as_slice() == td::Slice(new_wallet_addr).substr(0, 32));
|
||||
|
||||
//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());
|
||||
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) {
|
||||
|
@ -201,20 +267,21 @@ static auto sync_send = [](auto &client, auto query) {
|
|||
|
||||
TEST(Tonlib, InitClose) {
|
||||
using tonlib_api::make_object;
|
||||
auto cfg = [](auto str) { return make_object<tonlib_api::config>(str, "", false, false); };
|
||||
{
|
||||
Client client;
|
||||
sync_send(client, make_object<tonlib_api::close>()).ensure();
|
||||
sync_send(client, make_object<tonlib_api::init>(make_object<tonlib_api::options>("", ".", false))).ensure_error();
|
||||
sync_send(client, make_object<tonlib_api::init>(make_object<tonlib_api::options>(nullptr, "."))).ensure_error();
|
||||
}
|
||||
{
|
||||
Client client;
|
||||
sync_send(client, make_object<tonlib_api::init>(nullptr)).ensure_error();
|
||||
sync_send(client, make_object<tonlib_api::init>(make_object<tonlib_api::options>("fdajkfldsjkafld", ".", false)))
|
||||
sync_send(client, make_object<tonlib_api::init>(make_object<tonlib_api::options>(cfg("fdajkfldsjkafld"), ".")))
|
||||
.ensure_error();
|
||||
sync_send(client, make_object<tonlib_api::init>(make_object<tonlib_api::options>("", "fdhskfds", false)))
|
||||
sync_send(client, make_object<tonlib_api::init>(make_object<tonlib_api::options>(nullptr, "fdhskfds")))
|
||||
.ensure_error();
|
||||
sync_send(client, make_object<tonlib_api::init>(make_object<tonlib_api::options>("", ".", false))).ensure();
|
||||
sync_send(client, make_object<tonlib_api::init>(make_object<tonlib_api::options>("", ".", false))).ensure_error();
|
||||
sync_send(client, make_object<tonlib_api::init>(make_object<tonlib_api::options>(nullptr, "."))).ensure();
|
||||
sync_send(client, make_object<tonlib_api::init>(make_object<tonlib_api::options>(nullptr, "."))).ensure_error();
|
||||
|
||||
td::Slice bad_config = R"abc(
|
||||
{
|
||||
|
@ -223,11 +290,11 @@ TEST(Tonlib, InitClose) {
|
|||
}
|
||||
)abc";
|
||||
|
||||
sync_send(client, make_object<tonlib_api::options_setConfig>(bad_config.str())).ensure_error();
|
||||
sync_send(client, make_object<tonlib_api::options_setConfig>(cfg(bad_config.str()))).ensure_error();
|
||||
sync_send(client, make_object<tonlib_api::testGiver_getAccountState>()).ensure_error();
|
||||
sync_send(client, make_object<tonlib_api::close>()).ensure();
|
||||
sync_send(client, make_object<tonlib_api::close>()).ensure_error();
|
||||
sync_send(client, make_object<tonlib_api::init>(make_object<tonlib_api::options>("", ".", false))).ensure_error();
|
||||
sync_send(client, make_object<tonlib_api::init>(make_object<tonlib_api::options>(nullptr, "."))).ensure_error();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -317,12 +384,32 @@ TEST(Tonlib, Keys) {
|
|||
CHECK(decrypted_key.private_key.as_octet_string() == other_decrypted_key.private_key.as_octet_string());
|
||||
}
|
||||
|
||||
TEST(Tonlib, ParseAddres) {
|
||||
using tonlib_api::make_object;
|
||||
Client client;
|
||||
|
||||
// init
|
||||
sync_send(client, make_object<tonlib_api::init>(make_object<tonlib_api::options>(nullptr, "."))).ensure();
|
||||
|
||||
sync_send(client, make_object<tonlib_api::unpackAccountAddress>("hello")).ensure_error();
|
||||
auto addr =
|
||||
sync_send(client,
|
||||
make_object<tonlib_api::unpackAccountAddress>("Ef9Tj6fMJP-OqhAdhKXxq36DL-HYSzCc3-9O6UNzqsgPfYFX"))
|
||||
.move_as_ok();
|
||||
ASSERT_EQ(-1, addr->workchain_id_);
|
||||
ASSERT_EQ(true, addr->bounceable_);
|
||||
ASSERT_EQ(false, addr->testnet_);
|
||||
|
||||
auto addr_str = sync_send(client, make_object<tonlib_api::packAccountAddress>(std::move(addr))).move_as_ok();
|
||||
ASSERT_EQ("Ef9Tj6fMJP-OqhAdhKXxq36DL-HYSzCc3-9O6UNzqsgPfYFX", addr_str->account_address_);
|
||||
}
|
||||
|
||||
TEST(Tonlib, KeysApi) {
|
||||
using tonlib_api::make_object;
|
||||
Client client;
|
||||
|
||||
// init
|
||||
sync_send(client, make_object<tonlib_api::init>(make_object<tonlib_api::options>("", ".", false))).ensure();
|
||||
sync_send(client, make_object<tonlib_api::init>(make_object<tonlib_api::options>(nullptr, "."))).ensure();
|
||||
auto local_password = td::SecureString("local password");
|
||||
auto mnemonic_password = td::SecureString("mnemonic password");
|
||||
{
|
||||
|
|
|
@ -118,7 +118,7 @@ void transfer_grams(Client& client, std::string from, std::string to, td::int64
|
|||
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, "GIFT"))
|
||||
tonlib_api::make_object<tonlib_api::accountAddress>(to), amount, 0, true, "GIFT"))
|
||||
.ensure();
|
||||
while (balance == get_balance(client, to)) {
|
||||
client.receive(1);
|
||||
|
@ -196,7 +196,8 @@ int main(int argc, char* argv[]) {
|
|||
|
||||
Client client;
|
||||
{
|
||||
sync_send(client, make_object<tonlib_api::init>(make_object<tonlib_api::options>(global_config_str, ".", false)))
|
||||
sync_send(client, make_object<tonlib_api::init>(make_object<tonlib_api::options>(
|
||||
make_object<tonlib_api::config>(global_config_str, "", false, false), ".")))
|
||||
.ensure();
|
||||
}
|
||||
//dump_transaction_history(client, get_test_giver_address(client));
|
||||
|
@ -209,7 +210,8 @@ int main(int argc, char* argv[]) {
|
|||
return 0;
|
||||
{
|
||||
// init
|
||||
sync_send(client, make_object<tonlib_api::init>(make_object<tonlib_api::options>(global_config_str, ".", false)))
|
||||
sync_send(client, make_object<tonlib_api::init>(make_object<tonlib_api::options>(
|
||||
make_object<tonlib_api::config>(global_config_str, "", false, false), ".")))
|
||||
.ensure();
|
||||
|
||||
auto key = sync_send(client, make_object<tonlib_api::createNewKey>(
|
||||
|
@ -224,7 +226,9 @@ int main(int argc, char* argv[]) {
|
|||
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>(global_config_str)).ensure();
|
||||
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));
|
||||
{
|
||||
|
@ -307,10 +311,10 @@ int main(int argc, char* argv[]) {
|
|||
}
|
||||
|
||||
{
|
||||
sync_send(client,
|
||||
make_object<tonlib_api::generic_sendGrams>(
|
||||
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, "GIFT"))
|
||||
make_object<tonlib_api::accountAddress>(test_giver_address), 1000000000ll * 3333 / 1000, 0,
|
||||
true, "GIFT"))
|
||||
.ensure();
|
||||
}
|
||||
while (true) {
|
||||
|
|
|
@ -21,6 +21,45 @@
|
|||
#include "td/utils/JsonBuilder.h"
|
||||
|
||||
namespace tonlib {
|
||||
td::Result<ton::BlockIdExt> parse_block_id_ext(td::JsonObject &obj) {
|
||||
ton::WorkchainId zero_workchain_id;
|
||||
{
|
||||
TRY_RESULT(wc, td::get_json_object_int_field(obj, "workchain"));
|
||||
zero_workchain_id = wc;
|
||||
}
|
||||
ton::ShardId zero_shard_id; // uint64
|
||||
{
|
||||
TRY_RESULT(shard_id, td::get_json_object_long_field(obj, "shard"));
|
||||
zero_shard_id = static_cast<ton::ShardId>(shard_id);
|
||||
}
|
||||
ton::BlockSeqno zero_seqno;
|
||||
{
|
||||
TRY_RESULT(seqno, td::get_json_object_int_field(obj, "seqno"));
|
||||
zero_seqno = seqno;
|
||||
}
|
||||
|
||||
ton::RootHash zero_root_hash;
|
||||
{
|
||||
TRY_RESULT(hash_b64, td::get_json_object_string_field(obj, "root_hash"));
|
||||
TRY_RESULT(hash, td::base64_decode(hash_b64));
|
||||
if (hash.size() * 8 != ton::RootHash::size()) {
|
||||
return td::Status::Error("Invalid config (8)");
|
||||
}
|
||||
zero_root_hash = ton::RootHash(td::ConstBitPtr(td::Slice(hash).ubegin()));
|
||||
}
|
||||
ton::FileHash zero_file_hash;
|
||||
{
|
||||
TRY_RESULT(hash_b64, td::get_json_object_string_field(obj, "file_hash"));
|
||||
TRY_RESULT(hash, td::base64_decode(hash_b64));
|
||||
if (hash.size() * 8 != ton::FileHash::size()) {
|
||||
return td::Status::Error("Invalid config (9)");
|
||||
}
|
||||
zero_file_hash = ton::RootHash(td::ConstBitPtr(td::Slice(hash).ubegin()));
|
||||
}
|
||||
|
||||
return ton::BlockIdExt(zero_workchain_id, zero_shard_id, zero_seqno, std::move(zero_root_hash),
|
||||
std::move(zero_file_hash));
|
||||
}
|
||||
td::Result<Config> Config::parse(std::string str) {
|
||||
TRY_RESULT(json, td::json_decode(str));
|
||||
if (json.type() != td::JsonValue::Type::Object) {
|
||||
|
@ -74,45 +113,13 @@ td::Result<Config> Config::parse(std::string str) {
|
|||
return td::Status::Error("Invalid config (7)");
|
||||
}
|
||||
TRY_RESULT(zero_state_obj, td::get_json_object_field(validator, "zero_state", td::JsonValue::Type::Object, false));
|
||||
auto &zero_state = zero_state_obj.get_object();
|
||||
|
||||
ton::WorkchainId zero_workchain_id;
|
||||
{
|
||||
TRY_RESULT(wc, td::get_json_object_int_field(zero_state, "workchain"));
|
||||
zero_workchain_id = wc;
|
||||
TRY_RESULT(zero_state_id, parse_block_id_ext(zero_state_obj.get_object()));
|
||||
res.zero_state_id = zero_state_id;
|
||||
auto r_init_block_obj = td::get_json_object_field(validator, "init_block", td::JsonValue::Type::Object, false);
|
||||
if (r_init_block_obj.is_ok()) {
|
||||
TRY_RESULT(init_block_id, parse_block_id_ext(r_init_block_obj.move_as_ok().get_object()));
|
||||
res.init_block_id = init_block_id;
|
||||
}
|
||||
ton::ShardId zero_shard_id; // uint64
|
||||
{
|
||||
TRY_RESULT(shard_id, td::get_json_object_long_field(zero_state, "shard"));
|
||||
zero_shard_id = static_cast<ton::ShardId>(shard_id);
|
||||
}
|
||||
ton::BlockSeqno zero_seqno;
|
||||
{
|
||||
TRY_RESULT(seqno, td::get_json_object_int_field(zero_state, "seqno"));
|
||||
zero_seqno = seqno;
|
||||
}
|
||||
|
||||
ton::RootHash zero_root_hash;
|
||||
{
|
||||
TRY_RESULT(hash_b64, td::get_json_object_string_field(zero_state, "root_hash"));
|
||||
TRY_RESULT(hash, td::base64_decode(hash_b64));
|
||||
if (hash.size() * 8 != ton::RootHash::size()) {
|
||||
return td::Status::Error("Invalid config (8)");
|
||||
}
|
||||
zero_root_hash = ton::RootHash(td::ConstBitPtr(td::Slice(hash).ubegin()));
|
||||
}
|
||||
ton::FileHash zero_file_hash;
|
||||
{
|
||||
TRY_RESULT(hash_b64, td::get_json_object_string_field(zero_state, "file_hash"));
|
||||
TRY_RESULT(hash, td::base64_decode(hash_b64));
|
||||
if (hash.size() * 8 != ton::FileHash::size()) {
|
||||
return td::Status::Error("Invalid config (9)");
|
||||
}
|
||||
zero_file_hash = ton::RootHash(td::ConstBitPtr(td::Slice(hash).ubegin()));
|
||||
}
|
||||
|
||||
res.zero_state_id = ton::BlockIdExt(zero_workchain_id, zero_shard_id, zero_seqno, std::move(zero_root_hash),
|
||||
std::move(zero_file_hash));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
|
|
@ -28,6 +28,7 @@ struct Config {
|
|||
td::IPAddress address;
|
||||
};
|
||||
ton::BlockIdExt zero_state_id;
|
||||
ton::BlockIdExt init_block_id;
|
||||
std::vector<LiteClient> lite_clients;
|
||||
static td::Result<Config> parse(std::string str);
|
||||
};
|
||||
|
|
|
@ -29,27 +29,70 @@ td::StringBuilder& operator<<(td::StringBuilder& sb, const LastBlockState& state
|
|||
<< td::tag("last_key_block", state.last_key_block_id.to_str()) << td::tag("utime", state.utime);
|
||||
}
|
||||
|
||||
LastBlock::LastBlock(ExtClientRef client, LastBlockState state, td::unique_ptr<Callback> callback)
|
||||
: state_(std::move(state)), callback_(std::move(callback)) {
|
||||
LastBlock::LastBlock(ExtClientRef client, LastBlockState state, Config config, td::unique_ptr<Callback> callback)
|
||||
: state_(std::move(state)), config_(std::move(config)), callback_(std::move(callback)) {
|
||||
client_.set_client(client);
|
||||
if (!config_.init_block_id.is_valid()) {
|
||||
check_init_block_state_ = QueryState::Done;
|
||||
}
|
||||
}
|
||||
|
||||
void LastBlock::get_last_block(td::Promise<LastBlockState> promise) {
|
||||
if (has_fatal_error()) {
|
||||
promise.set_error(fatal_error_.clone());
|
||||
return;
|
||||
}
|
||||
if (promises_.empty() && get_last_block_state_ == QueryState::Done) {
|
||||
get_last_block_state_ = QueryState::Empty;
|
||||
}
|
||||
promises_.push_back(std::move(promise));
|
||||
sync_loop();
|
||||
}
|
||||
|
||||
void LastBlock::sync_loop() {
|
||||
if (promises_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
update_zero_state(state_.zero_state_id);
|
||||
update_zero_state(ton::ZeroStateIdExt(config_.zero_state_id.id.workchain, config_.zero_state_id.root_hash,
|
||||
config_.zero_state_id.file_hash));
|
||||
|
||||
if (get_mc_info_state_ == QueryState::Empty) {
|
||||
get_mc_info_state_ = QueryState::Active;
|
||||
client_.send_query(ton::lite_api::liteServer_getMasterchainInfo(),
|
||||
[this](auto r_info) { this->on_masterchain_info(std::move(r_info)); });
|
||||
}
|
||||
|
||||
if (get_last_block_state_ == QueryState::Empty) {
|
||||
get_last_block_state_ = QueryState::Active;
|
||||
total_sync_ = td::Timer();
|
||||
validate_ = td::Timer(true);
|
||||
queries_ = 0;
|
||||
LOG(INFO) << "Begin last block synchronization " << state_;
|
||||
do_get_last_block();
|
||||
}
|
||||
promises_.push_back(std::move(promise));
|
||||
|
||||
if (check_init_block_state_ == QueryState::Empty) {
|
||||
if (state_.last_block_id.id.seqno >= config_.init_block_id.id.seqno) {
|
||||
check_init_block_state_ = QueryState::Active;
|
||||
// validate
|
||||
//total_sync_ = td::Timer();
|
||||
//validate_ = td::Timer(true);
|
||||
//queries_ = 0;
|
||||
LOG(INFO) << "Begin last block synchronization (check init_block)" << state_;
|
||||
do_check_init_block(state_.last_key_block_id);
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
if (get_mc_info_state_ == QueryState::Done && get_last_block_state_ == QueryState::Done &&
|
||||
check_init_block_state_ == QueryState::Done) {
|
||||
on_sync_ok();
|
||||
}
|
||||
}
|
||||
|
||||
void LastBlock::do_get_last_block() {
|
||||
//client_.send_query(ton::lite_api::liteServer_getMasterchainInfo(),
|
||||
//[this](auto r_info) { this->on_masterchain_info(std::move(r_info)); });
|
||||
//return;
|
||||
|
||||
//liteServer.getBlockProof mode:# known_block:tonNode.blockIdExt target_block:mode.0?tonNode.blockIdExt = liteServer.PartialBlockProof;
|
||||
queries_++;
|
||||
client_.send_query(
|
||||
|
@ -59,14 +102,19 @@ void LastBlock::do_get_last_block() {
|
|||
});
|
||||
}
|
||||
|
||||
td::Result<bool> LastBlock::process_block_proof(
|
||||
void LastBlock::do_check_init_block(ton::BlockIdExt from) {
|
||||
//liteServer.getBlockProof mode:# known_block:tonNode.blockIdExt target_block:mode.0?tonNode.blockIdExt = liteServer.PartialBlockProof;
|
||||
//queries_++;
|
||||
client_.send_query(ton::lite_api::liteServer_getBlockProof(1, create_tl_lite_block_id(from),
|
||||
create_tl_lite_block_id(config_.init_block_id)),
|
||||
[this, from = state_.last_key_block_id](auto r_block_proof) {
|
||||
this->on_init_block_proof(from, std::move(r_block_proof));
|
||||
});
|
||||
}
|
||||
|
||||
td::Result<std::unique_ptr<block::BlockProofChain>> LastBlock::process_block_proof(
|
||||
ton::BlockIdExt from,
|
||||
td::Result<ton::ton_api::object_ptr<ton::lite_api::liteServer_partialBlockProof>> r_block_proof) {
|
||||
validate_.resume();
|
||||
SCOPE_EXIT {
|
||||
validate_.pause();
|
||||
};
|
||||
|
||||
TRY_RESULT(block_proof, std::move(r_block_proof));
|
||||
LOG(DEBUG) << "Got proof FROM\n" << to_string(block_proof->from_) << "TO\n" << to_string(block_proof->to_);
|
||||
TRY_RESULT(chain, liteclient::deserialize_proof_chain(std::move(block_proof)));
|
||||
|
@ -86,51 +134,82 @@ td::Result<bool> LastBlock::process_block_proof(
|
|||
if (is_changed) {
|
||||
callback_->on_state_changed(state_);
|
||||
}
|
||||
return chain->complete;
|
||||
return std::move(chain);
|
||||
}
|
||||
|
||||
void LastBlock::on_block_proof(
|
||||
ton::BlockIdExt from,
|
||||
td::Result<ton::ton_api::object_ptr<ton::lite_api::liteServer_partialBlockProof>> r_block_proof) {
|
||||
auto r_is_ready = process_block_proof(from, std::move(r_block_proof));
|
||||
validate_.resume();
|
||||
auto r_chain = process_block_proof(from, std::move(r_block_proof));
|
||||
validate_.pause();
|
||||
bool is_ready;
|
||||
if (r_is_ready.is_error()) {
|
||||
LOG(WARNING) << "Error during last block synchronization " << r_is_ready.error();
|
||||
if (r_chain.is_error()) {
|
||||
LOG(WARNING) << "Error during last block synchronization " << r_chain.error();
|
||||
if (config_.init_block_id.is_valid()) {
|
||||
if (state_.last_key_block_id.id.seqno < config_.init_block_id.id.seqno) {
|
||||
on_sync_error(td::Status::Error(PSLICE() << "Sync failed and we can't validate config.init_block: "
|
||||
<< r_chain.move_as_error()));
|
||||
}
|
||||
}
|
||||
is_ready = true;
|
||||
} else {
|
||||
is_ready = r_is_ready.move_as_ok();
|
||||
is_ready = r_chain.ok()->complete;
|
||||
}
|
||||
if (is_ready) {
|
||||
LOG(INFO) << "End last block synchronization " << state_ << "\n"
|
||||
<< " net queries: " << queries_ << "\n"
|
||||
<< " total: " << total_sync_ << " validation: " << validate_;
|
||||
for (auto& promise : promises_) {
|
||||
auto state = state_;
|
||||
promise.set_value(std::move(state));
|
||||
}
|
||||
promises_.clear();
|
||||
get_last_block_state_ = QueryState::Done;
|
||||
sync_loop();
|
||||
} else {
|
||||
do_get_last_block();
|
||||
}
|
||||
}
|
||||
|
||||
void LastBlock::on_init_block_proof(
|
||||
ton::BlockIdExt from,
|
||||
td::Result<ton::ton_api::object_ptr<ton::lite_api::liteServer_partialBlockProof>> r_block_proof) {
|
||||
validate_.resume();
|
||||
auto r_chain = process_block_proof(from, std::move(r_block_proof));
|
||||
validate_.pause();
|
||||
if (r_chain.is_error()) {
|
||||
check_init_block_state_ = QueryState::Empty;
|
||||
on_sync_error(
|
||||
td::Status::Error(PSLICE() << "Error during last block synchronization (check init_block)" << r_chain.error()));
|
||||
return;
|
||||
}
|
||||
auto chain = r_chain.move_as_ok();
|
||||
if (chain->complete) {
|
||||
LOG(INFO) << "End last block synchronization " << state_ << "\n"
|
||||
<< " net queries: " << queries_ << "\n"
|
||||
<< " total: " << total_sync_ << " validation: " << validate_;
|
||||
get_last_block_state_ = QueryState::Done;
|
||||
sync_loop();
|
||||
} else {
|
||||
do_check_init_block(chain->to);
|
||||
}
|
||||
}
|
||||
|
||||
void LastBlock::on_masterchain_info(
|
||||
td::Result<ton::ton_api::object_ptr<ton::lite_api::liteServer_masterchainInfo>> r_info) {
|
||||
if (r_info.is_ok()) {
|
||||
auto info = r_info.move_as_ok();
|
||||
update_zero_state(create_zero_state_id(info->init_));
|
||||
update_mc_last_block(create_block_id(info->last_));
|
||||
get_mc_info_state_ = QueryState::Done;
|
||||
} else {
|
||||
get_mc_info_state_ = QueryState::Empty;
|
||||
LOG(WARNING) << "Failed liteServer_getMasterchainInfo " << r_info.error();
|
||||
on_sync_error(r_info.move_as_error());
|
||||
}
|
||||
for (auto& promise : promises_) {
|
||||
auto state = state_;
|
||||
promise.set_value(std::move(state));
|
||||
}
|
||||
promises_.clear();
|
||||
sync_loop();
|
||||
}
|
||||
|
||||
void LastBlock::update_zero_state(ton::ZeroStateIdExt zero_state_id) {
|
||||
if (has_fatal_error()) {
|
||||
return;
|
||||
}
|
||||
if (!zero_state_id.is_valid()) {
|
||||
LOG(ERROR) << "Ignore invalid zero state update";
|
||||
return;
|
||||
|
@ -142,17 +221,18 @@ void LastBlock::update_zero_state(ton::ZeroStateIdExt zero_state_id) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (state_.zero_state_id == state_.zero_state_id) {
|
||||
if (state_.zero_state_id == zero_state_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
LOG(FATAL) << "Masterchain zerostate mismatch: expected: " << state_.zero_state_id.to_str() << ", found "
|
||||
<< zero_state_id.to_str();
|
||||
// TODO: all other updates will be inconsitent.
|
||||
// One will have to restart ton client
|
||||
on_fatal_error(td::Status::Error(PSLICE() << "Masterchain zerostate mismatch: expected: "
|
||||
<< state_.zero_state_id.to_str() << ", found " << zero_state_id.to_str()));
|
||||
}
|
||||
|
||||
bool LastBlock::update_mc_last_block(ton::BlockIdExt mc_block_id) {
|
||||
if (has_fatal_error()) {
|
||||
return false;
|
||||
}
|
||||
if (!mc_block_id.is_valid()) {
|
||||
LOG(ERROR) << "Ignore invalid masterchain block";
|
||||
return false;
|
||||
|
@ -166,6 +246,9 @@ bool LastBlock::update_mc_last_block(ton::BlockIdExt mc_block_id) {
|
|||
}
|
||||
|
||||
bool LastBlock::update_mc_last_key_block(ton::BlockIdExt mc_key_block_id) {
|
||||
if (has_fatal_error()) {
|
||||
return false;
|
||||
}
|
||||
if (!mc_key_block_id.is_valid()) {
|
||||
LOG(ERROR) << "Ignore invalid masterchain block";
|
||||
return false;
|
||||
|
@ -183,4 +266,26 @@ void LastBlock::update_utime(td::int64 utime) {
|
|||
state_.utime = utime;
|
||||
}
|
||||
}
|
||||
|
||||
void LastBlock::on_sync_ok() {
|
||||
for (auto& promise : promises_) {
|
||||
auto state = state_;
|
||||
promise.set_value(std::move(state));
|
||||
}
|
||||
promises_.clear();
|
||||
}
|
||||
void LastBlock::on_sync_error(td::Status status) {
|
||||
for (auto& promise : promises_) {
|
||||
promise.set_error(status.clone());
|
||||
}
|
||||
promises_.clear();
|
||||
}
|
||||
void LastBlock::on_fatal_error(td::Status status) {
|
||||
fatal_error_ = std::move(status);
|
||||
on_sync_error(fatal_error_.clone());
|
||||
}
|
||||
|
||||
bool LastBlock::has_fatal_error() const {
|
||||
return fatal_error_.is_error();
|
||||
}
|
||||
} // namespace tonlib
|
||||
|
|
|
@ -19,8 +19,12 @@
|
|||
#pragma once
|
||||
#include "td/actor/actor.h"
|
||||
|
||||
#include "tonlib/Config.h"
|
||||
#include "tonlib/ExtClient.h"
|
||||
|
||||
namespace block {
|
||||
struct BlockProofChain;
|
||||
}
|
||||
namespace tonlib {
|
||||
td::StringBuilder &operator<<(td::StringBuilder &sb, const LastBlockState &state);
|
||||
template <unsigned int N, class StorerT>
|
||||
|
@ -116,14 +120,22 @@ class LastBlock : public td::actor::Actor {
|
|||
virtual void on_state_changed(LastBlockState state) = 0;
|
||||
};
|
||||
|
||||
explicit LastBlock(ExtClientRef client, LastBlockState state, td::unique_ptr<Callback> callback);
|
||||
explicit LastBlock(ExtClientRef client, LastBlockState state, Config config, td::unique_ptr<Callback> callback);
|
||||
void get_last_block(td::Promise<LastBlockState> promise);
|
||||
|
||||
private:
|
||||
ExtClient client_;
|
||||
LastBlockState state_;
|
||||
Config config_;
|
||||
td::unique_ptr<Callback> callback_;
|
||||
|
||||
td::Status fatal_error_;
|
||||
|
||||
enum class QueryState { Empty, Active, Done };
|
||||
QueryState get_mc_info_state_{QueryState::Empty};
|
||||
QueryState get_last_block_state_{QueryState::Empty};
|
||||
QueryState check_init_block_state_{QueryState::Empty};
|
||||
|
||||
// stats
|
||||
td::Timer total_sync_;
|
||||
td::Timer validate_;
|
||||
|
@ -131,11 +143,15 @@ class LastBlock : public td::actor::Actor {
|
|||
|
||||
std::vector<td::Promise<LastBlockState>> promises_;
|
||||
|
||||
void do_get_last_block();
|
||||
void do_check_init_block(ton::BlockIdExt from);
|
||||
void on_init_block_proof(
|
||||
ton::BlockIdExt from,
|
||||
td::Result<ton::ton_api::object_ptr<ton::lite_api::liteServer_partialBlockProof>> r_block_proof);
|
||||
void on_masterchain_info(td::Result<ton::ton_api::object_ptr<ton::lite_api::liteServer_masterchainInfo>> r_info);
|
||||
void do_get_last_block();
|
||||
void on_block_proof(ton::BlockIdExt from,
|
||||
td::Result<ton::ton_api::object_ptr<ton::lite_api::liteServer_partialBlockProof>> r_block_proof);
|
||||
td::Result<bool> process_block_proof(
|
||||
td::Result<std::unique_ptr<block::BlockProofChain>> process_block_proof(
|
||||
ton::BlockIdExt from,
|
||||
td::Result<ton::ton_api::object_ptr<ton::lite_api::liteServer_partialBlockProof>> r_block_proof);
|
||||
|
||||
|
@ -144,5 +160,12 @@ class LastBlock : public td::actor::Actor {
|
|||
bool update_mc_last_block(ton::BlockIdExt mc_block_id);
|
||||
bool update_mc_last_key_block(ton::BlockIdExt mc_key_block_id);
|
||||
void update_utime(td::int64 utime);
|
||||
|
||||
void on_sync_ok();
|
||||
void on_sync_error(td::Status status);
|
||||
void on_fatal_error(td::Status status);
|
||||
bool has_fatal_error() const;
|
||||
|
||||
void sync_loop();
|
||||
};
|
||||
} // namespace tonlib
|
||||
|
|
|
@ -44,13 +44,16 @@ td::Ref<vm::Cell> TestWallet::make_a_gift_message(const td::Ed25519::PrivateKey&
|
|||
td::BigInt256 dest_addr;
|
||||
dest_addr.import_bits(dest_address.addr.as_bitslice());
|
||||
vm::CellBuilder cb;
|
||||
cb.append_cellslice(binary_bitstring_to_cellslice("b{010000100}").move_as_ok())
|
||||
cb.append_cellslice(binary_bitstring_to_cellslice("b{01}").move_as_ok())
|
||||
.store_long(dest_address.bounceable, 1)
|
||||
.append_cellslice(binary_bitstring_to_cellslice("b{000100}").move_as_ok())
|
||||
.store_long(dest_address.workchain, 8)
|
||||
.store_int256(dest_addr, 256);
|
||||
block::tlb::t_Grams.store_integer_value(cb, td::BigInt256(gramms));
|
||||
auto message_inner = cb.store_zeroes(9 + 64 + 32 + 1 + 1).store_bytes("\0\0\0\0", 4).store_bytes(message).finalize();
|
||||
auto message_outer = vm::CellBuilder().store_long(seqno, 32).store_long(1, 8).store_ref(message_inner).finalize();
|
||||
std::string seq_no(4, 0);
|
||||
td::int8 send_mode = 3;
|
||||
auto message_outer =
|
||||
vm::CellBuilder().store_long(seqno, 32).store_long(send_mode, 8).store_ref(message_inner).finalize();
|
||||
auto signature = private_key.sign(message_outer->get_hash().as_slice()).move_as_ok();
|
||||
return vm::CellBuilder().store_bytes(signature).append_cellslice(vm::load_cell_slice(message_outer)).finalize();
|
||||
}
|
||||
|
|
|
@ -74,7 +74,6 @@ struct RawAccountState {
|
|||
td::Ref<vm::CellSlice> code;
|
||||
td::Ref<vm::CellSlice> data;
|
||||
block::AccountState::Info info;
|
||||
td::int64 sync_utime = 0;
|
||||
};
|
||||
|
||||
td::Result<td::int64> to_balance_or_throw(td::Ref<vm::CellSlice> balance_ref) {
|
||||
|
@ -191,7 +190,7 @@ class GetRawAccountState : public td::actor::Actor {
|
|||
auto serialized_state = account_state.state.clone();
|
||||
RawAccountState res;
|
||||
res.info = std::move(info);
|
||||
res.sync_utime = last_block_.utime;
|
||||
LOG_IF(ERROR, res.info.gen_utime > last_block_.utime) << res.info.gen_utime << " " << last_block_.utime;
|
||||
auto cell = res.info.root;
|
||||
if (cell.is_null()) {
|
||||
return res;
|
||||
|
@ -307,44 +306,54 @@ void TonlibClient::init_ext_client() {
|
|||
private:
|
||||
td::actor::ActorShared<> parent_;
|
||||
};
|
||||
ext_client_outbound_ = {};
|
||||
ref_cnt_++;
|
||||
raw_client_ = ExtClientLazy::create(lite_client.adnl_id, lite_client.address,
|
||||
td::make_unique<Callback>(td::actor::actor_shared()));
|
||||
}
|
||||
}
|
||||
|
||||
void TonlibClient::update_last_block_state(LastBlockState state) {
|
||||
last_block_storage_.save_state("none", state);
|
||||
void TonlibClient::update_last_block_state(LastBlockState state, td::uint32 config_generation) {
|
||||
if (config_generation == config_generation_) {
|
||||
last_block_storage_.save_state(blockchain_name_, state);
|
||||
}
|
||||
}
|
||||
|
||||
void TonlibClient::init_last_block() {
|
||||
ref_cnt_++;
|
||||
class Callback : public LastBlock::Callback {
|
||||
public:
|
||||
Callback(td::actor::ActorShared<TonlibClient> client) : client_(std::move(client)) {
|
||||
Callback(td::actor::ActorShared<TonlibClient> client, td::uint32 config_generation)
|
||||
: client_(std::move(client)), config_generation_(config_generation) {
|
||||
}
|
||||
void on_state_changed(LastBlockState state) override {
|
||||
send_closure(client_, &TonlibClient::update_last_block_state, std::move(state));
|
||||
send_closure(client_, &TonlibClient::update_last_block_state, std::move(state), config_generation_);
|
||||
}
|
||||
|
||||
private:
|
||||
td::actor::ActorShared<TonlibClient> client_;
|
||||
td::uint32 config_generation_;
|
||||
};
|
||||
LastBlockState state;
|
||||
|
||||
auto r_state = last_block_storage_.get_state("none");
|
||||
if (r_state.is_error()) {
|
||||
LOG(WARNING) << "Unknown LastBlockState: " << r_state.error();
|
||||
td::Result<LastBlockState> r_state;
|
||||
if (!ignore_cache_) {
|
||||
r_state = last_block_storage_.get_state(blockchain_name_);
|
||||
}
|
||||
if (ignore_cache_ || r_state.is_error()) {
|
||||
LOG_IF(WARNING, !ignore_cache_) << "Unknown LastBlockState: " << r_state.error();
|
||||
state.zero_state_id = ton::ZeroStateIdExt(config_.zero_state_id.id.workchain, config_.zero_state_id.root_hash,
|
||||
config_.zero_state_id.file_hash),
|
||||
state.last_block_id = config_.zero_state_id;
|
||||
state.last_key_block_id = config_.zero_state_id;
|
||||
last_block_storage_.save_state(blockchain_name_, state);
|
||||
} else {
|
||||
state = r_state.move_as_ok();
|
||||
}
|
||||
|
||||
raw_last_block_ = td::actor::create_actor<LastBlock>("LastBlock", get_client_ref(), std::move(state),
|
||||
td::make_unique<Callback>(td::actor::actor_shared(this)));
|
||||
raw_last_block_ =
|
||||
td::actor::create_actor<LastBlock>("LastBlock", get_client_ref(), std::move(state), config_,
|
||||
td::make_unique<Callback>(td::actor::actor_shared(this), config_generation_));
|
||||
client_.set_client(get_client_ref());
|
||||
}
|
||||
|
||||
|
@ -416,6 +425,8 @@ bool TonlibClient::is_static_request(td::int32 id) {
|
|||
case tonlib_api::testWallet_getAccountAddress::ID:
|
||||
case tonlib_api::wallet_getAccountAddress::ID:
|
||||
case tonlib_api::testGiver_getAccountAddress::ID:
|
||||
case tonlib_api::packAccountAddress::ID:
|
||||
case tonlib_api::unpackAccountAddress::ID:
|
||||
case tonlib_api::getBip39Hints::ID:
|
||||
case tonlib_api::setLogStream::ID:
|
||||
case tonlib_api::getLogStream::ID:
|
||||
|
@ -472,7 +483,7 @@ tonlib_api::object_ptr<tonlib_api::Object> TonlibClient::do_static_request(
|
|||
if (r_account_address.is_error()) {
|
||||
return status_to_tonlib_api(r_account_address.error());
|
||||
}
|
||||
return tonlib_api::make_object<tonlib_api::accountAddress>(r_account_address.ok().rserialize());
|
||||
return tonlib_api::make_object<tonlib_api::accountAddress>(r_account_address.ok().rserialize(true));
|
||||
}
|
||||
tonlib_api::object_ptr<tonlib_api::Object> TonlibClient::do_static_request(
|
||||
const tonlib_api::testWallet_getAccountAddress& request) {
|
||||
|
@ -480,7 +491,7 @@ tonlib_api::object_ptr<tonlib_api::Object> TonlibClient::do_static_request(
|
|||
if (r_account_address.is_error()) {
|
||||
return status_to_tonlib_api(r_account_address.error());
|
||||
}
|
||||
return tonlib_api::make_object<tonlib_api::accountAddress>(r_account_address.ok().rserialize());
|
||||
return tonlib_api::make_object<tonlib_api::accountAddress>(r_account_address.ok().rserialize(true));
|
||||
}
|
||||
tonlib_api::object_ptr<tonlib_api::Object> TonlibClient::do_static_request(
|
||||
const tonlib_api::wallet_getAccountAddress& request) {
|
||||
|
@ -488,11 +499,39 @@ tonlib_api::object_ptr<tonlib_api::Object> TonlibClient::do_static_request(
|
|||
if (r_account_address.is_error()) {
|
||||
return status_to_tonlib_api(r_account_address.error());
|
||||
}
|
||||
return tonlib_api::make_object<tonlib_api::accountAddress>(r_account_address.ok().rserialize());
|
||||
return tonlib_api::make_object<tonlib_api::accountAddress>(r_account_address.ok().rserialize(true));
|
||||
}
|
||||
tonlib_api::object_ptr<tonlib_api::Object> TonlibClient::do_static_request(
|
||||
const tonlib_api::testGiver_getAccountAddress& request) {
|
||||
return tonlib_api::make_object<tonlib_api::accountAddress>(TestGiver::address().rserialize());
|
||||
return tonlib_api::make_object<tonlib_api::accountAddress>(TestGiver::address().rserialize(true));
|
||||
}
|
||||
|
||||
tonlib_api::object_ptr<tonlib_api::Object> TonlibClient::do_static_request(
|
||||
const tonlib_api::unpackAccountAddress& request) {
|
||||
auto r_account_address = block::StdAddress::parse(request.account_address_);
|
||||
if (r_account_address.is_error()) {
|
||||
return status_to_tonlib_api(r_account_address.move_as_error());
|
||||
}
|
||||
auto account_address = r_account_address.move_as_ok();
|
||||
return tonlib_api::make_object<tonlib_api::unpackedAccountAddress>(
|
||||
account_address.workchain, account_address.bounceable, account_address.testnet,
|
||||
account_address.addr.as_slice().str());
|
||||
}
|
||||
|
||||
tonlib_api::object_ptr<tonlib_api::Object> TonlibClient::do_static_request(
|
||||
const tonlib_api::packAccountAddress& request) {
|
||||
if (!request.account_address_) {
|
||||
return status_to_tonlib_api(td::Status::Error(400, "Field account_address must not be empty"));
|
||||
}
|
||||
if (request.account_address_->addr_.size() != 32) {
|
||||
return status_to_tonlib_api(td::Status::Error(400, "Field account_address.addr must not be exactly 32 bytes"));
|
||||
}
|
||||
block::StdAddress addr;
|
||||
addr.workchain = request.account_address_->workchain_id_;
|
||||
addr.bounceable = request.account_address_->bounceable_;
|
||||
addr.testnet = request.account_address_->testnet_;
|
||||
addr.addr.as_slice().copy_from(request.account_address_->addr_);
|
||||
return tonlib_api::make_object<tonlib_api::accountAddress>(addr.rserialize(true));
|
||||
}
|
||||
|
||||
tonlib_api::object_ptr<tonlib_api::Object> TonlibClient::do_static_request(tonlib_api::getBip39Hints& request) {
|
||||
|
@ -510,8 +549,7 @@ td::Status TonlibClient::do_request(const tonlib_api::init& request,
|
|||
}
|
||||
TRY_STATUS(key_storage_.set_directory(request.options_->keystore_directory_));
|
||||
TRY_STATUS(last_block_storage_.set_directory(request.options_->keystore_directory_));
|
||||
use_callbacks_for_network_ = request.options_->use_callbacks_for_network_;
|
||||
if (!request.options_->config_.empty()) {
|
||||
if (request.options_->config_) {
|
||||
TRY_STATUS(set_config(std::move(request.options_->config_)));
|
||||
}
|
||||
state_ = State::Running;
|
||||
|
@ -519,15 +557,26 @@ td::Status TonlibClient::do_request(const tonlib_api::init& request,
|
|||
return td::Status::OK();
|
||||
}
|
||||
|
||||
td::Status TonlibClient::set_config(std::string config) {
|
||||
if (config.empty()) {
|
||||
return td::Status::Error("config is empty");
|
||||
td::Status TonlibClient::set_config(object_ptr<tonlib_api::config> config) {
|
||||
if (!config) {
|
||||
return td::Status::Error(400, "config is empty");
|
||||
}
|
||||
TRY_RESULT(new_config, Config::parse(std::move(config)));
|
||||
if (new_config.lite_clients.empty()) {
|
||||
if (config->config_.empty()) {
|
||||
return td::Status::Error(400, "config is empty");
|
||||
}
|
||||
TRY_RESULT(new_config, Config::parse(std::move(config->config_)));
|
||||
if (new_config.lite_clients.empty() && !config->use_callbacks_for_network_) {
|
||||
return td::Status::Error("No lite clients in config");
|
||||
}
|
||||
config_ = std::move(new_config);
|
||||
config_generation_++;
|
||||
if (config->blockchain_name_.empty()) {
|
||||
blockchain_name_ = td::sha256(config_.zero_state_id.to_str()).substr(0, 16);
|
||||
} else {
|
||||
blockchain_name_ = config->blockchain_name_;
|
||||
}
|
||||
use_callbacks_for_network_ = config->use_callbacks_for_network_;
|
||||
ignore_cache_ = config->ignore_cache_;
|
||||
init_ext_client();
|
||||
init_last_block();
|
||||
return td::Status::OK();
|
||||
|
@ -541,9 +590,9 @@ td::Status TonlibClient::do_request(const tonlib_api::close& request,
|
|||
return td::Status::OK();
|
||||
}
|
||||
|
||||
td::Status TonlibClient::do_request(const tonlib_api::options_setConfig& request,
|
||||
td::Status TonlibClient::do_request(tonlib_api::options_setConfig& request,
|
||||
td::Promise<object_ptr<tonlib_api::ok>>&& promise) {
|
||||
TRY_STATUS(set_config(request.config_));
|
||||
TRY_STATUS(set_config(std::move(request.config_)));
|
||||
promise.set_value(tonlib_api::make_object<tonlib_api::ok>());
|
||||
return td::Status::OK();
|
||||
}
|
||||
|
@ -572,8 +621,8 @@ td::Result<tonlib_api::object_ptr<tonlib_api::raw_accountState>> to_raw_accountS
|
|||
.as_slice()
|
||||
.str();
|
||||
}
|
||||
return tonlib_api::make_object<tonlib_api::raw_accountState>(raw_state.balance, std::move(code), std::move(data),
|
||||
to_transaction_id(raw_state.info), raw_state.sync_utime);
|
||||
return tonlib_api::make_object<tonlib_api::raw_accountState>(
|
||||
raw_state.balance, std::move(code), std::move(data), to_transaction_id(raw_state.info), raw_state.info.gen_utime);
|
||||
}
|
||||
|
||||
td::Result<std::string> to_std_address_or_throw(td::Ref<vm::CellSlice> cs) {
|
||||
|
@ -588,7 +637,7 @@ td::Result<std::string> to_std_address_or_throw(td::Ref<vm::CellSlice> cs) {
|
|||
if (!tlb::csr_unpack(cs, addr)) {
|
||||
return td::Status::Error("Failed to unpack MsgAddressInt");
|
||||
}
|
||||
return block::StdAddress(addr.workchain_id, addr.address).rserialize();
|
||||
return block::StdAddress(addr.workchain_id, addr.address).rserialize(true);
|
||||
}
|
||||
|
||||
td::Result<std::string> to_std_address(td::Ref<vm::CellSlice> cs) {
|
||||
|
@ -740,7 +789,7 @@ td::Result<tonlib_api::object_ptr<tonlib_api::testWallet_accountState>> to_testW
|
|||
return td::Status::Error("Failed to parse seq_no");
|
||||
}
|
||||
return tonlib_api::make_object<tonlib_api::testWallet_accountState>(
|
||||
raw_state.balance, static_cast<td::uint32>(seqno), to_transaction_id(raw_state.info), raw_state.sync_utime);
|
||||
raw_state.balance, static_cast<td::uint32>(seqno), to_transaction_id(raw_state.info), raw_state.info.gen_utime);
|
||||
}
|
||||
|
||||
td::Result<tonlib_api::object_ptr<tonlib_api::wallet_accountState>> to_wallet_accountState(
|
||||
|
@ -755,7 +804,7 @@ td::Result<tonlib_api::object_ptr<tonlib_api::wallet_accountState>> to_wallet_ac
|
|||
return td::Status::Error("Failed to parse seq_no");
|
||||
}
|
||||
return tonlib_api::make_object<tonlib_api::wallet_accountState>(
|
||||
raw_state.balance, static_cast<td::uint32>(seqno), to_transaction_id(raw_state.info), raw_state.sync_utime);
|
||||
raw_state.balance, static_cast<td::uint32>(seqno), to_transaction_id(raw_state.info), raw_state.info.gen_utime);
|
||||
}
|
||||
|
||||
td::Result<tonlib_api::object_ptr<tonlib_api::testGiver_accountState>> to_testGiver_accountState(
|
||||
|
@ -770,7 +819,7 @@ td::Result<tonlib_api::object_ptr<tonlib_api::testGiver_accountState>> to_testGi
|
|||
return td::Status::Error("Failed to parse seq_no");
|
||||
}
|
||||
return tonlib_api::make_object<tonlib_api::testGiver_accountState>(
|
||||
raw_state.balance, static_cast<td::uint32>(seqno), to_transaction_id(raw_state.info), raw_state.sync_utime);
|
||||
raw_state.balance, static_cast<td::uint32>(seqno), to_transaction_id(raw_state.info), raw_state.info.gen_utime);
|
||||
}
|
||||
|
||||
td::Result<tonlib_api::object_ptr<tonlib_api::generic_AccountState>> to_generic_accountState(
|
||||
|
@ -778,7 +827,7 @@ td::Result<tonlib_api::object_ptr<tonlib_api::generic_AccountState>> to_generic_
|
|||
if (raw_state.code.is_null()) {
|
||||
return tonlib_api::make_object<tonlib_api::generic_accountStateUninited>(
|
||||
tonlib_api::make_object<tonlib_api::uninited_accountState>(raw_state.balance, to_transaction_id(raw_state.info),
|
||||
raw_state.sync_utime));
|
||||
raw_state.info.gen_utime));
|
||||
}
|
||||
|
||||
auto code_hash = raw_state.code->prefetch_ref()->get_hash();
|
||||
|
@ -892,21 +941,21 @@ td::Status TonlibClient::do_request(const tonlib_api::testWallet_init& request,
|
|||
TRY_RESULT(private_key, key_storage_.load_private_key(std::move(input_key)));
|
||||
auto init_message = TestWallet::get_init_message(td::Ed25519::PrivateKey(std::move(private_key.private_key)));
|
||||
return do_request(
|
||||
tonlib_api::raw_sendMessage(tonlib_api::make_object<tonlib_api::accountAddress>(address.rserialize()),
|
||||
tonlib_api::raw_sendMessage(tonlib_api::make_object<tonlib_api::accountAddress>(address.rserialize(true)),
|
||||
vm::std_boc_serialize(init_state).move_as_ok().as_slice().str(),
|
||||
vm::std_boc_serialize(init_message).move_as_ok().as_slice().str()),
|
||||
std::move(promise));
|
||||
}
|
||||
|
||||
td::Status TonlibClient::do_request(const tonlib_api::testWallet_sendGrams& request,
|
||||
td::Promise<object_ptr<tonlib_api::ok>>&& promise) {
|
||||
td::Promise<object_ptr<tonlib_api::sendGramsResult>>&& promise) {
|
||||
if (!request.destination_) {
|
||||
return td::Status::Error(400, "Field destination must not be empty");
|
||||
}
|
||||
if (!request.private_key_) {
|
||||
return td::Status::Error(400, "Field private_key must not be empty");
|
||||
}
|
||||
if (request.message_.size() > 124) {
|
||||
if (request.message_.size() > 70) {
|
||||
return td::Status::Error(400, "Message is too long");
|
||||
}
|
||||
TRY_RESULT(account_address, block::StdAddress::parse(request.destination_->account_address_));
|
||||
|
@ -914,16 +963,30 @@ td::Status TonlibClient::do_request(const tonlib_api::testWallet_sendGrams& requ
|
|||
TRY_RESULT(input_key, from_tonlib(*request.private_key_));
|
||||
auto address = GenericAccount::get_address(
|
||||
0 /*zerochain*/, TestWallet::get_init_state(td::Ed25519::PublicKey(input_key.key.public_key.copy())));
|
||||
TRY_RESULT(private_key, key_storage_.load_private_key(std::move(input_key)));
|
||||
return do_request(tonlib_api::raw_sendMessage(
|
||||
tonlib_api::make_object<tonlib_api::accountAddress>(address.rserialize()), "",
|
||||
vm::std_boc_serialize(TestWallet::make_a_gift_message(
|
||||
td::Ed25519::PrivateKey(std::move(private_key.private_key)),
|
||||
request.seqno_, request.amount_, request.message_, account_address))
|
||||
TRY_RESULT(private_key_str, key_storage_.load_private_key(std::move(input_key)));
|
||||
auto private_key = td::Ed25519::PrivateKey(std::move(private_key_str.private_key));
|
||||
std::string init_state;
|
||||
if (request.seqno_ == 0) {
|
||||
TRY_RESULT(public_key, private_key.get_public_key());
|
||||
init_state = vm::std_boc_serialize(TestWallet::get_init_state(public_key)).move_as_ok().as_slice().str();
|
||||
}
|
||||
td::Promise<object_ptr<tonlib_api::ok>> new_promise =
|
||||
[promise = std::move(promise)](td::Result<object_ptr<tonlib_api::ok>> res) mutable {
|
||||
if (res.is_error()) {
|
||||
promise.set_error(res.move_as_error());
|
||||
} else {
|
||||
promise.set_value(tonlib_api::make_object<tonlib_api::sendGramsResult>(0));
|
||||
}
|
||||
};
|
||||
return do_request(
|
||||
tonlib_api::raw_sendMessage(
|
||||
tonlib_api::make_object<tonlib_api::accountAddress>(address.rserialize(true)), std::move(init_state),
|
||||
vm::std_boc_serialize(TestWallet::make_a_gift_message(private_key, request.seqno_, request.amount_,
|
||||
request.message_, account_address))
|
||||
.move_as_ok()
|
||||
.as_slice()
|
||||
.str()),
|
||||
std::move(promise));
|
||||
std::move(new_promise));
|
||||
}
|
||||
|
||||
td::Status TonlibClient::do_request(tonlib_api::testWallet_getAccountState& request,
|
||||
|
@ -956,21 +1019,21 @@ td::Status TonlibClient::do_request(const tonlib_api::wallet_init& request,
|
|||
TRY_RESULT(private_key, key_storage_.load_private_key(std::move(input_key)));
|
||||
auto init_message = Wallet::get_init_message(td::Ed25519::PrivateKey(std::move(private_key.private_key)));
|
||||
return do_request(
|
||||
tonlib_api::raw_sendMessage(tonlib_api::make_object<tonlib_api::accountAddress>(address.rserialize()),
|
||||
tonlib_api::raw_sendMessage(tonlib_api::make_object<tonlib_api::accountAddress>(address.rserialize(true)),
|
||||
vm::std_boc_serialize(init_state).move_as_ok().as_slice().str(),
|
||||
vm::std_boc_serialize(init_message).move_as_ok().as_slice().str()),
|
||||
std::move(promise));
|
||||
}
|
||||
|
||||
td::Status TonlibClient::do_request(const tonlib_api::wallet_sendGrams& request,
|
||||
td::Promise<object_ptr<tonlib_api::ok>>&& promise) {
|
||||
td::Promise<object_ptr<tonlib_api::sendGramsResult>>&& promise) {
|
||||
if (!request.destination_) {
|
||||
return td::Status::Error(400, "Field destination must not be empty");
|
||||
}
|
||||
if (!request.private_key_) {
|
||||
return td::Status::Error(400, "Field private_key must not be empty");
|
||||
}
|
||||
if (request.message_.size() > 124) {
|
||||
if (request.message_.size() > 70) {
|
||||
return td::Status::Error(400, "Message is too long");
|
||||
}
|
||||
TRY_RESULT(valid_until, td::narrow_cast_safe<td::uint32>(request.valid_until_));
|
||||
|
@ -979,17 +1042,30 @@ td::Status TonlibClient::do_request(const tonlib_api::wallet_sendGrams& request,
|
|||
TRY_RESULT(input_key, from_tonlib(*request.private_key_));
|
||||
auto address = GenericAccount::get_address(
|
||||
0 /*zerochain*/, Wallet::get_init_state(td::Ed25519::PublicKey(input_key.key.public_key.copy())));
|
||||
TRY_RESULT(private_key, key_storage_.load_private_key(std::move(input_key)));
|
||||
TRY_RESULT(private_key_str, key_storage_.load_private_key(std::move(input_key)));
|
||||
auto private_key = td::Ed25519::PrivateKey(std::move(private_key_str.private_key));
|
||||
std::string init_state;
|
||||
if (request.seqno_ == 0) {
|
||||
TRY_RESULT(public_key, private_key.get_public_key());
|
||||
init_state = vm::std_boc_serialize(Wallet::get_init_state(public_key)).move_as_ok().as_slice().str();
|
||||
}
|
||||
td::Promise<object_ptr<tonlib_api::ok>> new_promise =
|
||||
[promise = std::move(promise), valid_until](td::Result<object_ptr<tonlib_api::ok>> res) mutable {
|
||||
if (res.is_error()) {
|
||||
promise.set_error(res.move_as_error());
|
||||
} else {
|
||||
promise.set_value(tonlib_api::make_object<tonlib_api::sendGramsResult>(valid_until));
|
||||
}
|
||||
};
|
||||
return do_request(
|
||||
tonlib_api::raw_sendMessage(
|
||||
tonlib_api::make_object<tonlib_api::accountAddress>(address.rserialize()), "",
|
||||
vm::std_boc_serialize(Wallet::make_a_gift_message(td::Ed25519::PrivateKey(std::move(private_key.private_key)),
|
||||
request.seqno_, valid_until, request.amount_,
|
||||
tonlib_api::make_object<tonlib_api::accountAddress>(address.rserialize(true)), std::move(init_state),
|
||||
vm::std_boc_serialize(Wallet::make_a_gift_message(private_key, request.seqno_, valid_until, request.amount_,
|
||||
request.message_, account_address))
|
||||
.move_as_ok()
|
||||
.as_slice()
|
||||
.str()),
|
||||
std::move(promise));
|
||||
std::move(new_promise));
|
||||
}
|
||||
|
||||
td::Status TonlibClient::do_request(tonlib_api::wallet_getAccountState& request,
|
||||
|
@ -1012,23 +1088,31 @@ td::Status TonlibClient::do_request(tonlib_api::wallet_getAccountState& request,
|
|||
|
||||
// TestGiver
|
||||
td::Status TonlibClient::do_request(const tonlib_api::testGiver_sendGrams& request,
|
||||
td::Promise<object_ptr<tonlib_api::ok>>&& promise) {
|
||||
td::Promise<object_ptr<tonlib_api::sendGramsResult>>&& promise) {
|
||||
if (!request.destination_) {
|
||||
return td::Status::Error(400, "Field destination must not be empty");
|
||||
}
|
||||
if (request.message_.size() > 124) {
|
||||
if (request.message_.size() > 70) {
|
||||
return td::Status::Error(400, "Message is too long");
|
||||
}
|
||||
TRY_RESULT(account_address, block::StdAddress::parse(request.destination_->account_address_));
|
||||
account_address.bounceable = false;
|
||||
td::Promise<object_ptr<tonlib_api::ok>> new_promise =
|
||||
[promise = std::move(promise)](td::Result<object_ptr<tonlib_api::ok>> res) mutable {
|
||||
if (res.is_error()) {
|
||||
promise.set_error(res.move_as_error());
|
||||
} else {
|
||||
promise.set_value(tonlib_api::make_object<tonlib_api::sendGramsResult>(0));
|
||||
}
|
||||
};
|
||||
return do_request(tonlib_api::raw_sendMessage(
|
||||
tonlib_api::make_object<tonlib_api::accountAddress>(TestGiver::address().rserialize()), "",
|
||||
tonlib_api::make_object<tonlib_api::accountAddress>(TestGiver::address().rserialize(true)), "",
|
||||
vm::std_boc_serialize(TestGiver::make_a_gift_message(request.seqno_, request.amount_,
|
||||
request.message_, account_address))
|
||||
.move_as_ok()
|
||||
.as_slice()
|
||||
.str()),
|
||||
std::move(promise));
|
||||
std::move(new_promise));
|
||||
}
|
||||
|
||||
td::Status TonlibClient::do_request(const tonlib_api::testGiver_getAccountState& request,
|
||||
|
@ -1085,21 +1169,16 @@ class TonlibQueryActor : public td::actor::Actor {
|
|||
class GenericSendGrams : public TonlibQueryActor {
|
||||
public:
|
||||
GenericSendGrams(td::actor::ActorShared<TonlibClient> client, tonlib_api::generic_sendGrams send_grams,
|
||||
td::Promise<tonlib_api::object_ptr<tonlib_api::ok>>&& promise)
|
||||
td::Promise<tonlib_api::object_ptr<tonlib_api::sendGramsResult>>&& promise)
|
||||
: TonlibQueryActor(std::move(client)), send_grams_(std::move(send_grams)), promise_(std::move(promise)) {
|
||||
timeout_ = td::Timestamp::in(15);
|
||||
}
|
||||
|
||||
private:
|
||||
tonlib_api::generic_sendGrams send_grams_;
|
||||
td::Promise<tonlib_api::object_ptr<tonlib_api::ok>> promise_;
|
||||
td::Promise<tonlib_api::object_ptr<tonlib_api::sendGramsResult>> promise_;
|
||||
|
||||
enum class SourceAction { Wait, Init, WaitInited, Ok } source_action_ = SourceAction::Wait;
|
||||
tonlib_api::object_ptr<tonlib_api::generic_AccountState> source_state_;
|
||||
block::StdAddress source_address_;
|
||||
td::Timestamp source_next_get_state_;
|
||||
td::Timestamp timeout_;
|
||||
bool has_source_state_query_{false};
|
||||
|
||||
tonlib_api::object_ptr<tonlib_api::generic_AccountState> destination_state_;
|
||||
bool is_destination_bounce_{false};
|
||||
|
@ -1117,6 +1196,7 @@ class GenericSendGrams : public TonlibQueryActor {
|
|||
}
|
||||
|
||||
td::Status do_start_up() {
|
||||
alarm_timestamp() = td::Timestamp::in(15);
|
||||
if (!send_grams_.destination_) {
|
||||
return td::Status::Error(400, "Field destination must not be empty");
|
||||
}
|
||||
|
@ -1161,39 +1241,26 @@ class GenericSendGrams : public TonlibQueryActor {
|
|||
}
|
||||
|
||||
td::Status do_on_source_state(td::Result<tonlib_api::object_ptr<tonlib_api::generic_AccountState>> r_state) {
|
||||
has_source_state_query_ = false;
|
||||
TRY_RESULT(state, std::move(r_state));
|
||||
source_state_ = std::move(state);
|
||||
if (source_action_ == SourceAction::Wait) {
|
||||
source_action_ = SourceAction::Ok;
|
||||
if (false && source_state_->get_id() == tonlib_api::generic_accountStateUninited::ID &&
|
||||
send_grams_.private_key_ && send_grams_.private_key_->key_) {
|
||||
if (source_state_->get_id() == tonlib_api::generic_accountStateUninited::ID && send_grams_.private_key_ &&
|
||||
send_grams_.private_key_->key_) {
|
||||
TRY_RESULT(key_bytes, block::PublicKey::parse(send_grams_.private_key_->key_->public_key_));
|
||||
auto key = td::Ed25519::PublicKey(td::SecureString(key_bytes.key));
|
||||
auto addr = GenericAccount::get_address(0 /*zerochain*/, TestWallet::get_init_state(key));
|
||||
if (addr.addr == source_address_.addr) {
|
||||
source_action_ = SourceAction::Init;
|
||||
send_query(tonlib_api::testWallet_init(clone(send_grams_.private_key_)),
|
||||
[actor_id = actor_id(this)](auto r_res) {
|
||||
send_closure(actor_id, &GenericSendGrams::on_source_init, std::move(r_res));
|
||||
});
|
||||
}
|
||||
}
|
||||
} else if (source_action_ == SourceAction::WaitInited) {
|
||||
if (source_state_->get_id() != tonlib_api::generic_accountStateUninited::ID) {
|
||||
source_action_ = SourceAction::Ok;
|
||||
}
|
||||
}
|
||||
return do_loop();
|
||||
}
|
||||
|
||||
void on_source_init(td::Result<tonlib_api::object_ptr<tonlib_api::ok>> r_ok) {
|
||||
do_on_source_init(std::move(r_ok));
|
||||
if (GenericAccount::get_address(0 /*zerochain*/, TestWallet::get_init_state(key)).addr == source_address_.addr) {
|
||||
auto state = ton::move_tl_object_as<tonlib_api::generic_accountStateUninited>(source_state_);
|
||||
source_state_ = tonlib_api::make_object<tonlib_api::generic_accountStateTestWallet>(
|
||||
tonlib_api::make_object<tonlib_api::testWallet_accountState>(-1, 0, nullptr,
|
||||
state->account_state_->sync_utime_));
|
||||
} else if (GenericAccount::get_address(0 /*zerochain*/, Wallet::get_init_state(key)).addr ==
|
||||
source_address_.addr) {
|
||||
auto state = ton::move_tl_object_as<tonlib_api::generic_accountStateUninited>(source_state_);
|
||||
source_state_ = tonlib_api::make_object<tonlib_api::generic_accountStateWallet>(
|
||||
tonlib_api::make_object<tonlib_api::wallet_accountState>(-1, 0, nullptr,
|
||||
state->account_state_->sync_utime_));
|
||||
}
|
||||
}
|
||||
|
||||
td::Status do_on_source_init(td::Result<tonlib_api::object_ptr<tonlib_api::ok>> r_ok) {
|
||||
TRY_RESULT(ok, std::move(r_ok));
|
||||
source_action_ = SourceAction::WaitInited;
|
||||
return do_loop();
|
||||
}
|
||||
|
||||
|
@ -1204,34 +1271,18 @@ class GenericSendGrams : public TonlibQueryActor {
|
|||
td::Status do_on_destination_state(td::Result<tonlib_api::object_ptr<tonlib_api::generic_AccountState>> r_state) {
|
||||
TRY_RESULT(state, std::move(r_state));
|
||||
destination_state_ = std::move(state);
|
||||
if (destination_state_->get_id() == tonlib_api::generic_accountStateUninited::ID) {
|
||||
//return td::Status::Error("Transfer to uninited wallet");
|
||||
if (destination_state_->get_id() == tonlib_api::generic_accountStateUninited::ID && is_destination_bounce_ &&
|
||||
!send_grams_.allow_send_to_uninited_) {
|
||||
return td::Status::Error(400, "DANGEROUS_TRANSACTION: Transfer to uninited wallet");
|
||||
}
|
||||
return do_loop();
|
||||
}
|
||||
|
||||
void alarm() override {
|
||||
check(do_loop());
|
||||
check(td::Status::Error("Timeout"));
|
||||
}
|
||||
td::Status do_loop() {
|
||||
if (timeout_.is_in_past()) {
|
||||
return td::Status::Error("Timeout");
|
||||
}
|
||||
alarm_timestamp().relax(timeout_);
|
||||
if (source_action_ == SourceAction::WaitInited && !has_source_state_query_) {
|
||||
if (source_next_get_state_.is_in_past()) {
|
||||
source_next_get_state_ = td::Timestamp::in(1);
|
||||
has_source_state_query_ = true;
|
||||
send_query(tonlib_api::generic_getAccountState(
|
||||
tonlib_api::make_object<tonlib_api::accountAddress>(send_grams_.source_->account_address_)),
|
||||
[actor_id = actor_id(this)](auto r_res) {
|
||||
send_closure(actor_id, &GenericSendGrams::on_source_state, std::move(r_res));
|
||||
});
|
||||
} else {
|
||||
alarm_timestamp().relax(source_next_get_state_);
|
||||
}
|
||||
}
|
||||
if (source_action_ != SourceAction::Ok || !destination_state_) {
|
||||
if (!source_state_ || !destination_state_) {
|
||||
return td::Status::OK();
|
||||
}
|
||||
downcast_call(*source_state_,
|
||||
|
@ -1254,7 +1305,10 @@ class GenericSendGrams : public TonlibQueryActor {
|
|||
[&](tonlib_api::generic_accountStateWallet& test_wallet_state) {
|
||||
send_query(tonlib_api::wallet_sendGrams(
|
||||
std::move(send_grams_.private_key_), std::move(send_grams_.destination_),
|
||||
test_wallet_state.account_state_->seqno_, std::numeric_limits<td::uint32>::max(),
|
||||
test_wallet_state.account_state_->seqno_,
|
||||
send_grams_.timeout_ == 0
|
||||
? 60 + test_wallet_state.account_state_->sync_utime_
|
||||
: send_grams_.timeout_ + test_wallet_state.account_state_->sync_utime_,
|
||||
send_grams_.amount_, std::move(send_grams_.message_)),
|
||||
std::move(promise_));
|
||||
stop();
|
||||
|
@ -1272,7 +1326,10 @@ class GenericSendGrams : public TonlibQueryActor {
|
|||
};
|
||||
|
||||
td::Status TonlibClient::do_request(tonlib_api::generic_sendGrams& request,
|
||||
td::Promise<object_ptr<tonlib_api::ok>>&& promise) {
|
||||
td::Promise<object_ptr<tonlib_api::sendGramsResult>>&& promise) {
|
||||
if (request.timeout_ < 0 || request.timeout_ > 300) {
|
||||
return td::Status::Error(400, "Invalid timeout: must be between 0 and 300");
|
||||
}
|
||||
auto id = actor_id_++;
|
||||
actors_[id] = td::actor::create_actor<GenericSendGrams>("GenericSendGrams", actor_shared(this, id),
|
||||
std::move(request), std::move(promise));
|
||||
|
@ -1284,7 +1341,7 @@ td::Status TonlibClient::do_request(const tonlib_api::createNewKey& request,
|
|||
TRY_RESULT(key, key_storage_.create_new_key(std::move(request.local_password_), std::move(request.mnemonic_password_),
|
||||
std::move(request.random_extra_seed_)));
|
||||
TRY_RESULT(key_bytes, block::PublicKey::from_bytes(key.public_key.as_slice()));
|
||||
promise.set_value(tonlib_api::make_object<tonlib_api::key>(key_bytes.serialize(), std::move(key.secret)));
|
||||
promise.set_value(tonlib_api::make_object<tonlib_api::key>(key_bytes.serialize(true), std::move(key.secret)));
|
||||
return td::Status::OK();
|
||||
}
|
||||
|
||||
|
@ -1321,7 +1378,7 @@ td::Status TonlibClient::do_request(const tonlib_api::importKey& request,
|
|||
TRY_RESULT(key, key_storage_.import_key(std::move(request.local_password_), std::move(request.mnemonic_password_),
|
||||
KeyStorage::ExportedKey{std::move(request.exported_key_->word_list_)}));
|
||||
TRY_RESULT(key_bytes, block::PublicKey::from_bytes(key.public_key.as_slice()));
|
||||
promise.set_value(tonlib_api::make_object<tonlib_api::key>(key_bytes.serialize(), std::move(key.secret)));
|
||||
promise.set_value(tonlib_api::make_object<tonlib_api::key>(key_bytes.serialize(true), std::move(key.secret)));
|
||||
return td::Status::OK();
|
||||
}
|
||||
|
||||
|
@ -1350,7 +1407,7 @@ td::Status TonlibClient::do_request(const tonlib_api::importPemKey& request,
|
|||
TRY_RESULT(key, key_storage_.import_pem_key(std::move(request.local_password_), std::move(request.key_password_),
|
||||
KeyStorage::ExportedPemKey{std::move(request.exported_key_->pem_)}));
|
||||
TRY_RESULT(key_bytes, block::PublicKey::from_bytes(key.public_key.as_slice()));
|
||||
promise.set_value(tonlib_api::make_object<tonlib_api::key>(key_bytes.serialize(), std::move(key.secret)));
|
||||
promise.set_value(tonlib_api::make_object<tonlib_api::key>(key_bytes.serialize(true), std::move(key.secret)));
|
||||
return td::Status::OK();
|
||||
}
|
||||
|
||||
|
@ -1374,7 +1431,7 @@ td::Status TonlibClient::do_request(const tonlib_api::importEncryptedKey& reques
|
|||
std::move(request.local_password_), std::move(request.key_password_),
|
||||
KeyStorage::ExportedEncryptedKey{std::move(request.exported_encrypted_key_->data_)}));
|
||||
TRY_RESULT(key_bytes, block::PublicKey::from_bytes(key.public_key.as_slice()));
|
||||
promise.set_value(tonlib_api::make_object<tonlib_api::key>(key_bytes.serialize(), std::move(key.secret)));
|
||||
promise.set_value(tonlib_api::make_object<tonlib_api::key>(key_bytes.serialize(true), std::move(key.secret)));
|
||||
return td::Status::OK();
|
||||
}
|
||||
|
||||
|
|
|
@ -47,6 +47,9 @@ class TonlibClient : public td::actor::Actor {
|
|||
enum class State { Uninited, Running, Closed } state_ = State::Uninited;
|
||||
td::unique_ptr<TonlibCallback> callback_;
|
||||
Config config_;
|
||||
td::uint32 config_generation_{0};
|
||||
std::string blockchain_name_;
|
||||
bool ignore_cache_{false};
|
||||
|
||||
bool use_callbacks_for_network_{false};
|
||||
td::actor::ActorId<ExtClientOutbound> ext_client_outbound_;
|
||||
|
@ -85,7 +88,7 @@ class TonlibClient : public td::actor::Actor {
|
|||
}
|
||||
}
|
||||
|
||||
void update_last_block_state(LastBlockState state);
|
||||
void update_last_block_state(LastBlockState state, td::uint32 config_generation_);
|
||||
void on_result(td::uint64 id, object_ptr<tonlib_api::Object> response);
|
||||
static bool is_static_request(td::int32 id);
|
||||
static bool is_uninited_request(td::int32 id);
|
||||
|
@ -98,6 +101,8 @@ class TonlibClient : public td::actor::Actor {
|
|||
static object_ptr<tonlib_api::Object> do_static_request(const tonlib_api::testWallet_getAccountAddress& request);
|
||||
static object_ptr<tonlib_api::Object> do_static_request(const tonlib_api::wallet_getAccountAddress& request);
|
||||
static object_ptr<tonlib_api::Object> do_static_request(const tonlib_api::testGiver_getAccountAddress& request);
|
||||
static object_ptr<tonlib_api::Object> do_static_request(const tonlib_api::packAccountAddress& request);
|
||||
static object_ptr<tonlib_api::Object> do_static_request(const tonlib_api::unpackAccountAddress& request);
|
||||
static object_ptr<tonlib_api::Object> do_static_request(tonlib_api::getBip39Hints& request);
|
||||
|
||||
static object_ptr<tonlib_api::Object> do_static_request(tonlib_api::setLogStream& request);
|
||||
|
@ -114,12 +119,10 @@ class TonlibClient : public td::actor::Actor {
|
|||
return td::Status::Error(400, "Function is unsupported");
|
||||
}
|
||||
|
||||
td::Status set_config(std::string config);
|
||||
|
||||
td::Status set_config(object_ptr<tonlib_api::config> config);
|
||||
td::Status do_request(const tonlib_api::init& request, td::Promise<object_ptr<tonlib_api::ok>>&& promise);
|
||||
td::Status do_request(const tonlib_api::close& request, td::Promise<object_ptr<tonlib_api::ok>>&& promise);
|
||||
td::Status do_request(const tonlib_api::options_setConfig& request,
|
||||
td::Promise<object_ptr<tonlib_api::ok>>&& promise);
|
||||
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(tonlib_api::raw_getAccountState& request,
|
||||
|
@ -129,23 +132,25 @@ class TonlibClient : public td::actor::Actor {
|
|||
|
||||
td::Status do_request(const tonlib_api::testWallet_init& request, td::Promise<object_ptr<tonlib_api::ok>>&& promise);
|
||||
td::Status do_request(const tonlib_api::testWallet_sendGrams& request,
|
||||
td::Promise<object_ptr<tonlib_api::ok>>&& promise);
|
||||
td::Promise<object_ptr<tonlib_api::sendGramsResult>>&& promise);
|
||||
td::Status do_request(tonlib_api::testWallet_getAccountState& request,
|
||||
td::Promise<object_ptr<tonlib_api::testWallet_accountState>>&& promise);
|
||||
|
||||
td::Status do_request(const tonlib_api::wallet_init& request, td::Promise<object_ptr<tonlib_api::ok>>&& promise);
|
||||
td::Status do_request(const tonlib_api::wallet_sendGrams& request, td::Promise<object_ptr<tonlib_api::ok>>&& promise);
|
||||
td::Status do_request(const tonlib_api::wallet_sendGrams& request,
|
||||
td::Promise<object_ptr<tonlib_api::sendGramsResult>>&& promise);
|
||||
td::Status do_request(tonlib_api::wallet_getAccountState& request,
|
||||
td::Promise<object_ptr<tonlib_api::wallet_accountState>>&& promise);
|
||||
|
||||
td::Status do_request(const tonlib_api::testGiver_getAccountState& request,
|
||||
td::Promise<object_ptr<tonlib_api::testGiver_accountState>>&& promise);
|
||||
td::Status do_request(const tonlib_api::testGiver_sendGrams& request,
|
||||
td::Promise<object_ptr<tonlib_api::ok>>&& promise);
|
||||
td::Promise<object_ptr<tonlib_api::sendGramsResult>>&& promise);
|
||||
|
||||
td::Status do_request(const tonlib_api::generic_getAccountState& request,
|
||||
td::Promise<object_ptr<tonlib_api::generic_AccountState>>&& promise);
|
||||
td::Status do_request(tonlib_api::generic_sendGrams& request, td::Promise<object_ptr<tonlib_api::ok>>&& promise);
|
||||
td::Status do_request(tonlib_api::generic_sendGrams& request,
|
||||
td::Promise<object_ptr<tonlib_api::sendGramsResult>>&& promise);
|
||||
|
||||
td::Status do_request(const tonlib_api::createNewKey& request, td::Promise<object_ptr<tonlib_api::key>>&& promise);
|
||||
td::Status do_request(const tonlib_api::exportKey& request,
|
||||
|
|
|
@ -49,15 +49,18 @@ td::Ref<vm::Cell> Wallet::make_a_gift_message(const td::Ed25519::PrivateKey& pri
|
|||
td::BigInt256 dest_addr;
|
||||
dest_addr.import_bits(dest_address.addr.as_bitslice());
|
||||
vm::CellBuilder cb;
|
||||
cb.append_cellslice(binary_bitstring_to_cellslice("b{010000100}").move_as_ok())
|
||||
cb.append_cellslice(binary_bitstring_to_cellslice("b{01}").move_as_ok())
|
||||
.store_long(dest_address.bounceable, 1)
|
||||
.append_cellslice(binary_bitstring_to_cellslice("b{000100}").move_as_ok())
|
||||
.store_long(dest_address.workchain, 8)
|
||||
.store_int256(dest_addr, 256);
|
||||
block::tlb::t_Grams.store_integer_value(cb, td::BigInt256(gramms));
|
||||
auto message_inner = cb.store_zeroes(9 + 64 + 32 + 1 + 1).store_bytes("\0\0\0\0", 4).store_bytes(message).finalize();
|
||||
td::int8 send_mode = 3;
|
||||
auto message_outer = vm::CellBuilder()
|
||||
.store_long(seqno, 32)
|
||||
.store_long(valid_until, 32)
|
||||
.store_long(1, 8)
|
||||
.store_long(send_mode, 8)
|
||||
.store_ref(message_inner)
|
||||
.finalize();
|
||||
std::string seq_no(4, 0);
|
||||
|
@ -68,8 +71,8 @@ td::Ref<vm::Cell> Wallet::make_a_gift_message(const td::Ed25519::PrivateKey& pri
|
|||
td::Ref<vm::Cell> Wallet::get_init_code() {
|
||||
static auto res = [] {
|
||||
auto serialized_code = td::base64_decode(
|
||||
"te6ccgEEBgEAAAAAaAABFP8A9KQT9KDyyAsBAgEgAgMCAUgEBQCA8oMI1xgg0x/TH/gjErnyY+1E0NMf0//"
|
||||
"RUTG68qED+QFUEEL5EPKi+AACkyDXSpbTB9QC+wDo0aTIyx/L/8ntVAAE0DAAEaCZL9qJoa4WPw==")
|
||||
"te6ccgEEAQEAAAAAVwAAqv8AIN0gggFMl7qXMO1E0NcLH+Ck8mCDCNcYINMf0x8B+CO78mPtRNDTH9P/"
|
||||
"0VExuvKhA/kBVBBC+RDyovgAApMg10qW0wfUAvsA6NGkyMsfy//J7VQ=")
|
||||
.move_as_ok();
|
||||
return vm::std_boc_deserialize(serialized_code).move_as_ok();
|
||||
}();
|
||||
|
|
|
@ -25,9 +25,14 @@ class TonlibCli : public td::actor::Actor {
|
|||
struct Options {
|
||||
bool enable_readline{true};
|
||||
std::string config;
|
||||
std::string name;
|
||||
std::string key_dir{"."};
|
||||
bool use_callbacks_for_network{false};
|
||||
bool use_simple_wallet{false};
|
||||
bool ignore_cache{false};
|
||||
|
||||
bool one_shot{false};
|
||||
std::string cmd;
|
||||
};
|
||||
TonlibCli(Options options) : options_(std::move(options)) {
|
||||
}
|
||||
|
@ -65,8 +70,10 @@ class TonlibCli : public td::actor::Actor {
|
|||
td::actor::ActorShared<TonlibCli> id_;
|
||||
};
|
||||
ref_cnt_++;
|
||||
if (!options_.one_shot) {
|
||||
io_ = td::TerminalIO::create("> ", options_.enable_readline, std::make_unique<Cb>(actor_shared(this)));
|
||||
td::actor::send_closure(io_, &td::TerminalIO::set_log_interface);
|
||||
}
|
||||
|
||||
class TonlibCb : public tonlib::TonlibCallback {
|
||||
public:
|
||||
|
@ -109,13 +116,20 @@ class TonlibCli : public td::actor::Actor {
|
|||
}
|
||||
|
||||
using tonlib_api::make_object;
|
||||
send_query(make_object<tonlib_api::init>(make_object<tonlib_api::options>(options_.config, options_.key_dir,
|
||||
options_.use_callbacks_for_network)),
|
||||
auto config = !options_.config.empty()
|
||||
? make_object<tonlib_api::config>(options_.config, options_.name,
|
||||
options_.use_callbacks_for_network, options_.ignore_cache)
|
||||
: nullptr;
|
||||
send_query(make_object<tonlib_api::init>(make_object<tonlib_api::options>(std::move(config), options_.key_dir)),
|
||||
[](auto r_ok) {
|
||||
LOG_IF(ERROR, r_ok.is_error()) << r_ok.error();
|
||||
td::TerminalIO::out() << "Tonlib is inited\n";
|
||||
});
|
||||
if (options_.one_shot) {
|
||||
td::actor::send_closure(actor_id(this), &TonlibCli::parse_line, td::BufferSlice(options_.cmd));
|
||||
}
|
||||
}
|
||||
|
||||
void hangup_shared() override {
|
||||
CHECK(ref_cnt_ > 0);
|
||||
ref_cnt_--;
|
||||
|
@ -130,6 +144,26 @@ class TonlibCli : public td::actor::Actor {
|
|||
td::actor::SchedulerContext::get()->stop();
|
||||
}
|
||||
|
||||
void on_wait() {
|
||||
if (options_.one_shot) {
|
||||
LOG(ERROR) << "FAILED (not enough data)";
|
||||
std::_Exit(2);
|
||||
}
|
||||
}
|
||||
|
||||
void on_error() {
|
||||
if (options_.one_shot) {
|
||||
LOG(ERROR) << "FAILED";
|
||||
std::_Exit(1);
|
||||
}
|
||||
}
|
||||
void on_ok() {
|
||||
if (options_.one_shot) {
|
||||
LOG(INFO) << "OK";
|
||||
std::_Exit(0);
|
||||
}
|
||||
}
|
||||
|
||||
void parse_line(td::BufferSlice line) {
|
||||
if (is_closing_) {
|
||||
return;
|
||||
|
@ -148,13 +182,15 @@ class TonlibCli : public td::actor::Actor {
|
|||
td::TerminalIO::out() << "help - show this help\n";
|
||||
td::TerminalIO::out() << "genkey - generate new secret key\n";
|
||||
td::TerminalIO::out() << "keys - show all stored keys\n";
|
||||
td::TerminalIO::out() << "exportkey [key_id] - export key\n";
|
||||
td::TerminalIO::out() << "setconfig <path> - set lite server config\n";
|
||||
td::TerminalIO::out() << "unpackaddress <address> - validate and parse address\n";
|
||||
td::TerminalIO::out() << "importkey - import key\n";
|
||||
td::TerminalIO::out() << "exportkey [<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()
|
||||
<< "gethistory <key_id> - get history fo simple wallet with requested key (last 10 transactions)\n";
|
||||
td::TerminalIO::out() << "init <key_id> - init simple wallet with requested key\n";
|
||||
td::TerminalIO::out() << "transfer <from_key_id> <to_key_id> <amount> - transfer <amount> of grams from "
|
||||
td::TerminalIO::out() << "transfer[f] <from_key_id> <to_key_id> <amount> - transfer <amount> of grams from "
|
||||
"<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";
|
||||
|
@ -174,20 +210,36 @@ class TonlibCli : public td::actor::Actor {
|
|||
} else if (cmd == "importkey") {
|
||||
import_key(parser.read_all());
|
||||
} else if (cmd == "setconfig") {
|
||||
set_config(parser.read_word());
|
||||
auto config = parser.read_word();
|
||||
auto name = parser.read_word();
|
||||
auto to_bool = [](td::Slice word) {
|
||||
if (word.empty()) {
|
||||
return false;
|
||||
}
|
||||
if (word == "0" || word == "FALSE" || word == "false") {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
auto use_callback = parser.read_word();
|
||||
auto force = parser.read_word();
|
||||
set_config(config, name, to_bool(use_callback), to_bool(force));
|
||||
} else if (cmd == "getstate") {
|
||||
get_state(parser.read_word());
|
||||
} else if (cmd == "gethistory") {
|
||||
get_history(parser.read_word());
|
||||
} else if (cmd == "init") {
|
||||
init_simple_wallet(parser.read_word());
|
||||
} else if (cmd == "transfer") {
|
||||
} else if (cmd == "transfer" || cmd == "transferf") {
|
||||
auto from = parser.read_word();
|
||||
auto to = parser.read_word();
|
||||
auto grams = parser.read_word();
|
||||
transfer(from, to, grams);
|
||||
auto message = parser.read_word();
|
||||
transfer(from, to, grams, message, cmd == "transferf");
|
||||
} else if (cmd == "hint") {
|
||||
get_hints(parser.read_word());
|
||||
} else if (cmd == "unpackaddress") {
|
||||
unpack_address(parser.read_word());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -251,6 +303,17 @@ class TonlibCli : public td::actor::Actor {
|
|||
};
|
||||
}
|
||||
|
||||
void unpack_address(td::Slice addr) {
|
||||
send_query(tonlib_api::make_object<tonlib_api::unpackAccountAddress>(addr.str()),
|
||||
[addr = addr.str()](auto r_parsed_addr) mutable {
|
||||
if (r_parsed_addr.is_error()) {
|
||||
LOG(ERROR) << "Failed to parse address: " << r_parsed_addr.error();
|
||||
return;
|
||||
}
|
||||
LOG(ERROR) << to_string(r_parsed_addr.ok());
|
||||
});
|
||||
}
|
||||
|
||||
void generate_key(td::SecureString entropy = {}) {
|
||||
if (entropy.size() < 20) {
|
||||
td::TerminalIO::out() << "Enter some entropy";
|
||||
|
@ -275,6 +338,7 @@ class TonlibCli : public td::actor::Actor {
|
|||
[this, password = std::move(password)](auto r_key) mutable {
|
||||
if (r_key.is_error()) {
|
||||
LOG(ERROR) << "Failed to create new key: " << r_key.error();
|
||||
return;
|
||||
}
|
||||
auto key = r_key.move_as_ok();
|
||||
LOG(ERROR) << to_string(key);
|
||||
|
@ -282,7 +346,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(key->public_key_, keys_.size() - 1, std::move(password));
|
||||
store_keys();
|
||||
});
|
||||
}
|
||||
|
@ -305,7 +369,13 @@ class TonlibCli : public td::actor::Actor {
|
|||
auto db = r_db.move_as_ok();
|
||||
td::ConstParser parser(db.as_slice());
|
||||
while (true) {
|
||||
auto public_key = parser.read_word();
|
||||
auto public_key = parser.read_word().str();
|
||||
{
|
||||
auto tmp = td::base64_decode(public_key);
|
||||
if (tmp.is_ok()) {
|
||||
public_key = td::base64url_encode(tmp.move_as_ok());
|
||||
}
|
||||
}
|
||||
auto secret_b64 = parser.read_word();
|
||||
if (secret_b64.empty()) {
|
||||
break;
|
||||
|
@ -313,10 +383,11 @@ class TonlibCli : public td::actor::Actor {
|
|||
auto r_secret = td::base64_decode_secure(secret_b64);
|
||||
if (r_secret.is_error()) {
|
||||
LOG(ERROR) << "Invalid secret database at " << key_db_path();
|
||||
return;
|
||||
}
|
||||
|
||||
KeyInfo info;
|
||||
info.public_key = public_key.str();
|
||||
info.public_key = public_key;
|
||||
info.secret = r_secret.move_as_ok();
|
||||
LOG(INFO) << info.public_key;
|
||||
|
||||
|
@ -324,13 +395,16 @@ class TonlibCli : public td::actor::Actor {
|
|||
}
|
||||
}
|
||||
|
||||
void dump_key(size_t i) {
|
||||
td::TerminalIO::out() << " #" << i << ": Public key: " << keys_[i].public_key << " "
|
||||
<< " Address: "
|
||||
<< to_account_address(PSLICE() << i, false).move_as_ok().address->account_address_ << "\n";
|
||||
}
|
||||
void dump_keys() {
|
||||
td::TerminalIO::out() << "Got " << keys_.size() << " keys"
|
||||
<< "\n";
|
||||
for (size_t i = 0; i < keys_.size(); i++) {
|
||||
td::TerminalIO::out() << " #" << i << ": " << keys_[i].public_key << "\n";
|
||||
td::TerminalIO::out() << " " << to_account_address(PSLICE() << i, false).move_as_ok().address->account_address_
|
||||
<< "\n";
|
||||
dump_key(i);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -447,12 +521,15 @@ class TonlibCli : public td::actor::Actor {
|
|||
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))),
|
||||
[key = std::move(key)](auto r_res) {
|
||||
[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;
|
||||
}
|
||||
td::TerminalIO::out() << to_string(r_res.ok());
|
||||
dump_key(key_i);
|
||||
for (auto& word : r_res.ok()->word_list_) {
|
||||
td::TerminalIO::out() << " " << word.as_slice() << "\n";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -496,7 +573,7 @@ class TonlibCli : public td::actor::Actor {
|
|||
});
|
||||
}
|
||||
|
||||
void set_config(td::Slice path) {
|
||||
void set_config(td::Slice path, td::Slice name, bool use_callback, bool ignore_cache) {
|
||||
auto r_data = td::read_file_str(path.str());
|
||||
if (r_data.is_error()) {
|
||||
td::TerminalIO::out() << "Can't read file [" << path << "] : " << r_data.error() << "\n";
|
||||
|
@ -505,7 +582,9 @@ class TonlibCli : public td::actor::Actor {
|
|||
|
||||
auto data = r_data.move_as_ok();
|
||||
using tonlib_api::make_object;
|
||||
send_query(make_object<tonlib_api::options_setConfig>(data), [](auto r_res) {
|
||||
send_query(make_object<tonlib_api::options_setConfig>(
|
||||
make_object<tonlib_api::config>(std::move(data), name.str(), use_callback, ignore_cache)),
|
||||
[](auto r_res) {
|
||||
if (r_res.is_error()) {
|
||||
td::TerminalIO::out() << "Can't set config: " << r_res.error() << "\n";
|
||||
return;
|
||||
|
@ -519,23 +598,27 @@ class TonlibCli : public td::actor::Actor {
|
|||
dump_keys();
|
||||
td::TerminalIO::out() << "Choose public key (hex prefix or #N)";
|
||||
cont_ = [this](td::Slice key) { this->get_state(key); };
|
||||
on_wait();
|
||||
return;
|
||||
}
|
||||
auto r_address = to_account_address(key, false);
|
||||
if (r_address.is_error()) {
|
||||
td::TerminalIO::out() << "Unknown key id: [" << key << "]\n";
|
||||
on_error();
|
||||
return;
|
||||
}
|
||||
auto address = r_address.move_as_ok();
|
||||
using tonlib_api::make_object;
|
||||
send_query(make_object<tonlib_api::generic_getAccountState>(
|
||||
ton::move_tl_object_as<tonlib_api::accountAddress>(std::move(address.address))),
|
||||
[](auto r_res) {
|
||||
[this](auto r_res) {
|
||||
if (r_res.is_error()) {
|
||||
td::TerminalIO::out() << "Can't get state: " << r_res.error() << "\n";
|
||||
on_error();
|
||||
return;
|
||||
}
|
||||
td::TerminalIO::out() << to_string(r_res.ok());
|
||||
on_ok();
|
||||
});
|
||||
}
|
||||
void get_history(td::Slice key) {
|
||||
|
@ -586,53 +669,75 @@ class TonlibCli : public td::actor::Actor {
|
|||
});
|
||||
}
|
||||
|
||||
void transfer(td::Slice from, td::Slice to, td::Slice grams) {
|
||||
void transfer(td::Slice from, td::Slice to, td::Slice grams, td::Slice message, bool allow_send_to_uninited) {
|
||||
auto r_from_address = to_account_address(from, true);
|
||||
if (r_from_address.is_error()) {
|
||||
td::TerminalIO::out() << "Unknown key id: [" << from << "] : " << r_from_address.error() << "\n";
|
||||
on_error();
|
||||
return;
|
||||
}
|
||||
auto r_to_address = to_account_address(to, false);
|
||||
if (r_to_address.is_error()) {
|
||||
td::TerminalIO::out() << "Unknown key id: [" << to << "] : " << r_to_address.error() << "\n";
|
||||
on_error();
|
||||
return;
|
||||
}
|
||||
auto r_grams = td::to_integer_safe<td::uint64>(grams);
|
||||
if (r_grams.is_error()) {
|
||||
td::TerminalIO::out() << "Invalid grams amount: [" << grams << "]\n";
|
||||
on_error();
|
||||
return;
|
||||
}
|
||||
if (from != "giver") {
|
||||
if (options_.one_shot) {
|
||||
transfer(r_from_address.move_as_ok(), r_to_address.move_as_ok(), r_grams.move_as_ok(), "", "",
|
||||
allow_send_to_uninited);
|
||||
return;
|
||||
}
|
||||
if (from != "giver" && message.empty()) {
|
||||
td::TerminalIO::out() << "Enter password (could be empty)";
|
||||
cont_ = [this, from = r_from_address.move_as_ok(), to = r_to_address.move_as_ok(), grams = r_grams.move_as_ok()](
|
||||
td::Slice password) mutable { this->transfer(std::move(from), std::move(to), grams, password); };
|
||||
cont_ = [this, from = r_from_address.move_as_ok(), to = r_to_address.move_as_ok(), grams = r_grams.move_as_ok(),
|
||||
allow_send_to_uninited](td::Slice password) mutable {
|
||||
this->transfer(std::move(from), std::move(to), grams, password, allow_send_to_uninited);
|
||||
};
|
||||
on_wait();
|
||||
return;
|
||||
}
|
||||
transfer(r_from_address.move_as_ok(), r_to_address.move_as_ok(), r_grams.move_as_ok(), "");
|
||||
if (message.empty()) {
|
||||
transfer(r_from_address.move_as_ok(), r_to_address.move_as_ok(), r_grams.move_as_ok(), "",
|
||||
allow_send_to_uninited);
|
||||
} else {
|
||||
transfer(r_from_address.move_as_ok(), r_to_address.move_as_ok(), r_grams.move_as_ok(), "", message,
|
||||
allow_send_to_uninited);
|
||||
}
|
||||
}
|
||||
|
||||
void transfer(Address from, Address to, td::uint64 grams, td::Slice password) {
|
||||
void transfer(Address from, Address to, td::uint64 grams, td::Slice password, bool allow_send_to_uninited) {
|
||||
td::TerminalIO::out() << "Enter message (could be empty)";
|
||||
cont_ = [this, from = std::move(from), to = std::move(to), grams,
|
||||
password = password.str()](td::Slice message) mutable {
|
||||
this->transfer(std::move(from), std::move(to), grams, password, message);
|
||||
cont_ = [this, from = std::move(from), to = std::move(to), grams, password = password.str(),
|
||||
allow_send_to_uninited](td::Slice message) mutable {
|
||||
this->transfer(std::move(from), std::move(to), grams, password, message, allow_send_to_uninited);
|
||||
};
|
||||
on_wait();
|
||||
return;
|
||||
}
|
||||
void transfer(Address from, Address to, td::uint64 grams, td::Slice password, td::Slice message) {
|
||||
void transfer(Address from, Address to, td::uint64 grams, td::Slice password, td::Slice message,
|
||||
bool allow_send_to_uninited) {
|
||||
using tonlib_api::make_object;
|
||||
auto key = !from.secret.empty()
|
||||
? make_object<tonlib_api::inputKey>(
|
||||
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, message.str()),
|
||||
[](auto r_res) {
|
||||
send_query(
|
||||
make_object<tonlib_api::generic_sendGrams>(std::move(key), std::move(from.address), std::move(to.address),
|
||||
grams, 30, allow_send_to_uninited, message.str()),
|
||||
[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();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -719,17 +824,28 @@ int main(int argc, char* argv[]) {
|
|||
options.key_dir = arg.str();
|
||||
return td::Status::OK();
|
||||
});
|
||||
p.add_option('E', "execute", "execute one command", [&](td::Slice arg) {
|
||||
options.one_shot = true;
|
||||
options.cmd = arg.str();
|
||||
return td::Status::OK();
|
||||
});
|
||||
p.add_option('v', "verbosity", "set verbosity level", [&](td::Slice arg) {
|
||||
auto verbosity = td::to_integer<int>(arg);
|
||||
SET_VERBOSITY_LEVEL(VERBOSITY_NAME(FATAL) + verbosity);
|
||||
return (verbosity >= 0 && verbosity <= 20) ? td::Status::OK() : td::Status::Error("verbosity must be 0..20");
|
||||
});
|
||||
p.add_option('C', "config", "set lite server config", [&](td::Slice arg) {
|
||||
p.add_option('C', "config-force", "set lite server config, drop config related blockchain cache", [&](td::Slice arg) {
|
||||
TRY_RESULT(data, td::read_file_str(arg.str()));
|
||||
options.config = std::move(data);
|
||||
options.ignore_cache = true;
|
||||
return td::Status::OK();
|
||||
});
|
||||
p.add_option('c', "config", "set lite server config", [&](td::Slice arg) {
|
||||
TRY_RESULT(data, td::read_file_str(arg.str()));
|
||||
options.config = std::move(data);
|
||||
return td::Status::OK();
|
||||
});
|
||||
p.add_option('c', "use-callbacks-for-network", "do not use this", [&]() {
|
||||
p.add_option('n', "use-callbacks-for-network", "do not use this", [&]() {
|
||||
options.use_callbacks_for_network = true;
|
||||
return td::Status::OK();
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue