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: ð.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 }