github.com/evdatsion/aphelion-dpos-bft@v0.32.1/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 cmn "github.com/evdatsion/aphelion-dpos-bft/libs/common" 12 "github.com/evdatsion/aphelion-dpos-bft/libs/log" 13 "github.com/evdatsion/aphelion-dpos-bft/p2p" 14 "github.com/evdatsion/aphelion-dpos-bft/types" 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/evdatsion/aphelion-dpos-bft/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(cmn.RandStr(12)) 68 height := minHeight + cmn.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 start := int64(42) 76 peers := makePeers(10, start+1, 1000) 77 errorsCh := make(chan peerError, 1000) 78 requestsCh := make(chan BlockRequest, 1000) 79 pool := NewBlockPool(start, requestsCh, errorsCh) 80 pool.SetLogger(log.TestingLogger()) 81 82 err := pool.Start() 83 if err != nil { 84 t.Error(err) 85 } 86 87 defer pool.Stop() 88 89 peers.start() 90 defer peers.stop() 91 92 // Introduce each peer. 93 go func() { 94 for _, peer := range peers { 95 pool.SetPeerHeight(peer.id, peer.height) 96 } 97 }() 98 99 // Start a goroutine to pull blocks 100 go func() { 101 for { 102 if !pool.IsRunning() { 103 return 104 } 105 first, second := pool.PeekTwoBlocks() 106 if first != nil && second != nil { 107 pool.PopRequest() 108 } else { 109 time.Sleep(1 * time.Second) 110 } 111 } 112 }() 113 114 // Pull from channels 115 for { 116 select { 117 case err := <-errorsCh: 118 t.Error(err) 119 case request := <-requestsCh: 120 t.Logf("Pulled new BlockRequest %v", request) 121 if request.Height == 300 { 122 return // Done! 123 } 124 125 peers[request.PeerID].inputChan <- inputData{t, pool, request} 126 } 127 } 128 } 129 130 func TestBlockPoolTimeout(t *testing.T) { 131 start := int64(42) 132 peers := makePeers(10, start+1, 1000) 133 errorsCh := make(chan peerError, 1000) 134 requestsCh := make(chan BlockRequest, 1000) 135 pool := NewBlockPool(start, requestsCh, errorsCh) 136 pool.SetLogger(log.TestingLogger()) 137 err := pool.Start() 138 if err != nil { 139 t.Error(err) 140 } 141 defer pool.Stop() 142 143 for _, peer := range peers { 144 t.Logf("Peer %v", peer.id) 145 } 146 147 // Introduce each peer. 148 go func() { 149 for _, peer := range peers { 150 pool.SetPeerHeight(peer.id, peer.height) 151 } 152 }() 153 154 // Start a goroutine to pull blocks 155 go func() { 156 for { 157 if !pool.IsRunning() { 158 return 159 } 160 first, second := pool.PeekTwoBlocks() 161 if first != nil && second != nil { 162 pool.PopRequest() 163 } else { 164 time.Sleep(1 * time.Second) 165 } 166 } 167 }() 168 169 // Pull from channels 170 counter := 0 171 timedOut := map[p2p.ID]struct{}{} 172 for { 173 select { 174 case err := <-errorsCh: 175 t.Log(err) 176 // consider error to be always timeout here 177 if _, ok := timedOut[err.peerID]; !ok { 178 counter++ 179 if counter == len(peers) { 180 return // Done! 181 } 182 } 183 case request := <-requestsCh: 184 t.Logf("Pulled new BlockRequest %+v", request) 185 } 186 } 187 } 188 189 func TestBlockPoolRemovePeer(t *testing.T) { 190 peers := make(testPeers, 10) 191 for i := 0; i < 10; i++ { 192 peerID := p2p.ID(fmt.Sprintf("%d", i+1)) 193 height := int64(i + 1) 194 peers[peerID] = testPeer{peerID, height, make(chan inputData)} 195 } 196 requestsCh := make(chan BlockRequest) 197 errorsCh := make(chan peerError) 198 199 pool := NewBlockPool(1, requestsCh, errorsCh) 200 pool.SetLogger(log.TestingLogger()) 201 err := pool.Start() 202 require.NoError(t, err) 203 defer pool.Stop() 204 205 // add peers 206 for peerID, peer := range peers { 207 pool.SetPeerHeight(peerID, peer.height) 208 } 209 assert.EqualValues(t, 10, pool.MaxPeerHeight()) 210 211 // remove not-existing peer 212 assert.NotPanics(t, func() { pool.RemovePeer(p2p.ID("Superman")) }) 213 214 // remove peer with biggest height 215 pool.RemovePeer(p2p.ID("10")) 216 assert.EqualValues(t, 9, pool.MaxPeerHeight()) 217 218 // remove all peers 219 for peerID := range peers { 220 pool.RemovePeer(peerID) 221 } 222 223 assert.EqualValues(t, 0, pool.MaxPeerHeight()) 224 }