Skip to content

Commit

Permalink
Part 4 of proto array fork choice - check nodes viable (prysmaticlabs…
Browse files Browse the repository at this point in the history
…#4625)

* Docs

* Interface definitions

* Fmt and gazelle

* Rename interface to interfaces

* Define all the type for protoarray

* Gaz

* Add error types

* Add compute delta helper

* Compute delta tests

* Gaz

* Add checking if nodes viable

* Test for viable head

* Test for non leaf node can lead to viable head

* Extra space

* Remove fmt print

* Update beacon-chain/forkchoice/protoarray/nodes.go

Co-Authored-By: Nishant Das <nishdas93@gmail.com>

Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com>
Co-authored-by: Nishant Das <nish1993@hotmail.com>
  • Loading branch information
3 people authored and cryptomental committed Feb 28, 2020
1 parent 47a64ce commit 0867a56
Show file tree
Hide file tree
Showing 4 changed files with 116 additions and 3 deletions.
6 changes: 5 additions & 1 deletion beacon-chain/forkchoice/protoarray/BUILD.bazel
Expand Up @@ -6,6 +6,7 @@ go_library(
"doc.go",
"errors.go",
"helpers.go",
"nodes.go",
"types.go",
],
importpath = "github.com/prysmaticlabs/prysm/beacon-chain/forkchoice/protoarray",
Expand All @@ -18,7 +19,10 @@ go_library(

go_test(
name = "go_default_test",
srcs = ["helpers_test.go"],
srcs = [
"helpers_test.go",
"nodes_test.go",
],
embed = [":go_default_library"],
deps = [
"//shared/hashutil:go_default_library",
Expand Down
4 changes: 2 additions & 2 deletions beacon-chain/forkchoice/protoarray/helpers.go
Expand Up @@ -45,7 +45,7 @@ func computeDeltas(
// that means we have not seen the block before.
nextDeltaIndex, ok := blockIndices[vote.nextRoot]
if ok {
// Extra protection against out of bound, the `nextDeltaIndex` which defines
// Protection against out of bound, the `nextDeltaIndex` which defines
// the block location in the dag can not exceed the total `delta` length.
if int(nextDeltaIndex) >= len(deltas) {
return nil, nil, errInvalidNodeDelta
Expand All @@ -55,7 +55,7 @@ func computeDeltas(

currentDeltaIndex, ok := blockIndices[vote.currentRoot]
if ok {
// Extra protection against out of bound (same as above).
// Protection against out of bound (same as above)
if int(currentDeltaIndex) >= len(deltas) {
return nil, nil, errInvalidNodeDelta
}
Expand Down
48 changes: 48 additions & 0 deletions beacon-chain/forkchoice/protoarray/nodes.go
@@ -0,0 +1,48 @@
package protoarray

import (
"context"

"go.opencensus.io/trace"
)

// leadsToViableHead returns true if the node or the best descendent of the node is viable for head.
// Any node with diff finalized or justified epoch than the ones in fork choice store
// should not be viable to head.
func (s *Store) leadsToViableHead(ctx context.Context, node *Node) (bool, error) {
ctx, span := trace.StartSpan(ctx, "protoArrayForkChoice.leadsToViableHead")
defer span.End()

var bestDescendentViable bool
bestDescendentIndex := node.bestDescendant

// If the best descendant is not part of the leaves.
if bestDescendentIndex != nonExistentNode {
// Protection against out of bound, best descendent index can not be
// exceeds length of nodes list.
if bestDescendentIndex >= uint64(len(s.nodes)) {
return false, errInvalidBestDescendantIndex
}

bestDescendentNode := s.nodes[bestDescendentIndex]
bestDescendentViable = s.viableForHead(ctx, bestDescendentNode)
}

// The node is viable as long as the best descendent is viable.
return bestDescendentViable || s.viableForHead(ctx, node), nil
}

// viableForHead returns true if the node is viable to head.
// Any node with diff finalized or justified epoch than the ones in fork choice store
// should not be viable to head.
func (s *Store) viableForHead(ctx context.Context, node *Node) bool {
ctx, span := trace.StartSpan(ctx, "protoArrayForkChoice.viableForHead")
defer span.End()

// `node` is viable if its justified epoch and finalized epoch are the same as the one in `Store`.
// It's also viable if we are in genesis epoch.
justified := s.justifiedEpoch == node.justifiedEpoch || s.justifiedEpoch == 0
finalized := s.finalizedEpoch == node.finalizedEpoch || s.finalizedEpoch == 0

return justified && finalized
}
61 changes: 61 additions & 0 deletions beacon-chain/forkchoice/protoarray/nodes_test.go
@@ -0,0 +1,61 @@
package protoarray

import (
"context"
"testing"
)

func TestStore_leadsToViableHead(t *testing.T) {
tests := []struct {
n *Node
justifiedEpoch uint64
finalizedEpoch uint64
want bool
}{
{&Node{}, 0, 0, true},
{&Node{}, 1, 0, false},
{&Node{}, 0, 1, false},
{&Node{finalizedEpoch: 1, justifiedEpoch: 1}, 1, 1, true},
{&Node{finalizedEpoch: 1, justifiedEpoch: 1}, 2, 2, false},
{&Node{finalizedEpoch: 3, justifiedEpoch: 4}, 4, 3, true},
}
for _, tc := range tests {
s := &Store{
justifiedEpoch: tc.justifiedEpoch,
finalizedEpoch: tc.finalizedEpoch,
nodes: []*Node{tc.n},
}
got, err := s.leadsToViableHead(context.Background(), tc.n)
if err != nil {
t.Fatal(err)
}
if got != tc.want {
t.Errorf("viableForHead() = %v, want %v", got, tc.want)
}
}
}

func TestStore_viableForHead(t *testing.T) {
tests := []struct {
n *Node
justifiedEpoch uint64
finalizedEpoch uint64
want bool
}{
{&Node{}, 0, 0, true},
{&Node{}, 1, 0, false},
{&Node{}, 0, 1, false},
{&Node{finalizedEpoch: 1, justifiedEpoch: 1}, 1, 1, true},
{&Node{finalizedEpoch: 1, justifiedEpoch: 1}, 2, 2, false},
{&Node{finalizedEpoch: 3, justifiedEpoch: 4}, 4, 3, true},
}
for _, tc := range tests {
s := &Store{
justifiedEpoch: tc.justifiedEpoch,
finalizedEpoch: tc.finalizedEpoch,
}
if got := s.viableForHead(context.Background(), tc.n); got != tc.want {
t.Errorf("viableForHead() = %v, want %v", got, tc.want)
}
}
}

0 comments on commit 0867a56

Please sign in to comment.