/* 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/boc.h" #include "vm/cellslice.h" #include "vm/cells.h" #include "common/AtomicRef.h" #include "vm/cells/MerkleProof.h" #include "vm/cells/MerkleUpdate.h" #include "vm/db/BlobView.h" #include "vm/db/CellStorage.h" #include "vm/db/CellHashTable.h" #include "vm/db/TonDb.h" #include "vm/db/StaticBagOfCellsDb.h" #include "td/utils/base64.h" #include "td/utils/benchmark.h" #include "td/utils/crypto.h" #include "td/utils/Random.h" #include "td/utils/Slice.h" #include "td/utils/Status.h" #include "td/utils/Timer.h" #include "td/utils/filesystem.h" #include "td/utils/port/path.h" #include "td/utils/format.h" #include "td/utils/misc.h" #include "td/utils/tests.h" #include "td/utils/tl_parsers.h" #include "td/utils/tl_helpers.h" #include "td/db/RocksDb.h" #include "td/db/MemoryKeyValue.h" #include #include #include struct Step { std::function func; td::uint32 weight; }; class RandomSteps { public: RandomSteps(std::vector steps) : steps_(std::move(steps)) { for (auto &step : steps_) { steps_sum_ += step.weight; } } template void step(Random &rnd) { auto w = rnd() % steps_sum_; for (auto &step : steps_) { if (w < step.weight) { step.func(); break; } w -= step.weight; } } private: std::vector steps_; td::int32 steps_sum_ = 0; }; namespace vm { std::vector do_get_serialization_modes() { std::vector res; for (int i = 0; i < 32; i++) { if ((i & BagOfCells::Mode::WithCacheBits) && !(i & BagOfCells::Mode::WithIndex)) { continue; } res.push_back(i); } return res; } const std::vector &get_serialization_modes() { static auto modes = do_get_serialization_modes(); return modes; } template int get_random_serialization_mode(T &rnd) { auto &modes = get_serialization_modes(); return modes[rnd.fast(0, (int)modes.size() - 1)]; } class BenchSha256 : public td::Benchmark { public: std::string get_description() const override { return "SHA256"; } void run(int n) override { int res = 0; for (int i = 0; i < n; i++) { digest::SHA256 hasher; hasher.feed("abcd", 4); unsigned char buf[32]; hasher.extract(buf); res += buf[0]; } td::do_not_optimize_away(res); } }; class BenchSha256Reuse : public td::Benchmark { public: std::string get_description() const override { return "SHA256 reuse"; } void run(int n) override { int res = 0; digest::SHA256 hasher; for (int i = 0; i < n; i++) { hasher.reset(); hasher.feed("abcd", 4); unsigned char buf[32]; hasher.extract(buf); res += buf[0]; } td::do_not_optimize_away(res); } }; class BenchSha256Low : public td::Benchmark { public: std::string get_description() const override { return "SHA256 low level"; } void run(int n) override { int res = 0; SHA256_CTX ctx; for (int i = 0; i < n; i++) { SHA256_Init(&ctx); SHA256_Update(&ctx, "abcd", 4); unsigned char buf[32]; SHA256_Final(buf, &ctx); res += buf[0]; } td::do_not_optimize_away(res); } }; class BenchSha256Tdlib : public td::Benchmark { public: std::string get_description() const override { return "SHA256 TDLib"; } void run(int n) override { int res = 0; static TD_THREAD_LOCAL td::Sha256State *ctx; for (int i = 0; i < n; i++) { td::init_thread_local(ctx); ctx->init(); ctx->feed("abcd"); unsigned char buf[32]; ctx->extract(td::MutableSlice(buf, 32), false); res += buf[0]; } td::do_not_optimize_away(res); } }; TEST(Cell, sha_benchmark) { bench(BenchSha256Tdlib()); bench(BenchSha256Low()); bench(BenchSha256Reuse()); bench(BenchSha256()); } std::string serialize_boc(Ref cell, int mode = 31) { CHECK(cell.not_null()); vm::BagOfCells boc; boc.add_root(std::move(cell)); boc.import_cells().ensure(); auto res = boc.serialize_to_string(mode); CHECK(res.size() != 0); return res; } std::string serialize_boc(td::Span> cells, int mode = 31) { CHECK(!cells.empty()); vm::BagOfCells boc; for (auto cell : cells) { boc.add_root(std::move(cell)); } boc.import_cells().ensure(); auto res = boc.serialize_to_string(mode); CHECK(res.size() != 0); return res; } Ref deserialize_boc(td::Slice serialized) { vm::BagOfCells boc; boc.deserialize(serialized).ensure(); return boc.get_root_cell(); } std::vector> deserialize_boc_multiple(td::Slice serialized) { vm::BagOfCells boc; boc.deserialize(serialized).ensure(); std::vector> res; for (int i = 0; i < boc.get_root_count(); i++) { res.push_back(boc.get_root_cell(i)); } return res; } class CellExplorer { public: struct Op { enum { Pop, ReadCellSlice } type; bool should_load; int children_mask; }; struct Exploration { std::vector ops; std::string log; std::set visited; std::vector> visited_cells; }; static Exploration explore(Ref root, std::vector ops) { CellExplorer e(root); for (auto op : ops) { e.do_op(op); } return e.get_exploration(); } template static Exploration random_explore(Ref root, T &rnd) { CellExplorer e(root); int it = 0; int cnt = rnd.fast(1, 100); while (it++ < cnt && e.do_random_op(rnd)) { } return e.get_exploration(); } private: CellExplorer(Ref root) { if (root.not_null()) { cells_.push_back(std::move(root)); } } std::vector> cells_; Ref cs_; std::vector ops_; std::set visited_; std::map> visited_cells_; td::StringBuilder log_{{}, true}; void do_op(Op op) { ops_.push_back(op); log_op(op); switch (op.type) { case op.Pop: { CHECK(!cells_.empty()); CHECK(cs_.is_null()); auto cell = std::move(cells_.back()); cells_.pop_back(); visited_cells_.emplace(cell->get_hash(), cell); log_cell(cell); if (op.should_load) { log_loaded_cell(cell); visited_.insert(cell->get_hash()); // It is ok to visit the same vertex multiple times cs_ = Ref{true, NoVm(), std::move(cell)}; } break; } case op.ReadCellSlice: { CHECK(cs_.not_null()); log_cell_slice(cs_); for (unsigned i = 0; i < cs_->size_refs(); i++) { if ((op.children_mask >> i) % 2 != 0) { cells_.push_back(cs_->prefetch_ref(i)); } } cs_ = {}; break; } } } template bool do_random_op(T &rnd) { if (cs_.not_null()) { int children_mask = 0; if (cs_->size_refs() != 0 && rnd.fast(0, 3) != 0) { //children_mask = rnd.fast(1, (1 << cs_->size_refs()) - 1); children_mask = (1 << cs_->size_refs()) - 1; } do_op({Op::ReadCellSlice, false, children_mask}); return true; } if (!cells_.empty()) { do_op({Op::Pop, rnd.fast(0, 30) != 0, 0}); return true; } return false; } Exploration get_exploration() { std::vector> visited_cells; for (auto &it : visited_cells_) { visited_cells.push_back(it.second); } return {std::move(ops_), log_.as_cslice().str(), std::move(visited_), std::move(visited_cells)}; } void log_op(Op op) { switch (op.type) { case op.Pop: log_ << "pop" << (op.should_load ? " and load" : "") << "\n"; break; case op.ReadCellSlice: log_ << "read slice " << op.children_mask << "\n"; break; } } void log_cell(const Ref &cell) { log_ << cell->get_level_mask().get_mask() << " " << cell->get_hash() << "\n"; } void log_loaded_cell(const Ref &cell) { log_ << "depth: "; for (unsigned i = 0; i <= cell->get_level(); i++) { log_ << cell->get_depth(i) << " "; } log_ << "\n"; } void log_cell_slice(const Ref &cs) { log_ << cs->special_type() << " " << cs->size() << " " << cs->size_refs() << " " << td::bitstring::bits_to_hex(cs->data_bits(), cs->size()) << "\n"; } }; class RandomBagOfCells { public: template RandomBagOfCells(size_t size, T &rnd, bool with_prunned_branches, std::vector> cells) { std::map depth; for (auto &cell : cells) { nodes_.emplace_back(cell, calc_depth(cell, depth)); } for (size_t i = 0; i < size; i++) { add_random_cell(rnd, with_prunned_branches); } } Ref get_root() { CHECK(!nodes_.empty()); // Fix root to be zero level while (nodes_.back().cell->get_level() != 0) { nodes_.emplace_back(CellBuilder::create_merkle_proof(nodes_.back().cell), nodes_.back().merkle_depth + 1); } return nodes_.back().cell; } template std::vector> get_random_roots(size_t size, T &rnd) { CHECK(!nodes_.empty()); std::vector> res(size); for (auto &c : res) { c = nodes_[rnd.fast(0, static_cast(nodes_.size()) - 1)].cell; } return res; } size_t get_size() const { return nodes_.size(); } template void add_random_cell(T &rnd, bool with_prunned_branches = true) { int cnt = 0; while (true) { CellBuilder cb; int next_cnt = rnd.fast(0, Cell::max_refs); int merkle_depth = 0; for (int j = 0; j < next_cnt && !nodes_.empty(); j++) { int to = rnd.fast(j == 0 && nodes_.size() > 3 ? (int)nodes_.size() - 3 : 0, (int)nodes_.size() - 1); merkle_depth = td::max(merkle_depth, nodes_.at(to).merkle_depth); cb.store_ref(nodes_[to].cell); } int size = rnd.fast(0, 4); for (int j = 0; j < size; j++) { cb.store_bytes(&"ab"[rnd.fast(0, 1)], 1); } if (rnd.fast(0, 4) == 4) { cb.store_bits(rnd.fast(0, 1) ? "\xff" : "\x55", rnd.fast(1, 7)); } Ref cell = cb.finalize(); auto cell_level = cell->get_level(); if (with_prunned_branches) { if (rnd.fast(0, 5) == 0 && cell_level + 1 < Cell::max_level) { cell = CellBuilder::create_pruned_branch(std::move(cell), cell_level + 1); } if (merkle_depth + 1 + cell->get_level() < Cell::max_level && rnd.fast(0, 10) == 0) { cell = CellBuilder::create_merkle_proof(std::move(cell)); merkle_depth++; } } if (merkle_depth + cell->get_level() >= Cell::max_level) { cnt++; CHECK(cnt < 1000); continue; } CHECK(cell.not_null()); nodes_.emplace_back(std::move(cell), merkle_depth); break; } } private: struct Node { Node() = default; Node(Ref cell, int merkle_depth) : cell(std::move(cell)), merkle_depth(merkle_depth) { } Ref cell; int merkle_depth; }; std::vector nodes_; auto calc_depth(const Ref &root, std::map &depth) -> int { auto it_flag = depth.emplace(root->get_hash(), 0); if (!it_flag.second) { return it_flag.first->second; } auto res = 0; CellSlice cs(NoVm(), root); for (unsigned i = 0; i < cs.size_refs(); i++) { res = std::max(res, calc_depth(cs.prefetch_ref(i), depth)); } if (cs.special_type() == Cell::SpecialType::MerkleProof) { res++; } depth[root->get_hash()] = res; return res; }; }; template void random_shuffle(td::MutableSpan v, td::Random::Xorshift128plus &rnd) { for (std::size_t i = 1; i < v.size(); i++) { auto pos = static_cast(rnd() % (i + 1)); std::swap(v[i], v[pos]); } } Ref gen_random_cell(int size, td::Random::Xorshift128plus &rnd, bool with_prunned_branches = true, std::vector> cells = {}) { if (!cells.empty()) { random_shuffle(td::MutableSpan>(cells), rnd); cells.resize(cells.size() % rnd()); } return RandomBagOfCells(size, rnd, with_prunned_branches, std::move(cells)).get_root(); } std::vector> gen_random_cells(int roots, int size, td::Random::Xorshift128plus &rnd, bool with_prunned_branches = true, std::vector> cells = {}) { if (!cells.empty()) { random_shuffle(td::MutableSpan>(cells), rnd); cells.resize(cells.size() % rnd()); } return RandomBagOfCells(size, rnd, with_prunned_branches, std::move(cells)).get_random_roots(roots, rnd); } TEST(Cell, MerkleProof) { td::Random::Xorshift128plus rnd{123}; for (int t = 0; t < 1000; t++) { bool with_prunned_branches = true; auto cell = gen_random_cell(rnd.fast(1, 1000), rnd, with_prunned_branches); auto exploration = CellExplorer::random_explore(cell, rnd); auto usage_tree = std::make_shared(); auto usage_cell = UsageCell::create(cell, usage_tree->root_ptr()); auto exploration2 = CellExplorer::explore(usage_cell, exploration.ops); ASSERT_EQ(exploration.log, exploration2.log); auto is_prunned = [&](const Ref &cell) { return exploration.visited.count(cell->get_hash()) == 0; }; auto proof = MerkleProof::generate(cell, is_prunned); // CellBuilder::virtualize(proof, 1); //ASSERT_EQ(1u, proof->get_level()); auto virtualized_proof = MerkleProof::virtualize(proof, 1); auto exploration3 = CellExplorer::explore(virtualized_proof, exploration.ops); ASSERT_EQ(exploration.log, exploration3.log); auto proof2 = MerkleProof::generate(cell, usage_tree.get()); CHECK(proof2->get_depth() == proof->get_depth()); auto virtualized_proof2 = MerkleProof::virtualize(proof2, 1); auto exploration4 = CellExplorer::explore(virtualized_proof2, exploration.ops); ASSERT_EQ(exploration.log, exploration4.log); } }; TEST(Cell, MerkleProofCombine) { td::Random::Xorshift128plus rnd{123}; for (int t = 0; t < 1000; t++) { bool with_prunned_branches = true; auto cell = gen_random_cell(rnd.fast(1, 1000), rnd, with_prunned_branches); auto exploration1 = CellExplorer::random_explore(cell, rnd); auto exploration2 = CellExplorer::random_explore(cell, rnd); Ref proof1; { auto usage_tree = std::make_shared(); auto usage_cell = UsageCell::create(cell, usage_tree->root_ptr()); CellExplorer::explore(usage_cell, exploration1.ops); proof1 = MerkleProof::generate(cell, usage_tree.get()); auto virtualized_proof = MerkleProof::virtualize(proof1, 1); auto exploration = CellExplorer::explore(virtualized_proof, exploration1.ops); ASSERT_EQ(exploration.log, exploration1.log); } Ref proof2; { auto usage_tree = std::make_shared(); auto usage_cell = UsageCell::create(cell, usage_tree->root_ptr()); CellExplorer::explore(usage_cell, exploration2.ops); proof2 = MerkleProof::generate(cell, usage_tree.get()); auto virtualized_proof = MerkleProof::virtualize(proof2, 1); auto exploration = CellExplorer::explore(virtualized_proof, exploration2.ops); ASSERT_EQ(exploration.log, exploration2.log); } Ref proof12; { auto usage_tree = std::make_shared(); auto usage_cell = UsageCell::create(cell, usage_tree->root_ptr()); CellExplorer::explore(usage_cell, exploration1.ops); CellExplorer::explore(usage_cell, exploration2.ops); proof12 = MerkleProof::generate(cell, usage_tree.get()); auto virtualized_proof = MerkleProof::virtualize(proof12, 1); auto exploration_a = CellExplorer::explore(virtualized_proof, exploration1.ops); auto exploration_b = CellExplorer::explore(virtualized_proof, exploration2.ops); ASSERT_EQ(exploration_a.log, exploration1.log); ASSERT_EQ(exploration_b.log, exploration2.log); } Ref proof_union; { proof_union = MerkleProof::combine(proof1, proof2); ASSERT_EQ(proof_union->get_hash(), proof12->get_hash()); auto virtualized_proof = MerkleProof::virtualize(proof_union, 1); auto exploration_a = CellExplorer::explore(virtualized_proof, exploration1.ops); auto exploration_b = CellExplorer::explore(virtualized_proof, exploration2.ops); ASSERT_EQ(exploration_a.log, exploration1.log); ASSERT_EQ(exploration_b.log, exploration2.log); } { auto cell = MerkleProof::virtualize(proof12, 1); auto usage_tree = std::make_shared(); auto usage_cell = UsageCell::create(cell, usage_tree->root_ptr()); CellExplorer::explore(usage_cell, exploration1.ops); auto proof = MerkleProof::generate(cell, usage_tree.get()); auto virtualized_proof = MerkleProof::virtualize(proof, 2); auto exploration = CellExplorer::explore(virtualized_proof, exploration1.ops); ASSERT_EQ(exploration.log, exploration1.log); if (proof->get_hash() != proof1->get_hash()) { CellSlice(NoVm(), proof12).print_rec(std::cerr); CellSlice(NoVm(), proof).print_rec(std::cerr); CellSlice(NoVm(), proof1).print_rec(std::cerr); LOG(ERROR) << proof->get_level() << " " << proof->get_hash().to_hex(); LOG(ERROR) << proof->get_level() << " " << proof1->get_hash().to_hex(); LOG(FATAL) << "?"; } } } }; int X = 20; Ref gen_random_cell(int size, Ref from, td::Random::Xorshift128plus &rnd, bool with_prunned_branches = true) { auto exploration = CellExplorer::random_explore(from, rnd); return gen_random_cell(size, rnd, with_prunned_branches, std::move(exploration.visited_cells)); } auto gen_merkle_update(Ref cell, td::Random::Xorshift128plus &rnd, bool with_prunned_branches) { auto usage_tree = std::make_shared(); auto usage_cell = UsageCell::create(cell, usage_tree->root_ptr()); auto new_cell = gen_random_cell(rnd.fast(1, X), usage_cell, rnd, with_prunned_branches); auto update = MerkleUpdate::generate(cell, new_cell, usage_tree.get()); return std::make_tuple(new_cell, update, usage_tree); }; void check_merkle_update(Ref A, Ref B, Ref AB) { CHECK(AB.not_null()); CHECK(A.not_null()); MerkleUpdate::may_apply(A, AB).ensure(); MerkleUpdate::validate(AB).ensure(); auto got_B = MerkleUpdate::apply(A, AB); ASSERT_EQ(B->get_hash(), got_B->get_hash()); }; TEST(Cell, MerkleUpdate) { td::Random::Xorshift128plus rnd{123}; for (int t = 0; t < 1000; t++) { bool with_prunned_branches = true; auto A = gen_random_cell(rnd.fast(1, 1000), rnd, with_prunned_branches); Ref B; Ref AB; std::tie(B, AB, std::ignore) = gen_merkle_update(A, rnd, with_prunned_branches); check_merkle_update(A, B, AB); } }; TEST(Cell, MerkleUpdateCombine) { td::Random::Xorshift128plus rnd{123}; for (int t = 0; t < 1000; t++) { bool with_prunned_branches = true; auto A = gen_random_cell(rnd.fast(1, X), rnd, with_prunned_branches); Ref B; Ref AB; std::tie(B, AB, std::ignore) = gen_merkle_update(A, rnd, with_prunned_branches); check_merkle_update(A, B, AB); Ref C; Ref BC; std::tie(C, BC, std::ignore) = gen_merkle_update(B, rnd, with_prunned_branches); check_merkle_update(B, C, BC); check_merkle_update(A, C, MerkleUpdate::combine(AB, BC)); } }; class BenchCellBuilder : public td::Benchmark { public: std::string get_description() const override { return "BenchCellBuilder"; } void run(int n) override { td::Random::Xorshift128plus rnd(123); std::string data(128, ' '); for (auto &c : data) { c = static_cast(rnd()); } for (int i = 0; i < n; i++) { CellBuilder cb; cb.store_bytes(data.data(), rnd() & 127); cb.finalize(false); } } }; TEST(TonDb, BenchCellBuilder) { td::bench(BenchCellBuilder()); } class BenchCellBuilder2 : public td::Benchmark { public: std::string get_description() const override { return "BenchCellBuilder"; } void run(int n) override { td::Random::Xorshift128plus rnd(123); for (int i = 0; i < n; i++) { gen_random_cell(rnd.fast(1, 1000), rnd); } } }; TEST(TonDb, BenchCellBuilder2) { td::bench(BenchCellBuilder2()); } class BenchCellBuilder3 : public td::Benchmark { public: std::string get_description() const override { return "BenchCellBuilder"; } void run(int n) override { td::Random::Xorshift128plus rnd(123); for (int i = 0; i < n; i++) { auto cell = gen_random_cell(rnd.fast(1, 1000), rnd, false); auto cell_hash = cell->get_hash().to_hex(); int mode = get_random_serialization_mode(rnd); auto serialized = serialize_boc(std::move(cell), mode); CHECK(serialized.size() != 0); auto loaded_cell = deserialize_boc(serialized); ASSERT_EQ(cell_hash, loaded_cell->get_hash().to_hex()); auto new_serialized = serialize_boc(std::move(loaded_cell), mode); ASSERT_EQ(serialized, new_serialized); } } }; TEST(TonDb, BenchCellBuilder3) { td::bench(BenchCellBuilder3()); } TEST(TonDb, Boc) { td::Random::Xorshift128plus rnd{123}; for (int t = 0; t < 1000; t++) { auto cell = gen_random_cell(rnd.fast(1, 1000), rnd); auto cell_hash = cell->get_hash(); auto mode = get_random_serialization_mode(rnd); auto serialized = serialize_boc(std::move(cell), mode); CHECK(serialized.size() != 0); auto loaded_cell = deserialize_boc(serialized); ASSERT_EQ(cell_hash, loaded_cell->get_hash()); auto new_serialized = serialize_boc(std::move(loaded_cell), mode); ASSERT_EQ(serialized, new_serialized); } }; TEST(TonDb, BocMultipleRoots) { td::Random::Xorshift128plus rnd{123}; for (int t = 0; t < 200; t++) { auto cells = gen_random_cells(rnd.fast(1, 10), rnd.fast(1, 1000), rnd); std::vector cell_hashes; for (size_t i = 0; i < cells.size(); i++) { cell_hashes.push_back(cells[i]->get_hash()); } auto mode = get_random_serialization_mode(rnd); auto serialized = serialize_boc(cells, mode); CHECK(serialized.size() != 0); auto loaded_cells = deserialize_boc_multiple(serialized); ASSERT_EQ(cell_hashes.size(), loaded_cells.size()); for (size_t i = 0; i < cell_hashes.size(); i++) { ASSERT_EQ(cell_hashes[i], loaded_cells[i]->get_hash()); } auto new_serialized = serialize_boc(std::move(loaded_cells), mode); ASSERT_EQ(serialized, new_serialized); } }; TEST(TonDb, DynamicBoc) { td::Random::Xorshift128plus rnd{123}; std::string old_root_hash; std::string old_root_serialization; auto kv = std::make_shared(); auto dboc = DynamicBagOfCellsDb::create(); dboc->set_loader(std::make_unique(kv)); for (int t = 1000; t >= 0; t--) { if (rnd() % 10 == 0) { dboc = DynamicBagOfCellsDb::create(); } dboc->set_loader(std::make_unique(kv)); Ref old_root; if (!old_root_hash.empty()) { old_root = dboc->load_cell(old_root_hash).move_as_ok(); auto serialization = serialize_boc(old_root); ASSERT_EQ(old_root_serialization, serialization); } auto cell = gen_random_cell(rnd.fast(1, 1000), rnd); old_root_hash = cell->get_hash().as_slice().str(); old_root_serialization = serialize_boc(cell); // Check that DynamicBagOfCells properly loads cells cell = vm::StaticBagOfCellsDbLazy::create(vm::BufferSliceBlobView::create(td::BufferSlice(old_root_serialization))) .move_as_ok() ->get_root_cell(0) .move_as_ok(); dboc->dec(old_root); if (t != 0) { dboc->inc(cell); } dboc->prepare_commit(); { CellStorer cell_storer(*kv); dboc->commit(cell_storer); } } ASSERT_EQ(0u, kv->count("").ok()); }; TEST(TonDb, DynamicBoc2) { int VERBOSITY_NAME(boc) = VERBOSITY_NAME(DEBUG) + 10; td::Random::Xorshift128plus rnd{123}; int total_roots = 10000; int max_roots = 20; std::vector root_hashes(max_roots); std::vector> roots(max_roots); int last_commit_at = 0; int first_root_id = 0; int last_root_id = 0; auto kv = std::make_shared(); auto dboc = DynamicBagOfCellsDb::create(); dboc->set_loader(std::make_unique(kv)); auto add_root = [&](Ref root) { dboc->inc(root); root_hashes[last_root_id % max_roots] = (root->get_hash().as_slice().str()); roots[last_root_id % max_roots] = root; last_root_id++; }; auto get_root = [&](int root_id) { VLOG(boc) << " from older root #" << root_id; auto from_root = roots[root_id % max_roots]; if (from_root.is_null()) { VLOG(boc) << " from db"; auto from_root_hash = root_hashes[root_id % max_roots]; from_root = dboc->load_cell(from_root_hash).move_as_ok(); } else { VLOG(boc) << "FROM MEMORY"; } return from_root; }; auto new_root = [&] { if (last_root_id == total_roots) { return; } if (last_root_id - first_root_id >= max_roots) { return; } VLOG(boc) << "add root"; Ref from_root; if (first_root_id != last_root_id) { from_root = get_root(rnd.fast(first_root_id, last_root_id - 1)); } VLOG(boc) << " ..."; add_root(gen_random_cell(rnd.fast(1, 20), from_root, rnd)); VLOG(boc) << " OK"; }; auto commit = [&] { VLOG(boc) << "commit"; dboc->prepare_commit(); { CellStorer cell_storer(*kv); dboc->commit(cell_storer); } dboc->set_loader(std::make_unique(kv)); for (int i = last_commit_at; i < last_root_id; i++) { roots[i % max_roots].clear(); } last_commit_at = last_root_id; }; auto reset = [&] { VLOG(boc) << "reset"; commit(); dboc = DynamicBagOfCellsDb::create(); dboc->set_loader(std::make_unique(kv)); }; auto delete_root = [&] { VLOG(boc) << "Delete root"; if (first_root_id == last_root_id) { return; } dboc->dec(get_root(first_root_id)); first_root_id++; VLOG(boc) << " OK"; }; RandomSteps steps({{new_root, 10}, {delete_root, 9}, {commit, 2}, {reset, 1}}); while (first_root_id != total_roots) { VLOG(boc) << first_root_id << " " << last_root_id << " " << kv->count("").ok(); steps.step(rnd); } commit(); ASSERT_EQ(0u, kv->count("").ok()); } template td::Status test_boc_deserializer(std::vector> cells, int mode) { auto total_data_cells_before = vm::DataCell::get_total_data_cells(); SCOPE_EXIT { auto total_data_cells_after = vm::DataCell::get_total_data_cells(); ASSERT_EQ(total_data_cells_before, total_data_cells_after); }; auto serialized = serialize_boc(cells, mode); CHECK(serialized.size() != 0); TRY_RESULT(boc_deserializer, BocDeserializerT::create(serialized)); TRY_RESULT(root_count, boc_deserializer->get_root_count()); ASSERT_EQ(cells.size(), root_count); std::vector> loaded_cells; for (size_t root_i = 0; root_i < root_count; root_i++) { TRY_RESULT(loaded_cell, boc_deserializer->get_root_cell(root_i)); auto cell = cells[root_i]; ASSERT_EQ(cell->get_level(), loaded_cell->get_level()); for (int i = 0; i <= (int)cell->get_level(); i++) { ASSERT_EQ(cell->get_hash(i), loaded_cell->get_hash(i)); } ASSERT_EQ(loaded_cell->get_hash(cell->get_level()), loaded_cell->get_hash()); loaded_cells.push_back(std::move(loaded_cell)); } auto new_serialized = serialize_boc(std::move(loaded_cells), mode); ASSERT_EQ(serialized, new_serialized); return td::Status::OK(); } template td::Status test_boc_deserializer_threads(Ref cell, int mode, td::Random::Xorshift128plus &rnd, size_t threads_n = 4) { auto serialized = serialize_boc(cell, mode); CHECK(serialized.size() != 0); std::vector explorations; for (size_t i = 0; i < threads_n; i++) { explorations.push_back(CellExplorer::random_explore(cell, rnd)); } TRY_RESULT(boc_deserializer, BocDeserializerT::create(serialized)); TRY_RESULT(root_count, boc_deserializer->get_root_count()); ASSERT_EQ(1u, root_count); TRY_RESULT(loaded_cell, boc_deserializer->get_root_cell(0)); std::vector threads; for (auto &exploration : explorations) { threads.emplace_back([&] { auto exploration2 = CellExplorer::explore(loaded_cell, exploration.ops); ASSERT_EQ(exploration.log, exploration2.log); }); } for (auto &thread : threads) { thread.join(); } return td::Status::OK(); } td::Status test_boc_deserializer_full(std::vector> cells) { for (auto mode : get_serialization_modes()) { TRY_STATUS(vm::test_boc_deserializer(cells, mode)); TRY_STATUS(vm::test_boc_deserializer(cells, mode)); } return td::Status::OK(); } td::Status test_boc_deserializer_full(Ref cell) { return test_boc_deserializer_full(std::vector>{std::move(cell)}); } template void test_boc_deserializer() { td::Random::Xorshift128plus rnd{123}; for (int t = 0; t < 1000; t++) { auto cells = gen_random_cells(rnd.fast(1, 10), static_cast(rnd() % 1000 + 1), rnd); for (auto mode : get_serialization_modes()) { test_boc_deserializer(cells, mode).ensure(); } } } TEST(TonDb, BocDeserializerBaseline) { test_boc_deserializer(); } TEST(TonDb, BocDeserializerSimple) { test_boc_deserializer(); } template void test_boc_deserializer_threads() { td::Random::Xorshift128plus rnd{123}; for (int t = 0; t < 20; t++) { auto cell = gen_random_cell(static_cast(rnd() % 1000 + 1), rnd); for (auto mode : get_serialization_modes()) { test_boc_deserializer_threads(cell, mode, rnd).ensure(); } } } TEST(TonDb, BocDeserializerSimpleThreads) { test_boc_deserializer_threads(); } class CompactArray { public: CompactArray(size_t size) { root_ = create(size, 0); size_ = size; } CompactArray(size_t size, Ref root) { root_ = std::move(root); size_ = size; } CompactArray(td::Span span) { root_ = create(span); size_ = span.size(); } CompactArray(CompactArray &&other) = default; CompactArray &operator=(CompactArray &&other) = default; td::Slice hash() const { return root()->get_hash().as_slice(); } void set(size_t pos, td::uint64 value) { root_ = set(root_, size_, pos, value); } td::uint64 get(size_t pos) { return get(root_, size_, pos, nullptr); } const Ref &root() const { return root_; } size_t size() const { return size_; } Ref merkle_proof(std::vector keys) { std::set hashes; for (auto key : keys) { get(root_, size_, key, &hashes); } auto is_prunned = [&](const Ref &cell) { return hashes.count(cell->get_hash()) == 0; }; return MerkleProof::generate_raw(root_, is_prunned); } private: Ref root_; size_t size_; static Ref create_list(td::uint64 value) { CellBuilder cb; cb.store_long(value, 64); return cb.finalize(); } static Ref create_node(Ref left, Ref right) { CellBuilder cb; cb.store_ref(std::move(left)); cb.store_ref(std::move(right)); return cb.finalize(); } static Ref create(size_t size, td::uint64 value) { if (size == 1) { return create_list(value); } return create_node(create(size / 2, value), create((size + 1) / 2, value)); } static Ref create(td::Span value) { if (value.size() == 1) { return create_list(value[0]); } return create_node(create(value.substr(0, value.size() / 2)), create(value.substr(value.size() / 2))); } static td::uint64 get(Ref any_cell, size_t size, size_t pos, std::set *hashes) { if (hashes) { hashes->insert(any_cell->get_hash()); } CellSlice cs(NoVm(), any_cell); assert(pos < size); if (size == 1) { return cs.fetch_long(64); } auto left = cs.fetch_ref(); if (pos < size / 2) { return get(left, size / 2, pos, hashes); } pos -= size / 2; auto right = cs.fetch_ref(); return get(right, (size + 1) / 2, pos, hashes); } static Ref set(Ref any_cell, size_t size, size_t pos, td::uint64 value) { CellSlice cs(NoVm(), any_cell); assert(pos < size); if (size == 1) { return create_list(value); } //LOG(ERROR) << cell->size_refs() << " " << cell->size_bits(); auto left = cs.fetch_ref(); auto right = cs.fetch_ref(); if (pos < size / 2) { left = set(left, size / 2, pos, value); } else { pos -= size / 2; right = set(right, (size + 1) / 2, pos, value); } return create_node(left, right); } }; class FastCompactArray { public: FastCompactArray(size_t size) : v_(size) { } void set(size_t pos, td::uint64 value) { v_.at(pos) = value; } td::uint64 get(size_t pos) { return v_.at(pos); } td::Span as_span() const { return v_; } private: std::vector v_; }; TEST(Cell, BocHands) { serialize_boc(CellBuilder{}.store_bytes("AAAAAAAA").finalize()); auto a = CellBuilder{}.store_bytes("abcd").store_ref(CellBuilder{}.store_bytes("???").finalize()).finalize(); a = CellBuilder{} .store_bits("XXX", 3) .store_ref(CellBuilder::create_pruned_branch(std::move(a), Cell::max_level)) .finalize(); auto serialized = serialize_boc(a); deserialize_boc(serialized); deserialize_boc(serialize_boc(std::vector>{a, a})); // CHECK backward compatibility with // serialized_boc_idx and serialized_boc_idx_crc32c //auto serialized_idx_crc_x = serialize_boc(a, BagOfCells::WithIndex | BagOfCells::WithCRC32C); //LOG(ERROR) << td::format::escaped(serialized_idx_crc_x); std::string serialized_idx_crc = td::Slice( "\254\303\247(\001\001\002\001\000*\004*\201\001P\001\210H\001\004\024\271\313\264\253\277\265\350dN\250{," "\372\021\012:I\354\322|\255\245\330\204+&\345\214\026\300\064\000\001\032\231\063\274") .str(); //auto serialized_idx_x = serialize_boc(a, BagOfCells::WithIndex); //LOG(ERROR) << td::format::escaped(serialized_idx_x); std::string serialized_idx = td::Slice( "h\377e\363\001\001\002\001\000*\004*\201\001P\001\210H\001\004\024\271\313\264\253\277\265\350dN\250{," "\372\021\012:I\354\322|\255\245\330\204+&\345\214\026\300\064\000\001") .str(); ASSERT_EQ(serialized, serialize_boc(deserialize_boc(serialized_idx))); ASSERT_EQ(serialized, serialize_boc(deserialize_boc(serialized_idx_crc))); } TEST(Cell, MerkleProofHands) { // data has a reference, because we do not prune lists auto data = CellBuilder{}.store_bytes("pruned data").store_ref(CellBuilder{}.finalize()).finalize(); auto prunned_data = CellBuilder::create_pruned_branch(data, data->get_level() + 1); ASSERT_EQ(1u, prunned_data->get_level()); ASSERT_EQ(prunned_data->get_hash(0), data->get_hash(0)); ASSERT_EQ(data->get_hash(0), data->get_hash(1)); ASSERT_TRUE(prunned_data->get_hash(1) != prunned_data->get_hash(0)); auto node = CellBuilder{}.store_bytes("protected data").store_ref(data).finalize(); auto proof = CellBuilder{}.store_bits(node->get_data(), node->get_bits()).store_ref(prunned_data).finalize(); ASSERT_EQ(0u, node->get_level()); ASSERT_EQ(1u, proof->get_level()); ASSERT_EQ(proof->get_hash(0), node->get_hash(0)); ASSERT_TRUE(proof->get_hash(1) != node->get_hash(1)); test_boc_deserializer_full(proof).ensure(); auto merkle_proof = CellBuilder::create_merkle_proof(proof); ASSERT_EQ(0u, merkle_proof->get_level()); test_boc_deserializer_full(merkle_proof).ensure(); { auto virtual_node = proof->virtualize({0, 1}); ASSERT_EQ(0u, virtual_node->get_level()); ASSERT_EQ(1u, virtual_node->get_virtualization()); CellSlice cs{NoVm(), virtual_node}; auto virtual_data = cs.fetch_ref(); ASSERT_EQ(0u, virtual_data->get_level()); ASSERT_EQ(1u, virtual_data->get_virtualization()); ASSERT_EQ(data->get_hash(), virtual_data->get_hash()); auto virtual_node_copy = CellBuilder{}.store_bits(node->get_data(), node->get_bits()).store_ref(virtual_data).finalize(); ASSERT_EQ(0u, virtual_node_copy->get_level()); ASSERT_EQ(1u, virtual_node_copy->get_virtualization()); ASSERT_EQ(virtual_node->get_hash(), virtual_node_copy->get_hash()); { auto two_nodes = CellBuilder{}.store_ref(virtual_node).store_ref(node).finalize(); ASSERT_EQ(0u, two_nodes->get_level()); ASSERT_EQ(1u, two_nodes->get_virtualization()); CellSlice cs2(NoVm(), two_nodes); ASSERT_EQ(1u, cs2.prefetch_ref(0)->get_virtualization()); ASSERT_EQ(0u, cs2.prefetch_ref(1)->get_virtualization()); } } LOG(ERROR) << td::NamedThreadSafeCounter::get_default(); } TEST(Cell, MerkleProofArrayHands) { // create simple array CompactArray arr(17); for (size_t i = 0; i < arr.size(); i++) { arr.set(i, i / 3); } // create merke proof for 4 5 6 and 16th elements std::vector keys = {4, 5, 6, 16}; auto proof = arr.merkle_proof(keys); ASSERT_EQ(1u, proof->get_level()); ASSERT_EQ(proof->get_hash(0), arr.root()->get_hash(0)); ASSERT_TRUE(proof->get_hash(1) != arr.root()->get_hash(1)); ASSERT_EQ(arr.root()->get_hash(0), arr.root()->get_hash(1)); CompactArray new_arr(arr.size(), proof->virtualize({0, 1})); for (auto k : keys) { ASSERT_EQ(arr.get(k), new_arr.get(k)); } test_boc_deserializer_full(proof).ensure(); test_boc_deserializer_full(CellBuilder::create_merkle_proof(proof)).ensure(); } TEST(Cell, MerkleUpdateHands) { auto data = CellBuilder{}.store_bytes("pruned data").store_ref(CellBuilder{}.finalize()).finalize(); auto node = CellBuilder{}.store_bytes("protected data").store_ref(data).finalize(); auto other_node = CellBuilder{}.store_bytes("other protected data").store_ref(data).finalize(); auto usage_tree = std::make_shared(); auto other_usage_tree = std::make_shared(); auto usage_cell = UsageCell::create(node, usage_tree->root_ptr()); auto child = CellSlice(vm::NoVm(), usage_cell).prefetch_ref(0); auto new_node = CellBuilder{}.store_bytes("new data").store_ref(child).finalize(); auto new_child = CellSlice(vm::NoVm(), new_node).prefetch_ref(0); auto update = MerkleUpdate::generate(usage_cell, new_node, usage_tree.get()); MerkleUpdate::may_apply(node, update).ensure(); MerkleUpdate::validate(update).ensure(); auto x = MerkleUpdate::apply(node, update); ASSERT_TRUE(serialize_boc(new_node) == serialize_boc(x)); MerkleUpdate::may_apply(other_node, update).ensure_error(); ASSERT_TRUE(MerkleUpdate::apply(other_node, update).is_null()); auto other_update = CellBuilder::create_merkle_update(CellBuilder::create_pruned_branch(other_node, 1), CellBuilder::create_pruned_branch(new_node, 1)); MerkleUpdate::may_apply(node, other_update).ensure_error(); MerkleUpdate::validate(other_update).ensure_error(); ASSERT_TRUE(MerkleUpdate::apply(other_node, other_update).is_null()); auto bad_update = CellBuilder::create_merkle_update(CellBuilder::create_pruned_branch(new_node, 1), CellBuilder::create_pruned_branch(other_node, 1)); CHECK(MerkleUpdate::combine(update, bad_update).is_null()); } TEST(Cell, MerkleUpdateArray) { // create simple array size_t n = 1 << 20; std::vector data; for (size_t i = 0; i < n; i++) { data.push_back(i / 3); } CompactArray arr(data); auto root = arr.root(); auto usage_tree = std::make_shared(); auto usage_cell = UsageCell::create(root, usage_tree->root_ptr()); arr = CompactArray(n, usage_cell); arr.set(n / 2, 0); arr.set(n / 2 + 1, 1); arr.set(n / 2 + 2, 2414221111); arr.set(n / 2 + 3, 2); auto update = MerkleUpdate::generate(usage_cell, arr.root(), usage_tree.get()); CellStorageStat stat; stat.compute_used_storage(update, false); ASSERT_EQ(stat.cells, 81u); //CellSlice(NoVm(), update).print_rec(std::cerr); check_merkle_update(root, arr.root(), update); } TEST(Cell, MerkleUpdateCombineArray) { size_t n = 1 << 10; std::vector data; for (size_t i = 0; i < n; i++) { data.push_back(i / 3); } CompactArray arr(data); auto from = arr.root(); std::shared_ptr usage_tree; Ref usage_cell; std::vector> updates; auto apply_op = [&](auto op) { auto A = arr.root(); usage_tree = std::make_shared(); usage_cell = UsageCell::create(arr.root(), usage_tree->root_ptr()); arr = CompactArray(n, usage_cell); op(); updates.push_back(MerkleUpdate::generate(A, arr.root(), usage_tree.get())); }; auto combine_all = [&]() { while (updates.size() > 1) { size_t i = updates.size() - 2; updates[i] = MerkleUpdate::combine(updates[i], updates[i + 1]); updates.pop_back(); CellStorageStat stat; stat.compute_used_storage(updates[i], false); } }; auto validate = [&](size_t size) { combine_all(); check_merkle_update(from, arr.root(), updates.at(0)); CellStorageStat stat; stat.compute_used_storage(updates[0], false); if (size != 0) { ASSERT_EQ(size, stat.cells); } }; apply_op([] {}); validate(3); apply_op([] {}); apply_op([] {}); apply_op([] {}); validate(3); apply_op([&] { for (size_t i = 0; i < n; i++) { arr.set(i, i / 3 + 10); } }); apply_op([&] { for (size_t i = 0; i < n; i++) { arr.set(i, i / 3); } }); validate(3); for (size_t i = 0; i + 1 < n; i++) { apply_op([&] { arr.set(i, i / 3 + 1); if (i != 0) { arr.set(i - 1, (i - 1) / 3); } }); } validate(41); } } // namespace vm class BenchBocSerializerImport : public td::Benchmark { public: BenchBocSerializerImport() { std::vector v(array_size); td::Random::Xorshift128plus rnd{123}; for (auto &x : v) { x = rnd(); } arr = vm::CompactArray(v); //serialization_ = td::BufferSlice(boc.serialize_to_string(15)); } std::string get_description() const override { return "BenchBocSerializer"; } void run(int n) override { for (int i = 0; i < n; i++) { vm::BagOfCells boc; boc.add_root(arr.root()); boc.import_cells().ensure(); } } private: td::BufferSlice serialization_; static constexpr td::uint32 array_size = 1024; vm::CompactArray arr{1}; }; class BenchBocSerializerSerialize : public td::Benchmark { public: BenchBocSerializerSerialize() { std::vector v(array_size); td::Random::Xorshift128plus rnd{123}; for (auto &x : v) { x = rnd(); } arr = vm::CompactArray(v); boc.add_root(arr.root()); boc.import_cells().ensure(); } std::string get_description() const override { return "BenchBocSerializer"; } void run(int n) override { for (int i = 0; i < n; i++) { boc.serialize_to_string(31); } } private: td::BufferSlice serialization_; static constexpr td::uint32 array_size = 1024; vm::CompactArray arr{1}; vm::BagOfCells boc; }; struct BenchBocDeserializerConfig { enum BlobType { File, Memory, FileMemoryMap, RocksDb } blob_type; int k{100}; enum Mode { Prefix, Range, Random } mode{Random}; bool with_index{true}; int threads_n{1}; }; td::StringBuilder &operator<<(td::StringBuilder &sb, const BenchBocDeserializerConfig &config) { sb << "load from "; switch (config.blob_type) { case BenchBocDeserializerConfig::File: sb << "file"; break; case BenchBocDeserializerConfig::Memory: sb << "memory"; break; case BenchBocDeserializerConfig::FileMemoryMap: sb << "file mmap"; break; case BenchBocDeserializerConfig::RocksDb: sb << "rocksdb"; break; } sb << td::tag("k", config.k) << " "; switch (config.mode) { case BenchBocDeserializerConfig::Prefix: sb << "prefix"; break; case BenchBocDeserializerConfig::Range: sb << "range"; break; case BenchBocDeserializerConfig::Random: sb << "random"; break; } sb << " " << (config.with_index ? "with" : "without") << " index"; sb << " " << config.threads_n << " threads"; return sb; } template class BenchBocDeserializer : public td::Benchmark { public: BenchBocDeserializer(std::string name, BenchBocDeserializerConfig config) : name_(std::move(name)), config_(config) { fast_array_ = vm::FastCompactArray(array_size); td::Random::Xorshift128plus rnd{123}; for (td::uint32 i = 0; i < array_size; i++) { auto val = rnd(); fast_array_.set(i, val); } vm::CompactArray arr(fast_array_.as_span()); auto db_path = "serialization_rocksdb"; if (config.blob_type == BenchBocDeserializerConfig::RocksDb) { { td::RocksDb::destroy(td::Slice(db_path)).ensure(); auto db = vm::TonDbImpl::open(td::Slice(db_path)).move_as_ok(); auto txn = db->begin_transaction(); auto smt = txn->begin_smartcontract(); SCOPE_EXIT { db->commit_transaction(std::move(txn)); }; SCOPE_EXIT { txn->commit_smartcontract(std::move(smt)); }; smt->set_root(arr.root()); } db_ = vm::TonDbImpl::open(td::Slice(db_path)).move_as_ok(); } else { serialization_ = td::BufferSlice(serialize_boc( arr.root(), vm::BagOfCells::WithIntHashes | vm::BagOfCells::WithTopHash | (config.with_index ? vm::BagOfCells::WithIndex | vm::BagOfCells::WithCacheBits : 0))); if (config.blob_type == BenchBocDeserializerConfig::File || config.blob_type == BenchBocDeserializerConfig::FileMemoryMap) { td::unlink("serialization").ignore(); td::write_file("serialization", serialization_.as_slice()).ensure(); } } root_ = arr.root(); } std::string get_description() const override { return PSTRING() << "BocDeserializer " << name_ << " " << config_; } vm::Ref load_root() { if (config_.blob_type == BenchBocDeserializerConfig::RocksDb) { auto txn = db_->begin_transaction(); auto smt = txn->begin_smartcontract(); SCOPE_EXIT { db_->abort_transaction(std::move(txn)); }; SCOPE_EXIT { txn->commit_smartcontract(std::move(smt)); }; LOG(ERROR) << "load root from rocksdb"; return smt->get_root(); } auto blob = [&] { switch (config_.blob_type) { case BenchBocDeserializerConfig::File: return vm::FileBlobView::create("serialization").move_as_ok(); case BenchBocDeserializerConfig::Memory: return vm::BufferSliceBlobView::create(serialization_.clone()); case BenchBocDeserializerConfig::FileMemoryMap: return vm::FileMemoryMappingBlobView::create("serialization").move_as_ok(); default: UNREACHABLE(); } UNREACHABLE(); }(); auto boc_deserializer = DeserializerT::create(std::move(blob)).move_as_ok(); ASSERT_EQ(1u, boc_deserializer->get_root_count().move_as_ok()); return boc_deserializer->get_root_cell(0).move_as_ok(); } void run(int n) override { td::Random::Xorshift128plus rnd{123}; std::vector threads; //TODO: use config.k auto K = config_.k == 0 ? n : config_.k; td::Stage stage; vm::Ref root; for (int t = 0; t < config_.threads_n; t++) { threads.emplace_back([&, seed = rnd(), thread_i = t] { for (int round_i = 0; round_i < n / K; round_i++) { if (thread_i == 0) { root = load_root(); } stage.wait(config_.threads_n * (2 * round_i + 1)); vm::CompactArray array(array_size, root); td::Random::Xorshift128plus rnd{seed}; td::uint64 start_pos = config_.mode == BenchBocDeserializerConfig::Range ? array_size / config_.threads_n * thread_i : 0; for (int k = 0; k < K; k++) { auto pos = start_pos; switch (config_.mode) { case BenchBocDeserializerConfig::Prefix: case BenchBocDeserializerConfig::Range: pos = (pos + k) % array_size; break; case BenchBocDeserializerConfig::Random: pos = rnd() % array_size; break; } ASSERT_EQ(fast_array_.get(td::narrow_cast(pos)), array.get(td::narrow_cast(pos))); } stage.wait(config_.threads_n * (2 * round_i + 2)); } }); } for (auto &thread : threads) { thread.join(); } } private: std::string name_; td::BufferSlice serialization_; BenchBocDeserializerConfig config_; static constexpr td::uint32 array_size = 1024 * 1024; vm::FastCompactArray fast_array_{array_size}; vm::Ref root_; vm::TonDb db_; }; TEST(TonDb, BenchBocSerializerImport) { if (0) { BenchBocSerializerImport b; while (true) { td::bench_n(b, 1000000); } } td::bench(BenchBocSerializerImport()); } TEST(TonDb, BenchBocSerializerSerialize) { td::bench(BenchBocSerializerSerialize()); } template void bench_deserializer(std::string name, bool full) { using Config = BenchBocDeserializerConfig; if (full) { for (auto k : {1, 10, 100}) { for (auto with_index : {false, true}) { for (auto mode : {Config::Prefix, Config::Range, Config::Random}) { for (auto blob_type : {Config::Memory, Config::File, Config::FileMemoryMap}) { BenchBocDeserializerConfig config; config.k = k; config.with_index = with_index; config.mode = mode; config.blob_type = blob_type; td::bench(BenchBocDeserializer(name, config)); } } } } } else { td::bench(BenchBocDeserializer(name, BenchBocDeserializerConfig())); } } template void bench_deserializer_threads(std::string name) { using Config = BenchBocDeserializerConfig; for (auto threads_n : {1, 4, 16}) { //for (auto threads_n : {16}) { //for (auto with_index : {false, true}) { //for (auto mode : {BenchBocDeserializerConfig::Prefix, BenchBocDeserializerConfig::Range, //BenchBocDeserializerConfig::Random}) { //for (auto from_file : {false, true}) { BenchBocDeserializerConfig config; config.threads_n = threads_n; config.k = 0; config.with_index = true; config.mode = Config::Random; config.mode = Config::Range; config.mode = Config::Prefix; config.blob_type = Config::Memory; td::bench(BenchBocDeserializer(name, config)); //td::bench_n(BenchBocThreadsDeserializer(name, config), 1000000); //} //} //} } } TEST(TonDb, BenchBocThreadsDeserializerSimple) { //td::bench_n(BenchBocDeserializer("simple", BenchBocDeserializerConfig()), 1000000); //std::exit(0); bench_deserializer_threads("simple"); } TEST(TonDb, BenchBocDeserializerSimple) { //td::bench_n(BenchBocDeserializer("simple", BenchBocDeserializerConfig()), 1000000); //std::exit(0); bench_deserializer("simple", false); } TEST(TonDb, BenchBocDeserializerBaseline) { //td::bench_n(BenchBocDeserializer("baseline", BenchBocDeserializerConfig()), 1000000); //std::exit(0); bench_deserializer("baseline", false); } TEST(TonDb, BenchBocDeserializerRocksDb) { //td::bench_n(BenchBocDeserializer("baseline", BenchBocDeserializerConfig()), 1000000); //std::exit(0); auto config = BenchBocDeserializerConfig(); config.blob_type = BenchBocDeserializerConfig::RocksDb; config.threads_n = 4; config.k = 0; td::bench(BenchBocDeserializer("rockdb", config)); } TEST(TonDb, CompactArray) { SET_VERBOSITY_LEVEL(VERBOSITY_NAME(ERROR)); td::Slice db_path = "compact_array_db"; td::RocksDb::destroy(db_path).ensure(); td::Random::Xorshift128plus rnd(123); auto next_array_size = [&rnd]() { static std::vector array_sizes = {1, 2, 4, 10, 37, 100, 1000, 10000}; return array_sizes[rnd() % array_sizes.size()]; }; vm::CompactArray array(2); vm::FastCompactArray fast_array(2); auto next_pos = [&] { return static_cast(rnd() % array.size()); }; auto db = vm::TonDbImpl::open(db_path).move_as_ok(); auto txn = db->begin_transaction(); auto smt = txn->begin_smartcontract(); SCOPE_EXIT { db->commit_transaction(std::move(txn)); }; SCOPE_EXIT { txn->commit_smartcontract(std::move(smt)); }; auto flush_to_db = [&] { if (rnd() % 10 != 0) { return; } bool restart_db = rnd() % 20 == 0; bool reload_array = rnd() % 5 == 0; smt->set_root(array.root()); txn->commit_smartcontract(std::move(smt)); db->commit_transaction(std::move(txn)); if (restart_db) { db->clear_cache(); //db.reset(); //db = vm::TonDbImpl::open(db_path).move_as_ok(); } txn = db->begin_transaction(); smt = txn->begin_smartcontract(); smt->validate_meta().ensure(); ASSERT_EQ(smt->get_root()->get_hash(), array.root()->get_hash()); if (reload_array) { auto size = array.size(); array = vm::CompactArray(size, smt->get_root()); } }; auto do_validate = [&](size_t pos) { ASSERT_EQ(array.get(pos), fast_array.get(pos)); }; auto validate = [&] { do_validate(next_pos()); }; auto validate_full = [&] { for (size_t pos = 0; pos < array.size(); pos++) { do_validate(pos); } }; auto set_value = [&] { auto pos = static_cast(rnd() % array.size()); auto value = rnd() % 3; array.set(pos, value); fast_array.set(pos, value); }; auto reset_array = [&] { auto size = next_array_size(); array = vm::CompactArray(size); fast_array = vm::FastCompactArray(size); }; RandomSteps steps({{reset_array, 1}, {set_value, 1000}, {validate, 10}, {validate_full, 2}, {flush_to_db, 1}}); for (size_t t = 0; t < 100000; t++) { if (t % 10000 == 0) { LOG(ERROR) << t; } steps.step(rnd); } }; TEST(TonDb, CompactArrayOld) { SET_VERBOSITY_LEVEL(VERBOSITY_NAME(ERROR)); using namespace vm; //auto kv = std::make_unique(); td::RocksDb::destroy("ttt").ensure(); auto ton_db = vm::TonDbImpl::open("ttt").move_as_ok(); //auto storage = std::make_unique(kv.get()); size_t array_size = 1000; std::string hash; td::Random::Xorshift128plus rnd(123); FastCompactArray fast_array(array_size); { auto txn = ton_db->begin_transaction(); SCOPE_EXIT { ton_db->commit_transaction(std::move(txn)); }; auto smart = txn->begin_smartcontract(""); SCOPE_EXIT { txn->commit_smartcontract(std::move(smart)); }; CompactArray arr(array_size); arr.set(array_size / 2, 124); fast_array.set(array_size / 2, 124); //for (size_t i = 0; i < array_size; i++) { //int x = rnd() % 2; //arr.set(i, x); //fast_array.set(i, x); //} smart->set_root(arr.root()); LOG(ERROR) << smart->get_root()->get_hash().to_hex(); } //LOG(ERROR) << "OK"; for (int i = 0; i < 100; i++) { if (i % 10 == 9) { //LOG(ERROR) << ton_db->stat(); ton_db.reset(); ton_db = vm::TonDbImpl::open("ttt").move_as_ok(); } auto txn = ton_db->begin_transaction(); SCOPE_EXIT { ton_db->commit_transaction(std::move(txn)); }; auto smart = txn->begin_smartcontract(""); //smart->validate_meta(); SCOPE_EXIT { txn->commit_smartcontract(std::move(smart)); }; if (i % 1000 == 0) { LOG(ERROR) << "i = " << i; } CompactArray arr(array_size, smart->get_root()); auto key = static_cast(rnd() % array_size); auto value = rnd() % 2; arr.set(key, value); fast_array.set(key, value); smart->set_root(arr.root()); //LOG(ERROR) << storage->size(); } { auto txn = ton_db->begin_transaction(); SCOPE_EXIT { ton_db->abort_transaction(std::move(txn)); }; auto smart = txn->begin_smartcontract(""); SCOPE_EXIT { txn->abort_smartcontract(std::move(smart)); }; CompactArray arr(array_size, smart->get_root()); for (size_t i = 0; i < array_size; i++) { ASSERT_EQ(fast_array.get(i), arr.get(i)); } } } TEST(TonDb, StackOverflow) { try { td::Ref cell = vm::CellBuilder().finalize(); for (int i = 0; i < 10000000; i++) { vm::CellBuilder cb; cb.store_ref(std::move(cell)); cell = cb.finalize(); } LOG(ERROR) << "A"; vm::test_boc_deserializer({cell}, 31); LOG(ERROR) << "B"; vm::test_boc_deserializer({cell}, 31); LOG(ERROR) << "C"; } catch (...) { } struct A : public td::CntObject { explicit A(td::Ref next) : next(next) { } td::Ref next; }; { td::Ref head; for (int i = 0; i < 10000000; i++) { td::Ref new_head = td::Ref(true, std::move(head)); head = std::move(new_head); } } } TEST(TonDb, BocRespectsUsageCell) { td::Random::Xorshift128plus rnd(123); auto cell = vm::gen_random_cell(20, rnd, true); auto usage_tree = std::make_shared(); auto usage_cell = vm::UsageCell::create(cell, usage_tree->root_ptr()); auto serialization = serialize_boc(usage_cell); auto proof = vm::MerkleProof::generate(cell, usage_tree.get()); auto virtualized_proof = vm::MerkleProof::virtualize(proof, 1); auto serialization_of_virtualized_cell = serialize_boc(virtualized_proof); ASSERT_STREQ(serialization, serialization_of_virtualized_cell); } TEST(TonDb, DynamicBocRespectsUsageCell) { td::Random::Xorshift128plus rnd(123); auto cell = vm::gen_random_cell(20, rnd, true); auto usage_tree = std::make_shared(); auto usage_cell = vm::UsageCell::create(cell, usage_tree->root_ptr()); auto kv = std::make_shared(); auto dboc = vm::DynamicBagOfCellsDb::create(); dboc->set_loader(std::make_unique(kv)); dboc->inc(usage_cell); { vm::CellStorer cell_storer(*kv); dboc->commit(cell_storer); } auto proof = vm::MerkleProof::generate(cell, usage_tree.get()); auto virtualized_proof = vm::MerkleProof::virtualize(proof, 1); auto serialization_of_virtualized_cell = serialize_boc(virtualized_proof); auto serialization = serialize_boc(cell); ASSERT_STREQ(serialization, serialization_of_virtualized_cell); } TEST(TonDb, DoNotMakeListsPrunned) { auto cell = vm::CellBuilder().store_bytes("abc").finalize(); auto is_prunned = [&](const td::Ref &cell) { return true; }; auto proof = vm::MerkleProof::generate(cell, is_prunned); auto virtualized_proof = vm::MerkleProof::virtualize(proof, 1); ASSERT_TRUE(virtualized_proof->get_virtualization() == 0); } TEST(TonDb, CellStat) { td::Random::Xorshift128plus rnd(123); bool with_prunned_branches = true; for (int i = 0; i < 1000; i++) { auto A = vm::gen_random_cell(100, rnd, with_prunned_branches); td::Ref B, AB, B_proof; std::shared_ptr usage_tree; std::tie(B, AB, usage_tree) = gen_merkle_update(A, rnd, with_prunned_branches); B_proof = vm::CellSlice(vm::NoVm(), AB).prefetch_ref(1); vm::CellStorageStat stat; stat.add_used_storage(B); vm::NewCellStorageStat new_stat; new_stat.add_cell({}); new_stat.add_cell(B); ASSERT_EQ(stat.cells, new_stat.get_stat().cells); ASSERT_EQ(stat.bits, new_stat.get_stat().bits); vm::CellStorageStat proof_stat; proof_stat.add_used_storage(B_proof); vm::NewCellStorageStat new_proof_stat; new_proof_stat.add_proof(B, usage_tree.get()); CHECK(new_proof_stat.get_stat().cells == 0); CHECK(new_proof_stat.get_proof_stat().cells <= proof_stat.cells); //CHECK(new_proof_stat.get_proof_stat().cells + new_proof_stat.get_proof_stat().external_refs >= proof_stat.cells); vm::NewCellStorageStat new_all_stat; new_all_stat.add_cell_and_proof(B, usage_tree.get()); CHECK(new_proof_stat.get_proof_stat() == new_all_stat.get_proof_stat()); CHECK(new_stat.get_stat() == new_all_stat.get_stat()); stat.add_used_storage(A); auto AB_stat = new_stat.get_stat() + const_cast(new_stat).tentative_add_cell(A); new_stat.add_cell(A); CHECK(AB_stat == new_stat.get_stat()); ASSERT_EQ(stat.cells, new_stat.get_stat().cells); ASSERT_EQ(stat.bits, new_stat.get_stat().bits); CHECK(usage_tree.unique()); usage_tree.reset(); td::Ref C, BC, C_proof; std::shared_ptr usage_tree_B; std::tie(C, BC, usage_tree_B) = gen_merkle_update(B, rnd, with_prunned_branches); C_proof = vm::CellSlice(vm::NoVm(), BC).prefetch_ref(1); auto BC_proof_stat = new_proof_stat.get_proof_stat() + new_proof_stat.tentative_add_proof(C, usage_tree_B.get()); new_proof_stat.add_proof(C, usage_tree_B.get()); CHECK(BC_proof_stat == new_proof_stat.get_proof_stat()); } } struct String { String() { total_strings.add(1); } String(std::string str) : str(std::move(str)) { total_strings.add(1); } ~String() { total_strings.add(-1); } static td::ThreadSafeCounter total_strings; std::string str; }; td::ThreadSafeCounter String::total_strings; TEST(Ref, AtomicRef) { struct Node { td::AtomicRefLockfree> name_; char pad[64]; }; int threads_n = 10; std::vector nodes(threads_n); std::vector threads(threads_n); int thread_id = 0; for (auto &thread : threads) { thread = td::thread([&] { for (int i = 0; i < 1000000; i++) { auto &node = nodes[td::Random::fast(0, threads_n / 3 - 1)]; auto name = node.name_.load(); if (name.not_null()) { CHECK(name->str == "one" || name->str == "twotwo"); } if (td::Random::fast(0, 5) == 0) { auto new_string = td::Ref>{true, td::Random::fast(0, 1) == 0 ? "one" : "twotwo"}; node.name_.store(std::move(new_string)); } } }); thread_id++; } for (auto &thread : threads) { thread.join(); } nodes.clear(); LOG(ERROR) << String::total_strings.sum(); } //TEST(Tmp, Boc) { //LOG(ERROR) << "A"; //auto data = td::read_file("boc"); //LOG(ERROR) << "B"; //auto cell = vm::deserialize_boc(data.move_as_ok().as_slice()); //vm::CellStorageStat stat; //stat.add_used_storage(cell, false); //LOG(ERROR) << stat.cells; ////LOG(ERROR) << "C"; ////auto new_data = vm::serialize_boc(cell); ////LOG(ERROR) << "D"; //vm::test_boc_deserializer({cell}, 31); //}