/* 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 "MicrochunkTree.h" #include "Torrent.h" #include "vm/cells/CellSlice.h" #include "vm/cells/MerkleProof.h" namespace ton { static td::Ref prun(const td::Ref &node) { vm::CellBuilder cb; cb.store_long(static_cast(vm::Cell::SpecialType::PrunnedBranch), 8); cb.store_long(1, 8); cb.store_bytes(node->get_hash(0).as_slice()); cb.store_long(node->get_depth(0), 16); return cb.finalize(true); } MicrochunkTree::Builder::Builder(td::uint64 file_size, td::uint64 prun_size) : file_size_(file_size), prun_size_(prun_size) { total_size_ = MICROCHUNK_SIZE; while (total_size_ < file_size) { total_size_ *= 2; } } void MicrochunkTree::Builder::add_data(td::Slice s) { CHECK(cur_size_ + s.size() <= file_size_); while (s.size() > 0) { size_t buf_ptr = cur_size_ % MICROCHUNK_SIZE; size_t buf_remaining = MICROCHUNK_SIZE - buf_ptr; if (buf_remaining > s.size()) { memcpy(cur_microchunk_ + buf_ptr, s.data(), s.size()); cur_size_ += s.size(); return; } memcpy(cur_microchunk_ + buf_ptr, s.data(), buf_remaining); cur_size_ += buf_remaining; s.remove_prefix(buf_remaining); add_microchunk(td::Slice(cur_microchunk_, MICROCHUNK_SIZE)); } } MicrochunkTree MicrochunkTree::Builder::finalize() { CHECK(cur_size_ == file_size_); if (cur_size_ % MICROCHUNK_SIZE != 0) { size_t buf_ptr = cur_size_ % MICROCHUNK_SIZE; size_t buf_remaining = MICROCHUNK_SIZE - buf_ptr; memset(cur_microchunk_ + buf_ptr, 0, buf_remaining); cur_size_ += buf_remaining; add_microchunk(td::Slice(cur_microchunk_, MICROCHUNK_SIZE)); } memset(cur_microchunk_, 0, MICROCHUNK_SIZE); while (cur_size_ < total_size_) { add_microchunk(td::Slice(cur_microchunk_, MICROCHUNK_SIZE)); cur_size_ += MICROCHUNK_SIZE; } CHECK(proof_.size() == 1); MicrochunkTree tree(vm::CellBuilder::create_merkle_proof(std::move(proof_[0]))); CHECK(tree.total_size_ == total_size_); return tree; } void MicrochunkTree::Builder::add_microchunk(td::Slice s) { CHECK(s.size() == MICROCHUNK_SIZE); td::Ref node = vm::CellBuilder().store_zeroes(2).store_bytes(s).finalize_novm(); while (!proof_.empty() && proof_.back()->get_depth(0) == node->get_depth(0)) { td::Ref left = std::move(proof_.back()); proof_.pop_back(); node = vm::CellBuilder().store_zeroes(2).store_ref(std::move(left)).store_ref(std::move(node)).finalize_novm(); if ((MICROCHUNK_SIZE << node->get_depth(0)) <= prun_size_) { node = prun(node); } } proof_.push_back(std::move(node)); } MicrochunkTree::MicrochunkTree(td::Ref root_proof) : root_proof_(root_proof) { td::Ref virt_root = vm::MerkleProof::virtualize(root_proof_, 1); CHECK(!virt_root.is_null()); CHECK(virt_root->get_depth() <= 50); total_size_ = MICROCHUNK_SIZE << virt_root->get_depth(); root_hash_ = virt_root->get_hash().bits(); } class GetMicrochunkProof { public: GetMicrochunkProof(td::uint64 l, td::uint64 r, Torrent &torrent) : l(l), r(r), torrent(torrent) { } td::Result> unprun(td::uint64 il, td::uint64 ir) { if (ir - il == MicrochunkTree::MICROCHUNK_SIZE) { TRY_RESULT(data, get_microchunk(il)); return vm::CellBuilder().store_zeroes(2).store_bytes(data).finalize_novm(); } td::uint64 imid = (il + ir) / 2; TRY_RESULT(node_l, unprun(il, imid)); TRY_RESULT(node_r, unprun(imid, ir)); td::Ref node = vm::CellBuilder().store_zeroes(2).store_ref(std::move(node_l)).store_ref(std::move(node_r)).finalize_novm(); if (l >= ir || il >= r) { node = prun(node); } return node; } td::Result> unprun(const td::Ref &node, td::uint64 il, td::uint64 ir) { vm::CellSlice cs(vm::NoVm(), node); if (!cs.is_special()) { return node; } TRY_RESULT(result, unprun(il, ir)); if (result->get_hash(0) != node->get_hash(0)) { return td::Status::Error("Hash mismatch"); } return result; } td::Result> get_proof(td::Ref node, td::uint64 il, td::uint64 ir) { if (l >= ir || il >= r) { return prun(node); } if (ir - il == MicrochunkTree::MICROCHUNK_SIZE) { return unprun(node, il, ir); } if (l <= il && ir <= r) { return prun(node); } td::uint64 imid = (il + ir) / 2; TRY_RESULT_ASSIGN(node, unprun(node, il, ir)); vm::CellSlice cs(vm::NoVm(), node); if (cs.size_ext() != 2 + (2 << 16)) { return td::Status::Error("Invalid node in microchunk tree"); } TRY_RESULT(node_l, get_proof(cs.prefetch_ref(0), il, imid)); TRY_RESULT(node_r, get_proof(cs.prefetch_ref(1), imid, ir)); return vm::CellBuilder().store_zeroes(2).store_ref(std::move(node_l)).store_ref(std::move(node_r)).finalize_novm(); } private: td::uint64 l, r; Torrent &torrent; td::uint64 cache_offset = 0; std::string cache; td::Result get_microchunk(td::uint64 l) { DCHECK(l % MicrochunkTree::MICROCHUNK_SIZE == 0); td::uint64 r = l + MicrochunkTree::MICROCHUNK_SIZE; if (!(cache_offset <= l && r <= cache_offset + cache.size())) { td::uint64 piece_size = torrent.get_info().piece_size; td::uint64 piece_i = l / piece_size; if (piece_i < torrent.get_info().pieces_count()) { TRY_RESULT(piece, torrent.get_piece_data(piece_i)); piece.resize(piece_size, '\0'); cache = std::move(piece); } else { cache = std::string(piece_size, '\0'); } cache_offset = piece_i * piece_size; } return td::Slice{cache.data() + (l - cache_offset), MicrochunkTree::MICROCHUNK_SIZE}; } }; td::Result> MicrochunkTree::get_proof(td::uint64 l, td::uint64 r, Torrent &torrent) const { if (root_proof_.is_null()) { return td::Status::Error("Empty microchunk tree"); } if (l % MICROCHUNK_SIZE != 0 || r % MICROCHUNK_SIZE != 0 || l >= r || r > total_size_) { return td::Status::Error("Invalid range"); } if (!torrent.inited_info()) { return td::Status::Error("Torrent info is not ready"); } if (!torrent.get_info().piece_size % MICROCHUNK_SIZE != 0) { return td::Status::Error("Invalid piece size in torrent"); } td::Ref root_raw = vm::CellSlice(vm::NoVm(), root_proof_).prefetch_ref(); TRY_RESULT(result, GetMicrochunkProof(l, r, torrent).get_proof(std::move(root_raw), 0, total_size_)); return vm::CellBuilder::create_merkle_proof(std::move(result)); } td::Result MicrochunkTree::Builder::build_for_torrent(Torrent &torrent, td::uint64 prun_size) { if (!torrent.inited_info()) { return td::Status::Error("Torrent info is not available"); } const TorrentInfo &info = torrent.get_info(); Builder builder(info.file_size, prun_size); td::uint64 pieces_count = info.pieces_count(); for (td::uint64 i = 0; i < pieces_count; ++i) { TRY_RESULT(piece, torrent.get_piece_data(i)); builder.add_data(piece); } MicrochunkTree tree = builder.finalize(); return tree; } } // namespace ton