mirror of
https://github.com/ton-blockchain/ton
synced 2025-02-12 11:12:16 +00:00
updated submodules, bugfixes
- added new fift/func code for validator complaint creation - bugfixes in validator - updates in tonlib - new versions of rocksdb/abseil - hardfork support
This commit is contained in:
parent
16a4566091
commit
9f008b129f
129 changed files with 8438 additions and 879 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -7,7 +7,6 @@ tl/generate/auto/
|
|||
compile_commands.json
|
||||
crypto/block/block-auto.cpp
|
||||
crypto/block/block-auto.h
|
||||
crypto/smartcont/*-code.fif
|
||||
crypto/smartcont/auto/
|
||||
test/regression-tests.cache/
|
||||
*.swp
|
||||
|
|
|
@ -115,6 +115,7 @@ if (TON_USE_ROCKSDB)
|
|||
if (ANDROID)
|
||||
set(PORTABLE ON CACHE BOOL "portable")
|
||||
endif()
|
||||
set(WITH_GFLAGS OFF CACHE BOOL "build with GFlags")
|
||||
set(WITH_TESTS OFF CACHE BOOL "build with tests")
|
||||
set(WITH_TOOLS OFF CACHE BOOL "build with tools")
|
||||
set(FAIL_ON_WARNINGS OFF CACHE BOOL "fail on warnings")
|
||||
|
@ -231,7 +232,7 @@ elseif (CLANG OR GCC)
|
|||
if (APPLE)
|
||||
#use "-Wl,-exported_symbols_list,${CMAKE_CURRENT_SOURCE_DIR}/export_list" for exported symbols
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fvisibility=hidden -Wl,-dead_strip,-x,-S")
|
||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fvisibility=hidden -Wl,-dead_strip,-x,-S")
|
||||
#set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fvisibility=hidden -Wl,-dead_strip,-x,-S")
|
||||
else()
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -ffunction-sections -fdata-sections")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--gc-sections -Wl,--exclude-libs,ALL")
|
||||
|
@ -385,6 +386,7 @@ add_subdirectory(validator)
|
|||
add_subdirectory(blockchain-explorer)
|
||||
add_subdirectory(validator-engine)
|
||||
add_subdirectory(validator-engine-console)
|
||||
add_subdirectory(create-hardfork)
|
||||
add_subdirectory(dht-server)
|
||||
add_subdirectory(utils)
|
||||
add_subdirectory(http)
|
||||
|
@ -445,6 +447,9 @@ if (NOT TON_ONLY_TONLIB)
|
|||
add_executable(test-db test/test-td-main.cpp ${TONDB_TEST_SOURCE})
|
||||
target_link_libraries(test-db PRIVATE ton_db memprof)
|
||||
|
||||
add_executable(test-rocksdb test/test-rocksdb.cpp)
|
||||
target_link_libraries(test-rocksdb PRIVATE memprof tddb tdutils)
|
||||
|
||||
add_executable(test-tddb test/test-td-main.cpp ${TDDB_TEST_SOURCE})
|
||||
target_link_libraries(test-tddb PRIVATE tdutils tddb ${CMAKE_THREAD_LIBS_INIT} memprof)
|
||||
|
||||
|
|
|
@ -1505,7 +1505,7 @@ void HttpQueryStatus::finish_query() {
|
|||
td::uint32 j = 0;
|
||||
for (auto &X : results_.results) {
|
||||
if (!X->values_[i].is_valid()) {
|
||||
A << "<td>FAIL</td>";
|
||||
A << "<td class=\"table-danger\">FAIL</td>";
|
||||
} else {
|
||||
if (m[j].count(X->values_[i].id.seqno) == 0) {
|
||||
m[j].insert(X->values_[i].id.seqno);
|
||||
|
|
|
@ -49,6 +49,7 @@ set(TON_CRYPTO_SOURCE
|
|||
common/bigexp.h
|
||||
common/util.h
|
||||
common/linalloc.hpp
|
||||
common/promiseop.hpp
|
||||
|
||||
ellcurve/Ed25519.h
|
||||
ellcurve/Fp25519.h
|
||||
|
@ -212,6 +213,7 @@ set(SMC_ENVELOPE_SOURCE
|
|||
smc-envelope/HighloadWalletV2.cpp
|
||||
smc-envelope/ManualDns.cpp
|
||||
smc-envelope/MultisigWallet.cpp
|
||||
smc-envelope/PaymentChannel.cpp
|
||||
smc-envelope/SmartContract.cpp
|
||||
smc-envelope/SmartContractCode.cpp
|
||||
smc-envelope/TestGiver.cpp
|
||||
|
@ -341,7 +343,7 @@ if (NOT CMAKE_CROSSCOMPILING)
|
|||
set(multiValueArgs SOURCE)
|
||||
set(FUNC_LIB_SOURCE smartcont/stdlib.fc)
|
||||
cmake_parse_arguments(ARG "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN} )
|
||||
string(REGEX REPLACE "[^a-zA-Z_]" "_" ID ${ARG_DEST})
|
||||
string(REGEX REPLACE "[^0-9a-zA-Z_]" "_" ID ${ARG_DEST})
|
||||
set(ARG_DEST_FIF "${ARG_DEST}.fif")
|
||||
add_custom_command(
|
||||
COMMENT "Generate ${ARG_DEST_FIF}"
|
||||
|
@ -374,10 +376,13 @@ if (NOT CMAKE_CROSSCOMPILING)
|
|||
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/restricted-wallet3-code SOURCE smartcont/restricted-wallet3-code.fc NAME restricted-wallet3)
|
||||
|
||||
GenFif(DEST smartcont/auto/dns-manual-code SOURCE smartcont/dns-manual-code.fc NAME dns-manual)
|
||||
GenFif(DEST smartcont/auto/dns-auto-code SOURCE smartcont/dns-auto-code.fc NAME dns-auto)
|
||||
|
||||
GenFif(DEST smartcont/auto/payment-channel-code SOURCE smartcont/payment-channel-code.fc NAME payment-channel)
|
||||
|
||||
GenFif(DEST smartcont/auto/simple-wallet-ext-code SOURCE smartcont/simple-wallet-ext-code.fc NAME simple-wallet-ext)
|
||||
endif()
|
||||
|
||||
|
@ -420,5 +425,13 @@ if (WINGETOPT_FOUND)
|
|||
target_link_libraries_system(dump-block wingetopt)
|
||||
endif()
|
||||
|
||||
add_executable(test-weight-distr block/test-weight-distr.cpp)
|
||||
target_include_directories(test-weight-distr PUBLIC $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/..>)
|
||||
target_link_libraries(test-weight-distr PUBLIC ton_crypto fift-lib ton_block)
|
||||
if (WINGETOPT_FOUND)
|
||||
target_link_libraries_system(test-weight-distr wingetopt)
|
||||
endif()
|
||||
|
||||
install(TARGETS fift func RUNTIME DESTINATION bin)
|
||||
install(DIRECTORY fift/lib/ DESTINATION lib/fift)
|
||||
|
|
|
@ -241,6 +241,14 @@ bool MsgAddressInt::extract_std_address(vm::CellSlice& cs, ton::WorkchainId& wor
|
|||
return false;
|
||||
}
|
||||
|
||||
bool MsgAddressInt::extract_std_address(Ref<vm::CellSlice> cs_ref, block::StdAddress& addr, bool rewrite) const {
|
||||
return extract_std_address(std::move(cs_ref), addr.workchain, addr.addr, rewrite);
|
||||
}
|
||||
|
||||
bool MsgAddressInt::extract_std_address(vm::CellSlice& cs, block::StdAddress& addr, bool rewrite) const {
|
||||
return extract_std_address(cs, addr.workchain, addr.addr, rewrite);
|
||||
}
|
||||
|
||||
bool MsgAddressInt::store_std_address(vm::CellBuilder& cb, ton::WorkchainId workchain,
|
||||
const ton::StdSmcAddress& addr) const {
|
||||
if (workchain >= -128 && workchain < 128) {
|
||||
|
@ -263,6 +271,14 @@ Ref<vm::CellSlice> MsgAddressInt::pack_std_address(ton::WorkchainId workchain, c
|
|||
}
|
||||
}
|
||||
|
||||
bool MsgAddressInt::store_std_address(vm::CellBuilder& cb, const block::StdAddress& addr) const {
|
||||
return store_std_address(cb, addr.workchain, addr.addr);
|
||||
}
|
||||
|
||||
Ref<vm::CellSlice> MsgAddressInt::pack_std_address(const block::StdAddress& addr) const {
|
||||
return pack_std_address(addr.workchain, addr.addr);
|
||||
}
|
||||
|
||||
const MsgAddressInt t_MsgAddressInt;
|
||||
|
||||
bool MsgAddress::validate_skip(int* ops, vm::CellSlice& cs, bool weak) const {
|
||||
|
|
|
@ -298,8 +298,13 @@ struct MsgAddressInt final : TLB_Complex {
|
|||
bool rewrite = true) const;
|
||||
bool extract_std_address(vm::CellSlice& cs, ton::WorkchainId& workchain, ton::StdSmcAddress& addr,
|
||||
bool rewrite = true) const;
|
||||
bool extract_std_address(Ref<vm::CellSlice> cs_ref, block::StdAddress& addr, bool rewrite = true) const;
|
||||
bool extract_std_address(vm::CellSlice& cs, block::StdAddress& addr, bool rewrite = true) const;
|
||||
bool store_std_address(vm::CellBuilder& cb, ton::WorkchainId workchain, const ton::StdSmcAddress& addr) const;
|
||||
Ref<vm::CellSlice> pack_std_address(ton::WorkchainId workchain, const ton::StdSmcAddress& addr) const;
|
||||
|
||||
bool store_std_address(vm::CellBuilder& cb, const block::StdAddress& addr) const;
|
||||
Ref<vm::CellSlice> pack_std_address(const block::StdAddress& addr) const;
|
||||
};
|
||||
|
||||
extern const MsgAddressInt t_MsgAddressInt;
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
#include "td/utils/crypto.h"
|
||||
#include "td/utils/tl_storers.h"
|
||||
#include "td/utils/misc.h"
|
||||
#include "td/utils/Random.h"
|
||||
|
||||
namespace block {
|
||||
using namespace std::literals::string_literals;
|
||||
|
@ -369,14 +370,14 @@ std::unique_ptr<MsgProcessedUptoCollection> MsgProcessedUptoCollection::unpack(t
|
|||
return v && v->valid ? std::move(v) : std::unique_ptr<MsgProcessedUptoCollection>{};
|
||||
}
|
||||
|
||||
bool MsgProcessedUpto::contains(const MsgProcessedUpto& other) const & {
|
||||
bool MsgProcessedUpto::contains(const MsgProcessedUpto& other) const& {
|
||||
return ton::shard_is_ancestor(shard, other.shard) && mc_seqno >= other.mc_seqno &&
|
||||
(last_inmsg_lt > other.last_inmsg_lt ||
|
||||
(last_inmsg_lt == other.last_inmsg_lt && !(last_inmsg_hash < other.last_inmsg_hash)));
|
||||
}
|
||||
|
||||
bool MsgProcessedUpto::contains(ton::ShardId other_shard, ton::LogicalTime other_lt, td::ConstBitPtr other_hash,
|
||||
ton::BlockSeqno other_mc_seqno) const & {
|
||||
ton::BlockSeqno other_mc_seqno) const& {
|
||||
return ton::shard_is_ancestor(shard, other_shard) && mc_seqno >= other_mc_seqno &&
|
||||
(last_inmsg_lt > other_lt || (last_inmsg_lt == other_lt && !(last_inmsg_hash < other_hash)));
|
||||
}
|
||||
|
@ -1546,6 +1547,89 @@ bool unpack_CreatorStats(Ref<vm::CellSlice> cs, DiscountedCounter& mc_cnt, Disco
|
|||
}
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
* Monte Carlo simulator for computing the share of shardchain blocks generated by each validator
|
||||
*
|
||||
*/
|
||||
|
||||
bool MtCarloComputeShare::compute() {
|
||||
ok = false;
|
||||
if (W.size() >= (1U << 31) || W.empty()) {
|
||||
return false;
|
||||
}
|
||||
K = std::min(K, N);
|
||||
if (K <= 0 || iterations <= 0) {
|
||||
return false;
|
||||
}
|
||||
double tot_weight = 0., acc = 0.;
|
||||
for (int i = 0; i < N; i++) {
|
||||
if (W[i] <= 0.) {
|
||||
return false;
|
||||
}
|
||||
tot_weight += W[i];
|
||||
}
|
||||
CW.resize(N);
|
||||
RW.resize(N);
|
||||
for (int i = 0; i < N; i++) {
|
||||
CW[i] = acc;
|
||||
acc += W[i] /= tot_weight;
|
||||
RW[i] = 0.;
|
||||
}
|
||||
R0 = 0.;
|
||||
H.resize(N);
|
||||
A.resize(K);
|
||||
for (long long it = 0; it < iterations; ++it) {
|
||||
gen_vset();
|
||||
}
|
||||
for (int i = 0; i < N; i++) {
|
||||
RW[i] = W[i] * (RW[i] + R0) / (double)iterations;
|
||||
}
|
||||
return ok = true;
|
||||
}
|
||||
|
||||
void MtCarloComputeShare::gen_vset() {
|
||||
double total_wt = 1.;
|
||||
int hc = 0;
|
||||
for (int i = 0; i < K; i++) {
|
||||
CHECK(total_wt > 0);
|
||||
double inv_wt = 1. / total_wt;
|
||||
R0 += inv_wt;
|
||||
for (int j = 0; j < i; j++) {
|
||||
RW[A[j]] -= inv_wt;
|
||||
}
|
||||
// double p = drand48() * total_wt;
|
||||
double p = (double)td::Random::fast_uint64() * total_wt / (1. * (1LL << 32) * (1LL << 32));
|
||||
for (int h = 0; h < hc; h++) {
|
||||
if (p < H[h].first) {
|
||||
break;
|
||||
}
|
||||
p += H[h].second;
|
||||
}
|
||||
int a = -1, b = N, c;
|
||||
while (b - a > 1) {
|
||||
c = ((a + b) >> 1);
|
||||
if (CW[c] <= p) {
|
||||
a = c;
|
||||
} else {
|
||||
b = c;
|
||||
}
|
||||
}
|
||||
CHECK(a >= 0 && a < N);
|
||||
CHECK(total_wt >= W[a]);
|
||||
total_wt -= W[a];
|
||||
double x = CW[a];
|
||||
c = hc++;
|
||||
while (c > 0 && H[c - 1].first > x) {
|
||||
H[c] = H[c - 1];
|
||||
--c;
|
||||
}
|
||||
H[c].first = x;
|
||||
H[c].second = W[a];
|
||||
A[i] = a;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
*
|
||||
* Other block-related functions
|
||||
|
@ -1723,7 +1807,7 @@ ton::AccountIdPrefixFull interpolate_addr(const ton::AccountIdPrefixFull& src, c
|
|||
unsigned long long mask = (std::numeric_limits<td::uint64>::max() >> (d - 32));
|
||||
return ton::AccountIdPrefixFull{dest.workchain, (dest.account_id_prefix & ~mask) | (src.account_id_prefix & mask)};
|
||||
} else {
|
||||
int mask = (-1 >> d);
|
||||
int mask = (int)(~0U >> d);
|
||||
return ton::AccountIdPrefixFull{(dest.workchain & ~mask) | (src.workchain & mask), src.account_id_prefix};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -163,12 +163,12 @@ struct MsgProcessedUpto {
|
|||
MsgProcessedUpto(ton::ShardId _shard, ton::BlockSeqno _mcseqno, ton::LogicalTime _lt, td::ConstBitPtr _hash)
|
||||
: shard(_shard), mc_seqno(_mcseqno), last_inmsg_lt(_lt), last_inmsg_hash(_hash) {
|
||||
}
|
||||
bool operator<(const MsgProcessedUpto& other) const & {
|
||||
bool operator<(const MsgProcessedUpto& other) const& {
|
||||
return shard < other.shard || (shard == other.shard && mc_seqno < other.mc_seqno);
|
||||
}
|
||||
bool contains(const MsgProcessedUpto& other) const &;
|
||||
bool contains(const MsgProcessedUpto& other) const&;
|
||||
bool contains(ton::ShardId other_shard, ton::LogicalTime other_lt, td::ConstBitPtr other_hash,
|
||||
ton::BlockSeqno other_mc_seqno) const &;
|
||||
ton::BlockSeqno other_mc_seqno) const&;
|
||||
// NB: this is for checking whether we have already imported an internal message
|
||||
bool already_processed(const EnqueuedMsgDescr& msg) const;
|
||||
bool can_check_processed() const {
|
||||
|
@ -596,6 +596,62 @@ struct BlockProofChain {
|
|||
td::Status validate(td::CancellationToken cancellation_token = {});
|
||||
};
|
||||
|
||||
// compute the share of shardchain blocks generated by each validator using Monte Carlo method
|
||||
class MtCarloComputeShare {
|
||||
int K, N;
|
||||
long long iterations;
|
||||
std::vector<double> W;
|
||||
std::vector<double> CW, RW;
|
||||
std::vector<std::pair<double, double>> H;
|
||||
std::vector<int> A;
|
||||
double R0;
|
||||
bool ok;
|
||||
|
||||
public:
|
||||
MtCarloComputeShare(int subset_size, const std::vector<double>& weights, long long iteration_count = 1000000)
|
||||
: K(subset_size), N((int)weights.size()), iterations(iteration_count), W(weights), ok(false) {
|
||||
compute();
|
||||
}
|
||||
MtCarloComputeShare(int subset_size, int set_size, const double* weights, long long iteration_count = 1000000)
|
||||
: K(subset_size), N(set_size), iterations(iteration_count), W(weights, weights + set_size), ok(false) {
|
||||
compute();
|
||||
}
|
||||
bool is_ok() const {
|
||||
return ok;
|
||||
}
|
||||
const double* share_array() const {
|
||||
return ok ? RW.data() : nullptr;
|
||||
}
|
||||
const double* weights_array() const {
|
||||
return ok ? W.data() : nullptr;
|
||||
}
|
||||
double operator[](int i) const {
|
||||
return ok ? RW.at(i) : -1.;
|
||||
}
|
||||
double share(int i) const {
|
||||
return ok ? RW.at(i) : -1.;
|
||||
}
|
||||
double weight(int i) const {
|
||||
return ok ? W.at(i) : -1.;
|
||||
}
|
||||
int size() const {
|
||||
return N;
|
||||
}
|
||||
int subset_size() const {
|
||||
return K;
|
||||
}
|
||||
long long performed_iterations() const {
|
||||
return iterations;
|
||||
}
|
||||
|
||||
private:
|
||||
bool set_error() {
|
||||
return ok = false;
|
||||
}
|
||||
bool compute();
|
||||
void gen_vset();
|
||||
};
|
||||
|
||||
int filter_out_msg_queue(vm::AugmentedDictionary& out_queue, ton::ShardIdFull old_shard, ton::ShardIdFull subshard);
|
||||
|
||||
std::ostream& operator<<(std::ostream& os, const ShardId& shard_id);
|
||||
|
|
|
@ -272,6 +272,7 @@ transaction$0111 account_addr:bits256 lt:uint64
|
|||
old:^X new:^X = MERKLE_UPDATE X;
|
||||
update_hashes#72 {X:Type} old_hash:bits256 new_hash:bits256
|
||||
= HASH_UPDATE X;
|
||||
!merkle_proof#03 {X:Type} virtual_hash:bits256 depth:uint16 virtual_root:^X = MERKLE_PROOF X;
|
||||
|
||||
acc_trans#5 account_addr:bits256
|
||||
transactions:(HashmapAug 64 ^Transaction CurrencyCollection)
|
||||
|
@ -731,8 +732,11 @@ top_block_descr_set#4ac789f3 collection:(HashmapE 96 ^TopBlockDescr) = TopBlockD
|
|||
//
|
||||
// VALIDATOR MISBEHAVIOR COMPLAINTS
|
||||
//
|
||||
no_blk_gen mc_blk_ref:ExtBlkRef from_utime:uint32 to_utime:uint32 state_proof:^Cell prod_proof:^Cell = ComplaintDescr;
|
||||
validator_complaint#bc validator_pubkey:uint256 description:^ComplaintDescr created_at:uint32 severity:uint8 reward_addr:uint256 paid:Grams suggested_fine:Grams suggested_fine_part:uint32 = ValidatorComplaint;
|
||||
prod_info#34 utime:uint32 mc_blk_ref:ExtBlkRef state_proof:^(MERKLE_PROOF Block)
|
||||
prod_proof:^(MERKLE_PROOF ShardState) = ProducerInfo;
|
||||
no_blk_gen from_utime:uint32 prod_info:^ProducerInfo = ComplaintDescr;
|
||||
no_blk_gen_diff prod_info_old:^ProducerInfo prod_info_new:^ProducerInfo = ComplaintDescr;
|
||||
validator_complaint#bc validator_pubkey:bits256 description:^ComplaintDescr created_at:uint32 severity:uint8 reward_addr:uint256 paid:Grams suggested_fine:Grams suggested_fine_part:uint32 = ValidatorComplaint;
|
||||
complaint_status#2d complaint:^ValidatorComplaint voters:(HashmapE 16 True) vset_id:uint256 weight_remaining:int64 = ValidatorComplaintStatus;
|
||||
|
||||
//
|
||||
|
@ -808,3 +812,25 @@ cap_method_pubkey#71f4 = SmcCapability;
|
|||
cap_is_wallet#2177 = SmcCapability;
|
||||
cap_name#ff name:Text = SmcCapability;
|
||||
|
||||
//
|
||||
// PAYMENT CHANNELS
|
||||
//
|
||||
|
||||
chan_config$_ init_timeout:uint32 close_timeout:uint32 a_key:bits256 b_key:bits256
|
||||
a_addr:^MsgAddressInt b_addr:^MsgAddressInt channel_id:uint64 = ChanConfig;
|
||||
|
||||
chan_state_init$000 signed_A:Bool signed_B:Bool min_A:Grams min_B:Grams expire_at:uint32 A:Grams B:Grams = ChanState;
|
||||
chan_state_close$001 signed_A:Bool signed_B:Bool promise_A:Grams promise_B:Grams expire_at:uint32 A:Grams B:Grams = ChanState;
|
||||
chan_state_payout$010 A:Grams B:Grams = ChanState;
|
||||
|
||||
chan_promise$_ channel_id:uint64 promise_A:Grams promise_B:Grams = ChanPromise;
|
||||
chan_signed_promise#_ sig:(Maybe ^bits512) promise:ChanPromise = ChanSignedPromise;
|
||||
|
||||
chan_msg_init#27317822 inc_A:Grams inc_B:Grams min_A:Grams min_B:Grams channel_id:uint64 = ChanMsg;
|
||||
chan_msg_close#f28ae183 extra_A:Grams extra_B:Grams promise:ChanSignedPromise = ChanMsg;
|
||||
chan_msg_timeout#43278a28 = ChanMsg;
|
||||
|
||||
chan_signed_msg$_ sig_A:(Maybe ^bits512) sig_B:(Maybe ^bits512) msg:ChanMsg = ChanSignedMsg;
|
||||
|
||||
chan_data$_ config:^ChanConfig state:^ChanState = ChanData;
|
||||
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
namespace block {
|
||||
using namespace std::literals::string_literals;
|
||||
|
||||
td::Status check_block_header_proof(td::Ref<vm::Cell> root, ton::BlockIdExt blkid, ton::Bits256* store_shard_hash_to,
|
||||
td::Status check_block_header_proof(td::Ref<vm::Cell> root, ton::BlockIdExt blkid, ton::Bits256* store_state_hash_to,
|
||||
bool check_state_hash, td::uint32* save_utime, ton::LogicalTime* save_lt) {
|
||||
ton::RootHash vhash{root->get_hash().bits()};
|
||||
if (vhash != blkid.root_hash) {
|
||||
|
@ -53,7 +53,7 @@ td::Status check_block_header_proof(td::Ref<vm::Cell> root, ton::BlockIdExt blki
|
|||
if (save_lt) {
|
||||
*save_lt = info.end_lt;
|
||||
}
|
||||
if (store_shard_hash_to) {
|
||||
if (store_state_hash_to) {
|
||||
vm::CellSlice upd_cs{vm::NoVmSpec(), blk.state_update};
|
||||
if (!(upd_cs.is_special() && upd_cs.prefetch_long(8) == 4 // merkle update
|
||||
&& upd_cs.size_ext() == 0x20228)) {
|
||||
|
@ -61,11 +61,11 @@ td::Status check_block_header_proof(td::Ref<vm::Cell> root, ton::BlockIdExt blki
|
|||
}
|
||||
auto upd_hash = upd_cs.prefetch_ref(1)->get_hash(0);
|
||||
if (!check_state_hash) {
|
||||
*store_shard_hash_to = upd_hash.bits();
|
||||
} else if (store_shard_hash_to->compare(upd_hash.bits())) {
|
||||
*store_state_hash_to = upd_hash.bits();
|
||||
} else if (store_state_hash_to->compare(upd_hash.bits())) {
|
||||
return td::Status::Error(PSTRING() << "state hash mismatch in block header of " << blkid.to_str()
|
||||
<< " : header declares " << upd_hash.bits().to_hex(256) << " expected "
|
||||
<< store_shard_hash_to->to_hex());
|
||||
<< store_state_hash_to->to_hex());
|
||||
}
|
||||
}
|
||||
return td::Status::OK();
|
||||
|
|
|
@ -25,7 +25,7 @@ namespace block {
|
|||
using td::Ref;
|
||||
|
||||
td::Status check_block_header_proof(td::Ref<vm::Cell> root, ton::BlockIdExt blkid,
|
||||
ton::Bits256* store_shard_hash_to = nullptr, bool check_state_hash = false,
|
||||
ton::Bits256* store_state_hash_to = nullptr, bool check_state_hash = false,
|
||||
td::uint32* save_utime = nullptr, ton::LogicalTime* save_lt = nullptr);
|
||||
td::Status check_shard_proof(ton::BlockIdExt blk, ton::BlockIdExt shard_blk, td::Slice shard_proof);
|
||||
td::Status check_account_proof(td::Slice proof, ton::BlockIdExt shard_blk, const block::StdAddress& addr,
|
||||
|
|
|
@ -1785,6 +1785,31 @@ std::vector<ton::ValidatorDescr> ValidatorSet::export_validator_set() const {
|
|||
return l;
|
||||
}
|
||||
|
||||
std::map<ton::Bits256, int> ValidatorSet::compute_validator_map() const {
|
||||
std::map<ton::Bits256, int> res;
|
||||
for (int i = 0; i < (int)list.size(); i++) {
|
||||
res.emplace(list[i].pubkey.as_bits256(), i);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
std::vector<double> ValidatorSet::export_scaled_validator_weights() const {
|
||||
std::vector<double> res;
|
||||
for (const auto& node : list) {
|
||||
res.push_back((double)node.weight / (double)total_weight);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
int ValidatorSet::lookup_public_key(td::ConstBitPtr pubkey) const {
|
||||
for (int i = 0; i < (int)list.size(); i++) {
|
||||
if (list[i].pubkey.as_bits256() == pubkey) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::vector<ton::ValidatorDescr> Config::do_compute_validator_set(const block::CatchainValidatorsConfig& ccv_conf,
|
||||
ton::ShardIdFull shard,
|
||||
const block::ValidatorSet& vset, ton::UnixTime time,
|
||||
|
|
|
@ -71,6 +71,12 @@ struct ValidatorSet {
|
|||
}
|
||||
const ValidatorDescr& at_weight(td::uint64 weight_pos) const;
|
||||
std::vector<ton::ValidatorDescr> export_validator_set() const;
|
||||
std::map<ton::Bits256, int> compute_validator_map() const;
|
||||
std::vector<double> export_scaled_validator_weights() const;
|
||||
int lookup_public_key(td::ConstBitPtr pubkey) const;
|
||||
int lookup_public_key(const td::Bits256& pubkey) const {
|
||||
return lookup_public_key(pubkey.bits());
|
||||
}
|
||||
};
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
|
199
crypto/block/test-weight-distr.cpp
Normal file
199
crypto/block/test-weight-distr.cpp
Normal file
|
@ -0,0 +1,199 @@
|
|||
/*
|
||||
This file is part of TON Blockchain source code.
|
||||
|
||||
TON Blockchain is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
TON Blockchain is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with TON Blockchain. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
In addition, as a special exception, the copyright holders give permission
|
||||
to link the code of portions of this program with the OpenSSL library.
|
||||
You must obey the GNU General Public License in all respects for all
|
||||
of the code used other than OpenSSL. If you modify file(s) with this
|
||||
exception, you may extend this exception to your version of the file(s),
|
||||
but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
exception statement from your version. If you delete this exception statement
|
||||
from all source files in the program, then also delete it here.
|
||||
|
||||
Copyright 2020 Telegram Systems LLP
|
||||
*/
|
||||
#include <iostream>
|
||||
#include "td/utils/Random.h"
|
||||
#include "td/utils/misc.h"
|
||||
#include "block/block.h"
|
||||
#include <getopt.h>
|
||||
|
||||
const int MAX_N = 1000, MAX_K = 100, DEFAULT_K = 7;
|
||||
|
||||
int verbosity;
|
||||
int N, K = DEFAULT_K;
|
||||
long long iterations = 1000000;
|
||||
|
||||
td::uint64 TWL, WL[MAX_N];
|
||||
double W[MAX_N], CW[MAX_N + 1], RW[MAX_N], R0;
|
||||
int A[MAX_N], C[MAX_N];
|
||||
long long TC;
|
||||
|
||||
void gen_vset() {
|
||||
static std::pair<double, double> H[MAX_N];
|
||||
double total_wt = 1.;
|
||||
int hc = 0;
|
||||
for (int i = 0; i < K; i++) {
|
||||
CHECK(total_wt > 0);
|
||||
double inv_wt = 1. / total_wt;
|
||||
R0 += inv_wt; // advanced mtcarlo stats
|
||||
for (int j = 0; j < i; j++) {
|
||||
RW[A[j]] -= inv_wt; // advanced mtcarlo stats
|
||||
}
|
||||
// double p = drand48() * total_wt;
|
||||
double p = (double)td::Random::fast_uint64() * total_wt / (1. * (1LL << 32) * (1LL << 32));
|
||||
for (int h = 0; h < hc; h++) {
|
||||
if (p < H[h].first) {
|
||||
break;
|
||||
}
|
||||
p += H[h].second;
|
||||
}
|
||||
int a = -1, b = N, c;
|
||||
while (b - a > 1) {
|
||||
c = ((a + b) >> 1);
|
||||
if (CW[c] <= p) {
|
||||
a = c;
|
||||
} else {
|
||||
b = c;
|
||||
}
|
||||
}
|
||||
CHECK(a >= 0 && a < N);
|
||||
CHECK(total_wt >= W[a]);
|
||||
total_wt -= W[a];
|
||||
double x = CW[a];
|
||||
c = hc++;
|
||||
while (c > 0 && H[c - 1].first > x) {
|
||||
H[c] = H[c - 1];
|
||||
--c;
|
||||
}
|
||||
H[c].first = x;
|
||||
H[c].second = W[a];
|
||||
A[i] = a;
|
||||
C[a]++; // simple mtcarlo stats
|
||||
// std::cout << a << ' ';
|
||||
}
|
||||
// std::cout << std::endl;
|
||||
++TC; // simple mtcarlo stats
|
||||
}
|
||||
|
||||
void mt_carlo() {
|
||||
for (int i = 0; i < N; i++) {
|
||||
C[i] = 0;
|
||||
RW[i] = 0.;
|
||||
}
|
||||
TC = 0;
|
||||
R0 = 0.;
|
||||
std::cout << "running " << iterations << " steps of Monte Carlo simulation\n";
|
||||
for (long long it = 0; it < iterations; ++it) {
|
||||
gen_vset();
|
||||
}
|
||||
for (int i = 0; i < N; i++) {
|
||||
RW[i] = W[i] * (RW[i] + R0) / (double)iterations;
|
||||
}
|
||||
}
|
||||
|
||||
double B[MAX_N];
|
||||
|
||||
void compute_bad_approx() {
|
||||
static double S[MAX_K + 1];
|
||||
S[0] = 1.;
|
||||
for (int i = 1; i <= K; i++) {
|
||||
S[i] = 0.;
|
||||
}
|
||||
for (int i = 0; i < N; i++) {
|
||||
double p = W[i];
|
||||
for (int j = K; j > 0; j--) {
|
||||
S[j] += p * S[j - 1];
|
||||
}
|
||||
}
|
||||
double Sk = S[K];
|
||||
for (int i = 0; i < N; i++) {
|
||||
double t = 1., p = W[i];
|
||||
for (int j = 1; j <= K; j++) {
|
||||
t = S[j] - p * t;
|
||||
}
|
||||
B[i] = 1. - t / Sk;
|
||||
}
|
||||
}
|
||||
|
||||
void usage() {
|
||||
std::cout
|
||||
<< "usage: test-weight-distr [-k<shard-val-num>][-m<iterations>][-s<rand-seed>]\nReads the set of validator "
|
||||
"weights from stdin and emulates validator shard distribution load\n\t-k <shard-val-num>\tSets the number of "
|
||||
"validators generating each shard\n\t-m <iterations>\tMonte Carlo simulation steps\n";
|
||||
std::exit(2);
|
||||
}
|
||||
|
||||
int main(int argc, char* const argv[]) {
|
||||
int i;
|
||||
int new_verbosity_level = VERBOSITY_NAME(INFO);
|
||||
// long seed = 0;
|
||||
while ((i = getopt(argc, argv, "hs:k:m:v:")) != -1) {
|
||||
switch (i) {
|
||||
case 'k':
|
||||
K = td::to_integer<int>(td::Slice(optarg));
|
||||
CHECK(K > 0 && K <= 100);
|
||||
break;
|
||||
case 'm':
|
||||
iterations = td::to_integer<long long>(td::Slice(optarg));
|
||||
CHECK(iterations > 0);
|
||||
break;
|
||||
case 's':
|
||||
// seed = td::to_integer<long>(td::Slice(optarg));
|
||||
// srand48(seed);
|
||||
break;
|
||||
case 'v':
|
||||
new_verbosity_level = VERBOSITY_NAME(FATAL) + (verbosity = td::to_integer<int>(td::Slice(optarg)));
|
||||
break;
|
||||
case 'h':
|
||||
usage();
|
||||
std::exit(2);
|
||||
default:
|
||||
usage();
|
||||
std::exit(2);
|
||||
}
|
||||
}
|
||||
SET_VERBOSITY_LEVEL(new_verbosity_level);
|
||||
for (N = 0; N < MAX_N && (std::cin >> WL[N]); N++) {
|
||||
CHECK(WL[N] > 0);
|
||||
TWL += WL[N];
|
||||
}
|
||||
CHECK(std::cin.eof());
|
||||
CHECK(N > 0 && TWL > 0 && N <= MAX_N);
|
||||
K = std::min(K, N);
|
||||
CHECK(K > 0 && K <= MAX_K);
|
||||
double acc = 0.;
|
||||
for (i = 0; i < N; i++) {
|
||||
CW[i] = acc;
|
||||
acc += W[i] = (double)WL[i] / (double)TWL;
|
||||
std::cout << "#" << i << ":\t" << W[i] << std::endl;
|
||||
}
|
||||
compute_bad_approx();
|
||||
mt_carlo();
|
||||
std::cout << "result of Monte Carlo simulation (" << iterations << " iterations):" << std::endl;
|
||||
std::cout << "idx\tweight\tmtcarlo1\tmtcarlo2\tapprox\n";
|
||||
for (i = 0; i < N; i++) {
|
||||
std::cout << "#" << i << ":\t" << W[i] << '\t' << (double)C[i] / (double)iterations << '\t' << RW[i] << '\t' << B[i]
|
||||
<< std::endl;
|
||||
}
|
||||
// same computation, but using a MtCarloComputeShare object
|
||||
block::MtCarloComputeShare MT(K, N, W, iterations);
|
||||
std::cout << "-----------------------\n";
|
||||
for (i = 0; i < N; i++) {
|
||||
std::cout << '#' << i << ":\t" << MT.weight(i) << '\t' << MT.share(i) << std::endl;
|
||||
}
|
||||
return 0;
|
||||
}
|
|
@ -1429,6 +1429,9 @@ int Transaction::try_action_send_msg(const vm::CellSlice& cs0, ActionPhase& ap,
|
|||
if (!tlb::csr_unpack(msg.info, erec)) {
|
||||
return -1;
|
||||
}
|
||||
if (act_rec.mode & ~3) {
|
||||
return -1; // invalid mode for an external message
|
||||
}
|
||||
info.src = std::move(erec.src);
|
||||
info.dest = std::move(erec.dest);
|
||||
// created_lt and created_at are ignored
|
||||
|
|
|
@ -272,7 +272,7 @@ class AnyIntView {
|
|||
int parse_binary_any(const char* str, int str_len, int* frac = nullptr);
|
||||
std::string to_dec_string_destroy_any();
|
||||
std::string to_dec_string_slow_destroy_any();
|
||||
std::string to_hex_string_any(bool upcase = false) const;
|
||||
std::string to_hex_string_any(bool upcase = false, int zero_pad = 0) const;
|
||||
std::string to_hex_string_slow_destroy_any();
|
||||
std::string to_binary_string_any() const;
|
||||
|
||||
|
@ -650,7 +650,7 @@ class BigIntG {
|
|||
std::string to_dec_string_destroy();
|
||||
std::string to_dec_string_slow() const;
|
||||
std::string to_hex_string_slow() const;
|
||||
std::string to_hex_string(bool upcase = false) const;
|
||||
std::string to_hex_string(bool upcase = false, int zero_pad = 0) const;
|
||||
std::string to_binary_string() const;
|
||||
double to_double() const {
|
||||
return is_valid() ? ldexp(top_double(), (n - 1) * word_shift) : NAN;
|
||||
|
@ -2290,16 +2290,19 @@ std::string AnyIntView<Tr>::to_hex_string_slow_destroy_any() {
|
|||
}
|
||||
|
||||
template <class Tr>
|
||||
std::string AnyIntView<Tr>::to_hex_string_any(bool upcase) const {
|
||||
std::string AnyIntView<Tr>::to_hex_string_any(bool upcase, int zero_pad) const {
|
||||
if (!is_valid()) {
|
||||
return "NaN";
|
||||
}
|
||||
int s = sgn(), k = 0;
|
||||
if (!s) {
|
||||
if (zero_pad > 0) {
|
||||
return std::string(zero_pad, '0');
|
||||
}
|
||||
return "0";
|
||||
}
|
||||
std::string x;
|
||||
x.reserve(((size() * word_shift + word_bits) >> 2) + 2);
|
||||
x.reserve(2 + std::max((size() * word_shift + word_bits) >> 2, zero_pad));
|
||||
assert(word_shift < word_bits - 4);
|
||||
const char* hex_digs = (upcase ? HEX_digits : hex_digits);
|
||||
word_t v = 0;
|
||||
|
@ -2317,6 +2320,11 @@ std::string AnyIntView<Tr>::to_hex_string_any(bool upcase) const {
|
|||
x += hex_digs[v & 15];
|
||||
v >>= 4;
|
||||
}
|
||||
if (zero_pad > 0) {
|
||||
while (x.size() < (unsigned)zero_pad) {
|
||||
x += '0';
|
||||
}
|
||||
}
|
||||
if (s < 0) {
|
||||
x += '-';
|
||||
}
|
||||
|
@ -2498,8 +2506,8 @@ std::string BigIntG<len, Tr>::to_hex_string_slow() const {
|
|||
}
|
||||
|
||||
template <int len, class Tr>
|
||||
std::string BigIntG<len, Tr>::to_hex_string(bool upcase) const {
|
||||
return as_any_int().to_hex_string_any(upcase);
|
||||
std::string BigIntG<len, Tr>::to_hex_string(bool upcase, int zero_pad) const {
|
||||
return as_any_int().to_hex_string_any(upcase, zero_pad);
|
||||
}
|
||||
|
||||
template <int len, class Tr>
|
||||
|
|
72
crypto/common/promiseop.hpp
Normal file
72
crypto/common/promiseop.hpp
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 2020 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
#include "refcnt.hpp"
|
||||
#include "td/actor/PromiseFuture.h"
|
||||
|
||||
namespace td {
|
||||
|
||||
template <typename S, typename T>
|
||||
class BinaryPromiseMerger : public CntObject {
|
||||
Result<S> first_;
|
||||
Result<T> second_;
|
||||
Promise<std::pair<S, T>> promise_;
|
||||
std::atomic<int> pending_;
|
||||
|
||||
public:
|
||||
BinaryPromiseMerger(Promise<std::pair<S, T>> promise) : promise_(std::move(promise)), pending_(2) {
|
||||
}
|
||||
static std::pair<Promise<S>, Promise<T>> split(Promise<std::pair<S, T>> promise) {
|
||||
auto ref = make_ref<BinaryPromiseMerger>(std::move(promise));
|
||||
auto& obj = ref.write();
|
||||
return std::make_pair(obj.left(), obj.right());
|
||||
}
|
||||
|
||||
private:
|
||||
Promise<S> left() {
|
||||
return [this, self = Ref<BinaryPromiseMerger>(this)](Result<S> res) {
|
||||
first_ = std::move(res);
|
||||
work();
|
||||
};
|
||||
}
|
||||
Promise<T> right() {
|
||||
return [this, self = Ref<BinaryPromiseMerger>(this)](Result<T> res) {
|
||||
second_ = std::move(res);
|
||||
work();
|
||||
};
|
||||
}
|
||||
void work() {
|
||||
if (!--pending_) {
|
||||
if (first_.is_error()) {
|
||||
promise_.set_error(first_.move_as_error());
|
||||
} else if (second_.is_error()) {
|
||||
promise_.set_error(second_.move_as_error());
|
||||
} else {
|
||||
promise_.set_result(std::pair<S, T>(first_.move_as_ok(), second_.move_as_ok()));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <typename S, typename T>
|
||||
std::pair<Promise<S>, Promise<T>> split_promise(Promise<std::pair<S, T>> promise) {
|
||||
return BinaryPromiseMerger<S, T>::split(std::move(promise));
|
||||
}
|
||||
|
||||
} // namespace td
|
|
@ -287,8 +287,8 @@ std::string dec_string2(RefInt256&& x) {
|
|||
return x.is_null() ? "(null)" : (x.is_unique() ? x.unique_write().to_dec_string_destroy() : x->to_dec_string());
|
||||
}
|
||||
|
||||
std::string hex_string(RefInt256 x, bool upcase) {
|
||||
return x.is_null() ? "(null)" : x->to_hex_string(upcase);
|
||||
std::string hex_string(RefInt256 x, bool upcase, int zero_pad) {
|
||||
return x.is_null() ? "(null)" : x->to_hex_string(upcase, zero_pad);
|
||||
}
|
||||
|
||||
std::string binary_string(RefInt256 x) {
|
||||
|
|
|
@ -120,7 +120,7 @@ extern RefInt256 bits_to_refint(td::ConstBitPtr bits, int n, bool sgnd = false);
|
|||
|
||||
extern std::string dec_string(RefInt256 x);
|
||||
extern std::string dec_string2(RefInt256&& x);
|
||||
extern std::string hex_string(RefInt256 x, bool upcase = false);
|
||||
extern std::string hex_string(RefInt256 x, bool upcase = false, int zero_pad = 0);
|
||||
extern std::string binary_string(RefInt256 x);
|
||||
|
||||
extern RefInt256 dec_string_to_int256(const std::string& s);
|
||||
|
|
|
@ -36,6 +36,7 @@ library TonUtil // TON Blockchain Fift Library
|
|||
|
||||
// ( x -- ) Displays a 64-digit hex number
|
||||
{ 64 0x. } : 64x.
|
||||
{ 64 0X. } : 64X.
|
||||
// ( wc addr -- ) Show address in <workchain>:<account> form
|
||||
{ swap ._ .":" 64x. } : .addr
|
||||
// ( wc addr flags -- ) Show address in base64url form
|
||||
|
@ -205,6 +206,13 @@ recursive append-long-bytes {
|
|||
{ 256 u>B B{3ee6} swap B+ dup crc16 16 u>B B+ B>base64 } : pubkey>$
|
||||
{ pubkey>$ type } : .pubkey
|
||||
|
||||
// ( S -- x ) parse validator-encoded public key
|
||||
{ base64>B dup Blen 36 <> abort"public key with magic must be 36 bytes long"
|
||||
4 B| swap 32 B>u@ 0xC6B41348 <> abort"unknown magic for public key (not Ed25519)"
|
||||
} : parse-val-pubkey
|
||||
{ bl word parse-val-pubkey 1 'nop } ::_ VPK'
|
||||
{ char } word base64>B 1 'nop } ::_ B64{
|
||||
|
||||
// adnl address parser
|
||||
{ 256 u>B B{2D} swap B+ dup crc16 16 u>B B+ } : adnl-preconv
|
||||
{ swap 32 /mod dup 26 < { 65 } { 24 } cond + rot swap hold } : Base32#
|
||||
|
|
|
@ -62,15 +62,31 @@ variable validators-weight
|
|||
validators-weight 0!
|
||||
|
||||
{ validator-dict @ second } : validator#
|
||||
// val-pubkey weight --
|
||||
{ dup 0<= abort"validator weight must be non-negative"
|
||||
dup 64 ufits not abort"validator weight must fit into 64 bits"
|
||||
64 ufits not abort"validator weight must fit into 64 bits"
|
||||
} : check-val-weight
|
||||
// ( val-pubkey weight -- c )
|
||||
{ dup check-val-weight
|
||||
over Blen 32 <> abort"validator public key must be 32 bytes long"
|
||||
tuck <b x{538e81278a} s, rot B, swap 64 u, b> <s
|
||||
validator-dict 2@ dup 1+ 3 -roll swap
|
||||
<b x{538e81278a} s, rot B, swap 64 u, b>
|
||||
} : serialize-validator
|
||||
// ( val-pubkey adnl weight -- c )
|
||||
{ dup check-val-weight
|
||||
over 256 ufits not abort"adnl address must fit into 256 bits"
|
||||
rot dup Blen 32 <> abort"validator public key must be 32 bytes long"
|
||||
<b x{738e81278a} s, swap B, swap 64 u, swap 256 u, b>
|
||||
} : serialize-adnl-validator
|
||||
// ( weight val-cell -- )
|
||||
{ swap validators-weight +!
|
||||
<s validator-dict 2@ dup 1+ 3 -roll swap
|
||||
16 udict!+ 0= abort"cannot add validator"
|
||||
swap validator-dict 2! validators-weight +!
|
||||
} : add-validator
|
||||
swap validator-dict 2!
|
||||
} : register-validator
|
||||
// val-pubkey weight --
|
||||
{ tuck serialize-validator register-validator } : add-validator
|
||||
// val-pubkey adnl-addr weight --
|
||||
{ -rot 2 pick serialize-adnl-validator register-validator
|
||||
} : add-adnl-validator
|
||||
// since-ut until-ut main-val-cnt-or-0 --
|
||||
{ ?dup 0= { validator# } if
|
||||
validator# 0= abort"no initial validators defined"
|
||||
|
@ -230,21 +246,24 @@ variable special-dict
|
|||
} : create-wallet1
|
||||
|
||||
// D x t -- D'
|
||||
{ <b rot Gram, swap rot 16 b>idict! not abort"cannot add value"
|
||||
{ <b rot Gram, swap rot 32 b>idict! not abort"cannot add value"
|
||||
} : rdict-entry
|
||||
{ 86400 * } : days*
|
||||
// balance -- dict
|
||||
{ dictnew
|
||||
over -32768 rdict-entry
|
||||
over 3/4 */ 92 rdict-entry
|
||||
over 1/2 */ 183 rdict-entry
|
||||
swap 1/4 */ 366 rdict-entry
|
||||
0 548 rdict-entry
|
||||
over 31 -1<< rdict-entry
|
||||
over 3/4 */ 91 days* rdict-entry
|
||||
over 1/2 */ 183 days* rdict-entry
|
||||
swap 1/4 */ 365 days* rdict-entry
|
||||
0 548 days* rdict-entry
|
||||
} : make-rdict
|
||||
|
||||
variable wallet2-start-at wallet2-start-at 0!
|
||||
now 86400 / 1+ 86400 * wallet2-start-at !
|
||||
// pubkey amount --
|
||||
{ over ."Key " pubkey>$ type ." -> "
|
||||
RWCode2 // code
|
||||
<b 1 32 u, 3 pick 256 u, 3 roll make-rdict dict, b> // data
|
||||
<b 1 32 u, 3 pick 256 u, 3 roll wallet2-start-at @ 32 u, make-rdict dict, b> // data
|
||||
empty_cell // libs
|
||||
3 roll // balance
|
||||
0 // split_depth
|
||||
|
|
29
crypto/smartcont/complaint-vote-req.fif
Normal file
29
crypto/smartcont/complaint-vote-req.fif
Normal file
|
@ -0,0 +1,29 @@
|
|||
#!/usr/bin/fift -s
|
||||
"TonUtil.fif" include
|
||||
"GetOpt.fif" include
|
||||
|
||||
"validator-to-sign.req" =: savefile
|
||||
|
||||
{ ."usage: " @' $0 type ." <validator-idx> <elect-id> <complaint-hash> [<savefile>]" cr
|
||||
."Creates an unsigned request to vote for complaint <complaint-hash> (decimal; prefix with '0x' if needed) of past validator set <elect-id> on behalf of validator with zero-based index <validator-idx> in current validator set (as stored in configuration parameter 34)." cr
|
||||
."The result is saved into <savefile> (" savefile type ." by default) and output in hexadecimal form, to be signed later by the validator public key" cr 1 halt
|
||||
} : usage
|
||||
|
||||
$# dup 3 < swap 4 > or ' usage if
|
||||
4 :$1..n
|
||||
|
||||
$1 parse-int dup =: val-idx
|
||||
16 ufits not abort"validator index out of range"
|
||||
$2 parse-int dup =: elect-id
|
||||
32 ufits not abort"invalid election id"
|
||||
$3 parse-int dup =: compl-hash
|
||||
256 ufits not abort"invalid complaint hash"
|
||||
$4 savefile replace-if-null =: savefile
|
||||
|
||||
."Creating a request to vote for complaint 0x" compl-hash 64x. ."of past validator set " elect-id .
|
||||
."on behalf of current validator with index " val-idx . cr
|
||||
|
||||
B{56744350} val-idx 16 u>B B+ elect-id 32 u>B B+ compl-hash 256 u>B B+
|
||||
dup Bx. cr
|
||||
dup B>base64url type cr
|
||||
savefile tuck B>file ."Saved to file " type cr
|
44
crypto/smartcont/complaint-vote-signed.fif
Normal file
44
crypto/smartcont/complaint-vote-signed.fif
Normal file
|
@ -0,0 +1,44 @@
|
|||
#!/usr/bin/fift -s
|
||||
"TonUtil.fif" include
|
||||
"GetOpt.fif" include
|
||||
|
||||
"vote-query.boc" =: savefile
|
||||
|
||||
{ ."usage: " @' $0 type ." <validator-idx> <elect-id> <complaint-hash> <validator-pubkey> <validator-signature> [<savefile>]" cr
|
||||
."Creates an internal message body to be sent from any smart contract residing in the masterchain to the elections smart contract containing a signed request to vote for complaint <complaint-hash> (decimal; prefix with '0x' if needed) of past validator set <elect-id> on behalf of current validator with zero-based index <validator-idx> and (Base64) public key <validator-pubkey> in current validator set (as stored in configuration parameter 34)" cr
|
||||
."<validator-signature> must be the base64 representation of Ed25519 signature of the previously generated unsigned request by means of <validator-pubkey>" cr
|
||||
."The result is saved into <savefile> (" savefile type ." by default), to be embedded later into an internal message" cr 1 halt
|
||||
} : usage
|
||||
|
||||
$# dup 5 < swap 6 > or ' usage if
|
||||
6 :$1..n
|
||||
|
||||
$1 parse-int dup =: val-idx
|
||||
16 ufits not abort"validator index out of range"
|
||||
$2 parse-int dup =: elect-id
|
||||
32 ufits not abort"invalid election id"
|
||||
$3 parse-int dup =: compl-hash
|
||||
256 ufits not abort"invalid complaint hash"
|
||||
$4 base64>B dup Blen 36 <> abort"validator Ed25519 public key must be exactly 36 bytes long"
|
||||
32 B>u@+ 0xC6B41348 <> abort"invalid Ed25519 public key: unknown magic number"
|
||||
=: pubkey
|
||||
$5 base64>B dup Blen 64 <> abort"validator Ed25519 signature must be exactly 64 bytes long"
|
||||
=: signature
|
||||
$6 savefile replace-if-null =: savefile
|
||||
|
||||
."Creating the body of an internal message to be sent to the elections smart contract" cr }
|
||||
."containing a signed request to vote for complaint 0x" compl-hash 64x. ."of past validator set " elect-id .
|
||||
."on behalf of current validator with index " val-idx . "and public key" pubkey Bx. cr
|
||||
|
||||
B{56744350} val-idx 16 u>B B+ elect-id 32 u>B B+ compl-hash 256 u>B B+ dup =: to_sign
|
||||
."String to sign is " Bx. cr
|
||||
|
||||
to_sign signature pubkey ed25519_chksign not abort"Ed25519 signature is invalid"
|
||||
."Provided a valid Ed25519 signature " signature Bx. ." with validator public key " pubkey Bx. cr
|
||||
|
||||
now 32 << compl-hash 32 1<< mod + =: query-id
|
||||
|
||||
<b x{56744370} s, query-id 64 u, signature B, to_sign B, b>
|
||||
."Internal message body is " dup <s csr. cr
|
||||
|
||||
2 boc+>B savefile tuck B>file ."Saved to file " type cr
|
|
@ -1097,8 +1097,21 @@ tuple past_elections() method_id {
|
|||
do {
|
||||
(id, var fs, var found) = past_elections.udict_get_prev?(32, id);
|
||||
if (found) {
|
||||
var info = [unpack_past_election(fs)];
|
||||
list = cons(pair(id, info), list);
|
||||
list = cons([id, unpack_past_election(fs)], list);
|
||||
}
|
||||
} until (~ found);
|
||||
return list;
|
||||
}
|
||||
|
||||
tuple past_elections_list() method_id {
|
||||
var (elect, credits, past_elections, grams, active_id, active_hash) = load_data();
|
||||
var id = (1 << 32);
|
||||
var list = null();
|
||||
do {
|
||||
(id, var fs, var found) = past_elections.udict_get_prev?(32, id);
|
||||
if (found) {
|
||||
var (unfreeze_at, stake_held, vset_hash, frozen_dict, total_stake, bonuses, complaints) = unpack_past_election(fs);
|
||||
list = cons([id, unfreeze_at, vset_hash, stake_held], list);
|
||||
}
|
||||
} until (~ found);
|
||||
return list;
|
||||
|
@ -1117,7 +1130,7 @@ _ complete_unpack_complaint(slice cs) inline_ref {
|
|||
return [[complaint.begin_parse().unpack_complaint()], voters_list, vset_id, weight_remaining];
|
||||
}
|
||||
|
||||
cell get_past_complaints(int election_id) inline_ref {
|
||||
cell get_past_complaints(int election_id) inline_ref method_id {
|
||||
var (elect, credits, past_elections, grams, active_id, active_hash) = load_data();
|
||||
var (fs, found?) = past_elections.udict_get?(32, election_id);
|
||||
ifnot (found?) {
|
||||
|
@ -1145,3 +1158,11 @@ tuple list_complaints(int election_id) method_id {
|
|||
} until (~ found?);
|
||||
return list;
|
||||
}
|
||||
|
||||
int complaint_storage_price(int bits, int refs, int expire_in) method_id {
|
||||
;; compute complaint storage/creation price
|
||||
var (deposit, bit_price, cell_price) = get_complaint_prices();
|
||||
var pps = (bits + 1024) * bit_price + (refs + 2) * cell_price;
|
||||
var paid = pps * expire_in + deposit;
|
||||
return paid + (1 << 30);
|
||||
}
|
||||
|
|
50
crypto/smartcont/envelope-complaint.fif
Normal file
50
crypto/smartcont/envelope-complaint.fif
Normal file
|
@ -0,0 +1,50 @@
|
|||
#!/usr/bin/fift -s
|
||||
"TonUtil.fif" include
|
||||
"GetOpt.fif" include
|
||||
|
||||
{ show-options-help 1 halt } : usage
|
||||
86400 3 * =: expire-in
|
||||
false =: critical
|
||||
-1 =: old-hash
|
||||
|
||||
begin-options
|
||||
" <election-id> <complaint-boc> [-x <expire-in>] [<savefile>]" +cr +tab
|
||||
+"Embeds a validator complaint loaded from file <complaint-boc> into an internal message body to be sent to the elector smart contract "
|
||||
+"and saves it as an internal message body into <savefile>.boc ('complaint-msg-body.boc' by default)"
|
||||
disable-digit-options generic-help-setopt
|
||||
"x" "--expires-in" { parse-int =: expire-in } short-long-option-arg
|
||||
"Sets complaint expiration time in seconds (default " expire-in (.) $+ +")" option-help
|
||||
"h" "--help" { usage } short-long-option
|
||||
"Shows a help message" option-help
|
||||
parse-options
|
||||
|
||||
$# dup 2 < swap 3 > or ' usage if
|
||||
3 :$1..n
|
||||
|
||||
$1 parse-int dup =: election-id
|
||||
32 ufits not abort"invalid election id"
|
||||
$2 =: boc-filename
|
||||
$3 "complaint-msg-body.boc" replace-if-null =: savefile
|
||||
expire-in now + =: expire-at
|
||||
|
||||
boc-filename dup ."Loading complaint from file `" type ."`" cr
|
||||
file>B B>boc dup =: complaint hash =: c-hash
|
||||
complaint <s 8 u@ 0xbc <> abort"Not a valid ValidatorComplaint"
|
||||
complaint <s csr.
|
||||
|
||||
." complaint envelope will expire at " expire-at . ."(in " expire-in . ."seconds)" cr
|
||||
now 32 << c-hash 0xffffffff and or =: query-id
|
||||
."Query id is " query-id . cr
|
||||
|
||||
<b x{52674370} s, query-id 64 u, election-id 32 u, expire-at 32 u,
|
||||
complaint <s s, b>
|
||||
|
||||
dup ."resulting internal message body: " <s csr. cr
|
||||
2 boc+>B dup Bx. cr
|
||||
|
||||
complaint
|
||||
totalcsize swap ."(a total of " . ."data bits, " . ."cell references -> "
|
||||
drop dup Blen . ."BoC data bytes)" cr
|
||||
|
||||
savefile tuck B>file
|
||||
."(Saved to file " type .")" cr
|
|
@ -29,8 +29,8 @@ def? $3 { @' $3 } { "rwallet" } cond constant file-base
|
|||
} : make-rdict
|
||||
|
||||
// Create new restricted wallet; code taken from `auto/restricted-wallet2-code.fif`
|
||||
"auto/restricted-wallet-code.fif" include // code
|
||||
<b 0 32 u, PubKey 256 u, amount make-rdict dict, b> // data
|
||||
"auto/restricted-wallet2-code.fif" include // code
|
||||
<b 0 32 u, PubKey 256 u, 0 32 u, amount make-rdict dict, b> // data
|
||||
null // no libraries
|
||||
<b b{0011} s, 3 roll ref, rot ref, swap dict, b> // create StateInit
|
||||
dup ."StateInit: " <s csr. cr
|
||||
|
|
315
crypto/smartcont/payment-channel-code.fc
Normal file
315
crypto/smartcont/payment-channel-code.fc
Normal file
|
@ -0,0 +1,315 @@
|
|||
;; WARINIG: NOT READY FOR A PRODUCTION!
|
||||
|
||||
int err:wrong_a_signature() asm "31 PUSHINT";
|
||||
int err:wrong_b_signature() asm "32 PUSHINT";
|
||||
int err:msg_value_too_small() asm "33 PUSHINT";
|
||||
int err:replay_protection() asm "34 PUSHINT";
|
||||
int err:no_timeout() asm "35 PUSHINT";
|
||||
int err:expected_init() asm "36 PUSHINT";
|
||||
int err:expected_close() asm "37 PUSHINT";
|
||||
int err:no_promise_signature() asm "38 PUSHINT";
|
||||
int err:wrong_channel_id() asm "39 PUSHINT";
|
||||
|
||||
int msg:init() asm "0x27317822 PUSHINT";
|
||||
int msg:close() asm "0xf28ae183 PUSHINT";
|
||||
int msg:timeout() asm "0x43278a28 PUSHINT";
|
||||
int msg:payout() asm "0x37fe7810 PUSHINT";
|
||||
|
||||
int state:init() asm "0 PUSHINT";
|
||||
int state:close() asm "1 PUSHINT";
|
||||
int state:payout() asm "2 PUSHINT";
|
||||
|
||||
|
||||
;; A - initial balance of Alice,
|
||||
;; B - initial balance of B
|
||||
;;
|
||||
;; To determine balance we track nondecreasing list of promises
|
||||
;; promise_A ;; promised by Alice to Bob
|
||||
;; promise_B ;; promised by Bob to Alice
|
||||
;;
|
||||
;; diff - balance between Alice and Bob. 0 in the beginning
|
||||
;; diff = promise_B - promise_A;
|
||||
;; diff = clamp(diff, -A, +B);
|
||||
;;
|
||||
;; final_A = A + diff;
|
||||
;; final_B = B + diff;
|
||||
|
||||
;; Data pack/unpack
|
||||
;;
|
||||
_ unpack_data() inline_ref {
|
||||
var cs = get_data().begin_parse();
|
||||
var res = (cs~load_ref(), cs~load_ref());
|
||||
cs.end_parse();
|
||||
return res;
|
||||
}
|
||||
|
||||
_ pack_data(cell config, cell state) impure inline_ref {
|
||||
set_data(begin_cell().store_ref(config).store_ref(state).end_cell());
|
||||
}
|
||||
|
||||
;; Config pack/unpack
|
||||
;;
|
||||
;; config$_ initTimeout:int exitTimeout:int a_key:int256 b_key:int256 a_addr b_addr channel_id:uint64 = Config;
|
||||
;;
|
||||
_ unpack_config(cell config) {
|
||||
var cs = config.begin_parse();
|
||||
var res = (
|
||||
cs~load_uint(32),
|
||||
cs~load_uint(32),
|
||||
cs~load_uint(256),
|
||||
cs~load_uint(256),
|
||||
cs~load_ref().begin_parse(),
|
||||
cs~load_ref().begin_parse(),
|
||||
cs~load_uint(64));
|
||||
cs.end_parse();
|
||||
return res;
|
||||
}
|
||||
|
||||
;; takes
|
||||
;; signedMesage$_ a_sig:Maybe<int256> b_sig:Maybe<int256> msg:Message = SignedMessage;
|
||||
;; checks signatures and unwap message.
|
||||
(slice, (int, int)) unwrap_signatures(slice cs, int a_key, int b_key) {
|
||||
int a? = cs~load_int(1);
|
||||
slice a_sig = cs;
|
||||
if (a?) {
|
||||
a_sig = cs~load_ref().begin_parse().preload_bits(512);
|
||||
}
|
||||
var b? = cs~load_int(1);
|
||||
slice b_sig = cs;
|
||||
if (b?) {
|
||||
b_sig = cs~load_ref().begin_parse().preload_bits(512);
|
||||
}
|
||||
int hash = cs.slice_hash();
|
||||
if (a?) {
|
||||
throw_unless(err:wrong_a_signature(), check_signature(hash, a_sig, a_key));
|
||||
}
|
||||
if (b?) {
|
||||
throw_unless(err:wrong_b_signature(), check_signature(hash, b_sig, b_key));
|
||||
}
|
||||
return (cs, (a?, b?));
|
||||
}
|
||||
|
||||
;; process message, give state is stateInit
|
||||
;;
|
||||
;; stateInit signed_A?:Bool signed_B?:Bool min_A:Grams min_B:Grams expire_at:uint32 A:Grams B:Grams = State;
|
||||
_ unpack_state_init(slice state) {
|
||||
return (
|
||||
state~load_int(1),
|
||||
state~load_int(1),
|
||||
state~load_grams(),
|
||||
state~load_grams(),
|
||||
state~load_uint(32),
|
||||
state~load_grams(),
|
||||
state~load_grams());
|
||||
|
||||
}
|
||||
_ pack_state_init(int signed_A?, int signed_B?, int min_A, int min_B, int expire_at, int A, int B) {
|
||||
return begin_cell()
|
||||
.store_int(state:init(), 3)
|
||||
.store_int(signed_A?, 1)
|
||||
.store_int(signed_B?, 1)
|
||||
.store_grams(min_A)
|
||||
.store_grams(min_B)
|
||||
.store_uint(expire_at, 32)
|
||||
.store_grams(A)
|
||||
.store_grams(B).end_cell();
|
||||
}
|
||||
|
||||
;; stateClosing$10 signed_A?:bool signed_B?:Bool promise_A:Grams promise_B:Grams exipire_at:uint32 A:Grams B:Grams = State;
|
||||
_ unpack_state_close(slice state) {
|
||||
return (
|
||||
state~load_int(1),
|
||||
state~load_int(1),
|
||||
state~load_grams(),
|
||||
state~load_grams(),
|
||||
state~load_uint(32),
|
||||
state~load_grams(),
|
||||
state~load_grams());
|
||||
}
|
||||
|
||||
_ pack_state_close(int signed_A?, int signed_B?, int promise_A, int promise_B, int expire_at, int A, int B) {
|
||||
return begin_cell()
|
||||
.store_int(state:close(), 3)
|
||||
.store_int(signed_A?, 1)
|
||||
.store_int(signed_B?, 1)
|
||||
.store_grams(promise_A)
|
||||
.store_grams(promise_B)
|
||||
.store_uint(expire_at, 32)
|
||||
.store_grams(A)
|
||||
.store_grams(B).end_cell();
|
||||
}
|
||||
|
||||
_ send_payout(slice s_addr, int amount, int channel_id, int flags) impure {
|
||||
send_raw_message(begin_cell()
|
||||
.store_uint(0x10, 6)
|
||||
.store_slice(s_addr)
|
||||
.store_grams(amount)
|
||||
.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
|
||||
.store_uint(msg:payout(), 32)
|
||||
.store_uint(channel_id, 64)
|
||||
.end_cell(), flags);
|
||||
}
|
||||
|
||||
|
||||
cell do_payout(int promise_A, int promise_B, int A, int B, slice a_addr, slice b_addr, int channel_id) impure {
|
||||
accept_message();
|
||||
|
||||
int diff = promise_B - promise_A;
|
||||
if (diff < - A) {
|
||||
diff = - A;
|
||||
}
|
||||
if (diff > B) {
|
||||
diff = B;
|
||||
}
|
||||
A += diff;
|
||||
B -= diff;
|
||||
|
||||
send_payout(a_addr, A, channel_id, 3);
|
||||
send_payout(b_addr, B, channel_id, 3);
|
||||
|
||||
return begin_cell()
|
||||
.store_int(state:payout(), 3)
|
||||
.store_grams(A)
|
||||
.store_grams(B)
|
||||
.end_cell();
|
||||
}
|
||||
|
||||
|
||||
;;
|
||||
;; init$000 inc_A:Grams inc_B:Grams min_A:Grams min_B:Grams = Message;
|
||||
;;
|
||||
cell with_init(slice state, int msg_value, slice msg, int msg_signed_A?, int msg_signed_B?,
|
||||
slice a_addr, slice b_addr, int init_timeout, int channel_id) {
|
||||
;; parse state
|
||||
(int signed_A?, int signed_B?, int min_A, int min_B, int expire_at, int A, int B) = unpack_state_init(state);
|
||||
|
||||
if (expire_at == 0) {
|
||||
expire_at = now() + init_timeout;
|
||||
}
|
||||
|
||||
int op = msg~load_uint(32);
|
||||
if (op == msg:timeout()) {
|
||||
throw_unless(err:no_timeout(), expire_at < now());
|
||||
return do_payout(0, 0, A, B, a_addr, b_addr, channel_id);
|
||||
}
|
||||
throw_unless(err:expected_init(), op == msg:init());
|
||||
|
||||
;; unpack init message
|
||||
(int inc_A, int inc_B, int upd_min_A, int upd_min_B, int got_channel_id) =
|
||||
(msg~load_grams(), msg~load_grams(), msg~load_grams(), msg~load_grams(), msg~load_uint(64));
|
||||
throw_unless(err:wrong_channel_id(), got_channel_id == channel_id);
|
||||
|
||||
;; TODO: we should reserve some part of the value for comission
|
||||
throw_if(err:msg_value_too_small(), msg_value < inc_A + inc_B);
|
||||
throw_unless(err:replay_protection(), (msg_signed_A? < signed_A?) | (msg_signed_B? < signed_B?));
|
||||
|
||||
A += inc_A;
|
||||
B += inc_B;
|
||||
|
||||
signed_A? |= msg_signed_A?;
|
||||
if (min_A < upd_min_A) {
|
||||
min_A = upd_min_A;
|
||||
}
|
||||
|
||||
signed_B? |= msg_signed_B?;
|
||||
if (min_B < upd_min_B) {
|
||||
min_B = upd_min_B;
|
||||
}
|
||||
|
||||
if (signed_A? & signed_B?) {
|
||||
if ((min_A > A) | (min_B > B)) {
|
||||
return do_payout(0, 0, A, B, a_addr, b_addr, channel_id);
|
||||
}
|
||||
|
||||
return pack_state_close(0, 0, 0, 0, 0, A, B);
|
||||
}
|
||||
|
||||
return pack_state_init(signed_A?, signed_B?, min_A, min_B, expire_at, A, B);
|
||||
}
|
||||
|
||||
;; close$001 extra_A:Grams extra_B:Grams sig:Maybe<int256> promise_A:Grams promise_B:Grams = Message;
|
||||
|
||||
cell with_close(slice cs, slice msg, int msg_signed_A?, int msg_signed_B?, int a_key, int b_key,
|
||||
slice a_addr, slice b_addr, int expire_timeout, int channel_id) {
|
||||
;; parse state
|
||||
(int signed_A?, int signed_B?, int promise_A, int promise_B, int expire_at, int A, int B) = unpack_state_close(cs);
|
||||
|
||||
if (expire_at == 0) {
|
||||
expire_at = now() + expire_timeout;
|
||||
}
|
||||
|
||||
int op = msg~load_uint(32);
|
||||
if (op == msg:timeout()) {
|
||||
throw_unless(err:no_timeout(), expire_at < now());
|
||||
return do_payout(promise_A, promise_B, A, B, a_addr, b_addr, channel_id);
|
||||
}
|
||||
throw_unless(err:expected_close(), op == msg:close());
|
||||
|
||||
;; also ensures that (msg_signed_A? | msg_signed_B?) is true
|
||||
throw_unless(err:replay_protection(), (msg_signed_A? < signed_A?) | (msg_signed_B? < signed_B?));
|
||||
signed_A? |= msg_signed_A?;
|
||||
signed_B? |= msg_signed_B?;
|
||||
|
||||
;; unpack close message
|
||||
(int extra_A, int extra_B) = (msg~load_grams(), msg~load_grams());
|
||||
int has_sig = msg~load_int(1);
|
||||
if (has_sig) {
|
||||
slice sig = msg~load_ref().begin_parse().preload_bits(512);
|
||||
int hash = msg.slice_hash();
|
||||
ifnot (msg_signed_A?) {
|
||||
throw_unless(err:wrong_a_signature(), check_signature(hash, sig, a_key));
|
||||
extra_A = 0;
|
||||
}
|
||||
ifnot (msg_signed_B?) {
|
||||
throw_unless(err:wrong_b_signature(), check_signature(hash, sig, b_key));
|
||||
extra_B = 0;
|
||||
}
|
||||
} else {
|
||||
throw_unless(err:no_promise_signature(), msg_signed_A? & msg_signed_B?);
|
||||
extra_A = 0;
|
||||
extra_B = 0;
|
||||
}
|
||||
(int got_channel_id, int update_promise_A, int update_promise_B) = (msg~load_uint(64), msg~load_grams(), msg~load_grams());
|
||||
throw_unless(err:wrong_channel_id(), got_channel_id == channel_id);
|
||||
|
||||
|
||||
accept_message();
|
||||
update_promise_A += extra_A;
|
||||
if (promise_A < update_promise_A) {
|
||||
promise_A = update_promise_A;
|
||||
}
|
||||
update_promise_B += extra_B;
|
||||
if (promise_B < update_promise_B) {
|
||||
promise_B = update_promise_B;
|
||||
}
|
||||
|
||||
if (signed_A? & signed_B?) {
|
||||
return do_payout(promise_A, promise_B, A, B, a_addr, b_addr, channel_id);
|
||||
}
|
||||
return pack_state_close(signed_A?, signed_B?, promise_A, promise_B, expire_at, A, B);
|
||||
}
|
||||
|
||||
() recv_any(int msg_value, slice msg) impure {
|
||||
(cell config, cell state) = unpack_data();
|
||||
(int init_timeout, int close_timeout, int a_key, int b_key, slice a_addr, slice b_addr, int channel_id) = config.unpack_config();
|
||||
(int msg_signed_A?, int msg_signed_B?) = msg~unwrap_signatures(a_key, b_key);
|
||||
|
||||
slice cs = state.begin_parse();
|
||||
int state_type = cs~load_uint(3);
|
||||
|
||||
if (state_type == state:init()) { ;; init
|
||||
state = with_init(cs, msg_value, msg, msg_signed_A?, msg_signed_B?, a_addr, b_addr, init_timeout, channel_id);
|
||||
} if (state_type == state:close()) {
|
||||
state = with_close(cs, msg, msg_signed_A?, msg_signed_B?, a_key, b_key, a_addr, b_addr, close_timeout, channel_id);
|
||||
}
|
||||
|
||||
pack_data(config, state);
|
||||
}
|
||||
|
||||
() recv_internal(int msg_value, cell in_msg_cell, slice in_msg) impure {
|
||||
recv_any(msg_value, in_msg);
|
||||
}
|
||||
|
||||
() recv_external(slice in_msg) impure {
|
||||
recv_any(0, in_msg);
|
||||
}
|
|
@ -1,13 +1,16 @@
|
|||
;; Restricted wallet (a variant of wallet-code.fc)
|
||||
;; until configuration parameter -13 is set, accepts messages only to elector smc
|
||||
;; restricts access to parts of balance until certain dates
|
||||
|
||||
() recv_internal(slice in_msg) impure {
|
||||
;; do nothing for internal messages
|
||||
}
|
||||
|
||||
_ days_passed() inline {
|
||||
var p = config_param(-13);
|
||||
return null?(p) ? -1 : (now() - begin_parse(p).preload_uint(32)) / 86400;
|
||||
_ seconds_passed(int start_at, int utime) inline_ref {
|
||||
ifnot (start_at) {
|
||||
var p = config_param(-13);
|
||||
start_at = null?(p) ? 0 : begin_parse(p).preload_uint(32);
|
||||
}
|
||||
return start_at ? utime - start_at : -1;
|
||||
}
|
||||
|
||||
() recv_external(slice in_msg) impure {
|
||||
|
@ -16,7 +19,7 @@ _ days_passed() inline {
|
|||
var (msg_seqno, valid_until) = (cs~load_uint(32), cs~load_uint(32));
|
||||
throw_if(35, valid_until <= now());
|
||||
var ds = get_data().begin_parse();
|
||||
var (stored_seqno, public_key, rdict) = (ds~load_uint(32), ds~load_uint(256), ds~load_dict());
|
||||
var (stored_seqno, public_key, start_at, rdict) = (ds~load_uint(32), ds~load_uint(256), ds~load_uint(32), ds~load_dict());
|
||||
ds.end_parse();
|
||||
throw_unless(33, msg_seqno == stored_seqno);
|
||||
ifnot (msg_seqno) {
|
||||
|
@ -24,14 +27,15 @@ _ days_passed() inline {
|
|||
set_data(begin_cell()
|
||||
.store_uint(stored_seqno + 1, 32)
|
||||
.store_uint(public_key, 256)
|
||||
.store_uint(start_at, 32)
|
||||
.store_dict(rdict)
|
||||
.end_cell());
|
||||
return ();
|
||||
}
|
||||
throw_unless(34, check_signature(slice_hash(in_msg), signature, public_key));
|
||||
accept_message();
|
||||
var ts = days_passed();
|
||||
var (_, value, found) = rdict.idict_get_preveq?(16, ts);
|
||||
var ts = seconds_passed(start_at, now());
|
||||
var (_, value, found) = rdict.idict_get_preveq?(32, ts);
|
||||
if (found) {
|
||||
raw_reserve(value~load_grams(), 2);
|
||||
}
|
||||
|
@ -45,6 +49,7 @@ _ days_passed() inline {
|
|||
set_data(begin_cell()
|
||||
.store_uint(stored_seqno + 1, 32)
|
||||
.store_uint(public_key, 256)
|
||||
.store_uint(start_at, 32)
|
||||
.store_dict(rdict)
|
||||
.end_cell());
|
||||
}
|
||||
|
@ -61,15 +66,23 @@ int get_public_key() method_id {
|
|||
return cs.preload_uint(256);
|
||||
}
|
||||
|
||||
int balance() method_id {
|
||||
int compute_balance_at(int utime) inline_ref {
|
||||
var ds = get_data().begin_parse().skip_bits(32 + 256);
|
||||
var rdict = ds~load_dict();
|
||||
var (start_at, rdict) = (ds~load_uint(32), ds~load_dict());
|
||||
ds.end_parse();
|
||||
var ts = days_passed();
|
||||
var ts = seconds_passed(start_at, utime);
|
||||
var balance = get_balance().pair_first();
|
||||
var (_, value, found) = rdict.idict_get_preveq?(16, ts);
|
||||
var (_, value, found) = rdict.idict_get_preveq?(32, ts);
|
||||
if (found) {
|
||||
balance = max(balance - value~load_grams(), 0);
|
||||
}
|
||||
return balance;
|
||||
}
|
||||
|
||||
int balance_at(int utime) method_id {
|
||||
return compute_balance_at(utime);
|
||||
}
|
||||
|
||||
int balance() method_id {
|
||||
return compute_balance_at(now());
|
||||
}
|
||||
|
|
103
crypto/smartcont/restricted-wallet3-code.fc
Normal file
103
crypto/smartcont/restricted-wallet3-code.fc
Normal file
|
@ -0,0 +1,103 @@
|
|||
;; Restricted wallet initialized by a third party (a variant of restricted-wallet2-code.fc)
|
||||
;; restricts access to parts of balance until certain dates
|
||||
|
||||
() recv_internal(slice in_msg) impure {
|
||||
;; do nothing for internal messages
|
||||
}
|
||||
|
||||
_ seconds_passed(int start_at, int utime) inline_ref {
|
||||
ifnot (start_at) {
|
||||
var p = config_param(-13);
|
||||
start_at = null?(p) ? 0 : begin_parse(p).preload_uint(32);
|
||||
}
|
||||
return start_at ? utime - start_at : -1;
|
||||
}
|
||||
|
||||
() recv_external(slice in_msg) impure {
|
||||
var signature = in_msg~load_bits(512);
|
||||
var cs = in_msg;
|
||||
var (subwallet_id, valid_until, msg_seqno) = (cs~load_uint(32), cs~load_uint(32), cs~load_uint(32));
|
||||
throw_if(35, valid_until <= now());
|
||||
var ds = get_data().begin_parse();
|
||||
var (stored_seqno, stored_subwallet, public_key) = (ds~load_uint(32), ds~load_uint(32), ds~load_uint(256));
|
||||
throw_unless(33, msg_seqno == stored_seqno);
|
||||
throw_unless(34, subwallet_id == stored_subwallet);
|
||||
throw_unless(36, check_signature(slice_hash(in_msg), signature, public_key));
|
||||
ifnot (msg_seqno) {
|
||||
public_key = ds~load_uint(256); ;; load "final" public key
|
||||
ds.end_parse();
|
||||
cs~touch();
|
||||
var (start_at, rdict) = (cs~load_uint(32), cs~load_dict());
|
||||
cs.end_parse();
|
||||
accept_message();
|
||||
set_data(begin_cell()
|
||||
.store_uint(stored_seqno + 1, 32)
|
||||
.store_uint(stored_subwallet, 32)
|
||||
.store_uint(public_key, 256)
|
||||
.store_uint(start_at, 32)
|
||||
.store_dict(rdict)
|
||||
.end_cell());
|
||||
return ();
|
||||
}
|
||||
var (start_at, rdict) = (ds~load_uint(32), ds~load_dict());
|
||||
ds.end_parse();
|
||||
accept_message();
|
||||
var ts = seconds_passed(start_at, now());
|
||||
var (_, value, found) = rdict.idict_get_preveq?(32, ts);
|
||||
if (found) {
|
||||
raw_reserve(value~load_grams(), 2);
|
||||
}
|
||||
cs~touch();
|
||||
while (cs.slice_refs()) {
|
||||
var mode = cs~load_uint(8);
|
||||
var msg = cs~load_ref();
|
||||
send_raw_message(msg, mode);
|
||||
}
|
||||
cs.end_parse();
|
||||
set_data(begin_cell()
|
||||
.store_uint(stored_seqno + 1, 32)
|
||||
.store_uint(stored_subwallet, 32)
|
||||
.store_uint(public_key, 256)
|
||||
.store_uint(start_at, 32)
|
||||
.store_dict(rdict)
|
||||
.end_cell());
|
||||
}
|
||||
|
||||
;; Get methods
|
||||
|
||||
int seqno() method_id {
|
||||
return get_data().begin_parse().preload_uint(32);
|
||||
}
|
||||
|
||||
int wallet_id() method_id {
|
||||
var ds = get_data().begin_parse();
|
||||
ds~load_uint(32);
|
||||
return ds.preload_uint(32);
|
||||
}
|
||||
|
||||
int get_public_key() method_id {
|
||||
var ds = get_data().begin_parse();
|
||||
ds~load_uint(32 + 32);
|
||||
return ds.preload_uint(256);
|
||||
}
|
||||
|
||||
int compute_balance_at(int utime) inline_ref {
|
||||
var ds = get_data().begin_parse().skip_bits(32 + 32 + 256);
|
||||
var (start_at, rdict) = (ds~load_uint(32), ds~load_dict());
|
||||
ds.end_parse();
|
||||
var ts = seconds_passed(start_at, utime);
|
||||
var balance = get_balance().pair_first();
|
||||
var (_, value, found) = rdict.idict_get_preveq?(32, ts);
|
||||
if (found) {
|
||||
balance = max(balance - value~load_grams(), 0);
|
||||
}
|
||||
return balance;
|
||||
}
|
||||
|
||||
int balance_at(int utime) method_id {
|
||||
return compute_balance_at(utime);
|
||||
}
|
||||
|
||||
int balance() method_id {
|
||||
return compute_balance_at(now());
|
||||
}
|
|
@ -18,5 +18,7 @@ dup Blen { 32 B>i@ } { drop Basechain } cond constant wallet_wc
|
|||
|
||||
file-base +".pk" dup file-exists? {
|
||||
dup file>B dup Blen 32 <> abort"Private key must be exactly 32 bytes long"
|
||||
=: wallet_pk ."Private key available in file " type cr
|
||||
tuck =: wallet_pk ."Private key available in file " type cr
|
||||
priv>pub 256 B>u@
|
||||
dup ."Corresponding public key is " .pubkey ." = " 64X. cr
|
||||
} { ."Private key file " type ." not found" cr } cond
|
||||
|
|
|
@ -21,6 +21,31 @@
|
|||
#include "block/block-auto.h"
|
||||
#include "block/block-parse.h"
|
||||
namespace ton {
|
||||
|
||||
namespace smc {
|
||||
td::Ref<vm::CellSlice> pack_grams(td::uint64 amount) {
|
||||
vm::CellBuilder cb;
|
||||
block::tlb::t_Grams.store_integer_value(cb, td::BigInt256(amount));
|
||||
return vm::load_cell_slice_ref(cb.finalize());
|
||||
}
|
||||
|
||||
bool unpack_grams(td::Ref<vm::CellSlice> cs, td::uint64& amount) {
|
||||
td::RefInt256 got;
|
||||
if (!block::tlb::t_Grams.as_integer_to(cs, got)) {
|
||||
return false;
|
||||
}
|
||||
if (!got->unsigned_fits_bits(63)) {
|
||||
return false;
|
||||
}
|
||||
auto x = got->to_long();
|
||||
if (x < 0) {
|
||||
return false;
|
||||
}
|
||||
amount = x;
|
||||
return true;
|
||||
}
|
||||
} // namespace smc
|
||||
|
||||
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)
|
||||
|
@ -47,7 +72,7 @@ void GenericAccount::store_int_message(vm::CellBuilder& cb, const block::StdAddr
|
|||
.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);
|
||||
cb.store_zeroes(9 + 64 + 32);
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> GenericAccount::create_ext_message(const block::StdAddress& address, td::Ref<vm::Cell> new_state,
|
||||
|
|
|
@ -23,6 +23,10 @@
|
|||
#include "SmartContract.h"
|
||||
|
||||
namespace ton {
|
||||
namespace smc {
|
||||
td::Ref<vm::CellSlice> pack_grams(td::uint64 amount);
|
||||
bool unpack_grams(td::Ref<vm::CellSlice> cs, td::uint64& amount);
|
||||
} // namespace smc
|
||||
class GenericAccount {
|
||||
public:
|
||||
static td::Ref<vm::Cell> get_init_state(td::Ref<vm::Cell> code, td::Ref<vm::Cell> data) noexcept;
|
||||
|
|
|
@ -73,17 +73,11 @@ td::Ref<vm::Cell> HighloadWallet::make_a_gift_message(const td::Ed25519::Private
|
|||
for (size_t i = 0; i < gifts.size(); i++) {
|
||||
auto& gift = gifts[i];
|
||||
td::int32 send_mode = 3;
|
||||
auto gramms = gift.gramms;
|
||||
if (gramms == -1) {
|
||||
gramms = 0;
|
||||
if (gift.gramms == -1) {
|
||||
send_mode += 128;
|
||||
}
|
||||
auto message_inner = create_int_message(gift);
|
||||
vm::CellBuilder cb;
|
||||
GenericAccount::store_int_message(cb, gift.destination, gramms);
|
||||
cb.store_bytes("\0\0\0\0", 4);
|
||||
vm::CellString::store(cb, gift.message, 35 * 8).ensure();
|
||||
auto message_inner = cb.finalize();
|
||||
cb = {};
|
||||
cb.store_long(send_mode, 8).store_ref(message_inner);
|
||||
auto key = messages.integer_key(td::make_refint(i), 16, false);
|
||||
messages.set_builder(key.bits(), 16, cb);
|
||||
|
|
|
@ -73,18 +73,11 @@ td::Ref<vm::Cell> HighloadWalletV2::make_a_gift_message(const td::Ed25519::Priva
|
|||
for (size_t i = 0; i < gifts.size(); i++) {
|
||||
auto& gift = gifts[i];
|
||||
td::int32 send_mode = 3;
|
||||
auto gramms = gift.gramms;
|
||||
if (gramms == -1) {
|
||||
gramms = 0;
|
||||
if (gift.gramms == -1) {
|
||||
send_mode += 128;
|
||||
}
|
||||
vm::CellBuilder cb;
|
||||
GenericAccount::store_int_message(cb, gift.destination, gramms);
|
||||
cb.store_bytes("\0\0\0\0", 4);
|
||||
vm::CellString::store(cb, gift.message, 35 * 8).ensure();
|
||||
auto message_inner = cb.finalize();
|
||||
cb = {};
|
||||
cb.store_long(send_mode, 8).store_ref(message_inner);
|
||||
cb.store_long(send_mode, 8).store_ref(create_int_message(gift));
|
||||
auto key = messages.integer_key(td::make_refint(i), 16, false);
|
||||
messages.set_builder(key.bits(), 16, cb);
|
||||
}
|
||||
|
|
264
crypto/smc-envelope/PaymentChannel.cpp
Normal file
264
crypto/smc-envelope/PaymentChannel.cpp
Normal file
|
@ -0,0 +1,264 @@
|
|||
#include "PaymentChannel.h"
|
||||
#include "GenericAccount.h"
|
||||
#include "vm/cells.h"
|
||||
#include "vm/cellslice.h"
|
||||
#include "Ed25519.h"
|
||||
#include "block/block-auto.h"
|
||||
#include "block/block-parse.h"
|
||||
|
||||
#include "SmartContract.h"
|
||||
#include "SmartContractCode.h"
|
||||
|
||||
namespace ton {
|
||||
using smc::pack_grams;
|
||||
using smc::unpack_grams;
|
||||
namespace pchan {
|
||||
|
||||
td::Ref<vm::Cell> Config::serialize() const {
|
||||
block::gen::ChanConfig::Record rec;
|
||||
|
||||
vm::CellBuilder a_addr_cb;
|
||||
block::tlb::t_MsgAddressInt.store_std_address(a_addr_cb, a_addr);
|
||||
rec.a_addr = a_addr_cb.finalize_novm();
|
||||
|
||||
vm::CellBuilder b_addr_cb;
|
||||
block::tlb::t_MsgAddressInt.store_std_address(b_addr_cb, b_addr);
|
||||
rec.b_addr = b_addr_cb.finalize_novm();
|
||||
|
||||
rec.a_key.as_slice().copy_from(a_key);
|
||||
rec.b_key.as_slice().copy_from(b_key);
|
||||
rec.init_timeout = init_timeout;
|
||||
rec.close_timeout = close_timeout;
|
||||
rec.channel_id = channel_id;
|
||||
|
||||
td::Ref<vm::Cell> res;
|
||||
CHECK(tlb::pack_cell(res, rec));
|
||||
return res;
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> MsgInit::serialize() const {
|
||||
block::gen::ChanMsg::Record_chan_msg_init rec;
|
||||
rec.min_A = pack_grams(min_A);
|
||||
rec.min_B = pack_grams(min_B);
|
||||
rec.inc_A = pack_grams(inc_A);
|
||||
rec.inc_B = pack_grams(inc_B);
|
||||
rec.channel_id = channel_id;
|
||||
|
||||
td::Ref<vm::Cell> res;
|
||||
CHECK(tlb::pack_cell(res, rec));
|
||||
return res;
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> Promise::serialize() const {
|
||||
block::gen::ChanPromise::Record rec;
|
||||
rec.channel_id = channel_id;
|
||||
rec.promise_A = pack_grams(promise_A);
|
||||
rec.promise_B = pack_grams(promise_B);
|
||||
td::Ref<vm::Cell> res;
|
||||
CHECK(tlb::pack_cell(res, rec));
|
||||
return res;
|
||||
}
|
||||
|
||||
td::SecureString sign(const td::Ref<vm::Cell>& msg, const td::Ed25519::PrivateKey* key) {
|
||||
return key->sign(msg->get_hash().as_slice()).move_as_ok();
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> maybe_sign(const td::Ref<vm::Cell>& msg, const td::Ed25519::PrivateKey* key) {
|
||||
if (!key) {
|
||||
return {};
|
||||
}
|
||||
return vm::CellBuilder().store_bytes(sign(msg, key).as_slice()).finalize();
|
||||
}
|
||||
|
||||
td::Ref<vm::CellSlice> maybe_ref(td::Ref<vm::Cell> msg) {
|
||||
vm::CellBuilder cb;
|
||||
CHECK(cb.store_maybe_ref(msg));
|
||||
return vm::load_cell_slice_ref(cb.finalize());
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> MsgClose::serialize() const {
|
||||
block::gen::ChanMsg::Record_chan_msg_close rec;
|
||||
rec.extra_A = pack_grams(extra_A);
|
||||
rec.extra_B = pack_grams(extra_B);
|
||||
rec.promise = signed_promise;
|
||||
|
||||
td::Ref<vm::Cell> res;
|
||||
CHECK(tlb::pack_cell(res, rec));
|
||||
return res;
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> MsgTimeout::serialize() const {
|
||||
block::gen::ChanMsg::Record_chan_msg_timeout rec;
|
||||
td::Ref<vm::Cell> res;
|
||||
CHECK(tlb::pack_cell(res, rec));
|
||||
return res;
|
||||
}
|
||||
|
||||
td::SecureString SignedPromise::signature(const td::Ed25519::PrivateKey* key, const td::Ref<vm::Cell>& promise) {
|
||||
return sign(promise, key);
|
||||
}
|
||||
td::Ref<vm::Cell> SignedPromise::create_and_serialize(td::Slice signature, const td::Ref<vm::Cell>& promise) {
|
||||
block::gen::ChanSignedPromise::Record rec;
|
||||
rec.promise = vm::load_cell_slice_ref(promise);
|
||||
LOG(ERROR) << "signature.size() = " << signature.size();
|
||||
rec.sig = maybe_ref(vm::CellBuilder().store_bytes(signature).finalize());
|
||||
td::Ref<vm::Cell> res;
|
||||
CHECK(tlb::pack_cell(res, rec));
|
||||
return res;
|
||||
}
|
||||
td::Ref<vm::Cell> SignedPromise::create_and_serialize(const td::Ed25519::PrivateKey* key,
|
||||
const td::Ref<vm::Cell>& promise) {
|
||||
block::gen::ChanSignedPromise::Record rec;
|
||||
rec.promise = vm::load_cell_slice_ref(promise);
|
||||
rec.sig = maybe_ref(maybe_sign(promise, key));
|
||||
td::Ref<vm::Cell> res;
|
||||
CHECK(tlb::pack_cell(res, rec));
|
||||
return res;
|
||||
}
|
||||
|
||||
bool SignedPromise::unpack(td::Ref<vm::Cell> cell) {
|
||||
block::gen::ChanSignedPromise::Record rec;
|
||||
if (!tlb::unpack_cell(cell, rec)) {
|
||||
return false;
|
||||
}
|
||||
block::gen::ChanPromise::Record rec_promise;
|
||||
if (!tlb::csr_unpack(rec.promise, rec_promise)) {
|
||||
return false;
|
||||
}
|
||||
promise.channel_id = rec_promise.channel_id;
|
||||
if (!unpack_grams(rec_promise.promise_A, promise.promise_A)) {
|
||||
return false;
|
||||
}
|
||||
if (!unpack_grams(rec_promise.promise_B, promise.promise_B)) {
|
||||
return false;
|
||||
}
|
||||
td::Ref<vm::Cell> sig_cell;
|
||||
if (!rec.sig->prefetch_maybe_ref(sig_cell)) {
|
||||
return false;
|
||||
}
|
||||
td::SecureString signature(64);
|
||||
vm::CellSlice cs = vm::load_cell_slice(sig_cell);
|
||||
if (!cs.prefetch_bytes(signature.as_mutable_slice())) {
|
||||
return false;
|
||||
}
|
||||
o_signature = std::move(signature);
|
||||
return true;
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> StateInit::serialize() const {
|
||||
block::gen::ChanState::Record_chan_state_init rec;
|
||||
rec.expire_at = expire_at;
|
||||
rec.min_A = pack_grams(min_A);
|
||||
rec.min_B = pack_grams(min_B);
|
||||
rec.A = pack_grams(A);
|
||||
rec.B = pack_grams(B);
|
||||
rec.signed_A = signed_A;
|
||||
rec.signed_B = signed_B;
|
||||
td::Ref<vm::Cell> res;
|
||||
CHECK(tlb::pack_cell(res, rec));
|
||||
return res;
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> Data::serialize() const {
|
||||
block::gen::ChanData::Record rec;
|
||||
rec.config = config;
|
||||
rec.state = state;
|
||||
td::Ref<vm::Cell> res;
|
||||
CHECK(block::gen::t_ChanData.cell_pack(res, rec));
|
||||
return res;
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> Data::init_state() {
|
||||
return StateInit().serialize();
|
||||
}
|
||||
} // namespace pchan
|
||||
|
||||
td::Result<PaymentChannel::Info> PaymentChannel::get_info() const {
|
||||
block::gen::ChanData::Record data_rec;
|
||||
if (!tlb::unpack_cell(get_state().data, data_rec)) {
|
||||
return td::Status::Error("Can't unpack data");
|
||||
}
|
||||
block::gen::ChanConfig::Record config_rec;
|
||||
if (!tlb::unpack_cell(data_rec.config, config_rec)) {
|
||||
return td::Status::Error("Can't unpack config");
|
||||
}
|
||||
pchan::Config config;
|
||||
config.a_key = td::SecureString(config_rec.a_key.as_slice());
|
||||
config.b_key = td::SecureString(config_rec.b_key.as_slice());
|
||||
block::tlb::t_MsgAddressInt.extract_std_address(vm::load_cell_slice_ref(config_rec.a_addr), config.a_addr);
|
||||
block::tlb::t_MsgAddressInt.extract_std_address(vm::load_cell_slice_ref(config_rec.b_addr), config.b_addr);
|
||||
config.init_timeout = static_cast<td::int32>(config_rec.init_timeout);
|
||||
config.close_timeout = static_cast<td::int32>(config_rec.close_timeout);
|
||||
config.channel_id = static_cast<td::int64>(config_rec.channel_id);
|
||||
|
||||
auto state_cs = vm::load_cell_slice(data_rec.state);
|
||||
Info res;
|
||||
switch (block::gen::t_ChanState.check_tag(state_cs)) {
|
||||
case block::gen::ChanState::chan_state_init: {
|
||||
pchan::StateInit state;
|
||||
block::gen::ChanState::Record_chan_state_init state_rec;
|
||||
if (!tlb::unpack_cell(data_rec.state, state_rec)) {
|
||||
return td::Status::Error("Can't unpack state");
|
||||
}
|
||||
bool ok = unpack_grams(state_rec.A, state.A) && unpack_grams(state_rec.B, state.B) &&
|
||||
unpack_grams(state_rec.min_A, state.min_A) && unpack_grams(state_rec.min_B, state.min_B);
|
||||
state.expire_at = state_rec.expire_at;
|
||||
state.signed_A = state_rec.signed_A;
|
||||
state.signed_B = state_rec.signed_B;
|
||||
if (!ok) {
|
||||
return td::Status::Error("Can't unpack state");
|
||||
}
|
||||
res.state = std::move(state);
|
||||
break;
|
||||
}
|
||||
case block::gen::ChanState::chan_state_close: {
|
||||
pchan::StateClose state;
|
||||
block::gen::ChanState::Record_chan_state_close state_rec;
|
||||
if (!tlb::unpack_cell(data_rec.state, state_rec)) {
|
||||
return td::Status::Error("Can't unpack state");
|
||||
}
|
||||
bool ok = unpack_grams(state_rec.A, state.A) && unpack_grams(state_rec.B, state.B) &&
|
||||
unpack_grams(state_rec.promise_A, state.promise_A) &&
|
||||
unpack_grams(state_rec.promise_B, state.promise_B);
|
||||
state.expire_at = state_rec.expire_at;
|
||||
state.signed_A = state_rec.signed_A;
|
||||
state.signed_B = state_rec.signed_B;
|
||||
if (!ok) {
|
||||
return td::Status::Error("Can't unpack state");
|
||||
}
|
||||
res.state = std::move(state);
|
||||
break;
|
||||
}
|
||||
case block::gen::ChanState::chan_state_payout: {
|
||||
pchan::StatePayout state;
|
||||
block::gen::ChanState::Record_chan_state_payout state_rec;
|
||||
if (!tlb::unpack_cell(data_rec.state, state_rec)) {
|
||||
return td::Status::Error("Can't unpack state");
|
||||
}
|
||||
bool ok = unpack_grams(state_rec.A, state.A) && unpack_grams(state_rec.B, state.B);
|
||||
if (!ok) {
|
||||
return td::Status::Error("Can't unpack state");
|
||||
}
|
||||
res.state = std::move(state);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
return td::Status::Error("Can't unpack state");
|
||||
}
|
||||
|
||||
res.config = std::move(config);
|
||||
res.description = block::gen::t_ChanState.as_string_ref(data_rec.state);
|
||||
|
||||
return std::move(res);
|
||||
} // namespace ton
|
||||
|
||||
td::optional<td::int32> PaymentChannel::guess_revision(const vm::Cell::Hash& code_hash) {
|
||||
for (auto i : ton::SmartContractCode::get_revisions(ton::SmartContractCode::PaymentChannel)) {
|
||||
auto code = SmartContractCode::get_code(SmartContractCode::PaymentChannel, i);
|
||||
if (code->get_hash() == code_hash) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
} // namespace ton
|
251
crypto/smc-envelope/PaymentChannel.h
Normal file
251
crypto/smc-envelope/PaymentChannel.h
Normal file
|
@ -0,0 +1,251 @@
|
|||
#pragma once
|
||||
#include "vm/cells.h"
|
||||
#include "vm/cellslice.h"
|
||||
#include "Ed25519.h"
|
||||
#include "block/block-auto.h"
|
||||
#include "block/block-parse.h"
|
||||
|
||||
#include "td/utils/Variant.h"
|
||||
|
||||
#include "SmartContract.h"
|
||||
#include "SmartContractCode.h"
|
||||
|
||||
namespace ton {
|
||||
namespace pchan {
|
||||
|
||||
//
|
||||
// Payment channels
|
||||
//
|
||||
struct Config {
|
||||
td::uint32 init_timeout{0};
|
||||
td::uint32 close_timeout{0};
|
||||
td::SecureString a_key;
|
||||
td::SecureString b_key;
|
||||
block::StdAddress a_addr;
|
||||
block::StdAddress b_addr;
|
||||
td::uint64 channel_id{0};
|
||||
|
||||
td::Ref<vm::Cell> serialize() const;
|
||||
};
|
||||
|
||||
struct MsgInit {
|
||||
td::uint64 inc_A{0};
|
||||
td::uint64 inc_B{0};
|
||||
td::uint64 min_A{0};
|
||||
td::uint64 min_B{0};
|
||||
td::uint64 channel_id{0};
|
||||
|
||||
td::Ref<vm::Cell> serialize() const;
|
||||
};
|
||||
|
||||
struct Promise {
|
||||
td::uint64 channel_id;
|
||||
td::uint64 promise_A{0};
|
||||
td::uint64 promise_B{0};
|
||||
td::Ref<vm::Cell> serialize() const;
|
||||
};
|
||||
|
||||
td::Ref<vm::Cell> maybe_sign(const td::Ref<vm::Cell>& msg, const td::Ed25519::PrivateKey* key);
|
||||
td::Ref<vm::CellSlice> maybe_ref(td::Ref<vm::Cell> msg);
|
||||
|
||||
struct MsgClose {
|
||||
td::uint64 extra_A{0};
|
||||
td::uint64 extra_B{0};
|
||||
td::Ref<vm::CellSlice> signed_promise;
|
||||
td::Ref<vm::Cell> serialize() const;
|
||||
};
|
||||
|
||||
struct MsgTimeout {
|
||||
td::Ref<vm::Cell> serialize() const;
|
||||
};
|
||||
|
||||
struct SignedPromise {
|
||||
Promise promise;
|
||||
td::optional<td::SecureString> o_signature;
|
||||
|
||||
bool unpack(td::Ref<vm::Cell> cell);
|
||||
static td::SecureString signature(const td::Ed25519::PrivateKey* key, const td::Ref<vm::Cell>& promise);
|
||||
static td::Ref<vm::Cell> create_and_serialize(td::Slice signature, const td::Ref<vm::Cell>& promise);
|
||||
static td::Ref<vm::Cell> create_and_serialize(const td::Ed25519::PrivateKey* key, const td::Ref<vm::Cell>& promise);
|
||||
};
|
||||
|
||||
struct StateInit {
|
||||
bool signed_A{false};
|
||||
bool signed_B{false};
|
||||
td::uint64 min_A{0};
|
||||
td::uint64 min_B{0};
|
||||
td::uint64 A{0};
|
||||
td::uint64 B{0};
|
||||
td::uint32 expire_at{0};
|
||||
|
||||
td::Ref<vm::Cell> serialize() const;
|
||||
};
|
||||
|
||||
struct StateClose {
|
||||
bool signed_A{false};
|
||||
bool signed_B{false};
|
||||
td::uint64 promise_A{0};
|
||||
td::uint64 promise_B{0};
|
||||
td::uint64 A{0};
|
||||
td::uint64 B{0};
|
||||
td::uint32 expire_at{0};
|
||||
};
|
||||
|
||||
struct StatePayout {
|
||||
td::uint64 A{0};
|
||||
td::uint64 B{0};
|
||||
};
|
||||
|
||||
struct Data {
|
||||
td::Ref<vm::Cell> config;
|
||||
td::Ref<vm::Cell> state;
|
||||
|
||||
static td::Ref<vm::Cell> init_state();
|
||||
|
||||
td::Ref<vm::Cell> serialize() const;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
struct MsgBuilder {
|
||||
td::Ed25519::PrivateKey* a_key{nullptr};
|
||||
td::Ed25519::PrivateKey* b_key{nullptr};
|
||||
|
||||
T&& with_a_key(td::Ed25519::PrivateKey* key) && {
|
||||
a_key = key;
|
||||
return static_cast<T&&>(*this);
|
||||
}
|
||||
T&& with_b_key(td::Ed25519::PrivateKey* key) && {
|
||||
b_key = key;
|
||||
return static_cast<T&&>(*this);
|
||||
}
|
||||
|
||||
td::Ref<vm::Cell> finalize() && {
|
||||
block::gen::ChanSignedMsg::Record rec;
|
||||
auto msg = static_cast<T&&>(*this).msg.serialize();
|
||||
rec.msg = vm::load_cell_slice_ref(msg);
|
||||
rec.sig_A = maybe_ref(maybe_sign(msg, a_key));
|
||||
rec.sig_B = maybe_ref(maybe_sign(msg, b_key));
|
||||
td::Ref<vm::Cell> res;
|
||||
CHECK(tlb::pack_cell(res, rec));
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
struct MsgInitBuilder : public MsgBuilder<MsgInitBuilder> {
|
||||
MsgInit msg;
|
||||
|
||||
MsgInitBuilder&& min_A(td::uint64 value) && {
|
||||
msg.min_A = value;
|
||||
return std::move(*this);
|
||||
}
|
||||
MsgInitBuilder&& min_B(td::uint64 value) && {
|
||||
msg.min_B = value;
|
||||
return std::move(*this);
|
||||
}
|
||||
MsgInitBuilder&& inc_A(td::uint64 value) && {
|
||||
msg.inc_A = value;
|
||||
return std::move(*this);
|
||||
}
|
||||
MsgInitBuilder&& inc_B(td::uint64 value) && {
|
||||
msg.inc_B = value;
|
||||
return std::move(*this);
|
||||
}
|
||||
MsgInitBuilder&& channel_id(td::uint64 value) && {
|
||||
msg.channel_id = value;
|
||||
return std::move(*this);
|
||||
}
|
||||
};
|
||||
|
||||
struct MsgTimeoutBuilder : public MsgBuilder<MsgTimeoutBuilder> {
|
||||
MsgTimeout msg;
|
||||
};
|
||||
|
||||
struct MsgCloseBuilder : public MsgBuilder<MsgCloseBuilder> {
|
||||
MsgClose msg;
|
||||
|
||||
MsgCloseBuilder&& extra_A(td::uint64 value) && {
|
||||
msg.extra_A = value;
|
||||
return std::move(*this);
|
||||
}
|
||||
MsgCloseBuilder&& extra_B(td::uint64 value) && {
|
||||
msg.extra_B = value;
|
||||
return std::move(*this);
|
||||
}
|
||||
MsgCloseBuilder&& signed_promise(td::Ref<vm::Cell> signed_promise) && {
|
||||
msg.signed_promise = vm::load_cell_slice_ref(signed_promise);
|
||||
return std::move(*this);
|
||||
}
|
||||
};
|
||||
|
||||
struct SignedPromiseBuilder {
|
||||
Promise promise;
|
||||
td::optional<td::SecureString> o_signature;
|
||||
td::Ed25519::PrivateKey* key{nullptr};
|
||||
|
||||
SignedPromiseBuilder& with_key(td::Ed25519::PrivateKey* key) {
|
||||
this->key = key;
|
||||
return *this;
|
||||
}
|
||||
SignedPromiseBuilder& promise_A(td::uint64 value) {
|
||||
promise.promise_A = value;
|
||||
return *this;
|
||||
}
|
||||
SignedPromiseBuilder& promise_B(td::uint64 value) {
|
||||
promise.promise_B = value;
|
||||
return *this;
|
||||
}
|
||||
SignedPromiseBuilder& channel_id(td::uint64 value) {
|
||||
promise.channel_id = value;
|
||||
return *this;
|
||||
}
|
||||
SignedPromiseBuilder& signature(td::SecureString signature) {
|
||||
o_signature = std::move(signature);
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool check_signature(td::Slice signature, const td::Ed25519::PublicKey& pk) {
|
||||
return pk.verify_signature(promise.serialize()->get_hash().as_slice(), signature).is_ok();
|
||||
}
|
||||
td::SecureString calc_signature() {
|
||||
CHECK(key);
|
||||
return SignedPromise::signature(key, promise.serialize());
|
||||
}
|
||||
td::Ref<vm::Cell> finalize() {
|
||||
if (o_signature) {
|
||||
return SignedPromise::create_and_serialize(o_signature.value().copy(), promise.serialize());
|
||||
} else {
|
||||
return SignedPromise::create_and_serialize(key, promise.serialize());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace pchan
|
||||
|
||||
class PaymentChannel : public SmartContract {
|
||||
public:
|
||||
PaymentChannel(State state) : SmartContract(std::move(state)) {
|
||||
}
|
||||
|
||||
struct Info {
|
||||
pchan::Config config;
|
||||
td::Variant<pchan::StateInit, pchan::StateClose, pchan::StatePayout> state;
|
||||
std::string description;
|
||||
};
|
||||
td::Result<Info> get_info() const;
|
||||
|
||||
static td::Ref<PaymentChannel> create(State state) {
|
||||
return td::Ref<PaymentChannel>(true, std::move(state));
|
||||
}
|
||||
static td::optional<td::int32> guess_revision(const vm::Cell::Hash& code_hash);
|
||||
static td::Ref<PaymentChannel> create(const pchan::Config& config, td::int32 revision) {
|
||||
State state;
|
||||
state.code = SmartContractCode::get_code(SmartContractCode::PaymentChannel, revision);
|
||||
pchan::Data data;
|
||||
data.config = config.serialize();
|
||||
pchan::StateInit init;
|
||||
data.state = init.serialize();
|
||||
state.data = data.serialize();
|
||||
return create(std::move(state));
|
||||
}
|
||||
};
|
||||
} // namespace ton
|
|
@ -32,19 +32,19 @@
|
|||
namespace ton {
|
||||
namespace {
|
||||
|
||||
td::Ref<vm::Stack> prepare_vm_stack(td::Ref<vm::CellSlice> body) {
|
||||
td::Ref<vm::Stack> prepare_vm_stack(td::RefInt256 amount, 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::make_refint(10000000000));
|
||||
stack.push_int(td::make_refint(10000000000));
|
||||
stack.push_int(std::move(amount));
|
||||
stack.push_cell(vm::CellBuilder().finalize());
|
||||
stack.push_cellslice(std::move(body));
|
||||
return stack_ref;
|
||||
}
|
||||
|
||||
td::Ref<vm::Tuple> prepare_vm_c7(td::uint32 now) {
|
||||
td::Ref<vm::Tuple> prepare_vm_c7(td::uint32 now, td::uint64 balance) {
|
||||
// TODO: fix initialization of c7
|
||||
td::BitArray<256> rand_seed;
|
||||
rand_seed.as_slice().fill(0);
|
||||
|
@ -58,7 +58,7 @@ td::Ref<vm::Tuple> prepare_vm_c7(td::uint32 now) {
|
|||
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)]
|
||||
block::CurrencyCollection(balance).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;
|
||||
|
@ -66,6 +66,15 @@ td::Ref<vm::Tuple> prepare_vm_c7(td::uint32 now) {
|
|||
return vm::make_tuple_ref(std::move(tuple));
|
||||
}
|
||||
|
||||
static int output_actions_count(td::Ref<vm::Cell> list) {
|
||||
int i = -1;
|
||||
do {
|
||||
++i;
|
||||
list = load_cell_slice(std::move(list)).prefetch_ref();
|
||||
} while (list.not_null());
|
||||
return i;
|
||||
}
|
||||
|
||||
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;
|
||||
|
@ -123,6 +132,8 @@ SmartContract::Answer run_smartcont(SmartContract::State state, td::Ref<vm::Stac
|
|||
if (res.success) {
|
||||
res.new_state.data = vm.get_c4();
|
||||
res.actions = vm.get_d(5);
|
||||
LOG(DEBUG) << "output actions:\n"
|
||||
<< block::gen::OutList{output_actions_count(res.actions)}.as_string_ref(res.actions);
|
||||
}
|
||||
LOG_IF(ERROR, gas_credit != 0 && (res.accepted && !res.success))
|
||||
<< "Accepted but failed with code " << res.code << "\n"
|
||||
|
@ -171,10 +182,13 @@ SmartContract::Answer SmartContract::run_method(Args args) {
|
|||
now = args.now.unwrap();
|
||||
}
|
||||
if (!args.c7) {
|
||||
args.c7 = prepare_vm_c7(now);
|
||||
args.c7 = prepare_vm_c7(now, args.balance);
|
||||
}
|
||||
if (!args.limits) {
|
||||
args.limits = vm::GasLimits{(long long)0, (long long)1000000, (long long)10000};
|
||||
bool is_internal = args.get_method_id().ok() == 0;
|
||||
|
||||
args.limits = vm::GasLimits{is_internal ? (long long)args.amount * 1000 : (long long)0, (long long)1000000,
|
||||
is_internal ? 0 : (long long)10000};
|
||||
}
|
||||
CHECK(args.stack);
|
||||
CHECK(args.method_id);
|
||||
|
@ -191,7 +205,7 @@ SmartContract::Answer SmartContract::run_get_method(Args args) const {
|
|||
now = args.now.unwrap();
|
||||
}
|
||||
if (!args.c7) {
|
||||
args.c7 = prepare_vm_c7(now);
|
||||
args.c7 = prepare_vm_c7(now, args.balance);
|
||||
}
|
||||
if (!args.limits) {
|
||||
args.limits = vm::GasLimits{1000000};
|
||||
|
@ -209,6 +223,11 @@ SmartContract::Answer SmartContract::run_get_method(td::Slice method, Args args)
|
|||
}
|
||||
|
||||
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));
|
||||
return run_method(
|
||||
args.set_stack(prepare_vm_stack(td::make_refint(0), vm::load_cell_slice_ref(cell))).set_method_id(-1));
|
||||
}
|
||||
SmartContract::Answer SmartContract::send_internal_message(td::Ref<vm::Cell> cell, Args args) {
|
||||
return run_method(
|
||||
args.set_stack(prepare_vm_stack(td::make_refint(args.amount), vm::load_cell_slice_ref(cell))).set_method_id(0));
|
||||
}
|
||||
} // namespace ton
|
||||
|
|
|
@ -57,6 +57,8 @@ class SmartContract : public td::CntObject {
|
|||
td::optional<td::Ref<vm::Stack>> stack;
|
||||
td::optional<td::int32> now;
|
||||
bool ignore_chksig{false};
|
||||
td::uint64 amount{0};
|
||||
td::uint64 balance{0};
|
||||
|
||||
Args() {
|
||||
}
|
||||
|
@ -95,6 +97,14 @@ class SmartContract : public td::CntObject {
|
|||
this->ignore_chksig = ignore_chksig;
|
||||
return std::move(*this);
|
||||
}
|
||||
Args&& set_amount(td::uint64 amount) {
|
||||
this->amount = amount;
|
||||
return std::move(*this);
|
||||
}
|
||||
Args&& set_balance(td::uint64 balance) {
|
||||
this->balance = balance;
|
||||
return std::move(*this);
|
||||
}
|
||||
|
||||
td::Result<td::int32> get_method_id() const {
|
||||
if (!method_id) {
|
||||
|
@ -109,6 +119,7 @@ class SmartContract : public td::CntObject {
|
|||
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 = {});
|
||||
Answer send_internal_message(td::Ref<vm::Cell> cell, Args args = {});
|
||||
|
||||
size_t code_size() const;
|
||||
size_t data_size() const;
|
||||
|
@ -122,6 +133,9 @@ class SmartContract : public td::CntObject {
|
|||
const State& get_state() const {
|
||||
return state_;
|
||||
}
|
||||
CntObject* make_copy() const override {
|
||||
return new SmartContract(state_);
|
||||
}
|
||||
|
||||
protected:
|
||||
State state_;
|
||||
|
|
|
@ -44,6 +44,8 @@ const auto& get_map() {
|
|||
#include "smartcont/auto/highload-wallet-code.cpp"
|
||||
#include "smartcont/auto/highload-wallet-v2-code.cpp"
|
||||
#include "smartcont/auto/dns-manual-code.cpp"
|
||||
#include "smartcont/auto/payment-channel-code.cpp"
|
||||
#include "smartcont/auto/restricted-wallet3-code.cpp"
|
||||
|
||||
with_tvm_code("highload-wallet-r1",
|
||||
"te6ccgEBBgEAhgABFP8A9KQT9KDyyAsBAgEgAgMCAUgEBQC88oMI1xgg0x/TH9Mf+CMTu/Jj7UTQ0x/TH9P/"
|
||||
|
@ -96,6 +98,14 @@ const auto& get_map() {
|
|||
"FwCEMQLTAAHAAZPUAdCY0wUBqgLXGAHiINdJwg/"
|
||||
"ypiB41yLXCwfyaHBTEddJqTYCmNMHAcAAEqEB5DDIywYBzxbJ0FADACBZ9KhvpSCUAvQEMJIybeICACg0A4AQ9FqZECOECUBE8AEBkjAx4gBmM"
|
||||
"SLAFZwy9AQQI4QJUELwAQHgIsAWmDIChAn0czAB4DAyIMAfkzD0BODAIJJtAeDyLG0B");
|
||||
with_tvm_code(
|
||||
"restricted-wallet3-r1",
|
||||
"te6ccgECEgEAAUsAART/APSkE/S88sgLAQIBIAIDAgFIBAUD+PKDCNcYINMf0x/THwL4I7vyY+1E0NMf0x/T/"
|
||||
"1NDuvKhUWK68qIG+QFUEHb5EPKkAY4fMwHT/9EB0x/0BNH4AAOkyMsfFMsfy/8Syx/0AMntVOEC0x/"
|
||||
"0BNH4ACH4I9s8IYAg9HtvpTGW+gAwcvsCkTDiApMg10qK6NECpMgPEBEABNAwAgEgBgcCASAICQIBSAwNAgFuCgsAEbjJftRNDXCx+"
|
||||
"AAXrc52omhpn5jrhf/AABesePaiaGmPmOuFj8ABDbbYHwR7Z5AOAQm1B1tnkA4BTu1E0IEBQNch0x/"
|
||||
"0BNEC2zz4J28QAoAg9HtvpTGX+gAwoXC2CZEw4g8AOiGOETGA8/gzIG6SMHCU0NcLH+IB3yGSAaGSW3/iAAzTB9QC+wAAHssfFMsfEsv/yx/"
|
||||
"0AMntVA==");
|
||||
return map;
|
||||
}();
|
||||
return map;
|
||||
|
@ -103,7 +113,6 @@ const auto& get_map() {
|
|||
} // namespace
|
||||
|
||||
td::Result<td::Ref<vm::Cell>> SmartContractCode::load(td::Slice name) {
|
||||
LOG(ERROR) << "LOAD " << name;
|
||||
auto& map = get_map();
|
||||
auto it = map.find(name);
|
||||
if (it == map.end()) {
|
||||
|
@ -146,6 +155,14 @@ td::Span<int> SmartContractCode::get_revisions(Type type) {
|
|||
static int res[] = {-1, 1};
|
||||
return res;
|
||||
}
|
||||
case Type::PaymentChannel: {
|
||||
static int res[] = {-1};
|
||||
return res;
|
||||
}
|
||||
case Type::RestrictedWallet: {
|
||||
static int res[] = {-1, 1};
|
||||
return res;
|
||||
}
|
||||
}
|
||||
UNREACHABLE();
|
||||
return {};
|
||||
|
@ -190,6 +207,10 @@ td::Ref<vm::Cell> SmartContractCode::get_code(Type type, int ext_revision) {
|
|||
return "multisig";
|
||||
case Type::ManualDns:
|
||||
return "dns-manual";
|
||||
case Type::PaymentChannel:
|
||||
return "payment-channel";
|
||||
case Type::RestrictedWallet:
|
||||
return "restricted-wallet3";
|
||||
}
|
||||
UNREACHABLE();
|
||||
return "";
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
|
||||
Copyright 2017-2020 Telegram Systems LLP
|
||||
*/
|
||||
#pragma once
|
||||
#include "vm/cells.h"
|
||||
|
||||
#include "td/utils/Span.h"
|
||||
|
@ -25,7 +26,18 @@ class SmartContractCode {
|
|||
public:
|
||||
static td::Result<td::Ref<vm::Cell>> load(td::Slice name);
|
||||
|
||||
enum Type { WalletV1 = 1, WalletV1Ext, WalletV2, WalletV3, HighloadWalletV1, HighloadWalletV2, ManualDns, Multisig };
|
||||
enum Type {
|
||||
WalletV1 = 1,
|
||||
WalletV1Ext,
|
||||
WalletV2,
|
||||
WalletV3,
|
||||
HighloadWalletV1,
|
||||
HighloadWalletV2,
|
||||
ManualDns,
|
||||
Multisig,
|
||||
PaymentChannel,
|
||||
RestrictedWallet
|
||||
};
|
||||
static td::Span<int> get_revisions(Type type);
|
||||
static td::Result<int> validate_revision(Type type, int revision);
|
||||
static td::Ref<vm::Cell> get_code(Type type, int revision = 0);
|
||||
|
|
|
@ -43,12 +43,7 @@ td::Ref<vm::Cell> TestGiver::make_a_gift_message_static(td::uint32 seqno, td::Sp
|
|||
|
||||
for (auto& gift : gifts) {
|
||||
td::int32 send_mode = 1;
|
||||
auto gramms = gift.gramms;
|
||||
vm::CellBuilder cbi;
|
||||
GenericAccount::store_int_message(cbi, gift.destination, gramms);
|
||||
store_gift_message(cbi, gift);
|
||||
auto message_inner = cbi.finalize();
|
||||
cb.store_long(send_mode, 8).store_ref(std::move(message_inner));
|
||||
cb.store_long(send_mode, 8).store_ref(create_int_message(gift));
|
||||
}
|
||||
|
||||
return cb.finalize();
|
||||
|
|
|
@ -46,18 +46,11 @@ td::Ref<vm::Cell> TestWallet::make_a_gift_message_static(const td::Ed25519::Priv
|
|||
|
||||
for (auto& gift : gifts) {
|
||||
td::int32 send_mode = 3;
|
||||
auto gramms = gift.gramms;
|
||||
if (gramms == -1) {
|
||||
gramms = 0;
|
||||
if (gift.gramms == -1) {
|
||||
send_mode += 128;
|
||||
}
|
||||
vm::CellBuilder cbi;
|
||||
GenericAccount::store_int_message(cbi, gift.destination, gramms);
|
||||
store_gift_message(cbi, gift);
|
||||
auto message_inner = cbi.finalize();
|
||||
cb.store_long(send_mode, 8).store_ref(std::move(message_inner));
|
||||
cb.store_long(send_mode, 8).store_ref(create_int_message(gift));
|
||||
}
|
||||
|
||||
auto message_outer = cb.finalize();
|
||||
auto signature = private_key.sign(message_outer->get_hash().as_slice()).move_as_ok();
|
||||
return vm::CellBuilder().store_bytes(signature).append_cellslice(vm::load_cell_slice(message_outer)).finalize();
|
||||
|
|
|
@ -52,16 +52,10 @@ td::Ref<vm::Cell> Wallet::make_a_gift_message(const td::Ed25519::PrivateKey& pri
|
|||
|
||||
for (auto& gift : gifts) {
|
||||
td::int32 send_mode = 3;
|
||||
auto gramms = gift.gramms;
|
||||
if (gramms == -1) {
|
||||
gramms = 0;
|
||||
if (gift.gramms == -1) {
|
||||
send_mode += 128;
|
||||
}
|
||||
vm::CellBuilder cbi;
|
||||
GenericAccount::store_int_message(cbi, gift.destination, gramms);
|
||||
store_gift_message(cbi, gift);
|
||||
auto message_inner = cbi.finalize();
|
||||
cb.store_long(send_mode, 8).store_ref(std::move(message_inner));
|
||||
cb.store_long(send_mode, 8).store_ref(create_int_message(gift));
|
||||
}
|
||||
|
||||
auto message_outer = cb.finalize();
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include "vm/cells/CellString.h"
|
||||
|
||||
#include "SmartContract.h"
|
||||
#include "GenericAccount.h"
|
||||
|
||||
namespace ton {
|
||||
class WalletInterface {
|
||||
|
@ -36,6 +37,7 @@ class WalletInterface {
|
|||
std::string message;
|
||||
|
||||
td::Ref<vm::Cell> body;
|
||||
td::Ref<vm::Cell> init_state;
|
||||
};
|
||||
|
||||
virtual ~WalletInterface() {
|
||||
|
@ -48,15 +50,29 @@ class WalletInterface {
|
|||
return td::Status::Error("Unsupported");
|
||||
}
|
||||
|
||||
td::Result<td::Ref<vm::Cell>> get_init_message(const td::Ed25519::PrivateKey &private_key,
|
||||
td::uint32 valid_until = std::numeric_limits<td::uint32>::max()) {
|
||||
td::Result<td::Ref<vm::Cell>> get_init_message(
|
||||
const td::Ed25519::PrivateKey &private_key,
|
||||
td::uint32 valid_until = std::numeric_limits<td::uint32>::max()) const {
|
||||
return make_a_gift_message(private_key, valid_until, {});
|
||||
}
|
||||
static td::Ref<vm::Cell> create_int_message(const Gift &gift) {
|
||||
vm::CellBuilder cbi;
|
||||
GenericAccount::store_int_message(cbi, gift.destination, gift.gramms < 0 ? 0 : gift.gramms);
|
||||
if (gift.init_state.not_null()) {
|
||||
cbi.store_ones(2);
|
||||
cbi.store_ref(gift.init_state);
|
||||
} else {
|
||||
cbi.store_zeroes(1);
|
||||
}
|
||||
cbi.store_zeroes(1);
|
||||
store_gift_message(cbi, gift);
|
||||
return cbi.finalize();
|
||||
}
|
||||
static void store_gift_message(vm::CellBuilder &cb, const Gift &gift) {
|
||||
if (gift.body.not_null()) {
|
||||
auto body = vm::load_cell_slice(gift.body);
|
||||
//TODO: handle error
|
||||
cb.append_cellslice_bool(body);
|
||||
CHECK(cb.append_cellslice_bool(body));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -62,16 +62,10 @@ td::Ref<vm::Cell> WalletV3::make_a_gift_message(const td::Ed25519::PrivateKey& p
|
|||
|
||||
for (auto& gift : gifts) {
|
||||
td::int32 send_mode = 3;
|
||||
auto gramms = gift.gramms;
|
||||
if (gramms == -1) {
|
||||
gramms = 0;
|
||||
if (gift.gramms == -1) {
|
||||
send_mode += 128;
|
||||
}
|
||||
vm::CellBuilder cbi;
|
||||
GenericAccount::store_int_message(cbi, gift.destination, gramms);
|
||||
store_gift_message(cbi, gift);
|
||||
auto message_inner = cbi.finalize();
|
||||
cb.store_long(send_mode, 8).store_ref(std::move(message_inner));
|
||||
cb.store_long(send_mode, 8).store_ref(create_int_message(gift));
|
||||
}
|
||||
|
||||
auto message_outer = cb.finalize();
|
||||
|
|
|
@ -70,3 +70,209 @@ class WalletV3 : public ton::SmartContract, public WalletInterface {
|
|||
td::Result<td::Ed25519::PublicKey> get_public_key_or_throw() const;
|
||||
};
|
||||
} // namespace ton
|
||||
|
||||
#include "smc-envelope/SmartContractCode.h"
|
||||
#include "smc-envelope/GenericAccount.h"
|
||||
#include "block/block-parse.h"
|
||||
#include <algorithm>
|
||||
namespace ton {
|
||||
template <class WalletT, class TraitsT>
|
||||
class WalletBase : public SmartContract, public WalletInterface {
|
||||
public:
|
||||
using Traits = TraitsT;
|
||||
using InitData = typename Traits::InitData;
|
||||
|
||||
explicit WalletBase(State state) : SmartContract(std::move(state)) {
|
||||
}
|
||||
static td::Ref<WalletT> create(State state) {
|
||||
return td::Ref<WalletT>(true, std::move(state));
|
||||
}
|
||||
static td::Ref<vm::Cell> get_init_code(int revision) {
|
||||
return SmartContractCode::get_code(get_code_type(), revision);
|
||||
};
|
||||
size_t get_max_gifts_size() const override {
|
||||
return Traits::max_gifts_size;
|
||||
}
|
||||
static SmartContractCode::Type get_code_type() {
|
||||
return Traits::code_type;
|
||||
}
|
||||
static td::optional<td::int32> guess_revision(const vm::Cell::Hash& code_hash) {
|
||||
for (auto i : ton::SmartContractCode::get_revisions(get_code_type())) {
|
||||
auto code = SmartContractCode::get_code(get_code_type(), i);
|
||||
if (code->get_hash() == code_hash) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
static td::Ref<WalletT> create(const InitData& init_data, int revision) {
|
||||
return td::Ref<WalletT>(true, State{get_init_code(revision), WalletT::get_init_data(init_data)});
|
||||
}
|
||||
|
||||
td::Result<td::uint32> get_seqno() const {
|
||||
return TRY_VM([&]() -> td::Result<td::uint32> {
|
||||
Answer answer = this->run_get_method("seqno");
|
||||
if (!answer.success) {
|
||||
return td::Status::Error("seqno get method failed");
|
||||
}
|
||||
return static_cast<td::uint32>(answer.stack.write().pop_long_range(std::numeric_limits<td::uint32>::max()));
|
||||
}());
|
||||
}
|
||||
td::Result<td::uint32> get_wallet_id() const {
|
||||
return TRY_VM([&]() -> td::Result<td::uint32> {
|
||||
Answer answer = this->run_get_method("wallet_id");
|
||||
if (!answer.success) {
|
||||
return td::Status::Error("seqno get method failed");
|
||||
}
|
||||
return static_cast<td::uint32>(answer.stack.write().pop_long_range(std::numeric_limits<td::uint32>::max()));
|
||||
}());
|
||||
}
|
||||
|
||||
td::Result<td::uint64> get_balance(td::uint64 account_balance, td::uint32 now) const {
|
||||
return TRY_VM([&]() -> td::Result<td::uint64> {
|
||||
Answer answer = this->run_get_method(Args().set_method_id("balance").set_balance(account_balance).set_now(now));
|
||||
if (!answer.success) {
|
||||
return td::Status::Error("balance get method failed");
|
||||
}
|
||||
return static_cast<td::uint64>(answer.stack.write().pop_long());
|
||||
}());
|
||||
}
|
||||
|
||||
td::Result<td::Ed25519::PublicKey> get_public_key() const override {
|
||||
return TRY_VM([&]() -> td::Result<td::Ed25519::PublicKey> {
|
||||
Answer answer = this->run_get_method("get_public_key");
|
||||
if (!answer.success) {
|
||||
return td::Status::Error("get_public_key get method failed");
|
||||
}
|
||||
auto key_int = answer.stack.write().pop_int();
|
||||
LOG(ERROR) << key_int->bit_size(false);
|
||||
td::SecureString bytes(32);
|
||||
if (!key_int->export_bytes(bytes.as_mutable_slice().ubegin(), bytes.size(), false)) {
|
||||
return td::Status::Error("not a public key");
|
||||
}
|
||||
return td::Ed25519::PublicKey(std::move(bytes));
|
||||
}());
|
||||
};
|
||||
};
|
||||
|
||||
struct RestrictedWalletTraits {
|
||||
struct InitData {
|
||||
td::SecureString init_key;
|
||||
td::SecureString main_key;
|
||||
td::uint32 wallet_id{0};
|
||||
};
|
||||
|
||||
static constexpr unsigned max_message_size = vm::CellString::max_bytes;
|
||||
static constexpr unsigned max_gifts_size = 4;
|
||||
static constexpr auto code_type = SmartContractCode::RestrictedWallet;
|
||||
};
|
||||
|
||||
class RestrictedWallet : public WalletBase<RestrictedWallet, RestrictedWalletTraits> {
|
||||
public:
|
||||
struct Config {
|
||||
td::uint32 start_at{0};
|
||||
std::vector<std::pair<td::int32, td::uint64>> limits;
|
||||
};
|
||||
|
||||
explicit RestrictedWallet(State state) : WalletBase(std::move(state)) {
|
||||
}
|
||||
|
||||
td::Result<Config> get_config() const {
|
||||
return TRY_VM([this]() -> td::Result<Config> {
|
||||
auto cs = vm::load_cell_slice(get_state().data);
|
||||
Config config;
|
||||
td::Ref<vm::Cell> dict_root;
|
||||
auto ok = cs.advance(32 + 32 + 256) && cs.fetch_uint_to(32, config.start_at) && cs.fetch_maybe_ref(dict_root);
|
||||
vm::Dictionary dict(std::move(dict_root), 32);
|
||||
dict.check_for_each([&](auto cs, auto ptr, auto ptr_bits) {
|
||||
auto r_seconds = td::narrow_cast_safe<td::int32>(dict.key_as_integer(ptr, true)->to_long());
|
||||
if (r_seconds.is_error()) {
|
||||
ok = false;
|
||||
return ok;
|
||||
}
|
||||
td::uint64 value;
|
||||
ok &= smc::unpack_grams(cs, value);
|
||||
config.limits.emplace_back(r_seconds.ok(), value);
|
||||
return ok;
|
||||
});
|
||||
if (!ok) {
|
||||
return td::Status::Error("Can't parse config");
|
||||
}
|
||||
std::sort(config.limits.begin(), config.limits.end());
|
||||
return config;
|
||||
}());
|
||||
}
|
||||
|
||||
static td::Ref<vm::Cell> get_init_data(const InitData& init_data) {
|
||||
vm::CellBuilder cb;
|
||||
cb.store_long(0, 32);
|
||||
cb.store_long(init_data.wallet_id, 32);
|
||||
CHECK(init_data.init_key.size() == 32);
|
||||
CHECK(init_data.main_key.size() == 32);
|
||||
cb.store_bytes(init_data.init_key.as_slice());
|
||||
cb.store_bytes(init_data.main_key.as_slice());
|
||||
return cb.finalize();
|
||||
}
|
||||
|
||||
td::Result<td::Ref<vm::Cell>> get_init_message(const td::Ed25519::PrivateKey& init_private_key,
|
||||
td::uint32 valid_until, const Config& config) const {
|
||||
vm::CellBuilder cb;
|
||||
TRY_RESULT(seqno, get_seqno());
|
||||
TRY_RESULT(wallet_id, get_wallet_id());
|
||||
LOG(ERROR) << "seqno: " << seqno << " wallet_id: " << wallet_id;
|
||||
if (seqno != 0) {
|
||||
return td::Status::Error("Wallet is already inited");
|
||||
}
|
||||
|
||||
cb.store_long(wallet_id, 32);
|
||||
cb.store_long(valid_until, 32);
|
||||
cb.store_long(seqno, 32);
|
||||
|
||||
cb.store_long(config.start_at, 32);
|
||||
vm::Dictionary dict(32);
|
||||
|
||||
auto add = [&](td::int32 till, td::uint64 value) {
|
||||
auto key = dict.integer_key(td::make_refint(till), 32, true);
|
||||
vm::CellBuilder gcb;
|
||||
block::tlb::t_Grams.store_integer_value(gcb, td::BigInt256(value));
|
||||
dict.set_builder(key.bits(), 32, gcb);
|
||||
};
|
||||
for (auto limit : config.limits) {
|
||||
add(limit.first, limit.second);
|
||||
}
|
||||
cb.store_maybe_ref(dict.get_root_cell());
|
||||
|
||||
auto message_outer = cb.finalize();
|
||||
auto signature = init_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::Result<td::Ref<vm::Cell>> make_a_gift_message(const td::Ed25519::PrivateKey& private_key, td::uint32 valid_until,
|
||||
td::Span<Gift> gifts) const override {
|
||||
CHECK(gifts.size() <= Traits::max_gifts_size);
|
||||
|
||||
vm::CellBuilder cb;
|
||||
TRY_RESULT(seqno, get_seqno());
|
||||
TRY_RESULT(wallet_id, get_wallet_id());
|
||||
if (seqno == 0) {
|
||||
return td::Status::Error("Wallet is not inited yet");
|
||||
}
|
||||
cb.store_long(wallet_id, 32);
|
||||
cb.store_long(valid_until, 32);
|
||||
cb.store_long(seqno, 32);
|
||||
|
||||
for (auto& gift : gifts) {
|
||||
td::int32 send_mode = 3;
|
||||
if (gift.gramms == -1) {
|
||||
send_mode += 128;
|
||||
}
|
||||
cb.store_long(send_mode, 8).store_ref(create_int_message(gift));
|
||||
}
|
||||
|
||||
auto message_outer = cb.finalize();
|
||||
auto signature = private_key.sign(message_outer->get_hash().as_slice()).move_as_ok();
|
||||
return vm::CellBuilder().store_bytes(signature).append_cellslice(vm::load_cell_slice(message_outer)).finalize();
|
||||
}
|
||||
};
|
||||
} // namespace ton
|
||||
|
|
|
@ -1542,6 +1542,7 @@ template <class DeserializerT>
|
|||
class BenchBocDeserializer : public td::Benchmark {
|
||||
public:
|
||||
BenchBocDeserializer(std::string name, BenchBocDeserializerConfig config) : name_(std::move(name)), config_(config) {
|
||||
td::PerfWarningTimer perf("A", 1);
|
||||
fast_array_ = vm::FastCompactArray(array_size);
|
||||
td::Random::Xorshift128plus rnd{123};
|
||||
for (td::uint32 i = 0; i < array_size; i++) {
|
||||
|
|
|
@ -21,7 +21,9 @@
|
|||
|
||||
#include "Ed25519.h"
|
||||
|
||||
#include "block/block-auto.h"
|
||||
#include "block/block.h"
|
||||
#include "block/block-parse.h"
|
||||
|
||||
#include "fift/Fift.h"
|
||||
#include "fift/words.h"
|
||||
|
@ -38,6 +40,7 @@
|
|||
#include "smc-envelope/WalletV3.h"
|
||||
#include "smc-envelope/HighloadWallet.h"
|
||||
#include "smc-envelope/HighloadWalletV2.h"
|
||||
#include "smc-envelope/PaymentChannel.h"
|
||||
|
||||
#include "td/utils/base64.h"
|
||||
#include "td/utils/crypto.h"
|
||||
|
@ -489,6 +492,99 @@ TEST(Tonlib, TestGiver) {
|
|||
CHECK(vm::std_boc_deserialize(wallet_query).move_as_ok()->get_hash() == res->get_hash());
|
||||
}
|
||||
|
||||
TEST(Tonlib, RestrictedWallet) {
|
||||
//auto source_lookup = fift::create_mem_source_lookup(load_source("smartcont/new-restricted-wallet2.fif")).move_as_ok();
|
||||
//source_lookup
|
||||
//.write_file("/auto/restricted-wallet2-code.fif", load_source("smartcont/auto/restricted-wallet2-code.fif"))
|
||||
//.ensure();
|
||||
//class ZeroOsTime : public fift::OsTime {
|
||||
//public:
|
||||
//td::uint32 now() override {
|
||||
//return 0;
|
||||
//}
|
||||
//};
|
||||
//source_lookup.set_os_time(std::make_unique<ZeroOsTime>());
|
||||
//auto priv_key = td::Ed25519::generate_private_key().move_as_ok();
|
||||
//auto pub_key = priv_key.get_public_key().move_as_ok();
|
||||
//auto pub_key_serialized = block::PublicKey::from_bytes(pub_key.as_octet_string()).move_as_ok().serialize(true);
|
||||
|
||||
//std::vector<std::string> args = {"path", pub_key_serialized, std::string("100")};
|
||||
//auto fift_output = fift::mem_run_fift(std::move(source_lookup), args).move_as_ok();
|
||||
|
||||
//ton::RestrictedWallet::InitData init_data;
|
||||
//td::uint64 x = 100 * 1000000000ull;
|
||||
//init_data.key = &pub_key;
|
||||
//init_data.start_at = 0;
|
||||
//init_data.limits = {{-32768, x}, {92, x * 3 / 4}, {183, x * 1 / 2}, {366, x * 1 / 4}, {548, 0}};
|
||||
//auto wallet = ton::RestrictedWallet::create(init_data, -1);
|
||||
|
||||
//ASSERT_EQ(0u, wallet->get_seqno().move_as_ok());
|
||||
//CHECK(pub_key.as_octet_string() == wallet->get_public_key().move_as_ok().as_octet_string());
|
||||
////LOG(ERROR) << wallet->get_balance(x, 60 * 60 * 24 * 400).move_as_ok();
|
||||
|
||||
//auto new_wallet_query = fift_output.source_lookup.read_file("rwallet-query.boc").move_as_ok().data;
|
||||
//auto new_wallet_addr = fift_output.source_lookup.read_file("rwallet.addr").move_as_ok().data;
|
||||
|
||||
//auto address = wallet->get_address(-1);
|
||||
////CHECK(address.addr.as_slice() == td::Slice(new_wallet_addr).substr(0, 32));
|
||||
//address.bounceable = false;
|
||||
//auto res = ton::GenericAccount::create_ext_message(address, wallet->get_init_state(),
|
||||
//wallet->get_init_message(priv_key).move_as_ok());
|
||||
//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());
|
||||
|
||||
//auto dest = block::StdAddress::parse("Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX").move_as_ok();
|
||||
//fift_output.source_lookup.write_file("/main.fif", load_source("smartcont/wallet-v2.fif")).ensure();
|
||||
//fift_output.source_lookup.write_file("rwallet.pk", priv_key.as_octet_string().as_slice()).ensure();
|
||||
//fift_output = fift::mem_run_fift(
|
||||
//std::move(fift_output.source_lookup),
|
||||
//{"aba", "rwallet", "-C", "TESTv2", "Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX", "0", "321"})
|
||||
//.move_as_ok();
|
||||
//auto wallet_query = fift_output.source_lookup.read_file("wallet-query.boc").move_as_ok().data;
|
||||
//ton::TestWallet::Gift gift;
|
||||
//gift.destination = dest;
|
||||
//gift.message = "TESTv2";
|
||||
//gift.gramms = 321000000000ll;
|
||||
////CHECK(priv_key.get_public_key().ok().as_octet_string() == wallet->get_public_key().ok().as_octet_string());
|
||||
//auto gift_message = ton::GenericAccount::create_ext_message(
|
||||
//address, {}, wallet->make_a_gift_message(priv_key, 60, {gift}).move_as_ok());
|
||||
//LOG(ERROR) << "-------";
|
||||
//vm::load_cell_slice(gift_message).print_rec(std::cerr);
|
||||
//LOG(ERROR) << "-------";
|
||||
//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, RestrictedWallet3) {
|
||||
auto init_priv_key = td::Ed25519::generate_private_key().move_as_ok();
|
||||
auto init_pub_key = init_priv_key.get_public_key().move_as_ok();
|
||||
auto priv_key = td::Ed25519::generate_private_key().move_as_ok();
|
||||
auto pub_key = priv_key.get_public_key().move_as_ok();
|
||||
|
||||
ton::RestrictedWallet::InitData init_data;
|
||||
init_data.init_key = init_pub_key.as_octet_string();
|
||||
init_data.main_key = pub_key.as_octet_string();
|
||||
init_data.wallet_id = 123;
|
||||
auto wallet = ton::RestrictedWallet::create(init_data, 1);
|
||||
|
||||
auto address = wallet->get_address();
|
||||
|
||||
td::uint64 x = 100 * 1000000000ull;
|
||||
ton::RestrictedWallet::Config config;
|
||||
config.start_at = 1;
|
||||
config.limits = {{-32768, x}, {92, x * 3 / 4}, {183, x * 1 / 2}, {366, x * 1 / 4}, {548, 0}};
|
||||
CHECK(wallet.write().send_external_message(wallet->get_init_message(init_priv_key, 10, config).move_as_ok()).success);
|
||||
CHECK(wallet->get_seqno().move_as_ok() == 1);
|
||||
|
||||
ton::WalletInterface::Gift gift;
|
||||
gift.destination = address;
|
||||
gift.message = "hello";
|
||||
CHECK(wallet.write().send_external_message(wallet->make_a_gift_message(priv_key, 10, {gift}).move_as_ok()).success);
|
||||
CHECK(wallet->get_seqno().move_as_ok() == 2);
|
||||
}
|
||||
|
||||
class SimpleWallet : public ton::SmartContract {
|
||||
public:
|
||||
SimpleWallet(State state) : SmartContract(std::move(state)) {
|
||||
|
@ -1286,3 +1382,397 @@ TEST(Smartcont, DnsManual) {
|
|||
// TODO: rethink semantic of creating an empty dictionary
|
||||
do_dns_test(CheckedDns(true, true));
|
||||
}
|
||||
|
||||
using namespace ton::pchan;
|
||||
|
||||
template <class T>
|
||||
struct ValidateState {
|
||||
T& self() {
|
||||
return static_cast<T&>(*this);
|
||||
}
|
||||
|
||||
void init(td::Ref<vm::Cell> state) {
|
||||
state_ = state;
|
||||
block::gen::ChanData::Record data_rec;
|
||||
if (!tlb::unpack_cell(state, data_rec)) {
|
||||
on_fatal_error(td::Status::Error("Expected Data"));
|
||||
return;
|
||||
}
|
||||
if (!tlb::unpack_cell(data_rec.state, self().rec)) {
|
||||
on_fatal_error(td::Status::Error("Expected StatePayout"));
|
||||
return;
|
||||
}
|
||||
CHECK(self().rec.A.not_null());
|
||||
}
|
||||
|
||||
T& expect_grams(td::Ref<vm::CellSlice> cs, td::uint64 expected, td::Slice name) {
|
||||
if (has_fatal_error_) {
|
||||
return self();
|
||||
}
|
||||
td::RefInt256 got;
|
||||
CHECK(cs.not_null());
|
||||
CHECK(block::tlb::t_Grams.as_integer_to(cs, got));
|
||||
if (got->cmp(expected) != 0) {
|
||||
on_error(td::Status::Error(PSLICE() << name << ": expected " << expected << ", got " << got->to_dec_string()));
|
||||
}
|
||||
return self();
|
||||
}
|
||||
template <class S>
|
||||
T& expect_eq(S a, S expected, td::Slice name) {
|
||||
if (has_fatal_error_) {
|
||||
return self();
|
||||
}
|
||||
if (!(a == expected)) {
|
||||
on_error(td::Status::Error(PSLICE() << name << ": expected " << expected << ", got " << a));
|
||||
}
|
||||
return self();
|
||||
}
|
||||
|
||||
td::Status finish() {
|
||||
if (errors_.empty()) {
|
||||
return td::Status::OK();
|
||||
}
|
||||
std::stringstream ss;
|
||||
block::gen::t_ChanData.print_ref(ss, state_);
|
||||
td::StringBuilder sb;
|
||||
for (auto& error : errors_) {
|
||||
sb << error << "\n";
|
||||
}
|
||||
sb << ss.str();
|
||||
return td::Status::Error(sb.as_cslice());
|
||||
}
|
||||
|
||||
void on_fatal_error(td::Status error) {
|
||||
CHECK(!has_fatal_error_);
|
||||
has_fatal_error_ = true;
|
||||
on_error(std::move(error));
|
||||
}
|
||||
void on_error(td::Status error) {
|
||||
CHECK(error.is_error());
|
||||
errors_.push_back(std::move(error));
|
||||
}
|
||||
|
||||
public:
|
||||
td::Ref<vm::Cell> state_;
|
||||
bool has_fatal_error_{false};
|
||||
std::vector<td::Status> errors_;
|
||||
};
|
||||
|
||||
struct ValidateStatePayout : public ValidateState<ValidateStatePayout> {
|
||||
ValidateStatePayout& expect_A(td::uint64 a) {
|
||||
expect_grams(rec.A, a, "A");
|
||||
return *this;
|
||||
}
|
||||
ValidateStatePayout& expect_B(td::uint64 b) {
|
||||
expect_grams(rec.B, b, "B");
|
||||
return *this;
|
||||
}
|
||||
|
||||
ValidateStatePayout(td::Ref<vm::Cell> state) {
|
||||
init(std::move(state));
|
||||
}
|
||||
|
||||
block::gen::ChanState::Record_chan_state_payout rec;
|
||||
};
|
||||
|
||||
struct ValidateStateInit : public ValidateState<ValidateStateInit> {
|
||||
ValidateStateInit& expect_A(td::uint64 a) {
|
||||
expect_grams(rec.A, a, "A");
|
||||
return *this;
|
||||
}
|
||||
ValidateStateInit& expect_B(td::uint64 b) {
|
||||
expect_grams(rec.B, b, "B");
|
||||
return *this;
|
||||
}
|
||||
ValidateStateInit& expect_min_A(td::uint64 a) {
|
||||
expect_grams(rec.min_A, a, "min_A");
|
||||
return *this;
|
||||
}
|
||||
ValidateStateInit& expect_min_B(td::uint64 b) {
|
||||
expect_grams(rec.min_B, b, "min_B");
|
||||
return *this;
|
||||
}
|
||||
ValidateStateInit& expect_expire_at(td::uint32 b) {
|
||||
expect_eq(rec.expire_at, b, "expire_at");
|
||||
return *this;
|
||||
}
|
||||
ValidateStateInit& expect_signed_A(bool x) {
|
||||
expect_eq(rec.signed_A, x, "signed_A");
|
||||
return *this;
|
||||
}
|
||||
ValidateStateInit& expect_signed_B(bool x) {
|
||||
expect_eq(rec.signed_B, x, "signed_B");
|
||||
return *this;
|
||||
}
|
||||
|
||||
ValidateStateInit(td::Ref<vm::Cell> state) {
|
||||
init(std::move(state));
|
||||
}
|
||||
|
||||
block::gen::ChanState::Record_chan_state_init rec;
|
||||
};
|
||||
|
||||
struct ValidateStateClose : public ValidateState<ValidateStateClose> {
|
||||
ValidateStateClose& expect_A(td::uint64 a) {
|
||||
expect_grams(rec.A, a, "A");
|
||||
return *this;
|
||||
}
|
||||
ValidateStateClose& expect_B(td::uint64 b) {
|
||||
expect_grams(rec.B, b, "B");
|
||||
return *this;
|
||||
}
|
||||
ValidateStateClose& expect_promise_A(td::uint64 a) {
|
||||
expect_grams(rec.promise_A, a, "promise_A");
|
||||
return *this;
|
||||
}
|
||||
ValidateStateClose& expect_promise_B(td::uint64 b) {
|
||||
expect_grams(rec.promise_B, b, "promise_B");
|
||||
return *this;
|
||||
}
|
||||
ValidateStateClose& expect_expire_at(td::uint32 b) {
|
||||
expect_eq(rec.expire_at, b, "expire_at");
|
||||
return *this;
|
||||
}
|
||||
ValidateStateClose& expect_signed_A(bool x) {
|
||||
expect_eq(rec.signed_A, x, "signed_A");
|
||||
return *this;
|
||||
}
|
||||
ValidateStateClose& expect_signed_B(bool x) {
|
||||
expect_eq(rec.signed_B, x, "signed_B");
|
||||
return *this;
|
||||
}
|
||||
|
||||
ValidateStateClose(td::Ref<vm::Cell> state) {
|
||||
init(std::move(state));
|
||||
}
|
||||
|
||||
block::gen::ChanState::Record_chan_state_close rec;
|
||||
};
|
||||
|
||||
// config$_ initTimeout:int exitTimeout:int a_key:int256 b_key:int256 a_addr b_addr channel_id:int256 = Config;
|
||||
TEST(Smarcont, Channel) {
|
||||
auto code = ton::SmartContractCode::get_code(ton::SmartContractCode::PaymentChannel);
|
||||
Config config;
|
||||
auto a_pkey = td::Ed25519::generate_private_key().move_as_ok();
|
||||
auto b_pkey = td::Ed25519::generate_private_key().move_as_ok();
|
||||
config.init_timeout = 20;
|
||||
config.close_timeout = 40;
|
||||
auto dest = block::StdAddress::parse("Ef9Tj6fMJP+OqhAdhKXxq36DL+HYSzCc3+9O6UNzqsgPfYFX").move_as_ok();
|
||||
config.a_addr = dest;
|
||||
config.b_addr = dest;
|
||||
config.a_key = a_pkey.get_public_key().ok().as_octet_string();
|
||||
config.b_key = b_pkey.get_public_key().ok().as_octet_string();
|
||||
config.channel_id = 123;
|
||||
|
||||
Data data;
|
||||
data.config = config.serialize();
|
||||
data.state = data.init_state();
|
||||
auto data_cell = data.serialize();
|
||||
|
||||
auto channel = ton::SmartContract::create(ton::SmartContract::State{code, data_cell});
|
||||
ValidateStateInit(channel->get_state().data)
|
||||
.expect_A(0)
|
||||
.expect_B(0)
|
||||
.expect_min_A(0)
|
||||
.expect_min_B(0)
|
||||
.expect_signed_A(false)
|
||||
.expect_signed_B(false)
|
||||
.expect_expire_at(0)
|
||||
.finish()
|
||||
.ensure();
|
||||
|
||||
enum err {
|
||||
ok = 0,
|
||||
wrong_a_signature = 31,
|
||||
wrong_b_signature,
|
||||
msg_value_too_small,
|
||||
replay_protection,
|
||||
no_timeout,
|
||||
expected_init,
|
||||
expected_close,
|
||||
no_promise_signature,
|
||||
wrong_channel_id
|
||||
};
|
||||
|
||||
#define expect_code(description, expected_code, e) \
|
||||
{ \
|
||||
auto res = e; \
|
||||
LOG_IF(FATAL, expected_code != res.code) << " res.code=" << res.code << " " << description << "\n" << #e; \
|
||||
}
|
||||
#define expect_ok(description, e) expect_code(description, 0, e)
|
||||
|
||||
expect_code("Trying to invoke a timeout while channel is empty", no_timeout,
|
||||
channel.write().send_external_message(MsgTimeoutBuilder().finalize(),
|
||||
ton::SmartContract::Args().set_now(1000000)));
|
||||
|
||||
expect_code("External init message with no signatures", replay_protection,
|
||||
channel.write().send_external_message(MsgInitBuilder().channel_id(config.channel_id).finalize()));
|
||||
expect_code("Internal init message with not enough value", msg_value_too_small,
|
||||
channel.write().send_internal_message(
|
||||
MsgInitBuilder().channel_id(config.channel_id).inc_A(1000).min_B(2000).with_a_key(&a_pkey).finalize(),
|
||||
ton::SmartContract::Args().set_amount(100)));
|
||||
expect_code(
|
||||
"Internal init message with wrong channel_id", wrong_channel_id,
|
||||
channel.write().send_internal_message(MsgInitBuilder().inc_A(1000).min_B(2000).with_a_key(&a_pkey).finalize(),
|
||||
ton::SmartContract::Args().set_amount(1000)));
|
||||
expect_ok("A init with (inc_A = 1000, min_A = 1, min_B = 2000)",
|
||||
channel.write().send_internal_message(MsgInitBuilder()
|
||||
.channel_id(config.channel_id)
|
||||
.inc_A(1000)
|
||||
.min_A(1)
|
||||
.min_B(2000)
|
||||
.with_a_key(&a_pkey)
|
||||
.finalize(),
|
||||
ton::SmartContract::Args().set_amount(1000)));
|
||||
ValidateStateInit(channel->get_state().data)
|
||||
.expect_A(1000)
|
||||
.expect_B(0)
|
||||
.expect_min_A(1)
|
||||
.expect_min_B(2000)
|
||||
.expect_signed_A(true)
|
||||
.expect_signed_B(false)
|
||||
.expect_expire_at(config.init_timeout)
|
||||
.finish()
|
||||
.ensure();
|
||||
|
||||
expect_code("Repeated init of A init with (inc_A = 100, min_B = 5000). Must be ignored", replay_protection,
|
||||
channel.write().send_internal_message(
|
||||
MsgInitBuilder().channel_id(config.channel_id).inc_A(100).min_B(5000).with_a_key(&a_pkey).finalize(),
|
||||
ton::SmartContract::Args().set_amount(1000)));
|
||||
expect_code(
|
||||
"Trying to invoke a timeout too early", no_timeout,
|
||||
channel.write().send_external_message(MsgTimeoutBuilder().finalize(), ton::SmartContract::Args().set_now(0)));
|
||||
|
||||
{
|
||||
auto channel_copy = channel;
|
||||
expect_ok("Invoke a timeout", channel_copy.write().send_external_message(MsgTimeoutBuilder().finalize(),
|
||||
ton::SmartContract::Args().set_now(21)));
|
||||
ValidateStatePayout(channel_copy->get_state().data).expect_A(1000).expect_B(0).finish().ensure();
|
||||
}
|
||||
{
|
||||
auto channel_copy = channel;
|
||||
expect_ok("B init with inc_B < min_B. Leads to immediate payout",
|
||||
channel_copy.write().send_internal_message(
|
||||
MsgInitBuilder().channel_id(config.channel_id).inc_B(1500).with_b_key(&b_pkey).finalize(),
|
||||
ton::SmartContract::Args().set_amount(1500)));
|
||||
ValidateStatePayout(channel_copy->get_state().data).expect_A(1000).expect_B(1500).finish().ensure();
|
||||
}
|
||||
|
||||
expect_ok("B init with (inc_B = 2000, min_A = 1, min_A = 1000)",
|
||||
channel.write().send_internal_message(
|
||||
MsgInitBuilder().channel_id(config.channel_id).inc_B(2000).min_A(1000).with_b_key(&b_pkey).finalize(),
|
||||
ton::SmartContract::Args().set_amount(2000)));
|
||||
ValidateStateClose(channel->get_state().data)
|
||||
.expect_A(1000)
|
||||
.expect_B(2000)
|
||||
.expect_promise_A(0)
|
||||
.expect_promise_B(0)
|
||||
.expect_signed_A(false)
|
||||
.expect_signed_B(false)
|
||||
.expect_expire_at(0)
|
||||
.finish()
|
||||
.ensure();
|
||||
|
||||
{
|
||||
auto channel_copy = channel;
|
||||
expect_ok("A&B send Promise(1000000, 1000000 + 10) signed by nobody",
|
||||
channel_copy.write().send_external_message(MsgCloseBuilder()
|
||||
.signed_promise(SignedPromiseBuilder()
|
||||
.promise_A(1000000)
|
||||
.promise_B(1000000 + 10)
|
||||
.channel_id(config.channel_id)
|
||||
.finalize())
|
||||
.with_a_key(&a_pkey)
|
||||
.with_b_key(&b_pkey)
|
||||
.finalize(),
|
||||
ton::SmartContract::Args().set_now(21)));
|
||||
ValidateStatePayout(channel_copy->get_state().data).expect_A(1000 + 10).expect_B(2000 - 10).finish().ensure();
|
||||
}
|
||||
{
|
||||
auto channel_copy = channel;
|
||||
expect_ok("A&B send Promise(1000000, 1000000 + 10) signed by A",
|
||||
channel_copy.write().send_external_message(MsgCloseBuilder()
|
||||
.signed_promise(SignedPromiseBuilder()
|
||||
.promise_A(1000000)
|
||||
.promise_B(1000000 + 10)
|
||||
.with_key(&a_pkey)
|
||||
.channel_id(config.channel_id)
|
||||
.finalize())
|
||||
.with_a_key(&a_pkey)
|
||||
.with_b_key(&b_pkey)
|
||||
.finalize(),
|
||||
ton::SmartContract::Args().set_now(21)));
|
||||
ValidateStatePayout(channel_copy->get_state().data).expect_A(1000 + 10).expect_B(2000 - 10).finish().ensure();
|
||||
}
|
||||
|
||||
expect_code(
|
||||
"A sends Promise(1000000, 0) signed by A", wrong_b_signature,
|
||||
channel.write().send_external_message(
|
||||
MsgCloseBuilder()
|
||||
.signed_promise(
|
||||
SignedPromiseBuilder().promise_A(1000000).with_key(&a_pkey).channel_id(config.channel_id).finalize())
|
||||
.with_a_key(&a_pkey)
|
||||
.finalize(),
|
||||
ton::SmartContract::Args().set_now(21)));
|
||||
expect_code(
|
||||
"B sends Promise(1000000, 0) signed by B", wrong_a_signature,
|
||||
channel.write().send_external_message(
|
||||
MsgCloseBuilder()
|
||||
.signed_promise(
|
||||
SignedPromiseBuilder().promise_A(1000000).with_key(&b_pkey).channel_id(config.channel_id).finalize())
|
||||
.with_b_key(&b_pkey)
|
||||
.finalize(),
|
||||
ton::SmartContract::Args().set_now(21)));
|
||||
expect_code("B sends Promise(1000000, 0) signed by A with wrong channel_id", wrong_channel_id,
|
||||
channel.write().send_external_message(MsgCloseBuilder()
|
||||
.signed_promise(SignedPromiseBuilder()
|
||||
.promise_A(1000000)
|
||||
.with_key(&a_pkey)
|
||||
.channel_id(config.channel_id + 1)
|
||||
.finalize())
|
||||
.with_b_key(&b_pkey)
|
||||
.finalize(),
|
||||
ton::SmartContract::Args().set_now(21)));
|
||||
expect_code(
|
||||
"B sends unsigned Promise(1000000, 0)", no_promise_signature,
|
||||
channel.write().send_external_message(
|
||||
MsgCloseBuilder()
|
||||
.signed_promise(SignedPromiseBuilder().promise_A(1000000).channel_id(config.channel_id).finalize())
|
||||
.with_b_key(&b_pkey)
|
||||
.finalize(),
|
||||
ton::SmartContract::Args().set_now(21)));
|
||||
|
||||
expect_ok(
|
||||
"B sends Promise(1000000, 0) signed by A",
|
||||
channel.write().send_external_message(
|
||||
MsgCloseBuilder()
|
||||
.signed_promise(
|
||||
SignedPromiseBuilder().promise_A(1000000).with_key(&a_pkey).channel_id(config.channel_id).finalize())
|
||||
.with_b_key(&b_pkey)
|
||||
.finalize(),
|
||||
ton::SmartContract::Args().set_now(21)));
|
||||
ValidateStateClose(channel->get_state().data)
|
||||
.expect_A(1000)
|
||||
.expect_B(2000)
|
||||
.expect_promise_A(1000000)
|
||||
.expect_promise_B(0)
|
||||
.expect_signed_A(false)
|
||||
.expect_signed_B(true)
|
||||
.expect_expire_at(21 + config.close_timeout)
|
||||
.finish()
|
||||
.ensure();
|
||||
|
||||
expect_ok("B sends Promise(0, 1000000 + 10) signed by A",
|
||||
channel.write().send_external_message(MsgCloseBuilder()
|
||||
.signed_promise(SignedPromiseBuilder()
|
||||
.promise_B(1000000 + 10)
|
||||
.with_key(&b_pkey)
|
||||
.channel_id(config.channel_id)
|
||||
.finalize())
|
||||
.with_a_key(&a_pkey)
|
||||
.finalize(),
|
||||
ton::SmartContract::Args().set_now(21)));
|
||||
ValidateStatePayout(channel->get_state().data).expect_A(1000 + 10).expect_B(2000 - 10).finish().ensure();
|
||||
#undef expect_ok
|
||||
#undef expect_code
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include "vm/cells/CellSlice.h"
|
||||
#include "vm/excno.hpp"
|
||||
#include "td/utils/bits.h"
|
||||
#include "td/utils/misc.h"
|
||||
|
||||
namespace vm {
|
||||
|
||||
|
@ -719,6 +720,10 @@ bool CellSlice::prefetch_bytes(unsigned char* buffer, unsigned bytes) const {
|
|||
}
|
||||
}
|
||||
|
||||
bool CellSlice::fetch_bytes(td::MutableSlice slice) {
|
||||
return fetch_bytes(slice.ubegin(), td::narrow_cast<unsigned>(slice.size()));
|
||||
}
|
||||
|
||||
bool CellSlice::fetch_bytes(unsigned char* buffer, unsigned bytes) {
|
||||
if (prefetch_bytes(buffer, bytes)) {
|
||||
advance(bytes * 8);
|
||||
|
@ -728,6 +733,10 @@ bool CellSlice::fetch_bytes(unsigned char* buffer, unsigned bytes) {
|
|||
}
|
||||
}
|
||||
|
||||
bool CellSlice::prefetch_bytes(td::MutableSlice slice) const {
|
||||
return prefetch_bytes(slice.ubegin(), td::narrow_cast<unsigned>(slice.size()));
|
||||
}
|
||||
|
||||
Ref<Cell> CellSlice::prefetch_ref(unsigned offset) const {
|
||||
if (offset < size_refs()) {
|
||||
auto ref_id = refs_st + offset;
|
||||
|
|
|
@ -218,7 +218,9 @@ class CellSlice : public td::CntObject {
|
|||
return prefetch_bits_to(buffer.bits(), n);
|
||||
}
|
||||
bool fetch_bytes(unsigned char* buffer, unsigned bytes);
|
||||
bool fetch_bytes(td::MutableSlice slice);
|
||||
bool prefetch_bytes(unsigned char* buffer, unsigned bytes) const;
|
||||
bool prefetch_bytes(td::MutableSlice slice) const;
|
||||
td::BitSlice as_bitslice() const {
|
||||
return prefetch_bits(size());
|
||||
}
|
||||
|
|
|
@ -148,6 +148,11 @@ class MerkleProofCombineFast {
|
|||
MerkleProofCombineFast(Ref<Cell> a, Ref<Cell> b) : a_(std::move(a)), b_(std::move(b)) {
|
||||
}
|
||||
td::Result<Ref<Cell>> run() {
|
||||
if (a_.is_null()) {
|
||||
return b_;
|
||||
} else if (b_.is_null()) {
|
||||
return a_;
|
||||
}
|
||||
TRY_RESULT_ASSIGN(a_, unpack_proof(a_));
|
||||
TRY_RESULT_ASSIGN(b_, unpack_proof(b_));
|
||||
TRY_RESULT(res, run_raw());
|
||||
|
@ -204,6 +209,11 @@ class MerkleProofCombine {
|
|||
MerkleProofCombine(Ref<Cell> a, Ref<Cell> b) : a_(std::move(a)), b_(std::move(b)) {
|
||||
}
|
||||
td::Result<Ref<Cell>> run() {
|
||||
if (a_.is_null()) {
|
||||
return b_;
|
||||
} else if (b_.is_null()) {
|
||||
return a_;
|
||||
}
|
||||
TRY_RESULT_ASSIGN(a_, unpack_proof(a_));
|
||||
TRY_RESULT_ASSIGN(b_, unpack_proof(b_));
|
||||
TRY_RESULT(res, run_raw());
|
||||
|
@ -323,6 +333,10 @@ Ref<Cell> MerkleProof::combine(Ref<Cell> a, Ref<Cell> b) {
|
|||
return res.move_as_ok();
|
||||
}
|
||||
|
||||
td::Result<Ref<Cell>> MerkleProof::combine_status(Ref<Cell> a, Ref<Cell> b) {
|
||||
return MerkleProofCombine(std::move(a), std::move(b)).run();
|
||||
}
|
||||
|
||||
Ref<Cell> MerkleProof::combine_fast(Ref<Cell> a, Ref<Cell> b) {
|
||||
auto res = MerkleProofCombineFast(std::move(a), std::move(b)).run();
|
||||
if (res.is_error()) {
|
||||
|
@ -331,6 +345,10 @@ Ref<Cell> MerkleProof::combine_fast(Ref<Cell> a, Ref<Cell> b) {
|
|||
return res.move_as_ok();
|
||||
}
|
||||
|
||||
td::Result<Ref<Cell>> MerkleProof::combine_fast_status(Ref<Cell> a, Ref<Cell> b) {
|
||||
return MerkleProofCombineFast(std::move(a), std::move(b)).run();
|
||||
}
|
||||
|
||||
Ref<Cell> MerkleProof::combine_raw(Ref<Cell> a, Ref<Cell> b) {
|
||||
auto res = MerkleProofCombine(std::move(a), std::move(b)).run_raw();
|
||||
if (res.is_error()) {
|
||||
|
|
|
@ -38,7 +38,9 @@ class MerkleProof {
|
|||
static Ref<Cell> virtualize(Ref<Cell> cell, int virtualization);
|
||||
|
||||
static Ref<Cell> combine(Ref<Cell> a, Ref<Cell> b);
|
||||
static td::Result<Ref<Cell>> combine_status(Ref<Cell> a, Ref<Cell> b);
|
||||
static Ref<Cell> combine_fast(Ref<Cell> a, Ref<Cell> b);
|
||||
static td::Result<Ref<Cell>> combine_fast_status(Ref<Cell> a, Ref<Cell> b);
|
||||
|
||||
// works with upwrapped proofs
|
||||
// works fine with cell of non-zero level, but this is not supported (yet?) in MerkeProof special cell
|
||||
|
|
|
@ -255,15 +255,15 @@ TonDbTransactionImpl::TonDbTransactionImpl(std::shared_ptr<KeyValue> kv) : kv_(s
|
|||
}
|
||||
|
||||
void TonDbTransactionImpl::begin() {
|
||||
kv_->begin_transaction();
|
||||
kv_->begin_write_batch();
|
||||
generation_++;
|
||||
}
|
||||
void TonDbTransactionImpl::commit() {
|
||||
kv_->commit_transaction();
|
||||
kv_->commit_write_batch();
|
||||
reader_.reset(kv_->snapshot().release());
|
||||
}
|
||||
void TonDbTransactionImpl::abort() {
|
||||
kv_->abort_transaction();
|
||||
kv_->abort_write_batch();
|
||||
}
|
||||
void TonDbTransactionImpl::clear_cache() {
|
||||
contracts_ = {};
|
||||
|
|
|
@ -77,6 +77,13 @@ class VmError {
|
|||
long long get_arg() const {
|
||||
return arg;
|
||||
}
|
||||
td::Status as_status() const {
|
||||
return td::Status::Error(td::Slice{get_msg()});
|
||||
}
|
||||
template <typename T>
|
||||
td::Status as_status(T pfx) const {
|
||||
return td::Status::Error(PSLICE() << pfx << get_msg());
|
||||
}
|
||||
};
|
||||
|
||||
struct VmNoGas {
|
||||
|
@ -90,6 +97,13 @@ struct VmNoGas {
|
|||
operator VmError() const {
|
||||
return VmError{Excno::out_of_gas, "out of gas"};
|
||||
}
|
||||
td::Status as_status() const {
|
||||
return td::Status::Error(td::Slice{get_msg()});
|
||||
}
|
||||
template <typename T>
|
||||
td::Status as_status(T pfx) const {
|
||||
return td::Status::Error(PSLICE() << pfx << get_msg());
|
||||
}
|
||||
};
|
||||
|
||||
struct VmVirtError {
|
||||
|
@ -106,6 +120,13 @@ struct VmVirtError {
|
|||
operator VmError() const {
|
||||
return VmError{Excno::virt_err, "prunned branch", virtualization};
|
||||
}
|
||||
td::Status as_status() const {
|
||||
return td::Status::Error(td::Slice{get_msg()});
|
||||
}
|
||||
template <typename T>
|
||||
td::Status as_status(T pfx) const {
|
||||
return td::Status::Error(PSLICE() << pfx << get_msg());
|
||||
}
|
||||
};
|
||||
|
||||
struct VmFatal {};
|
||||
|
@ -114,12 +135,12 @@ 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());
|
||||
} catch (vm::VmError& error) {
|
||||
return error.as_status("Got a vm exception: ");
|
||||
} catch (vm::VmVirtError& error) {
|
||||
return error.as_status("Got a vm virtualization exception: ");
|
||||
} catch (vm::VmNoGas& error) {
|
||||
return error.as_status("Got a vm no gas exception: ");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -33,6 +33,7 @@
|
|||
#include "vm/cells.h"
|
||||
#include "vm/stack.hpp"
|
||||
#include "block/block.h"
|
||||
#include "block/mc-config.h"
|
||||
#include "td/utils/filesystem.h"
|
||||
|
||||
using td::Ref;
|
||||
|
@ -67,8 +68,8 @@ class TestNode : public td::actor::Actor {
|
|||
ton::BlockIdExt last_block_id_, last_state_id_;
|
||||
td::BufferSlice last_block_data_, last_state_data_;
|
||||
|
||||
ton::StdSmcAddress dns_root_;
|
||||
bool dns_root_queried_{false};
|
||||
ton::StdSmcAddress dns_root_, elect_addr_;
|
||||
bool dns_root_queried_{false}, elect_addr_queried_{false};
|
||||
|
||||
std::string line_;
|
||||
const char *parse_ptr_, *parse_end_;
|
||||
|
@ -89,6 +90,9 @@ class TestNode : public td::actor::Actor {
|
|||
|
||||
std::unique_ptr<ton::adnl::AdnlExtClient::Callback> make_callback();
|
||||
|
||||
using creator_stats_func_t =
|
||||
std::function<bool(const td::Bits256&, const block::DiscountedCounter&, const block::DiscountedCounter&)>;
|
||||
|
||||
struct TransId {
|
||||
ton::Bits256 acc_addr;
|
||||
ton::LogicalTime trans_lt;
|
||||
|
@ -98,6 +102,75 @@ class TestNode : public td::actor::Actor {
|
|||
}
|
||||
};
|
||||
|
||||
struct BlockHdrInfo {
|
||||
ton::BlockIdExt blk_id;
|
||||
Ref<vm::Cell> proof, virt_blk_root;
|
||||
int mode;
|
||||
BlockHdrInfo() : mode(-1) {
|
||||
}
|
||||
BlockHdrInfo(const ton::BlockIdExt blk_id_, Ref<vm::Cell> proof_, Ref<vm::Cell> vroot_, int mode_)
|
||||
: blk_id(blk_id_), proof(std::move(proof_)), virt_blk_root(std::move(vroot_)), mode(mode_) {
|
||||
}
|
||||
};
|
||||
|
||||
struct ConfigInfo {
|
||||
std::unique_ptr<block::Config> config;
|
||||
Ref<vm::Cell> state_proof, config_proof;
|
||||
ConfigInfo() = default;
|
||||
ConfigInfo(std::unique_ptr<block::Config> config_, Ref<vm::Cell> state_proof_, Ref<vm::Cell> config_proof_)
|
||||
: config(std::move(config_)), state_proof(std::move(state_proof_)), config_proof(std::move(config_proof_)) {
|
||||
}
|
||||
};
|
||||
|
||||
struct CreatorStatsRes {
|
||||
int mode;
|
||||
bool complete{false};
|
||||
td::Bits256 last_key;
|
||||
Ref<vm::Cell> state_proof, data_proof;
|
||||
CreatorStatsRes(int mode_ = 0) : mode(mode_) {
|
||||
last_key.set_zero();
|
||||
}
|
||||
CreatorStatsRes(int mode_, const td::Bits256& key_, Ref<vm::Cell> st_proof_ = {}, Ref<vm::Cell> dproof_ = {})
|
||||
: mode(mode_), last_key(key_), state_proof(std::move(st_proof_)), data_proof(std::move(dproof_)) {
|
||||
}
|
||||
};
|
||||
|
||||
struct ValidatorLoadInfo {
|
||||
ton::BlockIdExt blk_id;
|
||||
Ref<vm::Cell> state_proof, data_proof, virt_root;
|
||||
std::unique_ptr<block::Config> config;
|
||||
ton::UnixTime block_created_at{0};
|
||||
ton::UnixTime valid_since{0};
|
||||
ton::LogicalTime end_lt{0};
|
||||
ton::Bits256 vset_hash;
|
||||
Ref<vm::Cell> vset_root;
|
||||
std::unique_ptr<block::ValidatorSet> vset;
|
||||
std::map<ton::Bits256, int> vset_map;
|
||||
int special_idx{-1};
|
||||
std::pair<td::int64, td::int64> created_total, created_special;
|
||||
std::vector<std::pair<td::int64, td::int64>> created;
|
||||
ValidatorLoadInfo(ton::BlockIdExt blkid, Ref<vm::Cell> root, Ref<vm::Cell> root2,
|
||||
std::unique_ptr<block::Config> cfg = {})
|
||||
: blk_id(blkid)
|
||||
, state_proof(std::move(root))
|
||||
, data_proof(std::move(root2))
|
||||
, config(std::move(cfg))
|
||||
, valid_since(0) {
|
||||
}
|
||||
td::Status unpack_vset();
|
||||
bool store_record(const td::Bits256& key, const block::DiscountedCounter& mc_cnt,
|
||||
const block::DiscountedCounter& shard_cnt);
|
||||
bool has_data() const {
|
||||
return blk_id.is_masterchain_ext() && state_proof.not_null() && data_proof.not_null() && config;
|
||||
}
|
||||
td::Status check_header_proof(ton::UnixTime* save_utime = nullptr, ton::LogicalTime* save_lt = nullptr) const;
|
||||
td::Result<Ref<vm::Cell>> build_proof(int idx, td::Bits256* save_pubkey = nullptr) const;
|
||||
td::Result<Ref<vm::Cell>> build_producer_info(int idx, td::Bits256* save_pubkey = nullptr) const;
|
||||
td::Status init_check_proofs();
|
||||
static td::Result<std::unique_ptr<ValidatorLoadInfo>> preinit_from_producer_info(Ref<vm::Cell> prod_info);
|
||||
td::Status load_special_creator_stat(const td::Bits256& spec_pubkey, bool load_total = true);
|
||||
};
|
||||
|
||||
void run_init_queries();
|
||||
char cur() const {
|
||||
return *parse_ptr_;
|
||||
|
@ -133,6 +206,8 @@ class TestNode : public td::actor::Actor {
|
|||
std::vector<vm::StackEntry> params, td::BufferSlice remote_c7, td::BufferSlice remote_libs,
|
||||
td::BufferSlice remote_result, int remote_exit_code,
|
||||
td::Promise<std::vector<vm::StackEntry>> promise);
|
||||
bool register_config_param(int idx, Ref<vm::Cell> value);
|
||||
bool register_config_param1(Ref<vm::Cell> value);
|
||||
bool register_config_param4(Ref<vm::Cell> value);
|
||||
bool dns_resolve_start(ton::WorkchainId workchain, ton::StdSmcAddress addr, ton::BlockIdExt blkid, std::string domain,
|
||||
int cat, int mode);
|
||||
|
@ -144,18 +219,24 @@ class TestNode : public td::actor::Actor {
|
|||
bool show_dns_record(std::ostream& os, int cat, Ref<vm::Cell> value, bool raw_dump);
|
||||
bool get_all_shards(std::string filename = "", bool use_last = true, ton::BlockIdExt blkid = {});
|
||||
void got_all_shards(ton::BlockIdExt blk, td::BufferSlice proof, td::BufferSlice data, std::string filename);
|
||||
bool get_config_params(ton::BlockIdExt blkid, td::Promise<td::Unit> do_after, int mode = 0, std::string filename = "",
|
||||
std::vector<int> params = {});
|
||||
void got_config_params(ton::BlockIdExt req_blkid, ton::BlockIdExt blkid, td::BufferSlice state_proof,
|
||||
td::BufferSlice cfg_proof, int mode, std::string filename, std::vector<int> params,
|
||||
td::Promise<td::Unit> do_after);
|
||||
bool parse_get_config_params(ton::BlockIdExt blkid, int mode = 0, std::string filename = "",
|
||||
std::vector<int> params = {});
|
||||
bool get_config_params(ton::BlockIdExt blkid, td::Promise<std::unique_ptr<block::Config>> promise, int mode = 0,
|
||||
std::string filename = "", std::vector<int> params = {});
|
||||
bool get_config_params_ext(ton::BlockIdExt blkid, td::Promise<ConfigInfo> promise, int mode = 0,
|
||||
std::string filename = "", std::vector<int> params = {});
|
||||
void got_config_params(ton::BlockIdExt req_blkid, int mode, std::string filename, std::vector<int> params,
|
||||
td::Result<td::BufferSlice> R, td::Promise<ConfigInfo> promise);
|
||||
bool get_block(ton::BlockIdExt blk, bool dump = false);
|
||||
void got_block(ton::BlockIdExt blkid, td::BufferSlice data, bool dump);
|
||||
bool get_state(ton::BlockIdExt blk, bool dump = false);
|
||||
void got_state(ton::BlockIdExt blkid, ton::RootHash root_hash, ton::FileHash file_hash, td::BufferSlice data,
|
||||
bool dump);
|
||||
bool get_block_header(ton::BlockIdExt blk, int mode);
|
||||
bool lookup_block(ton::ShardIdFull shard, int mode, td::uint64 arg);
|
||||
bool get_show_block_header(ton::BlockIdExt blk, int mode);
|
||||
bool get_block_header(ton::BlockIdExt blk, int mode, td::Promise<BlockHdrInfo> promise);
|
||||
bool lookup_show_block(ton::ShardIdFull shard, int mode, td::uint64 arg);
|
||||
bool lookup_block(ton::ShardIdFull shard, int mode, td::uint64 arg, td::Promise<BlockHdrInfo>);
|
||||
void got_block_header_raw(td::BufferSlice res, td::Promise<BlockHdrInfo> promise, ton::BlockIdExt req_blkid = {});
|
||||
void got_block_header(ton::BlockIdExt blkid, td::BufferSlice data, int mode);
|
||||
bool show_block_header(ton::BlockIdExt blkid, Ref<vm::Cell> root, int mode);
|
||||
bool show_state_header(ton::BlockIdExt blkid, Ref<vm::Cell> root, int mode);
|
||||
|
@ -177,9 +258,43 @@ class TestNode : public td::actor::Actor {
|
|||
void got_block_proof(ton::BlockIdExt from, ton::BlockIdExt to, int mode, td::BufferSlice res);
|
||||
bool get_creator_stats(ton::BlockIdExt blkid, int mode, unsigned req_count, ton::Bits256 start_after,
|
||||
ton::UnixTime min_utime);
|
||||
void got_creator_stats(ton::BlockIdExt req_blkid, ton::BlockIdExt blkid, int req_mode, int mode,
|
||||
td::Bits256 start_after, ton::UnixTime min_utime, td::BufferSlice state_proof,
|
||||
td::BufferSlice data_proof, int count, int req_count, bool complete);
|
||||
bool get_creator_stats(ton::BlockIdExt blkid, int mode, unsigned req_count, ton::Bits256 start_after,
|
||||
ton::UnixTime min_utime, creator_stats_func_t func, td::Promise<td::Bits256> promise);
|
||||
bool get_creator_stats(ton::BlockIdExt blkid, unsigned req_count, ton::UnixTime min_utime, creator_stats_func_t func,
|
||||
std::unique_ptr<CreatorStatsRes> state, td::Promise<std::unique_ptr<CreatorStatsRes>> promise);
|
||||
void got_creator_stats(ton::BlockIdExt req_blkid, ton::BlockIdExt blkid, int mode, ton::UnixTime min_utime,
|
||||
td::BufferSlice state_proof, td::BufferSlice data_proof, int count, int req_count,
|
||||
bool complete, creator_stats_func_t func, std::unique_ptr<CreatorStatsRes> state,
|
||||
td::Promise<std::unique_ptr<CreatorStatsRes>> promise);
|
||||
bool check_validator_load(int start_time, int end_time, int mode = 0, std::string file_pfx = "");
|
||||
void continue_check_validator_load(ton::BlockIdExt blkid1, Ref<vm::Cell> root1, ton::BlockIdExt blkid2,
|
||||
Ref<vm::Cell> root2, int mode = 0, std::string file_pfx = "");
|
||||
void continue_check_validator_load2(std::unique_ptr<ValidatorLoadInfo> info1,
|
||||
std::unique_ptr<ValidatorLoadInfo> info2, int mode = 0,
|
||||
std::string file_pfx = "");
|
||||
void continue_check_validator_load3(std::unique_ptr<ValidatorLoadInfo> info1,
|
||||
std::unique_ptr<ValidatorLoadInfo> info2, int mode = 0,
|
||||
std::string file_pfx = "");
|
||||
td::Status write_val_create_proof(ValidatorLoadInfo& info1, ValidatorLoadInfo& info2, int idx, bool severe,
|
||||
std::string file_pfx, int cnt);
|
||||
bool load_creator_stats(std::unique_ptr<ValidatorLoadInfo> load_to,
|
||||
td::Promise<std::unique_ptr<ValidatorLoadInfo>> promise, bool need_proofs);
|
||||
td::Status check_validator_load_proof(std::string filename, std::string vset_filename = "",
|
||||
ton::Bits256 vset_hash = ton::Bits256::zero());
|
||||
td::Status continue_check_validator_load_proof(std::unique_ptr<ValidatorLoadInfo> info1,
|
||||
std::unique_ptr<ValidatorLoadInfo> info2, Ref<vm::Cell> root);
|
||||
bool get_elector_addr(td::Promise<ton::StdSmcAddress> promise);
|
||||
bool get_past_validator_sets();
|
||||
bool send_past_vset_query(ton::StdSmcAddress elector_addr);
|
||||
void register_past_vset_info(vm::StackEntry list);
|
||||
bool get_complaints(unsigned elect_id, std::string file_pfx);
|
||||
void send_get_complaints_query(unsigned elect_id, ton::StdSmcAddress elector_addr, std::string file_pfx);
|
||||
void save_complaints(unsigned elect_id, Ref<vm::Cell> complaints, std::string file_pfx);
|
||||
td::Status get_complaint_price(unsigned expires_in, std::string filename);
|
||||
td::Status get_complaint_price(unsigned expires_in, unsigned bits, unsigned refs,
|
||||
td::Bits256 chash = td::Bits256::zero(), std::string filename = "");
|
||||
void send_compute_complaint_price_query(ton::StdSmcAddress elector_addr, unsigned expires_in, unsigned bits,
|
||||
unsigned refs, td::Bits256 chash, std::string filename);
|
||||
bool cache_cell(Ref<vm::Cell> cell);
|
||||
bool list_cached_cells() const;
|
||||
bool dump_cached_cell(td::Slice hash_pfx, td::Slice type_name = {});
|
||||
|
@ -222,6 +337,17 @@ class TestNode : public td::actor::Actor {
|
|||
bool show_new_blkids(bool all = false);
|
||||
bool complete_blkid(ton::BlockId partial_blkid, ton::BlockIdExt& complete_blkid) const;
|
||||
td::Promise<td::Unit> trivial_promise();
|
||||
template <typename T>
|
||||
td::Promise<T> trivial_promise_of() {
|
||||
return td::PromiseCreator::lambda([Self = actor_id(this)](td::Result<T> res) {
|
||||
if (res.is_error()) {
|
||||
LOG(ERROR) << "error: " << res.move_as_error();
|
||||
}
|
||||
});
|
||||
}
|
||||
static ton::UnixTime now() {
|
||||
return static_cast<td::uint32>(td::Clocks::system());
|
||||
}
|
||||
static const tlb::TypenameLookup& get_tlb_dict();
|
||||
|
||||
public:
|
||||
|
@ -292,7 +418,8 @@ class TestNode : public td::actor::Actor {
|
|||
//td::actor::SchedulerContext::get()->stop();
|
||||
}
|
||||
|
||||
void got_result();
|
||||
void got_result(td::Result<td::BufferSlice> R, td::Promise<td::BufferSlice> promise);
|
||||
void after_got_result(bool ok);
|
||||
bool envelope_send_query(td::BufferSlice query, td::Promise<td::BufferSlice> promise);
|
||||
void parse_line(td::BufferSlice data);
|
||||
|
||||
|
|
|
@ -44,8 +44,24 @@ class GetArg<R (C::*)(Arg) const> {
|
|||
using type = Arg;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct GetRet : public GetRet<decltype(&T::operator())> {};
|
||||
|
||||
template <class C, class R, class... Arg>
|
||||
class GetRet<R (C::*)(Arg...)> {
|
||||
public:
|
||||
using type = R;
|
||||
};
|
||||
template <class C, class R, class... Arg>
|
||||
class GetRet<R (C::*)(Arg...) const> {
|
||||
public:
|
||||
using type = R;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
using get_arg_t = std::decay_t<typename GetArg<T>::type>;
|
||||
template <class T>
|
||||
using get_ret_t = std::decay_t<typename GetRet<T>::type>;
|
||||
|
||||
template <class T>
|
||||
struct DropResult {
|
||||
|
@ -131,6 +147,7 @@ constexpr bool is_promise_interface_ptr() {
|
|||
template <class ValueT, class FunctionT>
|
||||
class LambdaPromise : public PromiseInterface<ValueT> {
|
||||
public:
|
||||
using ArgT = ValueT;
|
||||
void set_value(ValueT &&value) override {
|
||||
CHECK(has_lambda_.get());
|
||||
do_ok(std::move(value));
|
||||
|
@ -288,12 +305,6 @@ class Promise {
|
|||
std::unique_ptr<PromiseInterface<T>> promise_;
|
||||
};
|
||||
|
||||
template <class F>
|
||||
auto make_promise(F &&f) {
|
||||
using ValueT = detail::drop_result_t<detail::get_arg_t<F>>;
|
||||
return Promise<ValueT>(promise_interface_ptr(std::forward<F>(f)));
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
template <class... ArgsT>
|
||||
class JoinPromise : public PromiseInterface<Unit> {
|
||||
|
@ -331,6 +342,16 @@ class PromiseCreator {
|
|||
}
|
||||
};
|
||||
|
||||
template <class F>
|
||||
auto make_promise(F &&f) {
|
||||
using ValueT = typename decltype(PromiseCreator::lambda(std::move(f)))::ArgT;
|
||||
return Promise<ValueT>(PromiseCreator::lambda(std::move(f)));
|
||||
}
|
||||
template <class T>
|
||||
auto make_promise(Promise<T> &&f) {
|
||||
return std::move(f);
|
||||
}
|
||||
|
||||
template <class T = Unit>
|
||||
class SafePromise {
|
||||
public:
|
||||
|
@ -356,4 +377,145 @@ class SafePromise {
|
|||
Promise<T> promise_;
|
||||
Result<T> result_;
|
||||
};
|
||||
|
||||
template <class PromiseT, typename... ArgsT>
|
||||
class PromiseMerger;
|
||||
|
||||
template <class F>
|
||||
struct SplitPromise {
|
||||
using PromiseT = decltype(make_promise(std::declval<F>()));
|
||||
using ArgT = typename PromiseT::ArgT;
|
||||
|
||||
template <class S, class T>
|
||||
static std::pair<Promise<S>, Promise<T>> split(std::pair<S, T>);
|
||||
template <class... ArgsT>
|
||||
static std::tuple<Promise<ArgsT>...> split(std::tuple<ArgsT...>);
|
||||
using SplittedT = decltype(split(std::declval<ArgT>()));
|
||||
|
||||
template <class S, class T>
|
||||
static PromiseMerger<PromiseT, S, T> merger(std::pair<S, T>);
|
||||
template <class... ArgsT>
|
||||
static PromiseMerger<PromiseT, ArgsT...> merger(std::tuple<ArgsT...>);
|
||||
using MergerT = decltype(merger(std::declval<ArgT>()));
|
||||
};
|
||||
|
||||
template <class PromiseT, typename... ArgsT>
|
||||
class PromiseMerger : public std::enable_shared_from_this<PromiseMerger<PromiseT, ArgsT...>> {
|
||||
public:
|
||||
std::tuple<Result<ArgsT>...> args_;
|
||||
PromiseT promise_;
|
||||
|
||||
PromiseMerger(PromiseT promise) : promise_(std::move(promise)) {
|
||||
}
|
||||
~PromiseMerger() {
|
||||
td::Status status;
|
||||
tuple_for_each(args_, [&status](auto &&arg) {
|
||||
if (status.is_error()) {
|
||||
return;
|
||||
}
|
||||
if (arg.is_error()) {
|
||||
status = arg.move_as_error();
|
||||
}
|
||||
});
|
||||
if (status.is_error()) {
|
||||
promise_.set_error(std::move(status));
|
||||
return;
|
||||
}
|
||||
call_tuple([this](auto &&... args) { promise_.set_value({args.move_as_ok()...}); }, std::move(args_));
|
||||
}
|
||||
|
||||
template <class T>
|
||||
Promise<typename T::ValueT> make_promise(T &arg) {
|
||||
return [&arg, self = this->shared_from_this()](auto res) { arg = std::move(res); };
|
||||
}
|
||||
|
||||
template <class R>
|
||||
auto split() {
|
||||
return call_tuple([this](auto &&... arg) { return R{this->make_promise(arg)...}; }, std::move(args_));
|
||||
}
|
||||
};
|
||||
|
||||
template <class F>
|
||||
auto split_promise(F &&f) {
|
||||
auto merger = std::make_shared<typename SplitPromise<F>::MergerT>(std::move(f));
|
||||
return merger->template split<typename SplitPromise<F>::SplittedT>();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
struct PromiseFuture {
|
||||
Result<Promise<T>> promise_;
|
||||
Result<T> result_;
|
||||
~PromiseFuture() {
|
||||
if (promise_.is_ok()) {
|
||||
promise_.move_as_ok().set_result(std::move(result_));
|
||||
} else {
|
||||
LOG(ERROR) << "Lost PromiseFuture";
|
||||
}
|
||||
}
|
||||
};
|
||||
template <class T>
|
||||
struct Future;
|
||||
|
||||
template <class T>
|
||||
std::pair<Promise<T>, Future<T>> make_promise_future();
|
||||
|
||||
template <class T>
|
||||
struct Future {
|
||||
Promise<Promise<T>> promise_;
|
||||
Future(Promise<Promise<T>> promise) : promise_(std::move(promise)) {
|
||||
}
|
||||
|
||||
void finish(Promise<T> promise) {
|
||||
promise_.set_value(std::move(promise));
|
||||
}
|
||||
|
||||
template <class F>
|
||||
auto map(F &&f) {
|
||||
using R = detail::drop_result_t<decltype(f(std::declval<T>()))>;
|
||||
auto pf = make_promise_future<R>();
|
||||
promise_.set_value([p = std::move(pf.first), f = std::move(f)](Result<T> res) mutable {
|
||||
TRY_RESULT_PROMISE(p, x, std::move(res));
|
||||
p.set_result(f(std::move(x)));
|
||||
});
|
||||
|
||||
return std::move(pf.second);
|
||||
}
|
||||
|
||||
template <class F>
|
||||
auto fmap(F &&f) {
|
||||
return flatten(map(std::move(f)));
|
||||
}
|
||||
|
||||
template <class X>
|
||||
static Future<X> flatten(Future<Future<X>> ff) {
|
||||
auto pf = make_promise_future<X>();
|
||||
ff.promise_.set_value([p = std::move(pf.first)](Result<Future<X>> r_f) mutable {
|
||||
TRY_RESULT_PROMISE(p, f, std::move(r_f));
|
||||
// Promise<X> p
|
||||
// Future<X> f
|
||||
f.promise_.set_value(std::move(p));
|
||||
});
|
||||
return std::move(pf.second);
|
||||
}
|
||||
};
|
||||
|
||||
template <class T>
|
||||
Future<T> make_future(T &&value) {
|
||||
return Future<T>([value = std::move(value)](Result<Promise<T>> r_promise) mutable {
|
||||
if (r_promise.is_ok()) {
|
||||
r_promise.move_as_ok().set_value(std::move(value));
|
||||
} else {
|
||||
LOG(ERROR) << "Lost future";
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template <class T>
|
||||
std::pair<Promise<T>, Future<T>> make_promise_future() {
|
||||
auto pf = std::make_shared<PromiseFuture<T>>();
|
||||
Future<T> future([pf](Result<Promise<T>> res) mutable { pf->promise_ = std::move(res); });
|
||||
Promise<T> promise = [pf = std::move(pf)](Result<T> res) mutable { pf->result_ = std::move(res); };
|
||||
return std::make_pair(std::move(promise), std::move(future));
|
||||
}
|
||||
|
||||
} // namespace td
|
||||
|
|
|
@ -100,6 +100,28 @@ void send_closure(ActorIdT &&actor_id, FunctionT function, ArgsT &&... args) {
|
|||
|
||||
#endif
|
||||
|
||||
template <class ActorIdT, class FunctionT, class... ArgsT, class FunctionClassT = member_function_class_t<FunctionT>,
|
||||
size_t argument_count = member_function_argument_count<FunctionT>(),
|
||||
std::enable_if_t<argument_count == sizeof...(ArgsT), bool> with_promise = false>
|
||||
auto future_send_closure(ActorIdT &&actor_id, FunctionT function, ArgsT &&... args) {
|
||||
using R = ::td::detail::get_ret_t<std::decay_t<FunctionT>>;
|
||||
auto pf = make_promise_future<R>();
|
||||
send_closure(std::forward<ActorIdT>(actor_id), std::move(function), std::forward<ArgsT>(args)...,
|
||||
std::move(pf.first));
|
||||
return std::move(pf.second);
|
||||
}
|
||||
|
||||
template <class R, class ActorIdT, class FunctionT, class... ArgsT,
|
||||
class FunctionClassT = member_function_class_t<FunctionT>,
|
||||
size_t argument_count = member_function_argument_count<FunctionT>(),
|
||||
std::enable_if_t<argument_count != sizeof...(ArgsT), bool> with_promise = true>
|
||||
Future<R> future_send_closure(ActorIdT &&actor_id, FunctionT function, ArgsT &&... args) {
|
||||
auto pf = make_promise_future<R>();
|
||||
send_closure(std::forward<ActorIdT>(actor_id), std::move(function), std::forward<ArgsT>(args)...,
|
||||
std::move(pf.first));
|
||||
return std::move(pf.second);
|
||||
}
|
||||
|
||||
template <typename ActorIdT, typename FunctionT, typename... ArgsT>
|
||||
bool send_closure_bool(ActorIdT &&actor_id, FunctionT function, ArgsT &&... args) {
|
||||
send_closure(std::forward<ActorIdT>(actor_id), function, std::forward<ArgsT>(args)...);
|
||||
|
|
|
@ -63,6 +63,36 @@ class ActorSignals {
|
|||
using core::Actor;
|
||||
using core::SchedulerContext;
|
||||
using core::SchedulerId;
|
||||
using core::set_debug;
|
||||
|
||||
struct Debug {
|
||||
public:
|
||||
Debug() = default;
|
||||
Debug(std::shared_ptr<core::SchedulerGroupInfo> group_info) : group_info_(std::move(group_info)) {
|
||||
}
|
||||
template <class F>
|
||||
void for_each(F &&f) {
|
||||
for (auto &scheduler : group_info_->schedulers) {
|
||||
f(scheduler.io_worker->debug);
|
||||
for (auto &cpu : scheduler.cpu_workers) {
|
||||
f(cpu->debug);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void dump() {
|
||||
for_each([](core::Debug &debug) {
|
||||
core::DebugInfo info;
|
||||
debug.read(info);
|
||||
if (info.is_active) {
|
||||
LOG(ERROR) << info.name << " " << td::format::as_time(Time::now() - info.start_at);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<core::SchedulerGroupInfo> group_info_;
|
||||
};
|
||||
|
||||
class Scheduler {
|
||||
public:
|
||||
|
@ -110,6 +140,10 @@ class Scheduler {
|
|||
}
|
||||
}
|
||||
|
||||
Debug get_debug() {
|
||||
return Debug{group_info_};
|
||||
}
|
||||
|
||||
bool run() {
|
||||
start();
|
||||
while (schedulers_[0]->run(10)) {
|
||||
|
|
|
@ -32,6 +32,7 @@ void CpuWorker::run() {
|
|||
|
||||
MpmcWaiter::Slot slot;
|
||||
waiter_.init_slot(slot, thread_id);
|
||||
auto &debug = dispatcher.get_debug();
|
||||
while (true) {
|
||||
SchedulerMessage message;
|
||||
if (try_pop(message, thread_id)) {
|
||||
|
@ -39,6 +40,7 @@ void CpuWorker::run() {
|
|||
if (!message) {
|
||||
return;
|
||||
}
|
||||
auto lock = debug.start(message->get_name());
|
||||
ActorExecutor executor(*message, dispatcher, ActorExecutor::Options().with_from_queue());
|
||||
} else {
|
||||
waiter_.wait(slot);
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
#include "td/actor/core/IoWorker.h"
|
||||
|
||||
#include "td/actor/core/ActorExecutor.h"
|
||||
#include "td/actor/core/Scheduler.h"
|
||||
|
||||
namespace td {
|
||||
namespace actor {
|
||||
|
@ -42,6 +43,7 @@ bool IoWorker::run_once(double timeout) {
|
|||
auto &poll = SchedulerContext::get()->get_poll();
|
||||
#endif
|
||||
auto &heap = SchedulerContext::get()->get_heap();
|
||||
auto &debug = SchedulerContext::get()->get_debug();
|
||||
|
||||
auto now = Time::now(); // update Time::now_cached()
|
||||
while (!heap.empty() && heap.top_key() <= now) {
|
||||
|
@ -49,6 +51,7 @@ bool IoWorker::run_once(double timeout) {
|
|||
auto *actor_info = ActorInfo::from_heap_node(heap_node);
|
||||
|
||||
auto id = actor_info->unpin();
|
||||
auto lock = debug.start(actor_info->get_name());
|
||||
ActorExecutor executor(*actor_info, dispatcher, ActorExecutor::Options().with_has_poll(true));
|
||||
if (executor.can_send_immediate()) {
|
||||
executor.send_immediate(ActorSignals::one(ActorSignals::Alarm));
|
||||
|
@ -68,6 +71,7 @@ bool IoWorker::run_once(double timeout) {
|
|||
dispatcher.set_alarm_timestamp(message);
|
||||
continue;
|
||||
}
|
||||
auto lock = debug.start(message->get_name());
|
||||
ActorExecutor executor(*message, dispatcher, ActorExecutor::Options().with_from_queue().with_has_poll(true));
|
||||
}
|
||||
queue_.reader_flush();
|
||||
|
|
|
@ -25,6 +25,15 @@ namespace td {
|
|||
namespace actor {
|
||||
namespace core {
|
||||
|
||||
std::atomic<bool> debug;
|
||||
void set_debug(bool flag) {
|
||||
debug = flag;
|
||||
}
|
||||
|
||||
bool need_debug() {
|
||||
return debug.load(std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
Scheduler::Scheduler(std::shared_ptr<SchedulerGroupInfo> scheduler_group_info, SchedulerId id, size_t cpu_threads_count)
|
||||
: scheduler_group_info_(std::move(scheduler_group_info)), cpu_threads_(cpu_threads_count) {
|
||||
scheduler_group_info_->active_scheduler_count++;
|
||||
|
@ -128,13 +137,14 @@ void Scheduler::do_stop() {
|
|||
}
|
||||
|
||||
Scheduler::ContextImpl::ContextImpl(ActorInfoCreator *creator, SchedulerId scheduler_id, CpuWorkerId cpu_worker_id,
|
||||
SchedulerGroupInfo *scheduler_group, Poll *poll, KHeap<double> *heap)
|
||||
SchedulerGroupInfo *scheduler_group, Poll *poll, KHeap<double> *heap, Debug *debug)
|
||||
: creator_(creator)
|
||||
, scheduler_id_(scheduler_id)
|
||||
, cpu_worker_id_(cpu_worker_id)
|
||||
, scheduler_group_(scheduler_group)
|
||||
, poll_(poll)
|
||||
, heap_(heap) {
|
||||
, heap_(heap)
|
||||
, debug_(debug) {
|
||||
}
|
||||
|
||||
SchedulerId Scheduler::ContextImpl::get_scheduler_id() const {
|
||||
|
@ -184,6 +194,9 @@ KHeap<double> &Scheduler::ContextImpl::get_heap() {
|
|||
CHECK(has_heap());
|
||||
return *heap_;
|
||||
}
|
||||
Debug &Scheduler::ContextImpl::get_debug() {
|
||||
return *debug_;
|
||||
}
|
||||
|
||||
void Scheduler::ContextImpl::set_alarm_timestamp(const ActorInfoPtr &actor_info_ptr) {
|
||||
// Ideas for optimization
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#include "td/actor/core/SchedulerId.h"
|
||||
#include "td/actor/core/SchedulerMessage.h"
|
||||
|
||||
#include "td/utils/AtomicRead.h"
|
||||
#include "td/utils/Closure.h"
|
||||
#include "td/utils/common.h"
|
||||
#include "td/utils/format.h"
|
||||
|
@ -65,6 +66,54 @@ namespace actor {
|
|||
namespace core {
|
||||
class IoWorker;
|
||||
|
||||
struct DebugInfo {
|
||||
bool is_active{false};
|
||||
double start_at{0};
|
||||
static constexpr size_t name_size{32};
|
||||
char name[name_size] = {};
|
||||
void set_name(td::Slice from) {
|
||||
from.truncate(name_size - 1);
|
||||
std::memcpy(name, from.data(), from.size());
|
||||
name[from.size()] = 0;
|
||||
}
|
||||
};
|
||||
|
||||
void set_debug(bool flag);
|
||||
bool need_debug();
|
||||
|
||||
struct Debug {
|
||||
public:
|
||||
bool is_on() const {
|
||||
return need_debug();
|
||||
}
|
||||
struct Destructor {
|
||||
void operator()(Debug *info) {
|
||||
info->info_.lock().value().is_active = false;
|
||||
}
|
||||
};
|
||||
|
||||
void read(DebugInfo &info) {
|
||||
info_.read(info);
|
||||
}
|
||||
|
||||
std::unique_ptr<Debug, Destructor> start(td::Slice name) {
|
||||
if (!is_on()) {
|
||||
return {};
|
||||
}
|
||||
{
|
||||
auto lock = info_.lock();
|
||||
auto &value = lock.value();
|
||||
value.is_active = true;
|
||||
value.start_at = Time::now();
|
||||
value.set_name(name);
|
||||
}
|
||||
return std::unique_ptr<Debug, Destructor>(this);
|
||||
}
|
||||
|
||||
private:
|
||||
AtomicRead<DebugInfo> info_;
|
||||
};
|
||||
|
||||
struct WorkerInfo {
|
||||
enum class Type { Io, Cpu } type{Type::Io};
|
||||
WorkerInfo() = default;
|
||||
|
@ -73,6 +122,7 @@ struct WorkerInfo {
|
|||
}
|
||||
ActorInfoCreator actor_info_creator;
|
||||
CpuWorkerId cpu_worker_id;
|
||||
Debug debug;
|
||||
};
|
||||
|
||||
template <class T>
|
||||
|
@ -195,7 +245,7 @@ class Scheduler {
|
|||
class ContextImpl : public SchedulerContext {
|
||||
public:
|
||||
ContextImpl(ActorInfoCreator *creator, SchedulerId scheduler_id, CpuWorkerId cpu_worker_id,
|
||||
SchedulerGroupInfo *scheduler_group, Poll *poll, KHeap<double> *heap);
|
||||
SchedulerGroupInfo *scheduler_group, Poll *poll, KHeap<double> *heap, Debug *debug);
|
||||
|
||||
SchedulerId get_scheduler_id() const override;
|
||||
void add_to_queue(ActorInfoPtr actor_info_ptr, SchedulerId scheduler_id, bool need_poll) override;
|
||||
|
@ -208,6 +258,8 @@ class Scheduler {
|
|||
bool has_heap() override;
|
||||
KHeap<double> &get_heap() override;
|
||||
|
||||
Debug &get_debug() override;
|
||||
|
||||
void set_alarm_timestamp(const ActorInfoPtr &actor_info_ptr) override;
|
||||
|
||||
bool is_stop_requested() override;
|
||||
|
@ -225,6 +277,8 @@ class Scheduler {
|
|||
Poll *poll_;
|
||||
|
||||
KHeap<double> *heap_;
|
||||
|
||||
Debug *debug_;
|
||||
};
|
||||
|
||||
template <class F>
|
||||
|
@ -234,7 +288,8 @@ class Scheduler {
|
|||
#endif
|
||||
bool is_io_worker = worker_info.type == WorkerInfo::Type::Io;
|
||||
ContextImpl context(&worker_info.actor_info_creator, info_->id, worker_info.cpu_worker_id,
|
||||
scheduler_group_info_.get(), is_io_worker ? &poll_ : nullptr, is_io_worker ? &heap_ : nullptr);
|
||||
scheduler_group_info_.get(), is_io_worker ? &poll_ : nullptr, is_io_worker ? &heap_ : nullptr,
|
||||
&worker_info.debug);
|
||||
SchedulerContext::Guard guard(&context);
|
||||
f();
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@ class SchedulerDispatcher {
|
|||
virtual void set_alarm_timestamp(const ActorInfoPtr &actor_info_ptr) = 0;
|
||||
};
|
||||
|
||||
struct Debug;
|
||||
class SchedulerContext : public Context<SchedulerContext>, public SchedulerDispatcher {
|
||||
public:
|
||||
virtual ~SchedulerContext() = default;
|
||||
|
@ -55,6 +56,9 @@ class SchedulerContext : public Context<SchedulerContext>, public SchedulerDispa
|
|||
// Stop all schedulers
|
||||
virtual bool is_stop_requested() = 0;
|
||||
virtual void stop() = 0;
|
||||
|
||||
// Debug
|
||||
virtual Debug &get_debug() = 0;
|
||||
};
|
||||
} // namespace core
|
||||
} // namespace actor
|
||||
|
|
|
@ -675,7 +675,8 @@ TEST(Actor2, actor_function_result) {
|
|||
public:
|
||||
A(std::shared_ptr<td::Destructor> watcher) : watcher_(std::move(watcher)) {
|
||||
}
|
||||
void on_result(uint32 x, uint32 y) {
|
||||
void on_result(uint32 x, td::Result<uint32> r_y) {
|
||||
auto y = r_y.move_as_ok();
|
||||
LOG_CHECK(x * x == y) << x << " " << y;
|
||||
if (--cnt_ == 0) {
|
||||
stop();
|
||||
|
@ -683,7 +684,7 @@ TEST(Actor2, actor_function_result) {
|
|||
}
|
||||
void start_up() {
|
||||
b_ = create_actor<B>(ActorOptions().with_name("B"));
|
||||
cnt_ = 3;
|
||||
cnt_ = 5;
|
||||
send_closure(b_, &B::query, 3, [a = std::make_unique<int>(), self = actor_id(this)](td::Result<uint32> y) {
|
||||
LOG_IF(ERROR, y.is_error()) << y.error();
|
||||
send_closure(self, &A::on_result, 3, y.ok());
|
||||
|
@ -696,6 +697,11 @@ TEST(Actor2, actor_function_result) {
|
|||
CHECK(!self.empty());
|
||||
send_closure(self, &A::on_result, 5, y);
|
||||
});
|
||||
auto future = future_send_closure(b_, &B::query, 7);
|
||||
future.finish(td::promise_send_closure(actor_id(this), &A::on_result, 7));
|
||||
//TODO: deduce Future type (i.e. Future<td::uint32>)
|
||||
auto future2 = future_send_closure<td::uint32>(b_, &B::query_async, 7);
|
||||
future2.finish(td::promise_send_closure(actor_id(this), &A::on_result, 7));
|
||||
}
|
||||
|
||||
private:
|
||||
|
@ -714,12 +720,12 @@ TEST(Actor2, actor_function_result) {
|
|||
}
|
||||
|
||||
TEST(Actor2, actor_ping_pong) {
|
||||
auto group_info = std::make_shared<core::SchedulerGroupInfo>(1);
|
||||
core::Scheduler scheduler{group_info, SchedulerId{0}, 3};
|
||||
Scheduler scheduler{{3}, Scheduler::Paused};
|
||||
sb.clear();
|
||||
scheduler.start();
|
||||
|
||||
auto watcher = td::create_shared_destructor([] { SchedulerContext::get()->stop(); });
|
||||
td::actor::set_debug(true);
|
||||
for (int i = 0; i < 2000; i++) {
|
||||
scheduler.run_in_context([watcher] {
|
||||
class PingPong : public Actor {
|
||||
|
@ -781,9 +787,9 @@ TEST(Actor2, actor_ping_pong) {
|
|||
});
|
||||
}
|
||||
watcher.reset();
|
||||
while (scheduler.run(1000)) {
|
||||
while (scheduler.run(0.1)) {
|
||||
//scheduler.get_debug().dump();
|
||||
}
|
||||
core::Scheduler::close_scheduler_group(*group_info);
|
||||
sb.clear();
|
||||
}
|
||||
|
||||
|
|
|
@ -135,6 +135,78 @@ TEST(Actor, safe_promise) {
|
|||
ASSERT_EQ(res, 3);
|
||||
}
|
||||
|
||||
TEST(Actor, split_promise) {
|
||||
using td::Promise;
|
||||
using td::Result;
|
||||
using td::split_promise;
|
||||
using td::SplitPromise;
|
||||
{
|
||||
td::optional<std::pair<int, double>> x;
|
||||
auto pair = [&](Result<std::pair<int, double>> res) { x = res.move_as_ok(); };
|
||||
static_assert(std::is_same<SplitPromise<decltype(pair)>::ArgT, std::pair<int, double>>::value, "A");
|
||||
static_assert(
|
||||
std::is_same<SplitPromise<decltype(pair)>::SplittedT, std::pair<Promise<int>, Promise<double>>>::value, "A");
|
||||
auto splitted = split_promise(pair);
|
||||
static_assert(std::is_same<decltype(splitted), std::pair<Promise<int>, Promise<double>>>::value, "A");
|
||||
|
||||
splitted.first.set_value(1);
|
||||
splitted.second.set_value(2.0);
|
||||
CHECK(x.unwrap() == std::make_pair(1, 2.0));
|
||||
} // namespace td
|
||||
{
|
||||
td::optional<std::tuple<int, double, std::string>> x;
|
||||
auto triple = [&](Result<std::tuple<int, double, std::string>> res) { x = res.move_as_ok(); };
|
||||
static_assert(std::is_same<SplitPromise<decltype(triple)>::ArgT, std::tuple<int, double, std::string>>::value, "A");
|
||||
static_assert(std::is_same<SplitPromise<decltype(triple)>::SplittedT,
|
||||
std::tuple<Promise<int>, Promise<double>, Promise<std::string>>>::value,
|
||||
"A");
|
||||
auto splitted = split_promise(triple);
|
||||
static_assert(
|
||||
std::is_same<decltype(splitted), std::tuple<Promise<int>, Promise<double>, Promise<std::string>>>::value, "A");
|
||||
std::get<0>(splitted).set_value(1);
|
||||
std::get<1>(splitted).set_value(2.0);
|
||||
std::get<2>(splitted).set_value("hello");
|
||||
CHECK(x.unwrap() == std::make_tuple(1, 2.0, "hello"));
|
||||
}
|
||||
{
|
||||
int code = 0;
|
||||
auto pair = [&](Result<std::pair<int, double>> res) {
|
||||
res.ensure_error();
|
||||
code = res.error().code();
|
||||
};
|
||||
auto splitted = split_promise(td::Promise<std::pair<int, double>>(pair));
|
||||
splitted.second.set_error(td::Status::Error(123, "123"));
|
||||
CHECK(code == 0);
|
||||
splitted.first.set_value(1);
|
||||
CHECK(code == 123);
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Actor, promise_future) {
|
||||
using td::make_promise_future;
|
||||
{
|
||||
auto pf = make_promise_future<int>();
|
||||
td::optional<int> res;
|
||||
pf.second.map([](int x) { return x * 2; }).map([](int x) { return x + 10; }).map([&](int x) {
|
||||
res = x;
|
||||
return td::Unit();
|
||||
});
|
||||
CHECK(!res);
|
||||
pf.first.set_value(6);
|
||||
ASSERT_EQ(22, res.unwrap());
|
||||
}
|
||||
{
|
||||
LOG(ERROR) << "Second test";
|
||||
td::optional<int> res;
|
||||
td::make_future(6)
|
||||
.map([](int x) { return x * 2; })
|
||||
.map([](int x) { return x + 10; })
|
||||
.fmap([&](int x) { return td::make_future(x * 2); })
|
||||
.finish([&](int x) { res = x; });
|
||||
ASSERT_EQ(44, res.unwrap());
|
||||
}
|
||||
}
|
||||
|
||||
TEST(Actor2, actor_lost_promise) {
|
||||
using namespace td::actor;
|
||||
using namespace td;
|
||||
|
@ -459,7 +531,7 @@ class SampleActor : public Actor {
|
|||
detail::current_actor<Printer>().print_a();
|
||||
co_await OnActor(self);
|
||||
LOG(ERROR) << "exit print_a";
|
||||
co_return{};
|
||||
co_return {};
|
||||
}
|
||||
task<Unit> print_b() {
|
||||
auto self = actor_id(this);
|
||||
|
@ -468,7 +540,7 @@ class SampleActor : public Actor {
|
|||
detail::current_actor<Printer>().print_b();
|
||||
co_await OnActor(self);
|
||||
LOG(ERROR) << "exit print_b";
|
||||
co_return{};
|
||||
co_return {};
|
||||
}
|
||||
|
||||
immediate_task run_coroutine() {
|
||||
|
|
|
@ -55,6 +55,10 @@ class KeyValue : public KeyValueReader {
|
|||
virtual Status set(Slice key, Slice value) = 0;
|
||||
virtual Status erase(Slice key) = 0;
|
||||
|
||||
virtual Status begin_write_batch() = 0;
|
||||
virtual Status commit_write_batch() = 0;
|
||||
virtual Status abort_write_batch() = 0;
|
||||
|
||||
virtual Status begin_transaction() = 0;
|
||||
virtual Status commit_transaction() = 0;
|
||||
virtual Status abort_transaction() = 0;
|
||||
|
@ -86,6 +90,16 @@ class PrefixedKeyValue : public KeyValue {
|
|||
return kv_->erase(PSLICE() << prefix_ << key);
|
||||
}
|
||||
|
||||
Status begin_write_batch() override {
|
||||
return kv_->begin_write_batch();
|
||||
}
|
||||
Status commit_write_batch() override {
|
||||
return kv_->commit_write_batch();
|
||||
}
|
||||
Status abort_write_batch() override {
|
||||
return kv_->abort_write_batch();
|
||||
}
|
||||
|
||||
Status begin_transaction() override {
|
||||
return kv_->begin_transaction();
|
||||
}
|
||||
|
|
|
@ -61,6 +61,15 @@ std::unique_ptr<KeyValueReader> MemoryKeyValue::snapshot() {
|
|||
std::string MemoryKeyValue::stats() const {
|
||||
return PSTRING() << "MemoryKeyValueStats{" << tag("get_count", get_count_) << "}";
|
||||
}
|
||||
Status MemoryKeyValue::begin_write_batch() {
|
||||
UNREACHABLE();
|
||||
}
|
||||
Status MemoryKeyValue::commit_write_batch() {
|
||||
UNREACHABLE();
|
||||
}
|
||||
Status MemoryKeyValue::abort_write_batch() {
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
Status MemoryKeyValue::begin_transaction() {
|
||||
UNREACHABLE();
|
||||
|
|
|
@ -29,6 +29,10 @@ class MemoryKeyValue : public KeyValue {
|
|||
Status erase(Slice key) override;
|
||||
Result<size_t> count(Slice prefix) override;
|
||||
|
||||
Status begin_write_batch() override;
|
||||
Status commit_write_batch() override;
|
||||
Status abort_write_batch() override;
|
||||
|
||||
Status begin_transaction() override;
|
||||
Status commit_transaction() override;
|
||||
Status abort_transaction() override;
|
||||
|
|
|
@ -78,7 +78,18 @@ Result<RocksDb> RocksDb::open(std::string path) {
|
|||
options.bytes_per_sync = 1 << 20;
|
||||
options.writable_file_max_buffer_size = 2 << 14;
|
||||
options.statistics = statistics;
|
||||
TRY_STATUS(from_rocksdb(rocksdb::OptimisticTransactionDB::Open(options, std::move(path), &db)));
|
||||
rocksdb::OptimisticTransactionDBOptions occ_options;
|
||||
occ_options.validate_policy = rocksdb::OccValidationPolicy::kValidateSerial;
|
||||
rocksdb::ColumnFamilyOptions cf_options(options);
|
||||
std::vector<rocksdb::ColumnFamilyDescriptor> column_families;
|
||||
column_families.push_back(rocksdb::ColumnFamilyDescriptor(rocksdb::kDefaultColumnFamilyName, cf_options));
|
||||
std::vector<rocksdb::ColumnFamilyHandle *> handles;
|
||||
TRY_STATUS(from_rocksdb(
|
||||
rocksdb::OptimisticTransactionDB::Open(options, occ_options, std::move(path), column_families, &handles, &db)));
|
||||
CHECK(handles.size() == 1);
|
||||
// i can delete the handle since DBImpl is always holding a reference to
|
||||
// default column family
|
||||
delete handles[0];
|
||||
}
|
||||
return RocksDb(std::shared_ptr<rocksdb::OptimisticTransactionDB>(db), std::move(statistics));
|
||||
}
|
||||
|
@ -161,31 +172,41 @@ Result<size_t> RocksDb::count(Slice prefix) {
|
|||
return res;
|
||||
}
|
||||
|
||||
Status RocksDb::begin_write_batch() {
|
||||
CHECK(!transaction_);
|
||||
write_batch_ = std::make_unique<rocksdb::WriteBatch>();
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status RocksDb::begin_transaction() {
|
||||
//write_batch_ = std::make_unique<rocksdb::WriteBatch>();
|
||||
CHECK(!write_batch_);
|
||||
rocksdb::WriteOptions options;
|
||||
options.sync = true;
|
||||
transaction_.reset(db_->BeginTransaction(options, {}));
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status RocksDb::commit_transaction() {
|
||||
//CHECK(write_batch_);
|
||||
//auto write_batch = std::move(write_batch_);
|
||||
//rocksdb::WriteOptions options;
|
||||
//options.sync = true;
|
||||
//TRY_STATUS(from_rocksdb(db_->Write(options, write_batch.get())));
|
||||
//return Status::OK();
|
||||
Status RocksDb::commit_write_batch() {
|
||||
CHECK(write_batch_);
|
||||
auto write_batch = std::move(write_batch_);
|
||||
rocksdb::WriteOptions options;
|
||||
options.sync = true;
|
||||
return from_rocksdb(db_->Write(options, write_batch.get()));
|
||||
}
|
||||
|
||||
Status RocksDb::commit_transaction() {
|
||||
CHECK(transaction_);
|
||||
auto res = from_rocksdb(transaction_->Commit());
|
||||
transaction_.reset();
|
||||
return res;
|
||||
auto transaction = std::move(transaction_);
|
||||
return from_rocksdb(transaction->Commit());
|
||||
}
|
||||
|
||||
Status RocksDb::abort_write_batch() {
|
||||
CHECK(write_batch_);
|
||||
write_batch_.reset();
|
||||
return Status::OK();
|
||||
}
|
||||
|
||||
Status RocksDb::abort_transaction() {
|
||||
//CHECK(write_batch_);
|
||||
//write_batch_.reset();
|
||||
CHECK(transaction_);
|
||||
transaction_.reset();
|
||||
return Status::OK();
|
||||
|
|
|
@ -45,6 +45,10 @@ class RocksDb : public KeyValue {
|
|||
Status erase(Slice key) override;
|
||||
Result<size_t> count(Slice prefix) override;
|
||||
|
||||
Status begin_write_batch() override;
|
||||
Status commit_write_batch() override;
|
||||
Status abort_write_batch() override;
|
||||
|
||||
Status begin_transaction() override;
|
||||
Status commit_transaction() override;
|
||||
Status abort_transaction() override;
|
||||
|
|
90
tdutils/td/utils/AtomicRead.h
Normal file
90
tdutils/td/utils/AtomicRead.h
Normal file
|
@ -0,0 +1,90 @@
|
|||
/*
|
||||
This file is part of TON Blockchain source code.
|
||||
|
||||
TON Blockchain is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
TON Blockchain is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with TON Blockchain. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
In addition, as a special exception, the copyright holders give permission
|
||||
to link the code of portions of this program with the OpenSSL library.
|
||||
You must obey the GNU General Public License in all respects for all
|
||||
of the code used other than OpenSSL. If you modify file(s) with this
|
||||
exception, you may extend this exception to your version of the file(s),
|
||||
but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
exception statement from your version. If you delete this exception statement
|
||||
from all source files in the program, then also delete it here.
|
||||
|
||||
Copyright 2019-2020 Telegram Systems LLP
|
||||
*/
|
||||
#include <atomic>
|
||||
|
||||
#include "td/utils/common.h"
|
||||
#include "td/utils/port/thread.h"
|
||||
namespace td {
|
||||
template <class T>
|
||||
|
||||
class AtomicRead {
|
||||
public:
|
||||
void read(T &dest) const {
|
||||
while (true) {
|
||||
static_assert(std::is_trivially_copyable<T>::value, "T must be trivially copyable");
|
||||
auto version_before = version.load();
|
||||
memcpy(&dest, &value, sizeof(dest));
|
||||
auto version_after = version.load();
|
||||
if (version_before == version_after && version_before % 2 == 0) {
|
||||
break;
|
||||
}
|
||||
td::this_thread::yield();
|
||||
}
|
||||
}
|
||||
|
||||
friend struct Write;
|
||||
struct Write {
|
||||
explicit Write(AtomicRead *read) {
|
||||
read->do_lock();
|
||||
ptr.reset(read);
|
||||
}
|
||||
struct Destructor {
|
||||
void operator()(AtomicRead *read) const {
|
||||
read->do_unlock();
|
||||
}
|
||||
};
|
||||
T &operator*() {
|
||||
return value();
|
||||
}
|
||||
T *operator->() {
|
||||
return &value();
|
||||
}
|
||||
T &value() {
|
||||
CHECK(ptr);
|
||||
return ptr->value;
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<AtomicRead, Destructor> ptr;
|
||||
};
|
||||
Write lock() {
|
||||
return Write(this);
|
||||
}
|
||||
|
||||
private:
|
||||
std::atomic<td::uint64> version{0};
|
||||
T value;
|
||||
|
||||
void do_lock() {
|
||||
CHECK(++version % 2 == 1);
|
||||
}
|
||||
void do_unlock() {
|
||||
CHECK(++version % 2 == 0);
|
||||
}
|
||||
};
|
||||
}; // namespace td
|
|
@ -57,6 +57,15 @@
|
|||
} \
|
||||
}
|
||||
|
||||
#define TRY_STATUS_PROMISE_PREFIX(promise_name, status, prefix) \
|
||||
{ \
|
||||
auto try_status = (status); \
|
||||
if (try_status.is_error()) { \
|
||||
promise_name.set_error(try_status.move_as_error_prefix(prefix)); \
|
||||
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) \
|
||||
|
@ -437,6 +446,7 @@ class Status {
|
|||
template <class T = Unit>
|
||||
class Result {
|
||||
public:
|
||||
using ValueT = T;
|
||||
Result() : status_(Status::Error<-1>()) {
|
||||
}
|
||||
template <class S, std::enable_if_t<!std::is_same<std::decay_t<S>, Result>::value, int> = 0>
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
#include "td/utils/tests.h"
|
||||
#include "td/utils/benchmark.h"
|
||||
|
||||
#include "td/utils/AtomicRead.h"
|
||||
#include "td/utils/StealingQueue.h"
|
||||
#include "td/utils/MpmcQueue.h"
|
||||
|
||||
|
@ -79,6 +80,46 @@ TEST(AtomicRead, simple) {
|
|||
thread.join();
|
||||
}
|
||||
}
|
||||
TEST(AtomicRead, simple2) {
|
||||
td::Stage run;
|
||||
td::Stage check;
|
||||
|
||||
size_t threads_n = 10;
|
||||
std::vector<td::thread> threads;
|
||||
|
||||
struct Value {
|
||||
td::uint64 value = 0;
|
||||
char str[50] = "0 0 0 0";
|
||||
};
|
||||
AtomicRead<Value> value;
|
||||
|
||||
auto to_str = [](size_t i) { return PSTRING() << i << " " << i << " " << i << " " << i; };
|
||||
for (size_t i = 0; i < threads_n; i++) {
|
||||
threads.push_back(td::thread([&, id = static_cast<uint32>(i)] {
|
||||
for (uint64 round = 1; round < 10000; round++) {
|
||||
if (id == 0) {
|
||||
}
|
||||
run.wait(round * threads_n);
|
||||
if (id == 0) {
|
||||
auto x = value.lock();
|
||||
x->value = round;
|
||||
auto str = to_str(round);
|
||||
memcpy(x->str, str.c_str(), str.size() + 1);
|
||||
} else {
|
||||
Value x;
|
||||
value.read(x);
|
||||
LOG_CHECK(x.value == round || x.value == round - 1) << x.value << " " << round;
|
||||
CHECK(x.str == to_str(x.value));
|
||||
}
|
||||
check.wait(round * threads_n);
|
||||
}
|
||||
}));
|
||||
}
|
||||
for (auto &thread : threads) {
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(StealingQueue, simple) {
|
||||
uint64 sum;
|
||||
std::atomic<uint64> got_sum;
|
||||
|
|
97
test/test-rocksdb.cpp
Normal file
97
test/test-rocksdb.cpp
Normal file
|
@ -0,0 +1,97 @@
|
|||
/*
|
||||
This file is part of TON Blockchain source code.
|
||||
|
||||
TON Blockchain is free software; you can redistribute it and/or
|
||||
modify it under the terms of the GNU General Public License
|
||||
as published by the Free Software Foundation; either version 2
|
||||
of the License, or (at your option) any later version.
|
||||
|
||||
TON Blockchain is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with TON Blockchain. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
In addition, as a special exception, the copyright holders give permission
|
||||
to link the code of portions of this program with the OpenSSL library.
|
||||
You must obey the GNU General Public License in all respects for all
|
||||
of the code used other than OpenSSL. If you modify file(s) with this
|
||||
exception, you may extend this exception to your version of the file(s),
|
||||
but you are not obligated to do so. If you do not wish to do so, delete this
|
||||
exception statement from your version. If you delete this exception statement
|
||||
from all source files in the program, then also delete it here.
|
||||
|
||||
Copyright 2017-2020 Telegram Systems LLP
|
||||
*/
|
||||
#include "td/db/RocksDb.h"
|
||||
#include "td/utils/OptionsParser.h"
|
||||
#include "td/utils/port/signals.h"
|
||||
#include "td/utils/port/path.h"
|
||||
#include "td/utils/StringBuilder.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
std::string dir = "stress-db";
|
||||
int db_n = 20;
|
||||
int key_n = 1000000;
|
||||
|
||||
std::string get_db_path(int i) {
|
||||
return PSTRING() << dir << TD_DIR_SLASH << "db-" << i;
|
||||
}
|
||||
|
||||
void do_create_db() {
|
||||
td::mkdir(dir).ensure();
|
||||
for (int db_i = 0; db_i < db_n; db_i++) {
|
||||
LOG(ERROR) << "db_i=" << db_i;
|
||||
auto db = td::RocksDb::open(get_db_path(db_i)).move_as_ok();
|
||||
for (int key_i = 0; key_i < key_n; key_i++) {
|
||||
db.set(PSLICE() << key_i, PSLICE() << key_i);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void do_load_db() {
|
||||
static std::vector<td::RocksDb> dbs;
|
||||
for (int db_i = 0; db_i < db_n; db_i++) {
|
||||
LOG(ERROR) << "db_i=" << db_i;
|
||||
auto db = td::RocksDb::open(get_db_path(db_i)).move_as_ok();
|
||||
for (int key_i = 0; key_i < key_n; key_i++) {
|
||||
std::string value;
|
||||
db.get(PSLICE() << key_i, value).ensure();
|
||||
}
|
||||
dbs.push_back(std::move(db));
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
SET_VERBOSITY_LEVEL(verbosity_DEBUG);
|
||||
td::set_default_failure_signal_handler().ensure();
|
||||
|
||||
td::OptionsParser p;
|
||||
p.set_description("test basic adnl functionality");
|
||||
p.add_option('h', "help", "prints_help", [&]() {
|
||||
char b[10240];
|
||||
td::StringBuilder sb(td::MutableSlice{b, 10000});
|
||||
sb << p;
|
||||
std::cout << sb.as_cslice().c_str();
|
||||
std::exit(2);
|
||||
return td::Status::OK();
|
||||
});
|
||||
bool create_db = false;
|
||||
p.add_option('c', "create", "create test db", [&] {
|
||||
create_db = true;
|
||||
return td::Status::OK();
|
||||
});
|
||||
|
||||
auto res = p.run(argc, argv);
|
||||
LOG_IF(FATAL, res.is_error()) << res.error();
|
||||
|
||||
if (create_db) {
|
||||
do_create_db();
|
||||
} else {
|
||||
do_load_db();
|
||||
}
|
||||
return 0;
|
||||
}
|
2
third-party/abseil-cpp
vendored
2
third-party/abseil-cpp
vendored
|
@ -1 +1 @@
|
|||
Subproject commit 325fd7b042ff4ec34f7dd32e602cd81ad0e24b22
|
||||
Subproject commit df3ea785d8c30a9503321a3d35ee7d35808f190d
|
2
third-party/rocksdb
vendored
2
third-party/rocksdb
vendored
|
@ -1 +1 @@
|
|||
Subproject commit d6f2ecf49c28fee225477d39e2a1535a87919afe
|
||||
Subproject commit 0915c99f01b46f50af8e02da8b6528156f584b7c
|
|
@ -25,7 +25,7 @@ keyStoreTypeInMemory = KeyStoreType;
|
|||
config config:string blockchain_name:string use_callbacks_for_network:Bool ignore_cache:Bool = Config;
|
||||
|
||||
options config:config keystore_type:KeyStoreType = Options;
|
||||
options.configInfo default_wallet_id:int64 = options.ConfigInfo;
|
||||
options.configInfo default_wallet_id:int64 default_rwallet_init_public_key:string = options.ConfigInfo;
|
||||
options.info config_info:options.configInfo = options.Info;
|
||||
|
||||
key public_key:string secret:secureBytes = Key;
|
||||
|
@ -55,6 +55,8 @@ raw.message source:accountAddress destination:accountAddress value:int64 fwd_fee
|
|||
raw.transaction utime:int53 data:bytes transaction_id:internal.transactionId fee:int64 storage_fee:int64 other_fee:int64 in_msg:raw.message out_msgs:vector<raw.message> = raw.Transaction;
|
||||
raw.transactions transactions:vector<raw.transaction> previous_transaction_id:internal.transactionId = raw.Transactions;
|
||||
|
||||
pchan.config alice_public_key:string alice_address:accountAddress bob_public_key:string bob_address:accountAddress init_timeout:int32 close_timeout:int32 channel_id:int64 = pchan.Config;
|
||||
|
||||
raw.initialAccountState code:bytes data:bytes = InitialAccountState;
|
||||
testGiver.initialAccountState = InitialAccountState;
|
||||
testWallet.initialAccountState public_key:string = InitialAccountState;
|
||||
|
@ -62,7 +64,13 @@ wallet.initialAccountState public_key:string = InitialAccountState;
|
|||
wallet.v3.initialAccountState public_key:string wallet_id:int64 = InitialAccountState;
|
||||
wallet.highload.v1.initialAccountState public_key:string wallet_id:int64 = InitialAccountState;
|
||||
wallet.highload.v2.initialAccountState public_key:string wallet_id:int64 = InitialAccountState;
|
||||
|
||||
rwallet.limit seconds:int32 value:int64 = rwallet.Limit;
|
||||
rwallet.config start_at:int53 limits:vector<rwallet.limit> = rwallet.Config;
|
||||
rwallet.initialAccountState init_public_key:string public_key:string wallet_id:int64 = InitialAccountState;
|
||||
|
||||
dns.initialAccountState public_key:string wallet_id:int64 = InitialAccountState;
|
||||
pchan.initialAccountState config:pchan.config = InitialAccountState;
|
||||
|
||||
raw.accountState code:bytes data:bytes frozen_hash:bytes = AccountState;
|
||||
testWallet.accountState seqno:int32 = AccountState;
|
||||
|
@ -72,6 +80,13 @@ wallet.highload.v1.accountState wallet_id:int64 seqno:int32 = AccountState;
|
|||
wallet.highload.v2.accountState wallet_id:int64 = AccountState;
|
||||
testGiver.accountState seqno:int32 = AccountState;
|
||||
dns.accountState wallet_id:int64 = AccountState;
|
||||
rwallet.accountState wallet_id:int64 seqno:int32 unlocked_balance:int64 config:rwallet.config = AccountState;
|
||||
|
||||
pchan.stateInit signed_A:Bool signed_B:Bool min_A:int64 min_B:int64 expire_at:int53 A:int64 B:int64 = pchan.State;
|
||||
pchan.stateClose signed_A:Bool signed_B:Bool min_A:int64 min_B:int64 expire_at:int53 A:int64 B:int64 = pchan.State;
|
||||
pchan.statePayout A:int64 B:int64 = pchan.State;
|
||||
|
||||
pchan.accountState config:pchan.config state:pchan.State description:string = AccountState;
|
||||
uninited.accountState frozen_hash:bytes = AccountState;
|
||||
|
||||
fullAccountState balance:int64 last_transaction_id:internal.transactionId block_id:ton.blockIdExt sync_utime:int53 account_state:AccountState = FullAccountState;
|
||||
|
@ -83,7 +98,7 @@ syncStateInProgress from_seqno:int32 to_seqno:int32 current_seqno:int32 = SyncSt
|
|||
// MSG
|
||||
//
|
||||
|
||||
msg.dataRaw body:bytes = msg.Data;
|
||||
msg.dataRaw body:bytes init_state:bytes = msg.Data;
|
||||
msg.dataText text:bytes = msg.Data;
|
||||
msg.dataDecryptedText text:bytes = msg.Data;
|
||||
msg.dataEncryptedText text:bytes = msg.Data;
|
||||
|
@ -115,6 +130,21 @@ dns.actionSet entry:dns.entry = dns.Action;
|
|||
|
||||
dns.resolved entries:vector<dns.entry> = dns.Resolved;
|
||||
|
||||
|
||||
//
|
||||
// Payment channel
|
||||
//
|
||||
pchan.promise signature:bytes promise_A:int64 promise_B:int64 channel_id:int64 = pchan.Promise;
|
||||
|
||||
pchan.actionInit inc_A:int64 inc_B:int64 min_A:int64 min_B:int64 = pchan.Action;
|
||||
pchan.actionClose extra_A:int64 extra_B:int64 promise:pchan.promise = pchan.Action;
|
||||
pchan.actionTimeout = pchan.Action;
|
||||
|
||||
//
|
||||
// Restricted wallet initialization
|
||||
//
|
||||
rwallet.actionInit config:rwallet.config = rwallet.Action;
|
||||
|
||||
//
|
||||
// Actions
|
||||
//
|
||||
|
@ -122,12 +152,14 @@ dns.resolved entries:vector<dns.entry> = dns.Resolved;
|
|||
actionNoop = Action;
|
||||
actionMsg messages:vector<msg.message> allow_send_to_uninited:Bool = Action;
|
||||
actionDns actions:vector<dns.Action> = Action;
|
||||
actionPchan action:pchan.Action = Action;
|
||||
actionRwallet action:rwallet.actionInit = Action;
|
||||
//actionMultisig actions:vector<multisig.order> = Action;
|
||||
|
||||
fees in_fwd_fee:int53 storage_fee:int53 gas_fee:int53 fwd_fee:int53 = Fees;
|
||||
query.fees source_fees:fees destination_fees:vector<fees> = query.Fees;
|
||||
// query.emulationResult exit_code:int32 fees:fees = query.EmulationResult;
|
||||
query.info id:int53 valid_until:int53 body_hash:bytes = query.Info;
|
||||
query.info id:int53 valid_until:int53 body_hash:bytes body:bytes init_state:bytes = query.Info;
|
||||
|
||||
tvm.slice bytes:bytes = tvm.Slice;
|
||||
tvm.cell bytes:bytes = tvm.Cell;
|
||||
|
@ -218,7 +250,7 @@ sync = ton.BlockIdExt;
|
|||
getAccountAddress initial_account_state:InitialAccountState revision:int32 = AccountAddress;
|
||||
guessAccountRevision initial_account_state:InitialAccountState = AccountRevisionList;
|
||||
getAccountState account_address:accountAddress = FullAccountState;
|
||||
createQuery private_key:InputKey address:accountAddress timeout:int32 action:Action = query.Info;
|
||||
createQuery private_key:InputKey address:accountAddress timeout:int32 action:Action initial_account_state:InitialAccountState = query.Info;
|
||||
|
||||
msg.decrypt input_key:InputKey data:msg.dataEncryptedArray = msg.DataDecryptedArray;
|
||||
msg.decryptWithProof proof:bytes data:msg.dataEncrypted = msg.Data;
|
||||
|
@ -238,6 +270,12 @@ smc.runGetMethod id:int53 method:smc.MethodId stack:vector<tvm.StackEntry> = smc
|
|||
|
||||
dns.resolve account_address:accountAddress name:string category:int32 ttl:int32 = dns.Resolved;
|
||||
|
||||
pchan.signPromise input_key:InputKey promise:pchan.promise = pchan.Promise;
|
||||
pchan.validatePromise public_key:bytes promise:pchan.promise = Ok;
|
||||
|
||||
pchan.packPromise promise:pchan.promise = Data;
|
||||
pchan.unpackPromise data:secureBytes = pchan.Promise;
|
||||
|
||||
onLiteServerQueryResult id:int64 bytes:bytes = Ok;
|
||||
onLiteServerQueryError id:int64 error:error = Ok;
|
||||
|
||||
|
|
Binary file not shown.
|
@ -121,11 +121,12 @@ struct TransactionId {
|
|||
};
|
||||
|
||||
struct AccountState {
|
||||
enum Type { Empty, Wallet, Dns, Unknown } type{Empty};
|
||||
enum Type { Empty, Wallet, Dns, Pchan, Unknown } type{Empty};
|
||||
td::int64 sync_utime{-1};
|
||||
td::int64 balance{-1};
|
||||
TransactionId last_transaction_id;
|
||||
std::string address;
|
||||
tonlib_api::object_ptr<tonlib_api::fullAccountState> state;
|
||||
|
||||
bool is_inited() const {
|
||||
return type != Empty;
|
||||
|
@ -187,6 +188,10 @@ AccountState get_account_state(Client& client, std::string address) {
|
|||
case tonlib_api::dns_accountState::ID:
|
||||
res.type = AccountState::Dns;
|
||||
break;
|
||||
case tonlib_api::pchan_accountState::ID:
|
||||
res.type = AccountState::Pchan;
|
||||
res.state = std::move(state);
|
||||
break;
|
||||
default:
|
||||
res.type = AccountState::Unknown;
|
||||
break;
|
||||
|
@ -228,15 +233,38 @@ struct QueryInfo {
|
|||
std::string body_hash;
|
||||
};
|
||||
|
||||
struct Message {
|
||||
bool encrypted = false;
|
||||
td::optional<std::string> text;
|
||||
td::optional<td::string> raw;
|
||||
td::optional<td::string> init_state;
|
||||
static Message create_text(std::string text, bool encrypted) {
|
||||
Message res;
|
||||
res.text = text;
|
||||
res.encrypted = encrypted;
|
||||
return res;
|
||||
}
|
||||
static Message create_raw(std::string raw, std::string init_state) {
|
||||
Message res;
|
||||
res.raw = raw;
|
||||
res.init_state = init_state;
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
td::Result<QueryId> create_send_grams_query(Client& client, const Wallet& source, std::string destination,
|
||||
td::int64 amount, bool encrypted, std::string message, bool force = false,
|
||||
int timeout = 0, bool fake = false) {
|
||||
td::int64 amount, Message message, bool force = false, int timeout = 0,
|
||||
bool fake = false) {
|
||||
std::vector<tonlib_api::object_ptr<tonlib_api::msg_message>> msgs;
|
||||
tonlib_api::object_ptr<tonlib_api::msg_Data> data;
|
||||
if (encrypted) {
|
||||
data = tonlib_api::make_object<tonlib_api::msg_dataDecryptedText>(std::move(message));
|
||||
if (message.text) {
|
||||
if (message.encrypted) {
|
||||
data = tonlib_api::make_object<tonlib_api::msg_dataDecryptedText>(message.text.unwrap());
|
||||
} else {
|
||||
data = tonlib_api::make_object<tonlib_api::msg_dataText>(message.text.unwrap());
|
||||
}
|
||||
} else {
|
||||
data = tonlib_api::make_object<tonlib_api::msg_dataText>(std::move(message));
|
||||
data = tonlib_api::make_object<tonlib_api::msg_dataRaw>(message.raw.unwrap(), message.init_state.unwrap());
|
||||
}
|
||||
msgs.push_back(tonlib_api::make_object<tonlib_api::msg_message>(
|
||||
tonlib_api::make_object<tonlib_api::accountAddress>(destination), "", amount, std::move(data)));
|
||||
|
@ -244,7 +272,7 @@ td::Result<QueryId> create_send_grams_query(Client& client, const Wallet& source
|
|||
auto r_id =
|
||||
sync_send(client, tonlib_api::make_object<tonlib_api::createQuery>(
|
||||
fake ? source.key.get_fake_input_key() : source.key.get_input_key(), source.get_address(),
|
||||
timeout, tonlib_api::make_object<tonlib_api::actionMsg>(std::move(msgs), force)));
|
||||
timeout, tonlib_api::make_object<tonlib_api::actionMsg>(std::move(msgs), force), nullptr));
|
||||
TRY_RESULT(id, std::move(r_id));
|
||||
return QueryId{id->id_};
|
||||
}
|
||||
|
@ -253,7 +281,7 @@ td::Result<QueryId> create_update_dns_query(Client& client, const Wallet& dns,
|
|||
std::vector<tonlib_api::object_ptr<tonlib_api::dns_Action>> entries) {
|
||||
using namespace ton::tonlib_api;
|
||||
auto r_id = sync_send(client, make_object<createQuery>(dns.key.get_input_key(), dns.get_address(), 60,
|
||||
make_object<actionDns>(std::move(entries))));
|
||||
make_object<actionDns>(std::move(entries)), nullptr));
|
||||
TRY_RESULT(id, std::move(r_id));
|
||||
return QueryId{id->id_};
|
||||
}
|
||||
|
@ -289,7 +317,7 @@ td::Result<AccountState> wait_state_change(Client& client, const AccountState& o
|
|||
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;
|
||||
return std::move(new_state);
|
||||
}
|
||||
if (valid_until != 0 && new_state.sync_utime >= valid_until) {
|
||||
return td::Status::Error("valid_until expired");
|
||||
|
@ -325,18 +353,19 @@ td::Status transfer_grams(Client& client, const Wallet& wallet, std::string addr
|
|||
|
||||
LOG(INFO) << "Transfer: create query " << (double)amount / Gramm << " from " << wallet.address << " to " << address;
|
||||
bool encrypt = true;
|
||||
auto r_query_id = create_send_grams_query(client, wallet, address, amount, encrypt, message, fast);
|
||||
auto r_query_id =
|
||||
create_send_grams_query(client, wallet, address, amount, Message::create_text(message, encrypt), fast);
|
||||
if (r_query_id.is_error()) {
|
||||
LOG(INFO) << "Send query WITHOUT message encryption " << r_query_id.error();
|
||||
encrypt = false;
|
||||
r_query_id = create_send_grams_query(client, wallet, address, amount, encrypt, message, fast);
|
||||
r_query_id = create_send_grams_query(client, wallet, address, amount, Message::create_text(message, encrypt), fast);
|
||||
} else {
|
||||
LOG(INFO) << "Send query WITH message encryption";
|
||||
}
|
||||
if (r_query_id.is_error() && td::begins_with(r_query_id.error().message(), "DANGEROUS_TRANSACTION")) {
|
||||
ASSERT_TRUE(dst_state.type == AccountState::Empty);
|
||||
LOG(INFO) << "Transfer: recreate query due to DANGEROUS_TRANSACTION error";
|
||||
r_query_id = create_send_grams_query(client, wallet, address, amount, false, message, true);
|
||||
r_query_id = create_send_grams_query(client, wallet, address, amount, Message::create_text(message, encrypt), true);
|
||||
}
|
||||
|
||||
r_query_id.ensure();
|
||||
|
@ -458,8 +487,9 @@ Wallet create_empty_dns(Client& client) {
|
|||
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, false, "???", true, 0, true).move_as_ok();
|
||||
auto query_id = create_send_grams_query(client, wallet_a, wallet_b.address, 0, Message::create_text("???", false),
|
||||
true, 0, true)
|
||||
.move_as_ok();
|
||||
auto fees1 = query_estimate_fees(client, query_id, false);
|
||||
auto fees2 = query_estimate_fees(client, query_id, true);
|
||||
LOG(INFO) << "Fee without ignore_chksig\t" << fees1;
|
||||
|
@ -564,6 +594,141 @@ void dns_resolve(Client& client, const Wallet& dns, std::string name) {
|
|||
LOG(INFO) << "OK";
|
||||
}
|
||||
|
||||
void test_paychan(Client& client, const Wallet& giver_wallet) {
|
||||
LOG(INFO) << "Start test paychan";
|
||||
auto alice = create_empty_wallet(client);
|
||||
auto bob = create_empty_wallet(client);
|
||||
|
||||
using namespace ton::tonlib_api;
|
||||
int init_timeout = 10;
|
||||
int close_timeout = 10;
|
||||
int64 channel_id = static_cast<td::int64>(td::Random::fast_uint64());
|
||||
|
||||
auto get_initial_state = [&] {
|
||||
return make_object<pchan_initialAccountState>(make_object<pchan_config>(alice.key.public_key, alice.get_address(),
|
||||
bob.key.public_key, bob.get_address(),
|
||||
init_timeout, close_timeout, channel_id));
|
||||
};
|
||||
auto account_address = sync_send(client, make_object<tonlib_api::getAccountAddress>(get_initial_state(), -1))
|
||||
.move_as_ok()
|
||||
->account_address_;
|
||||
auto get_account_address = [&] { return make_object<accountAddress>(account_address); };
|
||||
|
||||
//pchan.actionInit inc_A:int64 inc_B:int64 min_A:int64 min_B:int64 = pchan.Action;
|
||||
//pchan.actionClose extra_A:int64 extra_B:int64 promise:pchan.promise = pchan.Action;
|
||||
//pchan.actionTimeout = pchan.Action;
|
||||
|
||||
auto create_init_query = [&](auto& wallet, td::int64 inc_A, td::int64 inc_B) {
|
||||
auto action = make_object<tonlib_api::actionPchan>(make_object<tonlib_api::pchan_actionInit>(inc_A, inc_B, 0, 0));
|
||||
auto r_id = sync_send(client, make_object<createQuery>(wallet.key.get_input_key(), get_account_address(), 60,
|
||||
std::move(action), get_initial_state()));
|
||||
r_id.ensure();
|
||||
return r_id.move_as_ok();
|
||||
};
|
||||
|
||||
auto send_query_via = [&](auto& wallet, auto query_info, auto destination, td::int64 value) {
|
||||
auto transfer_id =
|
||||
create_send_grams_query(client, wallet, std::move(destination), value,
|
||||
Message::create_raw(query_info->body_, query_info->init_state_), true, 60)
|
||||
.move_as_ok();
|
||||
::query_send(client, transfer_id);
|
||||
};
|
||||
send_query_via(giver_wallet, create_init_query(alice, 1 * Gramm, 0), account_address, 11 * Gramm / 10);
|
||||
send_query_via(giver_wallet, create_init_query(bob, 0, 1 * Gramm), account_address, 11 * Gramm / 10);
|
||||
|
||||
LOG(INFO) << "Wait till pchan " << account_address << " is inited ";
|
||||
td::optional<td::int64> now;
|
||||
while (true) {
|
||||
client.receive(1);
|
||||
auto state = get_account_state(client, account_address);
|
||||
if (!now) {
|
||||
now = state.sync_utime;
|
||||
}
|
||||
CHECK(now.value() + 60 > state.sync_utime);
|
||||
if (state.type != ::AccountState::Pchan) {
|
||||
continue;
|
||||
}
|
||||
auto pchan_state = tonlib_api::move_object_as<tonlib_api::pchan_accountState>(state.state->account_state_);
|
||||
if (pchan_state->state_->get_id() != tonlib_api::pchan_stateClose::ID) {
|
||||
continue;
|
||||
}
|
||||
LOG(INFO) << "Account type: " << state.type << " " << state.sync_utime << "\n" << to_string(pchan_state->state_);
|
||||
break;
|
||||
}
|
||||
|
||||
auto create_close_query = [&](auto& x_wallet, auto& y_wallet, td::int64 A, td::int64 B) {
|
||||
auto p = sync_send(client, make_object<pchan_signPromise>(y_wallet.key.get_input_key(),
|
||||
make_object<pchan_promise>("", A, B, channel_id)))
|
||||
.move_as_ok();
|
||||
auto action = make_object<tonlib_api::actionPchan>(make_object<tonlib_api::pchan_actionClose>(0, 0, std::move(p)));
|
||||
auto r_id = sync_send(client, make_object<createQuery>(x_wallet.key.get_input_key(), get_account_address(), 60,
|
||||
std::move(action), get_initial_state()));
|
||||
r_id.ensure();
|
||||
return r_id.move_as_ok();
|
||||
};
|
||||
|
||||
//auto send_query_ext = [&](auto query_info) { ::query_send(client, QueryId{query_info->id_}); };
|
||||
|
||||
send_query_via(giver_wallet, create_close_query(alice, bob, 0, 10 * Gramm), account_address, Gramm / 10);
|
||||
send_query_via(giver_wallet, create_close_query(bob, alice, 11 * Gramm, 0), account_address, Gramm / 10);
|
||||
|
||||
LOG(INFO) << "Wait till pchan " << account_address << " is closed ";
|
||||
now = {};
|
||||
int64 payout_A = 0;
|
||||
int64 payout_B = 0;
|
||||
while (true) {
|
||||
client.receive(1);
|
||||
auto state = get_account_state(client, account_address);
|
||||
if (!now) {
|
||||
now = state.sync_utime;
|
||||
}
|
||||
CHECK(now.value() + 60 > state.sync_utime);
|
||||
if (state.type != ::AccountState::Pchan) {
|
||||
continue;
|
||||
}
|
||||
auto pchan_state = tonlib_api::move_object_as<tonlib_api::pchan_accountState>(state.state->account_state_);
|
||||
if (pchan_state->state_->get_id() != tonlib_api::pchan_statePayout::ID) {
|
||||
continue;
|
||||
}
|
||||
LOG(INFO) << "Account type: " << state.type << " " << state.sync_utime << "\n" << to_string(pchan_state->state_);
|
||||
auto payout = tonlib_api::move_object_as<tonlib_api::pchan_statePayout>(pchan_state->state_);
|
||||
payout_A = payout->A_;
|
||||
payout_B = payout->B_;
|
||||
break;
|
||||
}
|
||||
|
||||
LOG(INFO) << "Wait till Alice has its share";
|
||||
now = {};
|
||||
while (payout_A != 0) {
|
||||
auto state = get_account_state(client, alice.address);
|
||||
if (!now) {
|
||||
now = state.sync_utime;
|
||||
}
|
||||
CHECK(now.value() + 60 > state.sync_utime);
|
||||
if (state.balance > 0) {
|
||||
ASSERT_EQ(payout_A, state.balance);
|
||||
LOG(INFO) << "Alice got: " << state.balance;
|
||||
break;
|
||||
}
|
||||
client.receive(1);
|
||||
}
|
||||
LOG(INFO) << "Wait till Bob has its share";
|
||||
now = {};
|
||||
while (payout_B != 0) {
|
||||
auto state = get_account_state(client, bob.address);
|
||||
if (!now) {
|
||||
now = state.sync_utime;
|
||||
}
|
||||
CHECK(now.value() + 60 > state.sync_utime);
|
||||
if (state.balance > 0) {
|
||||
ASSERT_EQ(payout_B, state.balance);
|
||||
LOG(INFO) << "Bob got: " << state.balance;
|
||||
break;
|
||||
}
|
||||
client.receive(1);
|
||||
}
|
||||
}
|
||||
|
||||
void test_dns(Client& client, const Wallet& giver_wallet) {
|
||||
auto A = create_empty_dns(client);
|
||||
auto A_B = create_empty_dns(client);
|
||||
|
@ -669,6 +834,7 @@ int main(int argc, char* argv[]) {
|
|||
// give wallet with some test grams to run test
|
||||
auto giver_wallet = import_wallet_from_pkey(client, giver_key_str, giver_key_pwd);
|
||||
|
||||
test_paychan(client, giver_wallet);
|
||||
test_dns(client, giver_wallet);
|
||||
test_back_and_forth_transfer(client, giver_wallet, false);
|
||||
test_back_and_forth_transfer(client, giver_wallet, true);
|
||||
|
|
|
@ -101,6 +101,7 @@ class Client::Impl final {
|
|||
scheduler_.run_in_context_external([] { td::actor::SchedulerContext::get()->stop(); });
|
||||
LOG(ERROR) << "join";
|
||||
scheduler_thread_.join();
|
||||
LOG(ERROR) << "join - done";
|
||||
}
|
||||
|
||||
private:
|
||||
|
|
|
@ -121,6 +121,26 @@ td::Result<Config> Config::parse(std::string str) {
|
|||
res.init_block_id = init_block_id;
|
||||
}
|
||||
|
||||
auto r_hardforks = td::get_json_object_field(validator, "hardforks", td::JsonValue::Type::Array, false);
|
||||
if (r_hardforks.is_ok()) {
|
||||
auto hardforks_obj = r_hardforks.move_as_ok();
|
||||
auto &hardforks = hardforks_obj.get_array();
|
||||
for (auto &fork : hardforks) {
|
||||
if (fork.type() != td::JsonValue::Type::Object) {
|
||||
return td::Status::Error("Invalid config (8)");
|
||||
}
|
||||
TRY_RESULT(fork_block, parse_block_id_ext(fork.get_object()));
|
||||
res.hardforks.push_back(std::move(fork_block));
|
||||
}
|
||||
}
|
||||
|
||||
for (auto hardfork : res.hardforks) {
|
||||
if (!res.init_block_id.is_valid() || hardfork.seqno() > res.init_block_id.seqno()) {
|
||||
LOG(INFO) << "Replace init_block with hardfork: " << res.init_block_id.to_str() << " -> " << hardfork.to_str();
|
||||
res.init_block_id = hardfork;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
} // namespace tonlib
|
||||
|
|
|
@ -29,7 +29,9 @@ struct Config {
|
|||
};
|
||||
ton::BlockIdExt zero_state_id;
|
||||
ton::BlockIdExt init_block_id;
|
||||
std::vector<ton::BlockIdExt> hardforks;
|
||||
std::vector<LiteClient> lite_clients;
|
||||
std::string name;
|
||||
static td::Result<Config> parse(std::string str);
|
||||
};
|
||||
} // namespace tonlib
|
||||
|
|
|
@ -93,9 +93,10 @@ struct LastBlockState {
|
|||
ton::BlockIdExt last_block_id;
|
||||
td::int64 utime{0};
|
||||
ton::BlockIdExt init_block_id;
|
||||
td::int32 vert_seqno{0};
|
||||
|
||||
static constexpr td::int32 magic = 0xa7f171a4;
|
||||
enum Version { None = 0, Magic, InitBlock, Next };
|
||||
enum Version { None = 0, Magic, InitBlock, VertSeqno, Next };
|
||||
static constexpr td::int32 version = Version::Next - 1;
|
||||
|
||||
template <class StorerT>
|
||||
|
@ -110,6 +111,7 @@ struct LastBlockState {
|
|||
store(last_block_id, storer);
|
||||
store(utime, storer);
|
||||
store(init_block_id, storer);
|
||||
store(vert_seqno, storer);
|
||||
}
|
||||
|
||||
template <class ParserT>
|
||||
|
@ -130,6 +132,9 @@ struct LastBlockState {
|
|||
if (version >= InitBlock) {
|
||||
parse(init_block_id, parser);
|
||||
}
|
||||
if (version >= VertSeqno) {
|
||||
parse(vert_seqno, parser);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -36,6 +36,7 @@
|
|||
#include "smc-envelope/HighloadWallet.h"
|
||||
#include "smc-envelope/HighloadWalletV2.h"
|
||||
#include "smc-envelope/TestGiver.h"
|
||||
#include "smc-envelope/PaymentChannel.h"
|
||||
#include "smc-envelope/SmartContractCode.h"
|
||||
|
||||
#include "auto/tl/tonlib_api.hpp"
|
||||
|
@ -106,7 +107,8 @@ auto to_tonlib_api(const ton::BlockIdExt& blk) {
|
|||
}
|
||||
|
||||
tonlib_api::object_ptr<tonlib_api::options_configInfo> to_tonlib_api(const TonlibClient::FullConfig& full_config) {
|
||||
return tonlib_api::make_object<tonlib_api::options_configInfo>(full_config.wallet_id);
|
||||
return tonlib_api::make_object<tonlib_api::options_configInfo>(full_config.wallet_id,
|
||||
full_config.rwallet_init_public_key);
|
||||
}
|
||||
|
||||
class TonlibQueryActor : public td::actor::Actor {
|
||||
|
@ -172,9 +174,69 @@ tonlib_api::object_ptr<tonlib_api::internal_transactionId> to_transaction_id(con
|
|||
}
|
||||
|
||||
std::string to_bytes(td::Ref<vm::Cell> cell) {
|
||||
if (cell.is_null()) {
|
||||
return "";
|
||||
}
|
||||
return vm::std_boc_serialize(cell, vm::BagOfCells::Mode::WithCRC32C).move_as_ok().as_slice().str();
|
||||
}
|
||||
|
||||
td::Result<block::PublicKey> get_public_key(td::Slice public_key) {
|
||||
TRY_RESULT_PREFIX(address, block::PublicKey::parse(public_key), TonlibError::InvalidPublicKey());
|
||||
return address;
|
||||
}
|
||||
|
||||
td::Result<block::StdAddress> get_account_address(td::Slice account_address) {
|
||||
TRY_RESULT_PREFIX(address, block::StdAddress::parse(account_address), TonlibError::InvalidAccountAddress());
|
||||
return address;
|
||||
}
|
||||
|
||||
td::Result<block::PublicKey> public_key_from_bytes(td::Slice bytes) {
|
||||
TRY_RESULT_PREFIX(key_bytes, block::PublicKey::from_bytes(bytes), TonlibError::Internal());
|
||||
return key_bytes;
|
||||
}
|
||||
|
||||
td::Result<ton::RestrictedWallet::InitData> to_init_data(const tonlib_api::rwallet_initialAccountState& rwallet_state) {
|
||||
TRY_RESULT(init_key_bytes, get_public_key(rwallet_state.init_public_key_));
|
||||
TRY_RESULT(key_bytes, get_public_key(rwallet_state.public_key_));
|
||||
ton::RestrictedWallet::InitData init_data;
|
||||
init_data.init_key = td::SecureString(init_key_bytes.key);
|
||||
init_data.main_key = td::SecureString(key_bytes.key);
|
||||
init_data.wallet_id = static_cast<td::uint32>(rwallet_state.wallet_id_);
|
||||
return std::move(init_data);
|
||||
}
|
||||
|
||||
td::Result<ton::pchan::Config> to_pchan_config(const tonlib_api::pchan_initialAccountState& pchan_state) {
|
||||
ton::pchan::Config config;
|
||||
if (!pchan_state.config_) {
|
||||
return TonlibError::EmptyField("config");
|
||||
}
|
||||
TRY_RESULT_PREFIX(a_key, get_public_key(pchan_state.config_->alice_public_key_),
|
||||
TonlibError::InvalidField("alice_public_key", ""));
|
||||
config.a_key = td::SecureString(a_key.key);
|
||||
TRY_RESULT_PREFIX(b_key, get_public_key(pchan_state.config_->bob_public_key_),
|
||||
TonlibError::InvalidField("bob_public_key", ""));
|
||||
config.b_key = td::SecureString(b_key.key);
|
||||
|
||||
if (!pchan_state.config_->alice_address_) {
|
||||
return TonlibError::EmptyField("config.alice_address");
|
||||
}
|
||||
TRY_RESULT_PREFIX(a_addr, get_account_address(pchan_state.config_->alice_address_->account_address_),
|
||||
TonlibError::InvalidField("alice_address", ""));
|
||||
config.a_addr = std::move(a_addr);
|
||||
|
||||
if (!pchan_state.config_->bob_address_) {
|
||||
return TonlibError::EmptyField("config.bob_address");
|
||||
}
|
||||
TRY_RESULT_PREFIX(b_addr, get_account_address(pchan_state.config_->bob_address_->account_address_),
|
||||
TonlibError::InvalidField("bob_address", ""));
|
||||
config.b_addr = std::move(b_addr);
|
||||
|
||||
config.channel_id = pchan_state.config_->channel_id_;
|
||||
config.init_timeout = pchan_state.config_->init_timeout_;
|
||||
config.close_timeout = pchan_state.config_->close_timeout_;
|
||||
return std::move(config);
|
||||
}
|
||||
|
||||
class AccountState {
|
||||
public:
|
||||
AccountState(block::StdAddress address, RawAccountState&& raw, td::uint32 wallet_id)
|
||||
|
@ -259,6 +321,55 @@ class AccountState {
|
|||
TRY_RESULT(wallet_id, wallet.get_wallet_id());
|
||||
return tonlib_api::make_object<tonlib_api::wallet_highload_v2_accountState>(static_cast<td::uint32>(wallet_id));
|
||||
}
|
||||
td::Result<tonlib_api::object_ptr<tonlib_api::rwallet_accountState>> to_rwallet_accountState() const {
|
||||
if (wallet_type_ != RestrictedWallet) {
|
||||
return TonlibError::AccountTypeUnexpected("RestrictedWallet");
|
||||
}
|
||||
auto wallet = ton::RestrictedWallet::create(get_smc_state());
|
||||
TRY_RESULT(seqno, wallet->get_seqno());
|
||||
TRY_RESULT(wallet_id, wallet->get_wallet_id());
|
||||
TRY_RESULT(balance, wallet->get_balance(raw_.balance, raw_.info.gen_utime));
|
||||
TRY_RESULT(config, wallet->get_config());
|
||||
|
||||
auto api_config = tonlib_api::make_object<tonlib_api::rwallet_config>();
|
||||
api_config->start_at_ = config.start_at;
|
||||
for (auto& limit : config.limits) {
|
||||
api_config->limits_.push_back(tonlib_api::make_object<tonlib_api::rwallet_limit>(limit.first, limit.second));
|
||||
}
|
||||
|
||||
return tonlib_api::make_object<tonlib_api::rwallet_accountState>(wallet_id, seqno, balance, std::move(api_config));
|
||||
}
|
||||
td::Result<tonlib_api::object_ptr<tonlib_api::pchan_accountState>> to_payment_channel_accountState() const {
|
||||
if (wallet_type_ != PaymentChannel) {
|
||||
return TonlibError::AccountTypeUnexpected("PaymentChannel");
|
||||
}
|
||||
auto pchan = ton::PaymentChannel::create(get_smc_state());
|
||||
TRY_RESULT(info, pchan->get_info());
|
||||
TRY_RESULT(a_key, public_key_from_bytes(info.config.a_key));
|
||||
TRY_RESULT(b_key, public_key_from_bytes(info.config.b_key));
|
||||
|
||||
tonlib_api::object_ptr<tonlib_api::pchan_State> tl_state;
|
||||
info.state.visit(td::overloaded(
|
||||
[&](const ton::pchan::StateInit& state) {
|
||||
tl_state = tonlib_api::make_object<tonlib_api::pchan_stateInit>(
|
||||
state.signed_A, state.signed_B, state.min_A, state.min_B, state.A, state.B, state.expire_at);
|
||||
},
|
||||
[&](const ton::pchan::StateClose& state) {
|
||||
tl_state = tonlib_api::make_object<tonlib_api::pchan_stateClose>(
|
||||
state.signed_A, state.signed_B, state.promise_A, state.promise_B, state.A, state.B, state.expire_at);
|
||||
},
|
||||
[&](const ton::pchan::StatePayout& state) {
|
||||
tl_state = tonlib_api::make_object<tonlib_api::pchan_statePayout>(state.A, state.B);
|
||||
}));
|
||||
|
||||
using tonlib_api::make_object;
|
||||
return tonlib_api::make_object<tonlib_api::pchan_accountState>(
|
||||
tonlib_api::make_object<tonlib_api::pchan_config>(
|
||||
a_key.serialize(true), make_object<tonlib_api::accountAddress>(info.config.a_addr.rserialize(true)),
|
||||
b_key.serialize(true), make_object<tonlib_api::accountAddress>(info.config.b_addr.rserialize(true)),
|
||||
info.config.init_timeout, info.config.close_timeout, info.config.channel_id),
|
||||
std::move(tl_state), info.description);
|
||||
}
|
||||
|
||||
td::Result<tonlib_api::object_ptr<tonlib_api::testGiver_accountState>> to_testGiver_accountState() const {
|
||||
if (wallet_type_ != Giver) {
|
||||
|
@ -298,11 +409,14 @@ class AccountState {
|
|||
return f(to_wallet_highload_v1_accountState());
|
||||
case HighloadWalletV2:
|
||||
return f(to_wallet_highload_v2_accountState());
|
||||
case RestrictedWallet:
|
||||
return f(to_rwallet_accountState());
|
||||
case ManualDns:
|
||||
return f(to_dns_accountState());
|
||||
default:
|
||||
UNREACHABLE();
|
||||
case PaymentChannel:
|
||||
return f(to_payment_channel_accountState());
|
||||
}
|
||||
UNREACHABLE();
|
||||
}
|
||||
|
||||
td::Result<tonlib_api::object_ptr<tonlib_api::fullAccountState>> to_fullAccountState() const {
|
||||
|
@ -321,7 +435,9 @@ class AccountState {
|
|||
WalletV3,
|
||||
HighloadWalletV1,
|
||||
HighloadWalletV2,
|
||||
ManualDns
|
||||
ManualDns,
|
||||
PaymentChannel,
|
||||
RestrictedWallet
|
||||
};
|
||||
WalletType get_wallet_type() const {
|
||||
return wallet_type_;
|
||||
|
@ -331,6 +447,7 @@ class AccountState {
|
|||
case AccountState::Empty:
|
||||
case AccountState::Unknown:
|
||||
case AccountState::ManualDns:
|
||||
case AccountState::PaymentChannel:
|
||||
return false;
|
||||
case AccountState::Giver:
|
||||
case AccountState::SimpleWallet:
|
||||
|
@ -338,6 +455,7 @@ class AccountState {
|
|||
case AccountState::WalletV3:
|
||||
case AccountState::HighloadWalletV1:
|
||||
case AccountState::HighloadWalletV2:
|
||||
case AccountState::RestrictedWallet:
|
||||
return true;
|
||||
}
|
||||
UNREACHABLE();
|
||||
|
@ -348,6 +466,7 @@ class AccountState {
|
|||
case AccountState::Empty:
|
||||
case AccountState::Unknown:
|
||||
case AccountState::ManualDns:
|
||||
case AccountState::PaymentChannel:
|
||||
return {};
|
||||
case AccountState::Giver:
|
||||
return td::make_unique<ton::TestGiver>(get_smc_state());
|
||||
|
@ -361,6 +480,8 @@ class AccountState {
|
|||
return td::make_unique<ton::HighloadWallet>(get_smc_state());
|
||||
case AccountState::HighloadWalletV2:
|
||||
return td::make_unique<ton::HighloadWalletV2>(get_smc_state());
|
||||
case AccountState::RestrictedWallet:
|
||||
return td::make_unique<ton::RestrictedWallet>(get_smc_state());
|
||||
}
|
||||
UNREACHABLE();
|
||||
return {};
|
||||
|
@ -393,6 +514,50 @@ class AccountState {
|
|||
return raw_;
|
||||
}
|
||||
|
||||
WalletType guess_type_by_init_state(tonlib_api::InitialAccountState& initial_account_state) {
|
||||
if (wallet_type_ != WalletType::Empty) {
|
||||
return wallet_type_;
|
||||
}
|
||||
downcast_call(
|
||||
initial_account_state,
|
||||
td::overloaded(
|
||||
[](auto& x) {},
|
||||
[&](tonlib_api::rwallet_initialAccountState& rwallet) {
|
||||
for (auto revision : ton::SmartContractCode::get_revisions(ton::SmartContractCode::RestrictedWallet)) {
|
||||
auto r_init_data = to_init_data(rwallet);
|
||||
if (r_init_data.is_error()) {
|
||||
continue;
|
||||
}
|
||||
auto wallet = ton::RestrictedWallet::create(r_init_data.move_as_ok(), revision);
|
||||
if (!(wallet->get_address() == address_)) {
|
||||
continue;
|
||||
}
|
||||
wallet_type_ = WalletType::RestrictedWallet;
|
||||
wallet_revision_ = revision;
|
||||
set_new_state(wallet->get_state());
|
||||
break;
|
||||
}
|
||||
},
|
||||
[&](tonlib_api::pchan_initialAccountState& pchan) {
|
||||
for (auto revision : ton::SmartContractCode::get_revisions(ton::SmartContractCode::PaymentChannel)) {
|
||||
auto r_conf = to_pchan_config(pchan);
|
||||
if (r_conf.is_error()) {
|
||||
continue;
|
||||
}
|
||||
auto conf = r_conf.move_as_ok();
|
||||
auto wallet = ton::PaymentChannel::create(conf, -1);
|
||||
if (!(wallet->get_address() == address_)) {
|
||||
continue;
|
||||
}
|
||||
wallet_type_ = WalletType::PaymentChannel;
|
||||
wallet_revision_ = revision;
|
||||
set_new_state(wallet->get_state());
|
||||
break;
|
||||
}
|
||||
}));
|
||||
return wallet_type_;
|
||||
}
|
||||
|
||||
WalletType guess_type_by_public_key(td::Ed25519::PublicKey& key) {
|
||||
if (wallet_type_ != WalletType::Empty) {
|
||||
return wallet_type_;
|
||||
|
@ -509,6 +674,18 @@ class AccountState {
|
|||
wallet_revision_ = o_revision.value();
|
||||
return wallet_type_;
|
||||
}
|
||||
o_revision = ton::PaymentChannel::guess_revision(code_hash);
|
||||
if (o_revision) {
|
||||
wallet_type_ = WalletType::PaymentChannel;
|
||||
wallet_revision_ = o_revision.value();
|
||||
return wallet_type_;
|
||||
}
|
||||
o_revision = ton::RestrictedWallet::guess_revision(code_hash);
|
||||
if (o_revision) {
|
||||
wallet_type_ = WalletType::RestrictedWallet;
|
||||
wallet_revision_ = o_revision.value();
|
||||
return wallet_type_;
|
||||
}
|
||||
|
||||
if (code_hash == ton::TestGiver::get_init_code_hash()) {
|
||||
wallet_type_ = WalletType::Giver;
|
||||
|
@ -516,8 +693,6 @@ class AccountState {
|
|||
wallet_type_ = WalletType::SimpleWallet;
|
||||
} else if (code_hash == ton::Wallet::get_init_code_hash()) {
|
||||
wallet_type_ = WalletType::Wallet;
|
||||
} else if (code_hash == ton::HighloadWallet::get_init_code_hash()) {
|
||||
wallet_type_ = WalletType::HighloadWalletV1;
|
||||
} else {
|
||||
LOG(WARNING) << "Unknown code hash: " << td::base64_encode(code_hash.as_slice());
|
||||
wallet_type_ = WalletType::Unknown;
|
||||
|
@ -545,6 +720,12 @@ class Query {
|
|||
td::Ref<vm::Cell> get_message() const {
|
||||
return raw_.message;
|
||||
}
|
||||
td::Ref<vm::Cell> get_message_body() const {
|
||||
return raw_.message_body;
|
||||
}
|
||||
td::Ref<vm::Cell> get_init_state() const {
|
||||
return raw_.new_state;
|
||||
}
|
||||
|
||||
vm::CellHash get_body_hash() const {
|
||||
return raw_.message_body->get_hash();
|
||||
|
@ -722,11 +903,8 @@ class Query {
|
|||
raw_.message_body, ton::SmartContract::Args().set_limits(gas_limits).set_ignore_chksig(ignore_chksig));
|
||||
td::int64 fwd_fee = 0;
|
||||
if (res.success) {
|
||||
//std::cerr << "new smart contract data: ";
|
||||
//load_cell_slice(res.new_state.data).print_rec(std::cerr);
|
||||
//std::cerr << "output actions: ";
|
||||
//int out_act_num = output_actions_count(res.actions);
|
||||
//block::gen::OutList{out_act_num}.print_ref(std::cerr, res.actions);
|
||||
LOG(DEBUG) << "output actions:\n"
|
||||
<< block::gen::OutList{output_actions_count(res.actions)}.as_string_ref(res.actions);
|
||||
|
||||
TRY_RESULT_ASSIGN(fwd_fee, calc_fwd_fees(res.actions, msg_prices, is_masterchain));
|
||||
}
|
||||
|
@ -1402,11 +1580,6 @@ tonlib_api::object_ptr<tonlib_api::Object> TonlibClient::do_static_request(const
|
|||
return tonlib_api::make_object<tonlib_api::ok>();
|
||||
}
|
||||
|
||||
td::Result<block::PublicKey> get_public_key(td::Slice public_key) {
|
||||
TRY_RESULT_PREFIX(address, block::PublicKey::parse(public_key), TonlibError::InvalidPublicKey());
|
||||
return address;
|
||||
}
|
||||
|
||||
td::Result<block::StdAddress> get_account_address(const tonlib_api::raw_initialAccountState& raw_state,
|
||||
td::int32 revision) {
|
||||
TRY_RESULT_PREFIX(code, vm::std_boc_deserialize(raw_state.code_), TonlibError::InvalidBagOfCells("raw_state.code"));
|
||||
|
@ -1467,9 +1640,15 @@ td::Result<block::StdAddress> get_account_address(const tonlib_api::dns_initialA
|
|||
return ton::ManualDns::create(key, static_cast<td::uint32>(dns_state.wallet_id_), revision)->get_address();
|
||||
}
|
||||
|
||||
td::Result<block::StdAddress> get_account_address(td::Slice account_address) {
|
||||
TRY_RESULT_PREFIX(address, block::StdAddress::parse(account_address), TonlibError::InvalidAccountAddress());
|
||||
return address;
|
||||
td::Result<block::StdAddress> get_account_address(const tonlib_api::pchan_initialAccountState& pchan_state,
|
||||
td::int32 revision) {
|
||||
TRY_RESULT(config, to_pchan_config(pchan_state));
|
||||
return ton::PaymentChannel::create(config, revision)->get_address();
|
||||
}
|
||||
td::Result<block::StdAddress> get_account_address(const tonlib_api::rwallet_initialAccountState& rwallet_state,
|
||||
td::int32 revision) {
|
||||
TRY_RESULT(init_data, to_init_data(rwallet_state));
|
||||
return ton::RestrictedWallet::create(init_data, revision)->get_address();
|
||||
}
|
||||
|
||||
td::Result<td::Bits256> get_adnl_address(td::Slice adnl_address) {
|
||||
|
@ -1493,6 +1672,8 @@ static td::optional<ton::SmartContractCode::Type> get_wallet_type(tonlib_api::In
|
|||
[](const tonlib_api::wallet_highload_v2_initialAccountState&) {
|
||||
return ton::SmartContractCode::HighloadWalletV2;
|
||||
},
|
||||
[](const tonlib_api::rwallet_initialAccountState&) { return ton::SmartContractCode::RestrictedWallet; },
|
||||
[](const tonlib_api::pchan_initialAccountState&) { return ton::SmartContractCode::PaymentChannel; },
|
||||
[](const tonlib_api::dns_initialAccountState&) { return ton::SmartContractCode::ManualDns; }));
|
||||
}
|
||||
|
||||
|
@ -1555,7 +1736,18 @@ td::Status TonlibClient::do_request(const tonlib_api::guessAccountRevision& requ
|
|||
td::Promise<std::vector<int>> promise_;
|
||||
|
||||
size_t left_{0};
|
||||
std::vector<int> res;
|
||||
struct Item {
|
||||
bool is_inited;
|
||||
td::int64 balance;
|
||||
int revision;
|
||||
auto key() const {
|
||||
return std::make_tuple(is_inited, balance, revision);
|
||||
}
|
||||
bool operator<(const Item& other) const {
|
||||
return key() > other.key();
|
||||
}
|
||||
};
|
||||
std::vector<Item> res;
|
||||
|
||||
void start_up() {
|
||||
left_ += addresses_.size();
|
||||
|
@ -1565,12 +1757,24 @@ td::Status TonlibClient::do_request(const tonlib_api::guessAccountRevision& requ
|
|||
}
|
||||
}
|
||||
void on_account_state(int revision, td::Result<td::unique_ptr<AccountState>> r_state) {
|
||||
if (r_state.is_ok() && r_state.ok()->get_wallet_type() != AccountState::WalletType::Empty) {
|
||||
res.push_back(revision);
|
||||
SCOPE_EXIT {
|
||||
on_account_state_finish();
|
||||
};
|
||||
if (!r_state.is_ok()) {
|
||||
return;
|
||||
}
|
||||
auto state = r_state.move_as_ok();
|
||||
if (state->get_balance() < 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
res.push_back({state->get_wallet_type() != AccountState::WalletType::Empty, state->get_balance(), revision});
|
||||
}
|
||||
void on_account_state_finish() {
|
||||
left_--;
|
||||
if (left_ == 0) {
|
||||
promise_.set_value(std::move(res));
|
||||
std::sort(res.begin(), res.end());
|
||||
promise_.set_value(td::transform(std::move(res), [](auto x) { return x.revision; }));
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
@ -1653,6 +1857,7 @@ class MasterConfig {
|
|||
public:
|
||||
void add_config(std::string name, std::string json) {
|
||||
auto config = std::make_shared<Config>(Config::parse(json).move_as_ok());
|
||||
config->name = name;
|
||||
if (!name.empty()) {
|
||||
by_name_[name] = config;
|
||||
}
|
||||
|
@ -1701,14 +1906,6 @@ const MasterConfig& get_default_master_config() {
|
|||
})abc");
|
||||
res.add_config("testnet2", R"abc({
|
||||
"liteservers": [
|
||||
{
|
||||
"ip": 1137658550,
|
||||
"port": 4924,
|
||||
"id": {
|
||||
"@type": "pub.ed25519",
|
||||
"key": "peJTw/arlRfssgTuf9BMypJzqOi7SXEqSPSWiEw2U1M="
|
||||
}
|
||||
}
|
||||
],
|
||||
"validator": {
|
||||
"@type": "validator.config.global",
|
||||
|
@ -1721,6 +1918,20 @@ const MasterConfig& get_default_master_config() {
|
|||
},
|
||||
"init_block": {"workchain":-1,"shard":-9223372036854775808,"seqno":2908451,"root_hash":"5+7X1QHVUBFLFMwa/yd/2fGzt2KeQtwr+o6UUFOQ7Qc=","file_hash":"gmiUgrtAbvEJZYDEkcbeNOhGPS3g+qCepSOEBFLZFzk="}
|
||||
}
|
||||
})abc");
|
||||
res.add_config("mainnet", R"abc({
|
||||
"liteservers": [
|
||||
],
|
||||
"validator": {
|
||||
"@type": "validator.config.global",
|
||||
"zero_state": {
|
||||
"workchain": -1,
|
||||
"shard": -9223372036854775808,
|
||||
"seqno": 0,
|
||||
"root_hash": "KbTmuSarbve4ce9SET3BRyDnlPZr4GMdWswt1nDQAl4=",
|
||||
"file_hash": "yRCjDRa-ChWiAGf5n3b25U17aqKzVL13C2Tzz8sqtJA="
|
||||
}
|
||||
}
|
||||
})abc");
|
||||
return res;
|
||||
}();
|
||||
|
@ -1740,7 +1951,6 @@ td::Result<TonlibClient::FullConfig> TonlibClient::validate_config(tonlib_api::o
|
|||
if (new_config.lite_clients.empty() && !config->use_callbacks_for_network_) {
|
||||
return TonlibError::InvalidConfig("no lite clients");
|
||||
}
|
||||
|
||||
td::optional<Config> o_master_config;
|
||||
std::string last_state_key;
|
||||
if (config->blockchain_name_.empty()) {
|
||||
|
@ -1755,6 +1965,12 @@ td::Result<TonlibClient::FullConfig> TonlibClient::validate_config(tonlib_api::o
|
|||
return TonlibError::InvalidConfig("zero_state differs from embedded zero_state");
|
||||
}
|
||||
|
||||
if (o_master_config && o_master_config.value().hardforks != new_config.hardforks) {
|
||||
return TonlibError::InvalidConfig("hardforks differs from embedded hardforks");
|
||||
}
|
||||
|
||||
int vert_seqno = static_cast<int>(new_config.hardforks.size());
|
||||
|
||||
LastBlockState state;
|
||||
td::Result<LastBlockState> r_state;
|
||||
if (!config->ignore_cache_) {
|
||||
|
@ -1773,6 +1989,25 @@ td::Result<TonlibClient::FullConfig> TonlibClient::validate_config(tonlib_api::o
|
|||
LOG(ERROR) << state.zero_state_id.to_str() << " " << zero_state.to_str();
|
||||
return TonlibError::InvalidConfig("zero_state differs from cached zero_state");
|
||||
}
|
||||
if (state.vert_seqno > vert_seqno) {
|
||||
LOG(ERROR) << "Stored vert_seqno is bigger than one in config: " << state.vert_seqno << " vs " << vert_seqno;
|
||||
return TonlibError::InvalidConfig("vert_seqno in cached state is bigger");
|
||||
}
|
||||
if (state.vert_seqno < vert_seqno) {
|
||||
state.zero_state_id = zero_state;
|
||||
state.last_block_id = new_config.zero_state_id;
|
||||
state.last_key_block_id = new_config.zero_state_id;
|
||||
state.init_block_id = ton::BlockIdExt{};
|
||||
LOG(WARNING) << "Drop cached state - vert_seqno is smaller than in config";
|
||||
}
|
||||
}
|
||||
state.vert_seqno = vert_seqno;
|
||||
|
||||
//TODO: this could be useful to override master config
|
||||
if (false && new_config.init_block_id.is_valid() &&
|
||||
state.last_key_block_id.id.seqno < new_config.init_block_id.id.seqno) {
|
||||
state.last_key_block_id = new_config.init_block_id;
|
||||
LOG(INFO) << "Use init block from USER config: " << new_config.init_block_id.to_str();
|
||||
}
|
||||
|
||||
if (o_master_config) {
|
||||
|
@ -1782,12 +2017,23 @@ td::Result<TonlibClient::FullConfig> TonlibClient::validate_config(tonlib_api::o
|
|||
state.last_key_block_id = master_config.init_block_id;
|
||||
LOG(INFO) << "Use init block from MASTER config: " << master_config.init_block_id.to_str();
|
||||
}
|
||||
if (!master_config.name.empty()) {
|
||||
if (new_config.name != master_config.name) {
|
||||
LOG(INFO) << "Use blockchain name from MASTER config: '" << master_config.name << "' (was '" << new_config.name
|
||||
<< "')";
|
||||
new_config.name = master_config.name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FullConfig res;
|
||||
res.config = std::move(new_config);
|
||||
res.use_callbacks_for_network = config->use_callbacks_for_network_;
|
||||
res.wallet_id = td::as<td::uint32>(res.config.zero_state_id.root_hash.as_slice().data());
|
||||
if (res.config.name.empty()) { // TODO == "mainnet"
|
||||
res.wallet_id = 0x4BA92D89;
|
||||
}
|
||||
res.rwallet_init_public_key = "Puasxr0QfFZZnYISRphVse7XHKfW7pZU5SJarVHXvQ+rpzkD";
|
||||
res.last_state_key = std::move(last_state_key);
|
||||
res.last_state = std::move(state);
|
||||
|
||||
|
@ -1906,7 +2152,7 @@ struct ToRawTransactions {
|
|||
}
|
||||
}
|
||||
if (!data) {
|
||||
data = tonlib_api::make_object<tonlib_api::msg_dataRaw>(to_bytes(std::move(body_cell)));
|
||||
data = tonlib_api::make_object<tonlib_api::msg_dataRaw>(to_bytes(std::move(body_cell)), "");
|
||||
}
|
||||
return data;
|
||||
};
|
||||
|
@ -2114,7 +2360,7 @@ td::Status TonlibClient::do_request(tonlib_api::raw_getTransactions& request,
|
|||
td::optional<td::Ed25519::PrivateKey> private_key;
|
||||
if (request.private_key_) {
|
||||
TRY_RESULT(input_key, from_tonlib(*request.private_key_));
|
||||
//NB: options<Status> has lot of problems. We use emplace to migitate them
|
||||
//NB: optional<Status> has lot of problems. We use emplace to migitate them
|
||||
td::optional<td::Status> o_status;
|
||||
//NB: rely on (and assert) that GetPrivateKey is a synchonous request
|
||||
make_request(int_api::GetPrivateKey{std::move(input_key)}, [&](auto&& r_key) {
|
||||
|
@ -2217,6 +2463,7 @@ class GenericCreateSendGrams : public TonlibQueryActor {
|
|||
std::string message;
|
||||
|
||||
td::Ref<vm::Cell> body;
|
||||
td::Ref<vm::Cell> init_state;
|
||||
|
||||
td::optional<td::Ed25519::PublicKey> o_public_key;
|
||||
};
|
||||
|
@ -2227,6 +2474,10 @@ class GenericCreateSendGrams : public TonlibQueryActor {
|
|||
// Should be splitted eventually
|
||||
std::vector<ton::ManualDns::Action> dns_actions_;
|
||||
|
||||
bool pchan_action_{false};
|
||||
|
||||
bool rwallet_action_{false};
|
||||
|
||||
void check(td::Status status) {
|
||||
if (status.is_error()) {
|
||||
promise_.set_error(std::move(status));
|
||||
|
@ -2257,34 +2508,36 @@ class GenericCreateSendGrams : public TonlibQueryActor {
|
|||
auto key = td::Ed25519::PublicKey(td::SecureString(public_key.key));
|
||||
res.o_public_key = std::move(key);
|
||||
}
|
||||
auto status =
|
||||
downcast_call2<td::Status>(*message.data_, td::overloaded(
|
||||
[&](tonlib_api::msg_dataRaw& text) {
|
||||
TRY_RESULT(body, vm::std_boc_deserialize(text.body_));
|
||||
res.body = std::move(body);
|
||||
return td::Status::OK();
|
||||
},
|
||||
[&](tonlib_api::msg_dataText& text) {
|
||||
res.message = text.text_;
|
||||
res.should_encrypt = false;
|
||||
res.is_encrypted = false;
|
||||
return td::Status::OK();
|
||||
},
|
||||
[&](tonlib_api::msg_dataDecryptedText& text) {
|
||||
res.message = text.text_;
|
||||
if (!has_private_key_) {
|
||||
return TonlibError::EmptyField("input_key");
|
||||
}
|
||||
res.should_encrypt = true;
|
||||
res.is_encrypted = true;
|
||||
return td::Status::OK();
|
||||
},
|
||||
[&](tonlib_api::msg_dataEncryptedText& text) {
|
||||
res.message = text.text_;
|
||||
res.should_encrypt = false;
|
||||
res.is_encrypted = true;
|
||||
return td::Status::OK();
|
||||
}));
|
||||
auto status = downcast_call2<td::Status>(
|
||||
*message.data_, td::overloaded(
|
||||
[&](tonlib_api::msg_dataRaw& text) {
|
||||
TRY_RESULT(body, vm::std_boc_deserialize(text.body_));
|
||||
TRY_RESULT(init_state, vm::std_boc_deserialize(text.init_state_, true));
|
||||
res.body = std::move(body);
|
||||
res.init_state = std::move(init_state);
|
||||
return td::Status::OK();
|
||||
},
|
||||
[&](tonlib_api::msg_dataText& text) {
|
||||
res.message = text.text_;
|
||||
res.should_encrypt = false;
|
||||
res.is_encrypted = false;
|
||||
return td::Status::OK();
|
||||
},
|
||||
[&](tonlib_api::msg_dataDecryptedText& text) {
|
||||
res.message = text.text_;
|
||||
if (!has_private_key_) {
|
||||
return TonlibError::EmptyField("input_key");
|
||||
}
|
||||
res.should_encrypt = true;
|
||||
res.is_encrypted = true;
|
||||
return td::Status::OK();
|
||||
},
|
||||
[&](tonlib_api::msg_dataEncryptedText& text) {
|
||||
res.message = text.text_;
|
||||
res.should_encrypt = false;
|
||||
res.is_encrypted = true;
|
||||
return td::Status::OK();
|
||||
}));
|
||||
// Use this limit as a preventive check
|
||||
if (res.message.size() > ton::Wallet::max_message_size) {
|
||||
return TonlibError::MessageTooLong();
|
||||
|
@ -2332,6 +2585,14 @@ class GenericCreateSendGrams : public TonlibQueryActor {
|
|||
}
|
||||
return td::Status::OK();
|
||||
},
|
||||
[&](tonlib_api::actionPchan& cell) {
|
||||
pchan_action_ = true;
|
||||
return td::Status::OK();
|
||||
},
|
||||
[&](tonlib_api::actionRwallet& cell) {
|
||||
rwallet_action_ = true;
|
||||
return td::Status::OK();
|
||||
},
|
||||
[&](tonlib_api::actionDns& cell) {
|
||||
for (auto& from_action : cell.actions_) {
|
||||
if (!from_action) {
|
||||
|
@ -2397,6 +2658,9 @@ class GenericCreateSendGrams : public TonlibQueryActor {
|
|||
td::Status do_on_source_state(td::Result<td::unique_ptr<AccountState>> r_state) {
|
||||
TRY_RESULT(state, std::move(r_state));
|
||||
source_ = std::move(state);
|
||||
if (source_->get_wallet_type() == AccountState::Empty && query_.initial_account_state_) {
|
||||
source_->guess_type_by_init_state(*query_.initial_account_state_);
|
||||
}
|
||||
if (source_->get_wallet_type() == AccountState::Empty && public_key_) {
|
||||
source_->guess_type_by_public_key(public_key_.value());
|
||||
}
|
||||
|
@ -2459,6 +2723,165 @@ class GenericCreateSendGrams : public TonlibQueryActor {
|
|||
return td::Status::OK();
|
||||
}
|
||||
|
||||
td::Status do_pchan_loop(td::Ref<ton::PaymentChannel> pchan, tonlib_api::actionPchan& action) {
|
||||
if (!action.action_) {
|
||||
return TonlibError::EmptyField("action");
|
||||
}
|
||||
|
||||
Query::Raw raw;
|
||||
auto valid_until = source_->get_sync_time();
|
||||
valid_until += query_.timeout_ == 0 ? 60 : query_.timeout_;
|
||||
raw.valid_until = valid_until;
|
||||
|
||||
TRY_RESULT(info, pchan->get_info());
|
||||
bool is_alice = false;
|
||||
bool is_bob = false;
|
||||
if (info.config.a_key == private_key_.value().get_public_key().move_as_ok().as_octet_string()) {
|
||||
LOG(ERROR) << "Alice key";
|
||||
is_alice = true;
|
||||
} else if (info.config.b_key == private_key_.value().get_public_key().move_as_ok().as_octet_string()) {
|
||||
LOG(ERROR) << "Bob key";
|
||||
is_bob = true;
|
||||
}
|
||||
if (!is_alice && !is_bob) {
|
||||
return TonlibError::InvalidField("private_key", "invalid for this smartcontract");
|
||||
}
|
||||
auto status = downcast_call2<td::Status>(
|
||||
*action.action_,
|
||||
td::overloaded(
|
||||
[&](tonlib_api::pchan_actionTimeout& timeout) {
|
||||
auto builder = ton::pchan::MsgTimeoutBuilder();
|
||||
if (is_alice) {
|
||||
std::move(builder).with_a_key(&private_key_.value());
|
||||
}
|
||||
if (is_bob) {
|
||||
std::move(builder).with_b_key(&private_key_.value());
|
||||
}
|
||||
raw.message_body = std::move(builder).finalize();
|
||||
return td::Status::OK();
|
||||
},
|
||||
[&](tonlib_api::pchan_actionInit& init) {
|
||||
auto builder = ton::pchan::MsgInitBuilder()
|
||||
.inc_A(init.inc_A_)
|
||||
.inc_B(init.inc_B_)
|
||||
.min_A(init.min_A_)
|
||||
.min_B(init.min_B_)
|
||||
.channel_id(info.config.channel_id);
|
||||
if (is_alice) {
|
||||
std::move(builder).with_a_key(&private_key_.value());
|
||||
}
|
||||
if (is_bob) {
|
||||
std::move(builder).with_b_key(&private_key_.value());
|
||||
}
|
||||
raw.message_body = std::move(builder).finalize();
|
||||
return td::Status::OK();
|
||||
},
|
||||
[&](tonlib_api::pchan_actionClose& close) {
|
||||
if (!close.promise_) {
|
||||
return TonlibError::EmptyField("promise");
|
||||
}
|
||||
|
||||
ton::pchan::SignedPromiseBuilder sbuilder;
|
||||
sbuilder.promise_A(close.promise_->promise_A_)
|
||||
.promise_B(close.promise_->promise_B_)
|
||||
.channel_id(close.promise_->channel_id_)
|
||||
.signature(td::SecureString(close.promise_->signature_));
|
||||
if (is_alice && !sbuilder.check_signature(close.promise_->signature_,
|
||||
td::Ed25519::PublicKey(info.config.b_key.copy()))) {
|
||||
return TonlibError::InvalidSignature();
|
||||
}
|
||||
if (is_bob && !sbuilder.check_signature(close.promise_->signature_,
|
||||
td::Ed25519::PublicKey(info.config.a_key.copy()))) {
|
||||
return TonlibError::InvalidSignature();
|
||||
}
|
||||
|
||||
auto builder = ton::pchan::MsgCloseBuilder()
|
||||
.extra_A(close.extra_A_)
|
||||
.extra_B(close.extra_B_)
|
||||
.signed_promise(sbuilder.finalize());
|
||||
if (is_alice) {
|
||||
std::move(builder).with_a_key(&private_key_.value());
|
||||
}
|
||||
if (is_bob) {
|
||||
std::move(builder).with_b_key(&private_key_.value());
|
||||
}
|
||||
raw.message_body = std::move(builder).finalize();
|
||||
return td::Status::OK();
|
||||
}));
|
||||
TRY_STATUS(std::move(status));
|
||||
|
||||
raw.new_state = source_->get_new_state();
|
||||
raw.message = ton::GenericAccount::create_ext_message(source_->get_address(), raw.new_state, raw.message_body);
|
||||
raw.source = std::move(source_);
|
||||
|
||||
promise_.set_value(td::make_unique<Query>(std::move(raw)));
|
||||
stop();
|
||||
return td::Status::OK();
|
||||
}
|
||||
td::Status do_pchan_loop() {
|
||||
if (!private_key_) {
|
||||
return TonlibError::EmptyField("private_key");
|
||||
}
|
||||
|
||||
auto pchan = ton::PaymentChannel::create(source_->get_smc_state());
|
||||
|
||||
return downcast_call2<td::Status>(
|
||||
*query_.action_, td::overloaded([&](tonlib_api::actionNoop& cell) { return td::Status::OK(); },
|
||||
[&](auto& cell) { return td::Status::Error(); },
|
||||
[&](tonlib_api::actionPchan& cell) { return do_pchan_loop(pchan, cell); }));
|
||||
}
|
||||
|
||||
td::Status do_rwallet_action(td::Ref<ton::RestrictedWallet> rwallet, tonlib_api::actionRwallet& action) {
|
||||
if (!action.action_) {
|
||||
return TonlibError::EmptyField("action");
|
||||
}
|
||||
auto& init = *action.action_;
|
||||
if (!init.config_) {
|
||||
return TonlibError::EmptyField("config");
|
||||
}
|
||||
TRY_RESULT_PREFIX(start_at, td::narrow_cast_safe<td::uint32>(init.config_->start_at_),
|
||||
TonlibError::InvalidField("start_at", "not a uint32"));
|
||||
ton::RestrictedWallet::Config config;
|
||||
config.start_at = start_at;
|
||||
for (auto& limit : init.config_->limits_) {
|
||||
if (!limit) {
|
||||
return TonlibError::EmptyField("limits");
|
||||
}
|
||||
TRY_RESULT_PREFIX(seconds, td::narrow_cast_safe<td::int32>(limit->seconds_),
|
||||
TonlibError::InvalidField("seconds", "not a int32"));
|
||||
TRY_RESULT_PREFIX(value, td::narrow_cast_safe<td::uint64>(limit->value_),
|
||||
TonlibError::InvalidField("value", "not a uint64"));
|
||||
config.limits.emplace_back(seconds, value);
|
||||
}
|
||||
Query::Raw raw;
|
||||
auto valid_until = source_->get_sync_time();
|
||||
valid_until += query_.timeout_ == 0 ? 60 : query_.timeout_;
|
||||
raw.valid_until = valid_until;
|
||||
|
||||
TRY_RESULT_PREFIX(message_body, rwallet->get_init_message(private_key_.value(), valid_until, config),
|
||||
TonlibError::Internal("Invalid rwalet init query"));
|
||||
raw.message_body = std::move(message_body);
|
||||
raw.new_state = source_->get_new_state();
|
||||
raw.message = ton::GenericAccount::create_ext_message(source_->get_address(), raw.new_state, raw.message_body);
|
||||
raw.source = std::move(source_);
|
||||
raw.destinations = std::move(destinations_);
|
||||
promise_.set_value(td::make_unique<Query>(std::move(raw)));
|
||||
stop();
|
||||
return td::Status::OK();
|
||||
}
|
||||
|
||||
td::Status do_rwallet_action() {
|
||||
if (!private_key_) {
|
||||
return TonlibError::EmptyField("private_key");
|
||||
}
|
||||
auto rwallet = ton::RestrictedWallet::create(source_->get_smc_state());
|
||||
LOG(ERROR) << rwallet->get_address().rserialize(true);
|
||||
return downcast_call2<td::Status>(
|
||||
*query_.action_,
|
||||
td::overloaded([&](auto& cell) { return td::Status::Error("UNREACHABLE"); },
|
||||
[&](tonlib_api::actionRwallet& cell) { return do_rwallet_action(rwallet, cell); }));
|
||||
}
|
||||
|
||||
td::Status do_loop() {
|
||||
if (!source_ || destinations_left_ != 0) {
|
||||
return td::Status::OK();
|
||||
|
@ -2470,6 +2893,12 @@ class GenericCreateSendGrams : public TonlibQueryActor {
|
|||
if (source_->get_wallet_type() == AccountState::ManualDns) {
|
||||
return do_dns_loop();
|
||||
}
|
||||
if (source_->get_wallet_type() == AccountState::PaymentChannel) {
|
||||
return do_pchan_loop();
|
||||
}
|
||||
if (rwallet_action_) {
|
||||
return do_rwallet_action();
|
||||
}
|
||||
|
||||
switch (source_->get_wallet_type()) {
|
||||
case AccountState::Empty:
|
||||
|
@ -2507,6 +2936,7 @@ class GenericCreateSendGrams : public TonlibQueryActor {
|
|||
}
|
||||
if (action.body.not_null()) {
|
||||
gift.body = action.body;
|
||||
gift.init_state = action.init_state;
|
||||
} else if (action.should_encrypt) {
|
||||
LOG(ERROR) << "TRY ENCRYPT";
|
||||
if (!private_key_) {
|
||||
|
@ -2581,7 +3011,7 @@ class GenericCreateSendGrams : public TonlibQueryActor {
|
|||
|
||||
return with_wallet(*source_->get_wallet());
|
||||
}
|
||||
};
|
||||
}; // namespace tonlib
|
||||
|
||||
td::int64 TonlibClient::register_query(td::unique_ptr<Query> query) {
|
||||
auto query_id = ++next_query_id_;
|
||||
|
@ -2594,8 +3024,9 @@ td::Result<tonlib_api::object_ptr<tonlib_api::query_info>> TonlibClient::get_que
|
|||
if (it == queries_.end()) {
|
||||
return TonlibError::InvalidQueryId();
|
||||
}
|
||||
return tonlib_api::make_object<tonlib_api::query_info>(id, it->second->get_valid_until(),
|
||||
it->second->get_body_hash().as_slice().str());
|
||||
return tonlib_api::make_object<tonlib_api::query_info>(
|
||||
id, it->second->get_valid_until(), it->second->get_body_hash().as_slice().str(),
|
||||
to_bytes(it->second->get_message_body()), to_bytes(it->second->get_init_state()));
|
||||
}
|
||||
|
||||
void TonlibClient::finish_create_query(td::Result<td::unique_ptr<Query>> r_query,
|
||||
|
@ -2938,6 +3369,8 @@ td::Status TonlibClient::do_request(const tonlib_api::smc_runGetMethod& request,
|
|||
stack.write().push(std::move(e));
|
||||
}
|
||||
args.set_stack(std::move(stack));
|
||||
args.set_balance(it->second->get_balance());
|
||||
args.set_now(it->second->get_sync_time());
|
||||
auto res = smc->run_get_method(std::move(args));
|
||||
|
||||
// smc.runResult gas_used:int53 stack:vector<tvm.StackEntry> exit_code:int32 = smc.RunResult;
|
||||
|
@ -3057,6 +3490,76 @@ td::Status TonlibClient::do_request(const tonlib_api::dns_resolve& request,
|
|||
return td::Status::OK();
|
||||
}
|
||||
|
||||
td::Status TonlibClient::do_request(tonlib_api::pchan_signPromise& request,
|
||||
td::Promise<object_ptr<tonlib_api::pchan_promise>>&& promise) {
|
||||
if (!request.promise_) {
|
||||
return TonlibError::EmptyField("promise");
|
||||
}
|
||||
if (!request.input_key_) {
|
||||
return TonlibError::EmptyField("input_key");
|
||||
}
|
||||
TRY_RESULT(input_key, from_tonlib(*request.input_key_));
|
||||
make_request(int_api::GetPrivateKey{std::move(input_key)},
|
||||
promise.wrap([promise = std::move(request.promise_)](auto key) mutable {
|
||||
auto private_key = td::Ed25519::PrivateKey(std::move(key.private_key));
|
||||
promise->signature_ = ton::pchan::SignedPromiseBuilder()
|
||||
.promise_A(promise->promise_A_)
|
||||
.promise_B(promise->promise_B_)
|
||||
.channel_id(promise->channel_id_)
|
||||
.with_key(&private_key)
|
||||
.calc_signature()
|
||||
.as_slice()
|
||||
.str();
|
||||
return std::move(promise);
|
||||
}));
|
||||
return td::Status::OK();
|
||||
}
|
||||
td::Status TonlibClient::do_request(tonlib_api::pchan_validatePromise& request,
|
||||
td::Promise<object_ptr<tonlib_api::ok>>&& promise) {
|
||||
if (!request.promise_) {
|
||||
return TonlibError::EmptyField("promise");
|
||||
}
|
||||
TRY_RESULT(key_bytes, get_public_key(request.public_key_));
|
||||
auto key = td::Ed25519::PublicKey(td::SecureString(key_bytes.key));
|
||||
bool is_ok = ton::pchan::SignedPromiseBuilder()
|
||||
.promise_A(request.promise_->promise_A_)
|
||||
.promise_B(request.promise_->promise_B_)
|
||||
.channel_id(request.promise_->channel_id_)
|
||||
.check_signature(request.promise_->signature_, key);
|
||||
if (!is_ok) {
|
||||
return TonlibError::InvalidSignature();
|
||||
}
|
||||
promise.set_value(tonlib_api::make_object<tonlib_api::ok>());
|
||||
return td::Status::OK();
|
||||
}
|
||||
td::Status TonlibClient::do_request(tonlib_api::pchan_packPromise& request,
|
||||
td::Promise<object_ptr<tonlib_api::data>>&& promise) {
|
||||
if (!request.promise_) {
|
||||
return TonlibError::EmptyField("promise");
|
||||
}
|
||||
promise.set_value(tonlib_api::make_object<tonlib_api::data>(
|
||||
td::SecureString(to_bytes(ton::pchan::SignedPromiseBuilder()
|
||||
.promise_A(request.promise_->promise_A_)
|
||||
.promise_B(request.promise_->promise_B_)
|
||||
.channel_id(request.promise_->channel_id_)
|
||||
.signature(td::SecureString(request.promise_->signature_))
|
||||
.finalize()))));
|
||||
return td::Status::OK();
|
||||
}
|
||||
|
||||
td::Status TonlibClient::do_request(tonlib_api::pchan_unpackPromise& request,
|
||||
td::Promise<object_ptr<tonlib_api::pchan_promise>>&& promise) {
|
||||
TRY_RESULT_PREFIX(body, vm::std_boc_deserialize(request.data_), TonlibError::InvalidBagOfCells("data"));
|
||||
ton::pchan::SignedPromise spromise;
|
||||
if (!spromise.unpack(body)) {
|
||||
return TonlibError::InvalidField("data", "Can't unpack as a promise");
|
||||
}
|
||||
promise.set_value(tonlib_api::make_object<tonlib_api::pchan_promise>(
|
||||
spromise.o_signature.value().as_slice().str(), spromise.promise.promise_A, spromise.promise.promise_B,
|
||||
spromise.promise.channel_id));
|
||||
return td::Status::OK();
|
||||
}
|
||||
|
||||
td::Status TonlibClient::do_request(tonlib_api::sync& request,
|
||||
td::Promise<object_ptr<tonlib_api::ton_blockIdExt>>&& promise) {
|
||||
// ton.blockIdExt workchain:int32 shard:int64 seqno:int32 root_hash:bytes file_hash:bytes = ton.BlockIdExt;
|
||||
|
@ -3067,11 +3570,6 @@ td::Status TonlibClient::do_request(tonlib_api::sync& request,
|
|||
return td::Status::OK();
|
||||
}
|
||||
|
||||
td::Result<block::PublicKey> public_key_from_bytes(td::Slice bytes) {
|
||||
TRY_RESULT_PREFIX(key_bytes, block::PublicKey::from_bytes(bytes), TonlibError::Internal());
|
||||
return key_bytes;
|
||||
}
|
||||
|
||||
td::Status TonlibClient::do_request(const tonlib_api::createNewKey& request,
|
||||
td::Promise<object_ptr<tonlib_api::key>>&& promise) {
|
||||
TRY_RESULT_PREFIX(
|
||||
|
|
|
@ -74,6 +74,7 @@ class TonlibClient : public td::actor::Actor {
|
|||
LastBlockState last_state;
|
||||
std::string last_state_key;
|
||||
td::uint32 wallet_id;
|
||||
std::string rwallet_init_public_key;
|
||||
};
|
||||
|
||||
private:
|
||||
|
@ -307,6 +308,15 @@ class TonlibClient : public td::actor::Actor {
|
|||
|
||||
td::Status do_request(const tonlib_api::dns_resolve& request,
|
||||
td::Promise<object_ptr<tonlib_api::dns_resolved>>&& promise);
|
||||
|
||||
td::Status do_request(tonlib_api::pchan_signPromise& request,
|
||||
td::Promise<object_ptr<tonlib_api::pchan_promise>>&& promise);
|
||||
td::Status do_request(tonlib_api::pchan_validatePromise& request, td::Promise<object_ptr<tonlib_api::ok>>&& promise);
|
||||
|
||||
td::Status do_request(tonlib_api::pchan_packPromise& request, td::Promise<object_ptr<tonlib_api::data>>&& promise);
|
||||
td::Status do_request(tonlib_api::pchan_unpackPromise& request,
|
||||
td::Promise<object_ptr<tonlib_api::pchan_promise>>&& promise);
|
||||
|
||||
void do_dns_request(std::string name, td::int32 category, td::int32 ttl, td::optional<ton::BlockIdExt> block_id,
|
||||
block::StdAddress address, td::Promise<object_ptr<tonlib_api::dns_resolved>>&& promise);
|
||||
struct DnsFinishData {
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
// INVALID_ACCOUNT_ADDRESS
|
||||
// INVALID_CONFIG
|
||||
// INVALID_PEM_KEY
|
||||
// INVALID_REVISION
|
||||
// INVALID_SIGNATURE
|
||||
// MESSAGE_TOO_LONG
|
||||
// EMPTY_FIELD
|
||||
// INVALID_FIELD
|
||||
|
@ -84,6 +84,9 @@ struct TonlibError {
|
|||
static td::Status InvalidRevision() {
|
||||
return td::Status::Error(400, "INVALID_REVISION");
|
||||
}
|
||||
static td::Status InvalidSignature() {
|
||||
return td::Status::Error(400, "INVALID_SIGNATURE");
|
||||
}
|
||||
static td::Status NeedConfig() {
|
||||
return td::Status::Error(400, "NeedConfig");
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1172,13 +1172,16 @@ td::Status ValidatorEngine::load_global_config() {
|
|||
|
||||
validator_options_ = ton::validator::ValidatorManagerOptions::create(zero_state, init_block);
|
||||
validator_options_.write().set_shard_check_function(
|
||||
[](ton::ShardIdFull shard, ton::validator::ValidatorManagerOptions::ShardCheckMode mode) -> bool {
|
||||
[](ton::ShardIdFull shard, ton::CatchainSeqno cc_seqno,
|
||||
ton::validator::ValidatorManagerOptions::ShardCheckMode mode) -> bool {
|
||||
if (mode == ton::validator::ValidatorManagerOptions::ShardCheckMode::m_monitor) {
|
||||
return true;
|
||||
}
|
||||
CHECK(mode == ton::validator::ValidatorManagerOptions::ShardCheckMode::m_validate);
|
||||
//return shard.is_masterchain();
|
||||
return true;
|
||||
/*ton::ShardIdFull p{ton::basechainId, ((cc_seqno * 1ull % 4) << 62) + 1};
|
||||
auto s = ton::shard_prefix(p, 2);
|
||||
return shard.is_masterchain() || ton::shard_intersects(shard, s);*/
|
||||
});
|
||||
if (state_ttl_ != 0) {
|
||||
validator_options_.write().set_state_ttl(state_ttl_);
|
||||
|
@ -1195,12 +1198,12 @@ td::Status ValidatorEngine::load_global_config() {
|
|||
if (key_proof_ttl_ != 0) {
|
||||
validator_options_.write().set_key_proof_ttl(key_proof_ttl_);
|
||||
}
|
||||
if (db_depth_ <= 32) {
|
||||
validator_options_.write().set_filedb_depth(db_depth_);
|
||||
}
|
||||
for (auto seq : unsafe_catchains_) {
|
||||
validator_options_.write().add_unsafe_resync_catchain(seq);
|
||||
}
|
||||
if (truncate_seqno_ > 0) {
|
||||
validator_options_.write().truncate_db(truncate_seqno_);
|
||||
}
|
||||
|
||||
std::vector<ton::BlockIdExt> h;
|
||||
for (auto &x : conf.validator_->hardforks_) {
|
||||
|
@ -3079,6 +3082,10 @@ std::atomic<bool> rotate_logs_flags{false};
|
|||
void force_rotate_logs(int sig) {
|
||||
rotate_logs_flags.store(true);
|
||||
}
|
||||
std::atomic<bool> need_scheduler_status_flag{false};
|
||||
void need_scheduler_status(int sig) {
|
||||
need_scheduler_status_flag.store(true);
|
||||
}
|
||||
|
||||
void dump_memory_stats() {
|
||||
if (!is_memprof_on()) {
|
||||
|
@ -3164,15 +3171,6 @@ int main(int argc, char *argv[]) {
|
|||
acts.push_back([&x, fname = fname.str()]() { td::actor::send_closure(x, &ValidatorEngine::set_fift_dir, fname); });
|
||||
return td::Status::OK();
|
||||
});
|
||||
p.add_option('F', "filedb-depth",
|
||||
"depth of autodirs for blocks, proofs, etc. Default value is 2. You need to clear the "
|
||||
"database, if you need to change this option",
|
||||
[&](td::Slice fname) {
|
||||
acts.push_back([&x, fname = fname.str()]() {
|
||||
td::actor::send_closure(x, &ValidatorEngine::set_db_depth, td::to_integer<td::uint32>(fname));
|
||||
});
|
||||
return td::Status::OK();
|
||||
});
|
||||
p.add_option('d', "daemonize", "set SIGHUP", [&]() {
|
||||
#if TD_DARWIN || TD_LINUX
|
||||
close(0);
|
||||
|
@ -3215,6 +3213,12 @@ int main(int argc, char *argv[]) {
|
|||
acts.push_back([&x, v]() { td::actor::send_closure(x, &ValidatorEngine::set_sync_ttl, v); });
|
||||
return td::Status::OK();
|
||||
});
|
||||
p.add_option('T', "truncate-db", "truncate db (with specified seqno as new top masterchain block seqno)",
|
||||
[&](td::Slice fname) {
|
||||
auto v = td::to_integer<ton::BlockSeqno>(fname);
|
||||
acts.push_back([&x, v]() { td::actor::send_closure(x, &ValidatorEngine::set_truncate_seqno, v); });
|
||||
return td::Status::OK();
|
||||
});
|
||||
p.add_option('U', "unsafe-catchain-restore", "use SLOW and DANGEROUS catchain recover method", [&](td::Slice id) {
|
||||
TRY_RESULT(seq, td::to_integer_safe<ton::CatchainSeqno>(id));
|
||||
acts.push_back([&x, seq]() { td::actor::send_closure(x, &ValidatorEngine::add_unsafe_catchain, seq); });
|
||||
|
@ -3242,7 +3246,9 @@ int main(int argc, char *argv[]) {
|
|||
}
|
||||
|
||||
td::set_runtime_signal_handler(1, need_stats).ensure();
|
||||
td::set_runtime_signal_handler(2, need_scheduler_status).ensure();
|
||||
|
||||
td::actor::set_debug(true);
|
||||
td::actor::Scheduler scheduler({threads});
|
||||
|
||||
scheduler.run_in_context([&] {
|
||||
|
@ -3258,6 +3264,10 @@ int main(int argc, char *argv[]) {
|
|||
if (need_stats_flag.exchange(false)) {
|
||||
dump_stats();
|
||||
}
|
||||
if (need_scheduler_status_flag.exchange(false)) {
|
||||
LOG(ERROR) << "DUMPING SCHEDULER STATISTICS";
|
||||
scheduler.get_debug().dump();
|
||||
}
|
||||
if (rotate_logs_flags.exchange(false)) {
|
||||
if (td::log_interface) {
|
||||
td::log_interface->rotate();
|
||||
|
|
|
@ -195,10 +195,10 @@ class ValidatorEngine : public td::actor::Actor {
|
|||
td::Clocks::Duration sync_ttl_ = 0;
|
||||
td::Clocks::Duration archive_ttl_ = 0;
|
||||
td::Clocks::Duration key_proof_ttl_ = 0;
|
||||
td::uint32 db_depth_ = 33;
|
||||
bool read_config_ = false;
|
||||
bool started_keyring_ = false;
|
||||
bool started_ = false;
|
||||
ton::BlockSeqno truncate_seqno_{0};
|
||||
|
||||
std::set<ton::CatchainSeqno> unsafe_catchains_;
|
||||
|
||||
|
@ -216,9 +216,6 @@ class ValidatorEngine : public td::actor::Actor {
|
|||
fift_dir_ = str;
|
||||
}
|
||||
void set_db_root(std::string db_root);
|
||||
void set_db_depth(td::uint32 value) {
|
||||
db_depth_ = value;
|
||||
}
|
||||
void set_state_ttl(td::Clocks::Duration t) {
|
||||
state_ttl_ = t;
|
||||
}
|
||||
|
@ -234,6 +231,9 @@ class ValidatorEngine : public td::actor::Actor {
|
|||
void set_key_proof_ttl(td::Clocks::Duration t) {
|
||||
key_proof_ttl_ = t;
|
||||
}
|
||||
void set_truncate_seqno(ton::BlockSeqno seqno) {
|
||||
truncate_seqno_ = seqno;
|
||||
}
|
||||
void add_ip(td::IPAddress addr) {
|
||||
addrs_.push_back(addr);
|
||||
}
|
||||
|
|
|
@ -134,7 +134,7 @@ std::vector<PublicKey> ValidatorSessionDescriptionImpl::export_full_nodes() cons
|
|||
}
|
||||
|
||||
double ValidatorSessionDescriptionImpl::get_delay(td::uint32 priority) const {
|
||||
return priority * opts_.next_candidate_delay;
|
||||
return ((sources_.size() >= 5 ? 0 : 1) + priority) * opts_.next_candidate_delay;
|
||||
}
|
||||
|
||||
td::uint32 ValidatorSessionDescriptionImpl::get_vote_for_author(td::uint32 attempt_seqno) const {
|
||||
|
|
|
@ -110,6 +110,25 @@ set(DISK_VALIDATOR_SOURCE
|
|||
${VALIDATOR_DB_SOURCE}
|
||||
)
|
||||
|
||||
set(HARDFORK_VALIDATOR_SOURCE
|
||||
apply-block.cpp
|
||||
block-handle.cpp
|
||||
shard-client.cpp
|
||||
validator-full-id.cpp
|
||||
validator-group.cpp
|
||||
validator-options.cpp
|
||||
|
||||
downloaders/wait-block-data-disk.cpp
|
||||
downloaders/wait-block-state.cpp
|
||||
downloaders/wait-block-state-merge.cpp
|
||||
downloaders/download-state.cpp
|
||||
|
||||
manager-init.cpp
|
||||
manager-hardfork.cpp
|
||||
|
||||
${VALIDATOR_DB_SOURCE}
|
||||
)
|
||||
|
||||
set(FULL_NODE_SOURCE
|
||||
full-node.h
|
||||
full-node.hpp
|
||||
|
@ -139,6 +158,7 @@ set(FULL_NODE_SOURCE
|
|||
|
||||
add_library(validator STATIC ${VALIDATOR_SOURCE})
|
||||
add_library(validator-disk STATIC ${DISK_VALIDATOR_SOURCE})
|
||||
add_library(validator-hardfork STATIC ${HARDFORK_VALIDATOR_SOURCE})
|
||||
add_library(full-node STATIC ${FULL_NODE_SOURCE})
|
||||
|
||||
target_include_directories(validator PUBLIC
|
||||
|
@ -155,6 +175,13 @@ target_include_directories(validator-disk PUBLIC
|
|||
${OPENSSL_INCLUDE_DIR}
|
||||
)
|
||||
|
||||
target_include_directories(validator-hardfork PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>/..
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>/../crypto
|
||||
${OPENSSL_INCLUDE_DIR}
|
||||
)
|
||||
|
||||
target_include_directories(full-node PUBLIC
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>
|
||||
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}>/..
|
||||
|
@ -168,5 +195,8 @@ target_link_libraries(validator PRIVATE tdutils tdactor adnl rldp tl_api dht tdf
|
|||
target_link_libraries(validator-disk PRIVATE tdutils tdactor adnl rldp tl_api dht tdfec
|
||||
overlay catchain validatorsession ton_crypto ton_block ton_db)
|
||||
|
||||
target_link_libraries(validator-hardfork PRIVATE tdutils tdactor adnl rldp tl_api dht tdfec
|
||||
overlay catchain validatorsession ton_crypto ton_block ton_db)
|
||||
|
||||
target_link_libraries(full-node PRIVATE tdutils tdactor adnl rldp tl_api dht tdfec
|
||||
overlay catchain validatorsession ton_crypto ton_block ton_db)
|
||||
|
|
|
@ -42,8 +42,8 @@ td::BufferSlice BlockHandleImpl::serialize() const {
|
|||
(flags & dbf_inited_masterchain_ref_block) ? masterchain_ref_seqno_ : 0);
|
||||
}
|
||||
|
||||
BlockHandleImpl::BlockHandleImpl(td::BufferSlice data) {
|
||||
auto obj = fetch_tl_object<ton_api::db_block_info>(std::move(data), true).move_as_ok();
|
||||
BlockHandleImpl::BlockHandleImpl(td::Slice data) {
|
||||
auto obj = fetch_tl_object<ton_api::db_block_info>(data, true).move_as_ok();
|
||||
flags_ = obj->flags_ & ~(Flags::dbf_processed | Flags::dbf_moved_handle);
|
||||
id_ = create_block_id(obj->id_);
|
||||
prev_[0] = (flags_ & dbf_inited_prev_left) ? create_block_id(obj->prev_left_) : BlockIdExt{};
|
||||
|
|
|
@ -517,7 +517,7 @@ struct BlockHandleImpl : public BlockHandleInterface {
|
|||
: id_(id), flags_(id_.is_masterchain() ? static_cast<td::uint32>(dbf_masterchain) : 0) {
|
||||
get_thread_safe_counter().add(1);
|
||||
}
|
||||
BlockHandleImpl(td::BufferSlice data);
|
||||
BlockHandleImpl(td::Slice data);
|
||||
~BlockHandleImpl() {
|
||||
LOG_CHECK(!need_flush()) << "flags=" << flags_;
|
||||
get_thread_safe_counter().add(-1);
|
||||
|
@ -532,7 +532,7 @@ struct BlockHandleImpl : public BlockHandleInterface {
|
|||
return std::make_shared<BlockHandleImpl>(id);
|
||||
}
|
||||
|
||||
static BlockHandle create(td::BufferSlice data) {
|
||||
static BlockHandle create(td::Slice data) {
|
||||
return std::make_shared<BlockHandleImpl>(std::move(data));
|
||||
}
|
||||
};
|
||||
|
|
|
@ -77,10 +77,20 @@ void ArchiveManager::update_handle(BlockHandle handle, td::Promise<td::Unit> pro
|
|||
FileDescription *f;
|
||||
if (handle->handle_moved_to_archive()) {
|
||||
CHECK(handle->inited_unix_time());
|
||||
if (!handle->need_flush()) {
|
||||
promise.set_value(td::Unit());
|
||||
return;
|
||||
}
|
||||
f = get_file_desc(handle->id().shard_full(), get_package_id(handle->masterchain_ref_block()), handle->id().seqno(),
|
||||
handle->unix_time(), handle->logical_time(), true);
|
||||
if (!f) {
|
||||
handle->flushed_upto(handle->version());
|
||||
promise.set_value(td::Unit());
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
f = get_file_desc(handle->id().shard_full(), get_temp_package_id(), 0, 0, 0, true);
|
||||
CHECK(f);
|
||||
}
|
||||
td::actor::send_closure(f->file_actor_id(), &ArchiveSlice::update_handle, std::move(handle), std::move(promise));
|
||||
}
|
||||
|
@ -540,7 +550,7 @@ void ArchiveManager::load_package(PackageId id) {
|
|||
}
|
||||
}
|
||||
|
||||
desc.file = td::actor::create_actor<ArchiveSlice>("slice", id.id, id.key, id.temp, db_root_);
|
||||
desc.file = td::actor::create_actor<ArchiveSlice>("slice", id.id, id.key, id.temp, false, db_root_);
|
||||
|
||||
get_file_map(id).emplace(id, std::move(desc));
|
||||
}
|
||||
|
@ -551,7 +561,6 @@ ArchiveManager::FileDescription *ArchiveManager::get_file_desc(ShardIdFull shard
|
|||
auto it = f.find(id);
|
||||
if (it != f.end()) {
|
||||
if (it->second.deleted) {
|
||||
CHECK(!force);
|
||||
return nullptr;
|
||||
}
|
||||
if (force && !id.temp) {
|
||||
|
@ -574,7 +583,7 @@ ArchiveManager::FileDescription *ArchiveManager::add_file_desc(ShardIdFull shard
|
|||
FileDescription desc{id, false};
|
||||
td::mkdir(db_root_ + id.path()).ensure();
|
||||
std::string prefix = PSTRING() << db_root_ << id.path() << id.name();
|
||||
desc.file = td::actor::create_actor<ArchiveSlice>("slice", id.id, id.key, id.temp, db_root_);
|
||||
desc.file = td::actor::create_actor<ArchiveSlice>("slice", id.id, id.key, id.temp, false, db_root_);
|
||||
if (!id.temp) {
|
||||
update_desc(desc, shard, seqno, ts, lt);
|
||||
}
|
||||
|
@ -648,7 +657,11 @@ ArchiveManager::FileDescription *ArchiveManager::get_file_desc_by_seqno(ShardIdF
|
|||
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;
|
||||
if (it->second.deleted) {
|
||||
return nullptr;
|
||||
} else {
|
||||
return &it->second;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
|
@ -660,7 +673,11 @@ ArchiveManager::FileDescription *ArchiveManager::get_file_desc_by_unix_time(Shar
|
|||
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.ts <= ts) {
|
||||
return &it->second;
|
||||
if (it->second.deleted) {
|
||||
return nullptr;
|
||||
} else {
|
||||
return &it->second;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
|
@ -672,7 +689,11 @@ ArchiveManager::FileDescription *ArchiveManager::get_file_desc_by_lt(ShardIdFull
|
|||
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.lt <= lt) {
|
||||
return &it->second;
|
||||
if (it->second.deleted) {
|
||||
return nullptr;
|
||||
} else {
|
||||
return &it->second;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
|
@ -823,6 +844,14 @@ void ArchiveManager::start_up() {
|
|||
}
|
||||
}
|
||||
|
||||
v = index_->get("finalizedupto", value);
|
||||
v.ensure();
|
||||
if (v.move_as_ok() == td::KeyValue::GetStatus::Ok) {
|
||||
auto R = td::to_integer_safe<td::uint32>(value);
|
||||
R.ensure();
|
||||
finalized_up_to_ = R.move_as_ok();
|
||||
}
|
||||
|
||||
td::WalkPath::run(db_root_ + "/archive/states/", [&](td::CSlice fname, td::WalkPath::Type t) -> void {
|
||||
if (t == td::WalkPath::Type::NotDir) {
|
||||
LOG(ERROR) << "checking file " << fname;
|
||||
|
@ -853,7 +882,7 @@ void ArchiveManager::start_up() {
|
|||
persistent_state_gc(FileHash::zero());
|
||||
}
|
||||
|
||||
void ArchiveManager::run_gc(UnixTime ts) {
|
||||
void ArchiveManager::run_gc(UnixTime ts, UnixTime archive_ttl) {
|
||||
auto p = get_temp_package_id_by_unixtime(ts);
|
||||
std::vector<PackageId> vec;
|
||||
for (auto &x : temp_files_) {
|
||||
|
@ -863,13 +892,37 @@ void ArchiveManager::run_gc(UnixTime ts) {
|
|||
break;
|
||||
}
|
||||
}
|
||||
if (vec.size() <= 1) {
|
||||
return;
|
||||
}
|
||||
vec.resize(vec.size() - 1, PackageId::empty(false, true));
|
||||
if (vec.size() > 1) {
|
||||
vec.resize(vec.size() - 1, PackageId::empty(false, true));
|
||||
|
||||
for (auto &x : vec) {
|
||||
delete_package(x, [](td::Unit) {});
|
||||
for (auto &x : vec) {
|
||||
delete_package(x, [](td::Unit) {});
|
||||
}
|
||||
}
|
||||
vec.clear();
|
||||
|
||||
if (archive_ttl > 0) {
|
||||
for (auto &f : files_) {
|
||||
auto &desc = f.second;
|
||||
if (desc.deleted) {
|
||||
continue;
|
||||
}
|
||||
auto it = desc.first_blocks.find(ShardIdFull{masterchainId});
|
||||
if (it == desc.first_blocks.end()) {
|
||||
continue;
|
||||
}
|
||||
if (it->second.ts < ts - archive_ttl) {
|
||||
vec.push_back(f.first);
|
||||
}
|
||||
}
|
||||
if (vec.size() > 1) {
|
||||
vec.resize(vec.size() - 1, PackageId::empty(false, true));
|
||||
|
||||
for (auto &x : vec) {
|
||||
LOG(ERROR) << "WARNING: deleting package " << x.id;
|
||||
delete_package(x, [](td::Unit) {});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1049,6 +1102,102 @@ void ArchiveManager::set_async_mode(bool mode, td::Promise<td::Unit> promise) {
|
|||
}
|
||||
}
|
||||
|
||||
void ArchiveManager::truncate(BlockSeqno masterchain_seqno, ConstBlockHandle handle, td::Promise<td::Unit> promise) {
|
||||
index_->begin_transaction().ensure();
|
||||
td::MultiPromise mp;
|
||||
auto ig = mp.init_guard();
|
||||
ig.add_promise(std::move(promise));
|
||||
for (auto &x : temp_files_) {
|
||||
if (!x.second.deleted) {
|
||||
td::actor::send_closure(x.second.file_actor_id(), &ArchiveSlice::destroy, ig.get_promise());
|
||||
x.second.file.release();
|
||||
}
|
||||
}
|
||||
temp_files_.clear();
|
||||
|
||||
{
|
||||
auto it = key_files_.begin();
|
||||
while (it != key_files_.end()) {
|
||||
if (it->first.id <= masterchain_seqno) {
|
||||
td::actor::send_closure(it->second.file_actor_id(), &ArchiveSlice::truncate, masterchain_seqno, handle,
|
||||
ig.get_promise());
|
||||
it++;
|
||||
} else {
|
||||
auto it2 = it;
|
||||
it++;
|
||||
td::actor::send_closure(it2->second.file_actor_id(), &ArchiveSlice::destroy, ig.get_promise());
|
||||
it2->second.file.release();
|
||||
index_
|
||||
->erase(create_serialize_tl_object<ton_api::db_files_package_key>(it2->second.id.id, it2->second.id.key,
|
||||
it2->second.id.temp)
|
||||
.as_slice())
|
||||
.ensure();
|
||||
key_files_.erase(it2);
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
auto it = files_.begin();
|
||||
while (it != files_.end()) {
|
||||
if (it->first.id <= masterchain_seqno) {
|
||||
td::actor::send_closure(it->second.file_actor_id(), &ArchiveSlice::truncate, masterchain_seqno, handle,
|
||||
ig.get_promise());
|
||||
it++;
|
||||
} else {
|
||||
auto it2 = it;
|
||||
it++;
|
||||
td::actor::send_closure(it2->second.file_actor_id(), &ArchiveSlice::destroy, ig.get_promise());
|
||||
it2->second.file.release();
|
||||
index_
|
||||
->erase(create_serialize_tl_object<ton_api::db_files_package_key>(it2->second.id.id, it2->second.id.key,
|
||||
it2->second.id.temp)
|
||||
.as_slice())
|
||||
.ensure();
|
||||
files_.erase(it2);
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
std::vector<td::int32> t;
|
||||
std::vector<td::int32> tk;
|
||||
std::vector<td::int32> tt;
|
||||
for (auto &e : files_) {
|
||||
t.push_back(e.first.id);
|
||||
}
|
||||
for (auto &e : key_files_) {
|
||||
tk.push_back(e.first.id);
|
||||
}
|
||||
for (auto &e : temp_files_) {
|
||||
tt.push_back(e.first.id);
|
||||
}
|
||||
index_
|
||||
->set(create_serialize_tl_object<ton_api::db_files_index_key>().as_slice(),
|
||||
create_serialize_tl_object<ton_api::db_files_index_value>(std::move(t), std::move(tk), std::move(tt))
|
||||
.as_slice())
|
||||
.ensure();
|
||||
}
|
||||
index_->commit_transaction().ensure();
|
||||
|
||||
{
|
||||
auto it = perm_states_.begin();
|
||||
while (it != perm_states_.end()) {
|
||||
int res = 0;
|
||||
it->second.ref().visit(td::overloaded(
|
||||
[&](const fileref::ZeroStateShort &x) { res = -1; },
|
||||
[&](const fileref::PersistentStateShort &x) { res = x.masterchain_seqno <= masterchain_seqno ? -1 : 1; },
|
||||
[&](const auto &obj) { res = 1; }));
|
||||
if (res <= 0) {
|
||||
it++;
|
||||
} else {
|
||||
auto it2 = it;
|
||||
it++;
|
||||
td::unlink(db_root_ + "/archive/states/" + it2->second.filename_short()).ignore();
|
||||
perm_states_.erase(it2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace validator
|
||||
|
||||
} // namespace ton
|
||||
|
|
|
@ -52,10 +52,10 @@ class ArchiveManager : public td::actor::Actor {
|
|||
void check_persistent_state(BlockIdExt block_id, BlockIdExt masterchain_block_id, td::Promise<bool> promise);
|
||||
void check_zero_state(BlockIdExt block_id, td::Promise<bool> promise);
|
||||
|
||||
//void truncate(BlockSeqno masterchain_seqno, td::Promise<td::Unit> promise);
|
||||
void truncate(BlockSeqno masterchain_seqno, ConstBlockHandle handle, td::Promise<td::Unit> promise);
|
||||
//void truncate_continue(BlockSeqno masterchain_seqno, td::Promise<td::Unit> promise);
|
||||
|
||||
void run_gc(UnixTime ts);
|
||||
void run_gc(UnixTime ts, UnixTime archive_ttl);
|
||||
|
||||
/* from LTDB */
|
||||
void get_block_by_unix_time(AccountIdPrefixFull account_id, UnixTime ts, td::Promise<ConstBlockHandle> promise);
|
||||
|
@ -104,6 +104,7 @@ class ArchiveManager : public td::actor::Actor {
|
|||
std::map<PackageId, FileDescription> files_;
|
||||
std::map<PackageId, FileDescription> key_files_;
|
||||
std::map<PackageId, FileDescription> temp_files_;
|
||||
BlockSeqno finalized_up_to_{0};
|
||||
bool async_mode_ = false;
|
||||
bool huge_transaction_started_ = false;
|
||||
td::uint32 huge_transaction_size_ = 0;
|
||||
|
|
|
@ -309,7 +309,7 @@ void ArchiveSlice::get_block_common(AccountIdPrefixFull account_id,
|
|||
}
|
||||
}
|
||||
f = true;
|
||||
auto G = fetch_tl_object<ton_api::db_lt_desc_value>(td::BufferSlice{value}, true);
|
||||
auto G = fetch_tl_object<ton_api::db_lt_desc_value>(value, true);
|
||||
G.ensure();
|
||||
auto g = G.move_as_ok();
|
||||
if (compare_desc(*g.get()) > 0) {
|
||||
|
@ -467,12 +467,18 @@ void ArchiveSlice::start_up() {
|
|||
R2 = kv_->get(PSTRING() << "status." << i, value);
|
||||
R2.ensure();
|
||||
auto len = td::to_integer<td::uint64>(value);
|
||||
R2 = kv_->get(PSTRING() << "version." << i, value);
|
||||
R2.ensure();
|
||||
td::uint32 ver = 0;
|
||||
if (R2.move_as_ok() == td::KeyValue::GetStatus::Ok) {
|
||||
ver = td::to_integer<td::uint32>(value);
|
||||
}
|
||||
auto v = archive_id_ + slice_size_ * i;
|
||||
add_package(v, len);
|
||||
add_package(v, len, ver);
|
||||
}
|
||||
} else {
|
||||
auto len = td::to_integer<td::uint64>(value);
|
||||
add_package(archive_id_, len);
|
||||
add_package(archive_id_, len, 0);
|
||||
}
|
||||
} else {
|
||||
if (!temp_ && !key_blocks_only_) {
|
||||
|
@ -482,13 +488,15 @@ void ArchiveSlice::start_up() {
|
|||
kv_->set("slices", "1").ensure();
|
||||
kv_->set("slice_size", td::to_string(slice_size_)).ensure();
|
||||
kv_->set("status.0", "0").ensure();
|
||||
kv_->set("version.0", td::to_string(default_package_version())).ensure();
|
||||
kv_->commit_transaction().ensure();
|
||||
add_package(archive_id_, 0, default_package_version());
|
||||
} else {
|
||||
kv_->begin_transaction().ensure();
|
||||
kv_->set("status", "0").ensure();
|
||||
kv_->commit_transaction().ensure();
|
||||
add_package(archive_id_, 0, 0);
|
||||
}
|
||||
add_package(archive_id_, 0);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -528,8 +536,12 @@ void ArchiveSlice::set_async_mode(bool mode, td::Promise<td::Unit> promise) {
|
|||
}
|
||||
}
|
||||
|
||||
ArchiveSlice::ArchiveSlice(td::uint32 archive_id, bool key_blocks_only, bool temp, std::string db_root)
|
||||
: archive_id_(archive_id), key_blocks_only_(key_blocks_only), temp_(temp), db_root_(std::move(db_root)) {
|
||||
ArchiveSlice::ArchiveSlice(td::uint32 archive_id, bool key_blocks_only, bool temp, bool finalized, std::string db_root)
|
||||
: archive_id_(archive_id)
|
||||
, key_blocks_only_(key_blocks_only)
|
||||
, temp_(temp)
|
||||
, finalized_(finalized)
|
||||
, db_root_(std::move(db_root)) {
|
||||
}
|
||||
|
||||
td::Result<ArchiveSlice::PackageInfo *> ArchiveSlice::choose_package(BlockSeqno masterchain_seqno, bool force) {
|
||||
|
@ -548,16 +560,17 @@ td::Result<ArchiveSlice::PackageInfo *> ArchiveSlice::choose_package(BlockSeqno
|
|||
begin_transaction();
|
||||
kv_->set("slices", td::to_string(v + 1)).ensure();
|
||||
kv_->set(PSTRING() << "status." << v, "0").ensure();
|
||||
kv_->set(PSTRING() << "version." << v, td::to_string(default_package_version())).ensure();
|
||||
commit_transaction();
|
||||
CHECK((masterchain_seqno - archive_id_) % slice_size_ == 0);
|
||||
add_package(masterchain_seqno, 0);
|
||||
add_package(masterchain_seqno, 0, default_package_version());
|
||||
return &packages_[v];
|
||||
} else {
|
||||
return &packages_[v];
|
||||
}
|
||||
}
|
||||
|
||||
void ArchiveSlice::add_package(td::uint32 seqno, td::uint64 size) {
|
||||
void ArchiveSlice::add_package(td::uint32 seqno, td::uint64 size, td::uint32 version) {
|
||||
PackageId p_id{seqno, key_blocks_only_, temp_};
|
||||
std::string path = PSTRING() << db_root_ << p_id.path() << p_id.name() << ".pack";
|
||||
auto R = Package::open(path, false, true);
|
||||
|
@ -565,9 +578,17 @@ void ArchiveSlice::add_package(td::uint32 seqno, td::uint64 size) {
|
|||
LOG(FATAL) << "failed to open/create archive '" << path << "': " << R.move_as_error();
|
||||
return;
|
||||
}
|
||||
auto idx = td::narrow_cast<td::uint32>(packages_.size());
|
||||
if (finalized_) {
|
||||
packages_.emplace_back(nullptr, td::actor::ActorOwn<PackageWriter>(), seqno, path, idx, version);
|
||||
return;
|
||||
}
|
||||
auto pack = std::make_shared<Package>(R.move_as_ok());
|
||||
if (version >= 1) {
|
||||
pack->truncate(size).ensure();
|
||||
}
|
||||
auto writer = td::actor::create_actor<PackageWriter>("writer", pack);
|
||||
packages_.emplace_back(std::move(pack), std::move(writer), seqno, path, 0);
|
||||
packages_.emplace_back(std::move(pack), std::move(writer), seqno, path, idx, version);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
@ -609,6 +630,207 @@ void ArchiveSlice::destroy(td::Promise<td::Unit> promise) {
|
|||
td::Timestamp::in(0.0));
|
||||
}
|
||||
|
||||
BlockSeqno ArchiveSlice::max_masterchain_seqno() {
|
||||
auto key = get_db_key_lt_desc(ShardIdFull{masterchainId});
|
||||
std::string value;
|
||||
auto F = kv_->get(key, value);
|
||||
F.ensure();
|
||||
if (F.move_as_ok() == td::KeyValue::GetStatus::NotFound) {
|
||||
return 0;
|
||||
}
|
||||
auto G = fetch_tl_object<ton_api::db_lt_desc_value>(value, true);
|
||||
G.ensure();
|
||||
auto g = G.move_as_ok();
|
||||
if (g->first_idx_ == g->last_idx_) {
|
||||
return 0;
|
||||
}
|
||||
auto last_idx = g->last_idx_ - 1;
|
||||
auto db_key = get_db_key_lt_el(ShardIdFull{masterchainId}, last_idx);
|
||||
F = kv_->get(db_key, value);
|
||||
F.ensure();
|
||||
CHECK(F.move_as_ok() == td::KeyValue::GetStatus::Ok);
|
||||
auto E = fetch_tl_object<ton_api::db_lt_el_value>(td::BufferSlice{value}, true);
|
||||
E.ensure();
|
||||
auto e = E.move_as_ok();
|
||||
return e->id_->seqno_;
|
||||
}
|
||||
|
||||
void ArchiveSlice::delete_file(FileReference ref_id) {
|
||||
std::string value;
|
||||
auto R = kv_->get(ref_id.hash().to_hex(), value);
|
||||
R.ensure();
|
||||
if (R.move_as_ok() == td::KeyValue::GetStatus::NotFound) {
|
||||
return;
|
||||
}
|
||||
kv_->erase(ref_id.hash().to_hex());
|
||||
}
|
||||
|
||||
void ArchiveSlice::delete_handle(ConstBlockHandle handle) {
|
||||
delete_file(fileref::Proof{handle->id()});
|
||||
delete_file(fileref::ProofLink{handle->id()});
|
||||
delete_file(fileref::Block{handle->id()});
|
||||
kv_->erase(get_db_key_block_info(handle->id()));
|
||||
}
|
||||
|
||||
void ArchiveSlice::move_file(FileReference ref_id, Package *old_pack, Package *pack) {
|
||||
LOG(DEBUG) << "moving " << ref_id.filename_short();
|
||||
std::string value;
|
||||
auto R = kv_->get(ref_id.hash().to_hex(), value);
|
||||
R.ensure();
|
||||
if (R.move_as_ok() == td::KeyValue::GetStatus::NotFound) {
|
||||
return;
|
||||
}
|
||||
auto offset = td::to_integer<td::uint64>(value);
|
||||
auto V = old_pack->read(offset);
|
||||
V.ensure();
|
||||
auto data = std::move(V.move_as_ok().second);
|
||||
auto r = pack->append(ref_id.filename(), std::move(data), false);
|
||||
kv_->set(ref_id.hash().to_hex(), td::to_string(r));
|
||||
}
|
||||
|
||||
void ArchiveSlice::move_handle(ConstBlockHandle handle, Package *old_pack, Package *pack) {
|
||||
move_file(fileref::Proof{handle->id()}, old_pack, pack);
|
||||
move_file(fileref::ProofLink{handle->id()}, old_pack, pack);
|
||||
move_file(fileref::Block{handle->id()}, old_pack, pack);
|
||||
}
|
||||
|
||||
bool ArchiveSlice::truncate_block(BlockSeqno masterchain_seqno, BlockIdExt block_id, td::uint32 cutoff_idx,
|
||||
Package *pack) {
|
||||
std::string value;
|
||||
auto R = kv_->get(get_db_key_block_info(block_id), value);
|
||||
R.ensure();
|
||||
CHECK(R.move_as_ok() == td::KeyValue::GetStatus::Ok);
|
||||
auto E = create_block_handle(value);
|
||||
E.ensure();
|
||||
auto handle = E.move_as_ok();
|
||||
auto seqno = handle->id().is_masterchain() ? handle->id().seqno() : handle->masterchain_ref_block();
|
||||
if (seqno > masterchain_seqno) {
|
||||
delete_handle(std::move(handle));
|
||||
return false;
|
||||
}
|
||||
|
||||
auto S = choose_package(seqno, false);
|
||||
S.ensure();
|
||||
auto p = S.move_as_ok();
|
||||
CHECK(p->idx <= cutoff_idx);
|
||||
if (p->idx == cutoff_idx) {
|
||||
move_handle(std::move(handle), p->package.get(), pack);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ArchiveSlice::truncate_shard(BlockSeqno masterchain_seqno, ShardIdFull shard, td::uint32 cutoff_idx,
|
||||
Package *pack) {
|
||||
auto key = get_db_key_lt_desc(shard);
|
||||
std::string value;
|
||||
auto F = kv_->get(key, value);
|
||||
F.ensure();
|
||||
if (F.move_as_ok() == td::KeyValue::GetStatus::NotFound) {
|
||||
return;
|
||||
}
|
||||
auto G = fetch_tl_object<ton_api::db_lt_desc_value>(value, true);
|
||||
G.ensure();
|
||||
auto g = G.move_as_ok();
|
||||
if (g->first_idx_ == g->last_idx_) {
|
||||
return;
|
||||
}
|
||||
|
||||
int new_last_idx = g->first_idx_;
|
||||
for (int i = g->first_idx_; i < g->last_idx_; i++) {
|
||||
auto db_key = get_db_key_lt_el(shard, i);
|
||||
F = kv_->get(db_key, value);
|
||||
F.ensure();
|
||||
CHECK(F.move_as_ok() == td::KeyValue::GetStatus::Ok);
|
||||
auto E = fetch_tl_object<ton_api::db_lt_el_value>(value, true);
|
||||
E.ensure();
|
||||
auto e = E.move_as_ok();
|
||||
|
||||
if (truncate_block(masterchain_seqno, create_block_id(e->id_), cutoff_idx, pack)) {
|
||||
CHECK(new_last_idx == i);
|
||||
new_last_idx = i + 1;
|
||||
}
|
||||
}
|
||||
|
||||
if (g->last_idx_ != new_last_idx) {
|
||||
g->last_idx_ = new_last_idx;
|
||||
kv_->set(key, serialize_tl_object(g, true)).ensure();
|
||||
}
|
||||
}
|
||||
|
||||
void ArchiveSlice::truncate(BlockSeqno masterchain_seqno, ConstBlockHandle handle, td::Promise<td::Unit> promise) {
|
||||
if (temp_ || archive_id_ > masterchain_seqno) {
|
||||
destroy(std::move(promise));
|
||||
return;
|
||||
}
|
||||
LOG(INFO) << "TRUNCATE: slice " << archive_id_ << " maxseqno= " << max_masterchain_seqno()
|
||||
<< " truncate_upto=" << masterchain_seqno;
|
||||
if (max_masterchain_seqno() <= masterchain_seqno) {
|
||||
promise.set_value(td::Unit());
|
||||
return;
|
||||
}
|
||||
|
||||
auto cutoff = choose_package(masterchain_seqno, false);
|
||||
cutoff.ensure();
|
||||
auto pack = cutoff.move_as_ok();
|
||||
CHECK(pack);
|
||||
|
||||
auto pack_r = Package::open(pack->path + ".new", false, true);
|
||||
pack_r.ensure();
|
||||
auto new_package = std::make_shared<Package>(pack_r.move_as_ok());
|
||||
new_package->truncate(0).ensure();
|
||||
|
||||
std::string value;
|
||||
auto status_key = create_serialize_tl_object<ton_api::db_lt_status_key>();
|
||||
auto R = kv_->get(status_key, value);
|
||||
R.ensure();
|
||||
|
||||
auto F = fetch_tl_object<ton_api::db_lt_status_value>(value, true);
|
||||
F.ensure();
|
||||
auto f = F.move_as_ok();
|
||||
|
||||
kv_->begin_transaction().ensure();
|
||||
for (int i = 0; i < f->total_shards_; i++) {
|
||||
auto shard_key = create_serialize_tl_object<ton_api::db_lt_shard_key>(i);
|
||||
R = kv_->get(shard_key, value);
|
||||
R.ensure();
|
||||
CHECK(R.move_as_ok() == td::KeyValue::GetStatus::Ok);
|
||||
|
||||
auto G = fetch_tl_object<ton_api::db_lt_shard_value>(value, true);
|
||||
G.ensure();
|
||||
auto g = G.move_as_ok();
|
||||
|
||||
truncate_shard(masterchain_seqno, ShardIdFull{g->workchain_, static_cast<td::uint64>(g->shard_)}, pack->idx,
|
||||
new_package.get());
|
||||
}
|
||||
|
||||
if (!sliced_mode_) {
|
||||
kv_->set("status", td::to_string(new_package->size())).ensure();
|
||||
} else {
|
||||
kv_->set(PSTRING() << "status." << pack->idx, td::to_string(new_package->size())).ensure();
|
||||
for (size_t i = pack->idx + 1; i < packages_.size(); i++) {
|
||||
kv_->erase(PSTRING() << "status." << i);
|
||||
kv_->erase(PSTRING() << "version." << i);
|
||||
}
|
||||
kv_->set("slices", td::to_string(pack->idx + 1));
|
||||
}
|
||||
|
||||
pack->package = new_package;
|
||||
pack->writer.reset();
|
||||
td::unlink(pack->path).ensure();
|
||||
td::rename(pack->path + ".new", pack->path).ensure();
|
||||
pack->writer = td::actor::create_actor<PackageWriter>("writer", new_package);
|
||||
|
||||
for (auto idx = pack->idx + 1; idx < packages_.size(); idx++) {
|
||||
td::unlink(packages_[idx].path).ensure();
|
||||
}
|
||||
packages_.erase(packages_.begin() + pack->idx + 1);
|
||||
|
||||
kv_->commit_transaction().ensure();
|
||||
|
||||
promise.set_value(td::Unit());
|
||||
}
|
||||
|
||||
} // namespace validator
|
||||
|
||||
} // namespace ton
|
||||
|
|
|
@ -73,7 +73,7 @@ class PackageWriter : public td::actor::Actor {
|
|||
|
||||
class ArchiveSlice : public td::actor::Actor {
|
||||
public:
|
||||
ArchiveSlice(td::uint32 archive_id, bool key_blocks_only, bool temp, std::string db_root);
|
||||
ArchiveSlice(td::uint32 archive_id, bool key_blocks_only, bool temp, bool finalized, std::string db_root);
|
||||
|
||||
void get_archive_id(BlockSeqno masterchain_seqno, td::Promise<td::uint64> promise);
|
||||
|
||||
|
@ -97,6 +97,7 @@ class ArchiveSlice : public td::actor::Actor {
|
|||
|
||||
void start_up() override;
|
||||
void destroy(td::Promise<td::Unit> promise);
|
||||
void truncate(BlockSeqno masterchain_seqno, ConstBlockHandle handle, td::Promise<td::Unit> promise);
|
||||
|
||||
void begin_transaction();
|
||||
void commit_transaction();
|
||||
|
@ -117,6 +118,7 @@ class ArchiveSlice : public td::actor::Actor {
|
|||
|
||||
bool key_blocks_only_;
|
||||
bool temp_;
|
||||
bool finalized_;
|
||||
|
||||
bool destroyed_ = false;
|
||||
bool async_mode_ = false;
|
||||
|
@ -130,19 +132,38 @@ class ArchiveSlice : public td::actor::Actor {
|
|||
|
||||
struct PackageInfo {
|
||||
PackageInfo(std::shared_ptr<Package> package, td::actor::ActorOwn<PackageWriter> writer, BlockSeqno id,
|
||||
std::string path, td::uint32 idx)
|
||||
: package(std::move(package)), writer(std ::move(writer)), id(id), path(std::move(path)), idx(idx) {
|
||||
std::string path, td::uint32 idx, td::uint32 version)
|
||||
: package(std::move(package))
|
||||
, writer(std ::move(writer))
|
||||
, id(id)
|
||||
, path(std::move(path))
|
||||
, idx(idx)
|
||||
, version(version) {
|
||||
}
|
||||
std::shared_ptr<Package> package;
|
||||
td::actor::ActorOwn<PackageWriter> writer;
|
||||
BlockSeqno id;
|
||||
std::string path;
|
||||
td::uint32 idx;
|
||||
td::uint32 version;
|
||||
};
|
||||
std::vector<PackageInfo> packages_;
|
||||
|
||||
td::Result<PackageInfo *> choose_package(BlockSeqno masterchain_seqno, bool force);
|
||||
void add_package(BlockSeqno masterchain_seqno, td::uint64 size);
|
||||
void add_package(BlockSeqno masterchain_seqno, td::uint64 size, td::uint32 version);
|
||||
void truncate_shard(BlockSeqno masterchain_seqno, ShardIdFull shard, td::uint32 cutoff_idx, Package *pack);
|
||||
bool truncate_block(BlockSeqno masterchain_seqno, BlockIdExt block_id, td::uint32 cutoff_idx, Package *pack);
|
||||
|
||||
void delete_handle(ConstBlockHandle handle);
|
||||
void delete_file(FileReference ref_id);
|
||||
void move_handle(ConstBlockHandle handle, Package *old_pack, Package *pack);
|
||||
void move_file(FileReference ref_id, Package *old_pack, Package *pack);
|
||||
|
||||
BlockSeqno max_masterchain_seqno();
|
||||
|
||||
static constexpr td::uint32 default_package_version() {
|
||||
return 1;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace validator
|
||||
|
|
|
@ -44,9 +44,9 @@ void CellDbIn::start_up() {
|
|||
auto empty = get_empty_key_hash();
|
||||
if (get_block(empty).is_error()) {
|
||||
DbEntry e{get_empty_key(), empty, empty, RootHash::zero()};
|
||||
cell_db_->begin_transaction().ensure();
|
||||
cell_db_->begin_write_batch().ensure();
|
||||
set_block(empty, std::move(e));
|
||||
cell_db_->commit_transaction().ensure();
|
||||
cell_db_->commit_write_batch().ensure();
|
||||
}
|
||||
last_gc_ = empty;
|
||||
}
|
||||
|
@ -89,12 +89,12 @@ void CellDbIn::store_cell(BlockIdExt block_id, td::Ref<vm::Cell> cell, td::Promi
|
|||
boc_->inc(cell);
|
||||
boc_->prepare_commit().ensure();
|
||||
vm::CellStorer stor{*cell_db_.get()};
|
||||
cell_db_->begin_transaction().ensure();
|
||||
cell_db_->begin_write_batch().ensure();
|
||||
boc_->commit(stor).ensure();
|
||||
set_block(empty, std::move(E));
|
||||
set_block(D.prev, std::move(P));
|
||||
set_block(key_hash, std::move(D));
|
||||
cell_db_->commit_transaction().ensure();
|
||||
cell_db_->commit_write_batch().ensure();
|
||||
|
||||
boc_->set_loader(std::make_unique<vm::CellLoader>(cell_db_->snapshot())).ensure();
|
||||
td::actor::send_closure(parent_, &CellDb::update_snapshot, cell_db_->snapshot());
|
||||
|
@ -181,12 +181,12 @@ void CellDbIn::gc_cont2(BlockHandle handle) {
|
|||
boc_->dec(cell);
|
||||
boc_->prepare_commit().ensure();
|
||||
vm::CellStorer stor{*cell_db_.get()};
|
||||
cell_db_->begin_transaction().ensure();
|
||||
cell_db_->begin_write_batch().ensure();
|
||||
boc_->commit(stor).ensure();
|
||||
cell_db_->erase(get_key(last_gc_)).ensure();
|
||||
set_block(F.prev, std::move(P));
|
||||
set_block(F.next, std::move(N));
|
||||
cell_db_->commit_transaction().ensure();
|
||||
cell_db_->commit_write_batch().ensure();
|
||||
alarm_timestamp() = td::Timestamp::now();
|
||||
|
||||
boc_->set_loader(std::make_unique<vm::CellLoader>(cell_db_->snapshot())).ensure();
|
||||
|
|
|
@ -409,10 +409,13 @@ void RootDb::prepare_stats(td::Promise<std::vector<std::pair<std::string, std::s
|
|||
auto merger = StatsMerger::create(std::move(promise));
|
||||
}
|
||||
|
||||
void RootDb::truncate(td::Ref<MasterchainState> state, td::Promise<td::Unit> promise) {
|
||||
void RootDb::truncate(BlockSeqno seqno, ConstBlockHandle handle, td::Promise<td::Unit> promise) {
|
||||
td::MultiPromise mp;
|
||||
auto ig = mp.init_guard();
|
||||
ig.add_promise(std::move(promise));
|
||||
|
||||
td::actor::send_closure(archive_db_, &ArchiveManager::truncate, seqno, handle, ig.get_promise());
|
||||
td::actor::send_closure(state_db_, &StateDb::truncate, seqno, handle, ig.get_promise());
|
||||
}
|
||||
|
||||
void RootDb::add_key_block_proof(td::Ref<Proof> proof, td::Promise<td::Unit> promise) {
|
||||
|
@ -483,8 +486,8 @@ void RootDb::set_async_mode(bool mode, td::Promise<td::Unit> promise) {
|
|||
td::actor::send_closure(archive_db_, &ArchiveManager::set_async_mode, mode, std::move(promise));
|
||||
}
|
||||
|
||||
void RootDb::run_gc(UnixTime ts) {
|
||||
td::actor::send_closure(archive_db_, &ArchiveManager::run_gc, ts);
|
||||
void RootDb::run_gc(UnixTime ts, UnixTime archive_ttl) {
|
||||
td::actor::send_closure(archive_db_, &ArchiveManager::run_gc, ts, archive_ttl);
|
||||
}
|
||||
|
||||
} // namespace validator
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue