github.com/koko1123/flow-go-1@v0.29.6/engine/collection/ingest/engine_test.go (about)

     1  package ingest
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"io"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/rs/zerolog"
    11  	"github.com/stretchr/testify/mock"
    12  	"github.com/stretchr/testify/suite"
    13  
    14  	"github.com/koko1123/flow-go-1/access"
    15  	"github.com/koko1123/flow-go-1/engine"
    16  	"github.com/koko1123/flow-go-1/model/flow"
    17  	"github.com/koko1123/flow-go-1/model/flow/factory"
    18  	"github.com/koko1123/flow-go-1/model/flow/filter"
    19  	"github.com/koko1123/flow-go-1/module/component"
    20  	"github.com/koko1123/flow-go-1/module/irrecoverable"
    21  	"github.com/koko1123/flow-go-1/module/mempool"
    22  	"github.com/koko1123/flow-go-1/module/mempool/epochs"
    23  	"github.com/koko1123/flow-go-1/module/mempool/herocache"
    24  	"github.com/koko1123/flow-go-1/module/metrics"
    25  	module "github.com/koko1123/flow-go-1/module/mock"
    26  	"github.com/koko1123/flow-go-1/network"
    27  	"github.com/koko1123/flow-go-1/network/mocknetwork"
    28  	realprotocol "github.com/koko1123/flow-go-1/state/protocol"
    29  	protocol "github.com/koko1123/flow-go-1/state/protocol/mock"
    30  	"github.com/koko1123/flow-go-1/storage"
    31  	"github.com/koko1123/flow-go-1/utils/unittest"
    32  	"github.com/koko1123/flow-go-1/utils/unittest/mocks"
    33  )
    34  
    35  type Suite struct {
    36  	suite.Suite
    37  
    38  	N_COLLECTORS int
    39  	N_CLUSTERS   uint
    40  
    41  	conduit *mocknetwork.Conduit
    42  	me      *module.Local
    43  	conf    Config
    44  
    45  	pools *epochs.TransactionPools
    46  
    47  	identities flow.IdentityList
    48  	clusters   flow.ClusterList
    49  
    50  	state      *protocol.State
    51  	snapshot   *protocol.Snapshot
    52  	epochQuery *mocks.EpochQuery
    53  	root       *flow.Block
    54  
    55  	// backend for mocks
    56  	blocks map[flow.Identifier]*flow.Block
    57  	final  *flow.Block
    58  
    59  	engine *Engine
    60  }
    61  
    62  func TestIngest(t *testing.T) {
    63  	suite.Run(t, new(Suite))
    64  }
    65  
    66  func (suite *Suite) SetupTest() {
    67  	var err error
    68  
    69  	suite.N_COLLECTORS = 4
    70  	suite.N_CLUSTERS = 2
    71  
    72  	log := zerolog.New(io.Discard)
    73  	metrics := metrics.NewNoopCollector()
    74  
    75  	net := new(mocknetwork.Network)
    76  	suite.conduit = new(mocknetwork.Conduit)
    77  	net.On("Register", mock.Anything, mock.Anything).Return(suite.conduit, nil).Once()
    78  
    79  	collectors := unittest.IdentityListFixture(suite.N_COLLECTORS, unittest.WithRole(flow.RoleCollection))
    80  	me := collectors[0]
    81  	others := unittest.IdentityListFixture(4, unittest.WithAllRolesExcept(flow.RoleCollection))
    82  	suite.identities = append(collectors, others...)
    83  
    84  	suite.me = new(module.Local)
    85  	suite.me.On("NodeID").Return(me.NodeID)
    86  
    87  	suite.pools = epochs.NewTransactionPools(func(_ uint64) mempool.Transactions {
    88  		return herocache.NewTransactions(1000, log, metrics)
    89  	})
    90  
    91  	assignments := unittest.ClusterAssignment(suite.N_CLUSTERS, collectors)
    92  	suite.clusters, err = factory.NewClusterList(assignments, collectors)
    93  	suite.Require().NoError(err)
    94  
    95  	suite.root = unittest.GenesisFixture()
    96  	suite.final = suite.root
    97  	suite.blocks = make(map[flow.Identifier]*flow.Block)
    98  	suite.blocks[suite.root.ID()] = suite.root
    99  
   100  	suite.state = new(protocol.State)
   101  	suite.snapshot = new(protocol.Snapshot)
   102  	suite.state.On("Final").Return(suite.snapshot)
   103  	suite.snapshot.On("Head").Return(
   104  		func() *flow.Header { return suite.final.Header },
   105  		func() error { return nil },
   106  	)
   107  	suite.state.On("AtBlockID", mock.Anything).Return(
   108  		func(blockID flow.Identifier) realprotocol.Snapshot {
   109  			snap := new(protocol.Snapshot)
   110  			block, ok := suite.blocks[blockID]
   111  			if ok {
   112  				snap.On("Head").Return(block.Header, nil)
   113  			} else {
   114  				snap.On("Head").Return(nil, storage.ErrNotFound)
   115  			}
   116  			snap.On("Epochs").Return(suite.epochQuery)
   117  			return snap
   118  		})
   119  
   120  	// set up the current epoch by default, with counter=1
   121  	epoch := new(protocol.Epoch)
   122  	epoch.On("Counter").Return(uint64(1), nil)
   123  	epoch.On("Clustering").Return(suite.clusters, nil)
   124  	suite.epochQuery = mocks.NewEpochQuery(suite.T(), 1, epoch)
   125  
   126  	suite.conf = DefaultConfig()
   127  	chain := flow.Testnet.Chain()
   128  	suite.engine, err = New(log, net, suite.state, metrics, metrics, metrics, suite.me, chain, suite.pools, suite.conf)
   129  	suite.Require().NoError(err)
   130  }
   131  
   132  func (suite *Suite) TestInvalidTransaction() {
   133  
   134  	suite.Run("missing field", func() {
   135  		tx := unittest.TransactionBodyFixture()
   136  		tx.ReferenceBlockID = suite.root.ID()
   137  		tx.Script = nil
   138  
   139  		err := suite.engine.ProcessTransaction(&tx)
   140  		suite.Assert().Error(err)
   141  		suite.Assert().True(errors.As(err, &access.IncompleteTransactionError{}))
   142  	})
   143  
   144  	suite.Run("gas limit exceeds the maximum allowed", func() {
   145  		tx := unittest.TransactionBodyFixture()
   146  		tx.Payer = unittest.RandomAddressFixture()
   147  		tx.ReferenceBlockID = suite.root.ID()
   148  		tx.GasLimit = flow.DefaultMaxTransactionGasLimit + 1
   149  
   150  		err := suite.engine.ProcessTransaction(&tx)
   151  		suite.Assert().Error(err)
   152  		suite.Assert().True(errors.As(err, &access.InvalidGasLimitError{}))
   153  	})
   154  
   155  	suite.Run("invalid reference block ID", func() {
   156  		tx := unittest.TransactionBodyFixture()
   157  		tx.ReferenceBlockID = unittest.IdentifierFixture()
   158  
   159  		err := suite.engine.ProcessTransaction(&tx)
   160  		suite.Assert().Error(err)
   161  		suite.Assert().True(errors.As(err, &engine.UnverifiableInputError{}))
   162  	})
   163  
   164  	suite.Run("un-parseable script", func() {
   165  		tx := unittest.TransactionBodyFixture()
   166  		tx.ReferenceBlockID = suite.root.ID()
   167  		tx.Script = []byte("definitely a real transaction")
   168  
   169  		err := suite.engine.ProcessTransaction(&tx)
   170  		suite.Assert().Error(err)
   171  		suite.Assert().True(errors.As(err, &access.InvalidScriptError{}))
   172  	})
   173  
   174  	suite.Run("invalid signature format", func() {
   175  		signer := flow.Testnet.Chain().ServiceAddress()
   176  		keyIndex := uint64(0)
   177  
   178  		sig1 := unittest.TransactionSignatureFixture()
   179  		sig1.KeyIndex = keyIndex
   180  		sig1.Address = signer
   181  		sig1.SignerIndex = 0
   182  
   183  		sig2 := unittest.TransactionSignatureFixture()
   184  		sig2.KeyIndex = keyIndex
   185  		sig2.Address = signer
   186  		sig2.SignerIndex = 1
   187  
   188  		suite.Run("invalid format of an envelope signature", func() {
   189  			invalidSig := unittest.InvalidFormatSignature()
   190  			tx := unittest.TransactionBodyFixture()
   191  			tx.ReferenceBlockID = suite.root.ID()
   192  			tx.EnvelopeSignatures[0] = invalidSig
   193  
   194  			err := suite.engine.ProcessTransaction(&tx)
   195  			suite.Assert().Error(err)
   196  			suite.Assert().True(errors.As(err, &access.InvalidSignatureError{}))
   197  		})
   198  
   199  		suite.Run("invalid format of a payload signature", func() {
   200  			invalidSig := unittest.InvalidFormatSignature()
   201  			tx := unittest.TransactionBodyFixture()
   202  			tx.ReferenceBlockID = suite.root.ID()
   203  			tx.PayloadSignatures = []flow.TransactionSignature{invalidSig}
   204  
   205  			err := suite.engine.ProcessTransaction(&tx)
   206  			suite.Assert().Error(err)
   207  			suite.Assert().True(errors.As(err, &access.InvalidSignatureError{}))
   208  		})
   209  
   210  		suite.Run("duplicated signature (envelope only)", func() {
   211  			tx := unittest.TransactionBodyFixture()
   212  			tx.ReferenceBlockID = suite.root.ID()
   213  			tx.EnvelopeSignatures = []flow.TransactionSignature{sig1, sig2}
   214  			err := suite.engine.ProcessTransaction(&tx)
   215  			suite.Assert().Error(err)
   216  			suite.Assert().True(errors.As(err, &access.DuplicatedSignatureError{}))
   217  		})
   218  
   219  		suite.Run("duplicated signature (payload only)", func() {
   220  			tx := unittest.TransactionBodyFixture()
   221  			tx.ReferenceBlockID = suite.root.ID()
   222  			tx.PayloadSignatures = []flow.TransactionSignature{sig1, sig2}
   223  
   224  			err := suite.engine.ProcessTransaction(&tx)
   225  			suite.Assert().Error(err)
   226  			suite.Assert().True(errors.As(err, &access.DuplicatedSignatureError{}))
   227  		})
   228  
   229  		suite.Run("duplicated signature (cross case)", func() {
   230  			tx := unittest.TransactionBodyFixture()
   231  			tx.ReferenceBlockID = suite.root.ID()
   232  			tx.PayloadSignatures = []flow.TransactionSignature{sig1}
   233  			tx.EnvelopeSignatures = []flow.TransactionSignature{sig2}
   234  
   235  			err := suite.engine.ProcessTransaction(&tx)
   236  			suite.Assert().Error(err)
   237  			suite.Assert().True(errors.As(err, &access.DuplicatedSignatureError{}))
   238  		})
   239  	})
   240  
   241  	suite.Run("invalid signature", func() {
   242  		// TODO cannot check signatures in MVP
   243  		unittest.SkipUnless(suite.T(), unittest.TEST_TODO, "skipping unimplemented test")
   244  	})
   245  
   246  	suite.Run("invalid address", func() {
   247  		invalid := unittest.InvalidAddressFixture()
   248  		tx := unittest.TransactionBodyFixture()
   249  		tx.ReferenceBlockID = suite.root.ID()
   250  		tx.Payer = invalid
   251  
   252  		err := suite.engine.ProcessTransaction(&tx)
   253  		suite.Assert().Error(err)
   254  		suite.Assert().True(errors.As(err, &access.InvalidAddressError{}))
   255  	})
   256  
   257  	suite.Run("expired reference block ID", func() {
   258  		// "finalize" a sufficiently high block that root block is expired
   259  		final := unittest.BlockFixture()
   260  		final.Header.Height = suite.root.Header.Height + flow.DefaultTransactionExpiry + 1
   261  		suite.final = &final
   262  
   263  		tx := unittest.TransactionBodyFixture()
   264  		tx.ReferenceBlockID = suite.root.ID()
   265  
   266  		err := suite.engine.ProcessTransaction(&tx)
   267  		suite.Assert().Error(err)
   268  		suite.Assert().True(errors.As(err, &access.ExpiredTransactionError{}))
   269  	})
   270  
   271  }
   272  
   273  // should return an error if the engine is shutdown and not processing transactions
   274  func (suite *Suite) TestComponentShutdown() {
   275  	tx := unittest.TransactionBodyFixture()
   276  	tx.ReferenceBlockID = suite.root.ID()
   277  
   278  	// start then shut down the engine
   279  	parentCtx, cancel := context.WithCancel(context.Background())
   280  	ctx, _ := irrecoverable.WithSignaler(parentCtx)
   281  	suite.engine.Start(ctx)
   282  	unittest.AssertClosesBefore(suite.T(), suite.engine.Ready(), 10*time.Millisecond)
   283  	cancel()
   284  	unittest.AssertClosesBefore(suite.T(), suite.engine.ShutdownSignal(), 10*time.Millisecond)
   285  
   286  	err := suite.engine.ProcessTransaction(&tx)
   287  	suite.Assert().ErrorIs(err, component.ErrComponentShutdown)
   288  }
   289  
   290  // should store transactions for local cluster and propagate to other cluster members
   291  func (suite *Suite) TestRoutingLocalCluster() {
   292  
   293  	local, _, ok := suite.clusters.ByNodeID(suite.me.NodeID())
   294  	suite.Require().True(ok)
   295  
   296  	// get a transaction that will be routed to local cluster
   297  	tx := unittest.TransactionBodyFixture()
   298  	tx.ReferenceBlockID = suite.root.ID()
   299  	tx = unittest.AlterTransactionForCluster(tx, suite.clusters, local, func(transaction *flow.TransactionBody) {})
   300  
   301  	// should route to local cluster
   302  	suite.conduit.
   303  		On("Multicast", &tx, suite.conf.PropagationRedundancy+1, local.NodeIDs()[0], local.NodeIDs()[1]).
   304  		Return(nil)
   305  
   306  	err := suite.engine.ProcessTransaction(&tx)
   307  	suite.Assert().NoError(err)
   308  
   309  	// should be added to local mempool for the current epoch
   310  	counter, err := suite.epochQuery.Current().Counter()
   311  	suite.Assert().NoError(err)
   312  	suite.Assert().True(suite.pools.ForEpoch(counter).Has(tx.ID()))
   313  	suite.conduit.AssertExpectations(suite.T())
   314  }
   315  
   316  // should not store transactions for a different cluster and should propagate
   317  // to the responsible cluster
   318  func (suite *Suite) TestRoutingRemoteCluster() {
   319  
   320  	// find a remote cluster
   321  	_, index, ok := suite.clusters.ByNodeID(suite.me.NodeID())
   322  	suite.Require().True(ok)
   323  	remote, ok := suite.clusters.ByIndex((index + 1) % suite.N_CLUSTERS)
   324  	suite.Require().True(ok)
   325  
   326  	// get a transaction that will be routed to remote cluster
   327  	tx := unittest.TransactionBodyFixture()
   328  	tx.ReferenceBlockID = suite.root.ID()
   329  	tx = unittest.AlterTransactionForCluster(tx, suite.clusters, remote, func(transaction *flow.TransactionBody) {})
   330  
   331  	// should route to remote cluster
   332  	suite.conduit.
   333  		On("Multicast", &tx, suite.conf.PropagationRedundancy+1, remote[0].NodeID, remote[1].NodeID).
   334  		Return(nil)
   335  
   336  	err := suite.engine.ProcessTransaction(&tx)
   337  	suite.Assert().NoError(err)
   338  
   339  	// should not be added to local mempool
   340  	counter, err := suite.epochQuery.Current().Counter()
   341  	suite.Assert().NoError(err)
   342  	suite.Assert().False(suite.pools.ForEpoch(counter).Has(tx.ID()))
   343  	suite.conduit.AssertExpectations(suite.T())
   344  }
   345  
   346  // should not store transactions for a different cluster and should not fail when propagating
   347  // to an empty cluster
   348  func (suite *Suite) TestRoutingToRemoteClusterWithNoNodes() {
   349  
   350  	// find a remote cluster
   351  	_, index, ok := suite.clusters.ByNodeID(suite.me.NodeID())
   352  	suite.Require().True(ok)
   353  
   354  	// set the next cluster to be empty
   355  	emptyIdentityList := flow.IdentityList{}
   356  	nextClusterIndex := (index + 1) % suite.N_CLUSTERS
   357  	suite.clusters[nextClusterIndex] = emptyIdentityList
   358  
   359  	// get a transaction that will be routed to remote cluster
   360  	tx := unittest.TransactionBodyFixture()
   361  	tx.ReferenceBlockID = suite.root.ID()
   362  	tx = unittest.AlterTransactionForCluster(tx, suite.clusters, emptyIdentityList, func(transaction *flow.TransactionBody) {})
   363  
   364  	// should attempt route to remote cluster without providing any node ids
   365  	suite.conduit.
   366  		On("Multicast", &tx, suite.conf.PropagationRedundancy+1).
   367  		Return(network.EmptyTargetList)
   368  
   369  	err := suite.engine.ProcessTransaction(&tx)
   370  	suite.Assert().NoError(err)
   371  
   372  	// should not be added to local mempool
   373  	counter, err := suite.epochQuery.Current().Counter()
   374  	suite.Assert().NoError(err)
   375  	suite.Assert().False(suite.pools.ForEpoch(counter).Has(tx.ID()))
   376  	suite.conduit.AssertExpectations(suite.T())
   377  }
   378  
   379  // should not propagate transactions received from another node (that node is
   380  // responsible for propagation)
   381  func (suite *Suite) TestRoutingLocalClusterFromOtherNode() {
   382  
   383  	local, _, ok := suite.clusters.ByNodeID(suite.me.NodeID())
   384  	suite.Require().True(ok)
   385  
   386  	// another node will send us the transaction
   387  	sender := local.Filter(filter.Not(filter.HasNodeID(suite.me.NodeID())))[0]
   388  
   389  	// get a transaction that will be routed to local cluster
   390  	tx := unittest.TransactionBodyFixture()
   391  	tx.ReferenceBlockID = suite.root.ID()
   392  	tx = unittest.AlterTransactionForCluster(tx, suite.clusters, local, func(transaction *flow.TransactionBody) {})
   393  
   394  	// should not route to any node
   395  	suite.conduit.AssertNumberOfCalls(suite.T(), "Multicast", 0)
   396  
   397  	err := suite.engine.onTransaction(sender.NodeID, &tx)
   398  	suite.Assert().NoError(err)
   399  
   400  	// should be added to local mempool for current epoch
   401  	counter, err := suite.epochQuery.Current().Counter()
   402  	suite.Assert().NoError(err)
   403  	suite.Assert().True(suite.pools.ForEpoch(counter).Has(tx.ID()))
   404  	suite.conduit.AssertExpectations(suite.T())
   405  }
   406  
   407  // should not route or store invalid transactions
   408  func (suite *Suite) TestRoutingInvalidTransaction() {
   409  
   410  	// find a remote cluster
   411  	_, index, ok := suite.clusters.ByNodeID(suite.me.NodeID())
   412  	suite.Require().True(ok)
   413  	remote, ok := suite.clusters.ByIndex((index + 1) % suite.N_CLUSTERS)
   414  	suite.Require().True(ok)
   415  
   416  	// get transaction for target cluster, but make it invalid
   417  	tx := unittest.TransactionBodyFixture()
   418  	tx = unittest.AlterTransactionForCluster(tx, suite.clusters, remote,
   419  		func(tx *flow.TransactionBody) {
   420  			tx.GasLimit = 0
   421  		})
   422  
   423  	// should not route to any node
   424  	suite.conduit.AssertNumberOfCalls(suite.T(), "Multicast", 0)
   425  
   426  	_ = suite.engine.ProcessTransaction(&tx)
   427  
   428  	// should not be added to local mempool
   429  	counter, err := suite.epochQuery.Current().Counter()
   430  	suite.Assert().NoError(err)
   431  	suite.Assert().False(suite.pools.ForEpoch(counter).Has(tx.ID()))
   432  	suite.conduit.AssertExpectations(suite.T())
   433  }
   434  
   435  // We should route to the appropriate cluster if our cluster assignment changes
   436  // on an epoch boundary. In this test, the clusters in epoch 2 are the reverse
   437  // of those in epoch 1, and we check that the transaction is routed based on
   438  // the clustering in epoch 2.
   439  func (suite *Suite) TestRouting_ClusterAssignmentChanged() {
   440  
   441  	epoch2Clusters := flow.ClusterList{
   442  		suite.clusters[1],
   443  		suite.clusters[0],
   444  	}
   445  	epoch2 := new(protocol.Epoch)
   446  	epoch2.On("Counter").Return(uint64(2), nil)
   447  	epoch2.On("Clustering").Return(epoch2Clusters, nil)
   448  	// update the mocks to behave as though we have transitioned to epoch 2
   449  	suite.epochQuery.Add(epoch2)
   450  	suite.epochQuery.Transition()
   451  
   452  	// get the local cluster in epoch 2
   453  	epoch2Local, _, ok := epoch2Clusters.ByNodeID(suite.me.NodeID())
   454  	suite.Require().True(ok)
   455  
   456  	// get a transaction that will be routed to local cluster
   457  	tx := unittest.TransactionBodyFixture()
   458  	tx.ReferenceBlockID = suite.root.ID()
   459  	tx = unittest.AlterTransactionForCluster(tx, epoch2Clusters, epoch2Local, func(transaction *flow.TransactionBody) {})
   460  
   461  	// should route to local cluster
   462  	suite.conduit.On("Multicast", &tx, suite.conf.PropagationRedundancy+1, epoch2Local.NodeIDs()[0], epoch2Local.NodeIDs()[1]).Return(nil).Once()
   463  
   464  	err := suite.engine.ProcessTransaction(&tx)
   465  	suite.Assert().NoError(err)
   466  
   467  	// should add to local mempool for epoch 2 only
   468  	suite.Assert().True(suite.pools.ForEpoch(2).Has(tx.ID()))
   469  	suite.Assert().False(suite.pools.ForEpoch(1).Has(tx.ID()))
   470  	suite.conduit.AssertExpectations(suite.T())
   471  }
   472  
   473  // We will discard all transactions when we aren't assigned to any cluster.
   474  func (suite *Suite) TestRouting_ClusterAssignmentRemoved() {
   475  
   476  	// remove ourselves from the cluster assignment for epoch 2
   477  	withoutMe := suite.identities.
   478  		Filter(filter.Not(filter.HasNodeID(suite.me.NodeID()))).
   479  		Filter(filter.HasRole(flow.RoleCollection))
   480  	epoch2Assignment := unittest.ClusterAssignment(suite.N_CLUSTERS, withoutMe)
   481  	epoch2Clusters, err := factory.NewClusterList(epoch2Assignment, withoutMe)
   482  	suite.Require().NoError(err)
   483  
   484  	epoch2 := new(protocol.Epoch)
   485  	epoch2.On("Counter").Return(uint64(2), nil)
   486  	epoch2.On("InitialIdentities").Return(withoutMe, nil)
   487  	epoch2.On("Clustering").Return(epoch2Clusters, nil)
   488  	// update the mocks to behave as though we have transitioned to epoch 2
   489  	suite.epochQuery.Add(epoch2)
   490  	suite.epochQuery.Transition()
   491  
   492  	// any transaction is OK here, since we're not in any cluster
   493  	tx := unittest.TransactionBodyFixture()
   494  	tx.ReferenceBlockID = suite.root.ID()
   495  
   496  	err = suite.engine.ProcessTransaction(&tx)
   497  	suite.Assert().Error(err)
   498  
   499  	// should not add to mempool
   500  	suite.Assert().False(suite.pools.ForEpoch(2).Has(tx.ID()))
   501  	suite.Assert().False(suite.pools.ForEpoch(1).Has(tx.ID()))
   502  	// should not propagate
   503  	suite.conduit.AssertNumberOfCalls(suite.T(), "Multicast", 0)
   504  }
   505  
   506  // The node is not a participant in epoch 2 and joins in epoch 3. We start the
   507  // test in epoch 2.
   508  //
   509  // Test that the node discards transactions in epoch 2 and handles them
   510  // in epoch 3.
   511  func (suite *Suite) TestRouting_ClusterAssignmentAdded() {
   512  
   513  	// EPOCH 2:
   514  
   515  	// remove ourselves from the cluster assignment for epoch 2
   516  	withoutMe := suite.identities.
   517  		Filter(filter.Not(filter.HasNodeID(suite.me.NodeID()))).
   518  		Filter(filter.HasRole(flow.RoleCollection))
   519  	epoch2Assignment := unittest.ClusterAssignment(suite.N_CLUSTERS, withoutMe)
   520  	epoch2Clusters, err := factory.NewClusterList(epoch2Assignment, withoutMe)
   521  	suite.Require().NoError(err)
   522  
   523  	epoch2 := new(protocol.Epoch)
   524  	epoch2.On("Counter").Return(uint64(2), nil)
   525  	epoch2.On("InitialIdentities").Return(withoutMe, nil)
   526  	epoch2.On("Clustering").Return(epoch2Clusters, nil)
   527  	// update the mocks to behave as though we have transitioned to epoch 2
   528  	suite.epochQuery.Add(epoch2)
   529  	suite.epochQuery.Transition()
   530  
   531  	// any transaction is OK here, since we're not in any cluster
   532  	tx := unittest.TransactionBodyFixture()
   533  	tx.ReferenceBlockID = suite.root.ID()
   534  
   535  	err = suite.engine.ProcessTransaction(&tx)
   536  	suite.Assert().Error(err)
   537  
   538  	// should not add to mempool
   539  	suite.Assert().False(suite.pools.ForEpoch(2).Has(tx.ID()))
   540  	suite.Assert().False(suite.pools.ForEpoch(1).Has(tx.ID()))
   541  	// should not propagate
   542  	suite.conduit.AssertNumberOfCalls(suite.T(), "Multicast", 0)
   543  
   544  	// EPOCH 3:
   545  
   546  	// include ourselves in cluster assignment
   547  	withMe := suite.identities.Filter(filter.HasRole(flow.RoleCollection))
   548  	epoch3Assignment := unittest.ClusterAssignment(suite.N_CLUSTERS, withMe)
   549  	epoch3Clusters, err := factory.NewClusterList(epoch3Assignment, withMe)
   550  	suite.Require().NoError(err)
   551  
   552  	epoch3 := new(protocol.Epoch)
   553  	epoch3.On("Counter").Return(uint64(3), nil)
   554  	epoch3.On("Clustering").Return(epoch3Clusters, nil)
   555  	// transition to epoch 3
   556  	suite.epochQuery.Add(epoch3)
   557  	suite.epochQuery.Transition()
   558  
   559  	// get the local cluster in epoch 2
   560  	epoch3Local, _, ok := epoch3Clusters.ByNodeID(suite.me.NodeID())
   561  	suite.Require().True(ok)
   562  
   563  	// get a transaction that will be routed to local cluster
   564  	tx = unittest.TransactionBodyFixture()
   565  	tx.ReferenceBlockID = suite.root.ID()
   566  	tx = unittest.AlterTransactionForCluster(tx, epoch3Clusters, epoch3Local, func(transaction *flow.TransactionBody) {})
   567  
   568  	// should route to local cluster
   569  	suite.conduit.On("Multicast", &tx, suite.conf.PropagationRedundancy+1, epoch3Local.NodeIDs()[0], epoch3Local.NodeIDs()[1]).Return(nil).Once()
   570  
   571  	err = suite.engine.ProcessTransaction(&tx)
   572  	suite.Assert().NoError(err)
   573  }