1
0
Fork 0
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:
ton 2020-04-27 16:01:46 +04:00
parent 16a4566091
commit 9f008b129f
129 changed files with 8438 additions and 879 deletions

1
.gitignore vendored
View file

@ -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

View file

@ -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)

View file

@ -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);

View file

@ -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)

View file

@ -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 {

View file

@ -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;

View file

@ -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};
}
}

View file

@ -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);

View file

@ -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;

View file

@ -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();

View file

@ -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,

View file

@ -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,

View file

@ -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)

View 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;
}

View file

@ -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

View file

@ -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>

View file

@ -0,0 +1,72 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
Copyright 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

View file

@ -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) {

View file

@ -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);

View file

@ -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#

View file

@ -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

View 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

View 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

View file

@ -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);
}

View 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

View file

@ -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

View 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);
}

View file

@ -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());
}

View 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());
}

View file

@ -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

View file

@ -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,

View file

@ -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;

View file

@ -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);

View file

@ -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);
}

View 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

View 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

View file

@ -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

View file

@ -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_;

View file

@ -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 "";

View file

@ -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);

View file

@ -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();

View file

@ -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();

View file

@ -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();

View file

@ -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;
}

View file

@ -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();

View file

@ -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

View file

@ -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++) {

View file

@ -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
}

View file

@ -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;

View file

@ -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());
}

View file

@ -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()) {

View file

@ -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

View file

@ -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_ = {};

View file

@ -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

View file

@ -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);

View file

@ -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

View file

@ -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)...);

View file

@ -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)) {

View file

@ -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);

View file

@ -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();

View file

@ -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

View file

@ -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();
}

View file

@ -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

View file

@ -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();
}

View file

@ -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() {

View file

@ -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();
}

View file

@ -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();

View file

@ -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;

View file

@ -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();

View file

@ -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;

View 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

View file

@ -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>

View file

@ -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
View 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;
}

@ -1 +1 @@
Subproject commit 325fd7b042ff4ec34f7dd32e602cd81ad0e24b22
Subproject commit df3ea785d8c30a9503321a3d35ee7d35808f190d

2
third-party/rocksdb vendored

@ -1 +1 @@
Subproject commit d6f2ecf49c28fee225477d39e2a1535a87919afe
Subproject commit 0915c99f01b46f50af8e02da8b6528156f584b7c

View file

@ -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.

View file

@ -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);

View file

@ -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:

View file

@ -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

View file

@ -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

View file

@ -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);
}
}
};

View file

@ -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(

View file

@ -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 {

View file

@ -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

View file

@ -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();

View file

@ -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);
}

View file

@ -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 {

View file

@ -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)

View file

@ -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{};

View file

@ -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));
}
};

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -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

View file

@ -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();

View file

@ -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