mirror of
https://github.com/OffchainLabs/prysm.git
synced 2026-05-02 03:02:54 -04:00
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:
committed by
Raul Jordan
parent
c041403a50
commit
5cc6de9e67
@@ -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",
|
||||
],
|
||||
)
|
||||
|
||||
12
beacon-chain/forkchoice/protoarray/errors.go
Normal file
12
beacon-chain/forkchoice/protoarray/errors.go
Normal 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")
|
||||
72
beacon-chain/forkchoice/protoarray/helpers.go
Normal file
72
beacon-chain/forkchoice/protoarray/helpers.go
Normal 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
|
||||
}
|
||||
325
beacon-chain/forkchoice/protoarray/helpers_test.go
Normal file
325
beacon-chain/forkchoice/protoarray/helpers_test.go
Normal 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[:])
|
||||
}
|
||||
Reference in New Issue
Block a user