Kagome
Polkadot Runtime Engine in C++17
vote_graph_impl.cpp
Go to the documentation of this file.
1 
8 
9 #include <stack>
10 
12 
14 
15  namespace {
18  template <typename Vector, typename Func>
19  inline void filter_if(Vector &&v, Func &&func) {
20  v.erase(std::remove_if(
21  v.begin(), v.end(), std::not_fn(std::forward<Func>(func))),
22  v.end());
23  }
24 
26  template <typename Collection, typename Item>
27  inline bool contains(const Collection &collection, const Item &item) {
28  auto it = collection.find(item);
29  return it != collection.end();
30  }
31 
32  // whether the given hash, number pair is a direct ancestor of this node.
33  inline bool inDirectAncestry(const VoteGraph::Entry &entry,
34  const BlockHash &hash,
35  const BlockNumber &number) {
36  // in direct ancestry?
37  auto opt = entry.getAncestorBlockBy(number);
38  return opt && opt == hash;
39  }
40  } // namespace
41 
43  std::shared_ptr<VoterSet> voter_set,
44  std::shared_ptr<Chain> chain)
45  : base_(base),
46  voter_set_(std::move(voter_set)),
47  chain_(std::move(chain)) {
48  entries_[base.hash].number = base.number;
49  heads_.insert(base.hash);
50  }
51 
52  std::optional<std::vector<primitives::BlockHash>>
54  if (contains(entries_, block.hash)) {
55  return std::nullopt;
56  }
57 
58  std::vector<BlockHash> containing;
59  std::unordered_set<BlockHash> visited;
60 
61  // iterate vote-heads and their ancestry backwards until we find the one
62  // with this target hash in that chain.
63  for (auto head : heads_) {
64  for (auto it = entries_.find(head); it != entries_.end();
65  it = entries_.find(head)) {
66  const auto &active_entry = it->second;
67 
68  // if node has been checked already, break
69  if (auto [_, inserted] = visited.emplace(head); not inserted) {
70  break;
71  }
72 
73  auto ancestor_opt = active_entry.getAncestorBlockBy(block.number);
74  if (ancestor_opt.has_value()) {
75  if (*ancestor_opt == block.hash) {
76  containing.push_back(head);
77  }
78  } else {
79  if (not active_entry.ancestors.empty()) {
80  head = active_entry.ancestors.back();
81  continue;
82  }
83  }
84 
85  break;
86  }
87  }
88 
89  return containing;
90  }
91 
92  outcome::result<void> VoteGraphImpl::insert(VoteType vote_type,
93  const BlockInfo &block,
94  const Id &voter) {
95  auto inw_res = voter_set_->indexAndWeight(voter);
96  if (!inw_res.has_value()) {
98  }
99  const auto [index, weight] = inw_res.value();
100 
101  if (auto containing_opt = findContainingNodes(block);
102  containing_opt.has_value()) {
103  if (containing_opt->empty()) {
104  OUTCOME_TRY(append(block));
105  } else {
106  introduceBranch(*containing_opt, block);
107  }
108  } else {
109  // this entry already exists
110  }
111 
112  // update cumulative vote data.
113  // NOTE: below this point, there always exists a node with the given hash
114  // and number.
115  BlockHash inspecting_hash = block.hash;
116  while (true) {
117  Entry &active_entry = entries_.at(inspecting_hash);
118  active_entry.cumulative_vote.set(vote_type, index, weight);
119  auto parent_it = active_entry.ancestors.rbegin();
120  if (parent_it != active_entry.ancestors.rend()) {
121  inspecting_hash = *parent_it;
122  } else {
123  break;
124  }
125  }
126 
127  return outcome::success();
128  }
129 
130  void VoteGraphImpl::remove(VoteType vote_type, const Id &voter) {
131  auto inw_res = voter_set_->indexAndWeight(voter);
132  if (inw_res.has_value()) {
133  const auto [index, weight] = inw_res.value();
134  for (auto &[_, entry] : entries_) {
135  entry.cumulative_vote.unset(vote_type, index, weight);
136  }
137  }
138  }
139 
140  outcome::result<void> VoteGraphImpl::append(const BlockInfo &block) {
141  if (base_.hash == block.hash) {
142  return outcome::success();
143  }
144  if (base_.number > block.number) {
146  }
147 
148  OUTCOME_TRY(ancestry, chain_->getAncestry(base_.hash, block.hash));
149 
150  BOOST_ASSERT_MSG(not ancestry.empty(),
151  "ancestry always contains at least 1 element - base");
152  BOOST_ASSERT_MSG(
153  ancestry.front() == block.hash,
154  "ancestry always contains provided block as the first element");
155  BOOST_ASSERT_MSG(ancestry.back() == base_.hash,
156  "ancestry always contains base block as the last element");
157 
158  auto entry_it = entries_.end();
159 
160  // Find first (best) ancestor among those presented in the entries,
161  // and take corresponding entry
162  auto ancestry_it = std::find_if(ancestry.begin() + 1,
163  ancestry.end(),
164  [this, &entry_it](auto &ancestor) {
165  return entry_it = entries_.find(ancestor),
166  entry_it != entries_.end();
167  });
168  BOOST_ASSERT_MSG(ancestry_it != ancestry.end(),
169  "at least one entry presents ancestor of appending block");
170 
171  // Found entry is got block as descendant
172  if (entry_it != entries_.end()) {
173  Entry &entry = entry_it->second;
174  entry.descendants.push_back(block.hash);
175  }
176 
177  // Needed ancestries is ancestries from parent to ancestor represented in
178  // found entry
179  std::vector<BlockHash> ancestors(ancestry.begin() + 1, ancestry_it + 1);
180 
181  // Block will become a head instead his oldest ancestor
182  if (!ancestors.empty()) {
183  BlockHash ancestor_hash = ancestors.back();
184  heads_.erase(ancestor_hash);
185  heads_.insert(block.hash);
186  }
187 
188  // New entry is got ancestors and added to entries container
189  Entry newEntry;
190  newEntry.number = block.number;
191  newEntry.ancestors = std::move(ancestors);
192  entries_.insert({block.hash, std::move(newEntry)});
193 
194  return outcome::success();
195  }
196 
198  const std::vector<primitives::BlockHash> &descendants,
199  const BlockInfo &ancestor) {
200  Entry new_entry;
201  new_entry.number = ancestor.number;
202  bool filled = false; // is newEntry.ancestors filled
203  std::optional<BlockHash> prev_ancestor_opt;
204 
205  for (const auto &descendant : descendants) {
206  Entry &entry = entries_.at(descendant);
207 
208  BOOST_ASSERT_MSG(inDirectAncestry(entry, ancestor.hash, ancestor.number),
209  "not in direct ancestry");
210  BOOST_ASSERT_MSG(ancestor.number <= entry.number,
211  "this function only invoked with direct ancestors; qed");
212 
213  size_t offset_size = entry.number - ancestor.number;
214  auto ancestors_copy = entry.ancestors;
215  BOOST_ASSERT(entry.ancestors.begin() + offset_size
216  < entry.ancestors.end());
217 
218  if (not filled) {
219  // `[offset_size..end)` elements
220  new_entry.ancestors = std::vector<BlockHash>{
221  entry.ancestors.begin() + offset_size, entry.ancestors.end()};
222 
223  auto last_it = entry.ancestors.rbegin();
224  if (last_it != entry.ancestors.rend()) {
225  prev_ancestor_opt = *last_it;
226  } else {
227  prev_ancestor_opt = std::nullopt;
228  }
229  filled = true;
230  }
231 
232  // `[0..offset_size)` elements
233  entry.ancestors = std::vector<BlockHash>{
234  entry.ancestors.begin(), entry.ancestors.begin() + offset_size};
235 
236  new_entry.descendants.push_back(descendant);
237 
238  new_entry.cumulative_vote.merge(entry.cumulative_vote, voter_set_);
239  }
240 
241  if (prev_ancestor_opt) {
242  auto it = entries_.find(*prev_ancestor_opt);
243  BOOST_ASSERT_MSG(it != entries_.end(),
244  "Prior ancestor is referenced from a node; qed");
245 
246  auto &prev_ancestor_entry = it->second;
247  std::unordered_set<BlockHash> set{new_entry.descendants.begin(),
248  new_entry.descendants.end()};
249 
250  // filter descendants
251  filter_if(prev_ancestor_entry.descendants,
252  [&set](const BlockHash &hash) { return !contains(set, hash); });
253  prev_ancestor_entry.descendants.push_back(ancestor.hash);
254  }
255 
256  entries_.insert({ancestor.hash, std::move(new_entry)});
257  }
258 
259  std::optional<BlockInfo> VoteGraphImpl::findGhost(
260  VoteType vote_type,
261  const std::optional<BlockInfo> &current_best,
262  const VoteGraph::Condition &condition) const {
263  bool force_constrain = false;
264  BlockHash node_key = base_.hash;
265 
266  if (current_best.has_value()) {
267  auto containing_opt = findContainingNodes(current_best.value());
268  if (containing_opt.has_value()) {
269  auto &containing = containing_opt.value();
270  if (containing.empty()) {
271  return std::nullopt;
272  }
273 
274  const Entry &entry = entries_.at(containing[0]);
275  auto ancestor_it = std::rbegin(entry.ancestors);
276  BOOST_ASSERT_MSG(
277  ancestor_it != std::rend(entry.ancestors),
278  "node containing non-node in history always has ancestor; qed");
279  node_key = *ancestor_it;
280  force_constrain = true;
281  } else {
282  node_key = current_best->hash;
283  force_constrain = false;
284  }
285  }
286 
287  Entry active_node = entries_.at(node_key);
288  if (not condition(active_node.cumulative_vote)) {
289  return std::nullopt;
290  }
291 
293  std::stack<std::reference_wrapper<const Entry>> nodes;
294 
295  nodes.push(active_node);
296  while (not nodes.empty()) {
297  auto &node = nodes.top().get();
298  nodes.pop();
299 
300  for (auto &descendant_hash : node.descendants) {
301  auto &descendant = entries_.at(descendant_hash);
302 
303  if (force_constrain and current_best) {
304  if (not inDirectAncestry(
305  descendant, current_best->hash, current_best->number)) {
306  continue;
307  }
308  }
309  if (not condition(descendant.cumulative_vote)) {
310  continue;
311  }
312 
313  if (descendant.number > active_node.number
314  or (descendant.number == active_node.number
315  and active_node.cumulative_vote.sum(vote_type)
316  < descendant.cumulative_vote.sum(vote_type))) {
317  node_key = descendant_hash;
318  active_node = descendant;
319 
320  nodes.push(descendant);
321  }
322  }
323 
324  force_constrain = false;
325  }
326 
327  std::optional<BlockInfo> info =
328  force_constrain ? current_best : std::nullopt;
329 
330  Subchain subchain =
331  ghostFindMergePoint(vote_type, node_key, active_node, info, condition);
332  auto &hashes = subchain.hashes;
333 
334  if (hashes.empty()) {
335  return std::nullopt;
336  }
337 
338  // return last hash with best number
339  return BlockInfo(subchain.best_number, hashes.back());
340  }
341 
343  VoteType vote_type,
344  const BlockHash &active_node_hash,
345  const VoteGraph::Entry &active_node,
346  const std::optional<BlockInfo> &force_constrain,
347  const VoteGraph::Condition &condition) const {
348  auto descendants = active_node.descendants;
349  filter_if(descendants, [&](const BlockHash &hash) {
350  if (not force_constrain) {
351  return true;
352  }
353  return inDirectAncestry(
354  entries_.at(hash), force_constrain->hash, force_constrain->number);
355  });
356 
357  const auto base_number = active_node.number;
358  auto best_number = active_node.number;
359  std::vector<BlockHash> hashes{active_node_hash};
360 
361  std::unordered_map<BlockHash, VoteWeight> descendant_blocks;
362 
363  size_t offset = 0;
364  while (true) {
365  std::optional<BlockHash> new_best;
366  std::optional<VoteWeight> new_best_vote_weight;
367 
368  ++offset;
369  for (const auto &d_node : descendants) {
370  const Entry &entry = entries_.at(d_node);
371  auto ancestor_opt = entry.getAncestorBlockBy(base_number + offset);
372  if (not ancestor_opt.has_value()) {
373  continue;
374  }
375 
376  BlockHash &d_block = ancestor_opt.value();
377  auto it = descendant_blocks.find(d_block);
378  if (it == descendant_blocks.end()) {
379  // if not found, insert then
380  descendant_blocks[d_block] = entry.cumulative_vote;
381  } else {
382  // if found, update weight
383  descendant_blocks[d_block].merge(entry.cumulative_vote, voter_set_);
384 
385  // check if block fulfills condition
386  if (condition(descendant_blocks[d_block])) {
387  if (not new_best_vote_weight
388  or new_best_vote_weight->sum(vote_type)
389  < descendant_blocks[d_block].sum(vote_type)) {
390  // we found our best block
391  new_best = d_block;
392  new_best_vote_weight = descendant_blocks[d_block];
393  }
394  }
395  }
396  }
397 
398  if (not new_best) {
399  break;
400  }
401 
402  ++best_number;
403  descendant_blocks.clear();
404  filter_if(descendants, [&](const BlockHash &hash) {
405  return inDirectAncestry(entries_.at(hash), *new_best, best_number);
406  });
407 
408  hashes.push_back(*new_best);
409  }
410 
411  return Subchain{hashes, best_number};
412  }
413 
414  void VoteGraphImpl::adjustBase(const std::vector<BlockHash> &ancestry_proof) {
415  if (ancestry_proof.empty()) {
416  return;
417  }
418 
419  // take last hash
420  auto new_hash = *std::rbegin(ancestry_proof);
421 
422  if (ancestry_proof.size() > base_.number) {
423  return;
424  }
425 
426  Entry &old_entry = entries_.at(base_.hash);
427  old_entry.ancestors.insert(old_entry.ancestors.end(),
428  ancestry_proof.begin(),
429  ancestry_proof.end());
430 
431  primitives::BlockNumber new_number = base_.number - ancestry_proof.size();
432 
433  Entry new_entry;
434  new_entry.number = new_number;
435  new_entry.descendants.push_back(base_.hash);
436  new_entry.cumulative_vote = old_entry.cumulative_vote;
437 
438  entries_[new_hash] = std::move(new_entry);
439  base_ = BlockInfo{new_number, new_hash};
440  }
441 
442  std::optional<BlockInfo> VoteGraphImpl::findAncestor(
443  VoteType vote_type,
444  const BlockInfo &block_arg,
445  const VoteGraph::Condition &condition) const {
446  for (auto block = block_arg;;) {
447  auto nodes_opt = findContainingNodes(block);
448 
449  if (not nodes_opt.has_value()) {
450  // The block has a vote-node in the graph.
451  const Entry &node = entries_.at(block.hash);
452 
453  // If the condition is fulfilled, we are done.
454  if (condition(node.cumulative_vote)) {
455  return block;
456  }
457 
458  // Not enough weight, check the parent block.
459  if (node.ancestors.empty()) {
460  return std::nullopt;
461  } else {
462  block.hash = node.ancestors[0];
463  block.number = node.number - 1;
464  }
465  } else {
466  const auto &children = nodes_opt.value();
467 
468  // If there are no vote-nodes below the block in the graph,
469  // the block is not in the graph at all.
470  if (children.empty()) {
471  return std::nullopt;
472  }
473 
474  // The block is "contained" in the graph (i.e. in the ancestry-chain
475  // of at least one vote-node) but does not itself have a vote-node.
476  VoteWeight cumulative_weight;
477  for (auto &child : children) {
478  const Entry &child_node = entries_.at(child);
479 
480  cumulative_weight.merge(child_node.cumulative_vote, voter_set_);
481  }
482 
483  // Check if the accumulated weight on all child vote-nodes is
484  // sufficient.
485  if (condition(cumulative_weight)) {
486  return block;
487  }
488 
489  // Not enough weight, check the parent vote-node.
490  const auto &child = children.back();
491  const Entry &child_node = entries_.at(child);
492  size_t offset = child_node.number - block.number;
493  if (offset < child_node.ancestors.size()) {
494  block.hash = child_node.ancestors[offset];
495  block.number = child_node.number - offset - 1;
496  } else {
497  return std::nullopt;
498  }
499  }
500  }
501  }
502 } // namespace kagome::consensus::grandpa
std::unordered_map< BlockHash, Entry > entries_
void adjustBase(const std::vector< BlockHash > &ancestry_proof) override
Definition: vote_graph.hpp:23
outcome::result< void > append(const BlockInfo &block)
std::unordered_set< BlockHash > heads_
STL namespace.
outcome::result< void > insert(VoteType vote_type, const BlockInfo &block, const Id &voter) override
Insert vote {.
std::optional< BlockInfo > findGhost(VoteType vote_type, const std::optional< BlockInfo > &current_best, const Condition &condition) const override
primitives::BlockInfo BlockInfo
Definition: structs.hpp:29
crypto::Ed25519PublicKey Id
Definition: common.hpp:19
Subchain ghostFindMergePoint(VoteType vote_type, const BlockHash &active_node_hash, const Entry &active_node, const std::optional< BlockInfo > &force_constrain, const Condition &condition) const
uint32_t BlockNumber
Definition: common.hpp:18
std::function< bool(const VoteWeight &)> Condition
Definition: vote_graph.hpp:61
void remove(VoteType vote_type, const Id &voter) override
Remove vote {.
void introduceBranch(const std::vector< primitives::BlockHash > &descendants, const BlockInfo &ancestor)
primitives::BlockNumber BlockNumber
Definition: common.hpp:24
void merge(const VoteWeight &other, const std::shared_ptr< VoterSet > &voter_set)
std::vector< BlockHash > descendants
Definition: vote_graph.hpp:28
primitives::BlockHash BlockHash
Definition: common.hpp:23
BlockNumber number
Definition: vote_graph.hpp:24
std::optional< BlockInfo > findAncestor(VoteType vote_type, const BlockInfo &block, const Condition &condition) const override
VoteGraphImpl(const BlockInfo &base, std::shared_ptr< VoterSet > voter_set, std::shared_ptr< Chain > chain)
std::optional< std::vector< primitives::BlockHash > > findContainingNodes(const BlockInfo &block) const