github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/orderer/consensus/kafka/chain_test.go (about)

     1  /*
     2  Copyright hechain. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package kafka
     8  
     9  import (
    10  	"context"
    11  	"errors"
    12  	"fmt"
    13  	"testing"
    14  	"time"
    15  
    16  	"github.com/Shopify/sarama"
    17  	"github.com/Shopify/sarama/mocks"
    18  	"github.com/golang/protobuf/proto"
    19  	"github.com/hechain20/hechain/common/channelconfig"
    20  	"github.com/hechain20/hechain/common/metrics/disabled"
    21  	"github.com/hechain20/hechain/orderer/common/blockcutter"
    22  	"github.com/hechain20/hechain/orderer/common/msgprocessor"
    23  	mockkafka "github.com/hechain20/hechain/orderer/consensus/kafka/mock"
    24  	mockblockcutter "github.com/hechain20/hechain/orderer/mocks/common/blockcutter"
    25  	mockmultichannel "github.com/hechain20/hechain/orderer/mocks/common/multichannel"
    26  	"github.com/hechain20/hechain/protoutil"
    27  	cb "github.com/hyperledger/fabric-protos-go/common"
    28  	ab "github.com/hyperledger/fabric-protos-go/orderer"
    29  	. "github.com/onsi/gomega"
    30  	"github.com/stretchr/testify/mock"
    31  	"github.com/stretchr/testify/require"
    32  )
    33  
    34  //go:generate counterfeiter -o mock/orderer_capabilities.go --fake-name OrdererCapabilities . ordererCapabilities
    35  
    36  type ordererCapabilities interface {
    37  	channelconfig.OrdererCapabilities
    38  }
    39  
    40  //go:generate counterfeiter -o mock/channel_capabilities.go --fake-name ChannelCapabilities . channelCapabilities
    41  
    42  type channelCapabilities interface {
    43  	channelconfig.ChannelCapabilities
    44  }
    45  
    46  //go:generate counterfeiter -o mock/channel_config.go --fake-name ChannelConfig . channelConfig
    47  
    48  type channelConfig interface {
    49  	channelconfig.Channel
    50  }
    51  
    52  func newMockOrderer(batchTimeout time.Duration, brokers []string, resubmission bool) *mockkafka.OrdererConfig {
    53  	mockCapabilities := &mockkafka.OrdererCapabilities{}
    54  	mockCapabilities.ResubmissionReturns(resubmission)
    55  	mockOrderer := &mockkafka.OrdererConfig{}
    56  	mockOrderer.CapabilitiesReturns(mockCapabilities)
    57  	mockOrderer.BatchTimeoutReturns(batchTimeout)
    58  	mockOrderer.KafkaBrokersReturns(brokers)
    59  	return mockOrderer
    60  }
    61  
    62  func newMockChannel() *mockkafka.ChannelConfig {
    63  	mockCapabilities := &mockkafka.ChannelCapabilities{}
    64  	mockCapabilities.ConsensusTypeMigrationReturns(false)
    65  	mockChannel := &mockkafka.ChannelConfig{}
    66  	mockChannel.CapabilitiesReturns(mockCapabilities)
    67  	return mockChannel
    68  }
    69  
    70  const (
    71  	extraShortTimeout = time.Millisecond
    72  	shortTimeout      = time.Second
    73  	longTimeout       = time.Hour
    74  
    75  	hitBranch = 50 * time.Millisecond
    76  )
    77  
    78  func TestChain(t *testing.T) {
    79  	oldestOffset := int64(0)
    80  	newestOffset := int64(5)
    81  	lastOriginalOffsetProcessed := int64(0)
    82  	lastResubmittedConfigOffset := int64(0)
    83  
    84  	message := sarama.StringEncoder("messageFoo")
    85  
    86  	newMocks := func(t *testing.T) (mockChannel channel, mockBroker *sarama.MockBroker, mockSupport *mockmultichannel.ConsenterSupport) {
    87  		mockChannel = newChannel(channelNameForTest(t), defaultPartition)
    88  		mockBroker = sarama.NewMockBroker(t, 0)
    89  		mockBroker.SetHandlerByMap(map[string]sarama.MockResponse{
    90  			"MetadataRequest": sarama.NewMockMetadataResponse(t).
    91  				SetBroker(mockBroker.Addr(), mockBroker.BrokerID()).
    92  				SetLeader(mockChannel.topic(), mockChannel.partition(), mockBroker.BrokerID()),
    93  			"ProduceRequest": sarama.NewMockProduceResponse(t).
    94  				SetError(mockChannel.topic(), mockChannel.partition(), sarama.ErrNoError),
    95  			"OffsetRequest": sarama.NewMockOffsetResponse(t).
    96  				SetOffset(mockChannel.topic(), mockChannel.partition(), sarama.OffsetOldest, oldestOffset).
    97  				SetOffset(mockChannel.topic(), mockChannel.partition(), sarama.OffsetNewest, newestOffset),
    98  			"FetchRequest": sarama.NewMockFetchResponse(t, 1).
    99  				SetMessage(mockChannel.topic(), mockChannel.partition(), newestOffset, message),
   100  		})
   101  		mockSupport = &mockmultichannel.ConsenterSupport{
   102  			ChannelIDVal:     mockChannel.topic(),
   103  			HeightVal:        uint64(3),
   104  			SharedConfigVal:  newMockOrderer(0, []string{mockBroker.Addr()}, false),
   105  			ChannelConfigVal: newMockChannel(),
   106  		}
   107  		return
   108  	}
   109  
   110  	t.Run("New", func(t *testing.T) {
   111  		_, mockBroker, mockSupport := newMocks(t)
   112  		defer func() { mockBroker.Close() }()
   113  		fakeLastOffsetPersisted := &mockkafka.MetricsGauge{}
   114  		fakeLastOffsetPersisted.WithReturns(fakeLastOffsetPersisted)
   115  		mockConsenter.(*consenterImpl).metrics.LastOffsetPersisted = fakeLastOffsetPersisted
   116  		chain, err := newChain(mockConsenter, mockSupport, newestOffset-1, lastOriginalOffsetProcessed, lastResubmittedConfigOffset)
   117  
   118  		require.NoError(t, err, "Expected newChain to return without errors")
   119  		select {
   120  		case <-chain.Errored():
   121  			logger.Debug("Errored() returned a closed channel as expected")
   122  		default:
   123  			t.Fatal("Errored() should have returned a closed channel")
   124  		}
   125  
   126  		select {
   127  		case <-chain.haltChan:
   128  			t.Fatal("haltChan should have been open")
   129  		default:
   130  			logger.Debug("haltChan is open as it should be")
   131  		}
   132  
   133  		select {
   134  		case <-chain.startChan:
   135  			t.Fatal("startChan should have been open")
   136  		default:
   137  			logger.Debug("startChan is open as it should be")
   138  		}
   139  
   140  		require.Equal(t, fakeLastOffsetPersisted.WithCallCount(), 1)
   141  		require.Equal(t, fakeLastOffsetPersisted.WithArgsForCall(0), []string{"channel", channelNameForTest(t)})
   142  		require.Equal(t, fakeLastOffsetPersisted.SetCallCount(), 1)
   143  		require.Equal(t, fakeLastOffsetPersisted.SetArgsForCall(0), float64(newestOffset-1))
   144  	})
   145  
   146  	t.Run("Start", func(t *testing.T) {
   147  		_, mockBroker, mockSupport := newMocks(t)
   148  		defer func() { mockBroker.Close() }()
   149  		// Set to -1 because we haven't sent the CONNECT message yet
   150  		chain, _ := newChain(mockConsenter, mockSupport, newestOffset-1, lastOriginalOffsetProcessed, lastResubmittedConfigOffset)
   151  
   152  		chain.Start()
   153  		select {
   154  		case <-chain.startChan:
   155  			logger.Debug("startChan is closed as it should be")
   156  		case <-time.After(shortTimeout):
   157  			t.Fatal("startChan should have been closed by now")
   158  		}
   159  
   160  		// Trigger the haltChan clause in the processMessagesToBlocks goroutine
   161  		close(chain.haltChan)
   162  	})
   163  
   164  	t.Run("Halt", func(t *testing.T) {
   165  		_, mockBroker, mockSupport := newMocks(t)
   166  		defer func() { mockBroker.Close() }()
   167  		chain, _ := newChain(mockConsenter, mockSupport, newestOffset-1, lastOriginalOffsetProcessed, lastResubmittedConfigOffset)
   168  
   169  		chain.Start()
   170  		select {
   171  		case <-chain.startChan:
   172  			logger.Debug("startChan is closed as it should be")
   173  		case <-time.After(shortTimeout):
   174  			t.Fatal("startChan should have been closed by now")
   175  		}
   176  
   177  		// Wait till the start phase has completed, then:
   178  		chain.Halt()
   179  
   180  		select {
   181  		case <-chain.haltChan:
   182  			logger.Debug("haltChan is closed as it should be")
   183  		case <-time.After(shortTimeout):
   184  			t.Fatal("haltChan should have been closed")
   185  		}
   186  
   187  		select {
   188  		case <-chain.errorChan:
   189  			logger.Debug("errorChan is closed as it should be")
   190  		case <-time.After(shortTimeout):
   191  			t.Fatal("errorChan should have been closed")
   192  		}
   193  	})
   194  
   195  	t.Run("DoubleHalt", func(t *testing.T) {
   196  		_, mockBroker, mockSupport := newMocks(t)
   197  		defer func() { mockBroker.Close() }()
   198  		chain, _ := newChain(mockConsenter, mockSupport, newestOffset-1, lastOriginalOffsetProcessed, lastResubmittedConfigOffset)
   199  
   200  		chain.Start()
   201  		select {
   202  		case <-chain.startChan:
   203  			logger.Debug("startChan is closed as it should be")
   204  		case <-time.After(shortTimeout):
   205  			t.Fatal("startChan should have been closed by now")
   206  		}
   207  
   208  		chain.Halt()
   209  
   210  		require.NotPanics(t, func() { chain.Halt() }, "Calling Halt() more than once shouldn't panic")
   211  	})
   212  
   213  	t.Run("StartWithProducerForChannelError", func(t *testing.T) {
   214  		_, mockBroker, mockSupport := newMocks(t)
   215  		defer func() { mockBroker.Close() }()
   216  		// Point to an empty brokers list
   217  		mockSupportCopy := *mockSupport
   218  		mockSupportCopy.SharedConfigVal = newMockOrderer(longTimeout, []string{}, false)
   219  
   220  		chain, _ := newChain(mockConsenter, &mockSupportCopy, newestOffset-1, lastOriginalOffsetProcessed, lastResubmittedConfigOffset)
   221  
   222  		// The production path will actually call chain.Start(). This is
   223  		// functionally equivalent and allows us to run assertions on it.
   224  		require.Panics(t, func() { startThread(chain) }, "Expected the Start() call to panic")
   225  	})
   226  
   227  	t.Run("StartWithConnectMessageError", func(t *testing.T) {
   228  		// Note that this test is affected by the following parameters:
   229  		// - Net.ReadTimeout
   230  		// - Consumer.Retry.Backoff
   231  		// - Metadata.Retry.Max
   232  		mockChannel, mockBroker, mockSupport := newMocks(t)
   233  		defer func() { mockBroker.Close() }()
   234  		chain, _ := newChain(mockConsenter, mockSupport, newestOffset-1, lastOriginalOffsetProcessed, lastResubmittedConfigOffset)
   235  
   236  		// Have the broker return an ErrNotLeaderForPartition error
   237  		mockBroker.SetHandlerByMap(map[string]sarama.MockResponse{
   238  			"MetadataRequest": sarama.NewMockMetadataResponse(t).
   239  				SetBroker(mockBroker.Addr(), mockBroker.BrokerID()).
   240  				SetLeader(mockChannel.topic(), mockChannel.partition(), mockBroker.BrokerID()),
   241  			"ProduceRequest": sarama.NewMockProduceResponse(t).
   242  				SetError(mockChannel.topic(), mockChannel.partition(), sarama.ErrNotLeaderForPartition),
   243  			"OffsetRequest": sarama.NewMockOffsetResponse(t).
   244  				SetOffset(mockChannel.topic(), mockChannel.partition(), sarama.OffsetOldest, oldestOffset).
   245  				SetOffset(mockChannel.topic(), mockChannel.partition(), sarama.OffsetNewest, newestOffset),
   246  			"FetchRequest": sarama.NewMockFetchResponse(t, 1).
   247  				SetMessage(mockChannel.topic(), mockChannel.partition(), newestOffset, message),
   248  		})
   249  
   250  		require.Panics(t, func() { startThread(chain) }, "Expected the Start() call to panic")
   251  	})
   252  
   253  	t.Run("enqueueIfNotStarted", func(t *testing.T) {
   254  		mockChannel, mockBroker, mockSupport := newMocks(t)
   255  		defer func() { mockBroker.Close() }()
   256  		chain, _ := newChain(mockConsenter, mockSupport, newestOffset-1, lastOriginalOffsetProcessed, lastResubmittedConfigOffset)
   257  
   258  		// As in StartWithConnectMessageError, have the broker return an
   259  		// ErrNotLeaderForPartition error, i.e. cause an error in the
   260  		// 'post connect message' step.
   261  		mockBroker.SetHandlerByMap(map[string]sarama.MockResponse{
   262  			"MetadataRequest": sarama.NewMockMetadataResponse(t).
   263  				SetBroker(mockBroker.Addr(), mockBroker.BrokerID()).
   264  				SetLeader(mockChannel.topic(), mockChannel.partition(), mockBroker.BrokerID()),
   265  			"ProduceRequest": sarama.NewMockProduceResponse(t).
   266  				SetError(mockChannel.topic(), mockChannel.partition(), sarama.ErrNotLeaderForPartition),
   267  			"OffsetRequest": sarama.NewMockOffsetResponse(t).
   268  				SetOffset(mockChannel.topic(), mockChannel.partition(), sarama.OffsetOldest, oldestOffset).
   269  				SetOffset(mockChannel.topic(), mockChannel.partition(), sarama.OffsetNewest, newestOffset),
   270  			"FetchRequest": sarama.NewMockFetchResponse(t, 1).
   271  				SetMessage(mockChannel.topic(), mockChannel.partition(), newestOffset, message),
   272  		})
   273  
   274  		// We don't need to create a legit envelope here as it's not inspected during this test
   275  		require.False(t, chain.enqueue(newRegularMessage([]byte("fooMessage"))), "Expected enqueue call to return false")
   276  	})
   277  
   278  	t.Run("StartWithConsumerForChannelError", func(t *testing.T) {
   279  		// Note that this test is affected by the following parameters:
   280  		// - Net.ReadTimeout
   281  		// - Consumer.Retry.Backoff
   282  		// - Metadata.Retry.Max
   283  
   284  		mockChannel, mockBroker, mockSupport := newMocks(t)
   285  		defer func() { mockBroker.Close() }()
   286  
   287  		// Provide an out-of-range offset
   288  		chain, _ := newChain(mockConsenter, mockSupport, newestOffset, lastOriginalOffsetProcessed, lastResubmittedConfigOffset)
   289  
   290  		mockBroker.SetHandlerByMap(map[string]sarama.MockResponse{
   291  			"MetadataRequest": sarama.NewMockMetadataResponse(t).
   292  				SetBroker(mockBroker.Addr(), mockBroker.BrokerID()).
   293  				SetLeader(mockChannel.topic(), mockChannel.partition(), mockBroker.BrokerID()),
   294  			"ProduceRequest": sarama.NewMockProduceResponse(t).
   295  				SetError(mockChannel.topic(), mockChannel.partition(), sarama.ErrNoError),
   296  			"OffsetRequest": sarama.NewMockOffsetResponse(t).
   297  				SetOffset(mockChannel.topic(), mockChannel.partition(), sarama.OffsetOldest, oldestOffset).
   298  				SetOffset(mockChannel.topic(), mockChannel.partition(), sarama.OffsetNewest, newestOffset),
   299  			"FetchRequest": sarama.NewMockFetchResponse(t, 1).
   300  				SetMessage(mockChannel.topic(), mockChannel.partition(), newestOffset, message),
   301  		})
   302  
   303  		require.Panics(t, func() { startThread(chain) }, "Expected the Start() call to panic")
   304  	})
   305  
   306  	t.Run("enqueueProper", func(t *testing.T) {
   307  		mockChannel, mockBroker, mockSupport := newMocks(t)
   308  		defer func() { mockBroker.Close() }()
   309  		chain, _ := newChain(mockConsenter, mockSupport, newestOffset-1, lastOriginalOffsetProcessed, lastResubmittedConfigOffset)
   310  
   311  		mockBroker.SetHandlerByMap(map[string]sarama.MockResponse{
   312  			"MetadataRequest": sarama.NewMockMetadataResponse(t).
   313  				SetBroker(mockBroker.Addr(), mockBroker.BrokerID()).
   314  				SetLeader(mockChannel.topic(), mockChannel.partition(), mockBroker.BrokerID()),
   315  			"ProduceRequest": sarama.NewMockProduceResponse(t).
   316  				SetError(mockChannel.topic(), mockChannel.partition(), sarama.ErrNoError),
   317  			"OffsetRequest": sarama.NewMockOffsetResponse(t).
   318  				SetOffset(mockChannel.topic(), mockChannel.partition(), sarama.OffsetOldest, oldestOffset).
   319  				SetOffset(mockChannel.topic(), mockChannel.partition(), sarama.OffsetNewest, newestOffset),
   320  			"FetchRequest": sarama.NewMockFetchResponse(t, 1).
   321  				SetMessage(mockChannel.topic(), mockChannel.partition(), newestOffset, message),
   322  		})
   323  
   324  		chain.Start()
   325  		select {
   326  		case <-chain.startChan:
   327  			logger.Debug("startChan is closed as it should be")
   328  		case <-time.After(shortTimeout):
   329  			t.Fatal("startChan should have been closed by now")
   330  		}
   331  
   332  		// enqueue should have access to the post path, and its ProduceRequest should go by without error.
   333  		// We don't need to create a legit envelope here as it's not inspected during this test
   334  		require.True(t, chain.enqueue(newRegularMessage([]byte("fooMessage"))), "Expected enqueue call to return true")
   335  
   336  		chain.Halt()
   337  	})
   338  
   339  	t.Run("enqueueIfHalted", func(t *testing.T) {
   340  		mockChannel, mockBroker, mockSupport := newMocks(t)
   341  		defer func() { mockBroker.Close() }()
   342  		chain, _ := newChain(mockConsenter, mockSupport, newestOffset-1, lastOriginalOffsetProcessed, lastResubmittedConfigOffset)
   343  
   344  		mockBroker.SetHandlerByMap(map[string]sarama.MockResponse{
   345  			"MetadataRequest": sarama.NewMockMetadataResponse(t).
   346  				SetBroker(mockBroker.Addr(), mockBroker.BrokerID()).
   347  				SetLeader(mockChannel.topic(), mockChannel.partition(), mockBroker.BrokerID()),
   348  			"ProduceRequest": sarama.NewMockProduceResponse(t).
   349  				SetError(mockChannel.topic(), mockChannel.partition(), sarama.ErrNoError),
   350  			"OffsetRequest": sarama.NewMockOffsetResponse(t).
   351  				SetOffset(mockChannel.topic(), mockChannel.partition(), sarama.OffsetOldest, oldestOffset).
   352  				SetOffset(mockChannel.topic(), mockChannel.partition(), sarama.OffsetNewest, newestOffset),
   353  			"FetchRequest": sarama.NewMockFetchResponse(t, 1).
   354  				SetMessage(mockChannel.topic(), mockChannel.partition(), newestOffset, message),
   355  		})
   356  
   357  		chain.Start()
   358  		select {
   359  		case <-chain.startChan:
   360  			logger.Debug("startChan is closed as it should be")
   361  		case <-time.After(shortTimeout):
   362  			t.Fatal("startChan should have been closed by now")
   363  		}
   364  		chain.Halt()
   365  
   366  		// haltChan should close access to the post path.
   367  		// We don't need to create a legit envelope here as it's not inspected during this test
   368  		require.False(t, chain.enqueue(newRegularMessage([]byte("fooMessage"))), "Expected enqueue call to return false")
   369  	})
   370  
   371  	t.Run("enqueueError", func(t *testing.T) {
   372  		mockChannel, mockBroker, mockSupport := newMocks(t)
   373  		defer func() { mockBroker.Close() }()
   374  		chain, _ := newChain(mockConsenter, mockSupport, newestOffset-1, lastOriginalOffsetProcessed, lastResubmittedConfigOffset)
   375  
   376  		// Use the "good" handler map that allows the Stage to complete without
   377  		// issues
   378  		mockBroker.SetHandlerByMap(map[string]sarama.MockResponse{
   379  			"MetadataRequest": sarama.NewMockMetadataResponse(t).
   380  				SetBroker(mockBroker.Addr(), mockBroker.BrokerID()).
   381  				SetLeader(mockChannel.topic(), mockChannel.partition(), mockBroker.BrokerID()),
   382  			"ProduceRequest": sarama.NewMockProduceResponse(t).
   383  				SetError(mockChannel.topic(), mockChannel.partition(), sarama.ErrNoError),
   384  			"OffsetRequest": sarama.NewMockOffsetResponse(t).
   385  				SetOffset(mockChannel.topic(), mockChannel.partition(), sarama.OffsetOldest, oldestOffset).
   386  				SetOffset(mockChannel.topic(), mockChannel.partition(), sarama.OffsetNewest, newestOffset),
   387  			"FetchRequest": sarama.NewMockFetchResponse(t, 1).
   388  				SetMessage(mockChannel.topic(), mockChannel.partition(), newestOffset, message),
   389  		})
   390  
   391  		chain.Start()
   392  		select {
   393  		case <-chain.startChan:
   394  			logger.Debug("startChan is closed as it should be")
   395  		case <-time.After(shortTimeout):
   396  			t.Fatal("startChan should have been closed by now")
   397  		}
   398  		defer chain.Halt()
   399  
   400  		// Now make it so that the next ProduceRequest is met with an error
   401  		mockBroker.SetHandlerByMap(map[string]sarama.MockResponse{
   402  			"ProduceRequest": sarama.NewMockProduceResponse(t).
   403  				SetError(mockChannel.topic(), mockChannel.partition(), sarama.ErrNotLeaderForPartition),
   404  		})
   405  
   406  		// We don't need to create a legit envelope here as it's not inspected during this test
   407  		require.False(t, chain.enqueue(newRegularMessage([]byte("fooMessage"))), "Expected enqueue call to return false")
   408  	})
   409  
   410  	t.Run("Order", func(t *testing.T) {
   411  		t.Run("ErrorIfNotStarted", func(t *testing.T) {
   412  			_, mockBroker, mockSupport := newMocks(t)
   413  			defer func() { mockBroker.Close() }()
   414  			chain, _ := newChain(mockConsenter, mockSupport, newestOffset-1, lastOriginalOffsetProcessed, lastResubmittedConfigOffset)
   415  
   416  			// We don't need to create a legit envelope here as it's not inspected during this test
   417  			require.Error(t, chain.Order(&cb.Envelope{}, uint64(0)))
   418  		})
   419  
   420  		t.Run("Proper", func(t *testing.T) {
   421  			mockChannel, mockBroker, mockSupport := newMocks(t)
   422  			defer func() { mockBroker.Close() }()
   423  			chain, _ := newChain(mockConsenter, mockSupport, newestOffset-1, lastOriginalOffsetProcessed, lastResubmittedConfigOffset)
   424  
   425  			mockBroker.SetHandlerByMap(map[string]sarama.MockResponse{
   426  				"MetadataRequest": sarama.NewMockMetadataResponse(t).
   427  					SetBroker(mockBroker.Addr(), mockBroker.BrokerID()).
   428  					SetLeader(mockChannel.topic(), mockChannel.partition(), mockBroker.BrokerID()),
   429  				"ProduceRequest": sarama.NewMockProduceResponse(t).
   430  					SetError(mockChannel.topic(), mockChannel.partition(), sarama.ErrNoError),
   431  				"OffsetRequest": sarama.NewMockOffsetResponse(t).
   432  					SetOffset(mockChannel.topic(), mockChannel.partition(), sarama.OffsetOldest, oldestOffset).
   433  					SetOffset(mockChannel.topic(), mockChannel.partition(), sarama.OffsetNewest, newestOffset),
   434  				"FetchRequest": sarama.NewMockFetchResponse(t, 1).
   435  					SetMessage(mockChannel.topic(), mockChannel.partition(), newestOffset, message),
   436  			})
   437  
   438  			chain.Start()
   439  			defer chain.Halt()
   440  
   441  			select {
   442  			case <-chain.startChan:
   443  				logger.Debug("startChan is closed as it should be")
   444  			case <-time.After(shortTimeout):
   445  				t.Fatal("startChan should have been closed by now")
   446  			}
   447  
   448  			// We don't need to create a legit envelope here as it's not inspected during this test
   449  			require.NoError(t, chain.Order(&cb.Envelope{}, uint64(0)), "Expect Order successfully")
   450  		})
   451  	})
   452  
   453  	t.Run("Configure", func(t *testing.T) {
   454  		t.Run("ErrorIfNotStarted", func(t *testing.T) {
   455  			_, mockBroker, mockSupport := newMocks(t)
   456  			defer func() { mockBroker.Close() }()
   457  			chain, _ := newChain(mockConsenter, mockSupport, newestOffset-1, lastOriginalOffsetProcessed, lastResubmittedConfigOffset)
   458  
   459  			// We don't need to create a legit envelope here as it's not inspected during this test
   460  			require.Error(t, chain.Configure(&cb.Envelope{}, uint64(0)))
   461  		})
   462  
   463  		t.Run("Proper", func(t *testing.T) {
   464  			mockChannel, mockBroker, mockSupport := newMocks(t)
   465  			defer func() { mockBroker.Close() }()
   466  			chain, _ := newChain(mockConsenter, mockSupport, newestOffset-1, lastOriginalOffsetProcessed, lastResubmittedConfigOffset)
   467  
   468  			mockBroker.SetHandlerByMap(map[string]sarama.MockResponse{
   469  				"MetadataRequest": sarama.NewMockMetadataResponse(t).
   470  					SetBroker(mockBroker.Addr(), mockBroker.BrokerID()).
   471  					SetLeader(mockChannel.topic(), mockChannel.partition(), mockBroker.BrokerID()),
   472  				"ProduceRequest": sarama.NewMockProduceResponse(t).
   473  					SetError(mockChannel.topic(), mockChannel.partition(), sarama.ErrNoError),
   474  				"OffsetRequest": sarama.NewMockOffsetResponse(t).
   475  					SetOffset(mockChannel.topic(), mockChannel.partition(), sarama.OffsetOldest, oldestOffset).
   476  					SetOffset(mockChannel.topic(), mockChannel.partition(), sarama.OffsetNewest, newestOffset),
   477  				"FetchRequest": sarama.NewMockFetchResponse(t, 1).
   478  					SetMessage(mockChannel.topic(), mockChannel.partition(), newestOffset, message),
   479  			})
   480  
   481  			chain.Start()
   482  			defer chain.Halt()
   483  
   484  			select {
   485  			case <-chain.startChan:
   486  				logger.Debug("startChan is closed as it should be")
   487  			case <-time.After(shortTimeout):
   488  				t.Fatal("startChan should have been closed by now")
   489  			}
   490  
   491  			// We don't need to create a legit envelope here as it's not inspected during this test
   492  			require.NoError(t, chain.Configure(&cb.Envelope{}, uint64(0)), "Expect Configure successfully")
   493  		})
   494  	})
   495  }
   496  
   497  func TestSetupTopicForChannel(t *testing.T) {
   498  	mockChannel := newChannel(channelNameForTest(t), defaultPartition)
   499  	haltChan := make(chan struct{})
   500  
   501  	mockBrokerNoError := sarama.NewMockBroker(t, 0)
   502  	defer mockBrokerNoError.Close()
   503  	metadataResponse := sarama.NewMockMetadataResponse(t)
   504  	metadataResponse.SetBroker(mockBrokerNoError.Addr(),
   505  		mockBrokerNoError.BrokerID())
   506  	metadataResponse.SetController(mockBrokerNoError.BrokerID())
   507  
   508  	mdrUnknownTopicOrPartition := &sarama.MetadataResponse{
   509  		Version:      1,
   510  		Brokers:      []*sarama.Broker{sarama.NewBroker(mockBrokerNoError.Addr())},
   511  		ControllerID: -1,
   512  		Topics: []*sarama.TopicMetadata{
   513  			{
   514  				Err:  sarama.ErrUnknownTopicOrPartition,
   515  				Name: mockChannel.topic(),
   516  			},
   517  		},
   518  	}
   519  
   520  	mockBrokerNoError.SetHandlerByMap(map[string]sarama.MockResponse{
   521  		"CreateTopicsRequest": sarama.NewMockWrapper(
   522  			&sarama.CreateTopicsResponse{
   523  				TopicErrors: map[string]*sarama.TopicError{
   524  					mockChannel.topic(): {
   525  						Err: sarama.ErrNoError,
   526  					},
   527  				},
   528  			}),
   529  		"MetadataRequest": sarama.NewMockWrapper(mdrUnknownTopicOrPartition),
   530  	})
   531  
   532  	mockBrokerTopicExists := sarama.NewMockBroker(t, 1)
   533  	defer mockBrokerTopicExists.Close()
   534  	mockBrokerTopicExists.SetHandlerByMap(map[string]sarama.MockResponse{
   535  		"CreateTopicsRequest": sarama.NewMockWrapper(
   536  			&sarama.CreateTopicsResponse{
   537  				TopicErrors: map[string]*sarama.TopicError{
   538  					mockChannel.topic(): {
   539  						Err: sarama.ErrTopicAlreadyExists,
   540  					},
   541  				},
   542  			}),
   543  		"MetadataRequest": sarama.NewMockWrapper(&sarama.MetadataResponse{
   544  			Version: 1,
   545  			Topics: []*sarama.TopicMetadata{
   546  				{
   547  					Name: channelNameForTest(t),
   548  					Err:  sarama.ErrNoError,
   549  				},
   550  			},
   551  		}),
   552  	})
   553  
   554  	mockBrokerInvalidTopic := sarama.NewMockBroker(t, 2)
   555  	defer mockBrokerInvalidTopic.Close()
   556  	metadataResponse = sarama.NewMockMetadataResponse(t)
   557  	metadataResponse.SetBroker(mockBrokerInvalidTopic.Addr(),
   558  		mockBrokerInvalidTopic.BrokerID())
   559  	metadataResponse.SetController(mockBrokerInvalidTopic.BrokerID())
   560  	mockBrokerInvalidTopic.SetHandlerByMap(map[string]sarama.MockResponse{
   561  		"CreateTopicsRequest": sarama.NewMockWrapper(
   562  			&sarama.CreateTopicsResponse{
   563  				TopicErrors: map[string]*sarama.TopicError{
   564  					mockChannel.topic(): {
   565  						Err: sarama.ErrInvalidTopic,
   566  					},
   567  				},
   568  			}),
   569  		"MetadataRequest": metadataResponse,
   570  	})
   571  
   572  	mockBrokerInvalidTopic2 := sarama.NewMockBroker(t, 3)
   573  	defer mockBrokerInvalidTopic2.Close()
   574  	mockBrokerInvalidTopic2.SetHandlerByMap(map[string]sarama.MockResponse{
   575  		"CreateTopicsRequest": sarama.NewMockWrapper(
   576  			&sarama.CreateTopicsResponse{
   577  				TopicErrors: map[string]*sarama.TopicError{
   578  					mockChannel.topic(): {
   579  						Err: sarama.ErrInvalidTopic,
   580  					},
   581  				},
   582  			}),
   583  		"MetadataRequest": sarama.NewMockWrapper(&sarama.MetadataResponse{
   584  			Version:      1,
   585  			Brokers:      []*sarama.Broker{sarama.NewBroker(mockBrokerInvalidTopic2.Addr())},
   586  			ControllerID: mockBrokerInvalidTopic2.BrokerID(),
   587  		}),
   588  	})
   589  
   590  	closedBroker := sarama.NewMockBroker(t, 99)
   591  	badAddress := closedBroker.Addr()
   592  	closedBroker.Close()
   593  
   594  	tests := []struct {
   595  		name         string
   596  		brokers      []string
   597  		brokerConfig *sarama.Config
   598  		version      sarama.KafkaVersion
   599  		expectErr    bool
   600  		errorMsg     string
   601  	}{
   602  		{
   603  			name:         "Unsupported Version",
   604  			brokers:      []string{mockBrokerNoError.Addr()},
   605  			brokerConfig: sarama.NewConfig(),
   606  			version:      sarama.V0_9_0_0,
   607  			expectErr:    false,
   608  		},
   609  		{
   610  			name:         "No Error",
   611  			brokers:      []string{mockBrokerNoError.Addr()},
   612  			brokerConfig: sarama.NewConfig(),
   613  			version:      sarama.V0_10_2_0,
   614  			expectErr:    false,
   615  		},
   616  		{
   617  			name:         "Topic Exists",
   618  			brokers:      []string{mockBrokerTopicExists.Addr()},
   619  			brokerConfig: sarama.NewConfig(),
   620  			version:      sarama.V0_10_2_0,
   621  			expectErr:    false,
   622  		},
   623  		{
   624  			name:         "Invalid Topic",
   625  			brokers:      []string{mockBrokerInvalidTopic.Addr()},
   626  			brokerConfig: sarama.NewConfig(),
   627  			version:      sarama.V0_10_2_0,
   628  			expectErr:    true,
   629  			errorMsg:     "process asked to exit",
   630  		},
   631  		{
   632  			name:         "Multiple Brokers - One No Error",
   633  			brokers:      []string{badAddress, mockBrokerNoError.Addr()},
   634  			brokerConfig: sarama.NewConfig(),
   635  			version:      sarama.V0_10_2_0,
   636  			expectErr:    false,
   637  		},
   638  		{
   639  			name:         "Multiple Brokers - All Errors",
   640  			brokers:      []string{badAddress, badAddress},
   641  			brokerConfig: sarama.NewConfig(),
   642  			version:      sarama.V0_10_2_0,
   643  			expectErr:    true,
   644  			errorMsg:     "failed to retrieve metadata",
   645  		},
   646  	}
   647  
   648  	for _, test := range tests {
   649  		test := test
   650  		t.Run(test.name, func(t *testing.T) {
   651  			test.brokerConfig.Version = test.version
   652  			err := setupTopicForChannel(
   653  				mockRetryOptions,
   654  				haltChan,
   655  				test.brokers,
   656  				test.brokerConfig,
   657  				&sarama.TopicDetail{
   658  					NumPartitions:     1,
   659  					ReplicationFactor: 2,
   660  				},
   661  				mockChannel)
   662  			if test.expectErr {
   663  				require.Contains(t, err.Error(), test.errorMsg)
   664  			} else {
   665  				require.NoError(t, err)
   666  			}
   667  		})
   668  	}
   669  }
   670  
   671  func TestSetupProducerForChannel(t *testing.T) {
   672  	if testing.Short() {
   673  		t.Skip("Skipping test in short mode")
   674  	}
   675  
   676  	mockBroker := sarama.NewMockBroker(t, 0)
   677  	defer mockBroker.Close()
   678  
   679  	mockChannel := newChannel(channelNameForTest(t), defaultPartition)
   680  
   681  	haltChan := make(chan struct{})
   682  
   683  	t.Run("Proper", func(t *testing.T) {
   684  		metadataResponse := new(sarama.MetadataResponse)
   685  		metadataResponse.AddBroker(mockBroker.Addr(), mockBroker.BrokerID())
   686  		metadataResponse.AddTopicPartition(mockChannel.topic(), mockChannel.partition(), mockBroker.BrokerID(), nil, nil, sarama.ErrNoError)
   687  		mockBroker.Returns(metadataResponse)
   688  
   689  		producer, err := setupProducerForChannel(mockConsenter.retryOptions(), haltChan, []string{mockBroker.Addr()}, mockBrokerConfig, mockChannel)
   690  		require.NoError(t, err, "Expected the setupProducerForChannel call to return without errors")
   691  		require.NoError(t, producer.Close(), "Expected to close the producer without errors")
   692  	})
   693  
   694  	t.Run("WithError", func(t *testing.T) {
   695  		_, err := setupProducerForChannel(mockConsenter.retryOptions(), haltChan, []string{}, mockBrokerConfig, mockChannel)
   696  		require.Error(t, err, "Expected the setupProducerForChannel call to return an error")
   697  	})
   698  }
   699  
   700  func TestGetHealthyClusterReplicaInfo(t *testing.T) {
   701  	mockBroker := sarama.NewMockBroker(t, 0)
   702  	defer mockBroker.Close()
   703  
   704  	mockChannel := newChannel(channelNameForTest(t), defaultPartition)
   705  
   706  	haltChan := make(chan struct{})
   707  
   708  	t.Run("Proper", func(t *testing.T) {
   709  		ids := []int32{int32(1), int32(2)}
   710  		metadataResponse := new(sarama.MetadataResponse)
   711  		metadataResponse.AddBroker(mockBroker.Addr(), mockBroker.BrokerID())
   712  		metadataResponse.AddTopicPartition(mockChannel.topic(), mockChannel.partition(), mockBroker.BrokerID(), ids, nil, sarama.ErrNoError)
   713  		mockBroker.Returns(metadataResponse)
   714  
   715  		replicaIDs, err := getHealthyClusterReplicaInfo(mockConsenter.retryOptions(), haltChan, []string{mockBroker.Addr()}, mockBrokerConfig, mockChannel)
   716  		require.NoError(t, err, "Expected the getHealthyClusterReplicaInfo call to return without errors")
   717  		require.Equal(t, replicaIDs, ids)
   718  	})
   719  
   720  	t.Run("WithError", func(t *testing.T) {
   721  		_, err := getHealthyClusterReplicaInfo(mockConsenter.retryOptions(), haltChan, []string{}, mockBrokerConfig, mockChannel)
   722  		require.Error(t, err, "Expected the getHealthyClusterReplicaInfo call to return an error")
   723  	})
   724  }
   725  
   726  func TestSetupConsumerForChannel(t *testing.T) {
   727  	mockBroker := sarama.NewMockBroker(t, 0)
   728  	defer func() { mockBroker.Close() }()
   729  
   730  	mockChannel := newChannel(channelNameForTest(t), defaultPartition)
   731  
   732  	oldestOffset := int64(0)
   733  	newestOffset := int64(5)
   734  
   735  	startFrom := int64(3)
   736  	message := sarama.StringEncoder("messageFoo")
   737  
   738  	mockBroker.SetHandlerByMap(map[string]sarama.MockResponse{
   739  		"MetadataRequest": sarama.NewMockMetadataResponse(t).
   740  			SetBroker(mockBroker.Addr(), mockBroker.BrokerID()).
   741  			SetLeader(mockChannel.topic(), mockChannel.partition(), mockBroker.BrokerID()),
   742  		"OffsetRequest": sarama.NewMockOffsetResponse(t).
   743  			SetOffset(mockChannel.topic(), mockChannel.partition(), sarama.OffsetOldest, oldestOffset).
   744  			SetOffset(mockChannel.topic(), mockChannel.partition(), sarama.OffsetNewest, newestOffset),
   745  		"FetchRequest": sarama.NewMockFetchResponse(t, 1).
   746  			SetMessage(mockChannel.topic(), mockChannel.partition(), startFrom, message),
   747  	})
   748  
   749  	haltChan := make(chan struct{})
   750  
   751  	t.Run("ProperParent", func(t *testing.T) {
   752  		parentConsumer, err := setupParentConsumerForChannel(mockConsenter.retryOptions(), haltChan, []string{mockBroker.Addr()}, mockBrokerConfig, mockChannel)
   753  		require.NoError(t, err, "Expected the setupParentConsumerForChannel call to return without errors")
   754  		require.NoError(t, parentConsumer.Close(), "Expected to close the parentConsumer without errors")
   755  	})
   756  
   757  	t.Run("ProperChannel", func(t *testing.T) {
   758  		parentConsumer, _ := setupParentConsumerForChannel(mockConsenter.retryOptions(), haltChan, []string{mockBroker.Addr()}, mockBrokerConfig, mockChannel)
   759  		defer func() { parentConsumer.Close() }()
   760  		channelConsumer, err := setupChannelConsumerForChannel(mockConsenter.retryOptions(), haltChan, parentConsumer, mockChannel, newestOffset)
   761  		require.NoError(t, err, "Expected the setupChannelConsumerForChannel call to return without errors")
   762  		require.NoError(t, channelConsumer.Close(), "Expected to close the channelConsumer without errors")
   763  	})
   764  
   765  	t.Run("WithParentConsumerError", func(t *testing.T) {
   766  		// Provide an empty brokers list
   767  		_, err := setupParentConsumerForChannel(mockConsenter.retryOptions(), haltChan, []string{}, mockBrokerConfig, mockChannel)
   768  		require.Error(t, err, "Expected the setupParentConsumerForChannel call to return an error")
   769  	})
   770  
   771  	t.Run("WithChannelConsumerError", func(t *testing.T) {
   772  		// Provide an out-of-range offset
   773  		parentConsumer, _ := setupParentConsumerForChannel(mockConsenter.retryOptions(), haltChan, []string{mockBroker.Addr()}, mockBrokerConfig, mockChannel)
   774  		_, err := setupChannelConsumerForChannel(mockConsenter.retryOptions(), haltChan, parentConsumer, mockChannel, newestOffset+1)
   775  		defer func() { parentConsumer.Close() }()
   776  		require.Error(t, err, "Expected the setupChannelConsumerForChannel call to return an error")
   777  	})
   778  }
   779  
   780  func TestCloseKafkaObjects(t *testing.T) {
   781  	mockChannel := newChannel(channelNameForTest(t), defaultPartition)
   782  
   783  	mockSupport := &mockmultichannel.ConsenterSupport{
   784  		ChannelIDVal: mockChannel.topic(),
   785  	}
   786  
   787  	oldestOffset := int64(0)
   788  	newestOffset := int64(5)
   789  
   790  	startFrom := int64(3)
   791  	message := sarama.StringEncoder("messageFoo")
   792  
   793  	mockBroker := sarama.NewMockBroker(t, 0)
   794  	defer func() { mockBroker.Close() }()
   795  
   796  	mockBroker.SetHandlerByMap(map[string]sarama.MockResponse{
   797  		"MetadataRequest": sarama.NewMockMetadataResponse(t).
   798  			SetBroker(mockBroker.Addr(), mockBroker.BrokerID()).
   799  			SetLeader(mockChannel.topic(), mockChannel.partition(), mockBroker.BrokerID()),
   800  		"OffsetRequest": sarama.NewMockOffsetResponse(t).
   801  			SetOffset(mockChannel.topic(), mockChannel.partition(), sarama.OffsetOldest, oldestOffset).
   802  			SetOffset(mockChannel.topic(), mockChannel.partition(), sarama.OffsetNewest, newestOffset),
   803  		"FetchRequest": sarama.NewMockFetchResponse(t, 1).
   804  			SetMessage(mockChannel.topic(), mockChannel.partition(), startFrom, message),
   805  	})
   806  
   807  	haltChan := make(chan struct{})
   808  
   809  	t.Run("Proper", func(t *testing.T) {
   810  		producer, _ := setupProducerForChannel(mockConsenter.retryOptions(), haltChan, []string{mockBroker.Addr()}, mockBrokerConfig, mockChannel)
   811  		parentConsumer, _ := setupParentConsumerForChannel(mockConsenter.retryOptions(), haltChan, []string{mockBroker.Addr()}, mockBrokerConfig, mockChannel)
   812  		channelConsumer, _ := setupChannelConsumerForChannel(mockConsenter.retryOptions(), haltChan, parentConsumer, mockChannel, startFrom)
   813  
   814  		// Set up a chain with just the minimum necessary fields instantiated so
   815  		// as to test the function
   816  		bareMinimumChain := &chainImpl{
   817  			ConsenterSupport: mockSupport,
   818  			producer:         producer,
   819  			parentConsumer:   parentConsumer,
   820  			channelConsumer:  channelConsumer,
   821  		}
   822  
   823  		errs := bareMinimumChain.closeKafkaObjects()
   824  
   825  		require.Len(t, errs, 0, "Expected zero errors")
   826  
   827  		require.NotPanics(t, func() {
   828  			channelConsumer.Close()
   829  		})
   830  
   831  		require.NotPanics(t, func() {
   832  			parentConsumer.Close()
   833  		})
   834  
   835  		// TODO For some reason this panic cannot be captured by the `assert`
   836  		// test framework. Not a dealbreaker but need to investigate further.
   837  		/* assert.Panics(t, func() {
   838  			producer.Close()
   839  		}) */
   840  	})
   841  
   842  	t.Run("ChannelConsumerError", func(t *testing.T) {
   843  		producer, _ := sarama.NewSyncProducer([]string{mockBroker.Addr()}, mockBrokerConfig)
   844  
   845  		// Unlike all other tests in this file, forcing an error on the
   846  		// channelConsumer.Close() call is more easily achieved using the mock
   847  		// Consumer. Thus we bypass the call to `setup*Consumer`.
   848  
   849  		// Have the consumer receive an ErrOutOfBrokers error.
   850  		mockParentConsumer := mocks.NewConsumer(t, nil)
   851  		mockParentConsumer.ExpectConsumePartition(mockChannel.topic(), mockChannel.partition(), startFrom).YieldError(sarama.ErrOutOfBrokers)
   852  		mockChannelConsumer, err := mockParentConsumer.ConsumePartition(mockChannel.topic(), mockChannel.partition(), startFrom)
   853  		require.NoError(t, err, "Expected no error when setting up the mock partition consumer")
   854  
   855  		bareMinimumChain := &chainImpl{
   856  			ConsenterSupport: mockSupport,
   857  			producer:         producer,
   858  			parentConsumer:   mockParentConsumer,
   859  			channelConsumer:  mockChannelConsumer,
   860  		}
   861  
   862  		errs := bareMinimumChain.closeKafkaObjects()
   863  
   864  		require.Len(t, errs, 1, "Expected 1 error returned")
   865  
   866  		require.NotPanics(t, func() {
   867  			mockChannelConsumer.Close()
   868  		})
   869  
   870  		require.NotPanics(t, func() {
   871  			mockParentConsumer.Close()
   872  		})
   873  	})
   874  }
   875  
   876  func TestGetLastCutBlockNumber(t *testing.T) {
   877  	testCases := []struct {
   878  		name     string
   879  		input    uint64
   880  		expected uint64
   881  	}{
   882  		{"Proper", uint64(2), uint64(1)},
   883  		{"Zero", uint64(1), uint64(0)},
   884  	}
   885  	for _, tc := range testCases {
   886  		t.Run(tc.name, func(t *testing.T) {
   887  			require.Equal(t, tc.expected, getLastCutBlockNumber(tc.input))
   888  		})
   889  	}
   890  }
   891  
   892  func TestGetLastOffsetPersisted(t *testing.T) {
   893  	mockChannel := newChannel(channelNameForTest(t), defaultPartition)
   894  	mockMetadata := &cb.Metadata{Value: protoutil.MarshalOrPanic(&ab.KafkaMetadata{
   895  		LastOffsetPersisted:         int64(5),
   896  		LastOriginalOffsetProcessed: int64(3),
   897  		LastResubmittedConfigOffset: int64(4),
   898  	})}
   899  
   900  	testCases := []struct {
   901  		name                string
   902  		md                  []byte
   903  		expectedPersisted   int64
   904  		expectedProcessed   int64
   905  		expectedResubmitted int64
   906  		panics              bool
   907  	}{
   908  		{"Proper", mockMetadata.Value, int64(5), int64(3), int64(4), false},
   909  		{"Empty", nil, sarama.OffsetOldest - 1, int64(0), int64(0), false},
   910  		{"Panics", tamperBytes(mockMetadata.Value), sarama.OffsetOldest - 1, int64(0), int64(0), true},
   911  	}
   912  
   913  	for _, tc := range testCases {
   914  		t.Run(tc.name, func(t *testing.T) {
   915  			if !tc.panics {
   916  				persisted, processed, resubmitted := getOffsets(tc.md, mockChannel.String())
   917  				require.Equal(t, tc.expectedPersisted, persisted)
   918  				require.Equal(t, tc.expectedProcessed, processed)
   919  				require.Equal(t, tc.expectedResubmitted, resubmitted)
   920  			} else {
   921  				require.Panics(t, func() {
   922  					getOffsets(tc.md, mockChannel.String())
   923  				}, "Expected getOffsets call to panic")
   924  			}
   925  		})
   926  	}
   927  }
   928  
   929  func TestSendConnectMessage(t *testing.T) {
   930  	mockBroker := sarama.NewMockBroker(t, 0)
   931  	defer func() { mockBroker.Close() }()
   932  
   933  	mockChannel := newChannel("mockChannelFoo", defaultPartition)
   934  
   935  	metadataResponse := new(sarama.MetadataResponse)
   936  	metadataResponse.AddBroker(mockBroker.Addr(), mockBroker.BrokerID())
   937  	metadataResponse.AddTopicPartition(mockChannel.topic(), mockChannel.partition(), mockBroker.BrokerID(), nil, nil, sarama.ErrNoError)
   938  	mockBroker.Returns(metadataResponse)
   939  
   940  	producer, _ := sarama.NewSyncProducer([]string{mockBroker.Addr()}, mockBrokerConfig)
   941  	defer func() { producer.Close() }()
   942  
   943  	haltChan := make(chan struct{})
   944  
   945  	t.Run("Proper", func(t *testing.T) {
   946  		successResponse := new(sarama.ProduceResponse)
   947  		successResponse.AddTopicPartition(mockChannel.topic(), mockChannel.partition(), sarama.ErrNoError)
   948  		mockBroker.Returns(successResponse)
   949  
   950  		require.NoError(t, sendConnectMessage(mockConsenter.retryOptions(), haltChan, producer, mockChannel), "Expected the sendConnectMessage call to return without errors")
   951  	})
   952  
   953  	t.Run("WithError", func(t *testing.T) {
   954  		// Note that this test is affected by the following parameters:
   955  		// - Net.ReadTimeout
   956  		// - Consumer.Retry.Backoff
   957  		// - Metadata.Retry.Max
   958  
   959  		// Have the broker return an ErrNotEnoughReplicas error
   960  		failureResponse := new(sarama.ProduceResponse)
   961  		failureResponse.AddTopicPartition(mockChannel.topic(), mockChannel.partition(), sarama.ErrNotEnoughReplicas)
   962  		mockBroker.Returns(failureResponse)
   963  
   964  		require.Error(t, sendConnectMessage(mockConsenter.retryOptions(), haltChan, producer, mockChannel), "Expected the sendConnectMessage call to return an error")
   965  	})
   966  }
   967  
   968  func TestSendTimeToCut(t *testing.T) {
   969  	mockBroker := sarama.NewMockBroker(t, 0)
   970  	defer func() { mockBroker.Close() }()
   971  
   972  	mockChannel := newChannel("mockChannelFoo", defaultPartition)
   973  
   974  	metadataResponse := new(sarama.MetadataResponse)
   975  	metadataResponse.AddBroker(mockBroker.Addr(), mockBroker.BrokerID())
   976  	metadataResponse.AddTopicPartition(mockChannel.topic(), mockChannel.partition(), mockBroker.BrokerID(), nil, nil, sarama.ErrNoError)
   977  	mockBroker.Returns(metadataResponse)
   978  
   979  	producer, err := sarama.NewSyncProducer([]string{mockBroker.Addr()}, mockBrokerConfig)
   980  	require.NoError(t, err, "Expected no error when setting up the sarama SyncProducer")
   981  	defer func() { producer.Close() }()
   982  
   983  	timeToCutBlockNumber := uint64(3)
   984  	var timer <-chan time.Time
   985  
   986  	t.Run("Proper", func(t *testing.T) {
   987  		successResponse := new(sarama.ProduceResponse)
   988  		successResponse.AddTopicPartition(mockChannel.topic(), mockChannel.partition(), sarama.ErrNoError)
   989  		mockBroker.Returns(successResponse)
   990  
   991  		timer = time.After(longTimeout)
   992  
   993  		require.NoError(t, sendTimeToCut(producer, mockChannel, timeToCutBlockNumber, &timer), "Expected the sendTimeToCut call to return without errors")
   994  		require.Nil(t, timer, "Expected the sendTimeToCut call to nil the timer")
   995  	})
   996  
   997  	t.Run("WithError", func(t *testing.T) {
   998  		// Note that this test is affected by the following parameters:
   999  		// - Net.ReadTimeout
  1000  		// - Consumer.Retry.Backoff
  1001  		// - Metadata.Retry.Max
  1002  		failureResponse := new(sarama.ProduceResponse)
  1003  		failureResponse.AddTopicPartition(mockChannel.topic(), mockChannel.partition(), sarama.ErrNotEnoughReplicas)
  1004  		mockBroker.Returns(failureResponse)
  1005  
  1006  		timer = time.After(longTimeout)
  1007  
  1008  		require.Error(t, sendTimeToCut(producer, mockChannel, timeToCutBlockNumber, &timer), "Expected the sendTimeToCut call to return an error")
  1009  		require.Nil(t, timer, "Expected the sendTimeToCut call to nil the timer")
  1010  	})
  1011  }
  1012  
  1013  func TestProcessMessagesToBlocks(t *testing.T) {
  1014  	mockBroker := sarama.NewMockBroker(t, 0)
  1015  	defer func() { mockBroker.Close() }()
  1016  
  1017  	mockChannel := newChannel("mockChannelFoo", defaultPartition)
  1018  
  1019  	metadataResponse := new(sarama.MetadataResponse)
  1020  	metadataResponse.AddBroker(mockBroker.Addr(), mockBroker.BrokerID())
  1021  	metadataResponse.AddTopicPartition(mockChannel.topic(), mockChannel.partition(), mockBroker.BrokerID(), nil, nil, sarama.ErrNoError)
  1022  	mockBroker.Returns(metadataResponse)
  1023  
  1024  	producer, _ := sarama.NewSyncProducer([]string{mockBroker.Addr()}, mockBrokerConfig)
  1025  
  1026  	mockBrokerConfigCopy := *mockBrokerConfig
  1027  	mockBrokerConfigCopy.ChannelBufferSize = 0
  1028  
  1029  	mockParentConsumer := mocks.NewConsumer(t, &mockBrokerConfigCopy)
  1030  	mpc := mockParentConsumer.ExpectConsumePartition(mockChannel.topic(), mockChannel.partition(), int64(0))
  1031  	mockChannelConsumer, err := mockParentConsumer.ConsumePartition(mockChannel.topic(), mockChannel.partition(), int64(0))
  1032  	require.NoError(t, err, "Expected no error when setting up the mock partition consumer")
  1033  
  1034  	t.Run("TimeToCut", func(t *testing.T) {
  1035  		t.Run("PendingMsgToCutProper", func(t *testing.T) {
  1036  			errorChan := make(chan struct{})
  1037  			close(errorChan)
  1038  			haltChan := make(chan struct{})
  1039  
  1040  			lastCutBlockNumber := uint64(3)
  1041  
  1042  			mockSupport := &mockmultichannel.ConsenterSupport{
  1043  				Blocks:          make(chan *cb.Block), // WriteBlock will post here
  1044  				BlockCutterVal:  mockblockcutter.NewReceiver(),
  1045  				ChannelIDVal:    mockChannel.topic(),
  1046  				HeightVal:       lastCutBlockNumber, // Incremented during the WriteBlock call
  1047  				SharedConfigVal: newMockOrderer(shortTimeout/2, []string{mockBroker.Addr()}, false),
  1048  			}
  1049  			defer close(mockSupport.BlockCutterVal.Block)
  1050  
  1051  			bareMinimumChain := &chainImpl{
  1052  				producer:        producer,
  1053  				parentConsumer:  mockParentConsumer,
  1054  				channelConsumer: mockChannelConsumer,
  1055  
  1056  				consenter:          mockConsenter,
  1057  				channel:            mockChannel,
  1058  				ConsenterSupport:   mockSupport,
  1059  				lastCutBlockNumber: lastCutBlockNumber,
  1060  
  1061  				errorChan:                      errorChan,
  1062  				haltChan:                       haltChan,
  1063  				doneProcessingMessagesToBlocks: make(chan struct{}),
  1064  			}
  1065  
  1066  			// We need the mock blockcutter to deliver a non-empty batch
  1067  			go func() {
  1068  				mockSupport.BlockCutterVal.Block <- struct{}{} // Let the `mockblockcutter.Ordered` call below return
  1069  				logger.Debugf("Mock blockcutter's Ordered call has returned")
  1070  			}()
  1071  			// We are "planting" a message directly to the mock blockcutter
  1072  			mockSupport.BlockCutterVal.Ordered(newMockEnvelope("fooMessage"))
  1073  
  1074  			done := make(chan struct{})
  1075  
  1076  			go func() {
  1077  				bareMinimumChain.processMessagesToBlocks()
  1078  				done <- struct{}{}
  1079  			}()
  1080  
  1081  			// Cut ancestors
  1082  			mockSupport.BlockCutterVal.CutAncestors = true
  1083  
  1084  			// This envelope will be added into pending list, waiting to be cut when timer fires
  1085  			mpc.YieldMessage(newMockConsumerMessage(newRegularMessage(protoutil.MarshalOrPanic(newMockEnvelope("fooMessage")))))
  1086  
  1087  			go func() {
  1088  				mockSupport.BlockCutterVal.Block <- struct{}{}
  1089  				logger.Debugf("Mock blockcutter's Ordered call has returned")
  1090  			}()
  1091  
  1092  			<-mockSupport.Blocks // Wait for the first block
  1093  
  1094  			logger.Debug("Closing haltChan to exit the infinite for-loop")
  1095  			close(haltChan) // Identical to chain.Halt()
  1096  			logger.Debug("haltChan closed")
  1097  			<-done
  1098  
  1099  			if bareMinimumChain.timer != nil {
  1100  				go func() {
  1101  					<-bareMinimumChain.timer // Fire the timer for garbage collection
  1102  				}()
  1103  			}
  1104  
  1105  			require.NotEmpty(t, mockSupport.BlockCutterVal.CurBatch, "Expected the blockCutter to be non-empty")
  1106  			require.NotNil(t, bareMinimumChain.timer, "Expected the cutTimer to be non-nil when there are pending envelopes")
  1107  		})
  1108  
  1109  		t.Run("ReceiveTimeToCutProper", func(t *testing.T) {
  1110  			errorChan := make(chan struct{})
  1111  			close(errorChan)
  1112  			haltChan := make(chan struct{})
  1113  
  1114  			lastCutBlockNumber := uint64(3)
  1115  
  1116  			mockSupport := &mockmultichannel.ConsenterSupport{
  1117  				Blocks:         make(chan *cb.Block), // WriteBlock will post here
  1118  				BlockCutterVal: mockblockcutter.NewReceiver(),
  1119  				ChannelIDVal:   mockChannel.topic(),
  1120  				HeightVal:      lastCutBlockNumber, // Incremented during the WriteBlock call
  1121  			}
  1122  			defer close(mockSupport.BlockCutterVal.Block)
  1123  
  1124  			bareMinimumChain := &chainImpl{
  1125  				parentConsumer:  mockParentConsumer,
  1126  				channelConsumer: mockChannelConsumer,
  1127  
  1128  				consenter:          mockConsenter,
  1129  				channel:            mockChannel,
  1130  				ConsenterSupport:   mockSupport,
  1131  				lastCutBlockNumber: lastCutBlockNumber,
  1132  
  1133  				errorChan:                      errorChan,
  1134  				haltChan:                       haltChan,
  1135  				doneProcessingMessagesToBlocks: make(chan struct{}),
  1136  			}
  1137  
  1138  			// We need the mock blockcutter to deliver a non-empty batch
  1139  			go func() {
  1140  				mockSupport.BlockCutterVal.Block <- struct{}{} // Let the `mockblockcutter.Ordered` call below return
  1141  				logger.Debugf("Mock blockcutter's Ordered call has returned")
  1142  			}()
  1143  			// We are "planting" a message directly to the mock blockcutter
  1144  			mockSupport.BlockCutterVal.Ordered(newMockEnvelope("fooMessage"))
  1145  
  1146  			var counts []uint64
  1147  			done := make(chan struct{})
  1148  
  1149  			go func() {
  1150  				counts, err = bareMinimumChain.processMessagesToBlocks()
  1151  				done <- struct{}{}
  1152  			}()
  1153  
  1154  			// This is the wrappedMessage that the for-loop will process
  1155  			mpc.YieldMessage(newMockConsumerMessage(newTimeToCutMessage(lastCutBlockNumber + 1)))
  1156  
  1157  			<-mockSupport.Blocks // Let the `mockConsenterSupport.WriteBlock` proceed
  1158  
  1159  			logger.Debug("Closing haltChan to exit the infinite for-loop")
  1160  			close(haltChan) // Identical to chain.Halt()
  1161  			logger.Debug("haltChan closed")
  1162  			<-done
  1163  
  1164  			require.NoError(t, err, "Expected the processMessagesToBlocks call to return without errors")
  1165  			require.Equal(t, uint64(1), counts[indexRecvPass], "Expected 1 message received and unmarshaled")
  1166  			require.Equal(t, uint64(1), counts[indexProcessTimeToCutPass], "Expected 1 TIMETOCUT message processed")
  1167  			require.Equal(t, lastCutBlockNumber+1, bareMinimumChain.lastCutBlockNumber, "Expected lastCutBlockNumber to be bumped up by one")
  1168  		})
  1169  
  1170  		t.Run("ReceiveTimeToCutZeroBatch", func(t *testing.T) {
  1171  			errorChan := make(chan struct{})
  1172  			close(errorChan)
  1173  			haltChan := make(chan struct{})
  1174  
  1175  			lastCutBlockNumber := uint64(3)
  1176  
  1177  			mockSupport := &mockmultichannel.ConsenterSupport{
  1178  				Blocks:         make(chan *cb.Block), // WriteBlock will post here
  1179  				BlockCutterVal: mockblockcutter.NewReceiver(),
  1180  				ChannelIDVal:   mockChannel.topic(),
  1181  				HeightVal:      lastCutBlockNumber, // Incremented during the WriteBlock call
  1182  			}
  1183  			defer close(mockSupport.BlockCutterVal.Block)
  1184  
  1185  			bareMinimumChain := &chainImpl{
  1186  				parentConsumer:  mockParentConsumer,
  1187  				channelConsumer: mockChannelConsumer,
  1188  
  1189  				channel:            mockChannel,
  1190  				ConsenterSupport:   mockSupport,
  1191  				lastCutBlockNumber: lastCutBlockNumber,
  1192  
  1193  				errorChan:                      errorChan,
  1194  				haltChan:                       haltChan,
  1195  				doneProcessingMessagesToBlocks: make(chan struct{}),
  1196  			}
  1197  
  1198  			var counts []uint64
  1199  			done := make(chan struct{})
  1200  
  1201  			go func() {
  1202  				counts, err = bareMinimumChain.processMessagesToBlocks()
  1203  				done <- struct{}{}
  1204  			}()
  1205  
  1206  			// This is the wrappedMessage that the for-loop will process
  1207  			mpc.YieldMessage(newMockConsumerMessage(newTimeToCutMessage(lastCutBlockNumber + 1)))
  1208  
  1209  			logger.Debug("Closing haltChan to exit the infinite for-loop")
  1210  			close(haltChan) // Identical to chain.Halt()
  1211  			logger.Debug("haltChan closed")
  1212  			<-done
  1213  
  1214  			require.Error(t, err, "Expected the processMessagesToBlocks call to return an error")
  1215  			require.Equal(t, uint64(1), counts[indexRecvPass], "Expected 1 message received and unmarshaled")
  1216  			require.Equal(t, uint64(1), counts[indexProcessTimeToCutError], "Expected 1 faulty TIMETOCUT message processed")
  1217  			require.Equal(t, lastCutBlockNumber, bareMinimumChain.lastCutBlockNumber, "Expected lastCutBlockNumber to stay the same")
  1218  		})
  1219  
  1220  		t.Run("ReceiveTimeToCutLargerThanExpected", func(t *testing.T) {
  1221  			errorChan := make(chan struct{})
  1222  			close(errorChan)
  1223  			haltChan := make(chan struct{})
  1224  
  1225  			lastCutBlockNumber := uint64(3)
  1226  
  1227  			mockSupport := &mockmultichannel.ConsenterSupport{
  1228  				Blocks:         make(chan *cb.Block), // WriteBlock will post here
  1229  				BlockCutterVal: mockblockcutter.NewReceiver(),
  1230  				ChannelIDVal:   mockChannel.topic(),
  1231  				HeightVal:      lastCutBlockNumber, // Incremented during the WriteBlock call
  1232  			}
  1233  			defer close(mockSupport.BlockCutterVal.Block)
  1234  
  1235  			bareMinimumChain := &chainImpl{
  1236  				parentConsumer:  mockParentConsumer,
  1237  				channelConsumer: mockChannelConsumer,
  1238  
  1239  				channel:            mockChannel,
  1240  				ConsenterSupport:   mockSupport,
  1241  				lastCutBlockNumber: lastCutBlockNumber,
  1242  
  1243  				errorChan:                      errorChan,
  1244  				haltChan:                       haltChan,
  1245  				doneProcessingMessagesToBlocks: make(chan struct{}),
  1246  			}
  1247  
  1248  			var counts []uint64
  1249  			done := make(chan struct{})
  1250  
  1251  			go func() {
  1252  				counts, err = bareMinimumChain.processMessagesToBlocks()
  1253  				done <- struct{}{}
  1254  			}()
  1255  
  1256  			// This is the wrappedMessage that the for-loop will process
  1257  			mpc.YieldMessage(newMockConsumerMessage(newTimeToCutMessage(lastCutBlockNumber + 2)))
  1258  
  1259  			logger.Debug("Closing haltChan to exit the infinite for-loop")
  1260  			close(haltChan) // Identical to chain.Halt()
  1261  			logger.Debug("haltChan closed")
  1262  			<-done
  1263  
  1264  			require.Error(t, err, "Expected the processMessagesToBlocks call to return an error")
  1265  			require.Equal(t, uint64(1), counts[indexRecvPass], "Expected 1 message received and unmarshaled")
  1266  			require.Equal(t, uint64(1), counts[indexProcessTimeToCutError], "Expected 1 faulty TIMETOCUT message processed")
  1267  			require.Equal(t, lastCutBlockNumber, bareMinimumChain.lastCutBlockNumber, "Expected lastCutBlockNumber to stay the same")
  1268  		})
  1269  
  1270  		t.Run("ReceiveTimeToCutStale", func(t *testing.T) {
  1271  			errorChan := make(chan struct{})
  1272  			close(errorChan)
  1273  			haltChan := make(chan struct{})
  1274  
  1275  			lastCutBlockNumber := uint64(3)
  1276  
  1277  			mockSupport := &mockmultichannel.ConsenterSupport{
  1278  				Blocks:         make(chan *cb.Block), // WriteBlock will post here
  1279  				BlockCutterVal: mockblockcutter.NewReceiver(),
  1280  				ChannelIDVal:   mockChannel.topic(),
  1281  				HeightVal:      lastCutBlockNumber, // Incremented during the WriteBlock call
  1282  			}
  1283  			defer close(mockSupport.BlockCutterVal.Block)
  1284  
  1285  			bareMinimumChain := &chainImpl{
  1286  				parentConsumer:  mockParentConsumer,
  1287  				channelConsumer: mockChannelConsumer,
  1288  
  1289  				channel:            mockChannel,
  1290  				ConsenterSupport:   mockSupport,
  1291  				lastCutBlockNumber: lastCutBlockNumber,
  1292  
  1293  				errorChan:                      errorChan,
  1294  				haltChan:                       haltChan,
  1295  				doneProcessingMessagesToBlocks: make(chan struct{}),
  1296  			}
  1297  
  1298  			var counts []uint64
  1299  			done := make(chan struct{})
  1300  
  1301  			go func() {
  1302  				counts, err = bareMinimumChain.processMessagesToBlocks()
  1303  				done <- struct{}{}
  1304  			}()
  1305  
  1306  			// This is the wrappedMessage that the for-loop will process
  1307  			mpc.YieldMessage(newMockConsumerMessage(newTimeToCutMessage(lastCutBlockNumber)))
  1308  
  1309  			logger.Debug("Closing haltChan to exit the infinite for-loop")
  1310  			close(haltChan) // Identical to chain.Halt()
  1311  			logger.Debug("haltChan closed")
  1312  			<-done
  1313  
  1314  			require.NoError(t, err, "Expected the processMessagesToBlocks call to return without errors")
  1315  			require.Equal(t, uint64(1), counts[indexRecvPass], "Expected 1 message received and unmarshaled")
  1316  			require.Equal(t, uint64(1), counts[indexProcessTimeToCutPass], "Expected 1 TIMETOCUT message processed")
  1317  			require.Equal(t, lastCutBlockNumber, bareMinimumChain.lastCutBlockNumber, "Expected lastCutBlockNumber to stay the same")
  1318  		})
  1319  	})
  1320  
  1321  	t.Run("Connect", func(t *testing.T) {
  1322  		t.Run("ReceiveConnect", func(t *testing.T) {
  1323  			errorChan := make(chan struct{})
  1324  			close(errorChan)
  1325  			haltChan := make(chan struct{})
  1326  
  1327  			mockSupport := &mockmultichannel.ConsenterSupport{
  1328  				ChannelIDVal: mockChannel.topic(),
  1329  			}
  1330  
  1331  			bareMinimumChain := &chainImpl{
  1332  				parentConsumer:  mockParentConsumer,
  1333  				channelConsumer: mockChannelConsumer,
  1334  
  1335  				channel:          mockChannel,
  1336  				ConsenterSupport: mockSupport,
  1337  
  1338  				errorChan:                      errorChan,
  1339  				haltChan:                       haltChan,
  1340  				doneProcessingMessagesToBlocks: make(chan struct{}),
  1341  			}
  1342  
  1343  			var counts []uint64
  1344  			done := make(chan struct{})
  1345  
  1346  			go func() {
  1347  				counts, err = bareMinimumChain.processMessagesToBlocks()
  1348  				done <- struct{}{}
  1349  			}()
  1350  
  1351  			// This is the wrappedMessage that the for-loop will process
  1352  			mpc.YieldMessage(newMockConsumerMessage(newConnectMessage()))
  1353  
  1354  			logger.Debug("Closing haltChan to exit the infinite for-loop")
  1355  			close(haltChan) // Identical to chain.Halt()
  1356  			logger.Debug("haltChan closed")
  1357  			<-done
  1358  
  1359  			require.NoError(t, err, "Expected the processMessagesToBlocks call to return without errors")
  1360  			require.Equal(t, uint64(1), counts[indexRecvPass], "Expected 1 message received and unmarshaled")
  1361  			require.Equal(t, uint64(1), counts[indexProcessConnectPass], "Expected 1 CONNECT message processed")
  1362  		})
  1363  	})
  1364  
  1365  	t.Run("Regular", func(t *testing.T) {
  1366  		t.Run("Error", func(t *testing.T) {
  1367  			errorChan := make(chan struct{})
  1368  			close(errorChan)
  1369  			haltChan := make(chan struct{})
  1370  
  1371  			mockSupport := &mockmultichannel.ConsenterSupport{
  1372  				ChannelIDVal: mockChannel.topic(),
  1373  			}
  1374  
  1375  			bareMinimumChain := &chainImpl{
  1376  				parentConsumer:  mockParentConsumer,
  1377  				channelConsumer: mockChannelConsumer,
  1378  
  1379  				channel:          mockChannel,
  1380  				ConsenterSupport: mockSupport,
  1381  
  1382  				errorChan:                      errorChan,
  1383  				haltChan:                       haltChan,
  1384  				doneProcessingMessagesToBlocks: make(chan struct{}),
  1385  			}
  1386  
  1387  			var counts []uint64
  1388  			done := make(chan struct{})
  1389  
  1390  			go func() {
  1391  				counts, err = bareMinimumChain.processMessagesToBlocks()
  1392  				done <- struct{}{}
  1393  			}()
  1394  
  1395  			// This is the wrappedMessage that the for-loop will process
  1396  			mpc.YieldMessage(newMockConsumerMessage(newRegularMessage(tamperBytes(protoutil.MarshalOrPanic(newMockEnvelope("fooMessage"))))))
  1397  
  1398  			logger.Debug("Closing haltChan to exit the infinite for-loop")
  1399  			close(haltChan) // Identical to chain.Halt()
  1400  			logger.Debug("haltChan closed")
  1401  			<-done
  1402  
  1403  			require.NoError(t, err, "Expected the processMessagesToBlocks call to return without errors")
  1404  			require.Equal(t, uint64(1), counts[indexRecvPass], "Expected 1 message received and unmarshaled")
  1405  			require.Equal(t, uint64(1), counts[indexProcessRegularError], "Expected 1 damaged REGULAR message processed")
  1406  		})
  1407  
  1408  		// This ensures regular kafka messages of type UNKNOWN are handled properly
  1409  		t.Run("Unknown", func(t *testing.T) {
  1410  			t.Run("Enqueue", func(t *testing.T) {
  1411  				errorChan := make(chan struct{})
  1412  				close(errorChan)
  1413  				haltChan := make(chan struct{})
  1414  
  1415  				lastCutBlockNumber := uint64(3)
  1416  
  1417  				mockSupport := &mockmultichannel.ConsenterSupport{
  1418  					Blocks:          make(chan *cb.Block), // WriteBlock will post here
  1419  					BlockCutterVal:  mockblockcutter.NewReceiver(),
  1420  					ChannelIDVal:    mockChannel.topic(),
  1421  					HeightVal:       lastCutBlockNumber, // Incremented during the WriteBlock call
  1422  					SharedConfigVal: newMockOrderer(longTimeout, []string{mockBroker.Addr()}, false),
  1423  				}
  1424  				defer close(mockSupport.BlockCutterVal.Block)
  1425  
  1426  				bareMinimumChain := &chainImpl{
  1427  					parentConsumer:  mockParentConsumer,
  1428  					channelConsumer: mockChannelConsumer,
  1429  
  1430  					channel:            mockChannel,
  1431  					ConsenterSupport:   mockSupport,
  1432  					lastCutBlockNumber: lastCutBlockNumber,
  1433  
  1434  					errorChan:                      errorChan,
  1435  					haltChan:                       haltChan,
  1436  					doneProcessingMessagesToBlocks: make(chan struct{}),
  1437  				}
  1438  
  1439  				var counts []uint64
  1440  				done := make(chan struct{})
  1441  
  1442  				go func() {
  1443  					counts, err = bareMinimumChain.processMessagesToBlocks()
  1444  					done <- struct{}{}
  1445  				}()
  1446  
  1447  				// This is the wrappedMessage that the for-loop will process
  1448  				mpc.YieldMessage(newMockConsumerMessage(newRegularMessage(protoutil.MarshalOrPanic(newMockEnvelope("fooMessage")))))
  1449  
  1450  				mockSupport.BlockCutterVal.Block <- struct{}{} // Let the `mockblockcutter.Ordered` call return
  1451  				logger.Debugf("Mock blockcutter's Ordered call has returned")
  1452  
  1453  				logger.Debug("Closing haltChan to exit the infinite for-loop")
  1454  				// We are guaranteed to hit the haltChan branch after hitting the REGULAR branch at least once
  1455  				close(haltChan) // Identical to chain.Halt()
  1456  				logger.Debug("haltChan closed")
  1457  				<-done
  1458  
  1459  				require.NoError(t, err, "Expected the processMessagesToBlocks call to return without errors")
  1460  				require.Equal(t, uint64(1), counts[indexRecvPass], "Expected 1 message received and unmarshaled")
  1461  				require.Equal(t, uint64(1), counts[indexProcessRegularPass], "Expected 1 REGULAR message processed")
  1462  			})
  1463  
  1464  			t.Run("CutBlock", func(t *testing.T) {
  1465  				errorChan := make(chan struct{})
  1466  				close(errorChan)
  1467  				haltChan := make(chan struct{})
  1468  
  1469  				lastCutBlockNumber := uint64(3)
  1470  
  1471  				mockSupport := &mockmultichannel.ConsenterSupport{
  1472  					Blocks:          make(chan *cb.Block), // WriteBlock will post here
  1473  					BlockCutterVal:  mockblockcutter.NewReceiver(),
  1474  					ChannelIDVal:    mockChannel.topic(),
  1475  					HeightVal:       lastCutBlockNumber, // Incremented during the WriteBlock call
  1476  					SharedConfigVal: newMockOrderer(longTimeout, []string{mockBroker.Addr()}, false),
  1477  				}
  1478  				defer close(mockSupport.BlockCutterVal.Block)
  1479  
  1480  				fakeLastOffsetPersisted := &mockkafka.MetricsGauge{}
  1481  				fakeLastOffsetPersisted.WithReturns(fakeLastOffsetPersisted)
  1482  				mockConsenter.(*consenterImpl).metrics.LastOffsetPersisted = fakeLastOffsetPersisted
  1483  
  1484  				bareMinimumChain := &chainImpl{
  1485  					parentConsumer:  mockParentConsumer,
  1486  					channelConsumer: mockChannelConsumer,
  1487  
  1488  					consenter:          mockConsenter,
  1489  					channel:            mockChannel,
  1490  					ConsenterSupport:   mockSupport,
  1491  					lastCutBlockNumber: lastCutBlockNumber,
  1492  
  1493  					errorChan:                      errorChan,
  1494  					haltChan:                       haltChan,
  1495  					doneProcessingMessagesToBlocks: make(chan struct{}),
  1496  				}
  1497  
  1498  				var counts []uint64
  1499  				done := make(chan struct{})
  1500  
  1501  				go func() {
  1502  					counts, err = bareMinimumChain.processMessagesToBlocks()
  1503  					done <- struct{}{}
  1504  				}()
  1505  
  1506  				mockSupport.BlockCutterVal.CutNext = true
  1507  
  1508  				// This is the wrappedMessage that the for-loop will process
  1509  				mpc.YieldMessage(newMockConsumerMessage(newRegularMessage(protoutil.MarshalOrPanic(newMockEnvelope("fooMessage")))))
  1510  
  1511  				mockSupport.BlockCutterVal.Block <- struct{}{} // Let the `mockblockcutter.Ordered` call return
  1512  				logger.Debugf("Mock blockcutter's Ordered call has returned")
  1513  				<-mockSupport.Blocks // Let the `mockConsenterSupport.WriteBlock` proceed
  1514  
  1515  				logger.Debug("Closing haltChan to exit the infinite for-loop")
  1516  				close(haltChan) // Identical to chain.Halt()
  1517  				logger.Debug("haltChan closed")
  1518  				<-done
  1519  
  1520  				require.NoError(t, err, "Expected the processMessagesToBlocks call to return without errors")
  1521  				require.Equal(t, uint64(1), counts[indexRecvPass], "Expected 1 message received and unmarshaled")
  1522  				require.Equal(t, uint64(1), counts[indexProcessRegularPass], "Expected 1 REGULAR message processed")
  1523  				require.Equal(t, lastCutBlockNumber+1, bareMinimumChain.lastCutBlockNumber, "Expected lastCutBlockNumber to be bumped up by one")
  1524  
  1525  				require.Equal(t, fakeLastOffsetPersisted.WithCallCount(), 1)
  1526  				require.Equal(t, fakeLastOffsetPersisted.WithArgsForCall(0), []string{"channel", "mockChannelFoo"})
  1527  				require.Equal(t, fakeLastOffsetPersisted.SetCallCount(), 1)
  1528  				require.Equal(t, fakeLastOffsetPersisted.SetArgsForCall(0), float64(9))
  1529  			})
  1530  
  1531  			// This test ensures the corner case in FAB-5709 is taken care of
  1532  			t.Run("SecondTxOverflows", func(t *testing.T) {
  1533  				if testing.Short() {
  1534  					t.Skip("Skipping test in short mode")
  1535  				}
  1536  
  1537  				errorChan := make(chan struct{})
  1538  				close(errorChan)
  1539  				haltChan := make(chan struct{})
  1540  
  1541  				lastCutBlockNumber := uint64(3)
  1542  
  1543  				mockSupport := &mockmultichannel.ConsenterSupport{
  1544  					Blocks:          make(chan *cb.Block), // WriteBlock will post here
  1545  					BlockCutterVal:  mockblockcutter.NewReceiver(),
  1546  					ChannelIDVal:    mockChannel.topic(),
  1547  					HeightVal:       lastCutBlockNumber, // Incremented during the WriteBlock call
  1548  					SharedConfigVal: newMockOrderer(longTimeout, []string{mockBroker.Addr()}, false),
  1549  				}
  1550  				defer close(mockSupport.BlockCutterVal.Block)
  1551  
  1552  				bareMinimumChain := &chainImpl{
  1553  					parentConsumer:  mockParentConsumer,
  1554  					channelConsumer: mockChannelConsumer,
  1555  
  1556  					consenter:          mockConsenter,
  1557  					channel:            mockChannel,
  1558  					ConsenterSupport:   mockSupport,
  1559  					lastCutBlockNumber: lastCutBlockNumber,
  1560  
  1561  					errorChan:                      errorChan,
  1562  					haltChan:                       haltChan,
  1563  					doneProcessingMessagesToBlocks: make(chan struct{}),
  1564  				}
  1565  
  1566  				var counts []uint64
  1567  				done := make(chan struct{})
  1568  
  1569  				go func() {
  1570  					counts, err = bareMinimumChain.processMessagesToBlocks()
  1571  					done <- struct{}{}
  1572  				}()
  1573  
  1574  				var block1, block2 *cb.Block
  1575  
  1576  				block1LastOffset := mpc.HighWaterMarkOffset()
  1577  				mpc.YieldMessage(newMockConsumerMessage(newRegularMessage(protoutil.MarshalOrPanic(newMockEnvelope("fooMessage")))))
  1578  				mockSupport.BlockCutterVal.Block <- struct{}{} // Let the `mockblockcutter.Ordered` call return
  1579  
  1580  				// Set CutAncestors to true so that second message overflows receiver batch
  1581  				mockSupport.BlockCutterVal.CutAncestors = true
  1582  				mpc.YieldMessage(newMockConsumerMessage(newRegularMessage(protoutil.MarshalOrPanic(newMockEnvelope("fooMessage")))))
  1583  				mockSupport.BlockCutterVal.Block <- struct{}{}
  1584  
  1585  				select {
  1586  				case block1 = <-mockSupport.Blocks: // Let the `mockConsenterSupport.WriteBlock` proceed
  1587  				case <-time.After(shortTimeout):
  1588  					logger.Fatalf("Did not receive a block from the blockcutter as expected")
  1589  				}
  1590  
  1591  				// Set CutNext to true to flush all pending messages
  1592  				mockSupport.BlockCutterVal.CutAncestors = false
  1593  				mockSupport.BlockCutterVal.CutNext = true
  1594  				block2LastOffset := mpc.HighWaterMarkOffset()
  1595  				mpc.YieldMessage(newMockConsumerMessage(newRegularMessage(protoutil.MarshalOrPanic(newMockEnvelope("fooMessage")))))
  1596  				mockSupport.BlockCutterVal.Block <- struct{}{}
  1597  
  1598  				select {
  1599  				case block2 = <-mockSupport.Blocks:
  1600  				case <-time.After(shortTimeout):
  1601  					logger.Fatalf("Did not receive a block from the blockcutter as expected")
  1602  				}
  1603  
  1604  				logger.Debug("Closing haltChan to exit the infinite for-loop")
  1605  				close(haltChan) // Identical to chain.Halt()
  1606  				logger.Debug("haltChan closed")
  1607  				<-done
  1608  
  1609  				require.NoError(t, err, "Expected the processMessagesToBlocks call to return without errors")
  1610  				require.Equal(t, uint64(3), counts[indexRecvPass], "Expected 2 messages received and unmarshaled")
  1611  				require.Equal(t, uint64(3), counts[indexProcessRegularPass], "Expected 2 REGULAR messages processed")
  1612  				require.Equal(t, lastCutBlockNumber+2, bareMinimumChain.lastCutBlockNumber, "Expected lastCutBlockNumber to be bumped up by two")
  1613  				require.Equal(t, block1LastOffset, extractEncodedOffset(block1.GetMetadata().Metadata[cb.BlockMetadataIndex_ORDERER]), "Expected encoded offset in first block to be %d", block1LastOffset)
  1614  				require.Equal(t, block2LastOffset, extractEncodedOffset(block2.GetMetadata().Metadata[cb.BlockMetadataIndex_ORDERER]), "Expected encoded offset in second block to be %d", block2LastOffset)
  1615  			})
  1616  
  1617  			t.Run("InvalidConfigEnv", func(t *testing.T) {
  1618  				errorChan := make(chan struct{})
  1619  				close(errorChan)
  1620  				haltChan := make(chan struct{})
  1621  
  1622  				lastCutBlockNumber := uint64(3)
  1623  
  1624  				mockSupport := &mockmultichannel.ConsenterSupport{
  1625  					Blocks:              make(chan *cb.Block), // WriteBlock will post here
  1626  					BlockCutterVal:      mockblockcutter.NewReceiver(),
  1627  					ChannelIDVal:        mockChannel.topic(),
  1628  					HeightVal:           lastCutBlockNumber, // Incremented during the WriteBlock call
  1629  					ClassifyMsgVal:      msgprocessor.ConfigMsg,
  1630  					ProcessConfigMsgErr: fmt.Errorf("Invalid config message"),
  1631  					SharedConfigVal:     newMockOrderer(longTimeout, []string{mockBroker.Addr()}, false),
  1632  				}
  1633  				defer close(mockSupport.BlockCutterVal.Block)
  1634  
  1635  				bareMinimumChain := &chainImpl{
  1636  					parentConsumer:  mockParentConsumer,
  1637  					channelConsumer: mockChannelConsumer,
  1638  
  1639  					channel:            mockChannel,
  1640  					ConsenterSupport:   mockSupport,
  1641  					lastCutBlockNumber: lastCutBlockNumber,
  1642  
  1643  					errorChan:                      errorChan,
  1644  					haltChan:                       haltChan,
  1645  					doneProcessingMessagesToBlocks: make(chan struct{}),
  1646  				}
  1647  
  1648  				var counts []uint64
  1649  				done := make(chan struct{})
  1650  
  1651  				go func() {
  1652  					counts, err = bareMinimumChain.processMessagesToBlocks()
  1653  					done <- struct{}{}
  1654  				}()
  1655  
  1656  				// This is the config wrappedMessage that the for-loop will process.
  1657  				mpc.YieldMessage(newMockConsumerMessage(newRegularMessage(protoutil.MarshalOrPanic(newMockConfigEnvelope()))))
  1658  
  1659  				logger.Debug("Closing haltChan to exit the infinite for-loop")
  1660  				// We are guaranteed to hit the haltChan branch after hitting the REGULAR branch at least once
  1661  				close(haltChan) // Identical to chain.Halt()
  1662  				logger.Debug("haltChan closed")
  1663  				<-done
  1664  
  1665  				require.NoError(t, err, "Expected the processMessagesToBlocks call to return without errors")
  1666  				require.Equal(t, uint64(1), counts[indexRecvPass], "Expected 1 message received and unmarshaled")
  1667  				require.Equal(t, uint64(1), counts[indexProcessRegularError], "Expected 1 REGULAR message error")
  1668  				require.Equal(t, lastCutBlockNumber, bareMinimumChain.lastCutBlockNumber, "Expected lastCutBlockNumber not to be incremented")
  1669  			})
  1670  
  1671  			t.Run("InvalidOrdererTxEnv", func(t *testing.T) {
  1672  				errorChan := make(chan struct{})
  1673  				close(errorChan)
  1674  				haltChan := make(chan struct{})
  1675  
  1676  				lastCutBlockNumber := uint64(3)
  1677  
  1678  				mockSupport := &mockmultichannel.ConsenterSupport{
  1679  					Blocks:              make(chan *cb.Block), // WriteBlock will post here
  1680  					BlockCutterVal:      mockblockcutter.NewReceiver(),
  1681  					ChannelIDVal:        mockChannel.topic(),
  1682  					HeightVal:           lastCutBlockNumber, // Incremented during the WriteBlock call
  1683  					ClassifyMsgVal:      msgprocessor.ConfigMsg,
  1684  					ProcessConfigMsgErr: fmt.Errorf("Invalid config message"),
  1685  					SharedConfigVal:     newMockOrderer(longTimeout, []string{mockBroker.Addr()}, false),
  1686  				}
  1687  				defer close(mockSupport.BlockCutterVal.Block)
  1688  
  1689  				bareMinimumChain := &chainImpl{
  1690  					parentConsumer:  mockParentConsumer,
  1691  					channelConsumer: mockChannelConsumer,
  1692  
  1693  					channel:            mockChannel,
  1694  					ConsenterSupport:   mockSupport,
  1695  					lastCutBlockNumber: lastCutBlockNumber,
  1696  
  1697  					errorChan:                      errorChan,
  1698  					haltChan:                       haltChan,
  1699  					doneProcessingMessagesToBlocks: make(chan struct{}),
  1700  				}
  1701  
  1702  				var counts []uint64
  1703  				done := make(chan struct{})
  1704  
  1705  				go func() {
  1706  					counts, err = bareMinimumChain.processMessagesToBlocks()
  1707  					done <- struct{}{}
  1708  				}()
  1709  
  1710  				// This is the config wrappedMessage that the for-loop will process.
  1711  				mpc.YieldMessage(newMockConsumerMessage(newRegularMessage(protoutil.MarshalOrPanic(newMockOrdererTxEnvelope()))))
  1712  
  1713  				logger.Debug("Closing haltChan to exit the infinite for-loop")
  1714  				// We are guaranteed to hit the haltChan branch after hitting the REGULAR branch at least once
  1715  				close(haltChan) // Identical to chain.Halt()
  1716  				logger.Debug("haltChan closed")
  1717  				<-done
  1718  
  1719  				require.NoError(t, err, "Expected the processMessagesToBlocks call to return without errors")
  1720  				require.Equal(t, uint64(1), counts[indexRecvPass], "Expected 1 message received and unmarshaled")
  1721  				require.Equal(t, uint64(1), counts[indexProcessRegularError], "Expected 1 REGULAR message error")
  1722  				require.Equal(t, lastCutBlockNumber, bareMinimumChain.lastCutBlockNumber, "Expected lastCutBlockNumber not to be incremented")
  1723  			})
  1724  
  1725  			t.Run("InvalidNormalEnv", func(t *testing.T) {
  1726  				errorChan := make(chan struct{})
  1727  				close(errorChan)
  1728  				haltChan := make(chan struct{})
  1729  
  1730  				lastCutBlockNumber := uint64(3)
  1731  
  1732  				mockSupport := &mockmultichannel.ConsenterSupport{
  1733  					Blocks:              make(chan *cb.Block), // WriteBlock will post here
  1734  					BlockCutterVal:      mockblockcutter.NewReceiver(),
  1735  					ChannelIDVal:        mockChannel.topic(),
  1736  					HeightVal:           lastCutBlockNumber, // Incremented during the WriteBlock call
  1737  					SharedConfigVal:     newMockOrderer(longTimeout, []string{mockBroker.Addr()}, false),
  1738  					ProcessNormalMsgErr: fmt.Errorf("Invalid normal message"),
  1739  				}
  1740  				defer close(mockSupport.BlockCutterVal.Block)
  1741  
  1742  				bareMinimumChain := &chainImpl{
  1743  					parentConsumer:  mockParentConsumer,
  1744  					channelConsumer: mockChannelConsumer,
  1745  
  1746  					channel:            mockChannel,
  1747  					ConsenterSupport:   mockSupport,
  1748  					lastCutBlockNumber: lastCutBlockNumber,
  1749  
  1750  					errorChan:                      errorChan,
  1751  					haltChan:                       haltChan,
  1752  					doneProcessingMessagesToBlocks: make(chan struct{}),
  1753  				}
  1754  
  1755  				var counts []uint64
  1756  				done := make(chan struct{})
  1757  
  1758  				go func() {
  1759  					counts, err = bareMinimumChain.processMessagesToBlocks()
  1760  					done <- struct{}{}
  1761  				}()
  1762  
  1763  				// This is the wrappedMessage that the for-loop will process
  1764  				mpc.YieldMessage(newMockConsumerMessage(newRegularMessage(protoutil.MarshalOrPanic(newMockEnvelope("fooMessage")))))
  1765  
  1766  				close(haltChan) // Identical to chain.Halt()
  1767  				<-done
  1768  
  1769  				require.NoError(t, err, "Expected the processMessagesToBlocks call to return without errors")
  1770  				require.Equal(t, uint64(1), counts[indexRecvPass], "Expected 1 message received and unmarshaled")
  1771  				require.Equal(t, uint64(1), counts[indexProcessRegularError], "Expected 1 REGULAR message processed")
  1772  			})
  1773  
  1774  			t.Run("CutConfigEnv", func(t *testing.T) {
  1775  				errorChan := make(chan struct{})
  1776  				close(errorChan)
  1777  				haltChan := make(chan struct{})
  1778  
  1779  				lastCutBlockNumber := uint64(3)
  1780  
  1781  				mockSupport := &mockmultichannel.ConsenterSupport{
  1782  					Blocks:          make(chan *cb.Block), // WriteBlock will post here
  1783  					BlockCutterVal:  mockblockcutter.NewReceiver(),
  1784  					ChannelIDVal:    mockChannel.topic(),
  1785  					HeightVal:       lastCutBlockNumber, // Incremented during the WriteBlock call
  1786  					SharedConfigVal: newMockOrderer(longTimeout, []string{mockBroker.Addr()}, false),
  1787  					ClassifyMsgVal:  msgprocessor.ConfigMsg,
  1788  				}
  1789  				defer close(mockSupport.BlockCutterVal.Block)
  1790  
  1791  				bareMinimumChain := &chainImpl{
  1792  					parentConsumer:  mockParentConsumer,
  1793  					channelConsumer: mockChannelConsumer,
  1794  
  1795  					consenter:          mockConsenter,
  1796  					channel:            mockChannel,
  1797  					ConsenterSupport:   mockSupport,
  1798  					lastCutBlockNumber: lastCutBlockNumber,
  1799  
  1800  					errorChan:                      errorChan,
  1801  					haltChan:                       haltChan,
  1802  					doneProcessingMessagesToBlocks: make(chan struct{}),
  1803  				}
  1804  
  1805  				var counts []uint64
  1806  				done := make(chan struct{})
  1807  
  1808  				go func() {
  1809  					counts, err = bareMinimumChain.processMessagesToBlocks()
  1810  					done <- struct{}{}
  1811  				}()
  1812  
  1813  				configBlkOffset := mpc.HighWaterMarkOffset()
  1814  				mpc.YieldMessage(newMockConsumerMessage(newRegularMessage(protoutil.MarshalOrPanic(newMockConfigEnvelope()))))
  1815  
  1816  				var configBlk *cb.Block
  1817  
  1818  				select {
  1819  				case configBlk = <-mockSupport.Blocks:
  1820  				case <-time.After(shortTimeout):
  1821  					logger.Fatalf("Did not receive a config block from the blockcutter as expected")
  1822  				}
  1823  
  1824  				close(haltChan) // Identical to chain.Halt()
  1825  				<-done
  1826  
  1827  				require.NoError(t, err, "Expected the processMessagesToBlocks call to return without errors")
  1828  				require.Equal(t, uint64(1), counts[indexRecvPass], "Expected 1 message received and unmarshaled")
  1829  				require.Equal(t, uint64(1), counts[indexProcessRegularPass], "Expected 1 REGULAR message processed")
  1830  				require.Equal(t, lastCutBlockNumber+1, bareMinimumChain.lastCutBlockNumber, "Expected lastCutBlockNumber to be incremented by 1")
  1831  				require.Equal(t, configBlkOffset, extractEncodedOffset(configBlk.GetMetadata().Metadata[cb.BlockMetadataIndex_ORDERER]), "Expected encoded offset in second block to be %d", configBlkOffset)
  1832  			})
  1833  
  1834  			// We are not expecting this type of message from Kafka
  1835  			t.Run("ConfigUpdateEnv", func(t *testing.T) {
  1836  				errorChan := make(chan struct{})
  1837  				close(errorChan)
  1838  				haltChan := make(chan struct{})
  1839  
  1840  				lastCutBlockNumber := uint64(3)
  1841  
  1842  				mockSupport := &mockmultichannel.ConsenterSupport{
  1843  					Blocks:          make(chan *cb.Block), // WriteBlock will post here
  1844  					BlockCutterVal:  mockblockcutter.NewReceiver(),
  1845  					ChannelIDVal:    mockChannel.topic(),
  1846  					HeightVal:       lastCutBlockNumber, // Incremented during the WriteBlock call
  1847  					SharedConfigVal: newMockOrderer(longTimeout, []string{mockBroker.Addr()}, false),
  1848  					ClassifyMsgVal:  msgprocessor.ConfigUpdateMsg,
  1849  				}
  1850  				defer close(mockSupport.BlockCutterVal.Block)
  1851  
  1852  				bareMinimumChain := &chainImpl{
  1853  					parentConsumer:  mockParentConsumer,
  1854  					channelConsumer: mockChannelConsumer,
  1855  
  1856  					channel:            mockChannel,
  1857  					ConsenterSupport:   mockSupport,
  1858  					lastCutBlockNumber: lastCutBlockNumber,
  1859  
  1860  					errorChan:                      errorChan,
  1861  					haltChan:                       haltChan,
  1862  					doneProcessingMessagesToBlocks: make(chan struct{}),
  1863  				}
  1864  
  1865  				var counts []uint64
  1866  				done := make(chan struct{})
  1867  
  1868  				go func() {
  1869  					counts, err = bareMinimumChain.processMessagesToBlocks()
  1870  					done <- struct{}{}
  1871  				}()
  1872  
  1873  				mpc.YieldMessage(newMockConsumerMessage(newRegularMessage(protoutil.MarshalOrPanic(newMockEnvelope("FooMessage")))))
  1874  
  1875  				close(haltChan) // Identical to chain.Halt()
  1876  				<-done
  1877  
  1878  				require.NoError(t, err, "Expected the processMessagesToBlocks call to return without errors")
  1879  				require.Equal(t, uint64(1), counts[indexRecvPass], "Expected 1 message received and unmarshaled")
  1880  				require.Equal(t, uint64(1), counts[indexProcessRegularError], "Expected 1 REGULAR message processed")
  1881  			})
  1882  
  1883  			t.Run("SendTimeToCut", func(t *testing.T) {
  1884  				t.Skip("Skipping test as it introduces a race condition")
  1885  
  1886  				// NB We haven't set a handlermap for the mock broker so we need to set
  1887  				// the ProduceResponse
  1888  				successResponse := new(sarama.ProduceResponse)
  1889  				successResponse.AddTopicPartition(mockChannel.topic(), mockChannel.partition(), sarama.ErrNoError)
  1890  				mockBroker.Returns(successResponse)
  1891  
  1892  				errorChan := make(chan struct{})
  1893  				close(errorChan)
  1894  				haltChan := make(chan struct{})
  1895  
  1896  				lastCutBlockNumber := uint64(3)
  1897  
  1898  				mockSupport := &mockmultichannel.ConsenterSupport{
  1899  					Blocks:          make(chan *cb.Block), // WriteBlock will post here
  1900  					BlockCutterVal:  mockblockcutter.NewReceiver(),
  1901  					ChannelIDVal:    mockChannel.topic(),
  1902  					HeightVal:       lastCutBlockNumber,                                                    // Incremented during the WriteBlock call
  1903  					SharedConfigVal: newMockOrderer(extraShortTimeout, []string{mockBroker.Addr()}, false), // ATTN
  1904  				}
  1905  				defer close(mockSupport.BlockCutterVal.Block)
  1906  
  1907  				bareMinimumChain := &chainImpl{
  1908  					producer:        producer,
  1909  					parentConsumer:  mockParentConsumer,
  1910  					channelConsumer: mockChannelConsumer,
  1911  
  1912  					channel:            mockChannel,
  1913  					ConsenterSupport:   mockSupport,
  1914  					lastCutBlockNumber: lastCutBlockNumber,
  1915  
  1916  					errorChan:                      errorChan,
  1917  					haltChan:                       haltChan,
  1918  					doneProcessingMessagesToBlocks: make(chan struct{}),
  1919  				}
  1920  
  1921  				var counts []uint64
  1922  				done := make(chan struct{})
  1923  
  1924  				go func() {
  1925  					counts, err = bareMinimumChain.processMessagesToBlocks()
  1926  					done <- struct{}{}
  1927  				}()
  1928  
  1929  				// This is the wrappedMessage that the for-loop will process
  1930  				mpc.YieldMessage(newMockConsumerMessage(newRegularMessage(protoutil.MarshalOrPanic(newMockEnvelope("fooMessage")))))
  1931  
  1932  				mockSupport.BlockCutterVal.Block <- struct{}{} // Let the `mockblockcutter.Ordered` call return
  1933  				logger.Debugf("Mock blockcutter's Ordered call has returned")
  1934  
  1935  				// Sleep so that the timer branch is activated before the exitChan one.
  1936  				// TODO This is a race condition, will fix in follow-up changeset
  1937  				time.Sleep(hitBranch)
  1938  
  1939  				logger.Debug("Closing haltChan to exit the infinite for-loop")
  1940  				close(haltChan) // Identical to chain.Halt()
  1941  				logger.Debug("haltChan closed")
  1942  				<-done
  1943  
  1944  				require.NoError(t, err, "Expected the processMessagesToBlocks call to return without errors")
  1945  				require.Equal(t, uint64(1), counts[indexRecvPass], "Expected 1 message received and unmarshaled")
  1946  				require.Equal(t, uint64(1), counts[indexProcessRegularPass], "Expected 1 REGULAR message processed")
  1947  				require.Equal(t, uint64(1), counts[indexSendTimeToCutPass], "Expected 1 TIMER event processed")
  1948  				require.Equal(t, lastCutBlockNumber, bareMinimumChain.lastCutBlockNumber, "Expected lastCutBlockNumber to stay the same")
  1949  			})
  1950  
  1951  			t.Run("SendTimeToCutError", func(t *testing.T) {
  1952  				// Note that this test is affected by the following parameters:
  1953  				// - Net.ReadTimeout
  1954  				// - Consumer.Retry.Backoff
  1955  				// - Metadata.Retry.Max
  1956  
  1957  				t.Skip("Skipping test as it introduces a race condition")
  1958  
  1959  				// Exact same test as ReceiveRegularAndSendTimeToCut.
  1960  				// Only difference is that the producer's attempt to send a TTC will
  1961  				// fail with an ErrNotEnoughReplicas error.
  1962  				failureResponse := new(sarama.ProduceResponse)
  1963  				failureResponse.AddTopicPartition(mockChannel.topic(), mockChannel.partition(), sarama.ErrNotEnoughReplicas)
  1964  				mockBroker.Returns(failureResponse)
  1965  
  1966  				errorChan := make(chan struct{})
  1967  				close(errorChan)
  1968  				haltChan := make(chan struct{})
  1969  
  1970  				lastCutBlockNumber := uint64(3)
  1971  
  1972  				mockSupport := &mockmultichannel.ConsenterSupport{
  1973  					Blocks:          make(chan *cb.Block), // WriteBlock will post here
  1974  					BlockCutterVal:  mockblockcutter.NewReceiver(),
  1975  					ChannelIDVal:    mockChannel.topic(),
  1976  					HeightVal:       lastCutBlockNumber,                                                    // Incremented during the WriteBlock call
  1977  					SharedConfigVal: newMockOrderer(extraShortTimeout, []string{mockBroker.Addr()}, false), // ATTN
  1978  				}
  1979  				defer close(mockSupport.BlockCutterVal.Block)
  1980  
  1981  				bareMinimumChain := &chainImpl{
  1982  					producer:        producer,
  1983  					parentConsumer:  mockParentConsumer,
  1984  					channelConsumer: mockChannelConsumer,
  1985  
  1986  					channel:            mockChannel,
  1987  					ConsenterSupport:   mockSupport,
  1988  					lastCutBlockNumber: lastCutBlockNumber,
  1989  
  1990  					errorChan:                      errorChan,
  1991  					haltChan:                       haltChan,
  1992  					doneProcessingMessagesToBlocks: make(chan struct{}),
  1993  				}
  1994  
  1995  				var counts []uint64
  1996  				done := make(chan struct{})
  1997  
  1998  				go func() {
  1999  					counts, err = bareMinimumChain.processMessagesToBlocks()
  2000  					done <- struct{}{}
  2001  				}()
  2002  
  2003  				// This is the wrappedMessage that the for-loop will process
  2004  				mpc.YieldMessage(newMockConsumerMessage(newRegularMessage(protoutil.MarshalOrPanic(newMockEnvelope("fooMessage")))))
  2005  
  2006  				mockSupport.BlockCutterVal.Block <- struct{}{} // Let the `mockblockcutter.Ordered` call return
  2007  				logger.Debugf("Mock blockcutter's Ordered call has returned")
  2008  
  2009  				// Sleep so that the timer branch is activated before the exitChan one.
  2010  				// TODO This is a race condition, will fix in follow-up changeset
  2011  				time.Sleep(hitBranch)
  2012  
  2013  				logger.Debug("Closing haltChan to exit the infinite for-loop")
  2014  				close(haltChan) // Identical to chain.Halt()
  2015  				logger.Debug("haltChan closed")
  2016  				<-done
  2017  
  2018  				require.NoError(t, err, "Expected the processMessagesToBlocks call to return without errors")
  2019  				require.Equal(t, uint64(1), counts[indexRecvPass], "Expected 1 message received and unmarshaled")
  2020  				require.Equal(t, uint64(1), counts[indexProcessRegularPass], "Expected 1 REGULAR message processed")
  2021  				require.Equal(t, uint64(1), counts[indexSendTimeToCutError], "Expected 1 faulty TIMER event processed")
  2022  				require.Equal(t, lastCutBlockNumber, bareMinimumChain.lastCutBlockNumber, "Expected lastCutBlockNumber to stay the same")
  2023  			})
  2024  		})
  2025  
  2026  		// This ensures regular kafka messages of type NORMAL are handled properly
  2027  		t.Run("Normal", func(t *testing.T) {
  2028  			lastOriginalOffsetProcessed := int64(3)
  2029  
  2030  			t.Run("ReceiveTwoRegularAndCutTwoBlocks", func(t *testing.T) {
  2031  				if testing.Short() {
  2032  					t.Skip("Skipping test in short mode")
  2033  				}
  2034  
  2035  				errorChan := make(chan struct{})
  2036  				close(errorChan)
  2037  				haltChan := make(chan struct{})
  2038  
  2039  				lastCutBlockNumber := uint64(3)
  2040  
  2041  				mockSupport := &mockmultichannel.ConsenterSupport{
  2042  					Blocks:          make(chan *cb.Block), // WriteBlock will post here
  2043  					BlockCutterVal:  mockblockcutter.NewReceiver(),
  2044  					ChannelIDVal:    mockChannel.topic(),
  2045  					HeightVal:       lastCutBlockNumber, // Incremented during the WriteBlock call
  2046  					SharedConfigVal: newMockOrderer(longTimeout, []string{mockBroker.Addr()}, false),
  2047  					SequenceVal:     uint64(0),
  2048  				}
  2049  				defer close(mockSupport.BlockCutterVal.Block)
  2050  
  2051  				bareMinimumChain := &chainImpl{
  2052  					parentConsumer:  mockParentConsumer,
  2053  					channelConsumer: mockChannelConsumer,
  2054  
  2055  					consenter:                   mockConsenter,
  2056  					channel:                     mockChannel,
  2057  					ConsenterSupport:            mockSupport,
  2058  					lastCutBlockNumber:          lastCutBlockNumber,
  2059  					lastOriginalOffsetProcessed: lastOriginalOffsetProcessed,
  2060  
  2061  					errorChan:                      errorChan,
  2062  					haltChan:                       haltChan,
  2063  					doneProcessingMessagesToBlocks: make(chan struct{}),
  2064  				}
  2065  
  2066  				var counts []uint64
  2067  				done := make(chan struct{})
  2068  
  2069  				go func() {
  2070  					counts, err = bareMinimumChain.processMessagesToBlocks()
  2071  					done <- struct{}{}
  2072  				}()
  2073  
  2074  				var block1, block2 *cb.Block
  2075  
  2076  				// This is the first wrappedMessage that the for-loop will process
  2077  				block1LastOffset := mpc.HighWaterMarkOffset()
  2078  				mpc.YieldMessage(newMockConsumerMessage(newNormalMessage(protoutil.MarshalOrPanic(newMockEnvelope("fooMessage")), uint64(0), int64(0))))
  2079  				mockSupport.BlockCutterVal.Block <- struct{}{} // Let the `mockblockcutter.Ordered` call return
  2080  				logger.Debugf("Mock blockcutter's Ordered call has returned")
  2081  
  2082  				mockSupport.BlockCutterVal.IsolatedTx = true
  2083  
  2084  				// This is the first wrappedMessage that the for-loop will process
  2085  				block2LastOffset := mpc.HighWaterMarkOffset()
  2086  				mpc.YieldMessage(newMockConsumerMessage(newNormalMessage(protoutil.MarshalOrPanic(newMockEnvelope("fooMessage")), uint64(0), int64(0))))
  2087  				mockSupport.BlockCutterVal.Block <- struct{}{}
  2088  				logger.Debugf("Mock blockcutter's Ordered call has returned for the second time")
  2089  
  2090  				select {
  2091  				case block1 = <-mockSupport.Blocks: // Let the `mockConsenterSupport.WriteBlock` proceed
  2092  				case <-time.After(shortTimeout):
  2093  					logger.Fatalf("Did not receive a block from the blockcutter as expected")
  2094  				}
  2095  
  2096  				select {
  2097  				case block2 = <-mockSupport.Blocks:
  2098  				case <-time.After(shortTimeout):
  2099  					logger.Fatalf("Did not receive a block from the blockcutter as expected")
  2100  				}
  2101  
  2102  				logger.Debug("Closing haltChan to exit the infinite for-loop")
  2103  				close(haltChan) // Identical to chain.Halt()
  2104  				logger.Debug("haltChan closed")
  2105  				<-done
  2106  
  2107  				require.NoError(t, err, "Expected the processMessagesToBlocks call to return without errors")
  2108  				require.Equal(t, uint64(2), counts[indexRecvPass], "Expected 2 messages received and unmarshaled")
  2109  				require.Equal(t, uint64(2), counts[indexProcessRegularPass], "Expected 2 REGULAR messages processed")
  2110  				require.Equal(t, lastCutBlockNumber+2, bareMinimumChain.lastCutBlockNumber, "Expected lastCutBlockNumber to be bumped up by two")
  2111  				require.Equal(t, block1LastOffset, extractEncodedOffset(block1.GetMetadata().Metadata[cb.BlockMetadataIndex_ORDERER]), "Expected encoded offset in first block to be %d", block1LastOffset)
  2112  				require.Equal(t, block2LastOffset, extractEncodedOffset(block2.GetMetadata().Metadata[cb.BlockMetadataIndex_ORDERER]), "Expected encoded offset in second block to be %d", block2LastOffset)
  2113  			})
  2114  
  2115  			t.Run("ReceiveRegularAndQueue", func(t *testing.T) {
  2116  				if testing.Short() {
  2117  					t.Skip("Skipping test in short mode")
  2118  				}
  2119  
  2120  				errorChan := make(chan struct{})
  2121  				close(errorChan)
  2122  				haltChan := make(chan struct{})
  2123  
  2124  				lastCutBlockNumber := uint64(3)
  2125  
  2126  				mockSupport := &mockmultichannel.ConsenterSupport{
  2127  					Blocks:          make(chan *cb.Block), // WriteBlock will post here
  2128  					BlockCutterVal:  mockblockcutter.NewReceiver(),
  2129  					ChannelIDVal:    mockChannel.topic(),
  2130  					HeightVal:       lastCutBlockNumber, // Incremented during the WriteBlock call
  2131  					SharedConfigVal: newMockOrderer(longTimeout, []string{mockBroker.Addr()}, false),
  2132  				}
  2133  				defer close(mockSupport.BlockCutterVal.Block)
  2134  
  2135  				bareMinimumChain := &chainImpl{
  2136  					parentConsumer:  mockParentConsumer,
  2137  					channelConsumer: mockChannelConsumer,
  2138  
  2139  					consenter:                   mockConsenter,
  2140  					channel:                     mockChannel,
  2141  					ConsenterSupport:            mockSupport,
  2142  					lastCutBlockNumber:          lastCutBlockNumber,
  2143  					lastOriginalOffsetProcessed: lastOriginalOffsetProcessed,
  2144  
  2145  					errorChan:                      errorChan,
  2146  					haltChan:                       haltChan,
  2147  					doneProcessingMessagesToBlocks: make(chan struct{}),
  2148  				}
  2149  
  2150  				var counts []uint64
  2151  				done := make(chan struct{})
  2152  
  2153  				go func() {
  2154  					counts, err = bareMinimumChain.processMessagesToBlocks()
  2155  					done <- struct{}{}
  2156  				}()
  2157  
  2158  				mockSupport.BlockCutterVal.CutNext = true
  2159  
  2160  				mpc.YieldMessage(newMockConsumerMessage(newNormalMessage(protoutil.MarshalOrPanic(newMockEnvelope("fooMessage")), uint64(0), int64(0))))
  2161  				mockSupport.BlockCutterVal.Block <- struct{}{} // Let the `mockblockcutter.Ordered` call return
  2162  				<-mockSupport.Blocks
  2163  
  2164  				close(haltChan)
  2165  				logger.Debug("haltChan closed")
  2166  				<-done
  2167  
  2168  				require.NoError(t, err, "Expected the processMessagesToBlocks call to return without errors")
  2169  				require.Equal(t, uint64(1), counts[indexRecvPass], "Expected 2 message received and unmarshaled")
  2170  				require.Equal(t, uint64(1), counts[indexProcessRegularPass], "Expected 1 REGULAR message processed")
  2171  			})
  2172  		})
  2173  
  2174  		// This ensures regular kafka messages of type CONFIG are handled properly
  2175  		t.Run("Config", func(t *testing.T) {
  2176  			// This test sends a normal tx, followed by a config tx. It should
  2177  			// immediately cut them into two blocks.
  2178  			t.Run("ReceiveConfigEnvelopeAndCut", func(t *testing.T) {
  2179  				errorChan := make(chan struct{})
  2180  				close(errorChan)
  2181  				haltChan := make(chan struct{})
  2182  
  2183  				lastCutBlockNumber := uint64(3)
  2184  
  2185  				mockSupport := &mockmultichannel.ConsenterSupport{
  2186  					Blocks:          make(chan *cb.Block), // WriteBlock will post here
  2187  					BlockCutterVal:  mockblockcutter.NewReceiver(),
  2188  					ChannelIDVal:    mockChannel.topic(),
  2189  					HeightVal:       lastCutBlockNumber, // Incremented during the WriteBlock call
  2190  					SharedConfigVal: newMockOrderer(longTimeout, []string{mockBroker.Addr()}, false),
  2191  				}
  2192  				defer close(mockSupport.BlockCutterVal.Block)
  2193  
  2194  				fakeLastOffsetPersisted := &mockkafka.MetricsGauge{}
  2195  				fakeLastOffsetPersisted.WithReturns(fakeLastOffsetPersisted)
  2196  				mockConsenter.(*consenterImpl).metrics.LastOffsetPersisted = fakeLastOffsetPersisted
  2197  
  2198  				bareMinimumChain := &chainImpl{
  2199  					parentConsumer:  mockParentConsumer,
  2200  					channelConsumer: mockChannelConsumer,
  2201  
  2202  					consenter:          mockConsenter,
  2203  					channel:            mockChannel,
  2204  					ConsenterSupport:   mockSupport,
  2205  					lastCutBlockNumber: lastCutBlockNumber,
  2206  
  2207  					errorChan:                      errorChan,
  2208  					haltChan:                       haltChan,
  2209  					doneProcessingMessagesToBlocks: make(chan struct{}),
  2210  				}
  2211  
  2212  				var counts []uint64
  2213  				done := make(chan struct{})
  2214  
  2215  				go func() {
  2216  					counts, err = bareMinimumChain.processMessagesToBlocks()
  2217  					done <- struct{}{}
  2218  				}()
  2219  
  2220  				normalBlkOffset := mpc.HighWaterMarkOffset()
  2221  				mpc.YieldMessage(newMockConsumerMessage(newNormalMessage(protoutil.MarshalOrPanic(newMockEnvelope("fooMessage")), uint64(0), int64(0))))
  2222  				mockSupport.BlockCutterVal.Block <- struct{}{} // Let the `mockblockcutter.Ordered` call return
  2223  
  2224  				configBlkOffset := mpc.HighWaterMarkOffset()
  2225  				mockSupport.ClassifyMsgVal = msgprocessor.ConfigMsg
  2226  				mpc.YieldMessage(newMockConsumerMessage(newConfigMessage(
  2227  					protoutil.MarshalOrPanic(newMockConfigEnvelope()),
  2228  					uint64(0),
  2229  					int64(0))))
  2230  
  2231  				var normalBlk, configBlk *cb.Block
  2232  				select {
  2233  				case normalBlk = <-mockSupport.Blocks:
  2234  				case <-time.After(shortTimeout):
  2235  					logger.Fatalf("Did not receive a normal block from the blockcutter as expected")
  2236  				}
  2237  
  2238  				select {
  2239  				case configBlk = <-mockSupport.Blocks:
  2240  				case <-time.After(shortTimeout):
  2241  					logger.Fatalf("Did not receive a config block from the blockcutter as expected")
  2242  				}
  2243  
  2244  				close(haltChan) // Identical to chain.Halt()
  2245  				<-done
  2246  
  2247  				require.NoError(t, err, "Expected the processMessagesToBlocks call to return without errors")
  2248  				require.Equal(t, uint64(2), counts[indexRecvPass], "Expected 1 message received and unmarshaled")
  2249  				require.Equal(t, uint64(2), counts[indexProcessRegularPass], "Expected 1 REGULAR message processed")
  2250  				require.Equal(t, lastCutBlockNumber+2, bareMinimumChain.lastCutBlockNumber, "Expected lastCutBlockNumber to be incremented by 2")
  2251  				require.Equal(t, normalBlkOffset, extractEncodedOffset(normalBlk.GetMetadata().Metadata[cb.BlockMetadataIndex_ORDERER]), "Expected encoded offset in first block to be %d", normalBlkOffset)
  2252  				require.Equal(t, configBlkOffset, extractEncodedOffset(configBlk.GetMetadata().Metadata[cb.BlockMetadataIndex_ORDERER]), "Expected encoded offset in second block to be %d", configBlkOffset)
  2253  
  2254  				require.Equal(t, fakeLastOffsetPersisted.WithCallCount(), 2)
  2255  				require.Equal(t, fakeLastOffsetPersisted.WithArgsForCall(0), []string{"channel", "mockChannelFoo"})
  2256  				require.Equal(t, fakeLastOffsetPersisted.WithArgsForCall(1), []string{"channel", "mockChannelFoo"})
  2257  				require.Equal(t, fakeLastOffsetPersisted.SetCallCount(), 2)
  2258  				require.Equal(t, fakeLastOffsetPersisted.SetArgsForCall(0), float64(normalBlkOffset))
  2259  				require.Equal(t, fakeLastOffsetPersisted.SetArgsForCall(1), float64(configBlkOffset))
  2260  			})
  2261  
  2262  			// This ensures config message is re-validated if config seq has advanced
  2263  			t.Run("RevalidateConfigEnvInvalid", func(t *testing.T) {
  2264  				if testing.Short() {
  2265  					t.Skip("Skipping test in short mode")
  2266  				}
  2267  
  2268  				errorChan := make(chan struct{})
  2269  				close(errorChan)
  2270  				haltChan := make(chan struct{})
  2271  
  2272  				lastCutBlockNumber := uint64(3)
  2273  
  2274  				mockSupport := &mockmultichannel.ConsenterSupport{
  2275  					Blocks:              make(chan *cb.Block), // WriteBlock will post here
  2276  					BlockCutterVal:      mockblockcutter.NewReceiver(),
  2277  					ChannelIDVal:        mockChannel.topic(),
  2278  					HeightVal:           lastCutBlockNumber, // Incremented during the WriteBlock call
  2279  					ClassifyMsgVal:      msgprocessor.ConfigMsg,
  2280  					SharedConfigVal:     newMockOrderer(longTimeout, []string{mockBroker.Addr()}, false),
  2281  					SequenceVal:         uint64(1),
  2282  					ProcessConfigMsgErr: fmt.Errorf("Invalid config message"),
  2283  				}
  2284  				defer close(mockSupport.BlockCutterVal.Block)
  2285  
  2286  				bareMinimumChain := &chainImpl{
  2287  					parentConsumer:  mockParentConsumer,
  2288  					channelConsumer: mockChannelConsumer,
  2289  
  2290  					channel:            mockChannel,
  2291  					ConsenterSupport:   mockSupport,
  2292  					lastCutBlockNumber: lastCutBlockNumber,
  2293  
  2294  					errorChan:                      errorChan,
  2295  					haltChan:                       haltChan,
  2296  					doneProcessingMessagesToBlocks: make(chan struct{}),
  2297  				}
  2298  
  2299  				var counts []uint64
  2300  				done := make(chan struct{})
  2301  
  2302  				go func() {
  2303  					counts, err = bareMinimumChain.processMessagesToBlocks()
  2304  					done <- struct{}{}
  2305  				}()
  2306  
  2307  				mpc.YieldMessage(newMockConsumerMessage(newConfigMessage(
  2308  					protoutil.MarshalOrPanic(newMockConfigEnvelope()),
  2309  					uint64(0),
  2310  					int64(0))))
  2311  				select {
  2312  				case <-mockSupport.Blocks:
  2313  					t.Fatalf("Expected no block being cut given invalid config message")
  2314  				case <-time.After(shortTimeout):
  2315  				}
  2316  
  2317  				close(haltChan) // Identical to chain.Halt()
  2318  				<-done
  2319  
  2320  				require.NoError(t, err, "Expected the processMessagesToBlocks call to return without errors")
  2321  				require.Equal(t, uint64(1), counts[indexRecvPass], "Expected 1 message received and unmarshaled")
  2322  				require.Equal(t, uint64(1), counts[indexProcessRegularError], "Expected 1 REGULAR message error")
  2323  			})
  2324  		})
  2325  	})
  2326  
  2327  	t.Run("KafkaError", func(t *testing.T) {
  2328  		t.Run("ReceiveKafkaErrorAndCloseErrorChan", func(t *testing.T) {
  2329  			// If we set up the mock broker so that it returns a response, if the
  2330  			// test finishes before the sendConnectMessage goroutine has received
  2331  			// this response, we will get a failure ("not all expectations were
  2332  			// satisfied") from the mock broker. So we sabotage the producer.
  2333  			failedProducer, _ := sarama.NewSyncProducer([]string{}, mockBrokerConfig)
  2334  
  2335  			// We need to have the sendConnectMessage goroutine die instantaneously,
  2336  			// otherwise we'll get a nil pointer dereference panic. We are
  2337  			// exploiting the admittedly hacky shortcut where a retriable process
  2338  			// returns immediately when given the nil time.Duration value for its
  2339  			// ticker.
  2340  			zeroRetryConsenter := &consenterImpl{}
  2341  
  2342  			// Let's assume an open errorChan, i.e. a healthy link between the
  2343  			// consumer and the Kafka partition corresponding to the channel
  2344  			errorChan := make(chan struct{})
  2345  
  2346  			haltChan := make(chan struct{})
  2347  
  2348  			mockSupport := &mockmultichannel.ConsenterSupport{
  2349  				ChannelIDVal: mockChannel.topic(),
  2350  			}
  2351  
  2352  			bareMinimumChain := &chainImpl{
  2353  				consenter:       zeroRetryConsenter, // For sendConnectMessage
  2354  				producer:        failedProducer,     // For sendConnectMessage
  2355  				parentConsumer:  mockParentConsumer,
  2356  				channelConsumer: mockChannelConsumer,
  2357  
  2358  				channel:          mockChannel,
  2359  				ConsenterSupport: mockSupport,
  2360  
  2361  				errorChan:                      errorChan,
  2362  				haltChan:                       haltChan,
  2363  				doneProcessingMessagesToBlocks: make(chan struct{}),
  2364  			}
  2365  
  2366  			var counts []uint64
  2367  			done := make(chan struct{})
  2368  
  2369  			go func() {
  2370  				counts, err = bareMinimumChain.processMessagesToBlocks()
  2371  				done <- struct{}{}
  2372  			}()
  2373  
  2374  			// This is what the for-loop will process
  2375  			mpc.YieldError(fmt.Errorf("fooError"))
  2376  
  2377  			logger.Debug("Closing haltChan to exit the infinite for-loop")
  2378  			close(haltChan) // Identical to chain.Halt()
  2379  			logger.Debug("haltChan closed")
  2380  			<-done
  2381  
  2382  			require.NoError(t, err, "Expected the processMessagesToBlocks call to return without errors")
  2383  			require.Equal(t, uint64(1), counts[indexRecvError], "Expected 1 Kafka error received")
  2384  
  2385  			select {
  2386  			case <-bareMinimumChain.errorChan:
  2387  				logger.Debug("errorChan is closed as it should be")
  2388  			default:
  2389  				t.Fatal("errorChan should have been closed")
  2390  			}
  2391  		})
  2392  
  2393  		t.Run("ReceiveKafkaErrorAndThenReceiveRegularMessage", func(t *testing.T) {
  2394  			t.Skip("Skipping test as it introduces a race condition")
  2395  
  2396  			// If we set up the mock broker so that it returns a response, if the
  2397  			// test finishes before the sendConnectMessage goroutine has received
  2398  			// this response, we will get a failure ("not all expectations were
  2399  			// satisfied") from the mock broker. So we sabotage the producer.
  2400  			failedProducer, _ := sarama.NewSyncProducer([]string{}, mockBrokerConfig)
  2401  
  2402  			// We need to have the sendConnectMessage goroutine die instantaneously,
  2403  			// otherwise we'll get a nil pointer dereference panic. We are
  2404  			// exploiting the admittedly hacky shortcut where a retriable process
  2405  			// returns immediately when given the nil time.Duration value for its
  2406  			// ticker.
  2407  			zeroRetryConsenter := &consenterImpl{}
  2408  
  2409  			// If the errorChan is closed already, the kafkaErr branch shouldn't
  2410  			// touch it
  2411  			errorChan := make(chan struct{})
  2412  			close(errorChan)
  2413  
  2414  			haltChan := make(chan struct{})
  2415  
  2416  			mockSupport := &mockmultichannel.ConsenterSupport{
  2417  				ChannelIDVal: mockChannel.topic(),
  2418  			}
  2419  
  2420  			bareMinimumChain := &chainImpl{
  2421  				consenter:       zeroRetryConsenter, // For sendConnectMessage
  2422  				producer:        failedProducer,     // For sendConnectMessage
  2423  				parentConsumer:  mockParentConsumer,
  2424  				channelConsumer: mockChannelConsumer,
  2425  
  2426  				channel:          mockChannel,
  2427  				ConsenterSupport: mockSupport,
  2428  
  2429  				errorChan:                      errorChan,
  2430  				haltChan:                       haltChan,
  2431  				doneProcessingMessagesToBlocks: make(chan struct{}),
  2432  			}
  2433  
  2434  			done := make(chan struct{})
  2435  
  2436  			go func() {
  2437  				_, err = bareMinimumChain.processMessagesToBlocks()
  2438  				done <- struct{}{}
  2439  			}()
  2440  
  2441  			// This is what the for-loop will process
  2442  			mpc.YieldError(fmt.Errorf("foo"))
  2443  
  2444  			// We tested this in ReceiveKafkaErrorAndCloseErrorChan, so this check
  2445  			// is redundant in that regard. We use it however to ensure the
  2446  			// kafkaErrBranch has been activated before proceeding with pushing the
  2447  			// regular message.
  2448  			select {
  2449  			case <-bareMinimumChain.errorChan:
  2450  				logger.Debug("errorChan is closed as it should be")
  2451  			case <-time.After(shortTimeout):
  2452  				t.Fatal("errorChan should have been closed by now")
  2453  			}
  2454  
  2455  			// This is the wrappedMessage that the for-loop will process. We use
  2456  			// a broken regular message here on purpose since this is the shortest
  2457  			// path and it allows us to test what we want.
  2458  			mpc.YieldMessage(newMockConsumerMessage(newRegularMessage(tamperBytes(protoutil.MarshalOrPanic(newMockEnvelope("fooMessage"))))))
  2459  
  2460  			// Sleep so that the Messages/errorChan branch is activated.
  2461  			// TODO Hacky approach, will need to revise eventually
  2462  			time.Sleep(hitBranch)
  2463  
  2464  			// Check that the errorChan was recreated
  2465  			select {
  2466  			case <-bareMinimumChain.errorChan:
  2467  				t.Fatal("errorChan should have been open")
  2468  			default:
  2469  				logger.Debug("errorChan is open as it should be")
  2470  			}
  2471  
  2472  			logger.Debug("Closing haltChan to exit the infinite for-loop")
  2473  			close(haltChan) // Identical to chain.Halt()
  2474  			logger.Debug("haltChan closed")
  2475  			<-done
  2476  		})
  2477  	})
  2478  }
  2479  
  2480  // This ensures message is re-validated if config seq has advanced
  2481  func TestResubmission(t *testing.T) {
  2482  	blockIngressMsg := func(t *testing.T, block bool, fn func() error) {
  2483  		wait := make(chan struct{})
  2484  		go func() {
  2485  			fn()
  2486  			wait <- struct{}{}
  2487  		}()
  2488  
  2489  		select {
  2490  		case <-wait:
  2491  			if block {
  2492  				t.Fatalf("Expected WaitReady to block")
  2493  			}
  2494  		case <-time.After(100 * time.Millisecond):
  2495  			if !block {
  2496  				t.Fatalf("Expected WaitReady not to block")
  2497  			}
  2498  		}
  2499  	}
  2500  
  2501  	mockBroker := sarama.NewMockBroker(t, 0)
  2502  	defer func() { mockBroker.Close() }()
  2503  
  2504  	mockChannel := newChannel(channelNameForTest(t), defaultPartition)
  2505  	mockBrokerConfigCopy := *mockBrokerConfig
  2506  	mockBrokerConfigCopy.ChannelBufferSize = 0
  2507  
  2508  	mockParentConsumer := mocks.NewConsumer(t, &mockBrokerConfigCopy)
  2509  	mpc := mockParentConsumer.ExpectConsumePartition(mockChannel.topic(), mockChannel.partition(), int64(0))
  2510  	mockChannelConsumer, err := mockParentConsumer.ConsumePartition(mockChannel.topic(), mockChannel.partition(), int64(0))
  2511  	require.NoError(t, err, "Expected no error when setting up the mock partition consumer")
  2512  
  2513  	t.Run("Normal", func(t *testing.T) {
  2514  		// This test lets kafka emit a mock re-submitted message that does not require reprocessing
  2515  		// (by setting OriginalOffset <= lastOriginalOffsetProcessed)
  2516  		t.Run("AlreadyProcessedDiscard", func(t *testing.T) {
  2517  			if testing.Short() {
  2518  				t.Skip("Skipping test in short mode")
  2519  			}
  2520  
  2521  			errorChan := make(chan struct{})
  2522  			close(errorChan)
  2523  			haltChan := make(chan struct{})
  2524  
  2525  			lastCutBlockNumber := uint64(3)
  2526  			lastOriginalOffsetProcessed := int64(3)
  2527  
  2528  			mockSupport := &mockmultichannel.ConsenterSupport{
  2529  				Blocks:          make(chan *cb.Block), // WriteBlock will post here
  2530  				BlockCutterVal:  mockblockcutter.NewReceiver(),
  2531  				ChannelIDVal:    mockChannel.topic(),
  2532  				HeightVal:       lastCutBlockNumber, // Incremented during the WriteBlock call
  2533  				SharedConfigVal: newMockOrderer(longTimeout, []string{mockBroker.Addr()}, true),
  2534  			}
  2535  			defer close(mockSupport.BlockCutterVal.Block)
  2536  
  2537  			bareMinimumChain := &chainImpl{
  2538  				parentConsumer:  mockParentConsumer,
  2539  				channelConsumer: mockChannelConsumer,
  2540  
  2541  				channel:                     mockChannel,
  2542  				ConsenterSupport:            mockSupport,
  2543  				lastCutBlockNumber:          lastCutBlockNumber,
  2544  				lastOriginalOffsetProcessed: lastOriginalOffsetProcessed,
  2545  
  2546  				errorChan:                      errorChan,
  2547  				haltChan:                       haltChan,
  2548  				doneProcessingMessagesToBlocks: make(chan struct{}),
  2549  			}
  2550  
  2551  			var counts []uint64
  2552  			done := make(chan struct{})
  2553  
  2554  			go func() {
  2555  				counts, err = bareMinimumChain.processMessagesToBlocks()
  2556  				done <- struct{}{}
  2557  			}()
  2558  
  2559  			mockSupport.BlockCutterVal.CutNext = true
  2560  
  2561  			mpc.YieldMessage(newMockConsumerMessage(newNormalMessage(protoutil.MarshalOrPanic(newMockEnvelope("fooMessage")), uint64(0), int64(2))))
  2562  
  2563  			select {
  2564  			case <-mockSupport.Blocks:
  2565  				t.Fatalf("Expected no block being cut")
  2566  			case <-time.After(shortTimeout):
  2567  			}
  2568  
  2569  			close(haltChan)
  2570  			logger.Debug("haltChan closed")
  2571  			<-done
  2572  
  2573  			require.NoError(t, err, "Expected the processMessagesToBlocks call to return without errors")
  2574  			require.Equal(t, uint64(1), counts[indexRecvPass], "Expected 2 message received and unmarshaled")
  2575  			require.Equal(t, uint64(1), counts[indexProcessRegularPass], "Expected 1 REGULAR message processed")
  2576  		})
  2577  
  2578  		// This test lets kafka emit a mock re-submitted message that requires reprocessing
  2579  		// (by setting OriginalOffset > lastOriginalOffsetProcessed)
  2580  		// Two normal messages are enqueued in this test case: reprossed normal message where
  2581  		// `originalOffset` is not 0, followed by a normal msg  where `originalOffset` is 0.
  2582  		// It tests the case that even no block is cut, `lastOriginalOffsetProcessed` is still
  2583  		// updated. We inspect the block to verify correct `LastOriginalOffsetProcessed` in the
  2584  		// kafka metadata.
  2585  		t.Run("ResubmittedMsgEnqueue", func(t *testing.T) {
  2586  			if testing.Short() {
  2587  				t.Skip("Skipping test in short mode")
  2588  			}
  2589  
  2590  			errorChan := make(chan struct{})
  2591  			close(errorChan)
  2592  			haltChan := make(chan struct{})
  2593  
  2594  			lastCutBlockNumber := uint64(3)
  2595  			lastOriginalOffsetProcessed := int64(3)
  2596  
  2597  			mockSupport := &mockmultichannel.ConsenterSupport{
  2598  				Blocks:          make(chan *cb.Block), // WriteBlock will post here
  2599  				BlockCutterVal:  mockblockcutter.NewReceiver(),
  2600  				ChannelIDVal:    mockChannel.topic(),
  2601  				HeightVal:       lastCutBlockNumber, // Incremented during the WriteBlock call
  2602  				SharedConfigVal: newMockOrderer(longTimeout, []string{mockBroker.Addr()}, true),
  2603  				SequenceVal:     uint64(0),
  2604  			}
  2605  			defer close(mockSupport.BlockCutterVal.Block)
  2606  
  2607  			bareMinimumChain := &chainImpl{
  2608  				parentConsumer:  mockParentConsumer,
  2609  				channelConsumer: mockChannelConsumer,
  2610  
  2611  				consenter:                   mockConsenter,
  2612  				channel:                     mockChannel,
  2613  				ConsenterSupport:            mockSupport,
  2614  				lastCutBlockNumber:          lastCutBlockNumber,
  2615  				lastOriginalOffsetProcessed: lastOriginalOffsetProcessed,
  2616  
  2617  				errorChan:                      errorChan,
  2618  				haltChan:                       haltChan,
  2619  				doneProcessingMessagesToBlocks: make(chan struct{}),
  2620  			}
  2621  
  2622  			var counts []uint64
  2623  			done := make(chan struct{})
  2624  
  2625  			go func() {
  2626  				counts, err = bareMinimumChain.processMessagesToBlocks()
  2627  				done <- struct{}{}
  2628  			}()
  2629  
  2630  			mpc.YieldMessage(newMockConsumerMessage(newNormalMessage(protoutil.MarshalOrPanic(newMockEnvelope("fooMessage")), uint64(0), int64(4))))
  2631  			mockSupport.BlockCutterVal.Block <- struct{}{}
  2632  
  2633  			select {
  2634  			case <-mockSupport.Blocks:
  2635  				t.Fatalf("Expected no block to be cut")
  2636  			case <-time.After(shortTimeout):
  2637  			}
  2638  
  2639  			mockSupport.BlockCutterVal.CutNext = true
  2640  			mpc.YieldMessage(newMockConsumerMessage(newNormalMessage(protoutil.MarshalOrPanic(newMockEnvelope("fooMessage")), uint64(0), int64(0))))
  2641  			mockSupport.BlockCutterVal.Block <- struct{}{}
  2642  
  2643  			select {
  2644  			case block := <-mockSupport.Blocks:
  2645  				metadata := &cb.Metadata{}
  2646  				proto.Unmarshal(block.Metadata.Metadata[cb.BlockMetadataIndex_ORDERER], metadata)
  2647  				kafkaMetadata := &ab.KafkaMetadata{}
  2648  				proto.Unmarshal(metadata.Value, kafkaMetadata)
  2649  				require.Equal(t, kafkaMetadata.LastOriginalOffsetProcessed, int64(4))
  2650  			case <-time.After(shortTimeout):
  2651  				t.Fatalf("Expected one block being cut")
  2652  			}
  2653  
  2654  			close(haltChan)
  2655  			logger.Debug("haltChan closed")
  2656  			<-done
  2657  
  2658  			require.NoError(t, err, "Expected the processMessagesToBlocks call to return without errors")
  2659  			require.Equal(t, uint64(2), counts[indexRecvPass], "Expected 2 message received and unmarshaled")
  2660  			require.Equal(t, uint64(2), counts[indexProcessRegularPass], "Expected 2 REGULAR message processed")
  2661  		})
  2662  
  2663  		t.Run("InvalidDiscard", func(t *testing.T) {
  2664  			if testing.Short() {
  2665  				t.Skip("Skipping test in short mode")
  2666  			}
  2667  
  2668  			errorChan := make(chan struct{})
  2669  			close(errorChan)
  2670  			haltChan := make(chan struct{})
  2671  
  2672  			lastCutBlockNumber := uint64(3)
  2673  
  2674  			mockSupport := &mockmultichannel.ConsenterSupport{
  2675  				Blocks:              make(chan *cb.Block), // WriteBlock will post here
  2676  				BlockCutterVal:      mockblockcutter.NewReceiver(),
  2677  				ChannelIDVal:        mockChannel.topic(),
  2678  				HeightVal:           lastCutBlockNumber, // Incremented during the WriteBlock call
  2679  				SharedConfigVal:     newMockOrderer(longTimeout, []string{mockBroker.Addr()}, true),
  2680  				SequenceVal:         uint64(1),
  2681  				ProcessNormalMsgErr: fmt.Errorf("Invalid normal message"),
  2682  			}
  2683  			defer close(mockSupport.BlockCutterVal.Block)
  2684  
  2685  			bareMinimumChain := &chainImpl{
  2686  				parentConsumer:  mockParentConsumer,
  2687  				channelConsumer: mockChannelConsumer,
  2688  
  2689  				channel:            mockChannel,
  2690  				ConsenterSupport:   mockSupport,
  2691  				lastCutBlockNumber: lastCutBlockNumber,
  2692  
  2693  				errorChan:                      errorChan,
  2694  				haltChan:                       haltChan,
  2695  				doneProcessingMessagesToBlocks: make(chan struct{}),
  2696  			}
  2697  
  2698  			var counts []uint64
  2699  			done := make(chan struct{})
  2700  
  2701  			go func() {
  2702  				counts, err = bareMinimumChain.processMessagesToBlocks()
  2703  				done <- struct{}{}
  2704  			}()
  2705  
  2706  			mpc.YieldMessage(newMockConsumerMessage(newNormalMessage(
  2707  				protoutil.MarshalOrPanic(newMockNormalEnvelope(t)),
  2708  				uint64(0),
  2709  				int64(0))))
  2710  			select {
  2711  			case <-mockSupport.Blocks:
  2712  				t.Fatalf("Expected no block being cut given invalid config message")
  2713  			case <-time.After(shortTimeout):
  2714  			}
  2715  
  2716  			close(haltChan) // Identical to chain.Halt()
  2717  			<-done
  2718  
  2719  			require.NoError(t, err, "Expected the processMessagesToBlocks call to return without errors")
  2720  			require.Equal(t, uint64(1), counts[indexRecvPass], "Expected 1 message received and unmarshaled")
  2721  			require.Equal(t, uint64(1), counts[indexProcessRegularError], "Expected 1 REGULAR message error")
  2722  		})
  2723  
  2724  		// This tests resubmission path with following steps:
  2725  		// 1) Kafka emits a message with lagged config seq, consenter is expected to re-process and
  2726  		//    re-submit the message. However, `WaitReady` shouldn't be blocked for a normal message
  2727  		// 2) Kafka is expected to receive a producer message where config seq is advanced to catch
  2728  		//    up with current config seq, and OriginalOffset is not nil to capture the offset that
  2729  		//    consenter previously received from Kafka
  2730  		// 3) when consenter receives Kafka message submitted in 2), where config seq is in sync,
  2731  		//    it cuts a block for it.
  2732  		t.Run("ValidResubmit", func(t *testing.T) {
  2733  			if testing.Short() {
  2734  				t.Skip("Skipping test in short mode")
  2735  			}
  2736  
  2737  			startChan := make(chan struct{})
  2738  			close(startChan)
  2739  			errorChan := make(chan struct{})
  2740  			close(errorChan)
  2741  			haltChan := make(chan struct{})
  2742  			doneReprocessing := make(chan struct{})
  2743  			close(doneReprocessing)
  2744  
  2745  			lastCutBlockNumber := uint64(3)
  2746  
  2747  			mockSupport := &mockmultichannel.ConsenterSupport{
  2748  				Blocks:          make(chan *cb.Block), // WriteBlock will post here
  2749  				BlockCutterVal:  mockblockcutter.NewReceiver(),
  2750  				ChannelIDVal:    mockChannel.topic(),
  2751  				HeightVal:       lastCutBlockNumber, // Incremented during the WriteBlock call
  2752  				SharedConfigVal: newMockOrderer(longTimeout, []string{mockBroker.Addr()}, true),
  2753  				SequenceVal:     uint64(1),
  2754  				ConfigSeqVal:    uint64(1),
  2755  			}
  2756  			defer close(mockSupport.BlockCutterVal.Block)
  2757  
  2758  			expectedKafkaMsgCh := make(chan *ab.KafkaMessage, 1)
  2759  			producer := mocks.NewSyncProducer(t, mockBrokerConfig)
  2760  			producer.ExpectSendMessageWithCheckerFunctionAndSucceed(func(val []byte) error {
  2761  				defer close(expectedKafkaMsgCh)
  2762  
  2763  				expectedKafkaMsg := &ab.KafkaMessage{}
  2764  				if err := proto.Unmarshal(val, expectedKafkaMsg); err != nil {
  2765  					return err
  2766  				}
  2767  
  2768  				regular := expectedKafkaMsg.GetRegular()
  2769  				if regular == nil {
  2770  					return fmt.Errorf("Expect message type to be regular")
  2771  				}
  2772  
  2773  				if regular.ConfigSeq != mockSupport.Sequence() {
  2774  					return fmt.Errorf("Expect new config seq to be %d, got %d", mockSupport.Sequence(), regular.ConfigSeq)
  2775  				}
  2776  
  2777  				if regular.OriginalOffset == 0 {
  2778  					return fmt.Errorf("Expect Original Offset to be non-zero if resubmission")
  2779  				}
  2780  
  2781  				expectedKafkaMsgCh <- expectedKafkaMsg
  2782  				return nil
  2783  			})
  2784  
  2785  			bareMinimumChain := &chainImpl{
  2786  				producer:        producer,
  2787  				parentConsumer:  mockParentConsumer,
  2788  				channelConsumer: mockChannelConsumer,
  2789  
  2790  				consenter:          mockConsenter,
  2791  				channel:            mockChannel,
  2792  				ConsenterSupport:   mockSupport,
  2793  				lastCutBlockNumber: lastCutBlockNumber,
  2794  
  2795  				startChan:                      startChan,
  2796  				errorChan:                      errorChan,
  2797  				haltChan:                       haltChan,
  2798  				doneProcessingMessagesToBlocks: make(chan struct{}),
  2799  				doneReprocessingMsgInFlight:    doneReprocessing,
  2800  			}
  2801  
  2802  			var counts []uint64
  2803  			done := make(chan struct{})
  2804  
  2805  			go func() {
  2806  				counts, err = bareMinimumChain.processMessagesToBlocks()
  2807  				done <- struct{}{}
  2808  			}()
  2809  
  2810  			mockSupport.BlockCutterVal.CutNext = true
  2811  
  2812  			mpc.YieldMessage(newMockConsumerMessage(newNormalMessage(
  2813  				protoutil.MarshalOrPanic(newMockNormalEnvelope(t)),
  2814  				uint64(0),
  2815  				int64(0))))
  2816  			select {
  2817  			case <-mockSupport.Blocks:
  2818  				t.Fatalf("Expected no block being cut given invalid config message")
  2819  			case <-time.After(shortTimeout):
  2820  			}
  2821  
  2822  			// check that WaitReady is not blocked for a in-flight reprocessed messages of type NORMAL
  2823  			waitReady := make(chan struct{})
  2824  			go func() {
  2825  				bareMinimumChain.WaitReady()
  2826  				waitReady <- struct{}{}
  2827  			}()
  2828  
  2829  			select {
  2830  			case <-waitReady:
  2831  			case <-time.After(100 * time.Millisecond):
  2832  				t.Fatalf("Expected WaitReady call to be unblock because all reprocessed messages are consumed")
  2833  			}
  2834  
  2835  			// Emits the kafka message produced by consenter
  2836  			select {
  2837  			case expectedKafkaMsg := <-expectedKafkaMsgCh:
  2838  				require.NotNil(t, expectedKafkaMsg)
  2839  				mpc.YieldMessage(newMockConsumerMessage(expectedKafkaMsg))
  2840  				mockSupport.BlockCutterVal.Block <- struct{}{}
  2841  			case <-time.After(shortTimeout):
  2842  				t.Fatalf("Expected to receive kafka message")
  2843  			}
  2844  
  2845  			select {
  2846  			case <-mockSupport.Blocks:
  2847  			case <-time.After(shortTimeout):
  2848  				t.Fatalf("Expected one block being cut")
  2849  			}
  2850  
  2851  			close(haltChan) // Identical to chain.Halt()
  2852  			<-done
  2853  
  2854  			require.NoError(t, err, "Expected the processMessagesToBlocks call to return without errors")
  2855  			require.Equal(t, uint64(2), counts[indexRecvPass], "Expected 2 message received and unmarshaled")
  2856  			require.Equal(t, uint64(2), counts[indexProcessRegularPass], "Expected 2 REGULAR message error")
  2857  		})
  2858  	})
  2859  
  2860  	t.Run("Config", func(t *testing.T) {
  2861  		// This test lets kafka emit a mock re-submitted message that does not require reprocessing
  2862  		// (by setting OriginalOffset <= lastOriginalOffsetProcessed)
  2863  		t.Run("AlreadyProcessedDiscard", func(t *testing.T) {
  2864  			if testing.Short() {
  2865  				t.Skip("Skipping test in short mode")
  2866  			}
  2867  
  2868  			errorChan := make(chan struct{})
  2869  			close(errorChan)
  2870  			haltChan := make(chan struct{})
  2871  
  2872  			lastCutBlockNumber := uint64(3)
  2873  			lastOriginalOffsetProcessed := int64(3)
  2874  
  2875  			mockSupport := &mockmultichannel.ConsenterSupport{
  2876  				Blocks:          make(chan *cb.Block), // WriteBlock will post here
  2877  				BlockCutterVal:  mockblockcutter.NewReceiver(),
  2878  				ChannelIDVal:    mockChannel.topic(),
  2879  				HeightVal:       lastCutBlockNumber, // Incremented during the WriteBlock call
  2880  				SharedConfigVal: newMockOrderer(longTimeout, []string{mockBroker.Addr()}, true),
  2881  			}
  2882  			defer close(mockSupport.BlockCutterVal.Block)
  2883  
  2884  			bareMinimumChain := &chainImpl{
  2885  				parentConsumer:  mockParentConsumer,
  2886  				channelConsumer: mockChannelConsumer,
  2887  
  2888  				channel:                     mockChannel,
  2889  				ConsenterSupport:            mockSupport,
  2890  				lastCutBlockNumber:          lastCutBlockNumber,
  2891  				lastOriginalOffsetProcessed: lastOriginalOffsetProcessed,
  2892  
  2893  				errorChan:                      errorChan,
  2894  				haltChan:                       haltChan,
  2895  				doneProcessingMessagesToBlocks: make(chan struct{}),
  2896  			}
  2897  
  2898  			var counts []uint64
  2899  			done := make(chan struct{})
  2900  
  2901  			go func() {
  2902  				counts, err = bareMinimumChain.processMessagesToBlocks()
  2903  				done <- struct{}{}
  2904  			}()
  2905  
  2906  			mpc.YieldMessage(newMockConsumerMessage(newConfigMessage(protoutil.MarshalOrPanic(newMockEnvelope("fooMessage")), uint64(0), int64(2))))
  2907  
  2908  			select {
  2909  			case <-mockSupport.Blocks:
  2910  				t.Fatalf("Expected no block being cut")
  2911  			case <-time.After(shortTimeout):
  2912  			}
  2913  
  2914  			close(haltChan)
  2915  			logger.Debug("haltChan closed")
  2916  			<-done
  2917  
  2918  			require.NoError(t, err, "Expected the processMessagesToBlocks call to return without errors")
  2919  			require.Equal(t, uint64(1), counts[indexRecvPass], "Expected 2 message received and unmarshaled")
  2920  			require.Equal(t, uint64(1), counts[indexProcessRegularPass], "Expected 1 REGULAR message processed")
  2921  		})
  2922  
  2923  		// This test simulated the non-deterministic case, where somebody resubmitted message at offset X,
  2924  		// whereas we didn't. That message was considered invalid by us during revalidation, however somebody
  2925  		// else deemed it to be valid, and resubmitted it.
  2926  		t.Run("Non-determinism", func(t *testing.T) {
  2927  			if testing.Short() {
  2928  				t.Skip("Skipping test in short mode")
  2929  			}
  2930  
  2931  			startChan := make(chan struct{})
  2932  			close(startChan)
  2933  			errorChan := make(chan struct{})
  2934  			close(errorChan)
  2935  			haltChan := make(chan struct{})
  2936  			doneReprocessing := make(chan struct{})
  2937  			close(doneReprocessing)
  2938  
  2939  			lastCutBlockNumber := uint64(3)
  2940  
  2941  			mockSupport := &mockmultichannel.ConsenterSupport{
  2942  				Blocks:              make(chan *cb.Block), // WriteBlock will post here
  2943  				BlockCutterVal:      mockblockcutter.NewReceiver(),
  2944  				ChannelIDVal:        mockChannel.topic(),
  2945  				HeightVal:           lastCutBlockNumber, // Incremented during the WriteBlock call
  2946  				SharedConfigVal:     newMockOrderer(longTimeout, []string{mockBroker.Addr()}, true),
  2947  				SequenceVal:         uint64(1),
  2948  				ConfigSeqVal:        uint64(1),
  2949  				ProcessConfigMsgVal: newMockConfigEnvelope(),
  2950  			}
  2951  			defer close(mockSupport.BlockCutterVal.Block)
  2952  
  2953  			bareMinimumChain := &chainImpl{
  2954  				parentConsumer:  mockParentConsumer,
  2955  				channelConsumer: mockChannelConsumer,
  2956  
  2957  				consenter:          mockConsenter,
  2958  				channel:            mockChannel,
  2959  				ConsenterSupport:   mockSupport,
  2960  				lastCutBlockNumber: lastCutBlockNumber,
  2961  
  2962  				lastResubmittedConfigOffset: int64(0),
  2963  
  2964  				startChan:                      startChan,
  2965  				errorChan:                      errorChan,
  2966  				haltChan:                       haltChan,
  2967  				doneProcessingMessagesToBlocks: make(chan struct{}),
  2968  				doneReprocessingMsgInFlight:    doneReprocessing,
  2969  			}
  2970  
  2971  			var counts []uint64
  2972  			done := make(chan struct{})
  2973  
  2974  			go func() {
  2975  				counts, err = bareMinimumChain.processMessagesToBlocks()
  2976  				done <- struct{}{}
  2977  			}()
  2978  
  2979  			// check that WaitReady is not blocked at beginning
  2980  			blockIngressMsg(t, false, bareMinimumChain.WaitReady)
  2981  
  2982  			// Message should be revalidated but considered invalid, so we don't resubmit it
  2983  			mockSupport.ProcessConfigMsgErr = fmt.Errorf("invalid message found during revalidation")
  2984  
  2985  			// Emits a config message with lagged config sequence
  2986  			mpc.YieldMessage(newMockConsumerMessage(newConfigMessage(
  2987  				protoutil.MarshalOrPanic(newMockConfigEnvelope()),
  2988  				uint64(0),
  2989  				int64(0))))
  2990  			select {
  2991  			case <-mockSupport.Blocks:
  2992  				t.Fatalf("Expected no block being cut")
  2993  			case <-time.After(shortTimeout):
  2994  			}
  2995  
  2996  			// check that WaitReady is still not blocked because we haven't resubmitted anything
  2997  			blockIngressMsg(t, false, bareMinimumChain.WaitReady)
  2998  
  2999  			// Somebody else resubmitted the message which we deemed to be invalid
  3000  			// We deliberately keep ProcessConfigMsgErr unchanged, so we could be
  3001  			// certain that we are not running into revalidation path.
  3002  			mpc.YieldMessage(newMockConsumerMessage(newConfigMessage(
  3003  				protoutil.MarshalOrPanic(newMockConfigEnvelope()),
  3004  				uint64(1),
  3005  				int64(5))))
  3006  
  3007  			select {
  3008  			case block := <-mockSupport.Blocks:
  3009  				metadata, err := protoutil.GetMetadataFromBlock(block, cb.BlockMetadataIndex_ORDERER)
  3010  				require.NoError(t, err, "Failed to get metadata from block")
  3011  				kafkaMetadata := &ab.KafkaMetadata{}
  3012  				err = proto.Unmarshal(metadata.Value, kafkaMetadata)
  3013  				require.NoError(t, err, "Failed to unmarshal metadata")
  3014  
  3015  				require.Equal(t, kafkaMetadata.LastResubmittedConfigOffset, int64(5), "LastResubmittedConfigOffset didn't catch up")
  3016  				require.Equal(t, kafkaMetadata.LastOriginalOffsetProcessed, int64(5), "LastOriginalOffsetProcessed doesn't match")
  3017  			case <-time.After(shortTimeout):
  3018  				t.Fatalf("Expected one block being cut")
  3019  			}
  3020  
  3021  			close(haltChan) // Identical to chain.Halt()
  3022  			<-done
  3023  
  3024  			require.NoError(t, err, "Expected the processMessagesToBlocks call to return without errors")
  3025  			require.Equal(t, uint64(2), counts[indexRecvPass], "Expected 2 message received and unmarshaled")
  3026  			require.Equal(t, uint64(1), counts[indexProcessRegularPass], "Expected 2 REGULAR message error")
  3027  		})
  3028  
  3029  		// This test lets kafka emit a mock re-submitted message whose config seq is still behind
  3030  		t.Run("ResubmittedMsgStillBehind", func(t *testing.T) {
  3031  			if testing.Short() {
  3032  				t.Skip("Skipping test in short mode")
  3033  			}
  3034  
  3035  			startChan := make(chan struct{})
  3036  			close(startChan)
  3037  			errorChan := make(chan struct{})
  3038  			close(errorChan)
  3039  			haltChan := make(chan struct{})
  3040  
  3041  			lastCutBlockNumber := uint64(3)
  3042  			lastOriginalOffsetProcessed := int64(3)
  3043  
  3044  			mockSupport := &mockmultichannel.ConsenterSupport{
  3045  				Blocks:              make(chan *cb.Block), // WriteBlock will post here
  3046  				BlockCutterVal:      mockblockcutter.NewReceiver(),
  3047  				ChannelIDVal:        mockChannel.topic(),
  3048  				HeightVal:           lastCutBlockNumber, // Incremented during the WriteBlock call
  3049  				SharedConfigVal:     newMockOrderer(longTimeout, []string{mockBroker.Addr()}, true),
  3050  				ChannelConfigVal:    newMockChannel(),
  3051  				SequenceVal:         uint64(2),
  3052  				ProcessConfigMsgVal: newMockConfigEnvelope(),
  3053  			}
  3054  			defer close(mockSupport.BlockCutterVal.Block)
  3055  
  3056  			producer := mocks.NewSyncProducer(t, mockBrokerConfig)
  3057  			producer.ExpectSendMessageWithCheckerFunctionAndSucceed(func(val []byte) error {
  3058  				return nil
  3059  			})
  3060  
  3061  			bareMinimumChain := &chainImpl{
  3062  				producer:        producer,
  3063  				parentConsumer:  mockParentConsumer,
  3064  				channelConsumer: mockChannelConsumer,
  3065  
  3066  				channel:                     mockChannel,
  3067  				ConsenterSupport:            mockSupport,
  3068  				lastCutBlockNumber:          lastCutBlockNumber,
  3069  				lastOriginalOffsetProcessed: lastOriginalOffsetProcessed,
  3070  
  3071  				startChan:                      startChan,
  3072  				errorChan:                      errorChan,
  3073  				haltChan:                       haltChan,
  3074  				doneProcessingMessagesToBlocks: make(chan struct{}),
  3075  				doneReprocessingMsgInFlight:    make(chan struct{}),
  3076  			}
  3077  
  3078  			// WaitReady should block at beginning since we are in the middle of reprocessing
  3079  			blockIngressMsg(t, true, bareMinimumChain.WaitReady)
  3080  
  3081  			var counts []uint64
  3082  			done := make(chan struct{})
  3083  
  3084  			go func() {
  3085  				counts, err = bareMinimumChain.processMessagesToBlocks()
  3086  				done <- struct{}{}
  3087  			}()
  3088  
  3089  			mpc.YieldMessage(newMockConsumerMessage(newConfigMessage(protoutil.MarshalOrPanic(newMockEnvelope("fooMessage")), uint64(1), int64(4))))
  3090  			select {
  3091  			case <-mockSupport.Blocks:
  3092  				t.Fatalf("Expected no block being cut")
  3093  			case <-time.After(shortTimeout):
  3094  			}
  3095  
  3096  			// WaitReady should still block as resubmitted config message is still behind current config seq
  3097  			blockIngressMsg(t, true, bareMinimumChain.WaitReady)
  3098  
  3099  			close(haltChan)
  3100  			logger.Debug("haltChan closed")
  3101  			<-done
  3102  
  3103  			require.NoError(t, err, "Expected the processMessagesToBlocks call to return without errors")
  3104  			require.Equal(t, uint64(1), counts[indexRecvPass], "Expected 1 message received and unmarshaled")
  3105  			require.Equal(t, uint64(1), counts[indexProcessRegularPass], "Expected 1 REGULAR message processed")
  3106  		})
  3107  
  3108  		t.Run("InvalidDiscard", func(t *testing.T) {
  3109  			if testing.Short() {
  3110  				t.Skip("Skipping test in short mode")
  3111  			}
  3112  
  3113  			errorChan := make(chan struct{})
  3114  			close(errorChan)
  3115  			haltChan := make(chan struct{})
  3116  
  3117  			lastCutBlockNumber := uint64(3)
  3118  
  3119  			mockSupport := &mockmultichannel.ConsenterSupport{
  3120  				Blocks:              make(chan *cb.Block), // WriteBlock will post here
  3121  				BlockCutterVal:      mockblockcutter.NewReceiver(),
  3122  				ChannelIDVal:        mockChannel.topic(),
  3123  				HeightVal:           lastCutBlockNumber, // Incremented during the WriteBlock call
  3124  				SharedConfigVal:     newMockOrderer(longTimeout, []string{mockBroker.Addr()}, true),
  3125  				SequenceVal:         uint64(1),
  3126  				ProcessConfigMsgErr: fmt.Errorf("Invalid config message"),
  3127  			}
  3128  			defer close(mockSupport.BlockCutterVal.Block)
  3129  
  3130  			bareMinimumChain := &chainImpl{
  3131  				parentConsumer:  mockParentConsumer,
  3132  				channelConsumer: mockChannelConsumer,
  3133  
  3134  				channel:            mockChannel,
  3135  				ConsenterSupport:   mockSupport,
  3136  				lastCutBlockNumber: lastCutBlockNumber,
  3137  
  3138  				errorChan:                      errorChan,
  3139  				haltChan:                       haltChan,
  3140  				doneProcessingMessagesToBlocks: make(chan struct{}),
  3141  			}
  3142  
  3143  			var counts []uint64
  3144  			done := make(chan struct{})
  3145  
  3146  			go func() {
  3147  				counts, err = bareMinimumChain.processMessagesToBlocks()
  3148  				done <- struct{}{}
  3149  			}()
  3150  
  3151  			mpc.YieldMessage(newMockConsumerMessage(newConfigMessage(
  3152  				protoutil.MarshalOrPanic(newMockNormalEnvelope(t)),
  3153  				uint64(0),
  3154  				int64(0))))
  3155  			select {
  3156  			case <-mockSupport.Blocks:
  3157  				t.Fatalf("Expected no block being cut given invalid config message")
  3158  			case <-time.After(shortTimeout):
  3159  			}
  3160  
  3161  			close(haltChan) // Identical to chain.Halt()
  3162  			<-done
  3163  
  3164  			require.NoError(t, err, "Expected the processMessagesToBlocks call to return without errors")
  3165  			require.Equal(t, uint64(1), counts[indexRecvPass], "Expected 1 message received and unmarshaled")
  3166  			require.Equal(t, uint64(1), counts[indexProcessRegularError], "Expected 1 REGULAR message error")
  3167  		})
  3168  
  3169  		// This tests resubmission path with following steps:
  3170  		// 1) Kafka emits a message with lagged config seq, consenter is expected to re-process and
  3171  		//    re-submit the message, as well as block `WaitReady` API
  3172  		// 2) Kafka is expected to receive a producer message where config seq is advanced to catch
  3173  		//    up with current config seq, and OriginalOffset is not nil to capture the offset that
  3174  		//    consenter previously received from Kafka
  3175  		// 3) when consenter receives Kafka message submitted in 2), where config seq is in sync,
  3176  		//    it cuts a block for it and lifts block on `WaitReady`.
  3177  		t.Run("ValidResubmit", func(t *testing.T) {
  3178  			if testing.Short() {
  3179  				t.Skip("Skipping test in short mode")
  3180  			}
  3181  
  3182  			startChan := make(chan struct{})
  3183  			close(startChan)
  3184  			errorChan := make(chan struct{})
  3185  			close(errorChan)
  3186  			haltChan := make(chan struct{})
  3187  			doneReprocessing := make(chan struct{})
  3188  			close(doneReprocessing)
  3189  
  3190  			lastCutBlockNumber := uint64(3)
  3191  
  3192  			mockSupport := &mockmultichannel.ConsenterSupport{
  3193  				Blocks:              make(chan *cb.Block), // WriteBlock will post here
  3194  				BlockCutterVal:      mockblockcutter.NewReceiver(),
  3195  				ChannelIDVal:        mockChannel.topic(),
  3196  				HeightVal:           lastCutBlockNumber, // Incremented during the WriteBlock call
  3197  				SharedConfigVal:     newMockOrderer(longTimeout, []string{mockBroker.Addr()}, true),
  3198  				ChannelConfigVal:    newMockChannel(),
  3199  				SequenceVal:         uint64(1),
  3200  				ConfigSeqVal:        uint64(1),
  3201  				ProcessConfigMsgVal: newMockConfigEnvelope(),
  3202  			}
  3203  			defer close(mockSupport.BlockCutterVal.Block)
  3204  
  3205  			expectedKafkaMsgCh := make(chan *ab.KafkaMessage, 1)
  3206  			producer := mocks.NewSyncProducer(t, mockBrokerConfig)
  3207  			producer.ExpectSendMessageWithCheckerFunctionAndSucceed(func(val []byte) error {
  3208  				defer close(expectedKafkaMsgCh)
  3209  
  3210  				expectedKafkaMsg := &ab.KafkaMessage{}
  3211  				if err := proto.Unmarshal(val, expectedKafkaMsg); err != nil {
  3212  					return err
  3213  				}
  3214  
  3215  				regular := expectedKafkaMsg.GetRegular()
  3216  				if regular == nil {
  3217  					return fmt.Errorf("Expect message type to be regular")
  3218  				}
  3219  
  3220  				if regular.ConfigSeq != mockSupport.Sequence() {
  3221  					return fmt.Errorf("Expect new config seq to be %d, got %d", mockSupport.Sequence(), regular.ConfigSeq)
  3222  				}
  3223  
  3224  				if regular.OriginalOffset == 0 {
  3225  					return fmt.Errorf("Expect Original Offset to be non-zero if resubmission")
  3226  				}
  3227  
  3228  				expectedKafkaMsgCh <- expectedKafkaMsg
  3229  				return nil
  3230  			})
  3231  
  3232  			bareMinimumChain := &chainImpl{
  3233  				producer:        producer,
  3234  				parentConsumer:  mockParentConsumer,
  3235  				channelConsumer: mockChannelConsumer,
  3236  
  3237  				consenter:          mockConsenter,
  3238  				channel:            mockChannel,
  3239  				ConsenterSupport:   mockSupport,
  3240  				lastCutBlockNumber: lastCutBlockNumber,
  3241  
  3242  				startChan:                      startChan,
  3243  				errorChan:                      errorChan,
  3244  				haltChan:                       haltChan,
  3245  				doneProcessingMessagesToBlocks: make(chan struct{}),
  3246  				doneReprocessingMsgInFlight:    doneReprocessing,
  3247  			}
  3248  
  3249  			var counts []uint64
  3250  			done := make(chan struct{})
  3251  
  3252  			go func() {
  3253  				counts, err = bareMinimumChain.processMessagesToBlocks()
  3254  				done <- struct{}{}
  3255  			}()
  3256  
  3257  			// check that WaitReady is not blocked at beginning
  3258  			blockIngressMsg(t, false, bareMinimumChain.WaitReady)
  3259  
  3260  			// Emits a config message with lagged config sequence
  3261  			mpc.YieldMessage(newMockConsumerMessage(newConfigMessage(
  3262  				protoutil.MarshalOrPanic(newMockConfigEnvelope()),
  3263  				uint64(0),
  3264  				int64(0))))
  3265  			select {
  3266  			case <-mockSupport.Blocks:
  3267  				t.Fatalf("Expected no block being cut given lagged config message")
  3268  			case <-time.After(shortTimeout):
  3269  			}
  3270  
  3271  			// check that WaitReady is actually blocked because of in-flight reprocessed messages
  3272  			blockIngressMsg(t, true, bareMinimumChain.WaitReady)
  3273  
  3274  			select {
  3275  			case expectedKafkaMsg := <-expectedKafkaMsgCh:
  3276  				require.NotNil(t, expectedKafkaMsg)
  3277  				// Emits the kafka message produced by consenter
  3278  				mpc.YieldMessage(newMockConsumerMessage(expectedKafkaMsg))
  3279  			case <-time.After(shortTimeout):
  3280  				t.Fatalf("Expected to receive kafka message")
  3281  			}
  3282  
  3283  			select {
  3284  			case <-mockSupport.Blocks:
  3285  			case <-time.After(shortTimeout):
  3286  				t.Fatalf("Expected one block being cut")
  3287  			}
  3288  
  3289  			// `WaitReady` should be unblocked now
  3290  			blockIngressMsg(t, false, bareMinimumChain.WaitReady)
  3291  
  3292  			close(haltChan) // Identical to chain.Halt()
  3293  			<-done
  3294  
  3295  			require.NoError(t, err, "Expected the processMessagesToBlocks call to return without errors")
  3296  			require.Equal(t, uint64(2), counts[indexRecvPass], "Expected 2 message received and unmarshaled")
  3297  			require.Equal(t, uint64(2), counts[indexProcessRegularPass], "Expected 2 REGULAR message error")
  3298  		})
  3299  	})
  3300  }
  3301  
  3302  // Test helper functions here.
  3303  
  3304  func newRegularMessage(payload []byte) *ab.KafkaMessage {
  3305  	return &ab.KafkaMessage{
  3306  		Type: &ab.KafkaMessage_Regular{
  3307  			Regular: &ab.KafkaMessageRegular{
  3308  				Payload: payload,
  3309  			},
  3310  		},
  3311  	}
  3312  }
  3313  
  3314  func newMockNormalEnvelope(t *testing.T) *cb.Envelope {
  3315  	return &cb.Envelope{Payload: protoutil.MarshalOrPanic(&cb.Payload{
  3316  		Header: &cb.Header{ChannelHeader: protoutil.MarshalOrPanic(
  3317  			&cb.ChannelHeader{Type: int32(cb.HeaderType_MESSAGE), ChannelId: channelNameForTest(t)})},
  3318  		Data: []byte("Foo"),
  3319  	})}
  3320  }
  3321  
  3322  func newMockConfigEnvelope() *cb.Envelope {
  3323  	return &cb.Envelope{Payload: protoutil.MarshalOrPanic(&cb.Payload{
  3324  		Header: &cb.Header{ChannelHeader: protoutil.MarshalOrPanic(
  3325  			&cb.ChannelHeader{Type: int32(cb.HeaderType_CONFIG), ChannelId: "foo"})},
  3326  		Data: protoutil.MarshalOrPanic(&cb.ConfigEnvelope{}),
  3327  	})}
  3328  }
  3329  
  3330  func newMockOrdererTxEnvelope() *cb.Envelope {
  3331  	return &cb.Envelope{Payload: protoutil.MarshalOrPanic(&cb.Payload{
  3332  		Header: &cb.Header{ChannelHeader: protoutil.MarshalOrPanic(
  3333  			&cb.ChannelHeader{Type: int32(cb.HeaderType_ORDERER_TRANSACTION), ChannelId: "foo"})},
  3334  		Data: protoutil.MarshalOrPanic(newMockConfigEnvelope()),
  3335  	})}
  3336  }
  3337  
  3338  func TestDeliverSession(t *testing.T) {
  3339  	type testEnvironment struct {
  3340  		channelID  string
  3341  		topic      string
  3342  		partition  int32
  3343  		height     int64
  3344  		nextOffset int64
  3345  		support    *mockConsenterSupport
  3346  		broker0    *sarama.MockBroker
  3347  		broker1    *sarama.MockBroker
  3348  		broker2    *sarama.MockBroker
  3349  		testMsg    sarama.Encoder
  3350  	}
  3351  
  3352  	// initializes test environment
  3353  	newTestEnvironment := func(t *testing.T) *testEnvironment {
  3354  		channelID := channelNameForTest(t)
  3355  		topic := channelID
  3356  		partition := int32(defaultPartition)
  3357  		height := int64(100)
  3358  		nextOffset := height + 1
  3359  		broker0 := sarama.NewMockBroker(t, 0)
  3360  		broker1 := sarama.NewMockBroker(t, 1)
  3361  		broker2 := sarama.NewMockBroker(t, 2)
  3362  
  3363  		// broker0 will seed the info about the other brokers and the partition leader
  3364  		broker0.SetHandlerByMap(map[string]sarama.MockResponse{
  3365  			"MetadataRequest": sarama.NewMockMetadataResponse(t).
  3366  				SetBroker(broker1.Addr(), broker1.BrokerID()).
  3367  				SetBroker(broker2.Addr(), broker2.BrokerID()).
  3368  				SetLeader(topic, partition, broker1.BrokerID()),
  3369  		})
  3370  
  3371  		// configure broker1 with responses needed for startup
  3372  		broker1.SetHandlerByMap(map[string]sarama.MockResponse{
  3373  			// CONNECT ProduceRequest
  3374  			"ProduceRequest": sarama.NewMockProduceResponse(t).
  3375  				SetError(topic, partition, sarama.ErrNoError),
  3376  			// respond to request for offset of topic
  3377  			"OffsetRequest": sarama.NewMockOffsetResponse(t).
  3378  				SetOffset(topic, partition, sarama.OffsetOldest, 0).
  3379  				SetOffset(topic, partition, sarama.OffsetNewest, nextOffset),
  3380  			// respond to fetch requests with empty response while starting up
  3381  			"FetchRequest": sarama.NewMockFetchResponse(t, 1),
  3382  		})
  3383  
  3384  		// configure broker2 with a default fetch request response
  3385  		broker2.SetHandlerByMap(map[string]sarama.MockResponse{
  3386  			// respond to fetch requests with empty response while starting up
  3387  			"FetchRequest": sarama.NewMockFetchResponse(t, 1),
  3388  		})
  3389  
  3390  		// setup mock blockcutter
  3391  		blockcutter := &mockReceiver{}
  3392  		blockcutter.On("Ordered", mock.Anything).Return([][]*cb.Envelope{{&cb.Envelope{}}}, false)
  3393  
  3394  		// setup mock chain support and mock method calls
  3395  		support := &mockConsenterSupport{}
  3396  		support.On("Height").Return(uint64(height))
  3397  		support.On("ChannelID").Return(topic)
  3398  		support.On("Sequence").Return(uint64(0))
  3399  		support.On("SharedConfig").Return(newMockOrderer(0, []string{broker0.Addr()}, false))
  3400  		support.On("ClassifyMsg", mock.Anything).Return(msgprocessor.NormalMsg, nil)
  3401  		support.On("ProcessNormalMsg", mock.Anything).Return(uint64(0), nil)
  3402  		support.On("BlockCutter").Return(blockcutter)
  3403  		support.On("CreateNextBlock", mock.Anything).Return(&cb.Block{})
  3404  		support.On("Serialize", []byte("creator"), nil)
  3405  
  3406  		// test message that will be returned by mock brokers
  3407  		testMsg := sarama.ByteEncoder(protoutil.MarshalOrPanic(
  3408  			newRegularMessage(protoutil.MarshalOrPanic(&cb.Envelope{
  3409  				Payload: protoutil.MarshalOrPanic(&cb.Payload{
  3410  					Header: &cb.Header{
  3411  						ChannelHeader: protoutil.MarshalOrPanic(&cb.ChannelHeader{
  3412  							ChannelId: topic,
  3413  						}),
  3414  					},
  3415  					Data: []byte("TEST_DATA"),
  3416  				}),
  3417  			})),
  3418  		))
  3419  
  3420  		return &testEnvironment{
  3421  			channelID:  channelID,
  3422  			topic:      topic,
  3423  			partition:  partition,
  3424  			height:     height,
  3425  			nextOffset: nextOffset,
  3426  			support:    support,
  3427  			broker0:    broker0,
  3428  			broker1:    broker1,
  3429  			broker2:    broker2,
  3430  			testMsg:    testMsg,
  3431  		}
  3432  	}
  3433  
  3434  	// BrokerDeath simulates the partition leader dying and a
  3435  	// second broker becoming the leader before the deliver session times out.
  3436  	t.Run("BrokerDeath", func(t *testing.T) {
  3437  		// initialize test environment
  3438  		env := newTestEnvironment(t)
  3439  
  3440  		// broker1 will be closed within the test
  3441  		defer env.broker0.Close()
  3442  		defer env.broker2.Close()
  3443  
  3444  		// initialize consenter
  3445  		consenter, _ := New(mockLocalConfig.Kafka, &disabled.Provider{}, &mockkafka.HealthChecker{}, nil, func(string) {})
  3446  
  3447  		// initialize chain
  3448  		metadata := &cb.Metadata{Value: protoutil.MarshalOrPanic(&ab.KafkaMetadata{LastOffsetPersisted: env.height})}
  3449  		chain, err := consenter.HandleChain(env.support, metadata)
  3450  		if err != nil {
  3451  			t.Fatal(err)
  3452  		}
  3453  
  3454  		// start the chain, and wait for it to settle down
  3455  		chain.Start()
  3456  		select {
  3457  		case <-chain.(*chainImpl).startChan:
  3458  			logger.Debug("chain started")
  3459  		case <-time.After(shortTimeout):
  3460  			t.Fatal("chain should have started by now")
  3461  		}
  3462  
  3463  		// direct blocks to this channel
  3464  		blocks := make(chan *cb.Block, 1)
  3465  		env.support.On("WriteBlock", mock.Anything, mock.Anything).Return().Run(func(arg1 mock.Arguments) {
  3466  			blocks <- arg1.Get(0).(*cb.Block)
  3467  		})
  3468  
  3469  		// send a few messages from broker1
  3470  		fetchResponse1 := sarama.NewMockFetchResponse(t, 1)
  3471  		for i := 0; i < 5; i++ {
  3472  			fetchResponse1.SetMessage(env.topic, env.partition, env.nextOffset, env.testMsg)
  3473  			env.nextOffset++
  3474  		}
  3475  		env.broker1.SetHandlerByMap(map[string]sarama.MockResponse{
  3476  			"FetchRequest": fetchResponse1,
  3477  		})
  3478  
  3479  		logger.Debug("Waiting for messages from broker1")
  3480  		for i := 0; i < 5; i++ {
  3481  			select {
  3482  			case <-blocks:
  3483  			case <-time.After(shortTimeout):
  3484  				t.Fatalf("timed out waiting for messages (receieved %d messages)", i)
  3485  			}
  3486  		}
  3487  
  3488  		// prepare broker2 to send a few messages
  3489  		fetchResponse2 := sarama.NewMockFetchResponse(t, 1)
  3490  		for i := 0; i < 5; i++ {
  3491  			fetchResponse2.SetMessage(env.topic, env.partition, env.nextOffset, env.testMsg)
  3492  			env.nextOffset++
  3493  		}
  3494  
  3495  		env.broker2.SetHandlerByMap(map[string]sarama.MockResponse{
  3496  			"ProduceRequest": sarama.NewMockProduceResponse(t).
  3497  				SetError(env.topic, env.partition, sarama.ErrNoError),
  3498  			"FetchRequest": fetchResponse2,
  3499  		})
  3500  
  3501  		// shutdown broker1
  3502  		env.broker1.Close()
  3503  
  3504  		// prepare broker0 to respond that broker2 is now the leader
  3505  		env.broker0.SetHandlerByMap(map[string]sarama.MockResponse{
  3506  			"MetadataRequest": sarama.NewMockMetadataResponse(t).
  3507  				SetLeader(env.topic, env.partition, env.broker2.BrokerID()),
  3508  		})
  3509  
  3510  		logger.Debug("Waiting for messages from broker2")
  3511  		for i := 0; i < 5; i++ {
  3512  			select {
  3513  			case <-blocks:
  3514  			case <-time.After(shortTimeout):
  3515  				t.Fatalf("timed out waiting for messages (receieved %d messages)", i)
  3516  			}
  3517  		}
  3518  
  3519  		chain.Halt()
  3520  	})
  3521  
  3522  	// An ErrOffsetOutOfRange is non-recoverable
  3523  	t.Run("ErrOffsetOutOfRange", func(t *testing.T) {
  3524  		// initialize test environment
  3525  		env := newTestEnvironment(t)
  3526  
  3527  		// broker cleanup
  3528  		defer env.broker2.Close()
  3529  		defer env.broker1.Close()
  3530  		defer env.broker0.Close()
  3531  
  3532  		// initialize consenter
  3533  		consenter, _ := New(mockLocalConfig.Kafka, &disabled.Provider{}, &mockkafka.HealthChecker{}, nil, func(string) {})
  3534  
  3535  		// initialize chain
  3536  		metadata := &cb.Metadata{Value: protoutil.MarshalOrPanic(&ab.KafkaMetadata{LastOffsetPersisted: env.height})}
  3537  		chain, err := consenter.HandleChain(env.support, metadata)
  3538  		if err != nil {
  3539  			t.Fatal(err)
  3540  		}
  3541  
  3542  		// start the chain, and wait for it to settle down
  3543  		chain.Start()
  3544  		select {
  3545  		case <-chain.(*chainImpl).startChan:
  3546  			logger.Debug("chain started")
  3547  		case <-time.After(shortTimeout):
  3548  			t.Fatal("chain should have started by now")
  3549  		}
  3550  
  3551  		// direct blocks to this channel
  3552  		blocks := make(chan *cb.Block, 1)
  3553  		env.support.On("WriteBlock", mock.Anything, mock.Anything).Return().Run(func(arg1 mock.Arguments) {
  3554  			blocks <- arg1.Get(0).(*cb.Block)
  3555  		})
  3556  
  3557  		// set broker1 to respond to two fetch requests:
  3558  		// - The first fetch request will get an ErrOffsetOutOfRange error response.
  3559  		// - The second fetch request will get a valid (i.e. non-error) response.
  3560  		fetchResponse := &sarama.FetchResponse{}
  3561  		fetchResponse.AddError(env.topic, env.partition, sarama.ErrOffsetOutOfRange)
  3562  		fetchResponse.AddMessage(env.topic, env.partition, nil, env.testMsg, env.nextOffset)
  3563  		env.nextOffset++
  3564  		env.broker1.SetHandlerByMap(map[string]sarama.MockResponse{
  3565  			"FetchRequest": sarama.NewMockWrapper(fetchResponse),
  3566  			// answers for CONNECT message
  3567  			"ProduceRequest": sarama.NewMockProduceResponse(t).
  3568  				SetError(env.topic, env.partition, sarama.ErrNoError),
  3569  		})
  3570  
  3571  		select {
  3572  		case <-blocks:
  3573  			// the valid fetch response should not of been fetched
  3574  			t.Fatal("Did not expect new blocks")
  3575  		case <-time.After(shortTimeout):
  3576  			t.Fatal("Errored() should have closed by now")
  3577  		case <-chain.Errored():
  3578  		}
  3579  
  3580  		chain.Halt()
  3581  	})
  3582  
  3583  	// test chain timeout
  3584  	t.Run("DeliverSessionTimedOut", func(t *testing.T) {
  3585  		// initialize test environment
  3586  		env := newTestEnvironment(t)
  3587  
  3588  		// broker cleanup
  3589  		defer env.broker2.Close()
  3590  		defer env.broker1.Close()
  3591  		defer env.broker0.Close()
  3592  
  3593  		// initialize consenter
  3594  		consenter, _ := New(mockLocalConfig.Kafka, &disabled.Provider{}, &mockkafka.HealthChecker{}, nil, func(string) {})
  3595  
  3596  		// initialize chain
  3597  		metadata := &cb.Metadata{Value: protoutil.MarshalOrPanic(&ab.KafkaMetadata{LastOffsetPersisted: env.height})}
  3598  		chain, err := consenter.HandleChain(env.support, metadata)
  3599  		if err != nil {
  3600  			t.Fatal(err)
  3601  		}
  3602  
  3603  		// start the chain, and wait for it to settle down
  3604  		chain.Start()
  3605  		select {
  3606  		case <-chain.(*chainImpl).startChan:
  3607  			logger.Debug("chain started")
  3608  		case <-time.After(shortTimeout):
  3609  			t.Fatal("chain should have started by now")
  3610  		}
  3611  
  3612  		// direct blocks to this channel
  3613  		blocks := make(chan *cb.Block, 1)
  3614  		env.support.On("WriteBlock", mock.Anything, mock.Anything).Return().Run(func(arg1 mock.Arguments) {
  3615  			blocks <- arg1.Get(0).(*cb.Block)
  3616  		})
  3617  
  3618  		metadataResponse := new(sarama.MetadataResponse)
  3619  		metadataResponse.AddTopicPartition(env.topic, env.partition, -1, []int32{}, []int32{}, sarama.ErrBrokerNotAvailable)
  3620  
  3621  		// configure seed broker to return error on metadata request, otherwise the
  3622  		// consumer client will keep 'subscribing' successfully to the topic/partition
  3623  		env.broker0.SetHandlerByMap(map[string]sarama.MockResponse{
  3624  			"MetadataRequest": sarama.NewMockWrapper(metadataResponse),
  3625  		})
  3626  
  3627  		// set broker1 to return an error.
  3628  		// Note that the following are not considered errors from the sarama client
  3629  		// consumer's point of view:
  3630  		// - ErrUnknownTopicOrPartition
  3631  		// - ErrNotLeaderForPartition
  3632  		// - ErrLeaderNotAvailable
  3633  		// - ErrReplicaNotAvailable:
  3634  		fetchResponse := &sarama.FetchResponse{}
  3635  		fetchResponse.AddError(env.topic, env.partition, sarama.ErrUnknown)
  3636  		env.broker1.SetHandlerByMap(map[string]sarama.MockResponse{
  3637  			"FetchRequest": sarama.NewMockWrapper(fetchResponse),
  3638  			// answers for CONNECT message
  3639  			"ProduceRequest": sarama.NewMockProduceResponse(t).
  3640  				SetError(env.topic, env.partition, sarama.ErrNoError),
  3641  		})
  3642  
  3643  		select {
  3644  		case <-blocks:
  3645  			t.Fatal("Did not expect new blocks")
  3646  		case <-time.After(mockRetryOptions.NetworkTimeouts.ReadTimeout + shortTimeout):
  3647  			t.Fatal("Errored() should have closed by now")
  3648  		case <-chain.Errored():
  3649  			t.Log("Errored() closed")
  3650  		}
  3651  
  3652  		chain.Halt()
  3653  	})
  3654  }
  3655  
  3656  func TestHealthCheck(t *testing.T) {
  3657  	gt := NewGomegaWithT(t)
  3658  	var err error
  3659  
  3660  	ch := newChannel("mockChannelFoo", defaultPartition)
  3661  	mockSyncProducer := &mockkafka.SyncProducer{}
  3662  	chain := &chainImpl{
  3663  		channel:  ch,
  3664  		producer: mockSyncProducer,
  3665  	}
  3666  
  3667  	err = chain.HealthCheck(context.Background())
  3668  	gt.Expect(err).NotTo(HaveOccurred())
  3669  	gt.Expect(mockSyncProducer.SendMessageCallCount()).To(Equal(1))
  3670  
  3671  	payload := protoutil.MarshalOrPanic(newConnectMessage())
  3672  	message := newProducerMessage(chain.channel, payload)
  3673  	gt.Expect(mockSyncProducer.SendMessageArgsForCall(0)).To(Equal(message))
  3674  
  3675  	// Only return error if the error is not for enough replicas
  3676  	mockSyncProducer.SendMessageReturns(int32(1), int64(1), sarama.ErrNotEnoughReplicas)
  3677  	chain.replicaIDs = []int32{int32(1), int32(2)}
  3678  	err = chain.HealthCheck(context.Background())
  3679  	gt.Expect(err).To(MatchError(fmt.Sprintf("[replica ids: [1 2]]: %s", sarama.ErrNotEnoughReplicas.Error())))
  3680  	gt.Expect(mockSyncProducer.SendMessageCallCount()).To(Equal(2))
  3681  
  3682  	// If another type of error is returned, it should be ignored by health check
  3683  	mockSyncProducer.SendMessageReturns(int32(1), int64(1), errors.New("error occurred"))
  3684  	err = chain.HealthCheck(context.Background())
  3685  	gt.Expect(err).NotTo(HaveOccurred())
  3686  	gt.Expect(mockSyncProducer.SendMessageCallCount()).To(Equal(3))
  3687  }
  3688  
  3689  type mockReceiver struct {
  3690  	mock.Mock
  3691  }
  3692  
  3693  func (r *mockReceiver) Ordered(msg *cb.Envelope) (messageBatches [][]*cb.Envelope, pending bool) {
  3694  	args := r.Called(msg)
  3695  	return args.Get(0).([][]*cb.Envelope), args.Bool(1)
  3696  }
  3697  
  3698  func (r *mockReceiver) Cut() []*cb.Envelope {
  3699  	args := r.Called()
  3700  	return args.Get(0).([]*cb.Envelope)
  3701  }
  3702  
  3703  type mockConsenterSupport struct {
  3704  	mock.Mock
  3705  }
  3706  
  3707  func (c *mockConsenterSupport) Block(seq uint64) *cb.Block {
  3708  	return nil
  3709  }
  3710  
  3711  func (c *mockConsenterSupport) VerifyBlockSignature([]*protoutil.SignedData, *cb.ConfigEnvelope) error {
  3712  	return nil
  3713  }
  3714  
  3715  func (c *mockConsenterSupport) NewSignatureHeader() (*cb.SignatureHeader, error) {
  3716  	args := c.Called()
  3717  	return args.Get(0).(*cb.SignatureHeader), args.Error(1)
  3718  }
  3719  
  3720  func (c *mockConsenterSupport) Sign(message []byte) ([]byte, error) {
  3721  	args := c.Called(message)
  3722  	return args.Get(0).([]byte), args.Error(1)
  3723  }
  3724  
  3725  func (c *mockConsenterSupport) Serialize() ([]byte, error) {
  3726  	args := c.Called()
  3727  	return args.Get(0).([]byte), args.Error(1)
  3728  }
  3729  
  3730  func (c *mockConsenterSupport) ClassifyMsg(chdr *cb.ChannelHeader) msgprocessor.Classification {
  3731  	args := c.Called(chdr)
  3732  	return args.Get(0).(msgprocessor.Classification)
  3733  }
  3734  
  3735  func (c *mockConsenterSupport) ProcessNormalMsg(env *cb.Envelope) (configSeq uint64, err error) {
  3736  	args := c.Called(env)
  3737  	return args.Get(0).(uint64), args.Error(1)
  3738  }
  3739  
  3740  func (c *mockConsenterSupport) ProcessConfigUpdateMsg(env *cb.Envelope) (config *cb.Envelope, configSeq uint64, err error) {
  3741  	args := c.Called(env)
  3742  	return args.Get(0).(*cb.Envelope), args.Get(1).(uint64), args.Error(2)
  3743  }
  3744  
  3745  func (c *mockConsenterSupport) ProcessConfigMsg(env *cb.Envelope) (config *cb.Envelope, configSeq uint64, err error) {
  3746  	args := c.Called(env)
  3747  	return args.Get(0).(*cb.Envelope), args.Get(1).(uint64), args.Error(2)
  3748  }
  3749  
  3750  func (c *mockConsenterSupport) BlockCutter() blockcutter.Receiver {
  3751  	args := c.Called()
  3752  	return args.Get(0).(blockcutter.Receiver)
  3753  }
  3754  
  3755  func (c *mockConsenterSupport) SharedConfig() channelconfig.Orderer {
  3756  	args := c.Called()
  3757  	return args.Get(0).(channelconfig.Orderer)
  3758  }
  3759  
  3760  func (c *mockConsenterSupport) ChannelConfig() channelconfig.Channel {
  3761  	args := c.Called()
  3762  	return args.Get(0).(channelconfig.Channel)
  3763  }
  3764  
  3765  func (c *mockConsenterSupport) CreateNextBlock(messages []*cb.Envelope) *cb.Block {
  3766  	args := c.Called(messages)
  3767  	return args.Get(0).(*cb.Block)
  3768  }
  3769  
  3770  func (c *mockConsenterSupport) WriteBlock(block *cb.Block, encodedMetadataValue []byte) {
  3771  	c.Called(block, encodedMetadataValue)
  3772  }
  3773  
  3774  func (c *mockConsenterSupport) WriteConfigBlock(block *cb.Block, encodedMetadataValue []byte) {
  3775  	c.Called(block, encodedMetadataValue)
  3776  }
  3777  
  3778  func (c *mockConsenterSupport) Sequence() uint64 {
  3779  	args := c.Called()
  3780  	return args.Get(0).(uint64)
  3781  }
  3782  
  3783  func (c *mockConsenterSupport) ChannelID() string {
  3784  	args := c.Called()
  3785  	return args.String(0)
  3786  }
  3787  
  3788  func (c *mockConsenterSupport) Height() uint64 {
  3789  	args := c.Called()
  3790  	return args.Get(0).(uint64)
  3791  }
  3792  
  3793  func (c *mockConsenterSupport) Append(block *cb.Block) error {
  3794  	c.Called(block)
  3795  	return nil
  3796  }