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

TON Storage utilities (#564)

* Rename chunk to piece in MerkleTree for consistency

* Refactor PeerManager

* Make PeerState thread-safe

* Download torrent by hash

* First version of storage daemon

* Download torrents partially

* Improve storing and loading torrent state in DB

* Rewrite MerkleTree

* "Remove torrent" in storage daemon

* Process errors, fix bugs in storage

* Move TonlibClientWrapper from rldp-http-proxy to tonlib

* Initial version of storage provider

* Move interaction with contracts to smc-util

* Improve TonlibClientWrapper interface

* Various improvements in storage provider

* Fix TorrentCreator.cpp

* Improve interface for partial download

* Client mode in storage-daemon

* Improve interface of storage-daemon-cli

* Fix calculating speed, show peers in storage-daemon

* Use permanent adnl id in storage daemon

* Fix sending large "storage.addUpdate" messages

* Improve printing torrents in cli

* Update tlo

* Fix RldpSender::on_ack

* Update storage provider

* Add "address" parameter to get-provider-params

* Allow client to close storage contract

* Limit torrent description

* Add more logs to storage provider

* smc.forget tonlib method

* Use smc.forget in storage daemon

* Optimize sending messages in smc-util.cpp

* Fix verbosity, remove excessive logs

* Json output in storage-daemon-cli

* Update storage provider contracts

* Fix rldp2 acks

* Change verbosity of logs in rldp2

* Update help and output of commands and in storage-daemon-cli

Co-authored-by: SpyCheese <mikle98@yandex.ru>
This commit is contained in:
EmelyanenkoK 2022-12-22 12:24:13 +03:00 committed by GitHub
parent 434dc487a4
commit 360ef54e6b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
75 changed files with 8872 additions and 1148 deletions

View file

@ -0,0 +1,32 @@
cmake_minimum_required(VERSION 3.0.2 FATAL_ERROR)
add_executable(embed-provider-code smartcont/embed-provider-code.cpp)
add_custom_command(
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMAND embed-provider-code smartcont/storage-provider-code.boc smartcont/provider-code.h
COMMENT "Generate provider-code.h"
DEPENDS embed-provider-code smartcont/storage-provider-code.boc
OUTPUT smartcont/provider-code.h
)
set(STORAGE_DAEMON_SOURCE
storage-daemon.cpp
StorageManager.h
StorageManager.cpp
StorageProvider.h
StorageProvider.cpp
smc-util.h
smc-util.cpp
smartcont/provider-code.h)
set(STORAGE_DAEMON_CLI_SOURCE
storage-daemon-cli.cpp
)
add_executable(storage-daemon ${STORAGE_DAEMON_SOURCE})
target_link_libraries(storage-daemon storage overlay tdutils tdactor adnl tl_api dht
rldp rldp2 fift-lib memprof git tonlib)
add_executable(storage-daemon-cli ${STORAGE_DAEMON_CLI_SOURCE})
target_link_libraries(storage-daemon-cli tdutils tdactor adnllite tl_api tl_lite_api tl-lite-utils ton_block terminal git)

View file

@ -0,0 +1,268 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
*/
#include "StorageManager.h"
#include "td/utils/filesystem.h"
#include "td/utils/port/path.h"
#include "td/db/RocksDb.h"
#include "td/actor/MultiPromise.h"
static overlay::OverlayIdFull get_overlay_id(td::Bits256 hash) {
td::BufferSlice hash_str(hash.as_slice());
return overlay::OverlayIdFull(std::move(hash_str));
}
StorageManager::StorageManager(adnl::AdnlNodeIdShort local_id, std::string db_root, td::unique_ptr<Callback> callback,
bool client_mode, td::actor::ActorId<adnl::Adnl> adnl,
td::actor::ActorId<ton_rldp::Rldp> rldp, td::actor::ActorId<overlay::Overlays> overlays)
: local_id_(local_id)
, db_root_(std::move(db_root))
, callback_(std::move(callback))
, client_mode_(client_mode)
, adnl_(std::move(adnl))
, rldp_(std::move(rldp))
, overlays_(std::move(overlays)) {
}
void StorageManager::start_up() {
CHECK(db_root_ != "");
td::mkdir(db_root_).ensure();
db_root_ = td::realpath(db_root_).move_as_ok();
td::mkdir(db_root_ + "/torrent-db").ensure();
td::mkdir(db_root_ + "/torrent-files").ensure();
LOG(INFO) << "Starting Storage manager. DB = " << db_root_;
db_ = std::make_shared<db::DbType>(
std::make_shared<td::RocksDb>(td::RocksDb::open(db_root_ + "/torrent-db").move_as_ok()));
db::db_get<ton_api::storage_db_torrentList>(
*db_, create_hash_tl_object<ton_api::storage_db_key_torrentList>(), true,
[SelfId = actor_id(this)](td::Result<tl_object_ptr<ton_api::storage_db_torrentList>> R) {
std::vector<td::Bits256> torrents;
if (R.is_error()) {
LOG(ERROR) << "Failed to load torrent list from db: " << R.move_as_error();
} else {
auto r = R.move_as_ok();
if (r != nullptr) {
torrents = std::move(r->torrents_);
}
}
td::actor::send_closure(SelfId, &StorageManager::load_torrents_from_db, std::move(torrents));
});
}
void StorageManager::load_torrents_from_db(std::vector<td::Bits256> torrents) {
td::MultiPromise mp;
auto ig = mp.init_guard();
ig.add_promise([SelfId = actor_id(this)](td::Result<td::Unit> R) {
td::actor::send_closure(SelfId, &StorageManager::after_load_torrents_from_db);
});
for (auto hash : torrents) {
CHECK(!torrents_.count(hash))
auto& entry = torrents_[hash];
entry.peer_manager = td::actor::create_actor<PeerManager>("PeerManager", local_id_, get_overlay_id(hash),
client_mode_, overlays_, adnl_, rldp_);
NodeActor::load_from_db(
db_, hash, create_callback(hash, entry.closing_state), PeerManager::create_callback(entry.peer_manager.get()),
[SelfId = actor_id(this), hash,
promise = ig.get_promise()](td::Result<td::actor::ActorOwn<NodeActor>> R) mutable {
td::actor::send_closure(SelfId, &StorageManager::loaded_torrent_from_db, hash, std::move(R));
promise.set_result(td::Unit());
});
}
}
void StorageManager::loaded_torrent_from_db(td::Bits256 hash, td::Result<td::actor::ActorOwn<NodeActor>> R) {
if (R.is_error()) {
LOG(ERROR) << "Failed to load torrent " << hash.to_hex() << " from db: " << R.move_as_error();
torrents_.erase(hash);
} else {
auto it = torrents_.find(hash);
CHECK(it != torrents_.end());
it->second.actor = R.move_as_ok();
LOG(INFO) << "Loaded torrent " << hash.to_hex() << " from db";
}
}
void StorageManager::after_load_torrents_from_db() {
LOG(INFO) << "Finished loading torrents from db (" << torrents_.size() << " torrents)";
db_store_torrent_list();
callback_->on_ready();
}
td::unique_ptr<NodeActor::Callback> StorageManager::create_callback(
td::Bits256 hash, std::shared_ptr<TorrentEntry::ClosingState> closing_state) {
class Callback : public NodeActor::Callback {
public:
Callback(td::actor::ActorId<StorageManager> id, td::Bits256 hash,
std::shared_ptr<TorrentEntry::ClosingState> closing_state)
: id_(std::move(id)), hash_(hash), closing_state_(std::move(closing_state)) {
}
void on_completed() override {
}
void on_closed(Torrent torrent) override {
CHECK(torrent.get_hash() == hash_);
td::actor::send_closure(id_, &StorageManager::on_torrent_closed, std::move(torrent), closing_state_);
}
private:
td::actor::ActorId<StorageManager> id_;
td::Bits256 hash_;
std::shared_ptr<TorrentEntry::ClosingState> closing_state_;
};
return td::make_unique<Callback>(actor_id(this), hash, std::move(closing_state));
}
void StorageManager::add_torrent(Torrent torrent, bool start_download, td::Promise<td::Unit> promise) {
TRY_STATUS_PROMISE(promise, add_torrent_impl(std::move(torrent), start_download));
db_store_torrent_list();
promise.set_result(td::Unit());
}
td::Status StorageManager::add_torrent_impl(Torrent torrent, bool start_download) {
td::Bits256 hash = torrent.get_hash();
if (torrents_.count(hash)) {
return td::Status::Error("Cannot add torrent: duplicate hash");
}
TorrentEntry& entry = torrents_[hash];
entry.hash = hash;
entry.peer_manager = td::actor::create_actor<PeerManager>("PeerManager", local_id_, get_overlay_id(hash),
client_mode_, overlays_, adnl_, rldp_);
auto context = PeerManager::create_callback(entry.peer_manager.get());
LOG(INFO) << "Added torrent " << hash.to_hex() << " , root_dir = " << torrent.get_root_dir();
entry.actor =
td::actor::create_actor<NodeActor>("Node", 1, std::move(torrent), create_callback(hash, entry.closing_state),
std::move(context), db_, start_download);
return td::Status::OK();
}
void StorageManager::add_torrent_by_meta(TorrentMeta meta, std::string root_dir, bool start_download,
td::Promise<td::Unit> promise) {
td::Bits256 hash(meta.info.get_hash());
Torrent::Options options;
options.root_dir = root_dir.empty() ? db_root_ + "/torrent-files/" + hash.to_hex() : root_dir;
TRY_RESULT_PROMISE(promise, torrent, Torrent::open(std::move(options), std::move(meta)));
add_torrent(std::move(torrent), start_download, std::move(promise));
}
void StorageManager::add_torrent_by_hash(td::Bits256 hash, std::string root_dir, bool start_download,
td::Promise<td::Unit> promise) {
Torrent::Options options;
options.root_dir = root_dir.empty() ? db_root_ + "/torrent-files/" + hash.to_hex() : root_dir;
TRY_RESULT_PROMISE(promise, torrent, Torrent::open(std::move(options), hash));
add_torrent(std::move(torrent), start_download, std::move(promise));
}
void StorageManager::set_active_download(td::Bits256 hash, bool active, td::Promise<td::Unit> promise) {
TRY_RESULT_PROMISE(promise, entry, get_torrent(hash));
td::actor::send_closure(entry->actor, &NodeActor::set_should_download, active);
promise.set_result(td::Unit());
}
void StorageManager::with_torrent(td::Bits256 hash, td::Promise<NodeActor::NodeState> promise) {
TRY_RESULT_PROMISE(promise, entry, get_torrent(hash));
td::actor::send_closure(entry->actor, &NodeActor::with_torrent, std::move(promise));
}
void StorageManager::get_all_torrents(td::Promise<std::vector<td::Bits256>> promise) {
std::vector<td::Bits256> result;
for (const auto& p : torrents_) {
result.push_back(p.first);
}
promise.set_result(std::move(result));
}
void StorageManager::db_store_torrent_list() {
std::vector<td::Bits256> torrents;
for (const auto& p : torrents_) {
torrents.push_back(p.first);
}
db_->set(create_hash_tl_object<ton_api::storage_db_key_torrentList>(),
create_serialize_tl_object<ton_api::storage_db_torrentList>(std::move(torrents)),
[](td::Result<td::Unit> R) {
if (R.is_error()) {
LOG(ERROR) << "Failed to save torrent list to db: " << R.move_as_error();
}
});
}
void StorageManager::set_all_files_priority(td::Bits256 hash, td::uint8 priority, td::Promise<bool> promise) {
TRY_RESULT_PROMISE(promise, entry, get_torrent(hash));
td::actor::send_closure(entry->actor, &NodeActor::set_all_files_priority, priority, std::move(promise));
}
void StorageManager::set_file_priority_by_idx(td::Bits256 hash, size_t idx, td::uint8 priority,
td::Promise<bool> promise) {
TRY_RESULT_PROMISE(promise, entry, get_torrent(hash));
td::actor::send_closure(entry->actor, &NodeActor::set_file_priority_by_idx, idx, priority, std::move(promise));
}
void StorageManager::set_file_priority_by_name(td::Bits256 hash, std::string name, td::uint8 priority,
td::Promise<bool> promise) {
TRY_RESULT_PROMISE(promise, entry, get_torrent(hash));
td::actor::send_closure(entry->actor, &NodeActor::set_file_priority_by_name, std::move(name), priority,
std::move(promise));
}
void StorageManager::remove_torrent(td::Bits256 hash, bool remove_files, td::Promise<td::Unit> promise) {
TRY_RESULT_PROMISE(promise, entry, get_torrent(hash));
LOG(INFO) << "Removing torrent " << hash.to_hex();
entry->closing_state->removing = true;
entry->closing_state->remove_files = remove_files;
entry->closing_state->promise = std::move(promise);
torrents_.erase(hash);
db_store_torrent_list();
}
void StorageManager::load_from(td::Bits256 hash, td::optional<TorrentMeta> meta, std::string files_path,
td::Promise<td::Unit> promise) {
TRY_RESULT_PROMISE(promise, entry, get_torrent(hash));
td::actor::send_closure(entry->actor, &NodeActor::load_from, std::move(meta), std::move(files_path),
std::move(promise));
}
void StorageManager::on_torrent_closed(Torrent torrent, std::shared_ptr<TorrentEntry::ClosingState> closing_state) {
if (!closing_state->removing) {
return;
}
if (closing_state->remove_files && torrent.inited_header()) {
size_t files_count = torrent.get_files_count().unwrap();
for (size_t i = 0; i < files_count; ++i) {
std::string path = torrent.get_file_path(i);
td::unlink(path).ignore();
// TODO: Check errors, remove empty directories
}
}
td::rmrf(db_root_ + "/torrent-files/" + torrent.get_hash().to_hex()).ignore();
NodeActor::cleanup_db(db_, torrent.get_hash(),
[promise = std::move(closing_state->promise)](td::Result<td::Unit> R) mutable {
if (R.is_error()) {
LOG(ERROR) << "Failed to cleanup database: " << R.move_as_error();
}
promise.set_result(td::Unit());
});
}
void StorageManager::wait_for_completion(td::Bits256 hash, td::Promise<td::Unit> promise) {
TRY_RESULT_PROMISE(promise, entry, get_torrent(hash));
td::actor::send_closure(entry->actor, &NodeActor::wait_for_completion, std::move(promise));
}
void StorageManager::get_peers_info(td::Bits256 hash,
td::Promise<tl_object_ptr<ton_api::storage_daemon_peerList>> promise) {
TRY_RESULT_PROMISE(promise, entry, get_torrent(hash));
td::actor::send_closure(entry->actor, &NodeActor::get_peers_info, std::move(promise));
}

View file

@ -0,0 +1,107 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "td/actor/actor.h"
#include "adnl/adnl.h"
#include "rldp2/rldp.h"
#include "overlay/overlays.h"
#include "storage/PeerManager.h"
#include "storage/db.h"
using namespace ton;
class StorageManager : public td::actor::Actor {
public:
class Callback {
public:
virtual ~Callback() = default;
virtual void on_ready() = 0;
};
StorageManager(adnl::AdnlNodeIdShort local_id, std::string db_root, td::unique_ptr<Callback> callback,
bool client_mode, td::actor::ActorId<adnl::Adnl> adnl, td::actor::ActorId<ton_rldp::Rldp> rldp,
td::actor::ActorId<overlay::Overlays> overlays);
void start_up() override;
void add_torrent(Torrent torrent, bool start_download, td::Promise<td::Unit> promise);
void add_torrent_by_meta(TorrentMeta meta, std::string root_dir, bool start_download, td::Promise<td::Unit> promise);
void add_torrent_by_hash(td::Bits256 hash, std::string root_dir, bool start_download, td::Promise<td::Unit> promise);
void set_active_download(td::Bits256 hash, bool active, td::Promise<td::Unit> promise);
void with_torrent(td::Bits256 hash, td::Promise<NodeActor::NodeState> promise);
void get_all_torrents(td::Promise<std::vector<td::Bits256>> promise);
void set_all_files_priority(td::Bits256 hash, td::uint8 priority, td::Promise<bool> promise);
void set_file_priority_by_idx(td::Bits256 hash, size_t idx, td::uint8 priority, td::Promise<bool> promise);
void set_file_priority_by_name(td::Bits256 hash, std::string name, td::uint8 priority, td::Promise<bool> promise);
void remove_torrent(td::Bits256 hash, bool remove_files, td::Promise<td::Unit> promise);
void load_from(td::Bits256 hash, td::optional<TorrentMeta> meta, std::string files_path,
td::Promise<td::Unit> promise);
void wait_for_completion(td::Bits256 hash, td::Promise<td::Unit> promise);
void get_peers_info(td::Bits256 hash, td::Promise<tl_object_ptr<ton_api::storage_daemon_peerList>> promise);
private:
adnl::AdnlNodeIdShort local_id_;
std::string db_root_;
td::unique_ptr<Callback> callback_;
bool client_mode_ = false;
td::actor::ActorId<adnl::Adnl> adnl_;
td::actor::ActorId<ton_rldp::Rldp> rldp_;
td::actor::ActorId<overlay::Overlays> overlays_;
std::shared_ptr<db::DbType> db_;
struct TorrentEntry {
td::Bits256 hash;
td::actor::ActorOwn<NodeActor> actor;
td::actor::ActorOwn<PeerManager> peer_manager;
struct ClosingState {
bool removing = false;
td::Promise<td::Unit> promise;
bool remove_files = false;
};
std::shared_ptr<ClosingState> closing_state = std::make_shared<ClosingState>();
};
std::map<td::Bits256, TorrentEntry> torrents_;
td::Status add_torrent_impl(Torrent torrent, bool start_download);
td::Result<TorrentEntry*> get_torrent(td::Bits256 hash) {
auto it = torrents_.find(hash);
if (it == torrents_.end()) {
return td::Status::Error("No such torrent");
}
return &it->second;
}
td::unique_ptr<NodeActor::Callback> create_callback(td::Bits256 hash,
std::shared_ptr<TorrentEntry::ClosingState> closing_state);
void load_torrents_from_db(std::vector<td::Bits256> torrents);
void loaded_torrent_from_db(td::Bits256 hash, td::Result<td::actor::ActorOwn<NodeActor>> R);
void after_load_torrents_from_db();
void db_store_torrent_list();
void on_torrent_closed(Torrent torrent, std::shared_ptr<TorrentEntry::ClosingState> closing_state);
};

View file

@ -0,0 +1,840 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
*/
#include "StorageProvider.h"
#include "td/db/RocksDb.h"
#include "td/utils/JsonBuilder.h"
#include "auto/tl/ton_api_json.h"
#include "td/utils/port/path.h"
#include "block/block-auto.h"
#include "common/delay.h"
#include "td/actor/MultiPromise.h"
td::Result<ProviderParams> ProviderParams::create(const tl_object_ptr<ton_api::storage_daemon_provider_params>& obj) {
ProviderParams p;
p.accept_new_contracts = obj->accept_new_contracts_;
p.rate_per_mb_day = td::string_to_int256(obj->rate_per_mb_day_);
if (p.rate_per_mb_day.is_null() || p.rate_per_mb_day->sgn() < 0) {
return td::Status::Error("Invalid rate");
}
p.max_span = obj->max_span_;
p.minimal_file_size = obj->minimal_file_size_;
p.maximal_file_size = obj->maximal_file_size_;
return p;
}
tl_object_ptr<ton_api::storage_daemon_provider_params> ProviderParams::tl() const {
return create_tl_object<ton_api::storage_daemon_provider_params>(
accept_new_contracts, rate_per_mb_day->to_dec_string(), max_span, minimal_file_size, maximal_file_size);
}
bool ProviderParams::to_builder(vm::CellBuilder& b) const {
return b.store_long_bool(accept_new_contracts, 1) && store_coins(b, rate_per_mb_day) &&
b.store_long_bool(max_span, 32) && b.store_long_bool(minimal_file_size, 64) &&
b.store_long_bool(maximal_file_size, 64);
}
StorageProvider::StorageProvider(ContractAddress account_address, std::string db_root,
td::actor::ActorId<tonlib::TonlibClientWrapper> tonlib_client,
td::actor::ActorId<StorageManager> storage_manager,
td::actor::ActorId<keyring::Keyring> keyring)
: main_address_(account_address)
, db_root_(std::move(db_root))
, tonlib_client_(std::move(tonlib_client))
, storage_manager_(std::move(storage_manager))
, keyring_(std::move(keyring)) {
}
void StorageProvider::start_up() {
LOG(INFO) << "Initing storage provider, account address: " << main_address_.to_string();
td::mkdir(db_root_).ensure();
db_ = std::make_unique<td::RocksDb>(td::RocksDb::open(db_root_).move_as_ok());
auto r_state = db::db_get<ton_api::storage_provider_db_state>(
*db_, create_hash_tl_object<ton_api::storage_provider_db_key_state>(), true);
r_state.ensure();
auto state = r_state.move_as_ok();
if (state) {
last_processed_lt_ = state->last_processed_lt_;
LOG(INFO) << "Loaded storage provider state";
LOG(INFO) << "Last processed lt: " << last_processed_lt_;
}
class Callback : public FabricContractWrapper::Callback {
public:
explicit Callback(td::actor::ActorId<StorageProvider> id) : id_(std::move(id)) {
}
void on_transaction(tl_object_ptr<tonlib_api::raw_transaction> transaction) override {
td::actor::send_closure(id_, &StorageProvider::process_transaction, std::move(transaction));
}
private:
td::actor::ActorId<StorageProvider> id_;
};
contract_wrapper_ =
td::actor::create_actor<FabricContractWrapper>("ContractWrapper", main_address_, tonlib_client_, keyring_,
td::make_unique<Callback>(actor_id(this)), last_processed_lt_);
auto r_config = db::db_get<ton_api::storage_daemon_providerConfig>(
*db_, create_hash_tl_object<ton_api::storage_provider_db_key_providerConfig>(), true);
r_config.ensure();
auto config_obj = r_config.move_as_ok();
if (config_obj) {
LOG(INFO) << "Loaded config from db";
config_ = Config(config_obj);
} else {
LOG(INFO) << "Using default config";
db_store_config();
}
LOG(INFO) << "Config: max_contracts=" << config_.max_contracts << ", max_total_size=" << config_.max_total_size;
auto r_contract_list = db::db_get<ton_api::storage_provider_db_contractList>(
*db_, create_hash_tl_object<ton_api::storage_provider_db_key_contractList>(), true);
r_contract_list.ensure();
auto contract_list = r_contract_list.move_as_ok();
if (contract_list) {
LOG(INFO) << "Loading " << contract_list->contracts_.size() << " contracts from db";
for (auto& c : contract_list->contracts_) {
ContractAddress address(c->wc_, c->addr_);
if (contracts_.count(address)) {
LOG(ERROR) << "Duplicate contract in db: " << address.to_string();
continue;
}
auto r_contract = db::db_get<ton_api::storage_provider_db_storageContract>(
*db_, create_hash_tl_object<ton_api::storage_provider_db_key_storageContract>(address.wc, address.addr),
true);
r_contract.ensure();
auto db_contract = r_contract.move_as_ok();
if (!db_contract) {
LOG(ERROR) << "Missing contract in db: " << address.to_string();
continue;
}
StorageContract& contract = contracts_[address];
contract.torrent_hash = db_contract->torrent_hash_;
contract.microchunk_hash = db_contract->microchunk_hash_;
contract.created_time = db_contract->created_time_;
contract.state = (StorageContract::State)db_contract->state_;
contract.file_size = db_contract->file_size_;
contract.max_span = db_contract->max_span_;
contract.rate = td::string_to_int256(db_contract->rate_);
contracts_total_size_ += contract.file_size;
auto r_tree = db::db_get<ton_api::storage_provider_db_microchunkTree>(
*db_, create_hash_tl_object<ton_api::storage_provider_db_key_microchunkTree>(address.wc, address.addr), true);
r_tree.ensure();
auto tree = r_tree.move_as_ok();
if (tree) {
contract.microchunk_tree = std::make_shared<MicrochunkTree>(vm::std_boc_deserialize(tree->data_).move_as_ok());
}
LOG(INFO) << "Loaded contract from db: " << address.to_string() << ", torrent=" << contract.torrent_hash.to_hex()
<< ", state=" << contract.state;
}
}
for (auto& p : contracts_) {
const ContractAddress& address = p.first;
StorageContract& contract = p.second;
switch (contract.state) {
case StorageContract::st_downloading:
init_new_storage_contract(address, contract);
break;
case StorageContract::st_downloaded:
check_contract_active(address);
break;
case StorageContract::st_active:
contract.check_next_proof_at = td::Timestamp::now();
break;
case StorageContract::st_closing:
check_storage_contract_deleted(address);
break;
default:
LOG(FATAL) << "Invalid contract state in db";
}
}
LOG(INFO) << "Loaded contracts from db";
alarm();
}
void StorageProvider::get_params(td::Promise<ProviderParams> promise) {
return get_provider_params(tonlib_client_, main_address_, std::move(promise));
}
void StorageProvider::get_provider_params(td::actor::ActorId<tonlib::TonlibClientWrapper> client,
ContractAddress address, td::Promise<ProviderParams> promise) {
run_get_method(
address, client, "get_storage_params", std::vector<tl_object_ptr<tonlib_api::tvm_StackEntry>>(),
promise.wrap([](std::vector<tl_object_ptr<tonlib_api::tvm_StackEntry>> stack) -> td::Result<ProviderParams> {
if (stack.size() != 5) {
return td::Status::Error(PSTRING() << "Method returned " << stack.size() << " values, 5 expected");
}
TRY_RESULT_PREFIX(accept_new_contracts, entry_to_int<int>(stack[0]), "Invalid accept_new_contracts: ");
TRY_RESULT_PREFIX(rate_per_mb_day, entry_to_int<td::RefInt256>(stack[1]), "Invalid rate_per_mb_day: ");
TRY_RESULT_PREFIX(max_span, entry_to_int<td::uint32>(stack[2]), "Invalid max_span: ");
TRY_RESULT_PREFIX(minimal_file_size, entry_to_int<td::uint64>(stack[3]), "Invalid minimal_file_size: ");
TRY_RESULT_PREFIX(maximal_file_size, entry_to_int<td::uint64>(stack[4]), "Invalid maximal_file_size: ");
ProviderParams params;
params.accept_new_contracts = accept_new_contracts;
params.rate_per_mb_day = rate_per_mb_day;
params.max_span = max_span;
params.minimal_file_size = minimal_file_size;
params.maximal_file_size = maximal_file_size;
return params;
}));
}
void StorageProvider::set_params(ProviderParams params, td::Promise<td::Unit> promise) {
vm::CellBuilder b;
b.store_long(0x54cbf19b, 32); // const op::update_storage_params = 0x54cbf19b;
b.store_long(0, 64); // query_id
if (!params.to_builder(b)) {
promise.set_error(td::Status::Error("Failed to store params to builder"));
return;
}
LOG(INFO) << "Sending external message to update provider parameters: " << params.accept_new_contracts << ", "
<< params.max_span << ", " << params.rate_per_mb_day << ", " << params.minimal_file_size << ", "
<< params.maximal_file_size;
td::actor::send_closure(contract_wrapper_, &FabricContractWrapper::send_internal_message, main_address_,
td::make_refint(100'000'000), b.as_cellslice(), std::move(promise));
}
void StorageProvider::db_store_state() {
LOG(DEBUG) << "db_store_state last_lt=" << last_processed_lt_;
db_->begin_transaction().ensure();
db_->set(create_hash_tl_object<ton_api::storage_provider_db_key_state>().as_slice(),
create_serialize_tl_object<ton_api::storage_provider_db_state>(last_processed_lt_))
.ensure();
db_->commit_transaction().ensure();
}
void StorageProvider::db_store_config() {
LOG(DEBUG) << "db_store_config";
db_->begin_transaction().ensure();
db_->set(create_hash_tl_object<ton_api::storage_provider_db_key_providerConfig>().as_slice(),
serialize_tl_object(config_.tl(), true))
.ensure();
db_->commit_transaction().ensure();
}
void StorageProvider::alarm() {
for (auto& p : contracts_) {
if (p.second.check_next_proof_at && p.second.check_next_proof_at.is_in_past()) {
p.second.check_next_proof_at = td::Timestamp::never();
check_next_proof(p.first, p.second);
}
alarm_timestamp().relax(p.second.check_next_proof_at);
}
}
void StorageProvider::process_transaction(tl_object_ptr<tonlib_api::raw_transaction> transaction) {
std::string new_contract_address;
for (auto& message : transaction->out_msgs_) {
auto data = dynamic_cast<tonlib_api::msg_dataRaw*>(message->msg_data_.get());
if (data == nullptr) {
continue;
}
auto r_body = vm::std_boc_deserialize(data->body_);
if (r_body.is_error()) {
LOG(ERROR) << "Invalid message body in tonlib response: " << r_body.move_as_error();
continue;
}
td::Ref<vm::Cell> body = r_body.move_as_ok();
vm::CellSlice cs = vm::load_cell_slice(body);
// const op::offer_storage_contract = 0x107c49ef;
if (cs.size() >= 32 && cs.prefetch_long(32) == 0x107c49ef) {
new_contract_address = message->destination_->account_address_;
}
}
if (!new_contract_address.empty()) {
auto P = td::PromiseCreator::lambda(
[SelfId = actor_id(this), lt = (td::uint64)transaction->transaction_id_->lt_](td::Result<td::Unit> R) {
if (R.is_error()) {
LOG(ERROR) << "Error during processing new storage contract, skipping: " << R.move_as_error();
}
});
on_new_storage_contract(ContractAddress::parse(new_contract_address).move_as_ok(), std::move(P));
}
last_processed_lt_ = transaction->transaction_id_->lt_;
db_store_state();
}
void StorageProvider::on_new_storage_contract(ContractAddress address, td::Promise<td::Unit> promise, int max_retries) {
LOG(INFO) << "Processing new storage contract: " << address.to_string();
get_storage_contract_data(
address, tonlib_client_,
[SelfId = actor_id(this), address, promise = std::move(promise),
max_retries](td::Result<StorageContractData> R) mutable {
if (R.is_error()) {
if (max_retries > 0) {
LOG(WARNING) << "Processing new storage contract: " << R.move_as_error() << ", retrying";
delay_action(
[SelfId = std::move(SelfId), promise = std::move(promise), address = std::move(address),
max_retries]() mutable {
td::actor::send_closure(SelfId, &StorageProvider::on_new_storage_contract, std::move(address),
std::move(promise), max_retries - 1);
},
td::Timestamp::in(5.0));
} else {
promise.set_error(R.move_as_error());
}
return;
}
td::actor::send_closure(SelfId, &StorageProvider::on_new_storage_contract_cont, address, R.move_as_ok(),
std::move(promise));
});
}
void StorageProvider::on_new_storage_contract_cont(ContractAddress address, StorageContractData data,
td::Promise<td::Unit> promise) {
auto it = contracts_.emplace(address, StorageContract());
if (!it.second) {
promise.set_error(td::Status::Error(PSTRING() << "Storage contract already registered: " << address.to_string()));
return;
}
LOG(INFO) << "New storage contract " << address.to_string() << ", torrent hash: " << data.torrent_hash.to_hex();
LOG(DEBUG) << "Stoage contract data: microchunk_hash=" << data.microchunk_hash << ", balance=" << data.balance
<< ", file_size=" << data.file_size << ", next_proof=" << data.next_proof
<< ", rate=" << data.rate_per_mb_day << ", max_span=" << data.max_span;
StorageContract& contract = it.first->second;
contract.torrent_hash = data.torrent_hash;
contract.microchunk_hash = data.microchunk_hash;
contract.state = StorageContract::st_downloading;
contract.created_time = (td::uint32)td::Clocks::system();
contract.file_size = data.file_size;
contract.max_span = data.max_span;
contract.rate = data.rate_per_mb_day;
contracts_total_size_ += contract.file_size;
promise.set_result(td::Unit());
if (contracts_.size() <= config_.max_contracts && contracts_total_size_ <= config_.max_total_size) {
db_update_storage_contract(address, true);
init_new_storage_contract(address, contract);
} else {
if (contracts_.size() > config_.max_contracts) {
LOG(WARNING) << "Cannot add new storage contract: too many contracts (limit = " << config_.max_contracts << ")";
} else {
LOG(WARNING) << "Cannot add new storage contract: total size exceeded (limit = "
<< td::format::as_size(config_.max_total_size) << ")";
}
contract.state = StorageContract::st_closing;
db_update_storage_contract(address, true);
do_close_storage_contract(address);
}
}
void StorageProvider::db_update_storage_contract(const ContractAddress& address, bool update_list) {
LOG(DEBUG) << "db_update_storage_contract " << address.to_string() << " " << update_list;
db_->begin_transaction().ensure();
if (update_list) {
std::vector<tl_object_ptr<ton_api::storage_provider_db_contractAddress>> list;
for (const auto& t : contracts_) {
list.push_back(create_tl_object<ton_api::storage_provider_db_contractAddress>(t.first.wc, t.first.addr));
}
db_->set(create_hash_tl_object<ton_api::storage_provider_db_key_contractList>().as_slice(),
create_serialize_tl_object<ton_api::storage_provider_db_contractList>(std::move(list)))
.ensure();
}
auto key = create_hash_tl_object<ton_api::storage_provider_db_key_storageContract>(address.wc, address.addr);
auto it = contracts_.find(address);
if (it == contracts_.end()) {
db_->erase(key.as_slice()).ensure();
} else {
const StorageContract& contract = it->second;
db_->set(key.as_slice(),
create_serialize_tl_object<ton_api::storage_provider_db_storageContract>(
contract.torrent_hash, contract.microchunk_hash, contract.created_time, (int)contract.state,
contract.file_size, contract.rate->to_dec_string(), contract.max_span));
}
db_->commit_transaction().ensure();
}
void StorageProvider::db_update_microchunk_tree(const ContractAddress& address) {
LOG(DEBUG) << "db_update_microchunk_tree " << address.to_string();
db_->begin_transaction().ensure();
auto key = create_hash_tl_object<ton_api::storage_provider_db_key_microchunkTree>(address.wc, address.addr);
auto it = contracts_.find(address);
if (it == contracts_.end() || it->second.microchunk_tree == nullptr) {
db_->erase(key.as_slice()).ensure();
} else {
db_->set(key.as_slice(), create_serialize_tl_object<ton_api::storage_provider_db_microchunkTree>(
vm::std_boc_serialize(it->second.microchunk_tree->get_root()).move_as_ok()));
}
db_->commit_transaction().ensure();
}
void StorageProvider::init_new_storage_contract(ContractAddress address, StorageContract& contract) {
CHECK(contract.state == StorageContract::st_downloading);
td::actor::send_closure(storage_manager_, &StorageManager::add_torrent_by_hash, contract.torrent_hash, "", false,
[](td::Result<td::Unit> R) {
// Ignore errors: error can mean that the torrent already exists, other errors will be caught later
if (R.is_error()) {
LOG(DEBUG) << "Add torrent: " << R.move_as_error();
} else {
LOG(DEBUG) << "Add torrent: OK";
}
});
td::actor::send_closure(storage_manager_, &StorageManager::set_active_download, contract.torrent_hash, true,
[SelfId = actor_id(this), address](td::Result<td::Unit> R) {
if (R.is_error()) {
LOG(ERROR) << "Failed to init storage contract: " << R.move_as_error();
td::actor::send_closure(SelfId, &StorageProvider::do_close_storage_contract, address);
return;
}
LOG(DEBUG) << "Set active download: OK";
});
td::actor::send_closure(
storage_manager_, &StorageManager::wait_for_completion, contract.torrent_hash,
[SelfId = actor_id(this), address, hash = contract.torrent_hash, microchunk_hash = contract.microchunk_hash,
manager = storage_manager_](td::Result<td::Unit> R) {
if (R.is_error()) {
LOG(WARNING) << "Failed to download torrent " << hash.to_hex() << ": " << R.move_as_error();
td::actor::send_closure(SelfId, &StorageProvider::do_close_storage_contract, address);
return;
}
LOG(DEBUG) << "Downloaded torrent " << hash;
td::actor::send_closure(
manager, &StorageManager::with_torrent, hash,
[SelfId, address, hash, microchunk_hash](td::Result<NodeActor::NodeState> R) {
auto r_microchunk_tree = [&]() -> td::Result<MicrochunkTree> {
TRY_RESULT(state, std::move(R));
Torrent& torrent = state.torrent;
if (!torrent.is_completed() || torrent.get_included_size() != torrent.get_info().file_size) {
return td::Status::Error("unknown error");
}
LOG(DEBUG) << "Building microchunk tree for " << hash;
TRY_RESULT(tree, MicrochunkTree::Builder::build_for_torrent(torrent));
if (tree.get_root_hash() != microchunk_hash) {
return td::Status::Error("microchunk tree hash mismatch");
}
return tree;
}();
if (r_microchunk_tree.is_error()) {
LOG(WARNING) << "Failed to download torrent " << hash.to_hex() << ": " << R.move_as_error();
td::actor::send_closure(SelfId, &StorageProvider::do_close_storage_contract, address);
} else {
td::actor::send_closure(SelfId, &StorageProvider::downloaded_torrent, address,
r_microchunk_tree.move_as_ok());
}
});
});
}
void StorageProvider::downloaded_torrent(ContractAddress address, MicrochunkTree microchunk_tree) {
auto it = contracts_.find(address);
if (it == contracts_.end()) {
LOG(WARNING) << "Contract " << address.to_string() << " does not exist anymore";
return;
}
auto& contract = it->second;
LOG(INFO) << "Finished downloading torrent " << contract.torrent_hash.to_hex() << " for contract "
<< address.to_string();
contract.state = StorageContract::st_downloaded;
contract.microchunk_tree = std::make_shared<MicrochunkTree>(std::move(microchunk_tree));
db_update_microchunk_tree(address);
db_update_storage_contract(address, false);
check_contract_active(address);
}
void StorageProvider::check_contract_active(ContractAddress address, td::Timestamp retry_until,
td::Timestamp retry_false_until) {
get_storage_contract_data(address, tonlib_client_,
[=, SelfId = actor_id(this)](td::Result<StorageContractData> R) mutable {
if (R.is_error()) {
LOG(WARNING) << "Failed to check that contract is active: " << R.move_as_error();
if (retry_until && retry_until.is_in_past()) {
delay_action(
[=]() {
td::actor::send_closure(SelfId, &StorageProvider::check_contract_active,
address, retry_until, retry_false_until);
},
td::Timestamp::in(5.0));
}
return;
}
if (R.ok().active) {
td::actor::send_closure(SelfId, &StorageProvider::activated_storage_contract, address);
} else if (retry_false_until && retry_false_until.is_in_past()) {
delay_action(
[=]() {
td::actor::send_closure(SelfId, &StorageProvider::check_contract_active, address,
retry_until, retry_false_until);
},
td::Timestamp::in(5.0));
} else {
td::actor::send_closure(SelfId, &StorageProvider::activate_contract_cont, address);
}
});
}
void StorageProvider::activate_contract_cont(ContractAddress address) {
vm::CellBuilder b;
b.store_long(0x7a361688, 32); // const op::accept_storage_contract = 0x7a361688;
b.store_long(0, 64); // query_id
LOG(DEBUG) << "Sending op::accept_storage_contract to " << address.to_string();
td::actor::send_closure(
contract_wrapper_, &FabricContractWrapper::send_internal_message, address, td::make_refint(100'000'000),
b.as_cellslice(), [SelfId = actor_id(this), address](td::Result<td::Unit> R) {
if (R.is_error()) {
LOG(ERROR) << "Failed to send activate message, retrying later: " << R.move_as_error();
delay_action([=]() { td::actor::send_closure(SelfId, &StorageProvider::activate_contract_cont, address); },
td::Timestamp::in(10.0));
return;
}
td::actor::send_closure(SelfId, &StorageProvider::check_contract_active, address, td::Timestamp::in(60.0),
td::Timestamp::in(40.0));
});
}
void StorageProvider::activated_storage_contract(ContractAddress address) {
auto it = contracts_.find(address);
if (it == contracts_.end()) {
LOG(WARNING) << "Contract " << address.to_string() << " does not exist anymore";
return;
}
LOG(INFO) << "Storage contract " << address.to_string() << " is active";
auto& contract = it->second;
contract.state = StorageContract::st_active;
db_update_storage_contract(address, false);
alarm_timestamp().relax(contract.check_next_proof_at = td::Timestamp::in(1.0));
}
void StorageProvider::do_close_storage_contract(ContractAddress address) {
auto it = contracts_.find(address);
if (it == contracts_.end()) {
LOG(WARNING) << "Contract " << address.to_string() << " does not exist anymore";
return;
}
LOG(INFO) << "Closing storage contract " << address.to_string();
auto& contract = it->second;
contract.state = StorageContract::st_closing;
db_update_storage_contract(address, false);
check_storage_contract_deleted(address);
}
void StorageProvider::send_close_storage_contract(ContractAddress address) {
vm::CellBuilder b;
b.store_long(0x79f937ea, 32); // const op::close_contract = 0x79f937ea;
b.store_long(0, 64); // query_id
LOG(DEBUG) << "Sending op::close_contract to " << address.to_string();
td::actor::send_closure(
contract_wrapper_, &FabricContractWrapper::send_internal_message, address, td::make_refint(100'000'000),
b.as_cellslice(), [SelfId = actor_id(this), address](td::Result<td::Unit> R) {
if (R.is_error()) {
LOG(ERROR) << "Failed to send close message, retrying later: " << R.move_as_error();
delay_action([=]() { td::actor::send_closure(SelfId, &StorageProvider::activate_contract_cont, address); },
td::Timestamp::in(10.0));
return;
}
td::actor::send_closure(SelfId, &StorageProvider::check_storage_contract_deleted, address,
td::Timestamp::in(40.0));
});
}
void StorageProvider::check_storage_contract_deleted(ContractAddress address, td::Timestamp retry_false_until) {
check_contract_exists(address, tonlib_client_, [=, SelfId = actor_id(this)](td::Result<bool> R) {
if (R.is_error()) {
delay_action(
[=]() {
td::actor::send_closure(SelfId, &StorageProvider::check_storage_contract_deleted, address,
retry_false_until);
},
td::Timestamp::in(10.0));
return;
}
if (!R.move_as_ok()) {
td::actor::send_closure(SelfId, &StorageProvider::storage_contract_deleted, address);
} else if (retry_false_until && !retry_false_until.is_in_past()) {
delay_action(
[=]() {
td::actor::send_closure(SelfId, &StorageProvider::check_storage_contract_deleted, address,
retry_false_until);
},
td::Timestamp::in(5.0));
} else {
td::actor::send_closure(SelfId, &StorageProvider::send_close_storage_contract, address);
}
});
}
void StorageProvider::storage_contract_deleted(ContractAddress address) {
auto it = contracts_.find(address);
if (it == contracts_.end()) {
return;
}
LOG(INFO) << "Storage contract " << address.to_string() << " was deleted";
td::Bits256 hash = it->second.torrent_hash;
contracts_total_size_ -= it->second.file_size;
contracts_.erase(it);
bool delete_torrent = true;
for (const auto& p : contracts_) {
if (p.second.torrent_hash == hash) {
delete_torrent = false;
break;
}
}
if (delete_torrent) {
LOG(INFO) << "Deleting torrent " << hash.to_hex();
td::actor::send_closure(storage_manager_, &StorageManager::remove_torrent, hash, true,
[](td::Result<td::Unit> R) {});
}
db_update_storage_contract(address, true);
}
void StorageProvider::check_next_proof(ContractAddress address, StorageContract& contract) {
if (contract.state != StorageContract::st_active) {
return;
}
CHECK(contract.microchunk_tree != nullptr);
get_storage_contract_data(
address, tonlib_client_, [SelfId = actor_id(this), address](td::Result<StorageContractData> R) {
td::actor::send_closure(SelfId, &StorageProvider::got_next_proof_info, address, std::move(R));
});
}
void StorageProvider::got_next_proof_info(ContractAddress address, td::Result<StorageContractData> R) {
auto it = contracts_.find(address);
if (it == contracts_.end() || it->second.state != StorageContract::st_active) {
return;
}
auto& contract = it->second;
if (R.is_error()) {
LOG(ERROR) << "get_next_proof_info for " << address.to_string() << ": " << R.move_as_error();
check_contract_exists(address, tonlib_client_, [SelfId = actor_id(this), address](td::Result<bool> R) {
td::actor::send_closure(SelfId, &StorageProvider::got_contract_exists, address, std::move(R));
});
return;
}
auto data = R.move_as_ok();
if (data.balance->sgn() == 0) {
LOG(INFO) << "Balance of contract " << address.to_string() << " is zero, closing";
do_close_storage_contract(address);
return;
}
td::uint32 send_at = data.last_proof_time + data.max_span / 2, now = (td::uint32)td::Clocks::system();
if (now < send_at) {
LOG(DEBUG) << "Will send proof in " << send_at - now << "s (last_proof_time=" << data.last_proof_time
<< ", max_span=" << data.max_span << ")";
alarm_timestamp().relax(contract.check_next_proof_at = td::Timestamp::in(send_at - now + 2));
return;
}
LOG(INFO) << "Sending proof for " << address.to_string() << ": next_proof=" << data.next_proof
<< ", max_span=" << data.max_span << ", last_proof_time=" << data.last_proof_time << " ("
<< now - data.last_proof_time << "s ago)";
td::actor::send_closure(
storage_manager_, &StorageManager::with_torrent, contract.torrent_hash,
[=, SelfId = actor_id(this), tree = contract.microchunk_tree](td::Result<NodeActor::NodeState> R) {
if (R.is_error()) {
LOG(ERROR) << "Missing torrent for " << address.to_string();
return;
}
auto state = R.move_as_ok();
td::uint64 l = data.next_proof / MicrochunkTree::MICROCHUNK_SIZE * MicrochunkTree::MICROCHUNK_SIZE;
td::uint64 r = l + MicrochunkTree::MICROCHUNK_SIZE;
auto proof = tree->get_proof(l, r, state.torrent);
td::actor::send_closure(SelfId, &StorageProvider::got_next_proof, address, std::move(proof));
});
}
void StorageProvider::got_contract_exists(ContractAddress address, td::Result<bool> R) {
auto it = contracts_.find(address);
if (it == contracts_.end() || it->second.state != StorageContract::st_active) {
return;
}
auto& contract = it->second;
if (R.is_error()) {
LOG(ERROR) << "Check contract exists for " << address.to_string() << ": " << R.move_as_error();
alarm_timestamp().relax(contract.check_next_proof_at = td::Timestamp::in(10.0));
return;
}
if (R.ok()) {
alarm_timestamp().relax(contract.check_next_proof_at = td::Timestamp::in(10.0));
return;
}
storage_contract_deleted(address);
}
void StorageProvider::got_next_proof(ContractAddress address, td::Result<td::Ref<vm::Cell>> R) {
if (R.is_error()) {
LOG(ERROR) << "Failed to build proof: " << R.move_as_error();
return;
}
LOG(INFO) << "Got proof, sending";
vm::CellBuilder b;
b.store_long(0x419d5d4d, 32); // const op::proof_storage = 0x419d5d4d;
b.store_long(0, 64); // query_id
b.store_ref(R.move_as_ok());
td::actor::send_closure(contract_wrapper_, &FabricContractWrapper::send_internal_message, address,
td::make_refint(100'000'000), b.as_cellslice(),
[SelfId = actor_id(this), address](td::Result<td::Unit> R) {
if (R.is_error()) {
LOG(ERROR) << "Failed to send proof message: " << R.move_as_error();
} else {
LOG(DEBUG) << "Proof for " << address.to_string() << " was sent";
}
td::actor::send_closure(SelfId, &StorageProvider::sent_next_proof, address);
});
}
void StorageProvider::sent_next_proof(ContractAddress address) {
auto it = contracts_.find(address);
if (it == contracts_.end() || it->second.state != StorageContract::st_active) {
return;
}
auto& contract = it->second;
alarm_timestamp().relax(contract.check_next_proof_at = td::Timestamp::in(30.0));
}
void StorageProvider::get_provider_info(bool with_balances, bool with_contracts,
td::Promise<tl_object_ptr<ton_api::storage_daemon_providerInfo>> promise) {
auto result = std::make_shared<ton_api::storage_daemon_providerInfo>();
td::MultiPromise mp;
auto ig = mp.init_guard();
ig.add_promise(promise.wrap(
[result](td::Unit) { return create_tl_object<ton_api::storage_daemon_providerInfo>(std::move(*result)); }));
result->address_ = main_address_.to_string();
result->config_ = config_.tl();
result->contracts_count_ = (int)contracts_.size();
result->contracts_total_size_ = contracts_total_size_;
if (with_balances) {
get_contract_balance(main_address_, tonlib_client_, ig.get_promise().wrap([result](td::RefInt256 balance) {
result->balance_ = balance->to_dec_string();
return td::Unit();
}));
} else {
result->balance_ = "-1";
}
if (with_contracts) {
for (const auto& p : contracts_) {
auto obj = create_tl_object<ton_api::storage_daemon_contractInfo>();
const StorageContract& contract = p.second;
obj->address_ = p.first.to_string();
obj->state_ = (int)contract.state;
obj->torrent_ = contract.torrent_hash;
obj->created_time_ = contract.created_time;
obj->rate_ = contract.rate->to_dec_string();
obj->max_span_ = contract.max_span;
obj->file_size_ = contract.file_size;
obj->downloaded_size_ = obj->file_size_;
obj->client_balance_ = "-1";
obj->contract_balance_ = "-1";
result->contracts_.push_back(std::move(obj));
}
size_t i = 0;
for (const auto& p : contracts_) {
const StorageContract& contract = p.second;
if (contract.state == StorageContract::st_downloading) {
td::actor::send_closure(storage_manager_, &StorageManager::with_torrent, contract.torrent_hash,
[i, result, promise = ig.get_promise()](td::Result<NodeActor::NodeState> R) mutable {
if (R.is_error()) {
result->contracts_[i]->downloaded_size_ = 0;
} else {
auto state = R.move_as_ok();
result->contracts_[i]->downloaded_size_ = state.torrent.get_included_ready_size();
}
promise.set_result(td::Unit());
});
}
if (with_balances) {
get_contract_balance(p.first, tonlib_client_,
[i, result, promise = ig.get_promise()](td::Result<td::RefInt256> R) mutable {
if (R.is_ok()) {
result->contracts_[i]->contract_balance_ = R.ok()->to_dec_string();
}
promise.set_result(td::Unit());
});
get_storage_contract_data(p.first, tonlib_client_,
[i, result, promise = ig.get_promise()](td::Result<StorageContractData> R) mutable {
auto S = [&]() -> td::Status {
TRY_RESULT(data, std::move(R));
result->contracts_[i]->client_balance_ = data.balance->to_dec_string();
return td::Status::OK();
}();
promise.set_result(td::Unit());
});
}
i += 1;
}
}
}
void StorageProvider::set_provider_config(Config config, td::Promise<td::Unit> promise) {
config_ = config;
LOG(INFO) << "Changing provider config: max_contracts=" << config_.max_contracts
<< ", max_total_size=" << config_.max_total_size;
db_store_config();
promise.set_result(td::Unit());
}
void StorageProvider::withdraw(ContractAddress address, td::Promise<td::Unit> promise) {
auto it = contracts_.find(address);
if (it == contracts_.end() || it->second.state != StorageContract::st_active) {
promise.set_error(td::Status::Error("No such storage contract"));
return;
}
if (it->second.state != StorageContract::st_active) {
promise.set_error(td::Status::Error("Storage contract is not active"));
return;
}
vm::CellBuilder b;
b.store_long(0x46ed2e94, 32); // const op::withdraw = 0x46ed2e94;
b.store_long(0, 64); // query_id
LOG(INFO) << "Sending op::withdraw to storage contract " << address.to_string();
td::actor::send_closure(contract_wrapper_, &FabricContractWrapper::send_internal_message, address,
td::make_refint(100'000'000), b.as_cellslice(), std::move(promise));
}
void StorageProvider::send_coins(ContractAddress dest, td::RefInt256 amount, std::string message,
td::Promise<td::Unit> promise) {
if (amount->sgn() < 0) {
promise.set_error(td::Status::Error("Amount is negative"));
return;
}
vm::CellBuilder b;
if (!message.empty()) {
b.store_long(0, 32);
if (b.remaining_bits() < message.size() * 8) {
promise.set_error(td::Status::Error("Message is too long (max 122 bytes)"));
return;
}
b.store_bytes(td::Slice(message));
}
LOG(INFO) << "Sending " << amount << " nanoTON to " << dest.to_string();
td::actor::send_closure(contract_wrapper_, &FabricContractWrapper::send_internal_message, dest, amount,
b.finalize_novm(), std::move(promise));
}
void StorageProvider::close_storage_contract(ContractAddress address, td::Promise<td::Unit> promise) {
if (!contracts_.count(address)) {
promise.set_error(td::Status::Error("No such storage contract"));
return;
}
do_close_storage_contract(address);
promise.set_result(td::Unit());
}
StorageProvider::Config::Config(const tl_object_ptr<ton_api::storage_daemon_providerConfig>& obj)
: max_contracts(obj->max_contracts_), max_total_size(obj->max_total_size_) {
}
tl_object_ptr<ton_api::storage_daemon_providerConfig> StorageProvider::Config::tl() const {
return create_tl_object<ton_api::storage_daemon_providerConfig>(max_contracts, max_total_size);
}

View file

@ -0,0 +1,127 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "td/actor/actor.h"
#include "storage/db.h"
#include "tonlib/tonlib/TonlibClientWrapper.h"
#include "StorageManager.h"
#include "keyring/keyring.h"
#include "smc-util.h"
#include "storage/MicrochunkTree.h"
using namespace ton;
struct ProviderParams {
bool accept_new_contracts = false;
td::RefInt256 rate_per_mb_day = td::zero_refint();
td::uint32 max_span = 0;
td::uint64 minimal_file_size = 0;
td::uint64 maximal_file_size = 0;
static td::Result<ProviderParams> create(const tl_object_ptr<ton_api::storage_daemon_provider_params>& obj);
tl_object_ptr<ton_api::storage_daemon_provider_params> tl() const;
bool to_builder(vm::CellBuilder& b) const;
};
class StorageProvider : public td::actor::Actor {
public:
struct Config {
td::uint32 max_contracts = 1000;
td::uint64 max_total_size = 128LL << 30;
Config() = default;
explicit Config(const tl_object_ptr<ton_api::storage_daemon_providerConfig>& obj);
tl_object_ptr<ton_api::storage_daemon_providerConfig> tl() const;
};
StorageProvider(ContractAddress address, std::string db_root,
td::actor::ActorId<tonlib::TonlibClientWrapper> tonlib_client,
td::actor::ActorId<StorageManager> storage_manager, td::actor::ActorId<keyring::Keyring> keyring);
void start_up() override;
void alarm() override;
void get_params(td::Promise<ProviderParams> promise);
static void get_provider_params(td::actor::ActorId<tonlib::TonlibClientWrapper>, ContractAddress address,
td::Promise<ProviderParams> promise);
void set_params(ProviderParams params, td::Promise<td::Unit> promise);
void get_provider_info(bool with_balances, bool with_contracts,
td::Promise<tl_object_ptr<ton_api::storage_daemon_providerInfo>> promise);
void set_provider_config(Config config, td::Promise<td::Unit> promise);
void withdraw(ContractAddress address, td::Promise<td::Unit> promise);
void send_coins(ContractAddress dest, td::RefInt256 amount, std::string message, td::Promise<td::Unit> promise);
void close_storage_contract(ContractAddress address, td::Promise<td::Unit> promise);
private:
ContractAddress main_address_;
std::string db_root_;
td::actor::ActorId<tonlib::TonlibClientWrapper> tonlib_client_;
td::actor::ActorId<StorageManager> storage_manager_;
td::actor::ActorId<keyring::Keyring> keyring_;
td::Promise<td::Unit> init_promise_;
Config config_;
std::unique_ptr<td::KeyValue> db_;
td::actor::ActorOwn<FabricContractWrapper> contract_wrapper_;
td::uint64 last_processed_lt_ = 0;
struct StorageContract {
enum State { st_downloading = 0, st_downloaded = 1, st_active = 2, st_closing = 3 };
td::Bits256 torrent_hash;
td::Bits256 microchunk_hash;
td::uint32 created_time;
State state;
td::uint64 file_size = 0;
td::uint32 max_span = 0;
td::RefInt256 rate = td::zero_refint();
// TODO: Compute and store only one tree for duplicating torrents
std::shared_ptr<MicrochunkTree> microchunk_tree;
td::Timestamp check_next_proof_at = td::Timestamp::never();
};
std::map<ContractAddress, StorageContract> contracts_;
td::uint64 contracts_total_size_ = 0;
void process_transaction(tl_object_ptr<tonlib_api::raw_transaction> transaction);
void db_store_state();
void db_store_config();
void db_update_storage_contract(const ContractAddress& address, bool update_list);
void db_update_microchunk_tree(const ContractAddress& address);
void on_new_storage_contract(ContractAddress address, td::Promise<td::Unit> promise, int max_retries = 10);
void on_new_storage_contract_cont(ContractAddress address, StorageContractData data, td::Promise<td::Unit> promise);
void init_new_storage_contract(ContractAddress address, StorageContract& contract);
void downloaded_torrent(ContractAddress address, MicrochunkTree microchunk_tree);
void check_contract_active(ContractAddress address, td::Timestamp retry_until = td::Timestamp::in(30.0),
td::Timestamp retry_false_until = td::Timestamp::never());
void activate_contract_cont(ContractAddress address);
void activated_storage_contract(ContractAddress address);
void do_close_storage_contract(ContractAddress address);
void check_storage_contract_deleted(ContractAddress address,
td::Timestamp retry_false_until = td::Timestamp::never());
void send_close_storage_contract(ContractAddress address);
void storage_contract_deleted(ContractAddress address);
void check_next_proof(ContractAddress address, StorageContract& contract);
void got_next_proof_info(ContractAddress address, td::Result<StorageContractData> R);
void got_contract_exists(ContractAddress address, td::Result<bool> R);
void got_next_proof(ContractAddress address, td::Result<td::Ref<vm::Cell>> R);
void sent_next_proof(ContractAddress address);
};

View file

@ -0,0 +1 @@
provider-code.h

View file

@ -0,0 +1,23 @@
# Storage Provider
Simple smart-contract system for conclusion of a storage agreements.
- guarantees that the provider stores the file
- no storage - no payment
- no penalties, if provider doesn't store file client can stop payment at any time
- no control that provider upload the file: client can stop payment at any time if not satisfied
## Storage Agreements Fabric
Storage provider deploy storage agreements fabric. Any client may request fabric to deploy storage agreement contract.
Fabric provides get-method `get_storage_params` which returns
- `accept_new_contracts?` - whether provider accepts new contracts
- `rate_per_mb_day` - price in nanoTON per Megabyte per day
- `max_span` - maximal timespan between proving file storage which will be paid
- `minimal_file_size` - minimal file size accepted by provider
- `maximal_file_size` - maximal file size accepted by provider
## Storage agreement
Agreement contract has client account and accept deposits to this account.
It also knows merkle root and allows provider to withdraw money from client account by providing merkle proof of file storage.
Client can stop agreement at any time.

View file

@ -0,0 +1,4 @@
func -SPA -o storage-contract.fif ../../../crypto/smartcont/stdlib.fc storage-contract.fc && \
(echo "\"storage-contract.fif\" include boc>B \"storage-contract-code.boc\" B>file" | fift) && \
func -SPA -o storage-provider.fif ../../../crypto/smartcont/stdlib.fc storage-provider.fc && \
(echo "\"storage-provider.fif\" include boc>B \"storage-provider-code.boc\" B>file" | fift)

View file

@ -0,0 +1,23 @@
const op::offer_storage_contract = 0x107c49ef;
const op::close_contract = 0x79f937ea;
const op::contract_deployed = 0xbf7bd0c1;
const op::storage_contract_confirmed = 0xd4caedcd;
const op::reward_withdrawal = 0xa91baf56;
const op::storage_contract_terminated = 0xb6236d63;
const op::accept_storage_contract = 0x7a361688;
const op::withdraw = 0x46ed2e94;
const op::proof_storage = 0x419d5d4d;
const op::update_pubkey = 0x53f34cd6;
const op::update_storage_params = 0x54cbf19b;
const error::not_enough_money = 1001;
const error::unauthorized = 401;
const error::wrong_proof = 1002;
const error::contract_not_active = 1003;
const error::file_too_small = 1004;
const error::file_too_big = 1005;
const error::no_new_contracts = 1006;
const error::contract_already_active = 1007;
const error::no_microchunk_hash = 1008;
const error::provider_params_changed = 1009;

View file

@ -0,0 +1,40 @@
#include <fstream>
#include <iostream>
#include <vector>
int main(int argc, char** argv) {
if (argc != 3) {
std::cerr << "Usage: generate-provider-code in.boc out.h\n";
return 1;
}
std::ifstream in(argv[1], std::ios_base::ate | std::ios_base::binary);
size_t size = in.tellg();
in.seekg(0, std::ios::beg);
std::vector<char> buf(size);
if (!in.read(buf.data(), size)) {
std::cerr << "Error: cannot read input\n";
return 1;
}
in.close();
std::ofstream out(argv[2]);
out << "// Auto-generated by embed-provider-code\n";
out << "#pragma once\n";
out << "const unsigned char STORAGE_PROVIDER_CODE[" << size << "] = {\n ";
for (size_t i = 0; i < size; ++i) {
if (i != 0) {
out << ",";
if (i % 32 == 31) {
out << "\n ";
}
}
out << (int)(unsigned char)buf[i];
}
out << "\n};\n";
if (!out) {
std::cerr << "Error: cannot write output\n";
return 1;
}
out.close();
return 0;
}

View file

@ -0,0 +1,266 @@
#include "constants.fc";
const CHUNK_SIZE = 64;
const fee::receipt_value = 20000000;
const fee::storage = 10000000;
{-
storage#_ active:Bool
balance:Coins provider:MsgAddress
merkle_hash:uint256 file_size:uint64 next_proof_byte:uint64
rate_per_mb_day:Coins
max_span:uint32 last_proof_time:uint32
^[client:MsgAddress torrent_hash:uint256] = Storage;
-}
(slice, int) begin_parse_special(cell c) asm "x{D739} s,";
int check_proof(int merkle_hash, int byte_to_proof, int file_size, cell file_dict_proof) {
(slice cs, int special) = file_dict_proof.begin_parse_special();
if (~ special) {
return false;
}
if (cs~load_uint(8) != 3) { ;; Merkle proof
return false;
}
if (cs~load_uint(256) != merkle_hash) {
return false;
}
cell file_dict = cs~load_ref();
int key_len = 0;
while ((CHUNK_SIZE << key_len) < file_size) {
key_len += 1;
}
(slice data, int found?) = file_dict.udict_get?(key_len, byte_to_proof / CHUNK_SIZE);
if(found?) {
return true;
}
return false;
}
() add_to_balance(int amount) impure inline_ref {
var ds = get_data().begin_parse();
var (active, balance, residue) = (ds~load_int(1), ds~load_grams(), ds);
balance += amount;
begin_cell()
.store_int(active, 1)
.store_coins(balance)
.store_slice(residue)
.end_cell().set_data();
}
(slice, int) get_client_data(ds) {
ds = ds.preload_ref().begin_parse();
return (ds~load_msg_addr(), ds~load_uint(256));
}
() recv_internal(int msg_value, cell in_msg_full, slice in_msg_body) impure {
slice cs = in_msg_full.begin_parse();
int flags = cs~load_uint(4);
if (flags & 1) { ;; ignore all bounced messages
return ();
}
slice sender_address = cs~load_msg_addr();
if (in_msg_body.slice_empty?()) {
return add_to_balance(msg_value);
}
int op = in_msg_body~load_uint(32);
if (op == 0) {
return add_to_balance(msg_value);
}
int query_id = in_msg_body~load_uint(64);
if(op == op::offer_storage_contract) {
add_to_balance(msg_value - 2 * fee::receipt_value);
var (client, torrent_hash) = get_client_data(get_data().begin_parse());
var msg = begin_cell()
.store_uint(0x18, 6)
.store_slice(client)
.store_coins(fee::receipt_value)
.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
.store_uint(op::contract_deployed, 32)
.store_uint(query_id, 64)
.store_uint(torrent_hash, 256)
.end_cell();
send_raw_message(msg, 0);
}
if (op == op::accept_storage_contract) {
var ds = get_data().begin_parse();
(int active, int balance, slice provider, slice rest) =
(ds~load_int(1), ds~load_coins(), ds~load_msg_addr(), ds);
throw_unless(error::contract_already_active, ~ active);
throw_unless(error::unauthorized, equal_slice_bits(sender_address, provider));
begin_cell()
.store_int(true, 1)
.store_coins(balance)
.store_slice(provider)
.store_slice(rest)
.end_cell().set_data();
var (client, torrent_hash) = get_client_data(rest);
var msg = begin_cell()
.store_uint(0x18, 6)
.store_slice(client)
.store_coins(fee::receipt_value)
.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
.store_uint(op::storage_contract_confirmed, 32)
.store_uint(cur_lt(), 64)
.store_uint(torrent_hash, 256)
.end_cell();
send_raw_message(msg, 0);
}
if (op == op::close_contract) {
var ds = get_data().begin_parse();
(int active, int balance, slice provider, slice rest) =
(ds~load_int(1), ds~load_coins(), ds~load_msg_addr(), ds);
var (client, torrent_hash) = get_client_data(rest);
throw_unless(error::unauthorized, equal_slice_bits(sender_address, provider) | equal_slice_bits(sender_address, client));
var client_msg = begin_cell()
.store_uint(0x18, 6)
.store_slice(client)
.store_coins(balance)
.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
.store_uint(op::storage_contract_terminated, 32)
.store_uint(cur_lt(), 64)
.store_uint(torrent_hash, 256)
.end_cell();
if(~ active) {
return send_raw_message(client_msg, 128 + 32);
}
send_raw_message(client_msg, 64);
var provider_msg = begin_cell()
.store_uint(0x18, 6)
.store_slice(provider)
.store_coins(0)
.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
.store_uint(op::storage_contract_terminated, 32)
.store_uint(cur_lt(), 64)
.store_uint(torrent_hash, 256)
.end_cell();
return send_raw_message(provider_msg, 128 + 32);
}
if (op == op::withdraw) {
var ds = get_data().begin_parse();
(int active, int balance, slice provider) = (ds~load_int(1), ds~load_coins(), ds~load_msg_addr());
throw_unless(error::contract_not_active, active);
throw_unless(error::unauthorized, equal_slice_bits(sender_address, provider));
if(balance > 0) {
raw_reserve(balance + fee::storage, 2);
}
var msg = begin_cell()
.store_uint(0x18, 6)
.store_slice(provider)
.store_coins(fee::receipt_value)
.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
.store_uint(op::reward_withdrawal, 32)
.store_uint(query_id, 64)
.end_cell();
send_raw_message(msg, 128 + 32);
}
if (op == op::proof_storage) {
cell file_dict_proof = in_msg_body~load_ref();
var ds = get_data().begin_parse();
var (active,
balance,
provider,
merkle_hash,
file_size,
next_proof,
rate_per_mb_day,
max_span,
last_proof_time,
client_data) = (ds~load_int(1),
ds~load_coins(),
ds~load_msg_addr(),
ds~load_uint(256),
ds~load_uint(64),
ds~load_uint(64),
ds~load_coins(),
ds~load_uint(32),
ds~load_uint(32),
ds~load_ref());
throw_unless(error::contract_not_active, active);
throw_unless(error::unauthorized, equal_slice_bits(sender_address, provider));
throw_unless(error::wrong_proof, check_proof(merkle_hash, next_proof, file_size, file_dict_proof));
next_proof = rand(file_size);
int actual_span = min(now() - last_proof_time, max_span);
int bounty = muldiv(file_size * rate_per_mb_day, actual_span, 24 * 60 * 60 * 1024 * 1024);
balance = max(0, balance - bounty);
last_proof_time = now();
begin_cell()
.store_int(true, 1)
.store_coins(balance)
.store_slice(provider)
.store_uint(merkle_hash, 256)
.store_uint(file_size, 64)
.store_uint(next_proof, 64)
.store_coins(rate_per_mb_day)
.store_uint(max_span, 32)
.store_uint(last_proof_time, 32)
.store_ref(client_data)
.end_cell().set_data();
;; Send remaining balance back
cell msg = begin_cell()
.store_uint(0x18, 6)
.store_slice(sender_address)
.store_uint(0, 4 + 1 + 4 + 4 + 64 + 32 + 1 + 1)
.end_cell();
send_raw_message(msg, 64 + 2);
}
}
_ get_storage_contract_data() method_id {
var ds = get_data().begin_parse();
var (active,
balance,
provider,
merkle_hash,
file_size,
next_proof,
rate_per_mb_day,
max_span,
last_proof_time,
rest) = (ds~load_int(1),
ds~load_coins(),
ds~load_msg_addr(),
ds~load_uint(256),
ds~load_uint(64),
ds~load_uint(64),
ds~load_coins(),
ds~load_uint(32),
ds~load_uint(32),
ds);
var (client, torrent_hash) = get_client_data(rest);
return (active, balance, provider, merkle_hash, file_size,
next_proof, rate_per_mb_day, max_span, last_proof_time,
client, torrent_hash);
}
_ get_torrent_hash() method_id {
var (active, balance, provider, merkle_hash, file_size,
next_proof, rate_per_mb_day, max_span, last_proof_time,
client, torrent_hash) = get_storage_contract_data();
return torrent_hash;
}
_ is_active() method_id {
return get_data().begin_parse().preload_int(1);
}
;; next_proof, last_proof_time, max_span
_ get_next_proof_info() method_id {
var (active, balance, provider, merkle_hash, file_size,
next_proof, rate_per_mb_day, max_span, last_proof_time,
client, torrent_hash) = get_storage_contract_data();
return (next_proof, last_proof_time, max_span);
}

View file

@ -0,0 +1,421 @@
"Asm.fif" include
// automatically generated from `../../../crypto/smartcont/stdlib.fc` `storage-contract.fc` incl:`constants.fc`
PROGRAM{
DECLPROC check_proof
DECLPROC add_to_balance
DECLPROC get_client_data
DECLPROC recv_internal
86593 DECLMETHOD get_storage_contract_data
71463 DECLMETHOD get_torrent_hash
122058 DECLMETHOD is_active
81490 DECLMETHOD get_next_proof_info
check_proof PROC:<{
// merkle_hash byte_to_proof file_size file_dict_proof
x{D739} s, // merkle_hash byte_to_proof file_size cs special
NOT // merkle_hash byte_to_proof file_size cs _7
IFJMP:<{ // merkle_hash byte_to_proof file_size cs
4 BLKDROP //
FALSE // _8
}> // merkle_hash byte_to_proof file_size cs
8 LDU // merkle_hash byte_to_proof file_size _9 cs
SWAP // merkle_hash byte_to_proof file_size cs _9
3 NEQINT // merkle_hash byte_to_proof file_size cs _13
IFJMP:<{ // merkle_hash byte_to_proof file_size cs
4 BLKDROP //
FALSE // _14
}> // merkle_hash byte_to_proof file_size cs
256 LDU // merkle_hash byte_to_proof file_size _15 cs
s0 s4 XCHG // cs byte_to_proof file_size _15 merkle_hash
NEQ // cs byte_to_proof file_size _18
IFJMP:<{ // cs byte_to_proof file_size
3 BLKDROP //
FALSE // _19
}> // cs byte_to_proof file_size
s0 s2 XCHG // file_size byte_to_proof cs
LDREF // file_size byte_to_proof _44 _43
DROP // file_size byte_to_proof file_dict
0 PUSHINT // file_size byte_to_proof file_dict key_len=0
WHILE:<{
64 PUSHINT // file_size byte_to_proof file_dict key_len _25=64
OVER // file_size byte_to_proof file_dict key_len _25=64 key_len
LSHIFT // file_size byte_to_proof file_dict key_len _26
s4 PUSH // file_size byte_to_proof file_dict key_len _26 file_size
LESS // file_size byte_to_proof file_dict key_len _27
}>DO<{ // file_size byte_to_proof file_dict key_len
INC // file_size byte_to_proof file_dict key_len
}> // file_size byte_to_proof file_dict key_len
s3 POP // key_len byte_to_proof file_dict
SWAP // key_len file_dict byte_to_proof
6 RSHIFT# // key_len file_dict _33
s0 s2 XCHG // _33 file_dict key_len
DICTUGET
NULLSWAPIFNOT // _45 _46
NIP // found?
IFJMP:<{ //
TRUE // _35
}> //
FALSE // _36
}>
add_to_balance PROCREF:<{
// amount
c4 PUSH // amount _2
CTOS // amount ds
1 LDI // amount _7 ds
LDGRAMS // amount active balance residue
s0 s3 XCHG // residue active balance amount
ADD // residue active balance
SWAP
NEWC // residue balance active _13
1 STI // residue balance _15
SWAP // residue _15 balance
STGRAMS // residue _16
SWAP // _16 residue
STSLICER // _17
ENDC // _18
c4 POP
}>
get_client_data PROC:<{
// ds
PLDREF // _1
CTOS // ds
LDMSGADDR // _3 ds
256 LDU // _3 _11 _10
DROP // _3 _5
}>
recv_internal PROC:<{
// msg_value in_msg_full in_msg_body
SWAP // msg_value in_msg_body in_msg_full
CTOS // msg_value in_msg_body cs
4 LDU // msg_value in_msg_body flags cs
SWAP
1 PUSHINT // msg_value in_msg_body cs flags _9=1
AND // msg_value in_msg_body cs _10
IFJMP:<{ // msg_value in_msg_body cs
3 BLKDROP //
}> // msg_value in_msg_body cs
LDMSGADDR // msg_value in_msg_body _421 _420
DROP // msg_value in_msg_body sender_address
OVER // msg_value in_msg_body sender_address in_msg_body
SEMPTY // msg_value in_msg_body sender_address _14
IFJMP:<{ // msg_value in_msg_body sender_address
2DROP // msg_value
add_to_balance INLINECALLDICT
}> // msg_value in_msg_body sender_address
SWAP // msg_value sender_address in_msg_body
32 LDU // msg_value sender_address op in_msg_body
OVER // msg_value sender_address op in_msg_body op
0 EQINT // msg_value sender_address op in_msg_body _21
IFJMP:<{ // msg_value sender_address op in_msg_body
3 BLKDROP // msg_value
add_to_balance INLINECALLDICT
}> // msg_value sender_address op in_msg_body
64 LDU // msg_value sender_address op query_id in_msg_body
s2 PUSH
276580847 PUSHINT // msg_value sender_address op query_id in_msg_body op _27=276580847
EQUAL // msg_value sender_address op query_id in_msg_body _28
IF:<{ // msg_value sender_address op query_id in_msg_body
s0 s4 XCHG
40000000 PUSHINT // in_msg_body sender_address op query_id msg_value _31
SUB // in_msg_body sender_address op query_id _32
add_to_balance INLINECALLDICT
c4 PUSH // in_msg_body sender_address op query_id _36
CTOS // in_msg_body sender_address op query_id _37
get_client_data CALLDICT // in_msg_body sender_address op query_id client torrent_hash
3212562625 PUSHINT // in_msg_body sender_address op query_id client torrent_hash _40=3212562625
0 PUSHINT // in_msg_body sender_address op query_id client torrent_hash _40=3212562625 _41=0
24 PUSHINT // in_msg_body sender_address op query_id client torrent_hash _40=3212562625 _41=0 _42=24
NEWC // in_msg_body sender_address op query_id client torrent_hash _40=3212562625 _41=0 _42=24 _43
6 STU // in_msg_body sender_address op query_id client torrent_hash _40=3212562625 _41=0 _45
s0 s4 XCHG2 // in_msg_body sender_address op query_id _41=0 torrent_hash _40=3212562625 _45 client
STSLICER // in_msg_body sender_address op query_id _41=0 torrent_hash _40=3212562625 _46
20000000 PUSHINT // in_msg_body sender_address op query_id _41=0 torrent_hash _40=3212562625 _46 _47=20000000
STGRAMS // in_msg_body sender_address op query_id _41=0 torrent_hash _40=3212562625 _48
s1 s3 XCHG // in_msg_body sender_address op query_id _40=3212562625 torrent_hash _41=0 _48
107 STU // in_msg_body sender_address op query_id _40=3212562625 torrent_hash _62
s1 s2 XCHG // in_msg_body sender_address op query_id torrent_hash _40=3212562625 _62
32 STU // in_msg_body sender_address op query_id torrent_hash _64
s2 s(-1) PUXC // in_msg_body sender_address op query_id torrent_hash query_id _64
64 STU // in_msg_body sender_address op query_id torrent_hash _66
256 STU // in_msg_body sender_address op query_id _68
ENDC // in_msg_body sender_address op query_id msg
0 PUSHINT // in_msg_body sender_address op query_id msg _70=0
SENDRAWMSG
}>ELSE<{
s4 POP // in_msg_body sender_address op query_id
}>
OVER
2050365064 PUSHINT // in_msg_body sender_address op query_id op _72=2050365064
EQUAL // in_msg_body sender_address op query_id _73
IF:<{ // in_msg_body sender_address op query_id
c4 PUSH // in_msg_body sender_address op query_id _75
CTOS // in_msg_body sender_address op query_id ds
1 LDI // in_msg_body sender_address op query_id _81 ds
LDGRAMS // in_msg_body sender_address op query_id _81 _84 ds
LDMSGADDR // in_msg_body sender_address op query_id active balance provider rest
s0 s3 XCHG // in_msg_body sender_address op query_id rest balance provider active
NOT // in_msg_body sender_address op query_id rest balance provider _89
1007 THROWIFNOT
s5 s0 PUSH2 // in_msg_body sender_address op query_id rest balance provider sender_address provider
SDEQ // in_msg_body sender_address op query_id rest balance provider _92
401 THROWIFNOT
TRUE // in_msg_body sender_address op query_id rest balance provider _94
NEWC // in_msg_body sender_address op query_id rest balance provider _94 _95
1 STI // in_msg_body sender_address op query_id rest balance provider _97
ROT // in_msg_body sender_address op query_id rest provider _97 balance
STGRAMS // in_msg_body sender_address op query_id rest provider _98
SWAP // in_msg_body sender_address op query_id rest _98 provider
STSLICER // in_msg_body sender_address op query_id rest _99
OVER // in_msg_body sender_address op query_id rest _99 rest
STSLICER // in_msg_body sender_address op query_id rest _100
ENDC // in_msg_body sender_address op query_id rest _101
c4 POP
get_client_data CALLDICT // in_msg_body sender_address op query_id client torrent_hash
LTIME // in_msg_body sender_address op query_id client torrent_hash _107
3570068941 PUSHINT // in_msg_body sender_address op query_id client torrent_hash _107 _108=3570068941
0 PUSHINT // in_msg_body sender_address op query_id client torrent_hash _107 _108=3570068941 _109=0
24 PUSHINT // in_msg_body sender_address op query_id client torrent_hash _107 _108=3570068941 _109=0 _110=24
NEWC // in_msg_body sender_address op query_id client torrent_hash _107 _108=3570068941 _109=0 _110=24 _111
6 STU // in_msg_body sender_address op query_id client torrent_hash _107 _108=3570068941 _109=0 _113
s0 s5 XCHG2 // in_msg_body sender_address op query_id _109=0 torrent_hash _107 _108=3570068941 _113 client
STSLICER // in_msg_body sender_address op query_id _109=0 torrent_hash _107 _108=3570068941 _114
20000000 PUSHINT // in_msg_body sender_address op query_id _109=0 torrent_hash _107 _108=3570068941 _114 _115=20000000
STGRAMS // in_msg_body sender_address op query_id _109=0 torrent_hash _107 _108=3570068941 _116
s1 s4 XCHG // in_msg_body sender_address op query_id _108=3570068941 torrent_hash _107 _109=0 _116
107 STU // in_msg_body sender_address op query_id _108=3570068941 torrent_hash _107 _130
s1 s3 XCHG // in_msg_body sender_address op query_id _107 torrent_hash _108=3570068941 _130
32 STU // in_msg_body sender_address op query_id _107 torrent_hash _132
s1 s2 XCHG // in_msg_body sender_address op query_id torrent_hash _107 _132
64 STU // in_msg_body sender_address op query_id torrent_hash _134
256 STU // in_msg_body sender_address op query_id _136
ENDC // in_msg_body sender_address op query_id msg
0 PUSHINT // in_msg_body sender_address op query_id msg _138=0
SENDRAWMSG
}> // in_msg_body sender_address op query_id
OVER
2046375914 PUSHINT // in_msg_body sender_address op query_id op _140=2046375914
EQUAL // in_msg_body sender_address op query_id _141
IFJMP:<{ // in_msg_body sender_address op query_id
s2 s3 XCHG
3 BLKDROP // sender_address
c4 PUSH // sender_address _143
CTOS // sender_address ds
1 LDI // sender_address _149 ds
LDGRAMS // sender_address _149 _152 ds
LDMSGADDR // sender_address active balance provider rest
get_client_data CALLDICT // sender_address active balance provider client torrent_hash
s5 s2 PUSH2 // sender_address active balance provider client torrent_hash sender_address provider
SDEQ // sender_address active balance provider client torrent_hash _160
s6 s2 XCPU // _160 active balance provider client torrent_hash sender_address client
SDEQ // _160 active balance provider client torrent_hash _161
s1 s6 XCHG // torrent_hash active balance provider client _160 _161
OR // torrent_hash active balance provider client _162
401 THROWIFNOT
LTIME // torrent_hash active balance provider client _165
3055775075 PUSHINT // torrent_hash active balance provider client _165 _166=3055775075
0 PUSHINT // torrent_hash active balance provider client _165 _166=3055775075 _167=0
24 PUSHINT // torrent_hash active balance provider client _165 _166=3055775075 _167=0 _168=24
NEWC // torrent_hash active balance provider client _165 _166=3055775075 _167=0 _168=24 _169
6 STU // torrent_hash active balance provider client _165 _166=3055775075 _167=0 _171
s0 s4 XCHG2 // torrent_hash active balance provider _167=0 _165 _166=3055775075 _171 client
STSLICER // torrent_hash active balance provider _167=0 _165 _166=3055775075 _172
s0 s5 XCHG2 // torrent_hash active _166=3055775075 provider _167=0 _165 _172 balance
STGRAMS // torrent_hash active _166=3055775075 provider _167=0 _165 _173
s1 s2 XCHG // torrent_hash active _166=3055775075 provider _165 _167=0 _173
107 STU // torrent_hash active _166=3055775075 provider _165 _187
s1 s3 XCHG // torrent_hash active _165 provider _166=3055775075 _187
32 STU // torrent_hash active _165 provider _189
s1 s2 XCHG // torrent_hash active provider _165 _189
64 STU // torrent_hash active provider _191
s3 s(-1) PUXC // torrent_hash active provider torrent_hash _191
256 STU // torrent_hash active provider _193
ENDC // torrent_hash active provider client_msg
s0 s2 XCHG // torrent_hash client_msg provider active
NOT // torrent_hash client_msg provider _195
IFJMP:<{ // torrent_hash client_msg provider
DROP
NIP // client_msg
160 PUSHINT // client_msg _198
SENDRAWMSG
}> // torrent_hash client_msg provider
SWAP
64 PUSHINT // torrent_hash provider client_msg _200=64
SENDRAWMSG
LTIME // torrent_hash provider _203
3055775075 PUSHINT // torrent_hash provider _203 _204=3055775075
0 PUSHINT // torrent_hash provider _203 _204=3055775075 _205=0
24 PUSHINT // torrent_hash provider _203 _204=3055775075 _205=0 _206=24
NEWC // torrent_hash provider _203 _204=3055775075 _205=0 _206=24 _207
6 STU // torrent_hash provider _203 _204=3055775075 _205=0 _209
s0 s4 XCHG2 // torrent_hash _205=0 _203 _204=3055775075 _209 provider
STSLICER // torrent_hash _205=0 _203 _204=3055775075 _210
s3 PUSH // torrent_hash _205=0 _203 _204=3055775075 _210 _211=0
STGRAMS // torrent_hash _205=0 _203 _204=3055775075 _212
s1 s3 XCHG // torrent_hash _204=3055775075 _203 _205=0 _212
107 STU // torrent_hash _204=3055775075 _203 _226
s1 s2 XCHG // torrent_hash _203 _204=3055775075 _226
32 STU // torrent_hash _203 _228
64 STU // torrent_hash _230
256 STU // _232
ENDC // provider_msg
160 PUSHINT // provider_msg _236
SENDRAWMSG
}> // in_msg_body sender_address op query_id
OVER
1189949076 PUSHINT // in_msg_body sender_address op query_id op _238=1189949076
EQUAL // in_msg_body sender_address op query_id _239
IF:<{ // in_msg_body sender_address op query_id
c4 PUSH // in_msg_body sender_address op query_id _241
CTOS // in_msg_body sender_address op query_id ds
1 LDI // in_msg_body sender_address op query_id _246 ds
LDGRAMS // in_msg_body sender_address op query_id _246 _249 ds
LDMSGADDR // in_msg_body sender_address op query_id _246 _249 _449 _448
DROP // in_msg_body sender_address op query_id active balance provider
s0 s2 XCHG // in_msg_body sender_address op query_id provider balance active
1003 THROWIFNOT
s4 s1 PUSH2 // in_msg_body sender_address op query_id provider balance sender_address provider
SDEQ // in_msg_body sender_address op query_id provider balance _256
401 THROWIFNOT
DUP // in_msg_body sender_address op query_id provider balance balance
0 GTINT // in_msg_body sender_address op query_id provider balance _259
IF:<{ // in_msg_body sender_address op query_id provider balance
10000000 PUSHINT // in_msg_body sender_address op query_id provider balance _260=10000000
ADD // in_msg_body sender_address op query_id provider _261
2 PUSHINT // in_msg_body sender_address op query_id provider _261 _262=2
RAWRESERVE
}>ELSE<{
DROP // in_msg_body sender_address op query_id provider
}>
2837163862 PUSHINT // in_msg_body sender_address op query_id provider _265=2837163862
0 PUSHINT // in_msg_body sender_address op query_id provider _265=2837163862 _266=0
24 PUSHINT // in_msg_body sender_address op query_id provider _265=2837163862 _266=0 _267=24
NEWC // in_msg_body sender_address op query_id provider _265=2837163862 _266=0 _267=24 _268
6 STU // in_msg_body sender_address op query_id provider _265=2837163862 _266=0 _270
s0 s3 XCHG2 // in_msg_body sender_address op query_id _266=0 _265=2837163862 _270 provider
STSLICER // in_msg_body sender_address op query_id _266=0 _265=2837163862 _271
20000000 PUSHINT // in_msg_body sender_address op query_id _266=0 _265=2837163862 _271 _272=20000000
STGRAMS // in_msg_body sender_address op query_id _266=0 _265=2837163862 _273
s1 s2 XCHG // in_msg_body sender_address op query_id _265=2837163862 _266=0 _273
107 STU // in_msg_body sender_address op query_id _265=2837163862 _287
32 STU // in_msg_body sender_address op query_id _289
64 STU // in_msg_body sender_address op _291
ENDC // in_msg_body sender_address op msg
160 PUSHINT // in_msg_body sender_address op msg _295
SENDRAWMSG
}>ELSE<{
DROP // in_msg_body sender_address op
}>
1100832077 PUSHINT // in_msg_body sender_address op _297=1100832077
EQUAL // in_msg_body sender_address _298
IF:<{ // in_msg_body sender_address
SWAP // sender_address in_msg_body
LDREF // sender_address _451 _450
DROP // sender_address file_dict_proof
c4 PUSH // sender_address file_dict_proof _303
CTOS // sender_address file_dict_proof ds
1 LDI // sender_address file_dict_proof _315 ds
LDGRAMS // sender_address file_dict_proof _315 _318 ds
LDMSGADDR // sender_address file_dict_proof _315 _318 _320 ds
256 LDU // sender_address file_dict_proof _315 _318 _320 _322 ds
64 LDU // sender_address file_dict_proof _315 _318 _320 _322 _325 ds
64 LDU // sender_address file_dict_proof _315 _318 _320 _322 _325 _328 ds
LDGRAMS // sender_address file_dict_proof _315 _318 _320 _322 _325 _328 _331 ds
32 LDU // sender_address file_dict_proof _315 _318 _320 _322 _325 _328 _331 _333 ds
32 LDU // sender_address file_dict_proof _315 _318 _320 _322 _325 _328 _331 _333 _336 ds
LDREF // sender_address file_dict_proof _315 _318 _320 _322 _325 _328 _331 _333 _336 _471 _470
DROP // sender_address file_dict_proof active balance provider merkle_hash file_size next_proof rate_per_mb_day max_span last_proof_time client_data
s0 s9 XCHG // sender_address file_dict_proof client_data balance provider merkle_hash file_size next_proof rate_per_mb_day max_span last_proof_time active
1003 THROWIFNOT
s10 s6 PUSH2 // sender_address file_dict_proof client_data balance provider merkle_hash file_size next_proof rate_per_mb_day max_span last_proof_time sender_address provider
SDEQ // sender_address file_dict_proof client_data balance provider merkle_hash file_size next_proof rate_per_mb_day max_span last_proof_time _344
401 THROWIFNOT
s5 s3 s(-1) PUXC2
s5 s10 PUXC // sender_address max_span client_data balance provider merkle_hash file_size last_proof_time rate_per_mb_day merkle_hash next_proof file_size file_dict_proof
check_proof CALLDICT // sender_address max_span client_data balance provider merkle_hash file_size last_proof_time rate_per_mb_day _347
1002 THROWIFNOT
s2 PUSH // sender_address max_span client_data balance provider merkle_hash file_size last_proof_time rate_per_mb_day file_size
RAND // sender_address max_span client_data balance provider merkle_hash file_size last_proof_time rate_per_mb_day next_proof
NOW // sender_address max_span client_data balance provider merkle_hash file_size last_proof_time rate_per_mb_day next_proof _351
s0 s3 XCHG2 // sender_address max_span client_data balance provider merkle_hash file_size next_proof rate_per_mb_day _351 last_proof_time
SUB // sender_address max_span client_data balance provider merkle_hash file_size next_proof rate_per_mb_day _352
s8 PUSH // sender_address max_span client_data balance provider merkle_hash file_size next_proof rate_per_mb_day _352 max_span
MIN // sender_address max_span client_data balance provider merkle_hash file_size next_proof rate_per_mb_day actual_span
s3 s1 PUSH2 // sender_address max_span client_data balance provider merkle_hash file_size next_proof rate_per_mb_day actual_span file_size rate_per_mb_day
MUL // sender_address max_span client_data balance provider merkle_hash file_size next_proof rate_per_mb_day actual_span _355
SWAP
90596966400 PUSHINTX // sender_address max_span client_data balance provider merkle_hash file_size next_proof rate_per_mb_day _355 actual_span _364
MULDIV // sender_address max_span client_data balance provider merkle_hash file_size next_proof rate_per_mb_day bounty
s0 s6 XCHG
0 PUSHINT
s0 s7 XCHG // sender_address max_span client_data _366=0 provider merkle_hash file_size next_proof rate_per_mb_day balance bounty
SUB // sender_address max_span client_data _366=0 provider merkle_hash file_size next_proof rate_per_mb_day _367
s1 s6 XCHG // sender_address max_span client_data rate_per_mb_day provider merkle_hash file_size next_proof _366=0 _367
MAX // sender_address max_span client_data rate_per_mb_day provider merkle_hash file_size next_proof balance
NOW // sender_address max_span client_data rate_per_mb_day provider merkle_hash file_size next_proof balance last_proof_time
TRUE // sender_address max_span client_data rate_per_mb_day provider merkle_hash file_size next_proof balance last_proof_time _370
NEWC // sender_address max_span client_data rate_per_mb_day provider merkle_hash file_size next_proof balance last_proof_time _370 _371
1 STI // sender_address max_span client_data rate_per_mb_day provider merkle_hash file_size next_proof balance last_proof_time _373
ROT // sender_address max_span client_data rate_per_mb_day provider merkle_hash file_size next_proof last_proof_time _373 balance
STGRAMS // sender_address max_span client_data rate_per_mb_day provider merkle_hash file_size next_proof last_proof_time _374
s0 s5 XCHG2 // sender_address max_span client_data rate_per_mb_day last_proof_time merkle_hash file_size next_proof _374 provider
STSLICER // sender_address max_span client_data rate_per_mb_day last_proof_time merkle_hash file_size next_proof _375
s1 s3 XCHG // sender_address max_span client_data rate_per_mb_day last_proof_time next_proof file_size merkle_hash _375
256 STU // sender_address max_span client_data rate_per_mb_day last_proof_time next_proof file_size _377
64 STU // sender_address max_span client_data rate_per_mb_day last_proof_time next_proof _379
64 STU // sender_address max_span client_data rate_per_mb_day last_proof_time _381
ROT // sender_address max_span client_data last_proof_time _381 rate_per_mb_day
STGRAMS // sender_address max_span client_data last_proof_time _382
s1 s3 XCHG // sender_address last_proof_time client_data max_span _382
32 STU // sender_address last_proof_time client_data _384
s1 s2 XCHG // sender_address client_data last_proof_time _384
32 STU // sender_address client_data _386
STREF // sender_address _387
ENDC // sender_address _388
c4 POP
0 PUSHINT // sender_address _391=0
24 PUSHINT // sender_address _391=0 _392=24
NEWC // sender_address _391=0 _392=24 _393
6 STU // sender_address _391=0 _395
ROT // _391=0 _395 sender_address
STSLICER // _391=0 _396
111 STU // _412
ENDC // msg
66 PUSHINT // msg _416
SENDRAWMSG
}>ELSE<{
2DROP //
}>
}>
get_storage_contract_data PROC:<{
//
c4 PUSH // _1
CTOS // ds
1 LDI // _13 ds
LDGRAMS // _13 _16 ds
LDMSGADDR // _13 _16 _18 ds
256 LDU // _13 _16 _18 _20 ds
64 LDU // _13 _16 _18 _20 _23 ds
64 LDU // _13 _16 _18 _20 _23 _26 ds
LDGRAMS // _13 _16 _18 _20 _23 _26 _29 ds
32 LDU // _13 _16 _18 _20 _23 _26 _29 _31 ds
32 LDU // active balance provider merkle_hash file_size next_proof rate_per_mb_day max_span last_proof_time rest
get_client_data CALLDICT // active balance provider merkle_hash file_size next_proof rate_per_mb_day max_span last_proof_time client torrent_hash
}>
get_torrent_hash PROC:<{
//
get_storage_contract_data CALLDICT // _12 _13 _14 _15 _16 _17 _18 _19 _20 _21 _22
10 1 BLKDROP2 // torrent_hash
}>
is_active PROC:<{
//
c4 PUSH // _0
CTOS // _1
1 PLDI // _3
}>
get_next_proof_info PROC:<{
//
get_storage_contract_data CALLDICT // _12 _13 _14 _15 _16 _17 _18 _19 _20 _21 _22
2DROP
s2 POP
5 3 BLKDROP2 // next_proof last_proof_time max_span
}>
}END>c

View file

@ -0,0 +1,227 @@
;; Storage contract fabric
#include "constants.fc";
const min_deploy_amount = 50000000;
cell storage_contract_code() asm """ "storage-contract-code.boc" file>B B>boc PUSHREF """;
slice calculate_address_by_stateinit(cell state_init) {
return begin_cell().store_uint(4, 3)
.store_int(0, 8)
.store_uint(cell_hash(state_init), 256)
.end_cell()
.begin_parse();
}
cell build_storage_contract_stateinit(int merkle_hash, int file_size, int rate_per_mb_day,
int max_span, slice client, int torrent_hash) {
cell data = begin_cell()
.store_int(0, 1) ;; active
.store_coins(0) ;; client balance
.store_slice(my_address())
.store_uint(merkle_hash, 256)
.store_uint(file_size, 64)
.store_uint(0, 64) ;; next_proof
.store_coins(rate_per_mb_day)
.store_uint(max_span, 32)
.store_uint(now(), 32) ;; last_proof_time
.store_ref(begin_cell()
.store_slice(client)
.store_uint(torrent_hash, 256)
.end_cell())
.end_cell();
cell state_init = begin_cell()
.store_uint(0, 2)
.store_maybe_ref(storage_contract_code())
.store_maybe_ref(data)
.store_uint(0, 1) .end_cell();
return state_init;
}
() deploy_storage_contract (slice client, int query_id, int file_size, int merkle_hash, int torrent_hash,
int expected_rate, int expected_max_span) impure {
var ds = get_data().begin_parse();
var (wallet_data,
accept_new_contracts?,
rate_per_mb_day,
max_span,
minimal_file_size,
maximal_file_size) = (ds~load_bits(32 + 32 + 256),
ds~load_int(1),
ds~load_coins(),
ds~load_uint(32),
ds~load_uint(64),
ds~load_uint(64));
throw_unless(error::no_new_contracts, accept_new_contracts?);
throw_unless(error::file_too_small, file_size >= minimal_file_size);
throw_unless(error::file_too_big, file_size <= maximal_file_size);
throw_unless(error::provider_params_changed, expected_rate == rate_per_mb_day);
throw_unless(error::provider_params_changed, expected_max_span == max_span);
cell state_init = build_storage_contract_stateinit(merkle_hash, file_size, rate_per_mb_day,
max_span, client, torrent_hash);
cell msg = begin_cell()
.store_uint(0x18, 6)
.store_slice(calculate_address_by_stateinit(state_init))
.store_coins(0)
.store_uint(4 + 2, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1)
.store_ref(state_init)
.store_uint(op::offer_storage_contract, 32)
.store_uint(query_id, 64)
.end_cell();
send_raw_message(msg, 64);
}
() recv_internal(int msg_value, cell in_msg_full, slice in_msg_body) impure {
slice cs = in_msg_full.begin_parse();
int flags = cs~load_uint(4);
if ((flags & 1) | in_msg_body.slice_empty?()) { ;; ignore all bounced and empty messages
return ();
}
slice sender_address = cs~load_msg_addr();
int op = in_msg_body~load_uint(32);
if (op == 0) { ;; transfer with text message
return ();
}
int query_id = in_msg_body~load_uint(64);
if(op == op::offer_storage_contract) {
throw_unless(error::not_enough_money, msg_value >= min_deploy_amount);
;; torrent_info piece_size:uint32 file_size:uint64 root_hash:(## 256) header_size:uint64 header_hash:(## 256)
;; microchunk_hash:(Maybe (## 256)) description:Text = TorrentInfo;
;;
;; new_storage_contract#00000001 query_id:uint64 info:(^ TorrentInfo) microchunk_hash:uint256
;; expected_rate:Coins expected_max_span:uint32 = NewStorageContract;
cell torrent_info = in_msg_body~load_ref();
int torrent_hash = cell_hash(torrent_info);
slice info_cs = torrent_info.begin_parse();
info_cs~skip_bits(32);
int file_size = info_cs~load_uint(64);
int merkle_hash = in_msg_body~load_uint(256);
int expected_rate = in_msg_body~load_coins();
int expected_max_span = in_msg_body~load_uint(32);
deploy_storage_contract(sender_address, query_id, file_size, merkle_hash, torrent_hash,
expected_rate, expected_max_span);
return ();
}
if(op == op::storage_contract_terminated) {
return ();
}
if(op == op::update_pubkey) {
if(~ equal_slice_bits(my_address(), sender_address)) {
return ();
}
var ds = get_data().begin_parse();
var (seqno_subwallet,
_,
non_wallet_data) = (ds~load_bits(32 + 32),
ds~load_uint(256),
ds);
int new_pubkey = in_msg_body~load_uint(256);
set_data(begin_cell()
.store_slice(seqno_subwallet)
.store_uint(new_pubkey, 256)
.store_slice(non_wallet_data)
.end_cell());
}
if(op == op::update_storage_params) {
if(~ equal_slice_bits(my_address(), sender_address)) {
return ();
}
var ds = get_data().begin_parse();
var wallet_data = ds~load_bits(32 + 32 + 256);
var(accept_new_contracts?,
rate_per_mb_day,
max_span,
minimal_file_size,
maximal_file_size) = (in_msg_body~load_int(1),
in_msg_body~load_coins(),
in_msg_body~load_uint(32),
in_msg_body~load_uint(64),
in_msg_body~load_uint(64));
set_data(begin_cell()
.store_slice(wallet_data)
.store_int(accept_new_contracts?, 1)
.store_coins(rate_per_mb_day)
.store_uint(max_span, 32)
.store_uint(minimal_file_size, 64)
.store_uint(maximal_file_size, 64)
.end_cell());
}
}
() recv_external(slice in_msg) impure {
var signature = in_msg~load_bits(512);
var cs = in_msg;
var (subwallet_id, valid_until, msg_seqno) = (cs~load_uint(32), cs~load_uint(32), cs~load_uint(32));
throw_if(35, valid_until <= now());
var ds = get_data().begin_parse();
var (stored_seqno,
stored_subwallet,
public_key,
non_wallet_data) = (ds~load_uint(32),
ds~load_uint(32),
ds~load_uint(256),
ds);
throw_unless(33, msg_seqno == stored_seqno);
throw_unless(34, subwallet_id == stored_subwallet);
throw_unless(35, check_signature(slice_hash(in_msg), signature, public_key));
accept_message();
cs~touch();
while (cs.slice_refs()) {
var mode = cs~load_uint(8);
send_raw_message(cs~load_ref(), mode);
}
set_data(begin_cell()
.store_uint(stored_seqno + 1, 32)
.store_uint(stored_subwallet, 32)
.store_uint(public_key, 256)
.store_slice(non_wallet_data)
.end_cell());
}
;; Get methods
int seqno() method_id {
return get_data().begin_parse().preload_uint(32);
}
int get_public_key() method_id {
var cs = get_data().begin_parse();
cs~load_uint(64);
return cs.preload_uint(256);
}
;; seqno, subwallet, key
_ get_wallet_params() method_id {
var ds = get_data().begin_parse();
var (stored_seqno, stored_subwallet, public_key) = (ds~load_uint(32), ds~load_uint(32), ds~load_uint(256));
return (stored_seqno, stored_subwallet, public_key);
}
_ get_storage_params() method_id {
var ds = get_data().begin_parse();
var (wallet_data,
accept_new_contracts?,
rate_per_mb_day,
max_span,
minimal_file_size,
maximal_file_size) = (ds~load_bits(32 + 32 + 256),
ds~load_int(1),
ds~load_coins(),
ds~load_uint(32),
ds~load_uint(64),
ds~load_uint(64));
return (accept_new_contracts?, rate_per_mb_day, max_span, minimal_file_size, maximal_file_size);
}
slice get_storage_contract_address(int merkle_hash, int file_size, slice client, int torrent_hash) method_id {
var (_, rate_per_mb_day, max_span, _, _) = get_storage_params();
cell state_init = build_storage_contract_stateinit(merkle_hash, file_size, rate_per_mb_day, max_span, client, torrent_hash);
return calculate_address_by_stateinit(state_init);
}

View file

@ -0,0 +1,340 @@
"Asm.fif" include
// automatically generated from `../../../crypto/smartcont/stdlib.fc` `storage-provider.fc` incl:`constants.fc`
PROGRAM{
DECLPROC calculate_address_by_stateinit
DECLPROC build_storage_contract_stateinit
DECLPROC deploy_storage_contract
DECLPROC recv_internal
DECLPROC recv_external
85143 DECLMETHOD seqno
78748 DECLMETHOD get_public_key
130271 DECLMETHOD get_wallet_params
104346 DECLMETHOD get_storage_params
119729 DECLMETHOD get_storage_contract_address
calculate_address_by_stateinit PROC:<{
// state_init
HASHCU // _1
0 PUSHINT // _1 _2=0
4 PUSHINT // _1 _2=0 _3=4
NEWC // _1 _2=0 _3=4 _4
3 STU // _1 _2=0 _6
8 STI // _1 _8
256 STU // _10
ENDC // _11
CTOS // _12
}>
build_storage_contract_stateinit PROC:<{
// merkle_hash file_size rate_per_mb_day max_span client torrent_hash
NEWC
ROT // merkle_hash file_size rate_per_mb_day max_span torrent_hash _7 client
STSLICER // merkle_hash file_size rate_per_mb_day max_span torrent_hash _8
256 STU // merkle_hash file_size rate_per_mb_day max_span _10
ENDC // merkle_hash file_size rate_per_mb_day max_span _11
NOW // merkle_hash file_size rate_per_mb_day max_span _11 _12
0 PUSHINT // merkle_hash file_size rate_per_mb_day max_span _11 _12 _13=0
DUP // merkle_hash file_size rate_per_mb_day max_span _11 _12 _13=0 _14=0
NEWC // merkle_hash file_size rate_per_mb_day max_span _11 _12 _13=0 _14=0 _15
1 STI // merkle_hash file_size rate_per_mb_day max_span _11 _12 _13=0 _17
OVER // merkle_hash file_size rate_per_mb_day max_span _11 _12 _13=0 _17 _18=0
STGRAMS // merkle_hash file_size rate_per_mb_day max_span _11 _12 _13=0 _19
MYADDR // merkle_hash file_size rate_per_mb_day max_span _11 _12 _13=0 _19 _20
STSLICER // merkle_hash file_size rate_per_mb_day max_span _11 _12 _13=0 _21
s1 s7 XCHG // _13=0 file_size rate_per_mb_day max_span _11 _12 merkle_hash _21
256 STU // _13=0 file_size rate_per_mb_day max_span _11 _12 _23
s1 s5 XCHG // _13=0 _12 rate_per_mb_day max_span _11 file_size _23
64 STU // _13=0 _12 rate_per_mb_day max_span _11 _25
s1 s5 XCHG // _11 _12 rate_per_mb_day max_span _13=0 _25
64 STU // _11 _12 rate_per_mb_day max_span _27
ROT // _11 _12 max_span _27 rate_per_mb_day
STGRAMS // _11 _12 max_span _28
32 STU // _11 _12 _30
32 STU // _11 _32
STREF // _33
ENDC // data
0 PUSHINT // data _36=0
"storage-contract-code.boc" file>B B>boc PUSHREF // data _36=0 _37
OVER // data _36=0 _37 _38=0
NEWC // data _36=0 _37 _38=0 _39
2 STU // data _36=0 _37 _41
STOPTREF // data _36=0 _42
s1 s2 XCHG // _36=0 data _42
STOPTREF // _36=0 _43
1 STU // _45
ENDC // state_init
}>
deploy_storage_contract PROC:<{
// client query_id file_size merkle_hash torrent_hash expected_rate expected_max_span
c4 PUSH // client query_id file_size merkle_hash torrent_hash expected_rate expected_max_span _8
CTOS // client query_id file_size merkle_hash torrent_hash expected_rate expected_max_span ds
320 PUSHINT // client query_id file_size merkle_hash torrent_hash expected_rate expected_max_span ds _21
LDSLICEX // client query_id file_size merkle_hash torrent_hash expected_rate expected_max_span _91 _90
NIP // client query_id file_size merkle_hash torrent_hash expected_rate expected_max_span ds
1 LDI // client query_id file_size merkle_hash torrent_hash expected_rate expected_max_span _23 ds
LDGRAMS // client query_id file_size merkle_hash torrent_hash expected_rate expected_max_span _23 _26 ds
32 LDU // client query_id file_size merkle_hash torrent_hash expected_rate expected_max_span _23 _26 _28 ds
64 LDU // client query_id file_size merkle_hash torrent_hash expected_rate expected_max_span _23 _26 _28 _31 ds
64 LDU // client query_id file_size merkle_hash torrent_hash expected_rate expected_max_span _23 _26 _28 _31 _101 _100
DROP // client query_id file_size merkle_hash torrent_hash expected_rate expected_max_span accept_new_contracts? rate_per_mb_day max_span minimal_file_size maximal_file_size
s0 s4 XCHG // client query_id file_size merkle_hash torrent_hash expected_rate expected_max_span maximal_file_size rate_per_mb_day max_span minimal_file_size accept_new_contracts?
1006 THROWIFNOT
s8 s(-1) PUXC // client query_id file_size merkle_hash torrent_hash expected_rate expected_max_span maximal_file_size rate_per_mb_day max_span file_size minimal_file_size
GEQ // client query_id file_size merkle_hash torrent_hash expected_rate expected_max_span maximal_file_size rate_per_mb_day max_span _40
1004 THROWIFNOT
s7 s2 PUXC // client query_id file_size merkle_hash torrent_hash expected_rate expected_max_span max_span rate_per_mb_day file_size maximal_file_size
LEQ // client query_id file_size merkle_hash torrent_hash expected_rate expected_max_span max_span rate_per_mb_day _43
1005 THROWIFNOT
s3 s3 XCPU // client query_id file_size merkle_hash torrent_hash rate_per_mb_day expected_max_span max_span expected_rate rate_per_mb_day
EQUAL // client query_id file_size merkle_hash torrent_hash rate_per_mb_day expected_max_span max_span _46
1009 THROWIFNOT
TUCK // client query_id file_size merkle_hash torrent_hash rate_per_mb_day max_span expected_max_span max_span
EQUAL // client query_id file_size merkle_hash torrent_hash rate_per_mb_day max_span _49
1009 THROWIFNOT
2SWAP
s1 s5 XCHG
s1 s6 XCHG // query_id merkle_hash file_size rate_per_mb_day max_span client torrent_hash
build_storage_contract_stateinit CALLDICT // query_id state_init
276580847 PUSHINT // query_id state_init _54=276580847
6 PUSHINT // query_id state_init _54=276580847 _57
24 PUSHINT // query_id state_init _54=276580847 _57 _58=24
NEWC // query_id state_init _54=276580847 _57 _58=24 _59
6 STU // query_id state_init _54=276580847 _57 _61
s3 PUSH // query_id state_init _54=276580847 _57 _61 state_init
calculate_address_by_stateinit CALLDICT // query_id state_init _54=276580847 _57 _61 _62
STSLICER // query_id state_init _54=276580847 _57 _63
0 PUSHINT // query_id state_init _54=276580847 _57 _63 _64=0
STGRAMS // query_id state_init _54=276580847 _57 _65
108 STU // query_id state_init _54=276580847 _81
s1 s2 XCHG // query_id _54=276580847 state_init _81
STREF // query_id _54=276580847 _82
32 STU // query_id _84
64 STU // _86
ENDC // msg
64 PUSHINT // msg _88=64
SENDRAWMSG
}>
recv_internal PROC:<{
SAMEALTSAVE // msg_value in_msg_full in_msg_body
SWAP // msg_value in_msg_body in_msg_full
CTOS // msg_value in_msg_body cs
4 LDU // msg_value in_msg_body flags cs
SWAP
1 PUSHINT // msg_value in_msg_body cs flags _9=1
AND // msg_value in_msg_body cs _10
s2 PUSH // msg_value in_msg_body cs _10 in_msg_body
SEMPTY // msg_value in_msg_body cs _10 _11
OR // msg_value in_msg_body cs _12
IFJMP:<{ // msg_value in_msg_body cs
3 BLKDROP //
}> // msg_value in_msg_body cs
LDMSGADDR // msg_value in_msg_body _141 _140
DROP // msg_value in_msg_body sender_address
SWAP // msg_value sender_address in_msg_body
32 LDU // msg_value sender_address op in_msg_body
OVER // msg_value sender_address op in_msg_body op
0 EQINT // msg_value sender_address op in_msg_body _21
IFJMP:<{ // msg_value sender_address op in_msg_body
4 BLKDROP //
}> // msg_value sender_address op in_msg_body
64 LDU // msg_value sender_address op query_id in_msg_body
s2 PUSH
276580847 PUSHINT // msg_value sender_address op query_id in_msg_body op _26=276580847
EQUAL // msg_value sender_address op query_id in_msg_body _27
IFJMP:<{ // msg_value sender_address op query_id in_msg_body
s2 POP // msg_value sender_address in_msg_body query_id
s0 s3 XCHG
50000000 PUSHINT // query_id sender_address in_msg_body msg_value _29=50000000
GEQ // query_id sender_address in_msg_body _30
1001 THROWIFNOT
LDREF // query_id sender_address torrent_info in_msg_body
OVER // query_id sender_address torrent_info in_msg_body torrent_info
HASHCU // query_id sender_address torrent_info in_msg_body torrent_hash
s0 s2 XCHG // query_id sender_address torrent_hash in_msg_body torrent_info
CTOS // query_id sender_address torrent_hash in_msg_body info_cs
32 PUSHINT // query_id sender_address torrent_hash in_msg_body info_cs _40=32
SDSKIPFIRST // query_id sender_address torrent_hash in_msg_body info_cs
64 LDU // query_id sender_address torrent_hash in_msg_body _149 _148
DROP // query_id sender_address torrent_hash in_msg_body file_size
SWAP // query_id sender_address torrent_hash file_size in_msg_body
256 LDU // query_id sender_address torrent_hash file_size merkle_hash in_msg_body
LDGRAMS // query_id sender_address torrent_hash file_size merkle_hash expected_rate in_msg_body
32 LDU // query_id sender_address torrent_hash file_size merkle_hash expected_rate _155 _154
DROP // query_id sender_address torrent_hash file_size merkle_hash expected_rate expected_max_span
s5 s6 XCHG
s3 s4 XCHG
s2 s3 XCHG // sender_address query_id file_size merkle_hash torrent_hash expected_rate expected_max_span
deploy_storage_contract CALLDICT
}> // msg_value sender_address op query_id in_msg_body
NIP
s3 POP // in_msg_body sender_address op
DUP
3055775075 PUSHINT // in_msg_body sender_address op op _58=3055775075
EQUAL // in_msg_body sender_address op _59
IFJMP:<{ // in_msg_body sender_address op
3 BLKDROP //
}> // in_msg_body sender_address op
DUP
1408453846 PUSHINT // in_msg_body sender_address op op _60=1408453846
EQUAL // in_msg_body sender_address op _61
IF:<{ // in_msg_body sender_address op
MYADDR // in_msg_body sender_address op _62
s2 PUSH // in_msg_body sender_address op _62 sender_address
SDEQ // in_msg_body sender_address op _63
NOT // in_msg_body sender_address op _64
IFJMP:<{ // in_msg_body sender_address op
3 BLKDROP //
RETALT
}> // in_msg_body sender_address op
c4 PUSH // in_msg_body sender_address op _66
CTOS // in_msg_body sender_address op ds
64 LDSLICE // in_msg_body sender_address op _71 ds
256 LDU // in_msg_body sender_address op _71 _159 _158
NIP // in_msg_body sender_address op seqno_subwallet non_wallet_data
s0 s4 XCHG // non_wallet_data sender_address op seqno_subwallet in_msg_body
256 LDU // non_wallet_data sender_address op seqno_subwallet new_pubkey in_msg_body
NEWC // non_wallet_data sender_address op seqno_subwallet new_pubkey in_msg_body _83
s0 s3 XCHG2 // non_wallet_data sender_address op in_msg_body new_pubkey _83 seqno_subwallet
STSLICER // non_wallet_data sender_address op in_msg_body new_pubkey _84
256 STU // non_wallet_data sender_address op in_msg_body _86
s0 s4 XCHG2 // in_msg_body sender_address op _86 non_wallet_data
STSLICER // in_msg_body sender_address op _87
ENDC // in_msg_body sender_address op _88
c4 POP
}> // in_msg_body sender_address op
1422651803 PUSHINT // in_msg_body sender_address op _90=1422651803
EQUAL // in_msg_body sender_address _91
IF:<{ // in_msg_body sender_address
MYADDR // in_msg_body sender_address _92
SWAP // in_msg_body _92 sender_address
SDEQ // in_msg_body _93
NOT // in_msg_body _94
IFJMP:<{ // in_msg_body
DROP //
RETALT
}> // in_msg_body
c4 PUSH // in_msg_body _96
CTOS // in_msg_body ds
320 PUSHINT // in_msg_body ds _104
LDSLICEX // in_msg_body _163 _162
DROP // in_msg_body wallet_data
SWAP // wallet_data in_msg_body
1 LDI // wallet_data _111 in_msg_body
LDGRAMS // wallet_data _111 _114 in_msg_body
32 LDU // wallet_data _111 _114 _116 in_msg_body
64 LDU // wallet_data _111 _114 _116 _119 in_msg_body
64 LDU // wallet_data _111 _114 _116 _119 _173 _172
DROP // wallet_data accept_new_contracts? rate_per_mb_day max_span minimal_file_size maximal_file_size
NEWC // wallet_data accept_new_contracts? rate_per_mb_day max_span minimal_file_size maximal_file_size _125
s0 s6 XCHG2 // maximal_file_size accept_new_contracts? rate_per_mb_day max_span minimal_file_size _125 wallet_data
STSLICER // maximal_file_size accept_new_contracts? rate_per_mb_day max_span minimal_file_size _126
s1 s4 XCHG // maximal_file_size minimal_file_size rate_per_mb_day max_span accept_new_contracts? _126
1 STI // maximal_file_size minimal_file_size rate_per_mb_day max_span _128
ROT // maximal_file_size minimal_file_size max_span _128 rate_per_mb_day
STGRAMS // maximal_file_size minimal_file_size max_span _129
32 STU // maximal_file_size minimal_file_size _131
64 STU // maximal_file_size _133
64 STU // _135
ENDC // _136
c4 POP
}>ELSE<{
2DROP //
}>
}>
recv_external PROC:<{
// in_msg
9 PUSHPOW2 // in_msg _3=512
LDSLICEX // signature in_msg
DUP // signature in_msg cs
32 LDU // signature in_msg _9 cs
32 LDU // signature in_msg _9 _12 cs
32 LDU // signature in_msg subwallet_id valid_until msg_seqno cs
s0 s2 XCHG
NOW // signature in_msg subwallet_id cs msg_seqno valid_until _19
LEQ // signature in_msg subwallet_id cs msg_seqno _20
35 THROWIF
c4 PUSH // signature in_msg subwallet_id cs msg_seqno _23
CTOS // signature in_msg subwallet_id cs msg_seqno ds
32 LDU // signature in_msg subwallet_id cs msg_seqno _29 ds
32 LDU // signature in_msg subwallet_id cs msg_seqno _29 _32 ds
256 LDU // signature in_msg subwallet_id cs msg_seqno stored_seqno stored_subwallet public_key non_wallet_data
s4 s3 XCPU // signature in_msg subwallet_id cs non_wallet_data stored_seqno stored_subwallet public_key msg_seqno stored_seqno
EQUAL // signature in_msg subwallet_id cs non_wallet_data stored_seqno stored_subwallet public_key _39
33 THROWIFNOT
s5 s1 XCPU // signature in_msg public_key cs non_wallet_data stored_seqno stored_subwallet subwallet_id stored_subwallet
EQUAL // signature in_msg public_key cs non_wallet_data stored_seqno stored_subwallet _42
34 THROWIFNOT
s0 s5 XCHG // signature stored_subwallet public_key cs non_wallet_data stored_seqno in_msg
HASHSU // signature stored_subwallet public_key cs non_wallet_data stored_seqno _45
s0 s6 s4 XC2PU // stored_seqno stored_subwallet public_key cs non_wallet_data _45 signature public_key
CHKSIGNU // stored_seqno stored_subwallet public_key cs non_wallet_data _46
35 THROWIFNOT
ACCEPT
SWAP // stored_seqno stored_subwallet public_key non_wallet_data cs
WHILE:<{
DUP // stored_seqno stored_subwallet public_key non_wallet_data cs cs
SREFS // stored_seqno stored_subwallet public_key non_wallet_data cs _51
}>DO<{ // stored_seqno stored_subwallet public_key non_wallet_data cs
8 LDU // stored_seqno stored_subwallet public_key non_wallet_data mode cs
LDREF // stored_seqno stored_subwallet public_key non_wallet_data mode _56 cs
s0 s2 XCHG // stored_seqno stored_subwallet public_key non_wallet_data cs _56 mode
SENDRAWMSG
}> // stored_seqno stored_subwallet public_key non_wallet_data cs
DROP // stored_seqno stored_subwallet public_key non_wallet_data
s0 s3 XCHG // non_wallet_data stored_subwallet public_key stored_seqno
INC // non_wallet_data stored_subwallet public_key _60
NEWC // non_wallet_data stored_subwallet public_key _60 _61
32 STU // non_wallet_data stored_subwallet public_key _63
s1 s2 XCHG // non_wallet_data public_key stored_subwallet _63
32 STU // non_wallet_data public_key _65
256 STU // non_wallet_data _67
SWAP // _67 non_wallet_data
STSLICER // _68
ENDC // _69
c4 POP
}>
seqno PROC:<{
//
c4 PUSH // _0
CTOS // _1
32 PLDU // _3
}>
get_public_key PROC:<{
//
c4 PUSH // _1
CTOS // cs
64 LDU // _9 _8
NIP // cs
256 PLDU // _7
}>
get_wallet_params PROC:<{
//
c4 PUSH // _1
CTOS // ds
32 LDU // _6 ds
32 LDU // _6 _9 ds
256 LDU // _6 _9 _20 _19
DROP // stored_seqno stored_subwallet public_key
}>
get_storage_params PROC:<{
//
c4 PUSH // _1
CTOS // ds
320 PUSHINT // ds _14
LDSLICEX // _31 _30
NIP // ds
1 LDI // _16 ds
LDGRAMS // _16 _19 ds
32 LDU // _16 _19 _21 ds
64 LDU // _16 _19 _21 _24 ds
64 LDU // _16 _19 _21 _24 _41 _40
DROP // accept_new_contracts? rate_per_mb_day max_span minimal_file_size maximal_file_size
}>
get_storage_contract_address PROC:<{
// merkle_hash file_size client torrent_hash
get_storage_params CALLDICT // merkle_hash file_size client torrent_hash _13 _14 _15 _16 _17
2DROP
s2 POP // merkle_hash file_size client torrent_hash max_span rate_per_mb_day
s1 s3 s3 XCHG3 // merkle_hash file_size rate_per_mb_day max_span client torrent_hash
build_storage_contract_stateinit CALLDICT // state_init
calculate_address_by_stateinit CALLDICT // _12
}>
}END>c

View file

@ -0,0 +1,487 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
*/
#include "smc-util.h"
#include "td/utils/filesystem.h"
#include "keys/encryptor.h"
#include "smartcont/provider-code.h"
static void smc_forget(td::actor::ActorId<tonlib::TonlibClientWrapper> client, td::int64 id) {
auto query = create_tl_object<tonlib_api::smc_forget>(id);
td::actor::send_closure(client, &tonlib::TonlibClientWrapper::send_request<tonlib_api::smc_forget>, std::move(query),
[](td::Result<tonlib_api::object_ptr<tonlib_api::ok>> R) mutable {
if (R.is_error()) {
LOG(WARNING) << "smc_forget failed: " << R.move_as_error();
}
});
}
void run_get_method(ContractAddress address, td::actor::ActorId<tonlib::TonlibClientWrapper> client, std::string method,
std::vector<tl_object_ptr<tonlib_api::tvm_StackEntry>> args,
td::Promise<std::vector<tl_object_ptr<tonlib_api::tvm_StackEntry>>> promise) {
LOG(DEBUG) << "Running get method " << method << " on " << address.to_string();
auto query =
create_tl_object<tonlib_api::smc_load>(create_tl_object<tonlib_api::accountAddress>(address.to_string()));
td::actor::send_closure(
client, &tonlib::TonlibClientWrapper::send_request<tonlib_api::smc_load>, std::move(query),
[client, method = std::move(method), args = std::move(args),
promise = std::move(promise)](td::Result<tonlib_api::object_ptr<tonlib_api::smc_info>> R) mutable {
TRY_RESULT_PROMISE(promise, obj, std::move(R));
auto query = create_tl_object<tonlib_api::smc_runGetMethod>(
obj->id_, create_tl_object<tonlib_api::smc_methodIdName>(std::move(method)), std::move(args));
td::actor::send_closure(
client, &tonlib::TonlibClientWrapper::send_request<tonlib_api::smc_runGetMethod>, std::move(query),
[client, id = obj->id_,
promise = std::move(promise)](td::Result<tonlib_api::object_ptr<tonlib_api::smc_runResult>> R) mutable {
smc_forget(client, id);
TRY_RESULT_PROMISE(promise, obj, std::move(R));
if (obj->exit_code_ != 0 && obj->exit_code_ != 1) {
promise.set_error(
td::Status::Error(PSTRING() << "Method execution finished with code " << obj->exit_code_));
return;
}
promise.set_result(std::move(obj->stack_));
});
});
}
void check_contract_exists(ContractAddress address, td::actor::ActorId<tonlib::TonlibClientWrapper> client,
td::Promise<bool> promise) {
auto query =
create_tl_object<tonlib_api::smc_load>(create_tl_object<tonlib_api::accountAddress>(address.to_string()));
td::actor::send_closure(
client, &tonlib::TonlibClientWrapper::send_request<tonlib_api::smc_load>, std::move(query),
[client, promise = std::move(promise)](td::Result<tonlib_api::object_ptr<tonlib_api::smc_info>> R) mutable {
TRY_RESULT_PROMISE(promise, obj, std::move(R));
auto query = create_tl_object<tonlib_api::smc_getState>(obj->id_);
td::actor::send_closure(
client, &tonlib::TonlibClientWrapper::send_request<tonlib_api::smc_getState>, std::move(query),
[client, id = obj->id_,
promise = std::move(promise)](td::Result<tonlib_api::object_ptr<tonlib_api::tvm_cell>> R) mutable {
smc_forget(client, id);
TRY_RESULT_PROMISE(promise, r, std::move(R));
promise.set_result(!r->bytes_.empty());
});
});
}
void get_contract_balance(ContractAddress address, td::actor::ActorId<tonlib::TonlibClientWrapper> client,
td::Promise<td::RefInt256> promise) {
auto query =
create_tl_object<tonlib_api::getAccountState>(create_tl_object<tonlib_api::accountAddress>(address.to_string()));
td::actor::send_closure(
client, &tonlib::TonlibClientWrapper::send_request<tonlib_api::getAccountState>, std::move(query),
promise.wrap([](tonlib_api::object_ptr<tonlib_api::fullAccountState> r) -> td::Result<td::RefInt256> {
return td::make_refint(r->balance_);
}));
}
FabricContractWrapper::FabricContractWrapper(ContractAddress address,
td::actor::ActorId<tonlib::TonlibClientWrapper> client,
td::actor::ActorId<keyring::Keyring> keyring,
td::unique_ptr<Callback> callback, td::uint64 last_processed_lt)
: address_(address)
, client_(std::move(client))
, keyring_(std::move(keyring))
, callback_(std::move(callback))
, last_processed_lt_(last_processed_lt) {
}
void FabricContractWrapper::start_up() {
alarm();
}
void FabricContractWrapper::alarm() {
if (process_transactions_at_ && process_transactions_at_.is_in_past()) {
process_transactions_at_ = td::Timestamp::never();
load_transactions();
}
alarm_timestamp().relax(process_transactions_at_);
if (send_message_at_ && send_message_at_.is_in_past()) {
send_message_at_ = td::Timestamp::never();
do_send_external_message();
}
alarm_timestamp().relax(send_message_at_);
}
void FabricContractWrapper::load_transactions() {
LOG(DEBUG) << "Loading transactions for " << address_.to_string() << ", last_lt=" << last_processed_lt_;
auto query =
create_tl_object<tonlib_api::getAccountState>(create_tl_object<tonlib_api::accountAddress>(address_.to_string()));
td::actor::send_closure(
client_, &tonlib::TonlibClientWrapper::send_request<tonlib_api::getAccountState>, std::move(query),
[SelfId = actor_id(this)](td::Result<tonlib_api::object_ptr<tonlib_api::fullAccountState>> R) mutable {
if (R.is_error()) {
td::actor::send_closure(SelfId, &FabricContractWrapper::loaded_last_transactions, R.move_as_error());
return;
}
auto obj = R.move_as_ok();
td::actor::send_closure(SelfId, &FabricContractWrapper::load_last_transactions,
std::vector<tl_object_ptr<tonlib_api::raw_transaction>>(),
std::move(obj->last_transaction_id_), (td::uint32)obj->sync_utime_);
});
}
void FabricContractWrapper::load_last_transactions(std::vector<tl_object_ptr<tonlib_api::raw_transaction>> transactions,
tl_object_ptr<tonlib_api::internal_transactionId> next_id,
td::uint32 utime) {
if ((td::uint64)next_id->lt_ <= last_processed_lt_) {
loaded_last_transactions(std::make_pair(std::move(transactions), utime));
return;
}
auto query = create_tl_object<tonlib_api::raw_getTransactionsV2>(
nullptr, create_tl_object<tonlib_api::accountAddress>(address_.to_string()), std::move(next_id), 10, false);
td::actor::send_closure(
client_, &tonlib::TonlibClientWrapper::send_request<tonlib_api::raw_getTransactionsV2>, std::move(query),
[transactions = std::move(transactions), last_processed_lt = last_processed_lt_, SelfId = actor_id(this),
utime](td::Result<tonlib_api::object_ptr<tonlib_api::raw_transactions>> R) mutable {
if (R.is_error()) {
td::actor::send_closure(SelfId, &FabricContractWrapper::loaded_last_transactions, R.move_as_error());
return;
}
auto obj = R.move_as_ok();
for (auto& transaction : obj->transactions_) {
if ((td::uint64)transaction->transaction_id_->lt_ <= last_processed_lt ||
(double)transaction->utime_ < td::Clocks::system() - 86400 || transactions.size() >= 1000) {
LOG(DEBUG) << "Stopping loading transactions (too many or too old)";
td::actor::send_closure(SelfId, &FabricContractWrapper::loaded_last_transactions,
std::make_pair(std::move(transactions), utime));
return;
}
LOG(DEBUG) << "Adding trtansaction, lt=" << transaction->transaction_id_->lt_;
transactions.push_back(std::move(transaction));
}
td::actor::send_closure(SelfId, &FabricContractWrapper::load_last_transactions, std::move(transactions),
std::move(obj->previous_transaction_id_), utime);
});
}
void FabricContractWrapper::loaded_last_transactions(
td::Result<std::pair<std::vector<tl_object_ptr<tonlib_api::raw_transaction>>, td::uint32>> R) {
if (R.is_error()) {
LOG(ERROR) << "Error during loading last transactions: " << R.move_as_error();
alarm_timestamp().relax(process_transactions_at_ = td::Timestamp::in(30.0));
return;
}
auto r = R.move_as_ok();
auto transactions = std::move(r.first);
td::uint32 utime = r.second;
LOG(DEBUG) << "Finished loading " << transactions.size() << " transactions. sync_utime=" << utime;
std::reverse(transactions.begin(), transactions.end());
for (tl_object_ptr<tonlib_api::raw_transaction>& transaction : transactions) {
LOG(DEBUG) << "Processing transaction tl=" << transaction->transaction_id_->lt_;
last_processed_lt_ = transaction->transaction_id_->lt_;
// transaction->in_msg_->source_->account_address_.empty() - message is external
if (current_ext_message_ && current_ext_message_.value().sent &&
transaction->in_msg_->source_->account_address_.empty()) {
auto msg_data = dynamic_cast<tonlib_api::msg_dataRaw*>(transaction->in_msg_->msg_data_.get());
if (msg_data == nullptr) {
continue;
}
auto r_body = vm::std_boc_deserialize(msg_data->body_);
if (r_body.is_error()) {
LOG(WARNING) << "Invalid response from tonlib: " << r_body.move_as_error();
continue;
}
td::Ref<vm::Cell> body = r_body.move_as_ok();
vm::CellSlice cs(vm::NoVm(), body);
if (cs.size() < 512 + 96) {
continue;
}
cs.skip_first(512 + 64);
auto seqno = (td::uint32)cs.fetch_ulong(32);
if (seqno != current_ext_message_.value().seqno) {
continue;
}
if (current_ext_message_.value().ext_msg_body_hash != body->get_hash().bits()) {
do_send_external_message_finish(td::Status::Error("Another external message with the same seqno was accepted"));
continue;
}
do_send_external_message_finish(&transaction->out_msgs_);
}
}
for (tl_object_ptr<tonlib_api::raw_transaction>& transaction : transactions) {
callback_->on_transaction(std::move(transaction));
}
if (current_ext_message_ && current_ext_message_.value().sent && current_ext_message_.value().timeout < utime) {
do_send_external_message_finish(td::Status::Error("Timeout"));
}
alarm_timestamp().relax(process_transactions_at_ = td::Timestamp::in(10.0));
}
void FabricContractWrapper::run_get_method(
std::string method, std::vector<tl_object_ptr<tonlib_api::tvm_StackEntry>> args,
td::Promise<std::vector<tl_object_ptr<tonlib_api::tvm_StackEntry>>> promise) {
::run_get_method(address_, client_, std::move(method), std::move(args), std::move(promise));
}
void FabricContractWrapper::send_internal_message(ContractAddress dest, td::RefInt256 coins, vm::CellSlice body,
td::Promise<td::Unit> promise) {
td::Bits256 body_hash = vm::CellBuilder().append_cellslice(body).finalize_novm()->get_hash().bits();
LOG(DEBUG) << "send_internal_message " << address_.to_string() << " -> " << dest.to_string() << ", " << coins
<< " nanoTON, body=" << body_hash.to_hex();
CHECK(coins->sgn() >= 0);
pending_messages_.push(PendingMessage{dest, std::move(coins), std::move(body), body_hash, std::move(promise)});
if (!send_message_at_ && !current_ext_message_) {
alarm_timestamp().relax(send_message_at_ = td::Timestamp::in(1.0));
}
}
void FabricContractWrapper::do_send_external_message() {
CHECK(!current_ext_message_);
LOG(DEBUG) << "do_send_external message: " << pending_messages_.size() << " messages in queue";
if (pending_messages_.empty()) {
return;
}
current_ext_message_ = CurrentExtMessage();
while (current_ext_message_.value().int_msgs.size() < 4 && !pending_messages_.empty()) {
PendingMessage msg = std::move(pending_messages_.front());
current_ext_message_.value().int_msgs.push_back(std::move(msg));
pending_messages_.pop();
}
run_get_method(
"get_wallet_params", {},
[SelfId = actor_id(this)](td::Result<std::vector<tl_object_ptr<tonlib_api::tvm_StackEntry>>> R) {
td::uint32 seqno = 0;
td::uint32 subwallet_id = 0;
td::Bits256 public_key = td::Bits256::zero();
auto S = [&]() -> td::Status {
TRY_RESULT(stack, std::move(R));
if (stack.size() != 3) {
return td::Status::Error(PSTRING() << "Method returned " << stack.size() << " values, 3 expected");
}
TRY_RESULT_PREFIX_ASSIGN(seqno, entry_to_int<td::uint32>(stack[0]), "Invalid seqno: ");
TRY_RESULT_PREFIX_ASSIGN(subwallet_id, entry_to_int<td::uint32>(stack[1]), "Invalid subwallet_id: ");
TRY_RESULT_PREFIX_ASSIGN(public_key, entry_to_bits256(stack[2]), "Invalid public_key: ");
return td::Status::OK();
}();
if (S.is_error()) {
td::actor::send_closure(SelfId, &FabricContractWrapper::do_send_external_message_finish,
S.move_as_error_prefix("Failed to get wallet params: "));
return;
}
td::actor::send_closure(SelfId, &FabricContractWrapper::do_send_external_message_cont, seqno, subwallet_id,
public_key);
});
}
void FabricContractWrapper::do_send_external_message_cont(td::uint32 seqno, td::uint32 subwallet_id,
td::Bits256 public_key) {
LOG(DEBUG) << "Got wallet params: seqno=" << seqno << ", subwallet_id=" << subwallet_id
<< ", key=" << public_key.to_hex();
CHECK(current_ext_message_);
current_ext_message_.value().seqno = seqno;
current_ext_message_.value().timeout = (td::uint32)td::Clocks::system() + 45;
vm::CellBuilder b;
b.store_long(subwallet_id, 32); // subwallet id.
b.store_long(current_ext_message_.value().timeout, 32); // valid until
b.store_long(seqno, 32); // seqno
for (const PendingMessage& msg : current_ext_message_.value().int_msgs) {
vm::CellBuilder b2;
b2.store_long(3 << 2, 6); // 0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddressInt
b2.append_cellslice(msg.dest.to_cellslice()); // dest:MsgAddressInt
store_coins(b2, msg.value); // grams:Grams
b2.store_zeroes(1 + 4 + 4 + 64 + 32 + 1); // extre, ihr_fee, fwd_fee, created_lt, created_at, init
// body:(Either X ^X)
if (b2.remaining_bits() >= 1 + msg.body.size() && b2.remaining_refs() >= msg.body.size_refs()) {
b2.store_zeroes(1);
b2.append_cellslice(msg.body);
} else {
b2.store_ones(1);
b2.store_ref(vm::CellBuilder().append_cellslice(msg.body).finalize_novm());
}
b.store_long(3, 8); // mode
b.store_ref(b2.finalize_novm()); // message
}
td::Ref<vm::Cell> to_sign = b.finalize_novm();
td::BufferSlice hash(to_sign->get_hash().as_slice());
LOG(DEBUG) << "Signing external message";
td::actor::send_closure(
keyring_, &keyring::Keyring::sign_message, PublicKey(pubkeys::Ed25519(public_key)).compute_short_id(),
std::move(hash), [SelfId = actor_id(this), data = std::move(to_sign)](td::Result<td::BufferSlice> R) mutable {
if (R.is_error()) {
td::actor::send_closure(SelfId, &FabricContractWrapper::do_send_external_message_finish,
R.move_as_error_prefix("Failed to sign message: "));
return;
}
auto signature = R.move_as_ok();
CHECK(signature.size() == 64);
vm::CellBuilder b;
b.store_bytes(signature);
b.append_cellslice(vm::load_cell_slice(data));
td::actor::send_closure(SelfId, &FabricContractWrapper::do_send_external_message_cont2, b.finalize_novm());
});
}
void FabricContractWrapper::do_send_external_message_cont2(td::Ref<vm::Cell> ext_msg_body) {
CHECK(current_ext_message_);
LOG(DEBUG) << "Signed external message, sending: seqno=" << current_ext_message_.value().seqno;
current_ext_message_.value().sent = true;
current_ext_message_.value().ext_msg_body_hash = ext_msg_body->get_hash().bits();
auto body = vm::std_boc_serialize(ext_msg_body).move_as_ok().as_slice().str();
auto query = create_tl_object<tonlib_api::raw_createAndSendMessage>(
create_tl_object<tonlib_api::accountAddress>(address_.to_string()), "", std::move(body));
td::actor::send_closure(client_, &tonlib::TonlibClientWrapper::send_request<tonlib_api::raw_createAndSendMessage>,
std::move(query), [SelfId = actor_id(this)](td::Result<tl_object_ptr<tonlib_api::ok>> R) {
if (R.is_error()) {
td::actor::send_closure(SelfId, &FabricContractWrapper::do_send_external_message_finish,
R.move_as_error_prefix("Failed to send message: "));
} else {
LOG(DEBUG) << "External message was sent to liteserver";
}
});
}
void FabricContractWrapper::do_send_external_message_finish(
td::Result<const std::vector<tl_object_ptr<tonlib_api::raw_message>>*> R) {
CHECK(current_ext_message_);
if (R.is_error()) {
LOG(DEBUG) << "Failed to send external message seqno=" << current_ext_message_.value().seqno << ": " << R.error();
for (auto& msg : current_ext_message_.value().int_msgs) {
msg.promise.set_error(R.error().clone());
}
} else {
LOG(DEBUG) << "External message seqno=" << current_ext_message_.value().seqno << " was sent";
const auto& out_msgs = *R.ok();
auto& msgs = current_ext_message_.value().int_msgs;
for (const auto& out_msg : out_msgs) {
ContractAddress dest = ContractAddress::parse(out_msg->destination_->account_address_).move_as_ok();
td::RefInt256 value = td::make_refint((td::uint64)out_msg->value_);
td::Bits256 body_hash;
body_hash.as_slice().copy_from(out_msg->body_hash_);
bool found = false;
for (size_t i = 0; i < msgs.size(); ++i) {
if (msgs[i].dest == dest && msgs[i].value->cmp(*value) == 0 && msgs[i].body_hash == body_hash) {
LOG(DEBUG) << "Internal message was sent dest=" << dest.to_string() << ", value=" << value
<< ", body_hash=" << body_hash.to_hex();
msgs[i].promise.set_result(td::Unit());
msgs.erase(msgs.begin() + i);
found = true;
break;
}
}
if (!found) {
LOG(DEBUG) << "Unexpected internal message was sent: dest=" << dest.to_string() << " value=" << value
<< " body_hash=" << body_hash;
}
}
for (auto& msg : msgs) {
LOG(DEBUG) << "Internal message WAS NOT SENT dest=" << msg.dest.to_string() << ", value=" << msg.value
<< ", body_hash=" << msg.body_hash.to_hex();
msg.promise.set_result(td::Status::Error("External message was accepted, but internal message was not sent"));
}
}
current_ext_message_ = {};
if (!pending_messages_.empty()) {
do_send_external_message();
}
}
bool store_coins(vm::CellBuilder& b, const td::RefInt256& x) {
unsigned len = (((unsigned)x->bit_size(false) + 7) >> 3);
if (len >= 16) {
return false;
}
return b.store_long_bool(len, 4) && b.store_int256_bool(*x, len * 8, false);
}
bool store_coins(vm::CellBuilder& b, td::uint64 x) {
return store_coins(b, td::make_refint(x));
}
td::Result<FabricContractInit> generate_fabric_contract(td::actor::ActorId<keyring::Keyring> keyring) {
auto private_key = PrivateKey{privkeys::Ed25519::random()};
td::Bits256 public_key = private_key.compute_public_key().ed25519_value().raw();
td::Slice code_boc(STORAGE_PROVIDER_CODE, sizeof(STORAGE_PROVIDER_CODE));
TRY_RESULT(code, vm::std_boc_deserialize(code_boc));
LOG(DEBUG) << "Generating storage provider state init. code_hash=" << code->get_hash().to_hex()
<< " public_key=" << public_key.to_hex();
vm::CellBuilder b;
b.store_long(0, 32); // seqno
b.store_long(0, 32); // subwallet_id
b.store_bytes(public_key.as_slice()); // public_key
b.store_long(0, 1); // accept_new_contracts (false by default)
store_coins(b, 1'000'000); // rate_per_mb_day
b.store_long(86400, 32); // max_span
b.store_long(1 << 20, 64); // min_file_size
b.store_long(1 << 30, 64); // max_file_size
td::Ref<vm::Cell> data = b.finalize_novm();
// _ split_depth:(Maybe (## 5)) special:(Maybe TickTock)
// code:(Maybe ^Cell) data:(Maybe ^Cell)
// library:(HashmapE 256 SimpleLib) = StateInit;
td::Ref<vm::Cell> state_init =
vm::CellBuilder().store_long(0b00110, 5).store_ref(std::move(code)).store_ref(std::move(data)).finalize_novm();
ContractAddress address{basechainId, state_init->get_hash().bits()};
// Message body
b = vm::CellBuilder();
b.store_long(0, 32); // subwallet_id
b.store_long((td::uint32)td::Clocks::system() + 3600 * 24 * 7, 32); // valid_until
b.store_long(0, 32); // seqno
td::Ref<vm::Cell> to_sign = b.finalize_novm();
TRY_RESULT(decryptor, private_key.create_decryptor());
TRY_RESULT(signature, decryptor->sign(to_sign->get_hash().as_slice()));
CHECK(signature.size() == 64);
td::Ref<vm::Cell> msg_body =
vm::CellBuilder().store_bytes(signature).append_cellslice(vm::CellSlice(vm::NoVm(), to_sign)).finalize_novm();
td::actor::send_closure(keyring, &keyring::Keyring::add_key, private_key, false,
[](td::Result<td::Unit> R) { R.ensure(); });
return FabricContractInit{address, state_init, msg_body};
}
td::Ref<vm::Cell> create_new_contract_message_body(td::Ref<vm::Cell> info, td::Bits256 microchunk_hash,
td::uint64 query_id, td::RefInt256 rate, td::uint32 max_span) {
// new_storage_contract#00000001 query_id:uint64 info:(^ TorrentInfo) microchunk_hash:uint256
// expected_rate:Coins expected_max_span:uint32 = NewStorageContract;
vm::CellBuilder b;
b.store_long(0x107c49ef, 32); // const op::offer_storage_contract = 0x107c49ef;
b.store_long(query_id, 64);
b.store_ref(std::move(info));
b.store_bytes(microchunk_hash.as_slice());
store_coins(b, rate);
b.store_long(max_span, 32);
return b.finalize_novm();
}
void get_storage_contract_data(ContractAddress address, td::actor::ActorId<tonlib::TonlibClientWrapper> client,
td::Promise<StorageContractData> promise) {
run_get_method(
address, client, "get_storage_contract_data", {},
promise.wrap([](std::vector<tl_object_ptr<tonlib_api::tvm_StackEntry>> stack) -> td::Result<StorageContractData> {
if (stack.size() < 11) {
return td::Status::Error("Too few entries");
}
// active, balance, provider, merkle_hash, file_size, next_proof, rate_per_mb_day, max_span, last_proof_time,
// client, torrent_hash
TRY_RESULT(active, entry_to_int<int>(stack[0]));
TRY_RESULT(balance, entry_to_int<td::RefInt256>(stack[1]));
TRY_RESULT(microchunk_hash, entry_to_bits256(stack[3]));
TRY_RESULT(file_size, entry_to_int<td::uint64>(stack[4]));
TRY_RESULT(next_proof, entry_to_int<td::uint64>(stack[5]));
TRY_RESULT(rate_per_mb_day, entry_to_int<td::RefInt256>(stack[6]));
TRY_RESULT(max_span, entry_to_int<td::uint32>(stack[7]));
TRY_RESULT(last_proof_time, entry_to_int<td::uint32>(stack[8]));
TRY_RESULT(torrent_hash, entry_to_bits256(stack[10]));
return StorageContractData{(bool)active, balance, microchunk_hash, file_size, next_proof,
rate_per_mb_day, max_span, last_proof_time, torrent_hash};
}));
}

View file

@ -0,0 +1,184 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
*/
#pragma once
#include "ton/ton-types.h"
#include "crypto/vm/cellslice.h"
#include "block/block-parse.h"
#include "td/actor/actor.h"
#include "tonlib/tonlib/TonlibClientWrapper.h"
#include <queue>
#include "keyring/keyring.h"
using namespace ton;
struct ContractAddress {
WorkchainId wc = workchainIdNotYet;
td::Bits256 addr = td::Bits256::zero();
ContractAddress() = default;
ContractAddress(WorkchainId wc, td::Bits256 addr) : wc(wc), addr(addr) {
}
std::string to_string() const {
return PSTRING() << wc << ":" << addr.to_hex();
}
td::Ref<vm::CellSlice> to_cellslice() const {
return block::tlb::t_MsgAddressInt.pack_std_address(wc, addr);
}
static td::Result<ContractAddress> parse(td::Slice s) {
TRY_RESULT(x, block::StdAddress::parse(s));
return ContractAddress(x.workchain, x.addr);
}
bool operator==(const ContractAddress& other) const {
return wc == other.wc && addr == other.addr;
}
bool operator!=(const ContractAddress& other) const {
return !(*this == other);
}
bool operator<(const ContractAddress& other) const {
return wc == other.wc ? addr < other.addr : wc < other.wc;
}
};
void run_get_method(ContractAddress address, td::actor::ActorId<tonlib::TonlibClientWrapper> client, std::string method,
std::vector<tl_object_ptr<tonlib_api::tvm_StackEntry>> args,
td::Promise<std::vector<tl_object_ptr<tonlib_api::tvm_StackEntry>>> promise);
void check_contract_exists(ContractAddress address, td::actor::ActorId<tonlib::TonlibClientWrapper> client,
td::Promise<bool> promise);
void get_contract_balance(ContractAddress address, td::actor::ActorId<tonlib::TonlibClientWrapper> client,
td::Promise<td::RefInt256> promise);
class FabricContractWrapper : public td::actor::Actor {
public:
class Callback {
public:
virtual ~Callback() = default;
virtual void on_transaction(tl_object_ptr<tonlib_api::raw_transaction> transaction) = 0;
};
explicit FabricContractWrapper(ContractAddress address, td::actor::ActorId<tonlib::TonlibClientWrapper> client,
td::actor::ActorId<keyring::Keyring> keyring, td::unique_ptr<Callback> callback,
td::uint64 last_processed_lt);
void start_up() override;
void alarm() override;
void run_get_method(std::string method, std::vector<tl_object_ptr<tonlib_api::tvm_StackEntry>> args,
td::Promise<std::vector<tl_object_ptr<tonlib_api::tvm_StackEntry>>> promise);
void send_internal_message(ContractAddress dest, td::RefInt256 coins, vm::CellSlice body,
td::Promise<td::Unit> promise);
private:
ContractAddress address_;
td::actor::ActorId<tonlib::TonlibClientWrapper> client_;
td::actor::ActorId<keyring::Keyring> keyring_;
td::unique_ptr<Callback> callback_;
td::Timestamp process_transactions_at_ = td::Timestamp::now();
td::uint64 last_processed_lt_ = 0;
struct PendingMessage {
ContractAddress dest;
td::RefInt256 value;
vm::CellSlice body;
td::Bits256 body_hash;
td::Promise<td::Unit> promise;
};
struct CurrentExtMessage {
std::vector<PendingMessage> int_msgs;
td::uint32 seqno = 0;
bool sent = false;
td::Bits256 ext_msg_body_hash = td::Bits256::zero();
td::uint32 timeout = 0;
};
std::queue<PendingMessage> pending_messages_;
td::Timestamp send_message_at_ = td::Timestamp::never();
td::optional<CurrentExtMessage> current_ext_message_;
void load_transactions();
void load_last_transactions(std::vector<tl_object_ptr<tonlib_api::raw_transaction>> transactions,
tl_object_ptr<tonlib_api::internal_transactionId> next_id, td::uint32 utime);
void loaded_last_transactions(
td::Result<std::pair<std::vector<tl_object_ptr<tonlib_api::raw_transaction>>, td::uint32>> R);
void do_send_external_message();
void do_send_external_message_cont(td::uint32 seqno, td::uint32 subwallet_id, td::Bits256 public_key);
void do_send_external_message_cont2(td::Ref<vm::Cell> ext_msg_body);
void do_send_external_message_finish(td::Result<const std::vector<tl_object_ptr<tonlib_api::raw_message>>*> R);
};
template <typename T>
inline td::Result<T> entry_to_int(const tl_object_ptr<tonlib_api::tvm_StackEntry>& entry) {
auto num = dynamic_cast<tonlib_api::tvm_stackEntryNumber*>(entry.get());
if (num == nullptr) {
return td::Status::Error("Unexpected value type");
}
return td::to_integer_safe<T>(num->number_->number_);
}
template <>
inline td::Result<td::RefInt256> entry_to_int<td::RefInt256>(const tl_object_ptr<tonlib_api::tvm_StackEntry>& entry) {
auto num = dynamic_cast<tonlib_api::tvm_stackEntryNumber*>(entry.get());
if (num == nullptr) {
return td::Status::Error("Unexpected value type");
}
auto x = td::dec_string_to_int256(num->number_->number_);
if (x.is_null()) {
return td::Status::Error("Invalid integer value");
}
return x;
}
inline td::Result<td::Bits256> entry_to_bits256(const tl_object_ptr<tonlib_api::tvm_StackEntry>& entry) {
TRY_RESULT(x, entry_to_int<td::RefInt256>(entry));
td::Bits256 bits;
if (!x->export_bytes(bits.data(), 32, false)) {
return td::Status::Error("Invalid int256");
}
return bits;
}
bool store_coins(vm::CellBuilder& b, const td::RefInt256& x);
bool store_coins(vm::CellBuilder& b, td::uint64 x);
struct FabricContractInit {
ContractAddress address;
td::Ref<vm::Cell> state_init;
td::Ref<vm::Cell> msg_body;
};
td::Result<FabricContractInit> generate_fabric_contract(td::actor::ActorId<keyring::Keyring> keyring);
td::Ref<vm::Cell> create_new_contract_message_body(td::Ref<vm::Cell> info, td::Bits256 microchunk_hash,
td::uint64 query_id, td::RefInt256 rate, td::uint32 max_span);
struct StorageContractData {
bool active;
td::RefInt256 balance;
td::Bits256 microchunk_hash;
td::uint64 file_size;
td::uint64 next_proof;
td::RefInt256 rate_per_mb_day;
td::uint32 max_span;
td::uint32 last_proof_time;
td::Bits256 torrent_hash;
};
void get_storage_contract_data(ContractAddress address, td::actor::ActorId<tonlib::TonlibClientWrapper> client,
td::Promise<StorageContractData> promise);

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,937 @@
/*
This file is part of TON Blockchain Library.
TON Blockchain Library is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 2 of the License, or
(at your option) any later version.
TON Blockchain Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with TON Blockchain Library. If not, see <http://www.gnu.org/licenses/>.
*/
#include "td/utils/filesystem.h"
#include "td/actor/actor.h"
#include "td/actor/MultiPromise.h"
#include "td/utils/OptionParser.h"
#include "td/utils/port/path.h"
#include "td/utils/port/signals.h"
#include "td/utils/port/user.h"
#include "td/utils/port/IPAddress.h"
#include "td/utils/Random.h"
#include "td/utils/FileLog.h"
#include "checksum.h"
#include "git.h"
#include "auto/tl/ton_api_json.h"
#include "common/delay.h"
#include "adnl/adnl.h"
#include "rldp2/rldp.h"
#include "dht/dht.h"
#include "overlay/overlays.h"
#include "Torrent.h"
#include "TorrentCreator.h"
#include "StorageManager.h"
#include "StorageProvider.h"
#if TD_DARWIN || TD_LINUX
#include <unistd.h>
#endif
#include <iostream>
using namespace ton;
td::BufferSlice create_query_error(td::CSlice message) {
return create_serialize_tl_object<ton_api::storage_daemon_queryError>(message.str());
}
td::BufferSlice create_query_error(td::Status error) {
return create_query_error(error.message());
}
class StorageDaemon : public td::actor::Actor {
public:
StorageDaemon(td::IPAddress ip_addr, bool client_mode, std::string global_config, std::string db_root,
td::uint16 control_port, bool enable_storage_provider)
: ip_addr_(ip_addr)
, client_mode_(client_mode)
, global_config_(std::move(global_config))
, db_root_(std::move(db_root))
, control_port_(control_port)
, enable_storage_provider_(enable_storage_provider) {
}
void start_up() override {
CHECK(db_root_ != "");
td::mkdir(db_root_).ensure();
keyring_ = keyring::Keyring::create(db_root_ + "/keyring");
{
auto S = load_global_config();
if (S.is_error()) {
LOG(FATAL) << "Failed to load global config: " << S;
}
}
{
auto S = load_daemon_config();
if (S.is_error()) {
LOG(FATAL) << "Failed to load daemon config: " << S;
}
}
init_adnl();
class Callback : public StorageManager::Callback {
public:
explicit Callback(td::actor::ActorId<StorageDaemon> actor) : actor_(std::move(actor)) {
}
void on_ready() override {
td::actor::send_closure(actor_, &StorageDaemon::inited_storage_manager);
}
private:
td::actor::ActorId<StorageDaemon> actor_;
};
manager_ = td::actor::create_actor<StorageManager>("storage", local_id_, db_root_ + "/torrent",
td::make_unique<Callback>(actor_id(this)), client_mode_,
adnl_.get(), rldp_.get(), overlays_.get());
}
td::Status load_global_config() {
TRY_RESULT_PREFIX(conf_data, td::read_file(global_config_), "failed to read: ");
TRY_RESULT_PREFIX(conf_json, td::json_decode(conf_data.as_slice()), "failed to parse json: ");
ton_api::config_global conf;
TRY_STATUS_PREFIX(ton_api::from_json(conf, conf_json.get_object()), "json does not fit TL scheme: ");
if (!conf.dht_) {
return td::Status::Error(ErrorCode::error, "does not contain [dht] section");
}
TRY_RESULT_PREFIX(dht, dht::Dht::create_global_config(std::move(conf.dht_)), "bad [dht] section: ");
dht_config_ = std::move(dht);
return td::Status::OK();
}
td::Status load_daemon_config() {
daemon_config_ = create_tl_object<ton_api::storage_daemon_config>();
auto r_conf_data = td::read_file(daemon_config_file());
if (r_conf_data.is_ok()) {
auto conf_data = r_conf_data.move_as_ok();
TRY_RESULT_PREFIX(conf_json, td::json_decode(conf_data.as_slice()), "failed to parse json: ");
TRY_STATUS_PREFIX(ton_api::from_json(*daemon_config_, conf_json.get_object()), "json does not fit TL scheme: ");
return td::Status::OK();
}
std::string keys_dir = db_root_ + "/cli-keys/";
LOG(INFO) << "First launch, storing keys for storage-daemon-cli to " << keys_dir;
td::mkdir(keys_dir).ensure();
auto generate_public_key = [&]() -> PublicKey {
auto pk = PrivateKey{privkeys::Ed25519::random()};
auto pub = pk.compute_public_key();
td::actor::send_closure(keyring_, &keyring::Keyring::add_key, std::move(pk), false, [](td::Unit) {});
return pub;
};
{
// Server key
daemon_config_->server_key_ = generate_public_key().tl();
TRY_STATUS(td::write_file(keys_dir + "server.pub", serialize_tl_object(daemon_config_->server_key_, true)));
}
{
// Client key
auto pk = PrivateKey{privkeys::Ed25519::random()};
daemon_config_->cli_key_hash_ = pk.compute_short_id().bits256_value();
TRY_STATUS(td::write_file(keys_dir + "client", serialize_tl_object(pk.tl(), true)));
}
daemon_config_->adnl_id_ = generate_public_key().tl();
daemon_config_->dht_id_ = generate_public_key().tl();
return save_daemon_config();
}
td::Status save_daemon_config() {
auto s = td::json_encode<std::string>(td::ToJson(*daemon_config_), true);
TRY_STATUS_PREFIX(td::write_file(daemon_config_file(), s), "Failed to write daemon config: ");
return td::Status::OK();
}
void init_adnl() {
CHECK(ip_addr_.is_valid());
adnl_network_manager_ = adnl::AdnlNetworkManager::create(static_cast<td::uint16>(ip_addr_.get_port()));
adnl_ = adnl::Adnl::create(db_root_, keyring_.get());
td::actor::send_closure(adnl_, &adnl::Adnl::register_network_manager, adnl_network_manager_.get());
adnl::AdnlCategoryMask cat_mask;
cat_mask[0] = true;
td::actor::send_closure(adnl_network_manager_, &adnl::AdnlNetworkManager::add_self_addr, ip_addr_,
std::move(cat_mask), 0);
adnl::AdnlAddressList addr_list;
if (!client_mode_) {
addr_list.add_udp_address(ip_addr_).ensure();
}
addr_list.set_version(static_cast<td::int32>(td::Clocks::system()));
addr_list.set_reinit_date(adnl::Adnl::adnl_start_time());
adnl::AdnlNodeIdFull local_id_full = adnl::AdnlNodeIdFull::create(daemon_config_->adnl_id_).move_as_ok();
local_id_ = local_id_full.compute_short_id();
td::actor::send_closure(adnl_, &adnl::Adnl::add_id, local_id_full, addr_list, static_cast<td::uint8>(0));
adnl::AdnlNodeIdFull dht_id_full = adnl::AdnlNodeIdFull::create(daemon_config_->dht_id_).move_as_ok();
dht_id_ = dht_id_full.compute_short_id();
td::actor::send_closure(adnl_, &adnl::Adnl::add_id, dht_id_full, addr_list, static_cast<td::uint8>(0));
if (client_mode_) {
auto D = dht::Dht::create_client(dht_id_, db_root_, dht_config_, keyring_.get(), adnl_.get());
D.ensure();
dht_ = D.move_as_ok();
} else {
auto D = dht::Dht::create(dht_id_, db_root_, dht_config_, keyring_.get(), adnl_.get());
D.ensure();
dht_ = D.move_as_ok();
}
td::actor::send_closure(adnl_, &adnl::Adnl::register_dht_node, dht_.get());
rldp_ = ton_rldp::Rldp::create(adnl_.get());
td::actor::send_closure(rldp_, &ton_rldp::Rldp::add_id, local_id_);
overlays_ = overlay::Overlays::create(db_root_, keyring_.get(), adnl_.get(), dht_.get());
}
void inited_storage_manager() {
if (enable_storage_provider_) {
if (!daemon_config_->provider_address_.empty()) {
auto provider_account = ContractAddress::parse(daemon_config_->provider_address_).move_as_ok();
init_tonlib_client();
provider_ = td::actor::create_actor<StorageProvider>("provider", provider_account, db_root_ + "/provider",
tonlib_client_.get(), manager_.get(), keyring_.get());
} else {
LOG(WARNING) << "Storage provider account is not set, it can be set in storage-daemon-cli";
}
}
init_control_interface();
}
void init_control_interface() {
if (control_port_ == 0) {
return;
}
auto adnl_id_full = adnl::AdnlNodeIdFull::create(daemon_config_->server_key_).move_as_ok();
auto adnl_id = adnl_id_full.compute_short_id();
td::actor::send_closure(adnl_, &adnl::Adnl::add_id, adnl_id_full, adnl::AdnlAddressList(),
static_cast<td::uint8>(255));
class Callback : public adnl::Adnl::Callback {
public:
explicit Callback(td::actor::ActorId<StorageDaemon> id) : self_id_(id) {
}
void receive_message(adnl::AdnlNodeIdShort src, ton::adnl::AdnlNodeIdShort dst, td::BufferSlice data) override {
}
void receive_query(adnl::AdnlNodeIdShort src, adnl::AdnlNodeIdShort dst, td::BufferSlice data,
td::Promise<td::BufferSlice> promise) override {
td::actor::send_closure(self_id_, &StorageDaemon::process_control_query, src, std::move(data),
std::move(promise));
}
private:
td::actor::ActorId<StorageDaemon> self_id_;
};
td::actor::send_closure(adnl_, &adnl::Adnl::subscribe, adnl_id, "", std::make_unique<Callback>(actor_id(this)));
td::actor::send_closure(adnl_, &adnl::Adnl::create_ext_server, std::vector<adnl::AdnlNodeIdShort>{adnl_id},
std::vector<td::uint16>{control_port_},
[SelfId = actor_id(this)](td::Result<td::actor::ActorOwn<adnl::AdnlExtServer>> R) {
if (R.is_error()) {
LOG(ERROR) << "Failed to init control interface: " << R.move_as_error();
return;
}
td::actor::send_closure(SelfId, &StorageDaemon::created_ext_server, R.move_as_ok());
});
}
void created_ext_server(td::actor::ActorOwn<adnl::AdnlExtServer> ext_server) {
ext_server_ = std::move(ext_server);
LOG(INFO) << "Started control interface on port " << control_port_;
}
void process_control_query(adnl::AdnlNodeIdShort src, td::BufferSlice data, td::Promise<td::BufferSlice> promise) {
promise = [promise = std::move(promise)](td::Result<td::BufferSlice> R) mutable {
if (R.is_error()) {
promise.set_value(create_query_error(R.move_as_error()));
} else {
promise.set_value(R.move_as_ok());
}
};
if (src.bits256_value() != daemon_config_->cli_key_hash_) {
promise.set_error(td::Status::Error("Not authorized"));
return;
}
auto F = fetch_tl_object<ton_api::Function>(data, true);
if (F.is_error()) {
promise.set_error(F.move_as_error_prefix("failed to parse control query: "));
return;
}
auto f = F.move_as_ok();
LOG(DEBUG) << "Running control query " << f->get_id();
ton_api::downcast_call(*f, [&](auto &obj) { run_control_query(obj, std::move(promise)); });
}
void run_control_query(ton_api::storage_daemon_setVerbosity &query, td::Promise<td::BufferSlice> promise) {
if (query.verbosity_ < 0 || query.verbosity_ > 10) {
promise.set_value(create_query_error("verbosity should be in range [0..10]"));
return;
}
SET_VERBOSITY_LEVEL(VERBOSITY_NAME(FATAL) + query.verbosity_);
promise.set_result(create_serialize_tl_object<ton_api::storage_daemon_success>());
}
void run_control_query(ton_api::storage_daemon_createTorrent &query, td::Promise<td::BufferSlice> promise) {
// Run in a separate thread
delay_action(
[promise = std::move(promise), manager = manager_.get(), query = std::move(query)]() mutable {
Torrent::Creator::Options options;
options.piece_size = 128 * 1024;
options.description = std::move(query.description_);
TRY_RESULT_PROMISE(promise, torrent, Torrent::Creator::create_from_path(std::move(options), query.path_));
td::Bits256 hash = torrent.get_hash();
td::actor::send_closure(manager, &StorageManager::add_torrent, std::move(torrent), false,
[manager, hash, promise = std::move(promise)](td::Result<td::Unit> R) mutable {
if (R.is_error()) {
promise.set_error(R.move_as_error());
} else {
get_torrent_info_full_serialized(manager, hash, std::move(promise));
}
});
},
td::Timestamp::now());
}
void run_control_query(ton_api::storage_daemon_addByHash &query, td::Promise<td::BufferSlice> promise) {
td::Bits256 hash = query.hash_;
bool start_download_now = query.start_download_ && query.priorities_.empty();
td::actor::send_closure(
manager_, &StorageManager::add_torrent_by_hash, hash, std::move(query.root_dir_), start_download_now,
query_add_torrent_cont(hash, query.start_download_, std::move(query.priorities_), std::move(promise)));
}
void run_control_query(ton_api::storage_daemon_addByMeta &query, td::Promise<td::BufferSlice> promise) {
TRY_RESULT_PROMISE(promise, meta, TorrentMeta::deserialize(query.meta_));
td::Bits256 hash(meta.info.get_hash());
bool start_download_now = query.start_download_ && query.priorities_.empty();
td::actor::send_closure(
manager_, &StorageManager::add_torrent_by_meta, std::move(meta), std::move(query.root_dir_), start_download_now,
query_add_torrent_cont(hash, query.start_download_, std::move(query.priorities_), std::move(promise)));
}
td::Promise<td::Unit> query_add_torrent_cont(td::Bits256 hash, bool start_download,
std::vector<tl_object_ptr<ton_api::storage_PriorityAction>> priorities,
td::Promise<td::BufferSlice> promise) {
return [manager = manager_.get(), hash, start_download = start_download, priorities = std::move(priorities),
promise = std::move(promise)](td::Result<td::Unit> R) mutable {
if (R.is_error()) {
promise.set_error(R.move_as_error());
return;
}
if (!priorities.empty()) {
for (auto &p : priorities) {
ton_api::downcast_call(
*p, td::overloaded(
[&](ton_api::storage_priorityAction_all &obj) {
td::actor::send_closure(manager, &StorageManager::set_all_files_priority, hash,
(td::uint8)obj.priority_, [](td::Result<bool>) {});
},
[&](ton_api::storage_priorityAction_idx &obj) {
td::actor::send_closure(manager, &StorageManager::set_file_priority_by_idx, hash, obj.idx_,
(td::uint8)obj.priority_, [](td::Result<bool>) {});
},
[&](ton_api::storage_priorityAction_name &obj) {
td::actor::send_closure(manager, &StorageManager::set_file_priority_by_name, hash,
std::move(obj.name_), (td::uint8)obj.priority_,
[](td::Result<bool>) {});
}));
}
if (start_download) {
td::actor::send_closure(manager, &StorageManager::set_active_download, hash, true,
[](td::Result<td::Unit>) {});
}
}
get_torrent_info_full_serialized(manager, hash, std::move(promise));
};
}
void run_control_query(ton_api::storage_daemon_setActiveDownload &query, td::Promise<td::BufferSlice> promise) {
td::actor::send_closure(
manager_, &StorageManager::set_active_download, query.hash_, query.active_,
promise.wrap([](td::Unit &&) { return create_serialize_tl_object<ton_api::storage_daemon_success>(); }));
}
void run_control_query(ton_api::storage_daemon_getTorrents &query, td::Promise<td::BufferSlice> promise) {
td::actor::send_closure(
manager_, &StorageManager::get_all_torrents,
[manager = manager_.get(), promise = std::move(promise)](td::Result<std::vector<td::Bits256>> R) mutable {
if (R.is_error()) {
promise.set_error(R.move_as_error());
return;
}
std::vector<td::Bits256> torrents = R.move_as_ok();
auto result = std::make_shared<std::vector<tl_object_ptr<ton_api::storage_daemon_torrent>>>(torrents.size());
td::MultiPromise mp;
auto ig = mp.init_guard();
for (size_t i = 0; i < torrents.size(); ++i) {
get_torrent_info_short(manager, torrents[i],
[i, result, promise = ig.get_promise()](
td::Result<tl_object_ptr<ton_api::storage_daemon_torrent>> R) mutable {
if (R.is_ok()) {
result->at(i) = R.move_as_ok();
}
promise.set_result(td::Unit());
});
}
ig.add_promise([promise = std::move(promise), result](td::Result<td::Unit> R) mutable {
if (R.is_error()) {
promise.set_error(R.move_as_error());
return;
}
auto v = std::move(*result);
v.erase(std::remove(v.begin(), v.end(), nullptr), v.end());
promise.set_result(create_serialize_tl_object<ton_api::storage_daemon_torrentList>(std::move(v)));
});
});
}
void run_control_query(ton_api::storage_daemon_getTorrentFull &query, td::Promise<td::BufferSlice> promise) {
get_torrent_info_full_serialized(manager_.get(), query.hash_, std::move(promise));
}
void run_control_query(ton_api::storage_daemon_getTorrentMeta &query, td::Promise<td::BufferSlice> promise) {
td::actor::send_closure(
manager_, &StorageManager::with_torrent, query.hash_,
promise.wrap([](NodeActor::NodeState state) -> td::Result<td::BufferSlice> {
Torrent &torrent = state.torrent;
if (!torrent.inited_info()) {
return td::Status::Error("Torrent meta is not available");
}
std::string meta_str = torrent.get_meta(Torrent::GetMetaOptions().with_proof_depth_limit(10)).serialize();
return create_serialize_tl_object<ton_api::storage_daemon_torrentMeta>(td::BufferSlice(meta_str));
}));
}
void run_control_query(ton_api::storage_daemon_getTorrentPeers &query, td::Promise<td::BufferSlice> promise) {
td::actor::send_closure(manager_, &StorageManager::get_peers_info, query.hash_,
promise.wrap([](tl_object_ptr<ton_api::storage_daemon_peerList> obj) -> td::BufferSlice {
return serialize_tl_object(obj, true);
}));
}
void run_control_query(ton_api::storage_daemon_setFilePriorityAll &query, td::Promise<td::BufferSlice> promise) {
TRY_RESULT_PROMISE(promise, priority, td::narrow_cast_safe<td::uint8>(query.priority_));
td::actor::send_closure(manager_, &StorageManager::set_all_files_priority, query.hash_, priority,
promise.wrap([](bool done) -> td::Result<td::BufferSlice> {
if (done) {
return create_serialize_tl_object<ton_api::storage_daemon_prioritySet>();
} else {
return create_serialize_tl_object<ton_api::storage_daemon_priorityPending>();
}
}));
}
void run_control_query(ton_api::storage_daemon_setFilePriorityByIdx &query, td::Promise<td::BufferSlice> promise) {
TRY_RESULT_PROMISE(promise, priority, td::narrow_cast_safe<td::uint8>(query.priority_));
td::actor::send_closure(manager_, &StorageManager::set_file_priority_by_idx, query.hash_, query.idx_, priority,
promise.wrap([](bool done) -> td::Result<td::BufferSlice> {
if (done) {
return create_serialize_tl_object<ton_api::storage_daemon_prioritySet>();
} else {
return create_serialize_tl_object<ton_api::storage_daemon_priorityPending>();
}
}));
}
void run_control_query(ton_api::storage_daemon_setFilePriorityByName &query, td::Promise<td::BufferSlice> promise) {
TRY_RESULT_PROMISE(promise, priority, td::narrow_cast_safe<td::uint8>(query.priority_));
td::actor::send_closure(manager_, &StorageManager::set_file_priority_by_name, query.hash_, std::move(query.name_),
priority, promise.wrap([](bool done) -> td::Result<td::BufferSlice> {
if (done) {
return create_serialize_tl_object<ton_api::storage_daemon_prioritySet>();
} else {
return create_serialize_tl_object<ton_api::storage_daemon_priorityPending>();
}
}));
}
void run_control_query(ton_api::storage_daemon_removeTorrent &query, td::Promise<td::BufferSlice> promise) {
td::actor::send_closure(
manager_, &StorageManager::remove_torrent, query.hash_, query.remove_files_,
promise.wrap([](td::Unit &&) { return create_serialize_tl_object<ton_api::storage_daemon_success>(); }));
}
void run_control_query(ton_api::storage_daemon_loadFrom &query, td::Promise<td::BufferSlice> promise) {
td::optional<TorrentMeta> meta;
if (!query.meta_.empty()) {
TRY_RESULT_PROMISE_ASSIGN(promise, meta, TorrentMeta::deserialize(query.meta_));
}
td::actor::send_closure(
manager_, &StorageManager::load_from, query.hash_, std::move(meta), std::move(query.path_),
[manager = manager_.get(), hash = query.hash_, promise = std::move(promise)](td::Result<td::Unit> R) mutable {
if (R.is_error()) {
promise.set_error(R.move_as_error());
} else {
get_torrent_info_short(manager, hash, promise.wrap([](tl_object_ptr<ton_api::storage_daemon_torrent> obj) {
return serialize_tl_object(obj, true);
}));
}
});
}
void run_control_query(ton_api::storage_daemon_getNewContractMessage &query, td::Promise<td::BufferSlice> promise) {
td::Promise<std::pair<td::RefInt256, td::uint32>> P =
[promise = std::move(promise), hash = query.hash_, query_id = query.query_id_,
manager = manager_.get()](td::Result<std::pair<td::RefInt256, td::uint32>> R) mutable {
TRY_RESULT_PROMISE(promise, r, std::move(R));
td::actor::send_closure(
manager, &StorageManager::with_torrent, hash,
promise.wrap([r = std::move(r), query_id](NodeActor::NodeState state) -> td::Result<td::BufferSlice> {
Torrent &torrent = state.torrent;
if (!torrent.is_completed()) {
return td::Status::Error("Torrent is not complete");
}
TRY_RESULT(microchunk_tree, MicrochunkTree::Builder::build_for_torrent(torrent, 1LL << 60));
td::Ref<vm::Cell> msg = create_new_contract_message_body(
torrent.get_info().as_cell(), microchunk_tree.get_root_hash(), query_id, r.first, r.second);
return create_serialize_tl_object<ton_api::storage_daemon_newContractMessage>(
vm::std_boc_serialize(msg).move_as_ok(), r.first->to_dec_string(), r.second);
}));
};
ton_api::downcast_call(*query.params_,
td::overloaded(
[&](ton_api::storage_daemon_newContractParams &obj) {
td::RefInt256 rate = td::string_to_int256(obj.rate_);
if (rate.is_null() || rate->sgn() < 0) {
P.set_error(td::Status::Error("Invalid rate"));
return;
}
P.set_result(std::make_pair(std::move(rate), (td::uint32)obj.max_span_));
},
[&](ton_api::storage_daemon_newContractParamsAuto &obj) {
TRY_RESULT_PROMISE(P, address, ContractAddress::parse(obj.provider_address_));
init_tonlib_client();
StorageProvider::get_provider_params(
tonlib_client_.get(), address, P.wrap([](ProviderParams params) {
return std::make_pair(std::move(params.rate_per_mb_day), params.max_span);
}));
}));
}
void run_control_query(ton_api::storage_daemon_importPrivateKey &query, td::Promise<td::BufferSlice> promise) {
auto pk = ton::PrivateKey{query.key_};
td::actor::send_closure(keyring_, &ton::keyring::Keyring::add_key, std::move(pk), false,
promise.wrap([hash = pk.compute_short_id()](td::Unit) mutable {
return create_serialize_tl_object<ton_api::storage_daemon_keyHash>(hash.bits256_value());
}));
}
void run_control_query(ton_api::storage_daemon_deployProvider &query, td::Promise<td::BufferSlice> promise) {
if (!enable_storage_provider_) {
promise.set_error(
td::Status::Error("Storage provider is not enabled, run daemon with --storage-provider to enable it"));
return;
}
if (!provider_.empty() || deploying_provider_) {
promise.set_error(td::Status::Error("Storage provider already exists"));
return;
}
TRY_RESULT_PROMISE_ASSIGN(promise, deploying_provider_, generate_fabric_contract(keyring_.get()));
promise.set_result(create_serialize_tl_object<ton_api::storage_daemon_providerAddress>(
deploying_provider_.value().address.to_string()));
do_deploy_provider();
}
void run_control_query(ton_api::storage_daemon_initProvider &query, td::Promise<td::BufferSlice> promise) {
if (!enable_storage_provider_) {
promise.set_error(
td::Status::Error("Storage provider is not enabled, run daemon with --storage-provider to enable it"));
return;
}
if (!provider_.empty() || deploying_provider_) {
promise.set_error(td::Status::Error("Storage provider already exists"));
return;
}
TRY_RESULT_PROMISE_PREFIX(promise, address, ContractAddress::parse(query.account_address_), "Invalid address: ");
do_init_provider(
address, promise.wrap([](td::Unit) { return create_serialize_tl_object<ton_api::storage_daemon_success>(); }));
}
void do_init_provider(ContractAddress address, td::Promise<td::Unit> promise, bool deploying = false) {
if (deploying && (!deploying_provider_ || deploying_provider_.value().address != address)) {
promise.set_error(td::Status::Error("Deploying was cancelled"));
return;
}
daemon_config_->provider_address_ = address.to_string();
TRY_STATUS_PROMISE(promise, save_daemon_config());
init_tonlib_client();
provider_ = td::actor::create_actor<StorageProvider>("provider", address, db_root_ + "/provider",
tonlib_client_.get(), manager_.get(), keyring_.get());
deploying_provider_ = {};
promise.set_result(td::Unit());
}
void do_deploy_provider() {
if (!deploying_provider_) {
return;
}
init_tonlib_client();
check_contract_exists(
deploying_provider_.value().address, tonlib_client_.get(),
[SelfId = actor_id(this), client = tonlib_client_.get(),
init = deploying_provider_.value()](td::Result<bool> R) mutable {
if (R.is_error()) {
LOG(INFO) << "Deploying storage contract: " << R.move_as_error();
delay_action([=]() { td::actor::send_closure(SelfId, &StorageDaemon::do_deploy_provider); },
td::Timestamp::in(5.0));
return;
}
if (R.ok()) {
LOG(INFO) << "Deploying storage contract: DONE";
td::actor::send_closure(
SelfId, &StorageDaemon::do_init_provider, init.address, [](td::Result<td::Unit>) {}, true);
return;
}
ContractAddress address = init.address;
td::BufferSlice state_init_boc = vm::std_boc_serialize(init.state_init).move_as_ok();
td::BufferSlice body_boc = vm::std_boc_serialize(init.msg_body).move_as_ok();
auto query = create_tl_object<tonlib_api::raw_createAndSendMessage>(
create_tl_object<tonlib_api::accountAddress>(address.to_string()), state_init_boc.as_slice().str(),
body_boc.as_slice().str());
td::actor::send_closure(
client, &tonlib::TonlibClientWrapper::send_request<tonlib_api::raw_createAndSendMessage>,
std::move(query), [=](td::Result<tl_object_ptr<tonlib_api::ok>> R) {
if (R.is_error()) {
LOG(INFO) << "Deploying storage contract: " << R.move_as_error();
}
delay_action([=]() { td::actor::send_closure(SelfId, &StorageDaemon::do_deploy_provider); },
td::Timestamp::in(5.0));
});
});
}
void run_control_query(ton_api::storage_daemon_removeStorageProvider &query, td::Promise<td::BufferSlice> promise) {
if (!enable_storage_provider_) {
promise.set_error(td::Status::Error("No storage provider"));
return;
}
if (provider_.empty() && !deploying_provider_) {
promise.set_error(td::Status::Error("No storage provider"));
return;
}
daemon_config_->provider_address_ = "";
TRY_STATUS_PROMISE(promise, save_daemon_config());
deploying_provider_ = {};
provider_ = {};
auto S = td::rmrf(db_root_ + "/provider");
if (S.is_error()) {
LOG(ERROR) << "Failed to delete provider directory: " << S;
}
promise.set_result(create_serialize_tl_object<ton_api::storage_daemon_success>());
}
void run_control_query(ton_api::storage_daemon_getProviderParams &query, td::Promise<td::BufferSlice> promise) {
if (!query.address_.empty()) {
TRY_RESULT_PROMISE_PREFIX(promise, address, ContractAddress::parse(query.address_), "Invalid address: ");
init_tonlib_client();
StorageProvider::get_provider_params(tonlib_client_.get(), address, promise.wrap([](ProviderParams params) {
return serialize_tl_object(params.tl(), true);
}));
}
if (provider_.empty()) {
promise.set_error(td::Status::Error("No storage provider"));
return;
}
td::actor::send_closure(provider_, &StorageProvider::get_params, promise.wrap([](ProviderParams params) {
return serialize_tl_object(params.tl(), true);
}));
}
void run_control_query(ton_api::storage_daemon_setProviderParams &query, td::Promise<td::BufferSlice> promise) {
if (provider_.empty()) {
promise.set_error(td::Status::Error("No storage provider"));
return;
}
TRY_RESULT_PROMISE(promise, params, ProviderParams::create(query.params_));
td::actor::send_closure(
provider_, &StorageProvider::set_params, std::move(params),
promise.wrap([](td::Unit) mutable { return create_serialize_tl_object<ton_api::storage_daemon_success>(); }));
}
template <class T>
void run_control_query(T &query, td::Promise<td::BufferSlice> promise) {
promise.set_error(td::Status::Error("unknown query"));
}
void run_control_query(ton_api::storage_daemon_getProviderInfo &query, td::Promise<td::BufferSlice> promise) {
if (provider_.empty()) {
promise.set_error(td::Status::Error("No storage provider"));
return;
}
td::actor::send_closure(provider_, &StorageProvider::get_provider_info, query.with_balances_, query.with_contracts_,
promise.wrap([](tl_object_ptr<ton_api::storage_daemon_providerInfo> info) {
return serialize_tl_object(info, true);
}));
}
void run_control_query(ton_api::storage_daemon_setProviderConfig &query, td::Promise<td::BufferSlice> promise) {
if (provider_.empty()) {
promise.set_error(td::Status::Error("No storage provider"));
return;
}
td::actor::send_closure(
provider_, &StorageProvider::set_provider_config, StorageProvider::Config(query.config_),
promise.wrap([](td::Unit) { return create_serialize_tl_object<ton_api::storage_daemon_success>(); }));
}
void run_control_query(ton_api::storage_daemon_withdraw &query, td::Promise<td::BufferSlice> promise) {
if (provider_.empty()) {
promise.set_error(td::Status::Error("No storage provider"));
return;
}
TRY_RESULT_PROMISE_PREFIX(promise, address, ContractAddress::parse(query.contract_), "Invalid address: ");
td::actor::send_closure(provider_, &StorageProvider::withdraw, address, promise.wrap([](td::Unit) {
return create_serialize_tl_object<ton_api::storage_daemon_success>();
}));
}
void run_control_query(ton_api::storage_daemon_sendCoins &query, td::Promise<td::BufferSlice> promise) {
if (provider_.empty()) {
promise.set_error(td::Status::Error("No storage provider"));
return;
}
TRY_RESULT_PROMISE_PREFIX(promise, address, ContractAddress::parse(query.address_), "Invalid address: ");
td::RefInt256 amount = td::string_to_int256(query.amount_);
if (amount.is_null()) {
promise.set_error(td::Status::Error("Invalid amount"));
return;
}
td::actor::send_closure(
provider_, &StorageProvider::send_coins, address, amount, std::move(query.message_),
promise.wrap([](td::Unit) { return create_serialize_tl_object<ton_api::storage_daemon_success>(); }));
}
void run_control_query(ton_api::storage_daemon_closeStorageContract &query, td::Promise<td::BufferSlice> promise) {
if (provider_.empty()) {
promise.set_error(td::Status::Error("No storage provider"));
return;
}
TRY_RESULT_PROMISE_PREFIX(promise, address, ContractAddress::parse(query.address_), "Invalid address: ");
td::actor::send_closure(provider_, &StorageProvider::close_storage_contract, address, promise.wrap([](td::Unit) {
return create_serialize_tl_object<ton_api::storage_daemon_success>();
}));
}
private:
static void fill_torrent_info_short(Torrent &torrent, ton_api::storage_daemon_torrent &obj) {
obj.hash_ = torrent.get_hash();
obj.root_dir_ = torrent.get_root_dir();
if (torrent.inited_info()) {
const Torrent::Info &info = torrent.get_info();
obj.flags_ = 1;
if (torrent.inited_header()) {
obj.flags_ |= 2;
}
obj.total_size_ = info.file_size;
obj.description_ = info.description;
if (torrent.inited_header()) {
obj.included_size_ = torrent.get_included_size();
obj.files_count_ = torrent.get_files_count().unwrap();
obj.dir_name_ = torrent.get_header().dir_name;
}
obj.downloaded_size_ = torrent.get_included_ready_size();
obj.completed_ = torrent.is_completed();
} else {
obj.flags_ = 0;
obj.downloaded_size_ = 0;
obj.completed_ = false;
}
if (torrent.get_fatal_error().is_error()) {
obj.flags_ |= 4;
obj.fatal_error_ = torrent.get_fatal_error().message().str();
}
}
static void fill_torrent_info_full(Torrent &torrent, ton_api::storage_daemon_torrentFull &obj) {
if (!obj.torrent_) {
obj.torrent_ = create_tl_object<ton_api::storage_daemon_torrent>();
}
fill_torrent_info_short(torrent, *obj.torrent_);
obj.files_.clear();
auto count = torrent.get_files_count();
if (!count) {
return;
}
for (size_t i = 0; i < count.value(); ++i) {
auto file = create_tl_object<ton_api::storage_daemon_fileInfo>();
file->name_ = torrent.get_file_name(i).str();
file->size_ = torrent.get_file_size(i);
file->downloaded_size_ = torrent.get_file_ready_size(i);
obj.files_.push_back(std::move(file));
}
}
static void get_torrent_info_short(td::actor::ActorId<StorageManager> manager, td::Bits256 hash,
td::Promise<tl_object_ptr<ton_api::storage_daemon_torrent>> promise) {
td::actor::send_closure(manager, &StorageManager::with_torrent, hash,
[promise = std::move(promise)](td::Result<NodeActor::NodeState> R) mutable {
if (R.is_error()) {
promise.set_result(R.move_as_error());
return;
}
auto state = R.move_as_ok();
auto obj = create_tl_object<ton_api::storage_daemon_torrent>();
fill_torrent_info_short(state.torrent, *obj);
obj->active_download_ = state.active_download;
obj->download_speed_ = state.download_speed;
obj->upload_speed_ = state.upload_speed;
promise.set_result(std::move(obj));
});
}
static void get_torrent_info_full_serialized(td::actor::ActorId<StorageManager> manager, td::Bits256 hash,
td::Promise<td::BufferSlice> promise) {
td::actor::send_closure(manager, &StorageManager::with_torrent, hash,
[promise = std::move(promise)](td::Result<NodeActor::NodeState> R) mutable {
if (R.is_error()) {
promise.set_error(R.move_as_error());
} else {
auto state = R.move_as_ok();
auto obj = create_tl_object<ton_api::storage_daemon_torrentFull>();
fill_torrent_info_full(state.torrent, *obj);
obj->torrent_->active_download_ = state.active_download;
obj->torrent_->download_speed_ = state.download_speed;
obj->torrent_->upload_speed_ = state.upload_speed;
for (size_t i = 0; i < obj->files_.size(); ++i) {
obj->files_[i]->priority_ =
(i < state.file_priority.size() ? state.file_priority[i] : 1);
}
promise.set_result(serialize_tl_object(obj, true));
}
});
}
td::IPAddress ip_addr_;
bool client_mode_;
std::string global_config_;
std::string db_root_;
td::uint16 control_port_;
bool enable_storage_provider_;
tl_object_ptr<ton_api::storage_daemon_config> daemon_config_;
std::shared_ptr<dht::DhtGlobalConfig> dht_config_;
adnl::AdnlNodeIdShort local_id_;
adnl::AdnlNodeIdShort dht_id_;
td::actor::ActorOwn<keyring::Keyring> keyring_;
td::actor::ActorOwn<adnl::AdnlNetworkManager> adnl_network_manager_;
td::actor::ActorOwn<adnl::Adnl> adnl_;
td::actor::ActorOwn<dht::Dht> dht_;
td::actor::ActorOwn<ton_rldp::Rldp> rldp_;
td::actor::ActorOwn<overlay::Overlays> overlays_;
td::actor::ActorOwn<adnl::AdnlExtServer> ext_server_;
td::actor::ActorOwn<StorageManager> manager_;
td::actor::ActorOwn<tonlib::TonlibClientWrapper> tonlib_client_;
td::actor::ActorOwn<StorageProvider> provider_;
td::optional<FabricContractInit> deploying_provider_;
void init_tonlib_client() {
if (!tonlib_client_.empty()) {
return;
}
auto r_conf_data = td::read_file(global_config_);
r_conf_data.ensure();
auto tonlib_options = tonlib_api::make_object<tonlib_api::options>(
tonlib_api::make_object<tonlib_api::config>(r_conf_data.move_as_ok().as_slice().str(), "", false, false),
tonlib_api::make_object<tonlib_api::keyStoreTypeInMemory>());
tonlib_client_ = td::actor::create_actor<tonlib::TonlibClientWrapper>("tonlibclient", std::move(tonlib_options));
}
std::string daemon_config_file() {
return db_root_ + "/config.json";
}
};
int main(int argc, char *argv[]) {
SET_VERBOSITY_LEVEL(verbosity_WARNING);
td::set_default_failure_signal_handler().ensure();
td::unique_ptr<td::LogInterface> logger_;
SCOPE_EXIT {
td::log_interface = td::default_log_interface;
};
td::IPAddress ip_addr;
bool client_mode = false;
std::string global_config, db_root;
td::uint16 control_port = 0;
bool enable_storage_provider = false;
td::OptionParser p;
p.set_description("Server for seeding and downloading bags of files (torrents)\n");
p.add_option('v', "verbosity", "set verbosity level", [&](td::Slice arg) {
int v = VERBOSITY_NAME(FATAL) + (td::to_integer<int>(arg));
SET_VERBOSITY_LEVEL(v);
});
p.add_option('V', "version", "shows storage-daemon build information", [&]() {
std::cout << "storage-daemon build information: [ Commit: " << GitMetadata::CommitSHA1()
<< ", Date: " << GitMetadata::CommitDate() << "]\n";
std::exit(0);
});
p.add_option('h', "help", "prints a help message", [&]() {
char b[10240];
td::StringBuilder sb(td::MutableSlice{b, 10000});
sb << p;
std::cout << sb.as_cslice().c_str();
std::exit(2);
});
p.add_checked_option('I', "ip", "set <ip>:<port> for adnl. :<port> for client mode",
[&](td::Slice arg) -> td::Status {
if (ip_addr.is_valid()) {
return td::Status::Error("Duplicate ip address");
}
if (!arg.empty() && arg[0] == ':') {
TRY_RESULT(port, td::to_integer_safe<td::uint16>(arg.substr(1)));
TRY_STATUS(ip_addr.init_ipv4_port("127.0.0.1", port));
client_mode = true;
} else {
TRY_STATUS(ip_addr.init_host_port(arg.str()));
}
return td::Status::OK();
});
p.add_checked_option('p', "control-port", "port for control interface", [&](td::Slice arg) -> td::Status {
TRY_RESULT_ASSIGN(control_port, td::to_integer_safe<td::uint16>(arg));
return td::Status::OK();
});
p.add_option('C', "global-config", "global TON configuration file",
[&](td::Slice arg) { global_config = arg.str(); });
p.add_option('D', "db", "db root", [&](td::Slice arg) { db_root = arg.str(); });
p.add_option('d', "daemonize", "set SIGHUP", [&]() {
td::set_signal_handler(td::SignalType::HangUp, [](int sig) {
#if TD_DARWIN || TD_LINUX
close(0);
setsid();
#endif
}).ensure();
});
p.add_option('l', "logname", "log to file", [&](td::Slice fname) {
logger_ = td::FileLog::create(fname.str()).move_as_ok();
td::log_interface = logger_.get();
});
p.add_option('P', "storage-provider", "run storage provider", [&]() { enable_storage_provider = true; });
td::actor::Scheduler scheduler({7});
scheduler.run_in_context([&] {
p.run(argc, argv).ensure();
td::actor::create_actor<StorageDaemon>("storage-daemon", ip_addr, client_mode, global_config, db_root, control_port,
enable_storage_provider)
.release();
});
while (scheduler.run(1)) {
}
}