2022-01-29 16:32:01 +00:00
|
|
|
|
package protoarray
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"testing"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"github.com/prysmaticlabs/prysm/config/params"
|
2022-04-29 14:32:11 +00:00
|
|
|
|
types "github.com/prysmaticlabs/prysm/consensus-types/primitives"
|
2022-01-29 16:32:01 +00:00
|
|
|
|
"github.com/prysmaticlabs/prysm/testing/assert"
|
|
|
|
|
"github.com/prysmaticlabs/prysm/testing/require"
|
|
|
|
|
)
|
|
|
|
|
|
2022-06-22 13:56:07 +00:00
|
|
|
|
// Helper function to simulate the block being on time or delayed for proposer
|
|
|
|
|
// boost. It alters the genesisTime tracked by the store.
|
|
|
|
|
func driftGenesisTime(f *ForkChoice, slot types.Slot, delay uint64) {
|
|
|
|
|
f.SetGenesisTime(uint64(time.Now().Unix()) - uint64(slot)*params.BeaconConfig().SecondsPerSlot - delay)
|
|
|
|
|
}
|
|
|
|
|
|
2022-01-29 16:32:01 +00:00
|
|
|
|
// Simple, ex-ante attack mitigation using proposer boost.
|
|
|
|
|
// In a nutshell, an adversarial block proposer in slot n+1 keeps its proposal hidden.
|
|
|
|
|
// The honest block proposer in slot n+2 will then propose an honest block. The
|
|
|
|
|
// adversary can now use its committee members’ votes from both slots n+1 and n+2.
|
|
|
|
|
// and release their withheld block of slot n+2 in an attempt to win fork choice.
|
|
|
|
|
// If the honest proposal is boosted at slot n+2, it will win against this attacker.
|
|
|
|
|
func TestForkChoice_BoostProposerRoot_PreventsExAnteAttack(t *testing.T) {
|
|
|
|
|
ctx := context.Background()
|
2022-03-28 21:34:41 +00:00
|
|
|
|
jEpoch, fEpoch := types.Epoch(0), types.Epoch(0)
|
2022-01-29 16:32:01 +00:00
|
|
|
|
zeroHash := params.BeaconConfig().ZeroHash
|
|
|
|
|
balances := make([]uint64, 64) // 64 active validators.
|
|
|
|
|
for i := 0; i < len(balances); i++ {
|
|
|
|
|
balances[i] = 10
|
|
|
|
|
}
|
|
|
|
|
t.Run("back-propagates boost score to ancestors after proposer boosting", func(t *testing.T) {
|
|
|
|
|
f := setup(jEpoch, fEpoch)
|
|
|
|
|
|
|
|
|
|
// The head should always start at the finalized block.
|
2022-06-09 22:28:30 +00:00
|
|
|
|
headRoot, err := f.Head(ctx, balances)
|
2022-01-29 16:32:01 +00:00
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.Equal(t, zeroHash, headRoot, "Incorrect head with genesis")
|
|
|
|
|
|
|
|
|
|
// Insert block at slot 1 into the tree and verify head is at that block:
|
|
|
|
|
// 0
|
|
|
|
|
// |
|
|
|
|
|
// 1 <- HEAD
|
|
|
|
|
slot := types.Slot(1)
|
2022-06-22 13:56:07 +00:00
|
|
|
|
driftGenesisTime(f, slot, 0)
|
2022-01-29 16:32:01 +00:00
|
|
|
|
newRoot := indexToHash(1)
|
2022-06-05 17:48:21 +00:00
|
|
|
|
state, blkRoot, err := prepareForkchoiceState(
|
|
|
|
|
ctx,
|
|
|
|
|
slot,
|
|
|
|
|
newRoot,
|
|
|
|
|
headRoot,
|
|
|
|
|
zeroHash,
|
|
|
|
|
jEpoch,
|
|
|
|
|
fEpoch,
|
2022-01-29 16:32:01 +00:00
|
|
|
|
)
|
2022-06-05 17:48:21 +00:00
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
require.NoError(t, f.InsertNode(ctx, state, blkRoot))
|
2022-01-29 16:32:01 +00:00
|
|
|
|
f.ProcessAttestation(ctx, []uint64{0}, newRoot, fEpoch)
|
2022-06-09 22:28:30 +00:00
|
|
|
|
headRoot, err = f.Head(ctx, balances)
|
2022-01-29 16:32:01 +00:00
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.Equal(t, newRoot, headRoot, "Incorrect head for justified epoch at slot 1")
|
|
|
|
|
|
|
|
|
|
// Insert block at slot 2 into the tree and verify head is at that block:
|
|
|
|
|
// 0
|
|
|
|
|
// |
|
|
|
|
|
// 1
|
|
|
|
|
// |
|
|
|
|
|
// 2 <- HEAD
|
|
|
|
|
slot = types.Slot(2)
|
2022-06-22 13:56:07 +00:00
|
|
|
|
driftGenesisTime(f, slot, 0)
|
2022-01-29 16:32:01 +00:00
|
|
|
|
newRoot = indexToHash(2)
|
2022-06-05 17:48:21 +00:00
|
|
|
|
state, blkRoot, err = prepareForkchoiceState(
|
|
|
|
|
ctx,
|
|
|
|
|
slot,
|
|
|
|
|
newRoot,
|
|
|
|
|
headRoot,
|
|
|
|
|
zeroHash,
|
|
|
|
|
jEpoch,
|
|
|
|
|
fEpoch,
|
2022-01-29 16:32:01 +00:00
|
|
|
|
)
|
2022-06-05 17:48:21 +00:00
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
require.NoError(t, f.InsertNode(ctx, state, blkRoot))
|
2022-01-29 16:32:01 +00:00
|
|
|
|
f.ProcessAttestation(ctx, []uint64{1}, newRoot, fEpoch)
|
2022-06-09 22:28:30 +00:00
|
|
|
|
headRoot, err = f.Head(ctx, balances)
|
2022-01-29 16:32:01 +00:00
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.Equal(t, newRoot, headRoot, "Incorrect head for justified epoch at slot 2")
|
|
|
|
|
|
|
|
|
|
// Insert block at slot 3 into the tree and verify head is at that block:
|
|
|
|
|
// 0
|
|
|
|
|
// |
|
|
|
|
|
// 1
|
|
|
|
|
// |
|
|
|
|
|
// 2
|
|
|
|
|
// |
|
|
|
|
|
// 3 <- HEAD
|
2022-03-28 21:34:41 +00:00
|
|
|
|
slot = types.Slot(3)
|
2022-06-22 13:56:07 +00:00
|
|
|
|
driftGenesisTime(f, slot, 0)
|
2022-03-28 21:34:41 +00:00
|
|
|
|
newRoot = indexToHash(3)
|
2022-06-05 17:48:21 +00:00
|
|
|
|
state, blkRoot, err = prepareForkchoiceState(
|
|
|
|
|
ctx,
|
|
|
|
|
slot,
|
|
|
|
|
newRoot,
|
|
|
|
|
headRoot,
|
|
|
|
|
zeroHash,
|
|
|
|
|
jEpoch,
|
|
|
|
|
fEpoch,
|
2022-01-29 16:32:01 +00:00
|
|
|
|
)
|
2022-06-05 17:48:21 +00:00
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
require.NoError(t, f.InsertNode(ctx, state, blkRoot))
|
2022-01-29 16:32:01 +00:00
|
|
|
|
f.ProcessAttestation(ctx, []uint64{2}, newRoot, fEpoch)
|
2022-06-09 22:28:30 +00:00
|
|
|
|
headRoot, err = f.Head(ctx, balances)
|
2022-01-29 16:32:01 +00:00
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.Equal(t, newRoot, headRoot, "Incorrect head for justified epoch at slot 3")
|
|
|
|
|
|
2022-05-17 19:26:56 +00:00
|
|
|
|
// Insert a second block at slot 4 into the tree and boost its score.
|
2022-01-29 16:32:01 +00:00
|
|
|
|
// 0
|
|
|
|
|
// |
|
|
|
|
|
// 1
|
|
|
|
|
// |
|
|
|
|
|
// 2
|
|
|
|
|
// / \
|
2022-05-17 19:26:56 +00:00
|
|
|
|
// 3 |
|
|
|
|
|
// 4 <- HEAD
|
|
|
|
|
slot = types.Slot(4)
|
2022-06-22 13:56:07 +00:00
|
|
|
|
driftGenesisTime(f, slot, 0)
|
2022-01-29 16:32:01 +00:00
|
|
|
|
newRoot = indexToHash(4)
|
2022-06-05 17:48:21 +00:00
|
|
|
|
state, blkRoot, err = prepareForkchoiceState(
|
|
|
|
|
ctx,
|
|
|
|
|
slot,
|
|
|
|
|
newRoot,
|
|
|
|
|
indexToHash(2),
|
|
|
|
|
zeroHash,
|
|
|
|
|
jEpoch,
|
|
|
|
|
fEpoch,
|
2022-01-29 16:32:01 +00:00
|
|
|
|
)
|
2022-06-05 17:48:21 +00:00
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
require.NoError(t, f.InsertNode(ctx, state, blkRoot))
|
2022-01-29 16:32:01 +00:00
|
|
|
|
f.ProcessAttestation(ctx, []uint64{3}, newRoot, fEpoch)
|
2022-06-09 22:28:30 +00:00
|
|
|
|
headRoot, err = f.Head(ctx, balances)
|
2022-01-29 16:32:01 +00:00
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.Equal(t, newRoot, headRoot, "Incorrect head for justified epoch at slot 3")
|
|
|
|
|
|
|
|
|
|
// Check the ancestor scores from the store.
|
2022-03-28 21:34:41 +00:00
|
|
|
|
require.Equal(t, 5, len(f.store.nodes))
|
2022-01-29 16:32:01 +00:00
|
|
|
|
|
|
|
|
|
// Expect nodes to have a boosted, back-propagated score.
|
|
|
|
|
// Ancestors have the added weights of their children. Genesis is a special exception at 0 weight,
|
|
|
|
|
require.Equal(t, f.store.nodes[0].weight, uint64(0))
|
|
|
|
|
|
2022-06-22 13:56:07 +00:00
|
|
|
|
// Proposer boost score with this tests parameters is 8
|
|
|
|
|
// Each of the nodes received one attestation accounting for 10.
|
|
|
|
|
// Node D is the only one with a proposer boost still applied:
|
2022-01-29 16:32:01 +00:00
|
|
|
|
//
|
2022-05-23 23:08:24 +00:00
|
|
|
|
// (A: 48) -> (B: 38) -> (C: 10)
|
2022-06-22 13:56:07 +00:00
|
|
|
|
// \--------------->(D: 18)
|
2022-01-29 16:32:01 +00:00
|
|
|
|
//
|
2022-06-22 13:56:07 +00:00
|
|
|
|
require.Equal(t, f.store.nodes[4].weight, uint64(18))
|
2022-05-23 23:08:24 +00:00
|
|
|
|
require.Equal(t, f.store.nodes[1].weight, uint64(48))
|
|
|
|
|
require.Equal(t, f.store.nodes[2].weight, uint64(38))
|
2022-05-17 19:26:56 +00:00
|
|
|
|
require.Equal(t, f.store.nodes[3].weight, uint64(10))
|
|
|
|
|
|
|
|
|
|
// Regression: process attestations for C, check that it
|
|
|
|
|
// becomes head, we need two attestations to have C.weight = 30 > 24 = D.weight
|
|
|
|
|
f.ProcessAttestation(ctx, []uint64{4, 5}, indexToHash(3), fEpoch)
|
2022-06-09 22:28:30 +00:00
|
|
|
|
headRoot, err = f.Head(ctx, balances)
|
2022-05-17 19:26:56 +00:00
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.Equal(t, indexToHash(3), headRoot, "Incorrect head for justified epoch at slot 4")
|
2022-01-29 16:32:01 +00:00
|
|
|
|
})
|
|
|
|
|
t.Run("vanilla ex ante attack", func(t *testing.T) {
|
|
|
|
|
f := setup(jEpoch, fEpoch)
|
|
|
|
|
|
|
|
|
|
// The head should always start at the finalized block.
|
2022-06-09 22:28:30 +00:00
|
|
|
|
r, err := f.Head(ctx, balances)
|
2022-01-29 16:32:01 +00:00
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.Equal(t, zeroHash, r, "Incorrect head with genesis")
|
|
|
|
|
|
|
|
|
|
// Proposer from slot 1 does not reveal their block, B, at slot 1.
|
|
|
|
|
// Proposer at slot 2 does reveal their block, C, and it becomes the head.
|
|
|
|
|
// C builds on A, as proposer at slot 1 did not reveal B.
|
|
|
|
|
// A
|
|
|
|
|
// / \
|
|
|
|
|
// (B?) \
|
|
|
|
|
// \
|
|
|
|
|
// C <- Slot 2 HEAD
|
|
|
|
|
honestBlockSlot := types.Slot(2)
|
2022-06-22 13:56:07 +00:00
|
|
|
|
driftGenesisTime(f, honestBlockSlot, 0)
|
2022-01-29 16:32:01 +00:00
|
|
|
|
honestBlock := indexToHash(2)
|
2022-06-05 17:48:21 +00:00
|
|
|
|
state, blkRoot, err := prepareForkchoiceState(
|
|
|
|
|
ctx,
|
|
|
|
|
honestBlockSlot,
|
|
|
|
|
honestBlock,
|
|
|
|
|
zeroHash,
|
|
|
|
|
zeroHash,
|
|
|
|
|
jEpoch,
|
|
|
|
|
fEpoch,
|
2022-01-29 16:32:01 +00:00
|
|
|
|
)
|
2022-06-05 17:48:21 +00:00
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
require.NoError(t, f.InsertNode(ctx, state, blkRoot))
|
2022-06-09 22:28:30 +00:00
|
|
|
|
r, err = f.Head(ctx, balances)
|
2022-01-29 16:32:01 +00:00
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.Equal(t, honestBlock, r, "Incorrect head for justified epoch at slot 2")
|
|
|
|
|
|
|
|
|
|
maliciouslyWithheldBlockSlot := types.Slot(1)
|
|
|
|
|
maliciouslyWithheldBlock := indexToHash(1)
|
2022-06-05 17:48:21 +00:00
|
|
|
|
state, blkRoot, err = prepareForkchoiceState(
|
|
|
|
|
ctx,
|
|
|
|
|
maliciouslyWithheldBlockSlot,
|
|
|
|
|
maliciouslyWithheldBlock,
|
|
|
|
|
zeroHash,
|
|
|
|
|
zeroHash,
|
|
|
|
|
jEpoch,
|
|
|
|
|
fEpoch,
|
2022-01-29 16:32:01 +00:00
|
|
|
|
)
|
2022-06-05 17:48:21 +00:00
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
require.NoError(t, f.InsertNode(ctx, state, blkRoot))
|
2022-01-29 16:32:01 +00:00
|
|
|
|
|
|
|
|
|
// Ensure the head is C, the honest block.
|
2022-06-09 22:28:30 +00:00
|
|
|
|
r, err = f.Head(ctx, balances)
|
2022-01-29 16:32:01 +00:00
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.Equal(t, honestBlock, r, "Incorrect head for justified epoch at slot 2")
|
|
|
|
|
|
|
|
|
|
// The maliciously withheld block has one vote.
|
|
|
|
|
votes := []uint64{1}
|
|
|
|
|
f.ProcessAttestation(ctx, votes, maliciouslyWithheldBlock, fEpoch)
|
2022-05-23 23:08:24 +00:00
|
|
|
|
// The honest block has one vote.
|
|
|
|
|
votes = []uint64{2}
|
|
|
|
|
f.ProcessAttestation(ctx, votes, honestBlock, fEpoch)
|
2022-01-29 16:32:01 +00:00
|
|
|
|
|
|
|
|
|
// Ensure the head is STILL C, the honest block, as the honest block had proposer boost.
|
2022-06-09 22:28:30 +00:00
|
|
|
|
r, err = f.Head(ctx, balances)
|
2022-01-29 16:32:01 +00:00
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.Equal(t, honestBlock, r, "Incorrect head for justified epoch at slot 2")
|
|
|
|
|
})
|
|
|
|
|
t.Run("adversarial attestations > proposer boosting", func(t *testing.T) {
|
|
|
|
|
f := setup(jEpoch, fEpoch)
|
|
|
|
|
|
|
|
|
|
// The head should always start at the finalized block.
|
2022-06-09 22:28:30 +00:00
|
|
|
|
r, err := f.Head(ctx, balances)
|
2022-01-29 16:32:01 +00:00
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.Equal(t, zeroHash, r, "Incorrect head with genesis")
|
|
|
|
|
|
|
|
|
|
// Proposer from slot 1 does not reveal their block, B, at slot 1.
|
|
|
|
|
// Proposer at slot 2 does reveal their block, C, and it becomes the head.
|
|
|
|
|
// C builds on A, as proposer at slot 1 did not reveal B.
|
|
|
|
|
// A
|
|
|
|
|
// / \
|
2022-06-22 13:56:07 +00:00
|
|
|
|
// (B?) \
|
2022-01-29 16:32:01 +00:00
|
|
|
|
// \
|
|
|
|
|
// C <- Slot 2 HEAD
|
|
|
|
|
honestBlockSlot := types.Slot(2)
|
2022-06-22 13:56:07 +00:00
|
|
|
|
driftGenesisTime(f, honestBlockSlot, 0)
|
2022-01-29 16:32:01 +00:00
|
|
|
|
honestBlock := indexToHash(2)
|
2022-06-05 17:48:21 +00:00
|
|
|
|
state, blkRoot, err := prepareForkchoiceState(
|
|
|
|
|
ctx,
|
|
|
|
|
honestBlockSlot,
|
|
|
|
|
honestBlock,
|
|
|
|
|
zeroHash,
|
|
|
|
|
zeroHash,
|
|
|
|
|
jEpoch,
|
|
|
|
|
fEpoch,
|
2022-01-29 16:32:01 +00:00
|
|
|
|
)
|
2022-06-05 17:48:21 +00:00
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
require.NoError(t, f.InsertNode(ctx, state, blkRoot))
|
2022-01-29 16:32:01 +00:00
|
|
|
|
|
|
|
|
|
// Ensure C is the head.
|
2022-06-09 22:28:30 +00:00
|
|
|
|
r, err = f.Head(ctx, balances)
|
2022-01-29 16:32:01 +00:00
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.Equal(t, honestBlock, r, "Incorrect head for justified epoch at slot 2")
|
|
|
|
|
|
|
|
|
|
maliciouslyWithheldBlockSlot := types.Slot(1)
|
|
|
|
|
maliciouslyWithheldBlock := indexToHash(1)
|
2022-06-05 17:48:21 +00:00
|
|
|
|
state, blkRoot, err = prepareForkchoiceState(
|
|
|
|
|
ctx,
|
|
|
|
|
maliciouslyWithheldBlockSlot,
|
|
|
|
|
maliciouslyWithheldBlock,
|
|
|
|
|
zeroHash,
|
|
|
|
|
zeroHash,
|
|
|
|
|
jEpoch,
|
|
|
|
|
fEpoch,
|
2022-01-29 16:32:01 +00:00
|
|
|
|
)
|
2022-06-05 17:48:21 +00:00
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
require.NoError(t, f.InsertNode(ctx, state, blkRoot))
|
2022-01-29 16:32:01 +00:00
|
|
|
|
|
|
|
|
|
// Ensure C is still the head after the malicious proposer reveals their block.
|
2022-06-09 22:28:30 +00:00
|
|
|
|
r, err = f.Head(ctx, balances)
|
2022-01-29 16:32:01 +00:00
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.Equal(t, honestBlock, r, "Incorrect head for justified epoch at slot 2")
|
|
|
|
|
|
|
|
|
|
// An attestation is received for B that has more voting power than C with the proposer boost,
|
|
|
|
|
// allowing B to then become the head if their attestation has enough adversarial votes.
|
|
|
|
|
votes := []uint64{1, 2}
|
|
|
|
|
f.ProcessAttestation(ctx, votes, maliciouslyWithheldBlock, fEpoch)
|
|
|
|
|
|
|
|
|
|
// Expect the head to have switched to B.
|
2022-06-09 22:28:30 +00:00
|
|
|
|
r, err = f.Head(ctx, balances)
|
2022-01-29 16:32:01 +00:00
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.Equal(t, maliciouslyWithheldBlock, r, "Expected B to become the head")
|
|
|
|
|
})
|
|
|
|
|
t.Run("boosting necessary to sandwich attack", func(t *testing.T) {
|
|
|
|
|
// Boosting necessary to sandwich attack.
|
|
|
|
|
// Objects:
|
|
|
|
|
// Block A - slot N
|
|
|
|
|
// Block B (parent A) - slot N+1
|
|
|
|
|
// Block C (parent A) - slot N+2
|
|
|
|
|
// Block D (parent B) - slot N+3
|
|
|
|
|
// Attestation_1 (Block C); size 1 - slot N+2 (honest)
|
|
|
|
|
// Steps:
|
|
|
|
|
// Block A received at N — A is head
|
|
|
|
|
// Block C received at N+2 — C is head
|
|
|
|
|
// Block B received at N+2 — C is head
|
|
|
|
|
// Attestation_1 received at N+3 — C is head
|
|
|
|
|
// Block D received at N+3 — D is head
|
|
|
|
|
f := setup(jEpoch, fEpoch)
|
|
|
|
|
a := zeroHash
|
|
|
|
|
|
|
|
|
|
// The head should always start at the finalized block.
|
2022-06-09 22:28:30 +00:00
|
|
|
|
r, err := f.Head(ctx, balances)
|
2022-01-29 16:32:01 +00:00
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.Equal(t, zeroHash, r, "Incorrect head with genesis")
|
|
|
|
|
|
|
|
|
|
cSlot := types.Slot(2)
|
2022-06-22 13:56:07 +00:00
|
|
|
|
driftGenesisTime(f, cSlot, 0)
|
2022-01-29 16:32:01 +00:00
|
|
|
|
c := indexToHash(2)
|
2022-06-05 17:48:21 +00:00
|
|
|
|
state, blkRoot, err := prepareForkchoiceState(
|
|
|
|
|
ctx,
|
|
|
|
|
cSlot,
|
|
|
|
|
c,
|
|
|
|
|
a, // parent
|
|
|
|
|
zeroHash,
|
|
|
|
|
jEpoch,
|
|
|
|
|
fEpoch,
|
2022-01-29 16:32:01 +00:00
|
|
|
|
)
|
2022-06-05 17:48:21 +00:00
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
require.NoError(t, f.InsertNode(ctx, state, blkRoot))
|
2022-01-29 16:32:01 +00:00
|
|
|
|
|
|
|
|
|
// Ensure C is the head.
|
2022-06-09 22:28:30 +00:00
|
|
|
|
r, err = f.Head(ctx, balances)
|
2022-01-29 16:32:01 +00:00
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.Equal(t, c, r, "Incorrect head for justified epoch at slot 2")
|
|
|
|
|
|
|
|
|
|
bSlot := types.Slot(1)
|
|
|
|
|
b := indexToHash(1)
|
2022-06-05 17:48:21 +00:00
|
|
|
|
state, blkRoot, err = prepareForkchoiceState(
|
|
|
|
|
ctx,
|
|
|
|
|
bSlot,
|
|
|
|
|
b,
|
|
|
|
|
a, // parent
|
|
|
|
|
zeroHash,
|
|
|
|
|
jEpoch,
|
|
|
|
|
fEpoch,
|
2022-01-29 16:32:01 +00:00
|
|
|
|
)
|
2022-06-05 17:48:21 +00:00
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
require.NoError(t, f.InsertNode(ctx, state, blkRoot))
|
2022-01-29 16:32:01 +00:00
|
|
|
|
|
|
|
|
|
// Ensure C is still the head.
|
2022-06-09 22:28:30 +00:00
|
|
|
|
r, err = f.Head(ctx, balances)
|
2022-01-29 16:32:01 +00:00
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.Equal(t, c, r, "Incorrect head for justified epoch at slot 2")
|
|
|
|
|
|
|
|
|
|
// An attestation for C is received at slot N+3.
|
|
|
|
|
votes := []uint64{1}
|
|
|
|
|
f.ProcessAttestation(ctx, votes, c, fEpoch)
|
|
|
|
|
|
|
|
|
|
// A block D, building on B, is received at slot N+3. It should not be able to win without boosting.
|
|
|
|
|
dSlot := types.Slot(3)
|
|
|
|
|
d := indexToHash(3)
|
2022-06-05 17:48:21 +00:00
|
|
|
|
state, blkRoot, err = prepareForkchoiceState(
|
|
|
|
|
ctx,
|
|
|
|
|
dSlot,
|
|
|
|
|
d,
|
|
|
|
|
b, // parent
|
|
|
|
|
zeroHash,
|
|
|
|
|
jEpoch,
|
|
|
|
|
fEpoch,
|
2022-01-29 16:32:01 +00:00
|
|
|
|
)
|
2022-06-05 17:48:21 +00:00
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
require.NoError(t, f.InsertNode(ctx, state, blkRoot))
|
2022-01-29 16:32:01 +00:00
|
|
|
|
|
|
|
|
|
// D cannot win without a boost.
|
2022-06-09 22:28:30 +00:00
|
|
|
|
r, err = f.Head(ctx, balances)
|
2022-01-29 16:32:01 +00:00
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
assert.Equal(t, c, r, "Expected C to remain the head")
|
|
|
|
|
|
2022-06-22 13:56:07 +00:00
|
|
|
|
// If the same block arrives with boosting then it becomes head:
|
|
|
|
|
driftGenesisTime(f, dSlot, 0)
|
|
|
|
|
d2 := indexToHash(30)
|
|
|
|
|
state, blkRoot, err = prepareForkchoiceState(
|
|
|
|
|
ctx,
|
|
|
|
|
dSlot,
|
|
|
|
|
d2,
|
|
|
|
|
b, // parent
|
|
|
|
|
zeroHash,
|
|
|
|
|
jEpoch,
|
|
|
|
|
fEpoch,
|
|
|
|
|
)
|
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
require.NoError(t, f.InsertNode(ctx, state, blkRoot))
|
2022-01-29 16:32:01 +00:00
|
|
|
|
|
2022-06-22 13:56:07 +00:00
|
|
|
|
votes = []uint64{2}
|
|
|
|
|
f.ProcessAttestation(ctx, votes, d2, fEpoch)
|
2022-01-29 16:32:01 +00:00
|
|
|
|
// Ensure D becomes the head thanks to boosting.
|
2022-06-09 22:28:30 +00:00
|
|
|
|
r, err = f.Head(ctx, balances)
|
2022-01-29 16:32:01 +00:00
|
|
|
|
require.NoError(t, err)
|
2022-06-22 13:56:07 +00:00
|
|
|
|
assert.Equal(t, d2, r, "Expected D to become the head")
|
2022-01-29 16:32:01 +00:00
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestForkChoice_BoostProposerRoot(t *testing.T) {
|
|
|
|
|
ctx := context.Background()
|
2022-06-22 13:56:07 +00:00
|
|
|
|
root := [32]byte{'A'}
|
|
|
|
|
zeroHash := [32]byte{}
|
2022-01-29 16:32:01 +00:00
|
|
|
|
|
|
|
|
|
t.Run("does not boost block from different slot", func(t *testing.T) {
|
2022-06-22 13:56:07 +00:00
|
|
|
|
f := setup(0, 0)
|
|
|
|
|
slot := types.Slot(0)
|
|
|
|
|
currentSlot := types.Slot(1)
|
|
|
|
|
driftGenesisTime(f, currentSlot, 0)
|
|
|
|
|
state, blkRoot, err := prepareForkchoiceState(ctx, slot, root, zeroHash, zeroHash, 0, 0)
|
2022-01-29 16:32:01 +00:00
|
|
|
|
require.NoError(t, err)
|
2022-06-22 13:56:07 +00:00
|
|
|
|
require.NoError(t, f.InsertNode(ctx, state, blkRoot))
|
|
|
|
|
require.Equal(t, [32]byte{}, f.store.proposerBoostRoot)
|
2022-01-29 16:32:01 +00:00
|
|
|
|
})
|
|
|
|
|
t.Run("does not boost untimely block from same slot", func(t *testing.T) {
|
2022-06-22 13:56:07 +00:00
|
|
|
|
f := setup(0, 0)
|
|
|
|
|
slot := types.Slot(1)
|
|
|
|
|
currentSlot := types.Slot(1)
|
|
|
|
|
driftGenesisTime(f, currentSlot, uint64(params.BeaconConfig().SecondsPerSlot-1))
|
|
|
|
|
state, blkRoot, err := prepareForkchoiceState(ctx, slot, root, zeroHash, zeroHash, 0, 0)
|
2022-01-29 16:32:01 +00:00
|
|
|
|
require.NoError(t, err)
|
2022-06-22 13:56:07 +00:00
|
|
|
|
require.NoError(t, f.InsertNode(ctx, state, blkRoot))
|
|
|
|
|
require.Equal(t, [32]byte{}, f.store.proposerBoostRoot)
|
2022-01-29 16:32:01 +00:00
|
|
|
|
})
|
|
|
|
|
t.Run("boosts perfectly timely block from same slot", func(t *testing.T) {
|
2022-06-22 13:56:07 +00:00
|
|
|
|
f := setup(0, 0)
|
|
|
|
|
slot := types.Slot(1)
|
|
|
|
|
currentSlot := types.Slot(1)
|
|
|
|
|
driftGenesisTime(f, currentSlot, 0)
|
|
|
|
|
state, blkRoot, err := prepareForkchoiceState(ctx, slot, root, zeroHash, zeroHash, 0, 0)
|
2022-01-29 16:32:01 +00:00
|
|
|
|
require.NoError(t, err)
|
2022-06-22 13:56:07 +00:00
|
|
|
|
require.NoError(t, f.InsertNode(ctx, state, blkRoot))
|
|
|
|
|
require.Equal(t, root, f.store.proposerBoostRoot)
|
2022-01-29 16:32:01 +00:00
|
|
|
|
})
|
|
|
|
|
t.Run("boosts timely block from same slot", func(t *testing.T) {
|
2022-06-22 13:56:07 +00:00
|
|
|
|
f := setup(0, 0)
|
|
|
|
|
slot := types.Slot(1)
|
|
|
|
|
currentSlot := types.Slot(1)
|
|
|
|
|
driftGenesisTime(f, currentSlot, 1)
|
|
|
|
|
state, blkRoot, err := prepareForkchoiceState(ctx, slot, root, zeroHash, zeroHash, 0, 0)
|
2022-01-29 16:32:01 +00:00
|
|
|
|
require.NoError(t, err)
|
2022-06-22 13:56:07 +00:00
|
|
|
|
require.NoError(t, f.InsertNode(ctx, state, blkRoot))
|
|
|
|
|
require.Equal(t, root, f.store.proposerBoostRoot)
|
2022-01-29 16:32:01 +00:00
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestForkChoice_computeProposerBoostScore(t *testing.T) {
|
|
|
|
|
t.Run("nil justified balances throws error", func(t *testing.T) {
|
|
|
|
|
_, err := computeProposerBoostScore(nil)
|
|
|
|
|
require.ErrorContains(t, "no active validators", err)
|
|
|
|
|
})
|
|
|
|
|
t.Run("normal active balances computes score", func(t *testing.T) {
|
|
|
|
|
validatorBalances := make([]uint64, 64) // Num validators
|
|
|
|
|
for i := 0; i < len(validatorBalances); i++ {
|
|
|
|
|
validatorBalances[i] = 10
|
|
|
|
|
}
|
|
|
|
|
// Avg balance is 10, and the number of validators is 64.
|
|
|
|
|
// With a committee size of num validators (64) / slots per epoch (32) == 2.
|
|
|
|
|
// we then have a committee weight of avg balance * committee size = 10 * 2 = 20.
|
|
|
|
|
// The score then becomes 10 * PROPOSER_SCORE_BOOST // 100, which is
|
2022-05-23 23:08:24 +00:00
|
|
|
|
// 20 * 40 / 100 = 8.
|
2022-01-29 16:32:01 +00:00
|
|
|
|
score, err := computeProposerBoostScore(validatorBalances)
|
|
|
|
|
require.NoError(t, err)
|
2022-05-23 23:08:24 +00:00
|
|
|
|
require.Equal(t, uint64(8), score)
|
2022-01-29 16:32:01 +00:00
|
|
|
|
})
|
|
|
|
|
}
|