github.com/koko1123/flow-go-1@v0.29.6/engine/verification/assigner/blockconsumer/consumer_test.go (about) 1 package blockconsumer_test 2 3 import ( 4 "sync" 5 "testing" 6 "time" 7 8 "github.com/dgraph-io/badger/v3" 9 "github.com/stretchr/testify/require" 10 11 "github.com/koko1123/flow-go-1/consensus/hotstuff/model" 12 "github.com/koko1123/flow-go-1/engine/testutil" 13 "github.com/koko1123/flow-go-1/engine/verification/assigner/blockconsumer" 14 vertestutils "github.com/koko1123/flow-go-1/engine/verification/utils/unittest" 15 "github.com/koko1123/flow-go-1/model/flow" 16 "github.com/koko1123/flow-go-1/model/flow/filter" 17 "github.com/koko1123/flow-go-1/module" 18 "github.com/koko1123/flow-go-1/module/jobqueue" 19 "github.com/koko1123/flow-go-1/module/metrics" 20 "github.com/koko1123/flow-go-1/module/trace" 21 bstorage "github.com/koko1123/flow-go-1/storage/badger" 22 "github.com/koko1123/flow-go-1/utils/unittest" 23 ) 24 25 // TestBlockToJob evaluates that a block can be converted to a job, 26 // and its corresponding job can be converted back to the same block. 27 func TestBlockToJob(t *testing.T) { 28 block := unittest.BlockFixture() 29 actual, err := jobqueue.JobToBlock(jobqueue.BlockToJob(&block)) 30 require.NoError(t, err) 31 require.Equal(t, &block, actual) 32 } 33 34 func TestProduceConsume(t *testing.T) { 35 // pushing 10 finalized blocks sequentially to block reader, with 3 workers on consumer and the block processor 36 // blocking on the blocks, results in processor only receiving the first three finalized blocks: 37 // 10 blocks sequentially --> block reader --> consumer can read and push 3 blocks at a time to processor --> blocking processor. 38 t.Run("pushing 10 blocks, blocking, receives 3", func(t *testing.T) { 39 received := make([]*flow.Block, 0) 40 lock := &sync.Mutex{} 41 neverFinish := func(notifier module.ProcessingNotifier, block *flow.Block) { 42 lock.Lock() 43 defer lock.Unlock() 44 received = append(received, block) 45 46 // this processor never notifies consumer that it is done with the block. 47 // hence from consumer perspective, it is blocking on each received block. 48 } 49 50 withConsumer(t, 10, 3, neverFinish, func(consumer *blockconsumer.BlockConsumer, blocks []*flow.Block) { 51 unittest.RequireCloseBefore(t, consumer.Ready(), time.Second, "could not start consumer") 52 53 for i := 0; i < len(blocks); i++ { 54 // consumer is only required to be "notified" that a new finalized block available. 55 // It keeps track of the last finalized block it has read, and read the next height upon 56 // getting notified as follows: 57 consumer.OnFinalizedBlock(&model.Block{}) 58 } 59 60 unittest.RequireCloseBefore(t, consumer.Done(), time.Second, "could not terminate consumer") 61 62 // expects the processor receive only the first 3 blocks (since it is blocked on those, hence no 63 // new block is fetched to process). 64 require.ElementsMatch(t, flow.GetIDs(blocks[:3]), flow.GetIDs(received)) 65 }) 66 }) 67 68 // pushing 10 finalized blocks sequentially to block reader, with 3 workers on consumer and the processor finishes processing 69 // each received block immediately, results in processor receiving all 10 blocks: 70 // 10 blocks sequentially --> block reader --> consumer can read and push 3 blocks at a time to processor --> processor finishes blocks 71 // immediately. 72 t.Run("pushing 100 blocks, non-blocking, receives 100", func(t *testing.T) { 73 received := make([]*flow.Block, 0) 74 lock := &sync.Mutex{} 75 var processAll sync.WaitGroup 76 alwaysFinish := func(notifier module.ProcessingNotifier, block *flow.Block) { 77 lock.Lock() 78 defer lock.Unlock() 79 80 received = append(received, block) 81 82 go func() { 83 notifier.Notify(block.ID()) 84 processAll.Done() 85 }() 86 } 87 88 withConsumer(t, 100, 3, alwaysFinish, func(consumer *blockconsumer.BlockConsumer, blocks []*flow.Block) { 89 unittest.RequireCloseBefore(t, consumer.Ready(), time.Second, "could not start consumer") 90 processAll.Add(len(blocks)) 91 92 for i := 0; i < len(blocks); i++ { 93 // consumer is only required to be "notified" that a new finalized block available. 94 // It keeps track of the last finalized block it has read, and read the next height upon 95 // getting notified as follows: 96 consumer.OnFinalizedBlock(&model.Block{}) 97 } 98 99 // waits until all blocks finish processing 100 unittest.RequireReturnsBefore(t, processAll.Wait, time.Second, "could not process all blocks on time") 101 unittest.RequireCloseBefore(t, consumer.Done(), time.Second, "could not terminate consumer") 102 103 // expects the mock engine receive all 100 blocks. 104 require.ElementsMatch(t, flow.GetIDs(blocks), flow.GetIDs(received)) 105 }) 106 }) 107 108 } 109 110 // withConsumer is a test helper that sets up a block consumer with specified number of workers. 111 // The block consumer operates on a block reader with a chain of specified number of finalized blocks 112 // ready to read and consumer. 113 func withConsumer( 114 t *testing.T, 115 blockCount int, 116 workerCount int, 117 process func(notifier module.ProcessingNotifier, block *flow.Block), 118 withBlockConsumer func(*blockconsumer.BlockConsumer, []*flow.Block), 119 ) { 120 unittest.RunWithBadgerDB(t, func(db *badger.DB) { 121 maxProcessing := uint64(workerCount) 122 123 processedHeight := bstorage.NewConsumerProgress(db, module.ConsumeProgressVerificationBlockHeight) 124 collector := &metrics.NoopCollector{} 125 tracer := trace.NewNoopTracer() 126 participants := unittest.IdentityListFixture(5, unittest.WithAllRoles()) 127 rootSnapshot := unittest.RootSnapshotFixture(participants) 128 s := testutil.CompleteStateFixture(t, collector, tracer, rootSnapshot) 129 130 engine := &mockBlockProcessor{ 131 process: process, 132 } 133 134 consumer, _, err := blockconsumer.NewBlockConsumer( 135 unittest.Logger(), 136 collector, 137 processedHeight, 138 s.Storage.Blocks, 139 s.State, 140 engine, 141 maxProcessing) 142 require.NoError(t, err) 143 144 // generates a chain of blocks in the form of root <- R1 <- C1 <- R2 <- C2 <- ... where Rs are distinct reference 145 // blocks (i.e., containing guarantees), and Cs are container blocks for their preceding reference block, 146 // Container blocks only contain receipts of their preceding reference blocks. But they do not 147 // hold any guarantees. 148 root, err := s.State.Params().Root() 149 require.NoError(t, err) 150 clusterCommittee := participants.Filter(filter.HasRole(flow.RoleCollection)) 151 results := vertestutils.CompleteExecutionReceiptChainFixture(t, root, blockCount/2, vertestutils.WithClusterCommittee(clusterCommittee)) 152 blocks := vertestutils.ExtendStateWithFinalizedBlocks(t, results, s.State) 153 // makes sure that we generated a block chain of requested length. 154 require.Len(t, blocks, blockCount) 155 156 withBlockConsumer(consumer, blocks) 157 }) 158 } 159 160 // mockBlockProcessor provides a FinalizedBlockProcessor with a plug-and-play process method. 161 type mockBlockProcessor struct { 162 notifier module.ProcessingNotifier 163 process func(module.ProcessingNotifier, *flow.Block) 164 } 165 166 func (e *mockBlockProcessor) ProcessFinalizedBlock(block *flow.Block) { 167 e.process(e.notifier, block) 168 } 169 170 func (e *mockBlockProcessor) WithBlockConsumerNotifier(notifier module.ProcessingNotifier) { 171 e.notifier = notifier 172 }