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