github.com/lazyledger/lazyledger-core@v0.35.0-dev.0.20210613111200-4c651f053571/blockchain/v0/pool_test.go (about) 1 package v0 2 3 import ( 4 "fmt" 5 mrand "math/rand" 6 "testing" 7 "time" 8 9 "github.com/stretchr/testify/assert" 10 "github.com/stretchr/testify/require" 11 12 "github.com/lazyledger/lazyledger-core/libs/log" 13 tmrand "github.com/lazyledger/lazyledger-core/libs/rand" 14 "github.com/lazyledger/lazyledger-core/p2p" 15 "github.com/lazyledger/lazyledger-core/types" 16 ) 17 18 func init() { 19 peerTimeout = 2 * time.Second 20 } 21 22 type testPeer struct { 23 id p2p.ID 24 base int64 25 height int64 26 inputChan chan inputData // make sure each peer's data is sequential 27 } 28 29 type inputData struct { 30 t *testing.T 31 pool *BlockPool 32 request BlockRequest 33 } 34 35 func (p testPeer) runInputRoutine() { 36 go func() { 37 for input := range p.inputChan { 38 p.simulateInput(input) 39 } 40 }() 41 } 42 43 // Request desired, pretend like we got the block immediately. 44 func (p testPeer) simulateInput(input inputData) { 45 block := &types.Block{Header: types.Header{Height: input.request.Height}} 46 input.pool.AddBlock(input.request.PeerID, block, 123) 47 // TODO: uncommenting this creates a race which is detected by: 48 // https://github.com/golang/go/blob/2bd767b1022dd3254bcec469f0ee164024726486/src/testing/testing.go#L854-L856 49 // see: https://github.com/tendermint/tendermint/issues/3390#issue-418379890 50 // input.t.Logf("Added block from peer %v (height: %v)", input.request.PeerID, input.request.Height) 51 } 52 53 type testPeers map[p2p.ID]testPeer 54 55 func (ps testPeers) start() { 56 for _, v := range ps { 57 v.runInputRoutine() 58 } 59 } 60 61 func (ps testPeers) stop() { 62 for _, v := range ps { 63 close(v.inputChan) 64 } 65 } 66 67 func makePeers(numPeers int, minHeight, maxHeight int64) testPeers { 68 peers := make(testPeers, numPeers) 69 for i := 0; i < numPeers; i++ { 70 peerID := p2p.ID(tmrand.Str(12)) 71 height := minHeight + mrand.Int63n(maxHeight-minHeight) 72 base := minHeight + int64(i) 73 if base > height { 74 base = height 75 } 76 peers[peerID] = testPeer{peerID, base, height, make(chan inputData, 10)} 77 } 78 return peers 79 } 80 81 func TestBlockPoolBasic(t *testing.T) { 82 start := int64(42) 83 peers := makePeers(10, start+1, 1000) 84 errorsCh := make(chan peerError, 1000) 85 requestsCh := make(chan BlockRequest, 1000) 86 pool := NewBlockPool(start, requestsCh, errorsCh) 87 pool.SetLogger(log.TestingLogger()) 88 89 err := pool.Start() 90 if err != nil { 91 t.Error(err) 92 } 93 94 t.Cleanup(func() { 95 if err := pool.Stop(); err != nil { 96 t.Error(err) 97 } 98 }) 99 100 peers.start() 101 defer peers.stop() 102 103 // Introduce each peer. 104 go func() { 105 for _, peer := range peers { 106 pool.SetPeerRange(peer.id, peer.base, peer.height) 107 } 108 }() 109 110 // Start a goroutine to pull blocks 111 go func() { 112 for { 113 if !pool.IsRunning() { 114 return 115 } 116 first, second := pool.PeekTwoBlocks() 117 if first != nil && second != nil { 118 pool.PopRequest() 119 } else { 120 time.Sleep(1 * time.Second) 121 } 122 } 123 }() 124 125 // Pull from channels 126 for { 127 select { 128 case err := <-errorsCh: 129 t.Error(err) 130 case request := <-requestsCh: 131 t.Logf("Pulled new BlockRequest %v", request) 132 if request.Height == 300 { 133 return // Done! 134 } 135 136 peers[request.PeerID].inputChan <- inputData{t, pool, request} 137 } 138 } 139 } 140 141 func TestBlockPoolTimeout(t *testing.T) { 142 start := int64(42) 143 peers := makePeers(10, start+1, 1000) 144 errorsCh := make(chan peerError, 1000) 145 requestsCh := make(chan BlockRequest, 1000) 146 pool := NewBlockPool(start, requestsCh, errorsCh) 147 pool.SetLogger(log.TestingLogger()) 148 err := pool.Start() 149 if err != nil { 150 t.Error(err) 151 } 152 t.Cleanup(func() { 153 if err := pool.Stop(); err != nil { 154 t.Error(err) 155 } 156 }) 157 158 for _, peer := range peers { 159 t.Logf("Peer %v", peer.id) 160 } 161 162 // Introduce each peer. 163 go func() { 164 for _, peer := range peers { 165 pool.SetPeerRange(peer.id, peer.base, peer.height) 166 } 167 }() 168 169 // Start a goroutine to pull blocks 170 go func() { 171 for { 172 if !pool.IsRunning() { 173 return 174 } 175 first, second := pool.PeekTwoBlocks() 176 if first != nil && second != nil { 177 pool.PopRequest() 178 } else { 179 time.Sleep(1 * time.Second) 180 } 181 } 182 }() 183 184 // Pull from channels 185 counter := 0 186 timedOut := map[p2p.ID]struct{}{} 187 for { 188 select { 189 case err := <-errorsCh: 190 t.Log(err) 191 // consider error to be always timeout here 192 if _, ok := timedOut[err.peerID]; !ok { 193 counter++ 194 if counter == len(peers) { 195 return // Done! 196 } 197 } 198 case request := <-requestsCh: 199 t.Logf("Pulled new BlockRequest %+v", request) 200 } 201 } 202 } 203 204 func TestBlockPoolRemovePeer(t *testing.T) { 205 peers := make(testPeers, 10) 206 for i := 0; i < 10; i++ { 207 peerID := p2p.ID(fmt.Sprintf("%d", i+1)) 208 height := int64(i + 1) 209 peers[peerID] = testPeer{peerID, 0, height, make(chan inputData)} 210 } 211 requestsCh := make(chan BlockRequest) 212 errorsCh := make(chan peerError) 213 214 pool := NewBlockPool(1, requestsCh, errorsCh) 215 pool.SetLogger(log.TestingLogger()) 216 err := pool.Start() 217 require.NoError(t, err) 218 t.Cleanup(func() { 219 if err := pool.Stop(); err != nil { 220 t.Error(err) 221 } 222 }) 223 224 // add peers 225 for peerID, peer := range peers { 226 pool.SetPeerRange(peerID, peer.base, peer.height) 227 } 228 assert.EqualValues(t, 10, pool.MaxPeerHeight()) 229 230 // remove not-existing peer 231 assert.NotPanics(t, func() { pool.RemovePeer(p2p.ID("Superman")) }) 232 233 // remove peer with biggest height 234 pool.RemovePeer(p2p.ID("10")) 235 assert.EqualValues(t, 9, pool.MaxPeerHeight()) 236 237 // remove all peers 238 for peerID := range peers { 239 pool.RemovePeer(peerID) 240 } 241 242 assert.EqualValues(t, 0, pool.MaxPeerHeight()) 243 }