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

     1  package requester
     2  
     3  import (
     4  	"math/rand"
     5  	"testing"
     6  	"time"
     7  
     8  	"github.com/rs/zerolog"
     9  	"github.com/stretchr/testify/assert"
    10  	"github.com/stretchr/testify/mock"
    11  	"github.com/stretchr/testify/require"
    12  	"github.com/vmihailenco/msgpack"
    13  	"go.uber.org/atomic"
    14  
    15  	module "github.com/koko1123/flow-go-1/module/mock"
    16  
    17  	"github.com/koko1123/flow-go-1/engine"
    18  	"github.com/koko1123/flow-go-1/model/flow"
    19  	"github.com/koko1123/flow-go-1/model/flow/filter"
    20  	"github.com/koko1123/flow-go-1/model/messages"
    21  	"github.com/koko1123/flow-go-1/module/metrics"
    22  	"github.com/koko1123/flow-go-1/network/mocknetwork"
    23  	protocol "github.com/koko1123/flow-go-1/state/protocol/mock"
    24  	"github.com/koko1123/flow-go-1/utils/unittest"
    25  )
    26  
    27  func TestEntityByID(t *testing.T) {
    28  
    29  	request := Engine{
    30  		unit:  engine.NewUnit(),
    31  		items: make(map[flow.Identifier]*Item),
    32  		rng:   rand.New(rand.NewSource(0)),
    33  	}
    34  
    35  	now := time.Now().UTC()
    36  
    37  	entityID := unittest.IdentifierFixture()
    38  	selector := filter.Any
    39  	request.EntityByID(entityID, selector)
    40  
    41  	assert.Len(t, request.items, 1)
    42  	item, contains := request.items[entityID]
    43  	if assert.True(t, contains) {
    44  		assert.Equal(t, item.EntityID, entityID)
    45  		assert.Equal(t, item.NumAttempts, uint(0))
    46  		cutoff := item.LastRequested.Add(item.RetryAfter)
    47  		assert.True(t, cutoff.Before(now)) // make sure we push out immediately
    48  	}
    49  }
    50  
    51  func TestDispatchRequestVarious(t *testing.T) {
    52  
    53  	identities := unittest.IdentityListFixture(16)
    54  	targetID := identities[0].NodeID
    55  
    56  	final := &protocol.Snapshot{}
    57  	final.On("Identities", mock.Anything).Return(
    58  		func(selector flow.IdentityFilter) flow.IdentityList {
    59  			return identities.Filter(selector)
    60  		},
    61  		nil,
    62  	)
    63  
    64  	state := &protocol.State{}
    65  	state.On("Final").Return(final)
    66  
    67  	cfg := Config{
    68  		BatchInterval:  200 * time.Millisecond,
    69  		BatchThreshold: 999,
    70  		RetryInitial:   100 * time.Millisecond,
    71  		RetryFunction:  RetryLinear(10 * time.Millisecond),
    72  		RetryAttempts:  2,
    73  		RetryMaximum:   300 * time.Millisecond,
    74  	}
    75  
    76  	// item that has just been added, should be included
    77  	justAdded := &Item{
    78  		EntityID:      unittest.IdentifierFixture(),
    79  		NumAttempts:   0,
    80  		LastRequested: time.Time{},
    81  		RetryAfter:    cfg.RetryInitial,
    82  		ExtraSelector: filter.Any,
    83  	}
    84  
    85  	// item was tried long time ago, should be included
    86  	triedAnciently := &Item{
    87  		EntityID:      unittest.IdentifierFixture(),
    88  		NumAttempts:   1,
    89  		LastRequested: time.Now().UTC().Add(-cfg.RetryMaximum),
    90  		RetryAfter:    cfg.RetryFunction(cfg.RetryInitial),
    91  		ExtraSelector: filter.Any,
    92  	}
    93  
    94  	// item that was just tried, should be excluded
    95  	triedRecently := &Item{
    96  		EntityID:      unittest.IdentifierFixture(),
    97  		NumAttempts:   1,
    98  		LastRequested: time.Now().UTC(),
    99  		RetryAfter:    cfg.RetryFunction(cfg.RetryInitial),
   100  	}
   101  
   102  	// item was tried twice, should be excluded
   103  	triedTwice := &Item{
   104  		EntityID:      unittest.IdentifierFixture(),
   105  		NumAttempts:   2,
   106  		LastRequested: time.Time{},
   107  		RetryAfter:    cfg.RetryInitial,
   108  		ExtraSelector: filter.Any,
   109  	}
   110  
   111  	items := make(map[flow.Identifier]*Item)
   112  	items[justAdded.EntityID] = justAdded
   113  	items[triedAnciently.EntityID] = triedAnciently
   114  	items[triedRecently.EntityID] = triedRecently
   115  	items[triedTwice.EntityID] = triedTwice
   116  
   117  	var nonce uint64
   118  
   119  	con := &mocknetwork.Conduit{}
   120  	con.On("Unicast", mock.Anything, mock.Anything).Run(
   121  		func(args mock.Arguments) {
   122  			request := args.Get(0).(*messages.EntityRequest)
   123  			originID := args.Get(1).(flow.Identifier)
   124  			nonce = request.Nonce
   125  			assert.Equal(t, originID, targetID)
   126  			assert.ElementsMatch(t, request.EntityIDs, []flow.Identifier{justAdded.EntityID, triedAnciently.EntityID})
   127  		},
   128  	).Return(nil)
   129  
   130  	request := Engine{
   131  		unit:     engine.NewUnit(),
   132  		metrics:  metrics.NewNoopCollector(),
   133  		cfg:      cfg,
   134  		state:    state,
   135  		con:      con,
   136  		items:    items,
   137  		requests: make(map[uint64]*messages.EntityRequest),
   138  		selector: filter.HasNodeID(targetID),
   139  		rng:      rand.New(rand.NewSource(0)),
   140  	}
   141  	dispatched, err := request.dispatchRequest()
   142  	require.NoError(t, err)
   143  	require.True(t, dispatched)
   144  
   145  	con.AssertExpectations(t)
   146  
   147  	request.unit.Lock()
   148  	assert.Contains(t, request.requests, nonce)
   149  	request.unit.Unlock()
   150  
   151  	// TODO: racy/slow test
   152  	time.Sleep(2 * cfg.RetryInitial)
   153  
   154  	request.unit.Lock()
   155  	assert.NotContains(t, request.requests, nonce)
   156  	request.unit.Unlock()
   157  }
   158  
   159  func TestDispatchRequestBatchSize(t *testing.T) {
   160  
   161  	batchLimit := uint(16)
   162  	totalItems := uint(99)
   163  
   164  	identities := unittest.IdentityListFixture(16)
   165  
   166  	final := &protocol.Snapshot{}
   167  	final.On("Identities", mock.Anything).Return(
   168  		func(selector flow.IdentityFilter) flow.IdentityList {
   169  			return identities.Filter(selector)
   170  		},
   171  		nil,
   172  	)
   173  
   174  	state := &protocol.State{}
   175  	state.On("Final").Return(final)
   176  
   177  	cfg := Config{
   178  		BatchInterval:  24 * time.Hour,
   179  		BatchThreshold: batchLimit,
   180  		RetryInitial:   24 * time.Hour,
   181  		RetryFunction:  RetryLinear(1),
   182  		RetryAttempts:  1,
   183  		RetryMaximum:   24 * time.Hour,
   184  	}
   185  
   186  	// item that has just been added, should be included
   187  	items := make(map[flow.Identifier]*Item)
   188  	for i := uint(0); i < totalItems; i++ {
   189  		item := &Item{
   190  			EntityID:      unittest.IdentifierFixture(),
   191  			NumAttempts:   0,
   192  			LastRequested: time.Time{},
   193  			RetryAfter:    cfg.RetryInitial,
   194  			ExtraSelector: filter.Any,
   195  		}
   196  		items[item.EntityID] = item
   197  	}
   198  
   199  	con := &mocknetwork.Conduit{}
   200  	con.On("Unicast", mock.Anything, mock.Anything).Run(
   201  		func(args mock.Arguments) {
   202  			request := args.Get(0).(*messages.EntityRequest)
   203  			assert.Len(t, request.EntityIDs, int(batchLimit))
   204  		},
   205  	).Return(nil)
   206  
   207  	request := Engine{
   208  		unit:     engine.NewUnit(),
   209  		metrics:  metrics.NewNoopCollector(),
   210  		cfg:      cfg,
   211  		state:    state,
   212  		con:      con,
   213  		items:    items,
   214  		requests: make(map[uint64]*messages.EntityRequest),
   215  		selector: filter.Any,
   216  		rng:      rand.New(rand.NewSource(0)),
   217  	}
   218  	dispatched, err := request.dispatchRequest()
   219  	require.NoError(t, err)
   220  	require.True(t, dispatched)
   221  
   222  	con.AssertExpectations(t)
   223  }
   224  
   225  func TestOnEntityResponseValid(t *testing.T) {
   226  
   227  	identities := unittest.IdentityListFixture(16)
   228  	targetID := identities[0].NodeID
   229  
   230  	final := &protocol.Snapshot{}
   231  	final.On("Identities", mock.Anything).Return(
   232  		func(selector flow.IdentityFilter) flow.IdentityList {
   233  			return identities.Filter(selector)
   234  		},
   235  		nil,
   236  	)
   237  
   238  	state := &protocol.State{}
   239  	state.On("Final").Return(final)
   240  
   241  	nonce := rand.Uint64()
   242  
   243  	wanted1 := unittest.CollectionFixture(1)
   244  	wanted2 := unittest.CollectionFixture(2)
   245  	unavailable := unittest.CollectionFixture(3)
   246  	unwanted := unittest.CollectionFixture(4)
   247  
   248  	now := time.Now()
   249  
   250  	iwanted1 := &Item{
   251  		EntityID:      wanted1.ID(),
   252  		LastRequested: now,
   253  		ExtraSelector: filter.Any,
   254  	}
   255  	iwanted2 := &Item{
   256  		EntityID:      wanted2.ID(),
   257  		LastRequested: now,
   258  		ExtraSelector: filter.Any,
   259  	}
   260  	iunavailable := &Item{
   261  		EntityID:      unavailable.ID(),
   262  		LastRequested: now,
   263  		ExtraSelector: filter.Any,
   264  	}
   265  
   266  	bwanted1, _ := msgpack.Marshal(wanted1)
   267  	bwanted2, _ := msgpack.Marshal(wanted2)
   268  	bunwanted, _ := msgpack.Marshal(unwanted)
   269  
   270  	res := &messages.EntityResponse{
   271  		Nonce:     nonce,
   272  		EntityIDs: []flow.Identifier{wanted1.ID(), wanted2.ID(), unwanted.ID()},
   273  		Blobs:     [][]byte{bwanted1, bwanted2, bunwanted},
   274  	}
   275  
   276  	req := &messages.EntityRequest{
   277  		Nonce:     nonce,
   278  		EntityIDs: []flow.Identifier{wanted1.ID(), wanted2.ID(), unavailable.ID()},
   279  	}
   280  
   281  	done := make(chan struct{})
   282  	called := *atomic.NewUint64(0)
   283  	request := Engine{
   284  		unit:     engine.NewUnit(),
   285  		metrics:  metrics.NewNoopCollector(),
   286  		state:    state,
   287  		items:    make(map[flow.Identifier]*Item),
   288  		requests: make(map[uint64]*messages.EntityRequest),
   289  		selector: filter.HasNodeID(targetID),
   290  		create:   func() flow.Entity { return &flow.Collection{} },
   291  		handle: func(flow.Identifier, flow.Entity) {
   292  			if called.Inc() >= 2 {
   293  				close(done)
   294  			}
   295  		},
   296  		rng: rand.New(rand.NewSource(0)),
   297  	}
   298  
   299  	request.items[iwanted1.EntityID] = iwanted1
   300  	request.items[iwanted2.EntityID] = iwanted2
   301  	request.items[iunavailable.EntityID] = iunavailable
   302  
   303  	request.requests[req.Nonce] = req
   304  
   305  	err := request.onEntityResponse(targetID, res)
   306  	assert.NoError(t, err)
   307  
   308  	// check that the request was removed
   309  	assert.NotContains(t, request.requests, nonce)
   310  
   311  	// check that the provided items were removed
   312  	assert.NotContains(t, request.items, wanted1.ID())
   313  	assert.NotContains(t, request.items, wanted2.ID())
   314  
   315  	// check that the missing item is still there
   316  	assert.Contains(t, request.items, unavailable.ID())
   317  
   318  	// make sure we processed two items
   319  	unittest.AssertClosesBefore(t, done, time.Second)
   320  
   321  	// check that the missing items timestamp was reset
   322  	assert.Equal(t, iunavailable.LastRequested, time.Time{})
   323  }
   324  
   325  func TestOnEntityIntegrityCheck(t *testing.T) {
   326  	identities := unittest.IdentityListFixture(16)
   327  	targetID := identities[0].NodeID
   328  
   329  	final := &protocol.Snapshot{}
   330  	final.On("Identities", mock.Anything).Return(
   331  		func(selector flow.IdentityFilter) flow.IdentityList {
   332  			return identities.Filter(selector)
   333  		},
   334  		nil,
   335  	)
   336  
   337  	state := &protocol.State{}
   338  	state.On("Final").Return(final)
   339  
   340  	nonce := rand.Uint64()
   341  
   342  	wanted := unittest.CollectionFixture(1)
   343  	wanted2 := unittest.CollectionFixture(2)
   344  
   345  	now := time.Now()
   346  
   347  	iwanted := &Item{
   348  		EntityID:       wanted.ID(),
   349  		LastRequested:  now,
   350  		ExtraSelector:  filter.Any,
   351  		checkIntegrity: true,
   352  	}
   353  
   354  	assert.NotEqual(t, wanted, wanted2)
   355  
   356  	// prepare payload from different entity
   357  	bwanted, _ := msgpack.Marshal(wanted2)
   358  
   359  	res := &messages.EntityResponse{
   360  		Nonce:     nonce,
   361  		EntityIDs: []flow.Identifier{wanted.ID()},
   362  		Blobs:     [][]byte{bwanted},
   363  	}
   364  
   365  	req := &messages.EntityRequest{
   366  		Nonce:     nonce,
   367  		EntityIDs: []flow.Identifier{wanted.ID()},
   368  	}
   369  
   370  	called := make(chan struct{})
   371  	request := Engine{
   372  		unit:     engine.NewUnit(),
   373  		metrics:  metrics.NewNoopCollector(),
   374  		state:    state,
   375  		items:    make(map[flow.Identifier]*Item),
   376  		requests: make(map[uint64]*messages.EntityRequest),
   377  		selector: filter.HasNodeID(targetID),
   378  		create:   func() flow.Entity { return &flow.Collection{} },
   379  		handle:   func(flow.Identifier, flow.Entity) { close(called) },
   380  		rng:      rand.New(rand.NewSource(0)),
   381  	}
   382  
   383  	request.items[iwanted.EntityID] = iwanted
   384  
   385  	request.requests[req.Nonce] = req
   386  
   387  	err := request.onEntityResponse(targetID, res)
   388  	assert.NoError(t, err)
   389  
   390  	// check that the request was removed
   391  	assert.NotContains(t, request.requests, nonce)
   392  
   393  	// check that the provided item wasn't removed
   394  	assert.Contains(t, request.items, wanted.ID())
   395  
   396  	iwanted.checkIntegrity = false
   397  	request.items[iwanted.EntityID] = iwanted
   398  	request.requests[req.Nonce] = req
   399  
   400  	err = request.onEntityResponse(targetID, res)
   401  	assert.NoError(t, err)
   402  
   403  	// make sure we process item without checking integrity
   404  	unittest.AssertClosesBefore(t, called, time.Second)
   405  }
   406  
   407  // Verify that the origin should not be checked when ValidateStaking config is set to false
   408  func TestOriginValidation(t *testing.T) {
   409  	identities := unittest.IdentityListFixture(16)
   410  	targetID := identities[0].NodeID
   411  	wrongID := identities[1].NodeID
   412  	meID := identities[3].NodeID
   413  
   414  	final := &protocol.Snapshot{}
   415  	final.On("Identities", mock.Anything).Return(
   416  		func(selector flow.IdentityFilter) flow.IdentityList {
   417  			return identities.Filter(selector)
   418  		},
   419  		nil,
   420  	)
   421  
   422  	state := &protocol.State{}
   423  	state.On("Final").Return(final)
   424  
   425  	me := &module.Local{}
   426  
   427  	me.On("NodeID").Return(meID)
   428  
   429  	nonce := rand.Uint64()
   430  
   431  	wanted := unittest.CollectionFixture(1)
   432  
   433  	now := time.Now()
   434  
   435  	iwanted := &Item{
   436  		EntityID:       wanted.ID(),
   437  		LastRequested:  now,
   438  		ExtraSelector:  filter.HasNodeID(targetID),
   439  		checkIntegrity: true,
   440  	}
   441  
   442  	// prepare payload
   443  	bwanted, _ := msgpack.Marshal(wanted)
   444  
   445  	res := &messages.EntityResponse{
   446  		Nonce:     nonce,
   447  		EntityIDs: []flow.Identifier{wanted.ID()},
   448  		Blobs:     [][]byte{bwanted},
   449  	}
   450  
   451  	req := &messages.EntityRequest{
   452  		Nonce:     nonce,
   453  		EntityIDs: []flow.Identifier{wanted.ID()},
   454  	}
   455  
   456  	network := &mocknetwork.Network{}
   457  	network.On("Register", mock.Anything, mock.Anything).Return(nil, nil)
   458  
   459  	e, err := New(
   460  		zerolog.Nop(),
   461  		metrics.NewNoopCollector(),
   462  		network,
   463  		me,
   464  		state,
   465  		"",
   466  		filter.HasNodeID(targetID),
   467  		func() flow.Entity { return &flow.Collection{} },
   468  	)
   469  	assert.NoError(t, err)
   470  
   471  	called := make(chan struct{})
   472  
   473  	e.WithHandle(func(origin flow.Identifier, _ flow.Entity) {
   474  		// we expect wrong origin to propagate here with validation disabled
   475  		assert.Equal(t, wrongID, origin)
   476  		close(called)
   477  	})
   478  
   479  	e.items[iwanted.EntityID] = iwanted
   480  	e.requests[req.Nonce] = req
   481  
   482  	err = e.onEntityResponse(wrongID, res)
   483  	assert.Error(t, err)
   484  	assert.IsType(t, engine.InvalidInputError{}, err)
   485  
   486  	e.cfg.ValidateStaking = false
   487  
   488  	err = e.onEntityResponse(wrongID, res)
   489  	assert.NoError(t, err)
   490  
   491  	// handler are called async, but this should be extremely quick
   492  	unittest.AssertClosesBefore(t, called, time.Second)
   493  }