From 0867a56518d1def4e783bbe6b7a7c746e58e4962 Mon Sep 17 00:00:00 2001 From: terence tsao Date: Thu, 23 Jan 2020 08:33:39 -0800 Subject: [PATCH] Part 4 of proto array fork choice - check nodes viable (#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 Co-authored-by: prylabs-bulldozer[bot] <58059840+prylabs-bulldozer[bot]@users.noreply.github.com> Co-authored-by: Nishant Das --- .../forkchoice/protoarray/BUILD.bazel | 6 +- beacon-chain/forkchoice/protoarray/helpers.go | 4 +- beacon-chain/forkchoice/protoarray/nodes.go | 48 +++++++++++++++ .../forkchoice/protoarray/nodes_test.go | 61 +++++++++++++++++++ 4 files changed, 116 insertions(+), 3 deletions(-) create mode 100644 beacon-chain/forkchoice/protoarray/nodes.go create mode 100644 beacon-chain/forkchoice/protoarray/nodes_test.go diff --git a/beacon-chain/forkchoice/protoarray/BUILD.bazel b/beacon-chain/forkchoice/protoarray/BUILD.bazel index d49ec40143e7..6b7628df9b78 100644 --- a/beacon-chain/forkchoice/protoarray/BUILD.bazel +++ b/beacon-chain/forkchoice/protoarray/BUILD.bazel @@ -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", @@ -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", diff --git a/beacon-chain/forkchoice/protoarray/helpers.go b/beacon-chain/forkchoice/protoarray/helpers.go index 76700090640a..6d7e9b24e7b5 100644 --- a/beacon-chain/forkchoice/protoarray/helpers.go +++ b/beacon-chain/forkchoice/protoarray/helpers.go @@ -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 @@ -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 } diff --git a/beacon-chain/forkchoice/protoarray/nodes.go b/beacon-chain/forkchoice/protoarray/nodes.go new file mode 100644 index 000000000000..2e79c80cef78 --- /dev/null +++ b/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 +} diff --git a/beacon-chain/forkchoice/protoarray/nodes_test.go b/beacon-chain/forkchoice/protoarray/nodes_test.go new file mode 100644 index 000000000000..a690595a4b9a --- /dev/null +++ b/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) + } + } +}