mirror of
				https://github.com/ton-blockchain/ton
				synced 2025-03-09 15:40:10 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			862 lines
		
	
	
	
		
			40 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			862 lines
		
	
	
	
		
			40 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/*
 | 
						|
    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"
 | 
						|
 | 
						|
namespace ton {
 | 
						|
 | 
						|
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:
 | 
						|
        after_contract_downloaded(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);
 | 
						|
    if (cs.size() >= 32) {
 | 
						|
      long long op_code = cs.prefetch_ulong(32);
 | 
						|
      // const op::offer_storage_contract = 0x107c49ef; -- old versions
 | 
						|
      // const op::deploy_storage_contract = 0xe4748df1; -- new versions
 | 
						|
      if((op_code == 0x107c49ef) || (op_code == 0xe4748df1)) {
 | 
						|
        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,
 | 
						|
                          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);
 | 
						|
  after_contract_downloaded(address);
 | 
						|
}
 | 
						|
 | 
						|
void StorageProvider::after_contract_downloaded(ContractAddress address, td::Timestamp retry_until,
 | 
						|
                                                td::Timestamp retry_false_until) {
 | 
						|
  auto it = contracts_.find(address);
 | 
						|
  if (it == contracts_.end()) {
 | 
						|
    LOG(WARNING) << "Contract " << address.to_string() << " does not exist anymore";
 | 
						|
    return;
 | 
						|
  }
 | 
						|
  auto& contract = it->second;
 | 
						|
  td::actor::send_closure(storage_manager_, &StorageManager::set_active_upload, contract.torrent_hash, true,
 | 
						|
                          [SelfId = actor_id(this), address](td::Result<td::Unit> R) {
 | 
						|
                            if (R.is_error()) {
 | 
						|
                              LOG(ERROR) << "Set active upload: " << R.move_as_error();
 | 
						|
                              return;
 | 
						|
                            }
 | 
						|
                            LOG(DEBUG) << "Set active upload: OK";
 | 
						|
                          });
 | 
						|
  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::after_contract_downloaded,
 | 
						|
                                                                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::after_contract_downloaded,
 | 
						|
                                                              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::after_contract_downloaded, 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);
 | 
						|
}
 | 
						|
 | 
						|
}  // namespace ton
 |