Part 3 of proto array fork choice - compute delta helper (#4617)

* 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

* Fix formatting and comments

* Apply suggestions from code review

Co-authored-by: Ivan Martinez <ivanthegreatdev@gmail.com>
This commit is contained in:
terence tsao
2020-01-22 12:19:52 -08:00
committed by Raul Jordan
parent c041403a50
commit 5cc6de9e67
4 changed files with 426 additions and 1 deletions

View File

@@ -1,11 +1,27 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"errors.go",
"helpers.go",
"types.go",
],
importpath = "github.com/prysmaticlabs/prysm/beacon-chain/forkchoice/protoarray",
visibility = ["//beacon-chain:__subpackages__"],
deps = [
"//shared/params:go_default_library",
"@io_opencensus_go//trace:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["helpers_test.go"],
embed = [":go_default_library"],
deps = [
"//shared/hashutil:go_default_library",
"//shared/params:go_default_library",
],
)

View File

@@ -0,0 +1,12 @@
package protoarray
import "errors"
var errUnknownFinalizedRoot = errors.New("unknown finalized root")
var errUnknownJustifiedRoot = errors.New("unknown justified root")
var errInvalidNodeIndex = errors.New("node index is invalid")
var errInvalidJustifiedIndex = errors.New("justified index is invalid")
var errInvalidBestDescendantIndex = errors.New("best descendant index is invalid")
var errInvalidParentDelta = errors.New("parent delta is invalid")
var errInvalidNodeDelta = errors.New("node delta is invalid")
var errInvalidDeltaLength = errors.New("delta length is invalid")

View File

@@ -0,0 +1,72 @@
package protoarray
import (
"context"
"github.com/prysmaticlabs/prysm/shared/params"
"go.opencensus.io/trace"
)
// This computes validator balance delta from validator votes.
// It returns a list of deltas that represents the difference between old balances and new balances.
func computeDeltas(
ctx context.Context,
blockIndices map[[32]byte]uint64,
votes []Vote,
oldBalances []uint64,
newBalances []uint64,
) ([]int, []Vote, error) {
ctx, span := trace.StartSpan(ctx, "protoArrayForkChoice.computeDeltas")
defer span.End()
deltas := make([]int, len(blockIndices))
for validatorIndex, vote := range votes {
oldBalance := uint64(0)
newBalance := uint64(0)
// Skip if validator has never voted for current root and next root (ie. if the
// votes are zero hash aka genesis block), there's nothing to compute.
if vote.currentRoot == params.BeaconConfig().ZeroHash && vote.nextRoot == params.BeaconConfig().ZeroHash {
continue
}
// If the validator index did not exist in `oldBalance` or `newBalance` list above, the balance is just 0.
if validatorIndex < len(oldBalances) {
oldBalance = oldBalances[validatorIndex]
}
if validatorIndex < len(newBalances) {
newBalance = newBalances[validatorIndex]
}
// Perform delta only if the validator's balance or vote has changed.
if vote.currentRoot != vote.nextRoot || oldBalance != newBalance {
// Ignore the vote if it's not known in `blockIndices`,
// 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
// the block location in the dag can not exceed the total `delta` length.
if int(nextDeltaIndex) >= len(deltas) {
return nil, nil, errInvalidNodeDelta
}
deltas[nextDeltaIndex] += int(newBalance)
}
currentDeltaIndex, ok := blockIndices[vote.currentRoot]
if ok {
// Extra protection against out of bound (same as above).
if int(currentDeltaIndex) >= len(deltas) {
return nil, nil, errInvalidNodeDelta
}
deltas[currentDeltaIndex] -= int(oldBalance)
}
}
// Rotate the validator vote.
vote.currentRoot = vote.nextRoot
votes[validatorIndex] = vote
}
return deltas, votes, nil
}

View File

