1
0
Fork 0
mirror of https://github.com/ton-blockchain/ton synced 2025-03-09 15:40:10 +00:00

updated smartcontracts

- updated smartcontracts
- updated fullnode database layout
- fixed memory leak in blockchain-explorer
- updated tonlib
This commit is contained in:
ton 2019-10-23 17:43:50 +04:00
parent 9c9248a9ae
commit c860ce3d1e
104 changed files with 7309 additions and 1335 deletions

View file

@ -9,7 +9,7 @@ get_filename_component(TON_REAL_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}" REALPAT
get_filename_component(TON_REAL_BINARY_DIR "${CMAKE_CURRENT_BINARY_DIR}" REALPATH)
if (TON_REAL_BINARY_DIR STREQUAL TON_REAL_SOURCE_DIR)
message(" Out-of-source build should be used to build TDLib.")
message(" Out-of-source build should be used to build TON.")
message(" You need to remove the files already created by CMake and")
message(" rerun CMake from a new directory:")
message(" rm -rf CMakeFiles CMakeCache.txt")
@ -190,6 +190,7 @@ endif()
set(CMAKE_THREAD_PREFER_PTHREAD ON)
set(THREADS_PREFER_PTHREAD_FLAG ON)
find_package(Threads REQUIRED)
find_package(ZLIB REQUIRED)
if (TON_ARCH AND NOT MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -march=${TON_ARCH}")
@ -380,7 +381,7 @@ if (NOT CMAKE_CROSSCOMPILING)
if (TDUTILS_MIME_TYPE)
set(TDMIME_AUTO tdmime_auto)
endif()
add_custom_target(prepare_cross_compiling DEPENDS tl_generate_common tlb_generate_block ${TDMIME_AUTO})
add_custom_target(prepare_cross_compiling DEPENDS tl_generate_common tlb_generate_block gen_fif ${TDMIME_AUTO})
endif()
#TESTS
@ -390,6 +391,9 @@ target_link_libraries(test-ed25519 PRIVATE ton_crypto)
add_executable(test-vm test/test-td-main.cpp ${TONVM_TEST_SOURCE})
target_link_libraries(test-vm PRIVATE ton_crypto fift-lib)
add_executable(test-smartcont test/test-td-main.cpp ${SMARTCONT_TEST_SOURCE})
target_link_libraries(test-smartcont PRIVATE smc-envelope fift-lib ton_db)
add_executable(test-cells test/test-td-main.cpp ${CELLS_TEST_SOURCE})
target_link_libraries(test-cells PRIVATE ton_crypto)
@ -490,10 +494,12 @@ endif()
enable_testing()
set(TEST_OPTIONS "--regression ${CMAKE_CURRENT_SOURCE_DIR}/test/regression-tests.ans --filter -Bench")
separate_arguments(TEST_OPTIONS)
add_test(test-ed25519-crypto crypto/test-ed25519-crypto)
add_test(test-ed25519 test-ed25519)
add_test(test-vm test-vm ${TEST_OPTIONS})
add_test(test-fift test-fift ${TEST_OPTIONS})
add_test(test-cells test-cells ${TEST_OPTIONS})
add_test(test-smartcont test-smartcont)
add_test(test-net test-net)
add_test(test-actors test-tdactor)

View file

@ -146,8 +146,8 @@ void AdnlExtServerImpl::add_tcp_port(td::uint16 port) {
}
};
auto act = td::actor::create_actor<TcpInfiniteListener>(td::actor::ActorOptions().with_name("listener").with_poll(),
port, std::make_unique<Callback>(actor_id(this)));
auto act = td::actor::create_actor<td::TcpInfiniteListener>(
td::actor::ActorOptions().with_name("listener").with_poll(), port, std::make_unique<Callback>(actor_id(this)));
listeners_.emplace(port, std::move(act));
}

View file

@ -32,69 +32,6 @@ namespace ton {
namespace adnl {
class TcpInfiniteListener : public td::actor::Actor {
public:
TcpInfiniteListener(td::int32 port, std::unique_ptr<td::TcpListener::Callback> callback)
: port_(port), callback_(std::move(callback)) {
}
private:
td::int32 port_;
std::unique_ptr<td::TcpListener::Callback> callback_;
td::actor::ActorOwn<td::TcpListener> tcp_listener_;
td::int32 refcnt_{0};
bool close_flag_{false};
void start_up() override {
loop();
}
void hangup() override {
close_flag_ = true;
tcp_listener_.reset();
if (refcnt_ == 0) {
stop();
}
}
void loop() override {
if (!tcp_listener_.empty()) {
return;
}
class Callback : public td::TcpListener::Callback {
public:
Callback(td::actor::ActorShared<TcpInfiniteListener> parent) : parent_(std::move(parent)) {
}
void accept(td::SocketFd fd) override {
td::actor::send_closure(parent_, &TcpInfiniteListener::accept, std::move(fd));
}
private:
td::actor::ActorShared<TcpInfiniteListener> parent_;
};
refcnt_++;
tcp_listener_ = td::actor::create_actor<td::TcpListener>(
td::actor::ActorOptions().with_name(PSLICE() << "TcpListener" << td::tag("port", port_)).with_poll(), port_,
std::make_unique<Callback>(actor_shared(this)));
}
void accept(td::SocketFd fd) {
callback_->accept(std::move(fd));
}
void hangup_shared() override {
refcnt_--;
tcp_listener_.reset();
if (close_flag_) {
if (refcnt_ == 0) {
stop();
}
} else {
alarm_timestamp() = td::Timestamp::in(5 /*5 seconds*/);
}
}
};
class AdnlExtServerImpl;
class AdnlInboundConnection : public AdnlExtConnection {
@ -150,7 +87,7 @@ class AdnlExtServerImpl : public AdnlExtServer {
td::actor::ActorId<AdnlPeerTable> peer_table_;
std::set<AdnlNodeIdShort> local_ids_;
std::set<td::uint16> ports_;
std::map<td::uint16, td::actor::ActorOwn<TcpInfiniteListener>> listeners_;
std::map<td::uint16, td::actor::ActorOwn<td::TcpInfiniteListener>> listeners_;
};
} // namespace adnl

View file

@ -393,8 +393,8 @@ class CoreActor : public CoreActorInterface {
clients_.emplace_back(ton::adnl::AdnlExtClient::create(ton::adnl::AdnlNodeIdFull{remote_public_key_},
remote_addr_, make_callback(0)));
}
daemon_ = MHD_start_daemon(MHD_USE_THREAD_PER_CONNECTION, static_cast<td::uint16>(http_port_), nullptr, nullptr,
&process_http_request, nullptr, MHD_OPTION_END);
daemon_ = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY, static_cast<td::uint16>(http_port_), nullptr, nullptr,
&process_http_request, nullptr, MHD_OPTION_THREAD_POOL_SIZE, 16, MHD_OPTION_END);
CHECK(daemon_ != nullptr);
}
};

24
common/int-to-string.hpp Normal file
View file

@ -0,0 +1,24 @@
#pragma once
#include "td/utils/int_types.h"
#include "td/utils/Slice.h"
namespace ton {
template <typename T>
typename std::enable_if_t<std::is_integral<T>::value, td::MutableSlice> store_int_to_slice(td::MutableSlice S,
const T &v) {
CHECK(S.size() >= sizeof(T));
S.copy_from(td::Slice(reinterpret_cast<const td::uint8 *>(&v), sizeof(T)));
return S.remove_prefix(sizeof(T));
}
template <typename T>
typename std::enable_if_t<std::is_integral<T>::value, T> fetch_int_from_slice(td::Slice S) {
CHECK(S.size() >= sizeof(T));
T v;
td::MutableSlice(reinterpret_cast<td::uint8 *>(&v), sizeof(T)).copy_from(S.truncate(sizeof(T)));
return v;
}
} // namespace ton

View file

