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