diff --git a/crypto/block/check-proof.cpp b/crypto/block/check-proof.cpp index 4abba64c..431a03fe 100644 --- a/crypto/block/check-proof.cpp +++ b/crypto/block/check-proof.cpp @@ -315,6 +315,113 @@ td::Result TransactionList::validate() const { return std::move(res); } +td::Result BlockTransaction::validate(bool check_proof) const { + if (root.is_null()) { + return td::Status::Error("transactions are expected to be non-empty"); + } + if (check_proof && proof->get_hash().bits().compare(root->get_hash().bits(), 256)) { + return td::Status::Error(PSLICE() << "transaction hash mismatch: Merkle proof expects " + << proof->get_hash().bits().to_hex(256) + << " but received data has " << root->get_hash().bits().to_hex(256)); + } + block::gen::Transaction::Record trans; + if (!tlb::unpack_cell(root, trans)) { + return td::Status::Error("cannot unpack transaction cell"); + } + Info res; + res.blkid = blkid; + res.now = trans.now; + res.lt = trans.lt; + res.hash = root->get_hash().bits(); + res.transaction = root; + return std::move(res); +} + +td::Result BlockTransactionList::validate(bool check_proof) const { + constexpr int max_answer_transactions = 256; + + TRY_RESULT_PREFIX(list, vm::std_boc_deserialize_multi(std::move(transactions_boc)), "cannot deserialize transactions boc: "); + std::vector> tx_proofs(list.size()); + + if (check_proof) { + try { + TRY_RESULT(proof_cell, vm::std_boc_deserialize(std::move(proof_boc))); + auto virt_root = vm::MerkleProof::virtualize(proof_cell, 1); + + if (blkid.root_hash != virt_root->get_hash().bits()) { + return td::Status::Error("Invalid block proof root hash"); + } + block::gen::Block::Record blk; + block::gen::BlockExtra::Record extra; + if (!(tlb::unpack_cell(virt_root, blk) && tlb::unpack_cell(std::move(blk.extra), extra))) { + return td::Status::Error("Error unpacking proof cell"); + } + vm::AugmentedDictionary acc_dict{vm::load_cell_slice_ref(extra.account_blocks), 256, + block::tlb::aug_ShardAccountBlocks}; + + bool eof = false; + ton::LogicalTime reverse = reverse_mode ? ~0ULL : 0; + ton::LogicalTime trans_lt = static_cast(start_lt); + td::Bits256 cur_addr = start_addr; + bool allow_same = true; + int count = 0; + while (!eof && count < req_count && count < max_answer_transactions) { + auto value = acc_dict.extract_value( + acc_dict.vm::DictionaryFixed::lookup_nearest_key(cur_addr.bits(), 256, !reverse, allow_same)); + if (value.is_null()) { + eof = true; + break; + } + allow_same = false; + if (cur_addr != start_addr) { + trans_lt = reverse; + } + + block::gen::AccountBlock::Record acc_blk; + if (!tlb::csr_unpack(std::move(value), acc_blk) || acc_blk.account_addr != cur_addr) { + return td::Status::Error("Error unpacking proof account block"); + } + vm::AugmentedDictionary trans_dict{vm::DictNonEmpty(), std::move(acc_blk.transactions), 64, + block::tlb::aug_AccountTransactions}; + td::BitArray<64> cur_trans{(long long)trans_lt}; + while (count < req_count && count < max_answer_transactions) { + auto tvalue = trans_dict.extract_value_ref( + trans_dict.vm::DictionaryFixed::lookup_nearest_key(cur_trans.bits(), 64, !reverse)); + if (tvalue.is_null()) { + trans_lt = reverse; + break; + } + if (static_cast(count) < tx_proofs.size()) { + tx_proofs[count] = std::move(tvalue); + } + count++; + } + } + if (static_cast(count) != list.size()) { + return td::Status::Error(PSLICE() << "Txs count mismatch in proof (" << count << ") and response (" << list.size() << ")"); + } + } catch (vm::VmError& err) { + return err.as_status("Couldn't verify proof: "); + } catch (vm::VmVirtError& err) { + return err.as_status("Couldn't verify proof: "); + } catch (...) { + return td::Status::Error("Unknown exception raised while verifying proof"); + } + } + + Info res; + for (int i = 0; i < static_cast(list.size()); i++) { + auto& root = list[i]; + BlockTransaction transaction; + transaction.root = root; + transaction.blkid = blkid; + transaction.proof = tx_proofs[i]; + TRY_RESULT(info, transaction.validate(check_proof)); + res.transactions.push_back(std::move(info)); + } + return std::move(res); +} + td::Status BlockProofLink::validate(td::uint32* save_utime) const { if (save_utime) { *save_utime = 0; diff --git a/crypto/block/check-proof.h b/crypto/block/check-proof.h index 527f3138..497a4eba 100644 --- a/crypto/block/check-proof.h +++ b/crypto/block/check-proof.h @@ -88,4 +88,36 @@ struct TransactionList { td::Result validate() const; }; +struct BlockTransaction { + ton::BlockIdExt blkid; + td::Ref root; + td::Ref proof; + + struct Info { + ton::BlockIdExt blkid; + td::uint32 now; + ton::LogicalTime lt; + ton::Bits256 hash; + td::Ref transaction; + }; + td::Result validate(bool check_proof) const; +}; + +struct BlockTransactionList { + ton::BlockIdExt blkid; + td::BufferSlice transactions_boc; + td::BufferSlice proof_boc; + ton::LogicalTime start_lt; + td::Bits256 start_addr; + bool reverse_mode; + int req_count; + + struct Info { + ton::BlockIdExt blkid; + std::vector transactions; + }; + + td::Result validate(bool check_proof) const; +}; + } // namespace block diff --git a/tl/generate/scheme/lite_api.tl b/tl/generate/scheme/lite_api.tl index a01da11a..ce4d2731 100644 --- a/tl/generate/scheme/lite_api.tl +++ b/tl/generate/scheme/lite_api.tl @@ -44,6 +44,7 @@ liteServer.transactionList ids:(vector tonNode.blockIdExt) transactions:bytes = liteServer.transactionId mode:# account:mode.0?int256 lt:mode.1?long hash:mode.2?int256 = liteServer.TransactionId; liteServer.transactionId3 account:int256 lt:long = liteServer.TransactionId3; liteServer.blockTransactions id:tonNode.blockIdExt req_count:# incomplete:Bool ids:(vector liteServer.transactionId) proof:bytes = liteServer.BlockTransactions; +liteServer.blockTransactionsExt id:tonNode.blockIdExt req_count:# incomplete:Bool transactions:bytes proof:bytes = liteServer.BlockTransactionsExt; liteServer.signature node_id_short:int256 signature:bytes = liteServer.Signature; liteServer.signatureSet validator_set_hash:int catchain_seqno:int signatures:(vector liteServer.signature) = liteServer.SignatureSet; liteServer.blockLinkBack to_key_block:Bool from:tonNode.blockIdExt to:tonNode.blockIdExt dest_proof:bytes proof:bytes state_proof:bytes = liteServer.BlockLink; @@ -76,6 +77,7 @@ liteServer.getOneTransaction id:tonNode.blockIdExt account:liteServer.accountId liteServer.getTransactions count:# account:liteServer.accountId lt:long hash:int256 = liteServer.TransactionList; liteServer.lookupBlock mode:# id:tonNode.blockId lt:mode.1?long utime:mode.2?int = liteServer.BlockHeader; liteServer.listBlockTransactions id:tonNode.blockIdExt mode:# count:# after:mode.7?liteServer.transactionId3 reverse_order:mode.6?true want_proof:mode.5?true = liteServer.BlockTransactions; +liteServer.listBlockTransactionsExt id:tonNode.blockIdExt mode:# count:# after:mode.7?liteServer.transactionId3 reverse_order:mode.6?true want_proof:mode.5?true = liteServer.BlockTransactionsExt; liteServer.getBlockProof mode:# known_block:tonNode.blockIdExt target_block:mode.0?tonNode.blockIdExt = liteServer.PartialBlockProof; liteServer.getConfigAll mode:# id:tonNode.blockIdExt = liteServer.ConfigInfo; liteServer.getConfigParams mode:# id:tonNode.blockIdExt param_list:(vector int) = liteServer.ConfigInfo; diff --git a/tl/generate/scheme/lite_api.tlo b/tl/generate/scheme/lite_api.tlo index 0572fd46..da64ac53 100644 Binary files a/tl/generate/scheme/lite_api.tlo and b/tl/generate/scheme/lite_api.tlo differ diff --git a/tl/generate/scheme/tonlib_api.tl b/tl/generate/scheme/tonlib_api.tl index c9a7df3d..bcfc625d 100644 --- a/tl/generate/scheme/tonlib_api.tl +++ b/tl/generate/scheme/tonlib_api.tl @@ -52,7 +52,7 @@ ton.blockIdExt workchain:int32 shard:int64 seqno:int32 root_hash:bytes file_hash raw.fullAccountState balance:int64 code:bytes data:bytes last_transaction_id:internal.transactionId block_id:ton.blockIdExt frozen_hash:bytes sync_utime:int53 = raw.FullAccountState; raw.message source:accountAddress destination:accountAddress value:int64 fwd_fee:int64 ihr_fee:int64 created_lt:int64 body_hash:bytes msg_data:msg.Data = raw.Message; -raw.transaction utime:int53 data:bytes transaction_id:internal.transactionId fee:int64 storage_fee:int64 other_fee:int64 in_msg:raw.message out_msgs:vector = raw.Transaction; +raw.transaction address:accountAddress utime:int53 data:bytes transaction_id:internal.transactionId fee:int64 storage_fee:int64 other_fee:int64 in_msg:raw.message out_msgs:vector = raw.Transaction; raw.transactions transactions:vector previous_transaction_id:internal.transactionId = raw.Transactions; raw.extMessageInfo hash:bytes = raw.ExtMessageInfo; @@ -215,6 +215,7 @@ blocks.shards shards:vector = blocks.Shards; blocks.accountTransactionId account:bytes lt:int64 = blocks.AccountTransactionId; blocks.shortTxId mode:# account:mode.0?bytes lt:mode.1?int64 hash:mode.2?bytes = liteServer.TransactionId; blocks.transactions id:ton.blockIdExt req_count:int32 incomplete:Bool transactions:vector = blocks.Transactions; +blocks.transactionsExt id:ton.blockIdExt req_count:int32 incomplete:Bool transactions:vector = blocks.TransactionsExt; blocks.header id:ton.blockIdExt global_id:int32 version:int32 flags:# after_merge:Bool after_split:Bool before_split:Bool want_merge:Bool want_split:Bool validator_list_hash_short:int32 catchain_seqno:int32 min_ref_mc_seqno:int32 is_key_block:Bool prev_key_block_seqno:int32 start_lt:int64 end_lt:int64 gen_utime:int53 vert_seqno:# prev_blocks:vector = blocks.Header; //blocks.shortData header:blocks.Header transactions:blocks.Header = blocks.BlockData; @@ -319,6 +320,7 @@ blocks.getMasterchainInfo = blocks.MasterchainInfo; blocks.getShards id:ton.blockIdExt = blocks.Shards; blocks.lookupBlock mode:int32 id:ton.blockId lt:int64 utime:int32 = ton.BlockIdExt; blocks.getTransactions id:ton.blockIdExt mode:# count:# after:blocks.accountTransactionId = blocks.Transactions; +blocks.getTransactionsExt id:ton.blockIdExt mode:# count:# after:blocks.accountTransactionId = blocks.TransactionsExt; blocks.getBlockHeader id:ton.blockIdExt = blocks.Header; blocks.getMasterchainBlockSignatures seqno:int32 = blocks.BlockSignatures; blocks.getShardBlockProof id:ton.blockIdExt mode:# from:mode.0?ton.blockIdExt = blocks.ShardBlockProof; diff --git a/tl/generate/scheme/tonlib_api.tlo b/tl/generate/scheme/tonlib_api.tlo index ffe4b1e7..023a4953 100644 Binary files a/tl/generate/scheme/tonlib_api.tlo and b/tl/generate/scheme/tonlib_api.tlo differ diff --git a/tonlib/tonlib/TonlibClient.cpp b/tonlib/tonlib/TonlibClient.cpp index d56bcd1a..c3c8bdb2 100644 --- a/tonlib/tonlib/TonlibClient.cpp +++ b/tonlib/tonlib/TonlibClient.cpp @@ -2966,6 +2966,7 @@ struct ToRawTransactions { std::vector> out_msgs; td::int64 fees = 0; td::int64 storage_fee = 0; + td::string address; if (info.transaction.not_null()) { data = to_bytes(info.transaction); block::gen::Transaction::Record trans; @@ -2974,11 +2975,6 @@ struct ToRawTransactions { } TRY_RESULT_ASSIGN(fees, to_balance(trans.total_fees)); - //LOG(ERROR) << fees; - - //std::ostringstream outp; - //block::gen::t_Transaction.print_ref(outp, info.transaction); - //LOG(INFO) << outp.str(); auto is_just = trans.r1.in_msg->prefetch_long(1); if (is_just == trans.r1.in_msg->fetch_long_eof) { @@ -3004,8 +3000,11 @@ struct ToRawTransactions { return td::Status::Error("Failed to fetch storage fee from transaction"); } storage_fee = storage_fees->to_long(); + auto std_address = block::StdAddress(info.blkid.id.workchain, trans.account_addr); + address = std_address.rserialize(true); } return tonlib_api::make_object( + tonlib_api::make_object(std::move(address)), info.now, data, tonlib_api::make_object(info.prev_trans_lt, info.prev_trans_hash.as_slice().str()), @@ -3032,6 +3031,74 @@ struct ToRawTransactions { return tonlib_api::make_object(std::move(transactions), std::move(transaction_id)); } + + td::Result> to_raw_transaction_or_throw( + block::BlockTransaction::Info&& info) { + std::string data; + + tonlib_api::object_ptr in_msg; + std::vector> out_msgs; + td::int64 fees = 0; + td::int64 storage_fee = 0; + td::string address; + if (info.transaction.not_null()) { + data = to_bytes(info.transaction); + block::gen::Transaction::Record trans; + if (!tlb::unpack_cell(info.transaction, trans)) { + return td::Status::Error("Failed to unpack Transaction"); + } + + TRY_RESULT_ASSIGN(fees, to_balance(trans.total_fees)); + + auto is_just = trans.r1.in_msg->prefetch_long(1); + if (is_just == trans.r1.in_msg->fetch_long_eof) { + return td::Status::Error("Failed to parse long"); + } + if (is_just == -1) { + auto msg = trans.r1.in_msg->prefetch_ref(); + TRY_RESULT(in_msg_copy, to_raw_message(trans.r1.in_msg->prefetch_ref())); + in_msg = std::move(in_msg_copy); + } + + if (trans.outmsg_cnt != 0) { + vm::Dictionary dict{trans.r1.out_msgs, 15}; + for (int x = 0; x < trans.outmsg_cnt; x++) { + TRY_RESULT(out_msg, to_raw_message(dict.lookup_ref(td::BitArray<15>{x}))); + fees += out_msg->fwd_fee_; + fees += out_msg->ihr_fee_; + out_msgs.push_back(std::move(out_msg)); + } + } + td::RefInt256 storage_fees; + if (!block::tlb::t_TransactionDescr.get_storage_fees(trans.description, storage_fees)) { + return td::Status::Error("Failed to fetch storage fee from transaction"); + } + storage_fee = storage_fees->to_long(); + auto std_address = block::StdAddress(info.blkid.id.workchain, trans.account_addr); + address = std_address.rserialize(true); + } + return tonlib_api::make_object( + tonlib_api::make_object(std::move(address)), + info.now, data, + tonlib_api::make_object(info.lt, + info.hash.as_slice().str()), + fees, storage_fee, fees - storage_fee, std::move(in_msg), std::move(out_msgs)); + } + + td::Result> to_raw_transaction(block::BlockTransaction::Info&& info) { + return TRY_VM(to_raw_transaction_or_throw(std::move(info))); + } + + td::Result>> to_raw_transactions( + block::BlockTransactionList::Info&& info) { + std::vector> transactions; + for (auto& transaction : info.transactions) { + TRY_RESULT(raw_transaction, to_raw_transaction(std::move(transaction))); + transactions.push_back(std::move(raw_transaction)); + } + + return std::move(transactions); + } }; // Raw @@ -5215,25 +5282,116 @@ auto to_tonlib_api(const ton::lite_api::liteServer_transactionId& txid) td::Status TonlibClient::do_request(const tonlib_api::blocks_getTransactions& request, td::Promise>&& promise) { TRY_RESULT(block, to_lite_api(*request.id_)) - TRY_RESULT(account, to_bits256((*request.after_).account_, "account")); - auto after = ton::lite_api::make_object(account, (*request.after_).lt_); + auto root_hash = block->root_hash_; + bool check_proof = request.mode_ & 32; + bool reverse_mode = request.mode_ & 64; + bool has_starting_tx = request.mode_ & 128; + + td::Bits256 start_addr; + ton::LogicalTime start_lt; + ton::lite_api::object_ptr after; + if (has_starting_tx) { + if (!request.after_) { + return td::Status::Error("Missing field `after`"); + } + TRY_RESULT_ASSIGN(start_addr, to_bits256(request.after_->account_, "account")); + start_lt = request.after_->lt_; + after = ton::lite_api::make_object(start_addr, start_lt); + } else { + start_addr = reverse_mode ? td::Bits256::ones() : td::Bits256::zero(); + start_lt = reverse_mode ? ~0ULL : 0; + after = nullptr; + } + client_.send_query(ton::lite_api::liteServer_listBlockTransactions( std::move(block), request.mode_, request.count_, std::move(after), - false, - false), - promise.wrap([](lite_api_ptr&& bTxes) { - const auto& id = bTxes->id_; - //for (auto id : ids) { + reverse_mode, + check_proof), + promise.wrap([check_proof, reverse_mode, root_hash, req_count = request.count_, start_addr, start_lt, mode = request.mode_] + (lite_api_ptr&& bTxes) -> td::Result> { + if (check_proof) { + try { + constexpr int max_answer_transactions = 256; + TRY_RESULT(proof_cell, vm::std_boc_deserialize(std::move(bTxes->proof_))); + auto virt_root = vm::MerkleProof::virtualize(proof_cell, 1); + + if (root_hash != virt_root->get_hash().bits()) { + return td::Status::Error("Invalid block proof root hash"); + } + block::gen::Block::Record blk; + block::gen::BlockExtra::Record extra; + if (!(tlb::unpack_cell(virt_root, blk) && tlb::unpack_cell(std::move(blk.extra), extra))) { + return td::Status::Error("Error unpacking proof cell"); + } + vm::AugmentedDictionary acc_dict{vm::load_cell_slice_ref(extra.account_blocks), 256, + block::tlb::aug_ShardAccountBlocks}; + + bool eof = false; + ton::LogicalTime reverse = reverse_mode ? ~0ULL : 0; + ton::LogicalTime trans_lt = static_cast(start_lt); + td::Bits256 cur_addr = start_addr; + bool allow_same = true; + int count = 0; + while (!eof && count < req_count && count < max_answer_transactions) { + auto value = acc_dict.extract_value( + acc_dict.vm::DictionaryFixed::lookup_nearest_key(cur_addr.bits(), 256, !reverse, allow_same)); + if (value.is_null()) { + eof = true; + break; + } + allow_same = false; + if (cur_addr != start_addr) { + trans_lt = reverse; + } + + block::gen::AccountBlock::Record acc_blk; + if (!tlb::csr_unpack(std::move(value), acc_blk) || acc_blk.account_addr != cur_addr) { + return td::Status::Error("Error unpacking proof account block"); + } + vm::AugmentedDictionary trans_dict{vm::DictNonEmpty(), std::move(acc_blk.transactions), 64, + block::tlb::aug_AccountTransactions}; + td::BitArray<64> cur_trans{(long long)trans_lt}; + while (count < req_count && count < max_answer_transactions) { + auto tvalue = trans_dict.extract_value_ref( + trans_dict.vm::DictionaryFixed::lookup_nearest_key(cur_trans.bits(), 64, !reverse)); + if (tvalue.is_null()) { + trans_lt = reverse; + break; + } + if (static_cast(count) < bTxes->ids_.size()) { + if (mode & 4 && !tvalue->get_hash().bits().equals(bTxes->ids_[count]->hash_.bits(), 256)) { + return td::Status::Error("Couldn't verify proof (hash)"); + } + if (mode & 2 && cur_trans != td::BitArray<64>(bTxes->ids_[count]->lt_)) { + return td::Status::Error("Couldn't verify proof (lt)"); + } + if (mode & 1 && cur_addr != bTxes->ids_[count]->account_) { + return td::Status::Error("Couldn't verify proof (account)"); + } + } + count++; + } + } + if (static_cast(count) != bTxes->ids_.size()) { + return td::Status::Error(PSLICE() << "Txs count mismatch in proof (" << count << ") and response (" << bTxes->ids_.size() << ")"); + } + } catch (vm::VmError& err) { + return err.as_status("Couldn't verify proof: "); + } catch (vm::VmVirtError& err) { + return err.as_status("Couldn't verify proof: "); + } catch (...) { + return td::Status::Error("Unknown exception raised while verifying proof"); + } + } + tonlib_api::blocks_transactions r; - r.id_ = to_tonlib_api(*id); + r.id_ = to_tonlib_api(*bTxes->id_); r.req_count_ = bTxes->req_count_; r.incomplete_ = bTxes->incomplete_; for (auto& id: bTxes->ids_) { - //tonlib_api::blocks_shortTxId txid = tonlib_api::blocks_shortTxId(id->mode_, id->account_.as_slice().str(), id->lt_, id->hash_.as_slice().str()); - //r.transactions_.push_back(txid); r.transactions_.push_back(to_tonlib_api(*id)); } return tonlib_api::make_object(std::move(r)); @@ -5241,6 +5399,70 @@ td::Status TonlibClient::do_request(const tonlib_api::blocks_getTransactions& re return td::Status::OK(); } +td::Status TonlibClient::do_request(const tonlib_api::blocks_getTransactionsExt& request, + td::Promise>&& promise) { + TRY_RESULT(block, to_lite_api(*request.id_)) + bool check_proof = request.mode_ & 32; + bool reverse_mode = request.mode_ & 64; + bool has_starting_tx = request.mode_ & 128; + + td::Bits256 start_addr; + ton::LogicalTime start_lt; + ton::lite_api::object_ptr after; + if (has_starting_tx) { + if (!request.after_) { + return td::Status::Error("Missing field `after`"); + } + TRY_RESULT_ASSIGN(start_addr, to_bits256(request.after_->account_, "account")); + start_lt = request.after_->lt_; + after = ton::lite_api::make_object(start_addr, start_lt); + } else { + start_addr = reverse_mode ? td::Bits256::ones() : td::Bits256::zero(); + start_lt = reverse_mode ? ~0ULL : 0; + after = nullptr; + } + auto block_id = ton::create_block_id(block); + client_.send_query(ton::lite_api::liteServer_listBlockTransactionsExt( + std::move(block), + request.mode_, + request.count_, + std::move(after), + reverse_mode, + check_proof), + promise.wrap([block_id, check_proof, reverse_mode, start_addr, start_lt, req_count = request.count_] + (lite_api_ptr&& bTxes) -> td::Result> { + if (block_id != create_block_id(bTxes->id_)) { + return td::Status::Error("Liteserver responded with wrong block"); + } + + block::BlockTransactionList list; + list.blkid = block_id; + list.transactions_boc = std::move(bTxes->transactions_); + list.proof_boc = std::move(bTxes->proof_); + list.reverse_mode = reverse_mode; + list.start_lt = start_lt; + list.start_addr = start_addr; + list.req_count = req_count; + auto info = list.validate(check_proof); + if (info.is_error()) { + return info.move_as_error_prefix("Validation of block::BlockTransactionList failed: "); + } + + auto raw_transactions = ToRawTransactions(td::optional()).to_raw_transactions(info.move_as_ok()); + if (raw_transactions.is_error()) { + return raw_transactions.move_as_error_prefix("Error occured while creating tonlib_api::raw_transaction: "); + } + + tonlib_api::blocks_transactionsExt r; + r.id_ = to_tonlib_api(*bTxes->id_); + r.req_count_ = bTxes->req_count_; + r.incomplete_ = bTxes->incomplete_; + r.transactions_ = raw_transactions.move_as_ok(); + return tonlib_api::make_object(std::move(r)); + })); + return td::Status::OK(); +} + td::Status TonlibClient::do_request(const tonlib_api::blocks_getBlockHeader& request, td::Promise>&& promise) { TRY_RESULT(block, to_lite_api(*request.id_)) diff --git a/tonlib/tonlib/TonlibClient.h b/tonlib/tonlib/TonlibClient.h index dbbb62a2..ed81760f 100644 --- a/tonlib/tonlib/TonlibClient.h +++ b/tonlib/tonlib/TonlibClient.h @@ -375,6 +375,8 @@ class TonlibClient : public td::actor::Actor { td::Promise>&& promise); td::Status do_request(const tonlib_api::blocks_getTransactions& block_data, td::Promise>&& promise); + td::Status do_request(const tonlib_api::blocks_getTransactionsExt& request, + td::Promise>&& promise); td::Status do_request(const tonlib_api::blocks_getBlockHeader& request, td::Promise>&& promise); td::Status do_request(const tonlib_api::blocks_getMasterchainBlockSignatures& request, diff --git a/validator/impl/liteserver.cpp b/validator/impl/liteserver.cpp index 66e73d8e..69861ba3 100644 --- a/validator/impl/liteserver.cpp +++ b/validator/impl/liteserver.cpp @@ -182,6 +182,11 @@ void LiteQuery::start_up() { (q.mode_ & 128) ? q.after_->account_ : td::Bits256::zero(), static_cast((q.mode_ & 128) ? (q.after_->lt_) : 0)); }, + [&](lite_api::liteServer_listBlockTransactionsExt& q) { + this->perform_listBlockTransactionsExt(ton::create_block_id(q.id_), q.mode_, q.count_, + (q.mode_ & 128) ? q.after_->account_ : td::Bits256::zero(), + static_cast((q.mode_ & 128) ? (q.after_->lt_) : 0)); + }, [&](lite_api::liteServer_getConfigParams& q) { this->perform_getConfigParams(ton::create_block_id(q.id_), (q.mode_ & 0xffff) | 0x10000, q.param_list_); }, @@ -1964,6 +1969,118 @@ void LiteQuery::finish_listBlockTransactions(int mode, int req_count) { finish_query(std::move(b)); } +void LiteQuery::perform_listBlockTransactionsExt(BlockIdExt blkid, int mode, int count, Bits256 account, LogicalTime lt) { + LOG(INFO) << "started a listBlockTransactionsExt(" << blkid.to_str() << ", " << mode << ", " << count << ", " + << account.to_hex() << ", " << lt << ") liteserver query"; + base_blk_id_ = blkid; + acc_addr_ = account; + trans_lt_ = lt; + set_continuation([this, mode, count]() -> void { finish_listBlockTransactionsExt(mode, count); }); + request_block_data(blkid); +} + +void LiteQuery::finish_listBlockTransactionsExt(int mode, int req_count) { + LOG(INFO) << "completing a listBlockTransactionsExt(" << base_blk_id_.to_str() << ", " << mode << ", " << req_count + << ", " << acc_addr_.to_hex() << ", " << trans_lt_ << ") liteserver query"; + constexpr int max_answer_transactions = 256; + CHECK(block_.not_null()); + auto block_root = block_->root_cell(); + CHECK(block_root.not_null()); + RootHash rhash{block_root->get_hash().bits()}; + CHECK(rhash == base_blk_id_.root_hash); + vm::MerkleProofBuilder pb; + auto virt_root = block_root; + if (mode & 32) { + // proof requested + virt_root = pb.init(std::move(virt_root)); + } + if ((mode & 192) == 64) { // reverse order, no starting point + acc_addr_.set_ones(); + trans_lt_ = ~0ULL; + } + std::vector> trans_roots; + bool eof = false; + ton::LogicalTime reverse = (mode & 64) ? ~0ULL : 0; + try { + block::gen::Block::Record blk; + block::gen::BlockExtra::Record extra; + if (!(tlb::unpack_cell(virt_root, blk) && tlb::unpack_cell(std::move(blk.extra), extra))) { + fatal_error("cannot find account transaction data in block "s + base_blk_id_.to_str()); + return; + } + vm::AugmentedDictionary acc_dict{vm::load_cell_slice_ref(extra.account_blocks), 256, + block::tlb::aug_ShardAccountBlocks}; + int count = 0; + bool allow_same = true; + td::Bits256 cur_addr = acc_addr_; + while (!eof && count < req_count && count < max_answer_transactions) { + Ref value; + try { + value = acc_dict.extract_value( + acc_dict.vm::DictionaryFixed::lookup_nearest_key(cur_addr.bits(), 256, !reverse, allow_same)); + } catch (vm::VmError err) { + fatal_error("error while traversing account block dictionary: "s + err.get_msg()); + return; + } + if (value.is_null()) { + eof = true; + break; + } + allow_same = false; + if (cur_addr != acc_addr_) { + trans_lt_ = reverse; + } + block::gen::AccountBlock::Record acc_blk; + if (!(tlb::csr_unpack(std::move(value), acc_blk) && acc_blk.account_addr == cur_addr)) { + fatal_error("invalid AccountBlock for account "s + cur_addr.to_hex()); + return; + } + vm::AugmentedDictionary trans_dict{vm::DictNonEmpty(), std::move(acc_blk.transactions), 64, + block::tlb::aug_AccountTransactions}; + td::BitArray<64> cur_trans{(long long)trans_lt_}; + while (count < req_count && count < max_answer_transactions) { + Ref tvalue; + try { + tvalue = trans_dict.extract_value_ref( + trans_dict.vm::DictionaryFixed::lookup_nearest_key(cur_trans.bits(), 64, !reverse)); + } catch (vm::VmError err) { + fatal_error("error while traversing transaction dictionary of an AccountBlock: "s + err.get_msg()); + return; + } + if (tvalue.is_null()) { + trans_lt_ = reverse; + break; + } + trans_roots.push_back(std::move(tvalue)); + ++count; + } + } + } catch (vm::VmError err) { + fatal_error("error while parsing AccountBlocks of block "s + base_blk_id_.to_str() + " : " + err.get_msg()); + return; + } + td::BufferSlice proof_data; + if (mode & 32) { + // create proof + auto proof_boc = pb.extract_proof_boc(); + if (proof_boc.is_error()) { + fatal_error(proof_boc.move_as_error()); + return; + } + proof_data = proof_boc.move_as_ok(); + } + auto res = vm::std_boc_serialize_multi(std::move(trans_roots)); + if (res.is_error()) { + fatal_error(res.move_as_error()); + return; + } + + auto b = ton::create_serialize_tl_object( + ton::create_tl_lite_block_id(base_blk_id_), req_count, !eof, res.move_as_ok(), std::move(proof_data)); + LOG(INFO) << "listBlockTransactionsExt() query completed"; + finish_query(std::move(b)); +} + void LiteQuery::perform_getBlockProof(ton::BlockIdExt from, ton::BlockIdExt to, int mode) { if (!(mode & 1)) { to.invalidate_clear(); diff --git a/validator/impl/liteserver.hpp b/validator/impl/liteserver.hpp index 47970aae..2707fdfe 100644 --- a/validator/impl/liteserver.hpp +++ b/validator/impl/liteserver.hpp @@ -133,6 +133,8 @@ class LiteQuery : public td::actor::Actor { void perform_lookupBlock(BlockId blkid, int mode, LogicalTime lt, UnixTime utime); void perform_listBlockTransactions(BlockIdExt blkid, int mode, int count, Bits256 account, LogicalTime lt); void finish_listBlockTransactions(int mode, int count); + void perform_listBlockTransactionsExt(BlockIdExt blkid, int mode, int count, Bits256 account, LogicalTime lt); + void finish_listBlockTransactionsExt(int mode, int count); void perform_getBlockProof(BlockIdExt from, BlockIdExt to, int mode); void continue_getBlockProof(BlockIdExt from, BlockIdExt to, int mode, BlockIdExt baseblk, Ref state);