mirror of
				https://github.com/ton-blockchain/ton
				synced 2025-03-09 15:40:10 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			510 lines
		
	
	
	
		
			17 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			510 lines
		
	
	
	
		
			17 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
/*
 | 
						|
    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 <http://www.gnu.org/licenses/>.
 | 
						|
 | 
						|
    Copyright 2017-2019 Telegram Systems LLP
 | 
						|
*/
 | 
						|
#include "vm/cells/MerkleUpdate.h"
 | 
						|
#include "vm/cells/MerkleProof.h"
 | 
						|
 | 
						|
#include "td/utils/HashMap.h"
 | 
						|
#include "td/utils/HashSet.h"
 | 
						|
 | 
						|
namespace vm {
 | 
						|
namespace detail {
 | 
						|
class MerkleUpdateApply {
 | 
						|
 public:
 | 
						|
  Ref<Cell> apply(Ref<Cell> from, Ref<Cell> update_from, Ref<Cell> update_to, td::uint32 from_level,
 | 
						|
                  td::uint32 to_level) {
 | 
						|
    if (from_level != from->get_level()) {
 | 
						|
      return {};
 | 
						|
    }
 | 
						|
    dfs_both(from, update_from, from_level);
 | 
						|
    return dfs(update_to, to_level);
 | 
						|
  }
 | 
						|
 | 
						|
 private:
 | 
						|
  using Key = std::pair<Cell::Hash, int>;
 | 
						|
  td::HashMap<Cell::Hash, Ref<Cell>> known_cells_;
 | 
						|
  td::HashMap<Key, Ref<Cell>> ready_cells_;
 | 
						|
 | 
						|
  void dfs_both(Ref<Cell> original, Ref<Cell> update_from, int merkle_depth) {
 | 
						|
    CellSlice cs_update_from(NoVm(), update_from);
 | 
						|
    known_cells_.emplace(original->get_hash(merkle_depth), original);
 | 
						|
    if (cs_update_from.special_type() == Cell::SpecialType::PrunnedBranch) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    int child_merkle_depth = cs_update_from.child_merkle_depth(merkle_depth);
 | 
						|
 | 
						|
    CellSlice cs_original(NoVm(), original);
 | 
						|
    for (unsigned i = 0; i < cs_original.size_refs(); i++) {
 | 
						|
      dfs_both(cs_original.prefetch_ref(i), cs_update_from.prefetch_ref(i), child_merkle_depth);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  Ref<Cell> dfs(Ref<Cell> cell, int merkle_depth) {
 | 
						|
    CellSlice cs(NoVm(), cell);
 | 
						|
    if (cs.special_type() == Cell::SpecialType::PrunnedBranch) {
 | 
						|
      if ((int)cell->get_level() == merkle_depth + 1) {
 | 
						|
        auto it = known_cells_.find(cell->get_hash(merkle_depth));
 | 
						|
        if (it != known_cells_.end()) {
 | 
						|
          return it->second;
 | 
						|
        }
 | 
						|
        return {};
 | 
						|
      }
 | 
						|
      return cell;
 | 
						|
    }
 | 
						|
    Key key{cell->get_hash(), merkle_depth};
 | 
						|
    {
 | 
						|
      auto it = ready_cells_.find(key);
 | 
						|
      if (it != ready_cells_.end()) {
 | 
						|
        return it->second;
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    int child_merkle_depth = cs.child_merkle_depth(merkle_depth);
 | 
						|
 | 
						|
    CellBuilder cb;
 | 
						|
    cb.store_bits(cs.fetch_bits(cs.size()));
 | 
						|
    for (unsigned i = 0; i < cs.size_refs(); i++) {
 | 
						|
      auto ref = dfs(cs.prefetch_ref(i), child_merkle_depth);
 | 
						|
      if (ref.is_null()) {
 | 
						|
        return {};
 | 
						|
      }
 | 
						|
      cb.store_ref(std::move(ref));
 | 
						|
    }
 | 
						|
    auto res = cb.finalize(cs.is_special());
 | 
						|
    ready_cells_.emplace(key, res);
 | 
						|
    return res;
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
class MerkleUpdateValidator {
 | 
						|
 public:
 | 
						|
  td::Status validate(Ref<Cell> update_from, Ref<Cell> update_to, td::uint32 from_level, td::uint32 to_level) {
 | 
						|
    dfs_from(update_from, from_level);
 | 
						|
    return dfs_to(update_to, to_level);
 | 
						|
  }
 | 
						|
 | 
						|
 private:
 | 
						|
  td::HashSet<Cell::Hash> known_cells_;
 | 
						|
  using Key = std::pair<Cell::Hash, int>;
 | 
						|
  td::HashSet<Key> visited_from_;
 | 
						|
  td::HashSet<Key> visited_to_;
 | 
						|
 | 
						|
  void dfs_from(Ref<Cell> cell, int merkle_depth) {
 | 
						|
    if (!visited_from_.emplace(cell->get_hash(), merkle_depth).second) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    CellSlice cs(NoVm(), cell);
 | 
						|
    known_cells_.insert(cell->get_hash(merkle_depth));
 | 
						|
    if (cs.special_type() == Cell::SpecialType::PrunnedBranch) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    int child_merkle_depth = cs.child_merkle_depth(merkle_depth);
 | 
						|
    for (unsigned i = 0; i < cs.size_refs(); i++) {
 | 
						|
      dfs_from(cs.prefetch_ref(i), child_merkle_depth);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  td::Status dfs_to(Ref<Cell> cell, int merkle_depth) {
 | 
						|
    if (!visited_to_.emplace(cell->get_hash(), merkle_depth).second) {
 | 
						|
      return td::Status::OK();
 | 
						|
    }
 | 
						|
    CellSlice cs(NoVm(), cell);
 | 
						|
    if (cs.special_type() == Cell::SpecialType::PrunnedBranch) {
 | 
						|
      if ((int)cell->get_level() == merkle_depth + 1) {
 | 
						|
        if (known_cells_.count(cell->get_hash(merkle_depth)) == 0) {
 | 
						|
          return td::Status::Error(PSLICE()
 | 
						|
                                   << "Unknown prunned cell (validate): " << cell->get_hash(merkle_depth).to_hex());
 | 
						|
        }
 | 
						|
      }
 | 
						|
      return td::Status::OK();
 | 
						|
    }
 | 
						|
    int child_merkle_depth = cs.child_merkle_depth(merkle_depth);
 | 
						|
 | 
						|
    for (unsigned i = 0; i < cs.size_refs(); i++) {
 | 
						|
      TRY_STATUS(dfs_to(cs.prefetch_ref(i), child_merkle_depth));
 | 
						|
    }
 | 
						|
    return td::Status::OK();
 | 
						|
  }
 | 
						|
};
 | 
						|
}  // namespace detail
 | 
						|
 | 
						|
td::Status MerkleUpdate::may_apply(Ref<Cell> from, Ref<Cell> update) {
 | 
						|
  if (update->get_level() != 0 || from->get_level() != 0) {
 | 
						|
    return td::Status::Error("Level of update of from is not zero");
 | 
						|
  }
 | 
						|
  CellSlice cs(NoVm(), std::move(update));
 | 
						|
  if (cs.special_type() != Cell::SpecialType::MerkleUpdate) {
 | 
						|
    return td::Status::Error("Update cell is not a MerkeUpdate");
 | 
						|
  }
 | 
						|
  auto update_from = cs.fetch_ref();
 | 
						|
  if (from->get_hash(0) != update_from->get_hash(0)) {
 | 
						|
    return td::Status::Error("Hash mismatch");
 | 
						|
  }
 | 
						|
  return td::Status::OK();
 | 
						|
}
 | 
						|
 | 
						|
Ref<Cell> MerkleUpdate::apply(Ref<Cell> from, Ref<Cell> update) {
 | 
						|
  if (update->get_level() != 0 || from->get_level() != 0) {
 | 
						|
    return {};
 | 
						|
  }
 | 
						|
  CellSlice cs(NoVm(), std::move(update));
 | 
						|
  if (cs.special_type() != Cell::SpecialType::MerkleUpdate) {
 | 
						|
    return {};
 | 
						|
  }
 | 
						|
  auto update_from = cs.fetch_ref();
 | 
						|
  auto update_to = cs.fetch_ref();
 | 
						|
  return apply_raw(std::move(from), std::move(update_from), std::move(update_to), 0, 0);
 | 
						|
}
 | 
						|
 | 
						|
Ref<Cell> MerkleUpdate::apply_raw(Ref<Cell> from, Ref<Cell> update_from, Ref<Cell> update_to, td::uint32 from_level,
 | 
						|
                                  td::uint32 to_level) {
 | 
						|
  if (from->get_hash(from_level) != update_from->get_hash(from_level)) {
 | 
						|
    LOG(DEBUG) << "invalid Merkle update: expected old value hash = " << update_from->get_hash(from_level).to_hex()
 | 
						|
               << ", applied to value with hash = " << from->get_hash(from_level).to_hex();
 | 
						|
    return {};
 | 
						|
  }
 | 
						|
  return detail::MerkleUpdateApply().apply(from, std::move(update_from), std::move(update_to), from_level, to_level);
 | 
						|
}
 | 
						|
 | 
						|
std::pair<Ref<Cell>, Ref<Cell>> MerkleUpdate::generate_raw(Ref<Cell> from, Ref<Cell> to, CellUsageTree *usage_tree) {
 | 
						|
  // create Merkle update cell->new_cell
 | 
						|
  auto update_to = MerkleProof::generate_raw(to, [tree = usage_tree](const Ref<Cell> &cell) {
 | 
						|
    auto loaded_cell = cell->load_cell().move_as_ok();  // FIXME
 | 
						|
    if (loaded_cell.data_cell->size_refs() == 0) {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
    return !loaded_cell.tree_node.empty() && loaded_cell.tree_node.mark_path(tree);
 | 
						|
  });
 | 
						|
  usage_tree->set_use_mark_for_is_loaded(true);
 | 
						|
  auto update_from = MerkleProof::generate_raw(from, usage_tree);
 | 
						|
 | 
						|
  return {std::move(update_from), std::move(update_to)};
 | 
						|
}
 | 
						|
 | 
						|
td::Status MerkleUpdate::validate_raw(Ref<Cell> update_from, Ref<Cell> update_to, td::uint32 from_level,
 | 
						|
                                      td::uint32 to_level) {
 | 
						|
  return detail::MerkleUpdateValidator().validate(std::move(update_from), std::move(update_to), from_level, to_level);
 | 
						|
}
 | 
						|
 | 
						|
td::Status MerkleUpdate::validate(Ref<Cell> update) {
 | 
						|
  if (update->get_level() != 0) {
 | 
						|
    return td::Status::Error("nonzero level");
 | 
						|
  }
 | 
						|
  CellSlice cs(NoVm(), std::move(update));
 | 
						|
  if (cs.special_type() != Cell::SpecialType::MerkleUpdate) {
 | 
						|
    return td::Status::Error("not a MerkleUpdate cell");
 | 
						|
  }
 | 
						|
  auto update_from = cs.fetch_ref();
 | 
						|
  auto update_to = cs.fetch_ref();
 | 
						|
  return validate_raw(std::move(update_from), std::move(update_to), 0, 0);
 | 
						|
}
 | 
						|
 | 
						|
Ref<Cell> MerkleUpdate::generate(Ref<Cell> from, Ref<Cell> to, CellUsageTree *usage_tree) {
 | 
						|
  auto from_level = from->get_level();
 | 
						|
  auto to_level = to->get_level();
 | 
						|
  if (from_level != 0 || to_level != 0) {
 | 
						|
    return {};
 | 
						|
  }
 | 
						|
  auto res = generate_raw(std::move(from), std::move(to), usage_tree);
 | 
						|
  return CellBuilder::create_merkle_update(res.first, res.second);
 | 
						|
}
 | 
						|
 | 
						|
namespace detail {
 | 
						|
class MerkleCombine {
 | 
						|
 public:
 | 
						|
  MerkleCombine(Ref<Cell> AB, Ref<Cell> CD) : AB_(std::move(AB)), CD_(std::move(CD)) {
 | 
						|
  }
 | 
						|
 | 
						|
  td::Result<Ref<Cell>> run() {
 | 
						|
    TRY_RESULT(AB, unpack_update(std::move(AB_)));
 | 
						|
    TRY_RESULT(CD, unpack_update(std::move(CD_)));
 | 
						|
    std::tie(A_, B_) = AB;
 | 
						|
    std::tie(C_, D_) = CD;
 | 
						|
    if (B_->get_hash(0) != C_->get_hash(0)) {
 | 
						|
      return td::Status::Error("Impossible to combine merkle updates");
 | 
						|
    }
 | 
						|
 | 
						|
    auto log = [](td::Slice name, auto cell) {
 | 
						|
      CellSlice cs(NoVm(), cell);
 | 
						|
      LOG(ERROR) << name << " " << cell->get_level();
 | 
						|
      cs.print_rec(std::cerr);
 | 
						|
    };
 | 
						|
    if (0) {
 | 
						|
      log("A", A_);
 | 
						|
      log("B", B_);
 | 
						|
      log("C", C_);
 | 
						|
      log("D", D_);
 | 
						|
    }
 | 
						|
 | 
						|
    // We have four bags of cells. A, B, C and D.
 | 
						|
    // X = Virtualize(A), A is subtree (merkle proof) of X
 | 
						|
    // Y = Virtualize(B) = Virtualize(C), B and C are  subrees of Y
 | 
						|
    // Z = Virtualize(D), D is subtree of Z
 | 
						|
    //
 | 
						|
    // Prunned cells bounded by merkle proof P are essentially cells which are impossible to load during traversal of Virtualize(P)
 | 
						|
    //
 | 
						|
    // We want to create new_A and new_D
 | 
						|
    // Virtualize(new_A) = X
 | 
						|
    // Virtualize(new_D) = Z
 | 
						|
    // All prunned branches bounded by new_D must be in new_A
 | 
						|
    // i.e. if we have all cells reachable in Virtualize(new_A) we may construct Z from them (and from new_D)
 | 
						|
    //
 | 
						|
    // Main idea is following
 | 
						|
    // 1. Create maximum subtrees of X and Z with all cells in A, B, C and D
 | 
						|
    // Max(V) - such maximum subtree
 | 
						|
    //
 | 
						|
    // 2. Max(A) and Max(D) should be merkle update already. But we want to minimize it
 | 
						|
    // So we cut all branches of Max(D) which are in maxA
 | 
						|
    // When we cut branch q in Max(D) we mark some path to q in Max(A)
 | 
						|
    // Then we cut all branches of Max(A) which are not marked.
 | 
						|
    //
 | 
						|
    // How to create Max(A)?
 | 
						|
    // We just store all cells reachable from A, B, C and D in big cache.
 | 
						|
    // It we reach bounded prunned cell during traversion we may continue traversial with a cell from the cache.
 | 
						|
    //
 | 
						|
    //
 | 
						|
    // 1. load_cells(root) - caches all cell reachable in Virtualize(root);
 | 
						|
    visited_.clear();
 | 
						|
    load_cells(A_, 0);
 | 
						|
    visited_.clear();
 | 
						|
    load_cells(B_, 0);
 | 
						|
    visited_.clear();
 | 
						|
    load_cells(C_, 0);
 | 
						|
    visited_.clear();
 | 
						|
    load_cells(D_, 0);
 | 
						|
 | 
						|
    // 2. mark_A(A) - Traverse Max(A), but uses all cached cells from step 1. Mark all visited cells
 | 
						|
    A_usage_tree_ = std::make_shared<CellUsageTree>();
 | 
						|
    mark_A(A_, 0, A_usage_tree_->root_id());
 | 
						|
 | 
						|
    // 3. create_D(D) - create new_D. Traverse Max(D), and stop at marked cells. Mark path in A to marked cells
 | 
						|
    auto new_D = create_D(D_, 0, 0);
 | 
						|
    if (new_D.is_null()) {
 | 
						|
      return td::Status::Error("Failed to combine updates. One of them is probably an invalid update");
 | 
						|
    }
 | 
						|
 | 
						|
    // 4. create_A(A) - create new_A. Traverse Max(A), and stop at cells not marked at step 3.
 | 
						|
    auto new_A = create_A(A_, 0, 0);
 | 
						|
    if (0) {
 | 
						|
      log("NewD", new_D);
 | 
						|
    }
 | 
						|
 | 
						|
    return CellBuilder::create_merkle_update(new_A, new_D);
 | 
						|
  }
 | 
						|
 | 
						|
 private:
 | 
						|
  Ref<Cell> AB_, CD_;
 | 
						|
  Ref<Cell> A_, B_, C_, D_;
 | 
						|
 | 
						|
  std::shared_ptr<CellUsageTree> A_usage_tree_;
 | 
						|
 | 
						|
  struct Info {
 | 
						|
    Ref<Cell> cell_;
 | 
						|
    Ref<Cell> prunned_cells_[Cell::max_level];  // Cache prunned cells with different levels to reuse them
 | 
						|
    CellUsageTree::NodeId A_node_id{0};
 | 
						|
 | 
						|
    Ref<Cell> get_prunned_cell(int depth) {
 | 
						|
      if (depth < Cell::max_level) {
 | 
						|
        return prunned_cells_[depth];
 | 
						|
      }
 | 
						|
      return {};
 | 
						|
    }
 | 
						|
    Ref<Cell> get_any_cell() const {
 | 
						|
      if (cell_.not_null()) {
 | 
						|
        return cell_;
 | 
						|
      }
 | 
						|
      for (auto &cell : prunned_cells_) {
 | 
						|
        if (cell.not_null()) {
 | 
						|
          return cell;
 | 
						|
        }
 | 
						|
      }
 | 
						|
      UNREACHABLE();
 | 
						|
    }
 | 
						|
  };
 | 
						|
  using Key = std::pair<Cell::Hash, int>;
 | 
						|
  td::HashMap<Cell::Hash, Info> cells_;
 | 
						|
  td::HashMap<Key, Ref<Cell>> create_A_res_;
 | 
						|
  td::HashMap<Key, Ref<Cell>> create_D_res_;
 | 
						|
  td::HashSet<Key> visited_;
 | 
						|
 | 
						|
  void load_cells(Ref<Cell> cell, int merkle_depth) {
 | 
						|
    if (!visited_.emplace(cell->get_hash(), merkle_depth).second) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    auto &info = cells_[cell->get_hash(merkle_depth)];
 | 
						|
    CellSlice cs(NoVm(), cell);
 | 
						|
 | 
						|
    // check if prunned cell is bounded
 | 
						|
    if (cs.special_type() == Cell::SpecialType::PrunnedBranch && static_cast<int>(cell->get_level()) > merkle_depth) {
 | 
						|
      info.prunned_cells_[cell->get_level() - 1] = std::move(cell);
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    info.cell_ = std::move(cell);
 | 
						|
 | 
						|
    auto child_merkle_depth = cs.child_merkle_depth(merkle_depth);
 | 
						|
    for (size_t i = 0, size = cs.size_refs(); i < size; i++) {
 | 
						|
      load_cells(cs.fetch_ref(), child_merkle_depth);
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  void mark_A(Ref<Cell> cell, int merkle_depth, CellUsageTree::NodeId node_id) {
 | 
						|
    CHECK(node_id != 0);
 | 
						|
 | 
						|
    // cell in cache may be virtualized with different level
 | 
						|
    // so we make merkle_depth as small as possible
 | 
						|
    merkle_depth = cell->get_level_mask().apply(merkle_depth).get_level();
 | 
						|
 | 
						|
    auto &info = cells_[cell->get_hash(merkle_depth)];
 | 
						|
    if (info.A_node_id != 0) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    info.A_node_id = node_id;
 | 
						|
    if (info.cell_.is_null()) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
 | 
						|
    CellSlice cs(NoVm(), info.cell_);
 | 
						|
    auto child_merkle_depth = cs.child_merkle_depth(merkle_depth);
 | 
						|
    for (int i = 0, size = cs.size_refs(); i < size; i++) {
 | 
						|
      mark_A(cs.fetch_ref(), child_merkle_depth, A_usage_tree_->create_child(node_id, i));
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  Ref<Cell> create_D(Ref<Cell> cell, int merkle_depth, int d_merkle_depth) {
 | 
						|
    merkle_depth = cell->get_level_mask().apply(merkle_depth).get_level();
 | 
						|
    auto key = Key(cell->get_hash(merkle_depth), d_merkle_depth);
 | 
						|
    auto it = create_D_res_.find(key);
 | 
						|
    if (it != create_D_res_.end()) {
 | 
						|
      return it->second;
 | 
						|
    }
 | 
						|
 | 
						|
    auto res = do_create_D(std::move(cell), merkle_depth, d_merkle_depth);
 | 
						|
    if (res.is_null()) {
 | 
						|
      return {};
 | 
						|
    }
 | 
						|
    create_D_res_.emplace(key, res);
 | 
						|
    return res;
 | 
						|
  }
 | 
						|
 | 
						|
  Ref<Cell> do_create_D(Ref<Cell> cell, int merkle_depth, int d_merkle_depth) {
 | 
						|
    auto &info = cells_[cell->get_hash(merkle_depth)];
 | 
						|
    if (info.A_node_id != 0) {
 | 
						|
      A_usage_tree_->mark_path(info.A_node_id);
 | 
						|
      Ref<Cell> res = info.get_prunned_cell(d_merkle_depth);
 | 
						|
      if (res.is_null()) {
 | 
						|
        res = CellBuilder::create_pruned_branch(info.get_any_cell(), d_merkle_depth + 1, merkle_depth);
 | 
						|
      }
 | 
						|
      return res;
 | 
						|
    }
 | 
						|
 | 
						|
    if (info.cell_.is_null()) {
 | 
						|
      return {};
 | 
						|
    }
 | 
						|
 | 
						|
    CellSlice cs(NoVm(), info.cell_);
 | 
						|
 | 
						|
    if (cs.size_refs() == 0) {
 | 
						|
      return info.cell_;
 | 
						|
    }
 | 
						|
 | 
						|
    auto child_merkle_depth = cs.child_merkle_depth(merkle_depth);
 | 
						|
    auto child_d_merkle_depth = cs.child_merkle_depth(d_merkle_depth);
 | 
						|
 | 
						|
    CellBuilder cb;
 | 
						|
    cb.store_bits(cs.fetch_bits(cs.size()));
 | 
						|
    for (unsigned i = 0; i < cs.size_refs(); i++) {
 | 
						|
      auto ref = create_D(cs.prefetch_ref(i), child_merkle_depth, child_d_merkle_depth);
 | 
						|
      if (ref.is_null()) {
 | 
						|
        return {};
 | 
						|
      }
 | 
						|
      cb.store_ref(std::move(ref));
 | 
						|
    }
 | 
						|
    return cb.finalize(cs.is_special());
 | 
						|
  }
 | 
						|
 | 
						|
  Ref<Cell> create_A(Ref<Cell> cell, int merkle_depth, int a_merkle_depth) {
 | 
						|
    merkle_depth = cell->get_level_mask().apply(merkle_depth).get_level();
 | 
						|
    auto key = Key(cell->get_hash(merkle_depth), a_merkle_depth);
 | 
						|
    auto it = create_A_res_.find(key);
 | 
						|
    if (it != create_A_res_.end()) {
 | 
						|
      return it->second;
 | 
						|
    }
 | 
						|
 | 
						|
    auto res = do_create_A(std::move(cell), merkle_depth, a_merkle_depth);
 | 
						|
    create_A_res_.emplace(key, res);
 | 
						|
    return res;
 | 
						|
  }
 | 
						|
 | 
						|
  Ref<Cell> do_create_A(Ref<Cell> cell, int merkle_depth, int a_merkle_depth) {
 | 
						|
    auto &info = cells_[cell->get_hash(merkle_depth)];
 | 
						|
 | 
						|
    CHECK(info.A_node_id != 0);
 | 
						|
    if (!A_usage_tree_->has_mark(info.A_node_id)) {
 | 
						|
      Ref<Cell> res = info.get_prunned_cell(a_merkle_depth);
 | 
						|
      if (res.is_null()) {
 | 
						|
        res = CellBuilder::create_pruned_branch(info.get_any_cell(), a_merkle_depth + 1, merkle_depth);
 | 
						|
      }
 | 
						|
      return res;
 | 
						|
    }
 | 
						|
 | 
						|
    CHECK(info.cell_.not_null());
 | 
						|
    CellSlice cs(NoVm(), info.cell_);
 | 
						|
 | 
						|
    CHECK(cs.size_refs() != 0);
 | 
						|
    if (cs.size_refs() == 0) {
 | 
						|
      return info.cell_;
 | 
						|
    }
 | 
						|
 | 
						|
    auto child_merkle_depth = cs.child_merkle_depth(merkle_depth);
 | 
						|
    auto child_a_merkle_depth = cs.child_merkle_depth(a_merkle_depth);
 | 
						|
 | 
						|
    CellBuilder cb;
 | 
						|
    cb.store_bits(cs.fetch_bits(cs.size()));
 | 
						|
    for (unsigned i = 0; i < cs.size_refs(); i++) {
 | 
						|
      cb.store_ref(create_A(cs.prefetch_ref(i), child_merkle_depth, child_a_merkle_depth));
 | 
						|
    }
 | 
						|
    return cb.finalize(cs.is_special());
 | 
						|
  }
 | 
						|
 | 
						|
  td::Result<std::pair<Ref<Cell>, Ref<Cell>>> unpack_update(Ref<Cell> update) const {
 | 
						|
    if (update->get_level() != 0) {
 | 
						|
      return td::Status::Error("level is not zero");
 | 
						|
    }
 | 
						|
    CellSlice cs(NoVm(), std::move(update));
 | 
						|
    if (cs.special_type() != Cell::SpecialType::MerkleUpdate) {
 | 
						|
      return td::Status::Error("Not a Merkle Update cell");
 | 
						|
    }
 | 
						|
    auto update_from = cs.fetch_ref();
 | 
						|
    auto update_to = cs.fetch_ref();
 | 
						|
    return std::make_pair(std::move(update_from), std::move(update_to));
 | 
						|
  }
 | 
						|
};
 | 
						|
}  // namespace detail
 | 
						|
 | 
						|
Ref<Cell> MerkleUpdate::combine(Ref<Cell> ab, Ref<Cell> bc) {
 | 
						|
  detail::MerkleCombine combine(ab, bc);
 | 
						|
  auto res = combine.run();
 | 
						|
  if (res.is_error()) {
 | 
						|
    return {};
 | 
						|
  }
 | 
						|
  return res.move_as_ok();
 | 
						|
}
 | 
						|
 | 
						|
}  // namespace vm
 |