/* 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 . Copyright 2017-2019 Telegram Systems LLP */ #include "vm/db/StaticBagOfCellsDb.h" #include "vm/cells/CellWithStorage.h" #include "vm/boc.h" #include "vm/cells/ExtCell.h" #include "td/utils/crypto.h" #include "td/utils/format.h" #include "td/utils/misc.h" #include "td/utils/port/RwMutex.h" #include "td/utils/ConcurrentHashTable.h" #include namespace vm { // // Common interface // template class RootCell : public Cell { struct PrivateTag {}; public: td::Result load_cell() const override { return cell_->load_cell(); } Ref virtualize(VirtualizationParameters virt) const override { return cell_->virtualize(virt); } td::uint32 get_virtualization() const override { return cell_->get_virtualization(); } CellUsageTree::NodePtr get_tree_node() const override { return cell_->get_tree_node(); } bool is_loaded() const override { return cell_->is_loaded(); } // hash and level LevelMask get_level_mask() const override { return cell_->get_level_mask(); } template static Ref create(Ref cell, T&& extra) { return Ref(true, std::move(cell), std::forward(extra), PrivateTag{}); } template RootCell(Ref cell, T&& extra, PrivateTag) : cell_(std::move(cell)), extra_(std::forward(extra)) { } private: Ref cell_; ExtraT extra_; td::uint16 do_get_depth(td::uint32 level) const override { return cell_->get_depth(level); } const Hash do_get_hash(td::uint32 level) const override { return cell_->get_hash(level); } }; class DataCellCacheNoop { public: Ref store(int idx, Ref cell) { return cell; } Ref load(int idx) { return {}; } void clear() { } }; class DataCellCacheMutex { public: Ref store(int idx, Ref cell) { auto lock = cells_rw_mutex_.lock_write(); return cells_.emplace(idx, std::move(cell)).first->second; } Ref load(int idx) { auto lock = cells_rw_mutex_.lock_read(); auto it = cells_.find(idx); if (it != cells_.end()) { return it->second; } return {}; } void clear() { auto guard = cells_rw_mutex_.lock_write(); cells_.clear(); } private: td::RwMutex cells_rw_mutex_; td::HashMap> cells_; }; class DataCellCacheTdlib { public: Ref store(int idx, Ref cell) { return Ref(cells_.insert(as_key(idx), cell.get())); } Ref load(int idx) { return Ref(cells_.find(as_key(idx), nullptr)); } void clear() { cells_.for_each([](auto key, auto value) { Ref{value}; }); } private: td::ConcurrentHashMap cells_; td::uint32 as_key(int idx) { td::uint32 key = static_cast(idx + 1); key *= 1000000007; return key; } }; struct StaticBocExtCellExtra { int idx; std::weak_ptr deserializer; }; class StaticBocLoader { public: static td::Result> load_data_cell(const Cell& cell, const StaticBocExtCellExtra& extra) { auto deserializer = extra.deserializer.lock(); if (!deserializer) { return td::Status::Error("StaticBocDb is already destroyed, cannot fetch cell"); } return deserializer->load_by_idx(extra.idx); } }; using StaticBocExtCell = ExtCell; using StaticBocRootCell = RootCell>; td::Result> StaticBagOfCellsDb::create_ext_cell(Cell::LevelMask level_mask, td::Slice hash, td::Slice depth, int idx) { TRY_RESULT(res, StaticBocExtCell::create(PrunnedCellInfo{level_mask, hash, depth}, StaticBocExtCellExtra{idx, shared_from_this()})); return std::move(res); } // // Baseline implementation // class StaticBagOfCellsDbBaselineImpl : public StaticBagOfCellsDb { public: StaticBagOfCellsDbBaselineImpl(std::vector> roots) : roots_(std::move(roots)) { } td::Result get_root_count() override { return roots_.size(); }; td::Result> get_root_cell(size_t idx) override { if (idx >= roots_.size()) { return td::Status::Error(PSLICE() << "invalid root_cell index: " << idx); } return roots_[idx]; }; private: std::vector> roots_; td::Result> load_by_idx(int idx) override { UNREACHABLE(); } }; td::Result> StaticBagOfCellsDbBaseline::create(std::unique_ptr data) { std::string buf(data->size(), '\0'); TRY_RESULT(slice, data->view(buf, 0)); return create(slice); } td::Result> StaticBagOfCellsDbBaseline::create(td::Slice data) { BagOfCells boc; TRY_RESULT(x, boc.deserialize(data)); if (x <= 0) { return td::Status::Error("failed to deserialize"); } std::vector> roots(boc.get_root_count()); for (int i = 0; i < boc.get_root_count(); i++) { roots[i] = boc.get_root_cell(i); } return std::make_shared(std::move(roots)); } // // Main implementation // class StaticBagOfCellsDbLazyImpl : public StaticBagOfCellsDb { public: explicit StaticBagOfCellsDbLazyImpl(std::unique_ptr data, StaticBagOfCellsDbLazy::Options options) : data_(std::move(data)), options_(std::move(options)) { get_thread_safe_counter().add(1); } td::Result get_root_count() override { TRY_STATUS(check_status()); TRY_STATUS(check_result(load_header())); return info_.root_count; }; td::Result> get_root_cell(size_t idx) override { TRY_STATUS(check_status()); TRY_RESULT(root_count, get_root_count()); if (idx >= root_count) { return td::Status::Error(PSLICE() << "invalid root_cell index: " << idx); } TRY_RESULT(cell_idx, load_root_idx(td::narrow_cast(idx))); // Load DataCell in order to ensure lower hashes correctness // They will be valid for all non-root cell automaically TRY_RESULT(data_cell, check_result(load_data_cell(td::narrow_cast(cell_idx)))); return create_root_cell(std::move(data_cell)); }; ~StaticBagOfCellsDbLazyImpl() { //LOG(ERROR) << deserialize_cell_cnt_ << " " << deserialize_cell_hash_cnt_; get_thread_safe_counter().add(-1); } private: std::atomic should_cache_cells_{true}; std::unique_ptr data_; StaticBagOfCellsDbLazy::Options options_; bool has_info_{false}; BagOfCells::Info info_; std::mutex index_i_mutex_; td::RwMutex index_data_rw_mutex_; std::string index_data_; std::atomic index_i_{0}; size_t index_offset_{0}; DataCellCacheMutex cells_; //DataCellCacheNoop cells_; //DataCellCacheTdlib cells_; int next_idx_{0}; Ref empty_cell_; //stats td::ThreadSafeCounter deserialize_cell_cnt_; td::ThreadSafeCounter deserialize_cell_hash_cnt_; std::atomic has_error_{false}; std::mutex status_mutex_; td::Status status_; static td::NamedThreadSafeCounter::CounterRef get_thread_safe_counter() { static auto res = td::NamedThreadSafeCounter::get_default().get_counter("StaticBagOfCellsDbLazy"); return res; } td::Status check_status() TD_WARN_UNUSED_RESULT { if (has_error_.load(std::memory_order_relaxed)) { std::lock_guard guard(status_mutex_); return status_.clone(); } return td::Status::OK(); } template T check_result(T&& to_check) { CHECK(status_.is_ok()); if (to_check.is_error()) { std::lock_guard guard(status_mutex_); has_error_.store(true); status_ = to_check.error().clone(); } return std::forward(to_check); } td::Result> load_by_idx(int idx) override { TRY_STATUS(check_status()); return check_result(load_data_cell(idx)); } struct Ptr { td::MutableSlice as_slice() { return data; } td::string data; }; // May be optimized auto alloc(size_t size) { //return td::StackAllocator::alloc(size); return Ptr{std::string(size, '\0')}; } td::Result load_idx_offset(int idx) { if (idx < 0) { return 0; } td::Slice offset_view; CHECK(info_.offset_byte_size <= 8); char arr[8]; td::RwMutex::ReadLock guard; if (info_.has_index) { TRY_RESULT(new_offset_view, data_->view(td::MutableSlice(arr, info_.offset_byte_size), info_.index_offset + idx * info_.offset_byte_size)); offset_view = new_offset_view; } else { guard = index_data_rw_mutex_.lock_read().move_as_ok(); offset_view = td::Slice(index_data_).substr(idx * info_.offset_byte_size, info_.offset_byte_size); } CHECK(offset_view.size() == (size_t)info_.offset_byte_size); return td::narrow_cast(info_.read_offset(offset_view.ubegin())); } td::Result load_root_idx(int root_i) { CHECK(root_i >= 0 && root_i < info_.root_count); if (!info_.has_roots) { return 0; } char arr[8]; TRY_RESULT(idx_view, data_->view(td::MutableSlice(arr, info_.ref_byte_size), info_.roots_offset + root_i * info_.ref_byte_size)); CHECK(idx_view.size() == (size_t)info_.ref_byte_size); return info_.read_ref(idx_view.ubegin()); } struct CellLocation { std::size_t begin; std::size_t end; bool should_cache; }; td::Result get_cell_location(int idx) { CHECK(idx >= 0); CHECK(idx < info_.cell_count); TRY_STATUS(preload_index(idx)); TRY_RESULT(from, load_idx_offset(idx - 1)); TRY_RESULT(till, load_idx_offset(idx)); CellLocation res; res.begin = from; res.end = till; res.should_cache = true; if (info_.has_cache_bits) { res.begin /= 2; res.should_cache = res.end % 2 == 1; res.end /= 2; } CHECK(std::numeric_limits::max() - res.begin >= info_.data_offset); CHECK(std::numeric_limits::max() - res.end >= info_.data_offset); res.begin += static_cast(info_.data_offset); res.end += static_cast(info_.data_offset); return res; } td::Status load_header() { if (has_info_) { return td::Status::OK(); } std::string header(1000, '\0'); TRY_RESULT(header_view, data_->view(td::MutableSlice(header).truncate(data_->size()), 0)) auto parse_res = info_.parse_serialized_header(header_view); if (parse_res <= 0) { return td::Status::Error("bag-of-cell error: failed to read header"); } if (info_.total_size < data_->size()) { return td::Status::Error("bag-of-cell error: not enough data"); } if (options_.check_crc32c && info_.has_crc32c) { std::string buf(td::narrow_cast(info_.total_size), '\0'); TRY_RESULT(data, data_->view(td::MutableSlice(buf), 0)); unsigned crc_computed = td::crc32c(td::Slice{data.ubegin(), data.uend() - 4}); unsigned crc_stored = td::as(data.uend() - 4); if (crc_computed != crc_stored) { return td::Status::Error(PSLICE() << "bag-of-cells CRC32C mismatch: expected " << td::format::as_hex(crc_computed) << ", found " << td::format::as_hex(crc_stored)); } } has_info_ = true; return td::Status::OK(); } td::Status preload_index(int idx) { if (info_.has_index) { return td::Status::OK(); } CHECK(idx < info_.cell_count); if (index_i_.load(std::memory_order_relaxed) > idx) { return td::Status::OK(); } std::lock_guard index_i_guard(index_i_mutex_); std::array buf; auto buf_slice = td::MutableSlice(buf.data(), buf.size()); for (; index_i_ <= idx; index_i_++) { auto offset = td::narrow_cast(info_.data_offset + index_offset_); CHECK(data_->size() >= offset); TRY_RESULT(cell, data_->view(buf_slice.copy().truncate(data_->size() - offset), offset)); CellSerializationInfo cell_info; TRY_STATUS(cell_info.init(cell, info_.ref_byte_size)); index_offset_ += cell_info.end_offset; LOG_CHECK((unsigned)info_.offset_byte_size <= 8) << info_.offset_byte_size; td::uint8 tmp[8]; info_.write_offset(tmp, index_offset_); auto guard = index_data_rw_mutex_.lock_write(); index_data_.append(reinterpret_cast(tmp), info_.offset_byte_size); } return td::Status::OK(); } Ref get_any_cell(int idx) { return get_data_cell(idx); } Ref get_data_cell(int idx) { return cells_.load(idx); } Ref set_data_cell(int idx, Ref cell) { if (/*idx >= info_.root_count || */ !should_cache_cells_.load(std::memory_order_relaxed)) { return cell; } CHECK(cell.not_null()); return cells_.store(idx, std::move(cell)); } Ref set_any_cell(int idx, Ref cell) { auto data_cell = Ref(cell); if (data_cell.is_null()) { return cell; } return set_data_cell(idx, std::move(data_cell)); } td::Result> load_any_cell(int idx) { { auto cell = get_any_cell(idx); if (cell.not_null()) { return std::move(cell); } } TRY_RESULT(cell_location, get_cell_location(idx)); auto buf = alloc(cell_location.end - cell_location.begin); TRY_RESULT(cell_slice, data_->view(buf.as_slice(), cell_location.begin)); TRY_RESULT(res, deserialize_any_cell(idx, cell_slice, cell_location.should_cache)); return std::move(res); } td::Result> load_data_cell(int idx) { { auto cell = get_data_cell(idx); if (cell.not_null()) { return std::move(cell); } } TRY_RESULT(cell_location, get_cell_location(idx)); auto buf = alloc(cell_location.end - cell_location.begin); TRY_RESULT(cell_slice, data_->view(buf.as_slice(), cell_location.begin)); TRY_RESULT(res, deserialize_data_cell(idx, cell_slice, cell_location.should_cache)); return std::move(res); } td::Result> deserialize_data_cell(int idx, td::Slice cell_slice, bool should_cache) { CellSerializationInfo cell_info; TRY_STATUS(cell_info.init(cell_slice, info_.ref_byte_size)); if (cell_slice.size() != cell_info.end_offset) { return td::Status::Error(PSLICE() << "unused space in cell #" << idx << " serialization"); } return deserialize_data_cell(idx, cell_slice, cell_info, should_cache); } td::Result> deserialize_data_cell(int idx, td::Slice cell_slice, const CellSerializationInfo& cell_info, bool should_cache) { deserialize_cell_cnt_.add(1); Ref refs[4]; CHECK(cell_info.refs_cnt <= 4); auto* ref_ptr = cell_slice.ubegin() + cell_info.refs_offset; for (int k = 0; k < cell_info.refs_cnt; k++, ref_ptr += info_.ref_byte_size) { int ref_idx = td::narrow_cast(info_.read_ref(ref_ptr)); if (ref_idx >= info_.cell_count) { return td::Status::Error(PSLICE() << "invalid bag-of-cells cell #" << idx << " refers to cell #" << ref_idx << " which is too big " << td::tag("cell_count", info_.cell_count)); } if (idx >= ref_idx) { return td::Status::Error(PSLICE() << "invalid bag-of-cells cell #" << idx << " refers to cell #" << ref_idx << " which is a backward reference"); } TRY_RESULT(ref, load_any_cell(ref_idx)); refs[k] = std::move(ref); } TRY_RESULT(data_cell, cell_info.create_data_cell(cell_slice, td::Span>(refs, cell_info.refs_cnt))); if (!should_cache) { return std::move(data_cell); } return set_data_cell(idx, std::move(data_cell)); } td::Result> deserialize_any_cell(int idx, td::Slice cell_slice, bool should_cache) { CellSerializationInfo cell_info; TRY_STATUS(cell_info.init(cell_slice, info_.ref_byte_size)); if (cell_info.with_hashes) { deserialize_cell_hash_cnt_.add(1); int n = cell_info.level_mask.get_hashes_count(); return create_ext_cell(cell_info.level_mask, cell_slice.substr(cell_info.hashes_offset, n * Cell::hash_bytes), cell_slice.substr(cell_info.depth_offset, n * Cell::depth_bytes), idx); } TRY_RESULT(data_cell, deserialize_data_cell(idx, cell_slice, cell_info, should_cache)); return std::move(data_cell); } td::Result> create_root_cell(Ref data_cell) { return StaticBocRootCell::create(std::move(data_cell), shared_from_this()); } }; td::Result> StaticBagOfCellsDbLazy::create(std::unique_ptr data, Options options) { return std::make_shared(std::move(data), std::move(options)); } td::Result> StaticBagOfCellsDbLazy::create(td::BufferSlice data, Options options) { return std::make_shared(vm::BufferSliceBlobView::create(std::move(data)), std::move(options)); } td::Result> StaticBagOfCellsDbLazy::create(std::string data, Options options) { return create(BufferSliceBlobView::create(td::BufferSlice(data)), std::move(options)); } } // namespace vm