mirror of
https://github.com/danog/ton.git
synced 2024-12-13 17:47:28 +01:00
511 lines
17 KiB
C++
511 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 win 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
|