@@ -0,0 +1,325 @@
package protoarray
import (
"context"
"encoding/binary"
"testing"
"github.com/prysmaticlabs/prysm/shared/hashutil"
"github.com/prysmaticlabs/prysm/shared/params"
)
func TestComputeDelta_ZeroHash(t *testing.T) {
validatorCount := uint64(16)
indices := make(map[[32]byte]uint64)
votes := make([]Vote, 0)
oldBalances := make([]uint64, 0)
newBalances := make([]uint64, 0)
for i := uint64(0); i < validatorCount; i++ {
indices[indexToHash(i)] = i
votes = append(votes, Vote{params.BeaconConfig().ZeroHash, params.BeaconConfig().ZeroHash, 0})
oldBalances = append(oldBalances, 0)
newBalances = append(newBalances, 0)
}
delta, _, err := computeDeltas(context.Background(), indices, votes, oldBalances, newBalances)
if err != nil {
t.Fatal(err)
}
if len(delta) != int(validatorCount) {
t.Error("Incorrect length")
}
for _, d := range delta {
if d != 0 {
t.Error("Delta should be zero")
}
}
for _, vote := range votes {
if vote.currentRoot != vote.nextRoot {
t.Errorf("The vote should have changed")
}
}
}
func TestComputeDelta_AllVoteTheSame(t *testing.T) {
validatorCount := uint64(16)
balance := uint64(32)
indices := make(map[[32]byte]uint64)
votes := make([]Vote, 0)
oldBalances := make([]uint64, 0)
newBalances := make([]uint64, 0)
for i := uint64(0); i < validatorCount; i++ {
indices[indexToHash(i)] = i
votes = append(votes, Vote{params.BeaconConfig().ZeroHash, indexToHash(0), 0})
oldBalances = append(oldBalances, balance)
newBalances = append(newBalances, balance)
}
delta, _, err := computeDeltas(context.Background(), indices, votes, oldBalances, newBalances)
if err != nil {
t.Fatal(err)
}
if len(delta) != int(validatorCount) {
t.Error("Incorrect length")
}
for i, d := range delta {
if i == 0 {
if uint64(d) != balance*validatorCount {
t.Error("Did not get correct balance")
}
} else {
if d != 0 {
t.Error("Delta should be zero")
}
}
}
for _, vote := range votes {
if vote.currentRoot != vote.nextRoot {
t.Errorf("The vote should have changed")
}
}
}
func TestComputeDelta_DifferentVotes(t *testing.T) {
validatorCount := uint64(16)
balance := uint64(32)
indices := make(map[[32]byte]uint64)
votes := make([]Vote, 0)
oldBalances := make([]uint64, 0)
newBalances := make([]uint64, 0)
for i := uint64(0); i < validatorCount; i++ {
indices[indexToHash(i)] = i
votes = append(votes, Vote{params.BeaconConfig().ZeroHash, indexToHash(i), 0})
oldBalances = append(oldBalances, balance)
newBalances = append(newBalances, balance)
}
delta, _, err := computeDeltas(context.Background(), indices, votes, oldBalances, newBalances)
if err != nil {
t.Fatal(err)
}
if len(delta) != int(validatorCount) {
t.Error("Incorrect length")
}
for _, d := range delta {
if uint64(d) != balance {
t.Error("Did not get correct delta")
}
}
for _, vote := range votes {
if vote.currentRoot != vote.nextRoot {
t.Errorf("The vote should have changed")
}
}
}
func TestComputeDelta_MovingVotes(t *testing.T) {
validatorCount := uint64(16)
balance := uint64(32)
indices := make(map[[32]byte]uint64)
votes := make([]Vote, 0)
oldBalances := make([]uint64, 0)
newBalances := make([]uint64, 0)
lastIndex := uint64(len(indices) - 1)
for i := uint64(0); i < validatorCount; i++ {
indices[indexToHash(i)] = i
votes = append(votes, Vote{indexToHash(0), indexToHash(lastIndex), 0})
oldBalances = append(oldBalances, balance)
newBalances = append(newBalances, balance)
}
delta, _, err := computeDeltas(context.Background(), indices, votes, oldBalances, newBalances)
if err != nil {
t.Fatal(err)
}
if len(delta) != int(validatorCount) {
t.Error("Incorrect length")
}
for i, d := range delta {
if i == 0 {
if d != -int(balance*validatorCount) {
t.Error("First root should have negative delta")
}
} else if i == int(lastIndex) {
if d != int(balance*validatorCount) {
t.Error("Last root should have positive delta")
}
} else {
if d != 0 {
t.Error("Delta should be zero")
}
}
}
for _, vote := range votes {
if vote.currentRoot != vote.nextRoot {
t.Errorf("The vote should have changed")
}
}
}
func TestComputeDelta_MoveOutOfTree(t *testing.T) {
balance := uint64(32)
indices := make(map[[32]byte]uint64)
votes := make([]Vote, 0)
oldBalances := []uint64{balance, balance}
newBalances := []uint64{balance, balance}
indices[indexToHash(1)] = 0
votes = append(votes, Vote{indexToHash(1), params.BeaconConfig().ZeroHash, 0})
votes = append(votes, Vote{indexToHash(1), [32]byte{'A'}, 0})
delta, _, err := computeDeltas(context.Background(), indices, votes, oldBalances, newBalances)
if err != nil {
t.Fatal(err)
}
if len(delta) != 1 {
t.Error("Incorrect length")
}
if delta[0] != 0-2*int(balance) {
t.Error("Incorrect delta")
}
for _, vote := range votes {
if vote.currentRoot != vote.nextRoot {
t.Errorf("The vote should have changed")
}
}
}
func TestComputeDelta_ChangingBalances(t *testing.T) {
oldBalance := uint64(32)
newBalance := oldBalance * 2
validatorCount := uint64(16)
indices := make(map[[32]byte]uint64)
votes := make([]Vote, 0)
oldBalances := make([]uint64, 0)
newBalances := make([]uint64, 0)
indices[indexToHash(1)] = 0
for i := uint64(0); i < validatorCount; i++ {
indices[indexToHash(i)] = i
votes = append(votes, Vote{indexToHash(0), indexToHash(1), 0})
oldBalances = append(oldBalances, oldBalance)
newBalances = append(newBalances, newBalance)
}
delta, _, err := computeDeltas(context.Background(), indices, votes, oldBalances, newBalances)
if err != nil {
t.Fatal(err)
}
if len(delta) != 16 {
t.Error("Incorrect length")
}
for i, d := range delta {
if i == 0 {
if d != -int(oldBalance*validatorCount) {
t.Error("First root should have negative delta")
}
} else if i == 1 {
if d != int(newBalance*validatorCount) {
t.Error("Last root should have positive delta")
}
} else {
if d != 0 {
t.Error("Delta should be zero")
}
}
}
for _, vote := range votes {
if vote.currentRoot != vote.nextRoot {
t.Errorf("The vote should have changed")
}
}
}
func TestComputeDelta_ValidatorAppear(t *testing.T) {
balance := uint64(32)
indices := make(map[[32]byte]uint64)
votes := make([]Vote, 0)
oldBalances := []uint64{balance}
newBalances := []uint64{balance, balance}
indices[indexToHash(1)] = 0
indices[indexToHash(2)] = 1
votes = append(votes, Vote{indexToHash(1), indexToHash(2), 0})
votes = append(votes, Vote{indexToHash(1), indexToHash(2), 0})
delta, _, err := computeDeltas(context.Background(), indices, votes, oldBalances, newBalances)
if err != nil {
t.Fatal(err)
}
if len(delta) != 2 {
t.Error("Incorrect length")
}
if delta[0] != 0-int(balance) {
t.Error("Incorrect delta")
}
if delta[1] != 2*int(balance) {
t.Error("Incorrect delta")
}
for _, vote := range votes {
if vote.currentRoot != vote.nextRoot {
t.Errorf("The vote should have changed")
}
}
}
func TestComputeDelta_ValidatorDisappears(t *testing.T) {
balance := uint64(32)
indices := make(map[[32]byte]uint64)
votes := make([]Vote, 0)
oldBalances := []uint64{balance, balance}
newBalances := []uint64{balance}
indices[indexToHash(1)] = 0
indices[indexToHash(2)] = 1
votes = append(votes, Vote{indexToHash(1), indexToHash(2), 0})
votes = append(votes, Vote{indexToHash(1), indexToHash(2), 0})
delta, _, err := computeDeltas(context.Background(), indices, votes, oldBalances, newBalances)
if err != nil {
t.Fatal(err)
}
if len(delta) != 2 {
t.Error("Incorrect length")
}
if delta[0] != 0-2*int(balance) {
t.Error("Incorrect delta")
}
if delta[1] != int(balance) {
t.Error("Incorrect delta")
}
for _, vote := range votes {
if vote.currentRoot != vote.nextRoot {
t.Errorf("The vote should have changed")
}
}
}
func indexToHash(i uint64) [32]byte {
var b [8]byte
binary.LittleEndian.PutUint64(b[:], i)
return hashutil.Hash(b[:])
}