2020-10-26 08:54:06 +00:00
|
|
|
package initialsync
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
2021-03-10 20:03:26 +00:00
|
|
|
"sync"
|
2020-10-26 08:54:06 +00:00
|
|
|
"testing"
|
2021-03-10 20:03:26 +00:00
|
|
|
"time"
|
2020-10-26 08:54:06 +00:00
|
|
|
|
|
|
|
"github.com/kevinms/leakybucket-go"
|
2020-11-05 07:27:46 +00:00
|
|
|
"github.com/libp2p/go-libp2p-core/network"
|
|
|
|
"github.com/libp2p/go-libp2p-core/peer"
|
2021-02-16 07:45:34 +00:00
|
|
|
types "github.com/prysmaticlabs/eth2-types"
|
2020-11-05 07:27:46 +00:00
|
|
|
mock "github.com/prysmaticlabs/prysm/beacon-chain/blockchain/testing"
|
|
|
|
"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
|
|
|
|
dbtest "github.com/prysmaticlabs/prysm/beacon-chain/db/testing"
|
|
|
|
p2pm "github.com/prysmaticlabs/prysm/beacon-chain/p2p"
|
2020-10-31 21:33:57 +00:00
|
|
|
p2pt "github.com/prysmaticlabs/prysm/beacon-chain/p2p/testing"
|
2021-03-02 19:36:03 +00:00
|
|
|
"github.com/prysmaticlabs/prysm/cmd/beacon-chain/flags"
|
2021-07-21 21:34:07 +00:00
|
|
|
eth "github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1"
|
|
|
|
"github.com/prysmaticlabs/prysm/proto/prysm/v1alpha1/wrapper"
|
2021-07-23 13:07:40 +00:00
|
|
|
p2ppb "github.com/prysmaticlabs/prysm/proto/prysm/v2"
|
2020-11-05 07:27:46 +00:00
|
|
|
"github.com/prysmaticlabs/prysm/shared/bytesutil"
|
2020-10-31 21:33:57 +00:00
|
|
|
"github.com/prysmaticlabs/prysm/shared/params"
|
2020-11-05 07:27:46 +00:00
|
|
|
"github.com/prysmaticlabs/prysm/shared/testutil"
|
2020-10-26 08:54:06 +00:00
|
|
|
"github.com/prysmaticlabs/prysm/shared/testutil/assert"
|
2020-10-31 21:33:57 +00:00
|
|
|
"github.com/prysmaticlabs/prysm/shared/testutil/require"
|
2020-10-26 08:54:06 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func TestBlocksFetcher_nonSkippedSlotAfter(t *testing.T) {
|
|
|
|
peersGen := func(size int) []*peerData {
|
|
|
|
blocks := append(makeSequence(1, 64), makeSequence(500, 640)...)
|
|
|
|
blocks = append(blocks, makeSequence(51200, 51264)...)
|
|
|
|
blocks = append(blocks, 55000)
|
|
|
|
blocks = append(blocks, makeSequence(57000, 57256)...)
|
|
|
|
var peersData []*peerData
|
|
|
|
for i := 0; i < size; i++ {
|
|
|
|
peersData = append(peersData, &peerData{
|
|
|
|
blocks: blocks,
|
|
|
|
finalizedEpoch: 1800,
|
|
|
|
headSlot: 57000,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
return peersData
|
|
|
|
}
|
|
|
|
chainConfig := struct {
|
|
|
|
peers []*peerData
|
|
|
|
}{
|
|
|
|
peers: peersGen(5),
|
|
|
|
}
|
|
|
|
|
2021-02-16 07:45:34 +00:00
|
|
|
mc, p2p, _ := initializeTestServices(t, []types.Slot{}, chainConfig.peers)
|
2020-10-26 08:54:06 +00:00
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
fetcher := newBlocksFetcher(
|
|
|
|
ctx,
|
|
|
|
&blocksFetcherConfig{
|
2020-10-31 21:33:57 +00:00
|
|
|
chain: mc,
|
|
|
|
p2p: p2p,
|
2020-10-26 08:54:06 +00:00
|
|
|
},
|
|
|
|
)
|
|
|
|
fetcher.rateLimiter = leakybucket.NewCollector(6400, 6400, false)
|
2021-02-16 07:45:34 +00:00
|
|
|
seekSlots := map[types.Slot]types.Slot{
|
2020-10-26 08:54:06 +00:00
|
|
|
0: 1,
|
|
|
|
10: 11,
|
|
|
|
31: 32,
|
|
|
|
32: 33,
|
|
|
|
63: 64,
|
|
|
|
64: 500,
|
|
|
|
160: 500,
|
|
|
|
352: 500,
|
|
|
|
480: 500,
|
|
|
|
512: 513,
|
|
|
|
639: 640,
|
|
|
|
640: 51200,
|
|
|
|
6640: 51200,
|
|
|
|
51200: 51201,
|
|
|
|
}
|
|
|
|
for seekSlot, expectedSlot := range seekSlots {
|
|
|
|
t.Run(fmt.Sprintf("range: %d (%d-%d)", expectedSlot-seekSlot, seekSlot, expectedSlot), func(t *testing.T) {
|
|
|
|
slot, err := fetcher.nonSkippedSlotAfter(ctx, seekSlot)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
assert.Equal(t, expectedSlot, slot, "Unexpected slot")
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
t.Run("test isolated non-skipped slot", func(t *testing.T) {
|
2021-02-16 07:45:34 +00:00
|
|
|
seekSlot := types.Slot(51264)
|
|
|
|
expectedSlot := types.Slot(55000)
|
2021-03-10 20:03:26 +00:00
|
|
|
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
wg.Add(1)
|
|
|
|
|
2020-10-26 08:54:06 +00:00
|
|
|
var i int
|
2021-03-10 20:03:26 +00:00
|
|
|
go func() {
|
|
|
|
for {
|
|
|
|
i++
|
|
|
|
slot, err := fetcher.nonSkippedSlotAfter(ctx, seekSlot)
|
|
|
|
assert.NoError(t, err)
|
|
|
|
if slot == expectedSlot {
|
|
|
|
wg.Done()
|
|
|
|
break
|
|
|
|
}
|
2020-10-26 08:54:06 +00:00
|
|
|
}
|
2021-03-10 20:03:26 +00:00
|
|
|
}()
|
|
|
|
if testutil.WaitTimeout(&wg, 5*time.Second) {
|
2020-10-26 08:54:06 +00:00
|
|
|
t.Errorf("Isolated non-skipped slot not found in %d iterations: %v", i, expectedSlot)
|
|
|
|
} else {
|
2020-10-31 21:33:57 +00:00
|
|
|
log.Debugf("Isolated non-skipped slot found in %d iterations", i)
|
2020-10-26 08:54:06 +00:00
|
|
|
}
|
|
|
|
})
|
2020-10-31 21:33:57 +00:00
|
|
|
|
|
|
|
t.Run("no peers with higher target epoch available", func(t *testing.T) {
|
|
|
|
peers := []*peerData{
|
|
|
|
{finalizedEpoch: 3, headSlot: 160},
|
|
|
|
{finalizedEpoch: 3, headSlot: 160},
|
|
|
|
{finalizedEpoch: 3, headSlot: 160},
|
|
|
|
{finalizedEpoch: 8, headSlot: 320},
|
|
|
|
{finalizedEpoch: 8, headSlot: 320},
|
|
|
|
{finalizedEpoch: 10, headSlot: 320},
|
|
|
|
{finalizedEpoch: 10, headSlot: 640},
|
|
|
|
}
|
|
|
|
p2p := p2pt.NewTestP2P(t)
|
|
|
|
connectPeers(t, p2p, peers, p2p.Peers())
|
|
|
|
fetcher := newBlocksFetcher(
|
|
|
|
ctx,
|
|
|
|
&blocksFetcherConfig{
|
|
|
|
chain: mc,
|
|
|
|
p2p: p2p,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
mc.FinalizedCheckPoint = ð.Checkpoint{
|
|
|
|
Epoch: 10,
|
|
|
|
}
|
|
|
|
require.NoError(t, mc.State.SetSlot(12*params.BeaconConfig().SlotsPerEpoch))
|
|
|
|
|
|
|
|
fetcher.mode = modeStopOnFinalizedEpoch
|
|
|
|
slot, err := fetcher.nonSkippedSlotAfter(ctx, 160)
|
|
|
|
assert.ErrorContains(t, errSlotIsTooHigh.Error(), err)
|
2021-02-16 07:45:34 +00:00
|
|
|
assert.Equal(t, types.Slot(0), slot)
|
2020-10-31 21:33:57 +00:00
|
|
|
|
|
|
|
fetcher.mode = modeNonConstrained
|
|
|
|
require.NoError(t, mc.State.SetSlot(20*params.BeaconConfig().SlotsPerEpoch))
|
|
|
|
slot, err = fetcher.nonSkippedSlotAfter(ctx, 160)
|
|
|
|
assert.ErrorContains(t, errSlotIsTooHigh.Error(), err)
|
2021-02-16 07:45:34 +00:00
|
|
|
assert.Equal(t, types.Slot(0), slot)
|
2020-10-31 21:33:57 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-11-05 07:27:46 +00:00
|
|
|
func TestBlocksFetcher_findFork(t *testing.T) {
|
|
|
|
// Chain graph:
|
|
|
|
// A - B - C - D - E
|
|
|
|
// \
|
|
|
|
// - C'- D'- E'- F'- G'
|
|
|
|
// Allow fetcher to proceed till E, then connect peer having alternative branch.
|
|
|
|
// Test that G' slot can be reached i.e. fetcher can track back and explore alternative paths.
|
2020-12-16 16:56:21 +00:00
|
|
|
beaconDB := dbtest.SetupDB(t)
|
2020-11-05 07:27:46 +00:00
|
|
|
p2p := p2pt.NewTestP2P(t)
|
|
|
|
|
|
|
|
// Chain contains blocks from 8 epochs (from 0 to 7, 256 is the start slot of epoch8).
|
|
|
|
chain1 := extendBlockSequence(t, []*eth.SignedBeaconBlock{}, 250)
|
2021-02-16 07:45:34 +00:00
|
|
|
finalizedSlot := types.Slot(63)
|
2020-11-05 07:27:46 +00:00
|
|
|
finalizedEpoch := helpers.SlotToEpoch(finalizedSlot)
|
|
|
|
|
|
|
|
genesisBlock := chain1[0]
|
2021-07-06 15:34:05 +00:00
|
|
|
require.NoError(t, beaconDB.SaveBlock(context.Background(), wrapper.WrappedPhase0SignedBeaconBlock(genesisBlock)))
|
2020-11-05 07:27:46 +00:00
|
|
|
genesisRoot, err := genesisBlock.Block.HashTreeRoot()
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2021-02-08 20:00:09 +00:00
|
|
|
st, err := testutil.NewBeaconState()
|
|
|
|
require.NoError(t, err)
|
2020-11-05 07:27:46 +00:00
|
|
|
mc := &mock.ChainService{
|
|
|
|
State: st,
|
|
|
|
Root: genesisRoot[:],
|
|
|
|
DB: beaconDB,
|
|
|
|
FinalizedCheckPoint: ð.Checkpoint{
|
|
|
|
Epoch: finalizedEpoch,
|
|
|
|
Root: []byte(fmt.Sprintf("finalized_root %d", finalizedEpoch)),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
defer cancel()
|
|
|
|
fetcher := newBlocksFetcher(
|
|
|
|
ctx,
|
|
|
|
&blocksFetcherConfig{
|
|
|
|
chain: mc,
|
|
|
|
p2p: p2p,
|
|
|
|
db: beaconDB,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
fetcher.rateLimiter = leakybucket.NewCollector(6400, 6400, false)
|
|
|
|
|
|
|
|
// Consume all chain1 blocks from many peers (alternative fork will be featured by a single peer,
|
|
|
|
// and should still be enough to explore alternative paths).
|
|
|
|
peers := make([]peer.ID, 0)
|
|
|
|
for i := 0; i < 5; i++ {
|
|
|
|
peers = append(peers, connectPeerHavingBlocks(t, p2p, chain1, finalizedSlot, p2p.Peers()))
|
|
|
|
}
|
|
|
|
|
|
|
|
blockBatchLimit := uint64(flags.Get().BlockBatchLimit) * 2
|
|
|
|
pidInd := 0
|
|
|
|
for i := uint64(1); i < uint64(len(chain1)); i += blockBatchLimit {
|
|
|
|
req := &p2ppb.BeaconBlocksByRangeRequest{
|
2021-02-16 07:45:34 +00:00
|
|
|
StartSlot: types.Slot(i),
|
2020-11-05 07:27:46 +00:00
|
|
|
Step: 1,
|
|
|
|
Count: blockBatchLimit,
|
|
|
|
}
|
|
|
|
blocks, err := fetcher.requestBlocks(ctx, req, peers[pidInd%len(peers)])
|
|
|
|
require.NoError(t, err)
|
|
|
|
for _, blk := range blocks {
|
|
|
|
require.NoError(t, beaconDB.SaveBlock(ctx, blk))
|
2021-05-26 16:19:54 +00:00
|
|
|
require.NoError(t, st.SetSlot(blk.Block().Slot()))
|
2020-11-05 07:27:46 +00:00
|
|
|
}
|
|
|
|
pidInd++
|
|
|
|
}
|
|
|
|
|
|
|
|
// Assert that all the blocks from chain1 are known.
|
|
|
|
for _, blk := range chain1 {
|
|
|
|
blkRoot, err := blk.Block.HashTreeRoot()
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, true, beaconDB.HasBlock(ctx, blkRoot) || mc.HasInitSyncBlock(blkRoot))
|
|
|
|
}
|
2021-02-16 07:45:34 +00:00
|
|
|
assert.Equal(t, types.Slot(250), mc.HeadSlot())
|
2020-11-05 07:27:46 +00:00
|
|
|
|
|
|
|
// Assert no blocks on further requests, disallowing to progress.
|
|
|
|
req := &p2ppb.BeaconBlocksByRangeRequest{
|
|
|
|
StartSlot: 251,
|
|
|
|
Step: 1,
|
|
|
|
Count: blockBatchLimit,
|
|
|
|
}
|
|
|
|
blocks, err := fetcher.requestBlocks(ctx, req, peers[pidInd%len(peers)])
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, 0, len(blocks))
|
|
|
|
|
|
|
|
// If no peers with unexplored paths exist, error should be returned.
|
|
|
|
fork, err := fetcher.findFork(ctx, 251)
|
|
|
|
require.ErrorContains(t, errNoPeersAvailable.Error(), err)
|
|
|
|
require.Equal(t, (*forkData)(nil), fork)
|
|
|
|
|
|
|
|
// Add peer that has blocks after 250, but those blocks are orphaned i.e. they do not have common
|
|
|
|
// ancestor with what we already have. So, error is expected.
|
|
|
|
chain1a := extendBlockSequence(t, []*eth.SignedBeaconBlock{}, 265)
|
|
|
|
connectPeerHavingBlocks(t, p2p, chain1a, finalizedSlot, p2p.Peers())
|
|
|
|
fork, err = fetcher.findFork(ctx, 251)
|
|
|
|
require.ErrorContains(t, errNoPeersWithAltBlocks.Error(), err)
|
|
|
|
require.Equal(t, (*forkData)(nil), fork)
|
|
|
|
|
|
|
|
// Add peer which has blocks after 250. It is not on another fork, but algorithm
|
|
|
|
// is smart enough to link back to common ancestor, w/o discriminating between forks. This is
|
|
|
|
// by design: fork exploration is undertaken when FSMs are stuck, so any progress is good.
|
|
|
|
chain1b := extendBlockSequence(t, chain1, 64)
|
|
|
|
curForkMoreBlocksPeer := connectPeerHavingBlocks(t, p2p, chain1b, finalizedSlot, p2p.Peers())
|
|
|
|
fork, err = fetcher.findFork(ctx, 251)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, 64, len(fork.blocks))
|
|
|
|
require.Equal(t, curForkMoreBlocksPeer, fork.peer)
|
|
|
|
// Save all chain1b blocks (so that they do not interfere with alternative fork)
|
|
|
|
for _, blk := range chain1b {
|
2021-07-06 15:34:05 +00:00
|
|
|
require.NoError(t, beaconDB.SaveBlock(ctx, wrapper.WrappedPhase0SignedBeaconBlock(blk)))
|
2020-11-05 07:27:46 +00:00
|
|
|
require.NoError(t, st.SetSlot(blk.Block.Slot))
|
|
|
|
}
|
2021-02-16 07:45:34 +00:00
|
|
|
forkSlot := types.Slot(129)
|
2020-11-05 07:27:46 +00:00
|
|
|
chain2 := extendBlockSequence(t, chain1[:forkSlot], 165)
|
|
|
|
// Assert that forked blocks from chain2 are unknown.
|
|
|
|
assert.Equal(t, 294, len(chain2))
|
|
|
|
for _, blk := range chain2[forkSlot:] {
|
|
|
|
blkRoot, err := blk.Block.HashTreeRoot()
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, false, beaconDB.HasBlock(ctx, blkRoot) || mc.HasInitSyncBlock(blkRoot))
|
|
|
|
}
|
|
|
|
|
|
|
|
// Search for alternative paths (add single peer having alternative path).
|
|
|
|
alternativePeer := connectPeerHavingBlocks(t, p2p, chain2, finalizedSlot, p2p.Peers())
|
|
|
|
fork, err = fetcher.findFork(ctx, 251)
|
|
|
|
require.NoError(t, err)
|
|
|
|
assert.Equal(t, alternativePeer, fork.peer)
|
|
|
|
assert.Equal(t, 65, len(fork.blocks))
|
|
|
|
ind := forkSlot
|
|
|
|
for _, blk := range fork.blocks {
|
2021-05-26 16:19:54 +00:00
|
|
|
require.Equal(t, blk.Block().Slot(), chain2[ind].Block.Slot)
|
2020-11-05 07:27:46 +00:00
|
|
|
ind++
|
|
|
|
}
|
|
|
|
|
|
|
|
// Process returned blocks and then attempt to extend chain (ensuring that parent block exists).
|
|
|
|
for _, blk := range fork.blocks {
|
|
|
|
require.NoError(t, beaconDB.SaveBlock(ctx, blk))
|
2021-05-26 16:19:54 +00:00
|
|
|
require.NoError(t, st.SetSlot(blk.Block().Slot()))
|
2020-11-05 07:27:46 +00:00
|
|
|
}
|
2021-02-16 07:45:34 +00:00
|
|
|
assert.Equal(t, forkSlot.Add(uint64(len(fork.blocks)-1)), mc.HeadSlot())
|
|
|
|
for i := forkSlot.Add(uint64(len(fork.blocks))); i < types.Slot(len(chain2)); i++ {
|
2020-11-05 07:27:46 +00:00
|
|
|
blk := chain2[i]
|
2021-02-16 07:45:34 +00:00
|
|
|
require.Equal(t, blk.Block.Slot, i, "incorrect block selected for slot %d", i)
|
2020-11-05 07:27:46 +00:00
|
|
|
// Only save is parent block exists.
|
|
|
|
parentRoot := bytesutil.ToBytes32(blk.Block.ParentRoot)
|
|
|
|
if beaconDB.HasBlock(ctx, parentRoot) || mc.HasInitSyncBlock(parentRoot) {
|
2021-07-06 15:34:05 +00:00
|
|
|
require.NoError(t, beaconDB.SaveBlock(ctx, wrapper.WrappedPhase0SignedBeaconBlock(blk)))
|
2020-11-05 07:27:46 +00:00
|
|
|
require.NoError(t, st.SetSlot(blk.Block.Slot))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Assert that all the blocks from chain2 are known.
|
|
|
|
for _, blk := range chain2 {
|
|
|
|
blkRoot, err := blk.Block.HashTreeRoot()
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, true, beaconDB.HasBlock(ctx, blkRoot) || mc.HasInitSyncBlock(blkRoot), "slot %d", blk.Block.Slot)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestBlocksFetcher_findForkWithPeer(t *testing.T) {
|
2020-12-16 16:56:21 +00:00
|
|
|
beaconDB := dbtest.SetupDB(t)
|
2020-11-05 07:27:46 +00:00
|
|
|
p1 := p2pt.NewTestP2P(t)
|
|
|
|
|
|
|
|
knownBlocks := extendBlockSequence(t, []*eth.SignedBeaconBlock{}, 128)
|
|
|
|
genesisBlock := knownBlocks[0]
|
2021-07-06 15:34:05 +00:00
|
|
|
require.NoError(t, beaconDB.SaveBlock(context.Background(), wrapper.WrappedPhase0SignedBeaconBlock(genesisBlock)))
|
2020-11-05 07:27:46 +00:00
|
|
|
genesisRoot, err := genesisBlock.Block.HashTreeRoot()
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2021-02-08 20:00:09 +00:00
|
|
|
st, err := testutil.NewBeaconState()
|
|
|
|
require.NoError(t, err)
|
2020-11-05 07:27:46 +00:00
|
|
|
mc := &mock.ChainService{
|
|
|
|
State: st,
|
|
|
|
Root: genesisRoot[:],
|
|
|
|
DB: beaconDB,
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
defer cancel()
|
|
|
|
fetcher := newBlocksFetcher(
|
|
|
|
ctx,
|
|
|
|
&blocksFetcherConfig{
|
|
|
|
chain: mc,
|
|
|
|
p2p: p1,
|
|
|
|
db: beaconDB,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
fetcher.rateLimiter = leakybucket.NewCollector(6400, 6400, false)
|
|
|
|
|
|
|
|
for _, blk := range knownBlocks {
|
2021-07-06 15:34:05 +00:00
|
|
|
require.NoError(t, beaconDB.SaveBlock(ctx, wrapper.WrappedPhase0SignedBeaconBlock(blk)))
|
2020-11-05 07:27:46 +00:00
|
|
|
require.NoError(t, st.SetSlot(blk.Block.Slot))
|
|
|
|
}
|
|
|
|
|
|
|
|
t.Run("slot is too early", func(t *testing.T) {
|
|
|
|
p2 := p2pt.NewTestP2P(t)
|
|
|
|
_, err := fetcher.findForkWithPeer(ctx, p2.PeerID(), 0)
|
|
|
|
assert.ErrorContains(t, "slot is too low to backtrack", err)
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("no peer status", func(t *testing.T) {
|
|
|
|
p2 := p2pt.NewTestP2P(t)
|
|
|
|
_, err := fetcher.findForkWithPeer(ctx, p2.PeerID(), 64)
|
|
|
|
assert.ErrorContains(t, "cannot obtain peer's status", err)
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("no non-skipped blocks found", func(t *testing.T) {
|
|
|
|
p2 := p2pt.NewTestP2P(t)
|
|
|
|
p1.Connect(p2)
|
|
|
|
defer func() {
|
|
|
|
assert.NoError(t, p1.Disconnect(p2.PeerID()))
|
|
|
|
}()
|
|
|
|
p1.Peers().SetChainState(p2.PeerID(), &p2ppb.Status{
|
|
|
|
HeadRoot: nil,
|
|
|
|
HeadSlot: 0,
|
|
|
|
})
|
|
|
|
_, err := fetcher.findForkWithPeer(ctx, p2.PeerID(), 64)
|
|
|
|
assert.ErrorContains(t, "cannot locate non-empty slot for a peer", err)
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("no diverging blocks", func(t *testing.T) {
|
|
|
|
p2 := connectPeerHavingBlocks(t, p1, knownBlocks, 64, p1.Peers())
|
|
|
|
defer func() {
|
|
|
|
assert.NoError(t, p1.Disconnect(p2))
|
|
|
|
}()
|
|
|
|
_, err := fetcher.findForkWithPeer(ctx, p2, 64)
|
|
|
|
assert.ErrorContains(t, "no alternative blocks exist within scanned range", err)
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("first block is diverging - backtrack successfully", func(t *testing.T) {
|
2021-02-16 07:45:34 +00:00
|
|
|
forkedSlot := types.Slot(24)
|
2020-11-05 07:27:46 +00:00
|
|
|
altBlocks := extendBlockSequence(t, knownBlocks[:forkedSlot], 128)
|
|
|
|
p2 := connectPeerHavingBlocks(t, p1, altBlocks, 128, p1.Peers())
|
|
|
|
defer func() {
|
|
|
|
assert.NoError(t, p1.Disconnect(p2))
|
|
|
|
}()
|
|
|
|
fork, err := fetcher.findForkWithPeer(ctx, p2, 64)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, 10, len(fork.blocks))
|
2021-05-26 16:19:54 +00:00
|
|
|
assert.Equal(t, forkedSlot, fork.blocks[0].Block().Slot(), "Expected slot %d to be ancestor", forkedSlot)
|
2020-11-05 07:27:46 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("first block is diverging - no common ancestor", func(t *testing.T) {
|
|
|
|
altBlocks := extendBlockSequence(t, []*eth.SignedBeaconBlock{}, 128)
|
|
|
|
p2 := connectPeerHavingBlocks(t, p1, altBlocks, 128, p1.Peers())
|
|
|
|
defer func() {
|
|
|
|
assert.NoError(t, p1.Disconnect(p2))
|
|
|
|
}()
|
|
|
|
_, err := fetcher.findForkWithPeer(ctx, p2, 64)
|
|
|
|
require.ErrorContains(t, "failed to find common ancestor", err)
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("mid block is diverging - no backtrack is necessary", func(t *testing.T) {
|
2021-02-16 07:45:34 +00:00
|
|
|
forkedSlot := types.Slot(60)
|
2020-11-05 07:27:46 +00:00
|
|
|
altBlocks := extendBlockSequence(t, knownBlocks[:forkedSlot], 128)
|
|
|
|
p2 := connectPeerHavingBlocks(t, p1, altBlocks, 128, p1.Peers())
|
|
|
|
defer func() {
|
|
|
|
assert.NoError(t, p1.Disconnect(p2))
|
|
|
|
}()
|
|
|
|
fork, err := fetcher.findForkWithPeer(ctx, p2, 64)
|
|
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, 64, len(fork.blocks))
|
2021-05-26 16:19:54 +00:00
|
|
|
assert.Equal(t, types.Slot(33), fork.blocks[0].Block().Slot())
|
2020-11-05 07:27:46 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func TestBlocksFetcher_findAncestor(t *testing.T) {
|
2020-12-16 16:56:21 +00:00
|
|
|
beaconDB := dbtest.SetupDB(t)
|
2020-11-05 07:27:46 +00:00
|
|
|
p2p := p2pt.NewTestP2P(t)
|
|
|
|
|
|
|
|
knownBlocks := extendBlockSequence(t, []*eth.SignedBeaconBlock{}, 128)
|
2021-02-16 07:45:34 +00:00
|
|
|
finalizedSlot := types.Slot(63)
|
2020-11-05 07:27:46 +00:00
|
|
|
finalizedEpoch := helpers.SlotToEpoch(finalizedSlot)
|
|
|
|
|
|
|
|
genesisBlock := knownBlocks[0]
|
2021-07-06 15:34:05 +00:00
|
|
|
require.NoError(t, beaconDB.SaveBlock(context.Background(), wrapper.WrappedPhase0SignedBeaconBlock(genesisBlock)))
|
2020-11-05 07:27:46 +00:00
|
|
|
genesisRoot, err := genesisBlock.Block.HashTreeRoot()
|
|
|
|
require.NoError(t, err)
|
|
|
|
|
2021-02-08 20:00:09 +00:00
|
|
|
st, err := testutil.NewBeaconState()
|
|
|
|
require.NoError(t, err)
|
2020-11-05 07:27:46 +00:00
|
|
|
mc := &mock.ChainService{
|
|
|
|
State: st,
|
|
|
|
Root: genesisRoot[:],
|
|
|
|
DB: beaconDB,
|
|
|
|
FinalizedCheckPoint: ð.Checkpoint{
|
|
|
|
Epoch: finalizedEpoch,
|
|
|
|
Root: []byte(fmt.Sprintf("finalized_root %d", finalizedEpoch)),
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
defer cancel()
|
|
|
|
fetcher := newBlocksFetcher(
|
|
|
|
ctx,
|
|
|
|
&blocksFetcherConfig{
|
|
|
|
chain: mc,
|
|
|
|
p2p: p2p,
|
|
|
|
db: beaconDB,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
fetcher.rateLimiter = leakybucket.NewCollector(6400, 6400, false)
|
2021-05-15 00:58:56 +00:00
|
|
|
pcl := fmt.Sprintf("%s/ssz_snappy", p2pm.RPCBlocksByRootTopicV1)
|
2020-11-05 07:27:46 +00:00
|
|
|
|
|
|
|
t.Run("error on request", func(t *testing.T) {
|
|
|
|
p2 := p2pt.NewTestP2P(t)
|
|
|
|
p2p.Connect(p2)
|
|
|
|
|
2021-07-06 15:34:05 +00:00
|
|
|
_, err := fetcher.findAncestor(ctx, p2.PeerID(), wrapper.WrappedPhase0SignedBeaconBlock(knownBlocks[4]))
|
2020-11-05 07:27:46 +00:00
|
|
|
assert.ErrorContains(t, "protocol not supported", err)
|
|
|
|
})
|
|
|
|
|
|
|
|
t.Run("no blocks", func(t *testing.T) {
|
|
|
|
p2 := p2pt.NewTestP2P(t)
|
|
|
|
p2p.Connect(p2)
|
|
|
|
|
|
|
|
p2.SetStreamHandler(pcl, func(stream network.Stream) {
|
|
|
|
assert.NoError(t, stream.Close())
|
|
|
|
})
|
|
|
|
|
2021-07-06 15:34:05 +00:00
|
|
|
fork, err := fetcher.findAncestor(ctx, p2.PeerID(), wrapper.WrappedPhase0SignedBeaconBlock(knownBlocks[4]))
|
2020-11-05 07:27:46 +00:00
|
|
|
assert.ErrorContains(t, "no common ancestor found", err)
|
|
|
|
assert.Equal(t, (*forkData)(nil), fork)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-10-31 21:33:57 +00:00
|
|
|
func TestBlocksFetcher_currentHeadAndTargetEpochs(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
name string
|
|
|
|
syncMode syncMode
|
|
|
|
peers []*peerData
|
2021-02-09 10:05:22 +00:00
|
|
|
ourFinalizedEpoch types.Epoch
|
2021-02-16 07:45:34 +00:00
|
|
|
ourHeadSlot types.Slot
|
2021-02-09 10:05:22 +00:00
|
|
|
expectedHeadEpoch types.Epoch
|
|
|
|
targetEpoch types.Epoch
|
2020-10-31 21:33:57 +00:00
|
|
|
targetEpochSupport int
|
|
|
|
}{
|
|
|
|
{
|
|
|
|
name: "ignore lower epoch peers in best finalized",
|
|
|
|
syncMode: modeStopOnFinalizedEpoch,
|
|
|
|
ourHeadSlot: 5 * params.BeaconConfig().SlotsPerEpoch,
|
|
|
|
expectedHeadEpoch: 4,
|
|
|
|
ourFinalizedEpoch: 4,
|
|
|
|
targetEpoch: 10,
|
|
|
|
targetEpochSupport: 3,
|
|
|
|
peers: []*peerData{
|
|
|
|
{finalizedEpoch: 3, headSlot: 160},
|
|
|
|
{finalizedEpoch: 3, headSlot: 160},
|
|
|
|
{finalizedEpoch: 3, headSlot: 160},
|
|
|
|
{finalizedEpoch: 3, headSlot: 160},
|
|
|
|
{finalizedEpoch: 3, headSlot: 160},
|
|
|
|
{finalizedEpoch: 8, headSlot: 320},
|
|
|
|
{finalizedEpoch: 8, headSlot: 320},
|
|
|
|
{finalizedEpoch: 10, headSlot: 320},
|
|
|
|
{finalizedEpoch: 10, headSlot: 320},
|
|
|
|
{finalizedEpoch: 10, headSlot: 320},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "resolve ties in best finalized",
|
|
|
|
syncMode: modeStopOnFinalizedEpoch,
|
|
|
|
ourHeadSlot: 5 * params.BeaconConfig().SlotsPerEpoch,
|
|
|
|
expectedHeadEpoch: 4,
|
|
|
|
ourFinalizedEpoch: 4,
|
|
|
|
targetEpoch: 10,
|
|
|
|
targetEpochSupport: 3,
|
|
|
|
peers: []*peerData{
|
|
|
|
{finalizedEpoch: 3, headSlot: 160},
|
|
|
|
{finalizedEpoch: 3, headSlot: 160},
|
|
|
|
{finalizedEpoch: 3, headSlot: 160},
|
|
|
|
{finalizedEpoch: 3, headSlot: 160},
|
|
|
|
{finalizedEpoch: 3, headSlot: 160},
|
|
|
|
{finalizedEpoch: 8, headSlot: 320},
|
|
|
|
{finalizedEpoch: 8, headSlot: 320},
|
|
|
|
{finalizedEpoch: 8, headSlot: 320},
|
|
|
|
{finalizedEpoch: 10, headSlot: 320},
|
|
|
|
{finalizedEpoch: 10, headSlot: 320},
|
|
|
|
{finalizedEpoch: 10, headSlot: 320},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
{
|
|
|
|
name: "best non-finalized",
|
|
|
|
syncMode: modeNonConstrained,
|
|
|
|
ourHeadSlot: 5 * params.BeaconConfig().SlotsPerEpoch,
|
|
|
|
expectedHeadEpoch: 5,
|
|
|
|
ourFinalizedEpoch: 4,
|
|
|
|
targetEpoch: 20,
|
|
|
|
targetEpochSupport: 1,
|
|
|
|
peers: []*peerData{
|
|
|
|
{finalizedEpoch: 3, headSlot: 160},
|
|
|
|
{finalizedEpoch: 3, headSlot: 160},
|
|
|
|
{finalizedEpoch: 3, headSlot: 160},
|
|
|
|
{finalizedEpoch: 3, headSlot: 160},
|
|
|
|
{finalizedEpoch: 3, headSlot: 160},
|
|
|
|
{finalizedEpoch: 8, headSlot: 320},
|
|
|
|
{finalizedEpoch: 8, headSlot: 320},
|
|
|
|
{finalizedEpoch: 10, headSlot: 320},
|
|
|
|
{finalizedEpoch: 10, headSlot: 320},
|
|
|
|
{finalizedEpoch: 10, headSlot: 320},
|
|
|
|
{finalizedEpoch: 15, headSlot: 640},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, tt := range tests {
|
|
|
|
t.Run(tt.name, func(t *testing.T) {
|
2021-02-16 07:45:34 +00:00
|
|
|
mc, p2p, _ := initializeTestServices(t, []types.Slot{}, tt.peers)
|
2020-10-31 21:33:57 +00:00
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
defer cancel()
|
|
|
|
fetcher := newBlocksFetcher(
|
|
|
|
ctx,
|
|
|
|
&blocksFetcherConfig{
|
|
|
|
chain: mc,
|
|
|
|
p2p: p2p,
|
|
|
|
},
|
|
|
|
)
|
|
|
|
mc.FinalizedCheckPoint = ð.Checkpoint{
|
|
|
|
Epoch: tt.ourFinalizedEpoch,
|
|
|
|
}
|
|
|
|
require.NoError(t, mc.State.SetSlot(tt.ourHeadSlot))
|
|
|
|
fetcher.mode = tt.syncMode
|
|
|
|
|
|
|
|
// Head and target epochs calculation.
|
|
|
|
headEpoch, targetEpoch, peers := fetcher.calculateHeadAndTargetEpochs()
|
|
|
|
assert.Equal(t, tt.expectedHeadEpoch, headEpoch, "Unexpected head epoch")
|
|
|
|
assert.Equal(t, tt.targetEpoch, targetEpoch, "Unexpected target epoch")
|
|
|
|
assert.Equal(t, tt.targetEpochSupport, len(peers), "Unexpected number of peers supporting target epoch")
|
|
|
|
|
|
|
|
// Best finalized and non-finalized slots.
|
2021-02-16 07:45:34 +00:00
|
|
|
finalizedSlot := params.BeaconConfig().SlotsPerEpoch.Mul(uint64(tt.targetEpoch))
|
2020-10-31 21:33:57 +00:00
|
|
|
if tt.syncMode == modeStopOnFinalizedEpoch {
|
|
|
|
assert.Equal(t, finalizedSlot, fetcher.bestFinalizedSlot(), "Unexpected finalized slot")
|
|
|
|
} else {
|
|
|
|
assert.Equal(t, finalizedSlot, fetcher.bestNonFinalizedSlot(), "Unexpected non-finalized slot")
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
2020-10-26 08:54:06 +00:00
|
|
|
}
|