github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/sync/initial-sync/initial_sync_test.go (about)

     1  package initialsync
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"sync"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/ethereum/go-ethereum/p2p/enr"
    12  	"github.com/libp2p/go-libp2p-core/network"
    13  	"github.com/libp2p/go-libp2p-core/peer"
    14  	types "github.com/prysmaticlabs/eth2-types"
    15  	mock "github.com/prysmaticlabs/prysm/beacon-chain/blockchain/testing"
    16  	"github.com/prysmaticlabs/prysm/beacon-chain/core/helpers"
    17  	"github.com/prysmaticlabs/prysm/beacon-chain/db"
    18  	dbtest "github.com/prysmaticlabs/prysm/beacon-chain/db/testing"
    19  	"github.com/prysmaticlabs/prysm/beacon-chain/p2p/peers"
    20  	p2pt "github.com/prysmaticlabs/prysm/beacon-chain/p2p/testing"
    21  	p2pTypes "github.com/prysmaticlabs/prysm/beacon-chain/p2p/types"
    22  	beaconsync "github.com/prysmaticlabs/prysm/beacon-chain/sync"
    23  	"github.com/prysmaticlabs/prysm/cmd/beacon-chain/flags"
    24  	p2ppb "github.com/prysmaticlabs/prysm/proto/beacon/p2p/v1"
    25  	eth "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1"
    26  	"github.com/prysmaticlabs/prysm/proto/eth/v1alpha1/wrapper"
    27  	"github.com/prysmaticlabs/prysm/shared/bytesutil"
    28  	"github.com/prysmaticlabs/prysm/shared/featureconfig"
    29  	"github.com/prysmaticlabs/prysm/shared/hashutil"
    30  	"github.com/prysmaticlabs/prysm/shared/params"
    31  	"github.com/prysmaticlabs/prysm/shared/sliceutil"
    32  	"github.com/prysmaticlabs/prysm/shared/testutil"
    33  	"github.com/prysmaticlabs/prysm/shared/testutil/assert"
    34  	"github.com/prysmaticlabs/prysm/shared/testutil/require"
    35  	"github.com/prysmaticlabs/prysm/shared/timeutils"
    36  	"github.com/sirupsen/logrus"
    37  )
    38  
    39  type testCache struct {
    40  	sync.RWMutex
    41  	rootCache       map[types.Slot][32]byte
    42  	parentSlotCache map[types.Slot]types.Slot
    43  }
    44  
    45  var cache = &testCache{}
    46  
    47  type peerData struct {
    48  	blocks         []types.Slot // slots that peer has blocks
    49  	finalizedEpoch types.Epoch
    50  	headSlot       types.Slot
    51  	failureSlots   []types.Slot // slots at which the peer will return an error
    52  	forkedPeer     bool
    53  }
    54  
    55  func TestMain(m *testing.M) {
    56  	logrus.SetLevel(logrus.DebugLevel)
    57  	logrus.SetOutput(ioutil.Discard)
    58  
    59  	resetCfg := featureconfig.InitWithReset(&featureconfig.Flags{
    60  		EnablePeerScorer: true,
    61  	})
    62  	defer resetCfg()
    63  
    64  	resetFlags := flags.Get()
    65  	flags.Init(&flags.GlobalFlags{
    66  		BlockBatchLimit:            64,
    67  		BlockBatchLimitBurstFactor: 10,
    68  	})
    69  	defer func() {
    70  		flags.Init(resetFlags)
    71  	}()
    72  
    73  	m.Run()
    74  }
    75  
    76  func initializeTestServices(t *testing.T, slots []types.Slot, peers []*peerData) (*mock.ChainService, *p2pt.TestP2P, db.Database) {
    77  	cache.initializeRootCache(slots, t)
    78  	beaconDB := dbtest.SetupDB(t)
    79  
    80  	p := p2pt.NewTestP2P(t)
    81  	connectPeers(t, p, peers, p.Peers())
    82  	cache.RLock()
    83  	genesisRoot := cache.rootCache[0]
    84  	cache.RUnlock()
    85  
    86  	err := beaconDB.SaveBlock(context.Background(), wrapper.WrappedPhase0SignedBeaconBlock(testutil.NewBeaconBlock()))
    87  	require.NoError(t, err)
    88  
    89  	st, err := testutil.NewBeaconState()
    90  	require.NoError(t, err)
    91  
    92  	return &mock.ChainService{
    93  		State: st,
    94  		Root:  genesisRoot[:],
    95  		DB:    beaconDB,
    96  		FinalizedCheckPoint: &eth.Checkpoint{
    97  			Epoch: 0,
    98  		},
    99  	}, p, beaconDB
   100  }
   101  
   102  // makeGenesisTime where now is the current slot.
   103  func makeGenesisTime(currentSlot types.Slot) time.Time {
   104  	return timeutils.Now().Add(-1 * time.Second * time.Duration(currentSlot) * time.Duration(params.BeaconConfig().SecondsPerSlot))
   105  }
   106  
   107  // sanity test on helper function
   108  func TestMakeGenesisTime(t *testing.T) {
   109  	currentSlot := types.Slot(64)
   110  	gt := makeGenesisTime(currentSlot)
   111  	require.Equal(t, currentSlot, helpers.SlotsSince(gt))
   112  }
   113  
   114  // helper function for sequences of block slots
   115  func makeSequence(start, end types.Slot) []types.Slot {
   116  	if end < start {
   117  		panic("cannot make sequence where end is before start")
   118  	}
   119  	seq := make([]types.Slot, 0, end-start+1)
   120  	for i := start; i <= end; i++ {
   121  		seq = append(seq, i)
   122  	}
   123  	return seq
   124  }
   125  
   126  func (c *testCache) initializeRootCache(reqSlots []types.Slot, t *testing.T) {
   127  	c.Lock()
   128  	defer c.Unlock()
   129  
   130  	c.rootCache = make(map[types.Slot][32]byte)
   131  	c.parentSlotCache = make(map[types.Slot]types.Slot)
   132  	parentSlot := types.Slot(0)
   133  
   134  	genesisBlock := testutil.NewBeaconBlock().Block
   135  	genesisRoot, err := genesisBlock.HashTreeRoot()
   136  	require.NoError(t, err)
   137  	c.rootCache[0] = genesisRoot
   138  	parentRoot := genesisRoot
   139  	for _, slot := range reqSlots {
   140  		currentBlock := testutil.NewBeaconBlock().Block
   141  		currentBlock.Slot = slot
   142  		currentBlock.ParentRoot = parentRoot[:]
   143  		parentRoot, err = currentBlock.HashTreeRoot()
   144  		require.NoError(t, err)
   145  		c.rootCache[slot] = parentRoot
   146  		c.parentSlotCache[slot] = parentSlot
   147  		parentSlot = slot
   148  	}
   149  }
   150  
   151  // sanity test on helper function
   152  func TestMakeSequence(t *testing.T) {
   153  	got := makeSequence(3, 5)
   154  	want := []types.Slot{3, 4, 5}
   155  	require.DeepEqual(t, want, got)
   156  }
   157  
   158  // Connect peers with local host. This method sets up peer statuses and the appropriate handlers
   159  // for each test peer.
   160  func connectPeers(t *testing.T, host *p2pt.TestP2P, data []*peerData, peerStatus *peers.Status) {
   161  	for _, d := range data {
   162  		connectPeer(t, host, d, peerStatus)
   163  	}
   164  }
   165  
   166  // connectPeer connects a peer to a local host.
   167  func connectPeer(t *testing.T, host *p2pt.TestP2P, datum *peerData, peerStatus *peers.Status) peer.ID {
   168  	const topic = "/eth2/beacon_chain/req/beacon_blocks_by_range/1/ssz_snappy"
   169  	p := p2pt.NewTestP2P(t)
   170  	p.SetStreamHandler(topic, func(stream network.Stream) {
   171  		defer func() {
   172  			assert.NoError(t, stream.Close())
   173  		}()
   174  
   175  		req := &p2ppb.BeaconBlocksByRangeRequest{}
   176  		assert.NoError(t, p.Encoding().DecodeWithMaxLength(stream, req))
   177  
   178  		requestedBlocks := makeSequence(req.StartSlot, req.StartSlot.Add((req.Count-1)*req.Step))
   179  
   180  		// Expected failure range
   181  		if len(sliceutil.IntersectionSlot(datum.failureSlots, requestedBlocks)) > 0 {
   182  			_, err := stream.Write([]byte{0x01})
   183  			assert.NoError(t, err)
   184  			msg := p2pTypes.ErrorMessage("bad")
   185  			_, err = p.Encoding().EncodeWithMaxLength(stream, &msg)
   186  			assert.NoError(t, err)
   187  			return
   188  		}
   189  
   190  		// Determine the correct subset of blocks to return as dictated by the test scenario.
   191  		slots := sliceutil.IntersectionSlot(datum.blocks, requestedBlocks)
   192  
   193  		ret := make([]*eth.SignedBeaconBlock, 0)
   194  		for _, slot := range slots {
   195  			if (slot - req.StartSlot).Mod(req.Step) != 0 {
   196  				continue
   197  			}
   198  			cache.RLock()
   199  			parentRoot := cache.rootCache[cache.parentSlotCache[slot]]
   200  			cache.RUnlock()
   201  			blk := testutil.NewBeaconBlock()
   202  			blk.Block.Slot = slot
   203  			blk.Block.ParentRoot = parentRoot[:]
   204  			// If forked peer, give a different parent root.
   205  			if datum.forkedPeer {
   206  				newRoot := hashutil.Hash(parentRoot[:])
   207  				blk.Block.ParentRoot = newRoot[:]
   208  			}
   209  			ret = append(ret, blk)
   210  			currRoot, err := blk.Block.HashTreeRoot()
   211  			require.NoError(t, err)
   212  			logrus.Tracef("block with slot %d , signing root %#x and parent root %#x", slot, currRoot, parentRoot)
   213  		}
   214  
   215  		if uint64(len(ret)) > req.Count {
   216  			ret = ret[:req.Count]
   217  		}
   218  
   219  		for i := 0; i < len(ret); i++ {
   220  			assert.NoError(t, beaconsync.WriteChunk(stream, nil, p.Encoding(), ret[i]))
   221  		}
   222  	})
   223  
   224  	p.Connect(host)
   225  
   226  	peerStatus.Add(new(enr.Record), p.PeerID(), nil, network.DirOutbound)
   227  	peerStatus.SetConnectionState(p.PeerID(), peers.PeerConnected)
   228  	peerStatus.SetChainState(p.PeerID(), &p2ppb.Status{
   229  		ForkDigest:     params.BeaconConfig().GenesisForkVersion,
   230  		FinalizedRoot:  []byte(fmt.Sprintf("finalized_root %d", datum.finalizedEpoch)),
   231  		FinalizedEpoch: datum.finalizedEpoch,
   232  		HeadRoot:       bytesutil.PadTo([]byte("head_root"), 32),
   233  		HeadSlot:       datum.headSlot,
   234  	})
   235  
   236  	return p.PeerID()
   237  }
   238  
   239  // extendBlockSequence extends block chain sequentially (creating genesis block, if necessary).
   240  func extendBlockSequence(t *testing.T, inSeq []*eth.SignedBeaconBlock, size int) []*eth.SignedBeaconBlock {
   241  	// Start from the original sequence.
   242  	outSeq := make([]*eth.SignedBeaconBlock, len(inSeq)+size)
   243  	copy(outSeq, inSeq)
   244  
   245  	// See if genesis block needs to be created.
   246  	startSlot := len(inSeq)
   247  	if len(inSeq) == 0 {
   248  		outSeq[0] = testutil.NewBeaconBlock()
   249  		outSeq[0].Block.StateRoot = testutil.Random32Bytes(t)
   250  		startSlot++
   251  		outSeq = append(outSeq, nil)
   252  	}
   253  
   254  	// Extend block chain sequentially.
   255  	for slot := startSlot; slot < len(outSeq); slot++ {
   256  		outSeq[slot] = testutil.NewBeaconBlock()
   257  		outSeq[slot].Block.Slot = types.Slot(slot)
   258  		parentRoot, err := outSeq[slot-1].Block.HashTreeRoot()
   259  		require.NoError(t, err)
   260  		outSeq[slot].Block.ParentRoot = parentRoot[:]
   261  		// Make sure that blocks having the same slot number, produce different hashes.
   262  		// That way different branches/forks will have different blocks for the same slots.
   263  		outSeq[slot].Block.StateRoot = testutil.Random32Bytes(t)
   264  	}
   265  
   266  	return outSeq
   267  }
   268  
   269  // connectPeerHavingBlocks connect host with a peer having provided blocks.
   270  func connectPeerHavingBlocks(
   271  	t *testing.T, host *p2pt.TestP2P, blocks []*eth.SignedBeaconBlock, finalizedSlot types.Slot,
   272  	peerStatus *peers.Status,
   273  ) peer.ID {
   274  	p := p2pt.NewTestP2P(t)
   275  
   276  	p.SetStreamHandler("/eth2/beacon_chain/req/beacon_blocks_by_range/1/ssz_snappy", func(stream network.Stream) {
   277  		defer func() {
   278  			_err := stream.Close()
   279  			_ = _err
   280  		}()
   281  
   282  		req := &p2ppb.BeaconBlocksByRangeRequest{}
   283  		assert.NoError(t, p.Encoding().DecodeWithMaxLength(stream, req))
   284  
   285  		for i := req.StartSlot; i < req.StartSlot.Add(req.Count*req.Step); i += types.Slot(req.Step) {
   286  			if uint64(i) >= uint64(len(blocks)) {
   287  				break
   288  			}
   289  			require.NoError(t, beaconsync.WriteChunk(stream, nil, p.Encoding(), blocks[i]))
   290  		}
   291  	})
   292  
   293  	p.SetStreamHandler("/eth2/beacon_chain/req/beacon_blocks_by_root/1/ssz_snappy", func(stream network.Stream) {
   294  		defer func() {
   295  			_err := stream.Close()
   296  			_ = _err
   297  		}()
   298  
   299  		req := new(p2pTypes.BeaconBlockByRootsReq)
   300  		assert.NoError(t, p.Encoding().DecodeWithMaxLength(stream, req))
   301  		if len(*req) == 0 {
   302  			return
   303  		}
   304  		for _, expectedRoot := range *req {
   305  			for _, blk := range blocks {
   306  				if root, err := blk.Block.HashTreeRoot(); err == nil && expectedRoot == root {
   307  					log.Printf("Found blocks_by_root: %#x for slot: %v", root, blk.Block.Slot)
   308  					_, err := stream.Write([]byte{0x00})
   309  					assert.NoError(t, err, "Failed to write to stream")
   310  					_, err = p.Encoding().EncodeWithMaxLength(stream, blk)
   311  					assert.NoError(t, err, "Could not send response back")
   312  				}
   313  			}
   314  		}
   315  	})
   316  
   317  	p.Connect(host)
   318  
   319  	finalizedEpoch := helpers.SlotToEpoch(finalizedSlot)
   320  	headRoot, err := blocks[len(blocks)-1].Block.HashTreeRoot()
   321  	require.NoError(t, err)
   322  
   323  	peerStatus.Add(new(enr.Record), p.PeerID(), nil, network.DirOutbound)
   324  	peerStatus.SetConnectionState(p.PeerID(), peers.PeerConnected)
   325  	peerStatus.SetChainState(p.PeerID(), &p2ppb.Status{
   326  		ForkDigest:     params.BeaconConfig().GenesisForkVersion,
   327  		FinalizedRoot:  []byte(fmt.Sprintf("finalized_root %d", finalizedEpoch)),
   328  		FinalizedEpoch: finalizedEpoch,
   329  		HeadRoot:       headRoot[:],
   330  		HeadSlot:       blocks[len(blocks)-1].Block.Slot,
   331  	})
   332  
   333  	return p.PeerID()
   334  }