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