/*
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 .
*/
#include "smc-util.h"
#include "td/utils/filesystem.h"
#include "keys/encryptor.h"
#include "smartcont/provider-code.h"
namespace ton {
static void smc_forget(td::actor::ActorId client, td::int64 id) {
auto query = create_tl_object(id);
td::actor::send_closure(client, &tonlib::TonlibClientWrapper::send_request, std::move(query),
[](td::Result> R) mutable {
if (R.is_error()) {
LOG(WARNING) << "smc_forget failed: " << R.move_as_error();
}
});
}
void run_get_method(ContractAddress address, td::actor::ActorId client, std::string method,
std::vector> args,
td::Promise>> promise) {
LOG(DEBUG) << "Running get method " << method << " on " << address.to_string();
auto query =
create_tl_object(create_tl_object(address.to_string()));
td::actor::send_closure(
client, &tonlib::TonlibClientWrapper::send_request, std::move(query),
[client, method = std::move(method), args = std::move(args),
promise = std::move(promise)](td::Result> R) mutable {
TRY_RESULT_PROMISE(promise, obj, std::move(R));
auto query = create_tl_object(
obj->id_, create_tl_object(std::move(method)), std::move(args));
td::actor::send_closure(
client, &tonlib::TonlibClientWrapper::send_request, std::move(query),
[client, id = obj->id_,
promise = std::move(promise)](td::Result> 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 client,
td::Promise promise) {
auto query =
create_tl_object(create_tl_object(address.to_string()));
td::actor::send_closure(
client, &tonlib::TonlibClientWrapper::send_request, std::move(query),
[client, promise = std::move(promise)](td::Result> R) mutable {
TRY_RESULT_PROMISE(promise, obj, std::move(R));
auto query = create_tl_object(obj->id_);
td::actor::send_closure(
client, &tonlib::TonlibClientWrapper::send_request, std::move(query),
[client, id = obj->id_,
promise = std::move(promise)](td::Result> 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 client,
td::Promise promise) {
auto query =
create_tl_object(create_tl_object(address.to_string()));
td::actor::send_closure(
client, &tonlib::TonlibClientWrapper::send_request, std::move(query),
promise.wrap([](tonlib_api::object_ptr r) -> td::Result {
return td::make_refint(r->balance_);
}));
}
FabricContractWrapper::FabricContractWrapper(ContractAddress address,
td::actor::ActorId client,
td::actor::ActorId keyring,
td::unique_ptr 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(create_tl_object(address_.to_string()));
td::actor::send_closure(
client_, &tonlib::TonlibClientWrapper::send_request, std::move(query),
[SelfId = actor_id(this)](td::Result> 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>(),
std::move(obj->last_transaction_id_), (td::uint32)obj->sync_utime_);
});
}
void FabricContractWrapper::load_last_transactions(std::vector> transactions,
tl_object_ptr 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(
nullptr, create_tl_object(address_.to_string()), std::move(next_id), 10, false);
td::actor::send_closure(
client_, &tonlib::TonlibClientWrapper::send_request, std::move(query),
[transactions = std::move(transactions), last_processed_lt = last_processed_lt_, SelfId = actor_id(this),
utime](td::Result> 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>, 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& 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(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 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& 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> args,
td::Promise>> promise) {
ton::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 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>> 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(stack[0]), "Invalid seqno: ");
TRY_RESULT_PREFIX_ASSIGN(subwallet_id, entry_to_int(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 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 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 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(
create_tl_object(address_.to_string()), "", std::move(body));
td::actor::send_closure(client_, &tonlib::TonlibClientWrapper::send_request,
std::move(query), [SelfId = actor_id(this)](td::Result> 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>*> 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 generate_fabric_contract(td::actor::ActorId 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 data = b.finalize_novm();
// _ split_depth:(Maybe (## 5)) special:(Maybe TickTock)
// code:(Maybe ^Cell) data:(Maybe ^Cell)
// library:(HashmapE 256 SimpleLib) = StateInit;
td::Ref 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 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 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 R) { R.ensure(); });
return FabricContractInit{address, state_init, msg_body};
}
td::Ref create_new_contract_message_body(td::Ref 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 client,
td::Promise promise) {
run_get_method(
address, client, "get_storage_contract_data", {},
promise.wrap([](std::vector> stack) -> td::Result {
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(stack[0]));
TRY_RESULT(balance, entry_to_int(stack[1]));
TRY_RESULT(microchunk_hash, entry_to_bits256(stack[3]));
TRY_RESULT(file_size, entry_to_int(stack[4]));
TRY_RESULT(next_proof, entry_to_int(stack[5]));
TRY_RESULT(rate_per_mb_day, entry_to_int(stack[6]));
TRY_RESULT(max_span, entry_to_int(stack[7]));
TRY_RESULT(last_proof_time, entry_to_int(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};
}));
}
} // namespace ton