Skip to content
This repository has been archived by the owner on Nov 15, 2023. It is now read-only.

Refactory of Fork-Tree data structure #11228

Merged
merged 22 commits into from
May 10, 2022
Merged

Conversation

davxy
Copy link
Member

@davxy davxy commented Apr 15, 2022

WARNING

This is a critical structure used by both GRANDPA and BABE consensus algorithms.
As such, a rigorous review is required


A good part of the methods where rewritten in order to:

  • provide a non-recursive implementation of all the methods
  • simplification of the overall implementation
  • rebalance only when strictly required (traversal is not free)
  • removal of some code without altering the structure api

A couple of well circumscribed unsafe operations were introduced.

In particular, to traverse the tree with the intent to mutate some parts of it, in an iterative implementation we need to maintain multiple mutable references to some nodes (in a stack).

This is not allowed in safe Rust.

Here we're resorting to raw pointers in a couple of places with extra attention to only use them for data that has a provable stable address (not subject to relocation/deletion)

@github-actions github-actions bot added the A0-please_review Pull request needs code review. label Apr 15, 2022
@davxy davxy added B0-silent Changes should not be mentioned in any release notes C1-low PR touches the given topic and has a low impact on builders. D5-nicetohaveaudit ⚠️ PR contains trivial changes to logic that should be properly reviewed. and removed A0-please_review Pull request needs code review. labels Apr 15, 2022
@davxy davxy requested review from andresilva, bkchr and a team April 15, 2022 16:08
@koute
Copy link
Contributor

koute commented Apr 19, 2022

Regarding the unsafe parts - I haven't looked at the code very closely, but it might be worthwhile to run the tests through MIRI, and for a core data structure like this one it'd also be nice (if possible) to hook it up to a fuzzying harness and see if it finds anything (if we want to be absolutely sure it's correct).

utils/fork-tree/src/lib.rs Outdated Show resolved Hide resolved
@davxy
Copy link
Member Author

davxy commented Apr 22, 2022

Regarding the unsafe parts - I haven't looked at the code very closely, but it might be worthwhile to run the tests through MIRI, and for a core data structure like this one it'd also be nice (if possible) to hook it up to a fuzzying harness and see if it finds anything (if we want to be absolutely sure it's correct).

Unsafe code has been removed at all. For map I've used @koute gist, for drain_filter I've used a similar method ;-)

@davxy davxy requested review from koute and a team April 22, 2022 23:36
@koute
Copy link
Contributor

koute commented Apr 26, 2022

Can you also add a test (or modify an existing one) which tests that the map returns the roots in the right order?

(self.roots.into_iter().rev().map(|node| (usize::MAX, node)).collect(); -> self.roots.into_iter().map(|node| (usize::MAX, node)).collect(); should make it fail; last time I checked adding/deleting the rev() made no difference in which tests were passing)

@davxy
Copy link
Member Author

davxy commented Apr 26, 2022

Can you also add a test (or modify an existing one) which tests that the map returns the roots in the right order?

(self.roots.into_iter().rev().map(|node| (usize::MAX, node)).collect(); -> self.roots.into_iter().map(|node| (usize::MAX, node)).collect(); should make it fail; last time I checked adding/deleting the rev() made no difference in which tests were passing)

@koute done

@davxy davxy self-assigned this Apr 27, 2022
utils/fork-tree/src/lib.rs Outdated Show resolved Hide resolved
utils/fork-tree/src/lib.rs Show resolved Hide resolved
utils/fork-tree/src/lib.rs Outdated Show resolved Hide resolved
utils/fork-tree/src/lib.rs Show resolved Hide resolved
We should find the deepest ancestor satisfying the predicate regardless
of the predicate evaluation result on the found node ancestors.

Added one test to excercise the bug
Copy link
Contributor

@koute koute left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still missing a comment that the iteration order in unspecified. (:

Besides that LGTM!

Copy link
Contributor

@andresilva andresilva left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall LGTM. Just one thing that might be missing is a custom implementation of Decode/Encode since the generated codec will likely use recursion as well. I guess this can be tested by creating a very deep tree and then trying to decode/encode.

utils/fork-tree/src/lib.rs Show resolved Hide resolved
utils/fork-tree/src/lib.rs Outdated Show resolved Hide resolved
utils/fork-tree/src/lib.rs Outdated Show resolved Hide resolved
utils/fork-tree/src/lib.rs Outdated Show resolved Hide resolved
utils/fork-tree/src/lib.rs Show resolved Hide resolved
utils/fork-tree/src/lib.rs Outdated Show resolved Hide resolved
davxy and others added 3 commits May 10, 2022 14:54
Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com>
Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com>
@davxy
Copy link
Member Author

davxy commented May 10, 2022

Overall LGTM. Just one thing that might be missing is a custom implementation of Decode/Encode since the generated codec will likely use recursion as well. I guess this can be tested by creating a very deep tree and then trying to decode/encode.

@andresilva I will check that and if that is the case I'll open one issue as a reminder to implement it in a separate PR

@davxy davxy merged commit b9df248 into master May 10, 2022
@davxy davxy deleted the davxy-iterative-fork-tree-methods branch May 10, 2022 14:06
coderobe added a commit that referenced this pull request May 25, 2022
coderobe added a commit that referenced this pull request May 25, 2022
godcodehunter pushed a commit to sensoriumxr/substrate that referenced this pull request Jun 22, 2022
* Iterative version of some fork-tree methods

* Prune doesn't require its generic args to be 'clone'

* Fork-tree import and drain-filter iterative implementations

* Fork-tree map iterative implementation

* Optimization of some operations, e.g. rebalance only when required

* Destructuring assignments not supported yet by stable rustc 1.57

* Safe implementation of 'map' and 'drain_filter' methods

* Remove dummy comment

* Trigger CI pipeline

* Test map for multi-root fork-tree and refactory of `find_node_index_where`

* Fix find node index with predicate

* Nits

* Tree traversal algorithm is not specified

* Move unspecified tree traversal warning to 'map'

* Immutable 'drain_filter' predicate

* Apply suggestions from code review

* Remove double mapping

Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com>
DaviRain-Su pushed a commit to octopus-network/substrate that referenced this pull request Aug 23, 2022
* Iterative version of some fork-tree methods

* Prune doesn't require its generic args to be 'clone'

* Fork-tree import and drain-filter iterative implementations

* Fork-tree map iterative implementation

* Optimization of some operations, e.g. rebalance only when required

* Destructuring assignments not supported yet by stable rustc 1.57

* Safe implementation of 'map' and 'drain_filter' methods

* Remove dummy comment

* Trigger CI pipeline

* Test map for multi-root fork-tree and refactory of `find_node_index_where`

* Fix find node index with predicate

* Nits

* Tree traversal algorithm is not specified

* Move unspecified tree traversal warning to 'map'

* Immutable 'drain_filter' predicate

* Apply suggestions from code review

* Remove double mapping

Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com>
ark0f pushed a commit to gear-tech/substrate that referenced this pull request Feb 27, 2023
* Iterative version of some fork-tree methods

* Prune doesn't require its generic args to be 'clone'

* Fork-tree import and drain-filter iterative implementations

* Fork-tree map iterative implementation

* Optimization of some operations, e.g. rebalance only when required

* Destructuring assignments not supported yet by stable rustc 1.57

* Safe implementation of 'map' and 'drain_filter' methods

* Remove dummy comment

* Trigger CI pipeline

* Test map for multi-root fork-tree and refactory of `find_node_index_where`

* Fix find node index with predicate

* Nits

* Tree traversal algorithm is not specified

* Move unspecified tree traversal warning to 'map'

* Immutable 'drain_filter' predicate

* Apply suggestions from code review

* Remove double mapping

Co-authored-by: André Silva <123550+andresilva@users.noreply.github.com>
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
B0-silent Changes should not be mentioned in any release notes C1-low PR touches the given topic and has a low impact on builders. D5-nicetohaveaudit ⚠️ PR contains trivial changes to logic that should be properly reviewed.
Projects
Status: done
Development

Successfully merging this pull request may close these issues.

3 participants