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  }