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:
parent
434dc487a4
commit
360ef54e6b
75 changed files with 8872 additions and 1148 deletions
32
storage/storage-daemon/CMakeLists.txt
Normal file
32
storage/storage-daemon/CMakeLists.txt
Normal 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)
|
268
storage/storage-daemon/StorageManager.cpp
Normal file
268
storage/storage-daemon/StorageManager.cpp
Normal 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));
|
||||
}
|
107
storage/storage-daemon/StorageManager.h
Normal file
107
storage/storage-daemon/StorageManager.h
Normal 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);
|
||||
};
|
840
storage/storage-daemon/StorageProvider.cpp
Normal file
840
storage/storage-daemon/StorageProvider.cpp
Normal 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);
|
||||
}
|
127
storage/storage-daemon/StorageProvider.h
Normal file
127
storage/storage-daemon/StorageProvider.h
Normal 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);
|
||||
};
|
1
storage/storage-daemon/smartcont/.gitignore
vendored
Normal file
1
storage/storage-daemon/smartcont/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
provider-code.h
|
23
storage/storage-daemon/smartcont/README.md
Normal file
23
storage/storage-daemon/smartcont/README.md
Normal 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.
|
4
storage/storage-daemon/smartcont/build.sh
Executable file
4
storage/storage-daemon/smartcont/build.sh
Executable 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)
|
23
storage/storage-daemon/smartcont/constants.fc
Normal file
23
storage/storage-daemon/smartcont/constants.fc
Normal 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;
|
40
storage/storage-daemon/smartcont/embed-provider-code.cpp
Normal file
40
storage/storage-daemon/smartcont/embed-provider-code.cpp
Normal 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;
|
||||
}
|
BIN
storage/storage-daemon/smartcont/storage-contract-code.boc
Normal file
BIN
storage/storage-daemon/smartcont/storage-contract-code.boc
Normal file
Binary file not shown.
266
storage/storage-daemon/smartcont/storage-contract.fc
Normal file
266
storage/storage-daemon/smartcont/storage-contract.fc
Normal 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);
|
||||
}
|
421
storage/storage-daemon/smartcont/storage-contract.fif
Normal file
421
storage/storage-daemon/smartcont/storage-contract.fif
Normal 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
|
BIN
storage/storage-daemon/smartcont/storage-provider-code.boc
Normal file
BIN
storage/storage-daemon/smartcont/storage-provider-code.boc
Normal file
Binary file not shown.
227
storage/storage-daemon/smartcont/storage-provider.fc
Normal file
227
storage/storage-daemon/smartcont/storage-provider.fc
Normal 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);
|
||||
}
|
340
storage/storage-daemon/smartcont/storage-provider.fif
Normal file
340
storage/storage-daemon/smartcont/storage-provider.fif
Normal 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
|
487
storage/storage-daemon/smc-util.cpp
Normal file
487
storage/storage-daemon/smc-util.cpp
Normal 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};
|
||||
}));
|
||||
}
|
184
storage/storage-daemon/smc-util.h
Normal file
184
storage/storage-daemon/smc-util.h
Normal 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);
|
1747
storage/storage-daemon/storage-daemon-cli.cpp
Normal file
1747
storage/storage-daemon/storage-daemon-cli.cpp
Normal file
File diff suppressed because it is too large
Load diff
937
storage/storage-daemon/storage-daemon.cpp
Normal file
937
storage/storage-daemon/storage-daemon.cpp
Normal 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)) {
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue