diff --git a/CMakeLists.txt b/CMakeLists.txt
index 8146bef9..4d59081b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -441,7 +441,7 @@ if (USE_LIBRAPTORQ)
endif()
add_executable(test-hello-world test/test-hello-world.cpp )
-target_link_libraries(test-hello-world tl_api crypto ton_crypto)
+target_link_libraries(test-hello-world tl_api ton_crypto)
add_executable(test-adnl test/test-adnl.cpp)
target_link_libraries(test-adnl adnl adnltest dht tl_api)
@@ -510,6 +510,12 @@ add_test(test-tonlib-offline test-tonlib-offline)
#BEGIN internal
if (NOT TON_ONLY_TONLIB)
+add_test(test-adnl test-adnl)
+add_test(test-dht test-dht)
+add_test(test-rldp test-rldp)
+add_test(test-validator-session-state test-validator-session-state)
+add_test(test-catchain test-catchain)
+
add_test(test-fec test-fec)
add_test(test-tddb test-tddb ${TEST_OPTIONS})
add_test(test-db test-db ${TEST_OPTIONS})
diff --git a/blockchain-explorer/blockchain-explorer-http.cpp b/blockchain-explorer/blockchain-explorer-http.cpp
index 43d63d91..d5d41a15 100644
--- a/blockchain-explorer/blockchain-explorer-http.cpp
+++ b/blockchain-explorer/blockchain-explorer-http.cpp
@@ -379,6 +379,23 @@ HttpAnswer& HttpAnswer::operator<<(AccountCell acc_c) {
return *this;
}
+ *this << "
\n";
+
*this << "";
}
+HttpAnswer& HttpAnswer::operator<<(ConfigParam conf) {
+ std::ostringstream os;
+ *this << "param " << conf.idx << "
";
+ if (conf.idx >= 0) {
+ *this << RawData{conf.root, conf.idx};
+ } else {
+ *this << RawData{conf.root};
+ }
+ *this << "\n";
+ return *this;
+}
+
HttpAnswer& HttpAnswer::operator<<(Error error) {
return *this << "" << error.error.to_string() << "
";
}
+HttpAnswer& HttpAnswer::operator<<(Notification n) {
+ return *this << "" << n.text << "
";
+}
+
void HttpAnswer::block_id_link(ton::BlockIdExt block_id) {
*this << "workchain=" << block_id.id.workchain << "&shard=" << ton::shard_to_str(block_id.id.shard)
<< "&seqno=" << block_id.id.seqno << "&roothash=" << block_id.root_hash << "&filehash=" << block_id.file_hash;
diff --git a/blockchain-explorer/blockchain-explorer-http.hpp b/blockchain-explorer/blockchain-explorer-http.hpp
index dadd7e17..4fbb42c9 100644
--- a/blockchain-explorer/blockchain-explorer-http.hpp
+++ b/blockchain-explorer/blockchain-explorer-http.hpp
@@ -84,6 +84,9 @@ class HttpAnswer {
struct BlockViewLink {
ton::BlockIdExt block_id;
};
+ struct ConfigViewLink {
+ ton::BlockIdExt block_id;
+ };
struct BlockDownloadLink {
ton::BlockIdExt block_id;
};
@@ -116,9 +119,16 @@ class HttpAnswer {
struct CodeBlock {
std::string data;
};
+ struct ConfigParam {
+ td::int32 idx;
+ td::Ref root;
+ };
struct Error {
td::Status error;
};
+ struct Notification {
+ std::string text;
+ };
template
struct RawData {
td::Ref root;
@@ -189,14 +199,17 @@ class HttpAnswer {
HttpAnswer &operator<<(TransactionLinkShort trans);
HttpAnswer &operator<<(BlockLink block);
HttpAnswer &operator<<(BlockViewLink block);
+ HttpAnswer &operator<<(ConfigViewLink block);
HttpAnswer &operator<<(BlockDownloadLink block);
HttpAnswer &operator<<(Error error);
+ HttpAnswer &operator<<(Notification notification);
HttpAnswer &operator<<(TransactionList trans);
HttpAnswer &operator<<(CodeBlock block) {
return *this << "" << block.data << "
";
}
+ HttpAnswer &operator<<(ConfigParam conf);
template
HttpAnswer &operator<<(RawData data) {
@@ -220,3 +233,16 @@ class HttpAnswer {
std::unique_ptr sb_;
td::BufferSlice buf_;
};
+
+template <>
+struct HttpAnswer::RawData {
+ td::Ref root;
+ RawData(td::Ref root) : root(std::move(root)) {
+ }
+};
+template <>
+inline HttpAnswer &HttpAnswer::operator<<(RawData data) {
+ std::ostringstream outp;
+ vm::load_cell_slice(data.root).print_rec(outp);
+ return *this << CodeBlock{outp.str()};
+}
diff --git a/blockchain-explorer/blockchain-explorer-query.cpp b/blockchain-explorer/blockchain-explorer-query.cpp
index 061aace6..db731f14 100644
--- a/blockchain-explorer/blockchain-explorer-query.cpp
+++ b/blockchain-explorer/blockchain-explorer-query.cpp
@@ -28,6 +28,8 @@
*/
#include "blockchain-explorer-query.hpp"
#include "blockchain-explorer-http.hpp"
+#include "block/mc-config.h"
+#include "crypto/block/check-proof.h"
#include "auto/tl/lite_api.h"
@@ -39,6 +41,40 @@
#include "common/errorcode.h"
#include "block/block-auto.h"
+#include "crypto/vm/utils.h"
+#include "td/utils/crypto.h"
+
+#include "vm/boc.h"
+#include "vm/cellops.h"
+#include "vm/cells/MerkleProof.h"
+#include "vm/continuation.h"
+#include "vm/cp0.h"
+
+namespace {
+
+td::Ref prepare_vm_c7(ton::UnixTime now, ton::LogicalTime lt, td::Ref my_addr,
+ const block::CurrencyCollection &balance) {
+ td::BitArray<256> rand_seed;
+ td::RefInt256 rand_seed_int{true};
+ td::Random::secure_bytes(rand_seed.as_slice());
+ if (!rand_seed_int.unique_write().import_bits(rand_seed.cbits(), 256, false)) {
+ return {};
+ }
+ auto tuple = vm::make_tuple_ref(td::make_refint(0x076ef1ea), // [ magic:0x076ef1ea
+ td::make_refint(0), // actions:Integer
+ td::make_refint(0), // msgs_sent:Integer
+ td::make_refint(now), // unixtime:Integer
+ td::make_refint(lt), // block_lt:Integer
+ td::make_refint(lt), // trans_lt:Integer
+ std::move(rand_seed_int), // rand_seed:Integer
+ balance.as_vm_tuple(), // balance_remaining:[Integer (Maybe Cell)]
+ my_addr, // myself:MsgAddressInt
+ vm::StackEntry()); // global_config:(Maybe Cell) ] = SmartContractInfo;
+ LOG(DEBUG) << "SmartContractInfo initialized with " << vm::StackEntry(tuple).to_string();
+ return vm::make_tuple_ref(std::move(tuple));
+}
+
+} // namespace
td::Result parse_block_id(std::map &opts, bool allow_empty) {
if (allow_empty) {
@@ -990,6 +1026,419 @@ void HttpQueryViewLastBlock::finish_query() {
stop();
}
+HttpQueryConfig::HttpQueryConfig(std::string prefix, ton::BlockIdExt block_id, std::vector params,
+ td::Promise promise)
+ : HttpQueryCommon(prefix, std::move(promise)), block_id_(block_id), params_(std::move(params)) {
+}
+
+HttpQueryConfig::HttpQueryConfig(std::map opts, std::string prefix,
+ td::Promise promise)
+ : HttpQueryCommon(prefix, std::move(promise)) {
+ auto R = parse_block_id(opts, true);
+ if (R.is_error()) {
+ error_ = R.move_as_error();
+ return;
+ }
+ block_id_ = R.move_as_ok();
+
+ auto it = opts.find("param");
+ if (it != opts.end()) {
+ auto R2 = td::to_integer_safe(it->second);
+ if (R2.is_error()) {
+ error_ = R2.move_as_error();
+ return;
+ }
+ params_.push_back(R2.move_as_ok());
+ }
+}
+
+void HttpQueryConfig::start_up() {
+ if (error_.is_error()) {
+ abort_query(std::move(error_));
+ return;
+ }
+ if (block_id_.is_valid()) {
+ send_main_query();
+ } else {
+ auto P = td::PromiseCreator::lambda([SelfId = actor_id(this)](td::Result R) {
+ if (R.is_error()) {
+ td::actor::send_closure(SelfId, &HttpQueryConfig::abort_query, R.move_as_error());
+ } else {
+ td::actor::send_closure(SelfId, &HttpQueryConfig::got_block, R.move_as_ok());
+ }
+ });
+
+ auto query = ton::serialize_tl_object(ton::create_tl_object(), true);
+ td::actor::send_closure(CoreActorInterface::instance_actor_id(), &CoreActorInterface::send_lite_query,
+ std::move(query), std::move(P));
+ }
+}
+
+void HttpQueryConfig::got_block(td::BufferSlice data) {
+ auto F = ton::fetch_tl_object(std::move(data), true);
+ if (F.is_error()) {
+ abort_query(F.move_as_error());
+ return;
+ }
+ auto f = F.move_as_ok();
+ block_id_ = ton::create_block_id(f->last_);
+
+ send_main_query();
+}
+
+void HttpQueryConfig::send_main_query() {
+ auto P = td::PromiseCreator::lambda([SelfId = actor_id(this)](td::Result R) {
+ if (R.is_error()) {
+ td::actor::send_closure(SelfId, &HttpQueryConfig::abort_query, R.move_as_error());
+ } else {
+ td::actor::send_closure(SelfId, &HttpQueryConfig::got_result, R.move_as_ok());
+ }
+ });
+ auto query =
+ params_.size() > 0
+ ? ton::serialize_tl_object(ton::create_tl_object(
+ 0, ton::create_tl_lite_block_id(block_id_), std::vector(params_)),
+ true)
+ : ton::serialize_tl_object(ton::create_tl_object(
+ 0, ton::create_tl_lite_block_id(block_id_)),
+ true);
+ td::actor::send_closure(CoreActorInterface::instance_actor_id(), &CoreActorInterface::send_lite_query,
+ std::move(query), std::move(P));
+}
+
+void HttpQueryConfig::got_result(td::BufferSlice data) {
+ auto F = ton::fetch_tl_object(std::move(data), true);
+ if (F.is_error()) {
+ abort_query(F.move_as_error());
+ return;
+ }
+ auto f = F.move_as_ok();
+
+ state_proof_ = std::move(f->state_proof_);
+ config_proof_ = std::move(f->config_proof_);
+
+ finish_query();
+}
+
+void HttpQueryConfig::finish_query() {
+ if (promise_) {
+ auto page = [&]() -> std::string {
+ HttpAnswer A{"config", prefix_};
+ A.set_block_id(block_id_);
+ auto R = block::check_extract_state_proof(block_id_, state_proof_.as_slice(), config_proof_.as_slice());
+ if (R.is_error()) {
+ A.abort(PSTRING() << "masterchain state proof for " << block_id_.to_str()
+ << " is invalid : " << R.move_as_error());
+ return A.finish();
+ }
+ try {
+ auto res = block::Config::extract_from_state(R.move_as_ok(), 0);
+ if (res.is_error()) {
+ A.abort(PSTRING() << "cannot unpack configuration: " << res.move_as_error());
+ return A.finish();
+ }
+ auto config = res.move_as_ok();
+ if (params_.size() > 0) {
+ A << "params: ";
+ for (int i : params_) {
+ auto value = config->get_config_param(i);
+ if (value.not_null()) {
+ A << "" << i << " ";
+ }
+ }
+ A << "
";
+ for (int i : params_) {
+ auto value = config->get_config_param(i);
+ if (value.not_null()) {
+ A << HttpAnswer::ConfigParam{i, value};
+ } else {
+ A << HttpAnswer::Error{td::Status::Error(404, PSTRING() << "empty param " << i)};
+ }
+ }
+ } else {
+ A << "params: ";
+ config->foreach_config_param([&](int i, td::Ref value) {
+ if (value.not_null()) {
+ A << "" << i << " ";
+ }
+ return true;
+ });
+ A << "
";
+ config->foreach_config_param([&](int i, td::Ref value) {
+ if (value.not_null()) {
+ A << HttpAnswer::ConfigParam{i, value};
+ }
+ return true;
+ });
+ }
+ } catch (vm::VmError &err) {
+ A.abort(PSTRING() << "error while traversing configuration: " << err.get_msg());
+ } catch (vm::VmVirtError &err) {
+ A.abort(PSTRING() << "virtualization error while traversing configuration: " << err.get_msg());
+ }
+ return A.finish();
+ }();
+ promise_.set_value(
+ MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY));
+ }
+ stop();
+}
+
+HttpQuerySendForm::HttpQuerySendForm(std::string prefix, td::Promise promise)
+ : HttpQueryCommon(prefix, std::move(promise)) {
+}
+
+HttpQuerySendForm::HttpQuerySendForm(std::map opts, std::string prefix,
+ td::Promise promise)
+ : HttpQueryCommon(prefix, std::move(promise)) {
+}
+
+void HttpQuerySendForm::start_up() {
+ finish_query();
+}
+
+void HttpQuerySendForm::finish_query() {
+ if (promise_) {
+ auto page = [&]() -> std::string {
+ HttpAnswer A{"send", prefix_};
+ A << "";
+ return A.finish();
+ }();
+ promise_.set_value(
+ MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY));
+ }
+ stop();
+}
+
+HttpQuerySend::HttpQuerySend(std::string prefix, td::BufferSlice data, td::Promise promise)
+ : HttpQueryCommon(prefix, std::move(promise)), data_(std::move(data)) {
+}
+
+HttpQuerySend::HttpQuerySend(std::map opts, std::string prefix,
+ td::Promise promise)
+ : HttpQueryCommon(prefix, std::move(promise)) {
+ auto it = opts.find("filedata");
+ if (it != opts.end()) {
+ data_ = td::BufferSlice{it->second};
+ } else {
+ error_ = td::Status::Error("no file data");
+ return;
+ }
+}
+
+void HttpQuerySend::start_up() {
+ if (error_.is_error()) {
+ abort_query(std::move(error_));
+ return;
+ }
+ auto P = td::PromiseCreator::lambda([SelfId = actor_id(this)](td::Result R) {
+ if (R.is_error()) {
+ td::actor::send_closure(SelfId, &HttpQuerySend::abort_query, R.move_as_error());
+ } else {
+ td::actor::send_closure(SelfId, &HttpQuerySend::got_result, R.move_as_ok());
+ }
+ });
+ auto query =
+ ton::serialize_tl_object(ton::create_tl_object(std::move(data_)), true);
+ td::actor::send_closure(CoreActorInterface::instance_actor_id(), &CoreActorInterface::send_lite_query,
+ std::move(query), std::move(P));
+}
+
+void HttpQuerySend::got_result(td::BufferSlice data) {
+ auto F = ton::fetch_tl_object(std::move(data), true);
+ if (F.is_error()) {
+ abort_query(F.move_as_error());
+ } else {
+ status_ = F.move_as_ok()->status_;
+ }
+ finish_query();
+}
+
+void HttpQuerySend::finish_query() {
+ if (promise_) {
+ auto page = [&]() -> std::string {
+ HttpAnswer A{"send", prefix_};
+ if (status_ >= 0) {
+ A << HttpAnswer::Notification{"success"};
+ } else {
+ A << HttpAnswer::Error{td::Status::Error(status_, "failed")};
+ }
+ return A.finish();
+ }();
+ promise_.set_value(
+ MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY));
+ }
+ stop();
+}
+
+HttpQueryRunMethod::HttpQueryRunMethod(ton::BlockIdExt block_id, block::StdAddress addr, std::string method_name,
+ std::vector params, std::string prefix,
+ td::Promise promise)
+ : HttpQueryCommon(std::move(prefix), std::move(promise))
+ , block_id_(block_id)
+ , addr_(addr)
+ , method_name_(std::move(method_name))
+ , params_(std::move(params)) {
+}
+
+HttpQueryRunMethod::HttpQueryRunMethod(std::map opts, std::string prefix,
+ td::Promise promise)
+ : HttpQueryCommon(std::move(prefix), std::move(promise)) {
+ auto R = parse_block_id(opts, true);
+ if (R.is_ok()) {
+ block_id_ = R.move_as_ok();
+ if (!block_id_.is_valid()) {
+ block_id_.id.workchain = ton::masterchainId;
+ block_id_.id.shard = ton::shardIdAll;
+ block_id_.id.seqno = static_cast(0xffffffff);
+ block_id_.root_hash.set_zero();
+ block_id_.file_hash.set_zero();
+ }
+ } else {
+ error_ = R.move_as_error();
+ return;
+ }
+ auto R2 = parse_account_addr(opts);
+ if (R2.is_ok()) {
+ addr_ = R2.move_as_ok();
+ } else {
+ error_ = R2.move_as_error();
+ return;
+ }
+ auto it = opts.find("method");
+ if (it == opts.end()) {
+ error_ = td::Status::Error("no method");
+ return;
+ } else {
+ method_name_ = it->second;
+ }
+ it = opts.find("params");
+ if (it != opts.end()) {
+ auto R3 = vm::parse_stack_entries(it->second);
+ if (R3.is_error()) {
+ error_ = R3.move_as_error();
+ return;
+ }
+ params_ = R3.move_as_ok();
+ }
+}
+
+void HttpQueryRunMethod::start_up_query() {
+ auto P = td::PromiseCreator::lambda([SelfId = actor_id(this)](td::Result R) {
+ if (R.is_error()) {
+ td::actor::send_closure(SelfId, &HttpQueryRunMethod::abort_query, R.move_as_error_prefix("litequery failed: "));
+ } else {
+ td::actor::send_closure(SelfId, &HttpQueryRunMethod::got_account, R.move_as_ok());
+ }
+ });
+ auto a = ton::create_tl_object(addr_.workchain, addr_.addr);
+ auto query = ton::serialize_tl_object(ton::create_tl_object(
+ ton::create_tl_lite_block_id(block_id_), std::move(a)),
+ true);
+ td::actor::send_closure(CoreActorInterface::instance_actor_id(), &CoreActorInterface::send_lite_query,
+ std::move(query), std::move(P));
+}
+
+void HttpQueryRunMethod::got_account(td::BufferSlice data) {
+ auto F = ton::fetch_tl_object(std::move(data), true);
+ if (F.is_error()) {
+ abort_query(F.move_as_error());
+ return;
+ }
+
+ auto f = F.move_as_ok();
+ data_ = std::move(f->state_);
+ proof_ = std::move(f->proof_);
+ shard_proof_ = std::move(f->shard_proof_);
+ block_id_ = ton::create_block_id(f->id_);
+ res_block_id_ = ton::create_block_id(f->shardblk_);
+
+ finish_query();
+}
+
+void HttpQueryRunMethod::finish_query() {
+ if (promise_) {
+ auto page = [&]() -> std::string {
+ HttpAnswer A{"account", prefix_};
+ A.set_account_id(addr_);
+ A.set_block_id(res_block_id_);
+
+ block::AccountState account_state;
+ account_state.blk = block_id_;
+ account_state.shard_blk = res_block_id_;
+ account_state.shard_proof = std::move(shard_proof_);
+ account_state.proof = std::move(proof_);
+ account_state.state = std::move(data_);
+ auto r_info = account_state.validate(block_id_, addr_);
+ if (r_info.is_error()) {
+ A.abort(r_info.move_as_error());
+ return A.finish();
+ }
+ auto info = r_info.move_as_ok();
+ if (info.root.is_null()) {
+ A.abort(PSTRING() << "account state of " << addr_ << " is empty (cannot run method `" << method_name_ << "`)");
+ return A.finish();
+ }
+ block::gen::Account::Record_account acc;
+ block::gen::AccountStorage::Record store;
+ block::CurrencyCollection balance;
+ if (!(tlb::unpack_cell(info.root, acc) && tlb::csr_unpack(acc.storage, store) &&
+ balance.validate_unpack(store.balance))) {
+ A.abort("error unpacking account state");
+ return A.finish();
+ }
+ int tag = block::gen::t_AccountState.get_tag(*store.state);
+ switch (tag) {
+ case block::gen::AccountState::account_uninit:
+ A.abort(PSTRING() << "account " << addr_ << " not initialized yet (cannot run any methods)");
+ return A.finish();
+ case block::gen::AccountState::account_frozen:
+ A.abort(PSTRING() << "account " << addr_ << " frozen (cannot run any methods)");
+ return A.finish();
+ }
+
+ CHECK(store.state.write().fetch_ulong(1) == 1); // account_init$1 _:StateInit = AccountState;
+ block::gen::StateInit::Record state_init;
+ CHECK(tlb::csr_unpack(store.state, state_init));
+ auto code = state_init.code->prefetch_ref();
+ auto data = state_init.data->prefetch_ref();
+ auto stack = td::make_ref(std::move(params_));
+ td::int64 method_id = (td::crc16(td::Slice{method_name_}) & 0xffff) | 0x10000;
+ stack.write().push_smallint(method_id);
+ long long gas_limit = vm::GasLimits::infty;
+ // OstreamLogger ostream_logger(ctx.error_stream);
+ // auto log = create_vm_log(ctx.error_stream ? &ostream_logger : nullptr);
+ vm::GasLimits gas{gas_limit};
+ LOG(DEBUG) << "creating VM";
+ vm::VmState vm{code, std::move(stack), gas, 1, data, vm::VmLog()};
+ vm.set_c7(prepare_vm_c7(info.gen_utime, info.gen_lt, acc.addr, balance)); // tuple with SmartContractInfo
+ // vm.incr_stack_trace(1); // enable stack dump after each step
+ int exit_code = ~vm.run();
+ if (exit_code != 0) {
+ A.abort(PSTRING() << "VM terminated with error code " << exit_code);
+ return A.finish();
+ }
+ stack = vm.get_stack_ref();
+ {
+ std::ostringstream os;
+ os << "result: ";
+ stack->dump(os, 3);
+
+ A << HttpAnswer::CodeBlock{os.str()};
+ }
+
+ return A.finish();
+ }();
+ promise_.set_value(
+ MHD_create_response_from_buffer(page.length(), const_cast(page.c_str()), MHD_RESPMEM_MUST_COPY));
+ }
+ stop();
+}
HttpQueryStatus::HttpQueryStatus(std::string prefix, td::Promise promise)
: HttpQueryCommon(std::move(prefix), std::move(promise)) {
}
diff --git a/blockchain-explorer/blockchain-explorer-query.hpp b/blockchain-explorer/blockchain-explorer-query.hpp
index 2acf9efb..ce7a713f 100644
--- a/blockchain-explorer/blockchain-explorer-query.hpp
+++ b/blockchain-explorer/blockchain-explorer-query.hpp
@@ -258,6 +258,77 @@ class HttpQueryViewLastBlock : public HttpQueryCommon {
ton::BlockIdExt res_block_id_;
};
+class HttpQueryConfig : public HttpQueryCommon {
+ public:
+ HttpQueryConfig(std::string prefix, ton::BlockIdExt block_id, std::vector params,
+ td::Promise promise);
+ HttpQueryConfig(std::map opts, std::string prefix, td::Promise promise);
+
+ void finish_query();
+
+ void start_up() override;
+ void got_block(td::BufferSlice result);
+ void send_main_query();
+ void got_result(td::BufferSlice result);
+
+ private:
+ ton::BlockIdExt block_id_;
+ std::vector params_;
+
+ td::BufferSlice state_proof_;
+ td::BufferSlice config_proof_;
+};
+
+class HttpQuerySendForm : public HttpQueryCommon {
+ public:
+ HttpQuerySendForm(std::string prefix, td::Promise promise);
+ HttpQuerySendForm(std::map opts, std::string prefix, td::Promise promise);
+
+ void start_up() override;
+ void finish_query();
+
+ private:
+};
+
+class HttpQuerySend : public HttpQueryCommon {
+ public:
+ HttpQuerySend(std::string prefix, td::BufferSlice data, td::Promise promise);
+ HttpQuerySend(std::map opts, std::string prefix, td::Promise promise);
+
+ void finish_query();
+
+ void start_up() override;
+ void got_result(td::BufferSlice result);
+
+ private:
+ td::BufferSlice data_;
+ td::int32 status_;
+};
+
+class HttpQueryRunMethod : public HttpQueryCommon {
+ public:
+ HttpQueryRunMethod(ton::BlockIdExt block_id, block::StdAddress addr, std::string method_name,
+ std::vector params, std::string prefix, td::Promise promise);
+ HttpQueryRunMethod(std::map opts, std::string prefix, td::Promise promise);
+
+ void finish_query();
+
+ void start_up_query() override;
+ void got_account(td::BufferSlice result);
+
+ private:
+ ton::BlockIdExt block_id_;
+ block::StdAddress addr_;
+
+ std::string method_name_;
+ std::vector params_;
+
+ td::BufferSlice data_;
+ td::BufferSlice proof_;
+ td::BufferSlice shard_proof_;
+ ton::BlockIdExt res_block_id_;
+};
+
class HttpQueryStatus : public HttpQueryCommon {
public:
HttpQueryStatus(std::string prefix, td::Promise promise);
diff --git a/blockchain-explorer/blockchain-explorer.cpp b/blockchain-explorer/blockchain-explorer.cpp
index 2fa1bf75..b3776e3e 100644
--- a/blockchain-explorer/blockchain-explorer.cpp
+++ b/blockchain-explorer/blockchain-explorer.cpp
@@ -49,6 +49,12 @@
#include "blockchain-explorer-http.hpp"
#include "blockchain-explorer-query.hpp"
+#include "vm/boc.h"
+#include "vm/cellops.h"
+#include "vm/cells/MerkleProof.h"
+#include "vm/continuation.h"
+#include "vm/cp0.h"
+
#include "auto/tl/lite_api.h"
#include "ton/lite-tl.hpp"
#include "tl-utils/lite-utils.hpp"
@@ -263,22 +269,80 @@ class CoreActor : public CoreActorInterface {
return MHD_YES;
}
+ struct HttpRequestExtra {
+ HttpRequestExtra(MHD_Connection* connection, bool is_post) {
+ if (is_post) {
+ postprocessor = MHD_create_post_processor(connection, 1 << 14, iterate_post, static_cast(this));
+ }
+ }
+ ~HttpRequestExtra() {
+ MHD_destroy_post_processor(postprocessor);
+ }
+ static int iterate_post(void* coninfo_cls, enum MHD_ValueKind kind, const char* key, const char* filename,
+ const char* content_type, const char* transfer_encoding, const char* data, uint64_t off,
+ size_t size) {
+ auto ptr = static_cast(coninfo_cls);
+ ptr->total_size += strlen(key) + size;
+ if (ptr->total_size > MAX_POST_SIZE) {
+ return MHD_NO;
+ }
+ std::string k = key;
+ if (ptr->opts[k].size() < off + size) {
+ ptr->opts[k].resize(off + size);
+ }
+ td::MutableSlice(ptr->opts[k]).remove_prefix(off).copy_from(td::Slice(data, size));
+ return MHD_YES;
+ }
+ MHD_PostProcessor* postprocessor;
+ std::map opts;
+ td::uint64 total_size = 0;
+ };
+
+ static void request_completed(void* cls, struct MHD_Connection* connection, void** ptr,
+ enum MHD_RequestTerminationCode toe) {
+ auto e = static_cast(*ptr);
+ if (e) {
+ delete e;
+ }
+ }
+
static int process_http_request(void* cls, struct MHD_Connection* connection, const char* url, const char* method,
const char* version, const char* upload_data, size_t* upload_data_size, void** ptr) {
- static int dummy;
struct MHD_Response* response = nullptr;
int ret;
- if (0 != std::strcmp(method, "GET"))
+ bool is_post = false;
+ if (std::strcmp(method, "GET") == 0) {
+ is_post = false;
+ } else if (std::strcmp(method, "POST") == 0) {
+ is_post = true;
+ } else {
return MHD_NO; /* unexpected method */
- if (&dummy != *ptr) {
- /* The first time only the headers are valid,
- do not respond in the first round... */
- *ptr = &dummy;
- return MHD_YES;
}
- if (0 != *upload_data_size)
- return MHD_NO; /* upload data in a GET!? */
+ std::map opts;
+ if (!is_post) {
+ if (!*ptr) {
+ *ptr = static_cast(new HttpRequestExtra{connection, false});
+ return MHD_YES;
+ }
+ if (0 != *upload_data_size)
+ return MHD_NO; /* upload data in a GET!? */
+ } else {
+ if (!*ptr) {
+ *ptr = static_cast(new HttpRequestExtra{connection, true});
+ return MHD_YES;
+ }
+ auto e = static_cast(*ptr);
+ if (0 != *upload_data_size) {
+ CHECK(e->postprocessor);
+ MHD_post_process(e->postprocessor, upload_data, *upload_data_size);
+ *upload_data_size = 0;
+ return MHD_YES;
+ }
+ for (auto& o : e->opts) {
+ opts[o.first] = std::move(o.second);
+ }
+ }
std::string url_s = url;
@@ -295,7 +359,6 @@ class CoreActor : public CoreActorInterface {
command = url_s.substr(pos + 1);
}
- std::map opts;
MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, get_arg_iterate, static_cast(&opts));
if (command == "status") {
@@ -352,6 +415,26 @@ class CoreActor : public CoreActorInterface {
.release();
}};
response = g.wait();
+ } else if (command == "config") {
+ HttpQueryRunner g{[&](td::Promise promise) {
+ td::actor::create_actor("getconfig", opts, prefix, std::move(promise)).release();
+ }};
+ response = g.wait();
+ } else if (command == "send") {
+ HttpQueryRunner g{[&](td::Promise promise) {
+ td::actor::create_actor("send", opts, prefix, std::move(promise)).release();
+ }};
+ response = g.wait();
+ } else if (command == "sendform") {
+ HttpQueryRunner g{[&](td::Promise promise) {
+ td::actor::create_actor("sendform", opts, prefix, std::move(promise)).release();
+ }};
+ response = g.wait();
+ } else if (command == "runmethod") {
+ HttpQueryRunner g{[&](td::Promise promise) {
+ td::actor::create_actor("runmethod", opts, prefix, std::move(promise)).release();
+ }};
+ response = g.wait();
} else {
ret = MHD_NO;
}
@@ -394,7 +477,8 @@ class CoreActor : public CoreActorInterface {
remote_addr_, make_callback(0)));
}
daemon_ = MHD_start_daemon(MHD_USE_SELECT_INTERNALLY, static_cast(http_port_), nullptr, nullptr,
- &process_http_request, nullptr, MHD_OPTION_THREAD_POOL_SIZE, 16, MHD_OPTION_END);
+ &process_http_request, nullptr, MHD_OPTION_NOTIFY_COMPLETED, request_completed, nullptr,
+ MHD_OPTION_THREAD_POOL_SIZE, 16, MHD_OPTION_END);
CHECK(daemon_ != nullptr);
}
};
@@ -567,6 +651,8 @@ int main(int argc, char* argv[]) {
});
#endif
+ vm::init_op_cp0();
+
td::actor::Scheduler scheduler({2});
scheduler_ptr = &scheduler;
scheduler.run_in_context([&] { x = td::actor::create_actor("testnode"); });
diff --git a/blockchain-explorer/blockchain-explorer.hpp b/blockchain-explorer/blockchain-explorer.hpp
index 84b86658..59e42cce 100644
--- a/blockchain-explorer/blockchain-explorer.hpp
+++ b/blockchain-explorer/blockchain-explorer.hpp
@@ -32,6 +32,8 @@
#include "ton/ton-types.h"
#include "td/utils/port/IPAddress.h"
+#define MAX_POST_SIZE (64 << 10)
+
class CoreActorInterface : public td::actor::Actor {
public:
struct RemoteNodeStatus {
diff --git a/crypto/CMakeLists.txt b/crypto/CMakeLists.txt
index 38f65500..ff7d9227 100644
--- a/crypto/CMakeLists.txt
+++ b/crypto/CMakeLists.txt
@@ -35,6 +35,7 @@ set(TON_CRYPTO_SOURCE
vm/debugops.cpp
vm/tonops.cpp
vm/boc.cpp
+ vm/utils.cpp
tl/tlblib.cpp
Ed25519.h
@@ -80,6 +81,7 @@ set(TON_CRYPTO_SOURCE
vm/tupleops.h
vm/tonops.h
vm/vmstate.h
+ vm/utils.h
vm/cells.h
vm/cellslice.h
diff --git a/crypto/block/block.cpp b/crypto/block/block.cpp
index bdbf08a6..b2cad142 100644
--- a/crypto/block/block.cpp
+++ b/crypto/block/block.cpp
@@ -789,10 +789,11 @@ td::Status ShardState::unpack_state(ton::BlockIdExt blkid, Ref prev_st
return td::Status::Error(-666, "ShardState of "s + id_.to_str() + " does not contain a valid global_balance");
}
if (extra.r1.flags & 1) {
- if (extra.r1.block_create_stats->prefetch_ulong(8) != 0x17) {
+ if (extra.r1.block_create_stats->prefetch_ulong(8) == 0x17) {
+ block_create_stats_ = std::make_unique(extra.r1.block_create_stats->prefetch_ref(), 256);
+ } else {
return td::Status::Error(-666, "ShardState of "s + id_.to_str() + " does not contain a valid BlockCreateStats");
}
- block_create_stats_ = std::make_unique(extra.r1.block_create_stats->prefetch_ref(), 256);
} else {
block_create_stats_ = std::make_unique(256);
}
@@ -1846,6 +1847,18 @@ td::Status check_block_header(Ref block_root, const ton::BlockIdExt& i
return td::Status::OK();
}
+std::unique_ptr get_block_create_stats_dict(Ref state_root) {
+ block::gen::ShardStateUnsplit::Record info;
+ block::gen::McStateExtra::Record extra;
+ block::gen::BlockCreateStats::Record_block_create_stats cstats;
+ if (!(::tlb::unpack_cell(std::move(state_root), info) && info.custom->size_refs() &&
+ ::tlb::unpack_cell(info.custom->prefetch_ref(), extra) && (extra.r1.flags & 1) &&
+ ::tlb::csr_unpack(std::move(extra.r1.block_create_stats), cstats))) {
+ return {};
+ }
+ return std::make_unique(std::move(cstats.counters), 256);
+}
+
std::unique_ptr get_prev_blocks_dict(Ref state_root) {
block::gen::ShardStateUnsplit::Record info;
block::gen::McStateExtra::Record extra_info;
diff --git a/crypto/block/block.h b/crypto/block/block.h
index fd9a724f..0cfbe24a 100644
--- a/crypto/block/block.h
+++ b/crypto/block/block.h
@@ -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;
};
@@ -514,6 +514,9 @@ struct DiscountedCounter {
return last_updated == other.last_updated && total == other.total && cnt2048 <= other.cnt2048 + 1 &&
other.cnt2048 <= cnt2048 + 1 && cnt65536 <= other.cnt65536 + 1 && other.cnt65536 <= cnt65536 + 1;
}
+ bool modified_since(ton::UnixTime utime) const {
+ return last_updated >= utime;
+ }
bool validate();
bool increase_by(unsigned count, ton::UnixTime now);
bool fetch(vm::CellSlice& cs);
@@ -629,6 +632,8 @@ td::Status unpack_block_prev_blk_try(Ref block_root, const ton::BlockI
td::Status check_block_header(Ref block_root, const ton::BlockIdExt& id,
ton::Bits256* store_shard_hash_to = nullptr);
+std::unique_ptr get_block_create_stats_dict(Ref state_root);
+
std::unique_ptr get_prev_blocks_dict(Ref state_root);
bool get_old_mc_block_id(vm::AugmentedDictionary* prev_blocks_dict, ton::BlockSeqno seqno, ton::BlockIdExt& blkid,
ton::LogicalTime* end_lt = nullptr);
diff --git a/crypto/block/block.tlb b/crypto/block/block.tlb
index ec91ef57..074a5d4b 100644
--- a/crypto/block/block.tlb
+++ b/crypto/block/block.tlb
@@ -366,6 +366,10 @@ action_send_msg#0ec3c86d mode:(## 8)
action_set_code#ad4de08e new_code:^Cell = OutAction;
action_reserve_currency#36e6b809 mode:(## 8)
currency:CurrencyCollection = OutAction;
+libref_hash$0 lib_hash:bits256 = LibRef;
+libref_ref$1 library:^Cell = LibRef;
+action_change_library#26fa1dd4 mode:(## 7) { mode <= 2 }
+ libref:LibRef = OutAction;
out_list_node$_ prev:^Cell action:OutAction = OutListNode;
//
@@ -505,6 +509,7 @@ _ (HashmapAugE 32 KeyExtBlkRef KeyMaxLt) = OldMcBlocksInfo;
counters#_ last_updated:uint32 total:uint64 cnt2048:uint64 cnt65536:uint64 = Counters;
creator_info#4 mc_blocks:Counters shard_blocks:Counters = CreatorStats;
block_create_stats#17 counters:(HashmapE 256 CreatorStats) = BlockCreateStats;
+block_create_stats_ext#34 counters:(HashmapAugE 256 CreatorStats uint32) = BlockCreateStats;
masterchain_state_extra#cc26
shard_hashes:ShardHashes
diff --git a/crypto/block/mc-config.cpp b/crypto/block/mc-config.cpp
index b5959f75..c686e200 100644
--- a/crypto/block/mc-config.cpp
+++ b/crypto/block/mc-config.cpp
@@ -1710,6 +1710,30 @@ std::vector Config::compute_total_validator_set(int next) c
return res.move_as_ok()->export_validator_set();
}
+td::Result> Config::unpack_validator_set_start_stop(Ref vset_root) {
+ if (vset_root.is_null()) {
+ return td::Status::Error("validator set absent");
+ }
+ gen::ValidatorSet::Record_validators_ext rec;
+ if (tlb::unpack_cell(vset_root, rec)) {
+ return std::pair(rec.utime_since, rec.utime_until);
+ }
+ gen::ValidatorSet::Record_validators rec0;
+ if (tlb::unpack_cell(std::move(vset_root), rec0)) {
+ return std::pair(rec0.utime_since, rec0.utime_until);
+ }
+ return td::Status::Error("validator set is invalid");
+}
+
+std::pair Config::get_validator_set_start_stop(int next) const {
+ auto res = unpack_validator_set_start_stop(get_config_param(next < 0 ? 32 : (next ? 36 : 34)));
+ if (res.is_error()) {
+ return {0, 0};
+ } else {
+ return res.move_as_ok();
+ }
+}
+
bool WorkchainInfo::unpack(ton::WorkchainId wc, vm::CellSlice& cs) {
workchain = ton::workchainInvalid;
if (wc == ton::workchainInvalid) {
diff --git a/crypto/block/mc-config.h b/crypto/block/mc-config.h
index 241ae88c..55bf1122 100644
--- a/crypto/block/mc-config.h
+++ b/crypto/block/mc-config.h
@@ -50,7 +50,7 @@ struct ValidatorDescr {
: pubkey(_pubkey), weight(_weight), cum_weight(_cum_weight) {
adnl_addr.set_zero();
}
- bool operator<(td::uint64 wt_pos) const& {
+ bool operator<(td::uint64 wt_pos) const & {
return cum_weight < wt_pos;
}
};
@@ -558,6 +558,7 @@ class Config {
const ValidatorSet* get_cur_validator_set() const {
return cur_validators_.get();
}
+ std::pair get_validator_set_start_stop(int next = 0) const;
ton::ValidatorSessionConfig get_consensus_config() const;
bool foreach_config_param(std::function)> scan_func) const;
Ref get_workchain_info(ton::WorkchainId workchain_id) const;
@@ -577,6 +578,7 @@ class Config {
static td::Result> unpack_config(Ref config_csr, int mode = 0);
static td::Result> extract_from_state(Ref mc_state_root, int mode = 0);
static td::Result> extract_from_key_block(Ref key_block_root, int mode = 0);
+ static td::Result> unpack_validator_set_start_stop(Ref root);
protected:
Config(int _mode) : mode(_mode) {
diff --git a/crypto/block/transaction.cpp b/crypto/block/transaction.cpp
index 6667cf87..df4cf883 100644
--- a/crypto/block/transaction.cpp
+++ b/crypto/block/transaction.cpp
@@ -1098,6 +1098,9 @@ bool Transaction::prepare_action_phase(const ActionPhaseConfig& cfg) {
case block::gen::OutAction::action_reserve_currency:
err_code = try_action_reserve_currency(cs, ap, cfg);
break;
+ case block::gen::OutAction::action_change_library:
+ err_code = try_action_change_library(cs, ap, cfg);
+ break;
}
if (err_code) {
ap.result_code = (err_code == -1 ? 34 : err_code);
@@ -1148,6 +1151,56 @@ int Transaction::try_action_set_code(vm::CellSlice& cs, ActionPhase& ap, const A
return 0;
}
+int Transaction::try_action_change_library(vm::CellSlice& cs, ActionPhase& ap, const ActionPhaseConfig& cfg) {
+ block::gen::OutAction::Record_action_change_library rec;
+ if (!tlb::unpack_exact(cs, rec)) {
+ return -1;
+ }
+ // mode: +0 = remove library, +1 = add private library, +2 = add public library
+ Ref lib_ref = rec.libref->prefetch_ref();
+ ton::Bits256 hash;
+ if (lib_ref.not_null()) {
+ hash = lib_ref->get_hash().bits();
+ } else {
+ CHECK(rec.libref.write().fetch_ulong(1) == 0 && rec.libref.write().fetch_bits_to(hash));
+ }
+ try {
+ vm::Dictionary dict{new_library, 256};
+ if (!rec.mode) {
+ // remove library
+ dict.lookup_delete(hash);
+ LOG(DEBUG) << "removed " << ((rec.mode >> 1) ? "public" : "private") << " library with hash " << hash.to_hex();
+ } else {
+ auto val = dict.lookup(hash);
+ if (val.not_null()) {
+ bool is_public = val->prefetch_ulong(1);
+ auto ref = val->prefetch_ref();
+ if (hash == ref->get_hash().bits()) {
+ lib_ref = ref;
+ if (is_public == (rec.mode >> 1)) {
+ // library already in required state
+ ap.spec_actions++;
+ return 0;
+ }
+ }
+ }
+ if (lib_ref.is_null()) {
+ // library code not found
+ return 41;
+ }
+ vm::CellBuilder cb;
+ CHECK(cb.store_bool_bool(rec.mode >> 1) && cb.store_ref_bool(std::move(lib_ref)));
+ CHECK(dict.set_builder(hash, cb));
+ LOG(DEBUG) << "added " << ((rec.mode >> 1) ? "public" : "private") << " library with hash " << hash.to_hex();
+ }
+ new_library = std::move(dict).extract_root_cell();
+ } catch (vm::VmError& vme) {
+ return 42;
+ }
+ ap.spec_actions++;
+ return 0;
+}
+
// msg_fwd_fees = (lump_price + ceil((bit_price * msg.bits + cell_price * msg.cells)/2^16)) nanograms
// ihr_fwd_fees = ceil((msg_fwd_fees * ihr_price_factor)/2^16) nanograms
// bits in the root cell of a message are not included in msg.bits (lump_price pays for them)
diff --git a/crypto/block/transaction.h b/crypto/block/transaction.h
index 25f80595..10150f1c 100644
--- a/crypto/block/transaction.h
+++ b/crypto/block/transaction.h
@@ -65,10 +65,10 @@ struct NewOutMsg {
NewOutMsg(ton::LogicalTime _lt, Ref _msg, Ref _trans)
: lt(_lt), msg(std::move(_msg)), trans(std::move(_trans)) {
}
- bool operator<(const NewOutMsg& other) const& {
+ bool operator<(const NewOutMsg& other) const & {
return lt < other.lt || (lt == other.lt && msg->get_hash() < other.msg->get_hash());
}
- bool operator>(const NewOutMsg& other) const& {
+ bool operator>(const NewOutMsg& other) const & {
return lt > other.lt || (lt == other.lt && other.msg->get_hash() < msg->get_hash());
}
};
@@ -371,6 +371,7 @@ struct Transaction {
Ref prepare_vm_c7(const ComputePhaseConfig& cfg) const;
bool prepare_rand_seed(td::BitArray<256>& rand_seed, const ComputePhaseConfig& cfg) const;
int try_action_set_code(vm::CellSlice& cs, ActionPhase& ap, const ActionPhaseConfig& cfg);
+ int try_action_change_library(vm::CellSlice& cs, ActionPhase& ap, const ActionPhaseConfig& cfg);
int try_action_send_msg(const vm::CellSlice& cs, ActionPhase& ap, const ActionPhaseConfig& cfg, int redoing = 0);
int try_action_reserve_currency(vm::CellSlice& cs, ActionPhase& ap, const ActionPhaseConfig& cfg);
bool check_replace_src_addr(Ref& src_addr) const;
diff --git a/crypto/fift/lib/Asm.fif b/crypto/fift/lib/Asm.fif
index eb9034ec..730ef78a 100644
--- a/crypto/fift/lib/Asm.fif
+++ b/crypto/fift/lib/Asm.fif
@@ -1013,6 +1013,8 @@ x{FB00} @Defop SENDRAWMSG
x{FB02} @Defop RAWRESERVE
x{FB03} @Defop RAWRESERVEX
x{FB04} @Defop SETCODE
+x{FB06} @Defop SETLIBCODE
+x{FB07} @Defop CHANGELIB
//
// debug primitives
diff --git a/crypto/fift/lib/Fift.fif b/crypto/fift/lib/Fift.fif
index 5430c359..724ef812 100644
--- a/crypto/fift/lib/Fift.fif
+++ b/crypto/fift/lib/Fift.fif
@@ -111,3 +111,4 @@ variable base
{ true (atom) drop } : atom
{ bl word atom 1 'nop } ::_ `
{ hole dup 1 { @ execute } does create } : recursive
+{ 0 { 1+ dup 1 ' $() does over (.) "$" swap $+ 0 (create) } rot times drop } : :$1..n
diff --git a/crypto/fift/lib/GetOpt.fif b/crypto/fift/lib/GetOpt.fif
new file mode 100644
index 00000000..ee8a59af
--- /dev/null
+++ b/crypto/fift/lib/GetOpt.fif
@@ -0,0 +1,92 @@
+library GetOpt // Simple command-line options parser
+"Lists.fif" include
+
+// May be used as follows:
+// begin-options
+// "h" { ."Help Message" 0 halt } short-option
+// "v" { parse-int =: verbosity } short-option-arg
+// "i" "--interactive" { true =: interactive } short-long-option
+// parse-options
+
+// ( l -- l') computes tail of list l if non-empty; else ()
+{ dup null? ' cdr ifnot } : safe-cdr
+// ( l c -- l') deletes first c elements from list l
+{ ' safe-cdr swap times } : list-delete-first
+// ( l n c -- l' ) deletes c elements starting from n-th in list l
+recursive list-delete-range {
+ dup 0<= { 2drop } {
+ over 0<= { nip list-delete-first } {
+ swap 1- swap rot uncons 2swap list-delete-range cons
+ } cond } cond
+} swap !
+// ( n c -- ) deletes $n .. $(n+c-1) from the argument list $*
+{ swap 1- $* @ swap rot list-delete-range $* ! } : $*del..
+// ( s s' -- ? ) checks whether s' is a prefix of s
+{ tuck $len over $len over >= { $| drop $= } { 2drop drop false } cond
+} : $pfx?
+// ( s -- ? ) checks whether s is an option (a string beginning with '-')
+{ dup $len 1 > { "-" $pfx? } { drop false } cond } : is-opt?
+// ( l -- s i or 0 ) finds first string in l beginning with '-'
+{ 0 { 1+ over null? { 2drop 0 true } {
+ swap uncons over is-opt? { drop swap true } { nip swap false } cond
+ } cond } until
+} : list-find-opt
+// ( -- s i or 0 ) finds first option in cmdline args
+{ $* @ list-find-opt } : first-opt
+// ( s t -- ? ) checks whether short/long option s matches description t
+{ third $= } : short-option-matches
+' second : get-opt-flags
+' first : get-opt-exec
+{ dup get-opt-flags 4 and 0= 3 + [] $=
+} : long-option-matches
+// ( s l -- t -1 or 0 ) finds short/long option s in list l
+{ swap 1 { swap short-option-matches } does assoc-gen
+} : lookup-short-option
+{ swap 1 { swap long-option-matches } does assoc-gen
+} : lookup-long-option
+// ( s -- s' null or s' s'' ) Splits long option --opt=arg at '='
+{ dup "=" $pos 1+ ?dup { tuck $| swap rot 1- $| drop swap } { null } cond
+} : split-longopt
+// ( l -- i or 0 )
+// parses command line arguments according to option description list l
+// and returns index i of first incorrect option
+{ { first-opt dup 0= { true } {
+ swap dup "--" $pfx? { // l i s
+ dup $len 2 = { drop dup 1 $*del.. 0 true } {
+ split-longopt swap 3 pick
+ lookup-long-option not { drop true } { // l i s' t f
+ dup get-opt-exec swap get-opt-flags 3 and // l i s' e f'
+ 2 pick null? { dup 1 = } { dup 0= negate } cond // l i s' e f' f''
+ dup 1 = { 2drop 2drop true } {
+ { drop nip over 1+ $() swap execute 2 $*del.. false } {
+ ' nip ifnot execute 1 $*del.. false
+ } cond } cond } cond } cond } { // l i s
+ 1 $| nip {
+ dup $len 0= { drop 1 $*del.. false true } {
+ 1 $| swap 3 pick // l i s' s l
+ lookup-short-option not { drop true true } { // l i s' t
+ dup get-opt-exec swap get-opt-flags 3 and // l i s' e f'
+ ?dup 0= { execute false } {
+ 2 pick $len { drop execute "" false } {
+ 2 = { nip null swap execute "" false } { // l i e
+ nip over 1+ $() swap execute 2 $*del.. false true
+ } cond } cond } cond } cond } cond } until
+ } cond
+ } cond } until nip
+} : getopt
+// ( l -- ) Parses options and throws an error on failure
+{ getopt ?dup { $() "cannot parse command line options near `" swap $+ +"`" abort } if
+} : run-getopt
+
+anon constant opt-list-marker
+' opt-list-marker : begin-options
+{ opt-list-marker list-until-marker } : end-options
+{ end-options run-getopt } : parse-options
+// ( s e -- o ) Creates short/long option s with execution token e
+{ 0 rot triple } dup : short-option : long-option
+// ( s s' e -- o ) Creates a combined short option s and long option s' with execution token e
+{ 4 2swap 4 tuple } : short-long-option
+{ 1 rot triple } dup : short-option-arg : long-option-arg
+{ 2 rot triple } dup : short-option-?arg : long-option-?arg
+{ 5 2swap 4 tuple } : short-long-option-arg
+{ 6 2swap 4 tuple } : short-long-option-?arg
diff --git a/crypto/fift/utils.cpp b/crypto/fift/utils.cpp
index fdbd0c7e..3ed11c31 100644
--- a/crypto/fift/utils.cpp
+++ b/crypto/fift/utils.cpp
@@ -46,6 +46,9 @@ td::Result load_Lists_fif(std::string dir = "") {
td::Result load_Lisp_fif(std::string dir = "") {
return load_source("Lisp.fif", dir);
}
+td::Result load_GetOpt_fif(std::string dir = "") {
+ return load_source("GetOpt.fif", dir);
+}
class MemoryFileLoader : public fift::FileLoader {
public:
@@ -115,6 +118,10 @@ td::Result create_source_lookup(std::string main, bool need_
TRY_RESULT(f, load_TonUtil_fif(dir));
loader->add_file("/TonUtil.fif", std::move(f));
}
+ {
+ TRY_RESULT(f, load_GetOpt_fif(dir));
+ loader->add_file("/GetOpt.fif", std::move(f));
+ }
}
if (need_lisp) {
TRY_RESULT(f, load_Lisp_fif(dir));
diff --git a/crypto/fift/words.cpp b/crypto/fift/words.cpp
index ed703b12..7459656b 100644
--- a/crypto/fift/words.cpp
+++ b/crypto/fift/words.cpp
@@ -604,6 +604,12 @@ void interpret_str_split(vm::Stack& stack) {
stack.push_string(std::string{str, sz});
}
+void interpret_str_pos(vm::Stack& stack) {
+ auto s2 = stack.pop_string(), s1 = stack.pop_string();
+ auto pos = s1.find(s2);
+ stack.push_smallint(pos == std::string::npos ? -1 : pos);
+}
+
void interpret_str_reverse(vm::Stack& stack) {
std::string s = stack.pop_string();
auto it = s.begin();
@@ -659,6 +665,20 @@ void interpret_utf8_str_split(vm::Stack& stack) {
}
}
+void interpret_utf8_str_pos(vm::Stack& stack) {
+ auto s2 = stack.pop_string(), s1 = stack.pop_string();
+ auto pos = s1.find(s2);
+ if (pos == std::string::npos) {
+ stack.push_smallint(-1);
+ return;
+ }
+ int cnt = 0;
+ for (std::size_t i = 0; i < pos; i++) {
+ cnt += ((s1[i] & 0xc0) != 0x80);
+ }
+ stack.push_smallint(cnt);
+}
+
void interpret_str_remove_trailing_int(vm::Stack& stack, int arg) {
char x = (char)(arg ? arg : stack.pop_long_range(127));
std::string s = stack.pop_string();
@@ -2336,12 +2356,38 @@ void interpret_db_run_vm_parallel(IntCtx& ctx) {
do_interpret_db_run_vm_parallel(ctx.error_stream, ctx.stack, ctx.ton_db, threads_n, tasks_n);
}
+Ref cmdline_args{true};
+
+void interpret_get_fixed_cmdline_arg(vm::Stack& stack, int n) {
+ if (!n) {
+ return;
+ }
+ auto v = cmdline_args->get();
+ while (true) {
+ if (v.empty()) {
+ stack.push(vm::StackEntry{});
+ return;
+ }
+ auto t = v.as_tuple_range(2, 2);
+ if (t.is_null()) {
+ throw IntError{"invalid cmdline arg list"};
+ }
+ if (!--n) {
+ stack.push(t->at(0));
+ return;
+ }
+ v = t->at(1);
+ }
+}
+
// n -- executes $n
void interpret_get_cmdline_arg(IntCtx& ctx) {
int n = ctx.stack.pop_smallint_range(999999);
- char buffer[14];
- sprintf(buffer, "$%d ", n);
- auto entry = ctx.dictionary->lookup(std::string{buffer});
+ if (n) {
+ interpret_get_fixed_cmdline_arg(ctx.stack, n);
+ return;
+ }
+ auto entry = ctx.dictionary->lookup("$0 ");
if (!entry) {
throw IntError{"-?"};
} else {
@@ -2349,6 +2395,19 @@ void interpret_get_cmdline_arg(IntCtx& ctx) {
}
}
+void interpret_get_cmdline_arg_count(vm::Stack& stack) {
+ auto v = cmdline_args->get();
+ int cnt;
+ for (cnt = 0; !v.empty(); cnt++) {
+ auto t = v.as_tuple_range(2, 2);
+ if (t.is_null()) {
+ throw IntError{"invalid cmdline arg list"};
+ }
+ v = t->at(1);
+ }
+ stack.push_smallint(cnt);
+}
+
void interpret_getenv(vm::Stack& stack) {
auto str = stack.pop_string();
auto value = str.size() < 1024 ? getenv(str.c_str()) : nullptr;
@@ -2568,6 +2627,7 @@ void init_words_common(Dictionary& d) {
d.def_stack_word("$= ", interpret_str_equal);
d.def_stack_word("$cmp ", interpret_str_cmp);
d.def_stack_word("$reverse ", interpret_str_reverse);
+ d.def_stack_word("$pos ", interpret_str_pos);
d.def_stack_word("(-trailing) ", std::bind(interpret_str_remove_trailing_int, _1, 0));
d.def_stack_word("-trailing ", std::bind(interpret_str_remove_trailing_int, _1, ' '));
d.def_stack_word("-trailing0 ", std::bind(interpret_str_remove_trailing_int, _1, '0'));
@@ -2575,6 +2635,7 @@ void init_words_common(Dictionary& d) {
d.def_stack_word("Blen ", interpret_bytes_len);
d.def_stack_word("$Len ", interpret_utf8_str_len);
d.def_stack_word("$Split ", interpret_utf8_str_split);
+ d.def_stack_word("$Pos ", interpret_utf8_str_pos);
d.def_ctx_word("Bx. ", std::bind(interpret_bytes_hex_print_raw, _1, true));
d.def_stack_word("B>X ", std::bind(interpret_bytes_to_hex, _1, true));
d.def_stack_word("B>x ", std::bind(interpret_bytes_to_hex, _1, false));
@@ -2766,6 +2827,10 @@ void init_words_common(Dictionary& d) {
d.def_ctx_word("quit ", interpret_quit);
d.def_ctx_word("bye ", interpret_bye);
d.def_stack_word("halt ", interpret_halt);
+ // cmdline args
+ d.def_stack_word("$* ", std::bind(interpret_literal, _1, vm::StackEntry{cmdline_args}));
+ d.def_stack_word("$# ", interpret_get_cmdline_arg_count);
+ d.def_ctx_word("$() ", interpret_get_cmdline_arg);
}
void init_words_ton(Dictionary& d) {
@@ -2799,13 +2864,16 @@ void import_cmdline_args(Dictionary& d, std::string arg0, int n, const char* con
using namespace std::placeholders;
LOG(DEBUG) << "import_cmdlist_args(" << arg0 << "," << n << ")";
d.def_stack_word("$0 ", std::bind(interpret_literal, _1, vm::StackEntry{arg0}));
- for (int i = 0; i < n; i++) {
- char buffer[14];
- sprintf(buffer, "$%d ", i + 1);
- d.def_stack_word(buffer, std::bind(interpret_literal, _1, vm::StackEntry{argv[i]}));
+ vm::StackEntry list;
+ for (int i = n - 1; i >= 0; i--) {
+ list = vm::StackEntry::cons(vm::StackEntry{argv[i]}, list);
+ }
+ cmdline_args->set(std::move(list));
+ for (int i = 1; i <= n; i++) {
+ char buffer[14];
+ sprintf(buffer, "$%d ", i);
+ d.def_stack_word(buffer, std::bind(interpret_get_fixed_cmdline_arg, _1, i));
}
- d.def_stack_word("$# ", std::bind(interpret_const, _1, n));
- d.def_ctx_word("$() ", interpret_get_cmdline_arg);
}
std::pair numeric_value_ext(std::string s, bool allow_frac = true) {
diff --git a/crypto/smartcont/config-code.fc b/crypto/smartcont/config-code.fc
index c7058f50..8c453cde 100644
--- a/crypto/smartcont/config-code.fc
+++ b/crypto/smartcont/config-code.fc
@@ -9,10 +9,9 @@
(cell, int, int, cell) load_data() inline {
var cs = get_data().begin_parse();
- var (cfg_dict, stored_seqno, public_key) = (cs~load_ref(), cs~load_uint(32), cs~load_uint(256));
- var vote_dict = cs.slice_empty?() ? new_dict() : cs~load_dict();
+ var res = (cs~load_ref(), cs~load_uint(32), cs~load_uint(256), cs~load_dict());
cs.end_parse();
- return (cfg_dict, stored_seqno, public_key, vote_dict);
+ return res;
}
() store_data(cfg_dict, stored_seqno, public_key, vote_dict) impure inline {
diff --git a/crypto/smartcont/elector-code.fc b/crypto/smartcont/elector-code.fc
index 37257d1b..563aa7bc 100644
--- a/crypto/smartcont/elector-code.fc
+++ b/crypto/smartcont/elector-code.fc
@@ -796,7 +796,7 @@ _ participant_list() method_id {
_ participant_list_extended() method_id {
var elect = get_data().begin_parse().preload_dict();
if (elect.null?()) {
- return nil;
+ return (0, 0, 0, 0, nil, 0, 0);
}
var (elect_at, elect_close, min_stake, total_stake, members, failed, finished) = elect.unpack_elect();
var l = nil;
@@ -809,7 +809,7 @@ _ participant_list_extended() method_id {
l = cons(pair(id, tuple4(stake, max_factor, addr, adnl_addr)), l);
}
} until (~ f);
- return l;
+ return (elect_at, elect_close, min_stake, total_stake, l, failed, finished);
}
;; computes the return stake
diff --git a/crypto/smartcont/gen-zerostate.fif b/crypto/smartcont/gen-zerostate.fif
index 4277445f..5c4de564 100644
--- a/crypto/smartcont/gen-zerostate.fif
+++ b/crypto/smartcont/gen-zerostate.fif
@@ -160,7 +160,7 @@ Masterchain swap
// 9 4 1 config.validator_num!
1000 100 13 config.validator_num!
// min-stake max-stake min-total-stake max-factor
-GR$10000 GR$10000000 GR$1000000 sg~10 config.validator_stake_limits!
+GR$10000 GR$10000000 GR$500000 sg~10 config.validator_stake_limits!
// elected-for elect-start-before elect-end-before stakes-frozen-for
// 400000 200000 4000 400000 config.election_params!
// 4000 2000 500 1000 config.election_params! // DEBUG
@@ -219,7 +219,7 @@ now dup orig_vset_valid_for + 0 config.validators!
0 32 u, // seqno
"config-master" +suffix +".pk" load-generate-keypair drop
B,
- newdict dict, // vote dict
+ dictnew dict, // vote dict
b> // data
empty_cell // libraries
GR$10 // balance
@@ -237,8 +237,8 @@ Masterchain swap
*/
// pubkey amount `create-wallet1` or pubkey amount `create-wallet2`
-PK'PuZPPXK5Rff9SvtoS7Y9lUuEixvy-J6aishYFj3Qn6P0pJMb GR$100 create-wallet1
-PK'PuYiB1zAWzr4p8j6I681+sGUrRGcn6Ylf7vXl0xaUl/w6Xfg GR$170 create-wallet0
+PK'PuZPPXK5Rff9SvtoS7Y9lUuEixvy-J6aishYFj3Qn6P0pJMb GR$1000000000 create-wallet1
+PK'PuYiB1zAWzr4p8j6I681+sGUrRGcn6Ylf7vXl0xaUl/w6Xfg GR$1700000000 create-wallet0
/*
*
diff --git a/crypto/smartcont/multisig-code.fc b/crypto/smartcont/multisig-code.fc
index cbe2d30f..36e21a7b 100644
--- a/crypto/smartcont/multisig-code.fc
+++ b/crypto/smartcont/multisig-code.fc
@@ -2,21 +2,32 @@
_ unpack_state() inline_ref {
var ds = begin_parse(get_data());
- var res = (ds~load_uint(8), ds~load_uint(8), ds~load_uint(64), ds~load_dict(), ds~load_dict());
+ var res = (ds~load_uint(32), ds~load_uint(8), ds~load_uint(8), ds~load_uint(64), ds~load_dict(), ds~load_dict());
ds.end_parse();
return res;
}
-_ pack_state(cell pending_queries, cell public_keys, int last_cleaned, int k, int n) inline_ref {
+_ pack_state(cell pending_queries, cell owner_infos, int last_cleaned, int k, int n, int wallet_id) inline_ref {
return begin_cell()
+ .store_uint(wallet_id, 32)
.store_uint(n, 8)
.store_uint(k, 8)
.store_uint(last_cleaned, 64)
- .store_dict(public_keys)
+ .store_dict(owner_infos)
.store_dict(pending_queries)
.end_cell();
}
+_ pack_owner_info(int public_key, int flood) inline_ref {
+ return begin_cell()
+ .store_uint(public_key, 256)
+ .store_uint(flood, 8);
+}
+
+_ unpack_owner_info(slice cs) inline_ref {
+ return (cs~load_uint(256), cs~load_uint(8));
+}
+
(int, int) check_signatures(cell public_keys, cell signatures, int hash, int cnt_bits) inline_ref {
int cnt = 0;
@@ -41,44 +52,60 @@ _ pack_state(cell pending_queries, cell public_keys, int last_cleaned, int k, in
return (cnt, cnt_bits);
}
-
() recv_internal(slice in_msg) impure {
;; do nothing for internal messages
}
-(int, int, slice) unpack_query_data(slice in_msg, int n, slice query, var found?) inline_ref {
+(int, int, int, slice) unpack_query_data(slice in_msg, int n, slice query, var found?, int root_i) inline_ref {
if (found?) {
throw_unless(35, query~load_int(1));
- (int cnt, int cnt_bits, slice msg) = (query~load_uint(8), query~load_uint(n), query);
+ (int creator_i, int cnt, int cnt_bits, slice msg) = (query~load_uint(8), query~load_uint(8), query~load_uint(n), query);
throw_unless(36, slice_hash(msg) == slice_hash(in_msg));
- return (cnt, cnt_bits, msg);
+ return (creator_i, cnt, cnt_bits, msg);
}
- return (0, 0, in_msg);
+ return (root_i, 0, 0, in_msg);
}
() try_init() impure inline_ref {
;; first query without signatures is always accepted
- (int n, int k, int last_cleaned, cell public_keys, cell pending_queries) = unpack_state();
+ (int wallet_id, int n, int k, int last_cleaned, cell owner_infos, cell pending_queries) = unpack_state();
throw_if(37, last_cleaned);
accept_message();
- set_data(pack_state(pending_queries, public_keys, 1, k, n));
+ set_data(pack_state(pending_queries, owner_infos, 1, k, n, wallet_id));
}
-cell update_pending_queries(cell pending_queries, slice msg, int query_id, int cnt, int cnt_bits, int n, int k) impure inline_ref {
+(cell, cell) update_pending_queries(cell pending_queries, cell owner_infos, slice msg, int query_id, int creator_i, int cnt, int cnt_bits, int n, int k) impure inline_ref {
if (cnt >= k) {
+ accept_message();
while (msg.slice_refs()) {
var mode = msg~load_uint(8);
send_raw_message(msg~load_ref(), mode);
}
pending_queries~udict_set_builder(64, query_id, begin_cell().store_int(0, 1));
+
+ (slice owner_info, var found?) = owner_infos.udict_get?(8, creator_i);
+ (int public_key, int flood) = unpack_owner_info(owner_info);
+ owner_infos~udict_set_builder(8, creator_i, pack_owner_info(public_key, flood - 1));
} else {
pending_queries~udict_set_builder(64, query_id, begin_cell()
.store_uint(1, 1)
+ .store_uint(creator_i, 8)
.store_uint(cnt, 8)
.store_uint(cnt_bits, n)
.store_slice(msg));
}
- return pending_queries;
+ return (pending_queries, owner_infos);
+}
+
+(int, int) calc_boc_size(int cells, int bits, slice root) {
+ cells += 1;
+ bits += root.slice_bits();
+
+ while (root.slice_refs()) {
+ (cells, bits) = calc_boc_size(cells, bits, root~load_ref().begin_parse());
+ }
+
+ return (cells, bits);
}
() recv_external(slice in_msg) impure {
@@ -92,44 +119,62 @@ cell update_pending_queries(cell pending_queries, slice msg, int query_id, int c
int root_hash = slice_hash(in_msg);
int root_i = in_msg~load_uint(8);
- (int n, int k, int last_cleaned, cell public_keys, cell pending_queries) = unpack_state();
+ (int wallet_id, int n, int k, int last_cleaned, cell owner_infos, cell pending_queries) = unpack_state();
last_cleaned -= last_cleaned == 0;
- (slice public_key, var found?) = public_keys.udict_get?(8, root_i);
+ (slice owner_info, var found?) = owner_infos.udict_get?(8, root_i);
+ (int public_key, int flood) = unpack_owner_info(owner_info);
throw_unless(31, found?);
- throw_unless(32, check_signature(root_hash, root_signature, public_key.preload_uint(256)));
+ throw_unless(32, check_signature(root_hash, root_signature, public_key));
cell signatures = in_msg~load_dict();
var hash = slice_hash(in_msg);
+ int query_wallet_id = in_msg~load_uint(32);
+ throw_unless(42, query_wallet_id == wallet_id);
+
int query_id = in_msg~load_uint(64);
+ (int cnt, int bits) = calc_boc_size(0, 0, in_msg);
+ throw_if(40, (cnt > 8) | (bits > 2048));
+
+ (slice query, var found?) = pending_queries.udict_get?(64, query_id);
+
+ ifnot (found?) {
+ flood += 1;
+ throw_if(39, flood > 10);
+ }
+
var bound = (now() << 32);
throw_if(33, query_id < bound);
- (slice query, var found?) = pending_queries.udict_get?(64, query_id);
- (int cnt, int cnt_bits, slice msg) = unpack_query_data(in_msg, n, query, found?);
+ (int creator_i, int cnt, int cnt_bits, slice msg) = unpack_query_data(in_msg, n, query, found?, root_i);
int mask = 1 << root_i;
throw_if(34, cnt_bits & mask);
cnt_bits |= mask;
cnt += 1;
- ;; TODO: reserve some gas or FAIL
- accept_message();
+ set_gas_limit(100000);
- pending_queries = update_pending_queries(pending_queries, msg, query_id, cnt, cnt_bits, n, k);
- set_data(pack_state(pending_queries, public_keys, last_cleaned, k, n));
+ ifnot (found?) {
+ owner_infos~udict_set_builder(8, root_i, pack_owner_info(public_key, flood));
+ throw_if(41, (cnt < k) & (bound + ((60 * 60) << 32) > query_id));
+ }
+
+ (pending_queries, owner_infos) = update_pending_queries(pending_queries, owner_infos, msg, query_id, creator_i, cnt, cnt_bits, n, k);
+ set_data(pack_state(pending_queries, owner_infos, last_cleaned, k, n, wallet_id));
commit();
int need_save = 0;
ifnot (cell_null?(signatures) | (cnt >= k)) {
- (int new_cnt, cnt_bits) = check_signatures(public_keys, signatures, hash, cnt_bits);
+ (int new_cnt, cnt_bits) = check_signatures(owner_infos, signatures, hash, cnt_bits);
cnt += new_cnt;
- pending_queries = update_pending_queries(pending_queries, msg, query_id, cnt, cnt_bits, n, k);
+ (pending_queries, owner_infos) = update_pending_queries(pending_queries, owner_infos, msg, query_id, creator_i, cnt, cnt_bits, n, k);
need_save = -1;
}
+ accept_message();
bound -= (64 << 32); ;; clean up records expired more than 64 seconds ago
int old_last_cleaned = last_cleaned;
do {
@@ -146,18 +191,18 @@ cell update_pending_queries(cell pending_queries, slice msg, int query_id, int c
} until (~ f);
if (need_save) {
- set_data(pack_state(pending_queries, public_keys, last_cleaned, k, n));
+ set_data(pack_state(pending_queries, owner_infos, last_cleaned, k, n, wallet_id));
}
}
;; Get methods
;; returns -1 for processed queries, 0 for unprocessed, 1 for unknown (forgotten)
(int, int) get_query_state(int query_id) method_id {
- (int n, _, int last_cleaned, _, cell pending_queries) = unpack_state();
+ (_, int n, _, int last_cleaned, _, cell pending_queries) = unpack_state();
(slice cs, var found) = pending_queries.udict_get?(64, query_id);
if (found) {
if (cs~load_int(1)) {
- cs~load_uint(8);
+ cs~load_uint(8 + 8);
return (0, cs~load_uint(n));
} else {
return (-1, 0);
@@ -172,8 +217,8 @@ int processed?(int query_id) method_id {
return x;
}
-cell create_init_state(int n, int k, cell public_keys) method_id {
- return pack_state(new_dict(), public_keys, 0, k, n);
+cell create_init_state(int wallet_id, int n, int k, cell owners_info) method_id {
+ return pack_state(new_dict(), owners_info, 0, k, n, wallet_id);
}
cell merge_list(cell a, cell b) {
@@ -196,7 +241,7 @@ cell merge_list(cell a, cell b) {
}
cell get_public_keys() method_id {
- (_, _, _, cell public_keys, _) = unpack_state();
+ (_, _, _, _, cell public_keys, _) = unpack_state();
return public_keys;
}
@@ -222,14 +267,14 @@ cell get_public_keys() method_id {
}
cell messages_by_mask(int mask) method_id {
- (int n, _, _, _, cell pending_queries) = unpack_state();
+ (_, int n, _, _, _, cell pending_queries) = unpack_state();
int i = -1;
cell a = new_dict();
do {
(i, var cs, var f) = pending_queries.udict_get_next?(64, i);
if (f) {
if (cs~load_int(1)) {
- int cnt_bits = cs.skip_bits(8).preload_uint(n);
+ int cnt_bits = cs.skip_bits(8 + 8).preload_uint(n);
if (cnt_bits & mask) {
a~udict_set_builder(64, i, begin_cell().store_slice(cs));
}
@@ -248,7 +293,7 @@ cell get_messages_unsigned() method_id {
}
(int, int) get_n_k() method_id {
- (int n, int k, _, _, _) = unpack_state();
+ (_, int n, int k, _, _, _) = unpack_state();
return (n, k);
}
diff --git a/crypto/smartcont/show-addr.fif b/crypto/smartcont/show-addr.fif
index 5d309464..51b2df8c 100755
--- a/crypto/smartcont/show-addr.fif
+++ b/crypto/smartcont/show-addr.fif
@@ -1,12 +1,13 @@
#!/usr/bin/fift -s
"TonUtil.fif" include
-{ ."usage: " @' $0 type ." " cr
+{ ."usage: " $0 type ." " cr
."Shows the address of a simple wallet created by new-wallet.fif, with address in .addr "
."and private key in file .pk" cr 1 halt
} : usage
-def? $# { @' $# 1 > ' usage if } if
-def? $1 { @' $1 } { "new-wallet" } cond constant file-base
+$# 1 > ' usage if
+1 :$1..n
+$1 dup null? { drop "new-wallet" } if =: file-base
file-base +".addr" dup ."Loading wallet address from " type cr file>B 32 B|
dup Blen { 32 B>i@ } { drop Basechain } cond constant wallet_wc
diff --git a/crypto/smartcont/stdlib.fc b/crypto/smartcont/stdlib.fc
index ba941670..12889a13 100644
--- a/crypto/smartcont/stdlib.fc
+++ b/crypto/smartcont/stdlib.fc
@@ -40,6 +40,7 @@ cont get_c3() impure asm "c3 PUSH";
cont bless(slice s) impure asm "BLESS";
() accept_message() impure asm "ACCEPT";
+() set_gas_limit(int limit) impure asm "SETGASLIMIT";
() commit() impure asm "COMMIT";
int min(int x, int y) asm "MIN";
diff --git a/crypto/smartcont/wallet.fif b/crypto/smartcont/wallet.fif
index bdd03399..e5cf6ad4 100755
--- a/crypto/smartcont/wallet.fif
+++ b/crypto/smartcont/wallet.fif
@@ -1,35 +1,32 @@
#!/usr/bin/fift -s
"TonUtil.fif" include
+"GetOpt.fif" include
{ ."usage: " @' $0 type ." [-n] [-B ] [-C ] []" cr
."Creates a request to simple wallet created by new-wallet.fif, with private key loaded from file .pk "
."and address from .addr, and saves it into .boc ('wallet-query.boc' by default)" cr 1 halt
} : usage
+
"" =: comment // comment for simple transfers
true =: allow-bounce
-def? $5 { @' $5 "-n" $= { false =: allow-bounce [forget] $5
- def? $6 { @' $6 =: $5 [forget] $6 } if
- def? $7 { @' $7 =: $6 [forget] $7 } if
- @' $# 1- =: $#
- } if
-} if
-
-def? $6 { @' $5 dup "-B" $= swap "-C" $= tuck or
- { @' $6 swap { =: comment } { =: body-boc-file } cond [forget] $6
- def? $7 { @' $7 =: $5 [forget] $7 } { [forget] $5 } cond
- @' $# 2- =: $#
- } if
-} if
+3 =: send-mode // mode for SENDRAWMSG: +1 - sender pays fees, +2 - ignore errors
+
+begin-options
+ "n" "--no-bounce" { false =: allow-bounce } short-long-option
+ "B" "--body" { =: body-boc-file } short-long-option-arg
+ "C" "--comment" { =: comment } short-long-option-arg
+ "m" "--mode" { parse-int =: send-mode } short-long-option-arg
+ "h" "--help" { usage } short-long-option
+parse-options
+
$# dup 4 < swap 5 > or ' usage if
-
-true constant bounce
-
+5 :$1..n
+true =: bounce
$1 =: file-base
$2 bounce parse-load-address allow-bounce and =: bounce 2=: dest_addr
$3 parse-int =: seqno
$4 $>GR =: amount
-def? $5 { @' $5 } { "wallet-query" } cond constant savefile
-3 constant send-mode // mode for SENDRAWMSG: +1 - sender pays fees, +2 - ignore errors
+$5 dup null? { drop "wallet-query" } if =: savefile
// "" 1 { 69091 * 1+ 65535 and tuck 2521 / 65 + hold swap } 1000 times drop =: comment
file-base +".addr" load-address
diff --git a/crypto/smc-envelope/MultisigWallet.cpp b/crypto/smc-envelope/MultisigWallet.cpp
index 36830179..0c8b56d7 100644
--- a/crypto/smc-envelope/MultisigWallet.cpp
+++ b/crypto/smc-envelope/MultisigWallet.cpp
@@ -8,8 +8,13 @@
namespace ton {
-MultisigWallet::QueryBuilder::QueryBuilder(td::int64 query_id, td::Ref msg, int mode) {
- msg_ = vm::CellBuilder().store_long(query_id, 64).store_long(mode, 8).store_ref(std::move(msg)).finalize();
+MultisigWallet::QueryBuilder::QueryBuilder(td::uint32 wallet_id, td::int64 query_id, td::Ref msg, int mode) {
+ msg_ = vm::CellBuilder()
+ .store_long(wallet_id, 32)
+ .store_long(query_id, 64)
+ .store_long(mode, 8)
+ .store_ref(std::move(msg))
+ .finalize();
}
void MultisigWallet::QueryBuilder::sign(td::int32 id, td::Ed25519::PrivateKey& pk) {
CHECK(id < td::narrow_cast(mask_.size()));
@@ -87,26 +92,29 @@ std::vector MultisigWallet::get_public_keys() const {
return res;
}
-td::Ref MultisigWallet::create_init_data(std::vector public_keys, int k) const {
+td::Ref MultisigWallet::create_init_data(td::uint32 wallet_id, std::vector public_keys,
+ int k) const {
vm::Dictionary pk(8);
for (size_t i = 0; i < public_keys.size(); i++) {
auto key = pk.integer_key(td::make_refint(i), 8, false);
- pk.set_builder(key.bits(), 8, vm::CellBuilder().store_bytes(public_keys[i].as_slice()));
+ pk.set_builder(key.bits(), 8, vm::CellBuilder().store_bytes(public_keys[i].as_slice()).store_long(0, 8));
}
- auto res = run_get_method("create_init_state",
- {td::make_refint(public_keys.size()), td::make_refint(k), pk.get_root_cell()});
+ auto res = run_get_method("create_init_state", {td::make_refint(wallet_id), td::make_refint(public_keys.size()),
+ td::make_refint(k), pk.get_root_cell()});
CHECK(res.code == 0);
return res.stack.write().pop_cell();
}
-td::Ref MultisigWallet::create_init_data_fast(std::vector public_keys, int k) {
+td::Ref MultisigWallet::create_init_data_fast(td::uint32 wallet_id, std::vector public_keys,
+ int k) {
vm::Dictionary pk(8);
for (size_t i = 0; i < public_keys.size(); i++) {
auto key = pk.integer_key(td::make_refint(i), 8, false);
- pk.set_builder(key.bits(), 8, vm::CellBuilder().store_bytes(public_keys[i].as_slice()));
+ pk.set_builder(key.bits(), 8, vm::CellBuilder().store_bytes(public_keys[i].as_slice()).store_long(0, 8));
}
vm::CellBuilder cb;
+ cb.store_long(wallet_id, 32);
cb.store_long(public_keys.size(), 8).store_long(k, 8).store_long(0, 64);
cb.ensure_throw(cb.store_maybe_ref(pk.get_root_cell()));
cb.ensure_throw(cb.store_maybe_ref({}));
@@ -156,7 +164,7 @@ std::vector MultisigWallet::get_unsigned_messaged(int i
vm::Dictionary dict(std::move(cell), 64);
std::vector res;
dict.check_for_each([&](auto cs, auto ptr, auto ptr_bits) {
- cs.write().skip_first(8);
+ cs.write().skip_first(8 + 8);
Message message;
td::BigInt256 query_id;
query_id.import_bits(ptr, ptr_bits, false);
diff --git a/crypto/smc-envelope/MultisigWallet.h b/crypto/smc-envelope/MultisigWallet.h
index 10c6d076..17395e47 100644
--- a/crypto/smc-envelope/MultisigWallet.h
+++ b/crypto/smc-envelope/MultisigWallet.h
@@ -20,7 +20,7 @@ class MultisigWallet : public ton::SmartContract {
class QueryBuilder {
public:
- QueryBuilder(td::int64 query_id, td::Ref msg, int mode = 3);
+ QueryBuilder(td::uint32 wallet_id, td::int64 query_id, td::Ref msg, int mode = 3);
void sign(td::int32 id, td::Ed25519::PrivateKey& pk);
td::Ref create_inner() const;
@@ -42,8 +42,9 @@ class MultisigWallet : public ton::SmartContract {
// creation
static td::Ref create(td::Ref data = {});
- td::Ref create_init_data(std::vector public_keys, int k) const;
- static td::Ref create_init_data_fast(std::vector public_keys, int k);
+ td::Ref create_init_data(td::uint32 wallet_id, std::vector public_keys, int k) const;
+ static td::Ref create_init_data_fast(td::uint32 wallet_id, std::vector public_keys,
+ int k);
// get methods
int processed(td::uint64 query_id) const;
diff --git a/crypto/smc-envelope/SmartContract.cpp b/crypto/smc-envelope/SmartContract.cpp
index aec7bed4..02964a7c 100644
--- a/crypto/smc-envelope/SmartContract.cpp
+++ b/crypto/smc-envelope/SmartContract.cpp
@@ -30,10 +30,6 @@
namespace ton {
namespace {
-td::int32 get_method_id(td::Slice method_name) {
- unsigned crc = td::crc16(method_name);
- return (crc & 0xffff) | 0x10000;
-}
td::Ref prepare_vm_stack(td::Ref body) {
td::Ref stack_ref{true};
td::RefInt256 acc_addr{true};
diff --git a/crypto/smc-envelope/SmartContractCode.cpp b/crypto/smc-envelope/SmartContractCode.cpp
index 4e17139b..e61cab24 100644
--- a/crypto/smc-envelope/SmartContractCode.cpp
+++ b/crypto/smc-envelope/SmartContractCode.cpp
@@ -27,11 +27,7 @@ namespace ton {
namespace {
const auto& get_map() {
static auto map = [] {
- class Cmp : public std::less<> {
- public:
- using is_transparent = void;
- };
- std::map, Cmp> map;
+ std::map, std::less<>> map;
auto with_tvm_code = [&](auto name, td::Slice code_str) {
map[name] = vm::std_boc_deserialize(td::base64_decode(code_str).move_as_ok()).move_as_ok();
};
diff --git a/crypto/test/test-smartcont.cpp b/crypto/test/test-smartcont.cpp
index 750ed94b..0ec52f58 100644
--- a/crypto/test/test-smartcont.cpp
+++ b/crypto/test/test-smartcont.cpp
@@ -455,16 +455,17 @@ TEST(Smartcon, Multisig) {
int n = 100;
int k = 99;
+ td::uint32 wallet_id = std::numeric_limits::max() - 3;
std::vector keys;
for (int i = 0; i < n; i++) {
keys.push_back(td::Ed25519::generate_private_key().move_as_ok());
}
auto init_state = ms_lib->create_init_data(
- td::transform(keys, [](auto& key) { return key.get_public_key().ok().as_octet_string(); }), k);
+ wallet_id, td::transform(keys, [](auto& key) { return key.get_public_key().ok().as_octet_string(); }), k);
auto ms = ton::MultisigWallet::create(init_state);
- td::uint64 query_id = 123;
- ton::MultisigWallet::QueryBuilder qb(query_id, vm::CellBuilder().finalize());
+ td::uint64 query_id = 123 | ((100 * 60ull) << 32);
+ ton::MultisigWallet::QueryBuilder qb(wallet_id, query_id, vm::CellBuilder().finalize());
// first empty query (init)
CHECK(ms.write().send_external_message(vm::CellBuilder().finalize()).code == 0);
// first empty query
@@ -491,7 +492,7 @@ TEST(Smartcon, Multisig) {
ASSERT_EQ(0, ms->processed(query_id));
{
- ton::MultisigWallet::QueryBuilder qb(query_id, vm::CellBuilder().finalize());
+ ton::MultisigWallet::QueryBuilder qb(wallet_id, query_id, vm::CellBuilder().finalize());
for (int i = 50; i + 1 < 100; i++) {
qb.sign(i, keys[i]);
}
@@ -507,6 +508,7 @@ TEST(Smartcon, Multisig) {
TEST(Smartcont, MultisigStress) {
int n = 10;
int k = 5;
+ td::uint32 wallet_id = std::numeric_limits::max() - 3;
std::vector keys;
for (int i = 0; i < n; i++) {
@@ -515,13 +517,14 @@ TEST(Smartcont, MultisigStress) {
auto public_keys = td::transform(keys, [](auto& key) { return key.get_public_key().ok().as_octet_string(); });
auto ms_lib = ton::MultisigWallet::create();
auto init_state_old =
- ms_lib->create_init_data_fast(td::transform(public_keys, [](auto& key) { return key.copy(); }), k);
- auto init_state = ms_lib->create_init_data(td::transform(public_keys, [](auto& key) { return key.copy(); }), k);
+ ms_lib->create_init_data_fast(wallet_id, td::transform(public_keys, [](auto& key) { return key.copy(); }), k);
+ auto init_state =
+ ms_lib->create_init_data(wallet_id, td::transform(public_keys, [](auto& key) { return key.copy(); }), k);
CHECK(init_state_old->get_hash() == init_state->get_hash());
auto ms = ton::MultisigWallet::create(init_state);
CHECK(ms->get_public_keys() == public_keys);
- td::int32 now = 0;
+ td::int32 now = 100 * 60;
td::int32 qid = 1;
using Mask = std::bitset<128>;
struct Query {
@@ -566,7 +569,7 @@ TEST(Smartcont, MultisigStress) {
};
auto sign_query = [&](Query& query, Mask mask) {
- auto qb = ton::MultisigWallet::QueryBuilder(query.id, query.message);
+ auto qb = ton::MultisigWallet::QueryBuilder(wallet_id, query.id, query.message);
int first_i = -1;
for (int i = 0; i < (int)mask.size(); i++) {
if (mask.test(i)) {
diff --git a/crypto/vm/continuation.cpp b/crypto/vm/continuation.cpp
index 5273e159..83ba6e10 100644
--- a/crypto/vm/continuation.cpp
+++ b/crypto/vm/continuation.cpp
@@ -708,6 +708,9 @@ int VmState::step() {
}
int VmState::run() {
+ if (code.is_null()) {
+ throw VmError{Excno::fatal, "cannot run an uninitialized VM"};
+ }
int res;
Guard guard(this);
do {
diff --git a/crypto/vm/dict.h b/crypto/vm/dict.h
index 11234277..9bb11be3 100644
--- a/crypto/vm/dict.h
+++ b/crypto/vm/dict.h
@@ -237,6 +237,11 @@ class DictionaryFixed : public DictionaryBase {
Ref get_minmax_key(T& key_buffer, bool fetch_max = false, bool invert_first = false) {
return get_minmax_key(key_buffer.bits(), key_buffer.size(), fetch_max, invert_first);
}
+ template
+ Ref lookup_nearest_key(T& key_buffer, bool fetch_next = false, bool allow_eq = false,
+ bool invert_first = false) {
+ return lookup_nearest_key(key_buffer.bits(), key_buffer.size(), fetch_next, allow_eq, invert_first);
+ }
protected:
virtual int label_mode() const {
diff --git a/crypto/vm/stack.hpp b/crypto/vm/stack.hpp
index 1c7fb55c..507f98df 100644
--- a/crypto/vm/stack.hpp
+++ b/crypto/vm/stack.hpp
@@ -57,6 +57,11 @@ class Atom;
using Tuple = td::Cnt>;
+template
+Ref make_tuple_ref(Args&&... args) {
+ return td::make_cnt_ref>(std::vector{std::forward(args)...});
+}
+
struct from_object_t {};
constexpr from_object_t from_object{};
@@ -192,6 +197,10 @@ class StackEntry {
public:
static StackEntry make_list(std::vector&& elems);
static StackEntry make_list(const std::vector& elems);
+ template
+ static StackEntry cons(T1&& x, T2&& y) {
+ return StackEntry{make_tuple_ref(std::forward(x), std::forward(y))};
+ }
template
static StackEntry maybe(Ref ref) {
if (ref.is_null()) {
@@ -268,11 +277,6 @@ inline void swap(StackEntry& se1, StackEntry& se2) {
se1.swap(se2);
}
-template
-Ref make_tuple_ref(Args&&... args) {
- return td::make_cnt_ref>(std::vector{std::forward(args)...});
-}
-
const StackEntry& tuple_index(const Tuple& tup, unsigned idx);
StackEntry tuple_extend_index(const Ref& tup, unsigned idx);
unsigned tuple_extend_set_index(Ref& tup, unsigned idx, StackEntry&& value, bool force = false);
diff --git a/crypto/vm/tonops.cpp b/crypto/vm/tonops.cpp
index e2142f07..8697ddcc 100644
--- a/crypto/vm/tonops.cpp
+++ b/crypto/vm/tonops.cpp
@@ -662,12 +662,49 @@ int exec_set_code(VmState* st) {
return install_output_action(st, cb.finalize());
}
+int exec_set_lib_code(VmState* st) {
+ VM_LOG(st) << "execute SETLIBCODE";
+ Stack& stack = st->get_stack();
+ stack.check_underflow(2);
+ int mode = stack.pop_smallint_range(2);
+ auto code = stack.pop_cell();
+ CellBuilder cb;
+ if (!(cb.store_ref_bool(get_actions(st)) // out_list$_ {n:#} prev:^(OutList n)
+ && cb.store_long_bool(0x26fa1dd4, 32) // action_change_library#26fa1dd4
+ && cb.store_long_bool(mode * 2 + 1, 8) // mode:(## 7) { mode <= 2 }
+ && cb.store_ref_bool(std::move(code)))) { // libref:LibRef = OutAction;
+ throw VmError{Excno::cell_ov, "cannot serialize new library code into an output action cell"};
+ }
+ return install_output_action(st, cb.finalize());
+}
+
+int exec_change_lib(VmState* st) {
+ VM_LOG(st) << "execute CHANGELIB";
+ Stack& stack = st->get_stack();
+ stack.check_underflow(2);
+ int mode = stack.pop_smallint_range(2);
+ auto hash = stack.pop_int_finite();
+ if (!hash->unsigned_fits_bits(256)) {
+ throw VmError{Excno::range_chk, "library hash must be non-negative"};
+ }
+ CellBuilder cb;
+ if (!(cb.store_ref_bool(get_actions(st)) // out_list$_ {n:#} prev:^(OutList n)
+ && cb.store_long_bool(0x26fa1dd4, 32) // action_change_library#26fa1dd4
+ && cb.store_long_bool(mode * 2, 8) // mode:(## 7) { mode <= 2 }
+ && cb.store_int256_bool(hash, 256, false))) { // libref:LibRef = OutAction;
+ throw VmError{Excno::cell_ov, "cannot serialize library hash into an output action cell"};
+ }
+ return install_output_action(st, cb.finalize());
+}
+
void register_ton_message_ops(OpcodeTable& cp0) {
using namespace std::placeholders;
cp0.insert(OpcodeInstr::mksimple(0xfb00, 16, "SENDRAWMSG", exec_send_raw_message))
.insert(OpcodeInstr::mksimple(0xfb02, 16, "RESERVERAW", std::bind(exec_reserve_raw, _1, 0)))
.insert(OpcodeInstr::mksimple(0xfb03, 16, "RESERVERAWX", std::bind(exec_reserve_raw, _1, 1)))
- .insert(OpcodeInstr::mksimple(0xfb04, 16, "SETCODE", exec_set_code));
+ .insert(OpcodeInstr::mksimple(0xfb04, 16, "SETCODE", exec_set_code))
+ .insert(OpcodeInstr::mksimple(0xfb06, 16, "SETLIBCODE", exec_set_lib_code))
+ .insert(OpcodeInstr::mksimple(0xfb07, 16, "CHANGELIB", exec_change_lib));
}
void register_ton_ops(OpcodeTable& cp0) {
diff --git a/crypto/vm/utils.cpp b/crypto/vm/utils.cpp
new file mode 100644
index 00000000..25e01f21
--- /dev/null
+++ b/crypto/vm/utils.cpp
@@ -0,0 +1,134 @@
+#include "utils.h"
+
+namespace vm {
+
+td::Result convert_stack_entry(td::Slice word);
+td::Result> parse_stack_entries_in(td::Slice& str, bool prefix_only = false);
+td::Result parse_stack_entry_in(td::Slice& str, bool prefix_only = false);
+
+namespace {
+
+td::Slice& skip_spaces(td::Slice& str, const char* delims) {
+ while (str.size() > 0 && strchr(delims, str[0])) {
+ str.remove_prefix(1);
+ }
+ return str;
+}
+
+td::Slice get_word(td::Slice& str, const char* delims, const char* specials) {
+ skip_spaces(str, delims);
+
+ size_t p = 0;
+ while (p < str.size() && !strchr(delims, str[p])) {
+ if (specials && strchr(specials, str[p])) {
+ if (!p) {
+ p++;
+ }
+ break;
+ }
+ p++;
+ }
+
+ td::Slice ret = str.copy().truncate(p);
+ str.remove_prefix(p);
+ return ret;
+}
+
+} // namespace
+
+td::Result parse_stack_entry_in(td::Slice& str, bool prefix_only) {
+ auto word = get_word(str, " \t", "[()]");
+ if (word.empty()) {
+ return td::Status::Error("stack value expected instead of end-of-line");
+ }
+ if (word.size() == 1 && (word[0] == '[' || word[0] == '(')) {
+ int expected = (word[0] == '(' ? ')' : ']');
+ TRY_RESULT(values, parse_stack_entries_in(str, true));
+ word = get_word(str, " \t", "[()]");
+ if (word.size() != 1 || word[0] != expected) {
+ return td::Status::Error("closing bracket expected");
+ }
+ vm::StackEntry value;
+ if (expected == ']') {
+ value = vm::StackEntry{std::move(values)};
+ } else {
+ value = vm::StackEntry::make_list(std::move(values));
+ }
+ if (prefix_only || (skip_spaces(str, " \t").size() == 0)) {
+ return value;
+ } else {
+ return td::Status::Error("extra data at the end");
+ }
+ } else {
+ return convert_stack_entry(word);
+ }
+}
+
+td::Result convert_stack_entry(td::Slice str) {
+ if (str.empty() || str.size() > 65535) {
+ return td::Status::Error("too long string");
+ }
+ int l = (int)str.size();
+ if (str[0] == '"') {
+ vm::CellBuilder cb;
+ if (l == 1 || str.back() != '"' || l >= 127 + 2 || !cb.store_bytes_bool(str.data() + 1, l - 2)) {
+ return td::Status::Error("incomplete (or too long) string");
+ }
+ return vm::StackEntry{vm::load_cell_slice_ref(cb.finalize())};
+ }
+ if (l >= 3 && (str[0] == 'x' || str[0] == 'b') && str[1] == '{' && str.back() == '}') {
+ unsigned char buff[128];
+ int bits =
+ (str[0] == 'x')
+ ? (int)td::bitstring::parse_bitstring_hex_literal(buff, sizeof(buff), str.begin() + 2, str.end() - 1)
+ : (int)td::bitstring::parse_bitstring_binary_literal(buff, sizeof(buff), str.begin() + 2, str.end() - 1);
+ if (bits < 0) {
+ return td::Status::Error("failed to parse raw b{...}/x{...} number");
+ }
+ return vm::StackEntry{
+ Ref{true, vm::CellBuilder().store_bits(td::ConstBitPtr{buff}, bits).finalize()}};
+ }
+ auto num = td::RefInt256{true};
+ auto& x = num.unique_write();
+ if (l >= 3 && str[0] == '0' && str[1] == 'x') {
+ if (x.parse_hex(str.data() + 2, l - 2) != l - 2) {
+ return td::Status::Error("failed to parse 0x... hex number");
+ }
+ } else if (l >= 4 && str[0] == '-' && str[1] == '0' && str[2] == 'x') {
+ if (x.parse_hex(str.data() + 3, l - 3) != l - 3) {
+ return td::Status::Error("failed to parse -0x... hex number");
+ }
+ x.negate().normalize();
+ } else if (!l || x.parse_dec(str.data(), l) != l) {
+ return td::Status::Error("failed to parse dec number");
+ }
+ return vm::StackEntry{std::move(num)};
+}
+
+td::Result> parse_stack_entries_in(td::Slice& str, bool prefix_only) {
+ std::vector ret;
+ while (!skip_spaces(str, " \t").empty()) {
+ auto c = str.copy();
+ auto word = get_word(c, " \t", "[()]");
+ if (word == "]" || word == ")") {
+ if (prefix_only) {
+ return ret;
+ } else {
+ return td::Status::Error("not paired closing bracket");
+ }
+ }
+ TRY_RESULT(value, parse_stack_entry_in(str, true));
+ ret.push_back(std::move(value));
+ }
+ return ret;
+}
+
+td::Result> parse_stack_entries(td::Slice str, bool prefix_only) {
+ return parse_stack_entries_in(str, prefix_only);
+}
+
+td::Result parse_stack_entry(td::Slice str, bool prefix_only) {
+ return parse_stack_entry_in(str, prefix_only);
+}
+
+} // namespace vm
diff --git a/crypto/vm/utils.h b/crypto/vm/utils.h
new file mode 100644
index 00000000..e1afe60c
--- /dev/null
+++ b/crypto/vm/utils.h
@@ -0,0 +1,11 @@
+#pragma once
+#include "stack.hpp"
+
+#include
+
+namespace vm {
+
+td::Result> parse_stack_entries(td::Slice str, bool prefix_only = false);
+td::Result parse_stack_entry(td::Slice str, bool prefix_only = false);
+
+} // namespace vm
diff --git a/doc/fiftbase.tex b/doc/fiftbase.tex
index a175f3a4..d39bf3fc 100644
--- a/doc/fiftbase.tex
+++ b/doc/fiftbase.tex
@@ -1848,6 +1848,7 @@ For example, the active prefix word {\tt B\{}, used for defining {\em Bytes\/} l
\item {\tt \$@?+} ($s$ $x$ -- $S$ $s'$ $-1$ or $s$ $0$), similar to {\tt \$@+}, but uses a flag to indicate failure instead of throwing an exception, cf.~\ptref{p:slice.ops}.
\item {\tt \$cmp} ($S$ $S'$ -- $x$), returns $0$ if strings $S$ and $S'$ are equal, $-1$ if $S$ is lexicographically less than $S'$, and $1$ if $S$ is lexicographically greater than $S'$, cf.~\ptref{p:string.cmp.ops}.
\item {\tt \$len} ($S$ -- $x$), computes the byte length (not the UTF-8 character length!) of a string, cf.~\ptref{p:string.ops}.
+\item {\tt \$pos} ($S$ $S'$ -- $x$ or $-1$), returns the position (byte offset)~$x$ of the first occurence of substring $S'$ in string~$S$ or $-1$.
\item {\tt \$reverse} ($S$ -- $S'$), reverses the order of UTF-8 characters in {\em String\/}~$S$. If $S$ is not a valid UTF-8 string, the return value is undefined and may be also invalid.
\item {\tt \%1<{<}} ($x$ $y$ -- $z$), computes $z:=x\bmod 2^y=x\&(2^y-1)$ for two {\em Integer\/}s $x$ and $0\leq y\leq 256$.
\item {\tt \underline{'} $\langle\textit{word-name}\rangle$} ( -- $e$), returns the execution token equal to the current (compile-time) definition of $\langle\textit{word-name}\rangle$, cf.~\ptref{p:blocks}. If the specified word is not found, throws an exception.
diff --git a/doc/tvm.tex b/doc/tvm.tex
index 26f7df24..73a2145b 100644
--- a/doc/tvm.tex
+++ b/doc/tvm.tex
@@ -2267,7 +2267,9 @@ The following primitives, which use the above conventions, are defined:
\item {\tt FB02} --- {\tt RAWRESERVE} ($x$ $y$ -- ), creates an output action which would reserve exactly $x$ nanograms (if $y=0$), at most $x$ nanograms (if $y=2$), or all but $x$ nanograms (if $y=1$ or $y=3$), from the remaining balance of the account. It is roughly equivalent to creating an outbound message carrying $x$ nanograms (or $b-x$ nanograms, where $b$ is the remaining balance) to oneself, so that the subsequent output actions would not be able to spend more money than the remainder. Bit $+2$ in $y$ means that the external action does not fail if the specified amount cannot be reserved; instead, all remaining balance is reserved. Currently $x$ must be a non-negative integer, and $y$ must be in the range $0\ldots 3$.
\item {\tt FB03} --- {\tt RAWRESERVEX} ($s$ $y$ -- ), similar to {\tt RAWRESERVE}, but accepts a {\em Slice $s$} with a {\em CurrencyCollection\/} as an argument. In this way currencies other than Grams can be reserved.
\item {\tt FB04} --- {\tt SETCODE} ($c$ -- ), creates an output action that would change this smart contract code to that given by {\em Cell\/}~$c$. Notice that this change will take effect only after the successful termination of the current run of the smart contract.
-\item {\tt FB05}--{\tt FB3F} --- Reserved for output action primitives.
+\item {\tt FB06} --- {\tt SETLIBCODE} ($c$ $x$ -- ), creates an output action that would modify the collection of this smart contract libraries by adding or removing library with code given in {\em Cell\/}~$c$. If $x=0$, the library is actually removed if it was previously present in the collection (if not, this action does nothing). If $x=1$, the library is added as a private library, and if $x=2$, the library is added as a public library (and becomes available to all smart contracts if the current smart contract resides in the masterchain); if the library was present in the collection before, its public/private status is changed according to $x$. Values of $x$ other than $0\ldots 2$ are invalid.
+\item {\tt FB07} --- {\tt CHANGELIB} ($h$ $x$ -- ), creates an output action similarly to {\tt SETLIBCODE}, but instead of the library code accepts its hash as an unsigned 256-bit integer $h$. If $x\neq0$ and the library with hash $h$ is absent from the library collection of this smart contract, this output action will fail.
+\item {\tt FB08}--{\tt FB3F} --- Reserved for output action primitives.
\end{itemize}
\mysubsection{Debug primitives}\label{p:prim.debug}
diff --git a/lite-client/lite-client.cpp b/lite-client/lite-client.cpp
index a4d3af51..d3041193 100644
--- a/lite-client/lite-client.cpp
+++ b/lite-client/lite-client.cpp
@@ -59,6 +59,7 @@
#include "vm/cp0.h"
#include "ton/ton-shard.h"
#include "openssl/rand.hpp"
+#include "crypto/vm/utils.h"
#if TD_DARWIN || TD_LINUX
#include
@@ -756,91 +757,6 @@ bool TestNode::parse_shard_id(ton::ShardIdFull& shard) {
return convert_shard_id(get_word(), shard) || set_error("cannot parse full shard identifier or prefix");
}
-bool TestNode::parse_stack_value(td::Slice str, vm::StackEntry& value) {
- if (str.empty() || str.size() > 65535) {
- return false;
- }
- int l = (int)str.size();
- if (str[0] == '"') {
- vm::CellBuilder cb;
- if (l == 1 || str.back() != '"' || l >= 127 + 2 || !cb.store_bytes_bool(str.data() + 1, l - 2)) {
- return false;
- }
- value = vm::StackEntry{vm::load_cell_slice_ref(cb.finalize())};
- return true;
- }
- if (l >= 3 && (str[0] == 'x' || str[0] == 'b') && str[1] == '{' && str.back() == '}') {
- unsigned char buff[128];
- int bits =
- (str[0] == 'x')
- ? (int)td::bitstring::parse_bitstring_hex_literal(buff, sizeof(buff), str.begin() + 2, str.end() - 1)
- : (int)td::bitstring::parse_bitstring_binary_literal(buff, sizeof(buff), str.begin() + 2, str.end() - 1);
- if (bits < 0) {
- return false;
- }
- value =
- vm::StackEntry{Ref{true, vm::CellBuilder().store_bits(td::ConstBitPtr{buff}, bits).finalize()}};
- return true;
- }
- auto num = td::RefInt256{true};
- auto& x = num.unique_write();
- if (l >= 3 && str[0] == '0' && str[1] == 'x') {
- if (x.parse_hex(str.data() + 2, l - 2) != l - 2) {
- return false;
- }
- } else if (l >= 4 && str[0] == '-' && str[1] == '0' && str[2] == 'x') {
- if (x.parse_hex(str.data() + 3, l - 3) != l - 3) {
- return false;
- }
- x.negate().normalize();
- } else if (!l || x.parse_dec(str.data(), l) != l) {
- return false;
- }
- value = vm::StackEntry{std::move(num)};
- return true;
-}
-
-bool TestNode::parse_stack_value(vm::StackEntry& value) {
- auto word = get_word_ext(" \t", "[()]");
- if (word.empty()) {
- return set_error("stack value expected instead of end-of-line");
- }
- if (word.size() == 1 && (word[0] == '[' || word[0] == '(')) {
- int expected = (word[0] == '(' ? ')' : ']');
- std::vector values;
- if (!parse_stack_values(values)) {
- return false;
- }
- word = get_word_ext(" \t", "[()]");
- if (word.size() != 1 || word[0] != expected) {
- return set_error("closing bracket expected");
- }
- if (expected == ']') {
- value = vm::StackEntry{std::move(values)};
- } else {
- value = vm::StackEntry::make_list(std::move(values));
- }
- return true;
- } else {
- return parse_stack_value(word, value) || set_error("invalid vm stack value");
- }
-}
-
-bool TestNode::parse_stack_values(std::vector& values) {
- values.clear();
- while (!seekeoln()) {
- if (cur() == ']' || cur() == ')') {
- break;
- }
- values.emplace_back();
- if (!parse_stack_value(values.back())) {
- values.pop_back();
- return false;
- }
- }
- return true;
-}
-
bool TestNode::set_error(std::string err_msg) {
return set_error(td::Status::Error(-1, err_msg));
}
@@ -912,6 +828,11 @@ bool TestNode::show_help(std::string command) {
"header\n"
"byutime \tLooks up a block by workchain, shard and creation time, and "
"shows its header\n"
+ "creatorstats [ []]\tLists block creator statistics by validator public "
+ "key\n"
+ "recentcreatorstats [ []]\tLists block creator statistics "
+ "updated after by validator public "
+ "key\n"
"known\tShows the list of all known block ids\n"
"privkey \tLoads a private key from file\n"
"help []\tThis help\n"
@@ -999,6 +920,12 @@ bool TestNode::do_parse_line() {
return parse_shard_id(shard) && parse_uint32(utime) && seekeoln() && lookup_block(shard, 4, utime);
} else if (word == "bylt") {
return parse_shard_id(shard) && parse_lt(lt) && seekeoln() && lookup_block(shard, 2, lt);
+ } else if (word == "creatorstats" || word == "recentcreatorstats") {
+ count = 1000;
+ int mode = (word == "recentcreatorstats" ? 4 : 0);
+ return parse_block_id_ext(blkid) && (!mode || parse_uint32(utime)) && (seekeoln() || parse_uint32(count)) &&
+ (seekeoln() || (parse_hash(hash) && (mode |= 1))) && seekeoln() &&
+ get_creator_stats(blkid, mode, count, hash, utime);
} else if (word == "known") {
return eoln() && show_new_blkids(true);
} else if (word == "quit" && eoln()) {
@@ -1090,13 +1017,12 @@ bool TestNode::get_account_state(ton::WorkchainId workchain, ton::StdSmcAddress
bool TestNode::parse_run_method(ton::WorkchainId workchain, ton::StdSmcAddress addr, ton::BlockIdExt ref_blkid,
std::string method_name) {
- std::vector params;
- if (!parse_stack_values(params)) {
- return set_error("cannot parse list of TVM stack values");
- }
- if (!seekeoln()) {
- return set_error("extra characters after a list of TVM stack values");
+ auto R = vm::parse_stack_entries(td::Slice(parse_ptr_, parse_end_));
+ if (R.is_error()) {
+ return set_error(R.move_as_error().to_string());
}
+ parse_ptr_ = parse_end_;
+ auto params = R.move_as_ok();
if (!ref_blkid.is_valid()) {
return set_error("must obtain last block information before making other queries");
}
@@ -2236,6 +2162,107 @@ void TestNode::got_block_proof(ton::BlockIdExt from, ton::BlockIdExt to, int mod
show_new_blkids();
}
+bool TestNode::get_creator_stats(ton::BlockIdExt blkid, int mode, unsigned req_count, ton::Bits256 start_after,
+ ton::UnixTime min_utime) {
+ if (!(ready_ && !client_.empty())) {
+ return set_error("server connection not ready");
+ }
+ if (!blkid.is_masterchain_ext()) {
+ return set_error("only masterchain blocks contain block creator statistics");
+ }
+ if (!(mode & 1)) {
+ start_after.set_zero();
+ }
+ auto b = ton::serialize_tl_object(ton::create_tl_object(
+ mode, ton::create_tl_lite_block_id(blkid), req_count, start_after, min_utime),
+ true);
+ LOG(INFO) << "requesting up to " << req_count << " block creator stats records with respect to masterchain block "
+ << blkid.to_str() << " starting from validator public key " << start_after.to_hex() << " created after "
+ << min_utime << " (mode=" << mode << ")";
+ return envelope_send_query(std::move(b), [ Self = actor_id(this), mode, blkid, req_count, start_after,
+ min_utime ](td::Result R) {
+ if (R.is_error()) {
+ return;
+ }
+ auto F = ton::fetch_tl_object(R.move_as_ok(), true);
+ if (F.is_error()) {
+ LOG(ERROR) << "cannot parse answer to liteServer.getValidatorStats";
+ } else {
+ auto f = F.move_as_ok();
+ td::actor::send_closure_later(Self, &TestNode::got_creator_stats, blkid, ton::create_block_id(f->id_), mode,
+ f->mode_, start_after, min_utime, std::move(f->state_proof_),
+ std::move(f->data_proof_), f->count_, req_count, f->complete_);
+ }
+ });
+}
+
+void TestNode::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) {
+ LOG(INFO) << "got answer to getValidatorStats query: " << count << " records out of " << req_count << ", "
+ << (complete ? "complete" : "incomplete");
+ if (!blkid.is_masterchain_ext()) {
+ LOG(ERROR) << "reference block " << blkid.to_str()
+ << " for block creator statistics is not a valid masterchain block";
+ return;
+ }
+ if (count > req_count) {
+ LOG(ERROR) << "obtained " << count << " answers to getValidatorStats query, but only " << req_count
+ << " were requested";
+ return;
+ }
+ if (blkid != req_blkid) {
+ LOG(ERROR) << "answer to getValidatorStats refers to masterchain block " << blkid.to_str()
+ << " different from requested " << req_blkid.to_str();
+ return;
+ }
+ auto R = block::check_extract_state_proof(blkid, state_proof.as_slice(), data_proof.as_slice());
+ if (R.is_error()) {
+ LOG(ERROR) << "masterchain state proof for " << blkid.to_str() << " is invalid : " << R.move_as_error().to_string();
+ return;
+ }
+ bool allow_eq = (mode & 3) != 1;
+ ton::Bits256 key{start_after};
+ std::ostringstream os;
+ try {
+ auto dict = block::get_block_create_stats_dict(R.move_as_ok());
+ if (!dict) {
+ LOG(ERROR) << "cannot extract BlockCreateStats from mc state";
+ return;
+ }
+ for (int i = 0; i < count + (int)complete; i++) {
+ auto v = dict->lookup_nearest_key(key, true, allow_eq);
+ if (v.is_null()) {
+ if (i != count) {
+ LOG(ERROR) << "could fetch only " << i << " CreatorStats entries out of " << count
+ << " declared in answer to getValidatorStats";
+ return;
+ }
+ break;
+ }
+ block::DiscountedCounter mc_cnt, shard_cnt;
+ if (!block::unpack_CreatorStats(std::move(v), mc_cnt, shard_cnt)) {
+ LOG(ERROR) << "invalid CreatorStats record with key " << key.to_hex();
+ return;
+ }
+ if (mc_cnt.modified_since(min_utime) || shard_cnt.modified_since(min_utime)) {
+ os << key.to_hex() << " mc_cnt:" << mc_cnt << " shard_cnt:" << shard_cnt << std::endl;
+ }
+ allow_eq = false;
+ }
+ if (complete) {
+ os << "(complete)" << std::endl;
+ } else {
+ os << "(incomplete, repeat query from " << key.to_hex() << " )" << std::endl;
+ }
+ td::TerminalIO::out() << os.str();
+ } catch (vm::VmError& err) {
+ LOG(ERROR) << "error while traversing block creator stats: " << err.get_msg();
+ } catch (vm::VmVirtError& err) {
+ LOG(ERROR) << "virtualization error while traversing block creator stats: " << err.get_msg();
+ }
+}
+
int main(int argc, char* argv[]) {
SET_VERBOSITY_LEVEL(verbosity_INFO);
td::set_default_failure_signal_handler();
diff --git a/lite-client/lite-client.h b/lite-client/lite-client.h
index bb4f31df..f016c96e 100644
--- a/lite-client/lite-client.h
+++ b/lite-client/lite-client.h
@@ -153,6 +153,12 @@ class TestNode : public td::actor::Actor {
std::vector trans, td::BufferSlice proof);
bool get_block_proof(ton::BlockIdExt from, ton::BlockIdExt to, int mode);
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);
+ // parser
bool do_parse_line();
bool show_help(std::string command);
td::Slice get_word(char delim = ' ');
diff --git a/tddb/td/db/MemoryKeyValue.h b/tddb/td/db/MemoryKeyValue.h
index f2c261fe..f2c04713 100644
--- a/tddb/td/db/MemoryKeyValue.h
+++ b/tddb/td/db/MemoryKeyValue.h
@@ -38,11 +38,7 @@ class MemoryKeyValue : public KeyValue {
std::string stats() const override;
private:
- class Cmp : public std::less<> {
- public:
- using is_transparent = void;
- };
- std::map map_;
+ std::map> map_;
int64 get_count_{0};
};
} // namespace td
diff --git a/test/test-adnl.cpp b/test/test-adnl.cpp
index 88758fc7..1b415644 100644
--- a/test/test-adnl.cpp
+++ b/test/test-adnl.cpp
@@ -42,6 +42,7 @@ int main() {
SET_VERBOSITY_LEVEL(verbosity_INFO);
std::string db_root_ = "tmp-ee";
+ td::rmrf(db_root_).ignore();
td::mkdir(db_root_).ensure();
td::set_default_failure_signal_handler().ensure();
diff --git a/test/test-catchain.cpp b/test/test-catchain.cpp
index 49a1c196..0e9bd207 100644
--- a/test/test-catchain.cpp
+++ b/test/test-catchain.cpp
@@ -217,6 +217,7 @@ int main(int argc, char *argv[]) {
td::set_default_failure_signal_handler().ensure();
std::string db_root_ = "tmp-ee";
+ td::rmrf(db_root_).ignore();
td::mkdir(db_root_).ensure();
td::set_default_failure_signal_handler().ensure();
diff --git a/test/test-dht.cpp b/test/test-dht.cpp
index 8bbb8d18..61a49fe6 100644
--- a/test/test-dht.cpp
+++ b/test/test-dht.cpp
@@ -42,6 +42,7 @@ int main() {
SET_VERBOSITY_LEVEL(verbosity_INFO);
std::string db_root_ = "tmp-ee";
+ td::rmrf(db_root_).ignore();
td::mkdir(db_root_).ensure();
td::set_default_failure_signal_handler().ensure();
diff --git a/test/test-rldp.cpp b/test/test-rldp.cpp
index 90d7ba04..0d1730d3 100644
--- a/test/test-rldp.cpp
+++ b/test/test-rldp.cpp
@@ -41,6 +41,7 @@ int main() {
SET_VERBOSITY_LEVEL(verbosity_INFO);
std::string db_root_ = "tmp-ee";
+ td::rmrf(db_root_).ignore();
td::mkdir(db_root_).ensure();
td::set_default_failure_signal_handler().ensure();
diff --git a/test/test-validator-session-state.cpp b/test/test-validator-session-state.cpp
index 877347bb..5c28380e 100644
--- a/test/test-validator-session-state.cpp
+++ b/test/test-validator-session-state.cpp
@@ -318,8 +318,8 @@ int main() {
CHECK(!found);
auto vec = s->choose_blocks_to_approve(desc, i);
CHECK(vec.size() == 1);
- CHECK(vec[1] == nullptr);
- CHECK(ton::validatorsession::SentBlock::get_block_id(vec[1]) == ton::validatorsession::skip_round_candidate_id());
+ CHECK(vec[0] == nullptr);
+ CHECK(ton::validatorsession::SentBlock::get_block_id(vec[0]) == ton::validatorsession::skip_round_candidate_id());
}
for (td::uint32 i = 0; i < total_nodes; i++) {
diff --git a/tl/generate/scheme/lite_api.tl b/tl/generate/scheme/lite_api.tl
index 9604624a..0dd3fa22 100644
--- a/tl/generate/scheme/lite_api.tl
+++ b/tl/generate/scheme/lite_api.tl
@@ -48,6 +48,7 @@ liteServer.blockLinkBack to_key_block:Bool from:tonNode.blockIdExt to:tonNode.bl
liteServer.blockLinkForward to_key_block:Bool from:tonNode.blockIdExt to:tonNode.blockIdExt dest_proof:bytes config_proof:bytes signatures:liteServer.SignatureSet = liteServer.BlockLink;
liteServer.partialBlockProof complete:Bool from:tonNode.blockIdExt to:tonNode.blockIdExt steps:(vector liteServer.BlockLink) = liteServer.PartialBlockProof;
liteServer.configInfo mode:# id:tonNode.blockIdExt state_proof:bytes config_proof:bytes = liteServer.ConfigInfo;
+liteServer.validatorStats mode:# id:tonNode.blockIdExt count:int complete:Bool state_proof:bytes data_proof:bytes = liteServer.ValidatorStats;
liteServer.debug.verbosity value:int = liteServer.debug.Verbosity;
@@ -71,6 +72,7 @@ liteServer.listBlockTransactions id:tonNode.blockIdExt mode:# count:# after:mode
liteServer.getBlockProof mode:# known_block:tonNode.blockIdExt target_block:mode.0?tonNode.blockIdExt = liteServer.PartialBlockProof;
liteServer.getConfigAll mode:# id:tonNode.blockIdExt = liteServer.ConfigInfo;
liteServer.getConfigParams mode:# id:tonNode.blockIdExt param_list:(vector int) = liteServer.ConfigInfo;
+liteServer.getValidatorStats#091a58bc mode:# id:tonNode.blockIdExt limit:int start_after:mode.0?int256 modified_after:mode.2?int = liteServer.ValidatorStats;
liteServer.queryPrefix = Object;
liteServer.query data:bytes = Object;
diff --git a/tl/generate/scheme/lite_api.tlo b/tl/generate/scheme/lite_api.tlo
index 9b01c0d8..02b507f4 100644
Binary files a/tl/generate/scheme/lite_api.tlo and b/tl/generate/scheme/lite_api.tlo differ
diff --git a/ton/ton-types.h b/ton/ton-types.h
index d4c7e33c..4743253a 100644
--- a/ton/ton-types.h
+++ b/ton/ton-types.h
@@ -25,6 +25,8 @@
#include "td/utils/UInt.h"
#include "td/utils/misc.h"
+#include
+
namespace ton {
using WorkchainId = td::int32;
@@ -141,7 +143,7 @@ struct AccountIdPrefixFull {
std::string to_str() const {
char buffer[64];
return std::string{
- buffer, (unsigned)snprintf(buffer, 63, "(%d,%016llx)", workchain, (unsigned long long)account_id_prefix)};
+ buffer, (unsigned)snprintf(buffer, 63, "(%d,%016llx)", workchain, static_cast(account_id_prefix))};
}
};
@@ -204,8 +206,8 @@ struct BlockId {
}
std::string to_str() const {
char buffer[64];
- return std::string{buffer,
- (unsigned)snprintf(buffer, 63, "(%d,%016llx,%u)", workchain, (unsigned long long)shard, seqno)};
+ return std::string{buffer, (unsigned)snprintf(buffer, 63, "(%d,%016llx,%u)", workchain,
+ static_cast(shard), seqno)};
}
};
@@ -282,7 +284,7 @@ struct BlockIdExt {
BlockIdExt v;
char rh[65];
char fh[65];
- auto r = sscanf(s.begin(), "(%d,%lx,%u):%64s:%64s", &v.id.workchain, &v.id.shard, &v.id.seqno, rh, fh);
+ auto r = sscanf(s.begin(), "(%d,%" SCNu64 ",%u):%64s:%64s", &v.id.workchain, &v.id.shard, &v.id.seqno, rh, fh);
if (r < 5) {
return td::Status::Error("failed to parse block id");
}
diff --git a/tonlib/test/online.cpp b/tonlib/test/online.cpp
index 65c6f6f8..0e25dd66 100644
--- a/tonlib/test/online.cpp
+++ b/tonlib/test/online.cpp
@@ -451,6 +451,7 @@ void test_multisig(Client& client, const Wallet& giver_wallet) {
int n = 16;
int k = 10;
+ td::uint32 wallet_id = 7;
std::vector private_keys;
for (int i = 0; i < n; i++) {
private_keys.push_back(td::Ed25519::generate_private_key().move_as_ok());
@@ -458,6 +459,7 @@ void test_multisig(Client& client, const Wallet& giver_wallet) {
auto ms = ton::MultisigWallet::create();
auto init_data = ms->create_init_data(
+ wallet_id,
td::transform(private_keys, [](const auto& pk) { return pk.get_public_key().move_as_ok().as_octet_string(); }),
k);
ms = ton::MultisigWallet::create(init_data);
@@ -472,7 +474,7 @@ void test_multisig(Client& client, const Wallet& giver_wallet) {
ton::GenericAccount::store_int_message(icb, block::StdAddress::parse(giver_wallet.address).move_as_ok(), 1);
icb.store_bytes("\0\0\0\0", 4);
vm::CellString::store(icb, "Greatings from multisig", 35 * 8).ensure();
- ton::MultisigWallet::QueryBuilder qb(-1 - i, icb.finalize());
+ ton::MultisigWallet::QueryBuilder qb(wallet_id, -1 - i, icb.finalize());
for (int i = 0; i < k - 1; i++) {
qb.sign(i, private_keys[i]);
}
diff --git a/tonlib/tonlib/KeyValue.cpp b/tonlib/tonlib/KeyValue.cpp
index ccf9a383..5943ea47 100644
--- a/tonlib/tonlib/KeyValue.cpp
+++ b/tonlib/tonlib/KeyValue.cpp
@@ -106,11 +106,7 @@ class KeyValueInmemory : public KeyValue {
}
private:
- class Cmp : public std::less<> {
- public:
- using is_transparent = void;
- };
- std::map map_;
+ std::map> map_;
};
} // namespace detail
diff --git a/validator-engine/validator-engine.cpp b/validator-engine/validator-engine.cpp
index 542ecf00..c110abbe 100644
--- a/validator-engine/validator-engine.cpp
+++ b/validator-engine/validator-engine.cpp
@@ -648,12 +648,13 @@ td::Result Config::config_del_gc(ton::PublicKeyHash key) {
class ValidatorElectionBidCreator : public td::actor::Actor {
public:
ValidatorElectionBidCreator(td::uint32 date, std::string addr, std::string wallet, std::string dir,
- td::actor::ActorId engine,
+ std::vector old_keys, td::actor::ActorId engine,
td::actor::ActorId