mirror of
https://gitlab.com/pulsechaincom/prysm-pulse.git
synced 2025-01-11 20:20:05 +00:00
362 lines
10 KiB
Go
362 lines
10 KiB
Go
package protoarray
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/prysmaticlabs/prysm/config/params"
|
|
types "github.com/prysmaticlabs/prysm/consensus-types/primitives"
|
|
"github.com/prysmaticlabs/prysm/encoding/bytesutil"
|
|
"github.com/prysmaticlabs/prysm/testing/require"
|
|
)
|
|
|
|
func slicesEqual(a, b [][32]byte) bool {
|
|
if len(a) != len(b) {
|
|
return false
|
|
}
|
|
|
|
mapA := make(map[[32]byte]bool, len(a))
|
|
for _, root := range a {
|
|
mapA[root] = true
|
|
}
|
|
for _, root := range b {
|
|
_, ok := mapA[root]
|
|
if !ok {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
func TestOptimistic_Outside_ForkChoice(t *testing.T) {
|
|
root0 := bytesutil.ToBytes32([]byte("hello0"))
|
|
|
|
nodeA := &Node{
|
|
slot: types.Slot(100),
|
|
root: bytesutil.ToBytes32([]byte("helloA")),
|
|
bestChild: 1,
|
|
status: valid,
|
|
}
|
|
nodes := []*Node{
|
|
nodeA,
|
|
}
|
|
ni := map[[32]byte]uint64{
|
|
nodeA.root: 0,
|
|
}
|
|
|
|
s := &Store{
|
|
nodes: nodes,
|
|
nodesIndices: ni,
|
|
}
|
|
|
|
f := &ForkChoice{
|
|
store: s,
|
|
}
|
|
_, err := f.IsOptimistic(root0)
|
|
require.ErrorIs(t, ErrUnknownNodeRoot, err)
|
|
}
|
|
|
|
// This tests the algorithm to update optimistic Status
|
|
// We start with the following diagram
|
|
//
|
|
// E -- F
|
|
// /
|
|
// C -- D
|
|
// / \
|
|
// A -- B G -- H -- I
|
|
// \ \
|
|
// J -- K -- L
|
|
//
|
|
// The Chain A -- B -- C -- D -- E is VALID.
|
|
//
|
|
func TestSetOptimisticToValid(t *testing.T) {
|
|
ctx := context.Background()
|
|
tests := []struct {
|
|
root [32]byte // the root of the new VALID block
|
|
testRoot [32]byte // root of the node we will test optimistic status
|
|
wantedOptimistic bool // wanted optimistic status for tested node
|
|
wantedErr error // wanted error message
|
|
}{
|
|
{
|
|
[32]byte{'i'},
|
|
[32]byte{'i'},
|
|
false,
|
|
nil,
|
|
},
|
|
{
|
|
[32]byte{'i'},
|
|
[32]byte{'f'},
|
|
true,
|
|
nil,
|
|
},
|
|
{
|
|
[32]byte{'i'},
|
|
[32]byte{'b'},
|
|
false,
|
|
nil,
|
|
},
|
|
{
|
|
[32]byte{'i'},
|
|
[32]byte{'h'},
|
|
false,
|
|
nil,
|
|
},
|
|
{
|
|
[32]byte{'b'},
|
|
[32]byte{'b'},
|
|
false,
|
|
nil,
|
|
},
|
|
{
|
|
[32]byte{'b'},
|
|
[32]byte{'h'},
|
|
true,
|
|
nil,
|
|
},
|
|
{
|
|
[32]byte{'b'},
|
|
[32]byte{'a'},
|
|
false,
|
|
nil,
|
|
},
|
|
{
|
|
[32]byte{'k'},
|
|
[32]byte{'k'},
|
|
false,
|
|
nil,
|
|
},
|
|
{
|
|
[32]byte{'k'},
|
|
[32]byte{'l'},
|
|
true,
|
|
nil,
|
|
},
|
|
{
|
|
[32]byte{'p'},
|
|
[32]byte{},
|
|
false,
|
|
ErrUnknownNodeRoot,
|
|
},
|
|
}
|
|
for _, tc := range tests {
|
|
f := setup(1, 1)
|
|
|
|
require.NoError(t, f.InsertOptimisticBlock(ctx, 100, [32]byte{'a'}, params.BeaconConfig().ZeroHash, [32]byte{'A'}, 1, 1))
|
|
require.NoError(t, f.InsertOptimisticBlock(ctx, 101, [32]byte{'b'}, [32]byte{'a'}, [32]byte{'B'}, 1, 1))
|
|
require.NoError(t, f.InsertOptimisticBlock(ctx, 102, [32]byte{'c'}, [32]byte{'b'}, [32]byte{'C'}, 1, 1))
|
|
require.NoError(t, f.InsertOptimisticBlock(ctx, 102, [32]byte{'j'}, [32]byte{'b'}, [32]byte{'J'}, 1, 1))
|
|
require.NoError(t, f.InsertOptimisticBlock(ctx, 103, [32]byte{'d'}, [32]byte{'c'}, [32]byte{'D'}, 1, 1))
|
|
require.NoError(t, f.InsertOptimisticBlock(ctx, 104, [32]byte{'e'}, [32]byte{'d'}, [32]byte{'E'}, 1, 1))
|
|
require.NoError(t, f.InsertOptimisticBlock(ctx, 104, [32]byte{'g'}, [32]byte{'d'}, [32]byte{'G'}, 1, 1))
|
|
require.NoError(t, f.InsertOptimisticBlock(ctx, 105, [32]byte{'f'}, [32]byte{'e'}, [32]byte{'F'}, 1, 1))
|
|
require.NoError(t, f.InsertOptimisticBlock(ctx, 105, [32]byte{'h'}, [32]byte{'g'}, [32]byte{'H'}, 1, 1))
|
|
require.NoError(t, f.InsertOptimisticBlock(ctx, 105, [32]byte{'k'}, [32]byte{'g'}, [32]byte{'K'}, 1, 1))
|
|
require.NoError(t, f.InsertOptimisticBlock(ctx, 106, [32]byte{'i'}, [32]byte{'h'}, [32]byte{'I'}, 1, 1))
|
|
require.NoError(t, f.InsertOptimisticBlock(ctx, 106, [32]byte{'l'}, [32]byte{'k'}, [32]byte{'L'}, 1, 1))
|
|
require.NoError(t, f.SetOptimisticToValid(context.Background(), [32]byte{'e'}))
|
|
optimistic, err := f.IsOptimistic([32]byte{'b'})
|
|
require.NoError(t, err)
|
|
require.Equal(t, false, optimistic)
|
|
|
|
err = f.SetOptimisticToValid(context.Background(), tc.root)
|
|
if tc.wantedErr != nil {
|
|
require.ErrorIs(t, err, tc.wantedErr)
|
|
} else {
|
|
require.NoError(t, err)
|
|
optimistic, err := f.IsOptimistic(tc.testRoot)
|
|
require.NoError(t, err)
|
|
require.Equal(t, tc.wantedOptimistic, optimistic)
|
|
}
|
|
}
|
|
}
|
|
|
|
// We test the algorithm to update a node from SYNCING to INVALID
|
|
// We start with the same diagram as above:
|
|
//
|
|
// E(2) -- F(1)
|
|
// /
|
|
// C(7) -- D(6)
|
|
// / \
|
|
// A(10) -- B(9) G(3) -- H(1) -- I(0)
|
|
// \ \
|
|
// J(1) -- K(1) -- L(0)
|
|
//
|
|
// And the chain A -- B -- C -- D -- E has been fully validated. The numbers in parentheses are
|
|
// the weights of the nodes.
|
|
//
|
|
func TestSetOptimisticToInvalid(t *testing.T) {
|
|
tests := []struct {
|
|
name string // test description
|
|
root [32]byte // the root of the new INVALID block
|
|
parentRoot [32]byte // the root of the parent block
|
|
payload [32]byte // the payload of the last valid hash
|
|
newBestChild uint64
|
|
newBestDescendant uint64
|
|
newParentWeight uint64
|
|
returnedRoots [][32]byte
|
|
}{
|
|
{
|
|
"Remove tip, parent was valid",
|
|
[32]byte{'j'},
|
|
[32]byte{'b'},
|
|
[32]byte{'B'},
|
|
3,
|
|
12,
|
|
8,
|
|
[][32]byte{[32]byte{'j'}},
|
|
},
|
|
{
|
|
"Remove tip, parent was optimistic",
|
|
[32]byte{'i'},
|
|
[32]byte{'h'},
|
|
[32]byte{'H'},
|
|
NonExistentNode,
|
|
NonExistentNode,
|
|
1,
|
|
[][32]byte{[32]byte{'i'}},
|
|
},
|
|
{
|
|
"Remove tip, lvh is inner and valid",
|
|
[32]byte{'i'},
|
|
[32]byte{'h'},
|
|
[32]byte{'D'},
|
|
6,
|
|
8,
|
|
3,
|
|
[][32]byte{[32]byte{'g'}, [32]byte{'h'}, [32]byte{'k'}, [32]byte{'i'}, [32]byte{'l'}},
|
|
},
|
|
{
|
|
"Remove inner, lvh is inner and optimistic",
|
|
[32]byte{'h'},
|
|
[32]byte{'g'},
|
|
[32]byte{'G'},
|
|
10,
|
|
12,
|
|
2,
|
|
[][32]byte{[32]byte{'h'}, [32]byte{'i'}},
|
|
},
|
|
{
|
|
"Remove tip, lvh is inner and optimistic",
|
|
[32]byte{'l'},
|
|
[32]byte{'k'},
|
|
[32]byte{'G'},
|
|
9,
|
|
11,
|
|
2,
|
|
[][32]byte{[32]byte{'k'}, [32]byte{'l'}},
|
|
},
|
|
{
|
|
"Remove tip, lvh is not an ancestor",
|
|
[32]byte{'j'},
|
|
[32]byte{'b'},
|
|
[32]byte{'C'},
|
|
5,
|
|
12,
|
|
7,
|
|
[][32]byte{[32]byte{'j'}},
|
|
},
|
|
{
|
|
"Remove inner, lvh is not an ancestor",
|
|
[32]byte{'g'},
|
|
[32]byte{'d'},
|
|
[32]byte{'J'},
|
|
NonExistentNode,
|
|
NonExistentNode,
|
|
1,
|
|
[][32]byte{[32]byte{'g'}, [32]byte{'h'}, [32]byte{'k'}, [32]byte{'i'}, [32]byte{'l'}},
|
|
},
|
|
{
|
|
"Remove not inserted, parent was invalid",
|
|
[32]byte{'z'},
|
|
[32]byte{'j'},
|
|
[32]byte{'B'},
|
|
3,
|
|
12,
|
|
8,
|
|
[][32]byte{[32]byte{'j'}},
|
|
},
|
|
{
|
|
"Remove not inserted, parent was valid",
|
|
[32]byte{'z'},
|
|
[32]byte{'j'},
|
|
[32]byte{'J'},
|
|
NonExistentNode,
|
|
NonExistentNode,
|
|
1,
|
|
[][32]byte{},
|
|
},
|
|
}
|
|
for _, tc := range tests {
|
|
ctx := context.Background()
|
|
f := setup(1, 1)
|
|
|
|
require.NoError(t, f.InsertOptimisticBlock(ctx, 100, [32]byte{'a'}, params.BeaconConfig().ZeroHash, params.BeaconConfig().ZeroHash, 1, 1))
|
|
require.NoError(t, f.InsertOptimisticBlock(ctx, 101, [32]byte{'b'}, [32]byte{'a'}, [32]byte{'B'}, 1, 1))
|
|
require.NoError(t, f.InsertOptimisticBlock(ctx, 102, [32]byte{'c'}, [32]byte{'b'}, [32]byte{'C'}, 1, 1))
|
|
require.NoError(t, f.InsertOptimisticBlock(ctx, 102, [32]byte{'j'}, [32]byte{'b'}, [32]byte{'J'}, 1, 1))
|
|
require.NoError(t, f.InsertOptimisticBlock(ctx, 103, [32]byte{'d'}, [32]byte{'c'}, [32]byte{'D'}, 1, 1))
|
|
require.NoError(t, f.InsertOptimisticBlock(ctx, 104, [32]byte{'e'}, [32]byte{'d'}, [32]byte{'E'}, 1, 1))
|
|
require.NoError(t, f.InsertOptimisticBlock(ctx, 104, [32]byte{'g'}, [32]byte{'d'}, [32]byte{'G'}, 1, 1))
|
|
require.NoError(t, f.InsertOptimisticBlock(ctx, 105, [32]byte{'f'}, [32]byte{'e'}, [32]byte{'F'}, 1, 1))
|
|
require.NoError(t, f.InsertOptimisticBlock(ctx, 105, [32]byte{'h'}, [32]byte{'g'}, [32]byte{'H'}, 1, 1))
|
|
require.NoError(t, f.InsertOptimisticBlock(ctx, 105, [32]byte{'k'}, [32]byte{'g'}, [32]byte{'K'}, 1, 1))
|
|
require.NoError(t, f.InsertOptimisticBlock(ctx, 106, [32]byte{'i'}, [32]byte{'h'}, [32]byte{'I'}, 1, 1))
|
|
require.NoError(t, f.InsertOptimisticBlock(ctx, 106, [32]byte{'l'}, [32]byte{'k'}, [32]byte{'L'}, 1, 1))
|
|
weights := []uint64{10, 10, 9, 7, 1, 6, 2, 3, 1, 1, 1, 0, 0}
|
|
f.store.nodesLock.Lock()
|
|
for i, node := range f.store.nodes {
|
|
node.weight = weights[i]
|
|
}
|
|
f.store.nodesLock.Unlock()
|
|
require.NoError(t, f.SetOptimisticToValid(ctx, [32]byte{'e'}))
|
|
roots, err := f.SetOptimisticToInvalid(ctx, tc.root, tc.parentRoot, tc.payload)
|
|
require.NoError(t, err)
|
|
f.store.nodesLock.RLock()
|
|
_, ok := f.store.nodesIndices[tc.root]
|
|
require.Equal(t, false, ok)
|
|
lvh := f.store.nodes[f.store.payloadIndices[tc.payload]]
|
|
require.Equal(t, true, slicesEqual(tc.returnedRoots, roots))
|
|
require.Equal(t, tc.newBestChild, lvh.bestChild)
|
|
require.Equal(t, tc.newBestDescendant, lvh.bestDescendant)
|
|
require.Equal(t, tc.newParentWeight, lvh.weight)
|
|
require.Equal(t, syncing, f.store.nodes[8].status /* F */)
|
|
require.Equal(t, valid, f.store.nodes[5].status /* E */)
|
|
f.store.nodesLock.RUnlock()
|
|
}
|
|
}
|
|
|
|
func TestSetOptimisticToInvalid_InvalidRoots(t *testing.T) {
|
|
ctx := context.Background()
|
|
f := setup(1, 1)
|
|
|
|
require.NoError(t, f.InsertOptimisticBlock(ctx, 100, [32]byte{'a'}, params.BeaconConfig().ZeroHash, params.BeaconConfig().ZeroHash, 1, 1))
|
|
require.NoError(t, f.InsertOptimisticBlock(ctx, 101, [32]byte{'b'}, [32]byte{'a'}, [32]byte{'B'}, 1, 1))
|
|
_, err := f.SetOptimisticToInvalid(ctx, [32]byte{'p'}, [32]byte{'p'}, [32]byte{'B'})
|
|
require.ErrorIs(t, ErrUnknownNodeRoot, err)
|
|
_, err = f.SetOptimisticToInvalid(ctx, [32]byte{'a'}, [32]byte{}, [32]byte{'p'})
|
|
require.ErrorIs(t, errInvalidFinalizedNode, err)
|
|
}
|
|
|
|
// This is a regression test (10445)
|
|
func TestSetOptimisticToInvalid_ProposerBoost(t *testing.T) {
|
|
ctx := context.Background()
|
|
f := setup(1, 1)
|
|
|
|
require.NoError(t, f.InsertOptimisticBlock(ctx, 100, [32]byte{'a'}, params.BeaconConfig().ZeroHash, [32]byte{'A'}, 1, 1))
|
|
require.NoError(t, f.InsertOptimisticBlock(ctx, 101, [32]byte{'b'}, [32]byte{'a'}, [32]byte{'B'}, 1, 1))
|
|
require.NoError(t, f.InsertOptimisticBlock(ctx, 101, [32]byte{'c'}, [32]byte{'b'}, [32]byte{'C'}, 1, 1))
|
|
f.store.proposerBoostLock.Lock()
|
|
f.store.proposerBoostRoot = [32]byte{'c'}
|
|
f.store.previousProposerBoostScore = 10
|
|
f.store.previousProposerBoostRoot = [32]byte{'b'}
|
|
f.store.proposerBoostLock.Unlock()
|
|
|
|
_, err := f.SetOptimisticToInvalid(ctx, [32]byte{'c'}, [32]byte{'b'}, [32]byte{'A'})
|
|
require.NoError(t, err)
|
|
f.store.proposerBoostLock.RLock()
|
|
require.Equal(t, uint64(0), f.store.previousProposerBoostScore)
|
|
require.DeepEqual(t, [32]byte{}, f.store.proposerBoostRoot)
|
|
require.DeepEqual(t, params.BeaconConfig().ZeroHash, f.store.previousProposerBoostRoot)
|
|
f.store.proposerBoostLock.RUnlock()
|
|
}
|