github.com/onflow/flow-go@v0.35.7-crescendo-preview.23-atree-inlining/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/v2" 9 "github.com/stretchr/testify/require" 10 11 "github.com/onflow/flow-go/consensus/hotstuff/model" 12 "github.com/onflow/flow-go/engine/testutil" 13 "github.com/onflow/flow-go/engine/verification/assigner/blockconsumer" 14 vertestutils "github.com/onflow/flow-go/engine/verification/utils/unittest" 15 "github.com/onflow/flow-go/model/flow" 16 "github.com/onflow/flow-go/model/flow/filter" 17 "github.com/onflow/flow-go/module" 18 "github.com/onflow/flow-go/module/jobqueue" 19 "github.com/onflow/flow-go/module/metrics" 20 "github.com/onflow/flow-go/module/trace" 21 bstorage "github.com/onflow/flow-go/storage/badger" 22 "github.com/onflow/flow-go/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 log := unittest.Logger() 127 participants := unittest.IdentityListFixture(5, unittest.WithAllRoles()) 128 rootSnapshot := unittest.RootSnapshotFixture(participants) 129 s := testutil.CompleteStateFixture(t, log, collector, tracer, rootSnapshot) 130 131 engine := &mockBlockProcessor{ 132 process: process, 133 } 134 135 consumer, _, err := blockconsumer.NewBlockConsumer( 136 unittest.Logger(), 137 collector, 138 processedHeight, 139 s.Storage.Blocks, 140 s.State, 141 engine, 142 maxProcessing) 143 require.NoError(t, err) 144 145 // generates a chain of blocks in the form of root <- R1 <- C1 <- R2 <- C2 <- ... where Rs are distinct reference 146 // blocks (i.e., containing guarantees), and Cs are container blocks for their preceding reference block, 147 // Container blocks only contain receipts of their preceding reference blocks. But they do not 148 // hold any guarantees. 149 root, err := s.State.Final().Head() 150 require.NoError(t, err) 151 rootProtocolState, err := s.State.Final().ProtocolState() 152 require.NoError(t, err) 153 rootProtocolStateID := rootProtocolState.ID() 154 clusterCommittee := participants.Filter(filter.HasRole[flow.Identity](flow.RoleCollection)) 155 sources := unittest.RandomSourcesFixture(110) 156 results := vertestutils.CompleteExecutionReceiptChainFixture( 157 t, 158 root, 159 rootProtocolStateID, 160 blockCount/2, 161 sources, 162 vertestutils.WithClusterCommittee(clusterCommittee), 163 ) 164 blocks := vertestutils.ExtendStateWithFinalizedBlocks(t, results, s.State) 165 // makes sure that we generated a block chain of requested length. 166 require.Len(t, blocks, blockCount) 167 168 withBlockConsumer(consumer, blocks) 169 }) 170 } 171 172 // mockBlockProcessor provides a FinalizedBlockProcessor with a plug-and-play process method. 173 type mockBlockProcessor struct { 174 notifier module.ProcessingNotifier 175 process func(module.ProcessingNotifier, *flow.Block) 176 } 177 178 func (e *mockBlockProcessor) ProcessFinalizedBlock(block *flow.Block) { 179 e.process(e.notifier, block) 180 } 181 182 func (e *mockBlockProcessor) WithBlockConsumerNotifier(notifier module.ProcessingNotifier) { 183 e.notifier = notifier 184 }