@ -88,6 +88,7 @@ set(TON_CRYPTO_SOURCE
vm/cells/CellBuilder.cpp
vm/cells/CellHash.cpp
vm/cells/CellSlice.cpp
vm/cells/CellString.cpp
vm/cells/CellTraits.cpp
vm/cells/CellUsageTree.cpp
vm/cells/DataCell.cpp
@ -99,6 +100,7 @@ set(TON_CRYPTO_SOURCE
vm/cells/CellBuilder.h
vm/cells/CellHash.h
vm/cells/CellSlice.h
vm/cells/CellString.h
vm/cells/CellTraits.h
vm/cells/CellUsageTree.h
vm/cells/CellWithStorage.h
@ -197,6 +199,24 @@ set(BLOCK_SOURCE
block/transaction.h
)
set(SMC_ENVELOPE_SOURCE
smc-envelope/GenericAccount.cpp
smc-envelope/MultisigWallet.cpp
smc-envelope/SmartContract.cpp
smc-envelope/SmartContractCode.cpp
smc-envelope/TestGiver.cpp
smc-envelope/TestWallet.cpp
smc-envelope/Wallet.cpp
smc-envelope/GenericAccount.h
smc-envelope/MultisigWallet.h
smc-envelope/SmartContract.h
smc-envelope/SmartContractCode.h
smc-envelope/TestGiver.h
smc-envelope/TestWallet.h
smc-envelope/Wallet.h
)
set(ED25519_TEST_SOURCE
${CMAKE_CURRENT_SOURCE_DIR}/test/Ed25519.cpp
PARENT_SCOPE
@ -217,6 +237,11 @@ set(TONVM_TEST_SOURCE
PARENT_SCOPE
)
set(SMARTCONT_TEST_SOURCE
${CMAKE_CURRENT_SOURCE_DIR}/test/test-smartcont.cpp
PARENT_SCOPE
)
set(FIFT_TEST_SOURCE
${CMAKE_CURRENT_SOURCE_DIR}/test/fift.cpp
PARENT_SCOPE
@ -329,10 +354,18 @@ if (NOT CMAKE_CROSSCOMPILING)
GenFif(DEST smartcont/auto/highload-wallet-code SOURCE smartcont/highload-wallet-code.fc NAME highload-wallet)
GenFif(DEST smartcont/auto/highload-wallet-v2-code SOURCE smartcont/highload-wallet-v2-code.fc NAME highoad-wallet-v2)
GenFif(DEST smartcont/auto/elector-code SOURCE smartcont/elector-code.fc NAME elector-code)
GenFif(DEST smartcont/auto/multisig-code SOURCE smartcont/multisig-code.fc NAME multisig)
GenFif(DEST smartcont/auto/restricted-wallet-code SOURCE smartcont/restricted-wallet-code.fc NAME restricted-wallet)
GenFif(DEST smartcont/auto/restricted-wallet2-code SOURCE smartcont/restricted-wallet2-code.fc NAME restricted-wallet2)
GenFif(DEST smartcont/auto/simple-wallet-ext-code SOURCE smartcont/simple-wallet-ext-code.fc NAME simple-wallet-ext)
endif()
add_library(smc-envelope ${SMC_ENVELOPE_SOURCE})
target_include_directories(smc-envelope PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>)
target_link_libraries(smc-envelope PUBLIC ton_crypto PRIVATE tdutils ton_block)
add_dependencies(smc-envelope gen_fif)
add_executable(create-state block/create-state.cpp)
target_include_directories(create-state PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/..>)

View file

@ -551,6 +551,9 @@ validator_addr#73 public_key:SigPubKey weight:uint64 adnl_addr:bits256 = Validat
validators#11 utime_since:uint32 utime_until:uint32
total:(## 16) main:(## 16) { main <= total } { main >= 1 }
list:(Hashmap 16 ValidatorDescr) = ValidatorSet;
validators_ext#12 utime_since:uint32 utime_until:uint32
total:(## 16) main:(## 16) { main <= total } { main >= 1 }
total_weight:uint64 list:(HashmapE 16 ValidatorDescr) = ValidatorSet;
_ config_addr:bits256 = ConfigParam 0;
_ elector_addr:bits256 = ConfigParam 1;

View file

@ -221,8 +221,6 @@ td::Status check_account_proof(td::Slice proof, ton::BlockIdExt shard_blk, const
td::Result<AccountState::Info> AccountState::validate(ton::BlockIdExt ref_blk, block::StdAddress addr) const {
TRY_RESULT_PREFIX(root, vm::std_boc_deserialize(state.as_slice(), true), "cannot deserialize account state");
LOG(INFO) << "got account state for " << addr << " with respect to blocks " << blk.to_str()
<< (shard_blk == blk ? "" : std::string{" and "} + shard_blk.to_str());
if (blk != ref_blk && ref_blk.id.seqno != ~0U) {
return td::Status::Error(PSLICE() << "obtained getAccountState() for a different reference block " << blk.to_str()
<< " instead of requested " << ref_blk.to_str());

View file

@ -387,11 +387,25 @@ td::Result<std::unique_ptr<ValidatorSet>> Config::unpack_validator_set(Ref<vm::C
if (vset_root.is_null()) {
return td::Status::Error("validator set is absent");
}
gen::ValidatorSet::Record rec;
if (!tlb::unpack_cell(std::move(vset_root), rec)) {
return td::Status::Error("validator set is invalid");
gen::ValidatorSet::Record_validators_ext rec;
Ref<vm::Cell> dict_root;
if (!tlb::unpack_cell(vset_root, rec)) {
gen::ValidatorSet::Record_validators rec0;
if (!tlb::unpack_cell(std::move(vset_root), rec0)) {
return td::Status::Error("validator set is invalid");
}
rec.utime_since = rec0.utime_since;
rec.utime_until = rec0.utime_until;
rec.total = rec0.total;
rec.main = rec0.main;
dict_root = vm::Dictionary::construct_root_from(*rec0.list);
rec.total_weight = 0;
} else if (rec.total_weight) {
dict_root = rec.list->prefetch_ref();
} else {
return td::Status::Error("validator set cannot have zero total weight");
}
vm::Dictionary dict{vm::Dictionary::construct_root_from(*rec.list), 16};
vm::Dictionary dict{std::move(dict_root), 16};
td::BitArray<16> key_buffer;
auto last = dict.get_minmax_key(key_buffer.bits(), 16, true);
if (last.is_null() || (int)key_buffer.to_ulong() != rec.total - 1) {
@ -428,6 +442,9 @@ td::Result<std::unique_ptr<ValidatorSet>> Config::unpack_validator_set(Ref<vm::C
ptr->list.emplace_back(sig_pubkey.pubkey, descr.weight, ptr->total_weight, descr.adnl_addr);
ptr->total_weight += descr.weight;
}
if (rec.total_weight && rec.total_weight != ptr->total_weight) {
return td::Status::Error("validator set declares incorrect total weight");
}
return std::move(ptr);
}
@ -517,6 +534,58 @@ td::Result<std::vector<StoragePrices>> Config::get_storage_prices() const {
return std::move(res);
}
td::Result<GasLimitsPrices> Config::get_gas_limits_prices(bool is_masterchain) const {
GasLimitsPrices res;
auto id = is_masterchain ? 20 : 21;
auto cell = get_config_param(id);
if (cell.is_null()) {
return td::Status::Error(PSLICE() << "configuration parameter " << id << " with gas prices is absent");
}
auto cs = vm::load_cell_slice(std::move(cell));
block::gen::GasLimitsPrices::Record_gas_flat_pfx flat;
if (tlb::unpack(cs, flat)) {
cs = *flat.other;
res.flat_gas_limit = flat.flat_gas_limit;
res.flat_gas_price = flat.flat_gas_price;
}
auto f = [&](const auto& r, td::uint64 spec_limit) {
res.gas_limit = r.gas_limit;
res.special_gas_limit = spec_limit;
res.gas_credit = r.gas_credit;
res.gas_price = r.gas_price;
res.freeze_due_limit = r.freeze_due_limit;
res.delete_due_limit = r.delete_due_limit;
};
block::gen::GasLimitsPrices::Record_gas_prices_ext rec;
if (tlb::unpack(cs, rec)) {
f(rec, rec.special_gas_limit);
} else {
block::gen::GasLimitsPrices::Record_gas_prices rec0;
if (tlb::unpack(cs, rec0)) {
f(rec0, rec0.gas_limit);
} else {
return td::Status::Error(PSLICE() << "configuration parameter " << id
<< " with gas prices is invalid - can't parse");
}
}
return res;
}
td::Result<MsgPrices> Config::get_msg_prices(bool is_masterchain) const {
auto id = is_masterchain ? 24 : 25;
auto cell = get_config_param(id);
if (cell.is_null()) {
return td::Status::Error(PSLICE() << "configuration parameter " << id << " with msg prices is absent");
}
auto cs = vm::load_cell_slice(std::move(cell));
block::gen::MsgForwardPrices::Record rec;
if (!tlb::unpack(cs, rec)) {
return td::Status::Error(PSLICE() << "configuration parameter " << id
<< " with msg prices is invalid - can't parse");
}
return MsgPrices(rec.lump_price, rec.bit_price, rec.cell_price, rec.ihr_price_factor, rec.first_frac, rec.next_frac);
}
CatchainValidatorsConfig Config::unpack_catchain_validators_config(Ref<vm::Cell> cell) {
block::gen::CatchainConfig::Record cfg;
if (cell.is_null() || !tlb::unpack_cell(std::move(cell), cfg)) {

View file

@ -50,7 +50,7 @@ struct ValidatorDescr {
: pubkey(_pubkey), weight(_weight), cum_weight(_cum_weight) {
adnl_addr.set_zero();
}
bool operator<(td::uint64 wt_pos) const & {
bool operator<(td::uint64 wt_pos) const& {
return cum_weight < wt_pos;
}
};
@ -327,6 +327,46 @@ struct StoragePrices {
, mc_bit_price(_mc_bprice)
, mc_cell_price(_mc_cprice) {
}
static td::RefInt256 compute_storage_fees(ton::UnixTime now, const std::vector<block::StoragePrices>& pricing,
const vm::CellStorageStat& storage_stat, ton::UnixTime last_paid,
bool is_special, bool is_masterchain);
};
struct GasLimitsPrices {
td::uint64 flat_gas_limit{0};
td::uint64 flat_gas_price{0};
td::uint64 gas_price{0};
td::uint64 special_gas_limit{0};
td::uint64 gas_limit{0};
td::uint64 gas_credit{0};
td::uint64 block_gas_limit{0};
td::uint64 freeze_due_limit{0};
td::uint64 delete_due_limit{0};
td::RefInt256 compute_gas_price(td::uint64 gas_used) const;
};
// msg_fwd_fees = (lump_price + ceil((bit_price * msg.bits + cell_price * msg.cells)/2^16)) nanograms
// ihr_fwd_fees = ceil((msg_fwd_fees * ihr_price_factor)/2^16) nanograms
// bits in the root cell of a message are not included in msg.bits (lump_price pays for them)
struct MsgPrices {
td::uint64 lump_price;
td::uint64 bit_price;
td::uint64 cell_price;
td::uint32 ihr_factor;
td::uint32 first_frac;
td::uint32 next_frac;
td::uint64 compute_fwd_fees(td::uint64 cells, td::uint64 bits) const;
std::pair<td::uint64, td::uint64> compute_fwd_ihr_fees(td::uint64 cells, td::uint64 bits,
bool ihr_disabled = false) const;
MsgPrices() = default;
MsgPrices(td::uint64 lump, td::uint64 bitp, td::uint64 cellp, td::uint32 ihrf, td::uint32 firstf, td::uint32 nextf)
: lump_price(lump), bit_price(bitp), cell_price(cellp), ihr_factor(ihrf), first_frac(firstf), next_frac(nextf) {
}
td::RefInt256 get_first_part(td::RefInt256 total) const;
td::uint64 get_first_part(td::uint64 total) const;
td::RefInt256 get_next_part(td::RefInt256 total) const;
};
struct CatchainValidatorsConfig {
@ -499,6 +539,8 @@ class Config {
bool is_special_smartcontract(const ton::StdSmcAddress& addr) const;
static td::Result<std::unique_ptr<ValidatorSet>> unpack_validator_set(Ref<vm::Cell> valset_root);
td::Result<std::vector<StoragePrices>> get_storage_prices() const;
td::Result<GasLimitsPrices> get_gas_limits_prices(bool is_masterchain = false) const;
td::Result<MsgPrices> get_msg_prices(bool is_masterchain = false) const;
static CatchainValidatorsConfig unpack_catchain_validators_config(Ref<vm::Cell> cell);
CatchainValidatorsConfig get_catchain_validators_config() const;
td::Status visit_validator_params() const;

View file

@ -421,7 +421,9 @@ void add_partial_storage_payment(td::BigInt256& payment, ton::UnixTime delta, co
payment += b;
}
td::RefInt256 Account::compute_storage_fees(ton::UnixTime now, const std::vector<block::StoragePrices>& pricing) const {
td::RefInt256 StoragePrices::compute_storage_fees(ton::UnixTime now, const std::vector<block::StoragePrices>& pricing,
const vm::CellStorageStat& storage_stat, ton::UnixTime last_paid,
bool is_special, bool is_masterchain) {
if (now <= last_paid || !last_paid || is_special || pricing.empty() || now <= pricing[0].valid_since) {
return {};
}
@ -438,7 +440,7 @@ td::RefInt256 Account::compute_storage_fees(ton::UnixTime now, const std::vector
ton::UnixTime valid_until = (i < n - 1 ? std::min(now, pricing[i + 1].valid_since) : now);
if (upto < valid_until) {
assert(upto >= pricing[i].valid_since);
add_partial_storage_payment(total.unique_write(), valid_until - upto, pricing[i], storage_stat, is_masterchain());
add_partial_storage_payment(total.unique_write(), valid_until - upto, pricing[i], storage_stat, is_masterchain);
}
upto = valid_until;
}
@ -446,6 +448,10 @@ td::RefInt256 Account::compute_storage_fees(ton::UnixTime now, const std::vector
return total;
}
td::RefInt256 Account::compute_storage_fees(ton::UnixTime now, const std::vector<block::StoragePrices>& pricing) const {
return StoragePrices::compute_storage_fees(now, pricing, storage_stat, last_paid, is_special, is_masterchain());
}
Transaction::Transaction(const Account& _account, int ttype, ton::LogicalTime req_start_lt, ton::UnixTime _now,
Ref<vm::Cell> _inmsg)
: trans_type(ttype)
@ -969,7 +975,7 @@ bool Transaction::prepare_compute_phase(const ComputePhaseConfig& cfg) {
gas = vm.get_gas_limits();
cp.gas_used = std::min<long long>(gas.gas_consumed(), gas.gas_limit);
cp.accepted = (gas.gas_credit == 0);
cp.success = (cp.accepted && (unsigned)cp.exit_code <= 1);
cp.success = (cp.accepted && vm.committed());
if (cp.accepted & use_msg_state) {
was_activated = true;
acc_status = Account::acc_active;
@ -978,8 +984,8 @@ bool Transaction::prepare_compute_phase(const ComputePhaseConfig& cfg) {
<< ", limit=" << gas.gas_limit << ", credit=" << gas.gas_credit;
LOG(INFO) << "out_of_gas=" << cp.out_of_gas << ", accepted=" << cp.accepted << ", success=" << cp.success;
if (cp.success) {
cp.new_data = vm.get_c4(); // c4 -> persistent data
cp.actions = vm.get_d(5); // c5 -> action list
cp.new_data = vm.get_committed_state().c4; // c4 -> persistent data
cp.actions = vm.get_committed_state().c5; // c5 -> action list
int out_act_num = output_actions_count(cp.actions);
if (verbosity > 2) {
std::cerr << "new smart contract data: ";

View file

@ -65,10 +65,10 @@ struct NewOutMsg {
NewOutMsg(ton::LogicalTime _lt, Ref<vm::Cell> _msg, Ref<vm::Cell> _trans)
: lt(_lt), msg(std::move(_msg)), trans(std::move(_trans)) {
}
bool operator<(const NewOutMsg& other) const & {
bool operator<(const NewOutMsg& other) const& {
return lt < other.lt || (lt == other.lt && msg->get_hash() < other.msg->get_hash());
}
bool operator>(const NewOutMsg& other) const & {
bool operator>(const NewOutMsg& other) const& {
return lt > other.lt || (lt == other.lt && other.msg->get_hash() < msg->get_hash());
}
};
@ -132,29 +132,6 @@ struct ComputePhaseConfig {
bool parse_GasLimitsPrices(Ref<vm::Cell> cell, td::RefInt256& freeze_due_limit, td::RefInt256& delete_due_limit);
};
// msg_fwd_fees = (lump_price + ceil((bit_price * msg.bits + cell_price * msg.cells)/2^16)) nanograms
// ihr_fwd_fees = ceil((msg_fwd_fees * ihr_price_factor)/2^16) nanograms
// bits in the root cell of a message are not included in msg.bits (lump_price pays for them)
struct MsgPrices {
td::uint64 lump_price;
td::uint64 bit_price;
td::uint64 cell_price;
td::uint32 ihr_factor;
td::uint32 first_frac;
td::uint32 next_frac;
td::uint64 compute_fwd_fees(td::uint64 cells, td::uint64 bits) const;
std::pair<td::uint64, td::uint64> compute_fwd_ihr_fees(td::uint64 cells, td::uint64 bits,
bool ihr_disabled = false) const;
MsgPrices() = default;
MsgPrices(td::uint64 lump, td::uint64 bitp, td::uint64 cellp, td::uint32 ihrf, td::uint32 firstf, td::uint32 nextf)
: lump_price(lump), bit_price(bitp), cell_price(cellp), ihr_factor(ihrf), first_frac(firstf), next_frac(nextf) {
}
td::RefInt256 get_first_part(td::RefInt256 total) const;
td::uint64 get_first_part(td::uint64 total) const;
td::RefInt256 get_next_part(td::RefInt256 total) const;
};
struct ActionPhaseConfig {
int max_actions{255};
MsgPrices fwd_std;

View file

@ -224,6 +224,8 @@ class BitSliceGen {
BitSliceGen(BitSliceGen&& bs, unsigned _offs, unsigned _len);
BitSliceGen(Pt* _ptr, unsigned _len) : ref(), ptr(_ptr), offs(0), len(_len) {
}
explicit BitSliceGen(Slice slice) : BitSliceGen(slice.data(), slice.size() * 8) {
}
~BitSliceGen() {
}
Pt* get_ptr() const {

View file

@ -970,6 +970,7 @@ x{F4B7} @Defop SUBDICTURPGET
x{F800} @Defop ACCEPT
x{F801} @Defop SETGASLIMIT
x{F80F} @Defop COMMIT
x{F82} @Defop(4u) GETPARAM
x{F823} @Defop NOW

View file

@ -139,7 +139,7 @@ recursive append-long-bytes {
// ( S -- x ) parse public key
{ dup $len 48 <> abort"public key must be 48 characters long"
base64>B dup Blen 36 <> abort"public key must be 48 characters long"
base64url>B dup Blen 36 <> abort"public key must be 48 characters long"
34 B| 16 B>u@ over crc16 <> abort"crc16 mismatch in public key"
16 B>u@+ 0x3ee6 <> abort"invalid tag in public key"
256 B>u@

View file

@ -1,4 +1,5 @@
"Asm.fif" include
"TonUtil.fif" include
31 -1<< constant wc_undef
0 constant wc_base
@ -187,6 +188,7 @@ dictnew constant special-dict
// restricted wallet creation
"auto/wallet-code.fif" include =: WCode0
"auto/restricted-wallet-code.fif" include =: RWCode1
"auto/restricted-wallet2-code.fif" include =: RWCode2
@ -200,7 +202,7 @@ dictnew constant special-dict
0 // ticktock
2 // mode: create
register_smc
Masterchain 6 .Addr cr
Masterchain swap 6 .Addr cr
} : create-wallet1
// D x t -- D'
@ -225,5 +227,18 @@ dictnew constant special-dict
0 // ticktock
2 // mode: create
register_smc
Masterchain 6 .Addr cr
Masterchain swap 6 .Addr cr
} : create-wallet2
// pubkey amount
{ over ."Key " pubkey>$ type ." -> "
WCode0 // code
<b 1 32 u, 3 roll 256 u, b> // data
empty_cell // libs
3 roll // balance
0 // split_depth
0 // ticktock
2 // mode: create
register_smc
Masterchain swap 6 .Addr cr
} : create-wallet0

View file

@ -1,15 +1,32 @@
;; Simple configuration smart contract
() set_conf_param(int index, cell value) impure {
var cs = begin_parse(get_data());
var cs = get_data().begin_parse();
var cfg_dict = cs~load_ref();
cfg_dict~idict_set_ref(32, index, value);
set_data(begin_cell().store_ref(cfg_dict).store_slice(cs).end_cell());
}
(cell, int, int, cell) load_data() inline {
var cs = get_data().begin_parse();
var (cfg_dict, stored_seqno, public_key) = (cs~load_ref(), cs~load_uint(32), cs~load_uint(256));
var vote_dict = cs.slice_empty?() ? new_dict() : cs~load_dict();
cs.end_parse();
return (cfg_dict, stored_seqno, public_key, vote_dict);
}
() store_data(cfg_dict, stored_seqno, public_key, vote_dict) impure inline {
set_data(begin_cell()
.store_ref(cfg_dict)
.store_uint(stored_seqno, 32)
.store_uint(public_key, 256)
.store_dict(vote_dict)
.end_cell());
}
(int, int) check_validator_set(cell vset) {
var cs = vset.begin_parse();
throw_unless(9, cs~load_uint(8) == 0x11); ;; validators#11
throw_if(9, (cs~load_uint(8) - 0x11) & -2); ;; validators#11 or validators_ext#12
int utime_since = cs~load_uint(32);
int utime_until = cs~load_uint(32);
int total = cs~load_uint(16);
@ -86,6 +103,83 @@
.end_cell(), 0);
}
() after_code_upgrade(slice param, cell old_code) impure method_id(1666) {
}
_ perform_action(cfg_dict, public_key, action, cs) {
if (action == 0x43665021) {
;; change one configuration parameter
var param_index = cs~load_uint(32);
var param_value = cs~load_ref();
cs.end_parse();
cfg_dict~idict_set_ref(32, param_index, param_value);
return (cfg_dict, public_key);
} elseif (action == 0x4e436f64) {
;; change configuration smart contract code
var new_code = cs~load_ref();
set_code(new_code);
var old_code = get_c3();
set_c3(new_code);
after_code_upgrade(cs, old_code);
throw(0);
return (cfg_dict, public_key);
} elseif (action == 0x50624b21) {
;; change configuration master public key
public_key = cs~load_uint(256);
cs.end_parse();
return (cfg_dict, public_key);
} elseif (action == 0x4e43ef05) {
;; change election smart contract code
change_elector_code(cs);
return (cfg_dict, public_key);
} else {
throw_if(32, action);
return (cfg_dict, public_key);
}
}
slice get_validator_descr(int idx) inline_ref {
var vset = config_param(34);
if (vset.null?()) {
return null();
}
var cs = begin_parse(vset);
cs~skip_bits(8 + 32 + 32 + 16 + 16);
var dict = begin_cell().store_slice(cs).end_cell();
var (value, _) = dict.udict_get?(16, idx);
return value;
}
(int, int) unpack_validator_descr(slice cs) inline {
;; ed25519_pubkey#8e81278a pubkey:bits256 = SigPubKey;
;; validator#53 public_key:SigPubKey weight:uint64 = ValidatorDescr;
;; validator_addr#73 public_key:SigPubKey weight:uint64 adnl_addr:bits256 = ValidatorDescr;
throw_unless(41, (cs~load_uint(8) & ~ 0x20) == 0x53);
throw_unless(41, cs~load_uint(32) == 0x8e81278a);
return (cs~load_uint(256), cs~load_uint(64));
}
slice create_new_entry(cs) inline {
return begin_cell().store_int(false, 1).store_uint(0, 64).store_uint(0, 256).store_slice(cs).end_cell().begin_parse();
}
cell register_vote(vote_dict, action, cs, idx, weight) {
int hash = 0;
var entry = null();
if (action & 1) {
hash = slice_hash(cs);
(entry, var found?) = vote_dict.udict_get?(256, hash);
ifnot (found?) {
entry = create_new_entry(cs);
}
} else {
hash = cs.preload_uint(256);
(entry, var found?) = vote_dict.udict_get?(256, hash);
throw_unless(42, found?);
}
return vote_dict;
}
() recv_external(slice in_msg) impure {
var signature = in_msg~load_bits(512);
var cs = in_msg;
@ -93,36 +187,28 @@
int msg_seqno = cs~load_uint(32);
var valid_until = cs~load_uint(32);
throw_if(35, valid_until < now());
var cs2 = begin_parse(get_data());
var cfg_dict = cs2~load_ref();
var stored_seqno = cs2~load_uint(32);
var public_key = cs2~load_uint(256);
cs2.end_parse();
var (cfg_dict, stored_seqno, public_key, vote_dict) = load_data();
throw_unless(33, msg_seqno == stored_seqno);
ifnot ((action - 0x566f7465) & -2) {
var idx = cs~load_uint(16);
var vdescr = get_validator_descr(idx);
var (val_pubkey, weight) = unpack_validator_descr(vdescr);
throw_unless(34, check_signature(slice_hash(in_msg), signature, val_pubkey));
accept_message();
stored_seqno += 1;
store_data(cfg_dict, stored_seqno, public_key, vote_dict);
commit();
vote_dict = register_vote(vote_dict, action, cs, idx, weight);
store_data(cfg_dict, stored_seqno, public_key, vote_dict);
return ();
}
throw_unless(34, check_signature(slice_hash(in_msg), signature, public_key));
accept_message();
if (action == 0x43665021) {
;; change one configuration parameter
var param_index = cs~load_uint(32);
var param_value = cs~load_ref();
cs.end_parse();
cfg_dict~idict_set_ref(32, param_index, param_value);
} elseif (action == 0x4e436f64) {
;; change configuration smart contract code
var new_code = cs~load_ref();
cs.end_parse();
set_code(new_code);
} elseif (action == 0x50624b21) {
;; change configuration master public key
public_key = cs~load_uint(256);
cs.end_parse();
} elseif (action == 0x4e43ef05) {
;; change election smart contract code
change_elector_code(cs);
} else {
throw_if(32, action);
}
set_data(begin_cell().store_ref(cfg_dict).store_uint(stored_seqno + 1, 32).store_uint(public_key, 256).end_cell());
stored_seqno += 1;
store_data(cfg_dict, stored_seqno, public_key, vote_dict);
commit();
(cfg_dict, public_key) = perform_action(cfg_dict, public_key, action, cs);
store_data(cfg_dict, stored_seqno, public_key, vote_dict);
}
() run_ticktock(int is_tock) impure {

View file

@ -408,7 +408,7 @@ _ compute_total_stake(l, n, m_stake) {
return tot_stake;
}
(cell, cell, cell, int, int) try_elect(credits, members, min_stake, max_stake, min_total_stake, max_stake_factor) {
(cell, cell, int, cell, int, int) try_elect(credits, members, min_stake, max_stake, min_total_stake, max_stake_factor) {
var cs = 16.config_param().begin_parse();
var (max_validators, _, min_validators) = (cs~load_uint(16), cs~load_uint(16), cs~load_uint(16));
cs.end_parse();
@ -435,7 +435,7 @@ _ compute_total_stake(l, n, m_stake) {
} until (~ f);
n = min(n, max_validators);
if (n < min_validators) {
return (credits, new_dict(), new_dict(), 0, 0);
return (credits, new_dict(), 0, new_dict(), 0, 0);
}
var l = nil;
do {
@ -464,7 +464,7 @@ _ compute_total_stake(l, n, m_stake) {
}
} until (i >= n);
if ((m == 0) | (best_stake < min_total_stake)) {
return (credits, new_dict(), new_dict(), 0, 0);
return (credits, new_dict(), 0, new_dict(), 0, 0);
}
;; we have to select first m validators from list l
l1 = touch(l);
@ -476,6 +476,7 @@ _ compute_total_stake(l, n, m_stake) {
;; create both the new validator set and the refund set
int i = 0;
var tot_stake = 0;
var tot_weight = 0;
var vset = new_dict();
var frozen = new_dict();
do {
@ -492,6 +493,7 @@ _ compute_total_stake(l, n, m_stake) {
;; validator_addr#73 public_key:SigPubKey weight:uint64 adnl_addr:bits256 = ValidatorDescr;
var weight = (true_stake << 60) / best_stake;
tot_stake += true_stake;
tot_weight += weight;
var vinfo = begin_cell()
.store_uint(adnl_addr ? 0x73 : 0x53, 8) ;; validator_addr#73 or validator#53
.store_uint(0x8e81278a, 32) ;; ed25519_pubkey#8e81278a
@ -514,7 +516,7 @@ _ compute_total_stake(l, n, m_stake) {
i += 1;
} until (l.null?());
throw_unless(49, tot_stake == best_stake);
return (credits, vset, frozen, tot_stake, m);
return (credits, vset, tot_weight, frozen, tot_stake, m);
}
int conduct_elections(ds, elect, credits) impure {
@ -545,7 +547,7 @@ int conduct_elections(ds, elect, credits) impure {
;; elections finished
return false;
}
(credits, var vdict, var frozen, var total_stakes, var cnt) = try_elect(credits, members, min_stake, max_stake, min_total_stake, max_stake_factor);
(credits, var vdict, var total_weight, var frozen, var total_stakes, var cnt) = try_elect(credits, members, min_stake, max_stake, min_total_stake, max_stake_factor);
;; pack elections; if cnt==0, set failed=true, finished=false.
failed = (cnt == 0);
finished = ~ failed;
@ -561,12 +563,13 @@ int conduct_elections(ds, elect, credits) impure {
var start = max(now() + elect_end_before - 60, elect_at);
var main_validators = config_param(16).begin_parse().skip_bits(16).preload_uint(16);
var vset = begin_cell()
.store_uint(0x11, 8) ;; validators#11
.store_uint(0x12, 8) ;; validators_ext#12
.store_uint(start, 32) ;; utime_since:uint32
.store_uint(start + elect_for, 32) ;; utime_until:uint32
.store_uint(cnt, 16) ;; total:(## 16)
.store_uint(min(cnt, main_validators), 16) ;; main:(## 16)
.store_slice(vdict.begin_parse()) ;; list:(Hashmap 16 ValidatorDescr)
.store_uint(min(cnt, main_validators), 16) ;; main:(## 16)
.store_uint(total_weight, 64) ;; total_weight:uint64
.store_dict(vdict) ;; list:(HashmapE 16 ValidatorDescr)
.end_cell();
var config_addr = config_param(0).begin_parse().preload_uint(256);
send_validator_set_to_config(config_addr, vset, elect_at);

View file

@ -225,6 +225,22 @@ Masterchain swap
"config-master" +suffix +".addr" save-address-verbose
// Other data
/*
*
* Initial wallets (test)
*
*/
// pubkey amount `create-wallet1` or pubkey amount `create-wallet2`
PK'PuZPPXK5Rff9SvtoS7Y9lUuEixvy-J6aishYFj3Qn6P0pJMb GR$100000000 create-wallet1
PK'PuYiB1zAWzr4p8j6I681+sGUrRGcn6Ylf7vXl0xaUl/w6Xfg GR$1700000000 create-wallet0
/*
*
* Create state
*
*/
create_state
cr cr ."new state is:" cr dup <s csr. cr
dup 31 boc+>B dup Bx. cr

View file

@ -0,0 +1,265 @@
;; Simple wallet smart contract
_ unpack_state() inline_ref {
var ds = begin_parse(get_data());
var res = (ds~load_uint(8), ds~load_uint(8), ds~load_uint(64), ds~load_dict(), ds~load_dict());
ds.end_parse();
return res;
}
_ pack_state(cell pending_queries, cell public_keys, int last_cleaned, int k, int n) inline_ref {
return begin_cell()
.store_uint(n, 8)
.store_uint(k, 8)
.store_uint(last_cleaned, 64)
.store_dict(public_keys)
.store_dict(pending_queries)
.end_cell();
}
(int, int) check_signatures(cell public_keys, cell signatures, int hash, int cnt_bits) inline_ref {
int cnt = 0;
do {
slice cs = signatures.begin_parse();
slice signature = cs~load_bits(512);
int i = cs~load_uint(8);
signatures = cs~load_dict();
(slice public_key, var found?) = public_keys.udict_get?(8, i);
throw_unless(37, found?);
throw_unless(38, check_signature(hash, signature, public_key.preload_uint(256)));
int mask = (1 << i);
int old_cnt_bits = cnt_bits;
cnt_bits |= mask;
int should_check = cnt_bits != old_cnt_bits;
cnt -= should_check;
} until (cell_null?(signatures));
return (cnt, cnt_bits);
}
() recv_internal(slice in_msg) impure {
;; do nothing for internal messages
}
(int, int, slice) unpack_query_data(slice in_msg, int n, slice query, var found?) inline_ref {
if (found?) {
throw_unless(35, query~load_int(1));
(int cnt, int cnt_bits, slice msg) = (query~load_uint(8), query~load_uint(n), query);
throw_unless(36, slice_hash(msg) == slice_hash(in_msg));
return (cnt, cnt_bits, msg);
}
return (0, 0, in_msg);
}
() try_init() impure inline_ref {
;; first query without signatures is always accepted
(int n, int k, int last_cleaned, cell public_keys, cell pending_queries) = unpack_state();
throw_if(37, last_cleaned);
accept_message();
set_data(pack_state(pending_queries, public_keys, 1, k, n));
}
cell update_pending_queries(cell pending_queries, slice msg, int query_id, int cnt, int cnt_bits, int n, int k) impure inline_ref {
if (cnt >= k) {
while (msg.slice_refs()) {
var mode = msg~load_uint(8);
send_raw_message(msg~load_ref(), mode);
}
pending_queries~udict_set_builder(64, query_id, begin_cell().store_int(0, 1));
} else {
pending_queries~udict_set_builder(64, query_id, begin_cell()
.store_uint(1, 1)
.store_uint(cnt, 8)
.store_uint(cnt_bits, n)
.store_slice(msg));
}
return pending_queries;
}
() recv_external(slice in_msg) impure {
;; empty message triggers init
if (slice_empty?(in_msg)) {
return try_init();
}
;; Check root signature
slice root_signature = in_msg~load_bits(512);
int root_hash = slice_hash(in_msg);
int root_i = in_msg~load_uint(8);
(int n, int k, int last_cleaned, cell public_keys, cell pending_queries) = unpack_state();
last_cleaned -= last_cleaned == 0;
(slice public_key, var found?) = public_keys.udict_get?(8, root_i);
throw_unless(31, found?);
throw_unless(32, check_signature(root_hash, root_signature, public_key.preload_uint(256)));
cell signatures = in_msg~load_dict();
var hash = slice_hash(in_msg);
int query_id = in_msg~load_uint(64);
var bound = (now() << 32);
throw_if(33, query_id < bound);
(slice query, var found?) = pending_queries.udict_get?(64, query_id);
(int cnt, int cnt_bits, slice msg) = unpack_query_data(in_msg, n, query, found?);
int mask = 1 << root_i;
throw_if(34, cnt_bits & mask);
cnt_bits |= mask;
cnt += 1;
;; TODO: reserve some gas or FAIL
accept_message();
pending_queries = update_pending_queries(pending_queries, msg, query_id, cnt, cnt_bits, n, k);
set_data(pack_state(pending_queries, public_keys, last_cleaned, k, n));
commit();
int need_save = 0;
ifnot (cell_null?(signatures) | (cnt >= k)) {
(int new_cnt, cnt_bits) = check_signatures(public_keys, signatures, hash, cnt_bits);
cnt += new_cnt;
pending_queries = update_pending_queries(pending_queries, msg, query_id, cnt, cnt_bits, n, k);
need_save = -1;
}
bound -= (64 << 32); ;; clean up records expired more than 64 seconds ago
int old_last_cleaned = last_cleaned;
do {
var (pending_queries', i, _, f) = pending_queries.udict_delete_get_min(64);
f~touch();
if (f) {
f = (i < bound);
}
if (f) {
pending_queries = pending_queries';
last_cleaned = i;
need_save = -1;
}
} until (~ f);
if (need_save) {
set_data(pack_state(pending_queries, public_keys, last_cleaned, k, n));
}
}
;; Get methods
;; returns -1 for processed queries, 0 for unprocessed, 1 for unknown (forgotten)
(int, int) get_query_state(int query_id) method_id {
(int n, _, int last_cleaned, _, cell pending_queries) = unpack_state();
(slice cs, var found) = pending_queries.udict_get?(64, query_id);
if (found) {
if (cs~load_int(1)) {
cs~load_uint(8);
return (0, cs~load_uint(n));
} else {
return (-1, 0);
}
} else {
return (-(query_id <= last_cleaned), 0);
}
}
int processed?(int query_id) method_id {
(int x, _) = get_query_state(query_id);
return x;
}
cell create_init_state(int n, int k, cell public_keys) method_id {
return pack_state(new_dict(), public_keys, 0, k, n);
}
cell merge_list(cell a, cell b) {
if (cell_null?(a)) {
return b;
}
if (cell_null?(b)) {
return a;
}
slice as = a.begin_parse();
if (as.slice_refs() != 0) {
cell tail = merge_list(as~load_ref(), b);
return begin_cell().store_slice(as).store_ref(tail).end_cell();
}
as~skip_last_bits(1);
;; as~skip_bits(1);
return begin_cell().store_slice(as).store_dict(b).end_cell();
}
cell get_public_keys() method_id {
(_, _, _, cell public_keys, _) = unpack_state();
return public_keys;
}
(int, int) check_query_signatures(cell query) method_id {
slice cs = query.begin_parse();
slice root_signature = cs~load_bits(512);
int root_hash = slice_hash(cs);
int root_i = cs~load_uint(8);
cell public_keys = get_public_keys();
(slice public_key, var found?) = public_keys.udict_get?(8, root_i);
throw_unless(31, found?);
throw_unless(32, check_signature(root_hash, root_signature, public_key.preload_uint(256)));
int mask = 1 << root_i;
cell signatures = cs~load_dict();
if (cell_null?(signatures)) {
return (1, mask);
}
(int cnt, mask) = check_signatures(public_keys, signatures, slice_hash(cs), mask);
return (cnt + 1, mask);
}
cell messages_by_mask(int mask) method_id {
(int n, _, _, _, cell pending_queries) = unpack_state();
int i = -1;
cell a = new_dict();
do {
(i, var cs, var f) = pending_queries.udict_get_next?(64, i);
if (f) {
if (cs~load_int(1)) {
int cnt_bits = cs.skip_bits(8).preload_uint(n);
if (cnt_bits & mask) {
a~udict_set_builder(64, i, begin_cell().store_slice(cs));
}
}
}
} until (~ f);
return a;
}
cell get_messages_unsigned_by_id(int id) method_id {
return messages_by_mask(1 << id);
}
cell get_messages_unsigned() method_id {
return messages_by_mask(~ 0);
}
(int, int) get_n_k() method_id {
(int n, int k, _, _, _) = unpack_state();
return (n, k);
}
cell merge_inner_queries(cell a, cell b) method_id {
slice ca = a.begin_parse();
slice cb = b.begin_parse();
cell list_a = ca~load_dict();
cell list_b = cb~load_dict();
throw_unless(31, slice_hash(ca) == slice_hash(cb));
return begin_cell()
.store_dict(merge_list(list_a, list_b))
.store_slice(ca)
.end_cell();
}

View file

@ -0,0 +1,67 @@
;; Simple wallet smart contract
cell create_state(int seqno, int public_key) {
return begin_cell().store_uint(seqno, 32).store_uint(public_key, 256).end_cell();
}
(int, int) load_state() {
var cs2 = begin_parse(get_data());
return (cs2~load_uint(32), cs2~load_uint(256));
}
() save_state(int seqno, int public_key) impure {
set_data(create_state(seqno, public_key));
}
() recv_internal(slice in_msg) impure {
;; do nothing for internal messages
}
slice do_verify_message(slice in_msg, int seqno, int public_key) {
var signature = in_msg~load_bits(512);
var cs = in_msg;
int msg_seqno = cs~load_uint(32);
throw_unless(33, msg_seqno == seqno);
throw_unless(34, check_signature(slice_hash(in_msg), signature, public_key));
return cs;
}
() recv_external(slice in_msg) impure {
(int stored_seqno, int public_key) = load_state();
var cs = do_verify_message(in_msg, stored_seqno, public_key);
accept_message();
cs~touch_slice();
if (cs.slice_refs()) {
var mode = cs~load_uint(8);
send_raw_message(cs~load_ref(), mode);
}
cs.end_parse();
save_state(stored_seqno + 1, public_key);
}
;; Get methods
int seqno() method_id {
return get_data().begin_parse().preload_uint(32);
}
cell create_init_state(int public_key) method_id {
return create_state(0, public_key);
}
cell prepare_send_message_with_seqno(int mode, cell msg, int seqno) method_id {
return begin_cell().store_uint(seqno, 32).store_uint(mode, 8).store_ref(msg).end_cell();
}
cell prepare_send_message(int mode, cell msg) method_id {
return prepare_send_message_with_seqno(mode, msg, seqno());
}
slice verify_message(slice msg) method_id {
var (stored_seqno, public_key) = load_state();
return do_verify_message(msg, stored_seqno, public_key);
}

View file

@ -16,6 +16,7 @@ forall X -> X first(tuple t) asm "FIRST";
forall X -> X second(tuple t) asm "SECOND";
forall X -> X third(tuple t) asm "THIRD";
forall X -> X fourth(tuple t) asm "3 INDEX";
forall X -> X null() asm "PUSHNULL";
int now() asm "NOW";
slice my_address() asm "MYADDR";
@ -34,8 +35,10 @@ int check_data_signature(slice data, slice signature, int public_key) asm "CHKSI
cell get_data() asm "c4 PUSH";
() set_data(cell c) impure asm "c4 POP";
cell get_c3() impure asm "c3 PUSH";
() set_c3(cell c) impure asm "c3 POP";
() accept_message() impure asm "ACCEPT";
() commit() impure asm "COMMIT";
int min(int x, int y) asm "MIN";
int max(int x, int y) asm "MAX";
@ -52,7 +55,10 @@ cell preload_ref(slice s) asm "PLDREF";
;; slice preload_bits(slice s, int len) asm "PLDSLICEX";
(slice, int) load_grams(slice s) asm( -> 1 0) "LDGRAMS";
slice skip_bits(slice s, int len) asm "SDSKIPFIRST";
(slice, ()) ~skip_bits(slice s, int len) asm "SDSKIPFIRST";
slice first_bits(slice s, int len) asm "SDCUTFIRST";
slice skip_last_bits(slice s, int len) asm "SDSKIPLAST";
(slice, ()) ~skip_last_bits(slice s, int len) asm "SDSKIPLAST";
(slice, cell) load_dict(slice s) asm( -> 1 0) "LDDICT";
cell preload_dict(slice s) asm "PLDDICT";
slice skip_dict(slice s) asm "SKIPDICT";
@ -94,6 +100,16 @@ cell udict_get_ref(cell dict, int key_len, int index) asm(index dict key_len) "D
(cell, slice, int) udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT";
(cell, (slice, int)) ~idict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTIDELGET" "NULLSWAPIFNOT";
(cell, (slice, int)) ~udict_delete_get?(cell dict, int key_len, int index) asm(index dict key_len) "DICTUDELGET" "NULLSWAPIFNOT";
cell udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET";
(cell, ()) ~udict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUSET";
cell idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET";
(cell, ()) ~idict_set(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTISET";
cell dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET";
(cell, ()) ~dict_set(cell dict, int key_len, slice index, slice value) asm(value index dict key_len) "DICTSET";
(cell, int) udict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUADD";
(cell, int) udict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTUREPLACE";
(cell, int) idict_add?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIADD";
(cell, int) idict_replace?(cell dict, int key_len, int index, slice value) asm(value index dict key_len) "DICTIREPLACE";
cell udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB";
(cell, ()) ~udict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTUSETB";
cell idict_set_builder(cell dict, int key_len, int index, builder value) asm(value index dict key_len) "DICTISETB";

View file

@ -0,0 +1,99 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "GenericAccount.h"
#include "block/block-auto.h"
#include "block/block-parse.h"
namespace ton {
td::Ref<vm::Cell> GenericAccount::get_init_state(td::Ref<vm::Cell> code, td::Ref<vm::Cell> data) noexcept {
return vm::CellBuilder()
.store_zeroes(2)
.store_ones(2)
.store_zeroes(1)
.store_ref(std::move(code))
.store_ref(std::move(data))
.finalize();
}
block::StdAddress GenericAccount::get_address(ton::WorkchainId workchain_id,
const td::Ref<vm::Cell>& init_state) noexcept {
return block::StdAddress(workchain_id, init_state->get_hash().bits(), true /*bounce*/);
}
void GenericAccount::store_int_message(vm::CellBuilder& cb, const block::StdAddress& dest_address, td::int64 gramms) {
td::BigInt256 dest_addr;
dest_addr.import_bits(dest_address.addr.as_bitslice());
cb.store_zeroes(1)
.store_ones(1)
.store_long(dest_address.bounceable, 1)
.store_zeroes(3)
.store_ones(1)
.store_zeroes(2)
.store_long(dest_address.workchain, 8)
.store_int256(dest_addr, 256);
block::tlb::t_Grams.store_integer_value(cb, td::BigInt256(gramms));
cb.store_zeroes(9 + 64 + 32 + 1 + 1);
}
td::Ref<vm::Cell> GenericAccount::create_ext_message(const block::StdAddress& address, td::Ref<vm::Cell> new_state,
td::Ref<vm::Cell> body) noexcept {
block::gen::Message::Record message;
/*info*/ {
block::gen::CommonMsgInfo::Record_ext_in_msg_info info;
/* src */
tlb::csr_pack(info.src, block::gen::MsgAddressExt::Record_addr_none{});
/* dest */ {
block::gen::MsgAddressInt::Record_addr_std dest;
dest.anycast = vm::CellBuilder().store_zeroes(1).as_cellslice_ref();
dest.workchain_id = address.workchain;
dest.address = address.addr;
tlb::csr_pack(info.dest, dest);
}
/* import_fee */ {
vm::CellBuilder cb;
block::tlb::t_Grams.store_integer_value(cb, td::BigInt256(0));
info.import_fee = cb.as_cellslice_ref();
}
tlb::csr_pack(message.info, info);
}
/* init */ {
if (new_state.not_null()) {
// Just(Left(new_state))
message.init = vm::CellBuilder()
.store_ones(1)
.store_zeroes(1)
.append_cellslice(vm::load_cell_slice(new_state))
.as_cellslice_ref();
} else {
message.init = vm::CellBuilder().store_zeroes(1).as_cellslice_ref();
CHECK(message.init.not_null());
}
}
/* body */ {
message.body = vm::CellBuilder().store_zeroes(1).append_cellslice(vm::load_cell_slice_ref(body)).as_cellslice_ref();
}
td::Ref<vm::Cell> res;
tlb::type_pack_cell(res, block::gen::t_Message_Any, message);
CHECK(res.not_null());
return res;
}
} // namespace ton

View file

@ -0,0 +1,31 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "vm/cells.h"
#include "block/block.h"
namespace ton {
class GenericAccount {
public:
static td::Ref<vm::Cell> get_init_state(td::Ref<vm::Cell> code, td::Ref<vm::Cell> data) noexcept;
static block::StdAddress get_address(ton::WorkchainId workchain_id, const td::Ref<vm::Cell>& init_state) noexcept;
static td::Ref<vm::Cell> create_ext_message(const block::StdAddress& address, td::Ref<vm::Cell> new_state,
td::Ref<vm::Cell> body) noexcept;
static void store_int_message(vm::CellBuilder& cb, const block::StdAddress& dest_address, td::int64 gramms);
};
} // namespace ton

View file

@ -0,0 +1,171 @@
#include "MultisigWallet.h"
#include "SmartContractCode.h"
#include "vm/dict.h"
#include "td/utils/misc.h"
namespace ton {
MultisigWallet::QueryBuilder::QueryBuilder(td::int64 query_id, td::Ref<vm::Cell> msg, int mode) {
msg_ = vm::CellBuilder().store_long(query_id, 64).store_long(mode, 8).store_ref(std::move(msg)).finalize();
}
void MultisigWallet::QueryBuilder::sign(td::int32 id, td::Ed25519::PrivateKey& pk) {
CHECK(id < td::narrow_cast<td::int32>(mask_.size()));
auto signature = pk.sign(msg_->get_hash().as_slice()).move_as_ok();
mask_.set(id);
vm::CellBuilder cb;
cb.store_bytes(signature.as_slice());
cb.store_long(id, 8);
cb.ensure_throw(cb.store_maybe_ref(std::move(dict_)));
dict_ = cb.finalize();
}
td::Ref<vm::Cell> MultisigWallet::QueryBuilder::create_inner() const {
vm::CellBuilder cb;
cb.ensure_throw(cb.store_maybe_ref(dict_));
return cb.append_cellslice(vm::load_cell_slice(msg_)).finalize();
}
td::Ref<vm::Cell> MultisigWallet::QueryBuilder::create(td::int32 id, td::Ed25519::PrivateKey& pk) const {
auto cell = create_inner();
vm::CellBuilder cb;
cb.store_long(id, 8);
cb.append_cellslice(vm::load_cell_slice(cell));
cell = cb.finalize();
auto signature = pk.sign(cell->get_hash().as_slice()).move_as_ok();
vm::CellBuilder cb2;
cb2.store_bytes(signature.as_slice());
cb2.append_cellslice(vm::load_cell_slice(cell));
return cb2.finalize();
}
td::Ref<MultisigWallet> MultisigWallet::create(td::Ref<vm::Cell> data) {
return td::Ref<MultisigWallet>(true, State{ton::SmartContractCode::multisig(), std::move(data)});
}
int MultisigWallet::processed(td::uint64 query_id) const {
auto res = run_get_method("processed?", {td::make_refint(query_id)});
return res.stack.write().pop_smallint_range(1, -1);
}
MultisigWallet::QueryState MultisigWallet::get_query_state(td::uint64 query_id) const {
auto ans = run_get_method("get_query_state", {td::make_refint(query_id)});
auto mask = ans.stack.write().pop_int();
auto state = ans.stack.write().pop_smallint_range(1, -1);
QueryState res;
if (state == 1) {
res.state = QueryState::Unknown;
} else if (state == 0) {
res.state = QueryState::NotReady;
for (size_t i = 0; i < res.mask.size(); i++) {
if (mask->get_bit(static_cast<int>(i))) {
res.mask.set(i);
}
}
} else {
res.state = QueryState::Sent;
}
return res;
}
std::vector<td::SecureString> MultisigWallet::get_public_keys() const {
auto ans = run_get_method("get_public_keys");
auto dict_root = ans.stack.write().pop_cell();
vm::Dictionary dict(std::move(dict_root), 8);
std::vector<td::SecureString> res;
dict.check_for_each([&](auto cs, auto x, auto y) {
td::SecureString key(32);
cs->prefetch_bytes(key.as_mutable_slice().ubegin(), td::narrow_cast<int>(key.size()));
res.push_back(std::move(key));
return true;
});
return res;
}
td::Ref<vm::Cell> MultisigWallet::create_init_data(std::vector<td::SecureString> public_keys, int k) const {
vm::Dictionary pk(8);
for (size_t i = 0; i < public_keys.size(); i++) {
auto key = pk.integer_key(td::make_refint(i), 8, false);
pk.set_builder(key.bits(), 8, vm::CellBuilder().store_bytes(public_keys[i].as_slice()));
}
auto res = run_get_method("create_init_state",
{td::make_refint(public_keys.size()), td::make_refint(k), pk.get_root_cell()});
CHECK(res.code == 0);
return res.stack.write().pop_cell();
}
td::Ref<vm::Cell> MultisigWallet::create_init_data_fast(std::vector<td::SecureString> public_keys, int k) {
vm::Dictionary pk(8);
for (size_t i = 0; i < public_keys.size(); i++) {
auto key = pk.integer_key(td::make_refint(i), 8, false);
pk.set_builder(key.bits(), 8, vm::CellBuilder().store_bytes(public_keys[i].as_slice()));
}
vm::CellBuilder cb;
cb.store_long(public_keys.size(), 8).store_long(k, 8).store_long(0, 64);
cb.ensure_throw(cb.store_maybe_ref(pk.get_root_cell()));
cb.ensure_throw(cb.store_maybe_ref({}));
return cb.finalize();
}
td::Ref<vm::Cell> MultisigWallet::merge_queries(td::Ref<vm::Cell> a, td::Ref<vm::Cell> b) const {
auto res = run_get_method("merge_queries", {a, b});
return res.stack.write().pop_cell();
}
MultisigWallet::Mask MultisigWallet::to_mask(td::RefInt256 mask) const {
Mask res_mask;
for (size_t i = 0; i < res_mask.size(); i++) {
if (mask->get_bit(static_cast<int>(i))) {
res_mask.set(i);
}
}
return res_mask;
}
std::pair<int, MultisigWallet::Mask> MultisigWallet::check_query_signatures(td::Ref<vm::Cell> a) const {
auto ans = run_get_method("check_query_signatures", {a});
auto mask = ans.stack.write().pop_int();
auto cnt = ans.stack.write().pop_smallint_range(128);
return std::make_pair(cnt, to_mask(mask));
}
std::pair<int, int> MultisigWallet::get_n_k() const {
auto ans = run_get_method("get_n_k");
auto k = ans.stack.write().pop_smallint_range(128);
auto n = ans.stack.write().pop_smallint_range(128);
return std::make_pair(n, k);
}
std::vector<MultisigWallet::Message> MultisigWallet::get_unsigned_messaged(int id) const {
SmartContract::Answer ans;
if (id == -1) {
ans = run_get_method("get_messages_unsigned");
} else {
ans = run_get_method("get_messages_unsigned_by_id", {td::make_refint(id)});
}
auto n_k = get_n_k();
auto cell = ans.stack.write().pop_maybe_cell();
vm::Dictionary dict(std::move(cell), 64);
std::vector<Message> res;
dict.check_for_each([&](auto cs, auto ptr, auto ptr_bits) {
cs.write().skip_first(8);
Message message;
td::BigInt256 query_id;
query_id.import_bits(ptr, ptr_bits, false);
message.query_id = static_cast<td::uint64>(query_id.to_long());
message.signed_by = to_mask(cs.write().fetch_int256(n_k.first, false));
message.message = cs.write().fetch_ref();
res.push_back(std::move(message));
return true;
});
return res;
}
} // namespace ton

View file

@ -0,0 +1,64 @@
#pragma once
#include "vm/cells.h"
#include "SmartContract.h"
#include "Ed25519.h"
#include <bitset>
namespace ton {
class MultisigWallet : public ton::SmartContract {
public:
MultisigWallet(State state) : SmartContract(std::move(state)) {
}
using Mask = std::bitset<128>;
struct QueryState {
enum State { Unknown, NotReady, Sent } state = Unknown;
Mask mask;
};
class QueryBuilder {
public:
QueryBuilder(td::int64 query_id, td::Ref<vm::Cell> msg, int mode = 3);
void sign(td::int32 id, td::Ed25519::PrivateKey& pk);
td::Ref<vm::Cell> create_inner() const;
td::Ref<vm::Cell> create(td::int32 id, td::Ed25519::PrivateKey& pk) const;
Mask get_mask() const {
return mask_;
}
private:
vm::Ref<vm::Cell> dict_;
td::Ref<vm::Cell> msg_;
Mask mask_;
};
MultisigWallet* make_copy() const override {
return new MultisigWallet{state_};
}
// creation
static td::Ref<MultisigWallet> create(td::Ref<vm::Cell> data = {});
td::Ref<vm::Cell> create_init_data(std::vector<td::SecureString> public_keys, int k) const;
static td::Ref<vm::Cell> create_init_data_fast(std::vector<td::SecureString> public_keys, int k);
// get methods
int processed(td::uint64 query_id) const;
QueryState get_query_state(td::uint64 query_id) const;
std::vector<td::SecureString> get_public_keys() const;
td::Ref<vm::Cell> merge_queries(td::Ref<vm::Cell> a, td::Ref<vm::Cell> b) const;
std::pair<int, Mask> check_query_signatures(td::Ref<vm::Cell> a) const;
std::pair<int, int> get_n_k() const;
Mask to_mask(td::RefInt256 mask) const;
struct Message {
td::uint64 query_id;
Mask signed_by;
td::Ref<vm::Cell> message;
};
std::vector<Message> get_unsigned_messaged(int id = -1) const;
};
} // namespace ton

View file

@ -0,0 +1,188 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "SmartContract.h"
#include "GenericAccount.h"
#include "block/block.h"
#include "block/block-auto.h"
#include "vm/cellslice.h"
#include "vm/cp0.h"
#include "vm/continuation.h"
#include "td/utils/crypto.h"
namespace ton {
namespace {
td::int32 get_method_id(td::Slice method_name) {
unsigned crc = td::crc16(method_name);
return (crc & 0xffff) | 0x10000;
}
td::Ref<vm::Stack> prepare_vm_stack(td::Ref<vm::CellSlice> body) {
td::Ref<vm::Stack> stack_ref{true};
td::RefInt256 acc_addr{true};
//CHECK(acc_addr.write().import_bits(account.addr.cbits(), 256));
vm::Stack& stack = stack_ref.write();
stack.push_int(td::RefInt256{true, 10000000000});
stack.push_int(td::RefInt256{true, 10000000000});
stack.push_cell(vm::CellBuilder().finalize());
stack.push_cellslice(std::move(body));
return stack_ref;
}
td::Ref<vm::Tuple> prepare_vm_c7() {
// TODO: fix initialization of c7
td::BitArray<256> rand_seed;
rand_seed.as_slice().fill(0);
td::RefInt256 rand_seed_int{true};
rand_seed_int.unique_write().import_bits(rand_seed.cbits(), 256, false);
auto tuple = vm::make_tuple_ref(
td::make_refint(0x076ef1ea), // [ magic:0x076ef1ea
td::make_refint(0), // actions:Integer
td::make_refint(0), // msgs_sent:Integer
td::make_refint(0), // unixtime:Integer
td::make_refint(0), // block_lt:Integer
td::make_refint(0), // trans_lt:Integer
std::move(rand_seed_int), // rand_seed:Integer
block::CurrencyCollection(1000000000).as_vm_tuple(), // balance_remaining:[Integer (Maybe Cell)]
vm::load_cell_slice_ref(vm::CellBuilder().finalize()) // myself:MsgAddressInt
//vm::StackEntry::maybe(td::Ref<vm::Cell>())
); // global_config:(Maybe Cell) ] = SmartContractInfo;
//LOG(DEBUG) << "SmartContractInfo initialized with " << vm::StackEntry(tuple).to_string();
return vm::make_tuple_ref(std::move(tuple));
}
SmartContract::Answer run_smartcont(SmartContract::State state, td::Ref<vm::Stack> stack, td::Ref<vm::Tuple> c7,
vm::GasLimits gas, bool ignore_chksig) {
auto gas_credit = gas.gas_credit;
vm::init_op_cp0();
vm::DictionaryBase::get_empty_dictionary();
class Logger : public td::LogInterface {
public:
void append(td::CSlice slice) override {
res.append(slice.data(), slice.size());
}
std::string res;
};
Logger logger;
vm::VmLog log{&logger, td::LogOptions::plain()};
if (GET_VERBOSITY_LEVEL() >= VERBOSITY_NAME(DEBUG)) {
log.log_options.level = 4;
log.log_options.fix_newlines = true;
log.log_mask |= vm::VmLog::DumpStack;
} else {
log.log_options.level = 0;
log.log_mask = 0;
}
SmartContract::Answer res;
vm::VmState vm{state.code, std::move(stack), gas, 1, state.data, log};
vm.set_c7(std::move(c7));
vm.set_chksig_always_succeed(ignore_chksig);
try {
res.code = ~vm.run();
} catch (...) {
LOG(FATAL) << "catch unhandled exception";
}
res.new_state = std::move(state);
res.stack = vm.get_stack_ref();
gas = vm.get_gas_limits();
res.gas_used = gas.gas_consumed();
res.accepted = gas.gas_credit == 0;
res.success = (res.accepted && (unsigned)res.code <= 1);
if (GET_VERBOSITY_LEVEL() >= VERBOSITY_NAME(DEBUG)) {
LOG(DEBUG) << "VM log\n" << logger.res;
std::ostringstream os;
res.stack->dump(os);
LOG(DEBUG) << "VM stack:\n" << os.str();
LOG(DEBUG) << "VM exit code: " << res.code;
LOG(DEBUG) << "VM accepted: " << res.accepted;
LOG(DEBUG) << "VM success: " << res.success;
}
if (res.success) {
res.new_state.data = vm.get_c4();
res.actions = vm.get_d(5);
}
LOG_IF(ERROR, gas_credit != 0 && (res.accepted && !res.success))
<< "Accepted but failed with code " << res.code << "\n"
<< res.gas_used << "\n";
return res;
}
} // namespace
td::Ref<vm::CellSlice> SmartContract::empty_slice() {
return vm::load_cell_slice_ref(vm::CellBuilder().finalize());
}
size_t SmartContract::code_size() const {
return vm::std_boc_serialize(state_.code).ok().size();
}
size_t SmartContract::data_size() const {
return vm::std_boc_serialize(state_.data).ok().size();
}
block::StdAddress SmartContract::get_address(WorkchainId workchain_id) const {
return GenericAccount::get_address(workchain_id, get_init_state());
}
td::Ref<vm::Cell> SmartContract::get_init_state() const {
return GenericAccount::get_init_state(get_state().code, get_state().data);
}
SmartContract::Answer SmartContract::run_method(Args args) {
if (!args.c7) {
args.c7 = prepare_vm_c7();
}
if (!args.limits) {
args.limits = vm::GasLimits{(long long)0, (long long)1000000, (long long)10000};
}
CHECK(args.stack);
CHECK(args.method_id);
args.stack.value().write().push_smallint(args.method_id.unwrap());
auto res =
run_smartcont(get_state(), args.stack.unwrap(), args.c7.unwrap(), args.limits.unwrap(), args.ignore_chksig);
state_ = res.new_state;
return res;
}
SmartContract::Answer SmartContract::run_get_method(Args args) const {
if (!args.c7) {
args.c7 = prepare_vm_c7();
}
if (!args.limits) {
args.limits = vm::GasLimits{1000000};
}
if (!args.stack) {
args.stack = td::Ref<vm::Stack>(true);
}
CHECK(args.method_id);
args.stack.value().write().push_smallint(args.method_id.unwrap());
return run_smartcont(get_state(), args.stack.unwrap(), args.c7.unwrap(), args.limits.unwrap(), args.ignore_chksig);
}
SmartContract::Answer SmartContract::run_get_method(td::Slice method, Args args) const {
return run_get_method(args.set_method_id(method));
}
SmartContract::Answer SmartContract::send_external_message(td::Ref<vm::Cell> cell, Args args) {
return run_method(args.set_stack(prepare_vm_stack(vm::load_cell_slice_ref(cell))).set_method_id(-1));
}
} // namespace ton

View file

@ -0,0 +1,116 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "vm/cells.h"
#include "vm/stack.hpp"
#include "vm/continuation.h"
#include "td/utils/optional.h"
#include "td/utils/crypto.h"
#include "block/block.h"
namespace ton {
class SmartContract : public td::CntObject {
static td::Ref<vm::CellSlice> empty_slice();
public:
struct State {
td::Ref<vm::Cell> code;
td::Ref<vm::Cell> data;
};
SmartContract(State state) : state_(std::move(state)) {
}
struct Answer {
SmartContract::State new_state;
bool accepted;
bool success;
td::Ref<vm::Stack> stack;
td::Ref<vm::Cell> actions;
td::int32 code;
td::int64 gas_used;
};
struct Args {
td::optional<td::int32> method_id;
td::optional<vm::GasLimits> limits;
td::optional<td::Ref<vm::Tuple>> c7;
td::optional<td::Ref<vm::Stack>> stack;
bool ignore_chksig{false};
Args() {
}
Args(std::initializer_list<vm::StackEntry> stack)
: stack(td::Ref<vm::Stack>(true, std::vector<vm::StackEntry>(std::move(stack)))) {
}
Args&& set_method_id(td::Slice method_name) {
unsigned crc = td::crc16(method_name);
return set_method_id((crc & 0xffff) | 0x10000);
}
Args&& set_method_id(td::int32 method_id) {
this->method_id = method_id;
return std::move(*this);
}
Args&& set_limits(vm::GasLimits limits) {
this->limits = std::move(limits);
return std::move(*this);
}
Args&& set_c7(td::Ref<vm::Tuple> c7) {
this->c7 = std::move(c7);
return std::move(*this);
}
Args&& set_stack(std::vector<vm::StackEntry> stack) {
this->stack = td::Ref<vm::Stack>(true, std::move(stack));
return std::move(*this);
}
Args&& set_stack(td::Ref<vm::Stack> stack) {
this->stack = std::move(stack);
return std::move(*this);
}
Args&& set_ignore_chksig(bool ignore_chksig) {
this->ignore_chksig = ignore_chksig;
return std::move(*this);
}
};
Answer run_method(Args args = {});
Answer run_get_method(Args args = {}) const;
Answer run_get_method(td::Slice method, Args args = {}) const;
Answer send_external_message(td::Ref<vm::Cell> cell, Args args = {});
size_t code_size() const;
size_t data_size() const;
static td::Ref<SmartContract> create(State state) {
return td::Ref<SmartContract>{true, std::move(state)};
}
block::StdAddress get_address(WorkchainId workchain_id = basechainId) const;
td::Ref<vm::Cell> get_init_state() const;
const State& get_state() const {
return state_;
}
protected:
State state_;
};
} // namespace ton

View file

@ -0,0 +1,72 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "SmartContractCode.h"
#include "vm/boc.h"
#include <map>
#include "td/utils/base64.h"
namespace ton {
namespace {
const auto& get_map() {
static auto map = [] {
class Cmp : public std::less<> {
public:
using is_transparent = void;
};
std::map<std::string, td::Ref<vm::Cell>, Cmp> map;
auto with_tvm_code = [&](auto name, td::Slice code_str) {
map[name] = vm::std_boc_deserialize(td::base64_decode(code_str).move_as_ok()).move_as_ok();
};
#include "smartcont/auto/multisig-code.cpp"
#include "smartcont/auto/simple-wallet-ext-code.cpp"
#include "smartcont/auto/simple-wallet-code.cpp"
#include "smartcont/auto/wallet-code.cpp"
return map;
}();
return map;
}
} // namespace
td::Result<td::Ref<vm::Cell>> SmartContractCode::load(td::Slice name) {
auto& map = get_map();
auto it = map.find(name);
if (it == map.end()) {
return td::Status::Error(PSLICE() << "Can't load td::ref<vm::cell " << name);
}
return it->second;
}
td::Ref<vm::Cell> SmartContractCode::multisig() {
auto res = load("multisig").move_as_ok();
return res;
}
td::Ref<vm::Cell> SmartContractCode::wallet() {
auto res = load("wallet").move_as_ok();
return res;
}
td::Ref<vm::Cell> SmartContractCode::simple_wallet() {
auto res = load("simple-wallet").move_as_ok();
return res;
}
td::Ref<vm::Cell> SmartContractCode::simple_wallet_ext() {
static auto res = load("simple-wallet-ext").move_as_ok();
return res;
}
} // namespace ton

View file

@ -0,0 +1,30 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "vm/cells.h"
namespace ton {
class SmartContractCode {
public:
static td::Result<td::Ref<vm::Cell>> load(td::Slice name);
static td::Ref<vm::Cell> multisig();
static td::Ref<vm::Cell> wallet();
static td::Ref<vm::Cell> simple_wallet();
static td::Ref<vm::Cell> simple_wallet_ext();
};
} // namespace ton

View file

@ -0,0 +1,62 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "TestGiver.h"
#include "GenericAccount.h"
#include "td/utils/base64.h"
namespace ton {
const block::StdAddress& TestGiver::address() noexcept {
static block::StdAddress res =
block::StdAddress::parse("kf_8uRo6OBbQ97jCx2EIuKm8Wmt6Vb15-KsQHFLbKSMiYIny").move_as_ok();
//static block::StdAddress res =
//block::StdAddress::parse("kf9tswzQaryeJ4aAYLy_phLhx4afF1aEvpUVak-2BuA0CmZi").move_as_ok();
return res;
}
vm::CellHash TestGiver::get_init_code_hash() noexcept {
return vm::CellHash::from_slice(td::base64_decode("wDkZp0yR4xo+9+BnuAPfGVjBzK6FPzqdv2DwRq3z3KE=").move_as_ok());
//return vm::CellHash::from_slice(td::base64_decode("YV/IANhoI22HVeatFh6S5LbCHp+5OilARfzW+VQPZgQ=").move_as_ok());
}
td::Ref<vm::Cell> TestGiver::make_a_gift_message(td::uint32 seqno, td::uint64 gramms, td::Slice message,
const block::StdAddress& dest_address) noexcept {
vm::CellBuilder cb;
GenericAccount::store_int_message(cb, dest_address, gramms);
cb.store_bytes("\0\0\0\0", 4);
vm::CellString::store(cb, message, 35 * 8).ensure();
auto message_inner = cb.finalize();
return vm::CellBuilder().store_long(seqno, 32).store_long(1, 8).store_ref(message_inner).finalize();
}
td::Result<td::uint32> TestGiver::get_seqno() const {
return TRY_VM(get_seqno_or_throw());
}
td::Result<td::uint32> TestGiver::get_seqno_or_throw() const {
if (state_.data.is_null()) {
return 0;
}
auto seqno = vm::load_cell_slice(state_.data).fetch_ulong(32);
if (seqno == vm::CellSlice::fetch_ulong_eof) {
return td::Status::Error("Failed to parse seq_no");
}
return static_cast<td::uint32>(seqno);
}
} // namespace ton

View file

@ -0,0 +1,39 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "SmartContract.h"
#include "block/block.h"
#include "vm/cells/CellString.h"
namespace ton {
class TestGiver : public SmartContract {
public:
explicit TestGiver(State state) : ton::SmartContract(std::move(state)) {
}
static constexpr unsigned max_message_size = vm::CellString::max_bytes;
static const block::StdAddress& address() noexcept;
static vm::CellHash get_init_code_hash() noexcept;
static td::Ref<vm::Cell> make_a_gift_message(td::uint32 seqno, td::uint64 gramms, td::Slice message,
const block::StdAddress& dest_address) noexcept;
td::Result<td::uint32> get_seqno() const;
private:
td::Result<td::uint32> get_seqno_or_throw() const;
};
} // namespace ton

View file

@ -0,0 +1,91 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "TestWallet.h"
#include "GenericAccount.h"
#include "vm/boc.h"
#include "td/utils/base64.h"
namespace ton {
td::Ref<vm::Cell> TestWallet::get_init_state(const td::Ed25519::PublicKey& public_key) noexcept {
auto code = get_init_code();
auto data = get_init_data(public_key);
return GenericAccount::get_init_state(std::move(code), std::move(data));
}
td::Ref<vm::Cell> TestWallet::get_init_message(const td::Ed25519::PrivateKey& private_key) noexcept {
std::string seq_no(4, 0);
auto signature =
private_key.sign(vm::CellBuilder().store_bytes(seq_no).finalize()->get_hash().as_slice()).move_as_ok();
return vm::CellBuilder().store_bytes(signature).store_bytes(seq_no).finalize();
}
td::Ref<vm::Cell> TestWallet::make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 seqno,
td::int64 gramms, td::Slice message,
const block::StdAddress& dest_address) noexcept {
td::int32 send_mode = 3;
if (gramms == -1) {
gramms = 0;
send_mode += 128;
}
vm::CellBuilder cb;
GenericAccount::store_int_message(cb, dest_address, gramms);
cb.store_bytes("\0\0\0\0", 4);
vm::CellString::store(cb, message, 35 * 8).ensure();
auto message_inner = cb.finalize();
auto message_outer =
vm::CellBuilder().store_long(seqno, 32).store_long(send_mode, 8).store_ref(message_inner).finalize();
auto signature = private_key.sign(message_outer->get_hash().as_slice()).move_as_ok();
return vm::CellBuilder().store_bytes(signature).append_cellslice(vm::load_cell_slice(message_outer)).finalize();
}
td::Ref<vm::Cell> TestWallet::get_init_code() noexcept {
static auto res = [] {
auto serialized_code = td::base64_decode(
"te6ccgEEAQEAAAAAUwAAov8AIN0gggFMl7qXMO1E0NcLH+Ck8mCBAgDXGCDXCx/tRNDTH9P/"
"0VESuvKhIvkBVBBE+RDyovgAAdMfMSDXSpbTB9QC+wDe0aTIyx/L/8ntVA==")
.move_as_ok();
return vm::std_boc_deserialize(serialized_code).move_as_ok();
}();
return res;
}
vm::CellHash TestWallet::get_init_code_hash() noexcept {
return get_init_code()->get_hash();
}
td::Ref<vm::Cell> TestWallet::get_init_data(const td::Ed25519::PublicKey& public_key) noexcept {
return vm::CellBuilder().store_long(0, 32).store_bytes(public_key.as_octet_string()).finalize();
}
td::Result<td::uint32> TestWallet::get_seqno() const {
return TRY_VM(get_seqno_or_throw());
}
td::Result<td::uint32> TestWallet::get_seqno_or_throw() const {
if (state_.data.is_null()) {
return 0;
}
auto seqno = vm::load_cell_slice(state_.data).fetch_ulong(32);
if (seqno == vm::CellSlice::fetch_ulong_eof) {
return td::Status::Error("Failed to parse seq_no");
}
return static_cast<td::uint32>(seqno);
}
} // namespace ton

View file

@ -0,0 +1,48 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "smc-envelope/SmartContract.h"
#include "vm/cells.h"
#include "Ed25519.h"
#include "block/block.h"
#include "vm/cells/CellString.h"
namespace ton {
class TestWallet : public ton::SmartContract {
public:
explicit TestWallet(State state) : ton::SmartContract(std::move(state)) {
}
static constexpr unsigned max_message_size = vm::CellString::max_bytes;
static td::Ref<vm::Cell> get_init_state(const td::Ed25519::PublicKey& public_key) noexcept;
static td::Ref<vm::Cell> get_init_message(const td::Ed25519::PrivateKey& private_key) noexcept;
static td::Ref<vm::Cell> make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 seqno,
td::int64 gramms, td::Slice message,
const block::StdAddress& dest_address) noexcept;
static td::Ref<vm::Cell> get_init_code() noexcept;
static vm::CellHash get_init_code_hash() noexcept;
static td::Ref<vm::Cell> get_init_data(const td::Ed25519::PublicKey& public_key) noexcept;
td::Result<td::uint32> get_seqno() const;
private:
td::Result<td::uint32> get_seqno_or_throw() const;
};
} // namespace ton

View file

@ -0,0 +1,100 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "Wallet.h"
#include "GenericAccount.h"
#include "vm/boc.h"
#include "vm/cells/CellString.h"
#include "td/utils/base64.h"
#include <limits>
namespace ton {
td::Ref<vm::Cell> Wallet::get_init_state(const td::Ed25519::PublicKey& public_key) noexcept {
auto code = get_init_code();
auto data = get_init_data(public_key);
return GenericAccount::get_init_state(std::move(code), std::move(data));
}
td::Ref<vm::Cell> Wallet::get_init_message(const td::Ed25519::PrivateKey& private_key) noexcept {
td::uint32 seqno = 0;
td::uint32 valid_until = std::numeric_limits<td::uint32>::max();
auto signature =
private_key
.sign(vm::CellBuilder().store_long(seqno, 32).store_long(valid_until, 32).finalize()->get_hash().as_slice())
.move_as_ok();
return vm::CellBuilder().store_bytes(signature).store_long(seqno, 32).store_long(valid_until, 32).finalize();
}
td::Ref<vm::Cell> Wallet::make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 seqno,
td::uint32 valid_until, td::int64 gramms, td::Slice message,
const block::StdAddress& dest_address) noexcept {
td::int32 send_mode = 3;
if (gramms == -1) {
gramms = 0;
send_mode += 128;
}
vm::CellBuilder cb;
GenericAccount::store_int_message(cb, dest_address, gramms);
cb.store_bytes("\0\0\0\0", 4);
vm::CellString::store(cb, message, 35 * 8).ensure();
auto message_inner = cb.finalize();
auto message_outer = vm::CellBuilder()
.store_long(seqno, 32)
.store_long(valid_until, 32)
.store_long(send_mode, 8)
.store_ref(message_inner)
.finalize();
auto signature = private_key.sign(message_outer->get_hash().as_slice()).move_as_ok();
return vm::CellBuilder().store_bytes(signature).append_cellslice(vm::load_cell_slice(message_outer)).finalize();
}
td::Ref<vm::Cell> Wallet::get_init_code() noexcept {
static auto res = [] {
auto serialized_code = td::base64_decode(
"te6ccgEEAQEAAAAAVwAAqv8AIN0gggFMl7qXMO1E0NcLH+Ck8mCDCNcYINMf0x8B+CO78mPtRNDTH9P/"
"0VExuvKhA/kBVBBC+RDyovgAApMg10qW0wfUAvsA6NGkyMsfy//J7VQ=")
.move_as_ok();
return vm::std_boc_deserialize(serialized_code).move_as_ok();
}();
return res;
}
vm::CellHash Wallet::get_init_code_hash() noexcept {
return get_init_code()->get_hash();
}
td::Ref<vm::Cell> Wallet::get_init_data(const td::Ed25519::PublicKey& public_key) noexcept {
return vm::CellBuilder().store_long(0, 32).store_bytes(public_key.as_octet_string()).finalize();
}
td::Result<td::uint32> Wallet::get_seqno() const {
return TRY_VM(get_seqno_or_throw());
}
td::Result<td::uint32> Wallet::get_seqno_or_throw() const {
if (state_.data.is_null()) {
return 0;
}
//FIXME use get method
return static_cast<td::uint32>(vm::load_cell_slice(state_.data).fetch_ulong(32));
}
} // namespace ton

View file

@ -0,0 +1,48 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "smc-envelope/SmartContract.h"
#include "vm/cells.h"
#include "Ed25519.h"
#include "block/block.h"
#include "vm/cells/CellString.h"
namespace ton {
class Wallet : ton::SmartContract {
public:
explicit Wallet(State state) : ton::SmartContract(std::move(state)) {
}
static constexpr unsigned max_message_size = vm::CellString::max_bytes;
static td::Ref<vm::Cell> get_init_state(const td::Ed25519::PublicKey& public_key) noexcept;
static td::Ref<vm::Cell> get_init_message(const td::Ed25519::PrivateKey& private_key) noexcept;
static td::Ref<vm::Cell> make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 seqno,
td::uint32 valid_until, td::int64 gramms, td::Slice message,
const block::StdAddress& dest_address) noexcept;
static td::Ref<vm::Cell> get_init_code() noexcept;
static vm::CellHash get_init_code_hash() noexcept;
static td::Ref<vm::Cell> get_init_data(const td::Ed25519::PublicKey& public_key) noexcept;
td::Result<td::uint32> get_seqno() const;
private:
td::Result<td::uint32> get_seqno_or_throw() const;
};
} // namespace ton

View file

@ -21,8 +21,12 @@
#include "td/utils/misc.h"
#include "td/utils/Slice.h"
#include "td/utils/tests.h"
#include "td/utils/JsonBuilder.h"
#include "wycheproof.h"
#include <string>
#include <utility>
unsigned char fixed_privkey[32] = "abacabadabacabaeabacabadabacaba";
unsigned char fixed_pubkey[32] = {0x6f, 0x9e, 0x5b, 0xde, 0xce, 0x87, 0x21, 0xeb, 0x57, 0x37, 0xfb,
@ -142,3 +146,74 @@ TEST(Crypto, ed25519) {
assert(!std::memcmp(secret12, secret21, 32));
*/
}
TEST(Crypto, wycheproof) {
std::vector<std::pair<std::string, std::string>> bad_tests;
auto json_str = wycheproof_ed25519();
auto value = td::json_decode(json_str).move_as_ok();
auto &root = value.get_object();
auto test_groups_o = get_json_object_field(root, "testGroups", td::JsonValue::Type::Array, false).move_as_ok();
auto &test_groups = test_groups_o.get_array();
auto from_hexc = [](char c) {
if (c >= '0' && c <= '9') {
return c - '0';
}
return c - 'a' + 10;
};
auto from_hex = [&](td::Slice s) {
CHECK(s.size() % 2 == 0);
std::string res(s.size() / 2, 0);
for (size_t i = 0; i < s.size(); i += 2) {
res[i / 2] = char(from_hexc(s[i]) * 16 + from_hexc(s[i + 1]));
}
return res;
};
for (auto &test_o : test_groups) {
auto &test = test_o.get_object();
auto key_o = get_json_object_field(test, "key", td::JsonValue::Type::Object, false).move_as_ok();
auto sk_str = td::get_json_object_string_field(key_o.get_object(), "sk", false).move_as_ok();
auto pk_str = td::get_json_object_string_field(key_o.get_object(), "pk", false).move_as_ok();
auto pk = td::Ed25519::PublicKey(td::SecureString(from_hex(pk_str)));
auto sk = td::Ed25519::PrivateKey(td::SecureString(from_hex(sk_str)));
CHECK(sk.get_public_key().move_as_ok().as_octet_string().as_slice() == pk.as_octet_string().as_slice());
//auto key =
//td::Ed25519::PrivateKey::from_pem(
//td::SecureString(td::get_json_object_string_field(test, "keyPem", false).move_as_ok()), td::SecureString())
//.move_as_ok();
auto tests_o = get_json_object_field(test, "tests", td::JsonValue::Type::Array, false).move_as_ok();
auto &tests = tests_o.get_array();
for (auto &test_o : tests) {
auto &test = test_o.get_object();
auto id = td::get_json_object_string_field(test, "tcId", false).move_as_ok();
auto comment = td::get_json_object_string_field(test, "comment", false).move_as_ok();
auto sig = from_hex(td::get_json_object_string_field(test, "sig", false).move_as_ok());
auto msg = from_hex(td::get_json_object_string_field(test, "msg", false).move_as_ok());
auto result = td::get_json_object_string_field(test, "result", false).move_as_ok();
auto has_result = pk.verify_signature(msg, sig).is_ok() ? "valid" : "invalid";
if (result != has_result) {
bad_tests.push_back({id, comment});
}
}
}
if (bad_tests.empty()) {
return;
}
LOG(ERROR) << "FAILED: " << td::format::as_array(bad_tests);
}
TEST(Crypto, almost_zero) {
td::SecureString pub(32);
td::SecureString sig(64);
td::SecureString msg(1);
pub.as_mutable_slice().ubegin()[31] = static_cast<unsigned char>(128);
for (td::int32 j = 0; j < 256; j++) {
msg.as_mutable_slice()[0] = (char)j;
if (td::Ed25519::PublicKey(pub.copy()).verify_signature(msg, sig).is_ok()) {
LOG(ERROR) << "FAILED: " << j;
break;
}
}
}

View file

@ -51,34 +51,6 @@
#include <openssl/sha.h>
struct Step {
std::function<void()> func;
td::uint32 weight;
};
class RandomSteps {
public:
RandomSteps(std::vector<Step> steps) : steps_(std::move(steps)) {
for (auto &step : steps_) {
steps_sum_ += step.weight;
}
}
template <class Random>
void step(Random &rnd) {
auto w = rnd() % steps_sum_;
for (auto &step : steps_) {
if (w < step.weight) {
step.func();
break;
}
w -= step.weight;
}
}
private:
std::vector<Step> steps_;
td::int32 steps_sum_ = 0;
};
namespace vm {
std::vector<int> do_get_serialization_modes() {
@ -879,7 +851,7 @@ TEST(TonDb, DynamicBoc2) {
VLOG(boc) << " OK";
};
RandomSteps steps({{new_root, 10}, {delete_root, 9}, {commit, 2}, {reset, 1}});
td::RandomSteps steps({{new_root, 10}, {delete_root, 9}, {commit, 2}, {reset, 1}});
while (first_root_id != total_roots) {
VLOG(boc) << first_root_id << " " << last_root_id << " " << kv->count("").ok();
steps.step(rnd);
@ -1732,7 +1704,7 @@ TEST(TonDb, CompactArray) {
fast_array = vm::FastCompactArray(size);
};
RandomSteps steps({{reset_array, 1}, {set_value, 1000}, {validate, 10}, {validate_full, 2}, {flush_to_db, 1}});
td::RandomSteps steps({{reset_array, 1}, {set_value, 1000}, {validate, 10}, {validate_full, 2}, {flush_to_db, 1}});
for (size_t t = 0; t < 100000; t++) {
if (t % 10000 == 0) {
LOG(ERROR) << t;

View file

@ -0,0 +1,524 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "vm/dict.h"
#include "common/bigint.hpp"
#include "Ed25519.h"
#include "block/block.h"
#include "fift/Fift.h"
#include "fift/words.h"
#include "fift/utils.h"
#include "smc-envelope/GenericAccount.h"
#include "smc-envelope/MultisigWallet.h"
#include "smc-envelope/SmartContract.h"
#include "smc-envelope/SmartContractCode.h"
#include "smc-envelope/TestGiver.h"
#include "smc-envelope/TestWallet.h"
#include "smc-envelope/Wallet.h"
#include "td/utils/base64.h"
#include "td/utils/crypto.h"
#include "td/utils/Random.h"
#include "td/utils/tests.h"
#include "td/utils/ScopeGuard.h"
#include "td/utils/StringBuilder.h"
#include "td/utils/Timer.h"
#include "td/utils/PathView.h"
#include "td/utils/filesystem.h"
#include "td/utils/port/path.h"
#include <bitset>
#include <set>
#include <tuple>
std::string current_dir() {
return td::PathView(td::realpath(__FILE__).move_as_ok()).parent_dir().str();
}
std::string load_source(std::string name) {
return td::read_file_str(current_dir() + "../../crypto/" + name).move_as_ok();
}
td::Ref<vm::Cell> get_test_wallet_source() {
std::string code = R"ABCD(
SETCP0 DUP IFNOTRET // return if recv_internal
DUP 85143 INT EQUAL IFJMP:<{ // "seqno" get-method
DROP c4 PUSHCTR CTOS 32 PLDU // cnt
}>
INC 32 THROWIF // fail unless recv_external
512 INT LDSLICEX DUP 32 PLDU // sign cs cnt
c4 PUSHCTR CTOS 32 LDU 256 LDU ENDS // sign cs cnt cnt' pubk
s1 s2 XCPU // sign cs cnt pubk cnt' cnt
EQUAL 33 THROWIFNOT // ( seqno mismatch? )
s2 PUSH HASHSU // sign cs cnt pubk hash
s0 s4 s4 XC2PU // pubk cs cnt hash sign pubk
CHKSIGNU // pubk cs cnt ?
34 THROWIFNOT // signature mismatch
ACCEPT
SWAP 32 LDU NIP
DUP SREFS IF:<{
// 3 INT 35 LSHIFT# 3 INT RAWRESERVE // reserve all but 103 Grams from the balance
8 LDU LDREF // pubk cnt mode msg cs
s0 s2 XCHG SENDRAWMSG // pubk cnt cs ; ( message sent )
}>
ENDS
INC NEWC 32 STU 256 STU ENDC c4 POPCTR
)ABCD";
return fift::compile_asm(code).move_as_ok();
}
td::Ref<vm::Cell> get_wallet_source() {
std::string code = R"ABCD(
SETCP0 DUP IFNOTRET // return if recv_internal
DUP 85143 INT EQUAL IFJMP:<{ // "seqno" get-method
DROP c4 PUSHCTR CTOS 32 PLDU // cnt
}>
INC 32 THROWIF // fail unless recv_external
9 PUSHPOW2 LDSLICEX DUP 32 LDU 32 LDU // signature in_msg msg_seqno valid_until cs
SWAP NOW LEQ 35 THROWIF // signature in_msg msg_seqno cs
c4 PUSH CTOS 32 LDU 256 LDU ENDS // signature in_msg msg_seqno cs stored_seqno public_key
s3 s1 XCPU // signature in_msg public_key cs stored_seqno msg_seqno stored_seqno
EQUAL 33 THROWIFNOT // signature in_msg public_key cs stored_seqno
s0 s3 XCHG HASHSU // signature stored_seqno public_key cs hash
s0 s4 s2 XC2PU CHKSIGNU 34 THROWIFNOT // cs stored_seqno public_key
ACCEPT
s0 s2 XCHG // public_key stored_seqno cs
WHILE:<{
DUP SREFS // public_key stored_seqno cs _40
}>DO<{ // public_key stored_seqno cs
// 3 INT 35 LSHIFT# 3 INT RAWRESERVE // reserve all but 103 Grams from the balance
8 LDU LDREF s0 s2 XCHG // public_key stored_seqno cs _45 mode
SENDRAWMSG // public_key stored_seqno cs
}>
ENDS INC // public_key seqno'
NEWC 32 STU 256 STU ENDC c4 POP
)ABCD";
return fift::compile_asm(code).move_as_ok();
}
TEST(Tonlib, TestWallet) {
LOG(ERROR) << td::base64_encode(std_boc_serialize(get_test_wallet_source()).move_as_ok());
CHECK(get_test_wallet_source()->get_hash() == ton::TestWallet::get_init_code()->get_hash());
auto fift_output = fift::mem_run_fift(load_source("smartcont/new-wallet.fif"), {"aba", "0"}).move_as_ok();
auto new_wallet_pk = fift_output.source_lookup.read_file("new-wallet.pk").move_as_ok().data;
auto new_wallet_query = fift_output.source_lookup.read_file("new-wallet-query.boc").move_as_ok().data;
auto new_wallet_addr = fift_output.source_lookup.read_file("new-wallet.addr").move_as_ok().data;
td::Ed25519::PrivateKey priv_key{td::SecureString{new_wallet_pk}};
auto pub_key = priv_key.get_public_key().move_as_ok();
auto init_state = ton::TestWallet::get_init_state(pub_key);
auto init_message = ton::TestWallet::get_init_message(priv_key);
auto address = ton::GenericAccount::get_address(0, init_state);
CHECK(address.addr.as_slice() == td::Slice(new_wallet_addr).substr(0, 32));
td::Ref<vm::Cell> res = ton::GenericAccount::create_ext_message(address, init_state, init_message);
LOG(ERROR) << "-------";
vm::load_cell_slice(res).print_rec(std::cerr);
LOG(ERROR) << "-------";
vm::load_cell_slice(vm::std_boc_deserialize(new_wallet_query).move_as_ok()).print_rec(std::cerr);
CHECK(vm::std_boc_deserialize(new_wallet_query).move_as_ok()->get_hash() == res->get_hash());
fift_output.source_lookup.write_file("/main.fif", load_source("smartcont/wallet.fif")).ensure();
auto dest = block::StdAddress::parse("Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX").move_as_ok();
fift_output = fift::mem_run_fift(std::move(fift_output.source_lookup),
{"aba", "new-wallet", "Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX", "123",
"321", "-C", "TEST"})
.move_as_ok();
auto wallet_query = fift_output.source_lookup.read_file("wallet-query.boc").move_as_ok().data;
auto gift_message = ton::GenericAccount::create_ext_message(
address, {}, ton::TestWallet::make_a_gift_message(priv_key, 123, 321000000000ll, "TEST", dest));
LOG(ERROR) << "-------";
vm::load_cell_slice(gift_message).print_rec(std::cerr);
LOG(ERROR) << "-------";
vm::load_cell_slice(vm::std_boc_deserialize(wallet_query).move_as_ok()).print_rec(std::cerr);
CHECK(vm::std_boc_deserialize(wallet_query).move_as_ok()->get_hash() == gift_message->get_hash());
}
td::Ref<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() == ton::Wallet::get_init_code()->get_hash());
auto fift_output = fift::mem_run_fift(load_source("smartcont/new-wallet-v2.fif"), {"aba", "0"}).move_as_ok();
auto new_wallet_pk = fift_output.source_lookup.read_file("new-wallet.pk").move_as_ok().data;
auto new_wallet_query = fift_output.source_lookup.read_file("new-wallet-query.boc").move_as_ok().data;
auto new_wallet_addr = fift_output.source_lookup.read_file("new-wallet.addr").move_as_ok().data;
td::Ed25519::PrivateKey priv_key{td::SecureString{new_wallet_pk}};
auto pub_key = priv_key.get_public_key().move_as_ok();
auto init_state = ton::Wallet::get_init_state(pub_key);
auto init_message = ton::Wallet::get_init_message(priv_key);
auto address = ton::GenericAccount::get_address(0, init_state);
CHECK(address.addr.as_slice() == td::Slice(new_wallet_addr).substr(0, 32));
td::Ref<vm::Cell> res = ton::GenericAccount::create_ext_message(address, init_state, init_message);
LOG(ERROR) << "-------";
vm::load_cell_slice(res).print_rec(std::cerr);
LOG(ERROR) << "-------";
vm::load_cell_slice(vm::std_boc_deserialize(new_wallet_query).move_as_ok()).print_rec(std::cerr);
CHECK(vm::std_boc_deserialize(new_wallet_query).move_as_ok()->get_hash() == res->get_hash());
fift_output.source_lookup.write_file("/main.fif", load_source("smartcont/wallet-v2.fif")).ensure();
class ZeroOsTime : public fift::OsTime {
public:
td::uint32 now() override {
return 0;
}
};
fift_output.source_lookup.set_os_time(std::make_unique<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 = ton::GenericAccount::create_ext_message(
address, {}, ton::Wallet::make_a_gift_message(priv_key, 123, 60, 321000000000ll, "TESTv2", dest));
LOG(ERROR) << "-------";
vm::load_cell_slice(gift_message).print_rec(std::cerr);
LOG(ERROR) << "-------";
vm::load_cell_slice(vm::std_boc_deserialize(wallet_query).move_as_ok()).print_rec(std::cerr);
CHECK(vm::std_boc_deserialize(wallet_query).move_as_ok()->get_hash() == gift_message->get_hash());
}
TEST(Tonlib, TestGiver) {
auto address =
block::StdAddress::parse("-1:60c04141c6a7b96d68615e7a91d265ad0f3a9a922e9ae9c901d4fa83f5d3c0d0").move_as_ok();
LOG(ERROR) << address.bounceable;
auto fift_output = fift::mem_run_fift(load_source("smartcont/testgiver.fif"),
{"aba", address.rserialize(), "0", "6.666", "wallet-query"})
.move_as_ok();
LOG(ERROR) << fift_output.output;
auto wallet_query = fift_output.source_lookup.read_file("wallet-query.boc").move_as_ok().data;
auto res = ton::GenericAccount::create_ext_message(
ton::TestGiver::address(), {},
ton::TestGiver::make_a_gift_message(0, 1000000000ll * 6666 / 1000, "GIFT", address));
vm::CellSlice(vm::NoVm(), res).print_rec(std::cerr);
CHECK(vm::std_boc_deserialize(wallet_query).move_as_ok()->get_hash() == res->get_hash());
}
class SimpleWallet : public ton::SmartContract {
public:
SimpleWallet(State state) : SmartContract(std::move(state)) {
}
const State& get_state() const {
return state_;
}
SimpleWallet* make_copy() const override {
return new SimpleWallet{state_};
}
static td::Ref<SimpleWallet> create_empty() {
return td::Ref<SimpleWallet>(true, State{ton::SmartContractCode::simple_wallet_ext(), {}});
}
static td::Ref<SimpleWallet> create(td::Ref<vm::Cell> data) {
return td::Ref<SimpleWallet>(true, State{ton::SmartContractCode::simple_wallet_ext(), std::move(data)});
}
static td::Ref<SimpleWallet> create_fast(td::Ref<vm::Cell> data) {
return td::Ref<SimpleWallet>(true, State{ton::SmartContractCode::simple_wallet(), std::move(data)});
}
td::int32 seqno() const {
auto res = run_get_method("seqno");
return res.stack.write().pop_smallint_range(1000000000);
}
td::Ref<vm::Cell> create_init_state(td::Slice public_key) const {
td::RefInt256 pk{true};
pk.write().import_bytes(public_key.ubegin(), public_key.size(), false);
auto res = run_get_method("create_init_state", {pk});
return res.stack.write().pop_cell();
}
td::Ref<vm::Cell> prepare_send_message(td::Ref<vm::Cell> msg, td::int8 mode = 3) const {
auto res = run_get_method("prepare_send_message", {td::make_refint(mode), msg});
return res.stack.write().pop_cell();
}
static td::Ref<vm::Cell> sign_message(vm::Ref<vm::Cell> body, const td::Ed25519::PrivateKey& pk) {
auto signature = pk.sign(body->get_hash().as_slice()).move_as_ok();
return vm::CellBuilder().store_bytes(signature.as_slice()).append_cellslice(vm::load_cell_slice(body)).finalize();
}
};
TEST(Smartcon, Simple) {
auto private_key = td::Ed25519::generate_private_key().move_as_ok();
auto public_key = private_key.get_public_key().move_as_ok().as_octet_string();
auto w_lib = SimpleWallet::create_empty();
auto init_data = w_lib->create_init_state(public_key);
auto w = SimpleWallet::create(init_data);
LOG(ERROR) << w->code_size();
auto fw = SimpleWallet::create_fast(init_data);
LOG(ERROR) << fw->code_size();
LOG(ERROR) << w->seqno();
for (int i = 0; i < 20; i++) {
auto msg = w->sign_message(w->prepare_send_message(vm::CellBuilder().finalize()), private_key);
w.write().send_external_message(msg);
fw.write().send_external_message(msg);
}
ASSERT_EQ(20, w->seqno());
CHECK(w->get_state().data->get_hash() == fw->get_state().data->get_hash());
}
namespace std { // ouch
bool operator<(const ton::MultisigWallet::Mask& a, const ton::MultisigWallet::Mask& b) {
for (size_t i = 0; i < a.size(); i++) {
if (a[i] != b[i]) {
return a[i] < b[i];
}
}
return false;
}
} // namespace std
TEST(Smartcon, Multisig) {
auto ms_lib = ton::MultisigWallet::create();
int n = 100;
int k = 99;
std::vector<td::Ed25519::PrivateKey> keys;
for (int i = 0; i < n; i++) {
keys.push_back(td::Ed25519::generate_private_key().move_as_ok());
}
auto init_state = ms_lib->create_init_data(
td::transform(keys, [](auto& key) { return key.get_public_key().ok().as_octet_string(); }), k);
auto ms = ton::MultisigWallet::create(init_state);
td::uint64 query_id = 123;
ton::MultisigWallet::QueryBuilder qb(query_id, vm::CellBuilder().finalize());
// first empty query (init)
CHECK(ms.write().send_external_message(vm::CellBuilder().finalize()).code == 0);
// first empty query
CHECK(ms.write().send_external_message(vm::CellBuilder().finalize()).code > 0);
for (int i = 0; i < 10; i++) {
auto query = qb.create(i, keys[i]);
auto ans = ms.write().send_external_message(query);
LOG(INFO) << "CODE: " << ans.code;
LOG(INFO) << "GAS: " << ans.gas_used;
}
for (int i = 0; i + 1 < 50; i++) {
qb.sign(i, keys[i]);
}
auto query = qb.create(49, keys[49]);
CHECK(ms->get_n_k() == std::make_pair(n, k));
auto ans = ms.write().send_external_message(query);
LOG(INFO) << "CODE: " << ans.code;
LOG(INFO) << "GAS: " << ans.gas_used;
CHECK(ans.success);
ASSERT_EQ(0, ms->processed(query_id));
CHECK(ms.write().send_external_message(query).code > 0);
ASSERT_EQ(0, ms->processed(query_id));
{
ton::MultisigWallet::QueryBuilder qb(query_id, vm::CellBuilder().finalize());
for (int i = 50; i + 1 < 100; i++) {
qb.sign(i, keys[i]);
}
query = qb.create(99, keys[99]);
}
ans = ms.write().send_external_message(query);
LOG(INFO) << "CODE: " << ans.code;
LOG(INFO) << "GAS: " << ans.gas_used;
ASSERT_EQ(-1, ms->processed(query_id));
}
TEST(Smartcont, MultisigStress) {
int n = 10;
int k = 5;
std::vector<td::Ed25519::PrivateKey> keys;
for (int i = 0; i < n; i++) {
keys.push_back(td::Ed25519::generate_private_key().move_as_ok());
}
auto public_keys = td::transform(keys, [](auto& key) { return key.get_public_key().ok().as_octet_string(); });
auto ms_lib = ton::MultisigWallet::create();
auto init_state_old =
ms_lib->create_init_data_fast(td::transform(public_keys, [](auto& key) { return key.copy(); }), k);
auto init_state = ms_lib->create_init_data(td::transform(public_keys, [](auto& key) { return key.copy(); }), k);
CHECK(init_state_old->get_hash() == init_state->get_hash());
auto ms = ton::MultisigWallet::create(init_state);
CHECK(ms->get_public_keys() == public_keys);
td::int32 now = 0;
td::int32 qid = 1;
using Mask = std::bitset<128>;
struct Query {
td::int64 id;
td::Ref<vm::Cell> message;
Mask signed_mask;
};
std::vector<Query> queries;
int max_queries = 300;
td::Random::Xorshift128plus rnd(123);
auto new_query = [&] {
if (qid > max_queries) {
return;
}
Query query;
query.id = (static_cast<td::int64>(now) << 32) | qid++;
query.message = vm::CellBuilder().store_bytes(td::rand_string('a', 'z', rnd.fast(0, 100))).finalize();
queries.push_back(std::move(query));
};
auto verify = [&] {
auto messages = ms->get_unsigned_messaged();
std::set<std::tuple<td::uint64, ton::MultisigWallet::Mask, std::string>> s;
std::set<std::tuple<td::uint64, ton::MultisigWallet::Mask, std::string>> t;
for (auto& m : messages) {
auto x = std::make_tuple(m.query_id, m.signed_by, m.message->get_hash().as_slice().str());
s.insert(std::move(x));
}
for (auto& q : queries) {
if (q.signed_mask.none()) {
continue;
}
t.insert(std::make_tuple(q.id, q.signed_mask, q.message->get_hash().as_slice().str()));
}
ASSERT_EQ(t.size(), s.size());
CHECK(s == t);
};
auto sign_query = [&](Query& query, Mask mask) {
auto qb = ton::MultisigWallet::QueryBuilder(query.id, query.message);
int first_i = -1;
for (int i = 0; i < (int)mask.size(); i++) {
if (mask.test(i)) {
if (first_i == -1) {
first_i = i;
} else {
qb.sign(i, keys[i]);
}
}
}
return qb.create(first_i, keys[first_i]);
};
auto send_signature = [&](td::Ref<vm::Cell> query) {
auto ans = ms.write().send_external_message(query);
LOG(ERROR) << "GAS: " << ans.gas_used;
return ans.code == 0;
};
auto is_ready = [&](Query& query) { return ms->processed(query.id) == -1; };
auto gen_query = [&](Query& query) {
auto x = rnd.fast(1, n);
Mask mask;
for (int t = 0; t < x; t++) {
mask.set(rnd() % n);
}
auto signature = sign_query(query, mask);
return std::make_pair(signature, mask);
};
auto rand_sign = [&] {
if (queries.empty()) {
return;
}
size_t query_i = rnd() % queries.size();
auto& query = queries[query_i];
Mask mask;
td::Ref<vm::Cell> signature;
std::tie(signature, mask) = gen_query(query);
if (false && rnd() % 6 == 0) {
Mask mask2;
td::Ref<vm::Cell> signature2;
std::tie(signature2, mask2) = gen_query(query);
for (int i = 0; i < (int)keys.size(); i++) {
if (mask[i]) {
signature = ms->merge_queries(std::move(signature), std::move(signature2));
break;
}
if (mask2[i]) {
signature = ms->merge_queries(std::move(signature2), std::move(signature));
break;
}
}
//signature = ms->merge_queries(std::move(signature), std::move(signature2));
mask |= mask2;
}
int got_cnt;
Mask got_cnt_bits;
std::tie(got_cnt, got_cnt_bits) = ms->check_query_signatures(signature);
CHECK(mask == got_cnt_bits);
bool expect_ok = true;
{
auto new_mask = mask & ~query.signed_mask;
expect_ok &= new_mask.any();
for (size_t i = 0; i < mask.size(); i++) {
if (mask[i]) {
expect_ok &= new_mask[i];
break;
}
}
}
ASSERT_EQ(expect_ok, send_signature(std::move(signature)));
if (expect_ok) {
query.signed_mask |= mask;
}
auto expect_is_ready = query.signed_mask.count() >= (size_t)k;
auto state = ms->get_query_state(query.id);
ASSERT_EQ(expect_is_ready, (state.state == ton::MultisigWallet::QueryState::Sent));
CHECK(expect_is_ready || state.mask == query.signed_mask);
ASSERT_EQ(expect_is_ready, is_ready(query));
if (expect_is_ready) {
queries.erase(queries.begin() + query_i);
}
verify();
};
td::RandomSteps steps({{rand_sign, 2}, {new_query, 1}});
while (!queries.empty() || qid <= max_queries) {
steps.step(rnd);
//LOG(ERROR) << ms->data_size();
}
LOG(INFO) << "Final code size: " << ms->code_size();
LOG(INFO) << "Final data size: " << ms->data_size();
}

1160
crypto/test/wycheproof.h Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,64 @@
#include "CellString.h"
#include "td/utils/misc.h"
#include "vm/cells/CellSlice.h"
namespace vm {
td::Status CellString::store(CellBuilder &cb, td::Slice slice, unsigned int top_bits) {
td::uint32 size = td::narrow_cast<td::uint32>(slice.size() * 8);
return store(cb, td::BitSlice(slice.ubegin(), size), top_bits);
}
td::Status CellString::store(CellBuilder &cb, td::BitSlice slice, unsigned int top_bits) {
if (slice.size() > max_bytes * 8) {
return td::Status::Error("String is too long (1)");
}
unsigned int head = td::min(slice.size(), td::min(cb.remaining_bits(), top_bits)) / 8 * 8;
auto max_bits = vm::Cell::max_bits / 8 * 8;
auto depth = 1 + (slice.size() - head + max_bits - 1) / max_bits;
if (depth > max_chain_length) {
return td::Status::Error("String is too long (2)");
}
cb.append_bitslice(slice.subslice(0, head));
slice.advance(head);
if (slice.size() == 0) {
return td::Status::OK();
}
CellBuilder child_cb;
store(child_cb, std::move(slice));
cb.store_ref(child_cb.finalize());
return td::Status::OK();
}
template <class F>
void CellString::for_each(F &&f, CellSlice &cs, unsigned int top_bits) {
unsigned int head = td::min(cs.size(), top_bits);
f(cs.prefetch_bits(head));
if (!cs.have_refs()) {
return;
}
auto ref = cs.prefetch_ref();
while (true) {
auto cs = vm::load_cell_slice(ref);
f(cs.prefetch_bits(cs.size()));
if (!cs.have_refs()) {
return;
}
ref = cs.prefetch_ref();
}
}
td::Result<td::string> CellString::load(CellSlice &cs, unsigned int top_bits) {
unsigned int size = 0;
for_each([&](auto slice) { size += slice.size(); }, cs, top_bits);
if (size % 8 != 0) {
return td::Status::Error("Size is not divisible by 8");
}
std::string res(size / 8, 0);
td::BitPtr to(td::MutableSlice(res).ubegin());
for_each([&](auto slice) { to.concat(slice); }, cs, top_bits);
CHECK(to.offs == (int)size);
return res;
}
} // namespace vm

View file

@ -0,0 +1,22 @@
#pragma once
#include "td/utils/Status.h"
#include "vm/cells/CellBuilder.h"
namespace vm {
class CellString {
public:
static constexpr unsigned int max_bytes = 1024;
static constexpr unsigned int max_chain_length = 16;
static td::Status store(CellBuilder &cb, td::Slice slice, unsigned int top_bits = Cell::max_bits);
static td::Status store(CellBuilder &cb, td::BitSlice slice, unsigned int top_bits = Cell::max_bits);
static td::Result<td::string> load(CellSlice &cs, unsigned int top_bits = Cell::max_bits);
private:
template <class F>
static void for_each(F &&f, CellSlice &cs, unsigned int top_bits = Cell::max_bits);
};
} // namespace vm

View file

@ -744,6 +744,9 @@ int VmState::run() {
}
} while (!res);
// LOG(INFO) << "[EN] data cells: " << DataCell::get_total_data_cells();
if ((res | 1) == -1) {
commit();
}
return res;
}

View file

@ -154,7 +154,7 @@ struct ControlData {
class Continuation : public td::CntObject {
public:
virtual int jump(VmState* st) const & = 0;
virtual int jump(VmState* st) const& = 0;
virtual int jump_w(VmState* st) &;
virtual ControlData* get_cdata() {
return 0;
@ -184,7 +184,7 @@ class QuitCont : public Continuation {
QuitCont(int _code = 0) : exit_code(_code) {
}
~QuitCont() override = default;
int jump(VmState* st) const & override {
int jump(VmState* st) const& override {
return ~exit_code;
}
};
@ -193,7 +193,7 @@ class ExcQuitCont : public Continuation {
public:
ExcQuitCont() = default;
~ExcQuitCont() override = default;
int jump(VmState* st) const & override;
int jump(VmState* st) const& override;
};
class PushIntCont : public Continuation {
@ -204,7 +204,7 @@ class PushIntCont : public Continuation {
PushIntCont(int val, Ref<Continuation> _next) : push_val(val), next(_next) {
}
~PushIntCont() override = default;
int jump(VmState* st) const & override;
int jump(VmState* st) const& override;
int jump_w(VmState* st) & override;
};
@ -217,7 +217,7 @@ class RepeatCont : public Continuation {
: body(std::move(_body)), after(std::move(_after)), count(_count) {
}
~RepeatCont() override = default;
int jump(VmState* st) const & override;
int jump(VmState* st) const& override;
int jump_w(VmState* st) & override;
};
@ -228,7 +228,7 @@ class AgainCont : public Continuation {
AgainCont(Ref<Continuation> _body) : body(std::move(_body)) {
}
~AgainCont() override = default;
int jump(VmState* st) const & override;
int jump(VmState* st) const& override;
int jump_w(VmState* st) & override;
};
@ -239,7 +239,7 @@ class UntilCont : public Continuation {
UntilCont(Ref<Continuation> _body, Ref<Continuation> _after) : body(std::move(_body)), after(std::move(_after)) {
}
~UntilCont() override = default;
int jump(VmState* st) const & override;
int jump(VmState* st) const& override;
int jump_w(VmState* st) & override;
};
@ -252,7 +252,7 @@ class WhileCont : public Continuation {
: cond(std::move(_cond)), body(std::move(_body)), after(std::move(_after)), chkcond(_chk) {
}
~WhileCont() override = default;
int jump(VmState* st) const & override;
int jump(VmState* st) const& override;
int jump_w(VmState* st) & override;
};
@ -268,7 +268,7 @@ class ArgContExt : public Continuation {
ArgContExt(const ArgContExt&) = default;
ArgContExt(ArgContExt&&) = default;
~ArgContExt() override = default;
int jump(VmState* st) const & override;
int jump(VmState* st) const& override;
int jump_w(VmState* st) & override;
ControlData* get_cdata() override {
return &data;
@ -303,7 +303,7 @@ class OrdCont : public Continuation {
td::CntObject* make_copy() const override {
return new OrdCont{*this};
}
int jump(VmState* st) const & override;
int jump(VmState* st) const& override;
int jump_w(VmState* st) & override;
ControlData* get_cdata() override {
@ -321,7 +321,7 @@ class OrdCont : public Continuation {
Ref<Stack> get_stack_ref() const {
return data.stack;
}
Ref<OrdCont> copy_ord() const & {
Ref<OrdCont> copy_ord() const& {
return Ref<OrdCont>{true, *this};
}
Ref<OrdCont> copy_ord() && {
@ -375,10 +375,16 @@ struct GasLimits {
}
};
struct CommittedState {
Ref<vm::Cell> c4, c5;
bool committed{false};
};
class VmState final : public VmStateInterface {
Ref<CellSlice> code;
Ref<Stack> stack;
ControlRegs cr;
CommittedState cstate;
int cp;
long long steps{0};
const DispatchTable* dispatch;
@ -388,6 +394,8 @@ class VmState final : public VmStateInterface {
std::vector<Ref<Cell>> libraries;
int stack_trace{0}, debug_off{0};
bool chksig_always_succeed{false};
public:
static constexpr unsigned cell_load_gas_price = 100, cell_create_gas_price = 500, exception_gas_price = 50,
tuple_entry_gas_price = 1;
@ -401,6 +409,10 @@ class VmState final : public VmStateInterface {
VmState(Ref<Cell> code_cell, Args&&... args)
: VmState(convert_code_cell(std::move(code_cell)), std::forward<Args>(args)...) {
}
VmState(const VmState&) = delete;
VmState(VmState&&) = delete;
VmState& operator=(const VmState&) = delete;
VmState& operator=(VmState&&) = delete;
bool set_gas_limits(long long _max, long long _limit, long long _credit = 0);
bool final_gas_ok() const {
return gas.final_ok();
@ -408,6 +420,12 @@ class VmState final : public VmStateInterface {
long long gas_consumed() const {
return gas.gas_consumed();
}
bool committed() const {
return cstate.committed;
}
const CommittedState& get_committed_state() const {
return cstate;
}
void consume_gas(long long amount) {
gas.consume(amount);
}
@ -567,6 +585,18 @@ class VmState final : public VmStateInterface {
return cont->is_unique() ? cont.unique_write().jump_w(this) : cont->jump(this);
}
static Ref<CellSlice> convert_code_cell(Ref<Cell> code_cell);
void commit() {
cstate.c4 = cr.d[0];
cstate.c5 = cr.d[1];
cstate.committed = true;
}
void set_chksig_always_succeed(bool flag) {
chksig_always_succeed = flag;
}
bool get_chksig_always_succeed() const {
return chksig_always_succeed;
}
private:
void init_cregs(bool same_c3 = false, bool push_0 = true);

View file

@ -18,6 +18,8 @@
*/
#pragma once
#include "td/utils/Status.h"
namespace vm {
enum class Excno : int {
@ -95,4 +97,19 @@ struct VmVirtError {
struct VmFatal {};
template <class F>
auto try_f(F&& f) noexcept -> decltype(f()) {
try {
return f();
} catch (vm::VmError error) {
return td::Status::Error(PSLICE() << "Got a vm exception: " << error.get_msg());
} catch (vm::VmVirtError error) {
return td::Status::Error(PSLICE() << "Got a vm virtualization exception: " << error.get_msg());
} catch (vm::VmNoGas error) {
return td::Status::Error(PSLICE() << "Got a vm no gas exception: " << error.get_msg());
}
}
#define TRY_VM(f) ::vm::try_f([&] { return f; })
} // namespace vm

View file

@ -75,10 +75,17 @@ int exec_set_gas_limit(VmState* st) {
return exec_set_gas_generic(st, gas);
}
int exec_commit(VmState* st) {
VM_LOG(st) << "execute COMMIT";
st->commit();
return 0;
}
void register_basic_gas_ops(OpcodeTable& cp0) {
using namespace std::placeholders;
cp0.insert(OpcodeInstr::mksimple(0xf800, 16, "ACCEPT", exec_accept))
.insert(OpcodeInstr::mksimple(0xf801, 16, "SETGASLIMIT", exec_set_gas_limit));
.insert(OpcodeInstr::mksimple(0xf801, 16, "SETGASLIMIT", exec_set_gas_limit))
.insert(OpcodeInstr::mksimple(0xf80f, 16, "COMMIT", exec_commit));
}
void register_ton_gas_ops(OpcodeTable& cp0) {
@ -268,7 +275,7 @@ int exec_ed25519_check_signature(VmState* st, bool from_slice) {
}
td::Ed25519::PublicKey pub_key{td::SecureString(td::Slice{key, 32})};
auto res = pub_key.verify_signature(td::Slice{data, data_len}, td::Slice{signature, 64});
stack.push_bool(res.is_ok());
stack.push_bool(res.is_ok() || st->get_chksig_always_succeed());
return 0;
}

View file

@ -2160,7 +2160,8 @@ Of the following primitives, only the first two are ``pure'' in the sense that t
\item {\tt F802} --- {\tt BUYGAS} ($x$ -- ), computes the amount of gas that can be bought for $x$ nanograms, and sets $g_l$ accordingly in the same way as {\tt SETGASLIMIT}.
\item {\tt F804} --- {\tt GRAMTOGAS} ($x$ -- $g$), computes the amount of gas that can be bought for $x$ nanograms. If $x$ is negative, returns 0. If $g$ exceeds $2^{63}-1$, it is replaced with this value.
\item {\tt F805} --- {\tt GASTOGRAM} ($g$ -- $x$), computes the price of $g$ gas in nanograms.
\item {\tt F806}--{\tt F80F} --- Reserved for gas-related primitives.
\item {\tt F806}--{\tt F80E} --- Reserved for gas-related primitives.
\item {\tt F80F} --- {\tt COMMIT} ( -- ), commits the current state of registers {\tt c4} (``persistent data'') and {\tt c5} (``actions'') so that the current execution is considered ``successful'' with the saved values even if an exception is thrown later.
\end{itemize}
\nxsubpoint\emb{Pseudo-random number generator primitives}

View file

@ -13,18 +13,18 @@ import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* Main class for interaction with the TDLib.
* Main class for interaction with the tonlib.
*/
public final class Client implements Runnable {
static {
System.loadLibrary("native-lib");
}
/**
* Interface for handler for results of queries to TDLib and incoming updates from TDLib.
* Interface for handler for results of queries to tonlib and incoming updates from tonlib.
*/
public interface ResultHandler {
/**
* Callback called on result of query to TDLib or incoming update from TDLib.
* Callback called on result of query to tonlib or incoming update from tonlib.
*
* @param object Result of query or update of type TonApi.Update about new events.
*/
@ -46,9 +46,9 @@ public final class Client implements Runnable {
}
/**
* Sends a request to the TDLib.
* Sends a request to the tonlib.
*
* @param query Object representing a query to the TDLib.
* @param query Object representing a query to the tonlib.
* @param resultHandler Result handler with onResult method which will be called with result
* of the query or with TonApi.Error as parameter. If it is null, nothing
* will be called.
@ -80,9 +80,9 @@ public final class Client implements Runnable {
}
/**
* Sends a request to the TDLib with an empty ExceptionHandler.
* Sends a request to the tonlib with an empty ExceptionHandler.
*
* @param query Object representing a query to the TDLib.
* @param query Object representing a query to the tonlib.
* @param resultHandler Result handler with onResult method which will be called with result
* of the query or with TonApi.Error as parameter. If it is null, then
* defaultExceptionHandler will be called.
@ -93,9 +93,9 @@ public final class Client implements Runnable {
}
/**
* Synchronously executes a TDLib request. Only a few marked accordingly requests can be executed synchronously.
* Synchronously executes a tonlib request. Only a few marked accordingly requests can be executed synchronously.
*
* @param query Object representing a query to the TDLib.
* @param query Object representing a query to the tonlib.
* @return request result.
* @throws NullPointerException if query is null.
*/
@ -107,10 +107,10 @@ public final class Client implements Runnable {
}
/**
* Replaces handler for incoming updates from the TDLib.
* Replaces handler for incoming updates from the tonlib.
*
* @param updatesHandler Handler with onResult method which will be called for every incoming
* update from the TDLib.
* update from the tonlib.
* @param exceptionHandler Exception handler with onException method which will be called on
* exception thrown from updatesHandler, if it is null, defaultExceptionHandler will be invoked.
*/
@ -119,10 +119,10 @@ public final class Client implements Runnable {
}
/**
* Replaces handler for incoming updates from the TDLib. Sets empty ExceptionHandler.
* Replaces handler for incoming updates from the tonlib. Sets empty ExceptionHandler.
*
* @param updatesHandler Handler with onResult method which will be called for every incoming
* update from the TDLib.
* update from the tonlib.
*/
public void setUpdatesHandler(ResultHandler updatesHandler) {
setUpdatesHandler(updatesHandler, null);
@ -157,7 +157,7 @@ public final class Client implements Runnable {
*/
public static Client create(ResultHandler updatesHandler, ExceptionHandler updatesExceptionHandler, ExceptionHandler defaultExceptionHandler) {
Client client = new Client(updatesHandler, updatesExceptionHandler, defaultExceptionHandler);
new Thread(client, "TDLib thread").start();
new Thread(client, "tonlib thread").start();
return client;
}

View file

@ -13,18 +13,18 @@ import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* Main class for interaction with the TDLib.
* Main class for interaction with the tonlib.
*/
public final class Client implements Runnable {
static {
System.loadLibrary("native-lib");
}
/**
* Interface for handler for results of queries to TDLib and incoming updates from TDLib.
* Interface for handler for results of queries to tonlib and incoming updates from tonlib.
*/
public interface ResultHandler {
/**
* Callback called on result of query to TDLib or incoming update from TDLib.
* Callback called on result of query to tonlib or incoming update from tonlib.
*
* @param object Result of query or update of type TonApi.Update about new events.
*/
@ -46,9 +46,9 @@ public final class Client implements Runnable {
}
/**
* Sends a request to the TDLib.
* Sends a request to the tonlib.
*
* @param query Object representing a query to the TDLib.
* @param query Object representing a query to the tonlib.
* @param resultHandler Result handler with onResult method which will be called with result
* of the query or with TonApi.Error as parameter. If it is null, nothing
* will be called.
@ -80,9 +80,9 @@ public final class Client implements Runnable {
}
/**
* Sends a request to the TDLib with an empty ExceptionHandler.
* Sends a request to the tonlib with an empty ExceptionHandler.
*
* @param query Object representing a query to the TDLib.
* @param query Object representing a query to the tonlib.
* @param resultHandler Result handler with onResult method which will be called with result
* of the query or with TonApi.Error as parameter. If it is null, then
* defaultExceptionHandler will be called.
@ -93,9 +93,9 @@ public final class Client implements Runnable {
}
/**
* Synchronously executes a TDLib request. Only a few marked accordingly requests can be executed synchronously.
* Synchronously executes a tonlib request. Only a few marked accordingly requests can be executed synchronously.
*
* @param query Object representing a query to the TDLib.
* @param query Object representing a query to the tonlib.
* @return request result.
* @throws NullPointerException if query is null.
*/
@ -107,10 +107,10 @@ public final class Client implements Runnable {
}
/**
* Replaces handler for incoming updates from the TDLib.
* Replaces handler for incoming updates from the tonlib.
*
* @param updatesHandler Handler with onResult method which will be called for every incoming
* update from the TDLib.
* update from the tonlib.
* @param exceptionHandler Exception handler with onException method which will be called on
* exception thrown from updatesHandler, if it is null, defaultExceptionHandler will be invoked.
*/
@ -119,10 +119,10 @@ public final class Client implements Runnable {
}
/**
* Replaces handler for incoming updates from the TDLib. Sets empty ExceptionHandler.
* Replaces handler for incoming updates from the tonlib. Sets empty ExceptionHandler.
*
* @param updatesHandler Handler with onResult method which will be called for every incoming
* update from the TDLib.
* update from the tonlib.
*/
public void setUpdatesHandler(ResultHandler updatesHandler) {
setUpdatesHandler(updatesHandler, null);
@ -157,7 +157,7 @@ public final class Client implements Runnable {
*/
public static Client create(ResultHandler updatesHandler, ExceptionHandler updatesExceptionHandler, ExceptionHandler defaultExceptionHandler) {
Client client = new Client(updatesHandler, updatesExceptionHandler, defaultExceptionHandler);
new Thread(client, "TDLib thread").start();
new Thread(client, "tonlib thread").start();
return client;
}

View file

@ -28,12 +28,12 @@
#include <iostream>
#include <tonlib/tonlib_client_json.h>
// Basic example of TONLib JSON interface usage.
// Basic example of tonlib JSON interface usage.
// Native interface should be preferred instead in C++, so here is only an example of
// the main event cycle, which should be essentially the same for all languages.
int main() {
// disable TDLib logging
// disable tonlib logging
void *client = tonlib_client_json_create();
// somehow share the client with other threads, which will be able to send requests via tonlib_client_json_send

View file

@ -4,6 +4,7 @@
#include "tl-utils/lite-utils.hpp"
#include "ton/lite-tl.hpp"
#include "td/utils/overloaded.h"
#include "td/utils/Random.h"
using namespace std::literals::string_literals;
@ -74,4 +75,25 @@ td::Result<std::unique_ptr<block::BlockProofChain>> deserialize_proof_chain(
LOG(DEBUG) << "deserialized a BlkProofChain of " << chain->link_count() << " links";
return std::move(chain);
}
td::Ref<vm::Tuple> prepare_vm_c7(ton::UnixTime now, ton::LogicalTime lt, td::Ref<vm::CellSlice> my_addr,
const block::CurrencyCollection& balance) {
td::BitArray<256> rand_seed;
td::RefInt256 rand_seed_int{true};
td::Random::secure_bytes(rand_seed.as_slice());
if (!rand_seed_int.unique_write().import_bits(rand_seed.cbits(), 256, false)) {
return {};
}
auto tuple = vm::make_tuple_ref(td::make_refint(0x076ef1ea), // [ magic:0x076ef1ea
td::make_refint(0), // actions:Integer
td::make_refint(0), // msgs_sent:Integer
td::make_refint(now), // unixtime:Integer
td::make_refint(lt), // block_lt:Integer
td::make_refint(lt), // trans_lt:Integer
std::move(rand_seed_int), // rand_seed:Integer
balance.as_vm_tuple(), // balance_remaining:[Integer (Maybe Cell)]
my_addr, // myself:MsgAddressInt
vm::StackEntry()); // global_config:(Maybe Cell) ] = SmartContractInfo;
LOG(DEBUG) << "SmartContractInfo initialized with " << vm::StackEntry(tuple).to_string();
return vm::make_tuple_ref(std::move(tuple));
}
} // namespace liteclient

View file

@ -7,4 +7,7 @@
namespace liteclient {
td::Result<std::unique_ptr<block::BlockProofChain>> deserialize_proof_chain(
ton::lite_api::object_ptr<ton::lite_api::liteServer_partialBlockProof> f);
}
td::Ref<vm::Tuple> prepare_vm_c7(ton::UnixTime now, ton::LogicalTime lt, td::Ref<vm::CellSlice> my_addr,
const block::CurrencyCollection& balance);
} // namespace liteclient

View file

@ -1223,28 +1223,6 @@ void TestNode::got_account_state(ton::BlockIdExt ref_blk, ton::BlockIdExt blk, t
}
}
Ref<vm::Tuple> TestNode::prepare_vm_c7(ton::UnixTime now, ton::LogicalTime lt, Ref<vm::CellSlice> my_addr,
const block::CurrencyCollection& balance) const {
td::BitArray<256> rand_seed;
td::RefInt256 rand_seed_int{true};
prng::rand_gen().rand_bytes(rand_seed.data(), 32);
if (!rand_seed_int.unique_write().import_bits(rand_seed.cbits(), 256, false)) {
return {};
}
auto tuple = vm::make_tuple_ref(td::make_refint(0x076ef1ea), // [ magic:0x076ef1ea
td::make_refint(0), // actions:Integer
td::make_refint(0), // msgs_sent:Integer
td::make_refint(now), // unixtime:Integer
td::make_refint(lt), // block_lt:Integer
td::make_refint(lt), // trans_lt:Integer
std::move(rand_seed_int), // rand_seed:Integer
balance.as_vm_tuple(), // balance_remaining:[Integer (Maybe Cell)]
my_addr, // myself:MsgAddressInt
vm::StackEntry()); // global_config:(Maybe Cell) ] = SmartContractInfo;
LOG(DEBUG) << "SmartContractInfo initialized with " << vm::StackEntry(tuple).to_string();
return vm::make_tuple_ref(std::move(tuple));
}
void TestNode::run_smc_method(ton::BlockIdExt ref_blk, ton::BlockIdExt blk, ton::BlockIdExt shard_blk,
td::BufferSlice shard_proof, td::BufferSlice proof, td::BufferSlice state,
ton::WorkchainId workchain, ton::StdSmcAddress addr, std::string method,
@ -1309,7 +1287,7 @@ void TestNode::run_smc_method(ton::BlockIdExt ref_blk, ton::BlockIdExt blk, ton:
vm::GasLimits gas{gas_limit};
LOG(DEBUG) << "creating VM";
vm::VmState vm{code, std::move(stack), gas, 1, data, vm::VmLog()};
vm.set_c7(prepare_vm_c7(info.gen_utime, info.gen_lt, acc.addr, balance)); // tuple with SmartContractInfo
vm.set_c7(liteclient::prepare_vm_c7(info.gen_utime, info.gen_lt, acc.addr, balance)); // tuple with SmartContractInfo
// vm.incr_stack_trace(1); // enable stack dump after each step
LOG(INFO) << "starting VM to run method `" << method << "` (" << method_id << ") of smart contract " << workchain
<< ":" << addr.to_hex();

View file

@ -119,8 +119,6 @@ class TestNode : public td::actor::Actor {
td::BufferSlice shard_proof, td::BufferSlice proof, td::BufferSlice state,
ton::WorkchainId workchain, ton::StdSmcAddress addr, std::string method,
std::vector<vm::StackEntry> params);
Ref<vm::Tuple> prepare_vm_c7(ton::UnixTime now, ton::LogicalTime lt, Ref<vm::CellSlice> my_addr,
const block::CurrencyCollection& balance) const;
bool get_all_shards(bool use_last = true, ton::BlockIdExt blkid = {});
void got_all_shards(ton::BlockIdExt blk, td::BufferSlice proof, td::BufferSlice data);
bool get_config_params(ton::BlockIdExt blkid, int mode = 0, std::string filename = "");

View file

@ -1145,6 +1145,273 @@ void run_queue_bench(int n, int m) {
#endif
}
struct Sem {
public:
void post() {
if (++cnt_ == 0) {
{
std::unique_lock<std::mutex> lk(mutex_);
}
cnd_.notify_one();
}
}
void wait(int cnt = 1) {
auto was = cnt_.fetch_sub(cnt);
if (was >= cnt) {
return;
}
std::unique_lock<std::mutex> lk(mutex_);
cnd_.wait(lk, [&] { return cnt_ >= 0; });
}
private:
std::mutex mutex_;
std::condition_variable cnd_;
std::atomic<int> cnt_{0};
};
class ChainedSpawn : public td::Benchmark {
public:
ChainedSpawn(bool use_io) : use_io_(use_io) {
}
std::string get_description() const {
return PSTRING() << "Chained create_actor use_io(" << use_io_ << ")";
}
void run(int n) {
class Task : public td::actor::Actor {
public:
Task(int n, Sem *sem) : n_(n), sem_(sem) {
}
void start_up() override {
if (n_ == 0) {
sem_->post();
} else {
td::actor::create_actor<Task>("Task", n_ - 1, sem_).release();
}
stop();
};
private:
int n_;
Sem *sem_{nullptr};
};
td::actor::Scheduler scheduler{{8}};
auto sch = td::thread([&] { scheduler.run(); });
Sem sem;
scheduler.run_in_context_external([&] {
for (int i = 0; i < n; i++) {
td::actor::create_actor<Task>(td::actor::ActorOptions().with_name("Task").with_poll(use_io_), 1000, &sem)
.release();
sem.wait();
}
td::actor::SchedulerContext::get()->stop();
});
sch.join();
}
private:
bool use_io_{false};
};
class ChainedSpawnInplace : public td::Benchmark {
public:
ChainedSpawnInplace(bool use_io) : use_io_(use_io) {
}
std::string get_description() const {
return PSTRING() << "Chained send_signal(self) use_io(" << use_io_ << ")";
}
void run(int n) {
class Task : public td::actor::Actor {
public:
Task(int n, Sem *sem) : n_(n), sem_(sem) {
}
void loop() override {
if (n_ == 0) {
sem_->post();
stop();
} else {
n_--;
send_signals(actor_id(this), td::actor::ActorSignals::wakeup());
}
};
private:
int n_;
Sem *sem_;
};
td::actor::Scheduler scheduler{{8}};
auto sch = td::thread([&] { scheduler.run(); });
Sem sem;
scheduler.run_in_context_external([&] {
for (int i = 0; i < n; i++) {
td::actor::create_actor<Task>(td::actor::ActorOptions().with_name("Task").with_poll(use_io_), 1000, &sem)
.release();
sem.wait();
}
td::actor::SchedulerContext::get()->stop();
});
sch.join();
}
private:
bool use_io_{false};
};
class PingPong : public td::Benchmark {
public:
PingPong(bool use_io) : use_io_(use_io) {
}
std::string get_description() const {
return PSTRING() << "PingPong use_io(" << use_io_ << ")";
}
void run(int n) {
if (n < 3) {
n = 3;
}
class Task : public td::actor::Actor {
public:
explicit Task(Sem *sem) : sem_(sem) {
}
void set_peer(td::actor::ActorId<Task> peer) {
peer_ = peer;
}
void ping(int n) {
if (n < 0) {
sem_->post();
stop();
}
send_closure(peer_, &Task::ping, n - 1);
}
private:
td::actor::ActorId<Task> peer_;
Sem *sem_;
};
td::actor::Scheduler scheduler{{8}};
auto sch = td::thread([&] { scheduler.run(); });
Sem sem;
scheduler.run_in_context_external([&] {
for (int i = 0; i < n; i++) {
auto a = td::actor::create_actor<Task>(td::actor::ActorOptions().with_name("Task").with_poll(use_io_), &sem)
.release();
auto b = td::actor::create_actor<Task>(td::actor::ActorOptions().with_name("Task").with_poll(use_io_), &sem)
.release();
send_closure(a, &Task::set_peer, b);
send_closure(b, &Task::set_peer, a);
send_closure(a, &Task::ping, 1000);
sem.wait(2);
}
td::actor::SchedulerContext::get()->stop();
});
sch.join();
}
private:
bool use_io_{false};
};
class SpawnMany : public td::Benchmark {
public:
SpawnMany(bool use_io) : use_io_(use_io) {
}
std::string get_description() const {
return PSTRING() << "Spawn many use_io(" << use_io_ << ")";
}
void run(int n) {
class Task : public td::actor::Actor {
public:
Task(Sem *sem) : sem_(sem) {
}
void start_up() override {
sem_->post();
stop();
};
private:
Sem *sem_;
};
td::actor::Scheduler scheduler{{8}};
Sem sem;
auto sch = td::thread([&] { scheduler.run(); });
scheduler.run_in_context_external([&] {
for (int i = 0; i < n; i++) {
int spawn_cnt = 10000;
for (int j = 0; j < spawn_cnt; j++) {
td::actor::create_actor<Task>(td::actor::ActorOptions().with_name("Task").with_poll(use_io_), &sem).release();
}
sem.wait(spawn_cnt);
}
td::actor::SchedulerContext::get()->stop();
});
sch.join();
}
private:
bool use_io_{false};
};
class YieldMany : public td::Benchmark {
public:
YieldMany(bool use_io) : use_io_(use_io) {
}
std::string get_description() const {
return PSTRING() << "Yield many use_io(" << use_io_ << ")";
}
void run(int n) {
int num_yield = 1000;
unsigned tasks_per_cpu = 50;
unsigned cpu_n = td::thread::hardware_concurrency();
class Task : public td::actor::Actor {
public:
explicit Task(int n, Sem *sem) : n_(n), sem_(sem) {
}
void loop() override {
if (n_ == 0) {
sem_->post();
stop();
} else {
n_--;
yield();
}
};
private:
int n_;
Sem *sem_;
};
td::actor::Scheduler scheduler{{cpu_n}};
auto sch = td::thread([&] { scheduler.run(); });
unsigned tasks = tasks_per_cpu * cpu_n;
Sem sem;
scheduler.run_in_context_external([&] {
for (int i = 0; i < n; i++) {
for (unsigned j = 0; j < tasks; j++) {
td::actor::create_actor<Task>(td::actor::ActorOptions().with_name("Task").with_poll(use_io_), num_yield, &sem)
.release();
}
sem.wait(tasks);
}
});
scheduler.run_in_context_external([&] { td::actor::SchedulerContext::get()->stop(); });
sch.join();
}
private:
bool use_io_{false};
};
int main(int argc, char **argv) {
if (argc > 1) {
if (argv[1][0] == 'a') {
@ -1159,6 +1426,18 @@ int main(int argc, char **argv) {
return 0;
}
bench(YieldMany(false));
bench(YieldMany(true));
bench(SpawnMany(false));
bench(SpawnMany(true));
bench(PingPong(false));
bench(PingPong(true));
bench(ChainedSpawnInplace(false));
bench(ChainedSpawnInplace(true));
bench(ChainedSpawn(false));
bench(ChainedSpawn(true));
return 0;
bench(ActorDummyQuery());
bench(ActorExecutorBenchmark());
bench(ActorSignalQuery());

View file

@ -225,6 +225,7 @@ class Promise {
promise_->set_error(std::move(error));
promise_.reset();
}
void set_result(Result<T> &&result) {
if (!promise_) {
return;
@ -260,6 +261,28 @@ class Promise {
explicit operator bool() {
return static_cast<bool>(promise_);
}
template <class V, class F>
auto do_wrap(V &&value, F &&func) {
if (value.is_ok()) {
set_result(func(value.move_as_ok()));
} else {
set_error(value.move_as_error());
}
}
template <class F>
auto do_wrap(td::Status status, F &&func) {
set_error(std::move(status));
}
template <class F>
auto wrap(F &&func) {
return [promise = std::move(*this), func = std::move(func)](auto &&res) mutable {
promise.do_wrap(std::move(res), std::move(func));
};
}
template <class... ArgsT>
auto send_closure(ArgsT &&... args);
private:
std::unique_ptr<PromiseInterface<T>> promise_;

View file

@ -162,4 +162,29 @@ void send_signals_later(ActorIdT &&actor_id, ActorSignals signals) {
detail::send_signals_later(id.as_actor_ref(), signals);
}
} // namespace actor
class SendClosure {
public:
template <class... ArgsT>
void operator()(ArgsT &&... args) const {
td::actor::send_closure(std::forward<ArgsT>(args)...);
}
};
template <class T>
template <class... ArgsT>
auto Promise<T>::send_closure(ArgsT &&... args) {
return [promise = std::move(*this), t = std::make_tuple(std::forward<ArgsT>(args)...)](auto &&r_res) mutable {
TRY_RESULT_PROMISE(promise, res, std::move(r_res));
td::call_tuple(SendClosure(), std::tuple_cat(std::move(t), std::make_tuple(std::move(res), std::move(promise))));
};
}
template <class... ArgsT>
auto promise_send_closure(ArgsT &&... args) {
return [t = std::make_tuple(std::forward<ArgsT>(args)...)](auto &&res) mutable {
td::call_tuple(SendClosure(), std::tuple_cat(std::move(t), std::make_tuple(std::move(res))));
};
}
} // namespace td

View file

@ -73,5 +73,57 @@ void TcpListener::loop() {
return stop();
}
}
TcpInfiniteListener::TcpInfiniteListener(int32 port, std::unique_ptr<TcpListener::Callback> callback)
: port_(port), callback_(std::move(callback)) {
}
void TcpInfiniteListener::start_up() {
loop();
}
void TcpInfiniteListener::hangup() {
close_flag_ = true;
tcp_listener_.reset();
if (refcnt_ == 0) {
stop();
}
}
void TcpInfiniteListener::loop() {
if (!tcp_listener_.empty()) {
return;
}
class Callback : public TcpListener::Callback {
public:
Callback(actor::ActorShared<TcpInfiniteListener> parent) : parent_(std::move(parent)) {
}
void accept(SocketFd fd) override {
actor::send_closure(parent_, &TcpInfiniteListener::accept, std::move(fd));
}
private:
actor::ActorShared<TcpInfiniteListener> parent_;
};
refcnt_++;
tcp_listener_ = actor::create_actor<TcpListener>(
actor::ActorOptions().with_name(PSLICE() << "TcpListener" << tag("port", port_)).with_poll(), port_,
std::make_unique<Callback>(actor_shared(this)));
}
void TcpInfiniteListener::accept(SocketFd fd) {
callback_->accept(std::move(fd));
}
void TcpInfiniteListener::hangup_shared() {
refcnt_--;
tcp_listener_.reset();
if (close_flag_) {
if (refcnt_ == 0) {
stop();
}
} else {
alarm_timestamp() = Timestamp::in(5 /*5 seconds*/);
}
}
} // namespace td

View file

@ -49,4 +49,23 @@ class TcpListener : public td::actor::Actor, private td::ObserverBase {
void loop() override;
};
class TcpInfiniteListener : public actor::Actor {
public:
TcpInfiniteListener(int32 port, std::unique_ptr<TcpListener::Callback> callback);
private:
int32 port_;
std::unique_ptr<TcpListener::Callback> callback_;
actor::ActorOwn<TcpListener> tcp_listener_;
int32 refcnt_{0};
bool close_flag_{false};
void start_up() override;
void hangup() override;
void loop() override;
void accept(SocketFd fd);
void hangup_shared() override;
};
} // namespace td

View file

@ -130,70 +130,6 @@ void UdpServerImpl::hangup_shared() {
stop();
}
class TcpInfiniteListener : public actor::Actor {
public:
TcpInfiniteListener(int32 port, std::unique_ptr<TcpListener::Callback> callback)
: port_(port), callback_(std::move(callback)) {
}
private:
int32 port_;
std::unique_ptr<TcpListener::Callback> callback_;
actor::ActorOwn<TcpListener> tcp_listener_;
int32 refcnt_{0};
bool close_flag_{false};
void start_up() override {
loop();
}
void hangup() override {
close_flag_ = true;
tcp_listener_.reset();
if (refcnt_ == 0) {
stop();
}
}
void loop() override {
if (!tcp_listener_.empty()) {
return;
}
class Callback : public TcpListener::Callback {
public:
Callback(actor::ActorShared<TcpInfiniteListener> parent) : parent_(std::move(parent)) {
}
void accept(SocketFd fd) override {
actor::send_closure(parent_, &TcpInfiniteListener::accept, std::move(fd));
}
private:
actor::ActorShared<TcpInfiniteListener> parent_;
};
VLOG(udp_server) << "Create listener";
refcnt_++;
tcp_listener_ = actor::create_actor<TcpListener>(
actor::ActorOptions().with_name(PSLICE() << "TcpListener" << tag("port", port_)).with_poll(), port_,
std::make_unique<Callback>(actor_shared(this)));
}
void accept(SocketFd fd) {
callback_->accept(std::move(fd));
}
void hangup_shared() override {
refcnt_--;
tcp_listener_.reset();
if (close_flag_) {
if (refcnt_ == 0) {
stop();
}
} else {
alarm_timestamp() = Timestamp::in(5 /*5 seconds*/);
}
}
};
class TcpClient : public td::actor::Actor, td::ObserverBase {
public:
class Callback {

View file

@ -48,6 +48,15 @@
} \
}
#define TRY_STATUS_PROMISE(promise_name, status) \
{ \
auto try_status = (status); \
if (try_status.is_error()) { \
promise_name.set_error(std::move(try_status)); \
return; \
} \
}
#define TRY_RESULT(name, result) TRY_RESULT_IMPL(TD_CONCAT(TD_CONCAT(r_, name), __LINE__), auto name, result)
#define TRY_RESULT_PROMISE(promise_name, name, result) \
@ -309,6 +318,11 @@ class Status {
return std::move(*this);
}
Auto move_as_ok() {
UNREACHABLE();
return {};
}
Status move_as_error_prefix(const Status &status) const TD_WARN_UNUSED_RESULT {
return status.move_as_error_suffix(message());
}

View file

@ -128,7 +128,7 @@ auto invoke(F &&f,
}
template <class F, class... Args, std::size_t... S>
auto call_tuple_impl(F &func, std::tuple<Args...> &&tuple, IntSeq<S...>) {
auto call_tuple_impl(F &&func, std::tuple<Args...> &&tuple, IntSeq<S...>) {
return func(std::forward<Args>(std::get<S>(tuple))...);
}
@ -163,7 +163,7 @@ class LogicAnd {
};
template <class F, class... Args>
auto call_tuple(F &func, std::tuple<Args...> &&tuple) {
auto call_tuple(F &&func, std::tuple<Args...> &&tuple) {
return detail::call_tuple_impl(func, std::move(tuple), detail::IntRange<sizeof...(Args)>());
}

View file

@ -38,6 +38,34 @@
namespace td {
class RandomSteps {
public:
struct Step {
std::function<void()> func;
td::uint32 weight;
};
RandomSteps(std::vector<Step> steps) : steps_(std::move(steps)) {
for (auto &step : steps_) {
steps_sum_ += step.weight;
}
}
template <class Random>
void step(Random &rnd) {
auto w = rnd() % steps_sum_;
for (auto &step : steps_) {
if (w < step.weight) {
step.func();
break;
}
w -= step.weight;
}
}
private:
std::vector<Step> steps_;
td::int32 steps_sum_ = 0;
};
class RegressionTester {
public:
virtual ~RegressionTester() = default;

View file

@ -128,7 +128,7 @@ class JavadocTlDocumentationGenerator extends TlDocumentationGenerator
$this->addDocumentation('public class TdApi {', <<<EOT
$nullable_type_import/**
* This class contains as static nested classes all other TDLib interface
* This class contains as static nested classes all other tonlib interface
* type-classes and function-classes.
* <p>
* It has no inner classes, functions or public members.
@ -138,7 +138,7 @@ EOT
$this->addDocumentation(' public abstract static class Object {', <<<EOT
/**
* This class is a base class for all TDLib interface classes.
* This class is a base class for all tonlib interface classes.
*/
EOT
);
@ -159,7 +159,7 @@ EOT
$this->addDocumentation(' public abstract static class Function extends Object {', <<<EOT
/**
* This class is a base class for all TDLib interface function-classes.
* This class is a base class for all tonlib interface function-classes.
*/
EOT
);

View file

@ -411,13 +411,14 @@ db.root.key.config = db.root.Key;
db.celldb.value block_id:tonNode.blockIdExt prev:int256 next:int256 root_hash:int256 = db.celldb.Value;
db.celldb.key.value hash:int256 = db.celldb.key.Value;
db.block.info id:tonNode.blockIdExt flags:# prev_left:flags.1?tonNode.blockIdExt
db.block.info#4ac6e727 id:tonNode.blockIdExt flags:# prev_left:flags.1?tonNode.blockIdExt
prev_right:flags.2?tonNode.blockIdExt
next_left:flags.3?tonNode.blockIdExt
next_right:flags.4?tonNode.blockIdExt
lt:flags.13?long
ts:flags.14?int
state:flags.17?int256 = db.block.Info;
db.block.packedInfo id:tonNode.blockIdExt unixtime:int offset:long = db.block.Info;
db.block.archivedInfo id:tonNode.blockIdExt flags:# next:flags.0?tonNode.blockIdExt = db.block.Info;
db.blockdb.value next:tonNode.blockIdExt data:bytes = db.blockdb.Value;
@ -436,6 +437,7 @@ db.filedb.key.proof block_id:tonNode.blockIdExt = db.filedb.Key;
db.filedb.key.proofLink block_id:tonNode.blockIdExt = db.filedb.Key;
db.filedb.key.signatures block_id:tonNode.blockIdExt = db.filedb.Key;
db.filedb.key.candidate id:db.candidate.id = db.filedb.Key;
db.filedb.key.blockInfo block_id:tonNode.blockIdExt = db.filedb.Key;
db.filedb.value key:db.filedb.Key prev:int256 next:int256 file_hash:int256 = db.filedb.Value;
@ -462,6 +464,13 @@ db.lt.desc.value first_idx:int last_idx:int last_seqno:int last_lt:long last_ts:
db.lt.shard.value workchain:int shard:long = db.lt.shard.Value;
db.lt.status.value total_shards:int = db.lt.status.Value;
db.archive.index.key = db.archive.Key;
db.archive.package.key unixtime:int key:Bool = db.archive.Key;
db.archive.index.value packages:(vector int) key_packages:(vector int) = db.archive.index.Value;
db.archive.package.firstBlock workchain:int shard:long seqno:int lt:long = db.archive.package.FirstBlock;
db.archive.package.value unixtime:int key:Bool firstblocks:(vector db.archive.package.firstBlock) deleted:Bool = db.archive.package.Value;
---functions---
---types---

Binary file not shown.

View file

@ -24,7 +24,8 @@ config config:string blockchain_name:string use_callbacks_for_network:Bool ignor
options config:config keystore_type:KeyStoreType = Options;
key public_key:string secret:secureBytes = Key;
inputKey key:key local_password:secureBytes = InputKey;
inputKeyRegular key:key local_password:secureBytes = InputKey;
inputKeyFake = InputKey;
exportedKey word_list:vector<secureString> = ExportedKey;
exportedPemKey pem:secureString = ExportedPemKey;
exportedEncryptedKey data:secureBytes = ExportedEncryptedKey;
@ -68,29 +69,39 @@ sendGramsResult sent_until:int53 body_hash:bytes = SendGramsResult;
syncStateDone = SyncState;
syncStateInProgress from_seqno:int32 to_seqno:int32 current_seqno:int32 = SyncState;
fees in_fwd_fee:int53 storage_fee:int53 gas_fee:int53 fwd_fee:int53= Fees;
query.fees source_fees:fees destination_fees:fees = query.Fees;
query.info id:int53 valid_until:int53 body_hash:bytes = query.Info;
smc.info id:int53 = smc.Info;
updateSendLiteServerQuery id:int64 data:bytes = Update;
updateSyncState sync_state:SyncState = Update;
//@class LogStream @description Describes a stream to which TDLib internal log is written
//@class LogStream @description Describes a stream to which tonlib internal log is written
//@description The log is written to stderr or an OS specific log
logStreamDefault = LogStream;
//@description The log is written to a file @path Path to the file to where the internal TDLib log will be written @max_file_size Maximum size of the file to where the internal TDLib log is written before the file will be auto-rotated
//@description The log is written to a file @path Path to the file to where the internal tonlib log will be written @max_file_size Maximum size of the file to where the internal tonlib log is written before the file will be auto-rotated
logStreamFile path:string max_file_size:int53 = LogStream;
//@description The log is written nowhere
logStreamEmpty = LogStream;
//@description Contains a TDLib internal log verbosity level @verbosity_level Log verbosity level
//@description Contains a tonlib internal log verbosity level @verbosity_level Log verbosity level
logVerbosityLevel verbosity_level:int32 = LogVerbosityLevel;
//@description Contains a list of available TDLib internal log tags @tags List of log tags
//@description Contains a list of available tonlib internal log tags @tags List of log tags
logTags tags:vector<string> = LogTags;
data bytes:secureBytes = Data;
tvm.cell bytes:string = tvm.Cell;
liteServer.info now:int53 version:int32 capabilities:int64 = liteServer.Info;
---functions---
init options:options = Ok;
@ -101,13 +112,13 @@ options.setConfig config:config = Ok;
createNewKey local_password:secureBytes mnemonic_password:secureBytes random_extra_seed:secureBytes = Key;
deleteKey key:key = Ok;
deleteAllKeys = Ok;
exportKey input_key:inputKey = ExportedKey;
exportPemKey input_key:inputKey key_password:secureBytes = ExportedPemKey;
exportEncryptedKey input_key:inputKey key_password:secureBytes = ExportedEncryptedKey;
exportKey input_key:InputKey = ExportedKey;
exportPemKey input_key:InputKey key_password:secureBytes = ExportedPemKey;
exportEncryptedKey input_key:InputKey key_password:secureBytes = ExportedEncryptedKey;
importKey local_password:secureBytes mnemonic_password:secureBytes exported_key:exportedKey = Key;
importPemKey local_password:secureBytes key_password:secureBytes exported_key:exportedPemKey = Key;
importEncryptedKey local_password:secureBytes key_password:secureBytes exported_encrypted_key:exportedEncryptedKey = Key;
changeLocalPassword input_key:inputKey new_local_password:secureBytes = Key;
changeLocalPassword input_key:InputKey new_local_password:secureBytes = Key;
encrypt decrypted_data:secureBytes secret:secureBytes = Data;
decrypt encrypted_data:secureBytes secret:secureBytes = Data;
@ -121,18 +132,20 @@ getBip39Hints prefix:string = Bip39Hints;
//raw.init initial_account_state:raw.initialAccountState = Ok;
raw.getAccountAddress initital_account_state:raw.initialAccountState = AccountAddress;
raw.getAccountState account_address:accountAddress = raw.AccountState;
raw.sendMessage destination:accountAddress initial_account_state:bytes data:bytes = Ok;
raw.getTransactions account_address:accountAddress from_transaction_id:internal.transactionId = raw.Transactions;
raw.sendMessage body:bytes = Ok;
raw.createAndSendMessage destination:accountAddress initial_account_state:bytes data:bytes = Ok;
raw.createQuery destination:accountAddress init_code:bytes init_data:bytes body:bytes = query.Info;
testWallet.init private_key:inputKey = Ok;
testWallet.init private_key:InputKey = Ok;
testWallet.getAccountAddress initital_account_state:testWallet.initialAccountState = AccountAddress;
testWallet.getAccountState account_address:accountAddress = testWallet.AccountState;
testWallet.sendGrams private_key:inputKey destination:accountAddress seqno:int32 amount:int64 message:bytes = SendGramsResult;
testWallet.sendGrams private_key:InputKey destination:accountAddress seqno:int32 amount:int64 message:bytes = SendGramsResult;
wallet.init private_key:inputKey = Ok;
wallet.init private_key:InputKey = Ok;
wallet.getAccountAddress initital_account_state:wallet.initialAccountState = AccountAddress;
wallet.getAccountState account_address:accountAddress = wallet.AccountState;
wallet.sendGrams private_key:inputKey destination:accountAddress seqno:int32 valid_until:int53 amount:int64 message:bytes = SendGramsResult;
wallet.sendGrams private_key:InputKey destination:accountAddress seqno:int32 valid_until:int53 amount:int64 message:bytes = SendGramsResult;
testGiver.getAccountState = testGiver.AccountState;
testGiver.getAccountAddress = AccountAddress;
@ -142,36 +155,50 @@ sync = Ok;
//generic.getAccountAddress initital_account_state:generic.InitialAccountState = AccountAddress;
generic.getAccountState account_address:accountAddress = generic.AccountState;
generic.sendGrams private_key:inputKey source:accountAddress destination:accountAddress amount:int64 timeout:int32 allow_send_to_uninited:Bool message:bytes = SendGramsResult;
generic.sendGrams private_key:InputKey source:accountAddress destination:accountAddress amount:int64 timeout:int32 allow_send_to_uninited:Bool message:bytes = SendGramsResult;
generic.createSendGramsQuery private_key:InputKey source:accountAddress destination:accountAddress amount:int64 timeout:int32 allow_send_to_uninited:Bool message:bytes = query.Info;
query.send id:int53 = Ok;
query.forget id:int53 = Ok;
query.estimateFees id:int53 ignore_chksig:Bool = query.Fees;
query.getInfo id:int53 = query.Info;
smc.load account_address:accountAddress = smc.Info;
smc.getCode id:int53 = tvm.Cell;
smc.getData id:int53 = tvm.Cell;
smc.getState id:int53 = tvm.Cell;
onLiteServerQueryResult id:int64 bytes:bytes = Ok;
onLiteServerQueryError id:int64 error:error = Ok;
runTests dir:string = Ok;
//@description Sets new log stream for internal logging of TDLib. This is an offline method. Can be called before authorization. Can be called synchronously @log_stream New log stream
liteServer.getInfo = liteServer.Info;
//@description Sets new log stream for internal logging of tonlib. This is an offline method. Can be called before authorization. Can be called synchronously @log_stream New log stream
setLogStream log_stream:LogStream = Ok;
//@description Returns information about currently used log stream for internal logging of TDLib. This is an offline method. Can be called before authorization. Can be called synchronously
//@description Returns information about currently used log stream for internal logging of tonlib. This is an offline method. Can be called before authorization. Can be called synchronously
getLogStream = LogStream;
//@description Sets the verbosity level of the internal logging of TDLib. This is an offline method. Can be called before authorization. Can be called synchronously
//@description Sets the verbosity level of the internal logging of tonlib. This is an offline method. Can be called before authorization. Can be called synchronously
//@new_verbosity_level New value of the verbosity level for logging. Value 0 corresponds to fatal errors, value 1 corresponds to errors, value 2 corresponds to warnings and debug warnings, value 3 corresponds to informational, value 4 corresponds to debug, value 5 corresponds to verbose debug, value greater than 5 and up to 1023 can be used to enable even more logging
setLogVerbosityLevel new_verbosity_level:int32 = Ok;
//@description Returns current verbosity level of the internal logging of TDLib. This is an offline method. Can be called before authorization. Can be called synchronously
//@description Returns current verbosity level of the internal logging of tonlib. This is an offline method. Can be called before authorization. Can be called synchronously
getLogVerbosityLevel = LogVerbosityLevel;
//@description Returns list of available TDLib internal log tags, for example, ["actor", "binlog", "connections", "notifications", "proxy"]. This is an offline method. Can be called before authorization. Can be called synchronously
//@description Returns list of available tonlib internal log tags, for example, ["actor", "binlog", "connections", "notifications", "proxy"]. This is an offline method. Can be called before authorization. Can be called synchronously
getLogTags = LogTags;
//@description Sets the verbosity level for a specified TDLib internal log tag. This is an offline method. Can be called before authorization. Can be called synchronously
//@description Sets the verbosity level for a specified tonlib internal log tag. This is an offline method. Can be called before authorization. Can be called synchronously
//@tag Logging tag to change verbosity level @new_verbosity_level New verbosity level; 1-1024
setLogTagVerbosityLevel tag:string new_verbosity_level:int32 = Ok;
//@description Returns current verbosity level for a specified TDLib internal log tag. This is an offline method. Can be called before authorization. Can be called synchronously @tag Logging tag to change verbosity level
//@description Returns current verbosity level for a specified tonlib internal log tag. This is an offline method. Can be called before authorization. Can be called synchronously @tag Logging tag to change verbosity level
getLogTagVerbosityLevel tag:string = LogVerbosityLevel;
//@description Adds a message to TDLib internal log. This is an offline method. Can be called before authorization. Can be called synchronously
//@description Adds a message to tonlib internal log. This is an offline method. Can be called before authorization. Can be called synchronously
//@verbosity_level Minimum verbosity level needed for the message to be logged, 0-1023 @text Text of a message to log
addLogMessage verbosity_level:int32 text:string = Ok;

Binary file not shown.

View file

@ -5,42 +5,34 @@ if (NOT OPENSSL_FOUND)
endif()
set(TONLIB_SOURCE
tonlib/CellString.cpp
tonlib/Client.cpp
tonlib/Config.cpp
tonlib/ExtClient.cpp
tonlib/ExtClientLazy.cpp
tonlib/ExtClientOutbound.cpp
tonlib/GenericAccount.cpp
tonlib/KeyStorage.cpp
tonlib/KeyValue.cpp
tonlib/LastBlock.cpp
tonlib/LastBlockStorage.cpp
tonlib/LastConfig.cpp
tonlib/Logging.cpp
tonlib/TestGiver.cpp
tonlib/TestWallet.cpp
tonlib/TonlibClient.cpp
tonlib/utils.cpp
tonlib/Wallet.cpp
tonlib/CellString.h
tonlib/Client.h
tonlib/Config.h
tonlib/ExtClient.h
tonlib/ExtClientLazy.h
tonlib/ExtClientOutbound.h
tonlib/GenericAccount.h
tonlib/KeyStorage.h
tonlib/KeyValue.h
tonlib/LastBlock.h
tonlib/LastBlockStorage.h
tonlib/LastConfig.h
tonlib/Logging.h
tonlib/TestGiver.h
tonlib/TestWallet.h
tonlib/TonlibCallback.h
tonlib/TonlibClient.h
tonlib/utils.h
tonlib/Wallet.h
tonlib/keys/bip39.cpp
tonlib/keys/DecryptedKey.cpp
@ -64,7 +56,7 @@ target_include_directories(tonlib PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>/..
$<BUILD_INTERFACE:${OPENSSL_INCLUDE_DIR}>
)
target_link_libraries(tonlib PRIVATE tdactor adnllite tl_lite_api tl-lite-utils ton_crypto ton_block lite-client-common)
target_link_libraries(tonlib PRIVATE tdactor adnllite tl_lite_api tl-lite-utils ton_crypto ton_block lite-client-common smc-envelope)
target_link_libraries(tonlib PUBLIC tdutils tl_tonlib_api)
if (TONLIB_ENABLE_JNI AND NOT ANDROID) # jni is available by default on Android
@ -134,7 +126,7 @@ if (NOT TON_USE_ABSEIL)
if (WIN32)
set(WINGETOPT_TARGET wingetopt)
endif()
install(TARGETS tdnet keys crc32c tdactor adnllite tl_api tl-utils tl_lite_api tl-lite-utils ton_crypto ton_block ${WINGETOPT_TARGET}
install(TARGETS tdnet keys crc32c tdactor adnllite tl_api tl-utils tl_lite_api tl-lite-utils ton_crypto ton_block smc-envelope ${WINGETOPT_TARGET}
tdutils tl_tonlib_api tonlib lite-client-common Tonlib EXPORT Tonlib
LIBRARY DESTINATION lib
ARCHIVE DESTINATION lib

View file

@ -17,23 +17,14 @@
Copyright 2017-2019 Telegram Systems LLP
*/
#include "fift/Fift.h"
#include "fift/words.h"
#include "fift/utils.h"
#include "block/block.h"
#include "block/block-auto.h"
#include "vm/cells.h"
#include "vm/boc.h"
#include "vm/cells/MerkleProof.h"
#include "vm/cells/CellString.h"
#include "tonlib/CellString.h"
#include "tonlib/utils.h"
#include "tonlib/TestGiver.h"
#include "tonlib/TestWallet.h"
#include "tonlib/Wallet.h"
#include "tonlib/GenericAccount.h"
#include "tonlib/TonlibClient.h"
#include "tonlib/Client.h"
@ -70,181 +61,6 @@ TEST(Tonlib, CellString) {
using namespace tonlib;
std::string current_dir() {
return td::PathView(td::realpath(__FILE__).move_as_ok()).parent_dir().str();
}
std::string load_source(std::string name) {
return td::read_file_str(current_dir() + "../../crypto/" + name).move_as_ok();
}
td::Ref<vm::Cell> get_test_wallet_source() {
std::string code = R"ABCD(
SETCP0 DUP IFNOTRET // return if recv_internal
DUP 85143 INT EQUAL IFJMP:<{ // "seqno" get-method
DROP c4 PUSHCTR CTOS 32 PLDU // cnt
}>
INC 32 THROWIF // fail unless recv_external
512 INT LDSLICEX DUP 32 PLDU // sign cs cnt
c4 PUSHCTR CTOS 32 LDU 256 LDU ENDS // sign cs cnt cnt' pubk
s1 s2 XCPU // sign cs cnt pubk cnt' cnt
EQUAL 33 THROWIFNOT // ( seqno mismatch? )
s2 PUSH HASHSU // sign cs cnt pubk hash
s0 s4 s4 XC2PU // pubk cs cnt hash sign pubk
CHKSIGNU // pubk cs cnt ?
34 THROWIFNOT // signature mismatch
ACCEPT
SWAP 32 LDU NIP
DUP SREFS IF:<{
// 3 INT 35 LSHIFT# 3 INT RAWRESERVE // reserve all but 103 Grams from the balance
8 LDU LDREF // pubk cnt mode msg cs
s0 s2 XCHG SENDRAWMSG // pubk cnt cs ; ( message sent )
}>
ENDS
INC NEWC 32 STU 256 STU ENDC c4 POPCTR
)ABCD";
return fift::compile_asm(code).move_as_ok();
}
td::Ref<vm::Cell> get_wallet_source() {
std::string code = R"ABCD(
SETCP0 DUP IFNOTRET // return if recv_internal
DUP 85143 INT EQUAL IFJMP:<{ // "seqno" get-method
DROP c4 PUSHCTR CTOS 32 PLDU // cnt
}>
INC 32 THROWIF // fail unless recv_external
9 PUSHPOW2 LDSLICEX DUP 32 LDU 32 LDU // signature in_msg msg_seqno valid_until cs
SWAP NOW LEQ 35 THROWIF // signature in_msg msg_seqno cs
c4 PUSH CTOS 32 LDU 256 LDU ENDS // signature in_msg msg_seqno cs stored_seqno public_key
s3 s1 XCPU // signature in_msg public_key cs stored_seqno msg_seqno stored_seqno
EQUAL 33 THROWIFNOT // signature in_msg public_key cs stored_seqno
s0 s3 XCHG HASHSU // signature stored_seqno public_key cs hash
s0 s4 s2 XC2PU CHKSIGNU 34 THROWIFNOT // cs stored_seqno public_key
ACCEPT
s0 s2 XCHG // public_key stored_seqno cs
WHILE:<{
DUP SREFS // public_key stored_seqno cs _40
}>DO<{ // public_key stored_seqno cs
// 3 INT 35 LSHIFT# 3 INT RAWRESERVE // reserve all but 103 Grams from the balance
8 LDU LDREF s0 s2 XCHG // public_key stored_seqno cs _45 mode
SENDRAWMSG // public_key stored_seqno cs
}>
ENDS INC // public_key seqno'
NEWC 32 STU 256 STU ENDC c4 POP
)ABCD";
return fift::compile_asm(code).move_as_ok();
}
TEST(Tonlib, TestWallet) {
LOG(ERROR) << td::base64_encode(std_boc_serialize(get_test_wallet_source()).move_as_ok());
CHECK(get_test_wallet_source()->get_hash() == TestWallet::get_init_code()->get_hash());
auto fift_output = fift::mem_run_fift(load_source("smartcont/new-wallet.fif"), {"aba", "0"}).move_as_ok();
auto new_wallet_pk = fift_output.source_lookup.read_file("new-wallet.pk").move_as_ok().data;
auto new_wallet_query = fift_output.source_lookup.read_file("new-wallet-query.boc").move_as_ok().data;
auto new_wallet_addr = fift_output.source_lookup.read_file("new-wallet.addr").move_as_ok().data;
td::Ed25519::PrivateKey priv_key{td::SecureString{new_wallet_pk}};
auto pub_key = priv_key.get_public_key().move_as_ok();
auto init_state = TestWallet::get_init_state(pub_key);
auto init_message = TestWallet::get_init_message(priv_key);
auto address = GenericAccount::get_address(0, init_state);
CHECK(address.addr.as_slice() == td::Slice(new_wallet_addr).substr(0, 32));
td::Ref<vm::Cell> res = GenericAccount::create_ext_message(address, init_state, init_message);
LOG(ERROR) << "-------";
vm::load_cell_slice(res).print_rec(std::cerr);
LOG(ERROR) << "-------";
vm::load_cell_slice(vm::std_boc_deserialize(new_wallet_query).move_as_ok()).print_rec(std::cerr);
CHECK(vm::std_boc_deserialize(new_wallet_query).move_as_ok()->get_hash() == res->get_hash());
fift_output.source_lookup.write_file("/main.fif", load_source("smartcont/wallet.fif")).ensure();
auto dest = block::StdAddress::parse("Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX").move_as_ok();
fift_output = fift::mem_run_fift(std::move(fift_output.source_lookup),
{"aba", "new-wallet", "Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX", "123",
"321", "-C", "TEST"})
.move_as_ok();
auto wallet_query = fift_output.source_lookup.read_file("wallet-query.boc").move_as_ok().data;
auto gift_message = GenericAccount::create_ext_message(
address, {}, TestWallet::make_a_gift_message(priv_key, 123, 321000000000ll, "TEST", dest));
LOG(ERROR) << "-------";
vm::load_cell_slice(gift_message).print_rec(std::cerr);
LOG(ERROR) << "-------";
vm::load_cell_slice(vm::std_boc_deserialize(wallet_query).move_as_ok()).print_rec(std::cerr);
CHECK(vm::std_boc_deserialize(wallet_query).move_as_ok()->get_hash() == gift_message->get_hash());
}
td::Ref<vm::Cell> get_wallet_source_fc() {
return fift::compile_asm(load_source("smartcont/wallet-code.fif"), "", false).move_as_ok();
}
TEST(Tonlib, Wallet) {
LOG(ERROR) << td::base64_encode(std_boc_serialize(get_wallet_source()).move_as_ok());
CHECK(get_wallet_source()->get_hash() == Wallet::get_init_code()->get_hash());
auto fift_output = fift::mem_run_fift(load_source("smartcont/new-wallet-v2.fif"), {"aba", "0"}).move_as_ok();
auto new_wallet_pk = fift_output.source_lookup.read_file("new-wallet.pk").move_as_ok().data;
auto new_wallet_query = fift_output.source_lookup.read_file("new-wallet-query.boc").move_as_ok().data;
auto new_wallet_addr = fift_output.source_lookup.read_file("new-wallet.addr").move_as_ok().data;
td::Ed25519::PrivateKey priv_key{td::SecureString{new_wallet_pk}};
auto pub_key = priv_key.get_public_key().move_as_ok();
auto init_state = Wallet::get_init_state(pub_key);
auto init_message = Wallet::get_init_message(priv_key);
auto address = GenericAccount::get_address(0, init_state);
CHECK(address.addr.as_slice() == td::Slice(new_wallet_addr).substr(0, 32));
td::Ref<vm::Cell> res = GenericAccount::create_ext_message(address, init_state, init_message);
LOG(ERROR) << "-------";
vm::load_cell_slice(res).print_rec(std::cerr);
LOG(ERROR) << "-------";
vm::load_cell_slice(vm::std_boc_deserialize(new_wallet_query).move_as_ok()).print_rec(std::cerr);
CHECK(vm::std_boc_deserialize(new_wallet_query).move_as_ok()->get_hash() == res->get_hash());
fift_output.source_lookup.write_file("/main.fif", load_source("smartcont/wallet-v2.fif")).ensure();
class ZeroOsTime : public fift::OsTime {
public:
td::uint32 now() override {
return 0;
}
};
fift_output.source_lookup.set_os_time(std::make_unique<ZeroOsTime>());
auto dest = block::StdAddress::parse("Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX").move_as_ok();
fift_output =
fift::mem_run_fift(std::move(fift_output.source_lookup),
{"aba", "new-wallet", "Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX", "123", "321"})
.move_as_ok();
auto wallet_query = fift_output.source_lookup.read_file("wallet-query.boc").move_as_ok().data;
auto gift_message = GenericAccount::create_ext_message(
address, {}, Wallet::make_a_gift_message(priv_key, 123, 60, 321000000000ll, "TESTv2", dest));
LOG(ERROR) << "-------";
vm::load_cell_slice(gift_message).print_rec(std::cerr);
LOG(ERROR) << "-------";
vm::load_cell_slice(vm::std_boc_deserialize(wallet_query).move_as_ok()).print_rec(std::cerr);
CHECK(vm::std_boc_deserialize(wallet_query).move_as_ok()->get_hash() == gift_message->get_hash());
}
TEST(Tonlib, TestGiver) {
auto address =
block::StdAddress::parse("-1:60c04141c6a7b96d68615e7a91d265ad0f3a9a922e9ae9c901d4fa83f5d3c0d0").move_as_ok();
LOG(ERROR) << address.bounceable;
auto fift_output = fift::mem_run_fift(load_source("smartcont/testgiver.fif"),
{"aba", address.rserialize(), "0", "6.666", "wallet-query"})
.move_as_ok();
LOG(ERROR) << fift_output.output;
auto wallet_query = fift_output.source_lookup.read_file("wallet-query.boc").move_as_ok().data;
auto res = GenericAccount::create_ext_message(
TestGiver::address(), {}, TestGiver::make_a_gift_message(0, 1000000000ll * 6666 / 1000, "GIFT", address));
vm::CellSlice(vm::NoVm(), res).print_rec(std::cerr);
CHECK(vm::std_boc_deserialize(wallet_query).move_as_ok()->get_hash() == res->get_hash());
}
TEST(Tonlib, PublicKey) {
block::PublicKey::parse("pubjns2gp7DGCnEH7EOWeCnb6Lw1akm538YYaz6sdLVHfRB2").ensure_error();
auto key = block::PublicKey::parse("Pubjns2gp7DGCnEH7EOWeCnb6Lw1akm538YYaz6sdLVHfRB2").move_as_ok();
@ -480,15 +296,15 @@ TEST(Tonlib, KeysApi) {
td::SecureString{}))
.move_as_ok();
sync_send(client, make_object<tonlib_api::exportKey>(make_object<tonlib_api::inputKey>(
sync_send(client, make_object<tonlib_api::exportKey>(make_object<tonlib_api::inputKeyRegular>(
make_object<tonlib_api::key>(key->public_key_, key->secret_.copy()),
td::SecureString("wrong password"))))
.ensure_error();
//exportKey input_key:inputKey = ExportedKey;
//exportKey input_key:inputKeyRegular = ExportedKey;
auto exported_key =
sync_send(client,
make_object<tonlib_api::exportKey>(make_object<tonlib_api::inputKey>(
make_object<tonlib_api::exportKey>(make_object<tonlib_api::inputKeyRegular>(
make_object<tonlib_api::key>(key->public_key_, key->secret_.copy()), local_password.copy())))
.move_as_ok();
LOG(ERROR) << to_string(exported_key);
@ -500,20 +316,20 @@ TEST(Tonlib, KeysApi) {
return word_list_copy;
};
//changeLocalPassword input_key:inputKey new_local_password:bytes = Key;
//changeLocalPassword input_key:inputKeyRegular new_local_password:bytes = Key;
auto new_key =
sync_send(client,
make_object<tonlib_api::changeLocalPassword>(
make_object<tonlib_api::inputKey>(
make_object<tonlib_api::inputKeyRegular>(
make_object<tonlib_api::key>(key->public_key_, key->secret_.copy()), local_password.copy()),
td::SecureString("tmp local password")))
.move_as_ok();
sync_send(client,
make_object<tonlib_api::exportKey>(make_object<tonlib_api::inputKey>(
make_object<tonlib_api::exportKey>(make_object<tonlib_api::inputKeyRegular>(
make_object<tonlib_api::key>(key->public_key_, new_key->secret_.copy()), local_password.copy())))
.ensure_error();
auto exported_key2 = sync_send(client, make_object<tonlib_api::exportKey>(make_object<tonlib_api::inputKey>(
auto exported_key2 = sync_send(client, make_object<tonlib_api::exportKey>(make_object<tonlib_api::inputKeyRegular>(
make_object<tonlib_api::key>(key->public_key_, new_key->secret_.copy()),
td::SecureString("tmp local password"))))
.move_as_ok();
@ -531,7 +347,7 @@ TEST(Tonlib, KeysApi) {
auto wrong_export_password = td::SecureString("wrong_export password");
auto exported_encrypted_key =
sync_send(client, make_object<tonlib_api::exportEncryptedKey>(
make_object<tonlib_api::inputKey>(
make_object<tonlib_api::inputKeyRegular>(
make_object<tonlib_api::key>(key->public_key_, new_key->secret_.copy()),
td::SecureString("tmp local password")),
export_password.copy()))
@ -576,12 +392,12 @@ TEST(Tonlib, KeysApi) {
CHECK(imported_key->public_key_ == key->public_key_);
CHECK(imported_key->secret_ != key->secret_);
//exportPemKey input_key:inputKey key_password:bytes = ExportedPemKey;
//exportPemKey input_key:inputKeyRegular key_password:bytes = ExportedPemKey;
auto pem_password = td::SecureString("pem password");
auto r_exported_pem_key = sync_send(
client,
make_object<tonlib_api::exportPemKey>(
make_object<tonlib_api::inputKey>(
make_object<tonlib_api::inputKeyRegular>(
make_object<tonlib_api::key>(key->public_key_, imported_key->secret_.copy()), new_local_password.copy()),
pem_password.copy()));
if (r_exported_pem_key.is_error() && r_exported_pem_key.error().message() == "INTERNAL Not supported") {

View file

@ -34,10 +34,12 @@
#include "ton/ton-tl.hpp"
#include "block/block.h"
#include "block/block-auto.h"
#include "Ed25519.h"
#include "tonlib/GenericAccount.h"
#include "tonlib/TestGiver.h"
#include "tonlib/TestWallet.h"
#include "smc-envelope/GenericAccount.h"
#include "smc-envelope/MultisigWallet.h"
#include "smc-envelope/TestGiver.h"
#include "smc-envelope/TestWallet.h"
#include "tonlib/LastBlock.h"
#include "tonlib/ExtClient.h"
#include "tonlib/utils.h"
@ -57,18 +59,21 @@
#include "td/utils/optional.h"
#include "td/utils/overloaded.h"
#include "td/utils/MpscPollableQueue.h"
#include "td/utils/port/path.h"
#include "td/utils/port/signals.h"
using namespace tonlib;
constexpr td::int64 Gramm = 1000000000;
auto sync_send = [](auto& client, auto query) {
using ReturnTypePtr = typename std::decay_t<decltype(*query)>::ReturnType;
using ReturnType = typename ReturnTypePtr::element_type;
client.send({1, std::move(query)});
while (true) {
auto response = client.receive(100);
if (response.object) {
if (response.object && response.id != 0) {
CHECK(response.id == 1);
if (response.object->get_id() == tonlib_api::error::ID) {
auto error = tonlib_api::move_object_as<tonlib_api::error>(response.object);
@ -78,87 +83,295 @@ auto sync_send = [](auto& client, auto query) {
}
}
};
auto static_send = [](auto query) {
using ReturnTypePtr = typename std::decay_t<decltype(*query)>::ReturnType;
using ReturnType = typename ReturnTypePtr::element_type;
auto response = Client::execute({1, std::move(query)});
if (response.object->get_id() == tonlib_api::error::ID) {
auto error = tonlib_api::move_object_as<tonlib_api::error>(response.object);
return td::Result<ReturnTypePtr>(td::Status::Error(error->code_, error->message_));
}
return td::Result<ReturnTypePtr>(tonlib_api::move_object_as<ReturnType>(response.object));
};
struct Key {
std::string public_key;
td::SecureString secret;
tonlib_api::object_ptr<tonlib_api::inputKey> get_input_key() const {
return tonlib_api::make_object<tonlib_api::inputKey>(
tonlib_api::object_ptr<tonlib_api::InputKey> get_input_key() const {
return tonlib_api::make_object<tonlib_api::inputKeyRegular>(
tonlib_api::make_object<tonlib_api::key>(public_key, secret.copy()), td::SecureString("local"));
}
tonlib_api::object_ptr<tonlib_api::InputKey> get_fake_input_key() const {
return tonlib_api::make_object<tonlib_api::inputKeyFake>();
}
};
struct Wallet {
std::string address;
Key key;
};
struct TransactionId {
td::int64 lt{0};
std::string hash;
};
struct AccountState {
enum Type { Empty, Wallet, Unknown } type{Empty};
td::int64 sync_utime{-1};
td::int64 balance{-1};
TransactionId last_transaction_id;
std::string address;
bool is_inited() const {
return type != Empty;
}
};
using tonlib_api::make_object;
void sync(Client& client) {
sync_send(client, make_object<tonlib_api::sync>()).ensure();
}
std::string wallet_address(Client& client, const Key& key) {
return sync_send(client, make_object<tonlib_api::wallet_getAccountAddress>(
make_object<tonlib_api::wallet_initialAccountState>(key.public_key)))
.move_as_ok()
->account_address_;
}
Wallet import_wallet_from_pkey(Client& client, std::string pkey, std::string password) {
auto key = sync_send(client, make_object<tonlib_api::importPemKey>(
td::SecureString("local"), td::SecureString(password),
make_object<tonlib_api::exportedPemKey>(td::SecureString(pkey))))
.move_as_ok();
Wallet wallet{"", {key->public_key_, std::move(key->secret_)}};
wallet.address = wallet_address(client, wallet.key);
return wallet;
}
std::string test_giver_address(Client& client) {
using tonlib_api::make_object;
return sync_send(client, make_object<tonlib_api::testGiver_getAccountAddress>()).move_as_ok()->account_address_;
}
td::int64 get_balance(Client& client, std::string address) {
AccountState get_account_state(Client& client, std::string address) {
auto generic_state = sync_send(client, tonlib_api::make_object<tonlib_api::generic_getAccountState>(
tonlib_api::make_object<tonlib_api::accountAddress>(address)))
.move_as_ok();
td::int64 res = 0;
tonlib_api::downcast_call(*generic_state, [&](auto& state) { res = state.account_state_->balance_; });
AccountState res;
tonlib_api::downcast_call(*generic_state, [&](auto& state) {
res.balance = state.account_state_->balance_;
res.sync_utime = state.account_state_->sync_utime_;
res.last_transaction_id.lt = state.account_state_->last_transaction_id_->lt_;
res.last_transaction_id.hash = state.account_state_->last_transaction_id_->hash_;
});
res.address = address;
switch (generic_state->get_id()) {
case tonlib_api::generic_accountStateUninited::ID:
res.type = AccountState::Empty;
break;
case tonlib_api::generic_accountStateWallet::ID:
res.type = AccountState::Wallet;
break;
default:
res.type = AccountState::Unknown;
break;
}
return res;
}
bool is_inited(Client& client, std::string address) {
auto generic_state = sync_send(client, tonlib_api::make_object<tonlib_api::generic_getAccountState>(
tonlib_api::make_object<tonlib_api::accountAddress>(address)))
.move_as_ok();
return generic_state->get_id() != tonlib_api::generic_accountStateUninited::ID;
struct QueryId {
td::int64 id;
};
struct Fee {
td::int64 in_fwd_fee{0};
td::int64 storage_fee{0};
td::int64 gas_fee{0};
td::int64 fwd_fee{0};
td::int64 sum() const {
return in_fwd_fee + storage_fee + gas_fee + fwd_fee;
}
};
template <class T>
auto to_fee(const T& fee) {
Fee res;
res.in_fwd_fee = fee->in_fwd_fee_;
res.storage_fee = fee->storage_fee_;
res.gas_fee = fee->gas_fee_;
res.fwd_fee = fee->fwd_fee_;
return res;
}
void transfer_grams(Client& client, std::string from, std::string to, td::int64 amount,
tonlib_api::object_ptr<tonlib_api::inputKey> input_key) {
auto balance = get_balance(client, to);
sync_send(client, tonlib_api::make_object<tonlib_api::generic_sendGrams>(
std::move(input_key), tonlib_api::make_object<tonlib_api::accountAddress>(from),
tonlib_api::make_object<tonlib_api::accountAddress>(to), amount, 0, true, "GIFT"))
.ensure();
while (balance == get_balance(client, to)) {
td::StringBuilder& operator<<(td::StringBuilder& sb, const Fee& fees) {
return sb << td::tag("in_fwd_fee", fees.in_fwd_fee) << td::tag("storage_fee", fees.storage_fee)
<< td::tag("gas_fee", fees.gas_fee) << td::tag("fwd_fee", fees.fwd_fee);
}
struct QueryInfo {
td::int64 valid_until;
std::string body_hash;
};
td::Result<QueryId> create_send_grams_query(Client& client, const Wallet& source, std::string destination,
td::int64 amount, std::string message, bool force = false, int timeout = 0,
bool fake = false) {
auto r_id = sync_send(client, tonlib_api::make_object<tonlib_api::generic_createSendGramsQuery>(
fake ? source.key.get_fake_input_key() : source.key.get_input_key(),
tonlib_api::make_object<tonlib_api::accountAddress>(source.address),
tonlib_api::make_object<tonlib_api::accountAddress>(destination), amount, timeout,
force, std::move(message)));
TRY_RESULT(id, std::move(r_id));
return QueryId{id->id_};
}
td::Result<QueryId> create_raw_query(Client& client, std::string source, std::string init_code, std::string init_data,
std::string body) {
auto r_id =
sync_send(client, tonlib_api::make_object<tonlib_api::raw_createQuery>(
tonlib_api::make_object<tonlib_api::accountAddress>(source), init_code, init_data, body));
TRY_RESULT(id, std::move(r_id));
return QueryId{id->id_};
}
std::pair<Fee, Fee> query_estimate_fees(Client& client, QueryId query_id, bool ignore_chksig = false) {
auto fees = sync_send(client, tonlib_api::make_object<tonlib_api::query_estimateFees>(query_id.id, ignore_chksig))
.move_as_ok();
return std::make_pair(to_fee(fees->source_fees_), to_fee(fees->destination_fees_));
}
void query_send(Client& client, QueryId query_id) {
sync_send(client, tonlib_api::make_object<tonlib_api::query_send>(query_id.id)).ensure();
}
QueryInfo query_get_info(Client& client, QueryId query_id) {
auto info = sync_send(client, tonlib_api::make_object<tonlib_api::query_getInfo>(query_id.id)).move_as_ok();
return QueryInfo{info->valid_until_, info->body_hash_};
}
td::Result<AccountState> wait_state_change(Client& client, const AccountState& old_state, td::int64 valid_until) {
while (true) {
auto new_state = get_account_state(client, old_state.address);
if (new_state.last_transaction_id.lt != old_state.last_transaction_id.lt) {
return new_state;
}
if (valid_until != 0 && new_state.sync_utime >= valid_until) {
return td::Status::Error("valid_until expired");
}
client.receive(1);
}
};
td::Result<tonlib_api::object_ptr<tonlib_api::raw_transactions>> get_transactions(Client& client, std::string address,
const TransactionId& from) {
auto got_transactions = sync_send(client, make_object<tonlib_api::raw_getTransactions>(
make_object<tonlib_api::accountAddress>(address),
make_object<tonlib_api::internal_transactionId>(from.lt, from.hash)))
.move_as_ok();
return std::move(got_transactions);
}
td::Status transfer_grams(Client& client, const Wallet& wallet, std::string address, td::int64 amount) {
auto src_state = get_account_state(client, wallet.address);
auto dst_state = get_account_state(client, address);
auto message = td::rand_string('a', 'z', 500);
LOG(INFO) << "Transfer: create query " << (double)amount / Gramm << " from " << wallet.address << " to " << address;
auto r_query_id = create_send_grams_query(client, wallet, address, amount, message);
if (r_query_id.is_error() && td::begins_with(r_query_id.error().message(), "DANGEROUS_TRANSACTION")) {
ASSERT_TRUE(dst_state.type == AccountState::Empty);
LOG(INFO) << "Transfer: recreate query due to DANGEROUS_TRANSACTION error";
r_query_id = create_send_grams_query(client, wallet, address, amount, message, true);
}
r_query_id.ensure();
QueryId query_id = r_query_id.move_as_ok();
auto query_info = query_get_info(client, query_id);
auto fees = query_estimate_fees(client, query_id);
LOG(INFO) << "Expected src fees: " << fees.first;
LOG(INFO) << "Expected dst fees: " << fees.second;
bool transfer_all = amount == src_state.balance;
if (!transfer_all && amount + fees.first.sum() + 10 > src_state.balance) {
return td::Status::Error("Not enough balance for query");
}
LOG(INFO) << "Transfer: send query";
query_send(client, query_id);
td::Timer timer;
TRY_RESULT(new_src_state, wait_state_change(client, src_state, query_info.valid_until));
LOG(INFO) << "Transfer: reached source in " << timer;
td::int64 lt;
td::int64 first_fee;
{
auto tr = get_transactions(client, src_state.address, new_src_state.last_transaction_id).move_as_ok();
CHECK(tr->transactions_.size() > 0);
const auto& txn = tr->transactions_[0];
CHECK(txn->in_msg_->body_hash_ == query_info.body_hash);
ASSERT_EQ(1u, txn->out_msgs_.size());
ASSERT_EQ(message, txn->out_msgs_[0]->message_);
lt = txn->out_msgs_[0]->created_lt_;
auto fee_difference = fees.first.sum() - txn->fee_;
first_fee = txn->fee_;
auto desc = PSTRING() << fee_difference << " storage:[" << fees.first.storage_fee << " vs " << txn->storage_fee_
<< "] other:[" << fees.first.sum() - fees.first.storage_fee << " vs " << txn->other_fee_
<< "]";
LOG(INFO) << "Source fee difference " << desc;
LOG_IF(ERROR, std::abs(fee_difference) > 1) << "Too big source fee difference " << desc;
}
TRY_RESULT(new_dst_state, wait_state_change(client, dst_state, new_src_state.sync_utime + 30));
LOG(INFO) << "Transfer: reached destination in " << timer;
{
auto tr = get_transactions(client, dst_state.address, new_dst_state.last_transaction_id).move_as_ok();
CHECK(tr->transactions_.size() > 0);
const auto& txn = tr->transactions_[0];
ASSERT_EQ(lt, txn->in_msg_->created_lt_);
if (transfer_all) {
ASSERT_EQ(amount - first_fee, txn->in_msg_->value_);
} else {
ASSERT_EQ(new_src_state.address, txn->in_msg_->source_);
}
ASSERT_EQ(new_src_state.address, txn->in_msg_->source_);
ASSERT_EQ(message, txn->in_msg_->message_);
auto fee_difference = fees.second.sum() - txn->fee_;
auto desc = PSTRING() << fee_difference << " storage:[" << fees.second.storage_fee << " vs " << txn->storage_fee_
<< "] other:[" << fees.second.sum() - fees.second.storage_fee << " vs " << txn->other_fee_
<< "]";
LOG(INFO) << "Destination fee difference " << desc;
LOG_IF(ERROR, std::abs(fee_difference) > 1) << "Too big destination fee difference " << desc;
}
return td::Status::OK();
}
Wallet create_empty_wallet(Client& client) {
using tonlib_api::make_object;
auto key = sync_send(client, make_object<tonlib_api::createNewKey>(td::SecureString("local"),
td::SecureString("mnemonic"), td::SecureString()))
auto key = sync_send(client, make_object<tonlib_api::createNewKey>(td::SecureString("local"), td::SecureString(),
td::SecureString()))
.move_as_ok();
Wallet wallet{"", {key->public_key_, std::move(key->secret_)}};
auto account_address =
sync_send(client, make_object<tonlib_api::testWallet_getAccountAddress>(
make_object<tonlib_api::testWallet_initialAccountState>(wallet.key.public_key)))
sync_send(client, make_object<tonlib_api::wallet_getAccountAddress>(
make_object<tonlib_api::wallet_initialAccountState>(wallet.key.public_key)))
.move_as_ok();
wallet.address = account_address->account_address_;
// get state of empty account
auto state = get_account_state(client, wallet.address);
ASSERT_EQ(-1, state.balance);
ASSERT_EQ(AccountState::Empty, state.type);
return wallet;
}
Wallet create_wallet(Client& client) {
using tonlib_api::make_object;
auto wallet = create_empty_wallet(client);
transfer_grams(client, test_giver_address(client), wallet.address, 6000000000, {});
sync_send(client, make_object<tonlib_api::testWallet_init>(wallet.key.get_input_key())).ensure();
while (!is_inited(client, wallet.address)) {
client.receive(1);
}
LOG(ERROR) << get_balance(client, wallet.address);
return wallet;
}
std::string get_test_giver_address(Client& client) {
return sync_send(client, tonlib_api::make_object<tonlib_api::testGiver_getAccountAddress>())
.move_as_ok()
->account_address_;
}
void dump_transaction_history(Client& client, std::string address) {
using tonlib_api::make_object;
auto state = sync_send(client, make_object<tonlib_api::testGiver_getAccountState>()).move_as_ok();
@ -180,166 +393,156 @@ void dump_transaction_history(Client& client, std::string address) {
LOG(ERROR) << cnt;
}
void test_estimate_fees_without_key(Client& client, const Wallet& wallet_a, const Wallet& wallet_b) {
LOG(ERROR) << " SUBTEST: estimate fees without key";
{
auto query_id = create_send_grams_query(client, wallet_a, wallet_b.address, 0, "???", true, 0, true).move_as_ok();
auto fees1 = query_estimate_fees(client, query_id, false);
auto fees2 = query_estimate_fees(client, query_id, true);
LOG(INFO) << "Fee without ignore_chksig\t" << fees1;
LOG(INFO) << "Fee with ignore_chksig\t" << fees2;
CHECK(fees1.first.gas_fee == 0);
CHECK(fees2.first.gas_fee != 0);
}
}
void test_back_and_forth_transfer(Client& client, const Wallet& giver_wallet, bool flag) {
LOG(ERROR) << "TEST: back and forth transfer";
// just generate private key and address
auto wallet_a = create_empty_wallet(client);
LOG(INFO) << wallet_a.address;
// get state of empty account
auto state = get_account_state(client, wallet_a.address);
ASSERT_EQ(-1, state.balance);
ASSERT_EQ(AccountState::Empty, state.type);
test_estimate_fees_without_key(client, giver_wallet, wallet_a);
// transfer from giver to a
transfer_grams(client, giver_wallet, wallet_a.address, 1 * Gramm).ensure();
state = get_account_state(client, wallet_a.address);
ASSERT_EQ(1 * Gramm, state.balance);
ASSERT_EQ(AccountState::Empty, state.type);
test_estimate_fees_without_key(client, wallet_a, giver_wallet);
if (flag) {
// transfer from a to giver
transfer_grams(client, wallet_a, giver_wallet.address, 5 * Gramm / 10).ensure();
state = get_account_state(client, wallet_a.address);
ASSERT_TRUE(state.balance < 5 * Gramm / 10);
ASSERT_EQ(AccountState::Wallet, state.type);
}
// transfer all remaining balance (test flag 128)
transfer_grams(client, wallet_a, giver_wallet.address, state.balance).ensure();
state = get_account_state(client, wallet_a.address);
ASSERT_TRUE(state.balance == 0);
ASSERT_EQ(AccountState::Wallet, state.type);
}
void test_multisig(Client& client, const Wallet& giver_wallet) {
LOG(ERROR) << "TEST: multisig";
int n = 16;
int k = 10;
std::vector<td::Ed25519::PrivateKey> private_keys;
for (int i = 0; i < n; i++) {
private_keys.push_back(td::Ed25519::generate_private_key().move_as_ok());
}
auto ms = ton::MultisigWallet::create();
auto init_data = ms->create_init_data(
td::transform(private_keys, [](const auto& pk) { return pk.get_public_key().move_as_ok().as_octet_string(); }),
k);
ms = ton::MultisigWallet::create(init_data);
auto raw_address = ms->get_address(ton::basechainId);
auto address = raw_address.rserialize();
transfer_grams(client, giver_wallet, address, 1 * Gramm).ensure();
auto init_state = ms->get_init_state();
// Just transfer all (some) money back in one query
vm::CellBuilder icb;
ton::GenericAccount::store_int_message(icb, block::StdAddress::parse(giver_wallet.address).move_as_ok(),
5 * Gramm / 10);
icb.store_bytes("\0\0\0\0", 4);
vm::CellString::store(icb, "Greatings from multisig", 35 * 8).ensure();
ton::MultisigWallet::QueryBuilder qb(-1, icb.finalize());
for (int i = 0; i < k - 1; i++) {
qb.sign(i, private_keys[i]);
}
auto query_id =
create_raw_query(client, address, vm::std_boc_serialize(ms->get_state().code).move_as_ok().as_slice().str(),
vm::std_boc_serialize(ms->get_state().data).move_as_ok().as_slice().str(),
vm::std_boc_serialize(qb.create(k - 1, private_keys[k - 1])).move_as_ok().as_slice().str())
.move_as_ok();
auto fees = query_estimate_fees(client, query_id);
LOG(INFO) << "Expected src fees: " << fees.first;
LOG(INFO) << "Expected dst fees: " << fees.second;
auto a_state = get_account_state(client, address);
query_send(client, query_id);
auto new_a_state = wait_state_change(client, a_state, a_state.sync_utime + 30).move_as_ok();
}
int main(int argc, char* argv[]) {
td::set_default_failure_signal_handler();
using tonlib_api::make_object;
td::OptionsParser p;
std::string global_config_str;
std::string giver_key_str;
std::string giver_key_pwd = "cucumber";
std::string keystore_dir = "test-keystore";
bool reset_keystore_dir = false;
p.add_option('C', "global-config", "file to read global config", [&](td::Slice fname) {
TRY_RESULT(str, td::read_file_str(fname.str()));
global_config_str = std::move(str);
LOG(ERROR) << global_config_str;
return td::Status::OK();
});
p.add_option('G', "giver-key", "file with a wallet key that should be used as a giver", [&](td::Slice fname) {
TRY_RESULT(str, td::read_file_str(fname.str()));
giver_key_str = std::move(str);
return td::Status::OK();
});
p.add_option('f', "force", "reser keystore dir", [&]() {
reset_keystore_dir = true;
return td::Status::OK();
});
p.run(argc, argv).ensure();
if (reset_keystore_dir) {
td::rmrf(keystore_dir).ignore();
td::mkdir(keystore_dir).ensure();
}
SET_VERBOSITY_LEVEL(VERBOSITY_NAME(INFO));
static_send(make_object<tonlib_api::setLogTagVerbosityLevel>("tonlib_query", 4)).ensure();
auto tags = static_send(make_object<tonlib_api::getLogTags>()).move_as_ok()->tags_;
for (auto& tag : tags) {
static_send(make_object<tonlib_api::setLogTagVerbosityLevel>(tag, 4)).ensure();
}
Client client;
{
sync_send(client, make_object<tonlib_api::init>(make_object<tonlib_api::options>(
make_object<tonlib_api::config>(global_config_str, "", false, false),
make_object<tonlib_api::keyStoreTypeDirectory>("."))))
make_object<tonlib_api::keyStoreTypeDirectory>(keystore_dir))))
.ensure();
}
//dump_transaction_history(client, get_test_giver_address(client));
auto wallet_a = create_wallet(client);
auto wallet_b = create_empty_wallet(client);
transfer_grams(client, wallet_a.address, wallet_b.address, 3000000000, wallet_a.key.get_input_key());
auto a = get_balance(client, wallet_a.address);
auto b = get_balance(client, wallet_b.address);
LOG(ERROR) << a << " " << b;
return 0;
{
// init
sync_send(client, make_object<tonlib_api::init>(make_object<tonlib_api::options>(
make_object<tonlib_api::config>(global_config_str, "", false, false),
make_object<tonlib_api::keyStoreTypeDirectory>("."))))
.ensure();
auto key = sync_send(client, make_object<tonlib_api::createNewKey>(
td::SecureString("local"), td::SecureString("mnemonic"), td::SecureString()))
.move_as_ok();
// wait till client is synchronized with blockchain.
// not necessary, but synchronized will be trigged anyway later
sync(client);
auto create_input_key = [&] {
return make_object<tonlib_api::inputKey>(make_object<tonlib_api::key>(key->public_key_, key->secret_.copy()),
td::SecureString("local"));
};
// give wallet with some test grams to run test
auto giver_wallet = import_wallet_from_pkey(client, giver_key_str, giver_key_pwd);
auto public_key_raw = key->public_key_;
td::Ed25519::PublicKey public_key_std(td::SecureString{public_key_raw});
sync_send(client, make_object<tonlib_api::options_setConfig>(
make_object<tonlib_api::config>(global_config_str, "", false, false)))
.ensure();
auto wallet_addr = GenericAccount::get_address(0, TestWallet::get_init_state(public_key_std));
{
auto account_address =
sync_send(client, make_object<tonlib_api::testWallet_getAccountAddress>(
make_object<tonlib_api::testWallet_initialAccountState>(public_key_raw)))
.move_as_ok();
ASSERT_EQ(wallet_addr.rserialize(), account_address->account_address_);
}
std::string test_giver_address;
{
auto account_address = sync_send(client, make_object<tonlib_api::testGiver_getAccountAddress>()).move_as_ok();
test_giver_address = account_address->account_address_;
ASSERT_EQ(TestGiver::address().rserialize(), test_giver_address);
}
{
auto account_address =
sync_send(
client,
make_object<tonlib_api::raw_getAccountAddress>(make_object<tonlib_api::raw_initialAccountState>(
vm::std_boc_serialize(TestWallet::get_init_code()).move_as_ok().as_slice().str(),
vm::std_boc_serialize(TestWallet::get_init_data(public_key_std)).move_as_ok().as_slice().str())))
.move_as_ok();
ASSERT_EQ(wallet_addr.rserialize(), account_address->account_address_);
}
{
auto state = sync_send(client, make_object<tonlib_api::raw_getAccountState>(
make_object<tonlib_api::accountAddress>(wallet_addr.rserialize())))
.move_as_ok();
LOG(ERROR) << to_string(state);
}
td::int32 seqno = 0;
{
auto state = sync_send(client, make_object<tonlib_api::testGiver_getAccountState>()).move_as_ok();
LOG(ERROR) << to_string(state);
seqno = state->seqno_;
}
{
sync_send(client, make_object<tonlib_api::testGiver_sendGrams>(
make_object<tonlib_api::accountAddress>(wallet_addr.rserialize()), seqno,
1000000000ll * 6666 / 1000, "GIFT"))
.ensure();
}
while (true) {
auto state = sync_send(client, make_object<tonlib_api::testGiver_getAccountState>()).move_as_ok();
if (state->seqno_ > seqno) {
break;
}
client.receive(1);
}
while (true) {
auto state = sync_send(client, make_object<tonlib_api::raw_getAccountState>(
make_object<tonlib_api::accountAddress>(wallet_addr.rserialize())))
.move_as_ok();
td::int64 grams_count = state->balance_;
if (grams_count > 0) {
LOG(ERROR) << "GOT " << grams_count;
break;
}
client.receive(1);
}
{ sync_send(client, make_object<tonlib_api::testWallet_init>(create_input_key())).ensure(); }
while (true) {
auto r_state = sync_send(client, make_object<tonlib_api::testWallet_getAccountState>(
make_object<tonlib_api::accountAddress>(wallet_addr.rserialize())));
if (r_state.is_ok()) {
LOG(ERROR) << to_string(r_state.ok());
break;
}
client.receive(1);
}
{
sync_send(client, make_object<tonlib_api::generic_sendGrams>(
create_input_key(), make_object<tonlib_api::accountAddress>(wallet_addr.rserialize()),
make_object<tonlib_api::accountAddress>(test_giver_address), 1000000000ll * 3333 / 1000, 0,
true, "GIFT"))
.ensure();
}
while (true) {
auto generic_state = sync_send(client, make_object<tonlib_api::generic_getAccountState>(
make_object<tonlib_api::accountAddress>(wallet_addr.rserialize())))
.move_as_ok();
if (generic_state->get_id() == tonlib_api::generic_accountStateTestWallet::ID) {
auto state = tonlib_api::move_object_as<tonlib_api::generic_accountStateTestWallet>(generic_state);
if (state->account_state_->balance_ < 5617007000) {
LOG(ERROR) << to_string(state);
break;
}
}
client.receive(1);
}
{
auto generic_state = sync_send(client, make_object<tonlib_api::generic_getAccountState>(
make_object<tonlib_api::accountAddress>(test_giver_address)))
.move_as_ok();
CHECK(generic_state->get_id() == tonlib_api::generic_accountStateTestGiver::ID);
LOG(ERROR) << to_string(generic_state);
}
}
test_back_and_forth_transfer(client, giver_wallet, false);
test_back_and_forth_transfer(client, giver_wallet, true);
test_multisig(client, giver_wallet);
return 0;
}

View file

@ -134,7 +134,7 @@ class Client::Impl final {
};
Client::Client() : impl_(std::make_unique<Impl>()) {
// At least it should be enough for everybody who uses TDLib
// At least it should be enough for everybody who uses tonlib
// FIXME
//td::init_openssl_threads();
}

View file

@ -19,12 +19,27 @@
#include "tonlib/ExtClient.h"
#include "tonlib/LastBlock.h"
#include "tonlib/LastConfig.h"
namespace tonlib {
ExtClient::~ExtClient() {
last_config_queries_.for_each([](auto id, auto &promise) { promise.set_error(TonlibError::Cancelled()); });
last_block_queries_.for_each([](auto id, auto &promise) { promise.set_error(TonlibError::Cancelled()); });
queries_.for_each([](auto id, auto &promise) { promise.set_error(TonlibError::Cancelled()); });
}
void ExtClient::with_last_config(td::Promise<LastConfigState> promise) {
auto query_id = last_config_queries_.create(std::move(promise));
td::Promise<LastConfigState> P = [query_id, self = this,
actor_id = td::actor::actor_id()](td::Result<LastConfigState> result) {
send_lambda(actor_id, [self, query_id, result = std::move(result)]() mutable {
self->last_config_queries_.extract(query_id).set_result(std::move(result));
});
};
if (client_.last_block_actor_.empty()) {
return P.set_error(TonlibError::NoLiteServers());
}
td::actor::send_closure(client_.last_config_actor_, &LastConfig::get_last_config, std::move(P));
}
void ExtClient::with_last_block(td::Promise<LastBlockState> promise) {
auto query_id = last_block_queries_.create(std::move(promise));
td::Promise<LastBlockState> P = [query_id, self = this,

View file

@ -33,10 +33,13 @@
namespace tonlib {
class LastBlock;
class LastConfig;
struct LastBlockState;
struct LastConfigState;
struct ExtClientRef {
td::actor::ActorId<ton::adnl::AdnlExtClient> andl_ext_client_;
td::actor::ActorId<LastBlock> last_block_actor_;
td::actor::ActorId<LastConfig> last_config_actor_;
};
class ExtClient {
@ -56,6 +59,7 @@ class ExtClient {
~ExtClient();
void with_last_block(td::Promise<LastBlockState> promise);
void with_last_config(td::Promise<LastConfigState> promise);
template <class QueryT>
void send_query(QueryT query, td::Promise<typename QueryT::ReturnType> promise, td::int32 seq_no = -1) {
@ -94,6 +98,7 @@ class ExtClient {
ExtClientRef client_;
td::Container<td::Promise<td::BufferSlice>> queries_;
td::Container<td::Promise<LastBlockState>> last_block_queries_;
td::Container<td::Promise<LastConfigState>> last_config_queries_;
void send_raw_query(td::BufferSlice query, td::Promise<td::BufferSlice> promise);
};

View file

@ -17,6 +17,7 @@
Copyright 2017-2019 Telegram Systems LLP
*/
#include "ExtClientLazy.h"
#include "TonlibError.h"
namespace tonlib {
class ExtClientLazyImp : public ton::adnl::AdnlExtClient {
@ -28,12 +29,18 @@ class ExtClientLazyImp : public ton::adnl::AdnlExtClient {
void check_ready(td::Promise<td::Unit> promise) override {
before_query();
if (client_.empty()) {
return promise.set_error(TonlibError::Cancelled());
}
send_closure(client_, &ton::adnl::AdnlExtClient::check_ready, std::move(promise));
}
void send_query(std::string name, td::BufferSlice data, td::Timestamp timeout,
td::Promise<td::BufferSlice> promise) override {
before_query();
if (client_.empty()) {
return promise.set_error(TonlibError::Cancelled());
}
send_closure(client_, &ton::adnl::AdnlExtClient::send_query, std::move(name), std::move(data), timeout,
std::move(promise));
}

View file

@ -106,6 +106,9 @@ td::Result<KeyStorage::ExportedKey> KeyStorage::export_key(InputKey input_key) {
}
td::Result<KeyStorage::PrivateKey> KeyStorage::load_private_key(InputKey input_key) {
if (is_fake_input_key(input_key)) {
return fake_private_key();
}
TRY_RESULT(decrypted_key, export_decrypted_key(std::move(input_key)));
PrivateKey private_key;
private_key.private_key = decrypted_key.private_key.as_octet_string();
@ -166,20 +169,46 @@ td::Result<KeyStorage::Key> KeyStorage::import_pem_key(td::Slice local_password,
return save_key(DecryptedKey({}, std::move(key)), local_password);
}
static std::string dummy_secret = "dummy secret of 32 bytes length!";
td::SecureString get_dummy_secret() {
return td::SecureString("dummy secret of 32 bytes length!");
}
td::Result<KeyStorage::ExportedEncryptedKey> KeyStorage::export_encrypted_key(InputKey input_key,
td::Slice key_password) {
TRY_RESULT(decrypted_key, export_decrypted_key(std::move(input_key)));
auto res = decrypted_key.encrypt(key_password, dummy_secret);
auto res = decrypted_key.encrypt(key_password, get_dummy_secret());
return ExportedEncryptedKey{std::move(res.encrypted_data)};
}
td::Result<KeyStorage::Key> KeyStorage::import_encrypted_key(td::Slice local_password, td::Slice key_password,
ExportedEncryptedKey exported_key) {
EncryptedKey encrypted_key{std::move(exported_key.data), td::Ed25519::PublicKey(td::SecureString()),
td::SecureString(dummy_secret)};
get_dummy_secret()};
TRY_RESULT_PREFIX(decrypted_key, encrypted_key.decrypt(key_password, false), TonlibError::KeyDecrypt());
return save_key(std::move(decrypted_key), local_password);
}
KeyStorage::PrivateKey KeyStorage::fake_private_key() {
return PrivateKey{td::SecureString(32, 0)};
}
KeyStorage::InputKey KeyStorage::fake_input_key() {
return InputKey{{td::SecureString(32, 0), td::SecureString(32, 0)}, {}};
}
bool KeyStorage::is_fake_input_key(InputKey &input_key) {
auto is_zero = [](td::Slice slice, size_t size) {
if (slice.size() != size) {
return false;
}
for (auto c : slice) {
if (c != 0) {
return false;
}
}
return true;
};
return is_zero(input_key.local_password, 0) && is_zero(input_key.key.secret, 32) &&
is_zero(input_key.key.public_key, 32);
}
} // namespace tonlib

View file

@ -69,6 +69,10 @@ class KeyStorage {
td::Result<PrivateKey> load_private_key(InputKey input_key);
static PrivateKey fake_private_key();
static InputKey fake_input_key();
static bool is_fake_input_key(InputKey& input_key);
private:
std::shared_ptr<KeyValue> kv_;

View file

@ -17,6 +17,7 @@
Copyright 2017-2019 Telegram Systems LLP
*/
#include "tonlib/LastBlock.h"
#include "tonlib/LastConfig.h"
#include "tonlib/utils.h"
@ -271,7 +272,7 @@ void LastBlock::update_zero_state(ton::ZeroStateIdExt zero_state_id, td::Slice s
}
if (!state_.zero_state_id.is_valid()) {
LOG(INFO) << "Init zerostate from " << source << ": " << zero_state_id.to_str();
VLOG(last_block) << "Init zerostate from " << source << ": " << zero_state_id.to_str();
state_.zero_state_id = std::move(zero_state_id);
return;
}
@ -295,7 +296,7 @@ bool LastBlock::update_mc_last_block(ton::BlockIdExt mc_block_id) {
}
if (!state_.last_block_id.is_valid() || state_.last_block_id.id.seqno < mc_block_id.id.seqno) {
state_.last_block_id = mc_block_id;
LOG(INFO) << "Update masterchain block id: " << state_.last_block_id.to_str();
VLOG(last_block) << "Update masterchain block id: " << state_.last_block_id.to_str();
return true;
}
return false;
@ -311,7 +312,7 @@ bool LastBlock::update_mc_last_key_block(ton::BlockIdExt mc_key_block_id) {
}
if (!state_.last_key_block_id.is_valid() || state_.last_key_block_id.id.seqno < mc_key_block_id.id.seqno) {
state_.last_key_block_id = mc_key_block_id;
LOG(INFO) << "Update masterchain key block id: " << state_.last_key_block_id.to_str();
VLOG(last_block) << "Update masterchain key block id: " << state_.last_key_block_id.to_str();
//LOG(ERROR) << td::int64(state_.last_key_block_id.id.shard) << " "
//<< td::base64_encode(state_.last_key_block_id.file_hash.as_slice()) << " "
//<< td::base64_encode(state_.last_key_block_id.root_hash.as_slice());
@ -330,7 +331,7 @@ bool LastBlock::update_init_block(ton::BlockIdExt init_block_id) {
}
if (state_.init_block_id != init_block_id) {
state_.init_block_id = init_block_id;
LOG(INFO) << "Update init block id: " << state_.init_block_id.to_str();
VLOG(last_block) << "Update init block id: " << state_.init_block_id.to_str();
return true;
}
return false;

View file

@ -0,0 +1,152 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#include "tonlib/LastConfig.h"
#include "tonlib/utils.h"
#include "ton/lite-tl.hpp"
#include "block/check-proof.h"
#include "block/mc-config.h"
#include "block/block-auto.h"
#include "lite-client/lite-client-common.h"
#include "LastBlock.h"
namespace tonlib {
// init_state <-> last_key_block
// state.valitated_init_state
// last_key_block ->
//
td::StringBuilder& operator<<(td::StringBuilder& sb, const LastConfigState& state) {
return sb;
}
LastConfig::LastConfig(ExtClientRef client, td::unique_ptr<Callback> callback) : callback_(std::move(callback)) {
client_.set_client(client);
VLOG(last_block) << "State: " << state_;
}
void LastConfig::get_last_config(td::Promise<LastConfigState> promise) {
if (promises_.empty() && get_config_state_ == QueryState::Done) {
VLOG(last_config) << "start";
VLOG(last_config) << "get_config: reset";
get_config_state_ = QueryState::Empty;
}
promises_.push_back(std::move(promise));
loop();
}
void LastConfig::with_last_block(td::Result<LastBlockState> r_last_block) {
if (r_last_block.is_error()) {
on_error(r_last_block.move_as_error());
return;
}
auto last_block = r_last_block.move_as_ok();
auto params = params_;
client_.send_query(ton::lite_api::liteServer_getConfigParams(0, create_tl_lite_block_id(last_block.last_block_id),
std::move(params)),
[this](auto r_config) { this->on_config(std::move(r_config)); });
}
void LastConfig::on_config(td::Result<ton::ton_api::object_ptr<ton::lite_api::liteServer_configInfo>> r_config) {
auto status = process_config(std::move(r_config));
if (status.is_ok()) {
on_ok();
get_config_state_ = QueryState::Done;
} else {
on_error(std::move(status));
get_config_state_ = QueryState::Empty;
}
}
td::Status LastConfig::process_config(
td::Result<ton::ton_api::object_ptr<ton::lite_api::liteServer_configInfo>> r_config) {
TRY_RESULT(raw_config, std::move(r_config));
TRY_STATUS_PREFIX(TRY_VM(process_config_proof(std::move(raw_config))), TonlibError::ValidateConfig());
return td::Status::OK();
}
td::Status LastConfig::process_config_proof(ton::ton_api::object_ptr<ton::lite_api::liteServer_configInfo> raw_config) {
auto blkid = create_block_id(raw_config->id_);
if (!blkid.is_masterchain_ext()) {
return td::Status::Error(PSLICE() << "reference block " << blkid.to_str()
<< " for the configuration is not a valid masterchain block");
}
TRY_RESULT(state, block::check_extract_state_proof(blkid, raw_config->state_proof_.as_slice(),
raw_config->config_proof_.as_slice()));
TRY_RESULT(config, block::Config::extract_from_state(std::move(state), 0));
for (auto i : params_) {
VLOG(last_config) << "ConfigParam(" << i << ") = ";
auto value = config->get_config_param(i);
if (value.is_null()) {
VLOG(last_config) << "(null)\n";
} else {
std::ostringstream os;
if (i >= 0) {
block::gen::ConfigParam{i}.print_ref(os, value);
os << std::endl;
}
vm::load_cell_slice(value).print_rec(os);
VLOG(last_config) << os.str();
}
}
state_.config.reset(config.release());
return td::Status::OK();
}
void LastConfig::loop() {
if (promises_.empty()) {
return;
}
if (get_config_state_ == QueryState::Empty) {
VLOG(last_block) << "get_config: start";
get_config_state_ = QueryState::Active;
client_.with_last_block(
[self = this](td::Result<LastBlockState> r_last_block) { self->with_last_block(std::move(r_last_block)); });
}
}
void LastConfig::on_ok() {
VLOG(last_block) << "ok " << state_;
for (auto& promise : promises_) {
auto state = state_;
promise.set_value(std::move(state));
}
promises_.clear();
}
void LastConfig::on_error(td::Status status) {
VLOG(last_config) << "error " << status;
for (auto& promise : promises_) {
promise.set_error(status.clone());
}
promises_.clear();
}
void LastConfig::tear_down() {
on_error(TonlibError::Cancelled());
}
} // namespace tonlib

View file

@ -0,0 +1,70 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 2017-2019 Telegram Systems LLP
*/
#pragma once
#include "td/actor/actor.h"
#include "tonlib/Config.h"
#include "tonlib/ExtClient.h"
#include "td/utils/CancellationToken.h"
#include "td/utils/tl_helpers.h"
#include "block/mc-config.h"
namespace tonlib {
struct LastConfigState {
std::shared_ptr<const block::Config> config;
};
td::StringBuilder& operator<<(td::StringBuilder& sb, const LastConfigState& state);
class LastConfig : public td::actor::Actor {
public:
class Callback {
public:
virtual ~Callback() {
}
};
explicit LastConfig(ExtClientRef client, td::unique_ptr<Callback> callback);
void get_last_config(td::Promise<LastConfigState> promise);
private:
td::unique_ptr<Callback> callback_;
ExtClient client_;
LastConfigState state_;
enum class QueryState { Empty, Active, Done };
QueryState get_config_state_{QueryState::Empty};
std::vector<td::Promise<LastConfigState>> promises_;
std::vector<td::int32> params_{18, 20, 21, 24, 25};
void with_last_block(td::Result<LastBlockState> r_last_block);
void on_config(td::Result<ton::ton_api::object_ptr<ton::lite_api::liteServer_configInfo>> r_config);
td::Status process_config(td::Result<ton::ton_api::object_ptr<ton::lite_api::liteServer_configInfo>> r_config);
td::Status process_config_proof(ton::ton_api::object_ptr<ton::lite_api::liteServer_configInfo> config);
void on_ok();
void on_error(td::Status status);
void loop() override;
void tear_down() override;
};
} // namespace tonlib

View file

@ -32,14 +32,22 @@
namespace tonlib {
static std::mutex logging_mutex;
static td::FileLog file_log;
static td::TsLog ts_log(&file_log);
static td::NullLog null_log;
struct LogData {
std::mutex logging_mutex;
td::FileLog file_log;
td::TsLog ts_log{&file_log};
td::NullLog null_log;
};
auto &log_data() {
static LogData data;
return data;
}
#define ADD_TAG(tag) \
{ #tag, &VERBOSITY_NAME(tag) }
static const std::map<td::Slice, int *> log_tags{ADD_TAG(tonlib_query), ADD_TAG(last_block)};
static const std::map<td::Slice, int *> log_tags{ADD_TAG(tonlib_query), ADD_TAG(last_block), ADD_TAG(last_config),
ADD_TAG(lite_server)};
#undef ADD_TAG
td::Status Logging::set_current_stream(tonlib_api::object_ptr<tonlib_api::LogStream> stream) {
@ -47,7 +55,7 @@ td::Status Logging::set_current_stream(tonlib_api::object_ptr<tonlib_api::LogStr
return td::Status::Error("Log stream must not be empty");
}
std::lock_guard<std::mutex> lock(logging_mutex);
std::lock_guard<std::mutex> lock(log_data().logging_mutex);
switch (stream->get_id()) {
case tonlib_api::logStreamDefault::ID:
td::log_interface = td::default_log_interface;
@ -59,13 +67,13 @@ td::Status Logging::set_current_stream(tonlib_api::object_ptr<tonlib_api::LogStr
return td::Status::Error("Max log file size should be positive");
}
TRY_STATUS(file_log.init(file_stream->path_, max_log_file_size));
TRY_STATUS(log_data().file_log.init(file_stream->path_, max_log_file_size));
std::atomic_thread_fence(std::memory_order_release); // better than nothing
td::log_interface = &ts_log;
td::log_interface = &log_data().ts_log;
return td::Status::OK();
}
case tonlib_api::logStreamEmpty::ID:
td::log_interface = &null_log;
td::log_interface = &log_data().null_log;
return td::Status::OK();
default:
UNREACHABLE();
@ -74,22 +82,22 @@ td::Status Logging::set_current_stream(tonlib_api::object_ptr<tonlib_api::LogStr
}
td::Result<tonlib_api::object_ptr<tonlib_api::LogStream>> Logging::get_current_stream() {
std::lock_guard<std::mutex> lock(logging_mutex);
std::lock_guard<std::mutex> lock(log_data().logging_mutex);
if (td::log_interface == td::default_log_interface) {
return tonlib_api::make_object<tonlib_api::logStreamDefault>();
}
if (td::log_interface == &null_log) {
if (td::log_interface == &log_data().null_log) {
return tonlib_api::make_object<tonlib_api::logStreamEmpty>();
}
if (td::log_interface == &ts_log) {
return tonlib_api::make_object<tonlib_api::logStreamFile>(file_log.get_path().str(),
file_log.get_rotate_threshold());
if (td::log_interface == &log_data().ts_log) {
return tonlib_api::make_object<tonlib_api::logStreamFile>(log_data().file_log.get_path().str(),
log_data().file_log.get_rotate_threshold());
}
return td::Status::Error("Log stream is unrecognized");
}
td::Status Logging::set_verbosity_level(int new_verbosity_level) {
std::lock_guard<std::mutex> lock(logging_mutex);
std::lock_guard<std::mutex> lock(log_data().logging_mutex);
if (0 <= new_verbosity_level && new_verbosity_level <= VERBOSITY_NAME(NEVER)) {
SET_VERBOSITY_LEVEL(VERBOSITY_NAME(FATAL) + new_verbosity_level);
return td::Status::OK();
@ -99,7 +107,7 @@ td::Status Logging::set_verbosity_level(int new_verbosity_level) {
}
int Logging::get_verbosity_level() {
std::lock_guard<std::mutex> lock(logging_mutex);
std::lock_guard<std::mutex> lock(log_data().logging_mutex);
return GET_VERBOSITY_LEVEL();
}
@ -113,7 +121,7 @@ td::Status Logging::set_tag_verbosity_level(td::Slice tag, int new_verbosity_lev
return td::Status::Error("Log tag is not found");
}
std::lock_guard<std::mutex> lock(logging_mutex);
std::lock_guard<std::mutex> lock(log_data().logging_mutex);
*it->second = td::clamp(new_verbosity_level, 1, VERBOSITY_NAME(NEVER));
return td::Status::OK();
}
@ -124,7 +132,7 @@ td::Result<int> Logging::get_tag_verbosity_level(td::Slice tag) {
return td::Status::Error("Log tag is not found");
}
std::lock_guard<std::mutex> lock(logging_mutex);
std::lock_guard<std::mutex> lock(log_data().logging_mutex);
return *it->second;
}

File diff suppressed because it is too large Load diff

View file

@ -34,6 +34,17 @@
#include <map>
namespace tonlib {
namespace int_api {
struct GetAccountState;
struct GetPrivateKey;
struct SendMessage;
inline std::string to_string(const int_api::SendMessage&) {
return "Send message";
}
} // namespace int_api
class AccountState;
class Query;
class TonlibClient : public td::actor::Actor {
public:
template <class T>
@ -66,6 +77,7 @@ class TonlibClient : public td::actor::Actor {
td::actor::ActorOwn<ton::adnl::AdnlExtClient> raw_client_;
td::actor::ActorId<ExtClientOutbound> ext_client_outbound_;
td::actor::ActorOwn<LastBlock> raw_last_block_;
td::actor::ActorOwn<LastConfig> raw_last_config_;
ExtClient client_;
td::CancellationTokenSource source_;
@ -76,6 +88,7 @@ class TonlibClient : public td::actor::Actor {
ExtClientRef get_client_ref();
void init_ext_client();
void init_last_block();
void init_last_config();
bool is_closing_{false};
td::uint32 ref_cnt_{1};
@ -127,9 +140,53 @@ class TonlibClient : public td::actor::Actor {
static object_ptr<tonlib_api::Object> do_static_request(const tonlib_api::decrypt& request);
static object_ptr<tonlib_api::Object> do_static_request(const tonlib_api::kdf& request);
template <class P>
td::Status do_request(const tonlib_api::runTests& request, P&&);
template <class P>
td::Status do_request(const tonlib_api::raw_getAccountAddress& request, P&&);
template <class P>
td::Status do_request(const tonlib_api::testWallet_getAccountAddress& request, P&&);
template <class P>
td::Status do_request(const tonlib_api::wallet_getAccountAddress& request, P&&);
template <class P>
td::Status do_request(const tonlib_api::testGiver_getAccountAddress& request, P&&);
template <class P>
td::Status do_request(const tonlib_api::packAccountAddress& request, P&&);
template <class P>
td::Status do_request(const tonlib_api::unpackAccountAddress& request, P&&);
template <class P>
td::Status do_request(tonlib_api::getBip39Hints& request, P&&);
template <class P>
td::Status do_request(tonlib_api::setLogStream& request, P&&);
template <class P>
td::Status do_request(const tonlib_api::getLogStream& request, P&&);
template <class P>
td::Status do_request(const tonlib_api::setLogVerbosityLevel& request, P&&);
template <class P>
td::Status do_request(const tonlib_api::setLogTagVerbosityLevel& request, P&&);
template <class P>
td::Status do_request(const tonlib_api::getLogVerbosityLevel& request, P&&);
template <class P>
td::Status do_request(const tonlib_api::getLogTagVerbosityLevel& request, P&&);
template <class P>
td::Status do_request(const tonlib_api::getLogTags& request, P&&);
template <class P>
td::Status do_request(const tonlib_api::addLogMessage& request, P&&);
template <class P>
td::Status do_request(const tonlib_api::encrypt& request, P&&);
template <class P>
td::Status do_request(const tonlib_api::decrypt& request, P&&);
template <class P>
td::Status do_request(const tonlib_api::kdf& request, P&&);
template <class T, class P>
td::Status do_request(const T& request, P&& promise) {
return td::Status::Error(400, "Function is unsupported");
void make_request(T&& request, P&& promise) {
auto status = do_request(std::forward<T>(request), std::move(promise));
if (status.is_error()) {
promise.operator()(std::move(status));
}
}
td::Status set_config(object_ptr<tonlib_api::config> config);
@ -138,6 +195,11 @@ class TonlibClient : public td::actor::Actor {
td::Status do_request(tonlib_api::options_setConfig& request, td::Promise<object_ptr<tonlib_api::ok>>&& promise);
td::Status do_request(const tonlib_api::raw_sendMessage& request, td::Promise<object_ptr<tonlib_api::ok>>&& promise);
td::Status do_request(const tonlib_api::raw_createAndSendMessage& request,
td::Promise<object_ptr<tonlib_api::ok>>&& promise);
td::Status do_request(const tonlib_api::raw_createQuery& request,
td::Promise<object_ptr<tonlib_api::query_info>>&& promise);
td::Status do_request(tonlib_api::raw_getAccountState& request,
td::Promise<object_ptr<tonlib_api::raw_accountState>>&& promise);
td::Status do_request(tonlib_api::raw_getTransactions& request,
@ -191,6 +253,48 @@ class TonlibClient : public td::actor::Actor {
td::Status do_request(const tonlib_api::onLiteServerQueryError& request,
td::Promise<object_ptr<tonlib_api::ok>>&& promise);
td::int64 next_query_id_{0};
std::map<td::int64, td::unique_ptr<Query>> queries_;
td::int64 register_query(td::unique_ptr<Query> query);
td::Result<tonlib_api::object_ptr<tonlib_api::query_info>> get_query_info(td::int64 id);
void finish_create_query(td::Result<td::unique_ptr<Query>> r_query,
td::Promise<object_ptr<tonlib_api::query_info>>&& promise);
void finish_send_query(td::Result<td::unique_ptr<Query>> r_query,
td::Promise<object_ptr<tonlib_api::sendGramsResult>>&& promise);
void query_estimate_fees(td::int64 id, bool ignore_chksig, td::Result<LastConfigState> r_state,
td::Promise<object_ptr<tonlib_api::query_fees>>&& promise);
td::Status do_request(const tonlib_api::query_getInfo& request,
td::Promise<object_ptr<tonlib_api::query_info>>&& promise);
td::Status do_request(const tonlib_api::query_estimateFees& request,
td::Promise<object_ptr<tonlib_api::query_fees>>&& promise);
td::Status do_request(const tonlib_api::query_send& request, td::Promise<object_ptr<tonlib_api::ok>>&& promise);
td::Status do_request(tonlib_api::query_forget& request, td::Promise<object_ptr<tonlib_api::ok>>&& promise);
td::Status do_request(tonlib_api::generic_createSendGramsQuery& request,
td::Promise<object_ptr<tonlib_api::query_info>>&& promise);
td::int64 next_smc_id_{0};
std::map<td::int64, td::unique_ptr<AccountState>> smcs_;
td::int64 register_smc(td::unique_ptr<AccountState> smc);
td::Result<tonlib_api::object_ptr<tonlib_api::smc_info>> get_smc_info(td::int64 id);
void finish_load_smc(td::unique_ptr<AccountState> query, td::Promise<object_ptr<tonlib_api::smc_info>>&& promise);
td::Status do_request(const tonlib_api::smc_load& request, td::Promise<object_ptr<tonlib_api::smc_info>>&& promise);
td::Status do_request(const tonlib_api::smc_getCode& request,
td::Promise<object_ptr<tonlib_api::tvm_cell>>&& promise);
td::Status do_request(const tonlib_api::smc_getData& request,
td::Promise<object_ptr<tonlib_api::tvm_cell>>&& promise);
td::Status do_request(const tonlib_api::smc_getState& request,
td::Promise<object_ptr<tonlib_api::tvm_cell>>&& promise);
td::Status do_request(int_api::GetAccountState request, td::Promise<td::unique_ptr<AccountState>>&&);
td::Status do_request(int_api::GetPrivateKey request, td::Promise<KeyStorage::PrivateKey>&&);
td::Status do_request(int_api::SendMessage request, td::Promise<td::Unit>&& promise);
td::Status do_request(const tonlib_api::liteServer_getInfo& request,
td::Promise<object_ptr<tonlib_api::liteServer_info>>&& promise);
void proxy_request(td::int64 query_id, std::string data);
friend class TonlibQueryActor;

View file

@ -27,6 +27,8 @@
// INVALID_MNEMONIC
// INVALID_BAG_OF_CELLS
// INVALID_PUBLIC_KEY
// INVALID_QUERY_ID
// INVALID_SMC_ID
// INVALID_ACCOUNT_ADDRESS
// INVALID_CONFIG
// INVALID_PEM_KEY
@ -65,6 +67,12 @@ struct TonlibError {
static td::Status InvalidAccountAddress() {
return td::Status::Error(400, "INVALID_ACCOUNT_ADDRESS");
}
static td::Status InvalidQueryId() {
return td::Status::Error(400, "INVALID_QUERY_ID");
}
static td::Status InvalidSmcId() {
return td::Status::Error(400, "INVALID_SMC_ID");
}
static td::Status InvalidConfig(td::Slice reason) {
return td::Status::Error(400, PSLICE() << "INVALID_CONFIG: " << reason);
}
@ -110,6 +118,9 @@ struct TonlibError {
static td::Status ValidateTransactions() {
return td::Status::Error(500, "VALIDATE_TRANSACTION");
}
static td::Status ValidateConfig() {
return td::Status::Error(500, "VALIDATE_CONFIG");
}
static td::Status ValidateZeroState(td::Slice message) {
return td::Status::Error(500, PSLICE() << "VALIDATE_ZERO_STATE: " << message);
}

View file

@ -20,6 +20,57 @@
#include <iostream>
#include <map>
// Consider this as a TODO list:
//
// (from lite-client)
// SUPPORTED
// "time\tGet server time\n"
// "remote-version\tShows server time, version and capabilities\n"
// "help [<command>]\tThis help\n" // TODO: support [<command>]
// "quit\tExit\n";
// "sendfile <filename>\tLoad a serialized message from <filename> and send it to server\n"
//
// "saveaccount[code|data] <filename> <addr> [<block-id-ext>]\tSaves into specified file the most recent state "
// "runmethod <addr> <method-id> <params>...\tRuns GET method <method-id> of account <addr> "
// "with specified parameters\n"
//
// WONTSUPPORT
//
// UNSUPPORTED
//"last\tGet last block and state info from server\n"
//"status\tShow connection and local database status\n"
//"getaccount <addr> [<block-id-ext>]\tLoads the most recent state of specified account; <addr> is in "
//"[<workchain>:]<hex-or-base64-addr> format\n"
//"(StateInit) or just the code or data of specified account; <addr> is in "
//"[<workchain>:]<hex-or-base64-addr> format\n"
//"allshards [<block-id-ext>]\tShows shard configuration from the most recent masterchain "
//"state or from masterchain state corresponding to <block-id-ext>\n"
//"getconfig [<param>...]\tShows specified or all configuration parameters from the latest masterchain state\n"
//"getconfigfrom <block-id-ext> [<param>...]\tShows specified or all configuration parameters from the "
//"masterchain state of <block-id-ext>\n"
//"saveconfig <filename> [<block-id-ext>]\tSaves all configuration parameters into specified file\n"
//"gethead <block-id-ext>\tShows block header for <block-id-ext>\n"
//"getblock <block-id-ext>\tDownloads block\n"
//"dumpblock <block-id-ext>\tDownloads and dumps specified block\n"
//"getstate <block-id-ext>\tDownloads state corresponding to specified block\n"
//"dumpstate <block-id-ext>\tDownloads and dumps state corresponding to specified block\n"
//"dumptrans <block-id-ext> <account-id> <trans-lt>\tDumps one transaction of specified account\n"
//"lasttrans[dump] <account-id> <trans-lt> <trans-hash> [<count>]\tShows or dumps specified transaction and "
//"several preceding "
//"ones\n"
//"listblocktrans[rev] <block-id-ext> <count> [<start-account-id> <start-trans-lt>]\tLists block transactions, "
//"starting immediately after or before the specified one\n"
//"blkproofchain[step] <from-block-id-ext> [<to-block-id-ext>]\tDownloads and checks proof of validity of the /"second "
//"indicated block (or the last known masterchain block) starting from given block\n"
//"byseqno <workchain> <shard-prefix> <seqno>\tLooks up a block by workchain, shard and seqno, and shows its "
//"header\n"
//"bylt <workchain> <shard-prefix> <lt>\tLooks up a block by workchain, shard and logical time, and shows its "
//"header\n"
//"byutime <workchain> <shard-prefix> <utime>\tLooks up a block by workchain, shard and creation time, and "
//"shows its header\n"
//"known\tShows the list of all known block ids\n"
//"privkey <filename>\tLoads a private key from file\n"
class TonlibCli : public td::actor::Actor {
public:
struct Options {
@ -201,8 +252,25 @@ class TonlibCli : public td::actor::Actor {
}
return true;
};
td::Promise<td::Unit> cmd_promise = [line = line.clone()](td::Result<td::Unit> res) {
if (res.is_ok()) {
// on_ok
} else {
td::TerminalIO::out() << "Query {" << line.as_slice() << "} FAILED: \n\t" << res.error() << "\n";
}
};
if (cmd == "help") {
td::TerminalIO::out() << "help - show this help\n";
td::TerminalIO::out() << "help\tThis help\n";
td::TerminalIO::out() << "time\tGet server time\n";
td::TerminalIO::out() << "remote-version\tShows server time, version and capabilities\n";
td::TerminalIO::out() << "sendfile <filename>\tLoad a serialized message from <filename> and send it to server\n";
td::TerminalIO::out() << "exit\tExit\n";
td::TerminalIO::out() << "quit\tExit\n";
td::TerminalIO::out()
<< "saveaccount[code|data] <filename> <addr>\tSaves into specified file the most recent state\n";
td::TerminalIO::out() << "genkey - generate new secret key\n";
td::TerminalIO::out() << "keys - show all stored keys\n";
td::TerminalIO::out() << "unpackaddress <address> - validate and parse address\n";
@ -210,6 +278,7 @@ class TonlibCli : public td::actor::Actor {
td::TerminalIO::out() << "importkey - import key\n";
td::TerminalIO::out() << "deletekeys - delete ALL PRIVATE KEYS\n";
td::TerminalIO::out() << "exportkey [<key_id>] - export key\n";
td::TerminalIO::out() << "exportkeypem [<key_id>] - export key\n";
td::TerminalIO::out() << "setconfig <path> [<name>] [<use_callback>] [<force>] - set lite server config\n";
td::TerminalIO::out() << "getstate <key_id> - get state of simple wallet with requested key\n";
td::TerminalIO::out()
@ -219,10 +288,9 @@ class TonlibCli : public td::actor::Actor {
"<from_key_id> to <to_key_id>.\n"
<< "\t<from_key_id> could also be 'giver'\n"
<< "\t<to_key_id> could also be 'giver' or smartcontract address\n";
td::TerminalIO::out() << "exit - exit from this programm\n";
} else if (cmd == "genkey") {
generate_key();
} else if (cmd == "exit") {
} else if (cmd == "exit" || cmd == "quit") {
is_closing_ = true;
client_.reset();
ref_cnt_--;
@ -233,8 +301,8 @@ class TonlibCli : public td::actor::Actor {
//delete_key(parser.read_word());
} else if (cmd == "deletekeys") {
delete_all_keys();
} else if (cmd == "exportkey") {
export_key(parser.read_word());
} else if (cmd == "exportkey" || cmd == "exportkeypem") {
export_key(cmd.str(), parser.read_word());
} else if (cmd == "importkey") {
import_key(parser.read_all());
} else if (cmd == "setconfig") {
@ -265,20 +333,93 @@ class TonlibCli : public td::actor::Actor {
set_bounceable(addr, to_bool(bounceable, true));
} else if (cmd == "netstats") {
dump_netstats();
// reviewed from here
} else if (cmd == "sync") {
sync();
sync(std::move(cmd_promise));
} else if (cmd == "time") {
remote_time(std::move(cmd_promise));
} else if (cmd == "remote-version") {
remote_version(std::move(cmd_promise));
} else if (cmd == "sendfile") {
send_file(parser.read_word(), std::move(cmd_promise));
} else if (cmd == "saveaccount" || cmd == "saveaccountdata" || cmd == "saveaccountcode") {
auto path = parser.read_word();
auto address = parser.read_word();
save_account(cmd, path, address, std::move(cmd_promise));
} else {
cmd_promise.set_error(td::Status::Error(PSLICE() << "Unkwnown query `" << cmd << "`"));
}
if (cmd_promise) {
cmd_promise.set_value(td::Unit());
}
}
void sync() {
using tonlib_api::make_object;
send_query(make_object<tonlib_api::sync>(), [](auto r_ok) {
LOG_IF(ERROR, r_ok.is_error()) << r_ok.error();
if (r_ok.is_ok()) {
td::TerminalIO::out() << "synchronized\n";
}
});
void remote_time(td::Promise<td::Unit> promise) {
send_query(tonlib_api::make_object<tonlib_api::liteServer_getInfo>(), promise.wrap([](auto&& info) {
td::TerminalIO::out() << "Lite server time is: " << info->now_ << "\n";
return td::Unit();
}));
}
void remote_version(td::Promise<td::Unit> promise) {
send_query(tonlib_api::make_object<tonlib_api::liteServer_getInfo>(), promise.wrap([](auto&& info) {
td::TerminalIO::out() << "Lite server time is: " << info->now_ << "\n";
td::TerminalIO::out() << "Lite server version is: " << info->version_ << "\n";
td::TerminalIO::out() << "Lite server capabilities are: " << info->capabilities_ << "\n";
return td::Unit();
}));
}
void send_file(td::Slice name, td::Promise<td::Unit> promise) {
TRY_RESULT_PROMISE(promise, data, td::read_file_str(name.str()));
send_query(tonlib_api::make_object<tonlib_api::raw_sendMessage>(std::move(data)), promise.wrap([](auto&& info) {
td::TerminalIO::out() << "Query was sent\n";
return td::Unit();
}));
}
void save_account(td::Slice cmd, td::Slice path, td::Slice address, td::Promise<td::Unit> promise) {
TRY_RESULT_PROMISE(promise, addr, to_account_address(address, false));
send_query(tonlib_api::make_object<tonlib_api::smc_load>(std::move(addr.address)),
promise.send_closure(actor_id(this), &TonlibCli::save_account_2, cmd.str(), path.str(), address.str()));
}
void save_account_2(std::string cmd, std::string path, std::string address,
tonlib_api::object_ptr<tonlib_api::smc_info> info, td::Promise<td::Unit> promise) {
auto with_query = [&, self = this](auto query, auto log) {
send_query(std::move(query),
promise.send_closure(actor_id(self), &TonlibCli::save_account_3, std::move(path), std::move(log)));
};
if (cmd == "saveaccount") {
with_query(tonlib_api::make_object<tonlib_api::smc_getState>(info->id_),
PSTRING() << "StateInit of account " << address);
} else if (cmd == "saveaccountcode") {
with_query(tonlib_api::make_object<tonlib_api::smc_getCode>(info->id_), PSTRING()
<< "Code of account " << address);
} else if (cmd == "saveaccountdata") {
with_query(tonlib_api::make_object<tonlib_api::smc_getData>(info->id_), PSTRING()
<< "Data of account " << address);
} else {
promise.set_error(td::Status::Error("Unknown query"));
}
}
void save_account_3(std::string path, std::string log, tonlib_api::object_ptr<tonlib_api::tvm_cell> cell,
td::Promise<td::Unit> promise) {
TRY_STATUS_PROMISE(promise, td::write_file(path, cell->bytes_));
td::TerminalIO::out() << log << " was successfully written to the disk(" << td::format::as_size(cell->bytes_.size())
<< ")\n";
promise.set_value(td::Unit());
}
void sync(td::Promise<td::Unit> promise) {
using tonlib_api::make_object;
send_query(make_object<tonlib_api::sync>(), promise.wrap([](auto&&) {
td::TerminalIO::out() << "synchronized\n";
return td::Unit();
}));
}
void dump_netstats() {
td::TerminalIO::out() << td::tag("snd", td::format::as_size(snd_bytes_)) << "\n";
td::TerminalIO::out() << td::tag("rcv", td::format::as_size(rcv_bytes_)) << "\n";
@ -298,15 +439,41 @@ class TonlibCli : public td::actor::Actor {
void on_tonlib_result(std::uint64_t id, tonlib_api::object_ptr<tonlib_api::Object> result) {
if (id == 0) {
if (result->get_id() == tonlib_api::updateSendLiteServerQuery::ID) {
auto update = tonlib_api::move_object_as<tonlib_api::updateSendLiteServerQuery>(std::move(result));
CHECK(!raw_client_.empty());
snd_bytes_ += update->data_.size();
send_closure(raw_client_, &ton::adnl::AdnlExtClient::send_query, "query", td::BufferSlice(update->data_),
td::Timestamp::in(5),
[actor_id = actor_id(this), id = update->id_](td::Result<td::BufferSlice> res) {
send_closure(actor_id, &TonlibCli::on_adnl_result, id, std::move(res));
});
switch (result->get_id()) {
case tonlib_api::updateSendLiteServerQuery::ID: {
auto update = tonlib_api::move_object_as<tonlib_api::updateSendLiteServerQuery>(std::move(result));
CHECK(!raw_client_.empty());
snd_bytes_ += update->data_.size();
send_closure(raw_client_, &ton::adnl::AdnlExtClient::send_query, "query", td::BufferSlice(update->data_),
td::Timestamp::in(5),
[actor_id = actor_id(this), id = update->id_](td::Result<td::BufferSlice> res) {
send_closure(actor_id, &TonlibCli::on_adnl_result, id, std::move(res));
});
return;
}
case tonlib_api::updateSyncState::ID: {
auto update = tonlib_api::move_object_as<tonlib_api::updateSyncState>(std::move(result));
switch (update->sync_state_->get_id()) {
case tonlib_api::syncStateDone::ID: {
td::TerminalIO::out() << "synchronization: DONE\n";
break;
}
case tonlib_api::syncStateInProgress::ID: {
auto progress = tonlib_api::move_object_as<tonlib_api::syncStateInProgress>(update->sync_state_);
auto from = progress->from_seqno_;
auto to = progress->to_seqno_;
auto at = progress->current_seqno_;
auto d = to - from;
if (d <= 0) {
td::TerminalIO::out() << "synchronization: ???\n";
} else {
td::TerminalIO::out() << "synchronization: " << 100 * (at - from) / d << "%\n";
}
break;
}
}
return;
}
}
}
auto it = query_handlers_.find(id);
@ -407,7 +574,7 @@ class TonlibCli : public td::actor::Actor {
info.public_key = key->public_key_;
info.secret = std::move(key->secret_);
keys_.push_back(std::move(info));
export_key(key->public_key_, keys_.size() - 1, std::move(password));
export_key("exportkey", key->public_key_, keys_.size() - 1, std::move(password));
store_keys();
});
}
@ -596,11 +763,11 @@ class TonlibCli : public td::actor::Actor {
td::TerminalIO::out() << "Ok\n";
});
}
void export_key(td::Slice key) {
void export_key(std::string cmd, td::Slice key) {
if (key.empty()) {
dump_keys();
td::TerminalIO::out() << "Choose public key (hex prefix or #N)";
cont_ = [this](td::Slice key) { this->export_key(key); };
cont_ = [this, cmd](td::Slice key) { this->export_key(cmd, key); };
return;
}
auto r_key_i = to_key_i(key);
@ -614,24 +781,40 @@ class TonlibCli : public td::actor::Actor {
<< "public key: " << td::buffer_to_hex(keys_[key_i].public_key) << "\n";
td::TerminalIO::out() << "Enter password (could be empty)";
cont_ = [this, key = key.str(), key_i](td::Slice password) { this->export_key(key, key_i, password); };
cont_ = [this, cmd, key = key.str(), key_i](td::Slice password) { this->export_key(cmd, key, key_i, password); };
}
void export_key(std::string key, size_t key_i, td::Slice password) {
void export_key(std::string cmd, std::string key, size_t key_i, td::Slice password) {
using tonlib_api::make_object;
send_query(make_object<tonlib_api::exportKey>(make_object<tonlib_api::inputKey>(
make_object<tonlib_api::key>(keys_[key_i].public_key, keys_[key_i].secret.copy()),
td::SecureString(password))),
[this, key = std::move(key), key_i](auto r_res) {
if (r_res.is_error()) {
td::TerminalIO::out() << "Can't export key id: [" << key << "] " << r_res.error() << "\n";
return;
}
dump_key(key_i);
for (auto& word : r_res.ok()->word_list_) {
td::TerminalIO::out() << " " << word.as_slice() << "\n";
}
});
if (cmd == "exportkey") {
send_query(make_object<tonlib_api::exportKey>(make_object<tonlib_api::inputKeyRegular>(
make_object<tonlib_api::key>(keys_[key_i].public_key, keys_[key_i].secret.copy()),
td::SecureString(password))),
[this, key = std::move(key), key_i](auto r_res) {
if (r_res.is_error()) {
td::TerminalIO::out() << "Can't export key id: [" << key << "] " << r_res.error() << "\n";
return;
}
dump_key(key_i);
for (auto& word : r_res.ok()->word_list_) {
td::TerminalIO::out() << " " << word.as_slice() << "\n";
}
});
} else {
send_query(make_object<tonlib_api::exportPemKey>(
make_object<tonlib_api::inputKeyRegular>(
make_object<tonlib_api::key>(keys_[key_i].public_key, keys_[key_i].secret.copy()),
td::SecureString(password)),
td::SecureString("cucumber")),
[this, key = std::move(key), key_i](auto r_res) {
if (r_res.is_error()) {
td::TerminalIO::out() << "Can't export key id: [" << key << "] " << r_res.error() << "\n";
return;
}
dump_key(key_i);
td::TerminalIO::out() << "\n" << r_res.ok()->pem_.as_slice() << "\n";
});
}
}
void import_key(td::Slice slice, std::vector<td::SecureString> words = {}) {
@ -669,7 +852,7 @@ class TonlibCli : public td::actor::Actor {
info.public_key = key->public_key_;
info.secret = std::move(key->secret_);
keys_.push_back(std::move(info));
export_key(key->public_key_, keys_.size() - 1, std::move(password));
export_key("exportkey", key->public_key_, keys_.size() - 1, std::move(password));
store_keys();
});
}
@ -830,21 +1013,42 @@ class TonlibCli : public td::actor::Actor {
}
using tonlib_api::make_object;
auto key = !from.secret.empty()
? make_object<tonlib_api::inputKey>(
? make_object<tonlib_api::inputKeyRegular>(
make_object<tonlib_api::key>(from.public_key, from.secret.copy()), td::SecureString(password))
: nullptr;
send_query(
make_object<tonlib_api::generic_sendGrams>(std::move(key), std::move(from.address), std::move(to.address),
grams, 60, allow_send_to_uninited, std::move(msg)),
[this](auto r_res) {
if (r_res.is_error()) {
td::TerminalIO::out() << "Can't transfer: " << r_res.error() << "\n";
on_error();
return;
}
td::TerminalIO::out() << to_string(r_res.ok());
on_ok();
});
send_query(make_object<tonlib_api::generic_createSendGramsQuery>(std::move(key), std::move(from.address),
std::move(to.address), grams, 60,
allow_send_to_uninited, std::move(msg)),
[self = this](auto r_res) {
if (r_res.is_error()) {
td::TerminalIO::out() << "Can't transfer: " << r_res.error() << "\n";
self->on_error();
return;
}
td::TerminalIO::out() << to_string(r_res.ok());
self->send_query(make_object<tonlib_api::query_estimateFees>(r_res.ok()->id_, false),
[self](auto r_res) {
if (r_res.is_error()) {
td::TerminalIO::out() << "Can't transfer: " << r_res.error() << "\n";
self->on_error();
return;
}
td::TerminalIO::out() << to_string(r_res.ok());
self->on_ok();
});
self->send_query(make_object<tonlib_api::query_send>(r_res.ok()->id_), [self](auto r_res) {
if (r_res.is_error()) {
td::TerminalIO::out() << "Can't transfer: " << r_res.error() << "\n";
self->on_error();
return;
}
td::TerminalIO::out() << to_string(r_res.ok());
self->on_ok();
});
self->on_ok();
});
}
void init_simple_wallet(td::Slice key) {
@ -871,7 +1075,7 @@ class TonlibCli : public td::actor::Actor {
void init_simple_wallet(std::string key, size_t key_i, td::Slice password) {
using tonlib_api::make_object;
if (options_.use_simple_wallet) {
send_query(make_object<tonlib_api::testWallet_init>(make_object<tonlib_api::inputKey>(
send_query(make_object<tonlib_api::testWallet_init>(make_object<tonlib_api::inputKeyRegular>(
make_object<tonlib_api::key>(keys_[key_i].public_key, keys_[key_i].secret.copy()),
td::SecureString(password))),
[key = std::move(key)](auto r_res) {
@ -882,7 +1086,7 @@ class TonlibCli : public td::actor::Actor {
td::TerminalIO::out() << to_string(r_res.ok());
});
} else {
send_query(make_object<tonlib_api::wallet_init>(make_object<tonlib_api::inputKey>(
send_query(make_object<tonlib_api::wallet_init>(make_object<tonlib_api::inputKeyRegular>(
make_object<tonlib_api::key>(keys_[key_i].public_key, keys_[key_i].secret.copy()),
td::SecureString(password))),
[key = std::move(key)](auto r_res) {

View file

@ -20,20 +20,9 @@
#include "td/utils/misc.h"
#include "vm/cellslice.h"
namespace tonlib {
int VERBOSITY_NAME(tonlib_query) = VERBOSITY_NAME(INFO);
int VERBOSITY_NAME(last_block) = VERBOSITY_NAME(INFO);
int VERBOSITY_NAME(lite_server) = VERBOSITY_NAME(INFO);
int VERBOSITY_NAME(tonlib_query) = VERBOSITY_NAME(DEBUG);
int VERBOSITY_NAME(last_block) = VERBOSITY_NAME(DEBUG);
int VERBOSITY_NAME(last_config) = VERBOSITY_NAME(DEBUG);
int VERBOSITY_NAME(lite_server) = VERBOSITY_NAME(DEBUG);
td::Result<td::Ref<vm::CellSlice>> binary_bitstring_to_cellslice(td::Slice literal) {
unsigned char buff[128];
if (!begins_with(literal, "b{") || !ends_with(literal, "}")) {
return td::Status::Error("Invalid binary bitstring constant");
}
int bits =
(int)td::bitstring::parse_bitstring_binary_literal(buff, sizeof(buff), literal.begin() + 2, literal.end() - 1);
if (bits < 0) {
return td::Status::Error("Invalid binary bitstring constant");
}
return td::Ref<vm::CellSlice>{true, vm::CellBuilder().store_bits(td::ConstBitPtr{buff}, bits).finalize()};
}
} // namespace tonlib

View file

@ -23,19 +23,8 @@
#include "block/block-parse.h"
namespace tonlib {
template <class F>
auto try_f(F&& f) noexcept -> decltype(f()) {
try {
return f();
} catch (vm::VmError error) {
return td::Status::Error(PSLICE() << "Got a vm exception: " << error.get_msg());
}
}
#define TRY_VM(f) try_f([&] { return f; })
extern int VERBOSITY_NAME(tonlib_query);
extern int VERBOSITY_NAME(last_block);
extern int VERBOSITY_NAME(last_config);
extern int VERBOSITY_NAME(lite_server);
td::Result<td::Ref<vm::CellSlice>> binary_bitstring_to_cellslice(td::Slice literal);
} // namespace tonlib

View file

@ -10,6 +10,8 @@ add_subdirectory(impl)
set(VALIDATOR_DB_SOURCE
db/archiver.cpp
db/archiver.hpp
db/archive-db.cpp
db/archive-db.hpp
db/blockdb.cpp
db/blockdb.hpp
db/celldb.cpp
@ -25,6 +27,9 @@ set(VALIDATOR_DB_SOURCE
db/statedb.cpp
db/staticfilesdb.cpp
db/staticfilesdb.hpp
db/package.hpp
db/package.cpp
)
set(VALIDATOR_HEADERS

View file

@ -59,6 +59,7 @@ struct BlockHandleImpl : public BlockHandleInterface {
dbf_moved = 0x1000000,
dbf_deleted = 0x2000000,
dbf_deleted_boc = 0x4000000,
dbf_moved_new = 0x8000000,
dbf_processed = 0x10000000,
};
@ -95,6 +96,9 @@ struct BlockHandleImpl : public BlockHandleInterface {
bool moved_to_storage() const override {
return flags_.load(std::memory_order_consume) & Flags::dbf_moved;
}
bool moved_to_archive() const override {
return flags_.load(std::memory_order_consume) & Flags::dbf_moved_new;
}
bool deleted() const override {
return flags_.load(std::memory_order_consume) & Flags::dbf_deleted;
}
@ -393,6 +397,15 @@ struct BlockHandleImpl : public BlockHandleInterface {
flags_ |= Flags::dbf_moved;
unlock();
}
void set_moved_to_archive() override {
if (flags_.load(std::memory_order_consume) & Flags::dbf_moved_new) {
return;
}
lock();
flags_ |= Flags::dbf_moved_new;
flags_ &= ~Flags::dbf_moved;
unlock();
}
void set_deleted() override {
if (flags_.load(std::memory_order_consume) & Flags::dbf_deleted) {
return;

332
validator/db/archive-db.cpp Normal file
View file

@ -0,0 +1,332 @@
#include "archive-db.hpp"
#include "common/errorcode.h"
#include "common/int-to-string.hpp"
#include "files-async.hpp"
#include "td/db/RocksDb.h"
#include "validator/fabric.h"
namespace ton {
namespace validator {
void PackageWriter::append(std::string filename, td::BufferSlice data,
td::Promise<std::pair<td::uint64, td::uint64>> promise) {
auto offset = package_->append(std::move(filename), std::move(data));
auto size = package_->size();
promise.set_value(std::pair<td::uint64, td::uint64>{offset, size});
}
class PackageReader : public td::actor::Actor {
public:
PackageReader(std::shared_ptr<Package> package, td::uint64 offset,
td::Promise<std::pair<std::string, td::BufferSlice>> promise)
: package_(std::move(package)), offset_(offset), promise_(std::move(promise)) {
}
void start_up() {
promise_.set_result(package_->read(offset_));
}
private:
std::shared_ptr<Package> package_;
td::uint64 offset_;
td::Promise<std::pair<std::string, td::BufferSlice>> promise_;
};
void ArchiveFile::start_up() {
auto R = Package::open(path_, false, true);
if (R.is_error()) {
LOG(FATAL) << "failed to open/create archive '" << path_ << "': " << R.move_as_error();
return;
}
package_ = std::make_shared<Package>(R.move_as_ok());
index_ = std::make_shared<td::RocksDb>(td::RocksDb::open(path_ + ".index").move_as_ok());
std::string value;
auto R2 = index_->get("status", value);
R2.ensure();
if (R2.move_as_ok() == td::KeyValue::GetStatus::Ok) {
auto len = td::to_integer<td::uint64>(value);
package_->truncate(len);
} else {
package_->truncate(0);
}
writer_ = td::actor::create_actor<PackageWriter>("writer", package_);
}
void ArchiveFile::write(FileDb::RefId ref_id, td::BufferSlice data, td::Promise<td::Unit> promise) {
auto P = td::PromiseCreator::lambda([SelfId = actor_id(this), ref_id, promise = std::move(promise)](
td::Result<std::pair<td::uint64, td::uint64>> R) mutable {
if (R.is_error()) {
promise.set_error(R.move_as_error());
return;
}
auto v = R.move_as_ok();
td::actor::send_closure(SelfId, &ArchiveFile::completed_write, std::move(ref_id), v.first, v.second,
std::move(promise));
});
FileHash hash;
ref_id.visit([&](const auto &obj) { hash = obj.hash(); });
td::actor::send_closure(writer_, &PackageWriter::append, hash.to_hex(), std::move(data), std::move(P));
}
void ArchiveFile::write_handle(BlockHandle handle, td::Promise<td::Unit> promise) {
FileHash hash = fileref::BlockInfo{handle->id()}.hash();
index_->begin_transaction().ensure();
do {
auto version = handle->version();
index_->set(hash.to_hex(), handle->serialize().as_slice().str()).ensure();
handle->flushed_upto(version);
} while (handle->need_flush());
index_->commit_transaction().ensure();
promise.set_value(td::Unit());
}
void ArchiveFile::completed_write(FileDb::RefId ref_id, td::uint64 offset, td::uint64 new_size,
td::Promise<td::Unit> promise) {
FileHash hash;
ref_id.visit([&](const auto &obj) { hash = obj.hash(); });
index_->begin_transaction().ensure();
index_->set("status", td::to_string(new_size)).ensure();
index_->set(hash.to_hex(), td::to_string(offset)).ensure();
index_->commit_transaction().ensure();
promise.set_value(td::Unit());
}
void ArchiveFile::read(FileDb::RefId ref_id, td::Promise<td::BufferSlice> promise) {
FileHash hash;
ref_id.visit([&](const auto &obj) { hash = obj.hash(); });
std::string value;
auto R = index_->get(hash.to_hex(), value);
R.ensure();
if (R.move_as_ok() == td::KeyValue::GetStatus::NotFound) {
promise.set_error(td::Status::Error(ErrorCode::notready, "not in db (archive)"));
return;
}
auto offset = td::to_integer<td::uint64>(value);
auto P = td::PromiseCreator::lambda(
[promise = std::move(promise)](td::Result<std::pair<std::string, td::BufferSlice>> R) mutable {
if (R.is_error()) {
promise.set_error(R.move_as_error());
} else {
promise.set_value(std::move(R.move_as_ok().second));
}
});
td::actor::create_actor<PackageReader>("reader", package_, offset, std::move(P)).release();
}
void ArchiveFile::read_handle(BlockIdExt block_id, td::Promise<BlockHandle> promise) {
FileHash hash = fileref::BlockInfo{block_id}.hash();
std::string value;
auto R = index_->get(hash.to_hex(), value);
R.ensure();
if (R.move_as_ok() == td::KeyValue::GetStatus::NotFound) {
promise.set_error(td::Status::Error(ErrorCode::notready, "not in archive db"));
return;
}
promise.set_result(create_block_handle(td::BufferSlice{value}));
}
ArchiveManager::ArchiveManager(std::string db_root) : db_root_(db_root) {
}
void ArchiveManager::write(UnixTime ts, bool key_block, FileDb::RefId ref_id, td::BufferSlice data,
td::Promise<td::Unit> promise) {
auto f = get_file(ts, key_block);
td::actor::send_closure(f->file_actor_id(), &ArchiveFile::write, std::move(ref_id), std::move(data),
std::move(promise));
}
void ArchiveManager::write_handle(BlockHandle handle, td::Promise<td::Unit> promise) {
auto f = get_file(handle->unix_time(), handle->is_key_block());
update_desc(*f, handle->id().shard_full(), handle->id().seqno(), handle->logical_time());
td::actor::send_closure(f->file_actor_id(), &ArchiveFile::write_handle, std::move(handle), std::move(promise));
}
void ArchiveManager::read(UnixTime ts, bool key_block, FileDb::RefId ref_id, td::Promise<td::BufferSlice> promise) {
auto f = get_file(ts, key_block);
td::actor::send_closure(f->file_actor_id(), &ArchiveFile::read, std::move(ref_id), std::move(promise));
}
void ArchiveManager::read_handle(BlockIdExt block_id, td::Promise<BlockHandle> promise) {
if (block_id.is_masterchain()) {
auto f = get_file_by_seqno(block_id.shard_full(), block_id.seqno(), true);
if (!f) {
read_handle_cont(block_id, std::move(promise));
return;
}
auto P = td::PromiseCreator::lambda(
[SelfId = actor_id(this), block_id, promise = std::move(promise)](td::Result<BlockHandle> R) mutable {
if (R.is_error()) {
td::actor::send_closure(SelfId, &ArchiveManager::read_handle_cont, block_id, std::move(promise));
} else {
promise.set_value(R.move_as_ok());
}
});
td::actor::send_closure(f->file_actor_id(), &ArchiveFile::read_handle, block_id, std::move(P));
} else {
read_handle_cont(block_id, std::move(promise));
}
}
void ArchiveManager::read_handle_cont(BlockIdExt block_id, td::Promise<BlockHandle> promise) {
auto f = get_file_by_seqno(block_id.shard_full(), block_id.seqno(), false);
if (!f) {
promise.set_error(td::Status::Error(ErrorCode::notready, "not in archive db"));
return;
}
td::actor::send_closure(f->file_actor_id(), &ArchiveFile::read_handle, block_id, std::move(promise));
}
UnixTime ArchiveManager::convert_ts(UnixTime ts, bool key_block) {
if (!key_block) {
return ts - (ts % (1 << 17));
} else {
return ts - (ts % (1 << 22));
}
}
ArchiveManager::FileDescription *ArchiveManager::get_file(UnixTime ts, bool key_block, bool force) {
ts = convert_ts(ts, key_block);
auto &f = key_block ? key_files_ : files_;
auto it = f.find(ts);
if (it != f.end()) {
return &it->second;
}
if (!force) {
return nullptr;
}
return add_file(ts, key_block);
}
ArchiveManager::FileDescription *ArchiveManager::add_file(UnixTime ts, bool key_block) {
CHECK((key_block ? key_files_ : files_).count(ts) == 0);
index_->begin_transaction().ensure();
{
std::vector<td::int32> t;
std::vector<td::int32> tk;
for (auto &e : files_) {
t.push_back(e.first);
}
for (auto &e : key_files_) {
tk.push_back(e.first);
}
(key_block ? tk : t).push_back(ts);
index_
->set(create_serialize_tl_object<ton_api::db_archive_index_key>().as_slice(),
create_serialize_tl_object<ton_api::db_archive_index_value>(std::move(t), std::move(tk)).as_slice())
.ensure();
}
{
index_
->set(create_serialize_tl_object<ton_api::db_archive_package_key>(ts, key_block).as_slice(),
create_serialize_tl_object<ton_api::db_archive_package_value>(
ts, key_block, std::vector<tl_object_ptr<ton_api::db_archive_package_firstBlock>>(), false)
.as_slice())
.ensure();
}
index_->commit_transaction().ensure();
FileDescription desc{ts, key_block};
auto w = td::actor::create_actor<ArchiveFile>(
PSTRING() << "archivefile" << ts,
PSTRING() << db_root_ << "/packed/" << (key_block ? "key" : "") << ts << ".pack", ts);
desc.file = std::move(w);
return &(key_block ? key_files_ : files_).emplace(ts, std::move(desc)).first->second;
}
void ArchiveManager::load_package(UnixTime ts, bool key_block) {
auto key = create_serialize_tl_object<ton_api::db_archive_package_key>(ts, key_block);
std::string value;
auto v = index_->get(key.as_slice(), value);
v.ensure();
CHECK(v.move_as_ok() == td::KeyValue::GetStatus::Ok);
auto R = fetch_tl_object<ton_api::db_archive_package_value>(value, true);
R.ensure();
auto x = R.move_as_ok();
if (x->deleted_) {
return;
}
FileDescription desc{ts, key_block};
for (auto &e : x->firstblocks_) {
desc.first_blocks[ShardIdFull{e->workchain_, static_cast<ShardId>(e->shard_)}] =
FileDescription::Desc{static_cast<td::uint32>(e->seqno_), static_cast<LogicalTime>(e->lt_)};
}
desc.file = td::actor::create_actor<ArchiveFile>(
PSTRING() << "archivefile" << ts,
PSTRING() << db_root_ << "/packed/" << (key_block ? "key" : "") << ts << ".pack", ts);
(key_block ? key_files_ : files_).emplace(ts, std::move(desc));
}
void ArchiveManager::update_desc(FileDescription &desc, ShardIdFull shard, BlockSeqno seqno, LogicalTime lt) {
auto it = desc.first_blocks.find(shard);
if (it != desc.first_blocks.end() && it->second.seqno <= seqno) {
return;
}
desc.first_blocks[shard] = FileDescription::Desc{seqno, lt};
std::vector<tl_object_ptr<ton_api::db_archive_package_firstBlock>> vec;
for (auto &e : desc.first_blocks) {
vec.push_back(create_tl_object<ton_api::db_archive_package_firstBlock>(e.first.workchain, e.first.shard,
e.second.seqno, e.second.lt));
}
index_->begin_transaction().ensure();
index_
->set(create_serialize_tl_object<ton_api::db_archive_package_key>(desc.unix_time, desc.key_block).as_slice(),
create_serialize_tl_object<ton_api::db_archive_package_value>(desc.unix_time, desc.key_block,
std::move(vec), false)
.as_slice())
.ensure();
index_->commit_transaction().ensure();
}
ArchiveManager::FileDescription *ArchiveManager::get_file_by_seqno(ShardIdFull shard, BlockSeqno seqno,
bool key_block) {
auto &f = (key_block ? key_files_ : files_);
for (auto it = f.rbegin(); it != f.rend(); it++) {
auto i = it->second.first_blocks.find(shard);
if (i != it->second.first_blocks.end() && i->second.seqno <= seqno) {
return &it->second;
}
}
return nullptr;
}
void ArchiveManager::start_up() {
td::mkdir(db_root_).ensure();
td::mkdir(db_root_ + "/packed").ensure();
index_ = std::make_shared<td::RocksDb>(td::RocksDb::open(db_root_ + "/packed/globalindex").move_as_ok());
std::string value;
auto v = index_->get(create_serialize_tl_object<ton_api::db_archive_index_key>().as_slice(), value);
v.ensure();
if (v.move_as_ok() == td::KeyValue::GetStatus::Ok) {
auto R = fetch_tl_object<ton_api::db_archive_index_value>(value, true);
R.ensure();
auto x = R.move_as_ok();
for (auto &d : x->packages_) {
load_package(d, false);
}
for (auto &d : x->key_packages_) {
load_package(d, true);
}
}
}
} // namespace validator
} // namespace ton

View file

@ -0,0 +1,97 @@
#pragma once
#include "td/actor/actor.h"
#include "td/utils/buffer.h"
#include "ton/ton-types.h"
#include "td/utils/port/FileFd.h"
#include "package.hpp"
#include "filedb.hpp"
#include "validator/interfaces/block-handle.h"
#include <map>
#include <list>
namespace ton {
namespace validator {
class PackageWriter : public td::actor::Actor {
public:
PackageWriter(std::shared_ptr<Package> package) : package_(std::move(package)) {
}
void append(std::string filename, td::BufferSlice data, td::Promise<std::pair<td::uint64, td::uint64>> promise);
private:
std::shared_ptr<Package> package_;
};
class ArchiveFile : public td::actor::Actor {
public:
ArchiveFile(std::string path, UnixTime ts) : path_(std::move(path)), ts_(ts) {
}
void start_up() override;
void write(FileDb::RefId ref_id, td::BufferSlice data, td::Promise<td::Unit> promise);
void write_handle(BlockHandle handle, td::Promise<td::Unit> promise);
void read(FileDb::RefId ref_id, td::Promise<td::BufferSlice> promise);
void read_handle(BlockIdExt block_id, td::Promise<BlockHandle> promise);
private:
void completed_write(FileDb::RefId ref_id, td::uint64 offset, td::uint64 new_size, td::Promise<td::Unit> promise);
std::shared_ptr<Package> package_;
std::shared_ptr<td::KeyValue> index_;
td::actor::ActorOwn<PackageWriter> writer_;
std::string path_;
UnixTime ts_;
};
class ArchiveManager : public td::actor::Actor {
public:
ArchiveManager(std::string db_root);
void write(UnixTime ts, bool key_block, FileDb::RefId ref_id, td::BufferSlice data, td::Promise<td::Unit> promise);
void write_handle(BlockHandle handle, td::Promise<td::Unit> promise);
void read(UnixTime ts, bool key_block, FileDb::RefId ref_id, td::Promise<td::BufferSlice> promise);
void read_handle(BlockIdExt block_id, td::Promise<BlockHandle> promise);
void start_up() override;
private:
void read_handle_cont(BlockIdExt block_id, td::Promise<BlockHandle> promise);
struct FileDescription {
struct Desc {
BlockSeqno seqno;
LogicalTime lt;
};
FileDescription(UnixTime unix_time, bool key_block) : unix_time(unix_time), key_block(key_block) {
}
auto file_actor_id() const {
return file.get();
}
UnixTime unix_time;
bool key_block;
std::map<ShardIdFull, Desc> first_blocks;
td::actor::ActorOwn<ArchiveFile> file;
};
void load_package(UnixTime ts, bool key_block);
UnixTime convert_ts(UnixTime ts, bool key_block);
FileDescription *get_file(UnixTime ts, bool key_block, bool force = true);
FileDescription *add_file(UnixTime ts, bool key_block);
void update_desc(FileDescription &desc, ShardIdFull shard, BlockSeqno seqno, LogicalTime lt);
FileDescription *get_file_by_seqno(ShardIdFull shard, BlockSeqno seqno, bool key_block);
std::string db_root_;
std::map<UnixTime, FileDescription> files_;
std::map<UnixTime, FileDescription> key_files_;
std::shared_ptr<td::KeyValue> index_;
};
} // namespace validator
} // namespace ton

View file

@ -26,8 +26,13 @@ namespace validator {
BlockArchiver::BlockArchiver(BlockIdExt block_id, td::actor::ActorId<RootDb> root_db,
td::actor::ActorId<FileDb> file_db, td::actor::ActorId<FileDb> archive_db,
td::Promise<td::Unit> promise)
: block_id_(block_id), root_db_(root_db), file_db_(file_db), archive_db_(archive_db), promise_(std::move(promise)) {
td::actor::ActorId<ArchiveManager> archive, td::Promise<td::Unit> promise)
: block_id_(block_id)
, root_db_(root_db)
, file_db_(file_db)
, archive_db_(archive_db)
, archive_(archive)
, promise_(std::move(promise)) {
}
void BlockArchiver::start_up() {
@ -40,7 +45,7 @@ void BlockArchiver::start_up() {
void BlockArchiver::got_block_handle(BlockHandle handle) {
handle_ = std::move(handle);
if (handle_->moved_to_storage()) {
if (handle_->moved_to_archive()) {
finish_query();
return;
}
@ -63,16 +68,21 @@ void BlockArchiver::got_block_handle(BlockHandle handle) {
R.ensure();
td::actor::send_closure(SelfId, &BlockArchiver::got_proof, R.move_as_ok());
});
td::actor::send_closure(file_db_, &FileDb::load_file, FileDb::RefId{fileref::Proof{block_id_}}, std::move(P));
if (handle_->moved_to_storage()) {
td::actor::send_closure(archive_db_, &FileDb::load_file, FileDb::RefId{fileref::Proof{block_id_}}, std::move(P));
} else {
td::actor::send_closure(file_db_, &FileDb::load_file, FileDb::RefId{fileref::Proof{block_id_}}, std::move(P));
}
}
void BlockArchiver::got_proof(td::BufferSlice data) {
auto P = td::PromiseCreator::lambda([SelfId = actor_id(this)](td::Result<FileHash> R) {
auto P = td::PromiseCreator::lambda([SelfId = actor_id(this)](td::Result<td::Unit> R) {
R.ensure();
td::actor::send_closure(SelfId, &BlockArchiver::written_proof);
});
td::actor::send_closure(archive_db_, &FileDb::store_file, FileDb::RefId{fileref::Proof{block_id_}}, std::move(data),
std::move(P));
td::actor::send_closure(archive_, &ArchiveManager::write, handle_->unix_time(), handle_->is_key_block(),
FileDb::RefId{fileref::Proof{block_id_}}, std::move(data), std::move(P));
}
void BlockArchiver::written_proof() {
@ -85,16 +95,21 @@ void BlockArchiver::written_proof() {
R.ensure();
td::actor::send_closure(SelfId, &BlockArchiver::got_proof_link, R.move_as_ok());
});
td::actor::send_closure(file_db_, &FileDb::load_file, FileDb::RefId{fileref::ProofLink{block_id_}}, std::move(P));
if (handle_->moved_to_storage()) {
td::actor::send_closure(archive_db_, &FileDb::load_file, FileDb::RefId{fileref::ProofLink{block_id_}},
std::move(P));
} else {
td::actor::send_closure(file_db_, &FileDb::load_file, FileDb::RefId{fileref::ProofLink{block_id_}}, std::move(P));
}
}
void BlockArchiver::got_proof_link(td::BufferSlice data) {
auto P = td::PromiseCreator::lambda([SelfId = actor_id(this)](td::Result<FileHash> R) {
auto P = td::PromiseCreator::lambda([SelfId = actor_id(this)](td::Result<td::Unit> R) {
R.ensure();
td::actor::send_closure(SelfId, &BlockArchiver::written_proof_link);
});
td::actor::send_closure(archive_db_, &FileDb::store_file, FileDb::RefId{fileref::ProofLink{block_id_}},
std::move(data), std::move(P));
td::actor::send_closure(archive_, &ArchiveManager::write, handle_->unix_time(), handle_->is_key_block(),
FileDb::RefId{fileref::ProofLink{block_id_}}, std::move(data), std::move(P));
}
void BlockArchiver::written_proof_link() {
@ -106,20 +121,24 @@ void BlockArchiver::written_proof_link() {
R.ensure();
td::actor::send_closure(SelfId, &BlockArchiver::got_block_data, R.move_as_ok());
});
td::actor::send_closure(file_db_, &FileDb::load_file, FileDb::RefId{fileref::Block{block_id_}}, std::move(P));
if (handle_->moved_to_storage()) {
td::actor::send_closure(archive_db_, &FileDb::load_file, FileDb::RefId{fileref::Block{block_id_}}, std::move(P));
} else {
td::actor::send_closure(file_db_, &FileDb::load_file, FileDb::RefId{fileref::Block{block_id_}}, std::move(P));
}
}
void BlockArchiver::got_block_data(td::BufferSlice data) {
auto P = td::PromiseCreator::lambda([SelfId = actor_id(this)](td::Result<FileHash> R) {
auto P = td::PromiseCreator::lambda([SelfId = actor_id(this)](td::Result<td::Unit> R) {
R.ensure();
td::actor::send_closure(SelfId, &BlockArchiver::written_block_data);
});
td::actor::send_closure(archive_db_, &FileDb::store_file, FileDb::RefId{fileref::Block{block_id_}}, std::move(data),
std::move(P));
td::actor::send_closure(archive_, &ArchiveManager::write, handle_->unix_time(), handle_->is_key_block(),
FileDb::RefId{fileref::Block{block_id_}}, std::move(data), std::move(P));
}
void BlockArchiver::written_block_data() {
handle_->set_moved_to_storage();
handle_->set_moved_to_archive();
auto P = td::PromiseCreator::lambda([SelfId = actor_id(this)](td::Result<td::Unit> R) {
R.ensure();

View file

@ -22,6 +22,7 @@
#include "td/actor/actor.h"
#include "validator/interfaces/block-handle.h"
#include "ton/ton-io.hpp"
#include "archive-db.hpp"
namespace ton {
@ -33,7 +34,8 @@ class FileDb;
class BlockArchiver : public td::actor::Actor {
public:
BlockArchiver(BlockIdExt block_id, td::actor::ActorId<RootDb> root_db, td::actor::ActorId<FileDb> file_db,
td::actor::ActorId<FileDb> archive_db, td::Promise<td::Unit> promise);
td::actor::ActorId<FileDb> archive_db, td::actor::ActorId<ArchiveManager> archive,
td::Promise<td::Unit> promise);
void abort_query(td::Status error);
@ -52,6 +54,7 @@ class BlockArchiver : public td::actor::Actor {
td::actor::ActorId<RootDb> root_db_;
td::actor::ActorId<FileDb> file_db_;
td::actor::ActorId<FileDb> archive_db_;
td::actor::ActorId<ArchiveManager> archive_;
td::Promise<td::Unit> promise_;
BlockHandle handle_;

View file

@ -331,6 +331,9 @@ FileDb::RefId FileDb::get_ref_from_tl(const ton_api::db_filedb_Key& from) {
[&](const ton_api::db_filedb_key_candidate& key) {
ref_id = fileref::Candidate{PublicKey{key.id_->source_}, create_block_id(key.id_->id_),
key.id_->collated_data_file_hash_};
},
[&](const ton_api::db_filedb_key_blockInfo& key) {
ref_id = fileref::BlockInfo{create_block_id(key.block_id_)};
}));
return ref_id;
}

View file

@ -130,6 +130,18 @@ class Candidate {
BlockIdExt block_id;
FileHash collated_data_file_hash;
};
class BlockInfo {
public:
tl_object_ptr<ton_api::db_filedb_Key> tl() const {
return create_tl_object<ton_api::db_filedb_key_blockInfo>(create_tl_block_id(block_id));
}
FileHash hash() const {
return create_hash_tl_object<ton_api::db_filedb_key_blockInfo>(create_tl_block_id(block_id));
}
BlockIdExt block_id;
};
}; // namespace fileref
class RootDb;
@ -138,7 +150,7 @@ class FileDb : public td::actor::Actor {
public:
using RefId =
td::Variant<fileref::Empty, fileref::Block, fileref::ZeroState, fileref::PersistentState, fileref::Proof,
fileref::Proof, fileref::ProofLink, fileref::Signatures, fileref::Candidate>;
fileref::Proof, fileref::ProofLink, fileref::Signatures, fileref::Candidate, fileref::BlockInfo>;
using RefIdHash = td::Bits256;
void store_file(RefId ref_id, td::BufferSlice data, td::Promise<FileHash> promise);

View file

@ -52,7 +52,7 @@ class WriteFile : public td::actor::Actor {
auto res = R.move_as_ok();
auto file = std::move(res.first);
auto old_name = res.second;
size_t offset = 0;
td::uint64 offset = 0;
while (data_.size() > 0) {
auto R = file.pwrite(data_.as_slice(), offset);
auto s = R.move_as_ok();

143
validator/db/package.cpp Normal file
View file

@ -0,0 +1,143 @@
#include "package.hpp"
#include "common/errorcode.h"
namespace ton {
namespace {
constexpr td::uint32 header_size() {
return 4;
}
constexpr td::uint32 max_data_size() {
return (1u << 31) - 1;
}
constexpr td::uint32 max_filename_size() {
return (1u << 16) - 1;
}
constexpr td::uint16 entry_header_magic() {
return 0x1e8b;
}
constexpr td::uint32 package_header_magic() {
return 0xae8fdd01;
}
} // namespace
Package::Package(td::FileFd fd) : fd_(std::move(fd)) {
}
td::Status Package::truncate(td::uint64 size) {
TRY_STATUS(fd_.seek(size + header_size()));
return fd_.truncate_to_current_position(size + header_size());
}
td::uint64 Package::append(std::string filename, td::Slice data) {
CHECK(data.size() <= max_data_size());
CHECK(filename.size() <= max_filename_size());
auto size = fd_.get_size().move_as_ok();
auto orig_size = size;
td::uint32 header[2];
header[0] = entry_header_magic() + (td::narrow_cast<td::uint32>(filename.size()) << 16);
header[1] = td::narrow_cast<td::uint32>(data.size());
CHECK(fd_.pwrite(td::Slice(reinterpret_cast<const td::uint8*>(header), 8), size).move_as_ok() == 8);
size += 8;
CHECK(fd_.pwrite(filename, size).move_as_ok() == filename.size());
size += filename.size();
CHECK(fd_.pwrite(data, size).move_as_ok() == data.size());
size += data.size();
fd_.sync().ensure();
return orig_size - header_size();
}
td::uint64 Package::size() const {
return fd_.get_size().move_as_ok() - header_size();
}
td::Result<std::pair<std::string, td::BufferSlice>> Package::read(td::uint64 offset) const {
offset += header_size();
td::uint32 header[2];
TRY_RESULT(s1, fd_.pread(td::MutableSlice(reinterpret_cast<td::uint8*>(header), 8), offset));
if (s1 != 8) {
return td::Status::Error(ErrorCode::notready, "too short read");
}
if ((header[0] & 0xffff) != entry_header_magic()) {
return td::Status::Error(ErrorCode::notready, "bad entry magic");
}
offset += 8;
auto fname_size = header[0] >> 16;
auto data_size = header[1];
std::string fname(fname_size, '\0');
TRY_RESULT(s2, fd_.pread(fname, offset));
if (s2 != fname_size) {
return td::Status::Error(ErrorCode::notready, "too short read (filename)");
}
offset += fname_size;
td::BufferSlice data{data_size};
TRY_RESULT(s3, fd_.pread(data.as_slice(), offset));
if (s3 != data_size) {
return td::Status::Error(ErrorCode::notready, "too short read (data)");
}
return std::pair<std::string, td::BufferSlice>{std::move(fname), std::move(data)};
}
td::Result<td::uint64> Package::advance(td::uint64 offset) {
offset += header_size();
td::uint32 header[2];
TRY_RESULT(s1, fd_.pread(td::MutableSlice(reinterpret_cast<td::uint8*>(header), 8), offset));
if (s1 != 8) {
return td::Status::Error(ErrorCode::notready, "too short read");
}
if ((header[0] & 0xffff) != entry_header_magic()) {
return td::Status::Error(ErrorCode::notready, "bad entry magic");
}
offset += 8 + (header[0] >> 16) + header[1];
if (offset > static_cast<td::uint64>(fd_.get_size().move_as_ok())) {
return td::Status::Error(ErrorCode::notready, "truncated read");
}
return offset - header_size();
}
td::Result<Package> Package::open(std::string path, bool read_only, bool create) {
td::uint32 flags = td::FileFd::Flags::Read;
if (!read_only) {
flags |= td::FileFd::Write;
}
if (create) {
flags |= td::FileFd::Create;
}
TRY_RESULT(fd, td::FileFd::open(path, flags));
TRY_RESULT(size, fd.get_size());
if (size < header_size()) {
if (!create) {
return td::Status::Error(ErrorCode::notready, "db is too short");
}
td::uint32 header[1];
header[0] = package_header_magic();
TRY_RESULT(s, fd.pwrite(td::Slice(reinterpret_cast<const td::uint8*>(header), header_size()), size));
if (s != header_size()) {
return td::Status::Error(ErrorCode::notready, "db write is short");
}
} else {
td::uint32 header[1];
TRY_RESULT(s, fd.pread(td::MutableSlice(reinterpret_cast<td::uint8*>(header), header_size()), 0));
if (s != header_size()) {
return td::Status::Error(ErrorCode::notready, "db read failed");
}
if (header[0] != package_header_magic()) {
return td::Status::Error(ErrorCode::notready, "magic mismatch");
}
}
return Package{std::move(fd)};
}
} // namespace ton

40
validator/db/package.hpp Normal file
View file

@ -0,0 +1,40 @@
#pragma once
#include "td/actor/actor.h"
#include "td/utils/port/FileFd.h"
#include "td/utils/buffer.h"
namespace ton {
class Package {
public:
static td::Result<Package> open(std::string path, bool read_only = false, bool create = false);
Package(td::FileFd fd);
td::Status truncate(td::uint64 size);
td::uint64 append(std::string filename, td::Slice data);
td::uint64 size() const;
td::Result<std::pair<std::string, td::BufferSlice>> read(td::uint64 offset) const;
td::Result<td::uint64> advance(td::uint64 offset);
struct Iterator {
td::uint64 offset;
Package &package;
Iterator operator++(int);
const Iterator operator++(int) const;
td::Result<std::pair<std::string, td::BufferSlice>> read() const;
};
Iterator begin();
const Iterator begin() const;
Iterator end();
const Iterator end() const;
private:
td::FileFd fd_;
};
} // namespace ton

View file

@ -32,7 +32,7 @@ namespace ton {
namespace validator {
void RootDb::store_block_data(BlockHandle handle, td::Ref<BlockData> block, td::Promise<td::Unit> promise) {
if (handle->moved_to_storage()) {
if (handle->moved_to_storage() || handle->moved_to_archive()) {
promise.set_value(td::Unit());
return;
}
@ -64,14 +64,19 @@ void RootDb::get_block_data(BlockHandle handle, td::Promise<td::Ref<BlockData>>
}
});
td::actor::send_closure(handle->moved_to_storage() ? archive_db_.get() : file_db_.get(), &FileDb::load_file,
FileDb::RefId{fileref::Block{handle->id()}}, std::move(P));
if (handle->moved_to_archive()) {
td::actor::send_closure(new_archive_db_, &ArchiveManager::read, handle->unix_time(), handle->is_key_block(),
FileDb::RefId{fileref::Block{handle->id()}}, std::move(P));
} else {
td::actor::send_closure(handle->moved_to_storage() ? old_archive_db_.get() : file_db_.get(), &FileDb::load_file,
FileDb::RefId{fileref::Block{handle->id()}}, std::move(P));
}
}
}
void RootDb::store_block_signatures(BlockHandle handle, td::Ref<BlockSignatureSet> data,
td::Promise<td::Unit> promise) {
if (handle->moved_to_storage()) {
if (handle->moved_to_storage() || handle->moved_to_archive()) {
promise.set_value(td::Unit());
return;
}
@ -94,7 +99,7 @@ void RootDb::get_block_signatures(BlockHandle handle, td::Promise<td::Ref<BlockS
if (!handle->inited_signatures()) {
promise.set_error(td::Status::Error(ErrorCode::notready, "not in db"));
} else {
if (handle->moved_to_storage()) {
if (handle->moved_to_storage() || handle->moved_to_archive()) {
promise.set_error(td::Status::Error(ErrorCode::error, "signatures already gc'd"));
return;
}
@ -111,7 +116,7 @@ void RootDb::get_block_signatures(BlockHandle handle, td::Promise<td::Ref<BlockS
}
void RootDb::store_block_proof(BlockHandle handle, td::Ref<Proof> proof, td::Promise<td::Unit> promise) {
if (handle->moved_to_storage()) {
if (handle->moved_to_storage() || handle->moved_to_archive()) {
promise.set_value(td::Unit());
return;
}
@ -142,13 +147,18 @@ void RootDb::get_block_proof(BlockHandle handle, td::Promise<td::Ref<Proof>> pro
promise.set_result(create_proof(id, R.move_as_ok()));
}
});
td::actor::send_closure(handle->moved_to_storage() ? archive_db_.get() : file_db_.get(), &FileDb::load_file,
FileDb::RefId{fileref::Proof{handle->id()}}, std::move(P));
if (handle->moved_to_archive()) {
td::actor::send_closure(new_archive_db_, &ArchiveManager::read, handle->unix_time(), handle->is_key_block(),
FileDb::RefId{fileref::Proof{handle->id()}}, std::move(P));
} else {
td::actor::send_closure(handle->moved_to_storage() ? old_archive_db_.get() : file_db_.get(), &FileDb::load_file,
FileDb::RefId{fileref::Proof{handle->id()}}, std::move(P));
}
}
}
void RootDb::store_block_proof_link(BlockHandle handle, td::Ref<ProofLink> proof, td::Promise<td::Unit> promise) {
if (handle->moved_to_storage()) {
if (handle->moved_to_storage() || handle->moved_to_archive()) {
promise.set_value(td::Unit());
return;
}
@ -179,8 +189,13 @@ void RootDb::get_block_proof_link(BlockHandle handle, td::Promise<td::Ref<ProofL
promise.set_result(create_proof_link(id, R.move_as_ok()));
}
});
td::actor::send_closure(handle->moved_to_storage() ? archive_db_.get() : file_db_.get(), &FileDb::load_file,
FileDb::RefId{fileref::ProofLink{handle->id()}}, std::move(P));
if (handle->moved_to_archive()) {
td::actor::send_closure(new_archive_db_, &ArchiveManager::read, handle->unix_time(), handle->is_key_block(),
FileDb::RefId{fileref::ProofLink{handle->id()}}, std::move(P));
} else {
td::actor::send_closure(handle->moved_to_storage() ? old_archive_db_.get() : file_db_.get(), &FileDb::load_file,
FileDb::RefId{fileref::ProofLink{handle->id()}}, std::move(P));
}
}
}
@ -225,7 +240,7 @@ void RootDb::get_block_candidate(PublicKey source, BlockIdExt id, FileHash colla
void RootDb::store_block_state(BlockHandle handle, td::Ref<ShardState> state,
td::Promise<td::Ref<ShardState>> promise) {
if (handle->moved_to_storage()) {
if (handle->moved_to_storage() || handle->moved_to_archive()) {
promise.set_value(std::move(state));
return;
}
@ -290,27 +305,27 @@ void RootDb::store_persistent_state_file(BlockIdExt block_id, BlockIdExt masterc
}
});
td::actor::send_closure(archive_db_, &FileDb::store_file,
td::actor::send_closure(old_archive_db_, &FileDb::store_file,
FileDb::RefId{fileref::PersistentState{block_id, masterchain_block_id}}, std::move(state),
std::move(P));
}
void RootDb::get_persistent_state_file(BlockIdExt block_id, BlockIdExt masterchain_block_id,
td::Promise<td::BufferSlice> promise) {
td::actor::send_closure(archive_db_, &FileDb::load_file,
td::actor::send_closure(old_archive_db_, &FileDb::load_file,
FileDb::RefId{fileref::PersistentState{block_id, masterchain_block_id}}, std::move(promise));
}
void RootDb::get_persistent_state_file_slice(BlockIdExt block_id, BlockIdExt masterchain_block_id, td::int64 offset,
td::int64 max_size, td::Promise<td::BufferSlice> promise) {
td::actor::send_closure(archive_db_, &FileDb::load_file_slice,
td::actor::send_closure(old_archive_db_, &FileDb::load_file_slice,
FileDb::RefId{fileref::PersistentState{block_id, masterchain_block_id}}, offset, max_size,
std::move(promise));
}
void RootDb::check_persistent_state_file_exists(BlockIdExt block_id, BlockIdExt masterchain_block_id,
td::Promise<bool> promise) {
td::actor::send_closure(archive_db_, &FileDb::check_file,
td::actor::send_closure(old_archive_db_, &FileDb::check_file,
FileDb::RefId{fileref::PersistentState{block_id, masterchain_block_id}}, std::move(promise));
}
@ -325,26 +340,38 @@ void RootDb::store_zero_state_file(BlockIdExt block_id, td::BufferSlice state, t
}
});
td::actor::send_closure(archive_db_, &FileDb::store_file, FileDb::RefId{fileref::ZeroState{block_id}},
td::actor::send_closure(old_archive_db_, &FileDb::store_file, FileDb::RefId{fileref::ZeroState{block_id}},
std::move(state), std::move(P));
}
void RootDb::get_zero_state_file(BlockIdExt block_id, td::Promise<td::BufferSlice> promise) {
td::actor::send_closure(archive_db_, &FileDb::load_file, FileDb::RefId{fileref::ZeroState{block_id}},
td::actor::send_closure(old_archive_db_, &FileDb::load_file, FileDb::RefId{fileref::ZeroState{block_id}},
std::move(promise));
}
void RootDb::check_zero_state_file_exists(BlockIdExt block_id, td::Promise<bool> promise) {
td::actor::send_closure(archive_db_, &FileDb::check_file, FileDb::RefId{fileref::ZeroState{block_id}},
td::actor::send_closure(old_archive_db_, &FileDb::check_file, FileDb::RefId{fileref::ZeroState{block_id}},
std::move(promise));
}
void RootDb::store_block_handle(BlockHandle handle, td::Promise<td::Unit> promise) {
td::actor::send_closure(block_db_, &BlockDb::store_block_handle, std::move(handle), std::move(promise));
if (handle->moved_to_archive()) {
td::actor::send_closure(new_archive_db_, &ArchiveManager::write_handle, std::move(handle), std::move(promise));
} else {
td::actor::send_closure(block_db_, &BlockDb::store_block_handle, std::move(handle), std::move(promise));
}
}
void RootDb::get_block_handle(BlockIdExt id, td::Promise<BlockHandle> promise) {
td::actor::send_closure(block_db_, &BlockDb::get_block_handle, id, std::move(promise));
auto P = td::PromiseCreator::lambda(
[db = block_db_.get(), id, promise = std::move(promise)](td::Result<BlockHandle> R) mutable {
if (R.is_error()) {
td::actor::send_closure(db, &BlockDb::get_block_handle, id, std::move(promise));
} else {
promise.set_value(R.move_as_ok());
}
});
td::actor::send_closure(new_archive_db_, &ArchiveManager::read_handle, id, std::move(P));
}
void RootDb::try_get_static_file(FileHash file_hash, td::Promise<td::BufferSlice> promise) {
@ -426,16 +453,17 @@ void RootDb::start_up() {
cell_db_ = td::actor::create_actor<CellDb>("celldb", actor_id(this), root_path_ + "/celldb/");
block_db_ = td::actor::create_actor<BlockDb>("blockdb", actor_id(this), root_path_ + "/blockdb/");
file_db_ = td::actor::create_actor<FileDb>("filedb", actor_id(this), root_path_ + "/files/", depth_, false);
archive_db_ =
old_archive_db_ =
td::actor::create_actor<FileDb>("filedbarchive", actor_id(this), root_path_ + "/archive/", depth_, true);
lt_db_ = td::actor::create_actor<LtDb>("ltdb", actor_id(this), root_path_ + "/ltdb/");
state_db_ = td::actor::create_actor<StateDb>("statedb", actor_id(this), root_path_ + "/state/");
static_files_db_ = td::actor::create_actor<StaticFilesDb>("staticfilesdb", actor_id(this), root_path_ + "/static/");
new_archive_db_ = td::actor::create_actor<ArchiveManager>("archivemanager", root_path_ + "/archive/");
}
void RootDb::archive(BlockIdExt block_id, td::Promise<td::Unit> promise) {
td::actor::create_actor<BlockArchiver>("archiveblock", block_id, actor_id(this), file_db_.get(), archive_db_.get(),
std::move(promise))
td::actor::create_actor<BlockArchiver>("archiveblock", block_id, actor_id(this), file_db_.get(),
old_archive_db_.get(), new_archive_db_.get(), std::move(promise))
.release();
}
@ -480,14 +508,15 @@ void RootDb::allow_gc(FileDb::RefId ref_id, bool is_archive, td::Promise<bool> p
CHECK(!is_archive);
td::actor::send_closure(validator_manager_, &ValidatorManager::allow_block_candidate_gc,
key.block_id, std::move(promise));
}));
},
[&](const fileref::BlockInfo &key) { UNREACHABLE(); }));
}
void RootDb::prepare_stats(td::Promise<std::vector<std::pair<std::string, std::string>>> promise) {
auto merger = StatsMerger::create(std::move(promise));
td::actor::send_closure(file_db_, &FileDb::prepare_stats, merger.make_promise("filedb."));
td::actor::send_closure(archive_db_, &FileDb::prepare_stats, merger.make_promise("archivedb."));
td::actor::send_closure(old_archive_db_, &FileDb::prepare_stats, merger.make_promise("archivedb."));
}
void RootDb::truncate(td::Ref<MasterchainState> state, td::Promise<td::Unit> promise) {

View file

@ -28,6 +28,7 @@
#include "ltdb.hpp"
#include "statedb.hpp"
#include "staticfilesdb.hpp"
#include "archive-db.hpp"
namespace ton {
@ -126,10 +127,11 @@ class RootDb : public Db {
td::actor::ActorOwn<CellDb> cell_db_;
td::actor::ActorOwn<BlockDb> block_db_;
td::actor::ActorOwn<FileDb> file_db_;
td::actor::ActorOwn<FileDb> archive_db_;
td::actor::ActorOwn<FileDb> old_archive_db_;
td::actor::ActorOwn<LtDb> lt_db_;
td::actor::ActorOwn<StateDb> state_db_;
td::actor::ActorOwn<StaticFilesDb> static_files_db_;
td::actor::ActorOwn<ArchiveManager> new_archive_db_;
};
} // namespace validator

Some files were not shown because too many files have changed in this diff Show more